This guide describes the public API in the current source version 0.8.1. Use the dependency guidance below and check that the mod is loaded when integrating conditionally.

FFItemWeight — Modding API

Guide for other mods (and AI agents) integrating with FFItemWeight (ffitemweight).

Minecraft1.20.1, 1.20.6, 1.21.1, 1.21.11, 26.1, 26.1.1, 26.1.2, 26.2, 26.3
LoaderFabric (all versions), Forge 47.1 (1.20.1), NeoForge (1.20.6, 1.21.1, 1.21.11, 26.1.2, 26.2)
Mod IDffitemweight
API packagecom.fyxe.ffitemweight.api
Client helperscom.fyxe.ffitemweight.api.client

Optional dependency:

[[dependencies.yourmod]]
    modId="ffitemweight"
    type="optional"
    versionRange="[0,)"
    ordering="NONE"
    side="BOTH"

On Fabric, list "ffitemweight": "*" under suggests (or depends when required) in fabric.mod.json. The first Fabric build is 0.7.0, so use a version range starting there when you need it. Forge 1.20.1 (mods.toml) uses mandatory=false in place of type="optional"; builds for Forge, 1.20.x and 1.21.11 start at 0.8.0, and Minecraft 26.x builds (Java 25) start at 0.9.0.

Use ModList.get().isLoaded("ffitemweight") on NeoForge, FabricLoader.getInstance().isModLoaded("ffitemweight") on Fabric, or FFUtilities' Mods.isLoaded, together with an optional compile-only dependency, before calling API classes. Each target is published as com.fyxe.ffitemweight:ffitemweight-<minecraft>-<loader>:<version> (for example ffitemweight-1.21.1-fabric, ffitemweight-1.21.11-neoforge or ffitemweight-1.20.1-forge).


Core concepts


Dependency check

import net.neoforged.fml.ModList;

if (!ModList.get().isLoaded("ffitemweight")) {
    return;
}

Reading item weights

import com.fyxe.ffitemweight.api.ItemWeightAPI;
import com.fyxe.ffitemweight.api.WeightCategory;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;

double unit = ItemWeightAPI.getUnitWeight(item);
double stack = ItemWeightAPI.getWeight(stack); // includes nested if enabled

WeightCategory cat = ItemWeightAPI.getItemWeightCategory(stack);
String name = cat.name();
int rgb = cat.rgb();
double bandMax = cat.maxWeight();

List<WeightCategory> allItemBands = ItemWeightAPI.getItemWeightCategories();
MethodNotes
getUnitWeight(Item)One item; default weight if unknown
getWeight(ItemStack)Count + nested contents
getItemWeightCategory(double/stack)Band from client weightCategories
getItemWeightCategories()Ordered immutable list

Empty stacks return 0.0. Null item uses the configured default unit weight.


Player totals and encumbrance

import net.minecraft.world.entity.player.Player;
import com.fyxe.ffitemweight.api.EncumbranceLevel;

boolean tracking = ItemWeightAPI.isPlayerWeightTrackingEnabled();

double total = ItemWeightAPI.getPlayerTotalWeight(player);
EncumbranceLevel enc = ItemWeightAPI.getPlayerEncumbrance(player);
int level = ItemWeightAPI.getPlayerEncumbranceLevel(player);

boolean heavyOrWorse = ItemWeightAPI.isPlayerEncumbranceAtLeast(player, 3);
boolean pastMedium = ItemWeightAPI.isPlayerOverLevel(player, 2);

EncumbranceLevel band = ItemWeightAPI.getEncumbranceCategory(total, player);
List<EncumbranceLevel> loads = ItemWeightAPI.getEncumbranceLevels();
MethodNotes
getPlayerTotalWeight(Player)Cached ~5 ticks; respects creative / slot toggles
getPlayerEncumbrance(Player)Current EncumbranceLevel after capacity scaling
getPlayerEncumbranceLevel(Player)Integer level from the datapack band
isPlayerEncumbranceAtLeast(p, level)Current level >= given level
isPlayerOverLevel(p, level)Total weight above that band's scaled capacity
getEncumbranceLevels()Base bands (capacities before scaling)

Creative players return 0 total (and no HUD) unless applyInventoryWeightsInCreative is true.


Capacity

boolean capOn = ItemWeightAPI.isCapacityScalingEnabled();
double mult = ItemWeightAPI.getPlayerCapacityMultiplier(player); // level formula
double flat = ItemWeightAPI.getPlayerCapacityFlatBonus(player);  // armour / accessories / enchants

double effectiveMax = ItemWeightAPI.scaleThreshold(2000.0, player); // Heavy Load example

double pieceBonus = ItemWeightAPI.getEquipmentCapacityBonus(chestStack);
boolean isGear = ItemWeightAPI.isCapacityEquipment(chestStack);

Formula used internally: threshold' = base * levelMultiplier + flatBonus.

Enchantment keys (datapack):


Effects toggles (read-only)

boolean potions = ItemWeightAPI.areEncumbranceEffectsEnabled();
boolean attrs = ItemWeightAPI.areEncumbranceAttributesEnabled();

Actual potion / attribute application is internal (EncumbranceEffects). Query toggles only; do not depend on internal applicator classes.


Cache and readiness

// After you mutate inventory in a way this mod does not already listen for
ItemWeightAPI.invalidatePlayerCache(player);
ItemWeightAPI.invalidatePlayerCache(null); // all players

boolean ready = ItemWeightAPI.isWeightTableReady(); // server computed / client synced
Set<Item> stillDefault = ItemWeightAPI.getFallbackItems(); // server audit helper

Built-in invalidation already covers: equipment change, item pickup, container close, item destroy, logout.


Client-only helpers

import com.fyxe.ffitemweight.api.client.ClientItemWeightAPI;

