@pkmn/engine 0.1.0-dev.a0447192 → 0.1.0-dev.a15b6c17

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 (73) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +22 -12
  3. package/build/pkg/addon/browser.d.ts +1 -1
  4. package/build/pkg/addon/browser.js +6 -2
  5. package/build/pkg/addon/browser.js.map +1 -1
  6. package/build/pkg/addon/node.js +22 -8
  7. package/build/pkg/addon/node.js.map +1 -1
  8. package/build/pkg/addon/wasm.d.ts +2 -0
  9. package/build/pkg/addon/wasm.js +53 -0
  10. package/build/pkg/addon/wasm.js.map +1 -0
  11. package/build/pkg/addon.d.ts +25 -5
  12. package/build/pkg/addon.js +8 -17
  13. package/build/pkg/addon.js.map +1 -1
  14. package/build/pkg/data/layout.json +15 -2
  15. package/build/pkg/data/protocol.json +23 -9
  16. package/build/pkg/data.js.map +1 -1
  17. package/build/pkg/gen1.d.ts +15 -18
  18. package/build/pkg/gen1.js +28 -19
  19. package/build/pkg/gen1.js.map +1 -1
  20. package/build/pkg/index.d.ts +37 -100
  21. package/build/pkg/index.js +23 -75
  22. package/build/pkg/index.js.map +1 -1
  23. package/build/pkg/protocol.d.ts +11 -2
  24. package/build/pkg/protocol.js.map +1 -1
  25. package/build/tools/debug.js +55 -12
  26. package/build/tools/debug.js.map +1 -1
  27. package/build/tools/display/debug.jsx +19 -94
  28. package/build/tools/display/debug.jsx.map +1 -1
  29. package/build/tools/display/index.js +35 -15
  30. package/build/tools/display/index.js.map +1 -1
  31. package/build/tools/display/ui.jsx +20 -9
  32. package/build/tools/display/ui.jsx.map +1 -1
  33. package/build/tools/display/util.js +140 -1
  34. package/build/tools/display/util.js.map +1 -1
  35. package/build.zig +239 -217
  36. package/package.json +34 -130
  37. package/src/bin/install-pkmn-engine +25 -9
  38. package/src/lib/bindings/c.zig +200 -0
  39. package/src/lib/bindings/node.zig +117 -0
  40. package/src/lib/bindings/wasm.zig +50 -0
  41. package/src/lib/common/array.zig +5 -12
  42. package/src/lib/common/debug.zig +33 -59
  43. package/src/lib/common/js.zig +3 -9
  44. package/src/lib/common/optional.zig +11 -21
  45. package/src/lib/common/options.zig +2 -0
  46. package/src/lib/common/protocol.zig +228 -197
  47. package/src/lib/common/rational.zig +275 -284
  48. package/src/lib/common/rng.zig +1 -3
  49. package/src/lib/common/util.zig +14 -31
  50. package/src/lib/gen1/calc.zig +159 -163
  51. package/src/lib/gen1/chance.zig +146 -93
  52. package/src/lib/gen1/data/moves.zig +2 -4
  53. package/src/lib/gen1/data/species.zig +1 -3
  54. package/src/lib/gen1/data/types.zig +3 -4
  55. package/src/lib/gen1/data.zig +12 -16
  56. package/src/lib/gen1/helpers.zig +12 -20
  57. package/src/lib/gen1/mechanics.zig +103 -78
  58. package/src/lib/gen2/calc.zig +12 -23
  59. package/src/lib/gen2/chance.zig +12 -18
  60. package/src/lib/gen2/data/items.zig +1 -3
  61. package/src/lib/gen2/data/moves.zig +2 -4
  62. package/src/lib/gen2/data/species.zig +1 -3
  63. package/src/lib/gen2/data/types.zig +7 -5
  64. package/src/lib/gen2/data.zig +19 -22
  65. package/src/lib/gen2/generated.zig +18 -23
  66. package/src/lib/gen2/helpers.zig +9 -18
  67. package/src/lib/gen2/mechanics.zig +58 -55
  68. package/src/lib/napi.h +2 -0
  69. package/src/lib/node.zig +4 -121
  70. package/src/lib/pkmn.zig +98 -19
  71. package/src/lib/wasm.zig +9 -9
  72. package/src/tools/display/dom.ts +43 -31
  73. package/src/tools/display/ui.css +0 -5
