Merge branch 'imbris/subscription-vd-fix' into 'master'

Fix issue with the region subscription system not updating on view distance changes and other semi related changes

See merge request veloren/veloren!3557
This commit is contained in:
Imbris 2022-08-26 04:12:02 +00:00
commit bc51d4d2a7
33 changed files with 795 additions and 389 deletions

View File

@ -8,11 +8,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Setting for disabling flashing lights
- Spectate mode for moderators.
- Currently playing music track and artist now shows in the debug menu.
- Added a setting to influence the gap between music track plays.
- Added a Craft All button.
- Server: Vacuum database on startup
- SeaChapel, greek/latin inspired dungeon for ocean biome coasts
- SeaChapel, greek/latin inspired dungeon for ocean biome coasts
- Entity view distance setting added (shown in graphics and network tabs). This setting controls
the distance at which entities are synced to the client and which entities are displayed in.
This is clamped to be no more than the current overall view distance setting.
- View distance settings that are lowered by the server limit (or other factors) now display an
extra ghost slider cursor when set above the limit (instead of snapping back to the limit).
Limits on the view distance by the server no longer affect the settings saved on the client.
### Changed
- Use fluent for translations
@ -21,15 +29,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- /kill_npcs no longer leaves drops behind and also has bug causing it to not destroy entities
fixed.
- Default present mode changed to Fifo (aka 'Vsync capped').
- Old "Entity View Distance" setting renamed to "Entity Detail Distance" (since this controls the
distance at which lower detail models are used for entities).
- Present mode options renamed for clarity: Fifo -> 'Vsync capped', Mailbox -> 'Vsync uncapped',
Immediate -> 'Vsync off'.
### Removed
### Fixed
- Fixed npc not handling interactions while fighting (especially merchants in trade)
- Fixed bug where you would still be burning after dying in lava.
- Workaround for rayon bug that caused lag spikes in slowjobs
- Fixed crash due to zooming out very far
- Client properly knows trade was cancelled when exiting to the character screen (and no longer
tries to display the trade window when rejoining)
- Cancel trades for an entity when it is deleted (note this doesn't effect trades between players
since their entities are not removed).
- Fixed bug where the view distance selection was not immediately applied to entity syncing when
first joining a server and when changing the view distance (previously this required moving to a
new chunk for the initial setting or subsequent change to apply).
## [0.13.0] - 2022-07-23
@ -68,8 +86,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- More varied ambient birdcalls
- Cave biomes
- Updated the Polish translation
- Setting for disabling flashing lights
- Spectate mode for moderators.
### Changed
@ -113,9 +129,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Combat music now loops and ends properly
- Modular weapons now have a selling price
- Closing a subwindow now only regrabs the cursor if no other subwindow requires it.
- Fixed npc not handling interactions while fighting (especially merchants in trade)
- Fixed bug where you would still be burning after dying in lava.
- Workaround for rayon bug that caused lag spikes in slowjobs
## [0.12.0] - 2022-02-19

View File

@ -54,15 +54,16 @@ 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
hud-settings-present_mode-fifo = Fifo
hud-settings-present_mode-mailbox = Mailbox
hud-settings-present_mode-immediate = Immediate
hud-settings-present_mode-vsync_capped = Vsync capped
hud-settings-present_mode-vsync_uncapped = Vsync uncapped
hud-settings-present_mode-vsync_off = Vsync off
hud-settings-fov = Field of View (deg)
hud-settings-gamma = Gamma
hud-settings-exposure = Exposure

View File

@ -221,7 +221,10 @@ impl BotClient {
let c = list.characters.get(0).unwrap();
if let Some(id) = c.character.id {
client.request_character(id);
client.request_character(id, common::ViewDistances {
terrain: 5,
entity: 5,
});
}
}
info!("ingame done");

View File

