Move serverside client to a component and communications into server ecs systems

This commit is contained in:
Imbris 2019-10-15 00:06:14 -04:00 committed by Imbris
parent c02cd0a730
commit 2703c8afe1
21 changed files with 1272 additions and 1225 deletions

1
Cargo.lock generated
View File

@ -3626,7 +3626,6 @@ dependencies = [
"crossbeam 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"hibitset 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
"indexmap 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"portpicker 0.1.0 (git+https://github.com/wusyong/portpicker-rs?branch=fix_ipv6)",

View File

@ -8,7 +8,7 @@ pub use crate::error::Error;
pub use specs::{join::Join, saveload::Marker, Entity as EcsEntity, ReadStorage};
use common::{
comp,
comp::{self, ControlEvent, Controller, ControllerInputs, InventoryManip},
msg::{
validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, RequestStateError,
ServerError, ServerInfo, ServerMsg, MAX_BYTES_CHAT_MSG,
@ -201,22 +201,33 @@ impl Client {
// Can't fail
}
pub fn use_inventory_slot(&mut self, x: usize) {
self.postbox.send_message(ClientMsg::UseInventorySlot(x))
pub fn use_inventory_slot(&mut self, slot: usize) {
self.postbox
.send_message(ClientMsg::ControlEvent(ControlEvent::InventoryManip(
InventoryManip::Use(slot),
)));
}
pub fn swap_inventory_slots(&mut self, a: usize, b: usize) {
self.postbox
.send_message(ClientMsg::SwapInventorySlots(a, b))
.send_message(ClientMsg::ControlEvent(ControlEvent::InventoryManip(
InventoryManip::Swap(a, b),
)));
}
pub fn drop_inventory_slot(&mut self, x: usize) {
self.postbox.send_message(ClientMsg::DropInventorySlot(x))
pub fn drop_inventory_slot(&mut self, slot: usize) {
self.postbox
.send_message(ClientMsg::ControlEvent(ControlEvent::InventoryManip(
InventoryManip::Drop(slot),
)));
}
pub fn pick_up(&mut self, entity: EcsEntity) {
if let Some(uid) = self.state.ecs().read_storage::<Uid>().get(entity).copied() {
self.postbox.send_message(ClientMsg::PickUp(uid.id()));
self.postbox
.send_message(ClientMsg::ControlEvent(ControlEvent::InventoryManip(
InventoryManip::Pickup(uid),
)));
}
}
@ -228,6 +239,18 @@ impl Client {
.is_some()
}
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));
}
pub fn view_distance(&self) -> Option<u32> {
self.view_distance
}
@ -283,16 +306,15 @@ impl Client {
}
pub fn collect_block(&mut self, pos: Vec3<i32>) {
self.postbox.send_message(ClientMsg::CollectBlock(pos));
self.postbox
.send_message(ClientMsg::ControlEvent(ControlEvent::InventoryManip(
InventoryManip::Collect(pos),
)));
}
/// Execute a single client tick, handle input and update the game state by the given duration.
#[allow(dead_code)]
pub fn tick(
&mut self,
controller: comp::Controller,
dt: Duration,
) -> Result<Vec<Event>, Error> {
pub fn tick(&mut self, inputs: ControllerInputs, dt: Duration) -> Result<Vec<Event>, Error> {
// 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
@ -310,8 +332,15 @@ impl Client {
// 1) Handle input from frontend.
// Pass character actions from frontend input to the player's entity.
if let ClientState::Character | ClientState::Dead = self.client_state {
self.state.write_component(self.entity, controller.clone());
self.postbox.send_message(ClientMsg::Controller(controller));
self.state.write_component(
self.entity,
Controller {
inputs: inputs.clone(),
events: Vec::new(),
},
);
self.postbox
.send_message(ClientMsg::ControllerInputs(inputs));
}
// 2) Build up a list of events for this frame, to be passed to the frontend.
@ -319,7 +348,7 @@ impl Client {
// Prepare for new events
{
let ecs = self.state.ecs_mut();
let ecs = self.state.ecs();
for (entity, _) in (&ecs.entities(), &ecs.read_storage::<comp::Body>()).join() {
let mut last_character_states =
ecs.write_storage::<comp::Last<comp::CharacterState>>();
@ -343,7 +372,7 @@ impl Client {
// 3) Update client local data
// 4) Tick the client's LocalState
self.state.tick(dt);
self.state.tick(dt, |_| {});
// 5) Terrain
let pos = self

View File

@ -7,14 +7,14 @@ use vek::*;
pub enum ControlEvent {
Mount(Uid),
Unmount,
InventoryManip(InventoryManip),
//Respawn,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Controller {
pub struct ControllerInputs {
pub primary: bool,
pub secondary: bool,
pub move_dir: Vec2<f32>,
pub look_dir: Vec3<f32>,
pub sit: bool,
pub jump: bool,
pub roll: bool,
@ -23,6 +23,13 @@ pub struct Controller {
pub climb_down: bool,
pub wall_leap: bool,
pub respawn: bool,
pub move_dir: Vec2<f32>,
pub look_dir: Vec3<f32>,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Controller {
pub inputs: ControllerInputs,
pub events: Vec<ControlEvent>,
}
@ -60,3 +67,12 @@ pub struct Mounting(pub Uid);
impl Component for Mounting {
type Storage = FlaggedStorage<Self, IDVStorage<Self>>;
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum InventoryManip {
Pickup(Uid),
Collect(Vec3<i32>),
Use(usize),
Swap(usize, usize),
Drop(usize),
}

View File

@ -18,7 +18,9 @@ pub use admin::Admin;
pub use agent::Agent;
pub use body::{humanoid, object, quadruped, quadruped_medium, Body};
pub use character_state::{ActionState, CharacterState, MovementState};
pub use controller::{ControlEvent, Controller, MountState, Mounting};
pub use controller::{
ControlEvent, Controller, ControllerInputs, InventoryManip, MountState, Mounting,
};
pub use inputs::CanBuild;
pub use inventory::{item, Inventory, InventoryUpdate, Item};
pub use last::Last;

View File

@ -30,6 +30,7 @@ pub enum ServerEvent {
entity: EcsEntity,
cause: comp::HealthSource,
},
InventoryManip(EcsEntity, comp::InventoryManip),
Respawn(EcsEntity),
Shoot {
entity: EcsEntity,
@ -46,6 +47,15 @@ pub enum ServerEvent {
Mount(EcsEntity, EcsEntity),
Unmount(EcsEntity),
Possess(Uid, Uid),
CreatePlayer {
entity: EcsEntity,
name: String,
body: comp::Body,
main: Option<comp::Item>,
},
ClientDisconnect(EcsEntity),
ChunkRequest(EcsEntity, Vec2<i32>),
ChatCmd(EcsEntity, String),
}
pub struct EventBus<E> {

View File

@ -14,12 +14,12 @@ pub enum ClientMsg {
body: comp::Body,
main: Option<comp::item::Tool>,
},
Controller(comp::Controller),
ControllerInputs(comp::ControllerInputs),
ControlEvent(comp::ControlEvent),
RequestState(ClientState),
SetViewDistance(u32),
BreakBlock(Vec3<i32>),
PlaceBlock(Vec3<i32>, Block),
CollectBlock(Vec3<i32>),
Ping,
Pong,
ChatMsg {
@ -31,10 +31,6 @@ pub enum ClientMsg {
vel: comp::Vel,
ori: comp::Ori,
},
UseInventorySlot(usize),
SwapInventorySlots(usize, usize),
DropInventorySlot(usize),
PickUp(u64),
TerrainChunkRequest {
key: Vec2<i32>,
},

View File

@ -195,9 +195,6 @@ impl RegionMap {
}
}
}
pub fn add(&mut self, entity: EcsEntity, pos: Vec3<f32>) {
self.add_entity(entity.id(), pos.map(|e| e as i32), None);
}
fn add_entity(&mut self, id: u32, pos: Vec3<i32>, from: Option<Vec2<i32>>) {
let key = Self::pos_key(pos);
if let Some(region) = self.regions.get_mut(&key) {

View File

@ -309,7 +309,7 @@ impl State {
}
/// Execute a single tick, simulating the game state by the given duration.
pub fn tick(&mut self, dt: Duration) {
pub fn tick(&mut self, dt: Duration, add_foreign_systems: impl Fn(&mut DispatcherBuilder)) {
// Change the time accordingly.
self.ecs.write_resource::<TimeOfDay>().0 += dt.as_secs_f64() * DAY_CYCLE_FACTOR;
self.ecs.write_resource::<Time>().0 += dt.as_secs_f64();
@ -380,22 +380,24 @@ impl State {
self.ecs.write_storage::<comp::Mounting>().remove(entity);
}
// Run systems to update the world.
// Create and run a dispatcher for ecs systems.
let mut dispatch_builder = DispatcherBuilder::new().with_pool(self.thread_pool.clone());
sys::add_local_systems(&mut dispatch_builder);
// This dispatches all the systems in parallel.
dispatch_builder.build().dispatch(&self.ecs.res);
self.ecs.maintain();
// Run RegionMap tick to update enitity region occupancy
// Run RegionMap tick to update entity region occupancy
self.ecs.write_resource::<RegionMap>().tick(
self.ecs.read_storage::<comp::Pos>(),
self.ecs.read_storage::<comp::Vel>(),
self.ecs.entities(),
);
// Run systems to update the world.
// Create and run a dispatcher for ecs systems.
let mut dispatch_builder = DispatcherBuilder::new().with_pool(self.thread_pool.clone());
sys::add_local_systems(&mut dispatch_builder);
// TODO: Consider alternative ways to do this
add_foreign_systems(&mut dispatch_builder);
// This dispatches all the systems in parallel.
dispatch_builder.build().dispatch(&self.ecs.res);
self.ecs.maintain();
// Apply terrain changes
let mut terrain = self.ecs.write_resource::<TerrainGrid>();
self.ecs

View File

@ -47,6 +47,8 @@ impl<'a> System<'a> for Sys {
controller.reset();
let mut inputs = &mut controller.inputs;
match agent {
Agent::Wanderer(bearing) => {
*bearing += Vec2::new(rand::random::<f32>() - 0.5, rand::random::<f32>() - 0.5)
@ -55,7 +57,7 @@ impl<'a> System<'a> for Sys {
- pos.0 * 0.0002;
if bearing.magnitude_squared() > 0.001 {
controller.move_dir = bearing.normalized();
inputs.move_dir = bearing.normalized();
}
}
Agent::Pet { target, offset } => {
@ -65,12 +67,12 @@ impl<'a> System<'a> for Sys {
let tgt_pos = tgt_pos.0 + *offset;
if tgt_pos.z > pos.0.z + 1.0 {
controller.jump = true;
inputs.jump = true;
}
// Move towards the target.
let dist: f32 = Vec2::from(tgt_pos - pos.0).magnitude();
controller.move_dir = if dist > 5.0 {
inputs.move_dir = if dist > 5.0 {
Vec2::from(tgt_pos - pos.0).normalized()
} else if dist < 1.5 && dist > 0.001 {
Vec2::from(pos.0 - tgt_pos).normalized()
@ -78,7 +80,7 @@ impl<'a> System<'a> for Sys {
Vec2::zero()
};
}
_ => controller.move_dir = Vec2::zero(),
_ => inputs.move_dir = Vec2::zero(),
}
// Change offset occasionally.
@ -102,28 +104,28 @@ impl<'a> System<'a> for Sys {
)
})
{
controller.look_dir = target_pos.0 - pos.0;
inputs.look_dir = target_pos.0 - pos.0;
let dist = Vec2::<f32>::from(target_pos.0 - pos.0).magnitude();
if target_stats.is_dead {
choose_new = true;
} else if dist < MIN_ATTACK_DIST && dist > 0.001 {
// Fight (and slowly move closer)
controller.move_dir =
inputs.move_dir =
Vec2::<f32>::from(target_pos.0 - pos.0).normalized() * 0.01;
controller.primary = true;
inputs.primary = true;
} else if dist < SIGHT_DIST {
controller.move_dir =
inputs.move_dir =
Vec2::<f32>::from(target_pos.0 - pos.0).normalized() * 0.96;
if rand::random::<f32>() < 0.02 {
controller.roll = true;
inputs.roll = true;
}
if target_character.movement == Glide && target_pos.0.z > pos.0.z + 5.0
{
controller.glide = true;
controller.jump = true;
inputs.glide = true;
inputs.jump = true;
}
} else {
choose_new = true;
@ -134,7 +136,7 @@ impl<'a> System<'a> for Sys {
* 0.1
- *bearing * 0.005;
controller.move_dir = if bearing.magnitude_squared() > 0.001 {
inputs.move_dir = if bearing.magnitude_squared() > 0.001 {
bearing.normalized()
} else {
Vec2::zero()

View File

@ -65,48 +65,50 @@ impl<'a> System<'a> for Sys {
)
.join()
{
let mut inputs = &mut controller.inputs;
if stats.is_dead {
// Respawn
if controller.respawn {
if inputs.respawn {
server_emitter.emit(ServerEvent::Respawn(entity));
}
continue;
}
// Move
controller.move_dir = if controller.move_dir.magnitude_squared() > 1.0 {
controller.move_dir.normalized()
inputs.move_dir = if inputs.move_dir.magnitude_squared() > 1.0 {
inputs.move_dir.normalized()
} else {
controller.move_dir
inputs.move_dir
};
if character.movement == Stand && controller.move_dir.magnitude_squared() > 0.0 {
if character.movement == Stand && inputs.move_dir.magnitude_squared() > 0.0 {
character.movement = Run;
} else if character.movement == Run && controller.move_dir.magnitude_squared() == 0.0 {
} else if character.movement == Run && inputs.move_dir.magnitude_squared() == 0.0 {
character.movement = Stand;
}
// Look
controller.look_dir = controller
inputs.look_dir = inputs
.look_dir
.try_normalized()
.unwrap_or(controller.move_dir.into());
.unwrap_or(inputs.move_dir.into());
// Glide
// TODO: Check for glide ability/item
if controller.glide
if inputs.glide
&& !physics.on_ground
&& (character.action == Idle || character.action.is_wield())
&& character.movement == Jump
&& body.is_humanoid()
{
character.movement = Glide;
} else if !controller.glide && character.movement == Glide {
} else if !inputs.glide && character.movement == Glide {
character.movement = Jump;
}
// Sit
if controller.sit
if inputs.sit
&& physics.on_ground
&& character.action == Idle
&& character.movement != Sit
@ -114,13 +116,13 @@ impl<'a> System<'a> for Sys {
{
character.movement = Sit;
} else if character.movement == Sit
&& (controller.move_dir.magnitude_squared() > 0.0 || !physics.on_ground)
&& (inputs.move_dir.magnitude_squared() > 0.0 || !physics.on_ground)
{
character.movement = Run;
}
// Wield
if controller.primary
if inputs.primary
&& character.action == Idle
&& (character.movement == Stand || character.movement == Run)
{
@ -135,7 +137,7 @@ impl<'a> System<'a> for Sys {
power,
..
}) => {
if controller.primary
if inputs.primary
&& (character.movement == Stand
|| character.movement == Run
|| character.movement == Jump)
@ -146,7 +148,7 @@ impl<'a> System<'a> for Sys {
character.action = Idle;
server_emitter.emit(ServerEvent::Shoot {
entity,
dir: controller.look_dir,
dir: inputs.look_dir,
body: comp::Body::Object(comp::object::Body::Arrow),
light: None,
gravity: Some(comp::Gravity(0.3)),
@ -174,7 +176,7 @@ impl<'a> System<'a> for Sys {
..
}) => {
// Melee Attack
if controller.primary
if inputs.primary
&& (character.movement == Stand
|| character.movement == Run
|| character.movement == Jump)
@ -189,7 +191,7 @@ impl<'a> System<'a> for Sys {
}
}
// Magical Bolt
if controller.secondary
if inputs.secondary
&& (
character.movement == Stand
//|| character.movement == Run
@ -204,7 +206,7 @@ impl<'a> System<'a> for Sys {
};
server_emitter.emit(ServerEvent::Shoot {
entity,
dir: controller.look_dir,
dir: inputs.look_dir,
body: comp::Body::Object(comp::object::Body::BoltFire),
gravity: Some(comp::Gravity(0.0)),
light: Some(comp::LightEmitter {
@ -232,7 +234,7 @@ impl<'a> System<'a> for Sys {
}
Some(Item::Tool { .. }) => {
// Melee Attack
if controller.primary
if inputs.primary
&& (character.movement == Stand
|| character.movement == Run
|| character.movement == Jump)
@ -248,27 +250,27 @@ impl<'a> System<'a> for Sys {
}
// Block
if controller.secondary
if inputs.secondary
&& (character.movement == Stand || character.movement == Run)
&& character.action.is_wield()
{
character.action = Block {
time_left: Duration::from_secs(5),
};
} else if !controller.secondary && character.action.is_block() {
} else if !inputs.secondary && character.action.is_block() {
character.action = Wield {
time_left: Duration::default(),
};
}
}
Some(Item::Debug(item::Debug::Boost)) => {
if controller.primary {
if inputs.primary {
local_emitter.emit(LocalEvent::Boost {
entity,
vel: controller.look_dir * 7.0,
vel: inputs.look_dir * 7.0,
});
}
if controller.secondary {
if inputs.secondary {
// Go upward
local_emitter.emit(LocalEvent::Boost {
entity,
@ -277,7 +279,7 @@ impl<'a> System<'a> for Sys {
}
}
Some(Item::Debug(item::Debug::Possess)) => {
if controller.primary
if inputs.primary
&& (character.movement == Stand
|| character.movement == Run
|| character.movement == Jump)
@ -289,7 +291,7 @@ impl<'a> System<'a> for Sys {
server_emitter.emit(ServerEvent::Shoot {
entity,
gravity: Some(comp::Gravity(0.1)),
dir: controller.look_dir,
dir: inputs.look_dir,
body: comp::Body::Object(comp::object::Body::ArrowSnake),
light: Some(comp::LightEmitter {
col: (0.0, 1.0, 0.3).into(),
@ -310,14 +312,14 @@ impl<'a> System<'a> for Sys {
}
}
// Block
if controller.secondary
if inputs.secondary
&& (character.movement == Stand || character.movement == Run)
&& character.action.is_wield()
{
character.action = Block {
time_left: Duration::from_secs(5),
};
} else if !controller.secondary && character.action.is_block() {
} else if !inputs.secondary && character.action.is_block() {
character.action = Wield {
time_left: Duration::default(),
};
@ -325,7 +327,7 @@ impl<'a> System<'a> for Sys {
}
None => {
// Attack
if controller.primary
if inputs.primary
&& (character.movement == Stand
|| character.movement == Run
|| character.movement == Jump)
@ -341,7 +343,7 @@ impl<'a> System<'a> for Sys {
}
// Roll
if controller.roll
if inputs.roll
&& (character.action == Idle || character.action.is_wield())
&& character.movement == Run
&& physics.on_ground
@ -352,16 +354,12 @@ impl<'a> System<'a> for Sys {
}
// Jump
if controller.jump
&& physics.on_ground
&& vel.0.z <= 0.0
&& !character.movement.is_roll()
{
if inputs.jump && physics.on_ground && vel.0.z <= 0.0 && !character.movement.is_roll() {
local_emitter.emit(LocalEvent::Jump(entity));
}
// Wall leap
if controller.wall_leap {
if inputs.wall_leap {
if let (Some(_wall_dir), Climb) = (physics.on_wall, character.movement) {
//local_emitter.emit(LocalEvent::WallLeap { entity, wall_dir });
}
@ -378,6 +376,9 @@ impl<'a> System<'a> for Sys {
}
}
ControlEvent::Unmount => server_emitter.emit(ServerEvent::Unmount(entity)),
ControlEvent::InventoryManip(manip) => {
server_emitter.emit(ServerEvent::InventoryManip(entity, manip))
} //ControlEvent::Respawn => server_emitter.emit(ServerEvent::Unmount(entity)),
}
}
}

View File

@ -97,11 +97,13 @@ impl<'a> System<'a> for Sys {
continue;
}
let inputs = &controller.inputs;
if character.movement.is_roll() {
vel.0 = Vec3::new(0.0, 0.0, vel.0.z)
+ (vel.0 * Vec3::new(1.0, 1.0, 0.0)
+ 1.5
* controller
* inputs
.move_dir
.try_normalized()
.unwrap_or(Vec2::from(vel.0).try_normalized().unwrap_or_default()))
@ -110,7 +112,7 @@ impl<'a> System<'a> for Sys {
}
if character.action.is_block() || character.action.is_attack() {
vel.0 += Vec2::broadcast(dt.0)
* controller.move_dir
* inputs.move_dir
* match physics.on_ground {
true if vel.0.magnitude_squared() < BLOCK_SPEED.powf(2.0) => BLOCK_ACCEL,
_ => 0.0,
@ -118,7 +120,7 @@ impl<'a> System<'a> for Sys {
} else {
// Move player according to move_dir
vel.0 += Vec2::broadcast(dt.0)
* controller.move_dir
* inputs.move_dir
* match (physics.on_ground, &character.movement) {
(true, Run) if vel.0.magnitude_squared() < HUMANOID_SPEED.powf(2.0) => {
HUMANOID_ACCEL
@ -148,7 +150,7 @@ impl<'a> System<'a> for Sys {
|| character.action.is_attack()
|| character.action.is_block()
{
Vec2::from(controller.look_dir).normalized()
Vec2::from(inputs.look_dir).normalized()
} else if let (Climb, Some(wall_dir)) = (character.movement, physics.on_wall) {
if Vec2::<f32>::from(wall_dir).magnitude_squared() > 0.001 {
Vec2::from(wall_dir).normalized()
@ -198,12 +200,12 @@ impl<'a> System<'a> for Sys {
// Climb
if let (true, Some(_wall_dir)) = (
(controller.climb | controller.climb_down) && vel.0.z <= CLIMB_SPEED,
(inputs.climb | inputs.climb_down) && vel.0.z <= CLIMB_SPEED,
physics.on_wall,
) {
if controller.climb_down && !controller.climb {
if inputs.climb_down && !inputs.climb {
vel.0 -= dt.0 * vel.0.map(|e| e.abs().powf(1.5) * e.signum() * 6.0);
} else if controller.climb && !controller.climb_down {
} else if inputs.climb && !inputs.climb_down {
vel.0.z = (vel.0.z + dt.0 * GRAVITY * 1.25).min(CLIMB_SPEED);
} else {
vel.0.z = vel.0.z + dt.0 * GRAVITY * 1.5;

View File

@ -27,6 +27,5 @@ prometheus = "0.7"
prometheus-static-metric = "0.2"
rouille = "3.0.0"
portpicker = { git = "https://github.com/wusyong/portpicker-rs", branch = "fix_ipv6" }
indexmap = "1.2.0"
# TODO: remove when upgrading to specs 0.15
hibitset = "0.5.3"

View File

@ -2,9 +2,7 @@ use common::{
msg::{ClientMsg, ClientState, RequestStateError, ServerMsg},
net::PostBox,
};
use hashbrown::{hash_map::DefaultHashBuilder, HashSet};
use indexmap::IndexMap;
use specs::Entity as EcsEntity;
use hashbrown::HashSet;
use specs::{Component, FlaggedStorage};
use specs_idvs::IDVStorage;
use vek::*;
@ -15,10 +13,29 @@ pub struct Client {
pub last_ping: f64,
}
impl Component for Client {
type Storage = FlaggedStorage<Self, IDVStorage<Self>>;
}
impl Client {
pub fn notify(&mut self, msg: ServerMsg) {
self.postbox.send_message(msg);
}
pub fn is_registered(&self) -> bool {
match self.client_state {
ClientState::Registered
| ClientState::Spectator
| ClientState::Dead
| ClientState::Character => true,
_ => false,
}
}
pub fn is_ingame(&self) -> bool {
match self.client_state {
ClientState::Spectator | ClientState::Character | ClientState::Dead => true,
_ => false,
}
}
pub fn allow_state(&mut self, new_state: ClientState) {
self.client_state = new_state;
self.postbox
@ -34,134 +51,6 @@ impl Client {
}
}
pub struct Clients {
clients: IndexMap<EcsEntity, Client, DefaultHashBuilder>,
}
impl Clients {
pub fn empty() -> Self {
Self {
clients: IndexMap::default(),
}
}
pub fn len(&mut self) -> usize {
self.clients.len()
}
pub fn add(&mut self, entity: EcsEntity, client: Client) {
self.clients.insert(entity, client);
}
pub fn get<'a>(&'a self, entity: &EcsEntity) -> Option<&'a Client> {
self.clients.get(entity)
}
pub fn get_mut<'a>(&'a mut self, entity: &EcsEntity) -> Option<&'a mut Client> {
self.clients.get_mut(entit:y)
}
pub fn remove<'a>(&'a mut self, entity: &EcsEntity) -> Option<Client> {
self.clients.remove(entity)
}
pub fn get_client_index_ingame<'a>(&'a mut self, entity: &EcsEntity) -> Option<usize> {
self.clients.get_full(entity).and_then(|(i, _, c)| {
if c.client_state == ClientState::Spectator
|| c.client_state == ClientState::Character
|| c.client_state == ClientState::Dead
{
Some(i)
} else {
None
}
})
}
pub fn remove_if<F: FnMut(EcsEntity, &mut Client) -> bool>(&mut self, mut f: F) {
self.clients.retain(|entity, client| !f(*entity, client));
}
pub fn notify_index(&mut self, index: usize, msg: ServerMsg) {
if let Some((_, client)) = self.clients.get_index_mut(index) {
client.notify(msg);
}
}
pub fn notify(&mut self, entity: EcsEntity, msg: ServerMsg) {
if let Some(client) = self.clients.get_mut(&entity) {
client.notify(msg);
}
}
pub fn notify_registered(&mut self, msg: ServerMsg) {
for client in self.clients.values_mut() {
if client.client_state != ClientState::Connected {
client.notify(msg.clone());
}
}
}
pub fn notify_ingame(&mut self, msg: ServerMsg) {
for client in self.clients.values_mut() {
if client.client_state == ClientState::Spectator
|| client.client_state == ClientState::Character
|| client.client_state == ClientState::Dead
{
client.notify(msg.clone());
}
}
}
pub fn notify_ingame_if<F: FnMut(EcsEntity) -> bool>(&mut self, msg: ServerMsg, mut f: F) {
for (_entity, client) in self.clients.iter_mut().filter(|(e, _)| f(**e)) {
if client.client_state == ClientState::Spectator
|| client.client_state == ClientState::Character
|| client.client_state == ClientState::Dead
{
client.notify(msg.clone());
}
}
}
pub fn notify_registered_except(&mut self, except_entity: EcsEntity, msg: ServerMsg) {
for (entity, client) in self.clients.iter_mut() {
if client.client_state != ClientState::Connected && *entity != except_entity {
client.notify(msg.clone());
}
}
}
pub fn notify_ingame_except(&mut self, except_entity: EcsEntity, msg: ServerMsg) {
for (entity, client) in self.clients.iter_mut() {
if (client.client_state == ClientState::Spectator
|| client.client_state == ClientState::Character
|| client.client_state == ClientState::Dead)
&& *entity != except_entity
{
client.notify(msg.clone());
}
}
}
pub fn notify_ingame_if_except<F: FnMut(EcsEntity) -> bool>(
&mut self,
except_entity: EcsEntity,
msg: ServerMsg,
mut f: F,
) {
for (entity, client) in self.clients.iter_mut().filter(|(e, _)| f(**e)) {
if (client.client_state == ClientState::Spectator
|| client.client_state == ClientState::Character
|| client.client_state == ClientState::Dead)
&& *entity != except_entity
{
client.notify(msg.clone());
}
}
}
}
// Distance from fuzzy_chunk before snapping to current chunk
pub const CHUNK_FUZZ: u32 = 2;
// Distance out of the range of a region before removing it from subscriptions

View File

@ -62,7 +62,7 @@ impl ChatCommand {
pub fn execute(&self, server: &mut Server, entity: EcsEntity, args: String) {
if self.needs_admin {
if !server.entity_is_admin(entity) {
server.clients.notify(
server.notify_client(
entity,
ServerMsg::private(format!(
"You don't have permission to use '/{}'.",
@ -243,7 +243,7 @@ fn handle_jump(server: &mut Server, entity: EcsEntity, args: String, action: &Ch
.write_component(entity, comp::Pos(current_pos.0 + Vec3::new(x, y, z)));
server.state.write_component(entity, comp::ForceUpdate);
}
None => server.clients.notify(
None => server.notify_client(
entity,
ServerMsg::private(String::from("You have no position.")),
),
@ -263,15 +263,13 @@ fn handle_goto(server: &mut Server, entity: EcsEntity, args: String, action: &Ch
.write_component(entity, comp::Pos(Vec3::new(x, y, z)));
server.state.write_component(entity, comp::ForceUpdate);
} else {
server.clients.notify(
server.notify_client(
entity,
ServerMsg::private(String::from("You have no position.")),
);
}
} else {
server
.clients
.notify(entity, ServerMsg::private(String::from(action.help_string)));
server.notify_client(entity, ServerMsg::private(String::from(action.help_string)));
}
}
@ -299,7 +297,7 @@ fn handle_time(server: &mut Server, entity: EcsEntity, args: String, action: &Ch
Err(_) => match NaiveTime::parse_from_str(n, "%H:%M") {
Ok(time) => time,
Err(_) => {
server.clients.notify(
server.notify_client(
entity,
ServerMsg::private(format!("'{}' is not a valid time.", n)),
);
@ -319,7 +317,7 @@ fn handle_time(server: &mut Server, entity: EcsEntity, args: String, action: &Ch
Some(time) => format!("It is {}", time.format("%H:%M").to_string()),
None => String::from("Unknown Time"),
};
server.clients.notify(entity, ServerMsg::private(msg));
server.notify_client(entity, ServerMsg::private(msg));
return;
}
};
@ -327,7 +325,7 @@ fn handle_time(server: &mut Server, entity: EcsEntity, args: String, action: &Ch
server.state.ecs_mut().write_resource::<TimeOfDay>().0 =
new_time.num_seconds_from_midnight() as f64;
server.clients.notify(
server.notify_client(
entity,
ServerMsg::private(format!(
"Time changed to: {}",
@ -340,19 +338,19 @@ fn handle_health(server: &mut Server, entity: EcsEntity, args: String, action: &
if let Ok(hp) = scan_fmt!(&args, action.arg_fmt, u32) {
if let Some(stats) = server
.state
.ecs_mut()
.ecs()
.write_storage::<comp::Stats>()
.get_mut(entity)
{
stats.health.set_to(hp, comp::HealthSource::Command);
} else {
server.clients.notify(
server.notify_client(
entity,
ServerMsg::private(String::from("You have no health.")),
);
}
} else {
server.clients.notify(
server.notify_client(
entity,
ServerMsg::private(String::from("You must specify health amount!")),
);
@ -368,9 +366,7 @@ fn handle_alias(server: &mut Server, entity: EcsEntity, args: String, action: &C
.get_mut(entity)
.map(|player| player.alias = alias);
} else {
server
.clients
.notify(entity, ServerMsg::private(String::from(action.help_string)));
server.notify_client(entity, ServerMsg::private(String::from(action.help_string)));
}
}
@ -388,31 +384,28 @@ fn handle_tp(server: &mut Server, entity: EcsEntity, args: String, action: &Chat
server.state.write_component(entity, pos);
server.state.write_component(entity, comp::ForceUpdate);
}
None => server.clients.notify(
None => server.notify_client(
entity,
ServerMsg::private(format!("Unable to teleport to player '{}'!", alias)),
),
},
None => {
server.clients.notify(
server.notify_client(
entity,
ServerMsg::private(format!("Player '{}' not found!", alias)),
);
server
.clients
.notify(entity, ServerMsg::private(String::from(action.help_string)));
server.notify_client(
entity,
ServerMsg::private(String::from(action.help_string)),
);
}
},
None => {
server
.clients
.notify(entity, ServerMsg::private(format!("You have no position!")));
server.notify_client(entity, ServerMsg::private(format!("You have no position!")));
}
}
} else {
server
.clients
.notify(entity, ServerMsg::private(String::from(action.help_string)));
server.notify_client(entity, ServerMsg::private(String::from(action.help_string)));
}
}
@ -443,12 +436,12 @@ fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &C
.with(agent)
.build();
}
server.clients.notify(
server.notify_client(
entity,
ServerMsg::private(format!("Spawned {} entities", amount).to_owned()),
);
}
None => server.clients.notify(
None => server.notify_client(
entity,
ServerMsg::private("You have no position!".to_owned()),
),
@ -456,9 +449,7 @@ fn handle_spawn(server: &mut Server, entity: EcsEntity, args: String, action: &C
}
}
_ => {
server
.clients
.notify(entity, ServerMsg::private(String::from(action.help_string)));
server.notify_client(entity, ServerMsg::private(String::from(action.help_string)));
}
}
}
@ -481,13 +472,9 @@ fn handle_players(server: &mut Server, entity: EcsEntity, _args: String, _action
s
});
server
.clients
.notify(entity, ServerMsg::private(header_message + &player_list));
server.notify_client(entity, ServerMsg::private(header_message + &player_list));
} else {
server
.clients
.notify(entity, ServerMsg::private(header_message));
server.notify_client(entity, ServerMsg::private(header_message));
}
}
@ -503,7 +490,7 @@ fn handle_build(server: &mut Server, entity: EcsEntity, _args: String, _action:
.ecs()
.write_storage::<comp::CanBuild>()
.remove(entity);
server.clients.notify(
server.notify_client(
entity,
ServerMsg::private(String::from("Toggled off build mode!")),
);
@ -513,7 +500,7 @@ fn handle_build(server: &mut Server, entity: EcsEntity, _args: String, _action:
.ecs()
.write_storage::<comp::CanBuild>()
.insert(entity, comp::CanBuild);
server.clients.notify(
server.notify_client(
entity,
ServerMsg::private(String::from("Toggled on build mode!")),
);
@ -523,9 +510,7 @@ fn handle_build(server: &mut Server, entity: EcsEntity, _args: String, _action:
fn handle_help(server: &mut Server, entity: EcsEntity, _args: String, _action: &ChatCommand) {
for cmd in CHAT_COMMANDS.iter() {
if !cmd.needs_admin || server.entity_is_admin(entity) {
server
.clients
.notify(entity, ServerMsg::private(String::from(cmd.help_string)));
server.notify_client(entity, ServerMsg::private(String::from(cmd.help_string)));
}
}
}
@ -564,7 +549,7 @@ fn handle_killnpcs(server: &mut Server, entity: EcsEntity, _args: String, _actio
} else {
"No NPCs on server.".to_string()
};
server.clients.notify(entity, ServerMsg::private(text));
server.notify_client(entity, ServerMsg::private(text));
}
fn handle_object(server: &mut Server, entity: EcsEntity, args: String, _action: &ChatCommand) {
@ -636,7 +621,7 @@ fn handle_object(server: &mut Server, entity: EcsEntity, args: String, _action:
Ok("carpet_human_squircle") => comp::object::Body::CarpetHumanSquircle,
Ok("crafting_bench") => comp::object::Body::CraftingBench,
_ => {
return server.clients.notify(
return server.notify_client(
entity,
ServerMsg::private(String::from("Object not found!")),
);
@ -657,7 +642,7 @@ fn handle_object(server: &mut Server, entity: EcsEntity, args: String, _action:
.normalized(),
))
.build();
server.clients.notify(
server.notify_client(
entity,
ServerMsg::private(format!(
"Spawned: {}",
@ -665,9 +650,7 @@ fn handle_object(server: &mut Server, entity: EcsEntity, args: String, _action:
)),
);
} else {
server
.clients
.notify(entity, ServerMsg::private(format!("You have no position!")));
server.notify_client(entity, ServerMsg::private(format!("You have no position!")));
}
}
@ -704,13 +687,9 @@ fn handle_light(server: &mut Server, entity: EcsEntity, args: String, action: &C
.with(comp::ForceUpdate)
.with(light_emitter)
.build();
server
.clients
.notify(entity, ServerMsg::private(format!("Spawned object.")));
server.notify_client(entity, ServerMsg::private(format!("Spawned object.")));
} else {
server
.clients
.notify(entity, ServerMsg::private(format!("You have no position!")));
server.notify_client(entity, ServerMsg::private(format!("You have no position!")));
}
}
@ -731,7 +710,7 @@ fn handle_lantern(server: &mut Server, entity: EcsEntity, args: String, action:
.get_mut(entity)
{
light.strength = s.max(0.1).min(10.0);
server.clients.notify(
server.notify_client(
entity,
ServerMsg::private(String::from("You adjusted flame strength.")),
);
@ -742,7 +721,7 @@ fn handle_lantern(server: &mut Server, entity: EcsEntity, args: String, action:
.ecs()
.write_storage::<comp::LightEmitter>()
.remove(entity);
server.clients.notify(
server.notify_client(
entity,
ServerMsg::private(String::from("You put out the lantern.")),
);
@ -765,7 +744,7 @@ fn handle_lantern(server: &mut Server, entity: EcsEntity, args: String, action:
},
);
server.clients.notify(
server.notify_client(
entity,
ServerMsg::private(String::from("You lit your lantern.")),
);
@ -781,7 +760,7 @@ fn handle_explosion(server: &mut Server, entity: EcsEntity, args: String, action
.ecs()
.read_resource::<EventBus<ServerEvent>>()
.emit(ServerEvent::Explosion { pos: pos.0, radius }),
None => server.clients.notify(
None => server.notify_client(
entity,
ServerMsg::private(String::from("You have no position!")),
),
@ -796,11 +775,9 @@ fn handle_waypoint(server: &mut Server, entity: EcsEntity, _args: String, _actio
.ecs()
.write_storage::<comp::Waypoint>()
.insert(entity, comp::Waypoint::new(pos.0));
server
.clients
.notify(entity, ServerMsg::private(String::from("Waypoint set!")));
server.notify_client(entity, ServerMsg::private(String::from("Waypoint set!")));
}
None => server.clients.notify(
None => server.notify_client(
entity,
ServerMsg::private(String::from("You have no position!")),
),
@ -824,19 +801,15 @@ fn handle_adminify(server: &mut Server, entity: EcsEntity, args: String, action:
}
},
None => {
server.clients.notify(
server.notify_client(
entity,
ServerMsg::private(format!("Player '{}' not found!", alias)),
);
server
.clients
.notify(entity, ServerMsg::private(String::from(action.help_string)));
server.notify_client(entity, ServerMsg::private(String::from(action.help_string)));
}
}
} else {
server
.clients
.notify(entity, ServerMsg::private(String::from(action.help_string)));
server.notify_client(entity, ServerMsg::private(String::from(action.help_string)));
}
}
@ -856,40 +829,40 @@ fn handle_tell(server: &mut Server, entity: EcsEntity, args: String, action: &Ch
.get(entity)
.map(|s| s.alias.clone())
{
server
.clients
.notify(player, ServerMsg::tell(format!("[{}] tells:{}", name, msg)));
server
.clients
.notify(entity, ServerMsg::tell(format!("To [{}]:{}", alias, msg)));
server.notify_client(
player,
ServerMsg::tell(format!("[{}] tells:{}", name, msg)),
);
server.notify_client(
entity,
ServerMsg::tell(format!("To [{}]:{}", alias, msg)),
);
} else {
server.clients.notify(
server.notify_client(
entity,
ServerMsg::private(String::from("Failed to send message.")),
);
}
} else {
server.clients.notify(
server.notify_client(
entity,
ServerMsg::private(format!("[{}] wants to talk to you.", alias)),
);
}
} else {
server.clients.notify(
server.notify_client(
entity,
ServerMsg::private(format!("You can't /tell yourself.")),
);
}
} else {
server.clients.notify(
server.notify_client(
entity,
ServerMsg::private(format!("Player '{}' not found!", alias)),
);
}
} else {
server
.clients
.notify(entity, ServerMsg::private(String::from(action.help_string)));
server.notify_client(entity, ServerMsg::private(String::from(action.help_string)));
}
}
@ -949,17 +922,15 @@ spawn_rate {:?} "#,
))
};
if let Some(s) = foo() {
server.clients.notify(entity, ServerMsg::private(s));
server.notify_client(entity, ServerMsg::private(s));
} else {
server.clients.notify(
server.notify_client(
entity,
ServerMsg::private(String::from("Not a pregenerated chunk.")),
);
}
} else {
server
.clients
.notify(entity, ServerMsg::private(String::from(action.help_string)));
server.notify_client(entity, ServerMsg::private(String::from(action.help_string)));
}
}
@ -972,24 +943,24 @@ fn handle_exp(server: &mut Server, entity: EcsEntity, args: String, action: &Cha
.find(|(_, player)| player.alias == alias)
.map(|(entity, _)| entity);
let mut error_msg = None;
match opt_player {
Some(_alias) => {
if let Some(stats) = ecs.write_storage::<comp::Stats>().get_mut(entity) {
stats.exp.change_by(exp);
} else {
server.clients.notify(
entity,
ServerMsg::private(String::from("Player has no stats!")),
);
error_msg = Some(ServerMsg::private(String::from("Player has no stats!")));
}
}
_ => {
server.clients.notify(
entity,
ServerMsg::private(format!("Player '{}' not found!", alias)),
);
error_msg = Some(ServerMsg::private(format!("Player '{}' not found!", alias)));
}
}
if let Some(msg) = error_msg {
server.notify_client(entity, msg);
}
}
}
@ -1022,7 +993,7 @@ fn handle_remove_lights(
}
}
}
None => server.clients.notify(
None => server.notify_client(
entity,
ServerMsg::private(String::from("You have no position.")),
),
@ -1034,7 +1005,7 @@ fn handle_remove_lights(
let _ = server.state.ecs_mut().delete_entity_synced(entity);
}
server.clients.notify(
server.notify_client(
entity,
ServerMsg::private(String::from(format!("Removed {} lights!", size))),
);

File diff suppressed because it is too large Load Diff

320
server/src/sys/message.rs Normal file
View File

@ -0,0 +1,320 @@
use crate::{auth_provider::AuthProvider, client::Client, CLIENT_TIMEOUT};
use common::{
comp::{Admin, Body, CanBuild, Controller, Item, Ori, Player, Pos, Vel},
event::{EventBus, ServerEvent},
msg::{validate_chat_msg, ChatMsgValidationError, MAX_BYTES_CHAT_MSG},
msg::{ClientMsg, ClientState, RequestStateError, ServerMsg},
state::{BlockChange, Time},
terrain::{Block, TerrainGrid},
vol::Vox,
};
use specs::{
Entities, Join, Read, ReadExpect, ReadStorage, System, Write, WriteExpect, WriteStorage,
};
/// This system will handle new messages from clients
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
Read<'a, EventBus<ServerEvent>>,
Read<'a, Time>,
ReadExpect<'a, TerrainGrid>,
ReadStorage<'a, Body>,
ReadStorage<'a, CanBuild>,
ReadStorage<'a, Admin>,
WriteExpect<'a, AuthProvider>,
Write<'a, BlockChange>,
WriteStorage<'a, Pos>,
WriteStorage<'a, Vel>,
WriteStorage<'a, Ori>,
WriteStorage<'a, Player>,
WriteStorage<'a, Client>,
WriteStorage<'a, Controller>,
);
fn run(
&mut self,
(
entities,
server_emitter,
time,
terrain,
bodies,
can_build,
admins,
mut accounts,
mut block_changes,
mut positions,
mut velocities,
mut orientations,
mut players,
mut clients,
mut controllers,
): Self::SystemData,
) {
let time = time.0;
let mut new_chat_msgs = Vec::new();
for (entity, client) in (&entities, &mut clients).join() {
let mut disconnect = false;
let new_msgs = client.postbox.new_messages();
// Update client ping.
if new_msgs.len() > 0 {
client.last_ping = time
} else if time - client.last_ping > CLIENT_TIMEOUT // Timeout
|| client.postbox.error().is_some()
// Postbox error
{
disconnect = true;
} else if time - client.last_ping > CLIENT_TIMEOUT * 0.5 {
// Try pinging the client if the timeout is nearing.
client.postbox.send_message(ServerMsg::Ping);
}
// Process incoming messages.
for msg in new_msgs {
match msg {
ClientMsg::RequestState(requested_state) => match requested_state {
ClientState::Connected => disconnect = true, // Default state
ClientState::Registered => match client.client_state {
// Use ClientMsg::Register instead.
ClientState::Connected => {
client.error_state(RequestStateError::WrongMessage)
}
ClientState::Registered => {
client.error_state(RequestStateError::Already)
}
ClientState::Spectator | ClientState::Character | ClientState::Dead => {
client.allow_state(ClientState::Registered)
}
ClientState::Pending => {}
},
ClientState::Spectator => match requested_state {
// Become Registered first.
ClientState::Connected => {
client.error_state(RequestStateError::Impossible)
}
ClientState::Spectator => {
client.error_state(RequestStateError::Already)
}
ClientState::Registered
| ClientState::Character
| ClientState::Dead => client.allow_state(ClientState::Spectator),
ClientState::Pending => {}
},
// Use ClientMsg::Character instead.
ClientState::Character => {
client.error_state(RequestStateError::WrongMessage)
}
ClientState::Dead => client.error_state(RequestStateError::Impossible),
ClientState::Pending => {}
},
// Valid player
ClientMsg::Register { player, password } if player.is_valid() => {
if !accounts.query(player.alias.clone(), password) {
client.error_state(RequestStateError::Denied);
break;
}
match client.client_state {
ClientState::Connected => {
let _ = players.insert(entity, player);
// Tell the client its request was successful.
client.allow_state(ClientState::Registered);
}
// Use RequestState instead (No need to send `player` again).
_ => client.error_state(RequestStateError::Impossible),
}
//client.allow_state(ClientState::Registered);
}
// Invalid player
ClientMsg::Register { .. } => client.error_state(RequestStateError::Impossible),
ClientMsg::SetViewDistance(view_distance) => match client.client_state {
ClientState::Character { .. } => {
players
.get_mut(entity)
.map(|player| player.view_distance = Some(view_distance));
}
_ => {}
},
ClientMsg::Character { name, body, main } => match client.client_state {
// Become Registered first.
ClientState::Connected => client.error_state(RequestStateError::Impossible),
ClientState::Registered | ClientState::Spectator | ClientState::Dead => {
if let (Some(player), None) = (
players.get(entity),
// Only send login message if the player didn't have a body
// previously
bodies.get(entity),
) {
new_chat_msgs.push((
None,
ServerMsg::broadcast(format!(
"[{}] is now online.",
&player.alias
)),
));
}
server_emitter.emit(ServerEvent::CreatePlayer {
entity,
name,
body,
main: main.map(|t| Item::Tool {
kind: t,
power: 10,
stamina: 0,
strength: 0,
dexterity: 0,
intelligence: 0,
}),
});
}
ClientState::Character => client.error_state(RequestStateError::Already),
ClientState::Pending => {}
},
ClientMsg::ControllerInputs(inputs) => match client.client_state {
ClientState::Connected
| ClientState::Registered
| ClientState::Spectator => {
client.error_state(RequestStateError::Impossible)
}
ClientState::Dead | ClientState::Character => {
if let Some(controller) = controllers.get_mut(entity) {
controller.inputs = inputs;
}
}
ClientState::Pending => {}
},
ClientMsg::ControlEvent(event) => match client.client_state {
ClientState::Connected
| ClientState::Registered
| ClientState::Spectator => {
client.error_state(RequestStateError::Impossible)
}
ClientState::Dead | ClientState::Character => {
if let Some(controller) = controllers.get_mut(entity) {
controller.events.push(event);
}
}
ClientState::Pending => {}
},
ClientMsg::ChatMsg { chat_type, message } => match client.client_state {
ClientState::Connected => client.error_state(RequestStateError::Impossible),
ClientState::Registered
| ClientState::Spectator
| ClientState::Dead
| ClientState::Character => match validate_chat_msg(&message) {
Ok(()) => new_chat_msgs
.push((Some(entity), ServerMsg::ChatMsg { chat_type, message })),
Err(ChatMsgValidationError::TooLong) => log::warn!(
"Recieved a chat message that's too long (max:{} len:{})",
MAX_BYTES_CHAT_MSG,
message.len()
),
},
ClientState::Pending => {}
},
ClientMsg::PlayerPhysics { pos, vel, ori } => match client.client_state {
ClientState::Character => {
let _ = positions.insert(entity, pos);
let _ = velocities.insert(entity, vel);
let _ = orientations.insert(entity, ori);
}
// Only characters can send positions.
_ => client.error_state(RequestStateError::Impossible),
},
ClientMsg::BreakBlock(pos) => {
if can_build.get(entity).is_some() {
block_changes.set(pos, Block::empty());
}
}
ClientMsg::PlaceBlock(pos, block) => {
if can_build.get(entity).is_some() {
block_changes.try_set(pos, block);
}
}
ClientMsg::TerrainChunkRequest { key } => match client.client_state {
ClientState::Connected | ClientState::Registered | ClientState::Dead => {
client.error_state(RequestStateError::Impossible);
}
ClientState::Spectator | ClientState::Character => {
match terrain.get_key(key) {
Some(chunk) => {
client.postbox.send_message(ServerMsg::TerrainChunkUpdate {
key,
chunk: Ok(Box::new(chunk.clone())),
})
}
None => server_emitter.emit(ServerEvent::ChunkRequest(entity, key)),
}
}
ClientState::Pending => {}
},
// Always possible.
ClientMsg::Ping => client.postbox.send_message(ServerMsg::Pong),
ClientMsg::Pong => {}
ClientMsg::Disconnect => {
disconnect = true;
}
}
}
if disconnect {
if let (Some(player), Some(_)) = (
players.get(entity),
// It only shows a message if you had a body (not in char selection)
bodies.get(entity),
) {
new_chat_msgs.push((
None,
ServerMsg::broadcast(format!("{} went offline.", &player.alias)),
));
}
server_emitter.emit(ServerEvent::ClientDisconnect(entity));
client.postbox.send_message(ServerMsg::Disconnect);
}
}
// Handle new chat messages.
for (entity, msg) in new_chat_msgs {
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..]);
server_emitter.emit(ServerEvent::ChatCmd(entity, argv));
} else {
let message = match players.get(entity) {
Some(player) => {
if admins.get(entity).is_some() {
format!("[ADMIN][{}] {}", &player.alias, message)
} else {
format!("[{}] {}", &player.alias, message)
}
}
None => format!("[<Unknown>] {}", message),
};
let msg = ServerMsg::ChatMsg { chat_type, message };
for client in (&mut clients).join().filter(|c| c.is_registered()) {
client.notify(msg.clone());
}
}
} else {
let msg = ServerMsg::ChatMsg { chat_type, message };
for client in (&mut clients).join().filter(|c| c.is_registered()) {
client.notify(msg.clone());
}
}
}
_ => {
panic!("Invalid message type.");
}
}
}
}
}

19
server/src/sys/mod.rs Normal file
View File

@ -0,0 +1,19 @@
pub mod sync;
//pub mod sync_chunk;
pub mod message;
pub mod subscription;
use specs::DispatcherBuilder;
// System names
const SYNC_SYS: &str = "server_sync_sys";
const SUBSCRIPTION_SYS: &str = "server_subscription_sys";
const MESSAGE_SYS: &str = "server_message_sys";
//const SYNC_CHUNK_SYS: &str = "server_sync_chunk_sys";
pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
dispatch_builder.add(subscription::Sys, SUBSCRIPTION_SYS, &[]);
dispatch_builder.add(sync::Sys, SYNC_SYS, &[SUBSCRIPTION_SYS]);
dispatch_builder.add(message::Sys, MESSAGE_SYS, &[]);
//dispatch_builder.add(sync_chunk::Sys, SYNC_CHUNKR_SYS, &[]);
}

View File

@ -0,0 +1,125 @@
use crate::client::{self, Client, RegionSubscription};
use common::{
comp::{Player, Pos},
msg::ServerMsg,
region::{region_in_vd, regions_in_vd, Event as RegionEvent, RegionMap},
state::Uid,
terrain::TerrainChunkSize,
vol::RectVolSize,
};
use specs::{Entities, Join, ReadExpect, ReadStorage, System, WriteStorage};
use vek::*;
/// This system will update region subscriptions based on client positions
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
ReadExpect<'a, RegionMap>,
ReadStorage<'a, Uid>,
ReadStorage<'a, Pos>,
ReadStorage<'a, Player>,
WriteStorage<'a, Client>,
WriteStorage<'a, RegionSubscription>,
);
fn run(
&mut self,
(entities, region_map, uids, positions, players, mut clients, mut subscriptions): Self::SystemData,
) {
// To update subscriptions
// 1. Iterate through clients
// 2. Calculate current chunk position
// 3. If chunk is the same return, otherwise continue (use fuzzyiness)
// 4. Iterate through subscribed regions
// 5. Check if region is still in range (use fuzzyiness)
// 6. If not in range
// - remove from hashset
// - inform client of which entities to remove
// 7. Determine list of regions that are in range and iterate through it
// - check if in hashset (hash calc) if not add it
let mut regions_to_remove = Vec::new();
for (client, subscription, pos, vd) in
(&mut clients, &mut subscriptions, &positions, &players)
.join()
.filter_map(|(c, s, pos, player)| player.view_distance.map(|v| (c, s, pos, v)))
{
let chunk = (Vec2::<f32>::from(pos.0))
.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 / sz as i32);
// Only update regions when moving to a new chunk
// uses a fuzzy border to prevent rapid triggering when moving along chunk boundaries
if chunk != subscription.fuzzy_chunk
&& (subscription
.fuzzy_chunk
.map2(TerrainChunkSize::RECT_SIZE, |e, sz| {
(e as f32 + 0.5) * sz as f32
})
- Vec2::from(pos.0))
.map2(TerrainChunkSize::RECT_SIZE, |e, sz| {
e.abs() > (sz / 2 + client::CHUNK_FUZZ) as f32
})
.reduce_or()
{
// Update current chunk
subscription.fuzzy_chunk = (Vec2::<f32>::from(pos.0))
.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 / sz as i32);
// Use the largest side length as our chunk size
let chunk_size = TerrainChunkSize::RECT_SIZE.reduce_max() as f32;
// Iterate through currently subscribed regions
for key in &subscription.regions {
// Check if the region is not within range anymore
if !region_in_vd(
*key,
pos.0,
(vd as f32 * chunk_size)
+ (client::CHUNK_FUZZ as f32 + client::REGION_FUZZ as f32 + chunk_size)
* 2.0f32.sqrt(),
) {
// Add to the list of regions to remove
regions_to_remove.push(*key);
}
}
// Iterate through regions to remove
for key in regions_to_remove.drain(..) {
// Remove region from this clients set of subscribed regions
subscription.regions.remove(&key);
// Tell the client to delete the entities in that region if it exists in the RegionMap
if let Some(region) = region_map.get(key) {
// Process entity left events since they won't be processed during phsyics sync because this region is no longer subscribed to
for event in region.events() {
match event {
RegionEvent::Entered(_, _) => {} // These don't need to be processed because this region is being thrown out anyway
RegionEvent::Left(id, maybe_key) => {
// Lookup UID for entity
if let Some(&uid) = uids.get(entities.entity(*id)) {
if !maybe_key
.as_ref()
.map(|key| subscription.regions.contains(key))
.unwrap_or(false)
{
client.notify(ServerMsg::DeleteEntity(uid.into()));
}
}
}
}
}
for (&uid, _) in (&uids, region.entities()).join() {
client.notify(ServerMsg::DeleteEntity(uid.into()))
}
}
}
for key in regions_in_vd(
pos.0,
(vd as f32 * chunk_size)
+ (client::CHUNK_FUZZ as f32 + chunk_size) * 2.0f32.sqrt(),
) {
if subscription.regions.insert(key) {
// TODO: send the client initial infromation for all the entities in this region
}
}
}
}
}
}

226
server/src/sys/sync.rs Normal file
View File

@ -0,0 +1,226 @@
use crate::{
client::{Client, RegionSubscription},
Tick,
};
use common::{
comp::{CharacterState, ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Pos, Vel},
msg::ServerMsg,
region::{Event as RegionEvent, RegionMap},
state::Uid,
};
use specs::{
Entities, Entity as EcsEntity, Join, Read, ReadExpect, ReadStorage, System, WriteStorage,
};
/// This system will send physics updates to the client
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
Read<'a, Tick>,
ReadExpect<'a, RegionMap>,
ReadStorage<'a, Uid>,
ReadStorage<'a, Pos>,
ReadStorage<'a, Vel>,
ReadStorage<'a, Ori>,
ReadStorage<'a, CharacterState>,
ReadStorage<'a, Inventory>,
ReadStorage<'a, RegionSubscription>,
WriteStorage<'a, Last<Pos>>,
WriteStorage<'a, Last<Vel>>,
WriteStorage<'a, Last<Ori>>,
WriteStorage<'a, Last<CharacterState>>,
WriteStorage<'a, Client>,
WriteStorage<'a, ForceUpdate>,
WriteStorage<'a, InventoryUpdate>,
);
fn run(
&mut self,
(
entities,
tick,
region_map,
uids,
positions,
velocities,
orientations,
character_states,
inventories,
subscriptions,
mut last_pos,
mut last_vel,
mut last_ori,
mut last_character_state,
mut clients,
mut force_updates,
mut inventory_updates,
): Self::SystemData,
) {
let tick = tick.0;
// To send entity updates
// 1. Iterate through regions
// 2. Iterate through region subscribers (ie clients)
// - Collect a list of entity ids for clients who are subscribed to this region (hash calc to check each)
// 3. Iterate through events from that region
// - For each entity left event, iterate through the client list and check if they are subscribed to the destination (hash calc per subscribed client per entity left event)
// - Do something with entity entered events when sphynx is removed??
// 4. Iterate through entities in that region
// 5. Inform clients of the component changes for that entity
// - Throttle update rate base on distance to each client
// Sync physics
// via iterating through regions
for (key, region) in region_map.iter() {
let mut subscribers = (&mut clients, &entities, &subscriptions, &positions)
.join()
.filter_map(|(client, entity, subscription, pos)| {
if client.is_ingame() && subscription.regions.contains(&key) {
Some((client, &subscription.regions, entity, *pos))
} else {
None
}
})
.collect::<Vec<_>>();
for event in region.events() {
match event {
RegionEvent::Entered(_, _) => {} // TODO use this
RegionEvent::Left(id, maybe_key) => {
// Lookup UID for entity
if let Some(&uid) = uids.get(entities.entity(*id)) {
for (client, regions, _, _) in &mut subscribers {
if !maybe_key
.as_ref()
.map(|key| regions.contains(key))
.unwrap_or(false)
{
client.notify(ServerMsg::DeleteEntity(uid.into()));
}
}
}
}
}
}
let mut send_msg =
|msg: ServerMsg, entity: EcsEntity, pos: Pos, force_update, throttle: bool| {
for (client, _, client_entity, client_pos) in &mut subscribers {
match force_update {
None if client_entity == &entity => {}
_ => {
let distance_sq = client_pos.0.distance_squared(pos.0);
// Throttle update rate based on distance to player
let update = if !throttle || distance_sq < 100.0f32.powi(2) {
true // Closer than 100.0 blocks
} else if distance_sq < 150.0f32.powi(2) {
(tick + entity.id() as u64) % 2 == 0
} else if distance_sq < 200.0f32.powi(2) {
(tick + entity.id() as u64) % 4 == 0
} else if distance_sq < 250.0f32.powi(2) {
(tick + entity.id() as u64) % 8 == 0
} else if distance_sq < 300.0f32.powi(2) {
(tick + entity.id() as u64) % 16 == 0
} else {
(tick + entity.id() as u64) % 32 == 0
};
if update {
client.notify(msg.clone());
}
}
}
}
};
for (_, entity, &uid, &pos, maybe_vel, maybe_ori, character_state, force_update) in (
region.entities(),
&entities,
&uids,
&positions,
velocities.maybe(),
orientations.maybe(),
character_states.maybe(),
force_updates.maybe(),
)
.join()
{
if last_pos.get(entity).map(|&l| l.0 != pos).unwrap_or(true) {
let _ = last_pos.insert(entity, Last(pos));
send_msg(
ServerMsg::EntityPos {
entity: uid.into(),
pos,
},
entity,
pos,
force_update,
true,
);
}
if let Some(&vel) = maybe_vel {
if last_vel.get(entity).map(|&l| l.0 != vel).unwrap_or(true) {
let _ = last_vel.insert(entity, Last(vel));
send_msg(
ServerMsg::EntityVel {
entity: uid.into(),
vel,
},
entity,
pos,
force_update,
true,
);
}
}
if let Some(&ori) = maybe_ori {
if last_ori.get(entity).map(|&l| l.0 != ori).unwrap_or(true) {
let _ = last_ori.insert(entity, Last(ori));
send_msg(
ServerMsg::EntityOri {
entity: uid.into(),
ori,
},
entity,
pos,
force_update,
true,
);
}
}
if let Some(&character_state) = character_state {
if last_character_state
.get(entity)
.map(|&l| !character_state.is_same_state(&l.0))
.unwrap_or(true)
{
let _ = last_character_state.insert(entity, Last(character_state));
send_msg(
ServerMsg::EntityCharacterState {
entity: uid.into(),
character_state,
},
entity,
pos,
force_update,
false,
);
}
}
}
}
// Sync inventories
for (client, inventory, _) in (&mut clients, &inventories, &inventory_updates).join() {
client.notify(ServerMsg::InventoryUpdate(inventory.clone()));
}
// Remove all force flags.
force_updates.clear();
inventory_updates.clear();
}
}

View File

@ -117,7 +117,7 @@ impl PlayState for CharSelectionState {
if let Err(err) = self
.client
.borrow_mut()
.tick(comp::Controller::default(), clock.get_last_delta())
.tick(comp::ControllerInputs::default(), clock.get_last_delta())
{
error!("Failed to tick the scene: {:?}", err);
return PlayStateResult::Pop;

View File

@ -25,7 +25,7 @@ pub struct SessionState {
client: Rc<RefCell<Client>>,
hud: Hud,
key_state: KeyState,
controller: comp::Controller,
inputs: comp::ControllerInputs,
selected_block: Block,
}
@ -43,7 +43,7 @@ impl SessionState {
scene,
client,
key_state: KeyState::new(),
controller: comp::Controller::default(),
inputs: comp::ControllerInputs::default(),
hud,
selected_block: Block::new(BlockKind::Normal, Rgb::broadcast(255)),
}
@ -53,7 +53,7 @@ impl SessionState {
impl SessionState {
/// Tick the session (and the client attached to it).
fn tick(&mut self, dt: Duration) -> Result<(), Error> {
for event in self.client.borrow_mut().tick(self.controller.clone(), dt)? {
for event in self.client.borrow_mut().tick(self.inputs.clone(), dt)? {
match event {
client::Event::Chat {
chat_type: _,
@ -139,9 +139,6 @@ impl PlayState for SessionState {
};
self.scene.set_select_pos(select_pos);
// Reset controller events
self.controller.clear_events();
// Handle window events.
for event in global_state.window.fetch_events() {
// Pass all events to the ui first.
@ -168,12 +165,12 @@ impl PlayState for SessionState {
client.place_block(build_pos, self.selected_block);
}
} else {
self.controller.primary = state
self.inputs.primary = state
}
}
Event::InputUpdate(GameInput::Secondary, state) => {
self.controller.secondary = false; // To be changed later on
self.inputs.secondary = false; // To be changed later on
let mut client = self.client.borrow_mut();
@ -194,7 +191,7 @@ impl PlayState for SessionState {
.map(|cs| cs.action.is_wield())
.unwrap_or(false)
{
self.controller.secondary = state;
self.inputs.secondary = state;
} else {
if let Some(select_pos) = select_pos {
client.collect_block(select_pos);
@ -217,36 +214,34 @@ impl PlayState for SessionState {
}
}
} else {
self.controller.roll = state;
self.inputs.roll = state;
}
}
Event::InputUpdate(GameInput::Respawn, state) => {
self.controller.respawn = state;
self.inputs.respawn = state;
}
Event::InputUpdate(GameInput::Jump, state) => {
self.controller.jump = state;
self.inputs.jump = state;
}
Event::InputUpdate(GameInput::Sit, state) => {
self.controller.sit = state;
self.inputs.sit = state;
}
Event::InputUpdate(GameInput::MoveForward, state) => self.key_state.up = state,
Event::InputUpdate(GameInput::MoveBack, state) => self.key_state.down = state,
Event::InputUpdate(GameInput::MoveLeft, state) => self.key_state.left = state,
Event::InputUpdate(GameInput::MoveRight, state) => self.key_state.right = state,
Event::InputUpdate(GameInput::Glide, state) => {
self.controller.glide = state;
self.inputs.glide = state;
}
Event::InputUpdate(GameInput::Climb, state) => self.controller.climb = state,
Event::InputUpdate(GameInput::Climb, state) => self.inputs.climb = state,
Event::InputUpdate(GameInput::ClimbDown, state) => {
self.controller.climb_down = state
}
Event::InputUpdate(GameInput::WallLeap, state) => {
self.controller.wall_leap = state
self.inputs.climb_down = state
}
Event::InputUpdate(GameInput::WallLeap, state) => self.inputs.wall_leap = state,
Event::InputUpdate(GameInput::Mount, true) => {
let client = self.client.borrow();
let mut client = self.client.borrow_mut();
if client.is_mounted() {
self.controller.push_event(comp::ControlEvent::Unmount);
client.unmount();
} else {
let player_pos = client
.state()
@ -271,15 +266,10 @@ impl PlayState for SessionState {
.min_by_key(|(_, pos, _)| {
(player_pos.0.distance_squared(pos.0) * 1000.0) as i32
})
.map(|(entity, _, _)| entity);
.map(|(uid, _, _)| uid);
if let Some(mountee_uid) =
closest_mountable.and_then(|mountee_entity| {
client.state().ecs().uid_from_entity(mountee_entity)
})
{
self.controller
.push_event(comp::ControlEvent::Mount(mountee_uid.into()));
if let Some(mountee_entity) = closest_mountable {
client.mount(mountee_entity);
}
}
}
@ -329,9 +319,9 @@ impl PlayState for SessionState {
Vec2::new(ori[0].sin(), ori[0].cos()),
);
let dir_vec = self.key_state.dir_vec();
self.controller.move_dir = unit_vecs.0 * dir_vec[0] + unit_vecs.1 * dir_vec[1];
self.inputs.move_dir = unit_vecs.0 * dir_vec[0] + unit_vecs.1 * dir_vec[1];
self.controller.look_dir = cam_dir;
self.inputs.look_dir = cam_dir;
// Perform an in-game tick.
if let Err(err) = self.tick(clock.get_avg_delta()) {