Skip to content

Commit

Permalink
Add a BlockTagIngredient for when an item tag cannot be used/doesn't …
Browse files Browse the repository at this point in the history
…exist (#976)
  • Loading branch information
IThundxr authored May 29, 2024
1 parent 75ca31a commit 4854412
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
import net.neoforged.neoforge.common.conditions.OrCondition;
import net.neoforged.neoforge.common.conditions.TagEmptyCondition;
import net.neoforged.neoforge.common.conditions.TrueCondition;
import net.neoforged.neoforge.common.crafting.BlockTagIngredient;
import net.neoforged.neoforge.common.crafting.CompoundIngredient;
import net.neoforged.neoforge.common.crafting.DataComponentIngredient;
import net.neoforged.neoforge.common.crafting.DifferenceIngredient;
Expand Down Expand Up @@ -376,6 +377,7 @@ public class NeoForgeMod {
public static final DeferredHolder<IngredientType<?>, IngredientType<DataComponentIngredient>> DATA_COMPONENT_INGREDIENT_TYPE = INGREDIENT_TYPES.register("components", () -> new IngredientType<>(DataComponentIngredient.CODEC));
public static final DeferredHolder<IngredientType<?>, IngredientType<DifferenceIngredient>> DIFFERENCE_INGREDIENT_TYPE = INGREDIENT_TYPES.register("difference", () -> new IngredientType<>(DifferenceIngredient.CODEC));
public static final DeferredHolder<IngredientType<?>, IngredientType<IntersectionIngredient>> INTERSECTION_INGREDIENT_TYPE = INGREDIENT_TYPES.register("intersection", () -> new IngredientType<>(IntersectionIngredient.CODEC));
public static final DeferredHolder<IngredientType<?>, IngredientType<BlockTagIngredient>> BLOCK_TAG_INGREDIENT = INGREDIENT_TYPES.register("block_tag", () -> new IngredientType<>(BlockTagIngredient.CODEC));

private static final DeferredRegister<FluidIngredientType<?>> FLUID_INGREDIENT_TYPES = DeferredRegister.create(NeoForgeRegistries.Keys.FLUID_INGREDIENT_TYPES, NeoForgeVersion.MOD_ID);
public static final DeferredHolder<FluidIngredientType<?>, FluidIngredientType<SingleFluidIngredient>> SINGLE_FLUID_INGREDIENT_TYPE = FLUID_INGREDIENT_TYPES.register("single", () -> new FluidIngredientType<>(SingleFluidIngredient.CODEC));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/

package net.neoforged.neoforge.common.crafting;

import com.mojang.serialization.MapCodec;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import net.minecraft.core.Holder;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.neoforged.neoforge.common.NeoForgeMod;
import org.jetbrains.annotations.Nullable;

/**
* {@link Ingredient} that matches {@link ItemStack}s of {@link Block}s from a {@link TagKey<Block>}, useful in cases
* like {@code "minecraft:convertable_to_mud"} where there isn't an accompanying item tag
* <p>
* Notice: This should not be used as a replacement for the normal {@link Ingredient#of(TagKey)},
* This should only be used when there is no way an item tag can be used in your use case
*/
public class BlockTagIngredient implements ICustomIngredient {
public static final MapCodec<BlockTagIngredient> CODEC = TagKey.codec(Registries.BLOCK)
.xmap(BlockTagIngredient::new, BlockTagIngredient::getTag).fieldOf("tag");

protected final TagKey<Block> tag;

@Nullable
protected ItemStack[] itemStacks;

public BlockTagIngredient(TagKey<Block> tag) {
this.tag = tag;
}

protected void dissolve() {
if (itemStacks == null) {
List<ItemStack> list = new ArrayList<>();
for (Holder<Block> block : BuiltInRegistries.BLOCK.getTagOrEmpty(tag)) {
ItemStack stack = new ItemStack(block.value());
if (!stack.isEmpty()) {
list.add(stack);
}
}

if (list.isEmpty()) {
ItemStack itemStack = new ItemStack(Blocks.BARRIER);
itemStack.set(DataComponents.CUSTOM_NAME, Component.literal("Empty Tag: " + this.tag.location()));
list.add(itemStack);
}

itemStacks = list.toArray(ItemStack[]::new);
}
}

@Override
public Stream<ItemStack> getItems() {
dissolve();
return Stream.of(itemStacks);
}

@Override
public boolean test(@Nullable ItemStack stack) {
if (stack == null)
return false;

dissolve();
for (ItemStack itemStack : itemStacks) {
if (itemStack.is(stack.getItem())) {
return true;
}
}

return false;
}

public TagKey<Block> getTag() {
return tag;
}

@Override
public boolean isSimple() {
return true;
}

@Override
public IngredientType<?> getType() {
return NeoForgeMod.BLOCK_TAG_INGREDIENT.get();
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof BlockTagIngredient that)) return false;
return tag.equals(that.tag);
}

@Override
public int hashCode() {
return tag.hashCode();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"parent": "minecraft:recipes/root",
"criteria": {
"has_item": {
"conditions": {
"items": [
{
"items": "minecraft:water_bucket"
}
]
},
"trigger": "minecraft:inventory_changed"
},
"has_the_recipe": {
"conditions": {
"recipe": "neotests_block_tag_ingredient:block_tag"
},
"trigger": "minecraft:recipe_unlocked"
}
},
"requirements": [
[
"has_the_recipe",
"has_item"
]
],
"rewards": {
"recipes": [
"neotests_block_tag_ingredient:block_tag"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"type": "minecraft:crafting_shapeless",
"category": "misc",
"ingredients": [
{
"type": "neoforge:block_tag",
"tag": "minecraft:convertable_to_mud"
},
{
"item": "minecraft:water_bucket"
}
],
"result": {
"count": 1,
"id": "minecraft:mud"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.BlockTags;
import net.minecraft.tags.ItemTags;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
Expand All @@ -47,6 +48,7 @@
import net.minecraft.world.level.block.entity.CrafterBlockEntity;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.neoforged.neoforge.common.conditions.ICondition;
import net.neoforged.neoforge.common.crafting.BlockTagIngredient;
import net.neoforged.neoforge.common.crafting.CompoundIngredient;
import net.neoforged.neoforge.common.crafting.DataComponentIngredient;
import net.neoforged.neoforge.common.crafting.DifferenceIngredient;
Expand All @@ -66,6 +68,37 @@

@ForEachTest(groups = "crafting.ingredient")
public class IngredientTests {
@GameTest
@EmptyTemplate
@TestHolder(description = "Tests if BlockTagIngredient works")
static void blockTagIngredient(final DynamicTest test, final RegistrationHelper reg) {
reg.addProvider(event -> new RecipeProvider(event.getGenerator().getPackOutput(), event.getLookupProvider()) {
@Override
protected void buildRecipes(RecipeOutput output) {
ShapelessRecipeBuilder.shapeless(RecipeCategory.MISC, Items.MUD)
.requires(new TestEnabledIngredient(new BlockTagIngredient(BlockTags.CONVERTABLE_TO_MUD).toVanilla(), test.framework(), test.id()).toVanilla())
.requires(Items.WATER_BUCKET)
.unlockedBy("has_item", has(Items.WATER_BUCKET))
.save(output, new ResourceLocation(reg.modId(), "block_tag"));
}
});

test.onGameTest(helper -> helper
.startSequence()
.thenExecute(() -> helper.setBlock(1, 1, 1, Blocks.CRAFTER.defaultBlockState().setValue(BlockStateProperties.ORIENTATION, FrontAndTop.UP_NORTH).setValue(CrafterBlock.CRAFTING, true)))
.thenExecute(() -> helper.setBlock(1, 2, 1, Blocks.CHEST))

.thenMap(() -> helper.requireBlockEntity(1, 1, 1, CrafterBlockEntity.class))
.thenExecute(crafter -> crafter.setItem(0, Items.DIRT.getDefaultInstance()))
.thenExecute(crafter -> crafter.setItem(1, Items.WATER_BUCKET.getDefaultInstance()))
.thenIdle(3)

.thenExecute(() -> helper.pulseRedstone(1, 1, 2, 2))
.thenExecuteAfter(7, () -> helper.assertContainerContains(1, 2, 1, Items.MUD))

.thenSucceed());
}

@GameTest
@EmptyTemplate
@TestHolder(description = "Tests if partial NBT ingredients match the correct stacks")
Expand Down

0 comments on commit 4854412

Please sign in to comment.