2019-08-19 12:39:23 +00:00
|
|
|
#![deny(unsafe_code)]
|
2019-06-29 15:04:06 +00:00
|
|
|
#![feature(drain_filter, bind_by_move_pattern_guards)]
|
2019-01-02 17:23:31 +00:00
|
|
|
|
2019-08-08 22:24:14 +00:00
|
|
|
pub mod auth_provider;
|
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-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-05-09 17:58:16 +00:00
|
|
|
client::{Client, Clients},
|
|
|
|
cmd::CHAT_COMMANDS,
|
2019-05-06 14:26:10 +00:00
|
|
|
};
|
2019-03-03 22:02:38 +00:00
|
|
|
use common::{
|
2019-03-04 19:50:26 +00:00
|
|
|
comp,
|
2019-08-25 14:49:54 +00:00
|
|
|
event::{EventBus, ServerEvent},
|
2019-07-04 16:14:45 +00:00
|
|
|
msg::{ClientMsg, ClientState, RequestStateError, ServerError, ServerInfo, ServerMsg},
|
2019-03-03 22:02:38 +00:00
|
|
|
net::PostOffice,
|
2019-08-07 17:17:04 +00:00
|
|
|
state::{BlockChange, State, TimeOfDay, Uid},
|
2019-09-09 08:46:58 +00:00
|
|
|
terrain::{block::Block, TerrainChunk, 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
|
|
|
};
|
2019-08-15 22:10:46 +00:00
|
|
|
use crossbeam::channel;
|
2019-08-11 20:39:41 +00:00
|
|
|
use hashbrown::HashSet;
|
2019-07-01 16:38:19 +00:00
|
|
|
use log::debug;
|
2019-09-07 13:10:57 +00:00
|
|
|
use metrics::ServerMetrics;
|
2019-07-29 17:23:26 +00:00
|
|
|
use rand::Rng;
|
2019-09-07 13:10:57 +00:00
|
|
|
use specs::{join::Join, world::EntityBuilder as EcsEntityBuilder, Builder, Entity as EcsEntity};
|
|
|
|
use std::{
|
|
|
|
i32,
|
|
|
|
net::SocketAddr,
|
|
|
|
sync::Arc,
|
|
|
|
time::{Duration, Instant},
|
|
|
|
};
|
2019-07-12 16:30:15 +00:00
|
|
|
use uvth::{ThreadPool, ThreadPoolBuilder};
|
2019-05-09 17:58:16 +00:00
|
|
|
use vek::*;
|
2019-08-02 14:37:26 +00:00
|
|
|
use world::{ChunkSupplement, World};
|
2019-04-16 13:06:30 +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-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>,
|
2019-01-02 17:23:31 +00:00
|
|
|
|
2019-03-03 22:02:38 +00:00
|
|
|
postoffice: PostOffice<ServerMsg, ClientMsg>,
|
2019-03-04 19:50:26 +00:00
|
|
|
clients: Clients,
|
2019-04-10 23:16:29 +00:00
|
|
|
|
|
|
|
thread_pool: ThreadPool,
|
2019-08-15 22:10:46 +00:00
|
|
|
chunk_tx: channel::Sender<(Vec2<i32>, (TerrainChunk, ChunkSupplement))>,
|
|
|
|
chunk_rx: channel::Receiver<(Vec2<i32>, (TerrainChunk, ChunkSupplement))>,
|
2019-05-17 17:44:30 +00:00
|
|
|
pending_chunks: HashSet<Vec2<i32>>,
|
2019-05-08 16:22:52 +00:00
|
|
|
|
2019-06-29 16:41:26 +00:00
|
|
|
server_settings: ServerSettings,
|
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
|
|
|
|
|
|
|
// TODO: anything but this
|
2019-08-08 22:24:14 +00:00
|
|
|
accounts: AuthProvider,
|
2019-01-02 17:23:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Server {
|
2019-05-06 14:26:10 +00:00
|
|
|
/// Create a new `Server` bound to the default socket.
|
2019-06-29 16:41:26 +00:00
|
|
|
pub fn new(settings: ServerSettings) -> Result<Self, Error> {
|
|
|
|
Self::bind(settings.address, settings)
|
2019-05-06 14:26:10 +00:00
|
|
|
}
|
|
|
|
|
2019-05-17 09:22:32 +00:00
|
|
|
/// Create a new server bound to the given socket.
|
2019-06-29 16:41:26 +00:00
|
|
|
pub fn bind<A: Into<SocketAddr>>(addrs: A, settings: ServerSettings) -> Result<Self, Error> {
|
2019-08-15 22:10:46 +00:00
|
|
|
let (chunk_tx, chunk_rx) = channel::unbounded();
|
2019-04-10 23:16:29 +00:00
|
|
|
|
2019-07-01 20:42:43 +00:00
|
|
|
let mut state = State::default();
|
2019-05-21 22:31:38 +00:00
|
|
|
state
|
|
|
|
.ecs_mut()
|
2019-08-05 18:32:22 +00:00
|
|
|
.add_resource(SpawnPoint(Vec3::new(16_384.0, 16_384.0, 512.0)));
|
2019-08-25 12:27:17 +00:00
|
|
|
state
|
|
|
|
.ecs_mut()
|
|
|
|
.add_resource(EventBus::<ServerEvent>::default());
|
2019-04-16 21:06:33 +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-06-06 14:48:41 +00:00
|
|
|
let this = Self {
|
2019-04-16 21:06:33 +00:00
|
|
|
state,
|
2019-06-29 16:41:26 +00:00
|
|
|
world: Arc::new(World::generate(settings.world_seed)),
|
2019-03-03 22:02:38 +00:00
|
|
|
|
2019-05-06 14:26:10 +00:00
|
|
|
postoffice: PostOffice::bind(addrs.into())?,
|
2019-03-04 19:50:26 +00:00
|
|
|
clients: Clients::empty(),
|
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-04-10 23:16:29 +00:00
|
|
|
chunk_tx,
|
|
|
|
chunk_rx,
|
2019-04-10 23:41:37 +00:00
|
|
|
pending_chunks: HashSet::new(),
|
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-05-08 16:22:52 +00:00
|
|
|
},
|
2019-09-06 14:21:09 +00:00
|
|
|
metrics: ServerMetrics::new(),
|
2019-08-08 22:24:14 +00:00
|
|
|
accounts: AuthProvider::new(),
|
2019-07-01 09:37:17 +00:00
|
|
|
server_settings: 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-05-17 09:22:32 +00:00
|
|
|
/// Build a non-player character.
|
2019-05-25 21:13:38 +00:00
|
|
|
pub fn create_npc(
|
|
|
|
&mut self,
|
2019-06-14 15:27:05 +00:00
|
|
|
pos: comp::Pos,
|
2019-08-25 09:35:54 +00:00
|
|
|
stats: comp::Stats,
|
2019-05-25 21:13:38 +00:00
|
|
|
body: comp::Body,
|
|
|
|
) -> EcsEntityBuilder {
|
2019-04-16 21:06:33 +00:00
|
|
|
self.state
|
|
|
|
.ecs_mut()
|
|
|
|
.create_entity_synced()
|
2019-05-25 21:13:38 +00:00
|
|
|
.with(pos)
|
2019-06-14 15:27:05 +00:00
|
|
|
.with(comp::Vel(Vec3::zero()))
|
|
|
|
.with(comp::Ori(Vec3::unit_y()))
|
2019-06-09 14:20:20 +00:00
|
|
|
.with(comp::Controller::default())
|
2019-06-30 11:48:28 +00:00
|
|
|
.with(body)
|
2019-08-25 09:35:54 +00:00
|
|
|
.with(stats)
|
2019-08-23 10:11:37 +00:00
|
|
|
.with(comp::CharacterState::default())
|
2019-04-16 21:06:33 +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-07-25 20:51:20 +00:00
|
|
|
.with(comp::LightEmitter {
|
|
|
|
offset: Vec3::unit_z(),
|
|
|
|
..comp::LightEmitter::default()
|
|
|
|
})
|
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,
|
|
|
|
) -> EcsEntityBuilder {
|
|
|
|
state
|
|
|
|
.ecs_mut()
|
|
|
|
.create_entity_synced()
|
|
|
|
.with(pos)
|
|
|
|
.with(vel)
|
|
|
|
.with(comp::Ori(Vec3::unit_y()))
|
|
|
|
.with(body)
|
|
|
|
}
|
|
|
|
|
2019-04-23 09:53:45 +00:00
|
|
|
pub fn create_player_character(
|
|
|
|
state: &mut State,
|
|
|
|
entity: EcsEntity,
|
|
|
|
client: &mut Client,
|
2019-05-12 21:21:18 +00:00
|
|
|
name: String,
|
2019-05-15 16:06:58 +00:00
|
|
|
body: comp::Body,
|
2019-08-27 19:56:46 +00:00
|
|
|
main: Option<comp::Item>,
|
2019-08-12 14:05:58 +00:00
|
|
|
server_settings: &ServerSettings,
|
2019-04-23 09:53:45 +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);
|
2019-08-27 19:56:46 +00:00
|
|
|
state.write_component(entity, comp::Stats::new(name, main));
|
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-08-23 10:11:37 +00:00
|
|
|
state.write_component(entity, comp::CharacterState::default());
|
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)
|
|
|
|
.unwrap()
|
|
|
|
.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-05-19 22:20:25 +00:00
|
|
|
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
|
|
|
|
fn handle_events(&mut self) {
|
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;
|
|
|
|
let clients = &mut self.clients;
|
|
|
|
|
2019-08-23 20:50:35 +00:00
|
|
|
let mut todo_remove = None;
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
let ecs = state.ecs_mut();
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
ServerEvent::Shoot(entity) => {
|
|
|
|
let pos = state
|
|
|
|
.ecs()
|
|
|
|
.read_storage::<comp::Pos>()
|
|
|
|
.get(entity)
|
|
|
|
.unwrap()
|
|
|
|
.0;
|
|
|
|
Self::create_projectile(
|
|
|
|
state,
|
|
|
|
comp::Pos(pos),
|
|
|
|
comp::Vel(Vec3::new(0.0, 100.0, 3.0)),
|
|
|
|
comp::Body::Object(comp::object::Body::Bomb),
|
|
|
|
)
|
|
|
|
.build();
|
|
|
|
}
|
2019-08-23 10:11:37 +00:00
|
|
|
|
2019-08-25 12:27:17 +00:00
|
|
|
ServerEvent::Die { entity, cause } => {
|
|
|
|
let ecs = state.ecs_mut();
|
|
|
|
// Chat message
|
|
|
|
if let Some(player) = ecs.read_storage::<comp::Player>().get(entity) {
|
|
|
|
let msg = if let comp::HealthSource::Attack { by } = cause {
|
|
|
|
ecs.entity_from_uid(by.into()).and_then(|attacker| {
|
|
|
|
ecs.read_storage::<comp::Player>().get(attacker).map(
|
|
|
|
|attacker_alias| {
|
|
|
|
format!(
|
|
|
|
"{} was killed by {}",
|
|
|
|
&player.alias, &attacker_alias.alias
|
|
|
|
)
|
|
|
|
},
|
|
|
|
)
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
None
|
2019-08-23 20:50:35 +00:00
|
|
|
}
|
2019-08-25 12:27:17 +00:00
|
|
|
.unwrap_or(format!("{} died", &player.alias));
|
|
|
|
|
|
|
|
clients.notify_registered(ServerMsg::kill(msg));
|
|
|
|
}
|
2019-08-23 10:11:37 +00:00
|
|
|
|
2019-08-23 20:50:35 +00:00
|
|
|
// Give EXP to the client
|
|
|
|
let mut stats = ecs.write_storage::<comp::Stats>();
|
|
|
|
|
|
|
|
if let Some(entity_stats) = stats.get(entity).cloned() {
|
|
|
|
if let comp::HealthSource::Attack { by } = cause {
|
|
|
|
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.health.maximum() as f64 / 10.0
|
|
|
|
+ entity_stats.level.level() as f64 * 10.0)
|
|
|
|
as i64,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
2019-08-23 10:11:37 +00:00
|
|
|
}
|
|
|
|
}
|
2019-08-25 12:27:17 +00:00
|
|
|
|
|
|
|
if let Some(client) = clients.get_mut(&entity) {
|
|
|
|
let _ = ecs.write_storage().insert(entity, comp::Vel(Vec3::zero()));
|
|
|
|
let _ = ecs.write_storage().insert(entity, comp::ForceUpdate);
|
|
|
|
client.force_state(ClientState::Dead);
|
|
|
|
} else {
|
2019-08-23 20:50:35 +00:00
|
|
|
todo_remove = Some(entity.clone());
|
2019-08-23 10:11:37 +00:00
|
|
|
}
|
|
|
|
}
|
2019-08-23 20:50:35 +00:00
|
|
|
|
2019-08-25 12:27:17 +00:00
|
|
|
ServerEvent::Respawn(entity) => {
|
|
|
|
// Only clients can respawn
|
|
|
|
if let Some(client) = clients.get_mut(&entity) {
|
|
|
|
client.allow_state(ClientState::Character);
|
|
|
|
state
|
|
|
|
.ecs_mut()
|
|
|
|
.write_storage::<comp::Stats>()
|
|
|
|
.get_mut(entity)
|
|
|
|
.map(|stats| stats.revive());
|
|
|
|
state
|
|
|
|
.ecs_mut()
|
|
|
|
.write_storage::<comp::Pos>()
|
|
|
|
.get_mut(entity)
|
|
|
|
.map(|pos| pos.0.z += 20.0);
|
|
|
|
let _ = state
|
|
|
|
.ecs_mut()
|
|
|
|
.write_storage()
|
|
|
|
.insert(entity, comp::ForceUpdate);
|
|
|
|
}
|
|
|
|
}
|
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()
|
|
|
|
.write_storage::<comp::MountState>()
|
|
|
|
.get_mut(mountee)
|
|
|
|
.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()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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
|
|
|
|
.ecs_mut()
|
|
|
|
.write_storage::<comp::MountState>()
|
|
|
|
.get_mut(mountee_entity)
|
|
|
|
.map(|ms| *ms = comp::MountState::Unmounted);
|
|
|
|
}
|
|
|
|
state.delete_component::<comp::Mounting>(mounter);
|
|
|
|
}
|
2019-08-23 20:50:35 +00:00
|
|
|
}
|
2019-08-23 20:50:35 +00:00
|
|
|
|
|
|
|
if let Some(entity) = todo_remove {
|
|
|
|
let _ = state.ecs_mut().delete_entity_synced(entity);
|
|
|
|
}
|
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-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.
|
|
|
|
//
|
|
|
|
// 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
|
|
|
|
// 6) Send relevant state updates to all clients
|
2019-09-06 14:21:09 +00:00
|
|
|
// 7) Update Metrics with current data
|
|
|
|
// 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
|
|
|
frontend_events.append(&mut self.handle_new_messages()?);
|
|
|
|
|
2019-08-07 17:17:04 +00:00
|
|
|
// Handle game events
|
|
|
|
self.handle_events();
|
|
|
|
|
2019-09-07 13:10:57 +00:00
|
|
|
let before_tick_4 = Instant::now();
|
2019-05-17 20:47:58 +00:00
|
|
|
// 4) Tick the client's LocalState.
|
2019-01-02 17:23:31 +00:00
|
|
|
self.state.tick(dt);
|
|
|
|
|
2019-05-18 08:59:58 +00:00
|
|
|
// Tick the world
|
|
|
|
self.world.tick(dt);
|
|
|
|
|
2019-09-07 13:10:57 +00:00
|
|
|
let before_tick_5 = Instant::now();
|
2019-05-17 20:47:58 +00:00
|
|
|
// 5) Fetch any generated `TerrainChunk`s and insert them into the terrain.
|
2019-05-17 09:22:32 +00:00
|
|
|
// Also, send the chunk data to anybody that is close by.
|
2019-08-02 14:37:26 +00:00
|
|
|
if let Ok((key, (chunk, supplement))) = self.chunk_rx.try_recv() {
|
2019-05-17 09:22:32 +00:00
|
|
|
// Send the chunk to all nearby players.
|
2019-05-27 17:01:00 +00:00
|
|
|
for (entity, view_distance, pos) in (
|
2019-04-22 08:20:25 +00:00
|
|
|
&self.state.ecs().entities(),
|
|
|
|
&self.state.ecs().read_storage::<comp::Player>(),
|
2019-06-14 15:27:05 +00:00
|
|
|
&self.state.ecs().read_storage::<comp::Pos>(),
|
2019-04-29 20:37:19 +00:00
|
|
|
)
|
|
|
|
.join()
|
2019-05-27 17:01:00 +00:00
|
|
|
.filter_map(|(entity, player, pos)| {
|
|
|
|
player.view_distance.map(|vd| (entity, vd, pos))
|
|
|
|
})
|
2019-04-29 20:37:19 +00:00
|
|
|
{
|
2019-04-25 19:25:22 +00:00
|
|
|
let chunk_pos = self.state.terrain().pos_key(pos.0.map(|e| e as i32));
|
2019-06-23 19:49:15 +00:00
|
|
|
let adjusted_dist_sqr = (Vec2::from(chunk_pos) - Vec2::from(key))
|
|
|
|
.map(|e: i32| (e.abs() as u32).checked_sub(2).unwrap_or(0))
|
|
|
|
.magnitude_squared();
|
2019-04-25 19:25:22 +00:00
|
|
|
|
2019-06-23 19:49:15 +00:00
|
|
|
if adjusted_dist_sqr <= view_distance.pow(2) {
|
2019-04-29 20:37:19 +00:00
|
|
|
self.clients.notify(
|
|
|
|
entity,
|
|
|
|
ServerMsg::TerrainChunkUpdate {
|
|
|
|
key,
|
|
|
|
chunk: Box::new(chunk.clone()),
|
|
|
|
},
|
|
|
|
);
|
2019-04-25 19:25:22 +00:00
|
|
|
}
|
2019-04-10 23:16:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
self.state.insert_chunk(key, chunk);
|
2019-04-25 19:08:26 +00:00
|
|
|
self.pending_chunks.remove(&key);
|
2019-08-02 14:37:26 +00:00
|
|
|
|
|
|
|
// Handle chunk supplement
|
|
|
|
for npc in supplement.npcs {
|
2019-08-03 10:35:16 +00:00
|
|
|
let (mut stats, mut body) = if rand::random() {
|
2019-08-27 19:56:46 +00:00
|
|
|
let stats = comp::Stats::new(
|
|
|
|
"Humanoid".to_string(),
|
|
|
|
Some(comp::Item::Tool {
|
|
|
|
kind: comp::item::Tool::Sword,
|
|
|
|
power: 10,
|
|
|
|
}),
|
|
|
|
);
|
2019-08-03 10:35:16 +00:00
|
|
|
let body = comp::Body::Humanoid(comp::humanoid::Body::random());
|
|
|
|
(stats, body)
|
|
|
|
} else {
|
2019-08-27 19:56:46 +00:00
|
|
|
let stats = comp::Stats::new("Wolf".to_string(), None);
|
2019-08-03 10:35:16 +00:00
|
|
|
let body = comp::Body::QuadrupedMedium(comp::quadruped_medium::Body::random());
|
|
|
|
(stats, body)
|
|
|
|
};
|
2019-08-02 18:56:37 +00:00
|
|
|
let mut scale = 1.0;
|
|
|
|
|
|
|
|
if npc.boss {
|
2019-08-02 20:31:22 +00:00
|
|
|
if rand::random::<f32>() < 0.8 {
|
2019-08-27 19:56:46 +00:00
|
|
|
stats = comp::Stats::new(
|
|
|
|
"Humanoid".to_string(),
|
|
|
|
Some(comp::Item::Tool {
|
|
|
|
kind: comp::item::Tool::Sword,
|
|
|
|
power: 10,
|
|
|
|
}),
|
|
|
|
);
|
2019-08-02 20:31:22 +00:00
|
|
|
body = comp::Body::Humanoid(comp::humanoid::Body::random());
|
|
|
|
}
|
2019-08-04 09:32:50 +00:00
|
|
|
stats = stats.with_max_health(500 + rand::random::<u32>() % 400);
|
|
|
|
scale = 2.5 + rand::random::<f32>();
|
2019-08-02 18:56:37 +00:00
|
|
|
}
|
|
|
|
|
2019-08-25 09:35:54 +00:00
|
|
|
self.create_npc(comp::Pos(npc.pos), stats, body)
|
2019-08-02 18:56:37 +00:00
|
|
|
.with(comp::Agent::enemy())
|
|
|
|
.with(comp::Scale(scale))
|
2019-08-02 14:37:26 +00:00
|
|
|
.build();
|
|
|
|
}
|
2019-04-25 19:08:26 +00:00
|
|
|
}
|
|
|
|
|
2019-07-01 13:38:45 +00:00
|
|
|
fn chunk_in_vd(
|
|
|
|
player_pos: Vec3<f32>,
|
|
|
|
chunk_pos: Vec2<i32>,
|
common: Rework volume API
See the doc comments in `common/src/vol.rs` for more information on
the API itself.
The changes include:
* Consistent `Err`/`Error` naming.
* Types are named `...Error`.
* `enum` variants are named `...Err`.
* Rename `VolMap{2d, 3d}` -> `VolGrid{2d, 3d}`. This is in preparation
to an upcoming change where a “map” in the game related sense will
be added.
* Add volume iterators. There are two types of them:
* _Position_ iterators obtained from the trait `IntoPosIterator`
using the method
`fn pos_iter(self, lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> ...`
which returns an iterator over `Vec3<i32>`.
* _Volume_ iterators obtained from the trait `IntoVolIterator`
using the method
`fn vol_iter(self, lower_bound: Vec3<i32>, upper_bound: Vec3<i32>) -> ...`
which returns an iterator over `(Vec3<i32>, &Self::Vox)`.
Those traits will usually be implemented by references to volume
types (i.e. `impl IntoVolIterator<'a> for &'a T` where `T` is some
type which usually implements several volume traits, such as `Chunk`).
* _Position_ iterators iterate over the positions valid for that
volume.
* _Volume_ iterators do the same but return not only the position
but also the voxel at that position, in each iteration.
* Introduce trait `RectSizedVol` for the use case which we have with
`Chonk`: A `Chonk` is sized only in x and y direction.
* Introduce traits `RasterableVol`, `RectRasterableVol`
* `RasterableVol` represents a volume that is compile-time sized and has
its lower bound at `(0, 0, 0)`. The name `RasterableVol` was chosen
because such a volume can be used with `VolGrid3d`.
* `RectRasterableVol` represents a volume that is compile-time sized at
least in x and y direction and has its lower bound at `(0, 0, z)`.
There's no requirement on he lower bound or size in z direction.
The name `RectRasterableVol` was chosen because such a volume can be
used with `VolGrid2d`.
2019-09-03 22:23:29 +00:00
|
|
|
terrain: &TerrainGrid,
|
2019-07-01 13:38:45 +00:00
|
|
|
vd: u32,
|
|
|
|
) -> bool {
|
2019-07-01 13:36:45 +00:00
|
|
|
let player_chunk_pos = terrain.pos_key(player_pos.map(|e| e as i32));
|
|
|
|
|
|
|
|
let adjusted_dist_sqr = Vec2::from(player_chunk_pos - chunk_pos)
|
|
|
|
.map(|e: i32| (e.abs() as u32).checked_sub(2).unwrap_or(0))
|
|
|
|
.magnitude_squared();
|
|
|
|
|
|
|
|
adjusted_dist_sqr <= vd.pow(2)
|
|
|
|
}
|
|
|
|
|
2019-05-17 09:22:32 +00:00
|
|
|
// Remove chunks that are too far from players.
|
2019-04-25 19:08:26 +00:00
|
|
|
let mut chunks_to_remove = Vec::new();
|
2019-07-01 13:36:45 +00:00
|
|
|
self.state.terrain().iter().for_each(|(chunk_key, _)| {
|
2019-05-19 00:45:02 +00:00
|
|
|
let mut should_drop = true;
|
2019-04-25 19:08:26 +00:00
|
|
|
|
2019-05-17 09:22:32 +00:00
|
|
|
// For each player with a position, calculate the distance.
|
2019-05-19 00:45:02 +00:00
|
|
|
for (player, pos) in (
|
2019-04-25 19:08:26 +00:00
|
|
|
&self.state.ecs().read_storage::<comp::Player>(),
|
2019-06-14 15:27:05 +00:00
|
|
|
&self.state.ecs().read_storage::<comp::Pos>(),
|
2019-04-29 20:37:19 +00:00
|
|
|
)
|
|
|
|
.join()
|
|
|
|
{
|
2019-06-05 16:32:33 +00:00
|
|
|
if player
|
|
|
|
.view_distance
|
2019-07-01 13:36:45 +00:00
|
|
|
.map(|vd| chunk_in_vd(pos.0, chunk_key, &self.state.terrain(), vd))
|
2019-06-05 16:32:33 +00:00
|
|
|
.unwrap_or(false)
|
|
|
|
{
|
2019-05-19 00:45:02 +00:00
|
|
|
should_drop = false;
|
2019-05-26 20:13:42 +00:00
|
|
|
break;
|
2019-05-19 00:45:02 +00:00
|
|
|
}
|
2019-04-25 19:08:26 +00:00
|
|
|
}
|
|
|
|
|
2019-05-19 00:45:02 +00:00
|
|
|
if should_drop {
|
2019-07-01 13:36:45 +00:00
|
|
|
chunks_to_remove.push(chunk_key);
|
2019-04-25 19:08:26 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
for key in chunks_to_remove {
|
|
|
|
self.state.remove_chunk(key);
|
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-03-04 19:50:26 +00:00
|
|
|
self.sync_clients();
|
|
|
|
|
2019-07-01 13:36:45 +00:00
|
|
|
// Sync changed chunks
|
2019-07-20 15:37:01 +00:00
|
|
|
'chunk: for chunk_key in &self.state.terrain_changes().modified_chunks {
|
2019-07-01 13:36:45 +00:00
|
|
|
let terrain = self.state.terrain();
|
|
|
|
|
|
|
|
for (entity, player, pos) in (
|
|
|
|
&self.state.ecs().entities(),
|
|
|
|
&self.state.ecs().read_storage::<comp::Player>(),
|
|
|
|
&self.state.ecs().read_storage::<comp::Pos>(),
|
|
|
|
)
|
|
|
|
.join()
|
|
|
|
{
|
|
|
|
if player
|
|
|
|
.view_distance
|
|
|
|
.map(|vd| chunk_in_vd(pos.0, *chunk_key, &terrain, vd))
|
|
|
|
.unwrap_or(false)
|
|
|
|
{
|
|
|
|
self.clients.notify(
|
|
|
|
entity,
|
|
|
|
ServerMsg::TerrainChunkUpdate {
|
|
|
|
key: *chunk_key,
|
|
|
|
chunk: Box::new(match self.state.terrain().get_key(*chunk_key) {
|
|
|
|
Some(chunk) => chunk.clone(),
|
|
|
|
None => break 'chunk,
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-08-02 17:48:14 +00:00
|
|
|
|
2019-07-20 15:37:01 +00:00
|
|
|
// Sync changed blocks
|
|
|
|
let msg =
|
|
|
|
ServerMsg::TerrainBlockUpdates(self.state.terrain_changes().modified_blocks.clone());
|
|
|
|
for (entity, player) in (
|
|
|
|
&self.state.ecs().entities(),
|
|
|
|
&self.state.ecs().read_storage::<comp::Player>(),
|
|
|
|
)
|
|
|
|
.join()
|
|
|
|
{
|
|
|
|
if player.view_distance.is_some() {
|
|
|
|
self.clients.notify(entity, msg.clone());
|
|
|
|
}
|
|
|
|
}
|
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
|
|
|
|
let to_delete = {
|
|
|
|
let terrain = self.state.terrain();
|
|
|
|
(
|
|
|
|
&self.state.ecs().entities(),
|
|
|
|
&self.state.ecs().read_storage::<comp::Pos>(),
|
|
|
|
&self.state.ecs().read_storage::<comp::Agent>(),
|
|
|
|
)
|
|
|
|
.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 {
|
|
|
|
let _ = self.state.ecs_mut().delete_entity(entity);
|
|
|
|
}
|
|
|
|
|
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-09-07 13:10:57 +00:00
|
|
|
self.metrics
|
|
|
|
.tick_time
|
|
|
|
.with_label_values(&["input"])
|
|
|
|
.set((before_tick_4 - before_tick_1).as_nanos() as i64);
|
|
|
|
self.metrics
|
|
|
|
.tick_time
|
|
|
|
.with_label_values(&["world"])
|
|
|
|
.set((before_tick_5 - before_tick_4).as_nanos() as i64);
|
|
|
|
self.metrics
|
|
|
|
.tick_time
|
|
|
|
.with_label_values(&["terrain"])
|
|
|
|
.set((before_tick_6 - before_tick_5).as_nanos() as i64);
|
|
|
|
self.metrics
|
|
|
|
.tick_time
|
|
|
|
.with_label_values(&["sync"])
|
|
|
|
.set((before_tick_7 - before_tick_6).as_nanos() as i64);
|
|
|
|
self.metrics.player_online.set(self.clients.len() 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-16 13:06:30 +00:00
|
|
|
let entity = self.state.ecs_mut().create_entity_synced().build();
|
2019-04-21 18:12:29 +00:00
|
|
|
let mut client = Client {
|
|
|
|
client_state: ClientState::Connected,
|
|
|
|
postbox,
|
|
|
|
last_ping: self.state.get_time(),
|
|
|
|
};
|
|
|
|
|
2019-07-01 11:19:26 +00:00
|
|
|
if self.server_settings.max_players <= self.clients.len() {
|
2019-07-04 16:14:45 +00:00
|
|
|
client.notify(ServerMsg::Error(ServerError::TooManyPlayers));
|
2019-07-01 11:19:26 +00:00
|
|
|
} else {
|
|
|
|
// Return the state of the current world (all of the components that Sphynx tracks).
|
|
|
|
client.notify(ServerMsg::InitialSync {
|
|
|
|
ecs_state: self.state.ecs().gen_state_package(),
|
|
|
|
entity_uid: self.state.ecs().uid_from_entity(entity).unwrap().into(), // Can't fail.
|
|
|
|
server_info: self.server_info.clone(),
|
|
|
|
});
|
|
|
|
|
|
|
|
frontend_events.push(Event::ClientConnected { entity });
|
|
|
|
}
|
2019-03-05 00:00:11 +00:00
|
|
|
|
2019-04-23 09:53:45 +00:00
|
|
|
self.clients.add(entity, client);
|
2019-03-03 22:02:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(frontend_events)
|
|
|
|
}
|
|
|
|
|
2019-05-17 09:22:32 +00:00
|
|
|
/// Handle new client 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-08-08 03:56:02 +00:00
|
|
|
let accounts = &mut self.accounts;
|
2019-08-12 14:05:58 +00:00
|
|
|
let server_settings = &self.server_settings;
|
2019-08-08 03:56:02 +00:00
|
|
|
|
2019-03-03 22:02:38 +00:00
|
|
|
let state = &mut self.state;
|
|
|
|
let mut new_chat_msgs = Vec::new();
|
2019-03-05 00:00:11 +00:00
|
|
|
let mut disconnected_clients = Vec::new();
|
2019-04-10 23:41:37 +00:00
|
|
|
let mut requested_chunks = Vec::new();
|
2019-07-20 15:37:01 +00:00
|
|
|
let mut modified_blocks = Vec::new();
|
2019-07-26 21:01:41 +00:00
|
|
|
let mut dropped_items = Vec::new();
|
2019-03-03 22:02:38 +00:00
|
|
|
|
2019-04-10 23:16:29 +00:00
|
|
|
self.clients.remove_if(|entity, client| {
|
2019-04-10 17:23:27 +00:00
|
|
|
let mut disconnect = false;
|
2019-03-03 22:02:38 +00:00
|
|
|
let new_msgs = client.postbox.new_messages();
|
|
|
|
|
2019-05-17 09:22:32 +00:00
|
|
|
// Update client ping.
|
2019-03-03 22:02:38 +00:00
|
|
|
if new_msgs.len() > 0 {
|
|
|
|
client.last_ping = state.get_time();
|
|
|
|
|
2019-05-17 09:22:32 +00:00
|
|
|
// Process incoming messages.
|
2019-03-03 22:02:38 +00:00
|
|
|
for msg in new_msgs {
|
2019-04-19 19:32:47 +00:00
|
|
|
match msg {
|
|
|
|
ClientMsg::RequestState(requested_state) => match requested_state {
|
2019-04-21 18:12:29 +00:00
|
|
|
ClientState::Connected => disconnect = true, // Default state
|
2019-04-21 15:52:15 +00:00
|
|
|
ClientState::Registered => match client.client_state {
|
2019-05-17 09:22:32 +00:00
|
|
|
// Use ClientMsg::Register instead.
|
2019-04-29 20:37:19 +00:00
|
|
|
ClientState::Connected => {
|
|
|
|
client.error_state(RequestStateError::WrongMessage)
|
|
|
|
}
|
|
|
|
ClientState::Registered => {
|
|
|
|
client.error_state(RequestStateError::Already)
|
|
|
|
}
|
2019-05-23 15:14:39 +00:00
|
|
|
ClientState::Spectator
|
|
|
|
| ClientState::Character
|
|
|
|
| ClientState::Dead => client.allow_state(ClientState::Registered),
|
2019-05-24 19:10:18 +00:00
|
|
|
ClientState::Pending => {}
|
2019-04-20 17:54:37 +00:00
|
|
|
},
|
|
|
|
ClientState::Spectator => match requested_state {
|
2019-05-17 09:22:32 +00:00
|
|
|
// Become Registered first.
|
2019-04-29 20:37:19 +00:00
|
|
|
ClientState::Connected => {
|
|
|
|
client.error_state(RequestStateError::Impossible)
|
|
|
|
}
|
|
|
|
ClientState::Spectator => {
|
|
|
|
client.error_state(RequestStateError::Already)
|
|
|
|
}
|
2019-05-23 15:14:39 +00:00
|
|
|
ClientState::Registered
|
|
|
|
| ClientState::Character
|
|
|
|
| ClientState::Dead => client.allow_state(ClientState::Spectator),
|
2019-05-24 19:10:18 +00:00
|
|
|
ClientState::Pending => {}
|
2019-04-20 17:54:37 +00:00
|
|
|
},
|
2019-05-17 09:22:32 +00:00
|
|
|
// Use ClientMsg::Character instead.
|
2019-04-29 20:37:19 +00:00
|
|
|
ClientState::Character => {
|
|
|
|
client.error_state(RequestStateError::WrongMessage)
|
|
|
|
}
|
2019-05-23 15:14:39 +00:00
|
|
|
ClientState::Dead => client.error_state(RequestStateError::Impossible),
|
2019-05-24 19:10:18 +00:00
|
|
|
ClientState::Pending => {}
|
2019-04-19 19:32:47 +00:00
|
|
|
},
|
2019-06-29 15:04:06 +00:00
|
|
|
// Valid player
|
2019-08-08 03:56:02 +00:00
|
|
|
ClientMsg::Register { player, password } if player.is_valid() => {
|
2019-08-08 22:24:14 +00:00
|
|
|
if !accounts.query(player.alias.clone(), password) {
|
|
|
|
client.error_state(RequestStateError::Denied);
|
|
|
|
break;
|
2019-08-08 03:56:02 +00:00
|
|
|
}
|
2019-06-29 15:48:34 +00:00
|
|
|
match client.client_state {
|
|
|
|
ClientState::Connected => {
|
|
|
|
Self::initialize_player(state, entity, client, player);
|
2019-05-27 17:45:43 +00:00
|
|
|
}
|
2019-06-29 15:48:34 +00:00
|
|
|
// Use RequestState instead (No need to send `player` again).
|
|
|
|
_ => client.error_state(RequestStateError::Impossible),
|
2019-04-29 20:37:19 +00:00
|
|
|
}
|
2019-08-08 15:23:58 +00:00
|
|
|
//client.allow_state(ClientState::Registered);
|
2019-06-29 15:48:34 +00:00
|
|
|
}
|
2019-06-29 15:04:06 +00:00
|
|
|
// Invalid player
|
2019-07-01 16:38:19 +00:00
|
|
|
ClientMsg::Register { .. } => {
|
2019-06-29 15:48:34 +00:00
|
|
|
client.error_state(RequestStateError::Impossible)
|
|
|
|
}
|
2019-05-19 00:45:02 +00:00
|
|
|
ClientMsg::SetViewDistance(view_distance) => match client.client_state {
|
|
|
|
ClientState::Character { .. } => {
|
|
|
|
state
|
|
|
|
.ecs_mut()
|
|
|
|
.write_storage::<comp::Player>()
|
|
|
|
.get_mut(entity)
|
|
|
|
.map(|player| player.view_distance = Some(view_distance));
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
},
|
2019-08-30 20:46:45 +00:00
|
|
|
ClientMsg::UseInventorySlot(x) => {
|
2019-08-30 19:54:33 +00:00
|
|
|
let item = state
|
2019-08-28 20:47:52 +00:00
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::Inventory>()
|
|
|
|
.get_mut(entity)
|
|
|
|
.and_then(|inv| inv.remove(x));
|
|
|
|
|
2019-08-30 19:54:33 +00:00
|
|
|
match item {
|
|
|
|
Some(comp::Item::Tool { .. }) | Some(comp::Item::Debug(_)) => {
|
|
|
|
if let Some(stats) =
|
|
|
|
state.ecs().write_storage::<comp::Stats>().get_mut(entity)
|
|
|
|
{
|
2019-08-30 22:09:25 +00:00
|
|
|
// 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(x, old_item));
|
|
|
|
}
|
2019-08-30 19:54:33 +00:00
|
|
|
|
|
|
|
stats.equipment.main = item;
|
2019-08-28 20:47:52 +00:00
|
|
|
}
|
|
|
|
}
|
2019-08-30 19:54:33 +00:00
|
|
|
_ => {}
|
2019-08-28 20:47:52 +00:00
|
|
|
}
|
2019-08-30 19:54:33 +00:00
|
|
|
state.write_component(entity, comp::InventoryUpdate);
|
2019-08-28 20:47:52 +00:00
|
|
|
}
|
2019-07-25 22:52:28 +00:00
|
|
|
ClientMsg::SwapInventorySlots(a, b) => {
|
|
|
|
state
|
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::Inventory>()
|
|
|
|
.get_mut(entity)
|
|
|
|
.map(|inv| inv.swap_slots(a, b));
|
|
|
|
state.write_component(entity, comp::InventoryUpdate);
|
|
|
|
}
|
2019-07-26 17:08:40 +00:00
|
|
|
ClientMsg::DropInventorySlot(x) => {
|
2019-07-26 21:01:41 +00:00
|
|
|
let item = state
|
2019-07-26 17:08:40 +00:00
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::Inventory>()
|
|
|
|
.get_mut(entity)
|
2019-07-26 21:01:41 +00:00
|
|
|
.and_then(|inv| inv.remove(x));
|
|
|
|
|
2019-07-26 17:08:40 +00:00
|
|
|
state.write_component(entity, comp::InventoryUpdate);
|
2019-07-26 21:01:41 +00:00
|
|
|
|
2019-07-29 16:19:08 +00:00
|
|
|
if let (Some(item), Some(pos)) =
|
|
|
|
(item, state.ecs().read_storage::<comp::Pos>().get(entity))
|
2019-07-26 21:01:41 +00:00
|
|
|
{
|
2019-07-29 16:19:08 +00:00
|
|
|
dropped_items.push((
|
|
|
|
*pos,
|
|
|
|
state
|
|
|
|
.ecs()
|
|
|
|
.read_storage::<comp::Ori>()
|
|
|
|
.get(entity)
|
|
|
|
.copied()
|
|
|
|
.unwrap_or(comp::Ori(Vec3::unit_y())),
|
|
|
|
item,
|
|
|
|
));
|
2019-07-26 21:01:41 +00:00
|
|
|
}
|
2019-07-26 17:08:40 +00:00
|
|
|
}
|
2019-07-29 16:19:08 +00:00
|
|
|
ClientMsg::PickUp(uid) => {
|
|
|
|
let item_entity = state.ecs_mut().entity_from_uid(uid);
|
|
|
|
|
|
|
|
let ecs = state.ecs_mut();
|
|
|
|
|
|
|
|
let item_entity = if let (Some((item, item_entity)), Some(inv)) = (
|
|
|
|
item_entity.and_then(|item_entity| {
|
|
|
|
ecs.write_storage::<comp::Item>()
|
|
|
|
.get_mut(item_entity)
|
2019-08-27 19:56:46 +00:00
|
|
|
.map(|item| (item.clone(), item_entity))
|
2019-07-29 16:19:08 +00:00
|
|
|
}),
|
|
|
|
ecs.write_storage::<comp::Inventory>().get_mut(entity),
|
|
|
|
) {
|
2019-08-28 20:47:52 +00:00
|
|
|
if inv.push(item).is_none() {
|
2019-07-29 16:19:08 +00:00
|
|
|
Some(item_entity)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(item_entity) = item_entity {
|
|
|
|
let _ = ecs.delete_entity_synced(item_entity);
|
|
|
|
}
|
|
|
|
|
|
|
|
state.write_component(entity, comp::InventoryUpdate);
|
|
|
|
}
|
2019-08-27 19:56:46 +00:00
|
|
|
ClientMsg::Character { name, body, main } => match client.client_state {
|
2019-05-17 09:22:32 +00:00
|
|
|
// Become Registered first.
|
2019-04-29 20:37:19 +00:00
|
|
|
ClientState::Connected => {
|
|
|
|
client.error_state(RequestStateError::Impossible)
|
|
|
|
}
|
2019-05-23 15:14:39 +00:00
|
|
|
ClientState::Registered
|
|
|
|
| ClientState::Spectator
|
|
|
|
| ClientState::Dead => {
|
2019-08-12 14:05:58 +00:00
|
|
|
Self::create_player_character(
|
|
|
|
state,
|
|
|
|
entity,
|
|
|
|
client,
|
|
|
|
name,
|
|
|
|
body,
|
2019-08-27 19:56:46 +00:00
|
|
|
main.map(|t| comp::Item::Tool { kind: t, power: 10 }),
|
2019-08-12 14:05:58 +00:00
|
|
|
&server_settings,
|
|
|
|
);
|
2019-07-05 20:16:06 +00:00
|
|
|
if let Some(player) =
|
|
|
|
state.ecs().read_storage::<comp::Player>().get(entity)
|
|
|
|
{
|
2019-07-21 18:34:52 +00:00
|
|
|
new_chat_msgs.push((
|
|
|
|
None,
|
2019-07-29 14:40:46 +00:00
|
|
|
ServerMsg::broadcast(format!(
|
|
|
|
"[{}] is now online.",
|
|
|
|
&player.alias
|
|
|
|
)),
|
2019-07-21 18:34:52 +00:00
|
|
|
));
|
2019-07-05 20:16:06 +00:00
|
|
|
}
|
2019-04-29 20:37:19 +00:00
|
|
|
}
|
|
|
|
ClientState::Character => {
|
|
|
|
client.error_state(RequestStateError::Already)
|
|
|
|
}
|
2019-05-24 19:10:18 +00:00
|
|
|
ClientState::Pending => {}
|
2019-04-19 19:32:47 +00:00
|
|
|
},
|
2019-06-09 14:20:20 +00:00
|
|
|
ClientMsg::Controller(controller) => match client.client_state {
|
|
|
|
ClientState::Connected
|
|
|
|
| ClientState::Registered
|
|
|
|
| ClientState::Spectator => {
|
|
|
|
client.error_state(RequestStateError::Impossible)
|
2019-05-25 21:13:38 +00:00
|
|
|
}
|
2019-06-09 14:20:20 +00:00
|
|
|
ClientState::Dead | ClientState::Character => {
|
|
|
|
state.write_component(entity, controller);
|
2019-05-25 21:13:38 +00:00
|
|
|
}
|
2019-06-09 14:20:20 +00:00
|
|
|
ClientState::Pending => {}
|
2019-05-25 21:13:38 +00:00
|
|
|
},
|
2019-07-21 18:34:52 +00:00
|
|
|
ClientMsg::ChatMsg { chat_type, message } => match client.client_state {
|
2019-04-29 20:37:19 +00:00
|
|
|
ClientState::Connected => {
|
|
|
|
client.error_state(RequestStateError::Impossible)
|
|
|
|
}
|
2019-04-23 09:53:45 +00:00
|
|
|
ClientState::Registered
|
|
|
|
| ClientState::Spectator
|
2019-05-23 15:14:39 +00:00
|
|
|
| ClientState::Dead
|
2019-07-21 18:34:52 +00:00
|
|
|
| ClientState::Character => new_chat_msgs
|
|
|
|
.push((Some(entity), ServerMsg::ChatMsg { chat_type, message })),
|
2019-05-24 19:10:18 +00:00
|
|
|
ClientState::Pending => {}
|
2019-04-19 19:32:47 +00:00
|
|
|
},
|
2019-05-31 18:45:16 +00:00
|
|
|
ClientMsg::PlayerPhysics { pos, vel, ori } => match client.client_state {
|
2019-04-19 19:32:47 +00:00
|
|
|
ClientState::Character => {
|
2019-04-10 23:16:29 +00:00
|
|
|
state.write_component(entity, pos);
|
|
|
|
state.write_component(entity, vel);
|
2019-05-31 18:45:16 +00:00
|
|
|
state.write_component(entity, ori);
|
2019-04-23 09:53:45 +00:00
|
|
|
}
|
2019-05-17 09:22:32 +00:00
|
|
|
// Only characters can send positions.
|
2019-04-22 14:25:37 +00:00
|
|
|
_ => client.error_state(RequestStateError::Impossible),
|
2019-04-19 19:32:47 +00:00
|
|
|
},
|
2019-07-02 18:19:16 +00:00
|
|
|
ClientMsg::BreakBlock(pos) => {
|
|
|
|
if state
|
|
|
|
.ecs_mut()
|
|
|
|
.read_storage::<comp::CanBuild>()
|
|
|
|
.get(entity)
|
|
|
|
.is_some()
|
|
|
|
{
|
2019-07-20 15:37:01 +00:00
|
|
|
modified_blocks.push((pos, Block::empty()));
|
2019-07-02 18:19:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
ClientMsg::PlaceBlock(pos, block) => {
|
|
|
|
if state
|
|
|
|
.ecs_mut()
|
|
|
|
.read_storage::<comp::CanBuild>()
|
|
|
|
.get(entity)
|
|
|
|
.is_some()
|
|
|
|
{
|
2019-07-20 15:37:01 +00:00
|
|
|
modified_blocks.push((pos, block));
|
2019-07-02 18:19:16 +00:00
|
|
|
}
|
|
|
|
}
|
2019-04-19 19:32:47 +00:00
|
|
|
ClientMsg::TerrainChunkRequest { key } => match client.client_state {
|
2019-05-23 15:14:39 +00:00
|
|
|
ClientState::Connected
|
|
|
|
| ClientState::Registered
|
|
|
|
| ClientState::Dead => {
|
2019-04-22 14:25:37 +00:00
|
|
|
client.error_state(RequestStateError::Impossible);
|
|
|
|
}
|
2019-04-19 19:32:47 +00:00
|
|
|
ClientState::Spectator | ClientState::Character => {
|
2019-04-16 13:06:30 +00:00
|
|
|
match state.terrain().get_key(key) {
|
2019-04-29 20:37:19 +00:00
|
|
|
Some(chunk) => {
|
|
|
|
client.postbox.send_message(ServerMsg::TerrainChunkUpdate {
|
|
|
|
key,
|
|
|
|
chunk: Box::new(chunk.clone()),
|
|
|
|
})
|
|
|
|
}
|
2019-04-16 13:06:30 +00:00
|
|
|
None => requested_chunks.push(key),
|
|
|
|
}
|
2019-04-23 09:53:45 +00:00
|
|
|
}
|
2019-05-24 19:10:18 +00:00
|
|
|
ClientState::Pending => {}
|
2019-04-23 09:53:45 +00:00
|
|
|
},
|
2019-05-17 09:22:32 +00:00
|
|
|
// Always possible.
|
2019-04-20 17:54:37 +00:00
|
|
|
ClientMsg::Ping => client.postbox.send_message(ServerMsg::Pong),
|
2019-04-23 09:53:45 +00:00
|
|
|
ClientMsg::Pong => {}
|
2019-05-27 17:45:43 +00:00
|
|
|
ClientMsg::Disconnect => {
|
|
|
|
disconnect = true;
|
|
|
|
}
|
2019-03-03 22:02:38 +00:00
|
|
|
}
|
|
|
|
}
|
2019-04-16 13:06:30 +00:00
|
|
|
} else if state.get_time() - client.last_ping > CLIENT_TIMEOUT || // Timeout
|
|
|
|
client.postbox.error().is_some()
|
|
|
|
// Postbox error
|
2019-03-03 22:02:38 +00:00
|
|
|
{
|
2019-04-10 17:23:27 +00:00
|
|
|
disconnect = true;
|
2019-03-06 13:51:01 +00:00
|
|
|
} else if state.get_time() - client.last_ping > CLIENT_TIMEOUT * 0.5 {
|
2019-05-17 09:22:32 +00:00
|
|
|
// Try pinging the client if the timeout is nearing.
|
2019-04-11 22:26:43 +00:00
|
|
|
client.postbox.send_message(ServerMsg::Ping);
|
2019-03-03 22:02:38 +00:00
|
|
|
}
|
|
|
|
|
2019-04-10 17:23:27 +00:00
|
|
|
if disconnect {
|
2019-05-27 17:45:43 +00:00
|
|
|
if let Some(player) = state.ecs().read_storage::<comp::Player>().get(entity) {
|
2019-07-21 18:34:52 +00:00
|
|
|
new_chat_msgs.push((
|
|
|
|
None,
|
2019-07-29 14:40:46 +00:00
|
|
|
ServerMsg::broadcast(format!("{} went offline.", &player.alias)),
|
2019-07-21 18:34:52 +00:00
|
|
|
));
|
2019-05-27 17:45:43 +00:00
|
|
|
}
|
2019-04-10 23:16:29 +00:00
|
|
|
disconnected_clients.push(entity);
|
2019-04-23 12:01:16 +00:00
|
|
|
client.postbox.send_message(ServerMsg::Disconnect);
|
2019-03-03 22:02:38 +00:00
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2019-05-17 09:22:32 +00:00
|
|
|
// Handle new chat messages.
|
2019-04-10 17:23:27 +00:00
|
|
|
for (entity, msg) in new_chat_msgs {
|
2019-07-21 18:34:52 +00:00
|
|
|
match msg {
|
|
|
|
ServerMsg::ChatMsg { chat_type, message } => {
|
|
|
|
if let Some(entity) = entity {
|
|
|
|
// Handle chat commands.
|
|
|
|
if message.starts_with("/") && message.len() > 1 {
|
|
|
|
let argv = String::from(&message[1..]);
|
|
|
|
self.process_chat_cmd(entity, argv);
|
|
|
|
} else {
|
|
|
|
let message =
|
|
|
|
match self.state.ecs().read_storage::<comp::Player>().get(entity) {
|
2019-08-14 15:51:59 +00:00
|
|
|
Some(player) => {
|
|
|
|
if self.entity_is_admin(entity) {
|
2019-08-12 16:11:06 +00:00
|
|
|
format!("[ADMIN][{}] {}", &player.alias, message)
|
2019-08-14 15:51:59 +00:00
|
|
|
} else {
|
|
|
|
format!("[{}] {}", &player.alias, message)
|
2019-08-12 16:11:06 +00:00
|
|
|
}
|
2019-08-14 15:51:59 +00:00
|
|
|
}
|
2019-07-29 14:40:46 +00:00
|
|
|
None => format!("[<Unknown>] {}", message),
|
2019-07-21 18:34:52 +00:00
|
|
|
};
|
|
|
|
self.clients
|
|
|
|
.notify_registered(ServerMsg::ChatMsg { chat_type, message });
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
self.clients
|
|
|
|
.notify_registered(ServerMsg::ChatMsg { chat_type, message });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
panic!("Invalid message type.");
|
2019-05-27 17:45:43 +00:00
|
|
|
}
|
2019-04-16 13:06:30 +00:00
|
|
|
}
|
2019-03-03 22:02:38 +00:00
|
|
|
}
|
|
|
|
|
2019-05-17 09:22:32 +00:00
|
|
|
// Handle client disconnects.
|
2019-04-10 17:23:27 +00:00
|
|
|
for entity in disconnected_clients {
|
2019-06-06 14:48:41 +00:00
|
|
|
if let Err(err) = self.state.ecs_mut().delete_entity_synced(entity) {
|
2019-06-29 20:40:40 +00:00
|
|
|
debug!("Failed to delete disconnected client: {:?}", err);
|
2019-06-06 14:48:41 +00:00
|
|
|
}
|
2019-03-05 00:00:11 +00:00
|
|
|
|
2019-04-16 13:06:30 +00:00
|
|
|
frontend_events.push(Event::ClientDisconnected { entity });
|
2019-03-05 00:00:11 +00:00
|
|
|
}
|
|
|
|
|
2019-05-17 09:22:32 +00:00
|
|
|
// Generate requested chunks.
|
2019-04-10 23:41:37 +00:00
|
|
|
for key in requested_chunks {
|
|
|
|
self.generate_chunk(key);
|
|
|
|
}
|
|
|
|
|
2019-07-20 15:37:01 +00:00
|
|
|
for (pos, block) in modified_blocks {
|
|
|
|
self.state.set_block(pos, block);
|
|
|
|
}
|
|
|
|
|
2019-07-29 16:19:08 +00:00
|
|
|
for (pos, ori, item) in dropped_items {
|
2019-07-29 17:23:26 +00:00
|
|
|
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;
|
2019-07-29 16:19:08 +00:00
|
|
|
self.create_object(Default::default(), comp::object::Body::Pouch)
|
2019-07-29 17:23:26 +00:00
|
|
|
.with(comp::Pos(pos.0 + Vec3::unit_z() * 0.25))
|
2019-07-26 21:01:41 +00:00
|
|
|
.with(item)
|
2019-07-29 17:23:26 +00:00
|
|
|
.with(comp::Vel(vel))
|
2019-07-26 21:01:41 +00:00
|
|
|
.build();
|
|
|
|
}
|
|
|
|
|
2019-03-03 22:02:38 +00:00
|
|
|
Ok(frontend_events)
|
|
|
|
}
|
2019-03-04 19:50:26 +00:00
|
|
|
|
2019-05-17 09:22:32 +00:00
|
|
|
/// Initialize a new client states with important information.
|
2019-04-21 18:12:29 +00:00
|
|
|
fn initialize_player(
|
2019-04-17 19:22:34 +00:00
|
|
|
state: &mut State,
|
|
|
|
entity: specs::Entity,
|
|
|
|
client: &mut Client,
|
|
|
|
player: comp::Player,
|
|
|
|
) {
|
2019-05-17 09:22:32 +00:00
|
|
|
// Save player metadata (for example the username).
|
2019-04-17 19:22:34 +00:00
|
|
|
state.write_component(entity, player);
|
|
|
|
|
2019-07-30 11:35:16 +00:00
|
|
|
// Sync physics of all entities
|
2019-08-23 10:11:37 +00:00
|
|
|
for (&uid, &pos, vel, ori, character_state) in (
|
2019-05-26 14:04:44 +00:00
|
|
|
&state.ecs().read_storage::<Uid>(),
|
2019-07-30 11:35:16 +00:00
|
|
|
&state.ecs().read_storage::<comp::Pos>(), // We assume all these entities have a position
|
2019-07-25 20:51:20 +00:00
|
|
|
state.ecs().read_storage::<comp::Vel>().maybe(),
|
|
|
|
state.ecs().read_storage::<comp::Ori>().maybe(),
|
2019-08-23 10:11:37 +00:00
|
|
|
state.ecs().read_storage::<comp::CharacterState>().maybe(),
|
2019-05-26 14:04:44 +00:00
|
|
|
)
|
|
|
|
.join()
|
|
|
|
{
|
2019-07-30 11:35:16 +00:00
|
|
|
client.notify(ServerMsg::EntityPos {
|
2019-05-26 14:04:44 +00:00
|
|
|
entity: uid.into(),
|
|
|
|
pos,
|
2019-04-17 20:26:11 +00:00
|
|
|
});
|
2019-07-30 11:35:16 +00:00
|
|
|
if let Some(vel) = vel.copied() {
|
|
|
|
client.notify(ServerMsg::EntityVel {
|
|
|
|
entity: uid.into(),
|
|
|
|
vel,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if let Some(ori) = ori.copied() {
|
|
|
|
client.notify(ServerMsg::EntityOri {
|
|
|
|
entity: uid.into(),
|
|
|
|
ori,
|
|
|
|
});
|
|
|
|
}
|
2019-08-23 10:11:37 +00:00
|
|
|
if let Some(character_state) = character_state.copied() {
|
|
|
|
client.notify(ServerMsg::EntityCharacterState {
|
2019-07-30 11:35:16 +00:00
|
|
|
entity: uid.into(),
|
2019-08-23 10:11:37 +00:00
|
|
|
character_state,
|
2019-07-30 11:35:16 +00:00
|
|
|
});
|
|
|
|
}
|
2019-04-17 20:26:11 +00:00
|
|
|
}
|
2019-04-19 19:32:47 +00:00
|
|
|
|
2019-05-17 09:22:32 +00:00
|
|
|
// Tell the client its request was successful.
|
2019-04-21 15:52:15 +00:00
|
|
|
client.allow_state(ClientState::Registered);
|
2019-04-17 19:22:34 +00:00
|
|
|
}
|
|
|
|
|
2019-05-17 09:22:32 +00:00
|
|
|
/// Sync client states with the most up to date information.
|
2019-03-04 19:50:26 +00:00
|
|
|
fn sync_clients(&mut self) {
|
2019-05-17 09:22:32 +00:00
|
|
|
// Sync 'logical' state using Sphynx.
|
2019-04-23 09:53:45 +00:00
|
|
|
self.clients
|
|
|
|
.notify_registered(ServerMsg::EcsSync(self.state.ecs_mut().next_sync_package()));
|
2019-04-14 20:30:27 +00:00
|
|
|
|
2019-07-30 05:24:36 +00:00
|
|
|
let ecs = self.state.ecs_mut();
|
2019-06-30 20:25:37 +00:00
|
|
|
|
|
|
|
// Sync physics
|
2019-07-30 11:35:16 +00:00
|
|
|
for (entity, &uid, &pos, force_update) in (
|
2019-07-30 05:24:36 +00:00
|
|
|
&ecs.entities(),
|
|
|
|
&ecs.read_storage::<Uid>(),
|
|
|
|
&ecs.read_storage::<comp::Pos>(),
|
|
|
|
ecs.read_storage::<comp::ForceUpdate>().maybe(),
|
2019-06-30 20:25:37 +00:00
|
|
|
)
|
|
|
|
.join()
|
|
|
|
{
|
|
|
|
let clients = &mut self.clients;
|
|
|
|
|
2019-07-30 11:35:16 +00:00
|
|
|
let in_vd = |entity| {
|
2019-07-30 08:49:41 +00:00
|
|
|
if let (Some(client_pos), Some(client_vd)) = (
|
2019-07-30 05:24:36 +00:00
|
|
|
ecs.read_storage::<comp::Pos>().get(entity),
|
|
|
|
ecs.read_storage::<comp::Player>()
|
|
|
|
.get(entity)
|
|
|
|
.map(|pl| pl.view_distance)
|
|
|
|
.and_then(|v| v),
|
|
|
|
) {
|
|
|
|
{
|
2019-07-30 08:49:41 +00:00
|
|
|
// Check if the entity is in the client's range
|
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
|
|
|
Vec2::from(pos.0 - client_pos.0)
|
|
|
|
.map2(TerrainChunkSize::RECT_SIZE, |d: f32, sz| {
|
2019-07-30 08:49:41 +00:00
|
|
|
(d.abs() as u32 / sz).checked_sub(2).unwrap_or(0)
|
|
|
|
})
|
|
|
|
.magnitude_squared()
|
|
|
|
< client_vd.pow(2)
|
2019-07-30 05:24:36 +00:00
|
|
|
}
|
2019-07-30 08:49:41 +00:00
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
};
|
2019-07-30 05:24:36 +00:00
|
|
|
|
2019-07-30 08:49:41 +00:00
|
|
|
let mut last_pos = ecs.write_storage::<comp::Last<comp::Pos>>();
|
|
|
|
let mut last_vel = ecs.write_storage::<comp::Last<comp::Vel>>();
|
|
|
|
let mut last_ori = ecs.write_storage::<comp::Last<comp::Ori>>();
|
2019-08-23 10:11:37 +00:00
|
|
|
let mut last_character_state = ecs.write_storage::<comp::Last<comp::CharacterState>>();
|
2019-07-30 08:49:41 +00:00
|
|
|
|
2019-07-31 18:08:15 +00:00
|
|
|
if let Some(client_pos) = ecs.read_storage::<comp::Pos>().get(entity) {
|
2019-07-30 08:49:41 +00:00
|
|
|
if last_pos
|
|
|
|
.get(entity)
|
2019-08-28 12:46:20 +00:00
|
|
|
.map(|&l| l.0 != *client_pos)
|
2019-07-30 08:49:41 +00:00
|
|
|
.unwrap_or(true)
|
|
|
|
{
|
2019-07-30 05:24:36 +00:00
|
|
|
let _ = last_pos.insert(entity, comp::Last(*client_pos));
|
2019-07-30 11:35:16 +00:00
|
|
|
let msg = ServerMsg::EntityPos {
|
|
|
|
entity: uid.into(),
|
|
|
|
pos: *client_pos,
|
|
|
|
};
|
|
|
|
match force_update {
|
|
|
|
Some(_) => clients.notify_ingame_if(msg, in_vd),
|
|
|
|
None => clients.notify_ingame_if_except(entity, msg, in_vd),
|
|
|
|
}
|
|
|
|
}
|
2019-07-31 18:08:15 +00:00
|
|
|
}
|
2019-07-30 11:35:16 +00:00
|
|
|
|
2019-07-31 18:08:15 +00:00
|
|
|
if let Some(client_vel) = ecs.read_storage::<comp::Vel>().get(entity) {
|
2019-07-30 11:35:16 +00:00
|
|
|
if last_vel
|
|
|
|
.get(entity)
|
2019-08-28 12:46:20 +00:00
|
|
|
.map(|&l| l.0 != *client_vel)
|
2019-07-30 11:35:16 +00:00
|
|
|
.unwrap_or(true)
|
|
|
|
{
|
2019-07-30 05:24:36 +00:00
|
|
|
let _ = last_vel.insert(entity, comp::Last(*client_vel));
|
2019-07-30 11:35:16 +00:00
|
|
|
let msg = ServerMsg::EntityVel {
|
|
|
|
entity: uid.into(),
|
|
|
|
vel: *client_vel,
|
|
|
|
};
|
|
|
|
match force_update {
|
|
|
|
Some(_) => clients.notify_ingame_if(msg, in_vd),
|
|
|
|
None => clients.notify_ingame_if_except(entity, msg, in_vd),
|
|
|
|
}
|
|
|
|
}
|
2019-07-31 18:08:15 +00:00
|
|
|
}
|
2019-07-30 11:35:16 +00:00
|
|
|
|
2019-07-31 18:08:15 +00:00
|
|
|
if let Some(client_ori) = ecs.read_storage::<comp::Ori>().get(entity) {
|
2019-07-30 11:35:16 +00:00
|
|
|
if last_ori
|
|
|
|
.get(entity)
|
2019-08-28 12:46:20 +00:00
|
|
|
.map(|&l| l.0 != *client_ori)
|
2019-07-30 11:35:16 +00:00
|
|
|
.unwrap_or(true)
|
|
|
|
{
|
2019-07-30 05:24:36 +00:00
|
|
|
let _ = last_ori.insert(entity, comp::Last(*client_ori));
|
2019-07-30 11:35:16 +00:00
|
|
|
let msg = ServerMsg::EntityOri {
|
|
|
|
entity: uid.into(),
|
|
|
|
ori: *client_ori,
|
|
|
|
};
|
|
|
|
match force_update {
|
|
|
|
Some(_) => clients.notify_ingame_if(msg, in_vd),
|
|
|
|
None => clients.notify_ingame_if_except(entity, msg, in_vd),
|
|
|
|
}
|
|
|
|
}
|
2019-07-31 18:08:15 +00:00
|
|
|
}
|
2019-07-30 11:35:16 +00:00
|
|
|
|
2019-08-23 10:11:37 +00:00
|
|
|
if let Some(client_character_state) =
|
|
|
|
ecs.read_storage::<comp::CharacterState>().get(entity)
|
|
|
|
{
|
|
|
|
if last_character_state
|
2019-07-30 11:35:16 +00:00
|
|
|
.get(entity)
|
2019-08-30 18:40:22 +00:00
|
|
|
.map(|&l| !client_character_state.is_same_state(&l.0))
|
2019-07-30 11:35:16 +00:00
|
|
|
.unwrap_or(true)
|
|
|
|
{
|
2019-08-23 10:11:37 +00:00
|
|
|
let _ =
|
|
|
|
last_character_state.insert(entity, comp::Last(*client_character_state));
|
|
|
|
let msg = ServerMsg::EntityCharacterState {
|
2019-07-30 11:35:16 +00:00
|
|
|
entity: uid.into(),
|
2019-08-23 10:11:37 +00:00
|
|
|
character_state: *client_character_state,
|
2019-07-30 11:35:16 +00:00
|
|
|
};
|
2019-07-30 08:49:41 +00:00
|
|
|
match force_update {
|
2019-07-30 11:35:16 +00:00
|
|
|
Some(_) => clients.notify_ingame_if(msg, in_vd),
|
|
|
|
None => clients.notify_ingame_if_except(entity, msg, in_vd),
|
2019-07-30 08:49:41 +00:00
|
|
|
}
|
2019-07-30 05:24:36 +00:00
|
|
|
}
|
2019-04-17 17:32:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-25 14:48:27 +00:00
|
|
|
// Sync inventories
|
|
|
|
for (entity, inventory, _) in (
|
|
|
|
&self.state.ecs().entities(),
|
|
|
|
&self.state.ecs().read_storage::<comp::Inventory>(),
|
|
|
|
&self.state.ecs().read_storage::<comp::InventoryUpdate>(),
|
|
|
|
)
|
|
|
|
.join()
|
|
|
|
{
|
|
|
|
self.clients
|
|
|
|
.notify(entity, ServerMsg::InventoryUpdate(inventory.clone()));
|
|
|
|
}
|
|
|
|
|
2019-05-17 09:22:32 +00:00
|
|
|
// Remove all force flags.
|
2019-04-29 20:37:19 +00:00
|
|
|
self.state
|
|
|
|
.ecs_mut()
|
2019-06-14 15:27:05 +00:00
|
|
|
.write_storage::<comp::ForceUpdate>()
|
2019-04-29 20:37:19 +00:00
|
|
|
.clear();
|
2019-07-25 14:48:27 +00:00
|
|
|
self.state
|
|
|
|
.ecs_mut()
|
|
|
|
.write_storage::<comp::InventoryUpdate>()
|
|
|
|
.clear();
|
2019-03-04 19:50:26 +00:00
|
|
|
}
|
2019-04-10 23:41:37 +00:00
|
|
|
|
2019-05-17 17:44:30 +00:00
|
|
|
pub fn generate_chunk(&mut self, key: Vec2<i32>) {
|
2019-04-10 23:41:37 +00:00
|
|
|
if self.pending_chunks.insert(key) {
|
|
|
|
let chunk_tx = self.chunk_tx.clone();
|
2019-05-16 17:40:32 +00:00
|
|
|
let world = self.world.clone();
|
2019-05-18 16:35:53 +00:00
|
|
|
self.thread_pool.execute(move || {
|
2019-05-27 22:07:39 +00:00
|
|
|
let _ = chunk_tx.send((key, world.generate_chunk(key)));
|
2019-05-18 16:35:53 +00:00
|
|
|
});
|
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 => {
|
|
|
|
self.clients.notify(
|
|
|
|
entity,
|
2019-08-01 17:53:34 +00:00
|
|
|
ServerMsg::private(format!(
|
2019-08-18 18:07:21 +00:00
|
|
|
"Unknown command '/{}'.\nType '/help' for available commands",
|
2019-04-16 13:06:30 +00:00
|
|
|
kwd
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
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-04-22 00:38:29 +00:00
|
|
|
self.clients.notify_registered(ServerMsg::Shutdown);
|
2019-03-05 18:39:18 +00:00
|
|
|
}
|
|
|
|
}
|