@pkmn/engine 0.1.0-dev.979a393a → 0.1.0-dev.a324c50f
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/README.md +32 -29
- 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/layout.json +25 -3
- package/build/pkg/gen1.d.ts +5 -4
- package/build/pkg/gen1.js +47 -31
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/index.d.ts +15 -12
- package/build/pkg/index.js.map +1 -1
- package/build/pkg/protocol.d.ts +4 -2
- package/build/pkg/protocol.js +4 -2
- package/build/pkg/protocol.js.map +1 -1
- package/build/test/display.js +31 -13
- package/build/test/display.js.map +1 -1
- package/build.zig +49 -39
- package/package.json +18 -14
- package/src/bin/install-pkmn-engine +11 -6
- package/src/bin/pkmn-debug +29 -6
- package/src/lib/binding/node.zig +46 -141
- package/src/lib/binding/wasm.zig +6 -4
- package/src/lib/common/data.zig +11 -11
- package/src/lib/common/debug.zig +21 -11
- package/src/lib/common/options.zig +12 -3
- package/src/lib/common/protocol.zig +239 -178
- package/src/lib/common/rng.zig +10 -10
- package/src/lib/gen1/README.md +111 -81
- package/src/lib/gen1/data/moves.zig +10 -10
- package/src/lib/gen1/data/species.zig +2 -2
- package/src/lib/gen1/data/types.zig +3 -3
- package/src/lib/gen1/data.zig +64 -43
- package/src/lib/gen1/helpers.zig +439 -32
- package/src/lib/gen1/mechanics.zig +987 -612
- package/src/lib/gen2/data/items.zig +3 -3
- package/src/lib/gen2/data/moves.zig +1 -1
- package/src/lib/gen2/data/species.zig +1 -1
- package/src/lib/gen2/data/types.zig +3 -3
- package/src/lib/gen2/data.zig +37 -26
- package/src/lib/gen2/helpers.zig +9 -11
- package/src/lib/gen2/mechanics.zig +5 -4
- package/src/lib/pkmn.zig +68 -9
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ A minimal, complete, Pokémon battle simulation engine optimized for performance
|
|
|
18
18
|
[designed](docs/DESIGN.md) for tooling, embedded systems, and [artificial
|
|
19
19
|
intelligence](https://github.com/pkmn/0-ERROR) use cases. This engine aims to be a frame-accurate and
|
|
20
20
|
[bug-for-bug compatible](http://www.catb.org/jargon/html/B/bug-for-bug-compatible.html)
|
|
21
|
-
implementation of both Pokémon battles as defined by the original game code
|
|
21
|
+
implementation of both Pokémon battles as defined by the original game code and the [Pokémon
|
|
22
22
|
Showdown](https://pokemonshowdown.com/)[^1] simulator which represents Pokémon battling as practically
|
|
23
23
|
interpreted online.
|
|
24
24
|
|
|
@@ -50,13 +50,13 @@ $ zig build --prefix /usr/local -Doptimize=ReleaseFast
|
|
|
50
50
|
|
|
51
51
|
The Zig website has [installation instructions](https://ziglang.org/learn/getting-started/) which
|
|
52
52
|
walk through how to install Zig on each platform - the engine code should work on Zig v0.11.0 dev
|
|
53
|
-
build
|
|
53
|
+
build 3859 or greater, though tracks Zig's master branch so this may change in the future if
|
|
54
54
|
breaking language changes are introduced:
|
|
55
55
|
|
|
56
56
|
`libpkmn` can be built with `-Dshowdown` to instead produce the Pokémon Showdown compatible
|
|
57
|
-
`libpkmn-showdown` library. Furthermore,
|
|
58
|
-
`libpkmn` and `libpkmn-showdown` objects available in the binary release are compiled with
|
|
59
|
-
|
|
57
|
+
`libpkmn-showdown` library. Furthermore, protocol message logging can be enabled through `-Dlog`.
|
|
58
|
+
The `libpkmn` and `libpkmn-showdown` objects available in the binary release are both compiled with
|
|
59
|
+
the `-Dlog` flag by default.
|
|
60
60
|
|
|
61
61
|
### `@pkmn/engine`
|
|
62
62
|
|
|
@@ -88,23 +88,23 @@ adding the following to your `build.zig`:
|
|
|
88
88
|
const std = @import("std");
|
|
89
89
|
const pkmn = @import("lib/pkmn/build.zig");
|
|
90
90
|
|
|
91
|
-
pub fn build(b: *std.
|
|
91
|
+
pub fn build(b: *std.Build) void {
|
|
92
92
|
...
|
|
93
|
-
exe.addModule("pkmn", pkmn.module(b, .{ .showdown = true, .
|
|
93
|
+
exe.addModule("pkmn", pkmn.module(b, .{ .showdown = true, .log = true }));
|
|
94
94
|
...
|
|
95
95
|
}
|
|
96
96
|
```
|
|
97
97
|
|
|
98
98
|
The engine's `build.zig` exposes a `module` function that takes an options structure to allow for
|
|
99
|
-
configuring whether or not Pokémon Showdown compatibility mode or
|
|
100
|
-
Alternatively, you may set options via a `pkmn_options` [root source file
|
|
99
|
+
configuring whether or not Pokémon Showdown compatibility mode or protocol message logging should be
|
|
100
|
+
enabled. Alternatively, you may set options via a `pkmn_options` [root source file
|
|
101
101
|
declaration](https://ziglang.org/documentation/master/#Root-Source-File). There are several
|
|
102
102
|
undocumented internal options that can be tweaked as well via build or root options, though these
|
|
103
103
|
options are not officially supported, affect correctness, and may change meaning or behavior without
|
|
104
104
|
warning. Use at your own risk.
|
|
105
105
|
|
|
106
106
|
```zig
|
|
107
|
-
pub const pkmn_options = .{ .showdown = true, .
|
|
107
|
+
pub const pkmn_options = .{ .showdown = true, .log = true };
|
|
108
108
|
```
|
|
109
109
|
|
|
110
110
|
## Usage
|
|
@@ -112,16 +112,16 @@ pub const pkmn_options = .{ .showdown = true, .trace = true };
|
|
|
112
112
|
For each Pokémon generation, the engine provides a battle structure with two functions - a function
|
|
113
113
|
to `update` the battle's state based on both players' choices and a function that defines which
|
|
114
114
|
`choices` are valid at that decision point (at the beginning of a battle both player's must "pass"
|
|
115
|
-
as their first choice in order to switch in the first member of their party). If `-
|
|
116
|
-
is enabled each update will produce logs which can be decoded into a buffer (`LOGS_SIZE`
|
|
117
|
-
are provided to make it easy to allocate a buffer of the correct size). Each `update` will
|
|
118
|
-
result to indicate either that the battle has terminated or which types of choices will be
|
|
119
|
-
for each player.
|
|
115
|
+
as their first choice in order to switch in the first member of their party). If `-Dlog` protocol
|
|
116
|
+
logging is enabled each update will produce logs which can be decoded into a buffer (`LOGS_SIZE`
|
|
117
|
+
constants are provided to make it easy to allocate a buffer of the correct size). Each `update` will
|
|
118
|
+
return a result to indicate either that the battle has terminated or which types of choices will be
|
|
119
|
+
available for each player.
|
|
120
120
|
|
|
121
121
|
Unlike [Pokémon Showdown's
|
|
122
122
|
SIM-PROTOCOL](https://github.com/smogon/pokemon-showdown/blob/master/sim/SIM-PROTOCOL.md#choice-requests)
|
|
123
123
|
which provides a rich request object at each decision point, the pkmn engine computes the possible
|
|
124
|
-
choices on demand based on the result of the previous `update` and the battle state (`
|
|
124
|
+
choices on demand based on the result of the previous `update` and the battle state (`CHOICES_SIZE`
|
|
125
125
|
is a good size to initialize the buffer passed to `choices`). **Attempting to update the battle with
|
|
126
126
|
a choice not present in the options returned by `choices` is undefined behavior and may corrupt
|
|
127
127
|
state or cause the engine to crash.**
|
|
@@ -139,19 +139,22 @@ potential logs that may result.
|
|
|
139
139
|
### C
|
|
140
140
|
|
|
141
141
|
[`pkmn.h`](src/include/pkmn.h) exports the C API for `libpkmn`. Symbols are all prefixed with
|
|
142
|
-
`pkmn_` to avoid name collisions. If `-
|
|
142
|
+
`pkmn_` to avoid name collisions. If `-Dlog` is enabled and logging throws an error then the error
|
|
143
143
|
will be encoded in the `pkmn_result` and can be checked with `pkmn_error`.
|
|
144
144
|
|
|
145
145
|
```c
|
|
146
146
|
#include <pkmn.h>
|
|
147
147
|
|
|
148
|
+
pkmn_battle_options options;
|
|
149
|
+
pkmn_gen1_battle_options_set(&options, ...);
|
|
148
150
|
pkmn_battle battle = ...;
|
|
149
|
-
|
|
151
|
+
|
|
150
152
|
pkmn_result result;
|
|
151
153
|
pkmn_choice c1 = 0, c2 = 0;
|
|
152
|
-
while (!pkmn_result_type(result = pkmn_battle_update(&battle, c1, c2,
|
|
154
|
+
while (!pkmn_result_type(result = pkmn_battle_update(&battle, c1, c2, &options))) {
|
|
153
155
|
c1 = choose(PKMN_PLAYER_P1, pkmn_result_p1(result));
|
|
154
156
|
c2 = choose(PKMN_PLAYER_P2, pkmn_result_p2(result));
|
|
157
|
+
pkmn_gen1_battle_options_set(&options, ...);
|
|
155
158
|
}
|
|
156
159
|
if (pkmn_error(result)) exit(1);
|
|
157
160
|
```
|
|
@@ -216,18 +219,18 @@ const std = @import("std");
|
|
|
216
219
|
const pkmn = @import("pkmn");
|
|
217
220
|
|
|
218
221
|
var random = std.rand.DefaultPrng.init(seed).random();
|
|
219
|
-
var
|
|
222
|
+
var choices: [pkmn.CHOICES_SIZE]pkmn.Choice = undefined;
|
|
220
223
|
|
|
221
224
|
var battle = ...
|
|
222
|
-
var
|
|
225
|
+
var options = pkmn.battle.options(...);
|
|
223
226
|
|
|
224
227
|
var c1 = pkmn.Choice{};
|
|
225
228
|
var c2 = pkmn.Choice{};
|
|
226
229
|
|
|
227
|
-
var result = try battle.update(c1, c2,
|
|
228
|
-
while (result.type == .None) : (result = try battle.update(c1, c2,
|
|
229
|
-
c1 =
|
|
230
|
-
c2 =
|
|
230
|
+
var result = try battle.update(c1, c2, &options);
|
|
231
|
+
while (result.type == .None) : (result = try battle.update(c1, c2, &options)) {
|
|
232
|
+
c1 = choices[random.uintLessThan(u8, battle.choices(.P1, result.p1, &choices))];
|
|
233
|
+
c2 = choices[random.uintLessThan(u8, battle.choices(.P2, result.p2, &choices))];
|
|
231
234
|
}
|
|
232
235
|
|
|
233
236
|
std.debug.print("{}\n", .{result.type});
|
|
@@ -250,10 +253,10 @@ following is a list of known `libpkmn` bindings written by developers outside of
|
|
|
250
253
|
organization](https://github.com/pkmn) that may be helpful (though note that these projects may not
|
|
251
254
|
necessarily be up-to-date/complete/correct - inclusion in this list does not imply endorsement):
|
|
252
255
|
|
|
253
|
-
| Language | License | URL
|
|
254
|
-
| ---------- | --------- |
|
|
255
|
-
| **C++** | `BSL-1.0` | https://github.com/pasyg/
|
|
256
|
-
| **Python** | `MIT` | https://github.com/AnnikaCodes/PyKMN
|
|
256
|
+
| Language | License | URL |
|
|
257
|
+
| ---------- | --------- | ------------------------------------ |
|
|
258
|
+
| **C++** | `BSL-1.0` | https://github.com/pasyg/wrapsire |
|
|
259
|
+
| **Python** | `MIT` | https://github.com/AnnikaCodes/PyKMN |
|
|
257
260
|
|
|
258
261
|
|
|
259
262
|
#### `pkmn-debug`
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Choice, Player, Result } from '.';
|
|
2
|
+
export declare function check(showdown: boolean): void;
|
|
3
|
+
export declare function supports(showdown: boolean, log?: boolean): boolean;
|
|
4
|
+
export declare function update(index: number, showdown: boolean, battle: ArrayBuffer, c1?: Choice, c2?: Choice, log?: ArrayBuffer): Result;
|
|
5
|
+
export declare function choices(index: number, showdown: boolean, battle: ArrayBuffer, player: Player, choice: Choice['type'], buf: ArrayBuffer): Choice[];
|
|
6
|
+
export declare function choose(index: number, showdown: boolean, battle: ArrayBuffer, player: Player, choice: Choice['type'], buf: ArrayBuffer, fn: (n: number) => number): Choice;
|
|
7
|
+
export declare function size(index: number, type: 'choices' | 'log'): number;
|
|
@@ -0,0 +1,112 @@
|
|
|
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.size = exports.choose = exports.choices = exports.update = exports.supports = exports.check = void 0;
|
|
27
|
+
const path = __importStar(require("path"));
|
|
28
|
+
const _1 = require(".");
|
|
29
|
+
const ROOT = path.join(__dirname, '..', '..');
|
|
30
|
+
let ADDON = undefined;
|
|
31
|
+
function load() {
|
|
32
|
+
if (ADDON)
|
|
33
|
+
return ADDON;
|
|
34
|
+
let pkmn = undefined;
|
|
35
|
+
let showdown = undefined;
|
|
36
|
+
try {
|
|
37
|
+
pkmn = require(path.join(ROOT, 'build', 'lib', 'pkmn.node')).engine;
|
|
38
|
+
}
|
|
39
|
+
catch { }
|
|
40
|
+
try {
|
|
41
|
+
showdown =
|
|
42
|
+
require(path.join(ROOT, 'build', 'lib', 'pkmn-showdown.node')).engine;
|
|
43
|
+
}
|
|
44
|
+
catch { }
|
|
45
|
+
if (!pkmn && !showdown) {
|
|
46
|
+
throw new Error('Could not find native addons - did you run `npx install-pkmn-engine`?');
|
|
47
|
+
}
|
|
48
|
+
return (ADDON = [pkmn, showdown]);
|
|
49
|
+
}
|
|
50
|
+
function check(showdown) {
|
|
51
|
+
if (!load()[+showdown]) {
|
|
52
|
+
const opts = ADDON[+!showdown].options.log ? ['-log'] : [];
|
|
53
|
+
if (showdown)
|
|
54
|
+
opts.push('-Dshowdown');
|
|
55
|
+
throw new Error(`@pkmn/engine has ${showdown ? 'not' : 'only'} been configured to support Pokémon Showdown.` +
|
|
56
|
+
`\n(running \`npx install-pkmn-engine --options='${opts.join(' ')}'\` can fix this issue).`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.check = check;
|
|
60
|
+
function supports(showdown, log) {
|
|
61
|
+
if (!load()[+showdown])
|
|
62
|
+
return false;
|
|
63
|
+
if (log === undefined)
|
|
64
|
+
return true;
|
|
65
|
+
return ADDON[+showdown].options.log === log;
|
|
66
|
+
}
|
|
67
|
+
exports.supports = supports;
|
|
68
|
+
function update(index, showdown, battle, c1, c2, log) {
|
|
69
|
+
return _1.Result.decode(ADDON[+showdown].bindings[index]
|
|
70
|
+
.update(battle, _1.Choice.encode(c1), _1.Choice.encode(c2), log));
|
|
71
|
+
}
|
|
72
|
+
exports.update = update;
|
|
73
|
+
function choices(index, showdown, battle, player, choice, buf) {
|
|
74
|
+
const request = choice[0] === 'p' ? 0 : choice[0] === 'm' ? 1 : 2;
|
|
75
|
+
const n = ADDON[+showdown].bindings[index].choices(battle, +(player !== 'p1'), request, buf);
|
|
76
|
+
// The top-level API signature means our hands our tied with respect to
|
|
77
|
+
// writing really fast bindings here. The simplest approach would be to return
|
|
78
|
+
// the ArrayBuffer the bindings populate as well as its size and only decode a
|
|
79
|
+
// Choice after the selection. However, given that we need to return
|
|
80
|
+
// `Choices[]` we need to decode all of them even if they're not all being
|
|
81
|
+
// used which is wasteful. This shouldn't be *that* bad as its a very small
|
|
82
|
+
// list, but its still wasted work. Switching the top-level API to return an
|
|
83
|
+
// Iterable<Choice> doesn't help as we need both the length and the ability to
|
|
84
|
+
// randomly access it, so the best way to make the current API fast would be
|
|
85
|
+
// to have the Zig bindings create Choice objects directly, only that won't
|
|
86
|
+
// scale well as it would require us to basically rewrite the low-level
|
|
87
|
+
// choices function for each generation within node.zig. We could do something
|
|
88
|
+
// really galaxy-brained and return some sort of frankenstein subclass of
|
|
89
|
+
// Array backed by ArrayBuffer which would lazily decode the Choice on access,
|
|
90
|
+
// but thats ultimately not worth the effort. You can't have both a high-level
|
|
91
|
+
// idiomatic API and performance here, hence why the choose function below
|
|
92
|
+
// exists.
|
|
93
|
+
const options = new Array(n);
|
|
94
|
+
const data = new Uint8Array(buf);
|
|
95
|
+
for (let i = 0; i < n; i++)
|
|
96
|
+
options[i] = _1.Choice.decode(data[i]);
|
|
97
|
+
return options;
|
|
98
|
+
}
|
|
99
|
+
exports.choices = choices;
|
|
100
|
+
function choose(index, showdown, battle, player, choice, buf, fn) {
|
|
101
|
+
const request = choice[0] === 'p' ? 0 : choice[0] === 'm' ? 1 : 2;
|
|
102
|
+
const n = ADDON[+showdown].bindings[index].choices(battle, +(player !== 'p1'), request, buf);
|
|
103
|
+
const data = new Uint8Array(buf);
|
|
104
|
+
return _1.Choice.decode(data[fn(n)]);
|
|
105
|
+
}
|
|
106
|
+
exports.choose = choose;
|
|
107
|
+
function size(index, type) {
|
|
108
|
+
const bindings = (ADDON[1] ?? ADDON[0]).bindings[index];
|
|
109
|
+
return type[0] === 'c' ? bindings.CHOICES_SIZE : bindings.LOGS_SIZE;
|
|
110
|
+
}
|
|
111
|
+
exports.size = size;
|
|
112
|
+
//# sourceMappingURL=addon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addon.js","sourceRoot":"","sources":["../../src/pkg/addon.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,15 +4,17 @@
|
|
|
4
4
|
"Battle": 384,
|
|
5
5
|
"Side": 184,
|
|
6
6
|
"Pokemon": 24,
|
|
7
|
-
"ActivePokemon": 32
|
|
7
|
+
"ActivePokemon": 32,
|
|
8
|
+
"Actions": 16,
|
|
9
|
+
"Summaries": 12
|
|
8
10
|
},
|
|
9
11
|
"offsets": {
|
|
10
12
|
"Battle": {
|
|
11
13
|
"sides": 0,
|
|
12
14
|
"turn": 368,
|
|
13
15
|
"last_damage": 370,
|
|
14
|
-
"
|
|
15
|
-
"rng":
|
|
16
|
+
"last_moves": 372,
|
|
17
|
+
"rng": 374
|
|
16
18
|
},
|
|
17
19
|
"Side": {
|
|
18
20
|
"pokemon": 0,
|
|
@@ -80,6 +82,26 @@
|
|
|
80
82
|
"disabled_duration": 52,
|
|
81
83
|
"disabled_move": 56,
|
|
82
84
|
"toxic": 59
|
|
85
|
+
},
|
|
86
|
+
"Action": {
|
|
87
|
+
"damage": 0,
|
|
88
|
+
"hit": 8,
|
|
89
|
+
"critical_hit": 10,
|
|
90
|
+
"secondary_chance": 12,
|
|
91
|
+
"speed_tie": 14,
|
|
92
|
+
"confused": 16,
|
|
93
|
+
"paralyzed": 18,
|
|
94
|
+
"duration": 20,
|
|
95
|
+
"durations": 24,
|
|
96
|
+
"move_slot": 40,
|
|
97
|
+
"multi_hit": 44,
|
|
98
|
+
"psywave": 48,
|
|
99
|
+
"metronome": 56
|
|
100
|
+
},
|
|
101
|
+
"Damage": {
|
|
102
|
+
"base": 0,
|
|
103
|
+
"final": 2,
|
|
104
|
+
"capped": 4
|
|
83
105
|
}
|
|
84
106
|
}
|
|
85
107
|
}
|
package/build/pkg/gen1.d.ts
CHANGED
|
@@ -39,10 +39,11 @@ export declare class Side implements Gen1.Side {
|
|
|
39
39
|
get(slot: Slot): Pokemon | undefined;
|
|
40
40
|
get lastUsedMove(): ID | undefined;
|
|
41
41
|
get lastSelectedMove(): ID | undefined;
|
|
42
|
-
get
|
|
42
|
+
get lastMoveIndex(): 1 | 2 | 3 | 4 | undefined;
|
|
43
|
+
get lastMoveCounterable(): boolean;
|
|
43
44
|
toJSON(): Gen1.Side;
|
|
44
45
|
static init(gen: Generation, lookup: Lookup, team: Partial<PokemonSet>[], data: DataView, offset: number): void;
|
|
45
|
-
static encode(gen: Generation, lookup: Lookup, battle: Data<Gen1.Battle>, side: Gen1.Side, data: DataView, offset: number): void;
|
|
46
|
+
static encode(gen: Generation, showdown: boolean, lookup: Lookup, battle: Data<Gen1.Battle>, side: Gen1.Side, data: DataView, offset: number): void;
|
|
46
47
|
}
|
|
47
48
|
export declare class Pokemon implements Gen1.Pokemon {
|
|
48
49
|
static Volatiles: {
|
|
@@ -77,11 +78,11 @@ export declare class Pokemon implements Gen1.Pokemon {
|
|
|
77
78
|
toxic: number;
|
|
78
79
|
};
|
|
79
80
|
private get sleep();
|
|
80
|
-
private get
|
|
81
|
+
private get ext();
|
|
81
82
|
private get toxic();
|
|
82
83
|
toJSON(): Gen1.Pokemon;
|
|
83
84
|
static init(gen: Generation, lookup: Lookup, set: Partial<PokemonSet>, data: DataView, offset: number, index: number): void;
|
|
84
|
-
static encodeStored(gen: Generation, lookup: Lookup, pokemon: Gen1.Pokemon, data: DataView, offset: number): void;
|
|
85
|
+
static encodeStored(gen: Generation, showdown: boolean, lookup: Lookup, pokemon: Gen1.Pokemon, data: DataView, offset: number): void;
|
|
85
86
|
static encodeActive(gen: Generation, lookup: Lookup, battle: Data<Gen1.Battle>, pokemon: Gen1.Pokemon | undefined, data: DataView, offset: number): void;
|
|
86
87
|
}
|
|
87
88
|
export declare class StoredPokemon {
|
package/build/pkg/gen1.js
CHANGED
|
@@ -50,7 +50,7 @@ class Battle {
|
|
|
50
50
|
this.config = config;
|
|
51
51
|
this.lookup = lookup;
|
|
52
52
|
this.data = data;
|
|
53
|
-
this.options = new ArrayBuffer(addon.size(0, '
|
|
53
|
+
this.options = new ArrayBuffer(addon.size(0, 'choices'));
|
|
54
54
|
this.buf = config.log ? new ArrayBuffer(addon.size(0, 'log')) : undefined;
|
|
55
55
|
this.log = config.log ? new DataView(this.buf) : undefined;
|
|
56
56
|
this.cache = [undefined, undefined];
|
|
@@ -86,7 +86,7 @@ class Battle {
|
|
|
86
86
|
return this.data.getUint16(OFFSETS.Battle.last_damage, data_2.LE);
|
|
87
87
|
}
|
|
88
88
|
get prng() {
|
|
89
|
-
const offset = OFFSETS.Battle.
|
|
89
|
+
const offset = OFFSETS.Battle.last_moves + (this.config.showdown ? 4 : 2);
|
|
90
90
|
// Pokémon Showdown's PRNGSeed is always big-endian
|
|
91
91
|
const seed = [0, 0, 0, 0];
|
|
92
92
|
if (this.config.showdown) {
|
|
@@ -99,10 +99,10 @@ class Battle {
|
|
|
99
99
|
// Gen12RNG seed requires an index, but if we always rotate the seed based on the index when
|
|
100
100
|
// reading we can avoid tracking the index as it will always be zero. NB: the exact bits of
|
|
101
101
|
// the Battle will not roundtrip, but the *logical* Battle state will.
|
|
102
|
-
const index = this.data.getUint8(offset +
|
|
103
|
-
for (let i = 0; i <
|
|
102
|
+
const index = this.data.getUint8(offset + 9);
|
|
103
|
+
for (let i = 0; i < 9; i++) {
|
|
104
104
|
const j = i - index;
|
|
105
|
-
seed[j < 0 ? j +
|
|
105
|
+
seed[j < 0 ? j + 9 : j] = this.data.getUint8(offset + i);
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
return seed;
|
|
@@ -127,20 +127,25 @@ class Battle {
|
|
|
127
127
|
const buf = new ArrayBuffer(SIZES.Battle);
|
|
128
128
|
const data = new DataView(buf);
|
|
129
129
|
let offset = OFFSETS.Battle.p1;
|
|
130
|
-
const
|
|
130
|
+
const lastMoves = [];
|
|
131
131
|
for (const side of battle.sides) {
|
|
132
|
-
Side.encode(gen, lookup, battle, side, data, offset);
|
|
132
|
+
Side.encode(gen, !!options.showdown, lookup, battle, side, data, offset);
|
|
133
133
|
offset += SIZES.Side;
|
|
134
|
-
|
|
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
|
+
}
|
|
135
141
|
}
|
|
136
142
|
data.setUint16(OFFSETS.Battle.turn, battle.turn, data_2.LE);
|
|
137
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]);
|
|
138
146
|
if (options.showdown) {
|
|
139
|
-
data.
|
|
140
|
-
data.
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
data.setUint8(OFFSETS.Battle.last_selected_indexes, (indexes[1] << 4) | indexes[0]);
|
|
147
|
+
data.setUint8(OFFSETS.Battle.last_moves + 2, lastMoves[2]);
|
|
148
|
+
data.setUint8(OFFSETS.Battle.last_moves + 3, lastMoves[3]);
|
|
144
149
|
}
|
|
145
150
|
encodePRNG(data, battle.prng);
|
|
146
151
|
return new Battle(lookup, data, options);
|
|
@@ -201,25 +206,38 @@ class Side {
|
|
|
201
206
|
const m = this.data.getUint8(this.offset + OFFSETS.Side.last_selected_move);
|
|
202
207
|
return m === 0 ? undefined : this.lookup.moveByNum(m);
|
|
203
208
|
}
|
|
204
|
-
get
|
|
209
|
+
get lastMoveIndex() {
|
|
205
210
|
let m;
|
|
206
211
|
if (this.battle.config.showdown) {
|
|
207
212
|
const off = this.id === 'p1' ? 0 : 2;
|
|
208
|
-
m = this.data.
|
|
213
|
+
m = this.data.getUint8(OFFSETS.Battle.last_moves + off);
|
|
209
214
|
}
|
|
210
215
|
else {
|
|
211
|
-
|
|
212
|
-
m = this.
|
|
216
|
+
const off = this.id === 'p1' ? 0 : 1;
|
|
217
|
+
m = this.data.getUint8(OFFSETS.Battle.last_moves + off) & 0x0F;
|
|
213
218
|
}
|
|
214
219
|
return m === 0 ? undefined : m;
|
|
215
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
|
+
}
|
|
216
233
|
toJSON() {
|
|
217
234
|
return {
|
|
218
235
|
active: this.active?.toJSON(),
|
|
219
236
|
pokemon: Array.from(this.pokemon).map(p => p.toJSON()),
|
|
220
237
|
lastSelectedMove: this.lastSelectedMove,
|
|
221
238
|
lastUsedMove: this.lastUsedMove,
|
|
222
|
-
|
|
239
|
+
lastMoveIndex: this.lastMoveIndex,
|
|
240
|
+
lastMoveCounterable: this.lastMoveCounterable,
|
|
223
241
|
};
|
|
224
242
|
}
|
|
225
243
|
static init(gen, lookup, team, data, offset) {
|
|
@@ -230,11 +248,11 @@ class Side {
|
|
|
230
248
|
data.setUint8(offset + OFFSETS.Side.order + i, i + 1);
|
|
231
249
|
}
|
|
232
250
|
}
|
|
233
|
-
static encode(gen, lookup, battle, side, data, offset) {
|
|
251
|
+
static encode(gen, showdown, lookup, battle, side, data, offset) {
|
|
234
252
|
let i = 0;
|
|
235
253
|
for (const pokemon of side.pokemon) {
|
|
236
254
|
const off = offset + OFFSETS.Side.pokemon + SIZES.Pokemon * (pokemon.position - 1);
|
|
237
|
-
Pokemon.encodeStored(gen, lookup, pokemon, data, off);
|
|
255
|
+
Pokemon.encodeStored(gen, showdown, lookup, pokemon, data, off);
|
|
238
256
|
data.setUint8(offset + OFFSETS.Side.order + (pokemon.position - 1), i + 1);
|
|
239
257
|
i++;
|
|
240
258
|
}
|
|
@@ -424,13 +442,14 @@ class Pokemon {
|
|
|
424
442
|
return status === 'psn' && this.toxic ? 'tox' : status;
|
|
425
443
|
}
|
|
426
444
|
get statusData() {
|
|
427
|
-
|
|
445
|
+
const sleep = this.sleep;
|
|
446
|
+
return { sleep, self: !!sleep && this.ext, toxic: this.toxic };
|
|
428
447
|
}
|
|
429
448
|
get sleep() {
|
|
430
449
|
const val = this.data.getUint8(this.offset.stored + OFFSETS.Pokemon.status);
|
|
431
450
|
return val & 0b111;
|
|
432
451
|
}
|
|
433
|
-
get
|
|
452
|
+
get ext() {
|
|
434
453
|
return (this.data.getUint8(this.offset.stored + OFFSETS.Pokemon.status) >> 7) === 1;
|
|
435
454
|
}
|
|
436
455
|
get toxic() {
|
|
@@ -480,7 +499,7 @@ class Pokemon {
|
|
|
480
499
|
data.setUint8(stored + OFFSETS.Pokemon.types, encodeTypes(lookup, species.types));
|
|
481
500
|
data.setUint8(stored + OFFSETS.Pokemon.level, set.level ?? 100);
|
|
482
501
|
}
|
|
483
|
-
static encodeStored(gen, lookup, pokemon, data, offset) {
|
|
502
|
+
static encodeStored(gen, showdown, lookup, pokemon, data, offset) {
|
|
484
503
|
let off = 0;
|
|
485
504
|
const stats = pokemon.stored.stats;
|
|
486
505
|
for (const stat of gen.stats) {
|
|
@@ -499,7 +518,7 @@ class Pokemon {
|
|
|
499
518
|
data.setUint8(offset + OFFSETS.Pokemon.species, species.num);
|
|
500
519
|
data.setUint8(offset + OFFSETS.Pokemon.types, encodeTypes(lookup, species.types));
|
|
501
520
|
data.setUint8(offset + OFFSETS.Pokemon.level, pokemon.level);
|
|
502
|
-
data.setUint8(offset + OFFSETS.Pokemon.status, encodeStatus(pokemon));
|
|
521
|
+
data.setUint8(offset + OFFSETS.Pokemon.status, encodeStatus(pokemon, showdown));
|
|
503
522
|
}
|
|
504
523
|
static encodeActive(gen, lookup, battle, pokemon, data, offset) {
|
|
505
524
|
if (!pokemon)
|
|
@@ -620,7 +639,7 @@ class StoredPokemon {
|
|
|
620
639
|
}
|
|
621
640
|
exports.StoredPokemon = StoredPokemon;
|
|
622
641
|
function encodePRNG(data, seed) {
|
|
623
|
-
const offset = OFFSETS.Battle.last_damage + 2 + (seed.length === 4 ? 4 :
|
|
642
|
+
const offset = OFFSETS.Battle.last_damage + 2 + (seed.length === 4 ? 4 : 2);
|
|
624
643
|
if (seed.length === 4) {
|
|
625
644
|
data.setUint16(offset, seed[data_2.LE ? 3 : 0], data_2.LE);
|
|
626
645
|
data.setUint16(offset + 2, seed[data_2.LE ? 2 : 1], data_2.LE);
|
|
@@ -628,7 +647,7 @@ function encodePRNG(data, seed) {
|
|
|
628
647
|
data.setUint16(offset + 6, seed[data_2.LE ? 0 : 3], data_2.LE);
|
|
629
648
|
}
|
|
630
649
|
else {
|
|
631
|
-
for (let i = 0; i <
|
|
650
|
+
for (let i = 0; i < 9; i++) {
|
|
632
651
|
data.setUint8(offset + i, seed[i]);
|
|
633
652
|
}
|
|
634
653
|
// NB: ArrayBuffer is zero initialized already so we don't need to set the index to zero
|
|
@@ -646,18 +665,15 @@ function decodeSigned(n) {
|
|
|
646
665
|
function encodeSigned(n) {
|
|
647
666
|
return (n < 0) ? 0b10000 + n : n;
|
|
648
667
|
}
|
|
649
|
-
function encodeStatus(pokemon) {
|
|
668
|
+
function encodeStatus(pokemon, showdown) {
|
|
650
669
|
if (pokemon.statusData.sleep) {
|
|
651
670
|
if (pokemon.status !== 'slp') {
|
|
652
671
|
throw new Error('Pokemon is not asleep but has non-zero sleep turns');
|
|
653
672
|
}
|
|
654
673
|
return (pokemon.statusData.self ? 0x80 : 0) | pokemon.statusData.sleep;
|
|
655
674
|
}
|
|
656
|
-
else if (pokemon.statusData.self) {
|
|
657
|
-
throw new Error('Pokemon is not asleep but has self-inflicted sleep bit set');
|
|
658
|
-
}
|
|
659
675
|
switch (pokemon.status) {
|
|
660
|
-
case 'tox':
|
|
676
|
+
case 'tox': return showdown ? 0b10001000 : 1 << 3;
|
|
661
677
|
case 'psn': return 1 << 3;
|
|
662
678
|
case 'brn': return 1 << 4;
|
|
663
679
|
case 'frz': return 1 << 5;
|