@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,702 @@
1
+ import type {Generation, AbilityName} from '../data/interface';
2
+ import {getItemBoostType, getNaturalGift, getFlingPower, getBerryResistType} from '../items';
3
+ import type {RawDesc} from '../desc';
4
+ import type {Field} from '../field';
5
+ import type {Move} from '../move';
6
+ import type {Pokemon} from '../pokemon';
7
+ import {Result} from '../result';
8
+ import {
9
+ getModifiedStat,
10
+ getStatDescriptionText,
11
+ getFinalSpeed,
12
+ getMoveEffectiveness,
13
+ checkAirLock,
14
+ checkForecast,
15
+ checkItem,
16
+ checkIntimidate,
17
+ checkDownload,
18
+ checkMultihitBoost,
19
+ countBoosts,
20
+ handleFixedDamageMoves,
21
+ } from './util';
22
+
23
+ export function calculateDPP(
24
+ gen: Generation,
25
+ attacker: Pokemon,
26
+ defender: Pokemon,
27
+ move: Move,
28
+ field: Field
29
+ ) {
30
+ // #region Initial
31
+
32
+ checkAirLock(attacker, field);
33
+ checkAirLock(defender, field);
34
+ checkForecast(attacker, field.weather);
35
+ checkForecast(defender, field.weather);
36
+ checkItem(attacker);
37
+ checkItem(defender);
38
+ checkIntimidate(gen, attacker, defender);
39
+ checkIntimidate(gen, defender, attacker);
40
+ checkDownload(attacker, defender);
41
+ checkDownload(defender, attacker);
42
+ attacker.stats.spe = getFinalSpeed(gen, attacker, field, field.attackerSide);
43
+ defender.stats.spe = getFinalSpeed(gen, defender, field, field.defenderSide);
44
+
45
+ const desc: RawDesc = {
46
+ attackerName: attacker.name,
47
+ moveName: move.name,
48
+ defenderName: defender.name,
49
+ };
50
+
51
+ const result = new Result(gen, attacker, defender, move, field, 0, desc);
52
+
53
+ if (move.category === 'Status' && !move.named('Nature Power')) {
54
+ return result;
55
+ }
56
+
57
+ if (field.defenderSide.isProtected && !move.breaksProtect) {
58
+ desc.isProtected = true;
59
+ return result;
60
+ }
61
+
62
+ if (move.name === 'Pain Split') {
63
+ const average = Math.floor((attacker.curHP() + defender.curHP()) / 2);
64
+ const damage = Math.max(0, defender.curHP() - average);
65
+ result.damage = damage;
66
+ return result;
67
+ }
68
+
69
+ const defenderAbilityIgnored = defender.hasAbility(
70
+ 'Battle Armor', 'Clear Body', 'Damp', 'Dry Skin',
71
+ 'Filter', 'Flash Fire', 'Flower Gift', 'Heatproof',
72
+ 'Hyper Cutter', 'Immunity', 'Inner Focus', 'Insomnia',
73
+ 'Keen Eye', 'Leaf Guard', 'Levitate', 'Lightning Rod',
74
+ 'Limber', 'Magma Armor', 'Marvel Scale', 'Motor Drive',
75
+ 'Oblivious', 'Own Tempo', 'Sand Veil', 'Shell Armor',
76
+ 'Shield Dust', 'Simple', 'Snow Cloak', 'Solid Rock',
77
+ 'Soundproof', 'Sticky Hold', 'Storm Drain', 'Sturdy',
78
+ 'Suction Cups', 'Tangled Feet', 'Thick Fat', 'Unaware',
79
+ 'Vital Spirit', 'Volt Absorb', 'Water Absorb', 'Water Veil',
80
+ 'White Smoke', 'Wonder Guard'
81
+ );
82
+
83
+ if (attacker.hasAbility('Mold Breaker') && defenderAbilityIgnored) {
84
+ defender.ability = '' as AbilityName;
85
+ desc.attackerAbility = attacker.ability;
86
+ }
87
+
88
+ const isCritical = move.isCrit && !defender.hasAbility('Battle Armor', 'Shell Armor');
89
+
90
+ if (move.named('Weather Ball')) {
91
+ move.type =
92
+ field.hasWeather('Sun') ? 'Fire'
93
+ : field.hasWeather('Rain') ? 'Water'
94
+ : field.hasWeather('Sand') ? 'Rock'
95
+ : field.hasWeather('Hail') ? 'Ice'
96
+ : 'Normal';
97
+ desc.weather = field.weather;
98
+ desc.moveType = move.type;
99
+ } else if (move.named('Judgment') && attacker.item && attacker.item.includes('Plate')) {
100
+ move.type = getItemBoostType(attacker.item)!;
101
+ } else if (move.named('Natural Gift') && attacker.item?.endsWith('Berry')) {
102
+ const gift = getNaturalGift(gen, attacker.item)!;
103
+ move.type = gift.t;
104
+ move.bp = gift.p;
105
+ desc.attackerItem = attacker.item;
106
+ desc.moveBP = move.bp;
107
+ desc.moveType = move.type;
108
+ } else if (move.named('Brick Break')) {
109
+ field.defenderSide.isReflect = false;
110
+ field.defenderSide.isLightScreen = false;
111
+ }
112
+
113
+ if (attacker.hasAbility('Normalize') && !move.named('Struggle')) {
114
+ move.type = 'Normal';
115
+ desc.attackerAbility = attacker.ability;
116
+ }
117
+
118
+ const isGhostRevealed = attacker.hasAbility('Scrappy') || field.defenderSide.isForesight;
119
+
120
+ const typeEffectivenessPrecedenceRules = [
121
+ 'Normal',
122
+ 'Fire',
123
+ 'Water',
124
+ 'Electric',
125
+ 'Grass',
126
+ 'Ice',
127
+ 'Fighting',
128
+ 'Poison',
129
+ 'Ground',
130
+ 'Flying',
131
+ 'Psychic',
132
+ 'Bug',
133
+ 'Rock',
134
+ 'Ghost',
135
+ 'Dragon',
136
+ 'Dark',
137
+ 'Steel',
138
+ ];
139
+
140
+ let firstDefenderType = defender.types[0];
141
+ let secondDefenderType = defender.types[1];
142
+
143
+ if (secondDefenderType && firstDefenderType !== secondDefenderType) {
144
+ const firstTypePrecedence = typeEffectivenessPrecedenceRules.indexOf(firstDefenderType);
145
+ const secondTypePrecedence = typeEffectivenessPrecedenceRules.indexOf(secondDefenderType);
146
+
147
+ if (firstTypePrecedence > secondTypePrecedence) {
148
+ [firstDefenderType, secondDefenderType] = [secondDefenderType, firstDefenderType];
149
+ }
150
+ }
151
+
152
+ let type1Effectiveness =
153
+ getMoveEffectiveness(gen, move, firstDefenderType, isGhostRevealed, field.isGravity);
154
+ let type2Effectiveness = secondDefenderType
155
+ ? getMoveEffectiveness(gen, move, secondDefenderType, isGhostRevealed, field.isGravity)
156
+ : 1;
157
+
158
+ let typeEffectiveness = type1Effectiveness * type2Effectiveness;
159
+
160
+ // Klutz doesn't let Iron Ball ground in generation 4
161
+ if (typeEffectiveness === 0 && move.hasType('Ground') &&
162
+ (defender.hasItem('Iron Ball') && !defender.hasAbility('Klutz'))) {
163
+ if (type1Effectiveness === 0) {
164
+ type1Effectiveness = 1;
165
+ } else if (defender.types[1] && type2Effectiveness === 0) {
166
+ type2Effectiveness = 1;
167
+ }
168
+ typeEffectiveness = type1Effectiveness * type2Effectiveness;
169
+ }
170
+
171
+ if (typeEffectiveness === 0) {
172
+ return result;
173
+ }
174
+
175
+ const ignoresWonderGuard = move.hasType('???') || move.named('Fire Fang');
176
+ if ((!ignoresWonderGuard && defender.hasAbility('Wonder Guard') && typeEffectiveness <= 1) ||
177
+ (move.hasType('Fire') && defender.hasAbility('Flash Fire')) ||
178
+ (move.hasType('Water') && defender.hasAbility('Dry Skin', 'Water Absorb')) ||
179
+ (move.hasType('Electric') && defender.hasAbility('Motor Drive', 'Volt Absorb')) ||
180
+ (move.hasType('Ground') && !field.isGravity &&
181
+ !defender.hasItem('Iron Ball') && defender.hasAbility('Levitate')) ||
182
+ (move.flags.sound && defender.hasAbility('Soundproof'))
183
+ ) {
184
+ desc.defenderAbility = defender.ability;
185
+ return result;
186
+ }
187
+
188
+ desc.HPEVs = getStatDescriptionText(gen, defender, 'hp');
189
+
190
+ const fixedDamage = handleFixedDamageMoves(attacker, move);
191
+ if (fixedDamage) {
192
+ result.damage = fixedDamage;
193
+ return result;
194
+ }
195
+
196
+ if (move.hits > 1) {
197
+ desc.hits = move.hits;
198
+ }
199
+
200
+ const isPhysical = move.category === 'Physical';
201
+
202
+ // #endregion
203
+ // #region Base Power
204
+
205
+ let basePower = calculateBasePowerDPP(gen, attacker, defender, move, field, desc);
206
+ if (basePower === 0) {
207
+ return result;
208
+ }
209
+ basePower = calculateBPModsDPP(attacker, defender, move, field, desc, basePower);
210
+
211
+ // #endregion
212
+ // #region (Special) Attack
213
+ const attack = calculateAttackDPP(gen, attacker, defender, move, field, desc, isCritical);
214
+
215
+ // #endregion
216
+
217
+ // #region (Special) Defense
218
+ const defense = calculateDefenseDPP(gen, attacker, defender, move, field, desc, isCritical);
219
+
220
+ // #endregion
221
+ // #region Damage
222
+
223
+ let baseDamage = Math.floor(
224
+ Math.floor((Math.floor((2 * attacker.level) / 5 + 2) * basePower * attack) / 50) / defense
225
+ );
226
+
227
+ if (attacker.hasStatus('brn') && isPhysical && !attacker.hasAbility('Guts')) {
228
+ baseDamage = Math.floor(baseDamage * 0.5);
229
+ desc.isBurned = true;
230
+ }
231
+
232
+ baseDamage = calculateFinalModsDPP(baseDamage, attacker, move, field, desc, isCritical);
233
+
234
+ // the random factor is applied between the LO mod and the STAB mod, so don't apply anything
235
+ // below this until we're inside the loop
236
+ let stabMod = 1;
237
+ if (move.hasType(...attacker.types)) {
238
+ if (attacker.hasAbility('Adaptability')) {
239
+ stabMod = 2;
240
+ desc.attackerAbility = attacker.ability;
241
+ } else {
242
+ stabMod = 1.5;
243
+ }
244
+ }
245
+
246
+ let filterMod = 1;
247
+ if (defender.hasAbility('Filter', 'Solid Rock') && typeEffectiveness > 1) {
248
+ filterMod = 0.75;
249
+ desc.defenderAbility = defender.ability;
250
+ }
251
+ let ebeltMod = 1;
252
+ if (attacker.hasItem('Expert Belt') && typeEffectiveness > 1) {
253
+ ebeltMod = 1.2;
254
+ desc.attackerItem = attacker.item;
255
+ }
256
+ let tintedMod = 1;
257
+ if (attacker.hasAbility('Tinted Lens') && typeEffectiveness < 1) {
258
+ tintedMod = 2;
259
+ desc.attackerAbility = attacker.ability;
260
+ }
261
+ let berryMod = 1;
262
+ if (move.hasType(getBerryResistType(defender.item)) &&
263
+ (typeEffectiveness > 1 || move.hasType('Normal'))) {
264
+ berryMod = 0.5;
265
+ desc.defenderItem = defender.item;
266
+ }
267
+
268
+ const damage: number[] = [];
269
+ for (let i = 0; i < 16; i++) {
270
+ damage[i] = Math.floor((baseDamage * (85 + i)) / 100);
271
+ damage[i] = Math.floor(damage[i] * stabMod);
272
+ damage[i] = Math.floor(damage[i] * type1Effectiveness);
273
+ damage[i] = Math.floor(damage[i] * type2Effectiveness);
274
+ damage[i] = Math.floor(damage[i] * filterMod);
275
+ damage[i] = Math.floor(damage[i] * ebeltMod);
276
+ damage[i] = Math.floor(damage[i] * tintedMod);
277
+ damage[i] = Math.floor(damage[i] * berryMod);
278
+ damage[i] = Math.max(1, damage[i]);
279
+ }
280
+ result.damage = damage;
281
+
282
+ if ((move.dropsStats && move.timesUsed! > 1) || move.hits > 1) {
283
+ // store boosts so intermediate boosts don't show.
284
+ const origDefBoost = desc.defenseBoost;
285
+ const origAtkBoost = desc.attackBoost;
286
+ let numAttacks = 1;
287
+ if (move.dropsStats && move.timesUsed! > 1) {
288
+ desc.moveTurns = `over ${move.timesUsed} turns`;
289
+ numAttacks = move.timesUsed!;
290
+ } else {
291
+ numAttacks = move.hits;
292
+ }
293
+ let usedItems = [false, false];
294
+ for (let times = 1; times < numAttacks; times++) {
295
+ usedItems = checkMultihitBoost(gen, attacker, defender, move,
296
+ field, desc, usedItems[0], usedItems[1]);
297
+ let newBasePower = calculateBasePowerDPP(gen, attacker, defender, move, field, desc);
298
+ newBasePower = calculateBPModsDPP(attacker, defender, move, field, desc, newBasePower);
299
+ const newAtk = calculateAttackDPP(gen, attacker, defender, move, field, desc, isCritical);
300
+ let baseDamage = Math.floor(
301
+ Math.floor(
302
+ (Math.floor((2 * attacker.level) / 5 + 2) * newBasePower * newAtk) / 50
303
+ ) / defense
304
+ );
305
+ if (attacker.hasStatus('brn') && isPhysical && !attacker.hasAbility('Guts')) {
306
+ baseDamage = Math.floor(baseDamage * 0.5);
307
+ desc.isBurned = true;
308
+ }
309
+ baseDamage = calculateFinalModsDPP(baseDamage, attacker, move, field, desc, isCritical);
310
+
311
+ let damageMultiplier = 0;
312
+ result.damage = result.damage.map(affectedAmount => {
313
+ let newFinalDamage = 0;
314
+ newFinalDamage = Math.floor((baseDamage * (85 + damageMultiplier)) / 100);
315
+ newFinalDamage = Math.floor(newFinalDamage * stabMod);
316
+ newFinalDamage = Math.floor(newFinalDamage * type1Effectiveness);
317
+ newFinalDamage = Math.floor(newFinalDamage * type2Effectiveness);
318
+ newFinalDamage = Math.floor(newFinalDamage * filterMod);
319
+ newFinalDamage = Math.floor(newFinalDamage * ebeltMod);
320
+ newFinalDamage = Math.floor(newFinalDamage * tintedMod);
321
+ newFinalDamage = Math.max(1, newFinalDamage);
322
+ damageMultiplier++;
323
+ return affectedAmount + newFinalDamage;
324
+ });
325
+ }
326
+ desc.defenseBoost = origDefBoost;
327
+ desc.attackBoost = origAtkBoost;
328
+ }
329
+
330
+ // #endregion
331
+
332
+ return result;
333
+ }
334
+
335
+ export function calculateBasePowerDPP(
336
+ gen: Generation,
337
+ attacker: Pokemon,
338
+ defender: Pokemon,
339
+ move: Move,
340
+ field: Field,
341
+ desc: RawDesc,
342
+ hit = 1,
343
+ ) {
344
+ let basePower = move.bp;
345
+ const turnOrder = attacker.stats.spe > defender.stats.spe ? 'first' : 'last';
346
+ switch (move.name) {
347
+ case 'Brine':
348
+ if (defender.curHP() <= defender.maxHP() / 2) {
349
+ basePower *= 2;
350
+ desc.moveBP = basePower;
351
+ }
352
+ break;
353
+ case 'Eruption':
354
+ case 'Water Spout':
355
+ basePower = Math.max(1, Math.floor((basePower * attacker.curHP()) / attacker.maxHP()));
356
+ desc.moveBP = basePower;
357
+ break;
358
+ case 'Facade':
359
+ if (attacker.hasStatus('par', 'psn', 'tox', 'brn')) {
360
+ basePower = move.bp * 2;
361
+ desc.moveBP = basePower;
362
+ }
363
+ break;
364
+ case 'Flail':
365
+ case 'Reversal':
366
+ const p = Math.floor((64 * attacker.curHP()) / attacker.maxHP());
367
+ basePower = p <= 1 ? 200 : p <= 5 ? 150 : p <= 12 ? 100 : p <= 21 ? 80 : p <= 42 ? 40 : 20;
368
+ desc.moveBP = basePower;
369
+ break;
370
+ case 'Fling':
371
+ basePower = getFlingPower(attacker.item);
372
+ desc.moveBP = basePower;
373
+ desc.attackerItem = attacker.item;
374
+ break;
375
+ case 'Grass Knot':
376
+ case 'Low Kick':
377
+ const w = defender.weightkg;
378
+ basePower = w >= 200 ? 120 : w >= 100 ? 100 : w >= 50 ? 80 : w >= 25 ? 60 : w >= 10 ? 40 : 20;
379
+ desc.moveBP = basePower;
380
+ break;
381
+ case 'Gyro Ball':
382
+ basePower = Math.min(150, Math.floor((25 * defender.stats.spe) / attacker.stats.spe));
383
+ desc.moveBP = basePower;
384
+ break;
385
+ case 'Payback':
386
+ if (turnOrder !== 'first') {
387
+ basePower *= 2;
388
+ desc.moveBP = basePower;
389
+ }
390
+ break;
391
+ case 'Punishment':
392
+ basePower = Math.min(200, 60 + 20 * countBoosts(gen, defender.boosts));
393
+ desc.moveBP = basePower;
394
+ break;
395
+ case 'Wake-Up Slap':
396
+ if (defender.hasStatus('slp')) {
397
+ basePower *= 2;
398
+ desc.moveBP = basePower;
399
+ }
400
+ break;
401
+ case 'Nature Power':
402
+ move.category = 'Special';
403
+ move.secondaries = true;
404
+ basePower = 80;
405
+ desc.moveName = 'Tri Attack';
406
+ break;
407
+ case 'Crush Grip':
408
+ case 'Wring Out':
409
+ basePower = Math.floor((defender.curHP() * 120) / defender.maxHP()) + 1;
410
+ desc.moveBP = basePower;
411
+ break;
412
+ case 'Triple Kick':
413
+ basePower = hit * 10;
414
+ desc.moveBP = move.hits === 2 ? 30 : move.hits === 3 ? 60 : 10;
415
+ break;
416
+ case 'Weather Ball':
417
+ basePower = move.bp * (field.weather ? 2 : 1);
418
+ desc.moveBP = basePower;
419
+ break;
420
+ default:
421
+ basePower = move.bp;
422
+ }
423
+ return basePower;
424
+ }
425
+
426
+ export function calculateBPModsDPP(
427
+ attacker: Pokemon,
428
+ defender: Pokemon,
429
+ move: Move,
430
+ field: Field,
431
+ desc: RawDesc,
432
+ basePower: number,
433
+ ) {
434
+ if (field.attackerSide.isHelpingHand) {
435
+ basePower = Math.floor(basePower * 1.5);
436
+ desc.isHelpingHand = true;
437
+ }
438
+
439
+ const isPhysical = move.category === 'Physical';
440
+ if ((attacker.hasItem('Muscle Band') && isPhysical) ||
441
+ (attacker.hasItem('Wise Glasses') && !isPhysical)) {
442
+ basePower = Math.floor(basePower * 1.1);
443
+ desc.attackerItem = attacker.item;
444
+ } else if (move.hasType(getItemBoostType(attacker.item)) ||
445
+ (attacker.hasItem('Adamant Orb') &&
446
+ attacker.named('Dialga') &&
447
+ move.hasType('Steel', 'Dragon')) ||
448
+ (attacker.hasItem('Lustrous Orb') &&
449
+ attacker.named('Palkia') &&
450
+ move.hasType('Water', 'Dragon')) ||
451
+ (attacker.hasItem('Griseous Orb') &&
452
+ attacker.named('Giratina-Origin') &&
453
+ move.hasType('Ghost', 'Dragon'))
454
+ ) {
455
+ basePower = Math.floor(basePower * 1.2);
456
+ desc.attackerItem = attacker.item;
457
+ }
458
+
459
+ if ((attacker.hasAbility('Reckless') && (move.recoil || move.hasCrashDamage)) ||
460
+ (attacker.hasAbility('Iron Fist') && move.flags.punch)) {
461
+ basePower = Math.floor(basePower * 1.2);
462
+ desc.attackerAbility = attacker.ability;
463
+ } else if ((attacker.curHP() <= attacker.maxHP() / 3 &&
464
+ ((attacker.hasAbility('Overgrow') && move.hasType('Grass')) ||
465
+ (attacker.hasAbility('Blaze') && move.hasType('Fire')) ||
466
+ (attacker.hasAbility('Torrent') && move.hasType('Water')) ||
467
+ (attacker.hasAbility('Swarm') && move.hasType('Bug')))) ||
468
+ (attacker.hasAbility('Technician') && basePower <= 60)
469
+ ) {
470
+ basePower = Math.floor(basePower * 1.5);
471
+ desc.attackerAbility = attacker.ability;
472
+ }
473
+
474
+ if ((defender.hasAbility('Heatproof') && move.hasType('Fire')) ||
475
+ (defender.hasAbility('Thick Fat') && (move.hasType('Fire', 'Ice')))) {
476
+ basePower = Math.floor(basePower * 0.5);
477
+ desc.defenderAbility = defender.ability;
478
+ } else if (defender.hasAbility('Dry Skin') && move.hasType('Fire')) {
479
+ basePower = Math.floor(basePower * 1.25);
480
+ desc.defenderAbility = defender.ability;
481
+ }
482
+ return basePower;
483
+ }
484
+
485
+ export function calculateAttackDPP(
486
+ gen: Generation,
487
+ attacker: Pokemon,
488
+ defender: Pokemon,
489
+ move: Move,
490
+ field: Field,
491
+ desc: RawDesc,
492
+ isCritical = false
493
+ ) {
494
+ const isPhysical = move.category === 'Physical';
495
+ const attackStat = isPhysical ? 'atk' : 'spa';
496
+ desc.attackEVs = getStatDescriptionText(gen, attacker, attackStat, attacker.nature);
497
+ let attack: number;
498
+ const attackBoost = attacker.boosts[attackStat];
499
+ const rawAttack = attacker.rawStats[attackStat];
500
+ if (attackBoost === 0 || (isCritical && attackBoost < 0)) {
501
+ attack = rawAttack;
502
+ } else if (defender.hasAbility('Unaware')) {
503
+ attack = rawAttack;
504
+ desc.defenderAbility = defender.ability;
505
+ } else if (attacker.hasAbility('Simple')) {
506
+ attack = getSimpleModifiedStat(rawAttack, attackBoost);
507
+ desc.attackerAbility = attacker.ability;
508
+ desc.attackBoost = attackBoost;
509
+ } else {
510
+ attack = getModifiedStat(rawAttack, attackBoost);
511
+ desc.attackBoost = attackBoost;
512
+ }
513
+
514
+ if (isPhysical && attacker.hasAbility('Pure Power', 'Huge Power')) {
515
+ attack *= 2;
516
+ desc.attackerAbility = attacker.ability;
517
+ } else if (field.hasWeather('Sun') &&
518
+ (attacker.hasAbility(isPhysical ? 'Flower Gift' : 'Solar Power'))
519
+ ) {
520
+ attack = Math.floor(attack * 1.5);
521
+ desc.attackerAbility = attacker.ability;
522
+ desc.weather = field.weather;
523
+ } else if (
524
+ (isPhysical &&
525
+ (attacker.hasAbility('Hustle') || (attacker.hasAbility('Guts') && attacker.status)) ||
526
+ (!isPhysical && attacker.abilityOn && attacker.hasAbility('Plus', 'Minus')))
527
+ ) {
528
+ attack = Math.floor(attack * 1.5);
529
+ desc.attackerAbility = attacker.ability;
530
+ } else if (isPhysical && attacker.hasAbility('Slow Start') && attacker.abilityOn) {
531
+ attack = Math.floor(attack / 2);
532
+ desc.attackerAbility = attacker.ability;
533
+ }
534
+
535
+ if (field.attackerSide.isFlowerGift && !attacker.hasAbility('Flower Gift') &&
536
+ field.hasWeather('Sun') && isPhysical) {
537
+ attack = Math.floor(attack * 1.5);
538
+ desc.weather = field.weather;
539
+ desc.isFlowerGiftAttacker = true;
540
+ }
541
+
542
+ if ((isPhysical ? attacker.hasItem('Choice Band') : attacker.hasItem('Choice Specs')) ||
543
+ (!isPhysical && attacker.hasItem('Soul Dew') && attacker.named('Latios', 'Latias'))) {
544
+ attack = Math.floor(attack * 1.5);
545
+ desc.attackerItem = attacker.item;
546
+ } else if (
547
+ (attacker.hasItem('Light Ball') && attacker.named('Pikachu')) ||
548
+ (attacker.hasItem('Thick Club') && attacker.named('Cubone', 'Marowak') && isPhysical) ||
549
+ (attacker.hasItem('Deep Sea Tooth') && attacker.named('Clamperl') && !isPhysical)
550
+ ) {
551
+ attack *= 2;
552
+ desc.attackerItem = attacker.item;
553
+ }
554
+ return attack;
555
+ }
556
+
557
+ export function calculateDefenseDPP(
558
+ gen: Generation,
559
+ attacker: Pokemon,
560
+ defender: Pokemon,
561
+ move: Move,
562
+ field: Field,
563
+ desc: RawDesc,
564
+ isCritical = false
565
+ ) {
566
+ const isPhysical = move.category === 'Physical';
567
+ const defenseStat = isPhysical ? 'def' : 'spd';
568
+ desc.defenseEVs = getStatDescriptionText(gen, defender, defenseStat, defender.nature);
569
+ let defense: number;
570
+ const defenseBoost = defender.boosts[defenseStat];
571
+ const rawDefense = defender.rawStats[defenseStat];
572
+ if (defenseBoost === 0 || (isCritical && defenseBoost > 0)) {
573
+ defense = rawDefense;
574
+ } else if (attacker.hasAbility('Unaware')) {
575
+ defense = rawDefense;
576
+ desc.attackerAbility = attacker.ability;
577
+ } else if (defender.hasAbility('Simple')) {
578
+ defense = getSimpleModifiedStat(rawDefense, defenseBoost);
579
+ desc.defenderAbility = defender.ability;
580
+ desc.defenseBoost = defenseBoost;
581
+ } else {
582
+ defense = getModifiedStat(rawDefense, defenseBoost);
583
+ desc.defenseBoost = defenseBoost;
584
+ }
585
+
586
+ if (defender.hasAbility('Marvel Scale') && defender.status && isPhysical) {
587
+ defense = Math.floor(defense * 1.5);
588
+ desc.defenderAbility = defender.ability;
589
+ } else if (defender.hasAbility('Flower Gift') && field.hasWeather('Sun') && !isPhysical) {
590
+ defense = Math.floor(defense * 1.5);
591
+ desc.defenderAbility = defender.ability;
592
+ desc.weather = field.weather;
593
+ } else if (field.defenderSide.isFlowerGift && field.hasWeather('Sun') && !isPhysical) {
594
+ defense = Math.floor(defense * 1.5);
595
+ desc.weather = field.weather;
596
+ desc.isFlowerGiftDefender = true;
597
+ }
598
+
599
+ if (defender.hasItem('Soul Dew') && defender.named('Latios', 'Latias') && !isPhysical) {
600
+ defense = Math.floor(defense * 1.5);
601
+ desc.defenderItem = defender.item;
602
+ } else if (
603
+ (defender.hasItem('Deep Sea Scale') && defender.named('Clamperl') && !isPhysical) ||
604
+ (defender.hasItem('Metal Powder') && defender.named('Ditto') && isPhysical)
605
+ ) {
606
+ defense *= 2;
607
+ desc.defenderItem = defender.item;
608
+ }
609
+
610
+ if (field.hasWeather('Sand') && defender.hasType('Rock') && !isPhysical) {
611
+ defense = Math.floor(defense * 1.5);
612
+ desc.weather = field.weather;
613
+ }
614
+
615
+ if (move.named('Explosion') || move.named('Self-Destruct')) {
616
+ defense = Math.floor(defense * 0.5);
617
+ }
618
+
619
+ if (defense < 1) {
620
+ defense = 1;
621
+ }
622
+ return defense;
623
+ }
624
+
625
+ function calculateFinalModsDPP(
626
+ baseDamage: number,
627
+ attacker: Pokemon,
628
+ move: Move,
629
+ field: Field,
630
+ desc: RawDesc,
631
+ isCritical = false,
632
+ ) {
633
+ const isPhysical = move.category === 'Physical';
634
+ if (!isCritical) {
635
+ const screenMultiplier = field.gameType !== 'Singles' ? 2 / 3 : 1 / 2;
636
+ if (isPhysical && field.defenderSide.isReflect) {
637
+ baseDamage = Math.floor(baseDamage * screenMultiplier);
638
+ desc.isReflect = true;
639
+ } else if (!isPhysical && field.defenderSide.isLightScreen) {
640
+ baseDamage = Math.floor(baseDamage * screenMultiplier);
641
+ desc.isLightScreen = true;
642
+ }
643
+ }
644
+
645
+ if (field.gameType !== 'Singles' &&
646
+ ['allAdjacent', 'allAdjacentFoes'].includes(move.target)) {
647
+ baseDamage = Math.floor((baseDamage * 3) / 4);
648
+ }
649
+
650
+ if ((field.hasWeather('Sun') && move.hasType('Fire')) ||
651
+ (field.hasWeather('Rain') && move.hasType('Water'))) {
652
+ baseDamage = Math.floor(baseDamage * 1.5);
653
+ desc.weather = field.weather;
654
+ } else if (
655
+ (field.hasWeather('Sun') && move.hasType('Water')) ||
656
+ (field.hasWeather('Rain') && move.hasType('Fire')) ||
657
+ (move.named('Solar Beam') && field.hasWeather('Rain', 'Sand', 'Hail'))
658
+ ) {
659
+ baseDamage = Math.floor(baseDamage * 0.5);
660
+ desc.weather = field.weather;
661
+ }
662
+
663
+ if (attacker.hasAbility('Flash Fire') && attacker.abilityOn && move.hasType('Fire')) {
664
+ baseDamage = Math.floor(baseDamage * 1.5);
665
+ desc.attackerAbility = 'Flash Fire';
666
+ }
667
+
668
+ baseDamage += 2;
669
+
670
+ if (isCritical) {
671
+ if (attacker.hasAbility('Sniper')) {
672
+ baseDamage *= 3;
673
+ desc.attackerAbility = attacker.ability;
674
+ } else {
675
+ baseDamage *= 2;
676
+ }
677
+ desc.isCritical = isCritical;
678
+ }
679
+
680
+ if (attacker.hasItem('Life Orb')) {
681
+ baseDamage = Math.floor(baseDamage * 1.3);
682
+ desc.attackerItem = attacker.item;
683
+ }
684
+
685
+ if (move.named('Pursuit') && field.defenderSide.isSwitching === 'out') {
686
+ // technician negates switching boost, thanks DaWoblefet
687
+ if (attacker.hasAbility('Technician')) {
688
+ baseDamage = Math.floor(baseDamage * 1);
689
+ } else {
690
+ baseDamage = Math.floor(baseDamage * 2);
691
+ desc.isSwitching = 'out';
692
+ }
693
+ }
694
+ return baseDamage;
695
+ }
696
+
697
+ function getSimpleModifiedStat(stat: number, mod: number) {
698
+ const simpleMod = Math.min(6, Math.max(-6, mod * 2));
699
+ return simpleMod > 0
700
+ ? Math.floor((stat * (2 + simpleMod)) / 2)
701
+ : simpleMod < 0 ? Math.floor((stat * 2) / (2 - simpleMod)) : stat;
702
+ }