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