* feat: Add Mineshaft Generation
* fix(structures): remove redundant mineshaft probability gate
The MINESHAFTS structure_set already gates generation via its
frequency reduction (0.004, LegacyType3) in placement, so the extra
0.01 roll in the generator was double-gating and made mineshafts far
too rare. No other Pumpkin structure generator rolls its own
probability, so the gate has been removed to match vanilla frequency.
* feat(structures): faithful port of vanilla mineshaft generation
Rewrites all mineshaft pieces to match vanilla MineshaftPieces
(decompiled from server.jar):
- Corridor: 3-wide tunnel with fence posts + plank beams every 5
blocks, floor planks below the box at y=-1, cobwebs, rails (with
correct NORTH_SOUTH / EAST_WEST shape per corridor axis), cave-spider
spawner (spider corridors), chest (1%), wood support pillars
- Room: carved dome chamber at Y=50, vanilla dims (8-13 wide)
- Crossing: 5x5 with corner pillars, optional two-floored
- Stairs: vanilla descending staircase (air-only, no planks)
- Assembly: vanilla piece weights (70% corridor / 10% stairs / 20%
crossing), depth <= 8, bounds = 80 blocks from start
- Liquid check (isInInvalidLocation): aborts pieces in water/lava
- Mesa variant: dark oak wood throughout
* feat(world): support generated loot minecarts
* Minecart Vanila Parity
* Rebased And Added Furnace, Hopper, TNT Minecarts
* Refactor minecarts and fix off-rail gravity
Split minecart variants into focused modules, correct airborne minecart physics, and remove the test-only entity chunk-loading rework.
* Fix mineshaft vanilla parity
---------
Co-authored-by: Zoltán Virágh <zoltan.viragh@cloudtalk.io>
* 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 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.
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
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.
* 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>
* fix(lighting): stop sky light updates from looping on unloaded chunks
The sky light propagation could spin forever when a light update happened
next to a chunk that wasn't loaded. Writes to an unloaded chunk are dropped
silently and reads come back as 0, so the "this neighbor is darker than us"
check stayed true on every pass and the same position kept getting queued
again. In practice this hangs a tick thread when a block is broken near the
edge of the loaded area.
Skip neighbors whose chunk isn't loaded in both the increase and decrease
passes, matching how the border of loaded chunks already behaves: light
doesn't bleed into chunks that aren't there yet.
* Use Level::is_chunk_loaded for the unloaded-chunk guard
* fix: prevent capacity overflow crash when flying with elytra or in creative mode
The NoiseBasedCountPlacementModifier::get_count() can return negative i32
values when foliage noise sampling produces negative results at certain
world coordinates. The previous code cast this negative i32 directly to
usize, causing an integer wrap to ~18 quintillion, which triggered a
capacity overflow panic in Vec allocation during chunk feature generation.
This aligns with vanilla Minecraft behavior which also clamps the count
to a minimum of 0 before using it.
Closes#2345
* Hi
* sin/cos util
* 4 nieghbor check
* formating
* air helper
* carver wrapper
* fmt
* clippy
* clipp2
* remove local y
* out of bound guard
* fluid tick
* surface rule
* wire up
* math correct
* start fix
* bring back xoroshiro oops
* scheduler guard
* simplify test
* clean test
* comment
* crash fix
* clippy
* clippy
* i hate you codex
* sin cos
* im stupid f32
* refactor: less manual BlockMetadata impls
* refactor: added BlockId type
BlockId is a wrapper for u16: it is valid for any u16 that is the id of a Block.
- Changed the Block.id field type to BlockId
- added named BlockId constants
- BlockMetadata::ids() now returns Box<[BlockId]>
- adjusted pumpkin-macros to use BlockId constants
- fixed some methods that were comparing blockstate ids or item ids (u16) against block ids (previously u16)
TODO: check if unsafe blocks can be removed; The compiler might understand that BlockId is always a valid index into mappings::TYPE_FROM_RAW_ID
* refactor: added BlockStateId type
A BlockStateId is a safe wrapper around the numerical index of a BlockState in pumpkin-data. They help avoiding validity checks (outside of IO and, currently, plugins), and make it easier for other contributors to reason about what they're comparing; BlockStateIds, BlockIds or Item ids (still u16).
pumpkin-data::BlockStateId replaces RawBlockState and BlockStateId from pumpkin-world.
- added the BlockStateId wrapper type
- made (almost; plugins) every function interacting with block states or block state ids use the wrapper type
- changed codegen logic to create/work with BlockStateIds
- made ChunkPalette parsing check BlockStateId validity (pumpkin-world::chunk::format::ChunkSectionBlockStates)
TODO: check if unsafe blocks can be removed; The compiler might understand that BlockStateId is always a valid index into mappings::BLOCK_ID_FROM_STATE_ID and mappings::STATE_FROM_STATE_ID
* refactor: imports
changed every use pumpkin_world::BlockStateId to pumpkin_data::BlockStateId
* refactor: Block- & BlockStateId
finishing touches;
- rebased on latest mater
- ensure there are no bound checks on Block(State)Id conversions, making them extremely cheap
- this required unsafe std::hint::assert_unchecked annotations because the compiler is (occasionally) stupid
- on debug builds the bound checks still exist because of ub_checks (see rust unstable book for the feature of the same name)
- added Safety notes to hopefully prevent anyone from enabling the creation of invalid Block(State)Ids in the future
- fixed a benchmark
BlockPalette::liquid_block_count() returned the inverse of what it
should: the Homogeneous arm returned 0 when the section was liquid and
the full VOLUME (4096) when it was not, and the Heterogeneous arm summed
the counts of non-liquid blocks (filtering on !is_liquid).
Flip both arms to actually count liquid blocks, mirroring the correct
sibling non_air_block_count(): Homogeneous yields VOLUME when the single
block is liquid (0 otherwise) and Heterogeneous filters on is_liquid.
This value is sent to clients as the fluid count in chunk data
(MC 26.1+), so the inversion reported wrong fluid counts to players.