9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-19 15:09:15 +00:00

Merge branch 'Xiao-MoMi:dev' into dev

This commit is contained in:
jhqwqmc
2025-11-29 00:09:40 +08:00
committed by GitHub
77 changed files with 1362 additions and 627 deletions

View File

@@ -42,7 +42,7 @@ public class MythicItemDrop extends ItemDrop implements IItemDrop {
context = ItemBuildContext.of(player); 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); ItemStack itemStack = this.customItem.buildItemStack(context, amountInt);
return adapt(itemStack).amount(amountInt); return adapt(itemStack).amount(amountInt);
} }

View File

@@ -42,6 +42,7 @@ import java.util.*;
public final class BukkitBlockManager extends AbstractBlockManager { public final class BukkitBlockManager extends AbstractBlockManager {
public static final Set<Object> CLIENT_SIDE_NOTE_BLOCKS = new HashSet<>(2048, 0.6f); public static final Set<Object> 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_FALSE = FastNMS.INSTANCE.method$StatePredicate$always(false);
private static final Object ALWAYS_TRUE = FastNMS.INSTANCE.method$StatePredicate$always(true); private static final Object ALWAYS_TRUE = FastNMS.INSTANCE.method$StatePredicate$always(true);
private static BukkitBlockManager instance; private static BukkitBlockManager instance;
@@ -80,12 +81,14 @@ public final class BukkitBlockManager extends AbstractBlockManager {
@Override @Override
public void init() { public void init() {
super.init();
this.initMirrorRegistry(); this.initMirrorRegistry();
this.initFireBlock(); this.initFireBlock();
this.deceiveBukkitRegistry(); this.deceiveBukkitRegistry();
this.markVanillaNoteBlocks(); this.markVanillaNoteBlocks();
this.findViewBlockingVanillaBlocks();
Arrays.fill(this.immutableBlockStates, EmptyBlock.INSTANCE.defaultState()); 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() { public static BukkitBlockManager instance() {
@@ -125,7 +128,7 @@ public final class BukkitBlockManager extends AbstractBlockManager {
@Override @Override
public void delayedLoad() { public void delayedLoad() {
this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings); // 重置方块映射表 this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings, this::isViewBlockingBlock); // 重置方块映射表
super.delayedLoad(); super.delayedLoad();
this.cachedVisualBlockStatePacket = VisualBlockStatePacket.create(); this.cachedVisualBlockStatePacket = VisualBlockStatePacket.create();
for (BukkitServerPlayer player : BukkitNetworkManager.instance().onlineUsers()) { for (BukkitServerPlayer player : BukkitNetworkManager.instance().onlineUsers()) {
@@ -223,7 +226,7 @@ public final class BukkitBlockManager extends AbstractBlockManager {
protected void applyPlatformSettings(ImmutableBlockState state) { protected void applyPlatformSettings(ImmutableBlockState state) {
DelegatingBlockState nmsState = (DelegatingBlockState) state.customBlockState().literalObject(); DelegatingBlockState nmsState = (DelegatingBlockState) state.customBlockState().literalObject();
nmsState.setBlockState(state); nmsState.setBlockState(state);
Object nmsVisualState = state.vanillaBlockState().literalObject(); Object nmsVisualState = state.visualBlockState().literalObject();
BlockSettings settings = state.settings(); BlockSettings settings = state.settings();
try { try {
@@ -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(); 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$useShapeForLightOcclusion.set(nmsState, useShapeForLightOcclusion);
CoreReflections.field$BlockStateBase$isRedstoneConductor.set(nmsState, settings.isRedstoneConductor().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE); 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$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); DelegatingBlock nmsBlock = (DelegatingBlock) BlockStateUtils.getBlockOwner(nmsState);
ObjectHolder<BlockShape> shapeHolder = nmsBlock.shapeDelegate(); ObjectHolder<BlockShape> shapeHolder = nmsBlock.shapeDelegate();
@@ -291,9 +301,16 @@ public final class BukkitBlockManager extends AbstractBlockManager {
this.burnableBlocks.add(nmsBlock); 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$isNoteBlock().set(nmsBlock, vanillaBlockId.equals(BlockKeys.NOTE_BLOCK));
BlockGenerator.field$CraftEngineBlock$isTripwire().set(nmsBlock, vanillaBlockId.equals(BlockKeys.TRIPWIRE)); 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());
}
// 根据客户端的状态决定其是否阻挡视线
super.viewBlockingBlocks[state.customBlockState().registryId()] = canBlockView(state.visualBlockState());
} catch (ReflectiveOperationException e) { } catch (ReflectiveOperationException e) {
this.plugin.logger().warn("Failed to apply platform block settings for block state " + state, e); this.plugin.logger().warn("Failed to apply platform block settings for block state " + state, e);
} }
@@ -379,6 +396,23 @@ public final class BukkitBlockManager extends AbstractBlockManager {
} }
} }
public boolean canBlockView(BlockStateWrapper wrapper) {
Object blockState = wrapper.literalObject();
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 @Override
protected void setVanillaBlockTags(Key id, List<String> tags) { protected void setVanillaBlockTags(Key id, List<String> tags) {
Object block = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, KeyUtils.toResourceLocation(id)); Object block = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, KeyUtils.toResourceLocation(id));

View File

@@ -20,7 +20,7 @@ public class BukkitCustomBlockStateWrapper extends AbstractBlockStateWrapper imp
@Override @Override
public BlockStateWrapper visualBlockState() { public BlockStateWrapper visualBlockState() {
return getImmutableBlockState().map(ImmutableBlockState::vanillaBlockState).orElse(null); return getImmutableBlockState().map(ImmutableBlockState::visualBlockState).orElse(null);
} }
@Override @Override

View File

@@ -180,7 +180,7 @@ public class BukkitBlockBehavior extends AbstractBlockBehavior {
public boolean isPathFindable(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception { public boolean isPathFindable(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Optional<ImmutableBlockState> optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(args[0]); Optional<ImmutableBlockState> optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(args[0]);
if (optionalCustomState.isEmpty()) return false; if (optionalCustomState.isEmpty()) return false;
BlockStateWrapper vanillaState = optionalCustomState.get().vanillaBlockState(); BlockStateWrapper vanillaState = optionalCustomState.get().visualBlockState();
if (vanillaState == null) return false; 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]); 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]);
} }

View File

@@ -135,7 +135,7 @@ public class CropBlockBehavior extends BukkitBlockBehavior {
if (isMaxAge(state)) if (isMaxAge(state))
return InteractionResult.PASS; return InteractionResult.PASS;
boolean sendSwing = false; boolean sendSwing = false;
Object visualState = state.vanillaBlockState().literalObject(); Object visualState = state.visualBlockState().literalObject();
Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState);
if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) {
boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState); 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(); ImmutableBlockState customState = optionalCustomState.get();
boolean sendParticles = false; boolean sendParticles = false;
Object visualState = customState.vanillaBlockState().literalObject(); Object visualState = customState.visualBlockState().literalObject();
Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState);
if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) {
boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, level, pos, visualState); boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, level, pos, visualState);

View File

@@ -147,7 +147,7 @@ public class FenceGateBlockBehavior extends BukkitBlockBehavior implements IsPat
Player player = context.getPlayer(); Player player = context.getPlayer();
if (player == null) return; if (player == null) return;
this.toggle(state, context.getLevel(), context.getClickedPos(), player); 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<ItemStack>) context.getItem())) { if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.visualBlockState().literalObject()), context.getHitResult(), (Item<ItemStack>) context.getItem())) {
player.swingHand(context.getHand()); player.swingHand(context.getHand());
} }
} }

View File

@@ -60,7 +60,7 @@ public class GrassBlockBehavior extends BukkitBlockBehavior {
} }
boolean sendParticles = false; boolean sendParticles = false;
ImmutableBlockState customState = optionalCustomState.get(); ImmutableBlockState customState = optionalCustomState.get();
Object visualState = customState.vanillaBlockState().literalObject(); Object visualState = customState.visualBlockState().literalObject();
Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState);
if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) {
boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, level, blockPos, visualState); boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, level, blockPos, visualState);
@@ -93,7 +93,7 @@ public class GrassBlockBehavior extends BukkitBlockBehavior {
if (!block.isEmpty()) if (!block.isEmpty())
return InteractionResult.PASS; return InteractionResult.PASS;
boolean sendSwing = false; boolean sendSwing = false;
Object visualState = state.vanillaBlockState().literalObject(); Object visualState = state.visualBlockState().literalObject();
Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState);
if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) {
boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState); boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState);

View File

@@ -115,7 +115,7 @@ public class SaplingBlockBehavior extends BukkitBlockBehavior {
} }
ImmutableBlockState customState = optionalCustomState.get(); ImmutableBlockState customState = optionalCustomState.get();
boolean sendParticles = false; boolean sendParticles = false;
Object visualState = customState.vanillaBlockState().literalObject(); Object visualState = customState.visualBlockState().literalObject();
Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState);
if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) {
boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, level, blockPos, visualState); 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()) if (ItemUtils.isEmpty(item) || !item.vanillaId().equals(ItemKeys.BONE_MEAL) || player == null || player.isAdventureMode())
return InteractionResult.PASS; return InteractionResult.PASS;
boolean sendSwing = false; boolean sendSwing = false;
Object visualState = state.vanillaBlockState().literalObject(); Object visualState = state.visualBlockState().literalObject();
Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState); Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState);
if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) { if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) {
boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState); boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState);

View File

@@ -33,7 +33,7 @@ public class SeatBlockBehavior extends BukkitBlockBehavior implements EntityBloc
@Override @Override
public BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state) { public BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state) {
return new SeatBlockEntity(pos, state, this.seats); return new SeatBlockEntity(pos, state, this.seats, this.directionProperty);
} }
@Override @Override

View File

@@ -120,7 +120,7 @@ public class TrapDoorBlockBehavior extends BukkitBlockBehavior implements IsPath
Player player = context.getPlayer(); Player player = context.getPlayer();
if (player == null) return; if (player == null) return;
this.toggle(state, context.getLevel(), context.getClickedPos(), player); 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<ItemStack>) context.getItem())) { if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.visualBlockState().literalObject()), context.getHitResult(), (Item<ItemStack>) context.getItem())) {
player.swingHand(context.getHand()); player.swingHand(context.getHand());
} }
} }

View File

