veloren/common/src/state.rs

403 lines
14 KiB
Rust
Raw Normal View History

use crate::{
2019-06-28 23:42:51 +00:00
comp,
event::{EventBus, LocalEvent, ServerEvent, SfxEventItem},
region::RegionMap,
2019-11-24 20:12:03 +00:00
sync::WorldSyncExt,
sys,
common: Rework volume API See the doc comments in `common/src/vol.rs` for more information on the API itself. The changes include: * Consistent `Err`/`Error` naming. * Types are named `...Error`. * `enum` variants are named `...Err`. * Rename `VolMap{2d, 3d}` -> `VolGrid{2d, 3d}`. This is in preparation to an upcoming change where a “map” in the game related sense will be added. * Add volume iterators. There are two types of them: * _Position_ iterators obtained from the trait `IntoPosIterator` using the method `fn pos_iter(self, lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> ...` which returns an iterator over `Vec3<i32>`. * _Volume_ iterators obtained from the trait `IntoVolIterator` using the method `fn vol_iter(self, lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> ...` which returns an iterator over `(Vec3<i32>, &Self::Vox)`. Those traits will usually be implemented by references to volume types (i.e. `impl IntoVolIterator<'a> for &'a T` where `T` is some type which usually implements several volume traits, such as `Chunk`). * _Position_ iterators iterate over the positions valid for that volume. * _Volume_ iterators do the same but return not only the position but also the voxel at that position, in each iteration. * Introduce trait `RectSizedVol` for the use case which we have with `Chonk`: A `Chonk` is sized only in x and y direction. * Introduce traits `RasterableVol`, `RectRasterableVol` * `RasterableVol` represents a volume that is compile-time sized and has its lower bound at `(0, 0, 0)`. The name `RasterableVol` was chosen because such a volume can be used with `VolGrid3d`. * `RectRasterableVol` represents a volume that is compile-time sized at least in x and y direction and has its lower bound at `(0, 0, z)`. There's no requirement on he lower bound or size in z direction. The name `RectRasterableVol` was chosen because such a volume can be used with `VolGrid2d`.
2019-09-03 22:23:29 +00:00
terrain::{Block, TerrainChunk, TerrainGrid},
vol::WriteVol,
};
2019-08-11 20:38:28 +00:00
use hashbrown::{HashMap, HashSet};
use rayon::{ThreadPool, ThreadPoolBuilder};
use serde_derive::{Deserialize, Serialize};
use specs::{
shred::{Fetch, FetchMut},
storage::{MaskedStorage as EcsMaskedStorage, Storage as EcsStorage},
2019-11-30 06:41:20 +00:00
Component, DispatcherBuilder, Entity as EcsEntity, WorldExt,
};
use std::{sync::Arc, time::Duration};
2019-01-23 20:01:58 +00:00
use vek::*;
2019-01-02 19:22:01 +00:00
/// How much faster should an in-game day be compared to a real day?
// TODO: Don't hard-code this.
const DAY_CYCLE_FACTOR: f64 = 24.0 * 2.0;
/// A resource that stores the time of day.
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct TimeOfDay(pub f64);
/// A resource that stores the tick (i.e: physics) time.
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
pub struct Time(pub f64);
2019-01-23 20:01:58 +00:00
/// A resource that stores the time since the previous tick.
#[derive(Default)]
pub struct DeltaTime(pub f32);
/// At what point should we stop speeding up physics to compensate for lag? If
/// we speed physics up too fast, we'd skip important physics events like
/// collisions. This constant determines the upper limit. If delta time exceeds
/// this value, the game's physics will begin to produce time lag. Ideally, we'd
/// avoid such a situation.
const MAX_DELTA_TIME: f32 = 1.0;
2020-03-10 18:12:16 +00:00
const HUMANOID_JUMP_ACCEL: f32 = 16.0;
2019-08-07 15:39:16 +00:00
#[derive(Default)]
pub struct BlockChange {
2019-08-11 20:38:28 +00:00
blocks: HashMap<Vec3<i32>, Block>,
}
impl BlockChange {
pub fn set(&mut self, pos: Vec3<i32>, block: Block) { self.blocks.insert(pos, block); }
2019-09-25 14:56:57 +00:00
pub fn try_set(&mut self, pos: Vec3<i32>, block: Block) -> Option<()> {
if !self.blocks.contains_key(&pos) {
self.blocks.insert(pos, block);
Some(())
} else {
None
}
}
pub fn clear(&mut self) { self.blocks.clear(); }
}
2019-08-07 15:39:16 +00:00
#[derive(Default)]
pub struct TerrainChanges {
2019-08-11 20:38:28 +00:00
pub new_chunks: HashSet<Vec2<i32>>,
pub modified_chunks: HashSet<Vec2<i32>>,
pub removed_chunks: HashSet<Vec2<i32>>,
pub modified_blocks: HashMap<Vec3<i32>, Block>,
2019-01-23 20:01:58 +00:00
}
impl TerrainChanges {
pub fn clear(&mut self) {
2019-01-23 20:01:58 +00:00
self.new_chunks.clear();
self.modified_chunks.clear();
2019-01-23 20:01:58 +00:00
self.removed_chunks.clear();
}
}
/// A type used to represent game state stored on both the client and the
/// server. This includes things like entity components, terrain data, and
/// global states like weather, time of day, etc.
2019-01-02 19:22:01 +00:00
pub struct State {
2019-11-04 00:57:36 +00:00
ecs: specs::World,
// Avoid lifetime annotation by storing a thread pool instead of the whole dispatcher
thread_pool: Arc<ThreadPool>,
2019-01-02 19:22:01 +00:00
}
impl Default for State {
/// Create a new `State`.
fn default() -> Self {
Self {
2019-11-04 00:57:36 +00:00
ecs: Self::setup_ecs_world(),
thread_pool: Arc::new(ThreadPoolBuilder::new().build().unwrap()),
}
}
}
2019-01-02 19:22:01 +00:00
impl State {
2019-11-04 00:57:36 +00:00
/// Creates ecs world and registers all the common components and resources
// TODO: Split up registering into server and client (e.g. move
// EventBus<ServerEvent> to the server)
2019-11-04 00:57:36 +00:00
fn setup_ecs_world() -> specs::World {
let mut ecs = specs::World::new();
// Uids for sync
ecs.register_sync_marker();
// Register server -> all clients synced components.
ecs.register::<comp::Loadout>();
2019-11-04 00:57:36 +00:00
ecs.register::<comp::Body>();
ecs.register::<comp::Player>();
ecs.register::<comp::Stats>();
ecs.register::<comp::Energy>();
2019-11-04 00:57:36 +00:00
ecs.register::<comp::CanBuild>();
ecs.register::<comp::LightEmitter>();
ecs.register::<comp::Item>();
ecs.register::<comp::Scale>();
ecs.register::<comp::Mounting>();
ecs.register::<comp::MountState>();
ecs.register::<comp::Mass>();
ecs.register::<comp::Sticky>();
ecs.register::<comp::Gravity>();
// Register components send from clients -> server
ecs.register::<comp::Controller>();
// Register components send directly from server -> all but one client
ecs.register::<comp::CharacterState>();
ecs.register::<comp::PhysicsState>();
// Register components synced from client -> server -> all other clients
ecs.register::<comp::Pos>();
ecs.register::<comp::Vel>();
ecs.register::<comp::Ori>();
ecs.register::<comp::Inventory>();
// Register server-local components
2019-11-04 00:57:36 +00:00
// TODO: only register on the server
ecs.register::<comp::Last<comp::Pos>>();
ecs.register::<comp::Last<comp::Vel>>();
ecs.register::<comp::Last<comp::Ori>>();
ecs.register::<comp::Last<comp::CharacterState>>();
ecs.register::<comp::Agent>();
ecs.register::<comp::Alignment>();
ecs.register::<comp::WaypointArea>();
ecs.register::<comp::ForceUpdate>();
ecs.register::<comp::InventoryUpdate>();
ecs.register::<comp::Admin>();
2019-09-25 20:22:39 +00:00
ecs.register::<comp::Waypoint>();
ecs.register::<comp::Projectile>();
2020-03-22 04:49:32 +00:00
ecs.register::<comp::Attacking>();
// Register synced resources used by the ECS.
2019-11-30 06:41:20 +00:00
ecs.insert(TimeOfDay(0.0));
// Register unsynced resources used by the ECS.
2019-11-30 06:41:20 +00:00
ecs.insert(Time(0.0));
ecs.insert(DeltaTime(0.0));
ecs.insert(TerrainGrid::new().unwrap());
ecs.insert(BlockChange::default());
ecs.insert(TerrainChanges::default());
// TODO: only register on the server
2019-11-30 06:41:20 +00:00
ecs.insert(EventBus::<ServerEvent>::default());
ecs.insert(EventBus::<LocalEvent>::default());
ecs.insert(EventBus::<SfxEventItem>::default());
ecs.insert(RegionMap::new());
2019-11-04 00:57:36 +00:00
ecs
}
/// Register a component with the state's ECS.
pub fn with_component<T: Component>(mut self) -> Self
where
<T as Component>::Storage: Default,
{
self.ecs.register::<T>();
self
}
/// Write a component attributed to a particular entity.
pub fn write_component<C: Component>(&mut self, entity: EcsEntity, comp: C) {
let _ = self.ecs.write_storage().insert(entity, comp);
}
/// Delete a component attributed to a particular entity.
pub fn delete_component<C: Component>(&mut self, entity: EcsEntity) -> Option<C> {
self.ecs.write_storage().remove(entity)
}
/// Read a component attributed to a particular entity.
pub fn read_component_cloned<C: Component + Clone>(&self, entity: EcsEntity) -> Option<C> {
self.ecs.read_storage().get(entity).cloned()
}
/// Get a read-only reference to the storage of a particular component type.
pub fn read_storage<C: Component>(&self) -> EcsStorage<C, Fetch<EcsMaskedStorage<C>>> {
self.ecs.read_storage::<C>()
}
/// Get a reference to the internal ECS world.
pub fn ecs(&self) -> &specs::World { &self.ecs }
2019-01-23 20:01:58 +00:00
/// Get a mutable reference to the internal ECS world.
pub fn ecs_mut(&mut self) -> &mut specs::World { &mut self.ecs }
/// Get a reference to the `TerrainChanges` structure of the state. This
/// contains information about terrain state that has changed since the
/// last game tick.
pub fn terrain_changes(&self) -> Fetch<TerrainChanges> { self.ecs.read_resource() }
2019-01-23 20:01:58 +00:00
/// Get the current in-game time of day.
///
/// Note that this should not be used for physics, animations or other such
/// localised timings.
pub fn get_time_of_day(&self) -> f64 { self.ecs.read_resource::<TimeOfDay>().0 }
2019-01-23 20:01:58 +00:00
/// Get the current in-game time.
///
/// Note that this does not correspond to the time of day.
pub fn get_time(&self) -> f64 { self.ecs.read_resource::<Time>().0 }
/// Get the current delta time.
pub fn get_delta_time(&self) -> f32 { self.ecs.read_resource::<DeltaTime>().0 }
2019-01-14 18:49:53 +00:00
/// Get a reference to this state's terrain.
pub fn terrain(&self) -> Fetch<TerrainGrid> { self.ecs.read_resource() }
/// Get a writable reference to this state's terrain.
pub fn terrain_mut(&self) -> FetchMut<TerrainGrid> { self.ecs.write_resource() }
2019-09-25 14:56:57 +00:00
/// Set a block in this state's terrain.
pub fn set_block(&mut self, pos: Vec3<i32>, block: Block) {
self.ecs.write_resource::<BlockChange>().set(pos, block);
}
/// Set a block in this state's terrain. Will return `None` if the block has
/// already been modified this tick.
2019-09-25 14:56:57 +00:00
pub fn try_set_block(&mut self, pos: Vec3<i32>, block: Block) -> Option<()> {
self.ecs.write_resource::<BlockChange>().try_set(pos, block)
}
/// Removes every chunk of the terrain.
pub fn clear_terrain(&mut self) {
let keys = self
.terrain_mut()
.drain()
.map(|(key, _)| key)
.collect::<Vec<_>>();
for key in keys {
self.remove_chunk(key);
}
}
/// Insert the provided chunk into this state's terrain.
pub fn insert_chunk(&mut self, key: Vec2<i32>, chunk: TerrainChunk) {
if self
.ecs
common: Rework volume API See the doc comments in `common/src/vol.rs` for more information on the API itself. The changes include: * Consistent `Err`/`Error` naming. * Types are named `...Error`. * `enum` variants are named `...Err`. * Rename `VolMap{2d, 3d}` -> `VolGrid{2d, 3d}`. This is in preparation to an upcoming change where a “map” in the game related sense will be added. * Add volume iterators. There are two types of them: * _Position_ iterators obtained from the trait `IntoPosIterator` using the method `fn pos_iter(self, lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> ...` which returns an iterator over `Vec3<i32>`. * _Volume_ iterators obtained from the trait `IntoVolIterator` using the method `fn vol_iter(self, lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> ...` which returns an iterator over `(Vec3<i32>, &Self::Vox)`. Those traits will usually be implemented by references to volume types (i.e. `impl IntoVolIterator<'a> for &'a T` where `T` is some type which usually implements several volume traits, such as `Chunk`). * _Position_ iterators iterate over the positions valid for that volume. * _Volume_ iterators do the same but return not only the position but also the voxel at that position, in each iteration. * Introduce trait `RectSizedVol` for the use case which we have with `Chonk`: A `Chonk` is sized only in x and y direction. * Introduce traits `RasterableVol`, `RectRasterableVol` * `RasterableVol` represents a volume that is compile-time sized and has its lower bound at `(0, 0, 0)`. The name `RasterableVol` was chosen because such a volume can be used with `VolGrid3d`. * `RectRasterableVol` represents a volume that is compile-time sized at least in x and y direction and has its lower bound at `(0, 0, z)`. There's no requirement on he lower bound or size in z direction. The name `RectRasterableVol` was chosen because such a volume can be used with `VolGrid2d`.
2019-09-03 22:23:29 +00:00
.write_resource::<TerrainGrid>()
.insert(key, Arc::new(chunk))
.is_some()
{
self.ecs
.write_resource::<TerrainChanges>()
2019-07-01 13:38:45 +00:00
.modified_chunks
.insert(key);
} else {
self.ecs
.write_resource::<TerrainChanges>()
2019-07-01 13:38:45 +00:00
.new_chunks
.insert(key);
}
2019-01-23 20:01:58 +00:00
}
/// Remove the chunk with the given key from this state's terrain, if it
/// exists.
pub fn remove_chunk(&mut self, key: Vec2<i32>) {
if self
.ecs
common: Rework volume API See the doc comments in `common/src/vol.rs` for more information on the API itself. The changes include: * Consistent `Err`/`Error` naming. * Types are named `...Error`. * `enum` variants are named `...Err`. * Rename `VolMap{2d, 3d}` -> `VolGrid{2d, 3d}`. This is in preparation to an upcoming change where a “map” in the game related sense will be added. * Add volume iterators. There are two types of them: * _Position_ iterators obtained from the trait `IntoPosIterator` using the method `fn pos_iter(self, lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> ...` which returns an iterator over `Vec3<i32>`. * _Volume_ iterators obtained from the trait `IntoVolIterator` using the method `fn vol_iter(self, lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> ...` which returns an iterator over `(Vec3<i32>, &Self::Vox)`. Those traits will usually be implemented by references to volume types (i.e. `impl IntoVolIterator<'a> for &'a T` where `T` is some type which usually implements several volume traits, such as `Chunk`). * _Position_ iterators iterate over the positions valid for that volume. * _Volume_ iterators do the same but return not only the position but also the voxel at that position, in each iteration. * Introduce trait `RectSizedVol` for the use case which we have with `Chonk`: A `Chonk` is sized only in x and y direction. * Introduce traits `RasterableVol`, `RectRasterableVol` * `RasterableVol` represents a volume that is compile-time sized and has its lower bound at `(0, 0, 0)`. The name `RasterableVol` was chosen because such a volume can be used with `VolGrid3d`. * `RectRasterableVol` represents a volume that is compile-time sized at least in x and y direction and has its lower bound at `(0, 0, z)`. There's no requirement on he lower bound or size in z direction. The name `RectRasterableVol` was chosen because such a volume can be used with `VolGrid2d`.
2019-09-03 22:23:29 +00:00
.write_resource::<TerrainGrid>()
.remove(key)
.is_some()
{
self.ecs
.write_resource::<TerrainChanges>()
2019-07-01 13:38:45 +00:00
.removed_chunks
.insert(key);
}
}
// Run RegionMap tick to update entity region occupancy
pub fn update_region_map(&self) {
self.ecs.write_resource::<RegionMap>().tick(
self.ecs.read_storage::<comp::Pos>(),
self.ecs.read_storage::<comp::Vel>(),
self.ecs.entities(),
);
}
// Apply terrain changes
pub fn apply_terrain_changes(&self) {
let mut terrain = self.ecs.write_resource::<TerrainGrid>();
let mut modified_blocks = std::mem::replace(
&mut self.ecs.write_resource::<BlockChange>().blocks,
Default::default(),
);
// Apply block modifications
// Only include in `TerrainChanges` if successful
modified_blocks.retain(|pos, block| terrain.set(*pos, *block).is_ok());
self.ecs.write_resource::<TerrainChanges>().modified_blocks = modified_blocks;
}
/// Execute a single tick, simulating the game state by the given duration.
pub fn tick(
&mut self,
dt: Duration,
add_foreign_systems: impl Fn(&mut DispatcherBuilder),
update_terrain_and_regions: bool,
) {
// Change the time accordingly.
self.ecs.write_resource::<TimeOfDay>().0 += dt.as_secs_f64() * DAY_CYCLE_FACTOR;
self.ecs.write_resource::<Time>().0 += dt.as_secs_f64();
// Update delta time.
// Beyond a delta time of MAX_DELTA_TIME, start lagging to avoid skipping
// important physics events.
self.ecs.write_resource::<DeltaTime>().0 = dt.as_secs_f32().min(MAX_DELTA_TIME);
if update_terrain_and_regions {
self.update_region_map();
}
// Run systems to update the world.
// Create and run a dispatcher for ecs systems.
let mut dispatch_builder = DispatcherBuilder::new().with_pool(self.thread_pool.clone());
sys::add_local_systems(&mut dispatch_builder);
// TODO: Consider alternative ways to do this
add_foreign_systems(&mut dispatch_builder);
// This dispatches all the systems in parallel.
2019-11-30 06:41:20 +00:00
dispatch_builder.build().dispatch(&self.ecs);
self.ecs.maintain();
if update_terrain_and_regions {
self.apply_terrain_changes();
}
2019-08-25 16:48:12 +00:00
// Process local events
let events = self.ecs.read_resource::<EventBus<LocalEvent>>().recv_all();
for event in events {
let mut velocities = self.ecs.write_storage::<comp::Vel>();
let mut controllers = self.ecs.write_storage::<comp::Controller>();
match event {
LocalEvent::Jump(entity) => {
if let Some(vel) = velocities.get_mut(entity) {
vel.0.z = HUMANOID_JUMP_ACCEL;
}
},
2020-03-21 22:55:20 +00:00
LocalEvent::KnockUp { entity, dir, force } => {
2020-03-15 13:20:42 +00:00
if let Some(vel) = velocities.get_mut(entity) {
2020-03-21 22:55:20 +00:00
vel.0 = dir * force;
vel.0.z = HUMANOID_JUMP_ACCEL;
}
},
LocalEvent::ApplyForce { entity, dir, force } => {
if let Some(vel) = velocities.get_mut(entity) {
vel.0 = dir * force;
2020-03-15 13:20:42 +00:00
}
},
LocalEvent::WallLeap { entity, wall_dir } => {
if let (Some(vel), Some(_controller)) =
(velocities.get_mut(entity), controllers.get_mut(entity))
{
let hspeed = Vec2::<f32>::from(vel.0).magnitude();
if hspeed > 0.001 && hspeed < 0.5 {
vel.0 += vel.0.normalized()
* Vec3::new(1.0, 1.0, 0.0)
* HUMANOID_JUMP_ACCEL
* 1.5
- wall_dir * 0.03;
vel.0.z = HUMANOID_JUMP_ACCEL * 0.5;
}
}
},
LocalEvent::Boost {
entity,
vel: extra_vel,
} => {
if let Some(vel) = velocities.get_mut(entity) {
vel.0 += extra_vel;
}
},
}
}
2019-01-23 20:01:58 +00:00
}
2019-08-25 16:48:12 +00:00
/// Clean up the state after a tick.
2019-01-23 20:01:58 +00:00
pub fn cleanup(&mut self) {
// Clean up data structures from the last tick.
self.ecs.write_resource::<TerrainChanges>().clear();
2019-01-02 19:22:01 +00:00
}
}