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 std::path::PathBuf;

use crate::block::Block;
use crate::ck3::validate::{validate_cost, validate_maa_stats};
use crate::context::ScopeContext;
use crate::everything::Everything;
use crate::fileset::{FileEntry, FileHandler};
use crate::helpers::{TigerHashMap, TigerHashSet, dup_error};
use crate::item::Item;
use crate::lowercase::Lowercase;
use crate::parse::ParserMemory;
use crate::pdxfile::PdxFile;
use crate::report::{ErrorKey, Severity, warn};
use crate::scopes::Scopes;
use crate::token::Token;
use crate::tooltipped::Tooltipped;
use crate::validator::Validator;
use crate::variables::Variables;

#[derive(Clone, Debug, Default)]
pub struct MenAtArmsTypes {
    menatarmsbasetypes: TigerHashSet<Token>,
    menatarmstypes: TigerHashMap<&'static str, MenAtArmsType>,
    menatarmsbasetypes_lc: TigerHashSet<Lowercase<'static>>,
}

impl MenAtArmsTypes {
    pub fn load_item(&mut self, key: Token, block: Block) {
        if let Some(other) = self.menatarmstypes.get(key.as_str())
            && other.key.loc.kind == key.loc.kind
        {
            dup_error(&key, &other.key, "men-at-arms type");
        }

        self.menatarmstypes.insert(key.as_str(), MenAtArmsType::new(key, block));
    }

    pub fn scan_variables(&self, registry: &mut Variables) {
        for item in self.menatarmstypes.values() {
            registry.scan(&item.block);
        }
    }

    pub fn base_exists(&self, key: &str) -> bool {
        self.menatarmsbasetypes.contains(key)
    }

    pub fn base_exists_lc(&self, key: &Lowercase) -> bool {
        self.menatarmsbasetypes_lc.contains(key)
    }

    pub fn iter_base_keys(&self) -> impl Iterator<Item = &Token> {
        self.menatarmsbasetypes.iter()
    }

    pub fn exists(&self, key: &str) -> bool {
        self.menatarmstypes.contains_key(key)
    }

    pub fn iter_keys(&self) -> impl Iterator<Item = &Token> {
        self.menatarmstypes.values().map(|item| &item.key)
    }

    pub fn validate(&self, data: &Everything) {
        for item in self.menatarmstypes.values() {
            item.validate(data);
        }
    }
}

impl FileHandler<Block> for MenAtArmsTypes {
    fn subpath(&self) -> PathBuf {
        PathBuf::from("common/men_at_arms_types")
    }

    fn load_file(&self, entry: &FileEntry, parser: &ParserMemory) -> Option<Block> {
        if !entry.filename().to_string_lossy().ends_with(".txt") {
            return None;
        }

        PdxFile::read(entry, parser)
    }

    fn handle_file(&mut self, _entry: &FileEntry, mut block: Block) {
        for (key, block) in block.drain_definitions_warn() {
            self.load_item(key, block);
        }
    }

    fn finalize(&mut self) {
        for maa in self.menatarmstypes.values() {
            if let Some(base) = maa.block.get_field_value("type") {
                self.menatarmsbasetypes.insert(base.clone());
                self.menatarmsbasetypes_lc.insert(Lowercase::new(base.as_str()));
            }
        }
    }
}

#[derive(Clone, Debug)]
pub struct MenAtArmsType {
    key: Token,
    block: Block,
}

impl MenAtArmsType {
    pub fn new(key: Token, block: Block) -> Self {
        MenAtArmsType { key, block }
    }

    pub fn validate(&self, data: &Everything) {
        let mut vd = Validator::new(&self.block, data);

        vd.req_field("type");
        vd.field_item("type", Item::MenAtArmsBase);
        if let Some(base) = self.block.get_field_value("type") {
            let modif = format!("stationed_{base}_damage_add");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
            let modif = format!("stationed_{base}_damage_mult");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
            let modif = format!("stationed_{base}_pursuit_add");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
            let modif = format!("stationed_{base}_pursuit_mult");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
            let modif = format!("stationed_{base}_screen_add");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
            let modif = format!("stationed_{base}_screen_mult");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
            let modif = format!("stationed_{base}_toughness_add");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
            let modif = format!("stationed_{base}_toughness_mult");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
            let modif = format!("stationed_{base}_siege_value_add");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
            let modif = format!("stationed_{base}_siege_value_mult");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);

            let modif = format!("{base}_damage_add");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
            let modif = format!("{base}_damage_mult");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
            let modif = format!("{base}_pursuit_add");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
            let modif = format!("{base}_pursuit_mult");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
            let modif = format!("{base}_screen_add");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
            let modif = format!("{base}_screen_mult");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
            let modif = format!("{base}_toughness_add");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
            let modif = format!("{base}_toughness_mult");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
            let modif = format!("{base}_recruitment_cost_mult");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
            let modif = format!("{base}_siege_value_add");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
            let modif = format!("{base}_siege_value_mult");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
            let modif = format!("{base}_maintenance_mult");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
            let modif = format!("{base}_max_size_add");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
            let modif = format!("{base}_max_size_mult");
            data.verify_exists_implied(Item::ModifierFormat, &modif, base);
        }

        data.verify_exists(Item::Localization, &self.key);
        let loca = format!("{}_flavor", &self.key);
        data.verify_exists_implied(Item::Localization, &loca, &self.key);