@@ -15,10 +15,12 @@ import net.momirealms.sparrow.nbt.CompoundTag;
public class SeatBlockEntity extends BlockEntity implements SeatOwner { public class SeatBlockEntity extends BlockEntity implements SeatOwner {
private final Seat<SeatBlockEntity>[] seats; private final Seat<SeatBlockEntity>[] seats;
private final Property<HorizontalDirection> facing;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public SeatBlockEntity(BlockPos pos, ImmutableBlockState blockState, SeatConfig[] seats) { public SeatBlockEntity(BlockPos pos, ImmutableBlockState blockState, SeatConfig[] seats, Property<HorizontalDirection> directionProperty) {
super(BukkitBlockEntityTypes.SEAT, pos, blockState); super(BukkitBlockEntityTypes.SEAT, pos, blockState);
this.facing = directionProperty;
this.seats = new Seat[seats.length]; this.seats = new Seat[seats.length];
for (int i = 0; i < seats.length; i++) { for (int i = 0; i < seats.length; i++) {
this.seats[i] = new BukkitSeat<>(this, seats[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) { public boolean spawnSeat(Player player) {
Property<?> facing = super.blockState.owner().value().getProperty("facing");
int yRot = 0; int yRot = 0;
if (facing != null && facing.valueClass() == HorizontalDirection.class) { if (this.facing != null) {
HorizontalDirection direction = (HorizontalDirection) super.blockState.get(facing); HorizontalDirection direction = super.blockState.get(facing);
yRot = switch (direction) { yRot = switch (direction) {
case NORTH -> 0; case NORTH -> 0;
case SOUTH -> 180; case SOUTH -> 180;

View File

@@ -61,6 +61,7 @@ public class BukkitSeatManager implements SeatManager, Listener {
@Override @Override
public void delayedInit() { public void delayedInit() {
Bukkit.getPluginManager().registerEvents(this.dismountListener, this.plugin.javaPlugin()); Bukkit.getPluginManager().registerEvents(this.dismountListener, this.plugin.javaPlugin());
Bukkit.getPluginManager().registerEvents(this, this.plugin.javaPlugin());
} }
@Override @Override

View File

@@ -101,7 +101,7 @@ public class AxeItemBehavior extends ItemBehavior {
// resend swing if it's not interactable on client side // resend swing if it's not interactable on client side
if (!InteractUtils.isInteractable( if (!InteractUtils.isInteractable(
bukkitPlayer, BlockStateUtils.fromBlockData(customState.vanillaBlockState().literalObject()), bukkitPlayer, BlockStateUtils.fromBlockData(customState.visualBlockState().literalObject()),
context.getHitResult(), item context.getHitResult(), item
) || player.isSecondaryUseActive()) { ) || player.isSecondaryUseActive()) {
player.swingHand(context.getHand()); player.swingHand(context.getHand());

View File

@@ -112,7 +112,7 @@ public class BlockItemBehavior extends BlockBoundItemBehavior {
} else { } else {
ImmutableBlockState customState = optionalCustomState.get(); ImmutableBlockState customState = optionalCustomState.get();
// custom block // 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; return InteractionResult.FAIL;
} }
} }

View File

@@ -77,10 +77,10 @@ public class FlintAndSteelItemBehavior extends ItemBehavior {
// 点击对象为自定义方块 // 点击对象为自定义方块
ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId); ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId);
// 原版外观也可燃 // 原版外观也可燃
if (BlockStateUtils.isBurnable(immutableBlockState.vanillaBlockState().literalObject())) { if (BlockStateUtils.isBurnable(immutableBlockState.visualBlockState().literalObject())) {
return InteractionResult.PASS; return InteractionResult.PASS;
} }
BlockData vanillaBlockState = BlockStateUtils.fromBlockData(immutableBlockState.vanillaBlockState().literalObject()); BlockData vanillaBlockState = BlockStateUtils.fromBlockData(immutableBlockState.visualBlockState().literalObject());
// 点击的是方块上面则只需要判断shift和可交互 // 点击的是方块上面则只需要判断shift和可交互
if (direction == Direction.UP) { if (direction == Direction.UP) {
// 客户端层面必须可交互 // 客户端层面必须可交互

View File

@@ -167,7 +167,7 @@ public class ItemEventListener implements Listener {
// fix client side issues // fix client side issues
if (action.isRightClick() && hitResult != null && 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(); player.updateInventory();
} }
@@ -272,13 +272,13 @@ public class ItemEventListener implements Listener {
if (immutableBlockState != null) { if (immutableBlockState != null) {
// client won't have sounds if the clientside block is interactable // client won't have sounds if the clientside block is interactable
// so we should check and resend sounds on BlockPlaceEvent // 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 (InteractUtils.isInteractable(player, craftBlockData, hitResult, itemInHand)) {
if (!serverPlayer.isSecondaryUseActive()) { if (!serverPlayer.isSecondaryUseActive()) {
serverPlayer.setResendSound(); serverPlayer.setResendSound();
} }
} else { } 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(); serverPlayer.setResendSwing();
} }
} }

View File

@@ -78,7 +78,7 @@ public class DebugCleanCacheCommand extends BukkitCommandFeature<CommandSender>
Set<BlockStateWrapper> ids = new HashSet<>(); Set<BlockStateWrapper> ids = new HashSet<>();
for (CustomBlock customBlock : instance.loadedBlocks().values()) { for (CustomBlock customBlock : instance.loadedBlocks().values()) {
for (ImmutableBlockState state : customBlock.variantProvider().states()) { for (ImmutableBlockState state : customBlock.variantProvider().states()) {
ids.add(state.vanillaBlockState()); ids.add(state.visualBlockState());
} }
} }
VisualBlockStateAllocator visualBlockStateAllocator = instance.blockParser().visualBlockStateAllocator(); VisualBlockStateAllocator visualBlockStateAllocator = instance.blockParser().visualBlockStateAllocator();

View File

@@ -283,10 +283,10 @@ public final class WorldStorageInjector {
if (Config.enableLightSystem()) { if (Config.enableLightSystem()) {
if (previousImmutableBlockState.isEmpty()) { if (previousImmutableBlockState.isEmpty()) {
// 原版块到自定义块,只需要判断新块是否和客户端视觉一致 // 原版块到自定义块,只需要判断新块是否和客户端视觉一致
updateLight(holder, newImmutableBlockState.vanillaBlockState().literalObject(), newState, x, y, z); updateLight(holder, newImmutableBlockState.visualBlockState().literalObject(), newState, x, y, z);
} else { } else {
// 自定义块到自定义块 // 自定义块到自定义块
updateLight$complex(holder, newImmutableBlockState.vanillaBlockState().literalObject(), newState, previousState, x, y, z); updateLight$complex(holder, newImmutableBlockState.visualBlockState().literalObject(), newState, previousState, x, y, z);
} }
} }
} else { } else {
@@ -311,7 +311,7 @@ public final class WorldStorageInjector {
} }
if (Config.enableLightSystem()) { if (Config.enableLightSystem()) {
// 自定义块到原版块,只需要判断旧块是否和客户端一直 // 自定义块到原版块,只需要判断旧块是否和客户端一直
BlockStateWrapper wrapper = previous.vanillaBlockState(); BlockStateWrapper wrapper = previous.visualBlockState();
if (wrapper != null) { if (wrapper != null) {
updateLight(holder, wrapper.literalObject(), previousState, x, y, z); updateLight(holder, wrapper.literalObject(), previousState, x, y, z);
} }

View File

@@ -23,6 +23,7 @@ import net.kyori.adventure.nbt.api.BinaryTagHolder;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.DataComponentValue; import net.kyori.adventure.text.event.DataComponentValue;
import net.kyori.adventure.text.event.HoverEvent; 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.CraftEngineBlocks;
import net.momirealms.craftengine.bukkit.api.CraftEngineFurniture; import net.momirealms.craftengine.bukkit.api.CraftEngineFurniture;
import net.momirealms.craftengine.bukkit.api.event.FurnitureAttemptBreakEvent; import net.momirealms.craftengine.bukkit.api.event.FurnitureAttemptBreakEvent;
@@ -91,9 +92,12 @@ import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider;
import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.util.*;
import net.momirealms.craftengine.core.world.*; import net.momirealms.craftengine.core.world.*;
import net.momirealms.craftengine.core.world.chunk.CEChunk; 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.Palette;
import net.momirealms.craftengine.core.world.chunk.PalettedContainer; 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.BlockEntityData;
import net.momirealms.craftengine.core.world.chunk.packet.MCSection; import net.momirealms.craftengine.core.world.chunk.packet.MCSection;
import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.CompoundTag;
@@ -127,6 +131,7 @@ import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Predicate;
public class BukkitNetworkManager implements NetworkManager, Listener, PluginMessageListener { public class BukkitNetworkManager implements NetworkManager, Listener, PluginMessageListener {
private static BukkitNetworkManager instance; private static BukkitNetworkManager instance;
@@ -290,7 +295,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
} }
} }
public void registerBlockStatePacketListeners(int[] blockStateMappings) { public void registerBlockStatePacketListeners(int[] blockStateMappings, Predicate<Integer> occlusionPredicate) {
int stoneId = BlockStateUtils.blockStateToId(MBlocks.STONE$defaultState); int stoneId = BlockStateUtils.blockStateToId(MBlocks.STONE$defaultState);
int vanillaBlocks = BlockStateUtils.vanillaBlockStateCount(); int vanillaBlocks = BlockStateUtils.vanillaBlockStateCount();
int[] newMappings = new int[blockStateMappings.length]; int[] newMappings = new int[blockStateMappings.length];
@@ -320,10 +325,11 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
newMappings, newMappings,
newMappingsMOD, newMappingsMOD,
newMappings.length, newMappings.length,
RegistryUtils.currentBiomeRegistrySize() RegistryUtils.currentBiomeRegistrySize(),
occlusionPredicate
), this.packetIds.clientboundLevelChunkWithLightPacket(), "ClientboundLevelChunkWithLightPacket"); ), this.packetIds.clientboundLevelChunkWithLightPacket(), "ClientboundLevelChunkWithLightPacket");
registerS2CGamePacketListener(new SectionBlockUpdateListener(newMappings, newMappingsMOD), this.packetIds.clientboundSectionBlocksUpdatePacket(), "ClientboundSectionBlocksUpdatePacket"); registerS2CGamePacketListener(new SectionBlockUpdateListener(newMappings, newMappingsMOD, occlusionPredicate), this.packetIds.clientboundSectionBlocksUpdatePacket(), "ClientboundSectionBlocksUpdatePacket");
registerS2CGamePacketListener(new BlockUpdateListener(newMappings, newMappingsMOD), this.packetIds.clientboundBlockUpdatePacket(), "ClientboundBlockUpdatePacket"); registerS2CGamePacketListener(new BlockUpdateListener(newMappings, newMappingsMOD, occlusionPredicate), this.packetIds.clientboundBlockUpdatePacket(), "ClientboundBlockUpdatePacket");
registerS2CGamePacketListener( registerS2CGamePacketListener(
VersionHelper.isOrAbove1_21_4() ? VersionHelper.isOrAbove1_21_4() ?
new LevelParticleListener1_21_4(newMappings, newMappingsMOD) : new LevelParticleListener1_21_4(newMappings, newMappingsMOD) :
@@ -1098,7 +1104,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
if (player.isAdventureMode()) { if (player.isAdventureMode()) {
if (Config.simplifyAdventureBreakCheck()) { if (Config.simplifyAdventureBreakCheck()) {
ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId); ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId);
if (!player.canBreak(pos, state.vanillaBlockState().literalObject())) { if (!player.canBreak(pos, state.visualBlockState().literalObject())) {
player.preventMiningBlock(); player.preventMiningBlock();
return; return;
} }
@@ -1360,7 +1366,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
Key itemId = state.settings().itemId(); Key itemId = state.settings().itemId();
// no item available // no item available
if (itemId == null) return; 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); Object vanillaBlockItem = FastNMS.INSTANCE.method$Block$asItem(vanillaBlock);
if (vanillaBlockItem == null) return; if (vanillaBlockItem == null) return;
Key addItemId = KeyUtils.namespacedKey2Key(item.getType().getKey()); Key addItemId = KeyUtils.namespacedKey2Key(item.getType().getKey());
@@ -1435,9 +1441,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
Object location = FastNMS.INSTANCE.field$ResourceKey$location(dimensionKey); Object location = FastNMS.INSTANCE.field$ResourceKey$location(dimensionKey);
World world = Bukkit.getWorld(Objects.requireNonNull(NamespacedKey.fromString(location.toString()))); World world = Bukkit.getWorld(Objects.requireNonNull(NamespacedKey.fromString(location.toString())));
if (world != null) { if (world != null) {
int sectionCount = (world.getMaxHeight() - world.getMinHeight()) / 16; player.setClientSideWorld(BukkitAdaptors.adapt(world));
player.setClientSideSectionCount(sectionCount);
player.setClientSideDimension(Key.of(location.toString()));
} else { } else {
CraftEngine.instance().logger().warn("Failed to handle ClientboundLoginPacket: World " + location + " does not exist"); CraftEngine.instance().logger().warn("Failed to handle ClientboundLoginPacket: World " + location + " does not exist");
} }
@@ -1465,10 +1469,9 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
Object location = FastNMS.INSTANCE.field$ResourceKey$location(dimensionKey); Object location = FastNMS.INSTANCE.field$ResourceKey$location(dimensionKey);
World world = Bukkit.getWorld(Objects.requireNonNull(NamespacedKey.fromString(location.toString()))); World world = Bukkit.getWorld(Objects.requireNonNull(NamespacedKey.fromString(location.toString())));
if (world != null) { if (world != null) {
int sectionCount = (world.getMaxHeight() - world.getMinHeight()) / 16; player.setClientSideWorld(BukkitAdaptors.adapt(world));
player.setClientSideSectionCount(sectionCount);
player.setClientSideDimension(Key.of(location.toString()));
player.clearTrackedChunks(); player.clearTrackedChunks();
player.clearTrackedBlockEntities();
} else { } else {
CraftEngine.instance().logger().warn("Failed to handle ClientboundRespawnPacket: World " + location + " does not exist"); CraftEngine.instance().logger().warn("Failed to handle ClientboundRespawnPacket: World " + location + " does not exist");
} }
@@ -1990,17 +1993,15 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
private final IntIdentityList biomeList; private final IntIdentityList biomeList;
private final IntIdentityList blockList; private final IntIdentityList blockList;
private final boolean needsDowngrade; private final boolean needsDowngrade;
private final Predicate<Integer> occlusionPredicate;
public LevelChunkWithLightListener(int[] blockStateMapper, int[] modBlockStateMapper, int blockRegistrySize, int biomeRegistrySize) { public LevelChunkWithLightListener(int[] blockStateMapper, int[] modBlockStateMapper, int blockRegistrySize, int biomeRegistrySize, Predicate<Integer> occlusionPredicate) {
this.blockStateMapper = blockStateMapper; this.blockStateMapper = blockStateMapper;
this.modBlockStateMapper = modBlockStateMapper; this.modBlockStateMapper = modBlockStateMapper;
this.biomeList = new IntIdentityList(biomeRegistrySize); this.biomeList = new IntIdentityList(biomeRegistrySize);
this.blockList = new IntIdentityList(blockRegistrySize); this.blockList = new IntIdentityList(blockRegistrySize);
this.needsDowngrade = MiscUtils.ceilLog2(BlockStateUtils.vanillaBlockStateCount()) != MiscUtils.ceilLog2(blockRegistrySize); this.needsDowngrade = MiscUtils.ceilLog2(BlockStateUtils.vanillaBlockStateCount()) != MiscUtils.ceilLog2(blockRegistrySize);
} this.occlusionPredicate = occlusionPredicate;
public int remapBlockState(int stateId, boolean enableMod) {
return enableMod ? this.modBlockStateMapper[stateId] : this.blockStateMapper[stateId];
} }
@Override @Override
@@ -2012,6 +2013,8 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
boolean named = !VersionHelper.isOrAbove1_20_2(); boolean named = !VersionHelper.isOrAbove1_20_2();
int[] remapper = user.clientModEnabled() ? this.modBlockStateMapper : this.blockStateMapper;
// 读取区块数据 // 读取区块数据
int heightmapsCount = 0; int heightmapsCount = 0;
Map<Integer, long[]> heightmapsMap = null; Map<Integer, long[]> heightmapsMap = null;
@@ -2033,36 +2036,94 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
buf.readBytes(chunkDataBytes); buf.readBytes(chunkDataBytes);
// 客户端侧section数量很重要不能读取此时玩家所在的真实世界包具有滞后性 // 客户端侧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]; MCSection[] sections = new MCSection[count];
FriendlyByteBuf chunkDataByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(chunkDataBytes)); FriendlyByteBuf chunkDataByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(chunkDataBytes));
boolean hasChangedAnyBlock = false; boolean hasChangedAnyBlock = false;
boolean hasGlobalPalette = false; boolean hasGlobalPalette = false;
// 创建客户端侧世界(只在开启实体情况下创建)
ClientSection[] clientSections = Config.enableEntityCulling() ? new ClientSection[count] : null;
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
MCSection mcSection = new MCSection(user.clientBlockList(), this.blockList, this.biomeList); MCSection mcSection = new MCSection(user.clientBlockList(), this.blockList, this.biomeList);
mcSection.readPacket(chunkDataByteBuf); mcSection.readPacket(chunkDataByteBuf);
PalettedContainer<Integer> container = mcSection.blockStateContainer(); PalettedContainer<Integer> container = mcSection.blockStateContainer();
// 重定向生物群系
if (remapBiomes(user, mcSection.biomeContainer())) { if (remapBiomes(user, mcSection.biomeContainer())) {
hasChangedAnyBlock = true; hasChangedAnyBlock = true;
} }
Palette<Integer> palette = container.data().palette(); Palette<Integer> palette = container.data().palette();
if (palette.canRemap()) { if (palette.canRemap()) {
if (palette.remapAndCheck(s -> remapBlockState(s, user.clientModEnabled()))) {
// 重定向方块
if (palette.remapAndCheck(s -> remapper[s])) {
hasChangedAnyBlock = true; 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);
clientSections[i] = new ClientSection(storage);
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 { } else {
hasGlobalPalette = true; hasGlobalPalette = true;
PackedOcclusionStorage storage = null;
if (clientSections != null) {
storage = new PackedOcclusionStorage(false);
clientSections[i] = new ClientSection(storage);
}
for (int j = 0; j < 4096; j++) { for (int j = 0; j < 4096; j++) {
int state = container.get(j); int state = container.get(j);
int newState = remapBlockState(state, user.clientModEnabled());
// 重定向方块
int newState = remapper[state];
if (newState != state) { if (newState != state) {
container.set(j, newState); container.set(j, newState);
hasChangedAnyBlock = true; hasChangedAnyBlock = true;
} }
// 写入视线遮挡数据
if (storage != null) {
storage.set(j, this.occlusionPredicate.test(state));
} }
} }
}
sections[i] = mcSection; sections[i] = mcSection;
} }
@@ -2127,77 +2188,82 @@ 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()); CEWorld ceWorld = clientSideWorld.storageWorld();
// 世界可能被卸载,因为包滞后
if (ceWorld != null) {
CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(chunkPos.longKey); CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(chunkPos.longKey);
if (ceChunk != null) { if (ceChunk != null) {
// 生成方块实体
ceChunk.spawnBlockEntities(player); ceChunk.spawnBlockEntities(player);
} }
} }
} }
}
public static class SectionBlockUpdateListener implements ByteBufferPacketListener { public static class SectionBlockUpdateListener implements ByteBufferPacketListener {
private final int[] blockStateMapper; private final int[] blockStateMapper;
private final int[] modBlockStateMapper; private final int[] modBlockStateMapper;
private final Predicate<Integer> occlusionPredicate;
public SectionBlockUpdateListener(int[] blockStateMapper, int[] modBlockStateMapper) { public SectionBlockUpdateListener(int[] blockStateMapper, int[] modBlockStateMapper, Predicate<Integer> occlusionPredicate) {
this.blockStateMapper = blockStateMapper; this.blockStateMapper = blockStateMapper;
this.modBlockStateMapper = modBlockStateMapper; this.modBlockStateMapper = modBlockStateMapper;
this.occlusionPredicate = occlusionPredicate;
} }
@Override @Override
public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) { public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) {
if (user.clientModEnabled()) { int[] remapper = user.clientModEnabled() ? this.modBlockStateMapper : this.blockStateMapper;
FriendlyByteBuf buf = event.getBuffer(); FriendlyByteBuf buf = event.getBuffer();
long pos = buf.readLong(); long sPos = buf.readLong();
int blocks = buf.readVarInt(); int blocks = buf.readVarInt();
short[] positions = new short[blocks]; short[] positions = new short[blocks];
int[] states = new int[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++) { for (int i = 0; i < blocks; i++) {
long k = buf.readVarLong(); long k = buf.readVarLong();
positions[i] = (short) ((int) (k & 4095L)); short posIndex = (short) ((int) (k & 4095L));
states[i] = modBlockStateMapper[((int) (k >>> 12))]; 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.clear();
buf.writeVarInt(event.packetID()); buf.writeVarInt(event.packetID());
buf.writeLong(pos); buf.writeLong(sPos);
buf.writeVarInt(blocks); buf.writeVarInt(blocks);
for (int i = 0; i < blocks; i++) { for (int i = 0; i < blocks; i++) {
buf.writeVarLong((long) states[i] << 12 | positions[i]); buf.writeVarLong((long) states[i] << 12 | positions[i]);
} }
event.setChanged(true); 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);
}
} }
} }
public static class BlockUpdateListener implements ByteBufferPacketListener { public static class BlockUpdateListener implements ByteBufferPacketListener {
private final int[] blockStateMapper; private final int[] blockStateMapper;
private final int[] modBlockStateMapper; private final int[] modBlockStateMapper;
private final Predicate<Integer> occlusionPredicate;
public BlockUpdateListener(int[] blockStateMapper, int[] modBlockStateMapper) { public BlockUpdateListener(int[] blockStateMapper, int[] modBlockStateMapper, Predicate<Integer> occlusionPredicate) {
this.blockStateMapper = blockStateMapper; this.blockStateMapper = blockStateMapper;
this.modBlockStateMapper = modBlockStateMapper; this.modBlockStateMapper = modBlockStateMapper;
this.occlusionPredicate = occlusionPredicate;
} }
@Override @Override
@@ -2205,6 +2271,12 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
FriendlyByteBuf buf = event.getBuffer(); FriendlyByteBuf buf = event.getBuffer();
BlockPos pos = buf.readBlockPos(); BlockPos pos = buf.readBlockPos();
int before = buf.readVarInt(); 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)) { if (user.clientModEnabled() && !BlockStateUtils.isVanillaBlock(before)) {
return; return;
} }
@@ -2378,6 +2450,13 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
if (eventId != WorldEvents.BLOCK_BREAK_EFFECT) return; if (eventId != WorldEvents.BLOCK_BREAK_EFFECT) return;
BlockPos blockPos = buf.readBlockPos(); BlockPos blockPos = buf.readBlockPos();
int state = buf.readInt(); 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(); boolean global = buf.readBoolean();
int newState = user.clientModEnabled() ? modBlockStateMapper[state] : blockStateMapper[state]; int newState = user.clientModEnabled() ? modBlockStateMapper[state] : blockStateMapper[state];
Object blockState = BlockStateUtils.idToBlockState(state); Object blockState = BlockStateUtils.idToBlockState(state);

View File

@@ -103,7 +103,7 @@ public record VisualBlockStatePacket(int[] data) implements ModPacket {
for (int i = 0; i < serverSideBlockCount; i++) { for (int i = 0; i < serverSideBlockCount; i++) {
ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(i + vanillaBlockStateCount); ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(i + vanillaBlockStateCount);
if (state.isEmpty()) continue; if (state.isEmpty()) continue;
mappings[state.customBlockState().registryId() - vanillaBlockStateCount] = state.vanillaBlockState().registryId(); mappings[state.customBlockState().registryId() - vanillaBlockStateCount] = state.visualBlockState().registryId();
} }
return new VisualBlockStatePacket(mappings); return new VisualBlockStatePacket(mappings);
} }

View File

@@ -4593,4 +4593,26 @@ public final class CoreReflections {
"world.level.levelgen.feature.Feature" "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);
}
}
} }

View File

@@ -23,6 +23,7 @@ import net.momirealms.craftengine.core.advancement.AdvancementType;
import net.momirealms.craftengine.core.block.BlockStateWrapper; import net.momirealms.craftengine.core.block.BlockStateWrapper;
import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.entity.BlockEntity; 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.data.EntityData;
import net.momirealms.craftengine.core.entity.player.GameMode; import net.momirealms.craftengine.core.entity.player.GameMode;
import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.entity.player.InteractionHand;
@@ -31,6 +32,7 @@ import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.config.Config;
import net.momirealms.craftengine.core.plugin.context.CooldownData; 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.locale.TranslationManager;
import net.momirealms.craftengine.core.plugin.network.ConnectionState; import net.momirealms.craftengine.core.plugin.network.ConnectionState;
import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler; import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler;
@@ -39,7 +41,8 @@ import net.momirealms.craftengine.core.sound.SoundSource;
import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.util.*;
import net.momirealms.craftengine.core.world.*; import net.momirealms.craftengine.core.world.*;
import net.momirealms.craftengine.core.world.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 net.momirealms.craftengine.core.world.collision.AABB;
import org.bukkit.*; import org.bukkit.*;
import org.bukkit.attribute.Attribute; import org.bukkit.attribute.Attribute;
@@ -85,8 +88,7 @@ public class BukkitServerPlayer extends Player {
private Reference<org.bukkit.entity.Player> playerRef; private Reference<org.bukkit.entity.Player> playerRef;
private Reference<Object> serverPlayerRef; private Reference<Object> serverPlayerRef;
// client side dimension info // client side dimension info
private int sectionCount; private World clientSideWorld;
private Key clientSideDimension;
// check main hand/offhand interaction // check main hand/offhand interaction
private int lastSuccessfulInteraction; private int lastSuccessfulInteraction;
// to prevent duplicated events // to prevent duplicated events
@@ -124,7 +126,7 @@ public class BukkitServerPlayer extends Player {
// cooldown data // cooldown data
private CooldownData cooldownData; private CooldownData cooldownData;
// tracked chunks // tracked chunks
private ConcurrentLong2ReferenceChainedHashTable<ChunkStatus> trackedChunks; private ConcurrentLong2ReferenceChainedHashTable<ClientChunk> trackedChunks;
// entity view // entity view
private Map<Integer, EntityPacketHandler> entityTypeView; private Map<Integer, EntityPacketHandler> entityTypeView;
// 通过指令或api设定的语言 // 通过指令或api设定的语言
@@ -138,6 +140,10 @@ public class BukkitServerPlayer extends Player {
private boolean isHackedBreak; private boolean isHackedBreak;
// 上一次停止挖掘包发出的时间 // 上一次停止挖掘包发出的时间
private int lastStopMiningTick; private int lastStopMiningTick;
// 跟踪到的方块实体渲染器
private final Map<BlockPos, VirtualCullableObject> trackedBlockEntityRenderers = new ConcurrentHashMap<>();
private final EntityCulling culling;
public BukkitServerPlayer(BukkitCraftEngine plugin, @Nullable Channel channel) { public BukkitServerPlayer(BukkitCraftEngine plugin, @Nullable Channel channel) {
this.channel = channel; this.channel = channel;
@@ -151,6 +157,7 @@ public class BukkitServerPlayer extends Player {
} }
} }
} }
this.culling = new EntityCulling(this, 64, 0.5);
} }
public void setPlayer(org.bukkit.entity.Player player) { public void setPlayer(org.bukkit.entity.Player player) {
@@ -477,21 +484,13 @@ public class BukkitServerPlayer extends Player {
} }
@Override @Override
public int clientSideSectionCount() { public World clientSideWorld() {
return sectionCount; return this.clientSideWorld;
}
public void setClientSideSectionCount(int sectionCount) {
this.sectionCount = sectionCount;
} }
@Override @Override
public Key clientSideDimension() { public void setClientSideWorld(World world) {
return clientSideDimension; this.clientSideWorld = world;
}
public void setClientSideDimension(Key clientSideDimension) {
this.clientSideDimension = clientSideDimension;
} }
public void setConnectionState(ConnectionState connectionState) { public void setConnectionState(ConnectionState connectionState) {
@@ -564,6 +563,15 @@ public class BukkitServerPlayer extends Player {
this.predictNextBlockToMine(); 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() { private void updateGUI() {
@@ -646,7 +654,7 @@ public class BukkitServerPlayer extends Player {
// instant break // instant break
boolean custom = immutableBlockState != null; boolean custom = immutableBlockState != null;
if (custom && getDestroyProgress(state, pos) >= 1f) { 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 it's not an instant break on client side, we should resend level event
if (vanillaBlockState != null && getDestroyProgress(vanillaBlockState.literalObject(), pos) < 1f) { if (vanillaBlockState != null && getDestroyProgress(vanillaBlockState.literalObject(), pos) < 1f) {
Object levelEventPacket = FastNMS.INSTANCE.constructor$ClientboundLevelEventPacket( Object levelEventPacket = FastNMS.INSTANCE.constructor$ClientboundLevelEventPacket(
@@ -811,7 +819,7 @@ public class BukkitServerPlayer extends Player {
// for simplified adventure break, switch mayBuild temporarily // for simplified adventure break, switch mayBuild temporarily
if (isAdventureMode() && Config.simplifyAdventureBreakCheck()) { if (isAdventureMode() && Config.simplifyAdventureBreakCheck()) {
// check the appearance state // check the appearance state
if (canBreak(hitPos, customState.vanillaBlockState().literalObject())) { if (canBreak(hitPos, customState.visualBlockState().literalObject())) {
// Error might occur so we use try here // Error might occur so we use try here
try { try {
FastNMS.INSTANCE.field$Player$mayBuild(serverPlayer, true); FastNMS.INSTANCE.field$Player$mayBuild(serverPlayer, true);
@@ -1180,12 +1188,12 @@ public class BukkitServerPlayer extends Player {
} }
@Override @Override
public ChunkStatus getTrackedChunk(long chunkPos) { public ClientChunk getTrackedChunk(long chunkPos) {
return this.trackedChunks.get(chunkPos); return this.trackedChunks.get(chunkPos);
} }
@Override @Override
public void addTrackedChunk(long chunkPos, ChunkStatus chunkStatus) { public void addTrackedChunk(long chunkPos, ClientChunk chunkStatus) {
this.trackedChunks.put(chunkPos, chunkStatus); this.trackedChunks.put(chunkPos, chunkStatus);
} }
@@ -1300,4 +1308,36 @@ public class BukkitServerPlayer extends Player {
public void sendTotemAnimation(Item<?> totem, @Nullable SoundData sound, boolean silent) { public void sendTotemAnimation(Item<?> totem, @Nullable SoundData sound, boolean silent) {
PlayerUtils.sendTotemAnimation(this, totem, sound, silent); PlayerUtils.sendTotemAnimation(this, totem, sound, silent);
} }
@Override
public void addTrackedBlockEntities(Map<BlockPos, ConstantBlockEntityRenderer> renders) {
for (Map.Entry<BlockPos, ConstantBlockEntityRenderer> entry : renders.entrySet()) {
this.trackedBlockEntityRenderers.put(entry.getKey(), new VirtualCullableObject(entry.getValue()));
}
}
@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<BlockPos> 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();
}
} }

View File

@@ -60,7 +60,7 @@ public final class EntityUtils {
Object serverLevel = BukkitAdaptors.adapt(player.getWorld()).serverWorld(); Object serverLevel = BukkitAdaptors.adapt(player.getWorld()).serverWorld();
Object serverPlayer = FastNMS.INSTANCE.method$CraftPlayer$getHandle(player); 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)) { 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 { try {
double floorHeight = (double) CoreReflections.method$BlockGetter$getBlockFloorHeight.invoke(serverLevel, LocationUtils.toBlockPos(pos)); double floorHeight = (double) CoreReflections.method$BlockGetter$getBlockFloorHeight.invoke(serverLevel, LocationUtils.toBlockPos(pos));
if (pos.y() + floorHeight > y + 0.75 || !isBlockFloorValid(floorHeight)) { if (pos.y() + floorHeight > y + 0.75 || !isBlockFloorValid(floorHeight)) {

View File

@@ -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.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils; 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.block.ImmutableBlockState;
import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.config.Config;
@@ -296,8 +297,10 @@ public class BukkitWorldManager implements WorldManager, Listener {
for (int z = 0; z < 16; z++) { for (int z = 0; z < 16; z++) {
for (int y = 0; y < 16; y++) { for (int y = 0; y < 16; y++) {
ImmutableBlockState customState = ceSection.getBlockState(x, y, z); ImmutableBlockState customState = ceSection.getBlockState(x, y, z);
if (!customState.isEmpty() && customState.vanillaBlockState() != null) { if (!customState.isEmpty()) {
FastNMS.INSTANCE.method$LevelChunkSection$setBlockState(section, x, y, z, customState.vanillaBlockState().literalObject(), false); BlockStateWrapper wrapper = customState.restoreBlockState();
if (wrapper != null) {
FastNMS.INSTANCE.method$LevelChunkSection$setBlockState(section, x, y, z, wrapper.literalObject(), false);
unsaved = true; unsaved = true;
} }
} }
@@ -306,6 +309,7 @@ public class BukkitWorldManager implements WorldManager, Listener {
} }
} }
} }
}
if (unsaved /*&& !FastNMS.INSTANCE.method$LevelChunk$isUnsaved(levelChunk)*/) { if (unsaved /*&& !FastNMS.INSTANCE.method$LevelChunk$isUnsaved(levelChunk)*/) {
FastNMS.INSTANCE.method$LevelChunk$markUnsaved(levelChunk); FastNMS.INSTANCE.method$LevelChunk$markUnsaved(levelChunk);
} }

View File

@@ -552,11 +552,10 @@ chunk-system:
remove: [] remove: []
convert: {} convert: {}
#client-optimization: client-optimization:
# # Using server-side ray tracing algorithms to hide certain entities and reduce client-side rendering pressure. # Using server-side ray tracing algorithms to hide certain entities and reduce client-side rendering pressure.
# entity-culling: entity-culling:
# enable: false enable: false
# whitelist-entities: []
# Enables or disables debug mode # Enables or disables debug mode
debug: debug:

View File

@@ -178,7 +178,7 @@ templates:
# template: default:loot_table/ore # template: default:loot_table/ore
# arguments: # arguments:
# ore_block: the ore block # 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 # ore_drop_count: the amount of the ore materials
# exp: the exp to drop # exp: the exp to drop
default:loot_table/ore: default:loot_table/ore:

View File

@@ -87,6 +87,7 @@ warning.config.type.quaternionf: "<yellow>Issue found in file <arg:0> - Failed t
warning.config.type.vector3f: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to Vector3f type for option '<arg:3>'.</yellow>" warning.config.type.vector3f: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to Vector3f type for option '<arg:3>'.</yellow>"
warning.config.type.vec3d: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to Vec3d type for option '<arg:3>'.</yellow>" warning.config.type.vec3d: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to Vec3d type for option '<arg:3>'.</yellow>"
warning.config.type.map: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to Map type for option '<arg:3>'.</yellow>" warning.config.type.map: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to Map type for option '<arg:3>'.</yellow>"
warning.config.type.aabb: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to AABB type for option '<arg:3>'.</yellow>"
warning.config.type.snbt.invalid_syntax: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Invalid snbt syntax '<arg:2>'.</yellow>" warning.config.type.snbt.invalid_syntax: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Invalid snbt syntax '<arg:2>'.</yellow>"
warning.config.number.missing_type: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is missing the required 'type' argument for number argument.</yellow>" warning.config.number.missing_type: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is missing the required 'type' argument for number argument.</yellow>"
warning.config.number.invalid_type: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is using an invalid number argument type '<arg:2>'.</yellow>" warning.config.number.invalid_type: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is using an invalid number argument type '<arg:2>'.</yellow>"

View File

@@ -85,6 +85,8 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
// 临时存储哪些视觉方块被使用了 // 临时存储哪些视觉方块被使用了
protected final Set<BlockStateWrapper> tempVisualBlockStatesInUse = new HashSet<>(); protected final Set<BlockStateWrapper> tempVisualBlockStatesInUse = new HashSet<>();
protected final Set<Key> tempVisualBlocksInUse = new HashSet<>(); protected final Set<Key> tempVisualBlocksInUse = new HashSet<>();
// 能遮挡视线的方块
protected final boolean[] viewBlockingBlocks;
// 声音映射表,和使用了哪些视觉方块有关 // 声音映射表,和使用了哪些视觉方块有关
protected Map<Key, Key> soundReplacements = Map.of(); protected Map<Key, Key> soundReplacements = Map.of();
// 是否使用了透明方块模型 // 是否使用了透明方块模型
@@ -102,6 +104,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
this.tempVanillaBlockStateModels = new JsonElement[vanillaBlockStateCount]; this.tempVanillaBlockStateModels = new JsonElement[vanillaBlockStateCount];
this.blockParser = new BlockParser(this.autoVisualBlockStateCandidates); this.blockParser = new BlockParser(this.autoVisualBlockStateCandidates);
this.blockStateMappingParser = new BlockStateMappingParser(); this.blockStateMappingParser = new BlockStateMappingParser();
this.viewBlockingBlocks = new boolean[vanillaBlockStateCount + customBlockCount];
Arrays.fill(this.blockStateMappings, -1); Arrays.fill(this.blockStateMappings, -1);
} }
@@ -232,6 +235,10 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
public abstract BlockBehavior createBlockBehavior(CustomBlock customBlock, List<Map<String, Object>> behaviorConfig); public abstract BlockBehavior createBlockBehavior(CustomBlock customBlock, List<Map<String, Object>> behaviorConfig);
public boolean isViewBlockingBlock(int stateId) {
return this.viewBlockingBlocks[stateId];
}
protected abstract void updateTags(); protected abstract void updateTags();
protected abstract boolean isVanillaBlock(Key id); protected abstract boolean isVanillaBlock(Key id);
@@ -593,7 +600,11 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
this.arrangeModelForStateAndVerify(visualBlockState, parseBlockModel(modelConfig)); 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); appearances.put(appearanceName, blockStateAppearance);
if (anyAppearance == null) { if (anyAppearance == null) {
anyAppearance = blockStateAppearance; anyAppearance = blockStateAppearance;
@@ -631,7 +642,8 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
continue; continue;
} }
for (ImmutableBlockState possibleState : possibleStates) { for (ImmutableBlockState possibleState : possibleStates) {
possibleState.setVanillaBlockState(appearance.blockState()); possibleState.setVisualBlockState(appearance.blockState());
possibleState.setEstimatedBoundingBox(appearance.estimateAABB());
appearance.blockEntityRenderer().ifPresent(possibleState::setConstantRenderers); appearance.blockEntityRenderer().ifPresent(possibleState::setConstantRenderers);
} }
} }
@@ -650,11 +662,12 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
} }
state.setBehavior(blockBehavior); state.setBehavior(blockBehavior);
int internalId = state.customBlockState().registryId(); int internalId = state.customBlockState().registryId();
BlockStateWrapper visualState = state.vanillaBlockState(); BlockStateWrapper visualState = state.visualBlockState();
// 校验,为未绑定外观的强行添加外观 // 校验,为未绑定外观的强行添加外观
if (visualState == null) { if (visualState == null) {
visualState = anyAppearance.blockState(); visualState = anyAppearance.blockState();
state.setVanillaBlockState(visualState); state.setVisualBlockState(visualState);
state.setEstimatedBoundingBox(anyAppearance.estimateAABB());
anyAppearance.blockEntityRenderer().ifPresent(state::setConstantRenderers); anyAppearance.blockEntityRenderer().ifPresent(state::setConstantRenderers);
} }
int appearanceId = visualState.registryId(); int appearanceId = visualState.registryId();

View File

@@ -49,6 +49,7 @@ public final class BlockKeys {
public static final Key TWISTING_VINES = Key.of("minecraft:twisting_vines"); public static final Key TWISTING_VINES = Key.of("minecraft:twisting_vines");
public static final Key KELP = Key.of("minecraft:kelp"); public static final Key KELP = Key.of("minecraft:kelp");
public static final Key CHORUS_PLANT = Key.of("minecraft:chorus_plant"); 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 CHEST = Key.of("minecraft:chest");
public static final Key BARREL = Key.of("minecraft:barrel"); public static final Key BARREL = Key.of("minecraft:barrel");

View File

@@ -22,4 +22,8 @@ public final class BlockRegistryMirror {
public static BlockStateWrapper stoneState() { public static BlockStateWrapper stoneState() {
return stoneState; return stoneState;
} }
public static BlockStateWrapper[] blockStates() {
return blockStates;
}
} }

View File

@@ -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.BlockEntityElement;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig;
import net.momirealms.craftengine.core.world.collision.AABB;
import java.util.Optional; import java.util.Optional;
public record BlockStateAppearance(BlockStateWrapper blockState, Optional<BlockEntityElementConfig<? extends BlockEntityElement>[]> blockEntityRenderer) { public record BlockStateAppearance(BlockStateWrapper blockState,
Optional<BlockEntityElementConfig<? extends BlockEntityElement>[]> blockEntityRenderer,
AABB estimateAABB) {
} }

View File

@@ -17,9 +17,11 @@ import net.momirealms.craftengine.core.registry.Holder;
import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.world.CEWorld; import net.momirealms.craftengine.core.world.CEWorld;
import net.momirealms.craftengine.core.world.World; 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.CompoundTag;
import net.momirealms.sparrow.nbt.NBT; import net.momirealms.sparrow.nbt.NBT;
import net.momirealms.sparrow.nbt.Tag; import net.momirealms.sparrow.nbt.Tag;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -33,12 +35,15 @@ public final class ImmutableBlockState {
private CompoundTag tag; private CompoundTag tag;
private BlockStateWrapper customBlockState; private BlockStateWrapper customBlockState;
private BlockStateWrapper vanillaBlockState; private BlockStateWrapper visualBlockState;
// 安全的在卸载ce后还原的方块
private BlockStateWrapper restoreBlockState;
private BlockBehavior behavior; private BlockBehavior behavior;
private BlockSettings settings; private BlockSettings settings;
private BlockEntityType<? extends BlockEntity> blockEntityType; private BlockEntityType<? extends BlockEntity> blockEntityType;
@Nullable @Nullable
private BlockEntityElementConfig<? extends BlockEntityElement>[] renderers; private BlockEntityElementConfig<? extends BlockEntityElement>[] renderers;
private AABB estimatedBoundingBox;
ImmutableBlockState( ImmutableBlockState(
Holder.Reference<CustomBlock> owner, Holder.Reference<CustomBlock> owner,
@@ -84,6 +89,14 @@ public final class ImmutableBlockState {
this.renderers = renderers; this.renderers = renderers;
} }
public void setEstimatedBoundingBox(AABB aabb) {
this.estimatedBoundingBox = aabb;
}
public AABB estimatedBoundingBox() {
return estimatedBoundingBox;
}
public boolean hasBlockEntity() { public boolean hasBlockEntity() {
return this.blockEntityType != null; return this.blockEntityType != null;
} }
@@ -96,16 +109,30 @@ public final class ImmutableBlockState {
return this.customBlockState; return this.customBlockState;
} }
@Deprecated
public BlockStateWrapper vanillaBlockState() { 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) { public void setCustomBlockState(@NotNull BlockStateWrapper customBlockState) {
this.customBlockState = customBlockState; this.customBlockState = customBlockState;
} }
public void setVanillaBlockState(@NotNull BlockStateWrapper vanillaBlockState) { public void setVisualBlockState(@NotNull BlockStateWrapper visualBlockState) {
this.vanillaBlockState = vanillaBlockState; this.visualBlockState = visualBlockState;
}
public void setRestoreBlockState(BlockStateWrapper restoreBlockState) {
this.restoreBlockState = restoreBlockState;
} }
public CompoundTag getNbtToSave() { public CompoundTag getNbtToSave() {

View File

@@ -2,16 +2,21 @@ package net.momirealms.craftengine.core.block.entity.render;
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
import net.momirealms.craftengine.core.entity.player.Player; 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; import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Experimental @ApiStatus.Experimental
public class ConstantBlockEntityRenderer { public class ConstantBlockEntityRenderer implements Cullable {
private final BlockEntityElement[] elements; private final BlockEntityElement[] elements;
public final AABB aabb;
public ConstantBlockEntityRenderer(BlockEntityElement[] elements) { public ConstantBlockEntityRenderer(BlockEntityElement[] elements, AABB aabb) {
this.elements = elements; this.elements = elements;
this.aabb = aabb;
} }
@Override
public void show(Player player) { public void show(Player player) {
for (BlockEntityElement element : this.elements) { for (BlockEntityElement element : this.elements) {
if (element != null) { if (element != null) {
@@ -20,6 +25,7 @@ public class ConstantBlockEntityRenderer {
} }
} }
@Override
public void hide(Player player) { public void hide(Player player) {
for (BlockEntityElement element : this.elements) { for (BlockEntityElement element : this.elements) {
if (element != null) { if (element != null) {
@@ -47,4 +53,9 @@ public class ConstantBlockEntityRenderer {
public BlockEntityElement[] elements() { public BlockEntityElement[] elements() {
return this.elements; return this.elements;
} }
@Override
public AABB aabb() {
return this.aabb;
}
} }

View File

@@ -2,6 +2,7 @@ package net.momirealms.craftengine.core.entity.player;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.core.advancement.AdvancementType; 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.entity.AbstractEntity;
import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.plugin.context.CooldownData; import net.momirealms.craftengine.core.plugin.context.CooldownData;
@@ -9,14 +10,14 @@ import net.momirealms.craftengine.core.plugin.network.NetWorkUser;
import net.momirealms.craftengine.core.sound.SoundData; import net.momirealms.craftengine.core.sound.SoundData;
import net.momirealms.craftengine.core.sound.SoundSource; import net.momirealms.craftengine.core.sound.SoundSource;
import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.*;
import net.momirealms.craftengine.core.world.Position; import net.momirealms.craftengine.core.world.chunk.client.VirtualCullableObject;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldPosition;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
public abstract class Player extends AbstractEntity implements NetWorkUser { public abstract class Player extends AbstractEntity implements NetWorkUser {
private static final Key TYPE = Key.of("minecraft:player"); private static final Key TYPE = Key.of("minecraft:player");
@@ -35,6 +36,8 @@ public abstract class Player extends AbstractEntity implements NetWorkUser {
@Override @Override
public abstract Object serverPlayer(); public abstract Object serverPlayer();
public abstract void setClientSideWorld(World world);
public abstract float getDestroyProgress(Object blockState, BlockPos pos); public abstract float getDestroyProgress(Object blockState, BlockPos pos);
public abstract void setClientSideCanBreakBlock(boolean canBreak); public abstract void setClientSideCanBreakBlock(boolean canBreak);
@@ -198,6 +201,16 @@ public abstract class Player extends AbstractEntity implements NetWorkUser {
public abstract void sendTotemAnimation(Item<?> totem, @Nullable SoundData sound, boolean silent); public abstract void sendTotemAnimation(Item<?> totem, @Nullable SoundData sound, boolean silent);
public abstract void addTrackedBlockEntities(Map<BlockPos, ConstantBlockEntityRenderer> renders);
public abstract void addTrackedBlockEntity(BlockPos blockPos, ConstantBlockEntityRenderer renderer);
public abstract VirtualCullableObject getTrackedBlockEntity(BlockPos blockPos);
public abstract void removeTrackedBlockEntities(Collection<BlockPos> renders);
public abstract void clearTrackedBlockEntities();
@Override @Override
public void remove() { public void remove() {
} }

View File

@@ -44,7 +44,7 @@ public class LootPool<T> {
} }
if (this.compositeCondition.test(context)) { if (this.compositeCondition.test(context)) {
Consumer<Item<T>> consumer = LootFunction.decorate(this.compositeFunction, lootConsumer, context); Consumer<Item<T>> 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) { for (int j = 0; j < i; ++j) {
this.addRandomItem(createFunctionApplier(consumer, context), context); this.addRandomItem(createFunctionApplier(consumer, context), context);
} }

View File

@@ -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.compatibility.PluginTaskRegistry;
import net.momirealms.craftengine.core.plugin.config.Config; 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.TemplateManager;
import net.momirealms.craftengine.core.plugin.config.template.TemplateManagerImpl;
import net.momirealms.craftengine.core.plugin.context.GlobalVariableManager; import net.momirealms.craftengine.core.plugin.context.GlobalVariableManager;
import net.momirealms.craftengine.core.plugin.dependency.Dependencies; import net.momirealms.craftengine.core.plugin.dependency.Dependencies;
import net.momirealms.craftengine.core.plugin.dependency.Dependency; import net.momirealms.craftengine.core.plugin.dependency.Dependency;

View File

@@ -203,6 +203,8 @@ public class Config {
protected boolean emoji$contexts$sign; protected boolean emoji$contexts$sign;
protected int emoji$max_emojis_per_parse; protected int emoji$max_emojis_per_parse;
protected boolean client_optimization$entity_culling$enable;
public Config(CraftEngine plugin) { public Config(CraftEngine plugin) {
this.plugin = plugin; this.plugin = plugin;
this.configVersion = PluginProperties.getValue("config"); this.configVersion = PluginProperties.getValue("config");
@@ -562,6 +564,9 @@ public class Config {
emoji$contexts$sign = config.getBoolean("emoji.contexts.sign", true); emoji$contexts$sign = config.getBoolean("emoji.contexts.sign", true);
emoji$max_emojis_per_parse = config.getInt("emoji.max-emojis-per-parse", 32); 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; firstTime = false;
} }
@@ -1152,6 +1157,10 @@ public class Config {
return instance.resource_pack$optimization$texture$zopfli_iterations; return instance.resource_pack$optimization$texture$zopfli_iterations;
} }
public static boolean enableEntityCulling() {
return instance.client_optimization$entity_culling$enable;
}
public YamlDocument loadOrCreateYamlData(String fileName) { public YamlDocument loadOrCreateYamlData(String fileName) {
Path path = this.plugin.dataFolderPath().resolve(fileName); Path path = this.plugin.dataFolderPath().resolve(fileName);
if (!Files.exists(path)) { if (!Files.exists(path)) {

View File

@@ -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.Manageable;
import net.momirealms.craftengine.core.plugin.config.ConfigParser; 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.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 { public interface TemplateManager extends Manageable {

View File

@@ -3,7 +3,6 @@ package net.momirealms.craftengine.core.plugin.config.template.argument;
import com.ezylang.evalex.Expression; import com.ezylang.evalex.Expression;
import com.ezylang.evalex.data.EvaluationValue; import com.ezylang.evalex.data.EvaluationValue;
import net.momirealms.craftengine.core.plugin.config.template.ArgumentString; 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 net.momirealms.craftengine.core.util.Key;
import java.util.Locale; import java.util.Locale;

View File

@@ -40,7 +40,7 @@ public class MatchBlockCondition<CTX extends Context> implements Condition<CTX>
Optional<WorldPosition> optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); Optional<WorldPosition> optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION);
if (optionalWorldPosition.isPresent()) { if (optionalWorldPosition.isPresent()) {
World world = optionalWorldPosition.get().world(); 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 MiscUtils.matchRegex(blockAt.id().asString(), this.ids, this.regexMatch);
} }
return false; return false;

View File

@@ -28,7 +28,7 @@ public class BreakBlockFunction<CTX extends Context> extends AbstractConditional
@Override @Override
public void runInternal(CTX ctx) { public void runInternal(CTX ctx) {
Optional<Player> optionalPlayer = ctx.getOptionalParameter(DirectContextParameters.PLAYER); Optional<Player> 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 @Override

View File

@@ -55,9 +55,9 @@ public class CycleBlockPropertyFunction<CTX extends Context> extends AbstractCon
Optional<WorldPosition> optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); Optional<WorldPosition> optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION);
if (optionalWorldPosition.isEmpty()) return; if (optionalWorldPosition.isEmpty()) return;
World world = optionalWorldPosition.get().world(); World world = optionalWorldPosition.get().world();
int x = MiscUtils.fastFloor(this.x.getDouble(ctx)); int x = MiscUtils.floor(this.x.getDouble(ctx));
int y = MiscUtils.fastFloor(this.y.getDouble(ctx)); int y = MiscUtils.floor(this.y.getDouble(ctx));
int z = MiscUtils.fastFloor(this.z.getDouble(ctx)); int z = MiscUtils.floor(this.z.getDouble(ctx));
BlockStateWrapper wrapper = updateBlockState(world.getBlock(x, y, z).blockState(), ctx); BlockStateWrapper wrapper = updateBlockState(world.getBlock(x, y, z).blockState(), ctx);
world.setBlockState(x, y, z, wrapper, this.updateFlags.getInt(ctx)); world.setBlockState(x, y, z, wrapper, this.updateFlags.getInt(ctx));
} }

View File

@@ -40,7 +40,7 @@ public class PlaceBlockFunction<CTX extends Context> extends AbstractConditional
Optional<WorldPosition> optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); Optional<WorldPosition> optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION);
if (optionalWorldPosition.isPresent()) { if (optionalWorldPosition.isPresent()) {
World world = optionalWorldPosition.get().world(); 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));
} }
} }

View File

@@ -48,7 +48,7 @@ public class RunFunction<CTX extends Context> extends AbstractConditionalFunctio
for (Function<CTX> function : functions) { for (Function<CTX> function : functions) {
function.run(ctx); 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);
} }
} }
} }

View File

@@ -45,9 +45,9 @@ public class TransformBlockFunction<CTX extends Context> extends AbstractConditi
Optional<WorldPosition> optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); Optional<WorldPosition> optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION);
if (optionalWorldPosition.isPresent()) { if (optionalWorldPosition.isPresent()) {
World world = optionalWorldPosition.get().world(); World world = optionalWorldPosition.get().world();
int x = MiscUtils.fastFloor(this.x.getDouble(ctx)); int x = MiscUtils.floor(this.x.getDouble(ctx));
int y = MiscUtils.fastFloor(this.y.getDouble(ctx)); int y = MiscUtils.floor(this.y.getDouble(ctx));
int z = MiscUtils.fastFloor(this.z.getDouble(ctx)); int z = MiscUtils.floor(this.z.getDouble(ctx));
BlockStateWrapper existingBlockState = world.getBlock(x, y, z).blockState().withProperties(this.properties); BlockStateWrapper existingBlockState = world.getBlock(x, y, z).blockState().withProperties(this.properties);
CompoundTag newProperties = new CompoundTag(); CompoundTag newProperties = new CompoundTag();
for (String propertyName : existingBlockState.getPropertyNames()) { for (String propertyName : existingBlockState.getPropertyNames()) {

View File

@@ -40,9 +40,9 @@ public class UpdateBlockPropertyFunction<CTX extends Context> extends AbstractCo
Optional<WorldPosition> optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); Optional<WorldPosition> optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION);
if (optionalWorldPosition.isPresent()) { if (optionalWorldPosition.isPresent()) {
World world = optionalWorldPosition.get().world(); World world = optionalWorldPosition.get().world();
int x = MiscUtils.fastFloor(this.x.getDouble(ctx)); int x = MiscUtils.floor(this.x.getDouble(ctx));
int y = MiscUtils.fastFloor(this.y.getDouble(ctx)); int y = MiscUtils.floor(this.y.getDouble(ctx));
int z = MiscUtils.fastFloor(this.z.getDouble(ctx)); int z = MiscUtils.floor(this.z.getDouble(ctx));
ExistingBlock blockAt = world.getBlock(x, y, z); ExistingBlock blockAt = world.getBlock(x, y, z);
BlockStateWrapper wrapper = blockAt.blockState().withProperties(this.properties); BlockStateWrapper wrapper = blockAt.blockState().withProperties(this.properties);
world.setBlockState(x, y, z, wrapper, this.updateFlags.getInt(ctx)); world.setBlockState(x, y, z, wrapper, this.updateFlags.getInt(ctx));

View File

@@ -20,9 +20,9 @@ public class EntityParameterProvider implements ChainParameterProvider<Entity> {
CONTEXT_FUNCTIONS.put(DirectContextParameters.YAW, Entity::xRot); CONTEXT_FUNCTIONS.put(DirectContextParameters.YAW, Entity::xRot);
CONTEXT_FUNCTIONS.put(DirectContextParameters.PITCH, Entity::yRot); CONTEXT_FUNCTIONS.put(DirectContextParameters.PITCH, Entity::yRot);
CONTEXT_FUNCTIONS.put(DirectContextParameters.POSITION, Entity::position); CONTEXT_FUNCTIONS.put(DirectContextParameters.POSITION, Entity::position);
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.fastFloor(p.x())); CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.floor(p.x()));
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.fastFloor(p.y())); CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.floor(p.y()));
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.fastFloor(p.z())); CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.floor(p.z()));
CONTEXT_FUNCTIONS.put(DirectContextParameters.NAME, Entity::name); CONTEXT_FUNCTIONS.put(DirectContextParameters.NAME, Entity::name);
CONTEXT_FUNCTIONS.put(DirectContextParameters.UUID, Entity::uuid); CONTEXT_FUNCTIONS.put(DirectContextParameters.UUID, Entity::uuid);
CONTEXT_FUNCTIONS.put(DirectContextParameters.WORLD, Entity::world); CONTEXT_FUNCTIONS.put(DirectContextParameters.WORLD, Entity::world);

View File

@@ -21,9 +21,9 @@ public class PlayerParameterProvider implements ChainParameterProvider<Player> {
CONTEXT_FUNCTIONS.put(DirectContextParameters.PITCH, Entity::xRot); CONTEXT_FUNCTIONS.put(DirectContextParameters.PITCH, Entity::xRot);
CONTEXT_FUNCTIONS.put(DirectContextParameters.YAW, Entity::yRot); CONTEXT_FUNCTIONS.put(DirectContextParameters.YAW, Entity::yRot);
CONTEXT_FUNCTIONS.put(DirectContextParameters.POSITION, Entity::position); CONTEXT_FUNCTIONS.put(DirectContextParameters.POSITION, Entity::position);
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.fastFloor(p.x())); CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.floor(p.x()));
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.fastFloor(p.y())); CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.floor(p.y()));
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.fastFloor(p.z())); CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.floor(p.z()));
CONTEXT_FUNCTIONS.put(DirectContextParameters.FOOD, Player::foodLevel); CONTEXT_FUNCTIONS.put(DirectContextParameters.FOOD, Player::foodLevel);
CONTEXT_FUNCTIONS.put(DirectContextParameters.SATURATION, Player::saturation); CONTEXT_FUNCTIONS.put(DirectContextParameters.SATURATION, Player::saturation);
CONTEXT_FUNCTIONS.put(DirectContextParameters.NAME, Player::name); CONTEXT_FUNCTIONS.put(DirectContextParameters.NAME, Player::name);

View File

@@ -20,9 +20,9 @@ public class PositionParameterProvider implements ChainParameterProvider<WorldPo
CONTEXT_FUNCTIONS.put(DirectContextParameters.Z, WorldPosition::z); CONTEXT_FUNCTIONS.put(DirectContextParameters.Z, WorldPosition::z);
CONTEXT_FUNCTIONS.put(DirectContextParameters.YAW, WorldPosition::xRot); CONTEXT_FUNCTIONS.put(DirectContextParameters.YAW, WorldPosition::xRot);
CONTEXT_FUNCTIONS.put(DirectContextParameters.PITCH, WorldPosition::yRot); CONTEXT_FUNCTIONS.put(DirectContextParameters.PITCH, WorldPosition::yRot);
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.fastFloor(p.x())); CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.floor(p.x()));
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.fastFloor(p.y())); CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.floor(p.y()));
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.fastFloor(p.z())); CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.floor(p.z()));
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")

View File

@@ -1,453 +1,362 @@
package net.momirealms.craftengine.core.plugin.entityculling; 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.util.MiscUtils;
import net.momirealms.craftengine.core.world.ChunkPos;
import net.momirealms.craftengine.core.world.MutableVec3d; import net.momirealms.craftengine.core.world.MutableVec3d;
import net.momirealms.craftengine.core.world.Vec3d; 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.Arrays;
import java.util.BitSet;
public class EntityCulling { public final class EntityCulling {
public static final int MAX_SAMPLES = 14;
// 面掩码常量 private final Player player;
private static final int ON_MIN_X = 0x01; private final int maxDistance;
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;
private final double aabbExpansion; private final double aabbExpansion;
private final DataProvider provider; private final boolean[] dotSelectors = new boolean[MAX_SAMPLES];
private final OcclusionCache cache; 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;
// 重用数据结构减少GC压力 public EntityCulling(Player player, int maxDistance, double aabbExpansion) {
private final BitSet skipList = new BitSet(); this.player = player;
private final MutableVec3d[] targetPoints = new MutableVec3d[15]; this.maxDistance = maxDistance;
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;
this.aabbExpansion = aabbExpansion; this.aabbExpansion = aabbExpansion;
// 预先初始化点对象 for (int i = 0; i < MAX_SAMPLES; i++) {
for(int i = 0; i < targetPoints.length; i++) { this.targetPoints[i] = new MutableVec3d(0,0,0);
targetPoints[i] = new MutableVec3d(0, 0, 0);
} }
} }
public boolean isAABBVisible(Vec3d aabbMin, MutableVec3d aabbMax, MutableVec3d viewerPosition) { public boolean isVisible(AABB aabb, Vec3d cameraPos) {
try { // 情空标志位
// 计算包围盒范围 Arrays.fill(this.canCheckLastHitBlock, false);
int maxX = MiscUtils.fastFloor(aabbMax.x + aabbExpansion); this.hitBlockCount = 0;
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);
cameraPos[0] = MiscUtils.fastFloor(viewerPosition.x); // 根据AABB获取能包裹此AABB的最小长方体
cameraPos[1] = MiscUtils.fastFloor(viewerPosition.y); int minX = MiscUtils.floor(aabb.minX - this.aabbExpansion);
cameraPos[2] = MiscUtils.fastFloor(viewerPosition.z); 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);
// 判断是否在包围盒内部 double cameraX = cameraPos.x;
Relative relX = Relative.from(minX, maxX, cameraPos[0]); double cameraY = cameraPos.y;
Relative relY = Relative.from(minY, maxY, cameraPos[1]); double cameraZ = cameraPos.z;
Relative relZ = Relative.from(minZ, maxZ, cameraPos[2]);
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) { if (relX == Relative.INSIDE && relY == Relative.INSIDE && relZ == Relative.INSIDE) {
return true; return true;
} }
skipList.clear(); // 如果设置了最大距离
if (this.maxDistance > 0) {
// 1. 快速检查缓存 // 计算AABB到相机的最小距离
int id = 0; double distanceSq = 0.0;
for (int x = minX; x <= maxX; x++) { // 计算XYZ轴方向的距离
for (int y = minY; y <= maxY; y++) { distanceSq += distanceSq(minX, maxX, cameraX, relX);
for (int z = minZ; z <= maxZ; z++) { distanceSq += distanceSq(minY, maxY, cameraY, relY);
int cachedValue = getCacheValue(x, y, z); distanceSq += distanceSq(minZ, maxZ, cameraZ, relZ);
if (cachedValue == 1) return true; // 缓存显示可见 // 检查距离是否超过最大值
if (cachedValue != 0) skipList.set(id); // 缓存显示不可见或遮挡 double maxDistanceSq = this.maxDistance * this.maxDistance;
id++; // 超过最大距离,剔除
} if (distanceSq > maxDistanceSq) {
}
}
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; return false;
} catch (Throwable t) {
t.printStackTrace();
return true; // 发生异常默认可见,防止渲染错误
} }
} }
// 接口定义 // 清空之前的缓存
public interface DataProvider { Arrays.fill(this.dotSelectors, false);
boolean prepareChunk(int chunkX, int chunkZ); if (relX == Relative.POSITIVE) {
boolean isOpaqueFullCube(int x, int y, int z); this.dotSelectors[0] = this.dotSelectors[2] = this.dotSelectors[4] = this.dotSelectors[6] = this.dotSelectors[10] = true;
default void cleanup() {} } else if (relX == Relative.NEGATIVE) {
default void checkingPosition(MutableVec3d[] targetPoints, int size, MutableVec3d viewerPosition) {} 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);
private boolean isVoxelVisible(MutableVec3d viewerPosition, MutableVec3d position, byte faceData, byte visibleOnFace) { if (this.dotSelectors[2]) targetPoints[size++].set(minX, minY, maxZ);
int targetSize = 0; if (this.dotSelectors[3]) targetPoints[size++].set(maxX, minY, maxZ);
Arrays.fill(dotselectors, false); 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((visibleOnFace & ON_MIN_X) != 0){ if (this.dotSelectors[7]) targetPoints[size++].set(maxX, maxY, maxZ);
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);
// 面中心点 // 面中心点
if (dotselectors[8]) targetPoints[targetSize++].add(position, 0.05, 0.5, 0.5); double averageX = (minX + maxX) / 2.0;
if (dotselectors[9]) targetPoints[targetSize++].add(position, 0.5, 0.05, 0.5); double averageY = (minY + maxY) / 2.0;
if (dotselectors[10]) targetPoints[targetSize++].add(position, 0.5, 0.5, 0.05); double averageZ = (minZ + maxZ) / 2.0;
if (dotselectors[11]) targetPoints[targetSize++].add(position, 0.95, 0.5, 0.5); if (this.dotSelectors[8]) targetPoints[size++].set(averageX, averageY, minZ);
if (dotselectors[12]) targetPoints[targetSize++].add(position, 0.5, 0.95, 0.5); if (this.dotSelectors[9]) targetPoints[size++].set(averageX, averageY, maxZ);
if (dotselectors[13]) targetPoints[targetSize++].add(position, 0.5, 0.5, 0.95); 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); return isVisible(cameraPos, this.targetPoints, size);
}
// 优化:使用基本数据类型代替对象分配
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;
} }
/** /**
* 基于网格的光线追踪 (DDA算法) * 检测射线与轴对齐边界框(AABB)是否相交
* 使用slab方法进行射线-AABB相交检测
*/ */
private boolean isVisible(MutableVec3d start, MutableVec3d[] targets, int size) { private boolean rayIntersection(int x, int y, int z, Vec3d rayOrigin, MutableVec3d rayDirection) {
int startX = cameraPos[0]; // 计算射线方向的倒数,避免除法运算
int startY = cameraPos[1]; // 这对于处理射线方向分量为0的情况很重要
int startZ = cameraPos[2]; MutableVec3d inverseRayDirection = new MutableVec3d(1, 1, 1).divide(rayDirection);
for (int v = 0; v < size; v++) { // 计算射线与边界框各对面slab的相交参数
MutableVec3d target = targets[v]; // 对于每个轴,计算射线进入和退出该轴对应两个平面的时间
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 relX = start.x - target.x; // 计算射线进入边界框的最大时间(最近进入点)
double relY = start.y - target.y; // 需要取各轴进入时间的最大值,因为射线必须进入所有轴的范围内
double relZ = start.z - target.z; double tEntry = Math.max(Math.max(Math.min(tMinX, tMaxX), Math.min(tMinY, tMaxY)), Math.min(tMinZ, tMaxZ));
// 优化避免在此处创建新的Vec3d对象进行归一化 // 计算射线退出边界框的最短时间(最早退出点)
if(allowRayChecks) { // 需要取各轴退出时间的最小值,因为射线一旦退出任一轴的范围就离开了边界框
double len = Math.sqrt(relX * relX + relY * relY + relZ * relZ); double tExit = Math.min(Math.min(Math.max(tMinX, tMaxX), Math.max(tMinY, tMaxY)), Math.max(tMinZ, tMaxZ));
// 传入归一化后的方向分量
if (rayIntersection(lastHitBlock, start, relX / len, relY / len, relZ / len)) { // 如果最早退出时间大于0说明整个边界框在射线起点后面
continue; // 这种情况我们视为不相交,因为通常我们只关心射线前方的相交
} if (tExit > 0) {
return false;
} }
double dimAbsX = Math.abs(relX); // 如果进入时间大于退出时间,说明没有有效的相交区间
double dimAbsY = Math.abs(relY); // 这发生在射线完全错过边界框的情况下
double dimAbsZ = Math.abs(relZ); // 满足以下条件说明射线与边界框相交:
// 1. 进入时间 <= 退出时间(存在有效相交区间)
// 2. 退出时间 <= 0边界框至少有一部分在射线起点前方或包含起点
return tEntry <= tExit;
}
double dimFracX = 1f / dimAbsX; /**
double dimFracY = 1f / dimAbsY; * 使用3D DDA算法检测从起点到多个目标点的视线是否通畅
double dimFracZ = 1f / dimAbsZ; * 算法基于数字微分分析,遍历射线路径上的所有方块
*/
private boolean isVisible(Vec3d start, MutableVec3d[] targets, int targetCount) {
int intersectCount = 1; // 起点所在方块的整数坐标(世界坐标转换为方块坐标)
int x_inc, y_inc, z_inc; int startBlockX = MiscUtils.floor(start.x);
double t_next_y, t_next_x, t_next_z; int startBlockY = MiscUtils.floor(start.y);
int startBlockZ = MiscUtils.floor(start.z);
// 初始化DDA步进参数 // 遍历所有目标点进行视线检测
if (dimAbsX == 0f) { outer: for (int targetIndex = 0; targetIndex < targetCount; targetIndex++) {
x_inc = 0; t_next_x = dimFracX; MutableVec3d currentTarget = targets[targetIndex];
} else if (target.x > start.x) {
x_inc = 1; // 计算起点到目标的相对向量(世界坐标差)
intersectCount += MiscUtils.fastFloor(target.x) - startX; double deltaX = start.x - currentTarget.x;
t_next_x = (startX + 1 - start.x) * dimFracX; 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 { } else {
x_inc = -1; break;
intersectCount += startX - MiscUtils.fastFloor(target.x); }
t_next_x = (start.x - startX) * dimFracX;
} }
if (dimAbsY == 0f) { // 计算相对向量的绝对值,用于确定各方向上的距离
y_inc = 0; t_next_y = dimFracY; double absDeltaX = Math.abs(deltaX);
} else if (target.y > start.y) { double absDeltaY = Math.abs(deltaY);
y_inc = 1; double absDeltaZ = Math.abs(deltaZ);
intersectCount += MiscUtils.fastFloor(target.y) - startY;
t_next_y = (startY + 1 - start.y) * dimFracY; // 预计算每单位距离在各方块边界上的步进增量
// 这些值表示射线穿过一个方块所需的时间分数
double stepIncrementX = 1.0 / (absDeltaX + 1e-10); // 避免除0
double stepIncrementY = 1.0 / (absDeltaY + 1e-10);
double stepIncrementZ = 1.0 / (absDeltaZ + 1e-10);
// 射线将穿过的总方块数量(包括起点和终点)
int totalBlocksToCheck = 1;
// 各方块坐标的步进方向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 { } else {
y_inc = -1; // 目标在起点左侧,向左步进
intersectCount += startY - MiscUtils.fastFloor(target.y); stepDirectionX = -1;
t_next_y = (start.y - startY) * dimFracY; totalBlocksToCheck += startBlockX - MiscUtils.floor(currentTarget.x);
nextStepTimeX = (start.x - startBlockX) * stepIncrementX;
} }
if (dimAbsZ == 0f) { // Y方向步进参数计算
z_inc = 0; t_next_z = dimFracZ; if (absDeltaY == 0.0) {
} else if (target.z > start.z) { // Y方向无变化射线平行于XZ平面
z_inc = 1; stepDirectionY = 0;
intersectCount += MiscUtils.fastFloor(target.z) - startZ; nextStepTimeY = stepIncrementY;
t_next_z = (startZ + 1 - start.z) * dimFracZ; } else if (currentTarget.y > start.y) {
// 目标在起点上方,向上步进
stepDirectionY = 1;
totalBlocksToCheck += MiscUtils.floor(currentTarget.y) - startBlockY;
nextStepTimeY = (startBlockY + 1 - start.y) * stepIncrementY;
} else { } else {
z_inc = -1; // 目标在起点下方,向下步进
intersectCount += startZ - MiscUtils.fastFloor(target.z); stepDirectionY = -1;
t_next_z = (start.z - startZ) * dimFracZ; totalBlocksToCheck += startBlockY - MiscUtils.floor(currentTarget.y);
nextStepTimeY = (start.y - startBlockY) * stepIncrementY;
} }
boolean finished = stepRay(startX, startY, startZ, // Z方向步进参数计算
dimFracX, dimFracY, dimFracZ, intersectCount, if (absDeltaZ == 0.0) {
x_inc, y_inc, z_inc, // Z方向无变化射线平行于XY平面
t_next_y, t_next_x, t_next_z); 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 {
// 目标在起点后方,向后步进
stepDirectionZ = -1;
totalBlocksToCheck += startBlockZ - MiscUtils.floor(currentTarget.z);
nextStepTimeZ = (start.z - startBlockZ) * stepIncrementZ;
}
provider.cleanup(); // 执行DDA步进算法遍历射线路径上的所有方块
if (finished) { boolean isLineOfSightClear = stepRay(
cacheResult(targets[0], true); startBlockX, startBlockY, startBlockZ,
stepIncrementX, stepIncrementY, stepIncrementZ, totalBlocksToCheck,
stepDirectionX, stepDirectionY, stepDirectionZ,
nextStepTimeY, nextStepTimeX, nextStepTimeZ);
// 如果当前目标点可见立即返回
if (isLineOfSightClear) {
return true; return true;
} else { } else {
allowRayChecks = true; this.canCheckLastHitBlock[this.hitBlockCount++] = true;
} }
} }
cacheResult(targets[0], false);
return false; return false;
} }
private boolean stepRay(int currentX, int currentY, int currentZ, private boolean stepRay(int currentBlockX, int currentBlockY, int currentBlockZ,
double distInX, double distInY, double distInZ, double stepSizeX, double stepSizeY, double stepSizeZ,
int n, int x_inc, int y_inc, int z_inc, int remainingSteps, int stepDirectionX, int stepDirectionY,
double t_next_y, double t_next_x, double t_next_z) { int stepDirectionZ, double nextStepTimeY, double nextStepTimeX,
double nextStepTimeZ) {
allowWallClipping = true; // 初始允许穿墙直到移出起始方块 // 遍历射线路径上的所有方块(跳过最后一个目标方块
for (; remainingSteps > 1; remainingSteps--) {
for (; n > 1; n--) { // 检查当前方块是否遮挡视线
// 检查缓存状态2=遮挡 if (isOccluding(currentBlockX, currentBlockY, currentBlockZ)) {
int cVal = getCacheValue(currentX, currentY, currentZ); this.lastHitBlock[this.hitBlockCount * 3] = currentBlockX;
if (cVal == 2 && !allowWallClipping) { this.lastHitBlock[this.hitBlockCount * 3 + 1] = currentBlockY;
lastHitBlock[0] = currentX; lastHitBlock[1] = currentY; lastHitBlock[2] = currentZ; this.lastHitBlock[this.hitBlockCount * 3 + 2] = currentBlockZ;
return false; return false; // 视线被遮挡,立即返回
} }
if (cVal == 0) { // 基于时间参数选择下一个要遍历的方块方向
// 未缓存查询Provider // 选择距离最近的方块边界作为下一步
int chunkX = currentX >> 4; if (nextStepTimeY < nextStepTimeX && nextStepTimeY < nextStepTimeZ) {
int chunkZ = currentZ >> 4; // Y方向边界最近垂直移动
if (!provider.prepareChunk(chunkX, chunkZ)) return false; currentBlockY += stepDirectionY;
nextStepTimeY += stepSizeY;
if (provider.isOpaqueFullCube(currentX, currentY, currentZ)) { } else if (nextStepTimeX < nextStepTimeY && nextStepTimeX < nextStepTimeZ) {
if (!allowWallClipping) { // X方向边界最近水平移动
cache.setLastHidden(); currentBlockX += stepDirectionX;
lastHitBlock[0] = currentX; lastHitBlock[1] = currentY; lastHitBlock[2] = currentZ; nextStepTimeX += stepSizeX;
return false;
}
} else { } else {
allowWallClipping = false; // Z方向边界最近深度移动
cache.setLastVisible(); currentBlockZ += stepDirectionZ;
nextStepTimeZ += stepSizeZ;
} }
} 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;
} else {
currentZ += z_inc;
t_next_z += distInZ;
}
}
return true; return true;
} }
// 缓存状态:-1=无效, 0=未检查, 1=可见, 2=遮挡 private double distanceSq(int min, int max, double camera, Relative rel) {
private int getCacheValue(int x, int y, int z) { if (rel == Relative.NEGATIVE) {
x -= cameraPos[0]; double dx = camera - max;
y -= cameraPos[1]; return dx * dx;
z -= cameraPos[2]; } else if (rel == Relative.POSITIVE) {
if (Math.abs(x) > reach - 2 || Math.abs(y) > reach - 2 || Math.abs(z) > reach - 2) { double dx = min - camera;
return -1; return dx * dx;
} }
return cache.getState(x + reach, y + reach, z + reach); return 0d;
} }
private void cacheResult(MutableVec3d vector, boolean result) { private boolean isOccluding(int x, int y, int z) {
int cx = MiscUtils.fastFloor(vector.x) - cameraPos[0] + reach; int chunkX = x >> 4;
int cy = MiscUtils.fastFloor(vector.y) - cameraPos[1] + reach; int chunkZ = z >> 4;
int cz = MiscUtils.fastFloor(vector.z) - cameraPos[2] + reach; ClientChunk trackedChunk;
if (result) cache.setVisible(cx, cy, cz); // 使用上次记录的值比每次走hash都更快
else cache.setHidden(cx, cy, cz); 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 resetCache() { public void removeLastVisitChunkIfMatches(int chunkX, int chunkZ) {
this.cache.resetCache(); if (this.lastVisitChunk != null && this.lastVisitChunkX == chunkX && this.lastVisitChunkZ == chunkZ) {
this.lastVisitChunk = null;
}
} }
private enum Relative { private enum Relative {
INSIDE, POSITIVE, NEGATIVE; INSIDE, POSITIVE, NEGATIVE;
public static Relative from(int min, int max, int pos) { public static Relative from(int min, int max, double pos) {
if (max > pos && min > pos) return POSITIVE; if (min > pos) return POSITIVE;
else if (min < pos && max < pos) return NEGATIVE; else if (max < pos) return NEGATIVE;
return INSIDE; 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);
}
}
} }

View File

@@ -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<int[]> {
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;
}
}

