tiger-lib 1.18.0

Library used by the tools ck3-tiger, vic3-tiger, and imperator-tiger. This library holds the bulk of the code for them. It can be built either for ck3-tiger with the feature ck3, or for vic3-tiger with the feature vic3, or for imperator-tiger with the feature imperator, but not both at the same time.
Documentation
use crate::block::{BV, Block};
use crate::ck3::modif::ModifKinds;
use crate::ck3::validate::validate_cost;
use crate::context::ScopeContext;
use crate::db::{Db, DbKind};
use crate::desc::validate_desc;
use crate::everything::Everything;
use crate::game::GameFlags;
use crate::item::{Item, ItemLoader};
use crate::modif::validate_modifs;
use crate::scopes::Scopes;
use crate::script_value::validate_non_dynamic_script_value;
use crate::token::Token;
use crate::tooltipped::Tooltipped;
use crate::validate::validate_modifiers_with_base;
use crate::validator::Validator;

#[derive(Clone, Debug)]
pub struct Building {
    is_upgrade: bool,
}

inventory::submit! {
    ItemLoader::Normal(GameFlags::Ck3, Item::Building, Building::add)
}

impl Building {
    pub fn new() -> Self {
        Self { is_upgrade: false }
    }

    pub fn add(db: &mut Db, key: Token, block: Block) {
        db.add(Item::Building, key, block, Box::new(Self::new()));
    }

    pub fn finalize(db: &mut Db) {
        let mut upgrades = Vec::new();
        for (_, block) in db.iter_key_block(Item::Building) {
            if let Some(token) = block.get_field_value("next_building") {
                upgrades.push(token.to_string());
            }
        }
        for upgrade in upgrades {
            db.set_property(Item::Building, &upgrade, "is_upgrade");
        }
    }
}

impl DbKind for Building {
    fn add_subitems(&self, key: &Token, block: &Block, db: &mut Db) {
        for token in block.get_field_values("flag") {
            db.add_flag(Item::BuildingFlag, token.clone());
        }
        if block.field_value_is("type", "special") {
            db.add_flag(Item::SpecialBuilding, key.clone());
        }
    }

