Merge branch 'event-chat-messages' into 'master'

Event chat messages

See merge request veloren/veloren!188

Former-commit-id: 5701dd41f52f76ee5733a3580da9fab59dc539e7
This commit is contained in:
Marcel 2019-05-28 10:45:24 +00:00
commit b5b61462ee
11 changed files with 136 additions and 62 deletions

View File

@ -294,14 +294,6 @@ impl Client {
.ecs_mut()
.write_storage::<comp::Jumping>()
.remove(self.entity);
self.state
.ecs_mut()
.write_storage::<comp::Dying>()
.remove(self.entity);
self.state
.ecs_mut()
.write_storage::<comp::Respawning>()
.remove(self.entity);
self.tick += 1;
Ok(frontend_events)

View File

@ -22,4 +22,5 @@ pub use inputs::Jumping;
pub use inputs::Respawning;
pub use player::Player;
pub use stats::Dying;
pub use stats::HealthSource;
pub use stats::Stats;

View File

@ -1,17 +1,33 @@
use crate::state::Time;
use crate::state::{Time, Uid};
use specs::{Component, FlaggedStorage, NullStorage, VecStorage};
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum HealthSource {
Attack { by: Uid }, // TODO: Implement weapon
Suicide,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Health {
pub current: u32,
pub maximum: u32,
pub last_change: Option<(i32, f64)>,
current: u32,
maximum: u32,
pub last_change: Option<(i32, f64, HealthSource)>,
}
impl Health {
pub fn change_by(&mut self, amount: i32) {
pub fn get_current(&self) -> u32 {
self.current
}
pub fn get_maximum(&self) -> u32 {
self.maximum
}
pub fn set_to(&mut self, amount: u32, cause: HealthSource) {
self.last_change = Some((amount as i32 - self.current as i32, 0.0, cause));
self.current = amount;
}
pub fn change_by(&mut self, amount: i32, cause: HealthSource) {
self.current = (self.current as i32 + amount).max(0) as u32;
self.last_change = Some((amount, 0.0));
self.last_change = Some((amount, 0.0, cause));
}
}
@ -19,10 +35,12 @@ impl Health {
pub struct Stats {
pub hp: Health,
pub xp: u32,
pub is_dead: bool,
}
impl Stats {
pub fn is_dead(&self) -> bool {
pub fn should_die(&self) -> bool {
// TODO: Remove
self.hp.current == 0
}
}
@ -36,6 +54,7 @@ impl Default for Stats {
last_change: None,
},
xp: 0,
is_dead: false,
}
}
}
@ -44,9 +63,11 @@ impl Component for Stats {
type Storage = FlaggedStorage<Self, VecStorage<Self>>;
}
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
pub struct Dying;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct Dying {
pub cause: HealthSource,
}
impl Component for Dying {
type Storage = NullStorage<Self>;
type Storage = VecStorage<Self>;
}

View File

@ -103,21 +103,24 @@ impl State {
ecs.register_synced::<comp::Actor>();
ecs.register_synced::<comp::Player>();
ecs.register_synced::<comp::Stats>();
ecs.register_synced::<comp::Attacking>();
ecs.register_synced::<comp::Attacking>(); // TODO: Don't send this to the client?
ecs.register::<comp::phys::ForceUpdate>();
// Register unsynced (or synced by other means) components.
// Register components synced by other means
ecs.register::<comp::phys::Pos>();
ecs.register::<comp::phys::Vel>();
ecs.register::<comp::phys::Dir>();
ecs.register::<comp::AnimationInfo>();
ecs.register::<comp::Attacking>();
// Register client-local components
ecs.register::<comp::Control>();
ecs.register::<comp::Jumping>();
// Register server-local components
ecs.register::<comp::Agent>();
ecs.register::<comp::Respawning>();
ecs.register::<comp::Gliding>();
ecs.register::<comp::Dying>();
ecs.register::<comp::Agent>();
ecs.register::<inventory::Inventory>();
// Register synced resources used by the ECS.

View File

@ -6,9 +6,10 @@ use vek::*;
use crate::{
comp::{
phys::{Dir, ForceUpdate, Pos, Vel},
Animation, AnimationInfo, Attacking, Control, Gliding, Jumping, Respawning, Stats,
Animation, AnimationInfo, Attacking, Control, Gliding, HealthSource, Jumping, Respawning,
Stats,
},
state::{DeltaTime, Time},
state::{DeltaTime, Time, Uid},
terrain::TerrainMap,
vol::{ReadVol, Vox},
};
@ -19,6 +20,7 @@ pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
ReadStorage<'a, Uid>,
Read<'a, Time>,
Read<'a, DeltaTime>,
ReadExpect<'a, TerrainMap>,
@ -39,6 +41,7 @@ impl<'a> System<'a> for Sys {
&mut self,
(
entities,
uids,
time,
dt,
terrain,
@ -142,8 +145,8 @@ impl<'a> System<'a> for Sys {
);
}
for (entity, pos, dir, attacking) in
(&entities, &positions, &directions, &mut attackings).join()
for (entity, &uid, pos, dir, attacking) in
(&entities, &uids, &positions, &directions, &mut attackings).join()
{
if !attacking.applied {
for (b, pos_b, mut stat_b, mut vel_b) in
@ -151,12 +154,12 @@ impl<'a> System<'a> for Sys {
{
// Check if it is a hit
if entity != b
&& !stat_b.is_dead()
&& !stat_b.is_dead
&& pos.0.distance_squared(pos_b.0) < 50.0
&& dir.0.angle_between(pos_b.0 - pos.0).to_degrees() < 70.0
{
// Deal damage
stat_b.hp.change_by(-10); // TODO: variable damage
stat_b.hp.change_by(-10, HealthSource::Attack { by: uid }); // TODO: variable damage and weapon
vel_b.0 += (pos_b.0 - pos.0).normalized() * 10.0;
vel_b.0.z = 15.0;
force_updates.insert(b, ForceUpdate);

View File

@ -21,8 +21,19 @@ impl<'a> System<'a> for Sys {
fn run(&mut self, (entities, dt, mut stats, mut dyings): Self::SystemData) {
for (entity, mut stat) in (&entities, &mut stats).join() {
if stat.hp.current == 0 {
dyings.insert(entity, Dying);
if stat.should_die() && !stat.is_dead {
// TODO: Replace is_dead with client states
dyings.insert(
entity,
Dying {
cause: stat
.hp
.last_change
.expect("Nothing caused the entity to die")
.2, // Safe because damage is necessary for death
},
);
stat.is_dead = true;
}
if let Some(change) = &mut stat.hp.last_change {
change.1 += dt.0 as f64;

View File

@ -150,7 +150,7 @@ fn handle_kill(server: &mut Server, entity: EcsEntity, args: String, action: &Ch
.ecs_mut()
.write_storage::<comp::Stats>()
.get_mut(entity)
.map(|s| s.hp.current = 0);
.map(|s| s.hp.set_to(0, comp::HealthSource::Suicide));
}
fn handle_alias(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) {

View File

@ -39,9 +39,16 @@ const CLIENT_TIMEOUT: f64 = 20.0; // Seconds
const DEFAULT_WORLD_SEED: u32 = 1337;
pub enum Event {
ClientConnected { entity: EcsEntity },
ClientDisconnected { entity: EcsEntity },
Chat { entity: EcsEntity, msg: String },
ClientConnected {
entity: EcsEntity,
},
ClientDisconnected {
entity: EcsEntity,
},
Chat {
entity: Option<EcsEntity>,
msg: String,
},
}
#[derive(Copy, Clone)]
@ -204,14 +211,37 @@ impl Server {
self.world.tick(dt);
// Sync deaths.
let todo_kill = (
&self.state.ecs().entities(),
&self.state.ecs().read_storage::<comp::Dying>(),
)
let ecs = &self.state.ecs();
let clients = &mut self.clients;
let todo_kill = (&ecs.entities(), &ecs.read_storage::<comp::Dying>())
.join()
.map(|(entity, _)| entity)
.map(|(entity, dying)| {
// Chat message
if let Some(player) = ecs.read_storage::<comp::Player>().get(entity) {
let msg = if let comp::HealthSource::Attack { by } = dying.cause {
ecs.entity_from_uid(by.into()).and_then(|attacker| {
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));
clients.notify_registered(ServerMsg::Chat(msg));
}
entity
})
.collect::<Vec<_>>();
// Actually kill them
for entity in todo_kill {
if let Some(client) = self.clients.get_mut(&entity) {
self.state
@ -220,6 +250,7 @@ impl Server {
client.force_state(ClientState::Dead);
} else {
self.state.ecs_mut().delete_entity_synced(entity);
continue;
}
}
@ -316,8 +347,6 @@ impl Server {
// Cleanup
let ecs = self.state.ecs_mut();
for entity in ecs.entities().join() {
ecs.write_storage::<comp::Jumping>().remove(entity);
ecs.write_storage::<comp::Gliding>().remove(entity);
ecs.write_storage::<comp::Dying>().remove(entity);
ecs.write_storage::<comp::Respawning>().remove(entity);
}
@ -415,7 +444,13 @@ impl Server {
},
ClientMsg::Register { player } => match client.client_state {
ClientState::Connected => {
Self::initialize_player(state, entity, client, player)
Self::initialize_player(state, entity, client, player);
if let Some(player) =
state.ecs().read_storage::<comp::Player>().get(entity)
{
new_chat_msgs
.push((None, format!("{} logged in", &player.alias)));
}
}
// Use RequestState instead (No need to send `player` again).
_ => client.error_state(RequestStateError::Impossible),
@ -464,7 +499,7 @@ impl Server {
ClientState::Registered
| ClientState::Spectator
| ClientState::Dead
| ClientState::Character => new_chat_msgs.push((entity, msg)),
| ClientState::Character => new_chat_msgs.push((Some(entity), msg)),
ClientState::Pending => {}
},
ClientMsg::PlayerAnimation(animation_info) => {
@ -507,7 +542,9 @@ impl Server {
// Always possible.
ClientMsg::Ping => client.postbox.send_message(ServerMsg::Pong),
ClientMsg::Pong => {}
ClientMsg::Disconnect => disconnect = true,
ClientMsg::Disconnect => {
disconnect = true;
}
}
}
} else if state.get_time() - client.last_ping > CLIENT_TIMEOUT || // Timeout
@ -521,6 +558,9 @@ impl Server {
}
if disconnect {
if let Some(player) = state.ecs().read_storage::<comp::Player>().get(entity) {
new_chat_msgs.push((None, format!("{} disconnected", &player.alias)));
}
disconnected_clients.push(entity);
client.postbox.send_message(ServerMsg::Disconnect);
true
@ -531,20 +571,23 @@ impl Server {
// Handle new chat messages.
for (entity, msg) in new_chat_msgs {
// Handle chat commands.
if msg.starts_with("/") && msg.len() > 1 {
let argv = String::from(&msg[1..]);
self.process_chat_cmd(entity, argv);
if let Some(entity) = entity {
// Handle chat commands.
if msg.starts_with("/") && msg.len() > 1 {
let argv = String::from(&msg[1..]);
self.process_chat_cmd(entity, argv);
} else {
self.clients.notify_registered(ServerMsg::Chat(
match self.state.ecs().read_storage::<comp::Player>().get(entity) {
Some(player) => format!("[{}] {}", &player.alias, msg),
None => format!("[<anon>] {}", msg),
},
));
}
} else {
self.clients.notify_registered(ServerMsg::Chat(
match self.state.ecs().read_storage::<comp::Player>().get(entity) {
Some(player) => format!("[{}] {}", &player.alias, msg),
None => format!("[<anon>] {}", msg),
},
));
frontend_events.push(Event::Chat { entity, msg });
self.clients.notify_registered(ServerMsg::Chat(msg.clone()));
}
frontend_events.push(Event::Chat { entity, msg });
}
// Handle client disconnects.

View File

@ -307,7 +307,7 @@ impl Hud {
let mut health_back_id_walker = self.ids.health_bar_backs.walk();
for (pos, name) in (&entities, &pos, &actor, &stats, player.maybe())
.join()
.filter(|(entity, _, _, stats, _)| *entity != me && !stats.is_dead())
.filter(|(entity, _, _, stats, _)| *entity != me && !stats.is_dead)
.map(|(entity, pos, actor, _, player)| match actor {
comp::Actor::Character {
name: char_name, ..
@ -338,7 +338,7 @@ impl Hud {
for (entity, pos, stats) in (&entities, &pos, &stats)
.join()
.filter(|(entity, _, stats)| *entity != me && !stats.is_dead())
.filter(|(entity, _, stats)| *entity != me && !stats.is_dead)
{
let back_id = health_back_id_walker.next(
&mut self.ids.health_bar_backs,
@ -358,7 +358,7 @@ impl Hud {
// Filling
Rectangle::fill_with(
[
120.0 * (stats.hp.current as f64 / stats.hp.maximum as f64),
120.0 * (stats.hp.get_current() as f64 / stats.hp.get_maximum() as f64),
8.0,
],
HP_COLOR,

View File

@ -78,7 +78,7 @@ impl<'a> Widget for Skillbar<'a> {
let next_level_xp = (level as f64).powi(4) - start_level_xp;
// TODO: We need a max xp value
let xp_percentage = (self.stats.xp as f64 - start_level_xp) / next_level_xp;
let hp_percentage = self.stats.hp.current as f64 / self.stats.hp.maximum as f64;
let hp_percentage = self.stats.hp.get_current() as f64 / self.stats.hp.get_maximum() as f64;
let mana_percentage = 1.0;
// TODO: Only show while aiming with a bow or when casting a spell.

View File

@ -475,7 +475,7 @@ impl FigureMgr {
// Change in health as color!
let col = stats
.and_then(|stats| stats.hp.last_change)
.map(|(change_by, time)| {
.map(|(change_by, time, _)| {
Rgba::broadcast(1.0)
+ Rgba::new(0.0, -1.0, -1.0, 0.0)
.map(|c| (c / (1.0 + DAMAGE_FADE_COEFFICIENT * time)) as f32)
@ -617,7 +617,7 @@ impl FigureMgr {
)
.join()
{
if stat.is_dead() {
if stat.is_dead {
continue;
}