1use std::collections::{BTreeSet, HashMap};
2
3use serde::{Deserialize, Serialize};
4
5use abstutil::{deserialize_multimap, serialize_multimap, MultiMap, Timer};
6use map_model::BuildingID;
7use widgetry::{Color, EventCtx};
8
9use crate::levels::Level;
10use crate::music::Music;
11
12#[derive(Serialize, Deserialize)]
14pub struct Session {
15 pub levels: Vec<Level>,
16 pub enable_modding: bool,
18 pub colors: ColorScheme,
19
20 pub high_scores: HashMap<String, Vec<usize>>,
22 pub levels_unlocked: usize,
23 pub current_vehicle: String,
24 pub vehicles_unlocked: BTreeSet<String>,
25 pub upzones_unlocked: usize,
26 pub upzones_explained: bool,
27 #[serde(
30 serialize_with = "serialize_multimap",
31 deserialize_with = "deserialize_multimap",
32 default
33 )]
34 pub upzones_per_level: MultiMap<String, BuildingID>,
35
36 #[serde(skip_serializing, skip_deserializing)]
37 pub music: Music,
38 pub play_music: bool,
39}
40
41#[derive(Serialize, Deserialize)]
42pub struct ColorScheme {
43 pub house: Color,
44 pub apartment: Color,
45 pub store: Color,
46 pub visited: Color,
47
48 pub score: Color,
49 pub energy: Color,
50 pub boost: Color,
51}
52
53impl Session {
54 pub fn load() -> Session {
55 let levels = Level::all();
56
57 if let Ok(mut session) = abstio::maybe_read_json::<Session>(
58 abstio::path_player("santa.json"),
59 &mut Timer::throwaway(),
60 ) {
61 if session.levels != levels {
62 if session.enable_modding {
63 warn!("Using modified levels from the session data");
64 } else {
65 warn!("Levels have changed; overwriting with the new version from the code");
66 session.levels = levels;
67 }
68 }
69 return session;
70 }
71
72 let mut high_scores = HashMap::new();
73 for level in &levels {
74 high_scores.insert(level.title.clone(), Vec::new());
75 }
76 Session {
77 levels,
78 enable_modding: false,
79 colors: ColorScheme {
80 house: Color::hex("#688865"),
81 apartment: Color::hex("#C0F879"),
82 store: Color::hex("#EE702E"),
83 visited: Color::BLACK,
84
85 score: Color::hex("#83AA51"),
86 energy: Color::hex("#D8B830"),
87 boost: Color::hex("#A32015"),
88 },
89
90 high_scores,
91 levels_unlocked: 1,
92 current_vehicle: "bike".to_string(),
93 vehicles_unlocked: vec!["bike".to_string()].into_iter().collect(),
94 upzones_unlocked: 0,
95 upzones_explained: false,
96 upzones_per_level: MultiMap::new(),
97
98 music: Music::empty(),
99 play_music: true,
100 }
101 }
102
103 pub fn record_score(&mut self, level: String, score: usize) -> Option<Vec<String>> {
105 let scores = self.high_scores.get_mut(&level).unwrap();
106 scores.push(score);
107 scores.sort_unstable();
108 scores.reverse();
109 scores.truncate(3);
110
111 let idx = self
112 .levels
113 .iter()
114 .position(|lvl| lvl.title == level)
115 .unwrap();
116 let level = &self.levels[idx];
117 let msg = if idx + 1 == self.levels_unlocked && score >= level.goal {
118 if idx + 1 == self.levels.len() {
119 Some(vec![
120 "All levels complete! Nice.".to_string(),
121 "Can you improve your score on other levels?".to_string(),
122 ])
123 } else {
124 self.levels_unlocked += 1;
125 let mut messages = vec!["New level unlocked!".to_string()];
126 if level.unlock_upzones > 0 {
127 self.upzones_unlocked += level.unlock_upzones;
128 messages.push(format!(
129 "Unlocked the ability to upzone {} buildings",
130 level.unlock_upzones
131 ));
132 }
133 for x in &level.unlock_vehicles {
134 self.vehicles_unlocked.insert(x.clone());
135 messages.push(format!("Unlocked the {}", x));
136 }
137 Some(messages)
138 }
139 } else {
140 None
142 };
143 self.save();
144 msg
145 }
146
147 pub fn unlock_all(&mut self) {
148 self.upzones_unlocked = 0;
149 for level in &self.levels {
150 self.vehicles_unlocked.extend(level.unlock_vehicles.clone());
151 self.upzones_unlocked += level.unlock_upzones;
152 }
153 self.levels_unlocked = self.levels.len();
154 self.upzones_explained = true;
155 }
156
157 pub fn update_music(&mut self, ctx: &mut EventCtx) {
158 let play_music = self.play_music;
159 self.music.event(ctx, &mut self.play_music);
160 if play_music != self.play_music {
161 self.save();
162 }
163 }
164
165 pub fn save(&self) {
166 abstio::write_json(abstio::path_player("santa.json"), self);
167 }
168}