feat(command): add execute command

This commit is contained in:
Alexander Medvedev 2026-07-14 14:55:07 +02:00
parent 75d1bc5a88
commit df159a5425
4 changed files with 592 additions and 0 deletions

View File

@ -0,0 +1,573 @@
use std::sync::Arc;
use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal};
use crate::command::argument_types::block::BlockArgumentType;
use crate::command::argument_types::coordinates::block_pos::BlockPosArgumentType;
use crate::command::argument_types::coordinates::rotation::RotationArgumentType;
use crate::command::argument_types::coordinates::vec3::Vec3ArgumentType;
use crate::command::argument_types::core::string::StringArgumentType;
use crate::command::argument_types::entity::EntityArgumentType;
use crate::command::argument_types::entity_anchor::EntityAnchorArgumentType;
use crate::command::argument_types::resource_key::ResourceKeyArgument;
use crate::command::context::command_context::CommandContext;
use crate::command::errors::error_types::CommandErrorType;
use crate::command::node::attached::{CommandNodeId, NodeId};
use crate::command::node::dispatcher::CommandDispatcher;
use crate::command::node::tree::Tree;
use crate::command::node::{CommandExecutor, CommandExecutorResult, RedirectModifier, Redirection};
use pumpkin_util::PermissionLvl;
use pumpkin_util::identifier::Identifier;
use pumpkin_util::math::vector2::Vector2;
use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry};
use pumpkin_util::text::TextComponent;
const DESCRIPTION: &str = "Execute a command with a modified context.";
const PERMISSION: &str = "minecraft:command.execute";
static ERROR_INVALID_DIMENSION: CommandErrorType<1> =
CommandErrorType::new("argument.dimension.invalid", "argument.dimension.invalid");
struct ExecuteRunExecutor;
impl CommandExecutor for ExecuteRunExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let command_str = StringArgumentType::get(context, "command")?;
let dispatcher = context.server().command_dispatcher.read().await;
let result = dispatcher
.execute_input(command_str, &context.source)
.await?;
Ok(result)
})
}
}
fn execute_as_modifier<'a>(
context: &'a CommandContext,
) -> crate::command::node::RedirectModifierResult<'a> {
Box::pin(async move {
let targets = EntityArgumentType::get_optional_entities(context, "targets").await?;
let mut sources = Vec::new();
for target in targets {
let mut source = context.source.as_ref().clone();
let display_name = target.get_display_name().await;
let name = target.get_name().get_text();
source.entity = Some(target.clone());
source.name = name;
source.display_name = display_name;
sources.push(Arc::new(source));
}
Ok(sources)
})
}
fn execute_at_modifier<'a>(
context: &'a CommandContext,
) -> crate::command::node::RedirectModifierResult<'a> {
Box::pin(async move {
let targets = EntityArgumentType::get_optional_entities(context, "targets").await?;
let mut sources = Vec::new();
for target in targets {
let entity = target.get_entity();
let mut source = context.source.as_ref().clone();
source.position = entity.pos.load();
source.rotation = Vector2::new(entity.yaw.load(), entity.pitch.load());
source.world = Some(entity.world.load().clone());
sources.push(Arc::new(source));
}
Ok(sources)
})
}
fn execute_in_modifier<'a>(
context: &'a CommandContext,
) -> crate::command::node::RedirectModifierResult<'a> {
Box::pin(async move {
let dimension_key = ResourceKeyArgument::get_registry_key(
context,
"dimension",
&Identifier::vanilla_static("dimension"),
&ERROR_INVALID_DIMENSION,
)?;
let dimension_name = dimension_key.identifier.to_string();
let server = context.server();
let worlds = server.worlds.load();
let target_world = worlds
.iter()
.find(|w| w.dimension.minecraft_name == dimension_name);
target_world.map_or_else(
|| {
Err(ERROR_INVALID_DIMENSION
.create_without_context(TextComponent::text(dimension_name)))
},
|target_world| {
let mut source = context.source.as_ref().clone();
source.world = Some(target_world.clone());
Ok(vec![Arc::new(source)])
},
)
})
}
fn execute_positioned_modifier<'a>(
context: &'a CommandContext,
) -> crate::command::node::RedirectModifierResult<'a> {
Box::pin(async move {
let pos = Vec3ArgumentType::get_vector3(context, "pos")?;
let mut source = context.source.as_ref().clone();
source.position = pos;
Ok(vec![Arc::new(source)])
})
}
fn execute_positioned_as_modifier<'a>(
context: &'a CommandContext,
) -> crate::command::node::RedirectModifierResult<'a> {
Box::pin(async move {
let targets = EntityArgumentType::get_optional_entities(context, "targets").await?;
let mut sources = Vec::new();
for target in targets {
let mut source = context.source.as_ref().clone();
source.position = target.get_entity().pos.load();
sources.push(Arc::new(source));
}
Ok(sources)
})
}
fn execute_rotated_modifier<'a>(
context: &'a CommandContext,
) -> crate::command::node::RedirectModifierResult<'a> {
Box::pin(async move {
let rot_coords = RotationArgumentType::get(context, "rotation")?;
let rot = rot_coords.rotation(&context.source);
let mut source = context.source.as_ref().clone();
source.rotation = rot;
Ok(vec![Arc::new(source)])
})
}
fn execute_rotated_as_modifier<'a>(
context: &'a CommandContext,
) -> crate::command::node::RedirectModifierResult<'a> {
Box::pin(async move {
let targets = EntityArgumentType::get_optional_entities(context, "targets").await?;
let mut sources = Vec::new();
for target in targets {
let entity = target.get_entity();
let mut source = context.source.as_ref().clone();
source.rotation = Vector2::new(entity.yaw.load(), entity.pitch.load());
sources.push(Arc::new(source));
}
Ok(sources)
})
}
fn execute_if_entity_modifier<'a>(
context: &'a CommandContext,
) -> crate::command::node::RedirectModifierResult<'a> {
Box::pin(async move {
let targets = EntityArgumentType::get_optional_entities(context, "targets").await?;
if targets.is_empty() {
Ok(vec![])
} else {
Ok(vec![context.source.clone()])
}
})
}
fn execute_unless_entity_modifier<'a>(
context: &'a CommandContext,
) -> crate::command::node::RedirectModifierResult<'a> {
Box::pin(async move {
let targets = EntityArgumentType::get_optional_entities(context, "targets").await?;
if targets.is_empty() {
Ok(vec![context.source.clone()])
} else {
Ok(vec![])
}
})
}
fn execute_align_modifier<'a>(
context: &'a CommandContext,
) -> crate::command::node::RedirectModifierResult<'a> {
Box::pin(async move {
let axes = StringArgumentType::get(context, "axes")?;
let mut source = context.source.as_ref().clone();
let has_x = axes.contains('x');
let has_y = axes.contains('y');
let has_z = axes.contains('z');
if has_x {
source.position.x = source.position.x.floor();
}
if has_y {
source.position.y = source.position.y.floor();
}
if has_z {
source.position.z = source.position.z.floor();
}
Ok(vec![Arc::new(source)])
})
}
fn execute_anchored_modifier<'a>(
context: &'a CommandContext,
) -> crate::command::node::RedirectModifierResult<'a> {
Box::pin(async move {
let anchor = EntityAnchorArgumentType::get(context, "anchor")?;
let mut source = context.source.as_ref().clone();
source.entity_anchor = anchor;
Ok(vec![Arc::new(source)])
})
}
fn execute_facing_modifier<'a>(
context: &'a CommandContext,
) -> crate::command::node::RedirectModifierResult<'a> {
Box::pin(async move {
let pos = Vec3ArgumentType::get_vector3(context, "pos")?;
let mut source = context.source.as_ref().clone();
let dx = pos.x - source.position.x;
let dy = pos.y - source.position.y;
let dz = pos.z - source.position.z;
let xz_dist = dx.hypot(dz);
let yaw = (dz.atan2(dx).to_degrees() as f32) - 90.0;
let pitch = -(dy.atan2(xz_dist).to_degrees() as f32);
source.rotation = Vector2::new(yaw, pitch);
Ok(vec![Arc::new(source)])
})
}
fn execute_facing_entity_modifier<'a>(
context: &'a CommandContext,
) -> crate::command::node::RedirectModifierResult<'a> {
Box::pin(async move {
let targets = EntityArgumentType::get_optional_entities(context, "targets").await?;
let anchor = EntityAnchorArgumentType::get(context, "anchor")?;
let mut sources = Vec::new();
for target in targets {
let target_pos = anchor.position_at_entity(target.get_entity());
let mut source = context.source.as_ref().clone();
let dx = target_pos.x - source.position.x;
let dy = target_pos.y - source.position.y;
let dz = target_pos.z - source.position.z;
let xz_dist = dx.hypot(dz);
let yaw = (dz.atan2(dx).to_degrees() as f32) - 90.0;
let pitch = -(dy.atan2(xz_dist).to_degrees() as f32);
source.rotation = Vector2::new(yaw, pitch);
sources.push(Arc::new(source));
}
Ok(sources)
})
}
fn execute_if_block_modifier<'a>(
context: &'a CommandContext,
) -> crate::command::node::RedirectModifierResult<'a> {
Box::pin(async move {
let pos = BlockPosArgumentType::get_block_pos(context, "pos")?;
let expected_block = BlockArgumentType::get(context, "block")?;
if let Some(ref world) = context.source.world {
let block = world.get_block(&pos);
if block == expected_block {
return Ok(vec![context.source.clone()]);
}
}
Ok(vec![])
})
}
fn execute_unless_block_modifier<'a>(
context: &'a CommandContext,
) -> crate::command::node::RedirectModifierResult<'a> {
Box::pin(async move {
let pos = BlockPosArgumentType::get_block_pos(context, "pos")?;
let expected_block = BlockArgumentType::get(context, "block")?;
if let Some(ref world) = context.source.world {
let block = world.get_block(&pos);
if block != expected_block {
return Ok(vec![context.source.clone()]);
}
} else {
return Ok(vec![context.source.clone()]);
}
Ok(vec![])
})
}
fn execute_if_loaded_modifier<'a>(
context: &'a CommandContext,
) -> crate::command::node::RedirectModifierResult<'a> {
Box::pin(async move {
let pos = BlockPosArgumentType::get_block_pos(context, "pos")?;
if let Some(ref world) = context.source.world
&& world.is_loaded(&pos)
{
return Ok(vec![context.source.clone()]);
}
Ok(vec![])
})
}
fn execute_unless_loaded_modifier<'a>(
context: &'a CommandContext,
) -> crate::command::node::RedirectModifierResult<'a> {
Box::pin(async move {
let pos = BlockPosArgumentType::get_block_pos(context, "pos")?;
if let Some(ref world) = context.source.world {
if !world.is_loaded(&pos) {
return Ok(vec![context.source.clone()]);
}
} else {
return Ok(vec![context.source.clone()]);
}
Ok(vec![])
})
}
fn execute_if_dimension_modifier<'a>(
context: &'a CommandContext,
) -> crate::command::node::RedirectModifierResult<'a> {
Box::pin(async move {
let dimension_key = ResourceKeyArgument::get_registry_key(
context,
"dimension",
&Identifier::vanilla_static("dimension"),
&ERROR_INVALID_DIMENSION,
)?;
let dimension_name = dimension_key.identifier.to_string();
if let Some(ref world) = context.source.world
&& world.dimension.minecraft_name == dimension_name
{
return Ok(vec![context.source.clone()]);
}
Ok(vec![])
})
}
fn execute_unless_dimension_modifier<'a>(
context: &'a CommandContext,
) -> crate::command::node::RedirectModifierResult<'a> {
Box::pin(async move {
let dimension_key = ResourceKeyArgument::get_registry_key(
context,
"dimension",
&Identifier::vanilla_static("dimension"),
&ERROR_INVALID_DIMENSION,
)?;
let dimension_name = dimension_key.identifier.to_string();
if let Some(ref world) = context.source.world {
if world.dimension.minecraft_name != dimension_name {
return Ok(vec![context.source.clone()]);
}
} else {
return Ok(vec![context.source.clone()]);
}
Ok(vec![])
})
}
#[allow(clippy::too_many_lines)]
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("execute", DESCRIPTION)
.requires(PERMISSION)
.then(literal("run").then(
argument("command", StringArgumentType::GreedyPhrase).executes(ExecuteRunExecutor),
))
.then(
literal("as").then(argument("targets", EntityArgumentType::Entities).fork(
Redirection::Root,
RedirectModifier::Custom(Arc::new(execute_as_modifier)),
)),
)
.then(
literal("at").then(argument("targets", EntityArgumentType::Entities).fork(
Redirection::Root,
RedirectModifier::Custom(Arc::new(execute_at_modifier)),
)),
)
.then(
literal("in").then(
argument(
"dimension",
ResourceKeyArgument(Identifier::vanilla_static("dimension")),
)
.redirect_with_modifier(
Redirection::Root,
RedirectModifier::Custom(Arc::new(execute_in_modifier)),
),
),
)
.then(
literal("positioned")
.then(
literal("as").then(argument("targets", EntityArgumentType::Entities).fork(
Redirection::Root,
RedirectModifier::Custom(Arc::new(execute_positioned_as_modifier)),
)),
)
.then(
argument("pos", Vec3ArgumentType::Default).redirect_with_modifier(
Redirection::Root,
RedirectModifier::Custom(Arc::new(execute_positioned_modifier)),
),
),
)
.then(
literal("rotated")
.then(
literal("as").then(argument("targets", EntityArgumentType::Entities).fork(
Redirection::Root,
RedirectModifier::Custom(Arc::new(execute_rotated_as_modifier)),
)),
)
.then(
argument("rotation", RotationArgumentType).redirect_with_modifier(
Redirection::Root,
RedirectModifier::Custom(Arc::new(execute_rotated_modifier)),
),
),
)
.then(literal("align").then(
argument("axes", StringArgumentType::SingleWord).redirect_with_modifier(
Redirection::Root,
RedirectModifier::Custom(Arc::new(execute_align_modifier)),
),
))
.then(literal("anchored").then(
argument("anchor", EntityAnchorArgumentType).redirect_with_modifier(
Redirection::Root,
RedirectModifier::Custom(Arc::new(execute_anchored_modifier)),
),
))
.then(
literal("facing")
.then(literal("entity").then(
argument("targets", EntityArgumentType::Entities).then(
argument("anchor", EntityAnchorArgumentType).fork(
Redirection::Root,
RedirectModifier::Custom(Arc::new(execute_facing_entity_modifier)),
),
),
))
.then(
argument("pos", Vec3ArgumentType::Default).redirect_with_modifier(
Redirection::Root,
RedirectModifier::Custom(Arc::new(execute_facing_modifier)),
),
),
)
.then(
literal("if")
.then(literal("entity").then(
argument("targets", EntityArgumentType::Entities).redirect_with_modifier(
Redirection::Root,
RedirectModifier::Custom(Arc::new(execute_if_entity_modifier)),
),
))
.then(
literal("block").then(argument("pos", BlockPosArgumentType).then(
argument("block", BlockArgumentType).redirect_with_modifier(
Redirection::Root,
RedirectModifier::Custom(Arc::new(execute_if_block_modifier)),
),
)),
)
.then(literal("loaded").then(
argument("pos", BlockPosArgumentType).redirect_with_modifier(
Redirection::Root,
RedirectModifier::Custom(Arc::new(execute_if_loaded_modifier)),
),
))
.then(
literal("dimension").then(
argument(
"dimension",
ResourceKeyArgument(Identifier::vanilla_static("dimension")),
)
.redirect_with_modifier(
Redirection::Root,
RedirectModifier::Custom(Arc::new(execute_if_dimension_modifier)),
),
),
),
)
.then(
literal("unless")
.then(literal("entity").then(
argument("targets", EntityArgumentType::Entities).redirect_with_modifier(
Redirection::Root,
RedirectModifier::Custom(Arc::new(execute_unless_entity_modifier)),
),
))
.then(
literal("block").then(argument("pos", BlockPosArgumentType).then(
argument("block", BlockArgumentType).redirect_with_modifier(
Redirection::Root,
RedirectModifier::Custom(Arc::new(execute_unless_block_modifier)),
),
)),
)
.then(literal("loaded").then(
argument("pos", BlockPosArgumentType).redirect_with_modifier(
Redirection::Root,
RedirectModifier::Custom(Arc::new(execute_unless_loaded_modifier)),
),
))
.then(
literal("dimension").then(
argument(
"dimension",
ResourceKeyArgument(Identifier::vanilla_static("dimension")),
)
.redirect_with_modifier(
Redirection::Root,
RedirectModifier::Custom(Arc::new(execute_unless_dimension_modifier)),
),
),
),
);
let execute_node_id = dispatcher.register(builder);
set_redirects_to_execute(
&mut dispatcher.tree,
NodeId::from(execute_node_id),
execute_node_id,
);
}
fn set_redirects_to_execute(tree: &mut Tree, parent: NodeId, execute_id: CommandNodeId) {
for child_id in tree.get_children(parent) {
if let Some(redirect) = tree[child_id].redirect()
&& matches!(redirect, Redirection::Root)
{
tree[child_id].set_redirect(Some(Redirection::Local(NodeId::from(execute_id))));
}
set_redirects_to_execute(tree, child_id, execute_id);
}
}

