@pkmn/engine 0.1.0-dev.53787794 → 0.1.0-dev.54a1aa85

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 (57) hide show
  1. package/README.md +5 -4
  2. package/build/pkg/addon/browser.d.ts +1 -1
  3. package/build/pkg/addon/browser.js +2 -1
  4. package/build/pkg/addon/browser.js.map +1 -1
  5. package/build/pkg/addon/node.js +2 -1
  6. package/build/pkg/addon/node.js.map +1 -1
  7. package/build/pkg/addon/wasm.d.ts +2 -0
  8. package/build/pkg/addon/wasm.js +44 -0
  9. package/build/pkg/addon/wasm.js.map +1 -0
  10. package/build/pkg/addon.d.ts +23 -3
  11. package/build/pkg/addon.js +8 -17
  12. package/build/pkg/addon.js.map +1 -1
  13. package/build/pkg/data/layout.json +15 -2
  14. package/build/pkg/data/protocol.json +23 -9
  15. package/build/pkg/gen1.d.ts +13 -17
  16. package/build/pkg/gen1.js +6 -10
  17. package/build/pkg/gen1.js.map +1 -1
  18. package/build/pkg/index.d.ts +35 -100
  19. package/build/pkg/index.js +6 -68
  20. package/build/pkg/index.js.map +1 -1
  21. package/build/tools/debug.js +2 -2
  22. package/build/tools/debug.js.map +1 -1
  23. package/build/tools/display/debug.jsx +21 -8
  24. package/build/tools/display/debug.jsx.map +1 -1
  25. package/build/tools/display/index.js +17 -8
  26. package/build/tools/display/index.js.map +1 -1
  27. package/build/tools/display/ui.jsx +7 -7
  28. package/build/tools/display/ui.jsx.map +1 -1
  29. package/build.zig +112 -68
  30. package/package.json +24 -16
  31. package/src/bin/install-pkmn-engine +1 -1
  32. package/src/lib/common/debug.zig +11 -6
  33. package/src/lib/common/options.zig +2 -0
  34. package/src/lib/common/protocol.zig +4 -8
  35. package/src/lib/common/rational.zig +7 -5
  36. package/src/lib/common/rng.zig +1 -3
  37. package/src/lib/gen1/calc.zig +96 -98
  38. package/src/lib/gen1/chance.zig +37 -21
  39. package/src/lib/gen1/data/moves.zig +2 -4
  40. package/src/lib/gen1/data/species.zig +1 -3
  41. package/src/lib/gen1/data/types.zig +1 -2
  42. package/src/lib/gen1/data.zig +7 -11
  43. package/src/lib/gen1/helpers.zig +5 -18
  44. package/src/lib/gen1/mechanics.zig +108 -90
  45. package/src/lib/gen2/calc.zig +9 -17
  46. package/src/lib/gen2/data/items.zig +1 -3
  47. package/src/lib/gen2/data/moves.zig +2 -4
  48. package/src/lib/gen2/data/species.zig +1 -3
  49. package/src/lib/gen2/data/types.zig +1 -2
  50. package/src/lib/gen2/data.zig +11 -15
  51. package/src/lib/gen2/generated.zig +18 -23
  52. package/src/lib/gen2/helpers.zig +5 -12
  53. package/src/lib/gen2/mechanics.zig +41 -38
  54. package/src/lib/node.zig +2 -4
  55. package/src/lib/pkmn.zig +4 -0
  56. package/src/lib/wasm.zig +36 -1
  57. package/src/tools/display/ui.css +12 -9
@@ -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;
@@ -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 Result.Error;
171
+ side.last_selected_move = last.id;
172
+ }
177
173
  }
178
174
  }
179
175
  return null;
@@ -341,7 +337,6 @@ fn doTurn(
341
337
  player_choice,
342
338
  player_rewrap,
343
339
  player_skip,
344
- true,
345
340
  &residual,
346
341
  options,
347
342
  )) |r| return r;
