-
-
Notifications
You must be signed in to change notification settings - Fork 6
Api Overview
Armor Hider ships a small public API for other mods to integrate with. You can register your own combat detection, hook into how equipment is intercepted and rendered, customize the color math used for fades, provide your own render types, or query and influence player configs.
This page is aimed at mod developers. If you just want to configure the mod as a player, see Configuration instead.
The API was introduced in 0.12.0. Types carry @since Javadoc so you can tell when a given method appeared.
There are two source sets, split by side:
-
de.zannagh.armorhider.api.*is common (available on both client and server) -
de.zannagh.armorhider.client.api.*is client-only
A few conventions worth knowing before you start:
- Interfaces marked
@ApiStatus.NonExtendableare meant to be called, not implemented. That's the registries and management surface. - Methods marked
@ApiStatus.Internalexist for the mod's own mixins. They're public for wiring reasons but aren't part of the integration surface, so don't call them. - Anything in an
implpackage is internal. Reach it through the public static facades.
There are two halves: a runtime accessor and a registration hook.
The accessor is ArmorHiderApi.getInstance(). It throws IllegalStateException if you call it before the mod has initialized, so the safe place to grab it is from the registration hook.
The registration hook is ArmorHiderInitializer, a functional interface discovered through Java's ServiceLoader. This is loader-agnostic, so the same integration works on Fabric and NeoForge with no per-loader metadata. You ship a service file at META-INF/services/de.zannagh.armorhider.api.ArmorHiderInitializer naming your implementation, and Armor Hider calls it once the API is fully initialized.
public final class MyIntegration implements ArmorHiderInitializer {
@Override
public void onInitializeArmorHider(ArmorHiderApi api) {
api.getCombatManagement().registerCombatEventConsumer(new MyCombatConsumer());
}
@Override
public int priority() {
return 0; // lower runs first, default is 0
}
}Initializers are sorted by priority() (ties fall back to discovery order). If one initializer throws, it's logged and the others still run.
Note that Armor Hider itself ships no service file. The mechanism exists purely for third parties.
The combat API is common-side and reached through ArmorHiderApi.getInstance().getCombatManagement(), which returns an ArmorHiderCombatManagementApi. See Combat Detection for the user-facing behavior.
The most common integration is registering a combat event consumer and firing events:
api.getCombatManagement().registerCombatEventConsumer(consumer);
api.getCombatManagement().registerCombatEvent(playerDisplayName);You can also register events with an explicit timestamp or a fully built ArmorHiderCombatEvent, and query state:
boolean fighting = api.getCombatManagement().isInCombat(playerDisplayName);
double faded = api.getCombatManagement().getCombatFade(playerDisplayName, originalTransparency);An ArmorHiderCombatEvent carries a player display name, a timestamp and a fade duration (the default implementation, DefaultCombatEvent, fades over 10 seconds). If you want to take over combat handling entirely rather than adding to it, overrideDefaultBehavior(customManagement, priority) installs a replacement management implementation, lowest priority integer wins.
To plug in your own detection logic, implement ArmorHiderCombatEventConsumer. It extends PrioritizedHandler, so you get the usual getPriority() (default 100), shouldHandle(...), shouldHandleExclusively(...) and handle(...), plus two combat-specific methods:
double getFadeFor(String playerDisplayName, double originalTransparency);
boolean isPlayerConsideredInCombat(String playerDisplayName);On the client there's a static convenience facade, AhCombatApi, which the mod's own damage mixins use. AhCombatApi.handleCombat(damageSource, victim) registers combat locally for both the victim and the attacker (when either is a player who opted into detection) and broadcasts a CombatLogEventPacket to the server. See Packets for how that propagates.
Since 0.13.0 there's a shortcut for the most common integration by far: hiding, fading or de-glinting equipment on your own condition. ArmorHiderRenderApi takes a predicate and does the rest, with no mixin and no AhRenderer involved.
AhRenderRule rule = ArmorHiderRenderApi.hideArmorWhen(EquipmentSlot.HEAD, Player::isSleeping);
// ...
rule.unregister();It covers armor slots, elytra wings and the off-hand, with priority and owner-tagged bulk teardown available through a builder. The surface is marked @ApiStatus.Experimental for now. See Using The Render Api for the methods, the precedence rules and the limits.
Reach for the render interception below instead when Armor Hider can't get at the thing you want to modify at all - a custom armor renderer, a modded cosmetic layer, your own render types.
This is the client-side extension point that the mod's own compat renderers are built on. If you want Armor Hider to hide or fade equipment that it doesn't know how to reach on its own (a custom armor renderer, a modded cosmetic layer), this is where you hook in.
Renderers are registered against a RenderScope, which is one of NONE, ARMOR_PIECE, ELYTRA, CAPE, OFFHAND, HEAD or ALL. Registration goes through AhRenderInterceptionRegistryApi:
AhRenderInterceptionRegistryApi.register(renderer); // uses defaultPriority(), 1000
AhRenderInterceptionRegistryApi.register(renderer, priority); // lower value winsThe interface you implement is AhRenderer, but you almost always want to extend AbstractArmorHiderRenderer, which handles the boilerplate (per-thread modification state, render-type-factory plumbing, and a standardIntercept(...) helper that does the empty/hide/fade decision for you):
public final class MyCapeRenderer extends AbstractArmorHiderRenderer {
@Override
public RenderScope getTargetScope() {
return RenderScope.CAPE;
}
@Override
public RenderInterceptionResult intercept(Object carrier, EquipmentSlot slot, ItemStack stack, CallbackInfo ci) {
if (!(carrier instanceof IdentityCarrier ic)) {
return RenderInterceptionResult.ignore();
}
return standardIntercept(ic, slot, stack, ci);
}
}
// then, from your ArmorHiderInitializer or client init:
AhRenderInterceptionRegistryApi.register(
new MyCapeRenderer(),
AhRenderInterceptionRegistryApi.defaultPriority() - 1); // beat the built-inintercept(...) returns a RenderInterceptionResult, which has three meaningful outcomes:
-
ignore(), vanilla proceeds untouched - intercept without cancel, the render still runs but inside Armor Hider's scope (so a fade can be applied)
- intercept and cancel, when the piece should be hidden outright
A renderer registered for RenderScope.ALL is a catch-all fallback, only consulted when nothing matches the specific scope. The built-in renderers (ArmorHiderItemRenderer, ArmorHiderCapeRenderer, ArmorHiderElytraRenderer, ArmorHiderOffhandRenderer, ArmorHiderHeadRenderer and AhGeckoLibRenderer) are all registered at defaultPriority(), so registering below that value overrides them. AhGeckoLibRenderer is the best reference for a real compat renderer.
You can also suppress interception conditionally. That's how the invisibility handling works: an InvisibilitySuppressor is installed on RenderScope.ALL so nothing gets intercepted while a player is invisible and the config says to respect it.
AhRenderManagementApi is the read side of the render state. It's @ApiStatus.NonExtendable and its mutators are internal, but the read methods are fair game: isInLevelRender(), isInEntityRender(), currentlyHandledPlayerName(), getActiveScope(...), shouldEnforceVanillaRendering(), shouldHideAccessory(typeKey, carrier) and so on. For the concepts behind level/entity render phases and scopes, see Scopes and Render Pipeline.
If you want to change the ARGB math used when armor fades (for gamma-correct or shader-friendly blending, for example), implement AhColorTransformer:
int applyTransparency(int color, float transparency); // replace alpha with transparency
int scaleAlpha(int color, float transparency); // multiply existing alpha
int whiteWithTransparency(float transparency); // white with the given alphaColors are packed ARGB (0xAARRGGBB) and transparency is in the range 0 to 1. Register it with AhRenderModificationApi.registerColorTransformer(transformer, priority), lower priority wins. The built-in is DefaultColorTransformer.
Armor Hider resolves its own translucent render types through AhRenderTypeFactory:
RenderType getTranslucentArmorRenderType(Identifier texture);
RenderType getTranslucentEntityRenderType(Identifier texture);
RenderType getTranslucentArmorTrimRenderType(boolean decal);
RenderType getTranslucentItemSheetRenderType();You can register a factory in two ways: per renderer via AhRenderer.registerRenderTypeFactory(factory), which only affects that one renderer, or globally via AhRenderModificationApi.registerRenderTypeFactory(factory, priority). The mod registers its own default at priority 1000, so a lower value wins. See Render Types for what the built-in types actually do and how they differ per game version.
AhRenderModificationApi is the version-independent facade that applies the modifications. Its render-type methods deliberately take and return Object rather than RenderType, so the API surface stays stable across the render-pipeline changes between Minecraft versions. It's pass-through safe: when no modification is active, it returns the originals.
The client-side config API is ArmorHiderPlayerConfigApi, reached through the public static field ArmorHiderClient.CLIENT_CONFIG_MANAGER. There's no static getter. This is a broad interface, mostly default methods over a small amount of state: your local PlayerConfig, the optional ServerConfiguration you got from an Armor-Hider-aware server, and a set of change listeners.
The single most useful method for a render integration is:
PlayerConfig resolveConfig(@Nullable String playerName);This is what the render pipeline calls per frame per player to decide how a given player should be drawn. It walks a precedence chain: your own local config for yourself, then any per-player override you set, then a global override if you've opted to apply it to everyone, then the server-broadcast config for that player if they're a modded player, and finally a fallback for unknown players. See Social Configuration and Multiplayer Sync for what those layers mean.
Other things you can do through this API:
- subscribe to config changes with
addConfigChangeListener(consumer)/removeConfigChangeListener(id) - read and write the local config (
getLocalPlayerConfig,setLocalPlayerConfig,saveCurrent, ...) - read the server config and, if you have permission level 3 or higher, push server-wide settings (
setAndSendServerWideSettings,setAndSendServerConfig) - manage per-player and global overrides
- query policy (
areIndividualConfigsAllowedByServer,areOtherPlayerConfigsAllowed,isArmorHiderGloballyDisabled, ...)
The compat framework in de.zannagh.armorhider.api.compat is how the mod detects other mods and defers their setup safely. It's technically public, but it's keyed to Armor Hider's own closed CompatFlags enum, so it isn't an open registration surface for arbitrary third-party mods, it's the internal mechanism the built-in compats use.
The important design point, if you're reading the compat code, is that CompatManager imports no Minecraft classes and detects mods by probing for the .class resource rather than loading it. That keeps it safe to run during mixin plugin load, and it's why some compats (Iris on NeoForge, for example) are deliberately never class-loaded early. Detection happens in two passes: a class-load-free resource probe at mixin time, then a later gap-fill. Compats that need real initialization (Entity Model Features and Iris) register a CompatInitializer that returns a CompatInitializationResult instead of throwing.
For the mixin-gating side of the same system, see Technical Details.
| Type | Side | How to reach it | For third parties |
|---|---|---|---|
ArmorHiderApi |
common | ArmorHiderApi.getInstance() |
call |
ArmorHiderInitializer |
common |
ServiceLoader (META-INF/services) |
implement |
ArmorHiderCombatManagementApi |
common | getInstance().getCombatManagement() |
call |
ArmorHiderCombatEventConsumer |
common | implement + register | implement |
AhCombatApi |
client | static methods | call |
ArmorHiderRenderApi |
client | static hide…When(...) / rule(...)
|
call (register render rules) |
AhRenderRule / AhHideContext
|
client | returned by / passed to rule registrations | call |
AhRenderInterceptionRegistryApi |
client | static register(...)
|
call (register renderers) |
AhRenderer |
client | extend AbstractArmorHiderRenderer
|
implement |
AhRenderManagementApi |
client | static read methods | call (reads only) |
AhRenderModificationApi |
client | static registries / scope context | call |
AhColorTransformer |
client | implement + register | implement |
AhRenderTypeFactory |
client | implement + register | implement |
ArmorHiderPlayerConfigApi |
client | ArmorHiderClient.CLIENT_CONFIG_MANAGER |
call |
CompatManager / CompatFlags
|
common | static (closed enum) | internal |