Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

world time implementation #526

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ default = [
"player_list",
"scoreboard",
"world_border",
"world_time",
"weather",
"testing",
]
Expand All @@ -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 = []

Expand All @@ -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
Expand Down Expand Up @@ -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"
304 changes: 161 additions & 143 deletions assets/depgraph.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions crates/valence_world_time/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
75 changes: 75 additions & 0 deletions crates/valence_world_time/README.md
Original file line number Diff line number Diff line change
@@ -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<Instance>>) {
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
## Advacing the world time
## Advancing 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 <value>`:

```rust ignore
#[derive(Component)]
pub struct DaylightCycle(pub bool);

fn handle_game_rule_daylight_cycle(
mut instances: Query<
(&mut WorldTime, &mut LinearTimeTicking, &DaylightCycle),
Changed<DaylightCycle>,
>,
) {
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 };
}
}
```
115 changes: 115 additions & 0 deletions crates/valence_world_time/src/extra.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
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<DayPhase> for u64 {
fn from(value: DayPhase) -> Self {
value as Self
}
}

/// Reference: <https://minecraft.fandom.com/wiki/Daylight_cycle#Moon_phases>
pub enum MoonPhase {
FullMoon = 0,
WaningGibbous = 1,
ThirdQuarter = 2,
WaningCrescent = 3,
NewMoon = 4,
WaxingCrescent = 5,
FirstQuarter = 6,
WaxingGibbous = 7,
}

impl From<MoonPhase> 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<i64>) {
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<u64>) {
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);
}
}
Loading
Loading