create a ServerMsg and ClientMsg enum and verify the state when in debug mode to benefit from the transition

This commit is contained in:
Marcel Märtens 2020-10-07 12:31:49 +02:00
parent e8452fafc6
commit ff374eab59
25 changed files with 668 additions and 575 deletions

View File

@ -25,11 +25,11 @@ use common::{
},
event::{EventBus, LocalEvent},
msg::{
validate_chat_msg, ChatMsgValidationError, ClientCharacterScreenMsg, ClientGeneralMsg,
ClientInGameMsg, ClientIngame, ClientRegisterMsg, ClientType, DisconnectReason,
validate_chat_msg, ChatMsgValidationError, ClientCharacterScreen, ClientGeneral,
ClientInGame, ClientIngame, ClientMsg, ClientRegister, ClientType, DisconnectReason,
InviteAnswer, Notification, PingMsg, PlayerInfo, PlayerListUpdate, RegisterError,
ServerCharacterScreenMsg, ServerGeneralMsg, ServerInGameMsg, ServerInfo, ServerInitMsg,
ServerRegisterAnswerMsg, MAX_BYTES_CHAT_MSG,
ServerCharacterScreen, ServerGeneral, ServerInGame, ServerInfo, ServerInit,
ServerRegisterAnswer, MAX_BYTES_CHAT_MSG,
},
outcome::Outcome,
recipe::RecipeBook,
@ -71,7 +71,7 @@ pub enum Event {
pub struct Client {
registered: bool,
client_ingame: Option<ClientIngame>,
in_game: Option<ClientIngame>,
thread_pool: ThreadPool,
pub server_info: ServerInfo,
/// Just the "base" layer for LOD; currently includes colors and nothing
@ -193,7 +193,7 @@ impl Client {
max_group_size,
client_timeout,
) = match block_on(register_stream.recv())? {
ServerInitMsg::GameSync {
ServerInit::GameSync {
entity_package,
time_of_day,
max_group_size,
@ -362,7 +362,7 @@ impl Client {
client_timeout,
))
},
ServerInitMsg::TooManyPlayers => Err(Error::TooManyPlayers),
ServerInit::TooManyPlayers => Err(Error::TooManyPlayers),
}?;
ping_stream.send(PingMsg::Ping)?;
@ -376,7 +376,7 @@ impl Client {
Ok(Self {
registered: false,
client_ingame: None,
in_game: None,
thread_pool,
server_info,
world_map,
@ -444,10 +444,9 @@ impl Client {
}
).unwrap_or(Ok(username))?;
self.register_stream
.send(ClientRegisterMsg { token_or_username })?;
self.send_msg_err(ClientRegister { token_or_username })?;
match block_on(self.register_stream.recv::<ServerRegisterAnswerMsg>())? {
match block_on(self.register_stream.recv::<ServerRegisterAnswer>())? {
Err(RegisterError::AlreadyLoggedIn) => Err(Error::AlreadyLoggedIn),
Err(RegisterError::AuthError(err)) => Err(Error::AuthErr(err)),
Err(RegisterError::InvalidCharacter) => Err(Error::InvalidCharacter),
@ -460,14 +459,69 @@ impl Client {
}
}
fn send_msg_err<S>(&mut self, msg: S) -> Result<(), network::StreamError>
where
S: Into<ClientMsg>,
{
let msg: ClientMsg = msg.into();
#[cfg(debug_assertions)]
{
//There assertions veriy that the state is correct when a msg is send!
match &msg {
ClientMsg::Type(_) | ClientMsg::Register(_) => assert!(
!self.registered,
"must not send msg when already registered"
),
ClientMsg::CharacterScreen(_) => {
assert!(
self.registered,
"must not send character_screen msg when not registered"
);
assert!(
self.in_game.is_none(),
"must not send character_screen msg when not in character screen"
);
},
ClientMsg::InGame(_) => assert!(
self.in_game.is_some(),
"must not send in_game msg when not in game"
),
ClientMsg::General(_) => assert!(
self.registered,
"must not send general msg when not registered"
),
ClientMsg::Ping(_) => (),
}
}
match msg {
ClientMsg::Type(msg) => self.register_stream.send(msg),
ClientMsg::Register(msg) => self.register_stream.send(msg),
ClientMsg::CharacterScreen(msg) => self.character_screen_stream.send(msg),
ClientMsg::InGame(msg) => self.in_game_stream.send(msg),
ClientMsg::General(msg) => self.general_stream.send(msg),
ClientMsg::Ping(msg) => self.ping_stream.send(msg),
}
}
fn send_msg<S>(&mut self, msg: S)
where
S: Into<ClientMsg>,
{
let res = self.send_msg_err(msg);
if let Err(e) = res {
warn!(
?e,
"connection to server no longer possible, couldn't send msg"
);
}
}
/// Request a state transition to `ClientState::Character`.
pub fn request_character(&mut self, character_id: CharacterId) {
self.character_screen_stream
.send(ClientCharacterScreenMsg::Character(character_id))
.unwrap();
self.send_msg(ClientCharacterScreen::Character(character_id));
//Assume we are in_game unless server tells us otherwise
self.client_ingame = Some(ClientIngame::Character);
self.in_game = Some(ClientIngame::Character);
self.active_character_id = Some(character_id);
}
@ -475,87 +529,59 @@ impl Client {
/// Load the current players character list
pub fn load_character_list(&mut self) {
self.character_list.loading = true;
self.character_screen_stream
.send(ClientCharacterScreenMsg::RequestCharacterList)
.unwrap();
self.send_msg(ClientCharacterScreen::RequestCharacterList);
}
/// New character creation
pub fn create_character(&mut self, alias: String, tool: Option<String>, body: comp::Body) {
self.character_list.loading = true;
self.character_screen_stream
.send(ClientCharacterScreenMsg::CreateCharacter { alias, tool, body })
.unwrap();
self.send_msg(ClientCharacterScreen::CreateCharacter { alias, tool, body });
}
/// Character deletion
pub fn delete_character(&mut self, character_id: CharacterId) {
self.character_list.loading = true;
self.character_screen_stream
.send(ClientCharacterScreenMsg::DeleteCharacter(character_id))
.unwrap();
self.send_msg(ClientCharacterScreen::DeleteCharacter(character_id));
}
/// Send disconnect message to the server
pub fn request_logout(&mut self) {
debug!("Requesting logout from server");
if let Err(e) = self.general_stream.send(ClientGeneralMsg::Disconnect) {
error!(
?e,
"Couldn't send disconnect package to server, did server close already?"
);
}
self.send_msg(ClientGeneral::Disconnect);
}
/// Request a state transition to `ClientState::Registered` from an ingame
/// state.
pub fn request_remove_character(&mut self) {
self.in_game_stream
.send(ClientInGameMsg::ExitInGame)
.unwrap();
}
pub fn request_remove_character(&mut self) { self.send_msg(ClientInGame::ExitInGame); }
pub fn set_view_distance(&mut self, view_distance: u32) {
self.view_distance = Some(view_distance.max(1).min(65));
self.in_game_stream
.send(ClientInGameMsg::SetViewDistance(
self.view_distance.unwrap(),
))
.unwrap();
// Can't fail
self.send_msg(ClientInGame::SetViewDistance(self.view_distance.unwrap()));
}
pub fn use_slot(&mut self, slot: comp::slot::Slot) {
self.in_game_stream
.send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip(
InventoryManip::Use(slot),
)))
.unwrap();
self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip(
InventoryManip::Use(slot),
)));
}
pub fn swap_slots(&mut self, a: comp::slot::Slot, b: comp::slot::Slot) {
self.in_game_stream
.send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip(
InventoryManip::Swap(a, b),
)))
.unwrap();
self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip(
InventoryManip::Swap(a, b),
)));
}
pub fn drop_slot(&mut self, slot: comp::slot::Slot) {
self.in_game_stream
.send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip(
InventoryManip::Drop(slot),
)))
.unwrap();
self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip(
InventoryManip::Drop(slot),
)));
}
pub fn pick_up(&mut self, entity: EcsEntity) {
if let Some(uid) = self.state.read_component_copied(entity) {
self.in_game_stream
.send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip(
InventoryManip::Pickup(uid),
)))
.unwrap();
self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip(
InventoryManip::Pickup(uid),
)));
}
}
@ -573,11 +599,9 @@ impl Client {
pub fn craft_recipe(&mut self, recipe: &str) -> bool {
if self.can_craft_recipe(recipe) {
self.in_game_stream
.send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip(
InventoryManip::CraftRecipe(recipe.to_string()),
)))
.unwrap();
self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip(
InventoryManip::CraftRecipe(recipe.to_string()),
)));
true
} else {
false
@ -594,15 +618,11 @@ impl Client {
}
pub fn enable_lantern(&mut self) {
self.singleton_stream
.send(ClientInGameMsg::ControlEvent(ControlEvent::EnableLantern))
.unwrap();
self.send_msg(ClientInGame::ControlEvent(ControlEvent::EnableLantern));
}
pub fn disable_lantern(&mut self) {
self.singleton_stream
.send(ClientInGameMsg::ControlEvent(ControlEvent::DisableLantern))
.unwrap();
self.send_msg(ClientInGame::ControlEvent(ControlEvent::DisableLantern));
}
pub fn max_group_size(&self) -> u32 { self.max_group_size }
@ -620,55 +640,43 @@ impl Client {
pub fn pending_invites(&self) -> &HashSet<Uid> { &self.pending_invites }
pub fn send_group_invite(&mut self, invitee: Uid) {
self.in_game_stream
.send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip(
GroupManip::Invite(invitee),
)))
.unwrap()
self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip(
GroupManip::Invite(invitee),
)))
}
pub fn accept_group_invite(&mut self) {
// Clear invite
self.group_invite.take();
self.in_game_stream
.send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip(
GroupManip::Accept,
)))
.unwrap();
self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip(
GroupManip::Accept,
)));
}
pub fn decline_group_invite(&mut self) {
// Clear invite
self.group_invite.take();
self.in_game_stream
.send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip(
GroupManip::Decline,
)))
.unwrap();
self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip(
GroupManip::Decline,
)));
}
pub fn leave_group(&mut self) {
self.in_game_stream
.send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip(
GroupManip::Leave,
)))
.unwrap();
self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip(
GroupManip::Leave,
)));
}
pub fn kick_from_group(&mut self, uid: Uid) {
self.in_game_stream
.send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip(
GroupManip::Kick(uid),
)))
.unwrap();
self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip(
GroupManip::Kick(uid),
)));
}
pub fn assign_group_leader(&mut self, uid: Uid) {
self.in_game_stream
.send(ClientInGameMsg::ControlEvent(ControlEvent::GroupManip(
GroupManip::AssignLeader(uid),
)))
.unwrap();
self.send_msg(ClientInGame::ControlEvent(ControlEvent::GroupManip(
GroupManip::AssignLeader(uid),
)));
}
pub fn is_mounted(&self) -> bool {
@ -689,17 +697,11 @@ impl Client {
pub fn mount(&mut self, entity: EcsEntity) {
if let Some(uid) = self.state.read_component_copied(entity) {
self.in_game_stream
.send(ClientInGameMsg::ControlEvent(ControlEvent::Mount(uid)))
.unwrap();
self.send_msg(ClientInGame::ControlEvent(ControlEvent::Mount(uid)));
}
}
pub fn unmount(&mut self) {
self.in_game_stream
.send(ClientInGameMsg::ControlEvent(ControlEvent::Unmount))
.unwrap();
}
pub fn unmount(&mut self) { self.send_msg(ClientInGame::ControlEvent(ControlEvent::Unmount)); }
pub fn respawn(&mut self) {
if self
@ -709,9 +711,7 @@ impl Client {
.get(self.entity)
.map_or(false, |s| s.is_dead)
{
self.in_game_stream
.send(ClientInGameMsg::ControlEvent(ControlEvent::Respawn))
.unwrap();
self.send_msg(ClientInGame::ControlEvent(ControlEvent::Respawn));
}
}
@ -808,9 +808,7 @@ impl Client {
{
controller.actions.push(control_action);
}
self.in_game_stream
.send(ClientInGameMsg::ControlAction(control_action))
.unwrap();
self.send_msg(ClientInGame::ControlAction(control_action));
}
pub fn view_distance(&self) -> Option<u32> { self.view_distance }
@ -839,10 +837,7 @@ impl Client {
/// Send a chat message to the server.
pub fn send_chat(&mut self, message: String) {
match validate_chat_msg(&message) {
Ok(()) => self
.general_stream
.send(ClientGeneralMsg::ChatMsg(message))
.unwrap(),
Ok(()) => self.send_msg(ClientGeneral::ChatMsg(message)),
Err(ChatMsgValidationError::TooLong) => tracing::warn!(
"Attempted to send a message that's too long (Over {} bytes)",
MAX_BYTES_CHAT_MSG
@ -857,23 +852,15 @@ impl Client {
}
pub fn place_block(&mut self, pos: Vec3<i32>, block: Block) {
self.in_game_stream
.send(ClientInGameMsg::PlaceBlock(pos, block))
.unwrap();
self.send_msg(ClientInGame::PlaceBlock(pos, block));
}
pub fn remove_block(&mut self, pos: Vec3<i32>) {
self.in_game_stream
.send(ClientInGameMsg::BreakBlock(pos))
.unwrap();
}
pub fn remove_block(&mut self, pos: Vec3<i32>) { self.send_msg(ClientInGame::BreakBlock(pos)); }
pub fn collect_block(&mut self, pos: Vec3<i32>) {
self.in_game_stream
.send(ClientInGameMsg::ControlEvent(ControlEvent::InventoryManip(
InventoryManip::Collect(pos),
)))
.unwrap();
self.send_msg(ClientInGame::ControlEvent(ControlEvent::InventoryManip(
InventoryManip::Collect(pos),
)));
}
/// Execute a single client tick, handle input and update the game state by
@ -905,7 +892,7 @@ impl Client {
// 1) Handle input from frontend.
// Pass character actions from frontend input to the player's entity.
if self.client_ingame.is_some() {
if self.in_game.is_some() {
if let Err(e) = self
.state
.ecs()
@ -928,8 +915,7 @@ impl Client {
"Couldn't access controller component on client entity"
);
}
self.in_game_stream
.send(ClientInGameMsg::ControllerInputs(inputs))?;
self.send_msg_err(ClientInGame::ControllerInputs(inputs))?;
}
// 2) Build up a list of events for this frame, to be passed to the frontend.
@ -1033,8 +1019,9 @@ impl Client {
if self.state.terrain().get_key(*key).is_none() {
if !skip_mode && !self.pending_chunks.contains_key(key) {
if self.pending_chunks.len() < 4 {
self.in_game_stream
.send(ClientInGameMsg::TerrainChunkRequest { key: *key })?;
self.send_msg_err(ClientInGame::TerrainChunkRequest {
key: *key,
})?;
self.pending_chunks.insert(*key, Instant::now());
} else {
skip_mode = true;
@ -1066,19 +1053,19 @@ impl Client {
// Send a ping to the server once every second
if self.state.get_time() - self.last_server_ping > 1. {
self.ping_stream.send(PingMsg::Ping)?;
self.send_msg_err(PingMsg::Ping)?;
self.last_server_ping = self.state.get_time();
}
// 6) Update the server about the player's physics attributes.
if self.client_ingame.is_some() {
if self.in_game.is_some() {
if let (Some(pos), Some(vel), Some(ori)) = (
self.state.read_storage().get(self.entity).cloned(),
self.state.read_storage().get(self.entity).cloned(),
self.state.read_storage().get(self.entity).cloned(),
) {
self.in_game_stream
.send(ClientInGameMsg::PlayerPhysics { pos, vel, ori })?;
.send(ClientInGame::PlayerPhysics { pos, vel, ori })?;
}
}
@ -1108,26 +1095,26 @@ impl Client {
fn handle_server_msg(
&mut self,
frontend_events: &mut Vec<Event>,
msg: ServerGeneralMsg,
msg: ServerGeneral,
) -> Result<(), Error> {
match msg {
ServerGeneralMsg::Disconnect(reason) => match reason {
ServerGeneral::Disconnect(reason) => match reason {
DisconnectReason::Shutdown => return Err(Error::ServerShutdown),
DisconnectReason::Requested => {
debug!("finally sending ClientMsg::Terminate");
frontend_events.push(Event::Disconnect);
self.general_stream.send(ClientGeneralMsg::Terminate)?;
self.send_msg_err(ClientGeneral::Terminate)?;
},
DisconnectReason::Kicked(reason) => {
debug!("sending ClientMsg::Terminate because we got kicked");
frontend_events.push(Event::Kicked(reason));
self.general_stream.send(ClientGeneralMsg::Terminate)?;
self.send_msg_err(ClientGeneral::Terminate)?;
},
},
ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Init(list)) => {
ServerGeneral::PlayerListUpdate(PlayerListUpdate::Init(list)) => {
self.player_list = list
},
ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Add(uid, player_info)) => {
ServerGeneral::PlayerListUpdate(PlayerListUpdate::Add(uid, player_info)) => {
if let Some(old_player_info) = self.player_list.insert(uid, player_info.clone()) {
warn!(
"Received msg to insert {} with uid {} into the player list but there was \
@ -1136,7 +1123,7 @@ impl Client {
);
}
},
ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Admin(uid, admin)) => {
ServerGeneral::PlayerListUpdate(PlayerListUpdate::Admin(uid, admin)) => {
if let Some(player_info) = self.player_list.get_mut(&uid) {
player_info.is_admin = admin;
} else {
@ -1147,7 +1134,7 @@ impl Client {
);
}
},
ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::SelectedCharacter(
ServerGeneral::PlayerListUpdate(PlayerListUpdate::SelectedCharacter(
uid,
char_info,
)) => {
@ -1161,7 +1148,7 @@ impl Client {
);
}
},
ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::LevelChange(uid, next_level)) => {
ServerGeneral::PlayerListUpdate(PlayerListUpdate::LevelChange(uid, next_level)) => {
if let Some(player_info) = self.player_list.get_mut(&uid) {
player_info.character = match &player_info.character {
Some(character) => Some(common::msg::CharacterInfo {
@ -1180,7 +1167,7 @@ impl Client {
};
}
},
ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Remove(uid)) => {
ServerGeneral::PlayerListUpdate(PlayerListUpdate::Remove(uid)) => {
// Instead of removing players, mark them as offline because we need to
// remember the names of disconnected players in chat.
//
@ -1205,7 +1192,7 @@ impl Client {
);
}
},
ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Alias(uid, new_name)) => {
ServerGeneral::PlayerListUpdate(PlayerListUpdate::Alias(uid, new_name)) => {
if let Some(player_info) = self.player_list.get_mut(&uid) {
player_info.player_alias = new_name;
} else {
@ -1216,38 +1203,38 @@ impl Client {
);
}
},
ServerGeneralMsg::ChatMsg(m) => frontend_events.push(Event::Chat(m)),
ServerGeneralMsg::SetPlayerEntity(uid) => {
ServerGeneral::ChatMsg(m) => frontend_events.push(Event::Chat(m)),
ServerGeneral::SetPlayerEntity(uid) => {
if let Some(entity) = self.state.ecs().entity_from_uid(uid.0) {
self.entity = entity;
} else {
return Err(Error::Other("Failed to find entity from uid.".to_owned()));
}
},
ServerGeneralMsg::TimeOfDay(time_of_day) => {
ServerGeneral::TimeOfDay(time_of_day) => {
*self.state.ecs_mut().write_resource() = time_of_day;
},
ServerGeneralMsg::EntitySync(entity_sync_package) => {
ServerGeneral::EntitySync(entity_sync_package) => {
self.state
.ecs_mut()
.apply_entity_sync_package(entity_sync_package);
},
ServerGeneralMsg::CompSync(comp_sync_package) => {
ServerGeneral::CompSync(comp_sync_package) => {
self.state
.ecs_mut()
.apply_comp_sync_package(comp_sync_package);
},
ServerGeneralMsg::CreateEntity(entity_package) => {
ServerGeneral::CreateEntity(entity_package) => {
self.state.ecs_mut().apply_entity_package(entity_package);
},
ServerGeneralMsg::DeleteEntity(entity) => {
ServerGeneral::DeleteEntity(entity) => {
if self.uid() != Some(entity) {
self.state
.ecs_mut()
.delete_entity_and_clear_from_uid_allocator(entity.0);
}
},
ServerGeneralMsg::Notification(n) => {
ServerGeneral::Notification(n) => {
frontend_events.push(Event::Notification(n));
},
}
@ -1257,10 +1244,10 @@ impl Client {
fn handle_server_in_game_msg(
&mut self,
frontend_events: &mut Vec<Event>,
msg: ServerInGameMsg,
msg: ServerInGame,
) -> Result<(), Error> {
match msg {
ServerInGameMsg::GroupUpdate(change_notification) => {
ServerInGame::GroupUpdate(change_notification) => {
use comp::group::ChangeNotification::*;
// Note: we use a hashmap since this would not work with entities outside
// the view distance
@ -1332,15 +1319,15 @@ impl Client {
},
}
},
ServerInGameMsg::GroupInvite { inviter, timeout } => {
ServerInGame::GroupInvite { inviter, timeout } => {
self.group_invite = Some((inviter, std::time::Instant::now(), timeout));
},
ServerInGameMsg::InvitePending(uid) => {
ServerInGame::InvitePending(uid) => {
if !self.pending_invites.insert(uid) {
warn!("Received message about pending invite that was already pending");
}
},
ServerInGameMsg::InviteComplete { target, answer } => {
ServerInGame::InviteComplete { target, answer } => {
if !self.pending_invites.remove(&target) {
warn!(
"Received completed invite message for invite that was not in the list of \
@ -1358,11 +1345,11 @@ impl Client {
frontend_events.push(Event::Chat(comp::ChatType::Meta.chat_msg(msg)));
},
// Cleanup for when the client goes back to the `in_game = None`
ServerInGameMsg::ExitInGameSuccess => {
self.client_ingame = None;
ServerInGame::ExitInGameSuccess => {
self.in_game = None;
self.clean_state();
},
ServerInGameMsg::InventoryUpdate(mut inventory, event) => {
ServerInGame::InventoryUpdate(mut inventory, event) => {
match event {
InventoryUpdateEvent::CollectFailed => {},
_ => {
@ -1376,25 +1363,25 @@ impl Client {
frontend_events.push(Event::InventoryUpdated(event));
},
ServerInGameMsg::TerrainChunkUpdate { key, chunk } => {
ServerInGame::TerrainChunkUpdate { key, chunk } => {
if let Ok(chunk) = chunk {
self.state.insert_chunk(key, *chunk);
}
self.pending_chunks.remove(&key);
},
ServerInGameMsg::TerrainBlockUpdates(mut blocks) => {
ServerInGame::TerrainBlockUpdates(mut blocks) => {
blocks.drain().for_each(|(pos, block)| {
self.state.set_block(pos, block);
});
},
ServerInGameMsg::SetViewDistance(vd) => {
ServerInGame::SetViewDistance(vd) => {
self.view_distance = Some(vd);
frontend_events.push(Event::SetViewDistance(vd));
},
ServerInGameMsg::Outcomes(outcomes) => {
ServerInGame::Outcomes(outcomes) => {
frontend_events.extend(outcomes.into_iter().map(Event::Outcome))
},
ServerInGameMsg::Knockback(impulse) => {
ServerInGame::Knockback(impulse) => {
self.state
.ecs()
.read_resource::<EventBus<LocalEvent>>()
@ -1409,24 +1396,24 @@ impl Client {
fn handle_server_character_screen_msg(
&mut self,
msg: ServerCharacterScreenMsg,
msg: ServerCharacterScreen,
) -> Result<(), Error> {
match msg {
ServerCharacterScreenMsg::CharacterListUpdate(character_list) => {
ServerCharacterScreen::CharacterListUpdate(character_list) => {
self.character_list.characters = character_list;
self.character_list.loading = false;
},
ServerCharacterScreenMsg::CharacterActionError(error) => {
ServerCharacterScreen::CharacterActionError(error) => {
warn!("CharacterActionError: {:?}.", error);
self.character_list.error = Some(error);
},
ServerCharacterScreenMsg::CharacterDataLoadError(error) => {
ServerCharacterScreen::CharacterDataLoadError(error) => {
trace!("Handling join error by server");
self.client_ingame = None;
self.in_game = None;
self.clean_state();
self.character_list.error = Some(error);
},
ServerCharacterScreenMsg::CharacterSuccess => {
ServerCharacterScreen::CharacterSuccess => {
debug!("client is now in ingame state on server");
if let Some(vd) = self.view_distance {
self.set_view_distance(vd);
@ -1439,7 +1426,7 @@ impl Client {
fn handle_ping_msg(&mut self, msg: PingMsg) -> Result<(), Error> {
match msg {
PingMsg::Ping => {
self.ping_stream.send(PingMsg::Pong)?;
self.send_msg_err(PingMsg::Pong)?;
},
PingMsg::Pong => {
self.last_server_pong = self.state.get_time();
@ -1535,7 +1522,7 @@ impl Client {
pub fn get_client_type(&self) -> ClientType { ClientType::Game }
pub fn get_in_game(&self) -> Option<ClientIngame> { self.client_ingame }
pub fn get_in_game(&self) -> Option<ClientIngame> { self.in_game }
pub fn get_registered(&self) -> bool { self.registered }
@ -1810,12 +1797,16 @@ impl Client {
impl Drop for Client {
fn drop(&mut self) {
trace!("Dropping client");
if let Err(e) = self.general_stream.send(ClientGeneralMsg::Disconnect) {
warn!(
?e,
"Error during drop of client, couldn't send disconnect package, is the connection \
already closed?",
);
if self.registered {
if let Err(e) = self.send_msg_err(ClientGeneral::Disconnect) {
warn!(
?e,
"Error during drop of client, couldn't send disconnect package, is the \
connection already closed?",
);
}
} else {
trace!("no disconnect msg necessary as client wasn't registered")
}
if let Err(e) = block_on(self.participant.take().unwrap().disconnect()) {
warn!(?e, "error when disconnecting, couldn't send all data");

View File

@ -1,4 +1,4 @@
use crate::{comp::group::Group, msg::ServerGeneralMsg, sync::Uid};
use crate::{comp::group::Group, msg::ServerGeneral, sync::Uid};
use serde::{Deserialize, Serialize};
use specs::Component;
use specs_idvs::IdvStorage;
@ -118,11 +118,11 @@ impl<G> ChatType<G> {
}
}
impl ChatType<String> {
pub fn server_msg<S>(self, msg: S) -> ServerGeneralMsg
pub fn server_msg<S>(self, msg: S) -> ServerGeneral
where
S: Into<String>,
{
ServerGeneralMsg::ChatMsg(self.chat_msg(msg))
ServerGeneral::ChatMsg(self.chat_msg(msg))
}
}
// Stores chat text, type

View File

@ -1,3 +1,4 @@
use super::PingMsg;
use crate::{
character::CharacterId,
comp,
@ -7,25 +8,48 @@ use crate::{
use serde::{Deserialize, Serialize};
use vek::*;
///This struct contains all messages the client might send (on different
/// streams though). It's used to verify the correctness of the state in
/// debug_assertions
#[derive(Debug, Clone)]
pub enum ClientMsg {
///Send on the first connection ONCE to identify client intention for
/// server
Type(ClientType),
///Send ONCE to register/auth to the server
Register(ClientRegister),
///Msg only to send while in character screen, e.g. `CreateCharacter`
CharacterScreen(ClientCharacterScreen),
///Msg only to send while playing in game, e.g. `PlayerPositionUpdates`
InGame(ClientInGame),
///Msg that can be send ALWAYS as soon as we are registered, e.g. `Chat`
General(ClientGeneral),
Ping(PingMsg),
}
/*
2nd Level Enums
*/
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum ClientType {
// Regular Client like Voxygen who plays the game
/// Regular Client like Voxygen who plays the game
Game,
// A Chatonly client, which doesn't want to connect via its character
/// A Chatonly client, which doesn't want to connect via its character
ChatOnly,
// A unprivileged bot, e.g. to request world information
// Or a privileged bot, e.g. to run admin commands used by server-cli
/// A unprivileged bot, e.g. to request world information
/// Or a privileged bot, e.g. to run admin commands used by server-cli
Bot { privileged: bool },
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ClientRegisterMsg {
pub struct ClientRegister {
pub token_or_username: String,
}
//messages send by clients only valid when in character screen
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ClientCharacterScreenMsg {
pub enum ClientCharacterScreen {
RequestCharacterList,
CreateCharacter {
alias: String,
@ -39,7 +63,7 @@ pub enum ClientCharacterScreenMsg {
//messages send by clients only valid when in game (with a character)
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ClientInGameMsg {
pub enum ClientInGame {
ControllerInputs(comp::ControllerInputs),
ControlEvent(comp::ControlEvent),
ControlAction(comp::ControlAction),
@ -62,8 +86,36 @@ pub enum ClientInGameMsg {
/// Messages sent from the client to the server
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ClientGeneralMsg {
pub enum ClientGeneral {
ChatMsg(String),
Disconnect,
Terminate,
}
/*
end of 2nd level Enums
*/
impl Into<ClientMsg> for ClientType {
fn into(self) -> ClientMsg { ClientMsg::Type(self) }
}
impl Into<ClientMsg> for ClientRegister {
fn into(self) -> ClientMsg { ClientMsg::Register(self) }
}
impl Into<ClientMsg> for ClientCharacterScreen {
fn into(self) -> ClientMsg { ClientMsg::CharacterScreen(self) }
}
impl Into<ClientMsg> for ClientInGame {
fn into(self) -> ClientMsg { ClientMsg::InGame(self) }
}
impl Into<ClientMsg> for ClientGeneral {
fn into(self) -> ClientMsg { ClientMsg::General(self) }
}
impl Into<ClientMsg> for PingMsg {
fn into(self) -> ClientMsg { ClientMsg::Ping(self) }
}

View File

@ -1,18 +1,20 @@
pub mod client;
pub mod ecs_packet;
pub mod server;
pub mod world_packet;
// Reexports
pub use self::{
client::{
ClientCharacterScreenMsg, ClientGeneralMsg, ClientInGameMsg, ClientRegisterMsg, ClientType,
ClientCharacterScreen, ClientGeneral, ClientInGame, ClientMsg, ClientRegister, ClientType,
},
ecs_packet::EcsCompPacket,
server::{
CharacterInfo, DisconnectReason, InviteAnswer, Notification, PlayerInfo, PlayerListUpdate,
RegisterError, ServerCharacterScreenMsg, ServerGeneralMsg, ServerInGameMsg, ServerInfo,
ServerInitMsg, ServerRegisterAnswerMsg,
RegisterError, ServerCharacterScreen, ServerGeneral, ServerInGame, ServerInfo, ServerInit,
ServerMsg, ServerRegisterAnswer,
},
world_packet::WorldMapMsg,
};
use serde::{Deserialize, Serialize};

View File

@ -1,4 +1,4 @@
use super::EcsCompPacket;
use super::{EcsCompPacket, PingMsg};
use crate::{
character::CharacterItem,
comp,
@ -14,6 +14,31 @@ use serde::{Deserialize, Serialize};
use std::time::Duration;
use vek::*;
///This struct contains all messages the server might send (on different
/// streams though)
#[derive(Debug, Clone)]
pub enum ServerMsg {
/// Basic info about server, send ONCE, clients need it to Register
Info(ServerInfo),
/// Initial data package, send BEFORE Register ONCE. Not Register relevant
Init(ServerInit),
/// Result to `ClientMsg::Register`. send ONCE
RegisterAnswer(ServerRegisterAnswer),
/// Msg only to send when client is on the character screen, e.g.
/// `CharacterListUpdate`
CharacterScreen(ServerCharacterScreen),
/// Msg only to send when client is playing in game, e.g.
/// `TerrainChunkUpdate`
InGame(ServerInGame),
///Msg that can be send ALWAYS as soon as client is registered, e.g. `Chat`
General(ServerGeneral),
Ping(PingMsg),
}
/*
2nd Level Enums
*/
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerInfo {
pub name: String,
@ -23,195 +48,26 @@ pub struct ServerInfo {
pub auth_provider: Option<String>,
}
/// Inform the client of updates to the player list.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PlayerListUpdate {
Init(HashMap<Uid, PlayerInfo>),
Add(Uid, PlayerInfo),
SelectedCharacter(Uid, CharacterInfo),
LevelChange(Uid, u32),
Admin(Uid, bool),
Remove(Uid),
Alias(Uid, String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlayerInfo {
pub is_admin: bool,
pub is_online: bool,
pub player_alias: String,
pub character: Option<CharacterInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CharacterInfo {
pub name: String,
pub level: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
/// World map information. Note that currently, we always send the whole thing
/// in one go, but the structure aims to try to provide information as locally
/// as possible, so that in the future we can split up large maps into multiple
/// WorldMapMsg fragments.
///
/// TODO: Update message format to make fragmentable, allowing us to send more
/// information without running into bandwidth issues.
///
/// TODO: Add information for rivers (currently, we just prerender them on the
/// server, but this is not a great solution for LoD. The map rendering code is
/// already set up to be able to take advantage of the river rendering being
/// split out, but the format is a little complicated for space reasons and it
/// may take some tweaking to get right, so we avoid sending it for now).
///
/// TODO: measure explicit compression schemes that might save space, e.g.
/// repeating the "small angles" optimization that works well on more detailed
/// shadow maps intended for height maps.
pub struct WorldMapMsg {
/// Log base 2 of world map dimensions (width × height) in chunks.
///
/// NOTE: Invariant: chunk count fits in a u16.
pub dimensions_lg: Vec2<u32>,
/// Sea level (used to provide a base altitude).
pub sea_level: f32,
/// Max height (used to scale altitudes).
pub max_height: f32,
/// RGB+A; the alpha channel is currently unused, but will be used in the
/// future. Entries are in the usual chunk order.
pub rgba: Vec<u32>,
/// Altitudes: bits 2 to 0 are unused, then bits 15 to 3 are used for
/// altitude. The remainder are currently unused, but we have plans to
/// use 7 bits for water depth (using an integer f7 encoding), and we
/// will find other uses for the remaining 12 bits.
pub alt: Vec<u32>,
/// Horizon mapping. This is a variant of shadow mapping that is
/// specifically designed for height maps; it takes advantage of their
/// regular structure (e.g. no holes) to compress all information needed
/// to decide when to cast a sharp shadow into a single nagle, the "horizon
/// angle." This is the smallest angle with the ground at which light can
/// pass through any occluders to reach the chunk, in some chosen
/// horizontal direction. This would not be sufficient for a more
/// complicated 3D structure, but it works for height maps since:
///
/// 1. they have no gaps, so as soon as light can shine through it will
/// always be able to do so, and
/// 2. we only care about lighting from the top, and only from the east and
/// west (since at a large scale like this we mostly just want to
/// handle variable sunlight; moonlight would present more challenges
/// but we currently have no plans to try to cast accurate shadows in
/// moonlight).
///
/// Our chosen format is two pairs of vectors,
/// with the first pair representing west-facing light (casting shadows on
/// the left side) and the second representing east-facing light
/// (casting shadows on the east side).
///
/// The pair of vectors consists of (with each vector in the usual chunk
/// order):
///
/// * Horizon angle pointing east (1 byte, scaled so 1 unit = 255° / 360).
/// We might consider switching to tangent if that represents the
/// information we care about better.
/// * Approximate (floor) height of maximal occluder. We currently use this
/// to try to deliver some approximation of soft shadows, which isn't that
/// big a deal on the world map but is probably needed in order to ensure
/// smooth transitions between chunks in LoD view. Additionally, when we
/// start using the shadow information to do local lighting on the world
/// map, we'll want a quick way to test where we can go out of shadow at
/// arbitrary heights (since the player and other entities cajn find
/// themselves far from the ground at times). While this is only an
/// approximation to a proper distance map, hopefully it will give us
/// something that feels reasonable enough for Veloren's style.
///
/// NOTE: On compression.
///
/// Horizon mapping has a lot of advantages for height maps (simple, easy to
/// understand, doesn't require any fancy math or approximation beyond
/// precision loss), though it loses a few of them by having to store
/// distance to occluder as well. However, just storing tons
/// and tons of regular shadow maps (153 for a full day cycle, stored at
/// irregular intervals) combined with clever explicit compression and
/// avoiding recording sharp local shadows (preferring retracing for
/// these), yielded a compression rate of under 3 bits per column! Since
/// we likely want to avoid per-column shadows for worlds of the sizes we
/// want, we'd still need to store *some* extra information to create
/// soft shadows, but it would still be nice to try to drive down our
/// size as much as possible given how compressible shadows of height
/// maps seem to be in practice. Therefore, we try to take advantage of the
/// way existing compression algorithms tend to work to see if we can
/// achieve significant gains without doing a lot of custom work.
///
/// Specifically, since our rays are cast east/west, we expect that for each
/// row, the horizon angles in each direction should be sequences of
/// monotonically increasing values (as chunks approach a tall
/// occluder), followed by sequences of no shadow, repeated
/// until the end of the map. Monotonic sequences and same-byte sequences
/// are usually easy to compress and existing algorithms are more likely
/// to be able to deal with them than jumbled data. If we were to keep
/// both directions in the same vector, off-the-shelf compression would
/// probably be less effective.
///
/// For related reasons, rather than storing distances as in a standard
/// distance map (which would lead to monotonically *decreasing* values
/// as we approached the occluder from a given direction), we store the
/// estimated *occluder height.* The idea here is that we replace the
/// monotonic sequences with constant sequences, which are extremely
/// straightforward to compress and mostly handled automatically by anything
/// that does run-length encoding (i.e. most off-the-shelf compression
/// algorithms).
///
/// We still need to benchmark this properly, as there's no guarantee our
/// current compression algorithms will actually work well on this data
/// in practice. It's possible that some other permutation (e.g. more
/// bits reserved for "distance to occluder" in exchange for an even
/// more predictible sequence) would end up compressing better than storing
/// angles, or that we don't need as much precision as we currently have
/// (256 possible angles).
pub horizons: [(Vec<u8>, Vec<u8>); 2],
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum InviteAnswer {
Accepted,
Declined,
TimedOut,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Notification {
WaypointSaved,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DisconnectReason {
/// Server shut down
Shutdown,
/// Client sent disconnect message
Requested,
/// Client was kicked
Kicked(String),
}
/// Reponse To ClientType
#[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(clippy::clippy::large_enum_variant)]
pub enum ServerInitMsg {
pub enum ServerInit {
TooManyPlayers,
GameSync {
entity_package: sync::EntityPackage<EcsCompPacket>,
time_of_day: state::TimeOfDay,
max_group_size: u32,
client_timeout: Duration,
world_map: WorldMapMsg,
world_map: crate::msg::world_packet::WorldMapMsg,
recipe_book: RecipeBook,
},
}
pub type ServerRegisterAnswerMsg = Result<(), RegisterError>;
pub type ServerRegisterAnswer = Result<(), RegisterError>;
//Messages only allowed while client in character screen
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ServerCharacterScreenMsg {
pub enum ServerCharacterScreen {
/// An error occurred while loading character data
CharacterDataLoadError(String),
/// A list of characters belonging to the a authenticated player was sent
@ -223,7 +79,7 @@ pub enum ServerCharacterScreenMsg {
//Messages only allowed while client is in game (with a character)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ServerInGameMsg {
pub enum ServerInGame {
GroupUpdate(comp::group::ChangeNotification<sync::Uid>),
// Indicate to the client that they are invited to join a group
GroupInvite {
@ -256,7 +112,7 @@ pub enum ServerInGameMsg {
/// Messages sent from the server to the client
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ServerGeneralMsg {
pub enum ServerGeneral {
PlayerListUpdate(PlayerListUpdate),
/// A message to go into the client chat box. The client is responsible for
/// formatting the message and turning it into a speech bubble.
@ -272,6 +128,58 @@ pub enum ServerGeneralMsg {
Notification(Notification),
}
/*
end of 2nd level Enums
*/
/// Inform the client of updates to the player list.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PlayerListUpdate {
Init(HashMap<Uid, PlayerInfo>),
Add(Uid, PlayerInfo),
SelectedCharacter(Uid, CharacterInfo),
LevelChange(Uid, u32),
Admin(Uid, bool),
Remove(Uid),
Alias(Uid, String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlayerInfo {
pub is_admin: bool,
pub is_online: bool,
pub player_alias: String,
pub character: Option<CharacterInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CharacterInfo {
pub name: String,
pub level: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum InviteAnswer {
Accepted,
Declined,
TimedOut,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Notification {
WaypointSaved,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DisconnectReason {
/// Server shut down
Shutdown,
/// Client sent disconnect message
Requested,
/// Client was kicked
Kicked(String),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum RegisterError {
AlreadyLoggedIn,
@ -286,6 +194,34 @@ impl From<AuthClientError> for RegisterError {
fn from(err: AuthClientError) -> Self { Self::AuthError(err.to_string()) }
}
impl From<comp::ChatMsg> for ServerGeneralMsg {
fn from(v: comp::ChatMsg) -> Self { ServerGeneralMsg::ChatMsg(v) }
impl From<comp::ChatMsg> for ServerGeneral {
fn from(v: comp::ChatMsg) -> Self { ServerGeneral::ChatMsg(v) }
}
impl Into<ServerMsg> for ServerInfo {
fn into(self) -> ServerMsg { ServerMsg::Info(self) }
}
impl Into<ServerMsg> for ServerInit {
fn into(self) -> ServerMsg { ServerMsg::Init(self) }
}
impl Into<ServerMsg> for ServerRegisterAnswer {
fn into(self) -> ServerMsg { ServerMsg::RegisterAnswer(self) }
}
impl Into<ServerMsg> for ServerCharacterScreen {
fn into(self) -> ServerMsg { ServerMsg::CharacterScreen(self) }
}
impl Into<ServerMsg> for ServerInGame {
fn into(self) -> ServerMsg { ServerMsg::InGame(self) }
}
impl Into<ServerMsg> for ServerGeneral {
fn into(self) -> ServerMsg { ServerMsg::General(self) }
}
impl Into<ServerMsg> for PingMsg {
fn into(self) -> ServerMsg { ServerMsg::Ping(self) }
}

View File

@ -0,0 +1,123 @@
use serde::{Deserialize, Serialize};
use vek::*;
#[derive(Debug, Clone, Serialize, Deserialize)]
/// World map information. Note that currently, we always send the whole thing
/// in one go, but the structure aims to try to provide information as locally
/// as possible, so that in the future we can split up large maps into multiple
/// WorldMapMsg fragments.
///
/// TODO: Update message format to make fragmentable, allowing us to send more
/// information without running into bandwidth issues.
///
/// TODO: Add information for rivers (currently, we just prerender them on the
/// server, but this is not a great solution for LoD. The map rendering code is
/// already set up to be able to take advantage of the river rendering being
/// split out, but the format is a little complicated for space reasons and it
/// may take some tweaking to get right, so we avoid sending it for now).
///
/// TODO: measure explicit compression schemes that might save space, e.g.
/// repeating the "small angles" optimization that works well on more detailed
/// shadow maps intended for height maps.
pub struct WorldMapMsg {
/// Log base 2 of world map dimensions (width × height) in chunks.
///
/// NOTE: Invariant: chunk count fits in a u16.
pub dimensions_lg: Vec2<u32>,
/// Sea level (used to provide a base altitude).
pub sea_level: f32,
/// Max height (used to scale altitudes).
pub max_height: f32,
/// RGB+A; the alpha channel is currently unused, but will be used in the
/// future. Entries are in the usual chunk order.
pub rgba: Vec<u32>,
/// Altitudes: bits 2 to 0 are unused, then bits 15 to 3 are used for
/// altitude. The remainder are currently unused, but we have plans to
/// use 7 bits for water depth (using an integer f7 encoding), and we
/// will find other uses for the remaining 12 bits.
pub alt: Vec<u32>,
/// Horizon mapping. This is a variant of shadow mapping that is
/// specifically designed for height maps; it takes advantage of their
/// regular structure (e.g. no holes) to compress all information needed
/// to decide when to cast a sharp shadow into a single nagle, the "horizon
/// angle." This is the smallest angle with the ground at which light can
/// pass through any occluders to reach the chunk, in some chosen
/// horizontal direction. This would not be sufficient for a more
/// complicated 3D structure, but it works for height maps since:
///
/// 1. they have no gaps, so as soon as light can shine through it will
/// always be able to do so, and
/// 2. we only care about lighting from the top, and only from the east and
/// west (since at a large scale like this we mostly just want to
/// handle variable sunlight; moonlight would present more challenges
/// but we currently have no plans to try to cast accurate shadows in
/// moonlight).
///
/// Our chosen format is two pairs of vectors,
/// with the first pair representing west-facing light (casting shadows on
/// the left side) and the second representing east-facing light
/// (casting shadows on the east side).
///
/// The pair of vectors consists of (with each vector in the usual chunk
/// order):
///
/// * Horizon angle pointing east (1 byte, scaled so 1 unit = 255° / 360).
/// We might consider switching to tangent if that represents the
/// information we care about better.
/// * Approximate (floor) height of maximal occluder. We currently use this
/// to try to deliver some approximation of soft shadows, which isn't that
/// big a deal on the world map but is probably needed in order to ensure
/// smooth transitions between chunks in LoD view. Additionally, when we
/// start using the shadow information to do local lighting on the world
/// map, we'll want a quick way to test where we can go out of shadow at
/// arbitrary heights (since the player and other entities cajn find
/// themselves far from the ground at times). While this is only an
/// approximation to a proper distance map, hopefully it will give us
/// something that feels reasonable enough for Veloren's style.
///
/// NOTE: On compression.
///
/// Horizon mapping has a lot of advantages for height maps (simple, easy to
/// understand, doesn't require any fancy math or approximation beyond
/// precision loss), though it loses a few of them by having to store
/// distance to occluder as well. However, just storing tons
/// and tons of regular shadow maps (153 for a full day cycle, stored at
/// irregular intervals) combined with clever explicit compression and
/// avoiding recording sharp local shadows (preferring retracing for
/// these), yielded a compression rate of under 3 bits per column! Since
/// we likely want to avoid per-column shadows for worlds of the sizes we
/// want, we'd still need to store *some* extra information to create
/// soft shadows, but it would still be nice to try to drive down our
/// size as much as possible given how compressible shadows of height
/// maps seem to be in practice. Therefore, we try to take advantage of the
/// way existing compression algorithms tend to work to see if we can
/// achieve significant gains without doing a lot of custom work.
///
/// Specifically, since our rays are cast east/west, we expect that for each
/// row, the horizon angles in each direction should be sequences of
/// monotonically increasing values (as chunks approach a tall
/// occluder), followed by sequences of no shadow, repeated
/// until the end of the map. Monotonic sequences and same-byte sequences
/// are usually easy to compress and existing algorithms are more likely
/// to be able to deal with them than jumbled data. If we were to keep
/// both directions in the same vector, off-the-shelf compression would
/// probably be less effective.
///
/// For related reasons, rather than storing distances as in a standard
/// distance map (which would lead to monotonically *decreasing* values
/// as we approached the occluder from a given direction), we store the
/// estimated *occluder height.* The idea here is that we replace the
/// monotonic sequences with constant sequences, which are extremely
/// straightforward to compress and mostly handled automatically by anything
/// that does run-length encoding (i.e. most off-the-shelf compression
/// algorithms).
///
/// We still need to benchmark this properly, as there's no guarantee our
/// current compression algorithms will actually work well on this data
/// in practice. It's possible that some other permutation (e.g. more
/// bits reserved for "distance to occluder" in exchange for an even
/// more predictible sequence) would end up compressing better than storing
/// angles, or that we don't need as much precision as we currently have
/// (256 possible angles).
pub horizons: [(Vec<u8>, Vec<u8>); 2],
}

View File

@ -1,16 +1,16 @@
use crate::error::Error;
use common::msg::{
ClientCharacterScreenMsg, ClientGeneralMsg, ClientInGameMsg, ClientIngame, ClientType, PingMsg,
ServerCharacterScreenMsg, ServerGeneralMsg, ServerInGameMsg, ServerInitMsg,
ClientCharacterScreen, ClientGeneral, ClientInGame, ClientIngame, ClientType, PingMsg,
ServerMsg,
};
use hashbrown::HashSet;
use network::{MessageBuffer, Participant, Stream};
use network::{Participant, Stream};
use serde::{de::DeserializeOwned, Serialize};
use specs::{Component, FlaggedStorage};
use specs_idvs::IdvStorage;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
Mutex,
};
use tracing::debug;
use vek::*;
@ -20,7 +20,7 @@ pub struct Client {
pub client_type: ClientType,
pub in_game: Option<ClientIngame>,
pub participant: Mutex<Option<Participant>>,
pub singleton_stream: Stream,
pub general_stream: Stream,
pub ping_stream: Stream,
pub register_stream: Stream,
pub character_screen_stream: Stream,
@ -44,6 +44,7 @@ impl Client {
}
}
/*
fn internal_send_raw(b: &AtomicBool, s: &mut Stream, msg: Arc<MessageBuffer>) {
if !b.load(Ordering::Relaxed) {
if let Err(e) = s.send_raw(msg) {
@ -52,29 +53,32 @@ impl Client {
}
}
}
*/
pub fn send_init(&mut self, msg: ServerInitMsg) {
Self::internal_send(&self.network_error, &mut self.register_stream, msg);
}
pub fn send_msg(&mut self, msg: ServerGeneralMsg) {
Self::internal_send(&self.network_error, &mut self.singleton_stream, msg);
}
pub fn send_in_game(&mut self, msg: ServerInGameMsg) {
Self::internal_send(&self.network_error, &mut self.in_game_stream, msg);
}
pub fn send_character_screen(&mut self, msg: ServerCharacterScreenMsg) {
Self::internal_send(&self.network_error, &mut self.character_screen_stream, msg);
}
pub fn send_ping(&mut self, msg: PingMsg) {
Self::internal_send(&self.network_error, &mut self.ping_stream, msg);
}
pub fn send_msg_raw(&mut self, msg: Arc<MessageBuffer>) {
Self::internal_send_raw(&self.network_error, &mut self.singleton_stream, msg);
pub fn send_msg<S>(&mut self, msg: S)
where
S: Into<ServerMsg>,
{
const ERR: &str = "Dont do that, thats only done once at the start, no via this class";
match msg.into() {
ServerMsg::Info(_) => panic!(ERR),
ServerMsg::Init(_) => panic!(ERR),
ServerMsg::RegisterAnswer(msg) => {
Self::internal_send(&self.network_error, &mut self.register_stream, &msg)
},
ServerMsg::CharacterScreen(msg) => {
Self::internal_send(&self.network_error, &mut self.character_screen_stream, &msg)
},
ServerMsg::InGame(msg) => {
Self::internal_send(&self.network_error, &mut self.in_game_stream, &msg)
},
ServerMsg::General(msg) => {
Self::internal_send(&self.network_error, &mut self.general_stream, &msg)
},
ServerMsg::Ping(msg) => {
Self::internal_send(&self.network_error, &mut self.ping_stream, &msg)
},
};
}
pub async fn internal_recv<M: DeserializeOwned>(
@ -95,15 +99,15 @@ impl Client {
}
}
pub async fn recv_msg(&mut self) -> Result<ClientGeneralMsg, Error> {
Self::internal_recv(&self.network_error, &mut self.singleton_stream).await
pub async fn recv_msg(&mut self) -> Result<ClientGeneral, Error> {
Self::internal_recv(&self.network_error, &mut self.general_stream).await
}
pub async fn recv_in_game_msg(&mut self) -> Result<ClientInGameMsg, Error> {
pub async fn recv_in_game_msg(&mut self) -> Result<ClientInGame, Error> {
Self::internal_recv(&self.network_error, &mut self.in_game_stream).await
}
pub async fn recv_character_screen_msg(&mut self) -> Result<ClientCharacterScreenMsg, Error> {
pub async fn recv_character_screen_msg(&mut self) -> Result<ClientCharacterScreen, Error> {
Self::internal_recv(&self.network_error, &mut self.character_screen_stream).await
}

View File

@ -12,7 +12,7 @@ use common::{
cmd::{ChatCommand, CHAT_COMMANDS, CHAT_SHORTCUTS},
comp::{self, ChatType, Item, LightEmitter, WaypointArea},
event::{EventBus, ServerEvent},
msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneralMsg, ServerInGameMsg},
msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneral, ServerInGame},
npc::{self, get_npc_name},
state::TimeOfDay,
sync::{Uid, WorldSyncExt},
@ -504,7 +504,7 @@ fn handle_alias(
ecs.read_storage::<comp::Player>().get(target),
old_alias_optional,
) {
let msg = ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Alias(
let msg = ServerGeneral::PlayerListUpdate(PlayerListUpdate::Alias(
*uid,
player.alias.clone(),
));
@ -669,9 +669,7 @@ fn handle_spawn(
.try_map(|e| uids.get(e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| {
c.send_in_game(ServerInGameMsg::GroupUpdate(g))
});
.map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g)));
},
);
} else if let Some(group) = match alignment {
@ -1161,7 +1159,7 @@ fn handle_waypoint(
server.notify_client(client, ChatType::CommandInfo.server_msg("Waypoint saved!"));
server.notify_client(
client,
ServerGeneralMsg::Notification(Notification::WaypointSaved),
ServerGeneral::Notification(Notification::WaypointSaved),
);
},
None => server.notify_client(
@ -1198,7 +1196,7 @@ fn handle_adminify(
ecs.write_storage().insert(player, comp::Admin).is_ok()
};
// Update player list so the player shows up as admin in client chat.
let msg = ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Admin(
let msg = ServerGeneral::PlayerListUpdate(PlayerListUpdate::Admin(
*ecs.read_storage::<Uid>()
.get(player)
.expect("Player should have uid"),
@ -1591,7 +1589,7 @@ fn find_target(
ecs: &specs::World,
opt_alias: Option<String>,
fallback: EcsEntity,
) -> Result<EcsEntity, ServerGeneralMsg> {
) -> Result<EcsEntity, ServerGeneral> {
if let Some(alias) = opt_alias {
(&ecs.entities(), &ecs.read_storage::<comp::Player>())
.join()
@ -1663,7 +1661,7 @@ fn handle_set_level(
.expect("Failed to get uid for player");
server
.state
.notify_registered_clients(ServerGeneralMsg::PlayerListUpdate(
.notify_registered_clients(ServerGeneral::PlayerListUpdate(
PlayerListUpdate::LevelChange(uid, lvl),
));
@ -1903,7 +1901,7 @@ fn kick_player(server: &mut Server, target_player: EcsEntity, reason: &str) {
.emit_now(ServerEvent::ClientDisconnect(target_player));
server.notify_client(
target_player,
ServerGeneralMsg::Disconnect(DisconnectReason::Kicked(reason.to_string())),
ServerGeneral::Disconnect(DisconnectReason::Kicked(reason.to_string())),
);
}

View File

@ -134,7 +134,7 @@ impl ConnectionHandler {
client_type,
in_game: None,
participant: std::sync::Mutex::new(Some(participant)),
singleton_stream: general_stream,
general_stream,
ping_stream,
register_stream,
in_game_stream,

View File

@ -12,7 +12,7 @@ use common::{
Player, Pos, Stats,
},
lottery::Lottery,
msg::{PlayerListUpdate, ServerGeneralMsg, ServerInGameMsg},
msg::{PlayerListUpdate, ServerGeneral, ServerInGame},
outcome::Outcome,
state::BlockChange,
sync::{Uid, UidAllocator, WorldSyncExt},
@ -44,7 +44,7 @@ pub fn handle_knockback(server: &Server, entity: EcsEntity, impulse: Vec3<f32>)
}
let mut clients = state.ecs().write_storage::<Client>();
if let Some(client) = clients.get_mut(entity) {
client.send_in_game(ServerInGameMsg::Knockback(impulse));
client.send_msg(ServerInGame::Knockback(impulse));
}
}
@ -656,7 +656,7 @@ pub fn handle_level_up(server: &mut Server, entity: EcsEntity, new_level: u32) {
server
.state
.notify_registered_clients(ServerGeneralMsg::PlayerListUpdate(
.notify_registered_clients(ServerGeneral::PlayerListUpdate(
PlayerListUpdate::LevelChange(*uid, new_level),
));
}

View File

@ -5,7 +5,7 @@ use common::{
group::{self, Group, GroupManager, Invite, PendingInvites},
ChatType, GroupManip,
},
msg::{InviteAnswer, ServerInGameMsg},
msg::{InviteAnswer, ServerInGame},
sync,
sync::WorldSyncExt,
};
@ -155,7 +155,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
(clients.get_mut(invitee), uids.get(entity).copied())
{
if send_invite() {
client.send_in_game(ServerInGameMsg::GroupInvite {
client.send_msg(ServerInGame::GroupInvite {
inviter,
timeout: PRESENTED_INVITE_TIMEOUT_DUR,
});
@ -171,7 +171,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
// Notify inviter that the invite is pending
if invite_sent {
if let Some(client) = clients.get_mut(entity) {
client.send_in_game(ServerInGameMsg::InvitePending(uid));
client.send_msg(ServerInGame::InvitePending(uid));
}
}
},
@ -196,7 +196,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
if let (Some(client), Some(target)) =
(clients.get_mut(inviter), uids.get(entity).copied())
{
client.send_in_game(ServerInGameMsg::InviteComplete {
client.send_msg(ServerInGame::InviteComplete {
target,
answer: InviteAnswer::Accepted,
})
@ -217,7 +217,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
.try_map(|e| uids.get(e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| c.send_in_game(ServerInGameMsg::GroupUpdate(g)));
.map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g)));
},
);
}
@ -244,7 +244,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
if let (Some(client), Some(target)) =
(clients.get_mut(inviter), uids.get(entity).copied())
{
client.send_in_game(ServerInGameMsg::InviteComplete {
client.send_msg(ServerInGame::InviteComplete {
target,
answer: InviteAnswer::Declined,
})
@ -269,7 +269,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
.try_map(|e| uids.get(e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| c.send_in_game(ServerInGameMsg::GroupUpdate(g)));
.map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g)));
},
);
},
@ -336,7 +336,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
.try_map(|e| uids.get(e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| c.send_in_game(ServerInGameMsg::GroupUpdate(g)));
.map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g)));
},
);
@ -410,7 +410,7 @@ pub fn handle_group(server: &mut Server, entity: specs::Entity, manip: GroupMani
.try_map(|e| uids.get(e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| c.send_in_game(ServerInGameMsg::GroupUpdate(g)));
.map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g)));
},
);
// Tell them they are the leader

View File

@ -4,7 +4,7 @@ use crate::{
};
use common::{
comp::{self, item},
msg::ServerGeneralMsg,
msg::ServerGeneral,
sync::{Uid, WorldSyncExt},
};
use specs::{world::WorldExt, Entity as EcsEntity};
@ -116,7 +116,7 @@ pub fn handle_possess(server: &Server, possessor_uid: Uid, possesse_uid: Uid) {
let mut clients = ecs.write_storage::<Client>();
if clients.get_mut(possesse).is_none() {
if let Some(mut client) = clients.remove(possessor) {
client.send_msg(ServerGeneralMsg::SetPlayerEntity(possesse_uid));
client.send_msg(ServerGeneral::SetPlayerEntity(possesse_uid));
clients
.insert(possesse, client)
.err()

View File

@ -5,7 +5,7 @@ use common::{
slot::{self, Slot},
Pos, MAX_PICKUP_RANGE_SQR,
},
msg::ServerInGameMsg,
msg::ServerInGame,
recipe::default_recipe_book,
sync::{Uid, WorldSyncExt},
vol::ReadVol,
@ -281,9 +281,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
.map(|g| (g, c))
})
.map(|(g, c)| {
c.send_in_game(
ServerInGameMsg::GroupUpdate(g),
)
c.send_msg(ServerInGame::GroupUpdate(g))
});
},
);

View File

@ -5,7 +5,7 @@ use crate::{
use common::{
comp,
comp::{group, Player},
msg::{PlayerListUpdate, ServerGeneralMsg, ServerInGameMsg},
msg::{PlayerListUpdate, ServerGeneral, ServerInGame},
span,
sync::{Uid, UidAllocator},
};
@ -34,7 +34,7 @@ pub fn handle_exit_ingame(server: &mut Server, entity: EcsEntity) {
if let (Some(mut client), Some(uid), Some(player)) = (maybe_client, maybe_uid, maybe_player) {
// Tell client its request was successful
client.in_game = None;
client.send_in_game(ServerInGameMsg::ExitInGameSuccess);
client.send_msg(ServerInGame::ExitInGameSuccess);
let entity_builder = state.ecs_mut().create_entity().with(client).with(player);
@ -130,9 +130,9 @@ pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event
) {
state.notify_registered_clients(comp::ChatType::Offline(*uid).server_msg(""));
state.notify_registered_clients(ServerGeneralMsg::PlayerListUpdate(
PlayerListUpdate::Remove(*uid),
));
state.notify_registered_clients(ServerGeneral::PlayerListUpdate(PlayerListUpdate::Remove(
*uid,
)));
}
// Make sure to remove the player from the logged in list. (See LoginProvider)

View File

@ -47,8 +47,8 @@ use common::{
comp::{self, ChatType},
event::{EventBus, ServerEvent},
msg::{
server::WorldMapMsg, ClientType, DisconnectReason, ServerCharacterScreenMsg,
ServerGeneralMsg, ServerInGameMsg, ServerInfo, ServerInitMsg,
ClientType, DisconnectReason, ServerCharacterScreen, ServerGeneral, ServerInfo, ServerInit,
ServerMsg, WorldMapMsg,
},
outcome::Outcome,
recipe::default_recipe_book,
@ -525,13 +525,13 @@ impl Server {
.messages()
.for_each(|query_result| match query_result.result {
CharacterLoaderResponseType::CharacterList(result) => match result {
Ok(character_list_data) => self.notify_character_screen_client(
Ok(character_list_data) => self.notify_client(
query_result.entity,
ServerCharacterScreenMsg::CharacterListUpdate(character_list_data),
ServerCharacterScreen::CharacterListUpdate(character_list_data),
),
Err(error) => self.notify_character_screen_client(
Err(error) => self.notify_client(
query_result.entity,
ServerCharacterScreenMsg::CharacterActionError(error.to_string()),
ServerCharacterScreen::CharacterActionError(error.to_string()),
),
},
CharacterLoaderResponseType::CharacterData(result) => {
@ -544,9 +544,9 @@ impl Server {
// We failed to load data for the character from the DB. Notify the
// client to push the state back to character selection, with the error
// to display
self.notify_character_screen_client(
self.notify_client(
query_result.entity,
ServerCharacterScreenMsg::CharacterDataLoadError(error.to_string()),
ServerCharacterScreen::CharacterDataLoadError(error.to_string()),
);
// Clean up the entity data on the server
@ -815,7 +815,7 @@ impl Server {
?client.participant,
"to many players, wont allow participant to connect"
);
client.register_stream.send(ServerInitMsg::TooManyPlayers)?;
client.register_stream.send(ServerInit::TooManyPlayers)?;
continue;
}
@ -839,7 +839,7 @@ impl Server {
.get_mut(entity)
.unwrap()
.register_stream
.send(ServerInitMsg::GameSync {
.send(ServerInit::GameSync {
// Send client their entity
entity_package: TrackedComps::fetch(&self.state.ecs())
.create_entity_package(entity, None, None, None),
@ -858,32 +858,14 @@ impl Server {
pub fn notify_client<S>(&self, entity: EcsEntity, msg: S)
where
S: Into<ServerGeneralMsg>,
S: Into<ServerMsg>,
{
if let Some(client) = self.state.ecs().write_storage::<Client>().get_mut(entity) {
client.send_msg(msg.into())
}
}
pub fn notify_in_game_client<S>(&self, entity: EcsEntity, msg: S)
where
S: Into<ServerInGameMsg>,
{
if let Some(client) = self.state.ecs().write_storage::<Client>().get_mut(entity) {
client.send_in_game(msg.into())
}
}
pub fn notify_character_screen_client<S>(&self, entity: EcsEntity, msg: S)
where
S: Into<ServerCharacterScreenMsg>,
{
if let Some(client) = self.state.ecs().write_storage::<Client>().get_mut(entity) {
client.send_character_screen(msg.into())
}
}
pub fn notify_registered_clients(&mut self, msg: ServerGeneralMsg) {
pub fn notify_registered_clients(&mut self, msg: ServerGeneral) {
self.state.notify_registered_clients(msg);
}
@ -963,7 +945,7 @@ impl Server {
impl Drop for Server {
fn drop(&mut self) {
self.state
.notify_registered_clients(ServerGeneralMsg::Disconnect(DisconnectReason::Shutdown));
.notify_registered_clients(ServerGeneral::Disconnect(DisconnectReason::Shutdown));
}
}

View File

@ -6,8 +6,8 @@ use common::{
comp,
effect::Effect,
msg::{
CharacterInfo, ClientIngame, PlayerListUpdate, ServerCharacterScreenMsg, ServerGeneralMsg,
ServerInGameMsg,
CharacterInfo, ClientIngame, PlayerListUpdate, ServerCharacterScreen, ServerGeneral,
ServerInGame, ServerMsg,
},
state::State,
sync::{Uid, UidAllocator, WorldSyncExt},
@ -62,7 +62,8 @@ pub trait StateExt {
fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents);
/// Iterates over registered clients and send each `ServerMsg`
fn send_chat(&self, msg: comp::UnresolvedChatMsg);
fn notify_registered_clients(&self, msg: ServerGeneralMsg);
fn notify_registered_clients(&self, msg: ServerGeneral);
fn notify_in_game_clients(&self, msg: ServerInGame);
/// Delete an entity, recording the deletion in [`DeletedEntities`]
fn delete_entity_recorded(
&mut self,
@ -220,7 +221,7 @@ impl StateExt for State {
// Tell the client its request was successful.
if let Some(client) = self.ecs().write_storage::<Client>().get_mut(entity) {
client.in_game = Some(ClientIngame::Character);
client.send_character_screen(ServerCharacterScreenMsg::CharacterSuccess)
client.send_msg(ServerCharacterScreen::CharacterSuccess)
}
}
@ -229,7 +230,7 @@ impl StateExt for State {
if let Some(player_uid) = self.read_component_copied::<Uid>(entity) {
// Notify clients of a player list update
self.notify_registered_clients(ServerGeneralMsg::PlayerListUpdate(
self.notify_registered_clients(ServerGeneral::PlayerListUpdate(
PlayerListUpdate::SelectedCharacter(player_uid, CharacterInfo {
name: String::from(&stats.name),
level: stats.level.level(),
@ -276,7 +277,7 @@ impl StateExt for State {
| comp::ChatType::Kill(_, _)
| comp::ChatType::Meta
| comp::ChatType::World(_) => {
self.notify_registered_clients(ServerGeneralMsg::ChatMsg(resolved_msg))
self.notify_registered_clients(ServerGeneral::ChatMsg(resolved_msg))
},
comp::ChatType::Tell(u, t) => {
for (client, uid) in (
@ -286,7 +287,7 @@ impl StateExt for State {
.join()
{
if uid == u || uid == t {
client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone()));
client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone()));
}
}
},
@ -298,7 +299,7 @@ impl StateExt for State {
if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
for (client, pos) in (&mut ecs.write_storage::<Client>(), &positions).join() {
if is_within(comp::ChatMsg::SAY_DISTANCE, pos, speaker_pos) {
client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone()));
client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone()));
}
}
}
@ -310,7 +311,7 @@ impl StateExt for State {
if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
for (client, pos) in (&mut ecs.write_storage::<Client>(), &positions).join() {
if is_within(comp::ChatMsg::REGION_DISTANCE, pos, speaker_pos) {
client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone()));
client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone()));
}
}
}
@ -322,7 +323,7 @@ impl StateExt for State {
if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
for (client, pos) in (&mut ecs.write_storage::<Client>(), &positions).join() {
if is_within(comp::ChatMsg::NPC_DISTANCE, pos, speaker_pos) {
client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone()));
client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone()));
}
}
}
@ -336,7 +337,7 @@ impl StateExt for State {
.join()
{
if s == &faction.0 {
client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone()));
client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone()));
}
}
},
@ -348,7 +349,7 @@ impl StateExt for State {
.join()
{
if g == group {
client.send_msg(ServerGeneralMsg::ChatMsg(resolved_msg.clone()));
client.send_msg(ServerGeneral::ChatMsg(resolved_msg.clone()));
}
}
},
@ -356,7 +357,8 @@ impl StateExt for State {
}
/// Sends the message to all connected clients
fn notify_registered_clients(&self, msg: ServerGeneralMsg) {
fn notify_registered_clients(&self, msg: ServerGeneral) {
let msg: ServerMsg = msg.into();
for client in (&mut self.ecs().write_storage::<Client>())
.join()
.filter(|c| c.registered)
@ -365,6 +367,17 @@ impl StateExt for State {
}
}
/// Sends the message to all clients playing in game
fn notify_in_game_clients(&self, msg: ServerInGame) {
let msg: ServerMsg = msg.into();
for client in (&mut self.ecs().write_storage::<Client>())
.join()
.filter(|c| c.in_game.is_some())
{
client.send_msg(msg.clone());
}
}
fn delete_entity_recorded(
&mut self,
entity: EcsEntity,
@ -388,7 +401,7 @@ impl StateExt for State {
.try_map(|e| uids.get(e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| c.send_in_game(ServerInGameMsg::GroupUpdate(g)));
.map(|(g, c)| c.send_msg(ServerInGame::GroupUpdate(g)));
},
);
}

View File

@ -8,7 +8,7 @@ use crate::{
};
use common::{
comp::{ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Player, Pos, Vel},
msg::{ServerGeneralMsg, ServerInGameMsg},
msg::{ServerGeneral, ServerInGame},
outcome::Outcome,
region::{Event as RegionEvent, RegionMap},
span,
@ -128,14 +128,13 @@ impl<'a> System<'a> for Sys {
(uid, pos, velocities.get(entity), orientations.get(entity))
})
}) {
let create_msg = ServerGeneralMsg::CreateEntity(
tracked_comps.create_entity_package(
let create_msg =
ServerGeneral::CreateEntity(tracked_comps.create_entity_package(
entity,
Some(*pos),
vel.copied(),
ori.copied(),
),
);
));
for (client, regions, client_entity, _) in &mut subscribers {
if maybe_key
.as_ref()
@ -158,7 +157,7 @@ impl<'a> System<'a> for Sys {
.map(|key| !regions.contains(key))
.unwrap_or(true)
{
client.send_msg(ServerGeneralMsg::DeleteEntity(uid));
client.send_msg(ServerGeneral::DeleteEntity(uid));
}
}
}
@ -175,14 +174,14 @@ impl<'a> System<'a> for Sys {
.take_deleted_in_region(key)
.unwrap_or_default(),
);
let entity_sync_msg = ServerGeneralMsg::EntitySync(entity_sync_package);
let comp_sync_msg = ServerGeneralMsg::CompSync(comp_sync_package);
let entity_sync_msg = ServerGeneral::EntitySync(entity_sync_package);
let comp_sync_msg = ServerGeneral::CompSync(comp_sync_package);
subscribers.iter_mut().for_each(move |(client, _, _, _)| {
client.send_msg(entity_sync_msg.clone());
client.send_msg(comp_sync_msg.clone());
});
let mut send_msg = |msg: ServerGeneralMsg,
let mut send_msg = |msg: ServerGeneral,
entity: EcsEntity,
pos: Pos,
force_update: Option<&ForceUpdate>,
@ -288,7 +287,7 @@ impl<'a> System<'a> for Sys {
}
send_msg(
ServerGeneralMsg::CompSync(comp_sync_package),
ServerGeneral::CompSync(comp_sync_package),
entity,
pos,
force_update,
@ -312,7 +311,7 @@ impl<'a> System<'a> for Sys {
})
{
for uid in &deleted {
client.send_msg(ServerGeneralMsg::DeleteEntity(Uid(*uid)));
client.send_msg(ServerGeneral::DeleteEntity(Uid(*uid)));
}
}
}
@ -321,7 +320,7 @@ impl<'a> System<'a> for Sys {
// Sync inventories
for (client, inventory, update) in (&mut clients, &inventories, &inventory_updates).join() {
client.send_in_game(ServerInGameMsg::InventoryUpdate(
client.send_msg(ServerInGame::InventoryUpdate(
inventory.clone(),
update.event(),
));
@ -342,7 +341,7 @@ impl<'a> System<'a> for Sys {
.cloned()
.collect::<Vec<_>>();
if !outcomes.is_empty() {
client.send_in_game(ServerInGameMsg::Outcomes(outcomes));
client.send_msg(ServerInGame::Outcomes(outcomes));
}
}
outcomes.clear();
@ -354,7 +353,7 @@ impl<'a> System<'a> for Sys {
// Sync resources
// TODO: doesn't really belong in this system (rename system or create another
// system?)
let tof_msg = ServerGeneralMsg::TimeOfDay(*time_of_day);
let tof_msg = ServerGeneral::TimeOfDay(*time_of_day);
for client in (&mut clients).join() {
client.send_msg(tof_msg.clone());
}

View File

@ -2,7 +2,7 @@ use super::SysTimer;
use crate::client::Client;
use common::{
comp::group::{Invite, PendingInvites},
msg::{InviteAnswer, ServerInGameMsg},
msg::{InviteAnswer, ServerInGame},
span,
sync::Uid,
};
@ -54,7 +54,7 @@ impl<'a> System<'a> for Sys {
if let (Some(client), Some(target)) =
(clients.get_mut(*inviter), uids.get(invitee).copied())
{
client.send_in_game(ServerInGameMsg::InviteComplete {
client.send_msg(ServerInGame::InviteComplete {
target,
answer: InviteAnswer::TimedOut,
})

View File

@ -15,10 +15,10 @@ use common::{
},
event::{EventBus, ServerEvent},
msg::{
validate_chat_msg, CharacterInfo, ChatMsgValidationError, ClientCharacterScreenMsg,
ClientGeneralMsg, ClientInGameMsg, ClientIngame, ClientRegisterMsg, DisconnectReason,
PingMsg, PlayerInfo, PlayerListUpdate, RegisterError, ServerCharacterScreenMsg,
ServerGeneralMsg, ServerInGameMsg, ServerRegisterAnswerMsg, MAX_BYTES_CHAT_MSG,
validate_chat_msg, CharacterInfo, ChatMsgValidationError, ClientCharacterScreen,
ClientGeneral, ClientInGame, ClientIngame, ClientRegister, DisconnectReason, PingMsg,
PlayerInfo, PlayerListUpdate, RegisterError, ServerCharacterScreen, ServerGeneral,
ServerInGame, ServerRegisterAnswer, MAX_BYTES_CHAT_MSG,
},
span,
state::{BlockChange, Time},
@ -45,10 +45,10 @@ impl Sys {
player_metrics: &ReadExpect<'_, PlayerMetrics>,
uids: &ReadStorage<'_, Uid>,
chat_modes: &ReadStorage<'_, ChatMode>,
msg: ClientGeneralMsg,
msg: ClientGeneral,
) -> Result<(), crate::error::Error> {
match msg {
ClientGeneralMsg::ChatMsg(message) => {
ClientGeneral::ChatMsg(message) => {
if client.registered {
match validate_chat_msg(&message) {
Ok(()) => {
@ -68,10 +68,10 @@ impl Sys {
}
}
},
ClientGeneralMsg::Disconnect => {
client.send_msg(ServerGeneralMsg::Disconnect(DisconnectReason::Requested));
ClientGeneral::Disconnect => {
client.send_msg(ServerGeneral::Disconnect(DisconnectReason::Requested));
},
ClientGeneralMsg::Terminate => {
ClientGeneral::Terminate => {
debug!(?entity, "Client send message to termitate session");
player_metrics
.clients_disconnected
@ -100,24 +100,24 @@ impl Sys {
players: &mut WriteStorage<'_, Player>,
controllers: &mut WriteStorage<'_, Controller>,
settings: &Read<'_, Settings>,
msg: ClientInGameMsg,
msg: ClientInGame,
) -> Result<(), crate::error::Error> {
if client.in_game.is_none() {
debug!(?entity, "client is not in_game, ignoring msg");
trace!(?msg, "ignored msg content");
if matches!(msg, ClientInGameMsg::TerrainChunkRequest{ .. }) {
if matches!(msg, ClientInGame::TerrainChunkRequest{ .. }) {
network_metrics.chunks_request_dropped.inc();
}
return Ok(());
}
match msg {
// Go back to registered state (char selection screen)
ClientInGameMsg::ExitInGame => {
ClientInGame::ExitInGame => {
client.in_game = None;
server_emitter.emit(ServerEvent::ExitIngame { entity });
client.send_in_game(ServerInGameMsg::ExitInGameSuccess);
client.send_msg(ServerInGame::ExitInGameSuccess);
},
ClientInGameMsg::SetViewDistance(view_distance) => {
ClientInGame::SetViewDistance(view_distance) => {
players.get_mut(entity).map(|player| {
player.view_distance = Some(
settings
@ -133,19 +133,19 @@ impl Sys {
.map(|max| view_distance > max)
.unwrap_or(false)
{
client.send_in_game(ServerInGameMsg::SetViewDistance(
client.send_msg(ServerInGame::SetViewDistance(
settings.max_view_distance.unwrap_or(0),
));
}
},
ClientInGameMsg::ControllerInputs(inputs) => {
ClientInGame::ControllerInputs(inputs) => {
if let Some(ClientIngame::Character) = client.in_game {
if let Some(controller) = controllers.get_mut(entity) {
controller.inputs.update_with_new(inputs);
}
}
},
ClientInGameMsg::ControlEvent(event) => {
ClientInGame::ControlEvent(event) => {
if let Some(ClientIngame::Character) = client.in_game {
// Skip respawn if client entity is alive
if let ControlEvent::Respawn = event {
@ -159,14 +159,14 @@ impl Sys {
}
}
},
ClientInGameMsg::ControlAction(event) => {
ClientInGame::ControlAction(event) => {
if let Some(ClientIngame::Character) = client.in_game {
if let Some(controller) = controllers.get_mut(entity) {
controller.actions.push(event);
}
}
},
ClientInGameMsg::PlayerPhysics { pos, vel, ori } => {
ClientInGame::PlayerPhysics { pos, vel, ori } => {
if let Some(ClientIngame::Character) = client.in_game {
if force_updates.get(entity).is_none()
&& stats.get(entity).map_or(true, |s| !s.is_dead)
@ -177,17 +177,17 @@ impl Sys {
}
}
},
ClientInGameMsg::BreakBlock(pos) => {
ClientInGame::BreakBlock(pos) => {
if let Some(block) = can_build.get(entity).and_then(|_| terrain.get(pos).ok()) {
block_changes.set(pos, block.into_vacant());
}
},
ClientInGameMsg::PlaceBlock(pos, block) => {
ClientInGame::PlaceBlock(pos, block) => {
if can_build.get(entity).is_some() {
block_changes.try_set(pos, block);
}
},
ClientInGameMsg::TerrainChunkRequest { key } => {
ClientInGame::TerrainChunkRequest { key } => {
let in_vd = if let (Some(view_distance), Some(pos)) = (
players.get(entity).and_then(|p| p.view_distance),
positions.get(entity),
@ -203,7 +203,7 @@ impl Sys {
match terrain.get_key(key) {
Some(chunk) => {
network_metrics.chunks_served_from_memory.inc();
client.send_in_game(ServerInGameMsg::TerrainChunkUpdate {
client.send_msg(ServerInGame::TerrainChunkUpdate {
key,
chunk: Ok(Box::new(chunk.clone())),
})
@ -217,17 +217,17 @@ impl Sys {
network_metrics.chunks_request_dropped.inc();
}
},
ClientInGameMsg::UnlockSkill(skill) => {
ClientInGame::UnlockSkill(skill) => {
stats
.get_mut(entity)
.map(|s| s.skill_set.unlock_skill(skill));
},
ClientInGameMsg::RefundSkill(skill) => {
ClientInGame::RefundSkill(skill) => {
stats
.get_mut(entity)
.map(|s| s.skill_set.refund_skill(skill));
},
ClientInGameMsg::UnlockSkillGroup(skill_group_type) => {
ClientInGame::UnlockSkillGroup(skill_group_type) => {
stats
.get_mut(entity)
.map(|s| s.skill_set.unlock_skill_group(skill_group_type));
@ -247,18 +247,18 @@ impl Sys {
players: &mut WriteStorage<'_, Player>,
editable_settings: &ReadExpect<'_, EditableSettings>,
alias_validator: &ReadExpect<'_, AliasValidator>,
msg: ClientCharacterScreenMsg,
msg: ClientCharacterScreen,
) -> Result<(), crate::error::Error> {
match msg {
// Request spectator state
ClientCharacterScreenMsg::Spectate => {
ClientCharacterScreen::Spectate => {
if client.registered {
client.in_game = Some(ClientIngame::Spectator)
} else {
debug!("dropped Spectate msg from unregistered client");
}
},
ClientCharacterScreenMsg::Character(character_id) => {
ClientCharacterScreen::Character(character_id) => {
if client.registered && client.in_game.is_none() {
// Only send login message if it wasn't already
// sent previously
@ -301,11 +301,9 @@ impl Sys {
}
}
} else {
client.send_character_screen(
ServerCharacterScreenMsg::CharacterDataLoadError(String::from(
"Failed to fetch player entity",
)),
)
client.send_msg(ServerCharacterScreen::CharacterDataLoadError(
String::from("Failed to fetch player entity"),
))
}
} else {
let registered = client.registered;
@ -313,15 +311,15 @@ impl Sys {
debug!(?registered, ?in_game, "dropped Character msg from client");
}
},
ClientCharacterScreenMsg::RequestCharacterList => {
ClientCharacterScreen::RequestCharacterList => {
if let Some(player) = players.get(entity) {
character_loader.load_character_list(entity, player.uuid().to_string())
}
},
ClientCharacterScreenMsg::CreateCharacter { alias, tool, body } => {
ClientCharacterScreen::CreateCharacter { alias, tool, body } => {
if let Err(error) = alias_validator.validate(&alias) {
debug!(?error, ?alias, "denied alias as it contained a banned word");
client.send_character_screen(ServerCharacterScreenMsg::CharacterActionError(
client.send_msg(ServerCharacterScreen::CharacterActionError(
error.to_string(),
));
} else if let Some(player) = players.get(entity) {
@ -335,7 +333,7 @@ impl Sys {
);
}
},
ClientCharacterScreenMsg::DeleteCharacter(character_id) => {
ClientCharacterScreen::DeleteCharacter(character_id) => {
if let Some(player) = players.get(entity) {
character_loader.delete_character(
entity,
@ -351,7 +349,7 @@ impl Sys {
#[allow(clippy::too_many_arguments)]
fn handle_ping_msg(client: &mut Client, msg: PingMsg) -> Result<(), crate::error::Error> {
match msg {
PingMsg::Ping => client.send_ping(PingMsg::Pong),
PingMsg::Ping => client.send_msg(PingMsg::Pong),
PingMsg::Pong => {},
}
Ok(())
@ -368,7 +366,7 @@ impl Sys {
admins: &mut WriteStorage<'_, Admin>,
players: &mut WriteStorage<'_, Player>,
editable_settings: &ReadExpect<'_, EditableSettings>,
msg: ClientRegisterMsg,
msg: ClientRegister,
) -> Result<(), crate::error::Error> {
let (username, uuid) = match login_provider.try_login(
&msg.token_or_username,
@ -379,7 +377,7 @@ impl Sys {
Err(err) => {
client
.register_stream
.send(ServerRegisterAnswerMsg::Err(err))?;
.send(ServerRegisterAnswer::Err(err))?;
return Ok(());
},
Ok((username, uuid)) => (username, uuid),
@ -391,9 +389,9 @@ impl Sys {
if !player.is_valid() {
// Invalid player
client.register_stream.send(ServerRegisterAnswerMsg::Err(
RegisterError::InvalidCharacter,
))?;
client
.register_stream
.send(ServerRegisterAnswer::Err(RegisterError::InvalidCharacter))?;
return Ok(());
}
@ -410,12 +408,10 @@ impl Sys {
// Tell the client its request was successful.
client.registered = true;
client
.register_stream
.send(ServerRegisterAnswerMsg::Ok(()))?;
client.register_stream.send(ServerRegisterAnswer::Ok(()))?;
// Send initial player list
client.send_msg(ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Init(
client.send_msg(ServerGeneral::PlayerListUpdate(PlayerListUpdate::Init(
player_list.clone(),
)));
@ -458,7 +454,7 @@ impl Sys {
alias_validator: &ReadExpect<'_, AliasValidator>,
) -> Result<(), crate::error::Error> {
loop {
let q1 = Client::internal_recv(&client.network_error, &mut client.singleton_stream);
let q1 = Client::internal_recv(&client.network_error, &mut client.general_stream);
let q2 = Client::internal_recv(&client.network_error, &mut client.in_game_stream);
let q3 =
Client::internal_recv(&client.network_error, &mut client.character_screen_stream);
@ -693,7 +689,7 @@ impl<'a> System<'a> for Sys {
server_emitter.emit(ServerEvent::ClientDisconnect(entity));
} else if time.0 - client.last_ping > settings.client_timeout.as_secs() as f64 * 0.5 {
// Try pinging the client if the timeout is nearing.
client.send_ping(PingMsg::Ping);
client.send_msg(PingMsg::Ping);
}
}
@ -702,7 +698,7 @@ impl<'a> System<'a> for Sys {
for entity in new_players {
if let (Some(uid), Some(player)) = (uids.get(entity), players.get(entity)) {
let msg =
ServerGeneralMsg::PlayerListUpdate(PlayerListUpdate::Add(*uid, PlayerInfo {
ServerGeneral::PlayerListUpdate(PlayerListUpdate::Add(*uid, PlayerInfo {
player_alias: player.alias.clone(),
is_online: true,
is_admin: admins.get(entity).is_some(),

View File

@ -5,7 +5,7 @@ use super::{
use crate::client::{self, Client, RegionSubscription};
use common::{
comp::{Ori, Player, Pos, Vel},
msg::ServerGeneralMsg,
msg::ServerGeneral,
region::{region_in_vd, regions_in_vd, Event as RegionEvent, RegionMap},
span,
sync::Uid,
@ -153,7 +153,7 @@ impl<'a> System<'a> for Sys {
.map(|key| subscription.regions.contains(key))
.unwrap_or(false)
{
client.send_msg(ServerGeneralMsg::DeleteEntity(uid));
client.send_msg(ServerGeneral::DeleteEntity(uid));
}
}
},
@ -161,7 +161,7 @@ impl<'a> System<'a> for Sys {
}
// Tell client to delete entities in the region
for (&uid, _) in (&uids, region.entities()).join() {
client.send_msg(ServerGeneralMsg::DeleteEntity(uid));
client.send_msg(ServerGeneral::DeleteEntity(uid));
}
}
// Send deleted entities since they won't be processed for this client in entity
@ -171,7 +171,7 @@ impl<'a> System<'a> for Sys {
.iter()
.flat_map(|v| v.iter())
{
client.send_msg(ServerGeneralMsg::DeleteEntity(Uid(*uid)));
client.send_msg(ServerGeneral::DeleteEntity(Uid(*uid)));
}
}
@ -196,7 +196,7 @@ impl<'a> System<'a> for Sys {
{
// Send message to create entity and tracked components and physics
// components
client.send_msg(ServerGeneralMsg::CreateEntity(
client.send_msg(ServerGeneral::CreateEntity(
tracked_comps.create_entity_package(
entity,
Some(*pos),
@ -249,7 +249,7 @@ pub fn initialize_region_subscription(world: &World, entity: specs::Entity) {
.join()
{
// Send message to create entity and tracked components and physics components
client.send_msg(ServerGeneralMsg::CreateEntity(
client.send_msg(ServerGeneral::CreateEntity(
tracked_comps.create_entity_package(
entity,
Some(*pos),

View File

@ -4,7 +4,7 @@ use common::{
comp::{self, bird_medium, Alignment, Player, Pos},
event::{EventBus, ServerEvent},
generation::get_npc_name,
msg::ServerInGameMsg,
msg::ServerInGame,
npc::NPC_NAMES,
span,
state::TerrainChanges,
@ -63,7 +63,7 @@ impl<'a> System<'a> for Sys {
Ok((chunk, supplement)) => (chunk, supplement),
Err(Some(entity)) => {
if let Some(client) = clients.get_mut(entity) {
client.send_in_game(ServerInGameMsg::TerrainChunkUpdate {
client.send_msg(ServerInGame::TerrainChunkUpdate {
key,
chunk: Err(()),
});
@ -90,7 +90,7 @@ impl<'a> System<'a> for Sys {
.magnitude_squared();
if adjusted_dist_sqr <= view_distance.pow(2) {
client.send_in_game(ServerInGameMsg::TerrainChunkUpdate {
client.send_msg(ServerInGame::TerrainChunkUpdate {
key,
chunk: Ok(Box::new(chunk.clone())),
});

View File

@ -2,7 +2,7 @@ use super::SysTimer;
use crate::client::Client;
use common::{
comp::{Player, Pos},
msg::ServerInGameMsg,
msg::ServerInGame,
span,
state::TerrainChanges,
terrain::TerrainGrid,
@ -38,7 +38,7 @@ impl<'a> System<'a> for Sys {
.map(|vd| super::terrain::chunk_in_vd(pos.0, *chunk_key, &terrain, vd))
.unwrap_or(false)
{
client.send_in_game(ServerInGameMsg::TerrainChunkUpdate {
client.send_msg(ServerInGame::TerrainChunkUpdate {
key: *chunk_key,
chunk: Ok(Box::new(match terrain.get_key(*chunk_key) {
Some(chunk) => chunk.clone(),
@ -51,10 +51,10 @@ impl<'a> System<'a> for Sys {
// TODO: Don't send all changed blocks to all clients
// Sync changed blocks
let msg = ServerInGameMsg::TerrainBlockUpdates(terrain_changes.modified_blocks.clone());
let msg = ServerInGame::TerrainBlockUpdates(terrain_changes.modified_blocks.clone());
for (player, client) in (&players, &mut clients).join() {
if player.view_distance.is_some() {
client.send_in_game(msg.clone());
client.send_msg(msg.clone());
}
}

View File

@ -2,7 +2,7 @@ use super::SysTimer;
use crate::client::Client;
use common::{
comp::{Player, Pos, Waypoint, WaypointArea},
msg::{Notification, ServerGeneralMsg},
msg::{Notification, ServerGeneral},
span,
state::Time,
};
@ -42,9 +42,8 @@ impl<'a> System<'a> for Sys {
if let Ok(wp_old) = waypoints.insert(entity, Waypoint::new(player_pos.0, *time))
{
if wp_old.map_or(true, |w| w.elapsed(*time) > NOTIFY_TIME) {
client.send_msg(ServerGeneralMsg::Notification(
Notification::WaypointSaved,
));
client
.send_msg(ServerGeneral::Notification(Notification::WaypointSaved));
}
}
}

View File

@ -38,7 +38,7 @@ use crate::{
use common::{
comp::{self, bird_medium, quadruped_low, quadruped_medium, quadruped_small},
generation::{ChunkSupplement, EntityInfo},
msg::server::WorldMapMsg,
msg::WorldMapMsg,
terrain::{Block, BlockKind, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
vol::{ReadVol, RectVolSize, WriteVol},
};

View File

@ -33,7 +33,7 @@ use crate::{
};
use common::{
assets,
msg::server::WorldMapMsg,
msg::WorldMapMsg,
store::Id,
terrain::{
map::MapConfig, uniform_idx_as_vec2, vec2_as_uniform_idx, BiomeKind, MapSizeLg,