Add an entity view distance setting that allows limiting the distance

entities are synced from and displayed in.

NOTE: Syncing entities work at the granularity regions which are
multi-chunk squares but the display of entities in voxygen is limited in
a circle with the radiues of the supplied distance.

Additional details and changes:
* Added `ViewDistances` struct in `common` that contains separate
  terrain and entity view distances (the entity view distance will be
  clamped by the terrain view distance in uses of this).
* View distance requests from the client to the server now use this
  type.
* When requesting the character or spectate state the client now passes
  its desired view distances. This is exposed as a new parameter on
  `Client::request_character`/`Client::request_spectate`. And the client
  no longer needs to send a view distance request after entering these
  states. This also allows us to avoid needing to initialize `Presence`
  with a default view distance value on the server.
* Removed `DerefFlaggedStorage` from `Presence` and `RegionSubscription` since the
  change tracking isn't used for these components.
* Add sliders in voxygen graphics and network tabs for this new setting.
  Show the clamped value as well as the selected value next to the
  slider.
* Rename existing "Entities View Distance" slider (which AFAIK controls
  the distance at which different LOD levels apply to figures) to
  "Entities Detail Distance" so we can use the former name for this new
  slider.
This commit is contained in:
Imbris 2022-08-21 23:21:39 -04:00
parent 895d6a2d8b
commit 334937568e
26 changed files with 435 additions and 219 deletions

View File

@ -54,9 +54,10 @@ hud-settings-auto_camera = Auto camera
hud-settings-bow_zoom = Zoom in when charging bow
hud-settings-reset_gameplay = Reset to Defaults
hud-settings-view_distance = View Distance
hud-settings-entity_view_distance = Entities View Distance
hud-settings-lod_distance = LoD Distance
hud-settings-sprites_view_distance = Sprites View Distance
hud-settings-figures_view_distance = Entities View Distance
hud-settings-entities_detail_distance = Entities Detail Distance
hud-settings-maximum_fps = Maximum FPS
hud-settings-background_fps = Background FPS
hud-settings-present_mode = Present Mode

View File

