Pumpkin/pumpkin/src/command/commands/teleport.rs
2026-05-03 21:31:46 +02:00

361 lines
13 KiB
Rust

use pumpkin_data::translation;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::text::TextComponent;
use crate::command::CommandError;
use crate::command::CommandResult;
use crate::command::args::ConsumedArgs;
use crate::command::args::FindArg;
use crate::command::args::entities::EntitiesArgumentConsumer;
use crate::command::args::entity::EntityArgumentConsumer;
use crate::command::args::position_3d::Position3DArgumentConsumer;
use crate::command::args::rotation::RotationArgumentConsumer;
use crate::command::tree::CommandTree;
use crate::command::tree::builder::{argument, literal};
use crate::command::{CommandExecutor, CommandSender};
use crate::entity::EntityBase;
use crate::world::World;
const NAMES: [&str; 2] = ["teleport", "tp"];
const DESCRIPTION: &str = "Teleports entities, including players."; // todo
/// position
const ARG_LOCATION: &str = "location";
/// single entity
const ARG_DESTINATION: &str = "destination";
/// multiple entities
const ARG_TARGETS: &str = "targets";
/// rotation: yaw/pitch
const ARG_ROTATION: &str = "rotation";
/// single entity
const ARG_FACING_ENTITY: &str = "facingEntity";
/// position
const ARG_FACING_LOCATION: &str = "facingLocation";
fn yaw_pitch_facing_position(
looking_from: &Vector3<f64>,
looking_towards: &Vector3<f64>,
) -> (f32, f32) {
let direction_vector = (looking_towards.sub(looking_from)).normalize();
let yaw_radians = -direction_vector.x.atan2(direction_vector.z);
let pitch_radians = (-direction_vector.y).asin();
let yaw_degrees = yaw_radians.to_degrees();
let pitch_degrees = pitch_radians.to_degrees();
(yaw_degrees as f32, pitch_degrees as f32)
}
fn resolve_sender_world(
sender: &CommandSender,
server: &crate::server::Server,
) -> std::sync::Arc<World> {
sender
.world()
.unwrap_or_else(|| server.worlds.load().first().unwrap().clone())
}
struct EntitiesToEntityExecutor;
impl CommandExecutor for EntitiesToEntityExecutor {
fn execute<'a>(
&'a self,
_sender: &'a CommandSender,
_server: &'a crate::server::Server,
args: &'a ConsumedArgs<'a>,
) -> CommandResult<'a> {
Box::pin(async move {
let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?;
let destination = EntityArgumentConsumer::find_arg(args, ARG_DESTINATION)?;
let destination = destination.get_entity();
let pos = destination.pos.load();
let yaw = destination.yaw.load();
let pitch = destination.pitch.load();
let world = destination.world.load_full();
if !World::is_valid(BlockPos(pos.floor_to_i32())) {
return Err(CommandError::CommandFailed(TextComponent::translate_cross(
translation::java::COMMANDS_TELEPORT_INVALIDPOSITION,
translation::java::COMMANDS_TELEPORT_INVALIDPOSITION,
[],
)));
}
for target in targets {
target
.clone()
.teleport(pos, Some(yaw), Some(pitch), world.clone())
.await;
}
Ok(targets.len() as i32)
})
}
}
struct EntitiesToPosFacingPosExecutor;
impl CommandExecutor for EntitiesToPosFacingPosExecutor {
fn execute<'a>(
&'a self,
sender: &'a CommandSender,
server: &'a crate::server::Server,
args: &'a ConsumedArgs<'a>,
) -> CommandResult<'a> {
Box::pin(async move {
let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?;
let pos = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?;
if !World::is_valid(BlockPos(pos.floor_to_i32())) {
return Err(CommandError::CommandFailed(TextComponent::translate_cross(
translation::java::COMMANDS_TELEPORT_INVALIDPOSITION,
translation::java::COMMANDS_TELEPORT_INVALIDPOSITION,
[],
)));
}
let facing_pos = Position3DArgumentConsumer::find_arg(args, ARG_FACING_LOCATION)?;
let (yaw, pitch) = yaw_pitch_facing_position(&pos, &facing_pos);
let world = resolve_sender_world(sender, server);
for target in targets {
target
.clone()
.teleport(pos, Some(yaw), Some(pitch), world.clone())
.await;
}
Ok(targets.len() as i32)
})
}
}
struct EntitiesToPosFacingEntityExecutor;
impl CommandExecutor for EntitiesToPosFacingEntityExecutor {
fn execute<'a>(
&'a self,
sender: &'a CommandSender,
server: &'a crate::server::Server,
args: &'a ConsumedArgs<'a>,
) -> CommandResult<'a> {
Box::pin(async move {
let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?;
let pos = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?;
if !World::is_valid(BlockPos(pos.floor_to_i32())) {
return Err(CommandError::CommandFailed(TextComponent::translate_cross(
translation::java::COMMANDS_TELEPORT_INVALIDPOSITION,
translation::java::COMMANDS_TELEPORT_INVALIDPOSITION,
[],
)));
}
let facing_entity = EntityArgumentConsumer::find_arg(args, ARG_FACING_ENTITY)?;
let (yaw, pitch) =
yaw_pitch_facing_position(&pos, &facing_entity.get_entity().pos.load());
let world = resolve_sender_world(sender, server);
for target in targets {
target
.clone()
.teleport(pos, Some(yaw), Some(pitch), world.clone())
.await;
}
Ok(targets.len() as i32)
})
}
}
struct EntitiesToPosWithRotationExecutor;
impl CommandExecutor for EntitiesToPosWithRotationExecutor {
fn execute<'a>(
&'a self,
sender: &'a CommandSender,
server: &'a crate::server::Server,
args: &'a ConsumedArgs<'a>,
) -> CommandResult<'a> {
Box::pin(async move {
let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?;
let pos = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?;
if !World::is_valid(BlockPos(pos.floor_to_i32())) {
return Err(CommandError::CommandFailed(TextComponent::translate_cross(
translation::java::COMMANDS_TELEPORT_INVALIDPOSITION,
translation::java::COMMANDS_TELEPORT_INVALIDPOSITION,
[],
)));
}
// Note: Rotation returns (yaw, is_yaw_relative, pitch, is_pitch_relative)
// For teleport, we use absolute values only (ignore relative flags)
let (yaw, _, pitch, _) = RotationArgumentConsumer::find_arg(args, ARG_ROTATION)?;
let world = resolve_sender_world(sender, server);
for target in targets {
target
.clone()
.teleport(pos, Some(yaw), Some(pitch), world.clone())
.await;
}
Ok(targets.len() as i32)
})
}
}
struct EntitiesToPosExecutor;
impl CommandExecutor for EntitiesToPosExecutor {
fn execute<'a>(
&'a self,
sender: &'a CommandSender,
server: &'a crate::server::Server,
args: &'a ConsumedArgs<'a>,
) -> CommandResult<'a> {
Box::pin(async move {
let targets = EntitiesArgumentConsumer::find_arg(args, ARG_TARGETS)?;
let pos = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?;
if !World::is_valid(BlockPos(pos.floor_to_i32())) {
return Err(CommandError::CommandFailed(TextComponent::translate_cross(
translation::java::COMMANDS_TELEPORT_INVALIDPOSITION,
translation::java::COMMANDS_TELEPORT_INVALIDPOSITION,
[],
)));
}
let world = resolve_sender_world(sender, server);
for target in targets {
let yaw = target.get_entity().yaw.load();
let pitch = target.get_entity().pitch.load();
target
.clone()
.teleport(pos, Some(yaw), Some(pitch), world.clone())
.await;
}
Ok(targets.len() as i32)
})
}
}
struct SelfToEntityExecutor;
impl CommandExecutor for SelfToEntityExecutor {
fn execute<'a>(
&'a self,
sender: &'a CommandSender,
_server: &'a crate::server::Server,
args: &'a ConsumedArgs<'a>,
) -> CommandResult<'a> {
Box::pin(async move {
let destination = EntityArgumentConsumer::find_arg(args, ARG_DESTINATION)?;
let destination = destination.get_entity();
let pos = destination.pos.load();
let yaw = destination.yaw.load();
let pitch = destination.pitch.load();
let world = destination.world.load_full();
match sender {
CommandSender::Player(player) => {
if !World::is_valid(BlockPos(pos.floor_to_i32())) {
return Err(CommandError::CommandFailed(TextComponent::translate_cross(
translation::java::COMMANDS_TELEPORT_INVALIDPOSITION,
translation::java::COMMANDS_TELEPORT_INVALIDPOSITION,
[],
)));
}
player
.clone()
.teleport(pos, Some(yaw), Some(pitch), world)
.await;
Ok(1)
}
_ => Err(CommandError::CommandFailed(TextComponent::translate_cross(
translation::java::PERMISSIONS_REQUIRES_PLAYER,
translation::java::PERMISSIONS_REQUIRES_PLAYER,
[],
))),
}
})
}
}
struct SelfToPosExecutor;
impl CommandExecutor for SelfToPosExecutor {
fn execute<'a>(
&'a self,
sender: &'a CommandSender,
_server: &'a crate::server::Server,
args: &'a ConsumedArgs<'a>,
) -> CommandResult<'a> {
Box::pin(async move {
match sender {
CommandSender::Player(player) => {
let pos = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?;
let yaw = player.living_entity.entity.yaw.load();
let pitch = player.living_entity.entity.pitch.load();
if !World::is_valid(BlockPos(pos.floor_to_i32())) {
return Err(CommandError::CommandFailed(TextComponent::translate_cross(
translation::java::COMMANDS_TELEPORT_INVALIDPOSITION,
translation::java::COMMANDS_TELEPORT_INVALIDPOSITION,
[],
)));
}
player
.clone()
.teleport(pos, Some(yaw), Some(pitch), player.world().clone())
.await;
Ok(1)
}
_ => Err(CommandError::CommandFailed(TextComponent::translate_cross(
translation::java::PERMISSIONS_REQUIRES_PLAYER,
translation::java::PERMISSIONS_REQUIRES_PLAYER,
[],
))),
}
})
}
}
pub fn init_command_tree() -> CommandTree {
CommandTree::new(NAMES, DESCRIPTION)
.then(argument(ARG_LOCATION, Position3DArgumentConsumer).execute(SelfToPosExecutor))
.then(argument(ARG_DESTINATION, EntityArgumentConsumer).execute(SelfToEntityExecutor))
.then(
argument(ARG_TARGETS, EntitiesArgumentConsumer)
.then(
argument(ARG_LOCATION, Position3DArgumentConsumer)
.execute(EntitiesToPosExecutor)
.then(
argument(ARG_ROTATION, RotationArgumentConsumer)
.execute(EntitiesToPosWithRotationExecutor),
)
.then(
literal("facing")
.then(
literal("entity").then(
argument(ARG_FACING_ENTITY, EntityArgumentConsumer)
.execute(EntitiesToPosFacingEntityExecutor),
),
)
.then(
argument(ARG_FACING_LOCATION, Position3DArgumentConsumer)
.execute(EntitiesToPosFacingPosExecutor),
),
),
)
.then(
argument(ARG_DESTINATION, EntityArgumentConsumer)
.execute(EntitiesToEntityExecutor),
),
)
}