Files
Pumpkin/pumpkin-data
Andrei Șugubete a0ff7f46dc feat: Durability Overhaul (#1848)
* feat: broadcast EntityStatus when equipment breaks

Pumpkin did not play any sound or show break particles when a tool or
piece of armour reached zero durability. Vanilla sends a per-slot
EntityStatus packet (47-52, 65, 68) that every nearby client uses to
play the break sound and spawn break particles textured with the item
that just broke.

Changes
-------

player.rs – new damage_item_in_slot(slot, amount) method:
- Generalises the old damage_held_item to cover all EquipmentSlots:
  MainHand, OffHand, Head, Chest, Legs, Feet.
- Resolves the correct PlayerInventory slot index per slot (36-39 for
  armour, 40 for off-hand) consistent with build_equipment_slots().
- Sends the EntityStatus break packet BEFORE clearing the slot so
  clients can use the item texture for break particles.
- Bypasses sync_hand_slot (which only broadcast for hand slots) and
  instead calls enqueue_slot_set_packet + send_equipment_changes
  directly, ensuring armour equipment changes also propagate.
- damage_held_item is kept as a thin convenience wrapper.

apply_mending_from_xp – DRY refactor:
- Replaces three separate candidate-gathering blocks (main hand, off
  hand, armour loop) with a single unified slot_pairs list iterated
  once. Behaviour is identical.

living.rs – damage_armor_items:
- When a player takes hit damage, armour durability is reduced through
  LivingEntity::damage_armor_items, a separate code path from the
  player item-damage methods. This path now also broadcasts the
  EntityStatus break packet when an armour piece's durability hits zero.

entity/mod.rs – shared helper:
- Add equipment_break_status(slot) -> EntityStatus as a #[must_use]
  pub const fn. Eliminates the duplicated slot→status match that would
  otherwise exist in both call sites (player.rs and living.rs).

Tests (16 new)
--------------
- entity::tests::equipment_break_status_maps_all_slots – asserts every
  EquipmentSlot variant (including Body and Saddle) maps to its
  expected EntityStatus break variant.
- item::tests (15 tests) – first unit tests for ItemStack durability
  logic, covering damage_item_with_context (zero/negative amount,
  non-damageable item, unbreakable item, accumulation, stack break at
  max damage, over-damage) and repair_item (zero/negative amount,
  undamaged item, partial repair, cap at current damage, full repair
  clears the Damage component patch entry) and set_damage (negative
  value clamps to zero).

* refactor: use table-driven cases in damage_increases_damage_value test

* chore: make clippy happy

* refactor: the rest of damage and repair tests to use table-driven cases

* feat: broadcast EntityStatus when equipment breaks

- Implement strict packet ordering (EntityStatus before slot sync)
- Add break broadcast in play.rs use_on_block for all tools
- Generalize damage_item_in_slot to support all equipped slots
- Trim PR comments to 1-2 professional lines
- Convert equipment_break_status test to table-driven format
- Ensure vanilla parity per decompiled 1.21.11 EntityEvent mappings

* feat(item): durability overhaul — DamageResult, armor/weapon parity, elytra fix

pumpkin-world
- Add DamageResult enum (Untouched/Damaged/Broken) replacing bool return on
  damage_item_with_context and damage_item
- Elytra clamped at max_damage-1; never deleted from inventory
- Stacked item break correctly returns Broken regardless of remaining count
- Add is_axe/is_pickaxe/is_shovel/is_hoe tag-based predicates to ItemStack
- 21 new unit tests covering all cases (elytra, stacks, Unbreaking, categories)

pumpkin
- Armor damage type filter: bypasses_armor_durability() driven by the
  vanilla minecraft:bypasses_armor data tag (1.21.11); id-based u8 lookup
- Elytra worn in armor slot skipped from hit damage
- Thorns: extra +2 durability cost + 1-4 magic retaliation damage
- Weapon 2-durability cost: axes/pickaxes/shovels/hoes in combat
- Fix armor slot Unbreaking formula (is_armor_slot() gate)
- Fix stacked-item break broadcast (Broken not is_empty())
- Extract combat_weapon_durability_cost as sync fn; reuse existing lock
- 5 forward-compatible TODO one-liners for shield/trident/crossbow/fishing rod/brush

* fix(item): resolve vanilla parity issues in durability and combat tracking

- Move armor durability calculation to pre-absorption damage
- Fix Thorns damage range to uniform float [1.0, 5.0) and correct execution order
- Attribute Thorns damage to victim for correct combat tracking
- Refactor `damage_item` to auto-infer armor via `#minecraft:enchantable/armor`
- Decouple Elytra clamping from generic item damage loop into dedicated methods
- Add TODOs for DAMAGE_RESISTANT, post-attack effects, and data-driven bypasses

Also reformatted long comments to be concise and techincal

* chore: trigger CI rerun

* fix: shears no longer take durability damage when attacking mobs (refactor + codegen to standardize)

Replace hardcoded item-category checks in combat_weapon_durability_cost
with a data-driven lookup of the minecraft:weapon component.

- Add item_damage_per_attack field to WeaponImpl with DataComponentImpl
- Wire WeaponComponent deserialization into pumpkin-codegen item builder
- Regenerate item data so weapon durability values come from items.json
- Items without the Weapon component (e.g. shears) return 0 cost

* chore: retrigger CI

* refactor: Remove Thorns retaliation and trigger mechanics

- Remove Thorns enchantment trigger check and probability
- Remove Thorns extra +2 durability cost on armor
- Remove Thorns retaliation damage to attacker (1-5 magic damage)
- Remove Thorns combat attribution via damage_with_context
- Remove related imports and TODO comments
- Keep source parameter for future compatibility

* fix: glaring issues

- Fix WeaponImpl::read_data to actually read item_damage_per_attack from NBT
  instead of hardcoding 1; custom weapons now load correctly
- Remove unused _source parameter from damage_armor_items signature
- Move is_axe/is_pickaxe/is_shovel/is_hoe behind #[cfg(test)] since they're
  test-only helpers, keeping public API clean

* fix: elytra unbreaking and Pre-absorption Armor Test
Also a comment for clarity

* refactor: remove elytra scope creep: damage handling and related tests

* adjustment: tighten unbreaking test, update comments to be correct

* fix: clamp negative NBT, widen Unbreaking test, document elytra and shield-disable TODOs

* refactor: armor bypass logic: use bitmask, hoist lookups, optimize durability loop

* feat: Remove hardcoded Elytra armor check; use EquippableImpl::damage_on_hurt for data-driven durability logic.

* fix: add bitmask compile-time assert, clarify NBT clamping, document damage_on_hurt logic, rename slot_result, add #[must_use] to damage_item, and fix all unused result warnings.

* fix: clarify stacked item break edge, strengthen bitmask asserts, document NBT error handling, add TODOs for tool break handling and loop short-circuit, remove dead code and trident stub, note TOCTOU, and document test determinism. All tests passing.

* fix: Various

CRITICAL ITEMS:
  * bypasses_armor_durability correctness: Verified by existing test suite
    (validates all 19 bypass types return true, 12 physical types return false).
  * TOCTOU race in attack(): Enhanced documentation with 3 specific mitigation
    options and justification for single-player safety.
  * must_use suppressions: Removed #[must_use] from damage_item; added TODO to
    restore once all DamageResult::Broken handlers implement proper semantics.

SIGNIFICANT ITEMS:
  * DamageResult::Broken semantics: Updated doc comment to clarify that breaking
    always broadcasts correctly—whether consuming a single item or decrementing
    a stack. Callers need not split the enum.
  * Stacked item multi-break behavior: Verified matches vanilla (one break per
    call, never chains). Updated comment to document this as correct behavior,
    not a regression.
  * should_apply_durability_damage_with visibility: Made private to prevent
    incorrect unbreaking formula usage outside durability context.
  * Elytra exemption caching optimization: Added PERF comment noting O(1)
    component lookup is already efficient (max 4/hit). Deferred to dedicated
    caching architecture pass.

MINOR ITEMS:
  * Unbreaking test windows: Tightened from (500–1500) to (865–1135) for ±5σ
    confidence—catches actual formula regressions while remaining statistically
    sound. Updated both tool and armor tests.
  * WeaponImpl NBT error handling: Documented conservative fallback behavior
    (clamp negatives, default missing keys). Added TODO for tracing::warn! at
    higher-level call site.
  * default_item_damage naming: Renamed return_1u32 for consistency and clarity
    in weapon damage context.
  * WeaponImpl Hash collision prevention: Added struct-level NOTE reminding
    developers to update get_hash() if fields are added. Deferred until
    multi-field scenario (currently impossible with one field).

* fix: Resolve duplicate `return_1f32` and restore `#[must_use]` on `damage_item`

- Removed duplicate `return_1f32` function in `pumpkin-codegen/src/item.rs` to fix a compile blocker.
- Restored `#[must_use]` attribute on `damage_item` in `pumpkin-world/src/item/mod.rs` to enforce proper handling of `DamageResult`.
2026-03-15 14:17:18 +01:00
..
2026-03-15 14:17:18 +01:00