@ -111,7 +111,6 @@ fn run_client(
let mut client = runtime
.block_on(Client::new(addr, runtime_clone, &mut None))
.expect("Failed to connect to the server");
client.set_view_distance(opt.vd);
// Login
// NOTE: use a no-auth server
@ -159,6 +158,10 @@ fn run_client(
.character
.id
.expect("Why is this an option?"),
common::ViewDistances {
terrain: opt.vd,
entity: opt.vd,
},
);
// If this is the admin client then adminify the other swarm members

View File

@ -81,6 +81,8 @@ use tokio::runtime::Runtime;
use tracing::{debug, error, trace, warn};
use vek::*;
pub const MAX_SELECTABLE_VIEW_DISTANCE: u32 = 65;
const PING_ROLLING_AVERAGE_SECS: usize = 10;
#[derive(Debug)]
@ -248,6 +250,8 @@ pub struct Client {
tick: u64,
state: State,
server_view_distance_limit: Option<u32>,
/// Terrrain view distance
view_distance: Option<u32>,
lod_distance: f32,
// TODO: move into voxygen
@ -707,6 +711,7 @@ impl Client {
tick: 0,
state,
server_view_distance_limit: None,
view_distance: None,
lod_distance: 4.0,
loaded_distance: 0.0,
@ -803,8 +808,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(_)
@ -879,16 +884,22 @@ 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,
) {
let view_distances = self.set_view_distances_local(view_distances);
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) {
let view_distances = self.set_view_distances_local(view_distances);
self.send_msg(ClientGeneral::Spectate(view_distances));
self.presence = Some(PresenceKind::Spectator);
}
@ -942,10 +953,27 @@ impl Client {
self.send_msg(ClientGeneral::ExitInGame);
}
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));
pub fn set_view_distances(&mut self, view_distances: common::ViewDistances) {
let view_distances = self.set_view_distances_local(view_distances);
self.send_msg(ClientGeneral::SetViewDistance(view_distances));
}
/// Clamps provided view distances, locally sets the terrain view distance
/// in the client's properties and returns the clamped values for the
/// caller to send to the server.
fn set_view_distances_local(
&mut self,
view_distances: common::ViewDistances,
) -> common::ViewDistances {
let view_distances = common::ViewDistances {
terrain: view_distances
.terrain
.max(1)
.min(MAX_SELECTABLE_VIEW_DISTANCE),
entity: view_distances.entity.max(1),
};
self.view_distance = Some(view_distances.terrain);
view_distances
}
pub fn set_lod_distance(&mut self, lod_distance: u32) {
@ -1486,6 +1514,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 +2284,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))
@ -2348,18 +2381,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

