use std::collections::HashMap;
use forge_foundation::ZoneType;
use crate::ability::effects::{self, EffectContext};
use crate::agent::{PassAgent, PlayerAgent};
use crate::card::Card;
use crate::game::GameState;
use crate::game_rng::ThreadRngAdapter;
use crate::ids::CardId;
use crate::mana::ManaPool;
use crate::spellability::build_spell_ability;
use crate::trigger::TriggerHandler;
use super::replacement_effect::{zone_matches, ReplacementEffect};
use super::replacement_handler::{ReplacementEvent, ReplacementRuntime};
use super::replacement_result::ReplacementResult;
use super::replacement_type::ReplacementType;
use crate::card_trait_base::CardTrait;
pub fn can_replace(
effect: &ReplacementEffect,
event: &ReplacementEvent,
game: &GameState,
source_card: &Card,
) -> bool {
if effect.event != ReplacementType::Moved {
return false;
}
let (moving_id, origin, destination, is_discard) = match event {
ReplacementEvent::Moved {
card,
origin,
destination,
is_discard,
..
} => (*card, *origin, *destination, *is_discard),
_ => return false,
};
if let Some(requires_discard) = effect.ir.discard {
if requires_discard != is_discard {
return false;
}
}
if let Some(dest) = effect.ir.destination_text.as_deref() {
if !zone_matches(dest, destination) {
return false;
}
}
if let Some(exclude) = effect.ir.exclude_destination_text.as_deref() {
if zone_matches(exclude, destination) {
return false;
}
}
if let Some(orig) = effect.ir.origin_text.as_deref() {
if !zone_matches(orig, origin) {
return false;
}
}
let moving_card = &game.cards[moving_id.index()];
if let Some(valid) = effect.ir.valid_card_selector.as_ref() {
if !effect.matches_compiled_valid_card(valid, moving_card, source_card) {
return false;
}
}
if effect.ir.flashback_cast == Some(true) && !moving_card.cast_with_flashback {
return false;
}
if effect.ir.harmonize_cast == Some(true) && !moving_card.cast_with_harmonize {
return false;
}
if let Some(valid_lki) = effect.ir.valid_lki_text.as_deref() {
if !effect.matches_valid_card(valid_lki, moving_card, source_card) {
return false;
}
}
if destination == ZoneType::Battlefield && !effect.can_replace_etb(source_card, moving_card) {
return false;
}
true
}
pub fn execute(
effect: &ReplacementEffect,
event: &mut ReplacementEvent,
game: &mut GameState,
source_card_id: CardId,
agents: Option<&mut [Box<dyn PlayerAgent>]>,
runtime: Option<&mut ReplacementRuntime<'_>>,
) -> ReplacementResult {
let (moving_id, _destination) = match event {
ReplacementEvent::Moved {
card, destination, ..
} => (*card, destination),
_ => return ReplacementResult::NotReplaced,
};
let redirect = effect
.ir
.new_destination_text
.as_deref()
.or(effect.replace_with());
if let Some(new_dest) = redirect {
let new_zone = match new_dest.trim() {
"Exile" => Some(ZoneType::Exile),
"Graveyard" => Some(ZoneType::Graveyard),
"Hand" => Some(ZoneType::Hand),
"Library" => Some(ZoneType::Library),
"Battlefield" => Some(ZoneType::Battlefield),
"Command" => Some(ZoneType::Command),
_ => None,
};
if let Some(z) = new_zone {
if let ReplacementEvent::Moved { destination, .. } = event {
*destination = z;
}
if z == ZoneType::Exile && effect.ir.exiled_with_effect_source {
let exile_source = game
.card(source_card_id)
.effect_source
.unwrap_or(source_card_id);
game.card_mut(moving_id).set_exiled_by(Some(exile_source));
game.card_mut(exile_source).add_remembered_card(moving_id);
}
return ReplacementResult::Updated;
}
}
if let Some(replace_with_key) = effect.replace_with() {
let succeeded = execute_replace_with(
effect,
replace_with_key,
game,
source_card_id,
event,
agents,
runtime,
);
if !succeeded {
return ReplacementResult::NotReplaced;
}
}
if let Some(result) = effect.ir.replacement_result.as_deref() {
return match result {
"Updated" => ReplacementResult::Updated,
"Replaced" => ReplacementResult::Replaced,
"Skipped" => ReplacementResult::Skipped,
"Prevented" => ReplacementResult::Prevented,
_ => ReplacementResult::Replaced,
};
}
ReplacementResult::Replaced
}
fn execute_replace_with(
effect: &ReplacementEffect,
replace_with: &str,
game: &mut GameState,
source_card_id: CardId,
event: &ReplacementEvent,
agents: Option<&mut [Box<dyn PlayerAgent>]>,
mut runtime: Option<&mut ReplacementRuntime<'_>>,
) -> bool {
let Some(raw) = game.card(source_card_id).svars.get(replace_with).cloned() else {
return false;
};
let controller = game.card(source_card_id).controller;
let mut sa = build_spell_ability(game, source_card_id, &raw, controller);
effect.set_replacing_objects(event, &mut sa);
#[allow(unused_assignments)]
let mut local_agents_storage: Option<Vec<Box<dyn PlayerAgent>>> = None;
let agents: &mut [Box<dyn PlayerAgent>] = if let Some(agents) = agents {
agents
} else {
local_agents_storage = Some(
(0..game.players.len())
.map(|_| Box::new(PassAgent) as Box<dyn PlayerAgent>)
.collect(),
);
local_agents_storage.as_mut().unwrap().as_mut_slice()
};
let mut local_mana_pools: Vec<ManaPool> =
(0..game.players.len()).map(|_| ManaPool::new()).collect();
let mana_pools_for_targets: &[ManaPool] = if let Some(rt) = runtime.as_ref() {
rt.mana_pools.as_slice()
} else {
local_mana_pools.as_slice()
};
if sa.uses_targeting() && !sa.setup_targets(game, agents, mana_pools_for_targets) {
return false;
}
let mut local_trigger_handler = TriggerHandler::new();
let local_token_templates: HashMap<String, Card> = HashMap::new();
let local_token_art_variants: HashMap<(String, String), usize> = HashMap::new();
let local_token_fallback: HashMap<String, String> = HashMap::new();
let local_edition_dates: HashMap<String, String> = HashMap::new();
let mut local_rng = ThreadRngAdapter;
let mut parent_target_card: Option<CardId> = None;
let mut parent_target_player = None;
let mut current_sa: Option<&crate::spellability::SpellAbility> = Some(&sa);
while let Some(cur) = current_sa {
let mut sa_with_ctx;
let sa_ref = if parent_target_player.is_some() && cur.target_chosen.target_player.is_none()
{
sa_with_ctx = cur.clone();
sa_with_ctx.target_chosen.target_player = parent_target_player;
&sa_with_ctx
} else {
cur
};
let (
trigger_handler_ref,
token_templates_ref,
token_art_ref,
token_fb_ref,
edition_dates_ref,
mana_pools_ref,
rng_ref,
): (
&mut TriggerHandler,
&HashMap<String, Card>,
&HashMap<(String, String), usize>,
&HashMap<String, String>,
&HashMap<String, String>,
&mut Vec<ManaPool>,
&mut dyn crate::game_rng::GameRng,
) = if let Some(rt) = runtime.as_deref_mut() {
(
rt.trigger_handler,
rt.token_templates,
rt.token_art_variants,
rt.token_fallback,
rt.edition_dates,
rt.mana_pools,
rt.rng,
)
} else {
(
&mut local_trigger_handler,
&local_token_templates,
&local_token_art_variants,
&local_token_fallback,
&local_edition_dates,
&mut local_mana_pools,
&mut local_rng,
)
};
let mut ctx = EffectContext {
game,
combat: None,
agents,
trigger_handler: trigger_handler_ref,
token_templates: token_templates_ref,
token_art_variants: token_art_ref,
token_fallback: token_fb_ref,
edition_dates: edition_dates_ref,
mana_pools: mana_pools_ref,
parent_target_card,
rng: rng_ref,
};
effects::resolve_effect(&mut ctx, sa_ref);
parent_target_card = sa_ref.target_chosen.target_card;
parent_target_player = sa_ref.target_chosen.target_player;
current_sa = cur.get_sub_ability();
}
true
}