Add recursive item stack NBT walker

This commit is contained in:
Ruling-0 committed 2026-08-27 19:45:59 -05:00
1 parent 490eefc6b7
commit 99907e564c
2 files changed
+141

No files matched your search

@@ -0,0 +1,43 @@
package com.gtnewhorizons.postea.utility;
import static net.minecraftforge.common.util.Constants.NBT.TAG_ANY_NUMERIC;
import static net.minecraftforge.common.util.Constants.NBT.TAG_COMPOUND;
import static net.minecraftforge.common.util.Constants.NBT.TAG_INT;
import static net.minecraftforge.common.util.Constants.NBT.TAG_SHORT;
import java.util.ArrayList;
import java.util.function.Consumer;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
/**
* Visits every serialized item stack in an NBT tree. A compound is a stack when it has a numeric {@code id} or an
* EndlessIDs {@code idExt} together with a {@code Count}. The walk is pre-order and descends into every compound
* and every list of compounds, so stacks nested inside another stack's {@code tag} (backpacks, storage cells) are
* visited too. Lists of lists are not descended.
*/
public final class NbtItemStacks {
private NbtItemStacks() {}
public static void forEach(NBTBase root, Consumer<NBTTagCompound> visitor) {
if (root instanceof NBTTagCompound compound) {
if (isStack(compound)) visitor.accept(compound);
for (Object key : new ArrayList<>(compound.func_150296_c())) {
forEach(compound.getTag((String) key), visitor);
}
} else if (root instanceof NBTTagList list && list.func_150303_d() == TAG_COMPOUND) {
for (int i = 0; i < list.tagCount(); i++) {
forEach(list.getCompoundTagAt(i), visitor);
}
}
}
public static boolean isStack(NBTTagCompound compound) {
return compound.hasKey("Count", TAG_ANY_NUMERIC)
&& (compound.hasKey("id", TAG_SHORT) || compound.hasKey("id", TAG_INT)
|| compound.hasKey("idExt", TAG_INT));
}
}
@@ -0,0 +1,98 @@
package com.gtnewhorizons.postea.utility;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import org.junit.jupiter.api.Test;
class NbtItemStacksTest {
private static NBTTagCompound stack(int id, int damage) {
NBTTagCompound stack = new NBTTagCompound();
stack.setShort("id", (short) id);
stack.setByte("Count", (byte) 1);
stack.setShort("Damage", (short) damage);
return stack;
}
private static List<NBTTagCompound> visited(NBTTagCompound root) {
List<NBTTagCompound> stacks = new ArrayList<>();
NbtItemStacks.forEach(root, stacks::add);
return stacks;
}
@Test
void visitsStacksInInventoryListsAndStacksNestedInsideAStacksTag() {
NBTTagCompound nested = stack(2, 0);
NBTTagCompound backpack = stack(1, 0);
NBTTagCompound backpackTag = new NBTTagCompound();
NBTTagList contents = new NBTTagList();
contents.appendTag(nested);
backpackTag.setTag("Items", contents);
backpack.setTag("tag", backpackTag);
NBTTagList items = new NBTTagList();
items.appendTag(backpack);
NBTTagCompound tileEntity = new NBTTagCompound();
tileEntity.setString("id", "Chest");
tileEntity.setTag("Items", items);
NBTTagList tileEntities = new NBTTagList();
tileEntities.appendTag(tileEntity);
NBTTagCompound level = new NBTTagCompound();
level.setTag("TileEntities", tileEntities);
assertEquals(Arrays.asList(backpack, nested), visited(level));
}
@Test
void ignoresCompoundsThatAreNotStacks() {
NBTTagCompound enchantment = new NBTTagCompound();
enchantment.setShort("id", (short) 3);
enchantment.setShort("lvl", (short) 1);
NBTTagCompound tileEntity = new NBTTagCompound();
tileEntity.setString("id", "Chest");
tileEntity.setInteger("Count", 1);
NBTTagCompound root = new NBTTagCompound();
root.setTag("ench", enchantment);
root.setTag("te", tileEntity);
assertEquals(Collections.emptyList(), visited(root));
}
@Test
void recognisesExtendedIdStacksWithoutADamageKey() {
NBTTagCompound extended = new NBTTagCompound();
extended.setShort("id", (short) 0);
extended.setInteger("idExt", 40000);
extended.setByte("Count", (byte) 3);
NBTTagCompound root = new NBTTagCompound();
root.setTag("Item", extended);
assertEquals(Collections.singletonList(extended), visited(root));
}
@Test
void aVisitorMayInvalidateTheStackItIsGiven() {
NBTTagCompound first = stack(5, 1);
NBTTagCompound second = stack(5, 2);
NBTTagList items = new NBTTagList();
items.appendTag(first);
items.appendTag(second);
NBTTagCompound root = new NBTTagCompound();
root.setTag("Items", items);
List<NBTTagCompound> seen = new ArrayList<>();
NbtItemStacks.forEach(root, tag -> {
seen.add(tag);
tag.removeTag("id");
});
assertEquals(Arrays.asList(first, second), seen);
}
}