@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,1588 @@
|
|
|
1
|
+
/* eslint-disable max-len */
|
|
2
|
+
|
|
3
|
+
import type {AbilityName, Terrain, Weather} from '../data/interface';
|
|
4
|
+
import {inGen, inGens, tests} from './helper';
|
|
5
|
+
|
|
6
|
+
describe('calc', () => {
|
|
7
|
+
describe('Multi-Gen', () => {
|
|
8
|
+
inGens(4, 7, ({gen, calculate, Pokemon, Move}) => {
|
|
9
|
+
test(`Grass Knot (gen ${gen})`, () => {
|
|
10
|
+
const result = calculate(Pokemon('Groudon'), Pokemon('Groudon'), Move('Grass Knot'));
|
|
11
|
+
expect(result.range()).toEqual([190, 224]);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
inGens(4, 7, ({gen, calculate, Pokemon, Move}) => {
|
|
16
|
+
test(`Arceus Plate (gen ${gen})`, () => {
|
|
17
|
+
const result = calculate(
|
|
18
|
+
Pokemon('Arceus', {item: 'Meadow Plate'}),
|
|
19
|
+
Pokemon('Blastoise'),
|
|
20
|
+
Move('Judgment')
|
|
21
|
+
);
|
|
22
|
+
expect(result.range()).toEqual([194, 230]);
|
|
23
|
+
expect(result.desc()).toBe(
|
|
24
|
+
'0 SpA Meadow Plate Arceus Judgment vs. 0 HP / 0 SpD Blastoise: 194-230 (64.8 - 76.9%) -- guaranteed 2HKO'
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
inGens(1, 9, ({gen, calculate, Pokemon, Move}) => {
|
|
30
|
+
test(`Night Shade / Seismic Toss (gen ${gen})`, () => {
|
|
31
|
+
const mew = Pokemon('Mew', {level: 50});
|
|
32
|
+
const vulpix = Pokemon('Vulpix');
|
|
33
|
+
for (const move of [Move('Seismic Toss'), Move('Night Shade')]) {
|
|
34
|
+
const result = calculate(mew, vulpix, move);
|
|
35
|
+
expect(result.damage).toBe(50);
|
|
36
|
+
expect(result.desc()).toBe(
|
|
37
|
+
gen < 3
|
|
38
|
+
? `Lvl 50 Mew ${move.name} vs. Vulpix: 50-50 (17.9 - 17.9%) -- guaranteed 6HKO`
|
|
39
|
+
: `Lvl 50 Mew ${move.name} vs. 0 HP Vulpix: 50-50 (23 - 23%) -- guaranteed 5HKO`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
tests('Comet Punch', ({gen, calculate, Pokemon, Move}) => {
|
|
46
|
+
expect(calculate(Pokemon('Snorlax'), Pokemon('Vulpix'), Move('Comet Punch'))).toMatch(gen, {
|
|
47
|
+
1: {range: [108, 129], desc: 'Snorlax Comet Punch (3 hits) vs. Vulpix', result: '(38.7 - 46.2%) -- approx. 3HKO'},
|
|
48
|
+
3: {range: [132, 156], desc: '0 Atk Snorlax Comet Punch (3 hits) vs. 0 HP / 0 Def Vulpix', result: '(60.8 - 71.8%) -- approx. 2HKO'},
|
|
49
|
+
4: {range: [129, 156], result: '(59.4 - 71.8%) -- approx. 2HKO'},
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
inGens(1, 9, ({gen, calculate, Pokemon, Move}) => {
|
|
54
|
+
test(`Immunity (gen ${gen})`, () => {
|
|
55
|
+
expect(calculate(Pokemon('Snorlax'), Pokemon('Gengar'), Move('Hyper Beam')).damage).toBe(0);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
inGens(1, 9, ({gen, calculate, Pokemon, Move}) => {
|
|
60
|
+
test(`Non-damaging (gen ${gen})`, () => {
|
|
61
|
+
const result = calculate(Pokemon('Snorlax'), Pokemon('Vulpix'), Move('Barrier'));
|
|
62
|
+
expect(result.damage).toBe(0);
|
|
63
|
+
expect(result.desc()).toBe('Snorlax Barrier vs. Vulpix: 0-0 (0 - 0%)');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
inGens(1, 9, ({gen, calculate, Pokemon, Move, Field}) => {
|
|
68
|
+
test(`Protect (gen ${gen})`, () => {
|
|
69
|
+
const field = Field({defenderSide: {isProtected: true}});
|
|
70
|
+
const snorlax = Pokemon('Snorlax');
|
|
71
|
+
const chansey = Pokemon('Chansey');
|
|
72
|
+
expect(calculate(snorlax, chansey, Move('Hyper Beam'), field).damage).toBe(0);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
inGens(1, 9, ({gen, calculate, Pokemon, Move, Field}) => {
|
|
77
|
+
test(`Critical hits ignore attack decreases (gen ${gen})`, () => {
|
|
78
|
+
const field = Field({defenderSide: {isReflect: true}});
|
|
79
|
+
|
|
80
|
+
const mew = Pokemon('Mew', {status: 'brn'});
|
|
81
|
+
const vulpix = Pokemon('Vulpix');
|
|
82
|
+
const explosion = Move('Explosion', {isCrit: true});
|
|
83
|
+
let result = calculate(mew, vulpix, explosion, field);
|
|
84
|
+
mew.boosts.atk = 2;
|
|
85
|
+
vulpix.boosts.def = 2;
|
|
86
|
+
if (gen < 2) {
|
|
87
|
+
expect(result.range()).toEqual([799, 939]);
|
|
88
|
+
expect(result.desc()).toBe(
|
|
89
|
+
'Mew Explosion vs. Vulpix on a critical hit: 799-939 (286.3 - 336.5%) -- guaranteed OHKO'
|
|
90
|
+
);
|
|
91
|
+
} else if (gen < 5 && gen > 2) {
|
|
92
|
+
expect(result.range()).toEqual([729, 858]);
|
|
93
|
+
expect(result.desc()).toBe(
|
|
94
|
+
'0 Atk burned Mew Explosion vs. 0 HP / 0 Def Vulpix on a critical hit: 729-858 (335.9 - 395.3%) -- guaranteed OHKO'
|
|
95
|
+
);
|
|
96
|
+
} else if (gen === 5) {
|
|
97
|
+
expect(result.range()).toEqual([364, 429]);
|
|
98
|
+
expect(result.desc()).toBe(
|
|
99
|
+
'0 Atk burned Mew Explosion vs. 0 HP / 0 Def Vulpix on a critical hit: 364-429 (167.7 - 197.6%) -- guaranteed OHKO'
|
|
100
|
+
);
|
|
101
|
+
} else if (gen >= 6) {
|
|
102
|
+
expect(result.range()).toEqual([273, 321]);
|
|
103
|
+
expect(result.desc()).toBe(
|
|
104
|
+
'0 Atk burned Mew Explosion vs. 0 HP / 0 Def Vulpix on a critical hit: 273-321 (125.8 - 147.9%) -- guaranteed OHKO'
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
explosion.isCrit = false;
|
|
108
|
+
result = calculate(mew, vulpix, explosion, field);
|
|
109
|
+
if (gen === 1) {
|
|
110
|
+
expect(result.range()).toEqual([102, 120]);
|
|
111
|
+
} else if (gen === 2) {
|
|
112
|
+
expect(result.range()).toEqual([149, 176]);
|
|
113
|
+
} else if (gen > 2 && gen < 5) {
|
|
114
|
+
expect(result.range()).toEqual([182, 215]);
|
|
115
|
+
} else {
|
|
116
|
+
expect(result.range()).toEqual([91, 107]);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
inGens(1, 9, ({gen, calculate, Pokemon, Move}) => {
|
|
122
|
+
test(`Struggle vs. Ghost (gen ${gen})`, () => {
|
|
123
|
+
const result = calculate(Pokemon('Mew'), Pokemon('Gengar'), Move('Struggle'));
|
|
124
|
+
if (gen < 2) {
|
|
125
|
+
expect(result.range()[1]).toBe(0);
|
|
126
|
+
} else {
|
|
127
|
+
expect(result.range()[1]).toBeGreaterThan(0);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
inGens(3, 9, ({gen, calculate, Pokemon, Move, Field}) => {
|
|
133
|
+
test(`Weather Ball should change type depending on the weather (gen ${gen})`, () => {
|
|
134
|
+
const weathers = [
|
|
135
|
+
{
|
|
136
|
+
weather: 'Sun', type: 'Fire', damage: {
|
|
137
|
+
adv: {range: [346, 408], desc: '(149.7 - 176.6%) -- guaranteed OHKO'},
|
|
138
|
+
dpp: {range: [342, 404], desc: '(148 - 174.8%) -- guaranteed OHKO'},
|
|
139
|
+
modern: {range: [344, 408], desc: '(148.9 - 176.6%) -- guaranteed OHKO'},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
weather: 'Rain', type: 'Water', damage: {
|
|
144
|
+
adv: {range: [86, 102], desc: '(37.2 - 44.1%) -- guaranteed 3HKO'},
|
|
145
|
+
dpp: {range: [85, 101], desc: '(36.7 - 43.7%) -- guaranteed 3HKO'},
|
|
146
|
+
modern: {range: [86, 102], desc: '(37.2 - 44.1%) -- guaranteed 3HKO'},
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
weather: 'Sand', type: 'Rock', damage: {
|
|
151
|
+
adv: {
|
|
152
|
+
range: [96, 114],
|
|
153
|
+
desc: '(41.5 - 49.3%) -- 82.4% chance to 2HKO after sandstorm damage',
|
|
154
|
+
},
|
|
155
|
+
dpp: {
|
|
156
|
+
range: [77, 91],
|
|
157
|
+
desc: '(33.3 - 39.3%) -- guaranteed 3HKO after sandstorm damage',
|
|
158
|
+
},
|
|
159
|
+
modern: {
|
|
160
|
+
range: [77, 91],
|
|
161
|
+
desc: '(33.3 - 39.3%) -- guaranteed 3HKO after sandstorm damage',
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
weather: 'Hail', type: 'Ice', damage: {
|
|
167
|
+
adv: {
|
|
168
|
+
range: [234, 276],
|
|
169
|
+
desc: '(101.2 - 119.4%) -- guaranteed OHKO',
|
|
170
|
+
},
|
|
171
|
+
dpp: {
|
|
172
|
+
range: [230, 272],
|
|
173
|
+
desc: '(99.5 - 117.7%) -- 93.8% chance to OHKO (guaranteed OHKO after hail damage)',
|
|
174
|
+
},
|
|
175
|
+
modern: {
|
|
176
|
+
range: [230, 272],
|
|
177
|
+
desc: '(99.5 - 117.7%) -- 93.8% chance to OHKO (guaranteed OHKO after hail damage)',
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
for (const {weather, type, damage} of weathers) {
|
|
184
|
+
const dmg = gen === 3 ? damage.adv : gen === 4 ? damage.dpp : damage.modern;
|
|
185
|
+
const [atk, def] = gen === 3 && type === 'Rock' ? ['Atk', 'Def'] : ['SpA', 'SpD'];
|
|
186
|
+
|
|
187
|
+
const result = calculate(
|
|
188
|
+
Pokemon('Castform'),
|
|
189
|
+
Pokemon('Bulbasaur'),
|
|
190
|
+
Move('Weather Ball'),
|
|
191
|
+
Field({weather: weather as Weather})
|
|
192
|
+
);
|
|
193
|
+
expect(result.range()).toEqual(dmg.range);
|
|
194
|
+
expect(result.desc()).toBe(
|
|
195
|
+
`0 ${atk} Castform Weather Ball (100 BP ${type}) vs. 0 HP / 0 ${def} Bulbasaur in ${weather}: ${dmg.range[0]}-${dmg.range[1]} ${dmg.desc}`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
inGens(6, 9, ({gen, calculate, Pokemon, Move}) => {
|
|
202
|
+
test(`Flying Press (gen ${gen})`, () => {
|
|
203
|
+
const attacker = Pokemon('Hawlucha');
|
|
204
|
+
const flyingPress = Move('Flying Press');
|
|
205
|
+
// Test it is 4x dmg if weak to flying and fighting
|
|
206
|
+
const result = calculate(attacker, Pokemon('Cacturne'), flyingPress);
|
|
207
|
+
if (gen === 6) {
|
|
208
|
+
expect(result.range()).toEqual([484, 576]);
|
|
209
|
+
expect(result.desc()).toBe(
|
|
210
|
+
'0 Atk Hawlucha Flying Press vs. 0 HP / 0 Def Cacturne: 484-576 (172.2 - 204.9%) -- guaranteed OHKO'
|
|
211
|
+
);
|
|
212
|
+
} else {
|
|
213
|
+
expect(result.range()).toEqual([612, 720]);
|
|
214
|
+
expect(result.desc()).toBe(
|
|
215
|
+
'0 Atk Hawlucha Flying Press vs. 0 HP / 0 Def Cacturne: 612-720 (217.7 - 256.2%) -- guaranteed OHKO'
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Test still maintains fighting immunities
|
|
220
|
+
const result2 = calculate(attacker, Pokemon('Spiritomb'), flyingPress);
|
|
221
|
+
expect(result2.range()).toEqual([0, 0]);
|
|
222
|
+
|
|
223
|
+
// Test fighting immunities can be overridden
|
|
224
|
+
const scrappyAttacker = Pokemon('Hawlucha', {'ability': 'Scrappy'});
|
|
225
|
+
const ringTargetSpiritomb = Pokemon('Spiritomb', {'item': 'Ring Target'});
|
|
226
|
+
const result3 = calculate(attacker, ringTargetSpiritomb, flyingPress);
|
|
227
|
+
const result4 = calculate(scrappyAttacker, Pokemon('Spiritomb'), flyingPress);
|
|
228
|
+
if (gen === 6) {
|
|
229
|
+
expect(result3.range()).toEqual([152, 180]);
|
|
230
|
+
expect(result4.range()).toEqual([152, 180]);
|
|
231
|
+
} else {
|
|
232
|
+
expect(result3.range()).toEqual([188, 224]);
|
|
233
|
+
expect(result4.range()).toEqual([188, 224]);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
inGens(6, 9, ({gen, calculate, Pokemon, Move}) => {
|
|
239
|
+
test(`Thousand Arrows and Ring Target Should negate damage nullfiers (gen ${gen})`, () => {
|
|
240
|
+
const result = calculate(Pokemon('Zygarde'), Pokemon('Swellow'), Move('Thousand Arrows'));
|
|
241
|
+
expect(result.range()).toEqual([147, 174]);
|
|
242
|
+
expect(result.desc()).toBe(
|
|
243
|
+
'0 Atk Zygarde Thousand Arrows vs. 0 HP / 0 Def Swellow: 147-174 (56.3 - 66.6%) -- guaranteed 2HKO'
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
inGens(5, 9, ({gen, calculate, Pokemon, Move}) => {
|
|
249
|
+
test(`Ring Target should negate type nullfiers (gen ${gen})`, () => {
|
|
250
|
+
const attacker = Pokemon('Mew');
|
|
251
|
+
const defender = Pokemon('Skarmory', {'item': 'Ring Target'});
|
|
252
|
+
const result = calculate(attacker, defender, Move('Sludge Bomb'));
|
|
253
|
+
expect(result.range()).toEqual([87, 103]);
|
|
254
|
+
expect(result.desc()).toBe(
|
|
255
|
+
'0 SpA Mew Sludge Bomb vs. 0 HP / 0 SpD Skarmory: 87-103 (32.1 - 38%) -- 94.6% chance to 3HKO'
|
|
256
|
+
);
|
|
257
|
+
const result2 = calculate(attacker, defender, Move('Earth Power'));
|
|
258
|
+
expect(result2.range()).toEqual([174, 206]);
|
|
259
|
+
expect(result2.desc()).toBe(
|
|
260
|
+
'0 SpA Mew Earth Power vs. 0 HP / 0 SpD Skarmory: 174-206 (64.2 - 76%) -- guaranteed 2HKO'
|
|
261
|
+
);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
describe('IVs are shown if applicable', () => {
|
|
266
|
+
inGens(3, 9, ({gen, calculate, Pokemon, Move}) => {
|
|
267
|
+
test(`Gen ${gen}`, () => {
|
|
268
|
+
const ivs = {spa: 9, spd: 9, hp: 9};
|
|
269
|
+
const evs = {spa: 9, spd: 9, hp: 9};
|
|
270
|
+
let result = calculate(Pokemon('Mew', {ivs}), Pokemon('Mew', {evs}), Move('Psychic'));
|
|
271
|
+
expect(result.desc()).toBe('0 SpA 9 IVs Mew Psychic vs. 9 HP / 9 SpD Mew: 43-51 (12.5 - 14.8%) -- possible 7HKO');
|
|
272
|
+
result = calculate(Pokemon('Mew', {evs}), Pokemon('Mew', {ivs}), Move('Psychic'));
|
|
273
|
+
expect(result.desc()).toBe('9 SpA Mew Psychic vs. 0 HP 9 IVs / 0 SpD 9 IVs Mew: 54-64 (16.9 - 20%) -- possible 5HKO');
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
inGens(4, 9, ({gen, calculate, Pokemon, Move}) => {
|
|
279
|
+
const zapdos = Pokemon('Zapdos', {item: 'Iron Ball'});
|
|
280
|
+
if (gen === 4) {
|
|
281
|
+
test(`Iron Ball negates ground immunities (gen ${gen})`, () => {
|
|
282
|
+
const result = calculate(Pokemon('Vibrava'), zapdos, Move('Earthquake'));
|
|
283
|
+
expect(result.range()).toEqual([186, 218]);
|
|
284
|
+
expect(result.desc()).toBe(
|
|
285
|
+
'0 Atk Vibrava Earthquake vs. 0 HP / 0 Def Zapdos: 186-218 (57.9 - 67.9%) -- guaranteed 2HKO'
|
|
286
|
+
);
|
|
287
|
+
});
|
|
288
|
+
} else {
|
|
289
|
+
test(`Iron Ball Should negate damage nullifiers (gen ${gen})`, () => {
|
|
290
|
+
const result = calculate(Pokemon('Vibrava'), zapdos, Move('Earthquake'));
|
|
291
|
+
expect(result.range()).toEqual([93, 109]);
|
|
292
|
+
expect(result.desc()).toBe(
|
|
293
|
+
'0 Atk Vibrava Earthquake vs. 0 HP / 0 Def Zapdos: 93-109 (28.9 - 33.9%) -- 1.2% chance to 3HKO'
|
|
294
|
+
);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
test(`Iron Ball negates levitate (gen ${gen})`, () => {
|
|
298
|
+
const result = calculate(Pokemon('Poliwrath'), Pokemon('Mismagius', {item: 'Iron Ball'}), Move('Mud Shot'));
|
|
299
|
+
expect(result.range()).toEqual([29, 35]);
|
|
300
|
+
expect(result.desc()).toBe(
|
|
301
|
+
'0 SpA Poliwrath Mud Shot vs. 0 HP / 0 SpD Mismagius: 29-35 (11.1 - 13.4%) -- possible 8HKO'
|
|
302
|
+
);
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
inGens(5, 9, ({gen, calculate, Pokemon, Move, Field}) => {
|
|
307
|
+
const dragonite = Pokemon('Dragonite', {ability: 'Multiscale'});
|
|
308
|
+
const dragonite1 = Pokemon('Dragonite', {ability: 'Multiscale', curHP: 69});
|
|
309
|
+
const dragonite2 = Pokemon('Dragonite', {ability: 'Shadow Shield', item: 'Heavy-Duty Boots'});
|
|
310
|
+
if (gen > 7) {
|
|
311
|
+
test(`Multiscale and Shadow Shield halves damage even if there are hazzards if holding Heavy-Duty Boots (gen ${gen})`, () => {
|
|
312
|
+
const field = Field({defenderSide: {isSR: true}});
|
|
313
|
+
const result = calculate(Pokemon('Abomasnow'), dragonite2, Move('Blizzard'), field);
|
|
314
|
+
expect(result.range()).toEqual([222, 264]);
|
|
315
|
+
expect(result.desc()).toBe(
|
|
316
|
+
'0 SpA Abomasnow Blizzard vs. 0 HP / 0 SpD Shadow Shield Dragonite: 222-264 (68.7 - 81.7%) -- guaranteed 2HKO'
|
|
317
|
+
);
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
test(`Multiscale and Shadow Shield should not halve damage if less than 100% HP (gen ${gen})`, () => {
|
|
321
|
+
const result = calculate(Pokemon('Abomasnow'), dragonite1, Move('Ice Shard'));
|
|
322
|
+
expect(result.range()).toEqual([168, 204]);
|
|
323
|
+
expect(result.desc()).toBe(
|
|
324
|
+
'0 Atk Abomasnow Ice Shard vs. 0 HP / 0 Def Dragonite: 168-204 (52 - 63.1%) -- guaranteed OHKO'
|
|
325
|
+
);
|
|
326
|
+
});
|
|
327
|
+
test(`Multiscale and Shadow Shield Should halve damage taken (gen ${gen})`, () => {
|
|
328
|
+
const result = calculate(Pokemon('Abomasnow'), dragonite, Move('Ice Shard'));
|
|
329
|
+
expect(result.range()).toEqual([84, 102]);
|
|
330
|
+
expect(result.desc()).toBe(
|
|
331
|
+
'0 Atk Abomasnow Ice Shard vs. 0 HP / 0 Def Multiscale Dragonite: 84-102 (26 - 31.5%) -- guaranteed 4HKO'
|
|
332
|
+
);
|
|
333
|
+
});
|
|
334
|
+
describe('Weight', function () {
|
|
335
|
+
describe('Heavy Metal', () => {
|
|
336
|
+
function testBP(ability: string) {
|
|
337
|
+
return calculate(
|
|
338
|
+
Pokemon('Simisage', {ability}),
|
|
339
|
+
Pokemon('Simisear', {ability: 'Heavy Metal'}),
|
|
340
|
+
Move('Grass Knot')
|
|
341
|
+
).rawDesc.moveBP;
|
|
342
|
+
}
|
|
343
|
+
it('should double the weight of a Pokemon', () => expect(testBP('Gluttony')).toBe(80));
|
|
344
|
+
it('should be negated by Mold Breaker', () => expect(testBP('Mold Breaker')).toBe(60));
|
|
345
|
+
});
|
|
346
|
+
describe('Light Metal', () => {
|
|
347
|
+
function testBP(ability: string) {
|
|
348
|
+
return calculate(
|
|
349
|
+
Pokemon('Simisage', {ability}),
|
|
350
|
+
Pokemon('Registeel', {ability: 'Light Metal'}),
|
|
351
|
+
Move('Grass Knot')
|
|
352
|
+
).rawDesc.moveBP;
|
|
353
|
+
}
|
|
354
|
+
it('should halve the weight of a Pokemon', () => expect(testBP('Gluttony')).toBe(100));
|
|
355
|
+
it('should be negated by Mold Breaker', () => expect(testBP('Mold Breaker')).toBe(120));
|
|
356
|
+
});
|
|
357
|
+
describe('Float Stone', () => {
|
|
358
|
+
function testBP(ability?: string) {
|
|
359
|
+
return calculate(
|
|
360
|
+
Pokemon('Simisage', {ability: 'Gluttony'}),
|
|
361
|
+
Pokemon('Registeel', {ability, item: 'Float Stone'}),
|
|
362
|
+
Move('Grass Knot')
|
|
363
|
+
).rawDesc.moveBP;
|
|
364
|
+
}
|
|
365
|
+
it('should halve the weight of a Pokemon', () => expect(testBP()).toBe(100));
|
|
366
|
+
it('should stack with Light Metal', () => expect(testBP('Light Metal')).toBe(80));
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
inGen(8, ({gen, Pokemon}) => {
|
|
372
|
+
test(`Pokemon should double their HP stat when dynamaxing (gen ${gen})`, () => {
|
|
373
|
+
const munchlax = Pokemon('Munchlax', {isDynamaxed: true});
|
|
374
|
+
expect(munchlax.curHP()).toBe(822);
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
inGens(7, 9, ({gen, calculate, Pokemon, Move, Field}) => {
|
|
379
|
+
test(`Psychic Terrain (gen ${gen})`, () => {
|
|
380
|
+
const field = Field({terrain: 'Psychic'});
|
|
381
|
+
const Mewtwo = Pokemon('Mewtwo', {
|
|
382
|
+
nature: 'Timid',
|
|
383
|
+
evs: {spa: 252},
|
|
384
|
+
boosts: {spa: 2},
|
|
385
|
+
});
|
|
386
|
+
const Milotic = Pokemon('Milotic', {
|
|
387
|
+
item: 'Flame Orb',
|
|
388
|
+
nature: 'Bold',
|
|
389
|
+
ability: 'Marvel Scale',
|
|
390
|
+
evs: {hp: 248, def: 184},
|
|
391
|
+
status: 'brn',
|
|
392
|
+
boosts: {spd: 1},
|
|
393
|
+
});
|
|
394
|
+
const Psystrike = Move('Psystrike');
|
|
395
|
+
const sPunch = Move('Sucker Punch');
|
|
396
|
+
let result = calculate(Mewtwo, Milotic, Psystrike, field);
|
|
397
|
+
if (gen < 8) {
|
|
398
|
+
expect(result.range()).toEqual([331, 391]);
|
|
399
|
+
expect(result.desc()).toBe(
|
|
400
|
+
'+2 252 SpA Mewtwo Psystrike vs. 248 HP / 184+ Def Marvel Scale Milotic in Psychic Terrain: 331-391 (84.2 - 99.4%) -- 37.5% chance to OHKO after burn damage'
|
|
401
|
+
);
|
|
402
|
+
} else {
|
|
403
|
+
expect(result.range()).toEqual([288, 339]);
|
|
404
|
+
expect(result.desc()).toBe(
|
|
405
|
+
'+2 252 SpA Mewtwo Psystrike vs. 248 HP / 184+ Def Marvel Scale Milotic in Psychic Terrain: 288-339 (73.2 - 86.2%) -- guaranteed 2HKO after burn damage'
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
result = calculate(Mewtwo, Milotic, sPunch, field);
|
|
409
|
+
expect(result.range()).toEqual([0, 0]);
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
inGens(6, 9, ({gen, calculate, Pokemon, Move}) => {
|
|
414
|
+
test(`Parental Bond (gen ${gen})`, () => {
|
|
415
|
+
let result = calculate(
|
|
416
|
+
Pokemon('Kangaskhan-Mega', {evs: {atk: 152}}),
|
|
417
|
+
Pokemon('Amoonguss', {nature: 'Bold', evs: {hp: 252, def: 152}}),
|
|
418
|
+
Move('Frustration')
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
if (gen === 6) {
|
|
422
|
+
expect(result.damage).toEqual([
|
|
423
|
+
[153, 154, 156, 157, 159, 162, 163, 165, 166, 168, 171, 172, 174, 175, 177, 180],
|
|
424
|
+
[76, 76, 78, 78, 79, 81, 81, 82, 82, 84, 85, 85, 87, 87, 88, 90],
|
|
425
|
+
]);
|
|
426
|
+
expect(result.desc()).toBe(
|
|
427
|
+
'152 Atk Parental Bond Kangaskhan-Mega Frustration vs. 252 HP / 152+ Def Amoonguss: 229-270 (53 - 62.5%) -- approx. 2HKO'
|
|
428
|
+
);
|
|
429
|
+
} else {
|
|
430
|
+
expect(result.damage).toEqual([
|
|
431
|
+
[153, 154, 156, 157, 159, 162, 163, 165, 166, 168, 171, 172, 174, 175, 177, 180],
|
|
432
|
+
[37, 37, 39, 39, 39, 40, 40, 40, 40, 42, 42, 42, 43, 43, 43, 45],
|
|
433
|
+
]);
|
|
434
|
+
expect(result.desc()).toBe(
|
|
435
|
+
'152 Atk Parental Bond Kangaskhan-Mega Frustration vs. 252 HP / 152+ Def Amoonguss: 190-225 (43.9 - 52%) -- approx. 6.6% chance to 2HKO'
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
result = calculate(
|
|
440
|
+
Pokemon('Kangaskhan-Mega', {level: 88}),
|
|
441
|
+
Pokemon('Amoonguss'),
|
|
442
|
+
Move('Seismic Toss')
|
|
443
|
+
);
|
|
444
|
+
expect(result.damage).toEqual([88, 88]);
|
|
445
|
+
expect(result.desc()).toBe(
|
|
446
|
+
'Lvl 88 Parental Bond Kangaskhan-Mega Seismic Toss vs. 0 HP Amoonguss: 176-176 (47.6 - 47.6%) -- guaranteed 3HKO'
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
result = calculate(
|
|
450
|
+
Pokemon('Kangaskhan-Mega', {evs: {atk: 252}}),
|
|
451
|
+
Pokemon('Aggron', {level: 72}),
|
|
452
|
+
Move('Power-Up Punch')
|
|
453
|
+
);
|
|
454
|
+
if (gen === 6) {
|
|
455
|
+
expect(result.desc()).toBe(
|
|
456
|
+
'252 Atk Parental Bond Kangaskhan-Mega Power-Up Punch vs. Lvl 72 0 HP / 0 Def Aggron: 248-296 (120.9 - 144.3%) -- guaranteed OHKO'
|
|
457
|
+
);
|
|
458
|
+
} else {
|
|
459
|
+
expect(result.desc()).toBe(
|
|
460
|
+
'252 Atk Parental Bond Kangaskhan-Mega Power-Up Punch vs. Lvl 72 0 HP / 0 Def Aggron: 196-236 (95.6 - 115.1%) -- 78.9% chance to OHKO'
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (gen === 6) return;
|
|
465
|
+
|
|
466
|
+
result = calculate(
|
|
467
|
+
Pokemon('Kangaskhan-Mega', {evs: {atk: 252}}),
|
|
468
|
+
Pokemon('Lunala'),
|
|
469
|
+
Move('Crunch')
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
expect(result.damage).toEqual([
|
|
473
|
+
[188, 190, 192, 194, 196, 198, 202, 204, 206, 208, 210, 212, 214, 216, 218, 222],
|
|
474
|
+
[92, 96, 96, 96, 96, 100, 100, 100, 104, 104, 104, 104, 108, 108, 108, 112],
|
|
475
|
+
]);
|
|
476
|
+
expect(result.desc()).toBe(
|
|
477
|
+
'252 Atk Parental Bond Kangaskhan-Mega Crunch vs. 0 HP / 0 Def Shadow Shield Lunala: 280-334 (67.4 - 80.4%) -- approx. 2HKO'
|
|
478
|
+
);
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
inGens(6, 9, ({gen, calculate, Pokemon, Move}) => {
|
|
483
|
+
test('Knock Off vs. Klutz', () => {
|
|
484
|
+
const weavile = Pokemon('Weavile');
|
|
485
|
+
const audino = Pokemon('Audino', {ability: 'Klutz', item: 'Leftovers'});
|
|
486
|
+
const audinoMega = Pokemon('Audino', {ability: 'Klutz', item: 'Audinite'});
|
|
487
|
+
const knockoff = Move('Knock Off');
|
|
488
|
+
const result = calculate(weavile, audino, knockoff);
|
|
489
|
+
expect(result.desc()).toBe(
|
|
490
|
+
'0 Atk Weavile Knock Off (97.5 BP) vs. 0 HP / 0 Def Audino: 139-165 (40 - 47.5%) -- guaranteed 3HKO'
|
|
491
|
+
);
|
|
492
|
+
const result2 = calculate(weavile, audinoMega, knockoff);
|
|
493
|
+
expect(result2.desc()).toBe(
|
|
494
|
+
'0 Atk Weavile Knock Off vs. 0 HP / 0 Def Audino: 93-111 (26.8 - 31.9%) -- guaranteed 4HKO'
|
|
495
|
+
);
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
inGens(5, 9, ({gen, calculate, Pokemon, Move}) => {
|
|
500
|
+
test(`Multi-hit interaction with Multiscale (gen ${gen})`, () => {
|
|
501
|
+
const result = calculate(
|
|
502
|
+
Pokemon('Mamoswine'),
|
|
503
|
+
Pokemon('Dragonite', {
|
|
504
|
+
ability: 'Multiscale',
|
|
505
|
+
}),
|
|
506
|
+
Move('Icicle Spear'),
|
|
507
|
+
);
|
|
508
|
+
expect(result.range()).toEqual([360, 430]);
|
|
509
|
+
expect(result.desc()).toBe(
|
|
510
|
+
'0 Atk Mamoswine Icicle Spear (3 hits) vs. 0 HP / 0 Def Multiscale Dragonite: 360-430 (111.4 - 133.1%) -- guaranteed OHKO'
|
|
511
|
+
);
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
inGens(5, 9, ({gen, calculate, Pokemon, Move}) => {
|
|
516
|
+
test(`Multi-hit interaction with Weak Armor (gen ${gen})`, () => {
|
|
517
|
+
let result = calculate(
|
|
518
|
+
Pokemon('Mamoswine'),
|
|
519
|
+
Pokemon('Skarmory', {
|
|
520
|
+
ability: 'Weak Armor',
|
|
521
|
+
}),
|
|
522
|
+
Move('Icicle Spear'),
|
|
523
|
+
);
|
|
524
|
+
expect(result.range()).toEqual([115, 138]);
|
|
525
|
+
expect(result.desc()).toBe(
|
|
526
|
+
'0 Atk Mamoswine Icicle Spear (3 hits) vs. 0 HP / 0 Def Weak Armor Skarmory: 115-138 (42.4 - 50.9%) -- approx. 2.7% chance to 2HKO'
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
result = calculate(
|
|
530
|
+
Pokemon('Mamoswine'),
|
|
531
|
+
Pokemon('Skarmory', {
|
|
532
|
+
ability: 'Weak Armor',
|
|
533
|
+
item: 'White Herb',
|
|
534
|
+
}),
|
|
535
|
+
Move('Icicle Spear'),
|
|
536
|
+
);
|
|
537
|
+
expect(result.range()).toEqual([89, 108]);
|
|
538
|
+
expect(result.desc()).toBe(
|
|
539
|
+
'0 Atk Mamoswine Icicle Spear (3 hits) vs. 0 HP / 0 Def White Herb Weak Armor Skarmory: 89-108 (32.8 - 39.8%) -- approx. 99.9% chance to 3HKO'
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
result = calculate(
|
|
543
|
+
Pokemon('Mamoswine'),
|
|
544
|
+
Pokemon('Skarmory', {
|
|
545
|
+
ability: 'Weak Armor',
|
|
546
|
+
item: 'White Herb',
|
|
547
|
+
boosts: {def: 2},
|
|
548
|
+
}),
|
|
549
|
+
Move('Icicle Spear'),
|
|
550
|
+
);
|
|
551
|
+
expect(result.range()).toEqual([56, 69]);
|
|
552
|
+
expect(result.desc()).toBe(
|
|
553
|
+
'0 Atk Mamoswine Icicle Spear (3 hits) vs. +2 0 HP / 0 Def Weak Armor Skarmory: 56-69 (20.6 - 25.4%) -- approx. 0.1% chance to 4HKO'
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
result = calculate(
|
|
557
|
+
Pokemon('Mamoswine', {
|
|
558
|
+
ability: 'Unaware',
|
|
559
|
+
}),
|
|
560
|
+
Pokemon('Skarmory', {
|
|
561
|
+
ability: 'Weak Armor',
|
|
562
|
+
item: 'White Herb',
|
|
563
|
+
boosts: {def: 2},
|
|
564
|
+
}),
|
|
565
|
+
Move('Icicle Spear'),
|
|
566
|
+
);
|
|
567
|
+
expect(result.range()).toEqual([75, 93]);
|
|
568
|
+
expect(result.desc()).toBe(
|
|
569
|
+
'0 Atk Unaware Mamoswine Icicle Spear (3 hits) vs. 0 HP / 0 Def Skarmory: 75-93 (27.6 - 34.3%) -- approx. 1.5% chance to 3HKO'
|
|
570
|
+
);
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
inGens(6, 9, ({gen, calculate, Pokemon, Move}) => {
|
|
575
|
+
test(`Multi-hit interaction with Mummy (gen ${gen})`, () => {
|
|
576
|
+
const result = calculate(
|
|
577
|
+
Pokemon('Pinsir-Mega'),
|
|
578
|
+
Pokemon('Cofagrigus', {
|
|
579
|
+
ability: 'Mummy',
|
|
580
|
+
}),
|
|
581
|
+
Move('Double Hit'),
|
|
582
|
+
);
|
|
583
|
+
if (gen === 6) {
|
|
584
|
+
expect(result.range()).toEqual([96, 113]);
|
|
585
|
+
expect(result.desc()).toBe(
|
|
586
|
+
'0 Atk Aerilate Pinsir-Mega Double Hit (2 hits) vs. 0 HP / 0 Def Mummy Cofagrigus: 96-113 (37.3 - 43.9%) -- approx. 3HKO'
|
|
587
|
+
);
|
|
588
|
+
} else {
|
|
589
|
+
expect(result.range()).toEqual([91, 107]);
|
|
590
|
+
expect(result.desc()).toBe(
|
|
591
|
+
'0 Atk Aerilate Pinsir-Mega Double Hit (2 hits) vs. 0 HP / 0 Def Mummy Cofagrigus: 91-107 (35.4 - 41.6%) -- approx. 3HKO'
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
inGens(7, 9, ({gen, calculate, Pokemon, Move}) => {
|
|
598
|
+
test(`Multi-hit interaction with Items (gen ${gen})`, () => {
|
|
599
|
+
let result = calculate(
|
|
600
|
+
Pokemon('Greninja'),
|
|
601
|
+
Pokemon('Gliscor', {
|
|
602
|
+
item: 'Luminous Moss',
|
|
603
|
+
}),
|
|
604
|
+
Move('Water Shuriken'),
|
|
605
|
+
);
|
|
606
|
+
expect(result.range()).toEqual([104, 126]);
|
|
607
|
+
expect(result.desc()).toBe(
|
|
608
|
+
'0 SpA Greninja Water Shuriken (15 BP) (3 hits) vs. 0 HP / 0 SpD Luminous Moss Gliscor: 104-126 (35.7 - 43.2%) -- approx. 3HKO'
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
result = calculate(
|
|
612
|
+
Pokemon('Greninja'),
|
|
613
|
+
Pokemon('Gliscor', {
|
|
614
|
+
ability: 'Simple',
|
|
615
|
+
item: 'Luminous Moss',
|
|
616
|
+
}),
|
|
617
|
+
Move('Water Shuriken'),
|
|
618
|
+
);
|
|
619
|
+
expect(result.range()).toEqual([92, 114]);
|
|
620
|
+
expect(result.desc()).toBe(
|
|
621
|
+
'0 SpA Greninja Water Shuriken (15 BP) (3 hits) vs. 0 HP / 0 SpD Luminous Moss Simple Gliscor: 92-114 (31.6 - 39.1%) -- approx. 79.4% chance to 3HKO'
|
|
622
|
+
);
|
|
623
|
+
|
|
624
|
+
result = calculate(
|
|
625
|
+
Pokemon('Greninja'),
|
|
626
|
+
Pokemon('Gliscor', {
|
|
627
|
+
ability: 'Contrary',
|
|
628
|
+
item: 'Luminous Moss',
|
|
629
|
+
}),
|
|
630
|
+
Move('Water Shuriken'),
|
|
631
|
+
);
|
|
632
|
+
expect(result.range()).toEqual([176, 210]);
|
|
633
|
+
expect(result.desc()).toBe(
|
|
634
|
+
'0 SpA Greninja Water Shuriken (15 BP) (3 hits) vs. 0 HP / 0 SpD Luminous Moss Contrary Gliscor: 176-210 (60.4 - 72.1%) -- approx. 2HKO'
|
|
635
|
+
);
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
// For the EoT tests, 5+ turns is tested separately because it uses the predictTotal instead of computeKOChance, and the code is different for each function
|
|
639
|
+
inGens(3, 9, ({gen, calculate, Pokemon, Move}) => {
|
|
640
|
+
// Mew has 100 Max Health, and Seismic Toss does 25 damage. Leftovers heals 6 HP
|
|
641
|
+
// On turn 5, Mew should be at -1 HP after the Seismic Toss. If Leftovers recovery is applied, the calc will think mew is at 5 HP, and return a 6HKO
|
|
642
|
+
test(`KOed Pokemon don't receive HP recovery after 5+ turns (gen ${gen})`, () => {
|
|
643
|
+
const chansey = Pokemon('Chansey', {
|
|
644
|
+
level: 25,
|
|
645
|
+
});
|
|
646
|
+
const mew = Pokemon('Mew', {
|
|
647
|
+
level: 30,
|
|
648
|
+
item: 'Leftovers',
|
|
649
|
+
ivs: {hp: 0},
|
|
650
|
+
});
|
|
651
|
+
const seismicToss = Move('Seismic Toss');
|
|
652
|
+
const result = calculate(chansey, mew, seismicToss);
|
|
653
|
+
expect(result.damage).toBe(25);
|
|
654
|
+
expect(result.desc()).toBe(
|
|
655
|
+
'Lvl 25 Chansey Seismic Toss vs. Lvl 30 0 HP 0 IVs Mew: 25-25 (25 - 25%) -- guaranteed 5HKO after Leftovers recovery'
|
|
656
|
+
);
|
|
657
|
+
});
|
|
658
|
+
});
|
|
659
|
+
// Similar to the last test, but for the computerKOChance function instead of predictTotal
|
|
660
|
+
inGens(3, 9, ({gen, calculate, Pokemon, Move}) => {
|
|
661
|
+
test(`KOed Pokemon don't receive HP recovery after 1-4 turns (gen ${gen})`, () => {
|
|
662
|
+
const chansey = Pokemon('Chansey', {
|
|
663
|
+
level: 55,
|
|
664
|
+
});
|
|
665
|
+
const mew = Pokemon('Mew', {
|
|
666
|
+
level: 30,
|
|
667
|
+
item: 'Leftovers',
|
|
668
|
+
ivs: {hp: 0},
|
|
669
|
+
});
|
|
670
|
+
const seismicToss = Move('Seismic Toss');
|
|
671
|
+
const result = calculate(chansey, mew, seismicToss);
|
|
672
|
+
expect(result.damage).toBe(55);
|
|
673
|
+
expect(result.desc()).toBe(
|
|
674
|
+
'Lvl 55 Chansey Seismic Toss vs. Lvl 30 0 HP 0 IVs Mew: 55-55 (55 - 55%) -- guaranteed 2HKO after Leftovers recovery'
|
|
675
|
+
);
|
|
676
|
+
});
|
|
677
|
+
});
|
|
678
|
+
inGens(3, 9, ({gen, calculate, Pokemon, Move}) => {
|
|
679
|
+
test(`End of turn damage is calculated correctly after 5+ turns (gen ${gen})`, () => {
|
|
680
|
+
const chansey = Pokemon('Chansey', {
|
|
681
|
+
level: 1,
|
|
682
|
+
});
|
|
683
|
+
const mew = Pokemon('Mew', {
|
|
684
|
+
level: 30,
|
|
685
|
+
status: 'tox',
|
|
686
|
+
toxicCounter: 1,
|
|
687
|
+
ivs: {hp: 0},
|
|
688
|
+
});
|
|
689
|
+
const seismicToss = Move('Seismic Toss');
|
|
690
|
+
const result = calculate(chansey, mew, seismicToss);
|
|
691
|
+
expect(result.damage).toBe(1);
|
|
692
|
+
expect(result.desc()).toBe(
|
|
693
|
+
'Lvl 1 Chansey Seismic Toss vs. Lvl 30 0 HP 0 IVs Mew: 1-1 (1 - 1%) -- guaranteed 6HKO after toxic damage'
|
|
694
|
+
);
|
|
695
|
+
});
|
|
696
|
+
});
|
|
697
|
+
inGens(3, 9, ({gen, calculate, Pokemon, Move, Field}) => {
|
|
698
|
+
test(`End of turn damage is calculated correctly after 1-4 turns (gen ${gen})`, () => {
|
|
699
|
+
const field = Field({
|
|
700
|
+
weather: 'Sand',
|
|
701
|
+
defenderSide: {
|
|
702
|
+
isSeeded: true,
|
|
703
|
+
},
|
|
704
|
+
});
|
|
705
|
+
const chansey = Pokemon('Chansey', {
|
|
706
|
+
level: 1,
|
|
707
|
+
});
|
|
708
|
+
const mew = Pokemon('Mew', {
|
|
709
|
+
level: 30,
|
|
710
|
+
status: 'tox',
|
|
711
|
+
toxicCounter: 1,
|
|
712
|
+
ivs: {hp: 0},
|
|
713
|
+
});
|
|
714
|
+
const seismicToss = Move('Seismic Toss');
|
|
715
|
+
const result = calculate(chansey, mew, seismicToss, field);
|
|
716
|
+
expect(result.damage).toBe(1);
|
|
717
|
+
expect(result.desc()).toBe(
|
|
718
|
+
'Lvl 1 Chansey Seismic Toss vs. Lvl 30 0 HP 0 IVs Mew: 1-1 (1 - 1%) -- guaranteed 4HKO after sandstorm damage, Leech Seed damage, and toxic damage'
|
|
719
|
+
);
|
|
720
|
+
});
|
|
721
|
+
});
|
|
722
|
+
inGens(3, 9, ({gen, calculate, Pokemon, Move, Field}) => {
|
|
723
|
+
test(`End of turn damage is calculated correctly on the first turn (gen ${gen})`, () => {
|
|
724
|
+
const field = Field({
|
|
725
|
+
weather: 'Sand',
|
|
726
|
+
});
|
|
727
|
+
const chansey = Pokemon('Chansey', {
|
|
728
|
+
level: 90,
|
|
729
|
+
});
|
|
730
|
+
const mew = Pokemon('Mew', {
|
|
731
|
+
level: 30,
|
|
732
|
+
status: 'brn',
|
|
733
|
+
ivs: {hp: 0},
|
|
734
|
+
});
|
|
735
|
+
const seismicToss = Move('Seismic Toss');
|
|
736
|
+
const result = calculate(chansey, mew, seismicToss, field);
|
|
737
|
+
expect(result.damage).toBe(90);
|
|
738
|
+
expect(result.desc()).toBe(
|
|
739
|
+
'Lvl 90 Chansey Seismic Toss vs. Lvl 30 0 HP 0 IVs Mew: 90-90 (90 - 90%) -- guaranteed OHKO after sandstorm damage and burn damage'
|
|
740
|
+
);
|
|
741
|
+
});
|
|
742
|
+
});
|
|
743
|
+
inGens(4, 9, ({gen, calculate, Pokemon, Move, Field}) => {
|
|
744
|
+
test(`Mold Breaker does not disable abilities that don't affect direct damage (gen ${gen})`, () => {
|
|
745
|
+
const attacker = Pokemon('Rampardos', {
|
|
746
|
+
ability: 'Mold Breaker',
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
const defender = Pokemon('Blastoise', {
|
|
750
|
+
ability: 'Rain Dish',
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
const field = Field({
|
|
754
|
+
weather: 'Rain',
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
const move = Move('Stone Edge');
|
|
758
|
+
|
|
759
|
+
const result = calculate(attacker, defender, move, field);
|
|
760
|
+
|
|
761
|
+
expect(result.defender.ability).toBe('Rain Dish');
|
|
762
|
+
|
|
763
|
+
expect(result.desc()).toBe(
|
|
764
|
+
'0 Atk Rampardos Stone Edge vs. 0 HP / 0 Def Blastoise: 168-198 (56.1 - 66.2%) -- guaranteed 2HKO after Rain Dish recovery'
|
|
765
|
+
);
|
|
766
|
+
});
|
|
767
|
+
});
|
|
768
|
+
inGens(8, 9, ({gen, calculate, Pokemon, Move, Field}) => {
|
|
769
|
+
test('Steely Spirit should boost Steel-type moves as a field effect.', () => {
|
|
770
|
+
const pokemon = Pokemon('Perrserker', {
|
|
771
|
+
ability: 'Battle Armor',
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
const move = Move('Iron Head');
|
|
775
|
+
|
|
776
|
+
let result = calculate(pokemon, pokemon, move);
|
|
777
|
+
|
|
778
|
+
expect(result.desc()).toBe(
|
|
779
|
+
'0 Atk Perrserker Iron Head vs. 0 HP / 0 Def Perrserker: 46-55 (16.3 - 19.5%) -- possible 6HKO'
|
|
780
|
+
);
|
|
781
|
+
|
|
782
|
+
const field = Field({attackerSide: {isSteelySpirit: true}});
|
|
783
|
+
|
|
784
|
+
result = calculate(pokemon, pokemon, move, field);
|
|
785
|
+
|
|
786
|
+
expect(result.desc()).toBe(
|
|
787
|
+
'0 Atk Perrserker with an ally\'s Steely Spirit Iron Head vs. 0 HP / 0 Def Perrserker: 70-83 (24.9 - 29.5%) -- 99.9% chance to 4HKO'
|
|
788
|
+
);
|
|
789
|
+
|
|
790
|
+
pokemon.ability = 'Steely Spirit' as AbilityName;
|
|
791
|
+
|
|
792
|
+
result = calculate(pokemon, pokemon, move, field);
|
|
793
|
+
|
|
794
|
+
expect(result.desc()).toBe(
|
|
795
|
+
'0 Atk Steely Spirit Perrserker with an ally\'s Steely Spirit Iron Head vs. 0 HP / 0 Def Perrserker: 105-124 (37.3 - 44.1%) -- guaranteed 3HKO'
|
|
796
|
+
);
|
|
797
|
+
});
|
|
798
|
+
});
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
describe('Gen 1', () => {
|
|
803
|
+
inGen(1, ({calculate, Pokemon, Move, Field}) => {
|
|
804
|
+
test('Basic: Gengar vs. Chansey', () => {
|
|
805
|
+
const result = calculate(Pokemon('Gengar'), Pokemon('Chansey'), Move('Thunderbolt'));
|
|
806
|
+
expect(result.range()).toEqual([79, 94]);
|
|
807
|
+
expect(result.desc()).toBe(
|
|
808
|
+
'Gengar Thunderbolt vs. Chansey: 79-94 (11.2 - 13.3%) -- possible 8HKO'
|
|
809
|
+
);
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
test('Light Screen', () => {
|
|
813
|
+
const field = Field({defenderSide: {isLightScreen: true}});
|
|
814
|
+
const result = calculate(Pokemon('Gengar'), Pokemon('Vulpix'), Move('Surf'), field);
|
|
815
|
+
expect(result.desc()).toBe(
|
|
816
|
+
'Gengar Surf vs. Vulpix through Light Screen: 108-128 (38.7 - 45.8%) -- guaranteed 3HKO'
|
|
817
|
+
);
|
|
818
|
+
});
|
|
819
|
+
});
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
describe('Gen 2', () => {
|
|
823
|
+
inGen(2, ({calculate, Pokemon, Move, Field}) => {
|
|
824
|
+
test('Basic: Gengar vs. Chansey', () => {
|
|
825
|
+
const result = calculate(
|
|
826
|
+
Pokemon('Gengar'),
|
|
827
|
+
Pokemon('Chansey', {item: 'Leftovers'}),
|
|
828
|
+
Move('Dynamic Punch')
|
|
829
|
+
);
|
|
830
|
+
expect(result.range()).toEqual([304, 358]);
|
|
831
|
+
expect(result.desc()).toBe(
|
|
832
|
+
'Gengar Dynamic Punch vs. Chansey: 304-358 (43.2 - 50.9%) -- guaranteed 3HKO after Leftovers recovery'
|
|
833
|
+
);
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
test('Struggle', () => {
|
|
837
|
+
const attacker = Pokemon('Skarmory', {boosts: {atk: 6, def: 6}});
|
|
838
|
+
const defender = Pokemon('Skarmory', {boosts: {atk: 6, def: 6}});
|
|
839
|
+
const move = Move('Struggle');
|
|
840
|
+
const result = calculate(attacker, defender, move);
|
|
841
|
+
expect(result.range()).toEqual([37, 44]);
|
|
842
|
+
expect(result.desc()).toBe(
|
|
843
|
+
'+6 Skarmory Struggle vs. +6 Skarmory: 37-44 (11.1 - 13.2%) -- possible 8HKO'
|
|
844
|
+
);
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
test('Present', () => {
|
|
848
|
+
const attacker = Pokemon('Togepi', {level: 5, boosts: {atk: -6}, status: 'brn'});
|
|
849
|
+
const defender = Pokemon('Umbreon', {boosts: {def: 6}});
|
|
850
|
+
const move = Move('Present');
|
|
851
|
+
const field = Field({defenderSide: {isReflect: true}});
|
|
852
|
+
const result = calculate(attacker, defender, move, field);
|
|
853
|
+
expect(result.range()).toEqual([125, 147]);
|
|
854
|
+
expect(result.desc()).toBe(
|
|
855
|
+
'-6 Lvl 5 burned Togepi Present vs. +6 Umbreon through Reflect: 125-147 (31.8 - 37.4%) -- 89.1% chance to 3HKO'
|
|
856
|
+
);
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
test('DVs', () => {
|
|
860
|
+
const aerodactyl = Pokemon('Aerodactyl');
|
|
861
|
+
const zapdos = Pokemon('Zapdos', {ivs: {atk: 29, def: 27}, item: 'Leftovers'});
|
|
862
|
+
expect(zapdos.ivs.hp).toBe(14);
|
|
863
|
+
|
|
864
|
+
const move = Move('Ancient Power');
|
|
865
|
+
const result = calculate(aerodactyl, zapdos, move);
|
|
866
|
+
expect(result.range()).toEqual([153, 180]);
|
|
867
|
+
expect(result.desc()).toBe(
|
|
868
|
+
'Aerodactyl Ancient Power vs. Zapdos: 153-180 (41.6 - 49%) -- guaranteed 3HKO after Leftovers recovery'
|
|
869
|
+
);
|
|
870
|
+
});
|
|
871
|
+
});
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
describe('Gen 3', () => {
|
|
875
|
+
inGen(3, ({calculate, Pokemon, Move, Field}) => {
|
|
876
|
+
test('Basic: Gengar vs. Chansey', () => {
|
|
877
|
+
const result = calculate(
|
|
878
|
+
Pokemon('Gengar', {
|
|
879
|
+
nature: 'Mild',
|
|
880
|
+
evs: {atk: 100},
|
|
881
|
+
}),
|
|
882
|
+
Pokemon('Chansey', {
|
|
883
|
+
item: 'Leftovers',
|
|
884
|
+
nature: 'Bold',
|
|
885
|
+
evs: {hp: 252, def: 252},
|
|
886
|
+
}),
|
|
887
|
+
Move('Focus Punch')
|
|
888
|
+
);
|
|
889
|
+
expect(result.range()).toEqual([346, 408]);
|
|
890
|
+
expect(result.desc()).toBe(
|
|
891
|
+
'100 Atk Gengar Focus Punch vs. 252 HP / 252+ Def Chansey: 346-408 (49.1 - 57.9%) -- 59% chance to 2HKO after Leftovers recovery'
|
|
892
|
+
);
|
|
893
|
+
});
|
|
894
|
+
test('Water Absorb', () => {
|
|
895
|
+
const cacturne = Pokemon('Cacturne', {
|
|
896
|
+
ability: 'Sand Veil',
|
|
897
|
+
});
|
|
898
|
+
const blastoise = Pokemon('Blastoise', {
|
|
899
|
+
evs: {spa: 252},
|
|
900
|
+
});
|
|
901
|
+
const surf = Move('Surf');
|
|
902
|
+
|
|
903
|
+
let result = calculate(blastoise, cacturne, surf);
|
|
904
|
+
expect(result.range()).toEqual([88, 104]);
|
|
905
|
+
expect(result.desc()).toBe(
|
|
906
|
+
'252 SpA Blastoise Surf vs. 0 HP / 0 SpD Cacturne: 88-104 (31.3 - 37%) -- 76.6% chance to 3HKO'
|
|
907
|
+
);
|
|
908
|
+
|
|
909
|
+
cacturne.ability = 'Water Absorb' as AbilityName;
|
|
910
|
+
result = calculate(blastoise, cacturne, surf);
|
|
911
|
+
expect(result.damage).toBe(0);
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
describe('Spread Moves', () => {
|
|
915
|
+
test('allAdjacent', () => {
|
|
916
|
+
const gengar = Pokemon('Gengar', {nature: 'Mild', evs: {atk: 100}});
|
|
917
|
+
const blissey = Pokemon('Chansey', {
|
|
918
|
+
item: 'Leftovers',
|
|
919
|
+
nature: 'Bold',
|
|
920
|
+
evs: {hp: 252, def: 252},
|
|
921
|
+
});
|
|
922
|
+
const field = Field({gameType: 'Doubles'});
|
|
923
|
+
const result = calculate(gengar, blissey, Move('Explosion'), field);
|
|
924
|
+
expect(result.range()).toEqual([578, 681]);
|
|
925
|
+
expect(result.desc()).toBe(
|
|
926
|
+
'100 Atk Gengar Explosion vs. 252 HP / 252+ Def Chansey: 578-681 (82.1 - 96.7%) -- guaranteed 2HKO after Leftovers recovery'
|
|
927
|
+
);
|
|
928
|
+
});
|
|
929
|
+
test('allAdjacentFoes', () => {
|
|
930
|
+
const gengar = Pokemon('Gengar', {nature: 'Modest', evs: {spa: 252}});
|
|
931
|
+
const blissey = Pokemon('Chansey', {
|
|
932
|
+
item: 'Leftovers',
|
|
933
|
+
nature: 'Bold',
|
|
934
|
+
evs: {hp: 252, def: 252},
|
|
935
|
+
});
|
|
936
|
+
const field = Field({gameType: 'Doubles'});
|
|
937
|
+
const result = calculate(gengar, blissey, Move('Blizzard'), field);
|
|
938
|
+
expect(result.range()).toEqual([69, 82]);
|
|
939
|
+
expect(result.desc()).toBe(
|
|
940
|
+
'252+ SpA Gengar Blizzard vs. 252 HP / 0 SpD Chansey: 69-82 (9.8 - 11.6%)'
|
|
941
|
+
);
|
|
942
|
+
});
|
|
943
|
+
});
|
|
944
|
+
});
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
describe('Gen 4', () => {
|
|
948
|
+
inGen(4, ({calculate, Pokemon, Move}) => {
|
|
949
|
+
test('Basic: Gengar vs. Chansey', () => {
|
|
950
|
+
const result = calculate(
|
|
951
|
+
Pokemon('Gengar', {
|
|
952
|
+
item: 'Choice Specs',
|
|
953
|
+
nature: 'Timid',
|
|
954
|
+
evs: {spa: 252},
|
|
955
|
+
boosts: {spa: 1},
|
|
956
|
+
}),
|
|
957
|
+
Pokemon('Chansey', {
|
|
958
|
+
item: 'Leftovers',
|
|
959
|
+
nature: 'Calm',
|
|
960
|
+
evs: {hp: 252, spd: 252},
|
|
961
|
+
}),
|
|
962
|
+
Move('Focus Blast')
|
|
963
|
+
);
|
|
964
|
+
expect(result.range()).toEqual([408, 482]);
|
|
965
|
+
expect(result.desc()).toBe(
|
|
966
|
+
'+1 252 SpA Choice Specs Gengar Focus Blast vs. 252 HP / 252+ SpD Chansey: 408-482 (57.9 - 68.4%) -- guaranteed 2HKO after Leftovers recovery'
|
|
967
|
+
);
|
|
968
|
+
});
|
|
969
|
+
test('Mold Breaker', () => {
|
|
970
|
+
const pinsir = Pokemon('Pinsir', {
|
|
971
|
+
item: 'Choice Band',
|
|
972
|
+
nature: 'Adamant',
|
|
973
|
+
ability: 'Hyper Cutter',
|
|
974
|
+
evs: {atk: 252},
|
|
975
|
+
});
|
|
976
|
+
const gengar = Pokemon('Gengar', {
|
|
977
|
+
item: 'Choice Specs',
|
|
978
|
+
nature: 'Timid',
|
|
979
|
+
evs: {spa: 252},
|
|
980
|
+
boosts: {spa: 1},
|
|
981
|
+
});
|
|
982
|
+
const earthquake = Move('Earthquake');
|
|
983
|
+
|
|
984
|
+
let result = calculate(pinsir, gengar, earthquake);
|
|
985
|
+
expect(result.damage).toBe(0);
|
|
986
|
+
|
|
987
|
+
pinsir.ability = 'Mold Breaker' as AbilityName;
|
|
988
|
+
result = calculate(pinsir, gengar, earthquake);
|
|
989
|
+
expect(result.range()).toEqual([528, 622]);
|
|
990
|
+
expect(result.desc()).toBe(
|
|
991
|
+
'252+ Atk Choice Band Mold Breaker Pinsir Earthquake vs. 0 HP / 0 Def Gengar: 528-622 (202.2 - 238.3%) -- guaranteed OHKO'
|
|
992
|
+
);
|
|
993
|
+
|
|
994
|
+
pinsir.boosts.atk = 2;
|
|
995
|
+
gengar.ability = 'Unaware' as AbilityName;
|
|
996
|
+
result = calculate(pinsir, gengar, earthquake);
|
|
997
|
+
expect(result.range()).toEqual([1054, 1240]);
|
|
998
|
+
});
|
|
999
|
+
});
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
describe('Gen 5', () => {
|
|
1003
|
+
inGen(5, ({calculate, Pokemon, Move}) => {
|
|
1004
|
+
test('Basic: Gengar vs. Chansey', () => {
|
|
1005
|
+
const result = calculate(
|
|
1006
|
+
Pokemon('Gengar', {
|
|
1007
|
+
item: 'Choice Specs',
|
|
1008
|
+
nature: 'Timid',
|
|
1009
|
+
evs: {spa: 252},
|
|
1010
|
+
boosts: {spa: 1},
|
|
1011
|
+
}),
|
|
1012
|
+
Pokemon('Chansey', {
|
|
1013
|
+
item: 'Eviolite',
|
|
1014
|
+
nature: 'Calm',
|
|
1015
|
+
evs: {hp: 252, spd: 252},
|
|
1016
|
+
}),
|
|
1017
|
+
Move('Focus Blast')
|
|
1018
|
+
);
|
|
1019
|
+
expect(result.range()).toEqual([274, 324]);
|
|
1020
|
+
expect(result.fullDesc('px')).toBe(
|
|
1021
|
+
'+1 252 SpA Choice Specs Gengar Focus Blast vs. 252 HP / 252+ SpD Eviolite Chansey: 274-324 (18 - 22px) -- guaranteed 3HKO'
|
|
1022
|
+
);
|
|
1023
|
+
});
|
|
1024
|
+
test('Technician with Low Kick', () => {
|
|
1025
|
+
const ambipom = Pokemon('Ambipom', {level: 50, ability: 'Technician'});
|
|
1026
|
+
const blissey = Pokemon('Blissey', {level: 50, evs: {hp: 252}});
|
|
1027
|
+
let result = calculate(ambipom, blissey, Move('Low Kick'));
|
|
1028
|
+
expect(result.range()).toEqual([272, 320]);
|
|
1029
|
+
expect(result.desc()).toBe(
|
|
1030
|
+
'0 Atk Technician Ambipom Low Kick (60 BP) vs. 252 HP / 0 Def Blissey: 272-320 (75.1 - 88.3%) -- guaranteed 2HKO'
|
|
1031
|
+
);
|
|
1032
|
+
|
|
1033
|
+
const aggron = Pokemon('Aggron', {level: 50, evs: {hp: 252}});
|
|
1034
|
+
result = calculate(ambipom, aggron, Move('Low Kick'));
|
|
1035
|
+
expect(result.range()).toEqual([112, 132]);
|
|
1036
|
+
expect(result.desc()).toBe(
|
|
1037
|
+
'0 Atk Ambipom Low Kick (120 BP) vs. 252 HP / 0 Def Aggron: 112-132 (63.2 - 74.5%) -- guaranteed 2HKO'
|
|
1038
|
+
);
|
|
1039
|
+
});
|
|
1040
|
+
});
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
describe('Gen 6', () => {
|
|
1044
|
+
inGen(6, ({calculate, Pokemon, Move}) => {
|
|
1045
|
+
test('Basic: Gengar vs. Chansey', () => {
|
|
1046
|
+
const result = calculate(
|
|
1047
|
+
Pokemon('Gengar', {
|
|
1048
|
+
item: 'Life Orb',
|
|
1049
|
+
nature: 'Modest',
|
|
1050
|
+
evs: {spa: 252},
|
|
1051
|
+
}),
|
|
1052
|
+
Pokemon('Chansey', {
|
|
1053
|
+
item: 'Eviolite',
|
|
1054
|
+
nature: 'Bold',
|
|
1055
|
+
evs: {hp: 252, def: 252},
|
|
1056
|
+
}),
|
|
1057
|
+
Move('Sludge Bomb')
|
|
1058
|
+
);
|
|
1059
|
+
expect(result.range()).toEqual([134, 160]);
|
|
1060
|
+
expect(result.desc()).toBe(
|
|
1061
|
+
'252+ SpA Life Orb Gengar Sludge Bomb vs. 252 HP / 0 SpD Eviolite Chansey: 134-160 (19 - 22.7%) -- possible 5HKO'
|
|
1062
|
+
);
|
|
1063
|
+
});
|
|
1064
|
+
});
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
describe('Gen 7', () => {
|
|
1068
|
+
inGen(7, ({calculate, Pokemon, Move, Field}) => {
|
|
1069
|
+
const abomasnow = Pokemon('Abomasnow', {
|
|
1070
|
+
item: 'Icy Rock',
|
|
1071
|
+
ability: 'Snow Warning',
|
|
1072
|
+
nature: 'Hasty',
|
|
1073
|
+
evs: {atk: 252, spd: 4, spe: 252},
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
const hoopa = Pokemon('Hoopa-Unbound', {
|
|
1077
|
+
item: 'Choice Band',
|
|
1078
|
+
ability: 'Magician',
|
|
1079
|
+
nature: 'Jolly',
|
|
1080
|
+
evs: {hp: 32, atk: 224, spe: 252},
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
test('Basic: Gengar vs. Chansey', () => {
|
|
1084
|
+
const result = calculate(
|
|
1085
|
+
Pokemon('Gengar', {
|
|
1086
|
+
item: 'Life Orb',
|
|
1087
|
+
nature: 'Modest',
|
|
1088
|
+
evs: {spa: 252},
|
|
1089
|
+
boosts: {spa: 3},
|
|
1090
|
+
}),
|
|
1091
|
+
Pokemon('Chansey', {
|
|
1092
|
+
item: 'Eviolite',
|
|
1093
|
+
nature: 'Bold',
|
|
1094
|
+
evs: {hp: 100, spd: 100},
|
|
1095
|
+
boosts: {spd: 1},
|
|
1096
|
+
}),
|
|
1097
|
+
Move('Sludge Bomb')
|
|
1098
|
+
);
|
|
1099
|
+
expect(result.range()).toEqual([204, 242]);
|
|
1100
|
+
expect(result.desc()).toBe(
|
|
1101
|
+
'+3 252+ SpA Life Orb Gengar Sludge Bomb vs. +1 100 HP / 100 SpD Eviolite Chansey: 204-242 (30.6 - 36.3%) -- 52.9% chance to 3HKO'
|
|
1102
|
+
);
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
test('Z-Move critical hits', () => {
|
|
1106
|
+
const zMove = Move('Wood Hammer', {useZ: true, isCrit: true});
|
|
1107
|
+
const result = calculate(abomasnow, hoopa, zMove);
|
|
1108
|
+
expect(result.range()).toEqual([555, 654]);
|
|
1109
|
+
expect(result.desc()).toBe(
|
|
1110
|
+
'252 Atk Abomasnow Bloom Doom (190 BP) vs. 32 HP / 0 Def Hoopa-Unbound on a critical hit: 555-654 (179.6 - 211.6%) -- guaranteed OHKO'
|
|
1111
|
+
);
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
test('Recoil & Recovery', () => {
|
|
1115
|
+
let result = calculate(abomasnow, hoopa, Move('Wood Hammer'));
|
|
1116
|
+
expect(result.range()).toEqual([234, 276]);
|
|
1117
|
+
expect(result.desc()).toBe(
|
|
1118
|
+
'252 Atk Abomasnow Wood Hammer vs. 32 HP / 0 Def Hoopa-Unbound: 234-276 (75.7 - 89.3%) -- guaranteed 2HKO'
|
|
1119
|
+
);
|
|
1120
|
+
const recoil = result.recoil();
|
|
1121
|
+
expect(recoil.recoil).toEqual([24, 28.3]);
|
|
1122
|
+
expect(recoil.text).toBe('24 - 28.3% recoil damage');
|
|
1123
|
+
|
|
1124
|
+
result = calculate(hoopa, abomasnow, Move('Drain Punch'));
|
|
1125
|
+
expect(result.range()).toEqual([398, 470]);
|
|
1126
|
+
expect(result.desc()).toBe(
|
|
1127
|
+
'224 Atk Choice Band Hoopa-Unbound Drain Punch vs. 0 HP / 0- Def Abomasnow: 398-470 (123.9 - 146.4%) -- guaranteed OHKO'
|
|
1128
|
+
);
|
|
1129
|
+
const recovery = result.recovery();
|
|
1130
|
+
expect(recovery.recovery).toEqual([161, 161]);
|
|
1131
|
+
expect(recovery.text).toBe('52.1 - 52.1% recovered');
|
|
1132
|
+
});
|
|
1133
|
+
test('Big Root', () => {
|
|
1134
|
+
const bigRoot = Pokemon('Blissey', {item: 'Big Root'});
|
|
1135
|
+
const result = calculate(bigRoot, abomasnow, Move('Drain Punch'));
|
|
1136
|
+
expect(result.range()).toEqual([38, 46]);
|
|
1137
|
+
expect(result.recovery().recovery).toEqual([24, 29]);
|
|
1138
|
+
});
|
|
1139
|
+
test('Loaded Field', () => {
|
|
1140
|
+
const field = Field({
|
|
1141
|
+
gameType: 'Doubles',
|
|
1142
|
+
terrain: 'Grassy',
|
|
1143
|
+
weather: 'Hail',
|
|
1144
|
+
defenderSide: {
|
|
1145
|
+
isSR: true,
|
|
1146
|
+
spikes: 1,
|
|
1147
|
+
isLightScreen: true,
|
|
1148
|
+
isSeeded: true,
|
|
1149
|
+
isFriendGuard: true,
|
|
1150
|
+
},
|
|
1151
|
+
attackerSide: {
|
|
1152
|
+
isHelpingHand: true,
|
|
1153
|
+
isTailwind: true,
|
|
1154
|
+
},
|
|
1155
|
+
});
|
|
1156
|
+
const result = calculate(abomasnow, hoopa, Move('Blizzard'), field);
|
|
1157
|
+
expect(result.range()).toEqual([50, 59]);
|
|
1158
|
+
expect(result.desc()).toBe(
|
|
1159
|
+
'0 SpA Abomasnow Helping Hand Blizzard vs. 32 HP / 0 SpD Hoopa-Unbound through Light Screen with an ally\'s Friend Guard: 50-59 (16.1 - 19%)' +
|
|
1160
|
+
' -- guaranteed 3HKO after Stealth Rock, 1 layer of Spikes, hail damage, Leech Seed damage, and Grassy Terrain recovery'
|
|
1161
|
+
);
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
test('Wring Out', () => {
|
|
1165
|
+
const smeargle = Pokemon('Smeargle', {level: 50, ability: 'Technician'});
|
|
1166
|
+
const blissey = Pokemon('Blissey', {level: 50, evs: {hp: 252}, curHP: 184});
|
|
1167
|
+
const result = calculate(smeargle, blissey, Move('Wring Out'));
|
|
1168
|
+
expect(result.range()).toEqual([15, 18]);
|
|
1169
|
+
expect(result.desc()).toBe(
|
|
1170
|
+
'0 SpA Technician Smeargle Wring Out (60 BP) vs. 252 HP / 0 SpD Blissey: 15-18 (4.1 - 4.9%)'
|
|
1171
|
+
);
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1174
|
+
test('Mold Breaker', () => {
|
|
1175
|
+
const pinsir = Pokemon('Pinsir', {
|
|
1176
|
+
item: 'Choice Band',
|
|
1177
|
+
nature: 'Adamant',
|
|
1178
|
+
ability: 'Hyper Cutter',
|
|
1179
|
+
evs: {atk: 252},
|
|
1180
|
+
});
|
|
1181
|
+
const gengar = Pokemon('Gengar', {
|
|
1182
|
+
item: 'Choice Specs',
|
|
1183
|
+
nature: 'Timid',
|
|
1184
|
+
ability: 'Levitate',
|
|
1185
|
+
evs: {spa: 252},
|
|
1186
|
+
boosts: {spa: 1},
|
|
1187
|
+
});
|
|
1188
|
+
const earthquake = Move('Earthquake');
|
|
1189
|
+
|
|
1190
|
+
let result = calculate(pinsir, gengar, earthquake);
|
|
1191
|
+
expect(result.damage).toBe(0);
|
|
1192
|
+
|
|
1193
|
+
pinsir.ability = 'Mold Breaker' as AbilityName;
|
|
1194
|
+
result = calculate(pinsir, gengar, earthquake);
|
|
1195
|
+
expect(result.range()).toEqual([528, 622]);
|
|
1196
|
+
expect(result.desc()).toBe(
|
|
1197
|
+
'252+ Atk Choice Band Mold Breaker Pinsir Earthquake vs. 0 HP / 0 Def Gengar: 528-622 (202.2 - 238.3%) -- guaranteed OHKO'
|
|
1198
|
+
);
|
|
1199
|
+
|
|
1200
|
+
pinsir.boosts.atk = 2;
|
|
1201
|
+
gengar.ability = 'Unaware' as AbilityName;
|
|
1202
|
+
result = calculate(pinsir, gengar, earthquake);
|
|
1203
|
+
expect(result.range()).toEqual([1054, 1240]);
|
|
1204
|
+
});
|
|
1205
|
+
|
|
1206
|
+
test('16-bit Overflow', () => {
|
|
1207
|
+
const result = calculate(
|
|
1208
|
+
Pokemon('Mewtwo-Mega-Y', {evs: {spa: 196}}),
|
|
1209
|
+
Pokemon('Wynaut', {level: 1, boosts: {spd: -6}}),
|
|
1210
|
+
Move('Fire Blast'),
|
|
1211
|
+
Field({attackerSide: {isHelpingHand: true}})
|
|
1212
|
+
);
|
|
1213
|
+
expect(result.damage).toEqual([
|
|
1214
|
+
55725, 56380, 57036, 57691,
|
|
1215
|
+
58347, 59003, 59658, 60314,
|
|
1216
|
+
60969, 61625, 62281, 62936,
|
|
1217
|
+
63592, 64247, 64903, 23, // <- overflow: 65559 & 0xFFFF
|
|
1218
|
+
]);
|
|
1219
|
+
});
|
|
1220
|
+
|
|
1221
|
+
test('32-bit Overflow', () => {
|
|
1222
|
+
let kyogre = Pokemon('Kyogre', {
|
|
1223
|
+
ability: 'Water Bubble',
|
|
1224
|
+
item: 'Choice Specs',
|
|
1225
|
+
curHP: 340, // we need 149 base power Water Spout
|
|
1226
|
+
ivs: {spa: 6}, // we need 311 Spa
|
|
1227
|
+
boosts: {spa: 6},
|
|
1228
|
+
});
|
|
1229
|
+
const wynaut = Pokemon('Wynaut', {level: 1, boosts: {spd: -6}});
|
|
1230
|
+
const waterSpout = Move('Water Spout');
|
|
1231
|
+
const field = Field({weather: 'Rain', attackerSide: {isHelpingHand: true}});
|
|
1232
|
+
|
|
1233
|
+
expect(calculate(kyogre, wynaut, waterSpout, field).range()).toEqual([55, 66]);
|
|
1234
|
+
|
|
1235
|
+
kyogre = Pokemon('Kyogre', {...kyogre, overrides: {types: ['Normal']}});
|
|
1236
|
+
expect(calculate(kyogre, wynaut, waterSpout, field).range()).toEqual([37, 44]);
|
|
1237
|
+
});
|
|
1238
|
+
});
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
describe('Gen 8', () => {
|
|
1242
|
+
inGen(8, ({calculate, Pokemon, Move, Field}) => {
|
|
1243
|
+
test('Basic: Gengar vs. Chansey', () => {
|
|
1244
|
+
const result = calculate(
|
|
1245
|
+
Pokemon('Gengar', {
|
|
1246
|
+
item: 'Life Orb',
|
|
1247
|
+
nature: 'Modest',
|
|
1248
|
+
evs: {spa: 252},
|
|
1249
|
+
boosts: {spa: 3},
|
|
1250
|
+
}),
|
|
1251
|
+
Pokemon('Chansey', {
|
|
1252
|
+
item: 'Eviolite',
|
|
1253
|
+
nature: 'Bold',
|
|
1254
|
+
evs: {hp: 100, spd: 100},
|
|
1255
|
+
boosts: {spd: 1},
|
|
1256
|
+
}),
|
|
1257
|
+
Move('Sludge Bomb')
|
|
1258
|
+
);
|
|
1259
|
+
expect(result.range()).toEqual([204, 242]);
|
|
1260
|
+
expect(result.desc()).toBe(
|
|
1261
|
+
'+3 252+ SpA Life Orb Gengar Sludge Bomb vs. +1 100 HP / 100 SpD Eviolite Chansey: 204-242 (30.6 - 36.3%) -- 52.9% chance to 3HKO'
|
|
1262
|
+
);
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
test('Knock Off vs. Silvally', () => {
|
|
1266
|
+
const sawk = Pokemon('Sawk', {ability: 'Mold Breaker', evs: {atk: 252}});
|
|
1267
|
+
const silvally = Pokemon('Silvally-Dark', {item: 'Dark Memory'});
|
|
1268
|
+
const knockoff = Move('Knock Off');
|
|
1269
|
+
const result = calculate(sawk, silvally, knockoff);
|
|
1270
|
+
expect(result.desc()).toBe(
|
|
1271
|
+
'252 Atk Sawk Knock Off vs. 0 HP / 0 Def Silvally-Dark: 36-43 (10.8 - 12.9%) -- possible 8HKO'
|
|
1272
|
+
);
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
test('-ate Abilities', () => {
|
|
1276
|
+
const sylveon = Pokemon('Sylveon', {ability: 'Pixilate', evs: {spa: 252}});
|
|
1277
|
+
const silvally = Pokemon('Silvally');
|
|
1278
|
+
const hypervoice = Move('Hyper Voice');
|
|
1279
|
+
const result = calculate(sylveon, silvally, hypervoice);
|
|
1280
|
+
expect(result.desc()).toBe(
|
|
1281
|
+
'252 SpA Pixilate Sylveon Hyper Voice vs. 0 HP / 0 SpD Silvally: 165-195 (49.8 - 58.9%) -- 99.6% chance to 2HKO'
|
|
1282
|
+
);
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
test('% chance to OHKO', () => {
|
|
1286
|
+
const abomasnow = Pokemon('Abomasnow', {
|
|
1287
|
+
level: 55,
|
|
1288
|
+
item: 'Choice Specs',
|
|
1289
|
+
evs: {spa: 252},
|
|
1290
|
+
});
|
|
1291
|
+
const deerling = Pokemon('Deerling', {evs: {hp: 36}});
|
|
1292
|
+
const blizzard = Move('Blizzard');
|
|
1293
|
+
const hail = Field({weather: 'Hail'});
|
|
1294
|
+
const result = calculate(abomasnow, deerling, blizzard, hail);
|
|
1295
|
+
expect(result.desc()).toBe(
|
|
1296
|
+
'Lvl 55 252 SpA Choice Specs Abomasnow Blizzard vs. 36 HP / 0 SpD Deerling: 236-278 (87.4 - 102.9%) -- 25% chance to OHKO (56.3% chance to OHKO after hail damage)'
|
|
1297
|
+
);
|
|
1298
|
+
});
|
|
1299
|
+
|
|
1300
|
+
test('% chance to OHKO with Leftovers', () => {
|
|
1301
|
+
const kyurem = Pokemon('Kyurem', {
|
|
1302
|
+
level: 100,
|
|
1303
|
+
item: 'Choice Specs',
|
|
1304
|
+
evs: {spa: 252},
|
|
1305
|
+
});
|
|
1306
|
+
const jirachi = Pokemon('Jirachi', {item: 'Leftovers'});
|
|
1307
|
+
const earthpower = Move('Earth Power');
|
|
1308
|
+
const result = calculate(kyurem, jirachi, earthpower);
|
|
1309
|
+
expect(result.desc()).toBe(
|
|
1310
|
+
'252 SpA Choice Specs Kyurem Earth Power vs. 0 HP / 0 SpD Jirachi: 294-348 (86.2 - 102%) -- 12.5% chance to OHKO'
|
|
1311
|
+
);
|
|
1312
|
+
});
|
|
1313
|
+
|
|
1314
|
+
test('Technician with Low Kick', () => {
|
|
1315
|
+
const ambipom = Pokemon('Ambipom', {level: 50, ability: 'Technician'});
|
|
1316
|
+
const blissey = Pokemon('Blissey', {level: 50, evs: {hp: 252}});
|
|
1317
|
+
let result = calculate(ambipom, blissey, Move('Low Kick'));
|
|
1318
|
+
expect(result.range()).toEqual([272, 320]);
|
|
1319
|
+
expect(result.desc()).toBe(
|
|
1320
|
+
'0 Atk Technician Ambipom Low Kick (60 BP) vs. 252 HP / 0 Def Blissey: 272-320 (75.1 - 88.3%) -- guaranteed 2HKO'
|
|
1321
|
+
);
|
|
1322
|
+
|
|
1323
|
+
const aggron = Pokemon('Aggron', {level: 50, evs: {hp: 252}});
|
|
1324
|
+
result = calculate(ambipom, aggron, Move('Low Kick'));
|
|
1325
|
+
expect(result.range()).toEqual([112, 132]);
|
|
1326
|
+
expect(result.desc()).toBe(
|
|
1327
|
+
'0 Atk Ambipom Low Kick (120 BP) vs. 252 HP / 0 Def Aggron: 112-132 (63.2 - 74.5%) -- guaranteed 2HKO'
|
|
1328
|
+
);
|
|
1329
|
+
});
|
|
1330
|
+
});
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
describe('Gen 9', () => {
|
|
1334
|
+
inGen(9, ({calculate, Pokemon, Move, Field}) => {
|
|
1335
|
+
test('Supreme Overlord', () => {
|
|
1336
|
+
const kingambit = Pokemon('Kingambit', {level: 100, ability: 'Supreme Overlord', alliesFainted: 0});
|
|
1337
|
+
const aggron = Pokemon('Aggron', {level: 100});
|
|
1338
|
+
let result = calculate(kingambit, aggron, Move('Iron Head'));
|
|
1339
|
+
expect(result.range()).toEqual([67, 79]);
|
|
1340
|
+
expect(result.desc()).toBe(
|
|
1341
|
+
'0 Atk Kingambit Iron Head vs. 0 HP / 0 Def Aggron: 67-79 (23.8 - 28.1%) -- 91.2% chance to 4HKO'
|
|
1342
|
+
);
|
|
1343
|
+
kingambit.alliesFainted = 5;
|
|
1344
|
+
result = calculate(kingambit, aggron, Move('Iron Head'));
|
|
1345
|
+
expect(result.range()).toEqual([100, 118]);
|
|
1346
|
+
expect(result.desc()).toBe(
|
|
1347
|
+
'0 Atk Supreme Overlord 5 allies fainted Kingambit Iron Head vs. 0 HP / 0 Def Aggron: 100-118 (35.5 - 41.9%) -- guaranteed 3HKO'
|
|
1348
|
+
);
|
|
1349
|
+
kingambit.alliesFainted = 10;
|
|
1350
|
+
result = calculate(kingambit, aggron, Move('Iron Head'));
|
|
1351
|
+
expect(result.range()).toEqual([100, 118]);
|
|
1352
|
+
expect(result.desc()).toBe(
|
|
1353
|
+
'0 Atk Supreme Overlord 5 allies fainted Kingambit Iron Head vs. 0 HP / 0 Def Aggron: 100-118 (35.5 - 41.9%) -- guaranteed 3HKO'
|
|
1354
|
+
);
|
|
1355
|
+
});
|
|
1356
|
+
test('Electro Drift/Collision Course boost on Super Effective hits', () => {
|
|
1357
|
+
const attacker = Pokemon('Arceus'); // same stats in each offense, does not get stab on fighting or electric
|
|
1358
|
+
let defender = Pokemon('Mew'); // neutral to both
|
|
1359
|
+
const calc = (move = Move('Electro Drift')) => calculate(attacker, defender, move).range();
|
|
1360
|
+
// 1x effectiveness should be identical to just using a 100 BP move
|
|
1361
|
+
const neutral = calc();
|
|
1362
|
+
const fusionBolt = Move('Fusion Bolt');
|
|
1363
|
+
expect(calc(fusionBolt)).toEqual(neutral);
|
|
1364
|
+
// 2x effectiveness
|
|
1365
|
+
defender = Pokemon('Manaphy');
|
|
1366
|
+
const se = calc();
|
|
1367
|
+
// expect some sort of boost compared to the control
|
|
1368
|
+
expect(calc(fusionBolt)).not.toEqual(se);
|
|
1369
|
+
// tera should be able to revoke the boost
|
|
1370
|
+
defender.teraType = 'Normal';
|
|
1371
|
+
expect(calc()).toEqual(neutral);
|
|
1372
|
+
// check if secondary type resist is handled
|
|
1373
|
+
const cc = Move('Collision Course'); // Fighting type
|
|
1374
|
+
defender = Pokemon('Jirachi'); // Steel / Psychic is neutral to fighting, so no boost
|
|
1375
|
+
expect(calc(cc)).toEqual(neutral);
|
|
1376
|
+
// tera should cause the boost to be applied
|
|
1377
|
+
defender.teraType = 'Normal';
|
|
1378
|
+
expect(calc(cc)).toEqual(se);
|
|
1379
|
+
});
|
|
1380
|
+
function testQP(ability: string, field?: {weather?: Weather; terrain?: Terrain}) {
|
|
1381
|
+
test(`${ability} should take into account boosted stats by default`, () => {
|
|
1382
|
+
const attacker = Pokemon('Iron Leaves', {ability, boostedStat: 'auto', boosts: {spa: 6}});
|
|
1383
|
+
// highest stat = defense
|
|
1384
|
+
const defender = Pokemon('Iron Treads', {ability, boostedStat: 'auto', boosts: {spd: 6}});
|
|
1385
|
+
|
|
1386
|
+
let result = calculate(attacker, defender, Move('Leaf Storm'), Field(field)).rawDesc;
|
|
1387
|
+
expect(result.attackerAbility).toBe(ability);
|
|
1388
|
+
expect(result.defenderAbility).toBe(ability);
|
|
1389
|
+
|
|
1390
|
+
result = calculate(attacker, defender, Move('Psyblade'), Field(field)).rawDesc;
|
|
1391
|
+
expect(result.attackerAbility).toBeUndefined();
|
|
1392
|
+
expect(result.defenderAbility).toBeUndefined();
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1395
|
+
function testQPOverride(ability: string, field?: {weather?: Weather; terrain?: Terrain}) {
|
|
1396
|
+
test(`${ability} should be able to be overridden with boostedStat`, () => {
|
|
1397
|
+
const attacker = Pokemon('Flutter Mane', {ability, boostedStat: 'atk', boosts: {spa: 6}});
|
|
1398
|
+
// highest stat = defense
|
|
1399
|
+
const defender = Pokemon('Walking Wake', {ability, boostedStat: 'def', boosts: {spd: 6}});
|
|
1400
|
+
|
|
1401
|
+
let result = calculate(attacker, defender, Move('Leaf Storm'), Field(field)).rawDesc;
|
|
1402
|
+
expect(result.attackerAbility).toBeUndefined();
|
|
1403
|
+
expect(result.defenderAbility).toBeUndefined();
|
|
1404
|
+
|
|
1405
|
+
result = calculate(attacker, defender, Move('Psyblade'), Field(field)).rawDesc;
|
|
1406
|
+
expect(result.attackerAbility).toBe(ability);
|
|
1407
|
+
expect(result.defenderAbility).toBe(ability);
|
|
1408
|
+
});
|
|
1409
|
+
}
|
|
1410
|
+
testQP('Quark Drive', {terrain: 'Electric'});
|
|
1411
|
+
testQP('Protosynthesis', {weather: 'Sun'});
|
|
1412
|
+
testQPOverride('Quark Drive', {terrain: 'Electric'});
|
|
1413
|
+
testQPOverride('Protosynthesis', {weather: 'Sun'});
|
|
1414
|
+
test('Meteor Beam/Electro Shot', () => {
|
|
1415
|
+
const defender = Pokemon('Arceus');
|
|
1416
|
+
const testCase = (options: {[k: string]: any}, expected: number) => {
|
|
1417
|
+
let result = calculate(Pokemon('Archaludon', options), defender, Move('Meteor Beam'));
|
|
1418
|
+
expect(result.attacker.boosts.spa).toBe(expected);
|
|
1419
|
+
result = calculate(Pokemon('Archaludon', options), defender, Move('Electro Shot'));
|
|
1420
|
+
expect(result.attacker.boosts.spa).toBe(expected);
|
|
1421
|
+
};
|
|
1422
|
+
testCase({}, 1); // raises by 1
|
|
1423
|
+
testCase({boosts: {spa: 6}}, 6); // caps at +6
|
|
1424
|
+
testCase({ability: 'Simple'}, 2);
|
|
1425
|
+
testCase({ability: 'Contrary'}, -1);
|
|
1426
|
+
});
|
|
1427
|
+
test('Revelation Dance should change type if Pokemon Terastallized', () => {
|
|
1428
|
+
const attacker = Pokemon('Oricorio-Pom-Pom');
|
|
1429
|
+
const defender = Pokemon('Sandaconda');
|
|
1430
|
+
let result = calculate(attacker, defender, Move('Revelation Dance'));
|
|
1431
|
+
expect(result.move.type).toBe('Electric');
|
|
1432
|
+
|
|
1433
|
+
attacker.teraType = 'Water';
|
|
1434
|
+
result = calculate(attacker, defender, Move('Revelation Dance'));
|
|
1435
|
+
expect(result.move.type).toBe('Water');
|
|
1436
|
+
});
|
|
1437
|
+
|
|
1438
|
+
test('Flower Gift, Power Spot, Battery, and switching boosts shouldn\'t have double spaces', () => {
|
|
1439
|
+
const attacker = Pokemon('Weavile');
|
|
1440
|
+
const defender = Pokemon('Vulpix');
|
|
1441
|
+
const field = Field({
|
|
1442
|
+
weather: 'Sun',
|
|
1443
|
+
attackerSide: {
|
|
1444
|
+
isFlowerGift: true,
|
|
1445
|
+
isPowerSpot: true,
|
|
1446
|
+
},
|
|
1447
|
+
defenderSide: {
|
|
1448
|
+
isSwitching: 'out',
|
|
1449
|
+
},
|
|
1450
|
+
});
|
|
1451
|
+
const result = calculate(attacker, defender, Move('Pursuit'), field);
|
|
1452
|
+
|
|
1453
|
+
expect(result.desc()).toBe(
|
|
1454
|
+
"0 Atk Weavile with an ally's Flower Gift Power Spot boosted switching boosted Pursuit (80 BP) vs. 0 HP / 0 Def Vulpix in Sun: 399-469 (183.8 - 216.1%) -- guaranteed OHKO"
|
|
1455
|
+
);
|
|
1456
|
+
});
|
|
1457
|
+
|
|
1458
|
+
test('Wind Rider should give an Attack boost in Tailwind', () => {
|
|
1459
|
+
const attacker = Pokemon('Brambleghast', {'ability': 'Wind Rider'});
|
|
1460
|
+
const defender = Pokemon('Brambleghast', {'ability': 'Wind Rider'});
|
|
1461
|
+
const field = Field({
|
|
1462
|
+
attackerSide: {
|
|
1463
|
+
isTailwind: true,
|
|
1464
|
+
},
|
|
1465
|
+
});
|
|
1466
|
+
|
|
1467
|
+
const result = calculate(attacker, defender, Move('Power Whip'), field);
|
|
1468
|
+
|
|
1469
|
+
expect(attacker.boosts.atk).toBe(0);
|
|
1470
|
+
expect(result.attacker.boosts.atk).toBe(1);
|
|
1471
|
+
});
|
|
1472
|
+
|
|
1473
|
+
describe('Tera Stellar', () => {
|
|
1474
|
+
const terastal = Pokemon('Arceus', {teraType: 'Stellar'});
|
|
1475
|
+
const control = Pokemon('Arceus');
|
|
1476
|
+
test('should only be displayed on defender for Stellar attacks', () => {
|
|
1477
|
+
expect(calculate(control, terastal, Move('Tera Blast'))
|
|
1478
|
+
.rawDesc
|
|
1479
|
+
.defenderTera).toBeUndefined();
|
|
1480
|
+
expect(calculate(terastal, terastal, Move('Tera Blast'))
|
|
1481
|
+
.rawDesc
|
|
1482
|
+
.defenderTera).toBeDefined();
|
|
1483
|
+
// make sure that it isn't caring about stellar first use
|
|
1484
|
+
expect(calculate(terastal, terastal, Move('Tera Blast', {isStellarFirstUse: true}))
|
|
1485
|
+
.rawDesc
|
|
1486
|
+
.defenderTera).toBeDefined();
|
|
1487
|
+
expect(calculate(control, terastal, Move('Tera Blast', {isStellarFirstUse: true}))
|
|
1488
|
+
.rawDesc
|
|
1489
|
+
.defenderTera).toBeUndefined();
|
|
1490
|
+
});
|
|
1491
|
+
test('should not be displayed for non-boosted attacks', () => expect(
|
|
1492
|
+
calculate(terastal, control, Move('Judgment', {isStellarFirstUse: false}))
|
|
1493
|
+
.rawDesc
|
|
1494
|
+
.attackerTera
|
|
1495
|
+
).toBeUndefined());
|
|
1496
|
+
test('should distinguish between first use for Tera Blast', () => {
|
|
1497
|
+
// I don't exactly care what the difference is
|
|
1498
|
+
const result = [true, false].map((isStellarFirstUse, ..._) =>
|
|
1499
|
+
calculate(terastal, control, Move('Tera Blast', {isStellarFirstUse}))
|
|
1500
|
+
.rawDesc
|
|
1501
|
+
.isStellarFirstUse);
|
|
1502
|
+
expect(result[0]).not.toEqual(result[1]);
|
|
1503
|
+
});
|
|
1504
|
+
});
|
|
1505
|
+
});
|
|
1506
|
+
describe('Descriptions', () => {
|
|
1507
|
+
inGen(9, ({gen, calculate, Pokemon, Move}) => {
|
|
1508
|
+
test('displayed chances should not round to 100%', () => {
|
|
1509
|
+
const result = calculate(
|
|
1510
|
+
Pokemon('Xerneas', {item: 'Choice Band', nature: 'Adamant', evs: {atk: 252}}),
|
|
1511
|
+
Pokemon('Necrozma-Dusk-Mane', {nature: 'Impish', evs: {hp: 252, def: 252}}),
|
|
1512
|
+
Move('Close Combat')
|
|
1513
|
+
);
|
|
1514
|
+
expect(result.kochance().chance).toBeGreaterThanOrEqual(0.9995);
|
|
1515
|
+
expect(result.kochance().text).toBe('99.9% chance to 3HKO');
|
|
1516
|
+
});
|
|
1517
|
+
test('displayed chances should not round to 0%', () => {
|
|
1518
|
+
const result = calculate(
|
|
1519
|
+
Pokemon('Deoxys-Attack', {evs: {spa: 44}}),
|
|
1520
|
+
Pokemon('Blissey', {nature: 'Calm', evs: {hp: 252, spd: 252}}),
|
|
1521
|
+
Move('Psycho Boost')
|
|
1522
|
+
);
|
|
1523
|
+
expect(result.kochance().chance).toBeLessThan(0.005); // it would round down.
|
|
1524
|
+
expect(result.kochance().text).toBe('0.1% chance to 4HKO');
|
|
1525
|
+
});
|
|
1526
|
+
});
|
|
1527
|
+
});
|
|
1528
|
+
describe('Some moves should break screens before doing damage', () => {
|
|
1529
|
+
inGens(3, 9, ({calculate, Pokemon, Move, Field}) => {
|
|
1530
|
+
test('Brick Break should break screens', () => {
|
|
1531
|
+
const pokemon = Pokemon('Mew');
|
|
1532
|
+
|
|
1533
|
+
const brickBreak = Move('Brick Break');
|
|
1534
|
+
const otherMove = Move('Vital Throw', {overrides: {basePower: 75}});
|
|
1535
|
+
|
|
1536
|
+
const field = Field({defenderSide: {isReflect: true}});
|
|
1537
|
+
|
|
1538
|
+
const brickBreakResult = calculate(pokemon, pokemon, brickBreak, field);
|
|
1539
|
+
expect(brickBreakResult.field.defenderSide.isReflect).toBe(false);
|
|
1540
|
+
|
|
1541
|
+
const otherMoveResult = calculate(pokemon, pokemon, otherMove, field);
|
|
1542
|
+
expect(otherMoveResult.field.defenderSide.isReflect).toBe(true);
|
|
1543
|
+
|
|
1544
|
+
expect(brickBreakResult.range()[0]).toBeGreaterThan(otherMoveResult.range()[0]);
|
|
1545
|
+
expect(brickBreakResult.range()[1]).toBeGreaterThan(otherMoveResult.range()[1]);
|
|
1546
|
+
});
|
|
1547
|
+
});
|
|
1548
|
+
inGens(7, 9, ({calculate, Pokemon, Move, Field}) => {
|
|
1549
|
+
test('Psychic Fangs should break screens', () => {
|
|
1550
|
+
const pokemon = Pokemon('Mew');
|
|
1551
|
+
|
|
1552
|
+
const psychicFangs = Move('Psychic Fangs');
|
|
1553
|
+
const otherMove = Move('Zen Headbutt', {overrides: {basePower: 75}});
|
|
1554
|
+
|
|
1555
|
+
const field = Field({defenderSide: {isReflect: true}});
|
|
1556
|
+
|
|
1557
|
+
const psychicFangsResult = calculate(pokemon, pokemon, psychicFangs, field);
|
|
1558
|
+
expect(psychicFangsResult.field.defenderSide.isReflect).toBe(false);
|
|
1559
|
+
|
|
1560
|
+
const otherMoveResult = calculate(pokemon, pokemon, otherMove, field);
|
|
1561
|
+
expect(otherMoveResult.field.defenderSide.isReflect).toBe(true);
|
|
1562
|
+
|
|
1563
|
+
expect(psychicFangsResult.range()[0]).toBeGreaterThan(otherMoveResult.range()[0]);
|
|
1564
|
+
expect(psychicFangsResult.range()[1]).toBeGreaterThan(otherMoveResult.range()[1]);
|
|
1565
|
+
});
|
|
1566
|
+
});
|
|
1567
|
+
inGen(9, ({calculate, Pokemon, Move, Field}) => {
|
|
1568
|
+
test('Raging Bull should break screens', () => {
|
|
1569
|
+
const pokemon = Pokemon('Tauros-Paldea-Aqua');
|
|
1570
|
+
|
|
1571
|
+
const ragingBull = Move('Raging Bull');
|
|
1572
|
+
const otherMove = Move('Waterfall', {overrides: {basePower: 90}});
|
|
1573
|
+
|
|
1574
|
+
const field = Field({defenderSide: {isReflect: true}});
|
|
1575
|
+
|
|
1576
|
+
const ragingBullResult = calculate(pokemon, pokemon, ragingBull, field);
|
|
1577
|
+
expect(ragingBullResult.field.defenderSide.isReflect).toBe(false);
|
|
1578
|
+
|
|
1579
|
+
const otherMoveResult = calculate(pokemon, pokemon, otherMove, field);
|
|
1580
|
+
expect(otherMoveResult.field.defenderSide.isReflect).toBe(true);
|
|
1581
|
+
|
|
1582
|
+
expect(ragingBullResult.range()[0]).toBeGreaterThan(otherMoveResult.range()[0]);
|
|
1583
|
+
expect(ragingBullResult.range()[1]).toBeGreaterThan(otherMoveResult.range()[1]);
|
|
1584
|
+
});
|
|
1585
|
+
});
|
|
1586
|
+
});
|
|
1587
|
+
});
|
|
1588
|
+
});
|