tiger-lib 1.17.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::context::ScopeContext;
use crate::data::events::Event;
use crate::desc::validate_desc;
use crate::effect::{validate_effect, validate_effect_internal};
use crate::everything::Everything;
use crate::item::Item;
use crate::lowercase::Lowercase;
use crate::report::{ErrorKey, err};
use crate::scopes::Scopes;
use crate::special_tokens::SpecialTokens;
use crate::token::Token;
use crate::tooltipped::Tooltipped;
use crate::validate::{ListType, validate_ai_chance, validate_modifiers_with_base};
use crate::validator::Validator;

const EVENT_TYPES: &[&str] = &[
    "character_event",
    "country_event",
    "location_event",
    "unit_event",
    "exploration_event",
    "age_event",
];

const EVENT_OUTCOMES: &[&str] = &["positive", "neutral", "negative"];

const EVENT_CATEGORY: &[&str] =
    &["disaster_event", "situation_event", "international_organization_event"];

pub fn get_event_scope(key: &Token, block: &Block) -> (Scopes, Token) {
    if let Some(event_type) = block.get_field_value("type") {
        match event_type.as_str() {
            "character_event" => (Scopes::Character, event_type.clone()),
            "country_event" => (Scopes::Country, event_type.clone()),
            "location_event" => (Scopes::Location, event_type.clone()),
            "unit_event" => (Scopes::Unit, event_type.clone()),
            "exploration_event" => (Scopes::Exploration, event_type.clone()),
            "age_event" => (Scopes::Age, event_type.clone()),
            _ => (Scopes::Country, key.clone()),
        }
    } else {
        (Scopes::Country, key.clone())
    }
}

pub fn validate_event(event: &Event, data: &Everything, sc: &mut ScopeContext) {
    let mut vd = Validator::new(&event.block, data);

    let mut tooltipped_immediate = Tooltipped::Past;
    let mut tooltipped = Tooltipped::Yes;

    vd.field_choice("type", EVENT_TYPES);

    vd.field_bool("orphan");
    vd.field_bool("hidden");
    let hidden = event.block.field_value_is("hidden", "yes");
    if hidden {
        tooltipped_immediate = Tooltipped::No;
        tooltipped = Tooltipped::No;
    }

    vd.field_trigger("trigger", Tooltipped::No, sc);
    vd.field_trigger("major_trigger", Tooltipped::No, sc);
    vd.field_effect("on_trigger_fail", Tooltipped::No, sc);
    sc.wipe_temporaries();
    vd.field_validated_block_sc("weight_multiplier", sc, validate_modifiers_with_base);
    vd.field_effect("immediate", tooltipped_immediate, sc);
    vd.field_item("image", Item::File);

    vd.multi_field_validated_block("dynamic_historical_event", |block, data| {
        let mut vd = Validator::new(block, data);
        vd.req_field("monthly_chance");
        vd.field_numeric("monthly_chance");

        vd.field_date("from");
        vd.field_date("to");

        vd.multi_field("tag");
    });

    vd.field_bool("major");
    vd.field_bool("fire_only_once");
    vd.field_bool("interface_lock");
    vd.field_bool("hide_portraits");

    //TODO: eu5 match with "game/loading_screen/gfx/illustrations/database"
    // Can be formated as either:
    //  10 = interior
    //  interior = 10
    vd.field_block("illustration_tags");

    vd.field_choice("outcome", EVENT_OUTCOMES);
    vd.field_choice("category", EVENT_CATEGORY);

    vd.field_validated_sc("title", sc, validate_desc);
    vd.field_validated_sc("desc", sc, validate_desc);
    vd.field_validated_sc("historical_info", sc, validate_desc);

    if !hidden {
        vd.req_field("option");
    }
    let mut has_options = false;
    vd.multi_field_validated_block("option", |block, data| {
        has_options = true;
        sc.wipe_temporaries();
        validate_event_option(block, data, sc, tooltipped);
    });
    vd.field_validated_key_block("after", |key, block, data| {
        if !has_options {
            let msg = "`after` effect will not run if there are no `option` blocks";
            let info = "you can put it in `immediate` instead";
            err(ErrorKey::Logic).msg(msg).info(info).loc(key).push();
        }
        validate_effect(block, data, sc, tooltipped);
    });
}

fn validate_event_option(
    block: &Block,
    data: &Everything,
    sc: &mut ScopeContext,
    tooltipped: Tooltipped,
) {
    // TODO: warn if they use desc, first_valid, random_valid, or triggered_desc directly
    // in the name or tooltip.

    let mut vd = Validator::new(block, data);
    vd.multi_field_validated("name", |bv, data| match bv {
        BV::Value(t) => {
            data.verify_exists(Item::Localization, t);
        }
        BV::Block(b) => {
            let mut vd = Validator::new(b, data);
            vd.req_field("text");
            vd.field_trigger("trigger", Tooltipped::No, sc);
            vd.field_validated_sc("text", sc, validate_desc);
        }
    });

    vd.field_trigger("trigger", Tooltipped::No, sc);
    // undocumented
    vd.field_trigger("show_as_unavailable", Tooltipped::No, sc);

    vd.field_bool("historical_option");
    vd.field_bool("exclusive");
    vd.field_bool("evil_option");
    vd.field_bool("high_risk_option");
    vd.field_bool("high_reward_option");

    vd.field_bool("default_option");
    vd.field_bool("highlighted_option");
    vd.field_bool("fallback");
    vd.field_script_value_no_breakdown("ai_will_select", sc);
    vd.field_validated_sc("ai_chance", sc, validate_ai_chance);
    validate_effect_internal(
        &Lowercase::new_unchecked("option"),
        ListType::None,
        block,
        data,
        sc,
        &mut vd,
        tooltipped,
        &mut SpecialTokens::none(),
    );
}