tiger_lib/data/
music.rs

1use std::path::PathBuf;
2
3use crate::block::Block;
4use crate::context::ScopeContext;
5use crate::db::{Db, DbKind};
6use crate::everything::Everything;
7use crate::fileset::{FileEntry, FileHandler};
8use crate::game::{Game, GameFlags};
9use crate::helpers::{TigerHashMap, dup_error};
10use crate::item::{Item, ItemLoader};
11use crate::parse::ParserMemory;
12use crate::pdxfile::PdxFile;
13use crate::report::{ErrorKey, Severity, report, warn};
14use crate::scopes::Scopes;
15use crate::token::Token;
16use crate::tooltipped::Tooltipped;
17use crate::validate::validate_optional_duration_int;
18use crate::validator::Validator;
19use crate::variables::Variables;
20
21#[derive(Clone, Debug, Default)]
22pub struct Musics {
23    musics: TigerHashMap<&'static str, Music>,
24}
25
26impl Musics {
27    pub fn load_item(&mut self, key: Token, block: Block) {
28        if let Some(other) = self.musics.get(key.as_str()) {
29            if other.key.loc.kind == key.loc.kind {
30                dup_error(&key, &other.key, "music");
31            }
32        }
33        self.musics.insert(key.as_str(), Music { key, block });
34    }
35
36    pub fn scan_variables(&self, registry: &mut Variables) {
37        for item in self.musics.values() {
38            registry.scan(&item.block);
39        }
40    }
41
42    pub fn exists(&self, key: &str) -> bool {
43        let dlc_music = match Game::game() {
44            #[cfg(feature = "ck3")]
45            Game::Ck3 => crate::ck3::tables::misc::DLC_MUSIC,
46            #[cfg(feature = "vic3")]
47            Game::Vic3 => crate::vic3::tables::misc::DLC_MUSIC,
48            #[cfg(feature = "imperator")]
49            Game::Imperator => crate::imperator::tables::misc::DLC_MUSIC,
50            #[cfg(feature = "eu5")]
51            Game::Eu5 => crate::eu5::tables::misc::DLC_MUSIC,
52            #[cfg(feature = "hoi4")]
53            Game::Hoi4 => crate::hoi4::tables::misc::DLC_MUSIC,
54        };
55        self.musics.contains_key(key) || dlc_music.contains(&key)
56    }
57
58    pub fn iter_keys(&self) -> impl Iterator<Item = &Token> {
59        self.musics.values().map(|item| &item.key)
60    }
61
62    pub fn verify_exists_implied(&self, key: &str, item: &Token, max_sev: Severity) {
63        if !self.exists(key) {
64            let msg = if key == item.as_str() {
65                "music not defined in music/ or dlc/*/music/".to_string()
66            } else {
67                format!("music {key} not defined in music/ or dlc/*/music/")
68            };
69            let info = "this could be due to a missing DLC";
70            report(ErrorKey::MissingSound, Item::Sound.severity().at_most(max_sev))
71                .msg(msg)
72                .info(info)
73                .loc(item)
74                .push();
75        }
76    }
77
78    pub fn validate(&self, data: &Everything) {
79        for item in self.musics.values() {
80            item.validate(data);
81        }
82    }
83}
84
85impl FileHandler<Block> for Musics {
86    fn subpath(&self) -> PathBuf {
87        PathBuf::from("music")
88    }
89
90    fn load_file(&self, entry: &FileEntry, parser: &ParserMemory) -> Option<Block> {
91        if entry.path().parent().unwrap().ends_with("music_player_categories") {
92            return None;
93        }
94        if !entry.filename().to_string_lossy().ends_with(".txt") {
95            return None;
96        }
97
98        PdxFile::read(entry, parser)
99    }
100
101    fn handle_file(&mut self, _entry: &FileEntry, mut block: Block) {
102        for (key, block) in block.drain_definitions_warn() {
103            self.load_item(key, block);
104        }
105    }
106}
107
108#[derive(Clone, Debug)]
109pub struct Music {
110    key: Token,
111    block: Block,
112}
113
114impl Music {
115    pub fn validate(&self, data: &Everything) {
116        let mut vd = Validator::new(&self.block, data);
117
118        if Game::is_eu5() {
119            vd.field_integer("priority");
120            vd.field("culture_tag");
121        } else {
122            let scope = match Game::game() {
123                #[cfg(feature = "ck3")]
124                Game::Ck3 => Scopes::Character,
125                #[cfg(feature = "vic3")]
126                Game::Vic3 => Scopes::Country,
127                #[cfg(feature = "imperator")]
128                Game::Imperator => Scopes::Country,
129                #[cfg(feature = "eu5")]
130                Game::Eu5 => Scopes::Country,
131                #[cfg(feature = "hoi4")]
132                Game::Hoi4 => Scopes::Country,
133            };
134            let mut sc = ScopeContext::new(scope, &self.key);
135            vd.field_localization("name", &mut sc);
136            vd.field_item("music", Item::Sound);
137            vd.field_item("group", Item::Music); // Take settings from this item
138            vd.field_integer("pause_factor");
139
140            vd.field_bool("mood");
141            vd.field_bool("is_prioritized_mood");
142            vd.field_bool("can_be_interrupted");
143
144            validate_optional_duration_int(&mut vd);
145            vd.field_integer("calls");
146
147            vd.field_bool("trigger_prio_override");
148            vd.field_trigger("is_valid", Tooltipped::No, &mut sc);
149
150            vd.field_list_numeric_exactly("subsequent_playback_chance", 3);
151        }
152    }
153}
154
155#[derive(Clone, Debug)]
156pub struct MusicPlayerCategory {}
157
158inventory::submit! {
159    ItemLoader::Normal(GameFlags::all(), Item::MusicPlayerCategory, MusicPlayerCategory::add)
160}
161
162impl MusicPlayerCategory {
163    pub fn add(db: &mut Db, key: Token, block: Block) {
164        if key.is("category") {
165            if let Some(id) = block.get_field_value("id") {
166                db.add(Item::MusicPlayerCategory, id.clone(), block, Box::new(Self {}));
167            } else {
168                let msg = "category without id";
169                warn(ErrorKey::FieldMissing).msg(msg).loc(key).push();
170            }
171        } else {
172            let msg = format!("unknown key {key} in music categories");
173            warn(ErrorKey::UnknownField).msg(msg).loc(key).push();
174        }
175    }
176}
177
178impl DbKind for MusicPlayerCategory {
179    fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
180        let mut vd = Validator::new(block, data);
181        vd.set_max_severity(Severity::Warning);
182
183        vd.field_value("id"); // used in ::add
184        vd.field_item("name", Item::Localization);
185        vd.field_list_items("tracks", Item::Music);
186    }
187}