Repository navigation
Developers Loot Info Registration Reference
Everything below is registered from registerServer(IServerRegistry registry) unless stated otherwise. Read
Tooltips first — it explains TooltipBuilder and the difference between exact-class category
lookup and superclass-walking value lookup.
Availability differs by Minecraft version. The API follows vanilla: a registration method only exists where the vanilla concept exists. The version notes below say where each one is available. When in doubt, check the
IServerRegistryinterface in the jar you compile against.
Turns your LootPoolEntryContainer into a node of the displayed tree.
registry.registerEntry(MyEntry.class, MyNodes::getMyEntryNode);public static IDataNode getMyEntryNode(IServerUtils utils, MyEntry entry, NumberExpr chance, NumberExpr sumWeight,
List<TooltipNode> chanceConditions,
List<LootItemFunction> functions, List<LootItemCondition> conditions) {
// combine the entry's own functions/conditions with the inherited ones,
// compute the final chance from chance, sumWeight and utils.getEntryWeight(utils, entry, ...),
// then build a node - reusing ALI's ItemNode is usually enough
return new ItemNode(finalChance, count, itemStack, tooltip, allFunctions, allConditions);
}The factory receives the raw chance and the pool's total weight as NumberExprs (the entry's own weight has to be
applied against it), the condition tooltips the NumberExpr.Branches in those expressions point at
(chanceConditions), plus the functions and conditions inherited from the pool. If your entry produces plain items,
build an ItemNode (float chance, NumberExpr count, item or tag, tooltip, functions, conditions); if it is a
composite entry with children, extend ListNode — see Nodes and Widgets.
Says how much weight your entry type takes in its pool, as a NumberExpr. An entry type with no weight registered
weighs 0. Lookup walks superclasses; ALI already covers vanilla's singleton, composite and alternatives entry
bases, so only an entry type outside those needs it:
registry.registerEntryWeight(MyEntry.class, (utils, entry, conditions) -> NumberExpr.constant(entry.weight()));A factory reads it back with utils.getEntryWeight(utils, entry, conditions).
Lists the entries your composite entry contains, so ALI finds the loot tables referenced from inside it. Vanilla's composite entries are already covered. Lookup walks superclasses:
registry.registerEntryChildren(MyCompositeEntry.class, (utils, entry) -> entry.children());Describes the entry itself as tooltip text. registerEntry decides how an entry becomes a node of the tree;
registerEntryTooltip decides how it reads when it is written into a tooltip instead — an entry nested in
another entry's Entries: list, or one carried by a function that fills a container.
registry.registerEntryTooltip(MyEntry.class, MyTooltips::getMyEntryTooltip);public static TooltipBuilder getMyEntryTooltip(IServerUtils utils, MyEntry entry) {
return TooltipBuilder.array((b) -> {
b.add(utils.getValueTooltip(utils, entry.item).build(MyLang.Value.ITEM));
b.add(utils.getValueTooltip(utils, entry.condition).build(MyLang.Branch.CONDITIONS));
}, MyLang.Entry.MY_ENTRY);
}Up to 26.2 an entry holds a list of conditions,
entry.conditions, instead of the singleentry.condition.
A composite entry describes its children by asking the utils for each one, so the nesting keeps working for entry types it has never heard of:
b.add(TooltipBuilder.array((c) -> {
for (LootPoolEntryContainer child : entry.children) {
c.add(utils.getEntryTooltip(utils, child));
}
}, MyLang.Branch.ENTRIES));Lookup is by exact class, like the other category registrations. An unregistered entry type falls back to a JSON dump of the entry, which is readable but noisy — register the types your mod adds.
The two most common registrations: describe your LootItemCondition / LootItemFunction.
registry.registerFunctionTooltip(MyFunction.class, MyTooltips::getMyFunctionTooltip);
registry.registerConditionTooltip(MyCondition.class, MyTooltips::getMyConditionTooltip);public static TooltipBuilder getMyFunctionTooltip(IServerUtils utils, MyFunction fun) {
return TooltipBuilder.array((b) -> {
b.add(utils.getValueTooltip(utils, fun.amount).build(MyLang.Value.AMOUNT));
b.add(utils.getValueTooltip(utils, fun.condition).build(MyLang.Branch.CONDITIONS));
}, MyLang.Functions.MY_FUNCTION);
}Up to 26.2 a conditional function holds a list of conditions,
fun.predicates, instead of the singlefun.condition.
Only relevant on Forge / NeoForge, for global loot modifiers. ALI judges every modifier against every page it displays — a block page, a mob page, a loot table page — and a page resolver answers that question for one class of yours:
registry.registerPageResolver(MyBlockCondition.class, (utils, condition, page) ->
GlobalLootModifierUtils.testBlocks(page, condition.blocks()::contains, true));The registered class is any class. Register a condition class when the condition is what names the target, and the modifier class itself when the target lives in the modifier's own fields:
registry.registerPageResolver(MyAddItemModifier.class, (utils, modifier, page) ->
GlobalLootModifierUtils.testTable(page, (id) -> id.getPath().contains(modifier.path()), false));A resolver answers a Verdict: Match.NO when the modifier can never apply on that page, Match.YES when the
page selects it, Match.UNKNOWN when nothing there settles it. null means "no opinion" and hands the term back
to ALI — the right answer for the pages your class says nothing about.
A LootPage carries what the page is made of: tableId(), the paramSet() vanilla rolls it with, blocks(),
entityTypes() and the lazily built samples(). GlobalLootModifierUtils.testBlocks / testEntityTypes /
testTable cover the usual answers — matching by predicate, so block::equals / id::equals covers a single
target, a collection's ::contains a fixed group, and a predicate over a tag, a path fragment or a regex an
open-ended family — so a hand-built Verdict is rarely needed.
The boolean those helpers take is explained. Pass true when the condition means nothing beyond selecting the
page and it is left out of the tooltip, because the page already tells the whole story; pass false when it also
constrains something else (a block state, an entity's NBT, …) and should stay visible. It has no effect for a
resolver registered on a modifier class: only conditions are ever hidden from the tooltip.
A condition with no resolver is still decided. ALI runs it against a context built for the page, which answers
what the page knows and refuses every read it cannot — a condition that needs a loot parameter the page never has
is ruled out that way, with no registration at all, and AllOf / AnyOf / inverted groups are descended into.
Register a resolver only where running the condition cannot answer it: when it reads your mod's own runtime state,
or when the answer lives in the modifier's fields.
A condition that reads the queried loot table id out of a carrier of its own — the loaders' loot table id conditions do exactly this — needs that carrier filled before it is run:
registry.registerLootContextPreparer((utils, context, page) -> MyLootHooks.setTableId(context, page.tableId()));Every preparer runs, in registration order, on every context ALI builds for a page. With the id in place the condition decides itself and needs no resolver of its own.
An entity_properties condition carrying a type_specific sub-predicate can only be judged when that
sub-predicate is a function of the entity class: ALI's sample entities are bare instances carrying no
spawn-time state — no variant, no age, nothing a mob decides when it spawns. Declare that for a sub-predicate of
your own:
registry.registerEntitySubPredicateResolver(MyKindPredicate.class, (utils, predicate, page) ->
page.samples().get().stream().allMatch((entity) -> entity instanceof MyMob) ? Verdict.yes(true) : Verdict.NO);1.21.1–26.1.2: keyed by the sub-predicate's
MapCodecinstead of its class, likeregisterEntitySubPredicateTooltip:registry.registerEntitySubPredicateResolver(MyKindPredicate.CODEC, ...).
An unregistered sub-predicate leaves the condition undecided, which is the safe answer — a resolver that reads state off a sample produces wrong verdicts instead.
Every count, chance, roll and trade price is a NumberExpr (com.yanny.aci.api): an expression describing the
value instead of only bounding it. The client derives the range, the most likely value, the enchantment level rows,
the chart and — with F3 + H — the formula from it, so a registration only has to say what the value is. Build
expressions through the static factories (NumberExpr.constant, range, uniformInt, binomial, add, mul,
min, max, clamp, fn, …), never the record constructors: the factories fold constants and simplify, so the
formula carries no 1 × or + 0. Expressions and their functions are described in Tooltips.
Converts your ContextIntProvider / ContextFloatProvider into a NumberExpr, so counts and rolls show a real
number, range and distribution instead of ? (<provider type>). Vanilla number providers come in these two kinds,
each held as a Holder, so each kind is registered with one converter:
registry.registerContextIntProvider(MyIntProvider.class, (utils, provider, conditions) ->
NumberExpr.uniformInt(provider.min(), provider.max()));
registry.registerContextFloatProvider(MyFloatProvider.class, (utils, provider, conditions) ->
NumberExpr.uniformFloat(provider.min(), provider.max()));Up to 26.2 there is a single
NumberProviderkind; seeregisterNumberProvider.
When the distribution is not known, say only what is: NumberExpr.range(min, max) gives the bounds and no most
likely value.
Every converter also receives conditions, the list of condition tooltips collected for the value being built. A
value that depends on a loot condition returns NumberExpr.cond(branches, otherwise): add the condition's tooltip
to conditions and give its branch that index, new NumberExpr.Branch(conditions.size() - 1, value) — the
condition's tooltip is then shown under its row. A branch whose condition has no tooltip uses -1 and shows
without it.
Reading a provider back out of your own function goes through utils.convertContextInt(utils, provider, conditions)
or utils.convertContextFloat(utils, provider, conditions), which take the Holder<ContextIntProvider> /
Holder<ContextFloatProvider> your function holds — pass on the conditions you were given.
Up to 26.2. A number provider is a single
NumberProviderwith bothgetFloatandgetInt; on 26.3 useregisterContextIntProvider/registerContextFloatProviderinstead.
registry.registerNumberProvider(MyProvider.class,
(utils, provider, conditions) -> NumberExpr.uniformFloat(provider.min(), provider.max()),
(utils, provider, conditions) -> NumberExpr.uniformInt(provider.min(), provider.max()));The first converter describes getFloat, the optional second one getInt. Give the second one when your
getInt is not just getFloat rounded — vanilla's uniform generator, for instance, draws a whole number between
the rounded bounds. Without it, the int value is the float one rounded. Both converters receive conditions as
described above.
Reading a provider back out of your own function goes through utils.convertNumber(utils, provider, conditions)
(float) or utils.convertIntNumber(utils, provider, conditions) (int), which take the NumberProvider itself — use
the one your function itself calls on the provider.
The same for vanilla's IntProvider / FloatProvider, wherever one appears inside a tooltip: one converter each,
with the same (utils, provider, conditions) shape. Read one back with utils.convertIntProvider /
utils.convertFloatProvider. Both work the same in Advanced Worldgen Info.
Available from 1.21.1. Converts your LevelBasedValue (enchantment effect amounts, enchanted-bonus chances, …) into
a NumberExpr of the enchantment level, which the converter receives as its third argument. Build the value from
it rather than evaluating per level — the client derives one row per level from the expression:
registry.registerLevelBasedValue(MyLevelValue.class, (utils, value, level) ->
NumberExpr.add(NumberExpr.constant(value.base()), NumberExpr.mul(NumberExpr.constant(value.perLevel()), level)));A value wrapping another one converts it through utils.convertLevelBasedValue(utils, inner, level). An
unregistered value, or one whose converter throws (logged), shows as ? (<value type>).
A function that changes how many items drop. Registering it lets the displayed count reflect the function instead
of being just the pool's roll count. The modifier receives the count so far and returns the new one; conditions
is the same condition list the number converters receive:
registry.registerCountModifier(MySetCountFunction.class, (utils, fun, count, conditions) ->
NumberExpr.add(count, utils.convertContextInt(utils, fun.count(), conditions)));Up to 26.2
fun.count()is aNumberProvider, read withutils.convertIntNumber(utils, fun.count(), conditions).
A count that grows with an enchantment uses the level as a variable, NumberExpr.level(enchantmentId, maxLevel).
The client shows one row per level, up to maxLevel, by substituting it — nothing is computed per level:
registry.registerCountModifier(MyHarvestFunction.class, (utils, fun, count, conditions) ->
NumberExpr.add(count, NumberExpr.uniformInt(NumberExpr.constant(0), NumberExpr.level("mymod:harvesting", 3))));A function carrying its own conditions is handled by ALI: the count splits into a row per condition (or, for a
plain random chance, into a weighted choice between the modified and the original count). A modifier that throws
turns only that value into ? (<function type>), logged — the loot table is still shown.
A condition that changes the drop chance (a random chance, a bonus per enchantment level, …), so the tree can show
the resulting probability. The chance is a fraction (0.25), rendered as a percent.
registry.registerChanceModifier(MyChanceCondition.class, (utils, condition, chance, conditions) ->
NumberExpr.mul(chance, NumberExpr.constant(condition.probability())));A function that changes the resulting ItemStack itself (enchanting it, setting components/NBT, …). The returned
stack is what the viewer shows in the slot.
registry.registerItemStackModifier(MyEnchantFunction.class, (utils, fun, stack) -> fun.apply(stack));For a function or condition that only wraps others. ALI applies count, chance and item modifiers per class, so a
set_count inside your wrapper would otherwise be ignored. An unwrapper returns the functions or conditions the
wrapper applies, and ALI treats them as if they stood in the wrapper's place. It unwraps recursively, so a returned
wrapper is unwrapped again. A wrapper nested inside another condition or function is still shown as itself in the
tooltip.
A loot table, pool and entry each hold a single condition and a single function, and several of them are combined
into one all_of / sequence. ALI unwraps those through the same unwrappers, so the conditions and functions
listed on a table, pool or entry are the unwrapped ones — a wrapper of yours placed there is listed as its contents
too.
Up to 26.2 a loot table, pool and entry hold lists of conditions and functions.
registry.registerConditionUnwrapper(MyAllOfCondition.class, (utils, condition) -> condition.terms());
registry.registerFunctionUnwrapper(MySequenceFunction.class, (utils, function) -> function.functions());Only unwrap what is exactly equivalent to the list you return: conditions that must all pass, or functions that
always run in order. Return null otherwise — for "any of", for an inverted condition, or for a wrapper with
conditions of its own — and the wrapper is kept as a whole. ALI already unwraps vanilla all_of and a sequence
without a condition of its own; references are resolved by Minecraft itself.
Up to 26.2 Minecraft leaves references unresolved, and ALI unwraps vanilla
all_of,sequence, predicate references and item modifier references.
For custom Ingredient subclasses — which only Forge allows. Vanilla Ingredient is final, so on Fabric and
NeoForge a custom ingredient is registered as a value tooltip instead; see registerIngredientUnwrapper below.
registry.registerIngredientTooltip(MyIngredient.class, MyTooltips::getMyIngredientTooltip);On some loaders a custom ingredient is not an Ingredient subclass of its own. On Fabric every custom ingredient
arrives in the same Fabric API wrapper class; on NeoForge it is a plain Ingredient holding an ICustomIngredient.
Exact-class dispatch through registerIngredientTooltip cannot tell such ingredients apart. An unwrapper returns the
object an ingredient wraps, or null for an ingredient it does not handle. The returned object is rendered through
the ordinary value tooltip tier; when no value tooltip matches it, ALI shows the generic tooltip built from the
ingredient's serialized form.
ALI registers the unwrapper for Fabric's CustomIngredient and NeoForge's ICustomIngredient itself, so on those
loaders a value tooltip for your custom ingredient's class is all you need:
registry.registerValueTooltip(MyCustomIngredient.class, MyTooltips::getMyCustomIngredientTooltip);Register an unwrapper of your own only for a wrapping scheme ALI does not know about:
registry.registerIngredientUnwrapper((ingredient) -> ingredient instanceof MyWrapper wrapper ? wrapper.getInner() : null);On Forge custom ingredients are Ingredient subclasses, so registerIngredientTooltip covers them.
| Method | Available | Purpose |
|---|---|---|
registerItemSubPredicateTooltip |
1.21.1 only | describe a custom ItemSubPredicate
|
registerEntitySubPredicateTooltip |
1.21.1+ | describe a custom EntitySubPredicate
|
registerDataComponentTypeTooltip |
1.21.1+ | describe the value of a custom DataComponentType
|
registerDataComponentPredicateTooltip |
1.21.11+ | describe a custom DataComponentPredicate
|
registerConsumeEffectTooltip |
1.21.11+ | describe a custom ConsumeEffect
|
registerSlotSourceTooltip |
1.21.11+ | describe a custom SlotSource
|
All follow the same shape, (utils, value) -> TooltipBuilder, keyed by the described class:
registry.registerSlotSourceTooltip(MySlotSource.class, MyTooltips::getMySlotSourceTooltip);One is keyed differently: registerDataComponentTypeTooltip takes the DataComponentType itself and receives
that component's value.
registry.registerDataComponentTypeTooltip(MyComponents.MY_COMPONENT, MyTooltips::getMyComponentTooltip);
registry.registerEntitySubPredicateTooltip(MySubPredicate.class, MyTooltips::getMySubPredicateTooltip);1.21.1–26.1.2:
registerEntitySubPredicateTooltipis keyed differently too, by the sub-predicate'sMapCodec:registry.registerEntitySubPredicateTooltip(MySubPredicate.CODEC, MyTooltips::getMySubPredicateTooltip);
Up to 1.21.11. From 26.1.2 on, a trade is data-driven (
minecraft:villager_trade), so there is noItemListingclass to describe and this method does not exist. Trades a mod still defines in code are handed over as nodes throughTradeLevel.OfTradesinstead.
registry.registerItemListing(MyItemListing.class, (utils, listing, condition) ->
new ItemsToItemsNode(
utils,
Either.left(listing.cost()), NumberExpr.constant(listing.cost().getCount()),
Either.left(ItemStack.EMPTY), NumberExpr.constant(0),
Either.left(listing.result()), NumberExpr.constant(listing.result().getCount()),
listing.maxUses(), listing.villagerXp(), listing.priceMultiplier(), condition));The node takes the two cost slots and the result, each as an item (or tag) plus its count as a NumberExpr — a
price rolled from a range is NumberExpr.uniformInt(min, max), and shows its most likely value like any count. The exact
constructor overloads differ slightly per Minecraft version — TradeUtils in ALI's own source is the reference. From
26.1.2 on there is no overload without per-slot tooltips: each slot is followed by a TooltipNode (a condition on a
cost, a modifier on the result, TooltipNode.empty() for none), and max uses and villager XP are NumberExprs,
followed by the trade's tooltip.
That is all that is needed to make the trade findable from either side: ItemsToItemsNode implements
ITradeNode, and the recipe viewer reads the accepted and offered items back out of the node itself. A trade node
of your own has to implement ITradeNode for that to work — see
Nodes and Widgets.
A slot may also be any node instead of one item — for a cost or result your mod describes as a loot pool entry, possibly several items with weights, conditions and functions:
new ItemsToItemsNode(
TradeUtils.getSlotNode(utils, costEntry), // a LootPoolEntryContainer, rolled like a pool of that one entry
TradeUtils.getEmptySlotNode(), // no second cost
TradeUtils.getSlotNode(utils, resultEntry),
tooltip); // the trade's own tooltip: max uses, XP, conditions, …Every item the slot's node can produce becomes one option of the slot: the recipe viewer cycles through them, showing the chance, count and functions of the one currently displayed, and the trade is found from any of them.
A slot whose item your trade picks at random from a known set is built the same way, from the set itself:
new ItemsToItemsNode(
utils,
TradeUtils.getItemSlotNode(List.of(new ItemStack(Items.EMERALD)), NumberExpr.uniformInt(2, 4), TooltipNode.empty()),
TradeUtils.getEmptySlotNode(),
TradeUtils.getItemSlotNode(saplings, NumberExpr.constant(1), TooltipNode.empty()), // one option per stack
maxUses, villagerXp, priceMultiplier, condition); // the usual trade tooltipTradeUtils.getSlotNode(List<IDataNode>) joins options built separately, for alternatives that differ in count. All
slots cycle on one clock, so two slots whose items vary together (a log and its stripped log) stay matched when both
list their options in the same order and number.
From 26.1.2 on only the first form exists, the one taking the trade's own tooltip.
A trader of your own — a custom entity, or a second kind of wandering merchant — has to be registered, otherwise
its offers are never scanned. Each level is a TradeLevel:
registry.registerTrades(
Identifier.fromNamespaceAndPath("mymod", "my_trader"), // the trader's ID
MyEntities.MY_TRADER.get(), // the trader's entity type, or null
(utils) -> { // Int2ObjectMap<TradeLevel>, per level
Int2ObjectMap<TradeLevel> levels = new Int2ObjectOpenHashMap<>();
levels.put(1, new TradeLevel.OfSet(ResourceKey.create(Registries.TRADE_SET,
Identifier.fromNamespaceAndPath("mymod", "my_trader/level_1"))));
levels.put(2, new TradeLevel.OfTrades(
new TradeLevelInfo(NumberExpr.constant(1), 0.5F), // offers picked from that level, and its chance
(utils) -> MyTrades.getNodes(utils))); // List<IDataNode>, built by your plugin
return levels;
});Up to 1.21.11 a level is a set of
ItemListings, and how many offers it contributes is a separate argument:registry.registerTrades( Identifier.fromNamespaceAndPath("mymod", "my_trader"), // the trader's ID MyEntities.MY_TRADER.get(), // the trader's entity type, or null () -> MyTrades.TRADES, // Int2ObjectMap<VillagerTrades.ItemListing[]>, per level (level) -> new TradeLevelInfo(NumberExpr.constant(2))); // offers picked from that level, and its chanceOn 1.21.1 and older the ID is a
ResourceLocation:ResourceLocation.fromNamespaceAndPath("mymod", "my_trader")on 1.21.1,new ResourceLocation("mymod", "my_trader")on 1.20.1.
- The ID is what the trade category patterns (
tradeCategories) are matched against, and it selects the displayed name: a villager profession is named byentity.minecraft.villager.<path>, anything else byentity.<namespace>.<path>. - The entity type links the trader to its entity: the recipe viewer shows the entity's spawn egg in front of
the trader's name, lists the trades under that spawn egg, and the name's tooltip shows the entity's ID. It is
nullfor a trader that is no entity — the entry then has neither. Several traders may share one entity type; every villager profession is registered withEntityType.VILLAGER. - The levels are built during the scan, not at registration time, so they may use data that is filled later
during startup. From 26.1.2 on that is a function taking
IServerUtils: a trader whose trades live in a datapack registry of your own reads it throughutils.lookupProvider(), andutils.convertIntNumberturns itsNumberProviders into theNumberExpraTradeLevelInfoneeds. Up to 1.21.11 it is a supplier taking nothing. -
How many offers a level contributes is shown in the level badge's tooltip, and so is a chance below
1, for a level the trader offers only sometimes. Where both come from depends on the kind of level, below.
A level is one of two kinds:
-
TradeLevel.OfSetnames aTradeSetof theminecraft:trade_setregistry. The set is looked up when the scan runs, so it may be one a datapack provides; a level whose set does not resolve is skipped. How many offers it contributes is the set's ownamount— a range, when thatamountis not a fixed number.TradeLevel.ofSets(...)turns anInt2ObjectMap<ResourceKey<TradeSet>>into levels of this kind, for a trader that consists of trade sets only. -
TradeLevel.OfTradesis for a trader that assembles its offers in Java and has no trade sets to name. It carries aTradeLevelInfo(offers picked, chance) and a function building the trade nodes — usuallyItemsToItemsNodes, seeregisterItemListingfor what the node needs. The function is called during the scan withIServerUtils.
Both kinds may be mixed in one trader, which is how a trader that adds some vanilla trade sets alongside its own offers is described.
Up to 1.21.11
registerTradestakes no level kinds: theTradeLevelInfothe last argument returns supplies the offers picked and the chance of every level — vanilla villagers use2for every level, the wandering trader5for its first pool and1for its second. On 1.20.1–1.21.11 levels returned byregisterTradeOverrideareTradeLevels, of the kindsTradeLevel.OfListings(anItemListing[]and itsTradeLevelInfo) andTradeLevel.OfTradesas above.
A level may pick its offers from several groups independently, each its own number — a trade group is a
TradeGroupNode(NumberExpr offers, List<IDataNode> trades) returned among the trades of a TradeLevel.OfTrades. The
level badge then counts the trades inside the groups, so pass the sum of the groups' selectionCounts as the level's
offers picked.
Every vanilla villager profession and the wandering trader are registered this way by ALI itself, so a modded
profession added to the vanilla VillagerProfession registry needs no registration — only a trader that is not a
villager profession does.
For a mod that replaces the trades of a trader that is already registered — its own data instead of the vanilla trades of a profession, for example:
registry.registerTradeOverride((utils, traderId) -> {
MyTradeTable table = MyTradeTables.get(traderId); // null when your mod leaves this trader alone
return table == null ? null : MyTrades.getLevels(utils, table); // Int2ObjectMap<TradeLevel>, per level
});It is called for every registered trader during the scan, after your mod's data is loaded. Overrides are asked in
the order they were registered and the first one that does not return null replaces the trader's levels; a trader
no override answers for keeps the levels it was registered with. The trader keeps its ID and entity type.
For loot your mod injects outside of loot tables and outside the Forge/NeoForge GLM system. The returned
ILootModifiers are applied to the matching block/entity/table nodes.
registry.registerLootModifiers((utils) -> MyLootRegistry.all().stream()
.map((entry) -> (ILootModifier<?>) new MyLootModifier(utils, entry))
.toList());For the Forge/NeoForge datapack GLM system, use the dedicated plugin interface instead: Global Loot Modifiers.
The same idea for modifiers judged per page (IPageLootModifier) instead of by a fixed type. This is how the
loader glue feeds the datapack GLMs in; a mod shipping its own GLMs goes through the plugin interface above.
Not on IServerRegistry alone — registerValueTooltip is the shared, superclass-aware tier:
registry.registerValueTooltip(MyPayload.class, (utils, value) -> TooltipBuilder.array((b) -> {
b.add(utils.getValueTooltip(utils, value.item()).build(MyLang.Value.ITEM));
b.add(utils.getValueTooltip(utils, value.amount()).build(MyLang.Value.AMOUNT));
}));Use it for your own records and data holders that appear inside another tooltip. Enums, collections, optionals,
numbers, Holders, tags and the like are already handled — do not register those. See Tooltips.
Optional. An enum is already displayed through a translation key derived from its class name; this only replaces the namespace and the derived part with ones you choose:
registry.registerEnumTranslation(MyMode.class, "mymod", "my_mode"); // mymod.enum.my_mode.fastSee Tooltips → Enum values for the key shape and the fallback behaviour.
Covered on their own pages, listed here so this reference is complete:
| Method | Registry | Where it is described |
|---|---|---|
registerCacheCleaner |
IServerRegistry |
Plugin Lifecycle → Caches for data collection |
registerTranslationKey |
ICommonRegistry |
Plugin Lifecycle → registerCommon |
registerEntityVariants |
ICommonRegistry |
Plugin Lifecycle → registerCommon |
registerDataNode / registerWidget
|
IClientRegistry |
Nodes and Widgets |
registerGlobalLootModifier |
IGlobalLootModifierPlugin.IRegistry |
Global Loot Modifiers |
Users
Loot Info
Worldgen Info
Developers
Loot Info
Examples
Worldgen Info