@pkmn/engine 0.1.0-dev.6c5fdce2 → 0.1.0-dev.6cc9e067

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.
@@ -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 {
@@ -1059,7 +1053,7 @@ fn checkCriticalHit(battle: anytype, player: Player, move: Move.Data, options: a
1059
1053
  const side = battle.side(player);
1060
1054
 
1061
1055
  // Base speed is used for the critical hit calculation, even when Transform-ed
1062
- var rate = @as(u16, Species.chance(side.stored().species));
1056
+ var rate: u16 = Species.chance(side.stored().species);
1063
1057
  // GLITCH: Focus Energy reduces critical hit rate instead of increasing it
1064
1058
  rate = if (side.active.volatiles.FocusEnergy) rate / 2 else @min(rate * 2, 255);
1065
1059
  rate = if (move.effect == .HighCritical) @min(rate * 4, 255) else rate / 2;
@@ -1116,9 +1110,9 @@ fn calcDamage(
1116
1110
  def = @max((def / 4) & 255, if (showdown) 1 else 0);
1117
1111
  }
1118
1112
 
1119
- 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);
1120
1114
 
1121
- 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;
1122
1116
 
1123
1117
  if (def == 0) return false;
1124
1118
 
@@ -1401,7 +1395,7 @@ fn moveHit(
1401
1395
  const overwritten = overwrite and state > 0;
1402
1396
  assert(!overwritten or (0 < state and state <= 255 and !side.active.volatiles.Bide));
1403
1397
 
1404
- var accuracy = if (overwritten) state else @as(u16, move.accuracy);
1398
+ var accuracy: u16 = if (overwritten) state else move.accuracy;
1405
1399
  var boost = BOOSTS[@as(u4, @intCast(@as(i8, side.active.boosts.accuracy) + 6))];
1406
1400
  accuracy = accuracy * boost[0] / boost[1];
1407
1401
  boost = BOOSTS[@as(u4, @intCast(@as(i8, -foe.active.boosts.evasion) + 6))];
@@ -1678,7 +1672,6 @@ fn onBegin(
1678
1672
  residual: *bool,
1679
1673
  options: anytype,
1680
1674
  ) !void {
1681
- assert(move.effect.onBegin());
1682
1675
  return switch (move.effect) {
1683
1676
  .Confusion => Effects.confusion(battle, player, move, options),
1684
1677
  .Conversion => Effects.conversion(battle, player, options),
@@ -1701,7 +1694,6 @@ fn onBegin(
1701
1694
  }
1702
1695
 
1703
1696
  fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1704
- assert(move.effect.onEnd());
1705
1697
  return switch (move.effect) {
1706
1698
  .Bide => Effects.bide(battle, player, options),
1707
1699
  // zig fmt: off
@@ -1723,7 +1715,6 @@ fn alwaysHappens(
1723
1715
  residual: *bool,
1724
1716
  options: anytype,
1725
1717
  ) !void {
1726
- assert(move.effect.alwaysHappens());
1727
1718
  return switch (move.effect) {
1728
1719
  .DrainHP, .DreamEater => Effects.drainHP(battle, player, options),
1729
1720
  .Explode => Effects.explode(battle, player),
@@ -2698,6 +2689,8 @@ pub const Rolls = struct {
2698
2689
  fn speedTie(battle: anytype, options: anytype) !bool {
2699
2690
  const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
2700
2691
  player == .P1
2692
+ else if (@hasDecl(@TypeOf(battle.rng), "speedTie"))
2693
+ battle.rng.speedTie()
2701
2694
  else if (showdown)
2702
2695
  battle.rng.range(u8, 0, 2) == 0
2703
2696
  else
@@ -2710,6 +2703,8 @@ pub const Rolls = struct {
2710
2703
  fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
2711
2704
  const crit = if (options.calc.overridden(player, .critical_hit)) |val|
2712
2705
  val == .true
2706
+ else if (@hasDecl(@TypeOf(battle.rng), "criticalHit"))
2707
+ battle.rng.criticalHit(player, rate)
2713
2708
  else if (showdown)
2714
2709
  battle.rng.chance(u8, rate, 256)
2715
2710
  else
@@ -2722,6 +2717,8 @@ pub const Rolls = struct {
2722
2717
  fn damage(battle: anytype, player: Player, options: anytype) !u8 {
2723
2718
  const roll = if (options.calc.overridden(player, .damage)) |val|
2724
2719
  val
2720
+ else if (@hasDecl(@TypeOf(battle.rng), "damage"))
2721
+ battle.rng.damage(player)
2725
2722
  else roll: {
2726
2723
  if (showdown) break :roll battle.rng.range(u8, 217, 256);
2727
2724
  while (true) {
@@ -2738,6 +2735,8 @@ pub const Rolls = struct {
2738
2735
  fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) bool {
2739
2736
  const ok = if (options.calc.overridden(player, .hit)) |val|
2740
2737
  val == .true
2738
+ else if (@hasDecl(@TypeOf(battle.rng), "hit"))
2739
+ battle.rng.hit(player, accuracy)
2741
2740
  else if (showdown)
2742
2741
  battle.rng.chance(u8, accuracy, 256)
2743
2742
  else
@@ -2750,6 +2749,8 @@ pub const Rolls = struct {
2750
2749
  fn confused(battle: anytype, player: Player, options: anytype) !bool {
2751
2750
  const cfz = if (options.calc.overridden(player, .confused)) |val|
2752
2751
  val == .true
2752
+ else if (@hasDecl(@TypeOf(battle.rng), "confused"))
2753
+ battle.rng.confused(player)
2753
2754
  else if (showdown)
2754
2755
  !battle.rng.chance(u8, 128, 256)
2755
2756
  else
@@ -2762,6 +2763,8 @@ pub const Rolls = struct {
2762
2763
  fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
2763
2764
  const par = if (options.calc.overridden(player, .paralyzed)) |val|
2764
2765
  val == .true
2766
+ else if (@hasDecl(@TypeOf(battle.rng), "paralyzed"))
2767
+ battle.rng.paralyzed(player)
2765
2768
  else if (showdown)
2766
2769
  battle.rng.chance(u8, 63, 256)
2767
2770
  else
@@ -2774,6 +2777,8 @@ pub const Rolls = struct {
2774
2777
  fn confusionChance(battle: anytype, player: Player, options: anytype) !bool {
2775
2778
  const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
2776
2779
  val == .true
2780
+ else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
2781
+ battle.rng.secondaryChance(player, 25)
2777
2782
  else if (showdown)
2778
2783
  battle.rng.chance(u8, 25, 256)
2779
2784
  else
@@ -2788,6 +2793,8 @@ pub const Rolls = struct {
2788
2793
 
2789
2794
  const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
2790
2795
  val == .true
2796
+ else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
2797
+ battle.rng.secondaryChance(player, rate)
2791
2798
  else if (showdown)
2792
2799
  battle.rng.chance(u8, rate, 256)
2793
2800
  else
@@ -2802,6 +2809,8 @@ pub const Rolls = struct {
2802
2809
 
2803
2810
  const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
2804
2811
  val == .true
2812
+ else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
2813
+ battle.rng.secondaryChance(player, rate)
2805
2814
  else if (showdown)
2806
2815
  battle.rng.chance(u8, rate, 256)
2807
2816
  else
@@ -2814,6 +2823,8 @@ pub const Rolls = struct {
2814
2823
  fn unboost(battle: anytype, player: Player, options: anytype) !bool {
2815
2824
  const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
2816
2825
  val == .true
2826
+ else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
2827
+ battle.rng.secondaryChance(player, 85)
2817
2828
  else if (showdown)
2818
2829
  battle.rng.chance(u8, 85, 256)
2819
2830
  else
@@ -2826,6 +2837,8 @@ pub const Rolls = struct {
2826
2837
  fn metronome(battle: anytype, player: Player, options: anytype) !Move {
2827
2838
  const move: Move = if (options.calc.overridden(player, .metronome)) |val|
2828
2839
  val
2840
+ else if (@hasDecl(@TypeOf(battle.rng), "metronome"))
2841
+ battle.rng.metronome(player)
2829
2842
  else if (showdown)
2830
2843
  Move.METRONOME[battle.rng.range(u8, 0, Move.METRONOME.len)]
2831
2844
  else move: {
@@ -2844,6 +2857,8 @@ pub const Rolls = struct {
2844
2857
  fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
2845
2858
  const power = if (options.calc.overridden(player, .psywave)) |val|
2846
2859
  val - 1
2860
+ else if (@hasDecl(@TypeOf(battle.rng), "psywave"))
2861
+ battle.rng.psywave(player)
2847
2862
  else if (showdown)
2848
2863
  battle.rng.range(u8, 0, max)
2849
2864
  else power: {
@@ -2861,6 +2876,8 @@ pub const Rolls = struct {
2861
2876
  fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
2862
2877
  const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2863
2878
  @intCast(val)
2879
+ else if (@hasDecl(@TypeOf(battle.rng), "sleepDuration"))
2880
+ battle.rng.sleepDuration(player)
2864
2881
  else if (showdown)
2865
2882
  battle.rng.range(u3, 1, 8)
2866
2883
  else duration: {
@@ -2878,6 +2895,8 @@ pub const Rolls = struct {
2878
2895
  fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
2879
2896
  const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
2880
2897
  val
2898
+ else if (@hasDecl(@TypeOf(battle.rng), "disableDuration"))
2899
+ battle.rng.disableDuration(player)
2881
2900
  else if (showdown)
2882
2901
  battle.rng.range(u4, 1, 9)
2883
2902
  else
@@ -2891,6 +2910,8 @@ pub const Rolls = struct {
2891
2910
  fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
2892
2911
  const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2893
2912
  @intCast(val)
2913
+ else if (@hasDecl(@TypeOf(battle.rng), "confusionDuration"))
2914
+ battle.rng.confusionDuration(player, self)
2894
2915
  else if (showdown)
2895
2916
  battle.rng.range(u3, 2, 6)
2896
2917
  else
@@ -2904,6 +2925,8 @@ pub const Rolls = struct {
2904
2925
  fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
2905
2926
  const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2906
2927
  @intCast(val)
2928
+ else if (@hasDecl(@TypeOf(battle.rng), "attackingDuration"))
2929
+ battle.rng.attackingDuration(player)
2907
2930
  else if (showdown)
2908
2931
  battle.rng.range(u3, 2, 4)
2909
2932
  else
@@ -2925,6 +2948,8 @@ pub const Rolls = struct {
2925
2948
  const roll = if (effect == .Binding) .duration else .multi_hit;
2926
2949
  const n: u3 = if (options.calc.overridden(player, roll)) |val|
2927
2950
  @intCast(val)
2951
+ else if (@hasDecl(@TypeOf(battle.rng), "distribution"))
2952
+ battle.rng.distribution(player)
2928
2953
  else if (showdown)
2929
2954
  DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
2930
2955
  else n: {
@@ -2957,7 +2982,9 @@ pub const Rolls = struct {
2957
2982
  else
2958
2983
  null;
2959
2984
 
2960
- 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: {
2961
2988
  if (showdown) {
2962
2989
  if (check_pp == 0) {
2963
2990
  var i: usize = moves.len;
@@ -14,7 +14,7 @@ const util = @import("../common/util.zig");
14
14
  const Player = @import("../common/data.zig").Player;
15
15
  const Optional = @import("../common/optional.zig").Optional;
16
16
 
17
- const data = @import("./data.zig");
17
+ const data = @import("data.zig");
18
18
 
19
19
  const enabled = options.chance;
20
20
  const showdown = options.showdown;
@@ -97,16 +97,17 @@ pub const Actions = extern struct {
97
97
  }
98
98
  };
99
99
 
100
+ // TODO: ziglang/zig#19730
100
101
  test Actions {
101
102
  const a: Actions = .{ .p1 = .{ .hit = .true, .critical_hit = .false, .damage = 245 } };
102
103
  const b: Actions = .{ .p1 = .{ .hit = .false, .critical_hit = .true, .damage = 246 } };
103
104
  const c: Actions = .{ .p1 = .{ .hit = .true } };
104
- const d: Actions = .{ .p2 = .{ .hit = .true, .durations = .{ .sleep = 2 } } };
105
- const e: Actions = .{ .p2 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
106
- const f: Actions = .{ .p1 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
107
- const g: Actions = .{ .p1 = .{ .hit = .false, .durations = .{ .confusion = 1 } } };
108
- const h: Actions =
109
- .{ .p1 = .{ .hit = .false, .durations = .{ .confusion = 1, .thrash = true } } };
105
+ // const d: Actions = .{ .p2 = .{ .hit = .true, .durations = .{ .sleep = 2 } } };
106
+ // const e: Actions = .{ .p2 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
107
+ // const f: Actions = .{ .p1 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
108
+ // const g: Actions = .{ .p1 = .{ .hit = .false, .durations = .{ .confusion = 1 } } };
109
+ // const h: Actions =
110
+ // .{ .p1 = .{ .hit = .false, .durations = .{ .confusion = 1, .thrash = true } } };
110
111
 
111
112
  try expect(a.eql(a));
112
113
  try expect(!a.eql(b));
@@ -119,10 +120,10 @@ test Actions {
119
120
  try expect(b.matches(a));
120
121
  try expect(!a.matches(c));
121
122
  try expect(!c.matches(a));
122
- try expect(d.matches(e));
123
- try expect(!d.matches(f));
124
- try expect(!g.matches(h));
125
- try expect(h.matches(h));
123
+ // try expect(d.matches(e));
124
+ // try expect(!d.matches(f));
125
+ // try expect(!g.matches(h));
126
+ // try expect(h.matches(h));
126
127
  }
127
128
 
128
129
  /// Information about the RNG that was observed during a Generation II battle `update` for a
@@ -699,7 +700,7 @@ test "Chance.damage" {
699
700
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
700
701
 
701
702
  try chance.damage(.P1, 217);
702
- try expectValue(@as(u8, 217), chance.actions.p1.damage);
703
+ try expectValue(217, chance.actions.p1.damage);
703
704
  try expectProbability(&chance.probability, 1, 39);
704
705
  }
705
706
 
@@ -824,7 +825,7 @@ test "Chance.present" {
824
825
  } else {
825
826
  try expectProbability(&chance.probability, 25, 256);
826
827
  }
827
- try expectValue(@as(u3, 4), chance.actions.p2.present);
828
+ try expectValue(4, chance.actions.p2.present);
828
829
 
829
830
  chance.reset();
830
831
 
@@ -834,7 +835,7 @@ test "Chance.present" {
834
835
  } else {
835
836
  try expectProbability(&chance.probability, 51, 256);
836
837
  }
837
- try expectValue(@as(u3, 1), chance.actions.p1.present);
838
+ try expectValue(1, chance.actions.p1.present);
838
839
  }
839
840
 
840
841
  test "Chance.magnitude" {
@@ -846,7 +847,7 @@ test "Chance.magnitude" {
846
847
  } else {
847
848
  try expectProbability(&chance.probability, 13, 256);
848
849
  }
849
- try expectValue(@as(u3, 7), chance.actions.p2.magnitude);
850
+ try expectValue(7, chance.actions.p2.magnitude);
850
851
 
851
852
  chance.reset();
852
853
 
@@ -856,7 +857,7 @@ test "Chance.magnitude" {
856
857
  } else {
857
858
  try expectProbability(&chance.probability, 51, 256);
858
859
  }
859
- try expectValue(@as(u3, 5), chance.actions.p1.magnitude);
860
+ try expectValue(5, chance.actions.p1.magnitude);
860
861
  }
861
862
 
862
863
  test "Chance.tripleKick" {
@@ -864,7 +865,7 @@ test "Chance.tripleKick" {
864
865
 
865
866
  try chance.tripleKick(.P1, 2);
866
867
  try expectProbability(&chance.probability, 1, 3);
867
- try expectValue(@as(u2, 2), chance.actions.p1.triple_kick);
868
+ try expectValue(2, chance.actions.p1.triple_kick);
868
869
  }
869
870
 
870
871
  test "Chance.spite" {
@@ -872,7 +873,7 @@ test "Chance.spite" {
872
873
 
873
874
  try chance.spite(.P1, 3);
874
875
  try expectProbability(&chance.probability, 1, 4);
875
- try expectValue(@as(u3, 3), chance.actions.p1.spite);
876
+ try expectValue(3, chance.actions.p1.spite);
876
877
  }
877
878
 
878
879
  test "Chance.conversion2" {
@@ -899,7 +900,7 @@ test "Chance.psywave" {
899
900
 
900
901
  try chance.psywave(.P2, 100, 150);
901
902
  try expectProbability(&chance.probability, 1, 150);
902
- try expectValue(@as(u8, 100), chance.actions.p2.psywave);
903
+ try expectValue(100, chance.actions.p2.psywave);
903
904
  }
904
905
 
905
906
  test "Chance.metronome" {
@@ -915,19 +916,19 @@ test "Chance.moveSlot" {
915
916
 
916
917
  try chance.moveSlot(.P2, 2, 2);
917
918
  try expectProbability(&chance.probability, 1, 2);
918
- try expectValue(@as(u4, 2), chance.actions.p2.move_slot);
919
+ try expectValue(2, chance.actions.p2.move_slot);
919
920
 
920
921
  chance.reset();
921
922
 
922
923
  try chance.moveSlot(.P1, 1, 3);
923
924
  try expectProbability(&chance.probability, 1, 3);
924
- try expectValue(@as(u4, 1), chance.actions.p1.move_slot);
925
+ try expectValue(1, chance.actions.p1.move_slot);
925
926
 
926
927
  chance.reset();
927
928
 
928
929
  try chance.moveSlot(.P1, 4, 1);
929
930
  try expectProbability(&chance.probability, 1, 1);
930
- try expectValue(@as(u4, 4), chance.actions.p1.move_slot);
931
+ try expectValue(4, chance.actions.p1.move_slot);
931
932
  }
932
933
 
933
934
  test "Chance.forceSwitch" {
@@ -935,19 +936,19 @@ test "Chance.forceSwitch" {
935
936
 
936
937
  try chance.forceSwitch(.P2, 2, 2);
937
938
  try expectProbability(&chance.probability, 1, 2);
938
- try expectValue(@as(u4, 2), chance.actions.p2.force_switch);
939
+ try expectValue(2, chance.actions.p2.force_switch);
939
940
 
940
941
  chance.reset();
941
942
 
942
943
  try chance.forceSwitch(.P1, 1, 3);
943
944
  try expectProbability(&chance.probability, 1, 3);
944
- try expectValue(@as(u4, 1), chance.actions.p1.force_switch);
945
+ try expectValue(1, chance.actions.p1.force_switch);
945
946
 
946
947
  chance.reset();
947
948
 
948
949
  try chance.forceSwitch(.P1, 4, 1);
949
950
  try expectProbability(&chance.probability, 1, 1);
950
- try expectValue(@as(u4, 4), chance.actions.p1.force_switch);
951
+ try expectValue(4, chance.actions.p1.force_switch);
951
952
  }
952
953
 
953
954
  test "Chance.multiHit" {
@@ -955,21 +956,21 @@ test "Chance.multiHit" {
955
956
 
956
957
  try chance.multiHit(.P1, 3);
957
958
  try expectProbability(&chance.probability, 3, 8);
958
- try expectValue(@as(u4, 3), chance.actions.p1.multi_hit);
959
+ try expectValue(3, chance.actions.p1.multi_hit);
959
960
 
960
961
  chance.reset();
961
962
 
962
963
  try chance.multiHit(.P2, 5);
963
964
  try expectProbability(&chance.probability, 1, 8);
964
- try expectValue(@as(u4, 5), chance.actions.p2.multi_hit);
965
+ try expectValue(5, chance.actions.p2.multi_hit);
965
966
  }
966
967
 
967
968
  test "Chance.duration" {
968
969
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
969
970
 
970
971
  chance.duration(.sleep, .P1, .P2, 2);
971
- try expectValue(@as(u4, 2), chance.actions.p1.duration);
972
- try expectValue(@as(u3, 1), chance.actions.p2.durations.sleep);
972
+ try expectValue(2, chance.actions.p1.duration);
973
+ try expectValue(1, chance.actions.p2.durations.sleep);
973
974
  }
974
975
 
975
976
  test "Chance.sleep" {
@@ -979,7 +980,7 @@ test "Chance.sleep" {
979
980
  chance.actions.p1.durations.sleep = @intCast(i);
980
981
  try chance.sleep(.P1, 0);
981
982
  try expectProbability(&chance.probability, 1, d);
982
- try expectValue(@as(u3, 0), chance.actions.p1.durations.sleep);
983
+ try expectValue(0, chance.actions.p1.durations.sleep);
983
984
 
984
985
  chance.reset();
985
986
 
@@ -1003,7 +1004,7 @@ test "Chance.confusion" {
1003
1004
  chance.actions.p2.durations.confusion = @intCast(i);
1004
1005
  try chance.confusion(.P2, 0);
1005
1006
  try expectProbability(&chance.probability, 1, d);
1006
- try expectValue(@as(u3, 0), chance.actions.p2.durations.confusion);
1007
+ try expectValue(0, chance.actions.p2.durations.confusion);
1007
1008
  try expectValue(false, chance.actions.p2.durations.thrash);
1008
1009
 
1009
1010
  chance.reset();
@@ -1028,7 +1029,7 @@ test "Chance.confusion" {
1028
1029
  chance.actions.p2.durations.confusion = @intCast(i);
1029
1030
  try chance.confusion(.P2, 0);
1030
1031
  try expectProbability(&chance.probability, 1, d);
1031
- try expectValue(@as(u3, 0), chance.actions.p2.durations.confusion);
1032
+ try expectValue(0, chance.actions.p2.durations.confusion);
1032
1033
  try expectValue(true, chance.actions.p2.durations.thrash);
1033
1034
 
1034
1035
  chance.reset();
@@ -1054,7 +1055,7 @@ test "Chance.disable" {
1054
1055
  chance.actions.p2.durations.disable = @intCast(i);
1055
1056
  try chance.disable(.P2, 0);
1056
1057
  try expectProbability(&chance.probability, 1, d);
1057
- try expectValue(@as(u4, 0), chance.actions.p2.durations.disable);
1058
+ try expectValue(0, chance.actions.p2.durations.disable);
1058
1059
 
1059
1060
  chance.reset();
1060
1061
  }
@@ -1074,7 +1075,7 @@ test "Chance.disable" {
1074
1075
  chance.actions.p2.durations.disable = @intCast(i);
1075
1076
  try chance.disable(.P2, 0);
1076
1077
  try expectProbability(&chance.probability, 1, d);
1077
- try expectValue(@as(u4, 0), chance.actions.p2.durations.disable);
1078
+ try expectValue(0, chance.actions.p2.durations.disable);
1078
1079
 
1079
1080
  chance.reset();
1080
1081
  }
@@ -1099,7 +1100,7 @@ test "Chance.attacking" {
1099
1100
  chance.actions.p2.durations.attacking = @intCast(i);
1100
1101
  try chance.attacking(.P2, 0);
1101
1102
  try expectProbability(&chance.probability, 1, d);
1102
- try expectValue(@as(u3, 0), chance.actions.p2.durations.attacking);
1103
+ try expectValue(0, chance.actions.p2.durations.attacking);
1103
1104
 
1104
1105
  chance.reset();
1105
1106
  }
@@ -1123,7 +1124,7 @@ test "Chance.binding" {
1123
1124
  chance.actions.p2.durations.binding = @intCast(i);
1124
1125
  try chance.binding(.P2, 0);
1125
1126
  try expectProbability(&chance.probability, 1, d);
1126
- try expectValue(@as(u3, 0), chance.actions.p2.durations.binding);
1127
+ try expectValue(0, chance.actions.p2.durations.binding);
1127
1128
 
1128
1129
  chance.reset();
1129
1130
  }
@@ -1147,7 +1148,7 @@ test "Chance.encore" {
1147
1148
  chance.actions.p2.durations.encore = @intCast(i);
1148
1149
  try chance.encore(.P2, 0);
1149
1150
  try expectProbability(&chance.probability, 1, d);
1150
- try expectValue(@as(u3, 0), chance.actions.p2.durations.encore);
1151
+ try expectValue(0, chance.actions.p2.durations.encore);
1151
1152
 
1152
1153
  chance.reset();
1153
1154
  }
@@ -248,12 +248,12 @@ test DVs {
248
248
  var dvs = DVs.from(.Mewtwo, .{ .def = 13 });
249
249
  try expectEqual(Gender.Unknown, dvs.gender);
250
250
  try expectEqual(Type.Ice, dvs.type);
251
- try expectEqual(@as(u8, 70), dvs.power());
251
+ try expectEqual(70, dvs.power());
252
252
 
253
253
  dvs = DVs.from(.Pikachu, .{ .spc = 14 });
254
254
  try expectEqual(Gender.Male, dvs.gender);
255
255
  try expectEqual(Type.Dark, dvs.type);
256
- try expectEqual(@as(u8, 69), dvs.power());
256
+ try expectEqual(69, dvs.power());
257
257
 
258
258
  dvs = DVs.from(.Cyndaquil, .{ .atk = 1, .def = 3, .spe = 10, .spc = 9 });
259
259
  try expectEqual(Gender.Female, dvs.gender);
@@ -380,9 +380,9 @@ test Stats {
380
380
  try expectEqual(0, stats.def);
381
381
 
382
382
  var base = Species.get(.Gyarados).stats;
383
- try expectEqual(@as(u16, 127), Stats(u16).calc("hp", base.hp, 0, 5120, 38));
383
+ try expectEqual(127, Stats(u16).calc("hp", base.hp, 0, 5120, 38));
384
384
  base = Species.get(.Pidgeot).stats;
385
- try expectEqual(@as(u16, 279), Stats(u16).calc("spe", base.spe, 15, 63001, 100));
385
+ try expectEqual(279, Stats(u16).calc("spe", base.spe, 15, 63001, 100));
386
386
  }
387
387
 
388
388
  pub const Boosts = packed struct(u32) {
@@ -428,8 +428,8 @@ test Move {
428
428
  try expectEqual(Move.Effect.ConfusionChance, move.effect);
429
429
  try expectEqual(rng.Gen12.percent(50), move.accuracy);
430
430
  try expectEqual(rng.Gen12.percent(100), move.chance);
431
- try expectEqual(@as(u8, 5), Move.pp(.DynamicPunch));
432
- try expectEqual(@as(i8, -1), Move.get(.Counter).priority);
431
+ try expectEqual(5, Move.pp(.DynamicPunch));
432
+ try expectEqual(-1, Move.get(.Counter).priority);
433
433
  try expect(!move.effect.isHighCritical());
434
434
  try expect(Move.get(.KarateChop).effect.isHighCritical());
435
435
  try expect(Move.get(.RazorWind).effect.isHighCritical());
@@ -439,7 +439,7 @@ pub const Species = species.Species;
439
439
 
440
440
  test Species {
441
441
  try expectEqual(152, @intFromEnum(Species.Chikorita));
442
- try expectEqual(@as(u8, 100), Species.get(.Celebi).stats.spd);
442
+ try expectEqual(100, Species.get(.Celebi).stats.spd);
443
443
  }
444
444
 
445
445
  pub const Type = types.Type;
@@ -452,7 +452,7 @@ test Types {
452
452
  try expect(!Type.Steel.special());
453
453
  try expect(Type.Dark.special());
454
454
 
455
- try expectEqual(@as(u8, 3), Type.Water.precedence());
455
+ try expectEqual(3, Type.Water.precedence());
456
456
  try expect(Type.Bug.precedence() > Type.Poison.precedence());
457
457
 
458
458
  try expectEqual(Effectiveness.Super, Type.effectiveness(.Ghost, .Psychic));
@@ -7,8 +7,6 @@ const common = @import("../common/data.zig");
7
7
  const data = @import("data.zig");
8
8
  const mechanics = @import("mechanics.zig");
9
9
 
10
- const assert = std.debug.assert;
11
-
12
10
  const Player = common.Player;
13
11
  const Result = common.Result;
14
12
 
@@ -65,17 +65,47 @@ pub const Battle = struct {
65
65
  };
66
66
 
67
67
  pub fn prng(rand: *PSRNG) data.PRNG {
68
- // FIXME: what is max range in gen2?
69
68
  return .{
70
69
  .src = .{
71
70
  .seed = if (showdown)
72
71
  rand.newSeed()
73
- else
74
- .{
75
- rand.range(u8, 0, 256), rand.range(u8, 0, 256), rand.range(u8, 0, 256),
76
- rand.range(u8, 0, 256), rand.range(u8, 0, 256), rand.range(u8, 0, 256),
77
- rand.range(u8, 0, 256), rand.range(u8, 0, 256), rand.range(u8, 0, 256),
78
- },
72
+ else seed: {
73
+ // GLITCH: The initial RNG seed in Gen II link battles is heavily constrained
74
+ // https://www.smogon.com/forums/threads/rng-in-rby-link-battles.3746779
75
+ var seed = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0 };
76
+
77
+ // Bytes in seed can only range from 0-252, not 0-255
78
+ const max: u8 = 253;
79
+ // Instructions take multiples of 4 cycles to execute
80
+ var timer: u16 = 4 * rand.range(u16, 0, 16384);
81
+ var add: u8 = rand.range(u8, 0, 256);
82
+ var sub: u8 = rand.range(u8, 0, 256);
83
+ var carry: u8 = 0;
84
+ var div: u8 = 0;
85
+
86
+ var i: u8 = 0;
87
+ while (i < seed.len) {
88
+ div = @intCast(timer >> 8);
89
+ // This is effectively @addWithOverflow but with three operands
90
+ const result = @as(u16, add) + @as(u16, div) + @as(u16, carry);
91
+ add = @intCast(result & 255);
92
+ carry = @intFromBool(result > 255);
93
+ timer +%= 44;
94
+ div = @intCast(timer >> 8);
95
+ sub -%= div +% carry;
96
+
97
+ if (sub < max) {
98
+ seed[i] = sub;
99
+ i += 1;
100
+ timer +%= 152;
101
+ carry = 1;
102
+ } else {
103
+ timer +%= 132;
104
+ carry = 0;
105
+ }
106
+ }
107
+ break :seed seed;
108
+ },
79
109
  },
80
110
  };
81
111
  }