1use std::{fmt, path::PathBuf};
2
3use thedes_asset::Assets;
4use thedes_settings::AudioSinkType;
5use thedes_tui::{
6 core::{audio, event::Key},
7 menu::{self, Menu},
8};
9use thiserror::Error;
10
11use crate::{SAVE_EXTENSION, session, settings};
12
13pub mod new_game;
14pub mod game_creation;
15pub mod load_game;
16
17#[derive(Debug, Error)]
18pub enum InitError {
19 #[error("Failed to initialize main menu")]
20 MainMenu(
21 #[from]
22 #[source]
23 menu::Error,
24 ),
25 #[error("Failed to initialize new game component")]
26 NewGame(
27 #[from]
28 #[source]
29 new_game::InitError,
30 ),
31 #[error("Inconsistent main menu, missing quit")]
32 MissingQuit,
33 #[error("Failed to create settings component")]
34 LoadSettings(#[from] settings::LoadError),
35}
36
37#[derive(Debug, Error)]
38pub enum Error {
39 #[error("Failed to run main menu")]
40 MainMenu(
41 #[from]
42 #[source]
43 menu::Error,
44 ),
45 #[error("Failed to run new game component")]
46 NewGame(
47 #[from]
48 #[source]
49 new_game::Error,
50 ),
51 #[error("Failed to run game creation")]
52 GameCreation(
53 #[from]
54 #[source]
55 game_creation::Error,
56 ),
57 #[error("Failed to run game session component")]
58 Session(
59 #[from]
60 #[source]
61 session::Error,
62 ),
63 #[error("Failed to create a game session")]
64 SessionInit(
65 #[from]
66 #[source]
67 session::InitError,
68 ),
69 #[error("Failed to load game")]
70 LoadGame(#[from] load_game::Error),
71 #[error("Failed to load asset")]
72 LoadAsset(#[from] thedes_asset::LoadError),
73 #[error("Failed to run settings component")]
74 Settings(#[from] settings::Error),
75 #[error("Failed to flush commands to audio controller")]
76 FlushAudio(#[from] audio::FlushError),
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80enum MainMenuItem {
81 NewGame,
82 LoadGame,
83 Settings,
84 Quit,
85}
86
87impl fmt::Display for MainMenuItem {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 f.write_str(match self {
90 Self::NewGame => "New Game",
91 Self::LoadGame => "Load Game",
92 Self::Settings => "Settings",
93 Self::Quit => "Quit",
94 })
95 }
96}
97
98#[derive(Debug, Clone)]
99pub struct Config {
100 pub saves_dir: PathBuf,
101 pub settings_path: PathBuf,
102}
103
104pub struct Component {
106 main_menu: Menu<MainMenuItem>,
107 new_game: new_game::Component,
108 game_creation: game_creation::Component,
109 load_game: load_game::Component,
110 session_config: session::Config,
111 saves_dir: PathBuf,
112 settings: settings::Component,
113}
114
115impl Component {
116 pub async fn new(config: Config) -> Result<Self, InitError> {
117 let main_menu_items = [
118 MainMenuItem::NewGame,
119 MainMenuItem::LoadGame,
120 MainMenuItem::Settings,
121 MainMenuItem::Quit,
122 ];
123
124 let quit_position = main_menu_items
125 .iter()
126 .position(|item| *item == MainMenuItem::Quit)
127 .ok_or(InitError::MissingQuit)?;
128
129 let main_menu_bindings = menu::default_key_bindings()
130 .with(Key::Char('q'), menu::Command::SelectConfirm(quit_position));
131
132 let main_menu = Menu::new("=== T H E D E S ===", main_menu_items)?
133 .with_keybindings(main_menu_bindings);
134
135 let new_game = new_game::Component::new()?;
136 let game_creation = game_creation::Component::new();
137
138 let load_game = load_game::Component::new();
139
140 let settings = settings::Component::load(config.settings_path).await?;
141
142 Ok(Self {
143 main_menu,
144 new_game,
145 game_creation,
146 load_game,
147 session_config: session::Config::new(),
148 saves_dir: config.saves_dir,
149 settings,
150 })
151 }
152
153 pub async fn run(
154 &mut self,
155 app: &mut thedes_tui::core::App,
156 ) -> Result<(), Error> {
157 for sink_type in [AudioSinkType::Music] {
158 app.audio_controller.queue([audio::Command::new_set_volume(
159 sink_type,
160 self.settings.values().audio().volume(sink_type),
161 )]);
162 }
163
164 let assets = Assets::get().await?;
165 app.audio_controller.queue([audio::Command::new_enter_repeated(
166 AudioSinkType::Music,
167 &assets.sound.main_theme[..],
168 )]);
169
170 app.audio_controller.flush()?;
171
172 loop {
173 self.main_menu.run(app).await?;
174
175 match self.main_menu.output() {
176 MainMenuItem::NewGame => {
177 self.new_game.set_seed(rand::random());
178 self.new_game.run(app).await?;
179 if self.new_game.is_cancelling() {
180 continue;
181 }
182 let seed = self.new_game.form().seed;
183 let config = thedes_gen::Config::new().with_seed(seed);
184 if let Some(game) =
185 self.game_creation.run(app, config).await?
186 {
187 let mut save_path = self.saves_dir.clone();
188 save_path.push(format!(
189 "{}{}",
190 self.new_game.form().name,
191 SAVE_EXTENSION,
192 ));
193 let mut session = self
194 .session_config
195 .clone()
196 .finish(save_path, game)?;
197 session.run(&mut self.settings, app).await?;
198 }
199 },
200
201 MainMenuItem::LoadGame => {
202 if let Some(save_path) =
203 self.load_game.run(&self.saves_dir, app).await?
204 {
205 let mut session = self
206 .session_config
207 .clone()
208 .finish_loading(save_path)
209 .await?;
210 session.run(&mut self.settings, app).await?;
211 }
212 },
213 MainMenuItem::Settings => {
214 self.settings.run(app).await?;
215 },
216 MainMenuItem::Quit => break,
217 }
218 }
219
220 Ok(())
221 }
222}