From 3552335d5d06dccd4f717510eda8bc7a39937a9e Mon Sep 17 00:00:00 2001 From: shartte Date: Mon, 16 Sep 2024 19:09:28 +0200 Subject: [PATCH] Introduce a custom recipe type for quartz knives to support damaging them on craft without duping them on repair (#8192) Fixes #8191 --- .../misc/network/parts/cable_anchor.json | 32 ---- .../recipe/network/parts/cable_anchor.json | 7 +- .../appeng/datagen/AE2DataGenerators.java | 2 + .../providers/recipes/CraftingRecipes.java | 6 - .../recipes/QuartzCuttingRecipesProvider.java | 38 +++++ .../tools/quartz/QuartzCuttingKnifeItem.java | 25 --- .../appeng/recipes/AERecipeSerializers.java | 2 + .../java/appeng/recipes/AERecipeTypes.java | 2 + .../quartzcutting/QuartzCuttingRecipe.java | 153 ++++++++++++++++++ .../QuartzCuttingRecipeSerializer.java | 23 +++ 10 files changed, 223 insertions(+), 67 deletions(-) delete mode 100644 src/generated/resources/data/ae2/advancement/recipes/misc/network/parts/cable_anchor.json create mode 100644 src/main/java/appeng/datagen/providers/recipes/QuartzCuttingRecipesProvider.java create mode 100644 src/main/java/appeng/recipes/quartzcutting/QuartzCuttingRecipe.java create mode 100644 src/main/java/appeng/recipes/quartzcutting/QuartzCuttingRecipeSerializer.java diff --git a/src/generated/resources/data/ae2/advancement/recipes/misc/network/parts/cable_anchor.json b/src/generated/resources/data/ae2/advancement/recipes/misc/network/parts/cable_anchor.json deleted file mode 100644 index 86b488cbde7..00000000000 --- a/src/generated/resources/data/ae2/advancement/recipes/misc/network/parts/cable_anchor.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "parent": "minecraft:recipes/root", - "criteria": { - "has_knife": { - "conditions": { - "items": [ - { - "items": "#ae2:knife" - } - ] - }, - "trigger": "minecraft:inventory_changed" - }, - "has_the_recipe": { - "conditions": { - "recipe": "ae2:network/parts/cable_anchor" - }, - "trigger": "minecraft:recipe_unlocked" - } - }, - "requirements": [ - [ - "has_the_recipe", - "has_knife" - ] - ], - "rewards": { - "recipes": [ - "ae2:network/parts/cable_anchor" - ] - } -} \ No newline at end of file diff --git a/src/generated/resources/data/ae2/recipe/network/parts/cable_anchor.json b/src/generated/resources/data/ae2/recipe/network/parts/cable_anchor.json index 91a833eaba1..c5d7d287b24 100644 --- a/src/generated/resources/data/ae2/recipe/network/parts/cable_anchor.json +++ b/src/generated/resources/data/ae2/recipe/network/parts/cable_anchor.json @@ -1,12 +1,11 @@ { - "type": "minecraft:crafting_shapeless", - "category": "misc", + "type": "ae2:quartz_cutting", "ingredients": [ { - "tag": "ae2:metal_ingots" + "tag": "ae2:knife" }, { - "tag": "ae2:knife" + "tag": "ae2:metal_ingots" } ], "result": { diff --git a/src/main/java/appeng/datagen/AE2DataGenerators.java b/src/main/java/appeng/datagen/AE2DataGenerators.java index 34284ad4ace..211edc4a6b6 100644 --- a/src/main/java/appeng/datagen/AE2DataGenerators.java +++ b/src/main/java/appeng/datagen/AE2DataGenerators.java @@ -51,6 +51,7 @@ import appeng.datagen.providers.recipes.EntropyRecipes; import appeng.datagen.providers.recipes.InscriberRecipes; import appeng.datagen.providers.recipes.MatterCannonAmmoProvider; +import appeng.datagen.providers.recipes.QuartzCuttingRecipesProvider; import appeng.datagen.providers.recipes.SmeltingRecipes; import appeng.datagen.providers.recipes.SmithingRecipes; import appeng.datagen.providers.recipes.TransformRecipes; @@ -116,6 +117,7 @@ public static void onGatherData(GatherDataEvent event) { pack.addProvider(bindRegistries(SmithingRecipes::new, registries)); pack.addProvider(bindRegistries(TransformRecipes::new, registries)); pack.addProvider(bindRegistries(ChargerRecipes::new, registries)); + pack.addProvider(bindRegistries(QuartzCuttingRecipesProvider::new, registries)); // Must run last pack.addProvider(packOutput -> localization); diff --git a/src/main/java/appeng/datagen/providers/recipes/CraftingRecipes.java b/src/main/java/appeng/datagen/providers/recipes/CraftingRecipes.java index 0ce2f70e58e..cfe828951c5 100644 --- a/src/main/java/appeng/datagen/providers/recipes/CraftingRecipes.java +++ b/src/main/java/appeng/datagen/providers/recipes/CraftingRecipes.java @@ -18,7 +18,6 @@ import net.minecraft.world.level.ItemLike; import net.neoforged.neoforge.common.crafting.DifferenceIngredient; -import appeng.api.ids.AETags; import appeng.api.stacks.AEKeyType; import appeng.api.util.AEColor; import appeng.core.AppEng; @@ -654,11 +653,6 @@ protected void buildRecipes(RecipeOutput consumer) { // recipes/network/parts // ==================================================== - ShapelessRecipeBuilder.shapeless(RecipeCategory.MISC, AEParts.CABLE_ANCHOR, 4) - .requires(AETags.METAL_INGOTS) - .requires(ConventionTags.QUARTZ_KNIFE) - .unlockedBy("has_knife", has(ConventionTags.QUARTZ_KNIFE)) - .save(consumer, AppEng.makeId("network/parts/cable_anchor")); ShapelessRecipeBuilder.shapeless(RecipeCategory.MISC, AEParts.ENERGY_ACCEPTOR) .requires(AEBlocks.ENERGY_ACCEPTOR) .unlockedBy("has_energy_acceptor", has(AEBlocks.ENERGY_ACCEPTOR)) diff --git a/src/main/java/appeng/datagen/providers/recipes/QuartzCuttingRecipesProvider.java b/src/main/java/appeng/datagen/providers/recipes/QuartzCuttingRecipesProvider.java new file mode 100644 index 00000000000..f7f41dd866f --- /dev/null +++ b/src/main/java/appeng/datagen/providers/recipes/QuartzCuttingRecipesProvider.java @@ -0,0 +1,38 @@ +package appeng.datagen.providers.recipes; + +import java.util.concurrent.CompletableFuture; + +import net.minecraft.core.HolderLookup; +import net.minecraft.core.NonNullList; +import net.minecraft.data.PackOutput; +import net.minecraft.data.recipes.RecipeOutput; +import net.minecraft.world.item.crafting.Ingredient; + +import appeng.api.ids.AETags; +import appeng.core.AppEng; +import appeng.core.definitions.AEParts; +import appeng.datagen.providers.tags.ConventionTags; +import appeng.recipes.quartzcutting.QuartzCuttingRecipe; + +public class QuartzCuttingRecipesProvider extends AE2RecipeProvider { + public QuartzCuttingRecipesProvider(PackOutput output, CompletableFuture registries) { + super(output, registries); + } + + @Override + protected void buildRecipes(RecipeOutput recipeOutput) { + recipeOutput.accept( + AppEng.makeId("network/parts/cable_anchor"), + new QuartzCuttingRecipe( + AEParts.CABLE_ANCHOR.stack(4), + NonNullList.of(Ingredient.EMPTY, + Ingredient.of(ConventionTags.QUARTZ_KNIFE), + Ingredient.of(AETags.METAL_INGOTS))), + null); + } + + @Override + public String getName() { + return "AE2 Quartz Cutting Recipes"; + } +} diff --git a/src/main/java/appeng/items/tools/quartz/QuartzCuttingKnifeItem.java b/src/main/java/appeng/items/tools/quartz/QuartzCuttingKnifeItem.java index 809f747bf99..dcf086e8325 100644 --- a/src/main/java/appeng/items/tools/quartz/QuartzCuttingKnifeItem.java +++ b/src/main/java/appeng/items/tools/quartz/QuartzCuttingKnifeItem.java @@ -18,10 +18,8 @@ package appeng.items.tools.quartz; -import org.apache.commons.lang3.mutable.MutableBoolean; import org.jetbrains.annotations.Nullable; -import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResultHolder; @@ -30,8 +28,6 @@ import net.minecraft.world.item.context.UseOnContext; import net.minecraft.world.level.Level; import net.minecraft.world.phys.BlockHitResult; -import net.neoforged.neoforge.common.CommonHooks; -import net.neoforged.neoforge.server.ServerLifecycleHooks; import appeng.api.implementations.menuobjects.IMenuItem; import appeng.api.implementations.menuobjects.ItemMenuHost; @@ -67,27 +63,6 @@ public InteractionResultHolder use(Level level, Player p, Interaction p.getItemInHand(hand)); } - @Override - public ItemStack getCraftingRemainingItem(ItemStack itemStack) { - var result = itemStack.copy(); - - var broken = new MutableBoolean(false); - if (CommonHooks.getCraftingPlayer() instanceof ServerPlayer serverPlayer) { - result.hurtAndBreak(1, serverPlayer.serverLevel(), serverPlayer, ignored -> broken.setTrue()); - } else { - var currentServer = ServerLifecycleHooks.getCurrentServer(); - if (currentServer != null) { - result.hurtAndBreak(1, currentServer.overworld(), null, ignored -> broken.setTrue()); - } - } - return broken.getValue() ? ItemStack.EMPTY : result; - } - - @Override - public boolean hasCraftingRemainingItem(ItemStack stack) { - return true; - } - @Nullable @Override public ItemMenuHost getMenuHost(Player player, ItemMenuHostLocator locator, diff --git a/src/main/java/appeng/recipes/AERecipeSerializers.java b/src/main/java/appeng/recipes/AERecipeSerializers.java index 7daf1ef3a36..8abb55b00e7 100644 --- a/src/main/java/appeng/recipes/AERecipeSerializers.java +++ b/src/main/java/appeng/recipes/AERecipeSerializers.java @@ -13,6 +13,7 @@ import appeng.recipes.handlers.ChargerRecipeSerializer; import appeng.recipes.handlers.InscriberRecipeSerializer; import appeng.recipes.mattercannon.MatterCannonAmmoSerializer; +import appeng.recipes.quartzcutting.QuartzCuttingRecipeSerializer; import appeng.recipes.transform.TransformRecipeSerializer; public final class AERecipeSerializers { @@ -32,6 +33,7 @@ private AERecipeSerializers() { register("storage_cell_upgrade", StorageCellUpgradeRecipeSerializer.INSTANCE); register("add_item_upgrade", AddItemUpgradeRecipeSerializer.INSTANCE); register("remove_item_upgrade", RemoveItemUpgradeRecipeSerializer.INSTANCE); + register("quartz_cutting", QuartzCuttingRecipeSerializer.INSTANCE); } private static void register(String id, RecipeSerializer serializer) { diff --git a/src/main/java/appeng/recipes/AERecipeTypes.java b/src/main/java/appeng/recipes/AERecipeTypes.java index 6ededf84f6b..7ff15e1e321 100644 --- a/src/main/java/appeng/recipes/AERecipeTypes.java +++ b/src/main/java/appeng/recipes/AERecipeTypes.java @@ -10,6 +10,7 @@ import appeng.recipes.handlers.ChargerRecipe; import appeng.recipes.handlers.InscriberRecipe; import appeng.recipes.mattercannon.MatterCannonAmmo; +import appeng.recipes.quartzcutting.QuartzCuttingRecipe; import appeng.recipes.transform.TransformRecipe; public final class AERecipeTypes { @@ -24,6 +25,7 @@ private AERecipeTypes() { public static final RecipeType INSCRIBER = register("inscriber"); public static final RecipeType CHARGER = register("charger"); public static final RecipeType MATTER_CANNON_AMMO = register("matter_cannon"); + public static final RecipeType QUARTZ_CUTTING = register("quartz_cutting"); private static > RecipeType register(String id) { RecipeType type = RecipeType.simple(AppEng.makeId(id)); diff --git a/src/main/java/appeng/recipes/quartzcutting/QuartzCuttingRecipe.java b/src/main/java/appeng/recipes/quartzcutting/QuartzCuttingRecipe.java new file mode 100644 index 00000000000..21adcd5e1c2 --- /dev/null +++ b/src/main/java/appeng/recipes/quartzcutting/QuartzCuttingRecipe.java @@ -0,0 +1,153 @@ +package appeng.recipes.quartzcutting; + +import java.util.ArrayList; + +import com.mojang.serialization.DataResult; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; + +import org.apache.commons.lang3.mutable.MutableBoolean; + +import net.minecraft.core.HolderLookup; +import net.minecraft.core.NonNullList; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.CraftingBookCategory; +import net.minecraft.world.item.crafting.CraftingInput; +import net.minecraft.world.item.crafting.CraftingRecipe; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.level.Level; +import net.neoforged.neoforge.common.CommonHooks; +import net.neoforged.neoforge.common.util.RecipeMatcher; +import net.neoforged.neoforge.server.ServerLifecycleHooks; + +import appeng.datagen.providers.tags.ConventionTags; + +public class QuartzCuttingRecipe implements CraftingRecipe { + static final int MAX_HEIGHT = 3; + static final int MAX_WIDTH = 3; + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec((builder) -> builder.group( + ItemStack.STRICT_CODEC.fieldOf("result").forGetter(QuartzCuttingRecipe::getResult), + Ingredient.CODEC_NONEMPTY.listOf().fieldOf("ingredients").flatXmap((r) -> { + var ingredients = r.toArray(Ingredient[]::new); + if (ingredients.length == 0) { + return DataResult.error(() -> "No ingredients for quartz cutting recipe"); + } else { + return ingredients.length > MAX_HEIGHT * MAX_WIDTH ? DataResult.error(() -> { + return "Too many ingredients for quartz cutting recipe. The maximum is: %s" + .formatted(MAX_HEIGHT * MAX_WIDTH); + }) : DataResult.success(NonNullList.of(Ingredient.EMPTY, ingredients)); + } + }, DataResult::success).forGetter(QuartzCuttingRecipe::getIngredients)) + .apply(builder, QuartzCuttingRecipe::new)); + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ItemStack.STREAM_CODEC, QuartzCuttingRecipe::getResult, + StreamCodec.of( + (buffer, value) -> { + buffer.writeVarInt(value.size()); + for (var ingredient : value) { + Ingredient.CONTENTS_STREAM_CODEC.encode(buffer, ingredient); + } + }, + buffer -> { + int count = buffer.readVarInt(); + NonNullList ingredients = NonNullList.withSize(count, Ingredient.EMPTY); + ingredients.replaceAll(ignored -> Ingredient.CONTENTS_STREAM_CODEC.decode(buffer)); + return ingredients; + }), + QuartzCuttingRecipe::getIngredients, + QuartzCuttingRecipe::new); + + final ItemStack result; + final NonNullList ingredients; + private final boolean isSimple; + + public QuartzCuttingRecipe(ItemStack result, NonNullList ingredients) { + this.result = result; + this.ingredients = ingredients; + this.isSimple = ingredients.stream().allMatch(Ingredient::isSimple); + } + + public RecipeSerializer getSerializer() { + return QuartzCuttingRecipeSerializer.INSTANCE; + } + + public CraftingBookCategory category() { + return CraftingBookCategory.MISC; + } + + public ItemStack getResultItem(HolderLookup.Provider registries) { + return this.result; + } + + public NonNullList getIngredients() { + return this.ingredients; + } + + public boolean matches(CraftingInput input, Level level) { + if (input.ingredientCount() != this.ingredients.size()) { + return false; + } else if (!this.isSimple) { + var nonEmptyItems = new ArrayList(input.ingredientCount()); + for (var item : input.items()) { + if (!item.isEmpty()) { + nonEmptyItems.add(item); + } + } + + return RecipeMatcher.findMatches(nonEmptyItems, this.ingredients) != null; + } else { + if (input.size() == 1 && this.ingredients.size() == 1) { + return this.ingredients.getFirst().test(input.getItem(0)); + } + return input.stackedContents().canCraft(this, null); + } + } + + public ItemStack assemble(CraftingInput input, HolderLookup.Provider registries) { + return this.result.copy(); + } + + public boolean canCraftInDimensions(int width, int height) { + return width * height >= this.ingredients.size(); + } + + private ItemStack getResult() { + return result; + } + + @Override + public NonNullList getRemainingItems(CraftingInput input) { + NonNullList remainingItems = NonNullList.withSize(input.size(), ItemStack.EMPTY); + + boolean damagedKnife = false; + + for (int i = 0; i < remainingItems.size(); i++) { + ItemStack item = input.getItem(i); + + if (!damagedKnife && item.is(ConventionTags.QUARTZ_KNIFE)) { + damagedKnife = true; + var result = item.copy(); + + var broken = new MutableBoolean(false); + if (CommonHooks.getCraftingPlayer() instanceof ServerPlayer serverPlayer) { + result.hurtAndBreak(1, serverPlayer.serverLevel(), serverPlayer, ignored -> broken.setTrue()); + } else { + var currentServer = ServerLifecycleHooks.getCurrentServer(); + if (currentServer != null) { + result.hurtAndBreak(1, currentServer.overworld(), null, ignored -> broken.setTrue()); + } + } + remainingItems.set(i, broken.getValue() ? ItemStack.EMPTY : result); + } else if (item.hasCraftingRemainingItem()) { + remainingItems.set(i, item.getCraftingRemainingItem()); + } + } + + return remainingItems; + } +} diff --git a/src/main/java/appeng/recipes/quartzcutting/QuartzCuttingRecipeSerializer.java b/src/main/java/appeng/recipes/quartzcutting/QuartzCuttingRecipeSerializer.java new file mode 100644 index 00000000000..8c64eb1f6c6 --- /dev/null +++ b/src/main/java/appeng/recipes/quartzcutting/QuartzCuttingRecipeSerializer.java @@ -0,0 +1,23 @@ +package appeng.recipes.quartzcutting; + +import com.mojang.serialization.MapCodec; + +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.world.item.crafting.RecipeSerializer; + +public class QuartzCuttingRecipeSerializer implements RecipeSerializer { + + public static final QuartzCuttingRecipeSerializer INSTANCE = new QuartzCuttingRecipeSerializer(); + + public QuartzCuttingRecipeSerializer() { + } + + public MapCodec codec() { + return QuartzCuttingRecipe.CODEC; + } + + public StreamCodec streamCodec() { + return QuartzCuttingRecipe.STREAM_CODEC; + } +}