Skip to content

Priorities, conflicts and debugging

Lesson 12.5

Players install many mods at once, and several of them may hook the same target. This lesson shows how mixins share a target, how to choose your place in the line, and how to see what is going on.

A chain, not a fight

All mixins on a target form a chain. Each one sees what the one before it left:

  • Two mods that halve entity.living.hurt damage in a head give a quarter.
  • A value mixin that doubles the knockback after another that tripled it gives six times.
  • After a head cancelled, the heads after it are skipped.

The order is the same on every computer and every run, so a server and its players always agree.

Priorities

You choose your place with .priority, the same scale as for events:

Constant Number Use it for
CL_PRIORITY_FIRST -2000 Rules that must win, like protection.
CL_PRIORITY_EARLY -1000
CL_PRIORITY_NORMAL 0 The default.
CL_PRIORITY_LATE 1000
CL_PRIORITY_LAST 2000 Final adjustments after everybody else.
CL_PRIORITY_MONITOR 10000 Only watching. A monitor mixin is read-only: its changes are dropped.
/* A protection mod: nobody hurts villagers, whatever other mods do. */
static void protect_villagers(cl_mixin_ci* ci, void* user) {
    (void)user;
    cl_mx_entity_living_hurt_set_return(ci, false);
    cl_mixin_cancel(ci);
}
CL_MIXIN(entity_living_hurt, head, protect_villagers, .priority = CL_PRIORITY_FIRST, .filter = "minecraft:villager");

/* A statistics mod: count real hits, after every mod had its say. */
static int g_hits;
static void count_hits(cl_mixin_ci* ci, void* user) {
    (void)user;
    if (*cl_mx_entity_living_hurt_ret_of(ci)) ++g_hits;
}
CL_MIXIN(entity_living_hurt, return, count_hits, .priority = CL_PRIORITY_MONITOR);

Within the same priority, the mods run in load order. If your mod must run right before or after a particular mod, give its id (several ids are separated by commas):

CL_MIXIN(entity_living_hurt, head, my_rule, .before = "armor_plus");
CL_MIXIN(entity_living_hurt, head, my_rule2, .after = "armor_plus,magic_shields");

To see cancelled calls too (to log them, for example), add .flags = CL_MXF_RECEIVE_CANCELLED. Such a head then runs read-only after a cancel. Flags combine with |: CL_MXF_OPTIONAL | CL_MXF_RECEIVE_CANCELLED.

For wraps, “first” means outermost: the first wrap’s code runs before all others and its cl_mixin_call_original runs the rest of the chain.

Conflicts

Most kinds of mixin simply chain. Two situations are real conflicts:

Situation What happens
Two mods overwrite one target The one that loads first wins. The other one’s overwrite is off (CLL-7005); the rest of that mod keeps working.
One mod overwrites a target that another mod has points or values in Those points and values never run, because the code they live in doesn’t (CLL-7010).
A wrap never calls cl_mixin_call_original The mods after it are hidden. The Mods screen shows “mod A hides mod B on target”.

When a mixin breaks

If your function traps (a null pointer, a division by zero, abort()), the game drops whatever that call changed, turns that one mixin off for the rest of the session, and carries on with vanilla behaviour. The log says why:

[safe_saddles] CLL-7009: disabled for entity.living.cause_fall_damage after an error: the WebAssembly callback trapped (see the log)

Your other mixins and events keep running. As with events, many errors in a short time disable the whole mod. Debugging shows how to find the line.

/clight mixins

In a world with cheats on (or as an operator on a server), type /clight mixins in chat. It lists every target that has mixins, in the order they run, with the mod, the priority and how often each was called:

entity.living.hurt
  head  protect_villagers  priority -2000  calls 12
  head  safe_saddles  priority 0  calls 40
  return  stats_mod  priority 10000  calls 52
entity.living.cause_fall_damage
  head  safe_saddles  priority 0  calls 3

More detail:

  • /clight mixins entity.living.hurt shows one target.
  • /clight mixins conflicts shows overwrite conflicts, mixins turned off after errors, and mods hiding other mods. It says no mixin conflicts when everything is fine.
  • /clight mixins stats shows calls, cancels and errors per mixin.

A mixin with calls 0 never ran: maybe the filter is misspelt ("minecraft:horses"), or the thing hasn’t happened yet.

When you start the game with clight run (development mode), it also writes the whole table and the conflicts to logs/clight-mixins.txt in the game folder, and rewrites it whenever the mixins change.

clight check

clight check reads the CL_MIXIN records from your built .wasm and checks every one: the target exists, the point or value exists, the target isn’t newer than your API version, and the mod has the permissions the target needs. A typo is reported before you ever start the game:

> clight check
error: bin/safe_saddles.wasm CL_MIXIN #0: CLL-7002 entity.living.hurt has no point 'before_knock' (it has point:after_blocking, value:invulnerability_ticks, value:knockback_strength, point:before_knockback, point:before_death)
FAILED: safe_saddles 1.0.0

The Celestial website runs the same check on upload and shows your mixins on the project page, so players can see what your mod changes.

Try it

Two mods both change player.attack crits: one sets the multiplier to 2.0, yours wants to add 0.5 to whatever the result is. Declare your mixin so that it always comes after the other mod (crit_master), and check with a command what the order is.

Show a solution
static void crit_bonus(cl_mixin_ci* ci, void* user) {
    (void)user;
    *cl_mx_player_attack_crit_multiplier_of(ci) += 0.5f;
}
CL_MIXIN_SITE(player_attack, value, crit_multiplier, crit_bonus, .after = "crit_master");

/clight mixins player.attack then shows value:crit_multiplier crit_master above your line. .priority = CL_PRIORITY_LATE would also work, but .after says exactly what you mean and doesn’t push you behind unrelated mods.

Recap: mixins chain in priority order (CL_PRIORITY_FIRST … MONITOR, then .before/.after, then load order), only overwrites truly conflict, a trapping mixin turns only itself off, and /clight mixins and clight check show it all.