diff --git a/CHANGELOG.md b/CHANGELOG.md index d2a842d4df..66cec07206 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Training dummy items - Added spin attack for axe - Creature specific stats +- Minimap compass ### Changed diff --git a/common/src/path.rs b/common/src/path.rs index 4d91693f2e..cf8e6b8e73 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -4,6 +4,7 @@ use crate::{ vol::{BaseVol, ReadVol}, }; use hashbrown::hash_map::DefaultHashBuilder; +use rand::prelude::*; use std::iter::FromIterator; use vek::*; @@ -71,11 +72,12 @@ impl Route { vel: Vec3, on_ground: bool, traversal_tolerance: f32, + slow_factor: f32, ) -> Option<(Vec3, f32)> where V: BaseVol + ReadVol, { - let (next0, next1, next_tgt) = loop { + let (next0, next1, next_tgt, be_precise) = loop { let next0 = self .next(0) .unwrap_or_else(|| pos.map(|e| e.floor() as i32)); @@ -85,28 +87,57 @@ impl Route { return None; } + let diagonals = [ + Vec2::new(1, 0), + Vec2::new(1, 1), + Vec2::new(0, 1), + Vec2::new(-1, 1), + Vec2::new(-1, 0), + Vec2::new(-1, -1), + Vec2::new(0, -1), + Vec2::new(1, -1), + ]; + let next1 = self.next(1).unwrap_or(next0); - let next_tgt = next0.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0); + + let be_precise = diagonals.iter().any(|pos| { + !walkable(vol, next0 + Vec3::new(pos.x, pos.y, 0)) + && !walkable(vol, next0 + Vec3::new(pos.x, pos.y, -1)) + && !walkable(vol, next0 + Vec3::new(pos.x, pos.y, -2)) + && !walkable(vol, next0 + Vec3::new(pos.x, pos.y, 1)) + }); + + let next0_tgt = next0.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0); + let next1_tgt = next1.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0); + let next_tgt = next0_tgt; + + // Maybe skip a node (useful with traversing downhill) + let closest_tgt = if next0_tgt.distance_squared(pos) < next1_tgt.distance_squared(pos) { + next0_tgt + } else { + next1_tgt + }; // Determine whether we're close enough to the next to to consider it completed - if pos.xy().distance_squared(next_tgt.xy()) < traversal_tolerance.powf(2.0) - && (pos.z - next_tgt.z > 1.2 || (pos.z - next_tgt.z > -0.2 && on_ground)) - && pos.z - next_tgt.z < 2.2 + let dist_sqrd = pos.xy().distance_squared(closest_tgt.xy()); + if dist_sqrd < traversal_tolerance.powf(2.0) * if be_precise { 0.25 } else { 1.0 } + && (pos.z - closest_tgt.z > 1.2 || (pos.z - closest_tgt.z > -0.2 && on_ground)) + && (pos.z - closest_tgt.z < 1.2 || (pos.z - closest_tgt.z < 2.9 && vel.z < -0.05)) && vel.z <= 0.0 // Only consider the node reached if there's nothing solid between us and it && vol - .ray(pos + Vec3::unit_z() * 1.5, next_tgt + Vec3::unit_z() * 1.5) + .ray(pos + Vec3::unit_z() * 1.5, closest_tgt + Vec3::unit_z() * 1.5) .until(|block| block.is_solid()) .cast() .0 - > pos.distance(next_tgt) * 0.9 + > pos.distance(closest_tgt) * 0.9 && self.next_idx < self.path.len() { // Node completed, move on to the next one self.next_idx += 1; } else { // The next node hasn't been reached yet, use it as a target - break (next0, next1, next_tgt); + break (next0, next1, next_tgt, be_precise); } }; @@ -157,38 +188,56 @@ impl Route { }; let align = |block_pos: Vec3, precision: f32| { - let lerp_block = |x, precision| Lerp::lerp(x, block_pos.xy().map(|e| e as f32), precision); + let lerp_block = + |x, precision| Lerp::lerp(x, block_pos.xy().map(|e| e as f32), precision); (0..4) .filter_map(|i| { let edge_line = LineSegment2 { - start: lerp_block((block_pos.xy() + corners[i]).map(|e| e as f32), precision), - end: lerp_block((block_pos.xy() + corners[i + 1]).map(|e| e as f32), precision), + start: lerp_block( + (block_pos.xy() + corners[i]).map(|e| e as f32), + precision, + ), + end: lerp_block( + (block_pos.xy() + corners[i + 1]).map(|e| e as f32), + precision, + ), }; - intersect(vel_line, edge_line) - .filter(|intersect| intersect.clamped( - block_pos.xy().map(|e| e as f32), - block_pos.xy().map(|e| e as f32 + 1.0), - ).distance_squared(*intersect) < 0.001) + intersect(vel_line, edge_line).filter(|intersect| { + intersect + .clamped( + block_pos.xy().map(|e| e as f32), + block_pos.xy().map(|e| e as f32 + 1.0), + ) + .distance_squared(*intersect) + < 0.001 + }) }) - .min_by_key(|intersect: &Vec2| (intersect.distance_squared(vel_line.end) * 1000.0) as i32) - .unwrap_or_else(|| (0..2) - .map(|i| (0..2).map(move |j| Vec2::new(i, j))) + .min_by_key(|intersect: &Vec2| { + (intersect.distance_squared(vel_line.end) * 1000.0) as i32 + }) + .unwrap_or_else(|| { + (0..2) + .map(|i| (0..2).map(move |j| Vec2::new(i, j))) .flatten() .map(|rpos| block_pos + rpos) .map(|block_pos| { let block_posf = block_pos.xy().map(|e| e as f32); let proj = vel_line.projected_point(block_posf); - let clamped = lerp_block(proj.clamped( - block_pos.xy().map(|e| e as f32), - block_pos.xy().map(|e| e as f32), - ), precision); + let clamped = lerp_block( + proj.clamped( + block_pos.xy().map(|e| e as f32), + block_pos.xy().map(|e| e as f32), + ), + precision, + ); (proj.distance_squared(clamped), clamped) }) .min_by_key(|(d2, _)| (d2 * 1000.0) as i32) .unwrap() - .1) + .1 + }) }; let bez = CubicBezier2 { @@ -214,21 +263,38 @@ impl Route { let bez = CubicBezier2 { start: pos.xy(), ctrl0: pos.xy() + vel.xy().try_normalized().unwrap_or(Vec2::zero()) * 1.0, - ctrl1: align(next0, (1.0 - straight_factor * if (next0.z as f32 - pos.z).abs() < 0.25 { 1.0 } else { 0.0 }).max(0.1)), + ctrl1: align( + next0, + (1.0 - straight_factor + * if (next0.z as f32 - pos.z).abs() < 0.25 && !be_precise { + 1.0 + } else { + 0.0 + }) + .max(0.1), + ), end: align(next1, 1.0), }; - let tgt2d = bez.evaluate(if (next0.z as f32 - pos.z).abs() < 0.25 { 0.25 } else { 0.5 }); - let tgt = Vec3::from(tgt2d) + Vec3::unit_z() * next_tgt.z; + let tgt2d = bez.evaluate(if (next0.z as f32 - pos.z).abs() < 0.25 { + 0.25 + } else { + 0.5 + }); + let tgt = if be_precise { + next_tgt + } else { + Vec3::from(tgt2d) + Vec3::unit_z() * next_tgt.z + }; Some(( tgt - pos, // Control the entity's speed to hopefully stop us falling off walls on sharp corners. // This code is very imperfect: it does its best but it can still fail for particularly // fast entities. - straight_factor * 0.75 + 0.25, + straight_factor * slow_factor + (1.0 - slow_factor), )) - .filter(|(bearing, _)| bearing.z < 2.1) + .filter(|(bearing, _)| bearing.z < 2.1) } } @@ -255,6 +321,7 @@ impl Chaser { tgt: Vec3, min_dist: f32, traversal_tolerance: f32, + slow_factor: f32, ) -> Option<(Vec3, f32)> where V: BaseVol + ReadVol, @@ -282,13 +349,14 @@ impl Chaser { } else { self.route .as_mut() - .and_then(|r| r.traverse(vol, pos, vel, on_ground, traversal_tolerance)) + .and_then(|r| r.traverse(vol, pos, vel, on_ground, traversal_tolerance, slow_factor)) // In theory this filter isn't needed, but in practice agents often try to take // stale paths that start elsewhere. This code makes sure that we're only using // paths that start near us, avoiding the agent doubling back to chase a stale // path. .filter(|(bearing, _)| bearing.xy() - .magnitude_squared() < (traversal_tolerance * 3.0).powf(2.0)) + .magnitude_squared() < 1.75f32.powf(2.0) + && thread_rng().gen::() > 0.025) } } else { None @@ -321,6 +389,24 @@ impl Chaser { } } +#[allow(clippy::float_cmp)] // TODO: Pending review in #587 +fn walkable(vol: &V, pos: Vec3) -> bool +where + V: BaseVol + ReadVol, +{ + vol.get(pos - Vec3::new(0, 0, 1)) + .map(|b| b.is_solid() && b.get_height() == 1.0) + .unwrap_or(false) + && vol + .get(pos + Vec3::new(0, 0, 0)) + .map(|b| !b.is_solid()) + .unwrap_or(true) + && vol + .get(pos + Vec3::new(0, 0, 1)) + .map(|b| !b.is_solid()) + .unwrap_or(true) +} + #[allow(clippy::float_cmp)] // TODO: Pending review in #587 fn find_path( astar: &mut Option, DefaultHashBuilder>>, @@ -331,19 +417,7 @@ fn find_path( where V: BaseVol + ReadVol, { - let is_walkable = |pos: &Vec3| { - vol.get(*pos - Vec3::new(0, 0, 1)) - .map(|b| b.is_solid() && b.get_height() == 1.0) - .unwrap_or(false) - && vol - .get(*pos + Vec3::new(0, 0, 0)) - .map(|b| !b.is_solid()) - .unwrap_or(true) - && vol - .get(*pos + Vec3::new(0, 0, 1)) - .map(|b| !b.is_solid()) - .unwrap_or(true) - }; + let is_walkable = |pos: &Vec3| walkable(vol, *pos); let get_walkable_z = |pos| { let mut z_incr = 0; for _ in 0..32 { @@ -437,12 +511,12 @@ where // ) }; - let crow_line = LineSegment2 { - start: startf.xy(), - end: endf.xy(), - }; - let transition = |a: &Vec3, b: &Vec3| { + let crow_line = LineSegment2 { + start: startf.xy(), + end: endf.xy(), + }; + // Modify the heuristic a little in order to prefer paths that take us on a // straight line toward our target. This means we get smoother movement. 1.0 + crow_line.distance_to_point(b.xy().map(|e| e as f32)) * 0.025 diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 7ebec30dc4..ce01809f80 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -31,9 +31,9 @@ impl Body { pub fn base_accel(&self) -> f32 { match self { Body::Humanoid(_) => 100.0, - Body::QuadrupedSmall(_) => 80.0, + Body::QuadrupedSmall(_) => 85.0, Body::QuadrupedMedium(_) => 180.0, - Body::BirdMedium(_) => 70.0, + Body::BirdMedium(_) => 80.0, Body::FishMedium(_) => 50.0, Body::Dragon(_) => 250.0, Body::BirdSmall(_) => 75.0, @@ -41,7 +41,7 @@ impl Body { Body::BipedLarge(_) => 120.0, Body::Object(_) => 40.0, Body::Golem(_) => 130.0, - Body::Critter(_) => 65.0, + Body::Critter(_) => 85.0, Body::QuadrupedLow(_) => 120.0, } } diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 0a3797dea1..aa6159f0a9 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -3,8 +3,8 @@ use crate::{ self, agent::Activity, item::{tool::ToolKind, ItemKind}, - Agent, Alignment, CharacterState, ChatMsg, ControlAction, Controller, Loadout, MountState, - Ori, Pos, Scale, Stats, Vel, PhysicsState, + Agent, Alignment, Body, CharacterState, ChatMsg, ControlAction, Controller, Loadout, + MountState, Ori, PhysicsState, Pos, Scale, Stats, Vel, }, event::{EventBus, ServerEvent}, path::Chaser, @@ -42,6 +42,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Uid>, ReadExpect<'a, TerrainGrid>, ReadStorage<'a, Alignment>, + ReadStorage<'a, Body>, WriteStorage<'a, Agent>, WriteStorage<'a, Controller>, ReadStorage<'a, MountState>, @@ -67,6 +68,7 @@ impl<'a> System<'a> for Sys { uids, terrain, alignments, + bodies, mut agents, mut controllers, mount_states, @@ -81,6 +83,7 @@ impl<'a> System<'a> for Sys { loadout, character_state, physics_state, + body, uid, agent, controller, @@ -94,6 +97,7 @@ impl<'a> System<'a> for Sys { &loadouts, &character_states, &physics_states, + bodies.maybe(), &uids, &mut agents, &mut controllers, @@ -131,6 +135,7 @@ impl<'a> System<'a> for Sys { // the world (especially since they would otherwise get stuck on // obstacles that smaller entities would not). let traversal_tolerance = scale + vel.0.xy().magnitude() * 0.2; + let slow_factor = body.map(|b| b.base_accel() / 250.0).unwrap_or(0.0).min(1.0); let mut do_idle = false; let mut choose_target = false; @@ -206,6 +211,7 @@ impl<'a> System<'a> for Sys { tgt_pos.0, AVG_FOLLOW_DIST, traversal_tolerance, + slow_factor, ) { inputs.move_dir = bearing.xy().try_normalized().unwrap_or(Vec2::zero()) @@ -323,6 +329,7 @@ impl<'a> System<'a> for Sys { tgt_pos.0, 1.25, traversal_tolerance, + slow_factor, ) { inputs.move_dir = Vec2::from(bearing) .try_normalized() diff --git a/voxygen/src/hud/minimap.rs b/voxygen/src/hud/minimap.rs index 3a0225539b..efc330ce7b 100644 --- a/voxygen/src/hud/minimap.rs +++ b/voxygen/src/hud/minimap.rs @@ -8,7 +8,7 @@ use common::{comp, terrain::TerrainChunkSize, vol::RectVolSize}; use conrod_core::{ color, position, widget::{self, Button, Image, Rectangle, Text}, - widget_ids, Colorable, Positionable, Sizeable, Widget, WidgetCommon, + widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, }; use specs::WorldExt; use vek::*; @@ -23,7 +23,11 @@ widget_ids! { mmap_plus, mmap_minus, grid, - indicator + indicator, + mmap_north, + mmap_east, + mmap_south, + mmap_west, } } @@ -39,6 +43,7 @@ pub struct MiniMap<'a> { fonts: &'a ConrodVoxygenFonts, #[conrod(common_builder)] common: widget::CommonBuilder, + ori: Vec3, } impl<'a> MiniMap<'a> { @@ -49,6 +54,7 @@ impl<'a> MiniMap<'a> { rot_imgs: &'a ImgsRot, world_map: &'a (img_ids::Rotations, Vec2), fonts: &'a ConrodVoxygenFonts, + ori: Vec3, ) -> Self { Self { show, @@ -58,6 +64,7 @@ impl<'a> MiniMap<'a> { world_map, fonts, common: widget::CommonBuilder::default(), + ori, } } } @@ -195,10 +202,12 @@ impl<'a> Widget for MiniMap<'a> { [w_src, h_src], ); + let map_size = Vec2::new(170.0, 170.0); + // Map Image Image::new(world_map.source_north) .middle_of(state.ids.mmap_frame_bg) - .w_h(170.0 * SCALE, 170.0 * SCALE) + .w_h(map_size.x * SCALE, map_size.y * SCALE) .parent(state.ids.mmap_frame_bg) .source_rectangle(rect_src) .set(state.ids.grid, ui); @@ -212,6 +221,37 @@ impl<'a> Widget for MiniMap<'a> { .floating(true) .parent(ui.window) .set(state.ids.indicator, ui); + + // Compass directions + let dirs = [ + (Vec2::new(0.0, 1.0), state.ids.mmap_north, "N", true), + (Vec2::new(1.0, 0.0), state.ids.mmap_east, "E", false), + (Vec2::new(0.0, -1.0), state.ids.mmap_south, "S", false), + (Vec2::new(-1.0, 0.0), state.ids.mmap_west, "W", false), + ]; + for (dir, id, name, bold) in dirs.iter() { + let cardinal_dir = Vec2::unit_x().rotated_z(self.ori.x as f64) * dir.x + + Vec2::unit_y().rotated_z(self.ori.x as f64) * dir.y; + let clamped = (cardinal_dir * 3.0) + / (cardinal_dir * 3.0).map(|e| e.abs()).reduce_partial_max(); + let pos = clamped * (map_size * 0.75 - 10.0); + Text::new(name) + .x_y_position_relative_to( + state.ids.grid, + position::Relative::Scalar(pos.x), + position::Relative::Scalar(pos.y), + ) + .font_size(self.fonts.cyri.scale(18)) + .font_id(self.fonts.cyri.conrod_id) + .color(if *bold { + Color::Rgba(0.7, 0.3, 0.3, 1.0) + } else { + TEXT_COLOR + }) + .floating(true) + .parent(ui.window) + .set(*id, ui); + } } else { Image::new(self.imgs.mmap_frame_closed) .w_h(174.0 * SCALE, 18.0 * SCALE) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index da04cedf2f..2768a7ab4b 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -598,6 +598,7 @@ impl Hud { debug_info: DebugInfo, dt: Duration, info: HudInfo, + camera: &Camera, ) -> Vec { let mut events = std::mem::replace(&mut self.events, Vec::new()); let (ref mut ui_widgets, ref mut tooltip_manager) = self.ui.set_widgets(); @@ -1497,6 +1498,7 @@ impl Hud { &self.rot_imgs, &self.world_map, &self.fonts, + camera.get_orientation(), ) .set(self.ids.minimap, ui_widgets) { @@ -2246,7 +2248,7 @@ impl Hud { if let Some(maybe_id) = self.to_focus.take() { self.ui.focus_widget(maybe_id); } - let events = self.update_layout(client, global_state, debug_info, dt, info); + let events = self.update_layout(client, global_state, debug_info, dt, info, camera); let camera::Dependents { view_mat, proj_mat, .. } = camera.dependents();