* 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`.
* refactor: parse placed and configured features at compile time
* fix: remove serde_json from pumpkin-world
* fix: lib.rs imports
* 🥀
* refactor: stripped serde from features and moved generated code to pumpkin-data
* Remove: include_json_static
---------
Co-authored-by: Alexander Medvedev <lilalexmed@proton.me>
* fix: preserve protocol ordering for sound event IDs in codegen
The Sound enum variants were sorted alphabetically, causing every
sound ID sent via the protocol to mismatch the client's expected
registration order. Only sort the LOOKUP table for binary search;
keep enum variants in the original sounds.json order.
Closes#1532
* chore: regenerate sound.rs with protocol-correct ordering