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