#![allow(clippy::too_many_arguments, clippy::type_complexity)]
#[macro_use]
extern crate anyhow;
#[macro_use]
extern crate log;
use abstio::MapName;
use abstutil::{CmdArgs, Timer};
use geom::Duration;
use map_gui::options::Options;
use map_gui::tools::URLManager;
use map_model::Map;
use sim::{Sim, SimFlags};
use widgetry::{EventCtx, Settings, State, Transition};
use crate::app::{App, Flags};
use crate::common::jump_to_time_upon_startup;
use crate::pregame::TitleScreen;
use crate::sandbox::{GameplayMode, SandboxMode};
mod app;
mod challenges;
mod common;
mod debug;
mod devtools;
mod edit;
mod info;
mod layer;
mod pregame;
mod sandbox;
pub fn main() {
let settings = Settings::new("A/B Street");
run(settings);
}
struct Setup {
flags: Flags,
opts: Options,
start_with_edits: Option<String>,
maybe_mode: Option<GameplayMode>,
initialize_tutorial: bool,
center_camera: Option<String>,
start_time: Option<Duration>,
load_kml: Option<String>,
diff_map: Option<String>,
}
fn run(mut settings: Settings) {
settings = settings
.read_svg(Box::new(abstio::slurp_bytes))
.window_icon(abstio::path("system/assets/pregame/icon.png"))
.loading_tips(map_gui::tools::loading_tips())
.require_minimum_width(1500.0);
let mut args = CmdArgs::new();
if args.enabled("--prebake") {
challenges::prebake::prebake_all();
return;
}
let mut setup = Setup {
flags: Flags {
sim_flags: SimFlags::from_args(&mut args),
live_map_edits: args.enabled("--live_map_edits"),
study_area: args.optional("--study_area"),
},
opts: Options::load_or_default(),
start_with_edits: args.optional("--edits"),
maybe_mode: None,
initialize_tutorial: false,
center_camera: args.optional("--cam"),
start_time: args.optional_parse("--time", |t| Duration::parse(t)),
load_kml: args.optional("--kml"),
diff_map: args.optional("--diff"),
};
settings = settings.canvas_settings(setup.opts.canvas_settings.clone());
setup.opts.toggle_day_night_colors = true;
setup.opts.update_from_args(&mut args);
if args.enabled("--dump_raw_events") {
settings = settings.dump_raw_events();
}
if let Some(s) = args.optional_parse("--scale_factor", |s| s.parse::<f64>()) {
settings = settings.scale_factor(s);
}
if let Some(x) = args.optional("--challenge") {
let mut aliases = Vec::new();
'OUTER: for (_, stages) in challenges::Challenge::all() {
for challenge in stages {
if challenge.alias == x {
setup.flags.sim_flags.load = challenge.gameplay.map_name().path();
setup.maybe_mode = Some(challenge.gameplay);
break 'OUTER;
} else {
aliases.push(challenge.alias);
}
}
}
if setup.maybe_mode.is_none() {
panic!("Invalid --challenge={}. Choices: {}", x, aliases.join(", "));
}
}
if let Some(n) = args.optional_parse("--tutorial", |s| s.parse::<usize>()) {
setup.initialize_tutorial = true;
setup.maybe_mode = Some(sandbox::GameplayMode::Tutorial(
sandbox::TutorialPointer::new(n - 1, 0),
));
}
let modifiers = setup.flags.sim_flags.modifiers.drain(..).collect();
if setup.maybe_mode.is_none() && setup.flags.sim_flags.load.contains("scenarios/") {
let (map_name, scenario) = abstio::parse_scenario_path(&setup.flags.sim_flags.load);
setup.flags.sim_flags.load = map_name.path();
setup.maybe_mode = Some(sandbox::GameplayMode::PlayScenario(
map_name, scenario, modifiers,
));
}
if let Some(site) = args.optional("--actdev") {
let site = site.replace("_", "-");
let city = site.replace("-", "_");
let name = MapName::new("gb", &city, "center");
setup.flags.sim_flags.load = name.path();
setup.flags.study_area = Some(site);
setup.flags.sim_flags.opts.infinite_parking = true;
let scenario = if args.optional("--actdev_scenario") == Some("go_active".to_string()) {
"go_active".to_string()
} else {
"base".to_string()
};
setup.maybe_mode = Some(sandbox::GameplayMode::Actdev(name, scenario, false));
}
args.done();
widgetry::run(settings, |ctx| setup_app(ctx, setup))
}
fn setup_app(ctx: &mut EventCtx, mut setup: Setup) -> (App, Vec<Box<dyn State<App>>>) {
let title = !setup.opts.dev
&& !setup.flags.sim_flags.load.contains("player/save")
&& !setup.flags.sim_flags.load.contains("/scenarios/")
&& setup.maybe_mode.is_none();
if title && setup.flags.sim_flags.load == MapName::seattle("montlake").path() {
if let Ok(default) = abstio::maybe_read_json::<map_gui::tools::DefaultMap>(
abstio::path_player("maps.json"),
&mut Timer::throwaway(),
) {
setup.flags.sim_flags.load = default.last_map.path();
}
}
if let Some(GameplayMode::PlayScenario(_, _, _))
| Some(GameplayMode::FixTrafficSignals)
| Some(GameplayMode::OptimizeCommute(_, _))
| Some(GameplayMode::Tutorial(_)) = setup.maybe_mode
{
setup.opts.color_scheme = map_gui::colors::ColorSchemeChoice::NightMode;
}
if title {
setup.opts.color_scheme = map_gui::colors::ColorSchemeChoice::Pregame;
}
let cs = map_gui::colors::ColorScheme::new(ctx, setup.opts.color_scheme);
let secondary = setup.diff_map.as_ref().map(|path| {
ctx.loading_screen("load secondary map", |ctx, mut timer| {
let mut map: Map = abstio::read_binary(path.clone(), &mut timer);
map.map_loaded_directly();
let sim = Sim::new(&map, setup.flags.sim_flags.opts.clone());
crate::app::PerMap::map_loaded(
map,
sim,
setup.flags.clone(),
&setup.opts,
&cs,
ctx,
&mut timer,
)
})
});
if setup.flags.sim_flags.load.contains("/maps/") {
let map = Map::blank();
let sim = Sim::new(&map, setup.flags.sim_flags.opts.clone());
let primary = crate::app::PerMap::map_loaded(
map,
sim,
setup.flags.clone(),
&setup.opts,
&cs,
ctx,
&mut Timer::throwaway(),
);
let app = App {
primary,
secondary,
cs,
opts: setup.opts.clone(),
per_obj: crate::app::PerObjectActions::new(),
session: crate::app::SessionState::empty(),
};
let map_name = MapName::from_path(&app.primary.current_flags.sim_flags.load).unwrap();
let states = vec![map_gui::load::MapLoader::new_state(
ctx,
&app,
map_name,
Box::new(move |ctx, app| Transition::Clear(finish_app_setup(ctx, app, title, setup))),
)];
(app, states)
} else {
let primary = ctx.loading_screen("load map", |ctx, mut timer| {
assert!(setup.flags.sim_flags.modifiers.is_empty());
let (map, sim, _) = setup.flags.sim_flags.load_synchronously(timer);
crate::app::PerMap::map_loaded(
map,
sim,
setup.flags.clone(),
&setup.opts,
&cs,
ctx,
&mut timer,
)
});
assert!(secondary.is_none());
let mut app = App {
primary,
secondary,
cs,
opts: setup.opts.clone(),
per_obj: crate::app::PerObjectActions::new(),
session: crate::app::SessionState::empty(),
};
let states = finish_app_setup(ctx, &mut app, title, setup);
(app, states)
}
}
fn finish_app_setup(
ctx: &mut EventCtx,
app: &mut App,
title: bool,
setup: Setup,
) -> Vec<Box<dyn State<App>>> {
if let Some((pt, zoom)) = setup
.center_camera
.and_then(|cam| URLManager::parse_center_camera(app, cam))
{
ctx.canvas.cam_zoom = zoom;
ctx.canvas.center_on_map_pt(pt);
} else {
app.primary.init_camera_for_loaded_map(ctx, title);
}
let savestate = if app
.primary
.current_flags
.sim_flags
.load
.contains("player/saves/")
{
assert!(setup.maybe_mode.is_none());
Some(app.primary.clear_sim())
} else {
None
};
if let Some(edits_name) = setup.start_with_edits {
let mut timer = Timer::new("apply initial edits");
let edits = map_model::MapEdits::load(
&app.primary.map,
abstio::path_edits(app.primary.map.get_name(), &edits_name),
&mut timer,
)
.or_else(|_| {
map_model::MapEdits::load(
&app.primary.map,
abstio::path(format!("system/proposals/{}.json", edits_name)),
&mut timer,
)
})
.unwrap();
crate::edit::apply_map_edits(ctx, app, edits);
app.primary
.map
.recalculate_pathfinding_after_edits(&mut timer);
app.primary.clear_sim();
}
if setup.initialize_tutorial {
crate::sandbox::gameplay::Tutorial::initialize(ctx, app);
}
let states: Vec<Box<dyn State<App>>> = if let Some(path) = setup.load_kml {
vec![
Box::new(TitleScreen::new(ctx, app)),
crate::devtools::kml::ViewKML::new_state(ctx, app, Some(path)),
]
} else if title {
vec![Box::new(TitleScreen::new(ctx, app))]
} else if let Some(ss) = savestate {
app.primary.sim = ss;
vec![SandboxMode::start_from_savestate(app)]
} else if let Some(mode) = setup.maybe_mode {
if let GameplayMode::Actdev(_, _, _) = mode {
vec![SandboxMode::async_new(
app,
mode,
jump_to_time_upon_startup(Duration::hours(8)),
)]
} else if let Some(t) = setup.start_time {
vec![SandboxMode::async_new(
app,
mode,
jump_to_time_upon_startup(t),
)]
} else {
vec![SandboxMode::simple_new(app, mode)]
}
} else {
if let Some(ref mut secondary) = app.secondary {
secondary.sim.timed_step(
&secondary.map,
Duration::hours(6) + Duration::minutes(30),
&mut None,
&mut Timer::throwaway(),
);
}
vec![SandboxMode::async_new(
app,
GameplayMode::Freeform(app.primary.map.get_name().clone()),
jump_to_time_upon_startup(Duration::hours(6)),
)]
};
states
}
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen(js_name = "run")]
pub fn run_wasm(root_dom_id: String, assets_base_url: String, assets_are_gzipped: bool) {
let settings = Settings::new("A/B Street")
.root_dom_element_id(root_dom_id)
.assets_base_url(assets_base_url)
.assets_are_gzipped(assets_are_gzipped);
run(settings);
}