1use super::*;
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
5pub enum AbilityCostStep {
6 #[serde(alias = "restOther", alias = "rest_other")]
7 RestOther,
9 #[serde(alias = "sacrificeFromStage", alias = "sacrifice_from_stage")]
10 SacrificeFromStage,
12 #[serde(alias = "discardFromHand", alias = "discard_from_hand")]
13 DiscardFromHand,
15 #[serde(alias = "clockFromHand", alias = "clock_from_hand")]
16 ClockFromHand,
18 #[serde(alias = "clockFromDeckTop", alias = "clock_from_deck_top")]
19 ClockFromDeckTop,
21 #[serde(alias = "revealFromHand", alias = "reveal_from_hand")]
22 RevealFromHand,
24}
25
26#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
28pub struct AbilityCost {
29 #[serde(default)]
30 pub stock: u8,
32 #[serde(default)]
33 pub rest_self: bool,
35 #[serde(default)]
36 pub rest_other: u8,
38 #[serde(default, alias = "sacrificeFromStage")]
39 pub sacrifice_from_stage: u8,
41 #[serde(default)]
42 pub discard_from_hand: u8,
44 #[serde(default)]
45 pub clock_from_hand: u8,
47 #[serde(default)]
48 pub clock_from_deck_top: u8,
50 #[serde(default)]
51 pub reveal_from_hand: u8,
53 #[serde(default, alias = "moveSelfToWaitingRoom")]
54 pub move_self_to_waiting_room: bool,
56 #[serde(default, alias = "returnSelfToHand")]
57 pub return_self_to_hand: bool,
59 #[serde(default, alias = "stepOrder")]
60 pub step_order: Vec<AbilityCostStep>,
62}
63
64impl AbilityCost {
65 pub fn is_empty(&self) -> bool {
67 self.stock == 0
68 && !self.rest_self
69 && self.rest_other == 0
70 && self.sacrifice_from_stage == 0
71 && self.discard_from_hand == 0
72 && self.clock_from_hand == 0
73 && self.clock_from_deck_top == 0
74 && self.reveal_from_hand == 0
75 && !self.move_self_to_waiting_room
76 && !self.return_self_to_hand
77 }
78
79 pub fn next_explicit_step(&self) -> Option<crate::state::CostStepKind> {
81 for step in &self.step_order {
82 match step {
83 AbilityCostStep::RestOther if self.rest_other > 0 => {
84 return Some(crate::state::CostStepKind::RestOther);
85 }
86 AbilityCostStep::SacrificeFromStage if self.sacrifice_from_stage > 0 => {
87 return Some(crate::state::CostStepKind::SacrificeFromStage);
88 }
89 AbilityCostStep::DiscardFromHand if self.discard_from_hand > 0 => {
90 return Some(crate::state::CostStepKind::DiscardFromHand);
91 }
92 AbilityCostStep::ClockFromHand if self.clock_from_hand > 0 => {
93 return Some(crate::state::CostStepKind::ClockFromHand);
94 }
95 AbilityCostStep::ClockFromDeckTop if self.clock_from_deck_top > 0 => {
96 return Some(crate::state::CostStepKind::ClockFromDeckTop);
97 }
98 AbilityCostStep::RevealFromHand if self.reveal_from_hand > 0 => {
99 return Some(crate::state::CostStepKind::RevealFromHand);
100 }
101 _ => {}
102 }
103 }
104 None
105 }
106}
107
108const fn default_target_side_self() -> TargetSide {
109 TargetSide::SelfSide
110}
111
112#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Hash)]
114pub struct AbilityDefConditions {
115 #[serde(default, alias = "requiresApproxEffects")]
116 pub requires_approx_effects: bool,
118 #[serde(default, alias = "climaxArea")]
119 pub climax_area: Option<AbilityDefClimaxAreaCondition>,
121 #[serde(default)]
122 pub turn: Option<ConditionTurn>,
124 #[serde(default, alias = "handLevelDelta")]
125 pub hand_level_delta: i8,
127 #[serde(default, alias = "selfWaitingRoomClimaxAtMost")]
128 pub self_waiting_room_climax_at_most: Option<u8>,
130 #[serde(default, alias = "selfClockCardIdsAny")]
131 pub self_clock_card_ids_any: Vec<CardId>,
133 #[serde(default, alias = "selfMemoryCardIdsAny")]
134 pub self_memory_card_ids_any: Vec<CardId>,
136 #[serde(default, alias = "selfMemoryAtMost")]
137 pub self_memory_at_most: Option<u8>,
139 #[serde(default, alias = "opponentStageHasLevelAtLeast")]
140 pub opponent_stage_has_level_at_least: Option<u8>,
142 #[serde(default, alias = "triggerCheckRevealedClimax")]
143 pub trigger_check_revealed_climax: bool,
145 #[serde(default, alias = "triggerCheckRevealedIcon")]
146 pub trigger_check_revealed_icon: Option<TriggerIcon>,
148 #[serde(default, alias = "ignoreColorRequirement")]
149 pub ignore_color_requirement: bool,
151 #[serde(default, alias = "zoneCount")]
152 pub zone_count: Option<ZoneCountCondition>,
154 #[serde(default, alias = "sourceRuleId")]
155 pub source_rule_id: Option<String>,
157}
158
159#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
161pub struct AbilityDefClimaxAreaCondition {
162 #[serde(default = "default_target_side_self")]
163 pub side: TargetSide,
165 #[serde(default, alias = "cardIds")]
166 pub card_ids: Vec<CardId>,
168}
169
170impl Default for AbilityDefClimaxAreaCondition {
171 fn default() -> Self {
172 Self {
173 side: default_target_side_self(),
174 card_ids: Vec::new(),
175 }
176 }
177}
178
179impl AbilityDefConditions {
180 fn has_play_requirement_overrides(&self) -> bool {
181 self.hand_level_delta != 0 || self.ignore_color_requirement
182 }
183}
184
185#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
187pub struct AbilityDef {
188 pub kind: AbilityKind,
190 pub timing: Option<AbilityTiming>,
192 pub effects: Vec<EffectTemplate>,
194 #[serde(default)]
195 pub effect_optional: Vec<bool>,
197 pub targets: Vec<TargetTemplate>,
199 #[serde(default)]
200 pub cost: AbilityCost,
202 #[serde(default, alias = "condition")]
203 pub conditions: AbilityDefConditions,
205 #[serde(default)]
206 pub target_card_type: Option<CardType>,
208 #[serde(default)]
209 pub target_trait: Option<u16>,
211 #[serde(default)]
212 pub target_level_max: Option<u8>,
214 #[serde(default)]
215 pub target_cost_max: Option<u8>,
217 #[serde(default, alias = "targetCardIds")]
218 pub target_card_ids: Vec<CardId>,
220 #[serde(default)]
221 pub target_limit: Option<u8>,
223}
224
225impl AbilityDef {
226 pub fn validate(&self) -> Result<()> {
228 if self.effects.is_empty() && !self.conditions.has_play_requirement_overrides() {
229 anyhow::bail!("AbilityDef must contain at least one effect");
230 }
231 if self.effects.len() > u8::MAX as usize {
232 anyhow::bail!("AbilityDef has too many effects");
233 }
234 if self.effect_optional.len() > self.effects.len() {
235 anyhow::bail!("AbilityDef optional flags exceed effects length");
236 }
237 if self.targets.len() > u8::MAX as usize {
238 anyhow::bail!("AbilityDef has too many targets");
239 }
240 if self.kind == AbilityKind::Continuous && !self.cost.is_empty() {
241 anyhow::bail!("AbilityDef cost is invalid for continuous abilities");
242 }
243 Ok(())
244 }
245}
246
247#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
249pub enum AbilityTiming {
250 BeginTurn,
252 BeginStandPhase,
254 AfterStandPhase,
256 BeginDrawPhase,
258 AfterDrawPhase,
260 BeginClockPhase,
262 AfterClockPhase,
264 LevelUp,
266 BeginMainPhase,
268 BeginClimaxPhase,
270 AfterClimaxPhase,
272 BeginAttackPhase,
274 BeginAttackDeclarationStep,
276 BeginEncoreStep,
278 EndPhase,
280 EndPhaseCleanup,
282 EndOfAttack,
284 AttackDeclaration,
286 OtherAttackDeclaration,
288 TriggerResolution,
290 Counter,
292 UseAct,
294 DamageResolution,
296 Encore,
298 OnPlay,
300 OnReverse,
302 BattleOpponentReverse,
304 DamageDealtNotCanceled,
306 DamageReceivedNotCanceled,
308 DamageDealtCanceled,
310 DamageReceivedCanceled,
312}
313
314#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
316#[allow(clippy::large_enum_variant)]
317pub enum AbilityTemplate {
318 Vanilla,
320 ContinuousPower {
322 amount: i32,
324 },
325 ContinuousCannotAttack,
327 ContinuousAttackCost {
329 cost: u8,
331 },
332 AutoOnPlayDraw {
334 count: u8,
336 },
337 AutoOnPlaySalvage {
339 count: u8,
341 optional: bool,
343 card_type: Option<CardType>,
345 },
346 AutoOnPlaySearchDeckTop {
348 count: u8,
350 optional: bool,
352 card_type: Option<CardType>,
354 },
355 AutoOnPlayRevealDeckTop {
357 count: u8,
359 },
360 AutoOnPlayStockCharge {
362 count: u8,
364 },
365 AutoOnPlayMillTop {
367 count: u8,
369 },
370 AutoOnPlayHeal {
372 count: u8,
374 },
375 AutoOnAttackDealDamage {
377 amount: u8,
379 cancelable: bool,
381 },
382 AutoEndPhaseDraw {
384 count: u8,
386 },
387 AutoOnReverseDraw {
389 count: u8,
391 },
392 AutoOnReverseSalvage {
394 count: u8,
396 optional: bool,
398 card_type: Option<CardType>,
400 },
401 EventDealDamage {
403 amount: u8,
405 cancelable: bool,
407 },
408 ActivatedPlaceholder,
410 ActivatedTargetedPower {
412 amount: i32,
414 count: u8,
416 target: TargetTemplate,
418 },
419 ActivatedPaidTargetedPower {
421 cost: u8,
423 amount: i32,
425 count: u8,
427 target: TargetTemplate,
429 },
430 ActivatedTargetedMoveToHand {
432 count: u8,
434 target: TargetTemplate,
436 },
437 ActivatedPaidTargetedMoveToHand {
439 cost: u8,
441 count: u8,
443 target: TargetTemplate,
445 },
446 ActivatedChangeController {
448 count: u8,
450 target: TargetTemplate,
452 },
453 ActivatedPaidChangeController {
455 cost: u8,
457 count: u8,
459 target: TargetTemplate,
461 },
462 CounterBackup {
464 power: i32,
466 },
467 CounterDamageReduce {
469 amount: u8,
471 },
472 CounterDamageCancel,
474 Bond {
476 cost: AbilityCost,
478 count: u8,
480 #[serde(default)]
481 target_ids: Vec<CardId>,
483 },
484 EncoreVariant {
486 cost: AbilityCost,
488 },
489 AbilityDef(
491 AbilityDef,
493 ),
494 Unsupported {
496 id: u32,
498 },
499}
500
501#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
503pub enum AbilityKind {
504 Continuous,
506 Activated,
508 Auto,
510}
511
512#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
514pub struct AbilitySpec {
515 pub kind: AbilityKind,
517 pub template: AbilityTemplate,
519}
520
521#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
523pub enum AbilityTemplateTag {
524 Vanilla,
526 ContinuousPower,
528 ContinuousCannotAttack,
530 ContinuousAttackCost,
532 AutoOnPlayDraw,
534 AutoOnPlaySalvage,
536 AutoOnPlaySearchDeckTop,
538 AutoOnPlayRevealDeckTop,
540 AutoOnPlayStockCharge,
542 AutoOnPlayMillTop,
544 AutoOnPlayHeal,
546 AutoOnAttackDealDamage,
548 AutoEndPhaseDraw,
550 AutoOnReverseDraw,
552 AutoOnReverseSalvage,
554 EventDealDamage,
556 ActivatedPlaceholder,
558 ActivatedTargetedPower,
560 ActivatedPaidTargetedPower,
562 ActivatedTargetedMoveToHand,
564 ActivatedPaidTargetedMoveToHand,
566 ActivatedChangeController,
568 ActivatedPaidChangeController,
570 CounterBackup,
572 CounterDamageReduce,
574 CounterDamageCancel,
576 Bond,
578 EncoreVariant,
580 AbilityDef,
582 Unsupported,
584}
585
586impl AbilityTemplate {
587 pub fn tag(&self) -> AbilityTemplateTag {
589 match self {
590 AbilityTemplate::Vanilla => AbilityTemplateTag::Vanilla,
591 AbilityTemplate::ContinuousPower { .. } => AbilityTemplateTag::ContinuousPower,
592 AbilityTemplate::ContinuousCannotAttack => AbilityTemplateTag::ContinuousCannotAttack,
593 AbilityTemplate::ContinuousAttackCost { .. } => {
594 AbilityTemplateTag::ContinuousAttackCost
595 }
596 AbilityTemplate::AutoOnPlayDraw { .. } => AbilityTemplateTag::AutoOnPlayDraw,
597 AbilityTemplate::AutoOnPlaySalvage { .. } => AbilityTemplateTag::AutoOnPlaySalvage,
598 AbilityTemplate::AutoOnPlaySearchDeckTop { .. } => {
599 AbilityTemplateTag::AutoOnPlaySearchDeckTop
600 }
601 AbilityTemplate::AutoOnPlayRevealDeckTop { .. } => {
602 AbilityTemplateTag::AutoOnPlayRevealDeckTop
603 }
604 AbilityTemplate::AutoOnPlayStockCharge { .. } => {
605 AbilityTemplateTag::AutoOnPlayStockCharge
606 }
607 AbilityTemplate::AutoOnPlayMillTop { .. } => AbilityTemplateTag::AutoOnPlayMillTop,
608 AbilityTemplate::AutoOnPlayHeal { .. } => AbilityTemplateTag::AutoOnPlayHeal,
609 AbilityTemplate::AutoOnAttackDealDamage { .. } => {
610 AbilityTemplateTag::AutoOnAttackDealDamage
611 }
612 AbilityTemplate::AutoEndPhaseDraw { .. } => AbilityTemplateTag::AutoEndPhaseDraw,
613 AbilityTemplate::AutoOnReverseDraw { .. } => AbilityTemplateTag::AutoOnReverseDraw,
614 AbilityTemplate::AutoOnReverseSalvage { .. } => {
615 AbilityTemplateTag::AutoOnReverseSalvage
616 }
617 AbilityTemplate::EventDealDamage { .. } => AbilityTemplateTag::EventDealDamage,
618 AbilityTemplate::ActivatedPlaceholder => AbilityTemplateTag::ActivatedPlaceholder,
619 AbilityTemplate::ActivatedTargetedPower { .. } => {
620 AbilityTemplateTag::ActivatedTargetedPower
621 }
622 AbilityTemplate::ActivatedPaidTargetedPower { .. } => {
623 AbilityTemplateTag::ActivatedPaidTargetedPower
624 }
625 AbilityTemplate::ActivatedTargetedMoveToHand { .. } => {
626 AbilityTemplateTag::ActivatedTargetedMoveToHand
627 }
628 AbilityTemplate::ActivatedPaidTargetedMoveToHand { .. } => {
629 AbilityTemplateTag::ActivatedPaidTargetedMoveToHand
630 }
631 AbilityTemplate::ActivatedChangeController { .. } => {
632 AbilityTemplateTag::ActivatedChangeController
633 }
634 AbilityTemplate::ActivatedPaidChangeController { .. } => {
635 AbilityTemplateTag::ActivatedPaidChangeController
636 }
637 AbilityTemplate::CounterBackup { .. } => AbilityTemplateTag::CounterBackup,
638 AbilityTemplate::CounterDamageReduce { .. } => AbilityTemplateTag::CounterDamageReduce,
639 AbilityTemplate::CounterDamageCancel => AbilityTemplateTag::CounterDamageCancel,
640 AbilityTemplate::Bond { .. } => AbilityTemplateTag::Bond,
641 AbilityTemplate::EncoreVariant { .. } => AbilityTemplateTag::EncoreVariant,
642 AbilityTemplate::AbilityDef(_) => AbilityTemplateTag::AbilityDef,
643 AbilityTemplate::Unsupported { .. } => AbilityTemplateTag::Unsupported,
644 }
645 }
646
647 pub fn activation_cost(&self) -> Option<u8> {
649 match self {
650 AbilityTemplate::ActivatedPaidTargetedPower { cost, .. }
651 | AbilityTemplate::ActivatedPaidTargetedMoveToHand { cost, .. }
652 | AbilityTemplate::ActivatedPaidChangeController { cost, .. } => Some(*cost),
653 _ => None,
654 }
655 }
656
657 pub fn activation_cost_spec(&self) -> AbilityCost {
659 match self {
660 AbilityTemplate::ActivatedPaidTargetedPower { cost, .. }
661 | AbilityTemplate::ActivatedPaidTargetedMoveToHand { cost, .. }
662 | AbilityTemplate::ActivatedPaidChangeController { cost, .. } => AbilityCost {
663 stock: *cost,
664 ..AbilityCost::default()
665 },
666 AbilityTemplate::Bond { cost, .. } => cost.clone(),
667 AbilityTemplate::AbilityDef(def) => def.cost.clone(),
668 _ => AbilityCost::default(),
669 }
670 }
671
672 pub fn encore_variant_cost(&self) -> Option<AbilityCost> {
674 match self {
675 AbilityTemplate::EncoreVariant { cost } => Some(cost.clone()),
676 _ => None,
677 }
678 }
679
680 pub fn timing(&self) -> Option<AbilityTiming> {
682 match self {
683 AbilityTemplate::AutoOnPlayDraw { .. }
684 | AbilityTemplate::AutoOnPlaySalvage { .. }
685 | AbilityTemplate::AutoOnPlaySearchDeckTop { .. }
686 | AbilityTemplate::AutoOnPlayRevealDeckTop { .. }
687 | AbilityTemplate::AutoOnPlayStockCharge { .. }
688 | AbilityTemplate::AutoOnPlayMillTop { .. }
689 | AbilityTemplate::AutoOnPlayHeal { .. }
690 | AbilityTemplate::Bond { .. } => Some(AbilityTiming::OnPlay),
691 AbilityTemplate::AutoOnAttackDealDamage { .. } => {
692 Some(AbilityTiming::AttackDeclaration)
693 }
694 AbilityTemplate::AutoEndPhaseDraw { .. } => Some(AbilityTiming::EndPhase),
695 AbilityTemplate::AutoOnReverseDraw { .. } => Some(AbilityTiming::OnReverse),
696 AbilityTemplate::AutoOnReverseSalvage { .. } => Some(AbilityTiming::OnReverse),
697 AbilityTemplate::CounterBackup { .. }
698 | AbilityTemplate::CounterDamageReduce { .. }
699 | AbilityTemplate::CounterDamageCancel => Some(AbilityTiming::Counter),
700 AbilityTemplate::EncoreVariant { .. } => Some(AbilityTiming::Encore),
701 AbilityTemplate::EventDealDamage { .. } => Some(AbilityTiming::OnPlay),
702 AbilityTemplate::AbilityDef(def) => def.timing,
703 _ => None,
704 }
705 }
706
707 pub fn is_event_play(&self) -> bool {
709 matches!(self, AbilityTemplate::EventDealDamage { .. })
710 }
711}
712
713impl AbilitySpec {
714 pub fn from_template(template: &AbilityTemplate) -> Self {
716 let kind = match template {
717 AbilityTemplate::ContinuousPower { .. }
718 | AbilityTemplate::ContinuousCannotAttack
719 | AbilityTemplate::ContinuousAttackCost { .. } => AbilityKind::Continuous,
720 AbilityTemplate::ActivatedPlaceholder
721 | AbilityTemplate::ActivatedTargetedPower { .. }
722 | AbilityTemplate::ActivatedPaidTargetedPower { .. }
723 | AbilityTemplate::ActivatedTargetedMoveToHand { .. }
724 | AbilityTemplate::ActivatedPaidTargetedMoveToHand { .. }
725 | AbilityTemplate::ActivatedChangeController { .. }
726 | AbilityTemplate::ActivatedPaidChangeController { .. } => AbilityKind::Activated,
727 AbilityTemplate::AbilityDef(def) => def.kind,
728 _ => AbilityKind::Auto,
729 };
730 Self {
731 kind,
732 template: template.clone(),
733 }
734 }
735
736 pub fn timing(&self) -> Option<AbilityTiming> {
738 self.template.timing()
739 }
740}