Merge branch 'vd_and_disclaimer' into 'master'

View Distance Tweaks and Showing Disclaimer Once

Closes #126

See merge request veloren/veloren!185

Former-commit-id: a808bbdea6d9cf84cbadb5c592f9db6dc6b0140d
This commit is contained in:
Forest Anderson 2019-05-29 02:09:40 +00:00
commit 09327914b5
12 changed files with 208 additions and 47 deletions

View File

@ -105,11 +105,15 @@ impl Client {
}
pub fn set_view_distance(&mut self, view_distance: u32) {
self.view_distance = Some(view_distance.max(5).min(25));
self.view_distance = Some(view_distance.max(1).min(25));
self.postbox
.send_message(ClientMsg::SetViewDistance(self.view_distance.unwrap())); // Can't fail
}
pub fn view_distance(&self) -> Option<u32> {
self.view_distance
}
/// Send a chat message to the server.
#[allow(dead_code)]
pub fn send_chat(&mut self, msg: String) {
@ -230,9 +234,9 @@ impl Client {
// Request chunks from the server.
// TODO: This is really inefficient.
'outer: for dist in 0..view_distance as i32 {
for i in chunk_pos.x - dist..chunk_pos.x + dist + 1 {
for j in chunk_pos.y - dist..chunk_pos.y + dist + 1 {
'outer: for dist in 0..=view_distance as i32 {
for i in chunk_pos.x - dist..=chunk_pos.x + dist {
for j in chunk_pos.y - dist..=chunk_pos.y + dist {
let key = Vec2::new(i, j);
if self.state.terrain().get_key(key).is_none()
&& !self.pending_chunks.contains_key(&key)

View File

@ -83,6 +83,16 @@ impl Clients {
}
}
pub fn notify_ingame_if<F: FnMut(EcsEntity) -> bool>(&mut self, msg: ServerMsg, mut f: F) {
for (entity, client) in self.clients.iter_mut().filter(|(e, _)| f(**e)) {
if client.client_state == ClientState::Spectator
|| client.client_state == ClientState::Character
{
client.notify(msg.clone());
}
}
}
pub fn notify_registered_except(&mut self, except_entity: EcsEntity, msg: ServerMsg) {
for (entity, client) in self.clients.iter_mut() {
if client.client_state != ClientState::Connected && *entity != except_entity {
@ -101,4 +111,20 @@ impl Clients {
}
}
}
pub fn notify_ingame_if_except<F: FnMut(EcsEntity) -> bool>(
&mut self,
except_entity: EcsEntity,
msg: ServerMsg,
mut f: F,
) {
for (entity, client) in self.clients.iter_mut().filter(|(e, _)| f(**e)) {
if (client.client_state == ClientState::Spectator
|| client.client_state == ClientState::Character)
&& *entity != except_entity
{
client.notify(msg.clone());
}
}
}
}

View File

@ -17,7 +17,8 @@ use common::{
msg::{ClientMsg, ClientState, RequestStateError, ServerMsg},
net::PostOffice,
state::{State, Uid},
terrain::TerrainChunk,
terrain::{TerrainChunk, TerrainChunkSize},
vol::VolSize,
};
use specs::{
join::Join, saveload::MarkedBuilder, world::EntityBuilder as EcsEntityBuilder, Builder,
@ -283,19 +284,22 @@ impl Server {
// Also, send the chunk data to anybody that is close by.
if let Ok((key, chunk)) = self.chunk_rx.try_recv() {
// Send the chunk to all nearby players.
for (entity, player, pos) in (
for (entity, view_distance, pos) in (
&self.state.ecs().entities(),
&self.state.ecs().read_storage::<comp::Player>(),
&self.state.ecs().read_storage::<comp::phys::Pos>(),
)
.join()
.filter_map(|(entity, player, pos)| {
player.view_distance.map(|vd| (entity, vd, pos))
})
{
let chunk_pos = self.state.terrain().pos_key(pos.0.map(|e| e as i32));
let dist = (Vec2::from(chunk_pos) - Vec2::from(key))
.map(|e: i32| e.abs())
.reduce_max() as u32;
if player.view_distance.map(|vd| dist < vd).unwrap_or(false) {
if dist <= view_distance {
self.clients.notify(
entity,
ServerMsg::TerrainChunkUpdate {
@ -329,6 +333,7 @@ impl Server {
if player.view_distance.map(|vd| dist <= vd).unwrap_or(false) {
should_drop = false;
break;
}
}
@ -686,9 +691,34 @@ impl Server {
dir,
};
let state = &self.state;
let mut clients = &mut self.clients;
let in_vd = |entity| {
// Get client position.
let client_pos = match state.ecs().read_storage::<comp::phys::Pos>().get(entity) {
Some(pos) => pos.0,
None => return false,
};
// Get client view distance
let client_vd = match state.ecs().read_storage::<comp::Player>().get(entity) {
Some(comp::Player {
view_distance: Some(vd),
..
}) => *vd,
_ => return false,
};
(pos.0 - client_pos)
.map2(TerrainChunkSize::SIZE, |d, sz| {
(d.abs() as u32) < client_vd * sz as u32
})
.reduce_and()
};
match force_update {
Some(_) => self.clients.notify_registered(msg),
None => self.clients.notify_registered_except(entity, msg),
Some(_) => clients.notify_ingame_if(msg, in_vd),
None => clients.notify_ingame_if_except(entity, msg, in_vd),
}
}
@ -710,8 +740,8 @@ impl Server {
animation_info: animation_info.clone(),
};
match force_update {
Some(_) => self.clients.notify_registered(msg),
None => self.clients.notify_registered_except(entity, msg),
Some(_) => self.clients.notify_ingame(msg),
None => self.clients.notify_ingame_except(entity, msg),
}
}
}

View File

@ -31,8 +31,7 @@ use crate::{
GlobalState,
};
use client::Client;
use common::comp;
use common::comp::phys::Pos;
use common::{comp, terrain::TerrainChunkSize, vol::VolSize};
use conrod_core::{
color, graph,
widget::{self, Button, Image, Rectangle, Text},
@ -105,7 +104,7 @@ font_ids! {
pub struct DebugInfo {
pub tps: f64,
pub ping_ms: f64,
pub coordinates: Option<Pos>,
pub coordinates: Option<comp::phys::Pos>,
}
pub enum Event {
@ -305,6 +304,14 @@ impl Hud {
let player = ecs.read_storage::<comp::Player>();
let entities = ecs.entities();
let me = client.entity();
let view_distance = client.view_distance().unwrap_or(1);
// Get player position.
let player_pos = client
.state()
.ecs()
.read_storage::<comp::phys::Pos>()
.get(client.entity())
.map_or(Vec3::zero(), |pos| pos.0);
let mut name_id_walker = self.ids.name_tags.walk();
let mut health_id_walker = self.ids.health_bars.walk();
let mut health_back_id_walker = self.ids.health_bar_backs.walk();
@ -313,6 +320,14 @@ impl Hud {
for (pos, name) in (&entities, &pos, &actor, &stats, player.maybe())
.join()
.filter(|(entity, _, _, stats, _)| *entity != me && !stats.is_dead)
// Don't process nametags outside the vd (visibility further limited by ui backend)
.filter(|(_, pos, _, _, _)| {
(pos.0 - player_pos)
.map2(TerrainChunkSize::SIZE, |d, sz| {
(d.abs() as u32) < view_distance * sz as u32
})
.reduce_and()
})
.map(|(entity, pos, actor, _, player)| match actor {
comp::Actor::Character {
name: char_name, ..
@ -342,14 +357,21 @@ impl Hud {
}
// Render Health Bars
for (entity, pos, stats) in
(&entities, &pos, &stats)
.join()
.filter(|(entity, _, stats)| {
*entity != me
&& !stats.is_dead
&& stats.hp.get_current() != stats.hp.get_maximum()
})
for (entity, pos, stats) in (&entities, &pos, &stats)
.join()
.filter(|(entity, _, stats)| {
*entity != me
&& !stats.is_dead
&& stats.hp.get_current() != stats.hp.get_maximum()
})
// Don't process health bars outside the vd (visibility further limited by ui backend)
.filter(|(_, pos, _)| {
(pos.0 - player_pos)
.map2(TerrainChunkSize::SIZE, |d, sz| {
(d.abs() as u32) < view_distance * sz as u32
})
.reduce_and()
})
{
let back_id = health_back_id_walker.next(
&mut self.ids.health_bar_backs,

View File

@ -83,6 +83,12 @@ fn main() {
let mut settings = Settings::load();
let window = Window::new(&settings).expect("Failed to create window!");
let mut global_state = GlobalState {
audio: AudioFrontend::new(&settings.audio),
window,
settings,
};
// Initialize logging.
let term_log_level = std::env::var_os("VOXYGEN_LOG")
.and_then(|env| env.to_str().map(|s| s.to_owned()))
@ -93,13 +99,13 @@ fn main() {
WriteLogger::new(
log::LevelFilter::Info,
Config::default(),
File::create(&settings.log.file).unwrap(),
File::create(&global_state.settings.log.file).unwrap(),
),
])
.unwrap();
// Set up panic handler to relay swish panic messages to the user
let settings_clone = settings.clone();
let settings_clone = global_state.settings.clone();
let default_hook = panic::take_hook();
panic::set_hook(Box::new(move |panic_info| {
let panic_info_payload = panic_info.payload();
@ -160,16 +166,10 @@ fn main() {
default_hook(panic_info);
}));
if settings.audio.audio_device == None {
settings.audio.audio_device = Some(AudioFrontend::get_default_device());
if global_state.settings.audio.audio_device == None {
global_state.settings.audio.audio_device = Some(AudioFrontend::get_default_device());
}
let mut global_state = GlobalState {
audio: AudioFrontend::new(&settings.audio),
window,
settings,
};
// Set up the initial play state.
let mut states: Vec<Box<dyn PlayState>> = vec![Box::new(MainMenuState::new(&mut global_state))];
states
@ -227,4 +227,7 @@ fn main() {
}
}
}
// Save settings to add new fields or create the file if it is not already there
// TODO: Handle this result.
global_state.settings.save_to_file();
}

View File

@ -106,7 +106,10 @@ impl PlayState for MainMenuState {
// Don't try to connect if there is already a connection in progress.
client_init = client_init.or(Some(ClientInit::new(
(server_address, DEFAULT_PORT, false),
comp::Player::new(username.clone(), Some(10)),
comp::Player::new(
username.clone(),
Some(global_state.settings.graphics.view_distance),
),
false,
)));
}
@ -114,6 +117,9 @@ impl PlayState for MainMenuState {
return PlayStateResult::Push(Box::new(StartSingleplayerState::new()));
}
MainMenuEvent::Quit => return PlayStateResult::Shutdown,
MainMenuEvent::DisclaimerClosed => {
global_state.settings.show_disclaimer = false
}
}
}

View File

@ -30,7 +30,10 @@ impl PlayState for StartSingleplayerState {
let client_init = ClientInit::new(
(server_address.clone(), self.sock.port(), true),
comp::Player::new(username.clone(), Some(10)),
comp::Player::new(
username.clone(),
Some(global_state.settings.graphics.view_distance),
),
true,
);

View File

@ -90,6 +90,7 @@ pub enum Event {
},
StartSingleplayer,
Quit,
DisclaimerClosed,
}
pub struct MainMenuUi {
@ -129,11 +130,11 @@ impl MainMenuUi {
login_error: None,
connecting: None,
show_servers: false,
show_disclaimer: true,
show_disclaimer: global_state.settings.show_disclaimer,
}
}
fn update_layout(&mut self, global_state: &GlobalState) -> Vec<Event> {
fn update_layout(&mut self, global_state: &mut GlobalState) -> Vec<Event> {
let mut events = Vec::new();
let ref mut ui_widgets = self.ui.set_widgets();
let version = env!("CARGO_PKG_VERSION");
@ -209,8 +210,9 @@ impl MainMenuUi {
.set(self.ids.disc_button, ui_widgets)
.was_clicked()
{
self.show_disclaimer = false
};
self.show_disclaimer = false;
events.push(Event::DisclaimerClosed);
}
} else {
// TODO: Don't use macros for this?
// Input fields

View File

@ -26,6 +26,8 @@ use common::{
figure::Segment,
msg,
msg::ClientState,
terrain::TerrainChunkSize,
vol::VolSize,
};
use dot_vox::DotVoxData;
use specs::{Component, Entity as EcsEntity, Join, VecStorage};
@ -461,6 +463,15 @@ impl FigureMgr {
pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) {
let time = client.state().get_time();
let ecs = client.state().ecs();
let view_distance = client.view_distance().unwrap_or(1);
// Get player position.
let player_pos = client
.state()
.ecs()
.read_storage::<comp::phys::Pos>()
.get(client.entity())
.map_or(Vec3::zero(), |pos| pos.0);
for (entity, pos, vel, dir, actor, animation_info, stats) in (
&ecs.entities(),
&ecs.read_storage::<comp::phys::Pos>(),
@ -472,6 +483,32 @@ impl FigureMgr {
)
.join()
{
// Don't process figures outside the vd
let vd_percent = (pos.0 - player_pos)
.map2(TerrainChunkSize::SIZE, |d, sz| {
(100 * d.abs() as u32) / (view_distance * sz)
})
.reduce_max();
// Keep from re-adding/removing entities on the border of the vd
if vd_percent > 120 {
match actor {
comp::Actor::Character { body, .. } => match body {
Body::Humanoid(_) => {
self.character_states.remove(&entity);
}
Body::Quadruped(_) => {
self.quadruped_states.remove(&entity);
}
Body::QuadrupedMedium(_) => {
self.QuadrupedMedium_states.remove(&entity);
}
},
}
continue;
} else if vd_percent > 100 {
continue;
}
// Change in health as color!
let col = stats
.and_then(|stats| stats.hp.last_change)
@ -610,17 +647,36 @@ impl FigureMgr {
let tick = client.get_tick();
let ecs = client.state().ecs();
for (entity, actor, stat) in (
let view_distance = client.view_distance().unwrap_or(1);
// Get player position.
let player_pos = client
.state()
.ecs()
.read_storage::<comp::phys::Pos>()
.get(client.entity())
.map_or(Vec3::zero(), |pos| pos.0);
for (entity, _, _, _, actor, _, _) in (
&ecs.entities(),
&ecs.read_storage::<comp::phys::Pos>(),
&ecs.read_storage::<comp::phys::Vel>(),
&ecs.read_storage::<comp::phys::Dir>(),
&ecs.read_storage::<comp::Actor>(),
&ecs.read_storage::<comp::Stats>(), // Just to make sure the entity is alive
&ecs.read_storage::<comp::AnimationInfo>(),
ecs.read_storage::<comp::Stats>().maybe(),
)
.join()
// Don't render figures outside the vd
.filter(|(_, pos, _, _, _, _, _)| {
(pos.0 - player_pos)
.map2(TerrainChunkSize::SIZE, |d, sz| {
(d.abs() as u32) < view_distance * sz as u32
})
.reduce_and()
})
// Don't render dead entities
.filter(|(e, _, _, _, a, _, stats)| stats.map_or(true, |s| !s.is_dead))
{
if stat.is_dead {
continue;
}
match actor {
comp::Actor::Character { body, .. } => {
if let Some((locals, bone_consts)) = match body {

View File

@ -116,8 +116,7 @@ impl Scene {
.ecs()
.read_storage::<comp::phys::Pos>()
.get(client.entity())
.map(|pos| pos.0)
.unwrap_or(Vec3::zero());
.map_or(Vec3::zero(), |pos| pos.0);
// Alter camera position to match player.
self.camera.set_focus_pos(player_pos + Vec3::unit_z() * 2.1);

View File

@ -13,6 +13,7 @@ pub struct Settings {
pub log: Log,
pub graphics: GraphicsSettings,
pub audio: AudioSettings,
pub show_disclaimer: bool,
}
/// `ControlSettings` contains keybindings.
@ -117,6 +118,7 @@ impl Default for Settings {
sfx_volume: 0.5,
audio_device: None,
},
show_disclaimer: true,
}
}
}

View File

@ -573,7 +573,15 @@ impl Ui {
/ 10.0)
.min(1.6)
.max(0.2);
Placement::InWorld(parameters.num, Some(parameters.res))
// Scale down ingame elements that are close to the camera
let res = if scale_factor > 3.2 {
parameters.res * scale_factor as f32 / 3.2
} else {
parameters.res
};
Placement::InWorld(parameters.num, Some(res))
} else {
Placement::InWorld(parameters.num, None)
};