@pkmn/engine 0.1.0-dev.437f6a52 → 0.1.0-dev.46307385

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.
@@ -65,7 +65,7 @@ pub fn Battle(comptime RNG: anytype) type {
65
65
  /// The last damage dealt by either side.
66
66
  last_damage: u16 = 0,
67
67
  /// Details about the last move selected/used by each player required to detect desyncs.
68
- last_moves: MoveDetails = .{},
68
+ last_moves: [2]MoveDetails = [_]MoveDetails{.{}} ** 2,
69
69
  /// The pseudo random number generator.
70
70
  rng: RNG,
71
71
 
@@ -86,6 +86,15 @@ pub fn Battle(comptime RNG: anytype) type {
86
86
  return player.ident(@intCast(self.side(player).order[0]));
87
87
  }
88
88
 
89
+ /// Returns the `MoveDetails` for the given `player`.
90
+ pub inline fn lastMove(
91
+ self: anytype,
92
+ player: Player,
93
+ ) PointerType(@TypeOf(self), MoveDetails) {
94
+ assert(@typeInfo(@TypeOf(self)).Pointer.child == Self);
95
+ return &self.last_moves[@intFromEnum(player)];
96
+ }
97
+
89
98
  /// Returns the result of applying Player 1's choice `c1` and Player 2's choice `c2` to the
90
99
  /// battle, optionally writing protocol message logs to `options.log` if `pkmn.options.log`
91
100
  /// is enabled.
@@ -219,17 +228,13 @@ pub const MoveSlot = extern struct {
219
228
 
220
229
  /// Details required to detect desyncs based on the move last selected/executed by players.
221
230
  pub const MoveDetails = packed struct {
222
- /// The move slot index of the last move selected in the battle menu by Player 1
223
- p1_index: if (showdown) u8 else u4 = 0,
224
- /// Whether the last move executed by Player 1 was counterable.
225
- p1_counterable: if (showdown) u8 else u4 = 0,
226
- /// The move slot index of the last move selected in the battle menu by Player 2
227
- p2_index: if (showdown) u8 else u4 = 0,
228
- /// Whether the last move executed by Player 2 was counterable.
229
- p2_counterable: if (showdown) u8 else u4 = 0,
231
+ /// The move slot index of the last move selected in the battle menu.
232
+ index: if (showdown) u8 else u4 = 0,
233
+ /// Whether the last move executed was counterable.
234
+ counterable: if (showdown) u8 else u4 = 0,
230
235
 
231
236
  comptime {
232
- assert(@sizeOf(MoveDetails) == if (showdown) 4 else 2);
237
+ assert(@sizeOf(MoveDetails) == if (showdown) 2 else 1);
233
238
  }
234
239
  };
235
240
 
@@ -350,47 +350,65 @@ pub const Rolls = struct {
350
350
  const U3_NONE = [_]u3{0};
351
351
  const U3_FORCED = [_]u3{1};
352
352
 
353
- /// Returns a slice with a range of values for sleep given the `action` state.
353
+ /// Returns a slice with a range of values for sleep given the `action` and `origin` states.
354
354
  pub inline fn sleep(action: Action, origin: Action) []const u3 {
355
- const turns = @field(action.durations, "sleep");
356
- _ = origin;
357
- return if (turns < 1 or turns >= 7) &U3_NONE else &[_]u3{ 0, turns };
355
+ const a = @field(action.durations, "sleep");
356
+ const o = @field(origin.durations, "sleep");
357
+ if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
358
+ assert(a == 0 or a == o + 1);
359
+ return if (o >= 7) &U3_NONE else &[_]u3{ 0, o + 1 };
358
360
  }
359
361
 
360
362
  const DISABLE_NONE = [_]u4{0};
361
363
 
362
- /// Returns a slice with a range of values for disable given the `action` state
364
+ /// Returns a slice with a range of values for disable given the `action` and `origin` states
363
365
  /// and the state of the `parent` (the player's Pokémon's remaining sleep turns).
364
366
  pub inline fn disable(action: Action, origin: Action, parent: u4) []const u4 {
365
- const turns = @field(action.durations, "disable");
366
- _ = origin;
367
- return if (parent > 0 or turns < 1 or turns >= 8) &DISABLE_NONE else &[_]u4{ 0, turns };
367
+ if (parent > 0) return &DISABLE_NONE;
368
+ const a = @field(action.durations, "disable");
369
+ const o = @field(origin.durations, "disable");
370
+ if (o == a) return if (o == 0) &DISABLE_NONE else &[_]u4{o};
371
+ assert(a == 0 or a == o + 1);
372
+ return if (o >= 8) &DISABLE_NONE else &[_]u4{ 0, o + 1 };
368
373
  }
369
374
 
370
- /// Returns a slice with a range of values for confusion given the `action` state
375
+ /// Returns a slice with a range of values for confusion given the `action` and `origin` states
371
376
  /// and the state of the `parent` (the player's remaining sleep turns).
372
377
  pub inline fn confusion(action: Action, origin: Action, parent: u4) []const u3 {
373
- const turns = @field(action.durations, "confusion");
374
- _ = origin;
375
- if (parent > 0 or turns < 1 or turns >= 5) return &U3_NONE;
376
- return if (turns < 2) &U3_FORCED else &[_]u3{ 0, turns };
378
+ if (parent > 0) return &U3_NONE;
379
+ const a = @field(action.durations, "confusion");
380
+ const o = @field(origin.durations, "confusion");
381
+ if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
382
+ assert(a == 0 or a == o + 1);
383
+ return if (o >= 5) &U3_NONE else if (o < 2)
384
+ if (a == 0) &U3_NONE else &U3_FORCED
385
+ else
386
+ &[_]u3{ 0, o + 1 };
377
387
  }
378
388
 
379
- /// Returns a slice with a range of values for attacking given the `action` state
389
+ /// Returns a slice with a range of values for attacking given the `action` and `origin` states
380
390
  /// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
381
391
  pub inline fn attacking(action: Action, origin: Action, parent: Optional(bool)) []const u3 {
382
- const turns = @field(action.durations, "attacking");
383
- _ = origin;
384
- if (parent == .true or turns < 1 or turns >= 3) return &U3_NONE;
385
- return if (turns < 2) &U3_FORCED else &[_]u3{ 0, turns };
392
+ if (parent == .true) return &U3_NONE;
393
+ const a = @field(action.durations, "attacking");
394
+ const o = @field(origin.durations, "attacking");
395
+ if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
396
+ assert(a == 0 or a == o + 1);
397
+ return if (o >= 3) &U3_NONE else if (o < 2)
398
+ if (a == 0) &U3_NONE else &U3_FORCED
399
+ else
400
+ &[_]u3{ 0, o + 1 };
386
401
  }
387
402
 
388
- /// Returns a slice with a range of values for binding given the `action` state
403
+ /// Returns a slice with a range of values for binding given the `action` and `origin` states
389
404
  /// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
390
405
  pub inline fn binding(action: Action, origin: Action, parent: Optional(bool)) []const u3 {
391
- const turns = @field(action.durations, "binding");
392
- _ = origin;
393
- return if (parent == .true or turns < 1 or turns >= 4) &U3_NONE else &[_]u3{ 0, turns };
406
+ if (parent == .true) return &U3_NONE;
407
+ const a = @field(action.durations, "binding");
408
+ const o = @field(origin.durations, "binding");
409
+ if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
410
+ assert(a == 0 or a == o + 1);
411
+ return if (o >= 4) &U3_NONE else &[_]u3{ 0, o + 1 };
394
412
  }
395
413
 
396
414
  const SLOT_NONE = [_]u4{0};
@@ -535,72 +553,207 @@ test "Rolls.duration" {
535
553
  try expectEqualSlices(u1, &.{0}, Rolls.duration(actions.p2, .false));
536
554
  }
537
555
 
538
- const FIXME: Action = .{};
539
-
540
556
  test "Rolls.sleep" {
541
- try expectEqualSlices(u3, &.{0}, Rolls.sleep(.{ .durations = .{ .sleep = 0 } }, FIXME));
542
- try expectEqualSlices(u3, &.{0}, Rolls.sleep(.{ .durations = .{ .sleep = 7 } }, FIXME));
543
- try expectEqualSlices(u3, &.{ 0, 4 }, Rolls.sleep(.{ .durations = .{ .sleep = 4 } }, FIXME));
557
+ try expectEqualSlices(u3, &.{0}, Rolls.sleep(
558
+ .{ .durations = .{ .sleep = 0 } },
559
+ .{ .durations = .{ .sleep = 0 } },
560
+ ));
561
+ try expectEqualSlices(u3, &.{ 0, 1 }, Rolls.sleep(
562
+ .{ .durations = .{ .sleep = 1 } },
563
+ .{ .durations = .{ .sleep = 0 } },
564
+ ));
565
+ try expectEqualSlices(u3, &.{ 0, 2 }, Rolls.sleep(
566
+ .{ .durations = .{ .sleep = 0 } },
567
+ .{ .durations = .{ .sleep = 1 } },
568
+ ));
569
+ try expectEqualSlices(u3, &.{1}, Rolls.sleep(
570
+ .{ .durations = .{ .sleep = 1 } },
571
+ .{ .durations = .{ .sleep = 1 } },
572
+ ));
573
+ try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.sleep(
574
+ .{ .durations = .{ .sleep = 0 } },
575
+ .{ .durations = .{ .sleep = 2 } },
576
+ ));
577
+ try expectEqualSlices(u3, &.{ 0, 7 }, Rolls.sleep(
578
+ .{ .durations = .{ .sleep = 7 } },
579
+ .{ .durations = .{ .sleep = 6 } },
580
+ ));
581
+ try expectEqualSlices(u3, &.{0}, Rolls.sleep(
582
+ .{ .durations = .{ .sleep = 0 } },
583
+ .{ .durations = .{ .sleep = 7 } },
584
+ ));
544
585
  }
545
586
 
546
587
  test "Rolls.disable" {
547
- try expectEqualSlices(u4, &.{0}, Rolls.disable(.{ .durations = .{ .disable = 0 } }, FIXME, 0));
548
- try expectEqualSlices(u4, &.{0}, Rolls.disable(.{ .durations = .{ .disable = 8 } }, FIXME, 0));
549
- try expectEqualSlices(u4, &.{0}, Rolls.disable(.{ .durations = .{ .disable = 4 } }, FIXME, 1));
550
- try expectEqualSlices(u4, &.{ 0, 4 }, Rolls.disable(.{
551
- .durations = .{ .disable = 4 },
552
- }, FIXME, 0));
588
+ try expectEqualSlices(u4, &.{0}, Rolls.disable(
589
+ .{ .durations = .{ .disable = 0 } },
590
+ .{ .durations = .{ .disable = 0 } },
591
+ 0,
592
+ ));
593
+ try expectEqualSlices(u4, &.{ 0, 1 }, Rolls.disable(
594
+ .{ .durations = .{ .disable = 1 } },
595
+ .{ .durations = .{ .disable = 0 } },
596
+ 0,
597
+ ));
598
+ try expectEqualSlices(u4, &.{ 0, 2 }, Rolls.disable(
599
+ .{ .durations = .{ .disable = 0 } },
600
+ .{ .durations = .{ .disable = 1 } },
601
+ 0,
602
+ ));
603
+ try expectEqualSlices(u4, &.{1}, Rolls.disable(
604
+ .{ .durations = .{ .disable = 1 } },
605
+ .{ .durations = .{ .disable = 1 } },
606
+ 0,
607
+ ));
608
+ try expectEqualSlices(u4, &.{ 0, 3 }, Rolls.disable(
609
+ .{ .durations = .{ .disable = 0 } },
610
+ .{ .durations = .{ .disable = 2 } },
611
+ 0,
612
+ ));
613
+ try expectEqualSlices(u4, &.{0}, Rolls.disable(
614
+ .{ .durations = .{ .disable = 0 } },
615
+ .{ .durations = .{ .disable = 2 } },
616
+ 1,
617
+ ));
618
+ try expectEqualSlices(u4, &.{ 0, 8 }, Rolls.disable(
619
+ .{ .durations = .{ .disable = 8 } },
620
+ .{ .durations = .{ .disable = 7 } },
621
+ 0,
622
+ ));
623
+ try expectEqualSlices(u4, &.{0}, Rolls.disable(
624
+ .{ .durations = .{ .disable = 0 } },
625
+ .{ .durations = .{ .disable = 8 } },
626
+ 0,
627
+ ));
553
628
  }
554
629
 
555
630
  test "Rolls.confusion" {
556
- try expectEqualSlices(u3, &.{0}, Rolls.confusion(.{
557
- .durations = .{ .confusion = 0 },
558
- }, FIXME, 0));
559
- try expectEqualSlices(u3, &.{0}, Rolls.confusion(.{
560
- .durations = .{ .confusion = 5 },
561
- }, FIXME, 0));
562
- try expectEqualSlices(u3, &.{0}, Rolls.confusion(.{
563
- .durations = .{ .confusion = 3 },
564
- }, FIXME, 1));
565
- try expectEqualSlices(u3, &.{1}, Rolls.confusion(.{
566
- .durations = .{ .confusion = 1 },
567
- }, FIXME, 0));
568
- try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.confusion(.{
569
- .durations = .{ .confusion = 3 },
570
- }, FIXME, 0));
631
+ try expectEqualSlices(u3, &.{0}, Rolls.confusion(
632
+ .{ .durations = .{ .confusion = 0 } },
633
+ .{ .durations = .{ .confusion = 0 } },
634
+ 0,
635
+ ));
636
+ try expectEqualSlices(u3, &.{1}, Rolls.confusion(
637
+ .{ .durations = .{ .confusion = 1 } },
638
+ .{ .durations = .{ .confusion = 0 } },
639
+ 0,
640
+ ));
641
+ try expectEqualSlices(u3, &.{0}, Rolls.confusion(
642
+ .{ .durations = .{ .confusion = 0 } },
643
+ .{ .durations = .{ .confusion = 1 } },
644
+ 0,
645
+ ));
646
+ try expectEqualSlices(u3, &.{1}, Rolls.confusion(
647
+ .{ .durations = .{ .confusion = 1 } },
648
+ .{ .durations = .{ .confusion = 1 } },
649
+ 0,
650
+ ));
651
+ try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.confusion(
652
+ .{ .durations = .{ .confusion = 0 } },
653
+ .{ .durations = .{ .confusion = 2 } },
654
+ 0,
655
+ ));
656
+ try expectEqualSlices(u3, &.{0}, Rolls.confusion(
657
+ .{ .durations = .{ .confusion = 0 } },
658
+ .{ .durations = .{ .confusion = 2 } },
659
+ 1,
660
+ ));
661
+ try expectEqualSlices(u3, &.{ 0, 5 }, Rolls.confusion(
662
+ .{ .durations = .{ .confusion = 5 } },
663
+ .{ .durations = .{ .confusion = 4 } },
664
+ 0,
665
+ ));
666
+ try expectEqualSlices(u3, &.{0}, Rolls.confusion(
667
+ .{ .durations = .{ .confusion = 0 } },
668
+ .{ .durations = .{ .confusion = 5 } },
669
+ 0,
670
+ ));
571
671
  }
