manabrew_engine/card/
card_zone_table.rs1use std::collections::HashMap;
4
5use forge_foundation::ZoneType;
6use serde::{Deserialize, Serialize};
7
8use crate::card::valid_filter;
9use crate::event::{RunParams, ZoneChangeRecord};
10use crate::game::GameState;
11use crate::ids::{CardId, PlayerId};
12use crate::parsing::CompiledSelector;
13use crate::spellability::SpellAbility;
14use crate::trigger::TriggerHandler;
15use crate::trigger::TriggerType;
16
17#[derive(Debug, Clone, Default, Serialize, Deserialize)]
18pub struct CardZoneTable {
19 data: HashMap<(ZoneType, ZoneType), Vec<CardId>>,
20 created_tokens: Vec<CardId>,
21 first_time_token_creators: Vec<PlayerId>,
22 last_state_battlefield: Vec<CardId>,
23 last_state_graveyard: Vec<CardId>,
24}
25
26impl CardZoneTable {
27 pub fn put(&mut self, origin: Option<ZoneType>, destination: Option<ZoneType>, card: CardId) {
29 let from = origin.unwrap_or(ZoneType::None);
30 let to = destination.unwrap_or(ZoneType::None);
31 self.data.entry((from, to)).or_default().push(card);
32 }
33
34 pub fn trigger_changes_zone_all(
35 &self,
36 trigger_handler: &mut TriggerHandler,
37 game: &GameState,
38 cause: Option<&SpellAbility>,
39 ) {
40 if !self.created_tokens.is_empty() {
41 trigger_handler.run_trigger(
42 TriggerType::TokenCreatedOnce,
43 RunParams {
44 cards: Some(self.created_tokens.clone()),
45 first_time_players: if self.first_time_token_creators.is_empty() {
46 None
47 } else {
48 Some(self.first_time_token_creators.clone())
49 },
50 ..Default::default()
51 },
52 false,
53 );
54 }
55 if !self.data.is_empty() {
56 let table = self.with_last_state(game);
57 for &card_id in table.last_state_battlefield() {
58 trigger_handler.register_active_ltb_trigger(game, card_id);
59 }
60 trigger_handler.run_trigger(
61 TriggerType::ChangesZoneAll,
62 RunParams {
63 cards: Some(table.all_cards()),
64 zone_changes: Some(table.zone_changes()),
65 change_zone_table: Some(table),
66 cause: cause.cloned(),
67 ..Default::default()
68 },
69 false,
70 );
71 }
72 }
73
74 pub fn filter_cards(
75 &self,
76 game: &GameState,
77 origin: Option<&[ZoneType]>,
78 destination: Option<&[ZoneType]>,
79 valid: Option<&CompiledSelector>,
80 source: CardId,
81 source_controller: PlayerId,
82 ) -> Vec<CardId> {
83 let mut out = Vec::new();
84 for (&(from, to), cards) in &self.data {
85 if let Some(origins) = origin {
86 if !origins.contains(&from) {
87 continue;
88 }
89 }
90 if let Some(destinations) = destination {
91 if !destinations.contains(&to) {
92 continue;
93 }
94 }
95 out.extend(cards.iter().copied());
96 }
97 if let Some(filter) = valid {
98 out.retain(|&cid| {
99 valid_filter::matches_valid_card_selector_in_game(
100 filter,
101 game.card(cid),
102 game.card(source),
103 game,
104 )
105 });
106 }
107 let _ = source_controller;
108 out
109 }
110
111 pub fn all_cards(&self) -> Vec<CardId> {
112 self.data.values().flat_map(|v| v.iter().copied()).collect()
113 }
114
115 pub fn zone_changes(&self) -> Vec<ZoneChangeRecord> {
116 let mut out = Vec::new();
117 for (&(origin, destination), cards) in &self.data {
118 for &card in cards {
119 out.push(ZoneChangeRecord {
120 origin,
121 destination,
122 card,
123 });
124 }
125 }
126 out
127 }
128
129 pub fn add_token(&mut self, card: CardId, owner: PlayerId, first_time: bool) {
130 self.created_tokens.push(card);
131 if first_time && !self.first_time_token_creators.contains(&owner) {
132 self.first_time_token_creators.push(owner);
133 }
134 }
135
136 pub fn last_state_battlefield(&self) -> &[CardId] {
137 &self.last_state_battlefield
138 }
139
140 pub fn last_state_graveyard(&self) -> &[CardId] {
141 &self.last_state_graveyard
142 }
143
144 fn with_last_state(&self, game: &GameState) -> Self {
145 let mut table = self.clone();
146 table.last_state_battlefield = game.pre_sba_battlefield.clone();
147 table.last_state_graveyard = game.cards_in_all_zones(ZoneType::Graveyard).collect();
148 table
149 }
150}