Skip to main content
Version: 26.1

Global Loot Modifiers

Global Loot Modifiers, or GLMs for short, are a data-driven way to modify drops without the need to overwrite dozens or hundreds of vanilla loot tables, or to handle effects that would require interactions with another mod's loot tables without knowing what mods are loaded.

GLMs work by first rolling the associated loot table and then applying the GLM to the result of rolling the table. GLMs are also stacking, rather than last-load-wins, to allow for multiple mods to modify the same loot table, this is similar to tags.

To register a GLM, you will need three things:

  • A JSON file representing your loot modifier. This file contains all the data for your modification, allowing data packs to tweak your effect. It is located at data/<namespace>/loot_modifiers/<path>.json.
  • A class that implements IGlobalLootModifier or extends LootModifier (which in turn implements IGlobalLootModifier). This class contains the code that makes the modifier work.
  • A map codec to encode and decode your loot modifier class. Usually, this is implemented as a public static final field in the loot modifier class.

The Loot Modifier JSON

This file contains all values related to your modifier, for example chances to apply, what items to add, etc. The JSON can be found at data/<namespace>/loot_modifiers/<path>.json, where <namespace> and <path> are parts of the unique Identifier. It is recommended to avoid hard-coded values wherever possible so that data pack makers can adjust balance if they wish to. A loot modifier must contain at least two fields and may contain more, depending on the circumstances:

  • The type field contains the registry name of the loot modifier.
  • The conditions field is a list of loot table conditions for this modifier to activate.
  • Additional properties may be required or optional, depending on the used codec.
tip

A common use case for GLMs is to add extra loot to one specific loot table. To achieve this, the neoforge:loot_table_id condition can be used.

An example usage may look something like this:

{
// This is the registry name of the loot modifier
"type": "examplemod:my_loot_modifier",
"conditions": [
// Loot table conditions here
],
// An optional property typically provided by loot modifiers
// to denote the order that the modifiers should be applied,
// from highest to lowest.
// Typically defaults to 1000.
"priority": 900,
// Extra properties specified by the codec
"field1": "somestring",
"field2": 10,
"field3": "minecraft:dirt"
}

IGlobalLootModifier and LootModifier

To actually apply the loot modifier to the loot table, a IGlobalLootModifier implementation must be specified. In most cases, you will want to use the LootModifier subclass, which handles things like conditions and priorities for you. To get started, we extend LootModifier in our loot modifier class:

// We cannot use a record because records cannot extend other classes.
public class MyLootModifier extends LootModifier {
// See below for how the codec works.
public static final MapCodec<MyLootModifier> CODEC = ...;
// Our extra properties.
private final String field1;
private final int field2;
private final Item field3;

// First constructor parameter is the list of conditions. The rest is our extra properties.
public MyLootModifier(LootItemCondition[] conditions, int priority, String field1, int field2, Item field3) {
super(conditions, priority);
this.field1 = field1;
this.field2 = field2;
this.field3 = field3;
}

// Return our codec here.
@Override
public MapCodec<? extends IGlobalLootModifier> codec() {
return CODEC;
}

// This is where the magic happens. Use your extra properties here if needed.
// Parameters are the existing loot, and the loot context.
@Override
protected ObjectArrayList<ItemStack> doApply(ObjectArrayList<ItemStack> generatedLoot, LootContext context) {
// Add your items to generatedLoot here.
return generatedLoot;
}
}
info

The returned list of drops from a modifier is fed into other modifiers in priority order, from highest to lowest. As such, modified loot can and should be expected to be modified by another loot modifier.

The Loot Modifier Codec

To tell the game about the existence of our loot modifier, we must define and register a codec for it. Reiterating on our previous example with the three fields, this would look something like this:

public static final MapCodec<MyLootModifier> CODEC = RecordCodecBuilder.mapCodec(inst ->
// LootModifier#codecStart adds the conditions field.
LootModifier.codecStart(inst).and(inst.group(
Codec.STRING.fieldOf("field1").forGetter(e -> e.field1),
Codec.INT.fieldOf("field2").forGetter(e -> e.field2),
BuiltInRegistries.ITEM.byNameCodec().fieldOf("field3").forGetter(e -> e.field3)
)).apply(inst, MyLootModifier::new)
);

Then, we register the codec to the registry:

public static final DeferredRegister<MapCodec<? extends IGlobalLootModifier>> GLOBAL_LOOT_MODIFIER_SERIALIZERS =
DeferredRegister.create(NeoForgeRegistries.Keys.GLOBAL_LOOT_MODIFIER_SERIALIZERS, ExampleMod.MOD_ID);

public static final Supplier<MapCodec<MyLootModifier>> MY_LOOT_MODIFIER =
GLOBAL_LOOT_MODIFIER_SERIALIZERS.register("my_loot_modifier", () -> MyLootModifier.CODEC);

Builtin Loot Modifiers

NeoForge provides a loot modifier out of the box for you to use:

neoforge:add_table

This loot modifier rolls a second loot table and adds the results to the loot table the modifier is applied to.

{
"type": "neoforge:add_table",
"conditions": [], // the required loot conditions
"priority": 1000, // the optional priority of execution
"table": "minecraft:chests/abandoned_mineshaft" // the second table to roll
}

Datagen

GLMs can be datagenned. This is done by subclassing GlobalLootModifierProvider:

public class MyGlobalLootModifierProvider extends GlobalLootModifierProvider {
// Get the parameters from the `GatherDataEvent`s.
public MyGlobalLootModifierProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> registries) {
super(output, registries, ExampleMod.MOD_ID);
}

@Override
protected void start() {
// Call #add to add a new GLM. This also adds a corresponding entry in global_loot_modifiers.json.
this.add(
// The name of the modifier. This will be the file name.
"my_loot_modifier_instance",
// The loot modifier to add. For the sake of example, we add a weather loot condition.
new MyLootModifier(new LootItemCondition[] {
WeatherCheck.weather().setRaining(true).build()
}, 900, "somestring", 10, Items.DIRT),
// A list of data load conditions. Note that these are unrelated to the loot conditions
// specified on the modifier itself. For the sake of example, we add a mod loaded condition.
// An overload of #add is available that accepts a vararg of conditions instead of a list.
List.of(new ModLoadedCondition("create"))
);
}
}

And like all data providers, you must register the provider to the GatherDataEvents:

@SubscribeEvent // on the mod event bus
public static void onGatherData(GatherDataEvent.Client event) {
// Call event.createDatapackRegistryObjects(...) first if adding datapack objects

event.createProvider(MyGlobalLootModifierProvider::new);
}