@pkmn/engine 0.1.0-dev.645b4750 → 0.1.0-dev.6b383c80

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 (53) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +22 -61
  3. package/build/pkg/addon/browser.js +2 -2
  4. package/build/pkg/addon/browser.js.map +1 -1
  5. package/build/pkg/addon/node.js +3 -3
  6. package/build/pkg/addon/node.js.map +1 -1
  7. package/build/pkg/addon/wasm.js.map +1 -1
  8. package/build/pkg/addon.d.ts +4 -4
  9. package/build/pkg/addon.js.map +1 -1
  10. package/build/pkg/data.js.map +1 -1
  11. package/build/pkg/gen1.js.map +1 -1
  12. package/build/pkg/protocol.d.ts +11 -2
  13. package/build/pkg/protocol.js.map +1 -1
  14. package/build/tools/debug.js +37 -3
  15. package/build/tools/debug.js.map +1 -1
  16. package/build/tools/display/debug.jsx +1 -87
  17. package/build/tools/display/debug.jsx.map +1 -1
  18. package/build/tools/display/ui.jsx +3 -2
  19. package/build/tools/display/ui.jsx.map +1 -1
  20. package/build/tools/display/util.js +125 -1
  21. package/build/tools/display/util.js.map +1 -1
  22. package/build.zig +224 -350
  23. package/package.json +22 -22
  24. package/src/bin/install-pkmn-engine +23 -22
  25. package/src/lib/bindings/c.zig +28 -82
  26. package/src/lib/bindings/node.zig +7 -11
  27. package/src/lib/bindings/wasm.zig +7 -15
  28. package/src/lib/common/array.zig +9 -16
  29. package/src/lib/common/data.zig +8 -8
  30. package/src/lib/common/debug.zig +46 -61
  31. package/src/lib/common/js.zig +3 -9
  32. package/src/lib/common/optional.zig +20 -25
  33. package/src/lib/common/protocol.zig +224 -189
  34. package/src/lib/common/rational.zig +91 -173
  35. package/src/lib/common/rng.zig +4 -4
  36. package/src/lib/common/util.zig +34 -46
  37. package/src/lib/gen1/calc.zig +128 -115
  38. package/src/lib/gen1/chance.zig +162 -101
  39. package/src/lib/gen1/data/types.zig +2 -2
  40. package/src/lib/gen1/data.zig +27 -24
  41. package/src/lib/gen1/helpers.zig +36 -26
  42. package/src/lib/gen1/mechanics.zig +42 -38
  43. package/src/lib/gen2/calc.zig +3 -6
  44. package/src/lib/gen2/chance.zig +55 -43
  45. package/src/lib/gen2/data/types.zig +6 -3
  46. package/src/lib/gen2/data.zig +20 -16
  47. package/src/lib/gen2/helpers.zig +27 -19
  48. package/src/lib/gen2/mechanics.zig +25 -36
  49. package/src/lib/napi.h +2 -0
  50. package/src/lib/node.zig +1 -5
  51. package/src/lib/pkmn.zig +91 -17
  52. package/src/lib/wasm.zig +2 -6
  53. package/src/tools/display/dom.ts +40 -25
@@ -21,15 +21,18 @@ pub const MAX_CHOICES: usize = 9; // move 1..4, switch 2..6
21
21
  /// The maximum number of bytes possibly logged by a single Generation I update.
22
22
  pub const MAX_LOGS: usize = 180;
23
23
 
24
+ const ReleaseSmall: std.builtin.OptimizeMode =
25
+ if (@hasDecl(std.builtin.OptimizeMode, "ReleaseSmall")) .small else .ReleaseSmall;
26
+
24
27
  /// The optimal size in bytes required to hold all Generation I choice options.
25
28
  /// At least as large as MAX_CHOICES.
26
- pub const CHOICES_SIZE = if (builtin.mode == .ReleaseSmall)
29
+ pub const CHOICES_SIZE = if (builtin.mode == ReleaseSmall)
27
30
  MAX_CHOICES
28
31
  else
29
32
  std.math.ceilPowerOfTwo(usize, MAX_CHOICES) catch unreachable;
30
33
  /// The optimal size in bytes required to hold the largest amount of log data possible from a
31
34
  /// single Generation I update. At least as large as MAX_LOGS.