View File

@ -22,8 +22,10 @@ mod dialog;
mod difficulty;
mod effect;
mod enchant;
mod execute;
mod experience;
mod fill;
mod forceload;
mod gamemode;
mod gamerule;
mod give;
@ -167,6 +169,8 @@ pub async fn default_dispatcher(
banlist::register(&mut dispatcher, registry);
difficulty::register(&mut dispatcher, registry);
dialog::register(&mut dispatcher, registry);
execute::register(&mut dispatcher, registry);
forceload::register(&mut dispatcher, registry);
help::register(&mut dispatcher, registry);
kill::register(&mut dispatcher, registry);
op::register(&mut dispatcher, registry);

View File

@ -273,6 +273,16 @@ impl AttachedNode {
}
}
/// Sets the redirection of this node.
pub const fn set_redirect(&mut self, redirect: Option<Redirection>) {
match self {
Self::Root(_) => {}
Self::Literal(node) => node.redirect = redirect,
Self::Command(node) => node.redirect = redirect,
Self::Argument(node) => node.redirect = redirect,
}
}
/// Gets all the requirements required for this node to be run.
#[must_use]
pub const fn requirements(&self) -> &Requirements {

View File

@ -221,6 +221,7 @@ pub struct World {
pub dragon_fight: Option<Mutex<dragon_fight::DragonFight>>,
pub spawn_state: ArcSwap<SpawnState>,
pub active_chunks: ArcSwap<FxHashSet<Vector2<i32>>>,
pub forced_chunks: std::sync::Mutex<FxHashSet<Vector2<i32>>>,
/// Block entities indexed by chunk, so ticking only visits the currently
/// active chunks instead of scanning every loaded block entity each tick.
pub block_entities: DashMap<Vector2<i32>, FxHashMap<BlockPos, Arc<dyn BlockEntity>>>,
@ -309,6 +310,7 @@ impl World {
dragon_fight,
spawn_state: ArcSwap::new(Arc::new(SpawnState::empty())),
active_chunks: ArcSwap::new(Arc::new(FxHashSet::default())),
forced_chunks: std::sync::Mutex::new(FxHashSet::default()),
server,
block_entities: DashMap::new(),
}
@ -325,6 +327,9 @@ impl World {
}
}
}
if let Ok(forced) = self.forced_chunks.lock() {
active_chunks.extend(forced.iter().copied());
}
let mut spawnable_chunks = 0;
for pos in &active_chunks {