mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
* 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
204 lines
6.7 KiB
Rust
204 lines
6.7 KiB
Rust
use std::env;
|
|
use std::fmt::Write;
|
|
use std::fs;
|
|
use std::path::Path;
|
|
|
|
fn main() {
|
|
let out_dir = env::var_os("OUT_DIR").unwrap();
|
|
let dest_path = Path::new(&out_dir).join("template_embeddings.rs");
|
|
|
|
let mut code = String::from(
|
|
"
|
|
#[allow(clippy::too_many_lines)]
|
|
#[allow(clippy::match_same_arms)]
|
|
#[must_use]
|
|
pub fn get_template_bytes(path: &str) -> Option<&'static [u8]> {\n match path {\n",
|
|
);
|
|
let mut pool_code = String::from(
|
|
"
|
|
#[allow(clippy::too_many_lines)]
|
|
#[allow(clippy::match_same_arms)]
|
|
#[must_use]
|
|
pub fn get_pool_elements(pool_id: &str) -> Option<&'static [&'static str]> {\n match pool_id {\n",
|
|
);
|
|
let mut template_pool_json_code = String::from(
|
|
"
|
|
#[allow(clippy::too_many_lines)]
|
|
#[allow(clippy::match_same_arms)]
|
|
#[must_use]
|
|
pub fn get_template_pool_json(path: &str) -> Option<&'static str> {\n match path {\n",
|
|
);
|
|
let mut processor_list_json_code = String::from(
|
|
"
|
|
#[allow(clippy::too_many_lines)]
|
|
#[allow(clippy::match_same_arms)]
|
|
#[must_use]
|
|
pub fn get_processor_list_json(path: &str) -> Option<&'static str> {\n match path {\n",
|
|
);
|
|
|
|
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,
|
|
&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(&["
|
|
);
|
|
for element in elements {
|
|
let _ = writeln!(pool_code, " \"{element}\",");
|
|
}
|
|
pool_code.push_str(" ]),\n");
|
|
}
|
|
}
|
|
|
|
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");
|
|
|
|
let worldgen_dir = Path::new(&manifest_dir).join("assets/worldgen");
|
|
process_json_dir(
|
|
&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");
|
|
processor_list_json_code.push_str(" _ => None,\n");
|
|
processor_list_json_code.push_str(" }\n}\n");
|
|
|
|
fs::write(
|
|
&dest_path,
|
|
format!("{code}\n{pool_code}\n{template_pool_json_code}\n{processor_list_json_code}"),
|
|
)
|
|
.unwrap();
|
|
println!("cargo:rerun-if-changed=assets/structures");
|
|
println!("cargo:rerun-if-changed=assets/worldgen");
|
|
}
|
|
|
|
fn process_dir(
|
|
dir: &Path,
|
|
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();
|
|
let path = entry.path();
|
|
let name = entry.file_name().into_string().unwrap();
|
|
|
|
if path.is_dir() {
|
|
let new_prefix = if prefix.is_empty() {
|
|
name
|
|
} else {
|
|
format!("{prefix}/{name}")
|
|
};
|
|
process_dir(&path, &new_prefix, code, pools, names);
|
|
} else if path
|
|
.extension()
|
|
.and_then(|s| s.to_str())
|
|
.is_some_and(|ext| ext.eq_ignore_ascii_case("nbt"))
|
|
{
|
|
let stem = path.file_stem().unwrap().to_string_lossy();
|
|
let template_name = if prefix.is_empty() {
|
|
stem.to_string()
|
|
} else {
|
|
format!("{prefix}/{stem}")
|
|
};
|
|
let abs_path = path.canonicalize().unwrap();
|
|
let _ = writeln!(
|
|
code,
|
|
" \"{template_name}\" => Some(include_bytes!(r#\"{abs}\"#)),",
|
|
template_name = template_name,
|
|
abs = abs_path.display()
|
|
);
|
|
names.push(template_name.clone());
|
|
|
|
if !prefix.is_empty() {
|
|
pools
|
|
.entry(prefix.to_string())
|
|
.or_default()
|
|
.push(template_name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn process_json_dir(dir: &Path, prefix: &str, code: &mut String, names: &mut Vec<String>) {
|
|
if !dir.exists() {
|
|
return;
|
|
}
|
|
|
|
let mut entries = fs::read_dir(dir)
|
|
.unwrap()
|
|
.map(Result::unwrap)
|
|
.collect::<Vec<_>>();
|
|
entries.sort_by_key(std::fs::DirEntry::file_name);
|
|
|
|
for entry in entries {
|
|
let path = entry.path();
|
|
let name = entry.file_name().into_string().unwrap();
|
|
if path.is_dir() {
|
|
let new_prefix = if prefix.is_empty() {
|
|
name
|
|
} else {
|
|
format!("{prefix}/{name}")
|
|
};
|
|
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,
|
|
" \"minecraft:{id}\" | \"{id}\" => Some(include_str!(r#\"{abs}\"#)),",
|
|
id = id,
|
|
abs = abs_path.display()
|
|
);
|
|
}
|
|
}
|
|
}
|