NeoForge oracle: export items' data components and armour facts

Each item now carries its default components (vanilla's codecs, 1.21.1's
form), its effective attribute modifiers (armour's come from a method,
not a component), enchantability and repair items, and for armour its
slot, defense, toughness, knockback resistance, equip sound and texture
layers: the ground truth an item port is compared against. The export
also creates its output folder instead of failing to write.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
umcekoandClaude Opus 5.5 committed 2026-10-29 17:13:19 +03:00
1 parent 5c66b27c9a
commit df96b6787d
3 files changed
+74 -1

No files matched your search

+3 -1
View File
@@ -759,7 +759,9 @@ python3 tools/mod_port_harness.py --work <work> # or --jars/--deps to expor
namespaces and every vanilla block: the class chain, states, per-state hardness, light, tool
need, map colour, sounds, outline and collision shapes; drops for hand, diamond tools, shears
and silk touch; placement on a floor and a wall; BlockSetType and WoodType. Then items (class,
stack size, rarity, food), recipes (type, result, count, resolved ingredients) and tags. One run
stack size, rarity, food; every default data component in 1.21.1's form, the effective attribute
modifiers, enchantability and repair items; armour's slot, defense, toughness, knockback resistance,
equip sound and texture layers), recipes (type, result, count, resolved ingredients) and tags. One run
with the seven mods below takes about 30 s.
- `crates/horizon-client/examples/mod_port_probe.rs` builds the content root from the carried
mods and dumps what Horizon's loaders make of the same blocks, drops, recipes and tags.
@@ -1,6 +1,10 @@
package dev.horizonlab.neoforge;
import com.google.gson.JsonElement;
import com.google.gson.stream.JsonWriter;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.DynamicOps;
import com.mojang.serialization.JsonOps;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
@@ -16,6 +20,7 @@ import java.util.TreeMap;
import java.util.TreeSet;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.component.TypedDataComponent;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
@@ -23,10 +28,14 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.item.ArmorItem;
import net.minecraft.world.item.ArmorMaterial;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.TieredItem;
import net.minecraft.world.item.component.ItemAttributeModifiers;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.RecipeHolder;
@@ -346,11 +355,72 @@ public final class FactsExporter {
json.name("saturation").value(food.saturation());
json.endObject();
}
itemComponents(item, stack);
json.endObject();
}
json.endObject();
}
// The item's own numbers in 1.21.1's forms: its default components, and what it keeps outside them.
void itemComponents(Item item, ItemStack stack) throws IOException {
DynamicOps<JsonElement> ops = server.registryAccess().createSerializationContext(JsonOps.INSTANCE);
json.name("components");
json.beginObject();
for (TypedDataComponent<?> c : stack.getComponents()) {
var encoded = c.encodeValue(ops).result();
if (encoded.isPresent()) {
json.name(BuiltInRegistries.DATA_COMPONENT_TYPE.getKey(c.type()).toString());
json.jsonValue(encoded.get().toString());
}
}
json.endObject();
// Armor's come from ArmorItem.getDefaultAttributeModifiers, not a component.
raw("attribute_modifiers", ItemAttributeModifiers.CODEC.encodeStart(ops, stack.getAttributeModifiers()));
json.name("enchantment_value").value(item.getEnchantmentValue());
Ingredient repair = null;
if (item instanceof TieredItem tiered) {
repair = tiered.getTier().getRepairIngredient();
} else if (item instanceof ArmorItem armor) {
repair = armor.getMaterial().value().repairIngredient().get();
}
if (repair != null) {
json.name("repair_items");
json.beginArray();
for (ItemStack s : repair.getItems()) json.value(BuiltInRegistries.ITEM.getKey(s.getItem()).toString());
json.endArray();
}
if (item instanceof ArmorItem armor) {
ArmorMaterial material = armor.getMaterial().value();
json.name("armor");
json.beginObject();
json.name("slot").value(armor.getEquipmentSlot().getName());
json.name("material").value(armor.getMaterial().unwrapKey().map(k -> k.location().toString()).orElse(""));
json.name("defense").value(armor.getDefense());
json.name("toughness").value(armor.getToughness());
json.name("knockback_resistance").value(material.knockbackResistance());
json.name("equip_sound").value(BuiltInRegistries.SOUND_EVENT.getKey(material.equipSound().value()).toString());
json.name("layers");
json.beginArray();
for (ArmorMaterial.Layer layer : material.layers()) {
json.beginObject();
json.name("texture").value(layer.texture(false).toString());
json.name("texture_legs").value(layer.texture(true).toString());
json.name("dyeable").value(layer.dyeable());
json.endObject();
}
json.endArray();
json.endObject();
}
}
void raw(String name, DataResult<JsonElement> encoded) throws IOException {
var value = encoded.result();
if (value.isPresent()) {
json.name(name);
json.jsonValue(value.get().toString());
}
}
void recipes() throws IOException {
json.name("recipes");
json.beginObject();
+1
View File
@@ -215,6 +215,7 @@ def export(args):
exporter = build(home)
home, run = instance('export', jars, [*deps, exporter])
out = Path(args.out).resolve()
out.parent.mkdir(parents=True, exist_ok=True)
out.unlink(missing_ok=True)
seconds, log = start(home, run, {'horizon.oracle.out': out, 'horizon.oracle.namespaces': namespaces})
if not out.exists():