Skip to content

Developers Examples Conditions And Functions

Yanny edited this page Oct 3, 2026 · 2 revisions

Example — loot conditions and functions

A complete, minimal plugin that makes ALI display one loot condition and one loot function of your own mod — the things datapacks call predicates and item modifiers. Works on every loader. The reference behind every step is Registration Reference → Conditions and functions and Numbers, chances and counts; this page only puts the pieces together.

Without a plugin ALI already lists your condition or function, as an Auto-detected dump of its fields, but it cannot tell what it does to the drop. After the steps below the tooltip describes each of them in words, and the chance and the count shown for the item already include their effect.

The condition and the function

Say your mod has a condition that passes with some probability, but only while it rains:

package com.example.mymod.loot;

import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;

public class RainChanceCondition implements LootItemCondition {
    public static final MapCodec<RainChanceCondition> MAP_CODEC = RecordCodecBuilder.mapCodec((i) -> i.group(
            Codec.FLOAT.fieldOf("probability").forGetter(RainChanceCondition::getProbability)
    ).apply(i, RainChanceCondition::new));

    private final float probability;

    public RainChanceCondition(float probability) {
        this.probability = probability;
    }

    @Override
    public MapCodec<RainChanceCondition> codec() {
        return MAP_CODEC;
    }

    @Override
    public boolean test(LootContext context) {
        return context.getLevel().isRaining() && context.getRandom().nextFloat() < probability;
    }

    public float getProbability() { return probability; }
}

and a function that adds a random bonus to the stack count:

package com.example.mymod.loot;

import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.core.Holder;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.functions.LootItemConditionalFunction;
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
import net.minecraft.world.level.storage.loot.providers.number.ints.ContextIntProvider;
import net.minecraft.world.level.storage.loot.providers.number.ints.ContextIntProviders;

import java.util.Optional;

public class AddBonusFunction extends LootItemConditionalFunction {
    public static final MapCodec<AddBonusFunction> MAP_CODEC = RecordCodecBuilder.mapCodec((i) -> commonFields(i)
            .and(ContextIntProviders.CODEC.fieldOf("bonus").forGetter(AddBonusFunction::getBonus))
            .apply(i, AddBonusFunction::new));

    private final Holder<ContextIntProvider> bonus;

    private AddBonusFunction(Optional<Holder<LootItemCondition>> condition, Holder<ContextIntProvider> bonus) {
        super(condition);
        this.bonus = bonus;
    }

    @Override
    public MapCodec<AddBonusFunction> codec() {
        return MAP_CODEC;
    }

    @Override
    protected ItemStack run(ItemStack stack, LootContext context) {
        stack.grow(bonus.value().getInt(context));
        return stack;
    }

    public Holder<ContextIntProvider> getBonus() { return bonus; }
    public Optional<Holder<LootItemCondition>> getCondition() { return condition; }
}

As for any other mod content, ALI only needs to be able to read the fields — add getters, or make the fields accessible some other way. condition is the function's own condition, inherited from LootItemConditionalFunction.

Both types are registered under your mod ID, like any other loot type. On Fabric, call this from your ModInitializer:

package com.example.mymod.loot;

import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;

public class MyModLoot {
    public static void register() {
        Registry.register(BuiltInRegistries.LOOT_CONDITION_TYPE, "mymod:rain_chance", RainChanceCondition.MAP_CODEC);
        Registry.register(BuiltInRegistries.LOOT_FUNCTION_TYPE, "mymod:add_bonus", AddBonusFunction.MAP_CODEC);
    }
}

On NeoForge, call MyModLoot.register(modEventBus) from your mod's constructor:

package com.example.mymod.loot;

import com.mojang.serialization.MapCodec;
import net.minecraft.core.registries.Registries;
import net.minecraft.world.level.storage.loot.functions.LootItemFunction;
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.neoforge.registries.DeferredRegister;

public class MyModLoot {
    private static final DeferredRegister<MapCodec<? extends LootItemCondition>> CONDITIONS =
            DeferredRegister.create(Registries.LOOT_CONDITION_TYPE, "mymod");
    private static final DeferredRegister<MapCodec<? extends LootItemFunction>> FUNCTIONS =
            DeferredRegister.create(Registries.LOOT_FUNCTION_TYPE, "mymod");

    public static void register(IEventBus modEventBus) {
        CONDITIONS.register("rain_chance", () -> RainChanceCondition.MAP_CODEC);
        FUNCTIONS.register("add_bonus", () -> AddBonusFunction.MAP_CODEC);
        CONDITIONS.register(modEventBus);
        FUNCTIONS.register(modEventBus);
    }
}

