- Parse the `model` field for skin textures in `player.rs` to support the hat skin model
- Update `player_info_update.rs` to mask unsupported action flags based on the Minecraft version (e.g., ignore `UPDATE_LIST_PRIORITY` for versions prior to 1.21.2)
- Add the `UPDATE_HAT` flag in `world/mod.rs` and send it alongside `UPDATE_LIST_PRIORITY`
- Sync multi-version `tracked_data` JSON files to reflect the protocol changes
Signed-off-by: Missing_Love <42416195+Q2297045667@users.noreply.github.com>
`ParrotEntity` had no interact handler, so a cookie used on a parrot did
nothing at all. Vanilla `Parrot.mobInteract` consumes the cookie, applies
poison for 900 ticks and then kills the parrot.
The branch is gated on the `parrot_poisonous_food` item tag rather than on
the cookie id, matching vanilla. Anything else falls through to the default
mob interaction, so leashing and name tags keep working.
* fix(entity): reset fall distance on Spectator/Creative gamemode switch
Prevents fall damage when a player switches to Spectator mid-fall and
then back to Survival before landing.
This is a pragmatic workaround — vanilla achieves this through multiple
independent defense layers (noPhysics, per-tick reset, on_ground force,
mayfly check) rather than an explicit reset. The TODOs in the code
track the remaining gaps for a full vanilla-faithful implementation.
Fixes#2372
* docs(entity): document Creative fall-distance over-forgiveness edge case
Add TODO clarifying that once vanilla fall-damage layers 1-4 are
implemented, the reset should be restricted to Spectator only. In
vanilla a non-flying Creative player keeps fallDistance across a
Survival switch (guarded by abilities.mayfly in causeFallDamage);
resetting here over-forgives that edge case.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: condense fall damage comment to reference vanilla methods
Reduced comment from 23 lines to 6 by:
- Removing detailed explanations of vanilla defense layers
- Directly referencing Player.aiStep() and Player.causeFallDamage()
- Keeping only actionable TODOs
- Switching to /* */ style
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Air was not saved to NBT, causing it to reset to full on every
login. This allowed players to exploit drowning by relogging to
restore lost air bubbles.
The player attack function used base_damage (2.0) + add_damage (0.0) = 2.0
when attacking with an empty hand, but vanilla applies -1.0 base_attack_damage
and -2.4 base_attack_speed for the fist, resulting in 1.0 damage and 1.6
attack speed. This caused entities like cows (10 HP) to die in 5 hits
instead of 10.
Also fix the cooldown scaling formula to match vanilla: the entire
(base + modifier) should be scaled by cooldown, not just the modifier.
* feat: add item frame entity and real comparator analog output
Item frames had no entity implementation, so frames loaded from
vanilla worlds fell into the generic entity fallback and lost their
Item, ItemRotation and Facing NBT on the next save. The comparator
also returned a hardcoded 1 for any attached frame.
Adds ItemFrameEntity (item frame + glow item frame) with vanilla NBT
round-trip, and makes the comparator return the vanilla signal:
0 when the frame is empty, otherwise rotation % 8 + 1. The frame
lookup now also includes glow item frames and matches on the frame's
stored facing instead of the yaw-derived horizontal facing.
* fix: mirror the default item frame facing into the entity data field
A frame created without NBT set facing to south but left the entity
data field at 0, which the spawn packet reads as down, so the frame
spawned facing the wrong way. Store the default in both places.
* fix(data): map missing main arm and skin tracked IDs for 1.21-1.21.6
* fix(player): send main hand and skin customisation metadata across versions
* fix(data): map missing main arm and skin tracked IDs in source JSON
* Add /place template command with BlockPlacer trait and tab-completion support
- Add BlockPlacer trait to abstract block placement over ProtoChunk (worldgen)
and WorldBlockPlacer (live command), used by place_template()
- Implement BlockPlacer for ProtoChunk (pumpkin-world) and WorldBlockPlacer
(pumpkin crate)
- Add World::queue_block_updates() to insert into unsent_block_changes
without triggering full set_block_state callbacks
- Generate _generated_all_template_names() at build time from structure
assets for use in tab-completion suggestions
- Add TemplateNameArgumentType for the new command system with
list_suggestions() using all_template_names()
- Implement /place template <template> [pos] in the new command system
(CommandDispatcher, CommandExecutor, ArgumentBuilder)
- Fix borrow-across-await: clone template name to owned String early
* feat: add /place structure and /place jigsaw commands
Adds /place structure <id> [pos] for all structure types with two placement paths:
- Jigsaw structures (ancient_city, bastion_remnant, etc.): fast path using
JigsawPlacement::add_pieces directly at the target position
- Non-jigsaw structures (desert_pyramid, end_city, etc.): generate pieces
via dispatch, place into synthetic ProtoChunks with pre-seeded heightmaps
and a stone floor so pieces can detect terrain, then delta-apply only
changed blocks to the world via WorldBlockPlacer
Also adds /place jigsaw <pool> <target> <depth> [pos] for manual jigsaw
template placement.
Architecture:
- generate_structure_position() extracts the shared generator dispatch
from try_generate_structure / lazily_generate_structure (both now
delegate to it, eliminating ~150 lines of duplicated match arms)
- place_pool_element_templates() extracted from PoolElementStructurePiece
for reuse by the command with WorldBlockPlacer
- StructureKeys gains from_name()/to_name()/all_names() via codegen
- StructureNameArgumentType and PoolNameArgumentType for tab-completion
- flat_ocean_floor_height_map made pub in ProtoChunk for heightmap seeding
* feat: add /place feature command
Adds /place feature <feature> [pos] for placing configured features
at a specific position. Resolves the PlacedFeature name via from_name(),
resolves the inner ConfiguredFeature from PLACED_FEATURES /
CONFIGURED_FEATURES, then calls ConfiguredFeature::generate() directly
at the target position -- skipping placement modifiers so the feature
appears exactly where specified.
Also adds /place structure improvements:
- Synthetic chunk terrain fill: stone below surface + grass on top, so
pieces that carve through solid terrain (stronghold corridors, etc.)
find material to work with. Snapshot/delta ensures only structure
blocks reach the world.
- Shared snapshot_blocks() and apply_delta() helpers eliminate ~30 lines
of duplicated diff logic between structure and feature paths.
- ground_y() and chunk_population_seed() helpers replace magic numbers.
- Structure success message now reports the structure name instead of
the piece count.
Infrastructure:
- PlacedFeature::all_names() generated via pumpkin-codegen for
tab-completion suggestions in PlacedFeatureNameArgumentType.
- GenerationCache trait implemented for ProtoChunk (single-chunk
delegation) so ConfiguredFeature::generate() works without the full
chunk-generation cache system.
- flat_ocean_floor_height_map made pub in ProtoChunk for heightmap
seeding.
- configured_features and feature modules made pub for access from
the command crate.
* fix(command): use world settings for place
* docs(place): clarify synthetic terrain delta behavior
* feat(command): add /reload command
* feat: campfire cooking + block entity persistence for all entity types
- Implement full campfire cooking: 4-slot cooking, recipe lookup,
auto-pop-off as ItemEntity or hopper insertion, config-gated manual
pickup, Inventory/Clearable traits, tick with particle effects
- Add sync_write_items_to_nbt() utility for sync-safe inventory NBT
serialization via try_lock()
- Extend add_block_entity() and update_block_entity() to persist NBT
with id/x/y/z fields to pending_block_entities
- Fix migrate_pending_block_entities(): run once per chunk activation
in update_active_chunks(), add already_loaded guard
- Add chunk_data_nbt() overrides to 35 block entity types — every
entity with persistent state now correctly sends data to clients
and survives chunk unload/reload
* style: cargo fmt
* rename: auto_pop_off -> campfire_auto_pop_off for clarity
* fix(persistence): report contended inventory slots
* refactor: split campfire cooking from persistence
* fix(command): remove stale reload registration
The cancelled handler in handle_use_item_on sent block.id (BlockId)
instead of the actual BlockStateId, causing clients to see wrong
block states (e.g. stone appearing as snow) when plugins cancel
PlayerInteractEvent on right-click.
* fix(entity): apply knockback resistance to living entities
Vanilla LivingEntity.knockback scales strength by 1.0 - KNOCKBACK_RESISTANCE,
but Pumpkin never applied it, so mobs with high resistance (iron golem,
warden, ravager) were knocked back like any other mob.
Scale both knockback paths by the victim's resistance: the base hit knockback
in LivingEntity, and the sprint/enchant bonus in handle_knockback, which now
takes the victim as &dyn EntityBase so it can read the attribute.
Fixes#1761
* docs(entity): clarify knockback resistance is not applied in apply_knockback
The previous note read as if every caller had to pre-scale strength. Ender
dragon knockback mirrors vanilla Entity.push, which ignores resistance, so
spell out which callers scale and which don't.
get_dead_coral_block_type mapped FIRE_CORAL_BLOCK to itself rather than
to DEAD_FIRE_CORAL_BLOCK, so a fire coral block left out of water stayed
alive forever while the other four colours bleached normally. The two
sibling tables for fans and plants both map it correctly.
The same table feeds BlockMetadata::ids, so this also stops the block
being registered twice and registers DEAD_FIRE_CORAL_BLOCK, which was
missing from the list.
Vanilla Player#attack ends its successful-hit branch with
causeFoodExhaustion(0.1F), and Block#playerDestroy calls
player.causeFoodExhaustion(0.005F) for every block destroyed.
Pumpkin never called add_exhaustion from either path: the only callers
were the Hunger status effect in entity/living.rs and jump/sprint
movement in entity/player.rs. Combat and mining were therefore free, so
the hunger bar was effectively cosmetic for a player who was not
sprinting.
Player::attack now adds 0.1 exhaustion at the end of the hit path,
after the damage_with_context early-return, so only hits that actually
deal damage cost hunger. The Java and Bedrock block-break handlers now
add MINE_BLOCK_EXHAUSTION (0.005) per block broken.
Mining exhaustion is gated exactly where vanilla gates
Block#playerDestroy: ServerPlayerGameMode#destroyBlock skips it in
creative and when the block was broken without a tool that can harvest
it, so the call sites reuse the existing can_harvest / block_drop
checks rather than adding new conditions. Creative and spectator
players are additionally covered by the existing abilities.invulnerable
guard in Player::add_exhaustion, which mirrors vanilla
Player#causeFoodExhaustion.
Limitation: swimming exhaustion (0.01 per metre) is still missing.
Player::progress_motion only implements the onGround branch of vanilla
ServerPlayer#checkMovementStatistics; adding the swimming and in-water
branches requires the whole else-if chain plus movement-state plumbing
(Player::is_swimming is itself still an inferred approximation carrying
a TODO), so it is deliberately left out of this change.
CommandSender::world() returns None for console and RCON, so the two
commands that call it via ok_or(InvalidRequirement) fail there while
/setblock at the same coordinates succeeds. Five other call sites had
each grown their own inline fallback to the first world, which is also
what vanilla does for server-console commands.
Add world_or_first(server) with that fallback and use it, and fold the
duplicated logic in setblock and teleport into it. world() is left
unchanged because it is part of the versioned WASM plugin ABI.
Flint and steel and fire charge lit any block carrying a lit property,
because can_be_lit searched the property list by name rather than checking
the block. That covers furnaces, smokers, blast furnaces, redstone lamps,
redstone torches, redstone ore and every copper bulb. Redstone lamps have
no use handler so the click fell straight through, and furnaces were
reachable by sneaking, which skips the block use handler. Gate on the
campfire, candle and candle cake tags, matching the three canLight
predicates vanilla checks. The extinguished branch above it was dead, as
no block in the generated data has that property, so it is removed.
The hoe called set_block_state outside its changed guard, so a click that
tilled nothing still rewrote the block to its default state. On a grass
block under a snow layer that cleared snowy and turned the block green.
Vanilla returns PASS and touches nothing.
Bone meal on bamboo could never grow it: the air check read the topmost
bamboo block rather than the space above it, so it always returned on the
first iteration. It also rolled 1 to 3 where vanilla rolls 1 to 2, and
grew from the original position rather than the top stalk.
The axe could not strip crimson and warped stems and hyphae, or bamboo
blocks. Bamboo is not in the logs tag, so the axis copy is extended to
cover it or a sideways bamboo block would snap upright when stripped.
The native loader reads METADATA as a `*const PluginMetadata`, but since
#1675 gave PluginMetadata owned String and Vec fields, `#[plugin_impl]`
has to export it as a `LazyLock` — a const can't allocate. The loader was
therefore reinterpreting the LazyLock's internals as a PluginMetadata.
Every native plugin ends up with an empty name and version. That alone
puts the data folder at plugins/ instead of plugins/<name>/ and makes
Context::register_permission reject every node, since it validates against
the plugin's namespace. And when the garbage string length happens not to
be zero, cloning the metadata takes the server down with a multi-terabyte
allocation request, which is what #2434 reports.
Go through a `*const LazyLock<PluginMetadata>` and deref it, which forces
the lock before the metadata is read.
CSoundEffect::new() scales the position by * 8 to convert from
fixed-point. write_packet_data was applying * 8 a second time,
resulting in positions being multiplied by 64.
Add regression test asserting the encoded X/Y/Z are exactly
floor(input_pos * 8), preventing this bug from reappearing.
The item-stack, uuid, and event WIT modules are generated but were never
re-exported from the plugin API, so plugin authors could not name ItemStack
(needed to build GUI icons, /give, skulls) or construct a uuid. This adds them
to the public re-exports, plus EventHandler/FromIntoEvent and the command
Arg/ArgumentType/StringType helpers.
Revives the change from #2325 (closed unmerged).
The chest in the stronghold square room was placed as a bare block state,
so no chest block entity was ever created for it. Opening a chest and
resolving a double chest both go through `get_block_entity`, so the chest
could neither be opened nor paired with a chest placed next to it.
Use the existing `add_chest` helper, which writes the block state and the
matching block entity with a loot table, as the nether fortress corridors
already do. Vanilla `StrongholdPieces$RoomCrossing` uses
`chests/stronghold_crossing` at the same position.