double localTotal = ClientItemWeightAPI.getLocalPlayerTotalWeight();
EncumbranceLevel localEnc = ClientItemWeightAPI.getLocalPlayerEncumbrance();
boolean overloaded = ClientItemWeightAPI.isLocalPlayerEncumbranceAtLeast(4);

Use for HUD / UI only. Gameplay rules belong on the server with ItemWeightAPI and a real Player / ServerPlayer.


Datapack weights (for content packs)

Path: data/<namespace>/item_weights/<name>.json

{
  "weights": [
    { "id": "mymod:widget", "weight": 2.5 },
    { "id": "mymod:.*_ore", "weight": 6.0, "regex": true },
    { "id": "mymod:boss_core", "weight": 50.0, "force": true }
  ]
}
FieldMeaning
idItem id, or regex when regex is true
weightUnit weight
regexTreat id as a full-id Java regex
forceLock base; recipes cannot overwrite

Prefer exact ids for anchors; leave craftable tools out of JSON so recipe derivation can fill them.

Datapack encumbrance

Path: data/<namespace>/ffitemweight/encumbrance/<id>.json

Override a built-in by shipping the same path (for example ffitemweight/encumbrance/heavy_load.json).

{
  "level": 3,
  "display_name": "Heavy Load",
  "color": "#FFAA00",
  "capacity": 2000,
  "icon": "ffitemweight:textures/gui/encumbrance/heavy_load.png",
  "shake": 1.5,
  "effects": [{ "id": "minecraft:slowness", "amplifier": 0 }],
  "attributes": [{ "id": "minecraft:generic.movement_speed", "operation": "multiply_total", "amount": -0.20 }]
}

Compare level in code. Treat display_name as a label only.


Events

On every loader listen with EncumbranceApiEvents.register(listener); listeners run on the server thread, in registration order. On NeoForge the same event is also posted on NeoForge.EVENT_BUS (it extends NeoForge's Event), so @SubscribeEvent listeners keep working. Fabric and Forge have no separate bus for it, so use EncumbranceApiEvents there.

EncumbranceChangedEvent (after level change, not cancellable)

Fired when the player's integer encumbrance level changes (for example 2 to 3). Checked about every 10 ticks with potion/attribute application. First observation after join does not fire (avoids login spam).

import com.fyxe.ffitemweight.api.EncumbranceApiEvents;
import com.fyxe.ffitemweight.api.EncumbranceChangedEvent;

// Any loader, for example in your mod initializer:
EncumbranceApiEvents.register(YourHooks::onEncumbranceChanged);

public static void onEncumbranceChanged(EncumbranceChangedEvent event) {
    if (event.isIncrease()) {
        // moved to a heavier band
    }
    int from = event.getOldLevel();
    int to = event.getNewLevel();
    double total = event.getTotalWeight();
    EncumbranceLevel band = event.getNewBand();
}
GetterMeaning
getPlayer()ServerPlayer
getOldLevel() / getNewLevel()Previous / new integer bands (Integer.MIN_VALUE when tracking is off)
getTotalWeight()Carried weight at transition
getOldBand() / getNewBand()EncumbranceLevel snapshots (old may be null)
isIncrease() / isDecrease()Direction along integer levels

Note: There is no pre-change cancellable event — weight is inventory-derived. Cancel gameplay side-effects in your own handlers instead.

Soft compatibility (do not hard-depend)

ModRole
EMITooltip weights via vanilla tooltip path
Accessories / CuriosSlot weight + capacity (Accessories preferred if both present)
CreateConfig recipe modifiers / filters / nested handlers

Integrate through ItemWeightAPI, not compat.* packages.


Stability

SurfaceStatus
com.fyxe.ffitemweight.api.*Public API
EncumbranceChangedEvent, EncumbranceApiEventsPublic API
com.fyxe.ffitemweight.api.client.*Public API (client)
WeightManager / CapacityHelper public methodsStable but prefer ItemWeightAPI
Config fields / TOML keysUser-facing; do not hard-code as API
EncumbranceEffects, compat.*, network internalsInternal

Minimal integration example

@EventBusSubscriber(modid = "yourmod")
public class YourWeightHooks {
    @SubscribeEvent
    public static void onSpellCast(YourSpellEvent event) {
        if (!(event.getEntity() instanceof ServerPlayer player)) return;
        if (!ModList.get().isLoaded("ffitemweight")) return;

        if (ItemWeightAPI.isPlayerEncumbranceAtLeast(player, 4)) {
            event.setCanceled(true);
            player.displayClientMessage(
                    Component.literal("Too encumbered to cast."), true);
            return;
        }

        // Optional: react to carried mass
        double mass = ItemWeightAPI.getPlayerTotalWeight(player);
        if (mass > 3000.0) {
            // apply your own mild penalty...
        }
    }
}

Datapack item weight references (0.6.4)

In data/<namespace>/item_weights/<name>.json, use an exact weight_from reference instead of a numeric weight:

{
  "weights": [
    { "id": "minecraft:oxidized_copper", "weight_from": "minecraft:copper_block" }
  ]
}

The item always uses the source item's final unit weight, including recipe calculation, clamps, and fallback. References are locked regardless of force and take precedence over numeric regex bases. Do not combine weight_from with weight or regex: true. Chains are supported. Missing items and cycles, including self-references and dependent chains, log warnings and retain normal regex/recipe/fallback calculation. Recipe ingredients follow the source's current weight. Final weights sync to clients; nested contents are still counted separately.

Default data maps all 63 oxidation and wax variants of the nine copper shapes to their unwaxed, unoxidized equivalents. Replace the same resource path to override these defaults. Later loaded exact numeric/reference entries replace earlier ones. Reload with /reload; Java API signatures are unchanged.