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

199 lines
6.7 KiB
Rust

use pumpkin_protocol::java::client::play::CClearTitle;
use pumpkin_util::text::TextComponent;
use crate::command::CommandResult;
use crate::command::args::time::TimeArgumentConsumer;
use crate::entity::EntityBase;
use crate::{
command::{
CommandError, CommandExecutor, CommandSender,
args::{
Arg, ConsumedArgs, FindArg, players::PlayersArgumentConsumer,
textcomponent::TextComponentArgConsumer,
},
tree::CommandTree,
tree::builder::{argument, literal},
},
entity::player::TitleMode,
};
const NAMES: [&str; 1] = ["title"];
const DESCRIPTION: &str = "Displays a title.";
const ARG_TARGETS: &str = "targets";
const ARG_TITLE: &str = "title";
const ARG_FADE_IN: &str = "fadeIn";
const ARG_STAY: &str = "stay";
const ARG_FADE_OUT: &str = "fadeOut";
/// bool: Whether to reset or not
struct ClearOrResetExecutor(bool);
impl CommandExecutor for ClearOrResetExecutor {
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(targets)) = args.get(&ARG_TARGETS) else {
return Err(CommandError::InvalidConsumption(Some(ARG_TARGETS.into())));
};
let reset = self.0;
for target in targets {
target.client.enqueue_packet(&CClearTitle::new(reset)).await;
}
sender
.send_message(if targets.len() == 1 {
let text = if reset {
"commands.title.reset.single"
} else {
"commands.title.cleared.single"
};
TextComponent::translate_cross(
text,
text,
[targets[0].get_display_name().await],
)
} else {
let text = if reset {
"commands.title.reset.multiple"
} else {
"commands.title.cleared.multiple"
};
TextComponent::translate_cross(
text,
text,
[TextComponent::text(targets.len().to_string())],
)
})
.await;
Ok(targets.len() as i32)
})
}
}
struct TitleExecutor(TitleMode);
impl CommandExecutor for TitleExecutor {
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(targets)) = args.get(&ARG_TARGETS) else {
return Err(CommandError::InvalidConsumption(Some(ARG_TARGETS.into())));
};
let text = TextComponentArgConsumer::find_arg(args, ARG_TITLE)?;
let mode = &self.0;
for target in targets {
target.show_title(&text, mode).await;
}
let mode_name = format!("{mode:?}").to_lowercase();
sender
.send_message(if targets.len() == 1 {
TextComponent::translate_cross(
format!("commands.title.show.{mode_name}.single").clone(),
format!("commands.title.show.{mode_name}.single"),
[targets[0].get_display_name().await],
)
} else {
TextComponent::translate_cross(
format!("commands.title.show.{mode_name}.multiple"),
format!("commands.title.show.{mode_name}.multiple"),
[TextComponent::text(targets.len().to_string())],
)
})
.await;
Ok(targets.len() as i32)
})
}
}
struct TimesTitleExecutor;
impl CommandExecutor for TimesTitleExecutor {
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(targets)) = args.get(&ARG_TARGETS) else {
return Err(CommandError::InvalidConsumption(Some(ARG_TARGETS.into())));
};
let fade_in = TimeArgumentConsumer::find_arg(args, ARG_FADE_IN)?;
let stay = TimeArgumentConsumer::find_arg(args, ARG_STAY)?;
let fade_out = TimeArgumentConsumer::find_arg(args, ARG_FADE_OUT)?;
for target in targets {
target.send_title_animation(fade_in, stay, fade_out).await;
}
sender
.send_message(if targets.len() == 1 {
TextComponent::translate_cross(
"commands.title.times.single",
"commands.title.times.single",
[targets[0].get_display_name().await],
)
} else {
TextComponent::translate_cross(
"commands.title.times.multiple",
"commands.title.times.multiple",
[TextComponent::text(targets.len().to_string())],
)
})
.await;
Ok(targets.len() as i32)
})
}
}
pub fn init_command_tree() -> CommandTree {
CommandTree::new(NAMES, DESCRIPTION).then(
argument(ARG_TARGETS, PlayersArgumentConsumer)
.then(literal("clear").execute(ClearOrResetExecutor(false)))
.then(literal("reset").execute(ClearOrResetExecutor(true)))
.then(
literal("title").then(
argument(ARG_TITLE, TextComponentArgConsumer)
.execute(TitleExecutor(TitleMode::Title)),
),
)
.then(
literal("subtitle").then(
argument(ARG_TITLE, TextComponentArgConsumer)
.execute(TitleExecutor(TitleMode::SubTitle)),
),
)
.then(
literal("actionbar").then(
argument(ARG_TITLE, TextComponentArgConsumer)
.execute(TitleExecutor(TitleMode::ActionBar)),
),
)
.then(literal("times").then(
argument(ARG_FADE_IN, TimeArgumentConsumer).then(
argument(ARG_STAY, TimeArgumentConsumer).then(
argument(ARG_FADE_OUT, TimeArgumentConsumer).execute(TimesTitleExecutor),
),
),
)),
)
}