@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,530 @@
1
+ /**
2
+ * Trigger Types for Lorcana Abilities
3
+ *
4
+ * Defines when triggered abilities activate. Lorcana uses three timing words:
5
+ * - "When" - triggers once when event occurs
6
+ * - "Whenever" - triggers each time event occurs
7
+ * - "At" - triggers at a specific game phase
8
+ *
9
+ * The trigger system uses the same targeting DSL as the rest of the codebase
10
+ * to filter what causes a trigger to fire.
11
+ *
12
+ * @example "When you play this character, draw 2 cards"
13
+ * ```typescript
14
+ * { event: "play", timing: "when", on: "SELF" }
15
+ * ```
16
+ *
17
+ * @example "Whenever this character quests, gain 1 lore"
18
+ * ```typescript
19
+ * { event: "quest", timing: "whenever", on: "SELF" }
20
+ * ```
21
+ *
22
+ * @example "Whenever you play a character, draw a card"
23
+ * ```typescript
24
+ * { event: "play", timing: "whenever", on: { controller: "you", cardType: "character" } }
25
+ * ```
26
+ */
27
+
28
+ import type { Condition } from "./condition-types";
29
+ import type {
30
+ CardReference,
31
+ CharacterFilter,
32
+ ItemFilter,
33
+ LocationFilter,
34
+ TargetController,
35
+ } from "./target-types";
36
+
37
+ // ============================================================================
38
+ // Trigger Timing
39
+ // ============================================================================
40
+
41
+ /**
42
+ * When the trigger activates relative to the event
43
+ *
44
+ * - "when" - typically one-time triggers
45
+ * - "whenever" - repeatable triggers
46
+ * - "at" - phase-based triggers
47
+ */
48
+ export type TriggerTiming = "when" | "whenever" | "at";
49
+
50
+ // ============================================================================
51
+ // Trigger Events
52
+ // ============================================================================
53
+
54
+ /**
55
+ * Base trigger events - what action occurred
56
+ *
57
+ * These are the fundamental game actions. Use the `on` field
58
+ * to specify what entity triggered the event.
59
+ */
60
+ export type TriggerEvent =
61
+ // Card actions
62
+ | "play" // A card is played
63
+ | "banish" // A card is banished
64
+ | "leave-play" // A card leaves play (any reason)
65
+
66
+ // Character actions
67
+ | "quest" // A character quests
68
+ | "challenge" // A character challenges another
69
+ | "challenged" // A character is challenged
70
+ | "damage" // A card takes damage
71
+ | "exert" // A card is exerted
72
+ | "ready" // A card is readied
73
+ | "move" // A character moves to a location
74
+ | "sing" // A character sings a song
75
+ | "be-chosen" // A card is chosen by an action or ability
76
+
77
+ // Combat-specific
78
+ | "banish-in-challenge" // Banished specifically during a challenge
79
+ | "deal-damage" // Deals damage (as opposed to taking damage)
80
+
81
+ // Resource events
82
+ | "draw" // A card is drawn
83
+ | "discard" // A card is discarded
84
+ | "ink" // A card is put into inkwell
85
+ | "gain-lore" // Lore is gained
86
+ | "lose-lore" // Lore is lost
87
+
88
+ // Turn phases
89
+ | "start-turn" // Start of turn
90
+ | "end-turn" // End of turn
91
+
92
+ // Additional events for parser support
93
+ | "remove-damage" // Damage is removed from a character
94
+ | "return-to-hand" // A card is returned to hand
95
+ // Event aliases for parser compatibility
96
+ | "start-of-turn" // Alias for start-turn
97
+ | "end-of-turn" // Alias for end-turn
98
+ // Extended events for card text coverage
99
+ | "put-into-inkwell" // A card is put into inkwell
100
+ | "add-to-inkwell" // Alias for put-into-inkwell
101
+ | "put-card-under" // A card is put under another card
102
+ // Additional trigger events
103
+ | "support" // Support ability triggers
104
+ | "inkwell"; // Card put into inkwell
105
+
106
+ // ============================================================================
107
+ // Trigger Subject (what triggers the event)
108
+ // ============================================================================
109
+
110
+ /**
111
+ * Card type filter for triggers
112
+ */
113
+ export type TriggerCardType =
114
+ | "character"
115
+ | "action"
116
+ | "item"
117
+ | "location"
118
+ | "song"
119
+ | "card"; // any card type
120
+
121
+ /**
122
+ * Simple trigger subject - common patterns as string literals
123
+ */
124
+ export type TriggerSubjectEnum =
125
+ | "SELF" // This card
126
+ | "YOUR_CHARACTERS" // Any of your characters
127
+ | "YOUR_OTHER_CHARACTERS" // Your characters except this one
128
+ | "OPPONENT_CHARACTERS" // Opponent's characters
129
+ | "OPPOSING_CHARACTERS" // Alias for OPPONENT_CHARACTERS
130
+ | "OTHER_CHARACTERS" // Any character except this one
131
+ | "ANY_CHARACTER" // Any character
132
+ | "YOUR_ITEMS" // Any of your items
133
+ | "YOUR_OTHER_ITEMS" // Your items except this one
134
+ | "YOUR_LOCATIONS" // Any of your locations
135
+ | "YOUR_ACTIONS" // Any of your actions
136
+ | "YOUR_SONGS" // Any of your songs
137
+ | "YOU" // The controller (for lore/draw events)
138
+ | "OPPONENT" // The opponent (for lore/draw events)
139
+ | "ANY_PLAYER" // Any player
140
+ // Classification-based triggers
141
+ | "FLOODBORN_CHARACTERS" // Floodborn characters you play
142
+ | "SELF_OR_SEVEN_DWARFS_CHARACTERS" // This character or Seven Dwarfs
143
+ | "CINDERELLA_CHARACTERS" // Characters named Cinderella
144
+ | "YOUR_CHARACTERS_COST_4_OR_MORE" // Your characters with cost 4+
145
+ // Extended trigger subjects for card text coverage
146
+ | "SONGS" // Songs (for song-related triggers)
147
+ | "YOUR_BROOM_CHARACTERS" // Your Broom characters
148
+ | "YOUR_MUSKETEER_CHARACTERS" // Your Musketeer characters
149
+ | "YOUR_BODYGUARD_CHARACTERS" // Your Bodyguard characters
150
+ | "CONTROLLER" // The controller of this card
151
+ | "CHARACTERS_HERE" // Characters at this location
152
+ | "YOUR_OTHER_STEEL_CHARACTERS" // Your other Steel characters
153
+ // Additional trigger subjects for more card coverage
154
+ | "CHARACTERS_AT_LOCATION" // Characters at a location
155
+ | "CHARACTERS_MOVED_HERE" // Characters that moved here
156
+ | "OPPONENTS_CARDS" // Opponent's cards
157
+ | "YOUR_PIRATE_CHARACTERS" // Your Pirate characters
158
+ | "CHARACTER_HERE" // Character at this location (singular)
159
+ | "SONG" // Song (singular)
160
+ | "YOUR_CHARACTERS_OR_LOCATIONS" // Your characters or locations
161
+ | "YOUR_OTHER_AMETHYST_CHARACTERS" // Your other Amethyst characters
162
+ | "YOUR_CHARACTERS_OR_LOCATIONS_WITH_CARD_UNDER"; // Your characters or locations with card under
163
+
164
+ /**
165
+ * Query-based trigger subject for complex filtering
166
+ *
167
+ * Uses the same DSL patterns as targeting
168
+ */
169
+ export interface TriggerSubjectQuery {
170
+ /** Whose card triggers this */
171
+ controller?: TargetController;
172
+
173
+ /** What type of card */
174
+ cardType?: TriggerCardType;
175
+
176
+ /** Additional filters (e.g., damaged, has keyword) - supports all card types */
177
+ filters?: (CharacterFilter | ItemFilter | LocationFilter)[];
178
+
179
+ /** Exclude self from matching */
180
+ excludeSelf?: boolean;
181
+
182
+ /** Classification filter (e.g., "Floodborn", "Princess") */
183
+ classification?: string;
184
+
185
+ /** Name filter */
186
+ name?: string;
187
+
188
+ /** Has specific keyword */
189
+ hasKeyword?: string;
190
+ }
191
+
192
+ /**
193
+ * Challenge-specific trigger context
194
+ *
195
+ * Used for triggers that fire during challenges to specify
196
+ * whether we're interested in the attacker or defender role
197
+ */
198
+ export interface ChallengeTriggerContext {
199
+ /** Which role in the challenge triggers this */
200
+ role: "attacker" | "defender" | "either";
201
+
202
+ /** Additional filters for the character in that role */
203
+ filters?: CharacterFilter[];
204
+ }
205
+
206
+ /**
207
+ * Extended trigger for challenge events with combat context
208
+ *
209
+ * @example "Whenever this character challenges a damaged character"
210
+ * ```typescript
211
+ * {
212
+ * event: "challenge",
213
+ * timing: "whenever",
214
+ * on: "SELF",
215
+ * defender: { filters: [{ type: "damaged" }] }
216
+ * }
217
+ * ```
218
+ */
219
+ export interface ChallengeTrigger extends BaseTrigger {
220
+ event: "challenge" | "challenged" | "banish-in-challenge";
221
+
222
+ /** Filter/context for the defender in the challenge */
223
+ defender?: {
224
+ filters?: CharacterFilter[];
225
+ controller?: TargetController;
226
+ };
227
+
228
+ /** Filter/context for the attacker in the challenge */
229
+ attacker?: {
230
+ filters?: CharacterFilter[];
231
+ controller?: TargetController;
232
+ };
233
+ }
234
+
235
+ /**
236
+ * Type guard to check if a trigger is a challenge trigger
237
+ */
238
+ export function isChallengeTrigger(
239
+ trigger: Trigger,
240
+ ): trigger is ChallengeTrigger {
241
+ return (
242
+ trigger.event === "challenge" ||
243
+ trigger.event === "challenged" ||
244
+ trigger.event === "banish-in-challenge"
245
+ );
246
+ }
247
+
248
+ /**
249
+ * Helper type for effects that need to reference the trigger source
250
+ *
251
+ * @example "Whenever a character is banished, draw a card for each damage on IT"
252
+ * Here "IT" refers to the trigger source (the banished character)
253
+ */
254
+ export type TriggerSourceReference = CardReference & { ref: "trigger-source" };
255
+
256
+ /**
257
+ * What entity triggers the event
258
+ */
259
+ export type TriggerSubject = TriggerSubjectEnum | TriggerSubjectQuery;
260
+
261
+ // ============================================================================
262
+ // Trigger Definition
263
+ // ============================================================================
264
+
265
+ /**
266
+ * Complete trigger definition
267
+ *
268
+ * @example "When you play this character"
269
+ * ```typescript
270
+ * { event: "play", timing: "when", on: "SELF" }
271
+ * ```
272
+ *
273
+ * @example "Whenever one of your characters quests"
274
+ * ```typescript
275
+ * { event: "quest", timing: "whenever", on: "YOUR_CHARACTERS" }
276
+ * ```
277
+ *
278
+ * @example "Whenever you play a Floodborn character"
279
+ * ```typescript
280
+ * {
281
+ * event: "play",
282
+ * timing: "whenever",
283
+ * on: { controller: "you", cardType: "character", classification: "Floodborn" }
284
+ * }
285
+ * ```
286
+ *
287
+ * @example "Whenever an opposing damaged character is banished"
288
+ * ```typescript
289
+ * {
290
+ * event: "banish",
291
+ * timing: "whenever",
292
+ * on: { controller: "opponent", cardType: "character", filters: [{ type: "damaged" }] }
293
+ * }
294
+ * ```
295
+ *
296
+ * @example "At the start of your turn"
297
+ * ```typescript
298
+ * { event: "start-turn", timing: "at", on: "YOU" }
299
+ * ```
300
+ *
301
+ * @example "The first time each turn this character quests"
302
+ * ```typescript
303
+ * {
304
+ * event: "quest",
305
+ * timing: "whenever",
306
+ * on: "SELF",
307
+ * restrictions: [{ type: "first-time-each-turn" }]
308
+ * }
309
+ * ```
310
+ */
311
+
312
+ export interface BaseTrigger {
313
+ /** The event that causes this trigger to fire */
314
+ event?: TriggerEvent;
315
+
316
+ /**
317
+ * Multiple events that can cause this trigger to fire
318
+ * Used for "When you play this character and when he leaves play"
319
+ */
320
+ events?: TriggerEvent[] | Array<{ event: string; on: string }>;
321
+
322
+ /** Timing word (when/whenever/at) - optional for parser compatibility */
323
+ timing?: TriggerTiming | "when-or-whenever";
324
+
325
+ /**
326
+ * What entity triggers this event
327
+ * - For card events: which card (SELF, query, etc.)
328
+ * - For player events: which player (YOU, OPPONENT, etc.)
329
+ * - For turn events: whose turn (YOU, OPPONENT)
330
+ */
331
+ on?: TriggerSubject;
332
+
333
+ /**
334
+ * Additional restrictions on when this trigger fires
335
+ * Uses the shared Restriction type from ability-types
336
+ */
337
+ restrictions?: TriggerRestriction[];
338
+
339
+ /**
340
+ * Challenge context - alternative to defender/attacker for challenge events
341
+ * Used by parser for simpler challenge-related triggers
342
+ */
343
+ challengeContext?: ChallengeTriggerContext;
344
+
345
+ /**
346
+ * Condition that must be true for the trigger to fire
347
+ * Used by parser for conditional triggers
348
+ */
349
+ condition?: Condition;
350
+ }
351
+
352
+ export type Trigger = BaseTrigger | ChallengeTrigger;
353
+
354
+ /**
355
+ * Restrictions specific to triggers
356
+ * Note: This is a subset of the full Restriction type, kept separate
357
+ * to avoid circular imports. The types are compatible.
358
+ */
359
+ export type TriggerRestriction =
360
+ // Usage tracking
361
+ | { type: "once-per-turn" }
362
+ | { type: "first-time-each-turn" }
363
+
364
+ // Turn phase
365
+ | { type: "during-turn"; whose: "your" | "opponent" }
366
+
367
+ // Context
368
+ | { type: "in-challenge" };
369
+
370
+ // ============================================================================
371
+ // Pre-built Trigger Patterns
372
+ // ============================================================================
373
+
374
+ /**
375
+ * Common trigger patterns for convenience
376
+ */
377
+ export const COMMON_TRIGGERS = {
378
+ /** "When you play this character" */
379
+ WHEN_PLAY_SELF: {
380
+ event: "play",
381
+ timing: "when",
382
+ on: "SELF",
383
+ } as const satisfies Trigger,
384
+
385
+ /** "Whenever this character quests" */
386
+ WHENEVER_QUEST_SELF: {
387
+ event: "quest",
388
+ timing: "whenever",
389
+ on: "SELF",
390
+ } as const satisfies Trigger,
391
+
392
+ /** "Whenever this character challenges" */
393
+ WHENEVER_CHALLENGE_SELF: {
394
+ event: "challenge",
395
+ timing: "whenever",
396
+ on: "SELF",
397
+ } as const satisfies Trigger,
398
+
399
+ /** "Whenever this character is challenged" */
400
+ WHENEVER_CHALLENGED_SELF: {
401
+ event: "challenged",
402
+ timing: "whenever",
403
+ on: "SELF",
404
+ } as const satisfies Trigger,
405
+
406
+ /** "When this character is banished" */
407
+ WHEN_BANISH_SELF: {
408
+ event: "banish",
409
+ timing: "when",
410
+ on: "SELF",
411
+ } as const satisfies Trigger,
412
+
413
+ /** "When this character is banished in a challenge" */
414
+ WHEN_BANISH_IN_CHALLENGE: {
415
+ event: "banish-in-challenge",
416
+ timing: "when",
417
+ on: "SELF",
418
+ } as const satisfies Trigger,
419
+
420
+ /** "At the start of your turn" */
421
+ AT_START_OF_TURN: {
422
+ event: "start-turn",
423
+ timing: "at",
424
+ on: "YOU",
425
+ } as const satisfies Trigger,
426
+
427
+ /** "At the end of your turn" */
428
+ AT_END_OF_TURN: {
429
+ event: "end-turn",
430
+ timing: "at",
431
+ on: "YOU",
432
+ } as const satisfies Trigger,
433
+
434
+ /** "Whenever you play a character" */
435
+ WHENEVER_PLAY_CHARACTER: {
436
+ event: "play",
437
+ timing: "whenever",
438
+ on: { controller: "you", cardType: "character" },
439
+ } as const satisfies Trigger,
440
+
441
+ /** "Whenever you play a song" */
442
+ WHENEVER_PLAY_SONG: {
443
+ event: "play",
444
+ timing: "whenever",
445
+ on: { controller: "you", cardType: "song" },
446
+ } as const satisfies Trigger,
447
+
448
+ /** "Whenever you play a Floodborn character" */
449
+ WHENEVER_PLAY_FLOODBORN: {
450
+ event: "play",
451
+ timing: "whenever",
452
+ on: {
453
+ controller: "you",
454
+ cardType: "character",
455
+ classification: "Floodborn",
456
+ },
457
+ } as const satisfies Trigger,
458
+
459
+ /** "When this character leaves play" */
460
+ WHEN_LEAVE_PLAY: {
461
+ event: "leave-play",
462
+ timing: "when",
463
+ on: "SELF",
464
+ } as const satisfies Trigger,
465
+
466
+ /** "Whenever one of your other characters is banished" */
467
+ WHENEVER_YOUR_OTHER_CHARACTER_BANISHED: {
468
+ event: "banish",
469
+ timing: "whenever",
470
+ on: "YOUR_OTHER_CHARACTERS",
471
+ } as const satisfies Trigger,
472
+
473
+ /** "Whenever an opposing character is banished" */
474
+ WHENEVER_OPPONENT_CHARACTER_BANISHED: {
475
+ event: "banish",
476
+ timing: "whenever",
477
+ on: "OPPONENT_CHARACTERS",
478
+ } as const satisfies Trigger,
479
+
480
+ /** "Whenever you draw a card" */
481
+ WHENEVER_YOU_DRAW: {
482
+ event: "draw",
483
+ timing: "whenever",
484
+ on: "YOU",
485
+ } as const satisfies Trigger,
486
+
487
+ /** "Whenever you gain lore" */
488
+ WHENEVER_YOU_GAIN_LORE: {
489
+ event: "gain-lore",
490
+ timing: "whenever",
491
+ on: "YOU",
492
+ } as const satisfies Trigger,
493
+ } as const;
494
+
495
+ // ============================================================================
496
+ // Type Guards
497
+ // ============================================================================
498
+
499
+ /**
500
+ * Check if trigger subject is a query object
501
+ */
502
+ export function isTriggerSubjectQuery(
503
+ subject: TriggerSubject,
504
+ ): subject is TriggerSubjectQuery {
505
+ return typeof subject === "object" && subject !== null;
506
+ }
507
+
508
+ /**
509
+ * Check if a trigger is a self-referential trigger
510
+ */
511
+ export function isSelfTrigger(trigger: Trigger): boolean {
512
+ return trigger.on === "SELF";
513
+ }
514
+
515
+ /**
516
+ * Check if a trigger is phase-based (at start/end of turn)
517
+ */
518
+ export function isPhaseTrigger(trigger: Trigger): boolean {
519
+ return trigger.event === "start-turn" || trigger.event === "end-turn";
520
+ }
521
+
522
+ /**
523
+ * Check if trigger has a specific restriction
524
+ */
525
+ export function hasRestriction(
526
+ trigger: Trigger,
527
+ type: TriggerRestriction["type"],
528
+ ): boolean {
529
+ return trigger.restrictions?.some((r) => r.type === type) ?? false;
530
+ }