diff --git a/assets/voxygen/i18n/en/hud/hud_settings.ron b/assets/voxygen/i18n/en/hud/hud_settings.ron index 88e8572bd3..5aff2284bc 100644 --- a/assets/voxygen/i18n/en/hud/hud_settings.ron +++ b/assets/voxygen/i18n/en/hud/hud_settings.ron @@ -48,6 +48,7 @@ "hud.settings.free_look_behavior": "Free look behavior", "hud.settings.auto_walk_behavior": "Auto walk behavior", "hud.settings.camera_clamp_behavior": "Camera clamp behavior", + "hud.settings.player_physics_behavior": "Player physics behavior", "hud.settings.stop_auto_walk_on_input": "Stop auto walk on movement", "hud.settings.auto_camera": "Auto camera", "hud.settings.reset_gameplay": "Reset to Defaults", diff --git a/client/src/lib.rs b/client/src/lib.rs index 5a4bf27060..db5848dd37 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -786,7 +786,8 @@ impl Client { | ClientGeneral::UnlockSkill(_) | ClientGeneral::RefundSkill(_) | ClientGeneral::RequestSiteInfo(_) - | ClientGeneral::UnlockSkillGroup(_) => &mut self.in_game_stream, + | ClientGeneral::UnlockSkillGroup(_) + | ClientGeneral::RequestPlayerPhysics { .. } => &mut self.in_game_stream, //Only in game, terrain ClientGeneral::TerrainChunkRequest { .. } => &mut self.terrain_stream, //Always possible @@ -800,6 +801,12 @@ impl Client { } } + pub fn request_player_physics(&mut self, server_authoritative: bool) { + self.send_msg(ClientGeneral::RequestPlayerPhysics { + server_authoritative, + }) + } + fn send_msg(&mut self, msg: S) where S: Into, diff --git a/common/net/src/msg/client.rs b/common/net/src/msg/client.rs index 66f87da4db..ccf9bdbd32 100644 --- a/common/net/src/msg/client.rs +++ b/common/net/src/msg/client.rs @@ -81,6 +81,9 @@ pub enum ClientGeneral { //Always possible ChatMsg(String), Terminate, + RequestPlayerPhysics { + server_authoritative: bool, + }, } impl ClientMsg { @@ -117,7 +120,8 @@ impl ClientMsg { | ClientGeneral::UnlockSkill(_) | ClientGeneral::RefundSkill(_) | ClientGeneral::RequestSiteInfo(_) - | ClientGeneral::UnlockSkillGroup(_) => { + | ClientGeneral::UnlockSkillGroup(_) + | ClientGeneral::RequestPlayerPhysics { .. } => { c_type == ClientType::Game && presence.is_some() }, //Always possible diff --git a/common/src/resources.rs b/common/src/resources.rs index dccede903a..10b932a9ee 100644 --- a/common/src/resources.rs +++ b/common/src/resources.rs @@ -32,3 +32,36 @@ pub enum GameMode { /// server #[derive(Copy, Clone, Default, Debug)] pub struct PlayerEntity(pub Option); + +#[derive(Clone, Debug)] +pub struct PlayerPhysicsSetting { + /// true if the client wants server-authoratative physics (e.g. to use + /// airships properly) + pub client_optin: bool, + /// true if the server is forcing server-authoratative physics (e.g. as + /// punishment for wallhacking) + pub server_optout: bool, +} + +impl Default for PlayerPhysicsSetting { + fn default() -> PlayerPhysicsSetting { + PlayerPhysicsSetting { + client_optin: false, + server_optout: false, + } + } +} + +impl PlayerPhysicsSetting { + pub fn server_authoritative(&self) -> bool { self.client_optin || self.server_optout } + + pub fn client_authoritative(&self) -> bool { !self.server_authoritative() } +} + +/// List of which players are using client-authoratative vs server-authoratative +/// physics, as a stop-gap until we can use server-authoratative physics for +/// everyone +#[derive(Clone, Default, Debug)] +pub struct PlayerPhysicsSettings { + pub settings: hashbrown::HashMap, +} diff --git a/common/sys/src/state.rs b/common/sys/src/state.rs index 6ab7019f74..fb4da97cfd 100644 --- a/common/sys/src/state.rs +++ b/common/sys/src/state.rs @@ -10,7 +10,7 @@ use common::{ event::{EventBus, LocalEvent, ServerEvent}, outcome::Outcome, region::RegionMap, - resources::{DeltaTime, GameMode, PlayerEntity, Time, TimeOfDay}, + resources::{DeltaTime, GameMode, PlayerEntity, PlayerPhysicsSettings, Time, TimeOfDay}, slowjob::SlowJobPool, terrain::{Block, TerrainChunk, TerrainGrid}, time::DayPeriod, @@ -278,6 +278,7 @@ impl State { ecs.insert(SysMetrics::default()); ecs.insert(PhysicsMetrics::default()); ecs.insert(Trades::default()); + ecs.insert(PlayerPhysicsSettings::default()); // Load plugins from asset directory #[cfg(feature = "plugins")] diff --git a/server/src/sys/entity_sync.rs b/server/src/sys/entity_sync.rs index 8e4ae46998..d6e544a31b 100644 --- a/server/src/sys/entity_sync.rs +++ b/server/src/sys/entity_sync.rs @@ -5,10 +5,10 @@ use crate::{ Tick, }; use common::{ - comp::{Collider, ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Pos, Vel}, + comp::{Collider, ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Player, Pos, Vel}, outcome::Outcome, region::{Event as RegionEvent, RegionMap}, - resources::TimeOfDay, + resources::{PlayerPhysicsSettings, TimeOfDay}, terrain::TerrainChunkSize, uid::Uid, vol::RectVolSize, @@ -45,6 +45,8 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, InventoryUpdate>, Write<'a, DeletedEntities>, Write<'a, Vec>, + Read<'a, PlayerPhysicsSettings>, + ReadStorage<'a, Player>, TrackedComps<'a>, ReadTrackers<'a>, ); @@ -76,6 +78,8 @@ impl<'a> System<'a> for Sys { mut inventory_updates, mut deleted_entities, mut outcomes, + player_physics_settings, + players, tracked_comps, trackers, ): Self::SystemData, @@ -201,22 +205,28 @@ impl<'a> System<'a> for Sys { for (client, _, client_entity, client_pos) in &mut subscribers { let mut comp_sync_package = CompSyncPackage::new(); - for (_, entity, &uid, (&pos, last_pos), vel, ori, force_update, collider) in ( - region.entities(), - &entities, - &uids, - (&positions, last_pos.mask().maybe()), - (&velocities, last_vel.mask().maybe()).maybe(), - (&orientations, last_vel.mask().maybe()).maybe(), - force_updates.mask().maybe(), - colliders.maybe(), - ) - .join() + for (_, entity, &uid, (&pos, last_pos), vel, ori, force_update, collider, player) in + ( + region.entities(), + &entities, + &uids, + (&positions, last_pos.mask().maybe()), + (&velocities, last_vel.mask().maybe()).maybe(), + (&orientations, last_vel.mask().maybe()).maybe(), + force_updates.mask().maybe(), + colliders.maybe(), + players.maybe(), + ) + .join() { + let player_physics_setting = player + .and_then(|p| player_physics_settings.settings.get(&p.uuid()).cloned()) + .unwrap_or_default(); // Decide how regularly to send physics updates. let send_now = if client_entity == &entity { // Don't send client physics updates about itself unless force update is set - force_update.is_some() || true + // or the client is subject to server-authoritative physics + force_update.is_some() || player_physics_setting.server_authoritative() } else if matches!(collider, Some(Collider::Voxel { .. })) { // Things with a voxel collider (airships, etc.) need to have very stable // physics so we always send updated for these where diff --git a/server/src/sys/msg/in_game.rs b/server/src/sys/msg/in_game.rs index c28a1db53a..a8dc2b8a6b 100644 --- a/server/src/sys/msg/in_game.rs +++ b/server/src/sys/msg/in_game.rs @@ -1,7 +1,10 @@ use crate::{client::Client, presence::Presence, Settings}; use common::{ - comp::{CanBuild, ControlEvent, Controller, ForceUpdate, Health, Ori, Pos, SkillSet, Vel}, + comp::{ + CanBuild, ControlEvent, Controller, ForceUpdate, Health, Ori, Player, Pos, SkillSet, Vel, + }, event::{EventBus, ServerEvent}, + resources::PlayerPhysicsSettings, terrain::TerrainGrid, vol::ReadVol, }; @@ -30,6 +33,8 @@ impl Sys { controllers: &mut WriteStorage<'_, Controller>, settings: &Read<'_, Settings>, build_areas: &Read<'_, BuildAreas>, + player_physics_settings: &mut Write<'_, PlayerPhysicsSettings>, + maybe_player: &Option<&Player>, msg: ClientGeneral, ) -> Result<(), crate::error::Error> { let presence = match maybe_presence { @@ -40,6 +45,12 @@ impl Sys { return Ok(()); }, }; + let player_physics_setting = maybe_player.map(|p| { + player_physics_settings + .settings + .entry(p.uuid()) + .or_default() + }); match msg { // Go back to registered state (char selection screen) ClientGeneral::ExitInGame => { @@ -93,14 +104,15 @@ impl Sys { } }, ClientGeneral::PlayerPhysics { pos, vel, ori } => { - /*if matches!(presence.kind, PresenceKind::Character(_)) + if matches!(presence.kind, PresenceKind::Character(_)) && force_updates.get(entity).is_none() && healths.get(entity).map_or(true, |h| !h.is_dead) + && player_physics_setting.map_or(true, |s| s.client_authoritative()) { let _ = positions.insert(entity, pos); let _ = velocities.insert(entity, vel); let _ = orientations.insert(entity, ori); - }*/ + } }, ClientGeneral::BreakBlock(pos) => { if let Some(comp_can_build) = can_build.get(entity) { @@ -156,6 +168,13 @@ impl Sys { ClientGeneral::RequestSiteInfo(id) => { server_emitter.emit(ServerEvent::RequestSiteInfo { entity, id }); }, + ClientGeneral::RequestPlayerPhysics { + server_authoritative, + } => { + if let Some(setting) = player_physics_setting { + setting.client_optin = server_authoritative; + } + }, _ => tracing::error!("not a client_in_game msg"), } Ok(()) @@ -184,6 +203,8 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Controller>, Read<'a, Settings>, Read<'a, BuildAreas>, + Write<'a, PlayerPhysicsSettings>, + ReadStorage<'a, Player>, ); const NAME: &'static str = "msg::in_game"; @@ -209,12 +230,19 @@ impl<'a> System<'a> for Sys { mut controllers, settings, build_areas, + mut player_physics_settings, + players, ): Self::SystemData, ) { let mut server_emitter = server_event_bus.emitter(); - for (entity, client, mut maybe_presence) in - (&entities, &mut clients, (&mut presences).maybe()).join() + for (entity, client, mut maybe_presence, player) in ( + &entities, + &mut clients, + (&mut presences).maybe(), + players.maybe(), + ) + .join() { let _ = super::try_recv_all(client, 2, |client, msg| { Self::handle_client_in_game_msg( @@ -234,6 +262,8 @@ impl<'a> System<'a> for Sys { &mut controllers, &settings, &build_areas, + &mut player_physics_settings, + &player, msg, ) }); diff --git a/voxygen/src/hud/settings_window/gameplay.rs b/voxygen/src/hud/settings_window/gameplay.rs index 9182796507..5b08fac973 100644 --- a/voxygen/src/hud/settings_window/gameplay.rs +++ b/voxygen/src/hud/settings_window/gameplay.rs @@ -44,6 +44,8 @@ widget_ids! { auto_walk_behavior_list, camera_clamp_behavior_text, camera_clamp_behavior_list, + player_physics_behavior_text, + player_physics_behavior_list, stop_auto_walk_on_input_button, stop_auto_walk_on_input_label, auto_camera_button, @@ -438,6 +440,43 @@ impl<'a> Widget for Gameplay<'a> { } } + // Player physics behavior + Text::new( + &self + .localized_strings + .get("hud.settings.player_physics_behavior"), + ) + .down_from(state.ids.auto_walk_behavior_list, 10.0) + .right_from(state.ids.camera_clamp_behavior_text, 118.0) + .font_size(self.fonts.cyri.scale(14)) + .font_id(self.fonts.cyri.conrod_id) + .color(TEXT_COLOR) + .set(state.ids.player_physics_behavior_text, ui); + + let player_physics_selected = + self.global_state.settings.gameplay.player_physics_behavior as usize; + + if let Some(clicked) = DropDownList::new( + &["Client-authoritative", "Server-authoritative"], + Some(player_physics_selected), + ) + .w_h(200.0, 30.0) + .color(MENU_BG) + .label_color(TEXT_COLOR) + .label_font_id(self.fonts.cyri.conrod_id) + .down_from(state.ids.player_physics_behavior_text, 8.0) + .set(state.ids.player_physics_behavior_list, ui) + { + match clicked { + 0 => events.push(ChangePlayerPhysicsBehavior { + server_authoritative: false, + }), + _ => events.push(ChangePlayerPhysicsBehavior { + server_authoritative: true, + }), + } + } + // Stop autowalk on input toggle let stop_auto_walk_on_input_toggle = ToggleButton::new( self.global_state.settings.gameplay.stop_auto_walk_on_input, diff --git a/voxygen/src/session/mod.rs b/voxygen/src/session/mod.rs index c7290a6f77..c415365d64 100644 --- a/voxygen/src/session/mod.rs +++ b/voxygen/src/session/mod.rs @@ -88,6 +88,9 @@ impl SessionState { scene .camera_mut() .set_fov_deg(global_state.settings.graphics.fov); + client + .borrow_mut() + .request_player_physics(global_state.settings.gameplay.player_physics_behavior); let hud = Hud::new(global_state, &client.borrow()); let walk_forward_dir = scene.camera().forward_xy(); let walk_right_dir = scene.camera().right_xy(); diff --git a/voxygen/src/session/settings_change.rs b/voxygen/src/session/settings_change.rs index b92f9c7745..d72fc26c35 100644 --- a/voxygen/src/session/settings_change.rs +++ b/voxygen/src/session/settings_change.rs @@ -46,6 +46,7 @@ pub enum Gameplay { ChangeFreeLookBehavior(PressBehavior), ChangeAutoWalkBehavior(PressBehavior), ChangeCameraClampBehavior(PressBehavior), + ChangePlayerPhysicsBehavior { server_authoritative: bool }, ChangeStopAutoWalkOnInput(bool), ChangeAutoCamera(bool), @@ -224,6 +225,15 @@ impl SettingsChange { Gameplay::ChangeCameraClampBehavior(behavior) => { settings.gameplay.camera_clamp_behavior = behavior; }, + Gameplay::ChangePlayerPhysicsBehavior { + server_authoritative, + } => { + settings.gameplay.player_physics_behavior = server_authoritative; + session_state + .client + .borrow_mut() + .request_player_physics(server_authoritative); + }, Gameplay::ChangeStopAutoWalkOnInput(state) => { settings.gameplay.stop_auto_walk_on_input = state; }, diff --git a/voxygen/src/settings/gameplay.rs b/voxygen/src/settings/gameplay.rs index efece86d34..488c641f2e 100644 --- a/voxygen/src/settings/gameplay.rs +++ b/voxygen/src/settings/gameplay.rs @@ -14,6 +14,7 @@ pub struct GameplaySettings { pub free_look_behavior: PressBehavior, pub auto_walk_behavior: PressBehavior, pub camera_clamp_behavior: PressBehavior, + pub player_physics_behavior: bool, pub stop_auto_walk_on_input: bool, pub auto_camera: bool, } @@ -30,6 +31,7 @@ impl Default for GameplaySettings { free_look_behavior: PressBehavior::Toggle, auto_walk_behavior: PressBehavior::Toggle, camera_clamp_behavior: PressBehavior::Toggle, + player_physics_behavior: false, stop_auto_walk_on_input: true, auto_camera: false, }