@pkmn/engine 0.1.0-dev.21a51fe4 → 0.1.0-dev.2ad317f2
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 +9 -1
- package/build.zig +25 -32
- package/package.json +1 -1
- package/src/bin/install-pkmn-engine +1 -1
- package/src/lib/gen1/README.md +83 -3
- package/src/lib/gen1/chance.zig +3 -3
- package/src/lib/gen1/helpers.zig +228 -75
- package/src/lib/gen1/mechanics.zig +4 -4
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
|
|
@@ -50,7 +58,7 @@ $ zig build --prefix /usr/local -Doptimize=ReleaseFast
|
|
|
50
58
|
|
|
51
59
|
The Zig website has [installation instructions](https://ziglang.org/learn/getting-started/) which
|
|
52
60
|
walk through how to install Zig on each platform - the engine code should work on Zig v0.11.0 dev
|
|
53
|
-
build
|
|
61
|
+
build 4282 or greater, though tracks Zig's master branch so this may change in the future if
|
|
54
62
|
breaking language changes are introduced:
|
|
55
63
|
|
|
56
64
|
`libpkmn` can be built with `-Dshowdown` to instead produce the Pokémon Showdown compatible
|
package/build.zig
CHANGED
|
@@ -35,6 +35,7 @@ pub fn build(b: *std.Build) !void {
|
|
|
35
35
|
const strip = b.option(bool, "strip", "Strip debugging symbols from binary") orelse false;
|
|
36
36
|
const pic = b.option(bool, "pic", "Force position independent code") orelse false;
|
|
37
37
|
const emit_asm = b.option(bool, "emit-asm", "Output .s (assembly code)") orelse false;
|
|
38
|
+
const emit_ll = b.option(bool, "emit-ll", "Output .ll (LLVM IR)") orelse false;
|
|
38
39
|
|
|
39
40
|
const cmd = b.findProgram(&.{"strip"}, &.{}) catch null;
|
|
40
41
|
|
|
@@ -145,7 +146,8 @@ pub fn build(b: *std.Build) !void {
|
|
|
145
146
|
lib.bundle_compiler_rt = true;
|
|
146
147
|
maybeStrip(b, lib, b.getInstallStep(), strip, cmd);
|
|
147
148
|
if (pic) lib.force_pic = pic;
|
|
148
|
-
if (emit_asm) lib.emit_asm = .
|
|
149
|
+
if (emit_asm) lib.emit_asm = .{ .emit_to = b.fmt("{s}.S", .{name}) };
|
|
150
|
+
if (emit_ll) lib.emit_llvm_ir = .{ .emit_to = b.fmt("{s}.ll", .{name}) };
|
|
149
151
|
b.installArtifact(lib);
|
|
150
152
|
c = true;
|
|
151
153
|
}
|
|
@@ -217,19 +219,15 @@ pub fn build(b: *std.Build) !void {
|
|
|
217
219
|
fuzz_config.tool.name = "fuzz";
|
|
218
220
|
const fuzz = tool(b, "src/test/fuzz.zig", fuzz_config);
|
|
219
221
|
|
|
222
|
+
const analyze = tool(b, "src/tools/analyze.zig", tools);
|
|
220
223
|
const dump = tool(b, "src/tools/dump.zig", tools);
|
|
221
224
|
const serde = tool(b, "src/tools/serde.zig", tools);
|
|
222
225
|
const transitions = tool(b, "src/tools/transitions.zig", tools);
|
|
223
226
|
|
|
224
|
-
|
|
225
|
-
b.addExecutable(.{ .name = "lint", .root_source_file = .{ .path = "src/tools/lint.zig" } });
|
|
226
|
-
if (tests.build) tests.step.dependOn(&lint_exe.step);
|
|
227
|
-
const lint = b.addRunArtifact(lint_exe);
|
|
228
|
-
|
|
227
|
+
b.step("analyze", "Run LLVM analysis tool").dependOn(&analyze.step);
|
|
229
228
|
b.step("benchmark", "Run benchmark code").dependOn(&benchmark.step);
|
|
230
229
|
b.step("dump", "Run protocol dump tool").dependOn(&dump.step);
|
|
231
230
|
b.step("fuzz", "Run fuzz tester").dependOn(&fuzz.step);
|
|
232
|
-
b.step("lint", "Lint source files").dependOn(&lint.step);
|
|
233
231
|
b.step("serde", "Run serialization/deserialization tool").dependOn(&serde.step);
|
|
234
232
|
b.step("test", "Run all tests").dependOn(&tests.step);
|
|
235
233
|
b.step("tools", "Install tools").dependOn(&ToolsStep.create(b, &exes).step);
|
|
@@ -263,12 +261,6 @@ const Config = struct {
|
|
|
263
261
|
cmd: ?[]const u8,
|
|
264
262
|
};
|
|
265
263
|
|
|
266
|
-
const TESTS = .{
|
|
267
|
-
"src/lib/common/test.zig",
|
|
268
|
-
"src/lib/gen1/test.zig",
|
|
269
|
-
"src/lib/gen2/test.zig",
|
|
270
|
-
};
|
|
271
|
-
|
|
272
264
|
const TestStep = struct {
|
|
273
265
|
step: std.Build.Step,
|
|
274
266
|
build: bool,
|
|
@@ -283,26 +275,27 @@ const TestStep = struct {
|
|
|
283
275
|
const step = std.Build.Step.init(.{ .id = .custom, .name = "Run all tests", .owner = b });
|
|
284
276
|
self.* = TestStep{ .step = step, .build = test_filter == null };
|
|
285
277
|
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
self.step.dependOn(&b.addRunArtifact(tests).step);
|
|
278
|
+
const path = test_file orelse "src/lib/test.zig";
|
|
279
|
+
const tests = b.addTest(.{
|
|
280
|
+
.name = b.fmt("{s}-{s}", .{
|
|
281
|
+
std.fs.path.basename(std.fs.path.dirname(path).?),
|
|
282
|
+
std.fs.path.stem(std.fs.path.basename(path)),
|
|
283
|
+
}),
|
|
284
|
+
.root_source_file = .{ .path = path },
|
|
285
|
+
.optimize = config.optimize,
|
|
286
|
+
.target = config.target,
|
|
287
|
+
.filter = test_filter,
|
|
288
|
+
});
|
|
289
|
+
tests.setMainPkgPath("./");
|
|
290
|
+
tests.addOptions("build_options", options);
|
|
291
|
+
tests.single_threaded = true;
|
|
292
|
+
maybeStrip(b, tests, &tests.step, config.strip, config.cmd);
|
|
293
|
+
if (config.pic) tests.force_pic = config.pic;
|
|
294
|
+
if (coverage) |c| {
|
|
295
|
+
tests.setExecCmd(&.{ "kcov", "--include-pattern=src/lib", c, null });
|
|
305
296
|
}
|
|
297
|
+
self.step.dependOn(&b.addRunArtifact(tests).step);
|
|
298
|
+
|
|
306
299
|
return self;
|
|
307
300
|
}
|
|
308
301
|
};
|
package/package.json
CHANGED
|
@@ -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, dev:
|
|
13
|
+
const ZIG_VERSION = {major: 0, minor: 11, patch: 0, dev: 4282};
|
|
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';
|
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/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
|
}
|
package/src/lib/gen1/helpers.zig
CHANGED
|
@@ -350,47 +350,65 @@ pub const Rolls = struct {
|
|
|
350
350
|
const U3_NONE = [_]u3{0};
|
|
351
351
|
const U3_FORCED = [_]u3{1};
|
|
352
352
|
|
|
353
|
-
/// Returns a slice with a range of values for sleep given the `action`
|
|
353
|
+
/// Returns a slice with a range of values for sleep given the `action` and `origin` states.
|
|
354
354
|
pub inline fn sleep(action: Action, origin: Action) []const u3 {
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
355
|
+
const a = @field(action.durations, "sleep");
|
|
356
|
+
const o = @field(origin.durations, "sleep");
|
|
357
|
+
if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
|
|
358
|
+
assert(a == 0 or a == o + 1);
|
|
359
|
+
return if (o >= 7) &U3_NONE else &[_]u3{ 0, o + 1 };
|
|
358
360
|
}
|
|
359
361
|
|
|
360
362
|
const DISABLE_NONE = [_]u4{0};
|
|
361
363
|
|
|
362
|
-
/// Returns a slice with a range of values for disable given the `action`
|
|
364
|
+
/// Returns a slice with a range of values for disable given the `action` and `origin` states
|
|
363
365
|
/// and the state of the `parent` (the player's Pokémon's remaining sleep turns).
|
|
364
366
|
pub inline fn disable(action: Action, origin: Action, parent: u4) []const u4 {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
367
|
+
if (parent > 0) return &DISABLE_NONE;
|
|
368
|
+
const a = @field(action.durations, "disable");
|
|
369
|
+
const o = @field(origin.durations, "disable");
|
|
370
|
+
if (o == a) return if (o == 0) &DISABLE_NONE else &[_]u4{o};
|
|
371
|
+
assert(a == 0 or a == o + 1);
|
|
372
|
+
return if (o >= 8) &DISABLE_NONE else &[_]u4{ 0, o + 1 };
|
|
368
373
|
}
|
|
369
374
|
|
|
370
|
-
/// Returns a slice with a range of values for confusion given the `action`
|
|
375
|
+
/// Returns a slice with a range of values for confusion given the `action` and `origin` states
|
|
371
376
|
/// and the state of the `parent` (the player's remaining sleep turns).
|
|
372
377
|
pub inline fn confusion(action: Action, origin: Action, parent: u4) []const u3 {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
return if (
|
|
378
|
+
if (parent > 0) return &U3_NONE;
|
|
379
|
+
const a = @field(action.durations, "confusion");
|
|
380
|
+
const o = @field(origin.durations, "confusion");
|
|
381
|
+
if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
|
|
382
|
+
assert(a == 0 or a == o + 1);
|
|
383
|
+
return if (o >= 5) &U3_NONE else if (o < 2)
|
|
384
|
+
if (a == 0) &U3_NONE else &U3_FORCED
|
|
385
|
+
else
|
|
386
|
+
&[_]u3{ 0, o + 1 };
|
|
377
387
|
}
|
|
378
388
|
|
|
379
|
-
/// Returns a slice with a range of values for attacking given the `action`
|
|
389
|
+
/// Returns a slice with a range of values for attacking given the `action` and `origin` states
|
|
380
390
|
/// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
|
|
381
391
|
pub inline fn attacking(action: Action, origin: Action, parent: Optional(bool)) []const u3 {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
return if (
|
|
392
|
+
if (parent == .true) return &U3_NONE;
|
|
393
|
+
const a = @field(action.durations, "attacking");
|
|
394
|
+
const o = @field(origin.durations, "attacking");
|
|
395
|
+
if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
|
|
396
|
+
assert(a == 0 or a == o + 1);
|
|
397
|
+
return if (o >= 3) &U3_NONE else if (o < 2)
|
|
398
|
+
if (a == 0) &U3_NONE else &U3_FORCED
|
|
399
|
+
else
|
|
400
|
+
&[_]u3{ 0, o + 1 };
|
|
386
401
|
}
|
|
387
402
|
|
|
388
|
-
/// Returns a slice with a range of values for binding given the `action`
|
|
403
|
+
/// Returns a slice with a range of values for binding given the `action` and `origin` states
|
|
389
404
|
/// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
|
|
390
405
|
pub inline fn binding(action: Action, origin: Action, parent: Optional(bool)) []const u3 {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
406
|
+
if (parent == .true) return &U3_NONE;
|
|
407
|
+
const a = @field(action.durations, "binding");
|
|
408
|
+
const o = @field(origin.durations, "binding");
|
|
409
|
+
if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
|
|
410
|
+
assert(a == 0 or a == o + 1);
|
|
411
|
+
return if (o >= 4) &U3_NONE else &[_]u3{ 0, o + 1 };
|
|
394
412
|
}
|
|
395
413
|
|
|
396
414
|
const SLOT_NONE = [_]u4{0};
|
|
@@ -535,72 +553,207 @@ test "Rolls.duration" {
|
|
|
535
553
|
try expectEqualSlices(u1, &.{0}, Rolls.duration(actions.p2, .false));
|
|
536
554
|
}
|
|
537
555
|
|
|
538
|
-
const FIXME: Action = .{};
|
|
539
|
-
|
|
540
556
|
test "Rolls.sleep" {
|
|
541
|
-
try expectEqualSlices(u3, &.{0}, Rolls.sleep(
|
|
542
|
-
|
|
543
|
-
|
|
557
|
+
try expectEqualSlices(u3, &.{0}, Rolls.sleep(
|
|
558
|
+
.{ .durations = .{ .sleep = 0 } },
|
|
559
|
+
.{ .durations = .{ .sleep = 0 } },
|
|
560
|
+
));
|
|
561
|
+
try expectEqualSlices(u3, &.{ 0, 1 }, Rolls.sleep(
|
|
562
|
+
.{ .durations = .{ .sleep = 1 } },
|
|
563
|
+
.{ .durations = .{ .sleep = 0 } },
|
|
564
|
+
));
|
|
565
|
+
try expectEqualSlices(u3, &.{ 0, 2 }, Rolls.sleep(
|
|
566
|
+
.{ .durations = .{ .sleep = 0 } },
|
|
567
|
+
.{ .durations = .{ .sleep = 1 } },
|
|
568
|
+
));
|
|
569
|
+
try expectEqualSlices(u3, &.{1}, Rolls.sleep(
|
|
570
|
+
.{ .durations = .{ .sleep = 1 } },
|
|
571
|
+
.{ .durations = .{ .sleep = 1 } },
|
|
572
|
+
));
|
|
573
|
+
try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.sleep(
|
|
574
|
+
.{ .durations = .{ .sleep = 0 } },
|
|
575
|
+
.{ .durations = .{ .sleep = 2 } },
|
|
576
|
+
));
|
|
577
|
+
try expectEqualSlices(u3, &.{ 0, 7 }, Rolls.sleep(
|
|
578
|
+
.{ .durations = .{ .sleep = 7 } },
|
|
579
|
+
.{ .durations = .{ .sleep = 6 } },
|
|
580
|
+
));
|
|
581
|
+
try expectEqualSlices(u3, &.{0}, Rolls.sleep(
|
|
582
|
+
.{ .durations = .{ .sleep = 0 } },
|
|
583
|
+
.{ .durations = .{ .sleep = 7 } },
|
|
584
|
+
));
|
|
544
585
|
}
|
|
545
586
|
|
|
546
587
|
test "Rolls.disable" {
|
|
547
|
-
try expectEqualSlices(u4, &.{0}, Rolls.disable(
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
588
|
+
try expectEqualSlices(u4, &.{0}, Rolls.disable(
|
|
589
|
+
.{ .durations = .{ .disable = 0 } },
|
|
590
|
+
.{ .durations = .{ .disable = 0 } },
|
|
591
|
+
0,
|
|
592
|
+
));
|
|
593
|
+
try expectEqualSlices(u4, &.{ 0, 1 }, Rolls.disable(
|
|
594
|
+
.{ .durations = .{ .disable = 1 } },
|
|
595
|
+
.{ .durations = .{ .disable = 0 } },
|
|
596
|
+
0,
|
|
597
|
+
));
|
|
598
|
+
try expectEqualSlices(u4, &.{ 0, 2 }, Rolls.disable(
|
|
599
|
+
.{ .durations = .{ .disable = 0 } },
|
|
600
|
+
.{ .durations = .{ .disable = 1 } },
|
|
601
|
+
0,
|
|
602
|
+
));
|
|
603
|
+
try expectEqualSlices(u4, &.{1}, Rolls.disable(
|
|
604
|
+
.{ .durations = .{ .disable = 1 } },
|
|
605
|
+
.{ .durations = .{ .disable = 1 } },
|
|
606
|
+
0,
|
|
607
|
+
));
|
|
608
|
+
try expectEqualSlices(u4, &.{ 0, 3 }, Rolls.disable(
|
|
609
|
+
.{ .durations = .{ .disable = 0 } },
|
|
610
|
+
.{ .durations = .{ .disable = 2 } },
|
|
611
|
+
0,
|
|
612
|
+
));
|
|
613
|
+
try expectEqualSlices(u4, &.{0}, Rolls.disable(
|
|
614
|
+
.{ .durations = .{ .disable = 0 } },
|
|
615
|
+
.{ .durations = .{ .disable = 2 } },
|
|
616
|
+
1,
|
|
617
|
+
));
|
|
618
|
+
try expectEqualSlices(u4, &.{ 0, 8 }, Rolls.disable(
|
|
619
|
+
.{ .durations = .{ .disable = 8 } },
|
|
620
|
+
.{ .durations = .{ .disable = 7 } },
|
|
621
|
+
0,
|
|
622
|
+
));
|
|
623
|
+
try expectEqualSlices(u4, &.{0}, Rolls.disable(
|
|
624
|
+
.{ .durations = .{ .disable = 0 } },
|
|
625
|
+
.{ .durations = .{ .disable = 8 } },
|
|
626
|
+
0,
|
|
627
|
+
));
|
|
553
628
|
}
|
|
554
629
|
|
|
555
630
|
test "Rolls.confusion" {
|
|
556
|
-
try expectEqualSlices(u3, &.{0}, Rolls.confusion(
|
|
557
|
-
.durations = .{ .confusion = 0 },
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
.durations = .{ .confusion =
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
631
|
+
try expectEqualSlices(u3, &.{0}, Rolls.confusion(
|
|
632
|
+
.{ .durations = .{ .confusion = 0 } },
|
|
633
|
+
.{ .durations = .{ .confusion = 0 } },
|
|
634
|
+
0,
|
|
635
|
+
));
|
|
636
|
+
try expectEqualSlices(u3, &.{1}, Rolls.confusion(
|
|
637
|
+
.{ .durations = .{ .confusion = 1 } },
|
|
638
|
+
.{ .durations = .{ .confusion = 0 } },
|
|
639
|
+
0,
|
|
640
|
+
));
|
|
641
|
+
try expectEqualSlices(u3, &.{0}, Rolls.confusion(
|
|
642
|
+
.{ .durations = .{ .confusion = 0 } },
|
|
643
|
+
.{ .durations = .{ .confusion = 1 } },
|
|
644
|
+
0,
|
|
645
|
+
));
|
|
646
|
+
try expectEqualSlices(u3, &.{1}, Rolls.confusion(
|
|
647
|
+
.{ .durations = .{ .confusion = 1 } },
|
|
648
|
+
.{ .durations = .{ .confusion = 1 } },
|
|
649
|
+
0,
|
|
650
|
+
));
|
|
651
|
+
try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.confusion(
|
|
652
|
+
.{ .durations = .{ .confusion = 0 } },
|
|
653
|
+
.{ .durations = .{ .confusion = 2 } },
|
|
654
|
+
0,
|
|
655
|
+
));
|
|
656
|
+
try expectEqualSlices(u3, &.{0}, Rolls.confusion(
|
|
657
|
+
.{ .durations = .{ .confusion = 0 } },
|
|
658
|
+
.{ .durations = .{ .confusion = 2 } },
|
|
659
|
+
1,
|
|
660
|
+
));
|
|
661
|
+
try expectEqualSlices(u3, &.{ 0, 5 }, Rolls.confusion(
|
|
662
|
+
.{ .durations = .{ .confusion = 5 } },
|
|
663
|
+
.{ .durations = .{ .confusion = 4 } },
|
|
664
|
+
0,
|
|
665
|
+
));
|
|
666
|
+
try expectEqualSlices(u3, &.{0}, Rolls.confusion(
|
|
667
|
+
.{ .durations = .{ .confusion = 0 } },
|
|
668
|
+
.{ .durations = .{ .confusion = 5 } },
|
|
669
|
+
0,
|
|
670
|
+
));
|
|
571
671
|
}
|
|
572
672
|
|
|
573
673
|
test "Rolls.attacking" {
|
|
574
|
-
try expectEqualSlices(u3, &.{0}, Rolls.attacking(
|
|
575
|
-
.durations = .{ .attacking = 0 },
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
.durations = .{ .attacking =
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
.
|
|
588
|
-
|
|
674
|
+
try expectEqualSlices(u3, &.{0}, Rolls.attacking(
|
|
675
|
+
.{ .durations = .{ .attacking = 0 } },
|
|
676
|
+
.{ .durations = .{ .attacking = 0 } },
|
|
677
|
+
.false,
|
|
678
|
+
));
|
|
679
|
+
try expectEqualSlices(u3, &.{1}, Rolls.attacking(
|
|
680
|
+
.{ .durations = .{ .attacking = 1 } },
|
|
681
|
+
.{ .durations = .{ .attacking = 0 } },
|
|
682
|
+
.false,
|
|
683
|
+
));
|
|
684
|
+
try expectEqualSlices(u3, &.{0}, Rolls.attacking(
|
|
685
|
+
.{ .durations = .{ .attacking = 0 } },
|
|
686
|
+
.{ .durations = .{ .attacking = 1 } },
|
|
687
|
+
.false,
|
|
688
|
+
));
|
|
689
|
+
try expectEqualSlices(u3, &.{1}, Rolls.attacking(
|
|
690
|
+
.{ .durations = .{ .attacking = 1 } },
|
|
691
|
+
.{ .durations = .{ .attacking = 1 } },
|
|
692
|
+
.false,
|
|
693
|
+
));
|
|
694
|
+
try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.attacking(
|
|
695
|
+
.{ .durations = .{ .attacking = 0 } },
|
|
696
|
+
.{ .durations = .{ .attacking = 2 } },
|
|
697
|
+
.false,
|
|
698
|
+
));
|
|
699
|
+
try expectEqualSlices(u3, &.{0}, Rolls.attacking(
|
|
700
|
+
.{ .durations = .{ .attacking = 0 } },
|
|
701
|
+
.{ .durations = .{ .attacking = 2 } },
|
|
702
|
+
.true,
|
|
703
|
+
));
|
|
704
|
+
try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.attacking(
|
|
705
|
+
.{ .durations = .{ .attacking = 3 } },
|
|
706
|
+
.{ .durations = .{ .attacking = 2 } },
|
|
707
|
+
.false,
|
|
708
|
+
));
|
|
709
|
+
try expectEqualSlices(u3, &.{0}, Rolls.attacking(
|
|
710
|
+
.{ .durations = .{ .attacking = 0 } },
|
|
711
|
+
.{ .durations = .{ .attacking = 3 } },
|
|
712
|
+
.false,
|
|
713
|
+
));
|
|
589
714
|
}
|
|
590
715
|
|
|
591
716
|
test "Rolls.binding" {
|
|
592
|
-
try expectEqualSlices(u3, &.{0}, Rolls.binding(
|
|
593
|
-
.durations = .{ .binding = 0 },
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
.durations = .{ .binding =
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
717
|
+
try expectEqualSlices(u3, &.{0}, Rolls.binding(
|
|
718
|
+
.{ .durations = .{ .binding = 0 } },
|
|
719
|
+
.{ .durations = .{ .binding = 0 } },
|
|
720
|
+
.false,
|
|
721
|
+
));
|
|
722
|
+
try expectEqualSlices(u3, &.{ 0, 1 }, Rolls.binding(
|
|
723
|
+
.{ .durations = .{ .binding = 1 } },
|
|
724
|
+
.{ .durations = .{ .binding = 0 } },
|
|
725
|
+
.false,
|
|
726
|
+
));
|
|
727
|
+
try expectEqualSlices(u3, &.{ 0, 2 }, Rolls.binding(
|
|
728
|
+
.{ .durations = .{ .binding = 0 } },
|
|
729
|
+
.{ .durations = .{ .binding = 1 } },
|
|
730
|
+
.false,
|
|
731
|
+
));
|
|
732
|
+
try expectEqualSlices(u3, &.{1}, Rolls.binding(
|
|
733
|
+
.{ .durations = .{ .binding = 1 } },
|
|
734
|
+
.{ .durations = .{ .binding = 1 } },
|
|
735
|
+
.false,
|
|
736
|
+
));
|
|
737
|
+
try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.binding(
|
|
738
|
+
.{ .durations = .{ .binding = 0 } },
|
|
739
|
+
.{ .durations = .{ .binding = 2 } },
|
|
740
|
+
.false,
|
|
741
|
+
));
|
|
742
|
+
try expectEqualSlices(u3, &.{0}, Rolls.binding(
|
|
743
|
+
.{ .durations = .{ .binding = 0 } },
|
|
744
|
+
.{ .durations = .{ .binding = 2 } },
|
|
745
|
+
.true,
|
|
746
|
+
));
|
|
747
|
+
try expectEqualSlices(u3, &.{ 0, 4 }, Rolls.binding(
|
|
748
|
+
.{ .durations = .{ .binding = 4 } },
|
|
749
|
+
.{ .durations = .{ .binding = 3 } },
|
|
750
|
+
.false,
|
|
751
|
+
));
|
|
752
|
+
try expectEqualSlices(u3, &.{0}, Rolls.binding(
|
|
753
|
+
.{ .durations = .{ .binding = 0 } },
|
|
754
|
+
.{ .durations = .{ .binding = 4 } },
|
|
755
|
+
.false,
|
|
756
|
+
));
|
|
604
757
|
}
|
|
605
758
|
|
|
606
759
|
test "Rolls.moveSlot" {
|
|
@@ -1332,7 +1332,7 @@ fn mirrorMove(
|
|
|
1332
1332
|
auto: bool,
|
|
1333
1333
|
residual: *bool,
|
|
1334
1334
|
options: anytype,
|
|
1335
|
-
) !?Result {
|
|
1335
|
+
) anyerror!?Result {
|
|
1336
1336
|
var side = battle.side(player);
|
|
1337
1337
|
const foe = battle.foe(player);
|
|
1338
1338
|
|
|
@@ -1359,7 +1359,7 @@ fn metronome(
|
|
|
1359
1359
|
auto: bool,
|
|
1360
1360
|
residual: *bool,
|
|
1361
1361
|
options: anytype,
|
|
1362
|
-
) !?Result {
|
|
1362
|
+
) anyerror!?Result {
|
|
1363
1363
|
var side = battle.side(player);
|
|
1364
1364
|
|
|
1365
1365
|
side.last_selected_move = try Rolls.metronome(battle, player, options);
|
|
@@ -2680,7 +2680,7 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
2680
2680
|
}
|
|
2681
2681
|
}
|
|
2682
2682
|
|
|
2683
|
-
inline fn isForced(active:
|
|
2683
|
+
inline fn isForced(active: anytype) bool {
|
|
2684
2684
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
2685
2685
|
active.volatiles.Thrashing or active.volatiles.Charging;
|
|
2686
2686
|
}
|
|
@@ -3072,7 +3072,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3072
3072
|
const side = battle.side(player);
|
|
3073
3073
|
const foe = battle.foe(player);
|
|
3074
3074
|
|
|
3075
|
-
var active = side.active;
|
|
3075
|
+
var active = &side.active;
|
|
3076
3076
|
const stored = side.stored();
|
|
3077
3077
|
|
|
3078
3078
|
// While players are not given any input options on the cartridge in these cases,
|