@robsonbittencourt/calc 0.10.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.
Files changed (144) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc +12 -0
  3. package/bundle +107 -0
  4. package/dist/adaptable.d.ts +6 -0
  5. package/dist/adaptable.js +28 -0
  6. package/dist/adaptable.js.map +1 -0
  7. package/dist/calc.d.ts +6 -0
  8. package/dist/calc.js +26 -0
  9. package/dist/calc.js.map +1 -0
  10. package/dist/data/abilities.d.ts +15 -0
  11. package/dist/data/abilities.js +448 -0
  12. package/dist/data/abilities.js.map +1 -0
  13. package/dist/data/index.d.ts +2 -0
  14. package/dist/data/index.js +30 -0
  15. package/dist/data/index.js.map +1 -0
  16. package/dist/data/interface.d.ts +150 -0
  17. package/dist/data/interface.js +3 -0
  18. package/dist/data/interface.js.map +1 -0
  19. package/dist/data/items.d.ts +24 -0
  20. package/dist/data/items.js +708 -0
  21. package/dist/data/items.js.map +1 -0
  22. package/dist/data/moves.d.ts +86 -0
  23. package/dist/data/moves.js +5014 -0
  24. package/dist/data/moves.js.map +1 -0
  25. package/dist/data/natures.d.ts +17 -0
  26. package/dist/data/natures.js +127 -0
  27. package/dist/data/natures.js.map +1 -0
  28. package/dist/data/production.min.js +1 -0
  29. package/dist/data/species.d.ts +48 -0
  30. package/dist/data/species.js +10126 -0
  31. package/dist/data/species.js.map +1 -0
  32. package/dist/data/types.d.ts +23 -0
  33. package/dist/data/types.js +538 -0
  34. package/dist/data/types.js.map +1 -0
  35. package/dist/desc.d.ts +65 -0
  36. package/dist/desc.js +866 -0
  37. package/dist/desc.js.map +1 -0
  38. package/dist/field.d.ts +49 -0
  39. package/dist/field.js +111 -0
  40. package/dist/field.js.map +1 -0
  41. package/dist/index.d.ts +44 -0
  42. package/dist/index.js +99 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/items.d.ts +13 -0
  45. package/dist/items.js +434 -0
  46. package/dist/items.js.map +1 -0
  47. package/dist/mechanics/gen12.d.ts +6 -0
  48. package/dist/mechanics/gen12.js +271 -0
  49. package/dist/mechanics/gen12.js.map +1 -0
  50. package/dist/mechanics/gen3.d.ts +11 -0
  51. package/dist/mechanics/gen3.js +371 -0
  52. package/dist/mechanics/gen3.js.map +1 -0
  53. package/dist/mechanics/gen4.d.ts +11 -0
  54. package/dist/mechanics/gen4.js +596 -0
  55. package/dist/mechanics/gen4.js.map +1 -0
  56. package/dist/mechanics/gen56.d.ts +13 -0
  57. package/dist/mechanics/gen56.js +836 -0
  58. package/dist/mechanics/gen56.js.map +1 -0
  59. package/dist/mechanics/gen789.d.ts +14 -0
  60. package/dist/mechanics/gen789.js +1325 -0
  61. package/dist/mechanics/gen789.js.map +1 -0
  62. package/dist/mechanics/util.d.ts +39 -0
  63. package/dist/mechanics/util.js +675 -0
  64. package/dist/mechanics/util.js.map +1 -0
  65. package/dist/move.d.ts +50 -0
  66. package/dist/move.js +324 -0
  67. package/dist/move.js.map +1 -0
  68. package/dist/pokemon.d.ts +55 -0
  69. package/dist/pokemon.js +240 -0
  70. package/dist/pokemon.js.map +1 -0
  71. package/dist/production.min.js +1 -0
  72. package/dist/result.d.ts +34 -0
  73. package/dist/result.js +94 -0
  74. package/dist/result.js.map +1 -0
  75. package/dist/state.d.ts +77 -0
  76. package/dist/state.js +3 -0
  77. package/dist/state.js.map +1 -0
  78. package/dist/stats.d.ts +26 -0
  79. package/dist/stats.js +183 -0
  80. package/dist/stats.js.map +1 -0
  81. package/dist/test/calc.test.d.ts +1 -0
  82. package/dist/test/calc.test.js +1297 -0
  83. package/dist/test/calc.test.js.map +1 -0
  84. package/dist/test/data.test.d.ts +1 -0
  85. package/dist/test/data.test.js +368 -0
  86. package/dist/test/data.test.js.map +1 -0
  87. package/dist/test/gen.d.ts +135 -0
  88. package/dist/test/gen.js +636 -0
  89. package/dist/test/gen.js.map +1 -0
  90. package/dist/test/helper.d.ts +55 -0
  91. package/dist/test/helper.js +174 -0
  92. package/dist/test/helper.js.map +1 -0
  93. package/dist/test/move.test.d.ts +1 -0
  94. package/dist/test/move.test.js +14 -0
  95. package/dist/test/move.test.js.map +1 -0
  96. package/dist/test/pokemon.test.d.ts +1 -0
  97. package/dist/test/pokemon.test.js +102 -0
  98. package/dist/test/pokemon.test.js.map +1 -0
  99. package/dist/test/stats.test.d.ts +1 -0
  100. package/dist/test/stats.test.js +64 -0
  101. package/dist/test/stats.test.js.map +1 -0
  102. package/dist/test/utils.test.d.ts +1 -0
  103. package/dist/test/utils.test.js +19 -0
  104. package/dist/test/utils.test.js.map +1 -0
  105. package/dist/util.d.ts +17 -0
  106. package/dist/util.js +115 -0
  107. package/dist/util.js.map +1 -0
  108. package/jest.config.js +11 -0
  109. package/package.json +40 -0
  110. package/src/adaptable.ts +12 -0
  111. package/src/calc.ts +40 -0
  112. package/src/data/abilities.ts +383 -0
  113. package/src/data/index.ts +36 -0
  114. package/src/data/interface.ts +176 -0
  115. package/src/data/items.ts +632 -0
  116. package/src/data/moves.ts +5028 -0
  117. package/src/data/natures.ts +65 -0
  118. package/src/data/species.ts +10098 -0
  119. package/src/data/types.ts +478 -0
  120. package/src/desc.ts +1063 -0
  121. package/src/field.ts +124 -0
  122. package/src/index.ts +156 -0
  123. package/src/items.ts +423 -0
  124. package/src/mechanics/gen12.ts +297 -0
  125. package/src/mechanics/gen3.ts +444 -0
  126. package/src/mechanics/gen4.ts +702 -0
  127. package/src/mechanics/gen56.ts +1134 -0
  128. package/src/mechanics/gen789.ts +1788 -0
  129. package/src/mechanics/util.ts +676 -0
  130. package/src/move.ts +337 -0
  131. package/src/pokemon.ts +244 -0
  132. package/src/result.ts +106 -0
  133. package/src/state.ts +81 -0
  134. package/src/stats.ts +213 -0
  135. package/src/test/calc.test.ts +1588 -0
  136. package/src/test/data.test.ts +129 -0
  137. package/src/test/gen.ts +514 -0
  138. package/src/test/helper.ts +185 -0
  139. package/src/test/move.test.ts +13 -0
  140. package/src/test/pokemon.test.ts +121 -0
  141. package/src/test/stats.test.ts +84 -0
  142. package/src/test/utils.test.ts +18 -0
  143. package/src/util.ts +153 -0
  144. package/tsconfig.json +10 -0
