@pkmn/engine 0.1.0-dev.028ab165 → 0.1.0-dev.047d6e71

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.
@@ -112,7 +112,7 @@ pub fn Battle(comptime RNG: anytype) type {
112
112
  /// make (i.e. on the cartridge a soft-lock occurs).
113
113
  ///
114
114
  /// This function will always return choices > 0 if `pkmn.options.showdown` is true.
115
- pub fn choices(self: *Self, player: Player, request: Choice.Type, out: []Choice) u8 {
115
+ pub fn choices(self: *const Self, player: Player, request: Choice.Type, out: []Choice) u8 {
116
116
  return mechanics.choices(self, player, request, out);
117
117
  }
118
118
  };
@@ -316,12 +316,12 @@ test Status {
316
316
  try expect(Status.is(Status.slp(5), .SLP));
317
317
  try expect(!Status.is(Status.slp(5), .EXT));
318
318
  try expect(!Status.is(Status.slp(7), .PSN));
319
- try expectEqual(@as(u3, 5), Status.duration(Status.slp(5)));
319
+ try expectEqual(5, Status.duration(Status.slp(5)));
320
320
  try expect(Status.is(Status.slf(2), .SLP));
321
321
  try expect(Status.is(Status.slf(2), .EXT));
322
322
  try expect(Status.is(Status.TOX, .PSN));
323
323
  try expect(Status.is(Status.TOX, .EXT));
324
- try expectEqual(@as(u3, 1), Status.duration(Status.slp(1)));
324
+ try expectEqual(1, Status.duration(Status.slp(1)));
325
325
  }
326
326
 
327
327
  /// Bitfield representation of volatile statuses and associated data in Generation I.
@@ -405,13 +405,13 @@ test Volatiles {
405
405
  try expect(!volatiles.Transform);
406
406
  try expect(!volatiles.MultiHit);
407
407
 
408
- try expectEqual(@as(u16, 235), volatiles.state);
409
- try expectEqual(@as(u8, 42), volatiles.substitute);
410
- try expectEqual(@as(u4, 2), volatiles.disable_move);
411
- try expectEqual(@as(u4, 4), volatiles.disable_duration);
412
- try expectEqual(@as(u4, 2), volatiles.confusion);
413
- try expectEqual(@as(u5, 4), volatiles.toxic);
414
- try expectEqual(@as(u4, 3), volatiles.attacks);
408
+ try expectEqual(235, volatiles.state);
409
+ try expectEqual(42, volatiles.substitute);
410
+ try expectEqual(2, volatiles.disable_move);
411
+ try expectEqual(4, volatiles.disable_duration);
412
+ try expectEqual(2, volatiles.confusion);
413
+ try expectEqual(4, volatiles.toxic);
414
+ try expectEqual(3, volatiles.attacks);
415
415
  }
416
416
 
417
417
  /// Representaiton of a Pokémon's stats in Generation I.
@@ -442,9 +442,9 @@ test Stats {
442
442
  try expectEqual(0, stats.def);
443
443
 
444
444
  var base = Species.get(.Gyarados).stats;
445
- try expectEqual(@as(u16, 127), Stats(u16).calc("hp", base.hp, 0, 5120, 38));
445
+ try expectEqual(127, Stats(u16).calc("hp", base.hp, 0, 5120, 38));
446
446
  base = Species.get(.Pidgeot).stats;
447
- try expectEqual(@as(u16, 279), Stats(u16).calc("spe", base.spe, 15, 63001, 100));
447
+ try expectEqual(279, Stats(u16).calc("spe", base.spe, 15, 63001, 100));
448
448
  }
449
449
 
450
450
  /// Representation of a Pokémon's boosts in Generation I.
@@ -537,7 +537,7 @@ pub const Species = species.Species;
537
537
 
538
538
  test Species {
539
539
  try expectEqual(2, @intFromEnum(Species.Ivysaur));
540
- try expectEqual(@as(u8, 100), Species.get(.Mew).stats.def);
540
+ try expectEqual(100, Species.get(.Mew).stats.def);
541
541
  }
542
542
 
543
543
  /// Representation of a Generation I type in Pokémon.
@@ -576,8 +576,8 @@ test Types {
576
576
  try expectEqual(Effectiveness.Resisted, Type.effectiveness(.Fire, .Water));
577
577
  try expectEqual(Effectiveness.Neutral, Type.effectiveness(.Normal, .Grass));
578
578
 
579
- try expectEqual(@as(u8, 3), Type.precedence(.Water, .Water));
580
- try expectEqual(@as(u8, 26), Type.precedence(.Bug, .Ghost));
579
+ try expectEqual(3, Type.precedence(.Water, .Water));
580
+ try expectEqual(26, Type.precedence(.Bug, .Ghost));
581
581
  try expect(Type.precedence(.Poison, .Bug) > Type.precedence(.Poison, .Poison));
582
582
 
583
583
  const t: Types = .{ .type1 = .Rock, .type2 = .Ground };
@@ -617,18 +617,18 @@ pub const DVs = struct {
617
617
 
618
618
  test DVs {
619
619
  var dvs: DVs = .{ .spc = 15, .spe = 15 };
620
- try expectEqual(@as(u4, 15), dvs.hp());
620
+ try expectEqual(15, dvs.hp());
621
621
  dvs = .{
622
622
  .atk = 5,
623
623
  .def = 15,
624
624
  .spe = 13,
625
625
  .spc = 13,
626
626
  };
627
- try expectEqual(@as(u4, 15), dvs.hp());
627
+ try expectEqual(15, dvs.hp());
628
628
  dvs = .{
629
629
  .def = 3,
630
630
  .spe = 10,
631
631
  .spc = 11,
632
632
  };
633
- try expectEqual(@as(u4, 13), dvs.hp());
633
+ try expectEqual(13, dvs.hp());
634
634
  }
@@ -478,12 +478,12 @@ test "Rolls.damage" {
478
478
  test "Rolls.coalesce" {
479
479
  var summaries =
480
480
  calc.Summaries{ .p1 = .{ .damage = .{ .base = 74, .final = 69, .capped = true } } };
481
- try expectEqual(@as(u8, 0), try Rolls.coalesce(.P2, 0, &summaries, false));
482
- try expectEqual(@as(u8, 241), try Rolls.coalesce(.P2, 238, &summaries, false));
483
- try expectEqual(@as(u8, 255), try Rolls.coalesce(.P2, 238, &summaries, true));
481
+ try expectEqual(0, try Rolls.coalesce(.P2, 0, &summaries, false));
482
+ try expectEqual(241, try Rolls.coalesce(.P2, 238, &summaries, false));
483
+ try expectEqual(255, try Rolls.coalesce(.P2, 238, &summaries, true));
484
484
  summaries.p1.damage.final = 74;
485
- try expectEqual(@as(u8, 217), try Rolls.coalesce(.P2, 217, &summaries, false));
486
- try expectEqual(@as(u8, 255), try Rolls.coalesce(.P2, 217, &summaries, true));
485
+ try expectEqual(217, try Rolls.coalesce(.P2, 217, &summaries, false));
486
+ try expectEqual(255, try Rolls.coalesce(.P2, 217, &summaries, true));
487
487
  }
488
488
 
489
489
  test "Rolls.hit" {
@@ -771,9 +771,9 @@ test "Rolls.psywave" {
771
771
 
772
772
  try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p1, &side, .None));
773
773
  var rolls = Rolls.psywave(actions.p2, &side, .None);
774
- try expectEqual(@as(u8, 150), rolls[rolls.len - 1]);
774
+ try expectEqual(150, rolls[rolls.len - 1]);
775
775
  side.stored().level = 81;
776
776
  rolls = Rolls.psywave(actions.p2, &side, .None);
777
- try expectEqual(@as(u8, 121), rolls[rolls.len - 1]);
777
+ try expectEqual(121, rolls[rolls.len - 1]);
778
778
  try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p2, &side, .false));
779
779
  }
@@ -71,8 +71,8 @@ pub fn update(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Result
71
71
  var p1 = battle.side(.P1);
72
72
  var p2 = battle.side(.P2);
73
73
 
74
- var r1 = showdown and p1.active.volatiles.Binding and c2.type == .Switch;
75
- var r2 = showdown and p2.active.volatiles.Binding and c1.type == .Switch;
74
+ const r1 = showdown and p1.active.volatiles.Binding and c2.type == .Switch;
75
+ const r2 = showdown and p2.active.volatiles.Binding and c1.type == .Switch;
76
76
 
77
77
  if (try turnOrder(battle, c1, c2, options) == .P1) {
78
78
  if (try doTurn(battle, .P1, c1, r1, s1, .P2, c2, r2, s2, options)) |r| return r;
@@ -90,11 +90,11 @@ fn start(battle: anytype, options: anytype) !Result {
90
90
  const p1 = battle.side(.P1);
91
91
  const p2 = battle.side(.P2);
92
92
 
93
- var p1_slot = findFirstAlive(p1);
93
+ const p1_slot = findFirstAlive(p1);
94
94
  assert(!showdown or p1_slot == 1);
95
95
  if (p1_slot == 0) return if (findFirstAlive(p2) == 0) Result.Tie else Result.Lose;
96
96
 
97
- var p2_slot = findFirstAlive(p2);
97
+ const p2_slot = findFirstAlive(p2);
98
98
  assert(!showdown or p2_slot == 1);
99
99
  if (p2_slot == 0) return Result.Win;
100
100
 
@@ -635,8 +635,6 @@ fn beforeMove(
635
635
  volatiles.state +%= battle.last_damage;
636
636
  }
637
637
 
638
- assert(volatiles.attacks > 0);
639
-
640
638
  if (options.calc.modify(player, .attacking)) |extend| {
641
639
  if (!extend) volatiles.attacks = 0;
642
640
  } else {
@@ -676,8 +674,6 @@ fn beforeMove(
676
674
  }
677
675
 
678
676
  if (volatiles.Thrashing) {
679
- assert(volatiles.attacks > 0);
680
-
681
677
  if (options.calc.modify(player, .attacking)) |extend| {
682
678
  if (!extend) volatiles.attacks = 0;
683
679
  } else {
@@ -701,8 +697,6 @@ fn beforeMove(
701
697
  }
702
698
 
703
699
  if (volatiles.Binding) {
704
- assert(volatiles.attacks > 0);
705
-
706
700
  if (options.calc.modify(player, .binding)) |extend| {
707
701
  if (!extend) volatiles.attacks = 0;
708
702
  } else {
@@ -738,7 +732,6 @@ fn canMove(
738
732
  const player_ident = battle.active(player);
739
733
  const move = Move.get(side.last_selected_move);
740
734
 
741
- var skip = skip_pp;
742
735
  if (side.active.volatiles.Charging) {
743
736
  side.active.volatiles.Charging = false;
744
737
  side.active.volatiles.Invulnerable = false;
@@ -750,7 +743,7 @@ fn canMove(
750
743
  }
751
744
 
752
745
  side.last_used_move = side.last_selected_move;
753
- if (!skip) decrementPP(side, mslot, auto);
746
+ if (!skip_pp) decrementPP(side, mslot, auto);
754
747
 
755
748
  const target = if (move.target == .Self) player else player.foe();
756
749
  try options.log.move(.{ player_ident, side.last_selected_move, battle.active(target), from });
@@ -902,7 +895,7 @@ fn doMove(
902
895
  }
903
896
  }
904
897
 
905
- var zero = battle.last_damage == 0;
898
+ const zero = battle.last_damage == 0;
906
899
  miss = if (showdown or skip)
907
900
  miss
908
901
  else
@@ -1060,7 +1053,7 @@ fn checkCriticalHit(battle: anytype, player: Player, move: Move.Data, options: a
1060
1053
  const side = battle.side(player);
1061
1054
 
1062
1055
  // Base speed is used for the critical hit calculation, even when Transform-ed
1063
- var rate = @as(u16, Species.chance(side.stored().species));
1056
+ var rate: u16 = Species.chance(side.stored().species);
1064
1057
  // GLITCH: Focus Energy reduces critical hit rate instead of increasing it
1065
1058
  rate = if (side.active.volatiles.FocusEnergy) rate / 2 else @min(rate * 2, 255);
1066
1059
  rate = if (move.effect == .HighCritical) @min(rate * 4, 255) else rate / 2;
@@ -1117,9 +1110,9 @@ fn calcDamage(
1117
1110
  def = @max((def / 4) & 255, if (showdown) 1 else 0);
1118
1111
  }
1119
1112
 
1120
- const lvl = @as(u32, side.stored().level * @as(u2, if (crit) 2 else 1));
1113
+ const lvl: u32 = side.stored().level * @as(u2, if (crit) 2 else 1);
1121
1114
 
1122
- def = @as(u32, if (move.effect == .Explode) @max(def / 2, 1) else def);
1115
+ def = if (move.effect == .Explode) @max(def / 2, 1) else def;
1123
1116
 
1124
1117
  if (def == 0) return false;
1125
1118
 
@@ -1402,7 +1395,7 @@ fn moveHit(
1402
1395
  const overwritten = overwrite and state > 0;
1403
1396
  assert(!overwritten or (0 < state and state <= 255 and !side.active.volatiles.Bide));
1404
1397
 
1405
- var accuracy = if (overwritten) state else @as(u16, move.accuracy);
1398
+ var accuracy: u16 = if (overwritten) state else move.accuracy;
1406
1399
  var boost = BOOSTS[@as(u4, @intCast(@as(i8, side.active.boosts.accuracy) + 6))];
1407
1400
  accuracy = accuracy * boost[0] / boost[1];
1408
1401
  boost = BOOSTS[@as(u4, @intCast(@as(i8, -foe.active.boosts.evasion) + 6))];
@@ -1679,7 +1672,6 @@ fn onBegin(
1679
1672
  residual: *bool,
1680
1673
  options: anytype,
1681
1674
  ) !void {
1682
- assert(move.effect.onBegin());
1683
1675
  return switch (move.effect) {
1684
1676
  .Confusion => Effects.confusion(battle, player, move, options),
1685
1677
  .Conversion => Effects.conversion(battle, player, options),
@@ -1702,7 +1694,6 @@ fn onBegin(
1702
1694
  }
1703
1695
 
1704
1696
  fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1705
- assert(move.effect.onEnd());
1706
1697
  return switch (move.effect) {
1707
1698
  .Bide => Effects.bide(battle, player, options),
1708
1699
  // zig fmt: off
@@ -1724,7 +1715,6 @@ fn alwaysHappens(
1724
1715
  residual: *bool,
1725
1716
  options: anytype,
1726
1717
  ) !void {
1727
- assert(move.effect.alwaysHappens());
1728
1718
  return switch (move.effect) {
1729
1719
  .DrainHP, .DreamEater => Effects.drainHP(battle, player, options),
1730
1720
  .Explode => Effects.explode(battle, player),
@@ -1972,7 +1962,7 @@ pub const Effects = struct {
1972
1962
  var side = battle.side(player);
1973
1963
  var foe = battle.foe(player);
1974
1964
 
1975
- var side_stored = side.stored();
1965
+ const side_stored = side.stored();
1976
1966
  var foe_stored = foe.stored();
1977
1967
 
1978
1968
  const player_ident = battle.active(player);
@@ -2441,7 +2431,7 @@ pub const Effects = struct {
2441
2431
  }
2442
2432
  return log.fail(.{ ident, .None });
2443
2433
  }
2444
- var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
2434
+ const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
2445
2435
  const stat = unmodifiedStats(battle, side).atk;
2446
2436
  stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
2447
2437
  try log.boost(.{ ident, reason, n });
@@ -2461,7 +2451,7 @@ pub const Effects = struct {
2461
2451
  }
2462
2452
  return log.fail(.{ ident, .None });
2463
2453
  }
2464
- var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
2454
+ const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
2465
2455
  const stat = unmodifiedStats(battle, side).def;
2466
2456
  stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
2467
2457
  try log.boost(.{ ident, .Defense, n });
@@ -2478,7 +2468,7 @@ pub const Effects = struct {
2478
2468
  }
2479
2469
  return log.fail(.{ ident, .None });
2480
2470
  }
2481
- var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
2471
+ const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
2482
2472
  const stat = unmodifiedStats(battle, side).spe;
2483
2473
  stats.spe = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
2484
2474
  try log.boost(.{ ident, .Speed, 2 });
@@ -2498,7 +2488,7 @@ pub const Effects = struct {
2498
2488
  }
2499
2489
  return log.fail(.{ ident, .None });
2500
2490
  }
2501
- var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
2491
+ const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
2502
2492
  const stat = unmodifiedStats(battle, side).spc;
2503
2493
  stats.spc = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
2504
2494
  try log.boost(.{ ident, .SpecialAttack, n });
@@ -2551,7 +2541,7 @@ pub const Effects = struct {
2551
2541
  }
2552
2542
  return log.fail(.{ foe_ident, .None });
2553
2543
  }
2554
- var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
2544
+ const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
2555
2545
  const stat = unmodifiedStats(battle, foe).atk;
2556
2546
  stats.atk = @max(1, stat * mod[0] / mod[1]);
2557
2547
  try log.boost(.{ foe_ident, .Attack, -1 });
@@ -2569,7 +2559,7 @@ pub const Effects = struct {
2569
2559
  }
2570
2560
  return log.fail(.{ foe_ident, .None });
2571
2561
  }
2572
- var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
2562
+ const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
2573
2563
  const stat = unmodifiedStats(battle, foe).def;
2574
2564
  stats.def = @max(1, stat * mod[0] / mod[1]);
2575
2565
  try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
@@ -2586,7 +2576,7 @@ pub const Effects = struct {
2586
2576
  }
2587
2577
  return log.fail(.{ foe_ident, .None });
2588
2578
  }
2589
- var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
2579
+ const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
2590
2580
  const stat = unmodifiedStats(battle, foe).spe;
2591
2581
  stats.spe = @max(1, stat * mod[0] / mod[1]);
2592
2582
  try log.boost(.{ foe_ident, .Speed, -1 });
@@ -2606,7 +2596,7 @@ pub const Effects = struct {
2606
2596
  }
2607
2597
  return log.fail(.{ foe_ident, .None });
2608
2598
  }
2609
- var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
2599
+ const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
2610
2600
  const stat = unmodifiedStats(battle, foe).spc;
2611
2601
  stats.spc = @max(1, stat * mod[0] / mod[1]);
2612
2602
  try log.boost(.{ foe_ident, .SpecialAttack, -1 });
@@ -2699,6 +2689,8 @@ pub const Rolls = struct {
2699
2689
  fn speedTie(battle: anytype, options: anytype) !bool {
2700
2690
  const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
2701
2691
  player == .P1
2692
+ else if (@hasDecl(@TypeOf(battle.rng), "speedTie"))
2693
+ battle.rng.speedTie()
2702
2694
  else if (showdown)
2703
2695
  battle.rng.range(u8, 0, 2) == 0
2704
2696
  else
@@ -2711,6 +2703,8 @@ pub const Rolls = struct {
2711
2703
  fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
2712
2704
  const crit = if (options.calc.overridden(player, .critical_hit)) |val|
2713
2705
  val == .true
2706
+ else if (@hasDecl(@TypeOf(battle.rng), "criticalHit"))
2707
+ battle.rng.criticalHit(player, rate)
2714
2708
  else if (showdown)
2715
2709
  battle.rng.chance(u8, rate, 256)
2716
2710
  else
@@ -2723,6 +2717,8 @@ pub const Rolls = struct {
2723
2717
  fn damage(battle: anytype, player: Player, options: anytype) !u8 {
2724
2718
  const roll = if (options.calc.overridden(player, .damage)) |val|
2725
2719
  val
2720
+ else if (@hasDecl(@TypeOf(battle.rng), "damage"))
2721
+ battle.rng.damage(player)
2726
2722
  else roll: {
2727
2723
  if (showdown) break :roll battle.rng.range(u8, 217, 256);
2728
2724
  while (true) {
@@ -2739,6 +2735,8 @@ pub const Rolls = struct {
2739
2735
  fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) bool {
2740
2736
  const ok = if (options.calc.overridden(player, .hit)) |val|
2741
2737
  val == .true
2738
+ else if (@hasDecl(@TypeOf(battle.rng), "hit"))
2739
+ battle.rng.hit(player, accuracy)
2742
2740
  else if (showdown)
2743
2741
  battle.rng.chance(u8, accuracy, 256)
2744
2742
  else
@@ -2751,6 +2749,8 @@ pub const Rolls = struct {
2751
2749
  fn confused(battle: anytype, player: Player, options: anytype) !bool {
2752
2750
  const cfz = if (options.calc.overridden(player, .confused)) |val|
2753
2751
  val == .true
2752
+ else if (@hasDecl(@TypeOf(battle.rng), "confused"))
2753
+ battle.rng.confused(player)
2754
2754
  else if (showdown)
2755
2755
  !battle.rng.chance(u8, 128, 256)
2756
2756
  else
@@ -2763,6 +2763,8 @@ pub const Rolls = struct {
2763
2763
  fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
2764
2764
  const par = if (options.calc.overridden(player, .paralyzed)) |val|
2765
2765
  val == .true
2766
+ else if (@hasDecl(@TypeOf(battle.rng), "paralyzed"))
2767
+ battle.rng.paralyzed(player)
2766
2768
  else if (showdown)
2767
2769
  battle.rng.chance(u8, 63, 256)
2768
2770
  else
@@ -2775,6 +2777,8 @@ pub const Rolls = struct {
2775
2777
  fn confusionChance(battle: anytype, player: Player, options: anytype) !bool {
2776
2778
  const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
2777
2779
  val == .true
2780
+ else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
2781
+ battle.rng.secondaryChance(player, 25)
2778
2782
  else if (showdown)
2779
2783
  battle.rng.chance(u8, 25, 256)
2780
2784
  else
@@ -2789,6 +2793,8 @@ pub const Rolls = struct {
2789
2793
 
2790
2794
  const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
2791
2795
  val == .true
2796
+ else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
2797
+ battle.rng.secondaryChance(player, rate)
2792
2798
  else if (showdown)
2793
2799
  battle.rng.chance(u8, rate, 256)
2794
2800
  else
@@ -2803,6 +2809,8 @@ pub const Rolls = struct {
2803
2809
 
2804
2810
  const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
2805
2811
  val == .true
2812
+ else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
2813
+ battle.rng.secondaryChance(player, rate)
2806
2814
  else if (showdown)
2807
2815
  battle.rng.chance(u8, rate, 256)
2808
2816
  else
@@ -2815,6 +2823,8 @@ pub const Rolls = struct {
2815
2823
  fn unboost(battle: anytype, player: Player, options: anytype) !bool {
2816
2824
  const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
2817
2825
  val == .true
2826
+ else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
2827
+ battle.rng.secondaryChance(player, 85)
2818
2828
  else if (showdown)
2819
2829
  battle.rng.chance(u8, 85, 256)
2820
2830
  else
@@ -2827,6 +2837,8 @@ pub const Rolls = struct {
2827
2837
  fn metronome(battle: anytype, player: Player, options: anytype) !Move {
2828
2838
  const move: Move = if (options.calc.overridden(player, .metronome)) |val|
2829
2839
  val
2840
+ else if (@hasDecl(@TypeOf(battle.rng), "metronome"))
2841
+ battle.rng.metronome(player)
2830
2842
  else if (showdown)
2831
2843
  Move.METRONOME[battle.rng.range(u8, 0, Move.METRONOME.len)]
2832
2844
  else move: {
@@ -2845,6 +2857,8 @@ pub const Rolls = struct {
2845
2857
  fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
2846
2858
  const power = if (options.calc.overridden(player, .psywave)) |val|
2847
2859
  val - 1
2860
+ else if (@hasDecl(@TypeOf(battle.rng), "psywave"))
2861
+ battle.rng.psywave(player)
2848
2862
  else if (showdown)
2849
2863
  battle.rng.range(u8, 0, max)
2850
2864
  else power: {
@@ -2862,6 +2876,8 @@ pub const Rolls = struct {
2862
2876
  fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
2863
2877
  const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2864
2878
  @intCast(val)
2879
+ else if (@hasDecl(@TypeOf(battle.rng), "sleepDuration"))
2880
+ battle.rng.sleepDuration(player)
2865
2881
  else if (showdown)
2866
2882
  battle.rng.range(u3, 1, 8)
2867
2883
  else duration: {
@@ -2879,6 +2895,8 @@ pub const Rolls = struct {
2879
2895
  fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
2880
2896
  const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
2881
2897
  val
2898
+ else if (@hasDecl(@TypeOf(battle.rng), "disableDuration"))
2899
+ battle.rng.disableDuration(player)
2882
2900
  else if (showdown)
2883
2901
  battle.rng.range(u4, 1, 9)
2884
2902
  else
@@ -2892,6 +2910,8 @@ pub const Rolls = struct {
2892
2910
  fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
2893
2911
  const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2894
2912
  @intCast(val)
2913
+ else if (@hasDecl(@TypeOf(battle.rng), "confusionDuration"))
2914
+ battle.rng.confusionDuration(player, self)
2895
2915
  else if (showdown)
2896
2916
  battle.rng.range(u3, 2, 6)
2897
2917
  else
@@ -2905,6 +2925,8 @@ pub const Rolls = struct {
2905
2925
  fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
2906
2926
  const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2907
2927
  @intCast(val)
2928
+ else if (@hasDecl(@TypeOf(battle.rng), "attackingDuration"))
2929
+ battle.rng.attackingDuration(player)
2908
2930
  else if (showdown)
2909
2931
  battle.rng.range(u3, 2, 4)
2910
2932
  else
@@ -2926,6 +2948,8 @@ pub const Rolls = struct {
2926
2948
  const roll = if (effect == .Binding) .duration else .multi_hit;
2927
2949
  const n: u3 = if (options.calc.overridden(player, roll)) |val|
2928
2950
  @intCast(val)
2951
+ else if (@hasDecl(@TypeOf(battle.rng), "distribution"))
2952
+ battle.rng.distribution(player)
2929
2953
  else if (showdown)
2930
2954
  DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
2931
2955
  else n: {
@@ -2958,7 +2982,9 @@ pub const Rolls = struct {
2958
2982
  else
2959
2983
  null;
2960
2984
 
2961
- const slot: u4 = overridden orelse slot: {
2985
+ const slot: u4 = overridden orelse if (@hasDecl(@TypeOf(battle.rng), "moveSlot"))
2986
+ battle.rng.moveSlot(player, moves, check_pp)
2987
+ else slot: {
2962
2988
  if (showdown) {
2963
2989
  if (check_pp == 0) {
2964
2990
  var i: usize = moves.len;
@@ -3038,7 +3064,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3038
3064
  const side = battle.side(player);
3039
3065
  const foe = battle.foe(player);
3040
3066
 
3041
- var active = &side.active;
3067
+ const active = &side.active;
3042
3068
  const stored = side.stored();
3043
3069
 
3044
3070
  // While players are not given any input options on the cartridge in these cases,