@pkmn/engine 0.1.0-dev.bcb16a6c → 0.1.0-dev.be951af1

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.
@@ -378,9 +378,9 @@ pub const Volatiles = packed struct(u64) {
378
378
  /// The identity of whom the active Pokémon is transformed into.
379
379
  transform: u4 = 0,
380
380
  /// The remaining turns the move is disabled.
381
- disabled_duration: u4 = 0,
381
+ disable_duration: u4 = 0,
382
382
  /// The move slot (1-4) that is disabled.
383
- disabled_move: u3 = 0,
383
+ disable_move: u3 = 0,
384
384
  /// The number of turns toxic damage has been accumulating.
385
385
  toxic: u5 = 0,
386
386
  };
@@ -395,8 +395,8 @@ test Volatiles {
395
395
  volatiles.Substitute = true;
396
396
  volatiles.substitute = 42;
397
397
  volatiles.toxic = 4;
398
- volatiles.disabled_move = 2;
399
- volatiles.disabled_duration = 4;
398
+ volatiles.disable_move = 2;
399
+ volatiles.disable_duration = 4;
400
400
 
401
401
  try expect(volatiles.Confusion);
402
402
  try expect(volatiles.Thrashing);
@@ -407,8 +407,8 @@ test Volatiles {
407
407
 
408
408
  try expectEqual(@as(u16, 235), volatiles.state);
409
409
  try expectEqual(@as(u8, 42), volatiles.substitute);
410
- try expectEqual(@as(u4, 2), volatiles.disabled_move);
411
- try expectEqual(@as(u4, 4), volatiles.disabled_duration);
410
+ try expectEqual(@as(u4, 2), volatiles.disable_move);
411
+ try expectEqual(@as(u4, 4), volatiles.disable_duration);
412
412
  try expectEqual(@as(u4, 2), volatiles.confusion);
413
413
  try expectEqual(@as(u5, 4), volatiles.toxic);
414
414
  try expectEqual(@as(u4, 3), volatiles.attacks);
@@ -455,25 +455,10 @@ 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
 
@@ -7,21 +7,19 @@ 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;
15
13
 
16
14
  const expectEqual = std.testing.expectEqual;
17
15
 
16
+ const showdown = pkmn.options.showdown;
17
+
18
18
  const Choice = common.Choice;
19
19
  const ID = common.ID;
20
20
  const Player = common.Player;
21
21
  const Result = common.Result;
22
22
 
23
- const showdown = pkmn.options.showdown;
24
-
25
23
  const Boost = protocol.Boost;
26
24
  const Damage = protocol.Damage;
27
25
  const Heal = protocol.Heal;
@@ -59,19 +57,15 @@ 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
61
  assert(c1.type != .Pass or c2.type != .Pass or battle.turn == 0);
66
62
  if (battle.turn == 0) return start(battle, options);
67
63
 
68
64
  var s1 = false;
69
65
  var s2 = false;
70
- var f1: ?Move = null;
71
- var f2: ?Move = null;
72
66
 
73
- if (selectMove(battle, .P1, c1, c2, &s1, &f1)) |r| return r;
74
- if (selectMove(battle, .P2, c2, c1, &s2, &f2)) |r| return r;
67
+ if (selectMove(battle, .P1, c1, c2, &s1)) |r| return r;
68
+ if (selectMove(battle, .P2, c2, c1, &s2)) |r| return r;
75
69
 
76
70
  var p1 = battle.side(.P1);
77
71
  var p2 = battle.side(.P2);
@@ -80,9 +74,9 @@ pub fn update(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Result
80
74
  var r2 = showdown and p2.active.volatiles.Binding and c1.type == .Switch;
81
75
 
82
76
  if (try turnOrder(battle, c1, c2, options) == .P1) {
83
- if (try doTurn(battle, .P1, c1, r1, s1, f1, .P2, c2, r2, s2, f2, options)) |r| return r;
77
+ if (try doTurn(battle, .P1, c1, r1, s1, .P2, c2, r2, s2, options)) |r| return r;
84
78
  } else {
85
- if (try doTurn(battle, .P2, c2, r2, s2, f2, .P1, c1, r1, s1, f1, options)) |r| return r;
79
+ if (try doTurn(battle, .P2, c2, r2, s2, .P1, c1, r1, s1, options)) |r| return r;
86
80
  }
87
81
 
88
82
  if (p1.active.volatiles.attacks == 0) p1.active.volatiles.Binding = false;
@@ -120,7 +114,6 @@ fn selectMove(
120
114
  choice: Choice,
121
115
  foe_choice: Choice,
122
116
  skip_turn: *bool,
123
- from: *?Move,
124
117
  ) ?Result {
125
118
  if (choice.type == .Pass) return null;
126
119
 
@@ -137,7 +130,6 @@ fn selectMove(
137
130
  return null;
138
131
  }
139
132
  if (volatiles.Rage) {
140
- from.* = side.last_used_move;
141
133
  if (showdown) {
142
134
  if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
143
135
  saveMove(battle, player, null);
@@ -147,7 +139,6 @@ fn selectMove(
147
139
  // Pokémon Showdown removes Flinch at the end-of-turn in its residual handler
148
140
  if (!showdown) volatiles.Flinch = false;
149
141
  if (volatiles.Thrashing or volatiles.Charging) {
150
- from.* = side.last_selected_move;
151
142
  if (showdown) {
152
143
  if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
153
144
  saveMove(battle, player, null);
@@ -168,9 +159,7 @@ fn selectMove(
168
159
  return null;
169
160
  }
170
161
  if (volatiles.Binding) {
171
- from.* = side.last_used_move;
172
162
  if (showdown) {
173
- if (foe_choice.type == .Switch) from.* = null;
174
163
  // Pokémon Showdown overwrites Mirror Move with whatever was selected - really this
175
164
  // should set side.last_selected_move = last.id to reuse Mirror Move and fail in order
176
165
  // to satisfy the conditions of the Desync Clause Mod. However, because Binding is still
@@ -206,7 +195,7 @@ fn selectMove(
206
195
  if (choice.data == 0) {
207
196
  const struggle = ok: {
208
197
  for (side.active.moves, 0..) |move, i| {
209
- if (move.pp > 0 and volatiles.disabled_move != i + 1) break :ok false;
198
+ if (move.pp > 0 and volatiles.disable_move != i + 1) break :ok false;
210
199
  }
211
200
  break :ok true;
212
201
  };
@@ -225,7 +214,7 @@ fn saveMove(battle: anytype, player: Player, choice: ?Choice) void {
225
214
  if (c.data == 0) {
226
215
  side.last_selected_move = .Struggle;
227
216
  } else {
228
- assert(showdown or side.active.volatiles.disabled_move != c.data);
217
+ assert(showdown or side.active.volatiles.disable_move != c.data);
229
218
  const move = side.active.move(c.data);
230
219
  // You cannot *select* a move with 0 PP (except on Pokémon Showdown where that is
231
220
  // sometimes required...), but a 0 PP move can be used automatically
@@ -336,12 +325,10 @@ fn doTurn(
336
325
  player_choice: Choice,
337
326
  player_rewrap: bool,
338
327
  player_skip: bool,
339
- player_from: ?Move,
340
328
  foe_player: Player,
341
329
  foe_choice: Choice,
342
330
  foe_rewrap: bool,
343
331
  foe_skip: bool,
344
- foe_from: ?Move,
345
332
  options: anytype,
346
333
  ) !?Result {
347
334
  assert(player_choice.type != .Pass);
@@ -354,7 +341,6 @@ fn doTurn(
354
341
  player_choice,
355
342
  player_rewrap,
356
343
  player_skip,
357
- player_from,
358
344
  &residual,
359
345
  options,
360
346
  )) |r| return r;
@@ -377,7 +363,6 @@ fn doTurn(
377
363
  foe_choice,
378
364
  foe_rewrap,
379
365
  foe_skip,
380
- foe_from,
381
366
  &residual,
382
367
  options,
383
368
  )) |r| return r;
@@ -405,7 +390,6 @@ fn executeMove(
405
390
  choice: Choice,
406
391
  rewrap: bool,
407
392
  skip: bool,
408
- from: ?Move,
409
393
  residual: *bool,
410
394
  options: anytype,
411
395
  ) !?Result {
@@ -440,8 +424,10 @@ fn executeMove(
440
424
  mslot = @intCast(battle.lastMove(player).index);
441
425
  const stored = side.stored();
442
426
  // GLITCH: Struggle bypass PP underflow via Hyper Beam / Trapping-switch auto selection
443
- auto = side.last_selected_move == .HyperBeam or side.active.volatiles.Bide or
444
- from != null or Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP);
427
+ auto = isForced(&side.active) or
428
+ side.active.volatiles.Binding or side.active.volatiles.Bide or
429
+ side.last_selected_move == .HyperBeam or
430
+ Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP);
445
431
  // If it wasn't Hyper Beam or the continuation of a move effect then we must have just
446
432
  // thawed, in which case we will desync unless the last_selected_move happened to be at
447
433
  // index 1 and the current Pokémon has the same move in its first slot.
@@ -463,7 +449,7 @@ fn executeMove(
463
449
 
464
450
  var skip_can = false;
465
451
  var skip_pp = false;
466
- switch (try beforeMove(battle, player, skip, from, residual, options)) {
452
+ switch (try beforeMove(battle, player, skip, residual, options)) {
467
453
  .done => return null,
468
454
  .skip_can => skip_can = true,
469
455
  .skip_pp => skip_pp = true,
@@ -471,9 +457,9 @@ fn executeMove(
471
457
  .err => return @as(?Result, Result.Error),
472
458
  }
473
459
 
474
- if (!skip_can and !try canMove(battle, player, mslot, auto, skip_pp, from, residual, options)) {
475
- return null;
476
- }
460
+ const can = skip_can or
461
+ try canMove(battle, player, mslot, auto, skip_pp, .None, residual, options);
462
+ if (!can) return null;
477
463
 
478
464
  return doMove(battle, player, mslot, rewrap, auto, residual, options);
479
465
  }
@@ -484,7 +470,6 @@ fn beforeMove(
484
470
  battle: anytype,
485
471
  player: Player,
486
472
  skip: bool,
487
- from: ?Move,
488
473
  residual: *bool,
489
474
  options: anytype,
490
475
  ) !BeforeMove {
@@ -545,16 +530,16 @@ fn beforeMove(
545
530
  return .done;
546
531
  }
547
532
 
548
- if (volatiles.disabled_duration > 0) {
533
+ if (volatiles.disable_duration > 0) {
549
534
  if (options.calc.modify(player, .disable)) |extend| {
550
- if (!extend) volatiles.disabled_duration = 0;
535
+ if (!extend) volatiles.disable_duration = 0;
551
536
  } else {
552
- volatiles.disabled_duration -= 1;
537
+ volatiles.disable_duration -= 1;
553
538
  }
554
- try options.chance.disable(player, volatiles.disabled_duration);
539
+ try options.chance.disable(player, volatiles.disable_duration);
555
540
 
556
- if (volatiles.disabled_duration == 0) {
557
- volatiles.disabled_move = 0;
541
+ if (volatiles.disable_duration == 0) {
542
+ volatiles.disable_move = 0;
558
543
  try log.end(ident, .Disable);
559
544
  }
560
545
  }
@@ -691,7 +676,6 @@ fn beforeMove(
691
676
 
692
677
  if (volatiles.Thrashing) {
693
678
  assert(volatiles.attacks > 0);
694
- assert(from != null);
695
679
 
696
680
  if (options.calc.modify(player, .attacking)) |extend| {
697
681
  if (!extend) volatiles.attacks = 0;
@@ -703,7 +687,7 @@ fn beforeMove(
703
687
  if (!showdown and handleThrashing(battle, active, player, options)) {
704
688
  try log.start(battle.active(player), .ConfusionSilent);
705
689
  }
706
- try log.move(ident, side.last_selected_move, battle.active(player.foe()), from);
690
+ try log.move(ident, side.last_selected_move, battle.active(player.foe()));
707
691
  if (showdown) {
708
692
  // This shouldn't actually set last_used_move, but Pokémon Showdown sets last
709
693
  // used in useMove and doesn't have the notion of skipping canMove semantics
@@ -725,12 +709,7 @@ fn beforeMove(
725
709
  }
726
710
  try options.chance.binding(player, volatiles.attacks);
727
711
 
728
- try log.move(
729
- battle.active(player),
730
- side.last_selected_move,
731
- battle.active(player.foe()),
732
- side.last_selected_move,
733
- );
712
+ try log.move(battle.active(player), side.last_selected_move, battle.active(player.foe()));
734
713
  if (showdown or battle.last_damage != 0) {
735
714
  const sub = showdown and foe.active.volatiles.Substitute;
736
715
  _ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
@@ -750,7 +729,7 @@ fn canMove(
750
729
  mslot: u4,
751
730
  auto: bool,
752
731
  skip_pp: bool,
753
- from: ?Move,
732
+ from: Move,
754
733
  residual: *bool,
755
734
  options: anytype,
756
735
  ) !bool {
@@ -758,33 +737,22 @@ fn canMove(
758
737
  const player_ident = battle.active(player);
759
738
  const move = Move.get(side.last_selected_move);
760
739
 
761
- const foe_last = battle.foe(player).last_used_move;
762
- const special = from != null and (from.? == .Metronome and
763
- (from.? == .MirrorMove and !(foe_last == .None or foe_last == .MirrorMove)));
764
-
765
740
  var skip = skip_pp;
766
741
  if (side.active.volatiles.Charging) {
767
742
  side.active.volatiles.Charging = false;
768
743
  side.active.volatiles.Invulnerable = false;
769
744
  } else if (move.effect == .Charge) {
770
- try options.log.move(player_ident, side.last_selected_move, .{}, from);
745
+ try options.log.moveFrom(player_ident, side.last_selected_move, .{}, from);
771
746
  setCounterable(battle, player, side, move);
772
747
  try Effects.charge(battle, player, options);
773
748
  return false;
774
749
  }
775
750
 
776
- if (!showdown or !special) side.last_used_move = side.last_selected_move;
751
+ side.last_used_move = side.last_selected_move;
777
752
  if (!skip) decrementPP(side, mslot, auto);
778
753
 
779
754
  const target = if (move.target == .Self) player else player.foe();
780
- // Pokémon Showdown's protocol for Rage and Binding moves should come with a [from], though we
781
- // don't have space to be able to track it in all circumstances and thus usually try to infer it
782
- // from side.last_used_move. However, this gets reset by the opponent switching or fainting,
783
- // meaning sometimes we lose this information. from is still set because it not being null is
784
- // relevant for control flow, but we don't have anything useful to put in the log. This is
785
- // regrettable, but this information is more "nice to have" than required
786
- const f = if (from != null and from.? == .None) null else from;
787
- try options.log.move(player_ident, side.last_selected_move, battle.active(target), f);
755
+ try options.log.moveFrom(player_ident, side.last_selected_move, battle.active(target), from);
788
756
  setCounterable(battle, player, side, move);
789
757
 
790
758
  if (move.effect.onBegin()) {
@@ -822,12 +790,14 @@ fn decrementPP(side: *Side, mslot: u4, auto: bool) void {
822
790
  assert(!volatiles.Rage and !volatiles.Thrashing and !volatiles.MultiHit);
823
791
  if (volatiles.Bide) return;
824
792
 
825
- assert(active.move(mslot).pp > 0 or auto);
826
- active.move(mslot).pp = @as(u6, @intCast(active.move(mslot).pp)) -% 1;
793
+ var move_slot = active.move(mslot);
794
+ assert(move_slot.pp > 0 or auto);
795
+ move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
827
796
  if (volatiles.Transform) return;
828
797
 
829
- assert(side.stored().move(mslot).pp > 0 or auto);
830
- side.stored().move(mslot).pp = @as(u6, @intCast(side.stored().move(mslot).pp)) -% 1;
798
+ move_slot = side.stored().move(mslot);
799
+ assert(move_slot.pp > 0 or auto);
800
+ move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
831
801
  assert(active.move(mslot).pp == side.stored().move(mslot).pp);
832
802
  }
833
803
 
@@ -1470,9 +1440,8 @@ fn checkFaint(
1470
1440
  const foe_fainted = foe.stored().hp == 0;
1471
1441
 
1472
1442
  const player_out = findFirstAlive(side) == 0;
1473
- const foe_out = findFirstAlive(foe) == 0;
1474
- const tie = player_out and foe_out;
1475
- const more = tie or player_out or foe_out;
1443
+ const foe_out = foe_fainted and findFirstAlive(foe) == 0;
1444
+ const more = player_out or foe_out;
1476
1445
 
1477
1446
  if (try faint(battle, player, !(more or foe_fainted), options)) |r| return r;
1478
1447
  if (foe_fainted) if (try faint(battle, player.foe(), !more, options)) |r| return r;
@@ -1480,7 +1449,7 @@ fn checkFaint(
1480
1449
  assert(!side.active.volatiles.MultiHit);
1481
1450
  assert(!foe.active.volatiles.MultiHit);
1482
1451
 
1483
- if (tie) {
1452
+ if (player_out and foe_out) {
1484
1453
  try options.log.tie();
1485
1454
  return Result.Tie;
1486
1455
  } else if (player_out) {
@@ -1666,9 +1635,9 @@ fn checkEBC(battle: anytype) bool {
1666
1635
  }
1667
1636
 
1668
1637
  fn disabled(side: *Side, ident: ID, options: anytype) !bool {
1669
- if (side.active.volatiles.disabled_move != 0) {
1638
+ if (side.active.volatiles.disable_move != 0) {
1670
1639
  // A Pokémon that transforms after being disabled may end up with less move slots
1671
- const m = side.active.moves[side.active.volatiles.disabled_move - 1].id;
1640
+ const m = side.active.moves[side.active.volatiles.disable_move - 1].id;
1672
1641
  // side.last_selected_move can be Struggle here on Pokemon Showdown we need an extra check
1673
1642
  const last = if (showdown and m == .Bide and side.active.volatiles.Bide)
1674
1643
  m
@@ -1896,7 +1865,7 @@ pub const Effects = struct {
1896
1865
  try options.chance.commit(player, .hit);
1897
1866
  }
1898
1867
 
1899
- if (volatiles.disabled_move != 0) {
1868
+ if (volatiles.disable_move != 0) {
1900
1869
  try options.log.fail(foe_ident, .None);
1901
1870
  return null;
1902
1871
  }
@@ -1921,18 +1890,17 @@ pub const Effects = struct {
1921
1890
  return Result.Error;
1922
1891
  }
1923
1892
 
1924
- volatiles.disabled_move =
1893
+ volatiles.disable_move =
1925
1894
  @intCast(try Rolls.moveSlot(battle, player, &foe.active.moves, n, options));
1926
- volatiles.disabled_duration = Rolls.disableDuration(battle, player, options);
1895
+ volatiles.disable_duration = Rolls.disableDuration(battle, player, options);
1927
1896
 
1928
- const id = foe.active.move(volatiles.disabled_move).id;
1897
+ const id = foe.active.move(volatiles.disable_move).id;
1929
1898
  try options.log.startEffect(foe_ident, .Disable, id);
1930
1899
  return null;
1931
1900
  }
1932
1901
 
1933
1902
  fn drainHP(battle: anytype, player: Player, options: anytype) !void {
1934
- var side = battle.side(player);
1935
- var stored = side.stored();
1903
+ var stored = battle.side(player).stored();
1936
1904
 
1937
1905
  if (battle.last_damage == 0) {
1938
1906
  assert(showdown);
@@ -2689,9 +2657,9 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
2689
2657
 
2690
2658
  options.chance.clearDurations(who, clear);
2691
2659
 
2692
- if (volatiles.disabled_move != 0) {
2693
- volatiles.disabled_move = 0;
2694
- volatiles.disabled_duration = 0;
2660
+ if (volatiles.disable_move != 0) {
2661
+ volatiles.disable_move = 0;
2662
+ volatiles.disable_duration = 0;
2695
2663
  try log.end(ident, .DisableSilent);
2696
2664
  }
2697
2665
  if (volatiles.Confusion) {
@@ -2756,7 +2724,7 @@ pub const Rolls = struct {
2756
2724
  return crit;
2757
2725
  }
2758
2726
 
2759
- inline fn damage(battle: anytype, player: anytype, options: anytype) !u8 {
2727
+ inline fn damage(battle: anytype, player: Player, options: anytype) !u8 {
2760
2728
  const roll = if (options.calc.overridden(player, .damage)) |val|
2761
2729
  val
2762
2730
  else roll: {
@@ -2863,11 +2831,9 @@ pub const Rolls = struct {
2863
2831
  inline fn metronome(battle: anytype, player: Player, options: anytype) !Move {
2864
2832
  const move: Move = if (options.calc.overridden(player, .metronome)) |val|
2865
2833
  val
2866
- else if (showdown) move: {
2867
- const r = battle.rng.range(u8, 0, @intFromEnum(Move.Struggle) - 2);
2868
- const mod = @as(u2, (if (r < @intFromEnum(Move.Metronome) - 1) 1 else 2));
2869
- break :move @enumFromInt(r + mod);
2870
- } else move: {
2834
+ else if (showdown)
2835
+ Move.METRONOME[battle.rng.range(u8, 0, Move.METRONOME.len)]
2836
+ else move: {
2871
2837
  while (true) {
2872
2838
  const r = battle.rng.next();
2873
2839
  if (r == 0 or r == @intFromEnum(Move.Metronome)) continue;
@@ -2901,7 +2867,7 @@ pub const Rolls = struct {
2901
2867
  const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2902
2868
  @intCast(val)
2903
2869
  else if (showdown)
2904
- @intCast(battle.rng.range(u8, 1, 8))
2870
+ battle.rng.range(u3, 1, 8)
2905
2871
  else duration: {
2906
2872
  while (true) {
2907
2873
  const r = battle.rng.next() & 7;
@@ -2918,7 +2884,7 @@ pub const Rolls = struct {
2918
2884
  const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
2919
2885
  val
2920
2886
  else if (showdown)
2921
- @intCast(battle.rng.range(u8, 1, 9))
2887
+ battle.rng.range(u4, 1, 9)
2922
2888
  else
2923
2889
  @intCast((battle.rng.next() & 7) + 1);
2924
2890
 
@@ -2931,7 +2897,7 @@ pub const Rolls = struct {
2931
2897
  const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2932
2898
  @intCast(val)
2933
2899
  else if (showdown)
2934
- @intCast(battle.rng.range(u8, 2, 6))
2900
+ battle.rng.range(u3, 2, 6)
2935
2901
  else
2936
2902
  @intCast((battle.rng.next() & 3) + 2);
2937
2903
 
@@ -2944,7 +2910,7 @@ pub const Rolls = struct {
2944
2910
  const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2945
2911
  @intCast(val)
2946
2912
  else if (showdown)
2947
- @intCast(battle.rng.range(u4, 2, 4))
2913
+ battle.rng.range(u3, 2, 4)
2948
2914
  else
2949
2915
  @intCast((battle.rng.next() & 1) + 2);
2950
2916
 
@@ -2953,6 +2919,8 @@ pub const Rolls = struct {
2953
2919
  return duration;
2954
2920
  }
2955
2921
 
2922
+ const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
2923
+
2956
2924
  inline fn distribution(
2957
2925
  battle: anytype,
2958
2926
  comptime effect: Move.Effect,
@@ -2963,7 +2931,7 @@ pub const Rolls = struct {
2963
2931
  const n: u3 = if (options.calc.overridden(player, roll)) |val|
2964
2932
  @intCast(val)
2965
2933
  else if (showdown)
2966
- DISTRIBUTION[battle.rng.range(u8, 0, DISTRIBUTION.len)]
2934
+ DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
2967
2935
  else n: {
2968
2936
  const r = (battle.rng.next() & 3);
2969
2937
  break :n @intCast((if (r < 2) r else battle.rng.next() & 3) + 2);
@@ -3010,10 +2978,9 @@ pub const Rolls = struct {
3010
2978
  while (i < moves.len and r > 0) : (i += 1) {
3011
2979
  if (moves[i].pp > 0) {
3012
2980
  r -= 1;
3013
- if (r == 0) break;
2981
+ if (r == 0) break :slot @intCast(i + 1);
3014
2982
  }
3015
2983
  }
3016
- break :slot @intCast(i + 1);
3017
2984
  }
3018
2985
  }
3019
2986
 
@@ -3129,7 +3096,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3129
3096
  if (m.id == if (active.volatiles.Bide) .Bide else side.last_selected_move) {
3130
3097
  // Pokémon Showdown displays Struggle if limited to Bide but unable to pick
3131
3098
  const struggle =
3132
- m.id == .Bide and (m.pp == 0 or active.volatiles.disabled_move == slot);
3099
+ m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
3133
3100
  const s = if (struggle) 0 else slot;
3134
3101
  out[n] = .{ .type = .Move, .data = s };
3135
3102
  n += 1;
@@ -3144,7 +3111,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3144
3111
  const m = active.moves[slot - 1];
3145
3112
  if (m.id == .None) break;
3146
3113
  if (m.pp == 0) continue;
3147
- if (active.volatiles.disabled_move == slot) continue;
3114
+ if (active.volatiles.disable_move == slot) continue;
3148
3115
  out[n] = .{ .type = .Move, .data = slot };
3149
3116
  n += 1;
3150
3117
  }
@@ -39,7 +39,7 @@ The following information is required to simulate a Generation II Pokémon battl
39
39
  | `volatiles.rollout` | `PlayerRolloutCount` | `volatiles.rollout.hitCount` |
40
40
  | `volatiles.confusion` | `PlayerConfuseCount` | `volatiles.confusion.duration` |
41
41
  | `volatiles.toxic` | `PlayerToxicCount` | `volatiles.residualdmg.counter` |
42
- | `volatiles.disabled` | `PlayerDisableCount` | `moveSlots.disabled` |
42
+ | `volatiles.disable` | `PlayerDisableCount` | `moveSlots.disabled` |
43
43
  | `volatiles.encore` | `PlayerEncoreCount` | `volatiles.encore.duration` |
44
44
  | `volatiles.perish_song` | `PlayerPerishCount` | `volatiles.perishsong.duration` |
45
45
  | `volatiles.fury_cutter` | `PlayerFuryCutterCount` | `volatiles.furycutter.multiplier` |