Add a toggle to negotiate the use of server-authoritative physics.

This commit is contained in:
Avi Weinstock 2021-04-14 15:51:03 -04:00
parent d6f4537d39
commit 50d0314a75
11 changed files with 162 additions and 22 deletions

View File

@ -48,6 +48,7 @@
"hud.settings.free_look_behavior": "Free look behavior", "hud.settings.free_look_behavior": "Free look behavior",
"hud.settings.auto_walk_behavior": "Auto walk behavior", "hud.settings.auto_walk_behavior": "Auto walk behavior",
"hud.settings.camera_clamp_behavior": "Camera clamp 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.stop_auto_walk_on_input": "Stop auto walk on movement",
"hud.settings.auto_camera": "Auto camera", "hud.settings.auto_camera": "Auto camera",
"hud.settings.reset_gameplay": "Reset to Defaults", "hud.settings.reset_gameplay": "Reset to Defaults",

View File

@ -786,7 +786,8 @@ impl Client {
| ClientGeneral::UnlockSkill(_) | ClientGeneral::UnlockSkill(_)
| ClientGeneral::RefundSkill(_) | ClientGeneral::RefundSkill(_)
| ClientGeneral::RequestSiteInfo(_) | ClientGeneral::RequestSiteInfo(_)
| ClientGeneral::UnlockSkillGroup(_) => &mut self.in_game_stream, | ClientGeneral::UnlockSkillGroup(_)
| ClientGeneral::RequestPlayerPhysics { .. } => &mut self.in_game_stream,
//Only in game, terrain //Only in game, terrain
ClientGeneral::TerrainChunkRequest { .. } => &mut self.terrain_stream, ClientGeneral::TerrainChunkRequest { .. } => &mut self.terrain_stream,
//Always possible //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<S>(&mut self, msg: S) fn send_msg<S>(&mut self, msg: S)
where where
S: Into<ClientMsg>, S: Into<ClientMsg>,

View File

@ -81,6 +81,9 @@ pub enum ClientGeneral {
//Always possible //Always possible
ChatMsg(String), ChatMsg(String),
Terminate, Terminate,
RequestPlayerPhysics {
server_authoritative: bool,
},
} }
impl ClientMsg { impl ClientMsg {
@ -117,7 +120,8 @@ impl ClientMsg {
| ClientGeneral::UnlockSkill(_) | ClientGeneral::UnlockSkill(_)
| ClientGeneral::RefundSkill(_) | ClientGeneral::RefundSkill(_)
| ClientGeneral::RequestSiteInfo(_) | ClientGeneral::RequestSiteInfo(_)
| ClientGeneral::UnlockSkillGroup(_) => { | ClientGeneral::UnlockSkillGroup(_)
| ClientGeneral::RequestPlayerPhysics { .. } => {
c_type == ClientType::Game && presence.is_some() c_type == ClientType::Game && presence.is_some()
}, },
//Always possible //Always possible

View File

@ -32,3 +32,36 @@ pub enum GameMode {
/// server /// server
#[derive(Copy, Clone, Default, Debug)] #[derive(Copy, Clone, Default, Debug)]
pub struct PlayerEntity(pub Option<Entity>); pub struct PlayerEntity(pub Option<Entity>);
#[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<uuid::Uuid, PlayerPhysicsSetting>,
}

View File

@ -10,7 +10,7 @@ use common::{
event::{EventBus, LocalEvent, ServerEvent}, event::{EventBus, LocalEvent, ServerEvent},
outcome::Outcome, outcome::Outcome,
region::RegionMap, region::RegionMap,
resources::{DeltaTime, GameMode, PlayerEntity, Time, TimeOfDay}, resources::{DeltaTime, GameMode, PlayerEntity, PlayerPhysicsSettings, Time, TimeOfDay},
slowjob::SlowJobPool, slowjob::SlowJobPool,
terrain::{Block, TerrainChunk, TerrainGrid}, terrain::{Block, TerrainChunk, TerrainGrid},
time::DayPeriod, time::DayPeriod,
@ -278,6 +278,7 @@ impl State {
ecs.insert(SysMetrics::default()); ecs.insert(SysMetrics::default());
ecs.insert(PhysicsMetrics::default()); ecs.insert(PhysicsMetrics::default());
ecs.insert(Trades::default()); ecs.insert(Trades::default());
ecs.insert(PlayerPhysicsSettings::default());
// Load plugins from asset directory // Load plugins from asset directory
#[cfg(feature = "plugins")] #[cfg(feature = "plugins")]

View File

@ -5,10 +5,10 @@ use crate::{
Tick, Tick,
}; };
use common::{ use common::{
comp::{Collider, ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Pos, Vel}, comp::{Collider, ForceUpdate, Inventory, InventoryUpdate, Last, Ori, Player, Pos, Vel},
outcome::Outcome, outcome::Outcome,
region::{Event as RegionEvent, RegionMap}, region::{Event as RegionEvent, RegionMap},
resources::TimeOfDay, resources::{PlayerPhysicsSettings, TimeOfDay},
terrain::TerrainChunkSize, terrain::TerrainChunkSize,
uid::Uid, uid::Uid,
vol::RectVolSize, vol::RectVolSize,
@ -45,6 +45,8 @@ impl<'a> System<'a> for Sys {
WriteStorage<'a, InventoryUpdate>, WriteStorage<'a, InventoryUpdate>,
Write<'a, DeletedEntities>, Write<'a, DeletedEntities>,
Write<'a, Vec<Outcome>>, Write<'a, Vec<Outcome>>,
Read<'a, PlayerPhysicsSettings>,
ReadStorage<'a, Player>,
TrackedComps<'a>, TrackedComps<'a>,
ReadTrackers<'a>, ReadTrackers<'a>,
); );
@ -76,6 +78,8 @@ impl<'a> System<'a> for Sys {
mut inventory_updates, mut inventory_updates,
mut deleted_entities, mut deleted_entities,
mut outcomes, mut outcomes,
player_physics_settings,
players,
tracked_comps, tracked_comps,
trackers, trackers,
): Self::SystemData, ): Self::SystemData,
@ -201,22 +205,28 @@ impl<'a> System<'a> for Sys {
for (client, _, client_entity, client_pos) in &mut subscribers { for (client, _, client_entity, client_pos) in &mut subscribers {
let mut comp_sync_package = CompSyncPackage::new(); let mut comp_sync_package = CompSyncPackage::new();
for (_, entity, &uid, (&pos, last_pos), vel, ori, force_update, collider) in ( for (_, entity, &uid, (&pos, last_pos), vel, ori, force_update, collider, player) in
region.entities(), (
&entities, region.entities(),
&uids, &entities,
(&positions, last_pos.mask().maybe()), &uids,
(&velocities, last_vel.mask().maybe()).maybe(), (&positions, last_pos.mask().maybe()),
(&orientations, last_vel.mask().maybe()).maybe(), (&velocities, last_vel.mask().maybe()).maybe(),
force_updates.mask().maybe(), (&orientations, last_vel.mask().maybe()).maybe(),
colliders.maybe(), force_updates.mask().maybe(),
) colliders.maybe(),
.join() 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. // Decide how regularly to send physics updates.
let send_now = if client_entity == &entity { let send_now = if client_entity == &entity {
// Don't send client physics updates about itself unless force update is set // 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 { .. })) { } else if matches!(collider, Some(Collider::Voxel { .. })) {
// Things with a voxel collider (airships, etc.) need to have very stable // Things with a voxel collider (airships, etc.) need to have very stable
// physics so we always send updated for these where // physics so we always send updated for these where

View File

@ -1,7 +1,10 @@
use crate::{client::Client, presence::Presence, Settings}; use crate::{client::Client, presence::Presence, Settings};
use common::{ 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}, event::{EventBus, ServerEvent},
resources::PlayerPhysicsSettings,
terrain::TerrainGrid, terrain::TerrainGrid,
vol::ReadVol, vol::ReadVol,
}; };
@ -30,6 +33,8 @@ impl Sys {
controllers: &mut WriteStorage<'_, Controller>, controllers: &mut WriteStorage<'_, Controller>,
settings: &Read<'_, Settings>, settings: &Read<'_, Settings>,
build_areas: &Read<'_, BuildAreas>, build_areas: &Read<'_, BuildAreas>,
player_physics_settings: &mut Write<'_, PlayerPhysicsSettings>,
maybe_player: &Option<&Player>,
msg: ClientGeneral, msg: ClientGeneral,
) -> Result<(), crate::error::Error> { ) -> Result<(), crate::error::Error> {
let presence = match maybe_presence { let presence = match maybe_presence {
@ -40,6 +45,12 @@ impl Sys {
return Ok(()); return Ok(());
}, },
}; };
let player_physics_setting = maybe_player.map(|p| {
player_physics_settings
.settings
.entry(p.uuid())
.or_default()
});
match msg { match msg {
// Go back to registered state (char selection screen) // Go back to registered state (char selection screen)
ClientGeneral::ExitInGame => { ClientGeneral::ExitInGame => {
@ -93,14 +104,15 @@ impl Sys {
} }
}, },
ClientGeneral::PlayerPhysics { pos, vel, ori } => { ClientGeneral::PlayerPhysics { pos, vel, ori } => {
/*if matches!(presence.kind, PresenceKind::Character(_)) if matches!(presence.kind, PresenceKind::Character(_))
&& force_updates.get(entity).is_none() && force_updates.get(entity).is_none()
&& healths.get(entity).map_or(true, |h| !h.is_dead) && 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 _ = positions.insert(entity, pos);
let _ = velocities.insert(entity, vel); let _ = velocities.insert(entity, vel);
let _ = orientations.insert(entity, ori); let _ = orientations.insert(entity, ori);
}*/ }
}, },
ClientGeneral::BreakBlock(pos) => { ClientGeneral::BreakBlock(pos) => {
if let Some(comp_can_build) = can_build.get(entity) { if let Some(comp_can_build) = can_build.get(entity) {
@ -156,6 +168,13 @@ impl Sys {
ClientGeneral::RequestSiteInfo(id) => { ClientGeneral::RequestSiteInfo(id) => {
server_emitter.emit(ServerEvent::RequestSiteInfo { entity, 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"), _ => tracing::error!("not a client_in_game msg"),
} }
Ok(()) Ok(())
@ -184,6 +203,8 @@ impl<'a> System<'a> for Sys {
WriteStorage<'a, Controller>, WriteStorage<'a, Controller>,
Read<'a, Settings>, Read<'a, Settings>,
Read<'a, BuildAreas>, Read<'a, BuildAreas>,
Write<'a, PlayerPhysicsSettings>,
ReadStorage<'a, Player>,
); );
const NAME: &'static str = "msg::in_game"; const NAME: &'static str = "msg::in_game";
@ -209,12 +230,19 @@ impl<'a> System<'a> for Sys {
mut controllers, mut controllers,
settings, settings,
build_areas, build_areas,
mut player_physics_settings,
players,
): Self::SystemData, ): Self::SystemData,
) { ) {
let mut server_emitter = server_event_bus.emitter(); let mut server_emitter = server_event_bus.emitter();
for (entity, client, mut maybe_presence) in for (entity, client, mut maybe_presence, player) in (
(&entities, &mut clients, (&mut presences).maybe()).join() &entities,
&mut clients,
(&mut presences).maybe(),
players.maybe(),
)
.join()
{ {
let _ = super::try_recv_all(client, 2, |client, msg| { let _ = super::try_recv_all(client, 2, |client, msg| {
Self::handle_client_in_game_msg( Self::handle_client_in_game_msg(
@ -234,6 +262,8 @@ impl<'a> System<'a> for Sys {
&mut controllers, &mut controllers,
&settings, &settings,
&build_areas, &build_areas,
&mut player_physics_settings,
&player,
msg, msg,
) )
}); });

View File

@ -44,6 +44,8 @@ widget_ids! {
auto_walk_behavior_list, auto_walk_behavior_list,
camera_clamp_behavior_text, camera_clamp_behavior_text,
camera_clamp_behavior_list, camera_clamp_behavior_list,
player_physics_behavior_text,
player_physics_behavior_list,
stop_auto_walk_on_input_button, stop_auto_walk_on_input_button,
stop_auto_walk_on_input_label, stop_auto_walk_on_input_label,
auto_camera_button, 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 // Stop autowalk on input toggle
let stop_auto_walk_on_input_toggle = ToggleButton::new( let stop_auto_walk_on_input_toggle = ToggleButton::new(
self.global_state.settings.gameplay.stop_auto_walk_on_input, self.global_state.settings.gameplay.stop_auto_walk_on_input,

View File

@ -88,6 +88,9 @@ impl SessionState {
scene scene
.camera_mut() .camera_mut()
.set_fov_deg(global_state.settings.graphics.fov); .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 hud = Hud::new(global_state, &client.borrow());
let walk_forward_dir = scene.camera().forward_xy(); let walk_forward_dir = scene.camera().forward_xy();
let walk_right_dir = scene.camera().right_xy(); let walk_right_dir = scene.camera().right_xy();

View File

@ -46,6 +46,7 @@ pub enum Gameplay {
ChangeFreeLookBehavior(PressBehavior), ChangeFreeLookBehavior(PressBehavior),
ChangeAutoWalkBehavior(PressBehavior), ChangeAutoWalkBehavior(PressBehavior),
ChangeCameraClampBehavior(PressBehavior), ChangeCameraClampBehavior(PressBehavior),
ChangePlayerPhysicsBehavior { server_authoritative: bool },
ChangeStopAutoWalkOnInput(bool), ChangeStopAutoWalkOnInput(bool),
ChangeAutoCamera(bool), ChangeAutoCamera(bool),
@ -224,6 +225,15 @@ impl SettingsChange {
Gameplay::ChangeCameraClampBehavior(behavior) => { Gameplay::ChangeCameraClampBehavior(behavior) => {
settings.gameplay.camera_clamp_behavior = 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) => { Gameplay::ChangeStopAutoWalkOnInput(state) => {
settings.gameplay.stop_auto_walk_on_input = state; settings.gameplay.stop_auto_walk_on_input = state;
}, },

View File

@ -14,6 +14,7 @@ pub struct GameplaySettings {
pub free_look_behavior: PressBehavior, pub free_look_behavior: PressBehavior,
pub auto_walk_behavior: PressBehavior, pub auto_walk_behavior: PressBehavior,
pub camera_clamp_behavior: PressBehavior, pub camera_clamp_behavior: PressBehavior,
pub player_physics_behavior: bool,
pub stop_auto_walk_on_input: bool, pub stop_auto_walk_on_input: bool,
pub auto_camera: bool, pub auto_camera: bool,
} }
@ -30,6 +31,7 @@ impl Default for GameplaySettings {
free_look_behavior: PressBehavior::Toggle, free_look_behavior: PressBehavior::Toggle,
auto_walk_behavior: PressBehavior::Toggle, auto_walk_behavior: PressBehavior::Toggle,
camera_clamp_behavior: PressBehavior::Toggle, camera_clamp_behavior: PressBehavior::Toggle,
player_physics_behavior: false,
stop_auto_walk_on_input: true, stop_auto_walk_on_input: true,
auto_camera: false, auto_camera: false,
} }