mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
feat: /place command (#2473)
* 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
This commit is contained in:
@@ -13,6 +13,7 @@ pub fn build_enum() -> TokenStream {
|
||||
|
||||
let mut from_name_arms = Vec::new();
|
||||
let mut to_name_arms = Vec::new();
|
||||
let mut all_names: Vec<String> = Vec::new();
|
||||
|
||||
let variants: Vec<TokenStream> = json
|
||||
.as_object()
|
||||
@@ -26,12 +27,18 @@ pub fn build_enum() -> TokenStream {
|
||||
to_name_arms.push(quote! {
|
||||
Self::#variant_name => #name,
|
||||
});
|
||||
all_names.push(format!("minecraft:{name}"));
|
||||
quote! {
|
||||
#variant_name,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let all_names_tokens: Vec<TokenStream> = all_names
|
||||
.iter()
|
||||
.map(|name| quote! { #name })
|
||||
.collect();
|
||||
|
||||
quote! {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub enum PlacedFeature {
|
||||
@@ -52,6 +59,11 @@ pub fn build_enum() -> TokenStream {
|
||||
#(#to_name_arms)*
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn all_names() -> &'static [&'static str] {
|
||||
&[#(#all_names_tokens),*]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use heck::ToPascalCase;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{ToTokens, format_ident, quote};
|
||||
use serde::Deserialize;
|
||||
@@ -429,11 +430,15 @@ pub fn build() -> TokenStream {
|
||||
|
||||
let mut structure_const_defs = TokenStream::new();
|
||||
let mut structure_lookup_arms = TokenStream::new();
|
||||
let mut structure_from_name_arms = Vec::new();
|
||||
let mut structure_to_name_arms = Vec::new();
|
||||
let mut structure_all_names: Vec<String> = Vec::new();
|
||||
|
||||
for (name, structure) in &structures_json {
|
||||
let stripped_name = name.strip_prefix("minecraft:").unwrap_or(name);
|
||||
let upper_name = stripped_name.to_uppercase();
|
||||
let const_name = format_ident!("{}", upper_name);
|
||||
let variant_ident = format_ident!("{}", stripped_name.to_pascal_case());
|
||||
let key_variant = structure_key_to_token(name);
|
||||
|
||||
structure_const_defs.extend(quote!(
|
||||
@@ -443,6 +448,14 @@ pub fn build() -> TokenStream {
|
||||
structure_lookup_arms.extend(quote!(
|
||||
#key_variant => &Self::#const_name,
|
||||
));
|
||||
|
||||
structure_from_name_arms.push(quote! {
|
||||
#stripped_name => Some(Self::#variant_ident),
|
||||
});
|
||||
structure_to_name_arms.push(quote! {
|
||||
Self::#variant_ident => #name,
|
||||
});
|
||||
structure_all_names.push(stripped_name.to_string());
|
||||
}
|
||||
|
||||
let mut structure_set_const_defs = TokenStream::new();
|
||||
@@ -465,6 +478,14 @@ pub fn build() -> TokenStream {
|
||||
all_structure_set_idents.push(const_name);
|
||||
}
|
||||
|
||||
let structure_all_names_tokens: Vec<TokenStream> = structure_all_names
|
||||
.iter()
|
||||
.map(|name| {
|
||||
let full = format!("minecraft:{name}");
|
||||
quote! { #full }
|
||||
})
|
||||
.collect();
|
||||
|
||||
quote!(
|
||||
use pumpkin_util::math::floor_div;
|
||||
use pumpkin_util::random::{
|
||||
@@ -510,6 +531,28 @@ pub fn build() -> TokenStream {
|
||||
TrialChambers,
|
||||
}
|
||||
|
||||
impl StructureKeys {
|
||||
pub fn from_name(name: &str) -> Option<Self> {
|
||||
let name = name.strip_prefix("minecraft:").unwrap_or(name);
|
||||
match name {
|
||||
#(#structure_from_name_arms)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn to_name(&self) -> &'static str {
|
||||
match self {
|
||||
#(#structure_to_name_arms)*
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn all_names() -> &'static [&'static str] {
|
||||
&[#(#structure_all_names_tokens),*]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StructureSet {
|
||||
pub placement: StructurePlacement,
|
||||
pub structures: &'static [WeightedEntry],
|
||||
|
||||
@@ -799,4 +799,271 @@ impl PlacedFeature {
|
||||
Self::WildflowersMeadow => "wildflowers_meadow",
|
||||
}
|
||||
}
|
||||
#[must_use]
|
||||
pub const fn all_names() -> &'static [&'static str] {
|
||||
&[
|
||||
"minecraft:acacia",
|
||||
"minecraft:acacia_checked",
|
||||
"minecraft:amethyst_geode",
|
||||
"minecraft:bamboo",
|
||||
"minecraft:bamboo_light",
|
||||
"minecraft:bamboo_vegetation",
|
||||
"minecraft:basalt_blobs",
|
||||
"minecraft:basalt_pillar",
|
||||
"minecraft:birch_bees_0002",
|
||||
"minecraft:birch_bees_0002_leaf_litter",
|
||||
"minecraft:birch_bees_002",
|
||||
"minecraft:birch_checked",
|
||||
"minecraft:birch_leaf_litter",
|
||||
"minecraft:birch_tall",
|
||||
"minecraft:blackstone_blobs",
|
||||
"minecraft:blue_ice",
|
||||
"minecraft:brown_mushroom_nether",
|
||||
"minecraft:brown_mushroom_normal",
|
||||
"minecraft:brown_mushroom_old_growth",
|
||||
"minecraft:brown_mushroom_swamp",
|
||||
"minecraft:brown_mushroom_taiga",
|
||||
"minecraft:cave_vines",
|
||||
"minecraft:cherry_bees_005",
|
||||
"minecraft:cherry_checked",
|
||||
"minecraft:chorus_plant",
|
||||
"minecraft:classic_vines_cave_feature",
|
||||
"minecraft:crimson_forest_vegetation",
|
||||
"minecraft:crimson_fungi",
|
||||
"minecraft:dark_forest_vegetation",
|
||||
"minecraft:dark_oak_checked",
|
||||
"minecraft:dark_oak_leaf_litter",
|
||||
"minecraft:delta",
|
||||
"minecraft:desert_well",
|
||||
"minecraft:disk_clay",
|
||||
"minecraft:disk_grass",
|
||||
"minecraft:disk_gravel",
|
||||
"minecraft:disk_sand",
|
||||
"minecraft:dripstone_cluster",
|
||||
"minecraft:end_gateway_return",
|
||||
"minecraft:end_island_decorated",
|
||||
"minecraft:end_platform",
|
||||
"minecraft:end_spike",
|
||||
"minecraft:fallen_birch_tree",
|
||||
"minecraft:fallen_jungle_tree",
|
||||
"minecraft:fallen_oak_tree",
|
||||
"minecraft:fallen_spruce_tree",
|
||||
"minecraft:fallen_super_birch_tree",
|
||||
"minecraft:fancy_oak_bees",
|
||||
"minecraft:fancy_oak_bees_0002_leaf_litter",
|
||||
"minecraft:fancy_oak_bees_002",
|
||||
"minecraft:fancy_oak_checked",
|
||||
"minecraft:fancy_oak_leaf_litter",
|
||||
"minecraft:flower_cherry",
|
||||
"minecraft:flower_default",
|
||||
"minecraft:flower_flower_forest",
|
||||
"minecraft:flower_forest_flowers",
|
||||
"minecraft:flower_meadow",
|
||||
"minecraft:flower_pale_garden",
|
||||
"minecraft:flower_plain",
|
||||
"minecraft:flower_plains",
|
||||
"minecraft:flower_swamp",
|
||||
"minecraft:flower_warm",
|
||||
"minecraft:forest_flowers",
|
||||
"minecraft:forest_rock",
|
||||
"minecraft:fossil_lower",
|
||||
"minecraft:fossil_upper",
|
||||
"minecraft:freeze_top_layer",
|
||||
"minecraft:glow_lichen",
|
||||
"minecraft:glowstone",
|
||||
"minecraft:glowstone_extra",
|
||||
"minecraft:grass_bonemeal",
|
||||
"minecraft:ice_patch",
|
||||
"minecraft:ice_spike",
|
||||
"minecraft:iceberg_blue",
|
||||
"minecraft:iceberg_packed",
|
||||
"minecraft:jungle_bush",
|
||||
"minecraft:jungle_tree",
|
||||
"minecraft:kelp_cold",
|
||||
"minecraft:kelp_warm",
|
||||
"minecraft:lake_lava_surface",
|
||||
"minecraft:lake_lava_underground",
|
||||
"minecraft:large_basalt_columns",
|
||||
"minecraft:large_dripstone",
|
||||
"minecraft:lush_caves_ceiling_vegetation",
|
||||
"minecraft:lush_caves_clay",
|
||||
"minecraft:lush_caves_vegetation",
|
||||
"minecraft:mangrove_checked",
|
||||
"minecraft:mega_jungle_tree_checked",
|
||||
"minecraft:mega_pine_checked",
|
||||
"minecraft:mega_spruce_checked",
|
||||
"minecraft:monster_room",
|
||||
"minecraft:monster_room_deep",
|
||||
"minecraft:mushroom_island_vegetation",
|
||||
"minecraft:nether_sprouts",
|
||||
"minecraft:oak",
|
||||
"minecraft:oak_bees_0002_leaf_litter",
|
||||
"minecraft:oak_bees_002",
|
||||
"minecraft:oak_checked",
|
||||
"minecraft:oak_leaf_litter",
|
||||
"minecraft:ore_ancient_debris_large",
|
||||
"minecraft:ore_andesite_lower",
|
||||
"minecraft:ore_andesite_upper",
|
||||
"minecraft:ore_blackstone",
|
||||
"minecraft:ore_clay",
|
||||
"minecraft:ore_coal_lower",
|
||||
"minecraft:ore_coal_upper",
|
||||
"minecraft:ore_copper",
|
||||
"minecraft:ore_copper_large",
|
||||
"minecraft:ore_debris_small",
|
||||
"minecraft:ore_diamond",
|
||||
"minecraft:ore_diamond_buried",
|
||||
"minecraft:ore_diamond_large",
|
||||
"minecraft:ore_diamond_medium",
|
||||
"minecraft:ore_diorite_lower",
|
||||
"minecraft:ore_diorite_upper",
|
||||
"minecraft:ore_dirt",
|
||||
"minecraft:ore_emerald",
|
||||
"minecraft:ore_gold",
|
||||
"minecraft:ore_gold_deltas",
|
||||
"minecraft:ore_gold_extra",
|
||||
"minecraft:ore_gold_lower",
|
||||
"minecraft:ore_gold_nether",
|
||||
"minecraft:ore_granite_lower",
|
||||
"minecraft:ore_granite_upper",
|
||||
"minecraft:ore_gravel",
|
||||
"minecraft:ore_gravel_nether",
|
||||
"minecraft:ore_infested",
|
||||
"minecraft:ore_iron_middle",
|
||||
"minecraft:ore_iron_small",
|
||||
"minecraft:ore_iron_upper",
|
||||
"minecraft:ore_lapis",
|
||||
"minecraft:ore_lapis_buried",
|
||||
"minecraft:ore_magma",
|
||||
"minecraft:ore_quartz_deltas",
|
||||
"minecraft:ore_quartz_nether",
|
||||
"minecraft:ore_redstone",
|
||||
"minecraft:ore_redstone_lower",
|
||||
"minecraft:ore_soul_sand",
|
||||
"minecraft:ore_tuff",
|
||||
"minecraft:pale_garden_flowers",
|
||||
"minecraft:pale_garden_vegetation",
|
||||
"minecraft:pale_moss_patch",
|
||||
"minecraft:pale_oak_checked",
|
||||
"minecraft:pale_oak_creaking_checked",
|
||||
"minecraft:patch_berry_bush",
|
||||
"minecraft:patch_berry_common",
|
||||
"minecraft:patch_berry_rare",
|
||||
"minecraft:patch_bush",
|
||||
"minecraft:patch_cactus",
|
||||
"minecraft:patch_cactus_decorated",
|
||||
"minecraft:patch_cactus_desert",
|
||||
"minecraft:patch_crimson_roots",
|
||||
"minecraft:patch_dead_bush",
|
||||
"minecraft:patch_dead_bush_2",
|
||||
"minecraft:patch_dead_bush_badlands",
|
||||
"minecraft:patch_dry_grass_badlands",
|
||||
"minecraft:patch_dry_grass_desert",
|
||||
"minecraft:patch_fire",
|
||||
"minecraft:patch_firefly_bush_near_water",
|
||||
"minecraft:patch_firefly_bush_near_water_swamp",
|
||||
"minecraft:patch_firefly_bush_swamp",
|
||||
"minecraft:patch_grass_badlands",
|
||||
"minecraft:patch_grass_forest",
|
||||
"minecraft:patch_grass_jungle",
|
||||
"minecraft:patch_grass_meadow",
|
||||
"minecraft:patch_grass_normal",
|
||||
"minecraft:patch_grass_plain",
|
||||
"minecraft:patch_grass_savanna",
|
||||
"minecraft:patch_grass_taiga",
|
||||
"minecraft:patch_grass_taiga_2",
|
||||
"minecraft:patch_large_fern",
|
||||
"minecraft:patch_leaf_litter",
|
||||
"minecraft:patch_melon",
|
||||
"minecraft:patch_melon_sparse",
|
||||
"minecraft:patch_pumpkin",
|
||||
"minecraft:patch_soul_fire",
|
||||
"minecraft:patch_sugar_cane",
|
||||
"minecraft:patch_sugar_cane_badlands",
|
||||
"minecraft:patch_sugar_cane_desert",
|
||||
"minecraft:patch_sugar_cane_swamp",
|
||||
"minecraft:patch_sunflower",
|
||||
"minecraft:patch_taiga_grass",
|
||||
"minecraft:patch_tall_grass",
|
||||
"minecraft:patch_tall_grass_2",
|
||||
"minecraft:patch_waterlily",
|
||||
"minecraft:pile_hay",
|
||||
"minecraft:pile_ice",
|
||||
"minecraft:pile_melon",
|
||||
"minecraft:pile_pumpkin",
|
||||
"minecraft:pile_snow",
|
||||
"minecraft:pine",
|
||||
"minecraft:pine_checked",
|
||||
"minecraft:pine_on_snow",
|
||||
"minecraft:pointed_dripstone",
|
||||
"minecraft:red_mushroom_nether",
|
||||
"minecraft:red_mushroom_normal",
|
||||
"minecraft:red_mushroom_old_growth",
|
||||
"minecraft:red_mushroom_swamp",
|
||||
"minecraft:red_mushroom_taiga",
|
||||
"minecraft:rooted_azalea_tree",
|
||||
"minecraft:rooted_sulfur_spring",
|
||||
"minecraft:sculk_patch_ancient_city",
|
||||
"minecraft:sculk_patch_deep_dark",
|
||||
"minecraft:sculk_vein",
|
||||
"minecraft:sea_pickle",
|
||||
"minecraft:seagrass_cold",
|
||||
"minecraft:seagrass_deep",
|
||||
"minecraft:seagrass_deep_cold",
|
||||
"minecraft:seagrass_deep_warm",
|
||||
"minecraft:seagrass_normal",
|
||||
"minecraft:seagrass_river",
|
||||
"minecraft:seagrass_swamp",
|
||||
"minecraft:seagrass_warm",
|
||||
"minecraft:small_basalt_columns",
|
||||
"minecraft:spore_blossom",
|
||||
"minecraft:spring_closed",
|
||||
"minecraft:spring_closed_double",
|
||||
"minecraft:spring_delta",
|
||||
"minecraft:spring_lava",
|
||||
"minecraft:spring_lava_frozen",
|
||||
"minecraft:spring_open",
|
||||
"minecraft:spring_water",
|
||||
"minecraft:spruce",
|
||||
"minecraft:spruce_checked",
|
||||
"minecraft:spruce_on_snow",
|
||||
"minecraft:sulfur_pool",
|
||||
"minecraft:sulfur_spike",
|
||||
"minecraft:sulfur_spike_cluster",
|
||||
"minecraft:super_birch_bees",
|
||||
"minecraft:super_birch_bees_0002",
|
||||
"minecraft:tall_mangrove_checked",
|
||||
"minecraft:trees_badlands",
|
||||
"minecraft:trees_birch",
|
||||
"minecraft:trees_birch_and_oak_leaf_litter",
|
||||
"minecraft:trees_cherry",
|
||||
"minecraft:trees_flower_forest",
|
||||
"minecraft:trees_grove",
|
||||
"minecraft:trees_jungle",
|
||||
"minecraft:trees_mangrove",
|
||||
"minecraft:trees_meadow",
|
||||
"minecraft:trees_old_growth_pine_taiga",
|
||||
"minecraft:trees_old_growth_spruce_taiga",
|
||||
"minecraft:trees_plains",
|
||||
"minecraft:trees_savanna",
|
||||
"minecraft:trees_snowy",
|
||||
"minecraft:trees_sparse_jungle",
|
||||
"minecraft:trees_swamp",
|
||||
"minecraft:trees_taiga",
|
||||
"minecraft:trees_water",
|
||||
"minecraft:trees_windswept_forest",
|
||||
"minecraft:trees_windswept_hills",
|
||||
"minecraft:trees_windswept_savanna",
|
||||
"minecraft:twisting_vines",
|
||||
"minecraft:underwater_magma",
|
||||
"minecraft:vines",
|
||||
"minecraft:void_start_platform",
|
||||
"minecraft:warm_ocean_vegetation",
|
||||
"minecraft:warped_forest_vegetation",
|
||||
"minecraft:warped_fungi",
|
||||
"minecraft:weeping_vines",
|
||||
"minecraft:wildflowers_birch_forest",
|
||||
"minecraft:wildflowers_meadow",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,126 @@ pub enum StructureKeys {
|
||||
TrailRuins,
|
||||
TrialChambers,
|
||||
}
|
||||
impl StructureKeys {
|
||||
pub fn from_name(name: &str) -> Option<Self> {
|
||||
let name = name.strip_prefix("minecraft:").unwrap_or(name);
|
||||
match name {
|
||||
"ancient_city" => Some(Self::AncientCity),
|
||||
"bastion_remnant" => Some(Self::BastionRemnant),
|
||||
"buried_treasure" => Some(Self::BuriedTreasure),
|
||||
"desert_pyramid" => Some(Self::DesertPyramid),
|
||||
"end_city" => Some(Self::EndCity),
|
||||
"fortress" => Some(Self::Fortress),
|
||||
"igloo" => Some(Self::Igloo),
|
||||
"jungle_pyramid" => Some(Self::JunglePyramid),
|
||||
"mansion" => Some(Self::Mansion),
|
||||
"mineshaft" => Some(Self::Mineshaft),
|
||||
"mineshaft_mesa" => Some(Self::MineshaftMesa),
|
||||
"monument" => Some(Self::Monument),
|
||||
"nether_fossil" => Some(Self::NetherFossil),
|
||||
"ocean_ruin_cold" => Some(Self::OceanRuinCold),
|
||||
"ocean_ruin_warm" => Some(Self::OceanRuinWarm),
|
||||
"pillager_outpost" => Some(Self::PillagerOutpost),
|
||||
"ruined_portal" => Some(Self::RuinedPortal),
|
||||
"ruined_portal_desert" => Some(Self::RuinedPortalDesert),
|
||||
"ruined_portal_jungle" => Some(Self::RuinedPortalJungle),
|
||||
"ruined_portal_mountain" => Some(Self::RuinedPortalMountain),
|
||||
"ruined_portal_nether" => Some(Self::RuinedPortalNether),
|
||||
"ruined_portal_ocean" => Some(Self::RuinedPortalOcean),
|
||||
"ruined_portal_swamp" => Some(Self::RuinedPortalSwamp),
|
||||
"shipwreck" => Some(Self::Shipwreck),
|
||||
"shipwreck_beached" => Some(Self::ShipwreckBeached),
|
||||
"stronghold" => Some(Self::Stronghold),
|
||||
"swamp_hut" => Some(Self::SwampHut),
|
||||
"trail_ruins" => Some(Self::TrailRuins),
|
||||
"trial_chambers" => Some(Self::TrialChambers),
|
||||
"village_desert" => Some(Self::VillageDesert),
|
||||
"village_plains" => Some(Self::VillagePlains),
|
||||
"village_savanna" => Some(Self::VillageSavanna),
|
||||
"village_snowy" => Some(Self::VillageSnowy),
|
||||
"village_taiga" => Some(Self::VillageTaiga),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
#[must_use]
|
||||
pub const fn to_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::AncientCity => "minecraft:ancient_city",
|
||||
Self::BastionRemnant => "minecraft:bastion_remnant",
|
||||
Self::BuriedTreasure => "minecraft:buried_treasure",
|
||||
Self::DesertPyramid => "minecraft:desert_pyramid",
|
||||
Self::EndCity => "minecraft:end_city",
|
||||
Self::Fortress => "minecraft:fortress",
|
||||
Self::Igloo => "minecraft:igloo",
|
||||
Self::JunglePyramid => "minecraft:jungle_pyramid",
|
||||
Self::Mansion => "minecraft:mansion",
|
||||
Self::Mineshaft => "minecraft:mineshaft",
|
||||
Self::MineshaftMesa => "minecraft:mineshaft_mesa",
|
||||
Self::Monument => "minecraft:monument",
|
||||
Self::NetherFossil => "minecraft:nether_fossil",
|
||||
Self::OceanRuinCold => "minecraft:ocean_ruin_cold",
|
||||
Self::OceanRuinWarm => "minecraft:ocean_ruin_warm",
|
||||
Self::PillagerOutpost => "minecraft:pillager_outpost",
|
||||
Self::RuinedPortal => "minecraft:ruined_portal",
|
||||
Self::RuinedPortalDesert => "minecraft:ruined_portal_desert",
|
||||
Self::RuinedPortalJungle => "minecraft:ruined_portal_jungle",
|
||||
Self::RuinedPortalMountain => "minecraft:ruined_portal_mountain",
|
||||
Self::RuinedPortalNether => "minecraft:ruined_portal_nether",
|
||||
Self::RuinedPortalOcean => "minecraft:ruined_portal_ocean",
|
||||
Self::RuinedPortalSwamp => "minecraft:ruined_portal_swamp",
|
||||
Self::Shipwreck => "minecraft:shipwreck",
|
||||
Self::ShipwreckBeached => "minecraft:shipwreck_beached",
|
||||
Self::Stronghold => "minecraft:stronghold",
|
||||
Self::SwampHut => "minecraft:swamp_hut",
|
||||
Self::TrailRuins => "minecraft:trail_ruins",
|
||||
Self::TrialChambers => "minecraft:trial_chambers",
|
||||
Self::VillageDesert => "minecraft:village_desert",
|
||||
Self::VillagePlains => "minecraft:village_plains",
|
||||
Self::VillageSavanna => "minecraft:village_savanna",
|
||||
Self::VillageSnowy => "minecraft:village_snowy",
|
||||
Self::VillageTaiga => "minecraft:village_taiga",
|
||||
}
|
||||
}
|
||||
#[must_use]
|
||||
pub const fn all_names() -> &'static [&'static str] {
|
||||
&[
|
||||
"minecraft:ancient_city",
|
||||
"minecraft:bastion_remnant",
|
||||
"minecraft:buried_treasure",
|
||||
"minecraft:desert_pyramid",
|
||||
"minecraft:end_city",
|
||||
"minecraft:fortress",
|
||||
"minecraft:igloo",
|
||||
"minecraft:jungle_pyramid",
|
||||
"minecraft:mansion",
|
||||
"minecraft:mineshaft",
|
||||
"minecraft:mineshaft_mesa",
|
||||
"minecraft:monument",
|
||||
"minecraft:nether_fossil",
|
||||
"minecraft:ocean_ruin_cold",
|
||||
"minecraft:ocean_ruin_warm",
|
||||
"minecraft:pillager_outpost",
|
||||
"minecraft:ruined_portal",
|
||||
"minecraft:ruined_portal_desert",
|
||||
"minecraft:ruined_portal_jungle",
|
||||
"minecraft:ruined_portal_mountain",
|
||||
"minecraft:ruined_portal_nether",
|
||||
"minecraft:ruined_portal_ocean",
|
||||
"minecraft:ruined_portal_swamp",
|
||||
"minecraft:shipwreck",
|
||||
"minecraft:shipwreck_beached",
|
||||
"minecraft:stronghold",
|
||||
"minecraft:swamp_hut",
|
||||
"minecraft:trail_ruins",
|
||||
"minecraft:trial_chambers",
|
||||
"minecraft:village_desert",
|
||||
"minecraft:village_plains",
|
||||
"minecraft:village_savanna",
|
||||
"minecraft:village_snowy",
|
||||
"minecraft:village_taiga",
|
||||
]
|
||||
}
|
||||
}
|
||||
pub struct StructureSet {
|
||||
pub placement: StructurePlacement,
|
||||
pub structures: &'static [WeightedEntry],
|
||||
|
||||
@@ -38,11 +38,20 @@ fn main() {
|
||||
|
||||
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
let assets_dir = Path::new(&manifest_dir).join("assets/structures");
|
||||
let mut all_template_names: Vec<String> = Vec::new();
|
||||
let mut all_pool_names: Vec<String> = Vec::new();
|
||||
if assets_dir.exists() {
|
||||
let mut pools = std::collections::BTreeMap::new();
|
||||
process_dir(&assets_dir, "", &mut code, &mut pools);
|
||||
process_dir(
|
||||
&assets_dir,
|
||||
"",
|
||||
&mut code,
|
||||
&mut pools,
|
||||
&mut all_template_names,
|
||||
);
|
||||
|
||||
for (pool_id, elements) in pools {
|
||||
all_pool_names.push(pool_id.clone());
|
||||
let _ = writeln!(
|
||||
pool_code,
|
||||
" \"minecraft:{pool_id}\" | \"{pool_id}\" => Some(&["
|
||||
@@ -57,6 +66,24 @@ fn main() {
|
||||
code.push_str(" _ => None,\n");
|
||||
code.push_str(" }\n}\n");
|
||||
|
||||
// Generate a function returning all available template names (for tab-completion)
|
||||
code.push_str(
|
||||
"#[must_use]\n#[allow(clippy::too_many_lines, clippy::large_stack_arrays)]\npub const fn _generated_all_template_names() -> &'static [&'static str] {\n &[\n",
|
||||
);
|
||||
for name in &all_template_names {
|
||||
let _ = writeln!(code, " \"{name}\",");
|
||||
}
|
||||
code.push_str(" ]\n}\n");
|
||||
|
||||
// Generate a function returning all available pool names (for tab-completion)
|
||||
code.push_str(
|
||||
"#[must_use]\n#[allow(clippy::too_many_lines, clippy::large_stack_arrays)]\npub const fn _generated_all_pool_names() -> &'static [&'static str] {\n &[\n",
|
||||
);
|
||||
for name in &all_pool_names {
|
||||
let _ = writeln!(code, " \"{name}\",");
|
||||
}
|
||||
code.push_str(" ]\n}\n");
|
||||
|
||||
pool_code.push_str(" _ => None,\n");
|
||||
pool_code.push_str(" }\n}\n");
|
||||
|
||||
@@ -65,11 +92,13 @@ fn main() {
|
||||
&worldgen_dir.join("template_pool"),
|
||||
"",
|
||||
&mut template_pool_json_code,
|
||||
&mut all_pool_names,
|
||||
);
|
||||
process_json_dir(
|
||||
&worldgen_dir.join("processor_list"),
|
||||
"",
|
||||
&mut processor_list_json_code,
|
||||
&mut Vec::new(),
|
||||
);
|
||||
template_pool_json_code.push_str(" _ => None,\n");
|
||||
template_pool_json_code.push_str(" }\n}\n");
|
||||
@@ -90,6 +119,7 @@ fn process_dir(
|
||||
prefix: &str,
|
||||
code: &mut String,
|
||||
pools: &mut std::collections::BTreeMap<String, Vec<String>>,
|
||||
names: &mut Vec<String>,
|
||||
) {
|
||||
for entry in fs::read_dir(dir).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
@@ -102,7 +132,7 @@ fn process_dir(
|
||||
} else {
|
||||
format!("{prefix}/{name}")
|
||||
};
|
||||
process_dir(&path, &new_prefix, code, pools);
|
||||
process_dir(&path, &new_prefix, code, pools, names);
|
||||
} else if path
|
||||
.extension()
|
||||
.and_then(|s| s.to_str())
|
||||
@@ -121,6 +151,7 @@ fn process_dir(
|
||||
template_name = template_name,
|
||||
abs = abs_path.display()
|
||||
);
|
||||
names.push(template_name.clone());
|
||||
|
||||
if !prefix.is_empty() {
|
||||
pools
|
||||
@@ -132,7 +163,7 @@ fn process_dir(
|
||||
}
|
||||
}
|
||||
|
||||
fn process_json_dir(dir: &Path, prefix: &str, code: &mut String) {
|
||||
fn process_json_dir(dir: &Path, prefix: &str, code: &mut String, names: &mut Vec<String>) {
|
||||
if !dir.exists() {
|
||||
return;
|
||||
}
|
||||
@@ -152,13 +183,14 @@ fn process_json_dir(dir: &Path, prefix: &str, code: &mut String) {
|
||||
} else {
|
||||
format!("{prefix}/{name}")
|
||||
};
|
||||
process_json_dir(&path, &new_prefix, code);
|
||||
process_json_dir(&path, &new_prefix, code, names);
|
||||
} else if let Some(stem) = name.strip_suffix(".json") {
|
||||
let id = if prefix.is_empty() {
|
||||
stem.to_string()
|
||||
} else {
|
||||
format!("{prefix}/{stem}")
|
||||
};
|
||||
names.push(id.clone());
|
||||
let abs_path = path.canonicalize().unwrap();
|
||||
let _ = writeln!(
|
||||
code,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
mod configured_features;
|
||||
/// So first we go trough all the placed features and check if we should place a feature somewhere using `placed_features`.
|
||||
/// then if we want to place a feature we place it using the `configured_features`, there is the logic for how we are going to place the feature
|
||||
pub mod configured_features;
|
||||
// So first we go trough all the placed features and check if we should place a feature
|
||||
// somewhere using `placed_features`. Then if we want to place a feature we place it
|
||||
// using the `configured_features`, there is the logic for how we are going to place the
|
||||
// feature.
|
||||
pub mod placed_features;
|
||||
|
||||
mod features;
|
||||
|
||||
@@ -550,6 +550,12 @@ pub trait ConditionalPlacementModifier {
|
||||
}
|
||||
|
||||
// generated code is now placed alongside other codegen outputs
|
||||
// in `src/generated` so it’s easier to find when upgrading MC versions.
|
||||
// in `src/generated` so it's easier to find when upgrading MC versions.
|
||||
// the path is relative to this file (up two levels to reach `src`).
|
||||
|
||||
/// Returns all placed feature names for tab-completion in `/place feature`.
|
||||
#[must_use]
|
||||
pub const fn all_placed_feature_names() -> &'static [&'static str] {
|
||||
pumpkin_data::placed_feature::PlacedFeature::all_names()
|
||||
}
|
||||
include!("../../../../pumpkin-data/src/generated/placed_features_generated.rs");
|
||||
|
||||
@@ -5,7 +5,7 @@ pub mod blender;
|
||||
mod block_predicate;
|
||||
mod block_state_provider;
|
||||
pub mod carver;
|
||||
mod feature;
|
||||
pub mod feature;
|
||||
pub mod generator;
|
||||
pub mod height_limit;
|
||||
pub mod height_provider;
|
||||
|
||||
@@ -57,6 +57,7 @@ use crate::{
|
||||
use pumpkin_data::tag::get_tag_ids;
|
||||
use pumpkin_nbt::compound::NbtCompound;
|
||||
|
||||
use crate::generation::structure::template::BlockPlacer;
|
||||
use crate::tick::{ScheduledTick, TickPriority};
|
||||
|
||||
enum ActiveSupplier {
|
||||
@@ -132,7 +133,7 @@ pub struct ProtoChunk {
|
||||
pub(crate) flat_block_map: Box<[BlockStateId]>,
|
||||
pub flat_biome_map: Box<[u8]>,
|
||||
pub flat_surface_height_map: [i16; CHUNK_AREA],
|
||||
flat_ocean_floor_height_map: [i16; CHUNK_AREA],
|
||||
pub flat_ocean_floor_height_map: [i16; CHUNK_AREA],
|
||||
pub flat_motion_blocking_height_map: [i16; CHUNK_AREA],
|
||||
pub flat_motion_blocking_no_leaves_height_map: [i16; CHUNK_AREA],
|
||||
structure_starts: FxHashMap<StructureKeys, StructureInstance>,
|
||||
@@ -1514,3 +1515,87 @@ impl BlockAccessor for ProtoChunk {
|
||||
BlockState::from_id_with_block(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockPlacer for ProtoChunk {
|
||||
fn get_block_state(&self, pos: &Vector3<i32>) -> BlockStateId {
|
||||
self.get_block_state(pos)
|
||||
}
|
||||
|
||||
fn set_block_state(&mut self, pos: &Vector3<i32>, state: &BlockState) {
|
||||
Self::set_block_state(self, pos.x, pos.y, pos.z, state);
|
||||
}
|
||||
|
||||
fn add_block_entity(&mut self, nbt: NbtCompound) {
|
||||
self.add_block_entity(nbt);
|
||||
}
|
||||
}
|
||||
|
||||
impl GenerationCache for ProtoChunk {
|
||||
fn get_center_chunk_mut(&mut self) -> &mut ProtoChunk {
|
||||
self
|
||||
}
|
||||
fn get_center_chunk(&self) -> &ProtoChunk {
|
||||
self
|
||||
}
|
||||
fn get_chunk_mut(&mut self, cx: i32, cz: i32) -> Option<&mut ProtoChunk> {
|
||||
(cx == self.x && cz == self.z).then_some(self)
|
||||
}
|
||||
fn get_chunk(&self, cx: i32, cz: i32) -> Option<&ProtoChunk> {
|
||||
(cx == self.x && cz == self.z).then_some(self)
|
||||
}
|
||||
fn try_get_proto_chunk(&self, cx: i32, cz: i32) -> Option<&ProtoChunk> {
|
||||
self.get_chunk(cx, cz)
|
||||
}
|
||||
fn get_block_state(&self, pos: &Vector3<i32>) -> BlockStateId {
|
||||
Self::get_block_state(self, pos)
|
||||
}
|
||||
fn get_fluid_and_fluid_state(&self, _pos: &Vector3<i32>) -> (Fluid, FluidState) {
|
||||
(
|
||||
Fluid::EMPTY,
|
||||
FluidState {
|
||||
height: 0.0,
|
||||
level: 0,
|
||||
is_empty: true,
|
||||
blast_resistance: 0.0,
|
||||
block_state_id: BlockStateId::AIR,
|
||||
is_still: false,
|
||||
is_source: false,
|
||||
falling: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
fn set_block_state(&mut self, pos: &Vector3<i32>, block_state: &BlockState) {
|
||||
Self::set_block_state(self, pos.x, pos.y, pos.z, block_state);
|
||||
}
|
||||
fn add_block_entity(&mut self, _pos: &Vector3<i32>, nbt: NbtCompound) {
|
||||
self.add_block_entity(nbt);
|
||||
}
|
||||
fn top_motion_blocking_block_height_exclusive(&self, x: i32, z: i32) -> i32 {
|
||||
Self::top_motion_blocking_block_height_exclusive(self, x, z)
|
||||
}
|
||||
fn top_motion_blocking_block_no_leaves_height_exclusive(&self, x: i32, z: i32) -> i32 {
|
||||
Self::top_motion_blocking_block_no_leaves_height_exclusive(self, x, z)
|
||||
}
|
||||
fn get_top_y(&self, heightmap: &HeightMap, x: i32, z: i32) -> i32 {
|
||||
Self::get_top_y(self, heightmap, x, z)
|
||||
}
|
||||
fn top_block_height_exclusive(&self, x: i32, z: i32) -> i32 {
|
||||
Self::top_block_height_exclusive(self, x, z)
|
||||
}
|
||||
fn ocean_floor_height_exclusive(&self, x: i32, z: i32) -> i32 {
|
||||
Self::ocean_floor_height_exclusive(self, x, z)
|
||||
}
|
||||
fn is_air(&self, local_pos: &Vector3<i32>) -> bool {
|
||||
self.is_air(local_pos)
|
||||
}
|
||||
fn get_biome_for_terrain_gen(&self, x: i32, y: i32, z: i32) -> &'static Biome {
|
||||
Self::get_biome(self, x, y, z)
|
||||
}
|
||||
fn get_blending_data(
|
||||
&self,
|
||||
_cx: i32,
|
||||
_cz: i32,
|
||||
) -> Option<&crate::generation::blender::blending_data::BlendingData> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,28 +30,17 @@ pub mod shiftable_piece;
|
||||
pub mod structures;
|
||||
pub mod template;
|
||||
|
||||
/// Creates a structure position by dispatching to the appropriate generator.
|
||||
/// Does NOT perform biome validation — callers that require it should add
|
||||
/// their own check after calling this function.
|
||||
#[must_use]
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn try_generate_structure(
|
||||
pub fn generate_structure_position(
|
||||
key: &StructureKeys,
|
||||
structure: &Structure,
|
||||
seed: i64,
|
||||
chunk: &ProtoChunk,
|
||||
sea_level: i32,
|
||||
height_sampler: Option<&mut dyn crate::generation::structure::structures::HeightSampler>,
|
||||
context: StructureGeneratorContext<'_>,
|
||||
) -> Option<StructurePosition> {
|
||||
let random = create_chunk_random(seed, chunk.x, chunk.z);
|
||||
let context = StructureGeneratorContext {
|
||||
seed,
|
||||
chunk_x: chunk.x,
|
||||
chunk_z: chunk.z,
|
||||
random,
|
||||
sea_level,
|
||||
min_y: chunk.bottom_y() as i32,
|
||||
height_sampler,
|
||||
structure_key: Some(*key),
|
||||
};
|
||||
let structure_pos = match key {
|
||||
match key {
|
||||
StructureKeys::BuriedTreasure => {
|
||||
BuriedTreasureGenerator::get_structure_position(&BuriedTreasureGenerator, context)
|
||||
}
|
||||
@@ -133,7 +122,30 @@ pub fn try_generate_structure(
|
||||
};
|
||||
generator.get_structure_position(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn try_generate_structure(
|
||||
key: &StructureKeys,
|
||||
structure: &Structure,
|
||||
seed: i64,
|
||||
chunk: &ProtoChunk,
|
||||
sea_level: i32,
|
||||
height_sampler: Option<&mut dyn crate::generation::structure::structures::HeightSampler>,
|
||||
) -> Option<StructurePosition> {
|
||||
let random = create_chunk_random(seed, chunk.x, chunk.z);
|
||||
let context = StructureGeneratorContext {
|
||||
seed,
|
||||
chunk_x: chunk.x,
|
||||
chunk_z: chunk.z,
|
||||
random,
|
||||
sea_level,
|
||||
min_y: chunk.bottom_y() as i32,
|
||||
height_sampler,
|
||||
structure_key: Some(*key),
|
||||
};
|
||||
let structure_pos = generate_structure_position(key, structure, context);
|
||||
|
||||
if let Some(pos) = structure_pos {
|
||||
// Get the biome at the structure's starting position.
|
||||
@@ -177,81 +189,7 @@ pub fn lazily_generate_structure(
|
||||
biome_supplier: &dyn BiomeSupplier,
|
||||
multi_noise_sampler: &mut MultiNoiseSampler,
|
||||
) -> Option<StructurePosition> {
|
||||
let structure_pos = match key {
|
||||
StructureKeys::BuriedTreasure => {
|
||||
BuriedTreasureGenerator::get_structure_position(&BuriedTreasureGenerator, context)
|
||||
}
|
||||
StructureKeys::SwampHut => {
|
||||
SwampHutGenerator::get_structure_position(&SwampHutGenerator, context)
|
||||
}
|
||||
StructureKeys::Stronghold => {
|
||||
StrongholdGenerator::get_structure_position(&StrongholdGenerator, context)
|
||||
}
|
||||
StructureKeys::Fortress => {
|
||||
NetherFortressGenerator::get_structure_position(&NetherFortressGenerator, context)
|
||||
}
|
||||
StructureKeys::NetherFossil => {
|
||||
NetherFossilGenerator::get_structure_position(&NetherFossilGenerator, context)
|
||||
}
|
||||
StructureKeys::Igloo => IglooGenerator::get_structure_position(&IglooGenerator, context),
|
||||
StructureKeys::DesertPyramid => DesertPyramidGenerator.get_structure_position(context),
|
||||
StructureKeys::JunglePyramid => JungleTempleGenerator.get_structure_position(context),
|
||||
StructureKeys::VillagePlains
|
||||
| StructureKeys::VillageDesert
|
||||
| StructureKeys::VillageSavanna
|
||||
| StructureKeys::VillageSnowy
|
||||
| StructureKeys::VillageTaiga
|
||||
| StructureKeys::AncientCity
|
||||
| StructureKeys::BastionRemnant
|
||||
| StructureKeys::PillagerOutpost
|
||||
| StructureKeys::TrailRuins
|
||||
| StructureKeys::TrialChambers => {
|
||||
let mut generator = JigsawGenerator::new(
|
||||
structure
|
||||
.start_pool
|
||||
.expect("Jigsaw structure must have a start pool"),
|
||||
structure.size.expect("Jigsaw structure must have a size"),
|
||||
);
|
||||
if *key == StructureKeys::PillagerOutpost {
|
||||
generator = generator.with_expansion_hack(true);
|
||||
}
|
||||
if let Some(start_jigsaw_name) = structure.start_jigsaw_name {
|
||||
generator = generator.with_start_jigsaw(start_jigsaw_name);
|
||||
}
|
||||
generator.get_structure_position(context)
|
||||
}
|
||||
StructureKeys::Shipwreck | StructureKeys::ShipwreckBeached => {
|
||||
let generator = ShipwreckGenerator {
|
||||
is_beached: *key == StructureKeys::ShipwreckBeached,
|
||||
};
|
||||
generator.get_structure_position(context)
|
||||
}
|
||||
StructureKeys::RuinedPortal
|
||||
| StructureKeys::RuinedPortalDesert
|
||||
| StructureKeys::RuinedPortalJungle
|
||||
| StructureKeys::RuinedPortalSwamp
|
||||
| StructureKeys::RuinedPortalMountain
|
||||
| StructureKeys::RuinedPortalOcean
|
||||
| StructureKeys::RuinedPortalNether => {
|
||||
let generator = RuinedPortalGenerator { variant: *key };
|
||||
generator.get_structure_position(context)
|
||||
}
|
||||
StructureKeys::OceanRuinCold | StructureKeys::OceanRuinWarm => {
|
||||
let generator = OceanRuinGenerator {
|
||||
is_warm: *key == StructureKeys::OceanRuinWarm,
|
||||
};
|
||||
generator.get_structure_position(context)
|
||||
}
|
||||
StructureKeys::EndCity => EndCityGenerator.get_structure_position(context),
|
||||
StructureKeys::Mansion => MansionGenerator.get_structure_position(context),
|
||||
StructureKeys::Monument => OceanMonumentGenerator.get_structure_position(context),
|
||||
StructureKeys::Mineshaft | StructureKeys::MineshaftMesa => {
|
||||
let generator = MineshaftGenerator {
|
||||
is_mesa: *key == StructureKeys::MineshaftMesa,
|
||||
};
|
||||
generator.get_structure_position(context)
|
||||
}
|
||||
};
|
||||
let structure_pos = generate_structure_position(key, structure, context);
|
||||
|
||||
if let Some(pos) = structure_pos {
|
||||
// Get the biome mathematically, bypassing the chunk boundaries entirely!
|
||||
|
||||
@@ -5,9 +5,11 @@ use crate::generation::structure::structures::{
|
||||
StructureGenerator, StructureGeneratorContext, StructurePieceBase, StructurePosition,
|
||||
};
|
||||
use crate::generation::structure::template::{
|
||||
BlockMirror, BlockRotation, PaletteEntry, StructureTemplate,
|
||||
BlockMirror, BlockPlacer, BlockRotation, PaletteEntry, StructureTemplate,
|
||||
};
|
||||
use pumpkin_util::math::block_box::BlockBox;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_util::random::RandomImpl;
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
@@ -542,6 +544,45 @@ impl StructurePieceBase for PoolElementStructurePiece {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn place_pool_element_templates(
|
||||
piece: &PoolElementStructurePiece,
|
||||
placer: &mut impl BlockPlacer,
|
||||
chunk_box: Option<&BlockBox>,
|
||||
) {
|
||||
let origin = Vector3::new(piece.pos.0.x, piece.pos.0.y, piece.pos.0.z);
|
||||
|
||||
piece
|
||||
.element
|
||||
.for_each_template(|_name, processor_list, legacy, template| {
|
||||
let corner = piece.rotation.rotate_offset(
|
||||
template.size.x.saturating_sub(1),
|
||||
template.size.z.saturating_sub(1),
|
||||
);
|
||||
let placement_origin = Vector3::new(
|
||||
origin.x + corner.0.min(0),
|
||||
origin.y,
|
||||
origin.z + corner.1.min(0),
|
||||
);
|
||||
let processors = match processor_list {
|
||||
ProcessorListRef::Named(name) => {
|
||||
crate::generation::structure::template::processor::load_processor_list(name)
|
||||
}
|
||||
ProcessorListRef::Empty => Arc::from([]),
|
||||
};
|
||||
crate::generation::structure::template::place_template(
|
||||
placer,
|
||||
&template,
|
||||
placement_origin,
|
||||
(0, 0),
|
||||
piece.rotation,
|
||||
legacy,
|
||||
piece.liquid_settings == LiquidSettings::ApplyWaterlog,
|
||||
processors.as_ref(),
|
||||
chunk_box,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
impl PoolElementStructurePiece {
|
||||
pub fn add_junction(&mut self, junction: JigsawJunction) {
|
||||
self.junctions.push(junction);
|
||||
|
||||
@@ -143,3 +143,26 @@ pub fn global_cache() -> &'static TemplateCache {
|
||||
pub fn get_template(name: &str) -> Option<Arc<StructureTemplate>> {
|
||||
global_cache().get(name)
|
||||
}
|
||||
|
||||
/// Returns a list of all available template names that can be loaded.
|
||||
///
|
||||
/// These are derived from the embedded structure files at compile time.
|
||||
/// Useful for tab-completion in commands.
|
||||
#[must_use]
|
||||
#[allow(clippy::used_underscore_items)]
|
||||
pub const fn all_template_names() -> &'static [&'static str] {
|
||||
_generated_all_template_names()
|
||||
}
|
||||
|
||||
/// Returns a list of all available structure names for `/place structure` tab-completion.
|
||||
#[must_use]
|
||||
pub const fn all_structure_names() -> &'static [&'static str] {
|
||||
pumpkin_data::structures::StructureKeys::all_names()
|
||||
}
|
||||
|
||||
/// Returns a list of all available pool names for `/place jigsaw` tab-completion.
|
||||
#[must_use]
|
||||
#[allow(clippy::used_underscore_items)]
|
||||
pub const fn all_pool_names() -> &'static [&'static str] {
|
||||
_generated_all_pool_names()
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ pub mod processor;
|
||||
mod structure_template;
|
||||
mod template_piece;
|
||||
|
||||
use pumpkin_data::BlockStateId;
|
||||
use pumpkin_data::Mirror;
|
||||
use pumpkin_data::Rotation;
|
||||
use pumpkin_nbt::{compound::NbtCompound, tag::NbtTag};
|
||||
@@ -40,14 +41,22 @@ use crate::ProtoChunk;
|
||||
|
||||
pub use block_state_resolver::BlockStateResolver;
|
||||
pub use cache::{
|
||||
TemplateCache, get_pool_elements, get_processor_list_json, get_template,
|
||||
get_template_pool_json, global_cache,
|
||||
TemplateCache, all_pool_names, all_structure_names, all_template_names, get_pool_elements,
|
||||
get_processor_list_json, get_template, get_template_pool_json, global_cache,
|
||||
};
|
||||
pub use processor::StructureProcessor;
|
||||
pub use pumpkin_data::{Mirror as BlockMirror, Rotation as BlockRotation};
|
||||
pub use pumpkin_data::{BlockState, Mirror as BlockMirror, Rotation as BlockRotation};
|
||||
pub use structure_template::{PaletteEntry, StructureTemplate, TemplateBlock, TemplateEntity};
|
||||
pub use template_piece::TemplatePiece;
|
||||
|
||||
/// Abstraction over block placement, implemented by both [`ProtoChunk`] (worldgen) and
|
||||
/// [`WorldBlockPlacer`] (live `/place template` command).
|
||||
pub trait BlockPlacer {
|
||||
fn get_block_state(&self, pos: &Vector3<i32>) -> BlockStateId;
|
||||
fn set_block_state(&mut self, pos: &Vector3<i32>, state: &BlockState);
|
||||
fn add_block_entity(&mut self, nbt: NbtCompound);
|
||||
}
|
||||
|
||||
/// Places a template at a world origin with an un-rotated XZ offset.
|
||||
///
|
||||
/// All rotation is handled internally:
|
||||
@@ -60,7 +69,7 @@ pub use template_piece::TemplatePiece;
|
||||
/// `offset` is the un-rotated XZ offset from origin (`x_offset`, `z_offset`) - rotation is applied automatically.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn place_template(
|
||||
chunk: &mut ProtoChunk,
|
||||
placer: &mut impl BlockPlacer,
|
||||
template: &StructureTemplate,
|
||||
origin: Vector3<i32>,
|
||||
offset: (i32, i32),
|
||||
@@ -126,7 +135,7 @@ pub fn place_template(
|
||||
let world_pos = Vector3::new(wx, wy, wz);
|
||||
|
||||
if apply_waterlogging
|
||||
&& chunk.get_block_state(&world_pos).to_block_id() == pumpkin_data::Block::WATER.id
|
||||
&& placer.get_block_state(&world_pos).to_block_id() == pumpkin_data::Block::WATER.id
|
||||
&& let Some((_, waterlogged)) = placed_entry
|
||||
.properties
|
||||
.iter_mut()
|
||||
@@ -143,7 +152,7 @@ pub fn place_template(
|
||||
// Apply processors
|
||||
let mut should_place = true;
|
||||
for processor in processors {
|
||||
let Some(processed_state) = processor.process(chunk, world_pos, state) else {
|
||||
let Some(processed_state) = processor.process(placer, world_pos, state) else {
|
||||
should_place = false;
|
||||
break;
|
||||
};
|
||||
@@ -157,7 +166,7 @@ pub fn place_template(
|
||||
continue;
|
||||
}
|
||||
|
||||
chunk.set_block_state(wx, wy, wz, state);
|
||||
placer.set_block_state(&Vector3::new(wx, wy, wz), state);
|
||||
|
||||
// Create block entities for interactive blocks (furnaces, chests, etc.)
|
||||
let block_entity_id = get_block_entity_id(&placed_entry.name);
|
||||
@@ -189,7 +198,7 @@ pub fn place_template(
|
||||
placed_nbt.put_long("LootTableSeed", random.next_i64());
|
||||
}
|
||||
|
||||
chunk.add_block_entity(placed_nbt);
|
||||
placer.add_block_entity(placed_nbt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use pumpkin_util::{
|
||||
use serde::Deserialize;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
use crate::ProtoChunk;
|
||||
use super::BlockPlacer;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum StructureProcessor {
|
||||
@@ -83,7 +83,7 @@ impl StructureProcessor {
|
||||
#[must_use]
|
||||
pub fn process(
|
||||
&self,
|
||||
chunk: &ProtoChunk,
|
||||
placer: &impl BlockPlacer,
|
||||
pos: Vector3<i32>,
|
||||
state: &'static BlockState,
|
||||
) -> Option<&'static BlockState> {
|
||||
@@ -106,10 +106,10 @@ impl StructureProcessor {
|
||||
.map_or(Some(state), |rule| Some(rule.output_state))
|
||||
}
|
||||
Self::ProtectedBlocks(blocks) => {
|
||||
let existing = chunk.get_block_state(&pos).to_block_id();
|
||||
let existing = placer.get_block_state(&pos).to_block_id();
|
||||
(!blocks.contains(existing)).then_some(state)
|
||||
}
|
||||
Self::Capped { limit: _, delegate } => delegate.process(chunk, pos, state),
|
||||
Self::Capped { limit: _, delegate } => delegate.process(placer, pos, state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,10 +230,14 @@ pub mod hex_color;
|
||||
pub mod identifier;
|
||||
pub mod nbt;
|
||||
pub mod objective;
|
||||
pub mod placed_feature;
|
||||
pub mod pool;
|
||||
pub mod range;
|
||||
pub mod resource_key;
|
||||
pub mod slot;
|
||||
pub mod structure;
|
||||
pub mod team;
|
||||
pub mod team_color;
|
||||
pub mod template;
|
||||
pub mod time;
|
||||
pub mod uuid;
|
||||
|
||||
49
pumpkin/src/command/argument_types/placed_feature.rs
Normal file
49
pumpkin/src/command/argument_types/placed_feature.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use std::pin::Pin;
|
||||
|
||||
use pumpkin_protocol::java::client::play::SuggestionProviders;
|
||||
use pumpkin_util::identifier::Identifier;
|
||||
|
||||
use crate::command::argument_types::FromStringReader;
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
|
||||
pub struct PlacedFeatureNameArgumentType;
|
||||
|
||||
impl ArgumentType for PlacedFeatureNameArgumentType {
|
||||
type Item = Identifier;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
Identifier::from_reader(reader)
|
||||
}
|
||||
|
||||
fn list_suggestions<'a>(
|
||||
&'a self,
|
||||
_context: &'a CommandContext,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Pin<Box<dyn Future<Output = Suggestions> + Send + 'a>> {
|
||||
let names = pumpkin_world::generation::feature::placed_features::all_placed_feature_names();
|
||||
Box::pin(async move {
|
||||
builder
|
||||
.filter_and_suggest_iter(names.iter().copied())
|
||||
.build()
|
||||
})
|
||||
}
|
||||
|
||||
fn client_side_parser(&'_ self) -> JavaClientArgumentType {
|
||||
JavaClientArgumentType::ResourceLocation
|
||||
}
|
||||
|
||||
fn override_suggestion_providers(&self) -> Option<SuggestionProviders> {
|
||||
Some(SuggestionProviders::AskServer)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<String> {
|
||||
vec![
|
||||
"minecraft:acacia".to_string(),
|
||||
"minecraft:amethyst_geode".to_string(),
|
||||
]
|
||||
}
|
||||
}
|
||||
45
pumpkin/src/command/argument_types/pool.rs
Normal file
45
pumpkin/src/command/argument_types/pool.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::command::argument_types::FromStringReader;
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use pumpkin_protocol::java::client::play::SuggestionProviders;
|
||||
use pumpkin_util::identifier::Identifier;
|
||||
|
||||
pub struct PoolNameArgumentType;
|
||||
|
||||
impl ArgumentType for PoolNameArgumentType {
|
||||
type Item = Identifier;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
Identifier::from_reader(reader)
|
||||
}
|
||||
|
||||
fn list_suggestions<'a>(
|
||||
&'a self,
|
||||
_context: &'a CommandContext,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Pin<Box<dyn Future<Output = Suggestions> + Send + 'a>> {
|
||||
let names = pumpkin_world::generation::structure::template::all_pool_names();
|
||||
Box::pin(async move {
|
||||
builder
|
||||
.filter_and_suggest_iter(names.iter().map(|n| format!("minecraft:{n}")))
|
||||
.build()
|
||||
})
|
||||
}
|
||||
|
||||
fn client_side_parser(&'_ self) -> JavaClientArgumentType {
|
||||
JavaClientArgumentType::ResourceLocation
|
||||
}
|
||||
|
||||
fn override_suggestion_providers(&self) -> Option<SuggestionProviders> {
|
||||
Some(SuggestionProviders::AskServer)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<String> {
|
||||
vec!["minecraft:village/plains/houses".to_string()]
|
||||
}
|
||||
}
|
||||
48
pumpkin/src/command/argument_types/structure.rs
Normal file
48
pumpkin/src/command/argument_types/structure.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::command::argument_types::FromStringReader;
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use pumpkin_protocol::java::client::play::SuggestionProviders;
|
||||
use pumpkin_util::identifier::Identifier;
|
||||
|
||||
pub struct StructureNameArgumentType;
|
||||
|
||||
impl ArgumentType for StructureNameArgumentType {
|
||||
type Item = Identifier;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
Identifier::from_reader(reader)
|
||||
}
|
||||
|
||||
fn list_suggestions<'a>(
|
||||
&'a self,
|
||||
_context: &'a CommandContext,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Pin<Box<dyn Future<Output = Suggestions> + Send + 'a>> {
|
||||
let names = pumpkin_world::generation::structure::template::all_structure_names();
|
||||
Box::pin(async move {
|
||||
builder
|
||||
.filter_and_suggest_iter(names.iter().copied())
|
||||
.build()
|
||||
})
|
||||
}
|
||||
|
||||
fn client_side_parser(&'_ self) -> JavaClientArgumentType {
|
||||
JavaClientArgumentType::ResourceLocation
|
||||
}
|
||||
|
||||
fn override_suggestion_providers(&self) -> Option<SuggestionProviders> {
|
||||
Some(SuggestionProviders::AskServer)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<String> {
|
||||
vec![
|
||||
"minecraft:village_plains".to_string(),
|
||||
"minecraft:ancient_city".to_string(),
|
||||
]
|
||||
}
|
||||
}
|
||||
46
pumpkin/src/command/argument_types/template.rs
Normal file
46
pumpkin/src/command/argument_types/template.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::command::argument_types::FromStringReader;
|
||||
use crate::command::argument_types::argument_type::{ArgumentType, JavaClientArgumentType};
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::command_syntax_error::CommandSyntaxError;
|
||||
use crate::command::string_reader::StringReader;
|
||||
use crate::command::suggestion::suggestions::{Suggestions, SuggestionsBuilder};
|
||||
use pumpkin_protocol::java::client::play::SuggestionProviders;
|
||||
use pumpkin_util::identifier::Identifier;
|
||||
|
||||
pub struct TemplateNameArgumentType;
|
||||
|
||||
impl ArgumentType for TemplateNameArgumentType {
|
||||
type Item = String;
|
||||
|
||||
fn parse(&self, reader: &mut StringReader) -> Result<Self::Item, CommandSyntaxError> {
|
||||
let identifier = Identifier::from_reader(reader)?;
|
||||
Ok(identifier.path().to_string())
|
||||
}
|
||||
|
||||
fn list_suggestions<'a>(
|
||||
&'a self,
|
||||
_context: &'a CommandContext,
|
||||
builder: SuggestionsBuilder,
|
||||
) -> Pin<Box<dyn Future<Output = Suggestions> + Send + 'a>> {
|
||||
let names = pumpkin_world::generation::structure::template::all_template_names();
|
||||
Box::pin(async move {
|
||||
builder
|
||||
.filter_and_suggest_iter(names.iter().map(|n| format!("minecraft:{n}")))
|
||||
.build()
|
||||
})
|
||||
}
|
||||
|
||||
fn client_side_parser(&'_ self) -> JavaClientArgumentType {
|
||||
JavaClientArgumentType::ResourceLocation
|
||||
}
|
||||
|
||||
fn override_suggestion_providers(&self) -> Option<SuggestionProviders> {
|
||||
Some(SuggestionProviders::AskServer)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<String> {
|
||||
vec!["igloo/top".to_string()]
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ mod op;
|
||||
mod pardon;
|
||||
mod pardonip;
|
||||
mod particle;
|
||||
mod place;
|
||||
mod playsound;
|
||||
mod plugin;
|
||||
mod plugins;
|
||||
@@ -181,6 +182,7 @@ pub async fn default_dispatcher(
|
||||
help::register(&mut dispatcher, registry);
|
||||
kill::register(&mut dispatcher, registry);
|
||||
op::register(&mut dispatcher, registry);
|
||||
place::register(&mut dispatcher, registry);
|
||||
random::register(&mut dispatcher, registry);
|
||||
list::register(&mut dispatcher, registry);
|
||||
loot::register(&mut dispatcher, registry);
|
||||
|
||||
750
pumpkin/src/command/commands/place.rs
Normal file
750
pumpkin/src/command/commands/place.rs
Normal file
@@ -0,0 +1,750 @@
|
||||
use pumpkin_data::chunk_gen_settings::GenerationSettings;
|
||||
use pumpkin_data::placed_feature::PlacedFeature as PlacedFeatureKey;
|
||||
use pumpkin_data::structures::{Structure, StructureKeys, StructureType};
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_data::{Block, BlockStateId, Mirror, Rotation};
|
||||
use pumpkin_util::identifier::Identifier;
|
||||
use pumpkin_util::math::block_box::BlockBox;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_util::random::hash_block_pos;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use pumpkin_world::generation::proto_chunk::ProtoChunk;
|
||||
use pumpkin_world::world::{BlockAccessor, WorldPortalExt};
|
||||
|
||||
use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal};
|
||||
use crate::command::argument_types::coordinates::block_pos::BlockPosArgumentType;
|
||||
use crate::command::argument_types::core::integer::IntegerArgumentType;
|
||||
use crate::command::argument_types::identifier::IdentifierArgumentType;
|
||||
use crate::command::argument_types::placed_feature::PlacedFeatureNameArgumentType;
|
||||
use crate::command::argument_types::pool::PoolNameArgumentType;
|
||||
use crate::command::argument_types::structure::StructureNameArgumentType;
|
||||
use crate::command::argument_types::template::TemplateNameArgumentType;
|
||||
use crate::command::context::command_context::CommandContext;
|
||||
use crate::command::errors::error_types::CommandErrorType;
|
||||
use crate::command::node::dispatcher::CommandDispatcher;
|
||||
use crate::command::node::{CommandExecutor, CommandExecutorResult};
|
||||
use crate::world::block_placer::WorldBlockPlacer;
|
||||
use pumpkin_world::generation::feature::configured_features::CONFIGURED_FEATURES;
|
||||
use pumpkin_world::generation::feature::placed_features::{Feature, PLACED_FEATURES};
|
||||
use pumpkin_world::generation::structure::structures::StructureGeneratorContext;
|
||||
use pumpkin_world::generation::structure::structures::jigsaw::{
|
||||
PoolElementStructurePiece, place_pool_element_templates,
|
||||
};
|
||||
use pumpkin_world::generation::structure::structures::jigsaw_placement::{
|
||||
DimensionPadding, JigsawPlacement, LiquidSettings, MaxDistance, PoolAliasLookup,
|
||||
};
|
||||
use pumpkin_world::generation::structure::template::BlockPlacer;
|
||||
|
||||
use pumpkin_util::PermissionLvl;
|
||||
use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry};
|
||||
use pumpkin_util::random::RandomGenerator;
|
||||
use pumpkin_util::random::legacy_rand::LegacyRand;
|
||||
|
||||
const DESCRIPTION: &str = "Places a structure template in the world.";
|
||||
const PERMISSION: &str = "minecraft:command.place";
|
||||
|
||||
static TEMPLATE_NOT_FOUND: CommandErrorType<1> = CommandErrorType::new(
|
||||
translation::java::COMMANDS_PLACE_TEMPLATE_INVALID,
|
||||
"commands.place.template.invalid",
|
||||
);
|
||||
static JIGSAW_FAILED: CommandErrorType<1> = CommandErrorType::new(
|
||||
translation::java::COMMANDS_PLACE_JIGSAW_FAILED,
|
||||
"commands.place.jigsaw.failed",
|
||||
);
|
||||
static STRUCTURE_INVALID: CommandErrorType<1> = CommandErrorType::new(
|
||||
translation::java::COMMANDS_PLACE_STRUCTURE_INVALID,
|
||||
"commands.place.structure.invalid",
|
||||
);
|
||||
static FEATURE_INVALID: CommandErrorType<1> = CommandErrorType::new(
|
||||
translation::java::COMMANDS_PLACE_FEATURE_INVALID,
|
||||
"commands.place.feature.invalid",
|
||||
);
|
||||
|
||||
const CHUNK_DIM: i32 = 16;
|
||||
|
||||
/// Clamps a world Y into valid chunk bounds, placing the surface one block below the target.
|
||||
fn ground_y(block_y: i32, chunk_min_y: i32, chunk_height: i32) -> i32 {
|
||||
(block_y - 1).clamp(chunk_min_y, chunk_min_y + chunk_height - 1)
|
||||
}
|
||||
|
||||
/// Vanilla-style chunk-seed derivation used by structure pieces and feature generators.
|
||||
const fn chunk_population_seed(cx: i32, cz: i32, world_seed: u64) -> u64 {
|
||||
(cx as i64)
|
||||
.wrapping_mul(341873128712)
|
||||
.wrapping_add((cz as i64).wrapping_mul(132897987541))
|
||||
.wrapping_add(world_seed as i64) as u64
|
||||
}
|
||||
|
||||
/// Minimal `WorldPortalExt` implementation for synthetic chunk placement.
|
||||
struct CommandBlockRegistry;
|
||||
|
||||
impl WorldPortalExt for CommandBlockRegistry {
|
||||
fn can_place_at(
|
||||
&self,
|
||||
_block: &pumpkin_data::Block,
|
||||
_state: &pumpkin_data::BlockState,
|
||||
_block_accessor: &dyn BlockAccessor,
|
||||
_block_pos: &BlockPos,
|
||||
) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn mirror(
|
||||
&self,
|
||||
block: &pumpkin_data::Block,
|
||||
state_id: BlockStateId,
|
||||
mirror: Mirror,
|
||||
) -> &'static pumpkin_data::BlockState {
|
||||
block.mirror(state_id, mirror)
|
||||
}
|
||||
|
||||
fn rotate(
|
||||
&self,
|
||||
block: &pumpkin_data::Block,
|
||||
state_id: BlockStateId,
|
||||
rotation: Rotation,
|
||||
) -> &'static pumpkin_data::BlockState {
|
||||
block.rotate(state_id, rotation)
|
||||
}
|
||||
|
||||
fn spawn_mobs_for_chunk_generation(
|
||||
&self,
|
||||
_cache: &mut dyn pumpkin_world::generation::proto_chunk::GenerationCache,
|
||||
_biome: &'static pumpkin_data::biome::Biome,
|
||||
_chunk_x: i32,
|
||||
_chunk_z: i32,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
struct PlaceTemplateExecutor;
|
||||
|
||||
impl CommandExecutor for PlaceTemplateExecutor {
|
||||
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
|
||||
Box::pin(async move {
|
||||
let template_name = context.get_argument::<String>("template")?.clone();
|
||||
|
||||
let block_pos =
|
||||
BlockPosArgumentType::get_block_pos(context, "pos").unwrap_or_else(|_| {
|
||||
let p = context.source.position;
|
||||
BlockPos::new(p.x as i32, p.y as i32, p.z as i32)
|
||||
});
|
||||
|
||||
let template_name = template_name
|
||||
.strip_prefix("minecraft:")
|
||||
.unwrap_or(&template_name)
|
||||
.to_string();
|
||||
let Some(template) =
|
||||
pumpkin_world::generation::structure::template::get_template(&template_name)
|
||||
else {
|
||||
return Err(TEMPLATE_NOT_FOUND
|
||||
.create_without_context(TextComponent::text(template_name.clone())));
|
||||
};
|
||||
|
||||
let mut placer = WorldBlockPlacer::new(context.world());
|
||||
pumpkin_world::generation::structure::template::place_template(
|
||||
&mut placer,
|
||||
&template,
|
||||
block_pos.0,
|
||||
(0, 0),
|
||||
Rotation::None,
|
||||
false,
|
||||
false,
|
||||
&[],
|
||||
None,
|
||||
);
|
||||
|
||||
placer.finalize().await;
|
||||
context
|
||||
.world()
|
||||
.queue_block_updates(&placer.changed_positions)
|
||||
.await;
|
||||
context.world().flush_block_updates().await;
|
||||
|
||||
context
|
||||
.source
|
||||
.send_feedback(
|
||||
TextComponent::translate(
|
||||
translation::java::COMMANDS_PLACE_TEMPLATE_SUCCESS,
|
||||
[
|
||||
TextComponent::text(template_name.clone()),
|
||||
TextComponent::text(block_pos.0.x.to_string()),
|
||||
TextComponent::text(block_pos.0.y.to_string()),
|
||||
TextComponent::text(block_pos.0.z.to_string()),
|
||||
],
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct PlaceJigsawExecutor;
|
||||
|
||||
impl CommandExecutor for PlaceJigsawExecutor {
|
||||
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
|
||||
Box::pin(async move {
|
||||
let pool = context.get_argument::<Identifier>("pool")?.to_string();
|
||||
let target = context.get_argument::<Identifier>("target")?.to_string();
|
||||
let max_depth = *context.get_argument::<i32>("max_depth")?;
|
||||
|
||||
let block_pos =
|
||||
BlockPosArgumentType::get_block_pos(context, "pos").unwrap_or_else(|_| {
|
||||
let p = context.source.position;
|
||||
BlockPos::new(p.x as i32, p.y as i32, p.z as i32)
|
||||
});
|
||||
|
||||
let (piece_count, placer) = {
|
||||
let seed = hash_block_pos(block_pos.0.x, block_pos.0.y, block_pos.0.z) as u64;
|
||||
let random = RandomGenerator::Legacy(LegacyRand::from_seed(seed));
|
||||
let world_gen = &context.world().level.world_gen;
|
||||
let settings = GenerationSettings::from_dimension(world_gen.dimension());
|
||||
let mut structure_context = StructureGeneratorContext {
|
||||
seed: seed as i64,
|
||||
chunk_x: 0,
|
||||
chunk_z: 0,
|
||||
random,
|
||||
sea_level: settings.sea_level,
|
||||
min_y: world_gen.dimension().min_y,
|
||||
height_sampler: None,
|
||||
structure_key: None,
|
||||
};
|
||||
|
||||
let position = JigsawPlacement::add_pieces(
|
||||
&mut structure_context,
|
||||
&pool,
|
||||
Some(&target),
|
||||
max_depth,
|
||||
block_pos,
|
||||
false,
|
||||
false,
|
||||
&MaxDistance::new(128),
|
||||
&DimensionPadding::ZERO,
|
||||
LiquidSettings::ApplyWaterlog,
|
||||
&PoolAliasLookup,
|
||||
)
|
||||
.ok_or_else(|| {
|
||||
JIGSAW_FAILED.create_without_context(TextComponent::text(pool.clone()))
|
||||
})?;
|
||||
|
||||
let collector = position.collector.lock().unwrap();
|
||||
let piece_count = collector.pieces.len();
|
||||
|
||||
let mut placer = WorldBlockPlacer::new(context.world());
|
||||
for piece in &collector.pieces {
|
||||
if let Some(jigsaw_piece) =
|
||||
piece.as_any().downcast_ref::<PoolElementStructurePiece>()
|
||||
{
|
||||
place_pool_element_templates(jigsaw_piece, &mut placer, None);
|
||||
}
|
||||
}
|
||||
|
||||
(piece_count, placer)
|
||||
};
|
||||
|
||||
placer.finalize().await;
|
||||
context
|
||||
.world()
|
||||
.queue_block_updates(&placer.changed_positions)
|
||||
.await;
|
||||
context.world().flush_block_updates().await;
|
||||
|
||||
context
|
||||
.source
|
||||
.send_feedback(
|
||||
TextComponent::translate(
|
||||
translation::java::COMMANDS_PLACE_JIGSAW_SUCCESS,
|
||||
[
|
||||
TextComponent::text(piece_count.to_string()),
|
||||
TextComponent::text(block_pos.0.x.to_string()),
|
||||
TextComponent::text(block_pos.0.y.to_string()),
|
||||
TextComponent::text(block_pos.0.z.to_string()),
|
||||
],
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct PlaceStructureExecutor;
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
impl CommandExecutor for PlaceStructureExecutor {
|
||||
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
|
||||
Box::pin(async move {
|
||||
let structure_id = context.get_argument::<Identifier>("structure")?;
|
||||
let structure_name = structure_id.to_string();
|
||||
|
||||
let key = StructureKeys::from_name(&structure_name).ok_or_else(|| {
|
||||
STRUCTURE_INVALID
|
||||
.create_without_context(TextComponent::text(structure_name.clone()))
|
||||
})?;
|
||||
|
||||
let structure = Structure::get(&key);
|
||||
|
||||
let block_pos =
|
||||
BlockPosArgumentType::get_block_pos(context, "pos").unwrap_or_else(|_| {
|
||||
let p = context.source.position;
|
||||
BlockPos::new(p.x as i32, p.y as i32, p.z as i32)
|
||||
});
|
||||
|
||||
let seed = hash_block_pos(block_pos.0.x, block_pos.0.y, block_pos.0.z) as u64;
|
||||
|
||||
let (_piece_count, placer) = {
|
||||
let world_gen = context.world().level.world_gen.clone();
|
||||
let settings = GenerationSettings::from_dimension(world_gen.dimension());
|
||||
|
||||
if structure.structure_type == StructureType::Jigsaw {
|
||||
let pool = structure.start_pool.ok_or_else(|| {
|
||||
STRUCTURE_INVALID
|
||||
.create_without_context(TextComponent::text(structure_name.clone()))
|
||||
})?;
|
||||
let size = structure.size.ok_or_else(|| {
|
||||
STRUCTURE_INVALID
|
||||
.create_without_context(TextComponent::text(structure_name.clone()))
|
||||
})?;
|
||||
|
||||
let random = RandomGenerator::Legacy(LegacyRand::from_seed(seed));
|
||||
|
||||
let position = JigsawPlacement::add_pieces(
|
||||
&mut StructureGeneratorContext {
|
||||
seed: seed as i64,
|
||||
chunk_x: block_pos.0.x >> 4,
|
||||
chunk_z: block_pos.0.z >> 4,
|
||||
random,
|
||||
sea_level: settings.sea_level,
|
||||
min_y: world_gen.dimension().min_y,
|
||||
height_sampler: None,
|
||||
structure_key: Some(key),
|
||||
},
|
||||
pool,
|
||||
structure.start_jigsaw_name,
|
||||
size,
|
||||
block_pos,
|
||||
structure.use_expansion_hack.unwrap_or(false),
|
||||
structure.project_start_to_heightmap.is_some(),
|
||||
&MaxDistance::new(structure.max_distance_from_center.unwrap_or(128)),
|
||||
&DimensionPadding::ZERO,
|
||||
LiquidSettings::ApplyWaterlog,
|
||||
&PoolAliasLookup,
|
||||
)
|
||||
.ok_or_else(|| {
|
||||
JIGSAW_FAILED
|
||||
.create_without_context(TextComponent::text(structure_name.clone()))
|
||||
})?;
|
||||
|
||||
let collector = position.collector.lock().unwrap();
|
||||
let piece_count = collector.pieces.len();
|
||||
|
||||
let mut placer = WorldBlockPlacer::new(context.world());
|
||||
for piece in &collector.pieces {
|
||||
if let Some(jigsaw_piece) =
|
||||
piece.as_any().downcast_ref::<PoolElementStructurePiece>()
|
||||
{
|
||||
place_pool_element_templates(jigsaw_piece, &mut placer, None);
|
||||
}
|
||||
}
|
||||
|
||||
(piece_count, placer)
|
||||
} else {
|
||||
let random = RandomGenerator::Legacy(LegacyRand::from_seed(seed));
|
||||
|
||||
let position =
|
||||
pumpkin_world::generation::structure::generate_structure_position(
|
||||
&key,
|
||||
structure,
|
||||
StructureGeneratorContext {
|
||||
seed: seed as i64,
|
||||
chunk_x: block_pos.0.x >> 4,
|
||||
chunk_z: block_pos.0.z >> 4,
|
||||
random,
|
||||
sea_level: settings.sea_level,
|
||||
min_y: world_gen.dimension().min_y,
|
||||
height_sampler: None,
|
||||
structure_key: Some(key),
|
||||
},
|
||||
)
|
||||
.ok_or_else(|| {
|
||||
STRUCTURE_INVALID
|
||||
.create_without_context(TextComponent::text(structure_name.clone()))
|
||||
})?;
|
||||
|
||||
let mut collector = position.collector.lock().unwrap();
|
||||
let piece_count = collector.pieces.len();
|
||||
|
||||
let mut placer = WorldBlockPlacer::new(context.world());
|
||||
|
||||
for piece in &collector.pieces {
|
||||
if let Some(jigsaw_piece) =
|
||||
piece.as_any().downcast_ref::<PoolElementStructurePiece>()
|
||||
{
|
||||
place_pool_element_templates(jigsaw_piece, &mut placer, None);
|
||||
}
|
||||
}
|
||||
|
||||
let has_non_jigsaw = collector.pieces.iter().any(|p| {
|
||||
p.as_any()
|
||||
.downcast_ref::<PoolElementStructurePiece>()
|
||||
.is_none()
|
||||
});
|
||||
|
||||
if has_non_jigsaw {
|
||||
let reg = CommandBlockRegistry;
|
||||
|
||||
let mut min_x = i32::MAX;
|
||||
let mut min_z = i32::MAX;
|
||||
let mut max_x = i32::MIN;
|
||||
let mut max_z = i32::MIN;
|
||||
|
||||
for piece in &collector.pieces {
|
||||
if piece
|
||||
.as_any()
|
||||
.downcast_ref::<PoolElementStructurePiece>()
|
||||
.is_none()
|
||||
{
|
||||
let bb = piece.bounding_box();
|
||||
min_x = min_x.min(bb.min.x);
|
||||
min_z = min_z.min(bb.min.z);
|
||||
max_x = max_x.max(bb.max.x);
|
||||
max_z = max_z.max(bb.max.z);
|
||||
}
|
||||
}
|
||||
|
||||
let start_cx = min_x >> 4;
|
||||
let end_cx = max_x >> 4;
|
||||
let start_cz = min_z >> 4;
|
||||
let end_cz = max_z >> 4;
|
||||
|
||||
for cx in start_cx..=end_cx {
|
||||
for cz in start_cz..=end_cz {
|
||||
let mut chunk = ProtoChunk::new(cx, cz, &world_gen);
|
||||
let chunk_min_y = chunk.bottom_y() as i32;
|
||||
let chunk_height = chunk.height() as i32;
|
||||
let surface_y = ground_y(block_pos.0.y, chunk_min_y, chunk_height);
|
||||
|
||||
// Seed heightmaps and fill below-surface with stone so
|
||||
// pieces that carve through solid terrain have material
|
||||
let ground = surface_y as i16;
|
||||
chunk.flat_surface_height_map = [ground; 256];
|
||||
chunk.flat_ocean_floor_height_map = [ground; 256];
|
||||
chunk.flat_motion_blocking_height_map = [ground; 256];
|
||||
chunk.flat_motion_blocking_no_leaves_height_map = [ground; 256];
|
||||
|
||||
for x in 0..CHUNK_DIM {
|
||||
for z in 0..CHUNK_DIM {
|
||||
for y in chunk_min_y..surface_y {
|
||||
chunk.set_block_state(
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
Block::STONE.default_state,
|
||||
);
|
||||
}
|
||||
chunk.set_block_state(
|
||||
x,
|
||||
surface_y,
|
||||
z,
|
||||
Block::GRASS_BLOCK.default_state,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let chunk_box = BlockBox::new(
|
||||
cx << 4,
|
||||
chunk_min_y,
|
||||
cz << 4,
|
||||
(cx << 4) + CHUNK_DIM - 1,
|
||||
chunk_min_y + chunk_height - 1,
|
||||
(cz << 4) + CHUNK_DIM - 1,
|
||||
);
|
||||
|
||||
let snapshot =
|
||||
snapshot_blocks(&chunk, cx, cz, chunk_min_y, chunk_height);
|
||||
|
||||
let mut rng = RandomGenerator::Legacy(LegacyRand::from_seed(
|
||||
chunk_population_seed(cx, cz, seed),
|
||||
));
|
||||
|
||||
for piece in &mut collector.pieces {
|
||||
if piece.bounding_box().intersects(&chunk_box) {
|
||||
piece.place(
|
||||
&mut chunk,
|
||||
®,
|
||||
&mut rng,
|
||||
seed as i64,
|
||||
&chunk_box,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
apply_delta(
|
||||
&chunk,
|
||||
&snapshot,
|
||||
cx,
|
||||
cz,
|
||||
chunk_min_y,
|
||||
chunk_height,
|
||||
&mut placer,
|
||||
);
|
||||
|
||||
for nbt in chunk.pending_block_entities.drain(..) {
|
||||
placer.block_entity_nbts.push(nbt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(piece_count, placer)
|
||||
}
|
||||
};
|
||||
|
||||
placer.finalize().await;
|
||||
context
|
||||
.world()
|
||||
.queue_block_updates(&placer.changed_positions)
|
||||
.await;
|
||||
context.world().flush_block_updates().await;
|
||||
|
||||
context
|
||||
.source
|
||||
.send_feedback(
|
||||
TextComponent::translate(
|
||||
translation::java::COMMANDS_PLACE_STRUCTURE_SUCCESS,
|
||||
[
|
||||
TextComponent::text(structure_name),
|
||||
TextComponent::text(block_pos.0.x.to_string()),
|
||||
TextComponent::text(block_pos.0.y.to_string()),
|
||||
TextComponent::text(block_pos.0.z.to_string()),
|
||||
],
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct PlaceFeatureExecutor;
|
||||
|
||||
impl CommandExecutor for PlaceFeatureExecutor {
|
||||
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
|
||||
Box::pin(async move {
|
||||
let feature_id = context.get_argument::<Identifier>("feature")?;
|
||||
let feature_name = feature_id.to_string();
|
||||
|
||||
let key = PlacedFeatureKey::from_name(&feature_name).ok_or_else(|| {
|
||||
FEATURE_INVALID.create_without_context(TextComponent::text(feature_name.clone()))
|
||||
})?;
|
||||
|
||||
let placed = PLACED_FEATURES.get(&key).ok_or_else(|| {
|
||||
FEATURE_INVALID.create_without_context(TextComponent::text(feature_name.clone()))
|
||||
})?;
|
||||
|
||||
let configured = match &placed.feature {
|
||||
Feature::Named(name) => CONFIGURED_FEATURES.get(name).ok_or_else(|| {
|
||||
FEATURE_INVALID
|
||||
.create_without_context(TextComponent::text(feature_name.clone()))
|
||||
})?,
|
||||
Feature::Inlined(f) => f.as_ref(),
|
||||
};
|
||||
|
||||
let block_pos =
|
||||
BlockPosArgumentType::get_block_pos(context, "pos").unwrap_or_else(|_| {
|
||||
let p = context.source.position;
|
||||
BlockPos::new(p.x as i32, p.y as i32, p.z as i32)
|
||||
});
|
||||
|
||||
let world_gen = context.world().level.world_gen.clone();
|
||||
let cx = block_pos.0.x >> 4;
|
||||
let cz = block_pos.0.z >> 4;
|
||||
let mut chunk = ProtoChunk::new(cx, cz, &world_gen);
|
||||
let bottom_y = chunk.bottom_y();
|
||||
let height = chunk.height();
|
||||
let chunk_min_y = bottom_y as i32;
|
||||
let chunk_height = height as i32;
|
||||
let surface_y = ground_y(block_pos.0.y, chunk_min_y, chunk_height);
|
||||
|
||||
let ground = surface_y as i16;
|
||||
chunk.flat_surface_height_map = [ground; 256];
|
||||
chunk.flat_ocean_floor_height_map = [ground; 256];
|
||||
chunk.flat_motion_blocking_height_map = [ground; 256];
|
||||
chunk.flat_motion_blocking_no_leaves_height_map = [ground; 256];
|
||||
|
||||
// Feature generation runs against a synthetic solid terrain, not the
|
||||
// live chunk. Features that depend on existing air, caves, or fluids
|
||||
// may therefore differ from normal world generation.
|
||||
for x in 0..CHUNK_DIM {
|
||||
for z in 0..CHUNK_DIM {
|
||||
for y in chunk_min_y..surface_y {
|
||||
chunk.set_block_state(x, y, z, Block::STONE.default_state);
|
||||
}
|
||||
chunk.set_block_state(x, surface_y, z, Block::GRASS_BLOCK.default_state);
|
||||
}
|
||||
}
|
||||
|
||||
let snapshot = snapshot_blocks(&chunk, cx, cz, chunk_min_y, chunk_height);
|
||||
|
||||
let reg = CommandBlockRegistry;
|
||||
let seed = hash_block_pos(block_pos.0.x, block_pos.0.y, block_pos.0.z) as u64;
|
||||
let mut random = RandomGenerator::Legacy(LegacyRand::from_seed(seed));
|
||||
|
||||
configured.generate(
|
||||
&mut chunk,
|
||||
®,
|
||||
bottom_y,
|
||||
height,
|
||||
key,
|
||||
&mut random,
|
||||
block_pos,
|
||||
);
|
||||
|
||||
let mut placer = WorldBlockPlacer::new(context.world());
|
||||
apply_delta(
|
||||
&chunk,
|
||||
&snapshot,
|
||||
cx,
|
||||
cz,
|
||||
chunk_min_y,
|
||||
chunk_height,
|
||||
&mut placer,
|
||||
);
|
||||
|
||||
for nbt in chunk.pending_block_entities.drain(..) {
|
||||
placer.block_entity_nbts.push(nbt);
|
||||
}
|
||||
|
||||
placer.finalize().await;
|
||||
context
|
||||
.world()
|
||||
.queue_block_updates(&placer.changed_positions)
|
||||
.await;
|
||||
context.world().flush_block_updates().await;
|
||||
|
||||
context
|
||||
.source
|
||||
.send_feedback(
|
||||
TextComponent::translate(
|
||||
translation::java::COMMANDS_PLACE_FEATURE_SUCCESS,
|
||||
[
|
||||
TextComponent::text(feature_name),
|
||||
TextComponent::text(block_pos.0.x.to_string()),
|
||||
TextComponent::text(block_pos.0.y.to_string()),
|
||||
TextComponent::text(block_pos.0.z.to_string()),
|
||||
],
|
||||
),
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn snapshot_blocks(
|
||||
chunk: &ProtoChunk,
|
||||
cx: i32,
|
||||
cz: i32,
|
||||
min_y: i32,
|
||||
height: i32,
|
||||
) -> Vec<BlockStateId> {
|
||||
let mut snap = Vec::with_capacity((CHUNK_DIM * height * CHUNK_DIM) as usize);
|
||||
for x in 0..CHUNK_DIM {
|
||||
for y in min_y..min_y + height {
|
||||
for z in 0..CHUNK_DIM {
|
||||
snap.push(chunk.get_block_state(&Vector3::new((cx << 4) + x, y, (cz << 4) + z)));
|
||||
}
|
||||
}
|
||||
}
|
||||
snap
|
||||
}
|
||||
|
||||
fn apply_delta(
|
||||
chunk: &ProtoChunk,
|
||||
snapshot: &[BlockStateId],
|
||||
cx: i32,
|
||||
cz: i32,
|
||||
min_y: i32,
|
||||
height: i32,
|
||||
placer: &mut WorldBlockPlacer<'_>,
|
||||
) {
|
||||
// The synthetic chunk is seeded with terrain so structure pieces can carve
|
||||
// against it. Applying the delta therefore intentionally propagates air
|
||||
// changes too, which can replace real terrain where a piece carved that
|
||||
// synthetic stone.
|
||||
let mut idx = 0usize;
|
||||
for x in 0..CHUNK_DIM {
|
||||
for y in min_y..min_y + height {
|
||||
for z in 0..CHUNK_DIM {
|
||||
let pos = Vector3::new((cx << 4) + x, y, (cz << 4) + z);
|
||||
let new_id = chunk.get_block_state(&pos);
|
||||
if new_id != snapshot[idx] {
|
||||
placer.set_block_state(&pos, new_id.to_state());
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(dispatcher: &mut CommandDispatcher, registry: &mut PermissionRegistry) {
|
||||
registry.register_permission_or_panic(Permission::new(
|
||||
PERMISSION,
|
||||
DESCRIPTION,
|
||||
PermissionDefault::Op(PermissionLvl::Two),
|
||||
));
|
||||
|
||||
dispatcher.register(
|
||||
command("place", DESCRIPTION)
|
||||
.requires(PERMISSION)
|
||||
.then(
|
||||
literal("template").then(
|
||||
argument("template", TemplateNameArgumentType)
|
||||
.executes(PlaceTemplateExecutor)
|
||||
.then(
|
||||
argument("pos", BlockPosArgumentType).executes(PlaceTemplateExecutor),
|
||||
),
|
||||
),
|
||||
)
|
||||
.then(
|
||||
literal("jigsaw").then(
|
||||
argument("pool", PoolNameArgumentType).then(
|
||||
argument("target", IdentifierArgumentType).then(
|
||||
argument("max_depth", IntegerArgumentType::new(1, 20))
|
||||
.executes(PlaceJigsawExecutor)
|
||||
.then(
|
||||
argument("pos", BlockPosArgumentType)
|
||||
.executes(PlaceJigsawExecutor),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.then(
|
||||
literal("structure").then(
|
||||
argument("structure", StructureNameArgumentType)
|
||||
.executes(PlaceStructureExecutor)
|
||||
.then(
|
||||
argument("pos", BlockPosArgumentType).executes(PlaceStructureExecutor),
|
||||
),
|
||||
),
|
||||
)
|
||||
.then(
|
||||
literal("feature").then(
|
||||
argument("feature", PlacedFeatureNameArgumentType)
|
||||
.executes(PlaceFeatureExecutor)
|
||||
.then(argument("pos", BlockPosArgumentType).executes(PlaceFeatureExecutor)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
51
pumpkin/src/world/block_placer.rs
Normal file
51
pumpkin/src/world/block_placer.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use pumpkin_data::{BlockState, BlockStateId};
|
||||
use pumpkin_nbt::compound::NbtCompound;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_world::generation::structure::template::BlockPlacer;
|
||||
use pumpkin_world::level::Level;
|
||||
|
||||
use crate::world::World;
|
||||
|
||||
pub struct WorldBlockPlacer<'a> {
|
||||
world: &'a World,
|
||||
pub block_entity_nbts: Vec<NbtCompound>,
|
||||
pub changed_positions: Vec<(BlockPos, BlockStateId)>,
|
||||
}
|
||||
|
||||
impl<'a> WorldBlockPlacer<'a> {
|
||||
#[must_use]
|
||||
pub const fn new(world: &'a World) -> Self {
|
||||
Self {
|
||||
world,
|
||||
block_entity_nbts: Vec::new(),
|
||||
changed_positions: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_async)]
|
||||
pub async fn finalize(&self) {
|
||||
for nbt in &self.block_entity_nbts {
|
||||
if let Some(block_entity) = crate::block::entities::block_entity_from_nbt(nbt) {
|
||||
self.world.add_block_entity(block_entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockPlacer for WorldBlockPlacer<'_> {
|
||||
fn get_block_state(&self, pos: &Vector3<i32>) -> BlockStateId {
|
||||
self.world
|
||||
.get_block_state_id(&BlockPos::new(pos.x, pos.y, pos.z))
|
||||
}
|
||||
|
||||
fn set_block_state(&mut self, pos: &Vector3<i32>, state: &BlockState) {
|
||||
let block_pos = BlockPos::new(pos.x, pos.y, pos.z);
|
||||
Level::set_block_state(&self.world.level, &block_pos, state.id);
|
||||
self.changed_positions.push((block_pos, state.id));
|
||||
}
|
||||
|
||||
fn add_block_entity(&mut self, nbt: NbtCompound) {
|
||||
self.block_entity_nbts.push(nbt);
|
||||
}
|
||||
}
|
||||
@@ -143,6 +143,7 @@ use scoreboard::Scoreboard;
|
||||
use time::LevelTime;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
pub mod block_placer;
|
||||
pub mod border;
|
||||
pub mod bossbar;
|
||||
pub mod custom_bossbar;
|
||||
@@ -1038,6 +1039,16 @@ impl World {
|
||||
.insert(position, block_state_id);
|
||||
}
|
||||
|
||||
/// Queues block state changes for broadcast to nearby players.
|
||||
///
|
||||
/// Call [`flush_block_updates`](Self::flush_block_updates) afterward to send the packets.
|
||||
pub async fn queue_block_updates(&self, changes: &[(BlockPos, BlockStateId)]) {
|
||||
let mut guard = self.unsent_block_changes.lock().await;
|
||||
for (pos, state_id) in changes {
|
||||
guard.insert(*pos, *state_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn flush_block_updates(&self) {
|
||||
let mut block_state_updates_by_chunk_section: HashMap<
|
||||
Vector3<i32>,
|
||||
|
||||
Reference in New Issue
Block a user