tiger_lib/ck3/data/
prov_terrain.rs

1use std::path::PathBuf;
2
3use crate::block::Block;
4use crate::everything::Everything;
5use crate::fileset::{FileEntry, FileHandler};
6use crate::helpers::{TigerHashMap, dup_error};
7use crate::item::Item;
8use crate::parse::ParserMemory;
9use crate::pdxfile::PdxFile;
10use crate::report::{ErrorKey, Severity, warn};
11use crate::token::{Loc, Token};
12use crate::validator::Validator;
13
14use super::provinces::ProvId;
15
16const DEFAULT_TERRAINS: &[&str] = &["default_land", "default_sea", "default_coastal_sea"];
17
18#[derive(Clone, Debug, Default)]
19pub struct ProvinceTerrains {
20    provinces: TigerHashMap<ProvId, ProvinceTerrain>,
21    file_loc: Option<Loc>,
22    defaults: [Option<Token>; DEFAULT_TERRAINS.len()],
23}
24
25impl ProvinceTerrains {
26    fn load_item(&mut self, id: ProvId, key: Token, value: Token) {
27        if let Some(province) = self.provinces.get_mut(&id) {
28            if province.key.loc.kind >= key.loc.kind {
29                dup_error(&key, &province.key, "province");
30            }
31            *province = ProvinceTerrain::new(key, value);
32        } else {
33            self.provinces.insert(id, ProvinceTerrain::new(key, value));
34        }
35    }
36
37    pub fn validate(&self, data: &Everything) {
38        for (provid, item) in &self.provinces {
39            item.validate(*provid, data);
40        }
41
42        // If no file was handled, for example when using `replace_paths`, self.file_loc is None.
43        // TODO: Find a way to denote `ErrorLoc` error report when no loc is available.
44        if let Some(loc) = self.file_loc {
45            for (name, terrains) in DEFAULT_TERRAINS.iter().zip(&self.defaults) {
46                if let Some(terrain) = terrains {
47                    data.verify_exists(Item::Terrain, terrain);
48                } else {
49                    let msg = format!("missing default terrain: {name}");
50                    warn(ErrorKey::Validation).msg(msg).loc(loc).push();
51                }
52            }
53        }
54    }
55}
56
57impl FileHandler<Block> for ProvinceTerrains {
58    fn subpath(&self) -> std::path::PathBuf {
59        PathBuf::from("common/province_terrain")
60    }
61
62    fn load_file(&self, entry: &FileEntry, parser: &ParserMemory) -> Option<Block> {
63        if !entry.filename().to_string_lossy().ends_with("province_terrain.txt") {
64            // Omit _province_properties.txt
65            return None;
66        }
67
68        PdxFile::read_detect_encoding(entry, parser)
69    }
70
71    fn handle_file(&mut self, _entry: &FileEntry, mut block: Block) {
72        self.file_loc = Some(block.loc);
73        for (key, value) in block.drain_assignments_warn() {
74            if let Ok(id) = key.as_str().parse() {
75                self.load_item(id, key, value);
76            } else if let Some(index) = DEFAULT_TERRAINS.iter().position(|&x| x == key.as_str()) {
77                if let Some(default) = &self.defaults[index] {
78                    if default.loc.kind >= key.loc.kind {
79                        dup_error(&key, default, "default terrain");
80                    }
81                } else {
82                    self.defaults[index] = Some(value);
83                }
84            } else {
85                let msg = "unexpected key, expected only province ids or default terrains";
86                warn(ErrorKey::Validation).msg(msg).loc(key).push();
87            }
88        }
89    }
90}
91
92#[derive(Clone, Debug)]
93pub struct ProvinceTerrain {
94    key: Token,
95    terrain: Token,
96}
97
98impl ProvinceTerrain {
99    fn new(key: Token, terrain: Token) -> Self {
100        Self { key, terrain }
101    }
102
103    fn validate(&self, provid: ProvId, data: &Everything) {
104        data.provinces_ck3.verify_exists_provid(provid, &self.key, Severity::Error);
105        data.verify_exists(Item::Terrain, &self.terrain);
106    }
107}
108
109#[derive(Clone, Debug, Default)]
110pub struct ProvinceProperties {
111    provinces: TigerHashMap<ProvId, ProvinceProperty>,
112}
113
114impl ProvinceProperties {
115    fn load_item(&mut self, id: ProvId, key: Token, mut block: Block) {
116        if let Some(province) = self.provinces.get_mut(&id) {
117            // Multiple entries are valid but could easily be a mistake.
118            if province.key.loc.kind >= key.loc.kind {
119                dup_error(&key, &province.key, "province");
120            }
121            province.block.append(&mut block);
122        } else {
123            self.provinces.insert(id, ProvinceProperty::new(key, block));
124        }
125    }
126
127    pub fn validate(&self, data: &Everything) {
128        for (provid, item) in &self.provinces {
129            item.validate(*provid, data);
130        }
131    }
132}
133
134impl FileHandler<Block> for ProvinceProperties {
135    fn subpath(&self) -> PathBuf {
136        PathBuf::from("common/province_terrain")
137    }
138
139    fn load_file(&self, entry: &FileEntry, parser: &ParserMemory) -> Option<Block> {
140        if !entry.filename().to_string_lossy().ends_with("province_properties.txt") {
141            // Omit _province_terrain.txt
142            return None;
143        }
144        PdxFile::read_detect_encoding(entry, parser)
145    }
146
147    fn handle_file(&mut self, _entry: &FileEntry, mut block: Block) {
148        for (key, block) in block.drain_definitions_warn() {
149            if let Ok(id) = key.as_str().parse() {
150                self.load_item(id, key, block);
151            } else {
152                let msg = "unexpected key, expected only province ids";
153                warn(ErrorKey::Validation).msg(msg).loc(key).push();
154            }
155        }
156    }
157}
158
159#[derive(Clone, Debug)]
160pub struct ProvinceProperty {
161    key: Token,
162    block: Block,
163}
164
165impl ProvinceProperty {
166    fn new(key: Token, block: Block) -> Self {
167        Self { key, block }
168    }
169
170    fn validate(&self, provid: ProvId, data: &Everything) {
171        data.provinces_ck3.verify_exists_provid(provid, &self.key, Severity::Error);
172        let mut vd = Validator::new(&self.block, data);
173        if data.provinces_ck3.is_sea_or_river(provid) {
174            vd.field_validated_value("winter_severity_bias", |_, mut vd| {
175                vd.maybe_is("0.0");
176            });
177            vd.ban_field("mild_winter_factor_override", || "sea and river province");
178            vd.ban_field("normal_winter_factor_override", || "sea and river province");
179            vd.ban_field("harsh_winter_factor_override", || "sea and river province");
180        } else {
181            vd.field_numeric_range("winter_severity_bias", 0.0..=1.0);
182            vd.field_numeric_range("mild_winter_factor_override", 0.0..=1.0);
183            vd.field_numeric_range("normal_winter_factor_override", 0.0..=1.0);
184            vd.field_numeric_range("harsh_winter_factor_override", 0.0..=1.0);
185        }
186    }
187}