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:
@@ -42,7 +42,7 @@ public class MythicItemDrop extends ItemDrop implements IItemDrop {
|
||||
context = ItemBuildContext.of(player);
|
||||
}
|
||||
}
|
||||
int amountInt = MiscUtils.fastFloor(amount + 0.5F);
|
||||
int amountInt = MiscUtils.floor(amount + 0.5F);
|
||||
ItemStack itemStack = this.customItem.buildItemStack(context, amountInt);
|
||||
return adapt(itemStack).amount(amountInt);
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ import java.util.*;
|
||||
|
||||
public final class BukkitBlockManager extends AbstractBlockManager {
|
||||
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_TRUE = FastNMS.INSTANCE.method$StatePredicate$always(true);
|
||||
private static BukkitBlockManager instance;
|
||||
@@ -80,12 +81,14 @@ public final class BukkitBlockManager extends AbstractBlockManager {
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
super.init();
|
||||
this.initMirrorRegistry();
|
||||
this.initFireBlock();
|
||||
this.deceiveBukkitRegistry();
|
||||
this.markVanillaNoteBlocks();
|
||||
this.findViewBlockingVanillaBlocks();
|
||||
Arrays.fill(this.immutableBlockStates, EmptyBlock.INSTANCE.defaultState());
|
||||
this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings); // 一定要预先初始化一次,预防id超出上限
|
||||
this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings, this::isViewBlockingBlock); // 一定要预先初始化一次,预防id超出上限
|
||||
}
|
||||
|
||||
public static BukkitBlockManager instance() {
|
||||
@@ -125,7 +128,7 @@ public final class BukkitBlockManager extends AbstractBlockManager {
|
||||
|
||||
@Override
|
||||
public void delayedLoad() {
|
||||
this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings); // 重置方块映射表
|
||||
this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings, this::isViewBlockingBlock); // 重置方块映射表
|
||||
super.delayedLoad();
|
||||
this.cachedVisualBlockStatePacket = VisualBlockStatePacket.create();
|
||||
for (BukkitServerPlayer player : BukkitNetworkManager.instance().onlineUsers()) {
|
||||
@@ -223,7 +226,7 @@ public final class BukkitBlockManager extends AbstractBlockManager {
|
||||
protected void applyPlatformSettings(ImmutableBlockState state) {
|
||||
DelegatingBlockState nmsState = (DelegatingBlockState) state.customBlockState().literalObject();
|
||||
nmsState.setBlockState(state);
|
||||
Object nmsVisualState = state.vanillaBlockState().literalObject();
|
||||
Object nmsVisualState = state.visualBlockState().literalObject();
|
||||
|
||||
BlockSettings settings = state.settings();
|
||||
try {
|
||||
@@ -240,8 +243,15 @@ public final class BukkitBlockManager extends AbstractBlockManager {
|
||||
boolean useShapeForLightOcclusion = settings.useShapeForLightOcclusion() == Tristate.UNDEFINED ? CoreReflections.field$BlockStateBase$useShapeForLightOcclusion.getBoolean(nmsVisualState) : settings.useShapeForLightOcclusion().asBoolean();
|
||||
CoreReflections.field$BlockStateBase$useShapeForLightOcclusion.set(nmsState, useShapeForLightOcclusion);
|
||||
CoreReflections.field$BlockStateBase$isRedstoneConductor.set(nmsState, settings.isRedstoneConductor().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE);
|
||||
|
||||
boolean suffocating = settings.isSuffocating() == Tristate.UNDEFINED ? (canBlockView(state.visualBlockState())) : (settings.isSuffocating().asBoolean());
|
||||
CoreReflections.field$BlockStateBase$isSuffocating.set(nmsState, suffocating ? ALWAYS_TRUE : ALWAYS_FALSE);
|
||||
CoreReflections.field$BlockStateBase$isSuffocating.set(nmsState, settings.isSuffocating().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE);
|
||||
CoreReflections.field$BlockStateBase$isViewBlocking.set(nmsState, settings.isViewBlocking() == Tristate.UNDEFINED ? settings.isSuffocating().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE : (settings.isViewBlocking().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE));
|
||||
CoreReflections.field$BlockStateBase$isViewBlocking.set(nmsState,
|
||||
settings.isViewBlocking() == Tristate.UNDEFINED ?
|
||||
(suffocating ? ALWAYS_TRUE : ALWAYS_FALSE) :
|
||||
(settings.isViewBlocking().asBoolean() ? ALWAYS_TRUE : ALWAYS_FALSE)
|
||||
);
|
||||
|
||||
DelegatingBlock nmsBlock = (DelegatingBlock) BlockStateUtils.getBlockOwner(nmsState);
|
||||
ObjectHolder<BlockShape> shapeHolder = nmsBlock.shapeDelegate();
|
||||
@@ -291,9 +301,16 @@ public final class BukkitBlockManager extends AbstractBlockManager {
|
||||
this.burnableBlocks.add(nmsBlock);
|
||||
}
|
||||
|
||||
Key vanillaBlockId = state.vanillaBlockState().ownerId();
|
||||
Key vanillaBlockId = state.visualBlockState().ownerId();
|
||||
BlockGenerator.field$CraftEngineBlock$isNoteBlock().set(nmsBlock, vanillaBlockId.equals(BlockKeys.NOTE_BLOCK));
|
||||
BlockGenerator.field$CraftEngineBlock$isTripwire().set(nmsBlock, vanillaBlockId.equals(BlockKeys.TRIPWIRE));
|
||||
if (vanillaBlockId.equals(BlockKeys.BARRIER)) {
|
||||
state.setRestoreBlockState(createBlockState("minecraft:glass"));
|
||||
} else {
|
||||
state.setRestoreBlockState(state.visualBlockState());
|
||||
}
|
||||
// 根据客户端的状态决定其是否阻挡视线
|
||||
super.viewBlockingBlocks[state.customBlockState().registryId()] = canBlockView(state.visualBlockState());
|
||||
} catch (ReflectiveOperationException e) {
|
||||
this.plugin.logger().warn("Failed to apply platform block settings for block state " + state, e);
|
||||
}
|
||||
@@ -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
|
||||
protected void setVanillaBlockTags(Key id, List<String> tags) {
|
||||
Object block = FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.BLOCK, KeyUtils.toResourceLocation(id));
|
||||
|
||||
@@ -20,7 +20,7 @@ public class BukkitCustomBlockStateWrapper extends AbstractBlockStateWrapper imp
|
||||
|
||||
@Override
|
||||
public BlockStateWrapper visualBlockState() {
|
||||
return getImmutableBlockState().map(ImmutableBlockState::vanillaBlockState).orElse(null);
|
||||
return getImmutableBlockState().map(ImmutableBlockState::visualBlockState).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -180,7 +180,7 @@ public class BukkitBlockBehavior extends AbstractBlockBehavior {
|
||||
public boolean isPathFindable(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
|
||||
Optional<ImmutableBlockState> optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(args[0]);
|
||||
if (optionalCustomState.isEmpty()) return false;
|
||||
BlockStateWrapper vanillaState = optionalCustomState.get().vanillaBlockState();
|
||||
BlockStateWrapper vanillaState = optionalCustomState.get().visualBlockState();
|
||||
if (vanillaState == null) return false;
|
||||
return FastNMS.INSTANCE.method$BlockStateBase$isPathFindable(vanillaState.literalObject(), VersionHelper.isOrAbove1_20_5() ? null : args[1], VersionHelper.isOrAbove1_20_5() ? null : args[2], args[isPathFindable$type]);
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ public class CropBlockBehavior extends BukkitBlockBehavior {
|
||||
if (isMaxAge(state))
|
||||
return InteractionResult.PASS;
|
||||
boolean sendSwing = false;
|
||||
Object visualState = state.vanillaBlockState().literalObject();
|
||||
Object visualState = state.visualBlockState().literalObject();
|
||||
Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState);
|
||||
if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) {
|
||||
boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState);
|
||||
@@ -158,7 +158,7 @@ public class CropBlockBehavior extends BukkitBlockBehavior {
|
||||
}
|
||||
ImmutableBlockState customState = optionalCustomState.get();
|
||||
boolean sendParticles = false;
|
||||
Object visualState = customState.vanillaBlockState().literalObject();
|
||||
Object visualState = customState.visualBlockState().literalObject();
|
||||
Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState);
|
||||
if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) {
|
||||
boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, level, pos, visualState);
|
||||
|
||||
@@ -147,7 +147,7 @@ public class FenceGateBlockBehavior extends BukkitBlockBehavior implements IsPat
|
||||
Player player = context.getPlayer();
|
||||
if (player == null) return;
|
||||
this.toggle(state, context.getLevel(), context.getClickedPos(), player);
|
||||
if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.vanillaBlockState().literalObject()), context.getHitResult(), (Item<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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ public class GrassBlockBehavior extends BukkitBlockBehavior {
|
||||
}
|
||||
boolean sendParticles = false;
|
||||
ImmutableBlockState customState = optionalCustomState.get();
|
||||
Object visualState = customState.vanillaBlockState().literalObject();
|
||||
Object visualState = customState.visualBlockState().literalObject();
|
||||
Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState);
|
||||
if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) {
|
||||
boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, level, blockPos, visualState);
|
||||
@@ -93,7 +93,7 @@ public class GrassBlockBehavior extends BukkitBlockBehavior {
|
||||
if (!block.isEmpty())
|
||||
return InteractionResult.PASS;
|
||||
boolean sendSwing = false;
|
||||
Object visualState = state.vanillaBlockState().literalObject();
|
||||
Object visualState = state.visualBlockState().literalObject();
|
||||
Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState);
|
||||
if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) {
|
||||
boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState);
|
||||
|
||||
@@ -115,7 +115,7 @@ public class SaplingBlockBehavior extends BukkitBlockBehavior {
|
||||
}
|
||||
ImmutableBlockState customState = optionalCustomState.get();
|
||||
boolean sendParticles = false;
|
||||
Object visualState = customState.vanillaBlockState().literalObject();
|
||||
Object visualState = customState.visualBlockState().literalObject();
|
||||
Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState);
|
||||
if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) {
|
||||
boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, level, blockPos, visualState);
|
||||
@@ -153,7 +153,7 @@ public class SaplingBlockBehavior extends BukkitBlockBehavior {
|
||||
if (ItemUtils.isEmpty(item) || !item.vanillaId().equals(ItemKeys.BONE_MEAL) || player == null || player.isAdventureMode())
|
||||
return InteractionResult.PASS;
|
||||
boolean sendSwing = false;
|
||||
Object visualState = state.vanillaBlockState().literalObject();
|
||||
Object visualState = state.visualBlockState().literalObject();
|
||||
Object visualStateBlock = BlockStateUtils.getBlockOwner(visualState);
|
||||
if (CoreReflections.clazz$BonemealableBlock.isInstance(visualStateBlock)) {
|
||||
boolean is = FastNMS.INSTANCE.method$BonemealableBlock$isValidBonemealTarget(visualStateBlock, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), visualState);
|
||||
|
||||
@@ -33,7 +33,7 @@ public class SeatBlockBehavior extends BukkitBlockBehavior implements EntityBloc
|
||||
|
||||
@Override
|
||||
public BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state) {
|
||||
return new SeatBlockEntity(pos, state, this.seats);
|
||||
return new SeatBlockEntity(pos, state, this.seats, this.directionProperty);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -120,7 +120,7 @@ public class TrapDoorBlockBehavior extends BukkitBlockBehavior implements IsPath
|
||||
Player player = context.getPlayer();
|
||||
if (player == null) return;
|
||||
this.toggle(state, context.getLevel(), context.getClickedPos(), player);
|
||||
if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.vanillaBlockState().literalObject()), context.getHitResult(), (Item<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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,12 @@ import net.momirealms.sparrow.nbt.CompoundTag;
|
||||
|
||||
public class SeatBlockEntity extends BlockEntity implements SeatOwner {
|
||||
private final Seat<SeatBlockEntity>[] seats;
|
||||
private final Property<HorizontalDirection> facing;
|
||||
|
||||
@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);
|
||||
this.facing = directionProperty;
|
||||
this.seats = new Seat[seats.length];
|
||||
for (int i = 0; i < seats.length; i++) {
|
||||
this.seats[i] = new BukkitSeat<>(this, seats[i]);
|
||||
@@ -38,10 +40,9 @@ public class SeatBlockEntity extends BlockEntity implements SeatOwner {
|
||||
}
|
||||
|
||||
public boolean spawnSeat(Player player) {
|
||||
Property<?> facing = super.blockState.owner().value().getProperty("facing");
|
||||
int yRot = 0;
|
||||
if (facing != null && facing.valueClass() == HorizontalDirection.class) {
|
||||
HorizontalDirection direction = (HorizontalDirection) super.blockState.get(facing);
|
||||
if (this.facing != null) {
|
||||
HorizontalDirection direction = super.blockState.get(facing);
|
||||
yRot = switch (direction) {
|
||||
case NORTH -> 0;
|
||||
case SOUTH -> 180;
|
||||
|
||||
@@ -61,6 +61,7 @@ public class BukkitSeatManager implements SeatManager, Listener {
|
||||
@Override
|
||||
public void delayedInit() {
|
||||
Bukkit.getPluginManager().registerEvents(this.dismountListener, this.plugin.javaPlugin());
|
||||
Bukkit.getPluginManager().registerEvents(this, this.plugin.javaPlugin());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -101,7 +101,7 @@ public class AxeItemBehavior extends ItemBehavior {
|
||||
|
||||
// resend swing if it's not interactable on client side
|
||||
if (!InteractUtils.isInteractable(
|
||||
bukkitPlayer, BlockStateUtils.fromBlockData(customState.vanillaBlockState().literalObject()),
|
||||
bukkitPlayer, BlockStateUtils.fromBlockData(customState.visualBlockState().literalObject()),
|
||||
context.getHitResult(), item
|
||||
) || player.isSecondaryUseActive()) {
|
||||
player.swingHand(context.getHand());
|
||||
|
||||
@@ -112,7 +112,7 @@ public class BlockItemBehavior extends BlockBoundItemBehavior {
|
||||
} else {
|
||||
ImmutableBlockState customState = optionalCustomState.get();
|
||||
// custom block
|
||||
if (!AdventureModeUtils.canPlace(context.getItem(), context.getLevel(), againstPos, Config.simplifyAdventurePlaceCheck() ? customState.vanillaBlockState().literalObject() : againstBlockState)) {
|
||||
if (!AdventureModeUtils.canPlace(context.getItem(), context.getLevel(), againstPos, Config.simplifyAdventurePlaceCheck() ? customState.visualBlockState().literalObject() : againstBlockState)) {
|
||||
return InteractionResult.FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,10 +77,10 @@ public class FlintAndSteelItemBehavior extends ItemBehavior {
|
||||
// 点击对象为自定义方块
|
||||
ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId);
|
||||
// 原版外观也可燃
|
||||
if (BlockStateUtils.isBurnable(immutableBlockState.vanillaBlockState().literalObject())) {
|
||||
if (BlockStateUtils.isBurnable(immutableBlockState.visualBlockState().literalObject())) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
BlockData vanillaBlockState = BlockStateUtils.fromBlockData(immutableBlockState.vanillaBlockState().literalObject());
|
||||
BlockData vanillaBlockState = BlockStateUtils.fromBlockData(immutableBlockState.visualBlockState().literalObject());
|
||||
// 点击的是方块上面,则只需要判断shift和可交互
|
||||
if (direction == Direction.UP) {
|
||||
// 客户端层面必须可交互
|
||||
|
||||
@@ -167,7 +167,7 @@ public class ItemEventListener implements Listener {
|
||||
|
||||
// fix client side issues
|
||||
if (action.isRightClick() && hitResult != null &&
|
||||
InteractUtils.canPlaceVisualBlock(player, BlockStateUtils.fromBlockData(immutableBlockState.vanillaBlockState().literalObject()), hitResult, itemInHand)) {
|
||||
InteractUtils.canPlaceVisualBlock(player, BlockStateUtils.fromBlockData(immutableBlockState.visualBlockState().literalObject()), hitResult, itemInHand)) {
|
||||
player.updateInventory();
|
||||
}
|
||||
|
||||
@@ -272,13 +272,13 @@ public class ItemEventListener implements Listener {
|
||||
if (immutableBlockState != null) {
|
||||
// client won't have sounds if the clientside block is interactable
|
||||
// so we should check and resend sounds on BlockPlaceEvent
|
||||
BlockData craftBlockData = BlockStateUtils.fromBlockData(immutableBlockState.vanillaBlockState().literalObject());
|
||||
BlockData craftBlockData = BlockStateUtils.fromBlockData(immutableBlockState.visualBlockState().literalObject());
|
||||
if (InteractUtils.isInteractable(player, craftBlockData, hitResult, itemInHand)) {
|
||||
if (!serverPlayer.isSecondaryUseActive()) {
|
||||
serverPlayer.setResendSound();
|
||||
}
|
||||
} else {
|
||||
if (BlockStateUtils.isReplaceable(immutableBlockState.customBlockState().literalObject()) && !BlockStateUtils.isReplaceable(immutableBlockState.vanillaBlockState().literalObject())) {
|
||||
if (BlockStateUtils.isReplaceable(immutableBlockState.customBlockState().literalObject()) && !BlockStateUtils.isReplaceable(immutableBlockState.visualBlockState().literalObject())) {
|
||||
serverPlayer.setResendSwing();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ public class DebugCleanCacheCommand extends BukkitCommandFeature<CommandSender>
|
||||
Set<BlockStateWrapper> ids = new HashSet<>();
|
||||
for (CustomBlock customBlock : instance.loadedBlocks().values()) {
|
||||
for (ImmutableBlockState state : customBlock.variantProvider().states()) {
|
||||
ids.add(state.vanillaBlockState());
|
||||
ids.add(state.visualBlockState());
|
||||
}
|
||||
}
|
||||
VisualBlockStateAllocator visualBlockStateAllocator = instance.blockParser().visualBlockStateAllocator();
|
||||
|
||||
@@ -283,10 +283,10 @@ public final class WorldStorageInjector {
|
||||
if (Config.enableLightSystem()) {
|
||||
if (previousImmutableBlockState.isEmpty()) {
|
||||
// 原版块到自定义块,只需要判断新块是否和客户端视觉一致
|
||||
updateLight(holder, newImmutableBlockState.vanillaBlockState().literalObject(), newState, x, y, z);
|
||||
updateLight(holder, newImmutableBlockState.visualBlockState().literalObject(), newState, x, y, z);
|
||||
} else {
|
||||
// 自定义块到自定义块
|
||||
updateLight$complex(holder, newImmutableBlockState.vanillaBlockState().literalObject(), newState, previousState, x, y, z);
|
||||
updateLight$complex(holder, newImmutableBlockState.visualBlockState().literalObject(), newState, previousState, x, y, z);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -311,7 +311,7 @@ public final class WorldStorageInjector {
|
||||
}
|
||||
if (Config.enableLightSystem()) {
|
||||
// 自定义块到原版块,只需要判断旧块是否和客户端一直
|
||||
BlockStateWrapper wrapper = previous.vanillaBlockState();
|
||||
BlockStateWrapper wrapper = previous.visualBlockState();
|
||||
if (wrapper != null) {
|
||||
updateLight(holder, wrapper.literalObject(), previousState, x, y, z);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import net.kyori.adventure.nbt.api.BinaryTagHolder;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.event.DataComponentValue;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.momirealms.craftengine.bukkit.api.BukkitAdaptors;
|
||||
import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks;
|
||||
import net.momirealms.craftengine.bukkit.api.CraftEngineFurniture;
|
||||
import net.momirealms.craftengine.bukkit.api.event.FurnitureAttemptBreakEvent;
|
||||
@@ -91,9 +92,12 @@ import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider;
|
||||
import net.momirealms.craftengine.core.util.*;
|
||||
import net.momirealms.craftengine.core.world.*;
|
||||
import net.momirealms.craftengine.core.world.chunk.CEChunk;
|
||||
import net.momirealms.craftengine.core.world.chunk.ChunkStatus;
|
||||
import net.momirealms.craftengine.core.world.chunk.Palette;
|
||||
import net.momirealms.craftengine.core.world.chunk.PalettedContainer;
|
||||
import net.momirealms.craftengine.core.world.chunk.client.ClientChunk;
|
||||
import net.momirealms.craftengine.core.world.chunk.client.ClientSection;
|
||||
import net.momirealms.craftengine.core.world.chunk.client.PackedOcclusionStorage;
|
||||
import net.momirealms.craftengine.core.world.chunk.client.SingularOcclusionStorage;
|
||||
import net.momirealms.craftengine.core.world.chunk.packet.BlockEntityData;
|
||||
import net.momirealms.craftengine.core.world.chunk.packet.MCSection;
|
||||
import net.momirealms.sparrow.nbt.CompoundTag;
|
||||
@@ -127,6 +131,7 @@ import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class BukkitNetworkManager implements NetworkManager, Listener, PluginMessageListener {
|
||||
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 vanillaBlocks = BlockStateUtils.vanillaBlockStateCount();
|
||||
int[] newMappings = new int[blockStateMappings.length];
|
||||
@@ -320,10 +325,11 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
|
||||
newMappings,
|
||||
newMappingsMOD,
|
||||
newMappings.length,
|
||||
RegistryUtils.currentBiomeRegistrySize()
|
||||
RegistryUtils.currentBiomeRegistrySize(),
|
||||
occlusionPredicate
|
||||
), this.packetIds.clientboundLevelChunkWithLightPacket(), "ClientboundLevelChunkWithLightPacket");
|
||||
registerS2CGamePacketListener(new SectionBlockUpdateListener(newMappings, newMappingsMOD), this.packetIds.clientboundSectionBlocksUpdatePacket(), "ClientboundSectionBlocksUpdatePacket");
|
||||
registerS2CGamePacketListener(new BlockUpdateListener(newMappings, newMappingsMOD), this.packetIds.clientboundBlockUpdatePacket(), "ClientboundBlockUpdatePacket");
|
||||
registerS2CGamePacketListener(new SectionBlockUpdateListener(newMappings, newMappingsMOD, occlusionPredicate), this.packetIds.clientboundSectionBlocksUpdatePacket(), "ClientboundSectionBlocksUpdatePacket");
|
||||
registerS2CGamePacketListener(new BlockUpdateListener(newMappings, newMappingsMOD, occlusionPredicate), this.packetIds.clientboundBlockUpdatePacket(), "ClientboundBlockUpdatePacket");
|
||||
registerS2CGamePacketListener(
|
||||
VersionHelper.isOrAbove1_21_4() ?
|
||||
new LevelParticleListener1_21_4(newMappings, newMappingsMOD) :
|
||||
@@ -1098,7 +1104,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
|
||||
if (player.isAdventureMode()) {
|
||||
if (Config.simplifyAdventureBreakCheck()) {
|
||||
ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(stateId);
|
||||
if (!player.canBreak(pos, state.vanillaBlockState().literalObject())) {
|
||||
if (!player.canBreak(pos, state.visualBlockState().literalObject())) {
|
||||
player.preventMiningBlock();
|
||||
return;
|
||||
}
|
||||
@@ -1360,7 +1366,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
|
||||
Key itemId = state.settings().itemId();
|
||||
// no item available
|
||||
if (itemId == null) return;
|
||||
Object vanillaBlock = FastNMS.INSTANCE.method$BlockState$getBlock(state.vanillaBlockState().literalObject());
|
||||
Object vanillaBlock = FastNMS.INSTANCE.method$BlockState$getBlock(state.visualBlockState().literalObject());
|
||||
Object vanillaBlockItem = FastNMS.INSTANCE.method$Block$asItem(vanillaBlock);
|
||||
if (vanillaBlockItem == null) return;
|
||||
Key addItemId = KeyUtils.namespacedKey2Key(item.getType().getKey());
|
||||
@@ -1435,9 +1441,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
|
||||
Object location = FastNMS.INSTANCE.field$ResourceKey$location(dimensionKey);
|
||||
World world = Bukkit.getWorld(Objects.requireNonNull(NamespacedKey.fromString(location.toString())));
|
||||
if (world != null) {
|
||||
int sectionCount = (world.getMaxHeight() - world.getMinHeight()) / 16;
|
||||
player.setClientSideSectionCount(sectionCount);
|
||||
player.setClientSideDimension(Key.of(location.toString()));
|
||||
player.setClientSideWorld(BukkitAdaptors.adapt(world));
|
||||
} else {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ClientboundLoginPacket: World " + location + " does not exist");
|
||||
}
|
||||
@@ -1465,10 +1469,9 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
|
||||
Object location = FastNMS.INSTANCE.field$ResourceKey$location(dimensionKey);
|
||||
World world = Bukkit.getWorld(Objects.requireNonNull(NamespacedKey.fromString(location.toString())));
|
||||
if (world != null) {
|
||||
int sectionCount = (world.getMaxHeight() - world.getMinHeight()) / 16;
|
||||
player.setClientSideSectionCount(sectionCount);
|
||||
player.setClientSideDimension(Key.of(location.toString()));
|
||||
player.setClientSideWorld(BukkitAdaptors.adapt(world));
|
||||
player.clearTrackedChunks();
|
||||
player.clearTrackedBlockEntities();
|
||||
} else {
|
||||
CraftEngine.instance().logger().warn("Failed to handle ClientboundRespawnPacket: World " + location + " does not exist");
|
||||
}
|
||||
@@ -1990,17 +1993,15 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
|
||||
private final IntIdentityList biomeList;
|
||||
private final IntIdentityList blockList;
|
||||
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.modBlockStateMapper = modBlockStateMapper;
|
||||
this.biomeList = new IntIdentityList(biomeRegistrySize);
|
||||
this.blockList = new IntIdentityList(blockRegistrySize);
|
||||
this.needsDowngrade = MiscUtils.ceilLog2(BlockStateUtils.vanillaBlockStateCount()) != MiscUtils.ceilLog2(blockRegistrySize);
|
||||
}
|
||||
|
||||
public int remapBlockState(int stateId, boolean enableMod) {
|
||||
return enableMod ? this.modBlockStateMapper[stateId] : this.blockStateMapper[stateId];
|
||||
this.occlusionPredicate = occlusionPredicate;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2012,6 +2013,8 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
|
||||
ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
|
||||
boolean named = !VersionHelper.isOrAbove1_20_2();
|
||||
|
||||
int[] remapper = user.clientModEnabled() ? this.modBlockStateMapper : this.blockStateMapper;
|
||||
|
||||
// 读取区块数据
|
||||
int heightmapsCount = 0;
|
||||
Map<Integer, long[]> heightmapsMap = null;
|
||||
@@ -2033,36 +2036,94 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
|
||||
buf.readBytes(chunkDataBytes);
|
||||
|
||||
// 客户端侧section数量很重要,不能读取此时玩家所在的真实世界,包具有滞后性
|
||||
int count = player.clientSideSectionCount();
|
||||
net.momirealms.craftengine.core.world.World clientSideWorld = player.clientSideWorld();
|
||||
WorldHeight worldHeight = clientSideWorld.worldHeight();
|
||||
int count = worldHeight.getSectionsCount();
|
||||
MCSection[] sections = new MCSection[count];
|
||||
FriendlyByteBuf chunkDataByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(chunkDataBytes));
|
||||
|
||||
boolean hasChangedAnyBlock = false;
|
||||
boolean hasGlobalPalette = false;
|
||||
|
||||
// 创建客户端侧世界(只在开启实体情况下创建)
|
||||
ClientSection[] clientSections = Config.enableEntityCulling() ? new ClientSection[count] : null;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
MCSection mcSection = new MCSection(user.clientBlockList(), this.blockList, this.biomeList);
|
||||
mcSection.readPacket(chunkDataByteBuf);
|
||||
|
||||
PalettedContainer<Integer> container = mcSection.blockStateContainer();
|
||||
// 重定向生物群系
|
||||
if (remapBiomes(user, mcSection.biomeContainer())) {
|
||||
hasChangedAnyBlock = true;
|
||||
}
|
||||
|
||||
Palette<Integer> palette = container.data().palette();
|
||||
if (palette.canRemap()) {
|
||||
if (palette.remapAndCheck(s -> remapBlockState(s, user.clientModEnabled()))) {
|
||||
|
||||
// 重定向方块
|
||||
if (palette.remapAndCheck(s -> remapper[s])) {
|
||||
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 {
|
||||
hasGlobalPalette = true;
|
||||
|
||||
PackedOcclusionStorage storage = null;
|
||||
if (clientSections != null) {
|
||||
storage = new PackedOcclusionStorage(false);
|
||||
clientSections[i] = new ClientSection(storage);
|
||||
}
|
||||
|
||||
for (int j = 0; j < 4096; j++) {
|
||||
int state = container.get(j);
|
||||
int newState = remapBlockState(state, user.clientModEnabled());
|
||||
|
||||
// 重定向方块
|
||||
int newState = remapper[state];
|
||||
if (newState != state) {
|
||||
container.set(j, newState);
|
||||
hasChangedAnyBlock = true;
|
||||
}
|
||||
|
||||
// 写入视线遮挡数据
|
||||
if (storage != null) {
|
||||
storage.set(j, this.occlusionPredicate.test(state));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sections[i] = mcSection;
|
||||
}
|
||||
|
||||
@@ -2127,13 +2188,17 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
|
||||
}
|
||||
|
||||
// 记录加载的区块
|
||||
player.addTrackedChunk(chunkPos.longKey, new ChunkStatus());
|
||||
player.addTrackedChunk(chunkPos.longKey, new ClientChunk(clientSections, worldHeight));
|
||||
|
||||
// 生成方块实体
|
||||
CEWorld ceWorld = BukkitWorldManager.instance().getWorld(player.world().uuid());
|
||||
CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(chunkPos.longKey);
|
||||
if (ceChunk != null) {
|
||||
ceChunk.spawnBlockEntities(player);
|
||||
CEWorld ceWorld = clientSideWorld.storageWorld();
|
||||
// 世界可能被卸载,因为包滞后
|
||||
if (ceWorld != null) {
|
||||
CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(chunkPos.longKey);
|
||||
if (ceChunk != null) {
|
||||
// 生成方块实体
|
||||
ceChunk.spawnBlockEntities(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2141,63 +2206,64 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
|
||||
public static class SectionBlockUpdateListener implements ByteBufferPacketListener {
|
||||
private final int[] blockStateMapper;
|
||||
private final int[] modBlockStateMapper;
|
||||
private final Predicate<Integer> occlusionPredicate;
|
||||
|
||||
public SectionBlockUpdateListener(int[] blockStateMapper, int[] modBlockStateMapper) {
|
||||
public SectionBlockUpdateListener(int[] blockStateMapper, int[] modBlockStateMapper, Predicate<Integer> occlusionPredicate) {
|
||||
this.blockStateMapper = blockStateMapper;
|
||||
this.modBlockStateMapper = modBlockStateMapper;
|
||||
this.occlusionPredicate = occlusionPredicate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(NetWorkUser user, ByteBufPacketEvent event) {
|
||||
if (user.clientModEnabled()) {
|
||||
FriendlyByteBuf buf = event.getBuffer();
|
||||
long pos = buf.readLong();
|
||||
int blocks = buf.readVarInt();
|
||||
short[] positions = new short[blocks];
|
||||
int[] states = new int[blocks];
|
||||
for (int i = 0; i < blocks; i++) {
|
||||
long k = buf.readVarLong();
|
||||
positions[i] = (short) ((int) (k & 4095L));
|
||||
states[i] = modBlockStateMapper[((int) (k >>> 12))];
|
||||
}
|
||||
buf.clear();
|
||||
buf.writeVarInt(event.packetID());
|
||||
buf.writeLong(pos);
|
||||
buf.writeVarInt(blocks);
|
||||
for (int i = 0; i < blocks; i++) {
|
||||
buf.writeVarLong((long) states[i] << 12 | positions[i]);
|
||||
}
|
||||
event.setChanged(true);
|
||||
} else {
|
||||
FriendlyByteBuf buf = event.getBuffer();
|
||||
long pos = buf.readLong();
|
||||
int blocks = buf.readVarInt();
|
||||
short[] positions = new short[blocks];
|
||||
int[] states = new int[blocks];
|
||||
for (int i = 0; i < blocks; i++) {
|
||||
long k = buf.readVarLong();
|
||||
positions[i] = (short) ((int) (k & 4095L));
|
||||
states[i] = blockStateMapper[((int) (k >>> 12))];
|
||||
}
|
||||
buf.clear();
|
||||
buf.writeVarInt(event.packetID());
|
||||
buf.writeLong(pos);
|
||||
buf.writeVarInt(blocks);
|
||||
for (int i = 0; i < blocks; i++) {
|
||||
buf.writeVarLong((long) states[i] << 12 | positions[i]);
|
||||
}
|
||||
event.setChanged(true);
|
||||
int[] remapper = user.clientModEnabled() ? this.modBlockStateMapper : this.blockStateMapper;
|
||||
FriendlyByteBuf buf = event.getBuffer();
|
||||
long sPos = buf.readLong();
|
||||
int blocks = buf.readVarInt();
|
||||
short[] positions = new short[blocks];
|
||||
int[] states = new int[blocks];
|
||||
|
||||
// 获取客户端侧区域
|
||||
ClientSection clientSection = null;
|
||||
if (Config.enableEntityCulling()) {
|
||||
SectionPos sectionPos = SectionPos.of(sPos);
|
||||
ClientChunk trackedChunk = user.getTrackedChunk(sectionPos.asChunkPos().longKey);
|
||||
clientSection = trackedChunk.sectionById(sectionPos.y);
|
||||
}
|
||||
|
||||
for (int i = 0; i < blocks; i++) {
|
||||
long k = buf.readVarLong();
|
||||
short posIndex = (short) ((int) (k & 4095L));
|
||||
positions[i] = posIndex;
|
||||
int beforeState = ((int) (k >>> 12));
|
||||
states[i] = remapper[beforeState];
|
||||
if (clientSection != null) {
|
||||
// 设置遮蔽状态
|
||||
BlockPos pos = SectionPos.unpackSectionRelativePos(posIndex);
|
||||
clientSection.setOccluding(pos.x, pos.y, pos.z, this.occlusionPredicate.test(beforeState));
|
||||
}
|
||||
}
|
||||
|
||||
buf.clear();
|
||||
buf.writeVarInt(event.packetID());
|
||||
buf.writeLong(sPos);
|
||||
buf.writeVarInt(blocks);
|
||||
for (int i = 0; i < blocks; i++) {
|
||||
buf.writeVarLong((long) states[i] << 12 | positions[i]);
|
||||
}
|
||||
event.setChanged(true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class BlockUpdateListener implements ByteBufferPacketListener {
|
||||
private final int[] blockStateMapper;
|
||||
private final int[] modBlockStateMapper;
|
||||
private final Predicate<Integer> occlusionPredicate;
|
||||
|
||||
public BlockUpdateListener(int[] blockStateMapper, int[] modBlockStateMapper) {
|
||||
public BlockUpdateListener(int[] blockStateMapper, int[] modBlockStateMapper, Predicate<Integer> occlusionPredicate) {
|
||||
this.blockStateMapper = blockStateMapper;
|
||||
this.modBlockStateMapper = modBlockStateMapper;
|
||||
this.occlusionPredicate = occlusionPredicate;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2205,6 +2271,12 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
|
||||
FriendlyByteBuf buf = event.getBuffer();
|
||||
BlockPos pos = buf.readBlockPos();
|
||||
int before = buf.readVarInt();
|
||||
if (Config.enableEntityCulling()) {
|
||||
ClientChunk trackedChunk = user.getTrackedChunk(ChunkPos.asLong(pos.x >> 4, pos.z >> 4));
|
||||
if (trackedChunk != null) {
|
||||
trackedChunk.setOccluding(pos.x, pos.y, pos.z, this.occlusionPredicate.test(before));
|
||||
}
|
||||
}
|
||||
if (user.clientModEnabled() && !BlockStateUtils.isVanillaBlock(before)) {
|
||||
return;
|
||||
}
|
||||
@@ -2378,6 +2450,13 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
|
||||
if (eventId != WorldEvents.BLOCK_BREAK_EFFECT) return;
|
||||
BlockPos blockPos = buf.readBlockPos();
|
||||
int state = buf.readInt();
|
||||
// 移除不透明设置
|
||||
if (Config.enableEntityCulling()) {
|
||||
ClientChunk trackedChunk = user.getTrackedChunk(ChunkPos.asLong(blockPos.x >> 4, blockPos.z >> 4));
|
||||
if (trackedChunk != null) {
|
||||
trackedChunk.setOccluding(blockPos.x, blockPos.y, blockPos.z, false);
|
||||
}
|
||||
}
|
||||
boolean global = buf.readBoolean();
|
||||
int newState = user.clientModEnabled() ? modBlockStateMapper[state] : blockStateMapper[state];
|
||||
Object blockState = BlockStateUtils.idToBlockState(state);
|
||||
|
||||
@@ -103,7 +103,7 @@ public record VisualBlockStatePacket(int[] data) implements ModPacket {
|
||||
for (int i = 0; i < serverSideBlockCount; i++) {
|
||||
ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(i + vanillaBlockStateCount);
|
||||
if (state.isEmpty()) continue;
|
||||
mappings[state.customBlockState().registryId() - vanillaBlockStateCount] = state.vanillaBlockState().registryId();
|
||||
mappings[state.customBlockState().registryId() - vanillaBlockStateCount] = state.visualBlockState().registryId();
|
||||
}
|
||||
return new VisualBlockStatePacket(mappings);
|
||||
}
|
||||
|
||||
@@ -4593,4 +4593,26 @@ public final class CoreReflections {
|
||||
"world.level.levelgen.feature.Feature"
|
||||
)
|
||||
);
|
||||
|
||||
public static final Class<?> clazz$EmptyBlockGetter = requireNonNull(
|
||||
BukkitReflectionUtils.findReobfOrMojmapClass(
|
||||
"world.level.BlockAccessAir",
|
||||
"world.level.EmptyBlockGetter"
|
||||
)
|
||||
);
|
||||
|
||||
public static final Method method$EmptyBlockGetter$values = requireNonNull(
|
||||
ReflectionUtils.getStaticMethod(clazz$EmptyBlockGetter, clazz$EmptyBlockGetter.arrayType())
|
||||
);
|
||||
|
||||
public static final Object instance$EmptyBlockGetter$INSTANCE;
|
||||
|
||||
static {
|
||||
try {
|
||||
Object[] values = (Object[]) method$EmptyBlockGetter$values.invoke(null);
|
||||
instance$EmptyBlockGetter$INSTANCE = values[0];
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new ReflectionInitException("Failed to init EmptyBlockGetter$INSTANCE", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import net.momirealms.craftengine.core.advancement.AdvancementType;
|
||||
import net.momirealms.craftengine.core.block.BlockStateWrapper;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.block.entity.BlockEntity;
|
||||
import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer;
|
||||
import net.momirealms.craftengine.core.entity.data.EntityData;
|
||||
import net.momirealms.craftengine.core.entity.player.GameMode;
|
||||
import net.momirealms.craftengine.core.entity.player.InteractionHand;
|
||||
@@ -31,6 +32,7 @@ import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.config.Config;
|
||||
import net.momirealms.craftengine.core.plugin.context.CooldownData;
|
||||
import net.momirealms.craftengine.core.plugin.entityculling.EntityCulling;
|
||||
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
|
||||
import net.momirealms.craftengine.core.plugin.network.ConnectionState;
|
||||
import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler;
|
||||
@@ -39,7 +41,8 @@ import net.momirealms.craftengine.core.sound.SoundSource;
|
||||
import net.momirealms.craftengine.core.util.*;
|
||||
import net.momirealms.craftengine.core.world.*;
|
||||
import net.momirealms.craftengine.core.world.World;
|
||||
import net.momirealms.craftengine.core.world.chunk.ChunkStatus;
|
||||
import net.momirealms.craftengine.core.world.chunk.client.ClientChunk;
|
||||
import net.momirealms.craftengine.core.world.chunk.client.VirtualCullableObject;
|
||||
import net.momirealms.craftengine.core.world.collision.AABB;
|
||||
import org.bukkit.*;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
@@ -85,8 +88,7 @@ public class BukkitServerPlayer extends Player {
|
||||
private Reference<org.bukkit.entity.Player> playerRef;
|
||||
private Reference<Object> serverPlayerRef;
|
||||
// client side dimension info
|
||||
private int sectionCount;
|
||||
private Key clientSideDimension;
|
||||
private World clientSideWorld;
|
||||
// check main hand/offhand interaction
|
||||
private int lastSuccessfulInteraction;
|
||||
// to prevent duplicated events
|
||||
@@ -124,7 +126,7 @@ public class BukkitServerPlayer extends Player {
|
||||
// cooldown data
|
||||
private CooldownData cooldownData;
|
||||
// tracked chunks
|
||||
private ConcurrentLong2ReferenceChainedHashTable<ChunkStatus> trackedChunks;
|
||||
private ConcurrentLong2ReferenceChainedHashTable<ClientChunk> trackedChunks;
|
||||
// entity view
|
||||
private Map<Integer, EntityPacketHandler> entityTypeView;
|
||||
// 通过指令或api设定的语言
|
||||
@@ -138,6 +140,10 @@ public class BukkitServerPlayer extends Player {
|
||||
private boolean isHackedBreak;
|
||||
// 上一次停止挖掘包发出的时间
|
||||
private int lastStopMiningTick;
|
||||
// 跟踪到的方块实体渲染器
|
||||
private final Map<BlockPos, VirtualCullableObject> trackedBlockEntityRenderers = new ConcurrentHashMap<>();
|
||||
|
||||
private final EntityCulling culling;
|
||||
|
||||
public BukkitServerPlayer(BukkitCraftEngine plugin, @Nullable 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) {
|
||||
@@ -477,21 +484,13 @@ public class BukkitServerPlayer extends Player {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int clientSideSectionCount() {
|
||||
return sectionCount;
|
||||
}
|
||||
|
||||
public void setClientSideSectionCount(int sectionCount) {
|
||||
this.sectionCount = sectionCount;
|
||||
public World clientSideWorld() {
|
||||
return this.clientSideWorld;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key clientSideDimension() {
|
||||
return clientSideDimension;
|
||||
}
|
||||
|
||||
public void setClientSideDimension(Key clientSideDimension) {
|
||||
this.clientSideDimension = clientSideDimension;
|
||||
public void setClientSideWorld(World world) {
|
||||
this.clientSideWorld = world;
|
||||
}
|
||||
|
||||
public void setConnectionState(ConnectionState connectionState) {
|
||||
@@ -564,6 +563,15 @@ public class BukkitServerPlayer extends Player {
|
||||
this.predictNextBlockToMine();
|
||||
}
|
||||
}
|
||||
if (Config.enableEntityCulling()) {
|
||||
long nano1 = System.nanoTime();
|
||||
for (VirtualCullableObject cullableObject : this.trackedBlockEntityRenderers.values()) {
|
||||
boolean visible = this.culling.isVisible(cullableObject.cullable.aabb(), LocationUtils.toVec3d(platformPlayer().getEyeLocation()));
|
||||
cullableObject.setShown(this, visible);
|
||||
}
|
||||
long nano2 = System.nanoTime();
|
||||
//CraftEngine.instance().logger().info("EntityCulling took " + (nano2 - nano1) / 1_000_000d + "ms");
|
||||
}
|
||||
}
|
||||
|
||||
private void updateGUI() {
|
||||
@@ -646,7 +654,7 @@ public class BukkitServerPlayer extends Player {
|
||||
// instant break
|
||||
boolean custom = immutableBlockState != null;
|
||||
if (custom && getDestroyProgress(state, pos) >= 1f) {
|
||||
BlockStateWrapper vanillaBlockState = immutableBlockState.vanillaBlockState();
|
||||
BlockStateWrapper vanillaBlockState = immutableBlockState.visualBlockState();
|
||||
// if it's not an instant break on client side, we should resend level event
|
||||
if (vanillaBlockState != null && getDestroyProgress(vanillaBlockState.literalObject(), pos) < 1f) {
|
||||
Object levelEventPacket = FastNMS.INSTANCE.constructor$ClientboundLevelEventPacket(
|
||||
@@ -811,7 +819,7 @@ public class BukkitServerPlayer extends Player {
|
||||
// for simplified adventure break, switch mayBuild temporarily
|
||||
if (isAdventureMode() && Config.simplifyAdventureBreakCheck()) {
|
||||
// check the appearance state
|
||||
if (canBreak(hitPos, customState.vanillaBlockState().literalObject())) {
|
||||
if (canBreak(hitPos, customState.visualBlockState().literalObject())) {
|
||||
// Error might occur so we use try here
|
||||
try {
|
||||
FastNMS.INSTANCE.field$Player$mayBuild(serverPlayer, true);
|
||||
@@ -1180,12 +1188,12 @@ public class BukkitServerPlayer extends Player {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkStatus getTrackedChunk(long chunkPos) {
|
||||
public ClientChunk getTrackedChunk(long chunkPos) {
|
||||
return this.trackedChunks.get(chunkPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTrackedChunk(long chunkPos, ChunkStatus chunkStatus) {
|
||||
public void addTrackedChunk(long chunkPos, ClientChunk chunkStatus) {
|
||||
this.trackedChunks.put(chunkPos, chunkStatus);
|
||||
}
|
||||
|
||||
@@ -1300,4 +1308,36 @@ public class BukkitServerPlayer extends Player {
|
||||
public void sendTotemAnimation(Item<?> totem, @Nullable SoundData sound, boolean silent) {
|
||||
PlayerUtils.sendTotemAnimation(this, totem, sound, silent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTrackedBlockEntities(Map<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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ public final class EntityUtils {
|
||||
Object serverLevel = BukkitAdaptors.adapt(player.getWorld()).serverWorld();
|
||||
Object serverPlayer = FastNMS.INSTANCE.method$CraftPlayer$getHandle(player);
|
||||
for (Object pose : List.of(CoreReflections.instance$Pose$STANDING, CoreReflections.instance$Pose$CROUCHING, CoreReflections.instance$Pose$SWIMMING)) {
|
||||
BlockPos pos = new BlockPos(MiscUtils.fastFloor(x), MiscUtils.fastFloor(y), MiscUtils.fastFloor(z));
|
||||
BlockPos pos = new BlockPos(MiscUtils.floor(x), MiscUtils.floor(y), MiscUtils.floor(z));
|
||||
try {
|
||||
double floorHeight = (double) CoreReflections.method$BlockGetter$getBlockFloorHeight.invoke(serverLevel, LocationUtils.toBlockPos(pos));
|
||||
if (pos.y() + floorHeight > y + 0.75 || !isBlockFloorValid(floorHeight)) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import net.momirealms.craftengine.bukkit.plugin.injector.WorldStorageInjector;
|
||||
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
|
||||
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
|
||||
import net.momirealms.craftengine.bukkit.util.LocationUtils;
|
||||
import net.momirealms.craftengine.core.block.BlockStateWrapper;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.config.Config;
|
||||
@@ -296,9 +297,12 @@ public class BukkitWorldManager implements WorldManager, Listener {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
for (int y = 0; y < 16; y++) {
|
||||
ImmutableBlockState customState = ceSection.getBlockState(x, y, z);
|
||||
if (!customState.isEmpty() && customState.vanillaBlockState() != null) {
|
||||
FastNMS.INSTANCE.method$LevelChunkSection$setBlockState(section, x, y, z, customState.vanillaBlockState().literalObject(), false);
|
||||
unsaved = true;
|
||||
if (!customState.isEmpty()) {
|
||||
BlockStateWrapper wrapper = customState.restoreBlockState();
|
||||
if (wrapper != null) {
|
||||
FastNMS.INSTANCE.method$LevelChunkSection$setBlockState(section, x, y, z, wrapper.literalObject(), false);
|
||||
unsaved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,11 +552,10 @@ chunk-system:
|
||||
remove: []
|
||||
convert: {}
|
||||
|
||||
#client-optimization:
|
||||
# # Using server-side ray tracing algorithms to hide certain entities and reduce client-side rendering pressure.
|
||||
# entity-culling:
|
||||
# enable: false
|
||||
# whitelist-entities: []
|
||||
client-optimization:
|
||||
# Using server-side ray tracing algorithms to hide certain entities and reduce client-side rendering pressure.
|
||||
entity-culling:
|
||||
enable: false
|
||||
|
||||
# Enables or disables debug mode
|
||||
debug:
|
||||
|
||||
@@ -178,7 +178,7 @@ templates:
|
||||
# template: default:loot_table/ore
|
||||
# arguments:
|
||||
# ore_block: the ore block
|
||||
# ore_drop: the drops of the ore
|
||||
# ore_drop: the drops of the ore material
|
||||
# ore_drop_count: the amount of the ore materials
|
||||
# exp: the exp to drop
|
||||
default:loot_table/ore:
|
||||
|
||||
@@ -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.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.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.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>"
|
||||
|
||||
@@ -85,6 +85,8 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
// 临时存储哪些视觉方块被使用了
|
||||
protected final Set<BlockStateWrapper> tempVisualBlockStatesInUse = new HashSet<>();
|
||||
protected final Set<Key> tempVisualBlocksInUse = new HashSet<>();
|
||||
// 能遮挡视线的方块
|
||||
protected final boolean[] viewBlockingBlocks;
|
||||
// 声音映射表,和使用了哪些视觉方块有关
|
||||
protected Map<Key, Key> soundReplacements = Map.of();
|
||||
// 是否使用了透明方块模型
|
||||
@@ -102,6 +104,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
this.tempVanillaBlockStateModels = new JsonElement[vanillaBlockStateCount];
|
||||
this.blockParser = new BlockParser(this.autoVisualBlockStateCandidates);
|
||||
this.blockStateMappingParser = new BlockStateMappingParser();
|
||||
this.viewBlockingBlocks = new boolean[vanillaBlockStateCount + customBlockCount];
|
||||
Arrays.fill(this.blockStateMappings, -1);
|
||||
}
|
||||
|
||||
@@ -232,6 +235,10 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
|
||||
public abstract BlockBehavior createBlockBehavior(CustomBlock customBlock, List<Map<String, Object>> behaviorConfig);
|
||||
|
||||
public boolean isViewBlockingBlock(int stateId) {
|
||||
return this.viewBlockingBlocks[stateId];
|
||||
}
|
||||
|
||||
protected abstract void updateTags();
|
||||
|
||||
protected abstract boolean isVanillaBlock(Key id);
|
||||
@@ -593,7 +600,11 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
this.arrangeModelForStateAndVerify(visualBlockState, parseBlockModel(modelConfig));
|
||||
}
|
||||
}
|
||||
BlockStateAppearance blockStateAppearance = new BlockStateAppearance(visualBlockState, parseBlockEntityRender(appearanceSection.get("entity-renderer")));
|
||||
BlockStateAppearance blockStateAppearance = new BlockStateAppearance(
|
||||
visualBlockState,
|
||||
parseBlockEntityRender(appearanceSection.get("entity-renderer")),
|
||||
ResourceConfigUtils.getAsAABB(appearanceSection.getOrDefault("aabb", 1), "aabb")
|
||||
);
|
||||
appearances.put(appearanceName, blockStateAppearance);
|
||||
if (anyAppearance == null) {
|
||||
anyAppearance = blockStateAppearance;
|
||||
@@ -631,7 +642,8 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
continue;
|
||||
}
|
||||
for (ImmutableBlockState possibleState : possibleStates) {
|
||||
possibleState.setVanillaBlockState(appearance.blockState());
|
||||
possibleState.setVisualBlockState(appearance.blockState());
|
||||
possibleState.setEstimatedBoundingBox(appearance.estimateAABB());
|
||||
appearance.blockEntityRenderer().ifPresent(possibleState::setConstantRenderers);
|
||||
}
|
||||
}
|
||||
@@ -650,11 +662,12 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
}
|
||||
state.setBehavior(blockBehavior);
|
||||
int internalId = state.customBlockState().registryId();
|
||||
BlockStateWrapper visualState = state.vanillaBlockState();
|
||||
BlockStateWrapper visualState = state.visualBlockState();
|
||||
// 校验,为未绑定外观的强行添加外观
|
||||
if (visualState == null) {
|
||||
visualState = anyAppearance.blockState();
|
||||
state.setVanillaBlockState(visualState);
|
||||
state.setVisualBlockState(visualState);
|
||||
state.setEstimatedBoundingBox(anyAppearance.estimateAABB());
|
||||
anyAppearance.blockEntityRenderer().ifPresent(state::setConstantRenderers);
|
||||
}
|
||||
int appearanceId = visualState.registryId();
|
||||
|
||||
@@ -49,6 +49,7 @@ public final class BlockKeys {
|
||||
public static final Key TWISTING_VINES = Key.of("minecraft:twisting_vines");
|
||||
public static final Key KELP = Key.of("minecraft:kelp");
|
||||
public static final Key CHORUS_PLANT = Key.of("minecraft:chorus_plant");
|
||||
public static final Key BARRIER = Key.of("minecraft:barrier");
|
||||
|
||||
public static final Key CHEST = Key.of("minecraft:chest");
|
||||
public static final Key BARREL = Key.of("minecraft:barrel");
|
||||
|
||||
@@ -22,4 +22,8 @@ public final class BlockRegistryMirror {
|
||||
public static BlockStateWrapper stoneState() {
|
||||
return stoneState;
|
||||
}
|
||||
|
||||
public static BlockStateWrapper[] blockStates() {
|
||||
return blockStates;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ package net.momirealms.craftengine.core.block;
|
||||
|
||||
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
|
||||
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig;
|
||||
import net.momirealms.craftengine.core.world.collision.AABB;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public record BlockStateAppearance(BlockStateWrapper blockState, Optional<BlockEntityElementConfig<? extends BlockEntityElement>[]> blockEntityRenderer) {
|
||||
public record BlockStateAppearance(BlockStateWrapper blockState,
|
||||
Optional<BlockEntityElementConfig<? extends BlockEntityElement>[]> blockEntityRenderer,
|
||||
AABB estimateAABB) {
|
||||
}
|
||||
|
||||
@@ -17,9 +17,11 @@ import net.momirealms.craftengine.core.registry.Holder;
|
||||
import net.momirealms.craftengine.core.util.MiscUtils;
|
||||
import net.momirealms.craftengine.core.world.CEWorld;
|
||||
import net.momirealms.craftengine.core.world.World;
|
||||
import net.momirealms.craftengine.core.world.collision.AABB;
|
||||
import net.momirealms.sparrow.nbt.CompoundTag;
|
||||
import net.momirealms.sparrow.nbt.NBT;
|
||||
import net.momirealms.sparrow.nbt.Tag;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -33,12 +35,15 @@ public final class ImmutableBlockState {
|
||||
|
||||
private CompoundTag tag;
|
||||
private BlockStateWrapper customBlockState;
|
||||
private BlockStateWrapper vanillaBlockState;
|
||||
private BlockStateWrapper visualBlockState;
|
||||
// 安全的,在卸载ce后还原的方块
|
||||
private BlockStateWrapper restoreBlockState;
|
||||
private BlockBehavior behavior;
|
||||
private BlockSettings settings;
|
||||
private BlockEntityType<? extends BlockEntity> blockEntityType;
|
||||
@Nullable
|
||||
private BlockEntityElementConfig<? extends BlockEntityElement>[] renderers;
|
||||
private AABB estimatedBoundingBox;
|
||||
|
||||
ImmutableBlockState(
|
||||
Holder.Reference<CustomBlock> owner,
|
||||
@@ -84,6 +89,14 @@ public final class ImmutableBlockState {
|
||||
this.renderers = renderers;
|
||||
}
|
||||
|
||||
public void setEstimatedBoundingBox(AABB aabb) {
|
||||
this.estimatedBoundingBox = aabb;
|
||||
}
|
||||
|
||||
public AABB estimatedBoundingBox() {
|
||||
return estimatedBoundingBox;
|
||||
}
|
||||
|
||||
public boolean hasBlockEntity() {
|
||||
return this.blockEntityType != null;
|
||||
}
|
||||
@@ -96,16 +109,30 @@ public final class ImmutableBlockState {
|
||||
return this.customBlockState;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public BlockStateWrapper vanillaBlockState() {
|
||||
return this.vanillaBlockState;
|
||||
return this.visualBlockState;
|
||||
}
|
||||
|
||||
public BlockStateWrapper visualBlockState() {
|
||||
return this.visualBlockState;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public BlockStateWrapper restoreBlockState() {
|
||||
return this.restoreBlockState;
|
||||
}
|
||||
|
||||
public void setCustomBlockState(@NotNull BlockStateWrapper customBlockState) {
|
||||
this.customBlockState = customBlockState;
|
||||
}
|
||||
|
||||
public void setVanillaBlockState(@NotNull BlockStateWrapper vanillaBlockState) {
|
||||
this.vanillaBlockState = vanillaBlockState;
|
||||
public void setVisualBlockState(@NotNull BlockStateWrapper visualBlockState) {
|
||||
this.visualBlockState = visualBlockState;
|
||||
}
|
||||
|
||||
public void setRestoreBlockState(BlockStateWrapper restoreBlockState) {
|
||||
this.restoreBlockState = restoreBlockState;
|
||||
}
|
||||
|
||||
public CompoundTag getNbtToSave() {
|
||||
|
||||
@@ -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.entity.player.Player;
|
||||
import net.momirealms.craftengine.core.world.Cullable;
|
||||
import net.momirealms.craftengine.core.world.collision.AABB;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
@ApiStatus.Experimental
|
||||
public class ConstantBlockEntityRenderer {
|
||||
public class ConstantBlockEntityRenderer implements Cullable {
|
||||
private final BlockEntityElement[] elements;
|
||||
public final AABB aabb;
|
||||
|
||||
public ConstantBlockEntityRenderer(BlockEntityElement[] elements) {
|
||||
public ConstantBlockEntityRenderer(BlockEntityElement[] elements, AABB aabb) {
|
||||
this.elements = elements;
|
||||
this.aabb = aabb;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show(Player player) {
|
||||
for (BlockEntityElement element : this.elements) {
|
||||
if (element != null) {
|
||||
@@ -20,6 +25,7 @@ public class ConstantBlockEntityRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hide(Player player) {
|
||||
for (BlockEntityElement element : this.elements) {
|
||||
if (element != null) {
|
||||
@@ -47,4 +53,9 @@ public class ConstantBlockEntityRenderer {
|
||||
public BlockEntityElement[] elements() {
|
||||
return this.elements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AABB aabb() {
|
||||
return this.aabb;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package net.momirealms.craftengine.core.entity.player;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.craftengine.core.advancement.AdvancementType;
|
||||
import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer;
|
||||
import net.momirealms.craftengine.core.entity.AbstractEntity;
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.plugin.context.CooldownData;
|
||||
@@ -9,14 +10,14 @@ import net.momirealms.craftengine.core.plugin.network.NetWorkUser;
|
||||
import net.momirealms.craftengine.core.sound.SoundData;
|
||||
import net.momirealms.craftengine.core.sound.SoundSource;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.world.BlockPos;
|
||||
import net.momirealms.craftengine.core.world.Position;
|
||||
import net.momirealms.craftengine.core.world.Vec3d;
|
||||
import net.momirealms.craftengine.core.world.WorldPosition;
|
||||
import net.momirealms.craftengine.core.world.*;
|
||||
import net.momirealms.craftengine.core.world.chunk.client.VirtualCullableObject;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class Player extends AbstractEntity implements NetWorkUser {
|
||||
private static final Key TYPE = Key.of("minecraft:player");
|
||||
@@ -35,6 +36,8 @@ public abstract class Player extends AbstractEntity implements NetWorkUser {
|
||||
@Override
|
||||
public abstract Object serverPlayer();
|
||||
|
||||
public abstract void setClientSideWorld(World world);
|
||||
|
||||
public abstract float getDestroyProgress(Object blockState, BlockPos pos);
|
||||
|
||||
public abstract void setClientSideCanBreakBlock(boolean canBreak);
|
||||
@@ -198,6 +201,16 @@ public abstract class Player extends AbstractEntity implements NetWorkUser {
|
||||
|
||||
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
|
||||
public void remove() {
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ public class LootPool<T> {
|
||||
}
|
||||
if (this.compositeCondition.test(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) {
|
||||
this.addRandomItem(createFunctionApplier(consumer, context), context);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import net.momirealms.craftengine.core.plugin.compatibility.CompatibilityManager
|
||||
import net.momirealms.craftengine.core.plugin.compatibility.PluginTaskRegistry;
|
||||
import net.momirealms.craftengine.core.plugin.config.Config;
|
||||
import net.momirealms.craftengine.core.plugin.config.template.TemplateManager;
|
||||
import net.momirealms.craftengine.core.plugin.config.template.TemplateManagerImpl;
|
||||
import net.momirealms.craftengine.core.plugin.context.GlobalVariableManager;
|
||||
import net.momirealms.craftengine.core.plugin.dependency.Dependencies;
|
||||
import net.momirealms.craftengine.core.plugin.dependency.Dependency;
|
||||
|
||||
@@ -203,6 +203,8 @@ public class Config {
|
||||
protected boolean emoji$contexts$sign;
|
||||
protected int emoji$max_emojis_per_parse;
|
||||
|
||||
protected boolean client_optimization$entity_culling$enable;
|
||||
|
||||
public Config(CraftEngine plugin) {
|
||||
this.plugin = plugin;
|
||||
this.configVersion = PluginProperties.getValue("config");
|
||||
@@ -562,6 +564,9 @@ public class Config {
|
||||
emoji$contexts$sign = config.getBoolean("emoji.contexts.sign", true);
|
||||
emoji$max_emojis_per_parse = config.getInt("emoji.max-emojis-per-parse", 32);
|
||||
|
||||
// client optimization
|
||||
client_optimization$entity_culling$enable = config.getBoolean("client-optimization.entity-culling.enable", false);
|
||||
|
||||
firstTime = false;
|
||||
}
|
||||
|
||||
@@ -1152,6 +1157,10 @@ public class Config {
|
||||
return instance.resource_pack$optimization$texture$zopfli_iterations;
|
||||
}
|
||||
|
||||
public static boolean enableEntityCulling() {
|
||||
return instance.client_optimization$entity_culling$enable;
|
||||
}
|
||||
|
||||
public YamlDocument loadOrCreateYamlData(String fileName) {
|
||||
Path path = this.plugin.dataFolderPath().resolve(fileName);
|
||||
if (!Files.exists(path)) {
|
||||
|
||||
@@ -2,14 +2,7 @@ package net.momirealms.craftengine.core.plugin.config.template;
|
||||
|
||||
import net.momirealms.craftengine.core.plugin.Manageable;
|
||||
import net.momirealms.craftengine.core.plugin.config.ConfigParser;
|
||||
import net.momirealms.craftengine.core.plugin.config.template.argument.TemplateArgument;
|
||||
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.SNBTReader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface TemplateManager extends Manageable {
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package net.momirealms.craftengine.core.plugin.config.template.argument;
|
||||
import com.ezylang.evalex.Expression;
|
||||
import com.ezylang.evalex.data.EvaluationValue;
|
||||
import net.momirealms.craftengine.core.plugin.config.template.ArgumentString;
|
||||
import net.momirealms.craftengine.core.plugin.config.template.TemplateManager;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
@@ -40,7 +40,7 @@ public class MatchBlockCondition<CTX extends Context> implements Condition<CTX>
|
||||
Optional<WorldPosition> optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION);
|
||||
if (optionalWorldPosition.isPresent()) {
|
||||
World world = optionalWorldPosition.get().world();
|
||||
ExistingBlock blockAt = world.getBlock(MiscUtils.fastFloor(this.x.getDouble(ctx)), MiscUtils.fastFloor(this.y.getDouble(ctx)), MiscUtils.fastFloor(this.z.getDouble(ctx)));
|
||||
ExistingBlock blockAt = world.getBlock(MiscUtils.floor(this.x.getDouble(ctx)), MiscUtils.floor(this.y.getDouble(ctx)), MiscUtils.floor(this.z.getDouble(ctx)));
|
||||
return MiscUtils.matchRegex(blockAt.id().asString(), this.ids, this.regexMatch);
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -28,7 +28,7 @@ public class BreakBlockFunction<CTX extends Context> extends AbstractConditional
|
||||
@Override
|
||||
public void runInternal(CTX ctx) {
|
||||
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
|
||||
|
||||
@@ -55,9 +55,9 @@ public class CycleBlockPropertyFunction<CTX extends Context> extends AbstractCon
|
||||
Optional<WorldPosition> optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION);
|
||||
if (optionalWorldPosition.isEmpty()) return;
|
||||
World world = optionalWorldPosition.get().world();
|
||||
int x = MiscUtils.fastFloor(this.x.getDouble(ctx));
|
||||
int y = MiscUtils.fastFloor(this.y.getDouble(ctx));
|
||||
int z = MiscUtils.fastFloor(this.z.getDouble(ctx));
|
||||
int x = MiscUtils.floor(this.x.getDouble(ctx));
|
||||
int y = MiscUtils.floor(this.y.getDouble(ctx));
|
||||
int z = MiscUtils.floor(this.z.getDouble(ctx));
|
||||
BlockStateWrapper wrapper = updateBlockState(world.getBlock(x, y, z).blockState(), ctx);
|
||||
world.setBlockState(x, y, z, wrapper, this.updateFlags.getInt(ctx));
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ public class PlaceBlockFunction<CTX extends Context> extends AbstractConditional
|
||||
Optional<WorldPosition> optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION);
|
||||
if (optionalWorldPosition.isPresent()) {
|
||||
World world = optionalWorldPosition.get().world();
|
||||
world.setBlockState(MiscUtils.fastFloor(this.x.getDouble(ctx)), MiscUtils.fastFloor(this.y.getDouble(ctx)), MiscUtils.fastFloor(this.z.getDouble(ctx)), this.lazyBlockState.get(), this.updateFlags.getInt(ctx));
|
||||
world.setBlockState(MiscUtils.floor(this.x.getDouble(ctx)), MiscUtils.floor(this.y.getDouble(ctx)), MiscUtils.floor(this.z.getDouble(ctx)), this.lazyBlockState.get(), this.updateFlags.getInt(ctx));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ public class RunFunction<CTX extends Context> extends AbstractConditionalFunctio
|
||||
for (Function<CTX> function : functions) {
|
||||
function.run(ctx);
|
||||
}
|
||||
}, delay, pos.world().platformWorld(), MiscUtils.fastFloor(pos.x()) >> 4, MiscUtils.fastFloor(pos.z()) >> 4);
|
||||
}, delay, pos.world().platformWorld(), MiscUtils.floor(pos.x()) >> 4, MiscUtils.floor(pos.z()) >> 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,9 +45,9 @@ public class TransformBlockFunction<CTX extends Context> extends AbstractConditi
|
||||
Optional<WorldPosition> optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION);
|
||||
if (optionalWorldPosition.isPresent()) {
|
||||
World world = optionalWorldPosition.get().world();
|
||||
int x = MiscUtils.fastFloor(this.x.getDouble(ctx));
|
||||
int y = MiscUtils.fastFloor(this.y.getDouble(ctx));
|
||||
int z = MiscUtils.fastFloor(this.z.getDouble(ctx));
|
||||
int x = MiscUtils.floor(this.x.getDouble(ctx));
|
||||
int y = MiscUtils.floor(this.y.getDouble(ctx));
|
||||
int z = MiscUtils.floor(this.z.getDouble(ctx));
|
||||
BlockStateWrapper existingBlockState = world.getBlock(x, y, z).blockState().withProperties(this.properties);
|
||||
CompoundTag newProperties = new CompoundTag();
|
||||
for (String propertyName : existingBlockState.getPropertyNames()) {
|
||||
|
||||
@@ -40,9 +40,9 @@ public class UpdateBlockPropertyFunction<CTX extends Context> extends AbstractCo
|
||||
Optional<WorldPosition> optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION);
|
||||
if (optionalWorldPosition.isPresent()) {
|
||||
World world = optionalWorldPosition.get().world();
|
||||
int x = MiscUtils.fastFloor(this.x.getDouble(ctx));
|
||||
int y = MiscUtils.fastFloor(this.y.getDouble(ctx));
|
||||
int z = MiscUtils.fastFloor(this.z.getDouble(ctx));
|
||||
int x = MiscUtils.floor(this.x.getDouble(ctx));
|
||||
int y = MiscUtils.floor(this.y.getDouble(ctx));
|
||||
int z = MiscUtils.floor(this.z.getDouble(ctx));
|
||||
ExistingBlock blockAt = world.getBlock(x, y, z);
|
||||
BlockStateWrapper wrapper = blockAt.blockState().withProperties(this.properties);
|
||||
world.setBlockState(x, y, z, wrapper, this.updateFlags.getInt(ctx));
|
||||
|
||||
@@ -20,9 +20,9 @@ public class EntityParameterProvider implements ChainParameterProvider<Entity> {
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.YAW, Entity::xRot);
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.PITCH, Entity::yRot);
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.POSITION, Entity::position);
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.fastFloor(p.x()));
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.fastFloor(p.y()));
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.fastFloor(p.z()));
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.floor(p.x()));
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.floor(p.y()));
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.floor(p.z()));
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.NAME, Entity::name);
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.UUID, Entity::uuid);
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.WORLD, Entity::world);
|
||||
|
||||
@@ -21,9 +21,9 @@ public class PlayerParameterProvider implements ChainParameterProvider<Player> {
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.PITCH, Entity::xRot);
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.YAW, Entity::yRot);
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.POSITION, Entity::position);
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.fastFloor(p.x()));
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.fastFloor(p.y()));
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.fastFloor(p.z()));
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.floor(p.x()));
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.floor(p.y()));
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.floor(p.z()));
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.FOOD, Player::foodLevel);
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.SATURATION, Player::saturation);
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.NAME, Player::name);
|
||||
|
||||
@@ -20,9 +20,9 @@ public class PositionParameterProvider implements ChainParameterProvider<WorldPo
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.Z, WorldPosition::z);
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.YAW, WorldPosition::xRot);
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.PITCH, WorldPosition::yRot);
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.fastFloor(p.x()));
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.fastFloor(p.y()));
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.fastFloor(p.z()));
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.floor(p.x()));
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.floor(p.y()));
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.floor(p.z()));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -1,453 +1,362 @@
|
||||
package net.momirealms.craftengine.core.plugin.entityculling;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.player.Player;
|
||||
import net.momirealms.craftengine.core.util.MiscUtils;
|
||||
import net.momirealms.craftengine.core.world.ChunkPos;
|
||||
import net.momirealms.craftengine.core.world.MutableVec3d;
|
||||
import net.momirealms.craftengine.core.world.Vec3d;
|
||||
import net.momirealms.craftengine.core.world.chunk.client.ClientChunk;
|
||||
import net.momirealms.craftengine.core.world.collision.AABB;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
|
||||
public class EntityCulling {
|
||||
|
||||
// 面掩码常量
|
||||
private static final int ON_MIN_X = 0x01;
|
||||
private static final int ON_MAX_X = 0x02;
|
||||
private static final int ON_MIN_Y = 0x04;
|
||||
private static final int ON_MAX_Y = 0x08;
|
||||
private static final int ON_MIN_Z = 0x10;
|
||||
private static final int ON_MAX_Z = 0x20;
|
||||
|
||||
private final int reach;
|
||||
public final class EntityCulling {
|
||||
public static final int MAX_SAMPLES = 14;
|
||||
private final Player player;
|
||||
private final int maxDistance;
|
||||
private final double aabbExpansion;
|
||||
private final DataProvider provider;
|
||||
private final OcclusionCache cache;
|
||||
private final boolean[] dotSelectors = new boolean[MAX_SAMPLES];
|
||||
private final MutableVec3d[] targetPoints = new MutableVec3d[MAX_SAMPLES];
|
||||
private final int[] lastHitBlock = new int[MAX_SAMPLES * 3];
|
||||
private final boolean[] canCheckLastHitBlock = new boolean[MAX_SAMPLES];
|
||||
private int hitBlockCount = 0;
|
||||
private int lastVisitChunkX = Integer.MAX_VALUE;
|
||||
private int lastVisitChunkZ = Integer.MAX_VALUE;
|
||||
private ClientChunk lastVisitChunk = null;
|
||||
|
||||
// 重用数据结构,减少GC压力
|
||||
private final BitSet skipList = new BitSet();
|
||||
private final MutableVec3d[] targetPoints = new MutableVec3d[15];
|
||||
private final MutableVec3d targetPos = new MutableVec3d(0, 0, 0);
|
||||
private final int[] cameraPos = new int[3];
|
||||
private final boolean[] dotselectors = new boolean[14];
|
||||
private final int[] lastHitBlock = new int[3];
|
||||
|
||||
// 状态标志
|
||||
private boolean allowRayChecks = false;
|
||||
private boolean allowWallClipping = false;
|
||||
|
||||
public EntityCulling(int maxDistance, DataProvider provider) {
|
||||
this(maxDistance, provider, new ArrayOcclusionCache(maxDistance), 0.5);
|
||||
}
|
||||
|
||||
public EntityCulling(int maxDistance, DataProvider provider, OcclusionCache cache, double aabbExpansion) {
|
||||
this.reach = maxDistance;
|
||||
this.provider = provider;
|
||||
this.cache = cache;
|
||||
public EntityCulling(Player player, int maxDistance, double aabbExpansion) {
|
||||
this.player = player;
|
||||
this.maxDistance = maxDistance;
|
||||
this.aabbExpansion = aabbExpansion;
|
||||
// 预先初始化点对象
|
||||
for(int i = 0; i < targetPoints.length; i++) {
|
||||
targetPoints[i] = new MutableVec3d(0, 0, 0);
|
||||
for (int i = 0; i < MAX_SAMPLES; i++) {
|
||||
this.targetPoints[i] = new MutableVec3d(0,0,0);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAABBVisible(Vec3d aabbMin, MutableVec3d aabbMax, MutableVec3d viewerPosition) {
|
||||
try {
|
||||
// 计算包围盒范围
|
||||
int maxX = MiscUtils.fastFloor(aabbMax.x + aabbExpansion);
|
||||
int maxY = MiscUtils.fastFloor(aabbMax.y + aabbExpansion);
|
||||
int maxZ = MiscUtils.fastFloor(aabbMax.z + aabbExpansion);
|
||||
int minX = MiscUtils.fastFloor(aabbMin.x - aabbExpansion);
|
||||
int minY = MiscUtils.fastFloor(aabbMin.y - aabbExpansion);
|
||||
int minZ = MiscUtils.fastFloor(aabbMin.z - aabbExpansion);
|
||||
public boolean isVisible(AABB aabb, Vec3d cameraPos) {
|
||||
// 情空标志位
|
||||
Arrays.fill(this.canCheckLastHitBlock, false);
|
||||
this.hitBlockCount = 0;
|
||||
|
||||
cameraPos[0] = MiscUtils.fastFloor(viewerPosition.x);
|
||||
cameraPos[1] = MiscUtils.fastFloor(viewerPosition.y);
|
||||
cameraPos[2] = MiscUtils.fastFloor(viewerPosition.z);
|
||||
// 根据AABB获取能包裹此AABB的最小长方体
|
||||
int minX = MiscUtils.floor(aabb.minX - this.aabbExpansion);
|
||||
int minY = MiscUtils.floor(aabb.minY - this.aabbExpansion);
|
||||
int minZ = MiscUtils.floor(aabb.minZ - this.aabbExpansion);
|
||||
int maxX = MiscUtils.ceil(aabb.maxX + this.aabbExpansion);
|
||||
int maxY = MiscUtils.ceil(aabb.maxY + this.aabbExpansion);
|
||||
int maxZ = MiscUtils.ceil(aabb.maxZ + this.aabbExpansion);
|
||||
|
||||
// 判断是否在包围盒内部
|
||||
Relative relX = Relative.from(minX, maxX, cameraPos[0]);
|
||||
Relative relY = Relative.from(minY, maxY, cameraPos[1]);
|
||||
Relative relZ = Relative.from(minZ, maxZ, cameraPos[2]);
|
||||
double cameraX = cameraPos.x;
|
||||
double cameraY = cameraPos.y;
|
||||
double cameraZ = cameraPos.z;
|
||||
|
||||
if(relX == Relative.INSIDE && relY == Relative.INSIDE && relZ == Relative.INSIDE) {
|
||||
return true;
|
||||
Relative relX = Relative.from(minX, maxX, cameraX);
|
||||
Relative relY = Relative.from(minY, maxY, cameraY);
|
||||
Relative relZ = Relative.from(minZ, maxZ, cameraZ);
|
||||
|
||||
// 相机位于实体内部
|
||||
if (relX == Relative.INSIDE && relY == Relative.INSIDE && relZ == Relative.INSIDE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果设置了最大距离
|
||||
if (this.maxDistance > 0) {
|
||||
// 计算AABB到相机的最小距离
|
||||
double distanceSq = 0.0;
|
||||
// 计算XYZ轴方向的距离
|
||||
distanceSq += distanceSq(minX, maxX, cameraX, relX);
|
||||
distanceSq += distanceSq(minY, maxY, cameraY, relY);
|
||||
distanceSq += distanceSq(minZ, maxZ, cameraZ, relZ);
|
||||
// 检查距离是否超过最大值
|
||||
double maxDistanceSq = this.maxDistance * this.maxDistance;
|
||||
// 超过最大距离,剔除
|
||||
if (distanceSq > maxDistanceSq) {
|
||||
return false;
|
||||
}
|
||||
|
||||
skipList.clear();
|
||||
|
||||
// 1. 快速检查缓存
|
||||
int id = 0;
|
||||
for (int x = minX; x <= maxX; x++) {
|
||||
for (int y = minY; y <= maxY; y++) {
|
||||
for (int z = minZ; z <= maxZ; z++) {
|
||||
int cachedValue = getCacheValue(x, y, z);
|
||||
if (cachedValue == 1) return true; // 缓存显示可见
|
||||
if (cachedValue != 0) skipList.set(id); // 缓存显示不可见或遮挡
|
||||
id++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
allowRayChecks = false;
|
||||
id = 0;
|
||||
|
||||
// 2. 遍历体素进行光线投射检查
|
||||
for (int x = minX; x <= maxX; x++) {
|
||||
// 预计算X轴面的可见性和边缘数据
|
||||
byte visibleOnFaceX = 0;
|
||||
byte faceEdgeDataX = 0;
|
||||
if (x == minX) { faceEdgeDataX |= ON_MIN_X; if (relX == Relative.POSITIVE) visibleOnFaceX |= ON_MIN_X; }
|
||||
if (x == maxX) { faceEdgeDataX |= ON_MAX_X; if (relX == Relative.NEGATIVE) visibleOnFaceX |= ON_MAX_X; }
|
||||
|
||||
for (int y = minY; y <= maxY; y++) {
|
||||
byte visibleOnFaceY = visibleOnFaceX;
|
||||
byte faceEdgeDataY = faceEdgeDataX;
|
||||
if (y == minY) { faceEdgeDataY |= ON_MIN_Y; if (relY == Relative.POSITIVE) visibleOnFaceY |= ON_MIN_Y; }
|
||||
if (y == maxY) { faceEdgeDataY |= ON_MAX_Y; if (relY == Relative.NEGATIVE) visibleOnFaceY |= ON_MAX_Y; }
|
||||
|
||||
for (int z = minZ; z <= maxZ; z++) {
|
||||
// 如果缓存已标记为不可见,跳过
|
||||
if(skipList.get(id++)) continue;
|
||||
|
||||
byte visibleOnFace = visibleOnFaceY;
|
||||
byte faceEdgeData = faceEdgeDataY;
|
||||
if (z == minZ) { faceEdgeData |= ON_MIN_Z; if (relZ == Relative.POSITIVE) visibleOnFace |= ON_MIN_Z; }
|
||||
if (z == maxZ) { faceEdgeData |= ON_MAX_Z; if (relZ == Relative.NEGATIVE) visibleOnFace |= ON_MAX_Z; }
|
||||
|
||||
if (visibleOnFace != 0) {
|
||||
targetPos.set(x, y, z);
|
||||
// 检查单个体素是否可见
|
||||
if (isVoxelVisible(viewerPosition, targetPos, faceEdgeData, visibleOnFace)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
return true; // 发生异常默认可见,防止渲染错误
|
||||
}
|
||||
}
|
||||
|
||||
// 接口定义
|
||||
public interface DataProvider {
|
||||
boolean prepareChunk(int chunkX, int chunkZ);
|
||||
boolean isOpaqueFullCube(int x, int y, int z);
|
||||
default void cleanup() {}
|
||||
default void checkingPosition(MutableVec3d[] targetPoints, int size, MutableVec3d viewerPosition) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查单个体素是否对观察者可见
|
||||
*/
|
||||
private boolean isVoxelVisible(MutableVec3d viewerPosition, MutableVec3d position, byte faceData, byte visibleOnFace) {
|
||||
int targetSize = 0;
|
||||
Arrays.fill(dotselectors, false);
|
||||
|
||||
// 根据相对位置选择需要检测的关键点(角点和面中心点)
|
||||
if((visibleOnFace & ON_MIN_X) != 0){
|
||||
dotselectors[0] = true;
|
||||
if((faceData & ~ON_MIN_X) != 0) { dotselectors[1] = dotselectors[4] = dotselectors[5] = true; }
|
||||
dotselectors[8] = true;
|
||||
}
|
||||
if((visibleOnFace & ON_MIN_Y) != 0){
|
||||
dotselectors[0] = true;
|
||||
if((faceData & ~ON_MIN_Y) != 0) { dotselectors[3] = dotselectors[4] = dotselectors[7] = true; }
|
||||
dotselectors[9] = true;
|
||||
}
|
||||
if((visibleOnFace & ON_MIN_Z) != 0){
|
||||
dotselectors[0] = true;
|
||||
if((faceData & ~ON_MIN_Z) != 0) { dotselectors[1] = dotselectors[4] = dotselectors[5] = true; }
|
||||
dotselectors[10] = true;
|
||||
}
|
||||
if((visibleOnFace & ON_MAX_X) != 0){
|
||||
dotselectors[4] = true;
|
||||
if((faceData & ~ON_MAX_X) != 0) { dotselectors[5] = dotselectors[6] = dotselectors[7] = true; }
|
||||
dotselectors[11] = true;
|
||||
}
|
||||
if((visibleOnFace & ON_MAX_Y) != 0){
|
||||
dotselectors[1] = true;
|
||||
if((faceData & ~ON_MAX_Y) != 0) { dotselectors[2] = dotselectors[5] = dotselectors[6] = true; }
|
||||
dotselectors[12] = true;
|
||||
}
|
||||
if((visibleOnFace & ON_MAX_Z) != 0){
|
||||
dotselectors[2] = true;
|
||||
if((faceData & ~ON_MAX_Z) != 0) { dotselectors[3] = dotselectors[6] = dotselectors[7] = true; }
|
||||
dotselectors[13] = true;
|
||||
}
|
||||
|
||||
// 填充目标点,使用偏移量防止Z-Fighting或精度问题
|
||||
if (dotselectors[0]) targetPoints[targetSize++].add(position, 0.05, 0.05, 0.05);
|
||||
if (dotselectors[1]) targetPoints[targetSize++].add(position, 0.05, 0.95, 0.05);
|
||||
if (dotselectors[2]) targetPoints[targetSize++].add(position, 0.05, 0.95, 0.95);
|
||||
if (dotselectors[3]) targetPoints[targetSize++].add(position, 0.05, 0.05, 0.95);
|
||||
if (dotselectors[4]) targetPoints[targetSize++].add(position, 0.95, 0.05, 0.05);
|
||||
if (dotselectors[5]) targetPoints[targetSize++].add(position, 0.95, 0.95, 0.05);
|
||||
if (dotselectors[6]) targetPoints[targetSize++].add(position, 0.95, 0.95, 0.95);
|
||||
if (dotselectors[7]) targetPoints[targetSize++].add(position, 0.95, 0.05, 0.95);
|
||||
// 清空之前的缓存
|
||||
Arrays.fill(this.dotSelectors, false);
|
||||
if (relX == Relative.POSITIVE) {
|
||||
this.dotSelectors[0] = this.dotSelectors[2] = this.dotSelectors[4] = this.dotSelectors[6] = this.dotSelectors[10] = true;
|
||||
} else if (relX == Relative.NEGATIVE) {
|
||||
this.dotSelectors[1] = this.dotSelectors[3] = this.dotSelectors[5] = this.dotSelectors[7] = this.dotSelectors[11] = true;
|
||||
}
|
||||
if (relY == Relative.POSITIVE) {
|
||||
this.dotSelectors[0] = this.dotSelectors[1] = this.dotSelectors[2] = this.dotSelectors[3] = this.dotSelectors[12] = true;
|
||||
} else if (relY == Relative.NEGATIVE) {
|
||||
this.dotSelectors[4] = this.dotSelectors[5] = this.dotSelectors[6] = this.dotSelectors[7] = this.dotSelectors[13] = true;
|
||||
}
|
||||
if (relZ == Relative.POSITIVE) {
|
||||
this.dotSelectors[0] = this.dotSelectors[1] = this.dotSelectors[4] = this.dotSelectors[5] = this.dotSelectors[8] = true;
|
||||
} else if (relZ == Relative.NEGATIVE) {
|
||||
this.dotSelectors[2] = this.dotSelectors[3] = this.dotSelectors[6] = this.dotSelectors[7] = this.dotSelectors[9] = true;
|
||||
}
|
||||
|
||||
int size = 0;
|
||||
if (this.dotSelectors[0]) targetPoints[size++].set(minX, minY, minZ);
|
||||
if (this.dotSelectors[1]) targetPoints[size++].set(maxX, minY, minZ);
|
||||
if (this.dotSelectors[2]) targetPoints[size++].set(minX, minY, maxZ);
|
||||
if (this.dotSelectors[3]) targetPoints[size++].set(maxX, minY, maxZ);
|
||||
if (this.dotSelectors[4]) targetPoints[size++].set(minX, maxY, minZ);
|
||||
if (this.dotSelectors[5]) targetPoints[size++].set(maxX, maxY, minZ);
|
||||
if (this.dotSelectors[6]) targetPoints[size++].set(minX, maxY, maxZ);
|
||||
if (this.dotSelectors[7]) targetPoints[size++].set(maxX, maxY, maxZ);
|
||||
// 面中心点
|
||||
if (dotselectors[8]) targetPoints[targetSize++].add(position, 0.05, 0.5, 0.5);
|
||||
if (dotselectors[9]) targetPoints[targetSize++].add(position, 0.5, 0.05, 0.5);
|
||||
if (dotselectors[10]) targetPoints[targetSize++].add(position, 0.5, 0.5, 0.05);
|
||||
if (dotselectors[11]) targetPoints[targetSize++].add(position, 0.95, 0.5, 0.5);
|
||||
if (dotselectors[12]) targetPoints[targetSize++].add(position, 0.5, 0.95, 0.5);
|
||||
if (dotselectors[13]) targetPoints[targetSize++].add(position, 0.5, 0.5, 0.95);
|
||||
double averageX = (minX + maxX) / 2.0;
|
||||
double averageY = (minY + maxY) / 2.0;
|
||||
double averageZ = (minZ + maxZ) / 2.0;
|
||||
if (this.dotSelectors[8]) targetPoints[size++].set(averageX, averageY, minZ);
|
||||
if (this.dotSelectors[9]) targetPoints[size++].set(averageX, averageY, maxZ);
|
||||
if (this.dotSelectors[10]) targetPoints[size++].set(minX, averageY, averageZ);
|
||||
if (this.dotSelectors[11]) targetPoints[size++].set(maxX, averageY, averageZ);
|
||||
if (this.dotSelectors[12]) targetPoints[size++].set(averageX, minY, averageZ);
|
||||
if (this.dotSelectors[13]) targetPoints[size].set(averageX, maxY, averageZ);
|
||||
|
||||
return isVisible(viewerPosition, targetPoints, targetSize);
|
||||
}
|
||||
|
||||
// 优化:使用基本数据类型代替对象分配
|
||||
private boolean rayIntersection(int[] b, MutableVec3d rayOrigin, double dirX, double dirY, double dirZ) {
|
||||
double invX = 1.0 / dirX;
|
||||
double invY = 1.0 / dirY;
|
||||
double invZ = 1.0 / dirZ;
|
||||
|
||||
double t1 = (b[0] - rayOrigin.x) * invX;
|
||||
double t2 = (b[0] + 1 - rayOrigin.x) * invX;
|
||||
double t3 = (b[1] - rayOrigin.y) * invY;
|
||||
double t4 = (b[1] + 1 - rayOrigin.y) * invY;
|
||||
double t5 = (b[2] - rayOrigin.z) * invZ;
|
||||
double t6 = (b[2] + 1 - rayOrigin.z) * invZ;
|
||||
|
||||
double tmin = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4)), Math.min(t5, t6));
|
||||
double tmax = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4)), Math.max(t5, t6));
|
||||
|
||||
// tmax > 0: 射线与AABB相交,但AABB在身后
|
||||
// tmin > tmax: 射线不相交
|
||||
return tmax > 0 && tmin <= tmax;
|
||||
return isVisible(cameraPos, this.targetPoints, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于网格的光线追踪 (DDA算法)
|
||||
* 检测射线与轴对齐边界框(AABB)是否相交
|
||||
* 使用slab方法进行射线-AABB相交检测
|
||||
*/
|
||||
private boolean isVisible(MutableVec3d start, MutableVec3d[] targets, int size) {
|
||||
int startX = cameraPos[0];
|
||||
int startY = cameraPos[1];
|
||||
int startZ = cameraPos[2];
|
||||
private boolean rayIntersection(int x, int y, int z, Vec3d rayOrigin, MutableVec3d rayDirection) {
|
||||
// 计算射线方向的倒数,避免除法运算
|
||||
// 这对于处理射线方向分量为0的情况很重要
|
||||
MutableVec3d inverseRayDirection = new MutableVec3d(1, 1, 1).divide(rayDirection);
|
||||
|
||||
for (int v = 0; v < size; v++) {
|
||||
MutableVec3d target = targets[v];
|
||||
// 计算射线与边界框各对面(slab)的相交参数
|
||||
// 对于每个轴,计算射线进入和退出该轴对应两个平面的时间
|
||||
double tMinX = (x - rayOrigin.x) * inverseRayDirection.x;
|
||||
double tMaxX = (x + 1 - rayOrigin.x) * inverseRayDirection.x;
|
||||
double tMinY = (y - rayOrigin.y) * inverseRayDirection.y;
|
||||
double tMaxY = (y + 1 - rayOrigin.y) * inverseRayDirection.y;
|
||||
double tMinZ = (z - rayOrigin.z) * inverseRayDirection.z;
|
||||
double tMaxZ = (z + 1 - rayOrigin.z) * inverseRayDirection.z;
|
||||
|
||||
double 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);
|
||||
// 传入归一化后的方向分量
|
||||
if (rayIntersection(lastHitBlock, start, relX / len, relY / len, relZ / len)) {
|
||||
continue;
|
||||
// 计算射线退出边界框的最短时间(最早退出点)
|
||||
// 需要取各轴退出时间的最小值,因为射线一旦退出任一轴的范围就离开了边界框
|
||||
double tExit = Math.min(Math.min(Math.max(tMinX, tMaxX), Math.max(tMinY, tMaxY)), Math.max(tMinZ, tMaxZ));
|
||||
|
||||
// 如果最早退出时间大于0,说明整个边界框在射线起点后面
|
||||
// 这种情况我们视为不相交,因为通常我们只关心射线前方的相交
|
||||
if (tExit > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果进入时间大于退出时间,说明没有有效的相交区间
|
||||
// 这发生在射线完全错过边界框的情况下
|
||||
// 满足以下条件说明射线与边界框相交:
|
||||
// 1. 进入时间 <= 退出时间(存在有效相交区间)
|
||||
// 2. 退出时间 <= 0(边界框至少有一部分在射线起点前方或包含起点)
|
||||
return tEntry <= tExit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用3D DDA算法检测从起点到多个目标点的视线是否通畅
|
||||
* 算法基于数字微分分析,遍历射线路径上的所有方块
|
||||
*/
|
||||
private boolean isVisible(Vec3d start, MutableVec3d[] targets, int targetCount) {
|
||||
|
||||
// 起点所在方块的整数坐标(世界坐标转换为方块坐标)
|
||||
int startBlockX = MiscUtils.floor(start.x);
|
||||
int startBlockY = MiscUtils.floor(start.y);
|
||||
int startBlockZ = MiscUtils.floor(start.z);
|
||||
|
||||
// 遍历所有目标点进行视线检测
|
||||
outer: for (int targetIndex = 0; targetIndex < targetCount; targetIndex++) {
|
||||
MutableVec3d currentTarget = targets[targetIndex];
|
||||
|
||||
// 计算起点到目标的相对向量(世界坐标差)
|
||||
double deltaX = start.x - currentTarget.x;
|
||||
double deltaY = start.y - currentTarget.y;
|
||||
double deltaZ = start.z - currentTarget.z;
|
||||
|
||||
// 检查之前命中的方块,大概率还是命中
|
||||
for (int i = 0; i < MAX_SAMPLES; i++) {
|
||||
if (this.canCheckLastHitBlock[i]) {
|
||||
int offset = i * 3;
|
||||
if (rayIntersection(this.lastHitBlock[offset], this.lastHitBlock[offset + 1], this.lastHitBlock[offset + 2], start, new MutableVec3d(deltaX, deltaY, deltaZ).normalize())) {
|
||||
continue outer;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
double dimAbsX = Math.abs(relX);
|
||||
double dimAbsY = Math.abs(relY);
|
||||
double dimAbsZ = Math.abs(relZ);
|
||||
// 计算相对向量的绝对值,用于确定各方向上的距离
|
||||
double absDeltaX = Math.abs(deltaX);
|
||||
double absDeltaY = Math.abs(deltaY);
|
||||
double absDeltaZ = Math.abs(deltaZ);
|
||||
|
||||
double dimFracX = 1f / dimAbsX;
|
||||
double dimFracY = 1f / dimAbsY;
|
||||
double dimFracZ = 1f / dimAbsZ;
|
||||
// 预计算每单位距离在各方块边界上的步进增量
|
||||
// 这些值表示射线穿过一个方块所需的时间分数
|
||||
double stepIncrementX = 1.0 / (absDeltaX + 1e-10); // 避免除0
|
||||
double stepIncrementY = 1.0 / (absDeltaY + 1e-10);
|
||||
double stepIncrementZ = 1.0 / (absDeltaZ + 1e-10);
|
||||
|
||||
int intersectCount = 1;
|
||||
int x_inc, y_inc, z_inc;
|
||||
double t_next_y, t_next_x, t_next_z;
|
||||
// 射线将穿过的总方块数量(包括起点和终点)
|
||||
int totalBlocksToCheck = 1;
|
||||
|
||||
// 初始化DDA步进参数
|
||||
if (dimAbsX == 0f) {
|
||||
x_inc = 0; t_next_x = dimFracX;
|
||||
} else if (target.x > start.x) {
|
||||
x_inc = 1;
|
||||
intersectCount += MiscUtils.fastFloor(target.x) - startX;
|
||||
t_next_x = (startX + 1 - start.x) * dimFracX;
|
||||
// 各方块坐标的步进方向(1: 正向, -1: 反向, 0: 静止)
|
||||
int stepDirectionX, stepDirectionY, stepDirectionZ;
|
||||
|
||||
// 到下一个方块边界的时间参数(射线参数化表示)
|
||||
double nextStepTimeX, nextStepTimeY, nextStepTimeZ;
|
||||
|
||||
// X方向步进参数计算
|
||||
if (absDeltaX == 0.0) {
|
||||
// X方向无变化,射线平行于YZ平面
|
||||
stepDirectionX = 0;
|
||||
nextStepTimeX = stepIncrementX;
|
||||
} else if (currentTarget.x > start.x) {
|
||||
// 目标在起点右侧,向右步进
|
||||
stepDirectionX = 1;
|
||||
totalBlocksToCheck += MiscUtils.floor(currentTarget.x) - startBlockX;
|
||||
nextStepTimeX = (startBlockX + 1 - start.x) * stepIncrementX;
|
||||
} else {
|
||||
x_inc = -1;
|
||||
intersectCount += startX - MiscUtils.fastFloor(target.x);
|
||||
t_next_x = (start.x - startX) * dimFracX;
|
||||
// 目标在起点左侧,向左步进
|
||||
stepDirectionX = -1;
|
||||
totalBlocksToCheck += startBlockX - MiscUtils.floor(currentTarget.x);
|
||||
nextStepTimeX = (start.x - startBlockX) * stepIncrementX;
|
||||
}
|
||||
|
||||
if (dimAbsY == 0f) {
|
||||
y_inc = 0; t_next_y = dimFracY;
|
||||
} else if (target.y > start.y) {
|
||||
y_inc = 1;
|
||||
intersectCount += MiscUtils.fastFloor(target.y) - startY;
|
||||
t_next_y = (startY + 1 - start.y) * dimFracY;
|
||||
// Y方向步进参数计算
|
||||
if (absDeltaY == 0.0) {
|
||||
// Y方向无变化,射线平行于XZ平面
|
||||
stepDirectionY = 0;
|
||||
nextStepTimeY = stepIncrementY;
|
||||
} else if (currentTarget.y > start.y) {
|
||||
// 目标在起点上方,向上步进
|
||||
stepDirectionY = 1;
|
||||
totalBlocksToCheck += MiscUtils.floor(currentTarget.y) - startBlockY;
|
||||
nextStepTimeY = (startBlockY + 1 - start.y) * stepIncrementY;
|
||||
} else {
|
||||
y_inc = -1;
|
||||
intersectCount += startY - MiscUtils.fastFloor(target.y);
|
||||
t_next_y = (start.y - startY) * dimFracY;
|
||||
// 目标在起点下方,向下步进
|
||||
stepDirectionY = -1;
|
||||
totalBlocksToCheck += startBlockY - MiscUtils.floor(currentTarget.y);
|
||||
nextStepTimeY = (start.y - startBlockY) * stepIncrementY;
|
||||
}
|
||||
|
||||
if (dimAbsZ == 0f) {
|
||||
z_inc = 0; t_next_z = dimFracZ;
|
||||
} else if (target.z > start.z) {
|
||||
z_inc = 1;
|
||||
intersectCount += MiscUtils.fastFloor(target.z) - startZ;
|
||||
t_next_z = (startZ + 1 - start.z) * dimFracZ;
|
||||
// Z方向步进参数计算
|
||||
if (absDeltaZ == 0.0) {
|
||||
// Z方向无变化,射线平行于XY平面
|
||||
stepDirectionZ = 0;
|
||||
nextStepTimeZ = stepIncrementZ;
|
||||
} else if (currentTarget.z > start.z) {
|
||||
// 目标在起点前方,向前步进
|
||||
stepDirectionZ = 1;
|
||||
totalBlocksToCheck += MiscUtils.floor(currentTarget.z) - startBlockZ;
|
||||
nextStepTimeZ = (startBlockZ + 1 - start.z) * stepIncrementZ;
|
||||
} else {
|
||||
z_inc = -1;
|
||||
intersectCount += startZ - MiscUtils.fastFloor(target.z);
|
||||
t_next_z = (start.z - startZ) * dimFracZ;
|
||||
// 目标在起点后方,向后步进
|
||||
stepDirectionZ = -1;
|
||||
totalBlocksToCheck += startBlockZ - MiscUtils.floor(currentTarget.z);
|
||||
nextStepTimeZ = (start.z - startBlockZ) * stepIncrementZ;
|
||||
}
|
||||
|
||||
boolean finished = stepRay(startX, startY, startZ,
|
||||
dimFracX, dimFracY, dimFracZ, intersectCount,
|
||||
x_inc, y_inc, z_inc,
|
||||
t_next_y, t_next_x, t_next_z);
|
||||
// 执行DDA步进算法,遍历射线路径上的所有方块
|
||||
boolean isLineOfSightClear = stepRay(
|
||||
startBlockX, startBlockY, startBlockZ,
|
||||
stepIncrementX, stepIncrementY, stepIncrementZ, totalBlocksToCheck,
|
||||
stepDirectionX, stepDirectionY, stepDirectionZ,
|
||||
nextStepTimeY, nextStepTimeX, nextStepTimeZ);
|
||||
|
||||
provider.cleanup();
|
||||
if (finished) {
|
||||
cacheResult(targets[0], true);
|
||||
// 如果当前目标点可见立即返回
|
||||
if (isLineOfSightClear) {
|
||||
return true;
|
||||
} else {
|
||||
allowRayChecks = true;
|
||||
this.canCheckLastHitBlock[this.hitBlockCount++] = true;
|
||||
}
|
||||
}
|
||||
cacheResult(targets[0], false);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean stepRay(int currentX, int currentY, int currentZ,
|
||||
double distInX, double distInY, double distInZ,
|
||||
int n, int x_inc, int y_inc, int z_inc,
|
||||
double t_next_y, double t_next_x, double t_next_z) {
|
||||
private boolean stepRay(int currentBlockX, int currentBlockY, int currentBlockZ,
|
||||
double stepSizeX, double stepSizeY, double stepSizeZ,
|
||||
int remainingSteps, int stepDirectionX, int stepDirectionY,
|
||||
int stepDirectionZ, double nextStepTimeY, double nextStepTimeX,
|
||||
double nextStepTimeZ) {
|
||||
|
||||
allowWallClipping = true; // 初始允许穿墙直到移出起始方块
|
||||
// 遍历射线路径上的所有方块(跳过最后一个目标方块)
|
||||
for (; remainingSteps > 1; remainingSteps--) {
|
||||
|
||||
for (; n > 1; n--) {
|
||||
// 检查缓存状态:2=遮挡
|
||||
int cVal = getCacheValue(currentX, currentY, currentZ);
|
||||
if (cVal == 2 && !allowWallClipping) {
|
||||
lastHitBlock[0] = currentX; lastHitBlock[1] = currentY; lastHitBlock[2] = currentZ;
|
||||
return false;
|
||||
// 检查当前方块是否遮挡视线
|
||||
if (isOccluding(currentBlockX, currentBlockY, currentBlockZ)) {
|
||||
this.lastHitBlock[this.hitBlockCount * 3] = currentBlockX;
|
||||
this.lastHitBlock[this.hitBlockCount * 3 + 1] = currentBlockY;
|
||||
this.lastHitBlock[this.hitBlockCount * 3 + 2] = currentBlockZ;
|
||||
return false; // 视线被遮挡,立即返回
|
||||
}
|
||||
|
||||
if (cVal == 0) {
|
||||
// 未缓存,查询Provider
|
||||
int chunkX = currentX >> 4;
|
||||
int chunkZ = currentZ >> 4;
|
||||
if (!provider.prepareChunk(chunkX, chunkZ)) return false;
|
||||
|
||||
if (provider.isOpaqueFullCube(currentX, currentY, currentZ)) {
|
||||
if (!allowWallClipping) {
|
||||
cache.setLastHidden();
|
||||
lastHitBlock[0] = currentX; lastHitBlock[1] = currentY; lastHitBlock[2] = currentZ;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
allowWallClipping = false;
|
||||
cache.setLastVisible();
|
||||
}
|
||||
} else if(cVal == 1) {
|
||||
allowWallClipping = false;
|
||||
}
|
||||
|
||||
// DDA算法选择下一个体素
|
||||
if (t_next_y < t_next_x && t_next_y < t_next_z) {
|
||||
currentY += y_inc;
|
||||
t_next_y += distInY;
|
||||
} else if (t_next_x < t_next_y && t_next_x < t_next_z) {
|
||||
currentX += x_inc;
|
||||
t_next_x += distInX;
|
||||
// 基于时间参数选择下一个要遍历的方块方向
|
||||
// 选择距离最近的方块边界作为下一步
|
||||
if (nextStepTimeY < nextStepTimeX && nextStepTimeY < nextStepTimeZ) {
|
||||
// Y方向边界最近,垂直移动
|
||||
currentBlockY += stepDirectionY;
|
||||
nextStepTimeY += stepSizeY;
|
||||
} else if (nextStepTimeX < nextStepTimeY && nextStepTimeX < nextStepTimeZ) {
|
||||
// X方向边界最近,水平移动
|
||||
currentBlockX += stepDirectionX;
|
||||
nextStepTimeX += stepSizeX;
|
||||
} else {
|
||||
currentZ += z_inc;
|
||||
t_next_z += distInZ;
|
||||
// Z方向边界最近,深度移动
|
||||
currentBlockZ += stepDirectionZ;
|
||||
nextStepTimeZ += stepSizeZ;
|
||||
}
|
||||
}
|
||||
|
||||
// 成功遍历所有中间方块,视线通畅
|
||||
return true;
|
||||
}
|
||||
|
||||
// 缓存状态:-1=无效, 0=未检查, 1=可见, 2=遮挡
|
||||
private int getCacheValue(int x, int y, int z) {
|
||||
x -= cameraPos[0];
|
||||
y -= cameraPos[1];
|
||||
z -= cameraPos[2];
|
||||
if (Math.abs(x) > reach - 2 || Math.abs(y) > reach - 2 || Math.abs(z) > reach - 2) {
|
||||
return -1;
|
||||
private double distanceSq(int min, int max, double camera, Relative rel) {
|
||||
if (rel == Relative.NEGATIVE) {
|
||||
double dx = camera - max;
|
||||
return dx * dx;
|
||||
} else if (rel == Relative.POSITIVE) {
|
||||
double dx = min - camera;
|
||||
return dx * dx;
|
||||
}
|
||||
return cache.getState(x + reach, y + reach, z + reach);
|
||||
return 0d;
|
||||
}
|
||||
|
||||
private void cacheResult(MutableVec3d vector, boolean result) {
|
||||
int cx = MiscUtils.fastFloor(vector.x) - cameraPos[0] + reach;
|
||||
int cy = MiscUtils.fastFloor(vector.y) - cameraPos[1] + reach;
|
||||
int cz = MiscUtils.fastFloor(vector.z) - cameraPos[2] + reach;
|
||||
if (result) cache.setVisible(cx, cy, cz);
|
||||
else cache.setHidden(cx, cy, cz);
|
||||
private boolean isOccluding(int x, int y, int z) {
|
||||
int chunkX = x >> 4;
|
||||
int chunkZ = z >> 4;
|
||||
ClientChunk trackedChunk;
|
||||
// 使用上次记录的值,比每次走hash都更快
|
||||
if (chunkX == this.lastVisitChunkX && chunkZ == this.lastVisitChunkZ) {
|
||||
trackedChunk = this.lastVisitChunk;
|
||||
} else {
|
||||
trackedChunk = this.player.getTrackedChunk(ChunkPos.asLong(chunkX, chunkZ));
|
||||
this.lastVisitChunk = trackedChunk;
|
||||
this.lastVisitChunkX = chunkX;
|
||||
this.lastVisitChunkZ = chunkZ;
|
||||
}
|
||||
if (trackedChunk == null) {
|
||||
return false;
|
||||
}
|
||||
return trackedChunk.isOccluding(x, y, z);
|
||||
}
|
||||
|
||||
public void resetCache() {
|
||||
this.cache.resetCache();
|
||||
public void removeLastVisitChunkIfMatches(int chunkX, int chunkZ) {
|
||||
if (this.lastVisitChunk != null && this.lastVisitChunkX == chunkX && this.lastVisitChunkZ == chunkZ) {
|
||||
this.lastVisitChunk = null;
|
||||
}
|
||||
}
|
||||
|
||||
private enum Relative {
|
||||
INSIDE, POSITIVE, NEGATIVE;
|
||||
public static Relative from(int min, int max, int pos) {
|
||||
if (max > pos && min > pos) return POSITIVE;
|
||||
else if (min < pos && max < pos) return NEGATIVE;
|
||||
public static Relative from(int min, int max, double pos) {
|
||||
if (min > pos) return POSITIVE;
|
||||
else if (max < pos) return NEGATIVE;
|
||||
return INSIDE;
|
||||
}
|
||||
}
|
||||
|
||||
public interface OcclusionCache {
|
||||
void resetCache();
|
||||
void setVisible(int x, int y, int z);
|
||||
void setHidden(int x, int y, int z);
|
||||
int getState(int x, int y, int z);
|
||||
void setLastHidden();
|
||||
void setLastVisible();
|
||||
}
|
||||
|
||||
// 使用位运算压缩存储状态的缓存实现
|
||||
public static class ArrayOcclusionCache implements OcclusionCache {
|
||||
private final int reachX2;
|
||||
private final byte[] cache;
|
||||
private int entry, offset;
|
||||
|
||||
public ArrayOcclusionCache(int reach) {
|
||||
this.reachX2 = reach * 2;
|
||||
// 每一个位置占2位
|
||||
this.cache = new byte[(reachX2 * reachX2 * reachX2) / 4 + 1];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetCache() {
|
||||
Arrays.fill(cache, (byte) 0);
|
||||
}
|
||||
|
||||
private void calcIndex(int x, int y, int z) {
|
||||
int positionKey = x + y * reachX2 + z * reachX2 * reachX2;
|
||||
entry = positionKey / 4;
|
||||
offset = (positionKey % 4) * 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisible(int x, int y, int z) {
|
||||
calcIndex(x, y, z);
|
||||
cache[entry] |= 1 << offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHidden(int x, int y, int z) {
|
||||
calcIndex(x, y, z);
|
||||
cache[entry] |= 1 << (offset + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getState(int x, int y, int z) {
|
||||
calcIndex(x, y, z);
|
||||
return (cache[entry] >> offset) & 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastVisible() {
|
||||
cache[entry] |= 1 << offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastHidden() {
|
||||
cache[entry] |= 1 << (offset + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,8 @@ import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.craftengine.core.plugin.Plugin;
|
||||
import net.momirealms.craftengine.core.util.IntIdentityList;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.world.chunk.ChunkStatus;
|
||||
import net.momirealms.craftengine.core.world.World;
|
||||
import net.momirealms.craftengine.core.world.chunk.client.ClientChunk;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -65,9 +66,7 @@ public interface NetWorkUser {
|
||||
@ApiStatus.Internal
|
||||
ConnectionState encoderState();
|
||||
|
||||
int clientSideSectionCount();
|
||||
|
||||
Key clientSideDimension();
|
||||
World clientSideWorld();
|
||||
|
||||
Object serverPlayer();
|
||||
|
||||
@@ -89,9 +88,9 @@ public interface NetWorkUser {
|
||||
|
||||
boolean isChunkTracked(long chunkPos);
|
||||
|
||||
ChunkStatus getTrackedChunk(long chunkPos);
|
||||
ClientChunk getTrackedChunk(long chunkPos);
|
||||
|
||||
void addTrackedChunk(long chunkPos, ChunkStatus chunkStatus);
|
||||
void addTrackedChunk(long chunkPos, ClientChunk chunkStatus);
|
||||
|
||||
void clearTrackedChunks();
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ public class Color {
|
||||
}
|
||||
|
||||
public static Color fromVector3f(Vector3f vec) {
|
||||
return new Color(0 << 24 /*不可省略*/ | MiscUtils.fastFloor(vec.x) << 16 | MiscUtils.fastFloor(vec.y) << 8 | MiscUtils.fastFloor(vec.z));
|
||||
return new Color(0 << 24 /*不可省略*/ | MiscUtils.floor(vec.x) << 16 | MiscUtils.floor(vec.y) << 8 | MiscUtils.floor(vec.z));
|
||||
}
|
||||
|
||||
public static int opaque(int color) {
|
||||
|
||||
@@ -21,19 +21,19 @@ public class MiscUtils {
|
||||
}
|
||||
});
|
||||
|
||||
public static int fastFloor(double value) {
|
||||
public static int floor(double value) {
|
||||
int truncated = (int) value;
|
||||
return value < (double) truncated ? truncated - 1 : truncated;
|
||||
}
|
||||
|
||||
public static int fastFloor(float value) {
|
||||
public static int floor(float value) {
|
||||
int truncated = (int) value;
|
||||
return value < (double) truncated ? truncated - 1 : truncated;
|
||||
}
|
||||
|
||||
public static int lerpDiscrete(float delta, int start, int end) {
|
||||
int i = end - start;
|
||||
return start + fastFloor(delta * (float) (i - 1)) + (delta > 0.0F ? 1 : 0);
|
||||
return start + floor(delta * (float) (i - 1)) + (delta > 0.0F ? 1 : 0);
|
||||
}
|
||||
|
||||
public static int murmurHash3Mixer(int value) {
|
||||
@@ -270,7 +270,7 @@ public class MiscUtils {
|
||||
}
|
||||
|
||||
public static byte packDegrees(float degrees) {
|
||||
return (byte) fastFloor(degrees * 256.0F / 360.0F);
|
||||
return (byte) floor(degrees * 256.0F / 360.0F);
|
||||
}
|
||||
|
||||
public static float unpackDegrees(byte degrees) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import net.momirealms.craftengine.core.plugin.locale.LocalizedException;
|
||||
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
|
||||
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
|
||||
import net.momirealms.craftengine.core.world.Vec3d;
|
||||
import net.momirealms.craftengine.core.world.collision.AABB;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3f;
|
||||
@@ -341,4 +342,52 @@ public final class ResourceConfigUtils {
|
||||
}
|
||||
TranslationManager.instance().log(e.node(), e.arguments());
|
||||
}
|
||||
|
||||
public static AABB getAsAABB(Object o, String option) {
|
||||
if (o == null) {
|
||||
throw new LocalizedResourceConfigException("warning.config.type.aabb", "null", option);
|
||||
}
|
||||
if (o instanceof Number number) {
|
||||
double min = -(number.doubleValue() / 2);
|
||||
double max = number.doubleValue() / 2;
|
||||
return new AABB(min, min, min, max, max, max);
|
||||
} else {
|
||||
double[] args;
|
||||
if (o instanceof List<?> list) {
|
||||
args = new double[list.size()];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (list.get(i) instanceof Number number) {
|
||||
args[i] = number.doubleValue();
|
||||
} else {
|
||||
try {
|
||||
args[i] = Double.parseDouble(list.get(i).toString());
|
||||
} catch (NumberFormatException e) {
|
||||
throw new LocalizedResourceConfigException("warning.config.type.aabb", o.toString(), option);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String[] split = o.toString().split(",");
|
||||
args = new double[split.length];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
try {
|
||||
args[i] = Double.parseDouble(split[i]);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new LocalizedResourceConfigException("warning.config.type.aabb", o.toString(), option);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (args.length == 1) {
|
||||
return new AABB(-args[0]/2, -args[0]/2, -args[0]/2, args[0]/2, args[0]/2, args[0]/2);
|
||||
} else if (args.length == 2) {
|
||||
return new AABB(-args[0]/2, -args[1]/2, -args[0]/2, args[0]/2, args[1]/2, args[0]/2);
|
||||
} else if (args.length == 3) {
|
||||
return new AABB(-args[0]/2, -args[1]/2, -args[2]/2, args[0]/2, args[1]/2, args[2]/2);
|
||||
} else if (args.length == 6) {
|
||||
return new AABB(args[0], args[1], args[2], args[3], args[4], args[5]);
|
||||
} else {
|
||||
throw new LocalizedResourceConfigException("warning.config.type.aabb", o.toString(), option);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public class BlockPos extends Vec3i {
|
||||
}
|
||||
|
||||
public static BlockPos fromVec3d(Vec3d vec) {
|
||||
return new BlockPos(MiscUtils.fastFloor(vec.x), MiscUtils.fastFloor(vec.y), MiscUtils.fastFloor(vec.z));
|
||||
return new BlockPos(MiscUtils.floor(vec.x), MiscUtils.floor(vec.y), MiscUtils.floor(vec.z));
|
||||
}
|
||||
|
||||
public static BlockPos of(long packedPos) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -23,9 +23,9 @@ public class EntityHitResult {
|
||||
}
|
||||
|
||||
private BlockPos getBlockPos() {
|
||||
int x = MiscUtils.fastFloor(this.position.x);
|
||||
int y = MiscUtils.fastFloor(this.position.y);
|
||||
int z = MiscUtils.fastFloor(this.position.z);
|
||||
int x = MiscUtils.floor(this.position.x);
|
||||
int y = MiscUtils.floor(this.position.y);
|
||||
int z = MiscUtils.floor(this.position.z);
|
||||
if (this.direction == Direction.UP) {
|
||||
if (this.position.y % 1 == 0) {
|
||||
y--;
|
||||
|
||||
@@ -14,9 +14,9 @@ public class MutableVec3d implements Position {
|
||||
}
|
||||
|
||||
public MutableVec3d toCenter() {
|
||||
this.x = MiscUtils.fastFloor(x) + 0.5;
|
||||
this.y = MiscUtils.fastFloor(y) + 0.5;
|
||||
this.z = MiscUtils.fastFloor(z) + 0.5;
|
||||
this.x = MiscUtils.floor(x) + 0.5;
|
||||
this.y = MiscUtils.floor(y) + 0.5;
|
||||
this.z = MiscUtils.floor(z) + 0.5;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,4 +17,39 @@ public class SectionPos extends Vec3i {
|
||||
public static int sectionRelative(int rel) {
|
||||
return rel & 15;
|
||||
}
|
||||
|
||||
public static SectionPos of(long packed) {
|
||||
return new SectionPos((int) (packed >> 42), (int) (packed << 44 >> 44), (int) (packed << 22 >> 42));
|
||||
}
|
||||
|
||||
public long asLong() {
|
||||
return ((long) this.x & 4194303L) << 42 | (long) this.y & 1048575L | ((long) this.z & 4194303L) << 20;
|
||||
}
|
||||
|
||||
public ChunkPos asChunkPos() {
|
||||
return new ChunkPos(this.x, this.z);
|
||||
}
|
||||
|
||||
public static short packSectionRelativePos(BlockPos pos) {
|
||||
return (short) ((pos.x & 15) << 8 | (pos.z & 15) << 4 | pos.y & 15);
|
||||
}
|
||||
|
||||
public static BlockPos unpackSectionRelativePos(short encoded) {
|
||||
int x = (encoded >> 8) & 15;
|
||||
int z = (encoded >> 4) & 15;
|
||||
int y = encoded & 15;
|
||||
return new BlockPos(x, y, z);
|
||||
}
|
||||
|
||||
public final int minBlockX() {
|
||||
return this.x << 4;
|
||||
}
|
||||
|
||||
public final int minBlockY() {
|
||||
return this.y << 4;
|
||||
}
|
||||
|
||||
public final int minBlockZ() {
|
||||
return this.z << 4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ public class Vec3d implements Position {
|
||||
}
|
||||
|
||||
public Vec3d toCenter() {
|
||||
return new Vec3d(MiscUtils.fastFloor(x) + 0.5, MiscUtils.fastFloor(y) + 0.5, MiscUtils.fastFloor(z) + 0.5);
|
||||
return new Vec3d(MiscUtils.floor(x) + 0.5, MiscUtils.floor(y) + 0.5, MiscUtils.floor(z) + 0.5);
|
||||
}
|
||||
|
||||
public Vec3d add(Vec3d vec) {
|
||||
|
||||
@@ -4,9 +4,9 @@ import net.momirealms.craftengine.core.util.Direction;
|
||||
|
||||
public class Vec3i implements Comparable<Vec3i> {
|
||||
public static final Vec3i ZERO = new Vec3i(0, 0, 0);
|
||||
protected int x;
|
||||
protected int y;
|
||||
protected int z;
|
||||
public final int x;
|
||||
public final int y;
|
||||
public final int z;
|
||||
|
||||
public Vec3i(int x, int y, int z) {
|
||||
this.x = x;
|
||||
@@ -30,21 +30,6 @@ public class Vec3i implements Comparable<Vec3i> {
|
||||
return x == 0 && y == 0 && z == 0 ? this : new Vec3i(this.x() + x, this.y() + y, this.z() + z);
|
||||
}
|
||||
|
||||
protected Vec3i setX(int x) {
|
||||
this.x = x;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected Vec3i setY(int y) {
|
||||
this.y = y;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected Vec3i setZ(int z) {
|
||||
this.z = z;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
return this == object || object instanceof Vec3i vec3i && this.x == vec3i.x && this.y == vec3i.y && this.z == vec3i.z;
|
||||
|
||||
@@ -77,15 +77,24 @@ public class ArrayPalette<T> implements Palette<T> {
|
||||
|
||||
@Override
|
||||
public boolean hasAny(Predicate<T> predicate) {
|
||||
for(int i = 0; i < this.size; ++i) {
|
||||
for (int i = 0; i < this.size; ++i) {
|
||||
if (predicate.test(this.array[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allMatch(Predicate<T> predicate) {
|
||||
for (int i = 0; i < this.size; ++i) {
|
||||
if (!predicate.test(this.array[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(int id) {
|
||||
if (id >= 0 && id < this.size) {
|
||||
|
||||
@@ -67,7 +67,7 @@ public class BiMapPalette<T> implements Palette<T> {
|
||||
|
||||
@Override
|
||||
public boolean hasAny(Predicate<T> predicate) {
|
||||
for(int i = 0; i < this.getSize(); ++i) {
|
||||
for (int i = 0; i < this.getSize(); ++i) {
|
||||
if (predicate.test(this.map.get(i))) {
|
||||
return true;
|
||||
}
|
||||
@@ -75,6 +75,16 @@ public class BiMapPalette<T> implements Palette<T> {
|
||||
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
|
||||
public T get(int id) {
|
||||
T object = this.map.get(id);
|
||||
|
||||
@@ -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.tick.*;
|
||||
import net.momirealms.craftengine.core.entity.player.Player;
|
||||
import net.momirealms.craftengine.core.plugin.config.Config;
|
||||
import net.momirealms.craftengine.core.plugin.logger.Debugger;
|
||||
import net.momirealms.craftengine.core.world.*;
|
||||
import net.momirealms.craftengine.core.world.chunk.client.VirtualCullableObject;
|
||||
import net.momirealms.craftengine.core.world.chunk.serialization.DefaultBlockEntityRendererSerializer;
|
||||
import net.momirealms.craftengine.core.world.chunk.serialization.DefaultBlockEntitySerializer;
|
||||
import net.momirealms.sparrow.nbt.ListTag;
|
||||
@@ -91,8 +93,12 @@ public class CEChunk {
|
||||
public void spawnBlockEntities(Player player) {
|
||||
try {
|
||||
this.renderLock.readLock().lock();
|
||||
for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) {
|
||||
renderer.show(player);
|
||||
if (Config.enableEntityCulling()) {
|
||||
player.addTrackedBlockEntities(this.constantBlockEntityRenderers);
|
||||
} else {
|
||||
for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) {
|
||||
renderer.show(player);
|
||||
}
|
||||
}
|
||||
for (DynamicBlockEntityRenderer renderer : this.dynamicBlockEntityRenderers.values()) {
|
||||
renderer.show(player);
|
||||
@@ -105,8 +111,12 @@ public class CEChunk {
|
||||
public void despawnBlockEntities(Player player) {
|
||||
try {
|
||||
this.renderLock.readLock().lock();
|
||||
for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) {
|
||||
renderer.hide(player);
|
||||
if (Config.enableEntityCulling()) {
|
||||
player.removeTrackedBlockEntities(this.constantBlockEntityRenderers.keySet());
|
||||
} else {
|
||||
for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) {
|
||||
renderer.hide(player);
|
||||
}
|
||||
}
|
||||
for (DynamicBlockEntityRenderer renderer : this.dynamicBlockEntityRenderers.values()) {
|
||||
renderer.hide(player);
|
||||
@@ -129,7 +139,7 @@ public class CEChunk {
|
||||
BlockEntityElementConfig<? extends BlockEntityElement>[] renderers = state.constantRenderers();
|
||||
if (renderers != null && renderers.length > 0) {
|
||||
BlockEntityElement[] elements = new BlockEntityElement[renderers.length];
|
||||
ConstantBlockEntityRenderer renderer = new ConstantBlockEntityRenderer(elements);
|
||||
ConstantBlockEntityRenderer renderer = new ConstantBlockEntityRenderer(elements, state.estimatedBoundingBox().move(pos));
|
||||
World wrappedWorld = this.world.world();
|
||||
List<Player> trackedBy = getTrackedBy();
|
||||
boolean hasTrackedBy = trackedBy != null && !trackedBy.isEmpty();
|
||||
@@ -146,8 +156,20 @@ public class CEChunk {
|
||||
if (element != null) {
|
||||
elements[0] = element;
|
||||
if (hasTrackedBy) {
|
||||
for (Player player : trackedBy) {
|
||||
element.transform(player);
|
||||
// 如果启用实体剔除,那么只对已经渲染的进行变换
|
||||
if (Config.enableEntityCulling()) {
|
||||
for (Player player : trackedBy) {
|
||||
VirtualCullableObject trackedBlockEntity = player.getTrackedBlockEntity(pos);
|
||||
if (trackedBlockEntity == null || trackedBlockEntity.isShown()) {
|
||||
element.transform(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 否则直接变换
|
||||
else {
|
||||
for (Player player : trackedBy) {
|
||||
element.transform(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
break outer;
|
||||
@@ -156,9 +178,18 @@ public class CEChunk {
|
||||
BlockEntityElement element = config.create(wrappedWorld, pos);
|
||||
elements[0] = element;
|
||||
if (hasTrackedBy) {
|
||||
for (Player player : trackedBy) {
|
||||
previousElement.hide(player);
|
||||
element.show(player);
|
||||
// 如果启用实体剔除,那么只添加记录
|
||||
if (Config.enableEntityCulling()) {
|
||||
for (Player player : trackedBy) {
|
||||
player.addTrackedBlockEntity(pos, renderer);
|
||||
}
|
||||
}
|
||||
// 否则直接显示
|
||||
else {
|
||||
for (Player player : trackedBy) {
|
||||
previousElement.hide(player);
|
||||
element.show(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,8 +204,20 @@ public class CEChunk {
|
||||
previousElements[j] = null;
|
||||
elements[i] = newElement;
|
||||
if (hasTrackedBy) {
|
||||
for (Player player : trackedBy) {
|
||||
newElement.transform(player);
|
||||
// 如果启用实体剔除,那么只对已经渲染的进行变换
|
||||
if (Config.enableEntityCulling()) {
|
||||
for (Player player : trackedBy) {
|
||||
VirtualCullableObject trackedBlockEntity = player.getTrackedBlockEntity(pos);
|
||||
if (trackedBlockEntity == null || trackedBlockEntity.isShown()) {
|
||||
newElement.transform(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 否则直接变换
|
||||
else {
|
||||
for (Player player : trackedBy) {
|
||||
newElement.transform(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
continue outer;
|
||||
@@ -184,8 +227,14 @@ public class CEChunk {
|
||||
BlockEntityElement newElement = config.create(wrappedWorld, pos);
|
||||
elements[i] = newElement;
|
||||
if (hasTrackedBy) {
|
||||
for (Player player : trackedBy) {
|
||||
newElement.show(player);
|
||||
if (Config.enableEntityCulling()) {
|
||||
for (Player player : trackedBy) {
|
||||
player.addTrackedBlockEntity(pos, renderer);
|
||||
}
|
||||
} else {
|
||||
for (Player player : trackedBy) {
|
||||
newElement.show(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,8 +254,14 @@ public class CEChunk {
|
||||
elements[i] = renderers[i].create(wrappedWorld, pos);
|
||||
}
|
||||
if (hasTrackedBy) {
|
||||
for (Player player : trackedBy) {
|
||||
renderer.show(player);
|
||||
if (Config.enableEntityCulling()) {
|
||||
for (Player player : trackedBy) {
|
||||
player.addTrackedBlockEntity(pos, renderer);
|
||||
}
|
||||
} else {
|
||||
for (Player player : trackedBy) {
|
||||
renderer.show(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,8 +288,14 @@ public class CEChunk {
|
||||
ConstantBlockEntityRenderer removed = this.constantBlockEntityRenderers.remove(pos);
|
||||
if (removed != null) {
|
||||
if (hide) {
|
||||
for (Player player : getTrackedBy()) {
|
||||
removed.hide(player);
|
||||
if (Config.enableEntityCulling()) {
|
||||
for (Player player : getTrackedBy()) {
|
||||
player.removeTrackedBlockEntities(List.of(pos));
|
||||
}
|
||||
} else {
|
||||
for (Player player : getTrackedBy()) {
|
||||
removed.hide(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -439,7 +500,7 @@ public class CEChunk {
|
||||
return Collections.unmodifiableCollection(this.blockEntities.values());
|
||||
}
|
||||
|
||||
public List<BlockPos> constantBlockEntityRenderers() {
|
||||
public List<BlockPos> constantBlockEntityRendererPositions() {
|
||||
try {
|
||||
this.renderLock.readLock().lock();
|
||||
return new ArrayList<>(this.constantBlockEntityRenderers.keySet());
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package net.momirealms.craftengine.core.world.chunk;
|
||||
|
||||
public class ChunkStatus {
|
||||
|
||||
public ChunkStatus() {
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,11 @@ public class IdListPalette<T> implements Palette<T> {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allMatch(Predicate<T> predicate) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(int id) {
|
||||
T object = this.idList.get(id);
|
||||
|
||||
@@ -13,6 +13,8 @@ public interface Palette<T> {
|
||||
|
||||
boolean hasAny(Predicate<T> predicate);
|
||||
|
||||
boolean allMatch(Predicate<T> predicate);
|
||||
|
||||
T get(int id);
|
||||
|
||||
int getSize();
|
||||
|
||||
@@ -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
|
||||
public T get(int id) {
|
||||
if (this.entry != null && id == 0) {
|
||||
|
||||
@@ -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)];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package net.momirealms.craftengine.core.world.chunk.client;
|
||||
|
||||
public interface ClientSectionOcclusionStorage {
|
||||
|
||||
boolean isOccluding(int index);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ public final class DefaultChunkSerializer {
|
||||
if (!blockEntities.isEmpty()) {
|
||||
chunkNbt.put("block_entities", blockEntities);
|
||||
}
|
||||
ListTag blockEntityRenders = DefaultBlockEntityRendererSerializer.serialize(chunk.constantBlockEntityRenderers());
|
||||
ListTag blockEntityRenders = DefaultBlockEntityRendererSerializer.serialize(chunk.constantBlockEntityRendererPositions());
|
||||
if (!blockEntityRenders.isEmpty()) {
|
||||
chunkNbt.put("block_entity_renderers", blockEntityRenders);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,17 @@ public class AABB {
|
||||
this.maxZ = Math.max(pos1.z, pos2.z);
|
||||
}
|
||||
|
||||
public AABB move(BlockPos pos) {
|
||||
return new AABB(
|
||||
this.minX + pos.x + 0.5,
|
||||
this.minY + pos.y + 0.5,
|
||||
this.minZ + pos.z + 0.5,
|
||||
this.maxX + pos.x + 0.5,
|
||||
this.maxY + pos.y + 0.5,
|
||||
this.maxZ + pos.z + 0.5
|
||||
);
|
||||
}
|
||||
|
||||
public AABB(BlockPos pos) {
|
||||
this(pos.x(), pos.y(), pos.z(), pos.x() + 1, pos.y() + 1, pos.z() + 1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user