From 4d057cb74f52f589f3a12cd8203dddeb0a6cc3c4 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Mon, 14 Dec 2020 15:03:22 -0800 Subject: [PATCH] Improve the out-of-energy arrow: - point to the driveway, not the building center - scale arrow length so it doesn't overshoot where we're supposed to go - explicitly say to go refill from a store --- experiment/src/after_level.rs | 2 + experiment/src/game.rs | 67 +++++++++++++++-------- experiment/src/levels.rs | 2 +- experiment/src/lib.rs | 2 +- experiment/src/{movement.rs => player.rs} | 0 experiment/src/vehicles.rs | 2 +- geom/src/polyline.rs | 18 ++++-- 7 files changed, 64 insertions(+), 29 deletions(-) rename experiment/src/{movement.rs => player.rs} (100%) diff --git a/experiment/src/after_level.rs b/experiment/src/after_level.rs index 059cec2a2b..6dbba05e9f 100644 --- a/experiment/src/after_level.rs +++ b/experiment/src/after_level.rs @@ -97,6 +97,8 @@ impl Strategize { ColorLegend::row(ctx, app.session.colors.house, "house"), ColorLegend::row(ctx, app.session.colors.apartment, "apartment"), ColorLegend::row(ctx, app.session.colors.store, "store"), + ]), + Widget::row(vec![ ColorLegend::row(ctx, Color::PINK, "upzoned store"), ColorLegend::row(ctx, Color::RED, "delivered!"), ]) diff --git a/experiment/src/game.rs b/experiment/src/game.rs index 586b79433c..b26e67fc70 100644 --- a/experiment/src/game.rs +++ b/experiment/src/game.rs @@ -14,7 +14,7 @@ use crate::animation::{Animator, Effect, SnowEffect}; use crate::buildings::{BldgState, Buildings}; use crate::levels::Level; use crate::meters::{custom_bar, make_bar}; -use crate::movement::Player; +use crate::player::Player; use crate::vehicles::Vehicle; use crate::{App, Transition}; @@ -63,7 +63,7 @@ impl Game { let status_panel = Panel::new(Widget::col(vec![ "Complete Deliveries".draw_text(ctx).named("score label"), Widget::draw_batch(ctx, GeomBatch::new()).named("score"), - "Remaining Gifts".draw_text(ctx), + "Remaining Gifts".draw_text(ctx).named("energy label"), Widget::draw_batch(ctx, GeomBatch::new()).named("energy"), ])) .aligned(HorizontalAlignment::RightInset, VerticalAlignment::TopInset) @@ -296,18 +296,27 @@ impl Game { self.animator.event(ctx, app.time); self.snow.event(ctx, app.time); if self.state.has_energy() { - self.state.energyless_arrow = None; + if self.state.energyless_arrow.is_some() { + self.state.energyless_arrow = None; + let label = "Remaining Gifts".draw_text(ctx); + self.status_panel.replace(ctx, "energy label", label); + } } else { if self.state.energyless_arrow.is_none() { - self.state.energyless_arrow = Some(EnergylessArrow::new(ctx, app.time)); + self.state.energyless_arrow = Some(EnergylessArrow::new( + ctx, + app.time, + self.state.bldgs.all_stores(), + )); + let label = Text::from(Line("Out of gifts - refill from a store!").fg(Color::RED)) + .draw(ctx); + self.status_panel.replace(ctx, "energy label", label); } - let stores = self.state.bldgs.all_stores(); - self.state.energyless_arrow.as_mut().unwrap().update( - ctx, - app, - self.player.get_pos(), - stores, - ); + self.state + .energyless_arrow + .as_mut() + .unwrap() + .update(ctx, app, self.player.get_pos()); } if self.state.boost != orig_boost { @@ -534,28 +543,33 @@ struct EnergylessArrow { draw: Drawable, started: Time, last_update: Time, + all_stores: Vec, } impl EnergylessArrow { - fn new(ctx: &EventCtx, started: Time) -> EnergylessArrow { + fn new(ctx: &EventCtx, started: Time, all_stores: Vec) -> EnergylessArrow { EnergylessArrow { draw: Drawable::empty(ctx), started, last_update: Time::START_OF_DAY, + all_stores, } } - fn update(&mut self, ctx: &mut EventCtx, app: &App, sleigh: Pt2D, all_stores: Vec) { + fn update(&mut self, ctx: &mut EventCtx, app: &App, sleigh: Pt2D) { if self.last_update == app.time { return; } self.last_update = app.time; - // Find the closest store as the crow -- or Santa -- flies + // Find the closest store as the crow -- or Santa -- flies. Point to the end of the + // driveway, since sometimes it's hard to quickly spot which road a building is connected + // to. // TODO Or pathfind and show them that? let store = app.map.get_b( - all_stores - .into_iter() - .min_by_key(|b| app.map.get_b(*b).label_center.fast_dist(sleigh)) + *self + .all_stores + .iter() + .min_by_key(|b| app.map.get_b(**b).driveway_geom.last_pt().fast_dist(sleigh)) .unwrap(), ); @@ -566,13 +580,22 @@ impl EnergylessArrow { let shift = (pct * std::f64::consts::PI).sin(); let thickness = Distance::meters(5.0 + shift); - let angle = sleigh.angle_to(store.label_center); - let arrow = PolyLine::must_new(vec![ + let goto = store.driveway_geom.last_pt(); + let angle = sleigh.angle_to(goto); + // TODO When we're too close, we get an awkward arrowcap; the intention was for it to + // disappear... + if let Some(arrow) = PolyLine::new(vec![ sleigh.project_away(Distance::meters(20.0), angle), - sleigh.project_away(Distance::meters(40.0), angle), + goto, ]) - .make_arrow(thickness, ArrowCap::Triangle); - self.draw = ctx.upload(GeomBatch::from(vec![(Color::RED.alpha(0.8), arrow)])); + .and_then(|pl| { + pl.maybe_exact_slice(Distance::ZERO, Distance::meters(20.0).min(pl.length())) + }) + .ok() + .and_then(|slice| slice.maybe_make_arrow(thickness, ArrowCap::Triangle)) + { + self.draw = ctx.upload(GeomBatch::from(vec![(Color::RED.alpha(0.8), arrow)])); + } } } diff --git a/experiment/src/levels.rs b/experiment/src/levels.rs index d32826711f..01eace1ab9 100644 --- a/experiment/src/levels.rs +++ b/experiment/src/levels.rs @@ -29,7 +29,7 @@ impl Level { map: MapName::seattle("montlake"), start: osm::NodeID(53084814), minimap_zoom: 1, - time_limit: Duration::seconds(5.0), + time_limit: Duration::seconds(50.0), goal: 20, unlock_upzones: 2, diff --git a/experiment/src/lib.rs b/experiment/src/lib.rs index a9aec09732..c5966aa601 100644 --- a/experiment/src/lib.rs +++ b/experiment/src/lib.rs @@ -9,8 +9,8 @@ mod controls; mod game; mod levels; mod meters; -mod movement; mod music; +mod player; mod session; mod title; mod vehicles; diff --git a/experiment/src/movement.rs b/experiment/src/player.rs similarity index 100% rename from experiment/src/movement.rs rename to experiment/src/player.rs diff --git a/experiment/src/vehicles.rs b/experiment/src/vehicles.rs index 6c8898e89d..01867afce9 100644 --- a/experiment/src/vehicles.rs +++ b/experiment/src/vehicles.rs @@ -22,7 +22,7 @@ impl Vehicle { normal_speed: Speed::miles_per_hour(30.0), tired_speed: Speed::miles_per_hour(10.0), - max_energy: 80, + max_energy: 20, max_boost: Duration::seconds(5.0), draw_frames: vec!["sleigh.svg"], diff --git a/geom/src/polyline.rs b/geom/src/polyline.rs index 5bd1a91c79..cd0b14202c 100644 --- a/geom/src/polyline.rs +++ b/geom/src/polyline.rs @@ -519,13 +519,13 @@ impl PolyLine { .exact_dashed_polygons(width, dash_len, dash_separation) } - pub fn make_arrow(&self, thickness: Distance, cap: ArrowCap) -> Polygon { + /// Fail if the length is too short. + pub fn maybe_make_arrow(&self, thickness: Distance, cap: ArrowCap) -> Option { let head_size = thickness * 2.0; let triangle_height = head_size / 2.0_f64.sqrt(); if self.length() < triangle_height + EPSILON_DIST { - // Just give up and make the thick line. - return self.make_polygons(thickness); + return None; } let slice = self.exact_slice(Distance::ZERO, self.length() - triangle_height); @@ -550,7 +550,17 @@ impl PolyLine { pts.extend(side2); pts.push(pts[0]); pts.dedup(); - Ring::must_new(pts).to_polygon() + Some(Ring::must_new(pts).to_polygon()) + } + + /// If the length is too short, just give up and make the thick line + pub fn make_arrow(&self, thickness: Distance, cap: ArrowCap) -> Polygon { + if let Some(p) = self.maybe_make_arrow(thickness, cap) { + p + } else { + // Just give up and make the thick line. + self.make_polygons(thickness) + } } pub fn make_double_arrow(&self, thickness: Distance, cap: ArrowCap) -> Polygon {