@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.
- package/package.json +46 -0
- package/src/abilities/ability-types.ts +766 -0
- package/src/abilities/condition-types.ts +1202 -0
- package/src/abilities/cost-types.ts +344 -0
- package/src/abilities/effect-types/amount-types.ts +115 -0
- package/src/abilities/effect-types/basic-effects.ts +200 -0
- package/src/abilities/effect-types/combined-types.ts +564 -0
- package/src/abilities/effect-types/control-flow.ts +317 -0
- package/src/abilities/effect-types/index.ts +136 -0
- package/src/abilities/effect-types/modifier-effects.ts +248 -0
- package/src/abilities/effect-types/movement-effects.ts +216 -0
- package/src/abilities/effect-types/scry-effects.ts +269 -0
- package/src/abilities/helpers/Abilities.ts +172 -0
- package/src/abilities/helpers/Conditions.ts +266 -0
- package/src/abilities/helpers/Costs.ts +83 -0
- package/src/abilities/helpers/Effects.ts +182 -0
- package/src/abilities/helpers/Targets.ts +193 -0
- package/src/abilities/helpers/Triggers.ts +167 -0
- package/src/abilities/helpers/index.ts +42 -0
- package/src/abilities/index.ts +401 -0
- package/src/abilities/target-types.ts +791 -0
- package/src/abilities/trigger-types.ts +530 -0
- package/src/cards/card-types.ts +502 -0
- package/src/cards/classifications.ts +86 -0
- package/src/cards/deck-validation.ts +71 -0
- package/src/cards/index.ts +77 -0
- package/src/cards/ink-types.ts +55 -0
- package/src/game/index.ts +14 -0
- package/src/game/state-types.ts +258 -0
- package/src/index.ts +16 -0
|
@@ -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
|
+
}
|