Implements the full vanilla Minecraft 26.2 mob equipment system, giving
mobs weapons, armor, enchantments, and loot-pickup flags at spawn.
Added:
- Data-driven EQUIPMENT_REGISTRY mapping 13 mob types (zombie, husk,
zombie_villager, drowned, zombified_piglin, skeleton, stray, bogged,
wither_skeleton, piglin, pillager, vindicator) to their weapon and
armor configurations, all verified against decompiled vanilla sources.
- Exact vanilla RegionalDifficulty formula from DifficultyInstance.java:
computes effective_difficulty and special_multiplier from base
difficulty, game time, chunk inhabited time, and moon phase. When
special_multiplier is 0.0 (fresh Normal/Easy worlds), equipment,
enchantments, and loot-pickup flags are suppressed, matching vanilla.
- Vanilla-accurate armor selection: random base tier (0-2) with up to 3
upgrade attempts at 10.87% each, partial armor break chance (10% on
Hard, 25% otherwise). Higher difficulty produces fewer pieces.
- Weighted enchantment selection with exclusive-set conflict resolution
and cost-based level determination. Curated flat pools per equipment
category (melee/trident/bow/crossbow/fishing_rod/head/chest/legs/feet).
- Per-slot equipment drop chances (0.085 default) with looting bonus
(+lootingLevel * 0.01 per vanilla EnchantmentHelper).
- Exact vanilla drop durability formula from Mob.dropCustomDeathLoot:
damage = maxDamage - random(1 + random(max(maxDamage-3, 1))).
- CAN_PICK_UP_LOOT mob flag (bit 3, value 8) set probabilistically at
55% * special_multiplier.
- Guard against empty equipment packets in send_equipment_changes to
prevent client decode crashes.
- Chunk inhabited_time tracking in world tick for difficulty scaling.
Integration: equip_mob_on_spawn is called from the blanket
EntityBase::init_data_tracker impl, so every mob type automatically
participates. New mobs are added by inserting a MobEquipmentDef into
the EQUIPMENT_REGISTRY with their weapon/armor config.
cargo check: passes / cargo clippy (pedantic): 0 warnings / cargo fmt: clean
* Fix egg: initialize git submodules during install
The Pumpkin repo uses pumpkin-plugin-wit as a git submodule, but the
install script only does 'git clone --depth 1' without --recurse-submodules.
Add 'git submodule update --init --recursive --depth 1' after checkout
so the WIT files required by bindgen!/wit_bindgen::generate are available.
* Potential fix for pull request finding
* feat(protocol): generate particle ID remap tables
Generate version-specific particle registry mappings from the existing ViaBackwards data for every supported Java protocol version.\n\nExpose the generated remapper through pumpkin-data and enable it for pumpkin-protocol so packet serializers can translate current registry IDs for older clients.
* fix(protocol): remap explosion particles for older clients
Translate the explosion particle registry ID for each client's negotiated Java version before serializing the explode packet.\n\nWithout this remap, 1.21.11 interpreted the 26.2 explosion-emitter ID as falling dust, consumed the following bytes as particle data, and disconnected with a decoder exception. Add packet-level coverage for both 1.21.11 and 26.2 encodings.
* fix(protocol): remap particle IDs for older clients
Level-particle packets and particle entity metadata still encoded raw 26.2 registry IDs, causing older clients to decode the wrong particle schema and disconnect. Route every active outbound particle path through the version remapper, preserve metadata payloads, and cover 1.21.11 and 26.2 encodings with regression tests.
`CommandDispatcher::find_suggestions` resolved the command tree and walked
its paths to produce server-side tab-complete suggestions without ever
checking the command's permission, unlike `dispatch`, which gates on
`self.permissions.get(key)` followed by `src.has_permission`.
Many legacy commands (e.g. pardon, deop, whitelist) register their
permission only in `self.permissions` and have no in-tree `Require` node.
Their argument consumers therefore leaked privileged data -- banned, op,
and whitelisted player names -- to unprivileged players through
tab-complete, even though those players could not execute the command.
Add the same permission gate used by `dispatch` after the command key is
resolved and before the tree is traversed: look up `self.permissions` for
the key and return an empty suggestion list if the permission is absent or
if the sender lacks it. This keeps suggestion visibility consistent with
execution authorization.
* fix(block): melt snow layers under high block light
Snow layers never melted because the block had no random_tick handler.
Mirror vanilla SnowLayerBlock.randomTick: on a random tick, remove the
layer when the block light level at its position exceeds 11 (e.g. from
a nearby torch).
Fixes#2179
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* style: apply rustfmt
Implements the vanilla /tag command (tracked in #15) along with the
entity-side storage it needs:
- Adds a scoreboard_tags: Mutex<HashSet<String>> field to Entity, with
add_scoreboard_tag / remove_scoreboard_tag helpers that enforce the
vanilla 1024-tag cap and report whether they changed anything.
- Serializes tags to/from the entity's "Tags" NBT list, matching the
vanilla format so tags round-trip through world saves.
- /tag <targets> add|remove <name> and /tag <targets> list, with the
existing commands.tag.* translation keys and single/multiple wording.
Implements the three vanilla save-control commands (tracked in #15):
- /save-all saves all online players' data and advancements, then
requests a chunk save from every world's chunk scheduler. It works
even while autosaving is disabled, matching Vanilla.
- /save-off disables periodic autosaving via a new save_enabled flag
on Level; running it again fails with commands.save.alreadyOff.
- /save-on re-enables autosaving; running it again fails with
commands.save.alreadyOn.
Implements the vanilla /random command (tracked in #15):
- /random value <range> draws a value and shows it to the source only.
- /random roll <range> draws a value and announces it to every player.
- Range validation matches Vanilla: spans of 0 fail with
commands.random.error.range_too_small, spans of i32::MAX - 1 or wider
(including open-ended ranges) fail with range_too_large.
Random sequences (/random reset and the [sequence] argument) are not
part of this change since named sequence storage does not exist yet.
Implements the vanilla /spreadplayers command (tracked in #15):
- Places targets at random surface locations within maxRange of a
center column, spread at least spreadDistance apart, using Vanilla's
iterative force-based relaxation followed by a surface-grounding pass.
- Rejects unsafe locations (liquid surfaces) and retries, matching
Vanilla's refusal to place entities on water.
- On failure reports commands.spreadplayers.failed.{entities,teams} with
the Vanilla suggested maximum spread; on success reports
commands.spreadplayers.success.* with the average pairwise distance.
respectTeams currently collapses all (teamless) targets onto a single
pile, since teams are not implemented yet; this matches Vanilla's
handling of teamless entities under respectTeams=true.
* fix(pumpkin): stop entities from duplicating on chunk reload
Entities lived in two places at once: the live World::entities list and the
serialized NBT in the entity chunk's data. On load the saved NBT was turned
into live entities but never cleared, and on unload each live entity was
appended back onto that still-populated list - so the persisted entity count
doubled every load/unload (reconnect) cycle. Freshly spawned entities hit the
same trap: add_entity_silent pushed their NBT into the chunk immediately, so
they were both live and serialized, doubling on the first unload too.
Make the live entity the single source of truth, matching vanilla:
- on load, take (clear) the chunk's serialized entities as they become live,
and restore their persisted UUID so they keep their identity;
- a second watcher of an already-loaded chunk is sent spawn packets built from
the live entities, not the stale NBT;
- entities are serialized fresh, from their current live state, only when their
chunk unloads (save_entity);
- add_entity_silent no longer serializes on spawn.
Because the live entity is serialized fresh on unload, any change made to it
while loaded (health, effects, ...) is persisted automatically, without having
to be written back to the chunk data by hand.
* Move UUID int-array NBT helpers into pumpkin-nbt
Review feedback: the UUID read helper doesn't belong in world/mod.rs.
NbtCompound now has put_uuid/get_uuid for the vanilla 4-int-array
layout, used by both Entity::write_nbt and the entity chunk loader.
Serialized bytes are unchanged.
* perf(generation): cache computed structure starts
set_structure_references runs for every chunk and, for each nearby structure
candidate, recomputed the structure's placement from scratch. For jigsaw
structures (villages, ancient cities, ...) that means re-running the full
jigsaw expansion for every chunk whose references overlap the structure -- the
same start recomputed many times over.
A structure's placement depends only on its start chunk and the world seed (the
surface-height estimate it uses is position-independent and min_y is constant
per dimension), so memoize it in GlobalStructureCache and reuse it. In the
bench, structure references drop from ~342us to ~105us.
* perf(lighting): use a fast hasher in the generation light engine
The BFS light propagator's visited/shadow_cache/pending_updates maps were
aliased to std HashSet/HashMap (SipHash) despite being named "Fast". They are
probed on every neighbour of every propagated block, so the hash function
dominates. Point the aliases at rustc-hash's FxHash (already a dependency).
Lighting generation drops from ~65ms to ~36ms and full chunk generation from
~103ms to ~68ms in the bench, with identical output.
* perf(lighting): propagate light through storage, not a shadow cache
The BFS light propagator kept a hashed shadow cache of in-flight light values
plus a per-chunk batched write buffer, layered on top of the light storage. The
storage is itself a fast array lookup, so the extra hashing and buffering cost
more than they saved. Read and write it directly and treat it as the single
source of truth.
Lighting generation drops from ~36ms to ~22ms and full chunk generation from
~68ms to ~52ms, output unchanged (all pumpkin-world tests, including the
fixed-seed ancient-city parity test, still pass).
---------
Co-authored-by: Alexander Medvedev <lilalexmed@proton.me>
`handle_encryption_response` unwrapped the RSA decrypt of the
client-supplied shared secret, panicking the connection task on a
malformed value; kick the client instead, mirroring the adjacent
`set_encryption` error handling.
`receive_velocity_plugin_response` called `data.split_at(32)` without a
length check, panicking on a velocity response shorter than 32 bytes;
guard the length and return `FailedVerifyIntegrity`.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(loot): animals drop cooked food without fire or Fire Aspect
Fix issue #2366 where farm animals dropped cooked meat instead of raw
when killed, regardless of fire state or weapon enchantments.
Root cause: LootCondition::EntityProperties lacked predicate fields for
is_on_fire and equipment enchantments, codegen discarded predicate data,
and the evaluator did not resolve 'direct_attacker'.
- Add is_on_fire and mainhand_enchantment_tag to EntityProperties
- Add predicate structs to codegen with correct serde renames
- Fix evaluator to resolve direct_attacker and check fire/enchantments
- Fix pre-existing v[0] panic on empty StringOrVec arrays
- Add 18 unit tests
* fix(loot): add backticks to doc comment for clippy
* fix(data): update block.rs generated loot tables with new EntityProperties fields
* fix(loot): merge match arms and collapse if for clippy
Add the `broadcast_console_to_ops` configuration option matching
vanilla's `broadcast-console-to-ops` server property. When set to
`false`, suppresses console and RCON command output from being
broadcast to online operators.
- Add `broadcast_console_to_ops` field to `CommandsConfig` (defaults
to `true` for vanilla compatibility)
- Track the setting via an `AtomicBool` in the command module
- Replace hardcoded `true` in `should_broadcast_console_to_ops` for
Console and RCON senders with the configurable value
- Initialize the setting during server startup from advanced config
Co-authored-by: Alexander Medvedev <lilalexmed@proton.me>