@pkmn/engine 0.1.0-dev.6ef582f1 → 0.1.0-dev.736f7a8b
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 +11 -3
- package/build.zig +31 -15
- package/package.json +11 -11
- package/src/bin/install-pkmn-engine +1 -1
- package/src/lib/common/protocol.zig +79 -25
- package/src/lib/gen1/README.md +83 -3
- package/src/lib/gen1/calc.zig +0 -1
- package/src/lib/gen1/chance.zig +3 -3
- package/src/lib/gen1/helpers.zig +228 -75
- package/src/lib/gen1/mechanics.zig +7 -0
- package/src/lib/gen2/README.md +75 -0
- package/src/lib/gen2/data/moves.zig +15 -2
- package/src/lib/gen2/data.zig +30 -15
- package/src/lib/gen2/helpers.zig +8 -7
- package/src/lib/gen2/mechanics.zig +348 -8
package/README.md
CHANGED
|
@@ -12,6 +12,14 @@
|
|
|
12
12
|
<img alt="License" src="https://img.shields.io/badge/License-MIT-blue.svg" />
|
|
13
13
|
</a>
|
|
14
14
|
</p>
|
|
15
|
+
|
|
16
|
+
> [!WARNING]
|
|
17
|
+
> **This project is under heavy development** and currently the `main` branch contains numerous breaking
|
|
18
|
+
> changes which may not work and which are not fully documented. Please **wait for the forthcoming
|
|
19
|
+
> initial v0.1 release** before depending on this project (code from the
|
|
20
|
+
> [`old`](https://github.com/pkmn/engine/releases/tag/old) tag can also be used to experiment with a
|
|
21
|
+
> more stable version of the codebase).
|
|
22
|
+
|
|
15
23
|
<hr />
|
|
16
24
|
|
|
17
25
|
A minimal, complete, Pokémon battle simulation engine optimized for performance and
|
|
@@ -49,9 +57,9 @@ $ zig build --prefix /usr/local -Doptimize=ReleaseFast
|
|
|
49
57
|
```
|
|
50
58
|
|
|
51
59
|
The Zig website has [installation instructions](https://ziglang.org/learn/getting-started/) which
|
|
52
|
-
walk through how to install Zig on each platform - the engine code should work on Zig v0.11.0
|
|
53
|
-
|
|
54
|
-
|
|
60
|
+
walk through how to install Zig on each platform - the engine code should work on Zig v0.11.0 or
|
|
61
|
+
greater, though tracks Zig's master branch so this may change in the future if breaking language
|
|
62
|
+
changes are introduced.
|
|
55
63
|
|
|
56
64
|
`libpkmn` can be built with `-Dshowdown` to instead produce the Pokémon Showdown compatible
|
|
57
65
|
`libpkmn-showdown` library. Furthermore, protocol message logging can be enabled through `-Dlog`.
|
package/build.zig
CHANGED
|
@@ -66,16 +66,16 @@ pub fn build(b: *std.Build) !void {
|
|
|
66
66
|
const addon = b.fmt("{s}.node", .{name});
|
|
67
67
|
const lib = b.addSharedLibrary(.{
|
|
68
68
|
.name = addon,
|
|
69
|
+
.main_pkg_path = .{ .path = "." },
|
|
69
70
|
.root_source_file = .{ .path = "src/lib/binding/node.zig" },
|
|
70
71
|
.optimize = optimize,
|
|
71
72
|
.target = target,
|
|
72
73
|
});
|
|
73
74
|
lib.addOptions("build_options", options);
|
|
74
|
-
lib.
|
|
75
|
-
lib.addSystemIncludePath(headers);
|
|
75
|
+
lib.addSystemIncludePath(.{ .path = headers });
|
|
76
76
|
lib.linkLibC();
|
|
77
77
|
if (node_import_lib) |il| {
|
|
78
|
-
lib.addObjectFile(il);
|
|
78
|
+
lib.addObjectFile(.{ .path = il });
|
|
79
79
|
} else if ((try std.zig.system.NativeTargetInfo.detect(target)).target.os.tag == .windows) {
|
|
80
80
|
try std.io.getStdErr().writeAll("Must provide --node-import-library path on Windows\n");
|
|
81
81
|
std.process.exit(1);
|
|
@@ -95,6 +95,7 @@ pub fn build(b: *std.Build) !void {
|
|
|
95
95
|
} else if (wasm) {
|
|
96
96
|
const lib = b.addSharedLibrary(.{
|
|
97
97
|
.name = name,
|
|
98
|
+
.main_pkg_path = .{ .path = "." },
|
|
98
99
|
.root_source_file = .{ .path = "src/lib/binding/wasm.zig" },
|
|
99
100
|
.optimize = switch (optimize) {
|
|
100
101
|
.ReleaseFast, .ReleaseSafe => .ReleaseSmall,
|
|
@@ -103,7 +104,6 @@ pub fn build(b: *std.Build) !void {
|
|
|
103
104
|
.target = .{ .cpu_arch = .wasm32, .os_tag = .freestanding },
|
|
104
105
|
});
|
|
105
106
|
lib.addOptions("build_options", options);
|
|
106
|
-
lib.setMainPkgPath("./");
|
|
107
107
|
lib.stack_size = wasm_stack_size;
|
|
108
108
|
lib.rdynamic = true;
|
|
109
109
|
lib.strip = strip;
|
|
@@ -121,14 +121,14 @@ pub fn build(b: *std.Build) !void {
|
|
|
121
121
|
} else if (dynamic) {
|
|
122
122
|
const lib = b.addSharedLibrary(.{
|
|
123
123
|
.name = name,
|
|
124
|
+
.main_pkg_path = .{ .path = "." },
|
|
124
125
|
.root_source_file = .{ .path = "src/lib/binding/c.zig" },
|
|
125
126
|
.version = try std.SemanticVersion.parse(version),
|
|
126
127
|
.optimize = optimize,
|
|
127
128
|
.target = target,
|
|
128
129
|
});
|
|
129
130
|
lib.addOptions("build_options", options);
|
|
130
|
-
lib.
|
|
131
|
-
lib.addIncludePath("src/include");
|
|
131
|
+
lib.addIncludePath(.{ .path = "src/include" });
|
|
132
132
|
maybeStrip(b, lib, b.getInstallStep(), strip, cmd);
|
|
133
133
|
if (pic) lib.force_pic = pic;
|
|
134
134
|
b.installArtifact(lib);
|
|
@@ -136,18 +136,30 @@ pub fn build(b: *std.Build) !void {
|
|
|
136
136
|
} else {
|
|
137
137
|
const lib = b.addStaticLibrary(.{
|
|
138
138
|
.name = name,
|
|
139
|
+
.main_pkg_path = .{ .path = "." },
|
|
139
140
|
.root_source_file = .{ .path = "src/lib/binding/c.zig" },
|
|
140
141
|
.optimize = optimize,
|
|
141
142
|
.target = target,
|
|
142
143
|
});
|
|
143
144
|
lib.addOptions("build_options", options);
|
|
144
|
-
lib.
|
|
145
|
-
lib.addIncludePath("src/include");
|
|
145
|
+
lib.addIncludePath(.{ .path = "src/include" });
|
|
146
146
|
lib.bundle_compiler_rt = true;
|
|
147
147
|
maybeStrip(b, lib, b.getInstallStep(), strip, cmd);
|
|
148
148
|
if (pic) lib.force_pic = pic;
|
|
149
|
-
if (emit_asm)
|
|
150
|
-
|
|
149
|
+
if (emit_asm) {
|
|
150
|
+
b.getInstallStep().dependOn(&b.addInstallFileWithDir(
|
|
151
|
+
lib.getEmittedAsm(),
|
|
152
|
+
.prefix,
|
|
153
|
+
b.fmt("{s}.s", .{name}),
|
|
154
|
+
).step);
|
|
155
|
+
}
|
|
156
|
+
if (emit_ll) {
|
|
157
|
+
b.getInstallStep().dependOn(&b.addInstallFileWithDir(
|
|
158
|
+
lib.getEmittedLlvmIr(),
|
|
159
|
+
.prefix,
|
|
160
|
+
b.fmt("{s}.ll", .{name}),
|
|
161
|
+
).step);
|
|
162
|
+
}
|
|
151
163
|
b.installArtifact(lib);
|
|
152
164
|
c = true;
|
|
153
165
|
}
|
|
@@ -219,12 +231,16 @@ pub fn build(b: *std.Build) !void {
|
|
|
219
231
|
fuzz_config.tool.name = "fuzz";
|
|
220
232
|
const fuzz = tool(b, "src/test/fuzz.zig", fuzz_config);
|
|
221
233
|
|
|
222
|
-
const
|
|
234
|
+
const analyze = tool(b, "src/tools/analyze.zig", tools);
|
|
223
235
|
const dump = tool(b, "src/tools/dump.zig", tools);
|
|
224
|
-
const serde = tool(b, "src/tools/serde.zig", tools);
|
|
225
236
|
const transitions = tool(b, "src/tools/transitions.zig", tools);
|
|
226
237
|
|
|
227
|
-
|
|
238
|
+
// FIXME: serde randomly fails to build in some release configurations
|
|
239
|
+
var hack = tools;
|
|
240
|
+
if (optimize != .Debug) hack.tool.tests = null;
|
|
241
|
+
const serde = tool(b, "src/tools/serde.zig", hack);
|
|
242
|
+
|
|
243
|
+
b.step("analyze", "Run LLVM analysis tool").dependOn(&analyze.step);
|
|
228
244
|
b.step("benchmark", "Run benchmark code").dependOn(&benchmark.step);
|
|
229
245
|
b.step("dump", "Run protocol dump tool").dependOn(&dump.step);
|
|
230
246
|
b.step("fuzz", "Run fuzz tester").dependOn(&fuzz.step);
|
|
@@ -281,12 +297,12 @@ const TestStep = struct {
|
|
|
281
297
|
std.fs.path.basename(std.fs.path.dirname(path).?),
|
|
282
298
|
std.fs.path.stem(std.fs.path.basename(path)),
|
|
283
299
|
}),
|
|
300
|
+
.main_pkg_path = .{ .path = "." },
|
|
284
301
|
.root_source_file = .{ .path = path },
|
|
285
302
|
.optimize = config.optimize,
|
|
286
303
|
.target = config.target,
|
|
287
304
|
.filter = test_filter,
|
|
288
305
|
});
|
|
289
|
-
tests.setMainPkgPath("./");
|
|
290
306
|
tests.addOptions("build_options", options);
|
|
291
307
|
tests.single_threaded = true;
|
|
292
308
|
maybeStrip(b, tests, &tests.step, config.strip, config.cmd);
|
|
@@ -343,7 +359,7 @@ const ToolsStep = struct {
|
|
|
343
359
|
const self = b.allocator.create(ToolsStep) catch @panic("OOM");
|
|
344
360
|
const step = std.Build.Step.init(.{ .id = .custom, .name = "Install tools", .owner = b });
|
|
345
361
|
self.* = ToolsStep{ .step = step };
|
|
346
|
-
for (exes.items) |t| self.step.dependOn(&b.addInstallArtifact(t).step);
|
|
362
|
+
for (exes.items) |t| self.step.dependOn(&b.addInstallArtifact(t, .{}).step);
|
|
347
363
|
return self;
|
|
348
364
|
}
|
|
349
365
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pkmn/engine",
|
|
3
|
-
"version": "0.1.0-dev.
|
|
3
|
+
"version": "0.1.0-dev.736f7a8b",
|
|
4
4
|
"description": "A minimal, complete, Pokémon battle simulation engine optimized for performance",
|
|
5
5
|
"repository": "github:pkmn/engine",
|
|
6
6
|
"license": "MIT",
|
|
@@ -73,30 +73,30 @@
|
|
|
73
73
|
"access": "public"
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
|
-
"@pkmn/data": "^0.7.
|
|
77
|
-
"@pkmn/protocol": "^0.6.
|
|
76
|
+
"@pkmn/data": "^0.7.50",
|
|
77
|
+
"@pkmn/protocol": "^0.6.13"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
|
-
"@pkmn/eslint-config": "^5.
|
|
81
|
-
"@pkmn/img": "^0.2.
|
|
80
|
+
"@pkmn/eslint-config": "^5.2.0",
|
|
81
|
+
"@pkmn/img": "^0.2.15",
|
|
82
82
|
"@pkmn/sets": "^5.0.4",
|
|
83
|
-
"@pkmn/sim": "0.7.
|
|
83
|
+
"@pkmn/sim": "0.7.50",
|
|
84
84
|
"@types/html-minifier": "^4.0.2",
|
|
85
|
-
"@types/jest": "^29.5.
|
|
85
|
+
"@types/jest": "^29.5.4",
|
|
86
86
|
"@types/minimist": "^1.2.2",
|
|
87
87
|
"@types/mustache": "^4.2.2",
|
|
88
|
-
"@types/node": "^20.
|
|
88
|
+
"@types/node": "^20.5.9",
|
|
89
89
|
"@vitest/coverage-c8": "^0.33.0",
|
|
90
90
|
"binaryen": "^114.0.0",
|
|
91
|
-
"eslint": "^8.
|
|
91
|
+
"eslint": "^8.48.0",
|
|
92
92
|
"html-minifier": "^4.0.0",
|
|
93
93
|
"json-stringify-pretty-compact": "3.0.0",
|
|
94
94
|
"minimist": "^1.2.8",
|
|
95
95
|
"mustache": "^4.2.0",
|
|
96
96
|
"source-map-support": "^0.5.21",
|
|
97
97
|
"trakr": "^0.2.0",
|
|
98
|
-
"typescript": "^5.
|
|
99
|
-
"vitest": "^0.
|
|
98
|
+
"typescript": "^5.2.2",
|
|
99
|
+
"vitest": "^0.34.4"
|
|
100
100
|
},
|
|
101
101
|
"scripts": {
|
|
102
102
|
"lint": "eslint --cache src --ext ts,js && eslint src/bin/*",
|
|
@@ -10,7 +10,7 @@ const path = require('path');
|
|
|
10
10
|
const {pipeline} = require('stream/promises');
|
|
11
11
|
const tty = require('tty');
|
|
12
12
|
|
|
13
|
-
const ZIG_VERSION = {major: 0, minor: 11, patch: 0
|
|
13
|
+
const ZIG_VERSION = {major: 0, minor: 11, patch: 0};
|
|
14
14
|
const DEV = ZIG_VERSION.dev ? `-dev.${ZIG_VERSION.dev}` : '';
|
|
15
15
|
const VERSION = `${ZIG_VERSION.major}.${ZIG_VERSION.minor}.${ZIG_VERSION.patch}${DEV}`;
|
|
16
16
|
const INDEX = 'https://ziglang.org/download/index.json';
|
|
@@ -136,6 +136,8 @@ pub const Damage = enum(u8) {
|
|
|
136
136
|
Confusion,
|
|
137
137
|
LeechSeed,
|
|
138
138
|
RecoilOf,
|
|
139
|
+
|
|
140
|
+
Spikes,
|
|
139
141
|
};
|
|
140
142
|
|
|
141
143
|
pub const Status = enum(u8) {
|
|
@@ -264,7 +266,15 @@ pub fn Log(comptime Writer: type) type {
|
|
|
264
266
|
if (!enabled) return;
|
|
265
267
|
|
|
266
268
|
try self.writer.writeAll(&.{ @intFromEnum(ArgType.Switch), @as(u8, @bitCast(ident)) });
|
|
267
|
-
|
|
269
|
+
if (@hasField(@TypeOf(pokemon.*), "dvs")) {
|
|
270
|
+
try self.writer.writeAll(&.{
|
|
271
|
+
@intFromEnum(pokemon.species),
|
|
272
|
+
@intFromEnum(pokemon.dvs.gender),
|
|
273
|
+
pokemon.level,
|
|
274
|
+
});
|
|
275
|
+
} else {
|
|
276
|
+
try self.writer.writeAll(&.{ @intFromEnum(pokemon.species), pokemon.level });
|
|
277
|
+
}
|
|
268
278
|
try self.writer.writeIntNative(u16, pokemon.hp);
|
|
269
279
|
try self.writer.writeIntNative(u16, pokemon.stats.hp);
|
|
270
280
|
try self.writer.writeAll(&.{pokemon.status});
|
|
@@ -646,9 +656,13 @@ pub const ByteStream = struct {
|
|
|
646
656
|
};
|
|
647
657
|
|
|
648
658
|
pub const Kind = enum { Move, Species, Type, Status };
|
|
649
|
-
pub const Formatter = fn (Kind, u8) []const u8;
|
|
650
659
|
|
|
651
|
-
pub fn format(
|
|
660
|
+
pub fn format(
|
|
661
|
+
comptime gen: anytype,
|
|
662
|
+
a: []const u8,
|
|
663
|
+
b: ?[]const u8,
|
|
664
|
+
color: bool,
|
|
665
|
+
) void {
|
|
652
666
|
print("\n", .{});
|
|
653
667
|
|
|
654
668
|
var i: usize = 0;
|
|
@@ -701,17 +715,27 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
|
|
|
701
715
|
.Move => {
|
|
702
716
|
const source = ID.from(@intCast(a[i]));
|
|
703
717
|
printc(" {s}({d})", .{ @tagName(source.player), source.id }, a, b, &i, 1, color);
|
|
704
|
-
printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
|
|
718
|
+
printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
|
|
705
719
|
const target = ID.from(@intCast(a[i]));
|
|
706
720
|
printc(" {s}({d})", .{ @tagName(target.player), target.id }, a, b, &i, 1, color);
|
|
707
721
|
const reason: Move = @enumFromInt(a[i]);
|
|
708
722
|
printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
|
|
709
|
-
if (reason == .From)
|
|
723
|
+
if (reason == .From) {
|
|
724
|
+
printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
|
|
725
|
+
}
|
|
710
726
|
},
|
|
711
727
|
.Switch => {
|
|
712
728
|
const id = ID.from(@intCast(a[i]));
|
|
713
729
|
printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
|
|
714
|
-
printc(" {s}", .{formatter(.Species, a[i])}, a, b, &i, 1, color);
|
|
730
|
+
printc(" {s}", .{formatter(gen, .Species, a[i])}, a, b, &i, 1, color);
|
|
731
|
+
if (@hasDecl(gen, "Gender")) {
|
|
732
|
+
if (a[i] == N(gen.Gender.Unknown)) {
|
|
733
|
+
i += 1;
|
|
734
|
+
} else {
|
|
735
|
+
var gender = if (a[i] == N(gen.Gender.Male)) "M" else "F";
|
|
736
|
+
printc(", {s}", .{gender}, a, b, &i, 1, color);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
715
739
|
printc(" L{d}", .{a[i]}, a, b, &i, 1, color);
|
|
716
740
|
switch (endian) {
|
|
717
741
|
.Big => {
|
|
@@ -727,7 +751,7 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
|
|
|
727
751
|
printc("/{d}", .{hp}, a, b, &i, 2, color);
|
|
728
752
|
},
|
|
729
753
|
}
|
|
730
|
-
printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
|
|
754
|
+
printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
|
|
731
755
|
},
|
|
732
756
|
.Cant => {
|
|
733
757
|
const id = ID.from(@intCast(a[i]));
|
|
@@ -735,7 +759,7 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
|
|
|
735
759
|
const reason: Cant = @enumFromInt(a[i]);
|
|
736
760
|
printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
|
|
737
761
|
if (reason == .Disable) {
|
|
738
|
-
printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
|
|
762
|
+
printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
|
|
739
763
|
}
|
|
740
764
|
},
|
|
741
765
|
.Faint,
|
|
@@ -775,7 +799,7 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
|
|
|
775
799
|
printc("/{d}", .{hp}, a, b, &i, 2, color);
|
|
776
800
|
},
|
|
777
801
|
}
|
|
778
|
-
printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
|
|
802
|
+
printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
|
|
779
803
|
if (arg == .Damage) {
|
|
780
804
|
const reason: Damage = @enumFromInt(a[i]);
|
|
781
805
|
printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
|
|
@@ -795,15 +819,17 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
|
|
|
795
819
|
.Status => {
|
|
796
820
|
const id = ID.from(@intCast(a[i]));
|
|
797
821
|
printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
|
|
798
|
-
printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
|
|
822
|
+
printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
|
|
799
823
|
const reason: Status = @enumFromInt(a[i]);
|
|
800
824
|
printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
|
|
801
|
-
if (reason == .From)
|
|
825
|
+
if (reason == .From) {
|
|
826
|
+
printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
|
|
827
|
+
}
|
|
802
828
|
},
|
|
803
829
|
.CureStatus => {
|
|
804
830
|
const id = ID.from(@intCast(a[i]));
|
|
805
831
|
printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
|
|
806
|
-
printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
|
|
832
|
+
printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
|
|
807
833
|
const reason: CureStatus = @enumFromInt(a[i]);
|
|
808
834
|
printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
|
|
809
835
|
},
|
|
@@ -826,7 +852,7 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
|
|
|
826
852
|
.Prepare => {
|
|
827
853
|
const id = ID.from(@intCast(a[i]));
|
|
828
854
|
printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
|
|
829
|
-
printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
|
|
855
|
+
printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
|
|
830
856
|
},
|
|
831
857
|
.Activate => {
|
|
832
858
|
const id = ID.from(@intCast(a[i]));
|
|
@@ -841,12 +867,12 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
|
|
|
841
867
|
if (@as(Start, @enumFromInt(reason)) == .TypeChange) {
|
|
842
868
|
const types = @as(gen1.Types, @bitCast(a[i]));
|
|
843
869
|
const args = .{
|
|
844
|
-
formatter(.Type, @intFromEnum(types.type1)),
|
|
845
|
-
formatter(.Type, @intFromEnum(types.type2)),
|
|
870
|
+
formatter(gen, .Type, @intFromEnum(types.type1)),
|
|
871
|
+
formatter(gen, .Type, @intFromEnum(types.type2)),
|
|
846
872
|
};
|
|
847
873
|
printc(" {s}/{s}", args, a, b, &i, 1, color);
|
|
848
874
|
} else if (reason >= @intFromEnum(Start.Disable)) {
|
|
849
|
-
printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
|
|
875
|
+
printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
|
|
850
876
|
}
|
|
851
877
|
},
|
|
852
878
|
.End => {
|
|
@@ -896,7 +922,7 @@ fn printc(
|
|
|
896
922
|
}
|
|
897
923
|
|
|
898
924
|
pub fn expectLog(
|
|
899
|
-
comptime
|
|
925
|
+
comptime gen: anytype,
|
|
900
926
|
expected: []const u8,
|
|
901
927
|
actual: []const u8,
|
|
902
928
|
offset: usize,
|
|
@@ -915,8 +941,8 @@ pub fn expectLog(
|
|
|
915
941
|
|
|
916
942
|
expectEqualBytes(expected, actual, offset) catch |err| switch (err) {
|
|
917
943
|
error.TestExpectedEqual => {
|
|
918
|
-
format(
|
|
919
|
-
format(
|
|
944
|
+
format(gen, expected, null, color);
|
|
945
|
+
format(gen, actual, expected, color);
|
|
920
946
|
return err;
|
|
921
947
|
},
|
|
922
948
|
else => return err,
|
|
@@ -956,6 +982,11 @@ const gen1 = struct {
|
|
|
956
982
|
pub const helpers = @import("../gen1/helpers.zig");
|
|
957
983
|
};
|
|
958
984
|
|
|
985
|
+
const gen2 = struct {
|
|
986
|
+
pub usingnamespace @import("../gen2/data.zig");
|
|
987
|
+
pub const helpers = @import("../gen2/helpers.zig");
|
|
988
|
+
};
|
|
989
|
+
|
|
959
990
|
var buf: [gen1.LOGS_SIZE]u8 = undefined;
|
|
960
991
|
var stream = ByteStream{ .buffer = &buf };
|
|
961
992
|
var log: FixedLog = .{ .writer = stream.writer() };
|
|
@@ -964,15 +995,19 @@ const M = gen1.Move;
|
|
|
964
995
|
const S = gen1.Species;
|
|
965
996
|
|
|
966
997
|
fn expectLog1(expected: []const u8, actual: []const u8) !void {
|
|
967
|
-
return expectLog(
|
|
998
|
+
return expectLog(gen1, expected, actual, 0);
|
|
968
999
|
}
|
|
969
1000
|
|
|
970
|
-
fn
|
|
1001
|
+
fn expectLog2(expected: []const u8, actual: []const u8) !void {
|
|
1002
|
+
return expectLog(gen2, expected, actual, 0);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
fn formatter(comptime gen: anytype, kind: Kind, byte: u8) []const u8 {
|
|
971
1006
|
return switch (kind) {
|
|
972
|
-
.Move => @tagName(@as(
|
|
973
|
-
.Species => @tagName(@as(
|
|
974
|
-
.Type => @tagName(@as(
|
|
975
|
-
.Status =>
|
|
1007
|
+
.Move => @tagName(@as(gen.Move, @enumFromInt(byte))),
|
|
1008
|
+
.Species => @tagName(@as(gen.Species, @enumFromInt(byte))),
|
|
1009
|
+
.Type => @tagName(@as(gen.Type, @enumFromInt(byte))),
|
|
1010
|
+
.Status => gen.Status.name(byte),
|
|
976
1011
|
};
|
|
977
1012
|
}
|
|
978
1013
|
|
|
@@ -1057,6 +1092,25 @@ test "|switch|" {
|
|
|
1057
1092
|
};
|
|
1058
1093
|
try expectLog1(expected, buf[0..9]);
|
|
1059
1094
|
stream.reset();
|
|
1095
|
+
|
|
1096
|
+
var blissey = gen2.helpers.Pokemon.init(.{ .species = .Blissey, .moves = &.{.Splash} });
|
|
1097
|
+
blissey.level = 91;
|
|
1098
|
+
blissey.hp = 200;
|
|
1099
|
+
blissey.stats.hp = 400;
|
|
1100
|
+
blissey.status = gen2.Status.init(.PAR);
|
|
1101
|
+
try log.switched(p2.ident(3), &blissey);
|
|
1102
|
+
expected = &(.{
|
|
1103
|
+
N(ArgType.Switch),
|
|
1104
|
+
0b1011,
|
|
1105
|
+
N(gen2.Species.Blissey),
|
|
1106
|
+
N(gen2.Gender.Female),
|
|
1107
|
+
91,
|
|
1108
|
+
} ++ switch (endian) {
|
|
1109
|
+
.Big => .{ 0, 200, 1, 144, par },
|
|
1110
|
+
.Little => .{ 200, 0, 144, 1, par },
|
|
1111
|
+
});
|
|
1112
|
+
try expectLog2(expected, buf[0..10]);
|
|
1113
|
+
stream.reset();
|
|
1060
1114
|
}
|
|
1061
1115
|
|
|
1062
1116
|
test "|cant|" {
|
package/src/lib/gen1/README.md
CHANGED
|
@@ -6,7 +6,9 @@ document](../../../docs/DESIGN.md). The Generation I engine is implemented and t
|
|
|
6
6
|
following files:
|
|
7
7
|
|
|
8
8
|
- [`data.zig`](data.zig) ([`data`](data)): contains definitions of all of the data structures used
|
|
9
|
-
|
|
9
|
+
to implement the core mechanics of the engine, described in detail [below](#data-structures)
|
|
10
|
+
- [`chance.zig`](chance.zig): TODO
|
|
11
|
+
- [`calc.zig`](calc.zig): TODO
|
|
10
12
|
- [`helpers.zig`](helpers.zig): helpers used to construct complex data types with
|
|
11
13
|
sensible defaults (internally used by tests and tools)
|
|
12
14
|
- [`mechanics.zig`](mechanics.zig): code which manipulates the data structures to implement the
|
|
@@ -245,9 +247,17 @@ engine maintains a `Type.PRECEDENCE` table with just the match-ups that are rele
|
|
|
245
247
|
pruned for efficiency) and only looks up precedence when necessary to minimize expensive searches.
|
|
246
248
|
Pokémon Showdown doesn't implement type precedence.
|
|
247
249
|
|
|
250
|
+
### `Chance` / `Actions` / `Action` / `Durations` / `Duration`
|
|
251
|
+
|
|
252
|
+
TODO
|
|
253
|
+
|
|
254
|
+
### `Calc` / `Overrides` / `Summaries` / `Summary`
|
|
255
|
+
|
|
256
|
+
TODO
|
|
257
|
+
|
|
248
258
|
## Information
|
|
249
259
|
|
|
250
|
-
The information of each field (in terms of [bits of
|
|
260
|
+
The information of each field required for mechanics (in terms of [bits of
|
|
251
261
|
entropy](https://en.wikipedia.org/wiki/Entropy_(information_theory))) is as follows:
|
|
252
262
|
|
|
253
263
|
| Data | Range | Bits | | Data | Range | Bits |
|
|
@@ -383,7 +393,7 @@ data in the browser for ease of debugging.
|
|
|
383
393
|
- `order` is a 6 byte array where `order[i]` represents the "slot" of `pokemon[i]`, where the slot
|
|
384
394
|
is usually a unique value from 1 to 6 but can be 0 in situations where a player brings less than six
|
|
385
395
|
Pokémon to battle (note however that if `order[i]` is 0 than for all j > 0 `order[i+j]` **must**
|
|
386
|
-
equal 0)
|
|
396
|
+
equal 0)
|
|
387
397
|
|
|
388
398
|
### `ActivePokemon`
|
|
389
399
|
|
|
@@ -469,6 +479,76 @@ data in the browser for ease of debugging.
|
|
|
469
479
|
| 22 | 23 | `type1`/`type2` | The Pokémon's stored types |
|
|
470
480
|
| 23 | 24 | `level` | The Pokémon's level |
|
|
471
481
|
|
|
482
|
+
|
|
483
|
+
### `Action`
|
|
484
|
+
|
|
485
|
+
`Actions` consist of an `Action` for Player 1 followed by an `Action` for Player 2.
|
|
486
|
+
|
|
487
|
+
> **NOTE:** The offsets in the following table represent *bits* and **not** bytes.
|
|
488
|
+
|
|
489
|
+
| Start | End | Data | Description |
|
|
490
|
+
| ----- | --- | ------------------------ | ------------------------------------------------------------------------------- |
|
|
491
|
+
| 0 | 8 | `damage` | The roll to be returned damage (217-255). |
|
|
492
|
+
| 8 | 10 | `hit` | The roll to be returned for accuracy (1 for miss, 2 for hit). |
|
|
493
|
+
| 10 | 12 | `critical_hit` | The roll to be returned for critical hits (1 for no, 2 for yes). |
|
|
494
|
+
| 12 | 14 | `secondary_chance` | The roll to be returned for secondary chance rolls (1 for no-proc, 2 for proc). |
|
|
495
|
+
| 14 | 16 | `speed_tie` | The player to return for speed ties (1 for Player 1, 2 for Player 2). |
|
|
496
|
+
| 16 | 18 | `confused` | The roll to be returned for confusion self-hits (1 for no-proc, 2 for proc). |
|
|
497
|
+
| 18 | 20 | `paralyzed` | The roll to be returned for full paralysis (1 for no-proc, 2 for proc). |
|
|
498
|
+
| 20 | 24 | `duration` | The roll to be returned for the duration (including for binding moves). |
|
|
499
|
+
| 24 | 40 | [`durations`](#duration) | Values of various durations. |
|
|
500
|
+
| 40 | 44 | `move_slot` | The roll to be returned for the move slot (1-4, invalid values ignored). |
|
|
501
|
+
| 44 | 48 | `move_slot` | The roll to be returned for a multi-hit move distribution (2-5). |
|
|
502
|
+
| 48 | 56 | `psywave` | One greater than the roll to be returned for Psywave damage. |
|
|
503
|
+
| 56 | 64 | `metronome` | The move to return for Metronome. |
|
|
504
|
+
|
|
505
|
+
- for any field a value of 0 is considered to be unset
|
|
506
|
+
- **both Player 1 and Player 2 must set the same value for `speed_tie`.**
|
|
507
|
+
- `duration` determines the roll for any effect which lasts over multiple turns, whereas `durations` can either be used to
|
|
508
|
+
track how long these effects have lasted (for the purposes of computing probablities with `-Dchance`) or can be used to
|
|
509
|
+
[override](#overrides) (extend or end) the duration of specfic effects with `-Dcalc`.
|
|
510
|
+
|
|
511
|
+
#### `Duration`
|
|
512
|
+
|
|
513
|
+
> **NOTE:** The offsets in the following table represent *bits* and **not** bytes.
|
|
514
|
+
|
|
515
|
+
| Start | End | Data |
|
|
516
|
+
| ----- | --- | ----------- |
|
|
517
|
+
| 0 | 3 | `sleep` |
|
|
518
|
+
| 3 | 6 | `confusion` |
|
|
519
|
+
| 4 | 10 | `disable` |
|
|
520
|
+
| 10 | 13 | `attacking` |
|
|
521
|
+
| 13 | 16 | `binding` |
|
|
522
|
+
|
|
523
|
+
- the meaning of each field depends on the context in which `Duration` appears:
|
|
524
|
+
- within `Chance` `actions`, the durations tracked refer to the number of turns a Pokémon has been
|
|
525
|
+
observed to be under an effect
|
|
526
|
+
- within `Overrides` `actions`, the durations refer to the value the next `Chance` `actions`
|
|
527
|
+
should be made to take on
|
|
528
|
+
- within `Overrides` `durations`, the durations serve as a mask to determine to whether or not the
|
|
529
|
+
durations in `Overrides` `actions` should be considered to be set or not
|
|
530
|
+
◊
|
|
531
|
+
### `Overrides`
|
|
532
|
+
|
|
533
|
+
| Start | End | Data | Description |
|
|
534
|
+
| ----- | --- | --------------------------- | ------------------------------------------------------------------------------------- |
|
|
535
|
+
| 0 | 8 | [`action.p1`](#action) | Rolls to force Player 1's RNG to return for specific events. |
|
|
536
|
+
| 8 | 16 | [`action.p2`](#action) | Rolls to force Player 2's RNG to return for specific events. |
|
|
537
|
+
| 16 | 18 | [`durations.p1`](#duration) | Whether or not to modify Player 1's effect durations to match `actions.p1.durations`. |
|
|
538
|
+
| 18 | 20 | [`durations.p2`](#duration) | Whether or not to modify Player 2's effect durations to match `actions.p1.durations`. |
|
|
539
|
+
| 20 | 24 | - | *Zero padding* |
|
|
540
|
+
|
|
541
|
+
### `Summaries`
|
|
542
|
+
|
|
543
|
+
| Start | End | Data | Description |
|
|
544
|
+
| ----- | --- | ------------------ | ------------------------------------------------------------------------------- |
|
|
545
|
+
| 0 | 2 | `p1.damage.base` | Player 1's base computed damage before the damage roll is applied. |
|
|
546
|
+
| 2 | 4 | `p1.damage.final` | Player 1's final computed damage that gets applied to the Pokémon. |
|
|
547
|
+
| 4 | 6 | `p1.damage.capped` | Whether higher damage from Player 1 will saturate / result in the same outcome. |
|
|
548
|
+
| 6 | 8 | `p2.damage.base` | Player 2's base computed damage before the damage roll is applied. |
|
|
549
|
+
| 8 | 10 | `p2.damage.final` | Player 2's final computed damage that gets applied to the Pokémon. |
|
|
550
|
+
| 10 | 12 | `p2.damage.capped` | Whether higher damage from Player 2 will saturate / result in the same outcome. |
|
|
551
|
+
|
|
472
552
|
## Bugs
|
|
473
553
|
|
|
474
554
|
In addition to its alternative [RNG semantics](#rng), Pokémon Showdown's implementation of the first
|
package/src/lib/gen1/calc.zig
CHANGED
package/src/lib/gen1/chance.zig
CHANGED
|
@@ -126,7 +126,7 @@ pub const Action = packed struct {
|
|
|
126
126
|
/// If not None, the value to be returned for
|
|
127
127
|
/// Rolls.{confusionChance,secondaryChance,poisonChance}.
|
|
128
128
|
secondary_chance: Optional(bool) = .None,
|
|
129
|
-
/// If not None, the
|
|
129
|
+
/// If not None, the Player to be returned by Rolls.speedTie.
|
|
130
130
|
speed_tie: Optional(Player) = .None,
|
|
131
131
|
|
|
132
132
|
/// If not None, the value to return for Rolls.confused.
|
|
@@ -181,9 +181,9 @@ pub const Action = packed struct {
|
|
|
181
181
|
if (v != 0) {
|
|
182
182
|
if (printed) try writer.writeAll(", ");
|
|
183
183
|
if (shape) {
|
|
184
|
-
try writer.print("{s}:?", .{
|
|
184
|
+
try writer.print("{s}:?", .{f.name});
|
|
185
185
|
} else {
|
|
186
|
-
try writer.print("{s}:{d}", .{
|
|
186
|
+
try writer.print("{s}:{d}", .{ f.name, v });
|
|
187
187
|
}
|
|
188
188
|
printed = true;
|
|
189
189
|
}
|