View File

@@ -6,7 +6,8 @@ import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.core.plugin.Plugin; import net.momirealms.craftengine.core.plugin.Plugin;
import net.momirealms.craftengine.core.util.IntIdentityList; import net.momirealms.craftengine.core.util.IntIdentityList;
import net.momirealms.craftengine.core.util.Key; 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.ApiStatus;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -65,9 +66,7 @@ public interface NetWorkUser {
@ApiStatus.Internal @ApiStatus.Internal
ConnectionState encoderState(); ConnectionState encoderState();
int clientSideSectionCount(); World clientSideWorld();
Key clientSideDimension();
Object serverPlayer(); Object serverPlayer();
@@ -89,9 +88,9 @@ public interface NetWorkUser {
boolean isChunkTracked(long chunkPos); 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(); void clearTrackedChunks();

View File

@@ -38,7 +38,7 @@ public class Color {
} }
public static Color fromVector3f(Vector3f vec) { 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) { public static int opaque(int color) {

View File

@@ -21,19 +21,19 @@ public class MiscUtils {
} }
}); });
public static int fastFloor(double value) { public static int floor(double value) {
int truncated = (int) value; int truncated = (int) value;
return value < (double) truncated ? truncated - 1 : truncated; return value < (double) truncated ? truncated - 1 : truncated;
} }
public static int fastFloor(float value) { public static int floor(float value) {
int truncated = (int) value; int truncated = (int) value;
return value < (double) truncated ? truncated - 1 : truncated; return value < (double) truncated ? truncated - 1 : truncated;
} }
public static int lerpDiscrete(float delta, int start, int end) { public static int lerpDiscrete(float delta, int start, int end) {
int i = end - start; 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) { public static int murmurHash3Mixer(int value) {
@@ -270,7 +270,7 @@ public class MiscUtils {
} }
public static byte packDegrees(float degrees) { 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) { public static float unpackDegrees(byte degrees) {

View File

@@ -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.LocalizedResourceConfigException;
import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
import net.momirealms.craftengine.core.world.Vec3d; import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.collision.AABB;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.joml.Quaternionf; import org.joml.Quaternionf;
import org.joml.Vector3f; import org.joml.Vector3f;
@@ -341,4 +342,52 @@ public final class ResourceConfigUtils {
} }
TranslationManager.instance().log(e.node(), e.arguments()); 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);
}
}
}
} }

