Added Calendar for temporal events based on the real-world calendar

This commit is contained in:
Joshua Barretto 2021-12-06 22:17:20 +00:00
parent c2bf1a5345
commit e3203080ed
17 changed files with 148 additions and 27 deletions

View File

@ -1790,8 +1790,9 @@ impl Client {
return Err(Error::Other("Failed to find entity from uid.".to_owned()));
}
},
ServerGeneral::TimeOfDay(time_of_day) => {
ServerGeneral::TimeOfDay(time_of_day, calendar) => {
self.target_time_of_day = Some(time_of_day);
*self.state.ecs_mut().write_resource() = calendar;
},
ServerGeneral::EntitySync(entity_sync_package) => {
self.state

View File

@ -12,6 +12,7 @@ bin_csv = ["ron", "csv", "structopt"]
bin_graphviz = ["petgraph"]
bin_cmd_doc_gen = []
rrt_pathfinding = ["kiddo"]
calendar_events = []
default = ["simd"]
@ -26,6 +27,8 @@ serde = { version = "1.0.110", features = ["derive", "rc"] }
# Util
enum-iterator = "0.7"
vek = { version = "=0.14.1", features = ["serde"] }
chrono = "0.4"
chrono-tz = "0.6"
# Strum
strum = { version = "0.23", features = ["derive"] }

View File

@ -4,6 +4,7 @@ use super::{
};
use crate::sync;
use common::{
calendar::Calendar,
character::{self, CharacterItem},
comp::{self, invite::InviteKind, item::MaterialStatManifest},
outcome::Outcome,
@ -176,7 +177,7 @@ pub enum ServerGeneral {
ChatMsg(comp::ChatMsg),
ChatMode(comp::ChatMode),
SetPlayerEntity(Uid),
TimeOfDay(TimeOfDay),
TimeOfDay(TimeOfDay, Calendar),
EntitySync(sync::EntitySyncPackage),
CompSync(sync::CompSyncPackage<EcsCompPacket>),
CreateEntity(sync::EntityPackage<EcsCompPacket>),
@ -305,7 +306,7 @@ impl ServerMsg {
| ServerGeneral::ChatMsg(_)
| ServerGeneral::ChatMode(_)
| ServerGeneral::SetPlayerEntity(_)
| ServerGeneral::TimeOfDay(_)
| ServerGeneral::TimeOfDay(_, _)
| ServerGeneral::EntitySync(_)
| ServerGeneral::CompSync(_)
| ServerGeneral::CreateEntity(_)

47
common/src/calendar.rs Normal file
View File

@ -0,0 +1,47 @@
use chrono::{DateTime, Datelike, Local, TimeZone, Utc};
use chrono_tz::Tz;
use hashbrown::HashSet;
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u16)]
pub enum CalendarEvent {
Christmas = 0,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Calendar {
events: HashSet<CalendarEvent>,
}
impl Calendar {
pub fn is_event(&self, event: CalendarEvent) -> bool { self.events.contains(&event) }
pub fn events(&self) -> impl ExactSizeIterator<Item = &CalendarEvent> + '_ {
self.events.iter()
}
pub fn from_events(events: Vec<CalendarEvent>) -> Self {
Self {
events: events.into_iter().collect(),
}
}
pub fn from_tz(tz: Option<Tz>) -> Self {
let mut this = Self::default();
let now = match tz {
Some(tz) => {
let utc = Utc::now().naive_utc();
DateTime::<Tz>::from_utc(utc, tz.offset_from_utc_datetime(&utc)).naive_local()
},
None => Local::now().naive_local(),
};
if now.month() == 12 && (24..=26).contains(&now.day()) {
this.events.insert(CalendarEvent::Christmas);
}
this
}
}

View File

@ -27,6 +27,8 @@ pub use common_assets as assets;
#[cfg(not(target_arch = "wasm32"))]
mod cached_spatial_grid;
#[cfg(not(target_arch = "wasm32"))]
pub mod calendar;
#[cfg(not(target_arch = "wasm32"))]
pub mod character;
#[cfg(not(target_arch = "wasm32"))] pub mod clock;
#[cfg(not(target_arch = "wasm32"))] pub mod cmd;

View File

@ -5,6 +5,7 @@ use crate::plugin::PluginMgr;
#[cfg(feature = "plugins")]
use common::uid::UidAllocator;
use common::{
calendar::Calendar,
comp,
event::{EventBus, LocalEvent, ServerEvent},
outcome::Outcome,
@ -199,6 +200,7 @@ impl State {
// Register synced resources used by the ECS.
ecs.insert(TimeOfDay(0.0));
ecs.insert(Calendar::default());
// Register unsynced resources used by the ECS.
ecs.insert(Time(0.0));

View File

@ -41,6 +41,7 @@ rustls = { version = "0.20", default-features = false }
rustls-pemfile = { version = "0.2.1", default-features = false }
atomicwrites = "0.3.0"
chrono = { version = "0.4.19", features = ["serde"] }
chrono-tz = { version = "0.6", features = ["serde"] }
humantime = "2.1.0"
itertools = "0.10"
lazy_static = "1.4.0"

View File

@ -2,7 +2,8 @@ use crate::metrics::ChunkGenMetrics;
#[cfg(not(feature = "worldgen"))]
use crate::test_world::{IndexOwned, World};
use common::{
generation::ChunkSupplement, resources::TimeOfDay, slowjob::SlowJobPool, terrain::TerrainChunk,
calendar::Calendar, generation::ChunkSupplement, resources::TimeOfDay, slowjob::SlowJobPool,
terrain::TerrainChunk,
};
use hashbrown::{hash_map::Entry, HashMap};
use specs::Entity as EcsEntity;
@ -43,7 +44,7 @@ impl ChunkGenerator {
slowjob_pool: &SlowJobPool,
world: Arc<World>,
index: IndexOwned,
time: TimeOfDay,
time: (TimeOfDay, Calendar),
) {
let v = if let Entry::Vacant(v) = self.pending_chunks.entry(key) {
v

View File

@ -123,7 +123,7 @@ impl Client {
| ServerGeneral::ChatMsg(_)
| ServerGeneral::ChatMode(_)
| ServerGeneral::SetPlayerEntity(_)
| ServerGeneral::TimeOfDay(_)
| ServerGeneral::TimeOfDay(_, _)
| ServerGeneral::EntitySync(_)
| ServerGeneral::CompSync(_)
| ServerGeneral::CreateEntity(_)
@ -195,7 +195,7 @@ impl Client {
| ServerGeneral::ChatMsg(_)
| ServerGeneral::ChatMode(_)
| ServerGeneral::SetPlayerEntity(_)
| ServerGeneral::TimeOfDay(_)
| ServerGeneral::TimeOfDay(_, _)
| ServerGeneral::EntitySync(_)
| ServerGeneral::CompSync(_)
| ServerGeneral::CreateEntity(_)

View File

@ -18,6 +18,7 @@ use authc::Uuid;
use chrono::{NaiveTime, Timelike, Utc};
use common::{
assets,
calendar::Calendar,
cmd::{
ChatCommand, BUFF_PACK, BUFF_PARSER, ITEM_SPECS, KIT_MANIFEST_PATH, PRESET_MANIFEST_PATH,
},
@ -986,9 +987,14 @@ fn handle_time(
// wait for the next 100th tick to receive the update).
let mut tod_lazymsg = None;
let clients = server.state.ecs().read_storage::<Client>();
let calendar = server.state.ecs().read_resource::<Calendar>();
for client in (&clients).join() {
let msg = tod_lazymsg
.unwrap_or_else(|| client.prepare(ServerGeneral::TimeOfDay(TimeOfDay(new_time))));
let msg = tod_lazymsg.unwrap_or_else(|| {
client.prepare(ServerGeneral::TimeOfDay(
TimeOfDay(new_time),
(*calendar).clone(),
))
});
let _ = client.send_prepared(&msg);
tod_lazymsg = Some(msg);
}

View File

@ -42,7 +42,7 @@ pub use crate::{
error::Error,
events::Event,
input::Input,
settings::{EditableSettings, Settings},
settings::{CalendarMode, EditableSettings, Settings},
};
#[cfg(feature = "persistent_world")]
@ -64,6 +64,7 @@ use crate::{
use common::grid::Grid;
use common::{
assets::AssetExt,
calendar::Calendar,
character::CharacterId,
cmd::ChatCommand,
comp::{self, item::MaterialStatManifest},
@ -593,6 +594,17 @@ impl Server {
self.state.ecs().write_resource::<Tick>().0 += 1;
self.state.ecs().write_resource::<TickStart>().0 = Instant::now();
// Update calendar events as time changes
// TODO: If a lot of calendar events get added, this might become expensive.
// Maybe don't do this every tick?
let new_calendar = match &self.state.ecs().read_resource::<Settings>().calendar_mode {
CalendarMode::None => Calendar::default(),
CalendarMode::Auto => Calendar::from_tz(None),
CalendarMode::Timezone(tz) => Calendar::from_tz(Some(*tz)),
CalendarMode::Events(events) => Calendar::from_events(events.clone()),
};
*self.state.ecs_mut().write_resource::<Calendar>() = new_calendar;
// This tick function is the centre of the Veloren universe. Most server-side
// things are managed from here, and as such it's important that it
// stays organised. Please consult the core developers before making
@ -891,7 +903,10 @@ impl Server {
&slow_jobs,
Arc::clone(world),
index.clone(),
*ecs.read_resource::<TimeOfDay>(),
(
*ecs.read_resource::<TimeOfDay>(),
(*ecs.read_resource::<Calendar>()).clone(),
),
);
});
}
@ -1104,7 +1119,10 @@ impl Server {
&slow_jobs,
Arc::clone(&self.world),
self.index.clone(),
*ecs.read_resource::<TimeOfDay>(),
(
*ecs.read_resource::<TimeOfDay>(),
(*ecs.read_resource::<Calendar>()).clone(),
),
);
}

View File

@ -14,7 +14,7 @@ pub use server_description::ServerDescription;
pub use whitelist::{Whitelist, WhitelistInfo, WhitelistRecord};
use chrono::Utc;
use common::resources::BattleMode;
use common::{calendar::CalendarEvent, resources::BattleMode};
use core::time::Duration;
use portpicker::pick_unused_port;
use serde::{Deserialize, Serialize};
@ -62,6 +62,18 @@ impl ServerBattleMode {
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum CalendarMode {
None,
Auto,
Timezone(chrono_tz::Tz),
Events(Vec<CalendarEvent>),
}
impl Default for CalendarMode {
fn default() -> Self { Self::Auto }
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct Settings {
@ -84,6 +96,7 @@ pub struct Settings {
pub spawn_town: Option<String>,
pub safe_spawn: bool,
pub max_player_for_kill_broadcast: Option<usize>,
pub calendar_mode: CalendarMode,
/// Experimental feature. No guaranteed forwards-compatibility, may be
/// removed at *any time* with no migration.
@ -107,6 +120,7 @@ impl Default for Settings {
max_view_distance: Some(65),
banned_words_files: Vec::new(),
max_player_group_size: 6,
calendar_mode: CalendarMode::Auto,
client_timeout: Duration::from_secs(40),
spawn_town: None,
safe_spawn: true,

View File

@ -8,6 +8,7 @@ use crate::{
wiring, BattleModeBuffer, SpawnPoint,
};
use common::{
calendar::Calendar,
character::CharacterId,
combat,
combat::DamageContributor,
@ -434,7 +435,8 @@ impl StateExt for State {
.for_each(|chunk_key| {
#[cfg(feature = "worldgen")]
{
chunk_generator.generate_chunk(None, chunk_key, &slow_jobs, Arc::clone(world), index.clone(), *ecs.read_resource::<TimeOfDay>());
let time = (*ecs.read_resource::<TimeOfDay>(), (*ecs.read_resource::<Calendar>()).clone());
chunk_generator.generate_chunk(None, chunk_key, &slow_jobs, Arc::clone(world), index.clone(), time);
}
});
}

View File

@ -5,6 +5,7 @@ use crate::{
Tick,
};
use common::{
calendar::Calendar,
comp::{Collider, ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Player, Pos, Vel},
outcome::Outcome,
region::{Event as RegionEvent, RegionMap},
@ -28,6 +29,7 @@ impl<'a> System<'a> for Sys {
Entities<'a>,
Read<'a, Tick>,
ReadExpect<'a, TimeOfDay>,
ReadExpect<'a, Calendar>,
ReadExpect<'a, RegionMap>,
ReadStorage<'a, Uid>,
ReadStorage<'a, Pos>,
@ -61,6 +63,7 @@ impl<'a> System<'a> for Sys {
entities,
tick,
time_of_day,
calendar,
region_map,
uids,
positions,
@ -360,8 +363,9 @@ impl<'a> System<'a> for Sys {
if tick % TOD_SYNC_FREQ == 0 {
let mut tod_lazymsg = None;
for client in (&clients).join() {
let msg = tod_lazymsg
.unwrap_or_else(|| client.prepare(ServerGeneral::TimeOfDay(*time_of_day)));
let msg = tod_lazymsg.unwrap_or_else(|| {
client.prepare(ServerGeneral::TimeOfDay(*time_of_day, (*calendar).clone()))
});
// We don't care much about stream errors here since they could just represent
// network disconnection, which is handled elsewhere.
let _ = client.send_prepared(&msg);

View File

@ -15,6 +15,7 @@ use crate::{
ChunkRequest, SpawnPoint, Tick,
};
use common::{
calendar::Calendar,
comp::{self, agent, bird_medium, BehaviorCapability, ForceUpdate, Pos, Waypoint},
event::{EventBus, ServerEvent},
generation::EntityInfo,
@ -110,6 +111,7 @@ impl<'a> System<'a> for Sys {
Read<'a, SpawnPoint>,
Read<'a, Settings>,
Read<'a, TimeOfDay>,
Read<'a, Calendar>,
ReadExpect<'a, SlowJobPool>,
ReadExpect<'a, IndexOwned>,
ReadExpect<'a, Arc<World>>,
@ -142,6 +144,7 @@ impl<'a> System<'a> for Sys {
spawn_point,
server_settings,
time_of_day,
calendar,
slow_jobs,
index,
world,
@ -176,7 +179,7 @@ impl<'a> System<'a> for Sys {
&slow_jobs,
Arc::clone(&world),
index.clone(),
*time_of_day,
(*time_of_day, calendar.clone()),
)
});

View File

@ -1,6 +1,7 @@
use crate::{column::ColumnSample, sim::SimChunk, IndexRef, CONFIG};
use common::{
assets::{self, AssetExt},
calendar::{Calendar, CalendarEvent},
generation::{ChunkSupplement, EntityInfo},
resources::TimeOfDay,
terrain::Block,
@ -40,7 +41,12 @@ impl assets::Asset for SpawnEntry {
impl SpawnEntry {
pub fn from(asset_specifier: &str) -> Self { Self::load_expect_cloned(asset_specifier) }
pub fn request(&self, requested_period: DayPeriod, underwater: bool) -> Option<Pack> {
pub fn request(
&self,
requested_period: DayPeriod,
calendar: Option<&Calendar>,
underwater: bool,
) -> Option<Pack> {
self.rules
.iter()
.find(|pack| {
@ -48,8 +54,15 @@ impl SpawnEntry {
.day_period
.iter()
.any(|period| *period == requested_period);
let calendar_match = if let Some(calendar) = calendar {
pack.calendar_events.as_ref().map_or(true, |events| {
events.iter().any(|event| calendar.is_event(*event))
})
} else {
false
};
let water_match = pack.is_underwater == underwater;
time_match && water_match
time_match && calendar_match && water_match
})
.cloned()
}
@ -97,6 +110,9 @@ pub struct Pack {
pub groups: Vec<(Weight, (Min, Max, String))>,
pub is_underwater: bool,
pub day_period: Vec<DayPeriod>,
#[serde(default)]
pub calendar_events: Option<Vec<CalendarEvent>>, /* None implies that the group isn't
* limited by calendar events */
}
impl Pack {
@ -271,7 +287,7 @@ pub fn apply_wildlife_supplement<'a, R: Rng>(
index: IndexRef,
chunk: &SimChunk,
supplement: &mut ChunkSupplement,
time: Option<TimeOfDay>,
time: Option<(TimeOfDay, Calendar)>,
) {
let scatter = &index.wildlife_spawns;
@ -289,12 +305,11 @@ pub fn apply_wildlife_supplement<'a, R: Rng>(
};
let underwater = col_sample.water_level > col_sample.alt;
let current_day_period;
if let Some(time) = time {
current_day_period = DayPeriod::from(time.0)
let (current_day_period, calendar) = if let Some((time, calendar)) = &time {
(DayPeriod::from(time.0), Some(calendar))
} else {
current_day_period = DayPeriod::Noon
}
(DayPeriod::Noon, None)
};
let entity_group = scatter
.iter()
@ -305,7 +320,7 @@ pub fn apply_wildlife_supplement<'a, R: Rng>(
.then(|| {
entry
.read()
.request(current_day_period, underwater)
.request(current_day_period, calendar, underwater)
.and_then(|pack| {
(dynamic_rng.gen::<f32>() < density * col_sample.spawn_rate
&& col_sample.gradient < Some(1.3))

View File

@ -49,6 +49,7 @@ use crate::{
};
use common::{
assets,
calendar::Calendar,
generation::{ChunkSupplement, EntityInfo},
resources::TimeOfDay,
terrain::{
@ -215,7 +216,7 @@ impl World {
chunk_pos: Vec2<i32>,
// TODO: misleading name
mut should_continue: impl FnMut() -> bool,
time: Option<TimeOfDay>,
time: Option<(TimeOfDay, Calendar)>,
) -> Result<(TerrainChunk, ChunkSupplement), ()> {
let mut sampler = self.sample_blocks();