@pkmn/engine 0.1.0-dev.683fba11 → 0.1.0-dev.6ba42cd5

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.
Files changed (56) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +30 -4
  3. package/build/pkg/addon/browser.js +4 -1
  4. package/build/pkg/addon/browser.js.map +1 -1
  5. package/build/pkg/addon/node.js +20 -7
  6. package/build/pkg/addon/node.js.map +1 -1
  7. package/build/pkg/addon/wasm.js +23 -14
  8. package/build/pkg/addon/wasm.js.map +1 -1
  9. package/build/pkg/addon.d.ts +5 -5
  10. package/build/pkg/addon.js.map +1 -1
  11. package/build/pkg/gen1.d.ts +2 -1
  12. package/build/pkg/gen1.js +22 -9
  13. package/build/pkg/gen1.js.map +1 -1
  14. package/build/pkg/index.d.ts +2 -0
  15. package/build/pkg/index.js +17 -7
  16. package/build/pkg/index.js.map +1 -1
  17. package/build/tools/debug.js +36 -9
  18. package/build/tools/debug.js.map +1 -1
  19. package/build/tools/display/debug.jsx +18 -7
  20. package/build/tools/display/debug.jsx.map +1 -1
  21. package/build/tools/display/index.js +18 -7
  22. package/build/tools/display/index.js.map +1 -1
  23. package/build/tools/display/ui.jsx +18 -8
  24. package/build/tools/display/ui.jsx.map +1 -1
  25. package/build/tools/display/util.js +16 -1
  26. package/build/tools/display/util.js.map +1 -1
  27. package/build.zig +176 -198
  28. package/package.json +31 -131
  29. package/src/bin/install-pkmn-engine +24 -8
  30. package/src/lib/bindings/c.zig +200 -0
  31. package/src/lib/bindings/node.zig +117 -0
  32. package/src/lib/bindings/wasm.zig +50 -0
  33. package/src/lib/common/array.zig +5 -12
  34. package/src/lib/common/debug.zig +11 -21
  35. package/src/lib/common/js.zig +3 -9
  36. package/src/lib/common/optional.zig +11 -21
  37. package/src/lib/common/protocol.zig +176 -163
  38. package/src/lib/common/rational.zig +275 -284
  39. package/src/lib/common/util.zig +14 -31
  40. package/src/lib/gen1/calc.zig +65 -62
  41. package/src/lib/gen1/chance.zig +17 -32
  42. package/src/lib/gen1/data/types.zig +2 -2
  43. package/src/lib/gen1/data.zig +5 -5
  44. package/src/lib/gen1/helpers.zig +8 -10
  45. package/src/lib/gen1/mechanics.zig +30 -29
  46. package/src/lib/gen2/calc.zig +3 -6
  47. package/src/lib/gen2/chance.zig +12 -18
  48. package/src/lib/gen2/data/types.zig +6 -3
  49. package/src/lib/gen2/data.zig +8 -7
  50. package/src/lib/gen2/helpers.zig +4 -6
  51. package/src/lib/gen2/mechanics.zig +28 -17
  52. package/src/lib/napi.h +2 -0
  53. package/src/lib/node.zig +3 -118
  54. package/src/lib/pkmn.zig +94 -19
  55. package/src/lib/wasm.zig +9 -44
  56. package/src/tools/display/dom.ts +5 -8
@@ -19,8 +19,6 @@ const Stats = data.Stats;
19
19
 
20
20
  const endian = builtin.cpu.arch.endian();
21
21
 
22
- const Struct = if (@hasField(std.builtin.Type, "struct")) .@"struct" else .Struct;
23
-
24
22
  /// Configuration options controlling the domain of valid Pokémon that can be generated by the
25
23
  /// random generator helpers.
