mirror of
https://github.com/Xiao-MoMi/craft-engine.git
synced 2026-01-04 15:41:38 +00:00
Merge branch 'Xiao-MoMi:dev' into dev
This commit is contained in:
@@ -45,6 +45,14 @@ public class ItemBlockEntityElementConfig implements BlockEntityElementConfig<It
|
||||
return new ItemBlockEntityElement(this, pos, previous.entityId1, previous.entityId2, !previous.config.position.equals(this.position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemBlockEntityElement createExact(World world, BlockPos pos, ItemBlockEntityElement previous) {
|
||||
if (!previous.config.equals(this)) {
|
||||
return null;
|
||||
}
|
||||
return new ItemBlockEntityElement(this, pos, previous.entityId1, previous.entityId2, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<ItemBlockEntityElement> elementClass() {
|
||||
return ItemBlockEntityElement.class;
|
||||
@@ -62,6 +70,12 @@ public class ItemBlockEntityElementConfig implements BlockEntityElementConfig<It
|
||||
return this.lazyMetadataPacket.apply(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof ItemBlockEntityElementConfig that)) return false;
|
||||
return this.position.equals(that.position);
|
||||
}
|
||||
|
||||
public static class Factory implements BlockEntityElementConfigFactory {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.momirealms.craftengine.bukkit.block.entity.renderer.element;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import net.momirealms.craftengine.bukkit.entity.data.ItemDisplayEntityData;
|
||||
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
|
||||
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement;
|
||||
@@ -61,14 +62,14 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo
|
||||
this.shadowStrength = shadowStrength;
|
||||
this.lazyMetadataPacket = player -> {
|
||||
List<Object> dataValues = new ArrayList<>();
|
||||
ItemDisplayEntityData.DisplayedItem.addEntityDataIfNotDefaultValue(item.apply(player).getLiteralObject(), dataValues);
|
||||
ItemDisplayEntityData.Scale.addEntityDataIfNotDefaultValue(this.scale, dataValues);
|
||||
ItemDisplayEntityData.RotationLeft.addEntityDataIfNotDefaultValue(this.rotation, dataValues);
|
||||
ItemDisplayEntityData.BillboardConstraints.addEntityDataIfNotDefaultValue(this.billboard.id(), dataValues);
|
||||
ItemDisplayEntityData.Translation.addEntityDataIfNotDefaultValue(this.translation, dataValues);
|
||||
ItemDisplayEntityData.DisplayType.addEntityDataIfNotDefaultValue(this.displayContext.id(), dataValues);
|
||||
ItemDisplayEntityData.ShadowRadius.addEntityDataIfNotDefaultValue(this.shadowRadius, dataValues);
|
||||
ItemDisplayEntityData.ShadowStrength.addEntityDataIfNotDefaultValue(this.shadowStrength, dataValues);
|
||||
ItemDisplayEntityData.DisplayedItem.addEntityData(item.apply(player).getLiteralObject(), dataValues);
|
||||
ItemDisplayEntityData.Scale.addEntityData(this.scale, dataValues);
|
||||
ItemDisplayEntityData.RotationLeft.addEntityData(this.rotation, dataValues);
|
||||
ItemDisplayEntityData.BillboardConstraints.addEntityData(this.billboard.id(), dataValues);
|
||||
ItemDisplayEntityData.Translation.addEntityData(this.translation, dataValues);
|
||||
ItemDisplayEntityData.DisplayType.addEntityData(this.displayContext.id(), dataValues);
|
||||
ItemDisplayEntityData.ShadowRadius.addEntityData(this.shadowRadius, dataValues);
|
||||
ItemDisplayEntityData.ShadowStrength.addEntityData(this.shadowStrength, dataValues);
|
||||
return dataValues;
|
||||
};
|
||||
}
|
||||
@@ -80,14 +81,6 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo
|
||||
|
||||
@Override
|
||||
public ItemDisplayBlockEntityElement create(World world, BlockPos pos, ItemDisplayBlockEntityElement previous) {
|
||||
Quaternionf previousRotation = previous.config.rotation;
|
||||
if (previousRotation.x != 0 || previousRotation.y != 0 || previousRotation.z != 0 || previousRotation.w != 1) {
|
||||
return null;
|
||||
}
|
||||
Vector3f translation = previous.config.translation;
|
||||
if (translation.x != 0 || translation.y != 0 || translation.z != 0) {
|
||||
return null;
|
||||
}
|
||||
return new ItemDisplayBlockEntityElement(this, pos, previous.entityId,
|
||||
previous.config.yRot != this.yRot ||
|
||||
previous.config.xRot != this.xRot ||
|
||||
@@ -95,6 +88,14 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemDisplayBlockEntityElement createExact(World world, BlockPos pos, ItemDisplayBlockEntityElement previous) {
|
||||
if (!previous.config.equals(this)) {
|
||||
return null;
|
||||
}
|
||||
return new ItemDisplayBlockEntityElement(this, pos, previous.entityId, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<ItemDisplayBlockEntityElement> elementClass() {
|
||||
return ItemDisplayBlockEntityElement.class;
|
||||
@@ -148,6 +149,16 @@ public class ItemDisplayBlockEntityElementConfig implements BlockEntityElementCo
|
||||
return this.lazyMetadataPacket.apply(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof ItemDisplayBlockEntityElementConfig that)) return false;
|
||||
return Float.compare(xRot, that.xRot) == 0 &&
|
||||
Float.compare(yRot, that.yRot) == 0 &&
|
||||
Objects.equal(position, that.position) &&
|
||||
Objects.equal(translation, that.translation) &&
|
||||
Objects.equal(rotation, that.rotation);
|
||||
}
|
||||
|
||||
public static class Factory implements BlockEntityElementConfigFactory {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.momirealms.craftengine.bukkit.block.entity.renderer.element;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.craftengine.bukkit.entity.data.TextDisplayEntityData;
|
||||
import net.momirealms.craftengine.bukkit.util.ComponentUtils;
|
||||
@@ -52,11 +53,11 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo
|
||||
this.billboard = billboard;
|
||||
this.lazyMetadataPacket = player -> {
|
||||
List<Object> dataValues = new ArrayList<>();
|
||||
TextDisplayEntityData.Text.addEntityDataIfNotDefaultValue(ComponentUtils.adventureToMinecraft(text(player)), dataValues);
|
||||
TextDisplayEntityData.Scale.addEntityDataIfNotDefaultValue(this.scale, dataValues);
|
||||
TextDisplayEntityData.RotationLeft.addEntityDataIfNotDefaultValue(this.rotation, dataValues);
|
||||
TextDisplayEntityData.BillboardConstraints.addEntityDataIfNotDefaultValue(this.billboard.id(), dataValues);
|
||||
TextDisplayEntityData.Translation.addEntityDataIfNotDefaultValue(this.translation, dataValues);
|
||||
TextDisplayEntityData.Text.addEntityData(ComponentUtils.adventureToMinecraft(text(player)), dataValues);
|
||||
TextDisplayEntityData.Scale.addEntityData(this.scale, dataValues);
|
||||
TextDisplayEntityData.RotationLeft.addEntityData(this.rotation, dataValues);
|
||||
TextDisplayEntityData.BillboardConstraints.addEntityData(this.billboard.id(), dataValues);
|
||||
TextDisplayEntityData.Translation.addEntityData(this.translation, dataValues);
|
||||
return dataValues;
|
||||
};
|
||||
}
|
||||
@@ -68,14 +69,6 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo
|
||||
|
||||
@Override
|
||||
public TextDisplayBlockEntityElement create(World world, BlockPos pos, TextDisplayBlockEntityElement previous) {
|
||||
Quaternionf previousRotation = previous.config.rotation;
|
||||
if (previousRotation.x != 0 || previousRotation.y != 0 || previousRotation.z != 0 || previousRotation.w != 1) {
|
||||
return null;
|
||||
}
|
||||
Vector3f translation = previous.config.translation;
|
||||
if (translation.x != 0 || translation.y != 0 || translation.z != 0) {
|
||||
return null;
|
||||
}
|
||||
return new TextDisplayBlockEntityElement(this, pos, previous.entityId,
|
||||
previous.config.yRot != this.yRot ||
|
||||
previous.config.xRot != this.xRot ||
|
||||
@@ -83,6 +76,14 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextDisplayBlockEntityElement createExact(World world, BlockPos pos, TextDisplayBlockEntityElement previous) {
|
||||
if (!previous.config.equals(this)) {
|
||||
return null;
|
||||
}
|
||||
return new TextDisplayBlockEntityElement(this, pos, previous.entityId, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<TextDisplayBlockEntityElement> elementClass() {
|
||||
return TextDisplayBlockEntityElement.class;
|
||||
@@ -124,6 +125,16 @@ public class TextDisplayBlockEntityElementConfig implements BlockEntityElementCo
|
||||
return this.lazyMetadataPacket.apply(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof TextDisplayBlockEntityElementConfig that)) return false;
|
||||
return Float.compare(xRot, that.xRot) == 0 &&
|
||||
Float.compare(yRot, that.yRot) == 0 &&
|
||||
Objects.equal(position, that.position) &&
|
||||
Objects.equal(translation, that.translation) &&
|
||||
Objects.equal(rotation, that.rotation);
|
||||
}
|
||||
|
||||
public static class Factory implements BlockEntityElementConfigFactory {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -42,6 +42,7 @@ public class BukkitCommandManager extends AbstractCommandManager<CommandSender>
|
||||
new SearchUsageAdminCommand(this, plugin),
|
||||
new TestCommand(this, plugin),
|
||||
new SetLocaleCommand(this, plugin),
|
||||
new SetEntityViewDistanceScaleCommand(this, plugin),
|
||||
new UnsetLocaleCommand(this, plugin),
|
||||
new DebugGetBlockStateRegistryIdCommand(this, plugin),
|
||||
new DebugGetBlockInternalIdCommand(this, plugin),
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package net.momirealms.craftengine.bukkit.plugin.command.feature;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.craftengine.bukkit.api.BukkitAdaptors;
|
||||
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
|
||||
import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
|
||||
import net.momirealms.craftengine.core.plugin.command.FlagKeys;
|
||||
import net.momirealms.craftengine.core.plugin.locale.MessageConstants;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.incendo.cloud.Command;
|
||||
import org.incendo.cloud.bukkit.parser.PlayerParser;
|
||||
import org.incendo.cloud.parser.standard.DoubleParser;
|
||||
|
||||
public class SetEntityViewDistanceScaleCommand extends BukkitCommandFeature<CommandSender> {
|
||||
|
||||
public SetEntityViewDistanceScaleCommand(CraftEngineCommandManager<CommandSender> commandManager, CraftEngine plugin) {
|
||||
super(commandManager, plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command.Builder<? extends CommandSender> assembleCommand(org.incendo.cloud.CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
|
||||
return builder
|
||||
.flag(FlagKeys.SILENT_FLAG)
|
||||
.required("player", PlayerParser.playerParser())
|
||||
.required("scale", DoubleParser.doubleParser(0.125, 8))
|
||||
.handler(context -> {
|
||||
Player player = context.get("player");
|
||||
double scale = context.get("scale");
|
||||
BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player);
|
||||
serverPlayer.setEntityCullingViewDistanceScale(scale);
|
||||
handleFeedback(context, MessageConstants.COMMAND_ENTITY_VIEW_DISTANCE_SCALE_SET_SUCCESS, Component.text(scale), Component.text(player.getName()));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureID() {
|
||||
return "set_entity_view_distance_scale";
|
||||
}
|
||||
}
|
||||
@@ -2046,7 +2046,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
|
||||
boolean hasGlobalPalette = false;
|
||||
|
||||
// 创建客户端侧世界(只在开启实体情况下创建)
|
||||
ClientSection[] clientSections = Config.enableEntityCulling() ? new ClientSection[count] : null;
|
||||
ClientSection[] clientSections = Config.entityCullingRayTracing() ? new ClientSection[count] : null;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
MCSection mcSection = new MCSection(user.clientBlockList(), this.blockList, this.biomeList);
|
||||
@@ -2225,7 +2225,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
|
||||
|
||||
// 获取客户端侧区域
|
||||
ClientSection clientSection = null;
|
||||
if (Config.enableEntityCulling()) {
|
||||
if (Config.entityCullingRayTracing()) {
|
||||
SectionPos sectionPos = SectionPos.of(sPos);
|
||||
ClientChunk trackedChunk = user.getTrackedChunk(sectionPos.asChunkPos().longKey);
|
||||
clientSection = trackedChunk.sectionById(sectionPos.y);
|
||||
@@ -2271,7 +2271,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
|
||||
FriendlyByteBuf buf = event.getBuffer();
|
||||
BlockPos pos = buf.readBlockPos();
|
||||
int before = buf.readVarInt();
|
||||
if (Config.enableEntityCulling()) {
|
||||
if (Config.entityCullingRayTracing()) {
|
||||
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));
|
||||
@@ -2451,7 +2451,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes
|
||||
BlockPos blockPos = buf.readBlockPos();
|
||||
int state = buf.readInt();
|
||||
// 移除不透明设置
|
||||
if (Config.enableEntityCulling()) {
|
||||
if (Config.entityCullingRayTracing()) {
|
||||
ClientChunk trackedChunk = user.getTrackedChunk(ChunkPos.asLong(blockPos.x >> 4, blockPos.z >> 4));
|
||||
if (trackedChunk != null) {
|
||||
trackedChunk.setOccluding(blockPos.x, blockPos.y, blockPos.z, false);
|
||||
|
||||
@@ -32,6 +32,7 @@ import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.config.Config;
|
||||
import net.momirealms.craftengine.core.plugin.context.CooldownData;
|
||||
import net.momirealms.craftengine.core.plugin.entityculling.CullingData;
|
||||
import net.momirealms.craftengine.core.plugin.entityculling.EntityCulling;
|
||||
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
|
||||
import net.momirealms.craftengine.core.plugin.network.ConnectionState;
|
||||
@@ -51,6 +52,7 @@ import org.bukkit.block.Block;
|
||||
import org.bukkit.damage.DamageSource;
|
||||
import org.bukkit.damage.DamageType;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
@@ -71,6 +73,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class BukkitServerPlayer extends Player {
|
||||
public static final Key SELECTED_LOCALE_KEY = Key.of("craftengine:locale");
|
||||
public static final Key ENTITY_CULLING_VIEW_DISTANCE_SCALE = Key.of("craftengine:entity_culling_view_distance_scale");
|
||||
private final BukkitCraftEngine plugin;
|
||||
|
||||
// connection state
|
||||
@@ -114,8 +117,6 @@ public class BukkitServerPlayer extends Player {
|
||||
private IntIdentityList blockList = new IntIdentityList(BlockStateUtils.vanillaBlockStateCount());
|
||||
// cache if player can break blocks
|
||||
private boolean clientSideCanBreak = true;
|
||||
// prevent AFK players from consuming too much CPU resource on predicting
|
||||
private Location previousEyeLocation;
|
||||
// a cooldown for better breaking experience
|
||||
private int lastSuccessfulBreak;
|
||||
// player's game tick
|
||||
@@ -142,8 +143,11 @@ public class BukkitServerPlayer extends Player {
|
||||
private int lastStopMiningTick;
|
||||
// 跟踪到的方块实体渲染器
|
||||
private final Map<BlockPos, VirtualCullableObject> trackedBlockEntityRenderers = new ConcurrentHashMap<>();
|
||||
|
||||
private final EntityCulling culling;
|
||||
private Vec3d firstPersonCameraVec3;
|
||||
private Vec3d thirdPersonCameraVec3;
|
||||
// 玩家眼睛所在位置
|
||||
private Location eyeLocation;
|
||||
|
||||
public BukkitServerPlayer(BukkitCraftEngine plugin, @Nullable Channel channel) {
|
||||
this.channel = channel;
|
||||
@@ -157,7 +161,7 @@ public class BukkitServerPlayer extends Player {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.culling = new EntityCulling(this, 64, 0.5);
|
||||
this.culling = new EntityCulling(this);
|
||||
}
|
||||
|
||||
public void setPlayer(org.bukkit.entity.Player player) {
|
||||
@@ -169,6 +173,8 @@ public class BukkitServerPlayer extends Player {
|
||||
this.isNameVerified = true;
|
||||
byte[] bytes = player.getPersistentDataContainer().get(KeyUtils.toNamespacedKey(CooldownData.COOLDOWN_KEY), PersistentDataType.BYTE_ARRAY);
|
||||
String locale = player.getPersistentDataContainer().get(KeyUtils.toNamespacedKey(SELECTED_LOCALE_KEY), PersistentDataType.STRING);
|
||||
Double scale = player.getPersistentDataContainer().get(KeyUtils.toNamespacedKey(ENTITY_CULLING_VIEW_DISTANCE_SCALE), PersistentDataType.DOUBLE);
|
||||
this.culling.setDistanceScale(Optional.ofNullable(scale).orElse(1.0));
|
||||
this.selectedLocale = TranslationManager.parseLocale(locale);
|
||||
this.trackedChunks = ConcurrentLong2ReferenceChainedHashTable.createWithCapacity(512, 0.5f);
|
||||
this.entityTypeView = new ConcurrentHashMap<>(256);
|
||||
@@ -509,10 +515,11 @@ public class BukkitServerPlayer extends Player {
|
||||
@Override
|
||||
public void tick() {
|
||||
// not fully online
|
||||
if (serverPlayer() == null) return;
|
||||
Object serverPlayer = serverPlayer();
|
||||
if (serverPlayer == null) return;
|
||||
org.bukkit.entity.Player bukkitPlayer = platformPlayer();
|
||||
if (VersionHelper.isFolia()) {
|
||||
try {
|
||||
Object serverPlayer = serverPlayer();
|
||||
Object gameMode = FastNMS.INSTANCE.field$ServerPlayer$gameMode(serverPlayer);
|
||||
this.gameTicks = (int) CoreReflections.field$ServerPlayerGameMode$gameTicks.get(gameMode);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
@@ -524,12 +531,30 @@ public class BukkitServerPlayer extends Player {
|
||||
if (this.gameTicks % 20 == 0) {
|
||||
this.updateGUI();
|
||||
}
|
||||
|
||||
// 更新眼睛位置
|
||||
{
|
||||
Location unsureEyeLocation = bukkitPlayer.getEyeLocation();
|
||||
Entity vehicle = bukkitPlayer.getVehicle();
|
||||
if (vehicle != null) {
|
||||
Object mountPos = FastNMS.INSTANCE.method$Entity$getPassengerRidingPosition(FastNMS.INSTANCE.method$CraftEntity$getHandle(vehicle), serverPlayer);
|
||||
unsureEyeLocation.set(FastNMS.INSTANCE.field$Vec3$x(mountPos), FastNMS.INSTANCE.field$Vec3$y(mountPos) + bukkitPlayer.getEyeHeight(), FastNMS.INSTANCE.field$Vec3$z(mountPos));
|
||||
}
|
||||
if (Config.predictBreaking() && !this.isDestroyingCustomBlock && !unsureEyeLocation.equals(this.eyeLocation)) {
|
||||
// if it's not destroying blocks, we do predict
|
||||
if ((gameTicks() + entityID()) % Config.predictBreakingInterval() == 0) {
|
||||
this.predictNextBlockToMine();
|
||||
}
|
||||
}
|
||||
this.eyeLocation = unsureEyeLocation;
|
||||
}
|
||||
|
||||
if (hasSwingHand()) {
|
||||
if (this.isDestroyingBlock) {
|
||||
this.tickBlockDestroy();
|
||||
} else if (this.lastStopMiningPos != null && this.gameTicks - this.lastStopMiningTick <= 5) {
|
||||
double range = getCachedInteractionRange();
|
||||
RayTraceResult result = platformPlayer().rayTraceBlocks(range, FluidCollisionMode.NEVER);
|
||||
RayTraceResult result = rayTrace(this.eyeLocation, range, FluidCollisionMode.NEVER);
|
||||
if (result != null) {
|
||||
Block hitBlock = result.getHitBlock();
|
||||
if (hitBlock != null) {
|
||||
@@ -552,25 +577,60 @@ public class BukkitServerPlayer extends Player {
|
||||
this.isHackedBreak = false;
|
||||
}
|
||||
}
|
||||
if (Config.predictBreaking() && !this.isDestroyingCustomBlock) {
|
||||
// if it's not destroying blocks, we do predict
|
||||
if ((gameTicks() + entityID()) % Config.predictBreakingInterval() == 0) {
|
||||
Location eyeLocation = platformPlayer().getEyeLocation();
|
||||
if (eyeLocation.equals(this.previousEyeLocation)) {
|
||||
return;
|
||||
|
||||
if (Config.entityCullingRayTracing()) {
|
||||
org.bukkit.entity.Player player = platformPlayer();
|
||||
Location eyeLocation = this.eyeLocation.clone();
|
||||
this.firstPersonCameraVec3 = LocationUtils.toVec3d(eyeLocation);
|
||||
int distance = 4;
|
||||
if (VersionHelper.isOrAbove1_21_6()) {
|
||||
Entity vehicle = player.getVehicle();
|
||||
if (vehicle != null && vehicle.getType() == EntityType.HAPPY_GHAST) {
|
||||
distance = 8;
|
||||
}
|
||||
this.previousEyeLocation = eyeLocation;
|
||||
this.predictNextBlockToMine();
|
||||
}
|
||||
this.thirdPersonCameraVec3 = LocationUtils.toVec3d(eyeLocation.subtract(eyeLocation.getDirection().multiply(distance)));
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entityCullingTick() {
|
||||
this.culling.restoreTokenOnTick();
|
||||
boolean useRayTracing = Config.entityCullingRayTracing();
|
||||
for (VirtualCullableObject cullableObject : this.trackedBlockEntityRenderers.values()) {
|
||||
CullingData cullingData = cullableObject.cullable.cullingData();
|
||||
if (cullingData != null) {
|
||||
boolean firstPersonVisible = this.culling.isVisible(cullingData, this.firstPersonCameraVec3, useRayTracing);
|
||||
// 之前可见
|
||||
if (cullableObject.isShown) {
|
||||
boolean thirdPersonVisible = this.culling.isVisible(cullingData, this.thirdPersonCameraVec3, useRayTracing);
|
||||
if (!firstPersonVisible && !thirdPersonVisible) {
|
||||
cullableObject.setShown(this, false);
|
||||
}
|
||||
}
|
||||
// 之前不可见
|
||||
else {
|
||||
// 但是第一人称可见了
|
||||
if (firstPersonVisible) {
|
||||
// 下次再说
|
||||
if (Config.enableEntityCullingRateLimiting() && !this.culling.takeToken()) {
|
||||
continue;
|
||||
}
|
||||
cullableObject.setShown(this, true);
|
||||
continue;
|
||||
}
|
||||
if (this.culling.isVisible(cullingData, this.thirdPersonCameraVec3, useRayTracing)) {
|
||||
// 下次再说
|
||||
if (Config.enableEntityCullingRateLimiting() && !this.culling.takeToken()) {
|
||||
continue;
|
||||
}
|
||||
cullableObject.setShown(this, true);
|
||||
}
|
||||
// 仍然不可见
|
||||
}
|
||||
} else {
|
||||
cullableObject.setShown(this, true);
|
||||
}
|
||||
long nano2 = System.nanoTime();
|
||||
//CraftEngine.instance().logger().info("EntityCulling took " + (nano2 - nano1) / 1_000_000d + "ms");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,17 +650,12 @@ public class BukkitServerPlayer extends Player {
|
||||
|
||||
public boolean canInteractWithBlock(BlockPos pos, double distance) {
|
||||
double d = this.getCachedInteractionRange() + distance;
|
||||
return (new AABB(pos)).distanceToSqr(this.getEyePosition()) < d * d;
|
||||
return (new AABB(pos)).distanceToSqr(this.getEyePos()) < d * d;
|
||||
}
|
||||
|
||||
public boolean canInteractPoint(Vec3d pos, double distance) {
|
||||
double d = this.getCachedInteractionRange() + distance;
|
||||
return Vec3d.distanceToSqr(this.getEyePosition(), pos) < d * d;
|
||||
}
|
||||
|
||||
public final Vec3d getEyePosition() {
|
||||
Location eyeLocation = this.platformPlayer().getEyeLocation();
|
||||
return new Vec3d(eyeLocation.getX(), eyeLocation.getY(), eyeLocation.getZ());
|
||||
return Vec3d.distanceToSqr(this.getEyePos(), pos) < d * d;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -620,7 +675,7 @@ public class BukkitServerPlayer extends Player {
|
||||
|
||||
private void predictNextBlockToMine() {
|
||||
double range = getCachedInteractionRange() + Config.extendedInteractionRange();
|
||||
RayTraceResult result = platformPlayer().rayTraceBlocks(range, FluidCollisionMode.NEVER);
|
||||
RayTraceResult result = rayTrace(this.eyeLocation, range, FluidCollisionMode.NEVER);
|
||||
if (result == null) {
|
||||
if (!this.clientSideCanBreak) {
|
||||
setClientSideCanBreakBlock(true);
|
||||
@@ -760,7 +815,7 @@ public class BukkitServerPlayer extends Player {
|
||||
try {
|
||||
org.bukkit.entity.Player player = platformPlayer();
|
||||
double range = getCachedInteractionRange();
|
||||
RayTraceResult result = player.rayTraceBlocks(range, FluidCollisionMode.NEVER);
|
||||
RayTraceResult result = rayTrace(this.eyeLocation, range, FluidCollisionMode.NEVER);
|
||||
if (result == null) return;
|
||||
Block hitBlock = result.getHitBlock();
|
||||
if (hitBlock == null) return;
|
||||
@@ -1200,6 +1255,9 @@ public class BukkitServerPlayer extends Player {
|
||||
@Override
|
||||
public void removeTrackedChunk(long chunkPos) {
|
||||
this.trackedChunks.remove(chunkPos);
|
||||
if (Config.entityCullingRayTracing()) {
|
||||
this.culling.removeLastVisitChunkIfMatches((int) chunkPos, (int) (chunkPos >> 32));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1276,6 +1334,13 @@ public class BukkitServerPlayer extends Player {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntityCullingViewDistanceScale(double value) {
|
||||
value = Math.min(Math.max(0.125, value), 8);
|
||||
this.culling.setDistanceScale(value);
|
||||
platformPlayer().getPersistentDataContainer().set(KeyUtils.toNamespacedKey(ENTITY_CULLING_VIEW_DISTANCE_SCALE), PersistentDataType.DOUBLE, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void giveExperiencePoints(int xpPoints) {
|
||||
platformPlayer().giveExp(xpPoints);
|
||||
@@ -1340,4 +1405,36 @@ public class BukkitServerPlayer extends Player {
|
||||
public void clearTrackedBlockEntities() {
|
||||
this.trackedBlockEntityRenderers.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorldPosition eyePosition() {
|
||||
return LocationUtils.toWorldPosition(this.getEyeLocation());
|
||||
}
|
||||
|
||||
public Location getEyeLocation() {
|
||||
org.bukkit.entity.Player player = platformPlayer();
|
||||
Location eyeLocation = player.getEyeLocation();
|
||||
Entity vehicle = player.getVehicle();
|
||||
if (vehicle != null) {
|
||||
Object mountPos = FastNMS.INSTANCE.method$Entity$getPassengerRidingPosition(FastNMS.INSTANCE.method$CraftEntity$getHandle(vehicle), serverPlayer());
|
||||
eyeLocation.set(FastNMS.INSTANCE.field$Vec3$x(mountPos), FastNMS.INSTANCE.field$Vec3$y(mountPos) + player.getEyeHeight(), FastNMS.INSTANCE.field$Vec3$z(mountPos));
|
||||
}
|
||||
return eyeLocation;
|
||||
}
|
||||
|
||||
public Vec3d getEyePos() {
|
||||
org.bukkit.entity.Player player = platformPlayer();
|
||||
Entity vehicle = player.getVehicle();
|
||||
if (vehicle != null) {
|
||||
Object mountPos = FastNMS.INSTANCE.method$Entity$getPassengerRidingPosition(FastNMS.INSTANCE.method$CraftEntity$getHandle(vehicle), serverPlayer());
|
||||
return new Vec3d(FastNMS.INSTANCE.field$Vec3$x(mountPos), FastNMS.INSTANCE.field$Vec3$y(mountPos) + player.getEyeHeight(), FastNMS.INSTANCE.field$Vec3$z(mountPos));
|
||||
} else {
|
||||
Location location = player.getLocation();
|
||||
return new Vec3d(location.getX(), location.getY() + player.getEyeHeight(), location.getZ());
|
||||
}
|
||||
}
|
||||
|
||||
private RayTraceResult rayTrace(Location start, double range, FluidCollisionMode mode) {
|
||||
return start.getWorld().rayTraceBlocks(start, start.getDirection(), range, mode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +129,12 @@ unset_locale:
|
||||
usage:
|
||||
- /ce feature locale unset
|
||||
|
||||
set_entity_view_distance_scale:
|
||||
enable: true
|
||||
permission: ce.command.admin.set_entity_view_distance_scale
|
||||
usage:
|
||||
- /ce feature entity-view-distance-scale set
|
||||
|
||||
# Debug commands
|
||||
debug_set_block:
|
||||
enable: true
|
||||
|
||||
@@ -552,10 +552,23 @@ chunk-system:
|
||||
remove: []
|
||||
convert: {}
|
||||
|
||||
# [Premium Exclusive]
|
||||
client-optimization:
|
||||
# Using server-side ray tracing algorithms to hide certain entities and reduce client-side rendering pressure.
|
||||
# Requires a restart to fully apply.
|
||||
entity-culling:
|
||||
enable: false
|
||||
enable: true
|
||||
# Using server-side ray tracing algorithms to hide block entities/furniture and reduce client-side rendering pressure.
|
||||
ray-tracing: true
|
||||
# Cull entities based on distance
|
||||
view-distance: 64 # -1 = no limit
|
||||
# Determining the number of threads to execute these raytrace operations
|
||||
threads: 1
|
||||
# Limit the maximum number of entities with visibility changes per tick for one player
|
||||
# This helps mitigate client-side performance impacts and server-side bandwidth spikes caused by a large number of entities appearing.
|
||||
rate-limiting:
|
||||
enable: true
|
||||
bucket-size: 1000
|
||||
restore-per-tick: 25
|
||||
|
||||
# Enables or disables debug mode
|
||||
debug:
|
||||
@@ -563,4 +576,6 @@ debug:
|
||||
packet: false
|
||||
furniture: false
|
||||
item: false
|
||||
resource-pack: false
|
||||
resource-pack: false
|
||||
block: false
|
||||
entity-culling: false
|
||||
@@ -93,7 +93,7 @@ items:
|
||||
state: barrier
|
||||
entity-renderer:
|
||||
item: default:sofa
|
||||
yaw: 90
|
||||
rotation: 90
|
||||
facing=north,shape=straight:
|
||||
state: barrier
|
||||
entity-renderer:
|
||||
@@ -102,12 +102,12 @@ items:
|
||||
state: barrier
|
||||
entity-renderer:
|
||||
item: default:sofa
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
facing=west,shape=straight:
|
||||
state: barrier
|
||||
entity-renderer:
|
||||
item: default:sofa
|
||||
yaw: 270
|
||||
rotation: 270
|
||||
facing=east,shape=inner_left:
|
||||
state: barrier
|
||||
entity-renderer:
|
||||
@@ -116,22 +116,22 @@ items:
|
||||
state: barrier
|
||||
entity-renderer:
|
||||
item: default:sofa_inner
|
||||
yaw: 270
|
||||
rotation: 270
|
||||
facing=south,shape=inner_left:
|
||||
state: barrier
|
||||
entity-renderer:
|
||||
item: default:sofa_inner
|
||||
yaw: 90
|
||||
rotation: 90
|
||||
facing=west,shape=inner_left:
|
||||
state: barrier
|
||||
entity-renderer:
|
||||
item: default:sofa_inner
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
facing=east,shape=inner_right:
|
||||
state: barrier
|
||||
entity-renderer:
|
||||
item: default:sofa_inner
|
||||
yaw: 90
|
||||
rotation: 90
|
||||
facing=north,shape=inner_right:
|
||||
state: barrier
|
||||
entity-renderer:
|
||||
@@ -140,12 +140,12 @@ items:
|
||||
state: barrier
|
||||
entity-renderer:
|
||||
item: default:sofa_inner
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
facing=west,shape=inner_right:
|
||||
state: barrier
|
||||
entity-renderer:
|
||||
item: default:sofa_inner
|
||||
yaw: 270
|
||||
rotation: 270
|
||||
variants:
|
||||
facing=east,shape=inner_left:
|
||||
appearance: facing=east,shape=inner_left
|
||||
|
||||
@@ -1834,354 +1834,354 @@ templates:
|
||||
state: ${base_block}[east=false,north=false,south=false,waterlogged=false,west=false]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
east=true,north=false,south=false,waterlogged=false,west=false:
|
||||
state: ${base_block}[east=true,north=false,south=false,waterlogged=false,west=false]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 270
|
||||
rotation: 270
|
||||
east=false,north=true,south=false,waterlogged=false,west=false:
|
||||
state: ${base_block}[east=false,north=true,south=false,waterlogged=false,west=false]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
east=false,north=false,south=true,waterlogged=false,west=false:
|
||||
state: ${base_block}[east=false,north=false,south=true,waterlogged=false,west=false]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 0
|
||||
rotation: 0
|
||||
east=false,north=false,south=false,waterlogged=false,west=true:
|
||||
state: ${base_block}[east=false,north=false,south=false,waterlogged=false,west=true]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 90
|
||||
rotation: 90
|
||||
east=true,north=true,south=false,waterlogged=false,west=false:
|
||||
state: ${base_block}[east=true,north=true,south=false,waterlogged=false,west=false]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
- item: ${fence_side_item}
|
||||
yaw: 270
|
||||
rotation: 270
|
||||
east=true,north=false,south=true,waterlogged=false,west=false:
|
||||
state: ${base_block}[east=true,north=false,south=true,waterlogged=false,west=false]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 0
|
||||
rotation: 0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 270
|
||||
rotation: 270
|
||||
east=true,north=false,south=false,waterlogged=false,west=true:
|
||||
state: ${base_block}[east=true,north=false,south=false,waterlogged=false,west=true]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 90
|
||||
rotation: 90
|
||||
- item: ${fence_side_item}
|
||||
yaw: 270
|
||||
rotation: 270
|
||||
east=false,north=true,south=true,waterlogged=false,west=false:
|
||||
state: ${base_block}[east=false,north=true,south=true,waterlogged=false,west=false]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 0
|
||||
rotation: 0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
east=false,north=true,south=false,waterlogged=false,west=true:
|
||||
state: ${base_block}[east=false,north=true,south=false,waterlogged=false,west=true]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 90
|
||||
rotation: 90
|
||||
- item: ${fence_side_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
east=false,north=false,south=true,waterlogged=false,west=true:
|
||||
state: ${base_block}[east=false,north=false,south=true,waterlogged=false,west=true]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 0
|
||||
rotation: 0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 90
|
||||
rotation: 90
|
||||
east=true,north=true,south=true,waterlogged=false,west=false:
|
||||
state: ${base_block}[east=true,north=true,south=true,waterlogged=false,west=false]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 0
|
||||
rotation: 0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
- item: ${fence_side_item}
|
||||
yaw: 270
|
||||
rotation: 270
|
||||
east=true,north=true,south=false,waterlogged=false,west=true:
|
||||
state: ${base_block}[east=true,north=true,south=false,waterlogged=false,west=true]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 90
|
||||
rotation: 90
|
||||
- item: ${fence_side_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
- item: ${fence_side_item}
|
||||
yaw: 270
|
||||
rotation: 270
|
||||
east=true,north=false,south=true,waterlogged=false,west=true:
|
||||
state: ${base_block}[east=true,north=false,south=true,waterlogged=false,west=true]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 0
|
||||
rotation: 0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 90
|
||||
rotation: 90
|
||||
- item: ${fence_side_item}
|
||||
yaw: 270
|
||||
rotation: 270
|
||||
east=false,north=true,south=true,waterlogged=false,west=true:
|
||||
state: ${base_block}[east=false,north=true,south=true,waterlogged=false,west=true]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 0
|
||||
rotation: 0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 90
|
||||
rotation: 90
|
||||
- item: ${fence_side_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
east=true,north=true,south=true,waterlogged=false,west=true:
|
||||
state: ${base_block}[east=true,north=true,south=true,waterlogged=false,west=true]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 0
|
||||
rotation: 0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 90
|
||||
rotation: 90
|
||||
- item: ${fence_side_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
- item: ${fence_side_item}
|
||||
yaw: 270
|
||||
rotation: 270
|
||||
east=false,north=false,south=false,waterlogged=true,west=false:
|
||||
state: ${base_block}[east=false,north=false,south=false,waterlogged=true,west=false]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
east=true,north=false,south=false,waterlogged=true,west=false:
|
||||
state: ${base_block}[east=true,north=false,south=false,waterlogged=true,west=false]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 270
|
||||
rotation: 270
|
||||
east=false,north=true,south=false,waterlogged=true,west=false:
|
||||
state: ${base_block}[east=false,north=true,south=false,waterlogged=true,west=false]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
east=false,north=false,south=true,waterlogged=true,west=false:
|
||||
state: ${base_block}[east=false,north=false,south=true,waterlogged=true,west=false]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 0
|
||||
rotation: 0
|
||||
east=false,north=false,south=false,waterlogged=true,west=true:
|
||||
state: ${base_block}[east=false,north=false,south=false,waterlogged=true,west=true]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 90
|
||||
rotation: 90
|
||||
east=true,north=true,south=false,waterlogged=true,west=false:
|
||||
state: ${base_block}[east=true,north=true,south=false,waterlogged=true,west=false]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
- item: ${fence_side_item}
|
||||
yaw: 270
|
||||
rotation: 270
|
||||
east=true,north=false,south=true,waterlogged=true,west=false:
|
||||
state: ${base_block}[east=true,north=false,south=true,waterlogged=true,west=false]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 0
|
||||
rotation: 0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 270
|
||||
rotation: 270
|
||||
east=true,north=false,south=false,waterlogged=true,west=true:
|
||||
state: ${base_block}[east=true,north=false,south=false,waterlogged=true,west=true]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 90
|
||||
rotation: 90
|
||||
- item: ${fence_side_item}
|
||||
yaw: 270
|
||||
rotation: 270
|
||||
east=false,north=true,south=true,waterlogged=true,west=false:
|
||||
state: ${base_block}[east=false,north=true,south=true,waterlogged=true,west=false]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 0
|
||||
rotation: 0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
east=false,north=true,south=false,waterlogged=true,west=true:
|
||||
state: ${base_block}[east=false,north=true,south=false,waterlogged=true,west=true]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 90
|
||||
rotation: 90
|
||||
- item: ${fence_side_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
east=false,north=false,south=true,waterlogged=true,west=true:
|
||||
state: ${base_block}[east=false,north=false,south=true,waterlogged=true,west=true]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 0
|
||||
rotation: 0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 90
|
||||
rotation: 90
|
||||
east=true,north=true,south=true,waterlogged=true,west=false:
|
||||
state: ${base_block}[east=true,north=true,south=true,waterlogged=true,west=false]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 0
|
||||
rotation: 0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
- item: ${fence_side_item}
|
||||
yaw: 270
|
||||
rotation: 270
|
||||
east=true,north=true,south=false,waterlogged=true,west=true:
|
||||
state: ${base_block}[east=true,north=true,south=false,waterlogged=true,west=true]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 90
|
||||
rotation: 90
|
||||
- item: ${fence_side_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
- item: ${fence_side_item}
|
||||
yaw: 270
|
||||
rotation: 270
|
||||
east=true,north=false,south=true,waterlogged=true,west=true:
|
||||
state: ${base_block}[east=true,north=false,south=true,waterlogged=true,west=true]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 0
|
||||
rotation: 0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 90
|
||||
rotation: 90
|
||||
- item: ${fence_side_item}
|
||||
yaw: 270
|
||||
rotation: 270
|
||||
east=false,north=true,south=true,waterlogged=true,west=true:
|
||||
state: ${base_block}[east=false,north=true,south=true,waterlogged=true,west=true]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 0
|
||||
rotation: 0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 90
|
||||
rotation: 90
|
||||
- item: ${fence_side_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
east=true,north=true,south=true,waterlogged=true,west=true:
|
||||
state: ${base_block}[east=true,north=true,south=true,waterlogged=true,west=true]
|
||||
entity-renderer:
|
||||
- item: ${fence_post_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
scale: 1.0003
|
||||
translation: 0,0.0001,0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 0
|
||||
rotation: 0
|
||||
- item: ${fence_side_item}
|
||||
yaw: 90
|
||||
rotation: 90
|
||||
- item: ${fence_side_item}
|
||||
yaw: 180
|
||||
rotation: 180
|
||||
- item: ${fence_side_item}
|
||||
yaw: 270
|
||||
rotation: 270
|
||||
variants:
|
||||
waterlogged=true:
|
||||
settings:
|
||||
|
||||
@@ -74,6 +74,7 @@ command.send_resource_pack.success.multiple: "<white>Send resource packs to <arg
|
||||
command.locale.set.failure: "<red>Invalid locale format: <arg:0></red>"
|
||||
command.locale.set.success: "<white>Updated selected locale to <arg:0> for <arg:1></white>"
|
||||
command.locale.unset.success: "<white>Cleared selected locale for <arg:0></white>"
|
||||
command.entity_view_distance_scale.set.success: "<white>Updated entity view distance scale to <arg:0> for <arg:1></white>"
|
||||
warning.network.resource_pack.unverified_uuid: "<yellow>Player <arg:0> is attempting to request a resource pack using a UUID (<arg:1>) that is not authenticated by the server.</yellow>"
|
||||
warning.config.pack.duplicated_files: "<red>Duplicated files Found. Please resolve them through config.yml 'resource-pack.duplicated-files-handler' section.</red>"
|
||||
warning.config.yaml.duplicated_key: "<red>Issue found in file <arg:0> - Found duplicated key '<arg:1>' at line <arg:2>, this might cause unexpected results.</red>"
|
||||
|
||||
@@ -31,6 +31,7 @@ import net.momirealms.craftengine.core.plugin.context.Context;
|
||||
import net.momirealms.craftengine.core.plugin.context.event.EventFunctions;
|
||||
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
|
||||
import net.momirealms.craftengine.core.plugin.context.function.Function;
|
||||
import net.momirealms.craftengine.core.plugin.entityculling.CullingData;
|
||||
import net.momirealms.craftengine.core.plugin.locale.LocalizedException;
|
||||
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
|
||||
import net.momirealms.craftengine.core.plugin.logger.Debugger;
|
||||
@@ -38,6 +39,7 @@ import net.momirealms.craftengine.core.registry.BuiltInRegistries;
|
||||
import net.momirealms.craftengine.core.registry.Holder;
|
||||
import net.momirealms.craftengine.core.registry.WritableRegistry;
|
||||
import net.momirealms.craftengine.core.util.*;
|
||||
import net.momirealms.craftengine.core.world.collision.AABB;
|
||||
import net.momirealms.sparrow.nbt.CompoundTag;
|
||||
import org.incendo.cloud.suggestion.Suggestion;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -52,6 +54,7 @@ import java.util.concurrent.ExecutionException;
|
||||
|
||||
public abstract class AbstractBlockManager extends AbstractModelGenerator implements BlockManager {
|
||||
private static final JsonElement EMPTY_VARIANT_MODEL = MiscUtils.init(new JsonObject(), o -> o.addProperty("model", "minecraft:block/empty"));
|
||||
private static final AABB DEFAULT_BLOCK_ENTITY_AABB = new AABB(-.5, -.5, -.5, .5, .5, .5);
|
||||
protected final BlockParser blockParser;
|
||||
protected final BlockStateMappingParser blockStateMappingParser;
|
||||
// 根据id获取自定义方块
|
||||
@@ -603,7 +606,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
BlockStateAppearance blockStateAppearance = new BlockStateAppearance(
|
||||
visualBlockState,
|
||||
parseBlockEntityRender(appearanceSection.get("entity-renderer")),
|
||||
ResourceConfigUtils.getAsAABB(appearanceSection.getOrDefault("aabb", 1), "aabb")
|
||||
parseCullingData(appearanceSection.get("entity-culling"))
|
||||
);
|
||||
appearances.put(appearanceName, blockStateAppearance);
|
||||
if (anyAppearance == null) {
|
||||
@@ -643,7 +646,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
}
|
||||
for (ImmutableBlockState possibleState : possibleStates) {
|
||||
possibleState.setVisualBlockState(appearance.blockState());
|
||||
possibleState.setEstimatedBoundingBox(appearance.estimateAABB());
|
||||
possibleState.setCullingData(appearance.cullingData());
|
||||
appearance.blockEntityRenderer().ifPresent(possibleState::setConstantRenderers);
|
||||
}
|
||||
}
|
||||
@@ -667,7 +670,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
if (visualState == null) {
|
||||
visualState = anyAppearance.blockState();
|
||||
state.setVisualBlockState(visualState);
|
||||
state.setEstimatedBoundingBox(anyAppearance.estimateAABB());
|
||||
state.setCullingData(anyAppearance.cullingData());
|
||||
anyAppearance.blockEntityRenderer().ifPresent(state::setConstantRenderers);
|
||||
}
|
||||
int appearanceId = visualState.registryId();
|
||||
@@ -707,6 +710,22 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
}, () -> GsonHelper.get().toJson(section)));
|
||||
}
|
||||
|
||||
private CullingData parseCullingData(Object arguments) {
|
||||
if (arguments instanceof Boolean b && !b) {
|
||||
return null;
|
||||
}
|
||||
if (!(arguments instanceof Map)) {
|
||||
return new CullingData(DEFAULT_BLOCK_ENTITY_AABB, Config.entityCullingViewDistance(), 0.5, true);
|
||||
}
|
||||
Map<String, Object> argumentsMap = ResourceConfigUtils.getAsMap(arguments, "entity-culling");
|
||||
return new CullingData(
|
||||
ResourceConfigUtils.getAsAABB(argumentsMap.getOrDefault("aabb", 1), "aabb"),
|
||||
ResourceConfigUtils.getAsInt(argumentsMap.getOrDefault("view-distance", Config.entityCullingViewDistance()), "view-distance"),
|
||||
ResourceConfigUtils.getAsDouble(argumentsMap.getOrDefault("aabb-expansion", 0.5), "aabb-expansion"),
|
||||
ResourceConfigUtils.getAsBoolean(argumentsMap.getOrDefault("ray-tracing", true), "ray-tracing")
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Optional<BlockEntityElementConfig<? extends BlockEntityElement>[]> parseBlockEntityRender(Object arguments) {
|
||||
if (arguments == null) return Optional.empty();
|
||||
|
||||
@@ -2,11 +2,12 @@ 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 net.momirealms.craftengine.core.plugin.entityculling.CullingData;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public record BlockStateAppearance(BlockStateWrapper blockState,
|
||||
Optional<BlockEntityElementConfig<? extends BlockEntityElement>[]> blockEntityRenderer,
|
||||
AABB estimateAABB) {
|
||||
@Nullable CullingData cullingData) {
|
||||
}
|
||||
|
||||
@@ -13,11 +13,11 @@ import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.loot.LootTable;
|
||||
import net.momirealms.craftengine.core.plugin.context.ContextHolder;
|
||||
import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters;
|
||||
import net.momirealms.craftengine.core.plugin.entityculling.CullingData;
|
||||
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;
|
||||
@@ -43,7 +43,8 @@ public final class ImmutableBlockState {
|
||||
private BlockEntityType<? extends BlockEntity> blockEntityType;
|
||||
@Nullable
|
||||
private BlockEntityElementConfig<? extends BlockEntityElement>[] renderers;
|
||||
private AABB estimatedBoundingBox;
|
||||
@Nullable
|
||||
private CullingData cullingData;
|
||||
|
||||
ImmutableBlockState(
|
||||
Holder.Reference<CustomBlock> owner,
|
||||
@@ -89,12 +90,13 @@ public final class ImmutableBlockState {
|
||||
this.renderers = renderers;
|
||||
}
|
||||
|
||||
public void setEstimatedBoundingBox(AABB aabb) {
|
||||
this.estimatedBoundingBox = aabb;
|
||||
@Nullable
|
||||
public CullingData cullingData() {
|
||||
return cullingData;
|
||||
}
|
||||
|
||||
public AABB estimatedBoundingBox() {
|
||||
return estimatedBoundingBox;
|
||||
public void setCullingData(@Nullable CullingData cullingData) {
|
||||
this.cullingData = cullingData;
|
||||
}
|
||||
|
||||
public boolean hasBlockEntity() {
|
||||
|
||||
@@ -2,18 +2,19 @@ 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.plugin.entityculling.CullingData;
|
||||
import net.momirealms.craftengine.core.world.Cullable;
|
||||
import net.momirealms.craftengine.core.world.collision.AABB;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ApiStatus.Experimental
|
||||
public class ConstantBlockEntityRenderer implements Cullable {
|
||||
private final BlockEntityElement[] elements;
|
||||
public final AABB aabb;
|
||||
public final CullingData cullingData;
|
||||
|
||||
public ConstantBlockEntityRenderer(BlockEntityElement[] elements, AABB aabb) {
|
||||
public ConstantBlockEntityRenderer(BlockEntityElement[] elements, @Nullable CullingData cullingData) {
|
||||
this.elements = elements;
|
||||
this.aabb = aabb;
|
||||
this.cullingData = cullingData;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -55,7 +56,11 @@ public class ConstantBlockEntityRenderer implements Cullable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AABB aabb() {
|
||||
return this.aabb;
|
||||
public CullingData cullingData() {
|
||||
return this.cullingData;
|
||||
}
|
||||
|
||||
public boolean canCull() {
|
||||
return this.cullingData != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,5 +11,9 @@ public interface BlockEntityElementConfig<E extends BlockEntityElement> {
|
||||
return null;
|
||||
}
|
||||
|
||||
default E createExact(World world, BlockPos pos, E previous) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<E> elementClass();
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public class TickingBlockEntityImpl<T extends BlockEntity> implements TickingBlo
|
||||
// 不是合法方块
|
||||
if (!this.blockEntity.isValidBlockState(state)) {
|
||||
this.chunk.removeBlockEntity(pos);
|
||||
Debugger.BLOCK_ENTITY.warn(() -> "Invalid block entity(" + this.blockEntity.getClass().getSimpleName() + ") with state " + state + " found at world " + this.chunk.world().name() + " " + pos, null);
|
||||
Debugger.BLOCK.warn(() -> "Invalid block entity(" + this.blockEntity.getClass().getSimpleName() + ") with state " + state + " found at world " + this.chunk.world().name() + " " + pos, null);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
||||
@@ -38,6 +38,8 @@ public abstract class Player extends AbstractEntity implements NetWorkUser {
|
||||
|
||||
public abstract void setClientSideWorld(World world);
|
||||
|
||||
public abstract void entityCullingTick();
|
||||
|
||||
public abstract float getDestroyProgress(Object blockState, BlockPos pos);
|
||||
|
||||
public abstract void setClientSideCanBreakBlock(boolean canBreak);
|
||||
@@ -189,6 +191,8 @@ public abstract class Player extends AbstractEntity implements NetWorkUser {
|
||||
|
||||
public abstract void setSelectedLocale(@Nullable Locale locale);
|
||||
|
||||
public abstract void setEntityCullingViewDistanceScale(double value);
|
||||
|
||||
public abstract void giveExperiencePoints(int xpPoints);
|
||||
|
||||
public abstract void giveExperienceLevels(int levels);
|
||||
@@ -214,4 +218,6 @@ public abstract class Player extends AbstractEntity implements NetWorkUser {
|
||||
@Override
|
||||
public void remove() {
|
||||
}
|
||||
|
||||
public abstract WorldPosition eyePosition();
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ import net.momirealms.craftengine.core.plugin.dependency.Dependencies;
|
||||
import net.momirealms.craftengine.core.plugin.dependency.Dependency;
|
||||
import net.momirealms.craftengine.core.plugin.dependency.DependencyManager;
|
||||
import net.momirealms.craftengine.core.plugin.dependency.DependencyManagerImpl;
|
||||
import net.momirealms.craftengine.core.plugin.entityculling.EntityCullingManager;
|
||||
import net.momirealms.craftengine.core.plugin.entityculling.EntityCullingManagerImpl;
|
||||
import net.momirealms.craftengine.core.plugin.gui.GuiManager;
|
||||
import net.momirealms.craftengine.core.plugin.gui.category.ItemBrowserManager;
|
||||
import net.momirealms.craftengine.core.plugin.gui.category.ItemBrowserManagerImpl;
|
||||
@@ -79,6 +81,7 @@ public abstract class CraftEngine implements Plugin {
|
||||
protected GlobalVariableManager globalVariableManager;
|
||||
protected ProjectileManager projectileManager;
|
||||
protected SeatManager seatManager;
|
||||
protected EntityCullingManager entityCullingManager;
|
||||
|
||||
private final PluginTaskRegistry beforeEnableTaskRegistry = new PluginTaskRegistry();
|
||||
private final PluginTaskRegistry afterEnableTaskRegistry = new PluginTaskRegistry();
|
||||
@@ -118,6 +121,8 @@ public abstract class CraftEngine implements Plugin {
|
||||
this.globalVariableManager = new GlobalVariableManager();
|
||||
// 初始化物品浏览器
|
||||
this.itemBrowserManager = new ItemBrowserManagerImpl(this);
|
||||
// 初始化实体剔除器
|
||||
this.entityCullingManager = new EntityCullingManagerImpl();
|
||||
}
|
||||
|
||||
public void setUpConfigAndLocale() {
|
||||
@@ -158,6 +163,7 @@ public abstract class CraftEngine implements Plugin {
|
||||
this.advancementManager.reload();
|
||||
this.projectileManager.reload();
|
||||
this.seatManager.reload();
|
||||
this.entityCullingManager.reload();
|
||||
}
|
||||
|
||||
private void runDelayTasks(boolean reloadRecipe) {
|
||||
@@ -349,6 +355,7 @@ public abstract class CraftEngine implements Plugin {
|
||||
if (this.translationManager != null) this.translationManager.disable();
|
||||
if (this.globalVariableManager != null) this.globalVariableManager.disable();
|
||||
if (this.projectileManager != null) this.projectileManager.disable();
|
||||
if (this.entityCullingManager != null) this.entityCullingManager.disable();
|
||||
if (this.scheduler != null) this.scheduler.shutdownScheduler();
|
||||
if (this.scheduler != null) this.scheduler.shutdownExecutor();
|
||||
if (this.commandManager != null) this.commandManager.unregisterFeatures();
|
||||
|
||||
@@ -54,6 +54,8 @@ public class Config {
|
||||
protected boolean debug$item;
|
||||
protected boolean debug$furniture;
|
||||
protected boolean debug$resource_pack;
|
||||
protected boolean debug$block;
|
||||
protected boolean debug$entity_culling;
|
||||
|
||||
protected boolean resource_pack$remove_tinted_leaves_particle;
|
||||
protected boolean resource_pack$generate_mod_assets;
|
||||
@@ -204,6 +206,12 @@ public class Config {
|
||||
protected int emoji$max_emojis_per_parse;
|
||||
|
||||
protected boolean client_optimization$entity_culling$enable;
|
||||
protected int client_optimization$entity_culling$view_distance;
|
||||
protected int client_optimization$entity_culling$threads;
|
||||
protected boolean client_optimization$entity_culling$ray_tracing;
|
||||
protected boolean client_optimization$entity_culling$rate_limiting$enable;
|
||||
protected int client_optimization$entity_culling$rate_limiting$bucket_size;
|
||||
protected int client_optimization$entity_culling$rate_limiting$restore_per_tick;
|
||||
|
||||
public Config(CraftEngine plugin) {
|
||||
this.plugin = plugin;
|
||||
@@ -307,6 +315,8 @@ public class Config {
|
||||
debug$item = config.getBoolean("debug.item", false);
|
||||
debug$furniture = config.getBoolean("debug.furniture", false);
|
||||
debug$resource_pack = config.getBoolean("debug.resource-pack", false);
|
||||
debug$block = config.getBoolean("debug.block", false);
|
||||
debug$entity_culling = config.getBoolean("debug.entity-culling", false);
|
||||
|
||||
// resource pack
|
||||
resource_pack$path = resolvePath(config.getString("resource-pack.path", "./generated/resource_pack.zip"));
|
||||
@@ -565,7 +575,15 @@ public class Config {
|
||||
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);
|
||||
if (firstTime) {
|
||||
client_optimization$entity_culling$enable = VersionHelper.PREMIUM && config.getBoolean("client-optimization.entity-culling.enable", false);
|
||||
}
|
||||
client_optimization$entity_culling$view_distance = config.getInt("client-optimization.entity-culling.view-distance", 64);
|
||||
client_optimization$entity_culling$threads = config.getInt("client-optimization.entity-culling.threads", 1);
|
||||
client_optimization$entity_culling$ray_tracing = client_optimization$entity_culling$enable && config.getBoolean("client-optimization.entity-culling.ray-tracing", true);
|
||||
client_optimization$entity_culling$rate_limiting$enable = config.getBoolean("client-optimization.entity-culling.rate-limiting.enable", true);
|
||||
client_optimization$entity_culling$rate_limiting$bucket_size = config.getInt("client-optimization.entity-culling.rate-limiting.bucket-size", 300);
|
||||
client_optimization$entity_culling$rate_limiting$restore_per_tick = config.getInt("client-optimization.entity-culling.rate-limiting.restore-per-tick", 5);
|
||||
|
||||
firstTime = false;
|
||||
}
|
||||
@@ -604,12 +622,12 @@ public class Config {
|
||||
return instance.debug$item;
|
||||
}
|
||||
|
||||
public static boolean debugBlockEntity() {
|
||||
return false;
|
||||
public static boolean debugBlock() {
|
||||
return instance.debug$block;
|
||||
}
|
||||
|
||||
public static boolean debugBlock() {
|
||||
return false;
|
||||
public static boolean debugEntityCulling() {
|
||||
return instance.debug$entity_culling;
|
||||
}
|
||||
|
||||
public static boolean debugFurniture() {
|
||||
@@ -1161,6 +1179,30 @@ public class Config {
|
||||
return instance.client_optimization$entity_culling$enable;
|
||||
}
|
||||
|
||||
public static int entityCullingViewDistance() {
|
||||
return instance.client_optimization$entity_culling$view_distance;
|
||||
}
|
||||
|
||||
public static int entityCullingThreads() {
|
||||
return instance.client_optimization$entity_culling$threads;
|
||||
}
|
||||
|
||||
public static boolean enableEntityCullingRateLimiting() {
|
||||
return instance.client_optimization$entity_culling$rate_limiting$enable;
|
||||
}
|
||||
|
||||
public static int entityCullingRateLimitingBucketSize() {
|
||||
return instance.client_optimization$entity_culling$rate_limiting$bucket_size;
|
||||
}
|
||||
|
||||
public static int entityCullingRateLimitingRestorePerTick() {
|
||||
return instance.client_optimization$entity_culling$rate_limiting$restore_per_tick;
|
||||
}
|
||||
|
||||
public static boolean entityCullingRayTracing() {
|
||||
return instance.client_optimization$entity_culling$ray_tracing;
|
||||
}
|
||||
|
||||
public YamlDocument loadOrCreateYamlData(String fileName) {
|
||||
Path path = this.plugin.dataFolderPath().resolve(fileName);
|
||||
if (!Files.exists(path)) {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package net.momirealms.craftengine.core.plugin.entityculling;
|
||||
|
||||
import net.momirealms.craftengine.core.world.collision.AABB;
|
||||
|
||||
public final class CullingData {
|
||||
public final AABB aabb;
|
||||
public final int maxDistance;
|
||||
public final double aabbExpansion;
|
||||
public final boolean rayTracing;
|
||||
|
||||
public CullingData(AABB aabb, int maxDistance, double aabbExpansion, boolean rayTracing) {
|
||||
this.aabb = aabb;
|
||||
this.maxDistance = maxDistance;
|
||||
this.aabbExpansion = aabbExpansion;
|
||||
this.rayTracing = rayTracing;
|
||||
}
|
||||
|
||||
public AABB aabb() {
|
||||
return this.aabb;
|
||||
}
|
||||
|
||||
public int maxDistance() {
|
||||
return this.maxDistance;
|
||||
}
|
||||
|
||||
public double aabbExpansion() {
|
||||
return this.aabbExpansion;
|
||||
}
|
||||
|
||||
public boolean rayTracing() {
|
||||
return this.rayTracing;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.momirealms.craftengine.core.plugin.entityculling;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.player.Player;
|
||||
import net.momirealms.craftengine.core.plugin.config.Config;
|
||||
import net.momirealms.craftengine.core.util.MiscUtils;
|
||||
import net.momirealms.craftengine.core.world.ChunkPos;
|
||||
import net.momirealms.craftengine.core.world.MutableVec3d;
|
||||
@@ -13,38 +14,58 @@ import java.util.Arrays;
|
||||
public final class EntityCulling {
|
||||
public static final int MAX_SAMPLES = 14;
|
||||
private final Player player;
|
||||
private final int maxDistance;
|
||||
private final double aabbExpansion;
|
||||
private final boolean[] dotSelectors = new boolean[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 final int[] lastHitBlock = new int[3];
|
||||
private boolean canCheckLastHitBlock = false;
|
||||
private int hitBlockCount = 0;
|
||||
private int lastVisitChunkX = Integer.MAX_VALUE;
|
||||
private int lastVisitChunkZ = Integer.MAX_VALUE;
|
||||
private ClientChunk lastVisitChunk = null;
|
||||
private int currentTokens = Config.entityCullingRateLimitingBucketSize();
|
||||
private double distanceScale = 1d;
|
||||
|
||||
public EntityCulling(Player player, int maxDistance, double aabbExpansion) {
|
||||
public EntityCulling(Player player) {
|
||||
this.player = player;
|
||||
this.maxDistance = maxDistance;
|
||||
this.aabbExpansion = aabbExpansion;
|
||||
for (int i = 0; i < MAX_SAMPLES; i++) {
|
||||
this.targetPoints[i] = new MutableVec3d(0,0,0);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isVisible(AABB aabb, Vec3d cameraPos) {
|
||||
public void setDistanceScale(double distanceScale) {
|
||||
this.distanceScale = distanceScale;
|
||||
}
|
||||
|
||||
public double distanceScale() {
|
||||
return distanceScale;
|
||||
}
|
||||
|
||||
public void restoreTokenOnTick() {
|
||||
this.currentTokens = Math.min(Config.entityCullingRateLimitingBucketSize(), this.currentTokens + Config.entityCullingRateLimitingRestorePerTick());
|
||||
}
|
||||
|
||||
public boolean takeToken() {
|
||||
if (this.currentTokens > 0) {
|
||||
this.currentTokens--;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isVisible(CullingData cullable, Vec3d cameraPos, boolean rayTracing) {
|
||||
// 情空标志位
|
||||
Arrays.fill(this.canCheckLastHitBlock, false);
|
||||
this.canCheckLastHitBlock = false;
|
||||
this.hitBlockCount = 0;
|
||||
AABB aabb = cullable.aabb;
|
||||
double aabbExpansion = cullable.aabbExpansion;
|
||||
|
||||
// 根据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);
|
||||
int minX = MiscUtils.floor(aabb.minX - aabbExpansion);
|
||||
int minY = MiscUtils.floor(aabb.minY - aabbExpansion);
|
||||
int minZ = MiscUtils.floor(aabb.minZ - aabbExpansion);
|
||||
int maxX = MiscUtils.ceil(aabb.maxX + aabbExpansion);
|
||||
int maxY = MiscUtils.ceil(aabb.maxY + aabbExpansion);
|
||||
int maxZ = MiscUtils.ceil(aabb.maxZ + aabbExpansion);
|
||||
|
||||
double cameraX = cameraPos.x;
|
||||
double cameraY = cameraPos.y;
|
||||
@@ -60,7 +81,8 @@ public final class EntityCulling {
|
||||
}
|
||||
|
||||
// 如果设置了最大距离
|
||||
if (this.maxDistance > 0) {
|
||||
double maxDistance = cullable.maxDistance * this.distanceScale;
|
||||
if (maxDistance > 0) {
|
||||
// 计算AABB到相机的最小距离
|
||||
double distanceSq = 0.0;
|
||||
// 计算XYZ轴方向的距离
|
||||
@@ -68,13 +90,17 @@ public final class EntityCulling {
|
||||
distanceSq += distanceSq(minY, maxY, cameraY, relY);
|
||||
distanceSq += distanceSq(minZ, maxZ, cameraZ, relZ);
|
||||
// 检查距离是否超过最大值
|
||||
double maxDistanceSq = this.maxDistance * this.maxDistance;
|
||||
double maxDistanceSq = maxDistance * maxDistance;
|
||||
// 超过最大距离,剔除
|
||||
if (distanceSq > maxDistanceSq) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!rayTracing || !cullable.rayTracing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 清空之前的缓存
|
||||
Arrays.fill(this.dotSelectors, false);
|
||||
if (relX == Relative.POSITIVE) {
|
||||
@@ -94,24 +120,24 @@ public final class EntityCulling {
|
||||
}
|
||||
|
||||
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 (this.dotSelectors[0]) targetPoints[size++].set(minX + 0.05, minY + 0.05, minZ + 0.05);
|
||||
if (this.dotSelectors[1]) targetPoints[size++].set(maxX - 0.05, minY + 0.05, minZ + 0.05);
|
||||
if (this.dotSelectors[2]) targetPoints[size++].set(minX + 0.05, minY + 0.05, maxZ - 0.05);
|
||||
if (this.dotSelectors[3]) targetPoints[size++].set(maxX - 0.05, minY + 0.05, maxZ - 0.05);
|
||||
if (this.dotSelectors[4]) targetPoints[size++].set(minX + 0.05, maxY - 0.05, minZ + 0.05);
|
||||
if (this.dotSelectors[5]) targetPoints[size++].set(maxX - 0.05, maxY - 0.05, minZ + 0.05);
|
||||
if (this.dotSelectors[6]) targetPoints[size++].set(minX + 0.05, maxY - 0.05, maxZ - 0.05);
|
||||
if (this.dotSelectors[7]) targetPoints[size++].set(maxX - 0.05, maxY - 0.05, maxZ - 0.05);
|
||||
// 面中心点
|
||||
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);
|
||||
if (this.dotSelectors[8]) targetPoints[size++].set(averageX, averageY, minZ + 0.05);
|
||||
if (this.dotSelectors[9]) targetPoints[size++].set(averageX, averageY, maxZ - 0.05);
|
||||
if (this.dotSelectors[10]) targetPoints[size++].set(minX + 0.05, averageY, averageZ);
|
||||
if (this.dotSelectors[11]) targetPoints[size++].set(maxX - 0.05, averageY, averageZ);
|
||||
if (this.dotSelectors[12]) targetPoints[size++].set(averageX, minY + 0.05, averageZ);
|
||||
if (this.dotSelectors[13]) targetPoints[size].set(averageX, maxY - 0.05, averageZ);
|
||||
|
||||
return isVisible(cameraPos, this.targetPoints, size);
|
||||
}
|
||||
@@ -168,7 +194,7 @@ public final class EntityCulling {
|
||||
int startBlockZ = MiscUtils.floor(start.z);
|
||||
|
||||
// 遍历所有目标点进行视线检测
|
||||
outer: for (int targetIndex = 0; targetIndex < targetCount; targetIndex++) {
|
||||
for (int targetIndex = 0; targetIndex < targetCount; targetIndex++) {
|
||||
MutableVec3d currentTarget = targets[targetIndex];
|
||||
|
||||
// 计算起点到目标的相对向量(世界坐标差)
|
||||
@@ -177,14 +203,9 @@ public final class EntityCulling {
|
||||
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;
|
||||
if (this.canCheckLastHitBlock) {
|
||||
if (rayIntersection(this.lastHitBlock[0], this.lastHitBlock[1], this.lastHitBlock[2], start, new MutableVec3d(deltaX, deltaY, deltaZ).normalize())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,9 +216,9 @@ public final class EntityCulling {
|
||||
|
||||
// 预计算每单位距离在各方块边界上的步进增量
|
||||
// 这些值表示射线穿过一个方块所需的时间分数
|
||||
double stepIncrementX = 1.0 / (absDeltaX + 1e-10); // 避免除0
|
||||
double stepIncrementY = 1.0 / (absDeltaY + 1e-10);
|
||||
double stepIncrementZ = 1.0 / (absDeltaZ + 1e-10);
|
||||
double stepIncrementX = 1.0 / absDeltaX;
|
||||
double stepIncrementY = 1.0 / absDeltaY;
|
||||
double stepIncrementZ = 1.0 / absDeltaZ;
|
||||
|
||||
// 射线将穿过的总方块数量(包括起点和终点)
|
||||
int totalBlocksToCheck = 1;
|
||||
@@ -270,21 +291,25 @@ public final class EntityCulling {
|
||||
if (isLineOfSightClear) {
|
||||
return true;
|
||||
} else {
|
||||
this.canCheckLastHitBlock[this.hitBlockCount++] = true;
|
||||
this.canCheckLastHitBlock = true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean stepRay(int currentBlockX, int currentBlockY, int currentBlockZ,
|
||||
private boolean stepRay(int startingX, int startingY, int startingZ,
|
||||
double stepSizeX, double stepSizeY, double stepSizeZ,
|
||||
int remainingSteps, int stepDirectionX, int stepDirectionY,
|
||||
int stepDirectionZ, double nextStepTimeY, double nextStepTimeX,
|
||||
double nextStepTimeZ) {
|
||||
int remainingSteps,
|
||||
int stepDirectionX, int stepDirectionY, int stepDirectionZ,
|
||||
double nextStepTimeY, double nextStepTimeX, double nextStepTimeZ) {
|
||||
|
||||
// 遍历射线路径上的所有方块(跳过最后一个目标方块)
|
||||
for (; remainingSteps > 1; remainingSteps--) {
|
||||
int currentBlockX = startingX;
|
||||
int currentBlockY = startingY;
|
||||
int currentBlockZ = startingZ;
|
||||
|
||||
// 遍历射线路径上的所有方块
|
||||
for (; remainingSteps > 0; remainingSteps--) {
|
||||
|
||||
// 检查当前方块是否遮挡视线
|
||||
if (isOccluding(currentBlockX, currentBlockY, currentBlockZ)) {
|
||||
@@ -315,6 +340,22 @@ public final class EntityCulling {
|
||||
return true;
|
||||
}
|
||||
|
||||
private int getCacheIndex(int x, int y, int z, int startX, int startY, int startZ) {
|
||||
int deltaX = startX + 16 - x;
|
||||
if (deltaX < 0 || deltaX >= 32) {
|
||||
return -1;
|
||||
}
|
||||
int deltaY = startY + 16 - y;
|
||||
if (deltaY < 0 || deltaY >= 32) {
|
||||
return -1;
|
||||
}
|
||||
int deltaZ = startZ + 16 - z;
|
||||
if (deltaZ < 0 || deltaZ >= 32) {
|
||||
return -1;
|
||||
}
|
||||
return deltaX + 32 * deltaY + 32 * 32 * deltaZ;
|
||||
}
|
||||
|
||||
private double distanceSq(int min, int max, double camera, Relative rel) {
|
||||
if (rel == Relative.NEGATIVE) {
|
||||
double dx = camera - max;
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package net.momirealms.craftengine.core.plugin.entityculling;
|
||||
|
||||
import net.momirealms.craftengine.core.plugin.Manageable;
|
||||
|
||||
public interface EntityCullingManager extends Manageable {
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package net.momirealms.craftengine.core.plugin.entityculling;
|
||||
|
||||
import net.momirealms.craftengine.core.plugin.config.Config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class EntityCullingManagerImpl implements EntityCullingManager {
|
||||
private final List<EntityCullingThread> threads = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
if (Config.enableEntityCulling()) {
|
||||
int threads = Math.min(64, Math.max(Config.entityCullingThreads(), 1));
|
||||
for (int i = 0; i < threads; i++) {
|
||||
EntityCullingThread thread = new EntityCullingThread(i, threads);
|
||||
this.threads.add(thread);
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
if (!this.threads.isEmpty()) {
|
||||
for (EntityCullingThread thread : this.threads) {
|
||||
thread.stop();
|
||||
}
|
||||
this.threads.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package net.momirealms.craftengine.core.plugin.entityculling;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.player.Player;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.config.Config;
|
||||
import net.momirealms.craftengine.core.plugin.logger.Debugger;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class EntityCullingThread {
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
private final AtomicBoolean isRunning = new AtomicBoolean(false);
|
||||
private final int id;
|
||||
private final int threads;
|
||||
private int timer;
|
||||
|
||||
public EntityCullingThread(int id, int threads) {
|
||||
this.id = id;
|
||||
this.threads = threads;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
// 错开线程启动时间,避免所有线程同时执行
|
||||
long initialDelay = this.id * (50L / this.threads);
|
||||
this.scheduler.scheduleAtFixedRate(this::scheduleTask, initialDelay, 50, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private void scheduleTask() {
|
||||
// 使用CAS操作,更安全
|
||||
if (!this.isRunning.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.scheduler.execute(() -> {
|
||||
try {
|
||||
int processed = 0;
|
||||
long startTime = System.nanoTime();
|
||||
|
||||
for (Player player : CraftEngine.instance().networkManager().onlineUsers()) {
|
||||
// 使用绝对值确保非负,使用 threads 而不是 threads-1 确保均匀分布
|
||||
if (Math.abs(player.uuid().hashCode()) % this.threads == this.id) {
|
||||
player.entityCullingTick();
|
||||
processed++;
|
||||
}
|
||||
}
|
||||
|
||||
long duration = System.nanoTime() - startTime;
|
||||
if (Config.debugEntityCulling() && this.timer++ % 20 == 0) {
|
||||
String value = String.format("EntityCullingThread-%d processed %d players in %sms",
|
||||
this.id, processed, String.format("%.2f", duration / 1_000_000.0));
|
||||
Debugger.ENTITY_CULLING.debug(() -> value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().severe("Failed to run entity culling tick: " + e.getMessage());
|
||||
} finally {
|
||||
this.isRunning.set(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
this.scheduler.shutdown();
|
||||
}
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -41,4 +41,5 @@ public interface MessageConstants {
|
||||
TranslatableComponent.Builder COMMAND_ITEM_CLEAR_FAILED_MULTIPLE = Component.translatable().key("command.item.clear.failed.multiple");
|
||||
TranslatableComponent.Builder COMMAND_ITEM_CLEAR_TEST_SINGLE = Component.translatable().key("command.item.clear.test.single");
|
||||
TranslatableComponent.Builder COMMAND_ITEM_CLEAR_TEST_MULTIPLE = Component.translatable().key("command.item.clear.test.multiple");
|
||||
TranslatableComponent.Builder COMMAND_ENTITY_VIEW_DISTANCE_SCALE_SET_SUCCESS = Component.translatable().key("command.entity_view_distance_scale.set.success");
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ public enum Debugger {
|
||||
RESOURCE_PACK(Config::debugResourcePack),
|
||||
ITEM(Config::debugItem),
|
||||
BLOCK(Config::debugBlock),
|
||||
BLOCK_ENTITY(Config::debugBlockEntity);
|
||||
ENTITY_CULLING(Config::debugEntityCulling);
|
||||
|
||||
private final Supplier<Boolean> condition;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.lang.Thread.UncaughtExceptionHandler;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -46,6 +47,13 @@ public abstract class AbstractJavaScheduler<T> implements SchedulerAdapter<T> {
|
||||
return new AsyncTask(future);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchedulerTask asyncRepeating(Consumer<SchedulerTask> task, long delay, long interval, TimeUnit unit) {
|
||||
LazyAsyncTask asyncTask = new LazyAsyncTask();
|
||||
asyncTask.future = this.scheduler.scheduleAtFixedRate(() -> this.worker.execute(() -> task.accept(asyncTask)), delay, interval, unit);
|
||||
return asyncTask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdownScheduler() {
|
||||
this.scheduler.shutdown();
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package net.momirealms.craftengine.core.plugin.scheduler;
|
||||
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
public class LazyAsyncTask implements SchedulerTask {
|
||||
|
||||
public ScheduledFuture<?> future;
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
if (future != null) {
|
||||
future.cancel(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancelled() {
|
||||
if (future == null) return false;
|
||||
return future.isCancelled();
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package net.momirealms.craftengine.core.plugin.scheduler;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface SchedulerAdapter<W> {
|
||||
|
||||
@@ -25,6 +26,8 @@ public interface SchedulerAdapter<W> {
|
||||
|
||||
SchedulerTask asyncRepeating(Runnable task, long delay, long interval, TimeUnit unit);
|
||||
|
||||
SchedulerTask asyncRepeating(Consumer<SchedulerTask> task, long delay, long interval, TimeUnit unit);
|
||||
|
||||
void shutdownScheduler();
|
||||
|
||||
void shutdownExecutor();
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package net.momirealms.craftengine.core.world;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.player.Player;
|
||||
import net.momirealms.craftengine.core.world.collision.AABB;
|
||||
import net.momirealms.craftengine.core.plugin.entityculling.CullingData;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface Cullable {
|
||||
|
||||
AABB aabb();
|
||||
|
||||
void show(Player player);
|
||||
|
||||
void hide(Player player);
|
||||
|
||||
@Nullable
|
||||
CullingData cullingData();
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityEl
|
||||
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.entityculling.CullingData;
|
||||
import net.momirealms.craftengine.core.plugin.logger.Debugger;
|
||||
import net.momirealms.craftengine.core.world.*;
|
||||
import net.momirealms.craftengine.core.world.chunk.client.VirtualCullableObject;
|
||||
@@ -139,7 +140,12 @@ 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, state.estimatedBoundingBox().move(pos));
|
||||
ConstantBlockEntityRenderer renderer = new ConstantBlockEntityRenderer(
|
||||
elements,
|
||||
Optional.ofNullable(state.cullingData())
|
||||
.map(data -> new CullingData(data.aabb.move(pos), data.maxDistance, data.aabbExpansion, data.rayTracing))
|
||||
.orElse(null)
|
||||
);
|
||||
World wrappedWorld = this.world.world();
|
||||
List<Player> trackedBy = getTrackedBy();
|
||||
boolean hasTrackedBy = trackedBy != null && !trackedBy.isEmpty();
|
||||
@@ -147,6 +153,12 @@ public class CEChunk {
|
||||
if (previous != null) {
|
||||
// 由于entity-render的体量基本都很小,所以考虑一个特殊情况,即前后都是1个renderer,对此情况进行简化和优化
|
||||
BlockEntityElement[] previousElements = previous.elements().clone();
|
||||
|
||||
/*
|
||||
*
|
||||
* 1 对 1,命中率最高
|
||||
*
|
||||
*/
|
||||
if (previousElements.length == 1 && renderers.length == 1) {
|
||||
BlockEntityElement previousElement = previousElements[0];
|
||||
BlockEntityElementConfig<? extends BlockEntityElement> config = renderers[0];
|
||||
@@ -156,18 +168,19 @@ public class CEChunk {
|
||||
if (element != null) {
|
||||
elements[0] = element;
|
||||
if (hasTrackedBy) {
|
||||
// 如果启用实体剔除,那么只对已经渲染的进行变换
|
||||
if (Config.enableEntityCulling()) {
|
||||
for (Player player : trackedBy) {
|
||||
for (Player player : trackedBy) {
|
||||
// 如果启用剔除,则暂时保留原先可见度,因为大概率可见度不发生变化
|
||||
if (Config.enableEntityCulling()) {
|
||||
VirtualCullableObject trackedBlockEntity = player.getTrackedBlockEntity(pos);
|
||||
if (trackedBlockEntity == null || trackedBlockEntity.isShown()) {
|
||||
if (trackedBlockEntity == null || trackedBlockEntity.isShown) {
|
||||
element.transform(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 否则直接变换
|
||||
else {
|
||||
for (Player player : trackedBy) {
|
||||
if (trackedBlockEntity != null) {
|
||||
trackedBlockEntity.setCullable(renderer);
|
||||
} else {
|
||||
player.addTrackedBlockEntity(pos, renderer);
|
||||
}
|
||||
} else {
|
||||
element.transform(player);
|
||||
}
|
||||
}
|
||||
@@ -178,44 +191,56 @@ public class CEChunk {
|
||||
BlockEntityElement element = config.create(wrappedWorld, pos);
|
||||
elements[0] = element;
|
||||
if (hasTrackedBy) {
|
||||
// 如果启用实体剔除,那么只添加记录
|
||||
if (Config.enableEntityCulling()) {
|
||||
for (Player player : trackedBy) {
|
||||
player.addTrackedBlockEntity(pos, renderer);
|
||||
}
|
||||
}
|
||||
// 否则直接显示
|
||||
else {
|
||||
for (Player player : trackedBy) {
|
||||
for (Player player : trackedBy) {
|
||||
if (Config.enableEntityCulling()) {
|
||||
VirtualCullableObject trackedBlockEntity = player.getTrackedBlockEntity(pos);
|
||||
if (trackedBlockEntity != null) {
|
||||
if (trackedBlockEntity.isShown) {
|
||||
trackedBlockEntity.setShown(player, false);
|
||||
}
|
||||
trackedBlockEntity.setCullable(renderer);
|
||||
} else {
|
||||
player.addTrackedBlockEntity(pos, renderer);
|
||||
}
|
||||
} else {
|
||||
previousElement.hide(player);
|
||||
element.show(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
/*
|
||||
*
|
||||
* 1 对 多, 多 对 多
|
||||
*
|
||||
*/
|
||||
else {
|
||||
|
||||
VirtualCullableObject[] previousObjects = hasTrackedBy ? new VirtualCullableObject[trackedBy.size()] : null;
|
||||
if (hasTrackedBy) {
|
||||
for (int j = 0; j < previousObjects.length; j++) {
|
||||
previousObjects[j] = trackedBy.get(j).getTrackedBlockEntity(pos);
|
||||
}
|
||||
}
|
||||
|
||||
outer: for (int i = 0; i < elements.length; i++) {
|
||||
BlockEntityElementConfig<? extends BlockEntityElement> config = renderers[i];
|
||||
/*
|
||||
* 严格可变换部分
|
||||
*/
|
||||
for (int j = 0; j < previousElements.length; j++) {
|
||||
BlockEntityElement previousElement = previousElements[j];
|
||||
if (previousElement != null && config.elementClass().isInstance(previousElement)) {
|
||||
BlockEntityElement newElement = ((BlockEntityElementConfig) config).create(wrappedWorld, pos, previousElement);
|
||||
BlockEntityElement newElement = ((BlockEntityElementConfig) config).createExact(wrappedWorld, pos, previousElement);
|
||||
if (newElement != null) {
|
||||
previousElements[j] = null;
|
||||
elements[i] = newElement;
|
||||
if (hasTrackedBy) {
|
||||
// 如果启用实体剔除,那么只对已经渲染的进行变换
|
||||
if (Config.enableEntityCulling()) {
|
||||
for (Player player : trackedBy) {
|
||||
VirtualCullableObject trackedBlockEntity = player.getTrackedBlockEntity(pos);
|
||||
if (trackedBlockEntity == null || trackedBlockEntity.isShown()) {
|
||||
newElement.transform(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 否则直接变换
|
||||
else {
|
||||
for (Player player : trackedBy) {
|
||||
for (int k = 0; k < trackedBy.size(); k++) {
|
||||
Player player = trackedBy.get(k);
|
||||
VirtualCullableObject cullableObject = previousObjects[k];
|
||||
if (cullableObject == null || cullableObject.isShown) {
|
||||
newElement.transform(player);
|
||||
}
|
||||
}
|
||||
@@ -224,21 +249,48 @@ public class CEChunk {
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 可变换部分
|
||||
*/
|
||||
for (int j = 0; j < previousElements.length; j++) {
|
||||
BlockEntityElement previousElement = previousElements[j];
|
||||
if (previousElement != null && config.elementClass().isInstance(previousElement)) {
|
||||
BlockEntityElement newElement = ((BlockEntityElementConfig) config).create(wrappedWorld, pos, previousElement);
|
||||
if (newElement != null) {
|
||||
previousElements[j] = null;
|
||||
elements[i] = newElement;
|
||||
if (hasTrackedBy) {
|
||||
for (int k = 0; k < trackedBy.size(); k++) {
|
||||
Player player = trackedBy.get(k);
|
||||
VirtualCullableObject cullableObject = previousObjects[k];
|
||||
if (cullableObject == null || cullableObject.isShown) {
|
||||
newElement.transform(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* 不可变换的直接生成
|
||||
*/
|
||||
BlockEntityElement newElement = config.create(wrappedWorld, pos);
|
||||
elements[i] = newElement;
|
||||
if (hasTrackedBy) {
|
||||
if (Config.enableEntityCulling()) {
|
||||
for (Player player : trackedBy) {
|
||||
player.addTrackedBlockEntity(pos, renderer);
|
||||
}
|
||||
} else {
|
||||
for (Player player : trackedBy) {
|
||||
for (int k = 0; k < trackedBy.size(); k++) {
|
||||
Player player = trackedBy.get(k);
|
||||
VirtualCullableObject cullableObject = previousObjects[k];
|
||||
if (cullableObject == null || cullableObject.isShown) {
|
||||
newElement.show(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasTrackedBy) {
|
||||
/*
|
||||
* 未能完成变化的,需要直接删除
|
||||
*/
|
||||
for (int i = 0; i < previousElements.length; i++) {
|
||||
BlockEntityElement previousElement = previousElements[i];
|
||||
if (previousElement != null) {
|
||||
@@ -247,19 +299,31 @@ public class CEChunk {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 添加 track
|
||||
for (int i = 0; i < previousObjects.length; i++) {
|
||||
VirtualCullableObject previousObject = previousObjects[i];
|
||||
if (previousObject != null) {
|
||||
previousObject.setCullable(renderer);
|
||||
} else {
|
||||
trackedBy.get(i).addTrackedBlockEntity(pos, renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
*
|
||||
* 全新方块实体
|
||||
*
|
||||
*/
|
||||
for (int i = 0; i < elements.length; i++) {
|
||||
elements[i] = renderers[i].create(wrappedWorld, pos);
|
||||
}
|
||||
if (hasTrackedBy) {
|
||||
if (Config.enableEntityCulling()) {
|
||||
for (Player player : trackedBy) {
|
||||
for (Player player : trackedBy) {
|
||||
if (Config.enableEntityCulling()) {
|
||||
player.addTrackedBlockEntity(pos, renderer);
|
||||
}
|
||||
} else {
|
||||
for (Player player : trackedBy) {
|
||||
} else {
|
||||
renderer.show(player);
|
||||
}
|
||||
}
|
||||
@@ -456,7 +520,7 @@ public class CEChunk {
|
||||
BlockPos pos = blockEntity.pos();
|
||||
ImmutableBlockState blockState = this.getBlockState(pos);
|
||||
if (!blockState.hasBlockEntity()) {
|
||||
Debugger.BLOCK_ENTITY.debug(() -> "Failed to add invalid block entity " + blockEntity.saveAsTag() + " at " + pos);
|
||||
Debugger.BLOCK.debug(() -> "Failed to add invalid block entity " + blockEntity.saveAsTag() + " at " + pos);
|
||||
return;
|
||||
}
|
||||
// 设置方块实体所在世界
|
||||
|
||||
@@ -4,14 +4,18 @@ 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 Cullable cullable;
|
||||
public boolean isShown;
|
||||
|
||||
public VirtualCullableObject(Cullable cullable) {
|
||||
this.cullable = cullable;
|
||||
this.isShown = false;
|
||||
}
|
||||
|
||||
public void setCullable(Cullable cullable) {
|
||||
this.cullable = cullable;
|
||||
}
|
||||
|
||||
public Cullable cullable() {
|
||||
return cullable;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ public final class DefaultBlockEntitySerializer {
|
||||
Key id = Key.of(data.getString("id"));
|
||||
BlockEntityType<?> type = BuiltInRegistries.BLOCK_ENTITY_TYPE.getValue(id);
|
||||
if (type == null) {
|
||||
Debugger.BLOCK_ENTITY.debug(() -> "Unknown block entity type: " + id);
|
||||
Debugger.BLOCK.debug(() -> "Unknown block entity type: " + id);
|
||||
} else {
|
||||
BlockPos pos = BlockEntity.readPosAndVerify(data, chunk.chunkPos());
|
||||
ImmutableBlockState blockState = chunk.getBlockState(pos);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
org.gradle.jvmargs=-Xmx1G
|
||||
|
||||
# Project settings
|
||||
project_version=0.0.65.13.1
|
||||
config_version=58
|
||||
lang_version=40
|
||||
project_version=0.0.65.15
|
||||
config_version=60
|
||||
lang_version=41
|
||||
project_group=net.momirealms
|
||||
latest_supported_version=1.21.10
|
||||
|
||||
@@ -48,7 +48,7 @@ byte_buddy_version=1.18.1
|
||||
ahocorasick_version=0.6.3
|
||||
snake_yaml_version=2.5
|
||||
anti_grief_version=1.0.5
|
||||
nms_helper_version=1.0.137
|
||||
nms_helper_version=1.0.138
|
||||
evalex_version=3.5.0
|
||||
reactive_streams_version=1.0.4
|
||||
amazon_awssdk_version=2.38.7
|
||||
|
||||
Reference in New Issue
Block a user