Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0482e06
feat: add nbt to jmespath evaluation
ReinderN Jan 20, 2026
f4d9c93
build: add jmespath dependencies and enable jarjar bundling
ReinderN Jan 20, 2026
15b9be8
feat: extend sfml grammar to support nbt filtering
ReinderN Jan 20, 2026
4e27994
feat: implement withnbt ast node and integrate into astbuilder
ReinderN Jan 20, 2026
1091b47
feat(sfml): introduce NBT filtering syntax to SFML grammar
ReinderN Jan 20, 2026
2c2e261
test: add nbt filtering tests
ReinderN Jan 20, 2026
864d724
feat: introduce nbt expression grammar syntax for filtering
ReinderN Jan 23, 2026
6aaf8aa
feat: implement nbt expression ast nodes with jmespath conversion
ReinderN Jan 23, 2026
cab22b4
feat: integrate nbt expression parsing into astbuilder
ReinderN Jan 23, 2026
513764f
test: add nbt expression feature tests
ReinderN Jan 23, 2026
c84b4cc
refactor: move getnbtfromstack to nbtjmespathevaluator
ReinderN Jan 23, 2026
e4714d1
fix: support wildcard patterns in resource identifiers and tag matchers
ReinderN Jan 24, 2026
81e5277
feat: add IN operator for NBT array membership checks
ReinderN Jan 24, 2026
48ca09c
feat: support wildcard patterns in NBT string comparisons
ReinderN Jan 24, 2026
8485731
build: make jarJar the default output, rename slim jar
ReinderN Jan 24, 2026
e937086
fix: support wildcard patterns in NBT array filter expressions
ReinderN Jan 24, 2026
d61f189
feat: log JMESPath conversion for NBT expressions
ReinderN Jan 24, 2026
1dc0130
docs: add NBT matching template program
ReinderN Jan 24, 2026
80dfecb
fix: nbt game tests to use new expression syntax
ReinderN Jan 25, 2026
2780a77
test: add NBT IN array filter test for potions
ReinderN Jan 25, 2026
1d9d410
Merge branch '1.19.2' into feature/nbt-matching
ReinderN Jan 26, 2026
0a0a592
Merge branch '1.19.2' into feature/nbt-matching
ReinderN Jan 28, 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
27 changes: 27 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,22 @@ dependencies {
// http://www.gradle.org/docs/current/userguide/dependency_management.html

antlr 'org.antlr:antlr4:4.9.1'

// JMESPath for NBT filtering - bundled via JarJar for distribution
jarJar('io.burt:jmespath-core:0.6.0') {
version {
strictly '[0.6.0,0.7.0)'
}
}
jarJar('io.burt:jmespath-gson:0.6.0') {
version {
strictly '[0.6.0,0.7.0)'
}
}
// Add to minecraftLibrary for development runtime (runClient, etc.)
minecraftLibrary 'io.burt:jmespath-core:0.6.0'
minecraftLibrary '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 @@ -503,6 +519,17 @@ jar.finalizedBy('reobfJar')
// However if you are in a multi-project build, dev time needs unobfed jar files, so you can delay the obfuscation until publishing by doing
// publish.dependsOn('reobfJar')

// 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'
}

publishing {
publications {
mavenJava(MavenPublication) {
Expand Down
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 forge:tools/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,59 @@
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 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);
enchantedSword.enchant(Enchantments.SHARPNESS, 5);

// 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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
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.alchemy.PotionUtils;
import net.minecraft.world.item.alchemy.Potions;

import java.util.Arrays;

/**
* Tests that NBT IN array filtering can exclude basic potions.
* Move only non-basic potions (not water, mundane, awkward, or thick).
*/
@SFMGameTest
public class NbtFilterPotionInArrayGameTest extends SFMGameTestDefinition {
@Override
public String template() {
return "3x2x1";
}

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

// Create basic potions (should NOT be moved)
ItemStack waterPotion = PotionUtils.setPotion(new ItemStack(Items.POTION), Potions.WATER);
ItemStack mundanePotion = PotionUtils.setPotion(new ItemStack(Items.POTION), Potions.MUNDANE);
ItemStack awkwardPotion = PotionUtils.setPotion(new ItemStack(Items.POTION), Potions.AWKWARD);
ItemStack thickPotion = PotionUtils.setPotion(new ItemStack(Items.POTION), Potions.THICK);

// Create non-basic potions (should be moved)
ItemStack healingPotion = PotionUtils.setPotion(new ItemStack(Items.POTION), Potions.HEALING);
ItemStack strengthPotion = PotionUtils.setPotion(new ItemStack(Items.POTION), Potions.STRENGTH);

test.setProgram("""
EVERY 20 TICKS DO
-- Only move potions that are NOT basic (water, mundane, awkward, thick)
INPUT WITHOUT NBT Potion IN [
"minecraft:water",
"minecraft:mundane",
"minecraft:awkward",
"minecraft:thick"
] FROM left
OUTPUT TO right
END
""");

// Put all potions in the left chest
test.preContents("left", Arrays.asList(
waterPotion,
mundanePotion,
awkwardPotion,
thickPotion,
healingPotion,
strengthPotion
));

// Only healing and strength potions should move to the right
// Basic potions stay in the left
test.postContents("left", Arrays.asList(
waterPotion.copy(),
mundanePotion.copy(),
awkwardPotion.copy(),
thickPotion.copy(),
ItemStack.EMPTY,
ItemStack.EMPTY
));
test.postContents("right", Arrays.asList(
healingPotion.copy(),
strengthPotion.copy()
));

test.run();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
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 that WITHOUT NBT filtering can move only non-enchanted items.
*/
@SFMGameTest
public class NbtFilterWithoutEnchantmentsGameTest 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);
enchantedSword.enchant(Enchantments.SHARPNESS, 5);

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

test.setProgram("""
EVERY 20 TICKS DO
-- Only move items WITHOUT enchantments
INPUT WITHOUT 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 plain sword should move to the right
test.postContents("left", Arrays.asList(
enchantedSword.copy(),
ItemStack.EMPTY
));
test.postContents("right", Arrays.asList(
plainSword.copy()
));

test.run();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* Game tests for NBT filtering feature using JMESPath expressions.
*/
package ca.teamdman.sfm.gametest.tests.nbt_filtering;
Loading