mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-24 17:37:22 +03:00
Overhaul how buildings get colored in different states, and reset things
when changing depots
This commit is contained in:
parent
02d5e3cea3
commit
f030086e8e
@ -1,14 +1,14 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use abstutil::{prettyprint_usize, Timer};
|
||||
use abstutil::prettyprint_usize;
|
||||
use geom::{Circle, Distance, Duration, Line, Polygon, Pt2D, Speed};
|
||||
use map_gui::tools::{nice_map_name, CityPicker, ColorScale, SimpleMinimap};
|
||||
use map_gui::tools::{nice_map_name, CityPicker, ColorScale, DivergingScale, SimpleMinimap};
|
||||
use map_gui::{Cached, SimpleApp, ID};
|
||||
use map_model::{BuildingID, BuildingType, PathConstraints};
|
||||
use widgetry::{
|
||||
lctrl, Btn, Checkbox, Color, Drawable, EventCtx, Fill, GeomBatch, GfxCtx, HorizontalAlignment,
|
||||
Key, Line, LinearGradient, Outcome, Panel, State, Text, TextExt, Transition, UpdateType,
|
||||
VerticalAlignment, Widget,
|
||||
Key, Line, LinearGradient, Outcome, Panel, RewriteColor, State, Text, TextExt, Transition,
|
||||
UpdateType, VerticalAlignment, Widget,
|
||||
};
|
||||
|
||||
use crate::animation::Animator;
|
||||
@ -28,11 +28,7 @@ pub struct Game {
|
||||
}
|
||||
|
||||
impl Game {
|
||||
pub fn new(
|
||||
ctx: &mut EventCtx,
|
||||
app: &SimpleApp,
|
||||
timer: &mut Timer,
|
||||
) -> Box<dyn State<SimpleApp>> {
|
||||
pub fn new(ctx: &mut EventCtx, app: &SimpleApp) -> Box<dyn State<SimpleApp>> {
|
||||
ctx.canvas.cam_zoom = ZOOM;
|
||||
|
||||
// Start on a commerical building
|
||||
@ -47,7 +43,7 @@ impl Game {
|
||||
.unwrap();
|
||||
let sleigh = depot.label_center;
|
||||
ctx.canvas.center_on_map_pt(sleigh);
|
||||
let state = SleighState::new(ctx, app, depot.id, timer);
|
||||
let state = SleighState::new(ctx, app, depot.id);
|
||||
|
||||
let panel = Panel::new(Widget::col(vec![
|
||||
Widget::row(vec![
|
||||
@ -135,8 +131,7 @@ impl Game {
|
||||
self.state.upzones_used += 1;
|
||||
self.state.houses.insert(b, BldgState::Depot);
|
||||
self.state.depot = b;
|
||||
self.state.redraw(ctx, app);
|
||||
self.state.redraw_depots(ctx, app);
|
||||
self.state.recalc_depots(ctx, app);
|
||||
self.sleigh = app.map.get_b(b).label_center;
|
||||
ctx.canvas.cam_zoom = ZOOM;
|
||||
ctx.canvas.center_on_map_pt(self.sleigh);
|
||||
@ -148,11 +143,13 @@ impl State<SimpleApp> for Game {
|
||||
let mut recharging = false;
|
||||
if let Some(dt) = ctx.input.nonblocking_is_update_event() {
|
||||
if let Some(b) = self.over_bldg.key() {
|
||||
if ctx.is_key_down(Key::Space) && self.state.recharge(ctx, app, b, dt) {
|
||||
self.state.depot = b;
|
||||
self.state.redraw(ctx, app);
|
||||
if ctx.is_key_down(Key::Space) && self.state.recharge(b, dt) {
|
||||
self.update_panel(ctx);
|
||||
recharging = true;
|
||||
if self.state.depot != b {
|
||||
self.state.depot = b;
|
||||
self.state.recalc_depots(ctx, app);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,10 +170,15 @@ impl State<SimpleApp> for Game {
|
||||
self.sleigh = self.sleigh.offset(dx, dy);
|
||||
ctx.canvas.center_on_map_pt(self.sleigh);
|
||||
|
||||
let key = OverBldg::key(app, self.sleigh, &self.state);
|
||||
let is_depot = key
|
||||
.map(|b| match self.state.houses.get(&b) {
|
||||
Some(BldgState::Depot) => true,
|
||||
_ => false,
|
||||
})
|
||||
.unwrap_or(false);
|
||||
self.over_bldg
|
||||
.update(OverBldg::key(app, self.sleigh, &self.state), |key| {
|
||||
OverBldg::value(ctx, app, key)
|
||||
});
|
||||
.update(key, |key| OverBldg::value(ctx, app, key, is_depot));
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,12 +215,10 @@ impl State<SimpleApp> for Game {
|
||||
ctx,
|
||||
app,
|
||||
Box::new(|ctx, app| {
|
||||
ctx.loading_screen("setup again", |ctx, mut timer| {
|
||||
Transition::Multi(vec![
|
||||
Transition::Pop,
|
||||
Transition::Replace(Game::new(ctx, app, &mut timer)),
|
||||
])
|
||||
})
|
||||
Transition::Multi(vec![
|
||||
Transition::Pop,
|
||||
Transition::Replace(Game::new(ctx, app)),
|
||||
])
|
||||
}),
|
||||
));
|
||||
}
|
||||
@ -228,7 +228,7 @@ impl State<SimpleApp> for Game {
|
||||
.houses
|
||||
.iter()
|
||||
.filter_map(|(id, state)| match state {
|
||||
BldgState::Undelivered { .. } => Some(*id),
|
||||
BldgState::Undelivered(_) => Some(*id),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
@ -253,18 +253,24 @@ impl State<SimpleApp> for Game {
|
||||
fn draw(&self, g: &mut GfxCtx, app: &SimpleApp) {
|
||||
self.panel.draw(g);
|
||||
if self.state.has_energy() {
|
||||
self.minimap
|
||||
.draw_with_extra_layer(g, app, Some(&self.state.draw_done));
|
||||
self.minimap.draw_with_extra_layers(
|
||||
g,
|
||||
app,
|
||||
vec![&self.state.draw_todo_houses, &self.state.draw_done_houses],
|
||||
);
|
||||
} else {
|
||||
self.minimap
|
||||
.draw_with_extra_layer(g, app, Some(&self.state.draw_all_depots));
|
||||
.draw_with_extra_layers(g, app, vec![&self.state.draw_all_depots]);
|
||||
}
|
||||
|
||||
g.redraw(&self.state.draw_scores);
|
||||
g.redraw(&self.state.draw_done);
|
||||
g.redraw(&self.state.draw_todo_houses);
|
||||
g.redraw(&self.state.draw_done_houses);
|
||||
if let Some(draw) = self.over_bldg.value() {
|
||||
g.redraw(&draw.0);
|
||||
}
|
||||
if !self.state.has_energy() {
|
||||
g.redraw(&self.state.draw_all_depots);
|
||||
}
|
||||
g.draw_polygon(
|
||||
Color::RED,
|
||||
Circle::new(self.sleigh, Distance::meters(5.0)).to_polygon(),
|
||||
@ -282,61 +288,31 @@ struct Config {
|
||||
}
|
||||
|
||||
struct SleighState {
|
||||
depot: BuildingID,
|
||||
config: Config,
|
||||
|
||||
score: usize,
|
||||
upzones_used: usize,
|
||||
energy: Duration,
|
||||
houses: HashMap<BuildingID, BldgState>,
|
||||
draw_scores: Drawable,
|
||||
draw_done: Drawable,
|
||||
config: Config,
|
||||
|
||||
depot: BuildingID,
|
||||
cost_to_house: HashMap<BuildingID, Duration>,
|
||||
|
||||
upzones_used: usize,
|
||||
upzoned_depots: Vec<BuildingID>,
|
||||
|
||||
draw_all_depots: Drawable,
|
||||
// This gets covered up by draw_done_houses, instead of an expensive update
|
||||
draw_todo_houses: Drawable,
|
||||
draw_done_houses: Drawable,
|
||||
}
|
||||
|
||||
impl SleighState {
|
||||
fn new(
|
||||
ctx: &mut EventCtx,
|
||||
app: &SimpleApp,
|
||||
depot: BuildingID,
|
||||
timer: &mut Timer,
|
||||
) -> SleighState {
|
||||
timer.start("calculate costs from depot");
|
||||
let house_costs = map_model::connectivity::all_costs_from(
|
||||
&app.map,
|
||||
depot,
|
||||
Duration::hours(3),
|
||||
PathConstraints::Pedestrian,
|
||||
);
|
||||
timer.stop("calculate costs from depot");
|
||||
|
||||
fn new(ctx: &mut EventCtx, app: &SimpleApp, depot: BuildingID) -> SleighState {
|
||||
let mut houses = HashMap::new();
|
||||
let mut batch = GeomBatch::new();
|
||||
timer.start_iter("assign score to houses", app.map.all_buildings().len());
|
||||
for b in app.map.all_buildings() {
|
||||
timer.next();
|
||||
if let BuildingType::Residential(_) = b.bldg_type {
|
||||
let score = b.id.0;
|
||||
let cost = house_costs.get(&b.id).cloned().unwrap_or(Duration::ZERO);
|
||||
let color = if cost < Duration::minutes(5) {
|
||||
Color::GREEN
|
||||
} else if cost < Duration::minutes(15) {
|
||||
Color::YELLOW
|
||||
} else {
|
||||
Color::RED
|
||||
};
|
||||
|
||||
houses.insert(b.id, BldgState::Undelivered { score, cost });
|
||||
// TODO Very expensive
|
||||
batch.append(
|
||||
Text::from_multiline(vec![
|
||||
Line(format!("{}", score)),
|
||||
Line(format!("{}", cost)).fg(color),
|
||||
])
|
||||
.render_to_batch(ctx.prerender)
|
||||
.scale(0.1)
|
||||
.centered_on(b.label_center),
|
||||
);
|
||||
houses.insert(b.id, BldgState::Undelivered(score));
|
||||
} else if !b.amenities.is_empty() {
|
||||
// TODO Maybe just food?
|
||||
houses.insert(b.id, BldgState::Depot);
|
||||
@ -350,51 +326,100 @@ impl SleighState {
|
||||
max_energy: Duration::minutes(90),
|
||||
upzone_rate: 30_000,
|
||||
};
|
||||
|
||||
let energy = config.max_energy;
|
||||
let mut s = SleighState {
|
||||
depot,
|
||||
score: 0,
|
||||
upzones_used: 0,
|
||||
energy: config.max_energy,
|
||||
houses,
|
||||
draw_scores: ctx.upload(batch),
|
||||
draw_done: Drawable::empty(ctx),
|
||||
config,
|
||||
|
||||
score: 0,
|
||||
energy,
|
||||
houses,
|
||||
|
||||
depot,
|
||||
cost_to_house: HashMap::new(),
|
||||
|
||||
upzones_used: 0,
|
||||
upzoned_depots: Vec::new(),
|
||||
|
||||
draw_all_depots: Drawable::empty(ctx),
|
||||
draw_todo_houses: Drawable::empty(ctx),
|
||||
draw_done_houses: Drawable::empty(ctx),
|
||||
};
|
||||
s.redraw(ctx, app);
|
||||
s.redraw_depots(ctx, app);
|
||||
|
||||
s.recalc_depots(ctx, app);
|
||||
s.recalc_deliveries(ctx, app);
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
fn redraw(&mut self, ctx: &mut EventCtx, app: &SimpleApp) {
|
||||
fn recalc_depots(&mut self, ctx: &mut EventCtx, app: &SimpleApp) {
|
||||
let mut batch = GeomBatch::new();
|
||||
for (b, state) in &self.houses {
|
||||
if let BldgState::Done = state {
|
||||
batch.push(Color::BLACK, app.map.get_b(*b).polygon.clone());
|
||||
}
|
||||
}
|
||||
// TODO This doesnt seem to be working
|
||||
for b in &self.upzoned_depots {
|
||||
batch.push(
|
||||
app.cs.commerical_building,
|
||||
app.map.get_b(*b).polygon.clone(),
|
||||
);
|
||||
}
|
||||
batch.push(Color::GREEN, app.map.get_b(self.depot).polygon.clone());
|
||||
self.draw_done = ctx.upload(batch);
|
||||
}
|
||||
batch.append(
|
||||
GeomBatch::load_svg(ctx.prerender, "system/assets/tools/star.svg")
|
||||
.centered_on(app.map.get_b(self.depot).label_center)
|
||||
.color(RewriteColor::ChangeAll(Color::YELLOW)),
|
||||
);
|
||||
|
||||
fn redraw_depots(&mut self, ctx: &mut EventCtx, app: &SimpleApp) {
|
||||
self.cost_to_house = map_model::connectivity::all_costs_from(
|
||||
&app.map,
|
||||
self.depot,
|
||||
Duration::hours(3),
|
||||
PathConstraints::Pedestrian,
|
||||
);
|
||||
|
||||
let worst_duration = Duration::minutes(15);
|
||||
let cost_scale =
|
||||
DivergingScale::new(Color::hex("#5D9630"), Color::WHITE, Color::hex("#A32015"))
|
||||
.range(0.0, worst_duration.inner_seconds());
|
||||
|
||||
for b in app.map.all_buildings() {
|
||||
match self.houses.get(&b.id) {
|
||||
Some(BldgState::Undelivered(_)) => {
|
||||
if let Some(cost) = self.cost_to_house.get(&b.id) {
|
||||
let color = cost_scale.eval(cost.inner_seconds()).unwrap();
|
||||
batch.push(color, b.polygon.clone());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Some(BldgState::Depot) => continue,
|
||||
_ => {}
|
||||
}
|
||||
// If the house isn't reachable at all or it's not a depot or residence, just blank it
|
||||
// out
|
||||
batch.push(Color::BLACK, b.polygon.clone());
|
||||
}
|
||||
|
||||
self.draw_todo_houses = ctx.upload(batch);
|
||||
|
||||
// Now highlight all depots for when we run out
|
||||
let mut batch = GeomBatch::new();
|
||||
for b in &self.upzoned_depots {
|
||||
batch.push(Color::YELLOW, app.map.get_b(*b).polygon.clone());
|
||||
}
|
||||
for (b, state) in &self.houses {
|
||||
if let BldgState::Depot = state {
|
||||
batch.push(Color::RED, app.map.get_b(*b).polygon.clone());
|
||||
batch.push(Color::YELLOW, app.map.get_b(*b).polygon.clone());
|
||||
}
|
||||
}
|
||||
self.draw_all_depots = ctx.upload(batch);
|
||||
}
|
||||
|
||||
fn recalc_deliveries(&mut self, ctx: &mut EventCtx, app: &SimpleApp) {
|
||||
let mut batch = GeomBatch::new();
|
||||
for (b, state) in &self.houses {
|
||||
if let BldgState::Done = state {
|
||||
batch.push(Color::BLACK, app.map.get_b(*b).polygon.clone());
|
||||
}
|
||||
}
|
||||
self.draw_done_houses = ctx.upload(batch);
|
||||
}
|
||||
|
||||
// If something changed, return the update to the score
|
||||
fn present_dropped(
|
||||
&mut self,
|
||||
@ -402,28 +427,23 @@ impl SleighState {
|
||||
app: &SimpleApp,
|
||||
id: BuildingID,
|
||||
) -> Option<usize> {
|
||||
if let Some(BldgState::Undelivered { score, cost }) = self.houses.get(&id).cloned() {
|
||||
self.score += score;
|
||||
self.houses.insert(id, BldgState::Done);
|
||||
self.energy -= cost;
|
||||
self.redraw(ctx, app);
|
||||
return Some(score);
|
||||
if let Some(BldgState::Undelivered(score)) = self.houses.get(&id).cloned() {
|
||||
if let Some(cost) = self.cost_to_house.get(&id) {
|
||||
self.score += score;
|
||||
self.houses.insert(id, BldgState::Done);
|
||||
self.energy -= *cost;
|
||||
self.recalc_deliveries(ctx, app);
|
||||
return Some(score);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// True if state change
|
||||
fn recharge(
|
||||
&mut self,
|
||||
ctx: &mut EventCtx,
|
||||
app: &SimpleApp,
|
||||
id: BuildingID,
|
||||
dt: Duration,
|
||||
) -> bool {
|
||||
fn recharge(&mut self, id: BuildingID, dt: Duration) -> bool {
|
||||
if let Some(BldgState::Depot) = self.houses.get(&id) {
|
||||
self.energy += self.config.recharge_rate * dt;
|
||||
self.energy = self.energy.min(self.config.max_energy);
|
||||
self.redraw(ctx, app);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
@ -448,7 +468,8 @@ impl SleighState {
|
||||
|
||||
#[derive(Clone)]
|
||||
enum BldgState {
|
||||
Undelivered { score: usize, cost: Duration },
|
||||
// Score
|
||||
Undelivered(usize),
|
||||
Depot,
|
||||
Done,
|
||||
}
|
||||
@ -474,11 +495,20 @@ impl OverBldg {
|
||||
None
|
||||
}
|
||||
|
||||
fn value(ctx: &mut EventCtx, app: &SimpleApp, key: BuildingID) -> OverBldg {
|
||||
OverBldg(ctx.upload(GeomBatch::from(vec![(
|
||||
Color::YELLOW,
|
||||
app.map.get_b(key).polygon.clone(),
|
||||
)])))
|
||||
fn value(ctx: &mut EventCtx, app: &SimpleApp, key: BuildingID, is_depot: bool) -> OverBldg {
|
||||
let mut batch = GeomBatch::new();
|
||||
// We only want to highlight when we're hovering over a depot and could recharge
|
||||
if is_depot {
|
||||
let polygon = &app.map.get_b(key).polygon;
|
||||
batch.push(
|
||||
Color::YELLOW,
|
||||
polygon
|
||||
.to_outline(Distance::meters(0.5))
|
||||
.unwrap_or_else(|_| polygon.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
OverBldg(ctx.upload(batch))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,9 +8,7 @@ fn main() {
|
||||
let mut opts = map_gui::options::Options::default();
|
||||
opts.color_scheme = map_gui::colors::ColorSchemeChoice::NightMode;
|
||||
let app = map_gui::SimpleApp::new_with_opts(ctx, abstutil::CmdArgs::new(), opts);
|
||||
let states = ctx.loading_screen("setup", |ctx, mut timer| {
|
||||
vec![game::Game::new(ctx, &app, &mut timer)]
|
||||
});
|
||||
let states = vec![game::Game::new(ctx, &app)];
|
||||
(app, states)
|
||||
});
|
||||
}
|
||||
|
@ -304,10 +304,10 @@ impl SimpleMinimap {
|
||||
}
|
||||
|
||||
pub fn draw(&self, g: &mut GfxCtx, app: &SimpleApp) {
|
||||
self.draw_with_extra_layer(g, app, None);
|
||||
self.draw_with_extra_layers(g, app, Vec::new());
|
||||
}
|
||||
|
||||
pub fn draw_with_extra_layer(&self, g: &mut GfxCtx, app: &SimpleApp, extra: Option<&Drawable>) {
|
||||
pub fn draw_with_extra_layers(&self, g: &mut GfxCtx, app: &SimpleApp, extra: Vec<&Drawable>) {
|
||||
self.panel.draw(g);
|
||||
if !self.zoomed {
|
||||
return;
|
||||
@ -334,7 +334,7 @@ impl SimpleMinimap {
|
||||
g.redraw(&app.draw_map.draw_all_unzoomed_parking_lots);
|
||||
g.redraw(&app.draw_map.draw_all_unzoomed_roads_and_intersections);
|
||||
g.redraw(&app.draw_map.draw_all_buildings);
|
||||
if let Some(draw) = extra {
|
||||
for draw in extra {
|
||||
g.redraw(draw);
|
||||
}
|
||||
// Not the building or parking lot paths
|
||||
|
Loading…
Reference in New Issue
Block a user