From 035d74f502779ce7b0fdcd824efc22a7ca7de2bc Mon Sep 17 00:00:00 2001 From: Marvin Altemeier Date: Sun, 16 Feb 2020 21:04:06 +0100 Subject: [PATCH] Move ServerEvents into own module --- server/src/events/entity_creation.rs | 84 +++ server/src/events/entity_manipulation.rs | 175 ++++++ server/src/events/interaction.rs | 171 ++++++ server/src/events/inventory_manip.rs | 221 +++++++ server/src/events/mod.rs | 109 ++++ server/src/events/player.rs | 60 ++ server/src/lib.rs | 752 +---------------------- 7 files changed, 829 insertions(+), 743 deletions(-) create mode 100644 server/src/events/entity_creation.rs create mode 100644 server/src/events/entity_manipulation.rs create mode 100644 server/src/events/interaction.rs create mode 100644 server/src/events/inventory_manip.rs create mode 100644 server/src/events/mod.rs create mode 100644 server/src/events/player.rs diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs new file mode 100644 index 0000000000..23bfa174db --- /dev/null +++ b/server/src/events/entity_creation.rs @@ -0,0 +1,84 @@ +use crate::{sys, Server, StateExt}; +use common::comp::{ + self, Agent, Alignment, Body, Gravity, LightEmitter, Pos, Projectile, Scale, Stats, Vel, + WaypointArea, +}; +use specs::{Builder, Entity as EcsEntity, WorldExt}; +use vek::{Rgb, Vec3}; + +pub fn handle_create_character( + server: &mut Server, + entity: EcsEntity, + name: String, + body: Body, + main: Option, +) { + let state = &mut server.state; + let server_settings = &server.server_settings; + + Server::create_player_character(state, entity, name, body, main, server_settings); + sys::subscription::initialize_region_subscription(state.ecs(), entity); +} + +pub fn handle_create_npc( + server: &mut Server, + pos: Pos, + stats: Stats, + body: Body, + agent: Agent, + alignment: Alignment, + scale: Scale, +) { + server + .state + .create_npc(pos, stats, body) + .with(agent) + .with(scale) + .with(alignment) + .build(); +} + +pub fn handle_shoot( + server: &mut Server, + entity: EcsEntity, + dir: Vec3, + body: Body, + light: Option, + projectile: Projectile, + gravity: Option, +) { + let state = server.state_mut(); + + let mut pos = state + .ecs() + .read_storage::() + .get(entity) + .expect("Failed to fetch entity") + .0; + + // TODO: Player height + pos.z += 1.2; + + let mut builder = + Server::create_projectile(state, Pos(pos), Vel(dir * 100.0), body, projectile); + if let Some(light) = light { + builder = builder.with(light) + } + if let Some(gravity) = gravity { + builder = builder.with(gravity) + } + + builder.build(); +} + +pub fn handle_create_waypoint(server: &mut Server, pos: Vec3) { + server + .create_object(Pos(pos), comp::object::Body::CampfireLit) + .with(LightEmitter { + offset: Vec3::unit_z() * 0.5, + col: Rgb::new(1.0, 0.65, 0.2), + strength: 2.0, + }) + .with(WaypointArea::default()) + .build(); +} diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs new file mode 100644 index 0000000000..022c17ce74 --- /dev/null +++ b/server/src/events/entity_manipulation.rs @@ -0,0 +1,175 @@ +use crate::{client::Client, Server, SpawnPoint, StateExt}; +use common::{ + comp::{self, HealthChange, HealthSource, Player, Stats}, + msg::ServerMsg, + state::BlockChange, + sync::{Uid, WorldSyncExt}, + terrain::{Block, TerrainGrid}, + vol::{ReadVol, Vox}, +}; +use log::error; +use specs::{Entity as EcsEntity, WorldExt}; +use vek::Vec3; + +pub fn handle_damage(server: &Server, uid: Uid, change: HealthChange) { + let state = &server.state; + let ecs = state.ecs(); + if let Some(entity) = ecs.entity_from_uid(uid.into()) { + if let Some(stats) = ecs.write_storage::().get_mut(entity) { + stats.health.change_by(change); + } + } +} + +pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSource) { + let state = server.state_mut(); + + // Chat message + if let Some(player) = state.ecs().read_storage::().get(entity) { + let msg = if let HealthSource::Attack { by } = cause { + state.ecs().entity_from_uid(by.into()).and_then(|attacker| { + state + .ecs() + .read_storage::() + .get(attacker) + .map(|attacker_alias| { + format!("{} was killed by {}", &player.alias, &attacker_alias.alias) + }) + }) + } else { + None + } + .unwrap_or(format!("{} died", &player.alias)); + + state.notify_registered_clients(ServerMsg::kill(msg)); + } + + { + // Give EXP to the killer if entity had stats + let mut stats = state.ecs().write_storage::(); + if let Some(entity_stats) = stats.get(entity).cloned() { + if let HealthSource::Attack { by } = cause { + state.ecs().entity_from_uid(by.into()).map(|attacker| { + if let Some(attacker_stats) = stats.get_mut(attacker) { + // TODO: Discuss whether we should give EXP by Player + // Killing or not. + attacker_stats + .exp + .change_by((entity_stats.level.level() * 10) as i64); + } + }); + } + } + } + + if state + .ecs() + .write_storage::() + .get_mut(entity) + .is_some() + { + state + .ecs() + .write_storage() + .insert(entity, comp::Vel(Vec3::zero())) + .err() + .map(|err| error!("Failed to set zero vel on dead client: {:?}", err)); + state + .ecs() + .write_storage() + .insert(entity, comp::ForceUpdate) + .err() + .map(|err| error!("Failed to insert ForceUpdate on dead client: {:?}", err)); + state + .ecs() + .write_storage::() + .get_mut(entity) + .map(|energy| energy.set_to(energy.maximum(), comp::EnergySource::Revive)); + let _ = state + .ecs() + .write_storage::() + .insert(entity, comp::CharacterState::default()); + } else { + // If not a player delete the entity + if let Err(err) = state.delete_entity_recorded(entity) { + error!("Failed to delete destroyed entity: {:?}", err); + } + } +} + +pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3) { + let state = &server.state; + if vel.z <= -37.0 { + if let Some(stats) = state.ecs().write_storage::().get_mut(entity) { + let falldmg = (vel.z / 2.5) as i32; + if falldmg < 0 { + stats.health.change_by(comp::HealthChange { + amount: falldmg, + cause: comp::HealthSource::World, + }); + } + } + } +} + +pub fn handle_respawn(server: &Server, entity: EcsEntity) { + let state = &server.state; + + // Only clients can respawn + if state + .ecs() + .write_storage::() + .get_mut(entity) + .is_some() + { + let respawn_point = state + .read_component_cloned::(entity) + .map(|wp| wp.get_pos()) + .unwrap_or(state.ecs().read_resource::().0); + + state + .ecs() + .write_storage::() + .get_mut(entity) + .map(|stats| stats.revive()); + state + .ecs() + .write_storage::() + .get_mut(entity) + .map(|pos| pos.0 = respawn_point); + state + .ecs() + .write_storage() + .insert(entity, comp::ForceUpdate) + .err() + .map(|err| { + error!( + "Error inserting ForceUpdate component when respawning client: {:?}", + err + ) + }); + } +} + +pub fn handle_explosion(server: &Server, pos: Vec3, radius: f32) { + const RAYS: usize = 500; + + for _ in 0..RAYS { + let dir = Vec3::new( + rand::random::() - 0.5, + rand::random::() - 0.5, + rand::random::() - 0.5, + ) + .normalized(); + + let ecs = server.state.ecs(); + let mut block_change = ecs.write_resource::(); + + let _ = ecs + .read_resource::() + .ray(pos, pos + dir * radius) + .until(|_| rand::random::() < 0.05) + .for_each(|pos| block_change.set(pos, Block::empty())) + .cast(); + } +} diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs new file mode 100644 index 0000000000..96be66e52b --- /dev/null +++ b/server/src/events/interaction.rs @@ -0,0 +1,171 @@ +use crate::{ + client::{Client, RegionSubscription}, + Server, +}; +use common::{ + assets, comp, + msg::ServerMsg, + sync::{Uid, WorldSyncExt}, +}; +use log::error; +use specs::{world::WorldExt, Entity as EcsEntity}; + +pub fn handle_mount(server: &mut Server, mounter: EcsEntity, mountee: EcsEntity) { + let state = server.state_mut(); + + if state + .ecs() + .read_storage::() + .get(mounter) + .is_none() + { + let not_mounting_yet = if let Some(comp::MountState::Unmounted) = state + .ecs() + .read_storage::() + .get(mountee) + .cloned() + { + true + } else { + false + }; + + if not_mounting_yet { + if let (Some(mounter_uid), Some(mountee_uid)) = ( + state.ecs().uid_from_entity(mounter), + state.ecs().uid_from_entity(mountee), + ) { + state.write_component(mountee, comp::MountState::MountedBy(mounter_uid.into())); + state.write_component(mounter, comp::Mounting(mountee_uid.into())); + } + } + } +} + +pub fn handle_unmount(server: &mut Server, mounter: EcsEntity) { + let state = server.state_mut(); + let mountee_entity = state + .ecs() + .write_storage::() + .get(mounter) + .and_then(|mountee| state.ecs().entity_from_uid(mountee.0.into())); + if let Some(mountee_entity) = mountee_entity { + state + .ecs() + .write_storage::() + .get_mut(mountee_entity) + .map(|ms| *ms = comp::MountState::Unmounted); + } + state.delete_component::(mounter); +} + +pub fn handle_possess(server: &Server, possessor_uid: Uid, possesse_uid: Uid) { + let state = &server.state; + let ecs = state.ecs(); + if let (Some(possessor), Some(possesse)) = ( + ecs.entity_from_uid(possessor_uid.into()), + ecs.entity_from_uid(possesse_uid.into()), + ) { + // You can't possess other players + let mut clients = ecs.write_storage::(); + if clients.get_mut(possesse).is_none() { + if let Some(mut client) = clients.remove(possessor) { + client.notify(ServerMsg::SetPlayerEntity(possesse_uid.into())); + clients.insert(possesse, client).err().map(|e| { + error!( + "Error inserting client component during possession: {:?}", + e + ) + }); + // Create inventory if it doesn't exist + { + let mut inventories = ecs.write_storage::(); + if let Some(inventory) = inventories.get_mut(possesse) { + inventory.push(assets::load_expect_cloned("common.items.debug.possess")); + } else { + inventories + .insert(possesse, comp::Inventory { + slots: vec![ + Some(assets::load_expect_cloned("common.items.debug.possess")), + None, + None, + None, + None, + None, + None, + None, + ], + }) + .err() + .map(|e| { + error!( + "Error inserting inventory component during possession: {:?}", + e + ) + }); + } + } + ecs.write_storage::() + .insert(possesse, comp::InventoryUpdate) + .err() + .map(|e| { + error!( + "Error inserting inventory update component during possession: {:?}", + e + ) + }); + // Move player component + { + let mut players = ecs.write_storage::(); + if let Some(player) = players.remove(possessor) { + players.insert(possesse, player).err().map(|e| { + error!( + "Error inserting player component during possession: {:?}", + e + ) + }); + } + } + // Transfer region subscription + { + let mut subscriptions = ecs.write_storage::(); + if let Some(s) = subscriptions.remove(possessor) { + subscriptions.insert(possesse, s).err().map(|e| { + error!( + "Error inserting subscription component during possession: {:?}", + e + ) + }); + } + } + // Remove will of the entity + ecs.write_storage::().remove(possesse); + // Reset controller of former shell + ecs.write_storage::() + .get_mut(possessor) + .map(|c| c.reset()); + // Transfer admin powers + { + let mut admins = ecs.write_storage::(); + if let Some(admin) = admins.remove(possessor) { + admins.insert(possesse, admin).err().map(|e| { + error!("Error inserting admin component during possession: {:?}", e) + }); + } + } + // Transfer waypoint + { + let mut waypoints = ecs.write_storage::(); + if let Some(waypoint) = waypoints.remove(possessor) { + waypoints.insert(possesse, waypoint).err().map(|e| { + error!( + "Error inserting waypoint component during possession {:?}", + e + ) + }); + } + } + } + } + } +} diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs new file mode 100644 index 0000000000..4485a62fb7 --- /dev/null +++ b/server/src/events/inventory_manip.rs @@ -0,0 +1,221 @@ +use crate::{Server, StateExt}; +use common::{ + comp, + sync::WorldSyncExt, + terrain::block::Block, + vol::{ReadVol, Vox}, +}; +use log::error; +use rand::Rng; +use specs::{join::Join, world::WorldExt, Builder, Entity as EcsEntity}; +use vek::Vec3; + +pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::InventoryManip) { + let state = server.state_mut(); + let mut dropped_items = Vec::new(); + + match manip { + comp::InventoryManip::Pickup(uid) => { + // TODO: enforce max pickup range + let item_entity = if let (Some((item, item_entity)), Some(inv)) = ( + state + .ecs() + .entity_from_uid(uid.into()) + .and_then(|item_entity| { + state + .ecs() + .write_storage::() + .get_mut(item_entity) + .map(|item| (item.clone(), item_entity)) + }), + state + .ecs() + .write_storage::() + .get_mut(entity), + ) { + if inv.push(item).is_none() { + Some(item_entity) + } else { + None + } + } else { + None + }; + + if let Some(item_entity) = item_entity { + if let Err(err) = state.delete_entity_recorded(item_entity) { + error!("Failed to delete picked up item entity: {:?}", err); + } + } + + state.write_component(entity, comp::InventoryUpdate); + }, + + comp::InventoryManip::Collect(pos) => { + let block = state.terrain().get(pos).ok().copied(); + if let Some(block) = block { + if block.is_collectible() + && state + .ecs() + .read_storage::() + .get(entity) + .map(|inv| !inv.is_full()) + .unwrap_or(false) + && state.try_set_block(pos, Block::empty()).is_some() + { + comp::Item::try_reclaim_from_block(block) + .map(|item| state.give_item(entity, item)); + } + } + }, + + comp::InventoryManip::Use(slot) => { + let item_opt = state + .ecs() + .write_storage::() + .get_mut(entity) + .and_then(|inv| inv.remove(slot)); + + if let Some(item) = item_opt { + match item.kind { + comp::ItemKind::Tool { .. } => { + if let Some(stats) = + state.ecs().write_storage::().get_mut(entity) + { + // Insert old item into inventory + if let Some(old_item) = stats.equipment.main.take() { + state + .ecs() + .write_storage::() + .get_mut(entity) + .map(|inv| inv.insert(slot, old_item)); + } + + stats.equipment.main = Some(item); + } + }, + comp::ItemKind::Consumable { effect, .. } => { + state.apply_effect(entity, effect); + }, + comp::ItemKind::Utility { kind } => match kind { + comp::item::Utility::Collar => { + let reinsert = if let Some(pos) = + state.read_storage::().get(entity) + { + if ( + &state.read_storage::(), + &state.read_storage::(), + ) + .join() + .filter(|(alignment, _)| { + alignment == &&comp::Alignment::Owned(entity) + }) + .count() + >= 3 + { + true + } else if let Some(tameable_entity) = { + let nearest_tameable = ( + &state.ecs().entities(), + &state.ecs().read_storage::(), + &state.ecs().read_storage::(), + ) + .join() + .filter(|(_, wild_pos, _)| { + wild_pos.0.distance_squared(pos.0) < 5.0f32.powf(2.0) + }) + .filter(|(_, _, alignment)| { + alignment == &&comp::Alignment::Wild + }) + .min_by_key(|(_, wild_pos, _)| { + (wild_pos.0.distance_squared(pos.0) * 100.0) as i32 + }) + .map(|(entity, _, _)| entity); + nearest_tameable + } { + let _ = state + .ecs() + .write_storage() + .insert(tameable_entity, comp::Alignment::Owned(entity)); + let _ = state + .ecs() + .write_storage() + .insert(tameable_entity, comp::Agent::default()); + false + } else { + true + } + } else { + true + }; + + if reinsert { + let _ = state + .ecs() + .write_storage::() + .get_mut(entity) + .map(|inv| inv.insert(slot, item)); + } + }, + }, + _ => { + let _ = state + .ecs() + .write_storage::() + .get_mut(entity) + .map(|inv| inv.insert(slot, item)); + }, + } + } + + state.write_component(entity, comp::InventoryUpdate); + }, + + comp::InventoryManip::Swap(a, b) => { + state + .ecs() + .write_storage::() + .get_mut(entity) + .map(|inv| inv.swap_slots(a, b)); + state.write_component(entity, comp::InventoryUpdate); + }, + + comp::InventoryManip::Drop(slot) => { + let item = state + .ecs() + .write_storage::() + .get_mut(entity) + .and_then(|inv| inv.remove(slot)); + + if let (Some(item), Some(pos)) = + (item, state.ecs().read_storage::().get(entity)) + { + dropped_items.push(( + *pos, + state + .ecs() + .read_storage::() + .get(entity) + .copied() + .unwrap_or(comp::Ori(Vec3::unit_y())), + item, + )); + } + state.write_component(entity, comp::InventoryUpdate); + }, + } + + // Drop items + for (pos, ori, item) in dropped_items { + let vel = ori.0.normalized() * 5.0 + + Vec3::unit_z() * 10.0 + + Vec3::::zero().map(|_| rand::thread_rng().gen::() - 0.5) * 4.0; + + server + .create_object(Default::default(), comp::object::Body::Pouch) + .with(comp::Pos(pos.0 + Vec3::unit_z() * 0.25)) + .with(item) + .with(comp::Vel(vel)) + .build(); + } +} diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs new file mode 100644 index 0000000000..dd9f3b332c --- /dev/null +++ b/server/src/events/mod.rs @@ -0,0 +1,109 @@ +use crate::Server; +use common::event::{EventBus, ServerEvent}; +use entity_creation::{ + handle_create_character, handle_create_npc, handle_create_waypoint, handle_shoot, +}; +use entity_manipulation::{ + handle_damage, handle_destroy, handle_explosion, handle_land_on_ground, handle_respawn, +}; +use interaction::{handle_mount, handle_possess, handle_unmount}; +use inventory_manip::handle_inventory; +use player::{handle_client_disconnect, handle_exit_ingame}; +use specs::{Entity as EcsEntity, WorldExt}; + +mod entity_creation; +mod entity_manipulation; +mod interaction; +mod inventory_manip; +mod player; + +pub enum Event { + ClientConnected { + entity: EcsEntity, + }, + ClientDisconnected { + entity: EcsEntity, + }, + Chat { + entity: Option, + msg: String, + }, +} + +impl Server { + pub fn handle_events(&mut self) -> Vec { + let mut frontend_events = Vec::new(); + + let mut requested_chunks = Vec::new(); + let mut chat_commands = Vec::new(); + + let events = self + .state + .ecs() + .read_resource::>() + .recv_all(); + + for event in events { + match event { + ServerEvent::Explosion { pos, radius } => handle_explosion(&self, pos, radius), + ServerEvent::Shoot { + entity, + dir, + body, + light, + projectile, + gravity, + } => handle_shoot(self, entity, dir, body, light, projectile, gravity), + ServerEvent::Damage { uid, change } => handle_damage(&self, uid, change), + ServerEvent::Destroy { entity, cause } => handle_destroy(self, entity, cause), + ServerEvent::InventoryManip(entity, manip) => handle_inventory(self, entity, manip), + ServerEvent::Respawn(entity) => handle_respawn(&self, entity), + ServerEvent::LandOnGround { entity, vel } => { + handle_land_on_ground(&self, entity, vel) + }, + ServerEvent::Mount(mounter, mountee) => handle_mount(self, mounter, mountee), + ServerEvent::Unmount(mounter) => handle_unmount(self, mounter), + ServerEvent::Possess(possessor_uid, possesse_uid) => { + handle_possess(&self, possessor_uid, possesse_uid) + }, + ServerEvent::CreateCharacter { + entity, + name, + body, + main, + } => handle_create_character(self, entity, name, body, main), + ServerEvent::ExitIngame { entity } => handle_exit_ingame(self, entity), + ServerEvent::CreateNpc { + pos, + stats, + body, + agent, + alignment, + scale, + } => handle_create_npc(self, pos, stats, body, agent, alignment, scale), + ServerEvent::CreateWaypoint(pos) => handle_create_waypoint(self, pos), + ServerEvent::ClientDisconnect(entity) => { + frontend_events.push(handle_client_disconnect(self, entity)) + }, + + ServerEvent::ChunkRequest(entity, key) => { + requested_chunks.push((entity, key)); + }, + ServerEvent::ChatCmd(entity, cmd) => { + chat_commands.push((entity, cmd)); + }, + } + } + + // Generate requested chunks. + for (entity, key) in requested_chunks { + self.generate_chunk(entity, key); + } + + for (entity, cmd) in chat_commands { + self.process_chat_cmd(entity, cmd); + } + + frontend_events + } +} diff --git a/server/src/events/player.rs b/server/src/events/player.rs new file mode 100644 index 0000000000..33ea7e7b63 --- /dev/null +++ b/server/src/events/player.rs @@ -0,0 +1,60 @@ +use super::Event; +use crate::{client::Client, Server, StateExt}; +use common::{ + comp, + msg::{ClientState, PlayerListUpdate, ServerMsg}, + sync::{Uid, UidAllocator}, +}; +use log::error; +use specs::{saveload::MarkerAllocator, Builder, Entity as EcsEntity, WorldExt}; + +pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) { + let state = server.state_mut(); + + // Create new entity with just `Client`, `Uid`, and `Player` components + // Easier than checking and removing all other known components + // Note: If other `ServerEvent`s are referring to this entity they will be + // disrupted + let maybe_client = state.ecs().write_storage::().remove(entity); + let maybe_uid = state.read_component_cloned::(entity); + let maybe_player = state.ecs().write_storage::().remove(entity); + if let (Some(mut client), Some(uid), Some(player)) = (maybe_client, maybe_uid, maybe_player) { + // Tell client its request was successful + client.allow_state(ClientState::Registered); + // Tell client to clear out other entities and its own components + client.notify(ServerMsg::ExitIngameCleanup); + + let entity_builder = state.ecs_mut().create_entity().with(client).with(player); + // Ensure UidAllocator maps this uid to the new entity + let uid = entity_builder + .world + .write_resource::() + .allocate(entity_builder.entity, Some(uid.into())); + entity_builder.with(uid).build(); + } + // Delete old entity + if let Err(err) = state.delete_entity_recorded(entity) { + error!("Failed to delete entity when removing character: {:?}", err); + } +} + +pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event { + let state = server.state_mut(); + + // Tell other clients to remove from player list + if let (Some(uid), Some(_)) = ( + state.read_storage::().get(entity), + state.read_storage::().get(entity), + ) { + state.notify_registered_clients(ServerMsg::PlayerListUpdate(PlayerListUpdate::Remove( + (*uid).into(), + ))) + } + + // Delete client entity + if let Err(err) = state.delete_entity_recorded(entity) { + error!("Failed to delete disconnected client: {:?}", err); + } + + Event::ClientDisconnected { entity } +} diff --git a/server/src/lib.rs b/server/src/lib.rs index 161aa4c9d8..b39fefea50 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -6,6 +6,7 @@ pub mod chunk_generator; pub mod client; pub mod cmd; pub mod error; +pub mod events; pub mod input; pub mod metrics; pub mod settings; @@ -13,7 +14,7 @@ pub mod sys; #[cfg(not(feature = "worldgen"))] mod test_world; // Reexports -pub use crate::{error::Error, input::Input, settings::ServerSettings}; +pub use crate::{error::Error, events::Event, input::Input, settings::ServerSettings}; use crate::{ auth_provider::AuthProvider, @@ -26,19 +27,18 @@ use common::{ assets, comp, effect::Effect, event::{EventBus, ServerEvent}, - msg::{ClientMsg, ClientState, PlayerListUpdate, ServerError, ServerInfo, ServerMsg}, + msg::{ClientMsg, ClientState, ServerError, ServerInfo, ServerMsg}, net::PostOffice, - state::{BlockChange, State, TimeOfDay}, - sync::{Uid, UidAllocator, WorldSyncExt}, - terrain::{block::Block, TerrainChunkSize, TerrainGrid}, - vol::{ReadVol, RectVolSize, Vox}, + state::{State, TimeOfDay}, + sync::{Uid, WorldSyncExt}, + terrain::TerrainChunkSize, + vol::{ReadVol, RectVolSize}, }; use log::{debug, error, warn}; use metrics::ServerMetrics; -use rand::Rng; use specs::{ - join::Join, saveload::MarkerAllocator, world::EntityBuilder as EcsEntityBuilder, Builder, - Entity as EcsEntity, RunNow, SystemData, WorldExt, + join::Join, world::EntityBuilder as EcsEntityBuilder, Builder, Entity as EcsEntity, RunNow, + SystemData, WorldExt, }; use std::{ i32, @@ -57,19 +57,6 @@ use world::{ const CLIENT_TIMEOUT: f64 = 20.0; // Seconds -pub enum Event { - ClientConnected { - entity: EcsEntity, - }, - ClientDisconnected { - entity: EcsEntity, - }, - Chat { - entity: Option, - msg: String, - }, -} - #[derive(Copy, Clone)] struct SpawnPoint(Vec3); @@ -317,727 +304,6 @@ impl Server { } /// Handle events coming through via the event bus - fn handle_events(&mut self) -> Vec { - let mut frontend_events = Vec::new(); - - let mut requested_chunks = Vec::new(); - let mut dropped_items = Vec::new(); - let mut chat_commands = Vec::new(); - - let events = self - .state - .ecs() - .read_resource::>() - .recv_all(); - for event in events { - let state = &mut self.state; - - let server_settings = &self.server_settings; - - match event { - ServerEvent::Explosion { pos, radius } => { - const RAYS: usize = 500; - - for _ in 0..RAYS { - let dir = Vec3::new( - rand::random::() - 0.5, - rand::random::() - 0.5, - rand::random::() - 0.5, - ) - .normalized(); - - let ecs = state.ecs(); - let mut block_change = ecs.write_resource::(); - - let _ = ecs - .read_resource::() - .ray(pos, pos + dir * radius) - .until(|_| rand::random::() < 0.05) - .for_each(|pos| block_change.set(pos, Block::empty())) - .cast(); - } - }, - - ServerEvent::Shoot { - entity, - dir, - body, - light, - projectile, - gravity, - } => { - let mut pos = state - .ecs() - .read_storage::() - .get(entity) - .expect("Failed to fetch entity") - .0; - - // TODO: Player height - pos.z += 1.2; - - let mut builder = Self::create_projectile( - state, - comp::Pos(pos), - comp::Vel(dir * 100.0), - body, - projectile, - ); - if let Some(light) = light { - builder = builder.with(light) - } - if let Some(gravity) = gravity { - builder = builder.with(gravity) - } - - builder.build(); - }, - - ServerEvent::Damage { uid, change } => { - let ecs = state.ecs(); - if let Some(entity) = ecs.entity_from_uid(uid.into()) { - if let Some(stats) = ecs.write_storage::().get_mut(entity) { - stats.health.change_by(change); - } - } - }, - - ServerEvent::Destroy { entity, cause } => { - // Chat message - if let Some(player) = state.ecs().read_storage::().get(entity) { - let msg = if let comp::HealthSource::Attack { by } = cause { - state.ecs().entity_from_uid(by.into()).and_then(|attacker| { - state - .ecs() - .read_storage::() - .get(attacker) - .map(|attacker_alias| { - format!( - "{} was killed by {}", - &player.alias, &attacker_alias.alias - ) - }) - }) - } else { - None - } - .unwrap_or(format!("{} died", &player.alias)); - - state.notify_registered_clients(ServerMsg::kill(msg)); - } - - { - // Give EXP to the killer if entity had stats - let mut stats = state.ecs().write_storage::(); - if let Some(entity_stats) = stats.get(entity).cloned() { - if let comp::HealthSource::Attack { by } = cause { - state.ecs().entity_from_uid(by.into()).map(|attacker| { - if let Some(attacker_stats) = stats.get_mut(attacker) { - // TODO: Discuss whether we should give EXP by Player - // Killing or not. - attacker_stats - .exp - .change_by((entity_stats.level.level() * 10) as i64); - } - }); - } - } - } - - if state - .ecs() - .write_storage::() - .get_mut(entity) - .is_some() - { - state - .ecs() - .write_storage() - .insert(entity, comp::Vel(Vec3::zero())) - .err() - .map(|err| error!("Failed to set zero vel on dead client: {:?}", err)); - state - .ecs() - .write_storage() - .insert(entity, comp::ForceUpdate) - .err() - .map(|err| { - error!("Failed to insert ForceUpdate on dead client: {:?}", err) - }); - state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|energy| { - energy.set_to(energy.maximum(), comp::EnergySource::Revive) - }); - let _ = state - .ecs() - .write_storage::() - .insert(entity, comp::CharacterState::default()); - } else { - // If not a player delete the entity - if let Err(err) = state.delete_entity_recorded(entity) { - error!("Failed to delete destroyed entity: {:?}", err); - } - } - }, - - ServerEvent::InventoryManip(entity, manip) => { - match manip { - comp::InventoryManip::Pickup(uid) => { - // TODO: enforce max pickup range - let item_entity = if let (Some((item, item_entity)), Some(inv)) = ( - state - .ecs() - .entity_from_uid(uid.into()) - .and_then(|item_entity| { - state - .ecs() - .write_storage::() - .get_mut(item_entity) - .map(|item| (item.clone(), item_entity)) - }), - state - .ecs() - .write_storage::() - .get_mut(entity), - ) { - if inv.push(item).is_none() { - Some(item_entity) - } else { - None - } - } else { - None - }; - - if let Some(item_entity) = item_entity { - if let Err(err) = state.delete_entity_recorded(item_entity) { - error!("Failed to delete picked up item entity: {:?}", err); - } - } - - state.write_component(entity, comp::InventoryUpdate); - }, - - comp::InventoryManip::Collect(pos) => { - let block = state.terrain().get(pos).ok().copied(); - if let Some(block) = block { - if block.is_collectible() - && state - .ecs() - .read_storage::() - .get(entity) - .map(|inv| !inv.is_full()) - .unwrap_or(false) - && state.try_set_block(pos, Block::empty()).is_some() - { - comp::Item::try_reclaim_from_block(block) - .map(|item| state.give_item(entity, item)); - } - } - }, - - comp::InventoryManip::Use(slot) => { - let item_opt = state - .ecs() - .write_storage::() - .get_mut(entity) - .and_then(|inv| inv.remove(slot)); - - if let Some(item) = item_opt { - match item.kind { - comp::ItemKind::Tool { .. } => { - if let Some(stats) = state - .ecs() - .write_storage::() - .get_mut(entity) - { - // Insert old item into inventory - if let Some(old_item) = stats.equipment.main.take() { - state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|inv| inv.insert(slot, old_item)); - } - - stats.equipment.main = Some(item); - } - }, - comp::ItemKind::Consumable { effect, .. } => { - state.apply_effect(entity, effect); - }, - comp::ItemKind::Utility { kind } => match kind { - comp::item::Utility::Collar => { - let reinsert = if let Some(pos) = - state.read_storage::().get(entity) - { - if ( - &state.read_storage::(), - &state.read_storage::(), - ) - .join() - .filter(|(alignment, _)| { - alignment - == &&comp::Alignment::Owned(entity) - }) - .count() - >= 3 - { - true - } else if let Some(tameable_entity) = { - let nearest_tameable = ( - &state.ecs().entities(), - &state.ecs().read_storage::(), - &state - .ecs() - .read_storage::(), - ) - .join() - .filter(|(_, wild_pos, _)| { - wild_pos.0.distance_squared(pos.0) - < 5.0f32.powf(2.0) - }) - .filter(|(_, _, alignment)| { - alignment == &&comp::Alignment::Wild - }) - .min_by_key(|(_, wild_pos, _)| { - (wild_pos.0.distance_squared(pos.0) - * 100.0) - as i32 - }) - .map(|(entity, _, _)| entity); - nearest_tameable - } { - let _ = state.ecs().write_storage().insert( - tameable_entity, - comp::Alignment::Owned(entity), - ); - let _ = state.ecs().write_storage().insert( - tameable_entity, - comp::Agent::default(), - ); - false - } else { - true - } - } else { - true - }; - - if reinsert { - let _ = state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|inv| inv.insert(slot, item)); - } - }, - }, - _ => { - let _ = state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|inv| inv.insert(slot, item)); - }, - } - } - - state.write_component(entity, comp::InventoryUpdate); - }, - - comp::InventoryManip::Swap(a, b) => { - state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|inv| inv.swap_slots(a, b)); - state.write_component(entity, comp::InventoryUpdate); - }, - - comp::InventoryManip::Drop(slot) => { - let item = state - .ecs() - .write_storage::() - .get_mut(entity) - .and_then(|inv| inv.remove(slot)); - - if let (Some(item), Some(pos)) = - (item, state.ecs().read_storage::().get(entity)) - { - dropped_items.push(( - *pos, - state - .ecs() - .read_storage::() - .get(entity) - .copied() - .unwrap_or(comp::Ori(Vec3::unit_y())), - item, - )); - } - state.write_component(entity, comp::InventoryUpdate); - }, - } - }, - - ServerEvent::Respawn(entity) => { - // Only clients can respawn - if state - .ecs() - .write_storage::() - .get_mut(entity) - .is_some() - { - let respawn_point = state - .read_component_cloned::(entity) - .map(|wp| wp.get_pos()) - .unwrap_or(state.ecs().read_resource::().0); - - state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|stats| stats.revive()); - state - .ecs() - .write_storage::() - .get_mut(entity) - .map(|pos| pos.0 = respawn_point); - state - .ecs() - .write_storage() - .insert(entity, comp::ForceUpdate) - .err() - .map(|err| { - error!( - "Error inserting ForceUpdate component when respawning \ - client: {:?}", - err - ) - }); - } - }, - - ServerEvent::LandOnGround { entity, vel } => { - if vel.z <= -37.0 { - if let Some(stats) = - state.ecs().write_storage::().get_mut(entity) - { - let falldmg = (vel.z / 2.5) as i32; - if falldmg < 0 { - stats.health.change_by(comp::HealthChange { - amount: falldmg, - cause: comp::HealthSource::World, - }); - } - } - } - }, - - ServerEvent::Mount(mounter, mountee) => { - if state - .ecs() - .read_storage::() - .get(mounter) - .is_none() - { - let not_mounting_yet = if let Some(comp::MountState::Unmounted) = state - .ecs() - .read_storage::() - .get(mountee) - .cloned() - { - true - } else { - false - }; - - if not_mounting_yet { - if let (Some(mounter_uid), Some(mountee_uid)) = ( - state.ecs().uid_from_entity(mounter), - state.ecs().uid_from_entity(mountee), - ) { - state.write_component( - mountee, - comp::MountState::MountedBy(mounter_uid.into()), - ); - state.write_component(mounter, comp::Mounting(mountee_uid.into())); - } - } - } - }, - - ServerEvent::Unmount(mounter) => { - let mountee_entity = state - .ecs() - .write_storage::() - .get(mounter) - .and_then(|mountee| state.ecs().entity_from_uid(mountee.0.into())); - if let Some(mountee_entity) = mountee_entity { - state - .ecs() - .write_storage::() - .get_mut(mountee_entity) - .map(|ms| *ms = comp::MountState::Unmounted); - } - state.delete_component::(mounter); - }, - - ServerEvent::Possess(possessor_uid, possesse_uid) => { - let ecs = state.ecs(); - if let (Some(possessor), Some(possesse)) = ( - ecs.entity_from_uid(possessor_uid.into()), - ecs.entity_from_uid(possesse_uid.into()), - ) { - // You can't possess other players - let mut clients = ecs.write_storage::(); - if clients.get_mut(possesse).is_none() { - if let Some(mut client) = clients.remove(possessor) { - client.notify(ServerMsg::SetPlayerEntity(possesse_uid.into())); - clients.insert(possesse, client).err().map(|e| { - error!( - "Error inserting client component during possession: {:?}", - e - ) - }); - // Create inventory if it doesn't exist - { - let mut inventories = ecs.write_storage::(); - if let Some(inventory) = inventories.get_mut(possesse) { - inventory.push(assets::load_expect_cloned( - "common.items.debug.possess", - )); - } else { - inventories - .insert(possesse, comp::Inventory { - slots: vec![ - Some(assets::load_expect_cloned( - "common.items.debug.possess", - )), - None, - None, - None, - None, - None, - None, - None, - ], - }) - .err() - .map(|e| { - error!( - "Error inserting inventory component during \ - possession: {:?}", - e - ) - }); - } - } - ecs.write_storage::() - .insert(possesse, comp::InventoryUpdate) - .err() - .map(|e| { - error!( - "Error inserting inventory update component during \ - possession: {:?}", - e - ) - }); - // Move player component - { - let mut players = ecs.write_storage::(); - if let Some(player) = players.remove(possessor) { - players.insert(possesse, player).err().map(|e| { - error!( - "Error inserting player component during \ - possession: {:?}", - e - ) - }); - } - } - // Transfer region subscription - { - let mut subscriptions = - ecs.write_storage::(); - if let Some(s) = subscriptions.remove(possessor) { - subscriptions.insert(possesse, s).err().map(|e| { - error!( - "Error inserting subscription component during \ - possession: {:?}", - e - ) - }); - } - } - // Remove will of the entity - ecs.write_storage::().remove(possesse); - // Reset controller of former shell - ecs.write_storage::() - .get_mut(possessor) - .map(|c| c.reset()); - // Transfer admin powers - { - let mut admins = ecs.write_storage::(); - if let Some(admin) = admins.remove(possessor) { - admins.insert(possesse, admin).err().map(|e| { - error!( - "Error inserting admin component during \ - possession: {:?}", - e - ) - }); - } - } - // Transfer waypoint - { - let mut waypoints = ecs.write_storage::(); - if let Some(waypoint) = waypoints.remove(possessor) { - waypoints.insert(possesse, waypoint).err().map(|e| { - error!( - "Error inserting waypoint component during \ - possession {:?}", - e - ) - }); - } - } - } - } - } - }, - - ServerEvent::CreateCharacter { - entity, - name, - body, - main, - } => { - Self::create_player_character( - state, - entity, - name, - body, - main, - &server_settings, - ); - sys::subscription::initialize_region_subscription(state.ecs(), entity); - }, - - ServerEvent::ExitIngame { entity } => { - // Create new entity with just `Client`, `Uid`, and `Player` components - // Easier than checking and removing all other known components - // Note: If other `ServerEvent`s are referring to this entity they will be - // disrupted - let maybe_client = state.ecs().write_storage::().remove(entity); - let maybe_uid = state.read_component_cloned::(entity); - let maybe_player = state.ecs().write_storage::().remove(entity); - if let (Some(mut client), Some(uid), Some(player)) = - (maybe_client, maybe_uid, maybe_player) - { - // Tell client its request was successful - client.allow_state(ClientState::Registered); - // Tell client to clear out other entities and its own components - client.notify(ServerMsg::ExitIngameCleanup); - - let entity_builder = - state.ecs_mut().create_entity().with(client).with(player); - // Ensure UidAllocator maps this uid to the new entity - let uid = entity_builder - .world - .write_resource::() - .allocate(entity_builder.entity, Some(uid.into())); - entity_builder.with(uid).build(); - } - // Delete old entity - if let Err(err) = state.delete_entity_recorded(entity) { - error!("Failed to delete entity when removing character: {:?}", err); - } - }, - - ServerEvent::CreateNpc { - pos, - stats, - body, - agent, - alignment, - scale, - } => { - state - .create_npc(pos, stats, body) - .with(agent) - .with(scale) - .with(alignment) - .build(); - }, - - ServerEvent::CreateWaypoint(pos) => { - self.create_object(comp::Pos(pos), comp::object::Body::CampfireLit) - .with(comp::LightEmitter { - offset: Vec3::unit_z() * 0.5, - col: Rgb::new(1.0, 0.65, 0.2), - strength: 2.0, - }) - .with(comp::WaypointArea::default()) - .build(); - }, - - ServerEvent::ClientDisconnect(entity) => { - // Tell other clients to remove from player list - if let (Some(uid), Some(_)) = ( - state.read_storage::().get(entity), - state.read_storage::().get(entity), - ) { - state.notify_registered_clients(ServerMsg::PlayerListUpdate( - PlayerListUpdate::Remove((*uid).into()), - )) - } - - // Delete client entity - if let Err(err) = state.delete_entity_recorded(entity) { - error!("Failed to delete disconnected client: {:?}", err); - } - - frontend_events.push(Event::ClientDisconnected { entity }); - }, - - ServerEvent::ChunkRequest(entity, key) => { - requested_chunks.push((entity, key)); - }, - - ServerEvent::ChatCmd(entity, cmd) => { - chat_commands.push((entity, cmd)); - }, - } - } - - // Generate requested chunks. - for (entity, key) in requested_chunks { - self.generate_chunk(entity, key); - } - - // Drop items - for (pos, ori, item) in dropped_items { - let vel = ori.0.normalized() * 5.0 - + Vec3::unit_z() * 10.0 - + Vec3::::zero().map(|_| rand::thread_rng().gen::() - 0.5) * 4.0; - self.create_object(Default::default(), comp::object::Body::Pouch) - .with(comp::Pos(pos.0 + Vec3::unit_z() * 0.25)) - .with(item) - .with(comp::Vel(vel)) - .build(); - } - - for (entity, cmd) in chat_commands { - self.process_chat_cmd(entity, cmd); - } - - frontend_events - } /// Execute a single server tick, handle input and update the game state by /// the given duration.