@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.
- package/.eslintignore +2 -0
- package/.eslintrc +12 -0
- package/bundle +107 -0
- package/dist/adaptable.d.ts +6 -0
- package/dist/adaptable.js +28 -0
- package/dist/adaptable.js.map +1 -0
- package/dist/calc.d.ts +6 -0
- package/dist/calc.js +26 -0
- package/dist/calc.js.map +1 -0
- package/dist/data/abilities.d.ts +15 -0
- package/dist/data/abilities.js +448 -0
- package/dist/data/abilities.js.map +1 -0
- package/dist/data/index.d.ts +2 -0
- package/dist/data/index.js +30 -0
- package/dist/data/index.js.map +1 -0
- package/dist/data/interface.d.ts +150 -0
- package/dist/data/interface.js +3 -0
- package/dist/data/interface.js.map +1 -0
- package/dist/data/items.d.ts +24 -0
- package/dist/data/items.js +708 -0
- package/dist/data/items.js.map +1 -0
- package/dist/data/moves.d.ts +86 -0
- package/dist/data/moves.js +5014 -0
- package/dist/data/moves.js.map +1 -0
- package/dist/data/natures.d.ts +17 -0
- package/dist/data/natures.js +127 -0
- package/dist/data/natures.js.map +1 -0
- package/dist/data/production.min.js +1 -0
- package/dist/data/species.d.ts +48 -0
- package/dist/data/species.js +10126 -0
- package/dist/data/species.js.map +1 -0
- package/dist/data/types.d.ts +23 -0
- package/dist/data/types.js +538 -0
- package/dist/data/types.js.map +1 -0
- package/dist/desc.d.ts +65 -0
- package/dist/desc.js +866 -0
- package/dist/desc.js.map +1 -0
- package/dist/field.d.ts +49 -0
- package/dist/field.js +111 -0
- package/dist/field.js.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +99 -0
- package/dist/index.js.map +1 -0
- package/dist/items.d.ts +13 -0
- package/dist/items.js +434 -0
- package/dist/items.js.map +1 -0
- package/dist/mechanics/gen12.d.ts +6 -0
- package/dist/mechanics/gen12.js +271 -0
- package/dist/mechanics/gen12.js.map +1 -0
- package/dist/mechanics/gen3.d.ts +11 -0
- package/dist/mechanics/gen3.js +371 -0
- package/dist/mechanics/gen3.js.map +1 -0
- package/dist/mechanics/gen4.d.ts +11 -0
- package/dist/mechanics/gen4.js +596 -0
- package/dist/mechanics/gen4.js.map +1 -0
- package/dist/mechanics/gen56.d.ts +13 -0
- package/dist/mechanics/gen56.js +836 -0
- package/dist/mechanics/gen56.js.map +1 -0
- package/dist/mechanics/gen789.d.ts +14 -0
- package/dist/mechanics/gen789.js +1325 -0
- package/dist/mechanics/gen789.js.map +1 -0
- package/dist/mechanics/util.d.ts +39 -0
- package/dist/mechanics/util.js +675 -0
- package/dist/mechanics/util.js.map +1 -0
- package/dist/move.d.ts +50 -0
- package/dist/move.js +324 -0
- package/dist/move.js.map +1 -0
- package/dist/pokemon.d.ts +55 -0
- package/dist/pokemon.js +240 -0
- package/dist/pokemon.js.map +1 -0
- package/dist/production.min.js +1 -0
- package/dist/result.d.ts +34 -0
- package/dist/result.js +94 -0
- package/dist/result.js.map +1 -0
- package/dist/state.d.ts +77 -0
- package/dist/state.js +3 -0
- package/dist/state.js.map +1 -0
- package/dist/stats.d.ts +26 -0
- package/dist/stats.js +183 -0
- package/dist/stats.js.map +1 -0
- package/dist/test/calc.test.d.ts +1 -0
- package/dist/test/calc.test.js +1297 -0
- package/dist/test/calc.test.js.map +1 -0
- package/dist/test/data.test.d.ts +1 -0
- package/dist/test/data.test.js +368 -0
- package/dist/test/data.test.js.map +1 -0
- package/dist/test/gen.d.ts +135 -0
- package/dist/test/gen.js +636 -0
- package/dist/test/gen.js.map +1 -0
- package/dist/test/helper.d.ts +55 -0
- package/dist/test/helper.js +174 -0
- package/dist/test/helper.js.map +1 -0
- package/dist/test/move.test.d.ts +1 -0
- package/dist/test/move.test.js +14 -0
- package/dist/test/move.test.js.map +1 -0
- package/dist/test/pokemon.test.d.ts +1 -0
- package/dist/test/pokemon.test.js +102 -0
- package/dist/test/pokemon.test.js.map +1 -0
- package/dist/test/stats.test.d.ts +1 -0
- package/dist/test/stats.test.js +64 -0
- package/dist/test/stats.test.js.map +1 -0
- package/dist/test/utils.test.d.ts +1 -0
- package/dist/test/utils.test.js +19 -0
- package/dist/test/utils.test.js.map +1 -0
- package/dist/util.d.ts +17 -0
- package/dist/util.js +115 -0
- package/dist/util.js.map +1 -0
- package/jest.config.js +11 -0
- package/package.json +40 -0
- package/src/adaptable.ts +12 -0
- package/src/calc.ts +40 -0
- package/src/data/abilities.ts +383 -0
- package/src/data/index.ts +36 -0
- package/src/data/interface.ts +176 -0
- package/src/data/items.ts +632 -0
- package/src/data/moves.ts +5028 -0
- package/src/data/natures.ts +65 -0
- package/src/data/species.ts +10098 -0
- package/src/data/types.ts +478 -0
- package/src/desc.ts +1063 -0
- package/src/field.ts +124 -0
- package/src/index.ts +156 -0
- package/src/items.ts +423 -0
- package/src/mechanics/gen12.ts +297 -0
- package/src/mechanics/gen3.ts +444 -0
- package/src/mechanics/gen4.ts +702 -0
- package/src/mechanics/gen56.ts +1134 -0
- package/src/mechanics/gen789.ts +1788 -0
- package/src/mechanics/util.ts +676 -0
- package/src/move.ts +337 -0
- package/src/pokemon.ts +244 -0
- package/src/result.ts +106 -0
- package/src/state.ts +81 -0
- package/src/stats.ts +213 -0
- package/src/test/calc.test.ts +1588 -0
- package/src/test/data.test.ts +129 -0
- package/src/test/gen.ts +514 -0
- package/src/test/helper.ts +185 -0
- package/src/test/move.test.ts +13 -0
- package/src/test/pokemon.test.ts +121 -0
- package/src/test/stats.test.ts +84 -0
- package/src/test/utils.test.ts +18 -0
- package/src/util.ts +153 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,1134 @@
|
|
|
1
|
+
import type {Generation, AbilityName} from '../data/interface';
|
|
2
|
+
import {toID} from '../util';
|
|
3
|
+
import {
|
|
4
|
+
getItemBoostType,
|
|
5
|
+
getNaturalGift,
|
|
6
|
+
getFlingPower,
|
|
7
|
+
getBerryResistType,
|
|
8
|
+
getTechnoBlast,
|
|
9
|
+
} from '../items';
|
|
10
|
+
import type {RawDesc} from '../desc';
|
|
11
|
+
import type {Field} from '../field';
|
|
12
|
+
import type {Move} from '../move';
|
|
13
|
+
import type {Pokemon} from '../pokemon';
|
|
14
|
+
import {Result} from '../result';
|
|
15
|
+
import {
|
|
16
|
+
chainMods,
|
|
17
|
+
checkAirLock,
|
|
18
|
+
checkDownload,
|
|
19
|
+
checkForecast,
|
|
20
|
+
checkInfiltrator,
|
|
21
|
+
checkIntimidate,
|
|
22
|
+
checkItem,
|
|
23
|
+
checkMultihitBoost,
|
|
24
|
+
checkSeedBoost,
|
|
25
|
+
checkWonderRoom,
|
|
26
|
+
computeFinalStats,
|
|
27
|
+
countBoosts,
|
|
28
|
+
getBaseDamage,
|
|
29
|
+
getStatDescriptionText,
|
|
30
|
+
getFinalDamage,
|
|
31
|
+
getModifiedStat,
|
|
32
|
+
getMoveEffectiveness,
|
|
33
|
+
getStabMod,
|
|
34
|
+
getWeight,
|
|
35
|
+
handleFixedDamageMoves,
|
|
36
|
+
isGrounded,
|
|
37
|
+
OF16, OF32,
|
|
38
|
+
pokeRound,
|
|
39
|
+
} from './util';
|
|
40
|
+
|
|
41
|
+
export function calculateBWXY(
|
|
42
|
+
gen: Generation,
|
|
43
|
+
attacker: Pokemon,
|
|
44
|
+
defender: Pokemon,
|
|
45
|
+
move: Move,
|
|
46
|
+
field: Field
|
|
47
|
+
) {
|
|
48
|
+
// #region Initial
|
|
49
|
+
|
|
50
|
+
checkAirLock(attacker, field);
|
|
51
|
+
checkAirLock(defender, field);
|
|
52
|
+
checkForecast(attacker, field.weather);
|
|
53
|
+
checkForecast(defender, field.weather);
|
|
54
|
+
checkItem(attacker, field.isMagicRoom);
|
|
55
|
+
checkItem(defender, field.isMagicRoom);
|
|
56
|
+
checkWonderRoom(attacker, field.isWonderRoom);
|
|
57
|
+
checkWonderRoom(defender, field.isWonderRoom);
|
|
58
|
+
checkSeedBoost(attacker, field);
|
|
59
|
+
checkSeedBoost(defender, field);
|
|
60
|
+
|
|
61
|
+
computeFinalStats(gen, attacker, defender, field, 'def', 'spd', 'spe');
|
|
62
|
+
|
|
63
|
+
checkIntimidate(gen, attacker, defender);
|
|
64
|
+
checkIntimidate(gen, defender, attacker);
|
|
65
|
+
checkDownload(attacker, defender, field.isWonderRoom);
|
|
66
|
+
checkDownload(defender, attacker, field.isWonderRoom);
|
|
67
|
+
|
|
68
|
+
computeFinalStats(gen, attacker, defender, field, 'atk', 'spa');
|
|
69
|
+
|
|
70
|
+
checkInfiltrator(attacker, field.defenderSide);
|
|
71
|
+
checkInfiltrator(defender, field.attackerSide);
|
|
72
|
+
|
|
73
|
+
const desc: RawDesc = {
|
|
74
|
+
attackerName: attacker.name,
|
|
75
|
+
moveName: move.name,
|
|
76
|
+
defenderName: defender.name,
|
|
77
|
+
isWonderRoom: field.isWonderRoom,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const result = new Result(gen, attacker, defender, move, field, 0, desc);
|
|
81
|
+
|
|
82
|
+
if (move.category === 'Status' && !move.named('Nature Power')) {
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (field.defenderSide.isProtected && !move.breaksProtect) {
|
|
87
|
+
desc.isProtected = true;
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (move.name === 'Pain Split') {
|
|
92
|
+
const average = Math.floor((attacker.curHP() + defender.curHP()) / 2);
|
|
93
|
+
const damage = Math.max(0, defender.curHP() - average);
|
|
94
|
+
result.damage = damage;
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const defenderAbilityIgnored = defender.hasAbility(
|
|
99
|
+
'Aroma Veil', 'Aura Break', 'Battle Armor', 'Big Pecks',
|
|
100
|
+
'Bulletproof', 'Clear Body', 'Contrary', 'Damp',
|
|
101
|
+
'Dark Aura', 'Dry Skin', 'Fairy Aura', 'Filter',
|
|
102
|
+
'Flash Fire', 'Flower Gift', 'Flower Veil', 'Friend Guard',
|
|
103
|
+
'Fur Coat', 'Grass Pelt', 'Heatproof', 'Heavy Metal',
|
|
104
|
+
'Hyper Cutter', 'Immunity', 'Inner Focus', 'Insomnia',
|
|
105
|
+
'Keen Eye', 'Leaf Guard', 'Levitate', 'Light Metal',
|
|
106
|
+
'Lightning Rod', 'Limber', 'Magic Bounce', 'Magma Armor',
|
|
107
|
+
'Marvel Scale', 'Motor Drive', 'Multiscale', 'Oblivious',
|
|
108
|
+
'Overcoat', 'Own Tempo', 'Sand Veil', 'Sap Sipper',
|
|
109
|
+
'Shell Armor', 'Shield Dust', 'Simple', 'Snow Cloak',
|
|
110
|
+
'Solid Rock', 'Soundproof', 'Sticky Hold', 'Storm Drain',
|
|
111
|
+
'Sturdy', 'Suction Cups', 'Sweet Veil', 'Tangled Feet',
|
|
112
|
+
'Telepathy', 'Thick Fat', 'Unaware', 'Vital Spirit',
|
|
113
|
+
'Volt Absorb', 'Water Absorb', 'Water Veil', 'White Smoke',
|
|
114
|
+
'Wonder Guard', 'Wonder Skin'
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
if (attacker.hasAbility('Mold Breaker', 'Teravolt', 'Turboblaze') && defenderAbilityIgnored) {
|
|
118
|
+
defender.ability = '' as AbilityName;
|
|
119
|
+
desc.attackerAbility = attacker.ability;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const isCritical =
|
|
123
|
+
move.isCrit && !defender.hasAbility('Battle Armor', 'Shell Armor') && move.timesUsed === 1;
|
|
124
|
+
|
|
125
|
+
if (move.named('Weather Ball')) {
|
|
126
|
+
move.type =
|
|
127
|
+
field.hasWeather('Sun', 'Harsh Sunshine') ? 'Fire'
|
|
128
|
+
: field.hasWeather('Rain', 'Heavy Rain') ? 'Water'
|
|
129
|
+
: field.hasWeather('Sand') ? 'Rock'
|
|
130
|
+
: field.hasWeather('Hail') ? 'Ice'
|
|
131
|
+
: 'Normal';
|
|
132
|
+
desc.weather = field.weather;
|
|
133
|
+
desc.moveType = move.type;
|
|
134
|
+
} else if (move.named('Judgment') && attacker.item && attacker.item.includes('Plate')) {
|
|
135
|
+
move.type = getItemBoostType(attacker.item)!;
|
|
136
|
+
} else if (move.named('Techno Blast') && attacker.item && attacker.item.includes('Drive')) {
|
|
137
|
+
move.type = getTechnoBlast(attacker.item)!;
|
|
138
|
+
} else if (move.named('Natural Gift') && attacker.item?.endsWith('Berry')) {
|
|
139
|
+
const gift = getNaturalGift(gen, attacker.item)!;
|
|
140
|
+
move.type = gift.t;
|
|
141
|
+
move.bp = gift.p;
|
|
142
|
+
desc.attackerItem = attacker.item;
|
|
143
|
+
desc.moveBP = move.bp;
|
|
144
|
+
desc.moveType = move.type;
|
|
145
|
+
} else if (move.named('Nature Power')) {
|
|
146
|
+
if (gen.num === 5) {
|
|
147
|
+
move.type = 'Ground';
|
|
148
|
+
} else {
|
|
149
|
+
move.type =
|
|
150
|
+
field.hasTerrain('Electric') ? 'Electric'
|
|
151
|
+
: field.hasTerrain('Grassy') ? 'Grass'
|
|
152
|
+
: field.hasTerrain('Misty') ? 'Fairy'
|
|
153
|
+
: 'Normal';
|
|
154
|
+
}
|
|
155
|
+
} else if (move.named('Brick Break')) {
|
|
156
|
+
field.defenderSide.isReflect = false;
|
|
157
|
+
field.defenderSide.isLightScreen = false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let hasAteAbilityTypeChange = false;
|
|
161
|
+
let isAerilate = false;
|
|
162
|
+
let isPixilate = false;
|
|
163
|
+
let isRefrigerate = false;
|
|
164
|
+
let isNormalize = false;
|
|
165
|
+
const noTypeChange =
|
|
166
|
+
move.named('Judgment', 'Nature Power', 'Techo Blast', 'Natural Gift', 'Weather Ball',
|
|
167
|
+
'Struggle');
|
|
168
|
+
|
|
169
|
+
if (!move.isZ && !noTypeChange) {
|
|
170
|
+
const normal = move.hasType('Normal');
|
|
171
|
+
if ((isAerilate = attacker.hasAbility('Aerilate') && normal)) {
|
|
172
|
+
move.type = 'Flying';
|
|
173
|
+
} else if ((isPixilate = attacker.hasAbility('Pixilate') && normal)) {
|
|
174
|
+
move.type = 'Fairy';
|
|
175
|
+
} else if ((isRefrigerate = attacker.hasAbility('Refrigerate') && normal)) {
|
|
176
|
+
move.type = 'Ice';
|
|
177
|
+
} else if ((isNormalize = attacker.hasAbility('Normalize'))) {
|
|
178
|
+
move.type = 'Normal';
|
|
179
|
+
}
|
|
180
|
+
if (isPixilate || isRefrigerate || isAerilate || isNormalize) {
|
|
181
|
+
desc.attackerAbility = attacker.ability;
|
|
182
|
+
}
|
|
183
|
+
if (isPixilate || isRefrigerate || isAerilate) {
|
|
184
|
+
hasAteAbilityTypeChange = true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (attacker.hasAbility('Gale Wings') && move.hasType('Flying')) {
|
|
189
|
+
move.priority = 1;
|
|
190
|
+
desc.attackerAbility = attacker.ability;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const isGhostRevealed = attacker.hasAbility('Scrappy') || field.defenderSide.isForesight;
|
|
194
|
+
const isRingTarget = defender.hasItem('Ring Target') && !defender.hasAbility('Klutz');
|
|
195
|
+
const type1Effectiveness =
|
|
196
|
+
getMoveEffectiveness(gen, move, defender.types[0], isGhostRevealed, field.isGravity,
|
|
197
|
+
isRingTarget);
|
|
198
|
+
const type2Effectiveness = defender.types[1]
|
|
199
|
+
? getMoveEffectiveness(gen, move, defender.types[1], isGhostRevealed, field.isGravity,
|
|
200
|
+
isRingTarget)
|
|
201
|
+
: 1;
|
|
202
|
+
let typeEffectiveness = type1Effectiveness * type2Effectiveness;
|
|
203
|
+
|
|
204
|
+
if (typeEffectiveness === 0 && move.named('Thousand Arrows')) {
|
|
205
|
+
typeEffectiveness = 1;
|
|
206
|
+
} else if (typeEffectiveness === 0 && move.hasType('Ground') &&
|
|
207
|
+
defender.hasItem('Iron Ball') && !defender.hasAbility('Klutz')) {
|
|
208
|
+
typeEffectiveness = 1;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (typeEffectiveness === 0) {
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if ((move.named('Sky Drop') &&
|
|
216
|
+
(defender.hasType('Flying') || defender.weightkg >= 200 || field.isGravity)) ||
|
|
217
|
+
(move.named('Synchronoise') && !defender.hasType(attacker.types[0]) &&
|
|
218
|
+
(!attacker.types[1] || !defender.hasType(attacker.types[1]))) ||
|
|
219
|
+
(move.named('Dream Eater') && !defender.hasStatus('slp'))
|
|
220
|
+
) {
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (
|
|
225
|
+
(field.hasWeather('Harsh Sunshine') && move.hasType('Water')) ||
|
|
226
|
+
(field.hasWeather('Heavy Rain') && move.hasType('Fire'))
|
|
227
|
+
) {
|
|
228
|
+
desc.weather = field.weather;
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (field.hasWeather('Strong Winds') && defender.hasType('Flying') &&
|
|
233
|
+
gen.types.get(toID(move.type))!.effectiveness['Flying']! > 1) {
|
|
234
|
+
typeEffectiveness /= 2;
|
|
235
|
+
desc.weather = field.weather;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if ((defender.hasAbility('Wonder Guard') && typeEffectiveness <= 1) ||
|
|
239
|
+
(move.hasType('Grass') && defender.hasAbility('Sap Sipper')) ||
|
|
240
|
+
(move.hasType('Fire') && defender.hasAbility('Flash Fire')) ||
|
|
241
|
+
(move.hasType('Water') && defender.hasAbility('Dry Skin', 'Storm Drain', 'Water Absorb')) ||
|
|
242
|
+
(move.hasType('Electric') &&
|
|
243
|
+
defender.hasAbility('Lightning Rod', 'Motor Drive', 'Volt Absorb')) ||
|
|
244
|
+
(move.hasType('Ground') &&
|
|
245
|
+
!field.isGravity && !move.named('Thousand Arrows') &&
|
|
246
|
+
!defender.hasItem('Iron Ball') && defender.hasAbility('Levitate')) ||
|
|
247
|
+
(move.flags.bullet && defender.hasAbility('Bulletproof')) ||
|
|
248
|
+
(move.flags.sound && defender.hasAbility('Soundproof'))
|
|
249
|
+
) {
|
|
250
|
+
desc.defenderAbility = defender.ability;
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (move.hasType('Ground') && !move.named('Thousand Arrows') &&
|
|
255
|
+
!field.isGravity && defender.hasItem('Air Balloon')) {
|
|
256
|
+
desc.defenderItem = defender.item;
|
|
257
|
+
return result;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (move.priority > 0 && field.hasTerrain('Psychic') && isGrounded(defender, field)) {
|
|
261
|
+
desc.terrain = field.terrain;
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
desc.HPEVs = getStatDescriptionText(gen, defender, 'hp');
|
|
266
|
+
|
|
267
|
+
const fixedDamage = handleFixedDamageMoves(attacker, move);
|
|
268
|
+
if (fixedDamage) {
|
|
269
|
+
if (attacker.hasAbility('Parental Bond')) {
|
|
270
|
+
result.damage = [fixedDamage, fixedDamage];
|
|
271
|
+
desc.attackerAbility = attacker.ability;
|
|
272
|
+
} else {
|
|
273
|
+
result.damage = fixedDamage;
|
|
274
|
+
}
|
|
275
|
+
return result;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (move.named('Final Gambit')) {
|
|
279
|
+
result.damage = attacker.curHP();
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (move.hits > 1) {
|
|
284
|
+
desc.hits = move.hits;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// #endregion
|
|
288
|
+
// #region Base Power
|
|
289
|
+
|
|
290
|
+
const basePower = calculateBasePowerBWXY(
|
|
291
|
+
gen,
|
|
292
|
+
attacker,
|
|
293
|
+
defender,
|
|
294
|
+
move,
|
|
295
|
+
field,
|
|
296
|
+
hasAteAbilityTypeChange,
|
|
297
|
+
desc
|
|
298
|
+
);
|
|
299
|
+
if (basePower === 0) {
|
|
300
|
+
return result;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// #endregion
|
|
304
|
+
// #region (Special) Attack
|
|
305
|
+
|
|
306
|
+
const attack = calculateAttackBWXY(gen, attacker, defender, move, field, desc, isCritical);
|
|
307
|
+
const attackStat = move.category === 'Special' ? 'spa' : 'atk';
|
|
308
|
+
|
|
309
|
+
// #endregion
|
|
310
|
+
|
|
311
|
+
// #region (Special) Defense
|
|
312
|
+
|
|
313
|
+
const defense = calculateDefenseBWXY(gen, attacker, defender, move, field, desc, isCritical);
|
|
314
|
+
|
|
315
|
+
// #endregion
|
|
316
|
+
// #region Damage
|
|
317
|
+
|
|
318
|
+
const baseDamage = calculateBaseDamageBWXY(
|
|
319
|
+
gen,
|
|
320
|
+
attacker,
|
|
321
|
+
basePower,
|
|
322
|
+
attack,
|
|
323
|
+
defense,
|
|
324
|
+
move,
|
|
325
|
+
field,
|
|
326
|
+
desc,
|
|
327
|
+
isCritical
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
// the random factor is applied between the crit mod and the stab mod, so don't apply anything
|
|
331
|
+
// below this until we're inside the loop
|
|
332
|
+
let stabMod = getStabMod(attacker, move, desc);
|
|
333
|
+
|
|
334
|
+
const applyBurn =
|
|
335
|
+
attacker.hasStatus('brn') &&
|
|
336
|
+
move.category === 'Physical' &&
|
|
337
|
+
!attacker.hasAbility('Guts') &&
|
|
338
|
+
!(move.named('Facade') && gen.num === 6);
|
|
339
|
+
desc.isBurned = applyBurn;
|
|
340
|
+
|
|
341
|
+
const finalMods = calculateFinalModsBWXY(
|
|
342
|
+
gen,
|
|
343
|
+
attacker,
|
|
344
|
+
defender,
|
|
345
|
+
move,
|
|
346
|
+
field,
|
|
347
|
+
desc,
|
|
348
|
+
isCritical,
|
|
349
|
+
typeEffectiveness
|
|
350
|
+
);
|
|
351
|
+
const finalMod = chainMods(finalMods, 41, 131072);
|
|
352
|
+
|
|
353
|
+
const isSpread = field.gameType !== 'Singles' &&
|
|
354
|
+
['allAdjacent', 'allAdjacentFoes'].includes(move.target);
|
|
355
|
+
|
|
356
|
+
let childDamage: number[] | undefined;
|
|
357
|
+
if (attacker.hasAbility('Parental Bond') && move.hits === 1 && !isSpread) {
|
|
358
|
+
const child = attacker.clone();
|
|
359
|
+
child.ability = 'Parental Bond (Child)' as AbilityName;
|
|
360
|
+
checkMultihitBoost(gen, child, defender, move, field, desc);
|
|
361
|
+
childDamage = calculateBWXY(gen, child, defender, move, field).damage as number[];
|
|
362
|
+
desc.attackerAbility = attacker.ability;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
let damage: number[] = [];
|
|
366
|
+
for (let i = 0; i < 16; i++) {
|
|
367
|
+
damage[i] =
|
|
368
|
+
getFinalDamage(baseDamage, i, typeEffectiveness, applyBurn, stabMod, finalMod);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
desc.attackBoost =
|
|
372
|
+
move.named('Foul Play') ? defender.boosts[attackStat] : attacker.boosts[attackStat];
|
|
373
|
+
|
|
374
|
+
if ((move.dropsStats && move.timesUsed! > 1) || move.hits > 1) {
|
|
375
|
+
// store boosts so intermediate boosts don't show.
|
|
376
|
+
const origDefBoost = desc.defenseBoost;
|
|
377
|
+
const origAtkBoost = desc.attackBoost;
|
|
378
|
+
let numAttacks = 1;
|
|
379
|
+
if (move.dropsStats && move.timesUsed! > 1) {
|
|
380
|
+
desc.moveTurns = `over ${move.timesUsed} turns`;
|
|
381
|
+
numAttacks = move.timesUsed!;
|
|
382
|
+
} else {
|
|
383
|
+
numAttacks = move.hits;
|
|
384
|
+
}
|
|
385
|
+
let usedItems = [false, false];
|
|
386
|
+
for (let times = 1; times < numAttacks; times++) {
|
|
387
|
+
usedItems = checkMultihitBoost(gen, attacker, defender, move,
|
|
388
|
+
field, desc, usedItems[0], usedItems[1]);
|
|
389
|
+
const newAtk = calculateAttackBWXY(gen, attacker, defender, move, field, desc, isCritical);
|
|
390
|
+
const newDef = calculateDefenseBWXY(gen, attacker, defender, move, field, desc, isCritical);
|
|
391
|
+
// Check if lost -ate ability. Typing stays the same, only boost is lost
|
|
392
|
+
// Cannot be regained during multihit move and no Normal moves with stat drawbacks
|
|
393
|
+
hasAteAbilityTypeChange = hasAteAbilityTypeChange &&
|
|
394
|
+
attacker.hasAbility('Aerilate', 'Galvanize', 'Pixilate', 'Refrigerate');
|
|
395
|
+
|
|
396
|
+
if ((move.dropsStats && move.timesUsed! > 1)) {
|
|
397
|
+
// Adaptability does not change between hits of a multihit, only between turns
|
|
398
|
+
stabMod = getStabMod(attacker, move, desc);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const newBasePower = calculateBasePowerBWXY(
|
|
402
|
+
gen,
|
|
403
|
+
attacker,
|
|
404
|
+
defender,
|
|
405
|
+
move,
|
|
406
|
+
field,
|
|
407
|
+
hasAteAbilityTypeChange,
|
|
408
|
+
desc
|
|
409
|
+
);
|
|
410
|
+
const newBaseDamage = getBaseDamage(attacker.level, newBasePower, newAtk, newDef);
|
|
411
|
+
const newFinalMods = calculateFinalModsBWXY(
|
|
412
|
+
gen,
|
|
413
|
+
attacker,
|
|
414
|
+
defender,
|
|
415
|
+
move,
|
|
416
|
+
field,
|
|
417
|
+
desc,
|
|
418
|
+
isCritical,
|
|
419
|
+
typeEffectiveness,
|
|
420
|
+
times
|
|
421
|
+
);
|
|
422
|
+
const newFinalMod = chainMods(newFinalMods, 41, 131072);
|
|
423
|
+
|
|
424
|
+
let damageMultiplier = 0;
|
|
425
|
+
damage = damage.map(affectedAmount => {
|
|
426
|
+
const newFinalDamage = getFinalDamage(
|
|
427
|
+
newBaseDamage,
|
|
428
|
+
damageMultiplier,
|
|
429
|
+
typeEffectiveness,
|
|
430
|
+
applyBurn,
|
|
431
|
+
stabMod,
|
|
432
|
+
newFinalMod
|
|
433
|
+
);
|
|
434
|
+
damageMultiplier++;
|
|
435
|
+
return affectedAmount + newFinalDamage;
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
desc.defenseBoost = origDefBoost;
|
|
439
|
+
desc.attackBoost = origAtkBoost;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
result.damage = childDamage ? [damage, childDamage] : damage;
|
|
443
|
+
|
|
444
|
+
// #endregion
|
|
445
|
+
|
|
446
|
+
return result;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export function calculateBasePowerBWXY(
|
|
450
|
+
gen: Generation,
|
|
451
|
+
attacker: Pokemon,
|
|
452
|
+
defender: Pokemon,
|
|
453
|
+
move: Move,
|
|
454
|
+
field: Field,
|
|
455
|
+
hasAteAbilityTypeChange: boolean,
|
|
456
|
+
desc: RawDesc,
|
|
457
|
+
hit = 1,
|
|
458
|
+
) {
|
|
459
|
+
let basePower: number;
|
|
460
|
+
const turnOrder = attacker.stats.spe > defender.stats.spe ? 'first' : 'last';
|
|
461
|
+
|
|
462
|
+
switch (move.name) {
|
|
463
|
+
case 'Payback':
|
|
464
|
+
basePower = move.bp * (turnOrder === 'last' ? 2 : 1);
|
|
465
|
+
desc.moveBP = basePower;
|
|
466
|
+
break;
|
|
467
|
+
case 'Pursuit':
|
|
468
|
+
const switching = field.defenderSide.isSwitching === 'out';
|
|
469
|
+
basePower = move.bp * (switching ? 2 : 1);
|
|
470
|
+
if (switching) desc.isSwitching = 'out';
|
|
471
|
+
desc.moveBP = basePower;
|
|
472
|
+
break;
|
|
473
|
+
case 'Electro Ball':
|
|
474
|
+
if (defender.stats.spe === 0) defender.stats.spe = 1;
|
|
475
|
+
const r = Math.floor(attacker.stats.spe / defender.stats.spe);
|
|
476
|
+
basePower = r >= 4 ? 150 : r >= 3 ? 120 : r >= 2 ? 80 : r >= 1 ? 60 : 40;
|
|
477
|
+
desc.moveBP = basePower;
|
|
478
|
+
break;
|
|
479
|
+
case 'Gyro Ball':
|
|
480
|
+
if (attacker.stats.spe === 0) attacker.stats.spe = 1;
|
|
481
|
+
basePower = Math.min(150, Math.floor((25 * defender.stats.spe) / attacker.stats.spe) + 1);
|
|
482
|
+
desc.moveBP = basePower;
|
|
483
|
+
break;
|
|
484
|
+
case 'Punishment':
|
|
485
|
+
basePower = Math.min(200, 60 + 20 * countBoosts(gen, defender.boosts));
|
|
486
|
+
desc.moveBP = basePower;
|
|
487
|
+
break;
|
|
488
|
+
case 'Low Kick':
|
|
489
|
+
case 'Grass Knot':
|
|
490
|
+
const w = getWeight(defender, desc, 'defender');
|
|
491
|
+
basePower = w >= 200 ? 120 : w >= 100 ? 100 : w >= 50 ? 80 : w >= 25 ? 60 : w >= 10 ? 40 : 20;
|
|
492
|
+
desc.moveBP = basePower;
|
|
493
|
+
break;
|
|
494
|
+
case 'Hex':
|
|
495
|
+
basePower = move.bp * (defender.status ? 2 : 1);
|
|
496
|
+
desc.moveBP = basePower;
|
|
497
|
+
break;
|
|
498
|
+
case 'Heavy Slam':
|
|
499
|
+
case 'Heat Crash':
|
|
500
|
+
const wr =
|
|
501
|
+
getWeight(attacker, desc, 'attacker') /
|
|
502
|
+
getWeight(defender, desc, 'defender');
|
|
503
|
+
basePower = wr >= 5 ? 120 : wr >= 4 ? 100 : wr >= 3 ? 80 : wr >= 2 ? 60 : 40;
|
|
504
|
+
desc.moveBP = basePower;
|
|
505
|
+
break;
|
|
506
|
+
case 'Stored Power':
|
|
507
|
+
case 'Power Trip':
|
|
508
|
+
basePower = 20 + 20 * countBoosts(gen, attacker.boosts);
|
|
509
|
+
desc.moveBP = basePower;
|
|
510
|
+
break;
|
|
511
|
+
case 'Acrobatics':
|
|
512
|
+
basePower = move.bp * (attacker.hasItem('Flying Gem') || !attacker.item ? 2 : 1);
|
|
513
|
+
desc.moveBP = basePower;
|
|
514
|
+
break;
|
|
515
|
+
case 'Assurance':
|
|
516
|
+
basePower = move.bp * (defender.hasAbility('Parental Bond (Child)') ? 2 : 1);
|
|
517
|
+
// NOTE: desc.attackerAbility = 'Parental Bond' will already reflect this boost
|
|
518
|
+
break;
|
|
519
|
+
case 'Wake-Up Slap':
|
|
520
|
+
basePower = move.bp * (defender.hasStatus('slp') ? 2 : 1);
|
|
521
|
+
desc.moveBP = basePower;
|
|
522
|
+
break;
|
|
523
|
+
case 'Smelling Salts':
|
|
524
|
+
basePower = move.bp * (defender.hasStatus('par') ? 2 : 1);
|
|
525
|
+
desc.moveBP = basePower;
|
|
526
|
+
break;
|
|
527
|
+
case 'Weather Ball':
|
|
528
|
+
basePower = move.bp * (field.weather && !field.hasWeather('Strong Winds') ? 2 : 1);
|
|
529
|
+
desc.moveBP = basePower;
|
|
530
|
+
break;
|
|
531
|
+
case 'Fling':
|
|
532
|
+
basePower = getFlingPower(attacker.item);
|
|
533
|
+
desc.moveBP = basePower;
|
|
534
|
+
desc.attackerItem = attacker.item;
|
|
535
|
+
break;
|
|
536
|
+
case 'Eruption':
|
|
537
|
+
case 'Water Spout':
|
|
538
|
+
basePower = Math.max(1, Math.floor((150 * attacker.curHP()) / attacker.maxHP()));
|
|
539
|
+
desc.moveBP = basePower;
|
|
540
|
+
break;
|
|
541
|
+
case 'Flail':
|
|
542
|
+
case 'Reversal':
|
|
543
|
+
const p = Math.floor((48 * attacker.curHP()) / attacker.maxHP());
|
|
544
|
+
basePower = p <= 1 ? 200 : p <= 4 ? 150 : p <= 9 ? 100 : p <= 16 ? 80 : p <= 32 ? 40 : 20;
|
|
545
|
+
desc.moveBP = basePower;
|
|
546
|
+
break;
|
|
547
|
+
case 'Nature Power':
|
|
548
|
+
if (gen.num === 5) {
|
|
549
|
+
move.category = 'Physical';
|
|
550
|
+
move.target = 'allAdjacent';
|
|
551
|
+
basePower = 100;
|
|
552
|
+
desc.moveName = 'Earthquake';
|
|
553
|
+
} else {
|
|
554
|
+
move.category = 'Special';
|
|
555
|
+
move.secondaries = true;
|
|
556
|
+
switch (field.terrain) {
|
|
557
|
+
case 'Electric':
|
|
558
|
+
basePower = 90;
|
|
559
|
+
desc.moveName = 'Thunderbolt';
|
|
560
|
+
break;
|
|
561
|
+
case 'Grassy':
|
|
562
|
+
basePower = 90;
|
|
563
|
+
desc.moveName = 'Energy Ball';
|
|
564
|
+
break;
|
|
565
|
+
case 'Misty':
|
|
566
|
+
basePower = 95;
|
|
567
|
+
desc.moveName = 'Moonblast';
|
|
568
|
+
break;
|
|
569
|
+
default:
|
|
570
|
+
basePower = 80;
|
|
571
|
+
desc.moveName = 'Tri Attack';
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
break;
|
|
575
|
+
// Triple Kick's damage increases after each consecutive hit (10, 20, 30)
|
|
576
|
+
case 'Triple Kick':
|
|
577
|
+
basePower = hit * 10;
|
|
578
|
+
desc.moveBP = move.hits === 2 ? 30 : move.hits === 3 ? 60 : 10;
|
|
579
|
+
break;
|
|
580
|
+
case 'Crush Grip':
|
|
581
|
+
case 'Wring Out':
|
|
582
|
+
basePower = 100 * Math.floor((defender.curHP() * 4096) / defender.maxHP());
|
|
583
|
+
basePower = Math.floor(Math.floor((120 * basePower + 2048 - 1) / 4096) / 100) || 1;
|
|
584
|
+
desc.moveBP = basePower;
|
|
585
|
+
break;
|
|
586
|
+
default:
|
|
587
|
+
basePower = move.bp;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (basePower === 0) {
|
|
591
|
+
return 0;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const bpMods = calculateBPModsBWXY(
|
|
595
|
+
gen,
|
|
596
|
+
attacker,
|
|
597
|
+
defender,
|
|
598
|
+
move,
|
|
599
|
+
field,
|
|
600
|
+
desc,
|
|
601
|
+
basePower,
|
|
602
|
+
hasAteAbilityTypeChange,
|
|
603
|
+
turnOrder
|
|
604
|
+
);
|
|
605
|
+
|
|
606
|
+
basePower = OF16(Math.max(1, pokeRound((basePower * chainMods(bpMods, 41, 2097152)) / 4096)));
|
|
607
|
+
return basePower;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
export function calculateBPModsBWXY(
|
|
611
|
+
gen: Generation,
|
|
612
|
+
attacker: Pokemon,
|
|
613
|
+
defender: Pokemon,
|
|
614
|
+
move: Move,
|
|
615
|
+
field: Field,
|
|
616
|
+
desc: RawDesc,
|
|
617
|
+
basePower: number,
|
|
618
|
+
hasAteAbilityTypeChange: boolean,
|
|
619
|
+
turnOrder: string
|
|
620
|
+
) {
|
|
621
|
+
const bpMods = [];
|
|
622
|
+
|
|
623
|
+
const defenderItem = (defender.item && defender.item !== '')
|
|
624
|
+
? defender.item : defender.disabledItem;
|
|
625
|
+
let resistedKnockOffDamage =
|
|
626
|
+
!defenderItem ||
|
|
627
|
+
(defender.named('Giratina-Origin') && defenderItem === 'Griseous Orb') ||
|
|
628
|
+
(defender.name.includes('Arceus') && defenderItem.includes('Plate')) ||
|
|
629
|
+
(defender.name.includes('Genesect') && defenderItem.includes('Drive')) ||
|
|
630
|
+
(defender.named('Groudon', 'Groudon-Primal') && defenderItem === 'Red Orb') ||
|
|
631
|
+
(defender.named('Kyogre', 'Kyogre-Primal') && defenderItem === 'Blue Orb');
|
|
632
|
+
|
|
633
|
+
// The last case only applies when the Pokemon is holding the Mega Stone that matches its species
|
|
634
|
+
// (or when it's already a Mega-Evolution)
|
|
635
|
+
if (!resistedKnockOffDamage && defenderItem) {
|
|
636
|
+
const item = gen.items.get(toID(defenderItem))!;
|
|
637
|
+
resistedKnockOffDamage = !!(item.megaEvolves && defender.name.includes(item.megaEvolves));
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
// Use BasePower after moves with custom BP to determine if Technician should boost
|
|
642
|
+
if ((attacker.hasAbility('Technician') && basePower <= 60) ||
|
|
643
|
+
(attacker.hasAbility('Flare Boost') &&
|
|
644
|
+
attacker.hasStatus('brn') && move.category === 'Special') ||
|
|
645
|
+
(attacker.hasAbility('Toxic Boost') &&
|
|
646
|
+
attacker.hasStatus('psn', 'tox') && move.category === 'Physical')
|
|
647
|
+
) {
|
|
648
|
+
bpMods.push(6144);
|
|
649
|
+
desc.attackerAbility = attacker.ability;
|
|
650
|
+
} else if (attacker.hasAbility('Analytic') && turnOrder !== 'first') {
|
|
651
|
+
bpMods.push(5325);
|
|
652
|
+
desc.attackerAbility = attacker.ability;
|
|
653
|
+
} else if (
|
|
654
|
+
attacker.hasAbility('Sand Force') &&
|
|
655
|
+
field.hasWeather('Sand') &&
|
|
656
|
+
move.hasType('Rock', 'Ground', 'Steel')
|
|
657
|
+
) {
|
|
658
|
+
bpMods.push(5325);
|
|
659
|
+
desc.attackerAbility = attacker.ability;
|
|
660
|
+
desc.weather = field.weather;
|
|
661
|
+
} else if (
|
|
662
|
+
(attacker.hasAbility('Reckless') && (move.recoil || move.hasCrashDamage)) ||
|
|
663
|
+
(attacker.hasAbility('Iron Fist') && move.flags.punch)
|
|
664
|
+
) {
|
|
665
|
+
bpMods.push(4915);
|
|
666
|
+
desc.attackerAbility = attacker.ability;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (defender.hasAbility('Heatproof') && move.hasType('Fire')) {
|
|
670
|
+
bpMods.push(2048);
|
|
671
|
+
desc.defenderAbility = defender.ability;
|
|
672
|
+
} else if (defender.hasAbility('Dry Skin') && move.hasType('Fire')) {
|
|
673
|
+
bpMods.push(5120);
|
|
674
|
+
desc.defenderAbility = defender.ability;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (attacker.hasAbility('Sheer Force') && move.secondaries) {
|
|
678
|
+
bpMods.push(5325);
|
|
679
|
+
desc.attackerAbility = attacker.ability;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (attacker.hasAbility('Rivalry') && ![attacker.gender, defender.gender].includes('N')) {
|
|
683
|
+
if (attacker.gender === defender.gender) {
|
|
684
|
+
bpMods.push(5120);
|
|
685
|
+
desc.rivalry = 'buffed';
|
|
686
|
+
} else {
|
|
687
|
+
bpMods.push(3072);
|
|
688
|
+
desc.rivalry = 'nerfed';
|
|
689
|
+
}
|
|
690
|
+
desc.attackerAbility = attacker.ability;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (attacker.item && getItemBoostType(attacker.item) === move.type) {
|
|
694
|
+
bpMods.push(4915);
|
|
695
|
+
desc.attackerItem = attacker.item;
|
|
696
|
+
} else if (
|
|
697
|
+
(attacker.hasItem('Muscle Band') && move.category === 'Physical') ||
|
|
698
|
+
(attacker.hasItem('Wise Glasses') && move.category === 'Special')
|
|
699
|
+
) {
|
|
700
|
+
bpMods.push(4505);
|
|
701
|
+
desc.attackerItem = attacker.item;
|
|
702
|
+
} else if (
|
|
703
|
+
(attacker.hasItem('Adamant Orb') &&
|
|
704
|
+
attacker.named('Dialga') &&
|
|
705
|
+
move.hasType('Steel', 'Dragon')) ||
|
|
706
|
+
(attacker.hasItem('Lustrous Orb') &&
|
|
707
|
+
attacker.named('Palkia') &&
|
|
708
|
+
move.hasType('Water', 'Dragon')) ||
|
|
709
|
+
(attacker.hasItem('Griseous Orb') &&
|
|
710
|
+
attacker.named('Giratina-Origin') &&
|
|
711
|
+
move.hasType('Ghost', 'Dragon'))
|
|
712
|
+
) {
|
|
713
|
+
bpMods.push(4915);
|
|
714
|
+
desc.attackerItem = attacker.item;
|
|
715
|
+
} else if (attacker.hasItem(`${move.type} Gem`)) {
|
|
716
|
+
bpMods.push(gen.num > 5 ? 5325 : 6144);
|
|
717
|
+
desc.attackerItem = attacker.item;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if ((move.named('Facade') && attacker.hasStatus('brn', 'par', 'psn', 'tox')) ||
|
|
721
|
+
(move.named('Brine') && defender.curHP() <= defender.maxHP() / 2) ||
|
|
722
|
+
(move.named('Venoshock') && defender.hasStatus('psn', 'tox'))) {
|
|
723
|
+
bpMods.push(8192);
|
|
724
|
+
desc.moveBP = basePower * 2;
|
|
725
|
+
} else if (gen.num > 5 && move.named('Knock Off') && !resistedKnockOffDamage) {
|
|
726
|
+
bpMods.push(6144);
|
|
727
|
+
desc.moveBP = basePower * 1.5;
|
|
728
|
+
} else if (move.named('Solar Beam') && field.hasWeather('Rain', 'Heavy Rain', 'Sand', 'Hail')) {
|
|
729
|
+
bpMods.push(2048);
|
|
730
|
+
desc.moveBP = basePower / 2;
|
|
731
|
+
desc.weather = field.weather;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if (field.attackerSide.isHelpingHand) {
|
|
735
|
+
bpMods.push(6144);
|
|
736
|
+
desc.isHelpingHand = true;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
if (hasAteAbilityTypeChange) {
|
|
740
|
+
bpMods.push(5325);
|
|
741
|
+
desc.attackerAbility = attacker.ability;
|
|
742
|
+
} else if (
|
|
743
|
+
(attacker.hasAbility('Mega Launcher') && move.flags.pulse) ||
|
|
744
|
+
(attacker.hasAbility('Strong Jaw') && move.flags.bite)
|
|
745
|
+
) {
|
|
746
|
+
bpMods.push(6144);
|
|
747
|
+
desc.attackerAbility = attacker.ability;
|
|
748
|
+
} else if (attacker.hasAbility('Tough Claws') && move.flags.contact) {
|
|
749
|
+
bpMods.push(5325);
|
|
750
|
+
desc.attackerAbility = attacker.ability;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const aura = `${move.type} Aura`;
|
|
754
|
+
const isAttackerAura = attacker.hasAbility(aura);
|
|
755
|
+
const isDefenderAura = defender.hasAbility(aura);
|
|
756
|
+
const isUserAuraBreak = attacker.hasAbility('Aura Break') || defender.hasAbility('Aura Break');
|
|
757
|
+
const isFieldAuraBreak = field.isAuraBreak;
|
|
758
|
+
const isFieldFairyAura = field.isFairyAura && move.type === 'Fairy';
|
|
759
|
+
const isFieldDarkAura = field.isDarkAura && move.type === 'Dark';
|
|
760
|
+
const auraActive = isAttackerAura || isDefenderAura || isFieldFairyAura || isFieldDarkAura;
|
|
761
|
+
const auraBreak = isFieldAuraBreak || isUserAuraBreak;
|
|
762
|
+
if (auraActive) {
|
|
763
|
+
if (auraBreak) {
|
|
764
|
+
bpMods.push(3072);
|
|
765
|
+
desc.attackerAbility = attacker.ability;
|
|
766
|
+
desc.defenderAbility = defender.ability;
|
|
767
|
+
} else {
|
|
768
|
+
bpMods.push(5448);
|
|
769
|
+
if (isAttackerAura) desc.attackerAbility = attacker.ability;
|
|
770
|
+
if (isDefenderAura) desc.defenderAbility = defender.ability;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// It's not actually clear if the terrain modifiers are base damage mods like weather or are
|
|
775
|
+
// base power mods like in Gen 7+, but the research doesn't exist for this yet so we match PS here
|
|
776
|
+
if (isGrounded(attacker, field)) {
|
|
777
|
+
if ((field.hasTerrain('Electric') && move.hasType('Electric')) ||
|
|
778
|
+
(field.hasTerrain('Grassy') && move.hasType('Grass'))
|
|
779
|
+
) {
|
|
780
|
+
bpMods.push(6144);
|
|
781
|
+
desc.terrain = field.terrain;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
if (isGrounded(defender, field)) {
|
|
785
|
+
if ((field.hasTerrain('Misty') && move.hasType('Dragon')) ||
|
|
786
|
+
(field.hasTerrain('Grassy') && move.named('Bulldoze', 'Earthquake'))
|
|
787
|
+
) {
|
|
788
|
+
bpMods.push(2048);
|
|
789
|
+
desc.terrain = field.terrain;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
return bpMods;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
export function calculateAttackBWXY(
|
|
797
|
+
gen: Generation,
|
|
798
|
+
attacker: Pokemon,
|
|
799
|
+
defender: Pokemon,
|
|
800
|
+
move: Move,
|
|
801
|
+
field: Field,
|
|
802
|
+
desc: RawDesc,
|
|
803
|
+
isCritical = false
|
|
804
|
+
) {
|
|
805
|
+
let attack: number;
|
|
806
|
+
const attackSource = move.named('Foul Play') ? defender : attacker;
|
|
807
|
+
const attackStat = move.category === 'Special' ? 'spa' : 'atk';
|
|
808
|
+
desc.attackEVs =
|
|
809
|
+
move.named('Foul Play')
|
|
810
|
+
? getStatDescriptionText(gen, defender, attackStat, defender.nature)
|
|
811
|
+
: getStatDescriptionText(gen, attacker, attackStat, attacker.nature);
|
|
812
|
+
|
|
813
|
+
if (attackSource.boosts[attackStat] === 0 ||
|
|
814
|
+
(isCritical && attackSource.boosts[attackStat] < 0)) {
|
|
815
|
+
attack = attackSource.rawStats[attackStat];
|
|
816
|
+
} else if (defender.hasAbility('Unaware')) {
|
|
817
|
+
attack = attackSource.rawStats[attackStat];
|
|
818
|
+
desc.defenderAbility = defender.ability;
|
|
819
|
+
} else {
|
|
820
|
+
attack = getModifiedStat(attackSource.rawStats[attackStat]!, attackSource.boosts[attackStat]!);
|
|
821
|
+
desc.attackBoost = attackSource.boosts[attackStat];
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// unlike all other attack modifiers, Hustle gets applied directly
|
|
825
|
+
if (attacker.hasAbility('Hustle') && move.category === 'Physical') {
|
|
826
|
+
attack = pokeRound((attack * 3) / 2);
|
|
827
|
+
desc.attackerAbility = attacker.ability;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const atMods = calculateAtModsBWXY(attacker, defender, move, field, desc);
|
|
831
|
+
attack = OF16(Math.max(1, pokeRound((attack * chainMods(atMods, 410, 131072)) / 4096)));
|
|
832
|
+
return attack;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
export function calculateAtModsBWXY(
|
|
836
|
+
attacker: Pokemon,
|
|
837
|
+
defender: Pokemon,
|
|
838
|
+
move: Move,
|
|
839
|
+
field: Field,
|
|
840
|
+
desc: RawDesc
|
|
841
|
+
) {
|
|
842
|
+
const atMods = [];
|
|
843
|
+
if (defender.hasAbility('Thick Fat') && move.hasType('Fire', 'Ice')) {
|
|
844
|
+
atMods.push(2048);
|
|
845
|
+
desc.defenderAbility = defender.ability;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
if ((attacker.hasAbility('Guts') && attacker.status && move.category === 'Physical') ||
|
|
849
|
+
(attacker.curHP() <= attacker.maxHP() / 3 &&
|
|
850
|
+
((attacker.hasAbility('Overgrow') && move.hasType('Grass')) ||
|
|
851
|
+
(attacker.hasAbility('Blaze') && move.hasType('Fire')) ||
|
|
852
|
+
(attacker.hasAbility('Torrent') && move.hasType('Water')) ||
|
|
853
|
+
(attacker.hasAbility('Swarm') && move.hasType('Bug')))) ||
|
|
854
|
+
(move.category === 'Special' && attacker.abilityOn && attacker.hasAbility('Plus', 'Minus'))
|
|
855
|
+
) {
|
|
856
|
+
atMods.push(6144);
|
|
857
|
+
desc.attackerAbility = attacker.ability;
|
|
858
|
+
} else if (attacker.hasAbility('Flash Fire') && attacker.abilityOn && move.hasType('Fire')) {
|
|
859
|
+
atMods.push(6144);
|
|
860
|
+
desc.attackerAbility = 'Flash Fire';
|
|
861
|
+
} else if (
|
|
862
|
+
(attacker.hasAbility('Solar Power') &&
|
|
863
|
+
field.hasWeather('Sun', 'Harsh Sunshine') &&
|
|
864
|
+
move.category === 'Special') ||
|
|
865
|
+
(attacker.named('Cherrim') &&
|
|
866
|
+
attacker.hasAbility('Flower Gift') &&
|
|
867
|
+
field.hasWeather('Sun', 'Harsh Sunshine') &&
|
|
868
|
+
move.category === 'Physical')
|
|
869
|
+
) {
|
|
870
|
+
atMods.push(6144);
|
|
871
|
+
desc.attackerAbility = attacker.ability;
|
|
872
|
+
desc.weather = field.weather;
|
|
873
|
+
} else if (
|
|
874
|
+
(attacker.hasAbility('Defeatist') && attacker.curHP() <= attacker.maxHP() / 2) ||
|
|
875
|
+
(attacker.hasAbility('Slow Start') && attacker.abilityOn && move.category === 'Physical')
|
|
876
|
+
) {
|
|
877
|
+
atMods.push(2048);
|
|
878
|
+
desc.attackerAbility = attacker.ability;
|
|
879
|
+
} else if (attacker.hasAbility('Huge Power', 'Pure Power') && move.category === 'Physical') {
|
|
880
|
+
atMods.push(8192);
|
|
881
|
+
desc.attackerAbility = attacker.ability;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
if (
|
|
885
|
+
field.attackerSide.isFlowerGift &&
|
|
886
|
+
!attacker.hasAbility('Flower Gift') &&
|
|
887
|
+
field.hasWeather('Sun', 'Harsh Sunshine') &&
|
|
888
|
+
move.category === 'Physical') {
|
|
889
|
+
atMods.push(6144);
|
|
890
|
+
desc.weather = field.weather;
|
|
891
|
+
desc.isFlowerGiftAttacker = true;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
if ((attacker.hasItem('Thick Club') &&
|
|
895
|
+
attacker.named('Cubone', 'Marowak', 'Marowak-Alola') &&
|
|
896
|
+
move.category === 'Physical') ||
|
|
897
|
+
(attacker.hasItem('Deep Sea Tooth') &&
|
|
898
|
+
attacker.named('Clamperl') &&
|
|
899
|
+
move.category === 'Special') ||
|
|
900
|
+
(attacker.hasItem('Light Ball') && attacker.name.startsWith('Pikachu') && !move.isZ)
|
|
901
|
+
) {
|
|
902
|
+
atMods.push(8192);
|
|
903
|
+
desc.attackerItem = attacker.item;
|
|
904
|
+
} else if (
|
|
905
|
+
(attacker.hasItem('Soul Dew') &&
|
|
906
|
+
attacker.named('Latios', 'Latias', 'Latios-Mega', 'Latias-Mega') &&
|
|
907
|
+
move.category === 'Special') ||
|
|
908
|
+
(attacker.hasItem('Choice Band') && move.category === 'Physical') ||
|
|
909
|
+
(attacker.hasItem('Choice Specs') && move.category === 'Special')
|
|
910
|
+
) {
|
|
911
|
+
atMods.push(6144);
|
|
912
|
+
desc.attackerItem = attacker.item;
|
|
913
|
+
}
|
|
914
|
+
return atMods;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
export function calculateDefenseBWXY(
|
|
918
|
+
gen: Generation,
|
|
919
|
+
attacker: Pokemon,
|
|
920
|
+
defender: Pokemon,
|
|
921
|
+
move: Move,
|
|
922
|
+
field: Field,
|
|
923
|
+
desc: RawDesc,
|
|
924
|
+
isCritical = false
|
|
925
|
+
) {
|
|
926
|
+
let defense: number;
|
|
927
|
+
const defenseStat = move.overrideDefensiveStat || move.category === 'Physical' ? 'def' : 'spd';
|
|
928
|
+
const hitsPhysical = defenseStat === 'def';
|
|
929
|
+
desc.defenseEVs = getStatDescriptionText(gen, defender, defenseStat, defender.nature);
|
|
930
|
+
if (defender.boosts[defenseStat] === 0 ||
|
|
931
|
+
(isCritical && defender.boosts[defenseStat] > 0) ||
|
|
932
|
+
move.ignoreDefensive) {
|
|
933
|
+
defense = defender.rawStats[defenseStat];
|
|
934
|
+
} else if (attacker.hasAbility('Unaware')) {
|
|
935
|
+
defense = defender.rawStats[defenseStat];
|
|
936
|
+
desc.attackerAbility = attacker.ability;
|
|
937
|
+
} else {
|
|
938
|
+
defense = getModifiedStat(defender.rawStats[defenseStat]!, defender.boosts[defenseStat]!);
|
|
939
|
+
desc.defenseBoost = defender.boosts[defenseStat];
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// unlike all other defense modifiers, Sandstorm SpD boost gets applied directly
|
|
943
|
+
if (field.hasWeather('Sand') && defender.hasType('Rock') && !hitsPhysical) {
|
|
944
|
+
defense = pokeRound((defense * 3) / 2);
|
|
945
|
+
desc.weather = field.weather;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
const dfMods = calculateDfModsBWXY(
|
|
949
|
+
gen,
|
|
950
|
+
defender,
|
|
951
|
+
field,
|
|
952
|
+
desc,
|
|
953
|
+
hitsPhysical
|
|
954
|
+
);
|
|
955
|
+
defense = OF16(Math.max(1, pokeRound((defense * chainMods(dfMods, 410, 131072)) / 4096)));
|
|
956
|
+
return defense;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
export function calculateDfModsBWXY(
|
|
960
|
+
gen: Generation,
|
|
961
|
+
defender: Pokemon,
|
|
962
|
+
field: Field,
|
|
963
|
+
desc: RawDesc,
|
|
964
|
+
hitsPhysical = false
|
|
965
|
+
) {
|
|
966
|
+
const dfMods = [];
|
|
967
|
+
if (defender.hasAbility('Marvel Scale') && defender.status && hitsPhysical) {
|
|
968
|
+
dfMods.push(6144);
|
|
969
|
+
desc.defenderAbility = defender.ability;
|
|
970
|
+
} else if (
|
|
971
|
+
defender.named('Cherrim') &&
|
|
972
|
+
defender.hasAbility('Flower Gift') &&
|
|
973
|
+
field.hasWeather('Sun', 'Harsh Sunshine') &&
|
|
974
|
+
!hitsPhysical
|
|
975
|
+
) {
|
|
976
|
+
dfMods.push(6144);
|
|
977
|
+
desc.defenderAbility = defender.ability;
|
|
978
|
+
desc.weather = field.weather;
|
|
979
|
+
} else if (
|
|
980
|
+
field.defenderSide.isFlowerGift &&
|
|
981
|
+
field.hasWeather('Sun', 'Harsh Sunshine') &&
|
|
982
|
+
!hitsPhysical) {
|
|
983
|
+
dfMods.push(6144);
|
|
984
|
+
desc.weather = field.weather;
|
|
985
|
+
desc.isFlowerGiftDefender = true;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
if (field.hasTerrain('Grassy') && defender.hasAbility('Grass Pelt') && hitsPhysical) {
|
|
989
|
+
dfMods.push(6144);
|
|
990
|
+
desc.defenderAbility = defender.ability;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
if ((!hitsPhysical && defender.hasItem('Soul Dew') &&
|
|
994
|
+
defender.named('Latios', 'Latias', 'Latios-Mega', 'Latias-Mega')) ||
|
|
995
|
+
(defender.hasItem('Eviolite') && gen.species.get(toID(defender.name))?.nfe) ||
|
|
996
|
+
(!hitsPhysical && defender.hasItem('Assault Vest'))) {
|
|
997
|
+
dfMods.push(6144);
|
|
998
|
+
desc.defenderItem = defender.item;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
if ((defender.hasItem('Metal Powder') && defender.named('Ditto') && hitsPhysical) ||
|
|
1002
|
+
(defender.hasItem('Deep Sea Scale') && defender.named('Clamperl') && !hitsPhysical)) {
|
|
1003
|
+
dfMods.push(8192);
|
|
1004
|
+
desc.defenderItem = defender.item;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
if (defender.hasAbility('Fur Coat') && hitsPhysical) {
|
|
1008
|
+
dfMods.push(8192);
|
|
1009
|
+
desc.defenderAbility = defender.ability;
|
|
1010
|
+
}
|
|
1011
|
+
return dfMods;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
function calculateBaseDamageBWXY(
|
|
1016
|
+
gen: Generation,
|
|
1017
|
+
attacker: Pokemon,
|
|
1018
|
+
basePower: number,
|
|
1019
|
+
attack: number,
|
|
1020
|
+
defense: number,
|
|
1021
|
+
move: Move,
|
|
1022
|
+
field: Field,
|
|
1023
|
+
desc: RawDesc,
|
|
1024
|
+
isCritical = false,
|
|
1025
|
+
) {
|
|
1026
|
+
let baseDamage = getBaseDamage(attacker.level, basePower, attack, defense);
|
|
1027
|
+
|
|
1028
|
+
const isSpread = field.gameType !== 'Singles' &&
|
|
1029
|
+
['allAdjacent', 'allAdjacentFoes'].includes(move.target);
|
|
1030
|
+
if (isSpread) {
|
|
1031
|
+
baseDamage = pokeRound(OF32(baseDamage * 3072) / 4096);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
if (attacker.hasAbility('Parental Bond (Child)')) {
|
|
1035
|
+
baseDamage = pokeRound(OF32(baseDamage * 2048) / 4096);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
if ((field.hasWeather('Sun', 'Harsh Sunshine') && move.hasType('Fire')) ||
|
|
1039
|
+
(field.hasWeather('Rain', 'Heavy Rain') && move.hasType('Water'))) {
|
|
1040
|
+
baseDamage = pokeRound(OF32(baseDamage * 6144) / 4096);
|
|
1041
|
+
desc.weather = field.weather;
|
|
1042
|
+
} else if (
|
|
1043
|
+
(field.hasWeather('Sun') && move.hasType('Water')) ||
|
|
1044
|
+
(field.hasWeather('Rain') && move.hasType('Fire'))
|
|
1045
|
+
) {
|
|
1046
|
+
baseDamage = pokeRound(OF32(baseDamage * 2048) / 4096);
|
|
1047
|
+
desc.weather = field.weather;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
if (isCritical) {
|
|
1051
|
+
baseDamage = Math.floor(OF32(baseDamage * (gen.num > 5 ? 1.5 : 2)));
|
|
1052
|
+
desc.isCritical = isCritical;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
return baseDamage;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
function calculateFinalModsBWXY(
|
|
1059
|
+
gen: Generation,
|
|
1060
|
+
attacker: Pokemon,
|
|
1061
|
+
defender: Pokemon,
|
|
1062
|
+
move: Move,
|
|
1063
|
+
field: Field,
|
|
1064
|
+
desc: RawDesc,
|
|
1065
|
+
isCritical = false,
|
|
1066
|
+
typeEffectiveness: number,
|
|
1067
|
+
hitCount = 0
|
|
1068
|
+
) {
|
|
1069
|
+
const finalMods = [];
|
|
1070
|
+
|
|
1071
|
+
if (field.defenderSide.isReflect && move.category === 'Physical' && !isCritical) {
|
|
1072
|
+
finalMods.push(field.gameType !== 'Singles' ? (gen.num > 5 ? 2732 : 2703) : 2048);
|
|
1073
|
+
desc.isReflect = true;
|
|
1074
|
+
} else if (field.defenderSide.isLightScreen && move.category === 'Special' && !isCritical) {
|
|
1075
|
+
finalMods.push(field.gameType !== 'Singles' ? (gen.num > 5 ? 2732 : 2703) : 2048);
|
|
1076
|
+
desc.isLightScreen = true;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
if (defender.hasAbility('Multiscale') && defender.curHP() === defender.maxHP() &&
|
|
1080
|
+
hitCount === 0 &&
|
|
1081
|
+
!field.defenderSide.isSR && (!field.defenderSide.spikes || defender.hasType('Flying')) &&
|
|
1082
|
+
!attacker.hasAbility('Parental Bond (Child)')) {
|
|
1083
|
+
finalMods.push(2048);
|
|
1084
|
+
desc.defenderAbility = defender.ability;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
if (attacker.hasAbility('Tinted Lens') && typeEffectiveness < 1) {
|
|
1088
|
+
finalMods.push(8192);
|
|
1089
|
+
desc.attackerAbility = attacker.ability;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
if (field.defenderSide.isFriendGuard) {
|
|
1093
|
+
finalMods.push(3072);
|
|
1094
|
+
desc.isFriendGuard = true;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
if (attacker.hasAbility('Sniper') && isCritical) {
|
|
1098
|
+
finalMods.push(6144);
|
|
1099
|
+
desc.attackerAbility = attacker.ability;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
if (defender.hasAbility('Solid Rock', 'Filter') && typeEffectiveness > 1) {
|
|
1103
|
+
finalMods.push(3072);
|
|
1104
|
+
desc.defenderAbility = defender.ability;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
if (attacker.hasItem('Metronome') && move.timesUsedWithMetronome! >= 1) {
|
|
1108
|
+
const timesUsedWithMetronome = Math.floor(move.timesUsedWithMetronome!);
|
|
1109
|
+
if (timesUsedWithMetronome <= 4) {
|
|
1110
|
+
finalMods.push(4096 + timesUsedWithMetronome * 819);
|
|
1111
|
+
} else {
|
|
1112
|
+
finalMods.push(8192);
|
|
1113
|
+
}
|
|
1114
|
+
desc.attackerItem = attacker.item;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
if (attacker.hasItem('Expert Belt') && typeEffectiveness > 1 && !move.isZ) {
|
|
1118
|
+
finalMods.push(4915);
|
|
1119
|
+
desc.attackerItem = attacker.item;
|
|
1120
|
+
} else if (attacker.hasItem('Life Orb')) {
|
|
1121
|
+
finalMods.push(5324);
|
|
1122
|
+
desc.attackerItem = attacker.item;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
if (move.hasType(getBerryResistType(defender.item)) &&
|
|
1126
|
+
(typeEffectiveness > 1 || move.hasType('Normal')) &&
|
|
1127
|
+
hitCount === 0 &&
|
|
1128
|
+
!attacker.hasAbility('Unnerve')) {
|
|
1129
|
+
finalMods.push(2048);
|
|
1130
|
+
desc.defenderItem = defender.item;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
return finalMods;
|
|
1134
|
+
}
|