mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Move serverside client to a component and communications into server ecs systems
This commit is contained in:
parent
c02cd0a730
commit
2703c8afe1
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -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)",
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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> {
|
||||
|
@ -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>,
|
||||
},
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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))),
|
||||
);
|
||||
|
1186
server/src/lib.rs
1186
server/src/lib.rs
File diff suppressed because it is too large
Load Diff
320
server/src/sys/message.rs
Normal file
320
server/src/sys/message.rs
Normal 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
19
server/src/sys/mod.rs
Normal 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, &[]);
|
||||
}
|
125
server/src/sys/subscription.rs
Normal file
125
server/src/sys/subscription.rs
Normal 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
226
server/src/sys/sync.rs
Normal 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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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()) {
|
||||
|
Loading…
Reference in New Issue
Block a user