26
24
  pub const Options = struct {
@@ -163,7 +161,7 @@ pub const Pokemon = struct {
163
161
  var pokemon = data.Pokemon{};
164
162
  pokemon.species = p.species;
165
163
  const species = Species.get(p.species);
166
- inline for (@field(@typeInfo(@TypeOf(pokemon.stats)), @tagName(Struct)).fields) |field| {
164
+ inline for (@typeInfo(@TypeOf(pokemon.stats)).@"struct".fields) |field| {
167
165
  const hp = comptime std.mem.eql(u8, field.name, "hp");
168
166
  const spc =
169
167
  comptime std.mem.eql(u8, field.name, "spa") or std.mem.eql(u8, field.name, "spd");
@@ -199,7 +197,7 @@ pub const Pokemon = struct {
199
197
  const lvl = if (rand.chance(u8, 1, 20)) rand.range(u8, 1, 99 + 1) else 100;
200
198
  var stats: Stats(u16) = .{};
201
199
  const dvs = DVs.random(rand);
202
- inline for (@field(@typeInfo(@TypeOf(stats)), @tagName(Struct)).fields) |field| {
200
+ inline for (@typeInfo(@TypeOf(stats)).@"struct".fields) |field| {
203
201
  @field(stats, field.name) = Stats(u16).calc(
204
202
  field.name,
205
203
  @field(species.stats, field.name),
@@ -212,7 +210,7 @@ pub const Pokemon = struct {
212
210
  );
213
211
  }
214
212
 
215
- var ms = [_]MoveSlot{.{}} ** 4;
213
+ var ms: [4]MoveSlot = @splat(.{});
216
214
  const n = if (rand.chance(u8, 1, 100)) rand.range(u4, 1, 3 + 1) else 4;
217
215
  for (0..n) |i| {
218
216
  var m: Move = .None;
@@ -292,23 +290,23 @@ else
292
290
  pub fn diff(
293
291
  a: *const data.Battle(data.PRNG),
294
292
  b: *const data.Battle(data.PRNG),
295
- w: protocol.ByteStream.Writer,
293
+ w: *protocol.Writer,
296
294
  ) !usize {
297
- const pos = w.stream.pos;
295
+ const pos = w.pos;
298
296
  const size = @sizeOf(data.Battle(data.PRNG)) - @sizeOf(data.PRNG);
299
297
  assert(size % 2 == 0);
300
298
  assert(size / 2 < 255);
301
299
 
302
300
  var i: usize = 0;
303
301
  while (i < size) : (i += 2) {
304
- const val = util.bytesAsValue(u16, std.mem.asBytes(a)[i .. i + 2]).*;
305
- if (val != util.bytesAsValue(u16, std.mem.asBytes(b)[i .. i + 2]).*) {
302
+ const val = std.mem.bytesAsValue(u16, std.mem.asBytes(a)[i .. i + 2]).*;
303
+ if (val != std.mem.bytesAsValue(u16, std.mem.asBytes(b)[i .. i + 2]).*) {
306
304
  try w.writeByte(@intCast(i / 2));
307
305
  try w.writeInt(u16, val, endian);
308
306
  }
309
307
  }
310
308
 
311
- return w.stream.pos - pos;
309
+ return w.pos - pos;
312
310
  }
313
311
 
314
312
  /// TODO
@@ -457,7 +457,7 @@ fn executeMove(
457
457
  // selection glitch - if the move was proced due to Metronome / Mirror Move it either subtracts
458
458
  // from the Hyper Beam slot if present or skips PP deduction entirely...
459
459
  if (showdown and !skip_pp and side.last_selected_move == .HyperBeam and
460
- side.last_selected_move != side.active.move(battle.lastMove(player).index).id)
460
+ side.last_selected_move != side.active.move(mslot).id)
461
461
  {
462
462
  assert(mslot == 1);
463
463
  const has_beam = has_beam: {
@@ -618,17 +618,10 @@ fn beforeMove(
618
618
  return .err;
619
619
  }
620
620
  }
621
- // Pokémon Showdown incorrectly changes the "target" of the confusion self-hit based
622
- // on the targeting behavior of the confused Pokémon's selected move which results
623
- // in the wrong behavior with respect to the Substitute + Confusion glitch
624
- const target = if (showdown and Move.get(side.last_selected_move).target == .Self)
625
- player
626
- else
627
- player.foe();
628
621
 
629
622
  const uncapped = battle.last_damage;
630
623
  // Skipping adjustDamage / randomizeDamage / checkHit
631
- _ = try applyDamage(battle, player, target, .Confusion, options);
624
+ _ = try applyDamage(battle, player, player.foe(), .Confusion, options);
632
625
  // Pokémon Showdown thinks that confusion damage is uncapped ¯\_(ツ)_/¯
633
626
  if (showdown) battle.last_damage = uncapped;
634
627
 
@@ -885,7 +878,7 @@ fn doMove(
885
878
  var miss = showdown and miss: {
886
879
  immune = move.target != .Self and !status and !counter and
887
880
  (@intFromEnum(move.type.effectiveness(foe.active.types.type1)) == 0 or
888
- @intFromEnum(move.type.effectiveness(foe.active.types.type2)) == 0);
881
+ @intFromEnum(move.type.effectiveness(foe.active.types.type2)) == 0);
889
882
  if (immune and move.effect != .Binding) break :miss true;
890
883
  if (move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe) {
891
884
  battle.last_damage = 0;
@@ -1026,7 +1019,7 @@ fn doMove(
1026
1019
  const sub = showdown and foe.active.volatiles.Substitute;
1027
1020
 
1028
1021
  var nullified = false;
1029
- var hit: u4 = 0;
1022
+ var hit: usize = 0;
1030
1023
  while (hit < hits) {
1031
1024
  if (hit == 0) {
1032
1025
  if (crit) try log.crit(.{battle.active(player.foe())});
@@ -1054,7 +1047,7 @@ fn doMove(
1054
1047
  if (showdown and move.effect == .Twineedle and !nullified and foe.stored().hp > 0) {
1055
1048
  try Effects.poison(battle, player, Move.get(.PoisonSting), options);
1056
1049
  }
1057
- try log.hitcount(.{ battle.active(player.foe()), hit });
1050
+ try log.hitcount(.{ battle.active(player.foe()), @as(u4, @intCast(hit)) });
1058
1051
  } else if (showdown and move.effect == .Binding) {
1059
1052
  // This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
1060
1053
  try Effects.binding(battle, player, rewrap, options);
@@ -1170,7 +1163,7 @@ fn calcDamage(
1170
1163
  // encountered so we need to make sure that in the event of a miss we commit the status of the
1171
1164
  // crit roll. The alternative to doing this extra work is to *always* commit the crit roll but
1172
1165
  // that results in counter intuitive probabilities in the common case
1173
- if (pkmn.options.chance and !pkmn.options.mod) {
1166
+ if (pkmn.options.chance and !pkmn.options.mod and move.effect != .Explode) {
1174
1167
  atk = attack(side, !crit, special);
1175
1168
  def = defense(target, !crit, special, false);
1176
1169
  if ((atk > 255 or def > 255) and (def / 4) & 255 == 0) options.chance.glitch();
@@ -1281,15 +1274,15 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
1281
1274
  // Pretend Splash was used as a stand-in when no move has been used to fail below with 0 BP
1282
1275
  const foe_last_selected_move =
1283
1276
  Move.get(if (foe.last_selected_move == .None or foe.last_selected_move == .SKIP_TURN)
1284
- .Splash
1285
- else
1286
- foe.last_selected_move);
1277
+ .Splash
1278
+ else
1279
+ foe.last_selected_move);
1287
1280
 
1288
1281
  const used = battle.lastMove(player.foe()).counterable != 0;
1289
1282
  const selected = foe_last_selected_move.bp > 0 and
1290
1283
  foe.last_selected_move != .Counter and
1291
1284
  (foe_last_selected_move.type == .Normal or
1292
- foe_last_selected_move.type == .Fighting);
1285
+ foe_last_selected_move.type == .Fighting);
1293
1286
 
1294
1287
  if (!used and !selected) {
1295
1288
  try options.log.fail(.{ battle.active(player), .None });
@@ -1304,11 +1297,10 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
1304
1297
  }
1305
1298
 
1306
1299
  // NOTE: llvm/llvm-project#58557
1307
- if (comptime builtin.target.isWasm()) {
1308
- battle.last_damage = @max(battle.last_damage *% 2, std.math.maxInt(u16));
1309
- } else {
1310
- battle.last_damage *|= 2;
1311
- }
1300
+ battle.last_damage = if (comptime builtin.target.cpu.arch.isWasm())
1301
+ @max(battle.last_damage *% 2, std.math.maxInt(u16))
1302
+ else
1303
+ battle.last_damage *| 2;
1312
1304
 
1313
1305
  // Pokémon Showdown calls moveHit before Counter
1314
1306
  if (!showdown and !try checkHit(battle, player, move, options)) return null;
@@ -1454,7 +1446,16 @@ fn moveHit(
1454
1446
  break :miss true;
1455
1447
  }
1456
1448
  if (!showdown) {
1457
- if (move.effect == .Swift) return true;
1449
+ if (move.effect == .Swift) {
1450
+ // If Swift could cause a division-by-zero freeze (which doesn't exist on Pokémon
1451
+ // Showdown, hence why the block above doesn't include this code), we need the
1452
+ // damage roll to be committed. However, the commit code will not save this roll if
1453
+ // the glitch occurs and the move was not marked as having hit. To work around this
1454
+ // we flag that the move hit (as Swift trivially does), but pass the probability of
1455
+ // the hit as zero to ensure it doesn't actually get saved.
1456
+ try options.chance.hit(player, true, 0);
1457
+ return true;
1458
+ }
1458
1459
  if (foe.active.volatiles.Invulnerable) break :miss true;
1459
1460
  }
1460
1461
 
@@ -3144,11 +3145,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3144
3145
  },
3145
3146
  .Switch => {
3146
3147
  const side = battle.side(player);
3147
- var slot: u4 = 2;
3148
+ var slot: usize = 2;
3148
3149
  while (slot <= 6) : (slot += 1) {
3149
3150
  const id = side.order[slot - 1];
3150
3151
  if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
3151
- out[n] = .{ .type = .Switch, .data = slot };
3152
+ out[n] = .{ .type = .Switch, .data = @intCast(slot) };
3152
3153
  n += 1;
3153
3154
  }
3154
3155
  if (n == 0) {
@@ -3179,11 +3180,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3179
3180
  return n;
3180
3181
  }
3181
3182
 
3182
- var slot: u4 = 2;
3183
+ var slot: usize = 2;
3183
3184
  while (slot <= 6) : (slot += 1) {
3184
3185
  const id = side.order[slot - 1];
3185
3186
  if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
3186
- out[n] = .{ .type = .Switch, .data = slot };
3187
+ out[n] = .{ .type = .Switch, .data = @intCast(slot) };
3187
3188
  n += 1;
3188
3189
  }
3189
3190
 
@@ -3216,7 +3217,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3216
3217
  const struggle =
3217
3218
  m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
3218
3219
  const s = if (struggle) 0 else slot;
3219
- out[n] = .{ .type = .Move, .data = s };
3220
+ out[n] = .{ .type = .Move, .data = @intCast(s) };
3220
3221
  n += 1;
3221
3222
  return n;
3222
3223
  }
@@ -3230,7 +3231,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3230
3231
  if (m.id == .None) break;
3231
3232
  if (m.pp == 0) continue;
3232
3233
  if (active.volatiles.disable_move == slot) continue;
3233
- out[n] = .{ .type = .Move, .data = slot };
3234
+ out[n] = .{ .type = .Move, .data = @intCast(slot) };
3234
3235
  n += 1;
3235
3236
  }
3236
3237
  // Struggle (Pokémon Showdown would use 'move 1' here)
@@ -15,9 +15,6 @@ const isPointerTo = util.isPointerTo;
15
15
  const Player = common.Player;
16
16
  const PointerType = util.PointerType;
17
17
 
18
- const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
19
- const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
20
-
21
18
  /// Information relevant to damage calculation that occured during a Generation II battle `update`.
22
19
  pub const Summaries = extern struct {
23
20
  /// Relevant information for Player 1.
@@ -91,8 +88,8 @@ pub const Calc = struct {
91
88
  };
92
89
 
93
90
  return if (switch (@typeInfo(@TypeOf(val))) {
94
- Enum => val != .None,
95
- Int => val != 0,
91
+ .@"enum" => val != .None,
92
+ .int => val != 0,
96
93
  else => unreachable,
97
94
  }) val else null;
98
95
  }
@@ -148,6 +145,6 @@ fn ReturnType(comptime field: Action.Field) type {
148
145
  return ?(switch (field) {
149
146
  .damages => u8,
150
147
  .critical_hits => Optional(bool),
151
- else => std.meta.FieldType(Action, field),
148
+ else => @FieldType(Action, @tagName(field)),
152
149
  });
153
150
  }
@@ -27,10 +27,6 @@ const Type = data.Type;
27
27
  const Effectiveness = data.Effectiveness;
28
28
  const TriAttack = data.TriAttack;
29
29
 
30
- const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
31
- const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
32
- const Struct = if (@hasField(std.builtin.Type, "struct")) .@"struct" else .Struct;
33
-
34
30
  /// Actions taken by a hypothetical "chance player" that convey information about which RNG events
35
31
  /// were observed during a Generation II battle `update`. This can additionally be provided as input
36
32
  /// to the `update` call to override the normal behavior of the RNG in order to force specific
@@ -57,15 +53,15 @@ pub const Actions = extern struct {
57
53
  /// same shape if they have the same fields set (though those fields need not necessarily be
58
54
  /// set to the same value).
59
55
  pub fn matches(a: Actions, b: Actions) bool {
60
- inline for (@field(@typeInfo(Actions), @tagName(Struct)).fields) |player| {
61
- inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
56
+ inline for (@typeInfo(Actions).@"struct".fields) |player| {
57
+ inline for (@typeInfo(Action).@"struct".fields) |field| {
62
58
  const a_val = @field(@field(a, player.name), field.name);
63
59
  const b_val = @field(@field(b, player.name), field.name);
64
60
 
65
61
  switch (@typeInfo(@TypeOf(a_val))) {
66
- Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
62
+ .@"enum" => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
67
63
  return false,
68
- Int => if ((a_val > 0) != (b_val > 0)) return false,
64
+ .int => if ((a_val > 0) != (b_val > 0)) return false,
69
65
  else => unreachable,
70
66
  }
71
67
  }
@@ -73,7 +69,7 @@ pub const Actions = extern struct {
73
69
  return true;
74
70
  }
75
71
 
76
- pub fn fmt(self: Actions, writer: anytype, shape: bool) !void {
72
+ pub fn fmt(self: Actions, writer: *std.Io.Writer, shape: bool) !void {
77
73
  try writer.writeAll("<P1 = ");
78
74
  try self.p1.fmt(writer, shape);
79
75
  try writer.writeAll(", P2 = ");
@@ -81,8 +77,7 @@ pub const Actions = extern struct {
81
77
  try writer.writeAll(">");
82
78
  }
83
79
 
84
- pub fn format(a: Actions, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
85
- _ = .{ f, o };
80
+ pub fn format(a: Actions, w: *std.Io.Writer) !void {
86
81
  try fmt(a, w, false);
87
82
  }
88
83
  };
@@ -186,18 +181,17 @@ pub const Action = packed struct(u128) {
186
181
 
187
182
  pub const Field = std.meta.FieldEnum(Action);
188
183
 
189
- pub fn format(a: Action, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
190
- _ = .{ f, o };
184
+ pub fn format(a: Action, w: *std.Io.Writer) !void {
191
185
  try fmt(a, w, false);
192
186
  }
193
187
 
194
- pub fn fmt(self: Action, writer: anytype, shape: bool) !void {
188
+ pub fn fmt(self: Action, writer: *std.Io.Writer, shape: bool) !void {
195
189
  try writer.writeByte('(');
196
190
  var printed = false;
197
- inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
191
+ inline for (@typeInfo(Action).@"struct".fields) |field| {
198
192
  const val = @field(self, field.name);
199
193
  switch (@typeInfo(@TypeOf(val))) {
200
- Enum => if (val != .None) {
194
+ .@"enum" => if (val != .None) {
201
195
  if (printed) try writer.writeAll(", ");
202
196
  if (shape) {
203
197
  try writer.print("{s}:?", .{field.name});
@@ -211,7 +205,7 @@ pub const Action = packed struct(u128) {
211
205
  }
212
206
  printed = true;
213
207
  },
214
- Int => if (val != 0) {
208
+ .int => if (val != 0) {
215
209
  if (printed) try writer.writeAll(", ");
216
210
  if (shape) {
217
211
  try writer.print("{s}:?", .{field.name});
@@ -786,7 +780,7 @@ pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
786
780
 
787
781
  r.reduce();
788
782
  if (r.p != p or r.q != q) {
789
- print("expected {d}/{d}, found {}\n", .{ p, q, r });
783
+ print("expected {d}/{d}, found {f}\n", .{ p, q, r });
790
784
  return error.TestExpectedEqual;
791
785
  }
792
786
  }
@@ -172,9 +172,12 @@ pub const Type = enum(u8) {
172
172
  return CHART[@intFromEnum(t1)][@intFromEnum(t2)];
173
173
  }
174
174
 
175
- /// The precedence order of Type `type`.
176
- pub fn precedence(self: Type) u8 {
177
- return PRECEDENCE[@intFromEnum(self)];
175
+ /// The precedence order of Type `t1` vs. Type `t2`.
176
+ pub fn precedence(t1: Type, t2: Type) u8 {
177
+ // The Ice vs. Fire matchup is out of order - return a higher
178
+ // number than anything else so that it sorts last correctly
179
+ if (t1 == .Ice and t2 == .Fire) return 18;
180
+ return PRECEDENCE[@intFromEnum(t2)];
178
181
  }
179
182
 
180
183
  /// The internal index of this Type used by Present.
@@ -57,7 +57,7 @@ pub fn Battle(comptime RNG: anytype) type {
57
57
  turn: u16 = 0,
58
58
  field: Field = .{},
59
59
 
60
- _: [padding]u8 = .{0} ** padding,
60
+ _: [padding]u8 = @splat(0),
61
61
 
62
62
  pub fn side(self: anytype, player: Player) PointerType(@TypeOf(self), Side) {
63
63
  assert(isPointerTo(self, Self));
@@ -100,13 +100,13 @@ pub const Weather = enum(u4) {
100
100
  };
101
101
 
102
102
  pub const Side = extern struct {
103
- pokemon: [6]Pokemon = [_]Pokemon{.{}} ** 6,
103
+ pokemon: [6]Pokemon = @splat(.{}),
104
104
  active: ActivePokemon align(8) = .{},
105
105
  conditions: Conditions = .{},
106
106
  // NB: wLastPlayerCounterMove / wLastEnemyCounterMove (wLastPlayerMove / wLastEnemyMove)
107
107
  last_used_move: Move = .None,
108
108
 
109
- _: [7]u8 = .{0} ** 7,
109
+ _: [7]u8 = @splat(0),
110
110
 
111
111
  const Conditions = packed struct(u32) {
112
112
  Spikes: bool = false,
@@ -161,7 +161,7 @@ pub const Side = extern struct {
161
161
  pub const ActivePokemon = extern struct {
162
162
  volatiles: Volatiles align(4) = .{},
163
163
  stats: Stats(u16) = .{},
164
- moves: [4]MoveSlot = [_]MoveSlot{.{}} ** 4,
164
+ moves: [4]MoveSlot = @splat(.{}),
165
165
  boosts: Boosts = .{},
166
166
  types: Types = .{},
167
167
  species: Species = .None,
@@ -182,7 +182,7 @@ pub const ActivePokemon = extern struct {
182
182
 
183
183
  pub const Pokemon = extern struct {
184
184
  stats: Stats(u16) = .{},
185
- moves: [4]MoveSlot = [_]MoveSlot{.{}} ** 4,
185
+ moves: [4]MoveSlot = @splat(.{}),
186
186
  types: Types = .{},
187
187
  dvs: DVs = .{},
188
188
  hp: u16 = 0,
@@ -449,8 +449,9 @@ test Types {
449
449
  try expect(!Type.Steel.special());
450
450
  try expect(Type.Dark.special());
451
451
 
452
- try expectEqual(@as(u8, 3), Type.Water.precedence());
453
- try expect(Type.Bug.precedence() > Type.Poison.precedence());
452
+ try expectEqual(@as(u8, 3), Type.precedence(.Water, .Water));
453
+ try expect(Type.precedence(.Poison, .Bug) > Type.precedence(.Poison, .Poison));
454
+ try expect(Type.precedence(.Ice, .Fire) > Type.precedence(.Ice, .Water));
454
455
 
455
456
  try expectEqual(Effectiveness.Super, Type.effectiveness(.Ghost, .Psychic));
456
457
  try expectEqual(Effectiveness.Super, Type.effectiveness(.Water, .Fire));
@@ -16,8 +16,6 @@ const showdown = options.showdown;
16
16
  const Species = data.Species;
17
17
  const Stats = data.Stats;
18
18
 
19
- const Struct = if (@hasField(std.builtin.Type, "struct")) .@"struct" else .Struct;
20
-
21
19
  pub const Options = struct {
22
20
  cleric: bool = showdown,
23
21
  };
@@ -74,7 +72,7 @@ pub fn prng(rand: *PSRNG) data.PRNG {
74
72
  var carry: u8 = 0;
75
73
  var div: u8 = 0;
76
74
 
77
- var i: u8 = 0;
75
+ var i: usize = 0;
78
76
  while (i < seed.len) {
79
77
  div = @intCast(timer >> 8);
80
78
  // This is effectively @addWithOverflow but with three operands
@@ -142,7 +140,7 @@ pub const Pokemon = struct {
142
140
  var pokemon = data.Pokemon{};
143
141
  pokemon.species = p.species;
144
142
  const species = Species.get(p.species);
145
- inline for (@field(@typeInfo(@TypeOf(pokemon.stats)), @tagName(Struct)).fields) |field| {
143
+ inline for (@typeInfo(@TypeOf(pokemon.stats)).@"struct".fields) |field| {
146
144
  const hp = comptime std.mem.eql(u8, field.name, "hp");
147
145
  const spc =
148
146
  comptime std.mem.eql(u8, field.name, "spa") or std.mem.eql(u8, field.name, "spd");
@@ -183,7 +181,7 @@ pub const Pokemon = struct {
183
181
 
184
182
  var stats: Stats(u16) = .{};
185
183
  const dvs = gen1.DVs.random(rand);
186
- inline for (@field(@typeInfo(@TypeOf(stats)), @tagName(Struct)).fields) |field| {
184
+ inline for (@typeInfo(@TypeOf(stats)).@"struct".fields) |field| {
187
185
  const hp = std.mem.eql(u8, field.name, "hp");
188
186
  const spc = std.mem.eql(u8, field.name, "spa") or std.mem.eql(u8, field.name, "spd");
189
187
  @field(stats, field.name) = Stats(u16).calc(
@@ -195,7 +193,7 @@ pub const Pokemon = struct {
195
193
  );
196
194
  }
197
195
 
198
- var ms = [_]MoveSlot{.{}} ** 4;
196
+ var ms: [4]MoveSlot = @splat(.{});
199
197
  const n = if (rand.chance(u8, 1, 100)) rand.range(u4, 1, 3 + 1) else 4;
200
198
  for (0..n) |i| {
201
199
  var m: Move = .None;
@@ -1,5 +1,6 @@
1
1
  const DEBUG = @import("../common/debug.zig").print;
2
2
 
3
+ const builtin = @import("builtin");
3
4
  const common = @import("../common/data.zig");
4
5
  const data = @import("data.zig");
5
6
  const generated = @import("generated.zig");
@@ -681,7 +682,7 @@ pub fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype)
681
682
 
682
683
  // Type effectiveness matchup precedence only matters with (NVE, SE)
683
684
  if (!showdown and (eff1 + eff2) == Effectiveness.mismatch and
684
- types.type1.precedence() > types.type2.precedence())
685
+ Type.precedence(state.type, types.type1) > Type.precedence(state.type, types.type2))
685
686
  {
686
687
  assert(eff2 != neutral);
687
688
  d = d *% eff2 / 10;
@@ -1426,7 +1427,8 @@ pub const Effects = struct {
1426
1427
  if (move.type.special() != (last == .MirrorCoat)) return;
1427
1428
  if (state.damage == 0) return;
1428
1429
 
1429
- state.damage *|= 2;
1430
+ // NOTE: llvm/llvm-project#58557
1431
+ state.damage = smul(state.damage, 2);
1430
1432
  state.miss = false;
1431
1433
  }
1432
1434
 
@@ -1521,15 +1523,18 @@ pub const Effects = struct {
1521
1523
 
1522
1524
  pub const double = struct {
1523
1525
  pub fn flying(battle: anytype, player: Player, state: *State, _: anytype) !void {
1524
- if (battle.foe(player).active.volatiles.Flying) state.damage *|= 2;
1526
+ if (!battle.foe(player).active.volatiles.Flying) return;
1527
+ state.damage = smul(state.damage, 2);
1525
1528
  }
1526
1529
 
1527
1530
  pub fn underground(battle: anytype, player: Player, state: *State, _: anytype) !void {
1528
- if (battle.foe(player).active.volatiles.Underground) state.damage *|= 2;
1531
+ if (!battle.foe(player).active.volatiles.Underground) return;
1532
+ state.damage = smul(state.damage, 2);
1529
1533
  }
1530
1534
 
1531
1535
  pub fn minimize(battle: anytype, player: Player, state: *State, _: anytype) !void {
1532
- if (battle.foe(player).active.volatiles.minimized) state.damage *|= 2;
1536
+ if (!battle.foe(player).active.volatiles.minimized) return;
1537
+ state.damage = smul(state.damage, 2);
1533
1538
  }
1534
1539
  };
1535
1540
 
@@ -1553,7 +1558,7 @@ pub const Effects = struct {
1553
1558
  const last = battle.foe(player).lastMove(true);
1554
1559
  const failed = volatiles.Encore or
1555
1560
  (last == .None or last == .Struggle or
1556
- last == .Encore or .last == .MirrorMove);
1561
+ last == .Encore or .last == .MirrorMove);
1557
1562
  if (failed) return options.log.fail(.{ foe_ident, .None });
1558
1563
 
1559
1564
  var slot: u3 = 0;
@@ -1727,7 +1732,7 @@ pub const Effects = struct {
1727
1732
  state.bp *= 2;
1728
1733
  assert(state.bp <= 160);
1729
1734
  } else {
1730
- state.damage *|= 2;
1735
+ state.damage = smul(state.damage, 2);
1731
1736
  }
1732
1737
  }
1733
1738
 
@@ -2198,7 +2203,7 @@ pub const Effects = struct {
2198
2203
 
2199
2204
  pub fn pursuit(battle: anytype, player: Player, state: *State, _: anytype) !void {
2200
2205
  // TODO: remove switching variable and just look at Choice saved in Battle struct?
2201
- if (battle.foe(player).active.volatiles.switching) state.damage *|= 2;
2206
+ if (battle.foe(player).active.volatiles.switching) state.damage = smul(state.damage, 2);
2202
2207
  }
2203
2208
 
2204
2209
  pub const rage = struct {
@@ -2211,7 +2216,7 @@ pub const Effects = struct {
2211
2216
  const side = battle.side(player);
2212
2217
 
2213
2218
  assert(side.active.volatiles.Rage);
2214
- state.damage *|= (side.active.volatiles.rage +| 1);
2219
+ state.damage = smul(state.damage, (side.active.volatiles.rage +| 1)); // TODO: |+
2215
2220
  }
2216
2221
  };
2217
2222
 
@@ -2306,7 +2311,7 @@ pub const Effects = struct {
2306
2311
  side.active.volatiles.attacks += 1;
2307
2312
  side.active.volatiles.Rollout = side.active.volatiles.attacks != 5;
2308
2313
 
2309
- if (side.active.volatiles.DefenseCurl) state.damage *|= 2;
2314
+ if (side.active.volatiles.DefenseCurl) state.damage = smul(state.damage, 2);
2310
2315
  }
2311
2316
  };
2312
2317
 
@@ -2834,6 +2839,12 @@ fn convertible(active: ActivePokemon, m: Move) bool {
2834
2839
  return t != .@"???" and !active.types.includes(t);
2835
2840
  }
2836
2841
 
2842
+ // NOTE: llvm/llvm-project#58557
2843
+ fn smul(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
2844
+ if (!comptime builtin.target.cpu.arch.isWasm()) return a *| b;
2845
+ return @max(a *% b, std.math.maxInt(@TypeOf(a)));
2846
+ }
2847
+
2837
2848
  pub const Rolls = struct {
2838
2849
  fn speedTie(battle: anytype, options: anytype) !bool {
2839
2850
  const p1 = if (options.calc.overridden(.P1, 0, .speed_tie)) |player|
@@ -3070,7 +3081,7 @@ pub const Rolls = struct {
3070
3081
  }
3071
3082
 
3072
3083
  // Conversion 2 can at most choose between 7 types (vs. a Grass-type attack)
3073
- var CONVERSION_2: [7]Type = [_]Type{.@"???"} ** 7;
3084
+ var CONVERSION_2: [7]Type = @splat(.@"???");
3074
3085
 
3075
3086
  fn conversion2(battle: anytype, player: Player, mtype: Type, options: anytype) !Type {
3076
3087
  var i: u8 = 0;
@@ -3495,11 +3506,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3495
3506
  },
3496
3507
  .Switch => {
3497
3508
  const side = battle.side(player);
3498
- var slot: u4 = 2;
3509
+ var slot: usize = 2;
3499
3510
  while (slot <= 6) : (slot += 1) {
3500
3511
  const id = side.order[slot - 1];
3501
3512
  if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
3502
- out[n] = .{ .type = .Switch, .data = slot };
3513
+ out[n] = .{ .type = .Switch, .data = @intCast(slot) };
3503
3514
  n += 1;
3504
3515
  }
3505
3516
  if (n == 0) {
@@ -3529,11 +3540,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3529
3540
  return n;
3530
3541
  }
3531
3542
 
3532
- var slot: u4 = 2;
3543
+ var slot: usize = 2;
3533
3544
  while (!active.volatiles.Trapped and slot <= 6) : (slot += 1) {
3534
3545
  const id = side.order[slot - 1];
3535
3546
  if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
3536
- out[n] = .{ .type = .Switch, .data = slot };
3547
+ out[n] = .{ .type = .Switch, .data = @intCast(slot) };
3537
3548
  n += 1;
3538
3549
  }
3539
3550
 
@@ -3566,7 +3577,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3566
3577
  const struggle =
3567
3578
  m.id == .Bide and (m.pp == 0 or active.volatiles.disable.move == slot);
3568
3579
  const s = if (struggle) 0 else slot;
3569
- out[n] = .{ .type = .Move, .data = s };
3580
+ out[n] = .{ .type = .Move, .data = @intCast(s) };
3570
3581
  n += 1;
3571
3582
  return n;
3572
3583
  }
@@ -3580,7 +3591,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3580
3591
  if (m.id == .None) break;
3581
3592
  if (m.pp == 0) continue;
3582
3593
  if (active.volatiles.disable.move == slot) continue;
3583
- out[n] = .{ .type = .Move, .data = slot };
3594
+ out[n] = .{ .type = .Move, .data = @intCast(slot) };
3584
3595
  n += 1;
3585
3596
  }
3586
3597
  // Struggle (Pokémon Showdown would use 'move 1' here)
package/src/lib/napi.h ADDED
@@ -0,0 +1,2 @@
1
+ #define NAPI_VERSION 8
2
+ #include <node_api.h>