mirror of
https://github.com/Xiao-MoMi/craft-engine.git
synced 2025-12-19 15:09:15 +00:00
Merge branch 'dev' into update/1.21.11
This commit is contained in:
@@ -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获取自定义方块
|
||||
@@ -258,6 +261,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
|
||||
public class BlockStateMappingParser extends SectionConfigParser {
|
||||
public static final String[] CONFIG_SECTION_NAME = new String[]{"block-state-mappings", "block-state-mapping"};
|
||||
private int count;
|
||||
|
||||
@Override
|
||||
public String[] sectionId() {
|
||||
@@ -269,6 +273,16 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
return LoadingSequence.BLOCK_STATE_MAPPING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return this.count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preProcess() {
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, Map<String, Object> section) throws LocalizedException {
|
||||
ExceptionCollector<LocalizedResourceConfigException> exceptionCollector = new ExceptionCollector<>();
|
||||
@@ -299,6 +313,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
List<BlockStateWrapper> blockStateWrappers = AbstractBlockManager.this.blockStateArranger.computeIfAbsent(blockOwnerId, k -> new ArrayList<>());
|
||||
blockStateWrappers.add(beforeState);
|
||||
AbstractBlockManager.this.autoVisualBlockStateCandidates[beforeState.registryId()] = createVisualBlockCandidate(beforeState);
|
||||
this.count++;
|
||||
}
|
||||
exceptionCollector.throwIfPresent();
|
||||
}
|
||||
@@ -328,6 +343,11 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
|
||||
this.visualBlockStateAllocator = new VisualBlockStateAllocator(AbstractBlockManager.this.plugin.dataFolderPath().resolve("cache").resolve("visual-block-states.json"), candidates, AbstractBlockManager.this::createVanillaBlockState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return AbstractBlockManager.this.byId.size();
|
||||
}
|
||||
|
||||
public void addPendingConfigSection(PendingConfigSection section) {
|
||||
this.pendingConfigSections.add(section);
|
||||
}
|
||||
@@ -603,7 +623,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 +663,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 +687,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 +727,18 @@ 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();
|
||||
|
||||
@@ -41,6 +41,7 @@ public class BlockSettings {
|
||||
float friction = 0.6f;
|
||||
float speedFactor = 1f;
|
||||
float jumpFactor = 1f;
|
||||
Map<CustomDataType<?>, Object> customData = new IdentityHashMap<>(4);
|
||||
|
||||
private BlockSettings() {}
|
||||
|
||||
@@ -107,9 +108,29 @@ public class BlockSettings {
|
||||
newSettings.speedFactor = settings.speedFactor;
|
||||
newSettings.jumpFactor = settings.jumpFactor;
|
||||
newSettings.friction = settings.friction;
|
||||
newSettings.customData = new IdentityHashMap<>(settings.customData);
|
||||
return newSettings;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getCustomData(CustomDataType<T> type) {
|
||||
return (T) this.customData.get(type);
|
||||
}
|
||||
|
||||
public void clearCustomData() {
|
||||
this.customData.clear();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T removeCustomData(CustomDataType<?> type) {
|
||||
return (T) this.customData.remove(type);
|
||||
}
|
||||
|
||||
public <T> void addCustomData(CustomDataType<T> key, T value) {
|
||||
this.customData.put(key, value);
|
||||
}
|
||||
|
||||
public Set<Key> tags() {
|
||||
return tags;
|
||||
}
|
||||
@@ -542,7 +563,7 @@ public class BlockSettings {
|
||||
}));
|
||||
}
|
||||
|
||||
private static void registerFactory(String id, Modifier.Factory factory) {
|
||||
public static void registerFactory(String id, Modifier.Factory factory) {
|
||||
FACTORIES.put(id, factory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -24,7 +24,7 @@ public abstract class BlockEntity {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public final CompoundTag saveAsTag() {
|
||||
public CompoundTag saveAsTag() {
|
||||
CompoundTag tag = new CompoundTag();
|
||||
this.saveId(tag);
|
||||
this.savePos(tag);
|
||||
|
||||
@@ -5,6 +5,7 @@ import net.momirealms.craftengine.core.util.Key;
|
||||
public final class BlockEntityTypeKeys {
|
||||
private BlockEntityTypeKeys() {}
|
||||
|
||||
public static final Key INACTIVE = Key.of("craftengine:inactive");
|
||||
public static final Key UNSAFE_COMPOSITE = Key.of("craftengine:unsafe_composite");
|
||||
public static final Key SIMPLE_STORAGE = Key.of("craftengine:simple_storage");
|
||||
public static final Key SIMPLE_PARTICLE = Key.of("craftengine:simple_particle");
|
||||
|
||||
@@ -7,6 +7,7 @@ import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.ResourceKey;
|
||||
|
||||
public abstract class BlockEntityTypes {
|
||||
public static final BlockEntityType<InactiveBlockEntity> INACTIVE = register(BlockEntityTypeKeys.INACTIVE);
|
||||
|
||||
public static <T extends BlockEntity> BlockEntityType<T> register(Key id) {
|
||||
BlockEntityType<T> type = new BlockEntityType<>(id);
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package net.momirealms.craftengine.core.block.entity;
|
||||
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.world.BlockPos;
|
||||
import net.momirealms.sparrow.nbt.CompoundTag;
|
||||
|
||||
public class InactiveBlockEntity extends BlockEntity {
|
||||
private final CompoundTag tag;
|
||||
|
||||
public InactiveBlockEntity(BlockPos pos,
|
||||
ImmutableBlockState blockState,
|
||||
CompoundTag tag) {
|
||||
super(BlockEntityTypes.INACTIVE, pos, blockState);
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag saveAsTag() {
|
||||
return this.tag;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package net.momirealms.craftengine.core.block.entity.render.element;
|
||||
import java.util.Map;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface BlockEntityElementConfigFactory {
|
||||
public interface BlockEntityElementConfigFactory<E extends BlockEntityElement> {
|
||||
|
||||
<E extends BlockEntityElement> BlockEntityElementConfig<E> create(Map<String, Object> args);
|
||||
BlockEntityElementConfig<E> create(Map<String, Object> args);
|
||||
}
|
||||
|
||||
@@ -15,14 +15,15 @@ public abstract class BlockEntityElementConfigs {
|
||||
public static final Key TEXT_DISPLAY = Key.of("craftengine:text_display");
|
||||
public static final Key ITEM = Key.of("craftengine:item");
|
||||
|
||||
public static void register(Key key, BlockEntityElementConfigFactory type) {
|
||||
((WritableRegistry<BlockEntityElementConfigFactory>) BuiltInRegistries.BLOCK_ENTITY_ELEMENT_TYPE)
|
||||
public static void register(Key key, BlockEntityElementConfigFactory<?> type) {
|
||||
((WritableRegistry<BlockEntityElementConfigFactory<?>>) BuiltInRegistries.BLOCK_ENTITY_ELEMENT_TYPE)
|
||||
.register(ResourceKey.create(Registries.BLOCK_ENTITY_ELEMENT_TYPE.location(), key), type);
|
||||
}
|
||||
|
||||
public static <E extends BlockEntityElement> BlockEntityElementConfig<E> fromMap(Map<String, Object> arguments) {
|
||||
Key type = Optional.ofNullable(arguments.get("type")).map(String::valueOf).map(it -> Key.withDefaultNamespace(it, "craftengine")).orElse(ITEM_DISPLAY);
|
||||
BlockEntityElementConfigFactory factory = BuiltInRegistries.BLOCK_ENTITY_ELEMENT_TYPE.getValue(type);
|
||||
@SuppressWarnings("unchecked")
|
||||
BlockEntityElementConfigFactory<E> factory = (BlockEntityElementConfigFactory<E>) BuiltInRegistries.BLOCK_ENTITY_ELEMENT_TYPE.getValue(type);
|
||||
if (factory == null) {
|
||||
throw new LocalizedResourceConfigException("warning.config.block.state.entity_renderer.invalid_type", type.toString());
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -11,6 +11,8 @@ import java.util.UUID;
|
||||
public interface Entity {
|
||||
Key type();
|
||||
|
||||
boolean isValid();
|
||||
|
||||
double x();
|
||||
|
||||
double y();
|
||||
|
||||
@@ -12,14 +12,14 @@ public interface EntityData<T> {
|
||||
|
||||
Object entityDataAccessor();
|
||||
|
||||
Object create(Object entityDataAccessor, Object value);
|
||||
Object create(Object entityDataAccessor, T value);
|
||||
|
||||
default Object createEntityDataIfNotDefaultValue(T value) {
|
||||
if (defaultValue().equals(value)) return null;
|
||||
return create(entityDataAccessor(), value);
|
||||
}
|
||||
|
||||
default Object createEntityData(Object value) {
|
||||
default Object createEntityData(T value) {
|
||||
return create(entityDataAccessor(), value);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.momirealms.craftengine.core.entity;
|
||||
package net.momirealms.craftengine.core.entity.display;
|
||||
|
||||
public enum Billboard {
|
||||
FIXED(0),
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.momirealms.craftengine.core.entity;
|
||||
package net.momirealms.craftengine.core.entity.display;
|
||||
|
||||
public enum ItemDisplayContext {
|
||||
NONE(0),
|
||||
@@ -1,89 +0,0 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture;
|
||||
|
||||
import net.momirealms.craftengine.core.loot.LootTable;
|
||||
import net.momirealms.craftengine.core.plugin.context.Context;
|
||||
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
|
||||
import net.momirealms.craftengine.core.plugin.context.function.Function;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public abstract class AbstractCustomFurniture implements CustomFurniture {
|
||||
private final Key id;
|
||||
private final FurnitureSettings settings;
|
||||
private final Map<AnchorType, Placement> placements;
|
||||
private final Map<EventTrigger, List<Function<Context>>> events;
|
||||
@Nullable
|
||||
private final LootTable<?> lootTable;
|
||||
|
||||
private final AnchorType anyType;
|
||||
|
||||
protected AbstractCustomFurniture(@NotNull Key id,
|
||||
@NotNull FurnitureSettings settings,
|
||||
@NotNull Map<AnchorType, Placement> placements,
|
||||
@NotNull Map<EventTrigger, List<Function<Context>>> events,
|
||||
@Nullable LootTable<?> lootTable) {
|
||||
this.id = id;
|
||||
this.settings = settings;
|
||||
this.placements = placements;
|
||||
this.lootTable = lootTable;
|
||||
this.events = events;
|
||||
this.anyType = placements.keySet().stream().findFirst().orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Context context, EventTrigger trigger) {
|
||||
for (Function<Context> function : Optional.ofNullable(this.events.get(trigger)).orElse(Collections.emptyList())) {
|
||||
function.run(context);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<AnchorType, Placement> placements() {
|
||||
return this.placements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FurnitureSettings settings() {
|
||||
return this.settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable LootTable<?> lootTable() {
|
||||
return this.lootTable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnchorType getAnyAnchorType() {
|
||||
return this.anyType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllowedPlacement(AnchorType anchorType) {
|
||||
return this.placements.containsKey(anchorType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Placement getPlacement(AnchorType anchorType) {
|
||||
return this.placements.get(anchorType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Placement getValidPlacement(AnchorType anchorType) {
|
||||
Placement placement = this.placements.get(anchorType);
|
||||
if (placement == null) {
|
||||
return this.placements.get(getAnyAnchorType());
|
||||
}
|
||||
return placement;
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.Billboard;
|
||||
import net.momirealms.craftengine.core.entity.ItemDisplayContext;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
public abstract class AbstractFurnitureElement implements FurnitureElement {
|
||||
private final Key item;
|
||||
private final Billboard billboard;
|
||||
private final ItemDisplayContext transform;
|
||||
private final Vector3f scale;
|
||||
private final Vector3f translation;
|
||||
private final Vector3f position;
|
||||
private final Quaternionf rotation;
|
||||
private final boolean applyDyedColor;
|
||||
private final float shadowRadius;
|
||||
private final float shadowStrength;
|
||||
|
||||
public AbstractFurnitureElement(Key item,
|
||||
Billboard billboard,
|
||||
ItemDisplayContext transform,
|
||||
Vector3f scale,
|
||||
Vector3f translation,
|
||||
Vector3f position,
|
||||
Quaternionf rotation,
|
||||
float shadowRadius,
|
||||
float shadowStrength,
|
||||
boolean applyDyedColor) {
|
||||
this.billboard = billboard;
|
||||
this.transform = transform;
|
||||
this.scale = scale;
|
||||
this.translation = translation;
|
||||
this.item = item;
|
||||
this.rotation = rotation;
|
||||
this.position = position;
|
||||
this.applyDyedColor = applyDyedColor;
|
||||
this.shadowRadius = shadowRadius;
|
||||
this.shadowStrength = shadowStrength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float shadowRadius() {
|
||||
return shadowRadius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float shadowStrength() {
|
||||
return shadowStrength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applyDyedColor() {
|
||||
return applyDyedColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Quaternionf rotation() {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key item() {
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Billboard billboard() {
|
||||
return billboard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemDisplayContext transform() {
|
||||
return transform;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector3f scale() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector3f translation() {
|
||||
return translation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector3f position() {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,18 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.Billboard;
|
||||
import net.momirealms.craftengine.core.entity.ItemDisplayContext;
|
||||
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElementConfig;
|
||||
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElementConfigs;
|
||||
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfig;
|
||||
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxTypes;
|
||||
import net.momirealms.craftengine.core.loot.LootTable;
|
||||
import net.momirealms.craftengine.core.pack.LoadingSequence;
|
||||
import net.momirealms.craftengine.core.pack.Pack;
|
||||
import net.momirealms.craftengine.core.pack.PendingConfigSection;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.config.Config;
|
||||
import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser;
|
||||
import net.momirealms.craftengine.core.plugin.context.event.EventFunctions;
|
||||
import net.momirealms.craftengine.core.plugin.entityculling.CullingData;
|
||||
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
|
||||
import net.momirealms.craftengine.core.util.GsonHelper;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
@@ -21,7 +25,7 @@ import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public abstract class AbstractFurnitureManager implements FurnitureManager {
|
||||
protected final Map<Key, CustomFurniture> byId = new HashMap<>();
|
||||
protected final Map<Key, FurnitureConfig> byId = new HashMap<>();
|
||||
private final CraftEngine plugin;
|
||||
private final FurnitureParser furnitureParser;
|
||||
// Cached command suggestions
|
||||
@@ -56,12 +60,12 @@ public abstract class AbstractFurnitureManager implements FurnitureManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<CustomFurniture> furnitureById(Key id) {
|
||||
public Optional<FurnitureConfig> furnitureById(Key id) {
|
||||
return Optional.ofNullable(this.byId.get(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Key, CustomFurniture> loadedFurniture() {
|
||||
public Map<Key, FurnitureConfig> loadedFurniture() {
|
||||
return Collections.unmodifiableMap(this.byId);
|
||||
}
|
||||
|
||||
@@ -70,11 +74,7 @@ public abstract class AbstractFurnitureManager implements FurnitureManager {
|
||||
this.byId.clear();
|
||||
}
|
||||
|
||||
protected abstract HitBoxConfig defaultHitBox();
|
||||
|
||||
protected abstract FurnitureElement.Builder furnitureElementBuilder();
|
||||
|
||||
protected abstract CustomFurniture.Builder furnitureBuilder();
|
||||
protected abstract FurnitureHitBoxConfig<?> defaultHitBox();
|
||||
|
||||
public class FurnitureParser extends IdSectionConfigParser {
|
||||
public static final String[] CONFIG_SECTION_NAME = new String[] { "furniture" };
|
||||
@@ -107,91 +107,76 @@ public abstract class AbstractFurnitureManager implements FurnitureManager {
|
||||
return LoadingSequence.FURNITURE;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int count() {
|
||||
return AbstractFurnitureManager.this.byId.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
|
||||
if (AbstractFurnitureManager.this.byId.containsKey(id)) {
|
||||
throw new LocalizedResourceConfigException("warning.config.furniture.duplicate");
|
||||
}
|
||||
EnumMap<AnchorType, CustomFurniture.Placement> placements = new EnumMap<>(AnchorType.class);
|
||||
Object placementObj = section.get("placement");
|
||||
Map<String, Object> placementMap = MiscUtils.castToMap(ResourceConfigUtils.requireNonNullOrThrow(placementObj, "warning.config.furniture.missing_placement"), false);
|
||||
if (placementMap.isEmpty()) {
|
||||
throw new LocalizedResourceConfigException("warning.config.furniture.missing_placement");
|
||||
}
|
||||
for (Map.Entry<String, Object> entry : placementMap.entrySet()) {
|
||||
// anchor type
|
||||
AnchorType anchorType = AnchorType.valueOf(entry.getKey().toUpperCase(Locale.ENGLISH));
|
||||
Map<String, Object> placementArguments = MiscUtils.castToMap(entry.getValue(), false);
|
||||
Optional<Vector3f> optionalLootSpawnOffset = Optional.ofNullable(placementArguments.get("loot-spawn-offset")).map(it -> ResourceConfigUtils.getAsVector3f(it, "loot-spawn-offset"));
|
||||
// furniture display elements
|
||||
List<FurnitureElement> elements = new ArrayList<>();
|
||||
List<Map<String, Object>> elementConfigs = (List<Map<String, Object>>) placementArguments.getOrDefault("elements", List.of());
|
||||
for (Map<String, Object> element : elementConfigs) {
|
||||
FurnitureElement furnitureElement = furnitureElementBuilder()
|
||||
.item(Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(element.get("item"), "warning.config.furniture.element.missing_item")))
|
||||
.applyDyedColor(ResourceConfigUtils.getAsBoolean(element.getOrDefault("apply-dyed-color", true), "apply-dyed-color"))
|
||||
.billboard(ResourceConfigUtils.getOrDefault(element.get("billboard"), o -> Billboard.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), Billboard.FIXED))
|
||||
.transform(ResourceConfigUtils.getOrDefault(ResourceConfigUtils.get(element, "transform", "display-transform"), o -> ItemDisplayContext.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), ItemDisplayContext.NONE))
|
||||
.scale(ResourceConfigUtils.getAsVector3f(element.getOrDefault("scale", "1"), "scale"))
|
||||
.position(ResourceConfigUtils.getAsVector3f(element.getOrDefault("position", "0"), "position"))
|
||||
.translation(ResourceConfigUtils.getAsVector3f(element.getOrDefault("translation", "0"), "translation"))
|
||||
.rotation(ResourceConfigUtils.getAsQuaternionf(element.getOrDefault("rotation", "0"), "rotation"))
|
||||
.shadowRadius(ResourceConfigUtils.getAsFloat(element.getOrDefault("shadow-radius", 0f), "shadow-radius"))
|
||||
.shadowStrength(ResourceConfigUtils.getAsFloat(element.getOrDefault("shadow-strength", 1f), "shadow-strength"))
|
||||
.build();
|
||||
elements.add(furnitureElement);
|
||||
}
|
||||
|
||||
// external model providers
|
||||
Map<String, Object> variantsMap = ResourceConfigUtils.getAsMap(ResourceConfigUtils.requireNonNullOrThrow(ResourceConfigUtils.get(section, "variants", "placement", "variant"), "warning.config.furniture.missing_variants"), "variants");
|
||||
if (variantsMap.isEmpty()) {
|
||||
throw new LocalizedResourceConfigException("warning.config.furniture.missing_variants");
|
||||
}
|
||||
|
||||
Map<String, FurnitureVariant> variants = new HashMap<>();
|
||||
for (Map.Entry<String, Object> e0 : variantsMap.entrySet()) {
|
||||
String variantName = e0.getKey();
|
||||
Map<String, Object> variantArguments = ResourceConfigUtils.getAsMap(e0.getValue(), variantName);
|
||||
Optional<Vector3f> optionalLootSpawnOffset = Optional.ofNullable(variantArguments.get("loot-spawn-offset")).map(it -> ResourceConfigUtils.getAsVector3f(it, "loot-spawn-offset"));
|
||||
List<FurnitureElementConfig<?>> elements = ResourceConfigUtils.parseConfigAsList(variantArguments.get("elements"), FurnitureElementConfigs::fromMap);
|
||||
|
||||
// fixme 外部模型不应该在这
|
||||
Optional<ExternalModel> externalModel;
|
||||
if (placementArguments.containsKey("model-engine")) {
|
||||
externalModel = Optional.of(plugin.compatibilityManager().createModel("ModelEngine", placementArguments.get("model-engine").toString()));
|
||||
} else if (placementArguments.containsKey("better-model")) {
|
||||
externalModel = Optional.of(plugin.compatibilityManager().createModel("BetterModel", placementArguments.get("better-model").toString()));
|
||||
if (variantArguments.containsKey("model-engine")) {
|
||||
externalModel = Optional.of(plugin.compatibilityManager().createModel("ModelEngine", variantArguments.get("model-engine").toString()));
|
||||
} else if (variantArguments.containsKey("better-model")) {
|
||||
externalModel = Optional.of(plugin.compatibilityManager().createModel("BetterModel", variantArguments.get("better-model").toString()));
|
||||
} else {
|
||||
externalModel = Optional.empty();
|
||||
}
|
||||
|
||||
// add hitboxes
|
||||
List<HitBoxConfig> hitboxes = ResourceConfigUtils.parseConfigAsList(placementArguments.get("hitboxes"), HitBoxTypes::fromMap);
|
||||
List<FurnitureHitBoxConfig<?>> hitboxes = ResourceConfigUtils.parseConfigAsList(variantArguments.get("hitboxes"), FurnitureHitBoxTypes::fromMap);
|
||||
if (hitboxes.isEmpty() && externalModel.isEmpty()) {
|
||||
hitboxes = List.of(defaultHitBox());
|
||||
}
|
||||
|
||||
// rules
|
||||
Map<String, Object> ruleSection = MiscUtils.castToMap(placementArguments.get("rules"), true);
|
||||
if (ruleSection != null) {
|
||||
placements.put(anchorType, new CustomFurniture.Placement(
|
||||
anchorType,
|
||||
elements.toArray(new FurnitureElement[0]),
|
||||
hitboxes.toArray(new HitBoxConfig[0]),
|
||||
ResourceConfigUtils.getOrDefault(ruleSection.get("rotation"), o -> RotationRule.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), RotationRule.ANY),
|
||||
ResourceConfigUtils.getOrDefault(ruleSection.get("alignment"), o -> AlignmentRule.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), AlignmentRule.CENTER),
|
||||
externalModel,
|
||||
optionalLootSpawnOffset
|
||||
));
|
||||
} else {
|
||||
placements.put(anchorType, new CustomFurniture.Placement(
|
||||
anchorType,
|
||||
elements.toArray(new FurnitureElement[0]),
|
||||
hitboxes.toArray(new HitBoxConfig[0]),
|
||||
RotationRule.ANY,
|
||||
AlignmentRule.CENTER,
|
||||
externalModel,
|
||||
optionalLootSpawnOffset
|
||||
));
|
||||
}
|
||||
variants.put(variantName, new FurnitureVariant(
|
||||
variantName,
|
||||
parseCullingData(section.get("entity-culling")),
|
||||
elements.toArray(new FurnitureElementConfig[0]),
|
||||
hitboxes.toArray(new FurnitureHitBoxConfig[0]),
|
||||
externalModel,
|
||||
optionalLootSpawnOffset
|
||||
));
|
||||
}
|
||||
|
||||
CustomFurniture furniture = furnitureBuilder()
|
||||
FurnitureConfig furniture = FurnitureConfig.builder()
|
||||
.id(id)
|
||||
.settings(FurnitureSettings.fromMap(MiscUtils.castToMap(section.get("settings"), true)))
|
||||
.placement(placements)
|
||||
.variants(variants)
|
||||
.events(EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event")))
|
||||
.lootTable(LootTable.fromMap(MiscUtils.castToMap(section.get("loot"), true)))
|
||||
.build();
|
||||
AbstractFurnitureManager.this.byId.put(id, furniture);
|
||||
}
|
||||
|
||||
private CullingData parseCullingData(Object arguments) {
|
||||
if (arguments instanceof Boolean b && !b)
|
||||
return null;
|
||||
if (!(arguments instanceof Map))
|
||||
return new CullingData(null, Config.entityCullingViewDistance(), 0.25, true);
|
||||
Map<String, Object> argumentsMap = ResourceConfigUtils.getAsMap(arguments, "entity-culling");
|
||||
return new CullingData(
|
||||
ResourceConfigUtils.getOrDefault(argumentsMap.get("aabb"), it -> ResourceConfigUtils.getAsAABB(it, "aabb"), null),
|
||||
ResourceConfigUtils.getAsInt(argumentsMap.getOrDefault("view-distance", Config.entityCullingViewDistance()), "view-distance"),
|
||||
ResourceConfigUtils.getAsDouble(argumentsMap.getOrDefault("aabb-expansion", 0.25), "aabb-expansion"),
|
||||
ResourceConfigUtils.getAsBoolean(argumentsMap.getOrDefault("ray-tracing", true), "ray-tracing")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture;
|
||||
|
||||
public enum AnchorType {
|
||||
GROUND(0),
|
||||
WALL(1),
|
||||
CEILING(2);
|
||||
GROUND(0, "ground"),
|
||||
WALL(1, "wall"),
|
||||
CEILING(2, "ceiling");
|
||||
|
||||
private final int id;
|
||||
private final String variantName;
|
||||
|
||||
AnchorType(int id) {
|
||||
AnchorType(int id, String variantName) {
|
||||
this.id = id;
|
||||
this.variantName = variantName;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String variantName() {
|
||||
return variantName;
|
||||
}
|
||||
|
||||
public static AnchorType byId(int id) {
|
||||
return values()[id];
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture;
|
||||
|
||||
import net.momirealms.craftengine.core.loot.LootTable;
|
||||
import net.momirealms.craftengine.core.plugin.context.Context;
|
||||
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
|
||||
import net.momirealms.craftengine.core.plugin.context.function.Function;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
// TODO 家具的设计存在问题。家具也应该存在不同的状态,而不是根据放置规则直接决定状态类型
|
||||
public interface CustomFurniture {
|
||||
|
||||
void execute(Context context, EventTrigger trigger);
|
||||
|
||||
Key id();
|
||||
|
||||
Map<AnchorType, Placement> placements();
|
||||
|
||||
FurnitureSettings settings();
|
||||
|
||||
@Nullable
|
||||
LootTable<?> lootTable();
|
||||
|
||||
AnchorType getAnyAnchorType();
|
||||
|
||||
boolean isAllowedPlacement(AnchorType anchorType);
|
||||
|
||||
Placement getPlacement(AnchorType anchorType);
|
||||
|
||||
Placement getValidPlacement(AnchorType anchorType);
|
||||
|
||||
interface Builder {
|
||||
|
||||
Builder id(Key id);
|
||||
|
||||
Builder placement(Map<AnchorType, Placement> placements);
|
||||
|
||||
Builder settings(FurnitureSettings settings);
|
||||
|
||||
Builder lootTable(LootTable<?> lootTable);
|
||||
|
||||
Builder events(Map<EventTrigger, List<Function<Context>>> events);
|
||||
|
||||
CustomFurniture build();
|
||||
}
|
||||
|
||||
record Placement(AnchorType anchorType,
|
||||
FurnitureElement[] elements,
|
||||
HitBoxConfig[] hitBoxConfigs,
|
||||
RotationRule rotationRule,
|
||||
AlignmentRule alignmentRule,
|
||||
Optional<ExternalModel> externalModel,
|
||||
Optional<Vector3f> dropOffset) {
|
||||
}
|
||||
}
|
||||
@@ -1,48 +1,274 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import net.momirealms.craftengine.core.entity.AbstractEntity;
|
||||
import net.momirealms.craftengine.core.entity.Entity;
|
||||
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElement;
|
||||
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElementConfig;
|
||||
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBox;
|
||||
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfig;
|
||||
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitboxPart;
|
||||
import net.momirealms.craftengine.core.entity.player.Player;
|
||||
import net.momirealms.craftengine.core.entity.seat.Seat;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.entityculling.CullingData;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.QuaternionUtils;
|
||||
import net.momirealms.craftengine.core.world.Cullable;
|
||||
import net.momirealms.craftengine.core.world.Vec3d;
|
||||
import net.momirealms.craftengine.core.world.World;
|
||||
import net.momirealms.craftengine.core.world.WorldPosition;
|
||||
import net.momirealms.craftengine.core.world.collision.AABB;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface Furniture {
|
||||
void initializeColliders();
|
||||
public abstract class Furniture implements Cullable {
|
||||
public final FurnitureConfig config;
|
||||
public final FurnitureDataAccessor dataAccessor;
|
||||
public final Entity metaDataEntity;
|
||||
|
||||
WorldPosition position();
|
||||
protected CullingData cullingData;
|
||||
protected FurnitureVariant currentVariant;
|
||||
protected FurnitureElement[] elements;
|
||||
protected Collider[] colliders;
|
||||
protected FurnitureHitBox[] hitboxes;
|
||||
protected Int2ObjectMap<FurnitureHitBox> hitboxMap;
|
||||
protected int[] virtualEntityIds;
|
||||
protected int[] colliderEntityIds;
|
||||
|
||||
boolean isValid();
|
||||
private boolean hasExternalModel;
|
||||
|
||||
void destroy();
|
||||
protected Furniture(Entity metaDataEntity, FurnitureDataAccessor data, FurnitureConfig config) {
|
||||
this.config = config;
|
||||
this.dataAccessor = data;
|
||||
this.metaDataEntity = metaDataEntity;
|
||||
this.setVariant(config.getVariant(data));
|
||||
}
|
||||
|
||||
void destroyColliders();
|
||||
public Entity metaDataEntity() {
|
||||
return this.metaDataEntity;
|
||||
}
|
||||
|
||||
void destroySeats();
|
||||
public FurnitureVariant getCurrentVariant() {
|
||||
return this.currentVariant;
|
||||
}
|
||||
|
||||
UUID uuid();
|
||||
public void setVariant(FurnitureVariant variant) {
|
||||
this.currentVariant = variant;
|
||||
this.hitboxMap = new Int2ObjectOpenHashMap<>();
|
||||
// 初始化家具元素
|
||||
IntList virtualEntityIds = new IntArrayList();
|
||||
FurnitureElementConfig<?>[] elementConfigs = variant.elementConfigs();
|
||||
this.elements = new FurnitureElement[elementConfigs.length];
|
||||
for (int i = 0; i < elementConfigs.length; i++) {
|
||||
FurnitureElement element = elementConfigs[i].create(this);
|
||||
this.elements[i] = element;
|
||||
element.collectVirtualEntityId(virtualEntityIds::addLast);
|
||||
}
|
||||
// 初始化碰撞箱
|
||||
FurnitureHitBoxConfig<?>[] furnitureHitBoxConfigs = variant.hitBoxConfigs();
|
||||
ObjectArrayList<Collider> colliders = new ObjectArrayList<>(furnitureHitBoxConfigs.length);
|
||||
this.hitboxes = new FurnitureHitBox[furnitureHitBoxConfigs.length];
|
||||
for (int i = 0; i < furnitureHitBoxConfigs.length; i++) {
|
||||
FurnitureHitBox hitbox = furnitureHitBoxConfigs[i].create(this);
|
||||
this.hitboxes[i] = hitbox;
|
||||
for (FurnitureHitboxPart part : hitbox.parts()) {
|
||||
this.hitboxMap.put(part.entityId(), hitbox);
|
||||
}
|
||||
hitbox.collectVirtualEntityId(virtualEntityIds::addLast);
|
||||
colliders.addAll(hitbox.colliders());
|
||||
}
|
||||
// 虚拟碰撞箱的实体id
|
||||
this.virtualEntityIds = virtualEntityIds.toIntArray();
|
||||
this.colliders = colliders.toArray(new Collider[0]);
|
||||
this.colliderEntityIds = colliders.stream().mapToInt(Collider::entityId).toArray();
|
||||
this.cullingData = createCullingData(variant.cullingData());
|
||||
// 外部模型
|
||||
Optional<ExternalModel> externalModel = variant.externalModel();
|
||||
if (externalModel.isPresent()) {
|
||||
this.hasExternalModel = true;
|
||||
try {
|
||||
externalModel.get().bindModel((AbstractEntity) this.metaDataEntity);
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("Failed to load external model for furniture " + id(), e);
|
||||
}
|
||||
} else {
|
||||
this.hasExternalModel = false;
|
||||
}
|
||||
}
|
||||
|
||||
int baseEntityId();
|
||||
private CullingData createCullingData(CullingData parent) {
|
||||
if (parent == null) return null;
|
||||
AABB aabb = parent.aabb;
|
||||
WorldPosition position = position();
|
||||
if (aabb == null) {
|
||||
List<AABB> aabbs = new ArrayList<>();
|
||||
for (FurnitureHitBoxConfig<?> hitBoxConfig : this.currentVariant.hitBoxConfigs()) {
|
||||
hitBoxConfig.prepareForPlacement(position, aabbs::add);
|
||||
}
|
||||
return new CullingData(getMaxAABB(aabbs), parent.maxDistance, parent.aabbExpansion, parent.rayTracing);
|
||||
} else {
|
||||
Vector3f[] vertices = new Vector3f[] {
|
||||
// 底面两个对角点
|
||||
new Vector3f((float) aabb.minX, (float) aabb.minY, (float) aabb.minZ),
|
||||
new Vector3f((float) aabb.maxX, (float) aabb.minY, (float) aabb.maxZ),
|
||||
// 顶面两个对角点
|
||||
new Vector3f((float) aabb.minX, (float) aabb.maxY, (float) aabb.minZ),
|
||||
new Vector3f((float) aabb.maxX, (float) aabb.maxY, (float) aabb.maxZ)
|
||||
};
|
||||
double minX = Double.MAX_VALUE, minY = aabb.minY; // Y方向不变
|
||||
double maxX = -Double.MAX_VALUE, maxY = aabb.maxY; // Y方向不变
|
||||
double minZ = Double.MAX_VALUE, maxZ = -Double.MAX_VALUE;
|
||||
for (Vector3f vertex : vertices) {
|
||||
Vec3d rotatedPos = getRelativePosition(position, vertex);
|
||||
minX = Math.min(minX, rotatedPos.x);
|
||||
minZ = Math.min(minZ, rotatedPos.z);
|
||||
maxX = Math.max(maxX, rotatedPos.x);
|
||||
maxZ = Math.max(maxZ, rotatedPos.z);
|
||||
}
|
||||
return new CullingData(new AABB(minX, minY, minZ, maxX, maxY, maxZ),
|
||||
parent.maxDistance, parent.aabbExpansion, parent.rayTracing);
|
||||
}
|
||||
}
|
||||
|
||||
private static @NotNull AABB getMaxAABB(List<AABB> aabbs) {
|
||||
double minX = 0;
|
||||
double minY = 0;
|
||||
double minZ = 0;
|
||||
double maxX = 0;
|
||||
double maxY = 0;
|
||||
double maxZ = 0;
|
||||
for (int i = 0; i < aabbs.size(); i++) {
|
||||
AABB aabb = aabbs.get(i);
|
||||
if (i == 0) {
|
||||
minX = aabb.minX;
|
||||
minY = aabb.minY;
|
||||
minZ = aabb.minZ;
|
||||
maxX = aabb.maxX;
|
||||
maxY = aabb.maxY;
|
||||
maxZ = aabb.maxZ;
|
||||
} else {
|
||||
minX = Math.min(minX, aabb.minX);
|
||||
minY = Math.min(minY, aabb.minY);
|
||||
minZ = Math.min(minZ, aabb.minZ);
|
||||
maxX = Math.max(maxX, aabb.maxX);
|
||||
maxY = Math.max(maxY, aabb.maxY);
|
||||
maxZ = Math.max(maxZ, aabb.maxZ);
|
||||
}
|
||||
}
|
||||
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
HitBox hitBoxByEntityId(int id);
|
||||
public FurnitureHitBox hitboxByEntityId(int entityId) {
|
||||
return this.hitboxMap.get(entityId);
|
||||
}
|
||||
|
||||
@Nullable HitBoxPart hitBoxPartByEntityId(int id);
|
||||
@Nullable
|
||||
@Override
|
||||
public CullingData cullingData() {
|
||||
return this.cullingData;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
AnchorType anchorType();
|
||||
public Key id() {
|
||||
return this.config.id();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
Key id();
|
||||
// 会发给玩家的包
|
||||
public int[] virtualEntityIds() {
|
||||
return this.virtualEntityIds;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
CustomFurniture config();
|
||||
public int[] colliderEntityIds() {
|
||||
return colliderEntityIds;
|
||||
}
|
||||
|
||||
boolean hasExternalModel();
|
||||
public UUID uuid() {
|
||||
return this.metaDataEntity.uuid();
|
||||
}
|
||||
|
||||
FurnitureExtraData extraData();
|
||||
@Override
|
||||
public void show(Player player) {
|
||||
for (FurnitureElement element : this.elements) {
|
||||
element.show(player);
|
||||
}
|
||||
for (FurnitureHitBox hitbox : this.hitboxes) {
|
||||
hitbox.show(player);
|
||||
}
|
||||
}
|
||||
|
||||
void setExtraData(FurnitureExtraData extraData);
|
||||
@Override
|
||||
public void hide(Player player) {
|
||||
for (FurnitureElement element : this.elements) {
|
||||
element.hide(player);
|
||||
}
|
||||
for (FurnitureHitBox hitbox : this.hitboxes) {
|
||||
hitbox.hide(player);
|
||||
}
|
||||
}
|
||||
|
||||
void save();
|
||||
public abstract void addCollidersToWorld();
|
||||
|
||||
public void destroySeats() {
|
||||
for (FurnitureHitBox hitbox : this.hitboxes) {
|
||||
for (Seat<FurnitureHitBox> seat : hitbox.seats()) {
|
||||
seat.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return this.metaDataEntity.isValid();
|
||||
}
|
||||
|
||||
public abstract void destroy();
|
||||
|
||||
public FurnitureConfig config() {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
public FurnitureDataAccessor dataAccessor() {
|
||||
return this.dataAccessor;
|
||||
}
|
||||
|
||||
public Collider[] colliders() {
|
||||
return this.colliders;
|
||||
}
|
||||
|
||||
public WorldPosition position() {
|
||||
return this.metaDataEntity.position();
|
||||
}
|
||||
|
||||
public int entityId() {
|
||||
return this.metaDataEntity.entityID();
|
||||
}
|
||||
|
||||
public boolean hasExternalModel() {
|
||||
return hasExternalModel;
|
||||
}
|
||||
|
||||
public Vec3d getRelativePosition(Vector3f position) {
|
||||
return getRelativePosition(this.position(), position);
|
||||
}
|
||||
|
||||
public static Vec3d getRelativePosition(WorldPosition location, Vector3f position) {
|
||||
Quaternionf conjugated = QuaternionUtils.toQuaternionf(0f, (float) Math.toRadians(180 - location.yRot()), 0f).conjugate();
|
||||
Vector3f offset = conjugated.transform(new Vector3f(position));
|
||||
return new Vec3d(location.x + offset.x, location.y + offset.y, location.z - offset.z);
|
||||
}
|
||||
|
||||
public World world() {
|
||||
return this.metaDataEntity.world();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture;
|
||||
|
||||
import net.momirealms.craftengine.core.util.Color;
|
||||
|
||||
public record FurnitureColorSource(Color dyedColor, int[] fireworkColors) {
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.furniture.behavior.FurnitureBehavior;
|
||||
import net.momirealms.craftengine.core.loot.LootTable;
|
||||
import net.momirealms.craftengine.core.plugin.context.Context;
|
||||
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
|
||||
import net.momirealms.craftengine.core.plugin.context.function.Function;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface FurnitureConfig {
|
||||
|
||||
void execute(Context context, EventTrigger trigger);
|
||||
|
||||
Key id();
|
||||
|
||||
FurnitureSettings settings();
|
||||
|
||||
@Nullable
|
||||
LootTable<?> lootTable();
|
||||
|
||||
Map<String, FurnitureVariant> variants();
|
||||
|
||||
default FurnitureVariant anyVariant() {
|
||||
return variants().values().stream().findFirst().get();
|
||||
}
|
||||
|
||||
default String anyVariantName() {
|
||||
return variants().keySet().stream().findFirst().get();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
FurnitureVariant getVariant(String variantName);
|
||||
|
||||
@NotNull
|
||||
FurnitureBehavior behavior();
|
||||
|
||||
@NotNull
|
||||
default FurnitureVariant getVariant(FurnitureDataAccessor accessor) {
|
||||
Optional<String> optionalVariant = accessor.variant();
|
||||
String variantName = null;
|
||||
if (optionalVariant.isPresent()) {
|
||||
variantName = optionalVariant.get();
|
||||
} else {
|
||||
@SuppressWarnings("deprecation")
|
||||
Optional<AnchorType> optionalAnchorType = accessor.anchorType();
|
||||
if (optionalAnchorType.isPresent()) {
|
||||
variantName = optionalAnchorType.get().name().toLowerCase(Locale.ROOT);
|
||||
accessor.setVariant(variantName);
|
||||
accessor.removeCustomData(FurnitureDataAccessor.ANCHOR_TYPE);
|
||||
}
|
||||
}
|
||||
if (variantName == null) {
|
||||
return anyVariant();
|
||||
}
|
||||
FurnitureVariant variant = getVariant(variantName);
|
||||
if (variant == null) {
|
||||
return anyVariant();
|
||||
}
|
||||
return variant;
|
||||
|
||||
}
|
||||
|
||||
static Builder builder() {
|
||||
return new FurnitureConfigImpl.BuilderImpl();
|
||||
}
|
||||
|
||||
interface Builder {
|
||||
|
||||
Builder id(Key id);
|
||||
|
||||
Builder variants(Map<String, FurnitureVariant> variants);
|
||||
|
||||
Builder settings(FurnitureSettings settings);
|
||||
|
||||
Builder lootTable(LootTable<?> lootTable);
|
||||
|
||||
Builder events(Map<EventTrigger, List<Function<Context>>> events);
|
||||
|
||||
Builder behavior(FurnitureBehavior behavior);
|
||||
|
||||
FurnitureConfig build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import net.momirealms.craftengine.core.entity.furniture.behavior.EmptyFurnitureBehavior;
|
||||
import net.momirealms.craftengine.core.entity.furniture.behavior.FurnitureBehavior;
|
||||
import net.momirealms.craftengine.core.loot.LootTable;
|
||||
import net.momirealms.craftengine.core.plugin.context.Context;
|
||||
import net.momirealms.craftengine.core.plugin.context.event.EventTrigger;
|
||||
import net.momirealms.craftengine.core.plugin.context.function.Function;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
class FurnitureConfigImpl implements FurnitureConfig {
|
||||
private final Key id;
|
||||
private final FurnitureSettings settings;
|
||||
private final Map<String, FurnitureVariant> variants;
|
||||
private final Map<EventTrigger, List<Function<Context>>> events;
|
||||
private final FurnitureBehavior behavior;
|
||||
@Nullable
|
||||
private final LootTable<?> lootTable;
|
||||
|
||||
private FurnitureConfigImpl(@NotNull Key id,
|
||||
@NotNull FurnitureSettings settings,
|
||||
@NotNull Map<String, FurnitureVariant> variants,
|
||||
@NotNull Map<EventTrigger, List<Function<Context>>> events,
|
||||
@NotNull FurnitureBehavior behavior,
|
||||
@Nullable LootTable<?> lootTable) {
|
||||
this.id = id;
|
||||
this.settings = settings;
|
||||
this.variants = ImmutableMap.copyOf(variants);
|
||||
this.lootTable = lootTable;
|
||||
this.behavior = behavior;
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Context context, EventTrigger trigger) {
|
||||
for (Function<Context> function : Optional.ofNullable(this.events.get(trigger)).orElse(Collections.emptyList())) {
|
||||
function.run(context);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key id() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FurnitureSettings settings() {
|
||||
return this.settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable LootTable<?> lootTable() {
|
||||
return this.lootTable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, FurnitureVariant> variants() {
|
||||
return this.variants;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FurnitureBehavior behavior() {
|
||||
return this.behavior;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public FurnitureVariant getVariant(String variantName) {
|
||||
return this.variants.get(variantName);
|
||||
}
|
||||
|
||||
public static class BuilderImpl implements Builder {
|
||||
private Key id;
|
||||
private Map<String, FurnitureVariant> variants;
|
||||
private FurnitureSettings settings;
|
||||
private Map<EventTrigger, List<Function<Context>>> events;
|
||||
private LootTable<?> lootTable;
|
||||
private FurnitureBehavior behavior = EmptyFurnitureBehavior.INSTANCE;
|
||||
|
||||
@Override
|
||||
public FurnitureConfig build() {
|
||||
return new FurnitureConfigImpl(this.id, this.settings, this.variants, this.events, this.behavior, this.lootTable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder id(Key id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder variants(Map<String, FurnitureVariant> variants) {
|
||||
this.variants = variants;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder settings(FurnitureSettings settings) {
|
||||
this.settings = settings;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder lootTable(LootTable<?> lootTable) {
|
||||
this.lootTable = lootTable;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder events(Map<EventTrigger, List<Function<Context>>> events) {
|
||||
this.events = events;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder behavior(FurnitureBehavior behavior) {
|
||||
this.behavior = behavior;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture;
|
||||
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.logger.Debugger;
|
||||
import net.momirealms.craftengine.core.util.Color;
|
||||
import net.momirealms.sparrow.nbt.CompoundTag;
|
||||
import net.momirealms.sparrow.nbt.NBT;
|
||||
import net.momirealms.sparrow.nbt.Tag;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
public class FurnitureDataAccessor {
|
||||
public static final String ITEM = "item";
|
||||
public static final String DYED_COLOR = "dyed_color";
|
||||
public static final String FIREWORK_EXPLOSION_COLORS = "firework_explosion_colors";
|
||||
public static final String VARIANT = "variant";
|
||||
@ApiStatus.Obsolete
|
||||
public static final String ANCHOR_TYPE = "anchor_type";
|
||||
|
||||
private final CompoundTag data;
|
||||
|
||||
public FurnitureDataAccessor(CompoundTag data) {
|
||||
this.data = data == null ? new CompoundTag() : data;
|
||||
}
|
||||
|
||||
public static FurnitureDataAccessor of(CompoundTag data) {
|
||||
return new FurnitureDataAccessor(data);
|
||||
}
|
||||
|
||||
public static FurnitureDataAccessor ofVariant(String variant) {
|
||||
FurnitureDataAccessor accessor = new FurnitureDataAccessor(new CompoundTag());
|
||||
accessor.setVariant(variant);
|
||||
return accessor;
|
||||
}
|
||||
|
||||
public CompoundTag copyTag() {
|
||||
return this.data.copy();
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public CompoundTag unsafeTag() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public void addCustomData(String key, Tag value) {
|
||||
this.data.put(key, value);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Tag getCustomData(String key) {
|
||||
return this.data.get(key);
|
||||
}
|
||||
|
||||
public void removeCustomData(String key) {
|
||||
this.data.remove(key);
|
||||
}
|
||||
|
||||
public Optional<Item<?>> item() {
|
||||
byte[] data = this.data.getByteArray(ITEM);
|
||||
if (data == null) return Optional.empty();
|
||||
try {
|
||||
return Optional.of(CraftEngine.instance().itemManager().fromByteArray(data));
|
||||
} catch (Exception e) {
|
||||
Debugger.FURNITURE.warn(() -> "Failed to read furniture item data", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public void setItem(Item<?> item) {
|
||||
this.data.putByteArray(ITEM, item.toByteArray());
|
||||
}
|
||||
|
||||
public FurnitureColorSource getColorSource() {
|
||||
return new FurnitureColorSource(dyedColor().orElse(null), fireworkExplosionColors().orElse(null));
|
||||
}
|
||||
|
||||
public Optional<int[]> fireworkExplosionColors() {
|
||||
if (this.data.containsKey(FIREWORK_EXPLOSION_COLORS)) return Optional.of(this.data.getIntArray(FIREWORK_EXPLOSION_COLORS));
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public void setFireworkExplosionColors(int[] colors) {
|
||||
if (colors == null) {
|
||||
this.data.remove(FIREWORK_EXPLOSION_COLORS);
|
||||
return;
|
||||
}
|
||||
this.data.putIntArray(FIREWORK_EXPLOSION_COLORS, colors);
|
||||
}
|
||||
|
||||
public Optional<Color> dyedColor() {
|
||||
if (this.data.containsKey(DYED_COLOR)) return Optional.of(Color.fromDecimal(this.data.getInt(DYED_COLOR)));
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public void setDyedColor(@Nullable Color color) {
|
||||
if (color == null) {
|
||||
this.data.remove(DYED_COLOR);
|
||||
return;
|
||||
}
|
||||
this.data.putInt(DYED_COLOR, color.color());
|
||||
}
|
||||
|
||||
public Optional<String> variant() {
|
||||
return Optional.ofNullable(this.data.getString(VARIANT));
|
||||
}
|
||||
|
||||
public void setVariant(String variant) {
|
||||
this.data.putString(VARIANT, variant);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@ApiStatus.Obsolete
|
||||
public Optional<AnchorType> anchorType() {
|
||||
if (this.data.containsKey(ANCHOR_TYPE)) return Optional.of(AnchorType.byId(this.data.getInt(ANCHOR_TYPE)));
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@ApiStatus.Obsolete
|
||||
public FurnitureDataAccessor anchorType(@SuppressWarnings("deprecation") AnchorType type) {
|
||||
this.data.putInt(ANCHOR_TYPE, type.getId());
|
||||
return this;
|
||||
}
|
||||
|
||||
public static FurnitureDataAccessor fromBytes(final byte[] data) throws IOException {
|
||||
return new FurnitureDataAccessor(NBT.fromBytes(data));
|
||||
}
|
||||
|
||||
public static byte[] toBytes(final FurnitureDataAccessor data) throws IOException {
|
||||
return NBT.toBytes(data.data);
|
||||
}
|
||||
|
||||
public byte[] toBytes() throws IOException {
|
||||
return toBytes(this);
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.Billboard;
|
||||
import net.momirealms.craftengine.core.entity.ItemDisplayContext;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface FurnitureElement {
|
||||
Quaternionf rotation();
|
||||
|
||||
Key item();
|
||||
|
||||
Billboard billboard();
|
||||
|
||||
ItemDisplayContext transform();
|
||||
|
||||
float shadowRadius();
|
||||
|
||||
float shadowStrength();
|
||||
|
||||
boolean applyDyedColor();
|
||||
|
||||
Vector3f scale();
|
||||
|
||||
Vector3f translation();
|
||||
|
||||
Vector3f position();
|
||||
|
||||
void initPackets(Furniture furniture, int entityId, @NotNull Quaternionf conjugated, Consumer<Object> packets);
|
||||
|
||||
interface Builder {
|
||||
|
||||
Builder item(Key item);
|
||||
|
||||
Builder billboard(Billboard billboard);
|
||||
|
||||
Builder transform(ItemDisplayContext transform);
|
||||
|
||||
Builder scale(Vector3f scale);
|
||||
|
||||
Builder translation(Vector3f translation);
|
||||
|
||||
Builder position(Vector3f position);
|
||||
|
||||
Builder rotation(Quaternionf rotation);
|
||||
|
||||
Builder applyDyedColor(boolean applyDyedColor);
|
||||
|
||||
Builder shadowStrength(float shadowStrength);
|
||||
|
||||
Builder shadowRadius(float shadowRadius);
|
||||
|
||||
FurnitureElement build();
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture;
|
||||
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.logger.Debugger;
|
||||
import net.momirealms.craftengine.core.util.Color;
|
||||
import net.momirealms.sparrow.nbt.CompoundTag;
|
||||
import net.momirealms.sparrow.nbt.NBT;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
public class FurnitureExtraData {
|
||||
public static final String ITEM = "item";
|
||||
public static final String DYED_COLOR = "dyed_color";
|
||||
public static final String FIREWORK_EXPLOSION_COLORS = "firework_explosion_colors";
|
||||
public static final String ANCHOR_TYPE = "anchor_type";
|
||||
|
||||
private final CompoundTag data;
|
||||
|
||||
public FurnitureExtraData(CompoundTag data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static FurnitureExtraData of(CompoundTag data) {
|
||||
return new FurnitureExtraData(data);
|
||||
}
|
||||
|
||||
public CompoundTag copyTag() {
|
||||
return this.data.copy();
|
||||
}
|
||||
|
||||
public CompoundTag unsafeTag() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public Optional<Item<?>> item() {
|
||||
byte[] data = this.data.getByteArray(ITEM);
|
||||
if (data == null) return Optional.empty();
|
||||
try {
|
||||
return Optional.of(CraftEngine.instance().itemManager().fromByteArray(data));
|
||||
} catch (Exception e) {
|
||||
Debugger.FURNITURE.warn(() -> "Failed to read furniture item data", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<int[]> fireworkExplosionColors() {
|
||||
if (this.data.containsKey(FIREWORK_EXPLOSION_COLORS)) return Optional.of(this.data.getIntArray(FIREWORK_EXPLOSION_COLORS));
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<Color> dyedColor() {
|
||||
if (this.data.containsKey(DYED_COLOR)) return Optional.of(Color.fromDecimal(this.data.getInt(DYED_COLOR)));
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<AnchorType> anchorType() {
|
||||
if (this.data.containsKey(ANCHOR_TYPE)) return Optional.of(AnchorType.byId(this.data.getInt(ANCHOR_TYPE)));
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public FurnitureExtraData anchorType(AnchorType type) {
|
||||
this.data.putInt(ANCHOR_TYPE, type.getId());
|
||||
return this;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static FurnitureExtraData fromBytes(final byte[] data) throws IOException {
|
||||
return new FurnitureExtraData(NBT.fromBytes(data));
|
||||
}
|
||||
|
||||
public static byte[] toBytes(final FurnitureExtraData data) throws IOException {
|
||||
return NBT.toBytes(data.data);
|
||||
}
|
||||
|
||||
public byte[] toBytes() throws IOException {
|
||||
return toBytes(this);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final CompoundTag data;
|
||||
|
||||
public Builder() {
|
||||
this.data = new CompoundTag();
|
||||
}
|
||||
|
||||
public Builder item(Item<?> item) {
|
||||
this.data.putByteArray(ITEM, item.toByteArray());
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder dyedColor(Color color) {
|
||||
if (color == null) return this;
|
||||
this.data.putInt(DYED_COLOR, color.color());
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder fireworkExplosionColors(int[] colors) {
|
||||
if (colors == null) return this;
|
||||
this.data.putIntArray(FIREWORK_EXPLOSION_COLORS, colors);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder anchorType(AnchorType type) {
|
||||
this.data.putInt(ANCHOR_TYPE, type.getId());
|
||||
return this;
|
||||
}
|
||||
|
||||
public FurnitureExtraData build() {
|
||||
return new FurnitureExtraData(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.AbstractEntity;
|
||||
import net.momirealms.craftengine.core.plugin.Manageable;
|
||||
import net.momirealms.craftengine.core.plugin.config.ConfigParser;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
@@ -25,22 +24,20 @@ public interface FurnitureManager extends Manageable {
|
||||
|
||||
Collection<Suggestion> cachedSuggestions();
|
||||
|
||||
Furniture place(WorldPosition position, CustomFurniture furniture, FurnitureExtraData extraData, boolean playSound);
|
||||
Furniture place(WorldPosition position, FurnitureConfig furniture, FurnitureDataAccessor extraData, boolean playSound);
|
||||
|
||||
Optional<CustomFurniture> furnitureById(Key id);
|
||||
Optional<FurnitureConfig> furnitureById(Key id);
|
||||
|
||||
Map<Key, CustomFurniture> loadedFurniture();
|
||||
Map<Key, FurnitureConfig> loadedFurniture();
|
||||
|
||||
boolean isFurnitureRealEntity(int entityId);
|
||||
boolean isFurnitureMetaEntity(int entityId);
|
||||
|
||||
@Nullable
|
||||
Furniture loadedFurnitureByRealEntityId(int entityId);
|
||||
Furniture loadedFurnitureByMetaEntityId(int entityId);
|
||||
|
||||
@Nullable
|
||||
default Furniture loadedFurnitureByRealEntity(AbstractEntity entity) {
|
||||
return loadedFurnitureByRealEntityId(entity.entityID());
|
||||
}
|
||||
Furniture loadedFurnitureByVirtualEntityId(int entityId);
|
||||
|
||||
@Nullable
|
||||
Furniture loadedFurnitureByEntityId(int entityId);
|
||||
Furniture loadedFurnitureByColliderEntityId(int entityId);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture;
|
||||
|
||||
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
|
||||
import net.momirealms.craftengine.core.util.CustomDataType;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.MiscUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class FurnitureSettings {
|
||||
@@ -13,6 +15,7 @@ public class FurnitureSettings {
|
||||
FurnitureSounds sounds = FurnitureSounds.EMPTY;
|
||||
@Nullable
|
||||
Key itemId;
|
||||
Map<CustomDataType<?>, Object> customData = new IdentityHashMap<>(4);
|
||||
|
||||
private FurnitureSettings() {}
|
||||
|
||||
@@ -29,6 +32,7 @@ public class FurnitureSettings {
|
||||
newSettings.sounds = settings.sounds;
|
||||
newSettings.itemId = settings.itemId;
|
||||
newSettings.minimized = settings.minimized;
|
||||
newSettings.customData = new IdentityHashMap<>(settings.customData);
|
||||
return newSettings;
|
||||
}
|
||||
|
||||
@@ -45,6 +49,25 @@ public class FurnitureSettings {
|
||||
return settings;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getCustomData(CustomDataType<T> type) {
|
||||
return (T) this.customData.get(type);
|
||||
}
|
||||
|
||||
public void clearCustomData() {
|
||||
this.customData.clear();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T removeCustomData(CustomDataType<?> type) {
|
||||
return (T) this.customData.remove(type);
|
||||
}
|
||||
|
||||
public <T> void addCustomData(CustomDataType<T> key, T value) {
|
||||
this.customData.put(key, value);
|
||||
}
|
||||
|
||||
public FurnitureSounds sounds() {
|
||||
return sounds;
|
||||
}
|
||||
@@ -103,7 +126,7 @@ public class FurnitureSettings {
|
||||
}));
|
||||
}
|
||||
|
||||
private static void registerFactory(String id, FurnitureSettings.Modifier.Factory factory) {
|
||||
public static void registerFactory(String id, FurnitureSettings.Modifier.Factory factory) {
|
||||
FACTORIES.put(id, factory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElementConfig;
|
||||
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfig;
|
||||
import net.momirealms.craftengine.core.plugin.entityculling.CullingData;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public record FurnitureVariant(String name,
|
||||
@Nullable CullingData cullingData,
|
||||
FurnitureElementConfig<?>[] elementConfigs,
|
||||
FurnitureHitBoxConfig<?>[] hitBoxConfigs,
|
||||
Optional<ExternalModel> externalModel,
|
||||
Optional<Vector3f> dropOffset) {
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.seat.Seat;
|
||||
import net.momirealms.craftengine.core.entity.seat.SeatOwner;
|
||||
import net.momirealms.craftengine.core.world.EntityHitResult;
|
||||
import net.momirealms.craftengine.core.world.Vec3d;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface HitBox extends SeatOwner {
|
||||
|
||||
Seat<HitBox>[] seats();
|
||||
|
||||
Optional<EntityHitResult> clip(Vec3d min, Vec3d max);
|
||||
|
||||
HitBoxPart[] parts();
|
||||
|
||||
HitBoxConfig config();
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.seat.SeatConfig;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.world.WorldPosition;
|
||||
import net.momirealms.craftengine.core.world.collision.AABB;
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface HitBoxConfig {
|
||||
|
||||
Key type();
|
||||
|
||||
void initPacketsAndColliders(int[] entityId, WorldPosition position, Quaternionf conjugated,
|
||||
BiConsumer<Object, Boolean> packets, Consumer<Collider> collider, Consumer<HitBoxPart> aabb);
|
||||
|
||||
void initShapeForPlacement(double x, double y, double z, float yaw, Quaternionf conjugated, Consumer<AABB> aabbs);
|
||||
|
||||
int[] acquireEntityIds(Supplier<Integer> entityIdSupplier);
|
||||
|
||||
SeatConfig[] seats();
|
||||
|
||||
Vector3f position();
|
||||
|
||||
boolean blocksBuilding();
|
||||
|
||||
boolean canBeHitByProjectile();
|
||||
|
||||
boolean canUseItemOn();
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface HitBoxConfigFactory {
|
||||
|
||||
HitBoxConfig create(Map<String, Object> arguments);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture;
|
||||
|
||||
import net.momirealms.craftengine.core.world.Vec3d;
|
||||
import net.momirealms.craftengine.core.world.collision.AABB;
|
||||
|
||||
public record HitBoxPart(int entityId, AABB aabb, Vec3d pos) {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture.behavior;
|
||||
|
||||
public final class EmptyFurnitureBehavior implements FurnitureBehavior {
|
||||
private EmptyFurnitureBehavior() {}
|
||||
|
||||
public static final EmptyFurnitureBehavior INSTANCE = new EmptyFurnitureBehavior();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture.behavior;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.furniture.Furniture;
|
||||
import net.momirealms.craftengine.core.entity.furniture.tick.FurnitureTicker;
|
||||
|
||||
public interface FurnitureBehavior {
|
||||
|
||||
default <T extends Furniture> FurnitureTicker<T> createSyncFurnitureTicker(T furniture) {
|
||||
return null;
|
||||
}
|
||||
|
||||
default <T extends Furniture> FurnitureTicker<T> createAsyncBlockEntityTicker(T furniture) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture.element;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.player.Player;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface FurnitureElement {
|
||||
|
||||
int[] virtualEntityIds();
|
||||
|
||||
void collectVirtualEntityId(Consumer<Integer> collector);
|
||||
|
||||
void show(Player player);
|
||||
|
||||
void hide(Player player);
|
||||
|
||||
default void deactivate() {}
|
||||
|
||||
default void activate() {}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture.element;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.furniture.Furniture;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface FurnitureElementConfig<E extends FurnitureElement> {
|
||||
|
||||
E create(@NotNull Furniture furniture);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture.element;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface FurnitureElementConfigFactory<E extends FurnitureElement> {
|
||||
|
||||
FurnitureElementConfig<E> create(Map<String, Object> args);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture.element;
|
||||
|
||||
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
|
||||
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
|
||||
import net.momirealms.craftengine.core.registry.Registries;
|
||||
import net.momirealms.craftengine.core.registry.WritableRegistry;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.ResourceKey;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class FurnitureElementConfigs {
|
||||
public static final Key ITEM_DISPLAY = Key.of("craftengine:item_display");
|
||||
public static final Key TEXT_DISPLAY = Key.of("craftengine:text_display");
|
||||
public static final Key ITEM = Key.of("craftengine:item");
|
||||
|
||||
public static void register(Key key, FurnitureElementConfigFactory<?> type) {
|
||||
((WritableRegistry<FurnitureElementConfigFactory<?>>) BuiltInRegistries.FURNITURE_ELEMENT_TYPE)
|
||||
.register(ResourceKey.create(Registries.FURNITURE_ELEMENT_TYPE.location(), key), type);
|
||||
}
|
||||
|
||||
public static <E extends FurnitureElement> FurnitureElementConfig<E> fromMap(Map<String, Object> arguments) {
|
||||
Key type = Optional.ofNullable(arguments.get("type")).map(String::valueOf).map(it -> Key.withDefaultNamespace(it, "craftengine")).orElse(ITEM_DISPLAY);
|
||||
@SuppressWarnings("unchecked")
|
||||
FurnitureElementConfigFactory<E> factory = (FurnitureElementConfigFactory<E>) BuiltInRegistries.FURNITURE_ELEMENT_TYPE.getValue(type);
|
||||
if (factory == null) {
|
||||
throw new LocalizedResourceConfigException("warning.config.furniture.element.invalid_type", type.toString());
|
||||
}
|
||||
return factory.create(arguments);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,20 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture;
|
||||
package net.momirealms.craftengine.core.entity.furniture.hitbox;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.seat.SeatConfig;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
public abstract class AbstractHitBoxConfig implements HitBoxConfig {
|
||||
public abstract class AbstractFurnitureHitBoxConfig<H extends FurnitureHitBox> implements FurnitureHitBoxConfig<H> {
|
||||
protected final SeatConfig[] seats;
|
||||
protected final Vector3f position;
|
||||
protected final boolean canUseItemOn;
|
||||
protected final boolean blocksBuilding;
|
||||
protected final boolean canBeHitByProjectile;
|
||||
|
||||
public AbstractHitBoxConfig(SeatConfig[] seats, Vector3f position, boolean canUseItemOn, boolean blocksBuilding, boolean canBeHitByProjectile) {
|
||||
public AbstractFurnitureHitBoxConfig(SeatConfig[] seats,
|
||||
Vector3f position,
|
||||
boolean canUseItemOn,
|
||||
boolean blocksBuilding,
|
||||
boolean canBeHitByProjectile) {
|
||||
this.seats = seats;
|
||||
this.position = position;
|
||||
this.canUseItemOn = canUseItemOn;
|
||||
@@ -30,16 +34,16 @@ public abstract class AbstractHitBoxConfig implements HitBoxConfig {
|
||||
|
||||
@Override
|
||||
public boolean blocksBuilding() {
|
||||
return blocksBuilding;
|
||||
return this.blocksBuilding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeHitByProjectile() {
|
||||
return canBeHitByProjectile;
|
||||
return this.canBeHitByProjectile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUseItemOn() {
|
||||
return canUseItemOn;
|
||||
return this.canUseItemOn;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture.hitbox;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.furniture.Collider;
|
||||
import net.momirealms.craftengine.core.entity.player.Player;
|
||||
import net.momirealms.craftengine.core.entity.seat.Seat;
|
||||
import net.momirealms.craftengine.core.entity.seat.SeatOwner;
|
||||
import net.momirealms.craftengine.core.world.EntityHitResult;
|
||||
import net.momirealms.craftengine.core.world.Vec3d;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface FurnitureHitBox extends SeatOwner {
|
||||
|
||||
Seat<FurnitureHitBox>[] seats();
|
||||
|
||||
List<Collider> colliders();
|
||||
|
||||
List<FurnitureHitboxPart> parts();
|
||||
|
||||
void show(Player player);
|
||||
|
||||
void hide(Player player);
|
||||
|
||||
FurnitureHitBoxConfig<?> config();
|
||||
|
||||
void collectVirtualEntityId(Consumer<Integer> collector);
|
||||
|
||||
default Optional<EntityHitResult> clip(Vec3d min, Vec3d max) {
|
||||
for (FurnitureHitboxPart value : parts()) {
|
||||
Optional<EntityHitResult> clip = value.aabb().clip(min, max);
|
||||
if (clip.isPresent()) {
|
||||
return clip;
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture.hitbox;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.furniture.Furniture;
|
||||
import net.momirealms.craftengine.core.entity.seat.SeatConfig;
|
||||
import net.momirealms.craftengine.core.world.WorldPosition;
|
||||
import net.momirealms.craftengine.core.world.collision.AABB;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface FurnitureHitBoxConfig<H extends FurnitureHitBox> {
|
||||
|
||||
H create(Furniture furniture);
|
||||
|
||||
SeatConfig[] seats();
|
||||
|
||||
Vector3f position();
|
||||
|
||||
boolean blocksBuilding();
|
||||
|
||||
boolean canBeHitByProjectile();
|
||||
|
||||
boolean canUseItemOn();
|
||||
|
||||
void prepareForPlacement(WorldPosition targetPos, Consumer<AABB> aabbConsumer);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture.hitbox;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface FurnitureHitBoxConfigFactory<H extends FurnitureHitBox> {
|
||||
|
||||
FurnitureHitBoxConfig<H> create(Map<String, Object> arguments);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture;
|
||||
package net.momirealms.craftengine.core.entity.furniture.hitbox;
|
||||
|
||||
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
|
||||
import net.momirealms.craftengine.core.registry.BuiltInRegistries;
|
||||
@@ -10,20 +10,22 @@ import net.momirealms.craftengine.core.util.ResourceKey;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class HitBoxTypes {
|
||||
public class FurnitureHitBoxTypes {
|
||||
public static final Key INTERACTION = Key.of("minecraft:interaction");
|
||||
public static final Key SHULKER = Key.of("minecraft:shulker");
|
||||
public static final Key HAPPY_GHAST = Key.of("minecraft:happy_ghast");
|
||||
public static final Key VIRTUAL = Key.of("minecraft:virtual");
|
||||
public static final Key CUSTOM = Key.of("minecraft:custom");
|
||||
|
||||
public static void register(Key key, HitBoxConfigFactory factory) {
|
||||
((WritableRegistry<HitBoxConfigFactory>) BuiltInRegistries.HITBOX_FACTORY)
|
||||
.register(ResourceKey.create(Registries.HITBOX_FACTORY.location(), key), factory);
|
||||
public static void register(Key key, FurnitureHitBoxConfigFactory<?> factory) {
|
||||
((WritableRegistry<FurnitureHitBoxConfigFactory<?>>) BuiltInRegistries.FURNITURE_HITBOX_TYPE)
|
||||
.register(ResourceKey.create(Registries.FURNITURE_HITBOX_TYPE.location(), key), factory);
|
||||
}
|
||||
|
||||
public static HitBoxConfig fromMap(Map<String, Object> arguments) {
|
||||
Key type = Optional.ofNullable(arguments.get("type")).map(String::valueOf).map(Key::of).orElse(HitBoxTypes.INTERACTION);
|
||||
HitBoxConfigFactory factory = BuiltInRegistries.HITBOX_FACTORY.getValue(type);
|
||||
public static <H extends FurnitureHitBox> FurnitureHitBoxConfig<H> fromMap(Map<String, Object> arguments) {
|
||||
Key type = Optional.ofNullable(arguments.get("type")).map(String::valueOf).map(Key::of).orElse(FurnitureHitBoxTypes.INTERACTION);
|
||||
@SuppressWarnings("unchecked")
|
||||
FurnitureHitBoxConfigFactory<H> factory = (FurnitureHitBoxConfigFactory<H>) BuiltInRegistries.FURNITURE_HITBOX_TYPE.getValue(type);
|
||||
if (factory == null) {
|
||||
throw new LocalizedResourceConfigException("warning.config.furniture.hitbox.invalid_type", type.toString());
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture.hitbox;
|
||||
|
||||
import net.momirealms.craftengine.core.world.Vec3d;
|
||||
import net.momirealms.craftengine.core.world.collision.AABB;
|
||||
|
||||
public record FurnitureHitboxPart(int entityId, AABB aabb, Vec3d pos, boolean interactive) {
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.momirealms.craftengine.core.entity.furniture.tick;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.furniture.Furniture;
|
||||
|
||||
public interface FurnitureTicker<T extends Furniture> {
|
||||
|
||||
void tick(T furniture);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.momirealms.craftengine.core.entity;
|
||||
package net.momirealms.craftengine.core.entity.item;
|
||||
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
|
||||
@@ -4,6 +4,7 @@ import net.kyori.adventure.text.Component;
|
||||
import net.momirealms.craftengine.core.advancement.AdvancementType;
|
||||
import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer;
|
||||
import net.momirealms.craftengine.core.entity.AbstractEntity;
|
||||
import net.momirealms.craftengine.core.entity.furniture.Furniture;
|
||||
import net.momirealms.craftengine.core.item.Item;
|
||||
import net.momirealms.craftengine.core.plugin.context.CooldownData;
|
||||
import net.momirealms.craftengine.core.plugin.network.NetWorkUser;
|
||||
@@ -38,6 +39,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 +192,16 @@ public abstract class Player extends AbstractEntity implements NetWorkUser {
|
||||
|
||||
public abstract void setSelectedLocale(@Nullable Locale locale);
|
||||
|
||||
public abstract void setEntityCullingViewDistanceScale(double value);
|
||||
|
||||
public abstract void setEnableEntityCulling(boolean enable);
|
||||
|
||||
public abstract boolean enableEntityCulling();
|
||||
|
||||
public abstract boolean enableFurnitureDebug();
|
||||
|
||||
public abstract void setEnableFurnitureDebug(boolean enableFurnitureDebug);
|
||||
|
||||
public abstract void giveExperiencePoints(int xpPoints);
|
||||
|
||||
public abstract void giveExperienceLevels(int levels);
|
||||
@@ -209,9 +222,24 @@ public abstract class Player extends AbstractEntity implements NetWorkUser {
|
||||
|
||||
public abstract void removeTrackedBlockEntities(Collection<BlockPos> renders);
|
||||
|
||||
public abstract void addTrackedFurniture(int entityId, Furniture furniture);
|
||||
|
||||
public abstract void clearTrackedBlockEntities();
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
}
|
||||
|
||||
public abstract void playParticle(Key particleId, double x, double y, double z);
|
||||
|
||||
public abstract void removeTrackedFurniture(int entityId);
|
||||
|
||||
public abstract void clearTrackedFurniture();
|
||||
|
||||
public abstract WorldPosition eyePosition();
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return this.isOnline();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package net.momirealms.craftengine.core.entity.projectile;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.Billboard;
|
||||
import net.momirealms.craftengine.core.entity.ItemDisplayContext;
|
||||
import net.momirealms.craftengine.core.entity.display.Billboard;
|
||||
import net.momirealms.craftengine.core.entity.display.ItemDisplayContext;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import org.joml.Quaternionf;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
@@ -442,9 +442,14 @@ public abstract class AbstractFontManager implements FontManager {
|
||||
return LoadingSequence.EMOJI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return AbstractFontManager.this.emojis.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
|
||||
if (emojis.containsKey(id)) {
|
||||
if (AbstractFontManager.this.emojis.containsKey(id)) {
|
||||
throw new LocalizedResourceConfigException("warning.config.emoji.duplicate");
|
||||
}
|
||||
String permission = (String) section.get("permission");
|
||||
@@ -510,6 +515,11 @@ public abstract class AbstractFontManager implements FontManager {
|
||||
return LoadingSequence.IMAGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return AbstractFontManager.this.images.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcess() {
|
||||
for (Map.Entry<Key, IdAllocator> entry : this.idAllocators.entrySet()) {
|
||||
|
||||
@@ -351,6 +351,11 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
|
||||
.toList();
|
||||
registerArmorTrimPattern(trims);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return AbstractItemManager.this.equipments.size();
|
||||
}
|
||||
}
|
||||
|
||||
public void addOrMergeEquipment(ComponentBasedEquipment equipment) {
|
||||
@@ -368,6 +373,11 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
|
||||
public static final String[] CONFIG_SECTION_NAME = new String[] {"items", "item"};
|
||||
private final Map<Key, IdAllocator> idAllocators = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return AbstractItemManager.this.customItemsById.size();
|
||||
}
|
||||
|
||||
private boolean isModernFormatRequired() {
|
||||
return Config.packMaxVersion().isAtOrAbove(MinecraftVersions.V1_21_4);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package net.momirealms.craftengine.core.item;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.Billboard;
|
||||
import net.momirealms.craftengine.core.entity.ItemDisplayContext;
|
||||
import net.momirealms.craftengine.core.entity.display.Billboard;
|
||||
import net.momirealms.craftengine.core.entity.display.ItemDisplayContext;
|
||||
import net.momirealms.craftengine.core.entity.projectile.ProjectileMeta;
|
||||
import net.momirealms.craftengine.core.item.equipment.ComponentBasedEquipment;
|
||||
import net.momirealms.craftengine.core.item.equipment.Equipment;
|
||||
@@ -134,6 +134,12 @@ public class ItemSettings {
|
||||
this.customData.clear();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T removeCustomData(CustomDataType<?> type) {
|
||||
return (T) this.customData.remove(type);
|
||||
}
|
||||
|
||||
public <T> void addCustomData(CustomDataType<T> key, T value) {
|
||||
this.customData.put(key, value);
|
||||
}
|
||||
@@ -440,8 +446,8 @@ public class ItemSettings {
|
||||
Key customTridentItemId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(args.get("item"), "warning.config.item.settings.projectile.missing_item"));
|
||||
ItemDisplayContext displayType = ItemDisplayContext.valueOf(args.getOrDefault("display-transform", "NONE").toString().toUpperCase(Locale.ENGLISH));
|
||||
Billboard billboard = Billboard.valueOf(args.getOrDefault("billboard", "FIXED").toString().toUpperCase(Locale.ENGLISH));
|
||||
Vector3f translation = ResourceConfigUtils.getAsVector3f(args.getOrDefault("translation", "0"), "translation");
|
||||
Vector3f scale = ResourceConfigUtils.getAsVector3f(args.getOrDefault("scale", "1"), "scale");
|
||||
Vector3f translation = ResourceConfigUtils.getAsVector3f(args.getOrDefault("translation", 0), "translation");
|
||||
Vector3f scale = ResourceConfigUtils.getAsVector3f(args.getOrDefault("scale", 1), "scale");
|
||||
Quaternionf rotation = ResourceConfigUtils.getAsQuaternionf(ResourceConfigUtils.get(args, "rotation"), "rotation");
|
||||
double range = ResourceConfigUtils.getAsDouble(args.getOrDefault("range", 1), "range");
|
||||
return settings -> settings.projectileMeta(new ProjectileMeta(customTridentItemId, displayType, billboard, scale, translation, rotation, range));
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package net.momirealms.craftengine.core.item.modifier;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import net.momirealms.craftengine.core.item.*;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
|
||||
import net.momirealms.craftengine.core.util.GsonHelper;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.Pair;
|
||||
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
|
||||
import net.momirealms.craftengine.core.util.snbt.TagParser;
|
||||
import net.momirealms.sparrow.nbt.CompoundTag;
|
||||
import net.momirealms.sparrow.nbt.Tag;
|
||||
|
||||
@@ -41,7 +44,12 @@ public class ComponentsModifier<I> implements ItemDataModifier<I> {
|
||||
if (string.startsWith("(json) ")) {
|
||||
return CraftEngine.instance().platform().jsonToSparrowNBT(GsonHelper.get().fromJson(string.substring("(json) ".length()), JsonElement.class));
|
||||
} else if (string.startsWith("(snbt) ")) {
|
||||
return CraftEngine.instance().platform().snbtToSparrowNBT(string.substring("(snbt) ".length()));
|
||||
String snbt = string.substring("(snbt) ".length());
|
||||
try {
|
||||
return TagParser.parseTagFully(snbt);
|
||||
} catch (CommandSyntaxException e) {
|
||||
throw new LocalizedResourceConfigException("warning.config.type.snbt.invalid_syntax", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
return CraftEngine.instance().platform().javaToSparrowNBT(value);
|
||||
|
||||
@@ -134,6 +134,11 @@ public abstract class AbstractRecipeManager<T> implements RecipeManager<T> {
|
||||
return CONFIG_SECTION_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return Math.max(0, AbstractRecipeManager.this.byId.size() - AbstractRecipeManager.this.dataPackRecipes.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
|
||||
if (!Config.enableRecipeSystem()) return;
|
||||
|
||||
@@ -232,10 +232,12 @@ public class CustomSmithingTransformRecipe<T> extends AbstractedFixedResultRecip
|
||||
public static final Key KEEP_COMPONENTS = Key.of("craftengine:keep_components");
|
||||
public static final Key KEEP_TAGS = Key.of("craftengine:keep_tags");
|
||||
public static final Key MERGE_ENCHANTMENTS = Key.of("craftengine:merge_enchantments");
|
||||
public static final Key KEEP_CUSTOM_DATA = Key.of("craftengine:keep_custom_data");
|
||||
|
||||
static {
|
||||
if (VersionHelper.isOrAbove1_20_5()) {
|
||||
register(KEEP_COMPONENTS, KeepComponents.FACTORY);
|
||||
register(KEEP_CUSTOM_DATA, KeepCustomData.FACTORY);
|
||||
} else {
|
||||
register(KEEP_TAGS, KeepTags.FACTORY);
|
||||
}
|
||||
@@ -315,6 +317,42 @@ public class CustomSmithingTransformRecipe<T> extends AbstractedFixedResultRecip
|
||||
}
|
||||
}
|
||||
|
||||
public static class KeepCustomData implements ItemDataProcessor {
|
||||
public static final Factory FACTORY = new Factory();
|
||||
private final List<String[]> paths;
|
||||
|
||||
public KeepCustomData(List<String[]> data) {
|
||||
this.paths = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(Item<?> item1, Item<?> item2, Item<?> item3) {
|
||||
for (String[] path : this.paths) {
|
||||
Object dataObj = item1.getJavaTag((Object[]) path);
|
||||
if (dataObj != null) {
|
||||
item3.setTag(dataObj, (Object[]) path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key type() {
|
||||
return ItemDataProcessors.KEEP_CUSTOM_DATA;
|
||||
}
|
||||
|
||||
public static class Factory implements ProcessorFactory {
|
||||
|
||||
@Override
|
||||
public ItemDataProcessor create(Map<String, Object> arguments) {
|
||||
List<String> paths = MiscUtils.getAsStringList(ResourceConfigUtils.requireNonNullOrThrow(
|
||||
arguments.get("paths"),
|
||||
"warning.config.recipe.smithing_transform.post_processor.keep_custom_data.missing_paths")
|
||||
);
|
||||
return new KeepCustomData(paths.stream().map(it -> it.split("\\.")).toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class KeepComponents implements ItemDataProcessor {
|
||||
public static final Factory FACTORY = new Factory();
|
||||
private final List<Key> components;
|
||||
@@ -339,6 +377,7 @@ public class CustomSmithingTransformRecipe<T> extends AbstractedFixedResultRecip
|
||||
}
|
||||
|
||||
public static class Factory implements ProcessorFactory {
|
||||
private static final Key CUSTOM_DATA = Key.of("minecraft", "custom_data");
|
||||
|
||||
@Override
|
||||
public ItemDataProcessor create(Map<String, Object> arguments) {
|
||||
@@ -347,7 +386,7 @@ public class CustomSmithingTransformRecipe<T> extends AbstractedFixedResultRecip
|
||||
throw new LocalizedResourceConfigException("warning.config.recipe.smithing_transform.post_processor.keep_component.missing_components");
|
||||
}
|
||||
List<String> components = MiscUtils.getAsStringList(componentsObj);
|
||||
return new KeepComponents(components.stream().map(Key::of).toList());
|
||||
return new KeepComponents(components.stream().map(Key::of).filter(it -> !CUSTOM_DATA.equals(it)).toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,7 +407,7 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
}
|
||||
Pack pack = new Pack(path, new PackMeta(author, description, version, namespace), enable);
|
||||
this.loadedPacks.put(path.getFileName().toString(), pack);
|
||||
this.plugin.logger().info("Loaded pack: " + pack.folder().getFileName() + ". Default namespace: " + namespace);
|
||||
this.plugin.logger().info(TranslationManager.instance().translateLog("info.pack.load", pack.folder().getFileName().toString(), namespace));
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
@@ -695,7 +695,12 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
parser.loadAll();
|
||||
parser.postProcess();
|
||||
long t2 = System.nanoTime();
|
||||
this.plugin.logger().info("Loaded " + parser.sectionId()[0] + " in " + String.format("%.2f", ((t2 - t1) / 1_000_000.0)) + " ms");
|
||||
int count = parser.count();
|
||||
if (parser.silentIfNotExists() && count == 0) {
|
||||
continue;
|
||||
}
|
||||
this.plugin.logger().info(TranslationManager.instance().translateLog("info.resource.load",
|
||||
parser.sectionId()[0], String.format("%.2f", ((t2 - t1) / 1_000_000.0)), String.valueOf(count)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -720,7 +725,7 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
|
||||
@Override
|
||||
public void generateResourcePack() throws IOException {
|
||||
this.plugin.logger().info("Generating resource pack...");
|
||||
this.plugin.logger().info(TranslationManager.instance().translateLog("info.resource_pack.start"));
|
||||
long time1 = System.currentTimeMillis();
|
||||
|
||||
// Create cache data
|
||||
@@ -768,17 +773,17 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
this.removeAllShaders(generatedPackPath);
|
||||
}
|
||||
long time2 = System.currentTimeMillis();
|
||||
this.plugin.logger().info("Generated resource pack in " + (time2 - time1) + "ms");
|
||||
this.plugin.logger().info(TranslationManager.instance().translateLog("info.resource_pack.generate", String.valueOf(time2 - time1)));
|
||||
if (Config.validateResourcePack()) {
|
||||
this.validateResourcePack(generatedPackPath);
|
||||
}
|
||||
long time3 = System.currentTimeMillis();
|
||||
this.plugin.logger().info("Validated resource pack in " + (time3 - time2) + "ms");
|
||||
this.plugin.logger().info(TranslationManager.instance().translateLog("info.resource_pack.validate", String.valueOf(time3 - time2)));
|
||||
if (Config.optimizeResourcePack()) {
|
||||
this.optimizeResourcePack(generatedPackPath);
|
||||
}
|
||||
long time4 = System.currentTimeMillis();
|
||||
this.plugin.logger().info("Optimized resource pack in " + (time4 - time3) + "ms");
|
||||
this.plugin.logger().info(TranslationManager.instance().translateLog("info.resource_pack.optimize", String.valueOf(time4 - time3)));
|
||||
Path finalPath = resourcePackPath();
|
||||
Files.createDirectories(finalPath.getParent());
|
||||
try {
|
||||
@@ -787,7 +792,7 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
this.plugin.logger().severe("Error zipping resource pack", e);
|
||||
}
|
||||
long time5 = System.currentTimeMillis();
|
||||
this.plugin.logger().info("Created resource pack zip file in " + (time5 - time4) + "ms");
|
||||
this.plugin.logger().info(TranslationManager.instance().translateLog("info.resource_pack.create", String.valueOf(time5 - time4)));
|
||||
this.generationEventDispatcher.accept(generatedPackPath, finalPath);
|
||||
}
|
||||
}
|
||||
@@ -1042,7 +1047,7 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
}
|
||||
|
||||
if (Config.optimizeJson()) {
|
||||
this.plugin.logger().info("> Optimizing json files...");
|
||||
this.plugin.logger().info(TranslationManager.instance().translateLog("info.resource_pack.optimize.json"));
|
||||
AtomicLong previousBytes = new AtomicLong(0L);
|
||||
AtomicLong afterBytes = new AtomicLong(0L);
|
||||
List<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||
@@ -1109,11 +1114,11 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
long originalSize = previousBytes.get();
|
||||
long optimizedSize = afterBytes.get();
|
||||
double compressionRatio = ((double) optimizedSize / originalSize) * 100;
|
||||
this.plugin.logger().info("□ Before/After/Ratio: " + formatSize(originalSize) + "/" + formatSize(optimizedSize) + "/" + String.format("%.2f%%", compressionRatio));
|
||||
this.plugin.logger().info(TranslationManager.instance().translateLog("info.resource_pack.optimize.result", formatSize(originalSize), formatSize(optimizedSize), String.format("%.2f%%", compressionRatio)));
|
||||
}
|
||||
|
||||
if (Config.optimizeTexture()) {
|
||||
this.plugin.logger().info("> Optimizing textures...");
|
||||
this.plugin.logger().info(TranslationManager.instance().translateLog("info.resource_pack.optimize.texture"));
|
||||
AtomicLong previousBytes = new AtomicLong(0L);
|
||||
AtomicLong afterBytes = new AtomicLong(0L);
|
||||
List<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||
@@ -1155,7 +1160,7 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
long originalSize = previousBytes.get();
|
||||
long optimizedSize = afterBytes.get();
|
||||
double compressionRatio = ((double) optimizedSize / originalSize) * 100;
|
||||
this.plugin.logger().info("□ Before/After/Ratio: " + formatSize(originalSize) + "/" + formatSize(optimizedSize) + "/" + String.format("%.2f%%", compressionRatio));
|
||||
this.plugin.logger().info(TranslationManager.instance().translateLog("info.resource_pack.optimize.result", formatSize(originalSize), formatSize(optimizedSize), String.format("%.2f%%", compressionRatio)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1170,7 +1175,7 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
" ".repeat(Math.max(0, emptyLength)) +
|
||||
"]";
|
||||
return String.format(
|
||||
"%s %d/%d (%.1f%%) | Time: %ss",
|
||||
"%s %d/%d (%.1f%%) | %ss",
|
||||
progressBar,
|
||||
current,
|
||||
total,
|
||||
@@ -2854,6 +2859,11 @@ public abstract class AbstractPackManager implements PackManager {
|
||||
this.excludeJson.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return this.excludeJson.size() + this.excludeTexture.size();
|
||||
}
|
||||
|
||||
public Set<String> excludeTexture() {
|
||||
return excludeTexture;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory;
|
||||
import net.momirealms.craftengine.core.pack.host.ResourcePackHosts;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.locale.LocalizedException;
|
||||
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
|
||||
import net.momirealms.craftengine.core.util.*;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -85,7 +86,7 @@ public class AlistHost implements ResourcePackHost {
|
||||
new TypeToken<Map<String, String>>(){}.getType()
|
||||
);
|
||||
this.cachedSha1 = cache.get("sha1");
|
||||
CraftEngine.instance().logger().info("[Alist] Loaded cached resource pack metadata");
|
||||
CraftEngine.instance().logger().info(TranslationManager.instance().translateLog("info.host.cache.load", "Alist"));
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("[Alist] Failed to load cache " + cachePath, e);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory;
|
||||
import net.momirealms.craftengine.core.pack.host.ResourcePackHosts;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.locale.LocalizedException;
|
||||
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
|
||||
import net.momirealms.craftengine.core.util.*;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -58,7 +59,7 @@ public class DropboxHost implements ResourcePackHost {
|
||||
this.refreshToken = getString(cache, "refresh_token");
|
||||
this.accessToken = getString(cache, "access_token");
|
||||
this.expiresAt = getLong(cache, "expires_at");
|
||||
CraftEngine.instance().logger().info("[Dropbox] Loaded cached resource pack info");
|
||||
CraftEngine.instance().logger().info(TranslationManager.instance().translateLog("info.host.cache.load", "Dropbox"));
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn("[Dropbox] Failed to load cache " + cachePath, e);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory;
|
||||
import net.momirealms.craftengine.core.pack.host.ResourcePackHosts;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.locale.LocalizedException;
|
||||
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
|
||||
import net.momirealms.craftengine.core.util.*;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -58,7 +59,7 @@ public class GitLabHost implements ResourcePackHost {
|
||||
if (uuidString != null && !uuidString.isEmpty()) {
|
||||
this.uuid = UUID.fromString(uuidString);
|
||||
}
|
||||
CraftEngine.instance().logger().info("[GitLab] Loaded cached resource pack info");
|
||||
CraftEngine.instance().logger().info(TranslationManager.instance().translateLog("info.host.cache.load", "GitLab"));
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn(
|
||||
"[GitLab] Failed to read cache file: " + cachePath, e);
|
||||
|
||||
@@ -8,6 +8,7 @@ import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory;
|
||||
import net.momirealms.craftengine.core.pack.host.ResourcePackHosts;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.locale.LocalizedException;
|
||||
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
|
||||
import net.momirealms.craftengine.core.util.GsonHelper;
|
||||
import net.momirealms.craftengine.core.util.Key;
|
||||
import net.momirealms.craftengine.core.util.MiscUtils;
|
||||
@@ -70,7 +71,7 @@ public class LobFileHost implements ResourcePackHost {
|
||||
if (uuidString != null && !uuidString.isEmpty()) {
|
||||
this.uuid = UUID.fromString(uuidString);
|
||||
}
|
||||
CraftEngine.instance().logger().info("[LobFile] Loaded cached resource pack info");
|
||||
CraftEngine.instance().logger().info(TranslationManager.instance().translateLog("info.host.cache.load", "LobFile"));
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn(
|
||||
"[LobFile] Failed to read cache file: " + e.getMessage());
|
||||
|
||||
@@ -8,6 +8,7 @@ import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory;
|
||||
import net.momirealms.craftengine.core.pack.host.ResourcePackHosts;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.locale.LocalizedException;
|
||||
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
|
||||
import net.momirealms.craftengine.core.util.*;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -76,7 +77,7 @@ public class OneDriveHost implements ResourcePackHost {
|
||||
this.sha1 = cache.get("sha1");
|
||||
this.fileId = cache.get("file-id");
|
||||
|
||||
CraftEngine.instance().logger().info("[OneDrive] Loaded cached resource pack info");
|
||||
CraftEngine.instance().logger().info(TranslationManager.instance().translateLog("info.host.cache.load", "OneDrive"));
|
||||
} catch (Exception e) {
|
||||
CraftEngine.instance().logger().warn(
|
||||
"[OneDrive] Failed to load cache" + cachePath, e);
|
||||
|
||||
@@ -28,7 +28,7 @@ public class SelfHost implements ResourcePackHost {
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<ResourcePackDownloadData>> requestResourcePackDownloadLink(UUID player) {
|
||||
ResourcePackDownloadData data = SelfHostHttpServer.instance().generateOneTimeUrl();
|
||||
ResourcePackDownloadData data = SelfHostHttpServer.instance().generateOneTimeUrl(player);
|
||||
if (data == null) return CompletableFuture.completedFuture(List.of());
|
||||
return CompletableFuture.completedFuture(List.of(data));
|
||||
}
|
||||
@@ -77,7 +77,7 @@ public class SelfHost implements ResourcePackHost {
|
||||
boolean oneTimeToken = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("one-time-token", true), "one-time-token");
|
||||
String protocol = arguments.getOrDefault("protocol", "http").toString();
|
||||
boolean denyNonMinecraftRequest = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("deny-non-minecraft-request", true), "deny-non-minecraft-request");
|
||||
|
||||
boolean strictValidation = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("strict-validation", false), "strict-validation");
|
||||
|
||||
Bandwidth limit = null;
|
||||
Map<String, Object> rateLimitingSection = ResourceConfigUtils.getAsMapOrNull(arguments.get("rate-limiting"), "rate-limiting");
|
||||
@@ -98,7 +98,7 @@ public class SelfHost implements ResourcePackHost {
|
||||
maxBandwidthUsage = ResourceConfigUtils.getAsLong(rateLimitingSection.getOrDefault("max-bandwidth-per-second", 0), "max-bandwidth");
|
||||
minDownloadSpeed = ResourceConfigUtils.getAsLong(rateLimitingSection.getOrDefault("min-download-speed-per-player", 50_000), "min-download-speed-per-player");
|
||||
}
|
||||
selfHostHttpServer.updateProperties(ip, port, url, denyNonMinecraftRequest, protocol, limit, oneTimeToken, maxBandwidthUsage, minDownloadSpeed);
|
||||
selfHostHttpServer.updateProperties(ip, port, url, denyNonMinecraftRequest, protocol, limit, oneTimeToken, maxBandwidthUsage, minDownloadSpeed, strictValidation);
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import io.netty.util.CharsetUtil;
|
||||
import io.netty.util.concurrent.GlobalEventExecutor;
|
||||
import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@@ -33,6 +34,8 @@ import java.nio.file.Path;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
@@ -41,7 +44,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class SelfHostHttpServer {
|
||||
private static SelfHostHttpServer instance;
|
||||
private final Cache<String, Boolean> oneTimePackUrls = Caffeine.newBuilder()
|
||||
private final Cache<String, String> oneTimePackUrls = Caffeine.newBuilder()
|
||||
.maximumSize(1024)
|
||||
.scheduler(Scheduler.systemScheduler())
|
||||
.expireAfterWrite(1, TimeUnit.MINUTES)
|
||||
@@ -67,6 +70,7 @@ public class SelfHostHttpServer {
|
||||
private String url;
|
||||
private boolean denyNonMinecraft = true;
|
||||
private boolean useToken;
|
||||
private boolean strictValidation = false;
|
||||
|
||||
private long globalUploadRateLimit = 0;
|
||||
private long minDownloadSpeed = 50_000;
|
||||
@@ -97,13 +101,15 @@ public class SelfHostHttpServer {
|
||||
Bandwidth limitPerIp,
|
||||
boolean token,
|
||||
long globalUploadRateLimit,
|
||||
long minDownloadSpeed) {
|
||||
long minDownloadSpeed,
|
||||
boolean strictValidation) {
|
||||
this.ip = ip;
|
||||
this.url = url;
|
||||
this.denyNonMinecraft = denyNonMinecraft;
|
||||
this.protocol = protocol;
|
||||
this.limitPerIp = limitPerIp;
|
||||
this.useToken = token;
|
||||
this.strictValidation = strictValidation;
|
||||
if (this.globalUploadRateLimit != globalUploadRateLimit || this.minDownloadSpeed != minDownloadSpeed) {
|
||||
this.globalUploadRateLimit = globalUploadRateLimit;
|
||||
this.minDownloadSpeed = minDownloadSpeed;
|
||||
@@ -161,7 +167,7 @@ public class SelfHostHttpServer {
|
||||
});
|
||||
try {
|
||||
serverChannel = b.bind(port).sync().channel();
|
||||
CraftEngine.instance().logger().info("Netty HTTP server started on port: " + port);
|
||||
CraftEngine.instance().logger().info(TranslationManager.instance().translateLog("info.host.self.netty_server", String.valueOf(port)));
|
||||
} catch (InterruptedException e) {
|
||||
CraftEngine.instance().logger().warn("Failed to start Netty server", e);
|
||||
Thread.currentThread().interrupt();
|
||||
@@ -214,8 +220,9 @@ public class SelfHostHttpServer {
|
||||
private void handleDownload(ChannelHandlerContext ctx, FullHttpRequest request, QueryStringDecoder queryDecoder) {
|
||||
// 使用一次性token
|
||||
if (useToken) {
|
||||
String token = queryDecoder.parameters().getOrDefault("token", java.util.Collections.emptyList()).stream().findFirst().orElse(null);
|
||||
if (!validateToken(token)) {
|
||||
String token = queryDecoder.parameters().getOrDefault("token", Collections.emptyList()).stream().findFirst().orElse(null);
|
||||
String clientUUID = strictValidation ? request.headers().get("X-Minecraft-UUID") : null;
|
||||
if (!validateToken(token, clientUUID)) {
|
||||
sendError(ctx, HttpResponseStatus.FORBIDDEN, "Invalid token");
|
||||
blockedRequests.incrementAndGet();
|
||||
return;
|
||||
@@ -225,7 +232,12 @@ public class SelfHostHttpServer {
|
||||
// 不是Minecraft客户端
|
||||
if (denyNonMinecraft) {
|
||||
String userAgent = request.headers().get(HttpHeaderNames.USER_AGENT);
|
||||
if (userAgent == null || !userAgent.startsWith("Minecraft Java/")) {
|
||||
boolean nonMinecraftClient = userAgent == null || !userAgent.startsWith("Minecraft Java/");
|
||||
if (strictValidation && !nonMinecraftClient) {
|
||||
String clientVersion = request.headers().get("X-Minecraft-Version");
|
||||
nonMinecraftClient = !Objects.equals(clientVersion, userAgent.substring("Minecraft Java/".length()));
|
||||
}
|
||||
if (nonMinecraftClient) {
|
||||
sendError(ctx, HttpResponseStatus.FORBIDDEN, "Invalid client");
|
||||
blockedRequests.incrementAndGet();
|
||||
return;
|
||||
@@ -300,10 +312,11 @@ public class SelfHostHttpServer {
|
||||
return rateLimiter.tryConsume(1);
|
||||
}
|
||||
|
||||
private boolean validateToken(String token) {
|
||||
private boolean validateToken(String token, String clientUUID) {
|
||||
if (token == null || token.length() != 36) return false;
|
||||
Boolean valid = oneTimePackUrls.getIfPresent(token);
|
||||
if (valid != null) {
|
||||
String valid = oneTimePackUrls.getIfPresent(token);
|
||||
boolean isValid = strictValidation ? Objects.equals(valid, clientUUID) : valid != null;
|
||||
if (isValid) {
|
||||
oneTimePackUrls.invalidate(token);
|
||||
return true;
|
||||
}
|
||||
@@ -348,7 +361,7 @@ public class SelfHostHttpServer {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ResourcePackDownloadData generateOneTimeUrl() {
|
||||
public ResourcePackDownloadData generateOneTimeUrl(UUID user) {
|
||||
if (this.resourcePackBytes == null) return null;
|
||||
|
||||
if (!this.useToken) {
|
||||
@@ -356,7 +369,7 @@ public class SelfHostHttpServer {
|
||||
}
|
||||
|
||||
String token = UUID.randomUUID().toString();
|
||||
oneTimePackUrls.put(token, true);
|
||||
oneTimePackUrls.put(token, strictValidation ? user.toString().replace("-", "") : "");
|
||||
return new ResourcePackDownloadData(
|
||||
url() + "download?token=" + URLEncoder.encode(token, StandardCharsets.UTF_8),
|
||||
packUUID,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -10,12 +10,8 @@ public interface Platform {
|
||||
|
||||
void dispatchCommand(String command);
|
||||
|
||||
Object snbtToJava(String nbt);
|
||||
|
||||
Tag jsonToSparrowNBT(JsonElement json);
|
||||
|
||||
Tag snbtToSparrowNBT(String nbt);
|
||||
|
||||
Tag javaToSparrowNBT(Object object);
|
||||
|
||||
World getWorld(String name);
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -25,4 +25,12 @@ public interface ConfigParser extends Comparable<ConfigParser> {
|
||||
void loadAll();
|
||||
|
||||
void clear();
|
||||
|
||||
default int count() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
default boolean silentIfNotExists() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package net.momirealms.craftengine.core.plugin.config.template;
|
||||
|
||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||
import net.momirealms.craftengine.core.plugin.config.template.argument.TemplateArgument;
|
||||
import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException;
|
||||
import net.momirealms.craftengine.core.util.SNBTReader;
|
||||
import net.momirealms.craftengine.core.util.snbt.TagParser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -67,8 +68,14 @@ public interface ArgumentString {
|
||||
} else {
|
||||
this.placeholder = placeholderContent.substring(0, separatorIndex);
|
||||
String defaultValueString = placeholderContent.substring(separatorIndex + 2);
|
||||
Object parsed;
|
||||
try {
|
||||
this.defaultValue = ((TemplateManagerImpl) TemplateManager.INSTANCE).preprocessUnknownValue(new SNBTReader(defaultValueString).deserializeAsJava());
|
||||
parsed = TagParser.parseObjectFully(defaultValueString);
|
||||
} catch (CommandSyntaxException e) {
|
||||
throw new LocalizedResourceConfigException("warning.config.type.snbt.invalid_syntax", e.getMessage());
|
||||
}
|
||||
try {
|
||||
this.defaultValue = ((TemplateManagerImpl) TemplateManager.INSTANCE).preprocessUnknownValue(parsed);
|
||||
} catch (LocalizedResourceConfigException e) {
|
||||
e.appendTailArgument(this.placeholder);
|
||||
throw e;
|
||||
|
||||
@@ -51,6 +51,11 @@ public class TemplateManagerImpl implements TemplateManager {
|
||||
return LoadingSequence.TEMPLATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return TemplateManagerImpl.this.templates.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseObject(Pack pack, Path path, String node, Key id, Object obj) {
|
||||
if (TemplateManagerImpl.this.templates.containsKey(id)) {
|
||||
|
||||
@@ -49,6 +49,11 @@ public class GlobalVariableManager implements Manageable {
|
||||
return CONFIG_SECTION_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return GlobalVariableManager.this.globalVariables.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseObject(Pack pack, Path path, String node, Key id, Object object) throws LocalizedException {
|
||||
if (object != null) {
|
||||
|
||||
@@ -11,7 +11,8 @@ public enum EventTrigger {
|
||||
BREAK("break", "dig"),
|
||||
PLACE("place", "build"),
|
||||
PICK_UP("pick_up", "pick"),
|
||||
STEP("step"),;
|
||||
STEP("step"),
|
||||
FALL("fall"),;
|
||||
|
||||
public static final Map<String, EventTrigger> BY_NAME = new HashMap<>();
|
||||
private final String[] names;
|
||||
|
||||
@@ -41,12 +41,12 @@ public class RemoveFurnitureFunction<CTX extends Context> extends AbstractCondit
|
||||
WorldPosition position = furniture.position();
|
||||
World world = position.world();
|
||||
furniture.destroy();
|
||||
LootTable lootTable = furniture.config().lootTable();
|
||||
LootTable lootTable = furniture.config.lootTable();
|
||||
if (dropLoot && lootTable != null) {
|
||||
ContextHolder.Builder builder = ContextHolder.builder()
|
||||
.withParameter(DirectContextParameters.POSITION, position)
|
||||
.withParameter(DirectContextParameters.FURNITURE, furniture)
|
||||
.withOptionalParameter(DirectContextParameters.FURNITURE_ITEM, furniture.extraData().item().orElse(null));
|
||||
.withOptionalParameter(DirectContextParameters.FURNITURE_ITEM, furniture.dataAccessor.item().orElse(null));
|
||||
Optional<Player> optionalPlayer = ctx.getOptionalParameter(DirectContextParameters.PLAYER);
|
||||
Player player = optionalPlayer.orElse(null);
|
||||
if (player != null) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package net.momirealms.craftengine.core.plugin.context.function;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.furniture.AnchorType;
|
||||
import net.momirealms.craftengine.core.entity.furniture.Furniture;
|
||||
import net.momirealms.craftengine.core.plugin.context.Condition;
|
||||
import net.momirealms.craftengine.core.plugin.context.Context;
|
||||
@@ -22,7 +21,7 @@ public class ReplaceFurnitureFunction<CTX extends Context> extends AbstractCondi
|
||||
private final NumberProvider z;
|
||||
private final NumberProvider pitch;
|
||||
private final NumberProvider yaw;
|
||||
private final AnchorType anchorType;
|
||||
private final String variant;
|
||||
private final boolean dropLoot;
|
||||
private final boolean playSound;
|
||||
|
||||
@@ -33,7 +32,7 @@ public class ReplaceFurnitureFunction<CTX extends Context> extends AbstractCondi
|
||||
NumberProvider z,
|
||||
NumberProvider pitch,
|
||||
NumberProvider yaw,
|
||||
AnchorType anchorType,
|
||||
String variant,
|
||||
boolean dropLoot,
|
||||
boolean playSound,
|
||||
List<Condition<CTX>> predicates
|
||||
@@ -45,7 +44,7 @@ public class ReplaceFurnitureFunction<CTX extends Context> extends AbstractCondi
|
||||
this.z = z;
|
||||
this.pitch = pitch;
|
||||
this.yaw = yaw;
|
||||
this.anchorType = anchorType;
|
||||
this.variant = variant;
|
||||
this.dropLoot = dropLoot;
|
||||
this.playSound = playSound;
|
||||
}
|
||||
@@ -71,7 +70,7 @@ public class ReplaceFurnitureFunction<CTX extends Context> extends AbstractCondi
|
||||
RemoveFurnitureFunction.removeFurniture(ctx, oldFurniture, dropLoot, playSound);
|
||||
|
||||
// Place the new furniture
|
||||
SpawnFurnitureFunction.spawnFurniture(this.newFurnitureId, newPosition, this.anchorType, this.playSound);
|
||||
SpawnFurnitureFunction.spawnFurniture(this.newFurnitureId, newPosition, this.variant, this.playSound);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,10 +93,10 @@ public class ReplaceFurnitureFunction<CTX extends Context> extends AbstractCondi
|
||||
NumberProvider z = NumberProviders.fromObject(arguments.getOrDefault("z", "<arg:furniture.z>"));
|
||||
NumberProvider pitch = NumberProviders.fromObject(arguments.getOrDefault("pitch", "<arg:furniture.pitch>"));
|
||||
NumberProvider yaw = NumberProviders.fromObject(arguments.getOrDefault("yaw", "<arg:furniture.yaw>"));
|
||||
AnchorType anchorType = ResourceConfigUtils.getAsEnum(arguments.get("anchor-type"), AnchorType.class, null);
|
||||
String variant = ResourceConfigUtils.getAsStringOrNull(ResourceConfigUtils.get(arguments, "variant", "anchor-type"));
|
||||
boolean dropLoot = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("drop-loot", true), "drop-loot");
|
||||
boolean playSound = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("play-sound", true), "play-sound");
|
||||
return new ReplaceFurnitureFunction<>(furnitureId, x, y, z, pitch, yaw, anchorType, dropLoot, playSound, getPredicates(arguments));
|
||||
return new ReplaceFurnitureFunction<>(furnitureId, x, y, z, pitch, yaw, variant, dropLoot, playSound, getPredicates(arguments));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package net.momirealms.craftengine.core.plugin.context.function;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.furniture.AnchorType;
|
||||
import net.momirealms.craftengine.core.entity.furniture.FurnitureExtraData;
|
||||
import net.momirealms.craftengine.core.entity.furniture.FurnitureDataAccessor;
|
||||
import net.momirealms.craftengine.core.plugin.CraftEngine;
|
||||
import net.momirealms.craftengine.core.plugin.context.Condition;
|
||||
import net.momirealms.craftengine.core.plugin.context.Context;
|
||||
@@ -15,7 +14,6 @@ import net.momirealms.craftengine.core.world.WorldPosition;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class SpawnFurnitureFunction<CTX extends Context> extends AbstractConditionalFunction<CTX> {
|
||||
private final Key furnitureId;
|
||||
@@ -24,7 +22,7 @@ public class SpawnFurnitureFunction<CTX extends Context> extends AbstractConditi
|
||||
private final NumberProvider z;
|
||||
private final NumberProvider pitch;
|
||||
private final NumberProvider yaw;
|
||||
private final AnchorType anchorType;
|
||||
private final String variant;
|
||||
private final boolean playSound;
|
||||
|
||||
public SpawnFurnitureFunction(
|
||||
@@ -34,7 +32,7 @@ public class SpawnFurnitureFunction<CTX extends Context> extends AbstractConditi
|
||||
NumberProvider z,
|
||||
NumberProvider pitch,
|
||||
NumberProvider yaw,
|
||||
AnchorType anchorType,
|
||||
String variant,
|
||||
boolean playSound,
|
||||
List<Condition<CTX>> predicates
|
||||
) {
|
||||
@@ -45,7 +43,7 @@ public class SpawnFurnitureFunction<CTX extends Context> extends AbstractConditi
|
||||
this.z = z;
|
||||
this.pitch = pitch;
|
||||
this.yaw = yaw;
|
||||
this.anchorType = anchorType;
|
||||
this.variant = variant;
|
||||
this.playSound = playSound;
|
||||
}
|
||||
|
||||
@@ -59,16 +57,12 @@ public class SpawnFurnitureFunction<CTX extends Context> extends AbstractConditi
|
||||
float pitchValue = this.pitch.getFloat(ctx);
|
||||
float yawValue = this.yaw.getFloat(ctx);
|
||||
WorldPosition position = new WorldPosition(world, xPos, yPos, zPos, pitchValue, yawValue);
|
||||
spawnFurniture(this.furnitureId, position, this.anchorType, this.playSound);
|
||||
spawnFurniture(this.furnitureId, position, this.variant, this.playSound);
|
||||
});
|
||||
}
|
||||
|
||||
public static void spawnFurniture(Key furnitureId, WorldPosition position, AnchorType anchorType, boolean playSound) {
|
||||
CraftEngine.instance().furnitureManager().furnitureById(furnitureId).ifPresent(furniture -> {
|
||||
AnchorType anchor = Optional.ofNullable(anchorType).orElse(furniture.getAnyAnchorType());
|
||||
FurnitureExtraData extraData = FurnitureExtraData.builder().anchorType(anchor).build();
|
||||
CraftEngine.instance().furnitureManager().place(position, furniture, extraData, playSound);
|
||||
});
|
||||
public static void spawnFurniture(Key furnitureId, WorldPosition position, String variant, boolean playSound) {
|
||||
CraftEngine.instance().furnitureManager().furnitureById(furnitureId).ifPresent(furniture -> CraftEngine.instance().furnitureManager().place(position, furniture, FurnitureDataAccessor.ofVariant(variant), playSound));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -90,9 +84,9 @@ public class SpawnFurnitureFunction<CTX extends Context> extends AbstractConditi
|
||||
NumberProvider z = NumberProviders.fromObject(arguments.getOrDefault("z", "<arg:position.z>"));
|
||||
NumberProvider pitch = NumberProviders.fromObject(arguments.getOrDefault("pitch", "<arg:position.pitch>"));
|
||||
NumberProvider yaw = NumberProviders.fromObject(arguments.getOrDefault("yaw", "<arg:position.yaw>"));
|
||||
AnchorType anchorType = ResourceConfigUtils.getAsEnum(arguments.get("anchor-type"), AnchorType.class, null);
|
||||
String variant = ResourceConfigUtils.getAsStringOrNull(ResourceConfigUtils.get(arguments, "variant", "anchor-type"));
|
||||
boolean playSound = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("play-sound", true), "play-sound");
|
||||
return new SpawnFurnitureFunction<>(furnitureId, x, y, z, pitch, yaw, anchorType, playSound, getPredicates(arguments));
|
||||
return new SpawnFurnitureFunction<>(furnitureId, x, y, z, pitch, yaw, variant, playSound, getPredicates(arguments));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package net.momirealms.craftengine.core.plugin.context.parameter;
|
||||
import net.momirealms.craftengine.core.block.CustomBlock;
|
||||
import net.momirealms.craftengine.core.block.ImmutableBlockState;
|
||||
import net.momirealms.craftengine.core.entity.Entity;
|
||||
import net.momirealms.craftengine.core.entity.furniture.AnchorType;
|
||||
import net.momirealms.craftengine.core.entity.furniture.Furniture;
|
||||
import net.momirealms.craftengine.core.entity.player.GameMode;
|
||||
import net.momirealms.craftengine.core.entity.player.InteractionHand;
|
||||
@@ -55,7 +54,7 @@ public final class DirectContextParameters {
|
||||
public static final ContextKey<Key> ID = ContextKey.direct("id");
|
||||
public static final ContextKey<Integer> CUSTOM_MODEL_DATA = ContextKey.direct("custom_model_data");
|
||||
public static final ContextKey<Furniture> FURNITURE = ContextKey.direct("furniture");
|
||||
public static final ContextKey<AnchorType> ANCHOR_TYPE = ContextKey.direct("anchor_type");
|
||||
public static final ContextKey<String> VARIANT = ContextKey.direct("variant");
|
||||
public static final ContextKey<InteractionHand> HAND = ContextKey.direct("hand");
|
||||
public static final ContextKey<Cancellable> EVENT = ContextKey.direct("event");
|
||||
public static final ContextKey<Boolean> IS_SNEAKING = ContextKey.direct("is_sneaking");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package net.momirealms.craftengine.core.plugin.context.parameter;
|
||||
|
||||
import net.momirealms.craftengine.core.entity.Entity;
|
||||
import net.momirealms.craftengine.core.entity.ItemEntity;
|
||||
import net.momirealms.craftengine.core.entity.item.ItemEntity;
|
||||
import net.momirealms.craftengine.core.plugin.context.ChainParameterProvider;
|
||||
import net.momirealms.craftengine.core.plugin.context.ContextKey;
|
||||
import net.momirealms.craftengine.core.util.MiscUtils;
|
||||
|
||||
@@ -12,9 +12,9 @@ import java.util.function.Function;
|
||||
public class FurnitureParameterProvider implements ChainParameterProvider<Furniture> {
|
||||
private static final Map<ContextKey<?>, Function<Furniture, Object>> CONTEXT_FUNCTIONS = new HashMap<>();
|
||||
static {
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.ID, Furniture::id);
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.ID, f -> f.config().id());
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.UUID, Furniture::uuid);
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.ANCHOR_TYPE, Furniture::anchorType);
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.VARIANT, f -> f.getCurrentVariant().name());
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.X, furniture -> furniture.position().x());
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.Y, furniture -> furniture.position().y());
|
||||
CONTEXT_FUNCTIONS.put(DirectContextParameters.Z, furniture -> furniture.position().z());
|
||||
|
||||
@@ -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,57 @@ 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) {
|
||||
// 情空标志位
|
||||
Arrays.fill(this.canCheckLastHitBlock, false);
|
||||
this.hitBlockCount = 0;
|
||||
public void setDistanceScale(double distanceScale) {
|
||||
this.distanceScale = distanceScale;
|
||||
}
|
||||
|
||||
// 根据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);
|
||||
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) {
|
||||
// 情空标志位
|
||||
this.canCheckLastHitBlock = false;
|
||||
this.hitBlockCount = 0;
|
||||
AABB aabb = cullable.aabb;
|
||||
double aabbExpansion = cullable.aabbExpansion;
|
||||
|
||||
double minX = aabb.minX - aabbExpansion;
|
||||
double minY = aabb.minY - aabbExpansion;
|
||||
double minZ = aabb.minZ - aabbExpansion;
|
||||
double maxX = aabb.maxX + aabbExpansion;
|
||||
double maxY = aabb.maxY + aabbExpansion;
|
||||
double maxZ = aabb.maxZ + aabbExpansion;
|
||||
|
||||
double cameraX = cameraPos.x;
|
||||
double cameraY = cameraPos.y;
|
||||
@@ -60,7 +80,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 +89,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) {
|
||||
@@ -111,8 +136,14 @@ public final class EntityCulling {
|
||||
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[13]) targetPoints[size++].set(averageX, maxY, averageZ);
|
||||
|
||||
// if (Config.debugEntityCulling()) {
|
||||
// for (int i = 0; i < size; i++) {
|
||||
// MutableVec3d targetPoint = this.targetPoints[i];
|
||||
// this.player.playParticle(Key.of("flame"), targetPoint.x, targetPoint.y, targetPoint.z);
|
||||
// }
|
||||
// }
|
||||
return isVisible(cameraPos, this.targetPoints, size);
|
||||
}
|
||||
|
||||
@@ -168,7 +199,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 +208,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 +221,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 +296,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,7 +345,23 @@ public final class EntityCulling {
|
||||
return true;
|
||||
}
|
||||
|
||||
private double distanceSq(int min, int max, double camera, Relative rel) {
|
||||
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(double min, double max, double camera, Relative rel) {
|
||||
if (rel == Relative.NEGATIVE) {
|
||||
double dx = camera - max;
|
||||
return dx * dx;
|
||||
@@ -353,7 +399,7 @@ public final class EntityCulling {
|
||||
|
||||
private enum Relative {
|
||||
INSIDE, POSITIVE, NEGATIVE;
|
||||
public static Relative from(int min, int max, double pos) {
|
||||
public static Relative from(double min, double max, double pos) {
|
||||
if (min > pos) return POSITIVE;
|
||||
else if (max < pos) return NEGATIVE;
|
||||
return INSIDE;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -109,6 +109,11 @@ public class ItemBrowserManagerImpl implements ItemBrowserManager {
|
||||
return LoadingSequence.CATEGORY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return ItemBrowserManagerImpl.this.byId.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
|
||||
String name = section.getOrDefault("name", id).toString();
|
||||
|
||||
@@ -41,4 +41,6 @@ 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");
|
||||
TranslatableComponent.Builder COMMAND_TOGGLE_ENTITY_CULLING_SUCCESS = Component.translatable().key("command.entity_culling.toggle.success");
|
||||
}
|
||||
|
||||
@@ -4,13 +4,12 @@ import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.translation.Translator;
|
||||
import net.momirealms.craftengine.core.plugin.Manageable;
|
||||
import net.momirealms.craftengine.core.plugin.config.ConfigParser;
|
||||
import net.momirealms.craftengine.core.plugin.text.minimessage.IndexedArgumentTag;
|
||||
import net.momirealms.craftengine.core.util.AdventureHelper;
|
||||
import org.incendo.cloud.suggestion.Suggestion;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public interface TranslationManager extends Manageable {
|
||||
@@ -74,6 +73,15 @@ public interface TranslationManager extends Manageable {
|
||||
}
|
||||
}
|
||||
|
||||
default String translateLog(String id, String... arguments) {
|
||||
String translation = miniMessageTranslation(id);
|
||||
if (translation == null) {
|
||||
return id;
|
||||
}
|
||||
Component deserialize = AdventureHelper.customMiniMessage().deserialize(translation, new IndexedArgumentTag(Arrays.stream(arguments).map(Component::text).toList()));
|
||||
return AdventureHelper.plainTextContent(deserialize);
|
||||
}
|
||||
|
||||
Set<String> translationKeys();
|
||||
|
||||
void log(String id, String... args);
|
||||
|
||||
@@ -277,6 +277,7 @@ public class TranslationManagerImpl implements TranslationManager {
|
||||
|
||||
public class TranslationParser extends IdSectionConfigParser {
|
||||
public static final String[] CONFIG_SECTION_NAME = new String[] {"translations", "translation", "l10n", "localization", "i18n", "internationalization"};
|
||||
private int count;
|
||||
|
||||
@Override
|
||||
public int loadingSequence() {
|
||||
@@ -288,6 +289,16 @@ public class TranslationManagerImpl implements TranslationManager {
|
||||
return CONFIG_SECTION_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return this.count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preProcess() {
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, String node, net.momirealms.craftengine.core.util.Key id, Map<String, Object> section) {
|
||||
Locale locale = TranslationManager.parseLocale(id.value());
|
||||
@@ -300,6 +311,7 @@ public class TranslationManagerImpl implements TranslationManager {
|
||||
String key = entry.getKey();
|
||||
bundle.put(key, entry.getValue().toString());
|
||||
TranslationManagerImpl.this.translationKeys.add(key);
|
||||
this.count++;
|
||||
}
|
||||
|
||||
TranslationManagerImpl.this.registry.registerAll(locale, bundle);
|
||||
@@ -313,6 +325,7 @@ public class TranslationManagerImpl implements TranslationManager {
|
||||
Component deserialize = AdventureHelper.miniMessage().deserialize(AdventureHelper.legacyToMiniMessage(s), ShiftTag.INSTANCE, ImageTag.INSTANCE);
|
||||
return AdventureHelper.getLegacy().serialize(deserialize);
|
||||
};
|
||||
private int count;
|
||||
|
||||
@Override
|
||||
public int loadingSequence() {
|
||||
@@ -324,6 +337,16 @@ public class TranslationManagerImpl implements TranslationManager {
|
||||
return CONFIG_SECTION_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return this.count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preProcess() {
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, String node, net.momirealms.craftengine.core.util.Key id, Map<String, Object> section) {
|
||||
String langId = id.value().toLowerCase(Locale.ENGLISH);
|
||||
@@ -333,6 +356,7 @@ public class TranslationManagerImpl implements TranslationManager {
|
||||
entry -> this.langProcessor.apply(String.valueOf(entry.getValue()))
|
||||
));
|
||||
TranslationManagerImpl.this.addClientTranslation(langId, sectionData);
|
||||
this.count += sectionData.size();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import net.momirealms.craftengine.core.entity.player.Player;
|
||||
|
||||
public interface EntityPacketHandler {
|
||||
|
||||
default boolean handleEntitiesRemove(IntList entityIds) {
|
||||
default boolean handleEntitiesRemove(NetWorkUser user, IntList entityIds) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -5,7 +5,8 @@ import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
|
||||
import net.momirealms.craftengine.core.block.entity.BlockEntityType;
|
||||
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory;
|
||||
import net.momirealms.craftengine.core.block.properties.PropertyFactory;
|
||||
import net.momirealms.craftengine.core.entity.furniture.HitBoxConfigFactory;
|
||||
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElementConfigFactory;
|
||||
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfigFactory;
|
||||
import net.momirealms.craftengine.core.item.ItemDataModifierFactory;
|
||||
import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory;
|
||||
import net.momirealms.craftengine.core.item.equipment.EquipmentFactory;
|
||||
@@ -76,7 +77,6 @@ public class BuiltInRegistries {
|
||||
public static final Registry<ConditionFactory<PathContext>> PATH_MATCHER_FACTORY = createConstantBoundRegistry(Registries.PATH_MATCHER_FACTORY, 16);
|
||||
public static final Registry<ResolutionFactory> RESOLUTION_FACTORY = createConstantBoundRegistry(Registries.RESOLUTION_FACTORY, 16);
|
||||
public static final Registry<CustomSmithingTransformRecipe.ItemDataProcessor.ProcessorFactory> SMITHING_RESULT_PROCESSOR_FACTORY = createConstantBoundRegistry(Registries.SMITHING_RESULT_PROCESSOR_FACTORY, 16);
|
||||
public static final Registry<HitBoxConfigFactory> HITBOX_FACTORY = createConstantBoundRegistry(Registries.HITBOX_FACTORY, 16);
|
||||
public static final Registry<ResourcePackHostFactory> RESOURCE_PACK_HOST_FACTORY = createConstantBoundRegistry(Registries.RESOURCE_PACK_HOST_FACTORY, 16);
|
||||
public static final Registry<FunctionFactory<Context>> EVENT_FUNCTION_FACTORY = createConstantBoundRegistry(Registries.EVENT_FUNCTION_FACTORY, 128);
|
||||
public static final Registry<ConditionFactory<Context>> EVENT_CONDITION_FACTORY = createConstantBoundRegistry(Registries.EVENT_CONDITION_FACTORY, 128);
|
||||
@@ -88,9 +88,11 @@ public class BuiltInRegistries {
|
||||
public static final Registry<PostProcessor.Type<?>> RECIPE_POST_PROCESSOR_TYPE = createConstantBoundRegistry(Registries.RECIPE_POST_PROCESSOR_TYPE, 16);
|
||||
public static final Registry<ItemUpdaterType<?>> ITEM_UPDATER_TYPE = createConstantBoundRegistry(Registries.ITEM_UPDATER_TYPE, 16);
|
||||
public static final Registry<NetworkCodec<FriendlyByteBuf, ? extends ModPacket>> MOD_PACKET = createConstantBoundRegistry(Registries.MOD_PACKET, 16);
|
||||
public static final Registry<BlockEntityType<?>> BLOCK_ENTITY_TYPE = createConstantBoundRegistry(Registries.BLOCK_ENTITY_TYPE, 128);
|
||||
public static final Registry<BlockEntityElementConfigFactory> BLOCK_ENTITY_ELEMENT_TYPE = createConstantBoundRegistry(Registries.BLOCK_ENTITY_ELEMENT_TYPE, 16);
|
||||
public static final Registry<BlockEntityType<?>> BLOCK_ENTITY_TYPE = createConstantBoundRegistry(Registries.BLOCK_ENTITY_TYPE, 64);
|
||||
public static final Registry<BlockEntityElementConfigFactory<?>> BLOCK_ENTITY_ELEMENT_TYPE = createConstantBoundRegistry(Registries.BLOCK_ENTITY_ELEMENT_TYPE, 16);
|
||||
public static final Registry<CraftRemainderFactory> CRAFT_REMAINDER_FACTORY = createConstantBoundRegistry(Registries.CRAFT_REMAINDER_FACTORY, 16);
|
||||
public static final Registry<FurnitureElementConfigFactory<?>> FURNITURE_ELEMENT_TYPE = createConstantBoundRegistry(Registries.FURNITURE_ELEMENT_TYPE, 16);
|
||||
public static final Registry<FurnitureHitBoxConfigFactory<?>> FURNITURE_HITBOX_TYPE = createConstantBoundRegistry(Registries.FURNITURE_HITBOX_TYPE, 16);
|
||||
|
||||
private static <T> Registry<T> createConstantBoundRegistry(ResourceKey<? extends Registry<T>> key, int expectedSize) {
|
||||
return new ConstantBoundRegistry<>(key, expectedSize);
|
||||
|
||||
@@ -5,7 +5,8 @@ import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
|
||||
import net.momirealms.craftengine.core.block.entity.BlockEntityType;
|
||||
import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfigFactory;
|
||||
import net.momirealms.craftengine.core.block.properties.PropertyFactory;
|
||||
import net.momirealms.craftengine.core.entity.furniture.HitBoxConfigFactory;
|
||||
import net.momirealms.craftengine.core.entity.furniture.element.FurnitureElementConfigFactory;
|
||||
import net.momirealms.craftengine.core.entity.furniture.hitbox.FurnitureHitBoxConfigFactory;
|
||||
import net.momirealms.craftengine.core.item.ItemDataModifierFactory;
|
||||
import net.momirealms.craftengine.core.item.behavior.ItemBehaviorFactory;
|
||||
import net.momirealms.craftengine.core.item.equipment.EquipmentFactory;
|
||||
@@ -78,7 +79,6 @@ public class Registries {
|
||||
public static final ResourceKey<Registry<ConditionFactory<PathContext>>> PATH_MATCHER_FACTORY = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("path_matcher_factory"));
|
||||
public static final ResourceKey<Registry<ResolutionFactory>> RESOLUTION_FACTORY = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("resolution_factory"));
|
||||
public static final ResourceKey<Registry<CustomSmithingTransformRecipe.ItemDataProcessor.ProcessorFactory>> SMITHING_RESULT_PROCESSOR_FACTORY = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("smithing_result_processor_factory"));
|
||||
public static final ResourceKey<Registry<HitBoxConfigFactory>> HITBOX_FACTORY = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("hitbox_factory"));
|
||||
public static final ResourceKey<Registry<ResourcePackHostFactory>> RESOURCE_PACK_HOST_FACTORY = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("resource_pack_host_factory"));
|
||||
public static final ResourceKey<Registry<FunctionFactory<Context>>> EVENT_FUNCTION_FACTORY = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("event_function_factory"));
|
||||
public static final ResourceKey<Registry<ConditionFactory<Context>>> EVENT_CONDITION_FACTORY = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("event_condition_factory"));
|
||||
@@ -91,6 +91,8 @@ public class Registries {
|
||||
public static final ResourceKey<Registry<ItemUpdaterType<?>>> ITEM_UPDATER_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("item_updater_type"));
|
||||
public static final ResourceKey<Registry<NetworkCodec<FriendlyByteBuf, ? extends ModPacket>>> MOD_PACKET = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("mod_packet_type"));
|
||||
public static final ResourceKey<Registry<BlockEntityType<?>>> BLOCK_ENTITY_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("block_entity_type"));
|
||||
public static final ResourceKey<Registry<BlockEntityElementConfigFactory>> BLOCK_ENTITY_ELEMENT_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("block_entity_element_type"));
|
||||
public static final ResourceKey<Registry<BlockEntityElementConfigFactory<?>>> BLOCK_ENTITY_ELEMENT_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("block_entity_element_type"));
|
||||
public static final ResourceKey<Registry<CraftRemainderFactory>> CRAFT_REMAINDER_FACTORY = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("craft_remainder_factory"));
|
||||
public static final ResourceKey<Registry<FurnitureElementConfigFactory<?>>> FURNITURE_ELEMENT_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("furniture_element_type"));
|
||||
public static final ResourceKey<Registry<FurnitureHitBoxConfigFactory<?>>> FURNITURE_HITBOX_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("furniture_hitbox_type"));
|
||||
}
|
||||
|
||||
@@ -97,6 +97,11 @@ public abstract class AbstractSoundManager implements SoundManager {
|
||||
return CONFIG_SECTION_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return AbstractSoundManager.this.songs.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
|
||||
if (AbstractSoundManager.this.songs.containsKey(id)) {
|
||||
@@ -124,6 +129,11 @@ public abstract class AbstractSoundManager implements SoundManager {
|
||||
return CONFIG_SECTION_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return AbstractSoundManager.this.byId.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseSection(Pack pack, Path path, String node, Key id, Map<String, Object> section) {
|
||||
if (AbstractSoundManager.this.byId.containsKey(id)) {
|
||||
|
||||
@@ -415,4 +415,8 @@ public class MiscUtils {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int growByHalf(int value, int minValue) {
|
||||
return (int) Math.max(Math.min((long) value + (value >> 1), 2147483639L), minValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public final class ResourceConfigUtils {
|
||||
return defaultValue;
|
||||
}
|
||||
try {
|
||||
return Enum.valueOf(clazz, o.toString().toUpperCase(Locale.ENGLISH));
|
||||
return Enum.valueOf(clazz, o.toString().toUpperCase(Locale.ROOT));
|
||||
} catch (IllegalArgumentException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
@@ -263,18 +263,26 @@ public final class ResourceConfigUtils {
|
||||
}
|
||||
|
||||
public static Vector3f getAsVector3f(Object o, String option) {
|
||||
if (o == null) return new Vector3f();
|
||||
if (o instanceof List<?> list && list.size() == 3) {
|
||||
return new Vector3f(Float.parseFloat(list.get(0).toString()), Float.parseFloat(list.get(1).toString()), Float.parseFloat(list.get(2).toString()));
|
||||
} else {
|
||||
String stringFormat = o.toString();
|
||||
String[] split = stringFormat.split(",");
|
||||
if (split.length == 3) {
|
||||
return new Vector3f(Float.parseFloat(split[0]), Float.parseFloat(split[1]), Float.parseFloat(split[2]));
|
||||
} else if (split.length == 1) {
|
||||
return new Vector3f(Float.parseFloat(split[0]));
|
||||
} else {
|
||||
throw new LocalizedResourceConfigException("warning.config.type.vector3f", stringFormat, option);
|
||||
switch (o) {
|
||||
case null -> {
|
||||
return new Vector3f();
|
||||
}
|
||||
case List<?> list when list.size() == 3 -> {
|
||||
return new Vector3f(Float.parseFloat(list.get(0).toString()), Float.parseFloat(list.get(1).toString()), Float.parseFloat(list.get(2).toString()));
|
||||
}
|
||||
case Number number -> {
|
||||
return new Vector3f(number.floatValue());
|
||||
}
|
||||
default -> {
|
||||
String stringFormat = o.toString();
|
||||
String[] split = stringFormat.split(",");
|
||||
if (split.length == 3) {
|
||||
return new Vector3f(Float.parseFloat(split[0]), Float.parseFloat(split[1]), Float.parseFloat(split[2]));
|
||||
} else if (split.length == 1) {
|
||||
return new Vector3f(Float.parseFloat(split[0]));
|
||||
} else {
|
||||
throw new LocalizedResourceConfigException("warning.config.type.vector3f", stringFormat, option);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -344,50 +352,54 @@ public final class ResourceConfigUtils {
|
||||
}
|
||||
|
||||
public static AABB getAsAABB(Object o, String option) {
|
||||
if (o == null) {
|
||||
throw new LocalizedResourceConfigException("warning.config.type.aabb", "null", option);
|
||||
}
|
||||
if (o instanceof Number number) {
|
||||
double min = -(number.doubleValue() / 2);
|
||||
double max = number.doubleValue() / 2;
|
||||
return new AABB(min, min, min, max, max, max);
|
||||
} else {
|
||||
double[] args;
|
||||
if (o instanceof List<?> list) {
|
||||
args = new double[list.size()];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (list.get(i) instanceof Number number) {
|
||||
args[i] = number.doubleValue();
|
||||
} else {
|
||||
switch (o) {
|
||||
case null -> throw new LocalizedResourceConfigException("warning.config.type.aabb", "null", option);
|
||||
case AABB aabb -> {
|
||||
return aabb;
|
||||
}
|
||||
case Number number -> {
|
||||
double min = -(number.doubleValue() / 2);
|
||||
double max = number.doubleValue() / 2;
|
||||
return new AABB(min, min, min, max, max, max);
|
||||
}
|
||||
default -> {
|
||||
double[] args;
|
||||
if (o instanceof List<?> list) {
|
||||
args = new double[list.size()];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (list.get(i) instanceof Number number) {
|
||||
args[i] = number.doubleValue();
|
||||
} else {
|
||||
try {
|
||||
args[i] = Double.parseDouble(list.get(i).toString());
|
||||
} catch (NumberFormatException e) {
|
||||
throw new LocalizedResourceConfigException("warning.config.type.aabb", o.toString(), option);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String[] split = o.toString().split(",");
|
||||
args = new double[split.length];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
try {
|
||||
args[i] = Double.parseDouble(list.get(i).toString());
|
||||
args[i] = Double.parseDouble(split[i]);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new LocalizedResourceConfigException("warning.config.type.aabb", o.toString(), option);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String[] split = o.toString().split(",");
|
||||
args = new double[split.length];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
try {
|
||||
args[i] = Double.parseDouble(split[i]);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new LocalizedResourceConfigException("warning.config.type.aabb", o.toString(), option);
|
||||
}
|
||||
if (args.length == 1) {
|
||||
return new AABB(-args[0] / 2, -args[0] / 2, -args[0] / 2, args[0] / 2, args[0] / 2, args[0] / 2);
|
||||
} else if (args.length == 2) {
|
||||
return new AABB(-args[0] / 2, -args[1] / 2, -args[0] / 2, args[0] / 2, args[1] / 2, args[0] / 2);
|
||||
} else if (args.length == 3) {
|
||||
return new AABB(-args[0] / 2, -args[1] / 2, -args[2] / 2, args[0] / 2, args[1] / 2, args[2] / 2);
|
||||
} else if (args.length == 6) {
|
||||
return new AABB(args[0], args[1], args[2], args[3], args[4], args[5]);
|
||||
} else {
|
||||
throw new LocalizedResourceConfigException("warning.config.type.aabb", o.toString(), option);
|
||||
}
|
||||
}
|
||||
if (args.length == 1) {
|
||||
return new AABB(-args[0]/2, -args[0]/2, -args[0]/2, args[0]/2, args[0]/2, args[0]/2);
|
||||
} else if (args.length == 2) {
|
||||
return new AABB(-args[0]/2, -args[1]/2, -args[0]/2, args[0]/2, args[1]/2, args[0]/2);
|
||||
} else if (args.length == 3) {
|
||||
return new AABB(-args[0]/2, -args[1]/2, -args[2]/2, args[0]/2, args[1]/2, args[2]/2);
|
||||
} else if (args.length == 6) {
|
||||
return new AABB(args[0], args[1], args[2], args[3], args[4], args[5]);
|
||||
} else {
|
||||
throw new LocalizedResourceConfigException("warning.config.type.aabb", o.toString(), option);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user