@pkmn/data 0.6.3 → 0.7.0

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/index.ts DELETED
@@ -1,841 +0,0 @@
1
- import {
2
- AbilityName,
3
- Condition,
4
- Data,
5
- Dex,
6
- EggGroup,
7
- EvoType,
8
- FormeName,
9
- GenderName,
10
- GenerationNum,
11
- ID,
12
- ItemName,
13
- Move,
14
- MoveCategory,
15
- MoveName,
16
- MoveSource,
17
- Nature,
18
- Nonstandard,
19
- Species as DexSpecies,
20
- SpeciesAbility,
21
- SpeciesName,
22
- SpeciesTag,
23
- StatID,
24
- StatsTable,
25
- Tier,
26
- Type as DexType,
27
- TypeName,
28
- } from '@pkmn/dex-types';
29
-
30
- const DEFAULT_EXISTS = (d: Data) => {
31
- if (!d.exists) return false;
32
- if ('isNonstandard' in d && d.isNonstandard) return false;
33
- if (d.kind === 'Ability' && d.id === 'noability') return false;
34
- return !('tier' in d && ['Illegal', 'Unreleased'].includes(d.tier));
35
- };
36
-
37
- const tr = (num: number, bits = 0) => bits ? (num >>> 0) % (2 ** bits) : num >>> 0;
38
-
39
- export type ExistsFn = (d: Data, g: GenerationNum) => boolean;
40
- type BoundExistsFn = (d: Data) => boolean;
41
-
42
- function assignWithout(a: {[key: string]: any}, b: {[key: string]: any}, exclude: Set<string>) {
43
- for (const key in b) {
44
- if (Object.prototype.hasOwnProperty.call(b, key) && !exclude.has(key)) {
45
- a[key] = b[key];
46
- }
47
- }
48
- return a;
49
- }
50
-
51
- export function toID(text: any): ID {
52
- if (text?.id) text = text.id;
53
- if (typeof text !== 'string' && typeof text !== 'number') return '';
54
- return ('' + text).toLowerCase().replace(/[^a-z0-9]+/g, '') as ID;
55
- }
56
-
57
- export class Generations {
58
- private readonly cache = Object.create(null) as {[num: number]: Generation};
59
-
60
- private readonly dex: Dex;
61
- private readonly exists: ExistsFn;
62
-
63
- static DEFAULT_EXISTS = DEFAULT_EXISTS;
64
-
65
- constructor(dex: Dex, exists = Generations.DEFAULT_EXISTS) {
66
- this.dex = dex;
67
- this.exists = exists;
68
- }
69
-
70
- get(g: string | number) {
71
- if (isNaN(+g)) throw new Error(`Invalid gen ${g}`);
72
- const gen = g as GenerationNum; // validated by forGen
73
- if (this.cache[gen]) return this.cache[gen];
74
- return (this.cache[gen] = new Generation(this.dex.forGen(gen), d => this.exists(d, gen)));
75
- }
76
-
77
- *[Symbol.iterator]() {
78
- for (let gen = 1; gen <= 8; gen++) {
79
- yield this.get(gen as GenerationNum);
80
- }
81
- }
82
- }
83
-
84
- export class Generation {
85
- readonly abilities: Abilities;
86
- readonly items: Items;
87
- readonly moves: Moves;
88
- readonly species: Species;
89
- readonly types: Types;
90
- readonly natures: Natures;
91
- readonly learnsets: Learnsets;
92
- readonly conditions: Conditions;
93
- readonly stats: Stats;
94
-
95
- readonly dex: Dex;
96
-
97
- private readonly exists: BoundExistsFn;
98
-
99
- static get(dex: Dex, g: string | number, exists = DEFAULT_EXISTS) {
100
- return new Generations(dex, exists).get(g);
101
- }
102
-
103
- constructor(dex: Dex, exists: BoundExistsFn) {
104
- this.dex = dex;
105
- this.exists = exists;
106
-
107
- this.abilities = new Abilities(this.dex, this.exists);
108
- this.items = new Items(this.dex, this.exists);
109
- this.moves = new Moves(this.dex, this.exists);
110
- this.species = new Species(this.dex, this.exists);
111
- this.natures = new Natures(this.dex, this.exists);
112
- this.types = new Types(this.dex, this.exists);
113
- this.learnsets = new Learnsets(this, this.dex, this.exists);
114
- this.conditions = new Conditions(this.dex, this.exists);
115
- this.stats = new Stats(this.dex);
116
- }
117
-
118
- get num() {
119
- return this.dex.gen;
120
- }
121
-
122
- toString() {
123
- return `[Generation:${this.num}]`;
124
- }
125
-
126
- toJSON() {
127
- return this.toString();
128
- }
129
- }
130
-
131
- export class Abilities {
132
- private readonly dex: Dex;
133
- private readonly exists: BoundExistsFn;
134
-
135
- constructor(dex: Dex, exists: BoundExistsFn) {
136
- this.dex = dex;
137
- this.exists = exists;
138
- }
139
-
140
- get(name: string) {
141
- const ability = this.dex.abilities.get(name);
142
- return this.exists(ability) ? ability : undefined;
143
- }
144
-
145
- *[Symbol.iterator]() {
146
- for (const ability in this.dex.data.Abilities) {
147
- const a = this.get(ability);
148
- if (a) yield a;
149
- }
150
- }
151
- }
152
-
153
- export class Items {
154
- private readonly dex: Dex;
155
- private readonly exists: BoundExistsFn;
156
-
157
- constructor(dex: Dex, exists: BoundExistsFn) {
158
- this.dex = dex;
159
- this.exists = exists;
160
- }
161
-
162
- get(name: string) {
163
- const item = this.dex.items.get(name);
164
- return this.exists(item) ? item : undefined;
165
- }
166
-
167
- *[Symbol.iterator]() {
168
- for (const item in this.dex.data.Items) {
169
- const i = this.get(item);
170
- if (i) yield i;
171
- }
172
- }
173
- }
174
-
175
- export class Moves {
176
- private readonly dex: Dex;
177
- private readonly exists: BoundExistsFn;
178
-
179
- constructor(dex: Dex, exists: BoundExistsFn) {
180
- this.dex = dex;
181
- this.exists = exists;
182
- }
183
-
184
- get(name: string) {
185
- const move = this.dex.moves.get(name);
186
- return this.exists(move) ? move : undefined;
187
- }
188
-
189
- *[Symbol.iterator]() {
190
- for (const move in this.dex.data.Moves) {
191
- const m = this.get(move);
192
- if (m) yield m;
193
- }
194
- }
195
- }
196
-
197
- export class Species {
198
- private readonly cache = Object.create(null) as { [id: string]: Specie };
199
-
200
- private readonly dex: Dex;
201
- private readonly exists: BoundExistsFn;
202
-
203
- constructor(dex: Dex, exists: BoundExistsFn) {
204
- this.dex = dex;
205
- this.exists = exists;
206
- }
207
-
208
- get(name: string) {
209
- const species = this.dex.species.get(name);
210
- if (!this.exists(species)) return undefined;
211
- const id = (species as any).speciesid || species.id; // FIXME Event-only ability hack
212
- const cached = this.cache[id];
213
- if (cached) return cached;
214
- return (this.cache[id] = new Specie(this.dex, this.exists, species));
215
- }
216
-
217
- *[Symbol.iterator]() {
218
- for (const species in this.dex.data.Species) {
219
- const s = this.get(species);
220
- if (s) yield s;
221
- }
222
- }
223
- }
224
-
225
- export class Specie implements DexSpecies {
226
- readonly id!: ID;
227
- readonly name!: SpeciesName;
228
- readonly fullname!: string;
229
- readonly exists!: boolean;
230
- readonly num!: number;
231
- readonly gen!: GenerationNum;
232
- readonly shortDesc!: string;
233
- readonly desc!: string;
234
- readonly isNonstandard!: Nonstandard | null;
235
- readonly duration?: number;
236
-
237
- readonly effectType!: 'Pokemon';
238
- readonly kind!: 'Species';
239
- readonly baseStats!: StatsTable;
240
- readonly baseSpecies!: SpeciesName;
241
- readonly baseForme!: FormeName | '';
242
- readonly forme!: FormeName | '';
243
- readonly abilities!: SpeciesAbility<AbilityName | ''>;
244
- readonly types!: [TypeName] | [TypeName, TypeName];
245
- readonly prevo?: SpeciesName | '';
246
- readonly evos?: SpeciesName[];
247
- readonly nfe: boolean;
248
- readonly eggGroups!: EggGroup[];
249
- readonly weightkg!: number;
250
- readonly weighthg!: number;
251
- readonly tags!: SpeciesTag[];
252
- readonly unreleasedHidden!: boolean | 'Past';
253
- readonly maleOnlyHidden!: boolean;
254
- readonly inheritsFrom!: ID;
255
- readonly tier!: Tier.Singles | Tier.Other;
256
- readonly doublesTier!: Tier.Doubles;
257
-
258
- readonly evoMove?: MoveName;
259
- readonly changesFrom?: SpeciesName;
260
- readonly cosmeticFormes?: SpeciesName[];
261
- readonly otherFormes?: SpeciesName[];
262
- readonly formeOrder?: SpeciesName[];
263
- readonly formes?: SpeciesName[];
264
- readonly genderRatio: { M: number; F: number };
265
- readonly isMega?: boolean;
266
- readonly isPrimal?: boolean;
267
- readonly battleOnly?: SpeciesName | SpeciesName[];
268
- readonly canGigantamax?: MoveName;
269
- readonly gmaxUnreleased?: boolean;
270
- readonly cannotDynamax?: boolean;
271
- readonly requiredAbility?: AbilityName;
272
- readonly requiredItem?: ItemName;
273
- readonly requiredItems?: ItemName[];
274
- readonly requiredMove?: MoveName;
275
- readonly gender?: GenderName;
276
- readonly maxHP?: number;
277
- readonly evoLevel?: number;
278
- readonly evoCondition?: string;
279
- readonly evoItem?: string;
280
- readonly evoType?: EvoType;
281
- readonly condition?: Partial<Condition>;
282
- readonly canHatch!: boolean;
283
-
284
- private readonly dex: Dex;
285
-
286
- private static readonly EXCLUDE = new Set([
287
- 'abilities',
288
- 'cosmeticFormes',
289
- 'evos',
290
- 'gender',
291
- 'genderRatio',
292
- 'nfe',
293
- 'otherFormes',
294
- 'prevo',
295
- ]);
296
-
297
- constructor(dex: Dex, exists: BoundExistsFn, species: DexSpecies) {
298
- assignWithout(this, species, Specie.EXCLUDE);
299
- this.dex = dex;
300
- if (this.dex.gen >= 2) {
301
- this.gender = species.gender;
302
- this.genderRatio = species.genderRatio;
303
- } else {
304
- this.genderRatio = {M: 0, F: 0};
305
- }
306
- if (this.dex.gen >= 3) {
307
- this.abilities = {0: species.abilities[0]};
308
- // "because PS", Pokemon have the abilities that were added in Gen 4 in Gen 3 :bigthonk:
309
- if (species.abilities[1] &&
310
- this.dex.abilities.get(species.abilities[1]).gen <= this.dex.gen) {
311
- this.abilities[1] = species.abilities[1];
312
- }
313
- if (this.dex.gen >= 5 && species.abilities.H) this.abilities.H = species.abilities.H;
314
- if (this.dex.gen >= 7 && species.abilities.S) this.abilities.S = species.abilities.S;
315
- } else {
316
- this.abilities = {0: ''};
317
- }
318
- this.evos = species.evos?.filter(s => exists(this.dex.species.get(s)));
319
- this.nfe = !!this.evos?.length;
320
- if (!this.nfe) this.evos = undefined;
321
- this.cosmeticFormes = species.cosmeticFormes?.filter(s => exists(this.dex.species.get(s)));
322
- if (!this.cosmeticFormes?.length) this.cosmeticFormes = undefined;
323
- this.otherFormes = species.otherFormes?.filter(s => exists(this.dex.species.get(s)));
324
- if (!this.otherFormes?.length) this.otherFormes = undefined;
325
- this.formeOrder = species.formeOrder?.filter(s => exists(this.dex.species.get(s)));
326
- if (!this.formeOrder?.length) this.formeOrder = undefined;
327
- this.formes = this.formeOrder?.filter(s =>
328
- this.dex.species.get(s).isNonstandard !== 'Gigantamax');
329
- this.prevo =
330
- species.prevo && exists(this.dex.species.get(species.prevo)) ? species.prevo : undefined;
331
- }
332
-
333
- get formeNum() {
334
- return (this.baseSpecies === this.name
335
- ? this.formeOrder ? this.formeOrder.findIndex(name => name === this.name) : 0
336
- : this.dex.species.get(this.baseSpecies).formeOrder!.findIndex(
337
- name => name === (this.isNonstandard === 'Gigantamax' ? this.baseSpecies : this.name)
338
- ));
339
- }
340
-
341
- toString() {
342
- return this.name;
343
- }
344
-
345
- toJSON() {
346
- return assignWithout({}, this, new Set(['dex']));
347
- }
348
- }
349
-
350
- export class Conditions {
351
- private readonly dex: Dex;
352
- private readonly exists: BoundExistsFn;
353
-
354
- constructor(dex: Dex, exists: BoundExistsFn) {
355
- this.dex = dex;
356
- this.exists = exists;
357
- }
358
-
359
- get(name: string) {
360
- const condition = this.dex.conditions.get(name);
361
- return this.exists(condition) ? condition : undefined;
362
- }
363
- }
364
-
365
- export class Natures {
366
- private readonly dex: Dex;
367
- private readonly exists: BoundExistsFn;
368
-
369
- constructor(dex: Dex, exists: BoundExistsFn) {
370
- this.dex = dex;
371
- this.exists = exists;
372
- }
373
-
374
- get(name: string) {
375
- if (this.dex.gen < 3) return undefined;
376
- const nature = this.dex.natures.get(name);
377
- return this.exists(nature) ? nature : undefined;
378
- }
379
-
380
- *[Symbol.iterator]() {
381
- for (const nature in this.dex.data.Natures) {
382
- const n = this.get(nature);
383
- if (n) yield n;
384
- }
385
- }
386
- }
387
-
388
- const EFFECTIVENESS = {
389
- '-3': 0.125,
390
- '-2': 0.25,
391
- '-1': 0.5,
392
- '0': 1,
393
- '1': 2,
394
- '2': 4,
395
- '3': 8,
396
- };
397
-
398
- type TypeTarget = { getTypes: () => TypeName[] } | { types: TypeName[] } | TypeName[] | TypeName;
399
-
400
- export class Types {
401
- private readonly cache = Object.create(null) as { [id: string]: Type };
402
-
403
- private readonly unknown: Type;
404
- private readonly dex: Dex;
405
- private readonly exists: BoundExistsFn;
406
-
407
- constructor(dex: Dex, exists: BoundExistsFn) {
408
- this.dex = dex;
409
- this.exists = exists;
410
- // PS doesn't contain data for the '???' type
411
- this.unknown = new Type({
412
- effectType: 'Type',
413
- kind: 'Type',
414
- // Regrettably PS ID's can't represent '???'
415
- id: '',
416
- name: '???',
417
- // Technically this only exists as a true type in Gens 2-4, but there are moves dealing
418
- // typeless damage in Gen 1 so we include it there.
419
- exists: dex.gen <= 4,
420
- gen: 1,
421
- // This gets filled in for us by Type's constructor
422
- damageTaken: {} as { [t in Exclude<TypeName, '???'>]: number },
423
- HPivs: {},
424
- HPdvs: {},
425
- }, dex, this);
426
- }
427
-
428
- get(name: string) {
429
- if (name === '???') return this.unknown;
430
- const type = this.dex.types.get(name);
431
- if (!this.exists(type)) return undefined;
432
- const cached = this.cache[type.id];
433
- if (cached) return cached;
434
- return (this.cache[type.id] = new Type(type, this.dex, this));
435
- }
436
-
437
- *[Symbol.iterator]() {
438
- for (const type in this.dex.data.Types) {
439
- const t = this.get(type);
440
- if (t) yield t;
441
- }
442
- if (this.dex.gen >= 2 && this.dex.gen <= 4) {
443
- yield this.unknown;
444
- }
445
- }
446
-
447
- getHiddenPower(ivs: StatsTable) {
448
- return this.dex.getHiddenPower(ivs);
449
- }
450
-
451
- canDamage(source: { type: TypeName } | TypeName, target: TypeTarget) {
452
- return this.dex.getImmunity(source, target);
453
- }
454
-
455
- totalEffectiveness(source: { type: TypeName } | TypeName, target: TypeTarget) {
456
- if (!this.canDamage(source, target)) return 0;
457
- const e = `${this.dex.getEffectiveness(source, target)}`;
458
- // convert from PS's ridiculous encoding to something usable
459
- return EFFECTIVENESS[e as keyof typeof EFFECTIVENESS];
460
- }
461
- }
462
-
463
- export type TypeEffectiveness = 0 | 0.5 | 1 | 2;
464
-
465
- const DAMAGE_TAKEN = [1, 2, 0.5, 0] as TypeEffectiveness[];
466
- const SPECIAL = ['Fire', 'Water', 'Grass', 'Electric', 'Ice', 'Psychic', 'Dark', 'Dragon'];
467
-
468
- export class Type {
469
- readonly id!: ID;
470
- readonly name!: TypeName;
471
- readonly effectType!: 'Type';
472
- readonly kind!: 'Type';
473
- readonly exists!: boolean;
474
- readonly gen!: GenerationNum;
475
- readonly effectiveness: { [t in TypeName]: TypeEffectiveness };
476
- readonly HPivs!: Partial<StatsTable>;
477
- readonly HPdvs!: Partial<StatsTable>;
478
- readonly category?: Exclude<MoveCategory, 'Status'>;
479
-
480
- private readonly types: Types;
481
-
482
- constructor(type: DexType, dex: Dex, types: Types) {
483
- Object.assign(this, type);
484
- this.types = types;
485
- this.category =
486
- this.name === 'Fairy' ? undefined : SPECIAL.includes(this.name) ? 'Special' : 'Physical';
487
- // convert from PS's ridiculous encoding to something usable (plus damage taken -> dealt)
488
- this.effectiveness = {'???': 1} as { [t in TypeName]: TypeEffectiveness };
489
- for (const k in dex.data.Types) {
490
- const t = k.charAt(0).toUpperCase() + k.slice(1) as Exclude<TypeName, '???'>;
491
- this.effectiveness[t] = DAMAGE_TAKEN[dex.data.Types[k].damageTaken[this.name] || 0];
492
- }
493
- }
494
-
495
- canDamage(target: TypeTarget) {
496
- return this.types.canDamage(this.name, target);
497
- }
498
-
499
- totalEffectiveness(target: TypeTarget) {
500
- return this.types.totalEffectiveness(this.name, target);
501
- }
502
-
503
- toString() {
504
- return this.name;
505
- }
506
-
507
- toJSON() {
508
- return assignWithout({}, this, new Set(['types']));
509
- }
510
- }
511
-
512
- const GEN3_HMS =
513
- new Set(['cut', 'fly', 'surf', 'strength', 'flash', 'rocksmash', 'waterfall', 'dive'] as ID[]);
514
- // NOTE: Whirlpool and Defog are Gen 4 HMs but the HMs differ in DPPt vs. HGSS
515
- const GEN4_HMS =
516
- new Set(['cut', 'fly', 'surf', 'strength', 'rocksmash', 'waterfall', 'rockclimb'] as ID[]);
517
-
518
- type Restriction = 'Pentagon' | 'Plus' | 'Galar';
519
-
520
- export class Learnsets {
521
- private readonly cache = Object.create(null) as {
522
- [speciesid: string]: {[moveid: string]: MoveSource[]};
523
- };
524
-
525
- private readonly gen: Generation;
526
- private readonly dex: Dex;
527
- private readonly exists: BoundExistsFn;
528
-
529
- constructor(gen: Generation, dex: Dex, exists: BoundExistsFn) {
530
- this.gen = gen;
531
- this.dex = dex;
532
- this.exists = exists;
533
- }
534
-
535
- async get(name: string) {
536
- const learnset = await this.dex.learnsets.get(toID(name));
537
- return this.exists(learnset) ? learnset : undefined;
538
- }
539
-
540
- async *[Symbol.iterator]() {
541
- if (!this.dex.data.Learnsets) await this.dex.learnsets.get('LOAD' as ID);
542
- for (const id in this.dex.data.Learnsets) {
543
- const l = await this.get(id);
544
- if (l) yield l;
545
- }
546
- }
547
-
548
- async* all(species: Specie) {
549
- let id = species.id;
550
- let learnset = await this.get(id);
551
- if (!learnset) {
552
- id = typeof species.battleOnly === 'string' && species.battleOnly !== species.baseSpecies
553
- ? toID(species.battleOnly)
554
- : toID(species.baseSpecies);
555
- learnset = await this.get(id);
556
- }
557
-
558
- while (learnset) {
559
- yield learnset;
560
-
561
- if (id === 'lycanrocdusk' || (species.id === 'rockruff' && id === 'rockruff')) {
562
- id = 'rockruffdusk' as ID;
563
- } else if (species.id === 'gastrodoneast') {
564
- id = 'gastrodon' as ID;
565
- } else if (species.id === 'pumpkaboosuper') {
566
- id = 'pumpkaboo' as ID;
567
- } else {
568
- id = toID(species.battleOnly || species.changesFrom || species.prevo);
569
- }
570
-
571
- if (!id) break;
572
- const s = this.gen.species.get(id);
573
- if (!s) break;
574
- species = s;
575
- learnset = await this.get(id);
576
- }
577
- }
578
-
579
- // BUG: this only covers what Pokémon Showdown deems "teambuilder legality" - proper legality
580
- // checks/restriction enforcement requires @pkmn/sim's TeamValidator.
581
- async learnable(name: string, restriction?: Restriction) {
582
- const species = this.gen.species.get(name);
583
- if (!species) return undefined;
584
-
585
- if (!restriction) {
586
- const cached = this.cache[species.id];
587
- if (cached) return cached;
588
- }
589
-
590
- const moves: {[moveid: string]: MoveSource[]} = {};
591
-
592
- for await (const learnset of this.all(species)) {
593
- if (learnset.learnset) {
594
- for (const moveid in learnset.learnset) {
595
- const move = this.gen.moves.get(moveid);
596
- if (move) {
597
- const sources = learnset.learnset[moveid];
598
- if (this.isLegal(move, sources, restriction || this.gen)) {
599
- const filtered = sources.filter(s => +s.charAt(0) <= this.gen.num);
600
- if (!filtered.length) continue;
601
- if (moves[move.id]) {
602
- // If we simply add filtered to moves[move.id] we may end up with some duplicates or
603
- // situations where we have mixed learnset information. We assume that while
604
- // moves[move.id] and filtered are already deduped, their union might not be, and
605
- // thus iterate through looking for unique prefixes. For efficiency, instead of
606
- // appending each deduped source from filtered to moves[move.id] immediately and
607
- // making each subsequent iteration longer we make a list of the unique sources to
608
- // add at the end. This is only safe given our assumption that filtered is unique
609
- // internally to begin with.
610
- const unique = [];
611
- // These lists are all expected to be short arrays so this O(n^2) linear searching
612
- // is still expected to be faster runtime-wise than a more sophisticated approach
613
- loop: for (const source of filtered) {
614
- const prefix = source.slice(0, 2);
615
- // sadly Babel chokes on using an .every(...) here due to throwIfClosureRequired
616
- for (const s of moves[move.id]) if (s.startsWith(prefix)) continue loop;
617
- unique.push(source);
618
- }
619
- moves[move.id].push(...unique);
620
- } else {
621
- moves[move.id] = filtered;
622
- }
623
- }
624
- }
625
- }
626
- }
627
- }
628
-
629
- if (!restriction) this.cache[species.id] = moves;
630
- return moves;
631
- }
632
-
633
- // BUG: this only covers what Pokémon Showdown deems "teambuilder legality" - proper legality
634
- // checks/restriction enforcement requires @pkmn/sim's TeamValidator.
635
- async canLearn(name: string, move: Move | string, restriction?: Restriction) {
636
- const species = this.gen.species.get(name);
637
- if (!species) return false;
638
-
639
- move = typeof move === 'string' && this.gen.moves.get(move) || move;
640
- if (typeof move === 'string') return false;
641
-
642
- for await (const learnset of this.all(species)) {
643
- if (this.isLegal(move, learnset.learnset?.[move.id], restriction || this.gen)) {
644
- return true;
645
- }
646
- }
647
-
648
- return false;
649
- }
650
-
651
- // BUG: this only covers what Pokémon Showdown deems "teambuilder legality" - proper legality
652
- // checks/restriction enforcement requires @pkmn/sim's TeamValidator.
653
- isLegal(move: Move, sources: MoveSource[] | undefined, gen: Generation | Restriction) {
654
- if (!sources) return undefined;
655
-
656
- const gens = sources.map(x => Number(x[0]));
657
- const minGen = Math.min(...gens);
658
- const vcOnly = (
659
- minGen === 7 && sources.every(x => x[0] !== '7' || x === '7V') ||
660
- minGen === 8 && sources.every(x => x[0] !== '8' || x === '8V')
661
- );
662
-
663
- if (gen === 'Pentagon') return gens.includes(6);
664
- if (gen === 'Plus') return gens.includes(7) && !vcOnly;
665
- if (gen === 'Galar') return gens.includes(8) && !vcOnly;
666
-
667
- if (this.gen.num >= 3 && minGen <= 4 && (GEN3_HMS.has(move.id) || GEN4_HMS.has(move.id))) {
668
- let legalGens = '';
669
- let available = false;
670
-
671
- if (minGen === 3) {
672
- legalGens += '3';
673
- available = true;
674
- }
675
- if (available) available = !GEN3_HMS.has(move.id);
676
-
677
- if (available || gens.includes(4)) {
678
- legalGens += '4';
679
- available = true;
680
- }
681
- if (available) available = !GEN4_HMS.has(move.id);
682
-
683
- const minUpperGen = available ? 5 : Math.min(...gens.filter(g => g > 4));
684
- legalGens += '012345678'.slice(minUpperGen);
685
- return legalGens.includes(`${gen.num}`);
686
- } else {
687
- return '012345678'.slice(minGen).includes(`${gen.num}`);
688
- }
689
- }
690
- }
691
-
692
- const STATS = ['hp', 'atk', 'def', 'spe', 'spa', 'spd'] as const;
693
-
694
- const NAMES: Readonly<{ [name: string]: StatID }> = {
695
- HP: 'hp', hp: 'hp',
696
- Attack: 'atk', Atk: 'atk', atk: 'atk',
697
- Defense: 'def', Def: 'def', def: 'def',
698
- 'Special Attack': 'spa', SpA: 'spa', SAtk: 'spa', SpAtk: 'spa', spa: 'spa',
699
- Special: 'spa', spc: 'spa', Spc: 'spa',
700
- 'Special Defense': 'spd', SpD: 'spd', SDef: 'spd', SpDef: 'spd', spd: 'spd',
701
- Speed: 'spe', Spe: 'spe', Spd: 'spe', spe: 'spe',
702
- };
703
-
704
- const DISPLAY: Readonly<{ [stat: string]: Readonly<[string, string]> }> = {
705
- hp: ['HP', 'HP'],
706
- atk: ['Atk', 'Attack'],
707
- def: ['Def', 'Defense'],
708
- spa: ['SpA', 'Special Attack'],
709
- spd: ['SpD', 'Special Defense'],
710
- spe: ['Spe', 'Speed'],
711
- spc: ['Spc', 'Special'],
712
- };
713
-
714
- export class Stats {
715
- private readonly dex: Dex;
716
- constructor(dex: Dex) {
717
- this.dex = dex;
718
- }
719
-
720
- calc(stat: StatID, base: number, iv = 31, ev?: number, level = 100, nature?: Nature) {
721
- if (ev === undefined) ev = this.dex.gen < 3 ? 252 : 0;
722
- if (this.dex.gen < 3) {
723
- iv = this.toDV(iv) * 2;
724
- nature = undefined;
725
- }
726
- if (stat === 'hp') {
727
- return base === 1 ? base : tr(tr(2 * base + iv + tr(ev / 4) + 100) * level / 100 + 10);
728
- } else {
729
- const val = tr(tr(2 * base + iv + tr(ev / 4)) * level / 100 + 5);
730
- if (nature !== undefined) {
731
- if (nature.plus === stat) return tr(tr(val * 110, 16) / 100);
732
- if (nature.minus === stat) return tr(tr(val * 90, 16) / 100);
733
- }
734
- return val;
735
- }
736
- }
737
-
738
- get(s: string): StatID | undefined {
739
- return NAMES[s];
740
- }
741
-
742
- display(str: string, full = false): string {
743
- let s: StatID | 'spc' | undefined = NAMES[str];
744
- if (s === undefined) return str;
745
- if (this.dex.gen === 1 && s === 'spa') s = 'spc';
746
- return DISPLAY[s][+full];
747
- }
748
-
749
- fill<T>(stats: Partial<StatsTable<T>>, val: T): StatsTable<T> {
750
- for (const stat of STATS) {
751
- if (!(stat in stats)) stats[stat] = val;
752
- }
753
- return stats as StatsTable<T>;
754
- }
755
-
756
- getHPDV(ivs: Partial<StatsTable>): number {
757
- return (
758
- (this.toDV(ivs.atk === undefined ? 31 : ivs.atk) % 2) * 8 +
759
- (this.toDV(ivs.def === undefined ? 31 : ivs.def) % 2) * 4 +
760
- (this.toDV(ivs.spe === undefined ? 31 : ivs.spe) % 2) * 2 +
761
- (this.toDV(ivs.spa === undefined ? 31 : ivs.spa) % 2)
762
- );
763
- }
764
-
765
- *[Symbol.iterator](): IterableIterator<StatID> {
766
- for (const s of STATS) {
767
- yield s;
768
- }
769
- }
770
-
771
- toDV(iv: number): number {
772
- return Math.floor(iv / 2);
773
- }
774
-
775
- toIV(dv: number): number {
776
- return dv * 2 + 1;
777
- }
778
- }
779
-
780
- export {
781
- ID,
782
- As,
783
- Weather,
784
- FieldCondition,
785
- SideCondition,
786
- GenerationNum,
787
- GenderName,
788
- StatID,
789
- StatsTable,
790
- BoostID,
791
- BoostsTable,
792
- MoveCategory,
793
- MoveTarget,
794
- Nonstandard,
795
- EvoType,
796
- EggGroup,
797
- SideID,
798
- Player,
799
- GameType,
800
- HPColor,
801
- StatusName,
802
- NatureName,
803
- TypeName,
804
- HPTypeName,
805
- Tier,
806
- PokemonSet,
807
- AbilityName,
808
- ItemName,
809
- MoveName,
810
- SpeciesName,
811
- FormeName,
812
- EffectType,
813
- Effect,
814
- DataKind,
815
- Data,
816
- EffectData,
817
- HitEffect,
818
- SecondaryEffect,
819
- ConditionData,
820
- AbilityData,
821
- ItemData,
822
- MoveData,
823
- SpeciesData,
824
- MoveSource,
825
- EventInfoData,
826
- LearnsetData,
827
- TypeData,
828
- NatureData,
829
- BasicEffect,
830
- Condition,
831
- Ability,
832
- Item,
833
- Move,
834
- // Species,
835
- EventInfo,
836
- Learnset,
837
- // Type,
838
- Nature,
839
- GenID,
840
- Dex,
841
- } from '@pkmn/dex-types';