From d9bf33ce119e0049ff9936de444723245ece941f Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Thu, 3 Jul 2025 05:55:57 +0800 Subject: [PATCH] =?UTF-8?q?=E7=9B=94=E7=94=B2=E9=87=8D=E6=9E=84part1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../projectile/BukkitProjectileManager.java | 27 +- .../bukkit/item/BukkitCustomItem.java | 1 + .../bukkit/item/BukkitItemManager.java | 82 +++- .../factory/ComponentItemFactory1_21_2.java | 22 +- .../reflection/minecraft/CoreReflections.java | 33 ++ .../reflection/minecraft/MRegistries.java | 5 + .../bukkit/sound/BukkitSoundManager.java | 19 +- common-files/src/main/resources/config.yml | 4 + .../resources/default/configuration/items.yml | 181 +++------ .../default/configuration/templates.yml | 131 ++++++- .../src/main/resources/translations/en.yml | 9 +- .../src/main/resources/translations/zh_cn.yml | 4 +- .../core/item/AbstractItemManager.java | 87 +++-- .../craftengine/core/item/ItemManager.java | 8 +- .../craftengine/core/item/ItemSettings.java | 47 ++- .../equipment/ComponentBasedEquipment.java | 35 +- .../core/item/equipment/Equipment.java | 5 +- .../equipment}/EquipmentLayerType.java | 2 +- .../core/item/equipment/Equipments.java | 17 +- .../item/equipment/TrimBasedEquipment.java | 26 +- .../modifier/EquippableAssetIdModifier.java | 51 +++ .../core/item/setting/EquipmentData.java | 4 +- .../core/item/setting/ItemEquipment.java | 34 +- .../core/pack/AbstractPackManager.java | 355 +++++++++++++++--- .../craftengine/core/pack/misc/Equipment.java | 51 --- .../core/pack/revision/Revisions.java | 1 + .../craftengine/core/plugin/CraftEngine.java | 2 + .../core/plugin/config/Config.java | 36 ++ .../core/plugin/config/ConfigParser.java | 6 + .../craftengine/core/util/Tristate.java | 8 + 30 files changed, 931 insertions(+), 362 deletions(-) rename core/src/main/java/net/momirealms/craftengine/core/{pack/misc => item/equipment}/EquipmentLayerType.java (95%) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/item/modifier/EquippableAssetIdModifier.java delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/pack/misc/Equipment.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/projectile/BukkitProjectileManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/projectile/BukkitProjectileManager.java index 1bdb20096..84af9f97c 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/projectile/BukkitProjectileManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/projectile/BukkitProjectileManager.java @@ -15,10 +15,7 @@ import net.momirealms.craftengine.core.util.VersionHelper; import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.enchantments.Enchantment; -import org.bukkit.entity.Arrow; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Projectile; -import org.bukkit.entity.ThrowableProjectile; +import org.bukkit.entity.*; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.HandlerList; @@ -32,6 +29,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; public class BukkitProjectileManager implements Listener, ProjectileManager { private static BukkitProjectileManager instance; @@ -47,11 +45,22 @@ public class BukkitProjectileManager implements Listener, ProjectileManager { @Override public void delayedInit() { Bukkit.getPluginManager().registerEvents(this, this.plugin.javaPlugin()); - for (World world : Bukkit.getWorlds()) { - List entities = world.getEntities(); - for (Entity entity : entities) { - if (entity instanceof Projectile projectile) { - handleProjectileLoad(projectile); + if (VersionHelper.isFolia()) { + for (World world : Bukkit.getWorlds()) { + List entities = world.getEntities(); + for (Entity entity : entities) { + if (entity instanceof Projectile projectile) { + projectile.getScheduler().run(this.plugin.javaPlugin(), (t) -> handleProjectileLoad(projectile), () -> {}); + } + } + } + } else { + for (World world : Bukkit.getWorlds()) { + List entities = world.getEntities(); + for (Entity entity : entities) { + if (entity instanceof Projectile projectile) { + handleProjectileLoad(projectile); + } } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitCustomItem.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitCustomItem.java index 40f031022..71aa3f6d1 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitCustomItem.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitCustomItem.java @@ -149,6 +149,7 @@ public class BukkitCustomItem extends AbstractCustomItem { @Override public CustomItem build() { this.modifiers.addAll(this.settings.modifiers()); + this.clientBoundModifiers.addAll(this.settings.clientBoundModifiers()); return new BukkitCustomItem(this.id, this.item, this.clientBoundItem, this.itemKey, this.clientBoundItemKey, List.copyOf(this.behaviors), List.copyOf(this.modifiers), List.copyOf(this.clientBoundModifiers), this.settings, this.events); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java index ab481abad..90da4c838 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/BukkitItemManager.java @@ -11,11 +11,14 @@ import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBuiltInRegistries; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MRegistries; +import net.momirealms.craftengine.bukkit.util.ComponentUtils; import net.momirealms.craftengine.bukkit.util.ItemUtils; import net.momirealms.craftengine.bukkit.util.KeyUtils; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.item.*; +import net.momirealms.craftengine.core.item.equipment.TrimBasedEquipment; import net.momirealms.craftengine.core.item.modifier.IdModifier; +import net.momirealms.craftengine.core.pack.AbstractPackManager; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; @@ -33,9 +36,7 @@ import org.bukkit.event.HandlerList; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.Optional; -import java.util.Set; +import java.util.*; public class BukkitItemManager extends AbstractItemManager { static { @@ -50,6 +51,8 @@ public class BukkitItemManager extends AbstractItemManager { private final DebugStickListener debugStickListener; private final ArmorEventListener armorEventListener; private final NetworkItemHandler networkItemHandler; + private final Object bedrockItemHolder; + private boolean registeredTrimMaterial; public BukkitItemManager(BukkitCraftEngine plugin) { super(plugin); @@ -61,6 +64,7 @@ public class BukkitItemManager extends AbstractItemManager { this.armorEventListener = new ArmorEventListener(); this.networkItemHandler = VersionHelper.isOrAbove1_20_5() ? new ModernNetworkItemHandler() : new LegacyNetworkItemHandler(); this.registerAllVanillaItems(); + this.bedrockItemHolder = FastNMS.INSTANCE.method$Registry$getHolderByResourceKey(MBuiltInRegistries.ITEM, FastNMS.INSTANCE.method$ResourceKey$create(MRegistries.ITEM, KeyUtils.toResourceLocation(Key.of("minecraft:bedrock")))).get();; } @Override @@ -134,6 +138,78 @@ public class BukkitItemManager extends AbstractItemManager { HandlerList.unregisterAll(this.armorEventListener); } + @Override + protected void registerArmorTrimPattern(Collection equipments) { + if (equipments.isEmpty()) return; + this.registerCustomTrimMaterial(); + Object registry = FastNMS.INSTANCE.method$RegistryAccess$lookupOrThrow(FastNMS.INSTANCE.registryAccess(), MRegistries.TRIM_PATTERN); + try { + CoreReflections.field$MappedRegistry$frozen.set(registry, false); + for (TrimBasedEquipment equipment : equipments) { + Object resourceLocation = KeyUtils.toResourceLocation(equipment.assetId()); + Object previous = FastNMS.INSTANCE.method$Registry$getValue(registry, resourceLocation); + if (previous == null) { + Object trimPattern = createTrimPattern(equipment.assetId()); + Object holder = CoreReflections.method$Registry$registerForHolder.invoke(null, registry, resourceLocation, trimPattern); + CoreReflections.method$Holder$Reference$bindValue.invoke(holder, trimPattern); + CoreReflections.field$Holder$Reference$tags.set(holder, Set.of()); + } + } + } catch (Exception e) { + this.plugin.logger().warn("Failed to register armor trim pattern.", e); + } finally { + try { + CoreReflections.field$MappedRegistry$frozen.set(registry, true); + } catch (ReflectiveOperationException ignored) { + } + } + } + + private void registerCustomTrimMaterial() { + if (this.registeredTrimMaterial) return; + Object registry = FastNMS.INSTANCE.method$RegistryAccess$lookupOrThrow(FastNMS.INSTANCE.registryAccess(), MRegistries.TRIM_MATERIAL); + Object resourceLocation = KeyUtils.toResourceLocation(Key.of("minecraft", AbstractPackManager.TRIM_MATERIAL)); + Object previous = FastNMS.INSTANCE.method$Registry$getValue(registry, resourceLocation); + if (previous == null) { + try { + CoreReflections.field$MappedRegistry$frozen.set(registry, false); + Object trimMaterial = createTrimMaterial(); + Object holder = CoreReflections.method$Registry$registerForHolder.invoke(null, registry, resourceLocation, trimMaterial); + CoreReflections.method$Holder$Reference$bindValue.invoke(holder, trimMaterial); + CoreReflections.field$Holder$Reference$tags.set(holder, Set.of()); + } catch (Exception e) { + this.plugin.logger().warn("Failed to register trim material.", e); + } finally { + try { + CoreReflections.field$MappedRegistry$frozen.set(registry, true); + } catch (ReflectiveOperationException ignored) { + } + } + } + this.registeredTrimMaterial = true; + } + + private Object createTrimPattern(Key key) throws ReflectiveOperationException { + if (VersionHelper.isOrAbove1_21_5()) { + return CoreReflections.constructor$TrimPattern.newInstance(KeyUtils.toResourceLocation(key), CoreReflections.instance$Component$empty, false); + } else if (VersionHelper.isOrAbove1_20_2()) { + return CoreReflections.constructor$TrimPattern.newInstance(KeyUtils.toResourceLocation(key), this.bedrockItemHolder, CoreReflections.instance$Component$empty, false); + } else { + return CoreReflections.constructor$TrimPattern.newInstance(KeyUtils.toResourceLocation(key), this.bedrockItemHolder, CoreReflections.instance$Component$empty); + } + } + + private Object createTrimMaterial() throws ReflectiveOperationException { + if (VersionHelper.isOrAbove1_21_5()) { + Object assetGroup = CoreReflections.method$MaterialAssetGroup$create.invoke(null, "custom"); + return CoreReflections.constructor$TrimMaterial.newInstance(assetGroup, CoreReflections.instance$Component$empty); + } else if (VersionHelper.isOrAbove1_21_4()) { + return CoreReflections.constructor$TrimMaterial.newInstance("custom", this.bedrockItemHolder, Map.of(), CoreReflections.instance$Component$empty); + } else { + return CoreReflections.constructor$TrimMaterial.newInstance("custom", this.bedrockItemHolder, 1_000_000.0f, Map.of(), CoreReflections.instance$Component$empty); + } + } + @SuppressWarnings("deprecation") @Override public Item fromByteArray(byte[] bytes) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_2.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_2.java index 254aeff5e..42d08779b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_2.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/factory/ComponentItemFactory1_21_2.java @@ -2,9 +2,17 @@ package net.momirealms.craftengine.bukkit.item.factory; import net.momirealms.craftengine.bukkit.item.ComponentItemWrapper; import net.momirealms.craftengine.bukkit.item.ComponentTypes; +import net.momirealms.craftengine.core.entity.EquipmentSlot; +import net.momirealms.craftengine.core.item.equipment.Equipments; import net.momirealms.craftengine.core.item.setting.EquipmentData; import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.sparrow.nbt.CompoundTag; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; import java.util.Optional; public class ComponentItemFactory1_21_2 extends ComponentItemFactory1_21 { @@ -52,6 +60,18 @@ public class ComponentItemFactory1_21_2 extends ComponentItemFactory1_21 { @Override protected Optional equippable(ComponentItemWrapper item) { - throw new UnsupportedOperationException("Not implemented yet."); + Optional optionalData = item.getJavaComponent(ComponentTypes.EQUIPPABLE); + if (optionalData.isEmpty()) return Optional.empty(); + Map data = MiscUtils.castToMap(optionalData.get(), false); + String slot = data.get("slot").toString(); + return Optional.of(new EquipmentData( + EquipmentSlot.valueOf(slot.toUpperCase(Locale.ENGLISH)), + data.containsKey("asset_id") ? Key.of((String) data.get("asset_id")) : null, + (boolean) data.getOrDefault("dispensable", true), + (boolean) data.getOrDefault("swappable", true), + (boolean) data.getOrDefault("damage_on_hurt", true), + (boolean) data.getOrDefault("equip_on_interact", false), + data.containsKey("camera_overlay") ? Key.of((String) data.get("camera_overlay")) : null + )); } } \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index 789706233..3e66af812 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -3588,12 +3588,37 @@ public final class CoreReflections { ); public static final Class clazz$TrimPattern = requireNonNull( + VersionHelper.isOrAbove1_21_2() ? BukkitReflectionUtils.findReobfOrMojmapClass( "world.item.equipment.trim.TrimPattern", "world.item.equipment.trim.TrimPattern" + ) : + BukkitReflectionUtils.findReobfOrMojmapClass( + "world.item.armortrim.TrimPattern", + "world.item.armortrim.TrimPattern" ) ); + public static final Class clazz$TrimMaterial = requireNonNull( + VersionHelper.isOrAbove1_21_2() ? + BukkitReflectionUtils.findReobfOrMojmapClass( + "world.item.equipment.trim.TrimMaterial", + "world.item.equipment.trim.TrimMaterial" + ) : + BukkitReflectionUtils.findReobfOrMojmapClass( + "world.item.armortrim.TrimMaterial", + "world.item.armortrim.TrimMaterial" + ) + ); + + public static final Class clazz$MaterialAssetGroup = BukkitReflectionUtils.findReobfOrMojmapClass( + "world.item.equipment.trim.MaterialAssetGroup", + "world.item.equipment.trim.MaterialAssetGroup" + ); + + public static final Method method$MaterialAssetGroup$create = Optional.ofNullable(clazz$MaterialAssetGroup) + .map(it -> ReflectionUtils.getStaticMethod(it, it, String.class)).orElse(null); + public static final Constructor constructor$TrimPattern = requireNonNull( VersionHelper.isOrAbove1_21_5() ? ReflectionUtils.getConstructor(clazz$TrimPattern, clazz$ResourceLocation, clazz$Component, boolean.class) : @@ -3601,4 +3626,12 @@ public final class CoreReflections { ReflectionUtils.getConstructor(clazz$TrimPattern, clazz$ResourceLocation, clazz$Holder, clazz$Component, boolean.class) : ReflectionUtils.getConstructor(clazz$TrimPattern, clazz$ResourceLocation, clazz$Holder, clazz$Component) ); + + public static final Constructor constructor$TrimMaterial = requireNonNull( + VersionHelper.isOrAbove1_21_5() ? + ReflectionUtils.getConstructor(clazz$TrimMaterial, clazz$MaterialAssetGroup, clazz$Component) : + VersionHelper.isOrAbove1_21_4() ? + ReflectionUtils.getConstructor(clazz$TrimMaterial, String.class, clazz$Holder, Map.class, clazz$Component) : + ReflectionUtils.getConstructor(clazz$TrimMaterial, String.class, clazz$Holder, float.class, Map.class, clazz$Component) + ); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MRegistries.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MRegistries.java index 9df179998..ad3d3f545 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MRegistries.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MRegistries.java @@ -24,6 +24,7 @@ public final class MRegistries { public static final Object CONFIGURED_FEATURE; public static final Object PLACED_FEATURE; public static final Object TRIM_PATTERN; + public static final Object TRIM_MATERIAL; @Nullable // 1.21+ public static final Object JUKEBOX_SONG; @Nullable // 1.21+ @@ -48,6 +49,7 @@ public final class MRegistries { Object registries$JukeboxSong = null; Object registries$Recipe = null; Object registries$TrimPattern = null; + Object registries$TrimMaterial = null; for (Field field : fields) { Type fieldType = field.getGenericType(); if (fieldType instanceof ParameterizedType paramType) { @@ -91,6 +93,8 @@ public final class MRegistries { registries$PlacedFeature = field.get(null); } else if (type == CoreReflections.clazz$TrimPattern) { registries$TrimPattern = field.get(null); + } else if (type == CoreReflections.clazz$TrimMaterial) { + registries$TrimMaterial = field.get(null); } } } @@ -111,6 +115,7 @@ public final class MRegistries { CONFIGURED_FEATURE = requireNonNull(registries$ConfiguredFeature); PLACED_FEATURE = requireNonNull(registries$PlacedFeature); TRIM_PATTERN = requireNonNull(registries$TrimPattern); + TRIM_MATERIAL = requireNonNull(registries$TrimMaterial); JUKEBOX_SONG = registries$JukeboxSong; RECIPE = registries$Recipe; } catch (ReflectiveOperationException e) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/sound/BukkitSoundManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/sound/BukkitSoundManager.java index aed458b8e..5b15587d7 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/sound/BukkitSoundManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/sound/BukkitSoundManager.java @@ -30,10 +30,10 @@ public class BukkitSoundManager extends AbstractSoundManager { @Override protected void registerSongs(Map songs) { if (songs.isEmpty()) return; + Object registry = FastNMS.INSTANCE.method$RegistryAccess$lookupOrThrow(FastNMS.INSTANCE.registryAccess(), MRegistries.JUKEBOX_SONG); try { // 获取 JUKEBOX_SONG 注册表 - Object registry = FastNMS.INSTANCE.method$RegistryAccess$lookupOrThrow(FastNMS.INSTANCE.registryAccess(), MRegistries.JUKEBOX_SONG); - unfreezeRegistry(registry); + CoreReflections.field$MappedRegistry$frozen.set(registry, false); for (Map.Entry entry : songs.entrySet()) { Key id = entry.getKey(); JukeboxSong jukeboxSong = entry.getValue(); @@ -54,17 +54,12 @@ public class BukkitSoundManager extends AbstractSoundManager { CoreReflections.field$Holder$Reference$tags.set(holder, Set.of()); } } - freezeRegistry(registry); } catch (Exception e) { - plugin.logger().warn("Failed to register jukebox songs.", e); + this.plugin.logger().warn("Failed to register jukebox songs.", e); + } finally { + try { + CoreReflections.field$MappedRegistry$frozen.set(registry, true); + } catch (ReflectiveOperationException ignored) {} } } - - private void unfreezeRegistry(Object registry) throws IllegalAccessException { - CoreReflections.field$MappedRegistry$frozen.set(registry, false); - } - - private void freezeRegistry(Object registry) throws IllegalAccessException { - CoreReflections.field$MappedRegistry$frozen.set(registry, true); - } } diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index b4ac8a596..08879aeec 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -138,6 +138,10 @@ resource-pack: resolution: type: merge_atlas +item: + # Make custom-model-data and item-model clientside by default + client-bound-model: false + equipment: # The sacrificed-vanilla-armor argument determines which vanilla armor gets completely removed (loses all its trims) # when you create new armor using trim types, freeing up a slot for your custom armor. diff --git a/common-files/src/main/resources/resources/default/configuration/items.yml b/common-files/src/main/resources/resources/default/configuration/items.yml index 6018cc3c2..4e28a3ee9 100644 --- a/common-files/src/main/resources/resources/default/configuration/items.yml +++ b/common-files/src/main/resources/resources/default/configuration/items.yml @@ -153,27 +153,6 @@ items#topaz_gears: template: default:model/simplified_handheld arguments: path: minecraft:item/custom/topaz_sword - $$>=1.21.2#armor: - default:topaz_helmet: - template: default:armor/topaz - arguments: - part: helmet - slot: head - default:topaz_chestplate: - template: default:armor/topaz - arguments: - part: chestplate - slot: chest - default:topaz_leggings: - template: default:armor/topaz - arguments: - part: leggings - slot: legs - default:topaz_boots: - template: default:armor/topaz - arguments: - part: boots - slot: feet $$>=1.21.4#topaz_trident: default:topaz_trident: material: trident @@ -288,6 +267,38 @@ items#topaz_gears: tints: - type: minecraft:dye default: -6265536 + default:topaz_helmet: + template: + - default:armor/topaz + - default:model/armor_trim + arguments: + part: helmet + slot: head + material: topaz + default:topaz_chestplate: + template: + - default:armor/topaz + - default:model/armor_trim + arguments: + part: chestplate + slot: chest + material: topaz + default:topaz_leggings: + template: + - default:armor/topaz + - default:model/armor_trim + arguments: + part: leggings + slot: legs + material: topaz + default:topaz_boots: + template: + - default:armor/topaz + - default:model/armor_trim + arguments: + part: boots + slot: feet + material: topaz templates: default:armor/topaz: material: chainmail_${part} @@ -298,122 +309,18 @@ templates: settings: tags: - default:topaz_tools - equippable: - slot: ${slot} - asset-id: topaz - humanoid: minecraft:topaz - humanoid-leggings: minecraft:topaz - model: - type: minecraft:select - property: minecraft:trim_material - fallback: - type: minecraft:model - path: minecraft:item/custom/topaz_${part} - generation: - parent: minecraft:item/generated - textures: - layer0: minecraft:item/custom/topaz_${part} - cases: - - when: minecraft:quartz - model: - type: minecraft:model - path: minecraft:item/custom/topaz_${part}_quartz_trim - generation: - parent: minecraft:item/generated - textures: - layer0: minecraft:item/custom/topaz_${part} - layer1: minecraft:trims/items/${part}_trim_quartz - - when: minecraft:iron - model: - type: minecraft:model - path: minecraft:item/custom/topaz_${part}_iron_trim - generation: - parent: minecraft:item/generated - textures: - layer0: minecraft:item/custom/topaz_${part} - layer1: minecraft:trims/items/${part}_trim_iron - - when: minecraft:netherite - model: - type: minecraft:model - path: minecraft:item/custom/topaz_${part}_netherite_trim - generation: - parent: minecraft:item/generated - textures: - layer0: minecraft:item/custom/topaz_${part} - layer1: minecraft:trims/items/${part}_trim_netherite - - when: minecraft:redstone - model: - type: minecraft:model - path: minecraft:item/custom/topaz_${part}_redstone_trim - generation: - parent: minecraft:item/generated - textures: - layer0: minecraft:item/custom/topaz_${part} - layer1: minecraft:trims/items/${part}_trim_redstone - - when: minecraft:copper - model: - type: minecraft:model - path: minecraft:item/custom/topaz_${part}_copper_trim - generation: - parent: minecraft:item/generated - textures: - layer0: minecraft:item/custom/topaz_${part} - layer1: minecraft:trims/items/${part}_trim_copper - - when: minecraft:gold - model: - type: minecraft:model - path: minecraft:item/custom/topaz_${part}_gold_trim - generation: - parent: minecraft:item/generated - textures: - layer0: minecraft:item/custom/topaz_${part} - layer1: minecraft:trims/items/${part}_trim_gold - - when: minecraft:emerald - model: - type: minecraft:model - path: minecraft:item/custom/topaz_${part}_emerald_trim - generation: - parent: minecraft:item/generated - textures: - layer0: minecraft:item/custom/topaz_${part} - layer1: minecraft:trims/items/${part}_trim_emerald - - when: minecraft:diamond - model: - type: minecraft:model - path: minecraft:item/custom/topaz_${part}_diamond_trim - generation: - parent: minecraft:item/generated - textures: - layer0: minecraft:item/custom/topaz_${part} - layer1: minecraft:trims/items/${part}_trim_diamond - - when: minecraft:lapis - model: - type: minecraft:model - path: minecraft:item/custom/topaz_${part}_lapis_trim - generation: - parent: minecraft:item/generated - textures: - layer0: minecraft:item/custom/topaz_${part} - layer1: minecraft:trims/items/${part}_trim_lapis - - when: minecraft:amethyst - model: - type: minecraft:model - path: minecraft:item/custom/topaz_${part}_amethyst_trim - generation: - parent: minecraft:item/generated - textures: - layer0: minecraft:item/custom/topaz_${part} - layer1: minecraft:trims/items/${part}_trim_amethyst - - when: minecraft:resin - model: - type: minecraft:model - path: minecraft:item/custom/topaz_${part}_resin_trim - generation: - parent: minecraft:item/generated - textures: - layer0: minecraft:item/custom/topaz_${part} - layer1: minecraft:trims/items/${part}_trim_resin -recipes#topaz_gears: + equipment: + asset-id: default:topaz + properties: + slot: ${slot} +equipments#topaz: + default:topaz: + type: + $$>=1.21.2: component + $$<1.21.2: trim + humanoid: minecraft:entity/equipment/humanoid/topaz + humanoid-leggings: minecraft:entity/equipment/humanoid_leggings/topaz +recipes#topaz: default:topaz_shovel: type: shaped pattern: diff --git a/common-files/src/main/resources/resources/default/configuration/templates.yml b/common-files/src/main/resources/resources/default/configuration/templates.yml index 57f5fd033..9d6e1e4c4 100644 --- a/common-files/src/main/resources/resources/default/configuration/templates.yml +++ b/common-files/src/main/resources/resources/default/configuration/templates.yml @@ -1,22 +1,5 @@ # This file contains some useful template data. If you have good ideas, you are welcome to contribute your template! -# These templates let you ditch the real custom_model_data on the server side. -# Instead, we use client side components sent via packets to control how items look. -default:item/client_bound_custom_model_data: - custom-model-data: ${custom_model_data} - data: - remove-components: - - minecraft:custom_model_data - client-bound-data: - custom-model-data: ${custom_model_data} -default:item/client_bound_item_model: - item-model: ${item_model} - data: - remove-components: - - minecraft:item_model - client-bound-data: - item-model: ${item_model} - # blocks templates#models#block: # template: default:model/cube_all @@ -162,6 +145,120 @@ templates#models#2d: texture: ${path} broken_model: ${broken_path} broken_texture: ${broken_path} + # template: default:model/armor_trim + # arguments: + # material: armor material type + # part: slot type + default:model/armor_trim: + type: minecraft:select + property: minecraft:trim_material + fallback: + type: minecraft:model + path: minecraft:item/custom/${material}_${part} + generation: + parent: minecraft:item/generated + textures: + layer0: minecraft:item/custom/${material}_${part} + cases: + - when: minecraft:quartz + model: + type: minecraft:model + path: minecraft:item/custom/${material}_${part}_quartz_trim + generation: + parent: minecraft:item/generated + textures: + layer0: minecraft:item/custom/${material}_${part} + layer1: minecraft:trims/items/${part}_trim_quartz + - when: minecraft:iron + model: + type: minecraft:model + path: minecraft:item/custom/${material}_${part}_iron_trim + generation: + parent: minecraft:item/generated + textures: + layer0: minecraft:item/custom/${material}_${part} + layer1: minecraft:trims/items/${part}_trim_iron + - when: minecraft:netherite + model: + type: minecraft:model + path: minecraft:item/custom/${material}_${part}_netherite_trim + generation: + parent: minecraft:item/generated + textures: + layer0: minecraft:item/custom/${material}_${part} + layer1: minecraft:trims/items/${part}_trim_netherite + - when: minecraft:redstone + model: + type: minecraft:model + path: minecraft:item/custom/${material}_${part}_redstone_trim + generation: + parent: minecraft:item/generated + textures: + layer0: minecraft:item/custom/${material}_${part} + layer1: minecraft:trims/items/${part}_trim_redstone + - when: minecraft:copper + model: + type: minecraft:model + path: minecraft:item/custom/${material}_${part}_copper_trim + generation: + parent: minecraft:item/generated + textures: + layer0: minecraft:item/custom/${material}_${part} + layer1: minecraft:trims/items/${part}_trim_copper + - when: minecraft:gold + model: + type: minecraft:model + path: minecraft:item/custom/${material}_${part}_gold_trim + generation: + parent: minecraft:item/generated + textures: + layer0: minecraft:item/custom/${material}_${part} + layer1: minecraft:trims/items/${part}_trim_gold + - when: minecraft:emerald + model: + type: minecraft:model + path: minecraft:item/custom/${material}_${part}_emerald_trim + generation: + parent: minecraft:item/generated + textures: + layer0: minecraft:item/custom/${material}_${part} + layer1: minecraft:trims/items/${part}_trim_emerald + - when: minecraft:diamond + model: + type: minecraft:model + path: minecraft:item/custom/${material}_${part}_diamond_trim + generation: + parent: minecraft:item/generated + textures: + layer0: minecraft:item/custom/${material}_${part} + layer1: minecraft:trims/items/${part}_trim_diamond + - when: minecraft:lapis + model: + type: minecraft:model + path: minecraft:item/custom/${material}_${part}_lapis_trim + generation: + parent: minecraft:item/generated + textures: + layer0: minecraft:item/custom/${material}_${part} + layer1: minecraft:trims/items/${part}_trim_lapis + - when: minecraft:amethyst + model: + type: minecraft:model + path: minecraft:item/custom/${material}_${part}_amethyst_trim + generation: + parent: minecraft:item/generated + textures: + layer0: minecraft:item/custom/${material}_${part} + layer1: minecraft:trims/items/${part}_trim_amethyst + - when: minecraft:resin + model: + type: minecraft:model + path: minecraft:item/custom/${material}_${part}_resin_trim + generation: + parent: minecraft:item/generated + textures: + layer0: minecraft:item/custom/${material}_${part} + layer1: minecraft:trims/items/${part}_trim_resin # shield templates#models#shield: diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 65f820e64..d20276d63 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -155,7 +155,9 @@ warning.config.furniture.hitbox.custom.invalid_entity: "Issue found in f warning.config.item.duplicate: "Issue found in file - Duplicated item ''. Please check if there is the same configuration in other files." warning.config.item.settings.unknown: "Issue found in file - The item '' is using an unknown setting type ''." warning.config.item.settings.invulnerable.invalid_damage_source: "Issue found in file - The item '' is using an unknown damage source ''. Allowed sources: []." -warning.config.item.settings.equippable.missing_slot: "Issue found in file - The item '' is missing the required 'slot' argument for 'equippable' settings." +warning.config.item.settings.equipment.missing_asset_id: "Issue found in file - The item '' is missing the required 'asset-id' argument for 'equipment' settings." +warning.config.item.settings.equipment.invalid_asset_id: "Issue found in file - The item '' is using an invalid 'asset-id' argument for 'equipment' settings. This might be because you haven't created this equipment configuration or misspelled the asset-id." +warning.config.item.settings.projectile.missing_item: "Issue found in file - The item '' is missing the required 'item' argument for 'projectile' settings." warning.config.item.missing_material: "Issue found in file - The item '' is missing the required 'material' argument." warning.config.item.invalid_material: "Issue found in file - The item '' is using an invalid material type ''." warning.config.item.invalid_custom_model_data: "Issue found in file - The item '' is using a negative custom model data '' which is invalid." @@ -387,4 +389,7 @@ warning.config.resource_pack.generation.missing_item_model: "Item 'Block state '' is missing model file: ''" warning.config.resource_pack.generation.missing_parent_model: "Model '' cannot find parent model: ''" warning.config.resource_pack.generation.malformatted_json: "Json file '' is malformatted." -warning.config.resource_pack.invalid_overlay_format: "Issue found in config.yml at 'resource-pack.overlay-format' - Invalid overlay format ''. Overlay format must contain the placeholder '{version}'." \ No newline at end of file +warning.config.resource_pack.invalid_overlay_format: "Issue found in config.yml at 'resource-pack.overlay-format' - Invalid overlay format ''. Overlay format must contain the placeholder '{version}'." +warning.config.equipment.duplicate: "" +warning.config.equipment.missing_type: "" +warning.config.equipment.invalid_type: "" \ No newline at end of file diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index ba3574bf3..9107fa290 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -156,7 +156,9 @@ warning.config.furniture.hitbox.custom.invalid_entity: "在文件 在文件 发现问题 - 重复的物品 '' 请检查其他文件中是否存在相同配置" warning.config.item.settings.unknown: "在文件 发现问题 - 物品 '' 使用了未知的设置类型 ''" warning.config.item.settings.invulnerable.invalid_damage_source: "在文件 发现问题 - 物品 '' 物品使用了未知的伤害来源类型 '' 允许的来源: []" -warning.config.item.settings.equippable.missing_slot: "在文件 发现问题 - 物品 '' 缺少了 'equippable' 设置所需的 'slot' 参数." +warning.config.item.settings.equipment.missing_asset_id: "在文件 发现问题 - 物品 '' 缺少 'equipment' 设置所需的 'asset-id' 参数." +warning.config.item.settings.equipment.invalid_asset_id: "在文件 发现问题 - 物品 '' 为 'equipment' 设置配置了无效的 'asset-id'. 这可能是因为你没有创建装备配置或是错误地拼写了 asset-id." +warning.config.item.settings.projectile.missing_item: "在文件 发现问题 - 物品 '' 缺少 'projectile' 设置所需的 'item' 参数." warning.config.item.missing_material: "在文件 发现问题 - 物品 '' 缺少必需的 'material' 参数" warning.config.item.invalid_material: "在文件 发现问题 - 物品 '' 使用了无效的材料类型 ''" warning.config.item.invalid_custom_model_data: "在文件 发现问题 - 物品 '' 使用了无效的负数模型值 ''." diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java index 6a6e57a04..291a7a4c0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractItemManager.java @@ -4,13 +4,14 @@ import net.momirealms.craftengine.core.item.behavior.ItemBehavior; import net.momirealms.craftengine.core.item.behavior.ItemBehaviors; import net.momirealms.craftengine.core.item.data.Enchantment; import net.momirealms.craftengine.core.item.data.JukeboxPlayable; +import net.momirealms.craftengine.core.item.equipment.Equipment; +import net.momirealms.craftengine.core.item.equipment.Equipments; +import net.momirealms.craftengine.core.item.equipment.TrimBasedEquipment; import net.momirealms.craftengine.core.item.modifier.*; import net.momirealms.craftengine.core.item.setting.EquipmentData; -import net.momirealms.craftengine.core.item.setting.ItemEquipment; import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; import net.momirealms.craftengine.core.pack.ResourceLocation; -import net.momirealms.craftengine.core.pack.misc.Equipment; import net.momirealms.craftengine.core.pack.model.*; import net.momirealms.craftengine.core.pack.model.generation.AbstractModelGenerator; import net.momirealms.craftengine.core.pack.model.generation.ModelGeneration; @@ -22,7 +23,6 @@ import net.momirealms.craftengine.core.plugin.config.ConfigParser; import net.momirealms.craftengine.core.plugin.context.event.EventFunctions; import net.momirealms.craftengine.core.plugin.context.text.TextProvider; import net.momirealms.craftengine.core.plugin.context.text.TextProviders; -import net.momirealms.craftengine.core.plugin.locale.LocalizedException; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.registry.BuiltInRegistries; @@ -49,13 +49,13 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl protected final Map> externalItemProviders = new HashMap<>(); protected final Map>> dataFunctions = new HashMap<>(); protected final Map> customItems = new HashMap<>(); - protected final Map>> customItemTags; - protected final Map> cmdConflictChecker; - protected final Map modernItemModels1_21_4; - protected final Map> modernItemModels1_21_2; - protected final Map> legacyOverrides; - protected final Map> modernOverrides; - protected final Map equipmentsToGenerate; + protected final Map>> customItemTags = new HashMap<>(); + protected final Map> cmdConflictChecker = new HashMap<>(); + protected final Map modernItemModels1_21_4 = new HashMap<>(); + protected final Map> modernItemModels1_21_2 = new HashMap<>(); + protected final Map> legacyOverrides = new HashMap<>(); + protected final Map> modernOverrides = new HashMap<>(); + protected final Map equipments = new HashMap<>(); // Cached command suggestions protected final List cachedSuggestions = new ArrayList<>(); protected final List cachedTotemSuggestions = new ArrayList<>(); @@ -65,13 +65,6 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl this.itemParser = new ItemParser(); this.equipmentParser = new EquipmentParser(); this.registerFunctions(); - this.legacyOverrides = new HashMap<>(); - this.modernOverrides = new HashMap<>(); - this.customItemTags = new HashMap<>(); - this.cmdConflictChecker = new HashMap<>(); - this.modernItemModels1_21_4 = new HashMap<>(); - this.modernItemModels1_21_2 = new HashMap<>(); - this.equipmentsToGenerate = new HashMap<>(); } @Override @@ -127,12 +120,22 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl this.legacyOverrides.clear(); this.modernOverrides.clear(); this.customItemTags.clear(); - this.equipmentsToGenerate.clear(); + this.equipments.clear(); this.cmdConflictChecker.clear(); this.modernItemModels1_21_4.clear(); this.modernItemModels1_21_2.clear(); } + @Override + public Map equipments() { + return Collections.unmodifiableMap(this.equipments); + } + + @Override + public Optional getEquipment(Key key) { + return Optional.ofNullable(this.equipments.get(key)); + } + @Override public Optional> getCustomItem(Key key) { return Optional.ofNullable(this.customItems.get(key)); @@ -156,13 +159,6 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl for (Key tag : tags) { this.customItemTags.computeIfAbsent(tag, k -> new ArrayList<>()).add(customItem.idHolder()); } - // equipment generation - ItemEquipment equipment = customItem.settings().equipment(); - if (equipment != null) { - EquipmentData data = equipment.data(); - Equipment equipmentJson = this.equipmentsToGenerate.computeIfAbsent(data.assetId(), k -> new Equipment()); - equipmentJson.addAll(equipment); - } return true; } @@ -252,11 +248,6 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl return Collections.unmodifiableMap(this.modernOverrides); } - @Override - public Map equipmentsToGenerate() { - return Collections.unmodifiableMap(this.equipmentsToGenerate); - } - @Override public boolean isVanillaItem(Key item) { return VANILLA_ITEMS.contains(item); @@ -264,6 +255,8 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl protected abstract CustomItem.Builder createPlatformItemBuilder(Holder id, Key material, Key clientBoundMaterial); + protected abstract void registerArmorTrimPattern(Collection equipments); + public class EquipmentParser implements ConfigParser { public static final String[] CONFIG_SECTION_NAME = new String[] {"equipments", "equipment"}; @@ -279,8 +272,21 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl @Override public void parseSection(Pack pack, Path path, Key id, Map section) { + if (AbstractItemManager.this.equipments.containsKey(id)) { + throw new LocalizedResourceConfigException("warning.config.equipment.duplicate"); + } + Equipment equipment = Equipments.fromMap(id, section); + AbstractItemManager.this.equipments.put(id, equipment); + } - + @Override + public void postProcess() { + registerArmorTrimPattern( + AbstractItemManager.this.equipments.values().stream() + .filter(TrimBasedEquipment.class::isInstance) + .map(TrimBasedEquipment.class::cast) + .toList() + ); } } @@ -320,6 +326,7 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl Key material = Key.from(isVanillaItem ? id.value() : ResourceConfigUtils.requireNonEmptyStringOrThrow(section.get("material"), "warning.config.item.missing_material").toLowerCase(Locale.ENGLISH)); Key clientBoundMaterial = section.containsKey("client-bound-material") ? Key.from(section.get("client-bound-material").toString().toLowerCase(Locale.ENGLISH)) : material; int customModelData = ResourceConfigUtils.getAsInt(section.getOrDefault("custom-model-data", 0), "custom-model-data"); + boolean clientBoundModel = section.containsKey("client-bound-model") ? ResourceConfigUtils.getAsBoolean(section.get("client-bound-data"), "client-bound-data") : Config.globalClientboundModel(); if (customModelData < 0) { throw new LocalizedResourceConfigException("warning.config.item.invalid_custom_model_data", String.valueOf(customModelData)); } @@ -335,7 +342,11 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl // To get at least one model provider // Sets some basic model info if (customModelData > 0) { - itemBuilder.clientBoundDataModifier(new CustomModelDataModifier<>(customModelData)); + if (clientBoundModel) { + itemBuilder.clientBoundDataModifier(new CustomModelDataModifier<>(customModelData)); + } else { + itemBuilder.dataModifier(new CustomModelDataModifier<>(customModelData)); + } } // Requires the item to have model before apply item-model else if (!hasItemModelSection && section.containsKey("model") && VersionHelper.isOrAbove1_21_2()) { @@ -343,7 +354,11 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl // customize or use the id itemModelKey = Key.from(section.getOrDefault("item-model", id.toString()).toString()); if (ResourceLocation.isValid(itemModelKey.toString())) { - itemBuilder.clientBoundDataModifier(new ItemModelModifier<>(itemModelKey)); + if (clientBoundModel) { + itemBuilder.clientBoundDataModifier(new ItemModelModifier<>(itemModelKey)); + } else { + itemBuilder.dataModifier(new ItemModelModifier<>(itemModelKey)); + } } else { itemModelKey = null; } @@ -351,7 +366,11 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl if (hasItemModelSection && VersionHelper.isOrAbove1_21_2()) { itemModelKey = Key.from(section.get("item-model").toString()); - itemBuilder.clientBoundDataModifier(new ItemModelModifier<>(itemModelKey)); + if (clientBoundModel) { + itemBuilder.clientBoundDataModifier(new ItemModelModifier<>(itemModelKey)); + } else { + itemBuilder.dataModifier(new ItemModelModifier<>(itemModelKey)); + } } // Get item data diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java index 627b4279c..6033ad68f 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemManager.java @@ -2,8 +2,8 @@ package net.momirealms.craftengine.core.item; import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.item.behavior.ItemBehavior; +import net.momirealms.craftengine.core.item.equipment.Equipment; import net.momirealms.craftengine.core.item.modifier.ItemDataModifier; -import net.momirealms.craftengine.core.pack.misc.Equipment; import net.momirealms.craftengine.core.pack.model.LegacyOverridesModel; import net.momirealms.craftengine.core.pack.model.ModernItemModel; import net.momirealms.craftengine.core.pack.model.generation.ModelGenerator; @@ -21,14 +21,14 @@ public interface ItemManager extends Manageable, ModelGenerator { void registerDataType(Function> factory, String... alias); + Map equipments(); + ConfigParser[] parsers(); Map> legacyItemOverrides(); Map> modernItemOverrides(); - Map equipmentsToGenerate(); - Map modernItemModels1_21_4(); Map> modernItemModels1_21_2(); @@ -57,6 +57,8 @@ public interface ItemManager extends Manageable, ModelGenerator { boolean registerExternalItemProvider(ExternalItemProvider externalItemProvider); + Optional getEquipment(Key key); + Optional> getCustomItem(Key key); Optional> getItemBehavior(Key key); diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java index 76a4f9d6e..1a545b8bc 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemSettings.java @@ -5,11 +5,13 @@ import net.momirealms.craftengine.core.entity.ItemDisplayContext; import net.momirealms.craftengine.core.entity.projectile.ProjectileMeta; import net.momirealms.craftengine.core.entity.projectile.ProjectileType; import net.momirealms.craftengine.core.item.equipment.ComponentBasedEquipment; +import net.momirealms.craftengine.core.item.equipment.Equipment; import net.momirealms.craftengine.core.item.modifier.EquippableModifier; import net.momirealms.craftengine.core.item.modifier.FoodModifier; import net.momirealms.craftengine.core.item.modifier.ItemDataModifier; import net.momirealms.craftengine.core.item.setting.*; -import net.momirealms.craftengine.core.pack.misc.EquipmentLayerType; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.sound.SoundData; import net.momirealms.craftengine.core.util.*; @@ -23,8 +25,6 @@ import java.util.stream.Collectors; public class ItemSettings { int fuelTime; Set tags = Set.of(); - @Nullable - ItemEquipment equipment; boolean canRepair = true; List anvilRepairItems = List.of(); boolean renameable = true; @@ -38,13 +38,21 @@ public class ItemSettings { List invulnerable = List.of(); boolean canEnchant = true; float compostProbability= 0.5f; + @Nullable + ItemEquipment equipment; private ItemSettings() {} public List> modifiers() { ArrayList> modifiers = new ArrayList<>(); - if (VersionHelper.isOrAbove1_21_2() && this.equipment != null) { - modifiers.add(new EquippableModifier<>(this.equipment.data())); + if (this.equipment != null) { + EquipmentData data = this.equipment.equipmentData(); + if (data != null) { + modifiers.add(new EquippableModifier<>(data)); + } + if (!this.equipment.clientBoundModel().asBoolean(Config.globalClientboundModel())) { + modifiers.add(this.equipment.equipment().modifier()); + } } if (VersionHelper.isOrAbove1_20_5() && this.foodData != null) { modifiers.add(new FoodModifier<>(this.foodData.nutrition(), this.foodData.saturation(), false)); @@ -52,6 +60,16 @@ public class ItemSettings { return modifiers; } + public List> clientBoundModifiers() { + ArrayList> modifiers = new ArrayList<>(); + if (this.equipment != null) { + if (this.equipment.clientBoundModel().asBoolean(Config.globalClientboundModel())) { + modifiers.add(this.equipment.equipment().modifier()); + } + } + return modifiers; + } + public static ItemSettings of() { return new ItemSettings(); } @@ -302,16 +320,31 @@ public class ItemSettings { Map args = MiscUtils.castToMap(value, false); EquipmentData data = EquipmentData.fromMap(args); ComponentBasedEquipment componentBasedEquipment = ComponentBasedEquipment.FACTORY.create(data.assetId(), args); - ItemEquipment itemEquipment = new ItemEquipment(data, componentBasedEquipment); + ItemEquipment itemEquipment = new ItemEquipment(Tristate.FALSE, data, componentBasedEquipment); return settings -> settings.equipment(itemEquipment); })); + registerFactory("equipment", (value -> { + Map args = MiscUtils.castToMap(value, false); + Tristate clientBoundModel = Tristate.of((Boolean) args.get("client-bound-model")); + Key assetId = Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(args.get("asset-id"), "warning.config.item.settings.equipment.missing_asset_id")); + Optional optionalEquipment = CraftEngine.instance().itemManager().getEquipment(assetId); + if (optionalEquipment.isEmpty()) { + throw new LocalizedResourceConfigException("warning.config.item.settings.equipment.invalid_asset_id"); + } + if (VersionHelper.isOrAbove1_21_2() && args.containsKey("slot")) { + EquipmentData data = EquipmentData.fromMap(args); + return settings -> settings.equipment(new ItemEquipment(clientBoundModel, data, optionalEquipment.get())); + } else { + return settings -> settings.equipment(new ItemEquipment(clientBoundModel, null, optionalEquipment.get())); + } + })); registerFactory("can-place", (value -> { boolean bool = ResourceConfigUtils.getAsBoolean(value, "can-place"); return settings -> settings.canPlaceRelatedVanillaBlock(bool); })); registerFactory("projectile", (value -> { Map args = MiscUtils.castToMap(value, false); - Key customTridentItemId = Key.of(Objects.requireNonNull(args.get("item"), "'item should not be null'").toString()); + 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 = MiscUtils.getAsVector3f(args.getOrDefault("translation", "0"), "translation"); diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/equipment/ComponentBasedEquipment.java b/core/src/main/java/net/momirealms/craftengine/core/item/equipment/ComponentBasedEquipment.java index 1422814da..7c5075eb7 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/equipment/ComponentBasedEquipment.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/equipment/ComponentBasedEquipment.java @@ -1,7 +1,9 @@ package net.momirealms.craftengine.core.item.equipment; +import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import net.momirealms.craftengine.core.pack.misc.EquipmentLayerType; +import net.momirealms.craftengine.core.item.modifier.EquippableAssetIdModifier; +import net.momirealms.craftengine.core.item.modifier.ItemDataModifier; import net.momirealms.craftengine.core.util.Key; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; @@ -14,7 +16,7 @@ import java.util.List; import java.util.Map; import java.util.function.Supplier; -public class ComponentBasedEquipment extends AbstractEquipment { +public class ComponentBasedEquipment extends AbstractEquipment implements Supplier { public static final Factory FACTORY = new Factory(); private final EnumMap> layers; @@ -24,10 +26,15 @@ public class ComponentBasedEquipment extends AbstractEquipment { } @Override - public Key renderingMethod() { + public Key type() { return Equipments.COMPONENT; } + @Override + public ItemDataModifier modifier() { + return new EquippableAssetIdModifier<>(this.assetId); + } + public EnumMap> layers() { return layers; } @@ -36,6 +43,28 @@ public class ComponentBasedEquipment extends AbstractEquipment { this.layers.put(layerType, layer); } + @Override + public JsonObject get() { + JsonObject jsonObject = new JsonObject(); + JsonObject layersJson = new JsonObject(); + jsonObject.add("layers", layersJson); + for (Map.Entry> entry : layers.entrySet()) { + EquipmentLayerType type = entry.getKey(); + List layerList = entry.getValue(); + setLayers(layersJson, layerList, type.id()); + } + return jsonObject; + } + + private void setLayers(JsonObject layersJson, List layers, String key) { + if (layers == null || layers.isEmpty()) return; + JsonArray layersArray = new JsonArray(); + for (ComponentBasedEquipment.Layer layer : layers) { + layersArray.add(layer.get()); + } + layersJson.add(key, layersArray); + } + public static class Factory implements EquipmentFactory { @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/equipment/Equipment.java b/core/src/main/java/net/momirealms/craftengine/core/item/equipment/Equipment.java index 90d3cf4bf..82c26006a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/equipment/Equipment.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/equipment/Equipment.java @@ -1,10 +1,13 @@ package net.momirealms.craftengine.core.item.equipment; +import net.momirealms.craftengine.core.item.modifier.ItemDataModifier; import net.momirealms.craftengine.core.util.Key; public interface Equipment { Key assetId(); - Key renderingMethod(); + Key type(); + + ItemDataModifier modifier(); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/misc/EquipmentLayerType.java b/core/src/main/java/net/momirealms/craftengine/core/item/equipment/EquipmentLayerType.java similarity index 95% rename from core/src/main/java/net/momirealms/craftengine/core/pack/misc/EquipmentLayerType.java rename to core/src/main/java/net/momirealms/craftengine/core/item/equipment/EquipmentLayerType.java index 80ea4cf54..b0a25b94c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/misc/EquipmentLayerType.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/equipment/EquipmentLayerType.java @@ -1,4 +1,4 @@ -package net.momirealms.craftengine.core.pack.misc; +package net.momirealms.craftengine.core.item.equipment; import java.util.HashMap; import java.util.Map; diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/equipment/Equipments.java b/core/src/main/java/net/momirealms/craftengine/core/item/equipment/Equipments.java index a422fd881..6ce7822fd 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/equipment/Equipments.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/equipment/Equipments.java @@ -1,18 +1,23 @@ package net.momirealms.craftengine.core.item.equipment; +import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.registry.BuiltInRegistries; import net.momirealms.craftengine.core.registry.Holder; 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.ResourceConfigUtils; import net.momirealms.craftengine.core.util.ResourceKey; -public class Equipments { +import java.util.Map; + +public final class Equipments { public static final Key TRIM = Key.of("craftengine:trim"); public static final Key COMPONENT = Key.of("craftengine:component"); static { register(TRIM, TrimBasedEquipment.FACTORY); + register(COMPONENT, ComponentBasedEquipment.FACTORY); } public static void register(Key key, EquipmentFactory factory) { @@ -20,4 +25,14 @@ public class Equipments { .registerForHolder(new ResourceKey<>(Registries.EQUIPMENT_FACTORY.location(), key)); holder.bindValue(factory); } + + public static Equipment fromMap(Key id, Map map) { + String type = ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("type"), "warning.config.equipment.missing_type"); + Key key = Key.withDefaultNamespace(type, Key.DEFAULT_NAMESPACE); + EquipmentFactory factory = BuiltInRegistries.EQUIPMENT_FACTORY.getValue(key); + if (factory == null) { + throw new LocalizedResourceConfigException("warning.config.equipment.invalid_type", type); + } + return factory.create(id, map); + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/equipment/TrimBasedEquipment.java b/core/src/main/java/net/momirealms/craftengine/core/item/equipment/TrimBasedEquipment.java index 736315df7..f1536d567 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/equipment/TrimBasedEquipment.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/equipment/TrimBasedEquipment.java @@ -1,42 +1,52 @@ package net.momirealms.craftengine.core.item.equipment; +import net.momirealms.craftengine.core.item.modifier.ItemDataModifier; +import net.momirealms.craftengine.core.item.modifier.TrimModifier; +import net.momirealms.craftengine.core.pack.AbstractPackManager; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import org.jetbrains.annotations.Nullable; import java.util.Map; +import java.util.Optional; public class TrimBasedEquipment extends AbstractEquipment { public static final Factory FACTORY = new Factory(); private final Key humanoid; private final Key humanoidLeggings; - public TrimBasedEquipment(Key assetId, Key humanoid, Key humanoidLeggings) { + public TrimBasedEquipment(Key assetId, @Nullable Key humanoid, @Nullable Key humanoidLeggings) { super(assetId); this.humanoid = humanoid; this.humanoidLeggings = humanoidLeggings; } @Override - public Key renderingMethod() { + public Key type() { return Equipments.TRIM; } + @Nullable public Key humanoid() { return humanoid; } + @Nullable public Key humanoidLeggings() { return humanoidLeggings; } + @Override + public ItemDataModifier modifier() { + return new TrimModifier<>(AbstractPackManager.TRIM_MATERIAL, this.assetId.toString()); + } + public static class Factory implements EquipmentFactory { + @Override public Equipment create(Key id, Map args) { - // todo node - String humanoidId = ResourceConfigUtils.requireNonEmptyStringOrThrow(args.get("humanoid"), ""); - String humanoidLeggingsId = ResourceConfigUtils.requireNonEmptyStringOrThrow(args.get("humanoid-leggings"), ""); - // todo 验证resource location - return new TrimBasedEquipment(id, Key.of(humanoidId), Key.of(humanoidLeggingsId)); + Key humanoidId = Optional.ofNullable((String) args.get("humanoid")).map(Key::of).orElse(null); + Key humanoidLeggingsId = Optional.ofNullable((String) args.get("humanoid-leggings")).map(Key::of).orElse(null); + return new TrimBasedEquipment(id, humanoidId, humanoidLeggingsId); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/EquippableAssetIdModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/EquippableAssetIdModifier.java new file mode 100644 index 000000000..9472dfca9 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/EquippableAssetIdModifier.java @@ -0,0 +1,51 @@ +package net.momirealms.craftengine.core.item.modifier; + +import net.momirealms.craftengine.core.item.ComponentKeys; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.craftengine.core.item.NetworkItemHandler; +import net.momirealms.craftengine.core.item.setting.EquipmentData; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.sparrow.nbt.CompoundTag; +import net.momirealms.sparrow.nbt.Tag; + +import java.util.Optional; + +public class EquippableAssetIdModifier implements ItemDataModifier { + private final Key assetId; + + public EquippableAssetIdModifier(Key assetsId) { + this.assetId = assetsId; + } + + @Override + public String name() { + return "equippable-asset-id"; + } + + @Override + public Item apply(Item item, ItemBuildContext context) { + Optional optionalData = item.equippable(); + optionalData.ifPresent(data -> item.equippable(new EquipmentData( + data.slot(), + this.assetId, + data.dispensable(), + data.swappable(), + data.damageOnHurt(), + data.equipOnInteract(), + data.cameraOverlay() + ))); + return item; + } + + @Override + public Item prepareNetworkItem(Item item, ItemBuildContext context, CompoundTag networkData) { + Tag previous = item.getNBTComponent(ComponentKeys.EQUIPPABLE); + if (previous != null) { + networkData.put(ComponentKeys.EQUIPPABLE.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.ADD, previous)); + } else { + networkData.put(ComponentKeys.EQUIPPABLE.asString(), NetworkItemHandler.pack(NetworkItemHandler.Operation.REMOVE)); + } + return item; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/setting/EquipmentData.java b/core/src/main/java/net/momirealms/craftengine/core/item/setting/EquipmentData.java index 39e4dfacf..01fbe40b3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/setting/EquipmentData.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/setting/EquipmentData.java @@ -9,6 +9,7 @@ import net.momirealms.sparrow.nbt.CompoundTag; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Arrays; import java.util.Locale; import java.util.Map; @@ -44,8 +45,9 @@ public class EquipmentData { public static EquipmentData fromMap(@NotNull final Map data) { String slot = (String) data.get("slot"); if (slot == null) { - throw new LocalizedResourceConfigException("warning.config.item.settings.equippable.missing_slot"); + throw new IllegalArgumentException("slot cannot be null"); } + // todo 重新写判断,不应该支持手部 EquipmentSlot slotEnum = EquipmentSlot.valueOf(slot.toUpperCase(Locale.ENGLISH)); EquipmentData.Builder builder = EquipmentData.builder().slot(slotEnum); if (data.containsKey("asset-id")) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/setting/ItemEquipment.java b/core/src/main/java/net/momirealms/craftengine/core/item/setting/ItemEquipment.java index da8e604b4..05854c47b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/setting/ItemEquipment.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/setting/ItemEquipment.java @@ -1,29 +1,33 @@ package net.momirealms.craftengine.core.item.setting; -import net.momirealms.craftengine.core.item.equipment.ComponentBasedEquipment; -import net.momirealms.craftengine.core.pack.misc.EquipmentLayerType; - -import java.util.EnumMap; -import java.util.List; +import net.momirealms.craftengine.core.item.equipment.Equipment; +import net.momirealms.craftengine.core.util.Tristate; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public class ItemEquipment { - private final EquipmentData data; - private final ComponentBasedEquipment equipment; + private final Tristate clientBoundModel; + private final Equipment equipment; + private final EquipmentData equipmentData; - public ItemEquipment(EquipmentData data, ComponentBasedEquipment equipment) { - this.data = data; + public ItemEquipment(Tristate clientBoundModel, @Nullable EquipmentData equipmentData, Equipment equipment) { + this.clientBoundModel = clientBoundModel; this.equipment = equipment; + this.equipmentData = equipmentData; } - public void addLayer(EquipmentLayerType layerType, List layer) { - this.equipment.addLayer(layerType, layer); + @NotNull + public Equipment equipment() { + return this.equipment; } - public EnumMap> layers() { - return this.equipment.layers(); + @Nullable + public EquipmentData equipmentData() { + return this.equipmentData; } - public EquipmentData data() { - return data; + @NotNull + public Tristate clientBoundModel() { + return clientBoundModel; } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java index eee612041..26ee625fd 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/AbstractPackManager.java @@ -9,12 +9,14 @@ import dev.dejvokep.boostedyaml.YamlDocument; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.momirealms.craftengine.core.font.BitmapImage; import net.momirealms.craftengine.core.font.Font; +import net.momirealms.craftengine.core.item.equipment.ComponentBasedEquipment; +import net.momirealms.craftengine.core.item.equipment.Equipment; +import net.momirealms.craftengine.core.item.equipment.TrimBasedEquipment; import net.momirealms.craftengine.core.pack.conflict.PathContext; import net.momirealms.craftengine.core.pack.conflict.resolution.ResolutionConditional; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.host.ResourcePackHosts; import net.momirealms.craftengine.core.pack.host.impl.NoneHost; -import net.momirealms.craftengine.core.pack.misc.Equipment; import net.momirealms.craftengine.core.pack.model.ItemModel; import net.momirealms.craftengine.core.pack.model.LegacyOverridesModel; import net.momirealms.craftengine.core.pack.model.ModernItemModel; @@ -25,6 +27,7 @@ import net.momirealms.craftengine.core.pack.model.rangedisptach.CustomModelDataR import net.momirealms.craftengine.core.pack.obfuscation.ObfA; import net.momirealms.craftengine.core.pack.obfuscation.ResourcePackGenerationException; import net.momirealms.craftengine.core.pack.revision.Revision; +import net.momirealms.craftengine.core.pack.revision.Revisions; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.config.ConfigParser; @@ -65,9 +68,8 @@ public abstract class AbstractPackManager implements PackManager { public static final Map PRESET_ITEMS = new HashMap<>(); public static final Set VANILLA_TEXTURES = new HashSet<>(); public static final Set VANILLA_MODELS = new HashSet<>(); + public static final String TRIM_MATERIAL = "custom"; private static final byte[] EMPTY_IMAGE; - private static final String[] TRIM_ITEMS = {"boots_trim","chestplate_trim","helmet_trim","leggings_trim"}; - private static final String[] TRIM_COLOR_PALETTES = {"amethyst","copper","diamond","diamond_darker","emerald","gold","gold_darker","iron","iron_darker","lapis","netherite","netherite_darker","quartz","redstone","resin","trim_palette"}; static { var stream = new ByteArrayOutputStream(); try { @@ -545,6 +547,7 @@ public abstract class AbstractPackManager implements PackManager { ConfigParser parser = entry.getKey(); if (!predicate.test(parser)) continue; long t1 = System.nanoTime(); + parser.preProcess(); for (CachedConfigSection cached : entry.getValue()) { for (Map.Entry configEntry : cached.config().entrySet()) { String key = configEntry.getKey(); @@ -578,6 +581,7 @@ public abstract class AbstractPackManager implements PackManager { } } } + 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"); } @@ -632,7 +636,7 @@ public abstract class AbstractPackManager implements PackManager { this.generateOverrideSounds(generatedPackPath); this.generateCustomSounds(generatedPackPath); this.generateClientLang(generatedPackPath); - this.generateEquipments(generatedPackPath); + this.generateEquipments(generatedPackPath, revisions::add); this.generateParticle(generatedPackPath); this.generatePackMetadata(generatedPackPath.resolve("pack.mcmeta"), revisions); if (Config.excludeShaders()) { @@ -1161,74 +1165,315 @@ public abstract class AbstractPackManager implements PackManager { } } - private void generateEquipments(Path generatedPackPath) { - for (Map.Entry entry : this.plugin.itemManager().equipmentsToGenerate().entrySet()) { - Key assetId = entry.getKey(); - if (Config.packMaxVersion().isAtOrAbove(MinecraftVersions.V1_21_4)) { - Path equipmentPath = generatedPackPath - .resolve("assets") - .resolve(assetId.namespace()) - .resolve("equipment") - .resolve(assetId.value() + ".json"); + private void generateEquipments(Path generatedPackPath, Consumer callback) { + // asset id + 是否有上身 + 是否有腿 + List> collectedTrims = new ArrayList<>(); - JsonObject equipmentJson = null; - if (Files.exists(equipmentPath)) { - try (BufferedReader reader = Files.newBufferedReader(equipmentPath)) { - equipmentJson = JsonParser.parseReader(reader).getAsJsonObject(); + // 为trim类型提供的两个兼容性值 + boolean needLegacyCompatibility = Config.packMinVersion().isBelow(MinecraftVersions.V1_21_2); + boolean needModernCompatibility = Config.packMaxVersion().isAtOrAbove(MinecraftVersions.V1_21_2); + + for (Equipment equipment : this.plugin.itemManager().equipments().values()) { + if (equipment instanceof ComponentBasedEquipment componentBasedEquipment) { + // 现代的盔甲生成 + Key assetId = componentBasedEquipment.assetId(); + if (Config.packMaxVersion().isAtOrAbove(MinecraftVersions.V1_21_4)) { + Path equipmentPath = generatedPackPath + .resolve("assets") + .resolve(assetId.namespace()) + .resolve("equipment") + .resolve(assetId.value() + ".json"); + + JsonObject equipmentJson = null; + if (Files.exists(equipmentPath)) { + try (BufferedReader reader = Files.newBufferedReader(equipmentPath)) { + equipmentJson = JsonParser.parseReader(reader).getAsJsonObject(); + } catch (IOException e) { + plugin.logger().warn("Failed to load existing sounds.json", e); + return; + } + } + if (equipmentJson != null) { + equipmentJson = GsonHelper.deepMerge(equipmentJson, componentBasedEquipment.get()); + } else { + equipmentJson = componentBasedEquipment.get(); + } + try { + Files.createDirectories(equipmentPath.getParent()); } catch (IOException e) { - plugin.logger().warn("Failed to load existing sounds.json", e); + plugin.logger().severe("Error creating " + equipmentPath.toAbsolutePath()); return; } + try { + GsonHelper.writeJsonFile(equipmentJson, equipmentPath); + } catch (IOException e) { + this.plugin.logger().severe("Error writing equipment file", e); + } } - if (equipmentJson != null) { - equipmentJson = GsonHelper.deepMerge(equipmentJson, entry.getValue().get()); - } else { - equipmentJson = entry.getValue().get(); + if (Config.packMaxVersion().isAtOrAbove(MinecraftVersions.V1_21_2) && Config.packMinVersion().isBelow(MinecraftVersions.V1_21_4)) { + Path equipmentPath = generatedPackPath + .resolve("assets") + .resolve(assetId.namespace()) + .resolve("models") + .resolve("equipment") + .resolve(assetId.value() + ".json"); + + JsonObject equipmentJson = null; + if (Files.exists(equipmentPath)) { + try (BufferedReader reader = Files.newBufferedReader(equipmentPath)) { + equipmentJson = JsonParser.parseReader(reader).getAsJsonObject(); + } catch (IOException e) { + plugin.logger().warn("Failed to load existing sounds.json", e); + return; + } + } + if (equipmentJson != null) { + equipmentJson = GsonHelper.deepMerge(equipmentJson, componentBasedEquipment.get()); + } else { + equipmentJson = componentBasedEquipment.get(); + } + try { + Files.createDirectories(equipmentPath.getParent()); + } catch (IOException e) { + plugin.logger().severe("Error creating " + equipmentPath.toAbsolutePath()); + return; + } + try { + GsonHelper.writeJsonFile(equipmentJson, equipmentPath); + } catch (IOException e) { + this.plugin.logger().severe("Error writing equipment file", e); + } } + } else if (equipment instanceof TrimBasedEquipment trimBasedEquipment) { + Key assetId = trimBasedEquipment.assetId(); + Key humanoidResourceLocation = trimBasedEquipment.humanoid(); + boolean hasLayer1 = humanoidResourceLocation != null; + Key humanoidLeggingsResourceLocation = trimBasedEquipment.humanoidLeggings(); + boolean hasLayer2 = humanoidLeggingsResourceLocation != null; + + if (hasLayer1) { + Path texture = generatedPackPath + .resolve("assets") + .resolve(humanoidResourceLocation.namespace()) + .resolve("textures") + .resolve(humanoidResourceLocation.value() + ".png"); + if (!Files.exists(texture) || !Files.isRegularFile(texture)) { + // todo 说话 + continue; + } + boolean shouldPreserve = false; + if (needLegacyCompatibility) { + Path legacyTarget = generatedPackPath + .resolve("assets") + .resolve(assetId.namespace()) + .resolve("textures") + .resolve("trims") + .resolve("models") + .resolve("armor") + .resolve(assetId.value() + "_" + TRIM_MATERIAL + ".png"); + if (!legacyTarget.equals(texture)) { + try { + Files.createDirectories(legacyTarget.getParent()); + Files.copy(texture, legacyTarget, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + plugin.logger().severe("Error writing armor texture file from " + texture + " to " + legacyTarget, e); + } + } else { + shouldPreserve = true; + } + } + if (needModernCompatibility) { + Path modernTarget = generatedPackPath + .resolve("assets") + .resolve(assetId.namespace()) + .resolve("textures") + .resolve("trims") + .resolve("entity") + .resolve("humanoid") + .resolve(assetId.value() + "_" + TRIM_MATERIAL + ".png"); + if (!modernTarget.equals(texture)) { + try { + Files.createDirectories(modernTarget.getParent()); + Files.copy(texture, modernTarget, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + plugin.logger().severe("Error writing armor texture file from " + texture + " to " + modernTarget, e); + } + } else { + shouldPreserve = true; + } + } + if (!shouldPreserve) { + try { + Files.delete(texture); + } catch (IOException e) { + this.plugin.logger().severe("Error deleting armor texture file from " + texture, e); + } + } + } + if (hasLayer2) { + Path texture = generatedPackPath + .resolve("assets") + .resolve(humanoidLeggingsResourceLocation.namespace()) + .resolve("textures") + .resolve(humanoidLeggingsResourceLocation.value() + ".png"); + if (!Files.exists(texture) && !Files.isRegularFile(texture)) { + // todo 说话 + continue; + } + boolean shouldPreserve = false; + if (needLegacyCompatibility) { + Path legacyTarget = generatedPackPath + .resolve("assets") + .resolve(assetId.namespace()) + .resolve("textures") + .resolve("trims") + .resolve("models") + .resolve("armor") + .resolve(assetId.value() + "_leggings_" + TRIM_MATERIAL + ".png"); + if (!legacyTarget.equals(texture)) { + try { + Files.createDirectories(legacyTarget.getParent()); + Files.copy(texture, legacyTarget, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + this.plugin.logger().severe("Error writing armor texture file from " + texture + " to " + legacyTarget, e); + } + } else { + shouldPreserve = true; + } + } + if (needModernCompatibility) { + Path modernTarget = generatedPackPath + .resolve("assets") + .resolve(assetId.namespace()) + .resolve("textures") + .resolve("trims") + .resolve("entity") + .resolve("humanoid_leggings") + .resolve(assetId.value() + "_" + TRIM_MATERIAL + ".png"); + if (!modernTarget.equals(texture)) { + try { + Files.createDirectories(modernTarget.getParent()); + Files.copy(texture, modernTarget, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + this.plugin.logger().severe("Error writing armor texture file from " + texture + " to " + modernTarget, e); + } + } else { + shouldPreserve = true; + } + } + if (!shouldPreserve) { + try { + Files.delete(texture); + } catch (IOException e) { + this.plugin.logger().severe("Error deleting armor texture file from " + texture, e); + } + } + } + collectedTrims.add(Tuple.of(assetId, hasLayer1, hasLayer2)); + } + } + + if (!collectedTrims.isEmpty()) { + // 获取基础atlas路径 + Path atlasPath = generatedPackPath + .resolve("assets") + .resolve("minecraft") + .resolve("atlases") + .resolve("armor_trims.json"); + // 读取先前sources内容 + JsonArray previousAtlasSources = null; + if (Files.exists(atlasPath) && Files.isRegularFile(atlasPath)) { try { - Files.createDirectories(equipmentPath.getParent()); - } catch (IOException e) { - plugin.logger().severe("Error creating " + equipmentPath.toAbsolutePath()); - return; - } - try { - GsonHelper.writeJsonFile(equipmentJson, equipmentPath); - } catch (IOException e) { - this.plugin.logger().severe("Error writing equipment file", e); + previousAtlasSources = GsonHelper.readJsonFile(atlasPath).getAsJsonObject().getAsJsonArray("sources"); + } catch (Exception ignored) { } } - if (Config.packMaxVersion().isAtOrAbove(MinecraftVersions.V1_21_2) && Config.packMinVersion().isBelow(MinecraftVersions.V1_21_4)) { - Path equipmentPath = generatedPackPath - .resolve("assets") - .resolve(assetId.namespace()) - .resolve("models") - .resolve("equipment") - .resolve(assetId.value() + ".json"); - - JsonObject equipmentJson = null; - if (Files.exists(equipmentPath)) { - try (BufferedReader reader = Files.newBufferedReader(equipmentPath)) { - equipmentJson = JsonParser.parseReader(reader).getAsJsonObject(); - } catch (IOException e) { - plugin.logger().warn("Failed to load existing sounds.json", e); - return; + // 准备新版本atlas + JsonObject modernTrimAtlasJson = null; + if (needModernCompatibility) { + modernTrimAtlasJson = new JsonObject(); + JsonArray sourcesArray = new JsonArray(); + modernTrimAtlasJson.add("sources", sourcesArray); + for (Tuple tuple : collectedTrims) { + if (tuple.mid()) { + JsonObject single1 = new JsonObject(); + single1.addProperty("type", "single"); + single1.addProperty("resource", tuple.left().namespace() + ":trims/entity/humanoid/" + tuple.left().value() + "_" + TRIM_MATERIAL); + sourcesArray.add(single1); + } + if (tuple.right()) { + JsonObject single2 = new JsonObject(); + single2.addProperty("type", "single"); + single2.addProperty("resource", tuple.left().namespace() + ":trims/entity/humanoid_leggings/" + tuple.left().value() + "_" + TRIM_MATERIAL); + sourcesArray.add(single2); } } - if (equipmentJson != null) { - equipmentJson = GsonHelper.deepMerge(equipmentJson, entry.getValue().get()); - } else { - equipmentJson = entry.getValue().get(); + if (previousAtlasSources != null) { + sourcesArray.addAll(previousAtlasSources); } + } + // 准备旧版本atlas + JsonObject legacyTrimAtlasJson = null; + if (needLegacyCompatibility) { + legacyTrimAtlasJson = new JsonObject(); + JsonArray sourcesArray = new JsonArray(); + legacyTrimAtlasJson.add("sources", sourcesArray); + for (Tuple tuple : collectedTrims) { + if (tuple.mid()) { + JsonObject single1 = new JsonObject(); + single1.addProperty("type", "single"); + single1.addProperty("resource", tuple.left().namespace() + ":trims/models/armor/" + tuple.left().value() + "_" + TRIM_MATERIAL); + sourcesArray.add(single1); + } + if (tuple.right()) { + JsonObject single2 = new JsonObject(); + single2.addProperty("type", "single"); + single2.addProperty("resource", tuple.left().namespace() + ":trims/models/armor/" + tuple.left().value() + "_leggings_" + TRIM_MATERIAL); + sourcesArray.add(single2); + } + } + if (previousAtlasSources != null) { + sourcesArray.addAll(previousAtlasSources); + } + } + // 创建atlas文件夹 + try { + Files.createDirectories(atlasPath.getParent()); + } catch (IOException e) { + this.plugin.logger().severe("Error creating " + atlasPath.toAbsolutePath(), e); + return; + } + // 写入atlas文件 + try (BufferedWriter writer = Files.newBufferedWriter(atlasPath)) { + JsonObject selected = needLegacyCompatibility ? legacyTrimAtlasJson : modernTrimAtlasJson; + // 优先写入旧版 + GsonHelper.get().toJson(selected, writer); + } catch (IOException e) { + this.plugin.logger().severe("Error writing " + atlasPath.toAbsolutePath(), e); + } + // 既要又要,那么需要overlay + if (needLegacyCompatibility && needModernCompatibility) { + Revision revision = Revisions.SINCE_1_21_2; + callback.accept(revision); + Path overlayAtlasPath = generatedPackPath + .resolve(Config.createOverlayFolderName(revision.versionString())) + .resolve("assets") + .resolve("minecraft") + .resolve("atlases") + .resolve("armor_trims.json"); + // 创建atlas文件夹 try { - Files.createDirectories(equipmentPath.getParent()); + Files.createDirectories(overlayAtlasPath.getParent()); } catch (IOException e) { - plugin.logger().severe("Error creating " + equipmentPath.toAbsolutePath()); + this.plugin.logger().severe("Error creating " + overlayAtlasPath.toAbsolutePath(), e); return; } - try { - GsonHelper.writeJsonFile(equipmentJson, equipmentPath); + // 写入atlas文件 + try (BufferedWriter writer = Files.newBufferedWriter(overlayAtlasPath)) { + GsonHelper.get().toJson(modernTrimAtlasJson, writer); + callback.accept(revision); } catch (IOException e) { - this.plugin.logger().severe("Error writing equipment file", e); + this.plugin.logger().severe("Error writing " + overlayAtlasPath.toAbsolutePath(), e); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/misc/Equipment.java b/core/src/main/java/net/momirealms/craftengine/core/pack/misc/Equipment.java deleted file mode 100644 index eabdca9d9..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/misc/Equipment.java +++ /dev/null @@ -1,51 +0,0 @@ -package net.momirealms.craftengine.core.pack.misc; - -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import net.momirealms.craftengine.core.item.equipment.ComponentBasedEquipment; -import net.momirealms.craftengine.core.item.setting.ItemEquipment; - -import java.util.EnumMap; -import java.util.List; -import java.util.Map; -import java.util.function.Supplier; - -public class Equipment implements Supplier { - private final EnumMap> layers; - - public Equipment() { - this.layers = new EnumMap<>(EquipmentLayerType.class); - } - - public void addAll(ItemEquipment equipment) { - for (Map.Entry> entry : equipment.layers().entrySet()) { - List layers = entry.getValue(); - List previous = this.layers.put(entry.getKey(), layers); - if (previous != null && !previous.equals(layers)) { - // todo 是否异常 - } - } - } - - @Override - public JsonObject get() { - JsonObject jsonObject = new JsonObject(); - JsonObject layersJson = new JsonObject(); - jsonObject.add("layers", layersJson); - for (Map.Entry> entry : layers.entrySet()) { - EquipmentLayerType type = entry.getKey(); - List layerList = entry.getValue(); - setLayers(layersJson, layerList, type.id()); - } - return jsonObject; - } - - private void setLayers(JsonObject layersJson, List layers, String key) { - if (layers == null || layers.isEmpty()) return; - JsonArray layersArray = new JsonArray(); - for (ComponentBasedEquipment.Layer layer : layers) { - layersArray.add(layer.get()); - } - layersJson.add(key, layersArray); - } -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/revision/Revisions.java b/core/src/main/java/net/momirealms/craftengine/core/pack/revision/Revisions.java index 82f434c2f..2b7fd7372 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/revision/Revisions.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/revision/Revisions.java @@ -6,4 +6,5 @@ public final class Revisions { private Revisions() {} public static final Revision SINCE_1_21_6 = Revision.since(MinecraftVersions.V1_21_6); + public static final Revision SINCE_1_21_2 = Revision.since(MinecraftVersions.V1_21_2); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java index 1ff4549c2..730340185 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java @@ -151,6 +151,8 @@ public abstract class CraftEngine implements Plugin { } catch (Exception e) { this.logger().warn("Failed to load resources folder", e); } + // register trims + this.itemManager.delayedLoad(); // init suggestions and packet mapper this.blockManager.delayedLoad(); // handle some special client lang for instance block_name diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index 5980d5d42..7b9e261a0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -143,6 +143,13 @@ public class Config { protected boolean image$intercept_packets$set_score; protected boolean image$intercept_packets$item; + protected boolean item$client_bound_model; + + protected String equipment$sacrificed_vanilla_armor$type; + protected Key equipment$sacrificed_vanilla_armor$asset_id; + protected Key equipment$sacrificed_vanilla_armor$humanoid; + protected Key equipment$sacrificed_vanilla_armor$humanoid_leggings; + protected boolean emoji$chat; protected boolean emoji$book; protected boolean emoji$anvil; @@ -321,6 +328,15 @@ public class Config { furniture$hide_base_entity = config.getBoolean("furniture.hide-base-entity", true); furniture$collision_entity_type = ColliderType.valueOf(config.getString("furniture.collision-entity-type", "interaction").toUpperCase(Locale.ENGLISH)); + // equipment + equipment$sacrificed_vanilla_armor$type = config.getString("equipment.sacrificed-vanilla-armor.type", "chainmail"); + equipment$sacrificed_vanilla_armor$asset_id = Key.of(config.getString("equipment.sacrificed-vanilla-armor.asset-id", "minecraft:chainmail")); + equipment$sacrificed_vanilla_armor$humanoid = Key.of(config.getString("equipment.sacrificed-vanilla-armor.humanoid")); + equipment$sacrificed_vanilla_armor$humanoid_leggings = Key.of(config.getString("equipment.sacrificed-vanilla-armor.humanoid-leggings")); + + // item + item$client_bound_model = config.getBoolean("item.client-bound-model", false); + // block block$sound_system$enable = config.getBoolean("block.sound-system.enable", true); block$simplify_adventure_break_check = config.getBoolean("block.simplify-adventure-break-check", false); @@ -739,6 +755,26 @@ public class Config { return instance.resource_pack$overlay_format.replace("{version}", version); } + public static Key sacrificedAssetId() { + return instance.equipment$sacrificed_vanilla_armor$asset_id; + } + + public static Key sacrificedHumanoid() { + return instance.equipment$sacrificed_vanilla_armor$humanoid; + } + + public static Key sacrificedHumanoidLeggings() { + return instance.equipment$sacrificed_vanilla_armor$humanoid_leggings; + } + + public static String sacrificedVanillaArmorType() { + return instance.equipment$sacrificed_vanilla_armor$type; + } + + public static boolean globalClientboundModel() { + return instance.item$client_bound_model; + } + public YamlDocument loadOrCreateYamlData(String fileName) { Path path = this.plugin.dataFolderPath().resolve(fileName); if (!Files.exists(path)) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigParser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigParser.java index 4d6fae451..901fbe8a9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigParser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/ConfigParser.java @@ -29,4 +29,10 @@ public interface ConfigParser extends Comparable { default int compareTo(@NotNull ConfigParser another) { return Integer.compare(loadingSequence(), another.loadingSequence()); } + + default void postProcess() { + } + + default void preProcess() { + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/Tristate.java b/core/src/main/java/net/momirealms/craftengine/core/util/Tristate.java index 1e3487202..3a37fe8cf 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/Tristate.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/Tristate.java @@ -24,4 +24,12 @@ public enum Tristate { public boolean asBoolean() { return this.booleanValue; } + + public boolean asBoolean(boolean defaultValue) { + if (this == UNDEFINED) { + return defaultValue; + } else { + return this.booleanValue; + } + } }