@pkmn/engine 0.1.0-dev.46307385 → 0.1.0-dev.4cc7c5c2

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.
@@ -30,7 +30,7 @@ pub const Type = enum(u4) {
30
30
  Ice,
31
31
  Dragon,
32
32
 
33
- const CHART = [15][15]Effectiveness{
33
+ const CHART: [15][15]Effectiveness = .{
34
34
  [_]Effectiveness{ N, N, N, N, N, R, N, I, N, N, N, N, N, N, N }, // Normal
35
35
  [_]Effectiveness{ S, N, R, R, N, S, R, I, N, N, N, N, R, S, N }, // Fighting
36
36
  [_]Effectiveness{ N, S, N, N, N, R, S, N, N, N, S, R, N, N, N }, // Flying
@@ -99,7 +99,7 @@ pub const Type = enum(u4) {
99
99
  return CHART[@intFromEnum(t1)][@intFromEnum(t2)];
100
100
  }
101
101
 
102
- /// The precedence order of type `t2` vs. type `t1`.
102
+ /// The precedence order of Type `t2` vs. Type `t1`.
103
103
  pub fn precedence(t1: Type, t2: Type) u8 {
104
104
  for (PRECEDENCE, 0..) |matchup, i| {
105
105
  if (matchup.type1 == t1 and matchup.type2 == t2) return @intCast(i);
@@ -214,7 +214,7 @@ pub const Pokemon = extern struct {
214
214
  }
215
215
  };
216
216
 
217
- /// Representation of a Generation I & II Pokémon's move slot in a battle.
217
+ /// Representation of a Generation I Pokémon's move slot in a battle.
218
218
  pub const MoveSlot = extern struct {
219
219
  /// The identifier for the move.
220
220
  id: Move = .None,
@@ -222,20 +222,16 @@ pub const MoveSlot = extern struct {
222
222
  pp: u8 = 0,
223
223
 
224
224
  comptime {
225
- assert(@sizeOf(MoveSlot) == @sizeOf(u16));
225
+ assert(@sizeOf(MoveSlot) == 2);
226
226
  }
227
227
  };
228
228
 
229
229
  /// Details required to detect desyncs based on the move last selected/executed by players.
230
- pub const MoveDetails = packed struct {
230
+ pub const MoveDetails = packed struct(if (showdown) u16 else u8) {
231
231
  /// The move slot index of the last move selected in the battle menu.
232
232
  index: if (showdown) u8 else u4 = 0,
233
233
  /// Whether the last move executed was counterable.
234
234
  counterable: if (showdown) u8 else u4 = 0,
235
-
236
- comptime {
237
- assert(@sizeOf(MoveDetails) == if (showdown) 2 else 1);
238
- }
239
235
  };
240
236
 
241
237
  /// Bitfield representation of a Generation I & II Pokémon's major status condition.
@@ -329,7 +325,7 @@ test Status {
329
325
  }
330
326
 
331
327
  /// Bitfield representation of volatile statuses and associated data in Generation I.
332
- pub const Volatiles = packed struct {
328
+ pub const Volatiles = packed struct(u64) {
333
329
  /// Whether the "Bide" volatile status is present.
334
330
  Bide: bool = false,
335
331
  /// Whether the "Thrashing" volatile status is present.
@@ -387,14 +383,10 @@ pub const Volatiles = packed struct {
387
383
  disabled_move: u3 = 0,
388
384
  /// The number of turns toxic damage has been accumulating.
389
385
  toxic: u5 = 0,
390
-
391
- comptime {
392
- assert(@sizeOf(Volatiles) == 8);
393
- }
394
386
  };
395
387
 
396
388
  test Volatiles {
397
- var volatiles = Volatiles{};
389
+ var volatiles: Volatiles = .{};
398
390
  volatiles.Confusion = true;
399
391
  volatiles.confusion = 2;
400
392
  volatiles.Thrashing = true;
@@ -456,7 +448,7 @@ test Stats {
456
448
  }
457
449
 
458
450
  /// Representation of a Pokémon's boosts in Generation I.
459
- pub const Boosts = packed struct {
451
+ pub const Boosts = packed struct(u32) {
460
452
  /// A Pokémon's Attack boosts.
461
453
  atk: i4 = 0,
462
454
  /// A Pokémon's Defense boosts.
@@ -471,14 +463,10 @@ pub const Boosts = packed struct {
471
463
  evasion: i4 = 0,
472
464
 
473
465
  _: u8 = 0,
474
-
475
- comptime {
476
- assert(@sizeOf(Boosts) == 4);
477
- }
478
466
  };
479
467
 
480
468
  test Boosts {
481
- const boosts = Boosts{ .spc = -6 };
469
+ const boosts: Boosts = .{ .spc = -6 };
482
470
  try expectEqual(0, boosts.atk);
483
471
  try expectEqual(-6, boosts.spc);
484
472
  }
@@ -490,7 +478,7 @@ test Move {
490
478
  try expectEqual(2, @intFromEnum(Move.KarateChop));
491
479
  const move = Move.get(.Fissure);
492
480
  try expectEqual(Move.Effect.OHKO, move.effect);
493
- try expectEqual(@as(u8, 30), move.accuracy);
481
+ try expectEqual(rng.Gen12.percent(30), move.accuracy);
494
482
  try expectEqual(Type.Ground, move.type);
495
483
 
496
484
  try expect(!Move.Effect.onBegin(.None));
@@ -628,16 +616,16 @@ pub const DVs = struct {
628
616
  };
629
617
 
630
618
  test DVs {
631
- var dvs = DVs{ .spc = 15, .spe = 15 };
619
+ var dvs: DVs = .{ .spc = 15, .spe = 15 };
632
620
  try expectEqual(@as(u4, 15), dvs.hp());
633
- dvs = DVs{
621
+ dvs = .{
634
622
  .atk = 5,
635
623
  .def = 15,
636
624
  .spe = 13,
637
625
  .spc = 13,
638
626
  };
639
627
  try expectEqual(@as(u4, 15), dvs.hp());
640
- dvs = DVs{
628
+ dvs = .{
641
629
  .def = 3,
642
630
  .spe = 10,
643
631
  .spc = 11,
@@ -455,36 +455,21 @@ pub const Rolls = struct {
455
455
  }
456
456
 
457
457
  const MOVE_NONE = [_]Move{.None};
458
- const MOVES = init: {
459
- var moves: [Move.size - 2]Move = undefined;
460
- var i: usize = 0;
461
- for (@typeInfo(Move).Enum.fields) |f| {
462
- if (!(std.mem.eql(u8, f.name, "None") or
463
- std.mem.eql(u8, f.name, "Metronome") or
464
- std.mem.eql(u8, f.name, "Struggle") or
465
- std.mem.eql(u8, f.name, "SKIP_TURN")))
466
- {
467
- moves[i] = @field(Move, f.name);
468
- i += 1;
469
- }
470
- }
471
- break :init moves;
472
- };
473
458
 
474
459
  /// Returns a slice with the correct range of values for metronome given the `action` state.
475
460
  pub inline fn metronome(action: Action) []const Move {
476
- return if (@field(action, "metronome") == .None) &MOVE_NONE else &MOVES;
461
+ return if (@field(action, "metronome") == .None) &MOVE_NONE else &Move.METRONOME;
477
462
  }
478
463
  };
479
464
 
480
465
  test "Rolls.speedTie" {
481
- const actions = Actions{ .p1 = .{ .speed_tie = .P2 } };
466
+ const actions: Actions = .{ .p1 = .{ .speed_tie = .P2 } };
482
467
  try expectEqualSlices(Optional(Player), &.{ .P1, .P2 }, Rolls.speedTie(actions.p1));
483
468
  try expectEqualSlices(Optional(Player), &.{.None}, Rolls.speedTie(actions.p2));
484
469
  }
485
470
 
486
471
  test "Rolls.damage" {
487
- const actions = Actions{ .p2 = .{ .damage = 221 } };
472
+ const actions: Actions = .{ .p2 = .{ .damage = 221 } };
488
473
  try expectEqual(Rolls.Range{ .min = 0, .max = 1 }, Rolls.damage(actions.p1, .None));
489
474
  try expectEqual(Rolls.Range{ .min = 217, .max = 256 }, Rolls.damage(actions.p2, .None));
490
475
  try expectEqual(Rolls.Range{ .min = 0, .max = 1 }, Rolls.damage(actions.p2, .false));
@@ -502,7 +487,7 @@ test "Rolls.coalesce" {
502
487
  }
503
488
 
504
489
  test "Rolls.hit" {
505
- const actions = Actions{ .p2 = .{ .hit = .true } };
490
+ const actions: Actions = .{ .p2 = .{ .hit = .true } };
506
491
  try expectEqualSlices(Optional(bool), &.{.None}, Rolls.hit(actions.p1, .None));
507
492
  try expectEqualSlices(Optional(bool), &.{ .false, .true }, Rolls.hit(actions.p2, .None));
508
493
  try expectEqualSlices(Optional(bool), &.{.None}, Rolls.hit(actions.p2, .true));
@@ -510,7 +495,7 @@ test "Rolls.hit" {
510
495
  }
511
496
 
512
497
  test "Rolls.secondaryChance" {
513
- const actions = Actions{ .p1 = .{ .secondary_chance = .true } };
498
+ const actions: Actions = .{ .p1 = .{ .secondary_chance = .true } };
514
499
  try expectEqualSlices(
515
500
  Optional(bool),
516
501
  &.{ .false, .true },
@@ -521,7 +506,7 @@ test "Rolls.secondaryChance" {
521
506
  }
522
507
 
523
508
  test "Rolls.criticalHit" {
524
- const actions = Actions{ .p1 = .{ .critical_hit = .true } };
509
+ const actions: Actions = .{ .p1 = .{ .critical_hit = .true } };
525
510
  try expectEqualSlices(
526
511
  Optional(bool),
527
512
  &.{ .false, .true },
@@ -532,14 +517,14 @@ test "Rolls.criticalHit" {
532
517
  }
533
518
 
534
519
  test "Rolls.confused" {
535
- const actions = Actions{ .p2 = .{ .confused = .true } };
520
+ const actions: Actions = .{ .p2 = .{ .confused = .true } };
536
521
  try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p1, 1));
537
522
  try expectEqualSlices(Optional(bool), &.{ .false, .true }, Rolls.confused(actions.p2, 1));
538
523
  try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p2, 0));
539
524
  }
540
525
 
541
526
  test "Rolls.paralyzed" {
542
- const actions = Actions{ .p2 = .{ .paralyzed = .true } };
527
+ const actions: Actions = .{ .p2 = .{ .paralyzed = .true } };
543
528
  try expectEqualSlices(Optional(bool), &.{.None}, Rolls.paralyzed(actions.p1, .None));
544
529
  try expectEqualSlices(Optional(bool), &.{ .false, .true }, Rolls.paralyzed(actions.p2, .None));
545
530
  try expectEqualSlices(Optional(bool), &.{ .false, .true }, Rolls.paralyzed(actions.p2, .false));
@@ -547,7 +532,7 @@ test "Rolls.paralyzed" {
547
532
  }
548
533
 
549
534
  test "Rolls.duration" {
550
- const actions = Actions{ .p2 = .{ .duration = 3 } };
535
+ const actions: Actions = .{ .p2 = .{ .duration = 3 } };
551
536
  try expectEqualSlices(u1, &.{0}, Rolls.duration(actions.p1, .None));
552
537
  try expectEqualSlices(u1, &.{1}, Rolls.duration(actions.p2, .None));
553
538
  try expectEqualSlices(u1, &.{0}, Rolls.duration(actions.p2, .false));
@@ -757,27 +742,27 @@ test "Rolls.binding" {
757
742
  }
758
743
 
759
744
  test "Rolls.moveSlot" {
760
- const actions = Actions{ .p2 = .{ .move_slot = 3 } };
745
+ const actions: Actions = .{ .p2 = .{ .move_slot = 3 } };
761
746
  try expectEqualSlices(u4, &.{0}, Rolls.moveSlot(actions.p1, .None));
762
747
  try expectEqualSlices(u4, &.{ 1, 2, 3, 4 }, Rolls.moveSlot(actions.p2, .None));
763
748
  try expectEqualSlices(u4, &.{0}, Rolls.moveSlot(actions.p2, .false));
764
749
  }
765
750
 
766
751
  test "Rolls.multiHit" {
767
- const actions = Actions{ .p2 = .{ .multi_hit = 3 } };
752
+ const actions: Actions = .{ .p2 = .{ .multi_hit = 3 } };
768
753
  try expectEqualSlices(u4, &.{0}, Rolls.multiHit(actions.p1, .None));
769
754
  try expectEqualSlices(u4, &.{ 2, 3, 4, 5 }, Rolls.multiHit(actions.p2, .None));
770
755
  try expectEqualSlices(u4, &.{0}, Rolls.multiHit(actions.p2, .false));
771
756
  }
772
757
 
773
758
  test "Rolls.metronome" {
774
- const actions = Actions{ .p2 = .{ .metronome = .Surf } };
759
+ const actions: Actions = .{ .p2 = .{ .metronome = .Surf } };
775
760
  try expectEqualSlices(Move, &.{.None}, Rolls.metronome(actions.p1));
776
761
  try expectEqual(@as(Move, @enumFromInt(24)), Rolls.metronome(actions.p2)[23]);
777
762
  }
778
763
 
779
764
  test "Rolls.psywave" {
780
- const actions = Actions{ .p2 = .{ .psywave = 79 } };
765
+ const actions: Actions = .{ .p2 = .{ .psywave = 79 } };
781
766
  var side = Side.init(&[_]Pokemon{.{
782
767
  .species = .Bulbasaur,
783
768
  .level = 100,
@@ -7,8 +7,6 @@ const DEBUG = @import("../common/debug.zig").print;
7
7
  const protocol = @import("../common/protocol.zig");
8
8
  const rng = @import("../common/rng.zig");
9
9
 
10
- const Action = @import("chance.zig").Action;
11
-
12
10
  const data = @import("data.zig");
13
11
 
14
12
  const assert = std.debug.assert;
@@ -59,13 +57,7 @@ const BOOSTS = &[_][2]u8{
59
57
 
60
58
  const MAX_STAT_VALUE = 999;
61
59
 
62
- const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
63
-
64
60
  pub fn update(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Result {
65
- // FIXME
66
- // std.debug.print("\n", .{});
67
- // DEBUG(battle.turn);
68
- // std.debug.print("// ", .{});
69
61
  assert(c1.type != .Pass or c2.type != .Pass or battle.turn == 0);
70
62
  if (battle.turn == 0) return start(battle, options);
71
63
 
@@ -214,12 +206,9 @@ fn selectMove(
214
206
  }
215
207
  break :ok true;
216
208
  };
217
-
218
209
  assert(struggle);
219
- saveMove(battle, player, choice);
220
- } else {
221
- saveMove(battle, player, choice);
222
210
  }
211
+ saveMove(battle, player, choice);
223
212
 
224
213
  return null;
225
214
  }
@@ -1438,7 +1427,7 @@ fn moveHit(
1438
1427
  const overwritten = overwrite and state > 0;
1439
1428
  assert(!overwritten or (0 < state and state <= 255 and !side.active.volatiles.Bide));
1440
1429
 
1441
- var accuracy = if (overwritten) state else @as(u16, Gen12.percent(move.accuracy));
1430
+ var accuracy = if (overwritten) state else @as(u16, move.accuracy);
1442
1431
  var boost = BOOSTS[@as(u4, @intCast(@as(i8, side.active.boosts.accuracy) + 6))];
1443
1432
  accuracy = accuracy * boost[0] / boost[1];
1444
1433
  boost = BOOSTS[@as(u4, @intCast(@as(i8, -foe.active.boosts.evasion) + 6))];
@@ -1499,8 +1488,8 @@ fn checkFaint(
1499
1488
  }
1500
1489
 
1501
1490
  const foe_choice: Choice.Type = if (foe_fainted) .Switch else .Pass;
1502
- if (player == .P1) return Result{ .p1 = .Switch, .p2 = foe_choice };
1503
- return Result{ .p1 = foe_choice, .p2 = .Switch };
1491
+ if (player == .P1) return .{ .p1 = .Switch, .p2 = foe_choice };
1492
+ return .{ .p1 = foe_choice, .p2 = .Switch };
1504
1493
  }
1505
1494
 
1506
1495
  fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result {
@@ -1548,21 +1537,19 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
1548
1537
  var volatiles = &side.active.volatiles;
1549
1538
 
1550
1539
  const brn = Status.is(stored.status, .BRN);
1551
- if (brn or Status.is(stored.status, .PSN)) blk: {
1540
+ if (brn or Status.is(stored.status, .PSN)) {
1552
1541
  var damage = @max(stored.stats.hp / 16, 1);
1553
-
1554
1542
  if (volatiles.Toxic) {
1555
1543
  volatiles.toxic += 1;
1556
1544
  damage *= volatiles.toxic;
1557
1545
  }
1546
+ damage = @min(damage, stored.hp);
1558
1547
 
1559
- const amount = @min(damage, stored.hp);
1560
- if (showdown and amount == 0) break :blk;
1561
-
1562
- stored.hp -= amount;
1563
-
1564
- // Pokémon Showdown uses damageOf here but its not relevant in Generation I
1565
- try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
1548
+ if (!showdown or damage > 0) {
1549
+ stored.hp -= damage;
1550
+ // Pokémon Showdown uses damageOf here but its not relevant in Generation I
1551
+ try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
1552
+ }
1566
1553
  }
1567
1554
 
1568
1555
  if (volatiles.LeechSeed) {
@@ -2395,12 +2382,17 @@ pub const Effects = struct {
2395
2382
  }
2396
2383
 
2397
2384
  fn switchAndTeleport(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
2398
- if (!showdown or battle.side(player).last_selected_move == .Teleport) return;
2399
-
2400
2385
  // Whirlwind/Roar should not roll to hit/reset damage but Pokémon Showdown does anyway
2401
- if (try checkHit(battle, player, move, options)) try options.chance.commit(player, .hit);
2402
-
2403
- battle.last_damage = 0;
2386
+ if (showdown) {
2387
+ if (battle.side(player).last_selected_move == .Teleport) return;
2388
+ if (try checkHit(battle, player, move, options)) {
2389
+ try options.chance.commit(player, .hit);
2390
+ }
2391
+ battle.last_damage = 0;
2392
+ } else {
2393
+ try options.log.fail(battle.active(player), .None);
2394
+ try options.log.laststill();
2395
+ }
2404
2396
  }
2405
2397
 
2406
2398
  fn thrashing(battle: anytype, player: Player, options: anytype) void {
@@ -2705,15 +2697,15 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
2705
2697
  }
2706
2698
  if (volatiles.Mist) {
2707
2699
  volatiles.Mist = false;
2708
- try log.end(ident, .Mist);
2700
+ try log.end(ident, .MistSilent);
2709
2701
  }
2710
2702
  if (volatiles.FocusEnergy) {
2711
2703
  volatiles.FocusEnergy = false;
2712
- try log.end(ident, .FocusEnergy);
2704
+ try log.end(ident, .FocusEnergySilent);
2713
2705
  }
2714
2706
  if (volatiles.LeechSeed) {
2715
2707
  volatiles.LeechSeed = false;
2716
- try log.end(ident, .LeechSeed);
2708
+ try log.end(ident, .LeechSeedSilent);
2717
2709
  }
2718
2710
  if (!showdown and volatiles.Toxic) {
2719
2711
  volatiles.Toxic = false;
@@ -2721,17 +2713,17 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
2721
2713
  }
2722
2714
  if (volatiles.LightScreen) {
2723
2715
  volatiles.LightScreen = false;
2724
- try log.end(ident, .LightScreen);
2716
+ try log.end(ident, .LightScreenSilent);
2725
2717
  }
2726
2718
  if (volatiles.Reflect) {
2727
2719
  volatiles.Reflect = false;
2728
- try log.end(ident, .Reflect);
2720
+ try log.end(ident, .ReflectSilent);
2729
2721
  }
2730
2722
  if (showdown and volatiles.Toxic) {
2731
2723
  volatiles.Toxic = false;
2732
2724
  // Pokémon Showdown erroneously clears the toxic counter
2733
2725
  volatiles.toxic = 0;
2734
- try log.end(ident, .Toxic);
2726
+ try log.end(ident, .ToxicSilent);
2735
2727
  }
2736
2728
  }
2737
2729
 
@@ -2867,11 +2859,9 @@ pub const Rolls = struct {
2867
2859
  inline fn metronome(battle: anytype, player: Player, options: anytype) !Move {
2868
2860
  const move: Move = if (options.calc.overridden(player, .metronome)) |val|
2869
2861
  val
2870
- else if (showdown) move: {
2871
- const r = battle.rng.range(u8, 0, @intFromEnum(Move.Struggle) - 2);
2872
- const mod = @as(u2, (if (r < @intFromEnum(Move.Metronome) - 1) 1 else 2));
2873
- break :move @enumFromInt(r + mod);
2874
- } else move: {
2862
+ else if (showdown)
2863
+ Move.METRONOME[battle.rng.range(u8, 0, Move.METRONOME.len)]
2864
+ else move: {
2875
2865
  while (true) {
2876
2866
  const r = battle.rng.next();
2877
2867
  if (r == 0 or r == @intFromEnum(Move.Metronome)) continue;
@@ -2905,7 +2895,7 @@ pub const Rolls = struct {
2905
2895
  const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2906
2896
  @intCast(val)
2907
2897
  else if (showdown)
2908
- @intCast(battle.rng.range(u8, 1, 8))
2898
+ battle.rng.range(u3, 1, 8)
2909
2899
  else duration: {
2910
2900
  while (true) {
2911
2901
  const r = battle.rng.next() & 7;
@@ -2922,7 +2912,7 @@ pub const Rolls = struct {
2922
2912
  const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
2923
2913
  val
2924
2914
  else if (showdown)
2925
- @intCast(battle.rng.range(u8, 1, 9))
2915
+ battle.rng.range(u4, 1, 9)
2926
2916
  else
2927
2917
  @intCast((battle.rng.next() & 7) + 1);
2928
2918
 
@@ -2935,7 +2925,7 @@ pub const Rolls = struct {
2935
2925
  const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2936
2926
  @intCast(val)
2937
2927
  else if (showdown)
2938
- @intCast(battle.rng.range(u8, 2, 6))
2928
+ battle.rng.range(u3, 2, 6)
2939
2929
  else
2940
2930
  @intCast((battle.rng.next() & 3) + 2);
2941
2931
 
@@ -2948,7 +2938,7 @@ pub const Rolls = struct {
2948
2938
  const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2949
2939
  @intCast(val)
2950
2940
  else if (showdown)
2951
- @intCast(battle.rng.range(u4, 2, 4))
2941
+ battle.rng.range(u3, 2, 4)
2952
2942
  else
2953
2943
  @intCast((battle.rng.next() & 1) + 2);
2954
2944
 
@@ -2957,6 +2947,8 @@ pub const Rolls = struct {
2957
2947
  return duration;
2958
2948
  }
2959
2949
 
2950
+ const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
2951
+
2960
2952
  inline fn distribution(
2961
2953
  battle: anytype,
2962
2954
  comptime effect: Move.Effect,
@@ -2967,7 +2959,7 @@ pub const Rolls = struct {
2967
2959
  const n: u3 = if (options.calc.overridden(player, roll)) |val|
2968
2960
  @intCast(val)
2969
2961
  else if (showdown)
2970
- DISTRIBUTION[battle.rng.range(u8, 0, DISTRIBUTION.len)]
2962
+ DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
2971
2963
  else n: {
2972
2964
  const r = (battle.rng.next() & 3);
2973
2965
  break :n @intCast((if (r < 2) r else battle.rng.next() & 3) + 2);
@@ -3014,10 +3006,9 @@ pub const Rolls = struct {
3014
3006
  while (i < moves.len and r > 0) : (i += 1) {
3015
3007
  if (moves[i].pp > 0) {
3016
3008
  r -= 1;
3017
- if (r == 0) break;
3009
+ if (r == 0) break :slot @intCast(i + 1);
3018
3010
  }
3019
3011
  }
3020
- break :slot @intCast(i + 1);
3021
3012
  }
3022
3013
  }
3023
3014
 
@@ -3039,11 +3030,11 @@ test "RNG agreement" {
3039
3030
  expected[i] = @intCast(i * 0x1000000);
3040
3031
  }
3041
3032
 
3042
- var spe = rng.FixedRNG(1, expected.len){ .rolls = expected };
3043
- var cfz = rng.FixedRNG(1, expected.len){ .rolls = expected };
3044
- var par = rng.FixedRNG(1, expected.len){ .rolls = expected };
3045
- var brn = rng.FixedRNG(1, expected.len){ .rolls = expected };
3046
- var eff = rng.FixedRNG(1, expected.len){ .rolls = expected };
3033
+ var spe: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
3034
+ var cfz: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
3035
+ var par: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
3036
+ var brn: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
3037
+ var eff: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
3047
3038
 
3048
3039
  for (0..expected.len) |i| {
3049
3040
  try expectEqual(spe.range(u8, 0, 2) == 0, i < Gen12.percent(50) + 1);