mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'imbris/random-fixes' into 'master'
Random Fixes 3 See merge request veloren/veloren!1463
This commit is contained in:
commit
397dffd5bc
@ -54,6 +54,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Moved hammer leap attack to skillbar
|
||||
- Reworked fire staff
|
||||
- Overhauled cloud shaders to add mist, light attenuation, an approximation of rayleigh scattering, etc.
|
||||
- Fixed a bug where a nearby item would also be collected when collecting collectible blocks
|
||||
- Allowed collecting nearby blocks without aiming at them
|
||||
- Made voxygen wait until singleplayer server is initialized before attempting to connect, removing the chance for it to give up on connecting if the server takes a while to start
|
||||
- Log where userdata folder is located
|
||||
|
||||
### Removed
|
||||
|
||||
|
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -2963,6 +2963,15 @@ dependencies = [
|
||||
"num-traits 0.2.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordered-float"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fe9037165d7023b1228bc4ae9a2fa1a2b0095eca6c2998c624723dfd01314a5"
|
||||
dependencies = [
|
||||
"num-traits 0.2.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "osascript"
|
||||
version = "0.3.0"
|
||||
@ -3691,7 +3700,7 @@ dependencies = [
|
||||
"crossbeam-utils 0.7.2",
|
||||
"linked-hash-map",
|
||||
"num_cpus",
|
||||
"ordered-float",
|
||||
"ordered-float 1.1.0",
|
||||
"rustc-hash",
|
||||
"stb_truetype",
|
||||
]
|
||||
@ -4962,6 +4971,7 @@ dependencies = [
|
||||
"native-dialog",
|
||||
"num 0.2.1",
|
||||
"old_school_gfx_glutin_ext",
|
||||
"ordered-float 2.0.0",
|
||||
"rand 0.7.3",
|
||||
"rodio",
|
||||
"ron",
|
||||
@ -5014,7 +5024,7 @@ dependencies = [
|
||||
"minifb",
|
||||
"noise",
|
||||
"num 0.2.1",
|
||||
"ordered-float",
|
||||
"ordered-float 1.1.0",
|
||||
"packed_simd_2",
|
||||
"rand 0.7.3",
|
||||
"rand_chacha 0.2.2",
|
||||
|
@ -9,7 +9,7 @@ const CLOCK_SMOOTHING: f64 = 0.9;
|
||||
pub struct Clock {
|
||||
last_sys_time: Instant,
|
||||
last_delta: Option<Duration>,
|
||||
running_tps_average: f64,
|
||||
running_average_delta: f64,
|
||||
compensation: f64,
|
||||
}
|
||||
|
||||
@ -18,18 +18,18 @@ impl Clock {
|
||||
Self {
|
||||
last_sys_time: Instant::now(),
|
||||
last_delta: None,
|
||||
running_tps_average: 0.0,
|
||||
running_average_delta: 0.0,
|
||||
compensation: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_tps(&self) -> f64 { 1.0 / self.running_tps_average }
|
||||
pub fn get_tps(&self) -> f64 { 1.0 / self.running_average_delta }
|
||||
|
||||
pub fn get_last_delta(&self) -> Duration {
|
||||
self.last_delta.unwrap_or_else(|| Duration::new(0, 0))
|
||||
}
|
||||
|
||||
pub fn get_avg_delta(&self) -> Duration { Duration::from_secs_f64(self.running_tps_average) }
|
||||
pub fn get_avg_delta(&self) -> Duration { Duration::from_secs_f64(self.running_average_delta) }
|
||||
|
||||
pub fn tick(&mut self, tgt: Duration) {
|
||||
span!(_guard, "tick", "Clock::tick");
|
||||
@ -37,9 +37,9 @@ impl Clock {
|
||||
|
||||
// Attempt to sleep to fill the gap.
|
||||
if let Some(sleep_dur) = tgt.checked_sub(delta) {
|
||||
if self.running_tps_average != 0.0 {
|
||||
if self.running_average_delta != 0.0 {
|
||||
self.compensation =
|
||||
(self.compensation + (tgt.as_secs_f64() / self.running_tps_average) - 1.0)
|
||||
(self.compensation + (tgt.as_secs_f64() / self.running_average_delta) - 1.0)
|
||||
.max(0.0)
|
||||
}
|
||||
|
||||
@ -53,10 +53,10 @@ impl Clock {
|
||||
|
||||
self.last_sys_time = Instant::now();
|
||||
self.last_delta = Some(delta);
|
||||
self.running_tps_average = if self.running_tps_average == 0.0 {
|
||||
self.running_average_delta = if self.running_average_delta == 0.0 {
|
||||
delta.as_secs_f64()
|
||||
} else {
|
||||
CLOCK_SMOOTHING * self.running_tps_average
|
||||
CLOCK_SMOOTHING * self.running_average_delta
|
||||
+ (1.0 - CLOCK_SMOOTHING) * delta.as_secs_f64()
|
||||
};
|
||||
}
|
||||
|
@ -8,9 +8,6 @@ use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, FlaggedStorage, HashMapStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
|
||||
// The limit on distance between the entity and a collectible (squared)
|
||||
pub const MAX_PICKUP_RANGE_SQR: f32 = 64.0;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Inventory {
|
||||
slots: Vec<Option<Item>>,
|
||||
|
@ -49,13 +49,13 @@ pub use inputs::CanBuild;
|
||||
pub use inventory::{
|
||||
item,
|
||||
item::{Item, ItemDrop},
|
||||
slot, Inventory, InventoryUpdate, InventoryUpdateEvent, MAX_PICKUP_RANGE_SQR,
|
||||
slot, Inventory, InventoryUpdate, InventoryUpdateEvent,
|
||||
};
|
||||
pub use last::Last;
|
||||
pub use location::{Waypoint, WaypointArea};
|
||||
pub use misc::Object;
|
||||
pub use phys::{Collider, ForceUpdate, Gravity, Mass, Ori, PhysicsState, Pos, Scale, Sticky, Vel};
|
||||
pub use player::{Player, MAX_MOUNT_RANGE_SQR};
|
||||
pub use player::Player;
|
||||
pub use projectile::Projectile;
|
||||
pub use shockwave::{Shockwave, ShockwaveHitEntities};
|
||||
pub use skills::{Skill, SkillGroup, SkillGroupType, SkillSet};
|
||||
|
@ -5,7 +5,6 @@ use specs::{Component, FlaggedStorage, NullStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
|
||||
const MAX_ALIAS_LEN: usize = 32;
|
||||
pub const MAX_MOUNT_RANGE_SQR: i32 = 20000;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Player {
|
||||
|
3
common/src/consts.rs
Normal file
3
common/src/consts.rs
Normal file
@ -0,0 +1,3 @@
|
||||
// The limit on distance between the entity and a collectible (squared)
|
||||
pub const MAX_PICKUP_RANGE: f32 = 8.0;
|
||||
pub const MAX_MOUNT_RANGE: f32 = 14.0;
|
@ -24,6 +24,7 @@ pub mod clock;
|
||||
pub mod cmd;
|
||||
pub mod combat;
|
||||
pub mod comp;
|
||||
pub mod consts;
|
||||
pub mod effect;
|
||||
pub mod event;
|
||||
pub mod explosion;
|
||||
|
@ -85,6 +85,7 @@ fn main() -> io::Result<()> {
|
||||
// Determine folder to save server data in
|
||||
let server_data_dir = {
|
||||
let mut path = common::userdata_dir_workspace!();
|
||||
info!("Using userdata folder at {}", path.display());
|
||||
path.push(server::DEFAULT_DATA_DIR_NAME);
|
||||
path
|
||||
};
|
||||
|
@ -3,8 +3,9 @@ use common::{
|
||||
comp::{
|
||||
self, item,
|
||||
slot::{self, Slot},
|
||||
Pos, MAX_PICKUP_RANGE_SQR,
|
||||
Pos,
|
||||
},
|
||||
consts::MAX_PICKUP_RANGE,
|
||||
msg::ServerGeneral,
|
||||
recipe::default_recipe_book,
|
||||
sync::{Uid, WorldSyncExt},
|
||||
@ -512,7 +513,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
|
||||
fn within_pickup_range(player_position: Option<&Pos>, item_position: Option<&Pos>) -> bool {
|
||||
match (player_position, item_position) {
|
||||
(Some(ppos), Some(ipos)) => ppos.0.distance_squared(ipos.0) < MAX_PICKUP_RANGE_SQR,
|
||||
(Some(ppos), Some(ipos)) => ppos.0.distance_squared(ipos.0) < MAX_PICKUP_RANGE.powi(2),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -72,13 +72,15 @@ impl Settings {
|
||||
match ron::de::from_reader(file) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
let default_settings = Self::default();
|
||||
let template_path = path.with_extension("template.ron");
|
||||
warn!(
|
||||
?e,
|
||||
"Failed to parse setting file! Falling back to default settings and \
|
||||
creating a template file for you to migrate your current settings file"
|
||||
creating a template file for you to migrate your current settings file: \
|
||||
{}",
|
||||
template_path.display()
|
||||
);
|
||||
let default_settings = Self::default();
|
||||
let template_path = path.with_extension("template.ron");
|
||||
if let Err(e) = default_settings.save_to_file(&template_path) {
|
||||
error!(?e, "Failed to create template settings file")
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ pub trait EditableSetting: Serialize + DeserializeOwned + Default {
|
||||
new_path = path.with_extension(format!("invalid{}.ron", i));
|
||||
}
|
||||
|
||||
warn!("Renaming invalid settings file to: {}", path.display());
|
||||
warn!("Renaming invalid settings file to: {}", new_path.display());
|
||||
if let Err(e) = fs::rename(&path, &new_path) {
|
||||
warn!(?e, ?path, ?new_path, "Failed to rename settings file.");
|
||||
}
|
||||
|
@ -66,6 +66,7 @@ hashbrown = {version = "0.7.2", features = ["rayon", "serde", "nightly"]}
|
||||
image = {version = "0.23.8", default-features = false, features = ["ico", "png"]}
|
||||
native-dialog = { version = "0.4.2", default-features = false, optional = true }
|
||||
num = "0.2"
|
||||
ordered-float = { version = "2.0.0", default-features = false }
|
||||
rand = "0.7"
|
||||
rodio = {version = "0.11", default-features = false, features = ["wav", "vorbis"]}
|
||||
ron = {version = "0.6", default-features = false}
|
||||
|
@ -1123,7 +1123,7 @@ impl Hud {
|
||||
for (pos, item, distance) in (&entities, &pos, &items)
|
||||
.join()
|
||||
.map(|(_, pos, item)| (pos, item, pos.0.distance_squared(player_pos)))
|
||||
.filter(|(_, _, distance)| distance < &common::comp::MAX_PICKUP_RANGE_SQR)
|
||||
.filter(|(_, _, distance)| distance < &common::consts::MAX_PICKUP_RANGE.powi(2))
|
||||
{
|
||||
let overitem_id = overitem_walker.next(
|
||||
&mut self.ids.overitems,
|
||||
@ -2455,6 +2455,28 @@ impl Hud {
|
||||
self.force_ungrab = !self.force_ungrab;
|
||||
true
|
||||
},
|
||||
|
||||
// If not showing the ui don't allow keys that change the ui state but do listen for
|
||||
// hotbar keys
|
||||
event if !self.show.ui => {
|
||||
if let WinEvent::InputUpdate(key, state) = event {
|
||||
if let Some(slot) = try_hotbar_slot_from_input(key) {
|
||||
handle_slot(
|
||||
slot,
|
||||
state,
|
||||
&mut self.events,
|
||||
&mut self.slot_manager,
|
||||
&mut self.hotbar,
|
||||
);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
|
||||
WinEvent::Zoom(_) => !cursor_grabbed && !self.ui.no_widget_capturing_mouse(),
|
||||
|
||||
WinEvent::InputUpdate(GameInput::Chat, true) => {
|
||||
@ -2521,107 +2543,20 @@ impl Hud {
|
||||
true
|
||||
},
|
||||
// Skillbar
|
||||
GameInput::Slot1 => {
|
||||
handle_slot(
|
||||
hotbar::Slot::One,
|
||||
state,
|
||||
&mut self.events,
|
||||
&mut self.slot_manager,
|
||||
&mut self.hotbar,
|
||||
);
|
||||
true
|
||||
input => {
|
||||
if let Some(slot) = try_hotbar_slot_from_input(input) {
|
||||
handle_slot(
|
||||
slot,
|
||||
state,
|
||||
&mut self.events,
|
||||
&mut self.slot_manager,
|
||||
&mut self.hotbar,
|
||||
);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
GameInput::Slot2 => {
|
||||
handle_slot(
|
||||
hotbar::Slot::Two,
|
||||
state,
|
||||
&mut self.events,
|
||||
&mut self.slot_manager,
|
||||
&mut self.hotbar,
|
||||
);
|
||||
true
|
||||
},
|
||||
GameInput::Slot3 => {
|
||||
handle_slot(
|
||||
hotbar::Slot::Three,
|
||||
state,
|
||||
&mut self.events,
|
||||
&mut self.slot_manager,
|
||||
&mut self.hotbar,
|
||||
);
|
||||
true
|
||||
},
|
||||
GameInput::Slot4 => {
|
||||
handle_slot(
|
||||
hotbar::Slot::Four,
|
||||
state,
|
||||
&mut self.events,
|
||||
&mut self.slot_manager,
|
||||
&mut self.hotbar,
|
||||
);
|
||||
true
|
||||
},
|
||||
GameInput::Slot5 => {
|
||||
handle_slot(
|
||||
hotbar::Slot::Five,
|
||||
state,
|
||||
&mut self.events,
|
||||
&mut self.slot_manager,
|
||||
&mut self.hotbar,
|
||||
);
|
||||
true
|
||||
},
|
||||
GameInput::Slot6 => {
|
||||
handle_slot(
|
||||
hotbar::Slot::Six,
|
||||
state,
|
||||
&mut self.events,
|
||||
&mut self.slot_manager,
|
||||
&mut self.hotbar,
|
||||
);
|
||||
true
|
||||
},
|
||||
GameInput::Slot7 => {
|
||||
handle_slot(
|
||||
hotbar::Slot::Seven,
|
||||
state,
|
||||
&mut self.events,
|
||||
&mut self.slot_manager,
|
||||
&mut self.hotbar,
|
||||
);
|
||||
true
|
||||
},
|
||||
GameInput::Slot8 => {
|
||||
handle_slot(
|
||||
hotbar::Slot::Eight,
|
||||
state,
|
||||
&mut self.events,
|
||||
&mut self.slot_manager,
|
||||
&mut self.hotbar,
|
||||
);
|
||||
true
|
||||
},
|
||||
GameInput::Slot9 => {
|
||||
handle_slot(
|
||||
hotbar::Slot::Nine,
|
||||
state,
|
||||
&mut self.events,
|
||||
&mut self.slot_manager,
|
||||
&mut self.hotbar,
|
||||
);
|
||||
true
|
||||
},
|
||||
GameInput::Slot10 => {
|
||||
handle_slot(
|
||||
hotbar::Slot::Ten,
|
||||
state,
|
||||
&mut self.events,
|
||||
&mut self.slot_manager,
|
||||
&mut self.hotbar,
|
||||
);
|
||||
true
|
||||
},
|
||||
_ => false,
|
||||
},
|
||||
// Else the player is typing in chat
|
||||
WinEvent::InputUpdate(_key, _) => self.typing(),
|
||||
@ -2676,8 +2611,8 @@ impl Hud {
|
||||
.handle_event(conrod_core::event::Input::Text("\t".to_string()));
|
||||
}
|
||||
|
||||
// Optimization: skip maintaining UI when it's off.
|
||||
if !self.show.ui {
|
||||
// Optimization: skip maintaining UI when it's off.
|
||||
return std::mem::take(&mut self.events);
|
||||
}
|
||||
|
||||
@ -2735,3 +2670,19 @@ fn get_buff_info(buff: &comp::Buff) -> BuffInfo {
|
||||
dur: buff.time,
|
||||
}
|
||||
}
|
||||
|
||||
fn try_hotbar_slot_from_input(input: GameInput) -> Option<hotbar::Slot> {
|
||||
Some(match input {
|
||||
GameInput::Slot1 => hotbar::Slot::One,
|
||||
GameInput::Slot2 => hotbar::Slot::Two,
|
||||
GameInput::Slot3 => hotbar::Slot::Three,
|
||||
GameInput::Slot4 => hotbar::Slot::Four,
|
||||
GameInput::Slot5 => hotbar::Slot::Five,
|
||||
GameInput::Slot6 => hotbar::Slot::Six,
|
||||
GameInput::Slot7 => hotbar::Slot::Seven,
|
||||
GameInput::Slot8 => hotbar::Slot::Eight,
|
||||
GameInput::Slot9 => hotbar::Slot::Nine,
|
||||
GameInput::Slot10 => hotbar::Slot::Ten,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
@ -91,9 +91,10 @@ impl<'a> Widget for Overitem<'a> {
|
||||
// ———
|
||||
|
||||
// scale at max distance is 10, and at min distance is 30
|
||||
let scale: f64 =
|
||||
((1.5 - (self.distance_from_player_sqr / common::comp::MAX_PICKUP_RANGE_SQR)) * 20.0)
|
||||
.into();
|
||||
let scale: f64 = ((1.5
|
||||
- (self.distance_from_player_sqr / common::consts::MAX_PICKUP_RANGE.powi(2)))
|
||||
* 20.0)
|
||||
.into();
|
||||
let text_font_size = scale * 1.0;
|
||||
let text_pos_y = scale * 1.2;
|
||||
let btn_rect_size = scale * 0.8;
|
||||
|
@ -19,7 +19,7 @@ use common::{
|
||||
clock::Clock,
|
||||
};
|
||||
use std::panic;
|
||||
use tracing::{error, warn};
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
fn main() {
|
||||
// Load the settings
|
||||
@ -35,6 +35,12 @@ fn main() {
|
||||
// Init logging and hold the guards.
|
||||
let _guards = logging::init(&settings);
|
||||
|
||||
if let Some(path) = veloren_voxygen::settings::voxygen_data_dir().parent() {
|
||||
info!("Using userdata dir at: {}", path.display());
|
||||
} else {
|
||||
error!("Can't log userdata dir, voxygen data dir has no parent!");
|
||||
}
|
||||
|
||||
// Set up panic handler to relay swish panic messages to the user
|
||||
let default_hook = panic::take_hook();
|
||||
panic::set_hook(Box::new(move |panic_info| {
|
||||
|
@ -51,7 +51,7 @@ impl PlayState for MainMenuState {
|
||||
&crate::i18n::i18n_asset_key(&global_state.settings.language.selected_language),
|
||||
);
|
||||
|
||||
//Poll server creation
|
||||
// Poll server creation
|
||||
#[cfg(feature = "singleplayer")]
|
||||
{
|
||||
if let Some(singleplayer) = &global_state.singleplayer {
|
||||
@ -62,6 +62,18 @@ impl PlayState for MainMenuState {
|
||||
self.client_init = None;
|
||||
self.main_menu_ui.cancel_connection();
|
||||
self.main_menu_ui.show_info(format!("Error: {:?}", error));
|
||||
} else {
|
||||
let server_settings = singleplayer.settings();
|
||||
// Attempt login after the server is finished initializing
|
||||
attempt_login(
|
||||
&mut global_state.settings,
|
||||
&mut global_state.info_message,
|
||||
"singleplayer".to_owned(),
|
||||
"".to_owned(),
|
||||
server_settings.gameserver_address.ip().to_string(),
|
||||
server_settings.gameserver_address.port(),
|
||||
&mut self.client_init,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -208,7 +220,8 @@ impl PlayState for MainMenuState {
|
||||
server_address,
|
||||
} => {
|
||||
attempt_login(
|
||||
global_state,
|
||||
&mut global_state.settings,
|
||||
&mut global_state.info_message,
|
||||
username,
|
||||
password,
|
||||
server_address,
|
||||
@ -229,18 +242,9 @@ impl PlayState for MainMenuState {
|
||||
},
|
||||
#[cfg(feature = "singleplayer")]
|
||||
MainMenuEvent::StartSingleplayer => {
|
||||
let (singleplayer, server_settings) = Singleplayer::new(None); // TODO: Make client and server use the same thread pool
|
||||
let singleplayer = Singleplayer::new(None); // TODO: Make client and server use the same thread pool
|
||||
|
||||
global_state.singleplayer = Some(singleplayer);
|
||||
|
||||
attempt_login(
|
||||
global_state,
|
||||
"singleplayer".to_owned(),
|
||||
"".to_owned(),
|
||||
server_settings.gameserver_address.ip().to_string(),
|
||||
server_settings.gameserver_address.port(),
|
||||
&mut self.client_init,
|
||||
);
|
||||
},
|
||||
MainMenuEvent::Settings => {}, // TODO
|
||||
MainMenuEvent::Quit => return PlayStateResult::Shutdown,
|
||||
@ -279,19 +283,20 @@ impl PlayState for MainMenuState {
|
||||
}
|
||||
|
||||
fn attempt_login(
|
||||
global_state: &mut GlobalState,
|
||||
settings: &mut Settings,
|
||||
info_message: &mut Option<String>,
|
||||
username: String,
|
||||
password: String,
|
||||
server_address: String,
|
||||
server_port: u16,
|
||||
client_init: &mut Option<ClientInit>,
|
||||
) {
|
||||
let mut net_settings = &mut global_state.settings.networking;
|
||||
let mut net_settings = &mut settings.networking;
|
||||
net_settings.username = username.clone();
|
||||
if !net_settings.servers.contains(&server_address) {
|
||||
net_settings.servers.push(server_address.clone());
|
||||
}
|
||||
if let Err(e) = global_state.settings.save_to_file() {
|
||||
if let Err(e) = settings.save_to_file() {
|
||||
warn!(?e, "Failed to save settings");
|
||||
}
|
||||
|
||||
@ -301,11 +306,11 @@ fn attempt_login(
|
||||
*client_init = Some(ClientInit::new(
|
||||
(server_address, server_port, false),
|
||||
username,
|
||||
Some(global_state.settings.graphics.view_distance),
|
||||
Some(settings.graphics.view_distance),
|
||||
password,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
global_state.info_message = Some("Invalid username".to_string());
|
||||
*info_message = Some("Invalid username".to_string());
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,9 @@ pub struct BlocksOfInterest {
|
||||
pub beehives: Vec<Vec3<i32>>,
|
||||
pub reeds: Vec<Vec3<i32>>,
|
||||
pub flowers: Vec<Vec3<i32>>,
|
||||
// Note: these are only needed for chunks within the iteraction range so this is a potential
|
||||
// area for optimization
|
||||
pub interactables: Vec<Vec3<i32>>,
|
||||
}
|
||||
|
||||
impl BlocksOfInterest {
|
||||
@ -24,6 +27,7 @@ impl BlocksOfInterest {
|
||||
let mut beehives = Vec::new();
|
||||
let mut reeds = Vec::new();
|
||||
let mut flowers = Vec::new();
|
||||
let mut interactables = Vec::new();
|
||||
|
||||
chunk
|
||||
.vol_iter(
|
||||
@ -34,29 +38,34 @@ impl BlocksOfInterest {
|
||||
chunk.get_max_z(),
|
||||
),
|
||||
)
|
||||
.for_each(|(pos, block)| match block.kind() {
|
||||
BlockKind::Leaves => {
|
||||
if thread_rng().gen_range(0, 16) == 0 {
|
||||
leaves.push(pos)
|
||||
}
|
||||
},
|
||||
BlockKind::Grass => {
|
||||
if thread_rng().gen_range(0, 16) == 0 {
|
||||
grass.push(pos)
|
||||
}
|
||||
},
|
||||
_ => match block.get_sprite() {
|
||||
Some(SpriteKind::Ember) => embers.push(pos),
|
||||
Some(SpriteKind::Beehive) => beehives.push(pos),
|
||||
Some(SpriteKind::Reed) => reeds.push(pos),
|
||||
Some(SpriteKind::PinkFlower) => flowers.push(pos),
|
||||
Some(SpriteKind::PurpleFlower) => flowers.push(pos),
|
||||
Some(SpriteKind::RedFlower) => flowers.push(pos),
|
||||
Some(SpriteKind::WhiteFlower) => flowers.push(pos),
|
||||
Some(SpriteKind::YellowFlower) => flowers.push(pos),
|
||||
Some(SpriteKind::Sunflower) => flowers.push(pos),
|
||||
_ => {},
|
||||
},
|
||||
.for_each(|(pos, block)| {
|
||||
match block.kind() {
|
||||
BlockKind::Leaves => {
|
||||
if thread_rng().gen_range(0, 16) == 0 {
|
||||
leaves.push(pos)
|
||||
}
|
||||
},
|
||||
BlockKind::Grass => {
|
||||
if thread_rng().gen_range(0, 16) == 0 {
|
||||
grass.push(pos)
|
||||
}
|
||||
},
|
||||
_ => match block.get_sprite() {
|
||||
Some(SpriteKind::Ember) => embers.push(pos),
|
||||
Some(SpriteKind::Beehive) => beehives.push(pos),
|
||||
Some(SpriteKind::Reed) => reeds.push(pos),
|
||||
Some(SpriteKind::PinkFlower) => flowers.push(pos),
|
||||
Some(SpriteKind::PurpleFlower) => flowers.push(pos),
|
||||
Some(SpriteKind::RedFlower) => flowers.push(pos),
|
||||
Some(SpriteKind::WhiteFlower) => flowers.push(pos),
|
||||
Some(SpriteKind::YellowFlower) => flowers.push(pos),
|
||||
Some(SpriteKind::Sunflower) => flowers.push(pos),
|
||||
_ => {},
|
||||
},
|
||||
}
|
||||
if block.is_collectible() {
|
||||
interactables.push(pos);
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
@ -66,6 +75,7 @@ impl BlocksOfInterest {
|
||||
beehives,
|
||||
reeds,
|
||||
flowers,
|
||||
interactables,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,10 +15,8 @@ use client::{self, Client};
|
||||
use common::{
|
||||
assets::Asset,
|
||||
comp,
|
||||
comp::{
|
||||
ChatMsg, ChatType, InventoryUpdateEvent, Pos, Vel, MAX_MOUNT_RANGE_SQR,
|
||||
MAX_PICKUP_RANGE_SQR,
|
||||
},
|
||||
comp::{ChatMsg, ChatType, InventoryUpdateEvent, Pos, Vel},
|
||||
consts::{MAX_MOUNT_RANGE, MAX_PICKUP_RANGE},
|
||||
event::EventBus,
|
||||
outcome::Outcome,
|
||||
span,
|
||||
@ -26,6 +24,7 @@ use common::{
|
||||
util::Dir,
|
||||
vol::ReadVol,
|
||||
};
|
||||
use ordered_float::OrderedFloat;
|
||||
use specs::{Join, WorldExt};
|
||||
use std::{cell::RefCell, rc::Rc, sync::Arc, time::Duration};
|
||||
use tracing::{error, info};
|
||||
@ -205,6 +204,7 @@ impl PlayState for SessionState {
|
||||
|
||||
fn tick(&mut self, global_state: &mut GlobalState, events: Vec<Event>) -> PlayStateResult {
|
||||
span!(_guard, "tick", "<Session as PlayState>::tick");
|
||||
// TODO: let mut client = self.client.borrow_mut();
|
||||
// NOTE: Not strictly necessary, but useful for hotloading translation changes.
|
||||
self.voxygen_i18n = VoxygenLocalization::load_expect(&i18n_asset_key(
|
||||
&global_state.settings.language.selected_language,
|
||||
@ -272,16 +272,24 @@ impl PlayState for SessionState {
|
||||
.get(self.client.borrow().entity())
|
||||
.is_some();
|
||||
|
||||
// Only highlight collectables
|
||||
self.scene.set_select_pos(select_pos.filter(|sp| {
|
||||
self.client
|
||||
.borrow()
|
||||
.state()
|
||||
.terrain()
|
||||
.get(*sp)
|
||||
.map(|b| b.is_collectible() || can_build)
|
||||
.unwrap_or(false)
|
||||
}));
|
||||
let interactable = select_interactable(
|
||||
&self.client.borrow(),
|
||||
self.target_entity,
|
||||
select_pos,
|
||||
&self.scene,
|
||||
);
|
||||
|
||||
// Only highlight interactables
|
||||
// unless in build mode where select_pos highlighted
|
||||
self.scene
|
||||
.set_select_pos(
|
||||
select_pos
|
||||
.filter(|_| can_build)
|
||||
.or_else(|| match interactable {
|
||||
Some(Interactable::Block(_, block_pos)) => Some(block_pos),
|
||||
_ => None,
|
||||
}),
|
||||
);
|
||||
|
||||
// Handle window events.
|
||||
for event in events {
|
||||
@ -457,36 +465,22 @@ impl PlayState for SessionState {
|
||||
.copied();
|
||||
if let Some(player_pos) = player_pos {
|
||||
// Find closest mountable entity
|
||||
let mut closest_mountable: Option<(specs::Entity, i32)> = None;
|
||||
|
||||
for (entity, pos, ms) in (
|
||||
let closest_mountable_entity = (
|
||||
&client.state().ecs().entities(),
|
||||
&client.state().ecs().read_storage::<comp::Pos>(),
|
||||
&client.state().ecs().read_storage::<comp::MountState>(),
|
||||
)
|
||||
.join()
|
||||
.filter(|(entity, _, _)| *entity != client.entity())
|
||||
{
|
||||
if comp::MountState::Unmounted != *ms {
|
||||
continue;
|
||||
}
|
||||
|
||||
let dist =
|
||||
(player_pos.0.distance_squared(pos.0) * 1000.0) as i32;
|
||||
if dist > MAX_MOUNT_RANGE_SQR {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(previous) = closest_mountable.as_mut() {
|
||||
if dist < previous.1 {
|
||||
*previous = (entity, dist);
|
||||
}
|
||||
} else {
|
||||
closest_mountable = Some((entity, dist));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((mountee_entity, _)) = closest_mountable {
|
||||
.filter(|(entity, _, mount_state)| {
|
||||
*entity != client.entity()
|
||||
&& **mount_state == comp::MountState::Unmounted
|
||||
})
|
||||
.map(|(entity, pos, _)| {
|
||||
(entity, player_pos.0.distance_squared(pos.0))
|
||||
})
|
||||
.filter(|(_, dist_sqr)| *dist_sqr < MAX_MOUNT_RANGE.powi(2))
|
||||
.min_by_key(|(_, dist_sqr)| OrderedFloat(*dist_sqr));
|
||||
if let Some((mountee_entity, _)) = closest_mountable_entity {
|
||||
client.mount(mountee_entity);
|
||||
}
|
||||
}
|
||||
@ -498,40 +492,25 @@ impl PlayState for SessionState {
|
||||
self.key_state.collect = state;
|
||||
|
||||
if state {
|
||||
let mut client = self.client.borrow_mut();
|
||||
|
||||
// Collect terrain sprites
|
||||
if let Some(select_pos) = self.scene.select_pos() {
|
||||
client.collect_block(select_pos);
|
||||
}
|
||||
|
||||
// Collect lootable entities
|
||||
let player_pos = client
|
||||
.state()
|
||||
.read_storage::<comp::Pos>()
|
||||
.get(client.entity())
|
||||
.copied();
|
||||
|
||||
if let Some(player_pos) = player_pos {
|
||||
let entity = self.target_entity.or_else(|| {
|
||||
(
|
||||
&client.state().ecs().entities(),
|
||||
&client.state().ecs().read_storage::<comp::Pos>(),
|
||||
&client.state().ecs().read_storage::<comp::Item>(),
|
||||
)
|
||||
.join()
|
||||
.filter(|(_, pos, _)| {
|
||||
pos.0.distance_squared(player_pos.0)
|
||||
< MAX_PICKUP_RANGE_SQR
|
||||
})
|
||||
.min_by_key(|(_, pos, _)| {
|
||||
(pos.0.distance_squared(player_pos.0) * 1000.0) as i32
|
||||
})
|
||||
.map(|(entity, _, _)| entity)
|
||||
});
|
||||
|
||||
if let Some(entity) = entity {
|
||||
client.pick_up(entity);
|
||||
if let Some(interactable) = interactable {
|
||||
let mut client = self.client.borrow_mut();
|
||||
match interactable {
|
||||
Interactable::Block(block, pos) => {
|
||||
if block.is_collectible() {
|
||||
client.collect_block(pos);
|
||||
}
|
||||
},
|
||||
Interactable::Entity(entity) => {
|
||||
if client
|
||||
.state()
|
||||
.ecs()
|
||||
.read_storage::<comp::Item>()
|
||||
.get(entity)
|
||||
.is_some()
|
||||
{
|
||||
client.pick_up(entity);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1162,6 +1141,7 @@ fn under_cursor(
|
||||
Option<Vec3<i32>>,
|
||||
Option<(specs::Entity, f32)>,
|
||||
) {
|
||||
span!(_guard, "under_cursor");
|
||||
// Choose a spot above the player's head for item distance checks
|
||||
let player_entity = client.entity();
|
||||
let player_pos = match client
|
||||
@ -1184,7 +1164,7 @@ fn under_cursor(
|
||||
// The ray hit something, is it within range?
|
||||
let (build_pos, select_pos) = if matches!(cam_ray.1, Ok(Some(_)) if
|
||||
player_pos.distance_squared(cam_pos + cam_dir * cam_dist)
|
||||
<= MAX_PICKUP_RANGE_SQR)
|
||||
<= MAX_PICKUP_RANGE.powi(2))
|
||||
{
|
||||
(
|
||||
Some((cam_pos + cam_dir * (cam_dist - 0.01)).map(|e| e.floor() as i32)),
|
||||
@ -1253,3 +1233,109 @@ fn under_cursor(
|
||||
// TODO: consider setting build/select to None when targeting an entity
|
||||
(build_pos, select_pos, target_entity)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum Interactable {
|
||||
Block(Block, Vec3<i32>),
|
||||
Entity(specs::Entity),
|
||||
}
|
||||
|
||||
/// Select interactable to hightlight, display interaction text for, and to
|
||||
/// interact with if the interact key is pressed
|
||||
/// Selected in the following order
|
||||
/// 1) Targeted entity (if interactable) (entities can't be target through
|
||||
/// blocks) 2) Selected block (if interactabl)
|
||||
/// 3) Closest of nearest interactable entity/block
|
||||
fn select_interactable(
|
||||
client: &Client,
|
||||
target_entity: Option<specs::Entity>,
|
||||
selected_pos: Option<Vec3<i32>>,
|
||||
scene: &Scene,
|
||||
) -> Option<Interactable> {
|
||||
span!(_guard, "select_interactable");
|
||||
use common::{spiral::Spiral2d, terrain::TerrainChunk, vol::RectRasterableVol};
|
||||
target_entity.map(Interactable::Entity)
|
||||
.or_else(|| selected_pos.and_then(|sp|
|
||||
client.state().terrain().get(sp).ok().copied()
|
||||
.filter(Block::is_collectible).map(|b| Interactable::Block(b, sp))
|
||||
))
|
||||
.or_else(|| {
|
||||
let ecs = client.state().ecs();
|
||||
let player_entity = client.entity();
|
||||
ecs
|
||||
.read_storage::<comp::Pos>()
|
||||
.get(player_entity).and_then(|player_pos| {
|
||||
let closest_interactable_entity = (
|
||||
&ecs.entities(),
|
||||
&ecs.read_storage::<comp::Pos>(),
|
||||
ecs.read_storage::<comp::Scale>().maybe(),
|
||||
&ecs.read_storage::<comp::Body>(),
|
||||
// Must have this comp to be interactable (for now)
|
||||
&ecs.read_storage::<comp::Item>(),
|
||||
)
|
||||
.join()
|
||||
.filter(|(e, _, _, _, _)| *e != player_entity)
|
||||
.map(|(e, p, s, b, _)| {
|
||||
let radius = s.map_or(1.0, |s| s.0) * b.radius();
|
||||
// Distance squared from player to the entity
|
||||
// Note: the position of entities is currently at their feet so this
|
||||
// distance is between their feet positions
|
||||
let dist_sqr = p.0.distance_squared(player_pos.0);
|
||||
(e, radius, dist_sqr)
|
||||
})
|
||||
// Roughly filter out entities farther than interaction distance
|
||||
.filter(|(_, r, d_sqr)| *d_sqr <= MAX_PICKUP_RANGE.powi(2) + 2.0 * MAX_PICKUP_RANGE * r + r.powi(2))
|
||||
// Note: entities are approximated as spheres here
|
||||
// to determine which is closer
|
||||
// Substract sphere radius from distance to the player
|
||||
.map(|(e, r, d_sqr)| (e, d_sqr.sqrt() - r))
|
||||
.min_by_key(|(_, dist)| OrderedFloat(*dist));
|
||||
|
||||
// Only search as far as closest interactable entity
|
||||
let search_dist = closest_interactable_entity
|
||||
.map_or(MAX_PICKUP_RANGE, |(_, dist)| dist);
|
||||
let player_chunk = player_pos.0.xy().map2(TerrainChunk::RECT_SIZE, |e, sz| {
|
||||
(e.floor() as i32).div_euclid(sz as i32)
|
||||
});
|
||||
let terrain = scene.terrain();
|
||||
|
||||
// Find closest interactable block
|
||||
// TODO: consider doing this one first?
|
||||
let closest_interactable_block_pos = Spiral2d::new()
|
||||
// TODO: this formula for the number to take was guessed
|
||||
// Note: assume RECT_SIZE.x == RECT_SIZE.y
|
||||
.take(((search_dist / TerrainChunk::RECT_SIZE.x as f32).ceil() as usize * 2 + 1).pow(2))
|
||||
.flat_map(|offset| {
|
||||
let chunk_pos = player_chunk + offset;
|
||||
let chunk_voxel_pos =
|
||||
Vec3::<i32>::from(chunk_pos * TerrainChunk::RECT_SIZE.map(|e| e as i32));
|
||||
terrain.get(chunk_pos).map(|data| (data, chunk_voxel_pos))
|
||||
})
|
||||
// TODO: maybe we could make this more efficient by putting the
|
||||
// interactables is some sort of spatial structure
|
||||
.flat_map(|(chunk_data, chunk_pos)| {
|
||||
chunk_data
|
||||
.blocks_of_interest
|
||||
.interactables
|
||||
.iter()
|
||||
.map(move |block_offset| chunk_pos + block_offset)
|
||||
})
|
||||
// TODO: confirm that adding 0.5 here is correct
|
||||
.map(|block_pos| (
|
||||
block_pos,
|
||||
block_pos.map(|e| e as f32 + 0.5)
|
||||
.distance_squared(player_pos.0)
|
||||
))
|
||||
.min_by_key(|(_, dist_sqr)| OrderedFloat(*dist_sqr));
|
||||
|
||||
// Pick closer one if they exist
|
||||
closest_interactable_block_pos
|
||||
.filter(|(_, dist_sqr)| search_dist.powi(2) > *dist_sqr)
|
||||
.and_then(|(block_pos, _)|
|
||||
client.state().terrain().get(block_pos).ok().copied()
|
||||
.map(|b| Interactable::Block(b, block_pos))
|
||||
)
|
||||
.or_else(|| closest_interactable_entity.map(|(e, _)| Interactable::Entity(e)))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -26,10 +26,12 @@ pub struct Singleplayer {
|
||||
pub receiver: Receiver<Result<(), ServerError>>,
|
||||
// Wether the server is stopped or not
|
||||
paused: Arc<AtomicBool>,
|
||||
// Settings that the server was started with
|
||||
settings: server::Settings,
|
||||
}
|
||||
|
||||
impl Singleplayer {
|
||||
pub fn new(client: Option<&Client>) -> (Self, server::Settings) {
|
||||
pub fn new(client: Option<&Client>) -> Self {
|
||||
let (sender, receiver) = unbounded();
|
||||
|
||||
// Determine folder to save server data in
|
||||
@ -119,17 +121,18 @@ impl Singleplayer {
|
||||
run_server(server, receiver, paused1);
|
||||
});
|
||||
|
||||
(
|
||||
Singleplayer {
|
||||
_server_thread: thread,
|
||||
sender,
|
||||
receiver: result_receiver,
|
||||
paused,
|
||||
},
|
||||
Singleplayer {
|
||||
_server_thread: thread,
|
||||
sender,
|
||||
receiver: result_receiver,
|
||||
paused,
|
||||
settings,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns reference to the settings the server was started with
|
||||
pub fn settings(&self) -> &server::Settings { &self.settings }
|
||||
|
||||
/// Returns wether or not the server is paused
|
||||
pub fn is_paused(&self) -> bool { self.paused.load(Ordering::SeqCst) }
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user