@@ -364,7 +359,6 @@ fn doTurn(
364
359
  foe_choice,
365
360
  foe_rewrap,
366
361
  foe_skip,
367
- false,
368
362
  &residual,
369
363
  options,
370
364
  )) |r| return r;
@@ -392,7 +386,6 @@ fn executeMove(
392
386
  choice: Choice,
393
387
  rewrap: bool,
394
388
  skip: bool,
395
- first: bool,
396
389
  residual: *bool,
397
390
  options: anytype,
398
391
  ) !?Result {
@@ -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);
@@ -452,7 +445,7 @@ fn executeMove(
452
445
 
453
446
  var skip_can = false;
454
447
  var skip_pp = false;
455
- switch (try beforeMove(battle, player, skip, first, residual, options)) {
448
+ switch (try beforeMove(battle, player, skip, residual, options)) {
456
449
  .done => return null,
457
450
  .skip_can => skip_can = true,
458
451
  .skip_pp => skip_pp = true,
@@ -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(battle.lastMove(player).index).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;
@@ -473,7 +485,6 @@ fn beforeMove(
473
485
  battle: anytype,
474
486
  player: Player,
475
487
  skip: bool,
476
- first: bool,
477
488
  residual: *bool,
478
489
  options: anytype,
479
490
  ) !BeforeMove {
@@ -558,24 +569,17 @@ fn beforeMove(
558
569
  // This can only happen if a Pokémon started the battle frozen/sleeping and was thawed/woken
559
570
  // before the side had a selected a move - we simply need to assume this leads to a desync
560
571
  if (side.last_selected_move == .None) {
561
- assert(!showdown);
572
+ assert(!pkmn.options.mod);
562
573
  return .err;
563
574
  }
564
575
 
565
576
  if (volatiles.Confusion) {
566
577
  assert(volatiles.confusion > 0);
567
578
  if (options.calc.overridden(player, .confusion)) |obs| switch (obs) {
568
- .started => {
569
- if (!first or (options.calc.overridden(player, .confused) orelse .None) != .None) {
570
- volatiles.confusion -= 1;
571
- } else {
572
- volatiles.confusion = 0;
573
- }
574
- },
579
+ .started, .ended => volatiles.confusion = 0,
575
580
  .continuing => if (volatiles.confusion > 1) {
576
581
  volatiles.confusion -= 1;
577
582
  },
578
- .ended => volatiles.confusion = 0,
579
583
  .overwritten => {},
580
584
  else => unreachable,
581
585
  } else {
@@ -703,7 +707,7 @@ fn beforeMove(
703
707
  try options.chance.attacking(player, if (volatiles.attacks == 0) .ended else .continuing);
704
708
 
705
709
  if (volatiles.attacks == 0) {
706
- const overwritten = pkmn.options.overwrite and volatiles.Confusion;
710
+ const overwritten = volatiles.Confusion;
707
711
 
708
712
  volatiles.Thrashing = false;
709
713
  volatiles.Confusion = true;
@@ -714,7 +718,11 @@ fn beforeMove(
714
718
  // can be expected to recognize this pattern and pass the information only to the
715
719
  // correct player)
716
720
  try log.start(.{ battle.active(player), .ConfusionSilent });
717
- 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);
718
726
  }
719
727
 
720
728
  // This shouldn't actually set last_used_move, but Pokémon Showdown sets last
@@ -780,9 +788,14 @@ fn canMove(
780
788
 
781
789
  if (move.effect == .Thrashing) {
782
790
  Effects.thrashing(battle, player, options);
783
- } else if (!showdown and move.effect == .Binding) {
784
- // Pokémon Showdown handles this after hit/miss checks and damage calculation
785
- 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
+ }
786
799
  }
787
800
 
788
801
  return true;
@@ -1042,22 +1055,20 @@ fn doMove(
1042
1055
  try Effects.poison(battle, player, Move.get(.PoisonSting), options);
1043
1056
  }
1044
1057
  try log.hitcount(.{ battle.active(player.foe()), hit });
1045
- } else if (showdown) {
1058
+ } else if (showdown and move.effect == .Binding) {
1046
1059
  // This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
1047
- if (move.effect == .Binding) {
1048
- try Effects.binding(battle, player, rewrap, options);
1049
- if (immune) {
1050
- battle.last_damage = 0;
1051
- assert(foe.stored().hp > 0);
1052
- // Pokémon Showdown logs |-damage| here instead of |-immune| because logic...
1053
- if (sub) {
1054
- try log.activate(.{ battle.active(player.foe()), .Substitute });
1055
- } else {
1056
- try log.damage(.{ battle.active(player.foe()), foe.stored(), .None });
1057
- try buildRage(battle, player.foe(), options);
1058
- }
1059
- 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);
1060
1070
  }
1071
+ return null;
1061
1072
  }
1062
1073
  }
1063
1074
 
@@ -1138,7 +1149,7 @@ fn calcDamage(
1138
1149
  if ((!showdown or !cfz) and (atk > 255 or def > 255)) {
1139
1150
  atk = @max((atk / 4) & 255, 1);
1140
1151
  // GLITCH: not adjusted to be a min of 1 on cartridge (can lead to division-by-zero freeze)
1141
- def = @max((def / 4) & 255, if (showdown) 1 else 0);
1152
+ def = @max((def / 4) & 255, if (pkmn.options.mod) 1 else 0);
1142
1153
  }
1143
1154
 
1144
1155
  const lvl: u32 = side.stored().level * @as(u2, if (crit) 2 else 1);
@@ -1159,7 +1170,7 @@ fn calcDamage(
1159
1170
  // encountered so we need to make sure that in the event of a miss we commit the status of the
1160
1171
  // crit roll. The alternative to doing this extra work is to *always* commit the crit roll but
1161
1172
  // that results in counter intuitive probabilities in the common case
1162
- if (pkmn.options.chance and !showdown) {
1173
+ if (pkmn.options.chance and !pkmn.options.mod) {
1163
1174
  atk = attack(side, !crit, special);
1164
1175
  def = defense(target, !crit, special, false);
1165
1176
  if ((atk > 255 or def > 255) and (def / 4) & 255 == 0) options.chance.glitch();
@@ -1244,13 +1255,13 @@ fn specialDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
1244
1255
  .Psywave => power: {
1245
1256
  const max: u8 = @intCast(@as(u16, side.stored().level) * 3 / 2);
1246
1257
  // GLITCH: Psywave infinite glitch loop
1247
- if (!showdown and max <= 1) return Result.Error;
1258
+ if (!pkmn.options.mod and max <= 1) return Result.Error;
1248
1259
  break :power try Rolls.psywave(battle, player, max, options);
1249
1260
  },
1250
1261
  else => unreachable,
1251
1262
  };
1252
1263
 
1253
- 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;
1254
1265
 
1255
1266
  const sub = showdown and foe.active.volatiles.Substitute;
1256
1267
  _ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
@@ -1287,12 +1298,16 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
1287
1298
 
1288
1299
  if (!used or !selected) {
1289
1300
  // GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
1290
- if (!showdown) return Result.Error;
1301
+ if (!pkmn.options.mod) return Result.Error;
1291
1302
  try options.log.fail(.{ battle.active(player), .None });
1292
1303
  return null;
1293
1304
  }
1294
1305
 
1295
- 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;
1296
1311
 
1297
1312
  // Pokémon Showdown calls moveHit before Counter
1298
1313
  if (!showdown and !try checkHit(battle, player, move, options)) return null;
@@ -1534,7 +1549,7 @@ fn faint(battle: anytype, player: Player, done: bool, cap: bool, options: anytyp
1534
1549
  assert(!foe_volatiles.MultiHit);
1535
1550
  if (foe_volatiles.Bide) {
1536
1551
  assert(!foe_volatiles.Thrashing and !foe_volatiles.Rage);
1537
- 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;
1538
1553
  if (foe_volatiles.state != 0) return Result.Error;
1539
1554
  }
1540
1555
 
@@ -1625,12 +1640,12 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
1625
1640
 
1626
1641
  battle.turn += 1;
1627
1642
 
1628
- if (showdown and pkmn.options.ebc and checkEBC(battle)) {
1643
+ if (pkmn.options.mod and pkmn.options.ebc and checkEBC(battle)) {
1629
1644
  try options.log.tie(.{});
1630
1645
  return Result.Tie;
1631
1646
  }
1632
1647
 
1633
- if (showdown and battle.turn >= 1000) {
1648
+ if (pkmn.options.mod and battle.turn >= 1000) {
1634
1649
  try options.log.tie(.{});
1635
1650
  return Result.Tie;
1636
1651
  } else if (battle.turn >= 65535) {
@@ -1879,6 +1894,7 @@ pub const Effects = struct {
1879
1894
 
1880
1895
  foe.active.volatiles.confusion = Rolls.confusionDuration(battle, player, options);
1881
1896
  options.chance.observe(.confusion, player.foe(), .started);
1897
+ options.calc.confusion(player.foe());
1882
1898
 
1883
1899
  try options.log.start(.{ battle.active(player.foe()), .Confusion });
1884
1900
  }
@@ -1924,12 +1940,11 @@ pub const Effects = struct {
1924
1940
 
1925
1941
  // Technically this is still considered simply a "miss" on the cartridge,
1926
1942
  // but diverging from Pokémon Showdown here would mostly just be pedantic
1927
- if (n == 0) {
1943
+ if (n == 0 or (pkmn.options.mod and err)) {
1928
1944
  try options.log.fail(.{ foe_ident, .None });
1929
1945
  return null;
1930
1946
  } else if (err) {
1931
1947
  // GLITCH: Transform + Mirror Move / Metronome PP softlock
1932
- assert(!showdown);
1933
1948
  return Result.Error;
1934
1949
  }
1935
1950
 
@@ -2007,7 +2022,7 @@ pub const Effects = struct {
2007
2022
  if (foe.active.types.includes(move.type)) return;
2008
2023
  if (!try Rolls.secondaryChance(battle, player, true, options)) return;
2009
2024
  // Freeze Clause Mod
2010
- 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;
2011
2026
 
2012
2027
  foe_stored.status = Status.init(.FRZ);
2013
2028
  // GLITCH: Hyper Beam recharging status is not cleared
@@ -2348,7 +2363,7 @@ pub const Effects = struct {
2348
2363
  }
2349
2364
 
2350
2365
  // Sleep Clause Mod
2351
- if (showdown) {
2366
+ if (pkmn.options.mod) {
2352
2367
  for (foe.pokemon) |p| {
2353
2368
  if (Status.is(p.status, .SLP) and !Status.is(p.status, .EXT)) return;
2354
2369
  }
@@ -2454,7 +2469,7 @@ pub const Effects = struct {
2454
2469
  if (side.active.volatiles.Binding) return;
2455
2470
  side.active.volatiles.Binding = true;
2456
2471
  // GLITCH: Hyper Beam automatic selection glitch if Recharging gets cleared on miss
2457
- // (Pokémon Showdown unitentionally patches this glitch, preventing automatic selection)
2472
+ // (Pokémon Showdown clears Recharging in onTryMove instead)
2458
2473
  if (!showdown) {
2459
2474
  foe.active.volatiles.Recharging = false;
2460
2475
  } else if (foe.stored().hp == 0) return if (!rewrap) battle.rng.advance(1);
@@ -2951,9 +2966,10 @@ pub const Rolls = struct {
2951
2966
  }
2952
2967
 
2953
2968
  fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
2954
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2955
- @intCast(val)
2956
- 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"))
2957
2973
  battle.rng.sleepDuration(player)
2958
2974
  else if (showdown)
2959
2975
  battle.rng.range(u3, 1, 8)
@@ -2970,9 +2986,10 @@ pub const Rolls = struct {
2970
2986
  }
2971
2987
 
2972
2988
  fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
2973
- const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
2974
- val
2975
- 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"))
2976
2993
  battle.rng.disableDuration(player)
2977
2994
  else if (showdown)
2978
2995
  battle.rng.range(u4, 1, 9)
@@ -2985,9 +3002,10 @@ pub const Rolls = struct {
2985
3002
  }
2986
3003
 
2987
3004
  fn confusionDuration(battle: anytype, player: Player, options: anytype) u3 {
2988
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2989
- @intCast(val)
2990
- 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"))
2991
3009
  battle.rng.confusionDuration(player)
2992
3010
  else if (showdown)
2993
3011
  battle.rng.range(u3, 2, 6)
@@ -3000,9 +3018,10 @@ pub const Rolls = struct {
3000
3018
  }
3001
3019
 
3002
3020
  fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
3003
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
3004
- @intCast(val)
3005
- 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"))
3006
3025
  battle.rng.attackingDuration(player)
3007
3026
  else if (showdown)
3008
3027
  battle.rng.range(u3, 2, 4)
@@ -3023,9 +3042,10 @@ pub const Rolls = struct {
3023
3042
  options: anytype,
3024
3043
  ) !u3 {
3025
3044
  const roll = if (effect == .Binding) .duration else .multi_hit;
3026
- const n: u3 = if (options.calc.overridden(player, roll)) |val|
3027
- @intCast(val)
3028
- 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"))
3029
3049
  battle.rng.distribution(player)
3030
3050
  else if (showdown)
3031
3051
  DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
@@ -3051,15 +3071,13 @@ pub const Rolls = struct {
3051
3071
  options: anytype,
3052
3072
  ) !u4 {
3053
3073
  // TODO: consider throwing error instead of rerolling?
3054
- const overridden = if (options.calc.overridden(player, .move_slot)) |val|
3074
+ const slot: u4 = (if (options.calc.overridden(player, .move_slot)) |val|
3055
3075
  if (moves[val - 1].id != .None and (check_pp == 0 or moves[val - 1].pp > 0))
3056
3076
  val
3057
3077
  else
3058
3078
  null
3059
3079
  else
3060
- null;
3061
-
3062
- const slot: u4 = overridden orelse if (@hasDecl(@TypeOf(battle.rng), "moveSlot"))
3080
+ null) orelse if (@hasDecl(@TypeOf(battle.rng), "moveSlot"))
3063
3081
  battle.rng.moveSlot(player, moves, check_pp)
3064
3082
  else slot: {
3065
3083
  if (showdown) {
@@ -3217,7 +3235,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3217
3235
  // Struggle (Pokémon Showdown would use 'move 1' here)
3218
3236
  if (n == before) {
3219
3237
  // GLITCH: Transform + Mirror Move / Metronome PP softlock
3220
- if (!showdown) {
3238
+ if (!pkmn.options.mod) {
3221
3239
  while (slot <= 4) : (slot += 1) {
3222
3240
  if (active.moves[slot - 1].pp != 0) return n;
3223
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;
@@ -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;
@@ -1,25 +1,18 @@
1
- const std = @import("std");
2
-
3
1
  const common = @import("../common/data.zig");
2
+ const data = @import("data.zig");
3
+ const gen1 = @import("../gen1/data.zig");
4
4
  const options = @import("../common/options.zig");
5
5
  const rng = @import("../common/rng.zig");
6
-
7
- const gen1 = @import("../gen1/data.zig");
8
-
9
- const data = @import("data.zig");
6
+ const std = @import("std");
10
7
 
11
8
  const assert = std.debug.assert;
12
-
13
9
  const Choice = common.Choice;
14
-
15
- const showdown = options.showdown;
16
-
17
- const PSRNG = rng.PSRNG;
18
-
19
10
  const DVs = data.DVs;
20
11
  const Item = data.Item;
21
12
  const Move = data.Move;
22
13
  const MoveSlot = data.MoveSlot;
14
+ const PSRNG = rng.PSRNG;
15
+ const showdown = options.showdown;
23
16
  const Species = data.Species;
24
17
  const Stats = data.Stats;
25
18