Mod compatibility

How Apoli integrates with other mods without hard-depending on them.

Apoli ships integrations with several other mods — accessory frameworks, Figura, Icarus, Simple Voice Chat, downed-state mods. Each is a gated compat module: it adds features when the other mod is present and does nothing (and never crashes) when it isn’t. This page is the pattern to follow for your own compat.

The gated-module pattern

A compat module lives in its own package (compat/<modid>/) and is only wired up when the target mod is loaded:

if (FabricLoader.getInstance().isModLoaded("trinkets")) {
    TrinketsBackend.register();
}

Everything mod-specific — the types it registers, the mixins it applies — stays behind that gate. Nothing in the core touches the other mod’s classes directly.

Built-in compat modules

ModuleAddsNeeds
compat.accessoryaccessory slots, conditions & actionsTrinkets / Accessories / Curios
compat.figuramodify_player_model → Figura avatarsFigura
compat.icarusapoli:wings flightIcarus
compat.voicechatvoice conditions & speak actionsSimple Voice Chat
compat.hardcorerevivalknockout / revivea downed-state mod

The accessory module is itself split behind a backend interface with Trinkets/Accessories/Curios implementations, each gated — so one bridge serves all three frameworks.

Mixins that might not apply

A compat mixin targets a class that only exists when the other mod is present. Gate it in the mixin plugin — and never load the class to check:

@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
    // getResource — does NOT define the class (Class.forName would, and break things)
    return getClass().getClassLoader()
        .getResource("dev/emi/trinkets/api/TrinketComponent.class") != null;
}

Loading a class in a config plugin defines it (and its supertypes) too early and hard-crashes other mods’ mixins. Check for the .class resource, or use the loader’s isModLoaded.

Rules

  1. No hard dependency. The mod must run with the compat target absent.
  2. Gate at every entry — registration, events, and mixins.
  3. Isolate mod-specific imports inside the compat package; the core stays clean.
  4. Build coordinates for the target go in compileOnly / modCompileOnly, not modImplementation.