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::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, warn};
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",
    "minor_character_event",
    "country_event",
    "minor_country_event",
    "major_country_event",
    "state_event",
    "province_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() {
            "minor_character_event" | "character_event" => (Scopes::Character, event_type.clone()),
            "country_event" | "minor_country_event" | "major_country_event" => {
                (Scopes::Country, event_type.clone())
            }
            "state_event" => (Scopes::State, event_type.clone()),
            "province_event" => (Scopes::Province, 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("hidden");
    vd.field_bool("interface_lock");
    vd.field_bool("fire_only_once");

    vd.field_trigger("trigger", Tooltipped::No, sc);
    vd.field_validated_block_sc("weight_multiplier", sc, validate_modifiers_with_base);

    vd.field_item_or_target(
        "goto_location",
        sc,
        Item::Province,
        Scopes::Province.union(Scopes::Country),
    );

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

    sc.wipe_temporaries();
    vd.field_effect("immediate", tooltipped_immediate, sc);

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

    let mut minor_event = false;
    if event.block.field_value_is("type", "minor_character_event")
        || event.block.field_value_is("type", "minor_country_event")
    {
        minor_event = true;
    }

    if !hidden && !minor_event {
        vd.req_field("picture");
    }
    vd.field_item("picture", Item::EventPicture);

    for field in &["left_portrait", "right_portrait"] {
        let mut count = 0;
        vd.multi_field_validated_value(field, |_, mut vd| {
            count += 1;
            vd.target_ok_this(sc, Scopes::Character);
            if count == 4 {
                let msg = format!("Event has more than 3 {field} attributes.");
                let info = "Events can only have up to 3 portraits displayed at a time.";
                warn(ErrorKey::Validation).msg(msg).info(info).loc(&event.key).push();
            }
        });
    }

    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();
        }
        sc.wipe_temporaries();
        validate_effect(block, data, sc, tooltipped_immediate);
    });
}

fn validate_event_option(
    block: &Block,
    data: &Everything,
    sc: &mut ScopeContext,
    tooltipped: Tooltipped,
) {
    let mut vd = Validator::new(block, data);
    vd.field_validated_sc("name", sc, validate_desc);

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

    vd.field_bool("exclusive");
    vd.field_bool("highlight");
    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(),
    );
}