1#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
3pub enum AbilityCostStep {
4 #[serde(alias = "restOther", alias = "rest_other")]
5 RestOther,
7 #[serde(alias = "sacrificeFromStage", alias = "sacrifice_from_stage")]
8 SacrificeFromStage,
10 #[serde(alias = "discardFromHand", alias = "discard_from_hand")]
11 DiscardFromHand,
13 #[serde(alias = "clockFromHand", alias = "clock_from_hand")]
14 ClockFromHand,
16 #[serde(alias = "clockFromDeckTop", alias = "clock_from_deck_top")]
17 ClockFromDeckTop,
19 #[serde(alias = "revealFromHand", alias = "reveal_from_hand")]
20 RevealFromHand,
22}
23
24#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
26pub struct AbilityCost {
27 #[serde(default)]
28 pub stock: u8,
30 #[serde(default)]
31 pub rest_self: bool,
33 #[serde(default)]
34 pub rest_other: u8,
36 #[serde(default, alias = "sacrificeFromStage")]
37 pub sacrifice_from_stage: u8,
39 #[serde(default)]
40 pub discard_from_hand: u8,
42 #[serde(default)]
43 pub clock_from_hand: u8,
45 #[serde(default)]
46 pub clock_from_deck_top: u8,
48 #[serde(default)]
49 pub reveal_from_hand: u8,
51 #[serde(default, alias = "moveSelfToWaitingRoom")]
52 pub move_self_to_waiting_room: bool,
54 #[serde(default, alias = "returnSelfToHand")]
55 pub return_self_to_hand: bool,
57 #[serde(default, alias = "stepOrder")]
58 pub step_order: Vec<AbilityCostStep>,
60}
61
62impl AbilityCost {
63 pub fn is_empty(&self) -> bool {
65 self.stock == 0
66 && !self.rest_self
67 && self.rest_other == 0
68 && self.sacrifice_from_stage == 0
69 && self.discard_from_hand == 0
70 && self.clock_from_hand == 0
71 && self.clock_from_deck_top == 0
72 && self.reveal_from_hand == 0
73 && !self.move_self_to_waiting_room
74 && !self.return_self_to_hand
75 }
76
77 pub fn next_explicit_step(&self) -> Option<crate::state::CostStepKind> {
79 for step in &self.step_order {
80 match step {
81 AbilityCostStep::RestOther if self.rest_other > 0 => {
82 return Some(crate::state::CostStepKind::RestOther);
83 }
84 AbilityCostStep::SacrificeFromStage if self.sacrifice_from_stage > 0 => {
85 return Some(crate::state::CostStepKind::SacrificeFromStage);
86 }
87 AbilityCostStep::DiscardFromHand if self.discard_from_hand > 0 => {
88 return Some(crate::state::CostStepKind::DiscardFromHand);
89 }
90 AbilityCostStep::ClockFromHand if self.clock_from_hand > 0 => {
91 return Some(crate::state::CostStepKind::ClockFromHand);
92 }
93 AbilityCostStep::ClockFromDeckTop if self.clock_from_deck_top > 0 => {
94 return Some(crate::state::CostStepKind::ClockFromDeckTop);
95 }
96 AbilityCostStep::RevealFromHand if self.reveal_from_hand > 0 => {
97 return Some(crate::state::CostStepKind::RevealFromHand);
98 }
99 _ => {}
100 }
101 }
102 None
103 }
104}
105
106const fn default_target_side_self() -> TargetSide {
107 TargetSide::SelfSide
108}
109
110#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Hash)]
112pub struct AbilityDefConditions {
113 #[serde(default, alias = "requiresApproxEffects")]
114 pub requires_approx_effects: bool,
116 #[serde(default, alias = "climaxArea")]
117 pub climax_area: Option<AbilityDefClimaxAreaCondition>,
119 #[serde(default)]
120 pub turn: Option<ConditionTurn>,
122 #[serde(default, alias = "handLevelDelta")]
123 pub hand_level_delta: i8,
125 #[serde(default, alias = "selfWaitingRoomClimaxAtMost")]
126 pub self_waiting_room_climax_at_most: Option<u8>,
128 #[serde(default, alias = "selfClockCardIdsAny")]
129 pub self_clock_card_ids_any: Vec<CardId>,
131 #[serde(default, alias = "selfMemoryCardIdsAny")]
132 pub self_memory_card_ids_any: Vec<CardId>,
134 #[serde(default, alias = "selfMemoryAtMost")]
135 pub self_memory_at_most: Option<u8>,
137 #[serde(default, alias = "opponentStageHasLevelAtLeast")]
138 pub opponent_stage_has_level_at_least: Option<u8>,
140 #[serde(default, alias = "triggerCheckRevealedClimax")]
141 pub trigger_check_revealed_climax: bool,
143 #[serde(default, alias = "triggerCheckRevealedIcon")]
144 pub trigger_check_revealed_icon: Option<TriggerIcon>,
146 #[serde(default, alias = "ignoreColorRequirement")]
147 pub ignore_color_requirement: bool,
149 #[serde(default, alias = "zoneCount")]
150 pub zone_count: Option<ZoneCountCondition>,
152 #[serde(default, alias = "sourceRuleId")]
153 pub source_rule_id: Option<String>,
155}
156
157#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
159pub struct AbilityDefClimaxAreaCondition {
160 #[serde(default = "default_target_side_self")]
161 pub side: TargetSide,
163 #[serde(default, alias = "cardIds")]
164 pub card_ids: Vec<CardId>,
166}
167
168impl Default for AbilityDefClimaxAreaCondition {
169 fn default() -> Self {
170 Self {
171 side: default_target_side_self(),
172 card_ids: Vec::new(),
173 }
174 }
175}
176
177impl AbilityDefConditions {
178 fn has_play_requirement_overrides(&self) -> bool {
179 self.hand_level_delta != 0 || self.ignore_color_requirement
180 }
181}
182
183#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
185pub struct AbilityDef {
186 pub kind: AbilityKind,
188 pub timing: Option<AbilityTiming>,
190 pub effects: Vec<EffectTemplate>,
192 #[serde(default)]
193 pub effect_optional: Vec<bool>,
195 pub targets: Vec<TargetTemplate>,
197 #[serde(default)]
198 pub cost: AbilityCost,
200 #[serde(default, alias = "condition")]
201 pub conditions: AbilityDefConditions,
203 #[serde(default)]
204 pub target_card_type: Option<CardType>,
206 #[serde(default)]
207 pub target_trait: Option<u16>,
209 #[serde(default)]
210 pub target_level_max: Option<u8>,
212 #[serde(default)]
213 pub target_cost_max: Option<u8>,
215 #[serde(default, alias = "targetCardIds")]
216 pub target_card_ids: Vec<CardId>,
218 #[serde(default)]
219 pub target_limit: Option<u8>,
221}
222
223impl AbilityDef {
224 pub fn validate(&self) -> Result<()> {
226 if self.effects.is_empty() && !self.conditions.has_play_requirement_overrides() {
227 anyhow::bail!("AbilityDef must contain at least one effect");
228 }
229 if self.effects.len() > u8::MAX as usize {
230 anyhow::bail!("AbilityDef has too many effects");
231 }
232 if self.effect_optional.len() > self.effects.len() {
233 anyhow::bail!("AbilityDef optional flags exceed effects length");
234 }
235 if self.targets.len() > u8::MAX as usize {
236 anyhow::bail!("AbilityDef has too many targets");
237 }
238 if self.kind == AbilityKind::Continuous && !self.cost.is_empty() {
239 anyhow::bail!("AbilityDef cost is invalid for continuous abilities");
240 }
241 Ok(())
242 }
243}
244
245#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
247pub enum AbilityTiming {
248 BeginTurn,
250 BeginStandPhase,
252 AfterStandPhase,
254 BeginDrawPhase,
256 AfterDrawPhase,
258 BeginClockPhase,
260 AfterClockPhase,
262 LevelUp,
264 BeginMainPhase,
266 BeginClimaxPhase,
268 AfterClimaxPhase,
270 BeginAttackPhase,
272 BeginAttackDeclarationStep,
274 BeginEncoreStep,
276 EndPhase,
278 EndPhaseCleanup,
280 EndOfAttack,
282 AttackDeclaration,
284 OtherAttackDeclaration,
286 TriggerResolution,
288 Counter,
290 UseAct,
292 DamageResolution,
294 Encore,
296 OnPlay,
298 OnReverse,
300 BattleOpponentReverse,
302 DamageDealtNotCanceled,
304 DamageReceivedNotCanceled,
306 DamageDealtCanceled,
308 DamageReceivedCanceled,
310}
311
312#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
314#[allow(clippy::large_enum_variant)]
315pub enum AbilityTemplate {
316 Vanilla,
318 ContinuousPower {
320 amount: i32,
322 },
323 ContinuousCannotAttack,
325 ContinuousAttackCost {
327 cost: u8,
329 },
330 AutoOnPlayDraw {
332 count: u8,
334 },
335 AutoOnPlaySalvage {
337 count: u8,
339 optional: bool,
341 card_type: Option<CardType>,
343 },
344 AutoOnPlaySearchDeckTop {
346 count: u8,
348 optional: bool,
350 card_type: Option<CardType>,
352 },
353 AutoOnPlayRevealDeckTop {
355 count: u8,
357 },
358 AutoOnPlayStockCharge {
360 count: u8,
362 },
363 AutoOnPlayMillTop {
365 count: u8,
367 },
368 AutoOnPlayHeal {
370 count: u8,
372 },
373 AutoOnAttackDealDamage {
375 amount: u8,
377 cancelable: bool,
379 },
380 AutoEndPhaseDraw {
382 count: u8,
384 },
385 AutoOnReverseDraw {
387 count: u8,
389 },
390 AutoOnReverseSalvage {
392 count: u8,
394 optional: bool,
396 card_type: Option<CardType>,
398 },
399 EventDealDamage {
401 amount: u8,
403 cancelable: bool,
405 },
406 ActivatedPlaceholder,
408 ActivatedTargetedPower {
410 amount: i32,
412 count: u8,
414 target: TargetTemplate,
416 },
417 ActivatedPaidTargetedPower {
419 cost: u8,
421 amount: i32,
423 count: u8,
425 target: TargetTemplate,
427 },
428 ActivatedTargetedMoveToHand {
430 count: u8,
432 target: TargetTemplate,
434 },
435 ActivatedPaidTargetedMoveToHand {
437 cost: u8,
439 count: u8,
441 target: TargetTemplate,
443 },
444 ActivatedChangeController {
446 count: u8,
448 target: TargetTemplate,
450 },
451 ActivatedPaidChangeController {
453 cost: u8,
455 count: u8,
457 target: TargetTemplate,
459 },
460 CounterBackup {
462 power: i32,
464 },
465 CounterDamageReduce {
467 amount: u8,
469 },
470 CounterDamageCancel,
472 Bond {
474 cost: AbilityCost,
476 count: u8,
478 #[serde(default)]
479 target_ids: Vec<CardId>,
481 },
482 EncoreVariant {
484 cost: AbilityCost,
486 },
487 AbilityDef(
489 AbilityDef,
491 ),
492 Unsupported {
494 id: u32,
496 },
497}
498
499#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
501pub enum AbilityKind {
502 Continuous,
504 Activated,
506 Auto,
508}
509
510#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
512pub struct AbilitySpec {
513 pub kind: AbilityKind,
515 pub template: AbilityTemplate,
517}
518
519#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
521pub enum AbilityTemplateTag {
522 Vanilla,
524 ContinuousPower,
526 ContinuousCannotAttack,
528 ContinuousAttackCost,
530 AutoOnPlayDraw,
532 AutoOnPlaySalvage,
534 AutoOnPlaySearchDeckTop,
536 AutoOnPlayRevealDeckTop,
538 AutoOnPlayStockCharge,
540 AutoOnPlayMillTop,
542 AutoOnPlayHeal,
544 AutoOnAttackDealDamage,
546 AutoEndPhaseDraw,
548 AutoOnReverseDraw,
550 AutoOnReverseSalvage,
552 EventDealDamage,
554 ActivatedPlaceholder,
556 ActivatedTargetedPower,
558 ActivatedPaidTargetedPower,
560 ActivatedTargetedMoveToHand,
562 ActivatedPaidTargetedMoveToHand,
564 ActivatedChangeController,
566 ActivatedPaidChangeController,
568 CounterBackup,
570 CounterDamageReduce,
572 CounterDamageCancel,
574 Bond,
576 EncoreVariant,
578 AbilityDef,
580 Unsupported,
582}
583
584impl AbilityTemplate {
585 pub fn tag(&self) -> AbilityTemplateTag {
587 match self {
588 AbilityTemplate::Vanilla => AbilityTemplateTag::Vanilla,
589 AbilityTemplate::ContinuousPower { .. } => AbilityTemplateTag::ContinuousPower,
590 AbilityTemplate::ContinuousCannotAttack => AbilityTemplateTag::ContinuousCannotAttack,
591 AbilityTemplate::ContinuousAttackCost { .. } => {
592 AbilityTemplateTag::ContinuousAttackCost
593 }
594 AbilityTemplate::AutoOnPlayDraw { .. } => AbilityTemplateTag::AutoOnPlayDraw,
595 AbilityTemplate::AutoOnPlaySalvage { .. } => AbilityTemplateTag::AutoOnPlaySalvage,
596 AbilityTemplate::AutoOnPlaySearchDeckTop { .. } => {
597 AbilityTemplateTag::AutoOnPlaySearchDeckTop
598 }
599 AbilityTemplate::AutoOnPlayRevealDeckTop { .. } => {
600 AbilityTemplateTag::AutoOnPlayRevealDeckTop
601 }
602 AbilityTemplate::AutoOnPlayStockCharge { .. } => {
603 AbilityTemplateTag::AutoOnPlayStockCharge
604 }
605 AbilityTemplate::AutoOnPlayMillTop { .. } => AbilityTemplateTag::AutoOnPlayMillTop,
606 AbilityTemplate::AutoOnPlayHeal { .. } => AbilityTemplateTag::AutoOnPlayHeal,
607 AbilityTemplate::AutoOnAttackDealDamage { .. } => {
608 AbilityTemplateTag::AutoOnAttackDealDamage
609 }
610 AbilityTemplate::AutoEndPhaseDraw { .. } => AbilityTemplateTag::AutoEndPhaseDraw,
611 AbilityTemplate::AutoOnReverseDraw { .. } => AbilityTemplateTag::AutoOnReverseDraw,
612 AbilityTemplate::AutoOnReverseSalvage { .. } => {
613 AbilityTemplateTag::AutoOnReverseSalvage
614 }
615 AbilityTemplate::EventDealDamage { .. } => AbilityTemplateTag::EventDealDamage,
616 AbilityTemplate::ActivatedPlaceholder => AbilityTemplateTag::ActivatedPlaceholder,
617 AbilityTemplate::ActivatedTargetedPower { .. } => {
618 AbilityTemplateTag::ActivatedTargetedPower
619 }
620 AbilityTemplate::ActivatedPaidTargetedPower { .. } => {
621 AbilityTemplateTag::ActivatedPaidTargetedPower
622 }
623 AbilityTemplate::ActivatedTargetedMoveToHand { .. } => {
624 AbilityTemplateTag::ActivatedTargetedMoveToHand
625 }
626 AbilityTemplate::ActivatedPaidTargetedMoveToHand { .. } => {
627 AbilityTemplateTag::ActivatedPaidTargetedMoveToHand
628 }
629 AbilityTemplate::ActivatedChangeController { .. } => {
630 AbilityTemplateTag::ActivatedChangeController
631 }
632 AbilityTemplate::ActivatedPaidChangeController { .. } => {
633 AbilityTemplateTag::ActivatedPaidChangeController
634 }
635 AbilityTemplate::CounterBackup { .. } => AbilityTemplateTag::CounterBackup,
636 AbilityTemplate::CounterDamageReduce { .. } => AbilityTemplateTag::CounterDamageReduce,
637 AbilityTemplate::CounterDamageCancel => AbilityTemplateTag::CounterDamageCancel,
638 AbilityTemplate::Bond { .. } => AbilityTemplateTag::Bond,
639 AbilityTemplate::EncoreVariant { .. } => AbilityTemplateTag::EncoreVariant,
640 AbilityTemplate::AbilityDef(_) => AbilityTemplateTag::AbilityDef,
641 AbilityTemplate::Unsupported { .. } => AbilityTemplateTag::Unsupported,
642 }
643 }
644
645 pub fn activation_cost(&self) -> Option<u8> {
647 match self {
648 AbilityTemplate::ActivatedPaidTargetedPower { cost, .. }
649 | AbilityTemplate::ActivatedPaidTargetedMoveToHand { cost, .. }
650 | AbilityTemplate::ActivatedPaidChangeController { cost, .. } => Some(*cost),
651 _ => None,
652 }
653 }
654
655 pub fn activation_cost_spec(&self) -> AbilityCost {
657 match self {
658 AbilityTemplate::ActivatedPaidTargetedPower { cost, .. }
659 | AbilityTemplate::ActivatedPaidTargetedMoveToHand { cost, .. }
660 | AbilityTemplate::ActivatedPaidChangeController { cost, .. } => AbilityCost {
661 stock: *cost,
662 ..AbilityCost::default()
663 },
664 AbilityTemplate::Bond { cost, .. } => cost.clone(),
665 AbilityTemplate::AbilityDef(def) => def.cost.clone(),
666 _ => AbilityCost::default(),
667 }
668 }
669
670 pub fn encore_variant_cost(&self) -> Option<AbilityCost> {
672 match self {
673 AbilityTemplate::EncoreVariant { cost } => Some(cost.clone()),
674 _ => None,
675 }
676 }
677
678 pub fn timing(&self) -> Option<AbilityTiming> {
680 match self {
681 AbilityTemplate::AutoOnPlayDraw { .. }
682 | AbilityTemplate::AutoOnPlaySalvage { .. }
683 | AbilityTemplate::AutoOnPlaySearchDeckTop { .. }
684 | AbilityTemplate::AutoOnPlayRevealDeckTop { .. }
685 | AbilityTemplate::AutoOnPlayStockCharge { .. }
686 | AbilityTemplate::AutoOnPlayMillTop { .. }
687 | AbilityTemplate::AutoOnPlayHeal { .. }
688 | AbilityTemplate::Bond { .. } => Some(AbilityTiming::OnPlay),
689 AbilityTemplate::AutoOnAttackDealDamage { .. } => {
690 Some(AbilityTiming::AttackDeclaration)
691 }
692 AbilityTemplate::AutoEndPhaseDraw { .. } => Some(AbilityTiming::EndPhase),
693 AbilityTemplate::AutoOnReverseDraw { .. } => Some(AbilityTiming::OnReverse),
694 AbilityTemplate::AutoOnReverseSalvage { .. } => Some(AbilityTiming::OnReverse),
695 AbilityTemplate::CounterBackup { .. }
696 | AbilityTemplate::CounterDamageReduce { .. }
697 | AbilityTemplate::CounterDamageCancel => Some(AbilityTiming::Counter),
698 AbilityTemplate::EncoreVariant { .. } => Some(AbilityTiming::Encore),
699 AbilityTemplate::EventDealDamage { .. } => Some(AbilityTiming::OnPlay),
700 AbilityTemplate::AbilityDef(def) => def.timing,
701 _ => None,
702 }
703 }
704
705 pub fn is_event_play(&self) -> bool {
707 matches!(self, AbilityTemplate::EventDealDamage { .. })
708 }
709}
710
711impl AbilitySpec {
712 pub fn from_template(template: &AbilityTemplate) -> Self {
714 let kind = match template {
715 AbilityTemplate::ContinuousPower { .. }
716 | AbilityTemplate::ContinuousCannotAttack
717 | AbilityTemplate::ContinuousAttackCost { .. } => AbilityKind::Continuous,
718 AbilityTemplate::ActivatedPlaceholder
719 | AbilityTemplate::ActivatedTargetedPower { .. }
720 | AbilityTemplate::ActivatedPaidTargetedPower { .. }
721 | AbilityTemplate::ActivatedTargetedMoveToHand { .. }
722 | AbilityTemplate::ActivatedPaidTargetedMoveToHand { .. }
723 | AbilityTemplate::ActivatedChangeController { .. }
724 | AbilityTemplate::ActivatedPaidChangeController { .. } => AbilityKind::Activated,
725 AbilityTemplate::AbilityDef(def) => def.kind,
726 _ => AbilityKind::Auto,
727 };
728 Self {
729 kind,
730 template: template.clone(),
731 }
732 }
733
734 pub fn timing(&self) -> Option<AbilityTiming> {
736 self.template.timing()
737 }
738}