@pkmn/engine 0.1.0-dev.f4ba6ebd → 0.1.0-dev.f6545544

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 (77) hide show
  1. package/README.md +5 -4
  2. package/build/pkg/addon/browser.d.ts +3 -0
  3. package/build/pkg/addon/browser.js +47 -0
  4. package/build/pkg/addon/browser.js.map +1 -0
  5. package/build/pkg/addon/node.d.ts +3 -0
  6. package/build/pkg/addon/node.js +110 -0
  7. package/build/pkg/addon/node.js.map +1 -0
  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 -3
  12. package/build/pkg/addon.js +34 -65
  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.d.ts +23 -1
  17. package/build/pkg/data.js.map +1 -1
  18. package/build/pkg/gen1.d.ts +15 -18
  19. package/build/pkg/gen1.js +28 -19
  20. package/build/pkg/gen1.js.map +1 -1
  21. package/build/pkg/index.d.ts +39 -89
  22. package/build/pkg/index.js +23 -70
  23. package/build/pkg/index.js.map +1 -1
  24. package/build/pkg/protocol.d.ts +20 -1
  25. package/build/pkg/protocol.js +105 -103
  26. package/build/pkg/protocol.js.map +1 -1
  27. package/build/tools/debug.js +52 -76
  28. package/build/tools/debug.js.map +1 -1
  29. package/build/tools/display/debug.jsx +152 -0
  30. package/build/tools/display/debug.jsx.map +1 -0
  31. package/build/tools/display/index.js +152 -0
  32. package/build/tools/display/index.js.map +1 -0
  33. package/build/tools/display/ui.jsx +359 -0
  34. package/build/tools/display/ui.jsx.map +1 -0
  35. package/build/tools/display/util.js +33 -0
  36. package/build/tools/display/util.js.map +1 -0
  37. package/build.zig +196 -84
  38. package/package.json +51 -135
  39. package/src/bin/install-pkmn-engine +3 -2
  40. package/src/bin/pkmn-debug +1 -1
  41. package/src/lib/bindings/c.zig +255 -0
  42. package/src/lib/bindings/node.zig +121 -0
  43. package/src/lib/bindings/wasm.zig +59 -0
  44. package/src/lib/common/array.zig +1 -1
  45. package/src/lib/common/debug.zig +39 -8
  46. package/src/lib/common/optional.zig +1 -1
  47. package/src/lib/common/options.zig +4 -0
  48. package/src/lib/common/protocol.zig +4 -8
  49. package/src/lib/common/rational.zig +7 -5
  50. package/src/lib/common/rng.zig +1 -3
  51. package/src/lib/common/util.zig +1 -1
  52. package/src/lib/gen1/calc.zig +208 -61
  53. package/src/lib/gen1/chance.zig +40 -22
  54. package/src/lib/gen1/data/moves.zig +2 -4
  55. package/src/lib/gen1/data/species.zig +1 -3
  56. package/src/lib/gen1/data/types.zig +1 -2
  57. package/src/lib/gen1/data.zig +9 -13
  58. package/src/lib/gen1/helpers.zig +5 -21
  59. package/src/lib/gen1/mechanics.zig +107 -82
  60. package/src/lib/gen2/calc.zig +9 -17
  61. package/src/lib/gen2/chance.zig +0 -3
  62. package/src/lib/gen2/data/items.zig +1 -3
  63. package/src/lib/gen2/data/moves.zig +2 -4
  64. package/src/lib/gen2/data/species.zig +1 -3
  65. package/src/lib/gen2/data/types.zig +1 -2
  66. package/src/lib/gen2/data.zig +11 -15
  67. package/src/lib/gen2/generated.zig +18 -23
  68. package/src/lib/gen2/helpers.zig +5 -16
  69. package/src/lib/gen2/mechanics.zig +41 -44
  70. package/src/lib/node.zig +3 -116
  71. package/src/lib/pkmn.zig +7 -2
  72. package/src/lib/wasm.zig +13 -9
  73. package/src/tools/display/dom.ts +95 -0
  74. package/src/tools/display/ui.css +354 -0
  75. package/build/test/display.js +0 -405
  76. package/build/test/display.js.map +0 -1
  77. package/src/test/display/pkmn.html.tmpl +0 -368
