Merge branch 'imbris/char-screen-transition' into 'master'

Fix issues regarding going back to the character selection screen

Closes #386

See merge request veloren/veloren!700
This commit is contained in:
Imbris 2020-01-02 19:40:21 +00:00
commit 4fa05150a2
16 changed files with 208 additions and 176 deletions

View File

@ -47,6 +47,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed region display name
- Fixed the bow fire rate
- Healthbars now flash on critical health
- Fixed ghosts when going back to character screen
- Fixed not being able to unmount
- Fixed non-humanoids being able to climb and glide
### Removed

View File

@ -14,10 +14,10 @@ members = [
# default profile for devs, fast to compile, okay enough to run, no debug information
[profile.dev]
opt-level = 2
overflow-checks = false
overflow-checks = true
debug-assertions = true
panic = "abort"
debug = false
debug = false
codegen-units = 8
lto = false
incremental = true
@ -44,7 +44,7 @@ opt-level = 2
inherits= 'dev'
debug = true
# this profil is used for veloren releases, compile time doesn't matter
# this profile is used for veloren releases, compile time doesn't matter
# we need stacktraces, light debug information, as much checks as possible
# I would like to put it in a seperate `official_release` target, but that doesnt share caches with `cargo test` and `cargo bench`
[profile.release]

View File

@ -5,7 +5,11 @@ pub mod error;
// Reexports
pub use crate::error::Error;
pub use specs::{join::Join, saveload::Marker, Entity as EcsEntity, ReadStorage, WorldExt};
pub use specs::{
join::Join,
saveload::{Marker, MarkerAllocator},
Builder, Entity as EcsEntity, ReadStorage, WorldExt,
};
use common::{
comp::{self, ControlEvent, Controller, ControllerInputs, InventoryManip},
@ -15,7 +19,7 @@ use common::{
},
net::PostBox,
state::State,
sync::{Uid, WorldSyncExt},
sync::{Uid, UidAllocator, WorldSyncExt},
terrain::{block::Block, TerrainChunk, TerrainChunkSize},
vol::RectVolSize,
ChatType,
@ -84,7 +88,7 @@ impl Client {
time_of_day,
// world_map: /*(map_size, world_map)*/map_size,
}) => {
// TODO: Voxygen should display this.
// TODO: Display that versions don't match in Voxygen
if server_info.git_hash != common::util::GIT_HASH.to_string() {
log::warn!(
"Server is running {}[{}], you are running {}[{}], versions might be incompatible!",
@ -183,17 +187,15 @@ impl Client {
self.client_state = ClientState::Pending;
}
/// Request a state transition to `ClientState::Character`.
/// Send disconnect message to the server
pub fn request_logout(&mut self) {
self.postbox
.send_message(ClientMsg::RequestState(ClientState::Connected));
self.postbox.send_message(ClientMsg::Disconnect);
self.client_state = ClientState::Pending;
}
/// Request a state transition to `ClientState::Character`.
/// Request a state transition to `ClientState::Registered` from an ingame state.
pub fn request_remove_character(&mut self) {
self.postbox
.send_message(ClientMsg::RequestState(ClientState::Registered));
self.postbox.send_message(ClientMsg::ExitIngame);
self.client_state = ClientState::Pending;
}
@ -282,9 +284,9 @@ impl Client {
}
/// Send a chat message to the server.
pub fn send_chat(&mut self, msg: String) {
match validate_chat_msg(&msg) {
Ok(()) => self.postbox.send_message(ClientMsg::chat(msg)),
pub fn send_chat(&mut self, message: String) {
match validate_chat_msg(&message) {
Ok(()) => self.postbox.send_message(ClientMsg::ChatMsg { message }),
Err(ChatMsgValidationError::TooLong) => log::warn!(
"Attempted to send a message that's too long (Over {} bytes)",
MAX_BYTES_CHAT_MSG
@ -331,7 +333,7 @@ impl Client {
// 1) Handle input from frontend.
// Pass character actions from frontend input to the player's entity.
if let ClientState::Character | ClientState::Dead = self.client_state {
if let ClientState::Character = self.client_state {
self.state.write_component(
self.entity,
Controller {
@ -560,8 +562,8 @@ impl Client {
.duration_since(self.last_server_ping)
.as_secs_f64();
}
ServerMsg::ChatMsg { chat_type, message } => {
frontend_events.push(Event::Chat { chat_type, message })
ServerMsg::ChatMsg { message, chat_type } => {
frontend_events.push(Event::Chat { message, chat_type })
}
ServerMsg::SetPlayerEntity(uid) => {
if let Some(entity) = self.state.ecs().entity_from_uid(uid) {
@ -591,6 +593,26 @@ impl Client {
.delete_entity_and_clear_from_uid_allocator(entity);
}
}
// Cleanup for when the client goes back to the `Registered` state
ServerMsg::ExitIngameCleanup => {
// Get client entity Uid
let client_uid = self
.state
.read_component_cloned::<Uid>(self.entity)
.map(|u| u.into())
.expect("Client doesn't have a Uid!!!");
// Clear ecs of all entities
self.state.ecs_mut().delete_all();
self.state.ecs_mut().maintain();
self.state.ecs_mut().insert(UidAllocator::default());
// Recreate client entity with Uid
let entity_builder = self.state.ecs_mut().create_entity();
let uid = entity_builder
.world
.write_resource::<UidAllocator>()
.allocate(entity_builder.entity, Some(client_uid));
self.entity = entity_builder.with(uid).build();
}
ServerMsg::EntityPos { entity, pos } => {
if let Some(entity) = self.state.ecs().entity_from_uid(entity) {
self.state.write_component(entity, pos);
@ -641,9 +663,6 @@ impl Client {
error, state
);
}
ServerMsg::ForceState(state) => {
self.client_state = state;
}
ServerMsg::Disconnect => {
frontend_events.push(Event::Disconnect);
}

View File

@ -88,12 +88,15 @@ pub enum ServerEvent {
Mount(EcsEntity, EcsEntity),
Unmount(EcsEntity),
Possess(Uid, Uid),
CreatePlayer {
CreateCharacter {
entity: EcsEntity,
name: String,
body: comp::Body,
main: Option<String>,
},
ExitIngame {
entity: EcsEntity,
},
CreateNpc {
pos: comp::Pos,
stats: comp::Stats,

View File

@ -1,6 +1,4 @@
use super::ClientState;
use crate::terrain::block::Block;
use crate::{comp, ChatType};
use crate::{comp, terrain::block::Block};
use vek::*;
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -14,16 +12,18 @@ pub enum ClientMsg {
body: comp::Body,
main: Option<String>, // Specifier for the weapon
},
/// Request `ClientState::Registered` from an ingame state
ExitIngame,
/// Request `ClientState::Spectator` from a registered or ingame state
Spectate,
ControllerInputs(comp::ControllerInputs),
ControlEvent(comp::ControlEvent),
RequestState(ClientState),
SetViewDistance(u32),
BreakBlock(Vec3<i32>),
PlaceBlock(Vec3<i32>, Block),
Ping,
Pong,
ChatMsg {
chat_type: ChatType, // This is unused afaik, TODO: remove
message: String,
},
PlayerPhysics {
@ -36,42 +36,3 @@ pub enum ClientMsg {
},
Disconnect,
}
impl ClientMsg {
pub fn chat(message: String) -> ClientMsg {
ClientMsg::ChatMsg {
chat_type: ChatType::Chat,
message,
}
}
pub fn tell(message: String) -> ClientMsg {
ClientMsg::ChatMsg {
chat_type: ChatType::Tell,
message,
}
}
pub fn game(message: String) -> ClientMsg {
ClientMsg::ChatMsg {
chat_type: ChatType::GameUpdate,
message,
}
}
pub fn broadcast(message: String) -> ClientMsg {
ClientMsg::ChatMsg {
chat_type: ChatType::Broadcast,
message,
}
}
pub fn private(message: String) -> ClientMsg {
ClientMsg::ChatMsg {
chat_type: ChatType::Private,
message,
}
}
pub fn kill(message: String) -> ClientMsg {
ClientMsg::ChatMsg {
chat_type: ChatType::Private,
message,
}
}
}

View File

@ -13,7 +13,6 @@ pub enum ClientState {
Connected,
Registered,
Spectator,
Dead,
Character,
}

View File

@ -41,7 +41,9 @@ pub enum ServerMsg {
},
PlayerListUpdate(PlayerListUpdate),
StateAnswer(Result<ClientState, (RequestStateError, ClientState)>),
ForceState(ClientState),
/// Trigger cleanup for when the client goes back to the `Registered` state from an ingame
/// state
ExitIngameCleanup,
Ping,
Pong,
ChatMsg {

View File

@ -327,6 +327,28 @@ impl<'a> System<'a> for Sys {
// Or do nothing
continue;
}
// Process controller events
for event in controller.events.drain(..) {
match event {
ControlEvent::Mount(mountee_uid) => {
if let Some(mountee_entity) =
uid_allocator.retrieve_entity_internal(mountee_uid.id())
{
server_emitter.emit(ServerEvent::Mount(entity, mountee_entity));
}
}
ControlEvent::Unmount => server_emitter.emit(ServerEvent::Unmount(entity)),
ControlEvent::InventoryManip(manip) => {
server_emitter.emit(ServerEvent::InventoryManip(entity, manip))
} /*ControlEvent::Respawn => {
if state.is_dead {
server_emitter.emit(ServerEvent::Respawn(entity)),
}
}*/
}
}
// If mounted, character state is controlled by mount
if mount.is_some() {
character.movement = Sit;
@ -394,14 +416,14 @@ impl<'a> System<'a> for Sys {
// Any Action + Falling
(action_state, Fall) => {
character.movement = get_state_from_move_dir(&inputs.move_dir);
if inputs.glide.is_pressed() {
if inputs.glide.is_pressed() && can_glide(body) {
character.movement = Glide;
continue;
}
// Try to climb
if let (true, Some(_wall_dir)) = (
inputs.climb.is_pressed() | inputs.climb_down.is_pressed()
&& body.is_humanoid(),
(inputs.climb.is_pressed() | inputs.climb_down.is_pressed())
&& can_climb(body),
physics.on_wall,
) {
character.movement = Climb;
@ -530,8 +552,8 @@ impl<'a> System<'a> for Sys {
// Try to climb
if let (true, Some(_wall_dir)) = (
inputs.climb.is_pressed() | inputs.climb_down.is_pressed()
&& body.is_humanoid(),
(inputs.climb.is_pressed() | inputs.climb_down.is_pressed())
&& can_climb(body),
physics.on_wall,
) {
character.movement = Climb;
@ -578,9 +600,7 @@ impl<'a> System<'a> for Sys {
// While not on ground ...
else {
// Try to glide
if physics.on_wall == None
&& inputs.glide.is_pressed()
&& body.is_humanoid()
if physics.on_wall == None && inputs.glide.is_pressed() && can_glide(&body)
{
character.movement = Glide;
continue;
@ -652,7 +672,7 @@ impl<'a> System<'a> for Sys {
if !inputs.glide.is_pressed() {
character.movement = Fall;
} else if let Some(_wall_dir) = physics.on_wall {
} else if let (Some(_wall_dir), true) = (physics.on_wall, can_climb(body)) {
character.movement = Climb;
}
@ -681,23 +701,14 @@ impl<'a> System<'a> for Sys {
// character.movement = Fall;
// }
};
// Process other controller events
for event in controller.events.drain(..) {
match event {
ControlEvent::Mount(mountee_uid) => {
if let Some(mountee_entity) =
uid_allocator.retrieve_entity_internal(mountee_uid.id())
{
server_emitter.emit(ServerEvent::Mount(entity, mountee_entity));
}
}
ControlEvent::Unmount => server_emitter.emit(ServerEvent::Unmount(entity)),
ControlEvent::InventoryManip(manip) => {
server_emitter.emit(ServerEvent::InventoryManip(entity, manip))
} //ControlEvent::Respawn => server_emitter.emit(ServerEvent::Unmount(entity)),
}
}
}
}
}
fn can_glide(body: &Body) -> bool {
body.is_humanoid()
}
fn can_climb(body: &Body) -> bool {
body.is_humanoid()
}

View File

@ -24,8 +24,8 @@ const CLEANUP_SYS: &str = "cleanup_sys";
pub fn add_local_systems(dispatch_builder: &mut DispatcherBuilder) {
dispatch_builder.add(agent::Sys, AGENT_SYS, &[]);
dispatch_builder.add(controller::Sys, CONTROLLER_SYS, &[AGENT_SYS]);
dispatch_builder.add(mount::Sys, MOUNT_SYS, &[CONTROLLER_SYS]);
dispatch_builder.add(mount::Sys, MOUNT_SYS, &[AGENT_SYS]);
dispatch_builder.add(controller::Sys, CONTROLLER_SYS, &[AGENT_SYS, MOUNT_SYS]);
dispatch_builder.add(movement::Sys, MOVEMENT_SYS, &[]);
dispatch_builder.add(combat::Sys, COMBAT_SYS, &[CONTROLLER_SYS]);
dispatch_builder.add(stats::Sys, STATS_SYS, &[COMBAT_SYS]);

View File

@ -40,9 +40,15 @@ impl<'a> System<'a> for Sys {
match mount_states.get_unchecked() {
MountState::Unmounted => {}
MountState::MountedBy(mounter_uid) => {
if let Some((controller, mounter)) = uid_allocator
// Note: currently controller events are not passed through since none of them
// are currently relevant to controlling the mounted entity
if let Some((inputs, mounter)) = uid_allocator
.retrieve_entity_internal(mounter_uid.id())
.and_then(|mounter| controllers.get(mounter).cloned().map(|x| (x, mounter)))
.and_then(|mounter| {
controllers
.get(mounter)
.map(|c| (c.inputs.clone(), mounter))
})
{
// TODO: consider joining on these? (remember we can use .maybe())
let pos = positions.get(entity).copied();
@ -53,7 +59,12 @@ impl<'a> System<'a> for Sys {
let _ = orientations.insert(mounter, ori);
let _ = velocities.insert(mounter, vel);
}
let _ = controllers.insert(entity, controller);
controllers.get_mut(entity).map(|controller| {
*controller = Controller {
inputs,
..Default::default()
}
});
} else {
*(mount_states.get_mut_unchecked()) = MountState::Unmounted;
}

View File

@ -215,8 +215,7 @@ impl<'a> System<'a> for Sys {
// Climb
if let (true, Some(_wall_dir)) = (
(inputs.climb.is_pressed() | inputs.climb_down.is_pressed())
&& vel.0.z <= CLIMB_SPEED,
character.movement == Climb && vel.0.z <= CLIMB_SPEED,
physics.on_wall,
) {
if inputs.climb_down.is_pressed() && !inputs.climb.is_pressed() {

View File

@ -11,6 +11,7 @@ pub struct Client {
pub client_state: ClientState,
pub postbox: PostBox<ServerMsg, ClientMsg>,
pub last_ping: f64,
pub login_msg_sent: bool,
}
impl Component for Client {
@ -23,16 +24,13 @@ impl Client {
}
pub fn is_registered(&self) -> bool {
match self.client_state {
ClientState::Registered
| ClientState::Spectator
| ClientState::Dead
| ClientState::Character => true,
ClientState::Registered | ClientState::Spectator | ClientState::Character => true,
_ => false,
}
}
pub fn is_ingame(&self) -> bool {
match self.client_state {
ClientState::Spectator | ClientState::Character | ClientState::Dead => true,
ClientState::Spectator | ClientState::Character => true,
_ => false,
}
}
@ -45,10 +43,6 @@ impl Client {
self.postbox
.send_message(ServerMsg::StateAnswer(Err((error, self.client_state))));
}
pub fn force_state(&mut self, new_state: ClientState) {
self.client_state = new_state;
self.postbox.send_message(ServerMsg::ForceState(new_state));
}
}
// Distance from fuzzy_chunk before snapping to current chunk

View File

@ -28,7 +28,7 @@ use common::{
msg::{ClientMsg, ClientState, PlayerListUpdate, ServerError, ServerInfo, ServerMsg},
net::PostOffice,
state::{BlockChange, State, TimeOfDay},
sync::{Uid, WorldSyncExt},
sync::{Uid, UidAllocator, WorldSyncExt},
terrain::{block::Block, TerrainChunkSize, TerrainGrid},
vol::{ReadVol, RectVolSize, Vox},
};
@ -36,8 +36,8 @@ use log::{debug, error};
use metrics::ServerMetrics;
use rand::Rng;
use specs::{
join::Join, world::EntityBuilder as EcsEntityBuilder, Builder, Entity as EcsEntity, RunNow,
SystemData, WorldExt,
join::Join, saveload::MarkerAllocator, world::EntityBuilder as EcsEntityBuilder, Builder,
Entity as EcsEntity, RunNow, SystemData, WorldExt,
};
use std::{
i32,
@ -413,10 +413,12 @@ impl Server {
}
}
let mut remove = true;
if let Some(client) = state.ecs().write_storage::<Client>().get_mut(entity) {
remove = false;
if state
.ecs()
.write_storage::<Client>()
.get_mut(entity)
.is_some()
{
state
.ecs()
.write_storage()
@ -431,10 +433,8 @@ impl Server {
.map(|err| {
error!("Failed to insert ForceUpdate on dead client: {:?}", err)
});
client.force_state(ClientState::Dead);
}
if remove {
} else {
// If not a player delete the entity
if let Err(err) = state.delete_entity_recorded(entity) {
error!("Failed to delete destroyed entity: {:?}", err);
}
@ -580,13 +580,17 @@ impl Server {
ServerEvent::Respawn(entity) => {
// Only clients can respawn
if let Some(client) = state.ecs().write_storage::<Client>().get_mut(entity) {
if state
.ecs()
.write_storage::<Client>()
.get_mut(entity)
.is_some()
{
let respawn_point = state
.read_component_cloned::<comp::Waypoint>(entity)
.map(|wp| wp.get_pos())
.unwrap_or(state.ecs().read_resource::<SpawnPoint>().0);
client.allow_state(ClientState::Character);
state
.ecs()
.write_storage::<comp::Stats>()
@ -597,10 +601,13 @@ impl Server {
.write_storage::<comp::Pos>()
.get_mut(entity)
.map(|pos| pos.0 = respawn_point);
let _ = state
state
.ecs()
.write_storage()
.insert(entity, comp::ForceUpdate);
.insert(entity, comp::ForceUpdate)
.err().map(|err|
error!("Error inserting ForceUpdate component when respawning client: {:?}", err)
);
}
}
@ -730,7 +737,7 @@ impl Server {
}
}
ServerEvent::CreatePlayer {
ServerEvent::CreateCharacter {
entity,
name,
body,
@ -747,6 +754,37 @@ impl Server {
sys::subscription::initialize_region_subscription(state.ecs(), entity);
}
ServerEvent::ExitIngame { entity } => {
// Create new entity with just `Client`, `Uid`, and `Player` components
// Easier than checking and removing all other known components
// Note: If other `ServerEvent`s are referring to this entity they will be
// disrupted
let maybe_client = state.ecs().write_storage::<Client>().remove(entity);
let maybe_uid = state.read_component_cloned::<Uid>(entity);
let maybe_player = state.ecs().write_storage::<comp::Player>().remove(entity);
if let (Some(mut client), Some(uid), Some(player)) =
(maybe_client, maybe_uid, maybe_player)
{
// Tell client its request was successful
client.allow_state(ClientState::Registered);
// Tell client to clear out other entities and its own components
client.notify(ServerMsg::ExitIngameCleanup);
let entity_builder =
state.ecs_mut().create_entity().with(client).with(player);
// Ensure UidAllocator maps this uid to the new entity
let uid = entity_builder
.world
.write_resource::<UidAllocator>()
.allocate(entity_builder.entity, Some(uid.into()));
entity_builder.with(uid).build();
}
// Delete old entity
if let Err(err) = state.delete_entity_recorded(entity) {
error!("Failed to delete entity when removing character: {:?}", err);
}
}
ServerEvent::CreateNpc {
pos,
stats,
@ -993,6 +1031,7 @@ impl Server {
client_state: ClientState::Connected,
postbox,
last_ping: self.state.get_time(),
login_msg_sent: false,
};
if self.server_settings.max_players

View File

@ -1,7 +1,7 @@
use super::SysTimer;
use crate::{auth_provider::AuthProvider, client::Client, CLIENT_TIMEOUT};
use common::{
comp::{Admin, Body, CanBuild, Controller, ForceUpdate, Ori, Player, Pos, Vel},
comp::{Admin, Body, CanBuild, Controller, ForceUpdate, Ori, Player, Pos, Stats, Vel},
event::{EventBus, ServerEvent},
msg::{
validate_chat_msg, ChatMsgValidationError, ClientMsg, ClientState, PlayerListUpdate,
@ -31,6 +31,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, CanBuild>,
ReadStorage<'a, Admin>,
ReadStorage<'a, ForceUpdate>,
ReadStorage<'a, Stats>,
WriteExpect<'a, AuthProvider>,
Write<'a, BlockChange>,
WriteStorage<'a, Pos>,
@ -54,6 +55,7 @@ impl<'a> System<'a> for Sys {
can_build,
admins,
force_updates,
stats,
mut accounts,
mut block_changes,
mut positions,
@ -98,40 +100,26 @@ impl<'a> System<'a> for Sys {
// Process incoming messages.
for msg in new_msgs {
match msg {
ClientMsg::RequestState(requested_state) => match requested_state {
ClientState::Connected => disconnect = true, // Default state
ClientState::Registered => match client.client_state {
// Use ClientMsg::Register instead.
ClientState::Connected => {
client.error_state(RequestStateError::WrongMessage)
}
ClientState::Registered => {
client.error_state(RequestStateError::Already)
}
ClientState::Spectator | ClientState::Character | ClientState::Dead => {
// TODO: remove position etc here
client.allow_state(ClientState::Registered)
}
ClientState::Pending => {}
},
ClientState::Spectator => match requested_state {
// Become Registered first.
ClientState::Connected => {
client.error_state(RequestStateError::Impossible)
}
ClientState::Spectator => {
client.error_state(RequestStateError::Already)
}
ClientState::Registered
| ClientState::Character
| ClientState::Dead => client.allow_state(ClientState::Spectator),
ClientState::Pending => {}
},
// Use ClientMsg::Character instead.
ClientState::Character => {
// Go back to registered state (char selection screen)
ClientMsg::ExitIngame => match client.client_state {
// Use ClientMsg::Register instead.
ClientState::Connected => {
client.error_state(RequestStateError::WrongMessage)
}
ClientState::Dead => client.error_state(RequestStateError::Impossible),
ClientState::Registered => client.error_state(RequestStateError::Already),
ClientState::Spectator | ClientState::Character => {
server_emitter.emit(ServerEvent::ExitIngame { entity });
}
ClientState::Pending => {}
},
// Request spectator state
ClientMsg::Spectate => match client.client_state {
// Become Registered first.
ClientState::Connected => client.error_state(RequestStateError::Impossible),
ClientState::Spectator => client.error_state(RequestStateError::Already),
ClientState::Registered | ClientState::Character => {
client.allow_state(ClientState::Spectator)
}
ClientState::Pending => {}
},
// Valid player
@ -173,12 +161,12 @@ impl<'a> System<'a> for Sys {
ClientMsg::Character { name, body, main } => match client.client_state {
// Become Registered first.
ClientState::Connected => client.error_state(RequestStateError::Impossible),
ClientState::Registered | ClientState::Spectator | ClientState::Dead => {
if let (Some(player), None) = (
ClientState::Registered | ClientState::Spectator => {
if let (Some(player), false) = (
players.get(entity),
// Only send login message if the player didn't have a body
// Only send login message if it wasn't already sent
// previously
bodies.get(entity),
client.login_msg_sent,
) {
new_chat_msgs.push((
None,
@ -187,9 +175,10 @@ impl<'a> System<'a> for Sys {
&player.alias
)),
));
client.login_msg_sent = true;
}
server_emitter.emit(ServerEvent::CreatePlayer {
server_emitter.emit(ServerEvent::CreateCharacter {
entity,
name,
body,
@ -205,7 +194,7 @@ impl<'a> System<'a> for Sys {
| ClientState::Spectator => {
client.error_state(RequestStateError::Impossible)
}
ClientState::Dead | ClientState::Character => {
ClientState::Character => {
if let Some(controller) = controllers.get_mut(entity) {
controller.inputs = inputs;
}
@ -218,21 +207,19 @@ impl<'a> System<'a> for Sys {
| ClientState::Spectator => {
client.error_state(RequestStateError::Impossible)
}
ClientState::Dead | ClientState::Character => {
ClientState::Character => {
if let Some(controller) = controllers.get_mut(entity) {
controller.events.push(event);
}
}
ClientState::Pending => {}
},
ClientMsg::ChatMsg { chat_type, message } => match client.client_state {
ClientMsg::ChatMsg { message } => match client.client_state {
ClientState::Connected => client.error_state(RequestStateError::Impossible),
ClientState::Registered
| ClientState::Spectator
| ClientState::Dead
| ClientState::Character => match validate_chat_msg(&message) {
Ok(()) => new_chat_msgs
.push((Some(entity), ServerMsg::ChatMsg { chat_type, message })),
Ok(()) => new_chat_msgs.push((Some(entity), ServerMsg::chat(message))),
Err(ChatMsgValidationError::TooLong) => log::warn!(
"Recieved a chat message that's too long (max:{} len:{})",
MAX_BYTES_CHAT_MSG,
@ -243,7 +230,9 @@ impl<'a> System<'a> for Sys {
},
ClientMsg::PlayerPhysics { pos, vel, ori } => match client.client_state {
ClientState::Character => {
if force_updates.get(entity).is_none() {
if force_updates.get(entity).is_none()
&& stats.get(entity).map_or(true, |s| !s.is_dead)
{
let _ = positions.insert(entity, pos);
let _ = velocities.insert(entity, vel);
let _ = orientations.insert(entity, ori);
@ -263,7 +252,7 @@ impl<'a> System<'a> for Sys {
}
}
ClientMsg::TerrainChunkRequest { key } => match client.client_state {
ClientState::Connected | ClientState::Registered | ClientState::Dead => {
ClientState::Connected | ClientState::Registered => {
client.error_state(RequestStateError::Impossible);
}
ClientState::Spectator | ClientState::Character => {

View File

@ -1,5 +1,5 @@
use client::{error::Error as ClientError, Client};
use common::comp;
use common::{comp, net::PostError};
use crossbeam::channel::{unbounded, Receiver, TryRecvError};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
@ -87,6 +87,10 @@ impl ClientInit {
}
Err(err) => {
match err {
ClientError::Network(PostError::Bincode(_)) => {
last_err = Some(Error::ConnectionFailed(err));
break 'tries;
}
// Assume the connection failed and try again soon
ClientError::Network(_) => {}
ClientError::TooManyPlayers => {

View File

@ -121,9 +121,7 @@ impl PlayState for SessionState {
// Game loop
let mut current_client_state = self.client.borrow().get_client_state();
while let ClientState::Pending | ClientState::Character | ClientState::Dead =
current_client_state
{
while let ClientState::Pending | ClientState::Character = current_client_state {
// Compute camera data
let (view_mat, _, cam_pos) = self
.scene