572
672
 
573
673
  test "Rolls.attacking" {
574
- try expectEqualSlices(u3, &.{0}, Rolls.attacking(.{
575
- .durations = .{ .attacking = 0 },
576
- }, FIXME, .false));
577
- try expectEqualSlices(u3, &.{0}, Rolls.attacking(.{
578
- .durations = .{ .attacking = 3 },
579
- }, FIXME, .false));
580
- try expectEqualSlices(u3, &.{0}, Rolls.attacking(.{
581
- .durations = .{ .attacking = 2 },
582
- }, FIXME, .true));
583
- try expectEqualSlices(u3, &.{1}, Rolls.attacking(.{
584
- .durations = .{ .attacking = 1 },
585
- }, FIXME, .false));
586
- try expectEqualSlices(u3, &.{ 0, 2 }, Rolls.attacking(.{
587
- .durations = .{ .attacking = 2 },
588
- }, FIXME, .false));
674
+ try expectEqualSlices(u3, &.{0}, Rolls.attacking(
675
+ .{ .durations = .{ .attacking = 0 } },
676
+ .{ .durations = .{ .attacking = 0 } },
677
+ .false,
678
+ ));
679
+ try expectEqualSlices(u3, &.{1}, Rolls.attacking(
680
+ .{ .durations = .{ .attacking = 1 } },
681
+ .{ .durations = .{ .attacking = 0 } },
682
+ .false,
683
+ ));
684
+ try expectEqualSlices(u3, &.{0}, Rolls.attacking(
685
+ .{ .durations = .{ .attacking = 0 } },
686
+ .{ .durations = .{ .attacking = 1 } },
687
+ .false,
688
+ ));
689
+ try expectEqualSlices(u3, &.{1}, Rolls.attacking(
690
+ .{ .durations = .{ .attacking = 1 } },
691
+ .{ .durations = .{ .attacking = 1 } },
692
+ .false,
693
+ ));
694
+ try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.attacking(
695
+ .{ .durations = .{ .attacking = 0 } },
696
+ .{ .durations = .{ .attacking = 2 } },
697
+ .false,
698
+ ));
699
+ try expectEqualSlices(u3, &.{0}, Rolls.attacking(
700
+ .{ .durations = .{ .attacking = 0 } },
701
+ .{ .durations = .{ .attacking = 2 } },
702
+ .true,
703
+ ));
704
+ try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.attacking(
705
+ .{ .durations = .{ .attacking = 3 } },
706
+ .{ .durations = .{ .attacking = 2 } },
707
+ .false,
708
+ ));
709
+ try expectEqualSlices(u3, &.{0}, Rolls.attacking(
710
+ .{ .durations = .{ .attacking = 0 } },
711
+ .{ .durations = .{ .attacking = 3 } },
712
+ .false,
713
+ ));
589
714
  }
