9
0
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:
jhqwqmc
2025-12-05 01:13:56 +08:00
committed by GitHub
216 changed files with 7673 additions and 3223 deletions

View File

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

View File

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

View File

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

View File

@@ -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() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 {

View File

@@ -11,6 +11,8 @@ import java.util.UUID;
public interface Entity {
Key type();
boolean isValid();
double x();
double y();

View File

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

View File

@@ -1,4 +1,4 @@
package net.momirealms.craftengine.core.entity;
package net.momirealms.craftengine.core.entity.display;
public enum Billboard {
FIXED(0),

View File

@@ -1,4 +1,4 @@
package net.momirealms.craftengine.core.entity;
package net.momirealms.craftengine.core.entity.display;
public enum ItemDisplayContext {
NONE(0),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +0,0 @@
package net.momirealms.craftengine.core.entity.furniture;
import java.util.Map;
public interface HitBoxConfigFactory {
HitBoxConfig create(Map<String, Object> arguments);
}

View File

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

View File

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

View File

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

View File

@@ -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() {}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
package net.momirealms.craftengine.core.entity;
package net.momirealms.craftengine.core.entity.item;
import net.momirealms.craftengine.core.item.Item;

View File

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

View File

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

View File

@@ -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()) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,

View File

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

View File

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

View File

@@ -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)) {

View File

@@ -25,4 +25,12 @@ public interface ConfigParser extends Comparable<ConfigParser> {
void loadAll();
void clear();
default int count() {
return -1;
}
default boolean silentIfNotExists() {
return true;
}
}

View File

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

View File

@@ -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)) {

View File

@@ -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) {

View File

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

View File

@@ -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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
package net.momirealms.craftengine.core.plugin.entityculling;
import net.momirealms.craftengine.core.plugin.Manageable;
public interface EntityCullingManager extends Manageable {
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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)) {

View File

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

View File

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