use forge_foundation::ZoneType;
use super::EffectContext;
use crate::event::RunParams;
use crate::trigger::TriggerType;
pub fn run(game: &mut crate::game::GameState, card_id: crate::ids::CardId) {
if game.card(card_id).zone != ZoneType::Battlefield {
return;
}
revert(game, card_id);
}
fn revert(game: &mut crate::game::GameState, card_id: crate::ids::CardId) {
if let Some(original) = game.card(card_id).original_controller_eot {
game.change_controller(card_id, original);
game.card_mut(card_id).set_original_controller_eot(None);
}
game.card_mut(card_id).lose_control_condition = None;
game.card_mut(card_id).clear_granted_keywords();
}
pub fn untap_hook(game: &mut crate::game::GameState, card_id: crate::ids::CardId) {
if game.card(card_id).lose_control_condition
== Some(crate::card::LoseControlCondition::NextUntap)
{
revert(game, card_id);
}
}
pub fn end_of_combat_hook(game: &mut crate::game::GameState) {
let targets: Vec<crate::ids::CardId> = game
.cards
.iter()
.filter(|c| {
c.lose_control_condition == Some(crate::card::LoseControlCondition::EndOfCombat)
})
.map(|c| c.id)
.collect();
for cid in targets {
if game.card(cid).zone == ZoneType::Battlefield {
revert(game, cid);
}
}
}
pub fn leaves_play_hook(game: &mut crate::game::GameState, card_id: crate::ids::CardId) {
if game.card(card_id).lose_control_condition
== Some(crate::card::LoseControlCondition::LeavesPlay)
{
game.card_mut(card_id).lose_control_condition = None;
game.card_mut(card_id).set_original_controller_eot(None);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ability::spell_ability_effect::SpellAbilityEffect;
use crate::agent::{PassAgent, PlayerAgent};
use crate::card::Card;
use crate::game::GameState;
use crate::ids::{CardId, PlayerId};
use crate::mana::ManaPool;
use crate::spellability::SpellAbility;
use crate::trigger::TriggerHandler;
use forge_foundation::{CardTypeLine, ColorSet, ManaCost, ZoneType};
use std::collections::HashMap;
fn creature(owner: PlayerId, name: &str) -> Card {
Card::new(
CardId(0),
name.to_string(),
owner,
CardTypeLine::parse("Creature Goblin"),
ManaCost::parse("R"),
ColorSet::RED,
Some(1),
Some(1),
vec![],
vec![],
)
}
#[test]
fn repeated_eot_control_gain_restores_first_controller() {
let p0 = PlayerId(0);
let p1 = PlayerId(1);
let mut game = GameState::new(&["Alice", "Bob"], 20);
let goblin = game.create_card(creature(p0, "Raging Goblin"));
game.move_card(goblin, ZoneType::Battlefield, p0);
let mut agents: Vec<Box<dyn PlayerAgent>> = vec![Box::new(PassAgent), Box::new(PassAgent)];
let mut trigger_handler = TriggerHandler::new();
let templates = HashMap::new();
let token_art_variants = HashMap::new();
let token_fallback = HashMap::new();
let edition_dates = HashMap::new();
let mut mana_pools = vec![ManaPool::new(), ManaPool::new()];
let mut rng = crate::game_rng::ThreadRngAdapter;
{
let mut ctx = EffectContext {
game: &mut game,
combat: None,
agents: &mut agents,
trigger_handler: &mut trigger_handler,
token_templates: &templates,
token_art_variants: &token_art_variants,
token_fallback: &token_fallback,
edition_dates: &edition_dates,
mana_pools: &mut mana_pools,
parent_target_card: None,
rng: &mut rng,
};
let mut steal = SpellAbility::new_simple(
Some(goblin),
p1,
"SP$ GainControl | ValidTgts$ Creature.OppCtrl | LoseControl$ EOT",
);
steal.target_chosen.target_card = Some(goblin);
ControlGainEffect::resolve(&mut ctx, &steal);
assert_eq!(ctx.game.card(goblin).controller, p1);
let mut steal_back = SpellAbility::new_simple(
Some(goblin),
p0,
"SP$ GainControl | ValidTgts$ Creature.OppCtrl | LoseControl$ EOT",
);
steal_back.target_chosen.target_card = Some(goblin);
ControlGainEffect::resolve(&mut ctx, &steal_back);
assert_eq!(ctx.game.card(goblin).controller, p0);
}
assert_eq!(game.card(goblin).original_controller_eot, Some(p0));
run(&mut game, goblin);
assert_eq!(game.card(goblin).controller, p0);
assert_eq!(game.card(goblin).original_controller_eot, None);
}
}
#[manabrew_engine_macros::spell_effect(ControlGainEffect)]
fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
let target_card = match sa.target_chosen.target_card {
Some(c) => c,
None => return,
};
if ctx.game.card(target_card).zone != ZoneType::Battlefield {
return;
}
let new_controller = sa.activating_player;
if !ctx
.game
.card(target_card)
.can_be_controlled_by(new_controller)
{
return;
}
let old_controller = ctx.game.card(target_card).controller;
ctx.game.change_controller(target_card, new_controller);
if old_controller != new_controller {
ctx.trigger_handler.run_trigger(
TriggerType::ChangesController,
RunParams {
card: Some(target_card),
player: Some(new_controller),
original_controller: Some(old_controller),
..Default::default()
},
false,
);
}
let already_scheduled = ctx.game.card(target_card).original_controller_eot.is_some();
if let Some(cond) = sa.ir.lose_control {
if !already_scheduled {
ctx.game
.card_mut(target_card)
.set_original_controller_eot(Some(old_controller));
}
ctx.game.card_mut(target_card).lose_control_condition = Some(cond);
}
if sa.ir.untap_on_resolve {
ctx.game.untap(target_card);
}
if let Some(kws_str) = sa.ir.add_kws.as_deref() {
let keywords: Vec<String> = kws_str.split(" & ").map(|s| s.to_string()).collect();
for kw in keywords {
ctx.game.card_mut(target_card).add_granted_keyword(&kw);
}
}
}