Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4eefc76
feat: add nbt to jmespath evaluation
ReinderN Jan 20, 2026
7c7abfe
build: add jmespath dependencies and enable jarjar bundling
ReinderN Jan 20, 2026
3e4957d
feat: extend sfml grammar to support nbt filtering
ReinderN Jan 20, 2026
8849660
feat: implement withnbt ast node and integrate into astbuilder
ReinderN Jan 20, 2026
1d55cf6
feat(sfml): introduce NBT filtering syntax to SFML grammar
ReinderN Jan 20, 2026
7d141dc
test: add nbt filtering tests
ReinderN Jan 20, 2026
c52824b
build: update jar manifest to use mod_version
ReinderN Jan 20, 2026
f376abe
build: add jmespath libraries
ReinderN Jan 20, 2026
7d0bdcb
feat: adapt new datacomponents
ReinderN Jan 20, 2026
a5e9b4d
test: fix tests for minecraft version
ReinderN Jan 20, 2026
6905359
feat: introduce nbt expression grammar syntax for filtering
ReinderN Jan 23, 2026
4b83e07
feat: implement nbt expression ast nodes with jmespath conversion
ReinderN Jan 23, 2026
8f54e87
feat: integrate nbt expression parsing into astbuilder
ReinderN Jan 23, 2026
3a18daa
test: add nbt expression feature tests
ReinderN Jan 23, 2026
c17939e
refactor: move getnbtfromstack to nbtjmespathevaluator
ReinderN Jan 23, 2026
5e31910
fix: support wildcard patterns in resource identifiers and tag matchers
ReinderN Jan 24, 2026
7c89b6d
feat: add IN operator for NBT array membership checks
ReinderN Jan 24, 2026
1f312a5
feat: support wildcard patterns in NBT string comparisons
ReinderN Jan 24, 2026
e009dad
build: make jarJar the default output, rename slim jar
ReinderN Jan 24, 2026
518f486
fix: support wildcard patterns in NBT array filter expressions
ReinderN Jan 24, 2026
3b7fad3
feat: log JMESPath conversion for NBT expressions
ReinderN Jan 24, 2026
ef461f9
feat: add NBT matching template with data component examples
ReinderN Jan 24, 2026
8cdf769
fix: each grammar
ReinderN Jan 25, 2026
029a4ee
fix: nbt game tests to use new syntax
ReinderN Jan 25, 2026
30a4534
test: add NBT IN array filter test for potions
ReinderN Jan 25, 2026
dade225
feat: fix NBT count filtering for large stacks
ReinderN Jan 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ runs {
jvmArguments '-XX:+IgnoreUnrecognizedVMOptions', '-XX:+AllowRedefinitionToAddDeleteMethods'

modSource project.sourceSets.main

// JMESPath library for NBT filtering
dependencies {
runtime 'io.burt:jmespath-core:0.6.0'
runtime 'io.burt:jmespath-gson:0.6.0'
}
}

client {
Expand Down Expand Up @@ -267,6 +273,22 @@ dependencies {
// http://www.gradle.org/docs/current/userguide/dependency_management.html

antlr 'org.antlr:antlr4:4.13.1'

// JMESPath for NBT filtering - bundled via JarJar for distribution
jarJar(implementation('io.burt:jmespath-core:0.6.0')) {
version {
strictly '[0.6.0,0.7.0)'
}
}
jarJar(implementation('io.burt:jmespath-gson:0.6.0')) {
version {
strictly '[0.6.0,0.7.0)'
}
}
// Add to runtime classpath for development (runClient, runGameTestServer, etc.)
runtimeOnly 'io.burt:jmespath-core:0.6.0'
runtimeOnly 'io.burt:jmespath-gson:0.6.0'

testImplementation 'com.github.javaparser:javaparser-symbol-solver-core:3.26.4'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
Expand Down Expand Up @@ -342,13 +364,24 @@ jar {
"Specification-Vendor" : mod_authors,
"Specification-Version" : "1", // We are version 1 of ourselves
"Implementation-Title" : project.name,
"Implementation-Version" : project.jar.archiveVersion,
"Implementation-Version" : mod_version,
"Implementation-Vendor" : mod_authors,
"Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")
])
}
}

// Enable jarJar for bundling JMESPath library
jarJar.enable()

// Use jarJar as the main output (remove -all suffix)
tasks.named('jarJar') {
archiveClassifier = ''
}
tasks.named('jar') {
archiveClassifier = 'slim'
}