@@ -1,36 +1,29 @@
1
- const std = @import("std");
2
-
3
- const pkmn = @import("../pkmn.zig");
1
+ const DEBUG = @import("../common/debug.zig").print;
4
2
 
3
+ const builtin = @import("builtin");
4
+ const chance = @import("chance.zig");
5
5
  const common = @import("../common/data.zig");
6
- const DEBUG = @import("../common/debug.zig").print;
6
+ const data = @import("data.zig");
7
+ const pkmn = @import("../pkmn.zig");
7
8
  const protocol = @import("../common/protocol.zig");
8
9
  const rng = @import("../common/rng.zig");
9
-
10
- const data = @import("data.zig");
11
- const chance = @import("chance.zig");
10
+ const std = @import("std");
12
11
 
13
12
  const assert = std.debug.assert;
14
-
15
- const expectEqual = std.testing.expectEqual;
16
-
17
- const showdown = pkmn.options.showdown;
18
-
19
- const Choice = common.Choice;
20
- const ID = common.ID;
21
- const Player = common.Player;
22
- const Result = common.Result;
23
-
24
13
  const Boost = protocol.Boost;
14
+ const Choice = common.Choice;
25
15
  const Damage = protocol.Damage;
16
+ const Effectiveness = data.Effectiveness;
17
+ const expectEqual = std.testing.expectEqual;
26
18
  const Fail = protocol.Fail;
27
- const Heal = protocol.Heal;
28
-
29
19
  const Gen12 = rng.Gen12;
30
-
31
- const Effectiveness = data.Effectiveness;
20
+ const Heal = protocol.Heal;
21
+ const ID = common.ID;
32
22
  const Move = data.Move;
33
23
  const MoveSlot = data.MoveSlot;
24
+ const Player = common.Player;
25
+ const Result = common.Result;
26
+ const showdown = pkmn.options.showdown;
34
27
  const Side = data.Side;
35
28
  const Species = data.Species;
36
29
  const Stats = data.Stats;
