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 #[serde(default, skip_serializing_if = "Option::is_none")]
61 #[ts(optional)]
62 pub back_face: Option<CardBackFaceSummary>,
63}
64
65#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
66#[serde(rename_all = "camelCase")]
67#[ts(export, export_to = "deck/index.ts")]
68pub struct CardBackFaceSummary {
69 pub name: String,
70 #[serde(default)]
71 pub mana_cost: String,
72 #[serde(default)]
73 pub type_line: String,
74 #[serde(default)]
75 pub oracle_text: String,
76 #[serde(default)]
77 pub uris: CardImageUris,
78}
79
80#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
81#[ts(export, export_to = "deck/index.ts")]
82pub struct CardImageUris {
83 #[serde(default)]
84 pub small: String,
85 #[serde(default)]
86 pub normal: String,
87 #[serde(default)]
88 pub large: String,
89 #[serde(default)]
90 pub png: String,
91 #[serde(default)]
92 pub art_crop: String,
93 #[serde(default)]
94 pub border_crop: String,
95}
96
97#[derive(Debug, Clone, Copy, Serialize, Deserialize, TS)]
98#[serde(rename_all = "snake_case")]
99#[ts(export, export_to = "deck/index.ts")]
100pub enum CardPartComponent {
101 Token,
102 ComboPiece,
103 MeldPart,
104 MeldResult,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize, TS)]
108#[serde(rename_all = "camelCase")]
109#[ts(export, export_to = "deck/index.ts")]
110pub struct CardPart {
111 pub name: String,
112 pub component: CardPartComponent,
113}
114
115#[derive(Debug, Clone, Default, Serialize, TS)]
116#[serde(rename_all = "camelCase")]
117#[ts(export, export_to = "deck/index.ts")]
118pub struct DeckCard {
119 pub identity: DeckCardIdentity,
120 #[serde(flatten)]
121 #[ts(flatten)]
122 pub rules: CardRulesSummary,
123 #[serde(default)]
124 pub uris: CardImageUris,
125 #[serde(default, skip_serializing_if = "Option::is_none")]
126 #[ts(optional)]
127 pub all_parts: Option<Vec<CardPart>>,
128}
129
130pub const OUTDATED_CLIENT_MESSAGE: &str =
131 "this app version is out of date — download the latest release at manabrew.app to play online";
132
133#[derive(Deserialize)]
138#[serde(rename_all = "camelCase")]
139struct DeckCardWire {
140 #[serde(default)]
141 identity: Option<DeckCardIdentity>,
142 #[serde(default)]
143 name: Option<String>,
144 #[serde(flatten)]
145 rules: CardRulesSummary,
146 #[serde(default)]
147 uris: CardImageUris,
148 #[serde(default)]
149 all_parts: Option<Vec<CardPart>>,
150}
151
152impl<'de> Deserialize<'de> for DeckCard {
153 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
154 let wire = DeckCardWire::deserialize(deserializer)?;
155 let identity = match wire.identity {
156 Some(identity) => identity,
157 None if wire.name.is_some() => {
158 return Err(serde::de::Error::custom(OUTDATED_CLIENT_MESSAGE));
159 }
160 None => return Err(serde::de::Error::missing_field("identity")),
161 };
162 Ok(DeckCard {
163 identity,
164 rules: wire.rules,
165 uris: wire.uris,
166 all_parts: wire.all_parts,
167 })
168 }
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize, TS)]
172#[serde(rename_all = "camelCase")]
173#[ts(export, export_to = "deck/index.ts")]
174pub struct DeckLabel {
175 pub name: String,
176 #[serde(default, skip_serializing_if = "Option::is_none")]
177 #[ts(optional)]
178 pub color: Option<String>,
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
182#[serde(rename_all = "camelCase")]
183#[ts(export, export_to = "deck/index.ts")]
184pub enum DeckFormat {
185 Standard,
186 Pioneer,
187 Modern,
188 Legacy,
189 Vintage,
190 Pauper,
191 Commander,
192 Brawl,
193 Oathbreaker,
194 Draft,
195 Sealed,
196}
197
198#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
199#[serde(rename_all = "camelCase")]
200#[ts(export, export_to = "deck/index.ts")]
201pub struct Deck {
202 #[serde(default, skip_serializing_if = "Option::is_none")]
203 #[ts(optional)]
204 pub version: Option<String>,
205 #[serde(default, skip_serializing_if = "Option::is_none")]
206 #[ts(optional)]
207 pub id: Option<String>,
208 pub name: String,
209 #[serde(default, skip_serializing_if = "Option::is_none")]
210 #[ts(optional)]
211 pub description: Option<String>,
212 #[serde(default, skip_serializing_if = "Option::is_none")]
213 #[ts(optional)]
214 pub color: Option<String>,
215 #[serde(default, skip_serializing_if = "Option::is_none")]
216 #[ts(optional)]
217 pub format: Option<DeckFormat>,
218 #[serde(default)]
219 pub cards: Vec<DeckCard>,
220 #[serde(default)]
221 pub sideboard: Vec<DeckCard>,
222 #[serde(default, skip_serializing_if = "Option::is_none")]
223 #[ts(optional)]
224 pub attractions: Option<Vec<DeckCard>>,
225 #[serde(default, skip_serializing_if = "Option::is_none")]
226 #[ts(optional)]
227 pub contraptions: Option<Vec<DeckCard>>,
228 #[serde(default, skip_serializing_if = "Option::is_none")]
229 #[ts(optional)]
230 pub schemes: Option<Vec<DeckCard>>,
231 #[serde(default, skip_serializing_if = "Option::is_none")]
232 #[ts(optional)]
233 pub planes: Option<Vec<DeckCard>>,
234 #[serde(default, skip_serializing_if = "Option::is_none")]
235 #[ts(optional)]
236 pub commanders: Option<Vec<DeckCard>>,
237 #[serde(default, skip_serializing_if = "Option::is_none")]
238 #[ts(optional)]
239 pub companion: Option<DeckCard>,
240 #[serde(default, skip_serializing_if = "Option::is_none")]
241 #[ts(optional)]
242 pub maybeboard: Option<Vec<DeckCard>>,
243 #[serde(default, skip_serializing_if = "Option::is_none")]
244 #[ts(optional)]
245 pub draft: Option<bool>,
246 #[serde(default, skip_serializing_if = "Option::is_none")]
247 #[ts(optional)]
248 pub labels: Option<Vec<DeckLabel>>,
249 #[serde(default, skip_serializing_if = "Option::is_none")]
250 #[ts(optional)]
251 pub cover_card_name: Option<String>,
252 #[serde(default, skip_serializing_if = "Option::is_none")]
253 #[ts(optional)]
254 pub cover_card_face: Option<u8>,
255 #[serde(default, skip_serializing_if = "Option::is_none")]
256 #[ts(optional)]
257 pub playmat: Option<String>,
258 #[serde(default, skip_serializing_if = "Option::is_none")]
259 #[ts(optional)]
260 pub playmat_settings: Option<PlaymatSettings>,
261 #[serde(default, skip_serializing_if = "Option::is_none")]
262 #[ts(optional, type = "Record<string, { x: number; y: number }>")]
263 pub stack_positions: Option<serde_json::Value>,
264 #[serde(default, skip_serializing_if = "Option::is_none")]
265 #[ts(optional)]
266 pub tokens: Option<Vec<DeckCard>>,
267}