@pkmn/engine 0.1.0-dev.028ab165
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/LICENSE +20 -0
- package/README.md +343 -0
- package/build/pkg/addon.d.ts +7 -0
- package/build/pkg/addon.js +112 -0
- package/build/pkg/addon.js.map +1 -0
- package/build/pkg/data/ids.json +240 -0
- package/build/pkg/data/layout.json +108 -0
- package/build/pkg/data/protocol.json +230 -0
- package/build/pkg/data.d.ts +91 -0
- package/build/pkg/data.js +150 -0
- package/build/pkg/data.js.map +1 -0
- package/build/pkg/gen1.d.ts +110 -0
- package/build/pkg/gen1.js +684 -0
- package/build/pkg/gen1.js.map +1 -0
- package/build/pkg/index.d.ts +386 -0
- package/build/pkg/index.js +122 -0
- package/build/pkg/index.js.map +1 -0
- package/build/pkg/protocol.d.ts +73 -0
- package/build/pkg/protocol.js +473 -0
- package/build/pkg/protocol.js.map +1 -0
- package/build/test/display.js +400 -0
- package/build/test/display.js.map +1 -0
- package/build/tools/debug.js +135 -0
- package/build/tools/debug.js.map +1 -0
- package/build.zig +371 -0
- package/package.json +224 -0
- package/src/bin/install-pkmn-engine +396 -0
- package/src/bin/pkmn-debug +62 -0
- package/src/lib/binding/node.zig +134 -0
- package/src/lib/binding/wasm.zig +12 -0
- package/src/lib/common/battle.zig +17 -0
- package/src/lib/common/data.zig +117 -0
- package/src/lib/common/debug.zig +74 -0
- package/src/lib/common/js.zig +118 -0
- package/src/lib/common/optional.zig +67 -0
- package/src/lib/common/options.zig +49 -0
- package/src/lib/common/protocol.zig +1841 -0
- package/src/lib/common/rational.zig +353 -0
- package/src/lib/common/rng.zig +243 -0
- package/src/lib/common/util.zig +12 -0
- package/src/lib/gen1/README.md +894 -0
- package/src/lib/gen1/calc.zig +426 -0
- package/src/lib/gen1/chance.zig +1060 -0
- package/src/lib/gen1/data/moves.zig +1866 -0
- package/src/lib/gen1/data/species.zig +1105 -0
- package/src/lib/gen1/data/types.zig +131 -0
- package/src/lib/gen1/data.zig +634 -0
- package/src/lib/gen1/helpers.zig +779 -0
- package/src/lib/gen1/mechanics.zig +3128 -0
- package/src/lib/gen2/README.md +329 -0
- package/src/lib/gen2/calc.zig +143 -0
- package/src/lib/gen2/chance.zig +1314 -0
- package/src/lib/gen2/data/items.zig +230 -0
- package/src/lib/gen2/data/moves.zig +2878 -0
- package/src/lib/gen2/data/species.zig +1798 -0
- package/src/lib/gen2/data/types.zig +226 -0
- package/src/lib/gen2/data.zig +463 -0
- package/src/lib/gen2/generated.zig +808 -0
- package/src/lib/gen2/helpers.zig +219 -0
- package/src/lib/gen2/mechanics.zig +3551 -0
- package/src/lib/pkmn.zig +113 -0
- package/src/test/display/pkmn.html.tmpl +368 -0
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.StoredPokemon = exports.Pokemon = exports.Side = exports.Battle = void 0;
|
|
27
|
+
const data_1 = require("@pkmn/data");
|
|
28
|
+
const addon = __importStar(require("./addon"));
|
|
29
|
+
const data_2 = require("./data");
|
|
30
|
+
const protocol_1 = require("./protocol");
|
|
31
|
+
const SIZES = data_2.LAYOUT[0].sizes;
|
|
32
|
+
const OFFSETS = data_2.LAYOUT[0].offsets;
|
|
33
|
+
OFFSETS.Stats.spa = OFFSETS.Stats.spd = OFFSETS.Stats.spc;
|
|
34
|
+
OFFSETS.Boosts.spa = OFFSETS.Boosts.spd = OFFSETS.Boosts.spc;
|
|
35
|
+
const VOLATILES = {};
|
|
36
|
+
for (const v in OFFSETS.Volatiles) {
|
|
37
|
+
if (v.charCodeAt(0) >= 65 && v.charCodeAt(0) <= 90) {
|
|
38
|
+
VOLATILES[v] = OFFSETS.Volatiles[v];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
class Battle {
|
|
42
|
+
config;
|
|
43
|
+
log;
|
|
44
|
+
lookup;
|
|
45
|
+
data;
|
|
46
|
+
options;
|
|
47
|
+
buf;
|
|
48
|
+
cache;
|
|
49
|
+
constructor(lookup, data, config) {
|
|
50
|
+
this.config = config;
|
|
51
|
+
this.lookup = lookup;
|
|
52
|
+
this.data = data;
|
|
53
|
+
this.options = new ArrayBuffer(addon.size(0, 'choices'));
|
|
54
|
+
this.buf = config.log ? new ArrayBuffer(addon.size(0, 'log')) : undefined;
|
|
55
|
+
this.log = config.log ? new DataView(this.buf) : undefined;
|
|
56
|
+
this.cache = [undefined, undefined];
|
|
57
|
+
}
|
|
58
|
+
update(c1, c2) {
|
|
59
|
+
return addon.update(0, !!this.config.showdown, this.data.buffer, c1, c2, this.buf);
|
|
60
|
+
}
|
|
61
|
+
choices(id, result) {
|
|
62
|
+
return addon.choices(0, !!this.config.showdown, this.data.buffer, id, result[id], this.options);
|
|
63
|
+
}
|
|
64
|
+
choose(id, result, fn) {
|
|
65
|
+
return addon.choose(0, !!this.config.showdown, this.data.buffer, id, result[id], this.options, fn);
|
|
66
|
+
}
|
|
67
|
+
get sides() {
|
|
68
|
+
return this._sides();
|
|
69
|
+
}
|
|
70
|
+
*_sides() {
|
|
71
|
+
yield this.side('p1');
|
|
72
|
+
yield this.side('p2');
|
|
73
|
+
}
|
|
74
|
+
side(id) {
|
|
75
|
+
const i = id === 'p1' ? 0 : 1;
|
|
76
|
+
const side = this.cache[i];
|
|
77
|
+
return side ?? (this.cache[i] = new Side(this, this.lookup, this.data, id));
|
|
78
|
+
}
|
|
79
|
+
foe(side) {
|
|
80
|
+
return this.side(side === 'p1' ? 'p2' : 'p1');
|
|
81
|
+
}
|
|
82
|
+
get turn() {
|
|
83
|
+
return this.data.getUint16(OFFSETS.Battle.turn, data_2.LE);
|
|
84
|
+
}
|
|
85
|
+
get lastDamage() {
|
|
86
|
+
return this.data.getUint16(OFFSETS.Battle.last_damage, data_2.LE);
|
|
87
|
+
}
|
|
88
|
+
get prng() {
|
|
89
|
+
const offset = OFFSETS.Battle.last_moves + (this.config.showdown ? 4 : 2);
|
|
90
|
+
// Pokémon Showdown's PRNGSeed is always big-endian
|
|
91
|
+
const seed = [0, 0, 0, 0];
|
|
92
|
+
if (this.config.showdown) {
|
|
93
|
+
seed[data_2.LE ? 3 : 0] = this.data.getUint16(offset, data_2.LE);
|
|
94
|
+
seed[data_2.LE ? 2 : 1] = this.data.getUint16(offset + 2, data_2.LE);
|
|
95
|
+
seed[data_2.LE ? 1 : 2] = this.data.getUint16(offset + 4, data_2.LE);
|
|
96
|
+
seed[data_2.LE ? 0 : 3] = this.data.getUint16(offset + 6, data_2.LE);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
// Gen12RNG seed requires an index, but if we always rotate the seed based on the index when
|
|
100
|
+
// reading we can avoid tracking the index as it will always be zero. NB: the exact bits of
|
|
101
|
+
// the Battle will not roundtrip, but the *logical* Battle state will.
|
|
102
|
+
const index = this.data.getUint8(offset + 9);
|
|
103
|
+
for (let i = 0; i < 9; i++) {
|
|
104
|
+
const j = i - index;
|
|
105
|
+
seed[j < 0 ? j + 9 : j] = this.data.getUint8(offset + i);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return seed;
|
|
109
|
+
}
|
|
110
|
+
toJSON() {
|
|
111
|
+
return {
|
|
112
|
+
sides: Array.from(this.sides).map(s => s.toJSON()),
|
|
113
|
+
turn: this.turn,
|
|
114
|
+
lastDamage: this.lastDamage,
|
|
115
|
+
prng: this.prng,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
static create(gen, lookup, options) {
|
|
119
|
+
const buf = new ArrayBuffer(SIZES.Battle);
|
|
120
|
+
const data = new DataView(buf);
|
|
121
|
+
Side.init(gen, lookup, options.p1.team, data, OFFSETS.Battle.p1);
|
|
122
|
+
Side.init(gen, lookup, options.p2.team, data, OFFSETS.Battle.p2);
|
|
123
|
+
encodePRNG(data, options.seed);
|
|
124
|
+
return new Battle(lookup, data, options);
|
|
125
|
+
}
|
|
126
|
+
static restore(gen, lookup, battle, options) {
|
|
127
|
+
const buf = new ArrayBuffer(SIZES.Battle);
|
|
128
|
+
const data = new DataView(buf);
|
|
129
|
+
let offset = OFFSETS.Battle.p1;
|
|
130
|
+
const lastMoves = [];
|
|
131
|
+
for (const side of battle.sides) {
|
|
132
|
+
Side.encode(gen, !!options.showdown, lookup, battle, side, data, offset);
|
|
133
|
+
offset += SIZES.Side;
|
|
134
|
+
if (options.showdown) {
|
|
135
|
+
lastMoves.push(+!!side.lastMoveCounterable);
|
|
136
|
+
lastMoves.push((side.lastMoveIndex ?? 0));
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
lastMoves.push((+!!side.lastMoveCounterable << 4) | (side.lastMoveIndex ?? 0));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
data.setUint16(OFFSETS.Battle.turn, battle.turn, data_2.LE);
|
|
143
|
+
data.setUint16(OFFSETS.Battle.last_damage, battle.lastDamage, data_2.LE);
|
|
144
|
+
data.setUint8(OFFSETS.Battle.last_moves, lastMoves[0]);
|
|
145
|
+
data.setUint8(OFFSETS.Battle.last_moves + 1, lastMoves[1]);
|
|
146
|
+
if (options.showdown) {
|
|
147
|
+
data.setUint8(OFFSETS.Battle.last_moves + 2, lastMoves[2]);
|
|
148
|
+
data.setUint8(OFFSETS.Battle.last_moves + 3, lastMoves[3]);
|
|
149
|
+
}
|
|
150
|
+
encodePRNG(data, battle.prng);
|
|
151
|
+
return new Battle(lookup, data, options);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
exports.Battle = Battle;
|
|
155
|
+
class Side {
|
|
156
|
+
id;
|
|
157
|
+
battle;
|
|
158
|
+
lookup;
|
|
159
|
+
data;
|
|
160
|
+
offset;
|
|
161
|
+
cache;
|
|
162
|
+
constructor(battle, lookup, data, id) {
|
|
163
|
+
this.id = id;
|
|
164
|
+
this.battle = battle;
|
|
165
|
+
this.lookup = lookup;
|
|
166
|
+
this.data = data;
|
|
167
|
+
this.offset = OFFSETS.Battle[id];
|
|
168
|
+
this.cache = [undefined, undefined, undefined, undefined, undefined, undefined];
|
|
169
|
+
}
|
|
170
|
+
get active() {
|
|
171
|
+
return started(this.data) ? this.get(1) : undefined;
|
|
172
|
+
}
|
|
173
|
+
get pokemon() {
|
|
174
|
+
return this._pokemon();
|
|
175
|
+
}
|
|
176
|
+
*_pokemon() {
|
|
177
|
+
for (let i = 1; i <= 6; i++) {
|
|
178
|
+
const poke = this.get(i);
|
|
179
|
+
if (!poke)
|
|
180
|
+
break;
|
|
181
|
+
yield poke;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
slot(id) {
|
|
185
|
+
for (let slot = 1; slot <= 6; slot++) {
|
|
186
|
+
const i = this.data.getUint8(this.offset + OFFSETS.Side.order + slot - 1);
|
|
187
|
+
if (i === id)
|
|
188
|
+
return slot;
|
|
189
|
+
}
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
get(slot) {
|
|
193
|
+
const id = this.data.getUint8(this.offset + OFFSETS.Side.order + slot - 1) - 1;
|
|
194
|
+
if (id < 0)
|
|
195
|
+
return undefined;
|
|
196
|
+
const poke = this.cache[id];
|
|
197
|
+
if (poke)
|
|
198
|
+
return poke;
|
|
199
|
+
return (this.cache[id] = new Pokemon(this.battle, this.lookup, this.data, this.offset, id));
|
|
200
|
+
}
|
|
201
|
+
get lastUsedMove() {
|
|
202
|
+
const m = this.data.getUint8(this.offset + OFFSETS.Side.last_used_move);
|
|
203
|
+
return m === 0 ? undefined : this.lookup.moveByNum(m);
|
|
204
|
+
}
|
|
205
|
+
get lastSelectedMove() {
|
|
206
|
+
const m = this.data.getUint8(this.offset + OFFSETS.Side.last_selected_move);
|
|
207
|
+
return m === 0 ? undefined : this.lookup.moveByNum(m);
|
|
208
|
+
}
|
|
209
|
+
get lastMoveIndex() {
|
|
210
|
+
let m;
|
|
211
|
+
if (this.battle.config.showdown) {
|
|
212
|
+
const off = this.id === 'p1' ? 0 : 2;
|
|
213
|
+
m = this.data.getUint8(OFFSETS.Battle.last_moves + off);
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
const off = this.id === 'p1' ? 0 : 1;
|
|
217
|
+
m = this.data.getUint8(OFFSETS.Battle.last_moves + off) & 0x0F;
|
|
218
|
+
}
|
|
219
|
+
return m === 0 ? undefined : m;
|
|
220
|
+
}
|
|
221
|
+
get lastMoveCounterable() {
|
|
222
|
+
let m;
|
|
223
|
+
if (this.battle.config.showdown) {
|
|
224
|
+
const off = this.id === 'p1' ? 1 : 3;
|
|
225
|
+
m = this.data.getUint8(OFFSETS.Battle.last_moves + off);
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
const off = this.id === 'p1' ? 0 : 1;
|
|
229
|
+
m = this.data.getUint8(OFFSETS.Battle.last_moves + off) & 0xF0;
|
|
230
|
+
}
|
|
231
|
+
return m !== 0;
|
|
232
|
+
}
|
|
233
|
+
toJSON() {
|
|
234
|
+
return {
|
|
235
|
+
active: this.active?.toJSON(),
|
|
236
|
+
pokemon: Array.from(this.pokemon).map(p => p.toJSON()),
|
|
237
|
+
lastSelectedMove: this.lastSelectedMove,
|
|
238
|
+
lastUsedMove: this.lastUsedMove,
|
|
239
|
+
lastMoveIndex: this.lastMoveIndex,
|
|
240
|
+
lastMoveCounterable: this.lastMoveCounterable,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
static init(gen, lookup, team, data, offset) {
|
|
244
|
+
const n = Math.min(6, team.length);
|
|
245
|
+
for (let i = 0; i < n; i++) {
|
|
246
|
+
const poke = team[i];
|
|
247
|
+
Pokemon.init(gen, lookup, poke, data, offset, i);
|
|
248
|
+
data.setUint8(offset + OFFSETS.Side.order + i, i + 1);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
static encode(gen, showdown, lookup, battle, side, data, offset) {
|
|
252
|
+
let i = 0;
|
|
253
|
+
for (const pokemon of side.pokemon) {
|
|
254
|
+
const off = offset + OFFSETS.Side.pokemon + SIZES.Pokemon * (pokemon.position - 1);
|
|
255
|
+
Pokemon.encodeStored(gen, showdown, lookup, pokemon, data, off);
|
|
256
|
+
data.setUint8(offset + OFFSETS.Side.order + (pokemon.position - 1), i + 1);
|
|
257
|
+
i++;
|
|
258
|
+
}
|
|
259
|
+
Pokemon.encodeActive(gen, lookup, battle, side.active, data, offset + OFFSETS.Side.active);
|
|
260
|
+
data.setUint8(offset + OFFSETS.Side.last_selected_move, lookup.moveByID(side.lastSelectedMove));
|
|
261
|
+
data.setUint8(offset + OFFSETS.Side.last_used_move, lookup.moveByID(side.lastUsedMove));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
exports.Side = Side;
|
|
265
|
+
class Pokemon {
|
|
266
|
+
static Volatiles = VOLATILES;
|
|
267
|
+
battle;
|
|
268
|
+
lookup;
|
|
269
|
+
data;
|
|
270
|
+
offset;
|
|
271
|
+
index;
|
|
272
|
+
stored;
|
|
273
|
+
constructor(battle, lookup, data, offset, index) {
|
|
274
|
+
this.battle = battle;
|
|
275
|
+
this.lookup = lookup;
|
|
276
|
+
this.data = data;
|
|
277
|
+
this.offset = {
|
|
278
|
+
order: offset + OFFSETS.Side.order,
|
|
279
|
+
active: offset + OFFSETS.Side.active,
|
|
280
|
+
stored: offset + OFFSETS.Side.pokemon + SIZES.Pokemon * index,
|
|
281
|
+
};
|
|
282
|
+
this.index = index;
|
|
283
|
+
this.stored = new StoredPokemon(this.lookup, this.data, this.offset.stored);
|
|
284
|
+
}
|
|
285
|
+
get position() {
|
|
286
|
+
return this.index + 1;
|
|
287
|
+
}
|
|
288
|
+
get active() {
|
|
289
|
+
return started(this.data) && this.position === this.data.getUint8(this.offset.order);
|
|
290
|
+
}
|
|
291
|
+
stat(stat) {
|
|
292
|
+
if (!this.active)
|
|
293
|
+
return this.stored.stat(stat);
|
|
294
|
+
const off = this.offset.active + OFFSETS.ActivePokemon.stats + OFFSETS.Stats[stat];
|
|
295
|
+
return this.data.getUint16(off, data_2.LE);
|
|
296
|
+
}
|
|
297
|
+
get stats() {
|
|
298
|
+
if (!this.active)
|
|
299
|
+
return this.stored.stats;
|
|
300
|
+
const spc = this.stat('spc');
|
|
301
|
+
return {
|
|
302
|
+
hp: this.stat('hp'),
|
|
303
|
+
atk: this.stat('atk'),
|
|
304
|
+
def: this.stat('def'),
|
|
305
|
+
spe: this.stat('spe'),
|
|
306
|
+
spa: spc,
|
|
307
|
+
spd: spc,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
volatile(bit) {
|
|
311
|
+
if (!this.active)
|
|
312
|
+
return false;
|
|
313
|
+
const off = this.offset.active + OFFSETS.ActivePokemon.volatiles + (bit >> 3);
|
|
314
|
+
const byte = this.data.getUint8(off);
|
|
315
|
+
return !!(byte & (1 << (bit & 7)));
|
|
316
|
+
}
|
|
317
|
+
get volatiles() {
|
|
318
|
+
if (!this.active)
|
|
319
|
+
return {};
|
|
320
|
+
const off = this.offset.active + OFFSETS.ActivePokemon.volatiles;
|
|
321
|
+
const volatiles = {};
|
|
322
|
+
for (const v in Pokemon.Volatiles) {
|
|
323
|
+
if (v === 'Toxic' || v === 'MultiHit')
|
|
324
|
+
continue;
|
|
325
|
+
const volatile = (0, data_1.toID)(v);
|
|
326
|
+
if (this.volatile(Pokemon.Volatiles[v])) {
|
|
327
|
+
if (volatile === 'bide') {
|
|
328
|
+
volatiles[volatile] = {
|
|
329
|
+
duration: (this.data.getUint8(off + (OFFSETS.Volatiles.attacks >> 3)) >> 5) & 0b111,
|
|
330
|
+
damage: this.data.getUint16(off + (OFFSETS.Volatiles.state >> 3), data_2.LE),
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
else if (volatile === 'binding') {
|
|
334
|
+
volatiles[volatile] = {
|
|
335
|
+
duration: (this.data.getUint8(off + (OFFSETS.Volatiles.attacks >> 3)) >> 5) & 0b111,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
else if (volatile === 'thrashing') {
|
|
339
|
+
volatiles[volatile] = {
|
|
340
|
+
duration: (this.data.getUint8(off + (OFFSETS.Volatiles.attacks >> 3)) >> 5) & 0b111,
|
|
341
|
+
accuracy: this.data.getUint16(off + (OFFSETS.Volatiles.state >> 3), data_2.LE),
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
else if (volatile === 'rage') {
|
|
345
|
+
volatiles[volatile] = {
|
|
346
|
+
accuracy: this.data.getUint16(off + (OFFSETS.Volatiles.state >> 3), data_2.LE),
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
else if (volatile === 'confusion') {
|
|
350
|
+
volatiles[volatile] = {
|
|
351
|
+
duration: (this.data.getUint8(off + (OFFSETS.Volatiles.confusion >> 3)) >> 2) & 0b111,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
else if (volatile === 'substitute') {
|
|
355
|
+
volatiles[volatile] = {
|
|
356
|
+
hp: this.data.getUint8(off + (OFFSETS.Volatiles.substitute >> 3)),
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
else if (volatile === 'transform') {
|
|
360
|
+
const { player, id } = (0, protocol_1.decodeIdentRaw)((this.data.getUint8(off + (OFFSETS.Volatiles.transform >> 3))) & 0x0F);
|
|
361
|
+
volatiles[volatile] = { player, slot: this.battle.side(player).slot(id) };
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
volatiles[volatile] = {};
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return volatiles;
|
|
369
|
+
}
|
|
370
|
+
move(slot) {
|
|
371
|
+
if (!this.active)
|
|
372
|
+
return this.stored.move(slot);
|
|
373
|
+
const off = this.offset.active + OFFSETS.ActivePokemon.moves;
|
|
374
|
+
const m = this.data.getUint8(off + ((slot - 1) << 1));
|
|
375
|
+
if (m === 0)
|
|
376
|
+
return undefined;
|
|
377
|
+
const id = this.lookup.moveByNum(m);
|
|
378
|
+
const pp = this.data.getUint8(off + ((slot << 1) - 1));
|
|
379
|
+
const byte = this.data.getUint8(this.offset.active +
|
|
380
|
+
OFFSETS.ActivePokemon.volatiles +
|
|
381
|
+
(OFFSETS.Volatiles.disable_move >> 3));
|
|
382
|
+
const disabled = ((byte & 0b111) === slot)
|
|
383
|
+
? this.data.getUint8(this.offset.active +
|
|
384
|
+
OFFSETS.ActivePokemon.volatiles +
|
|
385
|
+
(OFFSETS.Volatiles.disable_duration >> 3)) >> 4
|
|
386
|
+
: undefined;
|
|
387
|
+
return { id, pp, disabled };
|
|
388
|
+
}
|
|
389
|
+
get moves() {
|
|
390
|
+
if (!this.active)
|
|
391
|
+
return this.stored.moves;
|
|
392
|
+
return this._moves();
|
|
393
|
+
}
|
|
394
|
+
*_moves() {
|
|
395
|
+
for (let i = 1; i <= 4; i++) {
|
|
396
|
+
const move = this.move(i);
|
|
397
|
+
if (!move)
|
|
398
|
+
break;
|
|
399
|
+
yield move;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
boost(boost) {
|
|
403
|
+
if (!this.active)
|
|
404
|
+
return 0;
|
|
405
|
+
const b = OFFSETS.Boosts[boost];
|
|
406
|
+
const byte = this.data.getUint8(this.offset.active + OFFSETS.ActivePokemon.boosts + (b >> 3));
|
|
407
|
+
const val = (b & 7) ? byte >> 4 : byte & 0x0F;
|
|
408
|
+
return decodeSigned(val);
|
|
409
|
+
}
|
|
410
|
+
get boosts() {
|
|
411
|
+
const boosts = { atk: 0, def: 0, spe: 0, spa: 0, spd: 0, accuracy: 0, evasion: 0 };
|
|
412
|
+
if (!this.active)
|
|
413
|
+
return boosts;
|
|
414
|
+
for (const b in OFFSETS.Boosts) {
|
|
415
|
+
if (b === 'spc')
|
|
416
|
+
continue;
|
|
417
|
+
const boost = b;
|
|
418
|
+
boosts[boost] = this.boost(boost);
|
|
419
|
+
}
|
|
420
|
+
return boosts;
|
|
421
|
+
}
|
|
422
|
+
get species() {
|
|
423
|
+
if (!this.active)
|
|
424
|
+
return this.stored.species;
|
|
425
|
+
const off = this.offset.active + OFFSETS.ActivePokemon.species;
|
|
426
|
+
return this.lookup.speciesByNum(this.data.getUint8(off));
|
|
427
|
+
}
|
|
428
|
+
get types() {
|
|
429
|
+
if (!this.active)
|
|
430
|
+
return this.stored.types;
|
|
431
|
+
const off = this.offset.active + OFFSETS.ActivePokemon.types;
|
|
432
|
+
return (0, protocol_1.decodeTypes)(this.lookup, this.data.getUint8(off));
|
|
433
|
+
}
|
|
434
|
+
get level() {
|
|
435
|
+
return this.data.getUint8(this.offset.stored + OFFSETS.Pokemon.level);
|
|
436
|
+
}
|
|
437
|
+
get hp() {
|
|
438
|
+
return this.data.getUint16(this.offset.stored + OFFSETS.Pokemon.hp, data_2.LE);
|
|
439
|
+
}
|
|
440
|
+
get status() {
|
|
441
|
+
const status = (0, protocol_1.decodeStatus)(this.data.getUint8(this.offset.stored + OFFSETS.Pokemon.status));
|
|
442
|
+
return status === 'psn' && this.toxic ? 'tox' : status;
|
|
443
|
+
}
|
|
444
|
+
get statusData() {
|
|
445
|
+
const sleep = this.sleep;
|
|
446
|
+
return { sleep, self: !!sleep && this.ext, toxic: this.toxic };
|
|
447
|
+
}
|
|
448
|
+
get sleep() {
|
|
449
|
+
const val = this.data.getUint8(this.offset.stored + OFFSETS.Pokemon.status);
|
|
450
|
+
return val & 0b111;
|
|
451
|
+
}
|
|
452
|
+
get ext() {
|
|
453
|
+
return (this.data.getUint8(this.offset.stored + OFFSETS.Pokemon.status) >> 7) === 1;
|
|
454
|
+
}
|
|
455
|
+
get toxic() {
|
|
456
|
+
if (!this.active)
|
|
457
|
+
return 0;
|
|
458
|
+
const off = this.offset.active + OFFSETS.ActivePokemon.volatiles;
|
|
459
|
+
return this.data.getUint8(off + (OFFSETS.Volatiles.toxic >> 3)) >> 3;
|
|
460
|
+
}
|
|
461
|
+
toJSON() {
|
|
462
|
+
return {
|
|
463
|
+
species: this.species,
|
|
464
|
+
types: this.types,
|
|
465
|
+
level: this.level,
|
|
466
|
+
hp: this.hp,
|
|
467
|
+
status: this.status,
|
|
468
|
+
statusData: this.statusData,
|
|
469
|
+
stats: this.stats,
|
|
470
|
+
boosts: this.boosts,
|
|
471
|
+
moves: Array.from(this.moves),
|
|
472
|
+
volatiles: this.volatiles,
|
|
473
|
+
stored: this.stored.toJSON(),
|
|
474
|
+
position: this.position,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
static init(gen, lookup, set, data, offset, index) {
|
|
478
|
+
const stored = offset + OFFSETS.Side.pokemon + SIZES.Pokemon * index;
|
|
479
|
+
const species = gen.species.get(set.species);
|
|
480
|
+
let hp = 0;
|
|
481
|
+
let off = stored + OFFSETS.Pokemon.stats;
|
|
482
|
+
for (const stat of gen.stats) {
|
|
483
|
+
if (stat === 'spd')
|
|
484
|
+
break;
|
|
485
|
+
const val = gen.stats.calc(stat, species.baseStats[stat], set.ivs?.[stat] ?? 31, set.evs?.[stat] ?? 252, set.level ?? 100);
|
|
486
|
+
data.setUint16(off, val, data_2.LE);
|
|
487
|
+
if (stat === 'hp')
|
|
488
|
+
hp = val;
|
|
489
|
+
off += 2;
|
|
490
|
+
}
|
|
491
|
+
off = stored + OFFSETS.Pokemon.moves;
|
|
492
|
+
for (const m of set.moves) {
|
|
493
|
+
const move = gen.moves.get(m);
|
|
494
|
+
data.setUint8(off++, move.num);
|
|
495
|
+
data.setUint8(off++, Math.min(move.pp / 5 * 8, gen.num === 1 ? 61 : 64));
|
|
496
|
+
}
|
|
497
|
+
data.setUint16(stored + OFFSETS.Pokemon.hp, hp, data_2.LE);
|
|
498
|
+
data.setUint8(stored + OFFSETS.Pokemon.species, species.num);
|
|
499
|
+
data.setUint8(stored + OFFSETS.Pokemon.types, encodeTypes(lookup, species.types));
|
|
500
|
+
data.setUint8(stored + OFFSETS.Pokemon.level, set.level ?? 100);
|
|
501
|
+
}
|
|
502
|
+
static encodeStored(gen, showdown, lookup, pokemon, data, offset) {
|
|
503
|
+
let off = 0;
|
|
504
|
+
const stats = pokemon.stored.stats;
|
|
505
|
+
for (const stat of gen.stats) {
|
|
506
|
+
if (stat === 'spd')
|
|
507
|
+
break;
|
|
508
|
+
data.setUint16(offset + OFFSETS.Pokemon.stats + off, stats[stat], data_2.LE);
|
|
509
|
+
off += 2;
|
|
510
|
+
}
|
|
511
|
+
off = offset + OFFSETS.Pokemon.moves;
|
|
512
|
+
for (const ms of pokemon.stored.moves) {
|
|
513
|
+
data.setUint8(off++, lookup.moveByID(ms.id));
|
|
514
|
+
data.setUint8(off++, ms.pp);
|
|
515
|
+
}
|
|
516
|
+
data.setUint16(offset + OFFSETS.Pokemon.hp, pokemon.hp, data_2.LE);
|
|
517
|
+
const species = gen.species.get(pokemon.stored.species);
|
|
518
|
+
data.setUint8(offset + OFFSETS.Pokemon.species, species.num);
|
|
519
|
+
data.setUint8(offset + OFFSETS.Pokemon.types, encodeTypes(lookup, species.types));
|
|
520
|
+
data.setUint8(offset + OFFSETS.Pokemon.level, pokemon.level);
|
|
521
|
+
data.setUint8(offset + OFFSETS.Pokemon.status, encodeStatus(pokemon, showdown));
|
|
522
|
+
}
|
|
523
|
+
static encodeActive(gen, lookup, battle, pokemon, data, offset) {
|
|
524
|
+
if (!pokemon)
|
|
525
|
+
return;
|
|
526
|
+
let off = 0;
|
|
527
|
+
const stats = pokemon.stats;
|
|
528
|
+
for (const stat of gen.stats) {
|
|
529
|
+
if (stat === 'spd')
|
|
530
|
+
break;
|
|
531
|
+
data.setUint16(offset + OFFSETS.ActivePokemon.stats + off, stats[stat], data_2.LE);
|
|
532
|
+
off += 2;
|
|
533
|
+
}
|
|
534
|
+
off = offset + OFFSETS.ActivePokemon.moves;
|
|
535
|
+
let slot = 1;
|
|
536
|
+
const disabled = { duration: 0, move: 0 };
|
|
537
|
+
for (const ms of pokemon.moves) {
|
|
538
|
+
data.setUint8(off++, lookup.moveByID(ms.id));
|
|
539
|
+
data.setUint8(off++, ms.pp);
|
|
540
|
+
if (ms.disabled !== undefined) {
|
|
541
|
+
disabled.duration = ms.disabled;
|
|
542
|
+
disabled.move = slot;
|
|
543
|
+
}
|
|
544
|
+
slot++;
|
|
545
|
+
}
|
|
546
|
+
off = offset + OFFSETS.ActivePokemon.volatiles;
|
|
547
|
+
const volatiles = pokemon.volatiles;
|
|
548
|
+
data.setUint8(off + (Pokemon.Volatiles.Bide >> 3), +!!volatiles.bide | (+!!volatiles.thrashing << 1) | (+!!volatiles.flinch << 3) |
|
|
549
|
+
(+!!volatiles.charging << 4) | (+!!volatiles.binding << 5) |
|
|
550
|
+
(+!!volatiles.invulnerable << 6) | (+!!volatiles.confusion << 7));
|
|
551
|
+
data.setUint8(off + (Pokemon.Volatiles.Mist >> 3), +!!volatiles.mist | (+!!volatiles.focusenergy << 1) |
|
|
552
|
+
(+!!volatiles.substitute << 2) | (+!!volatiles.recharging << 3) |
|
|
553
|
+
(+!!volatiles.rage << 4) | (+!!volatiles.leechseed << 5) |
|
|
554
|
+
(+!!pokemon.statusData.toxic << 6) | (+!!volatiles.lightscreen << 7));
|
|
555
|
+
const confusion = volatiles.confusion?.duration ?? 0;
|
|
556
|
+
const attacks = volatiles.binding?.duration ??
|
|
557
|
+
volatiles.thrashing?.duration ?? 0;
|
|
558
|
+
data.setUint8(off + (Pokemon.Volatiles.Reflect >> 3), +!!volatiles.reflect | (+!!volatiles.transform << 1) |
|
|
559
|
+
(confusion << 2) | (attacks << 5));
|
|
560
|
+
const state = volatiles.bide?.damage ??
|
|
561
|
+
volatiles.thrashing?.accuracy ??
|
|
562
|
+
volatiles.rage?.accuracy ?? 0;
|
|
563
|
+
data.setUint16(off + (OFFSETS.Volatiles.state >> 3), state, data_2.LE);
|
|
564
|
+
data.setUint8(off + (OFFSETS.Volatiles.substitute >> 3), volatiles.substitute?.hp ?? 0);
|
|
565
|
+
let transform = 0;
|
|
566
|
+
if (volatiles.transform) {
|
|
567
|
+
const side = Array.from(battle.sides)[volatiles.transform.player === 'p1' ? 0 : 1];
|
|
568
|
+
const id = Array.from(side.pokemon)[volatiles.transform.slot - 1].position;
|
|
569
|
+
transform = (+!!(volatiles.transform.player === 'p2') << 3) | id;
|
|
570
|
+
}
|
|
571
|
+
data.setUint8(off + (OFFSETS.Volatiles.transform >> 3), (transform | (disabled.duration << 4)));
|
|
572
|
+
data.setUint8(off + (OFFSETS.Volatiles.toxic >> 3), (disabled.move | ((pokemon.statusData.toxic ?? 0) << 3)));
|
|
573
|
+
off = offset + OFFSETS.ActivePokemon.boosts;
|
|
574
|
+
const boosts = pokemon.boosts;
|
|
575
|
+
data.setUint8(off++, encodeSigned(boosts.atk) | (encodeSigned(boosts.def) << 4));
|
|
576
|
+
data.setUint8(off++, encodeSigned(boosts.spe) | (encodeSigned(boosts.spa) << 4));
|
|
577
|
+
data.setUint8(off++, encodeSigned(boosts.accuracy) | (encodeSigned(boosts.evasion) << 4));
|
|
578
|
+
data.setUint8(offset + OFFSETS.ActivePokemon.species, lookup.speciesByID(pokemon.species));
|
|
579
|
+
data.setUint8(offset + OFFSETS.ActivePokemon.types, encodeTypes(lookup, pokemon.types));
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
exports.Pokemon = Pokemon;
|
|
583
|
+
class StoredPokemon {
|
|
584
|
+
lookup;
|
|
585
|
+
data;
|
|
586
|
+
offset;
|
|
587
|
+
constructor(lookup, data, offset) {
|
|
588
|
+
this.lookup = lookup;
|
|
589
|
+
this.data = data;
|
|
590
|
+
this.offset = offset;
|
|
591
|
+
}
|
|
592
|
+
stat(stat) {
|
|
593
|
+
return this.data.getUint16(this.offset + OFFSETS.Pokemon.stats + OFFSETS.Stats[stat], data_2.LE);
|
|
594
|
+
}
|
|
595
|
+
get stats() {
|
|
596
|
+
const spc = this.stat('spc');
|
|
597
|
+
return {
|
|
598
|
+
hp: this.stat('hp'),
|
|
599
|
+
atk: this.stat('atk'),
|
|
600
|
+
def: this.stat('def'),
|
|
601
|
+
spe: this.stat('spe'),
|
|
602
|
+
spa: spc,
|
|
603
|
+
spd: spc,
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
move(slot) {
|
|
607
|
+
const m = this.data.getUint8(this.offset + OFFSETS.Pokemon.moves + ((slot - 1) << 1));
|
|
608
|
+
if (m === 0)
|
|
609
|
+
return undefined;
|
|
610
|
+
const id = this.lookup.moveByNum(m);
|
|
611
|
+
const pp = this.data.getUint8(this.offset + OFFSETS.Pokemon.moves + ((slot << 1) - 1));
|
|
612
|
+
return { id, pp };
|
|
613
|
+
}
|
|
614
|
+
get moves() {
|
|
615
|
+
return this._moves();
|
|
616
|
+
}
|
|
617
|
+
*_moves() {
|
|
618
|
+
for (let i = 1; i <= 4; i++) {
|
|
619
|
+
const move = this.move(i);
|
|
620
|
+
if (!move)
|
|
621
|
+
break;
|
|
622
|
+
yield move;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
get species() {
|
|
626
|
+
return this.lookup.speciesByNum(this.data.getUint8(this.offset + OFFSETS.Pokemon.species));
|
|
627
|
+
}
|
|
628
|
+
get types() {
|
|
629
|
+
return (0, protocol_1.decodeTypes)(this.lookup, this.data.getUint8(this.offset + OFFSETS.Pokemon.types));
|
|
630
|
+
}
|
|
631
|
+
toJSON() {
|
|
632
|
+
return {
|
|
633
|
+
species: this.species,
|
|
634
|
+
types: this.types,
|
|
635
|
+
stats: this.stats,
|
|
636
|
+
moves: Array.from(this.moves),
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
exports.StoredPokemon = StoredPokemon;
|
|
641
|
+
function encodePRNG(data, seed) {
|
|
642
|
+
const offset = OFFSETS.Battle.last_damage + 2 + (seed.length === 4 ? 4 : 2);
|
|
643
|
+
if (seed.length === 4) {
|
|
644
|
+
data.setUint16(offset, seed[data_2.LE ? 3 : 0], data_2.LE);
|
|
645
|
+
data.setUint16(offset + 2, seed[data_2.LE ? 2 : 1], data_2.LE);
|
|
646
|
+
data.setUint16(offset + 4, seed[data_2.LE ? 1 : 2], data_2.LE);
|
|
647
|
+
data.setUint16(offset + 6, seed[data_2.LE ? 0 : 3], data_2.LE);
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
for (let i = 0; i < 9; i++) {
|
|
651
|
+
data.setUint8(offset + i, seed[i]);
|
|
652
|
+
}
|
|
653
|
+
// NB: ArrayBuffer is zero initialized already so we don't need to set the index to zero
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
function started(data) {
|
|
657
|
+
return data.getUint16(OFFSETS.Battle.turn, data_2.LE) !== 0;
|
|
658
|
+
}
|
|
659
|
+
function encodeTypes(lookup, types) {
|
|
660
|
+
return (lookup.typeByName(types[1] ?? types[0]) << 4) | lookup.typeByName(types[0]);
|
|
661
|
+
}
|
|
662
|
+
function decodeSigned(n) {
|
|
663
|
+
return (n > 7) ? -(0b10000 - n) : n;
|
|
664
|
+
}
|
|
665
|
+
function encodeSigned(n) {
|
|
666
|
+
return (n < 0) ? 0b10000 + n : n;
|
|
667
|
+
}
|
|
668
|
+
function encodeStatus(pokemon, showdown) {
|
|
669
|
+
if (pokemon.statusData.sleep) {
|
|
670
|
+
if (pokemon.status !== 'slp') {
|
|
671
|
+
throw new Error('Pokemon is not asleep but has non-zero sleep turns');
|
|
672
|
+
}
|
|
673
|
+
return (pokemon.statusData.self ? 0x80 : 0) | pokemon.statusData.sleep;
|
|
674
|
+
}
|
|
675
|
+
switch (pokemon.status) {
|
|
676
|
+
case 'tox': return showdown ? 0b10001000 : 1 << 3;
|
|
677
|
+
case 'psn': return 1 << 3;
|
|
678
|
+
case 'brn': return 1 << 4;
|
|
679
|
+
case 'frz': return 1 << 5;
|
|
680
|
+
case 'par': return 1 << 6;
|
|
681
|
+
}
|
|
682
|
+
return 0;
|
|
683
|
+
}
|
|
684
|
+
//# sourceMappingURL=gen1.js.map
|