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