This guide describes the public API in the current source version 0.7.5. Use the dependency guidance below and check that the mod is loaded when integrating conditionally.
FFExhaustion — Modding API
Guide for other mods (and AI agents) integrating with FFExhaustion (ffexhaustion).
| Minecraft | 1.21.1 |
| Loader | NeoForge 21.1.x |
| Mod ID | ffexhaustion |
| API package | com.fyxe.ffexhaustion.api |
| Exhaustion range | 0.0 – 1.0 (higher = more fatigued) |
Dependency (optional):
[[dependencies.yourmod]]
modId="ffexhaustion"
type="optional"
versionRange="[0,)"
ordering="NONE"
side="BOTH"
Use ModList.get().isLoaded("ffexhaustion") or optional compile-only dependency before calling API classes.
Core concepts
- Server-authoritative. Mutations only apply on the logical server (
ServerPlayer). Client calls are no-ops. - Persisted on the player via NeoForge attachments (
ModAttachments.EXHAUSTION). - Death: controlled by server config
copyOnDeath(defaultfalse→ resets on death). Non-death clones (e.g. leaving the End) always keep the value. - Recovery delay: after many actions, recovery waits N ticks before draining toward 0.
- Level modifiers (optional): vanilla
Player.experienceLevelcan reduce gain and recovery delay when enabled in config.
Reading exhaustion
These player-based queries read the server-owned attachment. Use them on the logical server only. Client player attachments are not updated by HUD packets and can report zero or stale values. Recovery delay, recovery state, and status queries are also server-only.
import com.fyxe.ffexhaustion.api.ExhaustionAPI;
import net.minecraft.world.entity.player.Player;
float x = ExhaustionAPI.getExhaustion(player); // 0.0 – 1.0
int delay = ExhaustionAPI.getRecoveryDelayTicks(player);
boolean recovering = ExhaustionAPI.isRecovering(player);
boolean full = ExhaustionAPI.isFull(player);
boolean empty = ExhaustionAPI.isEmpty(player);
boolean tired = ExhaustionAPI.isAtLeast(player, 0.75f);
Client HUD / client-only code
import com.fyxe.ffexhaustion.api.client.ClientExhaustionAPI;
float local = ClientExhaustionAPI.getExhaustion();
boolean tired = ClientExhaustionAPI.isAtLeast(0.9f);
This is a synced cache, not authoritative. It may lag continuous server changes by up to five ticks. Do not use it for gameplay rules.
Changing exhaustion (server)
All of these fire the cancellable pre-change event. When the value actually changes, they also fire the changed event and sync the client HUD immediately. Non-finite inputs are ignored; finite values outside 0 to 1 are clamped.
import com.fyxe.ffexhaustion.api.ExhaustionAPI;
// Absolute set
ExhaustionAPI.setExhaustion(player, 0.5f, true); // value, triggerRecoveryDelay
// Relative change (level gain multiplier applied on increase)
ExhaustionAPI.increaseExhaustion(player, 0.05f, true);
ExhaustionAPI.decreaseExhaustion(player, 0.05f, false);
// No recovery delay
ExhaustionAPI.increaseExhaustionNoDelay(player, 0.02f);
ExhaustionAPI.decreaseExhaustionNoDelay(player, 0.02f);
// Exact amount, ignore level multiplier, no delay
ExhaustionAPI.increaseExhaustionRaw(player, 0.1f);
// Full reset
ExhaustionAPI.resetExhaustion(player);
// Delay timer only
ExhaustionAPI.setRecoveryDelayTicks(player, 40);
| Method | Level multiplier | Typical use |
|---|---|---|
increaseExhaustion(p, amt, delay) | Yes | Player actions, combat costs |
increaseExhaustion(p, amt, delay, raw) | If raw == false | Same, optional raw |
increaseExhaustionRaw(p, amt) | No | Scripted / exact values |
decreaseExhaustion(...) | N/A | Items, rest abilities |
setExhaustion(...) | N/A | Absolute control |
resetExhaustion(p) | N/A | Death effects, commands |
Events (NeoForge event bus)
Register on NeoForge.EVENT_BUS (common / server).
ExhaustionChangeEvent (cancellable, before apply)
import com.fyxe.ffexhaustion.api.ExhaustionChangeEvent;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.neoforge.common.NeoForge;
@SubscribeEvent
public void onExhaustionChange(ExhaustionChangeEvent event) {
// Cancel entirely
if (event.getPlayer().isCreative()) {
event.setCanceled(true);
return;
}
// Soften increases
if (event.getCause() == ExhaustionChangeEvent.Cause.INCREASE) {
float mid = (event.getOldValue() + event.getNewValue()) * 0.5f;
event.setNewValue(mid);
}
event.setTriggerDelay(false);
}
| Getter / setter | Meaning |
|---|---|
getPlayer() | ServerPlayer |
getOldValue() / getNewValue() | Before / proposed after |
setNewValue(float) | Clamps finite overrides; ignores NaN and infinities |
isTriggerDelay() / setTriggerDelay(boolean) | Recovery delay after apply |
getCause() | SET, INCREASE, DECREASE, RECOVERY, SYSTEM, OTHER |
getDelta() | new - old |
ExhaustionChangedEvent (after apply, not cancellable)
@SubscribeEvent
public void onExhaustionChanged(ExhaustionChangedEvent event) {
float x = event.getNewValue();
// e.g. grant advancement, play sound, update capability
}
Note: Continuous built-in drains (sprint per tick, shield hold, glider) may update the attachment without posting these events every tick (performance). Discrete API calls and most one-shot actions (jump, attack, Better Combat swing) do post them. On the server, re-read ExhaustionAPI.getExhaustion(player) if you need the live value each tick.
Level helpers
int level = ExhaustionAPI.getEffectiveLevel(player); // capped by config
float gainMult = ExhaustionAPI.getGainMultiplier(player); // 1.0 if disabled
int delayTicks = ExhaustionAPI.getEffectiveDelayTicks(player);
These respect ServerConfig level-modifier toggles. They use vanilla experienceLevel only (no custom level store).
Attachment (advanced)
import com.fyxe.ffexhaustion.data.ModAttachments;
import com.fyxe.ffexhaustion.data.ExhaustionData;
ExhaustionData data = player.getData(ModAttachments.EXHAUSTION);
float v = data.getValue();
Prefer ExhaustionAPI for writes so events and client sync stay consistent.
Configurable status systems (potions + attributes)
Optional server features (all disabled by default). Other mods can query state without reading config classes.
boolean potionsOn = ExhaustionAPI.arePotionEffectsEnabled();
boolean thresholdAttrs = ExhaustionAPI.areThresholdAttributesEnabled();
boolean scalingAttrs = ExhaustionAPI.areScalingAttributesEnabled();
List<ExhaustionStatusApplier.PotionEntry> potions = ExhaustionAPI.getPotionEffectEntries();
List<ExhaustionStatusApplier.ThresholdAttrEntry> steps = ExhaustionAPI.getThresholdAttributeEntries();
List<ExhaustionStatusApplier.ScalingAttrEntry> scales = ExhaustionAPI.getScalingAttributeEntries();
boolean anyPotion = ExhaustionAPI.hasActivePotionThreshold(player);
boolean anyStep = ExhaustionAPI.hasActiveThresholdAttribute(player);
double amount = ExhaustionAPI.getScalingAmount(player, scales.get(0));
double atHalf = ExhaustionAPI.getScalingAmountAt(scales.get(0), 0.5f);
Parsed entry records live in com.fyxe.ffexhaustion.effect.ExhaustionStatusApplier and are part of the supported surface for read access.
Config entry formats (server TOML)
potion_effects
enabled(bool, defaultfalse)entries— list of"minExhaustion;effect_id;amplifier;duration_ticks"- Example:
"0.5;minecraft:slowness;0;40"
attributes.threshold
enabled(bool, defaultfalse)entries— list of"minExhaustion;attribute_id;amount;operation"- Example:
"0.7;minecraft:generic.movement_speed;-0.15;add_multiplied_total" - Operations:
add_value|add_multiplied_base|add_multiplied_total
attributes.scaling
enabled(bool, defaultfalse)entries— list of"attribute_id;amountAtFull;operation"- Applied amount =
amountAtFull * exhaustion(exhaustion in[0, 1]) - Example:
"minecraft:generic.attack_damage;-0.25;add_multiplied_total"
These are independent of built-in sprint / attack-speed decay modifiers.
Soft compatibility patterns
FFExhaustion uses optional soft compat (reflection / soft mixins) for:
- Better Combat (
bettercombat) — multi-hit + miss via combo hooks - VC Gliders (
vc_gliders) — drain while gliding, force drop at threshold
Other mods should not depend on those packages. Integrate through ExhaustionAPI instead.
Config surface (for documentation only)
Server configs live under ffexhaustion-server.toml (names illustrative):
copyOnDeath— keep exhaustion after death (defaultfalse)- Recovery,
gain(sprinting, swim sprinting, floating, jumping, falling, attacking, shield), player level - Optional:
potion_effects.,attributes.threshold.,attributes.scaling.* - Optional compat:
compat.better_combat.,compat.gliders.
Do not hard-code config class field names in foreign mods; treat them as user-facing, not API.
Minimal integration example
@EventBusSubscriber(modid = "yourmod")
public class YourExhaustionHooks {
@SubscribeEvent
public static void onSpellCast(YourSpellEvent event) {
if (!(event.getEntity() instanceof ServerPlayer player)) return;
if (!ModList.get().isLoaded("ffexhaustion")) return;
if (ExhaustionAPI.isAtLeast(player, 0.9f)) {
event.setCanceled(true);
return;
}
ExhaustionAPI.increaseExhaustion(player, 0.08f, true);
}
}
Stability
- Package
com.fyxe.ffexhaustion.apiandcom.fyxe.ffexhaustion.api.clientare public API. ExhaustionStatusApplierentry records andExhaustionAPIstatus query helpers are public for read/integration.ExhaustionData/ModAttachmentsare stable enough for read access; prefer API for mutations.ExhaustionHandler,compat., andmixin.are internal — do not call them from other mods.