From f1da4dc4a1fcc7ad4ca2cbc793f3bf813d464361 Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Sat, 2 Aug 2025 00:41:55 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E7=89=B9=E6=AE=8A=E9=85=8D?= =?UTF-8?q?=E6=96=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../item/listener/ItemEventListener.java | 2 +- .../item/recipe/BukkitRecipeManager.java | 10 +- .../item/recipe/RecipeEventListener.java | 96 ++---- .../plugin/injector/RecipeInjector.java | 289 +++++++++++++++--- .../craftengine/core/item/ItemKeys.java | 1 + .../craftengine/core/item/ItemSettings.java | 30 +- .../core/item/data/FireworkExplosion.java | 6 + gradle.properties | 4 +- 8 files changed, 317 insertions(+), 121 deletions(-) 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 2b2277636..ab5660c90 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 @@ -468,7 +468,7 @@ public class ItemEventListener implements Listener { @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onEntityDamage(EntityDamageEvent event) { if (event.getEntity() instanceof org.bukkit.entity.Item item) { - Optional.ofNullable(this.plugin.itemManager().wrap(item.getItemStack())) + Optional.of(this.plugin.itemManager().wrap(item.getItemStack())) .flatMap(Item::getCustomItem) .ifPresent(it -> { if (it.settings().invulnerable().contains(DamageCauseUtils.fromBukkit(event.getCause()))) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java index 5ae08c405..0b018c5aa 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java @@ -88,9 +88,15 @@ public class BukkitRecipeManager extends AbstractRecipeManager { try { Key dyeRecipeId = Key.from("armor_dye"); MINECRAFT_RECIPE_REMOVER.accept(dyeRecipeId); - MINECRAFT_RECIPE_ADDER.accept(dyeRecipeId, RecipeInjector.createCustomDyeRecipe()); + MINECRAFT_RECIPE_ADDER.accept(dyeRecipeId, RecipeInjector.createCustomDyeRecipe(dyeRecipeId)); + Key repairRecipeId = Key.from("repair_item"); + MINECRAFT_RECIPE_REMOVER.accept(repairRecipeId); + MINECRAFT_RECIPE_ADDER.accept(repairRecipeId, RecipeInjector.createRepairItemRecipe(repairRecipeId)); + Key fireworkStarFadeRecipeId = Key.from("firework_star_fade"); + MINECRAFT_RECIPE_REMOVER.accept(fireworkStarFadeRecipeId); + MINECRAFT_RECIPE_ADDER.accept(fireworkStarFadeRecipeId, RecipeInjector.createFireworkStarFadeRecipe(fireworkStarFadeRecipeId)); } catch (ReflectiveOperationException e) { - throw new ReflectionInitException("Failed to inject dye recipes", e); + throw new ReflectionInitException("Failed to inject special recipes", e); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java index 79e9a28f8..2eb25c028 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/RecipeEventListener.java @@ -481,7 +481,7 @@ public class RecipeEventListener implements Listener { // 如果禁止在铁砧使用两个相同物品修复 firstCustom.ifPresent(it -> { - if (!it.settings().canRepair()) { + if (it.settings().canRepair() == Tristate.FALSE) { event.setResult(null); } }); @@ -522,7 +522,7 @@ public class RecipeEventListener implements Listener { Key firstId = wrappedFirst.id(); Optional> optionalCustomTool = wrappedFirst.getCustomItem(); // 物品无法被修复 - if (optionalCustomTool.isPresent() && !optionalCustomTool.get().settings().canRepair()) { + if (optionalCustomTool.isPresent() && optionalCustomTool.get().settings().canRepair() == Tristate.FALSE) { return; } @@ -679,93 +679,52 @@ public class RecipeEventListener implements Listener { } // only handle repair items for the moment - @EventHandler(ignoreCancelled = true) + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onSpecialRecipe(PrepareItemCraftEvent event) { -// if (!ConfigManager.enableRecipeSystem()) return; org.bukkit.inventory.Recipe recipe = event.getRecipe(); - if (recipe == null) - return; if (!(recipe instanceof ComplexRecipe complexRecipe)) return; CraftingInventory inventory = event.getInventory(); + ItemStack result = inventory.getResult(); + if (ItemStackUtils.isEmpty(result)) + return; boolean hasCustomItem = ItemStackUtils.hasCustomItem(inventory.getMatrix()); - if (!hasCustomItem) { + if (!hasCustomItem) return; - } - if (!CraftBukkitReflections.clazz$CraftComplexRecipe.isInstance(complexRecipe)) { - inventory.setResult(null); return; } - try { - // TODO 全部改注入 Object mcRecipe = CraftBukkitReflections.field$CraftComplexRecipe$recipe.get(complexRecipe); - if (CoreReflections.clazz$ArmorDyeRecipe.isInstance(mcRecipe)) { + if (CoreReflections.clazz$ArmorDyeRecipe.isInstance(mcRecipe) || CoreReflections.clazz$FireworkStarFadeRecipe.isInstance(mcRecipe)) { return; } - - // Repair recipe + // 处理修复配方,在此处理才能使用玩家参数构建物品 if (CoreReflections.clazz$RepairItemRecipe.isInstance(mcRecipe)) { - // repair item - ItemStack[] itemStacks = inventory.getMatrix(); - Pair onlyTwoItems = getTheOnlyTwoItem(itemStacks); - if (onlyTwoItems.left() == null || onlyTwoItems.right() == null) { - inventory.setResult(null); - return; - } - - Item left = plugin.itemManager().wrap(onlyTwoItems.left()); - Item right = plugin.itemManager().wrap(onlyTwoItems.right()); - if (!left.id().equals(right.id())) { - inventory.setResult(null); - return; - } - - int totalDamage = right.damage().orElse(0) + left.damage().orElse(0); - int totalMaxDamage = left.maxDamage() + right.maxDamage(); - // should be impossible, but take care - if (totalDamage >= totalMaxDamage) { - inventory.setResult(null); - return; - } - - Player player = InventoryUtils.getPlayerFromInventoryEvent(event); - - Optional> customItemOptional = plugin.itemManager().getCustomItem(left.id()); + Pair theOnlyTwoItem = getTheOnlyTwoItem(inventory.getMatrix()); + if (theOnlyTwoItem == null) return; + Item first = BukkitItemManager.instance().wrap(theOnlyTwoItem.left()); + Item right = BukkitItemManager.instance().wrap(theOnlyTwoItem.right()); + int max = Math.max(first.maxDamage(), right.maxDamage()); + int durability1 = first.maxDamage() - first.damage().orElse(0); + int durability2 = right.maxDamage() - right.damage().orElse(0); + int finalDurability = durability1 + durability2 + max * 5 / 100; + Optional> customItemOptional = plugin.itemManager().getCustomItem(first.id()); if (customItemOptional.isEmpty()) { inventory.setResult(null); return; } - - CustomItem customItem = customItemOptional.get(); - if (!customItem.settings().canRepair()) { - inventory.setResult(null); - return; - } - - Item newItem = customItem.buildItem(ItemBuildContext.of(plugin.adapt(player))); - int remainingDurability = totalMaxDamage - totalDamage; - int newItemDamage = Math.max(0, newItem.maxDamage() - remainingDurability); - newItem.damage(newItemDamage); + Player player = InventoryUtils.getPlayerFromInventoryEvent(event); + Item newItem = customItemOptional.get().buildItem(plugin.adapt(player)); + newItem.maxDamage(max); + newItem.damage(Math.max(max - finalDurability, 0)); inventory.setResult(newItem.getItem()); - } else if (CoreReflections.clazz$FireworkStarFadeRecipe.isInstance(mcRecipe)) { - ItemStack[] itemStacks = inventory.getMatrix(); - for (ItemStack itemStack : itemStacks) { - if (itemStack == null) continue; - Item item = plugin.itemManager().wrap(itemStack); - Optional> optionalCustomItem = item.getCustomItem(); - if (optionalCustomItem.isPresent() && optionalCustomItem.get().settings().dyeable() == Tristate.FALSE) { - inventory.setResult(null); - return; - } - } - } else { - inventory.setResult(null); return; } + // 其他配方不允许使用自定义物品 + inventory.setResult(null); } catch (Exception e) { - this.plugin.logger().warn("Failed to handle minecraft custom recipe", e); + this.plugin.logger().warn("Failed to handle custom recipe", e); } } @@ -776,7 +735,10 @@ public class RecipeEventListener implements Listener { if (itemStack == null) continue; if (first == null) { first = itemStack; - } else if (second == null) { + } else { + if (second != null) { + return null; + } second = itemStack; } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/RecipeInjector.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/RecipeInjector.java index 3298c5eba..8c8babf2e 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/RecipeInjector.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/injector/RecipeInjector.java @@ -1,8 +1,11 @@ package net.momirealms.craftengine.bukkit.plugin.injector; import com.mojang.datafixers.util.Pair; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; import net.bytebuddy.ByteBuddy; import net.bytebuddy.ClassFileVersion; +import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.modifier.Visibility; import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; import net.bytebuddy.implementation.FieldAccessor; @@ -10,8 +13,10 @@ import net.bytebuddy.implementation.MethodDelegation; import net.bytebuddy.implementation.bind.annotation.AllArguments; import net.bytebuddy.implementation.bind.annotation.RuntimeType; import net.bytebuddy.implementation.bind.annotation.This; +import net.bytebuddy.matcher.ElementMatcher; import net.bytebuddy.matcher.ElementMatchers; import net.momirealms.craftengine.bukkit.item.BukkitItemManager; +import net.momirealms.craftengine.bukkit.item.ComponentTypes; import net.momirealms.craftengine.bukkit.item.recipe.BukkitRecipeManager; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; @@ -21,12 +26,15 @@ import net.momirealms.craftengine.bukkit.util.ItemTags; import net.momirealms.craftengine.bukkit.util.KeyUtils; import net.momirealms.craftengine.core.item.CustomItem; import net.momirealms.craftengine.core.item.Item; +import net.momirealms.craftengine.core.item.ItemKeys; +import net.momirealms.craftengine.core.item.data.FireworkExplosion; import net.momirealms.craftengine.core.item.recipe.CustomCookingRecipe; import net.momirealms.craftengine.core.item.recipe.RecipeTypes; import net.momirealms.craftengine.core.item.recipe.UniqueIdItem; import net.momirealms.craftengine.core.item.recipe.input.SingleItemInput; import net.momirealms.craftengine.core.util.*; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.lang.reflect.Constructor; @@ -40,6 +48,8 @@ import java.util.function.Predicate; public class RecipeInjector { private static Class clazz$InjectedCacheChecker; private static Class clazz$InjectedArmorDyeRecipe; + private static Class clazz$InjectedRepairItemRecipe; + private static Class clazz$InjectedFireworkStarFadeRecipe; public static void init() { ByteBuddy byteBuddy = new ByteBuddy(ClassFileVersion.JAVA_V17); @@ -48,64 +58,96 @@ public class RecipeInjector { .name("net.momirealms.craftengine.bukkit.entity.InjectedCacheChecker") .implement(CoreReflections.clazz$RecipeManager$CachedCheck) .implement(InjectedCacheCheck.class) - .defineField("recipeType", Object.class, Visibility.PUBLIC) .method(ElementMatchers.named("recipeType")) .intercept(FieldAccessor.ofField("recipeType")) - .defineField("customRecipeType", Key.class, Visibility.PUBLIC) .method(ElementMatchers.named("customRecipeType")) .intercept(FieldAccessor.ofField("customRecipeType")) - .defineField("lastRecipe", Object.class, Visibility.PUBLIC) .method(ElementMatchers.named("lastRecipe")) .intercept(FieldAccessor.ofField("lastRecipe")) - .defineField("lastCustomRecipe", Key.class, Visibility.PUBLIC) .method(ElementMatchers.named("lastCustomRecipe")) .intercept(FieldAccessor.ofField("lastCustomRecipe")) - .method(ElementMatchers.named("getRecipeFor").or(ElementMatchers.named("a"))) .intercept(MethodDelegation.to( VersionHelper.isOrAbove1_21_2() ? - GetRecipeForMethodInterceptor1_21_2.INSTANCE : - (VersionHelper.isOrAbove1_21() ? - GetRecipeForMethodInterceptor1_21.INSTANCE : - VersionHelper.isOrAbove1_20_5() ? - GetRecipeForMethodInterceptor1_20_5.INSTANCE : - GetRecipeForMethodInterceptor1_20.INSTANCE) + GetRecipeForMethodInterceptor1_21_2.INSTANCE : + (VersionHelper.isOrAbove1_21() ? + GetRecipeForMethodInterceptor1_21.INSTANCE : + VersionHelper.isOrAbove1_20_5() ? + GetRecipeForMethodInterceptor1_20_5.INSTANCE : + GetRecipeForMethodInterceptor1_20.INSTANCE) )) .make() .load(RecipeInjector.class.getClassLoader()) .getLoaded(); + ElementMatcher.Junction matches = (VersionHelper.isOrAbove1_21() ? + ElementMatchers.takesArguments(CoreReflections.clazz$CraftingInput, CoreReflections.clazz$Level) : + ElementMatchers.takesArguments(CoreReflections.clazz$CraftingContainer, CoreReflections.clazz$Level) + ).and(ElementMatchers.returns(boolean.class)); + ElementMatcher.Junction assemble = ( + VersionHelper.isOrAbove1_21() ? + ElementMatchers.takesArguments(CoreReflections.clazz$CraftingInput, CoreReflections.clazz$HolderLookup$Provider) : + VersionHelper.isOrAbove1_20_5() ? + ElementMatchers.takesArguments(CoreReflections.clazz$CraftingContainer, CoreReflections.clazz$HolderLookup$Provider) : + ElementMatchers.takesArguments(CoreReflections.clazz$CraftingContainer, CoreReflections.clazz$RegistryAccess) + ).and(ElementMatchers.returns(CoreReflections.clazz$ItemStack)); + clazz$InjectedArmorDyeRecipe = byteBuddy .subclass(CoreReflections.clazz$ArmorDyeRecipe, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING) .name("net.momirealms.craftengine.bukkit.item.recipe.ArmorDyeRecipe") - .method((VersionHelper.isOrAbove1_21() ? - ElementMatchers.takesArguments(CoreReflections.clazz$CraftingInput, CoreReflections.clazz$Level) : - ElementMatchers.takesArguments(CoreReflections.clazz$CraftingContainer, CoreReflections.clazz$Level) - ).and(ElementMatchers.returns(boolean.class))) - .intercept(MethodDelegation.to(MatchesInterceptor.INSTANCE)) - .method(( - VersionHelper.isOrAbove1_21() ? - ElementMatchers.takesArguments(CoreReflections.clazz$CraftingInput, CoreReflections.clazz$HolderLookup$Provider) : - VersionHelper.isOrAbove1_20_5() ? - ElementMatchers.takesArguments(CoreReflections.clazz$CraftingContainer, CoreReflections.clazz$HolderLookup$Provider) : - ElementMatchers.takesArguments(CoreReflections.clazz$CraftingContainer, CoreReflections.clazz$RegistryAccess) - ).and(ElementMatchers.returns(CoreReflections.clazz$ItemStack))) - .intercept(MethodDelegation.to(AssembleInterceptor.INSTANCE)) + .method(matches) + .intercept(MethodDelegation.to(DyeMatchesInterceptor.INSTANCE)) + .method(assemble) + .intercept(MethodDelegation.to(DyeAssembleInterceptor.INSTANCE)) + .make() + .load(RecipeInjector.class.getClassLoader()) + .getLoaded(); + + clazz$InjectedFireworkStarFadeRecipe = byteBuddy + .subclass(CoreReflections.clazz$FireworkStarFadeRecipe) + .name("net.momirealms.craftengine.bukkit.item.recipe.FireworkStarFadeRecipe") + .method(matches) + .intercept(MethodDelegation.to(FireworkStarFadeMatchesInterceptor.INSTANCE)) + .method(assemble) + .intercept(MethodDelegation.to(FireworkStarFadeAssembleInterceptor.INSTANCE)) + .make() + .load(RecipeInjector.class.getClassLoader()) + .getLoaded(); + + clazz$InjectedRepairItemRecipe = byteBuddy + .subclass(CoreReflections.clazz$RepairItemRecipe, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING) + .name("net.momirealms.craftengine.bukkit.item.recipe.RepairItemRecipe") + // 只修改match逻辑,合并需要在事件里处理,否则无法应用变量 + .method(matches) + .intercept(MethodDelegation.to(RepairMatchesInterceptor.INSTANCE)) .make() .load(RecipeInjector.class.getClassLoader()) .getLoaded(); } - public static Object createCustomDyeRecipe() throws ReflectiveOperationException { + public static Object createRepairItemRecipe(Key id) throws ReflectiveOperationException { + return createSpecialRecipe(id, clazz$InjectedRepairItemRecipe); + } + + public static Object createCustomDyeRecipe(Key id) throws ReflectiveOperationException { + return createSpecialRecipe(id, clazz$InjectedArmorDyeRecipe); + } + + public static Object createFireworkStarFadeRecipe(Key id) throws ReflectiveOperationException { + return createSpecialRecipe(id, clazz$InjectedFireworkStarFadeRecipe); + } + + @NotNull + private static Object createSpecialRecipe(Key id, Class clazz$InjectedRepairItemRecipe) throws InstantiationException, IllegalAccessException, java.lang.reflect.InvocationTargetException { if (VersionHelper.isOrAbove1_20_2()) { - Constructor constructor = ReflectionUtils.getConstructor(clazz$InjectedArmorDyeRecipe, CoreReflections.clazz$CraftingBookCategory); + Constructor constructor = ReflectionUtils.getConstructor(clazz$InjectedRepairItemRecipe, CoreReflections.clazz$CraftingBookCategory); return constructor.newInstance(CoreReflections.instance$CraftingBookCategory$MISC); } else { - Constructor constructor = ReflectionUtils.getConstructor(clazz$InjectedArmorDyeRecipe, CoreReflections.clazz$ResourceLocation, CoreReflections.clazz$CraftingBookCategory); - return constructor.newInstance(KeyUtils.toResourceLocation(Key.of("armor_dye")), CoreReflections.instance$CraftingBookCategory$MISC); + Constructor constructor = ReflectionUtils.getConstructor(clazz$InjectedRepairItemRecipe, CoreReflections.clazz$ResourceLocation, CoreReflections.clazz$CraftingBookCategory); + return constructor.newInstance(KeyUtils.toResourceLocation(id), CoreReflections.instance$CraftingBookCategory$MISC); } } @@ -138,29 +180,161 @@ public class RecipeInjector { } } - private static final Function INGREDIENT_COUNT_CHECKER = + private static final Function INGREDIENT_SIZE_GETTER = VersionHelper.isOrAbove1_21() ? - (input) -> FastNMS.INSTANCE.method$CraftingInput$ingredientCount(input) < 2 : - (container) -> false; - private static final Function INGREDIENT_COUNT_GETTER = - VersionHelper.isOrAbove1_21() ? - FastNMS.INSTANCE::method$CraftingInput$ingredientCount : + FastNMS.INSTANCE::method$CraftingInput$size : FastNMS.INSTANCE::method$Container$getContainerSize; private static final BiFunction INGREDIENT_GETTER = VersionHelper.isOrAbove1_21() ? FastNMS.INSTANCE::method$CraftingInput$getItem : FastNMS.INSTANCE::method$Container$getItem; - public static class MatchesInterceptor { - public static final MatchesInterceptor INSTANCE = new MatchesInterceptor(); + private static final Function REPAIR_INGREDIENT_COUNT_CHECKER = + VersionHelper.isOrAbove1_21() ? + (input) -> FastNMS.INSTANCE.method$CraftingInput$ingredientCount(input) != 2 : + (container) -> false; + + public static class FireworkStarFadeMatchesInterceptor { + public static final FireworkStarFadeMatchesInterceptor INSTANCE = new FireworkStarFadeMatchesInterceptor(); @RuntimeType public Object intercept(@AllArguments Object[] args) { Object input = args[0]; - if (INGREDIENT_COUNT_CHECKER.apply(input)) { + if (DYE_INGREDIENT_COUNT_CHECKER.apply(input)) { return false; } - int size = INGREDIENT_COUNT_GETTER.apply(input); + boolean hasDye = false; + boolean hasFireworkStar = false; + int size = INGREDIENT_SIZE_GETTER.apply(input); + for (int i = 0; i < size; i++) { + Object itemStack = INGREDIENT_GETTER.apply(input, i); + if (FastNMS.INSTANCE.method$ItemStack$isEmpty(itemStack)) { + continue; + } + Item wrapped = BukkitItemManager.instance().wrap(FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(itemStack)); + if (isFireworkDye(wrapped)) { + hasDye = true; + } else { + if (!wrapped.id().equals(ItemKeys.FIREWORK_STAR)) { + return false; + } + if (hasFireworkStar) { + return false; + } + hasFireworkStar = true; + } + } + return hasDye && hasFireworkStar; + } + } + + public static class FireworkStarFadeAssembleInterceptor { + public static final FireworkStarFadeAssembleInterceptor INSTANCE = new FireworkStarFadeAssembleInterceptor(); + + @RuntimeType + public Object intercept(@AllArguments Object[] args) { + IntList colors = new IntArrayList(); + Item starItem = null; + Object input = args[0]; + int size = INGREDIENT_SIZE_GETTER.apply(input); + for (int i = 0; i < size; i++) { + Object itemStack = INGREDIENT_GETTER.apply(input, i); + if (FastNMS.INSTANCE.method$ItemStack$isEmpty(itemStack)) { + continue; + } + Item wrapped = BukkitItemManager.instance().wrap(FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(itemStack)); + if (isFireworkDye(wrapped)) { + Color color = getFireworkColor(wrapped); + if (color == null) { + return CoreReflections.instance$ItemStack$EMPTY; + } + colors.add(color.color()); + } else if (wrapped.id().equals(ItemKeys.FIREWORK_STAR)) { + starItem = wrapped.copyWithCount(1); + } + } + if (starItem == null || colors.isEmpty()) { + return CoreReflections.instance$ItemStack$EMPTY; + } + FireworkExplosion explosion = starItem.fireworkExplosion().orElse(FireworkExplosion.DEFAULT); + starItem.fireworkExplosion(explosion.withFadeColors(colors)); + return starItem.getLiteralObject(); + } + } + + public static class RepairMatchesInterceptor { + public static final RepairMatchesInterceptor INSTANCE = new RepairMatchesInterceptor(); + + @RuntimeType + public Object intercept(@AllArguments Object[] args) { + Object input = args[0]; + if (REPAIR_INGREDIENT_COUNT_CHECKER.apply(input)) { + return false; + } + return getItemsToCombine(input) != null; + } + } + + @Nullable + private static Pair, Item> getItemsToCombine(Object input) { + Item item1 = null; + Item item2 = null; + int size = INGREDIENT_SIZE_GETTER.apply(input); + for (int i = 0; i < size; i++) { + Object itemStack = INGREDIENT_GETTER.apply(input, i); + if (FastNMS.INSTANCE.method$ItemStack$isEmpty(itemStack)) { + continue; + } + Item wrapped = BukkitItemManager.instance().wrap(FastNMS.INSTANCE.method$CraftItemStack$asCraftMirror(itemStack)); + if (item1 == null) { + item1 = wrapped; + } else { + if (item2 != null) { + return null; + } + item2 = wrapped; + } + } + if (item1 == null || item2 == null) { + return null; + } + if (!canCombine(item1, item2)) { + return null; + } + return new Pair<>(item1, item2); + } + + private static boolean canCombine(Item input1, Item input2) { + if (input1.count() != 1 || !isDamageableItem(input1)) return false; + if (input2.count() != 1 || !isDamageableItem(input2)) return false; + if (!input1.id().equals(input2.id())) return false; + Optional> customItem = input1.getCustomItem(); + return customItem.isEmpty() || customItem.get().settings().canRepair() != Tristate.FALSE; + } + + private static boolean isDamageableItem(Item item) { + if (VersionHelper.isOrAbove1_20_5()) { + return item.hasComponent(ComponentTypes.MAX_DAMAGE) && item.hasComponent(ComponentTypes.DAMAGE); + } else { + return FastNMS.INSTANCE.method$Item$canBeDepleted(FastNMS.INSTANCE.method$ItemStack$getItem(item.getLiteralObject())); + } + } + + private static final Function DYE_INGREDIENT_COUNT_CHECKER = + VersionHelper.isOrAbove1_21() ? + (input) -> FastNMS.INSTANCE.method$CraftingInput$ingredientCount(input) < 2 : + (container) -> false; + + public static class DyeMatchesInterceptor { + public static final DyeMatchesInterceptor INSTANCE = new DyeMatchesInterceptor(); + + @RuntimeType + public Object intercept(@AllArguments Object[] args) { + Object input = args[0]; + if (DYE_INGREDIENT_COUNT_CHECKER.apply(input)) { + return false; + } + int size = INGREDIENT_SIZE_GETTER.apply(input); Item itemToDye = null; boolean hasDye = false; for (int i = 0; i < size; i++) { @@ -175,7 +349,7 @@ public class RecipeInjector { } itemToDye = wrapped; } else { - if (!isDye(wrapped)) { + if (!isArmorDye(wrapped)) { return false; } hasDye = true; @@ -185,15 +359,15 @@ public class RecipeInjector { } } - public static class AssembleInterceptor { - public static final AssembleInterceptor INSTANCE = new AssembleInterceptor(); + public static class DyeAssembleInterceptor { + public static final DyeAssembleInterceptor INSTANCE = new DyeAssembleInterceptor(); @RuntimeType public Object intercept(@AllArguments Object[] args) { List colors = new ArrayList<>(); Item itemToDye = null; Object input = args[0]; - int size = INGREDIENT_COUNT_GETTER.apply(input); + int size = INGREDIENT_SIZE_GETTER.apply(input); for (int i = 0; i < size; i++) { Object itemStack = INGREDIENT_GETTER.apply(input, i); if (FastNMS.INSTANCE.method$ItemStack$isEmpty(itemStack)) { @@ -212,7 +386,7 @@ public class RecipeInjector { } } if (itemToDye == null || itemToDye.isEmpty() || colors.isEmpty()) { - return null; + return CoreReflections.instance$ItemStack$EMPTY; } return itemToDye.applyDyedColors(colors).getLiteralObject(); } @@ -228,6 +402,16 @@ public class RecipeInjector { return getVanillaDyeColor(dyeItem); } + @Nullable + private static Color getFireworkColor(final Item dyeItem) { + Optional> optionalCustomItem = dyeItem.getCustomItem(); + if (optionalCustomItem.isPresent()) { + CustomItem customItem = optionalCustomItem.get(); + return Optional.ofNullable(customItem.settings().fireworkColor()).orElseGet(() -> getVanillaFireworkColor(dyeItem)); + } + return getVanillaFireworkColor(dyeItem); + } + private static final Predicate> IS_DYEABLE = VersionHelper.isOrAbove1_20_5() ? (item -> item.is(ItemTags.DYEABLE)) : @@ -258,7 +442,15 @@ public class RecipeInjector { return Color.fromDecimal(FastNMS.INSTANCE.method$DyeColor$getTextureDiffuseColor(FastNMS.INSTANCE.method$DyeItem$getDyeColor(dyeItem))); } - private static boolean isDye(Item dyeItem) { + @Nullable + private static Color getVanillaFireworkColor(final Item item) { + Object itemStack = item.getLiteralObject(); + Object dyeItem = FastNMS.INSTANCE.method$ItemStack$getItem(itemStack); + if (!CoreReflections.clazz$DyeItem.isInstance(dyeItem)) return null; + return Color.fromDecimal(FastNMS.INSTANCE.method$DyeColor$getFireworkColor(FastNMS.INSTANCE.method$DyeItem$getDyeColor(dyeItem))); + } + + private static boolean isArmorDye(Item dyeItem) { Optional> optionalCustomItem = dyeItem.getCustomItem(); if (optionalCustomItem.isPresent()) { CustomItem customItem = optionalCustomItem.get(); @@ -267,6 +459,15 @@ public class RecipeInjector { return isVanillaDyeItem(dyeItem); } + private static boolean isFireworkDye(Item dyeItem) { + Optional> optionalCustomItem = dyeItem.getCustomItem(); + if (optionalCustomItem.isPresent()) { + CustomItem customItem = optionalCustomItem.get(); + return customItem.settings().fireworkColor() != null || isVanillaDyeItem(dyeItem); + } + return isVanillaDyeItem(dyeItem); + } + private static boolean isVanillaDyeItem(Item item) { return CoreReflections.clazz$DyeItem.isInstance(FastNMS.INSTANCE.method$ItemStack$getItem(item.getLiteralObject())); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/ItemKeys.java b/core/src/main/java/net/momirealms/craftengine/core/item/ItemKeys.java index 841487680..cf0c172e5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/ItemKeys.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/ItemKeys.java @@ -60,6 +60,7 @@ public final class ItemKeys { public static final Key GREEN_DYE = Key.of("minecraft:green_dye"); public static final Key RED_DYE = Key.of("minecraft:red_dye"); public static final Key BLACK_DYE = Key.of("minecraft:black_dye"); + public static final Key FIREWORK_STAR = Key.of("minecraft:firework_star"); public static final Key[] AXES = new Key[] { WOODEN_AXE, STONE_AXE, IRON_AXE, GOLDEN_AXE, DIAMOND_AXE, NETHERITE_AXE 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 526f00969..f8d5ad125 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 @@ -25,7 +25,7 @@ import java.util.stream.Collectors; public class ItemSettings { int fuelTime; Set tags = Set.of(); - boolean canRepair = true; + Tristate canRepair = Tristate.UNDEFINED; List anvilRepairItems = List.of(); boolean renameable = true; boolean canPlaceRelatedVanillaBlock = false; @@ -43,6 +43,8 @@ public class ItemSettings { ItemEquipment equipment; @Nullable Color dyeColor; + @Nullable + Color fireworkColor; private ItemSettings() {} @@ -102,6 +104,7 @@ public class ItemSettings { newSettings.compostProbability = settings.compostProbability; newSettings.respectRepairableComponent = settings.respectRepairableComponent; newSettings.dyeColor = settings.dyeColor; + newSettings.fireworkColor = settings.fireworkColor; return newSettings; } @@ -125,7 +128,7 @@ public class ItemSettings { return canPlaceRelatedVanillaBlock; } - public boolean canRepair() { + public Tristate canRepair() { return canRepair; } @@ -184,7 +187,12 @@ public class ItemSettings { @Nullable public Color dyeColor() { - return dyeColor; + return this.dyeColor; + } + + @Nullable + public Color fireworkColor() { + return this.fireworkColor; } public List invulnerable() { @@ -195,6 +203,11 @@ public class ItemSettings { return compostProbability; } + public ItemSettings fireworkColor(Color color) { + this.fireworkColor = color; + return this; + } + public ItemSettings dyeColor(Color color) { this.dyeColor = color; return this; @@ -220,7 +233,7 @@ public class ItemSettings { return this; } - public ItemSettings canRepair(boolean canRepair) { + public ItemSettings canRepair(Tristate canRepair) { this.canRepair = canRepair; return this; } @@ -303,7 +316,7 @@ public class ItemSettings { static { registerFactory("repairable", (value -> { boolean bool = ResourceConfigUtils.getAsBoolean(value, "repairable"); - return settings -> settings.canRepair(bool); + return settings -> settings.canRepair(bool ? Tristate.TRUE : Tristate.FALSE); })); registerFactory("enchantable", (value -> { boolean bool = ResourceConfigUtils.getAsBoolean(value, "enchantable"); @@ -416,6 +429,13 @@ public class ItemSettings { return settings -> settings.dyeColor(Color.fromVector3f(MiscUtils.getAsVector3f(value, "dye-color"))); } })); + registerFactory("firework-color", (value -> { + if (value instanceof Integer i) { + return settings -> settings.fireworkColor(Color.fromDecimal(i)); + } else { + return settings -> settings.fireworkColor(Color.fromVector3f(MiscUtils.getAsVector3f(value, "firework-color"))); + } + })); registerFactory("food", (value -> { Map args = MiscUtils.castToMap(value, false); FoodData data = new FoodData( diff --git a/core/src/main/java/net/momirealms/craftengine/core/item/data/FireworkExplosion.java b/core/src/main/java/net/momirealms/craftengine/core/item/data/FireworkExplosion.java index bad976667..279f51799 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/item/data/FireworkExplosion.java +++ b/core/src/main/java/net/momirealms/craftengine/core/item/data/FireworkExplosion.java @@ -8,6 +8,12 @@ import java.util.Map; public record FireworkExplosion(Shape shape, IntList colors, IntList fadeColors, boolean hasTrail, boolean hasTwinkle) { + public static final FireworkExplosion DEFAULT = new FireworkExplosion(Shape.SMALL_BALL, IntList.of(), IntList.of(), false, false); + + public FireworkExplosion withFadeColors(@NotNull final IntList fadeColors) { + return new FireworkExplosion(this.shape, this.colors, fadeColors, this.hasTrail, this.hasTwinkle); + } + public enum Shape { SMALL_BALL(0, "small_ball"), LARGE_BALL(1, "large_ball"), diff --git a/gradle.properties b/gradle.properties index 1d95c9947..837b678ab 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G # Project settings # Rule: [major update].[feature update].[bug fix] -project_version=0.0.60.9 +project_version=0.0.60.10 config_version=43 lang_version=23 project_group=net.momirealms @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.4 anti_grief_version=0.18 -nms_helper_version=1.0.46 +nms_helper_version=1.0.48 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.31.23