590
715
 
591
716
  test "Rolls.binding" {
592
- try expectEqualSlices(u3, &.{0}, Rolls.binding(.{
593
- .durations = .{ .binding = 0 },
594
- }, FIXME, .false));
595
- try expectEqualSlices(u3, &.{0}, Rolls.binding(.{
596
- .durations = .{ .binding = 4 },
597
- }, FIXME, .false));
598
- try expectEqualSlices(u3, &.{0}, Rolls.binding(.{
599
- .durations = .{ .binding = 2 },
600
- }, FIXME, .true));
601
- try expectEqualSlices(u3, &.{ 0, 2 }, Rolls.binding(.{
602
- .durations = .{ .binding = 2 },
603
- }, FIXME, .false));
717
+ try expectEqualSlices(u3, &.{0}, Rolls.binding(
718
+ .{ .durations = .{ .binding = 0 } },
719
+ .{ .durations = .{ .binding = 0 } },
720
+ .false,
721
+ ));
722
+ try expectEqualSlices(u3, &.{ 0, 1 }, Rolls.binding(
723
+ .{ .durations = .{ .binding = 1 } },
724
+ .{ .durations = .{ .binding = 0 } },
725
+ .false,
726
+ ));
727
+ try expectEqualSlices(u3, &.{ 0, 2 }, Rolls.binding(
728
+ .{ .durations = .{ .binding = 0 } },
729
+ .{ .durations = .{ .binding = 1 } },
730
+ .false,
731
+ ));
732
+ try expectEqualSlices(u3, &.{1}, Rolls.binding(
733
+ .{ .durations = .{ .binding = 1 } },
734
+ .{ .durations = .{ .binding = 1 } },
735
+ .false,
736
+ ));
737
+ try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.binding(
738
+ .{ .durations = .{ .binding = 0 } },
739
+ .{ .durations = .{ .binding = 2 } },
740
+ .false,
741
+ ));
742
+ try expectEqualSlices(u3, &.{0}, Rolls.binding(
743
+ .{ .durations = .{ .binding = 0 } },
744
+ .{ .durations = .{ .binding = 2 } },
745
+ .true,
746
+ ));
747
+ try expectEqualSlices(u3, &.{ 0, 4 }, Rolls.binding(
748
+ .{ .durations = .{ .binding = 4 } },
749
+ .{ .durations = .{ .binding = 3 } },
750
+ .false,
751
+ ));
752
+ try expectEqualSlices(u3, &.{0}, Rolls.binding(
753
+ .{ .durations = .{ .binding = 0 } },
754
+ .{ .durations = .{ .binding = 4 } },
755
+ .false,
756
+ ));
604
757
  }
