2019-08-19 12:39:23 +00:00
#![ deny(unsafe_code) ]
2019-09-06 05:16:11 +00:00
#![ feature(label_break_value) ]
2019-03-05 18:39:18 +00:00
2019-03-03 22:02:38 +00:00
pub mod error ;
// Reexports
2019-05-22 20:53:24 +00:00
pub use crate ::error ::Error ;
2019-12-31 08:10:51 +00:00
pub use specs ::{
join ::Join ,
saveload ::{ Marker , MarkerAllocator } ,
2020-01-10 00:33:38 +00:00
Builder , DispatcherBuilder , Entity as EcsEntity , ReadStorage , WorldExt ,
2019-12-31 08:10:51 +00:00
} ;
2019-03-03 22:02:38 +00:00
use common ::{
2019-10-15 04:06:14 +00:00
comp ::{ self , ControlEvent , Controller , ControllerInputs , InventoryManip } ,
2019-08-14 04:38:54 +00:00
msg ::{
2019-12-23 06:02:00 +00:00
validate_chat_msg , ChatMsgValidationError , ClientMsg , ClientState , PlayerListUpdate ,
RequestStateError , ServerError , ServerInfo , ServerMsg , MAX_BYTES_CHAT_MSG ,
2019-08-14 04:38:54 +00:00
} ,
2019-04-23 09:53:45 +00:00
net ::PostBox ,
2019-11-24 20:12:03 +00:00
state ::State ,
2019-12-31 08:10:51 +00:00
sync ::{ Uid , UidAllocator , WorldSyncExt } ,
2019-09-06 13:23:38 +00:00
terrain ::{ block ::Block , TerrainChunk , TerrainChunkSize } ,
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 ::RectVolSize ,
2019-07-17 22:10:42 +00:00
ChatType ,
2019-03-03 22:02:38 +00:00
} ;
2019-08-11 19:54:20 +00:00
use hashbrown ::HashMap ;
2019-10-16 11:39:41 +00:00
use image ::DynamicImage ;
2019-09-06 13:23:38 +00:00
use log ::warn ;
2019-05-16 20:18:48 +00:00
use std ::{
net ::SocketAddr ,
2019-06-11 18:39:25 +00:00
sync ::Arc ,
2019-06-15 10:36:26 +00:00
time ::{ Duration , Instant } ,
2019-05-16 20:18:48 +00:00
} ;
2019-07-12 18:51:22 +00:00
use uvth ::{ ThreadPool , ThreadPoolBuilder } ;
2019-04-23 09:53:45 +00:00
use vek ::* ;
2019-01-02 17:23:31 +00:00
2019-10-17 04:09:01 +00:00
// The duration of network inactivity until the player is kicked to the main menu
2019-10-26 19:42:28 +00:00
// @TODO in the future, this should be configurable on the server, and be provided to the client
2019-04-24 07:59:42 +00:00
const SERVER_TIMEOUT : Duration = Duration ::from_secs ( 20 ) ;
2019-03-05 18:39:18 +00:00
2019-10-17 04:09:01 +00:00
// After this duration has elapsed, the user will begin getting kick warnings in their chat window
const SERVER_TIMEOUT_GRACE_PERIOD : Duration = Duration ::from_secs ( 14 ) ;
2019-03-03 22:02:38 +00:00
pub enum Event {
2019-07-17 22:10:42 +00:00
Chat {
chat_type : ChatType ,
message : String ,
} ,
2019-04-23 12:01:16 +00:00
Disconnect ,
2019-10-17 04:09:01 +00:00
DisconnectionNotification ( u64 ) ,
2019-01-02 17:23:31 +00:00
}
pub struct Client {
2019-05-24 19:10:18 +00:00
client_state : ClientState ,
2019-04-10 23:16:29 +00:00
thread_pool : ThreadPool ,
2019-05-08 16:22:52 +00:00
pub server_info : ServerInfo ,
2019-10-16 11:39:41 +00:00
pub world_map : Arc < DynamicImage > ,
2019-12-23 06:02:00 +00:00
pub player_list : HashMap < u64 , String > ,
2019-01-02 17:23:31 +00:00
2019-05-17 20:47:58 +00:00
postbox : PostBox < ClientMsg , ServerMsg > ,
2019-03-03 22:02:38 +00:00
2019-05-23 08:18:25 +00:00
last_server_ping : Instant ,
2019-10-17 04:09:01 +00:00
last_server_pong : Instant ,
2019-05-23 08:18:25 +00:00
last_ping_delta : f64 ,
2019-01-23 20:01:58 +00:00
tick : u64 ,
state : State ,
2019-04-19 19:32:47 +00:00
entity : EcsEntity ,
2019-06-23 19:49:15 +00:00
2019-05-19 00:45:02 +00:00
view_distance : Option < u32 > ,
2020-01-19 20:48:57 +00:00
// TODO: move into voxygen
2020-01-12 11:09:37 +00:00
loaded_distance : f32 ,
2019-04-11 22:26:43 +00:00
2019-05-17 17:44:30 +00:00
pending_chunks : HashMap < Vec2 < i32 > , Instant > ,
2019-01-02 17:23:31 +00:00
}
impl Client {
2019-01-12 15:57:19 +00:00
/// Create a new `Client`.
2019-05-19 00:45:02 +00:00
pub fn new < A : Into < SocketAddr > > ( addr : A , view_distance : Option < u32 > ) -> Result < Self , Error > {
2019-06-06 14:48:41 +00:00
let client_state = ClientState ::Connected ;
2019-04-11 22:26:43 +00:00
let mut postbox = PostBox ::to ( addr ) ? ;
2019-03-03 22:02:38 +00:00
2019-04-19 19:32:47 +00:00
// Wait for initial sync
2019-10-16 11:39:41 +00:00
let ( state , entity , server_info , world_map ) = match postbox . next_message ( ) {
2019-04-23 09:53:45 +00:00
Some ( ServerMsg ::InitialSync {
2019-12-18 05:22:52 +00:00
entity_package ,
2019-05-08 16:22:52 +00:00
server_info ,
2019-12-18 05:22:52 +00:00
time_of_day ,
2019-10-16 11:39:41 +00:00
// world_map: /*(map_size, world_map)*/map_size,
2019-04-23 09:53:45 +00:00
} ) = > {
2019-12-31 08:10:51 +00:00
// TODO: Display that versions don't match in Voxygen
2019-07-21 17:45:31 +00:00
if server_info . git_hash ! = common ::util ::GIT_HASH . to_string ( ) {
log ::warn! (
2019-10-18 13:32:26 +00:00
" Server is running {}[{}], you are running {}[{}], versions might be incompatible! " ,
2019-07-21 17:45:31 +00:00
server_info . git_hash ,
2019-10-18 13:32:26 +00:00
server_info . git_date ,
2019-10-18 10:03:01 +00:00
common ::util ::GIT_HASH . to_string ( ) ,
common ::util ::GIT_DATE . to_string ( ) ,
2019-07-21 17:45:31 +00:00
) ;
}
2019-12-18 05:22:52 +00:00
// Initialize `State`
2019-11-04 00:57:36 +00:00
let mut state = State ::default ( ) ;
2019-12-18 05:22:52 +00:00
let entity = state . ecs_mut ( ) . apply_entity_package ( entity_package ) ;
* state . ecs_mut ( ) . write_resource ( ) = time_of_day ;
2019-10-16 11:39:41 +00:00
// assert_eq!(world_map.len(), map_size.x * map_size.y);
let map_size = Vec2 ::new ( 1024 , 1024 ) ;
let world_map_raw = vec! [ 0 u8 ; 4 * /* world_map.len() */ map_size . x * map_size . y ] ;
// LittleEndian::write_u32_into(&world_map, &mut world_map_raw);
2019-10-23 17:29:39 +00:00
log ::debug! ( " Preparing image... " ) ;
2019-10-16 11:39:41 +00:00
let world_map = Arc ::new ( image ::DynamicImage ::ImageRgba8 ( {
// Should not fail if the dimensions are correct.
let world_map = image ::ImageBuffer ::from_raw (
map_size . x as u32 ,
map_size . y as u32 ,
world_map_raw ,
) ;
world_map . ok_or ( Error ::Other ( " Server sent a bad world map image " . into ( ) ) ) ?
} ) ) ;
2019-10-23 17:29:39 +00:00
log ::debug! ( " Done preparing image... " ) ;
2019-10-16 11:39:41 +00:00
( state , entity , server_info , world_map )
2019-04-23 09:53:45 +00:00
}
2019-07-04 16:14:45 +00:00
Some ( ServerMsg ::Error ( ServerError ::TooManyPlayers ) ) = > {
return Err ( Error ::TooManyPlayers )
}
2019-04-10 17:23:27 +00:00
_ = > return Err ( Error ::ServerWentMad ) ,
} ;
2019-04-24 07:59:42 +00:00
postbox . send_message ( ClientMsg ::Ping ) ;
2019-07-12 18:51:22 +00:00
let mut thread_pool = ThreadPoolBuilder ::new ( )
. name ( " veloren-worker " . into ( ) )
2019-06-05 13:13:24 +00:00
. build ( ) ;
// We reduce the thread count by 1 to keep rendering smooth
2019-07-12 17:35:11 +00:00
thread_pool . set_num_threads ( ( num_cpus ::get ( ) - 1 ) . max ( 1 ) ) ;
2019-06-05 13:13:24 +00:00
2019-03-03 22:02:38 +00:00
Ok ( Self {
2019-04-19 19:32:47 +00:00
client_state ,
2019-06-05 13:13:24 +00:00
thread_pool ,
2019-05-08 16:22:52 +00:00
server_info ,
2019-10-16 11:39:41 +00:00
world_map ,
2019-12-23 06:02:00 +00:00
player_list : HashMap ::new ( ) ,
2019-01-15 15:13:11 +00:00
2019-03-03 22:02:38 +00:00
postbox ,
2019-05-23 08:18:25 +00:00
last_server_ping : Instant ::now ( ) ,
2019-10-17 04:09:01 +00:00
last_server_pong : Instant ::now ( ) ,
2019-05-23 08:18:25 +00:00
last_ping_delta : 0.0 ,
2019-01-23 20:01:58 +00:00
tick : 0 ,
2019-03-03 22:02:38 +00:00
state ,
2019-04-22 00:38:29 +00:00
entity ,
2019-04-10 23:16:29 +00:00
view_distance ,
2020-01-12 11:09:37 +00:00
loaded_distance : 0.0 ,
2019-04-11 22:26:43 +00:00
2019-05-16 20:18:48 +00:00
pending_chunks : HashMap ::new ( ) ,
2019-03-03 22:02:38 +00:00
} )
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-05-25 21:13:38 +00:00
/// Request a state transition to `ClientState::Registered`.
2019-08-08 21:58:36 +00:00
pub fn register ( & mut self , player : comp ::Player , password : String ) -> Result < ( ) , Error > {
2019-08-08 16:05:38 +00:00
self . postbox
. send_message ( ClientMsg ::Register { player , password } ) ;
2019-08-08 16:01:15 +00:00
self . client_state = ClientState ::Pending ;
2019-08-08 21:58:36 +00:00
loop {
2019-08-08 15:23:58 +00:00
match self . postbox . next_message ( ) {
Some ( ServerMsg ::StateAnswer ( Err ( ( RequestStateError ::Denied , _ ) ) ) ) = > {
break Err ( Error ::InvalidAuth )
}
2019-08-08 21:58:36 +00:00
Some ( ServerMsg ::StateAnswer ( Ok ( ClientState ::Registered ) ) ) = > break Ok ( ( ) ) ,
2019-08-08 16:09:14 +00:00
_ = > { }
2019-08-08 15:23:58 +00:00
}
2019-08-08 21:58:36 +00:00
}
2019-04-21 18:12:29 +00:00
}
2019-05-25 21:13:38 +00:00
/// Request a state transition to `ClientState::Character`.
2019-10-22 18:18:40 +00:00
pub fn request_character ( & mut self , name : String , body : comp ::Body , main : Option < String > ) {
2019-05-17 20:47:58 +00:00
self . postbox
2019-08-27 19:56:46 +00:00
. send_message ( ClientMsg ::Character { name , body , main } ) ;
2019-05-24 19:10:18 +00:00
self . client_state = ClientState ::Pending ;
2019-05-17 20:47:58 +00:00
}
2019-12-31 08:10:51 +00:00
/// Send disconnect message to the server
2019-06-02 02:17:36 +00:00
pub fn request_logout ( & mut self ) {
2019-12-31 08:10:51 +00:00
self . postbox . send_message ( ClientMsg ::Disconnect ) ;
2019-06-02 02:17:36 +00:00
self . client_state = ClientState ::Pending ;
}
2019-12-31 08:10:51 +00:00
/// Request a state transition to `ClientState::Registered` from an ingame state.
2019-06-02 02:17:36 +00:00
pub fn request_remove_character ( & mut self ) {
2019-12-31 08:10:51 +00:00
self . postbox . send_message ( ClientMsg ::ExitIngame ) ;
2019-06-02 02:17:36 +00:00
self . client_state = ClientState ::Pending ;
}
2019-05-19 00:45:02 +00:00
pub fn set_view_distance ( & mut self , view_distance : u32 ) {
2019-10-16 11:39:41 +00:00
self . view_distance = Some ( view_distance . max ( 1 ) . min ( 65 ) ) ;
2019-05-19 00:45:02 +00:00
self . postbox
2019-08-26 09:49:14 +00:00
. send_message ( ClientMsg ::SetViewDistance ( self . view_distance . unwrap ( ) ) ) ;
// Can't fail
2019-05-19 00:45:02 +00:00
}
2019-10-15 04:06:14 +00:00
pub fn use_inventory_slot ( & mut self , slot : usize ) {
self . postbox
. send_message ( ClientMsg ::ControlEvent ( ControlEvent ::InventoryManip (
InventoryManip ::Use ( slot ) ,
) ) ) ;
2019-08-28 20:47:52 +00:00
}
2019-07-25 22:52:28 +00:00
pub fn swap_inventory_slots ( & mut self , a : usize , b : usize ) {
self . postbox
2019-10-15 04:06:14 +00:00
. send_message ( ClientMsg ::ControlEvent ( ControlEvent ::InventoryManip (
InventoryManip ::Swap ( a , b ) ,
) ) ) ;
2019-07-25 22:52:28 +00:00
}
2019-10-15 04:06:14 +00:00
pub fn drop_inventory_slot ( & mut self , slot : usize ) {
self . postbox
. send_message ( ClientMsg ::ControlEvent ( ControlEvent ::InventoryManip (
InventoryManip ::Drop ( slot ) ,
) ) ) ;
2019-07-26 17:08:40 +00:00
}
2019-07-29 16:19:08 +00:00
pub fn pick_up ( & mut self , entity : EcsEntity ) {
if let Some ( uid ) = self . state . ecs ( ) . read_storage ::< Uid > ( ) . get ( entity ) . copied ( ) {
2019-10-15 04:06:14 +00:00
self . postbox
. send_message ( ClientMsg ::ControlEvent ( ControlEvent ::InventoryManip (
InventoryManip ::Pickup ( uid ) ,
) ) ) ;
2019-07-29 16:19:08 +00:00
}
}
2019-09-09 19:11:40 +00:00
pub fn is_mounted ( & self ) -> bool {
self . state
. ecs ( )
. read_storage ::< comp ::Mounting > ( )
. get ( self . entity )
. is_some ( )
}
2019-10-15 04:06:14 +00:00
pub fn mount ( & mut self , entity : EcsEntity ) {
if let Some ( uid ) = self . state . ecs ( ) . read_storage ::< Uid > ( ) . get ( entity ) . copied ( ) {
self . postbox
. send_message ( ClientMsg ::ControlEvent ( ControlEvent ::Mount ( uid ) ) ) ;
}
}
pub fn unmount ( & mut self ) {
self . postbox
. send_message ( ClientMsg ::ControlEvent ( ControlEvent ::Unmount ) ) ;
}
2019-05-27 17:01:00 +00:00
pub fn view_distance ( & self ) -> Option < u32 > {
self . view_distance
}
2020-01-12 11:09:37 +00:00
pub fn loaded_distance ( & self ) -> f32 {
2019-06-05 16:32:33 +00:00
self . loaded_distance
}
2019-06-11 18:39:25 +00:00
pub fn current_chunk ( & self ) -> Option < Arc < TerrainChunk > > {
2019-06-15 10:36:26 +00:00
let chunk_pos = Vec2 ::from (
self . state
2019-06-14 15:27:05 +00:00
. read_storage ::< comp ::Pos > ( )
2019-06-15 10:36:26 +00:00
. get ( self . entity )
. cloned ( ) ?
. 0 ,
)
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
. map2 ( TerrainChunkSize ::RECT_SIZE , | e : f32 , sz | {
2019-06-15 10:36:26 +00:00
( e as u32 ) . div_euclid ( sz ) as i32
} ) ;
2019-06-11 18:39:25 +00:00
self . state . terrain ( ) . get_key_arc ( chunk_pos ) . cloned ( )
}
2019-07-25 17:41:06 +00:00
pub fn inventories ( & self ) -> ReadStorage < comp ::Inventory > {
self . state . read_storage ( )
}
2019-05-25 21:13:38 +00:00
/// Send a chat message to the server.
2019-12-31 08:10:51 +00:00
pub fn send_chat ( & mut self , message : String ) {
match validate_chat_msg ( & message ) {
Ok ( ( ) ) = > self . postbox . send_message ( ClientMsg ::ChatMsg { message } ) ,
2019-08-14 04:38:54 +00:00
Err ( ChatMsgValidationError ::TooLong ) = > log ::warn! (
" Attempted to send a message that's too long (Over {} bytes) " ,
MAX_BYTES_CHAT_MSG
) ,
}
2019-05-20 19:24:47 +00:00
}
2019-05-25 21:13:38 +00:00
/// Remove all cached terrain
pub fn clear_terrain ( & mut self ) {
self . state . clear_terrain ( ) ;
self . pending_chunks . clear ( ) ;
2019-05-23 08:18:25 +00:00
}
2019-07-02 18:19:16 +00:00
pub fn place_block ( & mut self , pos : Vec3 < i32 > , block : Block ) {
self . postbox . send_message ( ClientMsg ::PlaceBlock ( pos , block ) ) ;
}
pub fn remove_block ( & mut self , pos : Vec3 < i32 > ) {
self . postbox . send_message ( ClientMsg ::BreakBlock ( pos ) ) ;
}
2019-09-25 21:17:43 +00:00
pub fn collect_block ( & mut self , pos : Vec3 < i32 > ) {
2019-10-15 04:06:14 +00:00
self . postbox
. send_message ( ClientMsg ::ControlEvent ( ControlEvent ::InventoryManip (
InventoryManip ::Collect ( pos ) ,
) ) ) ;
2019-09-25 21:17:43 +00:00
}
2019-05-17 09:22:32 +00:00
/// Execute a single client tick, handle input and update the game state by the given duration.
2020-01-10 00:33:38 +00:00
pub fn tick (
& mut self ,
inputs : ControllerInputs ,
dt : Duration ,
add_foreign_systems : impl Fn ( & mut DispatcherBuilder ) ,
) -> Result < Vec < Event > , Error > {
2019-01-02 17:23:31 +00:00
// This tick function is the centre of the Veloren universe. Most client-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.
//
// 1) Collect input from the frontend, apply input effects to the state of the game
2019-05-17 20:47:58 +00:00
// 2) Handle messages from the server
// 3) Go through any events (timer-driven or otherwise) that need handling and apply them
2019-01-02 17:23:31 +00:00
// to the state of the game
2019-05-17 20:47:58 +00:00
// 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
// 6) Sync information to the server
2019-05-22 20:53:24 +00:00
// 7) Finish the tick, passing actions of the main thread back to the frontend
2019-05-17 20:47:58 +00:00
// 1) Handle input from frontend.
2019-05-22 20:53:24 +00:00
// Pass character actions from frontend input to the player's entity.
2019-12-31 08:10:51 +00:00
if let ClientState ::Character = self . client_state {
2019-10-15 04:06:14 +00:00
self . state . write_component (
self . entity ,
Controller {
inputs : inputs . clone ( ) ,
events : Vec ::new ( ) ,
} ,
) ;
self . postbox
. send_message ( ClientMsg ::ControllerInputs ( inputs ) ) ;
2019-06-17 17:52:06 +00:00
}
2019-03-02 03:48:30 +00:00
2019-05-17 20:47:58 +00:00
// 2) Build up a list of events for this frame, to be passed to the frontend.
let mut frontend_events = Vec ::new ( ) ;
2019-01-02 17:23:31 +00:00
2019-08-28 13:55:35 +00:00
// Prepare for new events
2019-08-25 22:22:43 +00:00
{
2019-10-15 04:06:14 +00:00
let ecs = self . state . ecs ( ) ;
2019-08-25 22:22:43 +00:00
for ( entity , _ ) in ( & ecs . entities ( ) , & ecs . read_storage ::< comp ::Body > ( ) ) . join ( ) {
2019-08-28 12:46:20 +00:00
let mut last_character_states =
2019-08-25 22:22:43 +00:00
ecs . write_storage ::< comp ::Last < comp ::CharacterState > > ( ) ;
if let Some ( client_character_state ) =
ecs . read_storage ::< comp ::CharacterState > ( ) . get ( entity )
{
2019-08-28 12:46:20 +00:00
if last_character_states
2019-08-25 22:22:43 +00:00
. get ( entity )
2019-08-30 18:40:22 +00:00
. map ( | & l | ! client_character_state . is_same_state ( & l . 0 ) )
2019-08-25 22:22:43 +00:00
. unwrap_or ( true )
{
2019-08-28 12:46:20 +00:00
let _ = last_character_states
2019-08-25 22:22:43 +00:00
. insert ( entity , comp ::Last ( * client_character_state ) ) ;
}
}
}
}
2019-08-28 13:55:35 +00:00
// Handle new messages from the server.
frontend_events . append ( & mut self . handle_new_messages ( ) ? ) ;
// 3) Update client local data
2019-05-17 20:47:58 +00:00
// 4) Tick the client's LocalState
2020-01-10 00:33:38 +00:00
self . state . tick ( dt , add_foreign_systems ) ;
2019-04-16 14:29:44 +00:00
2019-05-17 20:47:58 +00:00
// 5) Terrain
2019-04-25 19:08:26 +00:00
let pos = self
2019-04-23 09:53:45 +00:00
. state
2019-06-14 15:27:05 +00:00
. read_storage ::< comp ::Pos > ( )
2019-04-23 09:53:45 +00:00
. get ( self . entity )
2019-04-25 19:08:26 +00:00
. cloned ( ) ;
2019-05-19 00:45:02 +00:00
if let ( Some ( pos ) , Some ( view_distance ) ) = ( pos , self . view_distance ) {
2019-04-14 20:30:27 +00:00
let chunk_pos = self . state . terrain ( ) . pos_key ( pos . 0. map ( | e | e as i32 ) ) ;
2019-05-17 09:22:32 +00:00
// Remove chunks that are too far from the player.
2019-04-25 19:08:26 +00:00
let mut chunks_to_remove = Vec ::new ( ) ;
2019-04-25 19:25:22 +00:00
self . state . terrain ( ) . iter ( ) . for_each ( | ( key , _ ) | {
2019-12-23 06:02:00 +00:00
// Subtract 2 from the offset before computing squared magnitude
// 1 for the chunks needed bordering other chunks for meshing
// 1 as a buffer so that if the player moves back in that direction the chunks
// don't need to be reloaded
2019-07-02 19:00:57 +00:00
if ( chunk_pos - key )
2019-06-23 19:49:15 +00:00
. map ( | e : i32 | ( e . abs ( ) as u32 ) . checked_sub ( 2 ) . unwrap_or ( 0 ) )
. magnitude_squared ( )
> view_distance . pow ( 2 )
2019-05-09 17:58:16 +00:00
{
2019-04-25 19:25:22 +00:00
chunks_to_remove . push ( key ) ;
2019-04-25 19:08:26 +00:00
}
} ) ;
for key in chunks_to_remove {
self . state . remove_chunk ( key ) ;
}
2019-05-17 09:22:32 +00:00
// Request chunks from the server.
2020-01-12 11:09:37 +00:00
self . loaded_distance = ( ( view_distance * TerrainChunkSize ::RECT_SIZE . x ) as f32 ) . powi ( 2 ) ;
// +1 so we can find a chunk that's outside the vd for better fog
for dist in 0 .. view_distance as i32 + 1 {
2019-06-23 19:49:15 +00:00
// Only iterate through chunks that need to be loaded for circular vd
// The (dist - 2) explained:
// -0.5 because a chunk is visible if its corner is within the view distance
// -0.5 for being able to move to the corner of the current chunk
// -1 because chunks are not meshed if they don't have all their neighbors
// (notice also that view_distance is decreased by 1)
// (this subtraction on vd is ommitted elsewhere in order to provide a buffer layer of loaded chunks)
let top = if 2 * ( dist - 2 ) . max ( 0 ) . pow ( 2 ) > ( view_distance - 1 ) . pow ( 2 ) as i32 {
( ( view_distance - 1 ) . pow ( 2 ) as f32 - ( dist - 2 ) . pow ( 2 ) as f32 )
. sqrt ( )
. round ( ) as i32
+ 1
} else {
dist
} ;
2020-01-12 11:09:37 +00:00
let mut skip_mode = false ;
2020-01-12 14:45:20 +00:00
for i in - top .. top + 1 {
2019-06-23 19:49:15 +00:00
let keys = [
chunk_pos + Vec2 ::new ( dist , i ) ,
chunk_pos + Vec2 ::new ( i , dist ) ,
chunk_pos + Vec2 ::new ( - dist , i ) ,
chunk_pos + Vec2 ::new ( i , - dist ) ,
] ;
for key in keys . iter ( ) {
if self . state . terrain ( ) . get_key ( * key ) . is_none ( ) {
2020-01-12 11:09:37 +00:00
if ! skip_mode & & ! self . pending_chunks . contains_key ( key ) {
2019-06-05 16:32:33 +00:00
if self . pending_chunks . len ( ) < 4 {
self . postbox
2019-06-23 19:49:15 +00:00
. send_message ( ClientMsg ::TerrainChunkRequest { key : * key } ) ;
self . pending_chunks . insert ( * key , Instant ::now ( ) ) ;
2019-06-05 16:32:33 +00:00
} else {
2020-01-12 11:09:37 +00:00
skip_mode = true ;
2019-06-05 16:32:33 +00:00
}
2019-05-13 12:08:17 +00:00
}
2019-06-05 16:32:33 +00:00
2020-01-12 11:09:37 +00:00
let dist_to_player =
( self . state . terrain ( ) . key_pos ( * key ) . map ( | x | x as f32 )
+ TerrainChunkSize ::RECT_SIZE . map ( | x | x as f32 ) / 2.0 )
. distance_squared ( pos . 0. into ( ) ) ;
if dist_to_player < self . loaded_distance {
self . loaded_distance = dist_to_player ;
}
2019-04-11 22:26:43 +00:00
}
}
}
}
2020-01-12 11:09:37 +00:00
self . loaded_distance = self . loaded_distance . sqrt ( )
- ( ( TerrainChunkSize ::RECT_SIZE . x as f32 / 2.0 ) . powi ( 2 )
+ ( TerrainChunkSize ::RECT_SIZE . y as f32 / 2.0 ) . powi ( 2 ) )
. sqrt ( ) ;
2019-05-16 20:18:48 +00:00
2019-05-17 09:22:32 +00:00
// If chunks are taking too long, assume they're no longer pending.
2019-05-16 20:18:48 +00:00
let now = Instant ::now ( ) ;
2019-05-16 21:04:16 +00:00
self . pending_chunks
2019-06-05 18:00:17 +00:00
. retain ( | _ , created | now . duration_since ( * created ) < Duration ::from_secs ( 3 ) ) ;
2019-04-11 22:26:43 +00:00
}
2019-05-17 20:47:58 +00:00
// Send a ping to the server once every second
2019-05-23 08:18:25 +00:00
if Instant ::now ( ) . duration_since ( self . last_server_ping ) > Duration ::from_secs ( 1 ) {
self . postbox . send_message ( ClientMsg ::Ping ) ;
self . last_server_ping = Instant ::now ( ) ;
}
2019-05-17 20:47:58 +00:00
// 6) Update the server about the player's physics attributes.
2019-06-29 20:40:40 +00:00
if let ClientState ::Character = self . client_state {
2019-07-02 19:00:57 +00:00
if let ( Some ( pos ) , Some ( vel ) , Some ( ori ) ) = (
2019-06-29 20:40:40 +00:00
self . state . read_storage ( ) . get ( self . entity ) . cloned ( ) ,
self . state . read_storage ( ) . get ( self . entity ) . cloned ( ) ,
self . state . read_storage ( ) . get ( self . entity ) . cloned ( ) ,
) {
2019-07-02 19:00:57 +00:00
self . postbox
. send_message ( ClientMsg ::PlayerPhysics { pos , vel , ori } ) ;
2019-05-17 20:47:58 +00:00
}
}
2019-09-06 13:23:38 +00:00
/*
2019-06-04 12:45:41 +00:00
// Output debug metrics
2019-06-05 19:51:49 +00:00
if log_enabled! ( log ::Level ::Info ) & & self . tick % 600 = = 0 {
2019-06-04 12:49:57 +00:00
let metrics = self
. state
2019-06-04 12:45:41 +00:00
. terrain ( )
. iter ( )
. fold ( ChonkMetrics ::default ( ) , | a , ( _ , c ) | a + c . get_metrics ( ) ) ;
info! ( " {:?} " , metrics ) ;
}
2019-09-06 13:23:38 +00:00
* /
2019-06-04 12:45:41 +00:00
2019-05-17 20:47:58 +00:00
// 7) Finish the tick, pass control back to the frontend.
2019-01-23 20:01:58 +00:00
self . tick + = 1 ;
2019-03-03 22:02:38 +00:00
Ok ( frontend_events )
2019-01-02 17:23:31 +00:00
}
2019-01-23 20:01:58 +00:00
2019-05-17 09:22:32 +00:00
/// Clean up the client after a tick.
2019-01-23 20:01:58 +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 server messages.
2019-03-03 22:02:38 +00:00
fn handle_new_messages ( & mut self ) -> Result < Vec < Event > , Error > {
let mut frontend_events = Vec ::new ( ) ;
2019-10-17 04:09:01 +00:00
// Check that we have an valid connection.
2019-10-22 03:46:12 +00:00
// Use the last ping time as a 1s rate limiter, we only notify the user once per second
2019-10-17 04:09:01 +00:00
if Instant ::now ( ) . duration_since ( self . last_server_ping ) > Duration ::from_secs ( 1 ) {
let duration_since_last_pong = Instant ::now ( ) . duration_since ( self . last_server_pong ) ;
// Dispatch a notification to the HUD warning they will be kicked in {n} seconds
2019-10-22 03:46:12 +00:00
if duration_since_last_pong . as_secs ( ) > = SERVER_TIMEOUT_GRACE_PERIOD . as_secs ( ) {
if let Some ( seconds_until_kick ) =
SERVER_TIMEOUT . checked_sub ( duration_since_last_pong )
{
frontend_events . push ( Event ::DisconnectionNotification (
seconds_until_kick . as_secs ( ) ,
) ) ;
}
2019-10-17 04:09:01 +00:00
}
}
2019-03-03 22:02:38 +00:00
let new_msgs = self . postbox . new_messages ( ) ;
if new_msgs . len ( ) > 0 {
for msg in new_msgs {
match msg {
2019-07-04 16:14:45 +00:00
ServerMsg ::Error ( e ) = > match e {
ServerError ::TooManyPlayers = > return Err ( Error ::ServerWentMad ) ,
2019-08-08 03:56:02 +00:00
ServerError ::InvalidAuth = > return Err ( Error ::InvalidAuth ) ,
2019-07-05 12:16:08 +00:00
//TODO: ServerError::InvalidAlias => return Err(Error::InvalidAlias),
2019-07-04 16:14:45 +00:00
} ,
2019-03-03 22:02:38 +00:00
ServerMsg ::Shutdown = > return Err ( Error ::ServerShutdown ) ,
2019-07-04 16:14:45 +00:00
ServerMsg ::InitialSync { .. } = > return Err ( Error ::ServerWentMad ) ,
2019-12-23 06:02:00 +00:00
ServerMsg ::PlayerListUpdate ( PlayerListUpdate ::Init ( list ) ) = > {
self . player_list = list
}
ServerMsg ::PlayerListUpdate ( PlayerListUpdate ::Add ( uid , name ) ) = > {
if let Some ( old_name ) = self . player_list . insert ( uid , name . clone ( ) ) {
warn! ( " Received msg to insert {} with uid {} into the player list but there was already an entry for {} with the same uid that was overwritten! " , name , uid , old_name ) ;
}
}
ServerMsg ::PlayerListUpdate ( PlayerListUpdate ::Remove ( uid ) ) = > {
if self . player_list . remove ( & uid ) . is_none ( ) {
warn! ( " Received msg to remove uid {} from the player list by they weren't in the list! " , uid ) ;
}
}
ServerMsg ::PlayerListUpdate ( PlayerListUpdate ::Alias ( uid , new_name ) ) = > {
if let Some ( name ) = self . player_list . get_mut ( & uid ) {
* name = new_name ;
} else {
warn! ( " Received msg to alias player with uid {} to {} but this uid is not in the player list " , uid , new_name ) ;
}
}
2019-04-11 22:26:43 +00:00
ServerMsg ::Ping = > self . postbox . send_message ( ClientMsg ::Pong ) ,
2019-05-23 08:18:25 +00:00
ServerMsg ::Pong = > {
2019-10-17 04:09:01 +00:00
self . last_server_pong = Instant ::now ( ) ;
2019-05-23 08:18:25 +00:00
self . last_ping_delta = Instant ::now ( )
. duration_since ( self . last_server_ping )
2019-12-18 05:22:52 +00:00
. as_secs_f64 ( ) ;
2019-05-23 08:18:25 +00:00
}
2019-12-31 08:10:51 +00:00
ServerMsg ::ChatMsg { message , chat_type } = > {
frontend_events . push ( Event ::Chat { message , chat_type } )
2019-07-21 18:34:52 +00:00
}
2019-04-29 20:37:19 +00:00
ServerMsg ::SetPlayerEntity ( uid ) = > {
2019-10-23 16:38:09 +00:00
if let Some ( entity ) = self . state . ecs ( ) . entity_from_uid ( uid ) {
self . entity = entity ;
} else {
return Err ( Error ::Other ( " Failed to find entity from uid. " . to_owned ( ) ) ) ;
}
}
2019-12-18 05:22:52 +00:00
ServerMsg ::TimeOfDay ( time_of_day ) = > {
* self . state . ecs_mut ( ) . write_resource ( ) = time_of_day ;
}
2019-04-29 20:37:19 +00:00
ServerMsg ::EcsSync ( sync_package ) = > {
2019-12-18 05:22:52 +00:00
self . state . ecs_mut ( ) . apply_sync_package ( sync_package ) ;
2019-11-04 00:57:36 +00:00
}
ServerMsg ::CreateEntity ( entity_package ) = > {
2019-12-18 05:22:52 +00:00
self . state . ecs_mut ( ) . apply_entity_package ( entity_package ) ;
2019-04-29 20:37:19 +00:00
}
2019-10-06 17:35:47 +00:00
ServerMsg ::DeleteEntity ( entity ) = > {
2019-11-29 06:04:37 +00:00
if self
. state
. read_component_cloned ::< Uid > ( self . entity )
. map ( | u | u . into ( ) )
! = Some ( entity )
{
self . state
. ecs_mut ( )
. delete_entity_and_clear_from_uid_allocator ( entity ) ;
2019-10-06 17:35:47 +00:00
}
}
2019-12-31 08:10:51 +00:00
// Cleanup for when the client goes back to the `Registered` state
ServerMsg ::ExitIngameCleanup = > {
// Get client entity Uid
let client_uid = self
. state
. read_component_cloned ::< Uid > ( self . entity )
. map ( | u | u . into ( ) )
. expect ( " Client doesn't have a Uid!!! " ) ;
// Clear ecs of all entities
self . state . ecs_mut ( ) . delete_all ( ) ;
self . state . ecs_mut ( ) . maintain ( ) ;
self . state . ecs_mut ( ) . insert ( UidAllocator ::default ( ) ) ;
// Recreate client entity with Uid
let entity_builder = self . state . ecs_mut ( ) . create_entity ( ) ;
let uid = entity_builder
. world
. write_resource ::< UidAllocator > ( )
. allocate ( entity_builder . entity , Some ( client_uid ) ) ;
self . entity = entity_builder . with ( uid ) . build ( ) ;
}
2019-07-30 11:35:16 +00:00
ServerMsg ::EntityPos { entity , pos } = > {
if let Some ( entity ) = self . state . ecs ( ) . entity_from_uid ( entity ) {
self . state . write_component ( entity , pos ) ;
}
}
ServerMsg ::EntityVel { entity , vel } = > {
if let Some ( entity ) = self . state . ecs ( ) . entity_from_uid ( entity ) {
self . state . write_component ( entity , vel ) ;
}
}
ServerMsg ::EntityOri { entity , ori } = > {
if let Some ( entity ) = self . state . ecs ( ) . entity_from_uid ( entity ) {
self . state . write_component ( entity , ori ) ;
}
}
2019-08-23 10:11:37 +00:00
ServerMsg ::EntityCharacterState {
2019-04-29 20:37:19 +00:00
entity ,
2019-08-23 10:11:37 +00:00
character_state ,
2019-07-02 19:00:57 +00:00
} = > {
if let Some ( entity ) = self . state . ecs ( ) . entity_from_uid ( entity ) {
2019-08-23 10:11:37 +00:00
self . state . write_component ( entity , character_state ) ;
2019-04-23 09:53:45 +00:00
}
2019-07-02 19:00:57 +00:00
}
2019-07-25 14:48:27 +00:00
ServerMsg ::InventoryUpdate ( inventory ) = > {
self . state . write_component ( self . entity , inventory )
}
2019-04-11 22:26:43 +00:00
ServerMsg ::TerrainChunkUpdate { key , chunk } = > {
2019-09-16 01:38:53 +00:00
if let Ok ( chunk ) = chunk {
self . state . insert_chunk ( key , * chunk ) ;
}
2019-04-11 22:26:43 +00:00
self . pending_chunks . remove ( & key ) ;
2019-04-23 09:53:45 +00:00
}
2019-12-23 06:02:00 +00:00
ServerMsg ::TerrainBlockUpdates ( mut blocks ) = > {
blocks . drain ( ) . for_each ( | ( pos , block ) | {
self . state . set_block ( pos , block ) ;
} ) ;
}
2019-04-19 19:32:47 +00:00
ServerMsg ::StateAnswer ( Ok ( state ) ) = > {
2019-05-24 19:10:18 +00:00
self . client_state = state ;
2019-04-23 09:53:45 +00:00
}
2019-04-19 19:32:47 +00:00
ServerMsg ::StateAnswer ( Err ( ( error , state ) ) ) = > {
2019-08-08 16:01:15 +00:00
if error = = RequestStateError ::Denied {
warn! ( " Connection denied! " ) ;
return Err ( Error ::InvalidAuth ) ;
}
2019-07-02 18:56:29 +00:00
warn! (
" StateAnswer: {:?}. Server thinks client is in state {:?}. " ,
error , state
) ;
2019-04-23 09:53:45 +00:00
}
2019-04-23 12:01:16 +00:00
ServerMsg ::Disconnect = > {
frontend_events . push ( Event ::Disconnect ) ;
2019-04-23 09:53:45 +00:00
}
2019-03-03 22:02:38 +00:00
}
}
2019-03-05 18:39:18 +00:00
} else if let Some ( err ) = self . postbox . error ( ) {
2019-03-04 19:50:26 +00:00
return Err ( err . into ( ) ) ;
2019-04-24 07:59:42 +00:00
// We regularily ping in the tick method
2019-10-17 04:09:01 +00:00
} else if Instant ::now ( ) . duration_since ( self . last_server_pong ) > SERVER_TIMEOUT {
2019-03-05 18:39:18 +00:00
return Err ( Error ::ServerTimeout ) ;
2019-03-03 22:02:38 +00:00
}
Ok ( frontend_events )
}
2019-05-25 21:13:38 +00:00
/// Get the player's entity.
pub fn entity ( & self ) -> EcsEntity {
self . entity
}
/// Get the client state
pub fn get_client_state ( & self ) -> ClientState {
self . client_state
}
/// Get the current tick number.
pub fn get_tick ( & self ) -> u64 {
self . tick
}
pub fn get_ping_ms ( & self ) -> f64 {
self . last_ping_delta * 1000.0
}
/// Get a reference to the client's worker thread pool. This pool should be used for any
/// computationally expensive operations that run outside of the main thread (i.e., threads that
/// block on I/O operations are exempt).
2019-06-05 13:13:24 +00:00
pub fn thread_pool ( & self ) -> & ThreadPool {
2019-05-25 21:13:38 +00:00
& self . thread_pool
}
/// Get a reference to the client's game state.
pub fn state ( & self ) -> & State {
& self . state
}
/// Get a mutable reference to the client's game state.
pub fn state_mut ( & mut self ) -> & mut State {
& mut self . state
}
2019-04-24 07:59:42 +00:00
/// Get a vector of all the players on the server
2019-06-02 14:35:21 +00:00
pub fn get_players ( & mut self ) -> Vec < comp ::Player > {
// TODO: Don't clone players.
self . state
. ecs ( )
. read_storage ::< comp ::Player > ( )
2019-04-24 07:59:42 +00:00
. join ( )
2019-07-02 19:00:57 +00:00
. cloned ( )
2019-04-24 07:59:42 +00:00
. collect ( )
}
2019-03-03 22:02:38 +00:00
}
impl Drop for Client {
fn drop ( & mut self ) {
2019-04-11 22:26:43 +00:00
self . postbox . send_message ( ClientMsg ::Disconnect ) ;
2019-03-03 22:02:38 +00:00
}
2019-01-02 17:23:31 +00:00
}