Forge (1.20.1 and 1.21.1 only) registers the same way with net.minecraftforge.registries.DeferredRegister and net.minecraftforge.eventbus.api.IEventBus; see the version notes below.

26.1.2–26.2: RainChanceCondition and both MyModLoot variants are the same as above. A number provider is a plain NumberProvider, and the function's own conditions are the inherited List<LootItemCondition> predicates:

AddBonusFunction on 26.1.2–26.2
package com.example.mymod.loot;

import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.functions.LootItemConditionalFunction;
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
import net.minecraft.world.level.storage.loot.providers.number.NumberProvider;
import net.minecraft.world.level.storage.loot.providers.number.NumberProviders;

import java.util.List;

public class AddBonusFunction extends LootItemConditionalFunction {
    public static final MapCodec<AddBonusFunction> MAP_CODEC = RecordCodecBuilder.mapCodec((i) -> commonFields(i)
            .and(NumberProviders.CODEC.fieldOf("bonus").forGetter(AddBonusFunction::getBonus))
            .apply(i, AddBonusFunction::new));

    private final NumberProvider bonus;

    private AddBonusFunction(List<LootItemCondition> predicates, NumberProvider bonus) {
        super(predicates);
        this.bonus = bonus;
    }

    @Override
    public MapCodec<AddBonusFunction> codec() {
        return MAP_CODEC;
    }

    @Override
    protected ItemStack run(ItemStack stack, LootContext context) {
        stack.grow(bonus.getInt(context));
        return stack;
    }

    public NumberProvider getBonus() { return bonus; }
    public List<LootItemCondition> getPredicates() { return predicates; }
}

1.21.1–1.21.11: as on 26.1.2–26.2, but a condition and a function return their registered type from getType() instead of a codec from codec(), so the type objects are created in MyModLoot and registered there:

RainChanceCondition, AddBonusFunction and MyModLoot on 1.21.1–1.21.11
package com.example.mymod.loot;

import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;

public class RainChanceCondition implements LootItemCondition {
    public static final MapCodec<RainChanceCondition> MAP_CODEC = RecordCodecBuilder.mapCodec((i) -> i.group(
            Codec.FLOAT.fieldOf("probability").forGetter(RainChanceCondition::getProbability)
    ).apply(i, RainChanceCondition::new));

    private final float probability;

    public RainChanceCondition(float probability) {
        this.probability = probability;
    }

    @Override
    public LootItemConditionType getType() {
        return MyModLoot.RAIN_CHANCE;
    }

    @Override
    public boolean test(LootContext context) {
        return context.getLevel().isRaining() && context.getRandom().nextFloat() < probability;
    }

    public float getProbability() { return probability; }
}
package com.example.mymod.loot;

import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.functions.LootItemConditionalFunction;
import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType;
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
import net.minecraft.world.level.storage.loot.providers.number.NumberProvider;
import net.minecraft.world.level.storage.loot.providers.number.NumberProviders;

import java.util.List;

public class AddBonusFunction extends LootItemConditionalFunction {
    public static final MapCodec<AddBonusFunction> MAP_CODEC = RecordCodecBuilder.mapCodec((i) -> commonFields(i)
            .and(NumberProviders.CODEC.fieldOf("bonus").forGetter(AddBonusFunction::getBonus))
            .apply(i, AddBonusFunction::new));

    private final NumberProvider bonus;

    private AddBonusFunction(List<LootItemCondition> predicates, NumberProvider bonus) {
        super(predicates);
        this.bonus = bonus;
    }

    @Override
    public LootItemFunctionType<AddBonusFunction> getType() {
        return MyModLoot.ADD_BONUS;
    }

    @Override
    protected ItemStack run(ItemStack stack, LootContext context) {
        stack.grow(bonus.getInt(context));
        return stack;
    }

    public NumberProvider getBonus() { return bonus; }
    public List<LootItemCondition> getPredicates() { return predicates; }
}

Fabric:

package com.example.mymod.loot;

import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType;
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;

public class MyModLoot {
    public static final LootItemConditionType RAIN_CHANCE = new LootItemConditionType(RainChanceCondition.MAP_CODEC);
    public static final LootItemFunctionType<AddBonusFunction> ADD_BONUS = new LootItemFunctionType<>(AddBonusFunction.MAP_CODEC);

