1use serde::{Deserialize, Serialize};
2
3use crate::db::{CardId, TriggerIcon};
4use crate::events::RevealAudience;
5use crate::state::{
6 DamageType, ModifierDuration, ModifierKind, TargetSide, TargetSpec, TargetZone,
7};
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub enum EffectSourceKind {
11 Trigger,
12 Auto,
13 Activated,
14 Continuous,
15 EventPlay,
16 Counter,
17 Replacement,
18 System,
19}
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
22pub struct EffectId {
23 pub source_kind: EffectSourceKind,
24 pub source_card: CardId,
25 pub ability_index: u8,
26 pub effect_index: u8,
27}
28
29impl EffectId {
30 pub fn new(
31 source_kind: EffectSourceKind,
32 source_card: CardId,
33 ability_index: u8,
34 effect_index: u8,
35 ) -> Self {
36 Self {
37 source_kind,
38 source_card,
39 ability_index,
40 effect_index,
41 }
42 }
43}
44
45#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
46pub struct EffectSpec {
47 pub id: EffectId,
48 pub kind: EffectKind,
49 pub target: Option<TargetSpec>,
50 pub optional: bool,
51}
52
53#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
54pub enum EffectKind {
55 Draw {
56 count: u8,
57 },
58 Damage {
59 amount: i32,
60 cancelable: bool,
61 damage_type: DamageType,
62 },
63 AddModifier {
64 kind: ModifierKind,
65 magnitude: i32,
66 duration: ModifierDuration,
67 },
68 MoveToHand,
69 MoveToWaitingRoom,
70 MoveToStock,
71 MoveToClock,
72 Heal,
73 RestTarget,
74 StandTarget,
75 StockCharge {
76 count: u8,
77 },
78 MillTop {
79 target: TargetSide,
80 count: u8,
81 },
82 MoveStageSlot {
83 slot: u8,
84 },
85 SwapStageSlots,
86 RandomDiscardFromHand {
87 target: TargetSide,
88 count: u8,
89 },
90 RandomMill {
91 target: TargetSide,
92 count: u8,
93 },
94 RevealZoneTop {
95 target: TargetSide,
96 zone: TargetZone,
97 count: u8,
98 audience: RevealAudience,
99 },
100 MoveTriggerCardToHand,
101 ChangeController {
102 new_controller: TargetSide,
103 },
104 Standby {
105 target_slot: u8,
106 },
107 TreasureStock {
108 take_stock: bool,
109 },
110 ModifyPendingAttackDamage {
111 delta: i32,
112 },
113 TriggerIcon {
114 icon: TriggerIcon,
115 },
116 RevealDeckTop {
117 count: u8,
118 audience: RevealAudience,
119 },
120 CounterBackup {
121 power: i32,
122 },
123 CounterDamageReduce {
124 amount: u8,
125 },
126 CounterDamageCancel,
127}
128
129impl EffectKind {
130 pub fn expects_target(&self) -> bool {
131 matches!(
132 self,
133 EffectKind::AddModifier { .. }
134 | EffectKind::MoveToHand
135 | EffectKind::MoveToWaitingRoom
136 | EffectKind::MoveToStock
137 | EffectKind::MoveToClock
138 | EffectKind::Heal
139 | EffectKind::RestTarget
140 | EffectKind::StandTarget
141 | EffectKind::MoveStageSlot { .. }
142 | EffectKind::SwapStageSlots
143 | EffectKind::ChangeController { .. }
144 | EffectKind::Standby { .. }
145 )
146 }
147
148 pub fn requires_target_zone(&self, zone: TargetZone) -> bool {
149 match self {
150 EffectKind::MoveToHand => {
151 matches!(
152 zone,
153 TargetZone::Stage | TargetZone::WaitingRoom | TargetZone::DeckTop
154 )
155 }
156 EffectKind::MoveToWaitingRoom => matches!(
157 zone,
158 TargetZone::Stage
159 | TargetZone::Hand
160 | TargetZone::DeckTop
161 | TargetZone::Clock
162 | TargetZone::Level
163 | TargetZone::Stock
164 | TargetZone::Memory
165 | TargetZone::Climax
166 | TargetZone::Resolution
167 | TargetZone::WaitingRoom
168 ),
169 EffectKind::MoveToStock => matches!(
170 zone,
171 TargetZone::Stage
172 | TargetZone::Hand
173 | TargetZone::DeckTop
174 | TargetZone::Clock
175 | TargetZone::Level
176 | TargetZone::WaitingRoom
177 | TargetZone::Memory
178 | TargetZone::Climax
179 | TargetZone::Resolution
180 | TargetZone::Stock
181 ),
182 EffectKind::MoveToClock => matches!(
183 zone,
184 TargetZone::Stage
185 | TargetZone::Hand
186 | TargetZone::DeckTop
187 | TargetZone::WaitingRoom
188 | TargetZone::Resolution
189 | TargetZone::Clock
190 ),
191 EffectKind::Heal => matches!(zone, TargetZone::Clock),
192 EffectKind::ChangeController { .. } => matches!(zone, TargetZone::Stage),
193 EffectKind::AddModifier { .. } => matches!(zone, TargetZone::Stage),
194 EffectKind::RestTarget
195 | EffectKind::StandTarget
196 | EffectKind::MoveStageSlot { .. }
197 | EffectKind::SwapStageSlots => matches!(zone, TargetZone::Stage),
198 EffectKind::Standby { .. } => matches!(zone, TargetZone::WaitingRoom),
199 EffectKind::RandomDiscardFromHand { .. } => matches!(zone, TargetZone::Hand),
200 EffectKind::RandomMill { .. } => matches!(zone, TargetZone::DeckTop),
201 EffectKind::RevealZoneTop {
202 zone: reveal_zone, ..
203 } => zone == *reveal_zone,
204 _ => true,
205 }
206 }
207}
208
209#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
210pub struct EffectPayload {
211 pub spec: EffectSpec,
212 pub targets: Vec<crate::state::TargetRef>,
213}
214
215#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
216pub enum ReplacementHook {
217 Damage,
218}
219
220#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
221pub enum ReplacementKind {
222 CancelDamage,
223 RedirectDamage { new_target: TargetSide },
224}
225
226#[derive(Clone, Debug, Hash, Serialize, Deserialize)]
227pub struct ReplacementSpec {
228 pub id: EffectId,
229 pub source: CardId,
230 pub hook: ReplacementHook,
231 pub kind: ReplacementKind,
232 pub priority: i16,
233 pub insertion: u32,
234}