Skip to content

fix: respect Unbreaking (Прочность) when consuming shears/axe durability - #13

Merged
iSlavok merged 3 commits into
mainfrom
fix/unbreaking-durability
Jul 28, 2026
Merged

fix: respect Unbreaking (Прочность) when consuming shears/axe durability#13
iSlavok merged 3 commits into
mainfrom
fix/unbreaking-durability

Conversation

@iSlavok

@iSlavok iSlavok commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Problem

The Прочность enchantment (vanilla Unbreaking) had no effect when the mod/plugin
consumes tool durability: the shears (invisibility toggle) and the axe (wax removal) lost a
durability point on every use. Root cause: durability was decremented unconditionally,
bypassing vanilla Unbreaking.

Vanilla mechanic for a non-armor tool: a durability point is applied only with probability
1/(level+1) — i.e. skipped when random.nextInt(level+1) != 0. Level 0 → always applied,
so existing behaviour is preserved for un-enchanted tools.

This fix covers both platforms in the repo.

Plugin (Bukkit/Paper — plugin/)

  • Unified the byte-for-byte-identical damageShears/damageAxe into one damageTool.
  • Resolve the Unbreaking level version-stably via Enchantment.getByKey(minecraft("unbreaking"))
    • getEnchantmentLevel (0 if absent).
  • Apply a point only when the roll says so; RNG is an injectable constructor default param
    (random: java.util.Random = java.util.Random()) so existing FrameListener(plugin) call
    sites still compile.
  • Extracted the pure decision to internal fun consumesDurability(level, random) for unit tests.
  • New unit tests: level 0 always consumes (loop), a negative level always consumes, and
    Unbreaking III consumes ~1/4 of the time (tolerance band, seeded RNG).

Fabric mod (src/ — Stonecutter multi-version)

  • Gated both durability sites (shears in injectDamage, axe in injectWax) on the
    1/(level+1) roll using the world RNG — in both the yarn and mojmap (>=1.22) branches.
  • Added a cross-version unbreakingLevel helper, Stonecutter-branched because the enchantment
    API churns per version:
    • <1.21 (yarn): plain Enchantment object → EnchantmentHelper.getLevel(Enchantment, stack).
    • 1.21–1.21.1 (yarn): registry entry via manager.getOptionalWrapper(...).getOrThrow(...).
    • 1.21.2+ (yarn): registry entry via manager.getOrThrow(...).getOrThrow(...).
    • 1.22+ (mojmap): Holder via registryAccess().lookupOrThrow(...).getOrThrow(...) +
      getItemEnchantmentLevel.
  • The lookup is wrapped in the mixin's existing try/catch logging idiom; on any failure it
    returns level 0 (safe fallback = previous always-consume behaviour), so a bad lookup can
    never crash the interaction.
  • The gametest now asserts an Unbreaking-3 stack reads back level 3 through the same
    cross-version lookup (deterministic) — this catches a silent registry-resolution failure
    that compilation alone cannot.

Test evidence (run locally, offline)

  • Plugin: ./gradlew -p plugin testgreen, 27 tests (incl. the 3 new probability/level tests).
  • Fabric: ./gradlew buildBUILD SUCCESSFUL, all 9 anchors' :build tasks ran
    (yarn 1.18.2/1.19.4/1.20.4/1.20.6/1.21.1/1.21.8/1.21.10 and mojmap 26.1.2/26.2 on JDK 25).
  • Gametest: runGameTest on all 4 gametest-enabled nodes (1.20.6, 1.21.1, 1.21.8, 1.21.10) →
    "All required tests passed" on each, exercising the <1.21, 1.21–1.21.1, and 1.21.2+
    enchantment-lookup branches at runtime.

The mojmap >=1.22 branch was compiled locally (JDK 25 toolchain available in the sandbox);
its gametest is not enabled for 26.x (mod matrix), matching CI.

iSlavok and others added 3 commits July 28, 2026 12:37
Durability was decremented unconditionally, bypassing vanilla Unbreaking.
Unify damageShears/damageAxe into damageTool, resolve the Unbreaking level via
the registry key, and apply a point only with probability 1/(level+1) (level 0
keeps the old always-consume behaviour). RNG is an injectable ctor param and the
decision is extracted to consumesDurability for deterministic unit tests.
The mixin decremented tool durability unconditionally, ignoring vanilla
Unbreaking. Gate both durability sites (shears in injectDamage, axe in injectWax)
on a 1/(level+1) roll using the world RNG, in both the yarn and mojmap branches.
Add a cross-version unbreakingLevel helper (Stonecutter-branched: plain Enchantment
<1.21, RegistryEntry via getOptionalWrapper 1.21-1.21.1, via getOrThrow 1.21.2+,
Holder on mojmap 1.22+) wrapped in the existing try/catch (level 0 fallback). The
gametest now asserts an Unbreaking-3 stack reads back level 3.
@iSlavok
iSlavok merged commit 6d9526b into main Jul 28, 2026
2 checks passed
@iSlavok
iSlavok deleted the fix/unbreaking-durability branch July 28, 2026 08:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant