From b7586310f967773f5835413310fa4667537688b7 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sun, 17 Aug 2025 22:44:12 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=89=A9=E5=93=81=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/item/BukkitCustomItem.java | 15 ++++- .../bukkit/item/BukkitItemManager.java | 2 +- .../item/listener/ItemEventListener.java | 56 +++++++++++++++- common-files/src/main/resources/config.yml | 10 ++- .../src/main/resources/translations/en.yml | 3 + .../src/main/resources/translations/zh_cn.yml | 3 + .../core/item/AbstractCustomItem.java | 11 ++- .../core/item/AbstractItemManager.java | 37 ++++++++++ .../craftengine/core/item/CustomItem.java | 6 ++ .../craftengine/core/item/ItemManager.java | 4 ++ .../core/item/modifier/ArgumentsModifier.java | 2 +- .../core/item/modifier/ItemDataModifiers.java | 1 + .../item/modifier/ItemVersionModifier.java | 40 +++++++++++ .../core/item/updater/ItemUpdateConfig.java | 67 +++++++++++++++++++ .../core/item/updater/ItemUpdateResult.java | 6 ++ .../core/item/updater/ItemUpdater.java | 9 +++ .../core/item/updater/ItemUpdaterType.java | 10 +++ .../core/item/updater/ItemUpdaters.java | 42 ++++++++++++ .../item/updater/impl/ApplyDataOperation.java | 49 ++++++++++++++ .../item/updater/impl/ResetOperation.java | 63 +++++++++++++++++ .../item/updater/impl/TransmuteOperation.java | 32 +++++++++ .../core/plugin/config/Config.java | 24 +++++++ .../core/registry/BuiltInRegistries.java | 2 + .../craftengine/core/registry/Registries.java | 2 + .../core/world/chunk/SingularPalette.java | 2 +- gradle.properties | 6 +- 26 files changed, 492 insertions(+), 12 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemVersionModifier.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/item/updater/ItemUpdateConfig.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/item/updater/ItemUpdateResult.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/item/updater/ItemUpdater.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/item/updater/ItemUpdaterType.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/item/updater/ItemUpdaters.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/item/updater/impl/ApplyDataOperation.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/item/updater/impl/ResetOperation.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/item/updater/impl/TransmuteOperation.java 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 5bd11d54c..d58b647a9 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 @@ -5,6 +5,7 @@ import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.core.item.*; import net.momirealms.craftengine.core.item.behavior.ItemBehavior; import net.momirealms.craftengine.core.item.modifier.ItemDataModifier; +import net.momirealms.craftengine.core.item.updater.ItemUpdateConfig; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; import net.momirealms.craftengine.core.plugin.context.function.Function; @@ -25,8 +26,9 @@ public class BukkitCustomItem extends AbstractCustomItem { List behaviors, List> modifiers, List> clientBoundModifiers, ItemSettings settings, - Map>> events) { - super(isVanillaItem, id, materialKey, clientBoundMaterialKey, behaviors, modifiers, clientBoundModifiers, settings, events); + Map>> events, + ItemUpdateConfig updater) { + super(isVanillaItem, id, materialKey, clientBoundMaterialKey, behaviors, modifiers, clientBoundModifiers, settings, events, updater); this.item = item; this.clientItem = clientItem; } @@ -75,6 +77,7 @@ public class BukkitCustomItem extends AbstractCustomItem { private final List> modifiers = new ArrayList<>(4); private final List> clientBoundModifiers = new ArrayList<>(4); private ItemSettings settings; + private ItemUpdateConfig updater; public BuilderImpl(Object item, Object clientBoundItem) { this.item = item; @@ -153,12 +156,18 @@ public class BukkitCustomItem extends AbstractCustomItem { return this; } + @Override + public Builder updater(ItemUpdateConfig updater) { + this.updater = updater; + return this; + } + @Override public CustomItem build() { this.modifiers.addAll(this.settings.modifiers()); this.clientBoundModifiers.addAll(this.settings.clientBoundModifiers()); return new BukkitCustomItem(this.isVanillaItem, 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); + List.copyOf(this.modifiers), List.copyOf(this.clientBoundModifiers), this.settings, this.events, updater); } } } 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 19f69cfc5..c21500bb8 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 @@ -62,7 +62,7 @@ public class BukkitItemManager extends AbstractItemManager { instance = this; this.plugin = plugin; this.factory = BukkitItemFactory.create(plugin); - this.itemEventListener = new ItemEventListener(plugin); + this.itemEventListener = new ItemEventListener(plugin, this); this.debugStickListener = new DebugStickListener(plugin); this.armorEventListener = new ArmorEventListener(); this.networkItemHandler = VersionHelper.isOrAbove1_20_5() ? new ModernNetworkItemHandler() : new LegacyNetworkItemHandler(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java index 76f05c35a..db9aff35a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/listener/ItemEventListener.java @@ -4,6 +4,7 @@ import io.papermc.paper.event.block.CompostItemEvent; import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; import net.momirealms.craftengine.bukkit.api.event.CustomBlockInteractEvent; import net.momirealms.craftengine.bukkit.item.BukkitCustomItem; +import net.momirealms.craftengine.bukkit.item.BukkitItemManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; @@ -17,9 +18,11 @@ import net.momirealms.craftengine.core.entity.player.InteractionHand; import net.momirealms.craftengine.core.entity.player.InteractionResult; import net.momirealms.craftengine.core.item.CustomItem; import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemBuildContext; import net.momirealms.craftengine.core.item.behavior.ItemBehavior; import net.momirealms.craftengine.core.item.context.UseOnContext; import net.momirealms.craftengine.core.item.setting.FoodData; +import net.momirealms.craftengine.core.item.updater.ItemUpdateResult; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.context.ContextHolder; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; @@ -45,13 +48,16 @@ import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.enchantment.PrepareItemEnchantEvent; import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityPickupItemEvent; import org.bukkit.event.entity.FoodLevelChangeEvent; import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.event.player.PlayerInteractEntityEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerItemConsumeEvent; import org.bukkit.inventory.EnchantingInventory; import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import java.util.ArrayList; @@ -61,9 +67,11 @@ import java.util.Optional; public class ItemEventListener implements Listener { private final BukkitCraftEngine plugin; + private final BukkitItemManager itemManager; - public ItemEventListener(BukkitCraftEngine plugin) { + public ItemEventListener(BukkitCraftEngine plugin, BukkitItemManager itemManager) { this.plugin = plugin; + this.itemManager = itemManager; } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) @@ -534,4 +542,50 @@ public class ItemEventListener implements Listener { serverPlayer.sendPackets(packets, false); }); } + + /* + + 关于物品更新器 + + */ + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH) + public void onDropItem(PlayerDropItemEvent event) { + if (!Config.triggerUpdateDrop()) return; + org.bukkit.entity.Item itemDrop = event.getItemDrop(); + ItemStack itemStack = itemDrop.getItemStack(); + Item wrapped = this.itemManager.wrap(itemStack); + ItemUpdateResult result = this.itemManager.updateItem(wrapped, () -> ItemBuildContext.of(BukkitAdaptors.adapt(event.getPlayer()))); + if (result.updated()) { + itemDrop.setItemStack((ItemStack) result.finalItem().getItem()); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH) + public void onPickUpItem(EntityPickupItemEvent event) { + if (!Config.triggerUpdatePickUp()) return; + if (!(event.getEntity() instanceof Player player)) return; + org.bukkit.entity.Item itemDrop = event.getItem(); + ItemStack itemStack = itemDrop.getItemStack(); + Item wrapped = this.itemManager.wrap(itemStack); + ItemUpdateResult result = this.itemManager.updateItem(wrapped, () -> ItemBuildContext.of(BukkitAdaptors.adapt(player))); + if (result.updated()) { + itemDrop.setItemStack((ItemStack) result.finalItem().getItem()); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) + public void onInventoryClickItem(InventoryClickEvent event) { + if (!Config.triggerUpdateClick()) return; + if (!(event.getWhoClicked() instanceof Player player)) return; + Inventory clickedInventory = event.getClickedInventory(); + // 点击自己物品栏里的物品 + if (clickedInventory == null || clickedInventory != player.getInventory()) return; + ItemStack currentItem = event.getCurrentItem(); + Item wrapped = this.itemManager.wrap(currentItem); + ItemUpdateResult result = this.itemManager.updateItem(wrapped, () -> ItemBuildContext.of(BukkitAdaptors.adapt(player))); + if (!result.updated() || !result.replaced()) { + return; + } + event.setCurrentItem((ItemStack) result.finalItem().getItem()); + } } diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index 2d4cc5ea7..27ead33b2 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -142,6 +142,14 @@ item: client-bound-model: false # Add a tag on custom name and lore non-italic-tag: false + # Determines when to trigger the item updater + # This feature may incur some performance overhead. Please do not enable it unless necessary. + # Correct use case: When you designed incorrect weapon attributes and need to update the values for items already held by players. + # Wrong use case: When you want to update an item's name and lore to a newer version (In this case you should use client-bound-data instead of the item updater) + update-triggers: + click-in-inventory: false # this option won't work for players in creative mode + drop: false + pick-up: false equipment: # The sacrificed-vanilla-armor argument determines which vanilla armor gets completely removed (loses all its trims) @@ -170,7 +178,7 @@ block: # - Server MUST list ACTUAL CUSTOM BLOCK IDs in item's `can_break` component. # - Sending custom IDs (e.g., craftengine:note_block_0) to vanilla clients WILL CRASH THEM! # ✅ Solution: - # - Use `client-bound-item-data` to safely sync custom block data to clients. + # - Use `client-bound-data` to safely sync custom block data to clients. simplify-adventure-break-check: false # Similar to the option above, but designed for block placement simplify-adventure-place-check: false diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 6244a83e1..213fa69b8 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -243,6 +243,9 @@ warning.config.item.model.special.chest.invalid_openness: "Issue found i warning.config.item.model.special.shulker_box.missing_texture: "Issue found in file - The item '' is missing the required 'texture' argument for special model 'minecraft:shulker_box'." warning.config.item.model.special.shulker_box.invalid_openness: "Issue found in file - The item '' is using an invalid 'openness' value '' for special model 'minecraft:shulker_box'. Valid range '0~1.'" warning.config.item.model.special.head.missing_kind: "Issue found in file - The item '' is missing the required 'kind' argument for special model 'minecraft:head'." +warning.config.item.updater.missing_type: "Issue found in file - The item '' is missing the required 'type' argument for item updater." +warning.config.item.updater.invalid_type: "Issue found in file - The item '' is using an invalid 'type' argument '' for item updater." +warning.config.item.updater.transmute.missing_material: "Issue found in file - The item '' is missing the required 'material' argument for 'transmute' item updater." warning.config.block.duplicate: "Issue found in file - Duplicated block ''. Please check if there is the same configuration in other files." warning.config.block.missing_state: "Issue found in file - The block '' is missing the required 'state' argument." warning.config.block.state.property.missing_type: "Issue found in file - The block '' is missing the required 'type' argument for property ''." diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 93b136d5a..f67146759 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -243,6 +243,9 @@ warning.config.item.model.special.chest.invalid_openness: "在文件 在文件 发现问题 - 物品 '' 的 'minecraft:shulker_box' 特殊模型缺少必需的 'texture' 参数" warning.config.item.model.special.shulker_box.invalid_openness: "在文件 发现问题 - 物品 '' 的 'minecraft:shulker_box' 特殊模型使用了无效的 'openness' 值 '' 有效范围应为 0~1" warning.config.item.model.special.head.missing_kind: "在文件 发现问题 - 物品 '' 的 'minecraft:head' 特殊模型缺少必需的 'kind' 参数" +warning.config.item.updater.missing_type: "在文件 发现问题 - 物品 '' 缺少物品更新器必需的参数 'type'" +warning.config.item.updater.invalid_type: "在文件 发现问题 - 物品 '' 在物品更新器中使用了无效的 'type' 参数值 ''" +warning.config.item.updater.transmute.missing_material: "在文件 发现问题 - 物品 '' 缺少物品转换更新所需的 'material' 参数." warning.config.block.duplicate: "在文件 发现问题 - 重复的方块 '' 请检查其他文件中是否存在相同配置" warning.config.block.missing_state: "在文件 发现问题 - 方块 '' 缺少必需的 'state' 参数" warning.config.block.state.property.missing_type: "在文件 发现问题 - 方块 '' 的属性 '' 缺少必需的 'type' 参数" diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractCustomItem.java b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractCustomItem.java index 336d4c1a7..561d4d9f1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/AbstractCustomItem.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/AbstractCustomItem.java @@ -2,6 +2,7 @@ package net.momirealms.craftengine.core.item; import net.momirealms.craftengine.core.item.behavior.ItemBehavior; import net.momirealms.craftengine.core.item.modifier.ItemDataModifier; +import net.momirealms.craftengine.core.item.updater.ItemUpdateConfig; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; import net.momirealms.craftengine.core.plugin.context.function.Function; @@ -24,6 +25,7 @@ public abstract class AbstractCustomItem implements CustomItem { protected final List behaviors; protected final ItemSettings settings; protected final Map>> events; + protected final ItemUpdateConfig updater; @SuppressWarnings("unchecked") public AbstractCustomItem(boolean isVanillaItem, UniqueKey id, Key material, Key clientBoundMaterial, @@ -31,7 +33,8 @@ public abstract class AbstractCustomItem implements CustomItem { List> modifiers, List> clientBoundModifiers, ItemSettings settings, - Map>> events) { + Map>> events, + ItemUpdateConfig updater) { this.isVanillaItem = isVanillaItem; this.id = id; this.material = material; @@ -43,6 +46,7 @@ public abstract class AbstractCustomItem implements CustomItem { this.clientBoundModifiers = clientBoundModifiers.toArray(new ItemDataModifier[0]); this.behaviors = List.copyOf(behaviors); this.settings = settings; + this.updater = updater; } @Override @@ -52,6 +56,11 @@ public abstract class AbstractCustomItem implements CustomItem { } } + @Override + public Optional updater() { + return Optional.ofNullable(this.updater); + } + @Override public Key id() { return this.id.key(); 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 4b18d7194..f2fb1bba1 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 @@ -6,6 +6,10 @@ import net.momirealms.craftengine.core.item.behavior.ItemBehavior; import net.momirealms.craftengine.core.item.behavior.ItemBehaviors; import net.momirealms.craftengine.core.item.equipment.*; import net.momirealms.craftengine.core.item.modifier.*; +import net.momirealms.craftengine.core.item.updater.ItemUpdateConfig; +import net.momirealms.craftengine.core.item.updater.ItemUpdateResult; +import net.momirealms.craftengine.core.item.updater.ItemUpdater; +import net.momirealms.craftengine.core.item.updater.ItemUpdaters; import net.momirealms.craftengine.core.pack.AbstractPackManager; import net.momirealms.craftengine.core.pack.LoadingSequence; import net.momirealms.craftengine.core.pack.Pack; @@ -30,6 +34,7 @@ import org.incendo.cloud.type.Either; import java.nio.file.Path; import java.util.*; import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.stream.Stream; public abstract class AbstractItemManager extends AbstractModelGenerator implements ItemManager { @@ -144,6 +149,19 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl return Optional.ofNullable(this.customItemsByPath.get(path)); } + @Override + public ItemUpdateResult updateItem(Item item, Supplier contextSupplier) { + Optional> optionalCustomItem = item.getCustomItem(); + if (optionalCustomItem.isPresent()) { + CustomItem customItem = optionalCustomItem.get(); + Optional updater = customItem.updater(); + if (updater.isPresent()) { + return updater.get().update(item, contextSupplier); + } + } + return new ItemUpdateResult(item, false, false); + } + @Override public boolean addCustomItem(CustomItem customItem) { Key id = customItem.id(); @@ -417,6 +435,25 @@ public abstract class AbstractItemManager extends AbstractModelGenerator impl behaviors = Collections.emptyList(); } + // 如果有物品更新器 + if (section.containsKey("updater")) { + Map updater = ResourceConfigUtils.getAsMap(section.get("updater"), "updater"); + List versions = new ArrayList<>(2); + for (Map.Entry entry : updater.entrySet()) { + try { + int version = Integer.parseInt(entry.getKey()); + versions.add(new ItemUpdateConfig.Version( + version, + ResourceConfigUtils.parseConfigAsList(entry.getValue(), map -> ItemUpdaters.fromMap(id, map)).toArray(new ItemUpdater[0]) + )); + } catch (NumberFormatException ignored) { + } + } + ItemUpdateConfig config = new ItemUpdateConfig(versions); + itemBuilder.updater(config); + itemBuilder.dataModifier(new ItemVersionModifier<>(config.maxVersion())); + } + // 构建自定义物品 CustomItem customItem = itemBuilder .isVanillaItem(isVanillaItem) diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/CustomItem.java b/core/src/main/java/net/momirealms/craftengine/core/item/CustomItem.java index 5d0415a30..b02cbea0b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/CustomItem.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/CustomItem.java @@ -2,6 +2,7 @@ package net.momirealms.craftengine.core.item; import net.momirealms.craftengine.core.item.behavior.ItemBehavior; import net.momirealms.craftengine.core.item.modifier.ItemDataModifier; +import net.momirealms.craftengine.core.item.updater.ItemUpdateConfig; import net.momirealms.craftengine.core.plugin.context.PlayerOptionalContext; import net.momirealms.craftengine.core.plugin.context.event.EventTrigger; import net.momirealms.craftengine.core.plugin.context.function.Function; @@ -11,6 +12,7 @@ import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.Map; +import java.util.Optional; public interface CustomItem extends BuildableItem { @@ -32,6 +34,8 @@ public interface CustomItem extends BuildableItem { ItemSettings settings(); + Optional updater(); + default boolean is(Key tag) { return settings().tags().contains(tag); } @@ -64,6 +68,8 @@ public interface CustomItem extends BuildableItem { Builder settings(ItemSettings settings); + Builder updater(ItemUpdateConfig updater); + Builder events(Map>> events); CustomItem build(); 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 9be0dfb7f..4f34eff44 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 @@ -5,6 +5,7 @@ import net.momirealms.craftengine.core.item.behavior.ItemBehavior; import net.momirealms.craftengine.core.item.equipment.Equipment; import net.momirealms.craftengine.core.item.recipe.DatapackRecipeResult; import net.momirealms.craftengine.core.item.recipe.UniqueIdItem; +import net.momirealms.craftengine.core.item.updater.ItemUpdateResult; 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; @@ -18,6 +19,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.function.Supplier; public interface ItemManager extends Manageable, ModelGenerator { @@ -114,4 +116,6 @@ public interface ItemManager extends Manageable, ModelGenerator { Item applyTrim(Item base, Item addition, Item template, Key pattern); Item build(DatapackRecipeResult result); + + ItemUpdateResult updateItem(Item item, Supplier contextSupplier); } \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ArgumentsModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ArgumentsModifier.java index 33f4bb98f..b2b636f60 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ArgumentsModifier.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ArgumentsModifier.java @@ -37,7 +37,7 @@ public class ArgumentsModifier implements ItemDataModifier { @Override public Item apply(Item item, ItemBuildContext context) { if (VersionHelper.isOrAbove1_20_5()) { - CompoundTag customData = (CompoundTag) Optional.ofNullable(item.getSparrowNBTComponent(ComponentKeys.CUSTOM_DATA)).orElse(new CompoundTag()); + CompoundTag customData = (CompoundTag) Optional.ofNullable(item.getSparrowNBTComponent(ComponentKeys.CUSTOM_DATA)).orElseGet(CompoundTag::new); CompoundTag argumentTag = new CompoundTag(); for (Map.Entry entry : this.arguments.entrySet()) { argumentTag.put(entry.getKey(), new StringTag(entry.getValue().get(context))); diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemDataModifiers.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemDataModifiers.java index d2eb6a9c4..402efa393 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemDataModifiers.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemDataModifiers.java @@ -31,6 +31,7 @@ public final class ItemDataModifiers { public static final Key ATTRIBUTE_MODIFIERS = Key.of("craftengine:attribute-modifiers"); public static final Key ATTRIBUTES = Key.of("craftengine:attributes"); public static final Key ARGUMENTS = Key.of("craftengine:arguments"); + public static final Key VERSION = Key.of("craftengine:version"); public static final Key ITEM_NAME = Key.of("craftengine:item-name"); public static final Key OVERWRITABLE_ITEM_NAME = Key.of("craftengine:overwritable-item-name"); public static final Key JUKEBOX_PLAYABLE = Key.of("craftengine:jukebox-playable"); diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemVersionModifier.java b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemVersionModifier.java new file mode 100644 index 000000000..b3002918e --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/item/modifier/ItemVersionModifier.java @@ -0,0 +1,40 @@ +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.util.Key; +import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.sparrow.nbt.CompoundTag; + +import java.util.Optional; + +public class ItemVersionModifier implements ItemDataModifier { + public static final String VERSION_TAG = "craftengine:version"; + private final int version; + + public ItemVersionModifier(int version) { + this.version = version; + } + + public int version() { + return this.version; + } + + @Override + public Key type() { + return ItemDataModifiers.VERSION; + } + + @Override + public Item apply(Item item, ItemBuildContext context) { + if (VersionHelper.isOrAbove1_20_5()) { + CompoundTag customData = (CompoundTag) Optional.ofNullable(item.getSparrowNBTComponent(ComponentKeys.CUSTOM_DATA)).orElseGet(CompoundTag::new); + customData.putInt(VERSION_TAG, this.version); + item.setNBTComponent(ComponentKeys.CUSTOM_DATA, customData); + } else { + item.setTag(this.version, VERSION_TAG); + } + return item; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/updater/ItemUpdateConfig.java b/core/src/main/java/net/momirealms/craftengine/core/item/updater/ItemUpdateConfig.java new file mode 100644 index 000000000..9ed2df898 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/item/updater/ItemUpdateConfig.java @@ -0,0 +1,67 @@ +package net.momirealms.craftengine.core.item.updater; + +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.craftengine.core.item.modifier.ItemVersionModifier; +import net.momirealms.sparrow.nbt.NumericTag; +import net.momirealms.sparrow.nbt.Tag; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +public class ItemUpdateConfig { + private final List versions; + private final int maxVersion; + + public ItemUpdateConfig(List versions) { + this.versions = new ArrayList<>(versions); + this.versions.sort(Version::compareTo); + int maxVersion = 0; + for (Version version : versions) { + maxVersion = Math.max(maxVersion, version.version); + } + this.maxVersion = maxVersion; + } + + public int maxVersion() { + return maxVersion; + } + + public ItemUpdateResult update(Item item, Supplier context) { + Tag versionTag = item.getTag(ItemVersionModifier.VERSION_TAG); + int currentVersion = 0; + if (versionTag instanceof NumericTag numericTag) { + currentVersion = numericTag.getAsInt(); + } + if (currentVersion >= this.maxVersion) { + return new ItemUpdateResult(item, false, false); + } + ItemBuildContext buildContext = context.get(); + Item orginalItem = item; + for (Version version : this.versions) { + if (currentVersion < version.version) { + item = version.apply(item, buildContext); + } + } + item.setTag(this.maxVersion, ItemVersionModifier.VERSION_TAG); + return new ItemUpdateResult(item, orginalItem != item, true); + } + + public record Version(int version, ItemUpdater[] updaters) implements Comparable { + + @SuppressWarnings("unchecked") + public Item apply(Item item, ItemBuildContext context) { + for (ItemUpdater updater : (ItemUpdater[]) updaters) { + item = updater.update(item, context); + } + return item; + } + + @Override + public int compareTo(@NotNull ItemUpdateConfig.Version o) { + return Integer.compare(this.version, o.version); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/updater/ItemUpdateResult.java b/core/src/main/java/net/momirealms/craftengine/core/item/updater/ItemUpdateResult.java new file mode 100644 index 000000000..89b313971 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/item/updater/ItemUpdateResult.java @@ -0,0 +1,6 @@ +package net.momirealms.craftengine.core.item.updater; + +import net.momirealms.craftengine.core.item.Item; + +public record ItemUpdateResult(Item finalItem, boolean replaced, boolean updated) { +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/updater/ItemUpdater.java b/core/src/main/java/net/momirealms/craftengine/core/item/updater/ItemUpdater.java new file mode 100644 index 000000000..842b25e23 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/item/updater/ItemUpdater.java @@ -0,0 +1,9 @@ +package net.momirealms.craftengine.core.item.updater; + +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemBuildContext; + +public interface ItemUpdater { + + Item update(Item item, ItemBuildContext context); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/updater/ItemUpdaterType.java b/core/src/main/java/net/momirealms/craftengine/core/item/updater/ItemUpdaterType.java new file mode 100644 index 000000000..2b81e9a29 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/item/updater/ItemUpdaterType.java @@ -0,0 +1,10 @@ +package net.momirealms.craftengine.core.item.updater; + +import net.momirealms.craftengine.core.util.Key; + +import java.util.Map; + +public interface ItemUpdaterType { + + ItemUpdater create(Key item, Map args); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/updater/ItemUpdaters.java b/core/src/main/java/net/momirealms/craftengine/core/item/updater/ItemUpdaters.java new file mode 100644 index 000000000..9740c9fd9 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/item/updater/ItemUpdaters.java @@ -0,0 +1,42 @@ +package net.momirealms.craftengine.core.item.updater; + +import net.momirealms.craftengine.core.item.updater.impl.ApplyDataOperation; +import net.momirealms.craftengine.core.item.updater.impl.ResetOperation; +import net.momirealms.craftengine.core.item.updater.impl.TransmuteOperation; +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.ResourceConfigUtils; +import net.momirealms.craftengine.core.util.ResourceKey; + +import java.util.Map; + +@SuppressWarnings("unchecked") +public class ItemUpdaters { + public static final Key APPLY_DATA = Key.of("craftengine:apply_data"); + public static final Key TRANSMUTE = Key.of("craftengine:transmute"); + public static final Key RESET = Key.of("craftengine:reset"); + + static { + register(APPLY_DATA, ApplyDataOperation.TYPE); + register(TRANSMUTE, TransmuteOperation.TYPE); + register(RESET, ResetOperation.TYPE); + } + + public static void register(Key id, ItemUpdaterType type) { + WritableRegistry> registry = (WritableRegistry>) BuiltInRegistries.ITEM_UPDATER_TYPE; + registry.register(ResourceKey.create(Registries.ITEM_UPDATER_TYPE.location(), id), type); + } + + public static ItemUpdater fromMap(Key item, Map map) { + String type = ResourceConfigUtils.requireNonEmptyStringOrThrow(map.get("type"), "warning.config.item.updater.missing_type"); + Key key = Key.withDefaultNamespace(type, Key.DEFAULT_NAMESPACE); + ItemUpdaterType updaterType = (ItemUpdaterType) BuiltInRegistries.ITEM_UPDATER_TYPE.getValue(key); + if (updaterType == null) { + throw new LocalizedResourceConfigException("warning.config.item.updater.invalid_type", type); + } + return updaterType.create(item, map); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/updater/impl/ApplyDataOperation.java b/core/src/main/java/net/momirealms/craftengine/core/item/updater/impl/ApplyDataOperation.java new file mode 100644 index 000000000..53d06471d --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/item/updater/impl/ApplyDataOperation.java @@ -0,0 +1,49 @@ +package net.momirealms.craftengine.core.item.updater.impl; + +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.craftengine.core.item.modifier.ItemDataModifier; +import net.momirealms.craftengine.core.item.updater.ItemUpdater; +import net.momirealms.craftengine.core.item.updater.ItemUpdaterType; +import net.momirealms.craftengine.core.registry.BuiltInRegistries; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class ApplyDataOperation implements ItemUpdater { + public static final Type TYPE = new Type<>(); + private final List> modifiers; + + public ApplyDataOperation(List> modifiers) { + this.modifiers = modifiers; + } + + @Override + public Item update(Item item, ItemBuildContext context) { + if (this.modifiers != null) { + for (ItemDataModifier modifier : this.modifiers) { + modifier.apply(item, context); + } + } + return item; + } + + public static class Type implements ItemUpdaterType { + + @SuppressWarnings("unchecked") + @Override + public ItemUpdater create(Key item, Map args) { + List> modifiers = new ArrayList<>(); + Map data = ResourceConfigUtils.getAsMap(args.get("data"), "data"); + for (Map.Entry entry : data.entrySet()) { + Optional.ofNullable(BuiltInRegistries.ITEM_DATA_MODIFIER_FACTORY.getValue(Key.withDefaultNamespace(entry.getKey(), Key.DEFAULT_NAMESPACE))) + .ifPresent(factory -> modifiers.add((ItemDataModifier) factory.create(entry.getValue()))); + } + return new ApplyDataOperation<>(modifiers); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/updater/impl/ResetOperation.java b/core/src/main/java/net/momirealms/craftengine/core/item/updater/impl/ResetOperation.java new file mode 100644 index 000000000..f0d7d2d5d --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/item/updater/impl/ResetOperation.java @@ -0,0 +1,63 @@ +package net.momirealms.craftengine.core.item.updater.impl; + +import net.momirealms.craftengine.core.item.CustomItem; +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.craftengine.core.item.ItemManager; +import net.momirealms.craftengine.core.item.updater.ItemUpdater; +import net.momirealms.craftengine.core.item.updater.ItemUpdaterType; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.LazyReference; +import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.craftengine.core.util.VersionHelper; + +import java.util.List; +import java.util.Map; + +public class ResetOperation implements ItemUpdater { + public static final Type TYPE = new Type<>(); + private final LazyReference> item; + private final List componentsToKeep; + private final List tagsToKeep; + + public ResetOperation(LazyReference> item, List componentsToKeep, List tagsToKeep) { + this.componentsToKeep = componentsToKeep; + this.tagsToKeep = tagsToKeep; + this.item = item; + } + + @Override + public Item update(Item item, ItemBuildContext context) { + Item newItem = this.item.get().buildItem(context); + if (VersionHelper.COMPONENT_RELEASE) { + for (Key component : this.componentsToKeep) { + if (item.hasComponent(component)) { + newItem.setExactComponent(component, item.getExactComponent(component)); + } + } + } else { + for (String[] nbt : this.tagsToKeep) { + if (item.hasTag((Object[]) nbt)) { + newItem.setTag(item.getTag((Object[]) nbt), (Object[]) nbt); + } + } + } + return newItem; + } + + public static class Type implements ItemUpdaterType { + + @Override + public ItemUpdater create(Key item, Map args) { + return new ResetOperation<>( + LazyReference.lazyReference(() -> { + ItemManager itemManager = CraftEngine.instance().itemManager(); + return itemManager.getCustomItem(item).orElseThrow(); + }), + MiscUtils.getAsStringList(args.get("keep-components")).stream().map(Key::of).toList(), + MiscUtils.getAsStringList(args.get("keep-tags")).stream().map(tag -> tag.split("\\.")).toList() + ); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/updater/impl/TransmuteOperation.java b/core/src/main/java/net/momirealms/craftengine/core/item/updater/impl/TransmuteOperation.java new file mode 100644 index 000000000..fe046191f --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/item/updater/impl/TransmuteOperation.java @@ -0,0 +1,32 @@ +package net.momirealms.craftengine.core.item.updater.impl; + +import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemBuildContext; +import net.momirealms.craftengine.core.item.updater.ItemUpdater; +import net.momirealms.craftengine.core.item.updater.ItemUpdaterType; +import net.momirealms.craftengine.core.util.Key; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; + +import java.util.Map; + +public class TransmuteOperation implements ItemUpdater { + public static final Type TYPE = new Type<>(); + private final Key newMaterial; + + public TransmuteOperation(Key newMaterial) { + this.newMaterial = newMaterial; + } + + @Override + public Item update(Item item, ItemBuildContext context) { + return item.transmuteCopy(this.newMaterial, item.count()); + } + + public static class Type implements ItemUpdaterType { + + @Override + public ItemUpdater create(Key item, Map args) { + return new TransmuteOperation<>(Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(args.get("material"), "warning.config.item.updater.transmute.missing_material"))); + } + } +} 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 8439a43d6..6c1fe4b11 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 @@ -151,6 +151,10 @@ public class Config { protected boolean item$client_bound_model; protected boolean item$non_italic_tag; + protected boolean item$update_triggers$attack; + protected boolean item$update_triggers$click_in_inventory; + protected boolean item$update_triggers$drop; + protected boolean item$update_triggers$pick_up; protected String equipment$sacrificed_vanilla_armor$type; protected Key equipment$sacrificed_vanilla_armor$asset_id; @@ -359,6 +363,10 @@ public class Config { // item item$client_bound_model = config.getBoolean("item.client-bound-model", false); item$non_italic_tag = config.getBoolean("item.non-italic-tag", false); + item$update_triggers$attack = config.getBoolean("item.update-triggers.attack", false); + item$update_triggers$click_in_inventory = config.getBoolean("item.update-triggers.click-in-inventory", false); + item$update_triggers$drop = config.getBoolean("item.update-triggers.drop", false); + item$update_triggers$pick_up = config.getBoolean("item.update-triggers.pick-up", false); // block block$sound_system$enable = config.getBoolean("block.sound-system.enable", true); @@ -835,6 +843,22 @@ public class Config { return instance.recipe$ingredient_sources; } + public static boolean triggerUpdateAttack() { + return instance.item$update_triggers$attack; + } + + public static boolean triggerUpdateClick() { + return instance.item$update_triggers$click_in_inventory; + } + + public static boolean triggerUpdatePickUp() { + return instance.item$update_triggers$pick_up; + } + + public static boolean triggerUpdateDrop() { + return instance.item$update_triggers$drop; + } + public void setObf(boolean enable) { this.resource_pack$protection$obfuscation$enable = enable; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java b/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java index 482b2e1d5..806b20c22 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java +++ b/core/src/main/java/net/momirealms/craftengine/core/registry/BuiltInRegistries.java @@ -14,6 +14,7 @@ import net.momirealms.craftengine.core.item.recipe.network.legacy.LegacyRecipe; import net.momirealms.craftengine.core.item.recipe.network.modern.display.RecipeDisplay; import net.momirealms.craftengine.core.item.recipe.network.modern.display.slot.SlotDisplay; import net.momirealms.craftengine.core.item.recipe.result.PostProcessor; +import net.momirealms.craftengine.core.item.updater.ItemUpdaterType; import net.momirealms.craftengine.core.loot.LootContext; import net.momirealms.craftengine.core.loot.entry.LootEntryContainerFactory; import net.momirealms.craftengine.core.loot.function.ApplyBonusCountFunction; @@ -79,6 +80,7 @@ public class BuiltInRegistries { public static final Registry RECIPE_DISPLAY_TYPE = createConstantBoundRegistry(Registries.RECIPE_DISPLAY_TYPE); public static final Registry LEGACY_RECIPE_TYPE = createConstantBoundRegistry(Registries.LEGACY_RECIPE_TYPE); public static final Registry> RECIPE_POST_PROCESSOR_TYPE = createConstantBoundRegistry(Registries.RECIPE_POST_PROCESSOR_TYPE); + public static final Registry> ITEM_UPDATER_TYPE = createConstantBoundRegistry(Registries.ITEM_UPDATER_TYPE); private static Registry createConstantBoundRegistry(ResourceKey> key) { return new ConstantBoundRegistry<>(key); diff --git a/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java b/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java index 78e68ec0e..d06a53394 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java +++ b/core/src/main/java/net/momirealms/craftengine/core/registry/Registries.java @@ -14,6 +14,7 @@ import net.momirealms.craftengine.core.item.recipe.network.legacy.LegacyRecipe; import net.momirealms.craftengine.core.item.recipe.network.modern.display.RecipeDisplay; import net.momirealms.craftengine.core.item.recipe.network.modern.display.slot.SlotDisplay; import net.momirealms.craftengine.core.item.recipe.result.PostProcessor; +import net.momirealms.craftengine.core.item.updater.ItemUpdaterType; import net.momirealms.craftengine.core.loot.LootContext; import net.momirealms.craftengine.core.loot.entry.LootEntryContainerFactory; import net.momirealms.craftengine.core.loot.function.ApplyBonusCountFunction; @@ -81,4 +82,5 @@ public class Registries { public static final ResourceKey> RECIPE_DISPLAY_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("recipe_display_type")); public static final ResourceKey> LEGACY_RECIPE_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("legacy_recipe_type")); public static final ResourceKey>> RECIPE_POST_PROCESSOR_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("recipe_post_processor_type")); + public static final ResourceKey>> ITEM_UPDATER_TYPE = ResourceKey.create(ROOT_REGISTRY, Key.withDefaultNamespace("item_updater_type")); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/SingularPalette.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/SingularPalette.java index 4930529c9..35736ff09 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/SingularPalette.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/SingularPalette.java @@ -18,7 +18,7 @@ public class SingularPalette implements Palette { this.idList = idList; this.listener = listener; if (!entries.isEmpty()) { - this.entry = entries.get(0); + this.entry = entries.getFirst(); } } diff --git a/gradle.properties b/gradle.properties index b3c3e3382..4ce25d07a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,9 +2,9 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.61.7 -config_version=43 -lang_version=23 +project_version=0.0.61.8 +config_version=44 +lang_version=24 project_group=net.momirealms latest_supported_version=1.21.8