@@ -0,0 +1,129 @@
1
+ import {calculate, Pokemon, Move} from '../adaptable';
2
+ import type * as I from '../data/interface';
3
+
4
+ import * as calc from '../index';
5
+ import {Dex} from '@pkmn/dex';
6
+ import {Generations} from './gen';
7
+
8
+ const pkmn = {Generations: new Generations(Dex)};
9
+
10
+ const gens = [1, 2, 3, 4, 5, 6, 7, 8, 9] as I.GenerationNum[];
11
+
12
+ describe('Generations', () => {
13
+ test('abilities', () => {
14
+ for (const gen of gens) {
15
+ const p = Array.from(pkmn.Generations.get(gen).abilities);
16
+ const c = new Map<I.ID, I.Ability>();
17
+ for (const ability of calc.Generations.get(gen).abilities) c.set(ability.id, ability);
18
+
19
+ expect(Array.from(c.values()).map(s => s.name).sort()).toEqual(p.map(s => s.name).sort());
20
+ for (const ability of p) {
21
+ expect(c.get(ability.id)).toEqual(ability);
22
+ c.delete(ability.id);
23
+ }
24
+ expect(c.size).toBe(0);
25
+ }
26
+ });
27
+
28
+ test('items', () => {
29
+ for (const gen of gens) {
30
+ const p = Array.from(pkmn.Generations.get(gen).items);
31
+ const c = new Map<I.ID, I.Item>();
32
+ for (const item of calc.Generations.get(gen).items) c.set(item.id, item);
33
+
34
+ expect(Array.from(c.values()).map(s => s.name).sort()).toEqual(p.map(s => s.name).sort());
35
+ for (const item of p) {
36
+ expect(c.get(item.id)).toEqual(item);
37
+ c.delete(item.id);
38
+ }
39
+ expect(c.size).toBe(0);
40
+ }
41
+ });
42
+
43
+ test('moves', () => {
44
+ for (const gen of gens) {
45
+ const p = Array.from(pkmn.Generations.get(gen).moves);
46
+ const c = new Map<I.ID, I.Move>();
47
+ for (const move of calc.Generations.get(gen).moves) c.set(move.id, move);
48
+
49
+ expect(Array.from(c.values()).map(s => s.name).sort()).toEqual(p.map(s => s.name).sort());
50
+ for (const move of p) {
51
+ // Formerly toEqual, relax a bit so the calc can have properties aren't in pkmn/dex.
52
+ for (const [k, v] of Object.entries(move)) {
53
+ if (v === undefined) {
54
+ delete (move as any)[k];
55
+ }
56
+ }
57
+ expect(c.get(move.id)).toMatchObject(move);
58
+ c.delete(move.id);
59
+ }
60
+ expect(c.size).toBe(0);
61
+ }
62
+ });
63
+
64
+ test('species', () => {
65
+ for (const gen of gens) {
66
+ const p = Array.from(pkmn.Generations.get(gen).species);
67
+ const c = new Map<I.ID, I.Specie>();
68
+ for (const specie of calc.Generations.get(gen).species) c.set(specie.id, specie);
69
+ expect(Array.from(c.values()).map(s => s.name).sort()).toEqual(p.map(s => s.name).sort());
70
+ for (const specie of p) {
71
+ expect(c.get(specie.id)).toEqual(specie);
72
+ c.delete(specie.id);
73
+ }
74
+ expect(c.size).toBe(0);
75
+ }
76
+ });
77
+
78
+ test('types', () => {
79
+ for (const gen of gens) {
80
+ const p = Array.from(pkmn.Generations.get(gen).types);
81
+ const c = new Map<I.ID, I.Type>();
82
+ for (const type of calc.Generations.get(gen).types) c.set(type.id, type);
83
+
84
+ expect(Array.from(c.values()).map(s => s.name).sort()).toEqual(p.map(s => s.name).sort());
85
+ for (const type of p) {
86
+ expect(c.get(type.id)).toEqual(type);
87
+ c.delete(type.id);
88
+ }
89
+ expect(c.size).toBe(0);
90
+ }
91
+ });
92
+
93
+ test('natures', () => {
94
+ for (const gen of gens) {
95
+ const p = Array.from(pkmn.Generations.get(gen).natures);
96
+ const c = new Map<I.ID, I.Nature>();
97
+ for (const nature of calc.Generations.get(gen).natures) c.set(nature.id, nature);
98
+
99
+ expect(Array.from(c.values()).map(s => s.name).sort()).toEqual(p.map(s => s.name).sort());
100
+ for (const nature of p) {
101
+ expect(c.get(nature.id)).toEqual(nature);
102
+ c.delete(nature.id);
103
+ }
104
+ expect(c.size).toBe(0);
105
+ }
106
+ });
107
+ });
108
+
109
+ describe('Adaptable', () => {
110
+ test('usage', () => {
111
+ const gen = pkmn.Generations.get(5);
112
+ const result = calculate(
113
+ gen,
114
+ new Pokemon(gen, 'Gengar', {
115
+ item: 'Choice Specs' as I.ItemName,
116
+ nature: 'Timid',
117
+ evs: {spa: 252},
118
+ boosts: {spa: 1},
119
+ }),
120
+ new Pokemon(gen, 'Chansey', {
121
+ item: 'Eviolite' as I.ItemName,
122
+ nature: 'Calm',
123
+ evs: {hp: 252, spd: 252},
124
+ }),
125
+ new Move(gen, 'Focus Blast')
126
+ );
127
+ expect(result.range()).toEqual([274, 324]);
128
+ });
129
+ });
@@ -0,0 +1,514 @@
1
+ import type * as I from '../data/interface';
2
+ import type * as D from '@pkmn/dex';
3
+
4
+ export function toID(s: string) {
5
+ return ('' + s).toLowerCase().replace(/[^a-z0-9]+/g, '') as I.ID;
6
+ }
7
+
8
+ const GENERATIONS = Object.create(null) as {[num: number]: Generation};
9
+
10
+ export class Generations implements I.Generations {
11
+ private readonly dex: D.ModdedDex;
12
+
13
+ constructor(dex: D.ModdedDex) {
14
+ this.dex = dex;
15
+ }
16
+
17
+ get(gen: I.GenerationNum) {
18
+ if (GENERATIONS[gen]) return GENERATIONS[gen];
19
+ return (GENERATIONS[gen] = new Generation(this.dex.forGen(gen)));
20
+ }
21
+ }
22
+
23
+ class Generation implements I.Generation {
24
+ dex: D.ModdedDex;
25
+
26
+ abilities: Abilities;
27
+ items: Items;
28
+ moves: Moves;
29
+ species: Species;
30
+ types: Types;
31
+ natures: Natures;
32
+
33
+ num: I.GenerationNum;
34
+
35
+ constructor(dex: D.ModdedDex) {
36
+ this.dex = dex;
37
+
38
+ this.abilities = new Abilities(dex);
39
+ this.items = new Items(dex);
40
+ this.moves = new Moves(dex);
41
+ this.species = new Species(dex);
42
+ this.types = new Types(dex);
43
+ this.natures = new Natures(dex);
44
+ this.num = this.dex.gen;
45
+ }
46
+ }
47
+
48
+ class Abilities implements I.Abilities {
49
+ private readonly dex: D.ModdedDex;
50
+
51
+ constructor(dex: D.ModdedDex) {
52
+ this.dex = dex;
53
+ }
54
+
55
+ get(name: string) {
56
+ const ability = this.dex.abilities.get(name);
57
+ if (ability.isNonstandard === 'CAP' && this.dex.gen < 4) return undefined;
58
+ return exists(ability, this.dex.gen) ? new Ability(ability) : undefined;
59
+ }
60
+
61
+ *[Symbol.iterator]() {
62
+ for (const id in this.dex.data.Abilities) {
63
+ const a = this.get(id);
64
+ if (a) yield a;
65
+ }
66
+ }
67
+ }
68
+
69
+ class Ability implements I.Ability {
70
+ readonly kind: 'Ability';
71
+ readonly id: I.ID;
72
+ readonly name: I.AbilityName;
73
+
74
+ constructor(ability: D.Ability) {
75
+ this.kind = 'Ability';
76
+ this.id = ability.id as I.ID;
77
+ this.name = ability.name as I.AbilityName;
78
+ }
79
+ }
80
+
81
+ class Items implements I.Items {
82
+ private readonly dex: D.ModdedDex;
83
+
84
+ constructor(dex: D.ModdedDex) {
85
+ this.dex = dex;
86
+ }
87
+
88
+ get(name: string) {
89
+ if (this.dex.gen < 2) return undefined;
90
+ let item = this.dex.items.get(name);
91
+ // Enigma Berry is Unobtainable in Gen 3, but the damage calc supports Unobtainable data and
92
+ // needs the naturalGift data which is only defined in Gen 4.
93
+ if (this.dex.gen === 3 && item.id === 'enigmaberry') {
94
+ item = this.dex.forGen(4).items.get('enigmaberry');
95
+ }
96
+ return exists(item, this.dex.gen) ? new Item(item, this.dex.gen) : undefined;
97
+ }
98
+
99
+ *[Symbol.iterator]() {
100
+ for (const id in this.dex.data.Items) {
101
+ const i = this.get(id);
102
+ if (i) yield i;
103
+ }
104
+ }
105
+ }
106
+
107
+ class Item implements I.Item {
108
+ readonly kind: 'Item';
109
+ readonly id: I.ID;
110
+ readonly name: I.ItemName;
111
+ readonly megaEvolves?: I.SpeciesName;
112
+ readonly isBerry?: boolean;
113
+ readonly naturalGift?: Readonly<{basePower: number; type: I.TypeName}>;
114
+
115
+ constructor(item: D.Item, gen: I.GenerationNum) {
116
+ this.kind = 'Item';
117
+ this.id = item.id as I.ID;
118
+ this.name = item.name as I.ItemName;
119
+ this.megaEvolves = item.megaEvolves as I.SpeciesName;
120
+ this.isBerry = item.isBerry;
121
+ this.naturalGift = item.naturalGift && {
122
+ basePower: item.naturalGift.basePower - (gen === 2 ? 20 : 0),
123
+ type: item.naturalGift.type,
124
+ };
125
+ }
126
+ }
127
+
128
+ class Moves implements I.Moves {
129
+ private readonly dex: D.ModdedDex;
130
+
131
+ constructor(dex: D.ModdedDex) {
132
+ this.dex = dex;
133
+ }
134
+
135
+ get(name: string) {
136
+ const move = this.dex.moves.get(name);
137
+ return exists(move, this.dex.gen) ? new Move(move, this.dex) : undefined;
138
+ }
139
+
140
+ *[Symbol.iterator]() {
141
+ yield NoMove(this.dex);
142
+ for (const id in this.dex.data.Moves) {
143
+ const m = this.get(id);
144
+ if (m) yield m;
145
+ }
146
+ }
147
+ }
148
+
149
+ class Move implements I.Move {
150
+ readonly kind: 'Move';
151
+ readonly id: I.ID;
152
+ readonly name: I.MoveName;
153
+ readonly basePower: number;
154
+ readonly type: I.TypeName;
155
+ readonly category?: I.MoveCategory;
156
+ readonly flags: I.MoveFlags;
157
+ readonly secondaries?: any;
158
+ readonly target?: I.MoveTarget;
159
+ readonly recoil?: [number, number];
160
+ readonly hasCrashDamage?: boolean;
161
+ readonly mindBlownRecoil?: boolean;
162
+ readonly struggleRecoil?: boolean;
163
+ readonly willCrit?: boolean;
164
+ readonly drain?: [number, number];
165
+ readonly priority?: number;
166
+ readonly self?: I.SelfOrSecondaryEffect | null;
167
+ readonly ignoreDefensive?: boolean;
168
+ readonly overrideOffensiveStat?: I.StatIDExceptHP;
169
+ readonly overrideDefensiveStat?: I.StatIDExceptHP;
170
+ readonly overrideOffensivePokemon?: 'target' | 'source';
171
+ readonly overrideDefensivePokemon?: 'target' | 'source';
172
+ readonly breaksProtect?: boolean;
173
+ readonly isZ?: boolean;
174
+ readonly zMove?: {
175
+ basePower?: number;
176
+ };
177
+ readonly isMax?: boolean;
178
+ readonly maxMove?: {
179
+ basePower: number;
180
+ };
181
+ readonly multihit?: number | number[];
182
+ readonly multiaccuracy?: boolean;
183
+
184
+ constructor(move: D.Move, dex: D.ModdedDex) {
185
+ this.kind = 'Move';
186
+ this.id = move.id === 'hiddenpower' ? toID(move.name) : move.id as I.ID;
187
+ this.name = move.name as I.MoveName;
188
+ this.basePower = move.basePower;
189
+ this.type = move.type;
190
+ this.overrideOffensiveStat = move.overrideOffensiveStat;
191
+ this.overrideDefensiveStat = move.overrideDefensiveStat;
192
+ this.overrideOffensivePokemon = move.overrideOffensivePokemon;
193
+ this.overrideDefensivePokemon = move.overrideDefensivePokemon;
194
+
195
+ if (move.category === 'Status' || dex.gen >= 4) {
196
+ this.category = move.category;
197
+ }
198
+
199
+ if (move.recoil) this.recoil = move.recoil;
200
+ if (move.hasCrashDamage) this.hasCrashDamage = move.hasCrashDamage;
201
+ if (move.mindBlownRecoil) this.mindBlownRecoil = move.mindBlownRecoil;
202
+ if (move.struggleRecoil) this.struggleRecoil = move.struggleRecoil;
203
+
204
+ const stat = move.category === 'Special' ? 'spa' : 'atk';
205
+ if (move.self?.boosts && move.self.boosts[stat] && move.self.boosts[stat]! < 0) {
206
+ this.self = move.self;
207
+ }
208
+
209
+ if (move.multihit) this.multihit = move.multihit;
210
+ if (move.multiaccuracy) this.multiaccuracy = move.multiaccuracy;
211
+ if (move.drain) this.drain = move.drain;
212
+ if (move.willCrit) this.willCrit = move.willCrit;
213
+ if (move.priority > 0) this.priority = move.priority;
214
+
215
+ this.flags = {};
216
+ if (dex.gen >= 2) {
217
+ if (move.breaksProtect) this.breaksProtect = move.breaksProtect;
218
+ }
219
+ if (dex.gen >= 3) {
220
+ if (move.flags.contact) this.flags.contact = move.flags.contact;
221
+ if (move.flags.sound) this.flags.sound = move.flags.sound;
222
+
223
+ if (['allAdjacent', 'allAdjacentFoes'].includes(move.target)) {
224
+ this.target = move.target;
225
+ }
226
+ }
227
+ if (dex.gen >= 4) {
228
+ if (move.flags.punch) this.flags.punch = move.flags.punch;
229
+ if (move.flags.bite) this.flags.bite = move.flags.bite;
230
+ }
231
+ if (dex.gen >= 5) {
232
+ if (move.ignoreDefensive) this.ignoreDefensive = move.ignoreDefensive;
233
+
234
+ if ('secondaries' in move && move.secondaries?.length) {
235
+ this.secondaries = true;
236
+ }
237
+ }
238
+ if (dex.gen >= 6) {
239
+ if (move.flags.bullet) this.flags.bullet = move.flags.bullet;
240
+ if (move.flags.pulse) this.flags.pulse = move.flags.pulse;
241
+ }
242
+ if (dex.gen >= 7) {
243
+ if (move.isZ) this.isZ = true;
244
+ if (move.zMove?.basePower) this.zMove = {basePower: move.zMove.basePower};
245
+ }
246
+ if (dex.gen >= 8) {
247
+ if (move.isMax) this.isMax = true;
248
+ if (move.maxMove) this.maxMove = {basePower: move.maxMove.basePower};
249
+ }
250
+ if (dex.gen >= 9) {
251
+ if (move.flags.wind) this.flags.wind = move.flags.wind;
252
+ if (move.flags.slicing) this.flags.slicing = move.flags.slicing;
253
+ }
254
+ }
255
+ }
256
+
257
+ class Species implements I.Species {
258
+ private readonly dex: D.ModdedDex;
259
+
260
+ constructor(dex: D.ModdedDex) {
261
+ this.dex = dex;
262
+ }
263
+
264
+ get(name: string) {
265
+ const species = this.dex.species.get(name);
266
+ if (this.dex.gen >= 6 && species.id === 'aegislashboth') return AegislashBoth(this.dex);
267
+ return exists(species, this.dex.gen) ? new Specie(species, this.dex) : undefined;
268
+ }
269
+
270
+ *[Symbol.iterator]() {
271
+ for (const id in this.dex.data.Species) {
272
+ const s = this.get(id);
273
+ if (s) {
274
+ if (id === 'aegislash') yield AegislashBoth(this.dex);
275
+ yield s;
276
+ }
277
+ }
278
+ }
279
+ }
280
+
281
+ // Custom Move placeholder
282
+ function NoMove(dex: D.ModdedDex) {
283
+ return new Move({
284
+ id: 'nomove' as I.ID,
285
+ name: '(No Move)' as I.MoveName,
286
+ basePower: 0,
287
+ type: 'Normal',
288
+ category: 'Status',
289
+ target: 'any',
290
+ flags: {},
291
+ gen: 1,
292
+ priority: 0,
293
+ } as D.Move, dex);
294
+ }
295
+
296
+ class Specie implements I.Specie {
297
+ readonly kind: 'Species';
298
+ readonly id: I.ID;
299
+ readonly name: I.SpeciesName;
300
+
301
+ readonly types: [I.TypeName] | [I.TypeName, I.TypeName];
302
+ readonly baseStats: Readonly<I.StatsTable>;
303
+ readonly weightkg: number;
304
+ readonly nfe?: boolean;
305
+ readonly gender?: I.GenderName;
306
+ readonly otherFormes?: I.SpeciesName[];
307
+ readonly baseSpecies?: I.SpeciesName;
308
+ readonly abilities?: {0: I.AbilityName};
309
+
310
+ constructor(species: D.Species, dex: D.ModdedDex) {
311
+ this.kind = 'Species';
312
+ this.id = (species.id === 'aegislash' ? 'aegislashshield' : species.id) as I.ID;
313
+ this.name = (species.name === 'Aegislash' ? 'Aegislash-Shield' : species.name) as I.SpeciesName;
314
+ this.types = species.types;
315
+ this.baseStats = species.baseStats;
316
+ this.weightkg = species.weightkg;
317
+
318
+ const nfe = !!species.evos?.some((s: string) => exists(dex.species.get(s), dex.gen));
319
+ if (nfe) this.nfe = nfe;
320
+ if (species.gender === 'N' && dex.gen > 1) this.gender = species.gender;
321
+
322
+ const formes = species.otherFormes?.filter((s: string) => exists(dex.species.get(s), dex.gen));
323
+ if (species.id.startsWith('aegislash')) {
324
+ if (species.id === 'aegislashblade') {
325
+ this.otherFormes = ['Aegislash-Shield', 'Aegislash-Both'] as I.SpeciesName[];
326
+ } else {
327
+ this.baseSpecies = 'Aegislash-Blade' as I.SpeciesName;
328
+ }
329
+ } else if (species.id === 'toxtricity') {
330
+ this.otherFormes = [
331
+ 'Toxtricity-Gmax', 'Toxtricity-Low-Key', 'Toxtricity-Low-Key-Gmax',
332
+ ] as I.SpeciesName[];
333
+ } else if (species.id === 'toxtricitylowkey') {
334
+ this.baseSpecies = 'Toxtricity' as I.SpeciesName;
335
+ } else if (species.id === 'urshifu') {
336
+ this.otherFormes = [
337
+ 'Urshifu-Gmax', 'Urshifu-Rapid-Strike', 'Urshifu-Rapid-Strike-Gmax',
338
+ ] as I.SpeciesName[];
339
+ } else if (species.id === 'eternatus') {
340
+ this.otherFormes = ['Eternatus-Eternamax'] as I.SpeciesName[];
341
+ } else if (formes?.length) {
342
+ this.otherFormes = [...formes].sort() as I.SpeciesName[];
343
+ } else if (species.baseSpecies !== this.name) {
344
+ this.baseSpecies = species.baseSpecies as I.SpeciesName;
345
+ }
346
+ // TODO: clean this up with proper Gigantamax support
347
+ if (dex.gen === 8 && species.canGigantamax &&
348
+ !(species.id.startsWith('toxtricity') || species.id.startsWith('urshifu'))) {
349
+ const formes = this.otherFormes || [];
350
+ const gmax = dex.species.get(`${species.name}-Gmax`);
351
+ if (exists(gmax, dex.gen)) this.otherFormes = [...formes, gmax.name].sort();
352
+ }
353
+
354
+ if (dex.gen > 2) this.abilities = {0: species.abilities[0] as I.AbilityName};
355
+ }
356
+ }
357
+
358
+ // Custom Aegislash forme
359
+ function AegislashBoth(dex: D.ModdedDex) {
360
+ const shield = dex.species.get('aegislash')!;
361
+ const blade = dex.species.get('aegislashblade')!;
362
+ const baseStats = {
363
+ hp: shield.baseStats.hp,
364
+ atk: blade.baseStats.atk,
365
+ def: shield.baseStats.def,
366
+ spa: blade.baseStats.spa,
367
+ spd: shield.baseStats.spd,
368
+ spe: shield.baseStats.spe,
369
+ };
370
+ return new Specie({
371
+ ...shield,
372
+ baseStats,
373
+ id: 'aegislashboth' as I.ID,
374
+ name: 'Aegislash-Both' as I.SpeciesName,
375
+ } as D.Species, dex);
376
+ }
377
+
378
+ const DAMAGE_TAKEN = [1, 2, 0.5, 0] as I.TypeEffectiveness[];
379
+
380
+ export class Types implements I.Types {
381
+ private readonly dex: D.ModdedDex;
382
+ private readonly byID: {[id: string]: I.Type};
383
+
384
+ constructor(dex: D.ModdedDex) {
385
+ this.dex = dex;
386
+
387
+ const unknown = {
388
+ kind: 'Type',
389
+ id: '' as I.ID,
390
+ name: '???',
391
+ effectiveness: {},
392
+ } as I.Type;
393
+
394
+ this.byID = {};
395
+ for (const id in this.dex.data.Types) {
396
+ if (!exists(this.dex.types.get(id), this.dex.gen)) continue;
397
+ const name = id[0].toUpperCase() + id.slice(1) as Exclude<I.TypeName, '???'>;
398
+
399
+ const effectiveness = {'???': 1} as {[type in I.TypeName]: I.TypeEffectiveness};
400
+ for (const t2ID in this.dex.data.Types) {
401
+ if (!exists(this.dex.types.get(t2ID), this.dex.gen)) continue;
402
+ const t = t2ID[0].toUpperCase() + t2ID.slice(1) as Exclude<I.TypeName, '???'>;
403
+ effectiveness[t] = DAMAGE_TAKEN[this.dex.data.Types[t2ID].damageTaken[name]!];
404
+ }
405
+ (unknown.effectiveness as any)[name] = 1;
406
+
407
+ this.byID[id] = {kind: 'Type', id: id as I.ID, name, effectiveness};
408
+ }
409
+ this.byID[unknown.id] = unknown;
410
+ }
411
+
412
+ get(name: string) {
413
+ // toID('???') => '', as do many other things, but returning the '???' type seems appropriate :)
414
+ return this.byID[toID(name)];
415
+ }
416
+
417
+ *[Symbol.iterator]() {
418
+ for (const id in this.byID) {
419
+ yield this.byID[id];
420
+ }
421
+ }
422
+ }
423
+
424
+ export class Natures implements I.Natures {
425
+ private readonly dex: D.ModdedDex;
426
+
427
+ constructor(dex: D.ModdedDex) {
428
+ this.dex = dex;
429
+ }
430
+
431
+ get(name: string) {
432
+ const nature = this.dex.natures.get(name);
433
+ return nature.exists ? new Nature(nature) : undefined;
434
+ }
435
+
436
+ *[Symbol.iterator]() {
437
+ for (const id in this.dex.data.Natures) {
438
+ const n = this.get(id);
439
+ if (n) yield n;
440
+ }
441
+ }
442
+ }
443
+
444
+ class Nature implements I.Nature {
445
+ readonly kind: 'Nature';
446
+ readonly id: I.ID;
447
+ readonly name: I.NatureName;
448
+ readonly plus: I.StatID;
449
+ readonly minus: I.StatID;
450
+
451
+ constructor(nature: D.Nature) {
452
+ this.kind = 'Nature';
453
+ this.id = nature.id as I.ID;
454
+ this.name = nature.name;
455
+
456
+ switch (nature.id) {
457
+ case 'hardy':
458
+ this.plus = 'atk';
459
+ this.minus = 'atk';
460
+ break;
461
+ case 'docile':
462
+ this.plus = 'def';
463
+ this.minus = 'def';
464
+ break;
465
+ case 'bashful':
466
+ this.plus = 'spa';
467
+ this.minus = 'spa';
468
+ break;
469
+ case 'quirky':
470
+ this.plus = 'spd';
471
+ this.minus = 'spd';
472
+ break;
473
+ case 'serious':
474
+ this.plus = 'spe';
475
+ this.minus = 'spe';
476
+ break;
477
+ default:
478
+ this.plus = nature.plus!;
479
+ this.minus = nature.minus!;
480
+ }
481
+ }
482
+ }
483
+
484
+ const NATDEX_BANNED = [
485
+ 'Pikachu-Cosplay',
486
+ 'Pikachu-Rock-Star',
487
+ 'Pikachu-Belle',
488
+ 'Pikachu-Pop-Star',
489
+ 'Pikachu-PhD',
490
+ 'Pikachu-Libre',
491
+ 'Pichu-Spiky-eared',
492
+ 'Floette-Eternal',
493
+ ];
494
+
495
+ function exists(val: D.Ability| D.Item | D.Move | D.Species | D.Type, gen: I.GenerationNum) {
496
+ if (!val.exists || val.id === 'noability') return false;
497
+ if (gen === 7 && val.isNonstandard === 'LGPE') return true;
498
+ if (gen >= 8) {
499
+ if (gen === 8) {
500
+ if (('isMax' in val && val.isMax) || val.isNonstandard === 'Gigantamax') return true;
501
+ if (['eternatuseternamax', 'zarude', 'zarudedada'].includes(val.id)) return true;
502
+ if (val.isNonstandard === 'Future') return false;
503
+ }
504
+ if (val.isNonstandard === 'Past' && !NATDEX_BANNED.includes(val.name)) return true;
505
+ if (gen > 8 && 'isZ' in val && val.isZ) return false;
506
+ if (gen > 8 && val.isNonstandard === 'Unobtainable') return true;
507
+ }
508
+ if (gen >= 6 && ['floetteeternal'].includes(val.id)) return true;
509
+ // TODO: clean this up with proper Gigantamax support
510
+ if (val.isNonstandard && !['CAP', 'Unobtainable', 'Gigantamax'].includes(val.isNonstandard)) {
511
+ return false;
512
+ }
513
+ return !('tier' in val && ['Illegal', 'Unreleased'].includes(val.tier));
514
+ }