feat: add waypoint command
This commit is contained in:
parent
7e60303689
commit
19ccd152f1
@ -26,6 +26,20 @@ impl CUpdateTime {
|
|||||||
clock_updates: vec![(overworld_id, day_time, partial_tick, rate)],
|
clock_updates: vec![(overworld_id, day_time, partial_tick, rate)],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn new_clock(
|
||||||
|
game_time: i64,
|
||||||
|
clock_id: i32,
|
||||||
|
total_ticks: i64,
|
||||||
|
partial_tick: f32,
|
||||||
|
rate: f32,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
game_time,
|
||||||
|
clock_updates: vec![(clock_id, total_ticks, partial_tick, rate)],
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientPacket for CUpdateTime {
|
impl ClientPacket for CUpdateTime {
|
||||||
|
|||||||
@ -329,6 +329,35 @@ impl NamedColor {
|
|||||||
Self::White => 'f',
|
Self::White => 'f',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the Minecraft string identifier of this named color (e.g. `"black"`, `"dark_blue"`).
|
||||||
|
#[must_use]
|
||||||
|
pub const fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Black => "black",
|
||||||
|
Self::DarkBlue => "dark_blue",
|
||||||
|
Self::DarkGreen => "dark_green",
|
||||||
|
Self::DarkAqua => "dark_aqua",
|
||||||
|
Self::DarkRed => "dark_red",
|
||||||
|
Self::DarkPurple => "dark_purple",
|
||||||
|
Self::Gold => "gold",
|
||||||
|
Self::Gray => "gray",
|
||||||
|
Self::DarkGray => "dark_gray",
|
||||||
|
Self::Blue => "blue",
|
||||||
|
Self::Green => "green",
|
||||||
|
Self::Aqua => "aqua",
|
||||||
|
Self::Red => "red",
|
||||||
|
Self::LightPurple => "light_purple",
|
||||||
|
Self::Yellow => "yellow",
|
||||||
|
Self::White => "white",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for NamedColor {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(self.name())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&str> for NamedColor {
|
impl TryFrom<&str> for NamedColor {
|
||||||
|
|||||||
132
crates/pumpkin/src/command/args/hex_color.rs
Normal file
132
crates/pumpkin/src/command/args/hex_color.rs
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
use pumpkin_data::translation;
|
||||||
|
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders};
|
||||||
|
use pumpkin_util::text::TextComponent;
|
||||||
|
|
||||||
|
use crate::command::CommandSender;
|
||||||
|
use crate::command::args::{
|
||||||
|
Arg, ArgumentConsumer, ConsumeResult, ConsumeResultWithSyntax, DefaultNameArgConsumer, FindArg,
|
||||||
|
GetClientSideArgParser, SuggestResult,
|
||||||
|
};
|
||||||
|
use crate::command::dispatcher::CommandError;
|
||||||
|
use crate::command::errors::error_types::CommandErrorType;
|
||||||
|
use crate::command::tree::RawArgs;
|
||||||
|
use crate::server::Server;
|
||||||
|
|
||||||
|
pub const INVALID_HEX_ERROR_TYPE: CommandErrorType<1> = CommandErrorType::new(
|
||||||
|
translation::java::ARGUMENT_HEXCOLOR_INVALID,
|
||||||
|
translation::java::ARGUMENT_HEXCOLOR_INVALID,
|
||||||
|
);
|
||||||
|
|
||||||
|
pub struct HexColorArgumentConsumer;
|
||||||
|
|
||||||
|
fn parse_hex_color(color_str: &str) -> Option<u32> {
|
||||||
|
match color_str.len() {
|
||||||
|
3 => {
|
||||||
|
let r = u32::from_str_radix(&color_str[0..1], 16).ok()? * 17;
|
||||||
|
let g = u32::from_str_radix(&color_str[1..2], 16).ok()? * 17;
|
||||||
|
let b = u32::from_str_radix(&color_str[2..3], 16).ok()? * 17;
|
||||||
|
Some((r << 16) | (g << 8) | b)
|
||||||
|
}
|
||||||
|
6 => {
|
||||||
|
let r = u32::from_str_radix(&color_str[0..2], 16).ok()?;
|
||||||
|
let g = u32::from_str_radix(&color_str[2..4], 16).ok()?;
|
||||||
|
let b = u32::from_str_radix(&color_str[4..6], 16).ok()?;
|
||||||
|
Some((r << 16) | (g << 8) | b)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetClientSideArgParser for HexColorArgumentConsumer {
|
||||||
|
fn get_client_side_parser(&self) -> ArgumentType {
|
||||||
|
ArgumentType::HexColor
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_client_side_suggestion_type_override(&self) -> Option<SuggestionProviders> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArgumentConsumer for HexColorArgumentConsumer {
|
||||||
|
fn consume<'a, 'b>(
|
||||||
|
&'a self,
|
||||||
|
_sender: &'a CommandSender,
|
||||||
|
_server: &'a Server,
|
||||||
|
args: &'b mut RawArgs<'a>,
|
||||||
|
) -> ConsumeResult<'a> {
|
||||||
|
let s_opt: Option<&'a str> = args.pop().map(|arg| arg.value);
|
||||||
|
|
||||||
|
let result = s_opt.and_then(|s| parse_hex_color(s).map(Arg::HexColor));
|
||||||
|
|
||||||
|
Box::pin(async move { result })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn consume_with_syntax<'a>(
|
||||||
|
&'a self,
|
||||||
|
_sender: &'a CommandSender,
|
||||||
|
_server: &'a Server,
|
||||||
|
args: &mut RawArgs<'a>,
|
||||||
|
) -> ConsumeResultWithSyntax<'a> {
|
||||||
|
let Some(raw_arg) = args.pop() else {
|
||||||
|
return Box::pin(async { Ok(None) });
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = parse_hex_color(raw_arg.value)
|
||||||
|
.map(|color| Some(Arg::HexColor(color)))
|
||||||
|
.ok_or_else(|| {
|
||||||
|
INVALID_HEX_ERROR_TYPE.create_without_context(TextComponent::translate(
|
||||||
|
translation::java::ARGUMENT_HEXCOLOR_INVALID,
|
||||||
|
[TextComponent::text(raw_arg.value.to_string())],
|
||||||
|
))
|
||||||
|
});
|
||||||
|
|
||||||
|
Box::pin(async move { result })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn suggest<'a>(
|
||||||
|
&'a self,
|
||||||
|
_sender: &CommandSender,
|
||||||
|
_server: &'a Server,
|
||||||
|
_input: &'a str,
|
||||||
|
) -> SuggestResult<'a> {
|
||||||
|
let suggestions = vec![
|
||||||
|
CommandSuggestion::new("F00".to_string(), None),
|
||||||
|
CommandSuggestion::new("FF0000".to_string(), None),
|
||||||
|
];
|
||||||
|
Box::pin(async move { Ok(Some(suggestions)) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DefaultNameArgConsumer for HexColorArgumentConsumer {
|
||||||
|
fn default_name(&self) -> &'static str {
|
||||||
|
"color"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FindArg<'a> for HexColorArgumentConsumer {
|
||||||
|
type Data = u32;
|
||||||
|
|
||||||
|
fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result<Self::Data, CommandError> {
|
||||||
|
match args.get(name) {
|
||||||
|
Some(Arg::HexColor(color)) => Ok(*color),
|
||||||
|
_ => Err(CommandError::InvalidConsumption(Some(name.to_string()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_hex_color_works() {
|
||||||
|
assert_eq!(parse_hex_color("F00"), Some(0xFF0000));
|
||||||
|
assert_eq!(parse_hex_color("0F0"), Some(0x00FF00));
|
||||||
|
assert_eq!(parse_hex_color("00F"), Some(0x0000FF));
|
||||||
|
assert_eq!(parse_hex_color("FF0000"), Some(0xFF0000));
|
||||||
|
assert_eq!(parse_hex_color("123456"), Some(0x123456));
|
||||||
|
assert_eq!(parse_hex_color("FFFFF"), None);
|
||||||
|
assert_eq!(parse_hex_color("F"), None);
|
||||||
|
assert_eq!(parse_hex_color("GGGGGG"), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -41,6 +41,7 @@ pub mod entity;
|
|||||||
pub mod entity_anchor;
|
pub mod entity_anchor;
|
||||||
pub mod gamemode;
|
pub mod gamemode;
|
||||||
pub mod gameprofile;
|
pub mod gameprofile;
|
||||||
|
pub mod hex_color;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
pub mod players;
|
pub mod players;
|
||||||
pub mod position_2d;
|
pub mod position_2d;
|
||||||
@ -54,6 +55,7 @@ pub mod slot;
|
|||||||
pub mod sound;
|
pub mod sound;
|
||||||
pub mod sound_category;
|
pub mod sound_category;
|
||||||
pub mod summonable_entities;
|
pub mod summonable_entities;
|
||||||
|
pub mod team_color;
|
||||||
pub mod textcomponent;
|
pub mod textcomponent;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
|
|
||||||
@ -143,6 +145,8 @@ pub enum Arg<'a> {
|
|||||||
EntityAnchor(EntityAnchor),
|
EntityAnchor(EntityAnchor),
|
||||||
Slot(usize, String),
|
Slot(usize, String),
|
||||||
Slots(&'static [usize], String),
|
Slots(&'static [usize], String),
|
||||||
|
TeamColor(pumpkin_util::text::color::NamedColor),
|
||||||
|
HexColor(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// see [`crate::command::tree::builder::argument`] and [`CommandTree::execute`]/[`crate::command::tree::builder::NonLeafNodeBuilder::execute`]
|
/// see [`crate::command::tree::builder::argument`] and [`CommandTree::execute`]/[`crate::command::tree::builder::NonLeafNodeBuilder::execute`]
|
||||||
|
|||||||
130
crates/pumpkin/src/command/args/team_color.rs
Normal file
130
crates/pumpkin/src/command/args/team_color.rs
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
use pumpkin_data::translation;
|
||||||
|
use pumpkin_protocol::java::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders};
|
||||||
|
use pumpkin_util::text::TextComponent;
|
||||||
|
use pumpkin_util::text::color::NamedColor;
|
||||||
|
|
||||||
|
use crate::command::CommandSender;
|
||||||
|
use crate::command::args::{
|
||||||
|
Arg, ArgumentConsumer, ConsumeResult, ConsumeResultWithSyntax, DefaultNameArgConsumer, FindArg,
|
||||||
|
GetClientSideArgParser, SuggestResult,
|
||||||
|
};
|
||||||
|
use crate::command::dispatcher::CommandError;
|
||||||
|
use crate::command::errors::error_types::CommandErrorType;
|
||||||
|
use crate::command::tree::RawArgs;
|
||||||
|
use crate::server::Server;
|
||||||
|
|
||||||
|
pub const INVALID_COLOR_ERROR_TYPE: CommandErrorType<1> = CommandErrorType::new(
|
||||||
|
translation::java::ARGUMENT_COLOR_INVALID,
|
||||||
|
translation::java::ARGUMENT_COLOR_INVALID,
|
||||||
|
);
|
||||||
|
|
||||||
|
const TEAM_COLORS: [&str; 16] = [
|
||||||
|
"black",
|
||||||
|
"dark_blue",
|
||||||
|
"dark_green",
|
||||||
|
"dark_aqua",
|
||||||
|
"dark_red",
|
||||||
|
"dark_purple",
|
||||||
|
"gold",
|
||||||
|
"gray",
|
||||||
|
"dark_gray",
|
||||||
|
"blue",
|
||||||
|
"green",
|
||||||
|
"aqua",
|
||||||
|
"red",
|
||||||
|
"light_purple",
|
||||||
|
"yellow",
|
||||||
|
"white",
|
||||||
|
];
|
||||||
|
|
||||||
|
pub struct TeamColorArgumentConsumer;
|
||||||
|
|
||||||
|
impl GetClientSideArgParser for TeamColorArgumentConsumer {
|
||||||
|
fn get_client_side_parser(&self) -> ArgumentType {
|
||||||
|
ArgumentType::Color
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_client_side_suggestion_type_override(&self) -> Option<SuggestionProviders> {
|
||||||
|
Some(SuggestionProviders::AskServer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArgumentConsumer for TeamColorArgumentConsumer {
|
||||||
|
fn consume<'a, 'b>(
|
||||||
|
&'a self,
|
||||||
|
_sender: &'a CommandSender,
|
||||||
|
_server: &'a Server,
|
||||||
|
args: &'b mut RawArgs<'a>,
|
||||||
|
) -> ConsumeResult<'a> {
|
||||||
|
let s_opt: Option<&'a str> = args.pop().map(|arg| arg.value);
|
||||||
|
|
||||||
|
let result = s_opt.and_then(|s| NamedColor::try_from(s).ok().map(Arg::TeamColor));
|
||||||
|
|
||||||
|
Box::pin(async move { result })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn consume_with_syntax<'a>(
|
||||||
|
&'a self,
|
||||||
|
_sender: &'a CommandSender,
|
||||||
|
_server: &'a Server,
|
||||||
|
args: &mut RawArgs<'a>,
|
||||||
|
) -> ConsumeResultWithSyntax<'a> {
|
||||||
|
let Some(raw_arg) = args.pop() else {
|
||||||
|
return Box::pin(async { Ok(None) });
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = NamedColor::try_from(raw_arg.value)
|
||||||
|
.map(|color| Some(Arg::TeamColor(color)))
|
||||||
|
.map_err(|()| {
|
||||||
|
INVALID_COLOR_ERROR_TYPE.create_without_context(TextComponent::translate(
|
||||||
|
translation::java::ARGUMENT_COLOR_INVALID,
|
||||||
|
[TextComponent::text(raw_arg.value.to_string())],
|
||||||
|
))
|
||||||
|
});
|
||||||
|
|
||||||
|
Box::pin(async move { result })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn suggest<'a>(
|
||||||
|
&'a self,
|
||||||
|
_sender: &CommandSender,
|
||||||
|
_server: &'a Server,
|
||||||
|
input: &'a str,
|
||||||
|
) -> SuggestResult<'a> {
|
||||||
|
let suggestions: Vec<CommandSuggestion> = TEAM_COLORS
|
||||||
|
.iter()
|
||||||
|
.filter(|color| color.starts_with(input))
|
||||||
|
.map(|color| CommandSuggestion::new((*color).to_string(), None))
|
||||||
|
.collect();
|
||||||
|
Box::pin(async move { Ok(Some(suggestions)) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DefaultNameArgConsumer for TeamColorArgumentConsumer {
|
||||||
|
fn default_name(&self) -> &'static str {
|
||||||
|
"color"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FindArg<'a> for TeamColorArgumentConsumer {
|
||||||
|
type Data = NamedColor;
|
||||||
|
|
||||||
|
fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result<Self::Data, CommandError> {
|
||||||
|
match args.get(name) {
|
||||||
|
Some(Arg::TeamColor(color)) => Ok(*color),
|
||||||
|
_ => Err(CommandError::InvalidConsumption(Some(name.to_string()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_team_color() {
|
||||||
|
assert_eq!(NamedColor::try_from("red"), Ok(NamedColor::Red));
|
||||||
|
assert_eq!(NamedColor::try_from("dark_blue"), Ok(NamedColor::DarkBlue));
|
||||||
|
assert_eq!(NamedColor::try_from("invalid"), Err(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,11 +11,32 @@ use crate::command::{
|
|||||||
};
|
};
|
||||||
use crate::server::Server;
|
use crate::server::Server;
|
||||||
|
|
||||||
pub struct TimeArgumentConsumer;
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct TimeArgumentConsumer {
|
||||||
|
min: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TimeArgumentConsumer {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimeArgumentConsumer {
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self { min: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn min(min: i32) -> Self {
|
||||||
|
Self { min }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl GetClientSideArgParser for TimeArgumentConsumer {
|
impl GetClientSideArgParser for TimeArgumentConsumer {
|
||||||
fn get_client_side_parser(&self) -> ArgumentType {
|
fn get_client_side_parser(&self) -> ArgumentType {
|
||||||
ArgumentType::Time { min: 0 }
|
ArgumentType::Time { min: self.min }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_client_side_suggestion_type_override(&self) -> Option<SuggestionProviders> {
|
fn get_client_side_suggestion_type_override(&self) -> Option<SuggestionProviders> {
|
||||||
@ -34,23 +55,23 @@ impl ArgumentConsumer for TimeArgumentConsumer {
|
|||||||
|
|
||||||
let result: Option<Arg<'a>> = s_opt.and_then(|s| {
|
let result: Option<Arg<'a>> = s_opt.and_then(|s| {
|
||||||
let (num_str, unit) = s
|
let (num_str, unit) = s
|
||||||
.find(|c: char| c.is_alphabetic())
|
.find(|c: char| c.is_alphabetic() && c != '-')
|
||||||
.map_or((s, "t"), |pos| (&s[..pos], &s[pos..]));
|
.map_or((s, "t"), |pos| (&s[..pos], &s[pos..]));
|
||||||
|
|
||||||
let number = num_str.parse::<f32>().ok()?; // Replaces .ok()?
|
let number = num_str.parse::<f32>().ok()?;
|
||||||
|
|
||||||
if number < 0.0 {
|
let factor = match unit {
|
||||||
return None;
|
"d" => 24000.0,
|
||||||
}
|
"s" => 20.0,
|
||||||
|
"t" | "" => 1.0,
|
||||||
let ticks = match unit {
|
|
||||||
"d" => number * 24000.0,
|
|
||||||
"s" => number * 20.0,
|
|
||||||
"t" => number,
|
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let ticks = ticks.round() as i32;
|
let ticks = (number * factor).round() as i32;
|
||||||
|
|
||||||
|
if ticks < self.min {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
Some(Arg::Time(ticks))
|
Some(Arg::Time(ticks))
|
||||||
});
|
});
|
||||||
|
|||||||
@ -77,6 +77,7 @@ mod title;
|
|||||||
mod tps;
|
mod tps;
|
||||||
mod transfer;
|
mod transfer;
|
||||||
mod trigger;
|
mod trigger;
|
||||||
|
mod waypoint;
|
||||||
mod weather;
|
mod weather;
|
||||||
mod whitelist;
|
mod whitelist;
|
||||||
mod worldborder;
|
mod worldborder;
|
||||||
@ -150,6 +151,7 @@ pub async fn default_dispatcher(
|
|||||||
);
|
);
|
||||||
dispatcher.register(spectate::init_command_tree(), "minecraft:command.spectate");
|
dispatcher.register(spectate::init_command_tree(), "minecraft:command.spectate");
|
||||||
dispatcher.register(data::init_command_tree(), "minecraft:command.data");
|
dispatcher.register(data::init_command_tree(), "minecraft:command.data");
|
||||||
|
dispatcher.register(waypoint::init_command_tree(), "minecraft:command.waypoint");
|
||||||
// Three
|
// Three
|
||||||
dispatcher.register(deop::init_command_tree(), "minecraft:command.deop");
|
dispatcher.register(deop::init_command_tree(), "minecraft:command.deop");
|
||||||
dispatcher.register(kick::init_command_tree(), "minecraft:command.kick");
|
dispatcher.register(kick::init_command_tree(), "minecraft:command.kick");
|
||||||
@ -281,6 +283,13 @@ fn register_level_2_permissions(registry: &mut PermissionRegistry) {
|
|||||||
PermissionDefault::Op(PermissionLvl::Two),
|
PermissionDefault::Op(PermissionLvl::Two),
|
||||||
))
|
))
|
||||||
.expect("Permission already registered");
|
.expect("Permission already registered");
|
||||||
|
registry
|
||||||
|
.register_permission(Permission::new(
|
||||||
|
"minecraft:command.waypoint",
|
||||||
|
"List or modify waypoints",
|
||||||
|
PermissionDefault::Op(PermissionLvl::Two),
|
||||||
|
))
|
||||||
|
.expect("Permission already registered");
|
||||||
registry
|
registry
|
||||||
.register_permission(Permission::new(
|
.register_permission(Permission::new(
|
||||||
"minecraft:command.give",
|
"minecraft:command.give",
|
||||||
|
|||||||
@ -449,6 +449,50 @@ impl CommandExecutor for TeamListExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct TeamModifyColorResetExecutor;
|
||||||
|
|
||||||
|
impl CommandExecutor for TeamModifyColorResetExecutor {
|
||||||
|
fn execute<'a>(&'a self, context: &'a CommandContext) -> CommandExecutorResult<'a> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let team_name = TeamArgumentType::get(context, ARG_TEAM)?;
|
||||||
|
|
||||||
|
let world = context.world();
|
||||||
|
let mut scoreboard = world.scoreboard.lock().await;
|
||||||
|
|
||||||
|
let mut team = scoreboard
|
||||||
|
.get_teams()
|
||||||
|
.get(team_name)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
TEAM_NOT_FOUND_ERROR
|
||||||
|
.create_without_context(TextComponent::text(team_name.to_string()))
|
||||||
|
})?
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
if team.color == NamedColor::White {
|
||||||
|
return Err(COLOR_UNCHANGED_ERROR.create_without_context());
|
||||||
|
}
|
||||||
|
|
||||||
|
team.color = NamedColor::White;
|
||||||
|
let team_display_name = team.display_name.clone();
|
||||||
|
scoreboard.update_team(world, team);
|
||||||
|
|
||||||
|
context
|
||||||
|
.source
|
||||||
|
.send_feedback(
|
||||||
|
TextComponent::translate_cross(
|
||||||
|
translation::java::COMMANDS_TEAM_OPTION_COLOR_CLEAR_SUCCESS,
|
||||||
|
translation::java::COMMANDS_TEAM_OPTION_COLOR_CLEAR_SUCCESS,
|
||||||
|
[team_display_name],
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct TeamModifyColorExecutor;
|
struct TeamModifyColorExecutor;
|
||||||
|
|
||||||
impl CommandExecutor for TeamModifyColorExecutor {
|
impl CommandExecutor for TeamModifyColorExecutor {
|
||||||
@ -483,10 +527,7 @@ impl CommandExecutor for TeamModifyColorExecutor {
|
|||||||
TextComponent::translate_cross(
|
TextComponent::translate_cross(
|
||||||
translation::java::COMMANDS_TEAM_OPTION_COLOR_SUCCESS,
|
translation::java::COMMANDS_TEAM_OPTION_COLOR_SUCCESS,
|
||||||
translation::java::COMMANDS_TEAM_OPTION_COLOR_SUCCESS,
|
translation::java::COMMANDS_TEAM_OPTION_COLOR_SUCCESS,
|
||||||
[
|
[team_display_name, TextComponent::text(new_color.name())],
|
||||||
team_display_name,
|
|
||||||
TextComponent::text(format!("{new_color:?}").to_lowercase()),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
@ -962,13 +1003,17 @@ fn list_branch() -> LiteralArgumentBuilder {
|
|||||||
.then(argument(ARG_TEAM, TeamArgumentType).executes(TeamListExecutor { has_team: true }))
|
.then(argument(ARG_TEAM, TeamArgumentType).executes(TeamListExecutor { has_team: true }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::too_many_lines)]
|
||||||
fn modify_branch() -> LiteralArgumentBuilder {
|
fn modify_branch() -> LiteralArgumentBuilder {
|
||||||
literal("modify").then(
|
literal("modify").then(
|
||||||
argument(ARG_TEAM, TeamArgumentType)
|
argument(ARG_TEAM, TeamArgumentType)
|
||||||
.then(
|
.then(
|
||||||
literal("color").then(
|
literal("color")
|
||||||
argument(ARG_VALUE, TeamColorArgumentType).executes(TeamModifyColorExecutor),
|
.then(literal("reset").executes(TeamModifyColorResetExecutor))
|
||||||
),
|
.then(
|
||||||
|
argument(ARG_VALUE, TeamColorArgumentType)
|
||||||
|
.executes(TeamModifyColorExecutor),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.then(
|
.then(
|
||||||
literal("displayName").then(
|
literal("displayName").then(
|
||||||
|
|||||||
@ -2,14 +2,21 @@ use pumpkin_data::translation;
|
|||||||
use pumpkin_util::text::TextComponent;
|
use pumpkin_util::text::TextComponent;
|
||||||
|
|
||||||
use crate::command::CommandResult;
|
use crate::command::CommandResult;
|
||||||
use crate::command::args::{FindArg, time::TimeArgumentConsumer};
|
use crate::command::args::{
|
||||||
|
FindArg, bounded_num::BoundedNumArgumentConsumer,
|
||||||
|
resource_location::ResourceLocationArgumentConsumer, time::TimeArgumentConsumer,
|
||||||
|
};
|
||||||
use crate::command::dispatcher::CommandError;
|
use crate::command::dispatcher::CommandError;
|
||||||
use crate::command::tree::builder::{argument, literal};
|
use crate::command::tree::builder::{argument, literal};
|
||||||
use crate::command::{CommandExecutor, CommandSender, ConsumedArgs, tree::CommandTree};
|
use crate::command::{CommandExecutor, CommandSender, ConsumedArgs, tree::CommandTree};
|
||||||
|
|
||||||
const NAMES: [&str; 1] = ["time"];
|
const NAMES: [&str; 1] = ["time"];
|
||||||
const DESCRIPTION: &str = "Query the world time.";
|
const DESCRIPTION: &str = "Query or modify the world time and clocks.";
|
||||||
const ARG_TIME: &str = "time";
|
const ARG_TIME: &str = "time";
|
||||||
|
const ARG_RATE: &str = "rate";
|
||||||
|
const ARG_CLOCK: &str = "clock";
|
||||||
|
|
||||||
|
const DEFAULT_CLOCK: &str = "minecraft:overworld";
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
enum PresetTime {
|
enum PresetTime {
|
||||||
@ -31,18 +38,26 @@ impl PresetTime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
enum Mode {
|
enum Action {
|
||||||
Add,
|
|
||||||
Set(Option<PresetTime>),
|
Set(Option<PresetTime>),
|
||||||
|
Add,
|
||||||
|
Pause,
|
||||||
|
Resume,
|
||||||
|
Rate,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
enum QueryMode {
|
enum QueryMode {
|
||||||
DayTime,
|
Time,
|
||||||
GameTime,
|
GameTime,
|
||||||
|
DayTime,
|
||||||
Day,
|
Day,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fn wrap_time(ticks: i64) -> i32 {
|
||||||
|
(ticks % 2_147_483_647) as i32
|
||||||
|
}
|
||||||
|
|
||||||
struct QueryExecutor(QueryMode);
|
struct QueryExecutor(QueryMode);
|
||||||
|
|
||||||
impl CommandExecutor for QueryExecutor {
|
impl CommandExecutor for QueryExecutor {
|
||||||
@ -50,42 +65,72 @@ impl CommandExecutor for QueryExecutor {
|
|||||||
&'a self,
|
&'a self,
|
||||||
sender: &'a CommandSender,
|
sender: &'a CommandSender,
|
||||||
server: &'a crate::server::Server,
|
server: &'a crate::server::Server,
|
||||||
_args: &'a ConsumedArgs<'a>,
|
args: &'a ConsumedArgs<'a>,
|
||||||
) -> CommandResult<'a> {
|
) -> CommandResult<'a> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
|
let clock_name = ResourceLocationArgumentConsumer::find_arg(args, ARG_CLOCK)
|
||||||
|
.unwrap_or(DEFAULT_CLOCK);
|
||||||
let mode = self.0;
|
let mode = self.0;
|
||||||
// TODO: Maybe ask player for world, or get the current world
|
|
||||||
let worlds = server.worlds.load();
|
let worlds = server.worlds.load();
|
||||||
let world = worlds
|
let world = worlds
|
||||||
.first()
|
.first()
|
||||||
.expect("There should always be at least one world");
|
.expect("There should always be at least one world");
|
||||||
let level_time = world.level_time.lock().await;
|
let level_time = world.level_time.lock().await;
|
||||||
|
|
||||||
let curr_time = match mode {
|
match mode {
|
||||||
QueryMode::DayTime => level_time.query_daytime(),
|
QueryMode::GameTime => {
|
||||||
QueryMode::GameTime => level_time.query_gametime(),
|
let game_time = level_time.query_gametime();
|
||||||
QueryMode::Day => level_time.query_day(),
|
sender
|
||||||
};
|
.send_message(TextComponent::translate(
|
||||||
let bedrock_key = match mode {
|
translation::java::COMMANDS_TIME_QUERY_GAMETIME,
|
||||||
QueryMode::DayTime => translation::bedrock::COMMANDS_TIME_QUERY_DAYTIME,
|
[TextComponent::text(game_time.to_string())],
|
||||||
QueryMode::GameTime => translation::bedrock::COMMANDS_TIME_QUERY_GAMETIME,
|
))
|
||||||
QueryMode::Day => translation::bedrock::COMMANDS_TIME_QUERY_DAY,
|
.await;
|
||||||
};
|
Ok(wrap_time(game_time))
|
||||||
sender
|
}
|
||||||
.send_message(TextComponent::translate_cross(
|
QueryMode::Time => {
|
||||||
translation::java::COMMANDS_TIME_QUERY,
|
let total_ticks = level_time.time_of_day;
|
||||||
bedrock_key,
|
sender
|
||||||
[TextComponent::text(curr_time.to_string())],
|
.send_message(TextComponent::translate(
|
||||||
))
|
translation::java::COMMANDS_TIME_QUERY_ABSOLUTE,
|
||||||
.await;
|
[
|
||||||
Ok(curr_time as i32)
|
TextComponent::text(clock_name.to_string()),
|
||||||
|
TextComponent::text(total_ticks.to_string()),
|
||||||
|
],
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
Ok(wrap_time(total_ticks))
|
||||||
|
}
|
||||||
|
QueryMode::DayTime => {
|
||||||
|
let curr_time = level_time.query_daytime();
|
||||||
|
sender
|
||||||
|
.send_message(TextComponent::translate_cross(
|
||||||
|
translation::java::COMMANDS_TIME_QUERY,
|
||||||
|
translation::bedrock::COMMANDS_TIME_QUERY_DAYTIME,
|
||||||
|
[TextComponent::text(curr_time.to_string())],
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
Ok(curr_time as i32)
|
||||||
|
}
|
||||||
|
QueryMode::Day => {
|
||||||
|
let curr_time = level_time.query_day();
|
||||||
|
sender
|
||||||
|
.send_message(TextComponent::translate_cross(
|
||||||
|
translation::java::COMMANDS_TIME_QUERY,
|
||||||
|
translation::bedrock::COMMANDS_TIME_QUERY_DAY,
|
||||||
|
[TextComponent::text(curr_time.to_string())],
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
Ok(curr_time as i32)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ChangeExecutor(Mode);
|
struct ActionExecutor(Action);
|
||||||
|
|
||||||
impl CommandExecutor for ChangeExecutor {
|
impl CommandExecutor for ActionExecutor {
|
||||||
fn execute<'a>(
|
fn execute<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
sender: &'a CommandSender,
|
sender: &'a CommandSender,
|
||||||
@ -93,81 +138,194 @@ impl CommandExecutor for ChangeExecutor {
|
|||||||
args: &'a ConsumedArgs<'a>,
|
args: &'a ConsumedArgs<'a>,
|
||||||
) -> CommandResult<'a> {
|
) -> CommandResult<'a> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let time_count = if let Mode::Set(Some(preset)) = &self.0 {
|
let clock_name = ResourceLocationArgumentConsumer::find_arg(args, ARG_CLOCK)
|
||||||
preset.to_ticks()
|
.unwrap_or(DEFAULT_CLOCK);
|
||||||
} else if let Ok(ticks) = TimeArgumentConsumer::find_arg(args, ARG_TIME) {
|
let action = self.0;
|
||||||
ticks
|
|
||||||
} else {
|
|
||||||
return Err(CommandError::CommandFailed(TextComponent::text(
|
|
||||||
"Invalid time specified.",
|
|
||||||
)));
|
|
||||||
};
|
|
||||||
|
|
||||||
let mode = self.0;
|
|
||||||
// TODO: Maybe ask player for world, or get the current world
|
|
||||||
let worlds = server.worlds.load();
|
let worlds = server.worlds.load();
|
||||||
let world = worlds
|
let world = worlds
|
||||||
.first()
|
.first()
|
||||||
.expect("There should always be at least one world");
|
.expect("There should always be at least one world");
|
||||||
let mut level_time = world.level_time.lock().await;
|
let mut level_time = world.level_time.lock().await;
|
||||||
|
|
||||||
match mode {
|
match action {
|
||||||
Mode::Add => {
|
Action::Set(preset) => {
|
||||||
// add
|
let time_count = if let Some(p) = preset {
|
||||||
level_time.add_time(time_count.into());
|
p.to_ticks()
|
||||||
level_time.send_time(world).await;
|
} else if let Ok(ticks) = TimeArgumentConsumer::find_arg(args, ARG_TIME) {
|
||||||
let curr_time = level_time.query_daytime();
|
ticks
|
||||||
sender
|
} else {
|
||||||
.send_message(TextComponent::translate_cross(
|
return Err(CommandError::CommandFailed(TextComponent::text(
|
||||||
translation::java::COMMANDS_TIME_SET,
|
"Invalid time specified.",
|
||||||
translation::bedrock::COMMANDS_TIME_SET,
|
)));
|
||||||
[TextComponent::text(curr_time.to_string())],
|
};
|
||||||
))
|
|
||||||
.await;
|
|
||||||
Ok(curr_time as i32)
|
|
||||||
}
|
|
||||||
Mode::Set(_) => {
|
|
||||||
// set
|
|
||||||
level_time.set_time(time_count.into());
|
level_time.set_time(time_count.into());
|
||||||
level_time.send_time(world).await;
|
level_time.send_time(world).await;
|
||||||
sender
|
sender
|
||||||
.send_message(TextComponent::translate_cross(
|
.send_message(TextComponent::translate(
|
||||||
translation::java::COMMANDS_TIME_SET,
|
translation::java::COMMANDS_TIME_SET_ABSOLUTE,
|
||||||
translation::bedrock::COMMANDS_TIME_SET,
|
[
|
||||||
[TextComponent::text(time_count.to_string())],
|
TextComponent::text(clock_name.to_string()),
|
||||||
|
TextComponent::text(time_count.to_string()),
|
||||||
|
],
|
||||||
))
|
))
|
||||||
.await;
|
.await;
|
||||||
Ok(time_count)
|
Ok(time_count)
|
||||||
}
|
}
|
||||||
|
Action::Add => {
|
||||||
|
let time_count = TimeArgumentConsumer::find_arg(args, ARG_TIME)?;
|
||||||
|
level_time.add_time(time_count.into());
|
||||||
|
level_time.send_time(world).await;
|
||||||
|
let total_ticks = level_time.time_of_day;
|
||||||
|
sender
|
||||||
|
.send_message(TextComponent::translate(
|
||||||
|
translation::java::COMMANDS_TIME_SET_ABSOLUTE,
|
||||||
|
[
|
||||||
|
TextComponent::text(clock_name.to_string()),
|
||||||
|
TextComponent::text(total_ticks.to_string()),
|
||||||
|
],
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
Ok(wrap_time(total_ticks))
|
||||||
|
}
|
||||||
|
Action::Pause => {
|
||||||
|
level_time.set_paused(true);
|
||||||
|
level_time.send_time(world).await;
|
||||||
|
sender
|
||||||
|
.send_message(TextComponent::translate(
|
||||||
|
translation::java::COMMANDS_TIME_PAUSE,
|
||||||
|
[TextComponent::text(clock_name.to_string())],
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
Ok(1)
|
||||||
|
}
|
||||||
|
Action::Resume => {
|
||||||
|
level_time.set_paused(false);
|
||||||
|
level_time.send_time(world).await;
|
||||||
|
sender
|
||||||
|
.send_message(TextComponent::translate(
|
||||||
|
translation::java::COMMANDS_TIME_RESUME,
|
||||||
|
[TextComponent::text(clock_name.to_string())],
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
Ok(1)
|
||||||
|
}
|
||||||
|
Action::Rate => {
|
||||||
|
let rate_res = BoundedNumArgumentConsumer::<f32>::find_arg(args, ARG_RATE)?;
|
||||||
|
let rate = match rate_res {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
};
|
||||||
|
level_time.set_rate(rate);
|
||||||
|
level_time.send_time(world).await;
|
||||||
|
sender
|
||||||
|
.send_message(TextComponent::translate(
|
||||||
|
translation::java::COMMANDS_TIME_RATE,
|
||||||
|
[
|
||||||
|
TextComponent::text(clock_name.to_string()),
|
||||||
|
TextComponent::text(rate.to_string()),
|
||||||
|
],
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
Ok(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_command_tree() -> CommandTree {
|
pub fn init_command_tree() -> CommandTree {
|
||||||
CommandTree::new(NAMES, DESCRIPTION)
|
let set_node = literal("set")
|
||||||
|
.then(literal("day").execute(ActionExecutor(Action::Set(Some(PresetTime::Day)))))
|
||||||
|
.then(literal("noon").execute(ActionExecutor(Action::Set(Some(PresetTime::Noon)))))
|
||||||
|
.then(literal("night").execute(ActionExecutor(Action::Set(Some(PresetTime::Night)))))
|
||||||
|
.then(literal("midnight").execute(ActionExecutor(Action::Set(Some(PresetTime::Midnight)))))
|
||||||
.then(
|
.then(
|
||||||
literal("add")
|
argument(ARG_TIME, TimeArgumentConsumer::new())
|
||||||
.then(argument(ARG_TIME, TimeArgumentConsumer).execute(ChangeExecutor(Mode::Add))),
|
.execute(ActionExecutor(Action::Set(None))),
|
||||||
|
);
|
||||||
|
|
||||||
|
let add_node = literal("add").then(
|
||||||
|
argument(ARG_TIME, TimeArgumentConsumer::min(i32::MIN))
|
||||||
|
.execute(ActionExecutor(Action::Add)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let pause_node = literal("pause").execute(ActionExecutor(Action::Pause));
|
||||||
|
let resume_node = literal("resume").execute(ActionExecutor(Action::Resume));
|
||||||
|
|
||||||
|
let rate_node = literal("rate").then(
|
||||||
|
argument(
|
||||||
|
ARG_RATE,
|
||||||
|
BoundedNumArgumentConsumer::<f32>::new()
|
||||||
|
.min(1.0e-5)
|
||||||
|
.max(1000.0),
|
||||||
)
|
)
|
||||||
.then(
|
.execute(ActionExecutor(Action::Rate)),
|
||||||
literal("query")
|
);
|
||||||
.then(literal("daytime").execute(QueryExecutor(QueryMode::DayTime)))
|
|
||||||
.then(literal("gametime").execute(QueryExecutor(QueryMode::GameTime)))
|
let query_node = literal("query")
|
||||||
.then(literal("day").execute(QueryExecutor(QueryMode::Day))),
|
.then(literal("time").execute(QueryExecutor(QueryMode::Time)))
|
||||||
)
|
.then(literal("gametime").execute(QueryExecutor(QueryMode::GameTime)))
|
||||||
.then(
|
.then(literal("daytime").execute(QueryExecutor(QueryMode::DayTime)))
|
||||||
literal("set")
|
.then(literal("day").execute(QueryExecutor(QueryMode::Day)));
|
||||||
.then(literal("day").execute(ChangeExecutor(Mode::Set(Some(PresetTime::Day)))))
|
|
||||||
.then(literal("noon").execute(ChangeExecutor(Mode::Set(Some(PresetTime::Noon)))))
|
let of_clock_node = literal("of").then(
|
||||||
.then(literal("night").execute(ChangeExecutor(Mode::Set(Some(PresetTime::Night)))))
|
argument(ARG_CLOCK, ResourceLocationArgumentConsumer)
|
||||||
.then(
|
.then(
|
||||||
literal("midnight")
|
literal("set")
|
||||||
.execute(ChangeExecutor(Mode::Set(Some(PresetTime::Midnight)))),
|
.then(
|
||||||
)
|
literal("day").execute(ActionExecutor(Action::Set(Some(PresetTime::Day)))),
|
||||||
.then(
|
)
|
||||||
argument(ARG_TIME, TimeArgumentConsumer)
|
.then(
|
||||||
.execute(ChangeExecutor(Mode::Set(None))),
|
literal("noon")
|
||||||
|
.execute(ActionExecutor(Action::Set(Some(PresetTime::Noon)))),
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
literal("night")
|
||||||
|
.execute(ActionExecutor(Action::Set(Some(PresetTime::Night)))),
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
literal("midnight")
|
||||||
|
.execute(ActionExecutor(Action::Set(Some(PresetTime::Midnight)))),
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
argument(ARG_TIME, TimeArgumentConsumer::new())
|
||||||
|
.execute(ActionExecutor(Action::Set(None))),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
literal("add").then(
|
||||||
|
argument(ARG_TIME, TimeArgumentConsumer::min(i32::MIN))
|
||||||
|
.execute(ActionExecutor(Action::Add)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.then(literal("pause").execute(ActionExecutor(Action::Pause)))
|
||||||
|
.then(literal("resume").execute(ActionExecutor(Action::Resume)))
|
||||||
|
.then(
|
||||||
|
literal("rate").then(
|
||||||
|
argument(
|
||||||
|
ARG_RATE,
|
||||||
|
BoundedNumArgumentConsumer::<f32>::new()
|
||||||
|
.min(1.0e-5)
|
||||||
|
.max(1000.0),
|
||||||
|
)
|
||||||
|
.execute(ActionExecutor(Action::Rate)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
literal("query")
|
||||||
|
.then(literal("time").execute(QueryExecutor(QueryMode::Time)))
|
||||||
|
.then(literal("gametime").execute(QueryExecutor(QueryMode::GameTime)))
|
||||||
|
.then(literal("daytime").execute(QueryExecutor(QueryMode::DayTime)))
|
||||||
|
.then(literal("day").execute(QueryExecutor(QueryMode::Day))),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
CommandTree::new(NAMES, DESCRIPTION)
|
||||||
|
.then(set_node)
|
||||||
|
.then(add_node)
|
||||||
|
.then(pause_node)
|
||||||
|
.then(resume_node)
|
||||||
|
.then(rate_node)
|
||||||
|
.then(query_node)
|
||||||
|
.then(of_clock_node)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -187,12 +187,15 @@ pub fn init_command_tree() -> CommandTree {
|
|||||||
.execute(TitleExecutor(TitleMode::ActionBar)),
|
.execute(TitleExecutor(TitleMode::ActionBar)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.then(literal("times").then(
|
.then(
|
||||||
argument(ARG_FADE_IN, TimeArgumentConsumer).then(
|
literal("times").then(
|
||||||
argument(ARG_STAY, TimeArgumentConsumer).then(
|
argument(ARG_FADE_IN, TimeArgumentConsumer::new()).then(
|
||||||
argument(ARG_FADE_OUT, TimeArgumentConsumer).execute(TimesTitleExecutor),
|
argument(ARG_STAY, TimeArgumentConsumer::new()).then(
|
||||||
|
argument(ARG_FADE_OUT, TimeArgumentConsumer::new())
|
||||||
|
.execute(TimesTitleExecutor),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
164
crates/pumpkin/src/command/commands/waypoint.rs
Normal file
164
crates/pumpkin/src/command/commands/waypoint.rs
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
use pumpkin_data::translation;
|
||||||
|
use pumpkin_util::text::TextComponent;
|
||||||
|
|
||||||
|
use crate::command::CommandResult;
|
||||||
|
use crate::command::args::{
|
||||||
|
FindArg, entity::EntityArgumentConsumer, hex_color::HexColorArgumentConsumer,
|
||||||
|
resource_location::ResourceLocationArgumentConsumer, team_color::TeamColorArgumentConsumer,
|
||||||
|
};
|
||||||
|
use crate::command::tree::builder::{argument, literal};
|
||||||
|
use crate::command::{CommandExecutor, CommandSender, ConsumedArgs, tree::CommandTree};
|
||||||
|
|
||||||
|
const NAMES: [&str; 1] = ["waypoint"];
|
||||||
|
const DESCRIPTION: &str = "List or modify waypoints.";
|
||||||
|
const ARG_WAYPOINT: &str = "waypoint";
|
||||||
|
const ARG_COLOR: &str = "color";
|
||||||
|
const ARG_STYLE: &str = "style";
|
||||||
|
|
||||||
|
struct ListExecutor;
|
||||||
|
|
||||||
|
impl CommandExecutor for ListExecutor {
|
||||||
|
fn execute<'a>(
|
||||||
|
&'a self,
|
||||||
|
sender: &'a CommandSender,
|
||||||
|
server: &'a crate::server::Server,
|
||||||
|
_args: &'a ConsumedArgs<'a>,
|
||||||
|
) -> CommandResult<'a> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let worlds = server.worlds.load();
|
||||||
|
let world = worlds
|
||||||
|
.first()
|
||||||
|
.expect("There should always be at least one world");
|
||||||
|
let dimension = world.dimension.minecraft_name.to_string();
|
||||||
|
|
||||||
|
// Currently no active waypoints are tracked in the level
|
||||||
|
sender
|
||||||
|
.send_message(TextComponent::translate(
|
||||||
|
translation::java::COMMANDS_WAYPOINT_LIST_EMPTY,
|
||||||
|
[TextComponent::text(dimension)],
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
Ok(0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ColorAction {
|
||||||
|
Named,
|
||||||
|
Hex,
|
||||||
|
Reset,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ColorExecutor(ColorAction);
|
||||||
|
|
||||||
|
impl CommandExecutor for ColorExecutor {
|
||||||
|
fn execute<'a>(
|
||||||
|
&'a self,
|
||||||
|
sender: &'a CommandSender,
|
||||||
|
_server: &'a crate::server::Server,
|
||||||
|
args: &'a ConsumedArgs<'a>,
|
||||||
|
) -> CommandResult<'a> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let _waypoint_entity = EntityArgumentConsumer::find_arg(args, ARG_WAYPOINT)?;
|
||||||
|
|
||||||
|
match self.0 {
|
||||||
|
ColorAction::Named => {
|
||||||
|
let color = TeamColorArgumentConsumer::find_arg(args, ARG_COLOR)?;
|
||||||
|
sender
|
||||||
|
.send_message(TextComponent::translate(
|
||||||
|
translation::java::COMMANDS_WAYPOINT_MODIFY_COLOR,
|
||||||
|
[TextComponent::text(color.name()).color_named(color)],
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
ColorAction::Hex => {
|
||||||
|
let color_val = HexColorArgumentConsumer::find_arg(args, ARG_COLOR)?;
|
||||||
|
let hex_str = format!("{:06X}", color_val & 0xFFFFFF);
|
||||||
|
sender
|
||||||
|
.send_message(TextComponent::translate(
|
||||||
|
translation::java::COMMANDS_WAYPOINT_MODIFY_COLOR,
|
||||||
|
[TextComponent::text(hex_str)],
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
ColorAction::Reset => {
|
||||||
|
sender
|
||||||
|
.send_message(TextComponent::translate(
|
||||||
|
translation::java::COMMANDS_WAYPOINT_MODIFY_COLOR_RESET,
|
||||||
|
[],
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum StyleAction {
|
||||||
|
Set,
|
||||||
|
Reset,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StyleExecutor(StyleAction);
|
||||||
|
|
||||||
|
impl CommandExecutor for StyleExecutor {
|
||||||
|
fn execute<'a>(
|
||||||
|
&'a self,
|
||||||
|
sender: &'a CommandSender,
|
||||||
|
_server: &'a crate::server::Server,
|
||||||
|
args: &'a ConsumedArgs<'a>,
|
||||||
|
) -> CommandResult<'a> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let _waypoint_entity = EntityArgumentConsumer::find_arg(args, ARG_WAYPOINT)?;
|
||||||
|
|
||||||
|
match self.0 {
|
||||||
|
StyleAction::Set => {
|
||||||
|
let _style = ResourceLocationArgumentConsumer::find_arg(args, ARG_STYLE)?;
|
||||||
|
}
|
||||||
|
StyleAction::Reset => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
sender
|
||||||
|
.send_message(TextComponent::translate(
|
||||||
|
translation::java::COMMANDS_WAYPOINT_MODIFY_STYLE,
|
||||||
|
[],
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
Ok(0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_command_tree() -> CommandTree {
|
||||||
|
let color_node = literal("color")
|
||||||
|
.then(
|
||||||
|
argument(ARG_COLOR, TeamColorArgumentConsumer)
|
||||||
|
.execute(ColorExecutor(ColorAction::Named)),
|
||||||
|
)
|
||||||
|
.then(literal("hex").then(
|
||||||
|
argument(ARG_COLOR, HexColorArgumentConsumer).execute(ColorExecutor(ColorAction::Hex)),
|
||||||
|
))
|
||||||
|
.then(literal("reset").execute(ColorExecutor(ColorAction::Reset)));
|
||||||
|
|
||||||
|
let style_node = literal("style")
|
||||||
|
.then(literal("reset").execute(StyleExecutor(StyleAction::Reset)))
|
||||||
|
.then(
|
||||||
|
literal("set").then(
|
||||||
|
argument(ARG_STYLE, ResourceLocationArgumentConsumer)
|
||||||
|
.execute(StyleExecutor(StyleAction::Set)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let modify_node = literal("modify").then(
|
||||||
|
argument(ARG_WAYPOINT, EntityArgumentConsumer)
|
||||||
|
.then(color_node)
|
||||||
|
.then(style_node),
|
||||||
|
);
|
||||||
|
|
||||||
|
CommandTree::new(NAMES, DESCRIPTION)
|
||||||
|
.then(literal("list").execute(ListExecutor))
|
||||||
|
.then(modify_node)
|
||||||
|
}
|
||||||
@ -96,7 +96,7 @@ pub fn init_command_tree() -> CommandTree {
|
|||||||
.then(
|
.then(
|
||||||
literal("clear")
|
literal("clear")
|
||||||
.then(
|
.then(
|
||||||
argument(ARG_DURATION, TimeArgumentConsumer).execute(Executor {
|
argument(ARG_DURATION, TimeArgumentConsumer::new()).execute(Executor {
|
||||||
mode: WeatherMode::Clear,
|
mode: WeatherMode::Clear,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -107,7 +107,7 @@ pub fn init_command_tree() -> CommandTree {
|
|||||||
.then(
|
.then(
|
||||||
literal("rain")
|
literal("rain")
|
||||||
.then(
|
.then(
|
||||||
argument(ARG_DURATION, TimeArgumentConsumer).execute(Executor {
|
argument(ARG_DURATION, TimeArgumentConsumer::new()).execute(Executor {
|
||||||
mode: WeatherMode::Rain,
|
mode: WeatherMode::Rain,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -118,7 +118,7 @@ pub fn init_command_tree() -> CommandTree {
|
|||||||
.then(
|
.then(
|
||||||
literal("thunder")
|
literal("thunder")
|
||||||
.then(
|
.then(
|
||||||
argument(ARG_DURATION, TimeArgumentConsumer).execute(Executor {
|
argument(ARG_DURATION, TimeArgumentConsumer::new()).execute(Executor {
|
||||||
mode: WeatherMode::Thunder,
|
mode: WeatherMode::Thunder,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -2548,27 +2548,39 @@ impl Player {
|
|||||||
|
|
||||||
/// Sends the world time to only this player.
|
/// Sends the world time to only this player.
|
||||||
pub async fn send_time(&self, world: &World) {
|
pub async fn send_time(&self, world: &World) {
|
||||||
|
let advance_time = {
|
||||||
|
let lock = world.level_info.load();
|
||||||
|
lock.game_rules.advance_time
|
||||||
|
};
|
||||||
|
|
||||||
|
let l_world = world.level_time.lock().await;
|
||||||
if let Some((custom_time, relative)) = self.per_player_time.load() {
|
if let Some((custom_time, relative)) = self.per_player_time.load() {
|
||||||
let time_of_day = if relative {
|
let time_of_day = if relative {
|
||||||
let l_world = world.level_time.lock().await;
|
|
||||||
(l_world.time_of_day as u64 + custom_time) as i64
|
(l_world.time_of_day as u64 + custom_time) as i64
|
||||||
} else {
|
} else {
|
||||||
custom_time as i64
|
custom_time as i64
|
||||||
};
|
};
|
||||||
let l_world = world.level_time.lock().await;
|
let paused = l_world.paused || !advance_time;
|
||||||
|
let rate = if paused { 0.0 } else { l_world.rate };
|
||||||
self.client
|
self.client
|
||||||
.enqueue_packet_editioned(
|
.enqueue_packet_editioned(
|
||||||
&CUpdateTime::new(l_world.world_age, time_of_day, true),
|
&CUpdateTime::new_clock(
|
||||||
|
l_world.world_age,
|
||||||
|
0,
|
||||||
|
time_of_day,
|
||||||
|
l_world.partial_tick,
|
||||||
|
rate,
|
||||||
|
),
|
||||||
&CSetTime::new(time_of_day as _),
|
&CSetTime::new(time_of_day as _),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let l_world = world.level_time.lock().await;
|
let (total_ticks, partial_tick, rate) = l_world.pack_network_state(advance_time);
|
||||||
self.client
|
self.client
|
||||||
.enqueue_packet_editioned(
|
.enqueue_packet_editioned(
|
||||||
&CUpdateTime::new(l_world.world_age, l_world.time_of_day, true),
|
&CUpdateTime::new_clock(l_world.world_age, 0, total_ticks, partial_tick, rate),
|
||||||
&CSetTime::new(l_world.query_daytime() as _),
|
&CSetTime::new(l_world.query_daytime() as _),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|||||||
@ -81,7 +81,9 @@ impl OwnedArg {
|
|||||||
Arg::Enchantment(e) => Self::Enchantment(e),
|
Arg::Enchantment(e) => Self::Enchantment(e),
|
||||||
Arg::EntityAnchor(a) => Self::EntityAnchor(*a),
|
Arg::EntityAnchor(a) => Self::EntityAnchor(*a),
|
||||||
Arg::Advancement(a) => Self::Advancement(a),
|
Arg::Advancement(a) => Self::Advancement(a),
|
||||||
Arg::Slot(_, _) | Arg::Slots(_, _) => unreachable!(),
|
Arg::Slot(_, _) | Arg::Slots(_, _) | Arg::TeamColor(_) | Arg::HexColor(_) => {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -598,7 +598,7 @@ impl pumpkin::plugin::command::HostCommandNode for PluginHostState {
|
|||||||
ArgumentType::EntityAnchor => argument(name, EntityAnchorArgumentConsumer),
|
ArgumentType::EntityAnchor => argument(name, EntityAnchorArgumentConsumer),
|
||||||
ArgumentType::Gamemode => argument(name, GamemodeArgumentConsumer),
|
ArgumentType::Gamemode => argument(name, GamemodeArgumentConsumer),
|
||||||
ArgumentType::Difficulty => argument(name, DifficultyArgumentConsumer),
|
ArgumentType::Difficulty => argument(name, DifficultyArgumentConsumer),
|
||||||
ArgumentType::Time(_) => argument(name, TimeArgumentConsumer),
|
ArgumentType::Time(min) => argument(name, TimeArgumentConsumer::min(min.unwrap_or(0))),
|
||||||
_ => {
|
_ => {
|
||||||
return Err(wasmtime::Error::msg(format!(
|
return Err(wasmtime::Error::msg(format!(
|
||||||
"Unimplemented argument type: {arg_type:?}"
|
"Unimplemented argument type: {arg_type:?}"
|
||||||
|
|||||||
@ -1128,14 +1128,8 @@ impl World {
|
|||||||
async fn tick_environment(&self) {
|
async fn tick_environment(&self) {
|
||||||
let (world_age, is_night, time_of_day) = {
|
let (world_age, is_night, time_of_day) = {
|
||||||
let mut level_time = self.level_time.lock().await;
|
let mut level_time = self.level_time.lock().await;
|
||||||
let (advance_time, advance_weather) = {
|
let advance_time = self.level_info.load().game_rules.advance_time;
|
||||||
let lock = self.level_info.load();
|
level_time.tick(advance_time);
|
||||||
(
|
|
||||||
lock.game_rules.advance_time,
|
|
||||||
lock.game_rules.advance_weather,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
level_time.tick_time(advance_time, advance_weather);
|
|
||||||
|
|
||||||
// Auto-save logic
|
// Auto-save logic
|
||||||
if level_time.world_age % 100 == 0 {
|
if level_time.world_age % 100 == 0 {
|
||||||
|
|||||||
@ -2,10 +2,85 @@ use pumpkin_protocol::{bedrock::client::set_time::CSetTime, java::client::play::
|
|||||||
|
|
||||||
use super::World;
|
use super::World;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct ClockInstance {
|
||||||
|
pub total_ticks: i64,
|
||||||
|
pub partial_tick: f32,
|
||||||
|
pub rate: f32,
|
||||||
|
pub paused: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ClockInstance {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClockInstance {
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
total_ticks: 0,
|
||||||
|
partial_tick: 0.0,
|
||||||
|
rate: 1.0,
|
||||||
|
paused: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn load_from(
|
||||||
|
&mut self,
|
||||||
|
total_ticks: i64,
|
||||||
|
partial_tick: f32,
|
||||||
|
rate: f32,
|
||||||
|
paused: bool,
|
||||||
|
) {
|
||||||
|
self.total_ticks = total_ticks;
|
||||||
|
self.partial_tick = partial_tick;
|
||||||
|
self.rate = rate;
|
||||||
|
self.paused = paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(&mut self) {
|
||||||
|
if !self.paused {
|
||||||
|
self.partial_tick += self.rate;
|
||||||
|
let full_ticks = self.partial_tick.floor() as i32;
|
||||||
|
self.partial_tick -= full_ticks as f32;
|
||||||
|
self.total_ticks += full_ticks as i64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn set_total_ticks(&mut self, total_ticks: i64) {
|
||||||
|
self.total_ticks = total_ticks;
|
||||||
|
self.partial_tick = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_ticks(&mut self, ticks: i64) {
|
||||||
|
self.total_ticks = (self.total_ticks + ticks).max(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn set_paused(&mut self, paused: bool) {
|
||||||
|
self.paused = paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn set_rate(&mut self, rate: f32) {
|
||||||
|
self.rate = rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn pack_network_state(&self, advance_time: bool) -> (i64, f32, f32) {
|
||||||
|
let paused = self.paused || !advance_time;
|
||||||
|
let rate = if paused { 0.0 } else { self.rate };
|
||||||
|
(self.total_ticks, self.partial_tick, rate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct LevelTime {
|
pub struct LevelTime {
|
||||||
pub world_age: i64,
|
|
||||||
pub time_of_day: i64,
|
pub time_of_day: i64,
|
||||||
pub rain_time: i64,
|
pub world_age: i64,
|
||||||
|
pub partial_tick: f32,
|
||||||
|
pub rate: f32,
|
||||||
|
pub paused: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for LevelTime {
|
impl Default for LevelTime {
|
||||||
@ -18,19 +93,26 @@ impl LevelTime {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
world_age: 0,
|
|
||||||
time_of_day: 0,
|
time_of_day: 0,
|
||||||
rain_time: 0,
|
world_age: 0,
|
||||||
|
partial_tick: 0.0,
|
||||||
|
rate: 1.0,
|
||||||
|
paused: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn tick_time(&mut self, advance_time: bool, advance_weather: bool) {
|
pub const fn load_from(&mut self, time_of_day: i64, world_age: i64) {
|
||||||
|
self.time_of_day = time_of_day;
|
||||||
|
self.world_age = world_age;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(&mut self, advance_time: bool) {
|
||||||
self.world_age += 1;
|
self.world_age += 1;
|
||||||
if advance_weather {
|
if advance_time && !self.paused {
|
||||||
self.rain_time += 1;
|
self.partial_tick += self.rate;
|
||||||
}
|
let full_ticks = self.partial_tick.floor() as i32;
|
||||||
if advance_time {
|
self.partial_tick -= full_ticks as f32;
|
||||||
self.time_of_day += 1;
|
self.time_of_day += full_ticks as i64;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,20 +122,38 @@ impl LevelTime {
|
|||||||
lock.game_rules.advance_time
|
lock.game_rules.advance_time
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (total_ticks, partial_tick, rate) = self.pack_network_state(advance_time);
|
||||||
|
|
||||||
world
|
world
|
||||||
.broadcast_editioned(
|
.broadcast_editioned(
|
||||||
&CUpdateTime::new(self.world_age, self.time_of_day, advance_time),
|
&CUpdateTime::new_clock(self.world_age, 0, total_ticks, partial_tick, rate),
|
||||||
&CSetTime::new(self.time_of_day as _), // TODO do we need to tell bedrock that time is frozen?
|
&CSetTime::new(self.time_of_day as _), // TODO do we need to tell bedrock that time is frozen?
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn add_time(&mut self, time: i64) {
|
pub fn add_time(&mut self, time: i64) {
|
||||||
self.time_of_day += time;
|
self.time_of_day = (self.time_of_day + time).max(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn set_time(&mut self, time: i64) {
|
pub const fn set_time(&mut self, time: i64) {
|
||||||
self.time_of_day = time;
|
self.time_of_day = time;
|
||||||
|
self.partial_tick = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn set_paused(&mut self, paused: bool) {
|
||||||
|
self.paused = paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn set_rate(&mut self, rate: f32) {
|
||||||
|
self.rate = rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn pack_network_state(&self, advance_time: bool) -> (i64, f32, f32) {
|
||||||
|
let paused = self.paused || !advance_time;
|
||||||
|
let rate = if paused { 0.0 } else { self.rate };
|
||||||
|
(self.time_of_day, self.partial_tick, rate)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@ -76,3 +176,56 @@ impl LevelTime {
|
|||||||
(self.time_of_day % 24000) >= 12000 && (self.time_of_day % 24000) <= 23999
|
(self.time_of_day % 24000) >= 12000 && (self.time_of_day % 24000) <= 23999
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn clock_instance_ticking() {
|
||||||
|
let mut clock = ClockInstance::new();
|
||||||
|
assert_eq!(clock.total_ticks, 0);
|
||||||
|
assert_eq!(clock.partial_tick, 0.0);
|
||||||
|
assert_eq!(clock.rate, 1.0);
|
||||||
|
assert!(!clock.paused);
|
||||||
|
|
||||||
|
// Standard tick at rate 1.0
|
||||||
|
clock.tick();
|
||||||
|
assert_eq!(clock.total_ticks, 1);
|
||||||
|
assert_eq!(clock.partial_tick, 0.0);
|
||||||
|
|
||||||
|
// Half rate tick
|
||||||
|
clock.set_rate(0.5);
|
||||||
|
clock.tick();
|
||||||
|
assert_eq!(clock.total_ticks, 1);
|
||||||
|
assert_eq!(clock.partial_tick, 0.5);
|
||||||
|
clock.tick();
|
||||||
|
assert_eq!(clock.total_ticks, 2);
|
||||||
|
assert_eq!(clock.partial_tick, 0.0);
|
||||||
|
|
||||||
|
// Double rate tick
|
||||||
|
clock.set_rate(2.0);
|
||||||
|
clock.tick();
|
||||||
|
assert_eq!(clock.total_ticks, 4);
|
||||||
|
assert_eq!(clock.partial_tick, 0.0);
|
||||||
|
|
||||||
|
// Paused clock
|
||||||
|
clock.set_paused(true);
|
||||||
|
clock.tick();
|
||||||
|
assert_eq!(clock.total_ticks, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn level_time_set_and_add() {
|
||||||
|
let mut time = LevelTime::new();
|
||||||
|
time.set_time(1000);
|
||||||
|
assert_eq!(time.time_of_day, 1000);
|
||||||
|
assert_eq!(time.partial_tick, 0.0);
|
||||||
|
|
||||||
|
time.add_time(500);
|
||||||
|
assert_eq!(time.time_of_day, 1500);
|
||||||
|
|
||||||
|
time.add_time(-2000);
|
||||||
|
assert_eq!(time.time_of_day, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user