ayaka_model/
settings.rs

1use crate::*;
2use anyhow::Result;
3use serde::{de::DeserializeOwned, Deserialize, Serialize};
4use std::{
5    collections::HashMap,
6    path::{Path, PathBuf},
7};
8
9/// The settings of the game.
10#[derive(Debug, Clone, Deserialize, Serialize)]
11pub struct Settings {
12    /// The display language.
13    pub lang: Locale,
14    /// The secondary display language.
15    pub sub_lang: Option<Locale>,
16    /// Volume of background music.
17    pub bgm_volume: u8,
18    /// Volume of voices.
19    pub voice_volume: u8,
20    /// Volume of videos.
21    pub video_volume: u8,
22}
23
24impl Default for Settings {
25    fn default() -> Self {
26        Self {
27            lang: Locale::default(),
28            sub_lang: None,
29            bgm_volume: 100,
30            voice_volume: 100,
31            video_volume: 100,
32        }
33    }
34}
35
36/// The global record.
37#[derive(Debug, Default, Clone, Deserialize, Serialize)]
38pub struct GlobalRecord {
39    /// The key is the tag of paragraphs,
40    /// the value is the maximum text index.
41    pub record: HashMap<String, usize>,
42}
43
44impl GlobalRecord {
45    /// Determine if an [`RawContext`] has been visited,
46    /// by the paragraph tag and action index.
47    pub fn visited(&self, ctx: &RawContext) -> bool {
48        if let Some(max_act) = self.record.get(&ctx.cur_para) {
49            log::debug!("Test act: {}, max act: {}", ctx.cur_act, max_act);
50            *max_act >= ctx.cur_act
51        } else {
52            false
53        }
54    }
55
56    /// Update the global record with the latest [`RawContext`].
57    pub fn update(&mut self, ctx: &RawContext) {
58        self.record
59            .entry(ctx.cur_para.clone())
60            .and_modify(|act| *act = (*act).max(ctx.cur_act))
61            .or_insert(ctx.cur_act);
62    }
63}
64
65/// The specific record.
66#[derive(Debug, Default, Clone, Deserialize, Serialize)]
67pub struct ActionRecord {
68    /// The history actions.
69    pub history: Vec<RawContext>,
70}
71
72impl ActionRecord {
73    /// Get the [`RawContext`] object from the last [`Action`] in the history.
74    pub fn last_ctx(&self) -> Option<&RawContext> {
75        self.history.last()
76    }
77
78    /// Get the [`RawContext`] object from the last [`Action`] in the history,
79    /// and if the history is empty, create a new [`RawContext`] from the game.
80    pub fn last_ctx_with_game(&self, game: &Game) -> RawContext {
81        self.last_ctx()
82            .cloned()
83            .unwrap_or_else(|| game.start_context())
84    }
85}
86
87/// A settings manager trait.
88///
89/// This type should handle the file loading and saving,
90/// and manage the paths of the files.
91pub trait SettingsManager {
92    /// Load a file from specified path.
93    fn load_file<T: DeserializeOwned>(&self, path: impl AsRef<Path>) -> Result<T>;
94
95    /// Save a file to specified path.
96    fn save_file<T: Serialize>(&self, path: impl AsRef<Path>, data: &T, pretty: bool)
97        -> Result<()>;
98
99    /// Get the settings path.
100    fn settings_path(&self) -> Result<PathBuf>;
101
102    /// Load [`Settings`].
103    fn load_settings(&self) -> Result<Settings> {
104        self.load_file(self.settings_path()?)
105    }
106
107    /// Save [`Settings`].
108    fn save_settings(&self, data: &Settings) -> Result<()> {
109        self.save_file(self.settings_path()?, data, true)
110    }
111
112    /// Get the global record path.
113    fn global_record_path(&self, game: &str) -> Result<PathBuf>;
114
115    /// Load [`GlobalRecord`].
116    fn load_global_record(&self, game: &str) -> Result<GlobalRecord> {
117        self.load_file(self.global_record_path(game)?)
118    }
119
120    /// Save [`GlobalRecord`].
121    fn save_global_record(&self, game: &str, data: &GlobalRecord) -> Result<()> {
122        self.save_file(self.global_record_path(game)?, data, false)
123    }
124
125    /// Get an iterator of record paths.
126    fn records_path(&self, game: &str) -> Result<impl Iterator<Item = Result<PathBuf>>>;
127
128    /// Get the record path from index.
129    fn record_path(&self, game: &str, i: usize) -> Result<PathBuf>;
130
131    /// Load all [`ActionRecord`].
132    fn load_records(&self, game: &str) -> Result<Vec<ActionRecord>> {
133        self.records_path(game)?
134            .map(|path| path.and_then(|path| self.load_file(path)))
135            .collect()
136    }
137
138    /// Save all [`ActionRecord`].
139    fn save_records(&self, game: &str, contexts: &[ActionRecord]) -> Result<()> {
140        for (i, ctx) in contexts.iter().enumerate() {
141            self.save_file(self.record_path(game, i)?, ctx, false)?;
142        }
143        Ok(())
144    }
145}