Impove UX of server limits on the view distance

* When client gets its view distance set by the server it stores that as
  the max view distance allowed by the server.
* This is used to avoid skip sending requests if `set_view_distance` is
  called with larger values.
* This is then also displayed in the UI via greying out the slider when
  it goes past the limit and drawing another slide at the limit.
* The server clamping the client's view distance no longer leads to
  edits in voxygen's view distance settings (the view distance setting
  is instead preserved).
This commit is contained in:
Imbris 2022-08-21 02:16:12 -04:00
parent c36d6e873f
commit 895d6a2d8b
10 changed files with 129 additions and 80 deletions

View File

@ -248,6 +248,7 @@ pub struct Client {
tick: u64,
state: State,
server_view_distance_limit: Option<u32>,
view_distance: Option<u32>,
lod_distance: f32,
// TODO: move into voxygen
@ -707,6 +708,7 @@ impl Client {
tick: 0,
state,
server_view_distance_limit: None,
view_distance: None,
lod_distance: 4.0,
loaded_distance: 0.0,
@ -943,9 +945,11 @@ impl Client {
}
pub fn set_view_distance(&mut self, view_distance: u32) {
let view_distance = view_distance.max(1).min(65);
self.view_distance = Some(view_distance);
self.send_msg(ClientGeneral::SetViewDistance(view_distance));
if self.server_view_distance_limit.map_or(true, |limit| view_distance >= limit) {
let view_distance = view_distance.max(1).min(65);
self.view_distance = Some(view_distance);
self.send_msg(ClientGeneral::SetViewDistance(view_distance));
}
}
pub fn set_lod_distance(&mut self, lod_distance: u32) {
@ -1486,6 +1490,8 @@ impl Client {
pub fn view_distance(&self) -> Option<u32> { self.view_distance }
pub fn server_view_distance_limit(&self) -> Option<u32> { self.server_view_distance_limit }
pub fn loaded_distance(&self) -> f32 { self.loaded_distance }
pub fn position(&self) -> Option<Vec3<f32>> {
@ -2254,6 +2260,9 @@ impl Client {
ServerGeneral::SetViewDistance(vd) => {
self.view_distance = Some(vd);
frontend_events.push(Event::SetViewDistance(vd));
// If the server is correcting client vd selection we assume this is the max
// allowed view distance.
self.server_view_distance_limit = Some(vd);
},
ServerGeneral::Outcomes(outcomes) => {
frontend_events.extend(outcomes.into_iter().map(Event::Outcome))

View File

@ -3063,6 +3063,7 @@ impl Hud {
&self.imgs,
&self.fonts,
i18n,
client.server_view_distance_limit(),
fps as f32,
)
.set(self.ids.settings_window, ui_widgets)

View File

@ -93,6 +93,7 @@ pub struct SettingsWindow<'a> {
imgs: &'a Imgs,
fonts: &'a Fonts,
localized_strings: &'a Localization,
server_view_distance_limit: Option<u32>,
fps: f32,
#[conrod(common_builder)]
common: widget::CommonBuilder,
@ -105,6 +106,7 @@ impl<'a> SettingsWindow<'a> {
imgs: &'a Imgs,
fonts: &'a Fonts,
localized_strings: &'a Localization,
server_view_distance_limit: Option<u32>,
fps: f32,
) -> Self {
Self {
@ -113,6 +115,7 @@ impl<'a> SettingsWindow<'a> {
imgs,
fonts,
localized_strings,
server_view_distance_limit,
fps,
common: widget::CommonBuilder::default(),
}
@ -299,11 +302,17 @@ impl<'a> Widget for SettingsWindow<'a> {
}
},
SettingsTab::Video => {
for change in
video::Video::new(global_state, imgs, fonts, localized_strings, self.fps)
.top_left_with_margins_on(state.ids.settings_content_align, 0.0, 0.0)
.wh_of(state.ids.settings_content_align)
.set(state.ids.video, ui)
for change in video::Video::new(
global_state,
imgs,
fonts,
localized_strings,
self.server_view_distance_limit,
self.fps,
)
.top_left_with_margins_on(state.ids.settings_content_align, 0.0, 0.0)
.wh_of(state.ids.settings_content_align)
.set(state.ids.video, ui)
{
events.push(Event::SettingsChange(change.into()));
}
@ -327,11 +336,16 @@ impl<'a> Widget for SettingsWindow<'a> {
}
},
SettingsTab::Networking => {
for change in
networking::Networking::new(global_state, imgs, fonts, localized_strings)
.top_left_with_margins_on(state.ids.settings_content_align, 0.0, 0.0)
.wh_of(state.ids.settings_content_align)
.set(state.ids.networking, ui)
for change in networking::Networking::new(
global_state,
imgs,
fonts,
localized_strings,
self.server_view_distance_limit,
)
.top_left_with_margins_on(state.ids.settings_content_align, 0.0, 0.0)
.wh_of(state.ids.settings_content_align)
.set(state.ids.networking, ui)
{
events.push(Event::SettingsChange(change.into()));
}

View File

@ -34,6 +34,7 @@ pub struct Networking<'a> {
imgs: &'a Imgs,
fonts: &'a Fonts,
localized_strings: &'a Localization,
server_view_distance_limit: Option<u32>,
#[conrod(common_builder)]
common: widget::CommonBuilder,
}
@ -43,12 +44,14 @@ impl<'a> Networking<'a> {
imgs: &'a Imgs,
fonts: &'a Fonts,
localized_strings: &'a Localization,
server_view_distance_limit: Option<u32>,
) -> Self {
Self {
global_state,
imgs,
fonts,
localized_strings,
server_view_distance_limit,
common: widget::CommonBuilder::default(),
}
}
@ -107,6 +110,7 @@ impl<'a> Widget for Networking<'a> {
.down_from(state.ids.vd_text, 8.0)
.track_breadth(12.0)
.slider_length(10.0)
.soft_max(self.server_view_distance_limit.unwrap_or(u32::MAX))
.pad_track((5.0, 5.0))
.set(state.ids.vd_slider, ui)
{

View File

@ -131,6 +131,7 @@ pub struct Video<'a> {
imgs: &'a Imgs,
fonts: &'a Fonts,
localized_strings: &'a Localization,
server_view_distance_limit: Option<u32>,
fps: f32,
#[conrod(common_builder)]
common: widget::CommonBuilder,
@ -141,6 +142,7 @@ impl<'a> Video<'a> {
imgs: &'a Imgs,
fonts: &'a Fonts,
localized_strings: &'a Localization,
server_view_distance_limit: Option<u32>,
fps: f32,
) -> Self {
Self {
@ -148,6 +150,7 @@ impl<'a> Video<'a> {
imgs,
fonts,
localized_strings,
server_view_distance_limit,
fps,
common: widget::CommonBuilder::default(),
}
@ -295,6 +298,7 @@ impl<'a> Widget for Video<'a> {
.down_from(state.ids.vd_text, 8.0)
.track_breadth(12.0)
.slider_length(10.0)
.soft_max(self.server_view_distance_limit.unwrap_or(u32::MAX))
.pad_track((5.0, 5.0))
.set(state.ids.vd_slider, ui)
{

View File

@ -215,12 +215,7 @@ impl PlayState for CharSelectionState {
Ok(events) => {
for event in events {
match event {
client::Event::SetViewDistance(vd) => {
global_state.settings.graphics.view_distance = vd;
global_state
.settings
.save_to_file_warn(&global_state.config_dir);
},
client::Event::SetViewDistance(_vd) => {},
client::Event::Disconnect => {
global_state.info_message = Some(
localized_strings

View File

@ -231,12 +231,7 @@ impl PlayState for MainMenuState {
Ok(events) => {
for event in events {
match event {
client::Event::SetViewDistance(vd) => {
global_state.settings.graphics.view_distance = vd;
global_state
.settings
.save_to_file_warn(&global_state.config_dir);
},
client::Event::SetViewDistance(_vd) => {},
client::Event::Disconnect => {
global_state.info_message = Some(
localized_strings

View File

@ -351,12 +351,7 @@ impl SessionState {
client::Event::Notification(n) => {
self.hud.new_notification(n);
},
client::Event::SetViewDistance(vd) => {
global_state.settings.graphics.view_distance = vd;
global_state
.settings
.save_to_file_warn(&global_state.config_dir);
},
client::Event::SetViewDistance(_vd) => {},
client::Event::Outcome(outcome) => outcomes.push(outcome),
client::Event::CharacterCreated(_) => {},
client::Event::CharacterEdited(_) => {},

View File

@ -355,12 +355,7 @@ impl SettingsChange {
SettingsChange::Graphics(graphics_change) => {
match graphics_change {
Graphics::AdjustViewDistance(view_distance) => {
session_state
.client
.borrow_mut()
.set_view_distance(view_distance);
settings.graphics.view_distance = view_distance;
adjust_view_distance(view_distance, global_state, session_state)
},
Graphics::AdjustLodDistance(lod_distance) => {
session_state
@ -605,11 +600,7 @@ impl SettingsChange {
},
SettingsChange::Networking(networking_change) => match networking_change {
Networking::AdjustViewDistance(view_distance) => {
session_state
.client
.borrow_mut()
.set_view_distance(view_distance);
settings.graphics.view_distance = view_distance;
adjust_view_distance(view_distance, global_state, session_state)
},
Networking::ChangePlayerPhysicsBehavior {
server_authoritative,
@ -654,6 +645,21 @@ impl SettingsChange {
},
},
}
settings.save_to_file_warn(&global_state.config_dir);
global_state
.settings
.save_to_file_warn(&global_state.config_dir);
}
}
fn adjust_view_distance(
view_distance: u32,
global_state: &mut GlobalState,
session_state: &mut SessionState,
) {
session_state
.client
.borrow_mut()
.set_view_distance(view_distance);
global_state.settings.graphics.view_distance = view_distance;
}

View File

@ -29,6 +29,10 @@ pub struct ImageSlider<T, K> {
value: T,
min: T,
max: T,
// If `value > soft_max` we will display the slider at `soft_max` along with a faded ghost
// slider at `value`. The slider displayed at `soft_max` is purely a visual indicator and has
// no effect on the values produced by this slider.
soft_max: T,
/// The amount in which the slider's display should be skewed.
///
/// Higher skew amounts (above 1.0) will weigh lower values.
@ -65,6 +69,7 @@ widget_ids! {
struct Ids {
track,
slider,
soft_max_slider,
}
}
@ -76,6 +81,7 @@ pub struct State {
impl<T, K> ImageSlider<T, K> {
builder_methods! {
pub skew { skew = f32 }
pub soft_max { soft_max = T }
pub pad_track { track.padding = (f32, f32) }
pub hover_image { slider.hover_image_id = Some(image::Id) }
pub press_image { slider.press_image_id = Some(image::Id) }
@ -85,18 +91,16 @@ impl<T, K> ImageSlider<T, K> {
pub slider_color { slider.color = Some(Color) }
}
fn new(
value: T,
min: T,
max: T,
slider_image_id: image::Id,
track_image_id: image::Id,
) -> Self {
fn new(value: T, min: T, max: T, slider_image_id: image::Id, track_image_id: image::Id) -> Self
where
T: Copy,
{
Self {
common: widget::CommonBuilder::default(),
value,
min,
max,
soft_max: max,
skew: 1.0,
track: Track {
image_id: track_image_id,
@ -133,7 +137,7 @@ where
impl<T> ImageSlider<T, Discrete>
where
T: Integer,
T: Integer + Copy,
{
pub fn discrete(
value: T,
@ -266,45 +270,67 @@ where
.unwrap_or(slider.image_id);
// A rectangle for positioning and sizing the slider.
let value_perc = utils::map_range(new_value, min, max, 0.0, 1.0);
let unskewed_perc = value_perc.powf(1.0 / skew as f64);
let slider_rect = if is_horizontal {
let pos = utils::map_range(
unskewed_perc,
0.0,
1.0,
rect.x.start + start_pad,
rect.x.end - end_pad,
);
let w = slider.length.map_or(rect.w() / 10.0, |w| w as f64);
Rect {
x: Range::from_pos_and_len(pos, w),
..rect
}
} else {
let pos = utils::map_range(
unskewed_perc,
0.0,
1.0,
rect.y.start + start_pad,
rect.y.end - end_pad,
);
let h = slider.length.map_or(rect.h() / 10.0, |h| h as f64);
Rect {
y: Range::from_pos_and_len(pos, h),
..rect
let slider_rect = |slider_value| {
let value_perc = utils::map_range(slider_value, min, max, 0.0, 1.0);
let unskewed_perc = value_perc.powf(1.0 / skew as f64);
if is_horizontal {
let pos = utils::map_range(
unskewed_perc,
0.0,
1.0,
rect.x.start + start_pad,
rect.x.end - end_pad,
);
let w = slider.length.map_or(rect.w() / 10.0, |w| w as f64);
Rect {
x: Range::from_pos_and_len(pos, w),
..rect
}
} else {
let pos = utils::map_range(
unskewed_perc,
0.0,
1.0,
rect.y.start + start_pad,
rect.y.end - end_pad,
);
let h = slider.length.map_or(rect.h() / 10.0, |h| h as f64);
Rect {
y: Range::from_pos_and_len(pos, h),
..rect
}
}
};
let (x, y, w, h) = slider_rect.x_y_w_h();
// Whether soft max slider needs to be displayed and main slider faded to look
// like a ghost.
let over_soft_max = new_value > self.soft_max;
let (x, y, w, h) = slider_rect(new_value).x_y_w_h();
let fade = if over_soft_max { 0.5 } else { 1.0 };
Image::new(slider_image)
.x_y(x, y)
.w_h(w, h)
.parent(id)
.graphics_for(id)
.color(slider.color)
.color(Some(
slider
.color
.map_or(Color::Rgba(1.0, 1.0, 1.0, fade), |c: Color| c.alpha(fade)),
))
.set(state.ids.slider, ui);
if over_soft_max {
let (x, y, w, h) = slider_rect(self.soft_max).x_y_w_h();
Image::new(slider_image)
.x_y(x, y)
.w_h(w, h)
.parent(id)
.graphics_for(id)
.color(slider.color)
.set(state.ids.soft_max_slider, ui);
}
// If the value has just changed, return the new value.
if value != new_value {
Some(new_value)