@pkmn/engine 0.1.0-dev.d3e796d5 → 0.1.0-dev.d56bdadd

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.
@@ -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,23 +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
- // 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
 
72
64
  var s1 = false;
73
65
  var s2 = false;
74
- var f1: ?Move = null;
75
- var f2: ?Move = null;
76
66
 
77
- if (selectMove(battle, .P1, c1, c2, &s1, &f1)) |r| return r;
78
- 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;
79
69
 
80
70
  var p1 = battle.side(.P1);
81
71
  var p2 = battle.side(.P2);
@@ -84,9 +74,9 @@ pub fn update(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Result
84
74
  var r2 = showdown and p2.active.volatiles.Binding and c1.type == .Switch;
85
75
 
86
76
  if (try turnOrder(battle, c1, c2, options) == .P1) {
87
- 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;
88
78
  } else {
89
- 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;
90
80
  }
91
81
 
92
82
  if (p1.active.volatiles.attacks == 0) p1.active.volatiles.Binding = false;
@@ -124,7 +114,6 @@ fn selectMove(
124
114
  choice: Choice,
125
115
  foe_choice: Choice,
126
116
  skip_turn: *bool,
127
- from: *?Move,
128
117
  ) ?Result {
129
118
  if (choice.type == .Pass) return null;
130
119
 
@@ -141,7 +130,6 @@ fn selectMove(
141
130
  return null;
142
131
  }
143
132
  if (volatiles.Rage) {
144
- from.* = side.last_used_move;
145
133
  if (showdown) {
146
134
  if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
147
135
  saveMove(battle, player, null);
@@ -151,7 +139,6 @@ fn selectMove(
151
139
  // Pokémon Showdown removes Flinch at the end-of-turn in its residual handler
152
140
  if (!showdown) volatiles.Flinch = false;
153
141
  if (volatiles.Thrashing or volatiles.Charging) {
154
- from.* = side.last_selected_move;
155
142
  if (showdown) {
156
143
  if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
157
144
  saveMove(battle, player, null);
@@ -172,9 +159,7 @@ fn selectMove(
172
159
  return null;
173
160
  }
174
161
  if (volatiles.Binding) {
175
- from.* = side.last_used_move;
176
162
  if (showdown) {
177
- if (foe_choice.type == .Switch) from.* = null;
178
163
  // Pokémon Showdown overwrites Mirror Move with whatever was selected - really this
179
164
  // should set side.last_selected_move = last.id to reuse Mirror Move and fail in order
180
165
  // to satisfy the conditions of the Desync Clause Mod. However, because Binding is still
@@ -210,7 +195,7 @@ fn selectMove(
210
195
  if (choice.data == 0) {
211
196
  const struggle = ok: {
212
197
  for (side.active.moves, 0..) |move, i| {
213
- 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;
214
199
  }
215
200
  break :ok true;
216
201
  };
@@ -229,7 +214,7 @@ fn saveMove(battle: anytype, player: Player, choice: ?Choice) void {
229
214
  if (c.data == 0) {
230
215
  side.last_selected_move = .Struggle;
231
216
  } else {
232
- assert(showdown or side.active.volatiles.disabled_move != c.data);
217
+ assert(showdown or side.active.volatiles.disable_move != c.data);
233
218
  const move = side.active.move(c.data);
234
219
  // You cannot *select* a move with 0 PP (except on Pokémon Showdown where that is
235
220
  // sometimes required...), but a 0 PP move can be used automatically
@@ -340,12 +325,10 @@ fn doTurn(
340
325
  player_choice: Choice,
341
326
  player_rewrap: bool,
342
327
  player_skip: bool,
343
- player_from: ?Move,
344
328
  foe_player: Player,
345
329
  foe_choice: Choice,
346
330
  foe_rewrap: bool,
347
331
  foe_skip: bool,
348
- foe_from: ?Move,
349
332
  options: anytype,
350
333
  ) !?Result {
351
334
  assert(player_choice.type != .Pass);
@@ -358,7 +341,6 @@ fn doTurn(
358
341
  player_choice,
359
342
  player_rewrap,
360
343
  player_skip,
361
- player_from,
362
344
  &residual,
363
345
  options,
364
346
  )) |r| return r;
@@ -381,7 +363,6 @@ fn doTurn(
381
363
  foe_choice,
382
364
  foe_rewrap,
383
365
  foe_skip,
384
- foe_from,
385
366
  &residual,
386
367
  options,
387
368
  )) |r| return r;
@@ -409,7 +390,6 @@ fn executeMove(
409
390
  choice: Choice,
410
391
  rewrap: bool,
411
392
  skip: bool,
412
- from: ?Move,
413
393
  residual: *bool,
414
394
  options: anytype,
415
395
  ) !?Result {
@@ -444,8 +424,10 @@ fn executeMove(
444
424
  mslot = @intCast(battle.lastMove(player).index);
445
425
  const stored = side.stored();
446
426
  // GLITCH: Struggle bypass PP underflow via Hyper Beam / Trapping-switch auto selection
447
- auto = side.last_selected_move == .HyperBeam or side.active.volatiles.Bide or
448
- 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);
449
431
  // If it wasn't Hyper Beam or the continuation of a move effect then we must have just
450
432
  // thawed, in which case we will desync unless the last_selected_move happened to be at
451
433
  // index 1 and the current Pokémon has the same move in its first slot.
@@ -467,7 +449,7 @@ fn executeMove(
467
449
 
468
450
  var skip_can = false;
469
451
  var skip_pp = false;
470
- switch (try beforeMove(battle, player, skip, from, residual, options)) {
452
+ switch (try beforeMove(battle, player, skip, residual, options)) {
471
453
  .done => return null,
472
454
  .skip_can => skip_can = true,
473
455
  .skip_pp => skip_pp = true,
@@ -475,9 +457,9 @@ fn executeMove(
475
457
  .err => return @as(?Result, Result.Error),
476
458
  }
477
459
 
478
- if (!skip_can and !try canMove(battle, player, mslot, auto, skip_pp, from, residual, options)) {
479
- return null;
480
- }
460
+ const can = skip_can or
461
+ try canMove(battle, player, mslot, auto, skip_pp, .None, residual, options);
462
+ if (!can) return null;
481
463
 
482
464
  return doMove(battle, player, mslot, rewrap, auto, residual, options);
483
465
  }
@@ -488,7 +470,6 @@ fn beforeMove(
488
470
  battle: anytype,
489
471
  player: Player,
490
472
  skip: bool,
491
- from: ?Move,
492
473
  residual: *bool,
493
474
  options: anytype,
494
475
  ) !BeforeMove {
@@ -549,16 +530,16 @@ fn beforeMove(
549
530
  return .done;
550
531
  }
551
532
 
552
- if (volatiles.disabled_duration > 0) {
533
+ if (volatiles.disable_duration > 0) {
553
534
  if (options.calc.modify(player, .disable)) |extend| {
554
- if (!extend) volatiles.disabled_duration = 0;
535
+ if (!extend) volatiles.disable_duration = 0;
555
536
  } else {
556
- volatiles.disabled_duration -= 1;
537
+ volatiles.disable_duration -= 1;
557
538
  }
558
- try options.chance.disable(player, volatiles.disabled_duration);
539
+ try options.chance.disable(player, volatiles.disable_duration);
559
540
 
560
- if (volatiles.disabled_duration == 0) {
561
- volatiles.disabled_move = 0;
541
+ if (volatiles.disable_duration == 0) {
542
+ volatiles.disable_move = 0;
562
543
  try log.end(ident, .Disable);
563
544
  }
564
545
  }
@@ -695,7 +676,6 @@ fn beforeMove(
695
676
 
696
677
  if (volatiles.Thrashing) {
697
678
  assert(volatiles.attacks > 0);
698
- assert(from != null);
699
679
 
700
680
  if (options.calc.modify(player, .attacking)) |extend| {
701
681
  if (!extend) volatiles.attacks = 0;
@@ -707,7 +687,7 @@ fn beforeMove(
707
687
  if (!showdown and handleThrashing(battle, active, player, options)) {
708
688
  try log.start(battle.active(player), .ConfusionSilent);
709
689
  }
710
- 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()));
711
691
  if (showdown) {
712
692
  // This shouldn't actually set last_used_move, but Pokémon Showdown sets last
713
693
  // used in useMove and doesn't have the notion of skipping canMove semantics
@@ -729,12 +709,7 @@ fn beforeMove(
729
709
  }
730
710
  try options.chance.binding(player, volatiles.attacks);
731
711
 
732
- try log.move(
733
- battle.active(player),
734
- side.last_selected_move,
735
- battle.active(player.foe()),
736
- side.last_selected_move,
737
- );
712
+ try log.move(battle.active(player), side.last_selected_move, battle.active(player.foe()));
738
713
  if (showdown or battle.last_damage != 0) {
739
714
  const sub = showdown and foe.active.volatiles.Substitute;
740
715
  _ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
@@ -754,7 +729,7 @@ fn canMove(
754
729
  mslot: u4,
755
730
  auto: bool,
756
731
  skip_pp: bool,
757
- from: ?Move,
732
+ from: Move,
758
733
  residual: *bool,
759
734
  options: anytype,
760
735
  ) !bool {
@@ -762,33 +737,22 @@ fn canMove(
762
737
  const player_ident = battle.active(player);
763
738
  const move = Move.get(side.last_selected_move);
764
739
 
765
- const foe_last = battle.foe(player).last_used_move;
766
- const special = from != null and (from.? == .Metronome and
767
- (from.? == .MirrorMove and !(foe_last == .None or foe_last == .MirrorMove)));
768
-
769
740
  var skip = skip_pp;
770
741
  if (side.active.volatiles.Charging) {
771
742
  side.active.volatiles.Charging = false;
772
743
  side.active.volatiles.Invulnerable = false;
773
744
  } else if (move.effect == .Charge) {
774
- try options.log.move(player_ident, side.last_selected_move, .{}, from);
745
+ try options.log.moveFrom(player_ident, side.last_selected_move, .{}, from);
775
746
  setCounterable(battle, player, side, move);
776
747
  try Effects.charge(battle, player, options);
777
748
  return false;
778
749
  }
779
750
 
780
- if (!showdown or !special) side.last_used_move = side.last_selected_move;
751
+ side.last_used_move = side.last_selected_move;
781
752
  if (!skip) decrementPP(side, mslot, auto);
782
753
 
783
754
  const target = if (move.target == .Self) player else player.foe();
784
- // Pokémon Showdown's protocol for Rage and Binding moves should come with a [from], though we
785
- // don't have space to be able to track it in all circumstances and thus usually try to infer it
786
- // from side.last_used_move. However, this gets reset by the opponent switching or fainting,
787
- // meaning sometimes we lose this information. from is still set because it not being null is
788
- // relevant for control flow, but we don't have anything useful to put in the log. This is
789
- // regrettable, but this information is more "nice to have" than required
790
- const f = if (from != null and from.? == .None) null else from;
791
- 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);
792
756
  setCounterable(battle, player, side, move);
793
757
 
794
758
  if (move.effect.onBegin()) {
@@ -826,12 +790,14 @@ fn decrementPP(side: *Side, mslot: u4, auto: bool) void {
826
790
  assert(!volatiles.Rage and !volatiles.Thrashing and !volatiles.MultiHit);
827
791
  if (volatiles.Bide) return;
828
792
 
829
- assert(active.move(mslot).pp > 0 or auto);
830
- 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;
831
796
  if (volatiles.Transform) return;
832
797
 
833
- assert(side.stored().move(mslot).pp > 0 or auto);
834
- 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;
835
801
  assert(active.move(mslot).pp == side.stored().move(mslot).pp);
836
802
  }
837
803
 
@@ -1435,7 +1401,7 @@ fn moveHit(
1435
1401
  const overwritten = overwrite and state > 0;
1436
1402
  assert(!overwritten or (0 < state and state <= 255 and !side.active.volatiles.Bide));
1437
1403
 
1438
- var accuracy = if (overwritten) state else @as(u16, Gen12.percent(move.accuracy));
1404
+ var accuracy = if (overwritten) state else @as(u16, move.accuracy);
1439
1405
  var boost = BOOSTS[@as(u4, @intCast(@as(i8, side.active.boosts.accuracy) + 6))];
1440
1406
  accuracy = accuracy * boost[0] / boost[1];
1441
1407
  boost = BOOSTS[@as(u4, @intCast(@as(i8, -foe.active.boosts.evasion) + 6))];
@@ -1474,9 +1440,8 @@ fn checkFaint(
1474
1440
  const foe_fainted = foe.stored().hp == 0;
1475
1441
 
1476
1442
  const player_out = findFirstAlive(side) == 0;
1477
- const foe_out = findFirstAlive(foe) == 0;
1478
- const tie = player_out and foe_out;
1479
- 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;
1480
1445
 
1481
1446
  if (try faint(battle, player, !(more or foe_fainted), options)) |r| return r;
1482
1447
  if (foe_fainted) if (try faint(battle, player.foe(), !more, options)) |r| return r;
@@ -1484,7 +1449,7 @@ fn checkFaint(
1484
1449
  assert(!side.active.volatiles.MultiHit);
1485
1450
  assert(!foe.active.volatiles.MultiHit);
1486
1451
 
1487
- if (tie) {
1452
+ if (player_out and foe_out) {
1488
1453
  try options.log.tie();
1489
1454
  return Result.Tie;
1490
1455
  } else if (player_out) {
@@ -1496,8 +1461,8 @@ fn checkFaint(
1496
1461
  }
1497
1462
 
1498
1463
  const foe_choice: Choice.Type = if (foe_fainted) .Switch else .Pass;
1499
- if (player == .P1) return Result{ .p1 = .Switch, .p2 = foe_choice };
1500
- return Result{ .p1 = foe_choice, .p2 = .Switch };
1464
+ if (player == .P1) return .{ .p1 = .Switch, .p2 = foe_choice };
1465
+ return .{ .p1 = foe_choice, .p2 = .Switch };
1501
1466
  }
1502
1467
 
1503
1468
  fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result {
@@ -1545,21 +1510,19 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
1545
1510
  var volatiles = &side.active.volatiles;
1546
1511
 
1547
1512
  const brn = Status.is(stored.status, .BRN);
1548
- if (brn or Status.is(stored.status, .PSN)) blk: {
1513
+ if (brn or Status.is(stored.status, .PSN)) {
1549
1514
  var damage = @max(stored.stats.hp / 16, 1);
1550
-
1551
1515
  if (volatiles.Toxic) {
1552
1516
  volatiles.toxic += 1;
1553
1517
  damage *= volatiles.toxic;
1554
1518
  }
1519
+ damage = @min(damage, stored.hp);
1555
1520
 
1556
- const amount = @min(damage, stored.hp);
1557
- if (showdown and amount == 0) break :blk;
1558
-
1559
- stored.hp -= amount;
1560
-
1561
- // Pokémon Showdown uses damageOf here but its not relevant in Generation I
1562
- try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
1521
+ if (!showdown or damage > 0) {
1522
+ stored.hp -= damage;
1523
+ // Pokémon Showdown uses damageOf here but its not relevant in Generation I
1524
+ try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
1525
+ }
1563
1526
  }
1564
1527
 
1565
1528
  if (volatiles.LeechSeed) {
@@ -1672,9 +1635,9 @@ fn checkEBC(battle: anytype) bool {
1672
1635
  }
1673
1636
 
1674
1637
  fn disabled(side: *Side, ident: ID, options: anytype) !bool {
1675
- if (side.active.volatiles.disabled_move != 0) {
1638
+ if (side.active.volatiles.disable_move != 0) {
1676
1639
  // A Pokémon that transforms after being disabled may end up with less move slots
1677
- 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;
1678
1641
  // side.last_selected_move can be Struggle here on Pokemon Showdown we need an extra check
1679
1642
  const last = if (showdown and m == .Bide and side.active.volatiles.Bide)
1680
1643
  m
@@ -1689,7 +1652,7 @@ fn disabled(side: *Side, ident: ID, options: anytype) !bool {
1689
1652
  return false;
1690
1653
  }
1691
1654
 
1692
- inline fn buildRage(battle: anytype, who: Player, options: anytype) !void {
1655
+ fn buildRage(battle: anytype, who: Player, options: anytype) !void {
1693
1656
  const side = battle.side(who);
1694
1657
  if (side.active.volatiles.Rage and side.active.boosts.atk < 6) {
1695
1658
  try Effects.boost(battle, who, Move.get(.Rage), options);
@@ -1707,7 +1670,7 @@ fn handleThrashing(battle: anytype, active: *ActivePokemon, player: Player, opti
1707
1670
  return true;
1708
1671
  }
1709
1672
 
1710
- inline fn onBegin(
1673
+ fn onBegin(
1711
1674
  battle: anytype,
1712
1675
  player: Player,
1713
1676
  move: Move.Data,
@@ -1737,7 +1700,7 @@ inline fn onBegin(
1737
1700
  };
1738
1701
  }
1739
1702
 
1740
- inline fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1703
+ fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1741
1704
  assert(move.effect.onEnd());
1742
1705
  return switch (move.effect) {
1743
1706
  .Bide => Effects.bide(battle, player, options),
@@ -1753,7 +1716,7 @@ inline fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anyty
1753
1716
  };
1754
1717
  }
1755
1718
 
1756
- inline fn alwaysHappens(
1719
+ fn alwaysHappens(
1757
1720
  battle: anytype,
1758
1721
  player: Player,
1759
1722
  move: Move.Data,
@@ -1772,7 +1735,7 @@ inline fn alwaysHappens(
1772
1735
  };
1773
1736
  }
1774
1737
 
1775
- inline fn moveEffect(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1738
+ fn moveEffect(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1776
1739
  return switch (move.effect) {
1777
1740
  .BurnChance1, .BurnChance2 => Effects.burnChance(battle, player, move, options),
1778
1741
  .ConfusionChance => Effects.confusion(battle, player, move, options),
@@ -1902,7 +1865,7 @@ pub const Effects = struct {
1902
1865
  try options.chance.commit(player, .hit);
1903
1866
  }
1904
1867
 
1905
- if (volatiles.disabled_move != 0) {
1868
+ if (volatiles.disable_move != 0) {
1906
1869
  try options.log.fail(foe_ident, .None);
1907
1870
  return null;
1908
1871
  }
@@ -1927,18 +1890,17 @@ pub const Effects = struct {
1927
1890
  return Result.Error;
1928
1891
  }
1929
1892
 
1930
- volatiles.disabled_move =
1893
+ volatiles.disable_move =
1931
1894
  @intCast(try Rolls.moveSlot(battle, player, &foe.active.moves, n, options));
1932
- volatiles.disabled_duration = Rolls.disableDuration(battle, player, options);
1895
+ volatiles.disable_duration = Rolls.disableDuration(battle, player, options);
1933
1896
 
1934
- const id = foe.active.move(volatiles.disabled_move).id;
1897
+ const id = foe.active.move(volatiles.disable_move).id;
1935
1898
  try options.log.startEffect(foe_ident, .Disable, id);
1936
1899
  return null;
1937
1900
  }
1938
1901
 
1939
1902
  fn drainHP(battle: anytype, player: Player, options: anytype) !void {
1940
- var side = battle.side(player);
1941
- var stored = side.stored();
1903
+ var stored = battle.side(player).stored();
1942
1904
 
1943
1905
  if (battle.last_damage == 0) {
1944
1906
  assert(showdown);
@@ -2063,7 +2025,7 @@ pub const Effects = struct {
2063
2025
  // GLITCH: HP recovery move failure glitches
2064
2026
  const delta = stored.stats.hp - stored.hp;
2065
2027
  if (delta == 0 or (delta & 255 == 255 and stored.hp % 256 != 0)) {
2066
- return try options.log.fail(ident, .None);
2028
+ return options.log.fail(ident, .None);
2067
2029
  }
2068
2030
 
2069
2031
  const rest = side.last_selected_move == .Rest;
@@ -2145,7 +2107,7 @@ pub const Effects = struct {
2145
2107
  // Invulnerable foes or 1/256 miss can trigger |-miss| instead of |-fail|
2146
2108
  if (!try checkHit(battle, player, move, options)) return;
2147
2109
  try options.chance.commit(player, .hit);
2148
- return try options.log.fail(battle.active(player.foe()), .None);
2110
+ return options.log.fail(battle.active(player.foe()), .None);
2149
2111
  }
2150
2112
  }
2151
2113
  if (!try checkHit(battle, player, move, options)) return;
@@ -2363,8 +2325,7 @@ pub const Effects = struct {
2363
2325
  fn substitute(battle: anytype, player: Player, residual: *bool, options: anytype) !void {
2364
2326
  var side = battle.side(player);
2365
2327
  if (side.active.volatiles.Substitute) {
2366
- try options.log.fail(battle.active(player), .Substitute);
2367
- return;
2328
+ return options.log.fail(battle.active(player), .Substitute);
2368
2329
  }
2369
2330
 
2370
2331
  assert(side.stored().stats.hp <= 1023);
@@ -2375,10 +2336,7 @@ pub const Effects = struct {
2375
2336
  // the target's HP is exactly divisible by 4 (here we're using an inlined divCeil routine to
2376
2337
  // avoid having to convert to floating point)
2377
2338
  const required_hp = if (showdown) @divFloor(side.stored().stats.hp - 1, 4) + 1 else hp;
2378
- if (side.stored().hp < required_hp) {
2379
- try options.log.fail(battle.active(player), .Weak);
2380
- return;
2381
- }
2339
+ if (side.stored().hp < required_hp) return options.log.fail(battle.active(player), .Weak);
2382
2340
 
2383
2341
  // GLITCH: can leave the user with 0 HP (faints later) because didn't check '<=' above
2384
2342
  side.stored().hp -= hp;
@@ -2392,12 +2350,17 @@ pub const Effects = struct {
2392
2350
  }
2393
2351
 
2394
2352
  fn switchAndTeleport(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
2395
- if (!showdown or battle.side(player).last_selected_move == .Teleport) return;
2396
-
2397
2353
  // Whirlwind/Roar should not roll to hit/reset damage but Pokémon Showdown does anyway
2398
- if (try checkHit(battle, player, move, options)) try options.chance.commit(player, .hit);
2399
-
2400
- battle.last_damage = 0;
2354
+ if (showdown) {
2355
+ if (battle.side(player).last_selected_move == .Teleport) return;
2356
+ if (try checkHit(battle, player, move, options)) {
2357
+ try options.chance.commit(player, .hit);
2358
+ }
2359
+ battle.last_damage = 0;
2360
+ } else {
2361
+ try options.log.fail(battle.active(player), .None);
2362
+ try options.log.laststill();
2363
+ }
2401
2364
  }
2402
2365
 
2403
2366
  fn thrashing(battle: anytype, player: Player, options: anytype) void {
@@ -2465,7 +2428,7 @@ pub const Effects = struct {
2465
2428
  switch (move.effect) {
2466
2429
  .AttackUp1, .AttackUp2, .Rage => {
2467
2430
  assert(boosts.atk >= -6 and boosts.atk <= 6);
2468
- if (boosts.atk == 6) return try log.fail(ident, .None);
2431
+ if (boosts.atk == 6) return log.fail(ident, .None);
2469
2432
  const n: u2 = if (move.effect == .AttackUp2) 2 else 1;
2470
2433
  boosts.atk = @as(i4, @intCast(@min(6, @as(i8, boosts.atk) + n)));
2471
2434
  const reason = if (move.effect == .Rage) Boost.Rage else Boost.Attack;
@@ -2476,7 +2439,7 @@ pub const Effects = struct {
2476
2439
  try log.boost(ident, Boost.Attack, -1);
2477
2440
  if (move.effect == .Rage) return;
2478
2441
  }
2479
- return try log.fail(ident, .None);
2442
+ return log.fail(ident, .None);
2480
2443
  }
2481
2444
  var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
2482
2445
  const stat = unmodifiedStats(battle, side).atk;
@@ -2487,7 +2450,7 @@ pub const Effects = struct {
2487
2450
  },
2488
2451
  .DefenseUp1, .DefenseUp2 => {
2489
2452
  assert(boosts.def >= -6 and boosts.def <= 6);
2490
- if (boosts.def == 6) return try log.fail(ident, .None);
2453
+ if (boosts.def == 6) return log.fail(ident, .None);
2491
2454
  const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
2492
2455
  boosts.def = @intCast(@min(6, @as(i8, boosts.def) + n));
2493
2456
  if (stats.def == MAX_STAT_VALUE) {
@@ -2496,7 +2459,7 @@ pub const Effects = struct {
2496
2459
  try log.boost(ident, .Defense, n);
2497
2460
  try log.boost(ident, .Defense, -1);
2498
2461
  }
2499
- return try log.fail(ident, .None);
2462
+ return log.fail(ident, .None);
2500
2463
  }
2501
2464
  var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
2502
2465
  const stat = unmodifiedStats(battle, side).def;
@@ -2505,7 +2468,7 @@ pub const Effects = struct {
2505
2468
  },
2506
2469
  .SpeedUp2 => {
2507
2470
  assert(boosts.spe >= -6 and boosts.spe <= 6);
2508
- if (boosts.spe == 6) return try log.fail(ident, .None);
2471
+ if (boosts.spe == 6) return log.fail(ident, .None);
2509
2472
  boosts.spe = @intCast(@min(6, @as(i8, boosts.spe) + 2));
2510
2473
  if (stats.spe == MAX_STAT_VALUE) {
2511
2474
  boosts.spe -= 1;
@@ -2513,7 +2476,7 @@ pub const Effects = struct {
2513
2476
  try log.boost(ident, .Speed, 2);
2514
2477
  try log.boost(ident, .Speed, -1);
2515
2478
  }
2516
- return try log.fail(ident, .None);
2479
+ return log.fail(ident, .None);
2517
2480
  }
2518
2481
  var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
2519
2482
  const stat = unmodifiedStats(battle, side).spe;
@@ -2522,7 +2485,7 @@ pub const Effects = struct {
2522
2485
  },
2523
2486
  .SpecialUp1, .SpecialUp2 => {
2524
2487
  assert(boosts.spc >= -6 and boosts.spc <= 6);
2525
- if (boosts.spc == 6) return try log.fail(ident, .None);
2488
+ if (boosts.spc == 6) return log.fail(ident, .None);
2526
2489
  const n: u2 = if (move.effect == .SpecialUp2) 2 else 1;
2527
2490
  boosts.spc = @intCast(@min(6, @as(i8, boosts.spc) + n));
2528
2491
  if (stats.spc == MAX_STAT_VALUE) {
@@ -2533,7 +2496,7 @@ pub const Effects = struct {
2533
2496
  try log.boost(ident, .SpecialDefense, n);
2534
2497
  try log.boost(ident, .SpecialDefense, -1);
2535
2498
  }
2536
- return try log.fail(ident, .None);
2499
+ return log.fail(ident, .None);
2537
2500
  }
2538
2501
  var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
2539
2502
  const stat = unmodifiedStats(battle, side).spc;
@@ -2543,7 +2506,7 @@ pub const Effects = struct {
2543
2506
  },
2544
2507
  .EvasionUp1 => {
2545
2508
  assert(boosts.evasion >= -6 and boosts.evasion <= 6);
2546
- if (boosts.evasion == 6) return try log.fail(ident, .None);
2509
+ if (boosts.evasion == 6) return log.fail(ident, .None);
2547
2510
  boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
2548
2511
  try log.boost(ident, .Evasion, 1);
2549
2512
  },
@@ -2586,7 +2549,7 @@ pub const Effects = struct {
2586
2549
  try log.boost(foe_ident, .Attack, -1);
2587
2550
  try log.boost(foe_ident, .Attack, 1);
2588
2551
  }
2589
- return try log.fail(foe_ident, .None);
2552
+ return log.fail(foe_ident, .None);
2590
2553
  }
2591
2554
  var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
2592
2555
  const stat = unmodifiedStats(battle, foe).atk;
@@ -2604,7 +2567,7 @@ pub const Effects = struct {
2604
2567
  try log.boost(foe_ident, .Defense, -@as(i8, n));
2605
2568
  try log.boost(foe_ident, .Defense, 1);
2606
2569
  }
2607
- return try log.fail(foe_ident, .None);
2570
+ return log.fail(foe_ident, .None);
2608
2571
  }
2609
2572
  var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
2610
2573
  const stat = unmodifiedStats(battle, foe).def;
@@ -2621,7 +2584,7 @@ pub const Effects = struct {
2621
2584
  try log.boost(foe_ident, .Speed, -1);
2622
2585
  try log.boost(foe_ident, .Speed, 1);
2623
2586
  }
2624
- return try log.fail(foe_ident, .None);
2587
+ return log.fail(foe_ident, .None);
2625
2588
  }
2626
2589
  var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
2627
2590
  const stat = unmodifiedStats(battle, foe).spe;
@@ -2641,7 +2604,7 @@ pub const Effects = struct {
2641
2604
  try log.boost(foe_ident, .SpecialDefense, -1);
2642
2605
  try log.boost(foe_ident, .SpecialDefense, 1);
2643
2606
  }
2644
- return try log.fail(foe_ident, .None);
2607
+ return log.fail(foe_ident, .None);
2645
2608
  }
2646
2609
  var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
2647
2610
  const stat = unmodifiedStats(battle, foe).spc;
@@ -2677,7 +2640,7 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
2677
2640
  }
2678
2641
  }
2679
2642
 
2680
- inline fn isForced(active: anytype) bool {
2643
+ fn isForced(active: anytype) bool {
2681
2644
  return active.volatiles.Recharging or active.volatiles.Rage or
2682
2645
  active.volatiles.Thrashing or active.volatiles.Charging;
2683
2646
  }
@@ -2690,9 +2653,9 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
2690
2653
 
2691
2654
  options.chance.clearDurations(who, clear);
2692
2655
 
2693
- if (volatiles.disabled_move != 0) {
2694
- volatiles.disabled_move = 0;
2695
- volatiles.disabled_duration = 0;
2656
+ if (volatiles.disable_move != 0) {
2657
+ volatiles.disable_move = 0;
2658
+ volatiles.disable_duration = 0;
2696
2659
  try log.end(ident, .DisableSilent);
2697
2660
  }
2698
2661
  if (volatiles.Confusion) {
@@ -2702,15 +2665,15 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
2702
2665
  }
2703
2666
  if (volatiles.Mist) {
2704
2667
  volatiles.Mist = false;
2705
- try log.end(ident, .Mist);
2668
+ try log.end(ident, .MistSilent);
2706
2669
  }
2707
2670
  if (volatiles.FocusEnergy) {
2708
2671
  volatiles.FocusEnergy = false;
2709
- try log.end(ident, .FocusEnergy);
2672
+ try log.end(ident, .FocusEnergySilent);
2710
2673
  }
2711
2674
  if (volatiles.LeechSeed) {
2712
2675
  volatiles.LeechSeed = false;
2713
- try log.end(ident, .LeechSeed);
2676
+ try log.end(ident, .LeechSeedSilent);
2714
2677
  }
2715
2678
  if (!showdown and volatiles.Toxic) {
2716
2679
  volatiles.Toxic = false;
@@ -2718,22 +2681,22 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
2718
2681
  }
2719
2682
  if (volatiles.LightScreen) {
2720
2683
  volatiles.LightScreen = false;
2721
- try log.end(ident, .LightScreen);
2684
+ try log.end(ident, .LightScreenSilent);
2722
2685
  }
2723
2686
  if (volatiles.Reflect) {
2724
2687
  volatiles.Reflect = false;
2725
- try log.end(ident, .Reflect);
2688
+ try log.end(ident, .ReflectSilent);
2726
2689
  }
2727
2690
  if (showdown and volatiles.Toxic) {
2728
2691
  volatiles.Toxic = false;
2729
2692
  // Pokémon Showdown erroneously clears the toxic counter
2730
2693
  volatiles.toxic = 0;
2731
- try log.end(ident, .Toxic);
2694
+ try log.end(ident, .ToxicSilent);
2732
2695
  }
2733
2696
  }
2734
2697
 
2735
2698
  pub const Rolls = struct {
2736
- inline fn speedTie(battle: anytype, options: anytype) !bool {
2699
+ fn speedTie(battle: anytype, options: anytype) !bool {
2737
2700
  const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
2738
2701
  player == .P1
2739
2702
  else if (showdown)
@@ -2745,7 +2708,7 @@ pub const Rolls = struct {
2745
2708
  return p1;
2746
2709
  }
2747
2710
 
2748
- inline fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
2711
+ fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
2749
2712
  const crit = if (options.calc.overridden(player, .critical_hit)) |val|
2750
2713
  val == .true
2751
2714
  else if (showdown)
@@ -2757,7 +2720,7 @@ pub const Rolls = struct {
2757
2720
  return crit;
2758
2721
  }
2759
2722
 
2760
- inline fn damage(battle: anytype, player: anytype, options: anytype) !u8 {
2723
+ fn damage(battle: anytype, player: Player, options: anytype) !u8 {
2761
2724
  const roll = if (options.calc.overridden(player, .damage)) |val|
2762
2725
  val
2763
2726
  else roll: {
@@ -2773,7 +2736,7 @@ pub const Rolls = struct {
2773
2736
  return roll;
2774
2737
  }
2775
2738
 
2776
- inline fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) bool {
2739
+ fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) bool {
2777
2740
  const ok = if (options.calc.overridden(player, .hit)) |val|
2778
2741
  val == .true
2779
2742
  else if (showdown)
@@ -2785,7 +2748,7 @@ pub const Rolls = struct {
2785
2748
  return ok;
2786
2749
  }
2787
2750
 
2788
- inline fn confused(battle: anytype, player: Player, options: anytype) !bool {
2751
+ fn confused(battle: anytype, player: Player, options: anytype) !bool {
2789
2752
  const cfz = if (options.calc.overridden(player, .confused)) |val|
2790
2753
  val == .true
2791
2754
  else if (showdown)
@@ -2797,7 +2760,7 @@ pub const Rolls = struct {
2797
2760
  return cfz;
2798
2761
  }
2799
2762
 
2800
- inline fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
2763
+ fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
2801
2764
  const par = if (options.calc.overridden(player, .paralyzed)) |val|
2802
2765
  val == .true
2803
2766
  else if (showdown)
@@ -2809,7 +2772,7 @@ pub const Rolls = struct {
2809
2772
  return par;
2810
2773
  }
2811
2774
 
2812
- inline fn confusionChance(battle: anytype, player: Player, options: anytype) !bool {
2775
+ fn confusionChance(battle: anytype, player: Player, options: anytype) !bool {
2813
2776
  const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
2814
2777
  val == .true
2815
2778
  else if (showdown)
@@ -2821,7 +2784,7 @@ pub const Rolls = struct {
2821
2784
  return proc;
2822
2785
  }
2823
2786
 
2824
- inline fn secondaryChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
2787
+ fn secondaryChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
2825
2788
  const rate: u8 = if (low) 26 else 77;
2826
2789
 
2827
2790
  const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
@@ -2835,7 +2798,7 @@ pub const Rolls = struct {
2835
2798
  return proc;
2836
2799
  }
2837
2800
 
2838
- inline fn poisonChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
2801
+ fn poisonChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
2839
2802
  const rate: u8 = if (low) 52 else 103;
2840
2803
 
2841
2804
  const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
@@ -2849,7 +2812,7 @@ pub const Rolls = struct {
2849
2812
  return proc;
2850
2813
  }
2851
2814
 
2852
- inline fn unboost(battle: anytype, player: Player, options: anytype) !bool {
2815
+ fn unboost(battle: anytype, player: Player, options: anytype) !bool {
2853
2816
  const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
2854
2817
  val == .true
2855
2818
  else if (showdown)
@@ -2861,14 +2824,12 @@ pub const Rolls = struct {
2861
2824
  return proc;
2862
2825
  }
2863
2826
 
2864
- inline fn metronome(battle: anytype, player: Player, options: anytype) !Move {
2827
+ fn metronome(battle: anytype, player: Player, options: anytype) !Move {
2865
2828
  const move: Move = if (options.calc.overridden(player, .metronome)) |val|
2866
2829
  val
2867
- else if (showdown) move: {
2868
- const r = battle.rng.range(u8, 0, @intFromEnum(Move.Struggle) - 2);
2869
- const mod = @as(u2, (if (r < @intFromEnum(Move.Metronome) - 1) 1 else 2));
2870
- break :move @enumFromInt(r + mod);
2871
- } else move: {
2830
+ else if (showdown)
2831
+ Move.METRONOME[battle.rng.range(u8, 0, Move.METRONOME.len)]
2832
+ else move: {
2872
2833
  while (true) {
2873
2834
  const r = battle.rng.next();
2874
2835
  if (r == 0 or r == @intFromEnum(Move.Metronome)) continue;
@@ -2881,7 +2842,7 @@ pub const Rolls = struct {
2881
2842
  return move;
2882
2843
  }
2883
2844
 
2884
- inline fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
2845
+ fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
2885
2846
  const power = if (options.calc.overridden(player, .psywave)) |val|
2886
2847
  val - 1
2887
2848
  else if (showdown)
@@ -2898,11 +2859,11 @@ pub const Rolls = struct {
2898
2859
  return power;
2899
2860
  }
2900
2861
 
2901
- inline fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
2862
+ fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
2902
2863
  const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2903
2864
  @intCast(val)
2904
2865
  else if (showdown)
2905
- @intCast(battle.rng.range(u8, 1, 8))
2866
+ battle.rng.range(u3, 1, 8)
2906
2867
  else duration: {
2907
2868
  while (true) {
2908
2869
  const r = battle.rng.next() & 7;
@@ -2915,11 +2876,11 @@ pub const Rolls = struct {
2915
2876
  return duration;
2916
2877
  }
2917
2878
 
2918
- inline fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
2879
+ fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
2919
2880
  const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
2920
2881
  val
2921
2882
  else if (showdown)
2922
- @intCast(battle.rng.range(u8, 1, 9))
2883
+ battle.rng.range(u4, 1, 9)
2923
2884
  else
2924
2885
  @intCast((battle.rng.next() & 7) + 1);
2925
2886
 
@@ -2928,11 +2889,11 @@ pub const Rolls = struct {
2928
2889
  return duration;
2929
2890
  }
2930
2891
 
2931
- inline fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
2892
+ fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
2932
2893
  const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2933
2894
  @intCast(val)
2934
2895
  else if (showdown)
2935
- @intCast(battle.rng.range(u8, 2, 6))
2896
+ battle.rng.range(u3, 2, 6)
2936
2897
  else
2937
2898
  @intCast((battle.rng.next() & 3) + 2);
2938
2899
 
@@ -2941,11 +2902,11 @@ pub const Rolls = struct {
2941
2902
  return duration;
2942
2903
  }
2943
2904
 
2944
- inline fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
2905
+ fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
2945
2906
  const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2946
2907
  @intCast(val)
2947
2908
  else if (showdown)
2948
- @intCast(battle.rng.range(u4, 2, 4))
2909
+ battle.rng.range(u3, 2, 4)
2949
2910
  else
2950
2911
  @intCast((battle.rng.next() & 1) + 2);
2951
2912
 
@@ -2954,7 +2915,9 @@ pub const Rolls = struct {
2954
2915
  return duration;
2955
2916
  }
2956
2917
 
2957
- inline fn distribution(
2918
+ const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
2919
+
2920
+ fn distribution(
2958
2921
  battle: anytype,
2959
2922
  comptime effect: Move.Effect,
2960
2923
  player: Player,
@@ -2964,7 +2927,7 @@ pub const Rolls = struct {
2964
2927
  const n: u3 = if (options.calc.overridden(player, roll)) |val|
2965
2928
  @intCast(val)
2966
2929
  else if (showdown)
2967
- DISTRIBUTION[battle.rng.range(u8, 0, DISTRIBUTION.len)]
2930
+ DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
2968
2931
  else n: {
2969
2932
  const r = (battle.rng.next() & 3);
2970
2933
  break :n @intCast((if (r < 2) r else battle.rng.next() & 3) + 2);
@@ -3011,10 +2974,9 @@ pub const Rolls = struct {
3011
2974
  while (i < moves.len and r > 0) : (i += 1) {
3012
2975
  if (moves[i].pp > 0) {
3013
2976
  r -= 1;
3014
- if (r == 0) break;
2977
+ if (r == 0) break :slot @intCast(i + 1);
3015
2978
  }
3016
2979
  }
3017
- break :slot @intCast(i + 1);
3018
2980
  }
3019
2981
  }
3020
2982
 
@@ -3036,11 +2998,11 @@ test "RNG agreement" {
3036
2998
  expected[i] = @intCast(i * 0x1000000);
3037
2999
  }
3038
3000
 
3039
- var spe = rng.FixedRNG(1, expected.len){ .rolls = expected };
3040
- var cfz = rng.FixedRNG(1, expected.len){ .rolls = expected };
3041
- var par = rng.FixedRNG(1, expected.len){ .rolls = expected };
3042
- var brn = rng.FixedRNG(1, expected.len){ .rolls = expected };
3043
- var eff = rng.FixedRNG(1, expected.len){ .rolls = expected };
3001
+ var spe: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
3002
+ var cfz: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
3003
+ var par: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
3004
+ var brn: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
3005
+ var eff: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
3044
3006
 
3045
3007
  for (0..expected.len) |i| {
3046
3008
  try expectEqual(spe.range(u8, 0, 2) == 0, i < Gen12.percent(50) + 1);
@@ -3130,7 +3092,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3130
3092
  if (m.id == if (active.volatiles.Bide) .Bide else side.last_selected_move) {
3131
3093
  // Pokémon Showdown displays Struggle if limited to Bide but unable to pick
3132
3094
  const struggle =
3133
- m.id == .Bide and (m.pp == 0 or active.volatiles.disabled_move == slot);
3095
+ m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
3134
3096
  const s = if (struggle) 0 else slot;
3135
3097
  out[n] = .{ .type = .Move, .data = s };
3136
3098
  n += 1;
@@ -3145,7 +3107,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3145
3107
  const m = active.moves[slot - 1];
3146
3108
  if (m.id == .None) break;
3147
3109
  if (m.pp == 0) continue;
3148
- if (active.volatiles.disabled_move == slot) continue;
3110
+ if (active.volatiles.disable_move == slot) continue;
3149
3111
  out[n] = .{ .type = .Move, .data = slot };
3150
3112
  n += 1;
3151
3113
  }