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.oracle_text = face.oracle_text.replace("\\n", "\n");
card.color_identity = rules.color_identity;
card.attraction_lights = face.attraction_lights.clone();
card.initial_loyalty = face.initial_loyalty.clone();
for (k, v) in &face.svars {
card.svars.entry(k.clone()).or_insert_with(|| v.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);
}
card.generate_keyword_chapter_triggers();
if card.type_line.has_subtype("Saga") && card.has_chapter() {
let read_ahead = card.has_keyword("Read ahead");
let replacement = if read_ahead {
super::card_factory_util::make_read_ahead(&card, true)
} else {
super::card_factory_util::make_etb_counter("etbCounter:LORE:1", &card, true)
};
if let Some(replacement) = replacement {
card.add_replacement_effect(replacement);
}
if read_ahead {
let raw = "S$ Mode$ DisableTriggers | ValidCard$ Card.Self+ThisTurnEntered | ValidTrigger$ Triggered.ChapterNotLore | Secondary$ True | Description$ Chapter abilities of this Saga can't trigger the turn it entered the battlefield unless it has exactly the number of lore counters on it specified in the chapter symbol of that ability.";
if let Some(static_ability) = crate::staticability::parse_static_ability(raw) {
card.add_static_ability(static_ability);
}
}
}
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(),
oracle_text: back_face.oracle_text.replace("\\n", "\n"),
is_modal: rules.split_type == forge_foundation::CardSplitType::Modal,
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
}