tiger_lib/ck3/data/
houses.rs

1use crate::block::Block;
2use crate::ck3::modif::ModifKinds;
3use crate::ck3::validate::validate_cost;
4use crate::context::ScopeContext;
5use crate::db::{Db, DbKind};
6use crate::everything::Everything;
7use crate::game::GameFlags;
8use crate::item::{Item, ItemLoader};
9use crate::modif::validate_modifs;
10use crate::report::Severity;
11use crate::scopes::Scopes;
12use crate::token::Token;
13use crate::tooltipped::Tooltipped;
14use crate::validator::{Validator, ValueValidator};
15
16#[derive(Clone, Debug)]
17pub struct House {}
18
19inventory::submit! {
20    ItemLoader::Normal(GameFlags::Ck3, Item::House, House::add)
21}
22
23impl House {
24    pub fn add(db: &mut Db, key: Token, block: Block) {
25        db.add(Item::House, key, block, Box::new(Self {}));
26    }
27
28    pub fn get_dynasty<'a>(key: &str, data: &'a Everything) -> Option<&'a Token> {
29        data.database
30            .get_key_block(Item::House, key)
31            .and_then(|(_, block)| block.get_field_value("dynasty"))
32    }
33}
34
35impl DbKind for House {
36    fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
37        let mut vd = Validator::new(block, data);
38
39        vd.req_field("name");
40        vd.req_field("dynasty");
41
42        vd.field_item("name", Item::Localization);
43        vd.field_item("prefix", Item::Localization);
44        vd.field_item("motto", Item::Localization);
45        vd.field_item("dynasty", Item::Dynasty);
46        vd.field_value("forced_coa_religiongroup"); // TODO
47    }
48}
49
50#[derive(Clone, Debug)]
51pub struct HouseAspiration {}
52
53inventory::submit! {
54    ItemLoader::Normal(GameFlags::Ck3, Item::HouseAspiration, HouseAspiration::add)
55}
56
57impl HouseAspiration {
58    pub fn add(db: &mut Db, key: Token, block: Block) {
59        db.add(Item::HouseAspiration, key, block, Box::new(Self {}));
60    }
61}
62
63impl DbKind for HouseAspiration {
64    fn add_subitems(&self, _key: &Token, block: &Block, db: &mut Db) {
65        for block in block.get_field_blocks("level") {
66            if let Some(block) = block.get_field_block("parameters") {
67                for (key, value) in block.iter_assignments() {
68                    if value.lowercase_is("yes") || value.lowercase_is("no") {
69                        db.add_flag(Item::BooleanHousePowerParameter, key.clone());
70                    }
71                }
72            }
73            if let Some(block) = block.get_field_block("house_head_parameters") {
74                for (key, value) in block.iter_assignments() {
75                    if value.lowercase_is("yes") || value.lowercase_is("no") {
76                        db.add_flag(Item::BooleanHouseHeadParameter, key.clone());
77                    }
78                }
79            }
80        }
81    }
82
83    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
84        let mut vd = Validator::new(block, data);
85
86        let loca = format!("{key}_house_power");
87        data.verify_exists_implied(Item::Localization, &loca, key);
88
89        data.verify_icon("NGameIcons|HOUSE_POWER_BONUS_ICON_PATH", key, ".dds");
90
91        vd.field_bool("show_in_main_hud");
92        vd.field_trigger_rooted("is_shown", Tooltipped::No, Scopes::DynastyHouse);
93
94        vd.field_bool("is_default");
95
96        vd.req_field("level");
97        vd.multi_field_validated_block("level", |block, data| {
98            let mut vd = Validator::new(block, data);
99            vd.field_validated_key_block("cost", |key, block, data| {
100                let mut sc = ScopeContext::new(Scopes::Character, key);
101                validate_cost(block, data, &mut sc);
102            });
103            for field in &[
104                "powerful_family_top_liege_modifier",
105                "powerful_family_member_modifier",
106                "any_house_member_modifier",
107                "house_head_modifier",
108            ] {
109                vd.field_validated_block(field, |block, data| {
110                    let vd = Validator::new(block, data);
111                    validate_modifs(block, data, ModifKinds::Character, vd);
112                });
113            }
114
115            vd.field_script_value_no_breakdown_rooted("ai_score", Scopes::Character);
116            for field in &["parameters", "house_head_parameters"] {
117                vd.field_validated_block(field, |block, data| {
118                    let mut vd = Validator::new(block, data);
119                    vd.unknown_value_fields(|key, value| {
120                        let loca = format!("house_power_parameter_{key}");
121                        data.verify_exists_implied(Item::Localization, &loca, key);
122
123                        let mut vvd = ValueValidator::new(value, data);
124                        vvd.bool();
125                    });
126                });
127            }
128
129            vd.field_bool("can_request_great_project_contributions_from_allies");
130            vd.field_trigger_rooted("can_upgrade", Tooltipped::Yes, Scopes::Character);
131        });
132
133        vd.field_item("illustration", Item::File);
134
135        // TODO: figure out if this takes script values, and if so, what's the scope context.
136        vd.field_validated_block("cooldown", |block, data| {
137            let mut vd = Validator::new(block, data);
138            vd.field_integer("days");
139            vd.field_integer("weeks");
140            vd.field_integer("months");
141            vd.field_integer("years");
142        });
143
144        for field in &["on_changed", "on_upgraded"] {
145            vd.field_effect_builder(field, Tooltipped::Yes, |key| {
146                let mut sc = ScopeContext::new(Scopes::Character, key);
147                sc.define_name("house", Scopes::DynastyHouse, key);
148                sc
149            });
150        }
151
152        // undocumented
153
154        vd.field_item("confederation_type", Item::ConfederationType);
155    }
156}
157
158#[derive(Clone, Debug)]
159pub struct HouseRelationType {}
160
161inventory::submit! {
162    ItemLoader::Normal(GameFlags::Ck3, Item::HouseRelationType, HouseRelationType::add)
163}
164
165impl HouseRelationType {
166    pub fn add(db: &mut Db, key: Token, block: Block) {
167        db.add(Item::HouseRelationType, key, block, Box::new(Self {}));
168    }
169}
170
171impl DbKind for HouseRelationType {
172    fn add_subitems(&self, _key: &Token, block: &Block, db: &mut Db) {
173        if let Some(block) = block.get_field_block("levels") {
174            for (key, block) in block.iter_definitions() {
175                db.add_flag(Item::HouseRelationLevel, key.clone());
176                if let Some(block) = block.get_field_block("parameters") {
177                    for value in block.iter_values() {
178                        db.add_flag(Item::BooleanHouseRelationParameter, value.clone());
179                    }
180                }
181            }
182        }
183    }
184
185    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
186        data.verify_exists(Item::Localization, key);
187
188        let mut vd = Validator::new(block, data);
189        // TODO: check that the level is part of this relation type
190        vd.field_item("neutral_level", Item::HouseRelationLevel);
191
192        vd.req_field("levels");
193        vd.field_validated_block("levels", |block, data| {
194            let mut vd = Validator::new(block, data);
195            vd.unknown_block_fields(|k, block| {
196                let loca = format!("{key}_level_{k}");
197                data.verify_exists_implied(Item::Localization, &loca, key);
198                let loca = format!("{key}_level_{k}_desc");
199                data.verify_exists_implied(Item::Localization, &loca, key);
200                if let Some(icon_path) = data.get_defined_string_warn(
201                    k,
202                    "NGameIcons|HOUSE_RELATION_LEVEL_RENDERED_ICON_PATH",
203                ) {
204                    let pathname = format!("{icon_path}/{key}_level_{k}_rendered_icon.dds");
205                    data.verify_exists_implied_max_sev(Item::File, &pathname, k, Severity::Warning);
206                }
207                if let Some(icon_path) = data
208                    .get_defined_string_warn(k, "NGameIcons|HOUSE_RELATION_LEVEL_FLAT_ICON_PATH")
209                {
210                    let pathname = format!("{icon_path}/{key}_level_{k}_flat_icon.dds");
211                    data.verify_exists_implied_max_sev(Item::File, &pathname, k, Severity::Warning);
212                }
213
214                let mut vd = Validator::new(block, data);
215                vd.field_integer("opinion");
216                vd.field_integer("cohesion_contribution");
217                vd.field_validated_list("parameters", |value, data| {
218                    let loca = format!("house_relation_parameter_{value}");
219                    data.verify_exists_implied(Item::Localization, &loca, value);
220                });
221            });
222        });
223    }
224}