mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
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:
commit
bc51d4d2a7
23
CHANGELOG.md
23
CHANGELOG.md
@ -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
|
||||
- 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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
},
|
||||
_ => unreachable!("Not a character_screen msg"),
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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>),
|
||||
|
@ -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: (
|
||||
|
@ -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;
|
||||
}
|
||||
|
25
common/src/view_distances.rs
Normal file
25
common/src/view_distances.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
})
|
||||
};
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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,8 +302,14 @@ impl<'a> Widget for SettingsWindow<'a> {
|
||||
}
|
||||
},
|
||||
SettingsTab::Video => {
|
||||
for change in
|
||||
video::Video::new(global_state, imgs, fonts, localized_strings, self.fps)
|
||||
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)
|
||||
@ -327,8 +336,13 @@ impl<'a> Widget for SettingsWindow<'a> {
|
||||
}
|
||||
},
|
||||
SettingsTab::Networking => {
|
||||
for change in
|
||||
networking::Networking::new(global_state, imgs, fonts, localized_strings)
|
||||
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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,9 +270,10 @@ 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 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);
|
||||
let slider_rect = if is_horizontal {
|
||||
if is_horizontal {
|
||||
let pos = utils::map_range(
|
||||
unskewed_perc,
|
||||
0.0,
|
||||
@ -294,16 +299,37 @@ where
|
||||
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(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.slider, ui);
|
||||
.set(state.ids.soft_max_slider, ui);
|
||||
}
|
||||
|
||||
// If the value has just changed, return the new value.
|
||||
if value != new_value {
|
||||
|
Loading…
Reference in New Issue
Block a user