use forge_carddb::CardRules;
use forge_foundation::CardStateName;
use crate::ids::{CardId, PlayerId};
use crate::parsing::parse_classified_or_warn;
use crate::replacement::{parse_replacement_effect, ReplacementEffect};
use crate::staticability::{parse_static_ability, StaticAbility};
use crate::trigger::{parse_trigger, Trigger};
use super::{Card, CardOtherPart};
fn mark_triggers_card_state(triggers: &mut [Trigger], card: &Card, state_name: CardStateName) {
let mut state = crate::card::card_state::CardState::new(card.clone(), state_name);
state.set_name(match state_name {
CardStateName::RightSplit => card
.svars
.get("RoomRightSplitName")
.cloned()
.unwrap_or_else(|| card.card_name.clone()),
_ => card.card_name.clone(),
});
for trigger in triggers {
trigger.base.set_card_state(state.clone());
}
}
fn intrinsic_color(face: &forge_carddb::CardFace) -> forge_foundation::ColorSet {
if face.keywords.iter().any(|k| k == "Devoid") {
return forge_foundation::ColorSet::COLORLESS;
}
face.resolved_color()
}
pub(crate) struct ParsedComponents {
pub triggers: Vec<Trigger>,
pub static_abilities: Vec<StaticAbility>,
pub replacement_effects: Vec<ReplacementEffect>,
pub spell_cast_or_copy_raw: Vec<String>,
}
pub(crate) fn parse_card_components(face: &forge_carddb::CardFace) -> ParsedComponents {
let mut next_trigger_id = 0u32;
let mut triggers = Vec::new();
let mut spell_cast_or_copy_raw = Vec::new();
for raw in &face.triggers {
if let Some(trig) =
parse_classified_or_warn(parse_trigger(raw, &mut next_trigger_id), "Trigger", raw)
{
triggers.push(trig);
if raw.contains("Mode$ SpellCastOrCopy") {
spell_cast_or_copy_raw.push(raw.clone());
}
}
}
let mut static_abilities = Vec::new();
for raw in &face.static_abilities {
let prefixed = format!("S$ {}", raw);
if let Some(sa) =
parse_classified_or_warn(parse_static_ability(&prefixed), "StaticAbility", raw)
{
static_abilities.push(sa);
}
}
let replacement_effects: Vec<ReplacementEffect> = face
.replacements
.iter()
.filter_map(|raw| {
let prefixed = format!("R$ {}", raw);
parse_classified_or_warn(
parse_replacement_effect(&prefixed),
"ReplacementEffect",
raw,
)
})
.collect();
ParsedComponents {
triggers,
static_abilities,
replacement_effects,
spell_cast_or_copy_raw,
}
}
pub(crate) fn synthesize_derived(components: &mut ParsedComponents, existing_trigger_count: usize) {
let mut next_trig_id = (existing_trigger_count + components.triggers.len()) as u32;
for raw in &components.spell_cast_or_copy_raw {
let converted = raw.replace("Mode$ SpellCastOrCopy", "Mode$ SpellCopied");
if let Some(trig) = parse_trigger(&converted, &mut next_trig_id) {
components.triggers.push(trig);
}
}
for sa in &components.static_abilities {
if !sa.check_mode(&crate::staticability::StaticMode::OptionalAttackCost) {
continue;
}
let has_exert = sa
.ir
.cost
.as_deref()
.map(|c| c.contains("Exert"))
.unwrap_or(false);
if has_exert {
if let Some(svar_name) = sa.ir.trigger_text.as_deref() {
let raw = format!(
"Mode$ Exerted | ValidCard$ Card.Self | Execute$ {} | TriggerZones$ Battlefield",
svar_name
);
if let Some(mut trig) = parse_trigger(&raw, &mut next_trig_id) {
trig.execute = svar_name.to_string();
components.triggers.push(trig);
}
}
}
}
}
pub(crate) fn assemble_card(
rules: &CardRules,
owner: PlayerId,
mut components: ParsedComponents,
) -> Card {
let face = &rules.main_part;
let full_name = rules.name();
let mut card = Card::new(
CardId(0),
face.name.clone(),
owner,
face.type_line.clone(),
face.mana_cost.clone(),
intrinsic_color(face),
face.int_power,
face.int_toughness,
face.keywords.clone(),
face.abilities.clone(),
);
card.full_name = full_name;
card.color_identity = rules.color_identity;
card.attraction_lights = face.attraction_lights.clone();
card.initial_loyalty = face.initial_loyalty.clone();
if rules.split_type == forge_foundation::CardSplitType::Split
&& card.type_line.has_subtype("Room")
{
mark_triggers_card_state(&mut components.triggers, &card, CardStateName::LeftSplit);
}
for trig in components.triggers {
card.add_trigger(trig);
}
for (k, v) in &face.svars {
card.svars.entry(k.clone()).or_insert_with(|| v.clone());
}
super::card_factory_util::add_etb_keyword_replacements(&mut card);
super::card_factory_util::add_dredge_replacement(&mut card);
super::card_factory_util::add_madness_replacement(&mut card);
super::card_factory_util::add_flashback_replacement(&mut card);
super::card_factory_util::add_harmonize_replacement(&mut card);
for sa in components.static_abilities {
card.add_static_ability(sa);
}
for re in components.replacement_effects {
card.add_replacement_effect(re);
}
if rules.split_type == forge_foundation::CardSplitType::Split
&& card.type_line.has_subtype("Room")
{
if let Some(ref other_face) = rules.other_part {
let unlock_cost = other_face
.mana_cost
.to_string()
.replace('{', "")
.replace('}', " ")
.trim()
.to_string();
let unlock_name = &other_face.name;
card.set_s_var("RoomRightSplitName", unlock_name.as_str());
card.set_s_var("RoomRightSplitCost", &unlock_cost);
let ab_text = format!(
"AB$ UnlockDoor | Cost$ {} | SorcerySpeed$ True | CardState$ RightSplit | SpellDescription$ Unlock {}",
unlock_cost, unlock_name
);
let next_idx = card.activated_abilities.len();
if let Some(ab) = crate::ability::activated::parse_activated_ability(&ab_text, next_idx)
{
card.activated_abilities.push(ab);
}
for (k, v) in &other_face.svars {
card.svars.entry(k.clone()).or_insert_with(|| v.clone());
}
let mut next_trigger_id = card.triggers.len() as u32;
let mut other_triggers: Vec<_> = other_face
.triggers
.iter()
.filter_map(|raw| {
parse_classified_or_warn(
parse_trigger(raw, &mut next_trigger_id),
"Trigger",
raw,
)
})
.collect();
mark_triggers_card_state(&mut other_triggers, &card, CardStateName::RightSplit);
for trig in other_triggers {
card.add_trigger(trig);
}
}
}
if rules.split_type.is_dual_faced() {
if let Some(ref back_face) = rules.other_part {
let mut back_trigger_id = 0u32;
let back_triggers: Vec<_> = back_face
.triggers
.iter()
.filter_map(|raw| {
parse_classified_or_warn(
parse_trigger(raw, &mut back_trigger_id),
"Trigger",
raw,
)
})
.collect();
let back_static_abilities: Vec<StaticAbility> = back_face
.static_abilities
.iter()
.filter_map(|raw| {
parse_classified_or_warn(
parse_static_ability(&format!("S$ {}", raw)),
"StaticAbility",
raw,
)
})
.collect();
let back_replacement_effects: Vec<ReplacementEffect> = back_face
.replacements
.iter()
.filter_map(|raw| {
parse_classified_or_warn(
parse_replacement_effect(&format!("R$ {}", raw)),
"ReplacementEffect",
raw,
)
})
.collect();
card.other_part = Some(CardOtherPart {
name: back_face.name.clone(),
type_line: back_face.type_line.clone(),
mana_cost: back_face.mana_cost.clone(),
color: intrinsic_color(back_face),
base_power: back_face.int_power,
base_toughness: back_face.int_toughness,
keywords: crate::keyword::keyword_collection::KeywordCollection::from_strings(
&back_face.keywords,
),
abilities: back_face.abilities.clone(),
triggers: back_triggers,
static_abilities: back_static_abilities,
replacement_effects: back_replacement_effects,
svars: back_face.svars.clone(),
});
}
}
card.refresh_action_specs();
card.base_ability_count = card.activated_abilities.len();
card.base_trigger_count = card.triggers.len();
card
}