    public static void register() {
        Registry.register(BuiltInRegistries.LOOT_CONDITION_TYPE, "mymod:rain_chance", RAIN_CHANCE);
        Registry.register(BuiltInRegistries.LOOT_FUNCTION_TYPE, "mymod:add_bonus", ADD_BONUS);
    }
}

NeoForge, and Forge on 1.21.1 with the Forge packages named above:

package com.example.mymod.loot;

import net.minecraft.core.registries.Registries;
import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType;
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.neoforge.registries.DeferredRegister;

public class MyModLoot {
    public static final LootItemConditionType RAIN_CHANCE = new LootItemConditionType(RainChanceCondition.MAP_CODEC);
    public static final LootItemFunctionType<AddBonusFunction> ADD_BONUS = new LootItemFunctionType<>(AddBonusFunction.MAP_CODEC);

    private static final DeferredRegister<LootItemConditionType> CONDITIONS =
            DeferredRegister.create(Registries.LOOT_CONDITION_TYPE, "mymod");
    private static final DeferredRegister<LootItemFunctionType<?>> FUNCTIONS =
            DeferredRegister.create(Registries.LOOT_FUNCTION_TYPE, "mymod");

    public static void register(IEventBus modEventBus) {
        CONDITIONS.register("rain_chance", () -> RAIN_CHANCE);
        FUNCTIONS.register("add_bonus", () -> ADD_BONUS);
        CONDITIONS.register(modEventBus);
        FUNCTIONS.register(modEventBus);
    }
}

On 1.20.1 loot types are read from JSON by a Serializer instead of a codec, and the function's own conditions are the inherited array LootItemCondition[] predicates:

RainChanceCondition, AddBonusFunction and MyModLoot on 1.20.1
package com.example.mymod.loot;

import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;

public class RainChanceCondition implements LootItemCondition {
    private final float probability;

    public RainChanceCondition(float probability) {
        this.probability = probability;
    }

    @Override
    public LootItemConditionType getType() {
        return MyModLoot.RAIN_CHANCE;
    }

    @Override
    public boolean test(LootContext context) {
        return context.getLevel().isRaining() && context.getRandom().nextFloat() < probability;
    }

    public float getProbability() { return probability; }

    public static class Serializer implements net.minecraft.world.level.storage.loot.Serializer<RainChanceCondition> {
        @Override
        public void serialize(JsonObject json, RainChanceCondition condition, JsonSerializationContext context) {
            json.addProperty("probability", condition.probability);
        }

        @Override
        public RainChanceCondition deserialize(JsonObject json, JsonDeserializationContext context) {
            return new RainChanceCondition(GsonHelper.getAsFloat(json, "probability"));
        }
    }
}
package com.example.mymod.loot;

import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.storage.loot.LootContext;
import net.minecraft.world.level.storage.loot.functions.LootItemConditionalFunction;
import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType;
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
import net.minecraft.world.level.storage.loot.providers.number.NumberProvider;

public class AddBonusFunction extends LootItemConditionalFunction {
    private final NumberProvider bonus;

    public AddBonusFunction(LootItemCondition[] predicates, NumberProvider bonus) {
        super(predicates);
        this.bonus = bonus;
    }

    @Override
    public LootItemFunctionType getType() {
        return MyModLoot.ADD_BONUS;
    }

    @Override
    protected ItemStack run(ItemStack stack, LootContext context) {
        stack.grow(bonus.getInt(context));
        return stack;
    }

    public NumberProvider getBonus() { return bonus; }
    public LootItemCondition[] getPredicates() { return predicates; }

    public static class Serializer extends LootItemConditionalFunction.Serializer<AddBonusFunction> {
        @Override
        public void serialize(JsonObject json, AddBonusFunction function, JsonSerializationContext context) {
            super.serialize(json, function, context);
            json.add("bonus", context.serialize(function.bonus));
        }

        @Override
        public AddBonusFunction deserialize(JsonObject json, JsonDeserializationContext context,
                                            LootItemCondition[] predicates) {
            return new AddBonusFunction(predicates, GsonHelper.getAsObject(json, "bonus", context, NumberProvider.class));
        }
    }
}

Fabric:

package com.example.mymod.loot;

import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType;
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;

public class MyModLoot {
    public static final LootItemConditionType RAIN_CHANCE = new LootItemConditionType(new RainChanceCondition.Serializer());
    public static final LootItemFunctionType ADD_BONUS = new LootItemFunctionType(new AddBonusFunction.Serializer());

    public static void register() {
        Registry.register(BuiltInRegistries.LOOT_CONDITION_TYPE, "mymod:rain_chance", RAIN_CHANCE);
        Registry.register(BuiltInRegistries.LOOT_FUNCTION_TYPE, "mymod:add_bonus", ADD_BONUS);
    }
}

