@pkmn/engine 0.1.0-dev.9a0c07c3 → 0.1.0-dev.a324c50f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -29
- package/build/pkg/addon.d.ts +7 -0
- package/build/pkg/addon.js +112 -0
- package/build/pkg/addon.js.map +1 -0
- package/build/pkg/data/layout.json +25 -3
- package/build/pkg/gen1.d.ts +5 -4
- package/build/pkg/gen1.js +47 -31
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/index.d.ts +15 -12
- package/build/pkg/index.js.map +1 -1
- package/build/pkg/protocol.d.ts +4 -2
- package/build/pkg/protocol.js +4 -2
- package/build/pkg/protocol.js.map +1 -1
- package/build/test/display.js +31 -13
- package/build/test/display.js.map +1 -1
- package/build.zig +49 -39
- package/package.json +18 -14
- package/src/bin/install-pkmn-engine +11 -6
- package/src/bin/pkmn-debug +34 -7
- package/src/lib/binding/node.zig +46 -141
- package/src/lib/binding/wasm.zig +6 -4
- package/src/lib/common/data.zig +11 -11
- package/src/lib/common/debug.zig +21 -11
- package/src/lib/common/options.zig +12 -3
- package/src/lib/common/protocol.zig +239 -178
- package/src/lib/common/rng.zig +10 -10
- package/src/lib/gen1/README.md +111 -81
- package/src/lib/gen1/data/moves.zig +10 -10
- package/src/lib/gen1/data/species.zig +2 -2
- package/src/lib/gen1/data/types.zig +3 -3
- package/src/lib/gen1/data.zig +64 -43
- package/src/lib/gen1/helpers.zig +439 -32
- package/src/lib/gen1/mechanics.zig +987 -612
- package/src/lib/gen2/data/items.zig +3 -3
- package/src/lib/gen2/data/moves.zig +1 -1
- package/src/lib/gen2/data/species.zig +1 -1
- package/src/lib/gen2/data/types.zig +3 -3
- package/src/lib/gen2/data.zig +37 -26
- package/src/lib/gen2/helpers.zig +9 -11
- package/src/lib/gen2/mechanics.zig +5 -4
- package/src/lib/pkmn.zig +68 -9
package/src/lib/gen1/data.zig
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
const std = @import("std");
|
|
2
2
|
const builtin = @import("builtin");
|
|
3
3
|
|
|
4
|
+
const pkmn = @import("../pkmn.zig");
|
|
5
|
+
|
|
4
6
|
const data = @import("../common/data.zig");
|
|
5
|
-
const options = @import("../common/options.zig");
|
|
6
7
|
const protocol = @import("../common/protocol.zig");
|
|
7
8
|
const rng = @import("../common/rng.zig");
|
|
9
|
+
const util = @import("../common/util.zig");
|
|
8
10
|
|
|
9
11
|
const moves = @import("data/moves.zig");
|
|
10
12
|
const species = @import("data/species.zig");
|
|
11
13
|
const types = @import("data/types.zig");
|
|
12
14
|
|
|
15
|
+
const calc = @import("calc.zig");
|
|
16
|
+
const chance = @import("chance.zig");
|
|
13
17
|
const mechanics = @import("mechanics.zig");
|
|
14
18
|
|
|
15
19
|
const assert = std.debug.assert;
|
|
@@ -17,16 +21,16 @@ const expectEqual = std.testing.expectEqual;
|
|
|
17
21
|
const expect = std.testing.expect;
|
|
18
22
|
|
|
19
23
|
/// The minimum size in bytes required to hold all Generation I choice options.
|
|
20
|
-
pub const
|
|
24
|
+
pub const MAX_CHOICES: usize = 9; // move 1..4, switch 2..6
|
|
21
25
|
/// The optimal size in bytes required to hold all Generation I choice options.
|
|
22
|
-
/// At least as large as
|
|
26
|
+
/// At least as large as MAX_CHOICES.
|
|
23
27
|
pub const MAX_LOGS: usize = 180;
|
|
24
28
|
|
|
25
29
|
/// The maximum number of bytes possibly logged by a single Generation I update.
|
|
26
|
-
pub const
|
|
27
|
-
|
|
30
|
+
pub const CHOICES_SIZE = if (builtin.mode == .ReleaseSmall)
|
|
31
|
+
MAX_CHOICES
|
|
28
32
|
else
|
|
29
|
-
std.math.ceilPowerOfTwo(usize,
|
|
33
|
+
std.math.ceilPowerOfTwo(usize, MAX_CHOICES) catch unreachable;
|
|
30
34
|
/// The optimal size in bytes required to hold the largest amount of log data possible from a
|
|
31
35
|
/// single Generation I update. At least as large as MAX_LOGS.
|
|
32
36
|
pub const LOGS_SIZE = if (builtin.mode == .ReleaseSmall)
|
|
@@ -34,12 +38,17 @@ pub const LOGS_SIZE = if (builtin.mode == .ReleaseSmall)
|
|
|
34
38
|
else
|
|
35
39
|
std.math.ceilPowerOfTwo(usize, MAX_LOGS) catch unreachable;
|
|
36
40
|
|
|
41
|
+
/// Null object pattern implementation of pkmn.battle.Options for Generation I.
|
|
42
|
+
pub const NULL = pkmn.battle.options(protocol.NULL, chance.NULL, calc.NULL);
|
|
43
|
+
|
|
37
44
|
const Choice = data.Choice;
|
|
38
45
|
const ID = data.ID;
|
|
39
46
|
const Player = data.Player;
|
|
40
47
|
const Result = data.Result;
|
|
41
48
|
|
|
42
|
-
const
|
|
49
|
+
const PointerType = util.PointerType;
|
|
50
|
+
|
|
51
|
+
const showdown = pkmn.options.showdown;
|
|
43
52
|
|
|
44
53
|
/// The pseudo random number generator used by Generation I.
|
|
45
54
|
pub const PRNG = rng.PRNG(1);
|
|
@@ -55,30 +64,33 @@ pub fn Battle(comptime RNG: anytype) type {
|
|
|
55
64
|
turn: u16 = 0,
|
|
56
65
|
/// The last damage dealt by either side.
|
|
57
66
|
last_damage: u16 = 0,
|
|
58
|
-
///
|
|
59
|
-
|
|
67
|
+
/// Details about the last move selected/used by each player required to detect desyncs.
|
|
68
|
+
last_moves: MoveDetails = .{},
|
|
60
69
|
/// The pseudo random number generator.
|
|
61
70
|
rng: RNG,
|
|
62
71
|
|
|
63
72
|
/// Returns the `Side` for the given `player`.
|
|
64
|
-
pub inline fn side(self:
|
|
65
|
-
|
|
73
|
+
pub inline fn side(self: anytype, player: Player) PointerType(@TypeOf(self), Side) {
|
|
74
|
+
assert(@typeInfo(@TypeOf(self)).Pointer.child == Self);
|
|
75
|
+
return &self.sides[@intFromEnum(player)];
|
|
66
76
|
}
|
|
67
77
|
|
|
68
78
|
/// Returns the `Side` of the opponent for the given `player`
|
|
69
|
-
pub inline fn foe(self:
|
|
70
|
-
|
|
79
|
+
pub inline fn foe(self: anytype, player: Player) PointerType(@TypeOf(self), Side) {
|
|
80
|
+
assert(@typeInfo(@TypeOf(self)).Pointer.child == Self);
|
|
81
|
+
return &self.sides[@intFromEnum(player.foe())];
|
|
71
82
|
}
|
|
72
83
|
|
|
73
84
|
/// Returns an identifier for the active Pokémon of `player`.
|
|
74
|
-
pub inline fn active(self: *Self, player: Player) ID {
|
|
75
|
-
return player.ident(@intCast(
|
|
85
|
+
pub inline fn active(self: *const Self, player: Player) ID {
|
|
86
|
+
return player.ident(@intCast(self.side(player).order[0]));
|
|
76
87
|
}
|
|
77
88
|
|
|
78
89
|
/// Returns the result of applying Player 1's choice `c1` and Player 2's choice `c2` to the
|
|
79
|
-
/// battle, optionally writing protocol logs to `log` if `options.
|
|
80
|
-
|
|
81
|
-
|
|
90
|
+
/// battle, optionally writing protocol message logs to `options.log` if `pkmn.options.log`
|
|
91
|
+
/// is enabled.
|
|
92
|
+
pub fn update(self: *Self, c1: Choice, c2: Choice, options: anytype) !Result {
|
|
93
|
+
return mechanics.update(self, c1, c2, options);
|
|
82
94
|
}
|
|
83
95
|
|
|
84
96
|
/// Fills in at most `out.len` possible choices for the `player` given the previous `result`
|
|
@@ -90,7 +102,7 @@ pub fn Battle(comptime RNG: anytype) type {
|
|
|
90
102
|
/// interacts with Disable, in which case there are no possible choices for the player to
|
|
91
103
|
/// make (i.e. on the cartridge a soft-lock occurs).
|
|
92
104
|
///
|
|
93
|
-
/// This function will always return
|
|
105
|
+
/// This function will always return choices > 0 if `pkmn.options.showdown` is true.
|
|
94
106
|
pub fn choices(self: *Self, player: Player, request: Choice.Type, out: []Choice) u8 {
|
|
95
107
|
return mechanics.choices(self, player, request, out);
|
|
96
108
|
}
|
|
@@ -120,7 +132,8 @@ pub const Side = extern struct {
|
|
|
120
132
|
}
|
|
121
133
|
|
|
122
134
|
/// Returns the stored `Pokemon` corresponding to the one-indexed party `slot`.
|
|
123
|
-
pub inline fn get(self:
|
|
135
|
+
pub inline fn get(self: anytype, slot: u8) PointerType(@TypeOf(self), Pokemon) {
|
|
136
|
+
assert(@typeInfo(@TypeOf(self)).Pointer.child == Side);
|
|
124
137
|
assert(slot > 0 and slot <= 6);
|
|
125
138
|
const id = self.order[slot - 1];
|
|
126
139
|
assert(id > 0 and id <= 6);
|
|
@@ -128,7 +141,8 @@ pub const Side = extern struct {
|
|
|
128
141
|
}
|
|
129
142
|
|
|
130
143
|
/// Returns the stored `Pokemon` corresponding to the `active` Pokémon (slot 1).
|
|
131
|
-
pub inline fn stored(self:
|
|
144
|
+
pub inline fn stored(self: anytype) PointerType(@TypeOf(self), Pokemon) {
|
|
145
|
+
assert(@typeInfo(@TypeOf(self)).Pointer.child == Side);
|
|
132
146
|
return self.get(1);
|
|
133
147
|
}
|
|
134
148
|
};
|
|
@@ -153,7 +167,8 @@ pub const ActivePokemon = extern struct {
|
|
|
153
167
|
}
|
|
154
168
|
|
|
155
169
|
/// Returns the active Pokémon's current move slot located at the one-indexed `mslot`.
|
|
156
|
-
pub inline fn move(self:
|
|
170
|
+
pub inline fn move(self: anytype, mslot: u8) PointerType(@TypeOf(self), MoveSlot) {
|
|
171
|
+
assert(@typeInfo(@TypeOf(self)).Pointer.child == ActivePokemon);
|
|
157
172
|
assert(mslot > 0 and mslot <= 4);
|
|
158
173
|
assert(self.moves[mslot - 1].id != .None);
|
|
159
174
|
return &self.moves[mslot - 1];
|
|
@@ -182,7 +197,8 @@ pub const Pokemon = extern struct {
|
|
|
182
197
|
}
|
|
183
198
|
|
|
184
199
|
/// Returns the Pokémon's original move slot located at the one-indexed `mslot`.
|
|
185
|
-
pub inline fn move(self:
|
|
200
|
+
pub inline fn move(self: anytype, mslot: u8) PointerType(@TypeOf(self), MoveSlot) {
|
|
201
|
+
assert(@typeInfo(@TypeOf(self)).Pointer.child == Pokemon);
|
|
186
202
|
assert(mslot > 0 and mslot <= 4);
|
|
187
203
|
assert(self.moves[mslot - 1].id != .None);
|
|
188
204
|
return &self.moves[mslot - 1];
|
|
@@ -201,17 +217,19 @@ pub const MoveSlot = extern struct {
|
|
|
201
217
|
}
|
|
202
218
|
};
|
|
203
219
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
///
|
|
207
|
-
|
|
208
|
-
/// Player 1
|
|
209
|
-
|
|
210
|
-
///
|
|
211
|
-
|
|
220
|
+
/// Details required to detect desyncs based on the move last selected/executed by players.
|
|
221
|
+
pub const MoveDetails = packed struct {
|
|
222
|
+
/// The move slot index of the last move selected in the battle menu by Player 1
|
|
223
|
+
p1_index: if (showdown) u8 else u4 = 0,
|
|
224
|
+
/// Whether the last move executed by Player 1 was counterable.
|
|
225
|
+
p1_counterable: if (showdown) u8 else u4 = 0,
|
|
226
|
+
/// The move slot index of the last move selected in the battle menu by Player 2
|
|
227
|
+
p2_index: if (showdown) u8 else u4 = 0,
|
|
228
|
+
/// Whether the last move executed by Player 2 was counterable.
|
|
229
|
+
p2_counterable: if (showdown) u8 else u4 = 0,
|
|
212
230
|
|
|
213
231
|
comptime {
|
|
214
|
-
assert(@sizeOf(
|
|
232
|
+
assert(@sizeOf(MoveDetails) == if (showdown) 4 else 2);
|
|
215
233
|
}
|
|
216
234
|
};
|
|
217
235
|
|
|
@@ -241,13 +259,13 @@ pub const Status = enum(u8) {
|
|
|
241
259
|
/// Whether or not the status `num` is the same as `status`.
|
|
242
260
|
pub inline fn is(num: u8, status: Status) bool {
|
|
243
261
|
if (status == .SLP) return Status.duration(num) > 0;
|
|
244
|
-
return ((num >> @intCast(
|
|
262
|
+
return ((num >> @intCast(@intFromEnum(status))) & 1) != 0;
|
|
245
263
|
}
|
|
246
264
|
|
|
247
265
|
/// Initializes a non-sleep `status`. Use `slp` or `slf` to initialize a sleep status.
|
|
248
266
|
pub inline fn init(status: Status) u8 {
|
|
249
267
|
assert(status != .SLP and status != .EXT);
|
|
250
|
-
return @as(u8, 1) << @intCast(
|
|
268
|
+
return @as(u8, 1) << @intCast(@intFromEnum(status));
|
|
251
269
|
}
|
|
252
270
|
|
|
253
271
|
/// Initializes a non self-inflicted sleep status with duration `dur`.
|
|
@@ -264,7 +282,7 @@ pub const Status = enum(u8) {
|
|
|
264
282
|
|
|
265
283
|
/// Returns the duration of a sleep status.
|
|
266
284
|
pub inline fn duration(num: u8) u3 {
|
|
267
|
-
return @intCast(
|
|
285
|
+
return @intCast(num & SLP);
|
|
268
286
|
}
|
|
269
287
|
|
|
270
288
|
/// Returns whether `num` reflects any status.
|
|
@@ -411,10 +429,11 @@ pub fn Stats(comptime T: type) type {
|
|
|
411
429
|
/// Computes the value of `stat` given a `base`, `dv`, stat `exp` and `level`.
|
|
412
430
|
pub fn calc(comptime stat: []const u8, base: T, dv: u4, exp: u16, level: u8) T {
|
|
413
431
|
assert(level > 0 and level <= 100);
|
|
414
|
-
const
|
|
432
|
+
const float: f32 = @floatFromInt(exp);
|
|
433
|
+
const evs: u16 = @min(255, @as(u16, @intFromFloat(@ceil(@sqrt(float)))));
|
|
415
434
|
const core: u32 = (2 *% (@as(u32, base) +% dv)) +% (evs / 4);
|
|
416
435
|
const factor: u32 = if (std.mem.eql(u8, stat, "hp")) level + 10 else 5;
|
|
417
|
-
return @intCast(
|
|
436
|
+
return @intCast(core *% @as(u32, level) / 100 +% factor);
|
|
418
437
|
}
|
|
419
438
|
};
|
|
420
439
|
}
|
|
@@ -463,7 +482,7 @@ test Boosts {
|
|
|
463
482
|
pub const Move = moves.Move;
|
|
464
483
|
|
|
465
484
|
test Move {
|
|
466
|
-
try expectEqual(2, @
|
|
485
|
+
try expectEqual(2, @intFromEnum(Move.KarateChop));
|
|
467
486
|
const move = Move.get(.Fissure);
|
|
468
487
|
try expectEqual(Move.Effect.OHKO, move.effect);
|
|
469
488
|
try expectEqual(@as(u8, 30), move.accuracy);
|
|
@@ -524,7 +543,7 @@ test Move {
|
|
|
524
543
|
pub const Species = species.Species;
|
|
525
544
|
|
|
526
545
|
test Species {
|
|
527
|
-
try expectEqual(2, @
|
|
546
|
+
try expectEqual(2, @intFromEnum(Species.Ivysaur));
|
|
528
547
|
try expectEqual(@as(u8, 100), Species.get(.Mew).stats.def);
|
|
529
548
|
}
|
|
530
549
|
|
|
@@ -541,9 +560,11 @@ pub const Effectiveness = enum(u8) {
|
|
|
541
560
|
Super = 20,
|
|
542
561
|
|
|
543
562
|
/// Used to determine the effectiness of a neutral damage hit.
|
|
544
|
-
pub const neutral: u16 =
|
|
563
|
+
pub const neutral: u16 =
|
|
564
|
+
@intFromEnum(Effectiveness.Neutral) * @intFromEnum(Effectiveness.Neutral);
|
|
545
565
|
/// Used to detect whether there is a mismatch in type effectivness (relevant for precedence).
|
|
546
|
-
pub const mismatch: u16 =
|
|
566
|
+
pub const mismatch: u16 =
|
|
567
|
+
@intFromEnum(Effectiveness.Resisted) + @intFromEnum(Effectiveness.Super);
|
|
547
568
|
|
|
548
569
|
comptime {
|
|
549
570
|
assert(@sizeOf(Effectiveness) == 1);
|
|
@@ -551,8 +572,8 @@ pub const Effectiveness = enum(u8) {
|
|
|
551
572
|
};
|
|
552
573
|
|
|
553
574
|
test Types {
|
|
554
|
-
try expectEqual(14, @
|
|
555
|
-
try expectEqual(20, @
|
|
575
|
+
try expectEqual(14, @intFromEnum(Type.Dragon));
|
|
576
|
+
try expectEqual(20, @intFromEnum(Effectiveness.Super));
|
|
556
577
|
|
|
557
578
|
try expect(!Type.Ghost.special());
|
|
558
579
|
try expect(Type.Dragon.special());
|