Skip to content

Adding a Skill

Inside your plugin's registerExperienceType, call one of the registry's register overloads. Each skill needs a unique name, a SkillsConfig (its default settings) and the attribute it buffs.

The register Overloads

java
// Attribute bonus only
void register(String name, SkillsConfig config, Holder<Attribute> attributeHolder);

// Attribute bonus + custom per-refresh logic
void register(String name, SkillsConfig config, Holder<Attribute> attributeHolder, Handler handler);
  • name — the skill id. Every derived id is built from it (for example name + "_experience" for the XP attachment), so it must be unique.
  • config — the default SkillsConfig. It's written to the config file on first run and players can override every field afterward.
  • attributeHolder — the attribute the level bonus is applied to, e.g. Attributes.MOVEMENT_SPEED.
  • handler — optional; extra work run each time the skill is refreshed (see Handlers).

The SkillsConfig Record

java
new SkillsConfig(
    boolean enabled,
    float   incrementPerLevel,
    int     xpAwardActionCount,
    int     xpPointToAward,
    int     rgbColor,
    AttributeModifier.Operation operation,
    int     maxLevel,
    Identifier stat
)

Every field is documented on the Skill Config page. Note that stat must be a custom statistic — a built-in Experience Skills stat, a stat you registered yourself, or a minecraft:custom stat. Per-block / per-item statistics can't be used directly; pool them into a custom stat first (see Adding a Stat).

Example: A Simple Skill

A "Mining Fortune" style skill that raises Attack Damage as you break blocks:

java
@Override
public void registerExperienceType(IExperienceTypeRegistry registry) {
    registry.register(
        "prospecting",
        new SkillsConfig(
            true,                                        // enabled
            0.02f,                                       // +2% per level
            50,                                          // every 50 blocks broken
            5,                                           // award 5 points
            0x33CC66,                                    // orb/bar colour
            AttributeModifier.Operation.ADD_MULTIPLIED_BASE,
            100,                                         // level cap
            ModStats.BLOCK_BROKEN.id()                   // driving stat
        ),
        Attributes.LUCK                                  // attribute buffed
    );
}

Referencing stats

Built-in stat ids are exposed through ModStats (e.g. ModStats.BLOCK_BROKEN.id()). Vanilla custom stats come from net.minecraft.stats.Stats (e.g. Stats.JUMP, Stats.DAMAGE_DEALT).

Handlers

A Handler runs in addition to the automatic attribute bonus — use it for effects a single modifier can't express, like adjusting a second attribute or running custom logic.

java
@FunctionalInterface
interface Handler {
    void onUpdateSkill(ServerPlayer player, int level, ExperienceType experienceType);
}

It fires whenever the skill's status is refreshed — on login, on /experienceskills command changes, and each time the player levels the skill up — and only when the skill's attribute is present on the player.

Recompute from level, don't assume a delta

A handler can fire at any level, not just on a change. If its output depends on the level, recompute it from the supplied level each call rather than assuming it changed by one.

Example: The Built-in Jump Handler

The built-in jump skill uses a handler to keep SAFE_FALL_DISTANCE in step with the jump-height bonus, so higher jumps never turn into fall damage:

java
registry.register(
    "jump",
    new SkillsConfig(true, 0.02f, 100, 5, 14392458,
        AttributeModifier.Operation.ADD_MULTIPLIED_BASE, 100, Stats.JUMP),
    Attributes.JUMP_STRENGTH,
    (player, level, type) -> {
        AttributeInstance attr = player.getAttribute(Attributes.SAFE_FALL_DISTANCE);
        if (attr == null) return;

        float perLevel = type.getConfig().incrementPerLevel();
        float jumpStrengthAtLevel = 0.42f + (0.42f * perLevel * level);
        float safeFallBonus = safeFallDistanceForLevel(jumpStrengthAtLevel) - 3.0f;

        Identifier id = Constants.id("update_for_jump");
        attr.removeModifier(id);
        attr.addPermanentModifier(
            new AttributeModifier(id, safeFallBonus, AttributeModifier.Operation.ADD_VALUE));
    }
);

Note how it removes its old modifier before re-adding, and computes the bonus purely from level — both essential because the handler re-runs on every refresh.

Enabling / Disabling at Runtime

If a skill is disabled in config, its attribute bonus is removed and it awards no experience. A handler should mirror this — if it manages an extra modifier, remove that modifier when type.getConfig().enabled() is false.

Released under the All Rights Reserved License.