View File

@@ -23,7 +23,7 @@ public class BlockPos extends Vec3i {
} }
public static BlockPos fromVec3d(Vec3d vec) { 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) { public static BlockPos of(long packedPos) {

View File

@@ -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);
}

View File

@@ -23,9 +23,9 @@ public class EntityHitResult {
} }
private BlockPos getBlockPos() { private BlockPos getBlockPos() {
int x = MiscUtils.fastFloor(this.position.x); int x = MiscUtils.floor(this.position.x);
int y = MiscUtils.fastFloor(this.position.y); int y = MiscUtils.floor(this.position.y);
int z = MiscUtils.fastFloor(this.position.z); int z = MiscUtils.floor(this.position.z);
if (this.direction == Direction.UP) { if (this.direction == Direction.UP) {
if (this.position.y % 1 == 0) { if (this.position.y % 1 == 0) {
y--; y--;

View File

@@ -14,9 +14,9 @@ public class MutableVec3d implements Position {
} }
public MutableVec3d toCenter() { public MutableVec3d toCenter() {
this.x = MiscUtils.fastFloor(x) + 0.5; this.x = MiscUtils.floor(x) + 0.5;
this.y = MiscUtils.fastFloor(y) + 0.5; this.y = MiscUtils.floor(y) + 0.5;
this.z = MiscUtils.fastFloor(z) + 0.5; this.z = MiscUtils.floor(z) + 0.5;
return this; return this;
} }

View File

@@ -17,4 +17,39 @@ public class SectionPos extends Vec3i {
public static int sectionRelative(int rel) { public static int sectionRelative(int rel) {
return rel & 15; 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;
}
} }

View File

@@ -15,7 +15,7 @@ public class Vec3d implements Position {
} }
public Vec3d toCenter() { 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) { public Vec3d add(Vec3d vec) {

View File

@@ -4,9 +4,9 @@ import net.momirealms.craftengine.core.util.Direction;
public class Vec3i implements Comparable<Vec3i> { public class Vec3i implements Comparable<Vec3i> {
public static final Vec3i ZERO = new Vec3i(0, 0, 0); public static final Vec3i ZERO = new Vec3i(0, 0, 0);
protected int x; public final int x;
protected int y; public final int y;
protected int z; public final int z;
public Vec3i(int x, int y, int z) { public Vec3i(int x, int y, int z) {
this.x = x; this.x = x;
@@ -30,21 +30,6 @@ public class Vec3i implements Comparable<Vec3i> {
return x == 0 && y == 0 && z == 0 ? this : new Vec3i(this.x() + x, this.y() + y, this.z() + z); 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 @Override
public boolean equals(Object object) { public boolean equals(Object object) {
return this == object || object instanceof Vec3i vec3i && this.x == vec3i.x && this.y == vec3i.y && this.z == vec3i.z; return this == object || object instanceof Vec3i vec3i && this.x == vec3i.x && this.y == vec3i.y && this.z == vec3i.z;

View File

@@ -82,10 +82,19 @@ public class ArrayPalette<T> implements Palette<T> {
return true; return true;
} }
} }
return false; return false;
} }
@Override
public boolean allMatch(Predicate<T> predicate) {
for (int i = 0; i < this.size; ++i) {
if (!predicate.test(this.array[i])) {
return false;
}
}
return true;
}
@Override @Override
public T get(int id) { public T get(int id) {
if (id >= 0 && id < this.size) { if (id >= 0 && id < this.size) {

View File

@@ -75,6 +75,16 @@ public class BiMapPalette<T> implements Palette<T> {
return false; return false;
} }
@Override
public boolean allMatch(Predicate<T> predicate) {
for (int i = 0; i < this.getSize(); ++i) {
if (!predicate.test(this.map.get(i))) {
return false;
}
}
return true;
}
@Override @Override
public T get(int id) { public T get(int id) {
T object = this.map.get(id); T object = this.map.get(id);

View File

@@ -11,8 +11,10 @@ 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.render.element.BlockEntityElementConfig;
import net.momirealms.craftengine.core.block.entity.tick.*; import net.momirealms.craftengine.core.block.entity.tick.*;
import net.momirealms.craftengine.core.entity.player.Player; 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.plugin.logger.Debugger;
import net.momirealms.craftengine.core.world.*; 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.DefaultBlockEntityRendererSerializer;
import net.momirealms.craftengine.core.world.chunk.serialization.DefaultBlockEntitySerializer; import net.momirealms.craftengine.core.world.chunk.serialization.DefaultBlockEntitySerializer;
import net.momirealms.sparrow.nbt.ListTag; import net.momirealms.sparrow.nbt.ListTag;
@@ -91,9 +93,13 @@ public class CEChunk {
public void spawnBlockEntities(Player player) { public void spawnBlockEntities(Player player) {
try { try {
this.renderLock.readLock().lock(); this.renderLock.readLock().lock();
if (Config.enableEntityCulling()) {
player.addTrackedBlockEntities(this.constantBlockEntityRenderers);
} else {
for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) { for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) {
renderer.show(player); renderer.show(player);
} }
}
for (DynamicBlockEntityRenderer renderer : this.dynamicBlockEntityRenderers.values()) { for (DynamicBlockEntityRenderer renderer : this.dynamicBlockEntityRenderers.values()) {
renderer.show(player); renderer.show(player);
} }
@@ -105,9 +111,13 @@ public class CEChunk {
public void despawnBlockEntities(Player player) { public void despawnBlockEntities(Player player) {
try { try {
this.renderLock.readLock().lock(); this.renderLock.readLock().lock();
if (Config.enableEntityCulling()) {
player.removeTrackedBlockEntities(this.constantBlockEntityRenderers.keySet());
} else {
for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) { for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) {
renderer.hide(player); renderer.hide(player);
} }
}
for (DynamicBlockEntityRenderer renderer : this.dynamicBlockEntityRenderers.values()) { for (DynamicBlockEntityRenderer renderer : this.dynamicBlockEntityRenderers.values()) {
renderer.hide(player); renderer.hide(player);
} }
@@ -129,7 +139,7 @@ public class CEChunk {
BlockEntityElementConfig<? extends BlockEntityElement>[] renderers = state.constantRenderers(); BlockEntityElementConfig<? extends BlockEntityElement>[] renderers = state.constantRenderers();
if (renderers != null && renderers.length > 0) { if (renderers != null && renderers.length > 0) {
BlockEntityElement[] elements = new BlockEntityElement[renderers.length]; 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(); World wrappedWorld = this.world.world();
List<Player> trackedBy = getTrackedBy(); List<Player> trackedBy = getTrackedBy();
boolean hasTrackedBy = trackedBy != null && !trackedBy.isEmpty(); boolean hasTrackedBy = trackedBy != null && !trackedBy.isEmpty();
@@ -146,22 +156,43 @@ public class CEChunk {
if (element != null) { if (element != null) {
elements[0] = element; elements[0] = element;
if (hasTrackedBy) { if (hasTrackedBy) {
// 如果启用实体剔除,那么只对已经渲染的进行变换
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) { for (Player player : trackedBy) {
element.transform(player); element.transform(player);
} }
} }
}
break outer; break outer;
} }
} }
BlockEntityElement element = config.create(wrappedWorld, pos); BlockEntityElement element = config.create(wrappedWorld, pos);
elements[0] = element; elements[0] = element;
if (hasTrackedBy) { if (hasTrackedBy) {
// 如果启用实体剔除,那么只添加记录
if (Config.enableEntityCulling()) {
for (Player player : trackedBy) {
player.addTrackedBlockEntity(pos, renderer);
}
}
// 否则直接显示
else {
for (Player player : trackedBy) { for (Player player : trackedBy) {
previousElement.hide(player); previousElement.hide(player);
element.show(player); element.show(player);
} }
} }
} }
}
} else { } else {
outer: for (int i = 0; i < elements.length; i++) { outer: for (int i = 0; i < elements.length; i++) {
BlockEntityElementConfig<? extends BlockEntityElement> config = renderers[i]; BlockEntityElementConfig<? extends BlockEntityElement> config = renderers[i];
@@ -173,10 +204,22 @@ public class CEChunk {
previousElements[j] = null; previousElements[j] = null;
elements[i] = newElement; elements[i] = newElement;
if (hasTrackedBy) { if (hasTrackedBy) {
// 如果启用实体剔除,那么只对已经渲染的进行变换
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) { for (Player player : trackedBy) {
newElement.transform(player); newElement.transform(player);
} }
} }
}
continue outer; continue outer;
} }
} }
@@ -184,11 +227,17 @@ public class CEChunk {
BlockEntityElement newElement = config.create(wrappedWorld, pos); BlockEntityElement newElement = config.create(wrappedWorld, pos);
elements[i] = newElement; elements[i] = newElement;
if (hasTrackedBy) { if (hasTrackedBy) {
if (Config.enableEntityCulling()) {
for (Player player : trackedBy) {
player.addTrackedBlockEntity(pos, renderer);
}
} else {
for (Player player : trackedBy) { for (Player player : trackedBy) {
newElement.show(player); newElement.show(player);
} }
} }
} }
}
if (hasTrackedBy) { if (hasTrackedBy) {
for (int i = 0; i < previousElements.length; i++) { for (int i = 0; i < previousElements.length; i++) {
BlockEntityElement previousElement = previousElements[i]; BlockEntityElement previousElement = previousElements[i];
@@ -205,11 +254,17 @@ public class CEChunk {
elements[i] = renderers[i].create(wrappedWorld, pos); elements[i] = renderers[i].create(wrappedWorld, pos);
} }
if (hasTrackedBy) { if (hasTrackedBy) {
if (Config.enableEntityCulling()) {
for (Player player : trackedBy) {
player.addTrackedBlockEntity(pos, renderer);
}
} else {
for (Player player : trackedBy) { for (Player player : trackedBy) {
renderer.show(player); renderer.show(player);
} }
} }
} }
}
try { try {
this.renderLock.writeLock().lock(); this.renderLock.writeLock().lock();
this.constantBlockEntityRenderers.put(pos, renderer); this.constantBlockEntityRenderers.put(pos, renderer);
@@ -233,11 +288,17 @@ public class CEChunk {
ConstantBlockEntityRenderer removed = this.constantBlockEntityRenderers.remove(pos); ConstantBlockEntityRenderer removed = this.constantBlockEntityRenderers.remove(pos);
if (removed != null) { if (removed != null) {
if (hide) { if (hide) {
if (Config.enableEntityCulling()) {
for (Player player : getTrackedBy()) {
player.removeTrackedBlockEntities(List.of(pos));
}
} else {
for (Player player : getTrackedBy()) { for (Player player : getTrackedBy()) {
removed.hide(player); removed.hide(player);
} }
} }
} }
}
return removed; return removed;
} finally { } finally {
this.renderLock.writeLock().unlock(); this.renderLock.writeLock().unlock();
@@ -439,7 +500,7 @@ public class CEChunk {
return Collections.unmodifiableCollection(this.blockEntities.values()); return Collections.unmodifiableCollection(this.blockEntities.values());
} }
public List<BlockPos> constantBlockEntityRenderers() { public List<BlockPos> constantBlockEntityRendererPositions() {
try { try {
this.renderLock.readLock().lock(); this.renderLock.readLock().lock();
return new ArrayList<>(this.constantBlockEntityRenderers.keySet()); return new ArrayList<>(this.constantBlockEntityRenderers.keySet());

View File

@@ -1,7 +0,0 @@
package net.momirealms.craftengine.core.world.chunk;
public class ChunkStatus {
public ChunkStatus() {
}
}

View File

@@ -29,6 +29,11 @@ public class IdListPalette<T> implements Palette<T> {
return true; return true;
} }
@Override
public boolean allMatch(Predicate<T> predicate) {
return true;
}
@Override @Override
public T get(int id) { public T get(int id) {
T object = this.idList.get(id); T object = this.idList.get(id);

View File

@@ -13,6 +13,8 @@ public interface Palette<T> {
boolean hasAny(Predicate<T> predicate); boolean hasAny(Predicate<T> predicate);
boolean allMatch(Predicate<T> predicate);
T get(int id); T get(int id);
int getSize(); int getSize();

View File

@@ -45,6 +45,15 @@ public class SingularPalette<T> implements Palette<T> {
} }
} }
@Override
public boolean allMatch(Predicate<T> predicate) {
if (this.entry == null) {
throw new IllegalStateException("Use of an uninitialized palette");
} else {
return predicate.test(this.entry);
}
}
@Override @Override
public T get(int id) { public T get(int id) {
if (this.entry != null && id == 0) { if (this.entry != null && id == 0) {

View File

@@ -0,0 +1,53 @@
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 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 | x & 15, 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)];
}
}

View File

@@ -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;
}
public boolean isOccluding(int x, int y, int z) {
return isOccluding((y << 4 | z) << 4 | x);
}
public boolean isOccluding(int index) {
return this.storage.isOccluding(index);
}
public void setOccluding(int x, int y, int z, boolean value) {
this.setOccluding((y << 4 | z) << 4 | x, value);
}
public 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;
}
}
}
}

View File

@@ -0,0 +1,6 @@
package net.momirealms.craftengine.core.world.chunk.client;
public interface ClientSectionOcclusionStorage {
boolean isOccluding(int index);
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,32 @@
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 {
public 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) {
if (this.isShown == shown) return;
this.isShown = shown;
if (shown) {
this.cullable.show(player);
} else {
this.cullable.hide(player);
}
}
}

View File

@@ -32,7 +32,7 @@ public final class DefaultChunkSerializer {
if (!blockEntities.isEmpty()) { if (!blockEntities.isEmpty()) {
chunkNbt.put("block_entities", blockEntities); chunkNbt.put("block_entities", blockEntities);
} }
ListTag blockEntityRenders = DefaultBlockEntityRendererSerializer.serialize(chunk.constantBlockEntityRenderers()); ListTag blockEntityRenders = DefaultBlockEntityRendererSerializer.serialize(chunk.constantBlockEntityRendererPositions());
if (!blockEntityRenders.isEmpty()) { if (!blockEntityRenders.isEmpty()) {
chunkNbt.put("block_entity_renderers", blockEntityRenders); chunkNbt.put("block_entity_renderers", blockEntityRenders);
} }

View File

@@ -35,6 +35,17 @@ public class AABB {
this.maxZ = Math.max(pos1.z, pos2.z); 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) { public AABB(BlockPos pos) {
this(pos.x(), pos.y(), pos.z(), pos.x() + 1, pos.y() + 1, pos.z() + 1); this(pos.x(), pos.y(), pos.z(), pos.x() + 1, pos.y() + 1, pos.z() + 1);
} }