605
758
 
606
759
  test "Rolls.moveSlot" {
@@ -186,10 +186,7 @@ fn selectMove(
186
186
  assert(choice.data == 0);
187
187
  // GLITCH: https://glitchcity.wiki/Partial_trapping_move_Mirror_Move_link_battle_glitch
188
188
  if (foe_choice.type == .Switch) {
189
- const last = side.active.move(if (player == .P1)
190
- battle.last_moves.p1_index
191
- else
192
- battle.last_moves.p2_index);
189
+ const last = side.active.move(battle.lastMove(player).index);
193
190
  if (last.id == .Metronome) side.last_selected_move = last.id;
194
191
  if (last.id == .MirrorMove) return Result.Error;
195
192
  }
@@ -242,11 +239,7 @@ fn saveMove(battle: anytype, player: Player, choice: ?Choice) void {
242
239
  assert(showdown or move.pp != 0);
243
240
 
244
241
  side.last_selected_move = move.id;
245
- if (player == .P1) {
246
- battle.last_moves.p1_index = @intCast(c.data);
247
- } else {
248
- battle.last_moves.p2_index = @intCast(c.data);
249
- }
242
+ battle.lastMove(player).index = @intCast(c.data);
250
243
  }
251
244
  }
252
245
  }
@@ -264,11 +257,7 @@ fn switchIn(battle: anytype, player: Player, slot: u8, initial: bool, options: a
264
257
  side.order[0] = side.order[slot - 1];
265
258
  side.order[slot - 1] = out;
266
259
 
267
- if (player == .P1) {
268
- battle.last_moves.p1_index = 1;
269
- } else {
270
- battle.last_moves.p2_index = 1;
271
- }
260
+ battle.lastMove(player).index = 1;
272
261
 
273
262
  side.last_used_move = .None;
274
263
  foe.last_used_move = .None;
@@ -455,10 +444,7 @@ fn executeMove(
455
444
  if (mslot == 0 and side.last_selected_move != .None and side.last_selected_move != .Struggle) {
456
445
  // choice.data == 0 only happens with Struggle on Pokémon Showdown
457
446
  assert(!showdown);
458
- mslot = @intCast(if (player == .P1)
459
- battle.last_moves.p1_index
460
- else
461
- battle.last_moves.p2_index);
447
+ mslot = @intCast(battle.lastMove(player).index);
462
448
  const stored = side.stored();
463
449
  // GLITCH: Struggle bypass PP underflow via Hyper Beam / Trapping-switch auto selection
464
450
  auto = side.last_selected_move == .HyperBeam or side.active.volatiles.Bide or
@@ -479,10 +465,7 @@ fn executeMove(
479
465
  } else if (showdown and side.active.volatiles.Charging) {
480
466
  // Incorrect mslot with Pokémon Showdown choice semantics so we need to recover from index
481
467
  assert(mslot == 1);
482
- mslot = @intCast(if (player == .P1)
483
- battle.last_moves.p1_index
484
- else
485
- battle.last_moves.p2_index);
468
+ mslot = @intCast(battle.lastMove(player).index);
486
469
  }
487
470
 
488
471
  var skip_can = false;
@@ -834,11 +817,7 @@ fn setCounterable(battle: anytype, player: Player, side: *Side, move: Move.Data)
834
817
  // as the desync results from actually having two separate battle states that subtly disagree
835
818
  const counterable = side.last_selected_move != .Counter and move.bp > 0 and
836
819
  (move.type == .Normal or move.type == .Fighting);
837
- if (player == .P1) {
838
- battle.last_moves.p1_counterable = @intFromBool(counterable);
839
- } else {
840
- battle.last_moves.p2_counterable = @intFromBool(counterable);
841
- }
820
+ battle.lastMove(player).counterable = @intFromBool(counterable);
842
821
  }
843
822
 
844
823
  fn decrementPP(side: *Side, mslot: u4, auto: bool) void {
@@ -1278,11 +1257,7 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
1278
1257
  else
1279
1258
  foe.last_selected_move);
1280
1259
 
1281
- const used = (if (player.foe() == .P1)
1282
- battle.last_moves.p1_counterable
1283
- else
1284
- battle.last_moves.p2_counterable) != 0;
1285
-
1260
+ const used = battle.lastMove(player.foe()).counterable != 0;
1286
1261
  const selected = foe_last_selected_move.bp > 0 and
1287
1262
  foe.last_selected_move != .Counter and
1288
1263
  (foe_last_selected_move.type == .Normal or
@@ -1357,7 +1332,7 @@ fn mirrorMove(
1357
1332
  auto: bool,
1358
1333
  residual: *bool,
1359
1334
  options: anytype,
1360
- ) !?Result {
1335
+ ) anyerror!?Result {
1361
1336
  var side = battle.side(player);
1362
1337
  const foe = battle.foe(player);
1363
1338
 
@@ -1384,7 +1359,7 @@ fn metronome(
1384
1359
  auto: bool,
1385
1360
  residual: *bool,
1386
1361
  options: anytype,
1387
- ) !?Result {
1362
+ ) anyerror!?Result {
1388
1363
  var side = battle.side(player);
1389
1364
 
1390
1365
  side.last_selected_move = try Rolls.metronome(battle, player, options);
@@ -2705,7 +2680,7 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
2705
2680
  }
2706
2681
  }
2707
2682
 
2708
- inline fn isForced(active: ActivePokemon) bool {
2683
+ inline fn isForced(active: anytype) bool {
2709
2684
  return active.volatiles.Recharging or active.volatiles.Rage or
2710
2685
  active.volatiles.Thrashing or active.volatiles.Charging;
2711
2686
  }
@@ -2796,6 +2771,7 @@ pub const Rolls = struct {
2796
2771
  }
2797
2772
  };
2798
2773
 
2774
+ assert(roll >= 217 and roll <= 255);
2799
2775
  try options.chance.damage(player, roll);
2800
2776
  return roll;
2801
2777
  }
@@ -2920,6 +2896,7 @@ pub const Rolls = struct {
2920
2896
  }
2921
2897
  };
2922
2898
 
2899
+ assert(power < max);
2923
2900
  try options.chance.psywave(player, power, max);
2924
2901
  return power;
2925
2902
  }
