Skip to content

Commit 7cba2a8

Browse files
committed
Add unit tests and basic property filtering to item catalyst matcher.
1 parent 62db9b1 commit 7cba2a8

9 files changed

Lines changed: 239 additions & 12 deletions

File tree

src/main/java/dev/compactmods/crafting/recipes/catalyst/CatalystMatcherCodec.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@ public class CatalystMatcherCodec implements Codec<CatalystType<?>> {
2020
@Override
2121
public <T> DataResult<T> encode(CatalystType<?> input, DynamicOps<T> ops, T prefix) {
2222
ResourceLocation key = input.getRegistryName();
23-
if(key == null)
24-
return DataResult.error("Unknown registry element " + input);
25-
2623
T toMerge = ops.createString(key.toString());
2724
return ops.mergeToPrimitive(prefix, toMerge);
2825
}

src/main/java/dev/compactmods/crafting/recipes/catalyst/ItemStackCatalystMatcher.java

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,95 @@
11
package dev.compactmods.crafting.recipes.catalyst;
22

33
import java.util.HashSet;
4+
import java.util.Optional;
45
import java.util.Set;
6+
import java.util.function.Predicate;
57
import com.mojang.serialization.Codec;
68
import com.mojang.serialization.codecs.RecordCodecBuilder;
79
import dev.compactmods.crafting.Registration;
810
import dev.compactmods.crafting.api.catalyst.CatalystType;
911
import dev.compactmods.crafting.api.catalyst.ICatalystMatcher;
1012
import net.minecraft.item.Item;
1113
import net.minecraft.item.ItemStack;
14+
import net.minecraft.nbt.CompoundNBT;
15+
import net.minecraft.nbt.INBT;
1216
import net.minecraft.util.ResourceLocation;
17+
import net.minecraftforge.common.util.Constants;
1318
import net.minecraftforge.registries.ForgeRegistries;
1419
import net.minecraftforge.registries.ForgeRegistryEntry;
1520

1621
public class ItemStackCatalystMatcher extends ForgeRegistryEntry<CatalystType<?>>
1722
implements ICatalystMatcher, CatalystType<ItemStackCatalystMatcher> {
1823

1924
public static final Codec<ItemStackCatalystMatcher> CODEC = RecordCodecBuilder.create(i -> i.group(
20-
ResourceLocation.CODEC.fieldOf("item").forGetter(ItemStackCatalystMatcher::getItemId)
25+
ResourceLocation.CODEC.fieldOf("item").forGetter(ItemStackCatalystMatcher::getItemId),
26+
CompoundNBT.CODEC.optionalFieldOf("nbt").forGetter(ItemStackCatalystMatcher::getNbtTag)
2127
).apply(i, ItemStackCatalystMatcher::new));
2228

29+
private final Predicate<ItemStack> nbtMatcher;
30+
31+
private Optional<CompoundNBT> getNbtTag() {
32+
return Optional.of(new CompoundNBT());
33+
}
34+
2335
private final Item item;
2436

2537
public ItemStackCatalystMatcher() {
2638
this.item = null;
39+
this.nbtMatcher = (stack) -> true;
2740
}
2841

29-
public ItemStackCatalystMatcher(ResourceLocation item) {
42+
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
43+
public ItemStackCatalystMatcher(ResourceLocation item, Optional<CompoundNBT> nbt) {
3044
this.item = ForgeRegistries.ITEMS.getValue(item);
45+
this.nbtMatcher = buildMatcher(nbt.orElse(null));
3146
}
3247

3348
public ItemStackCatalystMatcher(ItemStack stack) {
3449
this.item = stack.getItem();
50+
this.nbtMatcher = (s) -> true;
51+
}
52+
53+
private Predicate<ItemStack> buildMatcher(CompoundNBT filter) {
54+
if(filter == null)
55+
return (stack) -> true;
56+
57+
return (stack) -> {
58+
// filters defined but item has no nbt
59+
if(!stack.hasTag() && !filter.isEmpty()) return false;
60+
return tagMatched(stack.getTag(), filter);
61+
};
3562
}
3663

64+
private boolean tagMatched(CompoundNBT node, CompoundNBT filter) {
65+
// filter: "mycompound: {}"
66+
if(filter.isEmpty()) return true;
67+
68+
return filter.getAllKeys().stream().allMatch(key -> {
69+
if (!node.contains(key))
70+
return false;
71+
72+
final byte tagType = filter.getTagType(key);
73+
if (tagType == Constants.NBT.TAG_COMPOUND) {
74+
// nested properties, recurse deeper
75+
return tagMatched(node.getCompound(key), filter.getCompound(key));
76+
} else {
77+
// all other key types are "primitives" - direct match time
78+
INBT primitive = node.get(key);
79+
if(primitive == null) return false;
80+
81+
return primitive.equals(filter.get(key));
82+
}
83+
});
84+
}
85+
86+
3787
public ResourceLocation getItemId() {
3888
return item.getRegistryName();
3989
}
4090

41-
// TODO - Expand
4291
public boolean matches(ItemStack stack) {
43-
return stack.getItem() == item;
92+
return stack.getItem() == item && this.nbtMatcher.test(stack);
4493
}
4594

4695
@Override

src/main/java/dev/compactmods/crafting/recipes/catalyst/ItemTagCatalystMatcher.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class ItemTagCatalystMatcher extends ForgeRegistryEntry<CatalystType<?>>
1919

2020
private static final Codec<ItemTagCatalystMatcher> CODEC = RecordCodecBuilder.create(i -> i.group(
2121
ITag.codec(() -> TagCollectionManager.getInstance().getItems())
22-
.fieldOf("tag").forGetter(ItemTagCatalystMatcher::getTag)
22+
.fieldOf("tag").forGetter((is) -> is.tag)
2323
).apply(i, ItemTagCatalystMatcher::new));
2424

2525
private final ITag<Item> tag;
@@ -32,10 +32,6 @@ public ItemTagCatalystMatcher(ITag<Item> tag) {
3232
this.tag = tag;
3333
}
3434

35-
private ITag<Item> getTag() {
36-
return tag;
37-
}
38-
3935
@Override
4036
public boolean matches(ItemStack stack) {
4137
return stack.getItem().is(tag);

src/test/java/dev/compactmods/crafting/tests/catalyst/CatalystJsonTests.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.util.Optional;
44
import com.google.gson.JsonElement;
55
import com.google.gson.JsonPrimitive;
6+
import com.mojang.serialization.DataResult;
67
import com.mojang.serialization.JsonOps;
78
import dev.compactmods.crafting.api.catalyst.CatalystType;
89
import dev.compactmods.crafting.api.catalyst.ICatalystMatcher;
@@ -41,6 +42,17 @@ void ChoosesCorrectCatalystCodec() {
4142
Assertions.assertTrue(type instanceof ItemStackCatalystMatcher);
4243
}
4344

45+
@Test
46+
@Tag("minecraft")
47+
void FailsDecodeOnUnmatchedCatalystType() {
48+
JsonElement node = new JsonPrimitive("compactcrafting:nonexistent");
49+
50+
final Optional<DataResult.PartialResult<CatalystType<?>>> catalyst = CatalystMatcherCodec.INSTANCE.parse(JsonOps.INSTANCE, node)
51+
.error();
52+
53+
Assertions.assertTrue(catalyst.isPresent());
54+
}
55+
4456
@Test
4557
@Tag("minecraft")
4658
void LoadsItemCatalystFromJson() {
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package dev.compactmods.crafting.tests.catalyst;
2+
3+
import java.util.Set;
4+
import com.mojang.serialization.JsonOps;
5+
import dev.compactmods.crafting.recipes.catalyst.ItemStackCatalystMatcher;
6+
import dev.compactmods.crafting.server.ServerConfig;
7+
import dev.compactmods.crafting.tests.util.FileHelper;
8+
import net.minecraft.item.ItemStack;
9+
import net.minecraft.item.Items;
10+
import net.minecraft.util.text.StringTextComponent;
11+
import org.junit.jupiter.api.Assertions;
12+
import org.junit.jupiter.api.Tag;
13+
import org.junit.jupiter.api.Test;
14+
15+
public class ItemStackCatalystTests {
16+
17+
@Tag("minecraft")
18+
@org.junit.jupiter.api.BeforeAll
19+
static void BeforeAllTests() {
20+
ServerConfig.RECIPE_REGISTRATION.set(true);
21+
ServerConfig.RECIPE_MATCHING.set(true);
22+
ServerConfig.FIELD_BLOCK_CHANGES.set(true);
23+
}
24+
25+
private ItemStackCatalystMatcher getMatcherFromFile(String s) {
26+
return ItemStackCatalystMatcher.CODEC
27+
.parse(JsonOps.INSTANCE, FileHelper.INSTANCE.getJsonFromFile(s))
28+
.getOrThrow(false, Assertions::fail);
29+
}
30+
31+
@Test
32+
@Tag("minecraft")
33+
void CanCreate() {
34+
final ItemStackCatalystMatcher matcher = getMatcherFromFile("catalysts/anvil_renamed_redstone.json");
35+
36+
final Set<ItemStack> possible = Assertions.assertDoesNotThrow(matcher::getPossible);
37+
Assertions.assertEquals(1, possible.size());
38+
}
39+
40+
@Test
41+
@Tag("minecraft")
42+
void FailsMatchNoNbt() {
43+
final ItemStackCatalystMatcher matcher = getMatcherFromFile("catalysts/anvil_renamed_redstone.json");
44+
45+
ItemStack testStack = new ItemStack(Items.REDSTONE);
46+
47+
// what an anvil does to rename - see AnvilScreen
48+
testStack.setHoverName(new StringTextComponent("Renamed Item"));
49+
50+
final Boolean matched = Assertions.assertDoesNotThrow(() -> matcher.matches(testStack), "Stack match attempt threw exception");
51+
52+
Assertions.assertTrue(matched);
53+
}
54+
55+
@Test
56+
@Tag("minecraft")
57+
void FailsMatchIfStackTagEmpty() {
58+
final ItemStackCatalystMatcher matcher = getMatcherFromFile("catalysts/item_nbt_nokey.json");
59+
60+
ItemStack testStack = new ItemStack(Items.REDSTONE);
61+
62+
final Boolean matched = Assertions.assertDoesNotThrow(() -> matcher.matches(testStack), "Stack match attempt threw exception");
63+
64+
Assertions.assertFalse(matched, "Stack matched even though it had filters, but stack NBT was empty.");
65+
}
66+
67+
@Test
68+
@Tag("minecraft")
69+
void FailsMatchOnUnmatchedFilter() {
70+
final ItemStackCatalystMatcher matcher = getMatcherFromFile("catalysts/item_nbt_nokey.json");
71+
72+
ItemStack testStack = new ItemStack(Items.REDSTONE);
73+
74+
// needed so the stack isn't empty
75+
testStack.getOrCreateTag().putString("hi", "there");
76+
77+
final Boolean matched = Assertions.assertDoesNotThrow(() -> matcher.matches(testStack), "Stack match attempt threw exception");
78+
79+
Assertions.assertFalse(matched, "Stack matched even though a filtered key was not present.");
80+
}
81+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package dev.compactmods.crafting.tests.catalyst;
2+
3+
import java.util.Set;
4+
import dev.compactmods.crafting.recipes.catalyst.ItemTagCatalystMatcher;
5+
import dev.compactmods.crafting.server.ServerConfig;
6+
import net.minecraft.item.ItemStack;
7+
import net.minecraft.item.Items;
8+
import net.minecraft.tags.ItemTags;
9+
import org.junit.jupiter.api.Assertions;
10+
import org.junit.jupiter.api.Tag;
11+
import org.junit.jupiter.api.Test;
12+
13+
public class TaggedCatalystTests {
14+
15+
@Tag("minecraft")
16+
@org.junit.jupiter.api.BeforeAll
17+
static void BeforeAllTests() {
18+
ServerConfig.RECIPE_REGISTRATION.set(true);
19+
ServerConfig.RECIPE_MATCHING.set(true);
20+
ServerConfig.FIELD_BLOCK_CHANGES.set(true);
21+
}
22+
23+
@Test
24+
@Tag("minecraft")
25+
void CanCreate() {
26+
ItemTagCatalystMatcher matcher = new ItemTagCatalystMatcher(ItemTags.PLANKS);
27+
Assertions.assertNotNull(matcher);
28+
29+
Assertions.assertDoesNotThrow(matcher::getCodec);
30+
Assertions.assertDoesNotThrow(matcher::getType);
31+
}
32+
33+
@Test
34+
@Tag("minecraft")
35+
void NullTagGivesEmptyPossible() {
36+
ItemTagCatalystMatcher matcher = new ItemTagCatalystMatcher(null);
37+
final Set<ItemStack> possible = Assertions.assertDoesNotThrow(matcher::getPossible);
38+
Assertions.assertTrue(possible.isEmpty());
39+
}
40+
41+
@Test
42+
@Tag("minecraft")
43+
void CanMatchPlanks() {
44+
ItemTagCatalystMatcher matcher = new ItemTagCatalystMatcher(ItemTags.PLANKS);
45+
46+
Assertions.assertTrue(matcher.matches(new ItemStack(Items.OAK_PLANKS)));
47+
Assertions.assertTrue(matcher.matches(new ItemStack(Items.SPRUCE_PLANKS)));
48+
Assertions.assertTrue(matcher.matches(new ItemStack(Items.ACACIA_PLANKS)));
49+
}
50+
51+
@Test
52+
@Tag("minecraft")
53+
void CanFetchPossible() {
54+
ItemTagCatalystMatcher matcher = new ItemTagCatalystMatcher(ItemTags.PLANKS);
55+
56+
final Set<ItemStack> possible = Assertions.assertDoesNotThrow(matcher::getPossible);
57+
58+
Assertions.assertEquals(ItemTags.PLANKS.getValues().size(), possible.size());
59+
}
60+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "compactcrafting:item",
3+
"item": "minecraft:redstone",
4+
"nbt": {
5+
"display": {
6+
"Name": "{\"text\":\"Renamed Item\"}"
7+
}
8+
}
9+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"_comment": "/give @p diamond_sword{display:{Name:\"\\\"HelloThere\\\"\"},Enchantments:[{id:sharpness,lvl:5}]} 1",
3+
"type": "compactcrafting:item",
4+
"item": "minecraft:diamond_sword",
5+
"nbt": {
6+
"display": {
7+
"Name": "HelloThere"
8+
},
9+
"Enchantments": [
10+
{
11+
"id": "minecraft:sharpness",
12+
"lvl": [1,2,3,4,5]
13+
}
14+
]
15+
}
16+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "compactcrafting:item",
3+
"item": "minecraft:redstone",
4+
"nbt": {
5+
"doesnotexist": true
6+
}
7+
}

0 commit comments

Comments
 (0)