Merge branch 'imarv/refactor-server-events' into 'master'

Move ServerEvents into own module

Closes #494

See merge request veloren/veloren!809
This commit is contained in:
Songtronix 2020-02-19 18:30:53 +00:00
commit 2d0b5d514a
7 changed files with 829 additions and 743 deletions

View File

@ -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<String>,
) {
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<f32>,
body: Body,
light: Option<LightEmitter>,
projectile: Projectile,
gravity: Option<Gravity>,
) {
let state = server.state_mut();
let mut pos = state
.ecs()
.read_storage::<Pos>()
.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<f32>) {
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();
}

View File

@ -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::<Stats>().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::<Player>().get(entity) {
let msg = if let HealthSource::Attack { by } = cause {
state.ecs().entity_from_uid(by.into()).and_then(|attacker| {
state
.ecs()
.read_storage::<Player>()
.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::<Stats>();
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::<Client>()
.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::<comp::Energy>()
.get_mut(entity)
.map(|energy| energy.set_to(energy.maximum(), comp::EnergySource::Revive));
let _ = state
.ecs()
.write_storage::<comp::CharacterState>()
.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<f32>) {
let state = &server.state;
if vel.z <= -37.0 {
if let Some(stats) = state.ecs().write_storage::<comp::Stats>().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::<Client>()
.get_mut(entity)
.is_some()
{
let respawn_point = state
.read_component_cloned::<comp::Waypoint>(entity)
.map(|wp| wp.get_pos())
.unwrap_or(state.ecs().read_resource::<SpawnPoint>().0);
state
.ecs()
.write_storage::<comp::Stats>()
.get_mut(entity)
.map(|stats| stats.revive());
state
.ecs()
.write_storage::<comp::Pos>()
.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<f32>, radius: f32) {
const RAYS: usize = 500;
for _ in 0..RAYS {
let dir = Vec3::new(
rand::random::<f32>() - 0.5,
rand::random::<f32>() - 0.5,
rand::random::<f32>() - 0.5,
)
.normalized();
let ecs = server.state.ecs();
let mut block_change = ecs.write_resource::<BlockChange>();
let _ = ecs
.read_resource::<TerrainGrid>()
.ray(pos, pos + dir * radius)
.until(|_| rand::random::<f32>() < 0.05)
.for_each(|pos| block_change.set(pos, Block::empty()))
.cast();
}
}

View File

@ -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::<comp::Mounting>()
.get(mounter)
.is_none()
{
let not_mounting_yet = if let Some(comp::MountState::Unmounted) = state
.ecs()
.read_storage::<comp::MountState>()
.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::<comp::Mounting>()
.get(mounter)
.and_then(|mountee| state.ecs().entity_from_uid(mountee.0.into()));
if let Some(mountee_entity) = mountee_entity {
state
.ecs()
.write_storage::<comp::MountState>()
.get_mut(mountee_entity)
.map(|ms| *ms = comp::MountState::Unmounted);
}
state.delete_component::<comp::Mounting>(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::<Client>();
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::<comp::Inventory>();
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::<comp::InventoryUpdate>()
.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::<comp::Player>();
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::<RegionSubscription>();
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::<comp::Agent>().remove(possesse);
// Reset controller of former shell
ecs.write_storage::<comp::Controller>()
.get_mut(possessor)
.map(|c| c.reset());
// Transfer admin powers
{
let mut admins = ecs.write_storage::<comp::Admin>();
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::<comp::Waypoint>();
if let Some(waypoint) = waypoints.remove(possessor) {
waypoints.insert(possesse, waypoint).err().map(|e| {
error!(
"Error inserting waypoint component during possession {:?}",
e
)
});
}
}
}
}
}
}

View File

@ -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::<comp::Item>()
.get_mut(item_entity)
.map(|item| (item.clone(), item_entity))
}),
state
.ecs()
.write_storage::<comp::Inventory>()
.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::<comp::Inventory>()
.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::<comp::Inventory>()
.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::<comp::Stats>().get_mut(entity)
{
// Insert old item into inventory
if let Some(old_item) = stats.equipment.main.take() {
state
.ecs()
.write_storage::<comp::Inventory>()
.get_mut(entity)
.map(|inv| inv.insert(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::<comp::Pos>().get(entity)
{
if (
&state.read_storage::<comp::Alignment>(),
&state.read_storage::<comp::Agent>(),
)
.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::<comp::Pos>(),
&state.ecs().read_storage::<comp::Alignment>(),
)
.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::<comp::Inventory>()
.get_mut(entity)
.map(|inv| inv.insert(slot, item));
}
},
},
_ => {
let _ = state
.ecs()
.write_storage::<comp::Inventory>()
.get_mut(entity)
.map(|inv| inv.insert(slot, item));
},
}
}
state.write_component(entity, comp::InventoryUpdate);
},
comp::InventoryManip::Swap(a, b) => {
state
.ecs()
.write_storage::<comp::Inventory>()
.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::<comp::Inventory>()
.get_mut(entity)
.and_then(|inv| inv.remove(slot));
if let (Some(item), Some(pos)) =
(item, state.ecs().read_storage::<comp::Pos>().get(entity))
{
dropped_items.push((
*pos,
state
.ecs()
.read_storage::<comp::Ori>()
.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::<f32>::zero().map(|_| rand::thread_rng().gen::<f32>() - 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();
}
}

109
server/src/events/mod.rs Normal file
View File

@ -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<EcsEntity>,
msg: String,
},
}
impl Server {
pub fn handle_events(&mut self) -> Vec<Event> {
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::<EventBus<ServerEvent>>()
.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
}
}

View File

@ -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::<Client>().remove(entity);
let maybe_uid = state.read_component_cloned::<Uid>(entity);
let maybe_player = state.ecs().write_storage::<comp::Player>().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::<UidAllocator>()
.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::<Uid>().get(entity),
state.read_storage::<comp::Player>().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 }
}

