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); 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"); vd.field_item("name", Item::Localization);
185 vd.field_list_items("tracks", Item::Music);
186 }
187}