1use serde::{Deserialize, Serialize};
2use ts_rs::TS;
3
4use crate::game::PlaymatSettings;
5
6#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
7#[serde(rename_all = "camelCase")]
8#[ts(export, export_to = "deck/index.ts")]
9pub struct DeckCardIdentity {
10 #[serde(default, skip_serializing_if = "String::is_empty")]
11 pub id: String,
12 pub name: String,
13 pub set_code: String,
14 pub card_number: String,
15 #[serde(default, skip_serializing_if = "Option::is_none")]
16 #[ts(optional)]
17 pub foil: Option<bool>,
18}
19
20#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
24#[serde(rename_all = "camelCase")]
25#[ts(export, export_to = "deck/index.ts")]
26pub struct CardRulesSummary {
27 #[serde(default)]
28 pub color: String,
29 #[serde(default)]
30 pub color_identity: Vec<String>,
31 #[serde(default)]
32 pub mana_cost: String,
33 #[serde(default)]
34 pub cmc: f32,
35 #[serde(default)]
36 pub types: Vec<String>,
37 #[serde(default)]
38 pub subtypes: Vec<String>,
39 #[serde(default)]
40 pub supertypes: Vec<String>,
41 #[serde(default, skip_serializing_if = "Option::is_none")]
42 #[ts(optional)]
43 pub keywords: Option<Vec<String>>,
44 #[serde(default, skip_serializing_if = "Option::is_none")]
45 #[ts(optional)]
46 pub power: Option<String>,
47 #[serde(default, skip_serializing_if = "Option::is_none")]
48 #[ts(optional)]
49 pub toughness: Option<String>,
50 #[serde(default)]
51 pub text: String,
52 #[serde(default, skip_serializing_if = "Option::is_none")]
53 #[ts(optional)]
54 pub layout: Option<String>,
55 #[serde(default, skip_serializing_if = "Option::is_none")]
56 #[ts(optional)]
57 pub is_double_faced: Option<bool>,
58}
59
60#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
61#[ts(export, export_to = "deck/index.ts")]
62pub struct CardImageUris {
63 #[serde(default)]
64 pub small: String,
65 #[serde(default)]
66 pub normal: String,
67 #[serde(default)]
68 pub large: String,
69 #[serde(default)]
70 pub png: String,
71 #[serde(default)]
72 pub art_crop: String,
73 #[serde(default)]
74 pub border_crop: String,
75}
76
77#[derive(Debug, Clone, Copy, Serialize, Deserialize, TS)]
78#[serde(rename_all = "snake_case")]
79#[ts(export, export_to = "deck/index.ts")]
80pub enum CardPartComponent {
81 Token,
82 ComboPiece,
83 MeldPart,
84 MeldResult,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize, TS)]
88#[serde(rename_all = "camelCase")]
89#[ts(export, export_to = "deck/index.ts")]
90pub struct CardPart {
91 pub name: String,
92 pub component: CardPartComponent,
93}
94
95#[derive(Debug, Clone, Default, Serialize, TS)]
96#[serde(rename_all = "camelCase")]
97#[ts(export, export_to = "deck/index.ts")]
98pub struct DeckCard {
99 pub identity: DeckCardIdentity,
100 #[serde(flatten)]
101 #[ts(flatten)]
102 pub rules: CardRulesSummary,
103 #[serde(default)]
104 pub uris: CardImageUris,
105 #[serde(default, skip_serializing_if = "Option::is_none")]
106 #[ts(optional)]
107 pub all_parts: Option<Vec<CardPart>>,
108}
109
110pub const OUTDATED_CLIENT_MESSAGE: &str =
111 "this app version is out of date — download the latest release at manabrew.app to play online";
112
113#[derive(Deserialize)]
118#[serde(rename_all = "camelCase")]
119struct DeckCardWire {
120 #[serde(default)]
121 identity: Option<DeckCardIdentity>,
122 #[serde(default)]
123 name: Option<String>,
124 #[serde(flatten)]
125 rules: CardRulesSummary,
126 #[serde(default)]
127 uris: CardImageUris,
128 #[serde(default)]
129 all_parts: Option<Vec<CardPart>>,
130}
131
132impl<'de> Deserialize<'de> for DeckCard {
133 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
134 let wire = DeckCardWire::deserialize(deserializer)?;
135 let identity = match wire.identity {
136 Some(identity) => identity,
137 None if wire.name.is_some() => {
138 return Err(serde::de::Error::custom(OUTDATED_CLIENT_MESSAGE));
139 }
140 None => return Err(serde::de::Error::missing_field("identity")),
141 };
142 Ok(DeckCard {
143 identity,
144 rules: wire.rules,
145 uris: wire.uris,
146 all_parts: wire.all_parts,
147 })
148 }
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize, TS)]
152#[serde(rename_all = "camelCase")]
153#[ts(export, export_to = "deck/index.ts")]
154pub struct DeckLabel {
155 pub name: String,
156 #[serde(default, skip_serializing_if = "Option::is_none")]
157 #[ts(optional)]
158 pub color: Option<String>,
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
162#[serde(rename_all = "camelCase")]
163#[ts(export, export_to = "deck/index.ts")]
164pub enum DeckFormat {
165 Standard,
166 Pioneer,
167 Modern,
168 Legacy,
169 Vintage,
170 Pauper,
171 Commander,
172 Brawl,
173 Oathbreaker,
174 Draft,
175 Sealed,
176}
177
178#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
179#[serde(rename_all = "camelCase")]
180#[ts(export, export_to = "deck/index.ts")]
181pub struct Deck {
182 #[serde(default, skip_serializing_if = "Option::is_none")]
183 #[ts(optional)]
184 pub version: Option<String>,
185 #[serde(default, skip_serializing_if = "Option::is_none")]
186 #[ts(optional)]
187 pub id: Option<String>,
188 pub name: String,
189 #[serde(default, skip_serializing_if = "Option::is_none")]
190 #[ts(optional)]
191 pub description: Option<String>,
192 #[serde(default, skip_serializing_if = "Option::is_none")]
193 #[ts(optional)]
194 pub color: Option<String>,
195 #[serde(default, skip_serializing_if = "Option::is_none")]
196 #[ts(optional)]
197 pub format: Option<DeckFormat>,
198 #[serde(default)]
199 pub cards: Vec<DeckCard>,
200 #[serde(default)]
201 pub sideboard: Vec<DeckCard>,
202 #[serde(default, skip_serializing_if = "Option::is_none")]
203 #[ts(optional)]
204 pub attractions: Option<Vec<DeckCard>>,
205 #[serde(default, skip_serializing_if = "Option::is_none")]
206 #[ts(optional)]
207 pub contraptions: Option<Vec<DeckCard>>,
208 #[serde(default, skip_serializing_if = "Option::is_none")]
209 #[ts(optional)]
210 pub schemes: Option<Vec<DeckCard>>,
211 #[serde(default, skip_serializing_if = "Option::is_none")]
212 #[ts(optional)]
213 pub planes: Option<Vec<DeckCard>>,
214 #[serde(default, skip_serializing_if = "Option::is_none")]
215 #[ts(optional)]
216 pub commanders: Option<Vec<DeckCard>>,
217 #[serde(default, skip_serializing_if = "Option::is_none")]
218 #[ts(optional)]
219 pub companion: Option<DeckCard>,
220 #[serde(default, skip_serializing_if = "Option::is_none")]
221 #[ts(optional)]
222 pub maybeboard: Option<Vec<DeckCard>>,
223 #[serde(default, skip_serializing_if = "Option::is_none")]
224 #[ts(optional)]
225 pub draft: Option<bool>,
226 #[serde(default, skip_serializing_if = "Option::is_none")]
227 #[ts(optional)]
228 pub labels: Option<Vec<DeckLabel>>,
229 #[serde(default, skip_serializing_if = "Option::is_none")]
230 #[ts(optional)]
231 pub cover_card_name: Option<String>,
232 #[serde(default, skip_serializing_if = "Option::is_none")]
233 #[ts(optional)]
234 pub cover_card_face: Option<u8>,
235 #[serde(default, skip_serializing_if = "Option::is_none")]
236 #[ts(optional)]
237 pub playmat: Option<String>,
238 #[serde(default, skip_serializing_if = "Option::is_none")]
239 #[ts(optional)]
240 pub playmat_settings: Option<PlaymatSettings>,
241 #[serde(default, skip_serializing_if = "Option::is_none")]
242 #[ts(optional, type = "Record<string, { x: number; y: number }>")]
243 pub stack_positions: Option<serde_json::Value>,
244 #[serde(default, skip_serializing_if = "Option::is_none")]
245 #[ts(optional)]
246 pub tokens: Option<Vec<DeckCard>>,
247}