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}