@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,791 @@
1
+ /**
2
+ * Target Types for Lorcana Abilities
3
+ *
4
+ * Defines how abilities select their targets. Uses a hybrid approach:
5
+ * - Common targeting patterns as string literal enums for simplicity
6
+ * - Complex targeting with query-based filters for advanced cases
7
+ *
8
+ * @example Simple targeting
9
+ * ```typescript
10
+ * const target: CharacterTarget = "CHOSEN_CHARACTER";
11
+ * ```
12
+ *
13
+ * @example Complex targeting with filters
14
+ * ```typescript
15
+ * const target: CharacterTarget = {
16
+ * selector: "chosen",
17
+ * owner: "opponent",
18
+ * filter: [{ type: "damaged" }, { type: "strength-comparison", comparison: "less-or-equal", value: 3 }]
19
+ * };
20
+ * ```
21
+ */
22
+
23
+ import type { TargetDSL } from "@drmxrcy/tcg-core-types";
24
+
25
+ // ============================================================================
26
+ // Player Targeting
27
+ // ============================================================================
28
+
29
+ /**
30
+ * Player target - who is affected by the ability
31
+ *
32
+ * @example "draw 2 cards" targets CONTROLLER
33
+ * @example "each opponent loses 1 lore" targets EACH_OPPONENT
34
+ */
35
+
36
+ export type PlayerTarget =
37
+ | "CONTROLLER" // The player who controls this card
38
+ | "OPPONENT" // A single opponent (2-player default)
39
+ | "OPPONENTS" // All opponents (alias for EACH_OPPONENT in 2-player)
40
+ | "EACH_PLAYER" // All players including controller
41
+ | "EACH_OPPONENT" // All opponents
42
+ | "CHOSEN_PLAYER" // A player chosen by the controller
43
+ | "CARD_OWNER" // The owner of the target card (context-dependent)
44
+ | "CURRENT_TURN" // The player whose turn it is
45
+ // Additional targets for parser support
46
+ | "NEXT_CHARACTER" // The next character played (for cost reduction)
47
+ | "SEVEN_DWARFS_CHARACTERS" // Seven Dwarfs characters (for lore gain)
48
+ | "THAT_PLAYER" // Reference to a previously mentioned player
49
+ | "CHALLENGER_OWNER" // Owner of the challenging character
50
+ | "THEIR_CHOSEN_CHARACTER" // Their chosen character (for each player effects)
51
+ | "PAWPSICLE_ITEM" // Specific item reference
52
+ // Extended targets for card text coverage
53
+ | "ALL_PLAYERS" // All players (for effects like "each player discards")
54
+ | "SELF" // Self reference (for gain lore effects on self)
55
+ | "BROOM_CHARACTERS" // Broom characters (for lore gain)
56
+ | "CHALLENGING_PLAYER" // The player doing the challenge
57
+ | "NEXT_ACTION" // The next action played (for cost reduction)
58
+ | "NEXT_ITEM" // The next item played (for cost reduction)
59
+ | "CARD_FROM_HAND" // A card from hand (for discard effects)
60
+ | "CHARACTERS_COST_3_OR_LESS" // Characters with cost 3 or less
61
+ | "CHARACTERS_COST_2_OR_LESS" // Characters with cost 2 or less
62
+ | "OPPOSING_CHARACTERS" // Alias for ALL_OPPOSING_CHARACTERS
63
+ | "LOCATIONS"; // All locations (for lore gain effects)
64
+
65
+ // ============================================================================
66
+ // Card References (Context-Aware)
67
+ // ============================================================================
68
+
69
+ /**
70
+ * Context-aware card references for abilities
71
+ *
72
+ * These allow effects to reference cards based on the current game context
73
+ * rather than requiring explicit targeting.
74
+ *
75
+ * @example Reference the card that triggered an ability
76
+ * ```typescript
77
+ * { ref: "trigger-source" }
78
+ * ```
79
+ *
80
+ * @example Reference the attacker in a challenge
81
+ * ```typescript
82
+ * { ref: "attacker" }
83
+ * ```
84
+ */
85
+ export type CardReference =
86
+ // Self-referential
87
+ | { ref: "self" } // This card (the one with the ability)
88
+
89
+ // Trigger context (for triggered abilities)
90
+ | { ref: "trigger-source" } // Card that triggered the ability
91
+
92
+ // Challenge context
93
+ | { ref: "attacker" } // Character doing the challenge
94
+ | { ref: "defender" } // Character being challenged
95
+
96
+ // Effect chain context
97
+ | { ref: "previous-target" } // Target selected earlier in effect chain
98
+
99
+ // Player context
100
+ | { ref: "controller" } // Controller of this card
101
+ | { ref: "opponent" }; // Opponent of controller (1v1)
102
+
103
+ /**
104
+ * Check if a value is a CardReference
105
+ */
106
+ export function isCardReference(value: unknown): value is CardReference {
107
+ return (
108
+ typeof value === "object" &&
109
+ value !== null &&
110
+ "ref" in value &&
111
+ typeof (value as CardReference).ref === "string"
112
+ );
113
+ }
114
+
115
+ // ============================================================================
116
+ // Lorcana Context
117
+ // ============================================================================
118
+
119
+ export interface LorcanaContext {
120
+ self?: boolean;
121
+ }
122
+
123
+ // ============================================================================
124
+ // Character Targeting - Common Patterns (Enums)
125
+ // ============================================================================
126
+
127
+ /**
128
+ * Common character targeting patterns as string literals
129
+ *
130
+ * These cover ~80% of targeting cases in Lorcana card texts
131
+ */
132
+ export type CharacterTargetEnum =
133
+ // Self-referential
134
+ | "SELF" // This character
135
+ | "THIS_CHARACTER" // Alias for SELF
136
+
137
+ // Chosen (requires player choice)
138
+ | "CHOSEN_CHARACTER" // Any character
139
+ | "CHOSEN_OPPOSING_CHARACTER" // Opponent's character
140
+ | "CHOSEN_CHARACTER_OF_YOURS" // Your character
141
+ | "ANOTHER_CHOSEN_CHARACTER" // Any character except self
142
+ | "ANOTHER_CHOSEN_CHARACTER_OF_YOURS" // Your character except self
143
+ | "CHOSEN_DAMAGED_CHARACTER" // Any damaged character
144
+ | "CHOSEN_DAMAGED_OPPOSING_CHARACTER" // Opponent's damaged character
145
+ | "CHOSEN_EXERTED_CHARACTER" // Any exerted character
146
+ | "CHOSEN_OTHER_CHARACTER" // Another character (not self)
147
+ | "CHOSEN_CHALLENGED_CHARACTER" // Character being challenged
148
+
149
+ // Your chosen characters
150
+ | "YOUR_CHOSEN_CHARACTER" // Your chosen character
151
+ | "YOUR_CHOSEN_DAMAGED_CHARACTER" // Your chosen damaged character
152
+ | "YOUR_CHOSEN_VILLAIN" // Your chosen Villain character
153
+ | "YOUR_CHOSEN_ITEM" // Your chosen item
154
+
155
+ // All/Each (affects multiple)
156
+ | "ALL_CHARACTERS" // Every character in play
157
+ | "ALL_OPPOSING_CHARACTERS" // All of opponent's characters
158
+ | "YOUR_CHARACTERS" // All of your characters
159
+ | "YOUR_OTHER_CHARACTERS" // All of your characters except self
160
+ | "YOUR_OTHER_CHARACTER" // Another of your characters (singular)
161
+ | "YOUR_OTHER_2_CHARACTERS" // 2 other characters of yours
162
+ | "EACH_CHARACTER" // Same as ALL_CHARACTERS
163
+ | "EACH_OPPOSING_CHARACTER" // Same as ALL_OPPOSING_CHARACTERS
164
+
165
+ // Up to N characters
166
+ | "UP_TO_2_CHOSEN_CHARACTERS" // Up to 2 chosen characters
167
+
168
+ // Classification-based targets
169
+ | "YOUR_OTHER_SEVEN_DWARFS_CHARACTERS" // Your Seven Dwarfs except self
170
+ | "YOUR_PRINCE_PRINCESS_KING_QUEEN_CHARACTERS" // Your royalty characters
171
+ | "YOUR_EXERTED_CHARACTERS" // Your exerted characters
172
+ | "YOUR_EVASIVE_CHARACTERS" // Your Evasive characters
173
+ | "YOUR_RECKLESS_CHARACTERS" // Your Reckless characters
174
+ | "CHOSEN_DRAGON_CHARACTER" // Chosen Dragon character
175
+
176
+ // Played card reference
177
+ | "PLAYED_CARD" // The card that was just played
178
+ | "THEIR_CHOSEN_CHARACTER" // Their chosen character (for each player effects)
179
+ | "CHOSEN_OPPOSING_CHARACTER_3_STRENGTH_OR_LESS" // Opposing character with 3 or less strength
180
+
181
+ // Challenge context
182
+ | "challenging-character" // The character doing the challenge
183
+ | "challenged-character" // The character being challenged
184
+
185
+ // Extended character targets for card text coverage
186
+ | "YOUR_MUSKETEER_CHARACTERS" // Your Musketeer characters
187
+ | "YOUR_OTHER_MUSKETEER_CHARACTERS" // Your other Musketeer characters
188
+ | "YOUR_JETSAM_CHARACTERS" // Your Jetsam characters
189
+ | "YOUR_FLOTSAM_CHARACTERS" // Your Flotsam characters
190
+ | "YOUR_BROOM_CHARACTERS" // Your Broom characters
191
+ | "YOUR_PETER_PAN_CHARACTERS" // Your Peter Pan characters
192
+ | "YOUR_BODYGUARD_CHARACTERS" // Your Bodyguard characters
193
+ | "OPPOSING_CHARACTERS" // Alias for ALL_OPPOSING_CHARACTERS
194
+ | "OPPOSING_EVASIVE_CHARACTERS" // Opposing Evasive characters
195
+ | "CHOSEN_VILLAIN_CHARACTER" // Chosen Villain character
196
+ | "CHOSEN_TE_KA_CHARACTER" // Chosen Te Ka character
197
+ | "CHALLENGED_CHARACTER" // The character being challenged (uppercase)
198
+ | "CHALLENGING_CHARACTER" // The character doing the challenge (uppercase)
199
+ | "BANISHED_CHARACTER" // The character that was banished
200
+ // Extended character targets for additional card text coverage
201
+ | "CHARACTERS_HERE" // Characters at this location
202
+ | "YOUR_OTHER_STEEL_CHARACTERS" // Your other Steel characters
203
+ | "YOUR_DEITY_CHARACTERS" // Your Deity characters
204
+ | "UP_TO_2_YOUR_CHARACTERS" // Up to 2 of your characters
205
+ | "THAT_LOCATION" // Reference to a previously mentioned location
206
+ | "CHOSEN_OPPOSING_DEITY_CHARACTER" // Chosen opposing Deity character
207
+ | "CHOSEN_CHARACTER_OR_LOCATION" // Chosen character or location
208
+ // Additional targets for set-005 and beyond
209
+ | "CHOSEN_CHARACTER_IN_DISCARD" // Chosen character in discard
210
+ | "CHOSEN_CARD_IN_DISCARD" // Chosen card in discard
211
+ | "CHOSEN_CARD_FROM_DISCARD" // Chosen card from discard (alias)
212
+ | "ALL_CHARACTERS_WITH_NAME_IN_DISCARD" // All characters with named card in discard
213
+ | "CHOSEN_CHARACTER_HERE" // Chosen character at this location
214
+ | "CHARACTERS_WITH_SUPPORT_HERE" // Characters with Support at this location
215
+ | "CHOSEN_ITEM_OR_LOCATION" // Chosen item or location
216
+ | "CHOSEN_CHARACTER_ITEM_OR_LOCATION" // Chosen character, item, or location
217
+ | "CHOSEN_CHARACTERS_OR_LOCATIONS" // Chosen characters or locations (plural)
218
+ | "YOUR_VILLAIN_CHARACTERS" // Your Villain characters
219
+ | "YOUR_ALIEN_CHARACTERS" // Your Alien characters
220
+ | "YOUR_OTHER_FAIRY_CHARACTERS" // Your other Fairy characters
221
+ | "YOUR_PUPPY_CHARACTERS" // Your Puppy characters
222
+ | "YOUR_OTHER_HERO_CHARACTERS" // Your other Hero characters
223
+ | "YOUR_ALLY_CHARACTERS" // Your Ally characters
224
+ | "YOUR_OTHER_AMBER_CHARACTERS" // Your other Amber characters
225
+ | "YOUR_OTHER_RUBY_CHARACTERS" // Your other Ruby characters
226
+ | "YOUR_OTHER_SAPPHIRE_CHARACTERS" // Your other Sapphire characters
227
+ | "YOUR_OTHER_EMERALD_CHARACTERS" // Your other Emerald characters
228
+ | "YOUR_DETECTIVE_CHARACTERS" // Your Detective characters
229
+ | "CHOSEN_DETECTIVE_CHARACTER" // Chosen Detective character
230
+ | "CHOSEN_OPPOSING_DAMAGED_CHARACTER" // Chosen opposing damaged character
231
+ | "CHOSEN_CHARACTERS" // Chosen characters (plural)
232
+ | "OPPONENT_CHOSEN_CHARACTER" // Opponent's chosen character
233
+ | "CHOSEN_OPPONENT" // Chosen opponent (for multiplayer)
234
+ | "YOUR_CHARACTERS_OR_LOCATIONS" // Your characters or locations
235
+ | "YOUR_CHARACTERS_OR_LOCATIONS_WITH_CARD_UNDER" // Your characters or locations with card under
236
+ | "CHARACTERS_AT_LOCATION" // Characters at a location
237
+ | "REVEALED_CARD" // The revealed card
238
+ | "TOP_OF_DECK" // Top of deck
239
+ // Additional targets for more card coverage
240
+ | "CHARACTER_HERE" // Character at this location (singular)
241
+ | "YOUR_PIRATE_CHARACTERS" // Your Pirate characters
242
+ | "YOUR_HERO_CHARACTERS" // Your Hero characters
243
+ | "YOUR_GARGOYLE_CHARACTERS" // Your Gargoyle characters
244
+ | "YOUR_DEMONA_CHARACTERS" // Your Demona characters
245
+ | "YOUR_OTHER_DETECTIVE_CHARACTERS" // Your other Detective characters
246
+ | "YOUR_OTHER_AMETHYST_CHARACTERS" // Your other Amethyst characters
247
+ | "YOUR_HAND" // Your hand (for card targets)
248
+ | "TRIGGERING_CHARACTER" // The character that triggered the ability
249
+ | "TRIGGERING_CARD" // The card that triggered the ability
250
+ | "THAT_ITEM" // Reference to a previously mentioned item
251
+ // More additional targets
252
+ | "YOUR_OTHER_SAPPHIRE_CHARACTERS" // Your other Sapphire characters
253
+ | "YOUR_OTHER_DETECTIVE_CHARACTERS" // Your other Detective characters
254
+ | "SONG_CARD" // A song card
255
+ | "RANDOM_CARDS_IN_INKWELL" // Random cards in inkwell
256
+ | "YOUR_RECKLESS_CHARACTERS" // Your Reckless characters
257
+ | "OPPOSING_DAMAGED_CHARACTERS" // Opposing damaged characters
258
+ | "ALL_CARDS_IN_INKWELL"; // All cards in inkwell
259
+
260
+ // ============================================================================
261
+ // Character Targeting - Query-Based (Complex)
262
+ // ============================================================================
263
+
264
+ /**
265
+ * Zone where targets can be found
266
+ */
267
+ export type TargetZone = "play" | "hand" | "discard" | "deck" | "inkwell";
268
+
269
+ /**
270
+ * Who controls the target
271
+ */
272
+ export type TargetController = "you" | "opponent" | "any" | "CURRENT_TURN";
273
+
274
+ /**
275
+ * Comparison operators for numeric filters
276
+ */
277
+ export type ComparisonOperator =
278
+ | "equal"
279
+ | "not-equal"
280
+ | "less"
281
+ | "greater"
282
+ | "less-or-equal"
283
+ | "greater-or-equal"
284
+ // Alternative naming conventions (for parser compatibility)
285
+ | "greater-than"
286
+ | "less-than"
287
+ | "more-than" // Alias for greater
288
+ // Additional aliases for natural language
289
+ | "or-more" // Alias for greater-or-equal
290
+ | "or-less"; // Alias for less-or-equal
291
+
292
+ // ============================================================================
293
+ // Shared Filter Types (used across Character, Location, Item targeting)
294
+ // ============================================================================
295
+
296
+ /**
297
+ * Base filters shared across all card types
298
+ * Uses a unified DSL so contributors only learn one pattern
299
+ */
300
+
301
+ // State filters
302
+ export interface DamagedFilter {
303
+ type: "damaged";
304
+ }
305
+
306
+ export interface UndamagedFilter {
307
+ type: "undamaged";
308
+ }
309
+
310
+ export interface ExertedFilter {
311
+ type: "exerted";
312
+ }
313
+
314
+ export interface ReadyFilter {
315
+ type: "ready";
316
+ }
317
+
318
+ // Property filters
319
+ export interface HasKeywordFilter {
320
+ type: "has-keyword";
321
+ keyword: string;
322
+ }
323
+
324
+ export interface HasClassificationFilter {
325
+ type: "has-classification";
326
+ classification: string;
327
+ }
328
+
329
+ export interface HasNameFilter {
330
+ type: "has-name";
331
+ name: string;
332
+ }
333
+
334
+ // Numeric comparison filters
335
+ export interface CostComparisonFilter {
336
+ type: "cost-comparison";
337
+ comparison: ComparisonOperator;
338
+ value: number;
339
+ }
340
+
341
+ export interface StrengthComparisonFilter {
342
+ type: "strength-comparison";
343
+ comparison: ComparisonOperator;
344
+ value: number;
345
+ }
346
+
347
+ export interface WillpowerComparisonFilter {
348
+ type: "willpower-comparison";
349
+ comparison: ComparisonOperator;
350
+ value: number;
351
+ }
352
+
353
+ export interface LoreComparisonFilter {
354
+ type: "lore-comparison";
355
+ comparison: ComparisonOperator;
356
+ value: number;
357
+ }
358
+
359
+ export interface MoveCostComparisonFilter {
360
+ type: "move-cost-comparison";
361
+ comparison: ComparisonOperator;
362
+ value: number;
363
+ }
364
+
365
+ // ============================================================================
366
+ // Source/Reference Filters
367
+ // ============================================================================
368
+
369
+ /**
370
+ * Filter by relationship to source card
371
+ *
372
+ * @example Filter to exclude self
373
+ * ```typescript
374
+ * { type: "source", ref: "other" }
375
+ * ```
376
+ *
377
+ * @example Filter to match the card that triggered this ability
378
+ * ```typescript
379
+ * { type: "source", ref: "trigger-source" }
380
+ * ```
381
+ */
382
+ export interface SourceFilter {
383
+ type: "source";
384
+ ref: "self" | "other" | "trigger-source";
385
+ }
386
+
387
+ /**
388
+ * Filter by challenge role
389
+ *
390
+ * @example Filter to match the attacker in a challenge
391
+ * ```typescript
392
+ * { type: "challenge-role", role: "attacker" }
393
+ * ```
394
+ */
395
+ export interface ChallengeRoleFilter {
396
+ type: "challenge-role";
397
+ role: "attacker" | "defender";
398
+ }
399
+
400
+ // ============================================================================
401
+ // Zone and Owner Filters
402
+ // ============================================================================
403
+
404
+ /**
405
+ * Filter by zone
406
+ *
407
+ * @example Filter to cards in play
408
+ * ```typescript
409
+ * { type: "zone", zone: "play" }
410
+ * ```
411
+ */
412
+ export interface ZoneFilter {
413
+ type: "zone";
414
+ zone: TargetZone | TargetZone[];
415
+ }
416
+
417
+ /**
418
+ * Filter by owner/controller
419
+ *
420
+ * @example Filter to opponent's cards
421
+ * ```typescript
422
+ * { type: "owner", owner: "opponent" }
423
+ * ```
424
+ */
425
+ export interface OwnerFilter {
426
+ type: "owner";
427
+ owner: "you" | "opponent" | "any";
428
+ }
429
+
430
+ // ============================================================================
431
+ // Generic Attribute Filter
432
+ // ============================================================================
433
+
434
+ /**
435
+ * Generic attribute comparison - extensible for future attributes
436
+ * This provides flexibility beyond the specific comparison filters
437
+ */
438
+ export type AttributeFilter =
439
+ | AttributeNumericFilter
440
+ | AttributeStringFilter
441
+ | AttributeBooleanFilter;
442
+
443
+ export interface AttributeNumericFilter {
444
+ type: "attribute";
445
+ attribute: "cost" | "strength" | "willpower" | "lore";
446
+ comparison: ComparisonOperator;
447
+ value: number;
448
+ /** Ignore stat bonuses when comparing */
449
+ ignoreBonuses?: boolean;
450
+ }
451
+
452
+ export interface AttributeStringFilter {
453
+ type: "attribute";
454
+ attribute: "name" | "title";
455
+ comparison: "equals" | "contains";
456
+ value: string;
457
+ }
458
+
459
+ export interface AttributeBooleanFilter {
460
+ type: "attribute";
461
+ attribute: "inkwell";
462
+ value: boolean;
463
+ }
464
+
465
+ /**
466
+ * All filters that can be applied to any card type
467
+ * Specific card types may only support a subset
468
+ */
469
+ export type CardFilter =
470
+ // State
471
+ | DamagedFilter
472
+ | UndamagedFilter
473
+ | ExertedFilter
474
+ | ReadyFilter
475
+ // Property
476
+ | HasKeywordFilter
477
+ | HasClassificationFilter
478
+ | HasNameFilter
479
+ // Numeric
480
+ | CostComparisonFilter
481
+ | StrengthComparisonFilter
482
+ | WillpowerComparisonFilter
483
+ | LoreComparisonFilter
484
+ | MoveCostComparisonFilter
485
+ // Source/Reference
486
+ | SourceFilter
487
+ | ChallengeRoleFilter
488
+ // Zone/Owner
489
+ | ZoneFilter
490
+ | OwnerFilter
491
+ // Generic Attribute
492
+ | AttributeFilter;
493
+
494
+ /**
495
+ * Filters applicable to characters
496
+ * (all except move-cost which is location-specific)
497
+ */
498
+ export type CharacterFilter =
499
+ // State
500
+ | DamagedFilter
501
+ | UndamagedFilter
502
+ | ExertedFilter
503
+ | ReadyFilter
504
+ // Property
505
+ | HasKeywordFilter
506
+ | HasClassificationFilter
507
+ | HasNameFilter
508
+ // Numeric comparisons
509
+ | CostComparisonFilter
510
+ | StrengthComparisonFilter
511
+ | WillpowerComparisonFilter
512
+ | LoreComparisonFilter
513
+ // Source/Reference
514
+ | SourceFilter
515
+ | ChallengeRoleFilter
516
+ // Zone/Owner
517
+ | ZoneFilter
518
+ | OwnerFilter
519
+ // Generic Attribute
520
+ | AttributeFilter;
521
+
522
+ // ============================================================================
523
+ // Character Targeting - Strict Query Variants
524
+ // ============================================================================
525
+
526
+ /**
527
+ * Base properties shared by all character query variants
528
+ * Extended from generic TargetDSL
529
+ */
530
+ export type CharacterQueryBase = TargetDSL<CharacterFilter[], LorcanaContext>;
531
+
532
+ /**
533
+ * Target exactly N characters
534
+ *
535
+ * @example Target exactly 2 characters
536
+ * ```typescript
537
+ * {
538
+ * selector: "chosen",
539
+ * count: { exactly: 2 },
540
+ * owner: "opponent"
541
+ * }
542
+ * ```
543
+ */
544
+ export interface ExactCountCharacterQuery extends CharacterQueryBase {
545
+ count: number | { exactly: number };
546
+ }
547
+
548
+ /**
549
+ * Target up to N characters (player chooses 0 to maxCount)
550
+ *
551
+ * @example Target up to 2 damaged opposing characters
552
+ * ```typescript
553
+ * {
554
+ * selector: "chosen",
555
+ * owner: "opponent",
556
+ * filter: [{ type: "damaged" }],
557
+ * count: { upTo: 2 }
558
+ * }
559
+ * ```
560
+ */
561
+ export interface UpToCountCharacterQuery extends CharacterQueryBase {
562
+ count: { upTo: number };
563
+ }
564
+
565
+ /**
566
+ * Target all matching characters
567
+ *
568
+ * @example Target all opposing characters
569
+ * ```typescript
570
+ * {
571
+ * selector: "all",
572
+ * owner: "opponent"
573
+ * }
574
+ * ```
575
+ */
576
+ export interface AllMatchingCharacterQuery extends CharacterQueryBase {
577
+ count: "all";
578
+ }
579
+
580
+ /**
581
+ * Complex character targeting with query-based filters
582
+ *
583
+ * Uses discriminated union to ensure type safety:
584
+ * - `count: number` for exact targeting
585
+ * - `count: "up-to"` requires `maxCount`
586
+ * - `count: "all"` for all matching
587
+ */
588
+ export type CharacterTargetQuery =
589
+ | ExactCountCharacterQuery
590
+ | UpToCountCharacterQuery
591
+ | AllMatchingCharacterQuery;
592
+
593
+ /**
594
+ * Union type for all character targeting options
595
+ */
596
+ export type CharacterTarget =
597
+ | CharacterTargetEnum
598
+ | CharacterTargetQuery
599
+ | CardReference;
600
+
601
+ // ============================================================================
602
+ // Location Targeting
603
+ // ============================================================================
604
+
605
+ /**
606
+ * Common location targeting patterns
607
+ */
608
+ export type LocationTargetEnum =
609
+ | "CHOSEN_LOCATION"
610
+ | "CHOSEN_OPPOSING_LOCATION"
611
+ | "YOUR_LOCATIONS"
612
+ | "ALL_OPPOSING_LOCATIONS"
613
+ | "THIS_LOCATION" // For abilities on locations
614
+ | "CHARACTERS_HERE"; // Characters at this location (for location abilities)
615
+
616
+ // ============================================================================
617
+ // Location Targeting - Strict Query Variants
618
+ // ============================================================================
619
+
620
+ /**
621
+ * Filters applicable to locations
622
+ * Uses shared filter types for consistency
623
+ */
624
+ export type LocationFilter =
625
+ | HasNameFilter
626
+ | WillpowerComparisonFilter
627
+ | MoveCostComparisonFilter
628
+ // Source/Reference
629
+ | SourceFilter
630
+ // Zone/Owner
631
+ | ZoneFilter
632
+ | OwnerFilter;
633
+
634
+ /**
635
+ * Base properties shared by all location query variants
636
+ */
637
+ export type LocationQueryBase = TargetDSL<LocationFilter[], LorcanaContext>;
638
+
639
+ /** Target exactly N locations */
640
+ export interface ExactCountLocationQuery extends LocationQueryBase {
641
+ count: number | { exactly: number };
642
+ }
643
+
644
+ /** Target up to N locations */
645
+ export interface UpToCountLocationQuery extends LocationQueryBase {
646
+ count: { upTo: number };
647
+ }
648
+
649
+ /** Target all matching locations */
650
+ export interface AllMatchingLocationQuery extends LocationQueryBase {
651
+ count: "all";
652
+ }
653
+
654
+ /**
655
+ * Complex location targeting with filters
656
+ */
657
+ export type LocationTargetQuery =
658
+ | ExactCountLocationQuery
659
+ | UpToCountLocationQuery
660
+ | AllMatchingLocationQuery;
661
+
662
+ export type LocationTarget =
663
+ | LocationTargetEnum
664
+ | LocationTargetQuery
665
+ | CardReference;
666
+
667
+ // ============================================================================
668
+ // Item Targeting
669
+ // ============================================================================
670
+
671
+ /**
672
+ * Common item targeting patterns
673
+ */
674
+ export type ItemTargetEnum =
675
+ | "CHOSEN_ITEM"
676
+ | "CHOSEN_OPPOSING_ITEM"
677
+ | "YOUR_ITEMS"
678
+ | "YOUR_ITEM" // Singular - one of your items
679
+ | "ALL_ITEMS"
680
+ | "ALL_OPPOSING_ITEMS"
681
+ | "THIS_ITEM"; // For abilities on items
682
+
683
+ // ============================================================================
684
+ // Item Targeting - Strict Query Variants
685
+ // ============================================================================
686
+
687
+ /**
688
+ * Filters applicable to items
689
+ * Uses shared filter types for consistency
690
+ */
691
+ export type ItemFilter =
692
+ | HasNameFilter
693
+ | CostComparisonFilter
694
+ | ExertedFilter
695
+ | ReadyFilter
696
+ // Source/Reference
697
+ | SourceFilter
698
+ // Zone/Owner
699
+ | ZoneFilter
700
+ | OwnerFilter;
701
+
702
+ /**
703
+ * Base properties shared by all item query variants
704
+ */
705
+ export type ItemQueryBase = TargetDSL<ItemFilter[], LorcanaContext>;
706
+
707
+ /** Target exactly N items */
708
+ export interface ExactCountItemQuery extends ItemQueryBase {
709
+ count: number | { exactly: number };
710
+ }
711
+
712
+ /** Target up to N items */
713
+ export interface UpToCountItemQuery extends ItemQueryBase {
714
+ count: { upTo: number };
715
+ }
716
+
717
+ /** Target all matching items */
718
+ export interface AllMatchingItemQuery extends ItemQueryBase {
719
+ count: "all";
720
+ }
721
+
722
+ /**
723
+ * Complex item targeting with filters
724
+ */
725
+ export type ItemTargetQuery =
726
+ | ExactCountItemQuery
727
+ | UpToCountItemQuery
728
+ | AllMatchingItemQuery;
729
+
730
+ export type ItemTarget = ItemTargetEnum | ItemTargetQuery | CardReference;
731
+
732
+ // ============================================================================
733
+ // Card Targeting (any card type)
734
+ // ============================================================================
735
+
736
+ /**
737
+ * Common card targeting patterns (any type)
738
+ */
739
+ export type CardTargetEnum =
740
+ | "CHOSEN_CARD"
741
+ | "CHOSEN_CARD_FROM_HAND"
742
+ | "CHOSEN_CARD_FROM_DISCARD"
743
+ | "TOP_CARD_OF_DECK"
744
+ | "revealed"
745
+ // Additional card targets for parser support
746
+ | "CHARACTER_FROM_DISCARD" // Character card from discard pile
747
+ | "SUPPORT_CHARACTER_FROM_DISCARD" // Support character from discard
748
+ | "CHOSEN_CHARACTER_OR_ITEM_COST_3_OR_LESS" // Character or item with cost 3 or less
749
+ // Extended card targets for card text coverage
750
+ | "CARD_FROM_ANY_DISCARD" // Card from any player's discard pile
751
+ | "ACTION_FROM_DISCARD" // Action card from discard pile
752
+ | "ITEM_FROM_DISCARD" // Item card from discard pile
753
+ | "CHARACTER_OR_ITEM" // Character or item card
754
+ | "BANISHED_CHARACTER"; // The character that was banished
755
+
756
+ export type CardTarget =
757
+ | CardTargetEnum
758
+ | CharacterTarget
759
+ | LocationTarget
760
+ | ItemTarget;
761
+
762
+ // ============================================================================
763
+ // Type Guards
764
+ // ============================================================================
765
+
766
+ /**
767
+ * Check if a character target is a query (vs enum)
768
+ */
769
+ export function isCharacterTargetQuery(
770
+ target: CharacterTarget,
771
+ ): target is CharacterTargetQuery {
772
+ return typeof target === "object"; // && (target as any).selector !== undefined;
773
+ }
774
+
775
+ /**
776
+ * Check if a location target is a query (vs enum)
777
+ */
778
+ export function isLocationTargetQuery(
779
+ target: LocationTarget,
780
+ ): target is LocationTargetQuery {
781
+ return typeof target === "object"; // && (target as any).selector !== undefined;
782
+ }
783
+
784
+ /**
785
+ * Check if an item target is a query (vs enum)
786
+ */
787
+ export function isItemTargetQuery(
788
+ target: ItemTarget,
789
+ ): target is ItemTargetQuery {
790
+ return typeof target === "object"; // && (target as any).selector !== undefined;
791
+ }