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}