@ -18,83 +18,82 @@
)]
#![feature(hash_drain_filter)]
/// Re-exported crates
#[cfg(not(target_arch = "wasm32"))]
pub use uuid;
macro_rules! cfg_not_wasm {
($($item:item)*) => {
$(
#[cfg(not(target_arch = "wasm32"))]
$item
)*
}
}
// Re-exported crates
cfg_not_wasm! {
pub use common_assets as assets;
pub use uuid;
}
// Modules
// modules
#[cfg(not(target_arch = "wasm32"))]
pub use common_assets as assets;
#[cfg(not(target_arch = "wasm32"))] pub mod astar;
#[cfg(not(target_arch = "wasm32"))]
mod cached_spatial_grid;
#[cfg(not(target_arch = "wasm32"))]
pub mod calendar;
#[cfg(not(target_arch = "wasm32"))]
pub mod character;
#[cfg(not(target_arch = "wasm32"))] pub mod clock;
#[cfg(not(target_arch = "wasm32"))] pub mod cmd;
pub mod combat;
pub mod comp;
pub mod consts;
#[cfg(not(target_arch = "wasm32"))] pub mod depot;
#[cfg(not(target_arch = "wasm32"))]
pub mod effect;
#[cfg(not(target_arch = "wasm32"))] pub mod event;
#[cfg(not(target_arch = "wasm32"))]
pub mod explosion;
#[cfg(not(target_arch = "wasm32"))]
pub mod figure;
#[cfg(not(target_arch = "wasm32"))]
pub mod generation;
#[cfg(not(target_arch = "wasm32"))] pub mod grid;
#[cfg(not(target_arch = "wasm32"))] pub mod link;
#[cfg(not(target_arch = "wasm32"))] pub mod lod;
#[cfg(not(target_arch = "wasm32"))]
pub mod lottery;
#[cfg(not(target_arch = "wasm32"))]
pub mod mounting;
#[cfg(not(target_arch = "wasm32"))] pub mod npc;
#[cfg(not(target_arch = "wasm32"))]
pub mod outcome;
#[cfg(not(target_arch = "wasm32"))] pub mod path;
#[cfg(not(target_arch = "wasm32"))] pub mod ray;
#[cfg(not(target_arch = "wasm32"))]
pub mod recipe;
#[cfg(not(target_arch = "wasm32"))]
pub mod region;
pub mod resources;
#[cfg(not(target_arch = "wasm32"))] pub mod rtsim;
#[cfg(not(target_arch = "wasm32"))]
pub mod skillset_builder;
#[cfg(not(target_arch = "wasm32"))]
pub mod slowjob;
#[cfg(not(target_arch = "wasm32"))]
pub mod spiral;
#[cfg(not(target_arch = "wasm32"))]
pub mod states;
#[cfg(not(target_arch = "wasm32"))] pub mod store;
#[cfg(not(target_arch = "wasm32"))]
pub mod terrain;
#[cfg(not(target_arch = "wasm32"))] pub mod time;
#[cfg(not(target_arch = "wasm32"))] pub mod trade;
#[cfg(not(target_arch = "wasm32"))] pub mod typed;
pub mod uid;
#[cfg(not(target_arch = "wasm32"))] pub mod util;
#[cfg(not(target_arch = "wasm32"))] pub mod vol;
#[cfg(not(target_arch = "wasm32"))]
pub mod volumes;
#[cfg(not(target_arch = "wasm32"))]
pub mod weather;
#[cfg(not(target_arch = "wasm32"))]
pub use cached_spatial_grid::CachedSpatialGrid;
#[cfg(not(target_arch = "wasm32"))]
pub use combat::{Damage, GroupTarget, Knockback, KnockbackDir};
// NOTE: Comment out macro to get rustfmt to re-order these as needed.
cfg_not_wasm! {
pub mod astar;
pub mod calendar;
pub mod character;
pub mod clock;
pub mod cmd;
pub mod depot;
pub mod effect;
pub mod event;
pub mod explosion;
pub mod figure;
pub mod generation;
pub mod grid;
pub mod link;
pub mod lod;
pub mod lottery;
pub mod mounting;
pub mod npc;
pub mod outcome;
pub mod path;
pub mod ray;
pub mod recipe;
pub mod region;
pub mod rtsim;
pub mod skillset_builder;
pub mod slowjob;
pub mod spiral;
pub mod states;
pub mod store;
pub mod terrain;
pub mod time;
pub mod trade;
pub mod util;
pub mod vol;
pub mod volumes;
pub mod weather;
mod cached_spatial_grid;
mod view_distances;
}
// We declare a macro in this module so there are issues referring to it by path
// within this crate if typed module is declared in macro expansion.
#[cfg(not(target_arch = "wasm32"))] pub mod typed;
pub use combat::{DamageKind, DamageSource};
#[cfg(not(target_arch = "wasm32"))]
pub use comp::inventory::loadout_builder::LoadoutBuilder;
#[cfg(not(target_arch = "wasm32"))]
pub use explosion::{Explosion, RadiusEffect};
#[cfg(not(target_arch = "wasm32"))]
pub use skillset_builder::SkillSetBuilder;
cfg_not_wasm! {
pub use cached_spatial_grid::CachedSpatialGrid;
pub use combat::{Damage, GroupTarget, Knockback, KnockbackDir};
pub use comp::inventory::loadout_builder::LoadoutBuilder;
pub use explosion::{Explosion, RadiusEffect};
pub use skillset_builder::SkillSetBuilder;
pub use view_distances::ViewDistances;
}

View File

