@drmxrcy/tcg-lorcana-types 0.0.0-202602060544

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1202 @@
1
+ /**
2
+ * Condition Types for Lorcana Abilities
3
+ *
4
+ * Defines conditions that must be met for abilities to activate or apply.
5
+ * Used in:
6
+ * - Triggered abilities: "Whenever this character quests, IF you have..."
7
+ * - Static abilities: "WHILE you have a character named..."
8
+ * - Conditional effects: "If an opponent has more lore than you..."
9
+ * - Optional effects: "you MAY draw a card"
10
+ *
11
+ * @example "If you have a character named Elsa in play"
12
+ * @example "While this character has no damage"
13
+ * @example "If you used Shift to play this character"
14
+ */
15
+
16
+ import type { CardType } from "../cards/card-types";
17
+ import type {
18
+ CharacterTarget,
19
+ ComparisonOperator,
20
+ TargetZone,
21
+ } from "./target-types";
22
+
23
+ // ============================================================================
24
+ // Character/Card Existence Conditions - Strict Variants
25
+ // ============================================================================
26
+
27
+ /**
28
+ * Check if a character with a specific name exists
29
+ *
30
+ * @example "If you have a character named Elsa in play"
31
+ */
32
+ export interface HasNamedCharacterCondition {
33
+ type: "has-named-character";
34
+ /** Character must have this name - REQUIRED */
35
+ name?: string;
36
+ /** Who controls the character - REQUIRED */
37
+ controller?: "you" | "opponent" | "any";
38
+ }
39
+
40
+ /**
41
+ * Check if a character with a specific classification exists
42
+ *
43
+ * @example "If you have a Floodborn character in play"
44
+ */
45
+ export interface HasCharacterWithClassificationCondition {
46
+ type: "has-character-with-classification";
47
+ /** Classification - REQUIRED */
48
+ classification: string;
49
+ /** Who controls the character - REQUIRED */
50
+ controller: "you" | "opponent" | "any";
51
+ }
52
+
53
+ /**
54
+ * Check if a character with a specific keyword exists
55
+ *
56
+ * @example "If you have a character with Bodyguard in play"
57
+ */
58
+ export interface HasCharacterWithKeywordCondition {
59
+ type: "has-character-with-keyword";
60
+ /** Keyword - REQUIRED */
61
+ keyword?: string;
62
+ /** Who controls the character - REQUIRED */
63
+ controller?: "you" | "opponent" | "any";
64
+ }
65
+
66
+ /**
67
+ * Check if a specific count of characters exists
68
+ *
69
+ * @example "If you have 3 or more characters in play"
70
+ */
71
+ export interface HasCharacterCountCondition {
72
+ type: "has-character-count";
73
+ /** Who controls the characters - REQUIRED */
74
+ controller?: "you" | "opponent" | "any";
75
+ /** Comparison operator - REQUIRED */
76
+ comparison?: ComparisonOperator;
77
+ /** Count to compare against - REQUIRED */
78
+ count?: number;
79
+ /** Optional classification filter */
80
+ classification?: string;
81
+ /** Optional keyword filter */
82
+ keyword?: string;
83
+ }
84
+
85
+ /**
86
+ * Combined type for all character existence conditions
87
+ */
88
+ export type HasCharacterCondition =
89
+ | HasNamedCharacterCondition
90
+ | HasCharacterWithClassificationCondition
91
+ | HasCharacterWithKeywordCondition
92
+ | HasCharacterCountCondition;
93
+
94
+ /**
95
+ * Check if an item with a specific name exists
96
+ */
97
+ export interface HasNamedItemCondition {
98
+ type: "has-named-item";
99
+ name: string;
100
+ controller: "you" | "opponent" | "any";
101
+ }
102
+
103
+ /**
104
+ * Check if a specific count of items exists
105
+ */
106
+ export interface HasItemCountCondition {
107
+ type: "has-item-count";
108
+ controller: "you" | "opponent" | "any";
109
+ comparison: ComparisonOperator;
110
+ count: number;
111
+ }
112
+
113
+ /**
114
+ * Check if you have a location in play
115
+ */
116
+ export interface HasLocationInPlayCondition {
117
+ type: "has-location-in-play";
118
+ controller?: "you" | "opponent" | "any";
119
+ }
120
+
121
+ /**
122
+ * Check if you have fewer characters than opponent
123
+ */
124
+ export interface HasFewerCharactersCondition {
125
+ type: "has-fewer-characters";
126
+ than: "opponent";
127
+ }
128
+
129
+ /**
130
+ * Check if opponent has more cards in hand
131
+ */
132
+ export interface OpponentHasMoreCardsCondition {
133
+ type: "opponent-has-more-cards";
134
+ what?: "cards-in-hand" | "lore";
135
+ }
136
+
137
+ /**
138
+ * Check if opponent has a damaged character
139
+ */
140
+ export interface OpponentHasDamagedCharacterCondition {
141
+ type: "opponent-has-damaged-character";
142
+ }
143
+
144
+ /**
145
+ * Check if target is a villain
146
+ */
147
+ export interface TargetIsVillainCondition {
148
+ type: "target-is-villain";
149
+ }
150
+
151
+ /**
152
+ * Check if target has no damage
153
+ */
154
+ export interface HasNoDamageCondition {
155
+ type: "has-no-damage";
156
+ target?: CharacterTarget | "SELF";
157
+ }
158
+
159
+ /**
160
+ * Check if character has strength comparison
161
+ */
162
+ export interface HasCharacterWithStrengthCondition {
163
+ type: "has-character-with-strength";
164
+ comparison: ComparisonOperator;
165
+ value: number;
166
+ controller?: "you" | "opponent" | "any";
167
+ }
168
+
169
+ /**
170
+ * Check if returned card is a princess
171
+ */
172
+ export interface ReturnedCardIsPrincessCondition {
173
+ type: "returned-card-is-princess";
174
+ }
175
+
176
+ /**
177
+ * Check if revealed card has same name
178
+ */
179
+ export interface RevealedHasSameNameCondition {
180
+ type: "revealed-has-same-name";
181
+ }
182
+
183
+ /**
184
+ * Check if there are characters here (at location)
185
+ */
186
+ export interface HasCharactersHereCondition {
187
+ type: "has-characters-here";
188
+ count?: number | { min: number };
189
+ }
190
+
191
+ /**
192
+ * Check if character is at a location
193
+ */
194
+ export interface HasCharacterAtLocationCondition {
195
+ type: "has-character-at-location";
196
+ }
197
+
198
+ /**
199
+ * Check if character is being challenged
200
+ */
201
+ export interface BeingChallengedCondition {
202
+ type: "being-challenged";
203
+ }
204
+
205
+ /**
206
+ * Check if self has damage
207
+ */
208
+ export interface SelfHasDamageCondition {
209
+ type: "self-has-damage";
210
+ }
211
+
212
+ /**
213
+ * Check if revealed card is a character with specific name
214
+ */
215
+ export interface RevealedIsCharacterNamedCondition {
216
+ type: "revealed-is-character-named";
217
+ name?: string;
218
+ }
219
+
220
+ /**
221
+ * Check if this is the second inkwell this turn
222
+ */
223
+ export interface SecondInkwellThisTurnCondition {
224
+ type: "second-inkwell-this-turn";
225
+ }
226
+
227
+ /**
228
+ * Check while in play
229
+ */
230
+ export interface WhileInPlayCondition {
231
+ type: "while-in-play";
232
+ }
233
+
234
+ /**
235
+ * Check if played a card this turn
236
+ */
237
+ export interface PlayedCardThisTurnCondition {
238
+ type: "played-card-this-turn";
239
+ cardType?: string;
240
+ }
241
+
242
+ /**
243
+ * Check if opponent has more than X cards
244
+ */
245
+ export interface OpponentHasMoreThanCardsCondition {
246
+ type: "opponent-has-more-than-cards";
247
+ count?: number;
248
+ }
249
+
250
+ /**
251
+ * Check if opponent has lore
252
+ */
253
+ export interface OpponentHasLoreCondition {
254
+ type: "opponent-has-lore";
255
+ comparison?: ComparisonOperator;
256
+ value?: number;
257
+ }
258
+
259
+ /**
260
+ * Check if you have the strongest character
261
+ */
262
+ export interface HasStrongestCharacterCondition {
263
+ type: "has-strongest-character";
264
+ }
265
+
266
+ /**
267
+ * Check if there's a damaged character here
268
+ */
269
+ export interface HasDamagedCharacterHereCondition {
270
+ type: "has-damaged-character-here";
271
+ }
272
+
273
+ /**
274
+ * Check if you have an item in play
275
+ */
276
+ export interface HasItemInPlayCondition {
277
+ type: "has-item-in-play";
278
+ controller?: "you" | "opponent" | "any";
279
+ }
280
+
281
+ /**
282
+ * Combined type for item existence conditions
283
+ */
284
+ export type HasItemCondition = HasNamedItemCondition | HasItemCountCondition;
285
+
286
+ /**
287
+ * Check if a location with a specific name exists
288
+ */
289
+ export interface HasNamedLocationCondition {
290
+ type: "has-named-location";
291
+ name: string;
292
+ controller: "you" | "opponent" | "any";
293
+ }
294
+
295
+ export interface HasCharacterHereCondition {
296
+ type: "has-character-here";
297
+ }
298
+
299
+ export interface RevealedMatchesNamedCondition {
300
+ type: "revealed-matches-named";
301
+ }
302
+
303
+ /**
304
+ * Check if revealed card matches a chosen name
305
+ */
306
+ export interface RevealedMatchesChosenNameCondition {
307
+ type: "revealed-matches-chosen-name";
308
+ }
309
+
310
+ /**
311
+ * "If you do" condition - effect was successfully resolved
312
+ */
313
+ export interface IfYouDoCondition {
314
+ type: "if-you-do";
315
+ }
316
+
317
+ /**
318
+ * Check if a specific count of locations exists
319
+ */
320
+ export interface HasLocationCountCondition {
321
+ type: "has-location-count";
322
+ controller: "you" | "opponent" | "any";
323
+ comparison: ComparisonOperator;
324
+ count: number;
325
+ }
326
+
327
+ /**
328
+ * Combined type for location existence conditions
329
+ */
330
+ export type HasLocationCondition =
331
+ | HasNamedLocationCondition
332
+ | HasLocationCountCondition;
333
+
334
+ /**
335
+ * Check if this character is at a location
336
+ */
337
+ export interface AtLocationCondition {
338
+ type: "at-location";
339
+ /** Specific location name (optional) */
340
+ locationName?: string;
341
+ }
342
+
343
+ // ============================================================================
344
+ // State Conditions - Strict Variants
345
+ // ============================================================================
346
+
347
+ /**
348
+ * Check if this character has any damage (simple check)
349
+ */
350
+ export interface HasAnyDamageCondition {
351
+ type: "has-any-damage";
352
+ }
353
+
354
+ /**
355
+ * Check if this character has a specific amount of damage
356
+ */
357
+ export interface DamageComparisonCondition {
358
+ type: "damage-comparison";
359
+ /** Comparison operator - REQUIRED */
360
+ comparison: ComparisonOperator;
361
+ /** Value to compare against - REQUIRED */
362
+ value: number;
363
+ }
364
+
365
+ /**
366
+ * Combined type for damage conditions
367
+ */
368
+ export type HasDamageCondition =
369
+ | HasAnyDamageCondition
370
+ | DamageComparisonCondition;
371
+
372
+ /**
373
+ * Check if this character has no damage
374
+ */
375
+ export interface NoDamageCondition {
376
+ type: "no-damage";
377
+ target?: "SELF" | CharacterTarget;
378
+ }
379
+
380
+ /**
381
+ * Check if this character is exerted
382
+ */
383
+ export interface IsExertedCondition {
384
+ type: "is-exerted";
385
+ }
386
+
387
+ /**
388
+ * Check if this character is ready
389
+ */
390
+ export interface IsReadyCondition {
391
+ type: "is-ready";
392
+ }
393
+
394
+ /**
395
+ * Check if this card has a card under it (Boost mechanic)
396
+ */
397
+ export interface HasCardUnderCondition {
398
+ type: "has-card-under";
399
+ }
400
+
401
+ // ============================================================================
402
+ // Zone Presence Conditions (Self)
403
+ // ============================================================================
404
+
405
+ /**
406
+ * Check if this card is in inkwell
407
+ * Used for abilities that only work while card is in inkwell
408
+ */
409
+ export interface InInkwellCondition {
410
+ type: "in-inkwell";
411
+ }
412
+
413
+ /**
414
+ * Check if this card is in play
415
+ * Used for abilities that only work while card is in play
416
+ */
417
+ export interface InPlayCondition {
418
+ type: "in-play";
419
+ }
420
+
421
+ // ============================================================================
422
+ // Legacy Resolution Conditions
423
+ // ============================================================================
424
+
425
+ /**
426
+ * Check resolution context (Legacy support)
427
+ * @deprecated Will be removed after Bodyguard/Shift refactoring
428
+ * Used for "If this was shifted" (legacy) or Bodyguard context
429
+ */
430
+ export interface ResolutionCondition {
431
+ type: "resolution";
432
+ value: "bodyguard" | "shift";
433
+ }
434
+
435
+ // ============================================================================
436
+ // Count/Comparison Conditions - Strict Variants
437
+ // ============================================================================
438
+
439
+ /**
440
+ * Base countable resources
441
+ */
442
+ export type CountableResource =
443
+ | "characters"
444
+ | "items"
445
+ | "locations"
446
+ | "cards-in-hand"
447
+ | "cards-in-inkwell"
448
+ | "cards-in-discard"
449
+ | "damage-on-self"
450
+ | "damaged-characters"
451
+ | "exerted-characters";
452
+
453
+ /**
454
+ * Count-based condition for generic resources
455
+ *
456
+ * @example "If you have 3 or more characters in play"
457
+ * @example "If you have no cards in your hand"
458
+ */
459
+ export interface ResourceCountCondition {
460
+ type: "resource-count";
461
+ /** What to count - REQUIRED */
462
+ what: CountableResource;
463
+ /** Whose resources to count - REQUIRED */
464
+ controller: "you" | "opponent" | "any";
465
+ /** Comparison operator - REQUIRED */
466
+ comparison: ComparisonOperator;
467
+ /** Value to compare against - REQUIRED */
468
+ value: number;
469
+ }
470
+
471
+ /**
472
+ * Count characters with a specific keyword
473
+ *
474
+ * @example "If you have 2 or more characters with Rush"
475
+ */
476
+ export interface KeywordCharacterCountCondition {
477
+ type: "keyword-character-count";
478
+ /** Which keyword - REQUIRED */
479
+ keyword: string;
480
+ /** Whose characters - REQUIRED */
481
+ controller: "you" | "opponent" | "any";
482
+ /** Comparison operator - REQUIRED */
483
+ comparison: ComparisonOperator;
484
+ /** Value to compare against - REQUIRED */
485
+ value: number;
486
+ }
487
+
488
+ /**
489
+ * Count characters with a specific classification
490
+ *
491
+ * @example "If you have 2 or more Floodborn characters"
492
+ */
493
+ export interface ClassificationCharacterCountCondition {
494
+ type: "classification-character-count";
495
+ /** Which classification - REQUIRED */
496
+ classification?: string;
497
+ /** Whose characters - REQUIRED */
498
+ controller?: "you" | "opponent" | "any";
499
+ /** Comparison operator - REQUIRED */
500
+ comparison?: ComparisonOperator;
501
+ /** Value to compare against - REQUIRED */
502
+ value?: number;
503
+ }
504
+
505
+ /**
506
+ * Combined type for all count conditions
507
+ */
508
+ export type CountCondition =
509
+ | ResourceCountCondition
510
+ | KeywordCharacterCountCondition
511
+ | ClassificationCharacterCountCondition;
512
+
513
+ /**
514
+ * Compare two values
515
+ *
516
+ * @example "If an opponent has more cards in their hand than you"
517
+ * @example "If you have more lore than each opponent"
518
+ */
519
+ export interface ComparisonCondition {
520
+ type: "comparison";
521
+ /** Left side of comparison */
522
+ left: ComparisonValue;
523
+ /** Comparison operator */
524
+ comparison: ComparisonOperator;
525
+ /** Right side of comparison */
526
+ right: ComparisonValue;
527
+ }
528
+
529
+ export type ComparisonValue =
530
+ | { type: "lore"; controller: "you" | "opponent" }
531
+ | { type: "cards-in-hand"; controller: "you" | "opponent" }
532
+ | { type: "cards-in-inkwell"; controller: "you" | "opponent" }
533
+ | { type: "character-count"; controller: "you" | "opponent" }
534
+ | { type: "damage-on-self" }
535
+ | { type: "strength-of-self" }
536
+ | { type: "constant"; value: number };
537
+
538
+ // ============================================================================
539
+ // Game State Conditions
540
+ // ============================================================================
541
+
542
+ /**
543
+ * Check if Shift was used to play this character
544
+ */
545
+ export interface UsedShiftCondition {
546
+ type: "used-shift";
547
+ }
548
+
549
+ // ============================================================================
550
+ // This-Turn Conditions - Strict Variants
551
+ // ============================================================================
552
+
553
+ /**
554
+ * Events that can be checked "this turn"
555
+ */
556
+ export type ThisTurnEvent =
557
+ | "played-song"
558
+ | "played-character"
559
+ | "played-action"
560
+ | "played-floodborn"
561
+ | "challenged"
562
+ | "quested"
563
+ | "banished-character"
564
+ | "damaged-character"
565
+ | "was-damaged"
566
+ | "inked";
567
+
568
+ /**
569
+ * Check if something happened this turn (simple boolean check)
570
+ *
571
+ * @example "If you played a song this turn"
572
+ */
573
+ export interface ThisTurnHappenedCondition {
574
+ type: "this-turn-happened";
575
+ /** What event to check - REQUIRED */
576
+ event: ThisTurnEvent;
577
+ /** Who did it - REQUIRED */
578
+ who: "you" | "opponent";
579
+ }
580
+
581
+ /**
582
+ * Check if something happened a specific number of times this turn
583
+ *
584
+ * @example "If you played 2 or more characters this turn"
585
+ */
586
+ export interface ThisTurnCountCondition {
587
+ type: "this-turn-count";
588
+ /** What event to check - REQUIRED */
589
+ event: ThisTurnEvent;
590
+ /** Who did it - REQUIRED */
591
+ who: "you" | "opponent";
592
+ /** Comparison operator - REQUIRED */
593
+ comparison: ComparisonOperator;
594
+ /** Count to compare against - REQUIRED */
595
+ count: number;
596
+ }
597
+
598
+ /**
599
+ * Combined type for this-turn conditions
600
+ */
601
+ export type ThisTurnCondition =
602
+ | ThisTurnHappenedCondition
603
+ | ThisTurnCountCondition;
604
+
605
+ /**
606
+ * Check whose turn it is
607
+ */
608
+ export interface TurnCondition {
609
+ type: "turn";
610
+ whose: "your" | "opponent";
611
+ }
612
+
613
+ /**
614
+ * Check if it's your turn (simplified version)
615
+ */
616
+ export interface YourTurnCondition {
617
+ type: "your-turn";
618
+ }
619
+
620
+ /**
621
+ * Check if a character is exerted
622
+ */
623
+ export interface ExertedCondition {
624
+ type: "exerted";
625
+ target?: "SELF" | CharacterTarget;
626
+ }
627
+
628
+ /**
629
+ * Check hand count
630
+ */
631
+ export interface HandCountCondition {
632
+ type: "hand-count";
633
+ controller: "you" | "opponent";
634
+ count: number;
635
+ comparison?: ComparisonOperator;
636
+ }
637
+
638
+ /**
639
+ * Check if a stat meets a threshold
640
+ */
641
+ export interface StatThresholdCondition {
642
+ type: "stat-threshold";
643
+ stat: "strength" | "willpower" | "lore";
644
+ value: number;
645
+ comparison: ComparisonOperator;
646
+ target?: "SELF" | CharacterTarget;
647
+ }
648
+
649
+ /**
650
+ * Check if something was played this turn
651
+ */
652
+ export interface PlayedThisTurnCondition {
653
+ type: "played-this-turn";
654
+ cardType?: "character" | "action" | "item" | "song";
655
+ }
656
+
657
+ /**
658
+ * Check if you have a character (simplified)
659
+ */
660
+ export interface HaveCharacterCondition {
661
+ type: "have-character";
662
+ name?: string;
663
+ classification?: string;
664
+ }
665
+
666
+ /**
667
+ * Check if you have a card
668
+ */
669
+ export interface HaveCardCondition {
670
+ type: "have-card";
671
+ cardType?: "character" | "action" | "item" | "song";
672
+ name?: string;
673
+ zone?: "hand" | "play" | "discard";
674
+ controller?: "you" | "opponent";
675
+ }
676
+
677
+ /**
678
+ * Check a name condition
679
+ */
680
+ export interface NameCondition {
681
+ type: "name";
682
+ name: string;
683
+ target?: CharacterTarget;
684
+ }
685
+
686
+ /**
687
+ * Character count condition (simplified)
688
+ */
689
+ export interface CharacterCountCondition {
690
+ type: "character-count";
691
+ count: number;
692
+ comparison?: ComparisonOperator;
693
+ controller?: "you" | "opponent";
694
+ }
695
+
696
+ /**
697
+ * Generic target condition (for parser flexibility)
698
+ */
699
+ export interface TargetCondition {
700
+ type: "target";
701
+ target: CharacterTarget | string;
702
+ }
703
+
704
+ /**
705
+ * Check if this is the first occurrence of something this turn
706
+ */
707
+ export interface FirstThisTurnCondition {
708
+ type: "first-this-turn";
709
+ event: "challenge" | "quest" | "action" | "character-play";
710
+ }
711
+
712
+ // ============================================================================
713
+ // Zone Conditions
714
+ // ============================================================================
715
+
716
+ /**
717
+ * Check for cards in a specific zone
718
+ */
719
+ export interface ZoneCondition {
720
+ type: "zone";
721
+ zone: TargetZone;
722
+ controller: "you" | "opponent";
723
+ /** Card type filter */
724
+ cardType?: CardType | "song";
725
+ /** Has cards / is empty */
726
+ hasCards?: boolean;
727
+ /** Card name filter */
728
+ cardName?: string;
729
+ }
730
+
731
+ // ============================================================================
732
+ // Combat Context Conditions
733
+ // ============================================================================
734
+
735
+ /**
736
+ * Check if the character is currently in a challenge
737
+ *
738
+ * Used for conditional keywords like "Resist +2 while challenging"
739
+ */
740
+ export interface InChallengeCondition {
741
+ type: "in-challenge";
742
+ }
743
+
744
+ // ============================================================================
745
+ // Player Choice Conditions
746
+ // ============================================================================
747
+
748
+ /**
749
+ * Player may choose to do something ("you may")
750
+ */
751
+ export interface PlayerChoiceCondition {
752
+ type: "player-choice";
753
+ /** Who makes the choice (defaults to controller) */
754
+ chooser?: "controller" | "opponent";
755
+ }
756
+
757
+ // ============================================================================
758
+ // Logical Operators
759
+ // ============================================================================
760
+
761
+ /**
762
+ * AND condition - all sub-conditions must be true
763
+ */
764
+ export interface AndCondition {
765
+ type: "and";
766
+ conditions: Condition[];
767
+ }
768
+
769
+ /**
770
+ * OR condition - at least one sub-condition must be true
771
+ */
772
+ export interface OrCondition {
773
+ type: "or";
774
+ conditions: Condition[];
775
+ }
776
+
777
+ /**
778
+ * NOT condition - sub-condition must be false
779
+ */
780
+ export interface NotCondition {
781
+ type: "not";
782
+ condition: Condition;
783
+ }
784
+
785
+ /**
786
+ * IF condition - catch-all for parser conditions
787
+ *
788
+ * Used when the parser cannot determine a more specific condition type.
789
+ * This allows for flexible "if" expressions that will be refined later.
790
+ *
791
+ * @example "If a Villain character is chosen..."
792
+ * @deprecated Prefer using specific condition types when possible
793
+ */
794
+ export interface IfCondition {
795
+ type: "if";
796
+ /** Natural language expression of the condition */
797
+ expression: string;
798
+ }
799
+
800
+ // ============================================================================
801
+ // Extended Condition Types for Parser Support
802
+ // ============================================================================
803
+
804
+ /**
805
+ * Check if you have another character (besides self)
806
+ */
807
+ export interface HasAnotherCharacterCondition {
808
+ type: "has-another-character";
809
+ classification?: string;
810
+ name?: string;
811
+ }
812
+
813
+ /**
814
+ * Check if you have a Captain character
815
+ */
816
+ export interface HasCaptainCharacterCondition {
817
+ type: "has-captain-character";
818
+ }
819
+
820
+ /**
821
+ * Check if self is exerted (alias for is-exerted)
822
+ */
823
+ export interface SelfExertedCondition {
824
+ type: "self-exerted";
825
+ }
826
+
827
+ /**
828
+ * Check if target is a Villain
829
+ */
830
+ export interface IsVillainCondition {
831
+ type: "is-villain";
832
+ target?: string;
833
+ }
834
+
835
+ /**
836
+ * Check if target is a Princess
837
+ */
838
+ export interface IsPrincessCondition {
839
+ type: "is-princess";
840
+ target?: string;
841
+ }
842
+
843
+ /**
844
+ * Check if target has a specific name
845
+ */
846
+ export interface IsNamedCondition {
847
+ type: "is-named";
848
+ name: string;
849
+ target?: string;
850
+ }
851
+
852
+ /**
853
+ * Check inkwell count
854
+ */
855
+ export interface InkwellCountCondition {
856
+ type: "inkwell-count";
857
+ controller: "you" | "opponent";
858
+ comparison?: ComparisonOperator;
859
+ count?: number;
860
+ /** Minimum inkwell count required */
861
+ minimum?: number;
862
+ }
863
+
864
+ /**
865
+ * Alias for has-named-character (parser compatibility)
866
+ */
867
+ export interface HasCharacterNamedCondition {
868
+ type: "has-character-named";
869
+ name: string;
870
+ controller?: "you" | "opponent" | "any";
871
+ }
872
+
873
+ /**
874
+ * Unless condition - negated condition wrapper
875
+ *
876
+ * @example "This character can't quest unless you have a Seven Dwarfs character"
877
+ */
878
+ export interface UnlessCondition {
879
+ type: "unless";
880
+ condition: Condition;
881
+ }
882
+
883
+ /**
884
+ * Lore comparison condition
885
+ *
886
+ * @example "If an opponent has more lore than you"
887
+ */
888
+ export interface LoreComparisonCondition {
889
+ type: "lore-comparison";
890
+ comparison: ComparisonOperator;
891
+ value?: number;
892
+ versus?: "you" | "opponent";
893
+ compareTo?: "you" | "opponent";
894
+ }
895
+
896
+ /**
897
+ * Second action in turn condition
898
+ *
899
+ * @example "The second time you play a card this turn"
900
+ */
901
+ export interface SecondInTurnCondition {
902
+ type: "second-in-turn";
903
+ action?: "play" | "quest" | "challenge" | "any";
904
+ }
905
+
906
+ /**
907
+ * Target is damaged condition
908
+ *
909
+ * @example "If the chosen character is damaged"
910
+ */
911
+ export interface TargetIsDamagedCondition {
912
+ type: "target-is-damaged";
913
+ }
914
+
915
+ // ============================================================================
916
+ // Combined Condition Type
917
+ // ============================================================================
918
+
919
+ /**
920
+ * All possible conditions
921
+ *
922
+ * Uses strict discriminated unions - each condition type has exactly
923
+ * the fields it needs with no ambiguous optional fields.
924
+ */
925
+ export type Condition =
926
+ // Character Existence (strict variants)
927
+ | HasNamedCharacterCondition
928
+ | HasCharacterWithClassificationCondition
929
+ | HasCharacterWithKeywordCondition
930
+ | HasCharacterCountCondition
931
+ // Item Existence (strict variants)
932
+ | HasNamedItemCondition
933
+ | HasItemCountCondition
934
+ // Location Existence (strict variants)
935
+ | HasNamedLocationCondition
936
+ | HasLocationCountCondition
937
+ // Location State
938
+ | AtLocationCondition
939
+ // Damage State (strict variants)
940
+ | HasAnyDamageCondition
941
+ | DamageComparisonCondition
942
+ | NoDamageCondition
943
+ // Card State
944
+ | IsExertedCondition
945
+ | IsReadyCondition
946
+ | HasCardUnderCondition
947
+ | InInkwellCondition
948
+ | InPlayCondition
949
+ // Count Conditions (strict variants)
950
+ | ResourceCountCondition
951
+ | KeywordCharacterCountCondition
952
+ | ClassificationCharacterCountCondition
953
+ // Comparison
954
+ | ComparisonCondition
955
+ // Game state
956
+ | UsedShiftCondition
957
+ // This-Turn Conditions (strict variants)
958
+ | ThisTurnHappenedCondition
959
+ | ThisTurnCountCondition
960
+ // Turn
961
+ | TurnCondition
962
+ | YourTurnCondition
963
+ | FirstThisTurnCondition
964
+ // Zone
965
+ | ZoneCondition
966
+ | HasCharacterHereCondition
967
+ | RevealedMatchesNamedCondition
968
+ | RevealedMatchesChosenNameCondition
969
+ // Effect resolution
970
+ | IfYouDoCondition
971
+ // Combat Context
972
+ | InChallengeCondition
973
+ // Choice
974
+ | PlayerChoiceCondition
975
+ // Logical
976
+ | AndCondition
977
+ | OrCondition
978
+ | NotCondition
979
+ // Parser catch-all
980
+ | IfCondition
981
+ // Legacy Resolution (deprecated)
982
+ | ResolutionCondition
983
+ // Additional conditions for parser support
984
+ | ExertedCondition
985
+ | HandCountCondition
986
+ | StatThresholdCondition
987
+ | PlayedThisTurnCondition
988
+ | HaveCharacterCondition
989
+ | HaveCardCondition
990
+ | NameCondition
991
+ | CharacterCountCondition
992
+ | TargetCondition
993
+ // Extended conditions for card text coverage
994
+ | HasAnotherCharacterCondition
995
+ | HasCaptainCharacterCondition
996
+ | SelfExertedCondition
997
+ | IsVillainCondition
998
+ | IsPrincessCondition
999
+ | IsNamedCondition
1000
+ | InkwellCountCondition
1001
+ | HasCharacterNamedCondition
1002
+ // Additional extended conditions
1003
+ | HasLocationInPlayCondition
1004
+ | HasFewerCharactersCondition
1005
+ | OpponentHasMoreCardsCondition
1006
+ | OpponentHasDamagedCharacterCondition
1007
+ | TargetIsVillainCondition
1008
+ | HasNoDamageCondition
1009
+ | HasCharacterWithStrengthCondition
1010
+ | ReturnedCardIsPrincessCondition
1011
+ | RevealedHasSameNameCondition
1012
+ | HasCharactersHereCondition
1013
+ | HasCharacterAtLocationCondition
1014
+ // More extended conditions
1015
+ | BeingChallengedCondition
1016
+ | SelfHasDamageCondition
1017
+ | RevealedIsCharacterNamedCondition
1018
+ | SecondInkwellThisTurnCondition
1019
+ | WhileInPlayCondition
1020
+ | PlayedCardThisTurnCondition
1021
+ | OpponentHasMoreThanCardsCondition
1022
+ | OpponentHasLoreCondition
1023
+ | HasStrongestCharacterCondition
1024
+ | HasDamagedCharacterHereCondition
1025
+ | HasItemInPlayCondition
1026
+ // Additional parser-compatible conditions
1027
+ | UnlessCondition
1028
+ | LoreComparisonCondition
1029
+ | SecondInTurnCondition
1030
+ | TargetIsDamagedCondition;
1031
+
1032
+ // ============================================================================
1033
+ // Condition Builders (convenience)
1034
+ // ============================================================================
1035
+
1036
+ /**
1037
+ * Create a "has character named X" condition
1038
+ */
1039
+ export function hasCharacterNamed(
1040
+ name: string,
1041
+ controller: "you" | "opponent" | "any" = "you",
1042
+ ): HasNamedCharacterCondition {
1043
+ return { type: "has-named-character", name, controller };
1044
+ }
1045
+
1046
+ /**
1047
+ * Create a "has character with classification" condition
1048
+ */
1049
+ export function hasCharacterWithClassification(
1050
+ classification: string,
1051
+ controller: "you" | "opponent" | "any" = "you",
1052
+ ): HasCharacterWithClassificationCondition {
1053
+ return {
1054
+ type: "has-character-with-classification",
1055
+ classification,
1056
+ controller,
1057
+ };
1058
+ }
1059
+
1060
+ /**
1061
+ * Create a "has character with keyword" condition
1062
+ */
1063
+ export function hasCharacterWithKeyword(
1064
+ keyword: string,
1065
+ controller: "you" | "opponent" | "any" = "you",
1066
+ ): HasCharacterWithKeywordCondition {
1067
+ return { type: "has-character-with-keyword", keyword, controller };
1068
+ }
1069
+
1070
+ /**
1071
+ * Create a "has X or more characters" condition
1072
+ */
1073
+ export function hasCharacterCount(
1074
+ count: number,
1075
+ controller: "you" | "opponent" | "any" = "you",
1076
+ comparison: ComparisonOperator = "greater-or-equal",
1077
+ ): HasCharacterCountCondition {
1078
+ return {
1079
+ type: "has-character-count",
1080
+ controller,
1081
+ comparison,
1082
+ count,
1083
+ };
1084
+ }
1085
+
1086
+ /**
1087
+ * Create a count-based resource condition
1088
+ */
1089
+ export function resourceCount(
1090
+ what: CountableResource,
1091
+ value: number,
1092
+ controller: "you" | "opponent" | "any" = "you",
1093
+ comparison: ComparisonOperator = "greater-or-equal",
1094
+ ): ResourceCountCondition {
1095
+ return {
1096
+ type: "resource-count",
1097
+ what,
1098
+ controller,
1099
+ comparison,
1100
+ value,
1101
+ };
1102
+ }
1103
+
1104
+ /**
1105
+ * Create a "while this character has no damage" condition
1106
+ */
1107
+ export function whileNoDamage(): NoDamageCondition {
1108
+ return { type: "no-damage" };
1109
+ }
1110
+
1111
+ /**
1112
+ * Create a "while this character has damage" condition
1113
+ */
1114
+ export function whileHasDamage(): HasAnyDamageCondition {
1115
+ return { type: "has-any-damage" };
1116
+ }
1117
+
1118
+ /**
1119
+ * Create a "if you used Shift" condition
1120
+ */
1121
+ export function ifUsedShift(): UsedShiftCondition {
1122
+ return { type: "used-shift" };
1123
+ }
1124
+
1125
+ /**
1126
+ * Create a "this turn happened" condition
1127
+ */
1128
+ export function thisTurnHappened(
1129
+ event: ThisTurnEvent,
1130
+ who: "you" | "opponent" = "you",
1131
+ ): ThisTurnHappenedCondition {
1132
+ return { type: "this-turn-happened", event, who };
1133
+ }
1134
+
1135
+ /**
1136
+ * Create an "in challenge" condition
1137
+ *
1138
+ * Used for conditional keywords like "Resist +2 while challenging"
1139
+ */
1140
+ export function inChallenge(): InChallengeCondition {
1141
+ return { type: "in-challenge" };
1142
+ }
1143
+
1144
+ /**
1145
+ * Create a "you may" condition
1146
+ */
1147
+ export function youMay(): PlayerChoiceCondition {
1148
+ return { type: "player-choice" };
1149
+ }
1150
+
1151
+ /**
1152
+ * Create an "and" condition
1153
+ */
1154
+ export function and(...conditions: Condition[]): AndCondition {
1155
+ return { type: "and", conditions };
1156
+ }
1157
+
1158
+ /**
1159
+ * Create an "or" condition
1160
+ */
1161
+ export function or(...conditions: Condition[]): OrCondition {
1162
+ return { type: "or", conditions };
1163
+ }
1164
+
1165
+ // ============================================================================
1166
+ // Type Guards
1167
+ // ============================================================================
1168
+
1169
+ /**
1170
+ * Check if condition is a logical operator
1171
+ */
1172
+ export function isLogicalCondition(
1173
+ condition: Condition,
1174
+ ): condition is AndCondition | OrCondition | NotCondition {
1175
+ return (
1176
+ condition.type === "and" ||
1177
+ condition.type === "or" ||
1178
+ condition.type === "not"
1179
+ );
1180
+ }
1181
+
1182
+ /**
1183
+ * Check if condition is a player choice ("you may")
1184
+ */
1185
+ export function isPlayerChoice(
1186
+ condition: Condition,
1187
+ ): condition is PlayerChoiceCondition {
1188
+ return condition.type === "player-choice";
1189
+ }
1190
+
1191
+ /**
1192
+ * Check if condition requires a count comparison
1193
+ */
1194
+ export function isCountCondition(
1195
+ condition: Condition,
1196
+ ): condition is CountCondition {
1197
+ return (
1198
+ condition.type === "resource-count" ||
1199
+ condition.type === "keyword-character-count" ||
1200
+ condition.type === "classification-character-count"
1201
+ );
1202
+ }