@@ -2936,6 +2913,7 @@ pub const Rolls = struct {
2936
2913
  }
2937
2914
  };
2938
2915
 
2916
+ assert(duration >= 1 and duration <= 7);
2939
2917
  options.chance.duration(.sleep, player, player.foe(), duration);
2940
2918
  return duration;
2941
2919
  }
@@ -2948,6 +2926,7 @@ pub const Rolls = struct {
2948
2926
  else
2949
2927
  @intCast((battle.rng.next() & 7) + 1);
2950
2928
 
2929
+ assert(duration >= 1 and duration <= 8);
2951
2930
  options.chance.duration(.disable, player, player.foe(), duration);
2952
2931
  return duration;
2953
2932
  }
@@ -2960,6 +2939,7 @@ pub const Rolls = struct {
2960
2939
  else
2961
2940
  @intCast((battle.rng.next() & 3) + 2);
2962
2941
 
2942
+ assert(duration >= 2 and duration <= 5);
2963
2943
  options.chance.duration(.confusion, player, if (self) player else player.foe(), duration);
2964
2944
  return duration;
2965
2945
  }
@@ -2972,6 +2952,7 @@ pub const Rolls = struct {
2972
2952
  else
2973
2953
  @intCast((battle.rng.next() & 1) + 2);
2974
2954
 
2955
+ assert(duration >= 2 and duration <= 3);
2975
2956
  options.chance.duration(.attacking, player, player, duration);
2976
2957
  return duration;
2977
2958
  }
