manabrew_engine/ability/effects/
venture_effect.rs1use forge_foundation::{CardTypeLine, ColorSet, ManaCost, ZoneType};
17
18use super::EffectContext;
19use crate::card::Card;
20use crate::event::RunParams;
21use crate::ids::{CardId, PlayerId};
22use crate::spellability::SpellAbility;
23use crate::trigger::TriggerType;
24
25const DUNGEONS: &[(&str, &[&str])] = &[
28 (
29 "Dungeon of the Mad Mage",
30 &[
31 "Yawning Portal",
32 "Dungeon Level",
33 "Goblin Bazaar",
34 "Twisted Caverns",
35 "Lost Level",
36 "Runestone Caverns",
37 "Mad Wizard's Lair",
38 ],
39 ),
40 (
41 "Lost Mine of Phandelver",
42 &[
43 "Cave Entrance",
44 "Goblin Lair",
45 "Mine Tunnels",
46 "Temple of Dumathoin",
47 ],
48 ),
49 (
50 "Tomb of Annihilation",
51 &[
52 "Trapped Entry",
53 "Veils of Fear",
54 "Sandfall Cell",
55 "Cradle of the Death God",
56 ],
57 ),
58 (
59 "Undercity",
60 &[
61 "Secret Entrance",
62 "Forge",
63 "Lost Well",
64 "Throne of the Dead Three",
65 ],
66 ),
67];
68
69#[manabrew_engine_macros::spell_effect(VentureEffect)]
73fn resolve(ctx: &mut EffectContext, sa: &crate::spellability::SpellAbility) {
74 let controller = sa.activating_player;
75 let players = if let Some(def) = sa.defined_player() {
76 super::resolve_defined_players(def, controller, ctx.game)
77 } else if let Some(target_player) = sa.target_chosen.target_player {
78 vec![target_player]
79 } else {
80 vec![controller]
81 };
82
83 for pid in players {
84 if ctx.game.player(pid).has_lost {
85 continue;
86 }
87 venture_into_dungeon(ctx, sa, pid);
88 }
89}
90
91fn venture_into_dungeon(ctx: &mut EffectContext, sa: &SpellAbility, player: PlayerId) {
92 let current_dungeon = find_current_dungeon(ctx, player);
94
95 let dungeon_id = match current_dungeon {
96 Some((id, room, dungeon_name)) => {
97 let rooms = get_dungeon_rooms(&dungeon_name);
99 if let Some(rooms) = rooms {
100 if !room.is_empty() && rooms.last().is_some_and(|last| *last == room) {
101 complete_dungeon(ctx, player, id);
103 create_dungeon(ctx, sa, player)
105 } else {
106 id
108 }
109 } else {
110 id
111 }
112 }
113 None => {
114 create_dungeon(ctx, sa, player)
116 }
117 };
118
119 let dungeon_name = ctx
121 .game
122 .card(dungeon_id)
123 .svars
124 .get("DungeonName")
125 .cloned()
126 .unwrap_or_default();
127 let current_room = ctx
128 .game
129 .card(dungeon_id)
130 .svars
131 .get("CurrentRoom")
132 .cloned()
133 .unwrap_or_default();
134
135 let rooms = get_dungeon_rooms(&dungeon_name);
136 let next_room = if let Some(rooms) = rooms {
137 if current_room.is_empty() {
138 rooms.first().map(|s| s.to_string())
140 } else {
141 let idx = rooms.iter().position(|r| *r == current_room);
143 match idx {
144 Some(i) if i + 1 < rooms.len() => Some(rooms[i + 1].to_string()),
145 _ => None, }
147 }
148 } else {
149 None
150 };
151
152 if let Some(next) = next_room {
153 ctx.game
154 .card_mut(dungeon_id)
155 .svars
156 .insert("CurrentRoom".to_string(), next.clone());
157
158 ctx.trigger_handler.run_trigger(
160 TriggerType::RoomEntered,
161 RunParams {
162 card: Some(dungeon_id),
163 player: Some(player),
164 room_name: Some(next),
165 ..Default::default()
166 },
167 false,
168 );
169 }
170}
171
172fn find_current_dungeon(ctx: &EffectContext, player: PlayerId) -> Option<(CardId, String, String)> {
173 ctx.game
174 .cards
175 .iter()
176 .find(|c| {
177 c.zone == ZoneType::Command && c.owner == player && c.svars.contains_key("DungeonName")
178 })
179 .map(|c| {
180 let room = c.svars.get("CurrentRoom").cloned().unwrap_or_default();
181 let name = c.svars.get("DungeonName").cloned().unwrap_or_default();
182 (c.id, room, name)
183 })
184}
185
186fn create_dungeon(ctx: &mut EffectContext, sa: &SpellAbility, player: PlayerId) -> CardId {
187 let dungeon_name = sa.ir.dungeon_text.clone().unwrap_or_else(|| {
189 let idx = ctx.rng.next_int(DUNGEONS.len() as i32) as usize % DUNGEONS.len();
191 DUNGEONS[idx].0.to_string()
192 });
193
194 let mut card = Card::new(
195 CardId(0),
196 dungeon_name.clone(),
197 player,
198 CardTypeLine::parse("Dungeon"),
199 ManaCost::parse(""),
200 ColorSet::COLORLESS,
201 None,
202 None,
203 vec![],
204 vec![],
205 );
206 card.set_controller(player);
207 card.set_s_var("DungeonName", dungeon_name);
208 card.set_s_var("CurrentRoom", String::new());
209
210 let id = ctx.game.create_card(card);
211 ctx.move_card(id, ZoneType::Command, player);
212 ctx.trigger_handler.register_active_trigger(ctx.game, id);
213
214 id
215}
216
217fn complete_dungeon(ctx: &mut EffectContext, player: PlayerId, dungeon_id: CardId) {
218 let old_zone = ctx.game.card(dungeon_id).zone;
220 ctx.move_card(dungeon_id, ZoneType::Exile, player);
221 super::emit_zone_trigger(ctx.trigger_handler, dungeon_id, old_zone, ZoneType::Exile);
222
223 ctx.trigger_handler.run_trigger(
225 TriggerType::DungeonCompleted,
226 RunParams {
227 card: Some(dungeon_id),
228 player: Some(player),
229 ..Default::default()
230 },
231 false,
232 );
233}
234
235fn get_dungeon_rooms(name: &str) -> Option<&'static [&'static str]> {
236 DUNGEONS
237 .iter()
238 .find(|(n, _)| *n == name)
239 .map(|(_, rooms)| *rooms)
240}