@pkmn/engine 0.1.0-dev.a15b6c17 → 0.1.0-dev.a19bc07d
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 +7 -6
- package/build.zig +125 -42
- package/package.json +13 -13
- package/src/lib/bindings/c.zig +4 -3
- package/src/lib/bindings/wasm.zig +3 -2
- package/src/lib/common/debug.zig +14 -3
- package/src/lib/common/optional.zig +20 -10
- package/src/lib/common/util.zig +21 -1
- package/src/lib/gen1/chance.zig +40 -17
- package/src/lib/gen1/helpers.zig +24 -14
- package/src/lib/gen2/chance.zig +29 -11
- package/src/lib/gen2/helpers.zig +25 -15
package/README.md
CHANGED
|
@@ -13,12 +13,10 @@
|
|
|
13
13
|
</a>
|
|
14
14
|
</p>
|
|
15
15
|
|
|
16
|
-
> [!WARNING]
|
|
16
|
+
> [!WARNING]
|
|
17
17
|
> **This project is under heavy development** and currently the `main` branch contains numerous breaking
|
|
18
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
|
|
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).
|
|
19
|
+
> initial v0.1 release** before depending on this project.
|
|
22
20
|
|
|
23
21
|
<hr />
|
|
24
22
|
|
|
@@ -86,8 +84,11 @@ lifecycle script](https://docs.npmjs.com/cli/v8/using-npm/scripts) runs
|
|
|
86
84
|
it can't find one, as well as looking for (and downloading, if necessary) the required Node headers
|
|
87
85
|
needed to successfully build the addons natively.
|
|
88
86
|
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
> [!IMPORTANT]
|
|
88
|
+
> If you have configured `npm` to `--ignore-scripts` (or are using [npm v12+ and have not
|
|
89
|
+
> approved](https://github.blog/changelog/2026-06-09-upcoming-breaking-changes-for-npm-v12/)
|
|
90
|
+
> `@pkmn/engine` via `npm approve-scripts @pkmn/engine`) **you must either run `npx install-pkmn-engine`
|
|
91
|
+
> directly or build the addons manually and place the artifacts in the expected paths.**
|
|
91
92
|
|
|
92
93
|
### `pkmn`
|
|
93
94
|
|
package/build.zig
CHANGED
|
@@ -12,7 +12,12 @@ pub const Options = struct {
|
|
|
12
12
|
calc: ?bool = null,
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
pub fn module(
|
|
15
|
+
pub fn module(
|
|
16
|
+
b: *std.Build,
|
|
17
|
+
options: Options,
|
|
18
|
+
target: std.Build.ResolvedTarget,
|
|
19
|
+
optimize: std.builtin.OptimizeMode,
|
|
20
|
+
) *std.Build.Module {
|
|
16
21
|
const build_options = b.addOptions();
|
|
17
22
|
build_options.addOption(?bool, "showdown", options.showdown);
|
|
18
23
|
build_options.addOption(?bool, "log", options.log);
|
|
@@ -22,7 +27,12 @@ pub fn module(b: *std.Build, options: Options) *std.Build.Module {
|
|
|
22
27
|
const root_source_file = b.path("src/lib/pkmn.zig");
|
|
23
28
|
const imports: []const std.Build.Module.Import =
|
|
24
29
|
&.{.{ .name = "build_options", .module = build_options.createModule() }};
|
|
25
|
-
return b.createModule(.{
|
|
30
|
+
return b.createModule(.{
|
|
31
|
+
.root_source_file = root_source_file,
|
|
32
|
+
.target = target,
|
|
33
|
+
.optimize = optimize,
|
|
34
|
+
.imports = imports,
|
|
35
|
+
});
|
|
26
36
|
}
|
|
27
37
|
|
|
28
38
|
pub fn build(b: *std.Build) !void {
|
|
@@ -45,7 +55,10 @@ pub fn build(b: *std.Build) !void {
|
|
|
45
55
|
const emit_asm = b.option(bool, "emit-asm", "Output .s (assembly code)") orelse false;
|
|
46
56
|
const emit_ll = b.option(bool, "emit-ll", "Output .ll (LLVM IR)") orelse false;
|
|
47
57
|
|
|
48
|
-
const cmd =
|
|
58
|
+
const cmd = if (@hasDecl(std.Build, "FindProgramOptions"))
|
|
59
|
+
b.findProgram(.{ .names = &.{"strip"} })
|
|
60
|
+
else
|
|
61
|
+
b.findProgram(&.{"strip"}, &.{}) catch null;
|
|
49
62
|
|
|
50
63
|
const json = @embedFile("package.json");
|
|
51
64
|
var parsed = try std.json.parseFromSlice(std.json.Value, b.allocator, json, .{});
|
|
@@ -99,7 +112,8 @@ pub fn build(b: *std.Build) !void {
|
|
|
99
112
|
const translate_c = b.addTranslateC(.{
|
|
100
113
|
.root_source_file = b.path("src/lib/napi.h"),
|
|
101
114
|
.target = target,
|
|
102
|
-
|
|
115
|
+
// TODO: workaround for ziglang/zig#35515
|
|
116
|
+
.optimize = .Debug,
|
|
103
117
|
});
|
|
104
118
|
translate_c.addSystemIncludePath(b.path(headers));
|
|
105
119
|
const addon = b.fmt("{s}.node", .{name});
|
|
@@ -195,7 +209,9 @@ pub fn build(b: *std.Build) !void {
|
|
|
195
209
|
b.fmt("{s}.ll", .{name}),
|
|
196
210
|
).step);
|
|
197
211
|
}
|
|
198
|
-
b.
|
|
212
|
+
const install = b.addInstallArtifact(lib, .{});
|
|
213
|
+
b.getInstallStep().dependOn(&install.step);
|
|
214
|
+
maybeRanlib(b, lib, &install.step);
|
|
199
215
|
c = true;
|
|
200
216
|
}
|
|
201
217
|
|
|
@@ -204,34 +220,45 @@ pub fn build(b: *std.Build) !void {
|
|
|
204
220
|
const header = b.addInstallFileWithDir(path, .header, "pkmn.h");
|
|
205
221
|
b.getInstallStep().dependOn(&header.step);
|
|
206
222
|
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
const file = try std.Io.Dir.path.relative(
|
|
210
|
-
b.allocator,
|
|
211
|
-
cwd,
|
|
212
|
-
&b.graph.environ_map,
|
|
213
|
-
cwd,
|
|
214
|
-
try b.cache_root.join(b.allocator, &.{pc}),
|
|
215
|
-
);
|
|
216
|
-
const pkgconfig_file = try std.Io.Dir.cwd().createFile(b.graph.io, file, .{});
|
|
217
|
-
defer pkgconfig_file.close(b.graph.io);
|
|
218
|
-
|
|
219
|
-
const dirname = comptime std.fs.path.dirname(@src().file) orelse ".";
|
|
220
|
-
var writer = pkgconfig_file.writer(b.graph.io, &.{});
|
|
221
|
-
try writer.interface.print(
|
|
222
|
-
\\prefix={0s}/{1s}
|
|
223
|
+
const content = try std.fmt.allocPrint(b.allocator,
|
|
224
|
+
\\prefix=${{pcfiledir}}/../..
|
|
223
225
|
\\includedir=${{prefix}}/include
|
|
224
226
|
\\libdir=${{prefix}}/lib
|
|
225
227
|
\\
|
|
226
|
-
\\Name: lib{
|
|
227
|
-
\\URL: https://github.com/{
|
|
228
|
-
\\Description: {
|
|
229
|
-
\\Version: {
|
|
228
|
+
\\Name: lib{0s}
|
|
229
|
+
\\URL: https://github.com/{1s}
|
|
230
|
+
\\Description: {2s}
|
|
231
|
+
\\Version: {3s}
|
|
230
232
|
\\Cflags: -I${{includedir}}
|
|
231
|
-
\\Libs: -L${{libdir}} -l{
|
|
232
|
-
, .{
|
|
233
|
+
\\Libs: -L${{libdir}} -l{0s}
|
|
234
|
+
, .{ name, repository.next().?, description, version });
|
|
233
235
|
|
|
234
|
-
|
|
236
|
+
const pc = b.fmt("lib{s}.pc", .{name});
|
|
237
|
+
if (@hasDecl(std.Build, "FindProgramOptions")) {
|
|
238
|
+
const write_file = b.addWriteFiles();
|
|
239
|
+
const pkgconfig = write_file.add(pc, content);
|
|
240
|
+
b.getInstallStep().dependOn(&b.addInstallFileWithDir(
|
|
241
|
+
pkgconfig,
|
|
242
|
+
.prefix,
|
|
243
|
+
b.fmt("share/pkgconfig/{s}", .{pc}),
|
|
244
|
+
).step);
|
|
245
|
+
} else {
|
|
246
|
+
const cwd = try std.process.currentPathAlloc(b.graph.io, b.allocator);
|
|
247
|
+
const file = try std.Io.Dir.path.relative(
|
|
248
|
+
b.allocator,
|
|
249
|
+
cwd,
|
|
250
|
+
&b.graph.environ_map,
|
|
251
|
+
cwd,
|
|
252
|
+
try b.cache_root.join(b.allocator, &.{pc}),
|
|
253
|
+
);
|
|
254
|
+
const pkgconfig = try std.Io.Dir.cwd().createFile(b.graph.io, file, .{});
|
|
255
|
+
defer pkgconfig.close(b.graph.io);
|
|
256
|
+
|
|
257
|
+
var writer = pkgconfig.writer(b.graph.io, &.{});
|
|
258
|
+
try writer.interface.writeAll(content);
|
|
259
|
+
|
|
260
|
+
b.installFile(file, b.fmt("share/pkgconfig/{s}", .{pc}));
|
|
261
|
+
}
|
|
235
262
|
}
|
|
236
263
|
|
|
237
264
|
const config: Config = .{
|
|
@@ -253,7 +280,6 @@ pub fn build(b: *std.Build) !void {
|
|
|
253
280
|
.chance = chance,
|
|
254
281
|
.calc = calc,
|
|
255
282
|
},
|
|
256
|
-
.module = pkmn,
|
|
257
283
|
.general = config,
|
|
258
284
|
.tool = .{
|
|
259
285
|
.tests = if (tests.build) tests else null,
|
|
@@ -285,8 +311,8 @@ pub fn build(b: *std.Build) !void {
|
|
|
285
311
|
if (dump) |t| b.step("dump", "Run protocol dump tool").dependOn(&t.step);
|
|
286
312
|
if (fuzz) |t| b.step("fuzz", "Run fuzz tester").dependOn(&t.step);
|
|
287
313
|
if (serde) |t| b.step("serde", "Run serialization/deserialization tool").dependOn(&t.step);
|
|
288
|
-
b.step("test", "Run all tests").dependOn(
|
|
289
|
-
b.step("tools", "Install tools").dependOn(
|
|
314
|
+
b.step("test", "Run all tests").dependOn(tests.step);
|
|
315
|
+
b.step("tools", "Install tools").dependOn(ToolsStep.create(b, &exes).step);
|
|
290
316
|
if (transitions) |t| {
|
|
291
317
|
b.step("transitions", "Visualize transitions algorithm search").dependOn(&t.step);
|
|
292
318
|
}
|
|
@@ -356,7 +382,12 @@ fn buildWasm(
|
|
|
356
382
|
exe.stack_size = wasm_stack_size;
|
|
357
383
|
exe.root_module.addOptions("build_options", options);
|
|
358
384
|
|
|
359
|
-
const opt =
|
|
385
|
+
const opt = if (@hasDecl(std.Build, "FindProgramOptions")) blk: {
|
|
386
|
+
if (exists(b, "./node_modules/.bin/wasm-opt") catch false) {
|
|
387
|
+
break :blk "./node_modules/.bin/wasm-opt";
|
|
388
|
+
}
|
|
389
|
+
break :blk b.findProgram(.{ .names = &.{"wasm-opt"} });
|
|
390
|
+
} else b.findProgram(&.{"wasm-opt"}, &.{"./node_modules/.bin"}) catch null;
|
|
360
391
|
if (optimize != .Debug and opt != null) {
|
|
361
392
|
const out = b.fmt("build/lib/{s}.wasm", .{name});
|
|
362
393
|
const sh = b.addSystemCommand(&.{ opt.?, "--enable-bulk-memory", "--enable-simd", "-O4" });
|
|
@@ -398,7 +429,7 @@ const Config = struct {
|
|
|
398
429
|
};
|
|
399
430
|
|
|
400
431
|
const TestStep = struct {
|
|
401
|
-
step: std.Build.Step,
|
|
432
|
+
step: *std.Build.Step,
|
|
402
433
|
build: bool,
|
|
403
434
|
|
|
404
435
|
pub fn create(b: *std.Build, options: *std.Build.Step.Options, config: Config) *TestStep {
|
|
@@ -407,8 +438,6 @@ const TestStep = struct {
|
|
|
407
438
|
b.option([]const u8, "test-filter", "Skip tests that do not match filter");
|
|
408
439
|
|
|
409
440
|
const self = b.allocator.create(TestStep) catch @panic("OOM");
|
|
410
|
-
const step = std.Build.Step.init(.{ .id = .custom, .name = "Run all tests", .owner = b });
|
|
411
|
-
self.* = .{ .step = step, .build = test_filter == null };
|
|
412
441
|
|
|
413
442
|
const path = b.path("src/lib/test.zig");
|
|
414
443
|
const tests = b.addTest(.{
|
|
@@ -428,7 +457,20 @@ const TestStep = struct {
|
|
|
428
457
|
if (coverage) |c| {
|
|
429
458
|
tests.setExecCmd(&.{ "kcov", "--include-pattern=src/lib", c, null });
|
|
430
459
|
}
|
|
431
|
-
|
|
460
|
+
|
|
461
|
+
if (@hasDecl(std.Build, "FindProgramOptions")) {
|
|
462
|
+
const run_step = b.addRunArtifact(tests);
|
|
463
|
+
self.* = .{ .step = &run_step.step, .build = test_filter == null };
|
|
464
|
+
} else {
|
|
465
|
+
const custom_step = b.allocator.create(std.Build.Step) catch @panic("OOM");
|
|
466
|
+
custom_step.* = std.Build.Step.init(.{
|
|
467
|
+
.id = .custom,
|
|
468
|
+
.name = "Run all tests",
|
|
469
|
+
.owner = b,
|
|
470
|
+
});
|
|
471
|
+
custom_step.dependOn(&b.addRunArtifact(tests).step);
|
|
472
|
+
self.* = .{ .step = custom_step, .build = test_filter == null };
|
|
473
|
+
}
|
|
432
474
|
|
|
433
475
|
return self;
|
|
434
476
|
}
|
|
@@ -436,7 +478,6 @@ const TestStep = struct {
|
|
|
436
478
|
|
|
437
479
|
const ToolConfig = struct {
|
|
438
480
|
options: Options,
|
|
439
|
-
module: ?*std.Build.Module,
|
|
440
481
|
general: Config,
|
|
441
482
|
tool: struct {
|
|
442
483
|
tests: ?*TestStep,
|
|
@@ -463,7 +504,7 @@ fn tool(b: *std.Build, path: []const u8, config: ToolConfig) !?*std.Build.Step.R
|
|
|
463
504
|
}),
|
|
464
505
|
});
|
|
465
506
|
|
|
466
|
-
const import =
|
|
507
|
+
const import = module(b, config.options, config.general.target, config.general.optimize);
|
|
467
508
|
exe.root_module.addImport("pkmn", import);
|
|
468
509
|
if (config.general.target.result.os.tag == .windows) {
|
|
469
510
|
exe.root_module.linkSystemLibrary("advapi32", .{});
|
|
@@ -474,7 +515,11 @@ fn tool(b: *std.Build, path: []const u8, config: ToolConfig) !?*std.Build.Step.R
|
|
|
474
515
|
|
|
475
516
|
const run = b.addRunArtifact(exe);
|
|
476
517
|
maybeStrip(b, exe, &run.step, config.general.strip, config.general.cmd);
|
|
477
|
-
if (
|
|
518
|
+
if (@hasDecl(std.Build, "FindProgramOptions")) {
|
|
519
|
+
run.addPassthruArgs();
|
|
520
|
+
} else {
|
|
521
|
+
if (b.args) |args| run.addArgs(args);
|
|
522
|
+
}
|
|
478
523
|
|
|
479
524
|
return run;
|
|
480
525
|
}
|
|
@@ -488,13 +533,33 @@ fn exists(b: *std.Build, path: []const u8) !bool {
|
|
|
488
533
|
}
|
|
489
534
|
|
|
490
535
|
const ToolsStep = struct {
|
|
491
|
-
step: std.Build.Step,
|
|
536
|
+
step: *std.Build.Step,
|
|
492
537
|
|
|
493
538
|
pub fn create(b: *std.Build, exes: *ArrayList(*std.Build.Step.Compile)) *ToolsStep {
|
|
494
539
|
const self = b.allocator.create(ToolsStep) catch @panic("OOM");
|
|
495
|
-
|
|
496
|
-
|
|
540
|
+
|
|
541
|
+
if (@hasDecl(std.Build, "FindProgramOptions")) {
|
|
542
|
+
const step = b.allocator.create(std.Build.Step.TopLevel) catch @panic("OOM");
|
|
543
|
+
step.* = .{
|
|
544
|
+
.step = std.Build.Step.init(.{
|
|
545
|
+
.tag = .top_level,
|
|
546
|
+
.name = "Install tools",
|
|
547
|
+
.owner = b,
|
|
548
|
+
}),
|
|
549
|
+
.description = "Install tools",
|
|
550
|
+
};
|
|
551
|
+
self.* = .{ .step = &step.step };
|
|
552
|
+
} else {
|
|
553
|
+
const step = b.allocator.create(std.Build.Step) catch @panic("OOM");
|
|
554
|
+
step.* = std.Build.Step.init(.{
|
|
555
|
+
.id = .custom,
|
|
556
|
+
.name = "Install tools",
|
|
557
|
+
.owner = b,
|
|
558
|
+
});
|
|
559
|
+
self.* = .{ .step = step };
|
|
560
|
+
}
|
|
497
561
|
for (exes.items) |t| self.step.dependOn(&b.addInstallArtifact(t, .{}).step);
|
|
562
|
+
|
|
498
563
|
return self;
|
|
499
564
|
}
|
|
500
565
|
};
|
|
@@ -516,3 +581,21 @@ pub fn exports(b: *std.Build, bytes: []const u8) ![][]const u8 {
|
|
|
516
581
|
|
|
517
582
|
return symbols.items;
|
|
518
583
|
}
|
|
584
|
+
|
|
585
|
+
fn maybeRanlib(
|
|
586
|
+
b: *std.Build,
|
|
587
|
+
artifact: *std.Build.Step.Compile,
|
|
588
|
+
install_step: *std.Build.Step,
|
|
589
|
+
) void {
|
|
590
|
+
if (builtin.os.tag != .macos) return;
|
|
591
|
+
if (artifact.linkage != .static) return;
|
|
592
|
+
|
|
593
|
+
const ranlib = if (comptime @hasDecl(std.Build, "FindProgramOptions"))
|
|
594
|
+
b.findProgram(.{ .names = &.{"ranlib"} }) orelse return
|
|
595
|
+
else
|
|
596
|
+
b.findProgram(&[_][]const u8{"ranlib"}, &[_][]const u8{}) catch return;
|
|
597
|
+
|
|
598
|
+
const sh = b.addSystemCommand(&[_][]const u8{ranlib});
|
|
599
|
+
sh.addFileArg(artifact.getEmittedBin());
|
|
600
|
+
install_step.dependOn(&sh.step);
|
|
601
|
+
}
|
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.a19bc07d",
|
|
4
4
|
"description": "A minimal, complete, Pokémon battle simulation engine optimized for performance",
|
|
5
5
|
"repository": "github:pkmn/engine",
|
|
6
6
|
"license": "MIT",
|
|
@@ -100,12 +100,12 @@
|
|
|
100
100
|
},
|
|
101
101
|
"dependencies": {
|
|
102
102
|
"@pkmn/data": "0.9.35",
|
|
103
|
-
"@pkmn/protocol": "^0.7.
|
|
103
|
+
"@pkmn/protocol": "^0.7.3"
|
|
104
104
|
},
|
|
105
105
|
"devDependencies": {
|
|
106
106
|
"@pkmn/dex-types": "0.9.35",
|
|
107
|
-
"@pkmn/eslint-config": "^9.
|
|
108
|
-
"@pkmn/img": "^0.3.
|
|
107
|
+
"@pkmn/eslint-config": "^9.28.0",
|
|
108
|
+
"@pkmn/img": "^0.3.4",
|
|
109
109
|
"@pkmn/sets": "^5.2.0",
|
|
110
110
|
"@pkmn/sim": "0.9.31",
|
|
111
111
|
"@pkmn/smogon": "0.5.21",
|
|
@@ -113,25 +113,25 @@
|
|
|
113
113
|
"@types/jest": "^30.0.0",
|
|
114
114
|
"@types/minimist": "^1.2.5",
|
|
115
115
|
"@types/mustache": "^4.2.6",
|
|
116
|
-
"@types/node": "^
|
|
117
|
-
"@types/react": "^19.2.
|
|
116
|
+
"@types/node": "^26.0.0",
|
|
117
|
+
"@types/react": "^19.2.17",
|
|
118
118
|
"@types/semver": "^7.7.1",
|
|
119
|
-
"@types/vscode": "^1.
|
|
120
|
-
"@vitest/coverage-v8": "^4.1.
|
|
121
|
-
"@vscode/vsce": "^3.9.
|
|
122
|
-
"binaryen": "^
|
|
123
|
-
"esbuild": "^0.28.
|
|
119
|
+
"@types/vscode": "^1.125.0",
|
|
120
|
+
"@vitest/coverage-v8": "^4.1.9",
|
|
121
|
+
"@vscode/vsce": "^3.9.2",
|
|
122
|
+
"binaryen": "^130.0.0",
|
|
123
|
+
"esbuild": "^0.28.1",
|
|
124
124
|
"eslint": "<10",
|
|
125
125
|
"html-minifier": "^4.0.0",
|
|
126
126
|
"json-stringify-pretty-compact": "3.0.0",
|
|
127
127
|
"minimist": "^1.2.8",
|
|
128
128
|
"mustache": "^4.2.0",
|
|
129
|
-
"semver": "^7.
|
|
129
|
+
"semver": "^7.8.5",
|
|
130
130
|
"source-map-support": "^0.5.21",
|
|
131
131
|
"table": "^6.9.0",
|
|
132
132
|
"trakr": "^0.2.0",
|
|
133
133
|
"typescript": "^6.0.3",
|
|
134
|
-
"vitest": "^4.1.
|
|
134
|
+
"vitest": "^4.1.9"
|
|
135
135
|
},
|
|
136
136
|
"scripts": {
|
|
137
137
|
"lint": "eslint --cache src",
|
package/src/lib/bindings/c.zig
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const pkmn = @import("../pkmn.zig");
|
|
2
2
|
const std = @import("std");
|
|
3
|
+
const util = @import("../common/util.zig");
|
|
3
4
|
|
|
4
5
|
const assert = std.debug.assert;
|
|
5
6
|
|
|
@@ -23,7 +24,7 @@ pub const MAX_LOGS = pkmn.MAX_LOGS;
|
|
|
23
24
|
pub const LOGS_SIZE = pkmn.LOGS_SIZE;
|
|
24
25
|
|
|
25
26
|
pub fn choice_init(choice: u8, data: u8) callconv(.c) u8 {
|
|
26
|
-
assert(choice <=
|
|
27
|
+
assert(choice <= util.EnumFieldsLen(pkmn.Choice.Type));
|
|
27
28
|
assert(data <= 6);
|
|
28
29
|
return @bitCast(pkmn.Choice{ .type = @enumFromInt(choice), .data = @intCast(data) });
|
|
29
30
|
}
|
|
@@ -190,8 +191,8 @@ pub fn gen(comptime num: comptime_int) type {
|
|
|
190
191
|
out: [*]u8,
|
|
191
192
|
n: usize,
|
|
192
193
|
) callconv(.c) u8 {
|
|
193
|
-
assert(player <=
|
|
194
|
-
assert(request <=
|
|
194
|
+
assert(player <= util.EnumFieldsLen(pkmn.Player));
|
|
195
|
+
assert(request <= util.EnumFieldsLen(pkmn.Choice.Type));
|
|
195
196
|
|
|
196
197
|
assert(!pkmn.options.showdown or n > 0);
|
|
197
198
|
return battle.choices(@enumFromInt(player), @enumFromInt(request), @ptrCast(out[0..n]));
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const pkmn = @import("../pkmn.zig");
|
|
2
2
|
const std = @import("std");
|
|
3
|
+
const util = @import("../common/util.zig");
|
|
3
4
|
|
|
4
5
|
const assert = std.debug.assert;
|
|
5
6
|
|
|
@@ -36,8 +37,8 @@ pub fn gen(comptime num: comptime_int) type {
|
|
|
36
37
|
request: u8,
|
|
37
38
|
buf: [*]u8,
|
|
38
39
|
) callconv(.c) u8 {
|
|
39
|
-
assert(player <=
|
|
40
|
-
assert(request <=
|
|
40
|
+
assert(player <= util.EnumFieldsLen(pkmn.Player));
|
|
41
|
+
assert(request <= util.EnumFieldsLen(pkmn.Choice.Type));
|
|
41
42
|
|
|
42
43
|
const n = CHOICES_SIZE;
|
|
43
44
|
return battle.choices(@enumFromInt(player), @enumFromInt(request), @ptrCast(buf[0..n]));
|
package/src/lib/common/debug.zig
CHANGED
|
@@ -24,9 +24,20 @@ pub fn print(value: anytype) void {
|
|
|
24
24
|
switch (@typeInfo(@TypeOf(value))) {
|
|
25
25
|
.@"struct" => |info| {
|
|
26
26
|
if (info.is_tuple) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
if (@hasField(@TypeOf(info), "fields")) {
|
|
28
|
+
inline for (info.fields, 0..) |f, i| {
|
|
29
|
+
inspect(fw, @field(value, f.name));
|
|
30
|
+
if (i < info.fields.len - 1) {
|
|
31
|
+
fw.interface.writeAll(" ") catch return;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
inline for (info.field_names, 0..) |name, i| {
|
|
36
|
+
inspect(fw, @field(value, name));
|
|
37
|
+
if (i < info.field_names.len - 1) {
|
|
38
|
+
fw.interface.writeAll(" ") catch return;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
30
41
|
}
|
|
31
42
|
} else {
|
|
32
43
|
inspect(fw, value);
|
|
@@ -6,21 +6,31 @@ const assert = std.debug.assert;
|
|
|
6
6
|
/// Optimized optional representation which stores the empty None value as a sentinel.
|
|
7
7
|
/// NOTE: ziglang/zig#104
|
|
8
8
|
pub fn Optional(comptime T: type) type {
|
|
9
|
-
const
|
|
10
|
-
.
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
const names = if (@hasDecl(std.meta, "fieldNames"))
|
|
10
|
+
std.meta.fieldNames(switch (@typeInfo(T)) {
|
|
11
|
+
.bool => enum { false, true },
|
|
12
|
+
else => T,
|
|
13
|
+
})
|
|
14
|
+
else blk: {
|
|
15
|
+
const fields = std.meta.fields(switch (@typeInfo(T)) {
|
|
16
|
+
.bool => enum { false, true },
|
|
17
|
+
else => T,
|
|
18
|
+
});
|
|
19
|
+
var ns: [fields.len][:0]const u8 = undefined;
|
|
20
|
+
inline for (fields, 0..) |f, i| ns[i] = f.name;
|
|
21
|
+
break :blk &ns;
|
|
22
|
+
};
|
|
13
23
|
|
|
14
|
-
const TagType = std.math.IntFittingRange(0,
|
|
15
|
-
var fieldNames: [
|
|
16
|
-
var fieldValues: [
|
|
24
|
+
const TagType = std.math.IntFittingRange(0, names.len);
|
|
25
|
+
var fieldNames: [names.len + 1][]const u8 = undefined;
|
|
26
|
+
var fieldValues: [names.len + 1]TagType = undefined;
|
|
17
27
|
|
|
18
28
|
fieldNames[0] = "None";
|
|
19
29
|
fieldValues[0] = 0;
|
|
20
30
|
|
|
21
|
-
inline for (
|
|
22
|
-
assert(!std.mem.eql(u8,
|
|
23
|
-
fieldNames[i] =
|
|
31
|
+
inline for (names, 1..) |name, i| {
|
|
32
|
+
assert(!std.mem.eql(u8, name, "None"));
|
|
33
|
+
fieldNames[i] = name;
|
|
24
34
|
fieldValues[i] = i;
|
|
25
35
|
}
|
|
26
36
|
|
package/src/lib/common/util.zig
CHANGED
|
@@ -4,7 +4,12 @@ const expect = std.testing.expect;
|
|
|
4
4
|
const expectEqual = std.testing.expectEqual;
|
|
5
5
|
|
|
6
6
|
pub fn PointerType(comptime P: type, comptime C: type) type {
|
|
7
|
-
|
|
7
|
+
const ptr_info = @typeInfo(P).pointer;
|
|
8
|
+
const is_const = if (@hasField(@TypeOf(ptr_info), "attrs"))
|
|
9
|
+
ptr_info.attrs.@"const"
|
|
10
|
+
else
|
|
11
|
+
ptr_info.is_const;
|
|
12
|
+
return if (is_const) *const C else *C;
|
|
8
13
|
}
|
|
9
14
|
|
|
10
15
|
test PointerType {
|
|
@@ -12,6 +17,21 @@ test PointerType {
|
|
|
12
17
|
try expectEqual(*const f64, PointerType(*const i32, f64));
|
|
13
18
|
}
|
|
14
19
|
|
|
20
|
+
pub fn EnumFieldsLen(comptime E: type) usize {
|
|
21
|
+
const info = @typeInfo(E).@"enum";
|
|
22
|
+
return if (@hasField(@TypeOf(info), "fields"))
|
|
23
|
+
info.fields.len
|
|
24
|
+
else
|
|
25
|
+
info.field_names.len;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
test EnumFieldsLen {
|
|
29
|
+
const E1 = enum { A, B, C };
|
|
30
|
+
const E2 = enum(u8) { A = 0, B = 10, C = 20, D = 30 };
|
|
31
|
+
try expectEqual(3, EnumFieldsLen(E1));
|
|
32
|
+
try expectEqual(4, EnumFieldsLen(E2));
|
|
33
|
+
}
|
|
34
|
+
|
|
15
35
|
pub fn isPointerTo(p: anytype, comptime P: type) bool {
|
|
16
36
|
const info = @typeInfo(@TypeOf(p));
|
|
17
37
|
return switch (info) {
|
package/src/lib/gen1/chance.zig
CHANGED
|
@@ -52,10 +52,23 @@ pub const Actions = extern struct {
|
|
|
52
52
|
/// same shape if they have the same fields set (though those fields need not necessarily be
|
|
53
53
|
/// set to the same value).
|
|
54
54
|
pub fn matches(a: Actions, b: Actions) bool {
|
|
55
|
-
inline for (@typeInfo(Actions).@"struct"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
inline for (if (@hasField(@TypeOf(@typeInfo(Actions).@"struct"), "fields"))
|
|
56
|
+
@typeInfo(Actions).@"struct".fields
|
|
57
|
+
else
|
|
58
|
+
@typeInfo(Actions).@"struct".field_names) |player|
|
|
59
|
+
{
|
|
60
|
+
const player_name = if (@hasField(@TypeOf(player), "name"))
|
|
61
|
+
player.name
|
|
62
|
+
else
|
|
63
|
+
player;
|
|
64
|
+
inline for (if (@hasField(@TypeOf(@typeInfo(Action).@"struct"), "fields"))
|
|
65
|
+
@typeInfo(Action).@"struct".fields
|
|
66
|
+
else
|
|
67
|
+
@typeInfo(Action).@"struct".field_names) |field|
|
|
68
|
+
{
|
|
69
|
+
const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
|
|
70
|
+
const a_val = @field(@field(a, player_name), field_name);
|
|
71
|
+
const b_val = @field(@field(b, player_name), field_name);
|
|
59
72
|
|
|
60
73
|
switch (@typeInfo(@TypeOf(a_val))) {
|
|
61
74
|
.@"enum" => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
|
|
@@ -216,31 +229,36 @@ pub const Action = packed struct(u64) {
|
|
|
216
229
|
pub fn fmt(self: Action, writer: *std.Io.Writer, shape: bool) !void {
|
|
217
230
|
try writer.writeByte('(');
|
|
218
231
|
var printed = false;
|
|
219
|
-
inline for (@typeInfo(Action).@"struct"
|
|
220
|
-
|
|
232
|
+
inline for (if (@hasField(@TypeOf(@typeInfo(Action).@"struct"), "fields"))
|
|
233
|
+
@typeInfo(Action).@"struct".fields
|
|
234
|
+
else
|
|
235
|
+
@typeInfo(Action).@"struct".field_names) |field|
|
|
236
|
+
{
|
|
237
|
+
const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
|
|
238
|
+
const val = @field(self, field_name);
|
|
221
239
|
switch (@typeInfo(@TypeOf(val))) {
|
|
222
240
|
.@"enum" => if (val != .None) {
|
|
223
241
|
if (printed) try writer.writeAll(", ");
|
|
224
242
|
if (shape) {
|
|
225
|
-
try writer.print("{s}:?", .{
|
|
243
|
+
try writer.print("{s}:?", .{field_name});
|
|
226
244
|
} else if (@TypeOf(val) == Optional(bool)) {
|
|
227
245
|
try writer.print("{s}{s}", .{
|
|
228
246
|
if (val == .false) "!" else "",
|
|
229
|
-
|
|
247
|
+
field_name,
|
|
230
248
|
});
|
|
231
249
|
} else if (@TypeOf(val) != Move and @TypeOf(val) != Optional(Player)) {
|
|
232
|
-
try writer.print("{s}{s}", .{ SYMBOLS[@intFromEnum(val) - 1],
|
|
250
|
+
try writer.print("{s}{s}", .{ SYMBOLS[@intFromEnum(val) - 1], field_name });
|
|
233
251
|
} else {
|
|
234
|
-
try writer.print("{s}:{s}", .{
|
|
252
|
+
try writer.print("{s}:{s}", .{ field_name, @tagName(val) });
|
|
235
253
|
}
|
|
236
254
|
printed = true;
|
|
237
255
|
},
|
|
238
256
|
.int => if (val != 0) {
|
|
239
257
|
if (printed) try writer.writeAll(", ");
|
|
240
258
|
if (shape) {
|
|
241
|
-
try writer.print("{s}:?", .{
|
|
259
|
+
try writer.print("{s}:?", .{field_name});
|
|
242
260
|
} else {
|
|
243
|
-
try writer.print("{s}:{d}", .{
|
|
261
|
+
try writer.print("{s}:{d}", .{ field_name, val });
|
|
244
262
|
}
|
|
245
263
|
printed = true;
|
|
246
264
|
},
|
|
@@ -293,20 +311,25 @@ pub const Duration = packed struct(u32) {
|
|
|
293
311
|
) !void {
|
|
294
312
|
try writer.writeByte('(');
|
|
295
313
|
var printed = false;
|
|
296
|
-
inline for (@typeInfo(Duration).@"struct"
|
|
297
|
-
|
|
314
|
+
inline for (if (@hasField(@TypeOf(@typeInfo(Duration).@"struct"), "fields"))
|
|
315
|
+
@typeInfo(Duration).@"struct".fields
|
|
316
|
+
else
|
|
317
|
+
@typeInfo(Duration).@"struct".field_names) |field|
|
|
318
|
+
{
|
|
319
|
+
const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
|
|
320
|
+
const val = @field(self, field_name);
|
|
298
321
|
switch (@typeInfo(@TypeOf(val))) {
|
|
299
322
|
.int => if (val != 0) {
|
|
300
323
|
if (printed) try writer.writeAll(", ");
|
|
301
|
-
if (comptime std.mem.eql(u8,
|
|
302
|
-
try writer.print("{s}:[", .{
|
|
324
|
+
if (comptime std.mem.eql(u8, field_name, "sleeps")) {
|
|
325
|
+
try writer.print("{s}:[", .{field_name});
|
|
303
326
|
for (0..6) |i| {
|
|
304
327
|
if (i != 0) try writer.writeByte(',');
|
|
305
328
|
try writer.print("{d}", .{Sleeps.get(val, i)});
|
|
306
329
|
}
|
|
307
330
|
try writer.writeAll("]");
|
|
308
331
|
} else {
|
|
309
|
-
try writer.print("{s}:{d}", .{
|
|
332
|
+
try writer.print("{s}:{d}", .{ field_name, val });
|
|
310
333
|
}
|
|
311
334
|
printed = true;
|
|
312
335
|
},
|
package/src/lib/gen1/helpers.zig
CHANGED
|
@@ -161,15 +161,20 @@ pub const Pokemon = struct {
|
|
|
161
161
|
var pokemon = data.Pokemon{};
|
|
162
162
|
pokemon.species = p.species;
|
|
163
163
|
const species = Species.get(p.species);
|
|
164
|
-
inline for (@typeInfo(@TypeOf(pokemon.stats)).@"struct"
|
|
165
|
-
|
|
164
|
+
inline for (if (@hasField(@TypeOf(@typeInfo(@TypeOf(pokemon.stats)).@"struct"), "fields"))
|
|
165
|
+
@typeInfo(@TypeOf(pokemon.stats)).@"struct".fields
|
|
166
|
+
else
|
|
167
|
+
@typeInfo(@TypeOf(pokemon.stats)).@"struct".field_names) |field|
|
|
168
|
+
{
|
|
169
|
+
const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
|
|
170
|
+
const hp = comptime std.mem.eql(u8, field_name, "hp");
|
|
166
171
|
const spc =
|
|
167
|
-
comptime std.mem.eql(u8,
|
|
168
|
-
@field(pokemon.stats,
|
|
169
|
-
|
|
170
|
-
@field(species.stats,
|
|
171
|
-
if (hp) p.dvs.hp() else if (spc) p.dvs.spc else @field(p.dvs,
|
|
172
|
-
@field(p.stats,
|
|
172
|
+
comptime std.mem.eql(u8, field_name, "spa") or std.mem.eql(u8, field_name, "spd");
|
|
173
|
+
@field(pokemon.stats, field_name) = Stats(u16).calc(
|
|
174
|
+
field_name,
|
|
175
|
+
@field(species.stats, field_name),
|
|
176
|
+
if (hp) p.dvs.hp() else if (spc) p.dvs.spc else @field(p.dvs, field_name),
|
|
177
|
+
@field(p.stats, field_name),
|
|
173
178
|
p.level,
|
|
174
179
|
);
|
|
175
180
|
}
|
|
@@ -197,14 +202,19 @@ pub const Pokemon = struct {
|
|
|
197
202
|
const lvl = if (rand.chance(u8, 1, 20)) rand.range(u8, 1, 99 + 1) else 100;
|
|
198
203
|
var stats: Stats(u16) = .{};
|
|
199
204
|
const dvs = DVs.random(rand);
|
|
200
|
-
inline for (@typeInfo(@TypeOf(stats)).@"struct"
|
|
201
|
-
@
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
+
inline for (if (@hasField(@TypeOf(@typeInfo(@TypeOf(stats)).@"struct"), "fields"))
|
|
206
|
+
@typeInfo(@TypeOf(stats)).@"struct".fields
|
|
207
|
+
else
|
|
208
|
+
@typeInfo(@TypeOf(stats)).@"struct".field_names) |field|
|
|
209
|
+
{
|
|
210
|
+
const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
|
|
211
|
+
@field(stats, field_name) = Stats(u16).calc(
|
|
212
|
+
field_name,
|
|
213
|
+
@field(species.stats, field_name),
|
|
214
|
+
if (comptime std.mem.eql(u8, field_name, "hp"))
|
|
205
215
|
dvs.hp()
|
|
206
216
|
else
|
|
207
|
-
@field(dvs,
|
|
217
|
+
@field(dvs, field_name),
|
|
208
218
|
if (rand.chance(u8, 1, 20)) rand.range(u16, 0, EXP + 1) else EXP,
|
|
209
219
|
lvl,
|
|
210
220
|
);
|
package/src/lib/gen2/chance.zig
CHANGED
|
@@ -53,10 +53,23 @@ pub const Actions = extern struct {
|
|
|
53
53
|
/// same shape if they have the same fields set (though those fields need not necessarily be
|
|
54
54
|
/// set to the same value).
|
|
55
55
|
pub fn matches(a: Actions, b: Actions) bool {
|
|
56
|
-
inline for (@typeInfo(Actions).@"struct"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
inline for (if (@hasField(@TypeOf(@typeInfo(Actions).@"struct"), "fields"))
|
|
57
|
+
@typeInfo(Actions).@"struct".fields
|
|
58
|
+
else
|
|
59
|
+
@typeInfo(Actions).@"struct".field_names) |player|
|
|
60
|
+
{
|
|
61
|
+
const player_name = if (@hasField(@TypeOf(player), "name"))
|
|
62
|
+
player.name
|
|
63
|
+
else
|
|
64
|
+
player;
|
|
65
|
+
inline for (if (@hasField(@TypeOf(@typeInfo(Action).@"struct"), "fields"))
|
|
66
|
+
@typeInfo(Action).@"struct".fields
|
|
67
|
+
else
|
|
68
|
+
@typeInfo(Action).@"struct".field_names) |field|
|
|
69
|
+
{
|
|
70
|
+
const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
|
|
71
|
+
const a_val = @field(@field(a, player_name), field_name);
|
|
72
|
+
const b_val = @field(@field(b, player_name), field_name);
|
|
60
73
|
|
|
61
74
|
switch (@typeInfo(@TypeOf(a_val))) {
|
|
62
75
|
.@"enum" => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
|
|
@@ -188,29 +201,34 @@ pub const Action = packed struct(u128) {
|
|
|
188
201
|
pub fn fmt(self: Action, writer: *std.Io.Writer, shape: bool) !void {
|
|
189
202
|
try writer.writeByte('(');
|
|
190
203
|
var printed = false;
|
|
191
|
-
inline for (@typeInfo(Action).@"struct"
|
|
192
|
-
|
|
204
|
+
inline for (if (@hasField(@TypeOf(@typeInfo(Action).@"struct"), "fields"))
|
|
205
|
+
@typeInfo(Action).@"struct".fields
|
|
206
|
+
else
|
|
207
|
+
@typeInfo(Action).@"struct".field_names) |field|
|
|
208
|
+
{
|
|
209
|
+
const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
|
|
210
|
+
const val = @field(self, field_name);
|
|
193
211
|
switch (@typeInfo(@TypeOf(val))) {
|
|
194
212
|
.@"enum" => if (val != .None) {
|
|
195
213
|
if (printed) try writer.writeAll(", ");
|
|
196
214
|
if (shape) {
|
|
197
|
-
try writer.print("{s}:?", .{
|
|
215
|
+
try writer.print("{s}:?", .{field_name});
|
|
198
216
|
} else if (@TypeOf(val) == Optional(bool)) {
|
|
199
217
|
try writer.print("{s}{s}", .{
|
|
200
218
|
if (val == .false) "!" else "",
|
|
201
|
-
|
|
219
|
+
field_name,
|
|
202
220
|
});
|
|
203
221
|
} else {
|
|
204
|
-
try writer.print("{s}:{s}", .{
|
|
222
|
+
try writer.print("{s}:{s}", .{ field_name, @tagName(val) });
|
|
205
223
|
}
|
|
206
224
|
printed = true;
|
|
207
225
|
},
|
|
208
226
|
.int => if (val != 0) {
|
|
209
227
|
if (printed) try writer.writeAll(", ");
|
|
210
228
|
if (shape) {
|
|
211
|
-
try writer.print("{s}:?", .{
|
|
229
|
+
try writer.print("{s}:?", .{field_name});
|
|
212
230
|
} else {
|
|
213
|
-
try writer.print("{s}:{d}", .{
|
|
231
|
+
try writer.print("{s}:{d}", .{ field_name, val });
|
|
214
232
|
}
|
|
215
233
|
printed = true;
|
|
216
234
|
},
|
package/src/lib/gen2/helpers.zig
CHANGED
|
@@ -140,15 +140,20 @@ pub const Pokemon = struct {
|
|
|
140
140
|
var pokemon = data.Pokemon{};
|
|
141
141
|
pokemon.species = p.species;
|
|
142
142
|
const species = Species.get(p.species);
|
|
143
|
-
inline for (@typeInfo(@TypeOf(pokemon.stats)).@"struct"
|
|
144
|
-
|
|
143
|
+
inline for (if (@hasField(@TypeOf(@typeInfo(@TypeOf(pokemon.stats)).@"struct"), "fields"))
|
|
144
|
+
@typeInfo(@TypeOf(pokemon.stats)).@"struct".fields
|
|
145
|
+
else
|
|
146
|
+
@typeInfo(@TypeOf(pokemon.stats)).@"struct".field_names) |field|
|
|
147
|
+
{
|
|
148
|
+
const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
|
|
149
|
+
const hp = comptime std.mem.eql(u8, field_name, "hp");
|
|
145
150
|
const spc =
|
|
146
|
-
comptime std.mem.eql(u8,
|
|
147
|
-
@field(pokemon.stats,
|
|
148
|
-
|
|
149
|
-
@field(species.stats,
|
|
150
|
-
if (hp) p.dvs.hp() else if (spc) p.dvs.spc else @field(p.dvs,
|
|
151
|
-
@field(p.stats,
|
|
151
|
+
comptime std.mem.eql(u8, field_name, "spa") or std.mem.eql(u8, field_name, "spd");
|
|
152
|
+
@field(pokemon.stats, field_name) = Stats(u16).calc(
|
|
153
|
+
field_name,
|
|
154
|
+
@field(species.stats, field_name),
|
|
155
|
+
if (hp) p.dvs.hp() else if (spc) p.dvs.spc else @field(p.dvs, field_name),
|
|
156
|
+
@field(p.stats, field_name),
|
|
152
157
|
p.level,
|
|
153
158
|
);
|
|
154
159
|
}
|
|
@@ -181,13 +186,18 @@ pub const Pokemon = struct {
|
|
|
181
186
|
|
|
182
187
|
var stats: Stats(u16) = .{};
|
|
183
188
|
const dvs = gen1.DVs.random(rand);
|
|
184
|
-
inline for (@typeInfo(@TypeOf(stats)).@"struct"
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
@
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
189
|
+
inline for (if (@hasField(@TypeOf(@typeInfo(@TypeOf(stats)).@"struct"), "fields"))
|
|
190
|
+
@typeInfo(@TypeOf(stats)).@"struct".fields
|
|
191
|
+
else
|
|
192
|
+
@typeInfo(@TypeOf(stats)).@"struct".field_names) |field|
|
|
193
|
+
{
|
|
194
|
+
const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
|
|
195
|
+
const hp = std.mem.eql(u8, field_name, "hp");
|
|
196
|
+
const spc = std.mem.eql(u8, field_name, "spa") or std.mem.eql(u8, field_name, "spd");
|
|
197
|
+
@field(stats, field_name) = Stats(u16).calc(
|
|
198
|
+
field_name,
|
|
199
|
+
@field(species.stats, field_name),
|
|
200
|
+
if (hp) dvs.hp() else if (spc) dvs.spc else @field(dvs, field_name),
|
|
191
201
|
if (rand.chance(u8, 1, 20)) rand.range(u8, 0, 255 + 1) else 255,
|
|
192
202
|
lvl,
|
|
193
203
|
);
|