32
- pub const LOGS_SIZE = if (builtin.mode == .ReleaseSmall)
35
+ pub const LOGS_SIZE = if (builtin.mode == ReleaseSmall)
33
36
  MAX_LOGS
34
37
  else
35
38
  std.math.ceilPowerOfTwo(usize, MAX_LOGS) catch unreachable;
@@ -62,7 +65,7 @@ pub fn Battle(comptime RNG: anytype) type {
62
65
  /// The last damage dealt by either side.
63
66
  last_damage: u16 = 0,
64
67
  /// Details about the last move selected/used by each player required to detect desyncs.
65
- last_moves: [2]MoveDetails = [_]MoveDetails{.{}} ** 2,
68
+ last_moves: [2]MoveDetails = @splat(.{}),
66
69
  /// The pseudo random number generator.
67
70
  rng: RNG,
68
71
 
@@ -122,12 +125,12 @@ test Battle {
122
125
  /// Representation of one side of a Generation I Pokémon battle.
123
126
  pub const Side = extern struct {
124
127
  /// The player's party in its original order
125
- pokemon: [6]Pokemon = [_]Pokemon{.{}} ** 6,
128
+ pokemon: [6]Pokemon = @splat(.{}),
126
129
  /// The active Pokémon for the side, zero initialized if the battle has yet to start.
127
130
  /// Note that fainted Pokémon are still consider "active" until their replacement switches in.
128
131
  active: ActivePokemon = .{},
129
132
  /// One-based slot indexes reflecting the current order of the player's party.
130
- order: [6]u8 = [_]u8{0} ** 6,
133
+ order: [6]u8 = @splat(0),
131
134
  /// The last move the player selected.
132
135
  last_selected_move: Move = .None,
133
136
  /// The last move the player used.
@@ -166,7 +169,7 @@ pub const ActivePokemon = extern struct {
166
169
  /// The active Pokémon's volatile statuses and associated data.
167
170
  volatiles: Volatiles = .{},
168
171
  /// The active Pokémon's current move slots.
169
- moves: [4]MoveSlot = [_]MoveSlot{.{}} ** 4,
172
+ moves: [4]MoveSlot = @splat(.{}),
170
173
 
171
174
  comptime {
172
175
  assert(@sizeOf(ActivePokemon) == 32);
@@ -186,7 +189,7 @@ pub const Pokemon = extern struct {
186
189
  /// The Pokémon's unmodified stats.
187
190
  stats: Stats(u16) = .{},
188
191
  /// The Pokémon's original stored move slots.
189
- moves: [4]MoveSlot = [_]MoveSlot{.{}} ** 4,
192
+ moves: [4]MoveSlot = @splat(.{}),
190
193
  /// The Pokémon's current HP.
191
194
  hp: u16 = 0,
192
195
  /// The Pokémon's current status.
@@ -313,12 +316,12 @@ test Status {
313
316
  try expect(Status.is(Status.slp(5), .SLP));
314
317
  try expect(!Status.is(Status.slp(5), .EXT));
315
318
  try expect(!Status.is(Status.slp(7), .PSN));
316
- try expectEqual(@as(u3, 5), Status.duration(Status.slp(5)));
319
+ try expectEqual(5, Status.duration(Status.slp(5)));
317
320
  try expect(Status.is(Status.slf(2), .SLP));
318
321
  try expect(Status.is(Status.slf(2), .EXT));
319
322
  try expect(Status.is(Status.TOX, .PSN));
320
323
  try expect(Status.is(Status.TOX, .EXT));
321
- try expectEqual(@as(u3, 1), Status.duration(Status.slp(1)));
324
+ try expectEqual(1, Status.duration(Status.slp(1)));
322
325
  }
323
326
 
324
327
  /// Bitfield representation of volatile statuses and associated data in Generation I.
@@ -402,13 +405,13 @@ test Volatiles {
402
405
  try expect(!volatiles.Transform);
403
406
  try expect(!volatiles.MultiHit);
404
407
 
405
- try expectEqual(@as(u16, 235), volatiles.state);
406
- try expectEqual(@as(u8, 42), volatiles.substitute);
407
- try expectEqual(@as(u4, 2), volatiles.disable_move);
408
- try expectEqual(@as(u4, 4), volatiles.disable_duration);
409
- try expectEqual(@as(u3, 2), volatiles.confusion);
410
- try expectEqual(@as(u5, 4), volatiles.toxic);
411
- try expectEqual(@as(u3, 3), volatiles.attacks);
408
+ try expectEqual(235, volatiles.state);
409
+ try expectEqual(42, volatiles.substitute);
410
+ try expectEqual(2, volatiles.disable_move);
411
+ try expectEqual(4, volatiles.disable_duration);
412
+ try expectEqual(2, volatiles.confusion);
413
+ try expectEqual(4, volatiles.toxic);
414
+ try expectEqual(3, volatiles.attacks);
412
415
  }
413
416
 
414
417
  /// Representation of a Pokémon's stats in Generation I.
@@ -439,9 +442,9 @@ test Stats {
439
442
  try expectEqual(0, stats.def);
440
443
 
441
444
  var base = Species.get(.Gyarados).stats;
442
- try expectEqual(@as(u16, 127), Stats(u16).calc("hp", base.hp, 0, 5120, 38));
445
+ try expectEqual(127, Stats(u16).calc("hp", base.hp, 0, 5120, 38));
443
446
  base = Species.get(.Pidgeot).stats;
444
- try expectEqual(@as(u16, 279), Stats(u16).calc("spe", base.spe, 15, 63001, 100));
447
+ try expectEqual(279, Stats(u16).calc("spe", base.spe, 15, 63001, 100));
445
448
  }
446
449
 
447
450
  /// Representation of a Pokémon's boosts in Generation I.
@@ -534,7 +537,7 @@ pub const Species = species.Species;
534
537
 
535
538
  test Species {
536
539
  try expectEqual(2, @intFromEnum(Species.Ivysaur));
537
- try expectEqual(@as(u8, 100), Species.get(.Mew).stats.def);
540
+ try expectEqual(100, Species.get(.Mew).stats.def);
538
541
  }
539
542
 
540
543
  /// Representation of a Generation I type in Pokémon.
@@ -573,8 +576,8 @@ test Types {
573
576
  try expectEqual(Effectiveness.Resisted, Type.effectiveness(.Fire, .Water));
574
577
  try expectEqual(Effectiveness.Neutral, Type.effectiveness(.Normal, .Grass));
575
578
 
576
- try expectEqual(@as(u8, 3), Type.precedence(.Water, .Water));
577
- try expectEqual(@as(u8, 26), Type.precedence(.Bug, .Ghost));
579
+ try expectEqual(3, Type.precedence(.Water, .Water));
580
+ try expectEqual(26, Type.precedence(.Bug, .Ghost));
578
581
  try expect(Type.precedence(.Poison, .Bug) > Type.precedence(.Poison, .Poison));
579
582
 
580
583
  const t: Types = .{ .type1 = .Rock, .type2 = .Ground };
@@ -614,18 +617,18 @@ pub const DVs = struct {
614
617
 
615
618
  test DVs {
616
619
  var dvs: DVs = .{ .spc = 15, .spe = 15 };
617
- try expectEqual(@as(u4, 15), dvs.hp());
620
+ try expectEqual(15, dvs.hp());
618
621
  dvs = .{
619
622
  .atk = 5,
620
623
  .def = 15,
621
624
  .spe = 13,
622
625
  .spc = 13,
623
626
  };
624
- try expectEqual(@as(u4, 15), dvs.hp());
627
+ try expectEqual(15, dvs.hp());
625
628
  dvs = .{
626
629
  .def = 3,
627
630
  .spe = 10,
628
631
  .spc = 11,
629
632
  };
630
- try expectEqual(@as(u4, 13), dvs.hp());
633
+ try expectEqual(13, dvs.hp());
631
634
  }
@@ -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,15 +161,20 @@ 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| {
167
- const hp = comptime std.mem.eql(u8, field.name, "hp");
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");
168
171
  const spc =
169
- comptime std.mem.eql(u8, field.name, "spa") or std.mem.eql(u8, field.name, "spd");
170
- @field(pokemon.stats, field.name) = Stats(u16).calc(
171
- field.name,
172
- @field(species.stats, field.name),
173
- if (hp) p.dvs.hp() else if (spc) p.dvs.spc else @field(p.dvs, field.name),
174
- @field(p.stats, field.name),
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),
175
178
  p.level,
176
179
  );
177
180
  }
@@ -199,20 +202,25 @@ pub const Pokemon = struct {
199
202
  const lvl = if (rand.chance(u8, 1, 20)) rand.range(u8, 1, 99 + 1) else 100;
200
203
  var stats: Stats(u16) = .{};
201
204
  const dvs = DVs.random(rand);
202
- inline for (@field(@typeInfo(@TypeOf(stats)), @tagName(Struct)).fields) |field| {
203
- @field(stats, field.name) = Stats(u16).calc(
204
- field.name,
205
- @field(species.stats, field.name),
206
- if (comptime std.mem.eql(u8, field.name, "hp"))
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"))
207
215
  dvs.hp()
208
216
  else
209
- @field(dvs, field.name),
217
+ @field(dvs, field_name),
210
218
  if (rand.chance(u8, 1, 20)) rand.range(u16, 0, EXP + 1) else EXP,
211
219
  lvl,
212
220
  );
213
221
  }
214
222
 
215
- var ms = [_]MoveSlot{.{}} ** 4;
223
+ var ms: [4]MoveSlot = @splat(.{});
216
224
  const n = if (rand.chance(u8, 1, 100)) rand.range(u4, 1, 3 + 1) else 4;
217
225
  for (0..n) |i| {
218
226
  var m: Move = .None;
@@ -281,9 +289,12 @@ pub fn swtch(slot: u4) Choice {
281
289
  /// Generation I battles.
282
290
  pub const MAX_DIFFS: usize = 120;
283
291
 
292
+ const ReleaseSmall: std.builtin.OptimizeMode =
293
+ if (@hasDecl(std.builtin.OptimizeMode, "ReleaseSmall")) .small else .ReleaseSmall;
294
+
284
295
  /// The optimal size in bytes required to hold the diff between two Generation I battles.
285
296
  /// At least as large as MAX_DIFFS.
286
- pub const DIFFS_SIZE = if (builtin.mode == .ReleaseSmall)
297
+ pub const DIFFS_SIZE = if (builtin.mode == ReleaseSmall)
287
298
  MAX_DIFFS
288
299
  else
289
300
  std.math.ceilPowerOfTwo(usize, MAX_DIFFS) catch unreachable;
@@ -292,23 +303,23 @@ else
292
303
  pub fn diff(
293
304
  a: *const data.Battle(data.PRNG),
294
305
  b: *const data.Battle(data.PRNG),
295
- w: protocol.ByteStream.Writer,
306
+ w: *protocol.Writer,
296
307
  ) !usize {
297
- const pos = w.stream.pos;
308
+ const pos = w.pos;
298
309
  const size = @sizeOf(data.Battle(data.PRNG)) - @sizeOf(data.PRNG);
299
310
  assert(size % 2 == 0);
300
311
  assert(size / 2 < 255);
301
312
 
302
313
  var i: usize = 0;
303
314
  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]).*) {
315
+ const val = std.mem.bytesAsValue(u16, std.mem.asBytes(a)[i .. i + 2]).*;
316
+ if (val != std.mem.bytesAsValue(u16, std.mem.asBytes(b)[i .. i + 2]).*) {
306
317
  try w.writeByte(@intCast(i / 2));
307
318
  try w.writeInt(u16, val, endian);
308
319
  }
309
320
  }
310
321
 
311
- return w.stream.pos - pos;
322
+ return w.pos - pos;
312
323
  }
313
324
 
314
325
  /// TODO
@@ -316,8 +327,7 @@ pub fn patch(battle: *data.Battle(data.PRNG), buf: []u8) void {
316
327
  var i: usize = 0;
317
328
  while (i < buf.len) : (i += 3) {
318
329
  const off = @as(u16, @intCast(buf[i])) * 2;
319
- const val = std.mem.readInt(u16, @as(*const [2]u8, @ptrCast(buf[i + 1 .. i + 3])), endian);
320
- const bytes: *[2]u8 = @ptrCast(std.mem.asBytes(battle)[off .. off + 2]);
321
- std.mem.writeInt(u16, bytes, val, endian);
330
+ const val = std.mem.readInt(u16, @ptrCast(buf[i + 1 .. i + 3]), endian);
331
+ std.mem.writeInt(u16, @ptrCast(std.mem.asBytes(battle)[off .. off + 2]), val, endian);
322
332
  }
323
333
  }
@@ -1,6 +1,5 @@
1
1
  const DEBUG = @import("../common/debug.zig").print;
2
2
 
3
- const builtin = @import("builtin");
4
3
  const chance = @import("chance.zig");
5
4
  const common = @import("../common/data.zig");
6
5
  const data = @import("data.zig");
@@ -84,11 +83,11 @@ fn start(battle: anytype, options: anytype) !Result {
84
83
 
85
84
  const p1_slot = findFirstAlive(p1);
86
85
  assert(!showdown or p1_slot == 1);
87
- if (p1_slot == 0) return if (findFirstAlive(p2) == 0) Result.Tie else Result.Lose;
86
+ if (p1_slot == 0) return if (findFirstAlive(p2) == 0) .Tie else .Lose;
88
87
 
89
88
  const p2_slot = findFirstAlive(p2);
90
89
  assert(!showdown or p2_slot == 1);
91
- if (p2_slot == 0) return Result.Win;
90
+ if (p2_slot == 0) return .Win;
92
91
 
93
92
  try switchIn(battle, .P1, p1_slot, true, options);
94
93
  try switchIn(battle, .P2, p2_slot, true, options);
@@ -167,7 +166,7 @@ fn selectMove(
167
166
  const last = side.active.move(battle.lastMove(player).index);
168
167
  if (last.id == .Metronome) side.last_selected_move = last.id;
169
168
  if (last.id == .MirrorMove) {
170
- if (!pkmn.options.mod) return Result.Error;
169
+ if (!pkmn.options.mod) return .Error;
171
170
  side.last_selected_move = last.id;
172
171
  }
173
172
  }
@@ -432,7 +431,7 @@ fn executeMove(
432
431
  // technically be from this Pokémon because it must be exactly 1 to not desync and
433
432
  // every Pokémon must have at least one move
434
433
  if (mslot != 1 or side.active.move(mslot).id != side.last_selected_move) {
435
- return Result.Error;
434
+ return .Error;
436
435
  } else {
437
436
  auto = true;
438
437
  }
@@ -450,7 +449,7 @@ fn executeMove(
450
449
  .skip_can => skip_can = true,
451
450
  .skip_pp => skip_pp = true,
452
451
  .ok => {},
453
- .err => return @as(?Result, Result.Error),
452
+ .err => return .Error,
454
453
  }
455
454
 
456
455
  // Pokémon Showdown incorrectly implements PP deduction when handling the Hyper Beam automatic
@@ -878,7 +877,7 @@ fn doMove(
878
877
  var miss = showdown and miss: {
879
878
  immune = move.target != .Self and !status and !counter and
880
879
  (@intFromEnum(move.type.effectiveness(foe.active.types.type1)) == 0 or
881
- @intFromEnum(move.type.effectiveness(foe.active.types.type2)) == 0);
880
+ @intFromEnum(move.type.effectiveness(foe.active.types.type2)) == 0);
882
881
  if (immune and move.effect != .Binding) break :miss true;
883
882
  if (move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe) {
884
883
  battle.last_damage = 0;
@@ -916,7 +915,7 @@ fn doMove(
916
915
  if (showdown) break :damage; // skip adjustDamage / randomizeDamage
917
916
  } else if (!calcDamage(battle, player, player.foe(), move, crit, options)) {
918
917
  if (!showdown) try options.chance.commit(player, .err);
919
- return @as(?Result, Result.Error);
918
+ return .Error;
920
919
  }
921
920
  if (battle.last_damage == 0) {
922
921
  immune = true;
@@ -1019,7 +1018,7 @@ fn doMove(
1019
1018
  const sub = showdown and foe.active.volatiles.Substitute;
1020
1019
 
1021
1020
  var nullified = false;
1022
- var hit: u4 = 0;
1021
+ var hit: usize = 0;
1023
1022
  while (hit < hits) {
1024
1023
  if (hit == 0) {
1025
1024
  if (crit) try log.crit(.{battle.active(player.foe())});
@@ -1047,7 +1046,7 @@ fn doMove(
1047
1046
  if (showdown and move.effect == .Twineedle and !nullified and foe.stored().hp > 0) {
1048
1047
  try Effects.poison(battle, player, Move.get(.PoisonSting), options);
1049
1048
  }
1050
- try log.hitcount(.{ battle.active(player.foe()), hit });
1049
+ try log.hitcount(.{ battle.active(player.foe()), @as(u4, @intCast(hit)) });
1051
1050
  } else if (showdown and move.effect == .Binding) {
1052
1051
  // This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
1053
1052
  try Effects.binding(battle, player, rewrap, options);
@@ -1248,13 +1247,13 @@ fn specialDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
1248
1247
  .Psywave => power: {
1249
1248
  const max: u8 = @intCast(@as(u16, side.stored().level) * 3 / 2);
1250
1249
  // GLITCH: Psywave infinite glitch loop
1251
- if (!pkmn.options.mod and max <= 1) return Result.Error;
1250
+ if (!pkmn.options.mod and max <= 1) return .Error;
1252
1251
  break :power try Rolls.psywave(battle, player, max, options);
1253
1252
  },
1254
1253
  else => unreachable,
1255
1254
  };
1256
1255
 
1257
- if (battle.last_damage == 0) return if (pkmn.options.mod) null else Result.Error;
1256
+ if (battle.last_damage == 0) return if (pkmn.options.mod) null else .Error;
1258
1257
 
1259
1258
  const sub = showdown and foe.active.volatiles.Substitute;
1260
1259
  _ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
@@ -1274,15 +1273,15 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
1274
1273
  // Pretend Splash was used as a stand-in when no move has been used to fail below with 0 BP
1275
1274
  const foe_last_selected_move =
1276
1275
  Move.get(if (foe.last_selected_move == .None or foe.last_selected_move == .SKIP_TURN)
1277
- .Splash
1278
- else
1279
- foe.last_selected_move);
1276
+ .Splash
1277
+ else
1278
+ foe.last_selected_move);
1280
1279
 
1281
1280
  const used = battle.lastMove(player.foe()).counterable != 0;
1282
1281
  const selected = foe_last_selected_move.bp > 0 and
1283
1282
  foe.last_selected_move != .Counter and
1284
1283
  (foe_last_selected_move.type == .Normal or
1285
- foe_last_selected_move.type == .Fighting);
1284
+ foe_last_selected_move.type == .Fighting);
1286
1285
 
1287
1286
  if (!used and !selected) {
1288
1287
  try options.log.fail(.{ battle.active(player), .None });
@@ -1291,16 +1290,12 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
1291
1290
 
1292
1291
  if (!used or !selected) {
1293
1292
  // GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
1294
- if (!pkmn.options.mod) return Result.Error;
1293
+ if (!pkmn.options.mod) return .Error;
1295
1294
  try options.log.fail(.{ battle.active(player), .None });
1296
1295
  return null;
1297
1296
  }
1298
1297
 
1299
- // NOTE: llvm/llvm-project#58557
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;
1298
+ battle.last_damage *|= 2;
1304
1299
 
1305
1300
  // Pokémon Showdown calls moveHit before Counter
1306
1301
  if (!showdown and !try checkHit(battle, player, move, options)) return null;
@@ -1446,7 +1441,16 @@ fn moveHit(
1446
1441
  break :miss true;
1447
1442
  }
1448
1443
  if (!showdown) {
1449
- if (move.effect == .Swift) return true;
1444
+ if (move.effect == .Swift) {
1445
+ // If Swift could cause a division-by-zero freeze (which doesn't exist on Pokémon
1446
+ // Showdown, hence why the block above doesn't include this code), we need the
1447
+ // damage roll to be committed. However, the commit code will not save this roll if
1448
+ // the glitch occurs and the move was not marked as having hit. To work around this
1449
+ // we flag that the move hit (as Swift trivially does), but pass the probability of
1450
+ // the hit as zero to ensure it doesn't actually get saved.
1451
+ try options.chance.hit(player, true, 0);
1452
+ return true;
1453
+ }
1450
1454
  if (foe.active.volatiles.Invulnerable) break :miss true;
1451
1455
  }
1452
1456
 
@@ -1519,13 +1523,13 @@ fn checkFaint(
1519
1523
 
1520
1524
  if (player_out and foe_out) {
1521
1525
  try options.log.tie(.{});
1522
- return Result.Tie;
1526
+ return .Tie;
1523
1527
  } else if (player_out) {
1524
1528
  try options.log.win(.{player.foe()});
1525
- return if (player == .P1) Result.Lose else Result.Win;
1529
+ return if (player == .P1) .Lose else .Win;
1526
1530
  } else if (foe_out) {
1527
1531
  try options.log.win(.{player});
1528
- return if (player == .P1) Result.Win else Result.Lose;
1532
+ return if (player == .P1) .Win else .Lose;
1529
1533
  }
1530
1534
 
1531
1535
  const foe_choice: Choice.Type = if (foe_fainted) .Switch else .Pass;
@@ -1543,7 +1547,7 @@ fn faint(battle: anytype, player: Player, done: bool, cap: bool, options: anytyp
1543
1547
  if (foe_volatiles.Bide) {
1544
1548
  assert(!foe_volatiles.Thrashing and !foe_volatiles.Rage);
1545
1549
  foe_volatiles.state = if (pkmn.options.mod) 0 else foe_volatiles.state & 255;
1546
- if (foe_volatiles.state != 0) return Result.Error;
1550
+ if (foe_volatiles.state != 0) return .Error;
1547
1551
  }
1548
1552
 
1549
1553
  // Clearing these is not strictly necessary as provided the battle hasn't ended the side that
@@ -1635,19 +1639,19 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
1635
1639
 
1636
1640
  if (pkmn.options.mod and pkmn.options.ebc and checkEBC(battle)) {
1637
1641
  try options.log.tie(.{});
1638
- return Result.Tie;
1642
+ return .Tie;
1639
1643
  }
1640
1644
 
1641
1645
  if (pkmn.options.mod and battle.turn >= 1000) {
1642
1646
  try options.log.tie(.{});
1643
- return Result.Tie;
1647
+ return .Tie;
1644
1648
  } else if (battle.turn >= 65535) {
1645
- return Result.Error;
1649
+ return .Error;
1646
1650
  }
1647
1651
 
1648
1652
  try options.log.turn(.{battle.turn});
1649
1653
 
1650
- return Result.Default;
1654
+ return .Default;
1651
1655
  }
1652
1656
 
1653
1657
  fn checkEBC(battle: anytype) bool {
@@ -1938,7 +1942,7 @@ pub const Effects = struct {
1938
1942
  return null;
1939
1943
  } else if (err) {
1940
1944
  // GLITCH: Transform + Mirror Move / Metronome PP softlock
1941
- return Result.Error;
1945
+ return .Error;
1942
1946
  }
1943
1947
 
1944
1948
  volatiles.disable_move =
@@ -3136,11 +3140,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3136
3140
  },
3137
3141
  .Switch => {
3138
3142
  const side = battle.side(player);
3139
- var slot: u4 = 2;
3143
+ var slot: usize = 2;
3140
3144
  while (slot <= 6) : (slot += 1) {
3141
3145
  const id = side.order[slot - 1];
3142
3146
  if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
3143
- out[n] = .{ .type = .Switch, .data = slot };
3147
+ out[n] = .{ .type = .Switch, .data = @intCast(slot) };
3144
3148
  n += 1;
3145
3149
  }
3146
3150
  if (n == 0) {
@@ -3171,11 +3175,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3171
3175
  return n;
3172
3176
  }
3173
3177
 
3174
- var slot: u4 = 2;
3178
+ var slot: usize = 2;
3175
3179
  while (slot <= 6) : (slot += 1) {
3176
3180
  const id = side.order[slot - 1];
3177
3181
  if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
3178
- out[n] = .{ .type = .Switch, .data = slot };
3182
+ out[n] = .{ .type = .Switch, .data = @intCast(slot) };
3179
3183
  n += 1;
3180
3184
  }
3181
3185
 
@@ -3208,7 +3212,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3208
3212
  const struggle =
3209
3213
  m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
3210
3214
  const s = if (struggle) 0 else slot;
3211
- out[n] = .{ .type = .Move, .data = s };
3215
+ out[n] = .{ .type = .Move, .data = @intCast(s) };
3212
3216
  n += 1;
3213
3217
  return n;
3214
3218
  }
@@ -3222,7 +3226,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3222
3226
  if (m.id == .None) break;
3223
3227
  if (m.pp == 0) continue;
3224
3228
  if (active.volatiles.disable_move == slot) continue;
3225
- out[n] = .{ .type = .Move, .data = slot };
3229
+ out[n] = .{ .type = .Move, .data = @intCast(slot) };
3226
3230
  n += 1;
3227
3231
  }
3228
3232
  // 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 => util.FieldType(Action, field),
148
+ else => @FieldType(Action, @tagName(field)),
152
149
  });
153
150
  }