feat(command): add spectate command
This commit is contained in:
parent
92835d5523
commit
02c8c84789
@ -53,6 +53,7 @@ mod setblock;
|
||||
mod setidletimeout;
|
||||
mod setworldspawn;
|
||||
mod spawnpoint;
|
||||
mod spectate;
|
||||
mod spreadplayers;
|
||||
mod stop;
|
||||
mod stopsound;
|
||||
@ -136,6 +137,7 @@ pub async fn default_dispatcher(
|
||||
spawnpoint::init_command_tree(),
|
||||
"minecraft:command.spawnpoint",
|
||||
);
|
||||
dispatcher.register(spectate::init_command_tree(), "minecraft:command.spectate");
|
||||
dispatcher.register(data::init_command_tree(), "minecraft:command.data");
|
||||
// Three
|
||||
dispatcher.register(deop::init_command_tree(), "minecraft:command.deop");
|
||||
@ -419,6 +421,13 @@ fn register_level_2_permissions(registry: &mut PermissionRegistry) {
|
||||
PermissionDefault::Op(PermissionLvl::Two),
|
||||
))
|
||||
.expect("Permission already registered");
|
||||
registry
|
||||
.register_permission(Permission::new(
|
||||
"minecraft:command.spectate",
|
||||
"Allows a player to spectate another entity",
|
||||
PermissionDefault::Op(PermissionLvl::Two),
|
||||
))
|
||||
.expect("Permission already registered");
|
||||
registry
|
||||
.register_permission(Permission::new(
|
||||
"pumpkin:command.tps",
|
||||
|
||||
233
pumpkin/src/command/commands/spectate.rs
Normal file
233
pumpkin/src/command/commands/spectate.rs
Normal file
@ -0,0 +1,233 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::command::args::entity::EntityArgumentConsumer;
|
||||
use crate::command::args::players::PlayersArgumentConsumer;
|
||||
use crate::command::args::{Arg, ConsumedArgs, FindArg};
|
||||
use crate::command::dispatcher::CommandError::{self, InvalidConsumption, InvalidRequirement};
|
||||
use crate::command::tree::CommandTree;
|
||||
use crate::command::tree::builder::{argument, require};
|
||||
use crate::command::{CommandExecutor, CommandResult, CommandSender};
|
||||
use crate::entity::EntityBase;
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_protocol::java::client::play::CSetCamera;
|
||||
use pumpkin_util::GameMode;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
const NAMES: [&str; 1] = ["spectate"];
|
||||
|
||||
const DESCRIPTION: &str = "Allows a player in spectator mode to spectate a given target entity.";
|
||||
|
||||
const ARG_TARGET: &str = "target";
|
||||
const ARG_PLAYER: &str = "player";
|
||||
|
||||
struct StopSpectateExecutor;
|
||||
|
||||
impl CommandExecutor for StopSpectateExecutor {
|
||||
fn execute<'a>(
|
||||
&'a self,
|
||||
sender: &'a CommandSender,
|
||||
_server: &'a crate::server::Server,
|
||||
_args: &'a ConsumedArgs<'a>,
|
||||
) -> CommandResult<'a> {
|
||||
Box::pin(async move {
|
||||
let Some(player) = sender.as_player() else {
|
||||
return Err(InvalidRequirement);
|
||||
};
|
||||
|
||||
if player.gamemode.load() != GameMode::Spectator {
|
||||
let display_name = player.get_display_name().await;
|
||||
return Err(CommandError::CommandFailed(TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_SPECTATE_NOT_SPECTATOR,
|
||||
translation::java::COMMANDS_SPECTATE_NOT_SPECTATOR,
|
||||
[display_name],
|
||||
)));
|
||||
}
|
||||
|
||||
player.camera_target_id.store(None);
|
||||
player
|
||||
.client
|
||||
.send_packet_now(&CSetCamera::new(player.entity_id().into()))
|
||||
.await;
|
||||
|
||||
sender
|
||||
.send_message(TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_SPECTATE_SUCCESS_STOPPED,
|
||||
translation::java::COMMANDS_SPECTATE_SUCCESS_STOPPED,
|
||||
[],
|
||||
))
|
||||
.await;
|
||||
|
||||
Ok(1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct SpectateTargetSelfExecutor;
|
||||
|
||||
impl CommandExecutor for SpectateTargetSelfExecutor {
|
||||
fn execute<'a>(
|
||||
&'a self,
|
||||
sender: &'a CommandSender,
|
||||
_server: &'a crate::server::Server,
|
||||
args: &'a ConsumedArgs<'a>,
|
||||
) -> CommandResult<'a> {
|
||||
Box::pin(async move {
|
||||
let Some(player) = sender.as_player() else {
|
||||
return Err(InvalidRequirement);
|
||||
};
|
||||
|
||||
if player.gamemode.load() != GameMode::Spectator {
|
||||
let display_name = player.get_display_name().await;
|
||||
return Err(CommandError::CommandFailed(TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_SPECTATE_NOT_SPECTATOR,
|
||||
translation::java::COMMANDS_SPECTATE_NOT_SPECTATOR,
|
||||
[display_name],
|
||||
)));
|
||||
}
|
||||
|
||||
let target = EntityArgumentConsumer::find_arg(args, ARG_TARGET)?;
|
||||
|
||||
let target_entity = target.get_entity();
|
||||
if target_entity.entity_id == player.entity_id() {
|
||||
return Err(CommandError::CommandFailed(TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_SPECTATE_SELF,
|
||||
translation::java::COMMANDS_SPECTATE_SELF,
|
||||
[],
|
||||
)));
|
||||
}
|
||||
|
||||
let target_world = target_entity.world.load_full();
|
||||
let player_world = player.world();
|
||||
if !Arc::ptr_eq(&target_world, &player_world) {
|
||||
let target_name = target.get_display_name().await;
|
||||
return Err(CommandError::CommandFailed(TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_SPECTATE_CANNOT_SPECTATE,
|
||||
translation::java::COMMANDS_SPECTATE_CANNOT_SPECTATE,
|
||||
[target_name],
|
||||
)));
|
||||
}
|
||||
|
||||
let target_id = target_entity.entity_id;
|
||||
player.camera_target_id.store(Some(target_id));
|
||||
player
|
||||
.client
|
||||
.send_packet_now(&CSetCamera::new(target_id.into()))
|
||||
.await;
|
||||
|
||||
let pos = target_entity.pos.load();
|
||||
let yaw = target_entity.yaw.load();
|
||||
let pitch = target_entity.pitch.load();
|
||||
player
|
||||
.clone()
|
||||
.teleport(pos, Some(yaw), Some(pitch), player_world)
|
||||
.await;
|
||||
|
||||
let target_name = target.get_display_name().await;
|
||||
sender
|
||||
.send_message(TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_SPECTATE_SUCCESS_STARTED,
|
||||
translation::java::COMMANDS_SPECTATE_SUCCESS_STARTED,
|
||||
[target_name],
|
||||
))
|
||||
.await;
|
||||
|
||||
Ok(1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct SpectateTargetOtherExecutor;
|
||||
|
||||
impl CommandExecutor for SpectateTargetOtherExecutor {
|
||||
fn execute<'a>(
|
||||
&'a self,
|
||||
sender: &'a CommandSender,
|
||||
_server: &'a crate::server::Server,
|
||||
args: &'a ConsumedArgs<'a>,
|
||||
) -> CommandResult<'a> {
|
||||
Box::pin(async move {
|
||||
let Some(Arg::Players(players)) = args.get(ARG_PLAYER) else {
|
||||
return Err(InvalidConsumption(Some(ARG_PLAYER.into())));
|
||||
};
|
||||
|
||||
let target = EntityArgumentConsumer::find_arg(args, ARG_TARGET)?;
|
||||
let target_entity = target.get_entity();
|
||||
let target_world = target_entity.world.load_full();
|
||||
|
||||
// First validate all players
|
||||
for player in players {
|
||||
if player.gamemode.load() != GameMode::Spectator {
|
||||
let display_name = player.get_display_name().await;
|
||||
return Err(CommandError::CommandFailed(TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_SPECTATE_NOT_SPECTATOR,
|
||||
translation::java::COMMANDS_SPECTATE_NOT_SPECTATOR,
|
||||
[display_name],
|
||||
)));
|
||||
}
|
||||
|
||||
if target_entity.entity_id == player.entity_id() {
|
||||
return Err(CommandError::CommandFailed(TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_SPECTATE_SELF,
|
||||
translation::java::COMMANDS_SPECTATE_SELF,
|
||||
[],
|
||||
)));
|
||||
}
|
||||
|
||||
let player_world = player.world();
|
||||
if !Arc::ptr_eq(&target_world, &player_world) {
|
||||
let target_name = target.get_display_name().await;
|
||||
return Err(CommandError::CommandFailed(TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_SPECTATE_CANNOT_SPECTATE,
|
||||
translation::java::COMMANDS_SPECTATE_CANNOT_SPECTATE,
|
||||
[target_name],
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
let mut succeeded = 0;
|
||||
for player in players {
|
||||
let target_id = target_entity.entity_id;
|
||||
player.camera_target_id.store(Some(target_id));
|
||||
player
|
||||
.client
|
||||
.send_packet_now(&CSetCamera::new(target_id.into()))
|
||||
.await;
|
||||
|
||||
let pos = target_entity.pos.load();
|
||||
let yaw = target_entity.yaw.load();
|
||||
let pitch = target_entity.pitch.load();
|
||||
let player_world = player.world();
|
||||
player
|
||||
.clone()
|
||||
.teleport(pos, Some(yaw), Some(pitch), player_world)
|
||||
.await;
|
||||
succeeded += 1;
|
||||
}
|
||||
|
||||
let target_name = target.get_display_name().await;
|
||||
sender
|
||||
.send_message(TextComponent::translate_cross(
|
||||
translation::java::COMMANDS_SPECTATE_SUCCESS_STARTED,
|
||||
translation::java::COMMANDS_SPECTATE_SUCCESS_STARTED,
|
||||
[target_name],
|
||||
))
|
||||
.await;
|
||||
|
||||
Ok(succeeded)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::redundant_closure_for_method_calls)]
|
||||
pub fn init_command_tree() -> CommandTree {
|
||||
CommandTree::new(NAMES, DESCRIPTION)
|
||||
.then(
|
||||
argument(ARG_TARGET, EntityArgumentConsumer)
|
||||
.then(require(|sender| sender.is_player()).execute(SpectateTargetSelfExecutor))
|
||||
.then(
|
||||
argument(ARG_PLAYER, PlayersArgumentConsumer)
|
||||
.execute(SpectateTargetOtherExecutor),
|
||||
),
|
||||
)
|
||||
.then(require(|sender| sender.is_player()).execute(StopSpectateExecutor))
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user