@ -0,0 +1,25 @@
#[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.
///
/// Also ensures both are at a minimum of 1 (unless the provided max is 0).
pub fn clamp(self, max: Option<u32>) -> Self {
let terrain = self.terrain.max(1).min(max.unwrap_or(u32::MAX));
Self {
terrain,
entity: self.entity.max(1).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,20 +1,24 @@
use common_net::msg::PresenceKind;
use hashbrown::HashSet;
use serde::{Deserialize, Serialize};
use specs::{Component, DenseVecStorage, DerefFlaggedStorage, NullStorage, VecStorage};
use specs::{Component, NullStorage};
use std::time::{Duration, Instant};
use vek::*;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Debug)]
pub struct Presence {
pub view_distance: u32,
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,
terrain_view_distance: ViewDistance::new(view_distances.terrain, now),
entity_view_distance: ViewDistance::new(view_distances.entity, now),
kind,
lossy_terrain_compression: false,
}
@ -22,8 +26,7 @@ impl Presence {
}
impl Component for Presence {
// Presence seems <= 64 bits, so it isn't worth using DenseVecStorage.
type Storage = DerefFlaggedStorage<Self, VecStorage<Self>>;
type Storage = specs::DenseVecStorage<Self>;
}
// Distance from fuzzy_chunk before snapping to current chunk
@ -34,11 +37,12 @@ pub const REGION_FUZZ: u32 = 16;
#[derive(Clone, Debug)]
pub struct RegionSubscription {
pub fuzzy_chunk: Vec2<i32>,
pub last_entity_view_distance: u32,
pub regions: HashSet<Vec2<i32>>,
}
impl Component for RegionSubscription {
type Storage = DerefFlaggedStorage<Self, DenseVecStorage<Self>>;
type Storage = specs::DenseVecStorage<Self>;
}
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)]
@ -47,3 +51,88 @@ pub struct RepositionOnChunkLoad;
impl Component for RepositionOnChunkLoad {
type Storage = NullStorage<Self>;
}
#[derive(PartialEq, Debug, Clone, Copy)]
enum Direction {
Up,
Down,
}
/// Distance from the [Presence] from which the world is loaded and information
/// is synced to clients.
///
/// We limit the frequency that changes in the view distance change direction
/// (e.g. shifting from increasing the value to decreasing it). This is useful
/// since we want to avoid rapid cycles of shrinking and expanding of the view
/// distance.
#[derive(Debug)]
pub struct ViewDistance {
direction: Direction,
last_direction_change_time: Instant,
target: Option<u32>,
current: u32,
}
impl ViewDistance {
/// Minimum time allowed between changes in direction of value adjustments.
const TIME_PER_DIR_CHANGE: Duration = Duration::from_millis(300);
pub fn new(start_value: u32, now: Instant) -> Self {
Self {
direction: Direction::Up,
last_direction_change_time: now - Self::TIME_PER_DIR_CHANGE,
target: None,
current: start_value,
}
}
/// Returns the current value.
pub fn current(&self) -> u32 { self.current }
/// Applies deferred change based on the whether the time to apply it has
/// been reached.
pub fn update(&mut self, now: Instant) {
if let Some(target_val) = self.target {
if now.saturating_duration_since(self.last_direction_change_time)
> Self::TIME_PER_DIR_CHANGE
{
self.last_direction_change_time = now;
self.current = target_val;
self.target = None;
}
}
}
/// Sets the target value.
///
/// If this hasn't been changed recently or it is in the same direction as
/// the previous change it will be applied immediately. Otherwise, it
/// will be deferred to a later time (limiting the frequency of changes
/// in the change direction).
pub fn set_target(&mut self, new_target: u32, now: Instant) {
use core::cmp::Ordering;
let new_direction = match new_target.cmp(&self.current) {
Ordering::Equal => return, // No change needed.
Ordering::Less => Direction::Down,
Ordering::Greater => Direction::Up,
};
// Change is in the same direction as before so we can just apply it.
if new_direction == self.direction {
self.current = new_target;
self.target = None;
// If it has already been a while since the last direction change we can
// directly apply the request and switch the direction.
} else if now.saturating_duration_since(self.last_direction_change_time)
> Self::TIME_PER_DIR_CHANGE
{
self.direction = new_direction;
self.last_direction_change_time = now;
self.current = new_target;
self.target = None;
// Otherwise, we need to defer the request.
} else {
self.target = Some(new_target);
}
}
}

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,8 @@ 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 as f32 * TerrainChunkSize::RECT_SIZE.x 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

