From 79e8d6cc02d00dfe742336c9a610d8be9c209879 Mon Sep 17 00:00:00 2001 From: tachibanayui <33594017+tachibanayui@users.noreply.github.com> Date: Sun, 17 Sep 2023 07:06:32 +0700 Subject: [PATCH 1/7] initial implementation of `valence_world_time` --- Cargo.toml | 4 + crates/valence_world_time/Cargo.toml | 17 ++ crates/valence_world_time/src/extra.rs | 117 ++++++++++ crates/valence_world_time/src/lib.rs | 281 +++++++++++++++++++++++++ src/lib.rs | 7 + 5 files changed, 426 insertions(+) create mode 100644 crates/valence_world_time/Cargo.toml create mode 100644 crates/valence_world_time/src/extra.rs create mode 100644 crates/valence_world_time/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index cccade60f..27d4fe98a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ default = [ "player_list", "scoreboard", "world_border", + "world_time", "weather", "testing", ] @@ -34,6 +35,7 @@ network = ["dep:valence_network"] player_list = ["dep:valence_player_list"] scoreboard = ["dep:valence_scoreboard"] world_border = ["dep:valence_world_border"] +world_time = ["dep:valence_world_time"] weather = ["dep:valence_weather"] testing = [] @@ -58,6 +60,7 @@ valence_registry.workspace = true valence_scoreboard = { workspace = true, optional = true } valence_weather = { workspace = true, optional = true } valence_world_border = { workspace = true, optional = true } +valence_world_time = { workspace = true, optional = true } valence_lang.workspace = true valence_text.workspace = true valence_ident.workspace = true @@ -195,4 +198,5 @@ valence_server_common = { path = "crates/valence_server_common", version = "0.2. valence_text = { path = "crates/valence_text", version = "0.2.0-alpha.1" } valence_weather = { path = "crates/valence_weather", version = "0.2.0-alpha.1" } valence_world_border = { path = "crates/valence_world_border", version = "0.2.0-alpha.1" } +valence_world_time = { path = "crates/valence_world_time", version = "0.2.0-alpha.1" } zip = "0.6.3" diff --git a/crates/valence_world_time/Cargo.toml b/crates/valence_world_time/Cargo.toml new file mode 100644 index 000000000..5abff562d --- /dev/null +++ b/crates/valence_world_time/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "valence_world_time" +description = "World time support for Valence" +readme = "README.md" +version.workspace = true +edition.workspace = true +repository.workspace = true +documentation.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bevy_app.workspace = true +bevy_ecs.workspace = true +valence_server.workspace = true +derive_more.workspace = true \ No newline at end of file diff --git a/crates/valence_world_time/src/extra.rs b/crates/valence_world_time/src/extra.rs new file mode 100644 index 000000000..a66504905 --- /dev/null +++ b/crates/valence_world_time/src/extra.rs @@ -0,0 +1,117 @@ +use crate::WorldTime; + +pub const DAY_LENGTH: u64 = 24000; + +/// Notable events of a 24-hour Minecraft day +pub enum DayPhase { + Day = 0, + Noon = 6000, + Sunset = 12000, + Night = 13000, + Midnight = 18000, + Sunrise = 23000, +} + +impl From for u64 { + fn from(value: DayPhase) -> Self { + value as Self + } +} + +/// Reference: +pub enum MoonPhase { + FullMoon = 0, + WaningGibbous = 1, + ThirdQuarter = 2, + WaningCrescent = 3, + NewMoon = 4, + WaxingCrescent = 5, + FirstQuarter = 6, + WaxingGibbous = 7, +} + +impl From for u64 { + fn from(value: MoonPhase) -> Self { + value as Self + } +} + + + +impl WorldTime { + /// This function ensure that adding time will not resulting in + /// time_of_day flipping sign. + pub fn add_time(&mut self, amount: impl Into) { + let client_ticking = self.client_time_ticking(); + self.time_of_day = self.time_of_day.abs().wrapping_add(amount.into()); + if self.time_of_day < 0 { + self.time_of_day = self.time_of_day + i64::MAX + 1; + } + + self.set_client_time_ticking(client_ticking); + } + + /// If the client advances world time locally without server updates. + pub fn client_time_ticking(&self) -> bool { + self.time_of_day >= 0 + } + + /// Sets if the client advances world time locally without server updates. + /// Note: If the resulting calculation set time_of_day to 0. This function + /// will set time -1 if time_of_day is 0 and is time ticking = false to + /// workaround protocol limitations + pub fn set_client_time_ticking(&mut self, val: bool) { + self.time_of_day = if val { + self.time_of_day.abs() + } else { + -self.time_of_day.abs() + }; + } + + /// Get the time part of `time_of_day` + pub fn current_day_time(&self) -> u64 { + self.time_of_day as u64 % DAY_LENGTH + } + + /// Set the time part of `time_of_day` + /// Use the [`DayPhase`] enum to easily handle common time + /// of day events without the need to look up information in the wiki. + pub fn set_current_day_time(&mut self, time: impl Into) { + let client_ticking = self.client_time_ticking(); + self.time_of_day = (self.day() * DAY_LENGTH + time.into() % DAY_LENGTH) as i64; + self.set_client_time_ticking(client_ticking); + } + + /// Get the current day part of `time_of_day` + pub fn day(&self) -> u64 { + self.time_of_day as u64 / DAY_LENGTH + } + + /// Set the current day `time_of_day` + pub fn set_day(&mut self, day: u64) { + let client_ticking = self.client_time_ticking(); + self.time_of_day = (day * DAY_LENGTH + self.current_day_time()) as i64; + self.set_client_time_ticking(client_ticking); + } + + /// Set the time_of_day to the next specified [`DayPhase`] + pub fn warp_to_next_day_phase(&mut self, phase: DayPhase) { + let phase_num: u64 = phase.into(); + if self.current_day_time() >= phase_num { + self.set_day(self.day() + 1); + } + + self.set_current_day_time(phase_num); + } + + /// Set the time_of_day to the next specified [`MoonPhase`] + pub fn wrap_to_next_moon_phase(&mut self, phase: MoonPhase) { + let phase_no: u64 = phase.into(); + if self.day() % 8 >= phase_no { + self.set_day(self.day() + 8 - (self.day() % 8)) + } + + self.set_day(self.day() + phase_no - self.day() % 8); + self.set_current_day_time(DayPhase::Night); + } +} diff --git a/crates/valence_world_time/src/lib.rs b/crates/valence_world_time/src/lib.rs new file mode 100644 index 000000000..e6e8cb29c --- /dev/null +++ b/crates/valence_world_time/src/lib.rs @@ -0,0 +1,281 @@ +//! # Controlling World Time +//! This module contains Components and Systems needed to update, tick, +//! broadcast information about the time of day and world age of a +//! [`ChunkLayer`]. +//! +//! ## Enable world time +//! To control world time of an [`ChunkLayer`], simply insert the +//! [`WorldTimeBundle`] bundle. We also need to broadcast world time updates to +//! clients. The [`IntervalTimeBroadcast::default()`] provides configuration to +//! mimic vanilla behavior: +//! ``` +//! fn enable(mut commands: Commands, instance: Entity) { +//! commands.entity(instance).insert(WorldTimeBundle::default()); +//! } +//! ``` +//! +//! ## Set the time explicitly +//! Mutating [`WorldTime`] will not automatically broadcast the +//! change to clients. Mutating [`SetTimeQuery`] to modify time +//! and broadcast the time changes immediately. +//! ``` +//! fn into_the_night(mut instances: Query<(&mut WorldTime, SetTimeQuery), With>) { +//! for (mut t1, mut t2) in instances.iter_mut() { +//! let time_to_set = DayPhase::Night.into(); +//! +//! // Using [`WorldTime`] - Change won't broadcast immediately +//! t1.time_of_day = time_to_set; +//! // Using [`SetTimeQuery`] - Change broadcast immediately +//! t2.time_of_day = time_to_set; +//! } +//! } +//! ``` +//! +//! ## Advacing the world time +//! Time of day and world age can be ticked individually using +//! [`LinearTimeTicking`] and [`LinearWorldAging`] respectively. +//! If these components don't meet your requirements +//! (eg: you need time increment follow a sine wave ~~for some reason~~), +//! you can tick the time yourself by modifying the respective +//! fields on [`WorldTime`]. +//! +//! ## Prevent client from automatically update WorldTime +//! *(mimics `/gamerule doDaylightCycle false`)* +//! +//! By default, client will continue to update world time if the server +//! doesn't send packet to sync time between client and server. +//! This can be toggled by using [`WorldTime::set_client_time_ticking()`] +//! of [`WorldTime`] to true. +//! +//! Here is an example of mimicing `/gamerule doDaylightCycle `: +//! ``` +//! #[derive(Component)] +//! pub struct DaylightCycle(pub bool); +//! +//! fn handle_game_rule_daylight_cycle( +//! mut instances: Query< +//! (&mut WorldTime, &mut LinearTimeTicking, &DaylightCycle), +//! Changed, +//! >, +//! ) { +//! for (mut time, mut ticking, doCycle) in instances.iter_mut() { +//! // Stop client from update +//! time.set_client_time_ticking(!doCycle.0); +//! ticking.speed = if doCycle.0 { 1 } else { 0 }; +//! } +//! } +//! ``` +#![allow(clippy::type_complexity)] +#![deny( + rustdoc::broken_intra_doc_links, + rustdoc::private_intra_doc_links, + rustdoc::missing_crate_level_docs, + rustdoc::invalid_codeblock_attributes, + rustdoc::invalid_rust_codeblocks, + rustdoc::bare_urls, + rustdoc::invalid_html_tags +)] +#![warn( + trivial_casts, + trivial_numeric_casts, + unused_lifetimes, + unused_import_braces, + unreachable_pub, + clippy::dbg_macro +)] + +pub mod extra; + +use bevy_app::{App, Plugin, PostUpdate}; +use bevy_ecs::bundle::Bundle; +use bevy_ecs::component::Component; +use bevy_ecs::schedule::IntoSystemConfigs; +use bevy_ecs::system::{Query, Res}; +use derive_more::{Deref, DerefMut}; +use valence_server::client::FlushPacketsSet; +use valence_server::protocol::packets::play::WorldTimeUpdateS2c; +use valence_server::protocol::WritePacket; +use valence_server::{ChunkLayer, Server}; + +pub struct WorldTimePlugin; + +impl Plugin for WorldTimePlugin { + fn build(&self, app: &mut App) { + app.add_systems( + PostUpdate, + ( + handle_interval_broadcast, + handle_linear_time_ticking, + handle_linear_world_aging, + ) + .before(FlushPacketsSet) + .before(handle_layer_time_boardcast), + ) + .add_systems(PostUpdate, handle_layer_time_boardcast); + } +} + +#[derive(Bundle, Default, Debug)] +pub struct WorldTimeBundle { + pub world_time: WorldTime, + pub broadcast: WorldTimeBroadcast, + pub interval: IntervalBroadcast, + pub linear_ticker: LinearTimeTicking, + pub linear_ticker_timestamp: LinearTimeTickerTimestamp, +} + +/// The base component to store time in a layer. +/// Tip: If you are looking to modify time in a layer, use +/// [`SetTimeQuery`] to also broadcast time immediately +#[derive(Component, Default, PartialEq, Clone, Copy, Debug)] +pub struct WorldTime { + /// The age of the world in 1/20ths of a second. + pub world_age: i64, + /// The current time of day in 1/20ths of a second. + /// The value should be in the range \[0, 24000]. + /// 6000 is noon, 12000 is sunset, and 18000 is midnight. + pub time_of_day: i64, +} + +/// Store information about the last broadcasted time. You shouldn't +/// mutate this component directly. +#[derive(Component, Default, Clone, Copy, Debug)] +pub struct WorldTimeBroadcast { + pub last_broadcasted: WorldTime, + pub timestamp: i64, + pub will_broadcast_this_tick: bool, +} + +/// This component will signal [`WorldTimeBroadcast`] to send +/// [`WorldTimeUpdateS2c`] packet on an interval. Note that +/// it compares the last broadcasted timestamp with the +/// current server tick to determine if an update should be sent. +#[derive(Component, Default, Deref, DerefMut, Clone, Copy, Debug)] +pub struct IntervalBroadcast(pub i64); + +/// Use this struct to set time and broadcast it immediately at +/// this tick +#[derive(Debug)] +pub struct SetTimeQuery { + time: &'static mut WorldTime, + broadcast: &'static mut WorldTimeBroadcast, +} + +impl Deref for SetTimeQuery { + type Target = WorldTime; + + fn deref(&self) -> &Self::Target { + &self.time + } +} + +impl DerefMut for SetTimeQuery { + fn deref_mut(&mut self) -> &mut Self::Target { + self.broadcast.will_broadcast_this_tick = true; + &mut self.time + } +} + +/// This component is responsible for managing time in a +/// linear fashion. It is commonly used to handle day-night cycles and +/// similar time-dependent processes. This component employs both an interval +/// and a rate to control time progression. +#[derive(Component, Clone, Copy, Debug)] +pub struct LinearTimeTicking { + /// The time interval (in server tick) between each time tick. + pub interval: i64, + + /// The rate at which time advances. A rate of 1 corresponds to real-time, + /// while values less than 1 make time progress slower than the server tick + /// rate. + pub rate: i64, +} + +#[derive(Component, Default, Deref, DerefMut, Clone, Copy, Debug)] +pub struct LinearTimeTickerTimestamp(pub i64); + +/// Similar to [`LinearTimeTicking`] but for world age +#[derive(Component, Clone, Copy, Debug)] +pub struct LinearWorldAging { + /// The time interval (in server tick) between each time tick. + pub interval: i64, + + /// The rate at which world age advances. A rate of 1 corresponds to + /// real-time, while values less than 1 make time progress slower than + /// the server tick rate. + pub rate: i64, +} + +#[derive(Component, Default, Deref, DerefMut, Clone, Copy, Debug)] +pub struct LinearWorldAgingTimestamp(pub i64); + +impl Default for LinearTimeTicking { + fn default() -> Self { + Self { + interval: 1, + rate: 1, + } + } +} + +fn handle_layer_time_boardcast( + mut layers: Query<(&mut ChunkLayer, &WorldTime, &mut WorldTimeBroadcast)>, + server: Res, +) { + for (mut layer, time, mut broadcast) in &mut layers { + if broadcast.will_broadcast_this_tick { + layer.write_packet(&WorldTimeUpdateS2c { + time_of_day: time.time_of_day, + world_age: time.world_age, + }); + + broadcast.will_broadcast_this_tick = false; + broadcast.timestamp = server.current_tick(); + } + } +} + +fn handle_interval_broadcast( + mut time: Query<(&IntervalBroadcast, &mut WorldTimeBroadcast)>, + server: Res, +) { + for (interval, mut broadcast) in &mut time { + if server.current_tick() - broadcast.timestamp >= interval.0 { + broadcast.will_broadcast_this_tick = true; + } + } +} + +fn handle_linear_time_ticking( + mut ticker: Query<( + &LinearTimeTicking, + &mut LinearTimeTickerTimestamp, + &mut WorldTime, + )>, + server: Res, +) { + for (info, mut ts, mut time) in &mut ticker { + let ct = server.current_tick(); + if ct - ts.0 >= info.interval { + time.time_of_day += info.rate; + ts.0 = ct; + } + } +} + +fn handle_linear_world_aging( + mut ticker: Query<( + &LinearWorldAging, + &mut LinearWorldAgingTimestamp, + &mut WorldTime, + )>, + server: Res, +) { + for (info, mut ts, mut time) in &mut ticker { + let ct = server.current_tick(); + if ct - ts.0 >= info.interval { + time.world_age += info.rate; + ts.0 = ct; + } + } +} diff --git a/src/lib.rs b/src/lib.rs index dfffdb3db..afb7e1d46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,6 +79,8 @@ pub use valence_server::*; pub use valence_weather as weather; #[cfg(feature = "world_border")] pub use valence_world_border as world_border; +#[cfg(feature = "world_time")] +pub use valence_world_time as world_time; /// Contains the most frequently used items in Valence projects. /// @@ -231,6 +233,11 @@ impl PluginGroup for DefaultPlugins { group = group.add(valence_world_border::WorldBorderPlugin); } + #[cfg(feature = "world_time")] + { + group = group.add(valence_world_time::WorldTimePlugin); + } + #[cfg(feature = "boss_bar")] { group = group.add(valence_boss_bar::BossBarPlugin); From 84c1881c1c401855c0849496430e593e4c8b22a3 Mon Sep 17 00:00:00 2001 From: tachibanayui <33594017+tachibanayui@users.noreply.github.com> Date: Sun, 17 Sep 2023 16:40:17 +0700 Subject: [PATCH 2/7] Add tests for `valence_world_time` --- crates/valence_world_time/src/lib.rs | 28 +++++++++-- src/tests.rs | 1 + src/tests/world_time.rs | 72 ++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 src/tests/world_time.rs diff --git a/crates/valence_world_time/src/lib.rs b/crates/valence_world_time/src/lib.rs index e6e8cb29c..62af5816b 100644 --- a/crates/valence_world_time/src/lib.rs +++ b/crates/valence_world_time/src/lib.rs @@ -150,9 +150,17 @@ pub struct WorldTimeBroadcast { /// [`WorldTimeUpdateS2c`] packet on an interval. Note that /// it compares the last broadcasted timestamp with the /// current server tick to determine if an update should be sent. -#[derive(Component, Default, Deref, DerefMut, Clone, Copy, Debug)] +#[derive(Component, Deref, DerefMut, Clone, Copy, Debug)] pub struct IntervalBroadcast(pub i64); +impl Default for IntervalBroadcast { + fn default() -> Self { + Self(20) + } +} + + + /// Use this struct to set time and broadcast it immediately at /// this tick #[derive(Debug)] @@ -191,6 +199,15 @@ pub struct LinearTimeTicking { pub rate: i64, } +impl Default for LinearTimeTicking { + fn default() -> Self { + Self { + interval: 1, + rate: 1, + } + } +} + #[derive(Component, Default, Deref, DerefMut, Clone, Copy, Debug)] pub struct LinearTimeTickerTimestamp(pub i64); @@ -206,10 +223,7 @@ pub struct LinearWorldAging { pub rate: i64, } -#[derive(Component, Default, Deref, DerefMut, Clone, Copy, Debug)] -pub struct LinearWorldAgingTimestamp(pub i64); - -impl Default for LinearTimeTicking { +impl Default for LinearWorldAging { fn default() -> Self { Self { interval: 1, @@ -218,6 +232,10 @@ impl Default for LinearTimeTicking { } } +#[derive(Component, Default, Deref, DerefMut, Clone, Copy, Debug)] +pub struct LinearWorldAgingTimestamp(pub i64); + + fn handle_layer_time_boardcast( mut layers: Query<(&mut ChunkLayer, &WorldTime, &mut WorldTimeBroadcast)>, server: Res, diff --git a/src/tests.rs b/src/tests.rs index 8fa089e69..1259610ff 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -7,3 +7,4 @@ mod player_list; mod scoreboard; mod weather; mod world_border; +mod world_time; diff --git a/src/tests/world_time.rs b/src/tests/world_time.rs new file mode 100644 index 000000000..33caac4a3 --- /dev/null +++ b/src/tests/world_time.rs @@ -0,0 +1,72 @@ +use valence_server::protocol::packets::play::WorldTimeUpdateS2c; +use valence_world_time::extra::{DayPhase, MoonPhase, DAY_LENGTH}; +use valence_world_time::{WorldTime, WorldTimeBundle}; + +use crate::testing::ScenarioSingleClient; + +#[test] +fn test_world_time_add() { + let mut time = WorldTime::default(); + time.add_time(10); + + assert_eq!(10, time.time_of_day); + assert!(time.client_time_ticking()); + + time.set_client_time_ticking(false); + assert_eq!(-10, time.time_of_day); + + time.add_time(-11); + assert_eq!(-i64::MAX, time.time_of_day); +} + +#[test] +fn test_world_time_modifications() { + let mut time = WorldTime::default(); + + time.set_day(3); + time.set_current_day_time(12000u64); + assert_eq!(3 * DAY_LENGTH + 12000, time.time_of_day as u64); + + time.warp_to_next_day_phase(DayPhase::Day); + assert_eq!(4 * DAY_LENGTH, time.time_of_day as u64); + + time.set_day(0); + time.wrap_to_next_moon_phase(MoonPhase::NewMoon); + assert_eq!( + 4 * DAY_LENGTH + DayPhase::Night as u64, + time.time_of_day as u64 + ) +} + +#[test] +fn test_time_ticking_broadcast() { + let ScenarioSingleClient { + mut app, + client: _, + mut helper, + layer, + } = prepare(); + + for _ in 0..40 { + app.update() + } + + helper + .collect_received() + .assert_count::(2); + + let x: &WorldTime = app.world.get(layer).unwrap(); + assert_eq!(x.time_of_day, 40); +} + +fn prepare() -> ScenarioSingleClient { + let mut s = ScenarioSingleClient::new(); + + // Process a tick to get past the "on join" logic. + s.app.update(); + s.app + .world + .entity_mut(s.layer) + .insert(WorldTimeBundle::default()); + s +} From a8ad55518fcfd8d8014a1ce77a91bfa7a3036614 Mon Sep 17 00:00:00 2001 From: tachibanayui <33594017+tachibanayui@users.noreply.github.com> Date: Sun, 17 Sep 2023 18:35:49 +0700 Subject: [PATCH 3/7] add example for `valance_world_time` --- crates/valence_world_time/src/lib.rs | 30 ++++- examples/wrapping_time.rs | 157 +++++++++++++++++++++++++++ 2 files changed, 181 insertions(+), 6 deletions(-) create mode 100644 examples/wrapping_time.rs diff --git a/crates/valence_world_time/src/lib.rs b/crates/valence_world_time/src/lib.rs index 62af5816b..61786226e 100644 --- a/crates/valence_world_time/src/lib.rs +++ b/crates/valence_world_time/src/lib.rs @@ -89,10 +89,11 @@ pub mod extra; use bevy_app::{App, Plugin, PostUpdate}; use bevy_ecs::bundle::Bundle; use bevy_ecs::component::Component; +use bevy_ecs::query::Changed; use bevy_ecs::schedule::IntoSystemConfigs; use bevy_ecs::system::{Query, Res}; use derive_more::{Deref, DerefMut}; -use valence_server::client::FlushPacketsSet; +use valence_server::client::{Client, FlushPacketsSet, VisibleChunkLayer}; use valence_server::protocol::packets::play::WorldTimeUpdateS2c; use valence_server::protocol::WritePacket; use valence_server::{ChunkLayer, Server}; @@ -111,7 +112,11 @@ impl Plugin for WorldTimePlugin { .before(FlushPacketsSet) .before(handle_layer_time_boardcast), ) - .add_systems(PostUpdate, handle_layer_time_boardcast); + .add_systems( + PostUpdate, + handle_layer_time_boardcast.before(init_time_for_new_clients), + ) + .add_systems(PostUpdate, init_time_for_new_clients); } } @@ -122,6 +127,8 @@ pub struct WorldTimeBundle { pub interval: IntervalBroadcast, pub linear_ticker: LinearTimeTicking, pub linear_ticker_timestamp: LinearTimeTickerTimestamp, + pub linear_world_age: LinearWorldAging, + pub linear_world_age_timestamp: LinearWorldAgingTimestamp, } /// The base component to store time in a layer. @@ -159,8 +166,6 @@ impl Default for IntervalBroadcast { } } - - /// Use this struct to set time and broadcast it immediately at /// this tick #[derive(Debug)] @@ -173,14 +178,14 @@ impl Deref for SetTimeQuery { type Target = WorldTime; fn deref(&self) -> &Self::Target { - &self.time + self.time } } impl DerefMut for SetTimeQuery { fn deref_mut(&mut self) -> &mut Self::Target { self.broadcast.will_broadcast_this_tick = true; - &mut self.time + self.time } } @@ -235,6 +240,19 @@ impl Default for LinearWorldAging { #[derive(Component, Default, Deref, DerefMut, Clone, Copy, Debug)] pub struct LinearWorldAgingTimestamp(pub i64); +fn init_time_for_new_clients( + mut clients: Query<(&mut Client, &VisibleChunkLayer), Changed>, + layers: Query<&WorldTime>, +) { + for (mut client, layer_ref) in &mut clients { + if let Ok(time) = layers.get(layer_ref.0) { + client.write_packet(&WorldTimeUpdateS2c { + time_of_day: time.time_of_day, + world_age: time.world_age, + }) + } + } +} fn handle_layer_time_boardcast( mut layers: Query<(&mut ChunkLayer, &WorldTime, &mut WorldTimeBroadcast)>, diff --git a/examples/wrapping_time.rs b/examples/wrapping_time.rs new file mode 100644 index 000000000..51d69abea --- /dev/null +++ b/examples/wrapping_time.rs @@ -0,0 +1,157 @@ +#![allow(clippy::type_complexity)] + +use std::time::Instant; + +use bevy_app::App; +use valence::client::despawn_disconnected_clients; +use valence::inventory::HeldItem; +use valence::message::SendMessage; +use valence::prelude::*; +use valence_world_time::{LinearTimeTicking, WorldTime, WorldTimeBundle}; + +const SPAWN_Y: i32 = 64; + +fn main() { + App::new() + .insert_resource(LastTickTimestamp { time: Instant::now() }) + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .add_systems( + Update, + ( + despawn_disconnected_clients, + init_clients, + show_time_info, + change_time, + ), + ) + .run(); +} + + +#[derive(Resource)] +struct LastTickTimestamp { + time: Instant +} + +fn setup( + mut commands: Commands, + server: Res, + biomes: Res, + dimensions: Res, +) { + let mut layer = LayerBundle::new(ident!("overworld"), &dimensions, &biomes, &server); + + for z in -5..5 { + for x in -5..5 { + layer.chunk.insert_chunk([x, z], UnloadedChunk::new()); + } + } + + for z in -25..25 { + for x in -25..25 { + layer + .chunk + .set_block([x, SPAWN_Y, z], BlockState::GRASS_BLOCK); + } + } + + let mut wb = WorldTimeBundle::default(); + wb.interval.0 = 200; + + commands.spawn((layer, wb)); +} + +fn show_time_info( + layers: Query<(&WorldTime, &LinearTimeTicking)>, + mut clients: Query<&mut Client>, + server: Res, + mut lt: ResMut +) { + let layer = layers.single(); + for mut c in &mut clients { + let mspt = lt.time.elapsed().as_millis(); + lt.time = Instant::now(); + + let msg = format!( + "Server {} | mspt: {} | Time: {} | interval: {} | rate: {}", + server.current_tick(), + mspt, + layer.0.time_of_day, + layer.1.interval, + layer.1.rate + ); + c.send_action_bar_message(msg); + } +} + +fn change_time( + mut event: EventReader, + client: Query<(&Client, &HeldItem)>, + mut time: Query<&mut LinearTimeTicking>, +) { + let mut ticker = time.single_mut(); + for e in &mut event { + if let Ok((_, hi)) = client.get(e.client) { + match hi.slot() { + 36 => ticker.rate += 1, + 37 => ticker.rate -= 1, + 38 => ticker.interval += 1, + 39 => ticker.interval -= 1, + _ => (), + }; + } + } +} + +fn init_clients( + mut clients: Query< + ( + &mut Client, + &mut EntityLayerId, + &mut VisibleChunkLayer, + &mut VisibleEntityLayers, + &mut Position, + &mut GameMode, + &mut Inventory, + ), + Added, + >, + layers: Query, With)>, +) { + for ( + mut client, + mut layer_id, + mut visible_chunk_layer, + mut visible_entity_layers, + mut pos, + mut game_mode, + mut inv, + ) in &mut clients + { + let layer = layers.single(); + + layer_id.0 = layer; + visible_chunk_layer.0 = layer; + visible_entity_layers.0.insert(layer); + pos.set([0.0, SPAWN_Y as f64 + 1.0, 0.0]); + *game_mode = GameMode::Creative; + + client.send_chat_message( + " + Touch grass (left click) to control time! + - Diamond: increase rate + - Dirt: decrease rate + - Clock: increase interval + - Compass: decrease interval + + Have fun! + ", + ); + + inv.set_slot(36, ItemStack::new(ItemKind::Diamond, 1, None)); + inv.set_slot(37, ItemStack::new(ItemKind::Dirt, 1, None)); + inv.set_slot(38, ItemStack::new(ItemKind::Clock, 1, None)); + inv.set_slot(39, ItemStack::new(ItemKind::Compass, 1, None)); + } +} From d236a91bf510994d9a088e322266bb9c07bcab4f Mon Sep 17 00:00:00 2001 From: tachibanayui <33594017+tachibanayui@users.noreply.github.com> Date: Sun, 17 Sep 2023 21:04:15 +0700 Subject: [PATCH 4/7] Fix CI --- assets/depgraph.svg | 894 ++++++++++++++----------- crates/valence_world_time/README.md | 75 +++ crates/valence_world_time/src/extra.rs | 2 - crates/valence_world_time/src/lib.rs | 68 +- examples/wrapping_time.rs | 9 +- 5 files changed, 600 insertions(+), 448 deletions(-) create mode 100644 crates/valence_world_time/README.md diff --git a/assets/depgraph.svg b/assets/depgraph.svg index b9523f69e..6a146773f 100644 --- a/assets/depgraph.svg +++ b/assets/depgraph.svg @@ -1,379 +1,523 @@ - + - - -%3 - - - -0 - -valence_advancement - - - -1 - -valence_server - - - -0->1 - - - - - -2 - -valence_entity - - - -1->2 - - - - - -11 - -valence_registry - - - -1->11 - - - - - -10 - -valence_server_common - - - -2->10 - - - - - -11->10 - - - - - -6 - -valence_protocol - - - -10->6 - - - - - -3 - -valence_math - - - -4 - -valence_nbt - - - -5 - -valence_ident - - - -7 - -valence_generated - - - -6->7 - - - - - -9 - -valence_text - - - -6->9 - - - - - -7->3 - - - - - -7->5 - - - - - -9->4 - - - - - -9->5 - - - - - -8 - -valence_build_utils - - - -12 - -valence_anvil - - - -12->1 - - - - - -13 - -valence_boss_bar - - - -13->1 - - - - - -14 - -valence_inventory - - - -14->1 - - - - - -15 - -valence_lang - - - -16 - -valence_network - - - -16->1 - - - - - -16->15 - - - - - -17 - -valence_player_list - - - -17->1 - - - - - -18 - -valence_scoreboard - - - -18->1 - - - - - -19 - -valence_spatial - - - -20 - -valence_weather - - - -20->1 - - - - - -21 - -valence_world_border - - - -21->1 - - - - - -22 - -dump_schedule - - - -23 - -valence - - - -22->23 - - - - - -23->0 - - - - - -23->12 - - - - - -23->13 - - - - - -23->14 - - - - - -23->16 - - - - - -23->17 - - - - - -23->18 - - - - - -23->20 - - - - - -23->21 - - - - - -24 - -packet_inspector - - - -24->6 - - - - - -25 - -playground - - - -25->23 - - - - - -26 - -stresser - - - -26->6 - - - - - + + + %3 + + + + 0 + + valence_advancement + + + + 1 + + valence_server + + + + 0->1 + + + + + + 2 + + valence_entity + + + + 1->2 + + + + + + 11 + + valence_registry + + + + 1->11 + + + + + + 10 + + valence_server_common + + + + 2->10 + + + + + + 11->10 + + + + + + 6 + + valence_protocol + + + + 10->6 + + + + + + 3 + + valence_math + + + + 4 + + valence_nbt + + + + 5 + + valence_ident + + + + 7 + + valence_generated + + + + 6->7 + + + + + + 9 + + valence_text + + + + 6->9 + + + + + + 7->3 + + + + + + 7->5 + + + + + + 9->4 + + + + + + 9->5 + + + + + + 8 + + valence_build_utils + + + + 12 + + valence_anvil + + + + 12->1 + + + + + + 13 + + valence_boss_bar + + + + 13->1 + + + + + + 14 + + valence_inventory + + + + 14->1 + + + + + + 15 + + valence_lang + + + + 16 + + valence_network + + + + 16->1 + + + + + + 16->15 + + + + + + 17 + + valence_player_list + + + + 17->1 + + + + + + 18 + + valence_scoreboard + + + + 18->1 + + + + + + 19 + + valence_spatial + + + + 20 + + valence_weather + + + + 20->1 + + + + + + 21 + + valence_world_border + + + + 21->1 + + + + + + 22 + + valence_world_time + + + + 22->1 + + + + + + 23 + + dump_schedule + + + + 24 + + valence + + + + 23->24 + + + + + + 24->0 + + + + + + 24->12 + + + + + + 24->13 + + + + + + 24->14 + + + + + + 24->16 + + + + + + 24->17 + + + + + + 24->18 + + + + + + 24->20 + + + + + + 24->21 + + + + + + 24->22 + + + + + + 25 + + packet_inspector + + + + 25->6 + + + + + + 26 + + playground + + + + 26->24 + + + + + + 27 + + stresser + + + + 27->6 + + + + + \ No newline at end of file diff --git a/crates/valence_world_time/README.md b/crates/valence_world_time/README.md new file mode 100644 index 000000000..8c0d507d3 --- /dev/null +++ b/crates/valence_world_time/README.md @@ -0,0 +1,75 @@ +# Controlling World Time + +This module contains Components and Systems needed to update, tick, +broadcast information about the time of day and world age of a +[`ChunkLayer`]. + +## Enable world time + +To control world time of an [`ChunkLayer`], simply insert the +[`WorldTimeBundle`] bundle. We also need to broadcast world time updates to +clients. The [`IntervalBroadcast::default()`] provides configuration to +mimic vanilla behavior: + +```rust ignore +fn enable(mut commands: Commands, instance: Entity) { + commands.entity(instance).insert(WorldTimeBundle::default()); +} +``` + +## Set the time explicitly + +Mutating [`WorldTime`] will not automatically broadcast the +change to clients. Mutating [`SetTimeQuery`] to modify time +and broadcast the time changes immediately. + +```rust ignore +fn into_the_night(mut instances: Query<(&mut WorldTime, SetTimeQuery), With>) { + for (mut t1, mut t2) in instances.iter_mut() { + let time_to_set = DayPhase::Night.into(); + + // Using [`WorldTime`] - Change won't broadcast immediately + t1.time_of_day = time_to_set; + // Using [`SetTimeQuery`] - Change broadcast immediately + t2.time_of_day = time_to_set; + } +} +``` + +## Advacing the world time + +Time of day and world age can be ticked individually using +[`LinearTimeTicking`] and [`LinearWorldAging`] respectively. +If these components don't meet your requirements +(eg: you need time increment follow a sine wave ~~for some reason~~), +you can tick the time yourself by modifying the respective +fields on [`WorldTime`]. + +## Prevent client from automatically update WorldTime + +_(mimics `/gamerule doDaylightCycle false`)_ + +By default, client will continue to update world time if the server +doesn't send packet to sync time between client and server. +This can be toggled by using [`WorldTime::set_client_time_ticking()`] +of [`WorldTime`] to true. + +Here is an example of mimicking `/gamerule doDaylightCycle `: + +```rust ignore +#[derive(Component)] +pub struct DaylightCycle(pub bool); + +fn handle_game_rule_daylight_cycle( + mut instances: Query< + (&mut WorldTime, &mut LinearTimeTicking, &DaylightCycle), + Changed, + >, +) { + for (mut time, mut ticking, doCycle) in instances.iter_mut() { + // Stop client from update + time.set_client_time_ticking(!doCycle.0); + ticking.speed = if doCycle.0 { 1 } else { 0 }; + } +} +``` diff --git a/crates/valence_world_time/src/extra.rs b/crates/valence_world_time/src/extra.rs index a66504905..54da745c0 100644 --- a/crates/valence_world_time/src/extra.rs +++ b/crates/valence_world_time/src/extra.rs @@ -36,8 +36,6 @@ impl From for u64 { } } - - impl WorldTime { /// This function ensure that adding time will not resulting in /// time_of_day flipping sign. diff --git a/crates/valence_world_time/src/lib.rs b/crates/valence_world_time/src/lib.rs index 61786226e..40827f814 100644 --- a/crates/valence_world_time/src/lib.rs +++ b/crates/valence_world_time/src/lib.rs @@ -1,70 +1,4 @@ -//! # Controlling World Time -//! This module contains Components and Systems needed to update, tick, -//! broadcast information about the time of day and world age of a -//! [`ChunkLayer`]. -//! -//! ## Enable world time -//! To control world time of an [`ChunkLayer`], simply insert the -//! [`WorldTimeBundle`] bundle. We also need to broadcast world time updates to -//! clients. The [`IntervalTimeBroadcast::default()`] provides configuration to -//! mimic vanilla behavior: -//! ``` -//! fn enable(mut commands: Commands, instance: Entity) { -//! commands.entity(instance).insert(WorldTimeBundle::default()); -//! } -//! ``` -//! -//! ## Set the time explicitly -//! Mutating [`WorldTime`] will not automatically broadcast the -//! change to clients. Mutating [`SetTimeQuery`] to modify time -//! and broadcast the time changes immediately. -//! ``` -//! fn into_the_night(mut instances: Query<(&mut WorldTime, SetTimeQuery), With>) { -//! for (mut t1, mut t2) in instances.iter_mut() { -//! let time_to_set = DayPhase::Night.into(); -//! -//! // Using [`WorldTime`] - Change won't broadcast immediately -//! t1.time_of_day = time_to_set; -//! // Using [`SetTimeQuery`] - Change broadcast immediately -//! t2.time_of_day = time_to_set; -//! } -//! } -//! ``` -//! -//! ## Advacing the world time -//! Time of day and world age can be ticked individually using -//! [`LinearTimeTicking`] and [`LinearWorldAging`] respectively. -//! If these components don't meet your requirements -//! (eg: you need time increment follow a sine wave ~~for some reason~~), -//! you can tick the time yourself by modifying the respective -//! fields on [`WorldTime`]. -//! -//! ## Prevent client from automatically update WorldTime -//! *(mimics `/gamerule doDaylightCycle false`)* -//! -//! By default, client will continue to update world time if the server -//! doesn't send packet to sync time between client and server. -//! This can be toggled by using [`WorldTime::set_client_time_ticking()`] -//! of [`WorldTime`] to true. -//! -//! Here is an example of mimicing `/gamerule doDaylightCycle `: -//! ``` -//! #[derive(Component)] -//! pub struct DaylightCycle(pub bool); -//! -//! fn handle_game_rule_daylight_cycle( -//! mut instances: Query< -//! (&mut WorldTime, &mut LinearTimeTicking, &DaylightCycle), -//! Changed, -//! >, -//! ) { -//! for (mut time, mut ticking, doCycle) in instances.iter_mut() { -//! // Stop client from update -//! time.set_client_time_ticking(!doCycle.0); -//! ticking.speed = if doCycle.0 { 1 } else { 0 }; -//! } -//! } -//! ``` +#![doc = include_str!("../README.md")] #![allow(clippy::type_complexity)] #![deny( rustdoc::broken_intra_doc_links, diff --git a/examples/wrapping_time.rs b/examples/wrapping_time.rs index 51d69abea..590b10ff2 100644 --- a/examples/wrapping_time.rs +++ b/examples/wrapping_time.rs @@ -13,7 +13,9 @@ const SPAWN_Y: i32 = 64; fn main() { App::new() - .insert_resource(LastTickTimestamp { time: Instant::now() }) + .insert_resource(LastTickTimestamp { + time: Instant::now(), + }) .add_plugins(DefaultPlugins) .add_systems(Startup, setup) .add_systems( @@ -28,10 +30,9 @@ fn main() { .run(); } - #[derive(Resource)] struct LastTickTimestamp { - time: Instant + time: Instant, } fn setup( @@ -66,7 +67,7 @@ fn show_time_info( layers: Query<(&WorldTime, &LinearTimeTicking)>, mut clients: Query<&mut Client>, server: Res, - mut lt: ResMut + mut lt: ResMut, ) { let layer = layers.single(); for mut c in &mut clients { From c0e7b991139cf49c98d24de27edf1dab676b2a92 Mon Sep 17 00:00:00 2001 From: tachibanayui <33594017+tachibanayui@users.noreply.github.com> Date: Sun, 17 Sep 2023 21:31:42 +0700 Subject: [PATCH 5/7] typo --- assets/depgraph.svg | 2 +- crates/valence_world_time/src/lib.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/depgraph.svg b/assets/depgraph.svg index 6a146773f..b368d45c3 100644 --- a/assets/depgraph.svg +++ b/assets/depgraph.svg @@ -520,4 +520,4 @@ points="660,-190.1 656.5,-180.1 653,-190.1 660,-190.1" /> - \ No newline at end of file + diff --git a/crates/valence_world_time/src/lib.rs b/crates/valence_world_time/src/lib.rs index 40827f814..9e72e07eb 100644 --- a/crates/valence_world_time/src/lib.rs +++ b/crates/valence_world_time/src/lib.rs @@ -44,11 +44,11 @@ impl Plugin for WorldTimePlugin { handle_linear_world_aging, ) .before(FlushPacketsSet) - .before(handle_layer_time_boardcast), + .before(handle_layer_time_broadcastcast), ) .add_systems( PostUpdate, - handle_layer_time_boardcast.before(init_time_for_new_clients), + handle_layer_time_broadcastcast.before(init_time_for_new_clients), ) .add_systems(PostUpdate, init_time_for_new_clients); } @@ -188,7 +188,7 @@ fn init_time_for_new_clients( } } -fn handle_layer_time_boardcast( +fn handle_layer_time_broadcastcast( mut layers: Query<(&mut ChunkLayer, &WorldTime, &mut WorldTimeBroadcast)>, server: Res, ) { From b7231f5f5438c5c7223727ed1e9f39b5f14a1739 Mon Sep 17 00:00:00 2001 From: tachibanayui <33594017+tachibanayui@users.noreply.github.com> Date: Sun, 17 Sep 2023 21:44:59 +0700 Subject: [PATCH 6/7] another typo --- crates/valence_world_time/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/valence_world_time/src/lib.rs b/crates/valence_world_time/src/lib.rs index 9e72e07eb..f06f02ad1 100644 --- a/crates/valence_world_time/src/lib.rs +++ b/crates/valence_world_time/src/lib.rs @@ -44,11 +44,11 @@ impl Plugin for WorldTimePlugin { handle_linear_world_aging, ) .before(FlushPacketsSet) - .before(handle_layer_time_broadcastcast), + .before(handle_layer_time_broadcast), ) .add_systems( PostUpdate, - handle_layer_time_broadcastcast.before(init_time_for_new_clients), + handle_layer_time_broadcast.before(init_time_for_new_clients), ) .add_systems(PostUpdate, init_time_for_new_clients); } @@ -188,7 +188,7 @@ fn init_time_for_new_clients( } } -fn handle_layer_time_broadcastcast( +fn handle_layer_time_broadcast( mut layers: Query<(&mut ChunkLayer, &WorldTime, &mut WorldTimeBroadcast)>, server: Res, ) { From 1fc4f29c8d5767d4ed37e4eb8a5b622c2bf86b2d Mon Sep 17 00:00:00 2001 From: tachibanayui <33594017+tachibanayui@users.noreply.github.com> Date: Sun, 17 Sep 2023 22:08:52 +0700 Subject: [PATCH 7/7] update depgraph again --- assets/depgraph.svg | 908 +++++++++++++++++++------------------------- 1 file changed, 391 insertions(+), 517 deletions(-) diff --git a/assets/depgraph.svg b/assets/depgraph.svg index b368d45c3..3c179e7f9 100644 --- a/assets/depgraph.svg +++ b/assets/depgraph.svg @@ -1,523 +1,397 @@ - + - - %3 - - - - 0 - - valence_advancement - - - - 1 - - valence_server - - - - 0->1 - - - - - - 2 - - valence_entity - - - - 1->2 - - - - - - 11 - - valence_registry - - - - 1->11 - - - - - - 10 - - valence_server_common - - - - 2->10 - - - - - - 11->10 - - - - - - 6 - - valence_protocol - - - - 10->6 - - - - - - 3 - - valence_math - - - - 4 - - valence_nbt - - - - 5 - - valence_ident - - - - 7 - - valence_generated - - - - 6->7 - - - - - - 9 - - valence_text - - - - 6->9 - - - - - - 7->3 - - - - - - 7->5 - - - - - - 9->4 - - - - - - 9->5 - - - - - - 8 - - valence_build_utils - - - - 12 - - valence_anvil - - - - 12->1 - - - - - - 13 - - valence_boss_bar - - - - 13->1 - - - - - - 14 - - valence_inventory - - - - 14->1 - - - - - - 15 - - valence_lang - - - - 16 - - valence_network - - - - 16->1 - - - - - - 16->15 - - - - - - 17 - - valence_player_list - - - - 17->1 - - - - - - 18 - - valence_scoreboard - - - - 18->1 - - - - - - 19 - - valence_spatial - - - - 20 - - valence_weather - - - - 20->1 - - - - - - 21 - - valence_world_border - - - - 21->1 - - - - - - 22 - - valence_world_time - - - - 22->1 - - - - - - 23 - - dump_schedule - - - - 24 - - valence - - - - 23->24 - - - - - - 24->0 - - - - - - 24->12 - - - - - - 24->13 - - - - - - 24->14 - - - - - - 24->16 - - - - - - 24->17 - - - - - - 24->18 - - - - - - 24->20 - - - - - - 24->21 - - - - - - 24->22 - - - - - - 25 - - packet_inspector - - - - 25->6 - - - - - - 26 - - playground - - - - 26->24 - - - - - - 27 - - stresser - - - - 27->6 - - - - + viewBox="0.00 0.00 1654.50 620.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + +%3 + + + +0 + +valence_advancement + + + +1 + +valence_server + + + +0->1 + + + + + +2 + +valence_entity + + + +1->2 + + + + + +11 + +valence_registry + + + +1->11 + + + + + +10 + +valence_server_common + + + +2->10 + + + + + +11->10 + + + + + +6 + +valence_protocol + + + +10->6 + + + + + +3 + +valence_math + + + +4 + +valence_nbt + + + +5 + +valence_ident + + + +7 + +valence_generated + + + +6->7 + + + + + +9 + +valence_text + + + +6->9 + + + + + +7->3 + + + + + +7->5 + + + + + +9->4 + + + + + +9->5 + + + + + +8 + +valence_build_utils + + + +12 + +valence_anvil + + + +12->1 + + + + + +13 + +valence_boss_bar + + + +13->1 + + + + + +14 + +valence_inventory + + + +14->1 + + + + + +15 + +valence_lang + + + +16 + +valence_network + + + +16->1 + + + + + +16->15 + + + + + +17 + +valence_player_list + + + +17->1 + + + + + +18 + +valence_scoreboard + + + +18->1 + + + + + +19 + +valence_spatial + + + +20 + +valence_weather + + + +20->1 + + + + + +21 + +valence_world_border + + + +21->1 + + + + + +22 + +valence_world_time + + + +22->1 + + + + + +23 + +dump_schedule + + + +24 + +valence + + + +23->24 + + + + + +24->0 + + + + + +24->12 + + + + + +24->13 + + + + + +24->14 + + + + + +24->16 + + + + + +24->17 + + + + + +24->18 + + + + + +24->20 + + + + + +24->21 + + + + + +24->22 + + + + + +25 + +packet_inspector + + + +25->6 + + + + + +26 + +playground + + + +26->24 + + + + + +27 + +stresser + + + +27->6 + + + +