    fn validate(&self, key: &Token, block: &Block, data: &Everything) {
        fn sc_builder(key: &Token) -> ScopeContext {
            let mut sc = ScopeContext::new(Scopes::Province, key);
            sc.define_name("holder", Scopes::Character, key);
            sc.define_name("county", Scopes::LandedTitle, key);
            sc
        }

        let mut vd = Validator::new(block, data);

        let graphical_only = block.get_field_bool("is_graphical_background").unwrap_or(false);
        if !graphical_only {
            let loca = format!("building_{key}");
            data.verify_exists_implied(Item::Localization, &loca, key);
            let loca = format!("building_{key}_desc");
            data.verify_exists_implied(Item::Localization, &loca, key);
            if !self.is_upgrade {
                let loca = format!("building_type_{key}");
                data.verify_exists_implied(Item::Localization, &loca, key);
                let loca = format!("building_type_{key}_desc");
                data.verify_exists_implied(Item::Localization, &loca, key);
            }
        }

        vd.field_icon("type_icon", "NGameIcons|BUILDING_TYPE_ICON_PATH", "");

        // TODO: int
        vd.field_validated("levy", validate_non_dynamic_script_value);
        // TODO: int
        vd.field_validated("max_garrison", validate_non_dynamic_script_value);
        // TODO: 0..1
        vd.field_validated("garrison_reinforcement_factor", validate_non_dynamic_script_value);
        // TODO: int
        vd.field_validated("construction_time", validate_non_dynamic_script_value);
        vd.field_choice("type", &["regular", "special", "duchy_capital", "great_building"]);

        vd.multi_field_validated_block("assets", |block, data| {
            let mut vd = Validator::new(block, data);
            vd.multi_field_validated_block("asset", validate_asset);
        });
        vd.multi_field_validated_block("asset", validate_asset);

        vd.field_trigger_builder(
            "is_enabled",
            if graphical_only { Tooltipped::No } else { Tooltipped::FailuresOnly },
            sc_builder,
        );

        // TODO: only for Great Buildings
        vd.field_trigger_builder("can_rebuild", Tooltipped::Yes, sc_builder);

        // For buildings that are upgrades, can_construct_potential is added to can_construct_showing_failures_only so it will be tooltipped
        vd.field_trigger_builder(
            "can_construct_potential",
            if block.get_field_bool("show_disabled").unwrap_or(false) || self.is_upgrade {
                Tooltipped::FailuresOnly
            } else {
                Tooltipped::No
            },
            sc_builder,
        );

        vd.field_trigger_builder(
            "can_construct_showing_failures_only",
            Tooltipped::FailuresOnly,
            sc_builder,
        );

        vd.field_trigger_builder("can_construct", Tooltipped::Yes, sc_builder);
        vd.field_bool("show_disabled");

        vd.field_script_value_rooted("cost_gold", Scopes::Character);
        vd.field_script_value_rooted("cost_piety", Scopes::Character);
        vd.field_script_value_rooted("cost_prestige", Scopes::Character);
        vd.field_validated_block_rooted("cost", Scopes::Character, validate_cost);

        vd.field_item("next_building", Item::Building);
        vd.field_validated_rooted("effect_desc", Scopes::None, validate_desc);

        let is_duchy_capital = block.get_field_value("type").is_some_and(|t| t.is("duchy_capital"));
        validate_modifiers(&mut vd, is_duchy_capital);
        // TODO: fallback requires an `is_enabled` trigger to be configured as well
        vd.field_validated_block("fallback", |block, data| {
            let mut vd = Validator::new(block, data);
            validate_modifiers(&mut vd, is_duchy_capital);
        });

        vd.multi_field_value("flag");

        vd.field_validated_key("ai_value", |key, bv, data| match bv {
            BV::Value(token) => {
                token.expect_integer();
            }
            BV::Block(block) => {
                let mut sc = ScopeContext::new(Scopes::Province, key);
                // TODO: docs say scope:character but script uses holder
                sc.define_name("holder", Scopes::Character, key);
                sc.define_name("holding", Scopes::HoldingType, key);
                validate_modifiers_with_base(block, data, &mut sc);
            }
        });

        vd.field_bool("is_graphical_background");

        for field in ["on_start", "on_cancelled", "on_complete"] {
            vd.field_effect_builder(field, Tooltipped::No, |key| {
                let mut sc = ScopeContext::new(Scopes::Province, key);
                sc.define_name("character", Scopes::Character, key);
                sc.define_name("holding", Scopes::HoldingType, key);
                sc
            });
        }

        // TODO: only for Great Buildings
        vd.field_item("great_project_type", Item::GreatProjectType);

        // undocumented

        // TODO: only for Great Buildings
        vd.field_validated_block_rooted("rebuild_cost", Scopes::Character, validate_cost);
        vd.field_bool("is_mandala_capital");

        vd.field_validated_block("override_modifiers", |block, data| {
            let mut vd = Validator::new(block, data);
            vd.field_item("requires_dlc_flag", Item::DlcFeature);
            validate_modifiers(&mut vd, is_duchy_capital);
        });
    }

    fn set_property(&mut self, _key: &Token, _block: &Block, property: &str) {
        if property == "is_upgrade" {
            self.is_upgrade = true;
        }
    }
}

