feat(command): add ride command

This commit is contained in:
Alexander Medvedev 2026-07-14 15:04:02 +02:00
parent e3f4bf23b0
commit 5692c65941
2 changed files with 200 additions and 0 deletions

View File

@ -45,6 +45,7 @@ mod plugin;
mod plugins;
mod pumpkin;
mod random;
mod ride;
mod rotate;
mod saveall;
mod saveoff;
@ -171,6 +172,7 @@ pub async fn default_dispatcher(
dialog::register(&mut dispatcher, registry);
execute::register(&mut dispatcher, registry);
forceload::register(&mut dispatcher, registry);
ride::register(&mut dispatcher, registry);
help::register(&mut dispatcher, registry);
kill::register(&mut dispatcher, registry);
op::register(&mut dispatcher, registry);

View File

@ -0,0 +1,198 @@
use crate::command::argument_builder::{ArgumentBuilder, argument, command, literal};
use crate::command::argument_types::entity::EntityArgumentType;
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::entity::EntityBase;
use pumpkin_data::translation;
use pumpkin_util::PermissionLvl;
use pumpkin_util::permission::{Permission, PermissionDefault, PermissionRegistry};
use pumpkin_util::text::TextComponent;
const DESCRIPTION: &str = "Mounts or dismounts target entities.";
const PERMISSION: &str = "minecraft:command.ride";
static ERROR_CANT_RIDE_PLAYERS: CommandErrorType<0> = CommandErrorType::new(
translation::java::COMMANDS_RIDE_MOUNT_FAILURE_CANT_RIDE_PLAYERS,
"Cannot ride players",
);
static ERROR_WRONG_DIMENSION: CommandErrorType<0> = CommandErrorType::new(
translation::java::COMMANDS_RIDE_MOUNT_FAILURE_WRONG_DIMENSION,
"Cannot ride across dimensions",
);
static ERROR_LOOP: CommandErrorType<0> = CommandErrorType::new(
translation::java::COMMANDS_RIDE_MOUNT_FAILURE_LOOP,
"Cannot ride recursively",
);
static ERROR_ALREADY_RIDING: CommandErrorType<2> = CommandErrorType::new(
translation::java::COMMANDS_RIDE_ALREADY_RIDING,
"Already riding",
);
static ERROR_NOT_RIDING: CommandErrorType<1> = CommandErrorType::new(
translation::java::COMMANDS_RIDE_NOT_RIDING,
"Not riding anything",
);
static ERROR_GENERIC: CommandErrorType<2> = CommandErrorType::new(
translation::java::COMMANDS_RIDE_MOUNT_FAILURE_GENERIC,
"Failed to mount",
);
#[allow(clippy::assigning_clones)]
async fn is_riding_recursive(entity: &dyn EntityBase, possible_vehicle: &dyn EntityBase) -> bool {
let mut current = possible_vehicle.get_entity().vehicle.lock().await.clone();
while let Some(vehicle) = current {
if vehicle.get_entity().entity_id == entity.get_entity().entity_id {
return true;
}
current = vehicle.get_entity().vehicle.lock().await.clone();
}
false
}
struct RideMountExecutor;
impl CommandExecutor for RideMountExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let targets = EntityArgumentType::get_entities(context, "target").await?;
let vehicle = EntityArgumentType::get_entity(context, "vehicle").await?;
if vehicle.get_player().is_some() {
return Err(ERROR_CANT_RIDE_PLAYERS.create_without_context());
}
let vehicle_world = vehicle.get_entity().world.load();
let mut success_count = 0;
let mut last_error = None;
for target in &targets {
let target_world = target.get_entity().world.load();
if target_world.dimension.minecraft_name != vehicle_world.dimension.minecraft_name {
last_error = Some(ERROR_WRONG_DIMENSION.create_without_context());
continue;
}
if target.get_entity().entity_id == vehicle.get_entity().entity_id {
last_error = Some(ERROR_LOOP.create_without_context());
continue;
}
if is_riding_recursive(target.as_ref(), vehicle.as_ref()).await {
last_error = Some(ERROR_LOOP.create_without_context());
continue;
}
let current_vehicle = target.get_entity().vehicle.lock().await.clone();
if let Some(ref curr_veh) = current_vehicle {
if curr_veh.get_entity().entity_id == vehicle.get_entity().entity_id {
last_error = Some(ERROR_ALREADY_RIDING.create_without_context(
target.get_display_name().await,
vehicle.get_display_name().await,
));
continue;
}
// Dismount first
curr_veh
.get_entity()
.remove_passenger(target.get_entity().entity_id)
.await;
}
vehicle
.get_entity()
.add_passenger(vehicle.clone(), target.clone())
.await;
success_count += 1;
let msg = TextComponent::translate_cross(
translation::java::COMMANDS_RIDE_MOUNT_SUCCESS,
translation::java::COMMANDS_RIDE_MOUNT_SUCCESS,
[
target.get_display_name().await,
vehicle.get_display_name().await,
],
);
context.source.send_feedback(msg, true).await;
}
if success_count == 0 {
if let Some(err) = last_error {
return Err(err);
}
return Err(ERROR_GENERIC.create_without_context(
targets[0].get_display_name().await,
vehicle.get_display_name().await,
));
}
Ok(success_count)
})
}
}
struct RideDismountExecutor;
impl CommandExecutor for RideDismountExecutor {
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
Box::pin(async move {
let targets = EntityArgumentType::get_entities(context, "target").await?;
let mut success_count = 0;
let mut last_error = None;
for target in &targets {
let current_vehicle = target.get_entity().vehicle.lock().await.clone();
if let Some(vehicle) = current_vehicle {
vehicle
.get_entity()
.remove_passenger(target.get_entity().entity_id)
.await;
success_count += 1;
let msg = TextComponent::translate_cross(
translation::java::COMMANDS_RIDE_DISMOUNT_SUCCESS,
translation::java::COMMANDS_RIDE_DISMOUNT_SUCCESS,
[target.get_display_name().await],
);
context.source.send_feedback(msg, true).await;
} else {
last_error = Some(
ERROR_NOT_RIDING.create_without_context(target.get_display_name().await),
);
}
}
if success_count == 0 {
if let Some(err) = last_error {
return Err(err);
}
return Err(
ERROR_NOT_RIDING.create_without_context(targets[0].get_display_name().await)
);
}
Ok(success_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("ride", DESCRIPTION).requires(PERMISSION).then(
argument("target", EntityArgumentType::Entities)
.then(literal("mount").then(
argument("vehicle", EntityArgumentType::Entity).executes(RideMountExecutor),
))
.then(literal("dismount").executes(RideDismountExecutor)),
);
dispatcher.register(builder);
}