mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
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:
commit
2d0b5d514a
84
server/src/events/entity_creation.rs
Normal file
84
server/src/events/entity_creation.rs
Normal 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();
|
||||
}
|
175
server/src/events/entity_manipulation.rs
Normal file
175
server/src/events/entity_manipulation.rs
Normal 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();
|
||||
}
|
||||
}
|
171
server/src/events/interaction.rs
Normal file
171
server/src/events/interaction.rs
Normal 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
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
221
server/src/events/inventory_manip.rs
Normal file
221
server/src/events/inventory_manip.rs
Normal 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
109
server/src/events/mod.rs
Normal 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
|
||||
}
|
||||
}
|
60
server/src/events/player.rs
Normal file
60
server/src/events/player.rs
Normal 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 }
|
||||
}
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user