2020-02-16 20:04:06 +00:00
|
|
|
use crate::{client::Client, Server, SpawnPoint, StateExt};
|
|
|
|
use common::{
|
2020-03-18 19:37:11 +00:00
|
|
|
assets,
|
2020-07-03 20:23:24 +00:00
|
|
|
comp::{self, item::lottery::Lottery, object, Body, HealthChange, HealthSource, Player, Stats},
|
2020-06-01 09:21:33 +00:00
|
|
|
msg::{PlayerListUpdate, ServerMsg},
|
2020-02-16 20:04:06 +00:00
|
|
|
state::BlockChange,
|
|
|
|
sync::{Uid, WorldSyncExt},
|
2020-04-01 14:07:10 +00:00
|
|
|
sys::combat::{BLOCK_ANGLE, BLOCK_EFFICIENCY},
|
2020-02-16 20:04:06 +00:00
|
|
|
terrain::{Block, TerrainGrid},
|
|
|
|
vol::{ReadVol, Vox},
|
|
|
|
};
|
2020-03-22 19:39:50 +00:00
|
|
|
use specs::{join::Join, Entity as EcsEntity, WorldExt};
|
2020-06-21 14:26:06 +00:00
|
|
|
use tracing::error;
|
2020-03-23 17:13:14 +00:00
|
|
|
use vek::Vec3;
|
2020-03-22 19:39:50 +00:00
|
|
|
|
2020-02-16 20:04:06 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-03 17:33:37 +00:00
|
|
|
/// Handle an entity dying. If it is a player, it will send a message to all
|
|
|
|
/// other players. If the entity that killed it had stats, then give it exp for
|
|
|
|
/// the kill. Experience given is equal to the level of the entity that was
|
|
|
|
/// killed times 10.
|
2020-02-16 20:04:06 +00:00
|
|
|
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) {
|
2020-03-20 13:26:18 +00:00
|
|
|
let msg = if let HealthSource::Attack { by }
|
|
|
|
| HealthSource::Projectile { owner: Some(by) } = cause
|
|
|
|
{
|
2020-02-16 20:04:06 +00:00
|
|
|
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));
|
|
|
|
|
2020-06-12 17:44:29 +00:00
|
|
|
state.notify_registered_clients(comp::ChatType::Kill.server_msg(msg));
|
2020-02-16 20:04:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
// 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() {
|
2020-03-20 13:26:18 +00:00
|
|
|
if let HealthSource::Attack { by } | HealthSource::Projectile { owner: Some(by) } =
|
|
|
|
cause
|
|
|
|
{
|
2020-02-16 20:04:06 +00:00
|
|
|
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()
|
2020-06-21 21:47:49 +00:00
|
|
|
.map(|e| error!(?e, ?entity, "Failed to set zero vel on dead client"));
|
2020-02-16 20:04:06 +00:00
|
|
|
state
|
|
|
|
.ecs()
|
|
|
|
.write_storage()
|
|
|
|
.insert(entity, comp::ForceUpdate)
|
|
|
|
.err()
|
2020-06-21 21:47:49 +00:00
|
|
|
.map(|e| error!(?e, ?entity, "Failed to insert ForceUpdate on dead client"));
|
2020-05-04 15:15:31 +00:00
|
|
|
state
|
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::LightEmitter>()
|
|
|
|
.remove(entity);
|
2020-02-16 20:04:06 +00:00
|
|
|
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());
|
2020-06-11 17:06:11 +00:00
|
|
|
} else if state.ecs().read_storage::<comp::Agent>().contains(entity) {
|
|
|
|
// Replace npc with loot
|
|
|
|
let _ = state
|
|
|
|
.ecs()
|
|
|
|
.write_storage()
|
|
|
|
.insert(entity, Body::Object(object::Body::Pouch));
|
2020-05-15 15:05:50 +00:00
|
|
|
|
2020-06-11 17:06:11 +00:00
|
|
|
let mut item_drops = state.ecs().write_storage::<comp::ItemDrop>();
|
|
|
|
let item = if let Some(item_drop) = item_drops.get(entity).cloned() {
|
|
|
|
item_drops.remove(entity);
|
|
|
|
item_drop.0
|
|
|
|
} else {
|
2020-07-04 16:40:23 +00:00
|
|
|
let chosen = assets::load_expect::<Lottery<_>>("common.loot_table");
|
2020-07-03 20:23:24 +00:00
|
|
|
let chosen = chosen.choose();
|
|
|
|
|
|
|
|
assets::load_expect_cloned(chosen)
|
2020-06-11 17:06:11 +00:00
|
|
|
};
|
2020-05-15 15:05:50 +00:00
|
|
|
|
2020-06-11 17:06:11 +00:00
|
|
|
let _ = state.ecs().write_storage().insert(entity, item);
|
2020-05-15 15:05:50 +00:00
|
|
|
|
2020-06-11 17:06:11 +00:00
|
|
|
state.ecs().write_storage::<comp::Stats>().remove(entity);
|
|
|
|
state.ecs().write_storage::<comp::Agent>().remove(entity);
|
|
|
|
state
|
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::LightEmitter>()
|
|
|
|
.remove(entity);
|
|
|
|
state
|
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::CharacterState>()
|
|
|
|
.remove(entity);
|
|
|
|
state
|
|
|
|
.ecs()
|
|
|
|
.write_storage::<comp::Controller>()
|
|
|
|
.remove(entity);
|
|
|
|
} else {
|
|
|
|
let _ = state
|
|
|
|
.delete_entity_recorded(entity)
|
2020-06-21 21:47:49 +00:00
|
|
|
.map_err(|e| error!(?e, ?entity, "Failed to delete destroyed entity"));
|
2020-06-11 17:06:11 +00:00
|
|
|
}
|
2020-03-18 19:37:11 +00:00
|
|
|
|
2020-06-11 17:06:11 +00:00
|
|
|
// TODO: Add Delete(time_left: Duration) component
|
|
|
|
/*
|
|
|
|
// If not a player delete the entity
|
|
|
|
if let Err(err) = state.delete_entity_recorded(entity) {
|
2020-06-21 21:47:49 +00:00
|
|
|
error!(?e, "Failed to delete destroyed entity");
|
2020-02-16 20:04:06 +00:00
|
|
|
}
|
2020-06-11 17:06:11 +00:00
|
|
|
*/
|
2020-02-16 20:04:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>) {
|
|
|
|
let state = &server.state;
|
2020-04-08 12:40:17 +00:00
|
|
|
if vel.z <= -30.0 {
|
2020-02-16 20:04:06 +00:00
|
|
|
if let Some(stats) = state.ecs().write_storage::<comp::Stats>().get_mut(entity) {
|
2020-04-08 12:40:17 +00:00
|
|
|
let falldmg = vel.z.powi(2) as i32 / 20 - 40;
|
|
|
|
stats.health.change_by(comp::HealthChange {
|
|
|
|
amount: -falldmg,
|
|
|
|
cause: comp::HealthSource::World,
|
|
|
|
});
|
2020-02-16 20:04:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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()
|
2020-06-21 21:47:49 +00:00
|
|
|
.map(|e| {
|
2020-02-16 20:04:06 +00:00
|
|
|
error!(
|
2020-06-21 21:47:49 +00:00
|
|
|
?e,
|
|
|
|
"Error inserting ForceUpdate component when respawning client"
|
2020-02-16 20:04:06 +00:00
|
|
|
)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-22 19:39:50 +00:00
|
|
|
pub fn handle_explosion(server: &Server, pos: Vec3<f32>, power: f32, owner: Option<Uid>) {
|
|
|
|
// Go through all other entities
|
2020-03-27 17:38:01 +00:00
|
|
|
let hit_range = 3.0 * power;
|
2020-03-22 19:39:50 +00:00
|
|
|
let ecs = &server.state.ecs();
|
2020-03-23 17:13:14 +00:00
|
|
|
for (pos_b, ori_b, character_b, stats_b) in (
|
2020-03-22 19:39:50 +00:00
|
|
|
&ecs.read_storage::<comp::Pos>(),
|
|
|
|
&ecs.read_storage::<comp::Ori>(),
|
2020-07-05 12:39:28 +00:00
|
|
|
ecs.read_storage::<comp::CharacterState>().maybe(),
|
2020-03-22 19:39:50 +00:00
|
|
|
&mut ecs.write_storage::<comp::Stats>(),
|
|
|
|
)
|
|
|
|
.join()
|
|
|
|
{
|
2020-03-23 11:50:08 +00:00
|
|
|
let distance_squared = pos.distance_squared(pos_b.0);
|
2020-03-22 19:39:50 +00:00
|
|
|
// Check if it is a hit
|
|
|
|
if !stats_b.is_dead
|
|
|
|
// Spherical wedge shaped attack field
|
|
|
|
// RADIUS
|
2020-03-23 11:50:08 +00:00
|
|
|
&& distance_squared < hit_range.powi(2)
|
2020-03-22 19:39:50 +00:00
|
|
|
{
|
|
|
|
// Weapon gives base damage
|
2020-03-23 17:13:14 +00:00
|
|
|
let mut dmg = ((1.0 - distance_squared / hit_range.powi(2)) * power * 10.0) as u32;
|
2020-03-22 19:39:50 +00:00
|
|
|
|
|
|
|
if rand::random() {
|
|
|
|
dmg += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Block
|
2020-07-05 12:39:28 +00:00
|
|
|
if character_b.map(|c_b| c_b.is_block()).unwrap_or(false)
|
2020-03-22 19:39:50 +00:00
|
|
|
&& ori_b.0.angle_between(pos - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0
|
|
|
|
{
|
|
|
|
dmg = (dmg as f32 * (1.0 - BLOCK_EFFICIENCY)) as u32
|
|
|
|
}
|
|
|
|
|
|
|
|
stats_b.health.change_by(HealthChange {
|
|
|
|
amount: -(dmg as i32),
|
|
|
|
cause: HealthSource::Projectile { owner },
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-16 20:04:06 +00:00
|
|
|
const RAYS: usize = 500;
|
|
|
|
|
2020-03-23 17:13:14 +00:00
|
|
|
// Color terrain
|
|
|
|
let mut touched_blocks = Vec::new();
|
2020-03-23 21:52:35 +00:00
|
|
|
let color_range = power * 2.7;
|
2020-02-16 20:04:06 +00:00
|
|
|
for _ in 0..RAYS {
|
|
|
|
let dir = Vec3::new(
|
|
|
|
rand::random::<f32>() - 0.5,
|
|
|
|
rand::random::<f32>() - 0.5,
|
|
|
|
rand::random::<f32>() - 0.5,
|
|
|
|
)
|
|
|
|
.normalized();
|
|
|
|
|
2020-03-23 17:13:14 +00:00
|
|
|
let _ = ecs
|
|
|
|
.read_resource::<TerrainGrid>()
|
2020-03-23 21:52:35 +00:00
|
|
|
.ray(pos, pos + dir * color_range)
|
2020-03-23 17:13:14 +00:00
|
|
|
.until(|_| rand::random::<f32>() < 0.05)
|
2020-07-05 12:39:28 +00:00
|
|
|
.for_each(|_: &Block, pos| touched_blocks.push(pos))
|
2020-03-23 17:13:14 +00:00
|
|
|
.cast();
|
|
|
|
}
|
|
|
|
|
|
|
|
let terrain = ecs.read_resource::<TerrainGrid>();
|
|
|
|
let mut block_change = ecs.write_resource::<BlockChange>();
|
2020-03-23 21:52:35 +00:00
|
|
|
for block_pos in touched_blocks {
|
|
|
|
if let Ok(block) = terrain.get(block_pos) {
|
|
|
|
let diff2 = block_pos.map(|b| b as f32).distance_squared(pos);
|
|
|
|
let fade = (1.0 - diff2 / color_range.powi(2)).max(0.0);
|
2020-03-23 17:13:14 +00:00
|
|
|
if let Some(mut color) = block.get_color() {
|
2020-03-23 21:52:35 +00:00
|
|
|
let r = color[0] as f32 + (fade * (color[0] as f32 * 0.5 - color[0] as f32));
|
|
|
|
let g = color[1] as f32 + (fade * (color[1] as f32 * 0.3 - color[1] as f32));
|
|
|
|
let b = color[2] as f32 + (fade * (color[2] as f32 * 0.3 - color[2] as f32));
|
|
|
|
color[0] = r as u8;
|
|
|
|
color[1] = g as u8;
|
|
|
|
color[2] = b as u8;
|
|
|
|
block_change.set(block_pos, Block::new(block.kind(), color));
|
2020-03-23 17:13:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Destroy terrain
|
|
|
|
for _ in 0..RAYS {
|
|
|
|
let dir = Vec3::new(
|
|
|
|
rand::random::<f32>() - 0.5,
|
|
|
|
rand::random::<f32>() - 0.5,
|
2020-03-23 21:52:35 +00:00
|
|
|
rand::random::<f32>() - 0.15,
|
2020-03-23 17:13:14 +00:00
|
|
|
)
|
|
|
|
.normalized();
|
2020-02-16 20:04:06 +00:00
|
|
|
|
2020-07-04 23:55:13 +00:00
|
|
|
let terrain = ecs.read_resource::<TerrainGrid>();
|
|
|
|
let _ = terrain
|
2020-03-22 19:39:50 +00:00
|
|
|
.ray(pos, pos + dir * power)
|
2020-07-05 10:13:16 +00:00
|
|
|
.until(|block| block.is_fluid() || rand::random::<f32>() < 0.05)
|
2020-07-05 12:39:28 +00:00
|
|
|
.for_each(|block: &Block, pos| {
|
|
|
|
if block.is_explodable() {
|
2020-07-04 23:55:13 +00:00
|
|
|
block_change.set(pos, Block::empty());
|
|
|
|
}
|
|
|
|
})
|
2020-02-16 20:04:06 +00:00
|
|
|
.cast();
|
|
|
|
}
|
|
|
|
}
|
2020-06-01 09:21:33 +00:00
|
|
|
|
|
|
|
pub fn handle_level_up(server: &mut Server, entity: EcsEntity, new_level: u32) {
|
|
|
|
let uids = server.state.ecs().read_storage::<Uid>();
|
|
|
|
let uid = uids
|
|
|
|
.get(entity)
|
2020-06-02 06:11:47 +00:00
|
|
|
.expect("Failed to fetch uid component for entity.");
|
2020-06-01 09:21:33 +00:00
|
|
|
|
|
|
|
server
|
|
|
|
.state
|
|
|
|
.notify_registered_clients(ServerMsg::PlayerListUpdate(PlayerListUpdate::LevelChange(
|
2020-06-02 06:11:47 +00:00
|
|
|
*uid, new_level,
|
2020-06-01 09:21:33 +00:00
|
|
|
)));
|
|
|
|
}
|