        vd.multi_field_validated_block("illustration", |block, data| {
            let mut vd = Validator::new(block, data);
            vd.field_trigger_rooted("trigger", Tooltipped::No, Scopes::Culture);
            vd.field_icon("reference", "NGameIcons|REGIMENTYPE_HORIZONTAL_IMAGE_PATH", ".dds");
            vd.field_icon("reference", "NGameIcons|REGIMENTYPE_VERTICAL_IMAGE_PATH", ".dds");
        });

        if let Some(icon) = vd.field_value("icon") {
            data.verify_icon("NGameIcons|REGIMENTYPE_ICON_PATH", icon, ".dds");
            data.verify_icon("NGameIcons|REGIMENTYPE_HORIZONTAL_IMAGE_PATH", icon, ".dds");
            data.verify_icon("NGameIcons|REGIMENTYPE_VERTICAL_IMAGE_PATH", icon, ".dds");
        } else if let Some(base) = self.block.get_field_value("type") {
            for define in &[
                "NGameIcons|REGIMENTYPE_ICON_PATH",
                "NGameIcons|REGIMENTYPE_HORIZONTAL_IMAGE_PATH",
                "NGameIcons|REGIMENTYPE_VERTICAL_IMAGE_PATH",
            ] {
                if let Some(icon_path) = data.get_defined_string_warn(&self.key, define) {
                    let base_path = format!("{icon_path}/{base}.dds");
                    let path = format!("{icon_path}/{}.dds", self.key);
                    data.mark_used(Item::File, &base_path);
                    if !data.fileset.exists(&base_path) {
                        data.verify_exists_implied_max_sev(
                            Item::File,
                            &path,
                            &self.key,
                            Severity::Warning,
                        );
                    }
                }
            }
        }

        // TODO: "use this instead of `can_recruit = { always = no }`"
        vd.field_bool("special_recruit_only");
        // TODO: "Mutually exclusive with being unlocked by innovation"
        vd.field_trigger_builder("can_recruit", Tooltipped::Yes, |key| {
            let mut sc = ScopeContext::new(Scopes::Character, key);
            sc.define_name("title", Scopes::LandedTitle, key);
            sc
        });

        vd.field_trigger_rooted("should_show_when_unavailable", Tooltipped::No, Scopes::Character);
        vd.field_trigger_rooted("access_through_subject", Tooltipped::No, Scopes::Character);

        vd.field_integer("max");
        validate_maa_stats(&mut vd);
        vd.field_integer("siege_tier");
        vd.field_bool("fights_in_main_phase");

        for field in &["buy_cost", "low_maintenance_cost", "high_maintenance_cost"] {
            vd.field_validated_key_block(field, |key, block, data| {
                let mut sc = ScopeContext::new(Scopes::Character, key);
                validate_cost(block, data, &mut sc);
            });
        }

        vd.field_validated_block("terrain_bonus", validate_terrain_bonus);
        vd.field_validated_block("holding_bonus", validate_holding_bonus);
        vd.field_validated_block("winter_bonus", validate_winter_bonus);
        vd.field_validated_block("era_bonus", validate_era_bonus);
        vd.field_validated_block("counters", validate_counters);

        vd.field_numeric("stack");
        vd.field_numeric("hired_stack_size");
        vd.field_integer("max_sub_regiments");
        vd.field_numeric("provision_cost");

        vd.field_script_value_rooted("ai_quality", Scopes::Character);
        vd.field_bool("allowed_in_hired_troops");
        vd.field_bool("fallback_in_hired_troops_if_unlocked");
        vd.field_bool("mercenary_fallback");
        vd.field_bool("holy_order_fallback");

        // undocumented

        vd.field_integer("max_regiments");
    }
}

pub fn validate_terrain_bonus(block: &Block, data: &Everything) {
    let mut vd = Validator::new(block, data);
    vd.unknown_block_fields(|key, block| {
        data.verify_exists(Item::Terrain, key);
        let mut vd = Validator::new(block, data);
        validate_maa_stats(&mut vd);
    });
}

pub fn validate_holding_bonus(block: &Block, data: &Everything) {
    let mut vd = Validator::new(block, data);
    vd.unknown_block_fields(|key, block| {
        data.verify_exists(Item::HoldingType, key);
        let mut vd = Validator::new(block, data);
        validate_maa_stats(&mut vd);
    });
}

pub fn validate_winter_bonus(block: &Block, data: &Everything) {
    let mut vd = Validator::new(block, data);
    vd.unknown_block_fields(|key, block| {
        if !(key.is("harsh_winter") || key.is("normal_winter")) {
            warn(ErrorKey::Validation).msg("unknown winter type").loc(key).push();
        }
        let mut vd = Validator::new(block, data);
        validate_maa_stats(&mut vd);
    });
}

fn validate_era_bonus(block: &Block, data: &Everything) {
    let mut vd = Validator::new(block, data);
    vd.unknown_block_fields(|key, block| {
        data.verify_exists(Item::CultureEra, key);
        let mut vd = Validator::new(block, data);
        validate_maa_stats(&mut vd);
    });
}

fn validate_counters(block: &Block, data: &Everything) {
    let mut vd = Validator::new(block, data);
    for key in &data.menatarmstypes.menatarmsbasetypes {
        vd.field_numeric(key.as_str());
    }
}