@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
package/src/move.ts ADDED
@@ -0,0 +1,337 @@
1
+ import type * as I from './data/interface';
2
+ import type {State} from './state';
3
+ import {toID, extend} from './util';
4
+
5
+ const SPECIAL = ['Fire', 'Water', 'Grass', 'Electric', 'Ice', 'Psychic', 'Dark', 'Dragon'];
6
+
7
+ export class Move implements State.Move {
8
+ gen: I.Generation;
9
+ name: I.MoveName;
10
+
11
+ originalName: string;
12
+ ability?: I.AbilityName;
13
+ item?: I.ItemName;
14
+ species?: I.SpeciesName;
15
+ useZ?: boolean;
16
+ useMax?: boolean;
17
+ overrides?: Partial<I.Move>;
18
+
19
+ hits: number;
20
+ timesUsed?: number;
21
+ timesUsedWithMetronome?: number;
22
+ bp: number;
23
+ type: I.TypeName;
24
+ category: I.MoveCategory;
25
+ flags: I.MoveFlags;
26
+ secondaries: any;
27
+ target: I.MoveTarget;
28
+ recoil?: [number, number];
29
+ hasCrashDamage: boolean;
30
+ mindBlownRecoil: boolean;
31
+ struggleRecoil: boolean;
32
+ isCrit: boolean;
33
+ isStellarFirstUse: boolean;
34
+ drain?: [number, number];
35
+ priority: number;
36
+ dropsStats?: number;
37
+ ignoreDefensive: boolean;
38
+ overrideOffensiveStat?: I.StatIDExceptHP;
39
+ overrideDefensiveStat?: I.StatIDExceptHP;
40
+ overrideOffensivePokemon?: 'target' | 'source';
41
+ overrideDefensivePokemon?: 'target' | 'source';
42
+ breaksProtect: boolean;
43
+ isZ: boolean;
44
+ isMax: boolean;
45
+ multiaccuracy: boolean;
46
+
47
+ constructor(
48
+ gen: I.Generation,
49
+ name: string,
50
+ options: Partial<State.Move> & {
51
+ ability?: I.AbilityName;
52
+ item?: I.ItemName;
53
+ species?: I.SpeciesName;
54
+ } = {}
55
+ ) {
56
+ name = options.name || name;
57
+ this.originalName = name;
58
+ let data: I.Move = extend(true, {name}, gen.moves.get(toID(name)), options.overrides);
59
+
60
+ this.hits = 1;
61
+ // If isZMove but there isn't a corresponding z-move, use the original move
62
+ if (options.useMax && data.maxMove) {
63
+ const maxMoveName: string = getMaxMoveName(
64
+ data.type,
65
+ data.name,
66
+ options.species,
67
+ !!(data.category === 'Status'),
68
+ options.ability
69
+ );
70
+ const maxMove = gen.moves.get(toID(maxMoveName));
71
+ const maxPower = () => {
72
+ if (['G-Max Drum Solo', 'G-Max Fire Ball', 'G-Max Hydrosnipe'].includes(maxMoveName)) {
73
+ return 160;
74
+ }
75
+ // TODO: checking basePower === 10 is fragile (what if the maxMove's basePower is
76
+ // overridden?) and also fails for Max Flare, which is strangely 100 BP in the game data
77
+ if (maxMove!.basePower === 10 || maxMoveName === 'Max Flare') {
78
+ return data.maxMove!.basePower;
79
+ }
80
+ return maxMove!.basePower;
81
+ };
82
+ data = extend(true, {}, maxMove, {
83
+ name: maxMoveName,
84
+ basePower: maxPower(),
85
+ category: data.category,
86
+ });
87
+ }
88
+ if (options.useZ && data.zMove?.basePower) {
89
+ const zMoveName: string = getZMoveName(data.name, data.type, options.item);
90
+ const zMove = gen.moves.get(toID(zMoveName));
91
+ data = extend(true, {}, zMove, {
92
+ name: zMoveName,
93
+ basePower: zMove!.basePower === 1 ? data.zMove.basePower : zMove!.basePower,
94
+ category: data.category,
95
+ });
96
+ } else {
97
+ if (data.multihit) {
98
+ if (data.multiaccuracy && typeof data.multihit === 'number') {
99
+ this.hits = options.hits || data.multihit;
100
+ } else {
101
+ if (typeof data.multihit === 'number') {
102
+ this.hits = data.multihit;
103
+ } else if (options.hits) {
104
+ this.hits = options.hits;
105
+ } else {
106
+ this.hits = (options.ability === 'Skill Link')
107
+ ? data.multihit[1]
108
+ : data.multihit[0] + 1;
109
+ }
110
+ }
111
+ }
112
+ this.timesUsedWithMetronome = options.timesUsedWithMetronome;
113
+ }
114
+ this.gen = gen;
115
+ this.name = data.name;
116
+ this.ability = options.ability;
117
+ this.item = options.item;
118
+ this.useZ = options.useZ;
119
+ this.useMax = options.useMax;
120
+ this.overrides = options.overrides;
121
+ this.species = options.species;
122
+
123
+ this.bp = data.basePower;
124
+ // These moves have a type, but the damage they deal is typeless so we override it
125
+ const typelessDamage =
126
+ (gen.num >= 2 && data.id === 'struggle') ||
127
+ (gen.num <= 4 && ['futuresight', 'doomdesire'].includes(data.id));
128
+ this.type = typelessDamage ? '???' : data.type;
129
+ this.category = data.category ||
130
+ (gen.num < 4 ? (SPECIAL.includes(data.type) ? 'Special' : 'Physical') : 'Status');
131
+
132
+ const stat = this.category === 'Special' ? 'spa' : 'atk';
133
+ if (data.self?.boosts && data.self.boosts[stat] && data.self.boosts[stat]! < 0) {
134
+ this.dropsStats = Math.abs(data.self.boosts[stat]!);
135
+ }
136
+ this.timesUsed = (this.dropsStats && options.timesUsed) || 1;
137
+ this.secondaries = data.secondaries;
138
+ // For the purposes of the damage formula only 'allAdjacent' and 'allAdjacentFoes' matter, so we
139
+ // simply default to 'any' for the others even though they may not actually be 'any'-target
140
+ this.target = data.target || 'any';
141
+ this.recoil = data.recoil;
142
+ this.hasCrashDamage = !!data.hasCrashDamage;
143
+ this.mindBlownRecoil = !!data.mindBlownRecoil;
144
+ this.struggleRecoil = !!data.struggleRecoil;
145
+ this.isCrit = !!options.isCrit || !!data.willCrit ||
146
+ // These don't *always* crit (255/256 chance), but for the purposes of the calc they do
147
+ gen.num === 1 && ['crabhammer', 'razorleaf', 'slash', 'karate chop'].includes(data.id);
148
+ this.isStellarFirstUse = !!options.isStellarFirstUse;
149
+ this.drain = data.drain;
150
+ this.flags = data.flags;
151
+ // The calc doesn't currently care about negative priority moves so we simply default to 0
152
+ this.priority = data.priority || 0;
153
+
154
+ this.ignoreDefensive = !!data.ignoreDefensive;
155
+ this.overrideOffensiveStat = data.overrideOffensiveStat;
156
+ this.overrideDefensiveStat = data.overrideDefensiveStat;
157
+ this.overrideOffensivePokemon = data.overrideOffensivePokemon;
158
+ this.overrideDefensivePokemon = data.overrideDefensivePokemon;
159
+ this.breaksProtect = !!data.breaksProtect;
160
+ this.isZ = !!data.isZ;
161
+ this.isMax = !!data.isMax;
162
+ this.multiaccuracy = !!data.multiaccuracy;
163
+
164
+ if (!this.bp) {
165
+ // Assume max happiness for these moves because the calc doesn't support happiness
166
+ if (['return', 'frustration', 'pikapapow', 'veeveevolley'].includes(data.id)) {
167
+ this.bp = 102;
168
+ }
169
+ }
170
+ }
171
+
172
+ named(...names: string[]) {
173
+ return names.includes(this.name);
174
+ }
175
+
176
+ hasType(...types: Array<(I.TypeName | undefined)>) {
177
+ return types.includes(this.type);
178
+ }
179
+
180
+ clone() {
181
+ return new Move(this.gen, this.originalName, {
182
+ ability: this.ability,
183
+ item: this.item,
184
+ species: this.species,
185
+ useZ: this.useZ,
186
+ useMax: this.useMax,
187
+ isCrit: this.isCrit,
188
+ isStellarFirstUse: this.isStellarFirstUse,
189
+ hits: this.hits,
190
+ timesUsed: this.timesUsed,
191
+ timesUsedWithMetronome: this.timesUsedWithMetronome,
192
+ overrides: this.overrides,
193
+ });
194
+ }
195
+ }
196
+
197
+ export function getZMoveName(moveName: string, moveType: I.TypeName, item?: string) {
198
+ item = item || '';
199
+ if (moveName.includes('Hidden Power')) return 'Breakneck Blitz';
200
+ if (moveName === 'Clanging Scales' && item === 'Kommonium Z') return 'Clangorous Soulblaze';
201
+ if (moveName === 'Darkest Lariat' && item === 'Incinium Z') return 'Malicious Moonsault';
202
+ if (moveName === 'Giga Impact' && item === 'Snorlium Z') return 'Pulverizing Pancake';
203
+ if (moveName === 'Moongeist Beam' && item === 'Lunalium Z') return 'Menacing Moonraze Maelstrom';
204
+ if (moveName === 'Photon Geyser' && item === 'Ultranecrozium Z') {
205
+ return 'Light That Burns the Sky';
206
+ }
207
+ if (moveName === 'Play Rough' && item === 'Mimikium Z') return 'Let\'s Snuggle Forever';
208
+ if (moveName === 'Psychic' && item === 'Mewnium Z') return 'Genesis Supernova';
209
+ if (moveName === 'Sparkling Aria' && item === 'Primarium Z') return 'Oceanic Operetta';
210
+ if (moveName === 'Spectral Thief' && item === 'Marshadium Z') {
211
+ return 'Soul-Stealing 7-Star Strike';
212
+ }
213
+ if (moveName === 'Spirit Shackle' && item === 'Decidium Z') return 'Sinister Arrow Raid';
214
+ if (moveName === 'Stone Edge' && item === 'Lycanium Z') return 'Splintered Stormshards';
215
+ if (moveName === 'Sunsteel Strike' && item === 'Solganium Z') return 'Searing Sunraze Smash';
216
+ if (moveName === 'Volt Tackle' && item === 'Pikanium Z') return 'Catastropika';
217
+ if (moveName === 'Nature\'s Madness' && item === 'Tapunium Z') return 'Guardian of Alola';
218
+ if (moveName === 'Thunderbolt') {
219
+ if (item === 'Aloraichium Z') return 'Stoked Sparksurfer';
220
+ if (item === 'Pikashunium Z') return '10,000,000 Volt Thunderbolt';
221
+ }
222
+ return ZMOVES_TYPING[moveType]!;
223
+ }
224
+
225
+ const ZMOVES_TYPING: {
226
+ [type in I.TypeName]?: string;
227
+ } = {
228
+ Bug: 'Savage Spin-Out',
229
+ Dark: 'Black Hole Eclipse',
230
+ Dragon: 'Devastating Drake',
231
+ Electric: 'Gigavolt Havoc',
232
+ Fairy: 'Twinkle Tackle',
233
+ Fighting: 'All-Out Pummeling',
234
+ Fire: 'Inferno Overdrive',
235
+ Flying: 'Supersonic Skystrike',
236
+ Ghost: 'Never-Ending Nightmare',
237
+ Grass: 'Bloom Doom',
238
+ Ground: 'Tectonic Rage',
239
+ Ice: 'Subzero Slammer',
240
+ Normal: 'Breakneck Blitz',
241
+ Poison: 'Acid Downpour',
242
+ Psychic: 'Shattered Psyche',
243
+ Rock: 'Continental Crush',
244
+ Steel: 'Corkscrew Crash',
245
+ Water: 'Hydro Vortex',
246
+ };
247
+
248
+ export function getMaxMoveName(
249
+ moveType: I.TypeName,
250
+ moveName?: string,
251
+ pokemonSpecies?: string,
252
+ isStatus?: boolean,
253
+ pokemonAbility?: string
254
+ ) {
255
+ if (isStatus) return 'Max Guard';
256
+ if (pokemonAbility === 'Normalize') return 'Max Strike';
257
+ if (moveType === 'Fire') {
258
+ if (pokemonSpecies === 'Charizard-Gmax') return 'G-Max Wildfire';
259
+ if (pokemonSpecies === 'Centiskorch-Gmax') return 'G-Max Centiferno';
260
+ if (pokemonSpecies === 'Cinderace-Gmax') return 'G-Max Fire Ball';
261
+ }
262
+ if (moveType === 'Normal') {
263
+ if (pokemonSpecies === 'Eevee-Gmax') return 'G-Max Cuddle';
264
+ if (pokemonSpecies === 'Meowth-Gmax') return 'G-Max Gold Rush';
265
+ if (pokemonSpecies === 'Snorlax-Gmax') return 'G-Max Replenish';
266
+ if (!(moveName === 'Weather Ball' || moveName === 'Terrain Pulse')) {
267
+ if (pokemonAbility === 'Pixilate') return 'Max Starfall';
268
+ if (pokemonAbility === 'Aerilate') return 'Max Airstream';
269
+ if (pokemonAbility === 'Refrigerate') return 'Max Hailstorm';
270
+ if (pokemonAbility === 'Galvanize') return 'Max Lightning';
271
+ }
272
+ }
273
+ if (moveType === 'Fairy') {
274
+ if (pokemonSpecies === 'Alcremie-Gmax') return 'G-Max Finale';
275
+ if (pokemonSpecies === 'Hatterene-Gmax') return 'G-Max Smite';
276
+ }
277
+ if (moveType === 'Steel') {
278
+ if (pokemonSpecies === 'Copperajah-Gmax') return 'G-Max Steelsurge';
279
+ if (pokemonSpecies === 'Melmetal-Gmax') return 'G-Max Meltdown';
280
+ }
281
+ if (moveType === 'Electric') {
282
+ if (pokemonSpecies === 'Pikachu-Gmax') return 'G-Max Volt Crash';
283
+ if (pokemonSpecies?.startsWith('Toxtricity') &&
284
+ pokemonSpecies?.endsWith('Gmax')) return 'G-Max Stun Shock';
285
+ }
286
+ if (moveType === 'Grass') {
287
+ if (pokemonSpecies === 'Appletun-Gmax') return 'G-Max Sweetness';
288
+ if (pokemonSpecies === 'Flapple-Gmax') return 'G-Max Tartness';
289
+ if (pokemonSpecies === 'Rillaboom-Gmax') return 'G-Max Drum Solo';
290
+ if (pokemonSpecies === 'Venusaur-Gmax') return 'G-Max Vine Lash';
291
+ }
292
+ if (moveType === 'Water') {
293
+ if (pokemonSpecies === 'Blastoise-Gmax') return 'G-Max Cannonade';
294
+ if (pokemonSpecies === 'Drednaw-Gmax') return 'G-Max Stonesurge';
295
+ if (pokemonSpecies === 'Inteleon-Gmax') return 'G-Max Hydrosnipe';
296
+ if (pokemonSpecies === 'Kingler-Gmax') return 'G-Max Foam Burst';
297
+ if (pokemonSpecies === 'Urshifu-Rapid-Strike-Gmax') return 'G-Max Rapid Flow';
298
+ }
299
+ if (moveType === 'Dark') {
300
+ if (pokemonSpecies === 'Grimmsnarl-Gmax') return 'G-Max Snooze';
301
+ if (pokemonSpecies === 'Urshifu-Gmax') return 'G-Max One Blow';
302
+ }
303
+ if (moveType === 'Poison' && pokemonSpecies === 'Garbodor-Gmax') return 'G-Max Malodor';
304
+ if (moveType === 'Fighting' && pokemonSpecies === 'Machamp-Gmax') return 'G-Max Chi Strike';
305
+ if (moveType === 'Ghost' && pokemonSpecies === 'Gengar-Gmax') return 'G-Max Terror';
306
+ if (moveType === 'Ice' && pokemonSpecies === 'Lapras-Gmax') return 'G-Max Resonance';
307
+ if (moveType === 'Flying' && pokemonSpecies === 'Corviknight-Gmax') return 'G-Max Wind Rage';
308
+ if (moveType === 'Dragon' && pokemonSpecies === 'Duraludon-Gmax') return 'G-Max Depletion';
309
+ if (moveType === 'Psychic' && pokemonSpecies === 'Orbeetle-Gmax') return 'G-Max Gravitas';
310
+ if (moveType === 'Rock' && pokemonSpecies === 'Coalossal-Gmax') return 'G-Max Volcalith';
311
+ if (moveType === 'Ground' && pokemonSpecies === 'Sandaconda-Gmax') return 'G-Max Sandblast';
312
+ if (moveType === 'Dark' && pokemonSpecies === 'Grimmsnarl-Gmax') return 'G-Max Snooze';
313
+ return 'Max ' + MAXMOVES_TYPING[moveType];
314
+ }
315
+
316
+ const MAXMOVES_TYPING: {
317
+ [type in I.TypeName]?: string;
318
+ } = {
319
+ Bug: 'Flutterby',
320
+ Dark: 'Darkness',
321
+ Dragon: 'Wyrmwind',
322
+ Electric: 'Lightning',
323
+ Fairy: 'Starfall',
324
+ Fighting: 'Knuckle',
325
+ Fire: 'Flare',
326
+ Flying: 'Airstream',
327
+ Ghost: 'Phantasm',
328
+ Grass: 'Overgrowth',
329
+ Ground: 'Quake',
330
+ Ice: 'Hailstorm',
331
+ Normal: 'Strike',
332
+ Poison: 'Ooze',
333
+ Psychic: 'Mindstorm',
334
+ Rock: 'Rockfall',
335
+ Steel: 'Steelspike',
336
+ Water: 'Geyser',
337
+ };
package/src/pokemon.ts ADDED
@@ -0,0 +1,244 @@
1
+ import type * as I from './data/interface';
2
+ import {Stats} from './stats';
3
+ import {toID, extend, assignWithout} from './util';
4
+ import type {State} from './state';
5
+
6
+ const STATS = ['hp', 'atk', 'def', 'spa', 'spd', 'spe'] as I.StatID[];
7
+ const SPC = new Set(['spc']);
8
+
9
+
10
+ export class Pokemon implements State.Pokemon {
11
+ gen: I.Generation;
12
+ name: I.SpeciesName;
13
+ species: I.Specie;
14
+
15
+ types: [I.TypeName] | [I.TypeName, I.TypeName];
16
+ weightkg: number;
17
+
18
+ level: number;
19
+ gender?: I.GenderName;
20
+ ability?: I.AbilityName;
21
+ abilityOn?: boolean;
22
+ isDynamaxed?: boolean;
23
+ dynamaxLevel?: number;
24
+ isSaltCure?: boolean;
25
+ alliesFainted?: number;
26
+ boostedStat?: I.StatIDExceptHP | 'auto';
27
+ item?: I.ItemName;
28
+ disabledItem?: I.ItemName;
29
+ teraType?: I.TypeName;
30
+
31
+ nature: I.NatureName;
32
+ ivs: I.StatsTable;
33
+ evs: I.StatsTable;
34
+ boosts: I.StatsTable;
35
+ rawStats: I.StatsTable;
36
+ stats: I.StatsTable;
37
+
38
+ originalCurHP: number;
39
+ status: I.StatusName | '';
40
+ toxicCounter: number;
41
+
42
+ moves: I.MoveName[];
43
+
44
+ constructor(
45
+ gen: I.Generation,
46
+ name: string,
47
+ options: Partial<State.Pokemon> & {
48
+ curHP?: number;
49
+ ivs?: Partial<I.StatsTable> & {spc?: number};
50
+ evs?: Partial<I.StatsTable> & {spc?: number};
51
+ boosts?: Partial<I.StatsTable> & {spc?: number};
52
+ } = {}
53
+ ) {
54
+ this.species = extend(true, {}, gen.species.get(toID(name)), options.overrides);
55
+
56
+ this.gen = gen;
57
+ this.name = options.name || name as I.SpeciesName;
58
+ this.types = this.species.types;
59
+ this.weightkg = this.species.weightkg;
60
+
61
+ this.level = options.level || 100;
62
+ this.gender = options.gender || this.species.gender || 'M';
63
+ this.ability = options.ability || this.species.abilities?.[0] || undefined;
64
+ this.abilityOn = !!options.abilityOn;
65
+
66
+ this.isDynamaxed = !!options.isDynamaxed;
67
+ this.dynamaxLevel = this.isDynamaxed
68
+ ? (options.dynamaxLevel === undefined ? 10 : options.dynamaxLevel) : undefined;
69
+ this.isSaltCure = !!options.isSaltCure;
70
+ this.alliesFainted = options.alliesFainted;
71
+ this.boostedStat = options.boostedStat;
72
+ this.teraType = options.teraType;
73
+ this.item = options.item;
74
+ this.nature = options.nature || ('Serious' as I.NatureName);
75
+ this.ivs = Pokemon.withDefault(gen, options.ivs, 31);
76
+ this.evs = Pokemon.withDefault(gen, options.evs, gen.num >= 3 ? 0 : 252);
77
+ this.boosts = Pokemon.withDefault(gen, options.boosts, 0, false);
78
+
79
+ // Gigantamax 'forms' inherit weight from their base species when not dynamaxed
80
+ // TODO: clean this up with proper Gigantamax support
81
+ if (this.weightkg === 0 && !this.isDynamaxed && this.species.baseSpecies) {
82
+ this.weightkg = gen.species.get(toID(this.species.baseSpecies))!.weightkg;
83
+ }
84
+
85
+ if (gen.num < 3) {
86
+ this.ivs.hp = Stats.DVToIV(
87
+ Stats.getHPDV({
88
+ atk: this.ivs.atk,
89
+ def: this.ivs.def,
90
+ spe: this.ivs.spe,
91
+ spc: this.ivs.spa,
92
+ })
93
+ );
94
+ }
95
+
96
+ this.rawStats = {} as I.StatsTable;
97
+ this.stats = {} as I.StatsTable;
98
+ for (const stat of STATS) {
99
+ const val = this.calcStat(gen, stat);
100
+ this.rawStats[stat] = val;
101
+ this.stats[stat] = val;
102
+ }
103
+
104
+ const curHP = options.curHP || options.originalCurHP;
105
+ this.originalCurHP = curHP && curHP <= this.rawStats.hp ? curHP : this.rawStats.hp;
106
+ this.status = options.status || '';
107
+ this.toxicCounter = options.toxicCounter || 0;
108
+ this.moves = options.moves || [];
109
+ }
110
+
111
+ maxHP(original = false) {
112
+ // Shedinja still has 1 max HP during the effect even if its Dynamax Level is maxed (DaWoblefet)
113
+ if (!original && this.isDynamaxed && this.species.baseStats.hp !== 1) {
114
+ return Math.floor((this.rawStats.hp * (150 + 5 * this.dynamaxLevel!)) / 100);
115
+ }
116
+
117
+ return this.rawStats.hp;
118
+ }
119
+
120
+ curHP(original = false) {
121
+ // Shedinja still has 1 max HP during the effect even if its Dynamax Level is maxed (DaWoblefet)
122
+ if (!original && this.isDynamaxed && this.species.baseStats.hp !== 1) {
123
+ return Math.ceil((this.originalCurHP * (150 + 5 * this.dynamaxLevel!)) / 100);
124
+ }
125
+
126
+ return this.originalCurHP;
127
+ }
128
+
129
+ hasAbility(...abilities: string[]) {
130
+ return !!(this.ability && abilities.includes(this.ability));
131
+ }
132
+
133
+ hasItem(...items: string[]) {
134
+ return !!(this.item && items.includes(this.item));
135
+ }
136
+
137
+ hasStatus(...statuses: I.StatusName[]) {
138
+ return !!(this.status && statuses.includes(this.status));
139
+ }
140
+
141
+ hasType(...types: I.TypeName[]) {
142
+ for (const type of types) {
143
+ if (this.teraType ? this.teraType === type : this.types.includes(type)) return true;
144
+ }
145
+ return false;
146
+ }
147
+
148
+ /** Ignores Tera type */
149
+ hasOriginalType(...types: I.TypeName[]) {
150
+ for (const type of types) {
151
+ if (this.types.includes(type)) return true;
152
+ }
153
+ return false;
154
+ }
155
+
156
+ named(...names: string[]) {
157
+ return names.includes(this.name);
158
+ }
159
+
160
+ clone() {
161
+ return new Pokemon(this.gen, this.name, {
162
+ level: this.level,
163
+ ability: this.ability,
164
+ abilityOn: this.abilityOn,
165
+ isDynamaxed: this.isDynamaxed,
166
+ dynamaxLevel: this.dynamaxLevel,
167
+ isSaltCure: this.isSaltCure,
168
+ alliesFainted: this.alliesFainted,
169
+ boostedStat: this.boostedStat,
170
+ item: this.item,
171
+ gender: this.gender,
172
+ nature: this.nature,
173
+ ivs: extend(true, {}, this.ivs),
174
+ evs: extend(true, {}, this.evs),
175
+ boosts: extend(true, {}, this.boosts),
176
+ originalCurHP: this.originalCurHP,
177
+ status: this.status,
178
+ teraType: this.teraType,
179
+ toxicCounter: this.toxicCounter,
180
+ moves: this.moves.slice(),
181
+ overrides: this.species,
182
+ });
183
+ }
184
+
185
+ private calcStat(gen: I.Generation, stat: I.StatID) {
186
+ return Stats.calcStat(
187
+ gen,
188
+ stat,
189
+ this.species.baseStats[stat],
190
+ this.ivs[stat]!,
191
+ this.evs[stat]!,
192
+ this.level,
193
+ this.nature
194
+ );
195
+ }
196
+
197
+ static getForme(
198
+ gen: I.Generation,
199
+ speciesName: string,
200
+ item?: I.ItemName,
201
+ moveName?: I.MoveName
202
+ ) {
203
+ const species = gen.species.get(toID(speciesName));
204
+ if (!species?.otherFormes) {
205
+ return speciesName;
206
+ }
207
+
208
+ let i = 0;
209
+ if (
210
+ (item &&
211
+ ((item.includes('ite') && !item.includes('ite Y')) ||
212
+ (speciesName === 'Groudon' && item === 'Red Orb') ||
213
+ (speciesName === 'Kyogre' && item === 'Blue Orb'))) ||
214
+ (moveName && speciesName === 'Meloetta' && moveName === 'Relic Song') ||
215
+ (speciesName === 'Rayquaza' && moveName === 'Dragon Ascent')
216
+ ) {
217
+ i = 1;
218
+ } else if (item?.includes('ite Y')) {
219
+ i = 2;
220
+ }
221
+
222
+ return i ? species.otherFormes[i - 1] : species.name;
223
+ }
224
+
225
+ private static withDefault(
226
+ gen: I.Generation,
227
+ current: Partial<I.StatsTable> & {spc?: number} | undefined,
228
+ val: number,
229
+ match = true,
230
+ ) {
231
+ const cur: Partial<I.StatsTable> = {};
232
+ if (current) {
233
+ assignWithout(cur, current, SPC);
234
+ if (current.spc) {
235
+ cur.spa = current.spc;
236
+ cur.spd = current.spc;
237
+ }
238
+ if (match && gen.num <= 2 && current.spa !== current.spd) {
239
+ throw new Error('Special Attack and Special Defense must match before Gen 3');
240
+ }
241
+ }
242
+ return {hp: val, atk: val, def: val, spa: val, spd: val, spe: val, ...cur};
243
+ }
244
+ }
package/src/result.ts ADDED
@@ -0,0 +1,106 @@
1
+ import {type RawDesc, display, displayMove, getRecovery, getRecoil, getKOChance} from './desc';
2
+ import type {Generation} from './data/interface';
3
+ import type {Field} from './field';
4
+ import type {Move} from './move';
5
+ import type {Pokemon} from './pokemon';
6
+
7
+ export type Damage = number | number[] | [number, number] | [number[], number[]];
8
+
9
+ export class Result {
10
+ gen: Generation;
11
+ attacker: Pokemon;
12
+ defender: Pokemon;
13
+ move: Move;
14
+ field: Field;
15
+ damage: number | number[] | [number[], number[]];
16
+ rawDesc: RawDesc;
17
+
18
+ constructor(
19
+ gen: Generation,
20
+ attacker: Pokemon,
21
+ defender: Pokemon,
22
+ move: Move,
23
+ field: Field,
24
+ damage: Damage,
25
+ rawDesc: RawDesc,
26
+ ) {
27
+ this.gen = gen;
28
+ this.attacker = attacker;
29
+ this.defender = defender;
30
+ this.move = move;
31
+ this.field = field;
32
+ this.damage = damage;
33
+ this.rawDesc = rawDesc;
34
+ }
35
+
36
+ /* get */ desc() {
37
+ return this.fullDesc();
38
+ }
39
+
40
+ range(): [number, number] {
41
+ const range = damageRange(this.damage);
42
+ if (typeof range[0] === 'number') return range as [number, number];
43
+ const d = range as [number[], number[]];
44
+ return [d[0][0] + d[0][1], d[1][0] + d[1][1]];
45
+ }
46
+
47
+ fullDesc(notation = '%', err = true) {
48
+ return display(
49
+ this.gen,
50
+ this.attacker,
51
+ this.defender,
52
+ this.move,
53
+ this.field,
54
+ this.damage,
55
+ this.rawDesc,
56
+ notation,
57
+ err
58
+ );
59
+ }
60
+
61
+ moveDesc(notation = '%') {
62
+ return displayMove(this.gen, this.attacker, this.defender, this.move, this.damage, notation);
63
+ }
64
+
65
+ recovery(notation = '%') {
66
+ return getRecovery(this.gen, this.attacker, this.defender, this.move, this.damage, notation);
67
+ }
68
+
69
+ recoil(notation = '%') {
70
+ return getRecoil(this.gen, this.attacker, this.defender, this.move, this.damage, notation);
71
+ }
72
+
73
+ kochance(err = true) {
74
+ return getKOChance(
75
+ this.gen,
76
+ this.attacker,
77
+ this.defender,
78
+ this.move,
79
+ this.field,
80
+ this.damage,
81
+ err
82
+ );
83
+ }
84
+ }
85
+
86
+ export function damageRange(
87
+ damage: Damage
88
+ ): [number, number] | [[number, number], [number, number]] {
89
+ // Fixed Damage
90
+ if (typeof damage === 'number') return [damage, damage];
91
+ // Standard Damage
92
+ if (damage.length > 2) {
93
+ const d = damage as number[];
94
+ if (d[0] > d[d.length - 1]) return [Math.min(...d), Math.max(...d)];
95
+ return [d[0], d[d.length - 1]];
96
+ }
97
+ // Fixed Parental Bond Damage
98
+ if (typeof damage[0] === 'number' && typeof damage[1] === 'number') {
99
+ return [[damage[0], damage[1]], [damage[0], damage[1]]];
100
+ }
101
+ // Parental Bond Damage
102
+ const d = damage as [number[], number[]];
103
+ if (d[0][0] > d[0][d[0].length - 1]) d[0] = d[0].slice().sort();
104
+ if (d[1][0] > d[1][d[1].length - 1]) d[1] = d[1].slice().sort();
105
+ return [[d[0][0], d[1][0]], [d[0][d[0].length - 1], d[1][d[1].length - 1]]];
106
+ }