Forge:

package com.example.mymod.loot;

import net.minecraft.core.registries.Registries;
import net.minecraft.world.level.storage.loot.functions.LootItemFunctionType;
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;

public class MyModLoot {
    public static final LootItemConditionType RAIN_CHANCE = new LootItemConditionType(new RainChanceCondition.Serializer());
    public static final LootItemFunctionType ADD_BONUS = new LootItemFunctionType(new AddBonusFunction.Serializer());

    private static final DeferredRegister<LootItemConditionType> CONDITIONS =
            DeferredRegister.create(Registries.LOOT_CONDITION_TYPE, "mymod");
    private static final DeferredRegister<LootItemFunctionType> FUNCTIONS =
            DeferredRegister.create(Registries.LOOT_FUNCTION_TYPE, "mymod");

    public static void register(IEventBus modEventBus) {
        CONDITIONS.register("rain_chance", () -> RAIN_CHANCE);
        FUNCTIONS.register("add_bonus", () -> ADD_BONUS);
        CONDITIONS.register(modEventBus);
        FUNCTIONS.register(modEventBus);
    }
}

Step 1 — the dependency

Compile against ALI and ACI and keep both optional at runtime, exactly as described in Getting Started → Depending on ALI. The classes below are the only ones allowed to reference ALI.

Step 2 — translation keys

Every text in an ALI tooltip is a translation key of yours. Declare them once, as an enum:

package com.example.mymod.ali;

import com.yanny.aci.language.ITooltipKey;
import com.yanny.aci.language.Translation;

public enum MyLang implements ITooltipKey {
    RAIN_CHANCE("mymod.ali.condition.rain_chance", "Rain Chance:"),
    ADD_BONUS("mymod.ali.function.add_bonus", "Add Bonus:"),
    PROBABILITY("mymod.ali.value.probability", "Probability: %s"),
    BONUS("mymod.ali.value.bonus", "Bonus: %s"),
    PREDICATES("mymod.ali.value.predicates", "Predicates:"),
    ;

    private final Translation translation;

    MyLang(String key, String english) {
        translation = new Translation(key, english);
    }

    @Override
    public Translation getTranslation() {
        return translation;
    }
}

and translate them in your own language file, assets/mymod/lang/en_us.json:

{
  "mymod.ali.condition.rain_chance": "Rain Chance:",
  "mymod.ali.function.add_bonus": "Add Bonus:",
  "mymod.ali.value.probability": "Probability: %s",
  "mymod.ali.value.bonus": "Bonus: %s",
  "mymod.ali.value.predicates": "Predicates:"
}

A key ending in : heads a group of lines; a key with %s is one line holding a value. The English text in the enum is not shown by itself — only what the language file says is.

Step 3 — the plugin class

package com.example.mymod.ali;

import com.example.mymod.loot.AddBonusFunction;
import com.example.mymod.loot.RainChanceCondition;
import com.yanny.ali.api.AliEntrypoint;
import com.yanny.ali.api.ICommonRegistry;
import com.yanny.ali.api.IPlugin;
import com.yanny.ali.api.IServerRegistry;

@AliEntrypoint
public class MyModAliPlugin implements IPlugin {
    @Override
    public String getModId() {
        return "mymod";
    }

    @Override
    public void registerCommon(ICommonRegistry registry) {
        for (MyLang key : MyLang.values()) {
            registry.registerTranslationKey(key.singular());
        }
    }

    @Override
    public void registerServer(IServerRegistry registry) {
        registry.registerConditionTooltip(RainChanceCondition.class, MyModTooltips::getRainChanceTooltip);
        registry.registerChanceModifier(RainChanceCondition.class, MyModTooltips::applyRainChance);

        registry.registerFunctionTooltip(AddBonusFunction.class, MyModTooltips::getAddBonusTooltip);
        registry.registerCountModifier(AddBonusFunction.class, MyModTooltips::applyAddBonus);
    }
}
  • On Forge / NeoForge @AliEntrypoint is the whole discovery. On Fabric list the class under the ali entrypoint in fabric.mod.json — see Making ALI find the plugin.
  • registerCommon runs on both sides: a key the client does not know is shown untranslated.
  • Every class is looked up exactly: a subclass of your condition or function needs a registration of its own.

Step 4 — the tooltips and the numbers

package com.example.mymod.ali;

