Skip to main content

manabrew_engine/trigger/
trigger_type.rs

1//! `TriggerType` enum + trigger script parser. Mirrors Java
2//! `TriggerType.java` and `TriggerHandler.parseTrigger`.
3//!
4//! `EnumString` + `#[strum(ascii_case_insensitive)]` derives a case-insensitive
5//! `FromStr` keyed on the variant name. Java aliases that aren't just a case
6//! variation get explicit `#[strum(serialize = "...")]` entries.
7
8use serde::{Deserialize, Serialize};
9use strum_macros::EnumString;
10
11use crate::trigger::trigger::{parse_trigger, Trigger};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default, EnumString)]
14#[strum(ascii_case_insensitive)]
15pub enum TriggerType {
16    #[default]
17    ChangesZone,
18    Phase,
19    SpellCast,
20    Attacks,
21    DamageDone,
22    /// Two creatures fought each other (SP$ Fight).
23    Fight,
24    /// One or more creatures fought (batched).
25    FightOnce,
26    /// A card was discarded (SP$ Discard).
27    Discarded,
28    /// A spell was countered (SP$ Counter).
29    Countered,
30    // ── New trigger types (issue #19) ──
31    /// A creature blocks an attacker.
32    Blocks,
33    /// An attacker is blocked by at least one creature.
34    AttackerBlocked,
35    /// An attacker is not blocked.
36    AttackerUnblocked,
37    /// A player gained life.
38    LifeGained,
39    /// A player lost life.
40    LifeLost,
41    /// A counter was added to a permanent.
42    CounterAdded,
43    /// A counter was removed from a permanent.
44    CounterRemoved,
45    /// A permanent was sacrificed.
46    Sacrificed,
47    /// A card was drawn.
48    Drawn,
49    /// A card was milled (library → graveyard).
50    Milled,
51    /// A permanent was tapped.
52    Taps,
53    /// A permanent was untapped.
54    Untaps,
55    /// A DFC was transformed.
56    Transformed,
57    /// An aura/equipment was attached.
58    Attached,
59    /// An aura/equipment was unattached.
60    Unattached,
61    /// A land was played.
62    LandPlayed,
63    /// A permanent became the target of a spell or ability.
64    BecomesTarget,
65    /// A permanent was tapped for mana.
66    TapsForMana,
67    /// An activated ability was activated.
68    AbilityActivated,
69    /// A creature explored.
70    Explored,
71    /// A creature became monstrous.
72    BecomeMonstrous,
73    /// A player became the monarch.
74    BecomeMonarch,
75    /// Damage was dealt to a player/creature for the first time this turn.
76    DamageDealtOnce,
77    /// A permanent was destroyed.
78    Destroyed,
79    /// A card was exiled.
80    Exiled,
81    /// A token was created.
82    TokenCreated,
83    /// A spell was copied (Storm, Replicate, etc.) — used by Magecraft.
84    SpellCopied,
85    /// A player took the initiative.
86    TakeInitiative,
87    /// A permanent phased out.
88    PhasedOut,
89    /// A permanent phased in.
90    PhasedIn,
91    // ── New trigger types (issue #54) ──
92    /// All attackers declared at once (batch).
93    AttackersDeclared,
94    /// All blockers declared at once (batch).
95    BlockersDeclared,
96    /// A card changed zones (batch/all variant).
97    ChangesZoneAll,
98    /// A permanent changed controller.
99    ChangesController,
100    /// A turn began.
101    #[strum(serialize = "TurnBegin", serialize = "newturn")]
102    TurnBegin,
103    /// Damage done once (first-time batch).
104    DamageDoneOnce,
105    /// Any spell cast (all players).
106    SpellCastAll,
107    /// Alias of SpellCast in Java TriggerType.
108    AbilityCast,
109    /// Alias of SpellCast in Java TriggerType.
110    SpellAbilityCast,
111    /// Alias of SpellCast in Java TriggerType.
112    SpellAbilityCopy,
113    /// Alias of SpellCast in Java TriggerType.
114    SpellCastOrCopy,
115    /// Alias of SpellCast in Java TriggerType.
116    SpellCopy,
117    /// Any player lost life (all players).
118    LifeLostAll,
119    /// Counter added once (batch).
120    CounterAddedOnce,
121    /// Any card discarded (all players).
122    DiscardedAll,
123    /// A permanent sacrificed once (batch).
124    SacrificedOnce,
125    /// A card was cycled.
126    #[strum(serialize = "Cycled", serialize = "cycling")]
127    Cycled,
128    /// Alias of PhasedIn in Java TriggerType.
129    PhaseIn,
130    /// Alias of PhasedOut in Java TriggerType.
131    PhaseOut,
132    /// Always fires (every phase).
133    Always,
134    /// Fires immediately when registered.
135    Immediate,
136    /// A player surveilled.
137    Surveil,
138    /// A player scried.
139    Scry,
140    /// A card was foretold.
141    #[strum(serialize = "Foretell", serialize = "foretold")]
142    Foretell,
143    /// A player searched their library.
144    SearchedLibrary,
145    /// A player shuffled their library.
146    Shuffled,
147    /// Mana was added to a player's pool.
148    ManaAdded,
149    /// A token was created (batch).
150    TokenCreatedOnce,
151    /// Any permanent tapped (all).
152    TapAll,
153    /// Any permanent untapped (all).
154    UntapAll,
155    /// A permanent became a target (batch).
156    BecomesTargetOnce,
157    /// An attacker was blocked by a specific creature.
158    AttackerBlockedByCreature,
159    /// An attacker was blocked (batch).
160    AttackerBlockedOnce,
161    /// An attacker was unblocked (batch).
162    AttackerUnblockedOnce,
163    /// Any spell cast (batch).
164    SpellCastOnce,
165    /// A spell of a specific type was cast.
166    SpellCastOfType,
167    /// Damage dealt (all instances).
168    DamageAll,
169    /// Damage was prevented (batch).
170    DamagePreventedOnce,
171    /// Excess damage dealt.
172    ExcessDamage,
173    /// A counter was removed (batch).
174    CounterRemovedOnce,
175    /// A creature was exerted.
176    Exerted,
177    /// A creature adapted.
178    Adapt,
179    /// A creature became renowned.
180    BecomeRenowned,
181    /// A creature evolved.
182    Evolved,
183    /// A player investigated.
184    Investigated,
185    /// A player proliferated.
186    Proliferate,
187    /// A player completed a dungeon.
188    CompletedDungeon,
189    /// Alias of CompletedDungeon in Java TriggerType.
190    DungeonCompleted,
191    /// A player committed a crime.
192    CommitCrime,
193    /// The Ring tempted a player.
194    RingTemptsYou,
195    /// A player paid life.
196    PayLife,
197    /// Echo was paid (or not) for a permanent.
198    PayEcho,
199    /// A new game started.
200    NewGame,
201    /// Day/night changed.
202    DayTimeChanges,
203    /// A class level was gained.
204    ClassLevelGained,
205    /// A card became plotted.
206    BecomesPlotted,
207    /// A player lost the game.
208    LosesGame,
209    /// A player discovered.
210    Discover,
211    /// Elementalbend event.
212    Elementalbend,
213    /// Alias of Elementalbend in Java TriggerType.
214    #[strum(disabled)]
215    ElementalBend,
216    /// Alias of Elementalbend in Java TriggerType.
217    Airbend,
218    /// Alias of Elementalbend in Java TriggerType.
219    Earthbend,
220    /// Alias of Elementalbend in Java TriggerType.
221    Firebend,
222    /// Alias of Elementalbend in Java TriggerType.
223    Waterbend,
224    /// Multiple cards phased out.
225    PhaseOutAll,
226    /// Planar die was rolled.
227    PlanarDice,
228    /// A vote resolved.
229    Vote,
230    /// A gift was given.
231    GiveGift,
232    /// A player visited an attraction.
233    VisitAttraction,
234    /// A room was entered.
235    EnteredRoom,
236    /// Alias of EnteredRoom in Java TriggerType.
237    RoomEntered,
238    /// Multiple cards were milled.
239    MilledAll,
240    /// Cards were milled once for a player.
241    MilledOnce,
242    /// A scheme was abandoned.
243    Abandoned,
244    /// Manifest dread event.
245    ManifestDread,
246    /// A card specialized.
247    Specializes,
248    /// A card trained.
249    Trains,
250    /// A card devoured.
251    Devoured,
252    /// Cards were conjured in batch.
253    ConjureAll,
254    /// Cards were sought in batch.
255    SeekAll,
256    /// A card became crewed.
257    BecomesCrewed,
258    /// A card was championed.
259    Championed,
260    /// A clash happened.
261    Clashed,
262    /// A card was mentored.
263    Mentored,
264    /// A room/door became fully unlocked.
265    FullyUnlock,
266    /// A spell ability resolved.
267    AbilityResolves,
268    /// A spell ability triggered another trigger.
269    AbilityTriggered,
270    /// A door was unlocked.
271    UnlockDoor,
272    /// Counter-added batch event across multiple entities.
273    CounterAddedAll,
274    /// Counter-added batch event for a single object/source pair.
275    CounterPlayerAddedAll,
276    /// Counter-added batch event grouped by counter type.
277    CounterTypeAddedAll,
278    /// Crew/Saddle/Station grouped trigger.
279    Crewed,
280    /// Saddle alias for Crewed trigger class.
281    Saddled,
282    /// Station alias for Crewed trigger class.
283    Stationed,
284    /// Damage done once grouped by controller.
285    DamageDoneOnceByController,
286    /// Excess damage dealt to multiple targets.
287    ExcessDamageAll,
288    /// A player collected evidence.
289    CollectEvidence,
290    /// A player foraged.
291    Forage,
292    /// A creature enlisted another creature.
293    Enlisted,
294    /// A player flipped a coin.
295    FlippedCoin,
296    /// A player rolled a die.
297    RolledDie,
298    /// A player completed a die-roll action.
299    RolledDieOnce,
300    /// Mana was expended (cumulative per-turn tracking for Expend mechanic).
301    ManaExpend,
302    /// A face-down card was turned face-up (Morph/Megamorph/Manifest).
303    TurnFaceUp,
304    /// Alias of Explored in Java TriggerType.
305    Explores,
306    /// A creature was exploited (Exploit keyword).
307    Exploited,
308    /// Cumulative upkeep was paid (or not). Mirrors Java TriggerType.PayCumulativeUpkeep.
309    PayCumulativeUpkeep,
310    // ── Niche trigger types (Planechase, Unfinity, Ikoria, etc.) ──
311    /// Chaos ensues (Planechase chaos die result).
312    ChaosEnsues,
313    /// A player planeswalked to a new plane (Planechase).
314    Planeswalk,
315    /// Alias of Planeswalk in Java TriggerType.
316    PlaneswalkedFrom,
317    /// Alias of Planeswalk in Java TriggerType.
318    PlaneswalkedTo,
319    /// A player claimed the prize from an attraction (Unfinity).
320    ClaimPrize,
321    /// A player advanced their crank counter (Unstable).
322    CrankAdvanced,
323    /// Alias of CrankAdvanced in Java TriggerType.
324    CrankContraption,
325    /// A Case enchantment was solved (MKM).
326    CaseSolved,
327    /// A creature became saddled (OTJ).
328    BecomesSaddled,
329    /// A scheme was set in motion (Archenemy).
330    SetInMotion,
331    /// A creature mutated onto another (IKO).
332    Mutates,
333    /// Alias of Unattached in Java TriggerType.
334    Unattach,
335    /// Alias of TakeInitiative in Java TriggerType.
336    TakesInitiative,
337    /// Alias of AttackersDeclared in Java TriggerType.
338    AttackersDeclaredOneTarget,
339}
340
341impl TriggerType {
342    /// Returns the string name of this trigger type, matching the Java TriggerType enum name.
343    pub fn name(&self) -> &'static str {
344        match self {
345            TriggerType::ChangesZone => "ChangesZone",
346            TriggerType::Phase => "Phase",
347            TriggerType::SpellCast => "SpellCast",
348            TriggerType::Attacks => "Attacks",
349            TriggerType::DamageDone => "DamageDone",
350            TriggerType::Fight => "Fight",
351            TriggerType::FightOnce => "FightOnce",
352            TriggerType::Discarded => "Discarded",
353            TriggerType::Countered => "Countered",
354            TriggerType::Blocks => "Blocks",
355            TriggerType::AttackerBlocked => "AttackerBlocked",
356            TriggerType::AttackerUnblocked => "AttackerUnblocked",
357            TriggerType::LifeGained => "LifeGained",
358            TriggerType::LifeLost => "LifeLost",
359            TriggerType::CounterAdded => "CounterAdded",
360            TriggerType::CounterRemoved => "CounterRemoved",
361            TriggerType::Sacrificed => "Sacrificed",
362            TriggerType::Drawn => "Drawn",
363            TriggerType::Milled => "Milled",
364            TriggerType::Taps => "Taps",
365            TriggerType::Untaps => "Untaps",
366            TriggerType::Transformed => "Transformed",
367            TriggerType::Attached => "Attached",
368            TriggerType::Unattached => "Unattached",
369            TriggerType::LandPlayed => "LandPlayed",
370            TriggerType::BecomesTarget => "BecomesTarget",
371            TriggerType::TapsForMana => "TapsForMana",
372            TriggerType::AbilityActivated => "AbilityActivated",
373            TriggerType::Explored => "Explored",
374            TriggerType::BecomeMonstrous => "BecomeMonstrous",
375            TriggerType::BecomeMonarch => "BecomeMonarch",
376            TriggerType::DamageDealtOnce => "DamageDealtOnce",
377            TriggerType::Destroyed => "Destroyed",
378            TriggerType::Exiled => "Exiled",
379            TriggerType::TokenCreated => "TokenCreated",
380            TriggerType::SpellCopied => "SpellCopied",
381            TriggerType::TakeInitiative => "TakeInitiative",
382            TriggerType::PhasedOut => "PhasedOut",
383            TriggerType::PhasedIn => "PhasedIn",
384            TriggerType::AttackersDeclared => "AttackersDeclared",
385            TriggerType::BlockersDeclared => "BlockersDeclared",
386            TriggerType::ChangesZoneAll => "ChangesZoneAll",
387            TriggerType::ChangesController => "ChangesController",
388            TriggerType::TurnBegin => "TurnBegin",
389            TriggerType::DamageDoneOnce => "DamageDoneOnce",
390            TriggerType::SpellCastAll => "SpellCastAll",
391            TriggerType::AbilityCast => "AbilityCast",
392            TriggerType::SpellAbilityCast => "SpellAbilityCast",
393            TriggerType::SpellAbilityCopy => "SpellAbilityCopy",
394            TriggerType::SpellCastOrCopy => "SpellCastOrCopy",
395            TriggerType::SpellCopy => "SpellCopy",
396            TriggerType::LifeLostAll => "LifeLostAll",
397            TriggerType::CounterAddedOnce => "CounterAddedOnce",
398            TriggerType::DiscardedAll => "DiscardedAll",
399            TriggerType::SacrificedOnce => "SacrificedOnce",
400            TriggerType::Cycled => "Cycled",
401            TriggerType::PhaseIn => "PhaseIn",
402            TriggerType::PhaseOut => "PhaseOut",
403            TriggerType::Always => "Always",
404            TriggerType::Immediate => "Immediate",
405            TriggerType::Surveil => "Surveil",
406            TriggerType::Scry => "Scry",
407            TriggerType::Foretell => "Foretell",
408            TriggerType::SearchedLibrary => "SearchedLibrary",
409            TriggerType::Shuffled => "Shuffled",
410            TriggerType::ManaAdded => "ManaAdded",
411            TriggerType::TokenCreatedOnce => "TokenCreatedOnce",
412            TriggerType::TapAll => "TapAll",
413            TriggerType::UntapAll => "UntapAll",
414            TriggerType::BecomesTargetOnce => "BecomesTargetOnce",
415            TriggerType::AttackerBlockedByCreature => "AttackerBlockedByCreature",
416            TriggerType::AttackerBlockedOnce => "AttackerBlockedOnce",
417            TriggerType::AttackerUnblockedOnce => "AttackerUnblockedOnce",
418            TriggerType::SpellCastOnce => "SpellCastOnce",
419            TriggerType::SpellCastOfType => "SpellCastOfType",
420            TriggerType::DamageAll => "DamageAll",
421            TriggerType::DamagePreventedOnce => "DamagePreventedOnce",
422            TriggerType::ExcessDamage => "ExcessDamage",
423            TriggerType::CounterRemovedOnce => "CounterRemovedOnce",
424            TriggerType::Exerted => "Exerted",
425            TriggerType::Adapt => "Adapt",
426            TriggerType::BecomeRenowned => "BecomeRenowned",
427            TriggerType::Evolved => "Evolved",
428            TriggerType::Investigated => "Investigated",
429            TriggerType::Proliferate => "Proliferate",
430            TriggerType::CompletedDungeon => "CompletedDungeon",
431            TriggerType::DungeonCompleted => "DungeonCompleted",
432            TriggerType::CommitCrime => "CommitCrime",
433            TriggerType::RingTemptsYou => "RingTemptsYou",
434            TriggerType::PayLife => "PayLife",
435            TriggerType::PayEcho => "PayEcho",
436            TriggerType::NewGame => "NewGame",
437            TriggerType::DayTimeChanges => "DayTimeChanges",
438            TriggerType::ClassLevelGained => "ClassLevelGained",
439            TriggerType::BecomesPlotted => "BecomesPlotted",
440            TriggerType::LosesGame => "LosesGame",
441            TriggerType::Discover => "Discover",
442            TriggerType::Elementalbend => "Elementalbend",
443            TriggerType::ElementalBend => "ElementalBend",
444            TriggerType::Airbend => "Airbend",
445            TriggerType::Earthbend => "Earthbend",
446            TriggerType::Firebend => "Firebend",
447            TriggerType::Waterbend => "Waterbend",
448            TriggerType::PhaseOutAll => "PhaseOutAll",
449            TriggerType::PlanarDice => "PlanarDice",
450            TriggerType::Vote => "Vote",
451            TriggerType::GiveGift => "GiveGift",
452            TriggerType::VisitAttraction => "VisitAttraction",
453            TriggerType::EnteredRoom => "EnteredRoom",
454            TriggerType::RoomEntered => "RoomEntered",
455            TriggerType::MilledAll => "MilledAll",
456            TriggerType::MilledOnce => "MilledOnce",
457            TriggerType::Abandoned => "Abandoned",
458            TriggerType::ManifestDread => "ManifestDread",
459            TriggerType::Specializes => "Specializes",
460            TriggerType::Trains => "Trains",
461            TriggerType::Devoured => "Devoured",
462            TriggerType::ConjureAll => "ConjureAll",
463            TriggerType::SeekAll => "SeekAll",
464            TriggerType::BecomesCrewed => "BecomesCrewed",
465            TriggerType::Championed => "Championed",
466            TriggerType::Clashed => "Clashed",
467            TriggerType::Mentored => "Mentored",
468            TriggerType::FullyUnlock => "FullyUnlock",
469            TriggerType::AbilityResolves => "AbilityResolves",
470            TriggerType::AbilityTriggered => "AbilityTriggered",
471            TriggerType::UnlockDoor => "UnlockDoor",
472            TriggerType::CounterAddedAll => "CounterAddedAll",
473            TriggerType::CounterPlayerAddedAll => "CounterPlayerAddedAll",
474            TriggerType::CounterTypeAddedAll => "CounterTypeAddedAll",
475            TriggerType::Crewed => "Crewed",
476            TriggerType::Saddled => "Saddled",
477            TriggerType::Stationed => "Stationed",
478            TriggerType::DamageDoneOnceByController => "DamageDoneOnceByController",
479            TriggerType::ExcessDamageAll => "ExcessDamageAll",
480            TriggerType::CollectEvidence => "CollectEvidence",
481            TriggerType::Forage => "Forage",
482            TriggerType::Enlisted => "Enlisted",
483            TriggerType::FlippedCoin => "FlippedCoin",
484            TriggerType::RolledDie => "RolledDie",
485            TriggerType::RolledDieOnce => "RolledDieOnce",
486            TriggerType::ManaExpend => "ManaExpend",
487            TriggerType::TurnFaceUp => "TurnFaceUp",
488            TriggerType::Explores => "Explores",
489            TriggerType::Exploited => "Exploited",
490            TriggerType::PayCumulativeUpkeep => "PayCumulativeUpkeep",
491            TriggerType::ChaosEnsues => "ChaosEnsues",
492            TriggerType::Planeswalk => "Planeswalk",
493            TriggerType::PlaneswalkedFrom => "PlaneswalkedFrom",
494            TriggerType::PlaneswalkedTo => "PlaneswalkedTo",
495            TriggerType::ClaimPrize => "ClaimPrize",
496            TriggerType::CrankAdvanced => "CrankAdvanced",
497            TriggerType::CrankContraption => "CrankContraption",
498            TriggerType::CaseSolved => "CaseSolved",
499            TriggerType::BecomesSaddled => "BecomesSaddled",
500            TriggerType::SetInMotion => "SetInMotion",
501            TriggerType::Mutates => "Mutates",
502            TriggerType::Unattach => "Unattach",
503            TriggerType::TakesInitiative => "TakesInitiative",
504            TriggerType::AttackersDeclaredOneTarget => "AttackersDeclaredOneTarget",
505        }
506    }
507}
508/// Case-insensitive TriggerType parser (Java `smartValueOf` parity).
509///
510/// Delegates to the `EnumString` derive on `TriggerType` — matches the variant
511/// name case-insensitively, plus the explicit Java aliases
512/// (`newturn`, `cycling`, `foretold`). Returns `None` for unknown values.
513pub fn smart_value_of(value: &str) -> Option<TriggerType> {
514    value.trim().parse().ok()
515}
516
517/// Java parity helper that parses a trigger script into a concrete Trigger.
518pub fn create_trigger(raw_trigger: &str, next_id: &mut u32) -> Option<Trigger> {
519    parse_trigger(raw_trigger, next_id)
520}