@@ -2992,6 +2973,7 @@ pub const Rolls = struct {
2992
2973
  break :n @intCast((if (r < 2) r else battle.rng.next() & 3) + 2);
2993
2974
  };
2994
2975
 
2976
+ assert(n >= 2 and n <= 5);
2995
2977
  if (effect == .Binding) {
2996
2978
  options.chance.duration(.binding, player, player, n);
2997
2979
  } else {
@@ -3097,7 +3079,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3097
3079
  const side = battle.side(player);
3098
3080
  const foe = battle.foe(player);
3099
3081
 
3100
- var active = side.active;
3082
+ var active = &side.active;
3101
3083
  const stored = side.stored();
3102
3084
 
3103
3085
  // While players are not given any input options on the cartridge in these cases,
@@ -54,13 +54,12 @@ The following information is required to simulate a Generation II Pokémon battl
54
54
  | | | |
55
55
  | `active.last_used_move` | `PlayerUsedMove` | `side.lastMove` |
56
56
 
57
- - Pokémon Showdown does not implement the correct Generation II RNG and as such its `seed` is
57
+ - Pokémon Showdown doesn't implement the correct Generation II RNG and as such its `seed` is
58
58
  different
59
- - `battle.turn` only needs to be tracked in order to be compatible with the Pokémon Showdown
60
- protocol
59
+ - `battle.turn` only needs to be tracked to be compatible with the Pokémon Showdown protocol
61
60
  - Battle results (win, lose, draw) or request states are communicated via the return code of
62
61
  `Battle.update`
63
- - Nicknames (`BattleMonNick`/`pokemon.name`) are not handled by the pkmn engine as they are expected
62
+ - Nicknames (`BattleMonNick`/`pokemon.name`) aren't handled by the pkmn engine as they're expected
64
63
  to be handled by driver code if required
65
64
 
66
65
  ==**TODO**==
@@ -77,9 +76,9 @@ between the data structure for the "active" Pokémon and its party members (see
77
76
 
78
77
  ### `Pokemon` / `ActivePokemon`
79
78
 
80
- Similar to the cartridge, in order to save space different information is stored depending on
81
- whether a [Pokémon](https://pkmn.cc/bulba/Pok%c3%a9mon_data_structure_%28Generation_II%29) is
82
- actively participating in battle vs. is switched out (pret's [`battle_struct` vs.
79
+ Similar to the cartridge, to save space different information is stored depending on whether a
80
+ [Pokémon](https://pkmn.cc/bulba/Pok%c3%a9mon_data_structure_%28Generation_II%29) is actively
81
+ participating in battle vs. is switched out (pret's [`battle_struct` vs.
83
82
  `party_struct`](https://pkmn.cc/pokecrystal/macros/ram.asm)). In Pokémon Showdown, all the Pokemon
84
83
  are represented by the same
85
84
  [`Pokemon`](https://github.com/smogon/pokemon-showdown/blob/master/sim/pokemon.ts) class, and static
@@ -93,14 +92,14 @@ A `MoveSlot` is a data-type for a `(move, current pp)` pair. A pared down versio
93
92
  Showdown's `Pokemon#moveSlot`, it also stores data from the cartridge's `battle_struct::Move` macro
94
93
  and can be used to replace the `PlayerMove*` data. Move PP is stored as a full byte instead of how
95
94
  the cartridge (`battle_struct::PP`) stores it (6 bits for current PP and the remaining 2 bits used
96
- to store the number of applied PP Ups). PP Up bits do not actually need to be stored on move slot
95
+ to store the number of applied PP Ups). PP Up bits don't actually need to be stored on move slot
97
96
  as max PP is never relevant in Generation II (the Mystery Berry always increases PP from 0 -> 5).
98
97
 
99
98
  #### `Status`
100
99
 
101
100
  Bitfield representation of a Pokémon's major [status
102
- condition](https://pkmn.cc/bulba/Status_condition), mirroring how it is stored on the cartridge. A
103
- value of `0x00` means that the Pokémon is not affected by any major status, otherwise the lower 3
101
+ condition](https://pkmn.cc/bulba/Status_condition), mirroring how it's stored on the cartridge. A
102
+ value of `0x00` means that the Pokémon isn't affected by any major status, otherwise the lower 3
104
103
  bits represent the remaining duration for Sleep. Other status are denoted by the presence of
105
104
  individual bits - at most one status should be set at any given time.
106
105
 
@@ -161,7 +160,7 @@ boosts should always range from `-6`...`6` instead of `1`...`13` as on the cartr
161
160
 
162
161
  `Moves` serves as an identifier for a unique [Pokémon move](https://pkmn.cc/bulba/Move) that can be
163
162
  used to retrieve a `Move` with information regarding base power, accuracy and type. As covered
164
- above, PP information isn't strictly necessary in Generation I, but fits neatly into the 4 bits of
163
+ earlier, PP information isn't strictly necessary in Generation I, but fits neatly into the 4 bits of
165
164
  padding after noticing that all PP values are multiples of 5. `Moves.None` exists as a special
166
165
  sentinel value to indicate `null`.
167
166
 
@@ -215,8 +214,8 @@ entropy](https://en.wikipedia.org/wiki/Entropy_(information_theory))) is as foll
215
214
  | **wrap** | 0...5 | 3 | | **perish song** | 0...4 | 3 |
216
215
  | **item** | TODO | 5? | | **gender** | 0..1 | 1 |
217
216
 
218
- From this we can determine the minimum bits required to store each data structure to determine how
219
- much overhead the representations above have after taking into consideration [alignment &
217
+ From this one can determine the minimum bits required to store each data structure to determine how
218
+ much overhead the preceding representations have after taking into consideration [alignment &
220
219
  padding](https://en.wikipedia.org/wiki/Data_structure_alignment) and
221
220
  [denormalization](https://en.wikipedia.org/wiki/Denormalization):
222
221