use rand_xorshift::XorShiftRng;
use abstutil::Timer;
use geom::Duration;
use map_model::{EditCmd, EditIntersection, Map, MapEdits};
use sim::{OrigPersonID, Scenario, ScenarioGenerator, ScenarioModifier};
use widgetry::{
lctrl, Btn, Color, EventCtx, GeomBatch, GfxCtx, Key, Line, Outcome, Panel, State, TextExt,
Widget,
};
pub use self::freeform::spawn_agents_around;
pub use self::tutorial::{Tutorial, TutorialPointer, TutorialState};
use crate::app::App;
use crate::challenges::{Challenge, ChallengesPicker};
use crate::edit::{apply_map_edits, SaveEdits};
use crate::game::Transition;
use crate::pregame::MainMenu;
use crate::sandbox::{Actions, SandboxControls, SandboxMode};
pub mod commute;
pub mod fix_traffic_signals;
pub mod freeform;
pub mod play_scenario;
pub mod tutorial;
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
pub enum GameplayMode {
Freeform(String),
PlayScenario(String, String, Vec<ScenarioModifier>),
FixTrafficSignals,
OptimizeCommute(OrigPersonID, Duration),
Tutorial(TutorialPointer),
}
pub trait GameplayState: downcast_rs::Downcast {
fn event(
&mut self,
ctx: &mut EventCtx,
app: &mut App,
controls: &mut SandboxControls,
actions: &mut Actions,
) -> Option<Transition>;
fn draw(&self, g: &mut GfxCtx, app: &App);
fn on_destroy(&self, _: &mut App) {}
fn can_move_canvas(&self) -> bool {
true
}
fn can_examine_objects(&self) -> bool {
true
}
fn has_common(&self) -> bool {
true
}
fn has_tool_panel(&self) -> bool {
true
}
fn has_time_panel(&self) -> bool {
true
}
fn has_speed(&self) -> bool {
true
}
fn has_agent_meter(&self) -> bool {
true
}
fn has_minimap(&self) -> bool {
true
}
}
downcast_rs::impl_downcast!(GameplayState);
pub enum LoadScenario {
Nothing,
Path(String),
Scenario(Scenario),
}
impl GameplayMode {
pub fn map_name(&self) -> &str {
match self {
GameplayMode::Freeform(ref name) => name,
GameplayMode::PlayScenario(ref name, _, _) => name,
GameplayMode::FixTrafficSignals => "downtown",
GameplayMode::OptimizeCommute(_, _) => "montlake",
GameplayMode::Tutorial(_) => "montlake",
}
}
pub fn scenario(
&self,
map: &Map,
num_agents: Option<usize>,
mut rng: XorShiftRng,
timer: &mut Timer,
) -> LoadScenario {
let name = match self {
GameplayMode::Freeform(_) => {
let mut s = Scenario::empty(map, "empty");
s.only_seed_buses = None;
return LoadScenario::Scenario(s);
}
GameplayMode::PlayScenario(_, ref scenario, _) => scenario.to_string(),
GameplayMode::Tutorial(_) => {
return LoadScenario::Nothing;
}
_ => "weekday".to_string(),
};
if name == "random" {
LoadScenario::Scenario(
(if let Some(n) = num_agents {
ScenarioGenerator::scaled_run(n)
} else {
ScenarioGenerator::small_run(map)
})
.generate(map, &mut rng, timer),
)
} else if name == "home_to_work" {
LoadScenario::Scenario(ScenarioGenerator::proletariat_robot(map, &mut rng, timer))
} else {
LoadScenario::Path(abstutil::path_scenario(map.get_name(), &name))
}
}
pub fn can_edit_lanes(&self) -> bool {
match self {
GameplayMode::FixTrafficSignals => false,
_ => true,
}
}
pub fn can_edit_stop_signs(&self) -> bool {
match self {
GameplayMode::FixTrafficSignals => false,
_ => true,
}
}
pub fn can_jump_to_time(&self) -> bool {
match self {
GameplayMode::Freeform(_) => false,
_ => true,
}
}
pub fn allows(&self, edits: &MapEdits) -> bool {
for cmd in &edits.commands {
match cmd {
EditCmd::ChangeRoad { .. } => {
if !self.can_edit_lanes() {
return false;
}
}
EditCmd::ChangeIntersection { ref new, .. } => match new {
EditIntersection::StopSign(_) | EditIntersection::Closed => {
if !self.can_edit_stop_signs() {
return false;
}
}
_ => {}
},
EditCmd::ChangeRouteSchedule { .. } => {}
}
}
true
}
pub fn initialize(&self, ctx: &mut EventCtx, app: &mut App) -> Box<dyn GameplayState> {
match self {
GameplayMode::Freeform(_) => freeform::Freeform::new(ctx, app),
GameplayMode::PlayScenario(_, ref scenario, ref modifiers) => {
play_scenario::PlayScenario::new(ctx, app, scenario, modifiers.clone())
}
GameplayMode::FixTrafficSignals => {
fix_traffic_signals::FixTrafficSignals::new(ctx, app)
}
GameplayMode::OptimizeCommute(p, goal) => {
commute::OptimizeCommute::new(ctx, app, *p, *goal)
}
GameplayMode::Tutorial(current) => Tutorial::new(ctx, app, *current),
}
}
}
fn challenge_header(ctx: &mut EventCtx, title: &str) -> Widget {
Widget::row(vec![
Line(title).small_heading().draw(ctx).centered_vert(),
Btn::svg_def("system/assets/tools/info.svg")
.build(ctx, "instructions", None)
.centered_vert(),
Widget::vert_separator(ctx, 50.0),
Btn::svg_def("system/assets/tools/edit_map.svg")
.build(ctx, "edit map", lctrl(Key::E))
.centered_vert(),
])
.padding(5)
}
pub struct FinalScore {
panel: Panel,
retry: GameplayMode,
next_mode: Option<GameplayMode>,
chose_next: bool,
chose_back_to_challenges: bool,
}
impl FinalScore {
pub fn new(
ctx: &mut EventCtx,
app: &App,
msg: String,
mode: GameplayMode,
next_mode: Option<GameplayMode>,
) -> Box<dyn State<App>> {
Box::new(FinalScore {
panel: Panel::new(
Widget::custom_row(vec![
Widget::draw_batch(
ctx,
GeomBatch::load_svg(ctx.prerender, "system/assets/characters/boss.svg")
.scale(0.75)
.autocrop(),
)
.container()
.outline(10.0, Color::BLACK)
.padding(10),
Widget::col(vec![
msg.draw_text(ctx),
Btn::text_bg2("Keep simulating").build_def(ctx, None),
Btn::text_bg2("Try again").build_def(ctx, None),
if next_mode.is_some() {
Btn::text_bg2("Next challenge").build_def(ctx, None)
} else {
Widget::nothing()
},
Btn::text_bg2("Back to challenges").build_def(ctx, None),
])
.outline(10.0, Color::BLACK)
.padding(10),
])
.bg(app.cs.panel_bg),
)
.build_custom(ctx),
retry: mode,
next_mode,
chose_next: false,
chose_back_to_challenges: false,
})
}
}
impl State<App> for FinalScore {
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
match self.panel.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
"Keep simulating" => {
return Transition::Pop;
}
"Try again" => {
return Transition::Multi(vec![
Transition::Pop,
Transition::Replace(SandboxMode::simple_new(ctx, app, self.retry.clone())),
]);
}
"Next challenge" => {
self.chose_next = true;
if app.primary.map.unsaved_edits() {
return Transition::Push(SaveEdits::new(
ctx,
app,
"Do you want to save your proposal first?",
true,
None,
Box::new(|_, _| {}),
));
}
}
"Back to challenges" => {
self.chose_back_to_challenges = true;
if app.primary.map.unsaved_edits() {
return Transition::Push(SaveEdits::new(
ctx,
app,
"Do you want to save your proposal first?",
true,
None,
Box::new(|_, _| {}),
));
}
}
_ => unreachable!(),
},
_ => {}
}
if self.chose_next || self.chose_back_to_challenges {
ctx.loading_screen("reset map and sim", |ctx, mut timer| {
apply_map_edits(ctx, app, app.primary.map.new_edits());
app.primary
.map
.recalculate_pathfinding_after_edits(&mut timer);
app.primary.clear_sim();
app.set_prebaked(None);
});
}
if self.chose_next {
return Transition::Clear(vec![
MainMenu::new(ctx, app),
SandboxMode::simple_new(ctx, app, self.next_mode.clone().unwrap()),
(Challenge::find(self.next_mode.as_ref().unwrap())
.0
.cutscene
.unwrap())(ctx, app, self.next_mode.as_ref().unwrap()),
]);
}
if self.chose_back_to_challenges {
return Transition::Clear(vec![
MainMenu::new(ctx, app),
ChallengesPicker::new(ctx, app),
]);
}
Transition::Keep
}
fn draw(&self, g: &mut GfxCtx, app: &App) {
g.clear(app.cs.dialog_bg);
self.panel.draw(g);
}
}