@ -17,6 +17,7 @@ use common_ecs::{Job, Origin, Phase, System};
use common_net::msg::{ClientGeneral, PresenceKind, ServerGeneral};
use common_state::{BlockChange, BuildAreas};
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, Write, WriteStorage};
use std::time::Instant;
use tracing::{debug, trace, warn};
use vek::*;
@ -49,9 +50,10 @@ impl Sys {
_terrain_persistence: &mut TerrainPersistenceData<'_>,
maybe_player: &Option<&Player>,
maybe_admin: &Option<&Admin>,
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");
@ -66,21 +68,15 @@ impl Sys {
client.send(ServerGeneral::ExitInGameSuccess)?;
*maybe_presence = None;
},
ClientGeneral::SetViewDistance(view_distance) => {
presence.view_distance = settings
.max_view_distance
.map(|max| view_distance.min(max))
.unwrap_or(view_distance);
ClientGeneral::SetViewDistance(view_distances) => {
let clamped_vds = view_distances.clamp(settings.max_view_distance);
//correct client if its VD is to high
if settings
.max_view_distance
.map(|max| view_distance > max)
.unwrap_or(false)
{
client.send(ServerGeneral::SetViewDistance(
settings.max_view_distance.unwrap_or(0),
))?;
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_distances.terrain != clamped_vds.terrain {
client.send(ServerGeneral::SetViewDistance(clamped_vds.terrain))?;
}
},
ClientGeneral::ControllerInputs(inputs) => {
@ -299,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(_)
@ -378,6 +374,8 @@ impl<'a> System<'a> for Sys {
) {
let mut server_emitter = server_event_bus.emitter();
let time_for_vd_changes = Instant::now();
for (entity, client, mut maybe_presence, player, maybe_admin) in (
&entities,
&mut clients,
@ -387,12 +385,15 @@ impl<'a> System<'a> for Sys {
)
.join()
{
// If an `ExitInGame` message is received this is set to `None` allowing further
// ingame messages to be ignored.
let mut clearable_maybe_presence = maybe_presence.as_deref_mut();
let _ = super::try_recv_all(client, 2, |client, msg| {
Self::handle_client_in_game_msg(
&mut server_emitter,
entity,
client,
&mut maybe_presence.as_deref_mut(),
&mut clearable_maybe_presence,
&terrain,
&can_build,
&is_rider,
@ -410,9 +411,17 @@ impl<'a> System<'a> for Sys {
&mut terrain_persistence,
&player,
&maybe_admin,
time_for_vd_changes,
msg,
)
});
// Ensure deferred view distance changes are applied (if the
// requsite time has elapsed).
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 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

@ -60,7 +60,8 @@ impl<'a> System<'a> for Sys {
// To update subscriptions
// 1. Iterate through clients
// 2. Calculate current chunk position
// 3. If chunk is the same return, otherwise continue (use fuzziness)
// 3. If chunk is different (use fuzziness) or the client view distance
// has changed continue, otherwise return
// 4. Iterate through subscribed regions
// 5. Check if region is still in range (use fuzziness)
// 6. If not in range
@ -78,13 +79,15 @@ impl<'a> System<'a> for Sys {
)
.join()
{
let vd = presence.view_distance;
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);
// Only update regions when moving to a new chunk
// uses a fuzzy border to prevent rapid triggering when moving along chunk
// boundaries
// Only update regions when moving to a new chunk or if view distance has
// changed.
//
// Uses a fuzzy border to prevent rapid triggering when moving along chunk
// boundaries.
if chunk != subscription.fuzzy_chunk
&& (subscription
.fuzzy_chunk
@ -96,7 +99,10 @@ impl<'a> System<'a> for Sys {
e.abs() > (sz / 2 + presence::CHUNK_FUZZ) as f32
})
.reduce_or()
|| subscription.last_entity_view_distance != vd
{
// Update the view distance
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);
@ -216,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 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(),
);
@ -261,6 +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_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.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) {
if chunk_in_vd(pos.0, chunk_key, &terrain, presence.terrain_view_distance.current()) {
should_drop = false;
break;
}

View File

@ -33,8 +33,12 @@ impl<'a> System<'a> for Sys {
// Sync changed chunks
for chunk_key in &terrain_changes.modified_chunks {
for (entity, presence, pos) in (&entities, &presences, &positions).join() {
if super::terrain::chunk_in_vd(pos.0, *chunk_key, &terrain, presence.view_distance)
{
if super::terrain::chunk_in_vd(
pos.0,
*chunk_key,
&terrain,
presence.terrain_view_distance.current(),
) {
chunk_send_emitter.emit(ChunkSendEntry {
entity,
chunk_key: *chunk_key,

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

@ -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,
@ -34,6 +37,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 +47,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(),
}
}
@ -94,34 +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,
client::MAX_SELECTABLE_VIEW_DISTANCE,
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(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,
client::MAX_SELECTABLE_VIEW_DISTANCE,
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(
@ -129,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,
@ -131,6 +134,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 +145,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 +153,7 @@ impl<'a> Video<'a> {
imgs,
fonts,
localized_strings,
server_view_distance_limit,
fps,
common: widget::CommonBuilder::default(),
}
@ -282,38 +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,
client::MAX_SELECTABLE_VIEW_DISTANCE,
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(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,
client::MAX_SELECTABLE_VIEW_DISTANCE,
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)
@ -346,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)
@ -388,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)
@ -436,7 +574,7 @@ impl<'a> Widget for Video<'a> {
// Present Mode
Text::new(&self.localized_strings.get_msg("hud-settings-present_mode"))
.down_from(state.ids.ld_slider, 10.0)
.right_from(state.ids.max_background_fps_value, 30.0)
.right_from(state.ids.max_background_fps_value, 40.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
@ -448,16 +586,11 @@ impl<'a> Widget for Video<'a> {
PresentMode::Immediate,
];
let mode_label_list = [
&self
.localized_strings
.get_msg("hud-settings-present_mode-fifo"),
&self
.localized_strings
.get_msg("hud-settings-present_mode-mailbox"),
&self
.localized_strings
.get_msg("hud-settings-present_mode-immediate"),
];
"hud-settings-present_mode-vsync_capped",
"hud-settings-present_mode-vsync_uncapped",
"hud-settings-present_mode-vsync_off",
]
.map(|k| self.localized_strings.get_msg(k));
// Get which present mode is currently active
let selected = mode_list
@ -465,7 +598,7 @@ impl<'a> Widget for Video<'a> {
.position(|x| *x == render_mode.present_mode);
if let Some(clicked) = DropDownList::new(&mode_label_list, selected)
.w_h(120.0, 22.0)
.w_h(150.0, 26.0)
.color(MENU_BG)
.label_color(TEXT_COLOR)
.label_font_id(self.fonts.cyri.conrod_id)
@ -651,87 +784,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,
@ -215,12 +218,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

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

@ -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(_) => {},
@ -1702,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,
@ -1788,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,13 +358,11 @@ 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;
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
@ -433,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
@ -604,12 +603,11 @@ 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;
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,
@ -654,6 +652,39 @@ impl SettingsChange {
},
},
}
settings.save_to_file_warn(&global_state.config_dir);
global_state
.settings
.save_to_file_warn(&global_state.config_dir);
}
}
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_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: client::MAX_SELECTABLE_VIEW_DISTANCE,
lod_distance: 200,
sprite_render_distance: 100,
particles_enabled: true,

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)