import com.example.mymod.loot.AddBonusFunction;
import com.example.mymod.loot.RainChanceCondition;
import com.yanny.aci.api.NumberExpr;
import com.yanny.aci.tooltip.TooltipBuilder;
import com.yanny.aci.tooltip.TooltipNode;
import com.yanny.ali.api.IServerUtils;

import java.util.List;

public class MyModTooltips {
    public static TooltipBuilder getRainChanceTooltip(IServerUtils utils, RainChanceCondition condition) {
        return TooltipBuilder.array((b) -> {
            b.add(utils.getValueTooltip(utils, condition.getProbability()).build(MyLang.PROBABILITY));
        }, MyLang.RAIN_CHANCE);
    }

    public static NumberExpr applyRainChance(IServerUtils utils, RainChanceCondition condition, NumberExpr chance,
                                             List<TooltipNode> conditions) {
        return NumberExpr.mul(chance, NumberExpr.constant(condition.getProbability()));
    }

    public static TooltipBuilder getAddBonusTooltip(IServerUtils utils, AddBonusFunction function) {
        return TooltipBuilder.array((b) -> {
            b.add(utils.getValueTooltip(utils, function.getBonus()).build(MyLang.BONUS));
            b.add(utils.getValueTooltip(utils, function.getCondition()).build(MyLang.PREDICATES));
        }, MyLang.ADD_BONUS);
    }

    public static NumberExpr applyAddBonus(IServerUtils utils, AddBonusFunction function, NumberExpr count,
                                           List<TooltipNode> conditions) {
        return NumberExpr.add(count, utils.convertContextInt(utils, function.getBonus(), conditions));
    }
}

Up to 26.2 the bonus is a NumberProvider, read with utils.convertIntNumber, and the function's conditions come from getPredicates(); the rest of the class is unchanged:

public static TooltipBuilder getAddBonusTooltip(IServerUtils utils, AddBonusFunction function) {
    return TooltipBuilder.array((b) -> {
        b.add(utils.getValueTooltip(utils, function.getBonus()).build(MyLang.BONUS));
        b.add(utils.getValueTooltip(utils, function.getPredicates()).build(MyLang.PREDICATES));
    }, MyLang.ADD_BONUS);
}

public static NumberExpr applyAddBonus(IServerUtils utils, AddBonusFunction function, NumberExpr count,
                                       List<TooltipNode> conditions) {
    return NumberExpr.add(count, utils.convertIntNumber(utils, function.getBonus(), conditions));
}

getValueTooltip takes the predicates as a List and, on 1.20.1, as an array alike.

What each part does:

  • registerConditionTooltip / registerFunctionTooltip describe the condition or function in words. They return a TooltipBuilder: array(children, key) is a heading with lines under it, and utils.getValueTooltip(utils, value).build(key) turns any value — a number, a number provider, an item, a tag, a condition or a list of them — into a line, using the same rendering ALI uses for vanilla. An empty value (an absent condition or an empty list of predicates, say) leaves no line behind. More on building tooltips in Tooltips.
  • registerChanceModifier tells ALI what the condition does to the chance of the drop: it gets the chance so far, as a fraction, and returns the new one. Without it the chance shown ignores your condition. The rain part cannot be known in advance; the tooltip states it, the chance covers the part that can be computed.
  • registerCountModifier does the same for the count: it gets the count so far and returns the new one. Read a number provider through utils.convertContextInt (or convertContextFloat for a ContextFloatProvider; up to 26.2 convertIntNumber or convertNumber, whichever one your function calls on it) and hand it the conditions list you received — a provider that picks its value by a condition records it there, so it is shown under its row. If your function has a condition of its own, ALI splits the count into a row with and a row without it; you do nothing for it.
  • Counts and chances are NumberExpr expressions, not plain numbers: build them with the static factories (NumberExpr.constant, add, mul, uniformInt, …) and the viewer derives the range, the most likely value and the chart from them. See Numbers, chances and counts.

A function that changes the stack itself rather than its count — enchants it, sets a name or NBT/components — registers registerItemStackModifier instead of (or as well as) a count modifier, so the slot shows the changed item; a condition or function that only wraps others registers an unwrapper. Both are in the Registration Reference.

Step 5 — check it

Start the game with ALI and a recipe viewer and open a loot table that uses your condition or function. Hovering the item shows Rain Chance: and Add Bonus: with their values instead of an Auto-detected dump, and the chance and count lines already include them. A count or chance modifier that throws is logged and turns only that value into ? (<type>) — the rest of the loot table is still shown.

Questions: Discord.

Clone this wiki locally