2019-08-19 12:39:23 +00:00
#![ deny(unsafe_code) ]
2019-10-03 21:28:17 +00:00
#![ feature(drain_filter) ]
2019-01-02 17:23:31 +00:00
2019-08-08 22:24:14 +00:00
pub mod auth_provider ;
2019-10-20 05:19:50 +00:00
pub mod chunk_generator ;
2019-03-03 22:02:38 +00:00
pub mod client ;
2019-04-23 09:53:45 +00:00
pub mod cmd ;
2019-03-03 22:02:38 +00:00
pub mod error ;
pub mod input ;
2019-09-06 14:21:09 +00:00
pub mod metrics ;
2019-09-07 13:10:57 +00:00
pub mod settings ;
2019-10-15 04:06:14 +00:00
pub mod sys ;
2020-01-22 03:12:17 +00:00
#[ cfg(not(feature = " worldgen " )) ]
mod test_world ;
2019-03-03 22:02:38 +00:00
// Reexports
2019-06-29 16:41:26 +00:00
pub use crate ::{ error ::Error , input ::Input , settings ::ServerSettings } ;
2019-03-03 22:02:38 +00:00
2019-05-09 17:58:16 +00:00
use crate ::{
2019-08-08 22:24:14 +00:00
auth_provider ::AuthProvider ,
2019-10-20 05:19:50 +00:00
chunk_generator ::ChunkGenerator ,
2019-10-15 04:06:14 +00:00
client ::{ Client , RegionSubscription } ,
2019-05-09 17:58:16 +00:00
cmd ::CHAT_COMMANDS ,
2019-12-18 05:22:52 +00:00
sys ::sentinel ::{ DeletedEntities , TrackedComps } ,
2019-05-06 14:26:10 +00:00
} ;
2019-03-03 22:02:38 +00:00
use common ::{
2019-10-22 18:18:40 +00:00
assets , comp ,
2019-09-25 15:52:58 +00:00
effect ::Effect ,
2019-08-25 14:49:54 +00:00
event ::{ EventBus , ServerEvent } ,
2019-12-23 06:02:00 +00:00
msg ::{ ClientMsg , ClientState , PlayerListUpdate , ServerError , ServerInfo , ServerMsg } ,
2019-03-03 22:02:38 +00:00
net ::PostOffice ,
2019-11-24 20:12:03 +00:00
state ::{ BlockChange , State , TimeOfDay } ,
2019-12-31 08:47:00 +00:00
sync ::{ Uid , UidAllocator , WorldSyncExt } ,
2019-10-20 05:19:50 +00:00
terrain ::{ block ::Block , TerrainChunkSize , TerrainGrid } ,
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
vol ::{ ReadVol , RectVolSize , Vox } ,
2019-03-03 22:02:38 +00:00
} ;
2020-01-03 04:23:38 +00:00
use log ::{ debug , error , warn } ;
2019-09-07 13:10:57 +00:00
use metrics ::ServerMetrics ;
2019-07-29 17:23:26 +00:00
use rand ::Rng ;
2019-11-04 00:57:36 +00:00
use specs ::{
2019-12-31 08:47:00 +00:00
join ::Join , saveload ::MarkerAllocator , world ::EntityBuilder as EcsEntityBuilder , Builder ,
Entity as EcsEntity , RunNow , SystemData , WorldExt ,
2019-11-04 00:57:36 +00:00
} ;
2019-09-07 13:10:57 +00:00
use std ::{
i32 ,
2019-10-20 05:19:50 +00:00
sync ::Arc ,
2019-09-07 13:10:57 +00:00
time ::{ Duration , Instant } ,
} ;
2020-01-23 14:10:49 +00:00
#[ cfg(not(feature = " worldgen " )) ]
use test_world ::{ World , WORLD_SIZE } ;
2019-07-12 16:30:15 +00:00
use uvth ::{ ThreadPool , ThreadPoolBuilder } ;
2019-05-09 17:58:16 +00:00
use vek ::* ;
2020-01-22 03:12:17 +00:00
#[ cfg(feature = " worldgen " ) ]
2020-01-11 20:38:30 +00:00
use world ::{
2020-01-20 18:19:06 +00:00
sim ::{ FileOpts , WorldOpts , DEFAULT_WORLD_MAP , WORLD_SIZE } ,
2020-01-11 20:38:30 +00:00
World ,
} ;
2020-01-22 03:12:17 +00:00
2019-04-17 14:46:04 +00:00
const CLIENT_TIMEOUT : f64 = 20.0 ; // Seconds
2019-01-02 17:23:31 +00:00
2019-03-03 22:02:38 +00:00
pub enum Event {
2019-05-27 17:45:43 +00:00
ClientConnected {
entity : EcsEntity ,
} ,
ClientDisconnected {
entity : EcsEntity ,
} ,
Chat {
entity : Option < EcsEntity > ,
msg : String ,
} ,
2019-01-02 17:23:31 +00:00
}
2019-05-21 22:04:39 +00:00
#[ derive(Copy, Clone) ]
struct SpawnPoint ( Vec3 < f32 > ) ;
2019-10-15 04:06:14 +00:00
// Tick count used for throttling network updates
// Note this doesn't account for dt (so update rate changes with tick rate)
#[ derive(Copy, Clone, Default) ]
pub struct Tick ( u64 ) ;
2019-01-02 17:23:31 +00:00
pub struct Server {
2019-01-02 19:22:01 +00:00
state : State ,
2019-05-16 17:40:32 +00:00
world : Arc < World > ,
2020-01-13 07:10:38 +00:00
map : Vec < u32 > ,
2019-01-02 17:23:31 +00:00
2019-03-03 22:02:38 +00:00
postoffice : PostOffice < ServerMsg , ClientMsg > ,
2019-04-10 23:16:29 +00:00
thread_pool : ThreadPool ,
2019-05-08 16:22:52 +00:00
server_info : ServerInfo ,
2019-09-06 14:21:09 +00:00
metrics : ServerMetrics ,
2019-08-08 03:56:02 +00:00
2019-10-15 04:06:14 +00:00
server_settings : ServerSettings ,
2019-01-02 17:23:31 +00:00
}
impl Server {
2019-10-11 12:19:55 +00:00
/// Create a new `Server`
2019-06-29 16:41:26 +00:00
pub fn new ( settings : ServerSettings ) -> Result < Self , Error > {
2019-07-01 20:42:43 +00:00
let mut state = State ::default ( ) ;
2019-11-30 06:41:20 +00:00
state . ecs_mut ( ) . insert ( EventBus ::< ServerEvent > ::default ( ) ) ;
2019-10-15 04:06:14 +00:00
// TODO: anything but this
2019-11-30 06:41:20 +00:00
state . ecs_mut ( ) . insert ( AuthProvider ::new ( ) ) ;
state . ecs_mut ( ) . insert ( Tick ( 0 ) ) ;
state . ecs_mut ( ) . insert ( ChunkGenerator ::new ( ) ) ;
2019-11-04 00:57:36 +00:00
// System timers for performance monitoring
2019-11-30 06:41:20 +00:00
state . ecs_mut ( ) . insert ( sys ::EntitySyncTimer ::default ( ) ) ;
state . ecs_mut ( ) . insert ( sys ::MessageTimer ::default ( ) ) ;
state . ecs_mut ( ) . insert ( sys ::SentinelTimer ::default ( ) ) ;
state . ecs_mut ( ) . insert ( sys ::SubscriptionTimer ::default ( ) ) ;
state . ecs_mut ( ) . insert ( sys ::TerrainSyncTimer ::default ( ) ) ;
state . ecs_mut ( ) . insert ( sys ::TerrainTimer ::default ( ) ) ;
2019-10-20 07:20:21 +00:00
// Server-only components
2019-10-06 17:35:47 +00:00
state . ecs_mut ( ) . register ::< RegionSubscription > ( ) ;
2019-10-15 04:06:14 +00:00
state . ecs_mut ( ) . register ::< Client > ( ) ;
2019-04-16 21:06:33 +00:00
2020-01-22 03:12:17 +00:00
#[ cfg(feature = " worldgen " ) ]
2020-01-11 20:38:30 +00:00
let world = World ::generate (
settings . world_seed ,
WorldOpts {
seed_elements : true ,
2020-01-18 18:41:37 +00:00
world_file : if let Some ( ref opts ) = settings . map_file {
opts . clone ( )
2020-01-11 20:38:30 +00:00
} else {
2020-01-18 18:41:37 +00:00
// Load default map from assets.
2020-01-20 18:19:06 +00:00
FileOpts ::LoadAsset ( DEFAULT_WORLD_MAP . into ( ) )
2020-01-11 20:38:30 +00:00
} ,
.. WorldOpts ::default ( )
2019-12-11 09:14:50 +00:00
} ,
2020-01-11 20:38:30 +00:00
) ;
2020-01-22 03:12:17 +00:00
#[ cfg(feature = " worldgen " ) ]
2020-01-13 07:10:38 +00:00
let map = world . sim ( ) . get_map ( ) ;
2019-11-01 00:24:18 +00:00
2020-01-22 03:12:17 +00:00
#[ cfg(not(feature = " worldgen " )) ]
let world = World ::generate ( settings . world_seed ) ;
2020-01-24 21:24:57 +00:00
#[ cfg(not(feature = " worldgen " )) ]
2020-01-24 10:40:52 +00:00
let map = vec! [ 0 ] ;
2020-01-22 03:12:17 +00:00
#[ cfg(feature = " worldgen " ) ]
2019-11-01 00:24:18 +00:00
let spawn_point = {
// NOTE: all of these `.map(|e| e as [type])` calls should compile into no-ops,
// but are needed to be explicit about casting (and to make the compiler stop complaining)
// spawn in the chunk, that is in the middle of the world
let spawn_chunk : Vec2 < i32 > = WORLD_SIZE . map ( | e | e as i32 ) / 2 ;
// calculate the absolute position of the chunk in the world
// (we could add TerrainChunkSize::RECT_SIZE / 2 here, to spawn in the midde of the chunk)
let spawn_location = spawn_chunk * TerrainChunkSize ::RECT_SIZE . map ( | e | e as i32 ) ;
// get a z cache for the collumn in which we want to spawn
let mut block_sampler = world . sample_blocks ( ) ;
let z_cache = block_sampler
. get_z_cache ( spawn_location )
. expect ( & format! ( " no z_cache found for chunk: {} " , spawn_chunk ) ) ;
// get the minimum and maximum z values at which there could be soild blocks
let ( min_z , _ , max_z ) = z_cache . get_z_limits ( & mut block_sampler ) ;
// round range outwards, so no potential air block is missed
let min_z = min_z . floor ( ) as i32 ;
let max_z = max_z . ceil ( ) as i32 ;
// loop over all blocks from min_z to max_z + 1
// until the first air block is found
// (up to max_z + 1, because max_z could still be a soild block)
// if no air block is found default to max_z + 1
2020-01-12 14:45:20 +00:00
let z = ( min_z .. ( max_z + 1 ) + 1 )
2019-11-01 00:24:18 +00:00
. find ( | z | {
block_sampler
. get_with_z_cache (
Vec3 ::new ( spawn_location . x , spawn_location . y , * z ) ,
Some ( & z_cache ) ,
false ,
)
. map ( | b | b . is_air ( ) )
. unwrap_or ( false )
} )
. unwrap_or ( max_z + 1 ) ;
// build the actual spawn point and
// add 0.5, so that the player spawns in the middle of the block
Vec3 ::new ( spawn_location . x , spawn_location . y , z ) . map ( | e | ( e as f32 ) ) + 0.5
} ;
2020-01-22 03:12:17 +00:00
#[ cfg(not(feature = " worldgen " )) ]
let spawn_point = Vec3 ::new ( 0.0 , 0.0 , 256.0 ) ;
2019-11-01 00:24:18 +00:00
// set the spawn point we calculated above
2019-11-30 06:41:20 +00:00
state . ecs_mut ( ) . insert ( SpawnPoint ( spawn_point ) ) ;
2019-11-01 00:24:18 +00:00
2019-07-12 13:03:35 +00:00
// Set starting time for the server.
state . ecs_mut ( ) . write_resource ::< TimeOfDay > ( ) . 0 = settings . start_time ;
2019-11-04 00:57:36 +00:00
// Register trackers
sys ::sentinel ::register_trackers ( & mut state . ecs_mut ( ) ) ;
2019-11-30 06:41:20 +00:00
state . ecs_mut ( ) . insert ( DeletedEntities ::default ( ) ) ;
2019-11-29 06:04:37 +00:00
2019-06-06 14:48:41 +00:00
let this = Self {
2019-04-16 21:06:33 +00:00
state ,
2019-11-01 00:24:18 +00:00
world : Arc ::new ( world ) ,
2020-01-13 07:10:38 +00:00
map ,
2019-03-03 22:02:38 +00:00
2019-10-11 12:19:55 +00:00
postoffice : PostOffice ::bind ( settings . gameserver_address ) ? ,
2019-04-10 23:16:29 +00:00
2019-07-12 19:29:16 +00:00
thread_pool : ThreadPoolBuilder ::new ( )
. name ( " veloren-worker " . into ( ) )
. build ( ) ,
2019-05-08 16:22:52 +00:00
server_info : ServerInfo {
2019-07-01 09:37:17 +00:00
name : settings . server_name . clone ( ) ,
description : settings . server_description . clone ( ) ,
2019-07-21 17:45:31 +00:00
git_hash : common ::util ::GIT_HASH . to_string ( ) ,
2019-10-18 13:32:26 +00:00
git_date : common ::util ::GIT_DATE . to_string ( ) ,
2019-05-08 16:22:52 +00:00
} ,
2019-10-23 18:23:31 +00:00
metrics : ServerMetrics ::new ( settings . metrics_address )
. expect ( " Failed to initialize server metrics submodule. " ) ,
2019-10-11 12:19:55 +00:00
server_settings : settings . clone ( ) ,
2019-04-16 21:06:33 +00:00
} ;
2019-11-23 14:34:03 +00:00
debug! ( " created veloren server with: {:?} " , & settings ) ;
2019-04-16 21:06:33 +00:00
Ok ( this )
2019-01-02 17:23:31 +00:00
}
2019-06-05 13:13:24 +00:00
pub fn with_thread_pool ( mut self , thread_pool : ThreadPool ) -> Self {
self . thread_pool = thread_pool ;
self
}
2019-01-15 15:13:11 +00:00
/// Get a reference to the server's game state.
2019-04-16 13:06:30 +00:00
pub fn state ( & self ) -> & State {
& self . state
}
2019-01-15 15:13:11 +00:00
/// Get a mutable reference to the server's game state.
2019-04-16 13:06:30 +00:00
pub fn state_mut ( & mut self ) -> & mut State {
& mut self . state
}
2019-01-12 15:57:19 +00:00
2019-01-15 15:13:11 +00:00
/// Get a reference to the server's world.
2019-04-16 13:06:30 +00:00
pub fn world ( & self ) -> & World {
& self . world
}
2019-01-15 15:13:11 +00:00
2019-07-21 12:42:45 +00:00
/// Build a static object entity
pub fn create_object (
& mut self ,
pos : comp ::Pos ,
object : comp ::object ::Body ,
) -> EcsEntityBuilder {
self . state
. ecs_mut ( )
. create_entity_synced ( )
. with ( pos )
. with ( comp ::Vel ( Vec3 ::zero ( ) ) )
. with ( comp ::Ori ( Vec3 ::unit_y ( ) ) )
. with ( comp ::Body ::Object ( object ) )
2019-10-23 19:40:45 +00:00
. with ( comp ::Mass ( 100.0 ) )
. with ( comp ::Gravity ( 1.0 ) )
2019-08-25 09:35:54 +00:00
//.with(comp::LightEmitter::default())
2019-07-21 12:42:45 +00:00
}
2019-08-25 12:27:17 +00:00
/// Build a projectile
pub fn create_projectile (
state : & mut State ,
pos : comp ::Pos ,
vel : comp ::Vel ,
body : comp ::Body ,
2019-09-17 12:43:19 +00:00
projectile : comp ::Projectile ,
2019-08-25 12:27:17 +00:00
) -> EcsEntityBuilder {
2019-10-17 20:59:36 +00:00
state
2019-08-25 12:27:17 +00:00
. ecs_mut ( )
. create_entity_synced ( )
. with ( pos )
. with ( vel )
2019-09-28 18:07:13 +00:00
. with ( comp ::Ori ( vel . 0. normalized ( ) ) )
2019-10-02 19:28:35 +00:00
. with ( comp ::Mass ( 0.0 ) )
2019-08-25 12:27:17 +00:00
. with ( body )
2019-09-17 12:43:19 +00:00
. with ( projectile )
2019-10-17 20:59:36 +00:00
. with ( comp ::Sticky )
2019-08-25 12:27:17 +00:00
}
2019-04-23 09:53:45 +00:00
pub fn create_player_character (
state : & mut State ,
entity : EcsEntity ,
2019-05-12 21:21:18 +00:00
name : String ,
2019-05-15 16:06:58 +00:00
body : comp ::Body ,
2019-10-22 18:18:40 +00:00
main : Option < String > ,
2019-08-12 14:05:58 +00:00
server_settings : & ServerSettings ,
2019-04-23 09:53:45 +00:00
) {
2019-10-22 18:18:40 +00:00
// Give no item when an invalid specifier is given
2019-10-24 08:13:32 +00:00
let main = main . and_then ( | specifier | assets ::load_cloned ( & specifier ) . ok ( ) ) ;
2019-10-22 18:18:40 +00:00
2019-05-21 22:04:39 +00:00
let spawn_point = state . ecs ( ) . read_resource ::< SpawnPoint > ( ) . 0 ;
2019-06-30 11:48:28 +00:00
state . write_component ( entity , body ) ;
2020-01-13 22:49:46 +00:00
state . write_component ( entity , comp ::Stats ::new ( name , body , main ) ) ;
2020-01-17 20:08:27 +00:00
state . write_component ( entity , comp ::Energy ::new ( 1000 ) ) ;
2019-06-09 14:20:20 +00:00
state . write_component ( entity , comp ::Controller ::default ( ) ) ;
2019-06-14 15:27:05 +00:00
state . write_component ( entity , comp ::Pos ( spawn_point ) ) ;
state . write_component ( entity , comp ::Vel ( Vec3 ::zero ( ) ) ) ;
state . write_component ( entity , comp ::Ori ( Vec3 ::unit_y ( ) ) ) ;
2019-10-17 20:59:36 +00:00
state . write_component ( entity , comp ::Gravity ( 1.0 ) ) ;
2019-08-23 10:11:37 +00:00
state . write_component ( entity , comp ::CharacterState ::default ( ) ) ;
2020-01-27 15:51:07 +00:00
state . write_component ( entity , comp ::Alignment ::Owned ( entity ) ) ;
2019-07-25 14:48:27 +00:00
state . write_component ( entity , comp ::Inventory ::default ( ) ) ;
2019-07-25 17:41:06 +00:00
state . write_component ( entity , comp ::InventoryUpdate ) ;
2019-05-22 20:53:24 +00:00
// Make sure physics are accepted.
2019-06-14 15:27:05 +00:00
state . write_component ( entity , comp ::ForceUpdate ) ;
2019-04-19 07:35:23 +00:00
2019-08-14 15:51:59 +00:00
// Give the Admin component to the player if their name exists in admin list
2019-08-12 16:11:06 +00:00
if server_settings . admins . contains (
& state
. ecs ( )
. read_storage ::< comp ::Player > ( )
. get ( entity )
2019-10-23 16:38:09 +00:00
. expect ( " Failed to fetch entity. " )
2019-08-12 16:11:06 +00:00
. alias ,
) {
2019-08-14 15:51:59 +00:00
state . write_component ( entity , comp ::Admin ) ;
2019-08-12 14:05:58 +00:00
}
2019-05-17 09:22:32 +00:00
// Tell the client its request was successful.
2019-10-15 04:06:14 +00:00
if let Some ( client ) = state . ecs ( ) . write_storage ::< Client > ( ) . get_mut ( entity ) {
client . allow_state ( ClientState ::Character ) ;
}
2019-04-19 07:35:23 +00:00
}
2019-08-07 15:39:16 +00:00
/// Handle events coming through via the event bus
2019-10-15 04:06:14 +00:00
fn handle_events ( & mut self ) -> Vec < Event > {
let mut frontend_events = Vec ::new ( ) ;
let mut requested_chunks = Vec ::new ( ) ;
let mut dropped_items = Vec ::new ( ) ;
let mut chat_commands = Vec ::new ( ) ;
2019-08-25 12:27:17 +00:00
let events = self
. state
. ecs ( )
. read_resource ::< EventBus < ServerEvent > > ( )
. recv_all ( ) ;
2019-08-23 20:50:35 +00:00
for event in events {
2019-08-25 12:27:17 +00:00
let state = & mut self . state ;
2019-10-15 04:06:14 +00:00
let server_settings = & self . server_settings ;
2019-08-25 12:27:17 +00:00
match event {
ServerEvent ::Explosion { pos , radius } = > {
const RAYS : usize = 500 ;
for _ in 0 .. RAYS {
let dir = Vec3 ::new (
rand ::random ::< f32 > ( ) - 0.5 ,
rand ::random ::< f32 > ( ) - 0.5 ,
rand ::random ::< f32 > ( ) - 0.5 ,
)
. normalized ( ) ;
2019-10-15 04:06:14 +00:00
let ecs = state . ecs ( ) ;
2019-08-25 12:27:17 +00:00
let mut block_change = ecs . write_resource ::< BlockChange > ( ) ;
let _ = 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
. read_resource ::< TerrainGrid > ( )
2019-08-25 12:27:17 +00:00
. ray ( pos , pos + dir * radius )
. until ( | _ | rand ::random ::< f32 > ( ) < 0.05 )
. for_each ( | pos | block_change . set ( pos , Block ::empty ( ) ) )
. cast ( ) ;
2019-08-07 17:17:04 +00:00
}
2019-08-25 12:27:17 +00:00
}
2019-09-28 19:35:28 +00:00
ServerEvent ::Shoot {
entity ,
dir ,
2019-10-11 04:30:34 +00:00
body ,
light ,
2019-09-28 19:35:28 +00:00
projectile ,
2019-10-17 20:59:36 +00:00
gravity ,
2019-09-28 19:35:28 +00:00
} = > {
2019-10-06 19:21:33 +00:00
let mut pos = state
2019-08-25 12:27:17 +00:00
. ecs ( )
. read_storage ::< comp ::Pos > ( )
. get ( entity )
2019-10-23 16:38:09 +00:00
. expect ( " Failed to fetch entity " )
2019-08-25 12:27:17 +00:00
. 0 ;
2019-10-06 19:21:33 +00:00
// TODO: Player height
pos . z + = 1.2 ;
2019-10-17 20:59:36 +00:00
let mut builder = Self ::create_projectile (
2019-08-25 12:27:17 +00:00
state ,
comp ::Pos ( pos ) ,
2019-09-16 15:58:40 +00:00
comp ::Vel ( dir * 100.0 ) ,
2019-10-11 04:30:34 +00:00
body ,
2019-09-28 19:35:28 +00:00
projectile ,
2019-10-17 20:59:36 +00:00
) ;
if let Some ( light ) = light {
builder = builder . with ( light )
}
if let Some ( gravity ) = gravity {
builder = builder . with ( gravity )
}
builder . build ( ) ;
2019-08-25 12:27:17 +00:00
}
2019-08-23 10:11:37 +00:00
2019-10-17 20:59:36 +00:00
ServerEvent ::Damage { uid , change } = > {
2019-10-15 04:06:14 +00:00
let ecs = state . ecs ( ) ;
2019-09-21 12:43:24 +00:00
if let Some ( entity ) = ecs . entity_from_uid ( uid . into ( ) ) {
if let Some ( stats ) = ecs . write_storage ::< comp ::Stats > ( ) . get_mut ( entity ) {
2019-10-17 20:59:36 +00:00
stats . health . change_by ( change ) ;
2019-09-21 12:43:24 +00:00
}
}
}
2019-09-17 12:43:19 +00:00
ServerEvent ::Destroy { entity , cause } = > {
2019-08-25 12:27:17 +00:00
// Chat message
2019-11-29 06:04:37 +00:00
if let Some ( player ) = state . ecs ( ) . read_storage ::< comp ::Player > ( ) . get ( entity ) {
2019-08-25 12:27:17 +00:00
let msg = if let comp ::HealthSource ::Attack { by } = cause {
2019-11-29 06:04:37 +00:00
state . ecs ( ) . entity_from_uid ( by . into ( ) ) . and_then ( | attacker | {
state
. ecs ( )
. read_storage ::< comp ::Player > ( )
. get ( attacker )
. map ( | attacker_alias | {
2019-08-25 12:27:17 +00:00
format! (
" {} was killed by {} " ,
& player . alias , & attacker_alias . alias
)
2019-11-29 06:04:37 +00:00
} )
2019-08-25 12:27:17 +00:00
} )
} else {
None
2019-08-23 20:50:35 +00:00
}
2019-08-25 12:27:17 +00:00
. unwrap_or ( format! ( " {} died " , & player . alias ) ) ;
2019-10-15 04:06:14 +00:00
state . notify_registered_clients ( ServerMsg ::kill ( msg ) ) ;
2019-08-25 12:27:17 +00:00
}
2019-08-23 10:11:37 +00:00
2019-11-29 06:04:37 +00:00
{
// Give EXP to the killer if entity had stats
let mut stats = state . ecs ( ) . write_storage ::< comp ::Stats > ( ) ;
if let Some ( entity_stats ) = stats . get ( entity ) . cloned ( ) {
if let comp ::HealthSource ::Attack { by } = cause {
state . ecs ( ) . entity_from_uid ( by . into ( ) ) . map ( | attacker | {
if let Some ( attacker_stats ) = stats . get_mut ( attacker ) {
// TODO: Discuss whether we should give EXP by Player Killing or not.
attacker_stats
. exp
. change_by ( ( entity_stats . level . level ( ) * 10 ) as i64 ) ;
}
} ) ;
}
2019-08-23 10:11:37 +00:00
}
}
2019-08-25 12:27:17 +00:00
2019-12-31 08:47:00 +00:00
if state
. ecs ( )
. write_storage ::< Client > ( )
. get_mut ( entity )
. is_some ( )
{
2019-12-20 03:31:41 +00:00
state
2019-11-29 06:04:37 +00:00
. ecs ( )
. write_storage ( )
2019-12-20 03:31:41 +00:00
. insert ( entity , comp ::Vel ( Vec3 ::zero ( ) ) )
. err ( )
. map ( | err | error! ( " Failed to set zero vel on dead client: {:?} " , err ) ) ;
state
2019-11-29 06:04:37 +00:00
. ecs ( )
. write_storage ( )
2019-12-20 03:31:41 +00:00
. insert ( entity , comp ::ForceUpdate )
. err ( )
. map ( | err | {
error! ( " Failed to insert ForceUpdate on dead client: {:?} " , err )
} ) ;
2020-01-18 14:00:59 +00:00
state
. ecs ( )
. write_storage ::< comp ::Energy > ( )
. get_mut ( entity )
. map ( | energy | {
energy . set_to ( energy . maximum ( ) , comp ::EnergySource ::Revive )
} ) ;
let _ = state
. ecs ( )
. write_storage ::< comp ::CharacterState > ( )
. insert ( entity , comp ::CharacterState ::default ( ) ) ;
2019-12-31 08:10:51 +00:00
} else {
// If not a player delete the entity
2019-11-29 06:04:37 +00:00
if let Err ( err ) = state . delete_entity_recorded ( entity ) {
error! ( " Failed to delete destroyed entity: {:?} " , err ) ;
}
2019-08-23 10:11:37 +00:00
}
}
2019-08-23 20:50:35 +00:00
2019-10-15 04:06:14 +00:00
ServerEvent ::InventoryManip ( entity , manip ) = > {
match manip {
comp ::InventoryManip ::Pickup ( uid ) = > {
// TODO: enforce max pickup range
let item_entity = if let ( Some ( ( item , item_entity ) ) , Some ( inv ) ) = (
state
. ecs ( )
. entity_from_uid ( uid . into ( ) )
. and_then ( | item_entity | {
state
. ecs ( )
. write_storage ::< comp ::Item > ( )
. get_mut ( item_entity )
. map ( | item | ( item . clone ( ) , item_entity ) )
} ) ,
state
. ecs ( )
. write_storage ::< comp ::Inventory > ( )
. get_mut ( entity ) ,
) {
if inv . push ( item ) . is_none ( ) {
Some ( item_entity )
} else {
None
}
} else {
None
} ;
if let Some ( item_entity ) = item_entity {
2019-11-29 06:04:37 +00:00
if let Err ( err ) = state . delete_entity_recorded ( item_entity ) {
error! ( " Failed to delete picked up item entity: {:?} " , err ) ;
}
2019-10-15 04:06:14 +00:00
}
state . write_component ( entity , comp ::InventoryUpdate ) ;
}
comp ::InventoryManip ::Collect ( pos ) = > {
let block = state . terrain ( ) . get ( pos ) . ok ( ) . copied ( ) ;
if let Some ( block ) = block {
if block . is_collectible ( )
& & state
. ecs ( )
. read_storage ::< comp ::Inventory > ( )
. get ( entity )
. map ( | inv | ! inv . is_full ( ) )
. unwrap_or ( false )
2020-01-13 22:49:46 +00:00
& & state . try_set_block ( pos , Block ::empty ( ) ) . is_some ( )
2019-10-15 04:06:14 +00:00
{
2020-01-13 22:49:46 +00:00
comp ::Item ::try_reclaim_from_block ( block )
. map ( | item | state . give_item ( entity , item ) ) ;
2019-10-15 04:06:14 +00:00
}
}
}
comp ::InventoryManip ::Use ( slot ) = > {
2019-10-22 18:18:40 +00:00
let item_opt = state
2019-10-15 04:06:14 +00:00
. ecs ( )
. write_storage ::< comp ::Inventory > ( )
. get_mut ( entity )
. and_then ( | inv | inv . remove ( slot ) ) ;
2020-01-13 22:49:46 +00:00
if let Some ( item ) = item_opt {
match item . kind {
2019-10-22 18:18:40 +00:00
comp ::ItemKind ::Tool { .. } = > {
if let Some ( stats ) = state
. ecs ( )
. write_storage ::< comp ::Stats > ( )
. get_mut ( entity )
{
// Insert old item into inventory
if let Some ( old_item ) = stats . equipment . main . take ( ) {
state
. ecs ( )
. write_storage ::< comp ::Inventory > ( )
. get_mut ( entity )
. map ( | inv | inv . insert ( slot , old_item ) ) ;
}
stats . equipment . main = Some ( item ) ;
2019-10-15 04:06:14 +00:00
}
}
2019-10-22 18:18:40 +00:00
comp ::ItemKind ::Consumable { effect , .. } = > {
state . apply_effect ( entity , effect ) ;
}
2020-01-29 12:01:28 +00:00
comp ::ItemKind ::Utility { kind } = > match kind {
comp ::item ::Utility ::Collar = > {
let reinsert = if let Some ( pos ) =
state . read_storage ::< comp ::Pos > ( ) . get ( entity )
{
if (
& state . read_storage ::< comp ::Alignment > ( ) ,
& state . read_storage ::< comp ::Agent > ( ) ,
)
. join ( )
. filter ( | ( alignment , _ ) | {
alignment
= = & & comp ::Alignment ::Owned ( entity )
} )
. count ( )
> = 3
{
true
} else if let Some ( tameable_entity ) = {
let nearest_tameable = (
& state . ecs ( ) . entities ( ) ,
& state . ecs ( ) . read_storage ::< comp ::Pos > ( ) ,
& state
. ecs ( )
. read_storage ::< comp ::Alignment > ( ) ,
)
. join ( )
. filter ( | ( _ , wild_pos , _ ) | {
wild_pos . 0. distance_squared ( pos . 0 )
< 5.0 f32 . powf ( 2.0 )
} )
. filter ( | ( _ , _ , alignment ) | {
alignment = = & & comp ::Alignment ::Wild
} )
. min_by_key ( | ( _ , wild_pos , _ ) | {
( wild_pos . 0. distance_squared ( pos . 0 )
* 100.0 )
as i32
} )
. map ( | ( entity , _ , _ ) | entity ) ;
nearest_tameable
} {
2020-01-29 16:37:46 +00:00
let _ = state . ecs ( ) . write_storage ( ) . insert (
tameable_entity ,
comp ::Alignment ::Owned ( entity ) ,
) ;
let _ = state . ecs ( ) . write_storage ( ) . insert (
tameable_entity ,
comp ::Agent ::default ( ) ,
) ;
2020-01-29 12:01:28 +00:00
false
} else {
true
}
} else {
true
} ;
if reinsert {
let _ = state
. ecs ( )
. write_storage ::< comp ::Inventory > ( )
. get_mut ( entity )
. map ( | inv | inv . insert ( slot , item ) ) ;
}
}
} ,
2019-10-22 18:18:40 +00:00
_ = > {
let _ = state
. ecs ( )
. write_storage ::< comp ::Inventory > ( )
. get_mut ( entity )
. map ( | inv | inv . insert ( slot , item ) ) ;
}
2020-01-13 22:49:46 +00:00
}
2019-10-15 04:06:14 +00:00
}
state . write_component ( entity , comp ::InventoryUpdate ) ;
}
comp ::InventoryManip ::Swap ( a , b ) = > {
state
. ecs ( )
. write_storage ::< comp ::Inventory > ( )
. get_mut ( entity )
. map ( | inv | inv . swap_slots ( a , b ) ) ;
state . write_component ( entity , comp ::InventoryUpdate ) ;
}
comp ::InventoryManip ::Drop ( slot ) = > {
let item = state
. ecs ( )
. write_storage ::< comp ::Inventory > ( )
. get_mut ( entity )
. and_then ( | inv | inv . remove ( slot ) ) ;
if let ( Some ( item ) , Some ( pos ) ) =
( item , state . ecs ( ) . read_storage ::< comp ::Pos > ( ) . get ( entity ) )
{
dropped_items . push ( (
* pos ,
state
. ecs ( )
. read_storage ::< comp ::Ori > ( )
. get ( entity )
. copied ( )
. unwrap_or ( comp ::Ori ( Vec3 ::unit_y ( ) ) ) ,
item ,
) ) ;
}
state . write_component ( entity , comp ::InventoryUpdate ) ;
}
}
}
2019-08-25 12:27:17 +00:00
ServerEvent ::Respawn ( entity ) = > {
// Only clients can respawn
2019-12-31 08:47:00 +00:00
if state
. ecs ( )
. write_storage ::< Client > ( )
. get_mut ( entity )
. is_some ( )
{
2019-09-25 20:22:39 +00:00
let respawn_point = state
. read_component_cloned ::< comp ::Waypoint > ( entity )
. map ( | wp | wp . get_pos ( ) )
. unwrap_or ( state . ecs ( ) . read_resource ::< SpawnPoint > ( ) . 0 ) ;
2019-08-25 12:27:17 +00:00
state
2019-10-15 04:06:14 +00:00
. ecs ( )
2019-08-25 12:27:17 +00:00
. write_storage ::< comp ::Stats > ( )
. get_mut ( entity )
. map ( | stats | stats . revive ( ) ) ;
state
2019-10-15 04:06:14 +00:00
. ecs ( )
2019-08-25 12:27:17 +00:00
. write_storage ::< comp ::Pos > ( )
. get_mut ( entity )
2019-09-25 20:22:39 +00:00
. map ( | pos | pos . 0 = respawn_point ) ;
2019-12-31 08:10:51 +00:00
state
2019-10-15 04:06:14 +00:00
. ecs ( )
2019-08-25 12:27:17 +00:00
. write_storage ( )
2019-12-31 08:10:51 +00:00
. insert ( entity , comp ::ForceUpdate )
2019-12-31 08:47:00 +00:00
. err ( ) . map ( | err |
2019-12-31 08:10:51 +00:00
error! ( " Error inserting ForceUpdate component when respawning client: {:?} " , err )
) ;
2019-08-25 12:27:17 +00:00
}
}
2019-10-08 16:50:26 +00:00
ServerEvent ::LandOnGround { entity , vel } = > {
2020-01-19 19:55:07 +00:00
if vel . z < = - 37.0 {
2019-10-15 04:06:14 +00:00
if let Some ( stats ) =
state . ecs ( ) . write_storage ::< comp ::Stats > ( ) . get_mut ( entity )
2019-10-08 16:50:26 +00:00
{
2020-01-19 19:55:07 +00:00
let falldmg = ( vel . z / 2.5 ) as i32 ;
2019-10-08 16:50:26 +00:00
if falldmg < 0 {
2019-10-17 20:59:36 +00:00
stats . health . change_by ( comp ::HealthChange {
amount : falldmg ,
cause : comp ::HealthSource ::World ,
} ) ;
2019-10-08 16:50:26 +00:00
}
}
}
}
2019-09-09 19:11:40 +00:00
ServerEvent ::Mount ( mounter , mountee ) = > {
if state
. ecs ( )
. read_storage ::< comp ::Mounting > ( )
. get ( mounter )
. is_none ( )
{
let not_mounting_yet = if let Some ( comp ::MountState ::Unmounted ) = state
. ecs ( )
2019-11-04 00:57:36 +00:00
. read_storage ::< comp ::MountState > ( )
. get ( mountee )
2019-09-09 19:11:40 +00:00
. cloned ( )
{
true
} else {
false
} ;
if not_mounting_yet {
if let ( Some ( mounter_uid ) , Some ( mountee_uid ) ) = (
state . ecs ( ) . uid_from_entity ( mounter ) ,
state . ecs ( ) . uid_from_entity ( mountee ) ,
) {
state . write_component (
mountee ,
comp ::MountState ::MountedBy ( mounter_uid . into ( ) ) ,
) ;
state . write_component ( mounter , comp ::Mounting ( mountee_uid . into ( ) ) ) ;
}
}
}
}
2019-10-15 04:06:14 +00:00
2019-09-09 19:11:40 +00:00
ServerEvent ::Unmount ( mounter ) = > {
let mountee_entity = state
. ecs ( )
. write_storage ::< comp ::Mounting > ( )
. get ( mounter )
. and_then ( | mountee | state . ecs ( ) . entity_from_uid ( mountee . 0. into ( ) ) ) ;
if let Some ( mountee_entity ) = mountee_entity {
state
2019-10-15 04:06:14 +00:00
. ecs ( )
2019-09-09 19:11:40 +00:00
. write_storage ::< comp ::MountState > ( )
. get_mut ( mountee_entity )
. map ( | ms | * ms = comp ::MountState ::Unmounted ) ;
}
state . delete_component ::< comp ::Mounting > ( mounter ) ;
}
2019-10-15 04:06:14 +00:00
2019-10-11 04:30:34 +00:00
ServerEvent ::Possess ( possessor_uid , possesse_uid ) = > {
2019-10-15 04:06:14 +00:00
let ecs = state . ecs ( ) ;
2019-10-11 04:30:34 +00:00
if let ( Some ( possessor ) , Some ( possesse ) ) = (
2019-10-15 04:06:14 +00:00
ecs . entity_from_uid ( possessor_uid . into ( ) ) ,
ecs . entity_from_uid ( possesse_uid . into ( ) ) ,
2019-10-11 04:30:34 +00:00
) {
// You can't possess other players
2019-10-15 04:06:14 +00:00
let mut clients = ecs . write_storage ::< Client > ( ) ;
if clients . get_mut ( possesse ) . is_none ( ) {
if let Some ( mut client ) = clients . remove ( possessor ) {
2019-10-11 04:30:34 +00:00
client . notify ( ServerMsg ::SetPlayerEntity ( possesse_uid . into ( ) ) ) ;
2019-10-15 04:06:14 +00:00
let _ = clients . insert ( possesse , client ) ;
2019-10-11 04:30:34 +00:00
// Create inventory if it doesn't exist
{
2019-10-15 04:06:14 +00:00
let mut inventories = ecs . write_storage ::< comp ::Inventory > ( ) ;
2019-10-11 23:30:05 +00:00
if let Some ( inventory ) = inventories . get_mut ( possesse ) {
2019-10-22 18:18:40 +00:00
inventory . push ( assets ::load_expect_cloned (
" common.items.debug.possess " ,
) ) ;
2019-10-11 04:30:34 +00:00
} else {
let _ = inventories . insert (
possesse ,
comp ::Inventory {
2019-10-22 18:18:40 +00:00
slots : vec ! [ Some ( assets ::load_expect_cloned (
" common.items.debug.possess " ,
2019-10-11 04:30:34 +00:00
) ) ] ,
} ,
) ;
}
}
2019-10-15 04:06:14 +00:00
let _ = ecs
2019-10-11 04:30:34 +00:00
. write_storage ::< comp ::InventoryUpdate > ( )
. insert ( possesse , comp ::InventoryUpdate ) ;
// Move player component
{
2019-10-15 04:06:14 +00:00
let mut players = ecs . write_storage ::< comp ::Player > ( ) ;
2019-10-21 00:59:53 +00:00
if let Some ( player ) = players . remove ( possessor ) {
2019-10-11 04:30:34 +00:00
let _ = players . insert ( possesse , player ) ;
}
}
// Remove will of the entity
2019-10-15 04:06:14 +00:00
let _ = ecs . write_storage ::< comp ::Agent > ( ) . remove ( possesse ) ;
2019-10-11 04:30:34 +00:00
// Transfer admin powers
{
2019-10-15 04:06:14 +00:00
let mut admins = ecs . write_storage ::< comp ::Admin > ( ) ;
2019-10-11 04:30:34 +00:00
if let Some ( admin ) = admins . remove ( possessor ) {
let _ = admins . insert ( possesse , admin ) ;
}
}
// Transfer waypoint
{
2019-10-15 04:06:14 +00:00
let mut waypoints = ecs . write_storage ::< comp ::Waypoint > ( ) ;
2019-10-11 04:30:34 +00:00
if let Some ( waypoint ) = waypoints . remove ( possessor ) {
let _ = waypoints . insert ( possesse , waypoint ) ;
}
}
}
}
}
}
2019-10-15 04:06:14 +00:00
2019-12-31 08:10:51 +00:00
ServerEvent ::CreateCharacter {
2019-10-15 04:06:14 +00:00
entity ,
name ,
body ,
main ,
} = > {
Self ::create_player_character (
state ,
entity ,
name ,
body ,
main ,
& server_settings ,
) ;
2019-11-29 06:04:37 +00:00
sys ::subscription ::initialize_region_subscription ( state . ecs ( ) , entity ) ;
2019-10-15 04:06:14 +00:00
}
2019-12-31 08:10:51 +00:00
ServerEvent ::ExitIngame { entity } = > {
2020-01-02 09:46:15 +00:00
// Create new entity with just `Client`, `Uid`, and `Player` components
2019-12-31 08:10:51 +00:00
// Easier than checking and removing all other known components
// Note: If other `ServerEvent`s are referring to this entity they will be
// disrupted
2019-12-31 08:47:00 +00:00
let maybe_client = state . ecs ( ) . write_storage ::< Client > ( ) . remove ( entity ) ;
let maybe_uid = state . read_component_cloned ::< Uid > ( entity ) ;
let maybe_player = state . ecs ( ) . write_storage ::< comp ::Player > ( ) . remove ( entity ) ;
if let ( Some ( mut client ) , Some ( uid ) , Some ( player ) ) =
( maybe_client , maybe_uid , maybe_player )
{
// Tell client its request was successful
client . allow_state ( ClientState ::Registered ) ;
2019-12-31 08:10:51 +00:00
// Tell client to clear out other entities and its own components
2019-12-31 08:47:00 +00:00
client . notify ( ServerMsg ::ExitIngameCleanup ) ;
2019-12-31 08:10:51 +00:00
let entity_builder =
state . ecs_mut ( ) . create_entity ( ) . with ( client ) . with ( player ) ;
// Ensure UidAllocator maps this uid to the new entity
let uid = entity_builder
. world
. write_resource ::< UidAllocator > ( )
. allocate ( entity_builder . entity , Some ( uid . into ( ) ) ) ;
entity_builder . with ( uid ) . build ( ) ;
}
// Delete old entity
if let Err ( err ) = state . delete_entity_recorded ( entity ) {
error! ( " Failed to delete entity when removing character: {:?} " , err ) ;
}
}
2019-10-20 05:19:50 +00:00
ServerEvent ::CreateNpc {
pos ,
stats ,
body ,
agent ,
2020-01-24 21:24:57 +00:00
alignment ,
2019-10-20 05:19:50 +00:00
scale ,
} = > {
state
. create_npc ( pos , stats , body )
. with ( agent )
. with ( scale )
2020-01-24 21:24:57 +00:00
. with ( alignment )
2019-10-20 05:19:50 +00:00
. build ( ) ;
}
2020-01-25 02:15:15 +00:00
ServerEvent ::CreateWaypoint ( pos ) = > {
self . create_object ( comp ::Pos ( pos ) , comp ::object ::Body ::CampfireLit )
. with ( comp ::LightEmitter {
offset : Vec3 ::unit_z ( ) * 0.5 ,
col : Rgb ::new ( 1.0 , 0.65 , 0.2 ) ,
strength : 2.0 ,
} )
. with ( comp ::WaypointArea ::default ( ) )
. build ( ) ;
}
2019-10-15 04:06:14 +00:00
ServerEvent ::ClientDisconnect ( entity ) = > {
2019-12-23 06:02:00 +00:00
// Tell other clients to remove from player list
if let ( Some ( uid ) , Some ( _ ) ) = (
state . read_storage ::< Uid > ( ) . get ( entity ) ,
state . read_storage ::< comp ::Player > ( ) . get ( entity ) ,
) {
state . notify_registered_clients ( ServerMsg ::PlayerListUpdate (
PlayerListUpdate ::Remove ( ( * uid ) . into ( ) ) ,
) )
}
// Delete client entity
2019-11-29 06:04:37 +00:00
if let Err ( err ) = state . delete_entity_recorded ( entity ) {
error! ( " Failed to delete disconnected client: {:?} " , err ) ;
2019-10-15 04:06:14 +00:00
}
frontend_events . push ( Event ::ClientDisconnected { entity } ) ;
}
ServerEvent ::ChunkRequest ( entity , key ) = > {
requested_chunks . push ( ( entity , key ) ) ;
}
ServerEvent ::ChatCmd ( entity , cmd ) = > {
chat_commands . push ( ( entity , cmd ) ) ;
}
2019-08-23 20:50:35 +00:00
}
2019-08-07 15:39:16 +00:00
}
2019-10-15 04:06:14 +00:00
// Generate requested chunks.
for ( entity , key ) in requested_chunks {
self . generate_chunk ( entity , key ) ;
}
// Drop items
for ( pos , ori , item ) in dropped_items {
let vel = ori . 0. normalized ( ) * 5.0
+ Vec3 ::unit_z ( ) * 10.0
+ Vec3 ::< f32 > ::zero ( ) . map ( | _ | rand ::thread_rng ( ) . gen ::< f32 > ( ) - 0.5 ) * 4.0 ;
self . create_object ( Default ::default ( ) , comp ::object ::Body ::Pouch )
. with ( comp ::Pos ( pos . 0 + Vec3 ::unit_z ( ) * 0.25 ) )
. with ( item )
. with ( comp ::Vel ( vel ) )
. build ( ) ;
}
for ( entity , cmd ) in chat_commands {
self . process_chat_cmd ( entity , cmd ) ;
}
frontend_events
2019-08-07 15:39:16 +00:00
}
2019-05-17 09:22:32 +00:00
/// Execute a single server tick, handle input and update the game state by the given duration.
2019-07-01 16:38:19 +00:00
pub fn tick ( & mut self , _input : Input , dt : Duration ) -> Result < Vec < Event > , Error > {
2019-10-15 04:06:14 +00:00
self . state . ecs ( ) . write_resource ::< Tick > ( ) . 0 + = 1 ;
2019-01-02 17:23:31 +00:00
// 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 significant changes to this code. Here is the
// approximate order of things. Please update it as this code changes.
//
2020-01-27 16:48:42 +00:00
// 1) Collect input from the frontend, apply input effects to the
// state of the game
// 2) Go through any events (timer-driven or otherwise) that need handling
// and apply them to the state of the game
// 3) Go through all incoming client network communications, apply them to
// the game state
// 4) Perform a single LocalState tick (i.e: update the world and entities
// in the world)
// 5) Go through the terrain update queue and apply all changes to
// the terrain
2019-01-02 17:23:31 +00:00
// 6) Send relevant state updates to all clients
2019-09-06 14:21:09 +00:00
// 7) Update Metrics with current data
2020-01-27 16:48:42 +00:00
// 8) Finish the tick, passing control of the main thread back
// to the frontend
2019-01-02 17:23:31 +00:00
2019-09-07 13:10:57 +00:00
let before_tick_1 = Instant ::now ( ) ;
2019-05-17 20:47:58 +00:00
// 1) Build up a list of events for this frame, to be passed to the frontend.
2019-03-03 22:02:38 +00:00
let mut frontend_events = Vec ::new ( ) ;
2019-05-17 09:22:32 +00:00
// If networking has problems, handle them.
2019-03-05 18:39:18 +00:00
if let Some ( err ) = self . postoffice . error ( ) {
2019-03-03 22:02:38 +00:00
return Err ( err . into ( ) ) ;
}
2019-05-17 20:47:58 +00:00
// 2)
2019-03-03 22:02:38 +00:00
2019-05-17 20:47:58 +00:00
// 3) Handle inputs from clients
frontend_events . append ( & mut self . handle_new_connections ( ) ? ) ;
2019-03-03 22:02:38 +00:00
2019-12-01 21:54:21 +00:00
// Run message recieving sys before the systems in common for decreased latency (e.g. run before controller system)
sys ::message ::Sys . run_now ( & self . state . ecs ( ) ) ;
2019-09-07 13:10:57 +00:00
let before_tick_4 = Instant ::now ( ) ;
2019-12-01 21:54:21 +00:00
2019-11-29 06:04:37 +00:00
// 4) Tick the server's LocalState.
2019-10-15 04:06:14 +00:00
self . state . tick ( dt , sys ::add_server_systems ) ;
2019-01-02 17:23:31 +00:00
2019-10-25 05:35:15 +00:00
let before_handle_events = Instant ::now ( ) ;
// Handle game events
frontend_events . append ( & mut self . handle_events ( ) ) ;
2019-05-18 08:59:58 +00:00
// Tick the world
self . world . tick ( dt ) ;
2019-05-17 20:47:58 +00:00
// 5) Fetch any generated `TerrainChunk`s and insert them into the terrain.
2019-10-20 07:20:21 +00:00
// in sys/terrain.rs
2019-04-10 23:16:29 +00:00
2019-09-07 13:10:57 +00:00
let before_tick_6 = Instant ::now ( ) ;
2019-05-17 20:47:58 +00:00
// 6) Synchronise clients with the new state of the world.
2019-07-01 13:36:45 +00:00
2019-08-02 17:48:14 +00:00
// Remove NPCs that are outside the view distances of all players
2019-10-06 17:35:47 +00:00
// This is done by removing NPCs in unloaded chunks
2019-08-02 17:48:14 +00:00
let to_delete = {
let terrain = self . state . terrain ( ) ;
(
& self . state . ecs ( ) . entities ( ) ,
& self . state . ecs ( ) . read_storage ::< comp ::Pos > ( ) ,
2020-01-25 20:43:34 +00:00
! & self . state . ecs ( ) . read_storage ::< comp ::Player > ( ) ,
2019-08-02 17:48:14 +00:00
)
. join ( )
. filter ( | ( _ , pos , _ ) | terrain . get ( pos . 0. map ( | e | e . floor ( ) as i32 ) ) . is_err ( ) )
. map ( | ( entity , _ , _ ) | entity )
. collect ::< Vec < _ > > ( )
} ;
for entity in to_delete {
2019-11-29 06:04:37 +00:00
if let Err ( err ) = self . state . delete_entity_recorded ( entity ) {
error! ( " Failed to delete agent outside the terrain: {:?} " , err ) ;
}
2019-08-02 17:48:14 +00:00
}
2019-09-07 13:10:57 +00:00
let before_tick_7 = Instant ::now ( ) ;
2019-09-06 14:21:09 +00:00
// 7) Update Metrics
2019-10-20 07:20:21 +00:00
let entity_sync_nanos = self
. state
. ecs ( )
. read_resource ::< sys ::EntitySyncTimer > ( )
. nanos as i64 ;
let message_nanos = self . state . ecs ( ) . read_resource ::< sys ::MessageTimer > ( ) . nanos as i64 ;
2019-11-04 00:57:36 +00:00
let sentinel_nanos = self . state . ecs ( ) . read_resource ::< sys ::SentinelTimer > ( ) . nanos as i64 ;
2019-10-20 07:20:21 +00:00
let subscription_nanos = self
. state
. ecs ( )
. read_resource ::< sys ::SubscriptionTimer > ( )
. nanos as i64 ;
let terrain_sync_nanos = self
. state
. ecs ( )
. read_resource ::< sys ::TerrainSyncTimer > ( )
. nanos as i64 ;
let terrain_nanos = self . state . ecs ( ) . read_resource ::< sys ::TerrainTimer > ( ) . nanos as i64 ;
let total_sys_nanos = entity_sync_nanos
+ message_nanos
2019-11-04 00:57:36 +00:00
+ sentinel_nanos
2019-10-20 07:20:21 +00:00
+ subscription_nanos
+ terrain_sync_nanos
+ terrain_nanos ;
2019-09-07 13:10:57 +00:00
self . metrics
. tick_time
. with_label_values ( & [ " input " ] )
2019-12-01 21:54:21 +00:00
. set ( ( before_tick_4 - before_tick_1 ) . as_nanos ( ) as i64 - message_nanos ) ;
2019-09-07 13:10:57 +00:00
self . metrics
. tick_time
2019-10-20 07:20:21 +00:00
. with_label_values ( & [ " state tick " ] )
2019-12-01 21:54:21 +00:00
. set (
( before_handle_events - before_tick_4 ) . as_nanos ( ) as i64
- ( total_sys_nanos - message_nanos ) ,
) ;
2019-10-25 05:35:15 +00:00
self . metrics
. tick_time
. with_label_values ( & [ " handle server events " ] )
. set ( ( before_tick_6 - before_handle_events ) . as_nanos ( ) as i64 ) ;
2019-09-07 13:10:57 +00:00
self . metrics
. tick_time
2019-11-24 20:12:03 +00:00
. with_label_values ( & [ " entity deletion " ] )
2019-10-20 07:20:21 +00:00
. set ( ( before_tick_7 - before_tick_6 ) . as_nanos ( ) as i64 ) ;
2019-09-07 13:10:57 +00:00
self . metrics
. tick_time
2019-10-20 07:20:21 +00:00
. with_label_values ( & [ " entity sync " ] )
. set ( entity_sync_nanos ) ;
self . metrics
. tick_time
. with_label_values ( & [ " message " ] )
. set ( message_nanos ) ;
self . metrics
. tick_time
. with_label_values ( & [ " subscription " ] )
. set ( subscription_nanos ) ;
self . metrics
. tick_time
. with_label_values ( & [ " terrain sync " ] )
. set ( terrain_sync_nanos ) ;
self . metrics
. tick_time
. with_label_values ( & [ " terrain " ] )
. set ( terrain_nanos ) ;
2019-10-15 04:06:14 +00:00
self . metrics
. player_online
. set ( self . state . ecs ( ) . read_storage ::< Client > ( ) . join ( ) . count ( ) as i64 ) ;
2019-09-10 13:22:34 +00:00
self . metrics
. time_of_day
. set ( self . state . ecs ( ) . read_resource ::< TimeOfDay > ( ) . 0 ) ;
if self . metrics . is_100th_tick ( ) {
let mut chonk_cnt = 0 ;
let chunk_cnt = self . state . terrain ( ) . iter ( ) . fold ( 0 , | a , ( _ , c ) | {
chonk_cnt + = 1 ;
a + c . sub_chunks_len ( )
} ) ;
self . metrics . chonks_count . set ( chonk_cnt as i64 ) ;
self . metrics . chunks_count . set ( chunk_cnt as i64 ) ;
}
2019-09-07 13:10:57 +00:00
//self.metrics.entity_count.set(self.state.);
self . metrics
. tick_time
. with_label_values ( & [ " metrics " ] )
. set ( before_tick_7 . elapsed ( ) . as_nanos ( ) as i64 ) ;
2019-09-06 14:21:09 +00:00
// 8) Finish the tick, pass control back to the frontend.
2019-05-25 21:13:38 +00:00
2019-03-03 22:02:38 +00:00
Ok ( frontend_events )
2019-01-02 17:23:31 +00:00
}
2019-01-30 12:11:34 +00:00
2019-05-17 09:22:32 +00:00
/// Clean up the server after a tick.
2019-01-30 12:11:34 +00:00
pub fn cleanup ( & mut self ) {
// Cleanup the local state
self . state . cleanup ( ) ;
}
2019-03-03 22:02:38 +00:00
2019-05-17 09:22:32 +00:00
/// Handle new client connections.
2019-03-03 22:02:38 +00:00
fn handle_new_connections ( & mut self ) -> Result < Vec < Event > , Error > {
let mut frontend_events = Vec ::new ( ) ;
2019-06-06 14:48:41 +00:00
for postbox in self . postoffice . new_postboxes ( ) {
2019-04-21 18:12:29 +00:00
let mut client = Client {
client_state : ClientState ::Connected ,
postbox ,
last_ping : self . state . get_time ( ) ,
2019-12-31 08:10:51 +00:00
login_msg_sent : false ,
2019-04-21 18:12:29 +00:00
} ;
2019-10-15 04:06:14 +00:00
if self . server_settings . max_players
< = self . state . ecs ( ) . read_storage ::< Client > ( ) . join ( ) . count ( )
{
// Note: in this case the client is dropped
2019-07-04 16:14:45 +00:00
client . notify ( ServerMsg ::Error ( ServerError ::TooManyPlayers ) ) ;
2019-07-01 11:19:26 +00:00
} else {
2019-10-15 04:06:14 +00:00
let entity = self
. state
. ecs_mut ( )
. create_entity_synced ( )
. with ( client )
. build ( ) ;
2019-12-18 05:22:52 +00:00
// Send client all the tracked components currently attached to its entity as well
// as synced resources (currently only `TimeOfDay`)
2019-11-23 14:34:03 +00:00
log ::debug! ( " Starting initial sync with client. " ) ;
2019-10-15 04:06:14 +00:00
self . state
. ecs ( )
. write_storage ::< Client > ( )
. get_mut ( entity )
. unwrap ( )
. notify ( ServerMsg ::InitialSync {
2019-12-18 05:22:52 +00:00
// Send client their entity
entity_package : TrackedComps ::fetch ( & self . state . ecs ( ) )
. create_entity_package ( entity ) ,
2019-10-15 04:06:14 +00:00
server_info : self . server_info . clone ( ) ,
2019-12-18 05:22:52 +00:00
time_of_day : * self . state . ecs ( ) . read_resource ( ) ,
2020-01-13 07:10:38 +00:00
world_map : ( WORLD_SIZE . map ( | e | e as u32 ) , self . map . clone ( ) ) ,
2019-10-15 04:06:14 +00:00
} ) ;
2019-11-23 14:34:03 +00:00
log ::debug! ( " Done initial sync with client. " ) ;
2019-07-01 11:19:26 +00:00
frontend_events . push ( Event ::ClientConnected { entity } ) ;
}
2019-07-26 21:01:41 +00:00
}
2019-03-03 22:02:38 +00:00
Ok ( frontend_events )
}
2019-03-04 19:50:26 +00:00
2019-10-15 04:06:14 +00:00
pub fn notify_client ( & self , entity : EcsEntity , msg : ServerMsg ) {
if let Some ( client ) = self . state . ecs ( ) . write_storage ::< Client > ( ) . get_mut ( entity ) {
client . notify ( msg )
2019-04-17 17:32:29 +00:00
}
2019-03-04 19:50:26 +00:00
}
2019-04-10 23:41:37 +00:00
2019-09-16 01:38:53 +00:00
pub fn generate_chunk ( & mut self , entity : EcsEntity , key : Vec2 < i32 > ) {
2019-10-20 05:19:50 +00:00
self . state
. ecs ( )
. write_resource ::< ChunkGenerator > ( )
. generate_chunk ( entity , key , & mut self . thread_pool , self . world . clone ( ) ) ;
2019-04-16 13:06:30 +00:00
}
2019-04-16 15:38:01 +00:00
fn process_chat_cmd ( & mut self , entity : EcsEntity , cmd : String ) {
2019-05-17 09:22:32 +00:00
// Separate string into keyword and arguments.
2019-04-16 13:06:30 +00:00
let sep = cmd . find ( ' ' ) ;
let ( kwd , args ) = match sep {
Some ( i ) = > ( cmd [ .. i ] . to_string ( ) , cmd [ ( i + 1 ) .. ] . to_string ( ) ) ,
None = > ( cmd , " " . to_string ( ) ) ,
} ;
2019-04-16 15:38:01 +00:00
2019-05-17 09:22:32 +00:00
// Find the command object and run its handler.
2019-04-16 13:06:30 +00:00
let action_opt = CHAT_COMMANDS . iter ( ) . find ( | x | x . keyword = = kwd ) ;
match action_opt {
2019-04-16 15:38:01 +00:00
Some ( action ) = > action . execute ( self , entity , args ) ,
2019-05-17 09:22:32 +00:00
// Unknown command
2019-04-16 13:06:30 +00:00
None = > {
2019-10-15 04:06:14 +00:00
if let Some ( client ) = self . state . ecs ( ) . write_storage ::< Client > ( ) . get_mut ( entity ) {
client . notify ( ServerMsg ::private ( format! (
2019-08-18 18:07:21 +00:00
" Unknown command '/{}'. \n Type '/help' for available commands " ,
2019-04-16 13:06:30 +00:00
kwd
2019-10-15 04:06:14 +00:00
) ) ) ;
}
2019-04-16 13:06:30 +00:00
}
2019-04-10 23:41:37 +00:00
}
}
2019-08-14 15:51:59 +00:00
fn entity_is_admin ( & self , entity : EcsEntity ) -> bool {
self . state
. read_storage ::< comp ::Admin > ( )
. get ( entity )
. is_some ( )
}
2019-01-02 17:23:31 +00:00
}
2019-03-05 18:39:18 +00:00
impl Drop for Server {
fn drop ( & mut self ) {
2019-10-15 04:06:14 +00:00
self . state . notify_registered_clients ( ServerMsg ::Shutdown ) ;
2019-03-05 18:39:18 +00:00
}
}
2019-09-25 14:56:57 +00:00
trait StateExt {
2019-09-25 22:53:43 +00:00
fn give_item ( & mut self , entity : EcsEntity , item : comp ::Item ) -> bool ;
2019-09-25 15:52:58 +00:00
fn apply_effect ( & mut self , entity : EcsEntity , effect : Effect ) ;
2019-10-15 04:06:14 +00:00
fn notify_registered_clients ( & self , msg : ServerMsg ) ;
2019-10-20 05:19:50 +00:00
fn create_npc (
& mut self ,
pos : comp ::Pos ,
stats : comp ::Stats ,
body : comp ::Body ,
) -> EcsEntityBuilder ;
2019-11-29 06:04:37 +00:00
fn delete_entity_recorded (
& mut self ,
entity : EcsEntity ,
) -> Result < ( ) , specs ::error ::WrongGeneration > ;
2019-09-25 14:56:57 +00:00
}
impl StateExt for State {
2019-09-25 22:53:43 +00:00
fn give_item ( & mut self , entity : EcsEntity , item : comp ::Item ) -> bool {
let success = self
. ecs ( )
2019-09-25 14:56:57 +00:00
. write_storage ::< comp ::Inventory > ( )
. get_mut ( entity )
2019-09-25 22:53:43 +00:00
. map ( | inv | inv . push ( item ) . is_none ( ) )
. unwrap_or ( false ) ;
if success {
self . write_component ( entity , comp ::InventoryUpdate ) ;
}
success
2019-09-25 14:56:57 +00:00
}
2019-09-25 15:52:58 +00:00
fn apply_effect ( & mut self , entity : EcsEntity , effect : Effect ) {
match effect {
2019-10-17 20:59:36 +00:00
Effect ::Health ( change ) = > {
2019-10-15 04:06:14 +00:00
self . ecs ( )
2019-09-25 15:52:58 +00:00
. write_storage ::< comp ::Stats > ( )
. get_mut ( entity )
2019-10-17 20:59:36 +00:00
. map ( | stats | stats . health . change_by ( change ) ) ;
2019-09-25 15:52:58 +00:00
}
Effect ::Xp ( xp ) = > {
2019-10-15 04:06:14 +00:00
self . ecs ( )
2019-09-25 15:52:58 +00:00
. write_storage ::< comp ::Stats > ( )
. get_mut ( entity )
. map ( | stats | stats . exp . change_by ( xp ) ) ;
}
}
}
2019-10-15 04:06:14 +00:00
2019-10-20 05:19:50 +00:00
/// Build a non-player character.
fn create_npc (
& mut self ,
pos : comp ::Pos ,
stats : comp ::Stats ,
body : comp ::Body ,
) -> EcsEntityBuilder {
self . ecs_mut ( )
. create_entity_synced ( )
. with ( pos )
. with ( comp ::Vel ( Vec3 ::zero ( ) ) )
. with ( comp ::Ori ( Vec3 ::unit_y ( ) ) )
. with ( comp ::Controller ::default ( ) )
. with ( body )
. with ( stats )
2020-01-24 21:24:57 +00:00
. with ( comp ::Alignment ::Npc )
2020-01-17 20:08:27 +00:00
. with ( comp ::Energy ::new ( 500 ) )
2019-10-20 05:19:50 +00:00
. with ( comp ::Gravity ( 1.0 ) )
. with ( comp ::CharacterState ::default ( ) )
}
2019-10-15 04:06:14 +00:00
fn notify_registered_clients ( & self , msg : ServerMsg ) {
for client in ( & mut self . ecs ( ) . write_storage ::< Client > ( ) )
. join ( )
. filter ( | c | c . is_registered ( ) )
{
client . notify ( msg . clone ( ) )
}
}
2019-11-29 06:04:37 +00:00
fn delete_entity_recorded (
& mut self ,
entity : EcsEntity ,
) -> Result < ( ) , specs ::error ::WrongGeneration > {
let ( maybe_uid , maybe_pos ) = (
self . ecs ( ) . read_storage ::< Uid > ( ) . get ( entity ) . copied ( ) ,
self . ecs ( ) . read_storage ::< comp ::Pos > ( ) . get ( entity ) . copied ( ) ,
) ;
let res = self . ecs_mut ( ) . delete_entity ( entity ) ;
if res . is_ok ( ) {
if let ( Some ( uid ) , Some ( pos ) ) = ( maybe_uid , maybe_pos ) {
2020-01-03 04:23:38 +00:00
if let Some ( region_key ) = self
2019-11-29 06:04:37 +00:00
. ecs ( )
. read_resource ::< common ::region ::RegionMap > ( )
. find_region ( entity , pos . 0 )
2020-01-03 04:23:38 +00:00
{
self . ecs ( )
. write_resource ::< DeletedEntities > ( )
. record_deleted_entity ( uid , region_key ) ;
} else {
// Don't panic if the entity wasn't found in a region maybe it was just created
// and then deleted before the region manager had a chance to assign it a
// region
warn! ( " Failed to find region containing entity during entity deletion, assuming it wasn't sent to any clients and so deletion doesn't need to be recorded for sync purposes " ) ;
}
2019-11-29 06:04:37 +00:00
}
}
res
}
2019-09-25 14:56:57 +00:00
}