use crate::block::{BV, Block};
use crate::ck3::validate::{
validate_ai_targets, validate_cost, validate_quick_trigger, validate_theme_background,
};
use crate::context::ScopeContext;
use crate::db::{Db, DbKind};
use crate::desc::validate_desc;
use crate::effect::validate_effect;
use crate::everything::Everything;
use crate::game::GameFlags;
use crate::item::{Item, ItemLoader};
use crate::report::{ErrorKey, warn};
use crate::scopes::Scopes;
use crate::token::Token;
use crate::tooltipped::Tooltipped;
use crate::trigger::{validate_target, validate_trigger};
use crate::validate::{validate_ai_chance, validate_duration, validate_modifiers_with_base};
use crate::validator::Validator;
#[derive(Clone, Debug)]
pub struct CharacterInteraction {}
inventory::submit! {
ItemLoader::Normal(GameFlags::Ck3, Item::CharacterInteraction, CharacterInteraction::add)
}
impl CharacterInteraction {
pub fn add(db: &mut Db, key: Token, block: Block) {
db.add(Item::CharacterInteraction, key, block, Box::new(Self {}));
}
}
impl DbKind for CharacterInteraction {
fn validate(&self, key: &Token, block: &Block, data: &Everything) {
let mut vd = Validator::new(block, data);
let mut sc = ScopeContext::new(Scopes::None, key);
sc.define_name("actor", Scopes::Character, key);
sc.define_name("recipient", Scopes::Character, key);
sc.define_name("hook", Scopes::Bool, key);
sc.define_name("secondary_actor", Scopes::Character, key);
sc.define_name("secondary_recipient", Scopes::Character, key);
sc.define_name("intermediary", Scopes::Character, key);
if let Some(target_type) = block.get_field_value("target_type") {
if target_type.is("artifact") {
sc.define_name("target", Scopes::Artifact, target_type);
} else if target_type.is("title") {
sc.define_name("target", Scopes::LandedTitle, target_type);
sc.define_name("landed_title", Scopes::LandedTitle, target_type);
}
} else if let Some(interface) = block.get_field_value("interface") {
if interface.is("interfere_in_war") || interface.is("call_ally") {
sc.define_name("target", Scopes::War, interface);
} else if interface.is("blackmail") {
sc.define_name("target", Scopes::Secret, interface);
} else if interface.is("council_task_interaction") {
sc.define_name("target", Scopes::CouncilTask, interface);
} else if interface.is("create_claimant_faction_against") {
sc.define_name("landed_title", Scopes::LandedTitle, interface);
} else if interface.is("modify_vassal_contract") {
sc.define_list("changed_obligations", Scopes::VassalObligationLevel, interface);
}
} else if let Some(special) = block.get_field_value("special_interaction") {
if special.is("invite_to_council_interaction") {
sc.define_name("target", Scopes::CouncilTask, special);
} else if special.is("end_war_attacker_victory_interaction")
|| special.is("end_war_attacker_defeat_interaction")
|| special.is("end_war_white_peace_interaction")
{
sc.define_name("war", Scopes::War, special);
} else if special.is("remove_scheme_interaction")
|| special.is("invite_to_scheme_interaction")
{
sc.define_name("scheme", Scopes::Scheme, special);
}
}
for block in block.get_field_blocks("send_option") {
if let Some(token) = block.get_field_value("flag") {
sc.define_name(token.as_str(), Scopes::Bool, token);
}
}
vd.field_validated_block("localization_values", |block, data| {
let mut vd = Validator::new(block, data);
vd.unknown_value_fields(|key, value| {
let scopes = validate_target(value, data, &mut sc, Scopes::all());
sc.define_name(key.as_str(), scopes, value);
});
});
vd.field_effect("redirect", Tooltipped::No, &mut sc);
vd.field_bool("ai_instant_response");
vd.field_effect("ai_set_target", Tooltipped::No, &mut sc);
vd.multi_field_validated_block("ai_targets", validate_ai_targets);
vd.field_validated_block("ai_target_quick_trigger", validate_quick_trigger);
vd.field_numeric("interface_priority");
vd.field_bool("common_interaction");
if !block.get_field_bool("hidden").unwrap_or(false) {
vd.req_field("category");
}
vd.field_item("category", Item::CharacterInteractionCategory);
if !vd.multi_field_validated_sc("icon", &mut sc, validate_icon) {
data.mark_used_icon("NGameIcons|CHARACTER_INTERACTION_ICON_PATH", key, ".dds");
}
vd.field_icon("alert_icon", "NGameIcons|CHARACTER_INTERACTION_ICON_PATH", ".dds");
vd.field_icon("icon_small", "NGameIcons|CHARACTER_INTERACTION_ICON_PATH", ".dds");
vd.field_validated_key("override_background", |key, bv, data| {
let mut sc = ScopeContext::new(Scopes::Character, key);
validate_theme_background(bv, data, &mut sc);
});
vd.field_trigger("is_highlighted", Tooltipped::No, &mut sc.clone());
vd.field_validated_sc("highlighted_reason", &mut sc, validate_desc);
vd.field_value("special_interaction");
vd.field_value("special_ai_interaction");
vd.field_bool("ai_intermediary_maybe");
vd.field_bool("ai_maybe");
vd.field_integer("ai_min_reply_days");
vd.field_integer("ai_max_reply_days");
vd.field_value("interface"); vd.field_list_choice(
"custom_character_sort",
&["candidate_score", "governor_efficiency", "obedience", "merit"],
);
vd.field_item("scheme", Item::Scheme);
vd.field_bool("popup_on_receive");
vd.field_bool("pause_on_receive");
vd.field_bool("force_notification");
vd.field_bool("ai_accept_negotiation");
vd.field_bool("secondary_scopes_optional");
vd.field_bool("hidden");
vd.field_validated_sc("use_diplomatic_range", &mut sc.clone(), validate_bool_or_trigger);
vd.field_bool("can_send_despite_rejection");
vd.field_bool("ignores_pending_interaction_block");
vd.field_validated_block_rerooted("cooldown", &sc, Scopes::Character, validate_duration);
vd.field_validated_block_rerooted(
"cooldown_against_recipient",
&sc,
Scopes::Character,
validate_duration,
);
vd.field_validated_block_rerooted(
"recipient_recieve_cooldown",
&sc,
Scopes::Character,
validate_duration,
);
vd.field_validated_block_rerooted(
"category_cooldown",
&sc,
Scopes::Character,
validate_duration,
);
vd.field_validated_block_rerooted(
"category_cooldown_against_recipient",
&sc,
Scopes::Character,
validate_duration,
);
vd.field_validated_block_rerooted(
"ignore_recipient_recieve_cooldown",
&sc,
Scopes::Character,
|block, data, sc| {
validate_trigger(block, data, sc, Tooltipped::No);
},
);
if !key.as_str().starts_with("ai_") && !block.get_field_bool("hidden").unwrap_or(false) {
if !block.has_key("name") {
data.verify_exists(Item::Localization, key);
}
if !block.has_key("desc") {
data.localization.suggest(&format!("{key}_desc"), key);
}
}
vd.field_validated_value("extra_icon", |k, mut vd| {
vd.item(Item::File);
let loca = format!("{key}_extra_icon");
data.verify_exists_implied(Item::Localization, &loca, k);
});
vd.field_trigger("should_use_extra_icon", Tooltipped::No, &mut sc.clone());
vd.field_trigger("is_shown", Tooltipped::No, &mut sc.clone());
vd.field_trigger("is_valid", Tooltipped::Yes, &mut sc.clone());
vd.field_trigger(
"is_valid_showing_failures_only",
Tooltipped::FailuresOnly,
&mut sc.clone(),
);
vd.field_trigger(
"has_valid_target_showing_failures_only",
Tooltipped::FailuresOnly,
&mut sc.clone(),
);
vd.field_trigger("has_valid_target", Tooltipped::Yes, &mut sc.clone());
vd.field_trigger("can_send", Tooltipped::Yes, &mut sc.clone());
vd.field_trigger("can_be_blocked", Tooltipped::Yes, &mut sc.clone());
vd.field_validated_key_block("populate_actor_list", |k, block, data| {
let loca = format!("actor_secondary_{key}");
data.verify_exists_implied(Item::Localization, &loca, k);
validate_effect(block, data, &mut sc.clone(), Tooltipped::No);
});
vd.field_validated_key_block("populate_recipient_list", |k, block, data| {
let loca = format!("recipient_secondary_{key}");
data.verify_exists_implied(Item::Localization, &loca, k);
validate_effect(block, data, &mut sc.clone(), Tooltipped::No);
});
vd.multi_field_validated_block("send_option", |b, data| {
let mut vd = Validator::new(b, data);
vd.req_field("flag");
if vd.field_localization("localization", &mut sc) {
vd.field_value("flag");
} else {
vd.field_localization("flag", &mut sc);
}
vd.field_trigger("is_shown", Tooltipped::No, &mut sc.clone());
vd.field_trigger("is_valid", Tooltipped::FailuresOnly, &mut sc.clone());
vd.field_trigger("starts_enabled", Tooltipped::No, &mut sc.clone());
vd.field_trigger("can_be_changed", Tooltipped::No, &mut sc.clone());
vd.field_validated_sc("current_description", &mut sc.clone(), validate_desc);
vd.field_bool("can_invalidate_interaction");
vd.field_script_value("scheme_preview_success_chance", &mut sc);
vd.field_script_value("scheme_preview_success_chance_max", &mut sc);
vd.field_script_value("scheme_preview_speed", &mut sc);
});
vd.field_bool("send_options_exclusive");
vd.field_effect("on_send", Tooltipped::Yes, &mut sc);
vd.field_effect("on_accept", Tooltipped::Yes, &mut sc);
vd.field_effect("on_decline", Tooltipped::Yes, &mut sc);
vd.field_effect("on_blocked_effect", Tooltipped::No, &mut sc);
vd.field_effect("pre_auto_accept", Tooltipped::No, &mut sc);
vd.field_effect("on_auto_accept", Tooltipped::Yes, &mut sc);
vd.field_effect("on_intermediary_accept", Tooltipped::Yes, &mut sc);
vd.field_effect("on_intermediary_decline", Tooltipped::Yes, &mut sc);
vd.field_integer("ai_frequency"); vd.field_validated_key_block("ai_frequency_by_tier", |key, b, data| {
let mut vd = Validator::new(b, data);
for tier in &["barony", "county", "duchy", "kingdom", "empire", "hegemony"] {
vd.req_field(tier);
vd.field_integer(tier);
}
if block.has_key("ai_frequency") {
let msg = "must not have both `ai_frequency` and `ai_frequency_by_tier`";
warn(ErrorKey::Validation).msg(msg).loc(key).push();
}
});
vd.field_trigger_rooted("ai_potential", Tooltipped::Yes, Scopes::Character);
if let Some(token) = block.get_key("ai_potential") {
if block.get_field_integer("ai_frequency").unwrap_or(0) == 0
&& !key.is("revoke_title_interaction")
&& !block.has_key("ai_frequency_by_tier")
{
let msg = "`ai_potential` will not be used if `ai_frequency` is 0";
warn(ErrorKey::Unneeded).msg(msg).loc(token).push();
}
let msg = "should use `is_available` instead of `ai_potential`";
warn(ErrorKey::Deprecated).msg(msg).loc(token).push();
}
vd.field_trigger_rooted("is_available", Tooltipped::Yes, Scopes::Character);
vd.field_validated_sc("ai_intermediary_accept", &mut sc.clone(), validate_ai_chance);
vd.field_validated_block_rerooted(
"ai_accept",
&sc,
Scopes::Character,
validate_modifiers_with_base,
);
vd.field_validated_block_rerooted(
"ai_will_do",
&sc,
Scopes::Character,
validate_modifiers_with_base,
);
vd.field_validated_sc("name", &mut sc.clone(), validate_desc);
vd.field_validated_sc("desc", &mut sc.clone(), validate_desc);
vd.field_choice("greeting", &["negative", "positive"]);
vd.field_validated_sc("prompt", &mut sc.clone(), validate_desc);
vd.field_validated_sc("intermediary_notification_text", &mut sc.clone(), validate_desc);
vd.field_validated_sc("notification_text", &mut sc.clone(), validate_desc);
vd.field_validated_sc("on_decline_summary", &mut sc.clone(), validate_desc);
vd.field_localization("answer_block_key", &mut sc);
vd.field_localization("answer_accept_key", &mut sc);
vd.field_localization("answer_reject_key", &mut sc);
vd.field_localization("answer_acknowledge_key", &mut sc);
vd.field_localization("options_heading", &mut sc);
vd.field_localization("pre_answer_maybe_breakdown_key", &mut sc);
vd.field_localization("pre_answer_no_breakdown_key", &mut sc);
vd.field_localization("pre_answer_yes_breakdown_key", &mut sc);
vd.field_localization("pre_answer_maybe_key", &mut sc);
vd.field_localization("pre_answer_no_key", &mut sc);
vd.field_localization("pre_answer_yes_key", &mut sc);
vd.field_localization("intermediary_breakdown_maybe", &mut sc);
vd.field_localization("intermediary_breakdown_no", &mut sc);
vd.field_localization("intermediary_breakdown_yes", &mut sc);
vd.field_localization("intermediary_answer_accept_key", &mut sc);
vd.field_localization("intermediary_answer_reject_key", &mut sc);
vd.field_localization("reply_item_key", &mut sc);
vd.field_localization("send_name", &mut sc);
vd.field_bool("needs_recipient_to_open");
vd.field_bool("show_effects_in_notification");
vd.field_bool("diarch_interaction");
vd.field_validated_sc("auto_accept", &mut sc.clone(), validate_bool_or_trigger);
vd.field_choice(
"target_type",
&["artifact", "title", "men_at_arms", "court_position_type", "count"],
);
vd.field_value("target_filter");
vd.field_validated_block_rerooted(
"can_be_picked",
&sc,
Scopes::Character,
|block, data, sc| {
validate_trigger(block, data, sc, Tooltipped::Yes);
},
);
vd.field_trigger("can_be_picked_title", Tooltipped::Yes, &mut sc.clone());
vd.field_trigger("can_be_picked_artifact", Tooltipped::Yes, &mut sc.clone());
vd.field_trigger("can_be_picked_regiment", Tooltipped::Yes, &mut sc.clone());
vd.field_trigger("needs_confirmation", Tooltipped::No, &mut sc.clone());
vd.field_validated_block_rerooted("cost", &sc, Scopes::None, validate_cost);
vd.field_list("filter_tags");
vd.field_bool("shows_military_strength");
}
}
fn validate_bool_or_trigger(bv: &BV, data: &Everything, sc: &mut ScopeContext) {
match bv {
BV::Value(t) => {
if !t.is("yes") && !t.is("no") {
warn(ErrorKey::Validation).msg("expected yes or no").loc(t).push();
}
}
BV::Block(b) => {
validate_trigger(b, data, sc, Tooltipped::No);
}
}
}
fn validate_icon(bv: &BV, data: &Everything, sc: &mut ScopeContext) {
match bv {
BV::Value(token) => {
data.verify_icon("NGameIcons|CHARACTER_INTERACTION_ICON_PATH", token, ".dds");
}
BV::Block(block) => {
let mut vd = Validator::new(block, data);
vd.req_field("reference");
vd.field_trigger("trigger", Tooltipped::No, sc);
vd.field_icon("reference", "NGameIcons|CHARACTER_INTERACTION_ICON_PATH", ".dds");
}
}
}