1use anyhow::{Context, Result};
2
3use super::ability::{
4 ability_sort_key, compile_effects_from_def, compile_effects_from_template, AbilitySpec,
5 AbilityTemplate,
6};
7use super::card::CardStatic;
8use super::types::{CardColor, CardId, CardType};
9
10pub const MAX_CARD_ID_INDEX: CardId = 1_000_000;
17
18#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
20#[serde(try_from = "CardDbRaw", into = "CardDbRaw")]
21pub struct CardDb {
22 pub cards: Vec<CardStatic>,
24 #[serde(skip)]
25 index: Vec<usize>,
26 #[serde(skip)]
27 valid_by_id: Vec<bool>,
28 #[serde(skip)]
29 power_by_id: Vec<i32>,
30 #[serde(skip)]
31 soul_by_id: Vec<u8>,
32 #[serde(skip)]
33 level_by_id: Vec<u8>,
34 #[serde(skip)]
35 cost_by_id: Vec<u8>,
36 #[serde(skip)]
37 color_by_id: Vec<CardColor>,
38 #[serde(skip)]
39 card_type_by_id: Vec<CardType>,
40 #[serde(skip)]
41 ability_specs: Vec<Vec<AbilitySpec>>,
42 #[serde(skip)]
43 compiled_ability_effects: Vec<Vec<Vec<crate::effects::EffectSpec>>>,
44}
45
46#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
47struct CardDbRaw {
48 cards: Vec<CardStatic>,
49}
50
51impl TryFrom<CardDbRaw> for CardDb {
52 type Error = anyhow::Error;
53
54 fn try_from(raw: CardDbRaw) -> Result<Self> {
55 CardDb::new(raw.cards)
56 }
57}
58
59impl From<CardDb> for CardDbRaw {
60 fn from(db: CardDb) -> Self {
61 Self { cards: db.cards }
62 }
63}
64
65impl CardDb {
66 pub fn new(cards: Vec<CardStatic>) -> Result<Self> {
70 let mut db = Self {
71 cards,
72 index: Vec::new(),
73 valid_by_id: Vec::new(),
74 power_by_id: Vec::new(),
75 soul_by_id: Vec::new(),
76 level_by_id: Vec::new(),
77 cost_by_id: Vec::new(),
78 color_by_id: Vec::new(),
79 card_type_by_id: Vec::new(),
80 ability_specs: Vec::new(),
81 compiled_ability_effects: Vec::new(),
82 };
83 db.build_index()?;
84 Ok(db)
85 }
86
87 pub fn get(&self, id: CardId) -> Option<&CardStatic> {
89 if id == 0 {
90 return None;
91 }
92 let idx = *self.index.get(id as usize)?;
93 if idx == usize::MAX {
94 return None;
95 }
96 self.cards.get(idx)
97 }
98
99 #[inline(always)]
100 pub fn max_card_id(&self) -> CardId {
102 self.index.len().saturating_sub(1).try_into().unwrap_or(0)
103 }
104
105 #[inline(always)]
106 pub fn is_valid_id(&self, id: CardId) -> bool {
108 self.valid_by_id.get(id as usize).copied().unwrap_or(false)
109 }
110
111 #[inline(always)]
112 pub fn power_by_id(&self, id: CardId) -> i32 {
114 self.power_by_id.get(id as usize).copied().unwrap_or(0)
115 }
116
117 #[inline(always)]
118 pub fn soul_by_id(&self, id: CardId) -> u8 {
120 self.soul_by_id.get(id as usize).copied().unwrap_or(0)
121 }
122
123 #[inline(always)]
124 pub fn level_by_id(&self, id: CardId) -> u8 {
126 self.level_by_id.get(id as usize).copied().unwrap_or(0)
127 }
128
129 #[inline(always)]
130 pub fn cost_by_id(&self, id: CardId) -> u8 {
132 self.cost_by_id.get(id as usize).copied().unwrap_or(0)
133 }
134
135 #[inline(always)]
136 pub fn color_by_id(&self, id: CardId) -> CardColor {
138 self.color_by_id
139 .get(id as usize)
140 .copied()
141 .unwrap_or(CardColor::Red)
142 }
143
144 #[inline(always)]
145 pub fn card_type_by_id(&self, id: CardId) -> CardType {
147 self.card_type_by_id
148 .get(id as usize)
149 .copied()
150 .unwrap_or(CardType::Character)
151 }
152
153 pub(super) fn build_index(&mut self) -> Result<()> {
154 let mut max_id: usize = 0;
155 for card in &mut self.cards {
156 if card.id == 0 {
157 anyhow::bail!("CardId 0 is reserved for empty and cannot appear in the db");
158 }
159 if card.id > MAX_CARD_ID_INDEX {
160 anyhow::bail!(
161 "CardId {} exceeds maximum supported id {}",
162 card.id,
163 MAX_CARD_ID_INDEX
164 );
165 }
166 if card.counter_timing
167 && !matches!(card.card_type, CardType::Event | CardType::Character)
168 {
169 eprintln!(
170 "CardId {} has counter timing but card_type {:?} is not eligible; disabling counter timing",
171 card.id, card.card_type
172 );
173 card.counter_timing = false;
174 }
175 for def in &card.ability_defs {
176 def.validate()
177 .with_context(|| format!("CardId {} AbilityDef invalid", card.id))?;
178 }
179 max_id = max_id.max(card.id as usize);
180 }
181 let mut index = vec![usize::MAX; max_id + 1];
182 let mut valid_by_id = vec![false; max_id + 1];
183 let mut power_by_id = vec![0i32; max_id + 1];
184 let mut soul_by_id = vec![0u8; max_id + 1];
185 let mut level_by_id = vec![0u8; max_id + 1];
186 let mut cost_by_id = vec![0u8; max_id + 1];
187 let mut color_by_id = vec![CardColor::Red; max_id + 1];
188 let mut card_type_by_id = vec![CardType::Character; max_id + 1];
189 for (i, card) in self.cards.iter().enumerate() {
190 let id = card.id as usize;
191 if index[id] != usize::MAX {
192 anyhow::bail!("Duplicate CardId {id}");
193 }
194 index[id] = i;
195 valid_by_id[id] = true;
196 power_by_id[id] = card.power;
197 soul_by_id[id] = card.soul;
198 level_by_id[id] = card.level;
199 cost_by_id[id] = card.cost;
200 color_by_id[id] = card.color;
201 card_type_by_id[id] = card.card_type;
202 }
203 self.index = index;
204 self.valid_by_id = valid_by_id;
205 self.power_by_id = power_by_id;
206 self.soul_by_id = soul_by_id;
207 self.level_by_id = level_by_id;
208 self.cost_by_id = cost_by_id;
209 self.color_by_id = color_by_id;
210 self.card_type_by_id = card_type_by_id;
211 self.build_ability_specs()?;
212 self.build_compiled_abilities()?;
213 Ok(())
214 }
215
216 fn build_ability_specs(&mut self) -> Result<()> {
217 let mut specs_list: Vec<Vec<AbilitySpec>> = Vec::with_capacity(self.cards.len());
218 for card in &self.cards {
219 for template in &card.abilities {
220 if matches!(
221 template,
222 AbilityTemplate::ActivatedPlaceholder | AbilityTemplate::Unsupported { .. }
223 ) {
224 anyhow::bail!(
225 "CardId {} uses unsupported ability template; update card db",
226 card.id
227 );
228 }
229 }
230 let mut specs: Vec<AbilitySpec> = card
231 .abilities
232 .iter()
233 .map(AbilitySpec::from_template)
234 .collect();
235 for def in &card.ability_defs {
236 specs.push(AbilitySpec::from_template(&AbilityTemplate::AbilityDef(
237 def.clone(),
238 )));
239 }
240 specs.sort_by_cached_key(ability_sort_key);
241 specs_list.push(specs);
242 }
243 self.ability_specs = specs_list;
244 Ok(())
245 }
246
247 fn build_compiled_abilities(&mut self) -> Result<()> {
248 let mut compiled: Vec<Vec<Vec<crate::effects::EffectSpec>>> =
249 Vec::with_capacity(self.cards.len());
250 for card in &self.cards {
251 let specs = self.iter_card_abilities_in_canonical_order(card.id);
252 let mut per_ability: Vec<Vec<crate::effects::EffectSpec>> =
253 Vec::with_capacity(specs.len());
254 for (ability_index, spec) in specs.iter().enumerate() {
255 let idx = u8::try_from(ability_index).map_err(|_| {
256 anyhow::anyhow!(
257 "Ability index out of range for card {}: {}",
258 card.id,
259 ability_index
260 )
261 })?;
262 let effects = match &spec.template {
263 AbilityTemplate::AbilityDef(def) => compile_effects_from_def(card.id, idx, def),
264 AbilityTemplate::Vanilla | AbilityTemplate::Unsupported { .. } => Vec::new(),
265 _ => compile_effects_from_template(card.id, idx, &spec.template),
266 };
267 per_ability.push(effects);
268 }
269 compiled.push(per_ability);
270 }
271 self.compiled_ability_effects = compiled;
272 Ok(())
273 }
274
275 pub fn iter_card_abilities_in_canonical_order(&self, card_id: CardId) -> &[AbilitySpec] {
277 let idx = match self.index.get(card_id as usize) {
278 Some(idx) => *idx,
279 None => return &[],
280 };
281 if idx == usize::MAX {
282 return &[];
283 }
284 self.ability_specs
285 .get(idx)
286 .map(|v| v.as_slice())
287 .unwrap_or(&[])
288 }
289
290 pub fn compiled_effects_for_ability(
292 &self,
293 card_id: CardId,
294 ability_index: usize,
295 ) -> &[crate::effects::EffectSpec] {
296 let idx = match self.index.get(card_id as usize) {
297 Some(idx) => *idx,
298 None => return &[],
299 };
300 if idx == usize::MAX {
301 return &[];
302 }
303 self.compiled_ability_effects
304 .get(idx)
305 .and_then(|per_ability| per_ability.get(ability_index))
306 .map(|v| v.as_slice())
307 .unwrap_or(&[])
308 }
309
310 pub fn compiled_effects_flat(&self, card_id: CardId) -> Vec<crate::effects::EffectSpec> {
312 let idx = match self.index.get(card_id as usize) {
313 Some(idx) => *idx,
314 None => return Vec::new(),
315 };
316 if idx == usize::MAX {
317 return Vec::new();
318 }
319 let Some(per_ability) = self.compiled_ability_effects.get(idx) else {
320 return Vec::new();
321 };
322 let mut out = Vec::new();
323 for effects in per_ability {
324 out.extend(effects.iter().cloned());
325 }
326 out
327 }
328}