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.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",

View File

@ -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<S>(&mut self, msg: S)
where
S: Into<ClientMsg>,

View File

@ -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

View File

@ -32,3 +32,36 @@ pub enum GameMode {
/// server
#[derive(Copy, Clone, Default, Debug)]
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},
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")]

View File

@ -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<Outcome>>,
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

View File

@ -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,
)
});

View File

@ -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,

View File

@ -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();

View File

@ -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;
},

View File

@ -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,
}