Skip to main content

manabrew_engine/ability/effects/
unlock_door_effect.rs

1//! UnlockDoor — unlock a door on a Room card.
2//! Ported from Java's UnlockDoorEffect: unlocks one side of a Room
3//! enchantment, activating its abilities.
4
5use forge_foundation::ZoneType;
6
7use super::EffectContext;
8use crate::event::RunParams;
9use crate::ids::CardId;
10use crate::spellability::SpellAbilityMode;
11use crate::trigger::TriggerType;
12
13fn unlocked_room_count(ctx: &EffectContext, card_id: CardId) -> i32 {
14    let card = ctx.game.card(card_id);
15    // Check explicit counter first.
16    if let Some(count) = card
17        .svars
18        .get("UnlockedRoomCount")
19        .and_then(|v| v.parse::<i32>().ok())
20    {
21        return count;
22    }
23    // A Room on the battlefield always has at least its first door unlocked
24    // (the door it was cast as).  The first door unlock happens implicitly at
25    // ETB without going through unlock_door_effect::resolve(), so
26    // UnlockedRoomCount is never set to 1.  Infer count=1 for Room cards on
27    // the battlefield.
28    if card.zone == ZoneType::Battlefield && card.type_line.has_subtype("Room") {
29        return 1;
30    }
31    0
32}
33
34/// Struct form of this effect so it can participate in the
35/// `SpellAbilityEffect` trait hierarchy — mirrors Java's
36/// `UnlockDoorEffect` class extending `SpellAbilityEffect`.
37#[manabrew_engine_macros::spell_effect(UnlockDoorEffect)]
38fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
39    let targets: Vec<CardId> = if let Some(target) = sa.target_chosen.target_card {
40        vec![target]
41    } else if let Some(source) = sa.source {
42        vec![source]
43    } else {
44        return;
45    };
46
47    let mode = sa.ir.mode.as_ref().unwrap_or(&SpellAbilityMode::ThisDoor);
48
49    for card_id in targets {
50        if ctx.game.card(card_id).zone != ZoneType::Battlefield {
51            continue;
52        }
53
54        let before = unlocked_room_count(ctx, card_id);
55        let mut after = before;
56        let mut unlocked = false;
57        match mode {
58            SpellAbilityMode::ThisDoor => {
59                ctx.game.card_mut(card_id).set_s_var("DoorUnlocked", "True");
60                if before < 2 {
61                    after = before + 1;
62                    unlocked = true;
63                }
64            }
65            SpellAbilityMode::Unlock => {
66                ctx.game.card_mut(card_id).set_s_var("DoorUnlocked", "True");
67                if before < 2 {
68                    after = before + 1;
69                    unlocked = true;
70                }
71            }
72            SpellAbilityMode::LockOrUnlock => {
73                let is_locked = ctx
74                    .game
75                    .card(card_id)
76                    .svars
77                    .get("DoorUnlocked")
78                    .is_none_or(|v| v != "True");
79                if is_locked {
80                    ctx.game.card_mut(card_id).set_s_var("DoorUnlocked", "True");
81                    if before < 2 {
82                        after = before + 1;
83                        unlocked = true;
84                    }
85                } else {
86                    ctx.game.card_mut(card_id).remove_s_var("DoorUnlocked");
87                    after = before.saturating_sub(1);
88                }
89            }
90            _ => {}
91        }
92
93        if after != before {
94            ctx.game
95                .card_mut(card_id)
96                .set_s_var("UnlockedRoomCount", after.to_string());
97        }
98
99        if unlocked {
100            ctx.trigger_handler.run_trigger(
101                TriggerType::UnlockDoor,
102                RunParams {
103                    card: Some(card_id),
104                    player: Some(sa.activating_player),
105                    card_state_name: sa.ir.card_state_name.clone(),
106                    ..Default::default()
107                },
108                true,
109            );
110            if before < 2 && after >= 2 {
111                // When both doors are unlocked, update the card name to the
112                // full combined name (e.g. "Walk-In Closet // Forgotten Cellar").
113                // Mirrors Java's Card.updateRooms() setting state to Original.
114                let full = ctx.game.card(card_id).full_name.clone();
115                ctx.game.card_mut(card_id).card_name = full;
116
117                ctx.trigger_handler.run_trigger(
118                    TriggerType::FullyUnlock,
119                    RunParams {
120                        card: Some(card_id),
121                        player: Some(sa.activating_player),
122                        ..Default::default()
123                    },
124                    true,
125                );
126            }
127        }
128    }
129}