fn validate_asset(block: &Block, data: &Everything) {
    let mut vd = Validator::new(block, data);
    vd.req_field("type");
    vd.field_choice("type", &["pdxmesh", "entity"]);

    let meshes = block.field_value_is("type", "pdxmesh");
    let itype = if meshes { Item::Pdxmesh } else { Item::Entity };

    vd.multi_field_item("name", itype);
    vd.field_list_items("names", itype);

    vd.field_item("domicile_building", Item::DomicileBuilding);

    vd.field_item("illustration", Item::File);

    vd.multi_field_validated("soundeffect", |bv, data| {
        match bv {
            BV::Value(token) => data.verify_exists(Item::Sound, token),
            BV::Block(block) => {
                let mut vd = Validator::new(block, data);
                vd.req_field("soundeffect");
                vd.field_item("soundeffect", Item::Sound);
                vd.field_block("soundparameter"); // TODO
            }
        }
    });

    vd.field_list_items("graphical_cultures", Item::BuildingGfx);
    vd.field_list_items("graphical_faiths", Item::GraphicalFaith);
    vd.field_list_items("graphical_regions", Item::Region); // TODO check that it's a graphical region
    vd.field_list_items("provinces", Item::Province);
    vd.field_list_items("governments", Item::GovernmentType);

    vd.field_item("requires_dlc_flag", Item::DlcFeature);
}