@ -249,6 +249,7 @@ pub struct Client {
state: State,
server_view_distance_limit: Option<u32>,
/// Terrrain view distance
view_distance: Option<u32>,
lod_distance: f32,
// TODO: move into voxygen
@ -805,8 +806,8 @@ impl Client {
| ClientGeneral::CreateCharacter { .. }
| ClientGeneral::EditCharacter { .. }
| ClientGeneral::DeleteCharacter(_)
| ClientGeneral::Character(_)
| ClientGeneral::Spectate => &mut self.character_screen_stream,
| ClientGeneral::Character(_, _)
| ClientGeneral::Spectate(_) => &mut self.character_screen_stream,
//Only in game
ClientGeneral::ControllerInputs(_)
| ClientGeneral::ControlEvent(_)
@ -881,16 +882,16 @@ impl Client {
}
/// Request a state transition to `ClientState::Character`.
pub fn request_character(&mut self, character_id: CharacterId) {
self.send_msg(ClientGeneral::Character(character_id));
pub fn request_character(&mut self, character_id: CharacterId, view_distances: common::ViewDistances) {
self.send_msg(ClientGeneral::Character(character_id, view_distances));
// Assume we are in_game unless server tells us otherwise
self.presence = Some(PresenceKind::Character(character_id));
}
/// Request a state transition to `ClientState::Spectate`.
pub fn request_spectate(&mut self) {
self.send_msg(ClientGeneral::Spectate);
pub fn request_spectate(&mut self, view_distances: common::ViewDistances) {
self.send_msg(ClientGeneral::Spectate(view_distances));
self.presence = Some(PresenceKind::Spectator);
}
@ -944,11 +945,14 @@ impl Client {
self.send_msg(ClientGeneral::ExitInGame);
}
pub fn set_view_distance(&mut self, view_distance: u32) {
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_view_distances(&mut self, view_distances: common::ViewDistances) {
if self.server_view_distance_limit.map_or(true, |limit| view_distances.terrain >= limit) {
let view_distances = common::ViewDistances {
terrain: view_distances.terrain.max(1).min(65),
entity: view_distances.entity,
};
self.view_distance = Some(view_distances.terrain);
self.send_msg(ClientGeneral::SetViewDistance(view_distances));
}
}
@ -2357,18 +2361,10 @@ impl Client {
ServerGeneral::CharacterEdited(character_id) => {
events.push(Event::CharacterEdited(character_id));
},
ServerGeneral::CharacterSuccess => {
debug!("client is now in ingame state on server");
if let Some(vd) = self.view_distance {
self.set_view_distance(vd);
}
},
ServerGeneral::CharacterSuccess => debug!("client is now in ingame state on server"),
ServerGeneral::SpectatorSuccess(spawn_point) => {
if let Some(vd) = self.view_distance {
events.push(Event::StartSpectate(spawn_point));
debug!("client is now in ingame state on server");
self.set_view_distance(vd);
}
events.push(Event::StartSpectate(spawn_point));
debug!("client is now in ingame state on server");
},
_ => unreachable!("Not a character_screen msg"),
}

View File

@ -4,6 +4,7 @@ use common::{
comp,
comp::{Skill, SkillGroupKind},
terrain::block::Block,
ViewDistances,
};
use serde::{Deserialize, Serialize};
use vek::*;
@ -60,13 +61,13 @@ pub enum ClientGeneral {
alias: String,
body: comp::Body,
},
Character(CharacterId),
Spectate,
Character(CharacterId, ViewDistances),
Spectate(ViewDistances),
//Only in game
ControllerInputs(Box<comp::ControllerInputs>),
ControlEvent(comp::ControlEvent),
ControlAction(comp::ControlAction),
SetViewDistance(u32),
SetViewDistance(ViewDistances),
BreakBlock(Vec3<i32>),
PlaceBlock(Vec3<i32>, Block),
ExitInGame,
@ -121,7 +122,7 @@ impl ClientMsg {
| ClientGeneral::DeleteCharacter(_) => {
c_type != ClientType::ChatOnly && presence.is_none()
},
ClientGeneral::Character(_) | ClientGeneral::Spectate => {
ClientGeneral::Character(_, _) | ClientGeneral::Spectate(_) => {
c_type == ClientType::Game && presence.is_none()
},
//Only in game

View File

@ -166,6 +166,10 @@ pub enum ServerGeneral {
/// from an ingame state
ExitInGameSuccess,
InventoryUpdate(comp::Inventory, comp::InventoryUpdateEvent),
/// NOTE: The client can infer that entity view distance will be at most the
/// terrain view distance that we send here (and if lower it won't be
/// modified). So we just need to send the terrain VD back to the client
/// if corrections are made.
SetViewDistance(u32),
Outcomes(Vec<Outcome>),
Knockback(Vec3<f32>),

View File

@ -107,8 +107,9 @@ pub enum ServerEvent {
InitCharacterData {
entity: EcsEntity,
character_id: CharacterId,
requested_view_distances: crate::ViewDistances,
},
InitSpectator(EcsEntity),
InitSpectator(EcsEntity, crate::ViewDistances),
UpdateCharacterData {
entity: EcsEntity,
components: (

View File

@ -81,6 +81,7 @@ pub mod terrain;
#[cfg(not(target_arch = "wasm32"))] pub mod typed;
pub mod uid;
#[cfg(not(target_arch = "wasm32"))] pub mod util;
mod view_distances;
#[cfg(not(target_arch = "wasm32"))] pub mod vol;
#[cfg(not(target_arch = "wasm32"))]
pub mod volumes;
@ -98,3 +99,4 @@ pub use comp::inventory::loadout_builder::LoadoutBuilder;
pub use explosion::{Explosion, RadiusEffect};
#[cfg(not(target_arch = "wasm32"))]
pub use skillset_builder::SkillSetBuilder;
pub use view_distances::ViewDistances;

View File

@ -0,0 +1,23 @@
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub struct ViewDistances {
pub terrain: u32,
/// Server will clamp this to `terrain` if it is larger.
///
/// NOTE: Importantly, the server still loads entities in the `terrain` view
/// distance (at least currently, please update this if you change it!),
/// but the syncing to the client is done based on the entity view
/// distance.
pub entity: u32,
}
impl ViewDistances {
/// Clamps the terrain view distance to an optional max and clamps the
/// entity view distance to the resulting terrain view distance.
pub fn clamp(self, max: Option<u32>) -> Self {
let terrain = max.unwrap_or(u32::MAX).min(self.terrain);
Self {
terrain,
entity: self.entity.min(terrain),
}
}
}

View File

@ -17,6 +17,7 @@ use common::{
rtsim::RtSimEntity,
uid::Uid,
util::Dir,
ViewDistances,
};
use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
use specs::{Builder, Entity as EcsEntity, WorldExt};
@ -29,12 +30,29 @@ pub fn handle_initialize_character(
server: &mut Server,
entity: EcsEntity,
character_id: CharacterId,
requested_view_distances: ViewDistances,
) {
server.state.initialize_character_data(entity, character_id);
let clamped_vds = requested_view_distances.clamp(server.settings().max_view_distance);
server
.state
.initialize_character_data(entity, character_id, clamped_vds);
// Correct client if its requested VD is too high.
if requested_view_distances.terrain != clamped_vds.terrain {
server.notify_client(entity, ServerGeneral::SetViewDistance(clamped_vds.terrain));
}
}
pub fn handle_initialize_spectator(server: &mut Server, entity: EcsEntity) {
server.state.initialize_spectator_data(entity);
pub fn handle_initialize_spectator(
server: &mut Server,
entity: EcsEntity,
requested_view_distances: ViewDistances,
) {
let clamped_vds = requested_view_distances.clamp(server.settings().max_view_distance);
server.state.initialize_spectator_data(entity, clamped_vds);
// Correct client if its requested VD is too high.
if requested_view_distances.terrain != clamped_vds.terrain {
server.notify_client(entity, ServerGeneral::SetViewDistance(clamped_vds.terrain));
}
sys::subscription::initialize_region_subscription(server.state.ecs(), entity);
}

View File

@ -140,8 +140,16 @@ impl Server {
ServerEvent::InitCharacterData {
entity,
character_id,
} => handle_initialize_character(self, entity, character_id),
ServerEvent::InitSpectator(entity) => handle_initialize_spectator(self, entity),
requested_view_distances,
} => handle_initialize_character(
self,
entity,
character_id,
requested_view_distances,
),
ServerEvent::InitSpectator(entity, requested_view_distances) => {
handle_initialize_spectator(self, entity, requested_view_distances)
},
ServerEvent::UpdateCharacterData { entity, components } => {
let (
body,

View File

@ -1,22 +1,24 @@
use common_net::msg::PresenceKind;
use hashbrown::HashSet;
use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage, NullStorage};
use specs::{Component, NullStorage};
use std::time::{Duration, Instant};
use vek::*;
#[derive(Debug)]
pub struct Presence {
// TODO: separate view distance for syncing entities?
pub view_distance: ViewDistance,
pub terrain_view_distance: ViewDistance,
pub entity_view_distance: ViewDistance,
pub kind: PresenceKind,
pub lossy_terrain_compression: bool,
}
impl Presence {
pub fn new(view_distance: u32, kind: PresenceKind) -> Self {
pub fn new(view_distances: common::ViewDistances, kind: PresenceKind) -> Self {
let now = Instant::now();
Self {
view_distance: ViewDistance::new(view_distance, Instant::now()),
terrain_view_distance: ViewDistance::new(view_distances.terrain, now),
entity_view_distance: ViewDistance::new(view_distances.entity, now),
kind,
lossy_terrain_compression: false,
}
@ -24,7 +26,7 @@ impl Presence {
}
impl Component for Presence {
type Storage = DerefFlaggedStorage<Self, specs::DenseVecStorage<Self>>;
type Storage = specs::DenseVecStorage<Self>;
}
// Distance from fuzzy_chunk before snapping to current chunk
@ -35,12 +37,12 @@ pub const REGION_FUZZ: u32 = 16;
#[derive(Clone, Debug)]
pub struct RegionSubscription {
pub fuzzy_chunk: Vec2<i32>,
pub last_view_distance: u32,
pub last_entity_view_distance: u32,
pub regions: HashSet<Vec2<i32>>,
}
impl Component for RegionSubscription {
type Storage = DerefFlaggedStorage<Self, specs::DenseVecStorage<Self>>;
type Storage = specs::DenseVecStorage<Self>;
}
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]

View File

@ -26,6 +26,7 @@ use common::{
resources::{Time, TimeOfDay},
slowjob::SlowJobPool,
uid::{Uid, UidAllocator},
ViewDistances,
};
use common_net::{
msg::{CharacterInfo, PlayerListUpdate, PresenceKind, ServerGeneral},
@ -107,9 +108,14 @@ pub trait StateExt {
index: &world::IndexOwned,
) -> EcsEntityBuilder;
/// Insert common/default components for a new character joining the server
fn initialize_character_data(&mut self, entity: EcsEntity, character_id: CharacterId);
fn initialize_character_data(
&mut self,
entity: EcsEntity,
character_id: CharacterId,
view_distances: ViewDistances,
);
/// Insert common/default components for a new spectator joining the server
fn initialize_spectator_data(&mut self, entity: EcsEntity);
fn initialize_spectator_data(&mut self, entity: EcsEntity, view_distances: ViewDistances);
/// Update the components associated with the entity's current character.
/// Performed after loading component data from the database
fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents);
@ -488,10 +494,21 @@ impl StateExt for State {
self.ecs_mut()
.create_entity_synced()
.with(pos)
.with(Presence::new(view_distance, PresenceKind::Spectator))
.with(Presence::new(
ViewDistances {
terrain: view_distance,
entity: view_distance,
},
PresenceKind::Spectator,
))
}
fn initialize_character_data(&mut self, entity: EcsEntity, character_id: CharacterId) {
fn initialize_character_data(
&mut self,
entity: EcsEntity,
character_id: CharacterId,
view_distances: ViewDistances,
) {
let spawn_point = self.ecs().read_resource::<SpawnPoint>().0;
if let Some(player_uid) = self.read_component_copied::<Uid>(entity) {
@ -519,10 +536,9 @@ impl StateExt for State {
// Make sure physics components are updated
self.write_component_ignore_entity_dead(entity, comp::ForceUpdate::forced());
const INITIAL_VD: u32 = 5; // will be changed after login
self.write_component_ignore_entity_dead(
entity,
Presence::new(INITIAL_VD, PresenceKind::Character(character_id)),
Presence::new(view_distances, PresenceKind::Character(character_id)),
);
// Tell the client its request was successful.
@ -532,7 +548,7 @@ impl StateExt for State {
}
}
fn initialize_spectator_data(&mut self, entity: EcsEntity) {
fn initialize_spectator_data(&mut self, entity: EcsEntity, view_distances: ViewDistances) {
let spawn_point = self.ecs().read_resource::<SpawnPoint>().0;
if self.read_component_copied::<Uid>(entity).is_some() {
@ -545,10 +561,9 @@ impl StateExt for State {
// Make sure physics components are updated
self.write_component_ignore_entity_dead(entity, comp::ForceUpdate::forced());
const INITIAL_VD: u32 = 5; //will be changed after login
self.write_component_ignore_entity_dead(
entity,
Presence::new(INITIAL_VD, PresenceKind::Spectator),
Presence::new(view_distances, PresenceKind::Spectator),
);
// Tell the client its request was successful.

View File

@ -384,7 +384,7 @@ impl<'a> System<'a> for Sys {
let is_near = |o_pos: Vec3<f32>| {
pos.zip_with(presence, |pos, presence| {
pos.0.xy().distance_squared(o_pos.xy())
< (presence.view_distance.current() as f32
< (presence.entity_view_distance.current() as f32
* TerrainChunkSize::RECT_SIZE.x as f32)
.powi(2)
})

View File

@ -64,17 +64,17 @@ impl Sys {
};
match msg {
// Request spectator state
ClientGeneral::Spectate => {
ClientGeneral::Spectate(requested_view_distances) => {
if let Some(admin) = admins.get(entity) && admin.0 >= AdminRole::Moderator {
send_join_messages()?;
server_emitter.emit(ServerEvent::InitSpectator(entity));
server_emitter.emit(ServerEvent::InitSpectator(entity, requested_view_distances));
} else {
debug!("dropped Spectate msg from unprivileged client")
}
},
ClientGeneral::Character(character_id) => {
ClientGeneral::Character(character_id, requested_view_distances) => {
if let Some(player) = players.get(entity) {
if presences.contains(entity) {
debug!("player already ingame, aborting");
@ -117,6 +117,7 @@ impl Sys {
server_emitter.emit(ServerEvent::InitCharacterData {
entity,
character_id,
requested_view_distances,
});
}
} else {

View File

@ -53,7 +53,7 @@ impl Sys {
time_for_vd_changes: Instant,
msg: ClientGeneral,
) -> Result<(), crate::error::Error> {
let presence = match maybe_presence {
let presence = match maybe_presence.as_deref_mut() {
Some(g) => g,
None => {
debug!(?entity, "client is not in_game, ignoring msg");
@ -68,16 +68,15 @@ impl Sys {
client.send(ServerGeneral::ExitInGameSuccess)?;
*maybe_presence = None;
},
ClientGeneral::SetViewDistance(view_distance) => {
let clamped_view_distance = settings
.max_view_distance
.map(|max| view_distance.min(max))
.unwrap_or(view_distance);
presence.view_distance.set_target(clamped_view_distance, time_for_vd_changes);
ClientGeneral::SetViewDistance(view_distances) => {
let clamped_vds = view_distances.clamp(settings.max_view_distance);
presence.terrain_view_distance.set_target(clamped_vds.terrain, time_for_vd_changes);
presence.entity_view_distance.set_target(clamped_vds.entity, time_for_vd_changes);
// Correct client if its requested VD is too high.
if view_distance != clamped_view_distance {
client.send(ServerGeneral::SetViewDistance(clamped_view_distance))?;
if view_distances.terrain != clamped_vds.terrain {
client.send(ServerGeneral::SetViewDistance(clamped_vds.terrain))?;
}
},
ClientGeneral::ControllerInputs(inputs) => {
@ -296,8 +295,8 @@ impl Sys {
| ClientGeneral::CreateCharacter { .. }
| ClientGeneral::EditCharacter { .. }
| ClientGeneral::DeleteCharacter(_)
| ClientGeneral::Character(_)
| ClientGeneral::Spectate
| ClientGeneral::Character(_, _)
| ClientGeneral::Spectate(_)
| ClientGeneral::TerrainChunkRequest { .. }
| ClientGeneral::LodZoneRequest { .. }
| ClientGeneral::ChatMsg(_)
@ -419,8 +418,9 @@ impl<'a> System<'a> for Sys {
// Ensure deferred view distance changes are applied (if the
// requsite time has elapsed).
if let Some(mut presence) = maybe_presence {
presence.view_distance.update(time_for_vd_changes);
if let Some(presence) = maybe_presence {
presence.terrain_view_distance.update(time_for_vd_changes);
presence.entity_view_distance.update(time_for_vd_changes);
}
}
}

View File

@ -76,7 +76,7 @@ impl<'a> System<'a> for Sys {
pos.0.xy().map(|e| e as f64).distance_squared(
key.map(|e| e as f64 + 0.5)
* TerrainChunkSize::RECT_SIZE.map(|e| e as f64),
) < ((presence.view_distance.current() as f64 - 1.0
) < ((presence.terrain_view_distance.current() as f64 - 1.0
+ 2.5 * 2.0_f64.sqrt())
* TerrainChunkSize::RECT_SIZE.x as f64)
.powi(2)

View File

@ -79,7 +79,7 @@ impl<'a> System<'a> for Sys {
)
.join()
{
let vd = presence.view_distance.current();
let vd = presence.entity_view_distance.current();
// Calculate current chunk
let chunk = (Vec2::<f32>::from(pos.0))
.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 / sz as i32);
@ -99,10 +99,10 @@ impl<'a> System<'a> for Sys {
e.abs() > (sz / 2 + presence::CHUNK_FUZZ) as f32
})
.reduce_or()
|| subscription.last_view_distance != vd
|| subscription.last_entity_view_distance != vd
{
// Update the view distance
subscription.last_view_distance = vd;
subscription.last_entity_view_distance = vd;
// Update current chunk
subscription.fuzzy_chunk = Vec2::<f32>::from(pos.0)
.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 / sz as i32);
@ -222,7 +222,7 @@ pub fn initialize_region_subscription(world: &World, entity: specs::Entity) {
let chunk_size = TerrainChunkSize::RECT_SIZE.reduce_max() as f32;
let regions = regions_in_vd(
client_pos.0,
(presence.view_distance.current() as f32 * chunk_size) as f32
(presence.entity_view_distance.current() as f32 * chunk_size) as f32
+ (presence::CHUNK_FUZZ as f32 + chunk_size) * 2.0f32.sqrt(),
);
@ -267,7 +267,7 @@ pub fn initialize_region_subscription(world: &World, entity: specs::Entity) {
if let Err(e) = world.write_storage().insert(entity, RegionSubscription {
fuzzy_chunk,
last_view_distance: presence.view_distance.current(),
last_entity_view_distance: presence.entity_view_distance.current(),
regions,
}) {
error!(?e, "Failed to insert region subscription component");

View File

@ -269,7 +269,7 @@ impl<'a> System<'a> for Sys {
.map(|e: i32| (e.unsigned_abs()).saturating_sub(2))
.magnitude_squared();
if adjusted_dist_sqr <= presence.view_distance.current().pow(2) {
if adjusted_dist_sqr <= presence.terrain_view_distance.current().pow(2) {
chunk_send_emitter.emit(ChunkSendEntry {
entity,
chunk_key: key,
@ -293,7 +293,7 @@ impl<'a> System<'a> for Sys {
// For each player with a position, calculate the distance.
for (presence, pos) in (&presences, &positions).join() {
if chunk_in_vd(pos.0, chunk_key, &terrain, presence.view_distance.current()) {
if chunk_in_vd(pos.0, chunk_key, &terrain, presence.terrain_view_distance.current()) {
should_drop = false;
break;
}

View File

@ -37,7 +37,7 @@ impl<'a> System<'a> for Sys {
pos.0,
*chunk_key,
&terrain,
presence.view_distance.current(),
presence.terrain_view_distance.current(),
) {
chunk_send_emitter.emit(ChunkSendEntry {
entity,

View File

@ -15,9 +15,12 @@ widget_ids! {
struct Ids {
window,
window_r,
vd_text,
vd_slider,
vd_value,
terrain_vd_text,
terrain_vd_slider,
terrain_vd_value,
entity_vd_text,
entity_vd_slider,
entity_vd_value,
player_physics_behavior_text,
player_physics_behavior_list,
lossy_terrain_compression_button,
@ -97,35 +100,84 @@ impl<'a> Widget for Networking<'a> {
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.vd_text, ui);
.set(state.ids.terrain_vd_text, ui);
let terrain_view_distance = self.global_state.settings.graphics.terrain_view_distance;
let server_view_distance_limit = self.server_view_distance_limit.unwrap_or(u32::MAX);
if let Some(new_val) = ImageSlider::discrete(
self.global_state.settings.graphics.view_distance,
terrain_view_distance,
1,
65,
self.imgs.slider_indicator,
self.imgs.slider,
)
.w_h(104.0, 22.0)
.down_from(state.ids.vd_text, 8.0)
.down_from(state.ids.terrain_vd_text, 8.0)
.track_breadth(12.0)
.slider_length(10.0)
.soft_max(self.server_view_distance_limit.unwrap_or(u32::MAX))
.soft_max(server_view_distance_limit)
.pad_track((5.0, 5.0))
.set(state.ids.vd_slider, ui)
.set(state.ids.terrain_vd_slider, ui)
{
events.push(NetworkingChange::AdjustViewDistance(new_val));
events.push(NetworkingChange::AdjustTerrainViewDistance(new_val));
}
Text::new(&format!(
"{}",
self.global_state.settings.graphics.view_distance
))
.right_from(state.ids.vd_slider, 8.0)
Text::new(&if terrain_view_distance <= server_view_distance_limit {
format!("{terrain_view_distance}")
} else {
format!("{terrain_view_distance} ({server_view_distance_limit})")
})
.right_from(state.ids.terrain_vd_slider, 8.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.vd_value, ui);
.set(state.ids.terrain_vd_value, ui);
// Entity View Distance
Text::new(
&self
.localized_strings
.get_msg("hud-settings-entity_view_distance"),
)
.down_from(state.ids.terrain_vd_slider, 10.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.entity_vd_text, ui);
let soft_entity_vd_max = self
.server_view_distance_limit
.unwrap_or(u32::MAX)
.min(terrain_view_distance);
let entity_view_distance = self.global_state.settings.graphics.entity_view_distance;
if let Some(new_val) = ImageSlider::discrete(
entity_view_distance,
1,
65,
self.imgs.slider_indicator,
self.imgs.slider,
)
.w_h(104.0, 22.0)
.down_from(state.ids.entity_vd_text, 8.0)
.track_breadth(12.0)
.slider_length(10.0)
.soft_max(soft_entity_vd_max)
.pad_track((5.0, 5.0))
.set(state.ids.entity_vd_slider, ui)
{
events.push(NetworkingChange::AdjustEntityViewDistance(new_val));
}
Text::new(&if entity_view_distance <= soft_entity_vd_max {
format!("{entity_view_distance}")
} else {
format!("{entity_view_distance} ({soft_entity_vd_max})")
})
.right_from(state.ids.entity_vd_slider, 8.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.entity_vd_value, ui);
// Player physics behavior
Text::new(
@ -133,7 +185,7 @@ impl<'a> Widget for Networking<'a> {
.localized_strings
.get_msg("hud-settings-player_physics_behavior"),
)
.down_from(state.ids.vd_slider, 8.0)
.down_from(state.ids.entity_vd_slider, 8.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)

View File

@ -36,9 +36,12 @@ widget_ids! {
reset_graphics_button,
fps_counter,
pipeline_recreation_text,
vd_slider,
vd_text,
vd_value,
terrain_vd_slider,
terrain_vd_text,
terrain_vd_value,
entity_vd_slider,
entity_vd_text,
entity_vd_value,
ld_slider,
ld_text,
ld_value,
@ -285,39 +288,126 @@ impl<'a> Widget for Video<'a> {
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.vd_text, ui);
.set(state.ids.terrain_vd_text, ui);
let terrain_view_distance = self.global_state.settings.graphics.terrain_view_distance;
let server_view_distance_limit = self.server_view_distance_limit.unwrap_or(u32::MAX);
if let Some(new_val) = ImageSlider::discrete(
self.global_state.settings.graphics.view_distance,
terrain_view_distance,
1,
65,
self.imgs.slider_indicator,
self.imgs.slider,
)
.w_h(104.0, 22.0)
.down_from(state.ids.vd_text, 8.0)
.down_from(state.ids.terrain_vd_text, 8.0)
.track_breadth(12.0)
.slider_length(10.0)
.soft_max(self.server_view_distance_limit.unwrap_or(u32::MAX))
.soft_max(server_view_distance_limit)
.pad_track((5.0, 5.0))
.set(state.ids.vd_slider, ui)
.set(state.ids.terrain_vd_slider, ui)
{
events.push(GraphicsChange::AdjustViewDistance(new_val));
events.push(GraphicsChange::AdjustTerrainViewDistance(new_val));
}
Text::new(&format!(
"{}",
self.global_state.settings.graphics.view_distance
))
.right_from(state.ids.vd_slider, 8.0)
Text::new(&if terrain_view_distance <= server_view_distance_limit {
format!("{terrain_view_distance}")
} else {
format!("{terrain_view_distance} ({server_view_distance_limit})")
})
.right_from(state.ids.terrain_vd_slider, 8.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.vd_value, ui);
.set(state.ids.terrain_vd_value, ui);
// Entity View Distance
let soft_entity_vd_max = self
.server_view_distance_limit
.unwrap_or(u32::MAX)
.min(terrain_view_distance);
let entity_view_distance = self.global_state.settings.graphics.entity_view_distance;
if let Some(new_val) = ImageSlider::discrete(
entity_view_distance,
1,
65,
self.imgs.slider_indicator,
self.imgs.slider,
)
.w_h(104.0, 22.0)
.right_from(state.ids.terrain_vd_slider, 70.0)
.track_breadth(12.0)
.slider_length(10.0)
.soft_max(soft_entity_vd_max)
.pad_track((5.0, 5.0))
.set(state.ids.entity_vd_slider, ui)
{
events.push(GraphicsChange::AdjustEntityViewDistance(new_val));
}
Text::new(
&self
.localized_strings
.get_msg("hud-settings-entity_view_distance"),
)
.up_from(state.ids.entity_vd_slider, 10.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.entity_vd_text, ui);
Text::new(&if entity_view_distance <= soft_entity_vd_max {
format!("{entity_view_distance}")
} else {
format!("{entity_view_distance} ({soft_entity_vd_max})")
})
.right_from(state.ids.entity_vd_slider, 8.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.entity_vd_value, ui);
// Sprites VD
if let Some(new_val) = ImageSlider::discrete(
self.global_state.settings.graphics.sprite_render_distance,
50,
500,
self.imgs.slider_indicator,
self.imgs.slider,
)
.w_h(104.0, 22.0)
.right_from(state.ids.entity_vd_slider, 70.0)
.track_breadth(12.0)
.slider_length(10.0)
.pad_track((5.0, 5.0))
.set(state.ids.sprite_dist_slider, ui)
{
events.push(GraphicsChange::AdjustSpriteRenderDistance(new_val));
}
Text::new(
&self
.localized_strings
.get_msg("hud-settings-sprites_view_distance"),
)
.up_from(state.ids.sprite_dist_slider, 10.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.sprite_dist_text, ui);
Text::new(&format!(
"{}",
self.global_state.settings.graphics.sprite_render_distance
))
.right_from(state.ids.sprite_dist_slider, 8.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.sprite_dist_value, ui);
// LoD Distance
Text::new(&self.localized_strings.get_msg("hud-settings-lod_distance"))
.down_from(state.ids.vd_slider, 10.0)
.down_from(state.ids.terrain_vd_slider, 10.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
@ -350,6 +440,50 @@ impl<'a> Widget for Video<'a> {
.color(TEXT_COLOR)
.set(state.ids.ld_value, ui);
// Figure LOD distance
if let Some(new_val) = ImageSlider::discrete(
self.global_state
.settings
.graphics
.figure_lod_render_distance,
50,
500,
self.imgs.slider_indicator,
self.imgs.slider,
)
.w_h(104.0, 22.0)
.right_from(state.ids.ld_slider, 70.0)
.track_breadth(12.0)
.slider_length(10.0)
.pad_track((5.0, 5.0))
.set(state.ids.figure_dist_slider, ui)
{
events.push(GraphicsChange::AdjustFigureLoDRenderDistance(new_val));
}
Text::new(
&self
.localized_strings
.get_msg("hud-settings-entities_detail_distance"),
)
.up_from(state.ids.figure_dist_slider, 8.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.figure_dist_text, ui);
Text::new(&format!(
"{}",
self.global_state
.settings
.graphics
.figure_lod_render_distance
))
.right_from(state.ids.figure_dist_slider, 8.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.figure_dist_value, ui);
// Max FPS
Text::new(&self.localized_strings.get_msg("hud-settings-maximum_fps"))
.down_from(state.ids.ld_slider, 10.0)
@ -392,7 +526,7 @@ impl<'a> Widget for Video<'a> {
.get_msg("hud-settings-background_fps"),
)
.down_from(state.ids.ld_slider, 10.0)
.right_from(state.ids.max_fps_value, 30.0)
.right_from(state.ids.max_fps_value, 44.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
@ -655,87 +789,6 @@ impl<'a> Widget for Video<'a> {
.color(TEXT_COLOR)
.set(state.ids.ambiance_value, ui);
// Sprites VD
if let Some(new_val) = ImageSlider::discrete(
self.global_state.settings.graphics.sprite_render_distance,
50,
500,
self.imgs.slider_indicator,
self.imgs.slider,
)
.w_h(104.0, 22.0)
.right_from(state.ids.vd_slider, 50.0)
.track_breadth(12.0)
.slider_length(10.0)
.pad_track((5.0, 5.0))
.set(state.ids.sprite_dist_slider, ui)
{
events.push(GraphicsChange::AdjustSpriteRenderDistance(new_val));
}
Text::new(
&self
.localized_strings
.get_msg("hud-settings-sprites_view_distance"),
)
.up_from(state.ids.sprite_dist_slider, 8.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.sprite_dist_text, ui);
Text::new(&format!(
"{}",
self.global_state.settings.graphics.sprite_render_distance
))
.right_from(state.ids.sprite_dist_slider, 8.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.sprite_dist_value, ui);
// Figure VD
if let Some(new_val) = ImageSlider::discrete(
self.global_state
.settings
.graphics
.figure_lod_render_distance,
50,
500,
self.imgs.slider_indicator,
self.imgs.slider,
)
.w_h(104.0, 22.0)
.right_from(state.ids.sprite_dist_slider, 50.0)
.track_breadth(12.0)
.slider_length(10.0)
.pad_track((5.0, 5.0))
.set(state.ids.figure_dist_slider, ui)
{
events.push(GraphicsChange::AdjustFigureLoDRenderDistance(new_val));
}
Text::new(
&self
.localized_strings
.get_msg("hud-settings-figures_view_distance"),
)
.up_from(state.ids.figure_dist_slider, 8.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.figure_dist_text, ui);
Text::new(&format!(
"{}",
self.global_state
.settings
.graphics
.figure_lod_render_distance
))
.right_from(state.ids.figure_dist_slider, 8.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.figure_dist_value, ui);
// AaMode
Text::new(
&self

View File

@ -137,10 +137,11 @@ impl PlayState for CharSelectionState {
ui::Event::Play(character_id) => {
{
let mut c = self.client.borrow_mut();
c.request_character(character_id);
//Send our ViewDistance and LoD distance
c.set_view_distance(global_state.settings.graphics.view_distance);
c.set_lod_distance(global_state.settings.graphics.lod_distance);
let graphics = &global_state.settings.graphics;
c.request_character(character_id, common::ViewDistances {
terrain: graphics.terrain_view_distance,
entity: graphics.entity_view_distance,
});
}
return PlayStateResult::Switch(Box::new(SessionState::new(
global_state,
@ -150,9 +151,11 @@ impl PlayState for CharSelectionState {
ui::Event::Spectate => {
{
let mut c = self.client.borrow_mut();
c.request_spectate();
c.set_view_distance(global_state.settings.graphics.view_distance);
c.set_lod_distance(global_state.settings.graphics.lod_distance);
let graphics = &global_state.settings.graphics;
c.request_spectate(common::ViewDistances {
terrain: graphics.terrain_view_distance,
entity: graphics.entity_view_distance,
});
}
return PlayStateResult::Switch(Box::new(SessionState::new(
global_state,

View File

@ -629,7 +629,7 @@ impl FigureMgr {
let time = state.get_time() as f32;
let tick = scene_data.tick;
let ecs = state.ecs();
let view_distance = scene_data.view_distance;
let view_distance = scene_data.entity_view_distance;
let dt = state.get_delta_time();
let dt_lerp = (15.0 * dt).min(1.0);
let frustum = camera.frustum();

View File

@ -120,7 +120,8 @@ pub struct SceneData<'a> {
pub mutable_viewpoint: bool,
pub target_entity: Option<specs::Entity>,
pub loaded_distance: f32,
pub view_distance: u32,
pub terrain_view_distance: u32, // not used currently
pub entity_view_distance: u32,
pub tick: u64,
pub gamma: f32,
pub exposure: f32,

View File

@ -1697,7 +1697,11 @@ impl PlayState for SessionState {
// Only highlight if interactable
target_entity: self.interactable.and_then(Interactable::entity),
loaded_distance: client.loaded_distance(),
view_distance: client.view_distance().unwrap_or(1),
terrain_view_distance: client.view_distance().unwrap_or(1),
entity_view_distance: client
.view_distance()
.unwrap_or(1)
.min(global_state.settings.graphics.entity_view_distance),
tick: client.get_tick(),
gamma: global_state.settings.graphics.gamma,
exposure: global_state.settings.graphics.exposure,
@ -1783,7 +1787,11 @@ impl PlayState for SessionState {
// Only highlight if interactable
target_entity: self.interactable.and_then(Interactable::entity),
loaded_distance: client.loaded_distance(),
view_distance: client.view_distance().unwrap_or(1),
terrain_view_distance: client.view_distance().unwrap_or(1),
entity_view_distance: client
.view_distance()
.unwrap_or(1)
.min(settings.graphics.entity_view_distance),
tick: client.get_tick(),
gamma: settings.graphics.gamma,
exposure: settings.graphics.exposure,

View File

@ -70,7 +70,8 @@ pub enum Gameplay {
}
#[derive(Clone)]
pub enum Graphics {
AdjustViewDistance(u32),
AdjustTerrainViewDistance(u32),
AdjustEntityViewDistance(u32),
AdjustLodDistance(u32),
AdjustLodDetail(u32),
AdjustSpriteRenderDistance(u32),
@ -147,7 +148,8 @@ pub enum Language {
}
#[derive(Clone)]
pub enum Networking {
AdjustViewDistance(u32),
AdjustTerrainViewDistance(u32),
AdjustEntityViewDistance(u32),
ChangePlayerPhysicsBehavior {
server_authoritative: bool,
},
@ -155,6 +157,8 @@ pub enum Networking {
#[cfg(feature = "discord")]
ToggleDiscordIntegration(bool),
// TODO: reset option (ensure it handles the entity/terrain vd the same as graphics reset
// option)
}
#[derive(Clone)]
@ -354,8 +358,11 @@ impl SettingsChange {
},
SettingsChange::Graphics(graphics_change) => {
match graphics_change {
Graphics::AdjustViewDistance(view_distance) => {
adjust_view_distance(view_distance, global_state, session_state)
Graphics::AdjustTerrainViewDistance(terrain_vd) => {
adjust_terrain_view_distance(terrain_vd, settings, session_state)
},
Graphics::AdjustEntityViewDistance(entity_vd) => {
adjust_entity_view_distance(entity_vd, settings, session_state)
},
Graphics::AdjustLodDistance(lod_distance) => {
session_state
@ -428,10 +435,7 @@ impl SettingsChange {
settings.graphics = GraphicsSettings::default();
let graphics = &settings.graphics;
// View distance
session_state
.client
.borrow_mut()
.set_view_distance(graphics.view_distance);
client_set_view_distance(settings, session_state);
// FOV
session_state.scene.camera_mut().set_fov_deg(graphics.fov);
session_state
@ -599,8 +603,11 @@ impl SettingsChange {
},
},
SettingsChange::Networking(networking_change) => match networking_change {
Networking::AdjustViewDistance(view_distance) => {
adjust_view_distance(view_distance, global_state, session_state)
Networking::AdjustTerrainViewDistance(terrain_vd) => {
adjust_terrain_view_distance(terrain_vd, settings, session_state)
},
Networking::AdjustEntityViewDistance(entity_vd) => {
adjust_entity_view_distance(entity_vd, settings, session_state)
},
Networking::ChangePlayerPhysicsBehavior {
server_authoritative,
@ -651,15 +658,33 @@ impl SettingsChange {
}
}
fn adjust_view_distance(
view_distance: u32,
global_state: &mut GlobalState,
use crate::settings::Settings;
fn adjust_terrain_view_distance(
terrain_vd: u32,
settings: &mut Settings,
session_state: &mut SessionState,
) {
settings.graphics.terrain_view_distance = terrain_vd;
client_set_view_distance(settings, session_state);
}
fn adjust_entity_view_distance(
entity_vd: u32,
settings: &mut Settings,
session_state: &mut SessionState,
) {
settings.graphics.entity_view_distance = entity_vd;
client_set_view_distance(settings, session_state);
}
fn client_set_view_distance(settings: &Settings, session_state: &mut SessionState) {
let view_distances = common::ViewDistances {
terrain: settings.graphics.terrain_view_distance,
entity: settings.graphics.entity_view_distance,
};
session_state
.client
.borrow_mut()
.set_view_distance(view_distance);
global_state.settings.graphics.view_distance = view_distance;
.set_view_distances(view_distances);
}

View File

@ -29,7 +29,8 @@ impl fmt::Display for Fps {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct GraphicsSettings {
pub view_distance: u32,
pub terrain_view_distance: u32,
pub entity_view_distance: u32,
pub lod_distance: u32,
pub sprite_render_distance: u32,
pub particles_enabled: bool,
@ -50,7 +51,8 @@ pub struct GraphicsSettings {
impl Default for GraphicsSettings {
fn default() -> Self {
Self {
view_distance: 10,
terrain_view_distance: 10,
entity_view_distance: 65,
lod_distance: 200,
sprite_render_distance: 100,
particles_enabled: true,