santa/
session.rs

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/// Persistent state that lasts across levels.
13#[derive(Serialize, Deserialize)]
14pub struct Session {
15    pub levels: Vec<Level>,
16    /// Enable this to use the levels, instead of overwriting them with the version in the code.
17    pub enable_modding: bool,
18    pub colors: ColorScheme,
19
20    /// Level title -> the top 3 scores
21    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    // This was added after the main release, so keep old save files working by allowing it to be
28    // missing.
29    #[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    /// If a message is returned, a new level and some powers were unlocked.
104    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            // Nothing new unlocked
141            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}