diff --git a/CHANGELOG.md b/CHANGELOG.md
index 734b6c53a7..c5458fa2ed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
 
diff --git a/Cargo.lock b/Cargo.lock
index fb8f8f1e2e..7bdaf0910a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
diff --git a/common/src/clock.rs b/common/src/clock.rs
index 02d4946e5f..5fc354622f 100644
--- a/common/src/clock.rs
+++ b/common/src/clock.rs
@@ -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()
         };
     }
diff --git a/common/src/comp/inventory/mod.rs b/common/src/comp/inventory/mod.rs
index d57f268d4a..f24fcc3ae1 100644
--- a/common/src/comp/inventory/mod.rs
+++ b/common/src/comp/inventory/mod.rs
@@ -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>>,
diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs
index 3aa9556d99..f2016bcb88 100644
--- a/common/src/comp/mod.rs
+++ b/common/src/comp/mod.rs
@@ -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};
diff --git a/common/src/comp/player.rs b/common/src/comp/player.rs
index a6818af377..7b12b8741a 100644
--- a/common/src/comp/player.rs
+++ b/common/src/comp/player.rs
@@ -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 {
diff --git a/common/src/consts.rs b/common/src/consts.rs
new file mode 100644
index 0000000000..c82a9f4cd3
--- /dev/null
+++ b/common/src/consts.rs
@@ -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;
diff --git a/common/src/lib.rs b/common/src/lib.rs
index 01b986408a..b35122fa67 100644
--- a/common/src/lib.rs
+++ b/common/src/lib.rs
@@ -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;
diff --git a/server-cli/src/main.rs b/server-cli/src/main.rs
index 73bdc649a4..1707656261 100644
--- a/server-cli/src/main.rs
+++ b/server-cli/src/main.rs
@@ -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
     };
diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs
index fff06d9e99..5dce6f7aab 100644
--- a/server/src/events/inventory_manip.rs
+++ b/server/src/events/inventory_manip.rs
@@ -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,
     }
 }
diff --git a/server/src/settings.rs b/server/src/settings.rs
index 379ca1d15e..759773b62a 100644
--- a/server/src/settings.rs
+++ b/server/src/settings.rs
@@ -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")
                     }
diff --git a/server/src/settings/editable.rs b/server/src/settings/editable.rs
index ce8ff36431..9b1c451fd1 100644
--- a/server/src/settings/editable.rs
+++ b/server/src/settings/editable.rs
@@ -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.");
                     }
diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml
index 439574c43f..3711dbb952 100644
--- a/voxygen/Cargo.toml
+++ b/voxygen/Cargo.toml
@@ -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}
diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs
index 907a04857f..a0496cadf5 100644
--- a/voxygen/src/hud/mod.rs
+++ b/voxygen/src/hud/mod.rs
@@ -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,
+    })
+}
diff --git a/voxygen/src/hud/overitem.rs b/voxygen/src/hud/overitem.rs
index 6ba6823dac..f45366176e 100644
--- a/voxygen/src/hud/overitem.rs
+++ b/voxygen/src/hud/overitem.rs
@@ -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;
diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs
index 06b672bcb2..80794f9134 100644
--- a/voxygen/src/main.rs
+++ b/voxygen/src/main.rs
@@ -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| {
diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs
index b996ba2e2d..a9a0d88718 100644
--- a/voxygen/src/menu/main/mod.rs
+++ b/voxygen/src/menu/main/mod.rs
@@ -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());
     }
 }
diff --git a/voxygen/src/scene/terrain/watcher.rs b/voxygen/src/scene/terrain/watcher.rs
index b589e09742..23989f305b 100644
--- a/voxygen/src/scene/terrain/watcher.rs
+++ b/voxygen/src/scene/terrain/watcher.rs
@@ -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,
         }
     }
 }
diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs
index d0b3d3a8c9..f99d8890c4 100644
--- a/voxygen/src/session.rs
+++ b/voxygen/src/session.rs
@@ -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)))
+                })
+        })
+}
diff --git a/voxygen/src/singleplayer.rs b/voxygen/src/singleplayer.rs
index e60064ffce..b9bd860142 100644
--- a/voxygen/src/singleplayer.rs
+++ b/voxygen/src/singleplayer.rs
@@ -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) }