Skip to content

Commit

Permalink
Introduce a custom recipe type for quartz knives to support damaging …
Browse files Browse the repository at this point in the history
…them on craft without duping them on repair (#8192)

Fixes #8191
  • Loading branch information
shartte authored Sep 16, 2024
1 parent 92e8dee commit 3552335
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 67 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/appeng/datagen/AE2DataGenerators.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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))
Expand Down
Original file line number Diff line number Diff line change
@@ -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<HolderLookup.Provider> 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";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -67,27 +63,6 @@ public InteractionResultHolder<ItemStack> 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,
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/appeng/recipes/AERecipeSerializers.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/appeng/recipes/AERecipeTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -24,6 +25,7 @@ private AERecipeTypes() {
public static final RecipeType<InscriberRecipe> INSCRIBER = register("inscriber");
public static final RecipeType<ChargerRecipe> CHARGER = register("charger");
public static final RecipeType<MatterCannonAmmo> MATTER_CANNON_AMMO = register("matter_cannon");
public static final RecipeType<QuartzCuttingRecipe> QUARTZ_CUTTING = register("quartz_cutting");

private static <T extends Recipe<?>> RecipeType<T> register(String id) {
RecipeType<T> type = RecipeType.simple(AppEng.makeId(id));
Expand Down
153 changes: 153 additions & 0 deletions src/main/java/appeng/recipes/quartzcutting/QuartzCuttingRecipe.java
Original file line number Diff line number Diff line change
@@ -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<QuartzCuttingRecipe> 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<RegistryFriendlyByteBuf, QuartzCuttingRecipe> 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<Ingredient> 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<Ingredient> ingredients;
private final boolean isSimple;

public QuartzCuttingRecipe(ItemStack result, NonNullList<Ingredient> 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<Ingredient> 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<ItemStack>(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<ItemStack> getRemainingItems(CraftingInput input) {
NonNullList<ItemStack> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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<QuartzCuttingRecipe> {

public static final QuartzCuttingRecipeSerializer INSTANCE = new QuartzCuttingRecipeSerializer();

public QuartzCuttingRecipeSerializer() {
}

public MapCodec<QuartzCuttingRecipe> codec() {
return QuartzCuttingRecipe.CODEC;
}

public StreamCodec<RegistryFriendlyByteBuf, QuartzCuttingRecipe> streamCodec() {
return QuartzCuttingRecipe.STREAM_CODEC;
}
}

0 comments on commit 3552335

Please sign in to comment.