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.hurtdamage 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.hurtshows one target./clight mixins conflictsshows overwrite conflicts, mixins turned off after errors, and mods hiding other mods. It saysno mixin conflictswhen everything is fine./clight mixins statsshows 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.