Merge branch 'imbris/player-list' into 'master'

Show players out of range on the player list, fix another overflow causing bug

See merge request veloren/veloren!691
This commit is contained in:
Imbris 2019-12-30 02:17:16 +00:00
commit cec303a3c6
12 changed files with 174 additions and 74 deletions

View File

@ -10,8 +10,8 @@ pub use specs::{join::Join, saveload::Marker, Entity as EcsEntity, ReadStorage,
use common::{
comp::{self, ControlEvent, Controller, ControllerInputs, InventoryManip},
msg::{
validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, RequestStateError,
ServerError, ServerInfo, ServerMsg, MAX_BYTES_CHAT_MSG,
validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, PlayerListUpdate,
RequestStateError, ServerError, ServerInfo, ServerMsg, MAX_BYTES_CHAT_MSG,
},
net::PostBox,
state::State,
@ -52,6 +52,7 @@ pub struct Client {
thread_pool: ThreadPool,
pub server_info: ServerInfo,
pub world_map: Arc<DynamicImage>,
pub player_list: HashMap<u64, String>,
postbox: PostBox<ClientMsg, ServerMsg>,
@ -71,7 +72,6 @@ pub struct Client {
impl Client {
/// Create a new `Client`.
#[allow(dead_code)]
pub fn new<A: Into<SocketAddr>>(addr: A, view_distance: Option<u32>) -> Result<Self, Error> {
let client_state = ClientState::Connected;
let mut postbox = PostBox::to(addr)?;
@ -137,6 +137,7 @@ impl Client {
thread_pool,
server_info,
world_map,
player_list: HashMap::new(),
postbox,
@ -154,7 +155,6 @@ impl Client {
})
}
#[allow(dead_code)]
pub fn with_thread_pool(mut self, thread_pool: ThreadPool) -> Self {
self.thread_pool = thread_pool;
self
@ -282,7 +282,6 @@ impl Client {
}
/// Send a chat message to the server.
#[allow(dead_code)]
pub fn send_chat(&mut self, msg: String) {
match validate_chat_msg(&msg) {
Ok(()) => self.postbox.send_message(ClientMsg::chat(msg)),
@ -294,7 +293,6 @@ impl Client {
}
/// Remove all cached terrain
#[allow(dead_code)]
pub fn clear_terrain(&mut self) {
self.state.clear_terrain();
self.pending_chunks.clear();
@ -316,7 +314,6 @@ impl Client {
}
/// Execute a single client tick, handle input and update the game state by the given duration.
#[allow(dead_code)]
pub fn tick(&mut self, inputs: ControllerInputs, dt: Duration) -> Result<Vec<Event>, Error> {
// This tick function is the centre of the Veloren universe. Most client-side things are
// managed from here, and as such it's important that it stays organised. Please consult
@ -389,6 +386,10 @@ impl Client {
// Remove chunks that are too far from the player.
let mut chunks_to_remove = Vec::new();
self.state.terrain().iter().for_each(|(key, _)| {
// Subtract 2 from the offset before computing squared magnitude
// 1 for the chunks needed bordering other chunks for meshing
// 1 as a buffer so that if the player moves back in that direction the chunks
// don't need to be reloaded
if (chunk_pos - key)
.map(|e: i32| (e.abs() as u32).checked_sub(2).unwrap_or(0))
.magnitude_squared()
@ -492,7 +493,6 @@ impl Client {
}
/// Clean up the client after a tick.
#[allow(dead_code)]
pub fn cleanup(&mut self) {
// Cleanup the local state
self.state.cleanup();
@ -531,6 +531,27 @@ impl Client {
},
ServerMsg::Shutdown => return Err(Error::ServerShutdown),
ServerMsg::InitialSync { .. } => return Err(Error::ServerWentMad),
ServerMsg::PlayerListUpdate(PlayerListUpdate::Init(list)) => {
self.player_list = list
}
ServerMsg::PlayerListUpdate(PlayerListUpdate::Add(uid, name)) => {
if let Some(old_name) = self.player_list.insert(uid, name.clone()) {
warn!("Received msg to insert {} with uid {} into the player list but there was already an entry for {} with the same uid that was overwritten!", name, uid, old_name);
}
}
ServerMsg::PlayerListUpdate(PlayerListUpdate::Remove(uid)) => {
if self.player_list.remove(&uid).is_none() {
warn!("Received msg to remove uid {} from the player list by they weren't in the list!", uid);
}
}
ServerMsg::PlayerListUpdate(PlayerListUpdate::Alias(uid, new_name)) => {
if let Some(name) = self.player_list.get_mut(&uid) {
*name = new_name;
} else {
warn!("Received msg to alias player with uid {} to {} but this uid is not in the player list", uid, new_name);
}
}
ServerMsg::Ping => self.postbox.send_message(ClientMsg::Pong),
ServerMsg::Pong => {
self.last_server_pong = Instant::now();
@ -602,9 +623,11 @@ impl Client {
}
self.pending_chunks.remove(&key);
}
ServerMsg::TerrainBlockUpdates(mut blocks) => blocks
.drain()
.for_each(|(pos, block)| self.state.set_block(pos, block)),
ServerMsg::TerrainBlockUpdates(mut blocks) => {
blocks.drain().for_each(|(pos, block)| {
self.state.set_block(pos, block);
});
}
ServerMsg::StateAnswer(Ok(state)) => {
self.client_state = state;
}
@ -636,24 +659,20 @@ impl Client {
}
/// Get the player's entity.
#[allow(dead_code)]
pub fn entity(&self) -> EcsEntity {
self.entity
}
/// Get the client state
#[allow(dead_code)]
pub fn get_client_state(&self) -> ClientState {
self.client_state
}
/// Get the current tick number.
#[allow(dead_code)]
pub fn get_tick(&self) -> u64 {
self.tick
}
#[allow(dead_code)]
pub fn get_ping_ms(&self) -> f64 {
self.last_ping_delta * 1000.0
}
@ -661,19 +680,16 @@ impl Client {
/// Get a reference to the client's worker thread pool. This pool should be used for any
/// computationally expensive operations that run outside of the main thread (i.e., threads that
/// block on I/O operations are exempt).
#[allow(dead_code)]
pub fn thread_pool(&self) -> &ThreadPool {
&self.thread_pool
}
/// Get a reference to the client's game state.
#[allow(dead_code)]
pub fn state(&self) -> &State {
&self.state
}
/// Get a mutable reference to the client's game state.
#[allow(dead_code)]
pub fn state_mut(&mut self) -> &mut State {
&mut self.state
}

View File

@ -23,7 +23,7 @@ pub enum ClientMsg {
Ping,
Pong,
ChatMsg {
chat_type: ChatType,
chat_type: ChatType, // This is unused afaik, TODO: remove
message: String,
},
PlayerPhysics {

View File

@ -5,7 +5,7 @@ pub mod server;
// Reexports
pub use self::client::ClientMsg;
pub use self::ecs_packet::EcsCompPacket;
pub use self::server::{RequestStateError, ServerError, ServerInfo, ServerMsg};
pub use self::server::{PlayerListUpdate, RequestStateError, ServerError, ServerInfo, ServerMsg};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum ClientState {

View File

@ -23,6 +23,14 @@ pub struct ServerInfo {
pub git_date: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PlayerListUpdate {
Init(HashMap<u64, String>),
Add(u64, String),
Remove(u64),
Alias(u64, String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ServerMsg {
InitialSync {
@ -31,6 +39,7 @@ pub enum ServerMsg {
time_of_day: state::TimeOfDay,
// world_map: Vec2<usize>, /*, Vec<u32>)*/
},
PlayerListUpdate(PlayerListUpdate),
StateAnswer(Result<ClientState, (RequestStateError, ClientState)>),
ForceState(ClientState),
Ping,

View File

@ -326,17 +326,14 @@ impl State {
// Apply terrain changes
let mut terrain = self.ecs.write_resource::<TerrainGrid>();
self.ecs
.read_resource::<BlockChange>()
.blocks
.iter()
.for_each(|(pos, block)| {
let _ = terrain.set(*pos, *block);
});
self.ecs.write_resource::<TerrainChanges>().modified_blocks = std::mem::replace(
let mut modified_blocks = std::mem::replace(
&mut self.ecs.write_resource::<BlockChange>().blocks,
Default::default(),
);
// Apply block modifications
// Only include in `TerrainChanges` if successful
modified_blocks.retain(|pos, block| terrain.set(*pos, *block).is_ok());
self.ecs.write_resource::<TerrainChanges>().modified_blocks = modified_blocks;
// Process local events
let events = self.ecs.read_resource::<EventBus<LocalEvent>>().recv_all();

View File

@ -167,6 +167,8 @@ impl<V: RectRasterableVol> VolGrid2d<V> {
pub struct CachedVolGrid2d<'a, V: RectRasterableVol> {
vol_grid_2d: &'a VolGrid2d<V>,
// This can't be invalidated by mutations of the chunks hashmap since we hold an immutable
// reference to the `VolGrid2d`
cache: Option<(Vec2<i32>, Arc<V>)>,
}
impl<'a, V: RectRasterableVol> CachedVolGrid2d<'a, V> {
@ -178,9 +180,9 @@ impl<'a, V: RectRasterableVol> CachedVolGrid2d<'a, V> {
}
}
impl<'a, V: RectRasterableVol + ReadVol> CachedVolGrid2d<'a, V> {
// Note: this may be invalidated by mutations of the chunks hashmap
#[inline(always)]
pub fn get(&mut self, pos: Vec3<i32>) -> Result<&V::Vox, VolGrid2dError<V>> {
// Calculate chunk key from block pos
let ck = VolGrid2d::<V>::chunk_key(pos);
let chunk = if self
.cache
@ -188,13 +190,16 @@ impl<'a, V: RectRasterableVol + ReadVol> CachedVolGrid2d<'a, V> {
.map(|(key, _)| *key == ck)
.unwrap_or(false)
{
// If the chunk with that key is in the cache use that
&self.cache.as_ref().unwrap().1
} else {
// Otherwise retrieve from the hashmap
let chunk = self
.vol_grid_2d
.chunks
.get(&ck)
.ok_or(VolGrid2dError::NoSuchChunk)?;
// Store most recently looked up chunk in the cache
self.cache = Some((ck, chunk.clone()));
chunk
};

View File

@ -8,11 +8,11 @@ use common::{
assets, comp,
event::{EventBus, ServerEvent},
hierarchical::ChunkPath,
msg::ServerMsg,
msg::{PlayerListUpdate, ServerMsg},
npc::{get_npc_name, NpcKind},
pathfinding::WorldPath,
state::TimeOfDay,
sync::WorldSyncExt,
sync::{Uid, WorldSyncExt},
terrain::{Block, BlockKind, TerrainChunkSize},
vol::RectVolSize,
};
@ -407,6 +407,19 @@ fn handle_alias(server: &mut Server, entity: EcsEntity, args: String, action: &C
.write_storage::<comp::Player>()
.get_mut(entity)
.map(|player| player.alias = alias);
// Update name on client player lists
let ecs = server.state.ecs();
if let (Some(uid), Some(player)) = (
ecs.read_storage::<Uid>().get(entity),
ecs.read_storage::<comp::Player>().get(entity),
) {
let msg = ServerMsg::PlayerListUpdate(PlayerListUpdate::Alias(
(*uid).into(),
player.alias.clone(),
));
server.state.notify_registered_clients(msg);
}
} else {
server.notify_client(entity, ServerMsg::private(String::from(action.help_string)));
}

View File

@ -25,7 +25,7 @@ use common::{
assets, comp,
effect::Effect,
event::{EventBus, ServerEvent},
msg::{ClientMsg, ClientState, ServerError, ServerInfo, ServerMsg},
msg::{ClientMsg, ClientState, PlayerListUpdate, ServerError, ServerInfo, ServerMsg},
net::PostOffice,
state::{BlockChange, State, TimeOfDay},
sync::{Uid, WorldSyncExt},
@ -762,6 +762,17 @@ impl Server {
}
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);
}

View File

@ -3,12 +3,16 @@ use crate::{auth_provider::AuthProvider, client::Client, CLIENT_TIMEOUT};
use common::{
comp::{Admin, Body, CanBuild, Controller, ForceUpdate, Ori, Player, Pos, Vel},
event::{EventBus, ServerEvent},
msg::{validate_chat_msg, ChatMsgValidationError, MAX_BYTES_CHAT_MSG},
msg::{ClientMsg, ClientState, RequestStateError, ServerMsg},
msg::{
validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, PlayerListUpdate,
RequestStateError, ServerMsg, MAX_BYTES_CHAT_MSG,
},
state::{BlockChange, Time},
sync::Uid,
terrain::{Block, TerrainGrid},
vol::Vox,
};
use hashbrown::HashMap;
use specs::{
Entities, Join, Read, ReadExpect, ReadStorage, System, Write, WriteExpect, WriteStorage,
};
@ -22,6 +26,7 @@ impl<'a> System<'a> for Sys {
Read<'a, Time>,
ReadExpect<'a, TerrainGrid>,
Write<'a, SysTimer<Self>>,
ReadStorage<'a, Uid>,
ReadStorage<'a, Body>,
ReadStorage<'a, CanBuild>,
ReadStorage<'a, Admin>,
@ -44,6 +49,7 @@ impl<'a> System<'a> for Sys {
time,
terrain,
mut timer,
uids,
bodies,
can_build,
admins,
@ -64,6 +70,14 @@ impl<'a> System<'a> for Sys {
let mut new_chat_msgs = Vec::new();
// Player list to send new players.
let player_list = (&uids, &players)
.join()
.map(|(uid, player)| ((*uid).into(), player.alias.clone()))
.collect::<HashMap<_, _>>();
// List of new players to update player lists of all clients.
let mut new_players = Vec::new();
for (entity, client) in (&entities, &mut clients).join() {
let mut disconnect = false;
let new_msgs = client.postbox.new_messages();
@ -127,10 +141,18 @@ impl<'a> System<'a> for Sys {
}
match client.client_state {
ClientState::Connected => {
// Add Player component to this client
let _ = players.insert(entity, player);
// Tell the client its request was successful.
client.allow_state(ClientState::Registered);
// Send initial player list
client.notify(ServerMsg::PlayerListUpdate(PlayerListUpdate::Init(
player_list.clone(),
)));
// Add to list to notify all clients of the new player
new_players.push(entity);
}
// Use RequestState instead (No need to send `player` again).
_ => client.error_state(RequestStateError::Impossible),
@ -281,6 +303,20 @@ impl<'a> System<'a> for Sys {
}
}
// Handle new players.
// Tell all clients to add them to the player list.
for entity in new_players {
if let (Some(uid), Some(player)) = (uids.get(entity), players.get(entity)) {
let msg = ServerMsg::PlayerListUpdate(PlayerListUpdate::Add(
(*uid).into(),
player.alias.clone(),
));
for client in (&mut clients).join().filter(|c| c.is_registered()) {
client.notify(msg.clone())
}
}
}
// Handle new chat messages.
for (entity, msg) in new_chat_msgs {
match msg {

View File

@ -71,6 +71,9 @@ impl<'a> System<'a> for Sys {
})
{
let chunk_pos = terrain.pos_key(pos.0.map(|e| e as i32));
// Subtract 2 from the offset before computing squared magnitude
// 1 since chunks need neighbors to be meshed
// 1 to act as a buffer if the player moves in that direction
let adjusted_dist_sqr = (Vec2::from(chunk_pos) - Vec2::from(key))
.map(|e: i32| (e.abs() as u32).checked_sub(2).unwrap_or(0))
.magnitude_squared();

View File

@ -1,13 +1,11 @@
use super::{img_ids::Imgs, Fonts, Show, TEXT_COLOR, TEXT_COLOR_3};
use common::comp;
use conrod_core::{
color,
widget::{self, Button, Image, Rectangle, Scrollbar, Text},
widget_ids, /*, Color*/
Colorable, Labelable, Positionable, Sizeable, Widget, WidgetCommon,
};
use specs::{Join, WorldExt};
use client::{self, Client};
@ -178,25 +176,12 @@ impl<'a> Widget for Social<'a> {
// Players list
// TODO: this list changes infrequently enough that it should not have to be recreated every frame
let ecs = self.client.state().ecs();
let players = ecs.read_storage::<comp::Player>();
let mut count = 0;
for player in players.join() {
if ids.player_names.len() <= count {
ids.update(|ids| {
ids.player_names
.resize(count + 1, &mut ui.widget_id_generator())
})
}
Text::new(&player.alias)
.down_from(ids.online_title, count as f64 * (15.0 + 3.0))
.font_size(15)
.font_id(self.fonts.cyri)
.color(TEXT_COLOR)
.set(ids.player_names[count], ui);
count += 1;
let count = self.client.player_list.len();
if ids.player_names.len() < count {
ids.update(|ids| {
ids.player_names
.resize(count, &mut ui.widget_id_generator())
})
}
Text::new(&format!("{} player(s) online\n", count))
.top_left_with_margins_on(ids.content_align, -2.0, 7.0)
@ -204,6 +189,14 @@ impl<'a> Widget for Social<'a> {
.font_id(self.fonts.cyri)
.color(TEXT_COLOR)
.set(ids.online_title, ui);
for (i, (_, player_alias)) in self.client.player_list.iter().enumerate() {
Text::new(player_alias)
.down(3.0)
.font_size(15)
.font_id(self.fonts.cyri)
.color(TEXT_COLOR)
.set(ids.player_names[i], ui);
}
}
// Friends Tab

View File

@ -17,7 +17,7 @@ use common::{
use crossbeam::channel;
use dot_vox::DotVoxData;
use frustum_query::frustum::Frustum;
use hashbrown::HashMap;
use hashbrown::{hash_map::Entry, HashMap};
use std::{f32, fmt::Debug, i32, marker::PhantomData, ops::Mul, time::Duration};
use vek::*;
@ -836,31 +836,48 @@ impl<V: RectRasterableVol> Terrain<V> {
.map(|(p, _)| *p)
{
let chunk_pos = client.state().terrain().pos_key(pos);
let new_mesh_state = ChunkMeshState {
pos: chunk_pos,
started_tick: current_tick,
active_worker: None,
};
// Only mesh if this chunk has all its neighbors
// If it does have all its neighbors either it should have already been meshed or is in
// mesh_todo
match self.mesh_todo.entry(chunk_pos) {
Entry::Occupied(mut entry) => {
entry.insert(new_mesh_state);
}
Entry::Vacant(entry) => {
if self.chunks.contains_key(&chunk_pos) {
entry.insert(new_mesh_state);
}
}
}
self.mesh_todo.insert(
chunk_pos,
ChunkMeshState {
pos: chunk_pos,
started_tick: current_tick,
active_worker: None,
},
);
// Handle chunks on chunk borders
// Handle block changes on chunk borders
for x in -1..2 {
for y in -1..2 {
let neighbour_pos = pos + Vec3::new(x, y, 0);
let neighbour_chunk_pos = client.state().terrain().pos_key(neighbour_pos);
if neighbour_chunk_pos != chunk_pos {
self.mesh_todo.insert(
neighbour_chunk_pos,
ChunkMeshState {
pos: neighbour_chunk_pos,
started_tick: current_tick,
active_worker: None,
},
);
let new_mesh_state = ChunkMeshState {
pos: neighbour_chunk_pos,
started_tick: current_tick,
active_worker: None,
};
// Only mesh if this chunk has all its neighbors
match self.mesh_todo.entry(neighbour_chunk_pos) {
Entry::Occupied(mut entry) => {
entry.insert(new_mesh_state);
}
Entry::Vacant(entry) => {
if self.chunks.contains_key(&neighbour_chunk_pos) {
entry.insert(new_mesh_state);
}
}
}
}
// TODO: Remesh all neighbours because we have complex lighting now