mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Added Calendar for temporal events based on the real-world calendar
This commit is contained in:
parent
c2bf1a5345
commit
e3203080ed
@ -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
|
||||
|
@ -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"] }
|
||||
|
@ -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
47
common/src/calendar.rs
Normal 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
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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));
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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(_)
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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()),
|
||||
)
|
||||
});
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user