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

Merge branch 'Xiao-MoMi:dev' into dev

This commit is contained in:
jhqwqmc
2025-12-05 01:27:43 +08:00
committed by GitHub
203 changed files with 7110 additions and 2785 deletions

View File

@@ -261,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() {
@@ -272,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<>();
@@ -302,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();
}
@@ -331,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);
}
@@ -711,12 +728,8 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem
}
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);
}
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"),

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

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

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

@@ -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 LinkedHashMap<>();
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,297 @@
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;
import java.util.concurrent.CompletableFuture;
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.setVariantInternal(config.getVariant(data));
}
void destroyColliders();
public Entity metaDataEntity() {
return this.metaDataEntity;
}
void destroySeats();
public FurnitureVariant getCurrentVariant() {
return this.currentVariant;
}
UUID uuid();
public abstract boolean setVariant(String variantName);
int baseEntityId();
public abstract CompletableFuture<Boolean> moveTo(WorldPosition position);
protected abstract void refresh();
protected void clearColliders() {
if (this.colliders != null) {
for (Collider collider : this.colliders) {
collider.destroy();
}
}
}
protected void setVariantInternal(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;
}
}
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) {
if (element != null) {
element.show(player);
}
}
for (FurnitureHitBox hitbox : this.hitboxes) {
if (hitbox != null) {
hitbox.show(player);
}
}
}
void setExtraData(FurnitureExtraData extraData);
@Override
public void hide(Player player) {
for (FurnitureElement element : this.elements) {
if (element != null) {
element.hide(player);
}
}
for (FurnitureHitBox hitbox : this.hitboxes) {
if (hitbox != null) {
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,130 @@
package net.momirealms.craftengine.core.entity.furniture;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
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 = ImmutableSortedMap.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;
@@ -193,6 +194,14 @@ public abstract class Player extends AbstractEntity implements NetWorkUser {
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);
@@ -213,11 +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

@@ -65,6 +65,7 @@ public final class ItemKeys {
public static final Key PURPLE_DYE = Key.of("minecraft:purple_dye");
public static final Key MAGENTA_DYE = Key.of("minecraft:magenta_dye");
public static final Key PINK_DYE = Key.of("minecraft:pink_dye");
public static final Key DEBUG_STICK = Key.of("minecraft:debug_stick");
public static final Key CARROT = Key.of("minecraft:carrot");
public static final Key POTATO = Key.of("minecraft:potato");

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) {
@@ -471,6 +471,7 @@ public abstract class AbstractPackManager implements PackManager {
plugin.saveResource("resources/default/configuration/templates/loot_tables.yml");
plugin.saveResource("resources/default/configuration/templates/recipes.yml");
plugin.saveResource("resources/default/configuration/templates/tool_levels.yml");
plugin.saveResource("resources/default/configuration/templates/events.yml");
plugin.saveResource("resources/default/configuration/categories.yml");
plugin.saveResource("resources/default/configuration/emoji.yml");
plugin.saveResource("resources/default/configuration/translations.yml");
@@ -695,7 +696,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 +726,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 +774,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 +793,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 +1048,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 +1115,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 +1161,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 +1176,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 +2860,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

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

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

@@ -16,6 +16,7 @@ import net.momirealms.craftengine.core.world.WorldPosition;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
@@ -71,7 +72,7 @@ public class CycleBlockPropertyFunction<CTX extends Context> extends AbstractCon
if (value == null) {
return wrapper.cycleProperty(this.property, inverse);
}
String mapValue = this.rules.get(value.toString());
String mapValue = this.rules.get(value.toString().toLowerCase(Locale.ROOT));
if (mapValue == null) {
return wrapper.cycleProperty(this.property, inverse);
}

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

@@ -59,13 +59,12 @@ public final class EntityCulling {
AABB aabb = cullable.aabb;
double aabbExpansion = cullable.aabbExpansion;
// 根据AABB获取能包裹此AABB的最小长方体
int minX = MiscUtils.floor(aabb.minX - aabbExpansion);
int minY = MiscUtils.floor(aabb.minY - aabbExpansion);
int minZ = MiscUtils.floor(aabb.minZ - aabbExpansion);
int maxX = MiscUtils.ceil(aabb.maxX + aabbExpansion);
int maxY = MiscUtils.ceil(aabb.maxY + aabbExpansion);
int maxZ = MiscUtils.ceil(aabb.maxZ + aabbExpansion);
double 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;
@@ -120,25 +119,31 @@ public final class EntityCulling {
}
int size = 0;
if (this.dotSelectors[0]) targetPoints[size++].set(minX + 0.05, minY + 0.05, minZ + 0.05);
if (this.dotSelectors[1]) targetPoints[size++].set(maxX - 0.05, minY + 0.05, minZ + 0.05);
if (this.dotSelectors[2]) targetPoints[size++].set(minX + 0.05, minY + 0.05, maxZ - 0.05);
if (this.dotSelectors[3]) targetPoints[size++].set(maxX - 0.05, minY + 0.05, maxZ - 0.05);
if (this.dotSelectors[4]) targetPoints[size++].set(minX + 0.05, maxY - 0.05, minZ + 0.05);
if (this.dotSelectors[5]) targetPoints[size++].set(maxX - 0.05, maxY - 0.05, minZ + 0.05);
if (this.dotSelectors[6]) targetPoints[size++].set(minX + 0.05, maxY - 0.05, maxZ - 0.05);
if (this.dotSelectors[7]) targetPoints[size++].set(maxX - 0.05, maxY - 0.05, maxZ - 0.05);
if (this.dotSelectors[0]) targetPoints[size++].set(minX, minY, minZ);
if (this.dotSelectors[1]) targetPoints[size++].set(maxX, minY, minZ);
if (this.dotSelectors[2]) targetPoints[size++].set(minX, minY, maxZ);
if (this.dotSelectors[3]) targetPoints[size++].set(maxX, minY, maxZ);
if (this.dotSelectors[4]) targetPoints[size++].set(minX, maxY, minZ);
if (this.dotSelectors[5]) targetPoints[size++].set(maxX, maxY, minZ);
if (this.dotSelectors[6]) targetPoints[size++].set(minX, maxY, maxZ);
if (this.dotSelectors[7]) targetPoints[size++].set(maxX, maxY, maxZ);
// 面中心点
double averageX = (minX + maxX) / 2.0;
double averageY = (minY + maxY) / 2.0;
double averageZ = (minZ + maxZ) / 2.0;
if (this.dotSelectors[8]) targetPoints[size++].set(averageX, averageY, minZ + 0.05);
if (this.dotSelectors[9]) targetPoints[size++].set(averageX, averageY, maxZ - 0.05);
if (this.dotSelectors[10]) targetPoints[size++].set(minX + 0.05, averageY, averageZ);
if (this.dotSelectors[11]) targetPoints[size++].set(maxX - 0.05, averageY, averageZ);
if (this.dotSelectors[12]) targetPoints[size++].set(averageX, minY + 0.05, averageZ);
if (this.dotSelectors[13]) targetPoints[size].set(averageX, maxY - 0.05, averageZ);
if (this.dotSelectors[8]) targetPoints[size++].set(averageX, averageY, minZ);
if (this.dotSelectors[9]) targetPoints[size++].set(averageX, averageY, maxZ);
if (this.dotSelectors[10]) targetPoints[size++].set(minX, averageY, averageZ);
if (this.dotSelectors[11]) targetPoints[size++].set(maxX, averageY, averageZ);
if (this.dotSelectors[12]) targetPoints[size++].set(averageX, minY, averageZ);
if (this.dotSelectors[13]) targetPoints[size++].set(averageX, maxY, averageZ);
// if (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);
}
@@ -356,7 +361,7 @@ public final class EntityCulling {
return deltaX + 32 * deltaY + 32 * 32 * deltaZ;
}
private double distanceSq(int min, int max, double camera, Relative rel) {
private double distanceSq(double min, double max, double camera, Relative rel) {
if (rel == Relative.NEGATIVE) {
double dx = camera - max;
return dx * dx;
@@ -394,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

@@ -113,6 +113,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

@@ -42,4 +42,5 @@ public interface MessageConstants {
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

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

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

View File

@@ -1,289 +0,0 @@
package net.momirealms.craftengine.core.util;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
public final class SNBTReader extends DefaultStringReader {
private static final char COMPOUND_START = '{';
private static final char COMPOUND_END = '}';
private static final char LIST_START = '[';
private static final char LIST_END = ']';
private static final char STRING_DELIMITER = '"';
private static final char SINGLE_QUOTES = '\'';
private static final char DOUBLE_QUOTES = '"';
private static final char KEY_VALUE_SEPARATOR = ':';
private static final char ELEMENT_SEPARATOR = ',';
private static final char ARRAY_DELIMITER = ';';
private static final char BYTE_ARRAY = 'b';
private static final char INT_ARRAY = 'i';
private static final char LONG_ARRAY = 'l';
public SNBTReader(String content) {
super(content);
}
public Object deserializeAsJava() {
Object result = this.parseValue();
this.skipWhitespace();
if (getCursor() != getTotalLength())
throw new IllegalArgumentException("Extra content at end: " + substring(getCursor(), getTotalLength()));
return result;
}
// 开始解析, 步进字符.
private Object parseValue() {
skipWhitespace();
return switch (peek()) {
case COMPOUND_START -> parseCompound();
case LIST_START -> parseList();
case DOUBLE_QUOTES -> {
skip();
yield readStringUntil(DOUBLE_QUOTES);
}
case SINGLE_QUOTES -> {
skip();
yield readStringUntil(SINGLE_QUOTES);
}
default -> parsePrimitive();
};
}
// 解析包小肠 {}
private Map<String, Object> parseCompound() {
skip(); // 跳过 '{'
skipWhitespace();
Map<String, Object> compoundMap = new LinkedHashMap<>();
if (canRead() && peek() != COMPOUND_END) {
do {
String key = parseKey();
if (!canRead() || peek() != KEY_VALUE_SEPARATOR) {
throw new IllegalArgumentException("Expected ':' at position " + getCursor());
}
skip(); // 跳过 ':'
Object value = parseValue();
compoundMap.put(key, value);
skipWhitespace();
} while (canRead() && peek() == ELEMENT_SEPARATOR && ++super.cursor > 0 /* 跳过 ',' */);
}
if (!canRead() || peek() != COMPOUND_END) {
throw new IllegalArgumentException("Expected '}' at position " + getCursor());
}
skip(); // 跳过 '}'
return compoundMap;
}
// 解析列表值 [1, 2, 3]
private Object parseList() {
skip(); // 跳过 '['
skipWhitespace();
// 检查接下来的2个非空格字符, 确认是否要走数组解析.
if (canRead()) {
setMarker(cursor); // 记录指针, 尝试解析数组.
char typeChar = Character.toLowerCase(peek());
if (typeChar == BYTE_ARRAY || typeChar == INT_ARRAY || typeChar == LONG_ARRAY) {
skip();
skipWhitespace();
if (canRead() && peek() == ARRAY_DELIMITER) { // 下一个必须是 ';'
skip();
switch (typeChar) { // 解析并返回数组喵
case BYTE_ARRAY -> {
return parseArray(list -> {
byte[] bytes = new byte[list.size()];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = list.get(i).byteValue();
}
return bytes;
});
}
case INT_ARRAY -> {
return parseArray(list -> {
int[] ints = new int[list.size()];
for (int i = 0; i < ints.length; i++) {
ints[i] = list.get(i).intValue();
}
return ints;
});
}
case LONG_ARRAY -> {
return parseArray(list -> {
long[] longs = new long[list.size()];
for (int i = 0; i < longs.length; i++) {
longs[i] = list.get(i).longValue();
}
return longs;
});
}
}
}
}
restore(); // 复原指针.
}
List<Object> elementList = new ArrayList<>();
if (canRead() && peek() != LIST_END) {
do {
elementList.add(parseValue());
skipWhitespace();
} while (canRead() && peek() == ELEMENT_SEPARATOR && ++super.cursor > 0 /* 跳过 ',' */);
}
if (!canRead() || peek() != LIST_END) {
throw new IllegalArgumentException("Expected ']' at position " + getCursor());
}
skip(); // 跳过 ']'
return elementList;
}
// 解析数组 [I; 11, 41, 54]
// ArrayType -> B, I, L.
private Object parseArray(Function<List<Number>, Object> convertor) {
skipWhitespace();
// 用来暂存解析出的数字
List<Number> elements = new ArrayList<>();
if (canRead() && peek() != LIST_END) {
do {
Object element = parseValue();
// 1.21.6的SNBT原版是支持 {key:[B;1,2b,0xFF]} 这种奇葩写法的, 越界部分会被自动舍弃, 如0xff的byte值为-1.
// 如果需要和原版对齐, 那么只需要判断是否是数字就行了.
// if (!(element instanceof Number number))
// throw new IllegalArgumentException("Error element type at pos " + getCursor());
if (!(element instanceof Number number))
throw new IllegalArgumentException("Error parsing number at pos " + getCursor());
elements.add(number); // 校验通过后加入
skipWhitespace();
} while (canRead() && peek() == ELEMENT_SEPARATOR && ++cursor > 0 /* 跳过 ',' */);
}
if (!canRead() || peek() != LIST_END)
throw new IllegalArgumentException("Expected ']' at position " + getCursor());
skip(); // 跳过 ']'
return convertor.apply(elements);
}
// 解析Key值
private String parseKey() {
skipWhitespace();
if (!canRead()) {
throw new IllegalArgumentException("Unterminated key at " + getCursor());
}
// 如果有双引号就委托给string解析处理.
char peek = peek();
if (peek == STRING_DELIMITER) {
skip();
return readStringUntil(STRING_DELIMITER);
} else if (peek == SINGLE_QUOTES) {
skip();
return readStringUntil(SINGLE_QUOTES);
}
int start = getCursor();
while (canRead()) {
char c = peek();
if (c == ' ') break; // 忽略 key 后面的空格, { a :1} 应当解析成 {a:1}
if (Character.isJavaIdentifierPart(c)) skip(); else break;
}
String key = substring(start, getCursor());
skipWhitespace(); // 跳过 key 后面的空格.
return key;
}
// 解析原生值
private Object parsePrimitive() {
// 先解析获取值的长度
int tokenStart = getCursor();
int lastWhitespace = 0; // 记录值末尾的空格数量,{a:炒鸡 大保健} 和 {a: 炒鸡 大保健 } 都应解析成 "炒鸡 大保健".
boolean contentHasWhitespace = false; // 记录值中有没有空格.
while (canRead()) {
char c = peek();
if (c == ',' || c == ']' || c == '}') break;
skip();
if (c == ' ') {
lastWhitespace++; // 遇到空格先增加值, 代表值尾部空格数量.
continue;
}
if (lastWhitespace > 0) {
lastWhitespace = 0; // 遇到正常字符时清空记录的尾部空格数.
contentHasWhitespace = true;
}
}
int tokenLength = getCursor() - tokenStart - lastWhitespace; // 计算值长度需要再减去尾部空格.
if (tokenLength == 0) return null; // 如果值长度为0则返回null.
if (contentHasWhitespace) return substring(tokenStart, tokenStart + tokenLength); // 如果值的中间有空格, 一定是字符串, 可直接返回.
// 布尔值检查
if (tokenLength == 4) {
if (matchesAt(tokenStart, "true")) return Boolean.TRUE;
if (matchesAt(tokenStart, "null")) return null; // 支持 {key:null}.
} else if (tokenLength == 5) {
if (matchesAt(tokenStart, "false")) return Boolean.FALSE;
}
if (tokenLength > 1) {
// 至少有1个字符给了后缀的可能性
char lastChar = charAt(tokenStart + tokenLength - 1);
try {
switch (lastChar) {
case 'b', 'B' -> {
return Byte.parseByte(substring(tokenStart, tokenStart + tokenLength - 1));
}
case 's', 'S' -> {
return Short.parseShort(substring(tokenStart, tokenStart + tokenLength - 1));
}
case 'l', 'L' -> {
return Long.parseLong(substring(tokenStart, tokenStart + tokenLength - 1));
}
case 'f', 'F' -> {
return Float.parseFloat(substring(tokenStart, tokenStart + tokenLength));
}
case 'd', 'D' -> {
return Double.parseDouble(substring(tokenStart, tokenStart + tokenLength));
}
default -> {
String fullString = substring(tokenStart, tokenStart + tokenLength);
try {
double d = Double.parseDouble(fullString);
if (d % 1 != 0 || fullString.contains(".") || fullString.contains("e")) {
return d;
} else {
return (int) d;
}
} catch (NumberFormatException e) {
return fullString;
}
}
}
} catch (NumberFormatException e) {
return substring(tokenStart, tokenStart + tokenLength);
}
} else {
char onlyChar = charAt(tokenStart);
if (isNumber(onlyChar)) {
return onlyChar - '0';
} else {
return String.valueOf(onlyChar);
}
}
}
// 工具函数: 快速检查布尔值字符串匹配, 忽略大小写.
private boolean matchesAt(int start, String target) {
for (int i = 0; i < target.length(); i++) {
char c1 = charAt(start + i);
char c2 = target.charAt(i);
if (c1 != c2 && c1 != (c2 ^ 32)) return false; // 忽略大小写比较
}
return true;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,92 @@
package net.momirealms.craftengine.core.util.snbt;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import com.mojang.serialization.DynamicOps;
import net.momirealms.craftengine.core.util.snbt.parse.*;
import net.momirealms.sparrow.nbt.util.UUIDUtil;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class SnbtOperations {
static final DelayedException<CommandSyntaxException> ERROR_EXPECTED_STRING_UUID = DelayedException.create(
new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.expected_string_uuid"))
);
static final DelayedException<CommandSyntaxException> ERROR_EXPECTED_NUMBER_OR_BOOLEAN = DelayedException.create(
new LocalizedSimpleCommandExceptionType(new LocalizedMessage("warning.config.type.snbt.parser.expected_number_or_boolean"))
);
public static final String BUILTIN_TRUE = "true";
public static final String BUILTIN_FALSE = "false";
public static final String BUILTIN_NULL = "null";
public static final Map<BuiltinKey, BuiltinOperation> BUILTIN_OPERATIONS = Map.of(
new BuiltinKey("bool", 1), new BuiltinOperation() {
@Override
public <T> T run(DynamicOps<T> ops, List<T> arguments, ParseState<StringReader> state) {
Boolean result = convert(ops, arguments.getFirst());
if (result == null) {
state.errorCollector().store(state.mark(), SnbtOperations.ERROR_EXPECTED_NUMBER_OR_BOOLEAN);
return null;
}
return ops.createBoolean(result);
}
@Nullable
private static <T> Boolean convert(DynamicOps<T> ops, T arg) {
Optional<Boolean> asBoolean = ops.getBooleanValue(arg).result();
if (asBoolean.isPresent()) {
return asBoolean.get();
} else {
Optional<Number> asNumber = ops.getNumberValue(arg).result();
return asNumber.isPresent() ? asNumber.get().doubleValue() != 0.0 : null;
}
}
}, new BuiltinKey("uuid", 1), new BuiltinOperation() {
@Override
public <T> T run(DynamicOps<T> ops, List<T> arguments, ParseState<StringReader> state) {
Optional<String> arg = ops.getStringValue(arguments.getFirst()).result();
if (arg.isEmpty()) {
state.errorCollector().store(state.mark(), SnbtOperations.ERROR_EXPECTED_STRING_UUID);
return null;
}
UUID uuid;
try {
uuid = UUID.fromString(arg.get());
} catch (IllegalArgumentException var7) {
state.errorCollector().store(state.mark(), SnbtOperations.ERROR_EXPECTED_STRING_UUID);
return null;
}
return ops.createIntList(IntStream.of(UUIDUtil.uuidToIntArray(uuid)));
}
}
);
public static final SuggestionSupplier<StringReader> BUILTIN_IDS = new SuggestionSupplier<>() {
private final Set<String> keys = Stream.concat(
Stream.of(BUILTIN_FALSE, BUILTIN_TRUE, BUILTIN_NULL), SnbtOperations.BUILTIN_OPERATIONS.keySet().stream().map(BuiltinKey::id)
)
.collect(Collectors.toSet());
@Override
public Stream<String> possibleValues(ParseState<StringReader> state) {
return this.keys.stream();
}
};
public record BuiltinKey(String id, int argCount) {
@Override
public @NotNull String toString() {
return this.id + "/" + this.argCount;
}
}
public interface BuiltinOperation {
@Nullable
<T> T run(DynamicOps<T> ops, List<T> arguments, ParseState<StringReader> state);
}
}

View File

@@ -0,0 +1,97 @@
package net.momirealms.craftengine.core.util.snbt;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import com.mojang.serialization.DynamicOps;
import com.mojang.serialization.JavaOps;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.util.snbt.parse.LocalizedMessage;
import net.momirealms.craftengine.core.util.snbt.parse.LocalizedSimpleCommandExceptionType;
import net.momirealms.craftengine.core.util.snbt.parse.grammar.Grammar;
import net.momirealms.sparrow.nbt.CompoundTag;
import net.momirealms.sparrow.nbt.Tag;
import net.momirealms.sparrow.nbt.codec.LegacyJavaOps;
import net.momirealms.sparrow.nbt.codec.LegacyNBTOps;
import net.momirealms.sparrow.nbt.codec.NBTOps;
@SuppressWarnings("unused")
public class TagParser<T> {
public static final SimpleCommandExceptionType ERROR_TRAILING_DATA = new LocalizedSimpleCommandExceptionType(
new LocalizedMessage("warning.config.type.snbt.parser.trailing")
);
public static final SimpleCommandExceptionType ERROR_EXPECTED_COMPOUND = new LocalizedSimpleCommandExceptionType(
new LocalizedMessage("warning.config.type.snbt.parser.expected.compound")
);
public static final char ELEMENT_SEPARATOR = ',';
public static final char NAME_VALUE_SEPARATOR = ':';
public static final TagParser<Tag> NBT_OPS_PARSER = create(VersionHelper.isOrAbove1_20_5() ? NBTOps.INSTANCE : LegacyNBTOps.INSTANCE);
public static final TagParser<Object> JAVA_OPS_PARSER = create(VersionHelper.isOrAbove1_20_5() ? JavaOps.INSTANCE : LegacyJavaOps.INSTANCE);
private final DynamicOps<T> ops;
private final Grammar<T> grammar;
private TagParser(DynamicOps<T> ops, Grammar<T> grammar) {
this.ops = ops;
this.grammar = grammar;
}
public DynamicOps<T> ops() {
return this.ops;
}
public static <T> TagParser<T> create(DynamicOps<T> ops) {
return new TagParser<>(ops, SnbtGrammar.createParser(ops));
}
private static CompoundTag castToCompoundOrThrow(StringReader reader, Tag result) throws CommandSyntaxException {
if (result instanceof CompoundTag compoundTag) {
return compoundTag;
}
throw ERROR_EXPECTED_COMPOUND.createWithContext(reader);
}
public static CompoundTag parseCompoundFully(String input) throws CommandSyntaxException {
StringReader reader = new StringReader(input);
Tag result = NBT_OPS_PARSER.parseFully(reader);
return castToCompoundOrThrow(reader, result);
}
public static Tag parseTagFully(String input) throws CommandSyntaxException {
StringReader reader = new StringReader(input);
return NBT_OPS_PARSER.parseFully(reader);
}
public static Object parseObjectFully(String input) throws CommandSyntaxException {
StringReader reader = new StringReader(input);
return JAVA_OPS_PARSER.parseFully(reader);
}
public T parseFully(String input) throws CommandSyntaxException {
return this.parseFully(new StringReader(input));
}
public T parseFully(StringReader reader) throws CommandSyntaxException {
T result = this.grammar.parse(reader);
reader.skipWhitespace();
if (reader.canRead()) {
throw ERROR_TRAILING_DATA.createWithContext(reader);
}
return result;
}
public T parseAsArgument(StringReader reader) throws CommandSyntaxException {
return this.grammar.parse(reader);
}
public static CompoundTag parseCompoundAsArgument(StringReader reader) throws CommandSyntaxException {
Tag result = parseTagAsArgument(reader);
return castToCompoundOrThrow(reader, result);
}
public static Tag parseTagAsArgument(StringReader reader) throws CommandSyntaxException {
return NBT_OPS_PARSER.parseAsArgument(reader);
}
public static Object parseObjectAsArgument(StringReader reader) throws CommandSyntaxException {
return JAVA_OPS_PARSER.parseAsArgument(reader);
}
}

View File

@@ -0,0 +1,15 @@
package net.momirealms.craftengine.core.util.snbt.parse;
import org.jetbrains.annotations.NotNull;
@SuppressWarnings("unused")
public record Atom<T>(String name) {
@Override
public @NotNull String toString() {
return "<" + this.name + ">";
}
public static <T> Atom<T> of(String name) {
return new Atom<>(name);
}
}

View File

@@ -0,0 +1,235 @@
package net.momirealms.craftengine.core.util.snbt.parse;
import net.momirealms.craftengine.core.util.MiscUtils;
import javax.annotation.Nullable;
@SuppressWarnings("unchecked")
public abstract class CachedParseState<S> implements ParseState<S> {
private PositionCache[] positionCache = new PositionCache[256];
private final ErrorCollector<S> errorCollector;
private final Scope scope = new Scope();
private SimpleControl[] controlCache = new SimpleControl[16];
private int nextControlToReturn;
private final Silent silent = new Silent();
public static final Object JAVA_NULL_VALUE_MARKER = new Object() {
@Override
public String toString() {
return "null";
}
};
protected CachedParseState(ErrorCollector<S> errorCollector) {
this.errorCollector = errorCollector;
}
@Override
public Scope scope() {
return this.scope;
}
@Override
public ErrorCollector<S> errorCollector() {
return this.errorCollector;
}
@Nullable
@Override
public <T> T parse(NamedRule<S, T> rule) {
int markBeforeParse = this.mark();
PositionCache positionCache = this.getCacheForPosition(markBeforeParse);
int entryIndex = positionCache.findKeyIndex(rule.name());
if (entryIndex != -1) {
CacheEntry<T> value = positionCache.getValue(entryIndex);
if (value != null) {
if (value == CachedParseState.CacheEntry.NEGATIVE) {
return null;
}
this.restore(value.markAfterParse);
return value.value;
}
} else {
entryIndex = positionCache.allocateNewEntry(rule.name());
}
T result = rule.value().parse(this);
CacheEntry<T> entry;
if (result == null) {
entry = CacheEntry.negativeEntry();
} else {
int markAfterParse = this.mark();
entry = new CacheEntry<>(result, markAfterParse);
}
positionCache.setValue(entryIndex, entry);
return result;
}
private PositionCache getCacheForPosition(int index) {
int currentSize = this.positionCache.length;
if (index >= currentSize) {
int newSize = MiscUtils.growByHalf(currentSize, index + 1);
PositionCache[] newCache = new PositionCache[newSize];
System.arraycopy(this.positionCache, 0, newCache, 0, currentSize);
this.positionCache = newCache;
}
PositionCache result = this.positionCache[index];
if (result == null) {
result = new PositionCache();
this.positionCache[index] = result;
}
return result;
}
@Override
public Control acquireControl() {
int currentSize = this.controlCache.length;
if (this.nextControlToReturn >= currentSize) {
int newSize = MiscUtils.growByHalf(currentSize, this.nextControlToReturn + 1);
SimpleControl[] newControlCache = new SimpleControl[newSize];
System.arraycopy(this.controlCache, 0, newControlCache, 0, currentSize);
this.controlCache = newControlCache;
}
int controlIndex = this.nextControlToReturn++;
SimpleControl entry = this.controlCache[controlIndex];
if (entry == null) {
entry = new SimpleControl();
this.controlCache[controlIndex] = entry;
} else {
entry.reset();
}
return entry;
}
@Override
public void releaseControl() {
this.nextControlToReturn--;
}
@Override
public ParseState<S> silent() {
return this.silent;
}
record CacheEntry<T>(@Nullable T value, int markAfterParse) {
public static final CacheEntry<?> NEGATIVE = new CacheEntry<>(null, -1);
public static <T> CacheEntry<T> negativeEntry() {
return (CacheEntry<T>) NEGATIVE;
}
}
static class PositionCache {
public static final int ENTRY_STRIDE = 2;
private static final int NOT_FOUND = -1;
private Object[] atomCache = new Object[16];
private int nextKey;
public int findKeyIndex(Atom<?> key) {
for (int i = 0; i < this.nextKey; i += ENTRY_STRIDE) {
if (this.atomCache[i] == key) {
return i;
}
}
return NOT_FOUND;
}
public int allocateNewEntry(Atom<?> key) {
int newKeyIndex = this.nextKey;
this.nextKey += ENTRY_STRIDE;
int newValueIndex = newKeyIndex + 1;
int currentSize = this.atomCache.length;
if (newValueIndex >= currentSize) {
int newSize = MiscUtils.growByHalf(currentSize, newValueIndex + 1);
Object[] newCache = new Object[newSize];
System.arraycopy(this.atomCache, 0, newCache, 0, currentSize);
this.atomCache = newCache;
}
this.atomCache[newKeyIndex] = key;
return newKeyIndex;
}
@Nullable
public <T> CacheEntry<T> getValue(int keyIndex) {
return (CacheEntry<T>) this.atomCache[keyIndex + 1];
}
public void setValue(int keyIndex, CacheEntry<?> entry) {
this.atomCache[keyIndex + 1] = entry;
}
}
class Silent implements ParseState<S> {
private final ErrorCollector<S> silentCollector = new ErrorCollector.Nop<>();
@Override
public ErrorCollector<S> errorCollector() {
return this.silentCollector;
}
@Override
public Scope scope() {
return CachedParseState.this.scope();
}
@Nullable
@Override
public <T> T parse(NamedRule<S, T> rule) {
return CachedParseState.this.parse(rule);
}
@Override
public S input() {
return CachedParseState.this.input();
}
@Override
public int mark() {
return CachedParseState.this.mark();
}
@Override
public void restore(int mark) {
CachedParseState.this.restore(mark);
}
@Override
public Control acquireControl() {
return CachedParseState.this.acquireControl();
}
@Override
public void releaseControl() {
CachedParseState.this.releaseControl();
}
@Override
public ParseState<S> silent() {
return this;
}
}
static class SimpleControl implements Control {
private boolean hasCut;
@Override
public void cut() {
this.hasCut = true;
}
@Override
public boolean hasCut() {
return this.hasCut;
}
public void reset() {
this.hasCut = false;
}
}
}

View File

@@ -0,0 +1,18 @@
package net.momirealms.craftengine.core.util.snbt.parse;
public interface Control {
Control UNBOUND = new Control() {
@Override
public void cut() {
}
@Override
public boolean hasCut() {
return false;
}
};
void cut();
boolean hasCut();
}

View File

@@ -0,0 +1,19 @@
package net.momirealms.craftengine.core.util.snbt.parse;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import net.momirealms.craftengine.core.util.snbt.parse.grammar.StringReaderTerms;
@FunctionalInterface
public interface DelayedException<T extends Exception> {
T create(String contents, int position);
static DelayedException<CommandSyntaxException> create(SimpleCommandExceptionType type) {
return (contents, position) -> type.createWithContext(StringReaderTerms.createReader(contents, position));
}
static DelayedException<CommandSyntaxException> create(DynamicCommandExceptionType type, String argument) {
return (contents, position) -> type.createWithContext(StringReaderTerms.createReader(contents, position), argument);
}
}

View File

@@ -0,0 +1,93 @@
package net.momirealms.craftengine.core.util.snbt.parse;
import javax.annotation.Nullable;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
@SuppressWarnings("unchecked")
public class Dictionary<S> {
private final Map<Atom<?>, Entry<S, ?>> terms = new IdentityHashMap<>();
public <T> NamedRule<S, T> put(Atom<T> name, Rule<S, T> entry) {
Entry<S, T> holder = (Entry<S, T>)this.terms.computeIfAbsent(name, Entry::new);
if (holder.value != null) {
throw new IllegalArgumentException("Trying to override rule: " + name);
}
holder.value = entry;
return holder;
}
public <T> NamedRule<S, T> putComplex(Atom<T> name, Term<S> term, Rule.RuleAction<S, T> action) {
return this.put(name, Rule.fromTerm(term, action));
}
public <T> NamedRule<S, T> put(Atom<T> name, Term<S> term, Rule.SimpleRuleAction<S, T> action) {
return this.put(name, Rule.fromTerm(term, action));
}
public void checkAllBound() {
List<? extends Atom<?>> unboundNames = this.terms.entrySet().stream()
.filter(entry -> entry.getValue() == null)
.map(Map.Entry::getKey)
.toList();
if (!unboundNames.isEmpty()) {
throw new IllegalStateException("Unbound names: " + unboundNames);
}
}
public <T> NamedRule<S, T> forward(Atom<T> name) {
return this.getOrCreateEntry(name);
}
private <T> Entry<S, T> getOrCreateEntry(Atom<T> name) {
return (Entry<S, T>)this.terms.computeIfAbsent(name, Entry::new);
}
public <T> Term<S> named(Atom<T> name) {
return new Reference<>(this.getOrCreateEntry(name), name);
}
public <T> Term<S> namedWithAlias(Atom<T> nameToParse, Atom<T> nameToStore) {
return new Reference<>(this.getOrCreateEntry(nameToParse), nameToStore);
}
static class Entry<S, T> implements NamedRule<S, T>, Supplier<String> {
private final Atom<T> name;
@Nullable
Rule<S, T> value;
private Entry(Atom<T> name) {
this.name = name;
}
@Override
public Atom<T> name() {
return this.name;
}
@Override
public Rule<S, T> value() {
return Objects.requireNonNull(this.value, this);
}
@Override
public String get() {
return "Unbound rule " + this.name;
}
}
record Reference<S, T>(Entry<S, T> ruleToParse, Atom<T> nameToStore) implements Term<S> {
@Override
public boolean parse(ParseState<S> state, Scope scope, Control control) {
T result = state.parse(this.ruleToParse);
if (result == null) {
return false;
}
scope.put(this.nameToStore, result);
return true;
}
}
}

View File

@@ -0,0 +1,97 @@
package net.momirealms.craftengine.core.util.snbt.parse;
import net.momirealms.craftengine.core.util.MiscUtils;
import java.util.ArrayList;
import java.util.List;
@SuppressWarnings("unchecked")
public interface ErrorCollector<S> {
void store(int cursor, SuggestionSupplier<S> suggestions, Object reason);
default void store(int cursor, Object reason) {
this.store(cursor, SuggestionSupplier.empty(), reason);
}
void finish(int finalCursor);
class LongestOnly<S> implements ErrorCollector<S> {
private MutableErrorEntry<S>[] entries = new MutableErrorEntry[16];
private int nextErrorEntry;
private int lastCursor = -1;
private void discardErrorsFromShorterParse(int cursor) {
if (cursor > this.lastCursor) {
this.lastCursor = cursor;
this.nextErrorEntry = 0;
}
}
@Override
public void finish(int finalCursor) {
this.discardErrorsFromShorterParse(finalCursor);
}
@Override
public void store(int cursor, SuggestionSupplier<S> suggestions, Object reason) {
this.discardErrorsFromShorterParse(cursor);
if (cursor == this.lastCursor) {
this.addErrorEntry(suggestions, reason);
}
}
private void addErrorEntry(SuggestionSupplier<S> suggestions, Object reason) {
int currentSize = this.entries.length;
if (this.nextErrorEntry >= currentSize) {
int newSize = MiscUtils.growByHalf(currentSize, this.nextErrorEntry + 1);
MutableErrorEntry<S>[] newEntries = new MutableErrorEntry[newSize];
System.arraycopy(this.entries, 0, newEntries, 0, currentSize);
this.entries = newEntries;
}
int entryIndex = this.nextErrorEntry++;
MutableErrorEntry<S> entry = this.entries[entryIndex];
if (entry == null) {
entry = new MutableErrorEntry<>();
this.entries[entryIndex] = entry;
}
entry.suggestions = suggestions;
entry.reason = reason;
}
public List<ErrorEntry<S>> entries() {
int errorCount = this.nextErrorEntry;
if (errorCount == 0) {
return List.of();
}
List<ErrorEntry<S>> result = new ArrayList<>(errorCount);
for (int i = 0; i < errorCount; i++) {
MutableErrorEntry<S> mutableErrorEntry = this.entries[i];
result.add(new ErrorEntry<>(this.lastCursor, mutableErrorEntry.suggestions, mutableErrorEntry.reason));
}
return result;
}
public int cursor() {
return this.lastCursor;
}
static class MutableErrorEntry<S> {
SuggestionSupplier<S> suggestions = SuggestionSupplier.empty();
Object reason = "empty";
}
}
class Nop<S> implements ErrorCollector<S> {
@Override
public void store(int cursor, SuggestionSupplier<S> suggestions, Object reason) {
}
@Override
public void finish(int finalCursor) {
}
}
}

View File

@@ -0,0 +1,4 @@
package net.momirealms.craftengine.core.util.snbt.parse;
public record ErrorEntry<S>(int cursor, SuggestionSupplier<S> suggestions, Object reason) {
}

View File

@@ -0,0 +1,84 @@
package net.momirealms.craftengine.core.util.snbt.parse;
import com.mojang.brigadier.Message;
import com.mojang.brigadier.exceptions.CommandExceptionType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
import net.momirealms.craftengine.core.util.AdventureHelper;
import java.util.Optional;
import java.util.function.Supplier;
public class LocalizedCommandSyntaxException extends CommandSyntaxException {
public static final int CONTEXT_AMOUNT = 50;
public static final String PARSE_ERROR_NODE = "warning.config.type.snbt.invalid_syntax.parse_error";
public static final String HERE_NODE = "warning.config.type.snbt.invalid_syntax.here";
private final Message message;
private final String input;
private final int cursor;
public LocalizedCommandSyntaxException(CommandExceptionType type, Message message) {
super(type, message);
this.message = message;
this.input = null;
this.cursor = -1;
}
public LocalizedCommandSyntaxException(CommandExceptionType type, Message message, String input, int cursor) {
super(type, message, input, cursor);
this.message = message;
this.input = input;
this.cursor = cursor;
}
@Override
public String getMessage() {
String message = this.message.getString();
final String context = getContext();
if (context == null) {
return message;
}
return generateLocalizedMessage(
PARSE_ERROR_NODE,
() -> message + " at position " + this.cursor + ": " + context,
message, String.valueOf(this.cursor), context
);
}
@Override
public String getContext() {
if (this.input == null || this.cursor < 0) {
return null;
}
final StringBuilder builder = new StringBuilder();
final int cursor = Math.min(this.input.length(), this.cursor);
if (cursor > CONTEXT_AMOUNT) {
builder.append("...");
}
builder.append(this.input, Math.max(0, cursor - CONTEXT_AMOUNT), cursor);
builder.append(generateLocalizedMessage(HERE_NODE, () -> "<--[HERE]"));
return builder.toString();
}
private String generateLocalizedMessage(String node, Supplier<String> fallback, String... arguments) {
try {
String rawMessage = Optional.ofNullable(TranslationManager.instance()
.miniMessageTranslation(node)).orElse(fallback.get());
String cleanMessage = AdventureHelper.miniMessage()
.stripTags(rawMessage);
for (int i = 0; i < arguments.length; i++) {
cleanMessage = cleanMessage.replace(
"<arg:" + i + ">",
arguments[i] != null ? arguments[i] : "null"
);
}
return cleanMessage;
} catch (Exception e) {
return fallback.get();
}
}
}

View File

@@ -0,0 +1,28 @@
package net.momirealms.craftengine.core.util.snbt.parse;
import com.mojang.brigadier.ImmutableStringReader;
import com.mojang.brigadier.Message;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import java.util.function.Function;
public class LocalizedDynamicCommandExceptionType extends DynamicCommandExceptionType {
private final Function<Object, Message> function;
public LocalizedDynamicCommandExceptionType(Function<Object, Message> function) {
super(function);
this.function = function;
}
@Override
public CommandSyntaxException create(final Object arg) {
return new LocalizedCommandSyntaxException(this, function.apply(arg));
}
@Override
public CommandSyntaxException createWithContext(final ImmutableStringReader reader, final Object arg) {
return new LocalizedCommandSyntaxException(this, function.apply(arg), reader.getString(), reader.getCursor());
}
}

View File

@@ -0,0 +1,53 @@
package net.momirealms.craftengine.core.util.snbt.parse;
import com.mojang.brigadier.Message;
import net.momirealms.craftengine.core.plugin.locale.TranslationManager;
import net.momirealms.craftengine.core.util.AdventureHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Optional;
public class LocalizedMessage implements Message {
private final String node;
private final String[] arguments;
public LocalizedMessage(
@NotNull String node,
@Nullable String... arguments
) {
this.node = node;
this.arguments = arguments != null
? Arrays.copyOf(arguments, arguments.length)
: new String[0];
}
@Override
public String getString() {
return generateLocalizedMessage();
}
private String generateLocalizedMessage() {
try {
String rawMessage = Optional.ofNullable(TranslationManager.instance()
.miniMessageTranslation(this.node)).orElse(this.node);
String cleanMessage = AdventureHelper.miniMessage()
.stripTags(rawMessage);
for (int i = 0; i < arguments.length; i++) {
cleanMessage = cleanMessage.replace(
"<arg:" + i + ">",
arguments[i] != null ? arguments[i] : "null"
);
}
return cleanMessage;
} catch (Exception e) {
return String.format(
"Failed to translate. Node: %s, Arguments: %s. Cause: %s",
node,
Arrays.toString(arguments),
e.getMessage()
);
}
}
}

Some files were not shown because too many files have changed in this diff Show More