Skip to main content

weiss_core/
effects.rs

1use serde::{Deserialize, Serialize};
2
3use crate::db::{
4    BattleOpponentMoveDestination, BattleOpponentMovePreludeAction, BrainstormMode, CardId,
5    ConditionTurn, GrantDuration, TriggerIcon, ZoneCountCondition,
6};
7use crate::events::RevealAudience;
8use crate::state::{DamageType, ModifierDuration, ModifierKind, TargetSide, TargetZone};
9
10mod id;
11mod payload;
12mod replacement;
13
14pub use id::{EffectId, EffectSourceKind, EffectSpec};
15pub use payload::EffectPayload;
16pub use replacement::{
17    ReplacementHook, ReplacementKind, ReplacementSpec, RuleOverrideKind, TerminalOutcomeSpec,
18};
19
20/// Effect kinds that can be executed by the engine.
21#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
22pub enum EffectKind {
23    /// Draw cards.
24    Draw {
25        /// Number of cards to draw.
26        count: u8,
27    },
28    /// Deal damage.
29    Damage {
30        /// Damage magnitude to deal.
31        amount: i32,
32        /// Whether damage can be canceled by revealing a climax.
33        cancelable: bool,
34        /// Damage classification (e.g., battle vs effect damage).
35        damage_type: DamageType,
36    },
37    /// Add a modifier to the target.
38    AddModifier {
39        /// Modifier kind (power, soul, etc).
40        kind: ModifierKind,
41        /// Signed magnitude of the modifier.
42        magnitude: i32,
43        /// Duration of the modifier.
44        duration: ModifierDuration,
45    },
46    /// Grant an ability definition to the target.
47    GrantAbilityDef {
48        /// Ability definition to grant.
49        ability: Box<crate::db::AbilityDef>,
50        /// Duration for which the ability is granted.
51        duration: GrantDuration,
52    },
53    /// Add power when the target's level is at least a threshold.
54    AddPowerIfTargetLevelAtLeast {
55        /// Power magnitude to add.
56        amount: i32,
57        /// Minimum level threshold for the target.
58        min_level: u8,
59        /// Duration of the modifier.
60        duration: ModifierDuration,
61    },
62    /// Add power scaled by the target's (computed) level.
63    AddPowerByTargetLevel {
64        /// Power multiplier per level.
65        multiplier: i32,
66        /// Duration of the modifier.
67        duration: ModifierDuration,
68    },
69    /// Add power if the battle opponent's level is at least a threshold.
70    AddPowerIfBattleOpponentLevelAtLeast {
71        /// Power magnitude to add.
72        amount: i32,
73        /// Minimum level threshold for the battle opponent.
74        min_level: u8,
75        /// Duration of the modifier.
76        duration: ModifierDuration,
77    },
78    /// Add soul if the battle opponent's level is at least a threshold.
79    AddSoulIfBattleOpponentLevelAtLeast {
80        /// Soul magnitude to add.
81        amount: i32,
82        /// Minimum level threshold for the battle opponent.
83        min_level: u8,
84        /// Duration of the modifier.
85        duration: ModifierDuration,
86    },
87    /// Add power if the battle opponent's level matches exactly.
88    AddPowerIfBattleOpponentLevelExact {
89        /// Power magnitude to add.
90        amount: i32,
91        /// Required battle opponent level.
92        level: u8,
93        /// Duration of the modifier.
94        duration: ModifierDuration,
95    },
96    /// Add power when another attacking character matches one of the provided card ids.
97    AddPowerIfOtherAttackerMatches {
98        /// Power magnitude to add.
99        amount: i32,
100        /// Duration of the modifier.
101        duration: ModifierDuration,
102        /// Allowed attacker card ids to match against.
103        attacker_card_ids: Vec<CardId>,
104    },
105    /// Add soul while this card occupies the middle center-stage position.
106    AddSoulIfMiddleCenter {
107        /// Soul magnitude to add.
108        amount: i32,
109    },
110    /// Add soul to the character facing the source card.
111    FacingOpponentAddSoul {
112        /// Soul magnitude to add.
113        amount: i32,
114    },
115    /// Add a modifier to the character facing the source card.
116    FacingOpponentAddModifier {
117        /// Modifier kind to apply.
118        kind: ModifierKind,
119        /// Signed magnitude of the modifier.
120        magnitude: i32,
121        /// Duration of the modifier.
122        duration: ModifierDuration,
123    },
124    /// Add a modifier to the source card if it is facing a matching opponent.
125    SelfAddModifierIfFacingOpponent {
126        /// Modifier kind to apply.
127        kind: ModifierKind,
128        /// Signed magnitude of the modifier.
129        magnitude: i32,
130        /// Duration of the modifier.
131        duration: ModifierDuration,
132        /// Optional maximum opponent level allowed.
133        max_level: Option<u8>,
134        /// Optional maximum opponent cost allowed.
135        max_cost: Option<u8>,
136        /// If true, require opponent level to exceed the source level.
137        level_gt_source_level: bool,
138    },
139    /// Add a modifier to targets when a conditional context is satisfied.
140    ConditionalAddModifier {
141        /// Modifier kind to apply.
142        kind: ModifierKind,
143        /// Signed magnitude of the modifier.
144        magnitude: i32,
145        /// Duration of the modifier.
146        duration: ModifierDuration,
147        /// Optional turn condition.
148        turn: Option<ConditionTurn>,
149        /// Optional zone-count condition.
150        zone_count: Option<ZoneCountCondition>,
151        /// Whether the source must have at least one marker.
152        require_source_marker: bool,
153        /// If true, scale magnitude by the number of markers under the source.
154        per_source_marker: bool,
155        /// If true, scale magnitude by the zone-count value.
156        per_zone_count: bool,
157        /// If true, skip applying the modifier to the source card itself.
158        exclude_source: bool,
159    },
160    /// Move target to hand.
161    MoveToHand,
162    /// Move target to waiting room.
163    MoveToWaitingRoom,
164    /// Move target to stock.
165    MoveToStock,
166    /// Move target to clock.
167    MoveToClock,
168    /// Move target to memory.
169    MoveToMemory,
170    /// Move target to bottom of deck.
171    MoveToDeckBottom,
172    /// Move a waiting-room card to the source card's stage slot.
173    MoveWaitingRoomCardToSourceSlot,
174    /// Return all cards from waiting room to deck, then shuffle.
175    RecycleWaitingRoomToDeckShuffle,
176    /// Move all stock to waiting room, then refill stock from deck top by the same count.
177    ResetStockFromDeckTop {
178        /// Side whose stock is reset.
179        target: TargetSide,
180    },
181    /// Move target card under the source card as a marker.
182    MoveToMarker,
183    /// Move the top card of your deck under this card as a marker.
184    MoveTopDeckToMarker,
185    /// Heal (move top clock to waiting room).
186    Heal,
187    /// Heal only if the source card was played from hand this turn and remains on stage.
188    HealIfSourcePlayedFromHandThisTurn,
189    /// Rest the target.
190    RestTarget,
191    /// Stand the target.
192    StandTarget,
193    /// Stock charge by count.
194    StockCharge {
195        /// Number of cards to stock-charge.
196        count: u8,
197    },
198    /// Mill top cards from deck.
199    MillTop {
200        /// Side whose deck is milled.
201        target: TargetSide,
202        /// Number of cards to mill.
203        count: u8,
204    },
205    /// Move target to a specific stage slot.
206    MoveStageSlot {
207        /// Stage slot index.
208        slot: u8,
209    },
210    /// Move the source card to the first open center-stage slot.
211    MoveThisToOpenCenter {
212        /// Whether the source must currently be facing an opponent.
213        require_facing: bool,
214    },
215    /// Move the source card to the first open back-stage slot.
216    MoveThisToOpenBack,
217    /// Swap two stage slots.
218    SwapStageSlots,
219    /// Random discard from hand.
220    RandomDiscardFromHand {
221        /// Side whose hand is discarded from.
222        target: TargetSide,
223        /// Number of cards to discard.
224        count: u8,
225    },
226    /// Random mill from deck.
227    RandomMill {
228        /// Side whose deck is milled.
229        target: TargetSide,
230        /// Number of cards to mill.
231        count: u8,
232    },
233    /// Reveal the top of a zone.
234    RevealZoneTop {
235        /// Side whose zone is revealed.
236        target: TargetSide,
237        /// Zone to reveal cards from.
238        zone: TargetZone,
239        /// Number of cards to reveal.
240        count: u8,
241        /// Reveal visibility audience.
242        audience: RevealAudience,
243    },
244    /// Reveal the top card of your deck; if its level is at least `min_level`,
245    /// move this card to hand. (Climax is treated as level 0.)
246    RevealTopIfLevelAtLeastMoveThisToHand {
247        /// Minimum level threshold for success.
248        min_level: u8,
249    },
250    /// Reveal the top card of your deck; if its level is at least `min_level`,
251    /// rest this card. (Climax is treated as level 0.)
252    RevealTopIfLevelAtLeastRestThis {
253        /// Minimum level threshold for success.
254        min_level: u8,
255    },
256    /// Reveal the top card of your deck; if its level is at least `min_level`,
257    /// move that revealed card to stock. (Climax is treated as level 0.)
258    RevealTopIfLevelAtLeastMoveTopToStock {
259        /// Minimum level threshold for success.
260        min_level: u8,
261    },
262    /// Look at the top `count` cards of your deck and reorder them on top.
263    LookTopDeckReorder {
264        /// Number of cards to look at and reorder.
265        count: u8,
266    },
267    /// Look at the top card and either leave it on top or move it to waiting room.
268    LookTopCardTopOrWaitingRoom,
269    /// Look at the top card and either leave it on top or move it to deck bottom.
270    LookTopCardTopOrBottom,
271    /// Look at top cards, move up to `choose_count` cards with level at least `min_level` to hand,
272    /// and send the rest to waiting room.
273    SearchTopDeckToHandLevelAtLeastMillRest {
274        /// Number of cards to look at from the top of deck.
275        look_count: u8,
276        /// Maximum number of cards to move to hand.
277        choose_count: u8,
278        /// Minimum level threshold for cards eligible to move to hand.
279        min_level: u8,
280    },
281    /// Reveal top deck card, then salvage up to `count` waiting-room characters with level at most
282    /// the revealed card's level.
283    RevealTopAndSalvageByRevealedLevel {
284        /// Number of cards to salvage.
285        count: u8,
286        /// Level to treat a climax as during level comparisons.
287        climax_level: u8,
288    },
289    /// Move the trigger card to hand.
290    MoveTriggerCardToHand,
291    /// Move the trigger card to stock.
292    MoveTriggerCardToStock,
293    /// Change controller of a card.
294    ChangeController {
295        /// New controller side.
296        new_controller: TargetSide,
297    },
298    /// Standby trigger resolution (place a character from waiting room onto stage).
299    Standby {
300        /// Destination stage slot index.
301        target_slot: u8,
302    },
303    /// Treasure trigger resolution (optionally take the stock).
304    TreasureStock {
305        /// Whether to take stock when resolving the treasure trigger.
306        take_stock: bool,
307    },
308    /// Modify pending attack damage by a delta.
309    ModifyPendingAttackDamage {
310        /// Signed delta to apply.
311        delta: i32,
312    },
313    /// Enable shot damage for the current attack.
314    EnableShotDamage {
315        /// Shot damage amount.
316        amount: u8,
317    },
318    /// Resolve a trigger icon effect directly.
319    TriggerIcon {
320        /// Trigger icon to resolve.
321        icon: TriggerIcon,
322    },
323    /// Reveal the top of the deck.
324    RevealDeckTop {
325        /// Number of cards to reveal.
326        count: u8,
327        /// Reveal visibility audience.
328        audience: RevealAudience,
329    },
330    /// Brainstorm resolver (reveal/mill then payoff per climax).
331    Brainstorm {
332        /// Number of cards to reveal/mill.
333        reveal_count: u8,
334        /// Payoff multiplier per climax revealed.
335        per_climax: u8,
336        /// Brainstorm payoff mode.
337        mode: BrainstormMode,
338    },
339    /// Optional brainstorm choice hook for draw-mode resolution.
340    BrainstormDrawChoice,
341    /// Set the total trigger checks to perform this attack's trigger step.
342    SetTriggerCheckCount {
343        /// Trigger check count to use.
344        count: u8,
345    },
346    /// Rest the source card if no other rested center-stage character is present.
347    RestThisIfNoOtherRestCenter,
348    /// Reverse this card's current battle opponent when a condition is met.
349    BattleOpponentReverseIf {
350        /// Optional maximum opponent level allowed.
351        max_level: Option<u8>,
352        /// Optional maximum opponent cost allowed.
353        max_cost: Option<u8>,
354        /// If true, require this card's level to exceed opponent level.
355        level_gt_opponent_level: bool,
356    },
357    /// Move this card's current battle opponent to the bottom of deck when a condition is met.
358    BattleOpponentMoveToDeckBottomIf {
359        /// Optional maximum opponent level allowed.
360        max_level: Option<u8>,
361        /// Optional maximum opponent cost allowed.
362        max_cost: Option<u8>,
363        /// If true, require this card's level to exceed opponent level.
364        level_gt_opponent_level: bool,
365    },
366    /// Move this card's current battle opponent to stock, then move the bottom stock card to
367    /// waiting room.
368    BattleOpponentMoveToStockThenBottomStockToWaitingRoomIf {
369        /// Optional maximum opponent level allowed.
370        max_level: Option<u8>,
371        /// Optional maximum opponent cost allowed.
372        max_cost: Option<u8>,
373        /// If true, require this card's level to exceed opponent level.
374        level_gt_opponent_level: bool,
375    },
376    /// Move top opponent clock to waiting room, then move this card's current battle opponent to
377    /// clock when a condition is met.
378    BattleOpponentMoveToClockAfterClockTopToWaitingRoomIf {
379        /// Optional maximum opponent level allowed.
380        max_level: Option<u8>,
381        /// Optional maximum opponent cost allowed.
382        max_cost: Option<u8>,
383        /// If true, require this card's level to exceed opponent level.
384        level_gt_opponent_level: bool,
385    },
386    /// Move this card's current battle opponent to memory when a condition is met.
387    BattleOpponentMoveToMemoryIf {
388        /// Optional maximum opponent level allowed.
389        max_level: Option<u8>,
390        /// Optional maximum opponent cost allowed.
391        max_cost: Option<u8>,
392        /// If true, require this card's level to exceed opponent level.
393        level_gt_opponent_level: bool,
394    },
395    /// Move this card's current battle opponent to clock when a condition is met.
396    BattleOpponentMoveToClockIf {
397        /// Optional maximum opponent level allowed.
398        max_level: Option<u8>,
399        /// Optional maximum opponent cost allowed.
400        max_cost: Option<u8>,
401        /// If true, require this card's level to exceed opponent level.
402        level_gt_opponent_level: bool,
403    },
404    /// Generalized battle-opponent movement effect.
405    BattleOpponentMove {
406        /// Destination zone for the battle opponent.
407        destination: BattleOpponentMoveDestination,
408        /// Optional prelude action applied before the destination move.
409        prelude: Option<BattleOpponentMovePreludeAction>,
410        /// Optional maximum opponent level allowed.
411        max_level: Option<u8>,
412        /// Optional maximum opponent cost allowed.
413        max_cost: Option<u8>,
414        /// If true, require this card's level to exceed opponent level.
415        level_gt_opponent_level: bool,
416    },
417    /// Put the top card of your deck into your stock if this card's battle opponent meets a level
418    /// threshold.
419    BattleOpponentTopDeckToStockIf {
420        /// Minimum opponent level threshold for success.
421        min_level: u8,
422    },
423    /// Prevent a player from using AUTO Encore for the rest of the turn.
424    CannotUseAutoEncoreForPlayer {
425        /// Side to apply the restriction to.
426        target: TargetSide,
427    },
428    /// Counter backup (power).
429    CounterBackup {
430        /// Power magnitude to add.
431        power: i32,
432    },
433    /// Counter damage reduction.
434    CounterDamageReduce {
435        /// Damage reduction magnitude.
436        amount: u8,
437    },
438    /// Counter damage cancel.
439    CounterDamageCancel,
440    /// Set terminal game outcome immediately.
441    SetTerminalOutcome {
442        /// Terminal outcome to set.
443        outcome: TerminalOutcomeSpec,
444    },
445    /// Apply a turn-scoped rule-action override.
446    ApplyRuleOverride {
447        /// Rule override kind to apply.
448        kind: RuleOverrideKind,
449    },
450}
451
452impl EffectKind {
453    /// Whether this effect expects a target to be selected.
454    pub fn expects_target(&self) -> bool {
455        matches!(
456            self,
457            EffectKind::AddModifier { .. }
458                | EffectKind::GrantAbilityDef { .. }
459                | EffectKind::AddPowerIfTargetLevelAtLeast { .. }
460                | EffectKind::AddPowerByTargetLevel { .. }
461                | EffectKind::ConditionalAddModifier { .. }
462                | EffectKind::MoveToHand
463                | EffectKind::MoveToWaitingRoom
464                | EffectKind::MoveToStock
465                | EffectKind::MoveToClock
466                | EffectKind::MoveToMemory
467                | EffectKind::MoveToDeckBottom
468                | EffectKind::MoveWaitingRoomCardToSourceSlot
469                | EffectKind::MoveToMarker
470                | EffectKind::Heal
471                | EffectKind::RestTarget
472                | EffectKind::StandTarget
473                | EffectKind::MoveStageSlot { .. }
474                | EffectKind::SwapStageSlots
475                | EffectKind::ChangeController { .. }
476                | EffectKind::Standby { .. }
477                | EffectKind::LookTopDeckReorder { .. }
478                | EffectKind::LookTopCardTopOrWaitingRoom
479                | EffectKind::LookTopCardTopOrBottom
480        )
481    }
482
483    /// Whether this effect can target a card in the given zone.
484    pub fn requires_target_zone(&self, zone: TargetZone) -> bool {
485        match self {
486            EffectKind::MoveToHand => {
487                matches!(
488                    zone,
489                    TargetZone::Stage | TargetZone::WaitingRoom | TargetZone::DeckTop
490                )
491            }
492            EffectKind::MoveToWaitingRoom => matches!(
493                zone,
494                TargetZone::Stage
495                    | TargetZone::Hand
496                    | TargetZone::DeckTop
497                    | TargetZone::Clock
498                    | TargetZone::Level
499                    | TargetZone::Stock
500                    | TargetZone::Memory
501                    | TargetZone::Climax
502                    | TargetZone::Resolution
503                    | TargetZone::WaitingRoom
504            ),
505            EffectKind::MoveToStock => matches!(
506                zone,
507                TargetZone::Stage
508                    | TargetZone::Hand
509                    | TargetZone::DeckTop
510                    | TargetZone::Clock
511                    | TargetZone::Level
512                    | TargetZone::WaitingRoom
513                    | TargetZone::Memory
514                    | TargetZone::Climax
515                    | TargetZone::Resolution
516                    | TargetZone::Stock
517            ),
518            EffectKind::MoveToClock => matches!(
519                zone,
520                TargetZone::Stage
521                    | TargetZone::Hand
522                    | TargetZone::DeckTop
523                    | TargetZone::WaitingRoom
524                    | TargetZone::Resolution
525                    | TargetZone::Clock
526            ),
527            EffectKind::MoveToMemory => matches!(
528                zone,
529                TargetZone::Stage
530                    | TargetZone::Hand
531                    | TargetZone::DeckTop
532                    | TargetZone::Clock
533                    | TargetZone::Level
534                    | TargetZone::Stock
535                    | TargetZone::WaitingRoom
536                    | TargetZone::Climax
537                    | TargetZone::Resolution
538                    | TargetZone::Memory
539            ),
540            EffectKind::MoveToDeckBottom => {
541                matches!(zone, TargetZone::Stage | TargetZone::DeckTop)
542            }
543            EffectKind::MoveWaitingRoomCardToSourceSlot => matches!(zone, TargetZone::WaitingRoom),
544            EffectKind::Heal => matches!(zone, TargetZone::Clock),
545            EffectKind::ChangeController { .. } => matches!(zone, TargetZone::Stage),
546            EffectKind::AddModifier { .. }
547            | EffectKind::GrantAbilityDef { .. }
548            | EffectKind::AddPowerIfTargetLevelAtLeast { .. }
549            | EffectKind::AddPowerByTargetLevel { .. }
550            | EffectKind::ConditionalAddModifier { .. } => {
551                matches!(zone, TargetZone::Stage)
552            }
553            EffectKind::MoveToMarker => matches!(zone, TargetZone::WaitingRoom),
554            EffectKind::LookTopDeckReorder { .. }
555            | EffectKind::LookTopCardTopOrWaitingRoom
556            | EffectKind::LookTopCardTopOrBottom => matches!(zone, TargetZone::DeckTop),
557            EffectKind::RestTarget
558            | EffectKind::StandTarget
559            | EffectKind::MoveStageSlot { .. }
560            | EffectKind::SwapStageSlots => matches!(zone, TargetZone::Stage),
561            EffectKind::Standby { .. } => matches!(zone, TargetZone::WaitingRoom),
562            EffectKind::RandomDiscardFromHand { .. } => matches!(zone, TargetZone::Hand),
563            EffectKind::RandomMill { .. } => matches!(zone, TargetZone::DeckTop),
564            EffectKind::RevealZoneTop {
565                zone: reveal_zone, ..
566            } => zone == *reveal_zone,
567            _ => true,
568        }
569    }
570}