fn validate_modifiers(vd: &mut Validator, is_duchy_capital: bool) {
    vd.multi_field_validated_block("character_modifier", |block, data| {
        let vd = Validator::new(block, data);
        validate_modifs(block, data, ModifKinds::Character, vd);
    });
    vd.multi_field_validated_block("character_culture_modifier", |block, data| {
        let mut vd = Validator::new(block, data);
        vd.req_field("parameter");
        vd.field_item("parameter", Item::CultureParameter);
        validate_modifs(block, data, ModifKinds::Character, vd);
    });
    vd.multi_field_validated_block("character_dynasty_modifier", |block, data| {
        let mut vd = Validator::new(block, data);
        vd.req_field("county_holder_dynasty_perk");
        vd.field_item("county_holder_dynasty_perk", Item::DynastyPerk);
        validate_modifs(block, data, ModifKinds::Character, vd);
    });
    vd.multi_field_validated_block("character_faith_modifier", |block, data| {
        let mut vd = Validator::new(block, data);
        vd.req_field("parameter");
        vd.field_item("parameter", Item::DoctrineParameter);
        validate_modifs(block, data, ModifKinds::Character, vd);
    });

    vd.multi_field_validated_block("province_modifier", |block, data| {
        let vd = Validator::new(block, data);
        validate_modifs(block, data, ModifKinds::Province, vd);
    });
    vd.multi_field_validated_block("province_culture_modifier", |block, data| {
        let mut vd = Validator::new(block, data);
        vd.req_field("parameter");
        vd.field_item("parameter", Item::CultureParameter);
        validate_modifs(block, data, ModifKinds::Province, vd);
    });
    vd.multi_field_validated_block("province_faith_modifier", |block, data| {
        let mut vd = Validator::new(block, data);
        vd.req_field("parameter");
        vd.field_item("parameter", Item::DoctrineParameter);
        validate_modifs(block, data, ModifKinds::Province, vd);
    });
    vd.multi_field_validated_block("province_terrain_modifier", |block, data| {
        let mut vd = Validator::new(block, data);
        vd.field_item("parameter", Item::CultureParameter);
        vd.field_item("terrain", Item::Terrain);
        vd.field_bool("is_coastal");
        validate_modifs(block, data, ModifKinds::Province, vd);
    });
    vd.multi_field_validated_block("province_dynasty_modifier", |block, data| {
        let mut vd = Validator::new(block, data);
        vd.req_field("county_holder_dynasty_perk");
        vd.field_item("county_holder_dynasty_perk", Item::DynastyPerk);
        validate_modifs(block, data, ModifKinds::Province, vd);
    });

    vd.multi_field_validated_block("county_modifier", |block, data| {
        let vd = Validator::new(block, data);
        validate_modifs(block, data, ModifKinds::County, vd);
    });
    vd.multi_field_validated_block("county_culture_modifier", |block, data| {
        let mut vd = Validator::new(block, data);
        vd.req_field("parameter");
        vd.field_item("parameter", Item::CultureParameter);
        validate_modifs(block, data, ModifKinds::County, vd);
    });
    vd.multi_field_validated_block("county_faith_modifier", |block, data| {
        let mut vd = Validator::new(block, data);
        vd.req_field("parameter");
        vd.field_item("parameter", Item::DoctrineParameter);
        validate_modifs(block, data, ModifKinds::County, vd);
    });

    if is_duchy_capital {
        vd.multi_field_validated_block("duchy_capital_county_modifier", |block, data| {
            let vd = Validator::new(block, data);
            validate_modifs(block, data, ModifKinds::County, vd);
        });
        vd.multi_field_validated_block("duchy_capital_county_culture_modifier", |block, data| {
            let mut vd = Validator::new(block, data);
            vd.req_field("parameter");
            vd.field_item("parameter", Item::CultureParameter);
            validate_modifs(block, data, ModifKinds::County, vd);
        });
        vd.multi_field_validated_block("duchy_capital_county_faith_modifier", |block, data| {
            let mut vd = Validator::new(block, data);
            vd.req_field("parameter");
            vd.field_item("parameter", Item::DoctrineParameter);
            validate_modifs(block, data, ModifKinds::County, vd);
        });

        vd.multi_field_validated_block("duchy_capital_county_situation_modifier", |block, data| {
            let mut vd = Validator::new(block, data);
            vd.req_field("parameter");
            vd.field_item("parameter", Item::SituationPhaseParameter);
            validate_modifs(block, data, ModifKinds::County, vd);
        });
    } else {
        vd.ban_field("duchy_capital_county_modifier", || "duchy_capital buildings");
        vd.ban_field("duchy_capital_county_culture_modifier", || "duchy_capital buildings");
        vd.ban_field("duchy_capital_county_situation_modifier", || "duchy_capital buildings");
    }

    vd.multi_field_validated_block("county_situation_modifier", |block, data| {
        let mut vd = Validator::new(block, data);
        vd.req_field("parameter");
        vd.field_item("parameter", Item::SituationPhaseParameter);
        validate_modifs(block, data, ModifKinds::County, vd);
    });

    vd.multi_field_validated_block("province_situation_modifier", |block, data| {
        let mut vd = Validator::new(block, data);
        vd.req_field("parameter");
        vd.field_item("parameter", Item::SituationPhaseParameter);
        validate_modifs(block, data, ModifKinds::Province, vd);
    });

    vd.multi_field_validated_block("character_situation_modifier", |block, data| {
        let mut vd = Validator::new(block, data);
        vd.req_field("parameter");
        vd.field_item("parameter", Item::SituationPhaseParameter);
        validate_modifs(block, data, ModifKinds::Character, vd);
    });

    vd.multi_field_validated_block("county_holding_modifier", |block, data| {
        let mut vd = Validator::new(block, data);
        vd.req_field("holding");
        vd.field_item("holding", Item::HoldingType);
        validate_modifs(block, data, ModifKinds::County, vd);
    });
    vd.multi_field_validated_block("county_dynasty_modifier", |block, data| {
        let mut vd = Validator::new(block, data);
        vd.req_field("county_holder_dynasty_perk");
        vd.field_item("county_holder_dynasty_perk", Item::DynastyPerk);
        validate_modifs(block, data, ModifKinds::County, vd);
    });
    vd.multi_field_validated_block("county_holder_character_modifier", |block, data| {
        let vd = Validator::new(block, data);
        validate_modifs(block, data, ModifKinds::Character, vd);
    });

    vd.multi_field_validated_block("province_government_modifier", |block, data| {
        let mut vd = Validator::new(block, data);
        vd.req_field("parameter");
        vd.field_item("parameter", Item::GovernmentFlag);
        validate_modifs(block, data, ModifKinds::Province, vd);
    });

    vd.multi_field_validated_block("character_government_modifier", |block, data| {
        let mut vd = Validator::new(block, data);
        vd.req_field("parameter");
        vd.field_item("parameter", Item::GovernmentFlag);
        validate_modifs(block, data, ModifKinds::Character, vd);
    });
}