@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,1788 @@
|
|
|
1
|
+
import type {Generation, AbilityName, StatID, Terrain} from '../data/interface';
|
|
2
|
+
import {toID} from '../util';
|
|
3
|
+
import {
|
|
4
|
+
getBerryResistType,
|
|
5
|
+
getFlingPower,
|
|
6
|
+
getItemBoostType,
|
|
7
|
+
getMultiAttack,
|
|
8
|
+
getNaturalGift,
|
|
9
|
+
getTechnoBlast,
|
|
10
|
+
SEED_BOOSTED_STAT,
|
|
11
|
+
} from '../items';
|
|
12
|
+
import type {RawDesc} from '../desc';
|
|
13
|
+
import type {Field} from '../field';
|
|
14
|
+
import type {Move} from '../move';
|
|
15
|
+
import type {Pokemon} from '../pokemon';
|
|
16
|
+
import {Result} from '../result';
|
|
17
|
+
import {
|
|
18
|
+
chainMods,
|
|
19
|
+
checkAirLock,
|
|
20
|
+
checkDauntlessShield,
|
|
21
|
+
checkDownload,
|
|
22
|
+
checkEmbody,
|
|
23
|
+
checkForecast,
|
|
24
|
+
checkInfiltrator,
|
|
25
|
+
checkIntimidate,
|
|
26
|
+
checkIntrepidSword,
|
|
27
|
+
checkItem,
|
|
28
|
+
checkMultihitBoost,
|
|
29
|
+
checkSeedBoost,
|
|
30
|
+
checkTeraformZero,
|
|
31
|
+
checkWindRider,
|
|
32
|
+
checkWonderRoom,
|
|
33
|
+
computeFinalStats,
|
|
34
|
+
countBoosts,
|
|
35
|
+
getBaseDamage,
|
|
36
|
+
getStatDescriptionText,
|
|
37
|
+
getFinalDamage,
|
|
38
|
+
getModifiedStat,
|
|
39
|
+
getQPBoostedStat,
|
|
40
|
+
getMoveEffectiveness,
|
|
41
|
+
getShellSideArmCategory,
|
|
42
|
+
getWeight,
|
|
43
|
+
handleFixedDamageMoves,
|
|
44
|
+
isGrounded,
|
|
45
|
+
OF16, OF32,
|
|
46
|
+
pokeRound,
|
|
47
|
+
isQPActive,
|
|
48
|
+
getStabMod,
|
|
49
|
+
getStellarStabMod,
|
|
50
|
+
} from './util';
|
|
51
|
+
|
|
52
|
+
export function calculateSMSSSV(
|
|
53
|
+
gen: Generation,
|
|
54
|
+
attacker: Pokemon,
|
|
55
|
+
defender: Pokemon,
|
|
56
|
+
move: Move,
|
|
57
|
+
field: Field
|
|
58
|
+
) {
|
|
59
|
+
// #region Initial
|
|
60
|
+
|
|
61
|
+
checkAirLock(attacker, field);
|
|
62
|
+
checkAirLock(defender, field);
|
|
63
|
+
checkTeraformZero(attacker, field);
|
|
64
|
+
checkTeraformZero(defender, field);
|
|
65
|
+
checkForecast(attacker, field.weather);
|
|
66
|
+
checkForecast(defender, field.weather);
|
|
67
|
+
checkItem(attacker, field.isMagicRoom);
|
|
68
|
+
checkItem(defender, field.isMagicRoom);
|
|
69
|
+
checkWonderRoom(attacker, field.isWonderRoom);
|
|
70
|
+
checkWonderRoom(defender, field.isWonderRoom);
|
|
71
|
+
checkSeedBoost(attacker, field);
|
|
72
|
+
checkSeedBoost(defender, field);
|
|
73
|
+
checkDauntlessShield(attacker, gen);
|
|
74
|
+
checkDauntlessShield(defender, gen);
|
|
75
|
+
checkEmbody(attacker, gen);
|
|
76
|
+
checkEmbody(defender, gen);
|
|
77
|
+
|
|
78
|
+
computeFinalStats(gen, attacker, defender, field, 'def', 'spd', 'spe');
|
|
79
|
+
|
|
80
|
+
checkIntimidate(gen, attacker, defender);
|
|
81
|
+
checkIntimidate(gen, defender, attacker);
|
|
82
|
+
checkDownload(attacker, defender, field.isWonderRoom);
|
|
83
|
+
checkDownload(defender, attacker, field.isWonderRoom);
|
|
84
|
+
checkIntrepidSword(attacker, gen);
|
|
85
|
+
checkIntrepidSword(defender, gen);
|
|
86
|
+
|
|
87
|
+
checkWindRider(attacker, field.attackerSide);
|
|
88
|
+
checkWindRider(defender, field.defenderSide);
|
|
89
|
+
|
|
90
|
+
if (move.named('Meteor Beam', 'Electro Shot')) {
|
|
91
|
+
attacker.boosts.spa +=
|
|
92
|
+
attacker.hasAbility('Simple') ? 2
|
|
93
|
+
: attacker.hasAbility('Contrary') ? -1
|
|
94
|
+
: 1;
|
|
95
|
+
// restrict to +- 6
|
|
96
|
+
attacker.boosts.spa = Math.min(6, Math.max(-6, attacker.boosts.spa));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
computeFinalStats(gen, attacker, defender, field, 'atk', 'spa');
|
|
100
|
+
|
|
101
|
+
checkInfiltrator(attacker, field.defenderSide);
|
|
102
|
+
checkInfiltrator(defender, field.attackerSide);
|
|
103
|
+
|
|
104
|
+
const desc: RawDesc = {
|
|
105
|
+
attackerName: attacker.name,
|
|
106
|
+
moveName: move.name,
|
|
107
|
+
defenderName: defender.name,
|
|
108
|
+
isDefenderDynamaxed: defender.isDynamaxed,
|
|
109
|
+
isWonderRoom: field.isWonderRoom,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// only display tera type if it applies
|
|
113
|
+
if (attacker.teraType !== 'Stellar' || move.name === 'Tera Blast' || move.isStellarFirstUse) {
|
|
114
|
+
// tera blast has special behavior with tera stellar
|
|
115
|
+
desc.isStellarFirstUse = attacker.name !== 'Terapagos-Stellar' && move.name === 'Tera Blast' &&
|
|
116
|
+
attacker.teraType === 'Stellar' && move.isStellarFirstUse;
|
|
117
|
+
desc.attackerTera = attacker.teraType;
|
|
118
|
+
}
|
|
119
|
+
if (defender.teraType !== 'Stellar') desc.defenderTera = defender.teraType;
|
|
120
|
+
|
|
121
|
+
if (move.named('Photon Geyser', 'Light That Burns the Sky') ||
|
|
122
|
+
(move.named('Tera Blast') && attacker.teraType)) {
|
|
123
|
+
move.category = attacker.stats.atk > attacker.stats.spa ? 'Physical' : 'Special';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const result = new Result(gen, attacker, defender, move, field, 0, desc);
|
|
127
|
+
|
|
128
|
+
if (move.category === 'Status' && !move.named('Nature Power')) {
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (move.flags.punch && attacker.hasItem('Punching Glove')) {
|
|
133
|
+
desc.attackerItem = attacker.item;
|
|
134
|
+
move.flags.contact = 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (move.named('Shell Side Arm') &&
|
|
138
|
+
getShellSideArmCategory(attacker, defender) === 'Physical') {
|
|
139
|
+
move.flags.contact = 1;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const breaksProtect = move.breaksProtect || move.isZ || attacker.isDynamaxed ||
|
|
143
|
+
(attacker.hasAbility('Unseen Fist') && move.flags.contact);
|
|
144
|
+
|
|
145
|
+
if (field.defenderSide.isProtected && !breaksProtect) {
|
|
146
|
+
desc.isProtected = true;
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (move.name === 'Pain Split') {
|
|
151
|
+
const average = Math.floor((attacker.curHP() + defender.curHP()) / 2);
|
|
152
|
+
const damage = Math.max(0, defender.curHP() - average);
|
|
153
|
+
result.damage = damage;
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const defenderAbilityIgnored = defender.hasAbility(
|
|
158
|
+
'Armor Tail', 'Aroma Veil', 'Aura Break', 'Battle Armor',
|
|
159
|
+
'Big Pecks', 'Bulletproof', 'Clear Body', 'Contrary',
|
|
160
|
+
'Damp', 'Dazzling', 'Disguise', 'Dry Skin',
|
|
161
|
+
'Earth Eater', 'Filter', 'Flash Fire', 'Flower Gift',
|
|
162
|
+
'Flower Veil', 'Fluffy', 'Friend Guard', 'Fur Coat',
|
|
163
|
+
'Good as Gold', 'Grass Pelt', 'Guard Dog', 'Heatproof',
|
|
164
|
+
'Heavy Metal', 'Hyper Cutter', 'Ice Face', 'Ice Scales',
|
|
165
|
+
'Illuminate', 'Immunity', 'Inner Focus', 'Insomnia',
|
|
166
|
+
'Keen Eye', 'Leaf Guard', 'Levitate', 'Light Metal',
|
|
167
|
+
'Lightning Rod', 'Limber', 'Magic Bounce', 'Magma Armor',
|
|
168
|
+
'Marvel Scale', "Mind's Eye", 'Mirror Armor', 'Motor Drive',
|
|
169
|
+
'Multiscale', 'Oblivious', 'Overcoat', 'Own Tempo',
|
|
170
|
+
'Pastel Veil', 'Punk Rock', 'Purifying Salt', 'Queenly Majesty',
|
|
171
|
+
'Sand Veil', 'Sap Sipper', 'Shell Armor', 'Shield Dust',
|
|
172
|
+
'Simple', 'Snow Cloak', 'Solid Rock', 'Soundproof',
|
|
173
|
+
'Sticky Hold', 'Storm Drain', 'Sturdy', 'Suction Cups',
|
|
174
|
+
'Sweet Veil', 'Tangled Feet', 'Telepathy', 'Tera Shell',
|
|
175
|
+
'Thermal Exchange', 'Thick Fat', 'Unaware', 'Vital Spirit',
|
|
176
|
+
'Volt Absorb', 'Water Absorb', 'Water Bubble', 'Water Veil',
|
|
177
|
+
'Well-Baked Body', 'White Smoke', 'Wind Rider', 'Wonder Guard',
|
|
178
|
+
'Wonder Skin'
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const attackerIgnoresAbility = attacker.hasAbility('Mold Breaker', 'Teravolt', 'Turboblaze');
|
|
182
|
+
const moveIgnoresAbility = move.named(
|
|
183
|
+
'G-Max Drum Solo',
|
|
184
|
+
'G-Max Fire Ball',
|
|
185
|
+
'G-Max Hydrosnipe',
|
|
186
|
+
'Light That Burns the Sky',
|
|
187
|
+
'Menacing Moonraze Maelstrom',
|
|
188
|
+
'Moongeist Beam',
|
|
189
|
+
'Photon Geyser',
|
|
190
|
+
'Searing Sunraze Smash',
|
|
191
|
+
'Sunsteel Strike'
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
if (defenderAbilityIgnored && (attackerIgnoresAbility || moveIgnoresAbility)) {
|
|
195
|
+
if (attackerIgnoresAbility) desc.attackerAbility = attacker.ability;
|
|
196
|
+
if (defender.hasItem('Ability Shield')) {
|
|
197
|
+
desc.defenderItem = defender.item;
|
|
198
|
+
} else {
|
|
199
|
+
defender.ability = '' as AbilityName;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const ignoresNeutralizingGas = [
|
|
204
|
+
'As One (Glastrier)', 'As One (Spectrier)', 'Battle Bond', 'Comatose',
|
|
205
|
+
'Disguise', 'Gulp Missile', 'Ice Face', 'Multitype', 'Neutralizing Gas',
|
|
206
|
+
'Power Construct', 'RKS System', 'Schooling', 'Shields Down',
|
|
207
|
+
'Stance Change', 'Tera Shift', 'Zen Mode', 'Zero to Hero',
|
|
208
|
+
];
|
|
209
|
+
|
|
210
|
+
if (attacker.hasAbility('Neutralizing Gas') &&
|
|
211
|
+
!ignoresNeutralizingGas.includes(defender.ability || '')) {
|
|
212
|
+
desc.attackerAbility = attacker.ability;
|
|
213
|
+
if (defender.hasItem('Ability Shield')) {
|
|
214
|
+
desc.defenderItem = defender.item;
|
|
215
|
+
} else {
|
|
216
|
+
defender.ability = '' as AbilityName;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (defender.hasAbility('Neutralizing Gas') &&
|
|
221
|
+
!ignoresNeutralizingGas.includes(attacker.ability || '')) {
|
|
222
|
+
desc.defenderAbility = defender.ability;
|
|
223
|
+
if (attacker.hasItem('Ability Shield')) {
|
|
224
|
+
desc.attackerItem = attacker.item;
|
|
225
|
+
} else {
|
|
226
|
+
attacker.ability = '' as AbilityName;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Merciless does not ignore Shell Armor, damage dealt to a poisoned Pokemon with Shell Armor
|
|
231
|
+
// will not be a critical hit (UltiMario)
|
|
232
|
+
const isCritical = !defender.hasAbility('Battle Armor', 'Shell Armor') &&
|
|
233
|
+
(move.isCrit || (attacker.hasAbility('Merciless') && defender.hasStatus('psn', 'tox'))) &&
|
|
234
|
+
move.timesUsed === 1;
|
|
235
|
+
|
|
236
|
+
let type = move.type;
|
|
237
|
+
if (move.originalName === 'Weather Ball') {
|
|
238
|
+
const holdingUmbrella = attacker.hasItem('Utility Umbrella');
|
|
239
|
+
type =
|
|
240
|
+
field.hasWeather('Sun', 'Harsh Sunshine') && !holdingUmbrella ? 'Fire'
|
|
241
|
+
: field.hasWeather('Rain', 'Heavy Rain') && !holdingUmbrella ? 'Water'
|
|
242
|
+
: field.hasWeather('Sand') ? 'Rock'
|
|
243
|
+
: field.hasWeather('Hail', 'Snow') ? 'Ice'
|
|
244
|
+
: 'Normal';
|
|
245
|
+
desc.weather = field.weather;
|
|
246
|
+
desc.moveType = type;
|
|
247
|
+
} else if (move.named('Judgment') && attacker.item && attacker.item.includes('Plate')) {
|
|
248
|
+
type = getItemBoostType(attacker.item)!;
|
|
249
|
+
} else if (move.originalName === 'Techno Blast' &&
|
|
250
|
+
attacker.item && attacker.item.includes('Drive')) {
|
|
251
|
+
type = getTechnoBlast(attacker.item)!;
|
|
252
|
+
desc.moveType = type;
|
|
253
|
+
} else if (move.originalName === 'Multi-Attack' &&
|
|
254
|
+
attacker.item && attacker.item.includes('Memory')) {
|
|
255
|
+
type = getMultiAttack(attacker.item)!;
|
|
256
|
+
desc.moveType = type;
|
|
257
|
+
} else if (move.named('Natural Gift') && attacker.item?.endsWith('Berry')) {
|
|
258
|
+
const gift = getNaturalGift(gen, attacker.item)!;
|
|
259
|
+
type = gift.t;
|
|
260
|
+
desc.moveType = type;
|
|
261
|
+
desc.attackerItem = attacker.item;
|
|
262
|
+
} else if (
|
|
263
|
+
move.named('Nature Power') ||
|
|
264
|
+
(move.originalName === 'Terrain Pulse' && isGrounded(attacker, field))
|
|
265
|
+
) {
|
|
266
|
+
type =
|
|
267
|
+
field.hasTerrain('Electric') ? 'Electric'
|
|
268
|
+
: field.hasTerrain('Grassy') ? 'Grass'
|
|
269
|
+
: field.hasTerrain('Misty') ? 'Fairy'
|
|
270
|
+
: field.hasTerrain('Psychic') ? 'Psychic'
|
|
271
|
+
: 'Normal';
|
|
272
|
+
desc.terrain = field.terrain;
|
|
273
|
+
|
|
274
|
+
if (move.isMax) {
|
|
275
|
+
desc.moveType = type;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// If the Nature Power user has the ability Prankster, it cannot affect
|
|
279
|
+
// Dark-types or grounded foes if Psychic Terrain is active
|
|
280
|
+
if (!(move.named('Nature Power') && attacker.hasAbility('Prankster')) &&
|
|
281
|
+
((defender.types.includes('Dark') ||
|
|
282
|
+
(field.hasTerrain('Psychic') && isGrounded(defender, field))))) {
|
|
283
|
+
desc.moveType = type;
|
|
284
|
+
}
|
|
285
|
+
} else if (move.originalName === 'Revelation Dance') {
|
|
286
|
+
if (attacker.teraType) {
|
|
287
|
+
type = attacker.teraType;
|
|
288
|
+
} else {
|
|
289
|
+
type = attacker.types[0];
|
|
290
|
+
}
|
|
291
|
+
} else if (move.named('Aura Wheel')) {
|
|
292
|
+
if (attacker.named('Morpeko')) {
|
|
293
|
+
type = 'Electric';
|
|
294
|
+
} else if (attacker.named('Morpeko-Hangry')) {
|
|
295
|
+
type = 'Dark';
|
|
296
|
+
}
|
|
297
|
+
} else if (move.named('Raging Bull')) {
|
|
298
|
+
if (attacker.named('Tauros-Paldea-Combat')) {
|
|
299
|
+
type = 'Fighting';
|
|
300
|
+
} else if (attacker.named('Tauros-Paldea-Blaze')) {
|
|
301
|
+
type = 'Fire';
|
|
302
|
+
} else if (attacker.named('Tauros-Paldea-Aqua')) {
|
|
303
|
+
type = 'Water';
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
field.defenderSide.isReflect = false;
|
|
307
|
+
field.defenderSide.isLightScreen = false;
|
|
308
|
+
field.defenderSide.isAuroraVeil = false;
|
|
309
|
+
} else if (move.named('Ivy Cudgel')) {
|
|
310
|
+
if (attacker.name.includes('Ogerpon-Cornerstone')) {
|
|
311
|
+
type = 'Rock';
|
|
312
|
+
} else if (attacker.name.includes('Ogerpon-Hearthflame')) {
|
|
313
|
+
type = 'Fire';
|
|
314
|
+
} else if (attacker.name.includes('Ogerpon-Wellspring')) {
|
|
315
|
+
type = 'Water';
|
|
316
|
+
}
|
|
317
|
+
} else if (
|
|
318
|
+
move.named('Tera Starstorm') && attacker.name === 'Terapagos-Stellar'
|
|
319
|
+
) {
|
|
320
|
+
move.target = 'allAdjacentFoes';
|
|
321
|
+
type = 'Stellar';
|
|
322
|
+
} else if (move.named('Brick Break', 'Psychic Fangs')) {
|
|
323
|
+
field.defenderSide.isReflect = false;
|
|
324
|
+
field.defenderSide.isLightScreen = false;
|
|
325
|
+
field.defenderSide.isAuroraVeil = false;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
let hasAteAbilityTypeChange = false;
|
|
329
|
+
let isAerilate = false;
|
|
330
|
+
let isPixilate = false;
|
|
331
|
+
let isRefrigerate = false;
|
|
332
|
+
let isGalvanize = false;
|
|
333
|
+
let isLiquidVoice = false;
|
|
334
|
+
let isNormalize = false;
|
|
335
|
+
const noTypeChange = move.named(
|
|
336
|
+
'Revelation Dance',
|
|
337
|
+
'Judgment',
|
|
338
|
+
'Nature Power',
|
|
339
|
+
'Techno Blast',
|
|
340
|
+
'Multi-Attack',
|
|
341
|
+
'Natural Gift',
|
|
342
|
+
'Weather Ball',
|
|
343
|
+
'Terrain Pulse',
|
|
344
|
+
'Struggle',
|
|
345
|
+
) || (move.named('Tera Blast') && attacker.teraType);
|
|
346
|
+
|
|
347
|
+
if (!move.isZ && !noTypeChange) {
|
|
348
|
+
const normal = type === 'Normal';
|
|
349
|
+
if ((isAerilate = attacker.hasAbility('Aerilate') && normal)) {
|
|
350
|
+
type = 'Flying';
|
|
351
|
+
} else if ((isGalvanize = attacker.hasAbility('Galvanize') && normal)) {
|
|
352
|
+
type = 'Electric';
|
|
353
|
+
} else if ((isLiquidVoice = attacker.hasAbility('Liquid Voice') && !!move.flags.sound)) {
|
|
354
|
+
type = 'Water';
|
|
355
|
+
} else if ((isPixilate = attacker.hasAbility('Pixilate') && normal)) {
|
|
356
|
+
type = 'Fairy';
|
|
357
|
+
} else if ((isRefrigerate = attacker.hasAbility('Refrigerate') && normal)) {
|
|
358
|
+
type = 'Ice';
|
|
359
|
+
} else if ((isNormalize = attacker.hasAbility('Normalize'))) { // Boosts any type
|
|
360
|
+
type = 'Normal';
|
|
361
|
+
}
|
|
362
|
+
if (isGalvanize || isPixilate || isRefrigerate || isAerilate || isNormalize) {
|
|
363
|
+
desc.attackerAbility = attacker.ability;
|
|
364
|
+
hasAteAbilityTypeChange = true;
|
|
365
|
+
} else if (isLiquidVoice) {
|
|
366
|
+
desc.attackerAbility = attacker.ability;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (move.named('Tera Blast') && attacker.teraType) {
|
|
371
|
+
type = attacker.teraType;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
move.type = type;
|
|
375
|
+
|
|
376
|
+
// FIXME: this is incorrect, should be move.flags.heal, not move.drain
|
|
377
|
+
if ((attacker.hasAbility('Triage') && move.drain) ||
|
|
378
|
+
(attacker.hasAbility('Gale Wings') &&
|
|
379
|
+
move.hasType('Flying') &&
|
|
380
|
+
attacker.curHP() === attacker.maxHP())) {
|
|
381
|
+
move.priority = 1;
|
|
382
|
+
desc.attackerAbility = attacker.ability;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const isGhostRevealed =
|
|
386
|
+
attacker.hasAbility('Scrappy') || attacker.hasAbility('Mind\'s Eye') ||
|
|
387
|
+
field.defenderSide.isForesight;
|
|
388
|
+
const isRingTarget =
|
|
389
|
+
defender.hasItem('Ring Target') && !defender.hasAbility('Klutz');
|
|
390
|
+
const type1Effectiveness = getMoveEffectiveness(
|
|
391
|
+
gen,
|
|
392
|
+
move,
|
|
393
|
+
defender.types[0],
|
|
394
|
+
isGhostRevealed,
|
|
395
|
+
field.isGravity,
|
|
396
|
+
isRingTarget
|
|
397
|
+
);
|
|
398
|
+
const type2Effectiveness = defender.types[1]
|
|
399
|
+
? getMoveEffectiveness(
|
|
400
|
+
gen,
|
|
401
|
+
move,
|
|
402
|
+
defender.types[1],
|
|
403
|
+
isGhostRevealed,
|
|
404
|
+
field.isGravity,
|
|
405
|
+
isRingTarget
|
|
406
|
+
)
|
|
407
|
+
: 1;
|
|
408
|
+
let typeEffectiveness = type1Effectiveness * type2Effectiveness;
|
|
409
|
+
|
|
410
|
+
if (defender.teraType && defender.teraType !== 'Stellar') {
|
|
411
|
+
typeEffectiveness = getMoveEffectiveness(
|
|
412
|
+
gen,
|
|
413
|
+
move,
|
|
414
|
+
defender.teraType,
|
|
415
|
+
isGhostRevealed,
|
|
416
|
+
field.isGravity,
|
|
417
|
+
isRingTarget
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (typeEffectiveness === 0 && move.hasType('Ground') &&
|
|
422
|
+
defender.hasItem('Iron Ball') && !defender.hasAbility('Klutz')) {
|
|
423
|
+
typeEffectiveness = 1;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (typeEffectiveness === 0 && move.named('Thousand Arrows')) {
|
|
427
|
+
typeEffectiveness = 1;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (typeEffectiveness === 0) {
|
|
431
|
+
return result;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if ((move.named('Sky Drop') &&
|
|
435
|
+
(defender.hasType('Flying') || defender.weightkg >= 200 || field.isGravity)) ||
|
|
436
|
+
(move.named('Synchronoise') && !defender.hasType(attacker.types[0]) &&
|
|
437
|
+
(!attacker.types[1] || !defender.hasType(attacker.types[1]))) ||
|
|
438
|
+
(move.named('Dream Eater') &&
|
|
439
|
+
(!(defender.hasStatus('slp') || defender.hasAbility('Comatose')))) ||
|
|
440
|
+
(move.named('Steel Roller') && !field.terrain) ||
|
|
441
|
+
(move.named('Poltergeist') && (!defender.item || isQPActive(defender, field)))
|
|
442
|
+
) {
|
|
443
|
+
return result;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (
|
|
447
|
+
(field.hasWeather('Harsh Sunshine') && move.hasType('Water')) ||
|
|
448
|
+
(field.hasWeather('Heavy Rain') && move.hasType('Fire'))
|
|
449
|
+
) {
|
|
450
|
+
desc.weather = field.weather;
|
|
451
|
+
return result;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (field.hasWeather('Strong Winds') && defender.hasType('Flying') &&
|
|
455
|
+
gen.types.get(toID(move.type))!.effectiveness['Flying']! > 1) {
|
|
456
|
+
typeEffectiveness /= 2;
|
|
457
|
+
desc.weather = field.weather;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (move.type === 'Stellar') {
|
|
461
|
+
desc.defenderTera = defender.teraType; // always show in this case
|
|
462
|
+
typeEffectiveness = !defender.teraType ? 1 : 2;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const turn2typeEffectiveness = typeEffectiveness;
|
|
466
|
+
|
|
467
|
+
// Tera Shell works only at full HP, but for all hits of multi-hit moves
|
|
468
|
+
if (defender.hasAbility('Tera Shell') &&
|
|
469
|
+
defender.curHP() === defender.maxHP() &&
|
|
470
|
+
(!field.defenderSide.isSR && (!field.defenderSide.spikes || defender.hasType('Flying')) ||
|
|
471
|
+
defender.hasItem('Heavy-Duty Boots'))
|
|
472
|
+
) {
|
|
473
|
+
typeEffectiveness = 0.5;
|
|
474
|
+
desc.defenderAbility = defender.ability;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if ((defender.hasAbility('Wonder Guard') && typeEffectiveness <= 1) ||
|
|
478
|
+
(move.hasType('Grass') && defender.hasAbility('Sap Sipper')) ||
|
|
479
|
+
(move.hasType('Fire') && defender.hasAbility('Flash Fire', 'Well-Baked Body')) ||
|
|
480
|
+
(move.hasType('Water') && defender.hasAbility('Dry Skin', 'Storm Drain', 'Water Absorb')) ||
|
|
481
|
+
(move.hasType('Electric') &&
|
|
482
|
+
defender.hasAbility('Lightning Rod', 'Motor Drive', 'Volt Absorb')) ||
|
|
483
|
+
(move.hasType('Ground') &&
|
|
484
|
+
!field.isGravity && !move.named('Thousand Arrows') &&
|
|
485
|
+
!defender.hasItem('Iron Ball') && defender.hasAbility('Levitate')) ||
|
|
486
|
+
(move.flags.bullet && defender.hasAbility('Bulletproof')) ||
|
|
487
|
+
(move.flags.sound && !move.named('Clangorous Soul') && defender.hasAbility('Soundproof')) ||
|
|
488
|
+
(move.priority > 0 && defender.hasAbility('Queenly Majesty', 'Dazzling', 'Armor Tail')) ||
|
|
489
|
+
(move.hasType('Ground') && defender.hasAbility('Earth Eater')) ||
|
|
490
|
+
(move.flags.wind && defender.hasAbility('Wind Rider'))
|
|
491
|
+
) {
|
|
492
|
+
desc.defenderAbility = defender.ability;
|
|
493
|
+
return result;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (move.hasType('Ground') && !move.named('Thousand Arrows') &&
|
|
497
|
+
!field.isGravity && defender.hasItem('Air Balloon')) {
|
|
498
|
+
desc.defenderItem = defender.item;
|
|
499
|
+
return result;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (move.priority > 0 && field.hasTerrain('Psychic') && isGrounded(defender, field)) {
|
|
503
|
+
desc.terrain = field.terrain;
|
|
504
|
+
return result;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const weightBasedMove = move.named('Heat Crash', 'Heavy Slam', 'Low Kick', 'Grass Knot');
|
|
508
|
+
if (defender.isDynamaxed && weightBasedMove) {
|
|
509
|
+
return result;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
desc.HPEVs = getStatDescriptionText(gen, defender, 'hp');
|
|
513
|
+
|
|
514
|
+
const fixedDamage = handleFixedDamageMoves(attacker, move);
|
|
515
|
+
if (fixedDamage) {
|
|
516
|
+
if (attacker.hasAbility('Parental Bond')) {
|
|
517
|
+
result.damage = [fixedDamage, fixedDamage];
|
|
518
|
+
desc.attackerAbility = attacker.ability;
|
|
519
|
+
} else {
|
|
520
|
+
result.damage = fixedDamage;
|
|
521
|
+
}
|
|
522
|
+
return result;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (move.named('Final Gambit')) {
|
|
526
|
+
result.damage = attacker.curHP();
|
|
527
|
+
return result;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (move.named('Guardian of Alola')) {
|
|
531
|
+
let zLostHP = Math.floor((defender.curHP() * 3) / 4);
|
|
532
|
+
if (field.defenderSide.isProtected && attacker.item && attacker.item.includes(' Z')) {
|
|
533
|
+
zLostHP = Math.ceil(zLostHP / 4 - 0.5);
|
|
534
|
+
}
|
|
535
|
+
result.damage = zLostHP;
|
|
536
|
+
return result;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (move.named('Nature\'s Madness')) {
|
|
540
|
+
const lostHP = field.defenderSide.isProtected ? 0 : Math.floor(defender.curHP() / 2);
|
|
541
|
+
result.damage = lostHP;
|
|
542
|
+
return result;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (move.named('Spectral Thief')) {
|
|
546
|
+
let stat: StatID;
|
|
547
|
+
for (stat in defender.boosts) {
|
|
548
|
+
if (defender.boosts[stat] > 0) {
|
|
549
|
+
attacker.boosts[stat] +=
|
|
550
|
+
attacker.hasAbility('Contrary') ? -defender.boosts[stat]! : defender.boosts[stat]!;
|
|
551
|
+
if (attacker.boosts[stat] > 6) attacker.boosts[stat] = 6;
|
|
552
|
+
if (attacker.boosts[stat] < -6) attacker.boosts[stat] = -6;
|
|
553
|
+
attacker.stats[stat] = getModifiedStat(attacker.rawStats[stat]!, attacker.boosts[stat]!);
|
|
554
|
+
defender.boosts[stat] = 0;
|
|
555
|
+
defender.stats[stat] = defender.rawStats[stat];
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (move.hits > 1) {
|
|
561
|
+
desc.hits = move.hits;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const turnOrder = attacker.stats.spe > defender.stats.spe ? 'first' : 'last';
|
|
565
|
+
|
|
566
|
+
// #endregion
|
|
567
|
+
// #region Base Power
|
|
568
|
+
|
|
569
|
+
const basePower = calculateBasePowerSMSSSV(
|
|
570
|
+
gen,
|
|
571
|
+
attacker,
|
|
572
|
+
defender,
|
|
573
|
+
move,
|
|
574
|
+
field,
|
|
575
|
+
hasAteAbilityTypeChange,
|
|
576
|
+
desc
|
|
577
|
+
);
|
|
578
|
+
if (basePower === 0) {
|
|
579
|
+
return result;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// #endregion
|
|
583
|
+
// #region (Special) Attack
|
|
584
|
+
const attack = calculateAttackSMSSSV(gen, attacker, defender, move, field, desc, isCritical);
|
|
585
|
+
const attackStat =
|
|
586
|
+
move.named('Shell Side Arm') &&
|
|
587
|
+
getShellSideArmCategory(attacker, defender) === 'Physical'
|
|
588
|
+
? 'atk'
|
|
589
|
+
: move.named('Body Press')
|
|
590
|
+
? 'def'
|
|
591
|
+
: move.category === 'Special'
|
|
592
|
+
? 'spa'
|
|
593
|
+
: 'atk';
|
|
594
|
+
// #endregion
|
|
595
|
+
|
|
596
|
+
// #region (Special) Defense
|
|
597
|
+
|
|
598
|
+
const defense = calculateDefenseSMSSSV(gen, attacker, defender, move, field, desc, isCritical);
|
|
599
|
+
const hitsPhysical = move.overrideDefensiveStat === 'def' || move.category === 'Physical' ||
|
|
600
|
+
(move.named('Shell Side Arm') && getShellSideArmCategory(attacker, defender) === 'Physical');
|
|
601
|
+
const defenseStat = hitsPhysical ? 'def' : 'spd';
|
|
602
|
+
|
|
603
|
+
// #endregion
|
|
604
|
+
// #region Damage
|
|
605
|
+
|
|
606
|
+
const baseDamage = calculateBaseDamageSMSSSV(
|
|
607
|
+
gen,
|
|
608
|
+
attacker,
|
|
609
|
+
defender,
|
|
610
|
+
basePower,
|
|
611
|
+
attack,
|
|
612
|
+
defense,
|
|
613
|
+
move,
|
|
614
|
+
field,
|
|
615
|
+
desc,
|
|
616
|
+
isCritical
|
|
617
|
+
);
|
|
618
|
+
|
|
619
|
+
if (hasTerrainSeed(defender) &&
|
|
620
|
+
field.hasTerrain(defender.item!.substring(0, defender.item!.indexOf(' ')) as Terrain) &&
|
|
621
|
+
SEED_BOOSTED_STAT[defender.item!] === defenseStat) {
|
|
622
|
+
// Last condition applies so the calc doesn't show a seed where it wouldn't affect the outcome
|
|
623
|
+
// (like Grassy Seed when being hit by a special move)
|
|
624
|
+
desc.defenderItem = defender.item;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// the random factor is applied between the crit mod and the stab mod, so don't apply anything
|
|
628
|
+
// below this until we're inside the loop
|
|
629
|
+
let preStellarStabMod = getStabMod(attacker, move, desc);
|
|
630
|
+
let stabMod = getStellarStabMod(attacker, move, preStellarStabMod);
|
|
631
|
+
|
|
632
|
+
const applyBurn =
|
|
633
|
+
attacker.hasStatus('brn') &&
|
|
634
|
+
move.category === 'Physical' &&
|
|
635
|
+
!attacker.hasAbility('Guts') &&
|
|
636
|
+
!move.named('Facade');
|
|
637
|
+
desc.isBurned = applyBurn;
|
|
638
|
+
const finalMods = calculateFinalModsSMSSSV(
|
|
639
|
+
gen,
|
|
640
|
+
attacker,
|
|
641
|
+
defender,
|
|
642
|
+
move,
|
|
643
|
+
field,
|
|
644
|
+
desc,
|
|
645
|
+
isCritical,
|
|
646
|
+
typeEffectiveness
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
let protect = false;
|
|
650
|
+
if (field.defenderSide.isProtected &&
|
|
651
|
+
(attacker.isDynamaxed || (move.isZ && attacker.item && attacker.item.includes(' Z')))) {
|
|
652
|
+
protect = true;
|
|
653
|
+
desc.isProtected = true;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const finalMod = chainMods(finalMods, 41, 131072);
|
|
657
|
+
|
|
658
|
+
const isSpread = field.gameType !== 'Singles' &&
|
|
659
|
+
['allAdjacent', 'allAdjacentFoes'].includes(move.target);
|
|
660
|
+
|
|
661
|
+
let childDamage: number[] | undefined;
|
|
662
|
+
if (attacker.hasAbility('Parental Bond') && move.hits === 1 && !isSpread) {
|
|
663
|
+
const child = attacker.clone();
|
|
664
|
+
child.ability = 'Parental Bond (Child)' as AbilityName;
|
|
665
|
+
checkMultihitBoost(gen, child, defender, move, field, desc);
|
|
666
|
+
childDamage = calculateSMSSSV(gen, child, defender, move, field).damage as number[];
|
|
667
|
+
desc.attackerAbility = attacker.ability;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
let damage = [];
|
|
671
|
+
for (let i = 0; i < 16; i++) {
|
|
672
|
+
damage[i] =
|
|
673
|
+
getFinalDamage(baseDamage, i, typeEffectiveness, applyBurn, stabMod, finalMod, protect);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
desc.attackBoost =
|
|
677
|
+
move.named('Foul Play') ? defender.boosts[attackStat] : attacker.boosts[attackStat];
|
|
678
|
+
|
|
679
|
+
if ((move.dropsStats && move.timesUsed! > 1) || move.hits > 1) {
|
|
680
|
+
// store boosts so intermediate boosts don't show.
|
|
681
|
+
const origDefBoost = desc.defenseBoost;
|
|
682
|
+
const origAtkBoost = desc.attackBoost;
|
|
683
|
+
|
|
684
|
+
let numAttacks = 1;
|
|
685
|
+
if (move.dropsStats && move.timesUsed! > 1) {
|
|
686
|
+
desc.moveTurns = `over ${move.timesUsed} turns`;
|
|
687
|
+
numAttacks = move.timesUsed!;
|
|
688
|
+
} else {
|
|
689
|
+
numAttacks = move.hits;
|
|
690
|
+
}
|
|
691
|
+
let usedItems = [false, false];
|
|
692
|
+
for (let times = 1; times < numAttacks; times++) {
|
|
693
|
+
usedItems = checkMultihitBoost(gen, attacker, defender, move,
|
|
694
|
+
field, desc, usedItems[0], usedItems[1]);
|
|
695
|
+
const newAttack = calculateAttackSMSSSV(gen, attacker, defender, move,
|
|
696
|
+
field, desc, isCritical);
|
|
697
|
+
const newDefense = calculateDefenseSMSSSV(gen, attacker, defender, move,
|
|
698
|
+
field, desc, isCritical);
|
|
699
|
+
// Check if lost -ate ability. Typing stays the same, only boost is lost
|
|
700
|
+
// Cannot be regained during multihit move and no Normal moves with stat drawbacks
|
|
701
|
+
hasAteAbilityTypeChange = hasAteAbilityTypeChange &&
|
|
702
|
+
attacker.hasAbility('Aerilate', 'Galvanize', 'Pixilate', 'Refrigerate', 'Normalize');
|
|
703
|
+
|
|
704
|
+
if ((move.dropsStats && move.timesUsed! > 1)) {
|
|
705
|
+
// Adaptability does not change between hits of a multihit, only between turns
|
|
706
|
+
preStellarStabMod = getStabMod(attacker, move, desc);
|
|
707
|
+
// Hack to make Tera Shell with multihit moves, but not over multiple turns
|
|
708
|
+
typeEffectiveness = turn2typeEffectiveness;
|
|
709
|
+
// Stellar damage boost applies for 1 turn, but all hits of multihit.
|
|
710
|
+
stabMod = getStellarStabMod(attacker, move, preStellarStabMod, times);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const newBasePower = calculateBasePowerSMSSSV(
|
|
714
|
+
gen,
|
|
715
|
+
attacker,
|
|
716
|
+
defender,
|
|
717
|
+
move,
|
|
718
|
+
field,
|
|
719
|
+
hasAteAbilityTypeChange,
|
|
720
|
+
desc,
|
|
721
|
+
times + 1
|
|
722
|
+
);
|
|
723
|
+
const newBaseDamage = calculateBaseDamageSMSSSV(
|
|
724
|
+
gen,
|
|
725
|
+
attacker,
|
|
726
|
+
defender,
|
|
727
|
+
newBasePower,
|
|
728
|
+
newAttack,
|
|
729
|
+
newDefense,
|
|
730
|
+
move,
|
|
731
|
+
field,
|
|
732
|
+
desc,
|
|
733
|
+
isCritical
|
|
734
|
+
);
|
|
735
|
+
const newFinalMods = calculateFinalModsSMSSSV(
|
|
736
|
+
gen,
|
|
737
|
+
attacker,
|
|
738
|
+
defender,
|
|
739
|
+
move,
|
|
740
|
+
field,
|
|
741
|
+
desc,
|
|
742
|
+
isCritical,
|
|
743
|
+
typeEffectiveness,
|
|
744
|
+
times
|
|
745
|
+
);
|
|
746
|
+
const newFinalMod = chainMods(newFinalMods, 41, 131072);
|
|
747
|
+
|
|
748
|
+
let damageMultiplier = 0;
|
|
749
|
+
damage = damage.map(affectedAmount => {
|
|
750
|
+
const newFinalDamage = getFinalDamage(
|
|
751
|
+
newBaseDamage,
|
|
752
|
+
damageMultiplier,
|
|
753
|
+
typeEffectiveness,
|
|
754
|
+
applyBurn,
|
|
755
|
+
stabMod,
|
|
756
|
+
newFinalMod,
|
|
757
|
+
protect
|
|
758
|
+
);
|
|
759
|
+
damageMultiplier++;
|
|
760
|
+
return affectedAmount + newFinalDamage;
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
desc.defenseBoost = origDefBoost;
|
|
764
|
+
desc.attackBoost = origAtkBoost;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
result.damage = childDamage ? [damage, childDamage] : damage;
|
|
768
|
+
|
|
769
|
+
// #endregion
|
|
770
|
+
|
|
771
|
+
return result;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
export function calculateBasePowerSMSSSV(
|
|
775
|
+
gen: Generation,
|
|
776
|
+
attacker: Pokemon,
|
|
777
|
+
defender: Pokemon,
|
|
778
|
+
move: Move,
|
|
779
|
+
field: Field,
|
|
780
|
+
hasAteAbilityTypeChange: boolean,
|
|
781
|
+
desc: RawDesc,
|
|
782
|
+
hit = 1,
|
|
783
|
+
) {
|
|
784
|
+
const turnOrder = attacker.stats.spe > defender.stats.spe ? 'first' : 'last';
|
|
785
|
+
|
|
786
|
+
let basePower: number;
|
|
787
|
+
|
|
788
|
+
switch (move.name) {
|
|
789
|
+
case 'Payback':
|
|
790
|
+
basePower = move.bp * (turnOrder === 'last' ? 2 : 1);
|
|
791
|
+
desc.moveBP = basePower;
|
|
792
|
+
break;
|
|
793
|
+
case 'Bolt Beak':
|
|
794
|
+
case 'Fishious Rend':
|
|
795
|
+
basePower = move.bp * (turnOrder !== 'last' ? 2 : 1);
|
|
796
|
+
desc.moveBP = basePower;
|
|
797
|
+
break;
|
|
798
|
+
case 'Pursuit':
|
|
799
|
+
const switching = field.defenderSide.isSwitching === 'out';
|
|
800
|
+
basePower = move.bp * (switching ? 2 : 1);
|
|
801
|
+
if (switching) desc.isSwitching = 'out';
|
|
802
|
+
desc.moveBP = basePower;
|
|
803
|
+
break;
|
|
804
|
+
case 'Electro Ball':
|
|
805
|
+
const r = Math.floor(attacker.stats.spe / defender.stats.spe);
|
|
806
|
+
basePower = r >= 4 ? 150 : r >= 3 ? 120 : r >= 2 ? 80 : r >= 1 ? 60 : 40;
|
|
807
|
+
if (defender.stats.spe === 0) basePower = 40;
|
|
808
|
+
desc.moveBP = basePower;
|
|
809
|
+
break;
|
|
810
|
+
case 'Gyro Ball':
|
|
811
|
+
basePower = Math.min(150, Math.floor((25 * defender.stats.spe) / attacker.stats.spe) + 1);
|
|
812
|
+
if (attacker.stats.spe === 0) basePower = 1;
|
|
813
|
+
desc.moveBP = basePower;
|
|
814
|
+
break;
|
|
815
|
+
case 'Punishment':
|
|
816
|
+
basePower = Math.min(200, 60 + 20 * countBoosts(gen, defender.boosts));
|
|
817
|
+
desc.moveBP = basePower;
|
|
818
|
+
break;
|
|
819
|
+
case 'Low Kick':
|
|
820
|
+
case 'Grass Knot':
|
|
821
|
+
const w = getWeight(defender, desc, 'defender');
|
|
822
|
+
basePower = w >= 200 ? 120 : w >= 100 ? 100 : w >= 50 ? 80 : w >= 25 ? 60 : w >= 10 ? 40 : 20;
|
|
823
|
+
desc.moveBP = basePower;
|
|
824
|
+
break;
|
|
825
|
+
case 'Hex':
|
|
826
|
+
case 'Infernal Parade':
|
|
827
|
+
// Hex deals double damage to Pokemon with Comatose (ih8ih8sn0w)
|
|
828
|
+
basePower = move.bp * (defender.status || defender.hasAbility('Comatose') ? 2 : 1);
|
|
829
|
+
desc.moveBP = basePower;
|
|
830
|
+
break;
|
|
831
|
+
case 'Barb Barrage':
|
|
832
|
+
basePower = move.bp * (defender.hasStatus('psn', 'tox') ? 2 : 1);
|
|
833
|
+
desc.moveBP = basePower;
|
|
834
|
+
break;
|
|
835
|
+
case 'Heavy Slam':
|
|
836
|
+
case 'Heat Crash':
|
|
837
|
+
const wr =
|
|
838
|
+
getWeight(attacker, desc, 'attacker') /
|
|
839
|
+
getWeight(defender, desc, 'defender');
|
|
840
|
+
basePower = wr >= 5 ? 120 : wr >= 4 ? 100 : wr >= 3 ? 80 : wr >= 2 ? 60 : 40;
|
|
841
|
+
desc.moveBP = basePower;
|
|
842
|
+
break;
|
|
843
|
+
case 'Stored Power':
|
|
844
|
+
case 'Power Trip':
|
|
845
|
+
basePower = 20 + 20 * countBoosts(gen, attacker.boosts);
|
|
846
|
+
desc.moveBP = basePower;
|
|
847
|
+
break;
|
|
848
|
+
case 'Acrobatics':
|
|
849
|
+
basePower = move.bp * (attacker.hasItem('Flying Gem') ||
|
|
850
|
+
(!attacker.item || isQPActive(attacker, field)) ? 2 : 1);
|
|
851
|
+
desc.moveBP = basePower;
|
|
852
|
+
break;
|
|
853
|
+
case 'Assurance':
|
|
854
|
+
basePower = move.bp * (defender.hasAbility('Parental Bond (Child)') ? 2 : 1);
|
|
855
|
+
// NOTE: desc.attackerAbility = 'Parental Bond' will already reflect this boost
|
|
856
|
+
break;
|
|
857
|
+
case 'Wake-Up Slap':
|
|
858
|
+
// Wake-Up Slap deals double damage to Pokemon with Comatose (ih8ih8sn0w)
|
|
859
|
+
basePower = move.bp * (defender.hasStatus('slp') || defender.hasAbility('Comatose') ? 2 : 1);
|
|
860
|
+
desc.moveBP = basePower;
|
|
861
|
+
break;
|
|
862
|
+
case 'Smelling Salts':
|
|
863
|
+
basePower = move.bp * (defender.hasStatus('par') ? 2 : 1);
|
|
864
|
+
desc.moveBP = basePower;
|
|
865
|
+
break;
|
|
866
|
+
case 'Weather Ball':
|
|
867
|
+
basePower = move.bp * (field.weather && !field.hasWeather('Strong Winds') ? 2 : 1);
|
|
868
|
+
if (field.hasWeather('Sun', 'Harsh Sunshine', 'Rain', 'Heavy Rain') &&
|
|
869
|
+
attacker.hasItem('Utility Umbrella')) basePower = move.bp;
|
|
870
|
+
desc.moveBP = basePower;
|
|
871
|
+
break;
|
|
872
|
+
case 'Terrain Pulse':
|
|
873
|
+
basePower = move.bp * (isGrounded(attacker, field) && field.terrain ? 2 : 1);
|
|
874
|
+
desc.moveBP = basePower;
|
|
875
|
+
break;
|
|
876
|
+
case 'Rising Voltage':
|
|
877
|
+
basePower = move.bp * ((isGrounded(defender, field) && field.hasTerrain('Electric')) ? 2 : 1);
|
|
878
|
+
desc.moveBP = basePower;
|
|
879
|
+
break;
|
|
880
|
+
case 'Psyblade':
|
|
881
|
+
basePower = move.bp * (field.hasTerrain('Electric') ? 1.5 : 1);
|
|
882
|
+
if (field.hasTerrain('Electric')) {
|
|
883
|
+
desc.moveBP = basePower;
|
|
884
|
+
desc.terrain = field.terrain;
|
|
885
|
+
}
|
|
886
|
+
break;
|
|
887
|
+
case 'Fling':
|
|
888
|
+
basePower = getFlingPower(attacker.item);
|
|
889
|
+
desc.moveBP = basePower;
|
|
890
|
+
desc.attackerItem = attacker.item;
|
|
891
|
+
break;
|
|
892
|
+
case 'Dragon Energy':
|
|
893
|
+
case 'Eruption':
|
|
894
|
+
case 'Water Spout':
|
|
895
|
+
basePower = Math.max(1, Math.floor((150 * attacker.curHP()) / attacker.maxHP()));
|
|
896
|
+
desc.moveBP = basePower;
|
|
897
|
+
break;
|
|
898
|
+
case 'Flail':
|
|
899
|
+
case 'Reversal':
|
|
900
|
+
const p = Math.floor((48 * attacker.curHP()) / attacker.maxHP());
|
|
901
|
+
basePower = p <= 1 ? 200 : p <= 4 ? 150 : p <= 9 ? 100 : p <= 16 ? 80 : p <= 32 ? 40 : 20;
|
|
902
|
+
desc.moveBP = basePower;
|
|
903
|
+
break;
|
|
904
|
+
case 'Natural Gift':
|
|
905
|
+
if (attacker.item?.endsWith('Berry')) {
|
|
906
|
+
const gift = getNaturalGift(gen, attacker.item)!;
|
|
907
|
+
basePower = gift.p;
|
|
908
|
+
desc.attackerItem = attacker.item;
|
|
909
|
+
desc.moveBP = move.bp;
|
|
910
|
+
} else {
|
|
911
|
+
basePower = move.bp;
|
|
912
|
+
}
|
|
913
|
+
break;
|
|
914
|
+
case 'Nature Power':
|
|
915
|
+
move.category = 'Special';
|
|
916
|
+
move.secondaries = true;
|
|
917
|
+
|
|
918
|
+
// Nature Power cannot affect Dark-types if it is affected by Prankster
|
|
919
|
+
if (attacker.hasAbility('Prankster') && defender.types.includes('Dark')) {
|
|
920
|
+
basePower = 0;
|
|
921
|
+
desc.moveName = 'Nature Power';
|
|
922
|
+
desc.attackerAbility = 'Prankster';
|
|
923
|
+
break;
|
|
924
|
+
}
|
|
925
|
+
switch (field.terrain) {
|
|
926
|
+
case 'Electric':
|
|
927
|
+
basePower = 90;
|
|
928
|
+
desc.moveName = 'Thunderbolt';
|
|
929
|
+
break;
|
|
930
|
+
case 'Grassy':
|
|
931
|
+
basePower = 90;
|
|
932
|
+
desc.moveName = 'Energy Ball';
|
|
933
|
+
break;
|
|
934
|
+
case 'Misty':
|
|
935
|
+
basePower = 95;
|
|
936
|
+
desc.moveName = 'Moonblast';
|
|
937
|
+
break;
|
|
938
|
+
case 'Psychic':
|
|
939
|
+
// Nature Power does not affect grounded Pokemon if it is affected by
|
|
940
|
+
// Prankster and there is Psychic Terrain active
|
|
941
|
+
if (attacker.hasAbility('Prankster') && isGrounded(defender, field)) {
|
|
942
|
+
basePower = 0;
|
|
943
|
+
desc.attackerAbility = 'Prankster';
|
|
944
|
+
} else {
|
|
945
|
+
basePower = 90;
|
|
946
|
+
desc.moveName = 'Psychic';
|
|
947
|
+
}
|
|
948
|
+
break;
|
|
949
|
+
default:
|
|
950
|
+
basePower = 80;
|
|
951
|
+
desc.moveName = 'Tri Attack';
|
|
952
|
+
}
|
|
953
|
+
break;
|
|
954
|
+
case 'Water Shuriken':
|
|
955
|
+
basePower = attacker.named('Greninja-Ash') && attacker.hasAbility('Battle Bond') ? 20 : 15;
|
|
956
|
+
desc.moveBP = basePower;
|
|
957
|
+
break;
|
|
958
|
+
// Triple Axel's damage increases after each consecutive hit (20, 40, 60)
|
|
959
|
+
case 'Triple Axel':
|
|
960
|
+
basePower = hit * 20;
|
|
961
|
+
desc.moveBP = move.hits === 2 ? 60 : move.hits === 3 ? 120 : 20;
|
|
962
|
+
break;
|
|
963
|
+
// Triple Kick's damage increases after each consecutive hit (10, 20, 30)
|
|
964
|
+
case 'Triple Kick':
|
|
965
|
+
basePower = hit * 10;
|
|
966
|
+
desc.moveBP = move.hits === 2 ? 30 : move.hits === 3 ? 60 : 10;
|
|
967
|
+
break;
|
|
968
|
+
case 'Crush Grip':
|
|
969
|
+
case 'Wring Out':
|
|
970
|
+
basePower = 100 * Math.floor((defender.curHP() * 4096) / defender.maxHP());
|
|
971
|
+
basePower = Math.floor(Math.floor((120 * basePower + 2048 - 1) / 4096) / 100) || 1;
|
|
972
|
+
desc.moveBP = basePower;
|
|
973
|
+
break;
|
|
974
|
+
case 'Hard Press':
|
|
975
|
+
basePower = 100 * Math.floor((defender.curHP() * 4096) / defender.maxHP());
|
|
976
|
+
basePower = Math.floor(Math.floor((100 * basePower + 2048 - 1) / 4096) / 100) || 1;
|
|
977
|
+
desc.moveBP = basePower;
|
|
978
|
+
break;
|
|
979
|
+
case 'Tera Blast':
|
|
980
|
+
basePower = attacker.teraType === 'Stellar' ? 100 : 80;
|
|
981
|
+
desc.moveBP = basePower;
|
|
982
|
+
break;
|
|
983
|
+
default:
|
|
984
|
+
basePower = move.bp;
|
|
985
|
+
}
|
|
986
|
+
if (basePower === 0) {
|
|
987
|
+
return 0;
|
|
988
|
+
}
|
|
989
|
+
if (move.named(
|
|
990
|
+
'Breakneck Blitz', 'Bloom Doom', 'Inferno Overdrive', 'Hydro Vortex', 'Gigavolt Havoc',
|
|
991
|
+
'Subzero Slammer', 'Supersonic Skystrike', 'Savage Spin-Out', 'Acid Downpour', 'Tectonic Rage',
|
|
992
|
+
'Continental Crush', 'All-Out Pummeling', 'Shattered Psyche', 'Never-Ending Nightmare',
|
|
993
|
+
'Devastating Drake', 'Black Hole Eclipse', 'Corkscrew Crash', 'Twinkle Tackle'
|
|
994
|
+
) || move.isMax) {
|
|
995
|
+
// show z-move power in description
|
|
996
|
+
desc.moveBP = move.bp;
|
|
997
|
+
}
|
|
998
|
+
const bpMods = calculateBPModsSMSSSV(
|
|
999
|
+
gen,
|
|
1000
|
+
attacker,
|
|
1001
|
+
defender,
|
|
1002
|
+
move,
|
|
1003
|
+
field,
|
|
1004
|
+
desc,
|
|
1005
|
+
basePower,
|
|
1006
|
+
hasAteAbilityTypeChange,
|
|
1007
|
+
turnOrder
|
|
1008
|
+
);
|
|
1009
|
+
basePower = OF16(Math.max(1, pokeRound((basePower * chainMods(bpMods, 41, 2097152)) / 4096)));
|
|
1010
|
+
if (
|
|
1011
|
+
attacker.teraType && move.type === attacker.teraType &&
|
|
1012
|
+
attacker.hasType(attacker.teraType) && move.hits === 1 && !move.multiaccuracy &&
|
|
1013
|
+
move.priority <= 0 && move.bp > 0 && !move.named('Dragon Energy', 'Eruption', 'Water Spout') &&
|
|
1014
|
+
basePower < 60 && gen.num >= 9
|
|
1015
|
+
) {
|
|
1016
|
+
basePower = 60;
|
|
1017
|
+
desc.moveBP = 60;
|
|
1018
|
+
}
|
|
1019
|
+
return basePower;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
export function calculateBPModsSMSSSV(
|
|
1023
|
+
gen: Generation,
|
|
1024
|
+
attacker: Pokemon,
|
|
1025
|
+
defender: Pokemon,
|
|
1026
|
+
move: Move,
|
|
1027
|
+
field: Field,
|
|
1028
|
+
desc: RawDesc,
|
|
1029
|
+
basePower: number,
|
|
1030
|
+
hasAteAbilityTypeChange: boolean,
|
|
1031
|
+
turnOrder: string
|
|
1032
|
+
) {
|
|
1033
|
+
const bpMods = [];
|
|
1034
|
+
|
|
1035
|
+
// Move effects
|
|
1036
|
+
const defenderItem = (defender.item && defender.item !== '')
|
|
1037
|
+
? defender.item : defender.disabledItem;
|
|
1038
|
+
let resistedKnockOffDamage =
|
|
1039
|
+
(!defenderItem || isQPActive(defender, field)) ||
|
|
1040
|
+
(defender.named('Dialga-Origin') && defenderItem === 'Adamant Crystal') ||
|
|
1041
|
+
(defender.named('Palkia-Origin') && defenderItem === 'Lustrous Globe') ||
|
|
1042
|
+
// Griseous Core for gen 9, Griseous Orb otherwise
|
|
1043
|
+
(defender.name.includes('Giratina-Origin') && defenderItem.includes('Griseous')) ||
|
|
1044
|
+
(defender.name.includes('Arceus') && defenderItem.includes('Plate')) ||
|
|
1045
|
+
(defender.name.includes('Genesect') && defenderItem.includes('Drive')) ||
|
|
1046
|
+
(defender.named('Groudon', 'Groudon-Primal') && defenderItem === 'Red Orb') ||
|
|
1047
|
+
(defender.named('Kyogre', 'Kyogre-Primal') && defenderItem === 'Blue Orb') ||
|
|
1048
|
+
(defender.name.includes('Silvally') && defenderItem.includes('Memory')) ||
|
|
1049
|
+
defenderItem.includes(' Z') ||
|
|
1050
|
+
(defender.named('Zacian') && defenderItem === 'Rusted Sword') ||
|
|
1051
|
+
(defender.named('Zamazenta') && defenderItem === 'Rusted Shield') ||
|
|
1052
|
+
(defender.name.includes('Ogerpon-Cornerstone') && defenderItem === 'Cornerstone Mask') ||
|
|
1053
|
+
(defender.name.includes('Ogerpon-Hearthflame') && defenderItem === 'Hearthflame Mask') ||
|
|
1054
|
+
(defender.name.includes('Ogerpon-Wellspring') && defenderItem === 'Wellspring Mask') ||
|
|
1055
|
+
(defender.named('Venomicon-Epilogue') && defenderItem === 'Vile Vial');
|
|
1056
|
+
|
|
1057
|
+
// The last case only applies when the Pokemon has the Mega Stone that matches its species
|
|
1058
|
+
// (or when it's already a Mega-Evolution)
|
|
1059
|
+
if (!resistedKnockOffDamage && defenderItem) {
|
|
1060
|
+
const item = gen.items.get(toID(defenderItem))!;
|
|
1061
|
+
resistedKnockOffDamage = !!item.megaEvolves && defender.name.includes(item.megaEvolves);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
if ((move.named('Facade') && attacker.hasStatus('brn', 'par', 'psn', 'tox')) ||
|
|
1065
|
+
(move.named('Brine') && defender.curHP() <= defender.maxHP() / 2) ||
|
|
1066
|
+
(move.named('Venoshock') && defender.hasStatus('psn', 'tox')) ||
|
|
1067
|
+
(move.named('Lash Out') && (countBoosts(gen, attacker.boosts) < 0))
|
|
1068
|
+
) {
|
|
1069
|
+
bpMods.push(8192);
|
|
1070
|
+
desc.moveBP = basePower * 2;
|
|
1071
|
+
} else if (
|
|
1072
|
+
move.named('Expanding Force') && isGrounded(attacker, field) && field.hasTerrain('Psychic')
|
|
1073
|
+
) {
|
|
1074
|
+
move.target = 'allAdjacentFoes';
|
|
1075
|
+
bpMods.push(6144);
|
|
1076
|
+
desc.moveBP = basePower * 1.5;
|
|
1077
|
+
} else if ((move.named('Knock Off') && !resistedKnockOffDamage) ||
|
|
1078
|
+
(move.named('Misty Explosion') && isGrounded(attacker, field) && field.hasTerrain('Misty')) ||
|
|
1079
|
+
(move.named('Grav Apple') && field.isGravity)
|
|
1080
|
+
) {
|
|
1081
|
+
bpMods.push(6144);
|
|
1082
|
+
desc.moveBP = basePower * 1.5;
|
|
1083
|
+
} else if (move.named('Solar Beam', 'Solar Blade') &&
|
|
1084
|
+
field.hasWeather('Rain', 'Heavy Rain', 'Sand', 'Hail', 'Snow')) {
|
|
1085
|
+
bpMods.push(2048);
|
|
1086
|
+
desc.moveBP = basePower / 2;
|
|
1087
|
+
desc.weather = field.weather;
|
|
1088
|
+
} else if (move.named('Collision Course', 'Electro Drift')) {
|
|
1089
|
+
const isGhostRevealed =
|
|
1090
|
+
attacker.hasAbility('Scrappy') || attacker.hasAbility('Mind\'s Eye') ||
|
|
1091
|
+
field.defenderSide.isForesight;
|
|
1092
|
+
const isRingTarget =
|
|
1093
|
+
defender.hasItem('Ring Target') && !defender.hasAbility('Klutz');
|
|
1094
|
+
const types = defender.teraType && defender.teraType !== 'Stellar'
|
|
1095
|
+
? [defender.teraType] : defender.types;
|
|
1096
|
+
const type1Effectiveness = getMoveEffectiveness(
|
|
1097
|
+
gen,
|
|
1098
|
+
move,
|
|
1099
|
+
types[0],
|
|
1100
|
+
isGhostRevealed,
|
|
1101
|
+
field.isGravity,
|
|
1102
|
+
isRingTarget
|
|
1103
|
+
);
|
|
1104
|
+
const type2Effectiveness = types[1] ? getMoveEffectiveness(
|
|
1105
|
+
gen,
|
|
1106
|
+
move,
|
|
1107
|
+
types[1],
|
|
1108
|
+
isGhostRevealed,
|
|
1109
|
+
field.isGravity,
|
|
1110
|
+
isRingTarget
|
|
1111
|
+
) : 1;
|
|
1112
|
+
if (type1Effectiveness * type2Effectiveness >= 2) {
|
|
1113
|
+
bpMods.push(5461);
|
|
1114
|
+
desc.moveBP = basePower * (5461 / 4096);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
if (field.attackerSide.isHelpingHand) {
|
|
1119
|
+
bpMods.push(6144);
|
|
1120
|
+
desc.isHelpingHand = true;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// Field effects
|
|
1124
|
+
|
|
1125
|
+
const terrainMultiplier = gen.num > 7 ? 5325 : 6144;
|
|
1126
|
+
if (isGrounded(attacker, field)) {
|
|
1127
|
+
if ((field.hasTerrain('Electric') && move.hasType('Electric')) ||
|
|
1128
|
+
(field.hasTerrain('Grassy') && move.hasType('Grass')) ||
|
|
1129
|
+
(field.hasTerrain('Psychic') && move.hasType('Psychic'))
|
|
1130
|
+
) {
|
|
1131
|
+
bpMods.push(terrainMultiplier);
|
|
1132
|
+
desc.terrain = field.terrain;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
if (isGrounded(defender, field)) {
|
|
1136
|
+
if ((field.hasTerrain('Misty') && move.hasType('Dragon')) ||
|
|
1137
|
+
(field.hasTerrain('Grassy') && move.named('Bulldoze', 'Earthquake'))
|
|
1138
|
+
) {
|
|
1139
|
+
bpMods.push(2048);
|
|
1140
|
+
desc.terrain = field.terrain;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// Abilities
|
|
1145
|
+
|
|
1146
|
+
// Use BasePower after moves with custom BP to determine if Technician should boost
|
|
1147
|
+
if ((attacker.hasAbility('Technician') && basePower <= 60) ||
|
|
1148
|
+
(attacker.hasAbility('Flare Boost') &&
|
|
1149
|
+
attacker.hasStatus('brn') && move.category === 'Special') ||
|
|
1150
|
+
(attacker.hasAbility('Toxic Boost') &&
|
|
1151
|
+
attacker.hasStatus('psn', 'tox') && move.category === 'Physical') ||
|
|
1152
|
+
(attacker.hasAbility('Mega Launcher') && move.flags.pulse) ||
|
|
1153
|
+
(attacker.hasAbility('Strong Jaw') && move.flags.bite) ||
|
|
1154
|
+
(attacker.hasAbility('Steely Spirit') && move.hasType('Steel')) ||
|
|
1155
|
+
(attacker.hasAbility('Sharpness') && move.flags.slicing)
|
|
1156
|
+
) {
|
|
1157
|
+
bpMods.push(6144);
|
|
1158
|
+
desc.attackerAbility = attacker.ability;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
const aura = `${move.type} Aura`;
|
|
1162
|
+
const isAttackerAura = attacker.hasAbility(aura);
|
|
1163
|
+
const isDefenderAura = defender.hasAbility(aura);
|
|
1164
|
+
const isUserAuraBreak = attacker.hasAbility('Aura Break') || defender.hasAbility('Aura Break');
|
|
1165
|
+
const isFieldAuraBreak = field.isAuraBreak;
|
|
1166
|
+
const isFieldFairyAura = field.isFairyAura && move.type === 'Fairy';
|
|
1167
|
+
const isFieldDarkAura = field.isDarkAura && move.type === 'Dark';
|
|
1168
|
+
const auraActive = isAttackerAura || isDefenderAura || isFieldFairyAura || isFieldDarkAura;
|
|
1169
|
+
const auraBreak = isFieldAuraBreak || isUserAuraBreak;
|
|
1170
|
+
if (auraActive) {
|
|
1171
|
+
if (auraBreak) {
|
|
1172
|
+
bpMods.push(3072);
|
|
1173
|
+
desc.attackerAbility = attacker.ability;
|
|
1174
|
+
desc.defenderAbility = defender.ability;
|
|
1175
|
+
} else {
|
|
1176
|
+
bpMods.push(5448);
|
|
1177
|
+
if (isAttackerAura) desc.attackerAbility = attacker.ability;
|
|
1178
|
+
if (isDefenderAura) desc.defenderAbility = defender.ability;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// Sheer Force does not power up max moves or remove the effects (SadisticMystic)
|
|
1183
|
+
if (
|
|
1184
|
+
(attacker.hasAbility('Sheer Force') &&
|
|
1185
|
+
(move.secondaries || move.named('Order Up')) && !move.isMax) ||
|
|
1186
|
+
(attacker.hasAbility('Sand Force') &&
|
|
1187
|
+
field.hasWeather('Sand') && move.hasType('Rock', 'Ground', 'Steel')) ||
|
|
1188
|
+
(attacker.hasAbility('Analytic') &&
|
|
1189
|
+
(turnOrder !== 'first' || field.defenderSide.isSwitching === 'out')) ||
|
|
1190
|
+
(attacker.hasAbility('Tough Claws') && move.flags.contact) ||
|
|
1191
|
+
(attacker.hasAbility('Punk Rock') && move.flags.sound)
|
|
1192
|
+
) {
|
|
1193
|
+
bpMods.push(5325);
|
|
1194
|
+
desc.attackerAbility = attacker.ability;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
if (field.attackerSide.isBattery && move.category === 'Special') {
|
|
1198
|
+
bpMods.push(5325);
|
|
1199
|
+
desc.isBattery = true;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
if (field.attackerSide.isPowerSpot) {
|
|
1203
|
+
bpMods.push(5325);
|
|
1204
|
+
desc.isPowerSpot = true;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
if (attacker.hasAbility('Rivalry') && ![attacker.gender, defender.gender].includes('N')) {
|
|
1208
|
+
if (attacker.gender === defender.gender) {
|
|
1209
|
+
bpMods.push(5120);
|
|
1210
|
+
desc.rivalry = 'buffed';
|
|
1211
|
+
} else {
|
|
1212
|
+
bpMods.push(3072);
|
|
1213
|
+
desc.rivalry = 'nerfed';
|
|
1214
|
+
}
|
|
1215
|
+
desc.attackerAbility = attacker.ability;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// The -ate abilities already changed move typing earlier, so most checks are done and desc is set
|
|
1219
|
+
// However, Max Moves also don't boost -ate Abilities
|
|
1220
|
+
if (!move.isMax && hasAteAbilityTypeChange) {
|
|
1221
|
+
bpMods.push(4915);
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
if ((attacker.hasAbility('Reckless') && (move.recoil || move.hasCrashDamage)) ||
|
|
1225
|
+
(attacker.hasAbility('Iron Fist') && move.flags.punch)
|
|
1226
|
+
) {
|
|
1227
|
+
bpMods.push(4915);
|
|
1228
|
+
desc.attackerAbility = attacker.ability;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
if (gen.num <= 8 && defender.hasAbility('Heatproof') && move.hasType('Fire')) {
|
|
1232
|
+
bpMods.push(2048);
|
|
1233
|
+
desc.defenderAbility = defender.ability;
|
|
1234
|
+
} else if (defender.hasAbility('Dry Skin') && move.hasType('Fire')) {
|
|
1235
|
+
bpMods.push(5120);
|
|
1236
|
+
desc.defenderAbility = defender.ability;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
if (attacker.hasAbility('Supreme Overlord') && attacker.alliesFainted) {
|
|
1240
|
+
const powMod = [4096, 4506, 4915, 5325, 5734, 6144];
|
|
1241
|
+
bpMods.push(powMod[Math.min(5, attacker.alliesFainted)]);
|
|
1242
|
+
desc.attackerAbility = attacker.ability;
|
|
1243
|
+
desc.alliesFainted = attacker.alliesFainted;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// Items
|
|
1247
|
+
|
|
1248
|
+
if (attacker.hasItem(`${move.type} Gem`)) {
|
|
1249
|
+
bpMods.push(5325);
|
|
1250
|
+
desc.attackerItem = attacker.item;
|
|
1251
|
+
} else if (
|
|
1252
|
+
(((attacker.hasItem('Adamant Crystal') && attacker.named('Dialga-Origin')) ||
|
|
1253
|
+
(attacker.hasItem('Adamant Orb') && attacker.named('Dialga'))) &&
|
|
1254
|
+
move.hasType('Steel', 'Dragon')) ||
|
|
1255
|
+
(((attacker.hasItem('Lustrous Orb') &&
|
|
1256
|
+
attacker.named('Palkia')) ||
|
|
1257
|
+
(attacker.hasItem('Lustrous Globe') && attacker.named('Palkia-Origin'))) &&
|
|
1258
|
+
move.hasType('Water', 'Dragon')) ||
|
|
1259
|
+
(((attacker.hasItem('Griseous Orb') || attacker.hasItem('Griseous Core')) &&
|
|
1260
|
+
(attacker.named('Giratina-Origin') || attacker.named('Giratina'))) &&
|
|
1261
|
+
move.hasType('Ghost', 'Dragon')) ||
|
|
1262
|
+
(attacker.hasItem('Vile Vial') &&
|
|
1263
|
+
attacker.named('Venomicon-Epilogue') &&
|
|
1264
|
+
move.hasType('Poison', 'Flying')) ||
|
|
1265
|
+
(attacker.hasItem('Soul Dew') &&
|
|
1266
|
+
attacker.named('Latios', 'Latias', 'Latios-Mega', 'Latias-Mega') &&
|
|
1267
|
+
move.hasType('Psychic', 'Dragon')) ||
|
|
1268
|
+
attacker.item && move.hasType(getItemBoostType(attacker.item)) ||
|
|
1269
|
+
(attacker.name.includes('Ogerpon-Cornerstone') && attacker.hasItem('Cornerstone Mask')) ||
|
|
1270
|
+
(attacker.name.includes('Ogerpon-Hearthflame') && attacker.hasItem('Hearthflame Mask')) ||
|
|
1271
|
+
(attacker.name.includes('Ogerpon-Wellspring') && attacker.hasItem('Wellspring Mask'))
|
|
1272
|
+
) {
|
|
1273
|
+
bpMods.push(4915);
|
|
1274
|
+
desc.attackerItem = attacker.item;
|
|
1275
|
+
} else if (
|
|
1276
|
+
(attacker.hasItem('Muscle Band') && move.category === 'Physical') ||
|
|
1277
|
+
(attacker.hasItem('Wise Glasses') && move.category === 'Special')
|
|
1278
|
+
) {
|
|
1279
|
+
bpMods.push(4505);
|
|
1280
|
+
desc.attackerItem = attacker.item;
|
|
1281
|
+
} else if (attacker.hasItem('Punching Glove') && move.flags.punch) {
|
|
1282
|
+
bpMods.push(4506);
|
|
1283
|
+
}
|
|
1284
|
+
return bpMods;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
export function calculateAttackSMSSSV(
|
|
1288
|
+
gen: Generation,
|
|
1289
|
+
attacker: Pokemon,
|
|
1290
|
+
defender: Pokemon,
|
|
1291
|
+
move: Move,
|
|
1292
|
+
field: Field,
|
|
1293
|
+
desc: RawDesc,
|
|
1294
|
+
isCritical = false
|
|
1295
|
+
) {
|
|
1296
|
+
let attack: number;
|
|
1297
|
+
const attackStat =
|
|
1298
|
+
move.named('Shell Side Arm') &&
|
|
1299
|
+
getShellSideArmCategory(attacker, defender) === 'Physical'
|
|
1300
|
+
? 'atk'
|
|
1301
|
+
: move.named('Body Press')
|
|
1302
|
+
? 'def'
|
|
1303
|
+
: move.category === 'Special'
|
|
1304
|
+
? 'spa'
|
|
1305
|
+
: 'atk';
|
|
1306
|
+
desc.attackEVs =
|
|
1307
|
+
move.named('Foul Play')
|
|
1308
|
+
? getStatDescriptionText(gen, defender, attackStat, defender.nature)
|
|
1309
|
+
: getStatDescriptionText(gen, attacker, attackStat, attacker.nature);
|
|
1310
|
+
const attackSource = move.named('Foul Play') ? defender : attacker;
|
|
1311
|
+
if (attackSource.boosts[attackStat] === 0 ||
|
|
1312
|
+
(isCritical && attackSource.boosts[attackStat] < 0)) {
|
|
1313
|
+
attack = attackSource.rawStats[attackStat];
|
|
1314
|
+
} else if (defender.hasAbility('Unaware')) {
|
|
1315
|
+
attack = attackSource.rawStats[attackStat];
|
|
1316
|
+
desc.defenderAbility = defender.ability;
|
|
1317
|
+
} else {
|
|
1318
|
+
attack = getModifiedStat(attackSource.rawStats[attackStat]!, attackSource.boosts[attackStat]!);
|
|
1319
|
+
desc.attackBoost = attackSource.boosts[attackStat];
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// unlike all other attack modifiers, Hustle gets applied directly
|
|
1323
|
+
if (attacker.hasAbility('Hustle') && move.category === 'Physical') {
|
|
1324
|
+
attack = pokeRound((attack * 3) / 2);
|
|
1325
|
+
desc.attackerAbility = attacker.ability;
|
|
1326
|
+
}
|
|
1327
|
+
const atMods = calculateAtModsSMSSSV(gen, attacker, defender, move, field, desc);
|
|
1328
|
+
attack = OF16(Math.max(1, pokeRound((attack * chainMods(atMods, 410, 131072)) / 4096)));
|
|
1329
|
+
return attack;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
export function calculateAtModsSMSSSV(
|
|
1333
|
+
gen: Generation,
|
|
1334
|
+
attacker: Pokemon,
|
|
1335
|
+
defender: Pokemon,
|
|
1336
|
+
move: Move,
|
|
1337
|
+
field: Field,
|
|
1338
|
+
desc: RawDesc
|
|
1339
|
+
) {
|
|
1340
|
+
const atMods = [];
|
|
1341
|
+
|
|
1342
|
+
// Slow Start also halves damage with special Z-moves
|
|
1343
|
+
if ((attacker.hasAbility('Slow Start') && attacker.abilityOn &&
|
|
1344
|
+
(move.category === 'Physical' || (move.category === 'Special' && move.isZ))) ||
|
|
1345
|
+
(attacker.hasAbility('Defeatist') && attacker.curHP() <= attacker.maxHP() / 2)
|
|
1346
|
+
) {
|
|
1347
|
+
atMods.push(2048);
|
|
1348
|
+
desc.attackerAbility = attacker.ability;
|
|
1349
|
+
} else if (
|
|
1350
|
+
(attacker.hasAbility('Solar Power') &&
|
|
1351
|
+
field.hasWeather('Sun', 'Harsh Sunshine') &&
|
|
1352
|
+
move.category === 'Special') ||
|
|
1353
|
+
(attacker.named('Cherrim') &&
|
|
1354
|
+
attacker.hasAbility('Flower Gift') &&
|
|
1355
|
+
field.hasWeather('Sun', 'Harsh Sunshine') &&
|
|
1356
|
+
move.category === 'Physical')) {
|
|
1357
|
+
atMods.push(6144);
|
|
1358
|
+
desc.attackerAbility = attacker.ability;
|
|
1359
|
+
desc.weather = field.weather;
|
|
1360
|
+
} else if (
|
|
1361
|
+
// Gorilla Tactics has no effect during Dynamax (Anubis)
|
|
1362
|
+
(attacker.hasAbility('Gorilla Tactics') && move.category === 'Physical' &&
|
|
1363
|
+
!attacker.isDynamaxed)) {
|
|
1364
|
+
atMods.push(6144);
|
|
1365
|
+
desc.attackerAbility = attacker.ability;
|
|
1366
|
+
} else if (
|
|
1367
|
+
(attacker.hasAbility('Guts') && attacker.status && move.category === 'Physical') ||
|
|
1368
|
+
(attacker.curHP() <= attacker.maxHP() / 3 &&
|
|
1369
|
+
((attacker.hasAbility('Overgrow') && move.hasType('Grass')) ||
|
|
1370
|
+
(attacker.hasAbility('Blaze') && move.hasType('Fire')) ||
|
|
1371
|
+
(attacker.hasAbility('Torrent') && move.hasType('Water')) ||
|
|
1372
|
+
(attacker.hasAbility('Swarm') && move.hasType('Bug')))) ||
|
|
1373
|
+
(move.category === 'Special' && attacker.abilityOn && attacker.hasAbility('Plus', 'Minus'))
|
|
1374
|
+
) {
|
|
1375
|
+
atMods.push(6144);
|
|
1376
|
+
desc.attackerAbility = attacker.ability;
|
|
1377
|
+
} else if (attacker.hasAbility('Flash Fire') && attacker.abilityOn && move.hasType('Fire')) {
|
|
1378
|
+
atMods.push(6144);
|
|
1379
|
+
desc.attackerAbility = 'Flash Fire';
|
|
1380
|
+
} else if (
|
|
1381
|
+
(attacker.hasAbility('Steelworker') && move.hasType('Steel')) ||
|
|
1382
|
+
(attacker.hasAbility('Dragon\'s Maw') && move.hasType('Dragon')) ||
|
|
1383
|
+
(attacker.hasAbility('Rocky Payload') && move.hasType('Rock'))
|
|
1384
|
+
) {
|
|
1385
|
+
atMods.push(6144);
|
|
1386
|
+
desc.attackerAbility = attacker.ability;
|
|
1387
|
+
} else if (attacker.hasAbility('Transistor') && move.hasType('Electric')) {
|
|
1388
|
+
atMods.push(gen.num >= 9 ? 5325 : 6144);
|
|
1389
|
+
desc.attackerAbility = attacker.ability;
|
|
1390
|
+
} else if (attacker.hasAbility('Stakeout') && attacker.abilityOn) {
|
|
1391
|
+
atMods.push(8192);
|
|
1392
|
+
desc.attackerAbility = attacker.ability;
|
|
1393
|
+
} else if (
|
|
1394
|
+
(attacker.hasAbility('Water Bubble') && move.hasType('Water')) ||
|
|
1395
|
+
(attacker.hasAbility('Huge Power', 'Pure Power') && move.category === 'Physical')
|
|
1396
|
+
) {
|
|
1397
|
+
atMods.push(8192);
|
|
1398
|
+
desc.attackerAbility = attacker.ability;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
if (
|
|
1402
|
+
field.attackerSide.isFlowerGift &&
|
|
1403
|
+
!attacker.hasAbility('Flower Gift') &&
|
|
1404
|
+
field.hasWeather('Sun', 'Harsh Sunshine') &&
|
|
1405
|
+
move.category === 'Physical') {
|
|
1406
|
+
atMods.push(6144);
|
|
1407
|
+
desc.weather = field.weather;
|
|
1408
|
+
desc.isFlowerGiftAttacker = true;
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
if (
|
|
1412
|
+
field.attackerSide.isSteelySpirit &&
|
|
1413
|
+
move.hasType('Steel')
|
|
1414
|
+
) {
|
|
1415
|
+
atMods.push(6144);
|
|
1416
|
+
desc.isSteelySpiritAttacker = true;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
if ((defender.hasAbility('Thick Fat') && move.hasType('Fire', 'Ice')) ||
|
|
1420
|
+
(defender.hasAbility('Water Bubble') && move.hasType('Fire')) ||
|
|
1421
|
+
(defender.hasAbility('Purifying Salt') && move.hasType('Ghost'))) {
|
|
1422
|
+
atMods.push(2048);
|
|
1423
|
+
desc.defenderAbility = defender.ability;
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
if (gen.num >= 9 && defender.hasAbility('Heatproof') && move.hasType('Fire')) {
|
|
1427
|
+
atMods.push(2048);
|
|
1428
|
+
desc.defenderAbility = defender.ability;
|
|
1429
|
+
}
|
|
1430
|
+
// Pokemon with "-of Ruin" Ability are immune to the opposing "-of Ruin" ability
|
|
1431
|
+
const isTabletsOfRuinActive = (defender.hasAbility('Tablets of Ruin') || field.isTabletsOfRuin) &&
|
|
1432
|
+
!attacker.hasAbility('Tablets of Ruin');
|
|
1433
|
+
const isVesselOfRuinActive = (defender.hasAbility('Vessel of Ruin') || field.isVesselOfRuin) &&
|
|
1434
|
+
!attacker.hasAbility('Vessel of Ruin');
|
|
1435
|
+
if (
|
|
1436
|
+
(isTabletsOfRuinActive && move.category === 'Physical') ||
|
|
1437
|
+
(isVesselOfRuinActive && move.category === 'Special')
|
|
1438
|
+
) {
|
|
1439
|
+
if (defender.hasAbility('Tablets of Ruin') || defender.hasAbility('Vessel of Ruin')) {
|
|
1440
|
+
desc.defenderAbility = defender.ability;
|
|
1441
|
+
} else {
|
|
1442
|
+
desc[move.category === 'Special' ? 'isVesselOfRuin' : 'isTabletsOfRuin'] = true;
|
|
1443
|
+
}
|
|
1444
|
+
atMods.push(3072);
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
if (isQPActive(attacker, field)) {
|
|
1448
|
+
if (
|
|
1449
|
+
(move.category === 'Physical' && getQPBoostedStat(attacker) === 'atk') ||
|
|
1450
|
+
(move.category === 'Special' && getQPBoostedStat(attacker) === 'spa')
|
|
1451
|
+
) {
|
|
1452
|
+
atMods.push(5325);
|
|
1453
|
+
desc.attackerAbility = attacker.ability;
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
if (
|
|
1458
|
+
(attacker.hasAbility('Hadron Engine') && move.category === 'Special' &&
|
|
1459
|
+
field.hasTerrain('Electric')) ||
|
|
1460
|
+
(attacker.hasAbility('Orichalcum Pulse') && move.category === 'Physical' &&
|
|
1461
|
+
field.hasWeather('Sun', 'Harsh Sunshine') && !attacker.hasItem('Utility Umbrella'))
|
|
1462
|
+
) {
|
|
1463
|
+
atMods.push(5461);
|
|
1464
|
+
desc.attackerAbility = attacker.ability;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
if ((attacker.hasItem('Thick Club') &&
|
|
1468
|
+
attacker.named('Cubone', 'Marowak', 'Marowak-Alola', 'Marowak-Alola-Totem') &&
|
|
1469
|
+
move.category === 'Physical') ||
|
|
1470
|
+
(attacker.hasItem('Deep Sea Tooth') &&
|
|
1471
|
+
attacker.named('Clamperl') &&
|
|
1472
|
+
move.category === 'Special') ||
|
|
1473
|
+
(attacker.hasItem('Light Ball') && attacker.name.includes('Pikachu') && !move.isZ)
|
|
1474
|
+
) {
|
|
1475
|
+
atMods.push(8192);
|
|
1476
|
+
desc.attackerItem = attacker.item;
|
|
1477
|
+
// Choice Band/Scarf/Specs move lock and stat boosts are ignored during Dynamax (Anubis)
|
|
1478
|
+
} else if (!move.isZ && !move.isMax &&
|
|
1479
|
+
((attacker.hasItem('Choice Band') && move.category === 'Physical') ||
|
|
1480
|
+
(attacker.hasItem('Choice Specs') && move.category === 'Special'))
|
|
1481
|
+
) {
|
|
1482
|
+
atMods.push(6144);
|
|
1483
|
+
desc.attackerItem = attacker.item;
|
|
1484
|
+
}
|
|
1485
|
+
return atMods;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
export function calculateDefenseSMSSSV(
|
|
1489
|
+
gen: Generation,
|
|
1490
|
+
attacker: Pokemon,
|
|
1491
|
+
defender: Pokemon,
|
|
1492
|
+
move: Move,
|
|
1493
|
+
field: Field,
|
|
1494
|
+
desc: RawDesc,
|
|
1495
|
+
isCritical = false
|
|
1496
|
+
) {
|
|
1497
|
+
let defense: number;
|
|
1498
|
+
const hitsPhysical = move.overrideDefensiveStat === 'def' || move.category === 'Physical' ||
|
|
1499
|
+
(move.named('Shell Side Arm') && getShellSideArmCategory(attacker, defender) === 'Physical');
|
|
1500
|
+
const defenseStat = hitsPhysical ? 'def' : 'spd';
|
|
1501
|
+
desc.defenseEVs = getStatDescriptionText(gen, defender, defenseStat, defender.nature);
|
|
1502
|
+
if (defender.boosts[defenseStat] === 0 ||
|
|
1503
|
+
(isCritical && defender.boosts[defenseStat] > 0) ||
|
|
1504
|
+
move.ignoreDefensive) {
|
|
1505
|
+
defense = defender.rawStats[defenseStat];
|
|
1506
|
+
} else if (attacker.hasAbility('Unaware')) {
|
|
1507
|
+
defense = defender.rawStats[defenseStat];
|
|
1508
|
+
desc.attackerAbility = attacker.ability;
|
|
1509
|
+
} else {
|
|
1510
|
+
defense = getModifiedStat(defender.rawStats[defenseStat]!, defender.boosts[defenseStat]!);
|
|
1511
|
+
desc.defenseBoost = defender.boosts[defenseStat];
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
// unlike all other defense modifiers, Sandstorm SpD boost gets applied directly
|
|
1515
|
+
if (field.hasWeather('Sand') && defender.hasType('Rock') && !hitsPhysical) {
|
|
1516
|
+
defense = pokeRound((defense * 3) / 2);
|
|
1517
|
+
desc.weather = field.weather;
|
|
1518
|
+
}
|
|
1519
|
+
if (field.hasWeather('Snow') && defender.hasType('Ice') && hitsPhysical) {
|
|
1520
|
+
defense = pokeRound((defense * 3) / 2);
|
|
1521
|
+
desc.weather = field.weather;
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
const dfMods = calculateDfModsSMSSSV(
|
|
1525
|
+
gen,
|
|
1526
|
+
attacker,
|
|
1527
|
+
defender,
|
|
1528
|
+
move,
|
|
1529
|
+
field,
|
|
1530
|
+
desc,
|
|
1531
|
+
isCritical,
|
|
1532
|
+
hitsPhysical
|
|
1533
|
+
);
|
|
1534
|
+
|
|
1535
|
+
return OF16(Math.max(1, pokeRound((defense * chainMods(dfMods, 410, 131072)) / 4096)));
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
export function calculateDfModsSMSSSV(
|
|
1539
|
+
gen: Generation,
|
|
1540
|
+
attacker: Pokemon,
|
|
1541
|
+
defender: Pokemon,
|
|
1542
|
+
move: Move,
|
|
1543
|
+
field: Field,
|
|
1544
|
+
desc: RawDesc,
|
|
1545
|
+
isCritical = false,
|
|
1546
|
+
hitsPhysical = false
|
|
1547
|
+
) {
|
|
1548
|
+
const dfMods = [];
|
|
1549
|
+
if (defender.hasAbility('Marvel Scale') && defender.status && hitsPhysical) {
|
|
1550
|
+
dfMods.push(6144);
|
|
1551
|
+
desc.defenderAbility = defender.ability;
|
|
1552
|
+
} else if (
|
|
1553
|
+
defender.named('Cherrim') &&
|
|
1554
|
+
defender.hasAbility('Flower Gift') &&
|
|
1555
|
+
field.hasWeather('Sun', 'Harsh Sunshine') &&
|
|
1556
|
+
!hitsPhysical
|
|
1557
|
+
) {
|
|
1558
|
+
dfMods.push(6144);
|
|
1559
|
+
desc.defenderAbility = defender.ability;
|
|
1560
|
+
desc.weather = field.weather;
|
|
1561
|
+
} else if (
|
|
1562
|
+
field.defenderSide.isFlowerGift &&
|
|
1563
|
+
field.hasWeather('Sun', 'Harsh Sunshine') &&
|
|
1564
|
+
!hitsPhysical) {
|
|
1565
|
+
dfMods.push(6144);
|
|
1566
|
+
desc.weather = field.weather;
|
|
1567
|
+
desc.isFlowerGiftDefender = true;
|
|
1568
|
+
} else if (
|
|
1569
|
+
defender.hasAbility('Grass Pelt') &&
|
|
1570
|
+
field.hasTerrain('Grassy') &&
|
|
1571
|
+
hitsPhysical
|
|
1572
|
+
) {
|
|
1573
|
+
dfMods.push(6144);
|
|
1574
|
+
desc.defenderAbility = defender.ability;
|
|
1575
|
+
} else if (defender.hasAbility('Fur Coat') && hitsPhysical) {
|
|
1576
|
+
dfMods.push(8192);
|
|
1577
|
+
desc.defenderAbility = defender.ability;
|
|
1578
|
+
}
|
|
1579
|
+
// Pokemon with "-of Ruin" Ability are immune to the opposing "-of Ruin" ability
|
|
1580
|
+
const isSwordOfRuinActive = (attacker.hasAbility('Sword of Ruin') || field.isSwordOfRuin) &&
|
|
1581
|
+
!defender.hasAbility('Sword of Ruin');
|
|
1582
|
+
const isBeadsOfRuinActive = (attacker.hasAbility('Beads of Ruin') || field.isBeadsOfRuin) &&
|
|
1583
|
+
!defender.hasAbility('Beads of Ruin');
|
|
1584
|
+
if (
|
|
1585
|
+
(isSwordOfRuinActive && hitsPhysical) ||
|
|
1586
|
+
(isBeadsOfRuinActive && !hitsPhysical)
|
|
1587
|
+
) {
|
|
1588
|
+
if (attacker.hasAbility('Sword of Ruin') || attacker.hasAbility('Beads of Ruin')) {
|
|
1589
|
+
desc.attackerAbility = attacker.ability;
|
|
1590
|
+
} else {
|
|
1591
|
+
desc[hitsPhysical ? 'isSwordOfRuin' : 'isBeadsOfRuin'] = true;
|
|
1592
|
+
}
|
|
1593
|
+
dfMods.push(3072);
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
if (isQPActive(defender, field)) {
|
|
1597
|
+
if (
|
|
1598
|
+
(hitsPhysical && getQPBoostedStat(defender) === 'def') ||
|
|
1599
|
+
(!hitsPhysical && getQPBoostedStat(defender) === 'spd')
|
|
1600
|
+
) {
|
|
1601
|
+
desc.defenderAbility = defender.ability;
|
|
1602
|
+
dfMods.push(5324);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
if ((defender.hasItem('Eviolite') &&
|
|
1607
|
+
(defender.name === 'Dipplin' || gen.species.get(toID(defender.name))?.nfe)) ||
|
|
1608
|
+
(!hitsPhysical && defender.hasItem('Assault Vest'))) {
|
|
1609
|
+
dfMods.push(6144);
|
|
1610
|
+
desc.defenderItem = defender.item;
|
|
1611
|
+
} else if (
|
|
1612
|
+
(defender.hasItem('Metal Powder') && defender.named('Ditto') && hitsPhysical) ||
|
|
1613
|
+
(defender.hasItem('Deep Sea Scale') && defender.named('Clamperl') && !hitsPhysical)
|
|
1614
|
+
) {
|
|
1615
|
+
dfMods.push(8192);
|
|
1616
|
+
desc.defenderItem = defender.item;
|
|
1617
|
+
}
|
|
1618
|
+
return dfMods;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
function calculateBaseDamageSMSSSV(
|
|
1622
|
+
gen: Generation,
|
|
1623
|
+
attacker: Pokemon,
|
|
1624
|
+
defender: Pokemon,
|
|
1625
|
+
basePower: number,
|
|
1626
|
+
attack: number,
|
|
1627
|
+
defense: number,
|
|
1628
|
+
move: Move,
|
|
1629
|
+
field: Field,
|
|
1630
|
+
desc: RawDesc,
|
|
1631
|
+
isCritical = false,
|
|
1632
|
+
) {
|
|
1633
|
+
let baseDamage = getBaseDamage(attacker.level, basePower, attack, defense);
|
|
1634
|
+
const isSpread = field.gameType !== 'Singles' &&
|
|
1635
|
+
['allAdjacent', 'allAdjacentFoes'].includes(move.target);
|
|
1636
|
+
if (isSpread) {
|
|
1637
|
+
baseDamage = pokeRound(OF32(baseDamage * 3072) / 4096);
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
if (attacker.hasAbility('Parental Bond (Child)')) {
|
|
1641
|
+
baseDamage = pokeRound(OF32(baseDamage * 1024) / 4096);
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
if (
|
|
1645
|
+
field.hasWeather('Sun') && move.named('Hydro Steam') && !attacker.hasItem('Utility Umbrella')
|
|
1646
|
+
) {
|
|
1647
|
+
baseDamage = pokeRound(OF32(baseDamage * 6144) / 4096);
|
|
1648
|
+
desc.weather = field.weather;
|
|
1649
|
+
} else if (!defender.hasItem('Utility Umbrella')) {
|
|
1650
|
+
if (
|
|
1651
|
+
(field.hasWeather('Sun', 'Harsh Sunshine') && move.hasType('Fire')) ||
|
|
1652
|
+
(field.hasWeather('Rain', 'Heavy Rain') && move.hasType('Water'))
|
|
1653
|
+
) {
|
|
1654
|
+
baseDamage = pokeRound(OF32(baseDamage * 6144) / 4096);
|
|
1655
|
+
desc.weather = field.weather;
|
|
1656
|
+
} else if (
|
|
1657
|
+
(field.hasWeather('Sun') && move.hasType('Water')) ||
|
|
1658
|
+
(field.hasWeather('Rain') && move.hasType('Fire'))
|
|
1659
|
+
) {
|
|
1660
|
+
baseDamage = pokeRound(OF32(baseDamage * 2048) / 4096);
|
|
1661
|
+
desc.weather = field.weather;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
if (isCritical) {
|
|
1666
|
+
baseDamage = Math.floor(OF32(baseDamage * 1.5));
|
|
1667
|
+
desc.isCritical = isCritical;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
return baseDamage;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
export function calculateFinalModsSMSSSV(
|
|
1674
|
+
gen: Generation,
|
|
1675
|
+
attacker: Pokemon,
|
|
1676
|
+
defender: Pokemon,
|
|
1677
|
+
move: Move,
|
|
1678
|
+
field: Field,
|
|
1679
|
+
desc: RawDesc,
|
|
1680
|
+
isCritical = false,
|
|
1681
|
+
typeEffectiveness: number,
|
|
1682
|
+
hitCount = 0
|
|
1683
|
+
) {
|
|
1684
|
+
const finalMods = [];
|
|
1685
|
+
|
|
1686
|
+
if (field.defenderSide.isReflect && move.category === 'Physical' &&
|
|
1687
|
+
!isCritical && !field.defenderSide.isAuroraVeil) {
|
|
1688
|
+
// doesn't stack with Aurora Veil
|
|
1689
|
+
finalMods.push(field.gameType !== 'Singles' ? 2732 : 2048);
|
|
1690
|
+
desc.isReflect = true;
|
|
1691
|
+
} else if (
|
|
1692
|
+
field.defenderSide.isLightScreen && move.category === 'Special' &&
|
|
1693
|
+
!isCritical && !field.defenderSide.isAuroraVeil
|
|
1694
|
+
) {
|
|
1695
|
+
// doesn't stack with Aurora Veil
|
|
1696
|
+
finalMods.push(field.gameType !== 'Singles' ? 2732 : 2048);
|
|
1697
|
+
desc.isLightScreen = true;
|
|
1698
|
+
}
|
|
1699
|
+
if (field.defenderSide.isAuroraVeil && !isCritical) {
|
|
1700
|
+
finalMods.push(field.gameType !== 'Singles' ? 2732 : 2048);
|
|
1701
|
+
desc.isAuroraVeil = true;
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
if (attacker.hasAbility('Neuroforce') && typeEffectiveness > 1) {
|
|
1705
|
+
finalMods.push(5120);
|
|
1706
|
+
desc.attackerAbility = attacker.ability;
|
|
1707
|
+
} else if (attacker.hasAbility('Sniper') && isCritical) {
|
|
1708
|
+
finalMods.push(6144);
|
|
1709
|
+
desc.attackerAbility = attacker.ability;
|
|
1710
|
+
} else if (attacker.hasAbility('Tinted Lens') && typeEffectiveness < 1) {
|
|
1711
|
+
finalMods.push(8192);
|
|
1712
|
+
desc.attackerAbility = attacker.ability;
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
if (defender.isDynamaxed && move.named('Dynamax Cannon', 'Behemoth Blade', 'Behemoth Bash')) {
|
|
1716
|
+
finalMods.push(8192);
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
if (defender.hasAbility('Multiscale', 'Shadow Shield') &&
|
|
1720
|
+
defender.curHP() === defender.maxHP() &&
|
|
1721
|
+
hitCount === 0 &&
|
|
1722
|
+
(!field.defenderSide.isSR && (!field.defenderSide.spikes || defender.hasType('Flying')) ||
|
|
1723
|
+
defender.hasItem('Heavy-Duty Boots')) && !attacker.hasAbility('Parental Bond (Child)')
|
|
1724
|
+
) {
|
|
1725
|
+
finalMods.push(2048);
|
|
1726
|
+
desc.defenderAbility = defender.ability;
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
if (defender.hasAbility('Fluffy') && move.flags.contact && !attacker.hasAbility('Long Reach')) {
|
|
1730
|
+
finalMods.push(2048);
|
|
1731
|
+
desc.defenderAbility = defender.ability;
|
|
1732
|
+
} else if (
|
|
1733
|
+
(defender.hasAbility('Punk Rock') && move.flags.sound) ||
|
|
1734
|
+
(defender.hasAbility('Ice Scales') && move.category === 'Special')
|
|
1735
|
+
) {
|
|
1736
|
+
finalMods.push(2048);
|
|
1737
|
+
desc.defenderAbility = defender.ability;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
if (defender.hasAbility('Solid Rock', 'Filter', 'Prism Armor') && typeEffectiveness > 1) {
|
|
1741
|
+
finalMods.push(3072);
|
|
1742
|
+
desc.defenderAbility = defender.ability;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
if (field.defenderSide.isFriendGuard) {
|
|
1746
|
+
finalMods.push(3072);
|
|
1747
|
+
desc.isFriendGuard = true;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
if (defender.hasAbility('Fluffy') && move.hasType('Fire')) {
|
|
1751
|
+
finalMods.push(8192);
|
|
1752
|
+
desc.defenderAbility = defender.ability;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
if (attacker.hasItem('Expert Belt') && typeEffectiveness > 1 && !move.isZ) {
|
|
1756
|
+
finalMods.push(4915);
|
|
1757
|
+
desc.attackerItem = attacker.item;
|
|
1758
|
+
} else if (attacker.hasItem('Life Orb')) {
|
|
1759
|
+
finalMods.push(5324);
|
|
1760
|
+
desc.attackerItem = attacker.item;
|
|
1761
|
+
} else if (attacker.hasItem('Metronome') && move.timesUsedWithMetronome! >= 1) {
|
|
1762
|
+
const timesUsedWithMetronome = Math.floor(move.timesUsedWithMetronome!);
|
|
1763
|
+
if (timesUsedWithMetronome <= 4) {
|
|
1764
|
+
finalMods.push(4096 + timesUsedWithMetronome * 819);
|
|
1765
|
+
} else {
|
|
1766
|
+
finalMods.push(8192);
|
|
1767
|
+
}
|
|
1768
|
+
desc.attackerItem = attacker.item;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
if (move.hasType(getBerryResistType(defender.item)) &&
|
|
1772
|
+
(typeEffectiveness > 1 || move.hasType('Normal')) &&
|
|
1773
|
+
hitCount === 0 &&
|
|
1774
|
+
!attacker.hasAbility('Unnerve', 'As One (Glastrier)', 'As One (Spectrier)')) {
|
|
1775
|
+
if (defender.hasAbility('Ripen')) {
|
|
1776
|
+
finalMods.push(1024);
|
|
1777
|
+
} else {
|
|
1778
|
+
finalMods.push(2048);
|
|
1779
|
+
}
|
|
1780
|
+
desc.defenderItem = defender.item;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
return finalMods;
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
function hasTerrainSeed(pokemon: Pokemon) {
|
|
1787
|
+
return pokemon.hasItem('Electric Seed', 'Misty Seed', 'Grassy Seed', 'Psychic Seed');
|
|
1788
|
+
}
|