@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,344 @@
1
+ /**
2
+ * Cost Types for Lorcana Abilities
3
+ *
4
+ * Defines the costs required to activate abilities in Lorcana.
5
+ * Activated abilities use the format: "{cost} - {effect}"
6
+ *
7
+ * Common costs:
8
+ * - {E} = Exert this card
9
+ * - {d} {I} = Pay ink (where d is a number)
10
+ * - Banish this card/item
11
+ * - Discard cards
12
+ *
13
+ * @example "{E} - Draw a card" (exert cost)
14
+ * @example "{E}, 2 {I} - Deal 3 damage" (exert + ink cost)
15
+ * @example "Banish this item - Gain 3 lore" (banish self cost)
16
+ */
17
+
18
+ import type { CardType } from "../cards/card-types";
19
+
20
+ // ============================================================================
21
+ // Individual Cost Components
22
+ // ============================================================================
23
+
24
+ /**
25
+ * Exert cost - tap/exhaust this card
26
+ */
27
+ export interface ExertCost {
28
+ type: "exert";
29
+ /** What to exert (defaults to self) */
30
+ target?: "self" | "another-character" | "item";
31
+ }
32
+
33
+ /**
34
+ * Ink cost - pay from inkwell
35
+ */
36
+ export interface InkCost {
37
+ type: "ink";
38
+ amount: number;
39
+ }
40
+
41
+ /**
42
+ * Banish cost - sacrifice a card
43
+ */
44
+ export type BanishCost =
45
+ | {
46
+ type: "banish";
47
+ /** Simple banish targets */
48
+ target: "self" | "item" | "character";
49
+ }
50
+ | {
51
+ type: "banish";
52
+ /** Banish a specific card type/name */
53
+ target: "specific";
54
+ /** For "specific" target - card type or name required */
55
+ cardType?: CardType;
56
+ /** For "specific" target - card name required */
57
+ cardName?: string;
58
+ };
59
+
60
+ /**
61
+ * Discard cost - discard cards from hand
62
+ */
63
+ export interface DiscardCost {
64
+ type: "discard";
65
+ /** How many cards to discard */
66
+ amount: number;
67
+ /** Whether the player chooses which cards (vs random) */
68
+ chosen?: boolean;
69
+ /** Specific card type required */
70
+ cardType?: CardType | "song";
71
+ /** Specific card name required */
72
+ cardName?: string;
73
+ }
74
+
75
+ /**
76
+ * Deal damage to self cost
77
+ */
78
+ export interface DamageSelfCost {
79
+ type: "damage-self";
80
+ amount: number;
81
+ }
82
+
83
+ /**
84
+ * Return card to hand cost
85
+ */
86
+ export interface ReturnToHandCost {
87
+ type: "return-to-hand";
88
+ /** What to return */
89
+ target:
90
+ | "self" // Return this card
91
+ | "another-character" // Return another of your characters
92
+ | "item"; // Return one of your items
93
+ }
94
+
95
+ /**
96
+ * Put card under another card cost
97
+ */
98
+ export interface PutUnderCost {
99
+ type: "put-under";
100
+ /** What goes under */
101
+ source: "card-from-hand" | "top-of-deck";
102
+ /** Card type requirement */
103
+ cardType?: CardType;
104
+ }
105
+
106
+ /**
107
+ * Exert another card cost (not self)
108
+ */
109
+ export interface ExertOtherCost {
110
+ type: "exert-other";
111
+ /** What to exert */
112
+ target: "character" | "item";
113
+ /** How many to exert */
114
+ amount?: number;
115
+ }
116
+
117
+ // ============================================================================
118
+ // Combined Cost
119
+ // ============================================================================
120
+
121
+ /**
122
+ * Individual cost component
123
+ */
124
+ export type CostComponent =
125
+ | ExertCost
126
+ | InkCost
127
+ | BanishCost
128
+ | DiscardCost
129
+ | DamageSelfCost
130
+ | ReturnToHandCost
131
+ | PutUnderCost
132
+ | ExertOtherCost;
133
+
134
+ /**
135
+ * Complete ability cost - can have multiple components
136
+ *
137
+ * @example "{E}" - just exert
138
+ * ```typescript
139
+ * { exert: true }
140
+ * ```
141
+ *
142
+ * @example "{E}, 2 {I}" - exert and pay 2 ink
143
+ * ```typescript
144
+ * { exert: true, ink: 2 }
145
+ * ```
146
+ *
147
+ * @example "{E}, Banish this item" - exert and banish self
148
+ * ```typescript
149
+ * { exert: true, banishSelf: true }
150
+ * ```
151
+ *
152
+ * @example "Choose and discard a card" - discard cost
153
+ * ```typescript
154
+ * { discardCards: 1, discardChosen: true }
155
+ * ```
156
+ *
157
+ * @remarks
158
+ * **Valid Cost Combinations:**
159
+ * - `exert` can be combined with any other cost
160
+ * - `ink` must be a positive number (omit if no ink cost)
161
+ * - Banish costs (`banishSelf`, `banishItem`, `banishCharacter`) are mutually exclusive
162
+ * - `discardChosen` requires `discardCards` to also be set
163
+ * - `discardCardType`/`discardCardName` require `discardCards` to also be set
164
+ * - Use `components` for complex costs not covered by simple fields
165
+ *
166
+ * @todo Consider refactoring to discriminated unions for better type safety in a future major version
167
+ */
168
+ export interface AbilityCost {
169
+ /** Whether to exert this card */
170
+ exert?: boolean;
171
+
172
+ /** Ink to pay from inkwell (must be positive if present) */
173
+ ink?: number;
174
+
175
+ /** Banish this card (mutually exclusive with banishItem/banishCharacter) */
176
+ banishSelf?: boolean;
177
+
178
+ /** Banish one of your items (mutually exclusive with banishSelf/banishCharacter) */
179
+ banishItem?: boolean;
180
+
181
+ /** Banish one of your characters (mutually exclusive with banishSelf/banishItem) */
182
+ banishCharacter?: boolean;
183
+
184
+ /** Banish another card (generic) */
185
+ banishOther?: boolean;
186
+
187
+ /** Number of cards to discard from hand */
188
+ discardCards?: number;
189
+
190
+ /** Alias for discardCards (singular form) */
191
+ discardCard?: number;
192
+
193
+ /** Whether discarded cards are chosen (requires discardCards) */
194
+ discardChosen?: boolean;
195
+
196
+ /** Specific card type required for discard (requires discardCards) */
197
+ discardCardType?: CardType | "song";
198
+
199
+ /** Specific card name required for discard (requires discardCards) */
200
+ discardCardName?: string;
201
+
202
+ /** Damage to deal to this character */
203
+ damageSelf?: number;
204
+
205
+ /** Return this card to hand */
206
+ returnSelfToHand?: boolean;
207
+
208
+ /** Return another character to hand */
209
+ returnCharacterToHand?: boolean;
210
+
211
+ /** Number of items to exert (other than self) */
212
+ exertItems?: number;
213
+
214
+ /** Number of characters to exert (other than self) */
215
+ exertCharacters?: number;
216
+
217
+ /** Exert a single character (singular form) */
218
+ exertCharacter?: boolean;
219
+
220
+ /** Exert another card (generic) */
221
+ exertOther?: boolean;
222
+
223
+ /** Target for cost (e.g., which character to exert) */
224
+ target?: string;
225
+
226
+ /**
227
+ * Discard cost as an object (alternative format)
228
+ * Used by parser for complex discard costs
229
+ */
230
+ discard?: {
231
+ cardType?: CardType | "song" | "character";
232
+ amount?: number;
233
+ };
234
+
235
+ /**
236
+ * Complex cost components (for less common costs)
237
+ * Used when the simple fields above don't suffice
238
+ */
239
+ components?: CostComponent[];
240
+ }
241
+
242
+ // ============================================================================
243
+ // Cost Builders (convenience)
244
+ // ============================================================================
245
+
246
+ /**
247
+ * Create an exert-only cost
248
+ */
249
+ export function exertCost(): AbilityCost {
250
+ return { exert: true };
251
+ }
252
+
253
+ /**
254
+ * Create an exert + ink cost
255
+ */
256
+ export function exertAndInkCost(ink: number): AbilityCost {
257
+ return { exert: true, ink };
258
+ }
259
+
260
+ /**
261
+ * Create a banish-self cost
262
+ */
263
+ export function banishSelfCost(): AbilityCost {
264
+ return { banishSelf: true };
265
+ }
266
+
267
+ /**
268
+ * Create an exert + banish item cost
269
+ */
270
+ export function exertAndBanishItemCost(): AbilityCost {
271
+ return { exert: true, banishItem: true };
272
+ }
273
+
274
+ /**
275
+ * Create a discard cost
276
+ */
277
+ export function discardCost(amount: number, chosen = true): AbilityCost {
278
+ return { discardCards: amount, discardChosen: chosen };
279
+ }
280
+
281
+ // ============================================================================
282
+ // Type Guards
283
+ // ============================================================================
284
+
285
+ /**
286
+ * Check if a cost requires exerting
287
+ */
288
+ export function requiresExert(cost: AbilityCost): boolean {
289
+ return cost.exert === true;
290
+ }
291
+
292
+ /**
293
+ * Check if a cost requires paying ink
294
+ */
295
+ export function requiresInk(cost: AbilityCost): boolean {
296
+ return (cost.ink ?? 0) > 0;
297
+ }
298
+
299
+ /**
300
+ * Check if a cost requires banishing something
301
+ */
302
+ export function requiresBanish(cost: AbilityCost): boolean {
303
+ return (
304
+ cost.banishSelf === true ||
305
+ cost.banishItem === true ||
306
+ cost.banishCharacter === true
307
+ );
308
+ }
309
+
310
+ /**
311
+ * Check if a cost requires discarding cards
312
+ */
313
+ export function requiresDiscard(cost: AbilityCost): boolean {
314
+ return (cost.discardCards ?? 0) > 0;
315
+ }
316
+
317
+ /**
318
+ * Get total ink cost
319
+ */
320
+ export function getInkCost(cost: AbilityCost): number {
321
+ return cost.ink ?? 0;
322
+ }
323
+
324
+ /**
325
+ * Check if cost is "free" (no cost)
326
+ */
327
+ export function isFreeCost(cost: AbilityCost): boolean {
328
+ return (
329
+ !(
330
+ cost.exert ||
331
+ cost.ink ||
332
+ cost.banishSelf ||
333
+ cost.banishItem ||
334
+ cost.banishCharacter ||
335
+ cost.discardCards ||
336
+ cost.damageSelf ||
337
+ cost.returnSelfToHand ||
338
+ cost.returnCharacterToHand ||
339
+ cost.exertItems ||
340
+ cost.exertCharacters
341
+ ) &&
342
+ (!cost.components || cost.components.length === 0)
343
+ );
344
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Shared Types for Effects
3
+ *
4
+ * Defines primitive types used across multiple effect modules:
5
+ * - Amount types (fixed or variable)
6
+ * - Effect duration types
7
+ */
8
+
9
+ import type { CardTarget, CharacterTarget } from "../target-types";
10
+
11
+ // ============================================================================
12
+ // Amount Types
13
+ // ============================================================================
14
+
15
+ /**
16
+ * Amount can be a fixed number, variable based on game state, or a string reference
17
+ */
18
+ export type Amount = number | VariableAmount | AmountString;
19
+
20
+ /**
21
+ * String-based amount references
22
+ */
23
+ export type AmountString =
24
+ | "all" // All damage, all cards, etc.
25
+ | "DISCARDED_COUNT" // Number of cards discarded
26
+ | "DISCARDED_CARD_LORE" // Lore value of discarded card
27
+ | "RETURNED_CARD_COST" // Cost of returned card
28
+ | "DAMAGE_DEALT" // Amount of damage dealt
29
+ | "OPPONENTS_DAMAGED_CHARACTER_COUNT" // Number of opponent's damaged characters
30
+ | "X" // Variable amount (determined at resolution)
31
+ // Extended amount references for card text coverage
32
+ | "DAMAGE_REMOVED" // Amount of damage removed
33
+ | "HAND" // Number of cards in hand
34
+ | "TARGET_COST" // Cost of target card
35
+ | "TARGET_STRENGTH" // Strength of target character
36
+ | "TARGET_WILLPOWER"; // Willpower of target character
37
+
38
+ /**
39
+ * Counter types for for-each amounts
40
+ */
41
+ export type ForEachCounterType =
42
+ | "characters"
43
+ | "damaged-characters"
44
+ | "items"
45
+ | "locations"
46
+ | "cards-in-hand"
47
+ | "cards-in-discard"
48
+ | "damage-on-self"
49
+ | "damage-on-target"
50
+ | "cards-under-self";
51
+
52
+ /**
53
+ * Variable amount calculated from game state
54
+ */
55
+ export type VariableAmount =
56
+ | { type: "damage-on-target" }
57
+ | { type: "damage-on-self" }
58
+ | {
59
+ type: "cards-in-hand";
60
+ controller: "you" | "opponent" | "opponents";
61
+ modifier?: number;
62
+ }
63
+ | { type: "characters-in-play"; controller: "you" | "opponent" | "opponents" }
64
+ | { type: "items-in-play"; controller: "you" | "opponent" }
65
+ | { type: "cards-in-discard"; controller: "you" | "opponent" }
66
+ | { type: "lore"; controller: "you" | "opponent" }
67
+ | { type: "strength-of"; target: CharacterTarget }
68
+ | { type: "willpower-of"; target: CharacterTarget }
69
+ | { type: "lore-value-of"; target: CharacterTarget }
70
+ | { type: "cost-of"; target: CardTarget }
71
+ | { type: "cards-under-self" }
72
+ | {
73
+ type: "classification-character-count";
74
+ classification: string;
75
+ controller: "you" | "opponent";
76
+ }
77
+ | { type: "locations-in-play"; controller: "you" | "opponent" }
78
+ // For-each based amounts
79
+ | {
80
+ type: "for-each";
81
+ counter: ForEachCounterType | { type: string; controller?: string };
82
+ count?: number | VariableAmount;
83
+ modifier?: number;
84
+ }
85
+ // Additional variable amounts
86
+ | { type: "count"; what?: string; controller?: string; of?: string }
87
+ | { type: "VARIABLE" } // Generic variable amount
88
+ | { type: "lore-lost" } // Amount of lore lost
89
+ | { type: "stat"; stat?: string; target?: string }; // Stat value
90
+
91
+ /**
92
+ * Check if amount is variable (vs fixed number)
93
+ */
94
+ export function isVariableAmount(amount: Amount): amount is VariableAmount {
95
+ return typeof amount === "object";
96
+ }
97
+
98
+ // ============================================================================
99
+ // Effect Duration Types
100
+ // ============================================================================
101
+
102
+ /**
103
+ * How long an effect lasts
104
+ */
105
+ export type EffectDuration =
106
+ | "this-turn"
107
+ | "until-start-of-next-turn"
108
+ | "until-end-of-turn"
109
+ | "permanent"
110
+ | "while-condition"
111
+ | "next-play-this-turn" // Used with static abilities
112
+ | "next-turn" // Until the start/end of their next turn
113
+ | "their-next-turn" // Until the opponent's next turn
114
+ | "while-in-play" // While the card is in play
115
+ | { type: string }; // Allow object-based durations for flexibility
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Basic Effect Types
3
+ *
4
+ * Core effect types for common game actions:
5
+ * - Draw/Discard cards
6
+ * - Deal/Remove damage
7
+ * - Gain/Lose lore
8
+ * - Exert/Ready/Banish cards
9
+ */
10
+
11
+ import type {
12
+ CharacterTarget,
13
+ ItemTarget,
14
+ LocationTarget,
15
+ PlayerTarget,
16
+ TargetZone,
17
+ } from "../target-types";
18
+ import type { Amount } from "./amount-types";
19
+
20
+ // ============================================================================
21
+ // Draw/Discard Effects
22
+ // ============================================================================
23
+
24
+ /**
25
+ * Draw cards effect
26
+ *
27
+ * @example "Draw 2 cards"
28
+ * @example "Each player draws a card"
29
+ */
30
+ export interface DrawEffect {
31
+ type: "draw";
32
+ amount?: Amount;
33
+ target?: PlayerTarget;
34
+ }
35
+
36
+ /**
37
+ * Discard cards effect
38
+ *
39
+ * @example "Choose and discard a card"
40
+ * @example "Each opponent discards a card at random"
41
+ */
42
+ export interface DiscardEffect {
43
+ type: "discard";
44
+ amount?: Amount;
45
+ target?: PlayerTarget;
46
+ /** Whether the affected player chooses which cards */
47
+ chosen?: boolean;
48
+ /** Who chooses (alternative to chosen) */
49
+ chosenBy?: "you" | "opponent" | "TARGET";
50
+ /** If not chosen, discard is random */
51
+ random?: boolean;
52
+ /** Discard from specific zone (default: hand) */
53
+ from?: TargetZone;
54
+ /** Filter for what can be discarded */
55
+ filter?: {
56
+ cardType?: string;
57
+ maxCost?: number;
58
+ classification?: string;
59
+ };
60
+ }
61
+
62
+ // ============================================================================
63
+ // Damage Effects
64
+ // ============================================================================
65
+
66
+ /**
67
+ * Deal damage effect
68
+ *
69
+ * @example "Deal 3 damage to chosen character"
70
+ * @example "Deal 2 damage to each opposing character"
71
+ */
72
+ export interface DealDamageEffect {
73
+ type: "deal-damage";
74
+ amount?: Amount;
75
+ target?: CharacterTarget | LocationTarget;
76
+ }
77
+
78
+ /**
79
+ * Put damage counters (different from "deal" - doesn't trigger "when dealt damage")
80
+ */
81
+ export interface PutDamageEffect {
82
+ type: "put-damage";
83
+ amount: Amount;
84
+ target: CharacterTarget | LocationTarget;
85
+ }
86
+
87
+ /**
88
+ * Remove damage effect
89
+ *
90
+ * @example "Remove up to 3 damage from chosen character"
91
+ */
92
+ export interface RemoveDamageEffect {
93
+ type: "remove-damage";
94
+ amount?: Amount;
95
+ target?: CharacterTarget | LocationTarget;
96
+ /** "up to" allows removing less than max */
97
+ upTo?: boolean;
98
+ }
99
+
100
+ /**
101
+ * Move damage counters effect
102
+ *
103
+ * @example "Move 2 damage from chosen character to another"
104
+ */
105
+ export interface MoveDamageEffect {
106
+ type: "move-damage";
107
+ amount?: Amount;
108
+ from?: CharacterTarget;
109
+ to?: CharacterTarget;
110
+ }
111
+
112
+ // ============================================================================
113
+ // Lore Effects
114
+ // ============================================================================
115
+
116
+ /**
117
+ * Gain lore effect
118
+ *
119
+ * @example "Gain 2 lore"
120
+ */
121
+ export interface GainLoreEffect {
122
+ type: "gain-lore";
123
+ amount?: Amount;
124
+ target?: PlayerTarget;
125
+ }
126
+
127
+ /**
128
+ * Lose lore effect
129
+ *
130
+ * @example "Each opponent loses 1 lore"
131
+ */
132
+ export interface LoseLoreEffect {
133
+ type: "lose-lore";
134
+ amount: Amount;
135
+ target?: PlayerTarget;
136
+ }
137
+
138
+ // ============================================================================
139
+ // Card State Effects
140
+ // ============================================================================
141
+
142
+ /**
143
+ * Exert effect
144
+ *
145
+ * @example "Exert chosen character"
146
+ */
147
+ export interface ExertEffect {
148
+ type: "exert";
149
+ target?: CharacterTarget | ItemTarget | LocationTarget;
150
+ }
151
+
152
+ /**
153
+ * Ready effect
154
+ *
155
+ * @example "Ready chosen character"
156
+ */
157
+ export interface ReadyEffect {
158
+ type: "ready";
159
+ target?: CharacterTarget | ItemTarget | LocationTarget;
160
+ /** Restriction after readying */
161
+ restriction?: "cant-quest" | "cant-challenge" | "cant-quest-or-challenge";
162
+ }
163
+
164
+ /**
165
+ * Banish effect
166
+ *
167
+ * @example "Banish chosen character"
168
+ * @example "Banish all opposing items"
169
+ */
170
+ export interface BanishEffect {
171
+ type: "banish";
172
+ target?: CharacterTarget | ItemTarget | LocationTarget;
173
+ }
174
+
175
+ // ============================================================================
176
+ // Look At / Reveal Effects
177
+ // ============================================================================
178
+
179
+ /**
180
+ * Look at cards effect
181
+ *
182
+ * @example "Look at the top 3 cards of your deck"
183
+ */
184
+ export interface LookAtCardsEffect {
185
+ type: "look-at-cards";
186
+ amount: Amount;
187
+ source: "deck" | "hand" | "discard";
188
+ target: PlayerTarget;
189
+ }
190
+
191
+ /**
192
+ * Put card into hand effect
193
+ *
194
+ * @example "Put a card into your hand"
195
+ */
196
+ export interface PutInHandEffect {
197
+ type: "put-in-hand";
198
+ source: "deck" | "discard" | "revealed";
199
+ target: PlayerTarget;
200
+ }