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::data::titles::Tier;
use crate::date::Date;
use crate::everything::Everything;
use crate::fileset::{FileEntry, FileHandler};
use crate::helpers::TigerHashMap;
use crate::item::Item;
use crate::parse::ParserMemory;
use crate::pdxfile::PdxFile;
use crate::report::{ErrorKey, err, 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 TitleHistories {
    histories: TigerHashMap<&'static str, TitleHistory>,
}

impl TitleHistories {
    pub fn load_item(&mut self, key: Token, mut block: Block) {
        if let Some(other) = self.histories.get_mut(key.as_str()) {
            // Multiple entries are valid but could easily be a mistake.
            if other.key.loc.kind >= key.loc.kind {
                warn(ErrorKey::DuplicateItem)
                    .msg("title has two definition blocks, they will be added together")
                    .loc(&other.key)
                    .loc_msg(key, "the other one is here")
                    .push();
            }
            other.block.append(&mut block);
        } else {
            self.histories.insert(key.as_str(), TitleHistory::new(key.clone(), block));
        }
    }

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

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

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

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

    pub fn verify_has_holder(&self, key: &Token, date: Date, data: &Everything, overlord: &str) {
        if let Some(item) = self.histories.get(key.as_str()) {
            item.verify_has_holder(key, date, data, overlord);
        } else {
            let msg = format!("{key} has no title history");
            err(ErrorKey::MissingItem).msg(msg).loc(key).push();
        }
    }
}

impl FileHandler<Block> for TitleHistories {
    fn subpath(&self) -> PathBuf {
        PathBuf::from("history/titles")
    }

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

        PdxFile::read_detect_encoding(entry, parser)
    }

    fn handle_file(&mut self, _entry: &FileEntry, mut block: Block) {
        for (key, block) in block.drain_definitions_warn() {
            if Tier::try_from(&key).is_ok() {
                self.load_item(key, block);
            } else {
                warn(ErrorKey::Validation).msg("expected title").loc(key).push();
            }
        }
    }
}

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

impl TitleHistory {
    pub fn new(key: Token, block: Block) -> Self {
        let tier = Tier::try_from(&key).unwrap(); // guaranteed by caller
        Self { key, block, tier }
    }

    pub fn verify_has_holder(&self, token: &Token, date: Date, data: &Everything, overlord: &str) {
        let info = format!("setting the {overlord} will not have effect here");

        if let Some(holder) = self.block.get_field_at_date("holder", date) {
            // if holder is not a value then we already warned about that
            if let Some(holder) = holder.get_value() {
                if holder.is("0") {
                    let msg = format!("{token} has no holder at {date}");
                    err(ErrorKey::History).msg(msg).info(info).loc(token).push();
                } else if !data.characters.is_alive(holder, date) {
                    let msg = format!("holder of {token} is not alive at {date}");
                    err(ErrorKey::History).msg(msg).info(info).loc(token).push();
                }
            }
        } else {
            let msg = format!("{token} has no holder at {date}");
            err(ErrorKey::History).msg(msg).info(info).loc(token).push();
        }
    }

    pub fn validate_history(&self, date: Date, block: &Block, data: &Everything) {
        let mut vd = Validator::new(block, data);
        vd.field_numeric("change_development_level");
        if let Some(token) = vd.field_value("holder")
            && !token.is("0")
        {
            data.verify_exists(Item::Character, token);
            if data.item_exists(Item::Character, token.as_str()) {
                data.characters.verify_alive(token, date);
            }
        }
        if let Some(token) = vd.field_value("holder_ignore_head_of_faith_requirement")
            && !token.is("0")
        {
            data.verify_exists(Item::Character, token);
            if data.item_exists(Item::Character, token.as_str()) {
                data.characters.verify_alive(token, date);
            }
        }

        if let Some(token) = vd.field_value("liege")
            && !token.is("0")
        {
            data.verify_exists(Item::Title, token);
            if let Some(title) = data.titles.get(token.as_str()) {
                if title.tier <= self.tier {
                    let msg = format!("liege must be higher tier than {}", self.key);
                    err(ErrorKey::TitleTier).msg(msg).loc(token).push();
                }
                data.title_history.verify_has_holder(token, date, data, "liege");
            }
        }

        if let Some(token) = vd.field_value("de_jure_liege")
            && !token.is("0")
        {
            data.verify_exists(Item::Title, token);
            if let Some(title) = data.titles.get(token.as_str())
                && title.tier <= self.tier
            {
                let msg = format!("liege must be higher tier than {}", self.key);
                err(ErrorKey::TitleTier).msg(msg).loc(token).push();
            }
        }

        vd.field_validated_block("tributary_of", |block, data| {
            let mut vd = Validator::new(block, data);
            vd.field_validated_value("suzerain", |_, mut vvd| {
                vvd.item(Item::Title);
                data.title_history.verify_has_holder(vvd.value(), date, data, "suzerain");
            });
            vd.field_item("contract_group", Item::SubjectContractGroup);
        });

        vd.field_item("government", Item::GovernmentType);

        vd.field_block("succession_laws"); // TODO
        vd.field_bool("remove_succession_laws");

        vd.field_item("name", Item::Localization);
        vd.field_bool("reset_name");

        vd.field_item("insert_title_history", Item::TitleHistory);

        vd.field_effect_rooted("effect", Tooltipped::No, Scopes::LandedTitle);
    }

    pub fn validate(&self, data: &Everything) {
        data.verify_exists(Item::Title, &self.key);

        let mut vd = Validator::new(&self.block, data);
        vd.validate_history_blocks(|date, _key, block, data| {
            self.validate_history(date, block, data);
        });
    }
}