Skip to main content

manabrew_engine/player/
registered_player.rs

1use serde::{Deserialize, Serialize};
2
3use crate::ids::PlayerId;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
6pub enum RegisteredPlayerVariant {
7    Archenemy,
8    ArchenemyRumble,
9    Commander,
10    Oathbreaker,
11    TinyLeaders,
12    Brawl,
13    Planechase,
14    Vanguard,
15    MomirBasic,
16    MoJhoSto,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct RegisteredPlayer {
21    pub id: Option<PlayerId>,
22    pub name: String,
23    pub original_deck: Vec<String>,
24    pub current_deck: Vec<String>,
25    pub starting_life: i32,
26    pub starting_hand: i32,
27    pub max_hand_size: i32,
28    pub mana_shards: i32,
29    pub team_number: i32,
30    pub cards_on_battlefield: Vec<String>,
31    pub extra_cards_on_battlefield: Vec<String>,
32    pub extra_cards_in_command_zone: Vec<String>,
33    pub schemes: Vec<String>,
34    pub planes: Vec<String>,
35    pub conspiracies: Vec<String>,
36    pub attractions: Vec<String>,
37    pub contraptions: Vec<String>,
38    pub commanders: Vec<String>,
39    pub vanguard_avatars: Vec<String>,
40    pub planeswalker: Option<String>,
41    pub random_foil: bool,
42    pub enable_etb_counters_effect: bool,
43    pub commander_damage_enabled: bool,
44}
45
46impl RegisteredPlayer {
47    pub fn new(name: impl Into<String>) -> Self {
48        Self {
49            id: None,
50            name: name.into(),
51            original_deck: Vec::new(),
52            current_deck: Vec::new(),
53            starting_life: 20,
54            starting_hand: 7,
55            max_hand_size: 7,
56            mana_shards: 0,
57            team_number: -1,
58            cards_on_battlefield: Vec::new(),
59            extra_cards_on_battlefield: Vec::new(),
60            extra_cards_in_command_zone: Vec::new(),
61            schemes: Vec::new(),
62            planes: Vec::new(),
63            conspiracies: Vec::new(),
64            attractions: Vec::new(),
65            contraptions: Vec::new(),
66            commanders: Vec::new(),
67            vanguard_avatars: Vec::new(),
68            planeswalker: None,
69            random_foil: false,
70            enable_etb_counters_effect: false,
71            commander_damage_enabled: true,
72        }
73    }
74
75    pub fn for_commander(name: impl Into<String>, commanders: Vec<String>) -> Self {
76        let mut rp = Self::new(name);
77        rp.starting_life = 40;
78        rp.commanders = commanders;
79        rp
80    }
81
82    pub fn has_enable_etb_counters_effect(&self) -> bool {
83        self.enable_etb_counters_effect
84    }
85
86    pub fn cards_on_battlefield(&self) -> impl Iterator<Item = &String> {
87        self.cards_on_battlefield
88            .iter()
89            .chain(self.extra_cards_on_battlefield.iter())
90    }
91
92    pub fn command_zone_cards(&self) -> impl Iterator<Item = &String> {
93        self.extra_cards_in_command_zone
94            .iter()
95            .chain(self.commanders.iter())
96    }
97
98    pub fn add_extra_cards_on_battlefield<I>(&mut self, cards: I)
99    where
100        I: IntoIterator<Item = String>,
101    {
102        self.extra_cards_on_battlefield.extend(cards);
103    }
104
105    pub fn add_extra_cards_in_command_zone<I>(&mut self, cards: I)
106    where
107        I: IntoIterator<Item = String>,
108    {
109        self.extra_cards_in_command_zone.extend(cards);
110    }
111
112    pub fn assign_conspiracies<I>(&mut self, cards: I)
113    where
114        I: IntoIterator<Item = String>,
115    {
116        self.conspiracies = cards.into_iter().collect();
117    }
118
119    pub fn for_variants(
120        name: impl Into<String>,
121        commanders: Vec<String>,
122        schemes: Vec<String>,
123        planes: Vec<String>,
124        vanguard_avatars: Vec<String>,
125    ) -> Self {
126        let mut rp = Self::new(name);
127        if !commanders.is_empty() {
128            rp.apply_variants(&[RegisteredPlayerVariant::Commander], false, &commanders);
129        }
130        rp.schemes = schemes;
131        rp.planes = planes;
132        rp.assign_vanguard_avatar(vanguard_avatars);
133        rp
134    }
135
136    pub fn for_variant_set(
137        name: impl Into<String>,
138        variants: &[RegisteredPlayerVariant],
139        commanders: Vec<String>,
140        schemes: Vec<String>,
141        player_is_archenemy: bool,
142        planes: Vec<String>,
143        vanguard_avatars: Vec<String>,
144    ) -> Self {
145        let mut rp = Self::new(name);
146        rp.apply_variants(variants, player_is_archenemy, &commanders);
147        rp.schemes = if variants.contains(&RegisteredPlayerVariant::Archenemy)
148            || variants.contains(&RegisteredPlayerVariant::ArchenemyRumble)
149        {
150            schemes
151        } else {
152            Vec::new()
153        };
154        if variants.contains(&RegisteredPlayerVariant::Planechase) {
155            rp.planes = planes;
156        }
157        if variants.iter().any(|variant| {
158            matches!(
159                variant,
160                RegisteredPlayerVariant::Vanguard
161                    | RegisteredPlayerVariant::MomirBasic
162                    | RegisteredPlayerVariant::MoJhoSto
163            )
164        }) {
165            rp.assign_vanguard_avatar(vanguard_avatars);
166        }
167        rp
168    }
169
170    pub fn apply_variants(
171        &mut self,
172        variants: &[RegisteredPlayerVariant],
173        player_is_archenemy: bool,
174        commanders: &[String],
175    ) {
176        self.commander_damage_enabled = true;
177        for variant in variants {
178            match variant {
179                RegisteredPlayerVariant::Archenemy if player_is_archenemy => {
180                    self.starting_life = 40;
181                }
182                RegisteredPlayerVariant::ArchenemyRumble => {
183                    self.starting_life = 40;
184                }
185                RegisteredPlayerVariant::Commander => {
186                    self.commanders = commanders.to_vec();
187                    self.starting_life += 20;
188                }
189                RegisteredPlayerVariant::Oathbreaker => {
190                    self.commanders = commanders.to_vec();
191                    self.commander_damage_enabled = false;
192                }
193                RegisteredPlayerVariant::TinyLeaders => {
194                    self.commanders = commanders.to_vec();
195                    self.starting_life += 5;
196                    self.commander_damage_enabled = false;
197                }
198                RegisteredPlayerVariant::Brawl => {
199                    self.commanders = commanders.to_vec();
200                    self.starting_life += 10;
201                    self.commander_damage_enabled = false;
202                }
203                RegisteredPlayerVariant::Planechase
204                | RegisteredPlayerVariant::Vanguard
205                | RegisteredPlayerVariant::MomirBasic
206                | RegisteredPlayerVariant::MoJhoSto
207                | RegisteredPlayerVariant::Archenemy => {}
208            }
209        }
210    }
211
212    pub fn assign_commander<I>(&mut self, commanders: I)
213    where
214        I: IntoIterator<Item = String>,
215    {
216        self.commanders = commanders.into_iter().collect();
217    }
218
219    pub fn assign_vanguard_avatar<I>(&mut self, avatars: I)
220    where
221        I: IntoIterator<Item = String>,
222    {
223        self.vanguard_avatars = avatars.into_iter().collect();
224        if !self.vanguard_avatars.is_empty() {
225            self.starting_life += self.vanguard_avatars.len() as i32;
226            self.starting_hand += self.vanguard_avatars.len() as i32;
227            self.max_hand_size = self.max_hand_size.max(self.starting_hand);
228        }
229    }
230
231    pub fn restore_deck(&mut self) {
232        self.current_deck = self.original_deck.clone();
233    }
234
235    pub fn use_random_foil(&mut self, value: bool) {
236        self.random_foil = value;
237    }
238
239    pub fn set_cards_on_battlefield<I>(&mut self, cards: I)
240    where
241        I: IntoIterator<Item = String>,
242    {
243        self.cards_on_battlefield = cards.into_iter().collect();
244    }
245
246    pub fn set_schemes<I>(&mut self, cards: I)
247    where
248        I: IntoIterator<Item = String>,
249    {
250        self.schemes = cards.into_iter().collect();
251    }
252
253    pub fn set_planes<I>(&mut self, cards: I)
254    where
255        I: IntoIterator<Item = String>,
256    {
257        self.planes = cards.into_iter().collect();
258    }
259
260    pub fn set_attractions<I>(&mut self, cards: I)
261    where
262        I: IntoIterator<Item = String>,
263    {
264        self.attractions = cards.into_iter().collect();
265    }
266
267    pub fn set_contraptions<I>(&mut self, cards: I)
268    where
269        I: IntoIterator<Item = String>,
270    {
271        self.contraptions = cards.into_iter().collect();
272    }
273}
274
275#[cfg(test)]
276mod tests {
277    use super::{RegisteredPlayer, RegisteredPlayerVariant};
278
279    #[test]
280    fn variant_set_applies_commander_family_life_rules() {
281        let rp = RegisteredPlayer::for_variant_set(
282            "Alice",
283            &[
284                RegisteredPlayerVariant::Commander,
285                RegisteredPlayerVariant::Planechase,
286            ],
287            vec!["A".to_string()],
288            vec![],
289            false,
290            vec!["Plane".to_string()],
291            vec![],
292        );
293        assert_eq!(rp.starting_life, 40);
294        assert_eq!(rp.commanders, vec!["A".to_string()]);
295        assert_eq!(rp.planes, vec!["Plane".to_string()]);
296        assert!(rp.commander_damage_enabled);
297    }
298
299    #[test]
300    fn oathbreaker_style_variants_disable_commander_damage_loss() {
301        for variant in [
302            RegisteredPlayerVariant::Oathbreaker,
303            RegisteredPlayerVariant::TinyLeaders,
304            RegisteredPlayerVariant::Brawl,
305        ] {
306            let rp = RegisteredPlayer::for_variant_set(
307                "Alice",
308                &[variant],
309                vec!["Commander".to_string()],
310                vec![],
311                false,
312                vec![],
313                vec![],
314            );
315            assert!(!rp.commander_damage_enabled);
316            assert_eq!(rp.commanders, vec!["Commander".to_string()]);
317        }
318    }
319}