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 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 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 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 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}