@@ -91,11 +84,11 @@ fn start(battle: anytype, options: anytype) !Result {
91
84
 
92
85
  const p1_slot = findFirstAlive(p1);
93
86
  assert(!showdown or p1_slot == 1);
94
- if (p1_slot == 0) return if (findFirstAlive(p2) == 0) Result.Tie else Result.Lose;
87
+ if (p1_slot == 0) return if (findFirstAlive(p2) == 0) .Tie else .Lose;
95
88
 
96
89
  const p2_slot = findFirstAlive(p2);
97
90
  assert(!showdown or p2_slot == 1);
98
- if (p2_slot == 0) return Result.Win;
91
+ if (p2_slot == 0) return .Win;
99
92
 
100
93
  try switchIn(battle, .P1, p1_slot, true, options);
101
94
  try switchIn(battle, .P2, p2_slot, true, options);
@@ -173,7 +166,10 @@ fn selectMove(
173
166
  if (foe_choice.type == .Switch) {
174
167
  const last = side.active.move(battle.lastMove(player).index);
175
168
  if (last.id == .Metronome) side.last_selected_move = last.id;
176
- if (last.id == .MirrorMove) return Result.Error;
169
+ if (last.id == .MirrorMove) {
170
+ if (!pkmn.options.mod) return .Error;
171
+ side.last_selected_move = last.id;
172
+ }
177
173
  }
178
174
  }
179
175
  return null;
@@ -424,7 +420,7 @@ fn executeMove(
424
420
  mslot = @intCast(battle.lastMove(player).index);
425
421
  const stored = side.stored();
426
422
  // GLITCH: Struggle bypass PP underflow via Hyper Beam / Trapping-switch auto selection
427
- auto = isForced(&side.active) or
423
+ auto = pkmn.options.mod or isForced(&side.active) or
428
424
  side.active.volatiles.Binding or side.active.volatiles.Bide or
429
425
  side.last_selected_move == .HyperBeam or
430
426
  Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP);
@@ -436,7 +432,7 @@ fn executeMove(
436
432
  // technically be from this Pokémon because it must be exactly 1 to not desync and
437
433
  // every Pokémon must have at least one move
438
434
  if (mslot != 1 or side.active.move(mslot).id != side.last_selected_move) {
439
- return Result.Error;
435
+ return .Error;
440
436
  } else {
441
437
  auto = true;
442
438
  }
@@ -454,7 +450,26 @@ fn executeMove(
454
450
  .skip_can => skip_can = true,
455
451
  .skip_pp => skip_pp = true,
456
452
  .ok => {},
457
- .err => return @as(?Result, Result.Error),
453
+ .err => return .Error,
454
+ }
455
+
456
+ // Pokémon Showdown incorrectly implements PP deduction when handling the Hyper Beam automatic
457
+ // selection glitch - if the move was proced due to Metronome / Mirror Move it either subtracts
458
+ // from the Hyper Beam slot if present or skips PP deduction entirely...
459
+ if (showdown and !skip_pp and side.last_selected_move == .HyperBeam and
460
+ side.last_selected_move != side.active.move(mslot).id)
461
+ {
462
+ assert(mslot == 1);
463
+ const has_beam = has_beam: {
464
+ for (side.active.moves, 0..) |m, i| {
465
+ if (m.id == .HyperBeam) {
466
+ mslot = @intCast(i + 1);
467
+ break :has_beam true;
468
+ }
469
+ }
470
+ break :has_beam false;
471
+ };
472
+ if (!has_beam) skip_pp = true;
458
473
  }
459
474
 
460
475
  const can = skip_can or
@@ -554,7 +569,7 @@ fn beforeMove(
554
569
  // This can only happen if a Pokémon started the battle frozen/sleeping and was thawed/woken
555
570
  // before the side had a selected a move - we simply need to assume this leads to a desync
556
571
  if (side.last_selected_move == .None) {
557
- assert(!showdown);
572
+ assert(!pkmn.options.mod);
558
573
  return .err;
559
574
  }
560
575
 
@@ -603,17 +618,10 @@ fn beforeMove(
603
618
  return .err;
604
619
  }
605
620
  }
606
- // Pokémon Showdown incorrectly changes the "target" of the confusion self-hit based
607
- // on the targeting behavior of the confused Pokémon's selected move which results
608
- // in the wrong behavior with respect to the Substitute + Confusion glitch
609
- const target = if (showdown and Move.get(side.last_selected_move).target == .Self)
610
- player
611
- else
612
- player.foe();
613
621
 
614
622
  const uncapped = battle.last_damage;
615
623
  // Skipping adjustDamage / randomizeDamage / checkHit
616
- _ = try applyDamage(battle, player, target, .Confusion, options);
624
+ _ = try applyDamage(battle, player, player.foe(), .Confusion, options);
617
625
  // Pokémon Showdown thinks that confusion damage is uncapped ¯\_(ツ)_/¯
618
626
  if (showdown) battle.last_damage = uncapped;
619
627
 
@@ -773,9 +781,14 @@ fn canMove(
773
781
 
774
782
  if (move.effect == .Thrashing) {
775
783
  Effects.thrashing(battle, player, options);
776
- } else if (!showdown and move.effect == .Binding) {
777
- // Pokémon Showdown handles this after hit/miss checks and damage calculation
778
- try Effects.binding(battle, player, false, options);
784
+ } else if (move.effect == .Binding) {
785
+ // Pokémon Showdown handles this after hit/miss checks and damage calculation, though clears
786
+ // Recharging in onTryMove (required for the Hyper Beam automatic selection glitch)
787
+ if (showdown) {
788
+ battle.foe(player).active.volatiles.Recharging = false;
789
+ } else {
790
+ try Effects.binding(battle, player, false, options);
791
+ }
779
792
  }
780
793
 
781
794
  return true;
@@ -865,7 +878,7 @@ fn doMove(
865
878
  var miss = showdown and miss: {
866
879
  immune = move.target != .Self and !status and !counter and
867
880
  (@intFromEnum(move.type.effectiveness(foe.active.types.type1)) == 0 or
868
- @intFromEnum(move.type.effectiveness(foe.active.types.type2)) == 0);
881
+ @intFromEnum(move.type.effectiveness(foe.active.types.type2)) == 0);
869
882
  if (immune and move.effect != .Binding) break :miss true;
870
883
  if (move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe) {
871
884
  battle.last_damage = 0;
@@ -903,7 +916,7 @@ fn doMove(
903
916
  if (showdown) break :damage; // skip adjustDamage / randomizeDamage
904
917
  } else if (!calcDamage(battle, player, player.foe(), move, crit, options)) {
905
918
  if (!showdown) try options.chance.commit(player, .err);
906
- return @as(?Result, Result.Error);
919
+ return .Error;
907
920
  }
908
921
  if (battle.last_damage == 0) {
909
922
  immune = true;
@@ -1006,7 +1019,7 @@ fn doMove(
1006
1019
  const sub = showdown and foe.active.volatiles.Substitute;
1007
1020
 
1008
1021
  var nullified = false;
1009
- var hit: u4 = 0;
1022
+ var hit: usize = 0;
1010
1023
  while (hit < hits) {
1011
1024
  if (hit == 0) {
1012
1025
  if (crit) try log.crit(.{battle.active(player.foe())});
@@ -1034,7 +1047,7 @@ fn doMove(
1034
1047
  if (showdown and move.effect == .Twineedle and !nullified and foe.stored().hp > 0) {
1035
1048
  try Effects.poison(battle, player, Move.get(.PoisonSting), options);
1036
1049
  }
1037
- try log.hitcount(.{ battle.active(player.foe()), hit });
1050
+ try log.hitcount(.{ battle.active(player.foe()), @as(u4, @intCast(hit)) });
1038
1051
  } else if (showdown and move.effect == .Binding) {
1039
1052
  // This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
1040
1053
  try Effects.binding(battle, player, rewrap, options);
@@ -1129,7 +1142,7 @@ fn calcDamage(
1129
1142
  if ((!showdown or !cfz) and (atk > 255 or def > 255)) {
1130
1143
  atk = @max((atk / 4) & 255, 1);
1131
1144
  // GLITCH: not adjusted to be a min of 1 on cartridge (can lead to division-by-zero freeze)
1132
- def = @max((def / 4) & 255, if (showdown) 1 else 0);
1145
+ def = @max((def / 4) & 255, if (pkmn.options.mod) 1 else 0);
1133
1146
  }
1134
1147
 
1135
1148
  const lvl: u32 = side.stored().level * @as(u2, if (crit) 2 else 1);
@@ -1150,7 +1163,7 @@ fn calcDamage(
1150
1163
  // encountered so we need to make sure that in the event of a miss we commit the status of the
1151
1164
  // crit roll. The alternative to doing this extra work is to *always* commit the crit roll but
1152
1165
  // that results in counter intuitive probabilities in the common case
1153
- if (pkmn.options.chance and !showdown) {
1166
+ if (pkmn.options.chance and !pkmn.options.mod and move.effect != .Explode) {
1154
1167
  atk = attack(side, !crit, special);
1155
1168
  def = defense(target, !crit, special, false);
1156
1169
  if ((atk > 255 or def > 255) and (def / 4) & 255 == 0) options.chance.glitch();
@@ -1235,13 +1248,13 @@ fn specialDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
1235
1248
  .Psywave => power: {
1236
1249
  const max: u8 = @intCast(@as(u16, side.stored().level) * 3 / 2);
1237
1250
  // GLITCH: Psywave infinite glitch loop
1238
- if (!showdown and max <= 1) return Result.Error;
1251
+ if (!pkmn.options.mod and max <= 1) return .Error;
1239
1252
  break :power try Rolls.psywave(battle, player, max, options);
1240
1253
  },
1241
1254
  else => unreachable,
1242
1255
  };
1243
1256
 
1244
- if (battle.last_damage == 0) return if (showdown) null else Result.Error;
1257
+ if (battle.last_damage == 0) return if (pkmn.options.mod) null else .Error;
1245
1258
 
1246
1259
  const sub = showdown and foe.active.volatiles.Substitute;
1247
1260
  _ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
@@ -1261,15 +1274,15 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
1261
1274
  // Pretend Splash was used as a stand-in when no move has been used to fail below with 0 BP
1262
1275
  const foe_last_selected_move =
1263
1276
  Move.get(if (foe.last_selected_move == .None or foe.last_selected_move == .SKIP_TURN)
1264
- .Splash
1265
- else
1266
- foe.last_selected_move);
1277
+ .Splash
1278
+ else
1279
+ foe.last_selected_move);
1267
1280
 
1268
1281
  const used = battle.lastMove(player.foe()).counterable != 0;
1269
1282
  const selected = foe_last_selected_move.bp > 0 and
1270
1283
  foe.last_selected_move != .Counter and
1271
1284
  (foe_last_selected_move.type == .Normal or
1272
- foe_last_selected_move.type == .Fighting);
1285
+ foe_last_selected_move.type == .Fighting);
1273
1286
 
1274
1287
  if (!used and !selected) {
1275
1288
  try options.log.fail(.{ battle.active(player), .None });
@@ -1278,12 +1291,16 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
1278
1291
 
1279
1292
  if (!used or !selected) {
1280
1293
  // GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
1281
- if (!showdown) return Result.Error;
1294
+ if (!pkmn.options.mod) return .Error;
1282
1295
  try options.log.fail(.{ battle.active(player), .None });
1283
1296
  return null;
1284
1297
  }
1285
1298
 
1286
- battle.last_damage *|= 2;
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;
1287
1304
 
1288
1305
  // Pokémon Showdown calls moveHit before Counter
1289
1306
  if (!showdown and !try checkHit(battle, player, move, options)) return null;
@@ -1429,7 +1446,16 @@ fn moveHit(
1429
1446
  break :miss true;
1430
1447
  }
1431
1448
  if (!showdown) {
1432
- 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
+ }
1433
1459
  if (foe.active.volatiles.Invulnerable) break :miss true;
1434
1460
  }
1435
1461
 
@@ -1502,13 +1528,13 @@ fn checkFaint(
1502
1528
 
1503
1529
  if (player_out and foe_out) {
1504
1530
  try options.log.tie(.{});
1505
- return Result.Tie;
1531
+ return .Tie;
1506
1532
  } else if (player_out) {
1507
1533
  try options.log.win(.{player.foe()});
1508
- return if (player == .P1) Result.Lose else Result.Win;
1534
+ return if (player == .P1) .Lose else .Win;
1509
1535
  } else if (foe_out) {
1510
1536
  try options.log.win(.{player});
1511
- return if (player == .P1) Result.Win else Result.Lose;
1537
+ return if (player == .P1) .Win else .Lose;
1512
1538
  }
1513
1539
 
1514
1540
  const foe_choice: Choice.Type = if (foe_fainted) .Switch else .Pass;
@@ -1525,8 +1551,8 @@ fn faint(battle: anytype, player: Player, done: bool, cap: bool, options: anytyp
1525
1551
  assert(!foe_volatiles.MultiHit);
1526
1552
  if (foe_volatiles.Bide) {
1527
1553
  assert(!foe_volatiles.Thrashing and !foe_volatiles.Rage);
1528
- foe_volatiles.state = if (showdown) 0 else foe_volatiles.state & 255;
1529
- if (foe_volatiles.state != 0) return Result.Error;
1554
+ foe_volatiles.state = if (pkmn.options.mod) 0 else foe_volatiles.state & 255;
1555
+ if (foe_volatiles.state != 0) return .Error;
1530
1556
  }
1531
1557
 
1532
1558
  // Clearing these is not strictly necessary as provided the battle hasn't ended the side that
@@ -1616,21 +1642,21 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
1616
1642
 
1617
1643
  battle.turn += 1;
1618
1644
 
1619
- if (showdown and pkmn.options.ebc and checkEBC(battle)) {
1645
+ if (pkmn.options.mod and pkmn.options.ebc and checkEBC(battle)) {
1620
1646
  try options.log.tie(.{});
1621
- return Result.Tie;
1647
+ return .Tie;
1622
1648
  }
1623
1649
 
1624
- if (showdown and battle.turn >= 1000) {
1650
+ if (pkmn.options.mod and battle.turn >= 1000) {
1625
1651
  try options.log.tie(.{});
1626
- return Result.Tie;
1652
+ return .Tie;
1627
1653
  } else if (battle.turn >= 65535) {
1628
- return Result.Error;
1654
+ return .Error;
1629
1655
  }
1630
1656
 
1631
1657
  try options.log.turn(.{battle.turn});
1632
1658
 
1633
- return Result.Default;
1659
+ return .Default;
1634
1660
  }
1635
1661
 
1636
1662
  fn checkEBC(battle: anytype) bool {
@@ -1916,13 +1942,12 @@ pub const Effects = struct {
1916
1942
 
1917
1943
  // Technically this is still considered simply a "miss" on the cartridge,
1918
1944
  // but diverging from Pokémon Showdown here would mostly just be pedantic
1919
- if (n == 0) {
1945
+ if (n == 0 or (pkmn.options.mod and err)) {
1920
1946
  try options.log.fail(.{ foe_ident, .None });
1921
1947
  return null;
1922
1948
  } else if (err) {
1923
1949
  // GLITCH: Transform + Mirror Move / Metronome PP softlock
1924
- assert(!showdown);
1925
- return Result.Error;
1950
+ return .Error;
1926
1951
  }
1927
1952
 
1928
1953
  volatiles.disable_move =
@@ -1999,7 +2024,7 @@ pub const Effects = struct {
1999
2024
  if (foe.active.types.includes(move.type)) return;
2000
2025
  if (!try Rolls.secondaryChance(battle, player, true, options)) return;
2001
2026
  // Freeze Clause Mod
2002
- if (showdown) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
2027
+ if (pkmn.options.mod) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
2003
2028
 
2004
2029
  foe_stored.status = Status.init(.FRZ);
2005
2030
  // GLITCH: Hyper Beam recharging status is not cleared
@@ -2340,7 +2365,7 @@ pub const Effects = struct {
2340
2365
  }
2341
2366
 
2342
2367
  // Sleep Clause Mod
2343
- if (showdown) {
2368
+ if (pkmn.options.mod) {
2344
2369
  for (foe.pokemon) |p| {
2345
2370
  if (Status.is(p.status, .SLP) and !Status.is(p.status, .EXT)) return;
2346
2371
  }
@@ -2446,7 +2471,7 @@ pub const Effects = struct {
2446
2471
  if (side.active.volatiles.Binding) return;
2447
2472
  side.active.volatiles.Binding = true;
2448
2473
  // GLITCH: Hyper Beam automatic selection glitch if Recharging gets cleared on miss
2449
- // (Pokémon Showdown unitentionally patches this glitch, preventing automatic selection)
2474
+ // (Pokémon Showdown clears Recharging in onTryMove instead)
2450
2475
  if (!showdown) {
2451
2476
  foe.active.volatiles.Recharging = false;
2452
2477
  } else if (foe.stored().hp == 0) return if (!rewrap) battle.rng.advance(1);
@@ -3120,11 +3145,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3120
3145
  },
3121
3146
  .Switch => {
3122
3147
  const side = battle.side(player);
3123
- var slot: u4 = 2;
3148
+ var slot: usize = 2;
3124
3149
  while (slot <= 6) : (slot += 1) {
3125
3150
  const id = side.order[slot - 1];
3126
3151
  if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
3127
- out[n] = .{ .type = .Switch, .data = slot };
3152
+ out[n] = .{ .type = .Switch, .data = @intCast(slot) };
3128
3153
  n += 1;
3129
3154
  }
3130
3155
  if (n == 0) {
@@ -3155,11 +3180,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3155
3180
  return n;
3156
3181
  }
3157
3182
 
3158
- var slot: u4 = 2;
3183
+ var slot: usize = 2;
3159
3184
  while (slot <= 6) : (slot += 1) {
3160
3185
  const id = side.order[slot - 1];
3161
3186
  if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
3162
- out[n] = .{ .type = .Switch, .data = slot };
3187
+ out[n] = .{ .type = .Switch, .data = @intCast(slot) };
3163
3188
  n += 1;
3164
3189
  }
3165
3190
 
@@ -3192,7 +3217,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3192
3217
  const struggle =
3193
3218
  m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
3194
3219
  const s = if (struggle) 0 else slot;
3195
- out[n] = .{ .type = .Move, .data = s };
3220
+ out[n] = .{ .type = .Move, .data = @intCast(s) };
3196
3221
  n += 1;
3197
3222
  return n;
3198
3223
  }
@@ -3206,13 +3231,13 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3206
3231
  if (m.id == .None) break;
3207
3232
  if (m.pp == 0) continue;
3208
3233
  if (active.volatiles.disable_move == slot) continue;
3209
- out[n] = .{ .type = .Move, .data = slot };
3234
+ out[n] = .{ .type = .Move, .data = @intCast(slot) };
3210
3235
  n += 1;
3211
3236
  }
3212
3237
  // Struggle (Pokémon Showdown would use 'move 1' here)
3213
3238
  if (n == before) {
3214
3239
  // GLITCH: Transform + Mirror Move / Metronome PP softlock
3215
- if (!showdown) {
3240
+ if (!pkmn.options.mod) {
3216
3241
  while (slot <= 4) : (slot += 1) {
3217
3242
  if (active.moves[slot - 1].pp != 0) return n;
3218
3243
  }
@@ -1,30 +1,19 @@
1
- const std = @import("std");
2
-
3
- const pkmn = @import("../pkmn.zig");
4
-
1
+ const chance = @import("chance.zig");
5
2
  const common = @import("../common/data.zig");
6
- const util = @import("../common/util.zig");
7
-
8
3
  const Optional = @import("../common/optional.zig").Optional;
4
+ const pkmn = @import("../pkmn.zig");
5
+ const std = @import("std");
6
+ const util = @import("../common/util.zig");
9
7
 
10
- const chance = @import("chance.zig");
11
-
8
+ const Action = chance.Action;
9
+ const Actions = chance.Actions;
12
10
  const assert = std.debug.assert;
13
-
11
+ const Criticals = chance.Criticals;
12
+ const Damages = chance.Damages;
14
13
  const enabled = pkmn.options.calc;
15
-
14
+ const isPointerTo = util.isPointerTo;
16
15
  const Player = common.Player;
17
-
18
16
  const PointerType = util.PointerType;
19
- const isPointerTo = util.isPointerTo;
20
-
21
- const Actions = chance.Actions;
22
- const Action = chance.Action;
23
- const Criticals = chance.Criticals;
24
- const Damages = chance.Damages;
25
-
26
- const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
27
- const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
28
17
 
29
18
  /// Information relevant to damage calculation that occured during a Generation II battle `update`.
30
19
  pub const Summaries = extern struct {
@@ -99,8 +88,8 @@ pub const Calc = struct {
99
88
  };
100
89
 
101
90
  return if (switch (@typeInfo(@TypeOf(val))) {
102
- Enum => val != .None,
103
- Int => val != 0,
91
+ .@"enum" => val != .None,
92
+ .int => val != 0,
104
93
  else => unreachable,
105
94
  }) val else null;
106
95
  }
@@ -156,6 +145,6 @@ fn ReturnType(comptime field: Action.Field) type {
156
145
  return ?(switch (field) {
157
146
  .damages => u8,
158
147
  .critical_hits => Optional(bool),
159
- else => std.meta.FieldType(Action, field),
148
+ else => @FieldType(Action, @tagName(field)),
160
149
  });
161
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
  }
@@ -1,11 +1,9 @@
1
1
  //! Code generated by `tools/generate` - manual edits will be overwritten.
2
2
 
3
- const std = @import("std");
4
-
5
3
  const data = @import("../../gen2/data.zig");
4
+ const std = @import("std");
6
5
 
7
6
  const assert = std.debug.assert;
8
-
9
7
  const Type = data.Type;
10
8
 
11
9
  /// Representation of a held item in Generation II Pokémon.
@@ -1,12 +1,10 @@
1
1
  //! Code generated by `tools/generate` - manual edits will be overwritten.
2
2
 
3
- const std = @import("std");
4
-
5
- const rng = @import("../../common/rng.zig");
6
3
  const gen2 = @import("../../gen2/data.zig");
4
+ const rng = @import("../../common/rng.zig");
5
+ const std = @import("std");
7
6
 
8
7
  const assert = std.debug.assert;
9
-
10
8
  const percent = rng.Gen12.percent;
11
9
  const Type = gen2.Type;
12
10
 
@@ -1,11 +1,9 @@
1
1
  //! Code generated by `tools/generate` - manual edits will be overwritten.
2
2
 
3
- const std = @import("std");
4
-
5
3
  const gen2 = @import("../../gen2/data.zig");
4
+ const std = @import("std");
6
5
 
7
6
  const assert = std.debug.assert;
8
-
9
7
  const Stats = gen2.Stats;
10
8
  const Types = gen2.Types;
11
9
 
@@ -1,8 +1,7 @@
1
1
  //! Code generated by `tools/generate` - manual edits will be overwritten.
2
2
 
3
- const std = @import("std");
4
-
5
3
  const gen1 = @import("../../gen1/data.zig");
4
+ const std = @import("std");
6
5
 
7
6
  const assert = std.debug.assert;
8
7
  const Effectiveness = gen1.Effectiveness;
@@ -173,9 +172,12 @@ pub const Type = enum(u8) {
173
172
  return CHART[@intFromEnum(t1)][@intFromEnum(t2)];
174
173
  }
175
174
 
176
- /// The precedence order of Type `type`.
177
- pub fn precedence(self: Type) u8 {
178
- 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)];
179
181
  }
180
182
 
181
183
  /// The internal index of this Type used by Present.