use forge_foundation::{ColorSet, ZoneType};
use super::trait_animate_effect::parse_animate_params;
use super::EffectContext;
use crate::ability::ability_ir::DefinedRef;
use crate::card::card_trait_changes::CardTraitChanges;
use crate::card::perpetual::perpetual_interface::PerpetualInterface;
use crate::card::perpetual::{
perpetual_abilities, perpetual_colors, perpetual_incorporate, perpetual_keywords,
perpetual_mana_cost, perpetual_new_pt, perpetual_types,
};
use crate::card::AnimateState;
use crate::spellability::SpellAbility;
use crate::trigger::parse_trigger;
use forge_foundation::ManaCost;
#[manabrew_engine_macros::spell_effect(AnimateEffect)]
fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
let controller = sa.activating_player;
let target_ids = resolve_animate_targets(ctx, sa, controller);
let anim_params = parse_animate_params(sa);
let power_str = anim_params.power.map(|p| p.to_string());
let toughness_str = anim_params.toughness.map(|t| t.to_string());
let types_str = if anim_params.add_types.is_empty() {
None
} else {
Some(anim_params.add_types.join(","))
};
let keywords_str = if anim_params.add_keywords.is_empty() {
None
} else {
Some(anim_params.add_keywords.join(","))
};
let remove_keywords_str = sa.ir.animate_remove_keywords_text.clone();
let colors_str = anim_params.colors.map(|c| c.join(","));
let triggers_str = sa.ir.animate_triggers_text.clone();
let overwrite_types = anim_params.overwrite_types;
let incorporate_cost = sa.ir.animate_incorporate_text.clone();
let mana_cost_override = sa.ir.animate_mana_cost_override_text.clone();
let is_perpetual = matches!(
sa.ir.duration,
Some(crate::spellability::AbilityDuration::Perpetual)
);
let is_permanent_duration = matches!(
sa.ir.duration,
Some(crate::spellability::AbilityDuration::Permanent)
);
let resolve_ts = ctx.game.next_effect_timestamp();
let mut parsed_triggers: Vec<crate::trigger::Trigger> = Vec::new();
if let Some(ref trigs) = triggers_str {
let source_id = sa.source.unwrap_or(crate::ids::CardId(0));
let source_svars = ctx.game.card(source_id).svars.clone();
let mut next_trig_id = 1000u32; for trig_name in trigs.split(',') {
let trig_name = trig_name.trim();
if trig_name.is_empty() {
continue;
}
if let Some(svar_text) = source_svars.get(trig_name) {
if let Some(mut trig) = parse_trigger(svar_text, &mut next_trig_id) {
trig.execute = trig.execute.clone();
parsed_triggers.push(trig);
}
}
}
}
let eot_targets = target_ids.clone();
for card_id in target_ids {
if ctx.game.card(card_id).zone != ZoneType::Battlefield {
continue;
}
let effect_ts = if is_perpetual { Some(resolve_ts) } else { None };
if let Some(ref mc) = incorporate_cost {
let ts = resolve_ts;
perpetual_incorporate::PerpetualIncorporate {
timestamp: ts,
incorporate: ManaCost::parse(mc),
}
.apply_effect(ctx.game.card_mut(card_id));
}
if let Some(ref mc) = mana_cost_override {
if let Some(ts) = effect_ts {
perpetual_mana_cost::PerpetualManaCost {
timestamp: ts,
mana_cost: ManaCost::parse(mc),
}
.apply_effect(ctx.game.card_mut(card_id));
}
}
if ctx.game.card(card_id).animate_state.is_none() {
let original_type_line = ctx.game.card(card_id).type_line.clone();
let original_base_power = ctx.game.card(card_id).base_power;
let original_base_toughness = ctx.game.card(card_id).base_toughness;
let original_color = ctx.game.card(card_id).color;
let original_keywords = ctx.game.card(card_id).keywords.clone();
ctx.game
.card_mut(card_id)
.set_animate_state(Some(AnimateState {
original_type_line,
original_base_power,
original_base_toughness,
original_color,
original_keywords: Some(original_keywords),
}));
}
if let Some(ref types) = types_str {
if overwrite_types {
ctx.game
.card_mut(card_id)
.set_type_line(forge_foundation::CardTypeLine::new());
}
for t in types.split(',') {
let t = t.trim();
if !t.is_empty() {
if let Some(ts) = effect_ts {
perpetual_types::PerpetualTypes {
timestamp: ts,
add_types: vec![t.to_string()],
}
.apply_effect(ctx.game.card_mut(card_id));
} else {
ctx.game.card_mut(card_id).add_type(t);
}
}
}
}
if sa
.ir
.spell_description_text
.as_deref()
.is_some_and(|desc| desc.to_ascii_lowercase().starts_with("crew"))
{
ctx.game.card_mut(card_id).becomes_crewed();
}
let parsed_power = power_str
.as_deref()
.and_then(|p| p.trim().parse::<i32>().ok());
let parsed_toughness = toughness_str
.as_deref()
.and_then(|t| t.trim().parse::<i32>().ok());
if let Some(ts) = effect_ts {
if parsed_power.is_some() || parsed_toughness.is_some() {
perpetual_new_pt::PerpetualNewPt {
timestamp: ts,
power: parsed_power,
toughness: parsed_toughness,
}
.apply_effect(ctx.game.card_mut(card_id));
}
} else {
if let Some(val) = parsed_power {
ctx.game.card_mut(card_id).set_base_power(Some(val));
}
if let Some(val) = parsed_toughness {
ctx.game.card_mut(card_id).set_base_toughness(Some(val));
}
}
let add_keywords: Vec<String> = keywords_str
.as_deref()
.map(|kws| {
kws.split(',')
.map(str::trim)
.filter(|kw| !kw.is_empty())
.map(str::to_string)
.collect()
})
.unwrap_or_default();
let remove_keywords: Vec<String> = remove_keywords_str
.as_deref()
.map(|kws| {
kws.split(',')
.map(str::trim)
.filter(|kw| !kw.is_empty())
.map(str::to_string)
.collect()
})
.unwrap_or_default();
if !add_keywords.is_empty() || !remove_keywords.is_empty() {
if let Some(ts) = effect_ts {
perpetual_keywords::PerpetualKeywords {
timestamp: ts,
add_keywords,
remove_keywords,
remove_all: false,
}
.apply_effect(ctx.game.card_mut(card_id));
} else if is_permanent_duration {
let card = ctx.game.card_mut(card_id);
for kw in &remove_keywords {
card.remove_changed_card_keywords(kw);
card.remove_hidden_extrinsic_keywords(kw);
card.pump_keywords.remove(kw);
}
for kw in &add_keywords {
card.add_changed_card_keywords(kw);
}
} else {
let card = ctx.game.card_mut(card_id);
for kw in &remove_keywords {
card.pump_keywords.remove(kw);
}
for kw in &add_keywords {
card.add_pump_keyword(kw);
}
}
}
if !parsed_triggers.is_empty() {
let source_id = sa.source.unwrap_or(crate::ids::CardId(0));
let source_svars = ctx.game.card(source_id).svars.clone();
if let Some(ts) = effect_ts {
let changes = CardTraitChanges {
triggers: parsed_triggers.clone(),
..Default::default()
};
perpetual_abilities::PerpetualAbilities {
timestamp: ts,
changes,
}
.apply_effect(ctx.game.card_mut(card_id));
for trig in &parsed_triggers {
if !trig.execute.is_empty() {
if let Some(exec_svar) = source_svars.get(&trig.execute) {
ctx.game
.card_mut(card_id)
.set_s_var_if_absent(trig.execute.clone(), exec_svar.clone());
}
}
}
} else {
for trig in &parsed_triggers {
ctx.game.card_mut(card_id).add_trigger(trig.clone());
ctx.game.card_mut(card_id).increment_pump_trigger_count();
if !trig.execute.is_empty() {
if let Some(exec_svar) = source_svars.get(&trig.execute) {
ctx.game
.card_mut(card_id)
.set_s_var_if_absent(trig.execute.clone(), exec_svar.clone());
}
}
}
}
ctx.trigger_handler
.register_active_trigger(ctx.game, card_id);
}
if let Some(ref colors) = colors_str {
let mut new_color = ColorSet::COLORLESS;
for c in colors.split(',') {
let c = c.trim().to_lowercase();
match c.as_str() {
"white" | "w" => new_color = new_color.union(ColorSet::WHITE),
"blue" | "u" => new_color = new_color.union(ColorSet::BLUE),
"black" | "b" => new_color = new_color.union(ColorSet::BLACK),
"red" | "r" => new_color = new_color.union(ColorSet::RED),
"green" | "g" => new_color = new_color.union(ColorSet::GREEN),
_ => {}
}
}
if let Some(ts) = effect_ts {
perpetual_colors::PerpetualColors {
timestamp: ts,
colors: new_color,
overwrite: true,
}
.apply_effect(ctx.game.card_mut(card_id));
} else {
ctx.game.card_mut(card_id).set_color(new_color);
}
}
}
if let Some(action) = sa.ir.at_eot.as_deref() {
let remembered: Vec<crate::ids::CardId> = eot_targets
.iter()
.copied()
.filter(|&cid| ctx.game.card(cid).zone == ZoneType::Battlefield)
.collect();
crate::ability::spell_ability_effect::register_at_eot(
ctx.trigger_handler,
ctx.game,
sa,
action,
remembered,
);
}
}
fn resolve_animate_targets(
ctx: &mut EffectContext,
sa: &SpellAbility,
_controller: crate::ids::PlayerId,
) -> Vec<crate::ids::CardId> {
if let Some(target) = sa.target_chosen.target_card {
return vec![target];
}
if let Some(defined) = sa.defined_ref() {
match defined {
DefinedRef::SelfCard => {
if let Some(src) = sa.source {
return vec![src];
}
}
DefinedRef::ParentTarget => {
if let Some(pt) = ctx.parent_target_card {
return vec![pt];
}
}
DefinedRef::Remembered => {
if let Some(src) = sa.source {
return ctx.game.card(src).remembered_cards.clone();
}
}
_ => {}
}
}
if let Some(src) = sa.source {
vec![src]
} else {
vec![]
}
}