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.
* feat: persist runtime-set item components
They serialized to nothing before; give the remaining ones a read/write so
their data survives a save/load. Predicate-based ones keep raw NBT for now.
* refactor: remove Cow where unnecessary
Copied it over from ItemModelImpl, but it makes no sense for base_color,
note_block_sound and tooltip_style: they're only ever built at runtime, so a
plain String is enough.
Third-party auth servers (drasl, Blessing Skin, littleskin.cn) don't send the
signatureRequired field in the hasJoined texture property, so parsing the
profile failed and the player was kicked. The field is unused, so default it
when missing.
fixes#301
Replace full-block entity queries with vanilla's centered 14x4x14 pressure plate detection box. Share the bounds between normal and weighted pressure plates and cover the geometry with unit tests.
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