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}