ayaka_model/
settings.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use crate::*;
use anyhow::Result;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{
    collections::HashMap,
    path::{Path, PathBuf},
};

/// The settings of the game.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Settings {
    /// The display language.
    pub lang: Locale,
    /// The secondary display language.
    pub sub_lang: Option<Locale>,
    /// Volume of background music.
    pub bgm_volume: u8,
    /// Volume of voices.
    pub voice_volume: u8,
    /// Volume of videos.
    pub video_volume: u8,
}

impl Default for Settings {
    fn default() -> Self {
        Self {
            lang: Locale::default(),
            sub_lang: None,
            bgm_volume: 100,
            voice_volume: 100,
            video_volume: 100,
        }
    }
}

/// The global record.
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
pub struct GlobalRecord {
    /// The key is the tag of paragraphs,
    /// the value is the maximum text index.
    pub record: HashMap<String, usize>,
}

impl GlobalRecord {
    /// Determine if an [`RawContext`] has been visited,
    /// by the paragraph tag and action index.
    pub fn visited(&self, ctx: &RawContext) -> bool {
        if let Some(max_act) = self.record.get(&ctx.cur_para) {
            log::debug!("Test act: {}, max act: {}", ctx.cur_act, max_act);
            *max_act >= ctx.cur_act
        } else {
            false
        }
    }

    /// Update the global record with the latest [`RawContext`].
    pub fn update(&mut self, ctx: &RawContext) {
        self.record
            .entry(ctx.cur_para.clone())
            .and_modify(|act| *act = (*act).max(ctx.cur_act))
            .or_insert(ctx.cur_act);
    }
}

/// The specific record.
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
pub struct ActionRecord {
    /// The history actions.
    pub history: Vec<RawContext>,
}

impl ActionRecord {
    /// Get the [`RawContext`] object from the last [`Action`] in the history.
    pub fn last_ctx(&self) -> Option<&RawContext> {
        self.history.last()
    }

    /// Get the [`RawContext`] object from the last [`Action`] in the history,
    /// and if the history is empty, create a new [`RawContext`] from the game.
    pub fn last_ctx_with_game(&self, game: &Game) -> RawContext {
        self.last_ctx()
            .cloned()
            .unwrap_or_else(|| game.start_context())
    }
}

/// A settings manager trait.
///
/// This type should handle the file loading and saving,
/// and manage the paths of the files.
pub trait SettingsManager {
    /// Load a file from specified path.
    fn load_file<T: DeserializeOwned>(&self, path: impl AsRef<Path>) -> Result<T>;

    /// Save a file to specified path.
    fn save_file<T: Serialize>(&self, path: impl AsRef<Path>, data: &T, pretty: bool)
        -> Result<()>;

    /// Get the settings path.
    fn settings_path(&self) -> Result<PathBuf>;

    /// Load [`Settings`].
    fn load_settings(&self) -> Result<Settings> {
        self.load_file(self.settings_path()?)
    }

    /// Save [`Settings`].
    fn save_settings(&self, data: &Settings) -> Result<()> {
        self.save_file(self.settings_path()?, data, true)
    }

    /// Get the global record path.
    fn global_record_path(&self, game: &str) -> Result<PathBuf>;

    /// Load [`GlobalRecord`].
    fn load_global_record(&self, game: &str) -> Result<GlobalRecord> {
        self.load_file(self.global_record_path(game)?)
    }

    /// Save [`GlobalRecord`].
    fn save_global_record(&self, game: &str, data: &GlobalRecord) -> Result<()> {
        self.save_file(self.global_record_path(game)?, data, false)
    }

    /// Get an iterator of record paths.
    fn records_path(&self, game: &str) -> Result<impl Iterator<Item = Result<PathBuf>>>;

    /// Get the record path from index.
    fn record_path(&self, game: &str, i: usize) -> Result<PathBuf>;

    /// Load all [`ActionRecord`].
    fn load_records(&self, game: &str) -> Result<Vec<ActionRecord>> {
        self.records_path(game)?
            .map(|path| path.and_then(|path| self.load_file(path)))
            .collect()
    }

    /// Save all [`ActionRecord`].
    fn save_records(&self, game: &str, contexts: &[ActionRecord]) -> Result<()> {
        for (i, ctx) in contexts.iter().enumerate() {
            self.save_file(self.record_path(game, i)?, ctx, false)?;
        }
        Ok(())
    }
}