Repository navigation
Developers Loot Info Global Loot Modifiers
Forge / NeoForge only. If your mod ships datapack global loot modifiers (GLM), ALI can already show them — as an Auto-detected JSON dump. To get them rendered properly, implement the GLM plugin interface. A complete minimal plugin is walked through in Example: a Global Loot Modifier.
com.yanny.ali.plugin.glm.IGlobalLootModifierPlugin extends IPlugin with one extra method:
@AliEntrypoint
public class MyModAliPlugin implements IGlobalLootModifierPlugin {
@Override
public String getModId() {
return "mymod";
}
@Override
public void registerGlobalLootModifier(IRegistry registry) {
registry.registerGlobalLootModifier(MyAddItemModifier.class, MyGlm::getAddItemModifier);
}
// registerCommon / registerClient / registerServer as usual
}The getter has the shape BiFunction<IServerUtils, T, Optional<IPageLootModifier>>. Returning
Optional.empty() means "this instance adds nothing displayable" and is a valid answer.
Building the IPageLootModifier by hand is rarely necessary. Hand the modifier itself and its own conditions to
GlobalLootModifierUtils.getLootModifier(utils, modifier, conditions, operationSupplier) instead and ALI judges it
against every page for you, reading the conditions to decide where it can and cannot apply:
public static Optional<IPageLootModifier> getAddItemModifier(IServerUtils utils, MyAddItemModifier modifier) {
return Optional.of(GlobalLootModifierUtils.getLootModifier(utils, modifier, NodeUtils.unwrapConditions(utils, modifier.getCondition()),
(page, conditions) -> List.of(new IOperation.AddOperation((stack) -> true, myNode(utils, conditions, modifier)))));
}getCondition() exposes the Optional<Holder<LootItemCondition>> your LootModifier holds;
NodeUtils.unwrapConditions (com.yanny.ali.plugin.common) turns that single, possibly combined condition into the
list ALI works with.
Up to 26.2: a
LootModifierholds aLootItemCondition[] conditionsarray — expose it asgetConditions()and pass it as a list:public static Optional<IPageLootModifier> getAddItemModifier(IServerUtils utils, MyAddItemModifier modifier) { return Optional.of(GlobalLootModifierUtils.getLootModifier(utils, modifier, Arrays.asList(modifier.getConditions()), (page, conditions) -> List.of(new IOperation.AddOperation((stack) -> true, myNode(utils, conditions, modifier))))); }
The operation supplier is called per page, with that page (LootPage — its table id, its loot parameters, and the
blocks or entity types it stands for) and the conditions still worth showing there: a condition the page fully
accounts for is left out, because the page already says it.
Most conditions need nothing from you. ALI decides a condition it has no resolver for by running it against a
context built for that page, which answers what the page knows — the loot parameters it is rolled with, its table
id — and refuses every read it cannot. A condition needing a parameter the page never has rules the modifier out by
itself, and AllOf / AnyOf / inverted groups are descended into.
Two cases do need telling, and
registerPageResolver covers both:
- A condition only your mod understands. One that reads your own runtime state, rather than the loot context, cannot be decided by running it. Register a resolver for that condition class.
-
A modifier that keeps its target in its own fields — a table id, a path fragment, a list of blocks — says
nothing in any condition. Register a resolver for the modifier class; that is what the
modifierargument above is passed for. It is read for modifiers ALI renders as Auto-detected too, so a resolver alone is enough to place an otherwise unadapted modifier on the right pages.
A condition of yours that reads the queried loot table id from its own carrier is the third case, and it wants
registerLootContextPreparer rather
than a resolver: fill the carrier and the condition decides itself.
null from a resolver means "no opinion", and every condition plus the modifier itself is consulted, so a modifier
may be pinned by its fields and constrained by its conditions at the same time.
If your modifier's node is one of ALI's own, such as ModifiedNode, its widget and client constructor are already
registered — registerClient needs nothing for it. Only a node type of your own needs registering, as described in
Nodes and Widgets.
An IPageLootModifier answers two questions, both about one page:
| Member | Meaning |
|---|---|
PageMatch test(LootPage page) |
whether it applies here — Match.YES, Match.NO, or Match.UNKNOWN when nothing settles it — together with the conditions this page does not already account for |
List<IOperation> getOperations(LootPage page, PageMatch match) |
what it does there, given the result of test for the same page |
PageMatch.NO is the ready-made answer for a page the modifier never applies to. A hand-built modifier that has no
conditions to hide passes all of its conditions as the unexplained ones, and the operations then show them in the
tooltip.
IOperation is a sealed set of three records, each carrying a Predicate<ItemStack> selecting the stacks it acts
on:
-
AddOperation(predicate, node)— adds a node to the matched loot, -
RemoveOperation(predicate, factory)— replaces a matched node with a "removed" rendering, -
ReplaceOperation(predicate, factory)— replaces a matched node with one or more other nodes.
These are the same operations ALI's own GLM support and the LootJS module build, so a modifier registered this way is displayed exactly like a built-in one.
A modifier is bound on a page when nothing answers Match.NO there and at least one condition — or the
modifier itself — answers Match.YES. A modifier that comes out Match.UNKNOWN everywhere, pointing at nothing in
particular and deciding at runtime, is shown only with showUnboundedGlobalLootModifiers enabled in the config,
since one such modifier can otherwise appear on hundreds of pages; it then lands wherever nothing rules it out.
A bound modifier is also checked against the items of the page before it is attached: it is kept when one of its operations matches an item that page can produce, or when the page produces no items at all. A modifier that matches nothing anywhere is dropped, and the count of those is logged at startup.
If your mod injects loot without the datapack GLM system (including anything on Fabric), do not use this
interface. Use registerLootModifiers on IServerRegistry instead. Those are ILootModifiers — the same
IOperations, but bound to a block, an entity type or one loot table by hand instead of being judged per page —
and they are registered directly:
registry.registerLootModifiers((utils) -> MyLootRegistry.all().stream()
.map((entry) -> (ILootModifier<?>) new MyLootModifier(utils, entry))
.toList());See the Registration Reference.
Users
Loot Info
Worldgen Info
Developers
Loot Info
Examples
Worldgen Info