weiss_core/
db.rs

1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3use std::fs;
4use std::path::Path;
5
6use crate::events::RevealAudience;
7use crate::state::{TargetSide, TargetZone};
8
9const WSDB_MAGIC: &[u8; 4] = b"WSDB";
10pub const WSDB_SCHEMA_VERSION: u32 = 1;
11
12pub type CardId = u32;
13
14#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
15pub enum CardType {
16    Character,
17    Event,
18    Climax,
19}
20
21#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
22pub enum CardColor {
23    Yellow,
24    Green,
25    Red,
26    Blue,
27    Colorless,
28}
29
30#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
31pub enum TriggerIcon {
32    Soul,
33    Shot,
34    Bounce,
35    Draw,
36    Treasure,
37    Gate,
38    Standby,
39}
40
41#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
42pub enum TargetTemplate {
43    OppFrontRow,
44    OppBackRow,
45    OppStage,
46    OppStageSlot { slot: u8 },
47    SelfFrontRow,
48    SelfBackRow,
49    SelfStage,
50    SelfStageSlot { slot: u8 },
51    This,
52    SelfWaitingRoom,
53    SelfHand,
54    SelfDeckTop,
55    SelfClock,
56    SelfLevel,
57    SelfStock,
58    SelfMemory,
59}
60
61#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
62pub enum EffectTemplate {
63    Draw {
64        count: u8,
65    },
66    DealDamage {
67        amount: u8,
68        cancelable: bool,
69    },
70    AddPower {
71        amount: i32,
72        duration_turn: bool,
73    },
74    MoveToHand,
75    MoveToWaitingRoom,
76    MoveToStock,
77    MoveToClock,
78    Heal,
79    RestTarget,
80    StandTarget,
81    StockCharge {
82        count: u8,
83    },
84    MillTop {
85        target: TargetSide,
86        count: u8,
87    },
88    MoveStageSlot {
89        slot: u8,
90    },
91    SwapStageSlots,
92    RandomDiscardFromHand {
93        target: TargetSide,
94        count: u8,
95    },
96    RandomMill {
97        target: TargetSide,
98        count: u8,
99    },
100    RevealZoneTop {
101        target: TargetSide,
102        zone: TargetZone,
103        count: u8,
104        audience: RevealAudience,
105    },
106    ChangeController,
107    CounterBackup {
108        power: i32,
109    },
110    CounterDamageReduce {
111        amount: u8,
112    },
113    CounterDamageCancel,
114}
115
116#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
117pub struct AbilityCost {
118    #[serde(default)]
119    pub stock: u8,
120    #[serde(default)]
121    pub rest_self: bool,
122    #[serde(default)]
123    pub rest_other: u8,
124    #[serde(default)]
125    pub discard_from_hand: u8,
126    #[serde(default)]
127    pub clock_from_hand: u8,
128    #[serde(default)]
129    pub clock_from_deck_top: u8,
130    #[serde(default)]
131    pub reveal_from_hand: u8,
132}
133
134impl AbilityCost {
135    pub fn is_empty(&self) -> bool {
136        self.stock == 0
137            && !self.rest_self
138            && self.rest_other == 0
139            && self.discard_from_hand == 0
140            && self.clock_from_hand == 0
141            && self.clock_from_deck_top == 0
142            && self.reveal_from_hand == 0
143    }
144}
145
146#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
147pub struct AbilityDef {
148    pub kind: AbilityKind,
149    pub timing: Option<AbilityTiming>,
150    pub effects: Vec<EffectTemplate>,
151    pub targets: Vec<TargetTemplate>,
152    #[serde(default)]
153    pub cost: AbilityCost,
154    #[serde(default)]
155    pub target_card_type: Option<CardType>,
156    #[serde(default)]
157    pub target_trait: Option<u16>,
158    #[serde(default)]
159    pub target_level_max: Option<u8>,
160    #[serde(default)]
161    pub target_cost_max: Option<u8>,
162    #[serde(default)]
163    pub target_limit: Option<u8>,
164}
165
166impl AbilityDef {
167    pub fn validate(&self) -> Result<()> {
168        if self.effects.is_empty() {
169            anyhow::bail!("AbilityDef must contain at least one effect");
170        }
171        if self.effects.len() > u8::MAX as usize {
172            anyhow::bail!("AbilityDef has too many effects");
173        }
174        if self.kind != AbilityKind::Activated && !self.cost.is_empty() {
175            anyhow::bail!("AbilityDef cost is only valid for activated abilities");
176        }
177        Ok(())
178    }
179}
180
181#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
182pub enum AbilityTiming {
183    BeginTurn,
184    BeginStandPhase,
185    AfterStandPhase,
186    BeginDrawPhase,
187    AfterDrawPhase,
188    BeginClockPhase,
189    AfterClockPhase,
190    BeginMainPhase,
191    BeginClimaxPhase,
192    AfterClimaxPhase,
193    BeginAttackPhase,
194    BeginAttackDeclarationStep,
195    BeginEncoreStep,
196    EndPhase,
197    EndPhaseCleanup,
198    EndOfAttack,
199    AttackDeclaration,
200    TriggerResolution,
201    Counter,
202    DamageResolution,
203    Encore,
204    OnPlay,
205    OnReverse,
206}
207
208#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
209pub enum AbilityTemplate {
210    Vanilla,
211    ContinuousPower {
212        amount: i32,
213    },
214    ContinuousCannotAttack,
215    ContinuousAttackCost {
216        cost: u8,
217    },
218    AutoOnPlayDraw {
219        count: u8,
220    },
221    AutoOnPlaySalvage {
222        count: u8,
223        optional: bool,
224        card_type: Option<CardType>,
225    },
226    AutoOnPlaySearchDeckTop {
227        count: u8,
228        optional: bool,
229        card_type: Option<CardType>,
230    },
231    AutoOnPlayRevealDeckTop {
232        count: u8,
233    },
234    AutoOnPlayStockCharge {
235        count: u8,
236    },
237    AutoOnPlayMillTop {
238        count: u8,
239    },
240    AutoOnPlayHeal {
241        count: u8,
242    },
243    AutoOnAttackDealDamage {
244        amount: u8,
245        cancelable: bool,
246    },
247    AutoEndPhaseDraw {
248        count: u8,
249    },
250    AutoOnReverseDraw {
251        count: u8,
252    },
253    AutoOnReverseSalvage {
254        count: u8,
255        optional: bool,
256        card_type: Option<CardType>,
257    },
258    EventDealDamage {
259        amount: u8,
260        cancelable: bool,
261    },
262    ActivatedPlaceholder,
263    ActivatedTargetedPower {
264        amount: i32,
265        count: u8,
266        target: TargetTemplate,
267    },
268    ActivatedPaidTargetedPower {
269        cost: u8,
270        amount: i32,
271        count: u8,
272        target: TargetTemplate,
273    },
274    ActivatedTargetedMoveToHand {
275        count: u8,
276        target: TargetTemplate,
277    },
278    ActivatedPaidTargetedMoveToHand {
279        cost: u8,
280        count: u8,
281        target: TargetTemplate,
282    },
283    ActivatedChangeController {
284        count: u8,
285        target: TargetTemplate,
286    },
287    ActivatedPaidChangeController {
288        cost: u8,
289        count: u8,
290        target: TargetTemplate,
291    },
292    CounterBackup {
293        power: i32,
294    },
295    CounterDamageReduce {
296        amount: u8,
297    },
298    CounterDamageCancel,
299    AbilityDef(AbilityDef),
300    Unsupported {
301        id: u32,
302    },
303}
304
305#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
306pub enum AbilityKind {
307    Continuous,
308    Activated,
309    Auto,
310}
311
312#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
313pub struct AbilitySpec {
314    pub kind: AbilityKind,
315    pub template: AbilityTemplate,
316}
317
318#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
319pub enum AbilityTemplateTag {
320    Vanilla,
321    ContinuousPower,
322    ContinuousCannotAttack,
323    ContinuousAttackCost,
324    AutoOnPlayDraw,
325    AutoOnPlaySalvage,
326    AutoOnPlaySearchDeckTop,
327    AutoOnPlayRevealDeckTop,
328    AutoOnPlayStockCharge,
329    AutoOnPlayMillTop,
330    AutoOnPlayHeal,
331    AutoOnAttackDealDamage,
332    AutoEndPhaseDraw,
333    AutoOnReverseDraw,
334    AutoOnReverseSalvage,
335    EventDealDamage,
336    ActivatedPlaceholder,
337    ActivatedTargetedPower,
338    ActivatedPaidTargetedPower,
339    ActivatedTargetedMoveToHand,
340    ActivatedPaidTargetedMoveToHand,
341    ActivatedChangeController,
342    ActivatedPaidChangeController,
343    CounterBackup,
344    CounterDamageReduce,
345    CounterDamageCancel,
346    AbilityDef,
347    Unsupported,
348}
349
350impl AbilityTemplate {
351    pub fn tag(&self) -> AbilityTemplateTag {
352        match self {
353            AbilityTemplate::Vanilla => AbilityTemplateTag::Vanilla,
354            AbilityTemplate::ContinuousPower { .. } => AbilityTemplateTag::ContinuousPower,
355            AbilityTemplate::ContinuousCannotAttack => AbilityTemplateTag::ContinuousCannotAttack,
356            AbilityTemplate::ContinuousAttackCost { .. } => {
357                AbilityTemplateTag::ContinuousAttackCost
358            }
359            AbilityTemplate::AutoOnPlayDraw { .. } => AbilityTemplateTag::AutoOnPlayDraw,
360            AbilityTemplate::AutoOnPlaySalvage { .. } => AbilityTemplateTag::AutoOnPlaySalvage,
361            AbilityTemplate::AutoOnPlaySearchDeckTop { .. } => {
362                AbilityTemplateTag::AutoOnPlaySearchDeckTop
363            }
364            AbilityTemplate::AutoOnPlayRevealDeckTop { .. } => {
365                AbilityTemplateTag::AutoOnPlayRevealDeckTop
366            }
367            AbilityTemplate::AutoOnPlayStockCharge { .. } => {
368                AbilityTemplateTag::AutoOnPlayStockCharge
369            }
370            AbilityTemplate::AutoOnPlayMillTop { .. } => AbilityTemplateTag::AutoOnPlayMillTop,
371            AbilityTemplate::AutoOnPlayHeal { .. } => AbilityTemplateTag::AutoOnPlayHeal,
372            AbilityTemplate::AutoOnAttackDealDamage { .. } => {
373                AbilityTemplateTag::AutoOnAttackDealDamage
374            }
375            AbilityTemplate::AutoEndPhaseDraw { .. } => AbilityTemplateTag::AutoEndPhaseDraw,
376            AbilityTemplate::AutoOnReverseDraw { .. } => AbilityTemplateTag::AutoOnReverseDraw,
377            AbilityTemplate::AutoOnReverseSalvage { .. } => {
378                AbilityTemplateTag::AutoOnReverseSalvage
379            }
380            AbilityTemplate::EventDealDamage { .. } => AbilityTemplateTag::EventDealDamage,
381            AbilityTemplate::ActivatedPlaceholder => AbilityTemplateTag::ActivatedPlaceholder,
382            AbilityTemplate::ActivatedTargetedPower { .. } => {
383                AbilityTemplateTag::ActivatedTargetedPower
384            }
385            AbilityTemplate::ActivatedPaidTargetedPower { .. } => {
386                AbilityTemplateTag::ActivatedPaidTargetedPower
387            }
388            AbilityTemplate::ActivatedTargetedMoveToHand { .. } => {
389                AbilityTemplateTag::ActivatedTargetedMoveToHand
390            }
391            AbilityTemplate::ActivatedPaidTargetedMoveToHand { .. } => {
392                AbilityTemplateTag::ActivatedPaidTargetedMoveToHand
393            }
394            AbilityTemplate::ActivatedChangeController { .. } => {
395                AbilityTemplateTag::ActivatedChangeController
396            }
397            AbilityTemplate::ActivatedPaidChangeController { .. } => {
398                AbilityTemplateTag::ActivatedPaidChangeController
399            }
400            AbilityTemplate::CounterBackup { .. } => AbilityTemplateTag::CounterBackup,
401            AbilityTemplate::CounterDamageReduce { .. } => AbilityTemplateTag::CounterDamageReduce,
402            AbilityTemplate::CounterDamageCancel => AbilityTemplateTag::CounterDamageCancel,
403            AbilityTemplate::AbilityDef(_) => AbilityTemplateTag::AbilityDef,
404            AbilityTemplate::Unsupported { .. } => AbilityTemplateTag::Unsupported,
405        }
406    }
407
408    pub fn activation_cost(&self) -> Option<u8> {
409        match self {
410            AbilityTemplate::ActivatedPaidTargetedPower { cost, .. }
411            | AbilityTemplate::ActivatedPaidTargetedMoveToHand { cost, .. }
412            | AbilityTemplate::ActivatedPaidChangeController { cost, .. } => Some(*cost),
413            _ => None,
414        }
415    }
416
417    pub fn activation_cost_spec(&self) -> AbilityCost {
418        match self {
419            AbilityTemplate::ActivatedPaidTargetedPower { cost, .. }
420            | AbilityTemplate::ActivatedPaidTargetedMoveToHand { cost, .. }
421            | AbilityTemplate::ActivatedPaidChangeController { cost, .. } => AbilityCost {
422                stock: *cost,
423                ..AbilityCost::default()
424            },
425            AbilityTemplate::AbilityDef(def) => def.cost,
426            _ => AbilityCost::default(),
427        }
428    }
429}
430
431fn ability_kind_key(kind: AbilityKind) -> u64 {
432    match kind {
433        AbilityKind::Continuous => 0,
434        AbilityKind::Activated => 1,
435        AbilityKind::Auto => 2,
436    }
437}
438
439fn ability_timing_key(timing: Option<AbilityTiming>) -> u64 {
440    match timing {
441        None => u64::MAX,
442        Some(AbilityTiming::BeginTurn) => 0,
443        Some(AbilityTiming::BeginStandPhase) => 1,
444        Some(AbilityTiming::AfterStandPhase) => 2,
445        Some(AbilityTiming::BeginDrawPhase) => 3,
446        Some(AbilityTiming::AfterDrawPhase) => 4,
447        Some(AbilityTiming::BeginClockPhase) => 5,
448        Some(AbilityTiming::AfterClockPhase) => 6,
449        Some(AbilityTiming::BeginMainPhase) => 7,
450        Some(AbilityTiming::BeginClimaxPhase) => 8,
451        Some(AbilityTiming::AfterClimaxPhase) => 9,
452        Some(AbilityTiming::BeginAttackPhase) => 10,
453        Some(AbilityTiming::BeginAttackDeclarationStep) => 11,
454        Some(AbilityTiming::BeginEncoreStep) => 12,
455        Some(AbilityTiming::EndPhase) => 13,
456        Some(AbilityTiming::EndPhaseCleanup) => 14,
457        Some(AbilityTiming::EndOfAttack) => 15,
458        Some(AbilityTiming::AttackDeclaration) => 16,
459        Some(AbilityTiming::TriggerResolution) => 17,
460        Some(AbilityTiming::Counter) => 18,
461        Some(AbilityTiming::DamageResolution) => 19,
462        Some(AbilityTiming::Encore) => 20,
463        Some(AbilityTiming::OnPlay) => 21,
464        Some(AbilityTiming::OnReverse) => 22,
465    }
466}
467
468fn target_template_key(target: TargetTemplate) -> u64 {
469    match target {
470        TargetTemplate::OppFrontRow => 0,
471        TargetTemplate::OppBackRow => 1,
472        TargetTemplate::OppStage => 2,
473        TargetTemplate::OppStageSlot { slot } => 3_000 + slot as u64,
474        TargetTemplate::SelfFrontRow => 4,
475        TargetTemplate::SelfBackRow => 5,
476        TargetTemplate::SelfStage => 6,
477        TargetTemplate::SelfStageSlot { slot } => 7_000 + slot as u64,
478        TargetTemplate::This => 15,
479        TargetTemplate::SelfWaitingRoom => 8,
480        TargetTemplate::SelfHand => 9,
481        TargetTemplate::SelfDeckTop => 10,
482        TargetTemplate::SelfClock => 11,
483        TargetTemplate::SelfLevel => 12,
484        TargetTemplate::SelfStock => 13,
485        TargetTemplate::SelfMemory => 14,
486    }
487}
488
489fn card_type_key(card_type: Option<CardType>) -> u64 {
490    match card_type {
491        None => 0,
492        Some(CardType::Character) => 1,
493        Some(CardType::Event) => 2,
494        Some(CardType::Climax) => 3,
495    }
496}
497
498fn target_side_key(side: TargetSide) -> u64 {
499    match side {
500        TargetSide::SelfSide => 0,
501        TargetSide::Opponent => 1,
502    }
503}
504
505fn target_zone_key(zone: TargetZone) -> u64 {
506    match zone {
507        TargetZone::Stage => 0,
508        TargetZone::Hand => 1,
509        TargetZone::DeckTop => 2,
510        TargetZone::Clock => 3,
511        TargetZone::Level => 4,
512        TargetZone::Stock => 5,
513        TargetZone::Memory => 6,
514        TargetZone::WaitingRoom => 7,
515        TargetZone::Climax => 8,
516        TargetZone::Resolution => 9,
517    }
518}
519
520fn reveal_audience_key(audience: RevealAudience) -> u64 {
521    match audience {
522        RevealAudience::Public => 0,
523        RevealAudience::BothPlayers => 1,
524        RevealAudience::OwnerOnly => 2,
525        RevealAudience::ControllerOnly => 3,
526        RevealAudience::ReplayOnly => 4,
527    }
528}
529
530fn effect_template_key(effect: &EffectTemplate, out: &mut Vec<u64>) {
531    match effect {
532        EffectTemplate::Draw { count } => {
533            out.push(0);
534            out.push(*count as u64);
535        }
536        EffectTemplate::DealDamage { amount, cancelable } => {
537            out.push(1);
538            out.push(*amount as u64);
539            out.push(u64::from(*cancelable));
540        }
541        EffectTemplate::AddPower {
542            amount,
543            duration_turn,
544        } => {
545            out.push(2);
546            out.push(*amount as i64 as u64);
547            out.push(u64::from(*duration_turn));
548        }
549        EffectTemplate::MoveToHand => {
550            out.push(3);
551        }
552        EffectTemplate::MoveToWaitingRoom => {
553            out.push(8);
554        }
555        EffectTemplate::MoveToStock => {
556            out.push(9);
557        }
558        EffectTemplate::MoveToClock => {
559            out.push(10);
560        }
561        EffectTemplate::Heal => {
562            out.push(17);
563        }
564        EffectTemplate::RestTarget => {
565            out.push(11);
566        }
567        EffectTemplate::StandTarget => {
568            out.push(12);
569        }
570        EffectTemplate::StockCharge { count } => {
571            out.push(13);
572            out.push(*count as u64);
573        }
574        EffectTemplate::MillTop { target, count } => {
575            out.push(18);
576            out.push(target_side_key(*target));
577            out.push(*count as u64);
578        }
579        EffectTemplate::MoveStageSlot { slot } => {
580            out.push(19);
581            out.push(*slot as u64);
582        }
583        EffectTemplate::SwapStageSlots => {
584            out.push(20);
585        }
586        EffectTemplate::RandomDiscardFromHand { target, count } => {
587            out.push(14);
588            out.push(target_side_key(*target));
589            out.push(*count as u64);
590        }
591        EffectTemplate::RandomMill { target, count } => {
592            out.push(15);
593            out.push(target_side_key(*target));
594            out.push(*count as u64);
595        }
596        EffectTemplate::RevealZoneTop {
597            target,
598            zone,
599            count,
600            audience,
601        } => {
602            out.push(16);
603            out.push(target_side_key(*target));
604            out.push(target_zone_key(*zone));
605            out.push(*count as u64);
606            out.push(reveal_audience_key(*audience));
607        }
608        EffectTemplate::ChangeController => {
609            out.push(4);
610        }
611        EffectTemplate::CounterBackup { power } => {
612            out.push(5);
613            out.push(*power as i64 as u64);
614        }
615        EffectTemplate::CounterDamageReduce { amount } => {
616            out.push(6);
617            out.push(*amount as u64);
618        }
619        EffectTemplate::CounterDamageCancel => {
620            out.push(7);
621        }
622    }
623}
624
625fn ability_def_key(def: &AbilityDef) -> Vec<u64> {
626    let mut key = Vec::with_capacity(16 + def.effects.len() * 5 + def.targets.len());
627    key.push(ability_kind_key(def.kind));
628    key.push(ability_timing_key(def.timing));
629    key.push(def.cost.stock as u64);
630    key.push(def.cost.rest_self as u64);
631    key.push(def.cost.rest_other as u64);
632    key.push(def.cost.discard_from_hand as u64);
633    key.push(def.cost.clock_from_hand as u64);
634    key.push(def.cost.clock_from_deck_top as u64);
635    key.push(def.cost.reveal_from_hand as u64);
636    key.push(def.effects.len() as u64);
637    for effect in &def.effects {
638        effect_template_key(effect, &mut key);
639    }
640    key.push(def.targets.len() as u64);
641    for target in &def.targets {
642        key.push(target_template_key(*target));
643    }
644    key.push(card_type_key(def.target_card_type));
645    key.push(def.target_trait.map(|v| v as u64 + 1).unwrap_or(0));
646    key.push(def.target_level_max.map(|v| v as u64 + 1).unwrap_or(0));
647    key.push(def.target_cost_max.map(|v| v as u64 + 1).unwrap_or(0));
648    key.push(def.target_limit.map(|v| v as u64 + 1).unwrap_or(0));
649    key
650}
651
652fn ability_template_key(template: &AbilityTemplate) -> Vec<u64> {
653    match template {
654        AbilityTemplate::Vanilla => Vec::new(),
655        AbilityTemplate::ContinuousPower { amount } => vec![*amount as i64 as u64],
656        AbilityTemplate::ContinuousCannotAttack => Vec::new(),
657        AbilityTemplate::ContinuousAttackCost { cost } => vec![*cost as u64],
658        AbilityTemplate::AutoOnPlayDraw { count } => vec![*count as u64],
659        AbilityTemplate::AutoOnPlaySalvage {
660            count,
661            optional,
662            card_type,
663        } => vec![
664            *count as u64,
665            u64::from(*optional),
666            card_type_key(*card_type),
667        ],
668        AbilityTemplate::AutoOnPlaySearchDeckTop {
669            count,
670            optional,
671            card_type,
672        } => vec![
673            *count as u64,
674            u64::from(*optional),
675            card_type_key(*card_type),
676        ],
677        AbilityTemplate::AutoOnPlayRevealDeckTop { count } => vec![*count as u64],
678        AbilityTemplate::AutoOnPlayStockCharge { count } => vec![*count as u64],
679        AbilityTemplate::AutoOnPlayMillTop { count } => vec![*count as u64],
680        AbilityTemplate::AutoOnPlayHeal { count } => vec![*count as u64],
681        AbilityTemplate::AutoOnAttackDealDamage { amount, cancelable } => {
682            vec![*amount as u64, u64::from(*cancelable)]
683        }
684        AbilityTemplate::AutoEndPhaseDraw { count } => vec![*count as u64],
685        AbilityTemplate::AutoOnReverseDraw { count } => vec![*count as u64],
686        AbilityTemplate::AutoOnReverseSalvage {
687            count,
688            optional,
689            card_type,
690        } => vec![
691            *count as u64,
692            u64::from(*optional),
693            card_type_key(*card_type),
694        ],
695        AbilityTemplate::EventDealDamage { amount, cancelable } => {
696            vec![*amount as u64, u64::from(*cancelable)]
697        }
698        AbilityTemplate::ActivatedPlaceholder => Vec::new(),
699        AbilityTemplate::ActivatedTargetedPower {
700            amount,
701            count,
702            target,
703        } => vec![
704            *amount as i64 as u64,
705            *count as u64,
706            target_template_key(*target),
707        ],
708        AbilityTemplate::ActivatedPaidTargetedPower {
709            cost,
710            amount,
711            count,
712            target,
713        } => vec![
714            *cost as u64,
715            *amount as i64 as u64,
716            *count as u64,
717            target_template_key(*target),
718        ],
719        AbilityTemplate::ActivatedTargetedMoveToHand { count, target } => {
720            vec![*count as u64, target_template_key(*target)]
721        }
722        AbilityTemplate::ActivatedPaidTargetedMoveToHand {
723            cost,
724            count,
725            target,
726        } => vec![*cost as u64, *count as u64, target_template_key(*target)],
727        AbilityTemplate::ActivatedChangeController { count, target } => {
728            vec![*count as u64, target_template_key(*target)]
729        }
730        AbilityTemplate::ActivatedPaidChangeController {
731            cost,
732            count,
733            target,
734        } => vec![*cost as u64, *count as u64, target_template_key(*target)],
735        AbilityTemplate::CounterBackup { power } => vec![*power as i64 as u64],
736        AbilityTemplate::CounterDamageReduce { amount } => vec![*amount as u64],
737        AbilityTemplate::CounterDamageCancel => Vec::new(),
738        AbilityTemplate::AbilityDef(def) => ability_def_key(def),
739        AbilityTemplate::Unsupported { id } => vec![*id as u64],
740    }
741}
742
743fn ability_sort_key(spec: &AbilitySpec) -> (u8, Vec<u64>) {
744    let tag = spec.template.tag() as u8;
745    (tag, ability_template_key(&spec.template))
746}
747
748impl AbilitySpec {
749    pub fn from_template(template: &AbilityTemplate) -> Self {
750        let kind = match template {
751            AbilityTemplate::ContinuousPower { .. }
752            | AbilityTemplate::ContinuousCannotAttack
753            | AbilityTemplate::ContinuousAttackCost { .. } => AbilityKind::Continuous,
754            AbilityTemplate::ActivatedPlaceholder
755            | AbilityTemplate::ActivatedTargetedPower { .. }
756            | AbilityTemplate::ActivatedPaidTargetedPower { .. }
757            | AbilityTemplate::ActivatedTargetedMoveToHand { .. }
758            | AbilityTemplate::ActivatedPaidTargetedMoveToHand { .. }
759            | AbilityTemplate::ActivatedChangeController { .. }
760            | AbilityTemplate::ActivatedPaidChangeController { .. } => AbilityKind::Activated,
761            AbilityTemplate::AbilityDef(def) => def.kind,
762            _ => AbilityKind::Auto,
763        };
764        Self {
765            kind,
766            template: template.clone(),
767        }
768    }
769
770    pub fn timing(&self) -> Option<AbilityTiming> {
771        match &self.template {
772            AbilityTemplate::AutoOnPlayDraw { .. } => Some(AbilityTiming::OnPlay),
773            AbilityTemplate::AutoOnPlaySalvage { .. } => Some(AbilityTiming::OnPlay),
774            AbilityTemplate::AutoOnPlaySearchDeckTop { .. } => Some(AbilityTiming::OnPlay),
775            AbilityTemplate::AutoOnPlayRevealDeckTop { .. } => Some(AbilityTiming::OnPlay),
776            AbilityTemplate::AutoOnPlayStockCharge { .. } => Some(AbilityTiming::OnPlay),
777            AbilityTemplate::AutoOnPlayMillTop { .. } => Some(AbilityTiming::OnPlay),
778            AbilityTemplate::AutoOnPlayHeal { .. } => Some(AbilityTiming::OnPlay),
779            AbilityTemplate::AutoOnAttackDealDamage { .. } => {
780                Some(AbilityTiming::AttackDeclaration)
781            }
782            AbilityTemplate::AutoEndPhaseDraw { .. } => Some(AbilityTiming::EndPhase),
783            AbilityTemplate::AutoOnReverseDraw { .. } => Some(AbilityTiming::OnReverse),
784            AbilityTemplate::AutoOnReverseSalvage { .. } => Some(AbilityTiming::OnReverse),
785            AbilityTemplate::CounterBackup { .. }
786            | AbilityTemplate::CounterDamageReduce { .. }
787            | AbilityTemplate::CounterDamageCancel => Some(AbilityTiming::Counter),
788            AbilityTemplate::EventDealDamage { .. } => Some(AbilityTiming::OnPlay),
789            AbilityTemplate::AbilityDef(def) => def.timing,
790            _ => None,
791        }
792    }
793
794    pub fn is_event_play(&self) -> bool {
795        matches!(self.template, AbilityTemplate::EventDealDamage { .. })
796    }
797}
798
799#[derive(Clone, Debug, Serialize, Deserialize)]
800pub struct CardStatic {
801    pub id: CardId,
802    #[serde(default)]
803    pub card_set: Option<String>,
804    pub card_type: CardType,
805    pub color: CardColor,
806    pub level: u8,
807    pub cost: u8,
808    pub power: i32,
809    pub soul: u8,
810    pub triggers: Vec<TriggerIcon>,
811    pub traits: Vec<u16>,
812    pub abilities: Vec<AbilityTemplate>,
813    #[serde(default)]
814    pub ability_defs: Vec<AbilityDef>,
815    #[serde(default)]
816    pub counter_timing: bool,
817    pub raw_text: Option<String>,
818}
819
820#[derive(Clone, Debug, Serialize, Deserialize)]
821pub struct CardDb {
822    pub cards: Vec<CardStatic>,
823    #[serde(skip)]
824    index: Vec<usize>,
825    #[serde(skip)]
826    ability_specs: Vec<Vec<AbilitySpec>>,
827    #[serde(skip)]
828    compiled_ability_effects: Vec<Vec<Vec<crate::effects::EffectSpec>>>,
829}
830
831impl CardDb {
832    pub fn new(cards: Vec<CardStatic>) -> Result<Self> {
833        let mut db = Self {
834            cards,
835            index: Vec::new(),
836            ability_specs: Vec::new(),
837            compiled_ability_effects: Vec::new(),
838        };
839        db.build_index()?;
840        Ok(db)
841    }
842
843    pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
844        let bytes = fs::read(&path)
845            .with_context(|| format!("Failed to read card db {:?}", path.as_ref()))?;
846        Self::from_wsdb_bytes(&bytes)
847    }
848
849    pub fn from_wsdb_bytes(bytes: &[u8]) -> Result<Self> {
850        if bytes.len() < 8 {
851            anyhow::bail!("Card db file too small");
852        }
853        if &bytes[0..4] != WSDB_MAGIC {
854            anyhow::bail!("Card db magic mismatch; expected WSDB header");
855        }
856        let version = u32::from_le_bytes(
857            bytes[4..8]
858                .try_into()
859                .map_err(|_| anyhow::anyhow!("Card db header missing version bytes"))?,
860        );
861        if version != WSDB_SCHEMA_VERSION {
862            anyhow::bail!(
863                "Unsupported card db schema version {version}, expected {WSDB_SCHEMA_VERSION}"
864            );
865        }
866        let payload = &bytes[8..];
867        Self::from_postcard_payload(payload)
868    }
869
870    pub fn from_postcard_payload(payload: &[u8]) -> Result<Self> {
871        let mut db: CardDb =
872            postcard::from_bytes(payload).context("Failed to decode card db payload")?;
873        db.build_index()?;
874        Ok(db)
875    }
876
877    pub fn get(&self, id: CardId) -> Option<&CardStatic> {
878        if id == 0 {
879            return None;
880        }
881        let idx = *self.index.get(id as usize)?;
882        if idx == usize::MAX {
883            return None;
884        }
885        self.cards.get(idx)
886    }
887
888    pub fn schema_version() -> u32 {
889        WSDB_SCHEMA_VERSION
890    }
891
892    pub fn to_bytes_with_header(&self) -> Result<Vec<u8>> {
893        let payload = postcard::to_stdvec(self)?;
894        let mut out = Vec::with_capacity(8 + payload.len());
895        out.extend_from_slice(WSDB_MAGIC);
896        out.extend_from_slice(&WSDB_SCHEMA_VERSION.to_le_bytes());
897        out.extend_from_slice(&payload);
898        Ok(out)
899    }
900
901    fn build_index(&mut self) -> Result<()> {
902        let mut max_id: usize = 0;
903        for card in &mut self.cards {
904            if card.id == 0 {
905                anyhow::bail!("CardId 0 is reserved for empty and cannot appear in the db");
906            }
907            if card.counter_timing
908                && !matches!(card.card_type, CardType::Event | CardType::Character)
909            {
910                eprintln!("CardId {} has counter timing but card_type {:?} is not eligible; disabling counter timing", card.id, card.card_type);
911                card.counter_timing = false;
912            }
913            for def in &card.ability_defs {
914                def.validate()
915                    .with_context(|| format!("CardId {} AbilityDef invalid", card.id))?;
916            }
917            max_id = max_id.max(card.id as usize);
918        }
919        let mut index = vec![usize::MAX; max_id + 1];
920        for (i, card) in self.cards.iter().enumerate() {
921            let id = card.id as usize;
922            if index[id] != usize::MAX {
923                anyhow::bail!("Duplicate CardId {id}");
924            }
925            index[id] = i;
926        }
927        self.index = index;
928        self.build_ability_specs()?;
929        self.build_compiled_abilities()?;
930        Ok(())
931    }
932
933    fn build_ability_specs(&mut self) -> Result<()> {
934        let mut specs_list: Vec<Vec<AbilitySpec>> = Vec::with_capacity(self.cards.len());
935        for card in &self.cards {
936            for template in &card.abilities {
937                if matches!(
938                    template,
939                    AbilityTemplate::ActivatedPlaceholder | AbilityTemplate::Unsupported { .. }
940                ) {
941                    anyhow::bail!(
942                        "CardId {} uses unsupported ability template; update card db",
943                        card.id
944                    );
945                }
946            }
947            let mut specs: Vec<AbilitySpec> = card
948                .abilities
949                .iter()
950                .map(AbilitySpec::from_template)
951                .collect();
952            for def in &card.ability_defs {
953                specs.push(AbilitySpec::from_template(&AbilityTemplate::AbilityDef(
954                    def.clone(),
955                )));
956            }
957            specs.sort_by_cached_key(ability_sort_key);
958            specs_list.push(specs);
959        }
960        self.ability_specs = specs_list;
961        Ok(())
962    }
963
964    fn build_compiled_abilities(&mut self) -> Result<()> {
965        let mut compiled: Vec<Vec<Vec<crate::effects::EffectSpec>>> =
966            Vec::with_capacity(self.cards.len());
967        for card in &self.cards {
968            let specs = self.iter_card_abilities_in_canonical_order(card.id);
969            let mut per_ability: Vec<Vec<crate::effects::EffectSpec>> =
970                Vec::with_capacity(specs.len());
971            for (ability_index, spec) in specs.iter().enumerate() {
972                let idx = ability_index as u8;
973                let effects = match &spec.template {
974                    AbilityTemplate::AbilityDef(def) => compile_effects_from_def(card.id, idx, def),
975                    AbilityTemplate::Vanilla | AbilityTemplate::Unsupported { .. } => Vec::new(),
976                    _ => compile_effects_from_template(card.id, idx, &spec.template),
977                };
978                per_ability.push(effects);
979            }
980            compiled.push(per_ability);
981        }
982        self.compiled_ability_effects = compiled;
983        Ok(())
984    }
985
986    pub fn iter_card_abilities_in_canonical_order(&self, card_id: CardId) -> &[AbilitySpec] {
987        let idx = match self.index.get(card_id as usize) {
988            Some(idx) => *idx,
989            None => return &[],
990        };
991        if idx == usize::MAX {
992            return &[];
993        }
994        self.ability_specs
995            .get(idx)
996            .map(|v| v.as_slice())
997            .unwrap_or(&[])
998    }
999
1000    pub fn compiled_effects_for_ability(
1001        &self,
1002        card_id: CardId,
1003        ability_index: usize,
1004    ) -> &[crate::effects::EffectSpec] {
1005        let idx = match self.index.get(card_id as usize) {
1006            Some(idx) => *idx,
1007            None => return &[],
1008        };
1009        if idx == usize::MAX {
1010            return &[];
1011        }
1012        self.compiled_ability_effects
1013            .get(idx)
1014            .and_then(|per_ability| per_ability.get(ability_index))
1015            .map(|v| v.as_slice())
1016            .unwrap_or(&[])
1017    }
1018
1019    pub fn compiled_effects_flat(&self, card_id: CardId) -> Vec<crate::effects::EffectSpec> {
1020        let idx = match self.index.get(card_id as usize) {
1021            Some(idx) => *idx,
1022            None => return Vec::new(),
1023        };
1024        if idx == usize::MAX {
1025            return Vec::new();
1026        }
1027        let Some(per_ability) = self.compiled_ability_effects.get(idx) else {
1028            return Vec::new();
1029        };
1030        let mut out = Vec::new();
1031        for effects in per_ability {
1032            out.extend(effects.iter().cloned());
1033        }
1034        out
1035    }
1036}
1037
1038fn target_spec_from_template(template: TargetTemplate, count: u8) -> crate::state::TargetSpec {
1039    let zone = match template {
1040        TargetTemplate::OppFrontRow
1041        | TargetTemplate::OppBackRow
1042        | TargetTemplate::OppStage
1043        | TargetTemplate::OppStageSlot { .. }
1044        | TargetTemplate::SelfFrontRow
1045        | TargetTemplate::SelfBackRow
1046        | TargetTemplate::SelfStage
1047        | TargetTemplate::SelfStageSlot { .. }
1048        | TargetTemplate::This => crate::state::TargetZone::Stage,
1049        TargetTemplate::SelfWaitingRoom => crate::state::TargetZone::WaitingRoom,
1050        TargetTemplate::SelfHand => crate::state::TargetZone::Hand,
1051        TargetTemplate::SelfDeckTop => crate::state::TargetZone::DeckTop,
1052        TargetTemplate::SelfClock => crate::state::TargetZone::Clock,
1053        TargetTemplate::SelfLevel => crate::state::TargetZone::Level,
1054        TargetTemplate::SelfStock => crate::state::TargetZone::Stock,
1055        TargetTemplate::SelfMemory => crate::state::TargetZone::Memory,
1056    };
1057    let card_type = match zone {
1058        crate::state::TargetZone::Stage => Some(CardType::Character),
1059        _ => None,
1060    };
1061    crate::state::TargetSpec {
1062        zone,
1063        side: match template {
1064            TargetTemplate::OppFrontRow
1065            | TargetTemplate::OppBackRow
1066            | TargetTemplate::OppStage
1067            | TargetTemplate::OppStageSlot { .. } => crate::state::TargetSide::Opponent,
1068            _ => crate::state::TargetSide::SelfSide,
1069        },
1070        slot_filter: match template {
1071            TargetTemplate::OppFrontRow | TargetTemplate::SelfFrontRow => {
1072                crate::state::TargetSlotFilter::FrontRow
1073            }
1074            TargetTemplate::OppBackRow | TargetTemplate::SelfBackRow => {
1075                crate::state::TargetSlotFilter::BackRow
1076            }
1077            TargetTemplate::OppStageSlot { slot } | TargetTemplate::SelfStageSlot { slot } => {
1078                crate::state::TargetSlotFilter::SpecificSlot(slot)
1079            }
1080            _ => crate::state::TargetSlotFilter::Any,
1081        },
1082        card_type,
1083        card_trait: None,
1084        level_max: None,
1085        cost_max: None,
1086        count,
1087        limit: None,
1088        source_only: matches!(template, TargetTemplate::This),
1089        reveal_to_controller: false,
1090    }
1091}
1092
1093fn compile_effects_from_template(
1094    card_id: CardId,
1095    ability_index: u8,
1096    template: &AbilityTemplate,
1097) -> Vec<crate::effects::EffectSpec> {
1098    let mut out = Vec::new();
1099    match template {
1100        AbilityTemplate::ContinuousPower { amount } => {
1101            out.push(crate::effects::EffectSpec {
1102                id: crate::effects::EffectId::new(
1103                    crate::effects::EffectSourceKind::Continuous,
1104                    card_id,
1105                    ability_index,
1106                    0,
1107                ),
1108                kind: crate::effects::EffectKind::AddModifier {
1109                    kind: crate::state::ModifierKind::Power,
1110                    magnitude: *amount,
1111                    duration: crate::state::ModifierDuration::WhileOnStage,
1112                },
1113                target: Some(target_spec_from_template(TargetTemplate::SelfStage, 1)),
1114                optional: false,
1115            });
1116        }
1117        AbilityTemplate::ContinuousCannotAttack => {
1118            out.push(crate::effects::EffectSpec {
1119                id: crate::effects::EffectId::new(
1120                    crate::effects::EffectSourceKind::Continuous,
1121                    card_id,
1122                    ability_index,
1123                    0,
1124                ),
1125                kind: crate::effects::EffectKind::AddModifier {
1126                    kind: crate::state::ModifierKind::CannotAttack,
1127                    magnitude: 1,
1128                    duration: crate::state::ModifierDuration::WhileOnStage,
1129                },
1130                target: Some(target_spec_from_template(TargetTemplate::SelfStage, 1)),
1131                optional: false,
1132            });
1133        }
1134        AbilityTemplate::ContinuousAttackCost { cost } => {
1135            out.push(crate::effects::EffectSpec {
1136                id: crate::effects::EffectId::new(
1137                    crate::effects::EffectSourceKind::Continuous,
1138                    card_id,
1139                    ability_index,
1140                    0,
1141                ),
1142                kind: crate::effects::EffectKind::AddModifier {
1143                    kind: crate::state::ModifierKind::AttackCost,
1144                    magnitude: *cost as i32,
1145                    duration: crate::state::ModifierDuration::WhileOnStage,
1146                },
1147                target: Some(target_spec_from_template(TargetTemplate::SelfStage, 1)),
1148                optional: false,
1149            });
1150        }
1151        AbilityTemplate::AutoOnPlayDraw { count } => {
1152            out.push(crate::effects::EffectSpec {
1153                id: crate::effects::EffectId::new(
1154                    crate::effects::EffectSourceKind::Auto,
1155                    card_id,
1156                    ability_index,
1157                    0,
1158                ),
1159                kind: crate::effects::EffectKind::Draw { count: *count },
1160                target: None,
1161                optional: false,
1162            });
1163        }
1164        AbilityTemplate::AutoOnPlaySalvage {
1165            count,
1166            optional,
1167            card_type,
1168        } => {
1169            let mut spec = target_spec_from_template(TargetTemplate::SelfWaitingRoom, *count);
1170            spec.card_type = *card_type;
1171            out.push(crate::effects::EffectSpec {
1172                id: crate::effects::EffectId::new(
1173                    crate::effects::EffectSourceKind::Auto,
1174                    card_id,
1175                    ability_index,
1176                    0,
1177                ),
1178                kind: crate::effects::EffectKind::MoveToHand,
1179                target: Some(spec),
1180                optional: *optional,
1181            });
1182        }
1183        AbilityTemplate::AutoOnPlaySearchDeckTop {
1184            count,
1185            optional,
1186            card_type,
1187        } => {
1188            let mut spec = target_spec_from_template(TargetTemplate::SelfDeckTop, 1);
1189            spec.card_type = *card_type;
1190            spec.limit = Some(*count);
1191            spec.reveal_to_controller = true;
1192            out.push(crate::effects::EffectSpec {
1193                id: crate::effects::EffectId::new(
1194                    crate::effects::EffectSourceKind::Auto,
1195                    card_id,
1196                    ability_index,
1197                    0,
1198                ),
1199                kind: crate::effects::EffectKind::MoveToHand,
1200                target: Some(spec),
1201                optional: *optional,
1202            });
1203        }
1204        AbilityTemplate::AutoOnPlayRevealDeckTop { count } => {
1205            out.push(crate::effects::EffectSpec {
1206                id: crate::effects::EffectId::new(
1207                    crate::effects::EffectSourceKind::Auto,
1208                    card_id,
1209                    ability_index,
1210                    0,
1211                ),
1212                kind: crate::effects::EffectKind::RevealDeckTop {
1213                    count: *count,
1214                    audience: crate::events::RevealAudience::ControllerOnly,
1215                },
1216                target: None,
1217                optional: false,
1218            });
1219        }
1220        AbilityTemplate::AutoOnPlayStockCharge { count } => {
1221            out.push(crate::effects::EffectSpec {
1222                id: crate::effects::EffectId::new(
1223                    crate::effects::EffectSourceKind::Auto,
1224                    card_id,
1225                    ability_index,
1226                    0,
1227                ),
1228                kind: crate::effects::EffectKind::StockCharge { count: *count },
1229                target: None,
1230                optional: false,
1231            });
1232        }
1233        AbilityTemplate::AutoOnPlayMillTop { count } => {
1234            out.push(crate::effects::EffectSpec {
1235                id: crate::effects::EffectId::new(
1236                    crate::effects::EffectSourceKind::Auto,
1237                    card_id,
1238                    ability_index,
1239                    0,
1240                ),
1241                kind: crate::effects::EffectKind::MillTop {
1242                    target: crate::state::TargetSide::SelfSide,
1243                    count: *count,
1244                },
1245                target: None,
1246                optional: false,
1247            });
1248        }
1249        AbilityTemplate::AutoOnPlayHeal { count } => {
1250            out.push(crate::effects::EffectSpec {
1251                id: crate::effects::EffectId::new(
1252                    crate::effects::EffectSourceKind::Auto,
1253                    card_id,
1254                    ability_index,
1255                    0,
1256                ),
1257                kind: crate::effects::EffectKind::Heal,
1258                target: Some(target_spec_from_template(TargetTemplate::SelfClock, *count)),
1259                optional: false,
1260            });
1261        }
1262        AbilityTemplate::AutoOnAttackDealDamage { amount, cancelable } => {
1263            out.push(crate::effects::EffectSpec {
1264                id: crate::effects::EffectId::new(
1265                    crate::effects::EffectSourceKind::Auto,
1266                    card_id,
1267                    ability_index,
1268                    0,
1269                ),
1270                kind: crate::effects::EffectKind::Damage {
1271                    amount: *amount as i32,
1272                    cancelable: *cancelable,
1273                    damage_type: crate::state::DamageType::Effect,
1274                },
1275                target: None,
1276                optional: false,
1277            });
1278        }
1279        AbilityTemplate::AutoEndPhaseDraw { count } => {
1280            out.push(crate::effects::EffectSpec {
1281                id: crate::effects::EffectId::new(
1282                    crate::effects::EffectSourceKind::Auto,
1283                    card_id,
1284                    ability_index,
1285                    0,
1286                ),
1287                kind: crate::effects::EffectKind::Draw { count: *count },
1288                target: None,
1289                optional: false,
1290            });
1291        }
1292        AbilityTemplate::AutoOnReverseDraw { count } => {
1293            out.push(crate::effects::EffectSpec {
1294                id: crate::effects::EffectId::new(
1295                    crate::effects::EffectSourceKind::Auto,
1296                    card_id,
1297                    ability_index,
1298                    0,
1299                ),
1300                kind: crate::effects::EffectKind::Draw { count: *count },
1301                target: None,
1302                optional: false,
1303            });
1304        }
1305        AbilityTemplate::AutoOnReverseSalvage {
1306            count,
1307            optional,
1308            card_type,
1309        } => {
1310            let mut spec = target_spec_from_template(TargetTemplate::SelfWaitingRoom, *count);
1311            spec.card_type = *card_type;
1312            out.push(crate::effects::EffectSpec {
1313                id: crate::effects::EffectId::new(
1314                    crate::effects::EffectSourceKind::Auto,
1315                    card_id,
1316                    ability_index,
1317                    0,
1318                ),
1319                kind: crate::effects::EffectKind::MoveToHand,
1320                target: Some(spec),
1321                optional: *optional,
1322            });
1323        }
1324        AbilityTemplate::EventDealDamage { amount, cancelable } => {
1325            out.push(crate::effects::EffectSpec {
1326                id: crate::effects::EffectId::new(
1327                    crate::effects::EffectSourceKind::EventPlay,
1328                    card_id,
1329                    ability_index,
1330                    0,
1331                ),
1332                kind: crate::effects::EffectKind::Damage {
1333                    amount: *amount as i32,
1334                    cancelable: *cancelable,
1335                    damage_type: crate::state::DamageType::Effect,
1336                },
1337                target: None,
1338                optional: false,
1339            });
1340        }
1341        AbilityTemplate::ActivatedPlaceholder => {
1342            out.push(crate::effects::EffectSpec {
1343                id: crate::effects::EffectId::new(
1344                    crate::effects::EffectSourceKind::Activated,
1345                    card_id,
1346                    ability_index,
1347                    0,
1348                ),
1349                kind: crate::effects::EffectKind::AddModifier {
1350                    kind: crate::state::ModifierKind::Power,
1351                    magnitude: 1000,
1352                    duration: crate::state::ModifierDuration::UntilEndOfTurn,
1353                },
1354                target: Some(target_spec_from_template(TargetTemplate::SelfStage, 1)),
1355                optional: false,
1356            });
1357        }
1358        AbilityTemplate::ActivatedTargetedPower {
1359            amount,
1360            count,
1361            target,
1362        } => {
1363            out.push(crate::effects::EffectSpec {
1364                id: crate::effects::EffectId::new(
1365                    crate::effects::EffectSourceKind::Activated,
1366                    card_id,
1367                    ability_index,
1368                    0,
1369                ),
1370                kind: crate::effects::EffectKind::AddModifier {
1371                    kind: crate::state::ModifierKind::Power,
1372                    magnitude: *amount,
1373                    duration: crate::state::ModifierDuration::UntilEndOfTurn,
1374                },
1375                target: Some(target_spec_from_template(*target, *count)),
1376                optional: false,
1377            });
1378        }
1379        AbilityTemplate::ActivatedPaidTargetedPower {
1380            amount,
1381            count,
1382            target,
1383            ..
1384        } => {
1385            out.push(crate::effects::EffectSpec {
1386                id: crate::effects::EffectId::new(
1387                    crate::effects::EffectSourceKind::Activated,
1388                    card_id,
1389                    ability_index,
1390                    0,
1391                ),
1392                kind: crate::effects::EffectKind::AddModifier {
1393                    kind: crate::state::ModifierKind::Power,
1394                    magnitude: *amount,
1395                    duration: crate::state::ModifierDuration::UntilEndOfTurn,
1396                },
1397                target: Some(target_spec_from_template(*target, *count)),
1398                optional: false,
1399            });
1400        }
1401        AbilityTemplate::ActivatedTargetedMoveToHand { count, target } => {
1402            out.push(crate::effects::EffectSpec {
1403                id: crate::effects::EffectId::new(
1404                    crate::effects::EffectSourceKind::Activated,
1405                    card_id,
1406                    ability_index,
1407                    0,
1408                ),
1409                kind: crate::effects::EffectKind::MoveToHand,
1410                target: Some(target_spec_from_template(*target, *count)),
1411                optional: false,
1412            });
1413        }
1414        AbilityTemplate::ActivatedPaidTargetedMoveToHand { count, target, .. } => {
1415            out.push(crate::effects::EffectSpec {
1416                id: crate::effects::EffectId::new(
1417                    crate::effects::EffectSourceKind::Activated,
1418                    card_id,
1419                    ability_index,
1420                    0,
1421                ),
1422                kind: crate::effects::EffectKind::MoveToHand,
1423                target: Some(target_spec_from_template(*target, *count)),
1424                optional: false,
1425            });
1426        }
1427        AbilityTemplate::ActivatedChangeController { count, target } => {
1428            out.push(crate::effects::EffectSpec {
1429                id: crate::effects::EffectId::new(
1430                    crate::effects::EffectSourceKind::Activated,
1431                    card_id,
1432                    ability_index,
1433                    0,
1434                ),
1435                kind: crate::effects::EffectKind::ChangeController {
1436                    new_controller: crate::state::TargetSide::SelfSide,
1437                },
1438                target: Some(target_spec_from_template(*target, *count)),
1439                optional: false,
1440            });
1441        }
1442        AbilityTemplate::ActivatedPaidChangeController { count, target, .. } => {
1443            out.push(crate::effects::EffectSpec {
1444                id: crate::effects::EffectId::new(
1445                    crate::effects::EffectSourceKind::Activated,
1446                    card_id,
1447                    ability_index,
1448                    0,
1449                ),
1450                kind: crate::effects::EffectKind::ChangeController {
1451                    new_controller: crate::state::TargetSide::SelfSide,
1452                },
1453                target: Some(target_spec_from_template(*target, *count)),
1454                optional: false,
1455            });
1456        }
1457        AbilityTemplate::CounterBackup { power } => {
1458            out.push(crate::effects::EffectSpec {
1459                id: crate::effects::EffectId::new(
1460                    crate::effects::EffectSourceKind::Counter,
1461                    card_id,
1462                    ability_index,
1463                    0,
1464                ),
1465                kind: crate::effects::EffectKind::CounterBackup { power: *power },
1466                target: None,
1467                optional: false,
1468            });
1469        }
1470        AbilityTemplate::CounterDamageReduce { amount } => {
1471            out.push(crate::effects::EffectSpec {
1472                id: crate::effects::EffectId::new(
1473                    crate::effects::EffectSourceKind::Counter,
1474                    card_id,
1475                    ability_index,
1476                    0,
1477                ),
1478                kind: crate::effects::EffectKind::CounterDamageReduce { amount: *amount },
1479                target: None,
1480                optional: false,
1481            });
1482        }
1483        AbilityTemplate::CounterDamageCancel => {
1484            out.push(crate::effects::EffectSpec {
1485                id: crate::effects::EffectId::new(
1486                    crate::effects::EffectSourceKind::Counter,
1487                    card_id,
1488                    ability_index,
1489                    0,
1490                ),
1491                kind: crate::effects::EffectKind::CounterDamageCancel,
1492                target: None,
1493                optional: false,
1494            });
1495        }
1496        AbilityTemplate::AbilityDef(_)
1497        | AbilityTemplate::Vanilla
1498        | AbilityTemplate::Unsupported { .. } => {}
1499    }
1500    out
1501}
1502
1503fn compile_effects_from_def(
1504    card_id: CardId,
1505    ability_index: u8,
1506    ability: &AbilityDef,
1507) -> Vec<crate::effects::EffectSpec> {
1508    let mut effects = Vec::with_capacity(ability.effects.len());
1509    for (effect_index, effect) in ability.effects.iter().enumerate() {
1510        let effect_id = crate::effects::EffectId::new(
1511            match ability.kind {
1512                AbilityKind::Continuous => crate::effects::EffectSourceKind::Continuous,
1513                AbilityKind::Activated => crate::effects::EffectSourceKind::Activated,
1514                AbilityKind::Auto => crate::effects::EffectSourceKind::Auto,
1515            },
1516            card_id,
1517            ability_index,
1518            effect_index as u8,
1519        );
1520        let (kind, target) = match effect {
1521            EffectTemplate::Draw { count } => {
1522                (crate::effects::EffectKind::Draw { count: *count }, None)
1523            }
1524            EffectTemplate::DealDamage { amount, cancelable } => (
1525                crate::effects::EffectKind::Damage {
1526                    amount: *amount as i32,
1527                    cancelable: *cancelable,
1528                    damage_type: crate::state::DamageType::Effect,
1529                },
1530                None,
1531            ),
1532            EffectTemplate::AddPower {
1533                amount,
1534                duration_turn,
1535            } => (
1536                crate::effects::EffectKind::AddModifier {
1537                    kind: crate::state::ModifierKind::Power,
1538                    magnitude: *amount,
1539                    duration: if *duration_turn {
1540                        crate::state::ModifierDuration::UntilEndOfTurn
1541                    } else {
1542                        crate::state::ModifierDuration::WhileOnStage
1543                    },
1544                },
1545                ability
1546                    .targets
1547                    .get(effect_index)
1548                    .or_else(|| ability.targets.first())
1549                    .map(|t| target_spec_from_template(*t, 1)),
1550            ),
1551            EffectTemplate::MoveToHand => (
1552                crate::effects::EffectKind::MoveToHand,
1553                ability
1554                    .targets
1555                    .get(effect_index)
1556                    .or_else(|| ability.targets.first())
1557                    .map(|t| target_spec_from_template(*t, 1)),
1558            ),
1559            EffectTemplate::MoveToWaitingRoom => (
1560                crate::effects::EffectKind::MoveToWaitingRoom,
1561                ability
1562                    .targets
1563                    .get(effect_index)
1564                    .or_else(|| ability.targets.first())
1565                    .map(|t| target_spec_from_template(*t, 1)),
1566            ),
1567            EffectTemplate::MoveToStock => (
1568                crate::effects::EffectKind::MoveToStock,
1569                ability
1570                    .targets
1571                    .get(effect_index)
1572                    .or_else(|| ability.targets.first())
1573                    .map(|t| target_spec_from_template(*t, 1)),
1574            ),
1575            EffectTemplate::MoveToClock => (
1576                crate::effects::EffectKind::MoveToClock,
1577                ability
1578                    .targets
1579                    .get(effect_index)
1580                    .or_else(|| ability.targets.first())
1581                    .map(|t| target_spec_from_template(*t, 1)),
1582            ),
1583            EffectTemplate::Heal => (
1584                crate::effects::EffectKind::Heal,
1585                ability
1586                    .targets
1587                    .get(effect_index)
1588                    .or_else(|| ability.targets.first())
1589                    .map(|t| target_spec_from_template(*t, 1)),
1590            ),
1591            EffectTemplate::RestTarget => (
1592                crate::effects::EffectKind::RestTarget,
1593                ability
1594                    .targets
1595                    .get(effect_index)
1596                    .or_else(|| ability.targets.first())
1597                    .map(|t| target_spec_from_template(*t, 1)),
1598            ),
1599            EffectTemplate::StandTarget => (
1600                crate::effects::EffectKind::StandTarget,
1601                ability
1602                    .targets
1603                    .get(effect_index)
1604                    .or_else(|| ability.targets.first())
1605                    .map(|t| target_spec_from_template(*t, 1)),
1606            ),
1607            EffectTemplate::StockCharge { count } => (
1608                crate::effects::EffectKind::StockCharge { count: *count },
1609                None,
1610            ),
1611            EffectTemplate::MillTop { target, count } => (
1612                crate::effects::EffectKind::MillTop {
1613                    target: *target,
1614                    count: *count,
1615                },
1616                None,
1617            ),
1618            EffectTemplate::MoveStageSlot { slot } => (
1619                crate::effects::EffectKind::MoveStageSlot { slot: *slot },
1620                ability
1621                    .targets
1622                    .get(effect_index)
1623                    .or_else(|| ability.targets.first())
1624                    .map(|t| target_spec_from_template(*t, 1)),
1625            ),
1626            EffectTemplate::SwapStageSlots => (
1627                crate::effects::EffectKind::SwapStageSlots,
1628                ability
1629                    .targets
1630                    .get(effect_index)
1631                    .or_else(|| ability.targets.first())
1632                    .map(|t| target_spec_from_template(*t, 2)),
1633            ),
1634            EffectTemplate::RandomDiscardFromHand { target, count } => (
1635                crate::effects::EffectKind::RandomDiscardFromHand {
1636                    target: *target,
1637                    count: *count,
1638                },
1639                None,
1640            ),
1641            EffectTemplate::RandomMill { target, count } => (
1642                crate::effects::EffectKind::RandomMill {
1643                    target: *target,
1644                    count: *count,
1645                },
1646                None,
1647            ),
1648            EffectTemplate::RevealZoneTop {
1649                target,
1650                zone,
1651                count,
1652                audience,
1653            } => (
1654                crate::effects::EffectKind::RevealZoneTop {
1655                    target: *target,
1656                    zone: *zone,
1657                    count: *count,
1658                    audience: *audience,
1659                },
1660                None,
1661            ),
1662            EffectTemplate::ChangeController => (
1663                crate::effects::EffectKind::ChangeController {
1664                    new_controller: crate::state::TargetSide::SelfSide,
1665                },
1666                ability
1667                    .targets
1668                    .get(effect_index)
1669                    .or_else(|| ability.targets.first())
1670                    .map(|t| target_spec_from_template(*t, 1)),
1671            ),
1672            EffectTemplate::CounterBackup { power } => (
1673                crate::effects::EffectKind::CounterBackup { power: *power },
1674                None,
1675            ),
1676            EffectTemplate::CounterDamageReduce { amount } => (
1677                crate::effects::EffectKind::CounterDamageReduce { amount: *amount },
1678                None,
1679            ),
1680            EffectTemplate::CounterDamageCancel => {
1681                (crate::effects::EffectKind::CounterDamageCancel, None)
1682            }
1683        };
1684        let target = target.map(|mut spec| {
1685            if let Some(card_type) = ability.target_card_type {
1686                spec.card_type = Some(card_type);
1687            }
1688            if let Some(trait_id) = ability.target_trait {
1689                spec.card_trait = Some(trait_id);
1690            }
1691            if let Some(level_max) = ability.target_level_max {
1692                spec.level_max = Some(level_max);
1693            }
1694            if let Some(cost_max) = ability.target_cost_max {
1695                spec.cost_max = Some(cost_max);
1696            }
1697            if let Some(limit) = ability.target_limit {
1698                if spec.zone == crate::state::TargetZone::DeckTop {
1699                    spec.limit = Some(limit);
1700                }
1701            }
1702            spec
1703        });
1704        effects.push(crate::effects::EffectSpec {
1705            id: effect_id,
1706            kind,
1707            target,
1708            optional: false,
1709        });
1710    }
1711    effects
1712}