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