// Example configuration to allow publishing using the maven-publish plugin
publishing {
publications {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package ca.teamdman.sfm.gametest.tests.compat.mekanism;

import ca.teamdman.sfm.common.blockentity.ManagerBlockEntity;
import ca.teamdman.sfm.common.label.LabelPositionHolder;
import ca.teamdman.sfm.common.registry.SFMBlocks;
import ca.teamdman.sfm.common.registry.SFMItems;
import ca.teamdman.sfm.gametest.SFMGameTest;
import ca.teamdman.sfm.gametest.SFMGameTestDefinition;
import ca.teamdman.sfm.gametest.SFMGameTestHelper;
import mekanism.common.registries.MekanismBlocks;
import mekanism.common.tile.TileEntityBin;
import net.minecraft.core.BlockPos;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;

import static ca.teamdman.sfm.gametest.SFMGameTestMethodHelpers.assertTrue;

/**
* Tests that NBT count filtering works correctly with stack sizes greater than 99.
* Uses Mekanism bins which can hold oversized stacks.
* This tests for issues with ItemStack.save() where count might have serialization issues.
*/
@SuppressWarnings({
"RedundantSuppression",
"DataFlowIssue",
"OptionalGetWithoutIsPresent",
"DuplicatedCode",
"ArraysAsListWithZeroOrOneArgument"
})
@SFMGameTest
public class MekBinNbtCountFilterGameTest extends SFMGameTestDefinition {

@Override
public String template() {
return "3x2x1";
}

@Override
public void run(SFMGameTestHelper helper) {
// designate positions
var leftPos = new BlockPos(2, 2, 0);
var rightPos = new BlockPos(0, 2, 0);
var managerPos = new BlockPos(1, 2, 0);

// set up the world
helper.setBlock(leftPos, MekanismBlocks.ULTIMATE_BIN.getBlock());
var left = ((TileEntityBin) helper.getBlockEntity(leftPos));
helper.setBlock(rightPos, MekanismBlocks.ULTIMATE_BIN.getBlock());
var right = ((TileEntityBin) helper.getBlockEntity(rightPos));
helper.setBlock(managerPos, SFMBlocks.MANAGER_BLOCK.get());
var manager = ((ManagerBlockEntity) helper.getBlockEntity(managerPos));

// set up the program - only move items with count > 32
// Note: Even though the bin contains 128 items, SFM queries return ItemStacks
// capped at maxStackSize (64), so we test with count > 32 to verify NBT filtering works
manager.setItem(0, new ItemStack(SFMItems.DISK_ITEM.get()));
manager.setProgram("""
EVERY 20 TICKS DO
INPUT WITH NBT count > 32 FROM a NORTH SIDE
OUTPUT TO b TOP SIDE
END
""".stripIndent());

// set the labels
LabelPositionHolder.empty()
.add("a", helper.absolutePos(leftPos))
.add("b", helper.absolutePos(rightPos))
.save(manager.getDisk());

// Put 128 diamonds in the left bin
// SFM will see stacks of 64 (maxStackSize), which is > 32, so they should be moved
left.getBinSlot().setStack(new ItemStack(Items.DIAMOND, 128));

helper.succeedIfManagerDidThingWithoutLagging(manager, () -> {
// 64 diamonds should have moved (one maxStackSize worth)
assertTrue(left.getBinSlot().getCount() == 128 - 64, "Left bin should have 64 remaining (128 - 64 transferred)");
assertTrue(right.getBinSlot().getCount() == 64, "Right bin should have 64 diamonds");
assertTrue(right.getBinSlot().getStack().getItem() == Items.DIAMOND, "Right bin should contain diamonds");
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package ca.teamdman.sfm.gametest.tests.nbt_filtering;

import ca.teamdman.sfm.gametest.LeftRightManagerTest;
import ca.teamdman.sfm.gametest.SFMGameTest;
import ca.teamdman.sfm.gametest.SFMGameTestDefinition;
import ca.teamdman.sfm.gametest.SFMGameTestHelper;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.enchantment.Enchantments;

import java.util.Arrays;

/**
* Tests combining NBT filtering with TAG filtering.
* Move only damaged swords (not other damaged items).
*/
@SFMGameTest
public class NbtFilterCombinedWithTagGameTest extends SFMGameTestDefinition {
@Override
public String template() {
return "3x2x1";
}

@Override
public void run(SFMGameTestHelper helper) {
var test = new LeftRightManagerTest(helper);

// Create a damaged sword
ItemStack damagedSword = new ItemStack(Items.DIAMOND_SWORD);
damagedSword.setDamageValue(50);

// Create a damaged pickaxe
ItemStack damagedPickaxe = new ItemStack(Items.DIAMOND_PICKAXE);
damagedPickaxe.setDamageValue(50);

test.setProgram("""
EVERY 20 TICKS DO
-- Only move damaged items that are also swords (have the sword tag)
INPUT WITH NBT damage > 0 AND TAG minecraft:swords FROM left
OUTPUT TO right
END
""");

// Put both items in the left chest
test.preContents("left", Arrays.asList(
damagedSword,
damagedPickaxe
));

// Only the damaged sword should move (matches both NBT and TAG filters)
// The damaged pickaxe stays because it doesn't have the sword tag
test.postContents("left", Arrays.asList(
ItemStack.EMPTY,
damagedPickaxe.copy()
));
test.postContents("right", Arrays.asList(
damagedSword.copy()
));

test.run();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package ca.teamdman.sfm.gametest.tests.nbt_filtering;

import ca.teamdman.sfm.gametest.LeftRightManagerTest;
import ca.teamdman.sfm.gametest.SFMGameTest;
import ca.teamdman.sfm.gametest.SFMGameTestDefinition;
import ca.teamdman.sfm.gametest.SFMGameTestHelper;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;

import java.util.Arrays;
import java.util.Collections;

/**
* Tests that NBT filtering can move only damaged items (Damage > 0).
*/
@SFMGameTest
public class NbtFilterDamagedItemsGameTest extends SFMGameTestDefinition {
@Override
public String template() {
return "3x2x1";
}

@Override
public void run(SFMGameTestHelper helper) {
var test = new LeftRightManagerTest(helper);

// Create a damaged sword (has Damage NBT)
ItemStack damagedSword = new ItemStack(Items.DIAMOND_SWORD);
damagedSword.setDamageValue(50);

// Create an undamaged sword (Damage = 0, but still has the tag)
ItemStack undamagedSword = new ItemStack(Items.DIAMOND_SWORD);

test.setProgram("""
EVERY 20 TICKS DO
-- Only move items with damage > 0
INPUT WITH NBT damage > 0 FROM left
OUTPUT TO right
END
""");

// Put both swords in the left chest
test.preContents("left", Arrays.asList(
damagedSword,
undamagedSword
));

// Only the damaged sword should move to the right
// The undamaged sword stays in the left
test.postContents("left", Arrays.asList(
ItemStack.EMPTY,
undamagedSword.copy()
));
test.postContents("right", Arrays.asList(
damagedSword.copy()
));

test.run();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package ca.teamdman.sfm.gametest.tests.nbt_filtering;

import ca.teamdman.sfm.common.enchantment.SFMEnchantmentCollection;
import ca.teamdman.sfm.common.enchantment.SFMEnchantmentCollectionKind;
import ca.teamdman.sfm.gametest.LeftRightManagerTest;
import ca.teamdman.sfm.gametest.SFMGameTest;
import ca.teamdman.sfm.gametest.SFMGameTestDefinition;
import ca.teamdman.sfm.gametest.SFMGameTestHelper;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.enchantment.Enchantments;

import java.util.Arrays;

/**
* Tests that NBT filtering can move only enchanted items.
*/
@SFMGameTest
public class NbtFilterEnchantedItemsGameTest extends SFMGameTestDefinition {
@Override
public String template() {
return "3x2x1";
}

@Override
public void run(SFMGameTestHelper helper) {
var test = new LeftRightManagerTest(helper);

// Create an enchanted sword
ItemStack enchantedSword = new ItemStack(Items.DIAMOND_SWORD);
SFMEnchantmentCollection enchantments = new SFMEnchantmentCollection();
enchantments.add(helper.createEnchantmentEntry(Enchantments.SHARPNESS, 5));
enchantments.write(enchantedSword, SFMEnchantmentCollectionKind.EnchantedLikeATool);

// Create a plain sword (no enchantments)
ItemStack plainSword = new ItemStack(Items.DIAMOND_SWORD);

test.setProgram("""
EVERY 20 TICKS DO
-- Only move items with enchantments (enchantments array is non-empty)
INPUT WITH NBT enchantments[0] FROM left
OUTPUT TO right
END
""");

// Put both swords in the left chest
test.preContents("left", Arrays.asList(
enchantedSword,
plainSword
));

// Only the enchanted sword should move to the right
test.postContents("left", Arrays.asList(
ItemStack.EMPTY,
plainSword.copy()
));
test.postContents("right", Arrays.asList(
enchantedSword.copy()
));

test.run();
}
}
Loading