feat(command): add fillbiome command
This commit is contained in:
parent
06fa1db1d7
commit
37bed1c8b5
@ -226,6 +226,7 @@ pub fn build() -> TokenStream {
|
|||||||
let mut variants = TokenStream::new();
|
let mut variants = TokenStream::new();
|
||||||
let mut name_to_type = TokenStream::new();
|
let mut name_to_type = TokenStream::new();
|
||||||
let mut id_to_type = TokenStream::new();
|
let mut id_to_type = TokenStream::new();
|
||||||
|
let mut all_variants = TokenStream::new();
|
||||||
|
|
||||||
for (name, biome) in biomes {
|
for (name, biome) in biomes {
|
||||||
let full_name = format!("minecraft:{name}");
|
let full_name = format!("minecraft:{name}");
|
||||||
@ -359,6 +360,7 @@ pub fn build() -> TokenStream {
|
|||||||
|
|
||||||
name_to_type.extend(quote! { #name => Some(&Self::#format_name), });
|
name_to_type.extend(quote! { #name => Some(&Self::#format_name), });
|
||||||
id_to_type.extend(quote! { #index => Some(&Self::#format_name), });
|
id_to_type.extend(quote! { #index => Some(&Self::#format_name), });
|
||||||
|
all_variants.extend(quote! { &Self::#format_name, });
|
||||||
}
|
}
|
||||||
|
|
||||||
let overworld_tree = biome_trees.overworld.into_token_stream();
|
let overworld_tree = biome_trees.overworld.into_token_stream();
|
||||||
@ -464,6 +466,8 @@ pub fn build() -> TokenStream {
|
|||||||
impl Biome {
|
impl Biome {
|
||||||
#variants
|
#variants
|
||||||
|
|
||||||
|
pub const ALL: &'static [&'static Self] = &[#all_variants];
|
||||||
|
|
||||||
pub fn from_name(name: &str) -> Option<&'static Self> {
|
pub fn from_name(name: &str) -> Option<&'static Self> {
|
||||||
match name {
|
match name {
|
||||||
#name_to_type
|
#name_to_type
|
||||||
|
|||||||
@ -8869,6 +8869,74 @@ impl Biome {
|
|||||||
},
|
},
|
||||||
spawn_costs: phf::phf_map! {},
|
spawn_costs: phf::phf_map! {},
|
||||||
};
|
};
|
||||||
|
pub const ALL: &'static [&'static Self] = &[
|
||||||
|
&Self::BADLANDS,
|
||||||
|
&Self::BAMBOO_JUNGLE,
|
||||||
|
&Self::BASALT_DELTAS,
|
||||||
|
&Self::BEACH,
|
||||||
|
&Self::BIRCH_FOREST,
|
||||||
|
&Self::CHERRY_GROVE,
|
||||||
|
&Self::COLD_OCEAN,
|
||||||
|
&Self::CRIMSON_FOREST,
|
||||||
|
&Self::DARK_FOREST,
|
||||||
|
&Self::DEEP_COLD_OCEAN,
|
||||||
|
&Self::DEEP_DARK,
|
||||||
|
&Self::DEEP_FROZEN_OCEAN,
|
||||||
|
&Self::DEEP_LUKEWARM_OCEAN,
|
||||||
|
&Self::DEEP_OCEAN,
|
||||||
|
&Self::DESERT,
|
||||||
|
&Self::DRIPSTONE_CAVES,
|
||||||
|
&Self::END_BARRENS,
|
||||||
|
&Self::END_HIGHLANDS,
|
||||||
|
&Self::END_MIDLANDS,
|
||||||
|
&Self::ERODED_BADLANDS,
|
||||||
|
&Self::FLOWER_FOREST,
|
||||||
|
&Self::FOREST,
|
||||||
|
&Self::FROZEN_OCEAN,
|
||||||
|
&Self::FROZEN_PEAKS,
|
||||||
|
&Self::FROZEN_RIVER,
|
||||||
|
&Self::GROVE,
|
||||||
|
&Self::ICE_SPIKES,
|
||||||
|
&Self::JAGGED_PEAKS,
|
||||||
|
&Self::JUNGLE,
|
||||||
|
&Self::LUKEWARM_OCEAN,
|
||||||
|
&Self::LUSH_CAVES,
|
||||||
|
&Self::MANGROVE_SWAMP,
|
||||||
|
&Self::MEADOW,
|
||||||
|
&Self::MUSHROOM_FIELDS,
|
||||||
|
&Self::NETHER_WASTES,
|
||||||
|
&Self::OCEAN,
|
||||||
|
&Self::OLD_GROWTH_BIRCH_FOREST,
|
||||||
|
&Self::OLD_GROWTH_PINE_TAIGA,
|
||||||
|
&Self::OLD_GROWTH_SPRUCE_TAIGA,
|
||||||
|
&Self::PALE_GARDEN,
|
||||||
|
&Self::PLAINS,
|
||||||
|
&Self::RIVER,
|
||||||
|
&Self::SAVANNA,
|
||||||
|
&Self::SAVANNA_PLATEAU,
|
||||||
|
&Self::SMALL_END_ISLANDS,
|
||||||
|
&Self::SNOWY_BEACH,
|
||||||
|
&Self::SNOWY_PLAINS,
|
||||||
|
&Self::SNOWY_SLOPES,
|
||||||
|
&Self::SNOWY_TAIGA,
|
||||||
|
&Self::SOUL_SAND_VALLEY,
|
||||||
|
&Self::SPARSE_JUNGLE,
|
||||||
|
&Self::STONY_PEAKS,
|
||||||
|
&Self::STONY_SHORE,
|
||||||
|
&Self::SULFUR_CAVES,
|
||||||
|
&Self::SUNFLOWER_PLAINS,
|
||||||
|
&Self::SWAMP,
|
||||||
|
&Self::TAIGA,
|
||||||
|
&Self::THE_END,
|
||||||
|
&Self::THE_VOID,
|
||||||
|
&Self::WARM_OCEAN,
|
||||||
|
&Self::WARPED_FOREST,
|
||||||
|
&Self::WINDSWEPT_FOREST,
|
||||||
|
&Self::WINDSWEPT_GRAVELLY_HILLS,
|
||||||
|
&Self::WINDSWEPT_HILLS,
|
||||||
|
&Self::WINDSWEPT_SAVANNA,
|
||||||
|
&Self::WOODED_BADLANDS,
|
||||||
|
];
|
||||||
pub fn from_name(name: &str) -> Option<&'static Self> {
|
pub fn from_name(name: &str) -> Option<&'static Self> {
|
||||||
match name {
|
match name {
|
||||||
"badlands" => Some(&Self::BADLANDS),
|
"badlands" => Some(&Self::BADLANDS),
|
||||||
|
|||||||
@ -522,7 +522,7 @@ impl ChunkSections {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_relative_biome(
|
pub fn set_relative_biome(
|
||||||
&mut self,
|
&self,
|
||||||
relative_x: usize,
|
relative_x: usize,
|
||||||
relative_y: usize,
|
relative_y: usize,
|
||||||
relative_z: usize,
|
relative_z: usize,
|
||||||
|
|||||||
@ -13,12 +13,16 @@ use std::pin::Pin;
|
|||||||
use std::string::ToString;
|
use std::string::ToString;
|
||||||
|
|
||||||
pub static ADVANCEMENT_REGISTRY: Identifier = Identifier::vanilla_static("advancement");
|
pub static ADVANCEMENT_REGISTRY: Identifier = Identifier::vanilla_static("advancement");
|
||||||
|
pub static BIOME_REGISTRY: Identifier = Identifier::vanilla_static("worldgen/biome");
|
||||||
|
|
||||||
pub const ERROR_INVALID_ADVANCEMENT: CommandErrorType<1> = CommandErrorType::new(
|
pub const ERROR_INVALID_ADVANCEMENT: CommandErrorType<1> = CommandErrorType::new(
|
||||||
translation::java::ADVANCEMENT_ADVANCEMENTNOTFOUND,
|
translation::java::ADVANCEMENT_ADVANCEMENTNOTFOUND,
|
||||||
translation::java::ADVANCEMENT_ADVANCEMENTNOTFOUND,
|
translation::java::ADVANCEMENT_ADVANCEMENTNOTFOUND,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
pub const ERROR_INVALID_BIOME: CommandErrorType<1> =
|
||||||
|
CommandErrorType::new("commands.fillbiome.invalid", "commands.fillbiome.invalid");
|
||||||
|
|
||||||
/// Represents an argument type used to get a resource key from an identifier.
|
/// Represents an argument type used to get a resource key from an identifier.
|
||||||
///
|
///
|
||||||
/// if you want an [`Advancement`] put the [`ADVANCEMENT_REGISTRY`]
|
/// if you want an [`Advancement`] put the [`ADVANCEMENT_REGISTRY`]
|
||||||
@ -54,6 +58,13 @@ impl ArgumentType for ResourceKeyArgument {
|
|||||||
.filter_and_suggest_iter(advancements.iter().map(ToString::to_string))
|
.filter_and_suggest_iter(advancements.iter().map(ToString::to_string))
|
||||||
.build()
|
.build()
|
||||||
})
|
})
|
||||||
|
} else if self.0 == BIOME_REGISTRY {
|
||||||
|
Box::pin(async move {
|
||||||
|
let biomes = pumpkin_data::biome::Biome::ALL
|
||||||
|
.iter()
|
||||||
|
.map(|biome| format!("minecraft:{}", biome.registry_id));
|
||||||
|
suggestions_builder.filter_and_suggest_iter(biomes).build()
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
Box::pin(async move { Suggestions::empty() })
|
Box::pin(async move { Suggestions::empty() })
|
||||||
}
|
}
|
||||||
@ -94,6 +105,20 @@ impl ResourceKeyArgument {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a [`CommandContext`]'s parsed resource key argument as a [`Biome`].
|
||||||
|
pub fn get_biome(
|
||||||
|
context: &CommandContext,
|
||||||
|
name: &str,
|
||||||
|
) -> Result<&'static pumpkin_data::biome::Biome, CommandSyntaxError> {
|
||||||
|
let resource_key: &ResourceKey =
|
||||||
|
Self::get_registry_key(context, name, &BIOME_REGISTRY, &ERROR_INVALID_BIOME)?;
|
||||||
|
let path = resource_key.identifier.path();
|
||||||
|
pumpkin_data::biome::Biome::from_name(path).ok_or_else(|| {
|
||||||
|
ERROR_INVALID_BIOME
|
||||||
|
.create_without_context(TextComponent::text(resource_key.identifier.to_string()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a [`CommandContext`]'s parsed resource key argument in the form of a [`ResourceKey`].
|
/// Returns a [`CommandContext`]'s parsed resource key argument in the form of a [`ResourceKey`].
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
|
|||||||
182
pumpkin/src/command/commands/fillbiome.rs
Normal file
182
pumpkin/src/command/commands/fillbiome.rs
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
use crate::command::argument_builder::{ArgumentBuilder, argument, command};
|
||||||
|
use crate::command::argument_types::coordinates::block_pos::{
|
||||||
|
BlockPosArgumentType, OUT_OF_BOUNDS_ERROR_TYPE,
|
||||||
|
};
|
||||||
|
use crate::command::argument_types::resource_key::{BIOME_REGISTRY, ResourceKeyArgument};
|
||||||
|
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 pumpkin_data::translation;
|
||||||
|
use pumpkin_protocol::java::client::play::CChunkData;
|
||||||
|
use pumpkin_util::PermissionLvl;
|
||||||
|
use pumpkin_util::math::vector2::Vector2;
|
||||||
|
use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry};
|
||||||
|
use pumpkin_util::text::TextComponent;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
const DESCRIPTION: &str = "Changes biomes of an area.";
|
||||||
|
const PERMISSION: &str = "minecraft:command.fillbiome";
|
||||||
|
|
||||||
|
const MAX_BIOME_BLOCKS: i64 = 32768;
|
||||||
|
|
||||||
|
static ERROR_TOOBIG: CommandErrorType<2> = CommandErrorType::new(
|
||||||
|
translation::java::COMMANDS_FILLBIOME_TOOBIG,
|
||||||
|
translation::java::COMMANDS_FILLBIOME_TOOBIG,
|
||||||
|
);
|
||||||
|
|
||||||
|
struct FillBiomeExecutor {
|
||||||
|
has_replace: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandExecutor for FillBiomeExecutor {
|
||||||
|
#[expect(clippy::too_many_lines)]
|
||||||
|
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let from_pos = BlockPosArgumentType::get_block_pos(context, "from")?;
|
||||||
|
let to_pos = BlockPosArgumentType::get_block_pos(context, "to")?;
|
||||||
|
|
||||||
|
let world = context.world();
|
||||||
|
if !world.is_in_build_limit(from_pos) || !world.is_in_build_limit(to_pos) {
|
||||||
|
return Err(OUT_OF_BOUNDS_ERROR_TYPE.create_without_context());
|
||||||
|
}
|
||||||
|
|
||||||
|
let min_x = from_pos.0.x.min(to_pos.0.x);
|
||||||
|
let max_x = from_pos.0.x.max(to_pos.0.x);
|
||||||
|
let min_y = from_pos.0.y.min(to_pos.0.y);
|
||||||
|
let max_y = from_pos.0.y.max(to_pos.0.y);
|
||||||
|
let min_z = from_pos.0.z.min(to_pos.0.z);
|
||||||
|
let max_z = from_pos.0.z.max(to_pos.0.z);
|
||||||
|
|
||||||
|
let biome_min_x = min_x >> 2;
|
||||||
|
let biome_max_x = max_x >> 2;
|
||||||
|
let biome_min_y = min_y >> 2;
|
||||||
|
let biome_max_y = max_y >> 2;
|
||||||
|
let biome_min_z = min_z >> 2;
|
||||||
|
let biome_max_z = max_z >> 2;
|
||||||
|
|
||||||
|
let volume = (biome_max_x - biome_min_x + 1) as i64
|
||||||
|
* (biome_max_y - biome_min_y + 1) as i64
|
||||||
|
* (biome_max_z - biome_min_z + 1) as i64;
|
||||||
|
|
||||||
|
if volume > MAX_BIOME_BLOCKS {
|
||||||
|
return Err(ERROR_TOOBIG.create_without_context_args_slice(&[
|
||||||
|
TextComponent::text(MAX_BIOME_BLOCKS.to_string()),
|
||||||
|
TextComponent::text(volume.to_string()),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
let target_biome = ResourceKeyArgument::get_biome(context, "biome")?;
|
||||||
|
|
||||||
|
let replace_biome = if self.has_replace {
|
||||||
|
Some(ResourceKeyArgument::get_biome(context, "replace_biome")?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut chunk_modifications: HashMap<Vector2<i32>, Vec<(usize, usize, usize)>> =
|
||||||
|
HashMap::new();
|
||||||
|
for y in biome_min_y..=biome_max_y {
|
||||||
|
for z in biome_min_z..=biome_max_z {
|
||||||
|
for x in biome_min_x..=biome_max_x {
|
||||||
|
let chunk_pos = Vector2::new(x >> 2, z >> 2);
|
||||||
|
let rel_x = (x & 3) as usize;
|
||||||
|
let rel_z = (z & 3) as usize;
|
||||||
|
let rel_y = (y - (world.min_y >> 2)) as usize;
|
||||||
|
chunk_modifications
|
||||||
|
.entry(chunk_pos)
|
||||||
|
.or_default()
|
||||||
|
.push((rel_x, rel_y, rel_z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let target_biome_id = target_biome.id;
|
||||||
|
let replace_biome_id = replace_biome.map(|b| b.id);
|
||||||
|
let mut changed_count = 0;
|
||||||
|
|
||||||
|
for (chunk_pos, mods) in chunk_modifications {
|
||||||
|
let (has_replaced, count) = world
|
||||||
|
.level
|
||||||
|
.get_or_fetch_chunk(chunk_pos, |chunk| {
|
||||||
|
let mut local_count = 0;
|
||||||
|
let mut modified = false;
|
||||||
|
for &(rel_x, rel_y, rel_z) in &mods {
|
||||||
|
let section_index = rel_y / 4;
|
||||||
|
let scale_y = rel_y % 4;
|
||||||
|
if let Some(current_id) =
|
||||||
|
chunk
|
||||||
|
.section
|
||||||
|
.get_noise_biome(section_index, rel_x, scale_y, rel_z)
|
||||||
|
{
|
||||||
|
if let Some(replace_id) = replace_biome_id {
|
||||||
|
if current_id == replace_id {
|
||||||
|
chunk.section.set_relative_biome(
|
||||||
|
rel_x,
|
||||||
|
rel_y,
|
||||||
|
rel_z,
|
||||||
|
target_biome_id,
|
||||||
|
);
|
||||||
|
local_count += 1;
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chunk.section.set_relative_biome(
|
||||||
|
rel_x,
|
||||||
|
rel_y,
|
||||||
|
rel_z,
|
||||||
|
target_biome_id,
|
||||||
|
);
|
||||||
|
local_count += 1;
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(modified, local_count)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if has_replaced {
|
||||||
|
changed_count += count;
|
||||||
|
let chunk = world
|
||||||
|
.level
|
||||||
|
.get_or_fetch_chunk(chunk_pos, std::clone::Clone::clone)
|
||||||
|
.await;
|
||||||
|
world.broadcast_to_chunk_except(chunk_pos, &[], &CChunkData(&chunk));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let msg = TextComponent::translate_cross(
|
||||||
|
translation::java::COMMANDS_FILLBIOME_SUCCESS_COUNT,
|
||||||
|
translation::java::COMMANDS_FILLBIOME_SUCCESS_COUNT,
|
||||||
|
[TextComponent::text(changed_count.to_string())],
|
||||||
|
);
|
||||||
|
context.source.send_feedback(msg, true).await;
|
||||||
|
|
||||||
|
Ok(changed_count)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(dispatcher: &mut CommandDispatcher, registry: &mut PermissionRegistry) {
|
||||||
|
registry.register_permission_or_panic(Permission::new(
|
||||||
|
PERMISSION,
|
||||||
|
DESCRIPTION,
|
||||||
|
PermissionDefault::Op(PermissionLvl::Two),
|
||||||
|
));
|
||||||
|
|
||||||
|
let builder = command("fillbiome", DESCRIPTION).requires(PERMISSION).then(
|
||||||
|
argument("from", BlockPosArgumentType).then(
|
||||||
|
argument("to", BlockPosArgumentType).then(
|
||||||
|
argument("biome", ResourceKeyArgument(BIOME_REGISTRY.clone()))
|
||||||
|
.executes(FillBiomeExecutor { has_replace: false })
|
||||||
|
.then(
|
||||||
|
argument("replace_biome", ResourceKeyArgument(BIOME_REGISTRY.clone()))
|
||||||
|
.executes(FillBiomeExecutor { has_replace: true }),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatcher.register(builder);
|
||||||
|
}
|
||||||
@ -25,6 +25,7 @@ mod enchant;
|
|||||||
mod execute;
|
mod execute;
|
||||||
mod experience;
|
mod experience;
|
||||||
mod fill;
|
mod fill;
|
||||||
|
mod fillbiome;
|
||||||
mod forceload;
|
mod forceload;
|
||||||
mod gamemode;
|
mod gamemode;
|
||||||
mod gamerule;
|
mod gamerule;
|
||||||
@ -173,6 +174,7 @@ pub async fn default_dispatcher(
|
|||||||
difficulty::register(&mut dispatcher, registry);
|
difficulty::register(&mut dispatcher, registry);
|
||||||
dialog::register(&mut dispatcher, registry);
|
dialog::register(&mut dispatcher, registry);
|
||||||
execute::register(&mut dispatcher, registry);
|
execute::register(&mut dispatcher, registry);
|
||||||
|
fillbiome::register(&mut dispatcher, registry);
|
||||||
forceload::register(&mut dispatcher, registry);
|
forceload::register(&mut dispatcher, registry);
|
||||||
ride::register(&mut dispatcher, registry);
|
ride::register(&mut dispatcher, registry);
|
||||||
recipe::register(&mut dispatcher, registry);
|
recipe::register(&mut dispatcher, registry);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user