View File

@ -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<EcsEntity>,
msg: String,
},
}
#[derive(Copy, Clone)]
struct SpawnPoint(Vec3<f32>);
@ -317,727 +304,6 @@ impl Server {
}
/// Handle events coming through via the event bus
fn handle_events(&mut self) -> Vec<Event> {
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::<EventBus<ServerEvent>>()
.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::<f32>() - 0.5,
rand::random::<f32>() - 0.5,
rand::random::<f32>() - 0.5,
)
.normalized();
let ecs = state.ecs();
let mut block_change = ecs.write_resource::<BlockChange>();
let _ = ecs
.read_resource::<TerrainGrid>()
.ray(pos, pos + dir * radius)
.until(|_| rand::random::<f32>() < 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::<comp::Pos>()
.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::<comp::Stats>().get_mut(entity) {
stats.health.change_by(change);
}
}
},
ServerEvent::Destroy { entity, cause } => {
// Chat message
if let Some(player) = state.ecs().read_storage::<comp::Player>().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::<comp::Player>()
.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::<comp::Stats>();
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::<Client>()
.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::<comp::Energy>()
.get_mut(entity)
.map(|energy| {
energy.set_to(energy.maximum(), comp::EnergySource::Revive)
});
let _ = state
.ecs()
.write_storage::<comp::CharacterState>()
.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::<comp::Item>()
.get_mut(item_entity)
.map(|item| (item.clone(), item_entity))
}),
state
.ecs()
.write_storage::<comp::Inventory>()
.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::<comp::Inventory>()
.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::<comp::Inventory>()
.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::<comp::Stats>()
.get_mut(entity)
{
// Insert old item into inventory
if let Some(old_item) = stats.equipment.main.take() {
state
.ecs()
.write_storage::<comp::Inventory>()
.get_mut(entity)
.map(|inv| inv.insert(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::<comp::Pos>().get(entity)
{
if (
&state.read_storage::<comp::Alignment>(),
&state.read_storage::<comp::Agent>(),
)
.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::<comp::Pos>(),
&state
.ecs()
.read_storage::<comp::Alignment>(),
)
.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::<comp::Inventory>()
.get_mut(entity)
.map(|inv| inv.insert(slot, item));
}
},
},
_ => {
let _ = state
.ecs()
.write_storage::<comp::Inventory>()
.get_mut(entity)
.map(|inv| inv.insert(slot, item));
},
}
}
state.write_component(entity, comp::InventoryUpdate);
},
comp::InventoryManip::Swap(a, b) => {
state
.ecs()
.write_storage::<comp::Inventory>()
.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::<comp::Inventory>()
.get_mut(entity)
.and_then(|inv| inv.remove(slot));
if let (Some(item), Some(pos)) =
(item, state.ecs().read_storage::<comp::Pos>().get(entity))
{
dropped_items.push((
*pos,
state
.ecs()
.read_storage::<comp::Ori>()
.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::<Client>()
.get_mut(entity)
.is_some()
{
let respawn_point = state
.read_component_cloned::<comp::Waypoint>(entity)
.map(|wp| wp.get_pos())
.unwrap_or(state.ecs().read_resource::<SpawnPoint>().0);
state
.ecs()
.write_storage::<comp::Stats>()
.get_mut(entity)
.map(|stats| stats.revive());
state
.ecs()
.write_storage::<comp::Pos>()
.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::<comp::Stats>().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::<comp::Mounting>()
.get(mounter)
.is_none()
{
let not_mounting_yet = if let Some(comp::MountState::Unmounted) = state
.ecs()
.read_storage::<comp::MountState>()
.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::<comp::Mounting>()
.get(mounter)
.and_then(|mountee| state.ecs().entity_from_uid(mountee.0.into()));
if let Some(mountee_entity) = mountee_entity {
state
.ecs()
.write_storage::<comp::MountState>()
.get_mut(mountee_entity)
.map(|ms| *ms = comp::MountState::Unmounted);
}
state.delete_component::<comp::Mounting>(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::<Client>();
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::<comp::Inventory>();
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::<comp::InventoryUpdate>()
.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::<comp::Player>();
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::<RegionSubscription>();
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::<comp::Agent>().remove(possesse);
// Reset controller of former shell
ecs.write_storage::<comp::Controller>()
.get_mut(possessor)
.map(|c| c.reset());
// Transfer admin powers
{
let mut admins = ecs.write_storage::<comp::Admin>();
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::<comp::Waypoint>();
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::<Client>().remove(entity);
let maybe_uid = state.read_component_cloned::<Uid>(entity);
let maybe_player = state.ecs().write_storage::<comp::Player>().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::<UidAllocator>()
.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::<Uid>().get(entity),
state.read_storage::<comp::Player>().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::<f32>::zero().map(|_| rand::thread_rng().gen::<f32>() - 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.