@@ -1,30 +1,21 @@
1
- const std = @import("std");
2
1
  const builtin = @import("builtin");
3
-
4
2
  const common = @import("../common/data.zig");
3
+ const data = @import("data.zig");
5
4
  const options = @import("../common/options.zig");
6
5
  const protocol = @import("../common/protocol.zig");
7
6
  const rng = @import("../common/rng.zig");
7
+ const std = @import("std");
8
8
  const util = @import("../common/util.zig");
9
9
 
10
- const data = @import("data.zig");
11
-
12
10
  const assert = std.debug.assert;
13
-
14
11
  const Choice = common.Choice;
15
- const Player = common.Player;
16
-
17
- const showdown = options.showdown;
18
- const chance = options.chance;
19
-
20
- const PSRNG = rng.PSRNG;
21
-
22
12
  const DVs = data.DVs;
23
13
  const Move = data.Move;
24
14
  const MoveSlot = data.MoveSlot;
15
+ const PSRNG = rng.PSRNG;
16
+ const showdown = options.showdown;
25
17
  const Species = data.Species;
26
18
  const Stats = data.Stats;
27
- const Status = data.Status;
28
19
 
29
20
  const endian = builtin.cpu.arch.endian();
30
21
 
@@ -269,14 +260,7 @@ fn blocked(m: Move) bool {
269
260
  fn durations(m: Move) bool {
270
261
  // Any moves that have effects that result in duration tracking with -Dchance
271
262
  return switch (Move.get(m).effect) {
272
- .Confusion,
273
- .Bide,
274
- .Sleep,
275
- .Rage,
276
- .Binding,
277
- .Thrashing,
278
- .ConfusionChance,
279
- .Disable,
263
+ .Haze,
280
264
  .Metronome,
281
265
  => true,
282
266
  else => false,
@@ -1,39 +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;
7
- const optional = @import("../common/optional.zig");
6
+ const data = @import("data.zig");
7
+ const pkmn = @import("../pkmn.zig");
8
8
  const protocol = @import("../common/protocol.zig");
9
9
  const rng = @import("../common/rng.zig");
10
-
11
- const data = @import("data.zig");
12
- const chance = @import("chance.zig");
10
+ const std = @import("std");
13
11
 
14
12
  const assert = std.debug.assert;
15
-
16
- const expectEqual = std.testing.expectEqual;
17
-
18
- const showdown = pkmn.options.showdown;
19
-
20
- const Choice = common.Choice;
21
- const ID = common.ID;
22
- const Player = common.Player;
23
- const Result = common.Result;
24
-
25
13
  const Boost = protocol.Boost;
14
+ const Choice = common.Choice;
26
15
  const Damage = protocol.Damage;
16
+ const Effectiveness = data.Effectiveness;
17
+ const expectEqual = std.testing.expectEqual;
27
18
  const Fail = protocol.Fail;
28
- const Heal = protocol.Heal;
29
-
30
19
  const Gen12 = rng.Gen12;
31
-
32
- const ActivePokemon = data.ActivePokemon;
33
- const Effectiveness = data.Effectiveness;
20
+ const Heal = protocol.Heal;
21
+ const ID = common.ID;
34
22
  const Move = data.Move;
35
23
  const MoveSlot = data.MoveSlot;
36
- const Pokemon = data.Pokemon;
24
+ const Player = common.Player;
25
+ const Result = common.Result;
26
+ const showdown = pkmn.options.showdown;
37
27
  const Side = data.Side;
38
28
  const Species = data.Species;
39
29
  const Stats = data.Stats;
@@ -176,7 +166,10 @@ fn selectMove(
176
166
  if (foe_choice.type == .Switch) {
177
167
  const last = side.active.move(battle.lastMove(player).index);
178
168
  if (last.id == .Metronome) side.last_selected_move = last.id;
179
- if (last.id == .MirrorMove) return Result.Error;
169
+ if (last.id == .MirrorMove) {
170
+ if (!pkmn.options.mod) return Result.Error;
171
+ side.last_selected_move = last.id;
172
+ }
180
173
  }
181
174
  }
182
175
  return null;
@@ -427,7 +420,7 @@ fn executeMove(
427
420
  mslot = @intCast(battle.lastMove(player).index);
428
421
  const stored = side.stored();
429
422
  // GLITCH: Struggle bypass PP underflow via Hyper Beam / Trapping-switch auto selection
430
- auto = isForced(&side.active) or
423
+ auto = pkmn.options.mod or isForced(&side.active) or
431
424
  side.active.volatiles.Binding or side.active.volatiles.Bide or
432
425
  side.last_selected_move == .HyperBeam or
433
426
  Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP);
@@ -460,6 +453,25 @@ fn executeMove(
460
453
  .err => return @as(?Result, Result.Error),
461
454
  }
462
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;
473
+ }
474
+
463
475
  const can = skip_can or
464
476
  try canMove(battle, player, mslot, auto, skip_pp, .None, residual, options);
465
477
  if (!can) return null;
@@ -557,18 +569,17 @@ fn beforeMove(
557
569
  // This can only happen if a Pokémon started the battle frozen/sleeping and was thawed/woken
558
570
  // before the side had a selected a move - we simply need to assume this leads to a desync
559
571
  if (side.last_selected_move == .None) {
560
- assert(!showdown);
572
+ assert(!pkmn.options.mod);
561
573
  return .err;
562
574
  }
563
575
 
564
576
  if (volatiles.Confusion) {
565
577
  assert(volatiles.confusion > 0);
566
578
  if (options.calc.overridden(player, .confusion)) |obs| switch (obs) {
567
- .started => volatiles.confusion -= 1,
579
+ .started, .ended => volatiles.confusion = 0,
568
580
  .continuing => if (volatiles.confusion > 1) {
569
581
  volatiles.confusion -= 1;
570
582
  },
571
- .ended => volatiles.confusion = 0,
572
583
  .overwritten => {},
573
584
  else => unreachable,
574
585
  } else {
@@ -707,7 +718,11 @@ fn beforeMove(
707
718
  // can be expected to recognize this pattern and pass the information only to the
708
719
  // correct player)
709
720
  try log.start(.{ battle.active(player), .ConfusionSilent });
710
- options.chance.observe(.confusion, player, if (overwritten) .overwritten else .started);
721
+ options.chance.observe(.confusion, player, if (overwritten)
722
+ if (pkmn.options.overwrite) .overwritten else .continuing
723
+ else
724
+ .started);
725
+ if (!overwritten) options.calc.confusion(player);
711
726
  }
712
727
 
713
728
  // This shouldn't actually set last_used_move, but Pokémon Showdown sets last
@@ -773,9 +788,14 @@ fn canMove(
773
788
 
774
789
  if (move.effect == .Thrashing) {
775
790
  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);
791
+ } else if (move.effect == .Binding) {
792
+ // Pokémon Showdown handles this after hit/miss checks and damage calculation, though clears
793
+ // Recharging in onTryMove (required for the Hyper Beam automatic selection glitch)
794
+ if (showdown) {
795
+ battle.foe(player).active.volatiles.Recharging = false;
796
+ } else {
797
+ try Effects.binding(battle, player, false, options);
798
+ }
779
799
  }
780
800
 
781
801
  return true;
@@ -1035,22 +1055,20 @@ fn doMove(
1035
1055
  try Effects.poison(battle, player, Move.get(.PoisonSting), options);
1036
1056
  }
1037
1057
  try log.hitcount(.{ battle.active(player.foe()), hit });
1038
- } else if (showdown) {
1058
+ } else if (showdown and move.effect == .Binding) {
1039
1059
  // This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
1040
- if (move.effect == .Binding) {
1041
- try Effects.binding(battle, player, rewrap, options);
1042
- if (immune) {
1043
- battle.last_damage = 0;
1044
- assert(foe.stored().hp > 0);
1045
- // Pokémon Showdown logs |-damage| here instead of |-immune| because logic...
1046
- if (sub) {
1047
- try log.activate(.{ battle.active(player.foe()), .Substitute });
1048
- } else {
1049
- try log.damage(.{ battle.active(player.foe()), foe.stored(), .None });
1050
- try buildRage(battle, player.foe(), options);
1051
- }
1052
- return null;
1060
+ try Effects.binding(battle, player, rewrap, options);
1061
+ if (immune) {
1062
+ battle.last_damage = 0;
1063
+ assert(foe.stored().hp > 0);
1064
+ // Pokémon Showdown logs |-damage| here instead of |-immune| because logic...
1065
+ if (sub) {
1066
+ try log.activate(.{ battle.active(player.foe()), .Substitute });
1067
+ } else {
1068
+ try log.damage(.{ battle.active(player.foe()), foe.stored(), .None });
1069
+ try buildRage(battle, player.foe(), options);
1053
1070
  }
1071
+ return null;
1054
1072
  }
1055
1073
  }
1056
1074
 
@@ -1131,7 +1149,7 @@ fn calcDamage(
1131
1149
  if ((!showdown or !cfz) and (atk > 255 or def > 255)) {
1132
1150
  atk = @max((atk / 4) & 255, 1);
1133
1151
  // GLITCH: not adjusted to be a min of 1 on cartridge (can lead to division-by-zero freeze)
1134
- def = @max((def / 4) & 255, if (showdown) 1 else 0);
1152
+ def = @max((def / 4) & 255, if (pkmn.options.mod) 1 else 0);
1135
1153
  }
1136
1154
 
1137
1155
  const lvl: u32 = side.stored().level * @as(u2, if (crit) 2 else 1);
@@ -1152,7 +1170,7 @@ fn calcDamage(
1152
1170
  // encountered so we need to make sure that in the event of a miss we commit the status of the
1153
1171
  // crit roll. The alternative to doing this extra work is to *always* commit the crit roll but
1154
1172
  // that results in counter intuitive probabilities in the common case
1155
- if (pkmn.options.chance and !showdown) {
1173
+ if (pkmn.options.chance and !pkmn.options.mod and move.effect != .Explode) {
1156
1174
  atk = attack(side, !crit, special);
1157
1175
  def = defense(target, !crit, special, false);
1158
1176
  if ((atk > 255 or def > 255) and (def / 4) & 255 == 0) options.chance.glitch();
@@ -1237,13 +1255,13 @@ fn specialDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
1237
1255
  .Psywave => power: {
1238
1256
  const max: u8 = @intCast(@as(u16, side.stored().level) * 3 / 2);
1239
1257
  // GLITCH: Psywave infinite glitch loop
1240
- if (!showdown and max <= 1) return Result.Error;
1258
+ if (!pkmn.options.mod and max <= 1) return Result.Error;
1241
1259
  break :power try Rolls.psywave(battle, player, max, options);
1242
1260
  },
1243
1261
  else => unreachable,
1244
1262
  };
1245
1263
 
1246
- if (battle.last_damage == 0) return if (showdown) null else Result.Error;
1264
+ if (battle.last_damage == 0) return if (pkmn.options.mod) null else Result.Error;
1247
1265
 
1248
1266
  const sub = showdown and foe.active.volatiles.Substitute;
1249
1267
  _ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
@@ -1280,12 +1298,16 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
1280
1298
 
1281
1299
  if (!used or !selected) {
1282
1300
  // GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
1283
- if (!showdown) return Result.Error;
1301
+ if (!pkmn.options.mod) return Result.Error;
1284
1302
  try options.log.fail(.{ battle.active(player), .None });
1285
1303
  return null;
1286
1304
  }
1287
1305
 
1288
- battle.last_damage *|= 2;
1306
+ // NOTE: llvm/llvm-project#58557
1307
+ battle.last_damage = if (comptime builtin.target.isWasm())
1308
+ @max(battle.last_damage *% 2, std.math.maxInt(u16))
1309
+ else
1310
+ battle.last_damage *| 2;
1289
1311
 
1290
1312
  // Pokémon Showdown calls moveHit before Counter
1291
1313
  if (!showdown and !try checkHit(battle, player, move, options)) return null;
@@ -1317,7 +1339,7 @@ fn applyDamage(
1317
1339
  subbed.active.volatiles.Substitute = false;
1318
1340
  // battle.last_damage is not updated with the amount of HP the Substitute had
1319
1341
  try options.log.end(.{ battle.active(sub_player), .Substitute });
1320
- options.calc.capped(sub_player);
1342
+ if (reason != .Confusion) options.calc.capped(sub_player);
1321
1343
  return true;
1322
1344
  } else {
1323
1345
  // Safe to truncate since less than subbed.volatiles.substitute which is a u8
@@ -1527,7 +1549,7 @@ fn faint(battle: anytype, player: Player, done: bool, cap: bool, options: anytyp
1527
1549
  assert(!foe_volatiles.MultiHit);
1528
1550
  if (foe_volatiles.Bide) {
1529
1551
  assert(!foe_volatiles.Thrashing and !foe_volatiles.Rage);
1530
- foe_volatiles.state = if (showdown) 0 else foe_volatiles.state & 255;
1552
+ foe_volatiles.state = if (pkmn.options.mod) 0 else foe_volatiles.state & 255;
1531
1553
  if (foe_volatiles.state != 0) return Result.Error;
1532
1554
  }
1533
1555
 
@@ -1618,12 +1640,12 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
1618
1640
 
1619
1641
  battle.turn += 1;
1620
1642
 
1621
- if (showdown and pkmn.options.ebc and checkEBC(battle)) {
1643
+ if (pkmn.options.mod and pkmn.options.ebc and checkEBC(battle)) {
1622
1644
  try options.log.tie(.{});
1623
1645
  return Result.Tie;
1624
1646
  }
1625
1647
 
1626
- if (showdown and battle.turn >= 1000) {
1648
+ if (pkmn.options.mod and battle.turn >= 1000) {
1627
1649
  try options.log.tie(.{});
1628
1650
  return Result.Tie;
1629
1651
  } else if (battle.turn >= 65535) {
@@ -1872,6 +1894,7 @@ pub const Effects = struct {
1872
1894
 
1873
1895
  foe.active.volatiles.confusion = Rolls.confusionDuration(battle, player, options);
1874
1896
  options.chance.observe(.confusion, player.foe(), .started);
1897
+ options.calc.confusion(player.foe());
1875
1898
 
1876
1899
  try options.log.start(.{ battle.active(player.foe()), .Confusion });
1877
1900
  }
@@ -1917,12 +1940,11 @@ pub const Effects = struct {
1917
1940
 
1918
1941
  // Technically this is still considered simply a "miss" on the cartridge,
1919
1942
  // but diverging from Pokémon Showdown here would mostly just be pedantic
1920
- if (n == 0) {
1943
+ if (n == 0 or (pkmn.options.mod and err)) {
1921
1944
  try options.log.fail(.{ foe_ident, .None });
1922
1945
  return null;
1923
1946
  } else if (err) {
1924
1947
  // GLITCH: Transform + Mirror Move / Metronome PP softlock
1925
- assert(!showdown);
1926
1948
  return Result.Error;
1927
1949
  }
1928
1950
 
@@ -2000,7 +2022,7 @@ pub const Effects = struct {
2000
2022
  if (foe.active.types.includes(move.type)) return;
2001
2023
  if (!try Rolls.secondaryChance(battle, player, true, options)) return;
2002
2024
  // Freeze Clause Mod
2003
- if (showdown) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
2025
+ if (pkmn.options.mod) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
2004
2026
 
2005
2027
  foe_stored.status = Status.init(.FRZ);
2006
2028
  // GLITCH: Hyper Beam recharging status is not cleared
@@ -2341,7 +2363,7 @@ pub const Effects = struct {
2341
2363
  }
2342
2364
 
2343
2365
  // Sleep Clause Mod
2344
- if (showdown) {
2366
+ if (pkmn.options.mod) {
2345
2367
  for (foe.pokemon) |p| {
2346
2368
  if (Status.is(p.status, .SLP) and !Status.is(p.status, .EXT)) return;
2347
2369
  }
@@ -2447,7 +2469,7 @@ pub const Effects = struct {
2447
2469
  if (side.active.volatiles.Binding) return;
2448
2470
  side.active.volatiles.Binding = true;
2449
2471
  // GLITCH: Hyper Beam automatic selection glitch if Recharging gets cleared on miss
2450
- // (Pokémon Showdown unitentionally patches this glitch, preventing automatic selection)
2472
+ // (Pokémon Showdown clears Recharging in onTryMove instead)
2451
2473
  if (!showdown) {
2452
2474
  foe.active.volatiles.Recharging = false;
2453
2475
  } else if (foe.stored().hp == 0) return if (!rewrap) battle.rng.advance(1);
@@ -2944,9 +2966,10 @@ pub const Rolls = struct {
2944
2966
  }
2945
2967
 
2946
2968
  fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
2947
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2948
- @intCast(val)
2949
- else if (@hasDecl(@TypeOf(battle.rng), "sleepDuration"))
2969
+ const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
2970
+ if (val >= 1 and val <= 7) @as(u3, @intCast(val)) else null
2971
+ else
2972
+ null) orelse if (@hasDecl(@TypeOf(battle.rng), "sleepDuration"))
2950
2973
  battle.rng.sleepDuration(player)
2951
2974
  else if (showdown)
2952
2975
  battle.rng.range(u3, 1, 8)
@@ -2963,9 +2986,10 @@ pub const Rolls = struct {
2963
2986
  }
2964
2987
 
2965
2988
  fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
2966
- const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
2967
- val
2968
- else if (@hasDecl(@TypeOf(battle.rng), "disableDuration"))
2989
+ const duration: u4 = (if (options.calc.overridden(player, .duration)) |val|
2990
+ if (val >= 1 and val <= 8) val else null
2991
+ else
2992
+ null) orelse if (@hasDecl(@TypeOf(battle.rng), "disableDuration"))
2969
2993
  battle.rng.disableDuration(player)
2970
2994
  else if (showdown)
2971
2995
  battle.rng.range(u4, 1, 9)
@@ -2978,9 +3002,10 @@ pub const Rolls = struct {
2978
3002
  }
2979
3003
 
2980
3004
  fn confusionDuration(battle: anytype, player: Player, options: anytype) u3 {
2981
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2982
- @intCast(val)
2983
- else if (@hasDecl(@TypeOf(battle.rng), "confusionDuration"))
3005
+ const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
3006
+ if (val >= 2 and val <= 5) @as(u3, @intCast(val)) else null
3007
+ else
3008
+ null) orelse if (@hasDecl(@TypeOf(battle.rng), "confusionDuration"))
2984
3009
  battle.rng.confusionDuration(player)
2985
3010
  else if (showdown)
2986
3011
  battle.rng.range(u3, 2, 6)
@@ -2993,9 +3018,10 @@ pub const Rolls = struct {
2993
3018
  }
2994
3019
 
2995
3020
  fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
2996
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2997
- @intCast(val)
2998
- else if (@hasDecl(@TypeOf(battle.rng), "attackingDuration"))
3021
+ const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
3022
+ if (val >= 2 and val <= 3) @as(u3, @intCast(val)) else null
3023
+ else
3024
+ null) orelse if (@hasDecl(@TypeOf(battle.rng), "attackingDuration"))
2999
3025
  battle.rng.attackingDuration(player)
3000
3026
  else if (showdown)
3001
3027
  battle.rng.range(u3, 2, 4)
@@ -3016,9 +3042,10 @@ pub const Rolls = struct {
3016
3042
  options: anytype,
3017
3043
  ) !u3 {
3018
3044
  const roll = if (effect == .Binding) .duration else .multi_hit;
3019
- const n: u3 = if (options.calc.overridden(player, roll)) |val|
3020
- @intCast(val)
3021
- else if (@hasDecl(@TypeOf(battle.rng), "distribution"))
3045
+ const n: u3 = (if (options.calc.overridden(player, roll)) |val|
3046
+ if (val >= 2 and val <= 5) @as(u3, @intCast(val)) else null
3047
+ else
3048
+ null) orelse if (@hasDecl(@TypeOf(battle.rng), "distribution"))
3022
3049
  battle.rng.distribution(player)
3023
3050
  else if (showdown)
3024
3051
  DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
@@ -3044,15 +3071,13 @@ pub const Rolls = struct {
3044
3071
  options: anytype,
3045
3072
  ) !u4 {
3046
3073
  // TODO: consider throwing error instead of rerolling?
3047
- const overridden = if (options.calc.overridden(player, .move_slot)) |val|
3074
+ const slot: u4 = (if (options.calc.overridden(player, .move_slot)) |val|
3048
3075
  if (moves[val - 1].id != .None and (check_pp == 0 or moves[val - 1].pp > 0))
3049
3076
  val
3050
3077
  else
3051
3078
  null
3052
3079
  else
3053
- null;
3054
-
3055
- const slot: u4 = overridden orelse if (@hasDecl(@TypeOf(battle.rng), "moveSlot"))
3080
+ null) orelse if (@hasDecl(@TypeOf(battle.rng), "moveSlot"))
3056
3081
  battle.rng.moveSlot(player, moves, check_pp)
3057
3082
  else slot: {
3058
3083
  if (showdown) {
@@ -3210,7 +3235,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3210
3235
  // Struggle (Pokémon Showdown would use 'move 1' here)
3211
3236
  if (n == before) {
3212
3237
  // GLITCH: Transform + Mirror Move / Metronome PP softlock
3213
- if (!showdown) {
3238
+ if (!pkmn.options.mod) {
3214
3239
  while (slot <= 4) : (slot += 1) {
3215
3240
  if (active.moves[slot - 1].pp != 0) return n;
3216
3241
  }
@@ -1,27 +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
17
 
26
18
  const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
27
19
  const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
@@ -8,7 +8,6 @@ const expectEqual = std.testing.expectEqual;
8
8
 
9
9
  const options = @import("../common/options.zig");
10
10
  const rational = @import("../common/rational.zig");
11
- const rng = @import("../common/rng.zig");
12
11
  const util = @import("../common/util.zig");
13
12
 
14
13
  const Array = @import("../common/array.zig").Array;
@@ -20,8 +19,6 @@ const data = @import("data.zig");
20
19
  const enabled = options.chance;
21
20
  const showdown = options.showdown;
22
21
 
23
- const Gen12 = rng.Gen12;
24
-
25
22
  const PointerType = util.PointerType;
26
23
  const isPointerTo = util.isPointerTo;
27
24
 
@@ -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;
@@ -1,24 +1,20 @@
1
- const std = @import("std");
2
- const builtin = @import("builtin");
3
-
4
- const pkmn = @import("../pkmn.zig");
5
-
6
- const data = @import("../common/data.zig");
7
1
  const DEBUG = @import("../common/debug.zig").print;
8
- const protocol = @import("../common/protocol.zig");
9
- const rng = @import("../common/rng.zig");
10
- const util = @import("../common/util.zig");
11
2
 
3
+ const builtin = @import("builtin");
4
+ const calc = @import("calc.zig");
5
+ const chance = @import("chance.zig");
6
+ const data = @import("../common/data.zig");
7
+ const gen1 = @import("../gen1/data.zig");
12
8
  const items = @import("data/items.zig");
9
+ const mechanics = @import("mechanics.zig");
13
10
  const moves = @import("data/moves.zig");
11
+ const pkmn = @import("../pkmn.zig");
12
+ const protocol = @import("../common/protocol.zig");
13
+ const rng = @import("../common/rng.zig");
14
14
  const species = @import("data/species.zig");
15
+ const std = @import("std");
15
16
  const types = @import("data/types.zig");
16
-
17
- const gen1 = @import("../gen1/data.zig");
18
-
19
- const calc = @import("calc.zig");
20
- const chance = @import("chance.zig");
21
- const mechanics = @import("mechanics.zig");
17
+ const util = @import("../common/util.zig");
22
18
 
23
19
  const assert = std.debug.assert;
24
20
  const expectEqual = std.testing.expectEqual;
@@ -1,37 +1,32 @@
1
1
  //! Code generated by `tools/generate` - manual edits will be overwritten.
2
2
 
3
- const std = @import("std");
4
-
5
3
  const common = @import("../common/data.zig");
6
-
7
4
  const data = @import("data.zig");
8
5
  const mechanics = @import("mechanics.zig");
6
+ const std = @import("std");
9
7
 
10
- const Player = common.Player;
11
- const Result = common.Result;
12
-
13
- const Effectiveness = data.Effectiveness;
14
- const Move = data.Move;
15
-
16
- const Effects = mechanics.Effects;
17
- const State = mechanics.State;
18
-
19
- const canMove = mechanics.canMove;
8
+ const adjustDamage = mechanics.adjustDamage;
9
+ const afterMove = mechanics.afterMove;
10
+ const applyDamage = mechanics.applyDamage;
11
+ const buildRage = mechanics.buildRage;
12
+ const calcDamage = mechanics.calcDamage;
20
13
  const canCharge = mechanics.canCharge;
14
+ const canMove = mechanics.canMove;
15
+ const checkCriticalHit = mechanics.checkCriticalHit;
16
+ const checkHit = mechanics.checkHit;
21
17
  const decrementPP = mechanics.decrementPP;
18
+ const destinyBond = mechanics.destinyBond;
22
19
  const doMove = mechanics.doMove;
23
- const checkHit = mechanics.checkHit;
24
- const checkCriticalHit = mechanics.checkCriticalHit;
25
- const calcDamage = mechanics.calcDamage;
26
- const adjustDamage = mechanics.adjustDamage;
27
- const randomizeDamage = mechanics.randomizeDamage;
28
- const applyDamage = mechanics.applyDamage;
29
- const reportOutcome = mechanics.reportOutcome;
30
20
  const effectChance = mechanics.effectChance;
31
- const afterMove = mechanics.afterMove;
32
- const buildRage = mechanics.buildRage;
21
+ const Effectiveness = data.Effectiveness;
22
+ const Effects = mechanics.Effects;
33
23
  const kingsRock = mechanics.kingsRock;
34
- const destinyBond = mechanics.destinyBond;
24
+ const Move = data.Move;
25
+ const Player = common.Player;
26
+ const randomizeDamage = mechanics.randomizeDamage;
27
+ const reportOutcome = mechanics.reportOutcome;
28
+ const Result = common.Result;
29
+ const State = mechanics.State;
35
30
 
36
31
  const SECONDARY = true;
37
32
  const KINGS = true;