@pkmn/engine 0.1.0-dev.1336408 → 0.1.0-dev.14ca6108

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.
@@ -6,6 +6,7 @@ const common = @import("../common/data.zig");
6
6
  const DEBUG = @import("../common/debug.zig").print;
7
7
  const protocol = @import("../common/protocol.zig");
8
8
  const rng = @import("../common/rng.zig");
9
+ const util = @import("../common/util.zig");
9
10
 
10
11
  const data = @import("data.zig");
11
12
 
@@ -13,14 +14,15 @@ const assert = std.debug.assert;
13
14
 
14
15
  const expectEqual = std.testing.expectEqual;
15
16
  const expectEqualSlices = std.testing.expectEqualSlices;
17
+ const expectEqualStrings = std.testing.expectEqualStrings;
18
+
19
+ const showdown = pkmn.options.showdown;
16
20
 
17
21
  const Choice = common.Choice;
18
22
  const ID = common.ID;
19
23
  const Player = common.Player;
20
24
  const Result = common.Result;
21
25
 
22
- const showdown = pkmn.options.showdown;
23
-
24
26
  const Activate = protocol.Activate;
25
27
  const Cant = protocol.Cant;
26
28
  const Damage = protocol.Damage;
@@ -30,6 +32,8 @@ const Start = protocol.Start;
30
32
 
31
33
  const Gen12 = rng.Gen12;
32
34
 
35
+ const PointerType = util.PointerType;
36
+
33
37
  const ActivePokemon = data.ActivePokemon;
34
38
  const Effectiveness = data.Effectiveness;
35
39
  const Item = data.Item;
@@ -89,6 +93,25 @@ const CRITICAL_HITS = &[_]u8{
89
93
 
90
94
  const MAX_STAT_VALUE = 999;
91
95
 
96
+ const State = struct {
97
+ damage: u16 = 0,
98
+ effectiveness: u16 = Effectiveness.neutral,
99
+ bp: u8 = 0,
100
+ move: Move = .None,
101
+ mslot: u4 = 0,
102
+ first: bool = false,
103
+ miss: bool = false,
104
+ crit: bool = false,
105
+
106
+ comptime {
107
+ assert(@sizeOf(State) == 10);
108
+ }
109
+
110
+ inline fn immune(self: *State) bool {
111
+ return self.effectiveness == 0;
112
+ }
113
+ };
114
+
92
115
  pub fn update(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Result {
93
116
  assert(c1.type != .Pass or c2.type != .Pass or battle.turn == 0);
94
117
  if (battle.turn == 0) return start(battle, options);
@@ -167,14 +190,14 @@ fn selectMove(battle: anytype, player: Player, choice: Choice) void {
167
190
  if (choice.data == 0) {
168
191
  const struggle = ok: {
169
192
  for (side.active.moves, 0..) |move, i| {
170
- if (move.pp > 0 and volatiles.disabled_move != i + 1) break :ok false;
193
+ if (move.pp > 0 and volatiles.disable.move != i + 1) break :ok false;
171
194
  }
172
195
  break :ok true;
173
196
  };
174
197
  assert(struggle);
175
198
  // TODO wCurPlayerMove = .Struggle;
176
199
  } else {
177
- assert(showdown or side.active.volatiles.disabled_move != choice.data);
200
+ assert(showdown or side.active.volatiles.disable.move != choice.data);
178
201
  const move = side.active.move(choice.data);
179
202
  // You cannot *select* a move with 0 PP
180
203
  assert(move.pp != 0);
@@ -326,6 +349,12 @@ fn doTurn(
326
349
  volatiles.Endure = false;
327
350
  volatiles.DestinyBond = false;
328
351
 
352
+ // FIXME
353
+ try handleResidual(battle, player, options);
354
+
355
+ // FIXME
356
+ try betweenTurns(battle, 0, options);
357
+
329
358
  return null;
330
359
  }
331
360
 
@@ -342,16 +371,16 @@ fn executeMove(
342
371
  }
343
372
 
344
373
  assert(choice.type == .Move);
345
- var mslot: u3 = @intCast(choice.data);
346
-
347
- // const move = Move.get(side.last_selected_move);
374
+ var mslot: u4 = @intCast(choice.data);
375
+ var move = Move.Pound; // TODO
348
376
 
349
- // CheckTurn
377
+ if (!try beforeMove(battle, player, move, options)) return null;
350
378
 
351
- return doMove(battle, player, mslot, first, options);
379
+ var state: State = .{ .move = move, .mslot = mslot, .first = first };
380
+ return doMove(battle, player, &state, options);
352
381
  }
353
382
 
354
- fn beforeMove(battle: anytype, player: Player, options: anytype) !bool {
383
+ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bool {
355
384
  var log = options.log;
356
385
  var side = battle.side(player);
357
386
  // const foe = battle.foe(player);
@@ -360,8 +389,6 @@ fn beforeMove(battle: anytype, player: Player, options: anytype) !bool {
360
389
  const ident = battle.active(player);
361
390
  var volatiles = &active.volatiles;
362
391
 
363
- const move = Move.Pound; // TODO
364
-
365
392
  if (volatiles.Recharging) {
366
393
  volatiles.Recharging = false;
367
394
  cantMove(volatiles);
@@ -412,16 +439,16 @@ fn beforeMove(battle: anytype, player: Player, options: anytype) !bool {
412
439
  return false;
413
440
  }
414
441
 
415
- if (volatiles.disabled_duration > 0) {
442
+ if (volatiles.disable.duration > 0) {
416
443
  // if (options.calc.modify(player, .disable)) |extend| {
417
- // if (!extend) volatiles.disabled_duration = 0;
444
+ // if (!extend) volatiles.disable.duration = 0;
418
445
  // } else {
419
- volatiles.disabled_duration -= 1;
446
+ volatiles.disable.duration -= 1;
420
447
  // }
421
- try options.chance.disable(player, volatiles.disabled_duration);
448
+ try options.chance.disable(player, volatiles.disable.duration);
422
449
 
423
- if (volatiles.disabled_duration == 0) {
424
- volatiles.disabled_move = 0;
450
+ if (volatiles.disable.duration == 0) {
451
+ volatiles.disable.move = 0;
425
452
  try log.end(ident, .Disable);
426
453
  }
427
454
  }
@@ -455,7 +482,7 @@ fn beforeMove(battle: anytype, player: Player, options: anytype) !bool {
455
482
  }
456
483
 
457
484
  if (volatiles.Attract) {
458
- try log.atrract(ident, .Attract);
485
+ try log.activate(ident, .Attract);
459
486
 
460
487
  if (try Rolls.attract(battle, player, options)) {
461
488
  cantMove(volatiles);
@@ -464,9 +491,9 @@ fn beforeMove(battle: anytype, player: Player, options: anytype) !bool {
464
491
  }
465
492
  }
466
493
 
467
- if (side.active.volatiles.disabled_move != 0) {
468
- // A Pokémon that transforms after being disabled may end up with less move slots
469
- const m = side.active.moves[side.active.volatiles.disabled_move - 1].id;
494
+ if (side.active.volatiles.disable.move != 0) {
495
+ // A Pokémon that transforms after being disable may end up with less move slots
496
+ const m = side.active.moves[side.active.volatiles.disable.move - 1].id;
470
497
  if (m != .None and m == move) {
471
498
  side.active.volatiles.Charging = false;
472
499
  cantMove(volatiles);
@@ -484,7 +511,7 @@ fn beforeMove(battle: anytype, player: Player, options: anytype) !bool {
484
511
  return true;
485
512
  }
486
513
 
487
- fn cantMove(volatiles: *Volatiles) void {
514
+ fn cantMove(volatiles: *align(8) Volatiles) void {
488
515
  volatiles.Bide = false;
489
516
  volatiles.Thrashing = false;
490
517
  volatiles.Charging = false;
@@ -495,7 +522,7 @@ fn cantMove(volatiles: *Volatiles) void {
495
522
  volatiles.fury_cutter = 0;
496
523
  }
497
524
 
498
- fn canMove(battle: anytype, player: Player, options: anytype) !bool {
525
+ fn canMove(battle: anytype, player: Player, options: anytype) !void {
499
526
  _ = battle;
500
527
  _ = player;
501
528
  _ = options;
@@ -520,325 +547,56 @@ fn decrementPP(side: *Side, mslot: u3) void {
520
547
  assert(active.move(mslot).pp == side.stored().move(mslot).pp);
521
548
  }
522
549
 
523
- fn doMove(battle: anytype, player: Player, mslot: u3, first: bool, options: anytype) !?Result {
524
- var side = battle.side(player);
525
- var move = Move.get(.Teleport); // side.last_selected_move
526
- _ = .{ side, mslot };
527
-
528
- switch (move.effect) {
529
- .None, .HighCritical, .AlwaysHit, .Priority, .Gust, .TriAttack, .Earthquake => {
530
- // TODO Gust skips kingsrock and does double fly damage
531
- // TODO triattack does own effect instead of kingsrock
532
- // TODO double damage for earthquake
533
- },
534
- .Attract => {
535
- try Effects.attract(battle, player, options); // TODO
536
- },
537
- .BatonPass => {
538
- try Effects.batonPass(battle, player, options); // TODO
539
- },
540
- .BeatUp => {
541
- try Effects.beatUp(battle, player, options); // TODO
542
- },
543
- .BellyDrum => {
544
- try Effects.bellyDrum(battle, player, options); // TODO
545
- },
546
- .Bide => {
547
- try Effects.bide(battle, player, options); // TODO
548
- },
549
- .Binding => {
550
- try Effects.binding(battle, player, options); // TODO
551
- },
552
- .BurnChance => {
553
- try Effects.burnChance(battle, player, move, options); // TODO
554
- },
555
- .Confusion, .ConfusionChance => {
556
- try Effects.confusion(battle, player, move, options); // TODO
557
- },
558
- .Conversion => {
559
- try Effects.conversion(battle, player, options); // TODO
560
- },
561
- .Conversion2 => {
562
- try Effects.conversion2(battle, player, options); // TODO
563
- },
564
- .Counter => {
565
- try Effects.counter(battle, player, move, options); // TODO
566
- },
567
- .Curse => {
568
- try Effects.curse(battle, player, options); // TODO
569
- },
570
- .DefenseCurl => {
571
- try Effects.defenseCurl(battle, player, options); // TODO
572
- },
573
- .DestinyBond => {
574
- try Effects.destinyBond(battle, player, options); // TODO
575
- },
576
- .Disable => {
577
- try Effects.disable(battle, player, options); // TODO
578
- },
579
- .DrainHP, .DreamEater => {
580
- try Effects.drainHP(battle, player, options); // TODO
581
- },
582
- .Encore => {
583
- try Effects.encore(battle, player, first, options); // TODO
584
- },
585
- .Endure => {
586
- try Effects.protect(battle, player, Move.Endure, first, options); // TODO
587
- },
588
- .Explode => {
589
- try Effects.explode(battle, player, options); // TODO
590
- },
591
- .FalseSwipe => {
592
- var damage: u16 = 0; // TODO
593
- damage = try Effects.falseSwipe(battle, player, damage);
594
- },
595
- .FixedDamage, .Psywave, .SuperFang => {
596
- try Effects.fixedDamage(battle, player, move, options); // TODO
597
- },
598
- .FlameWheel => {
599
- try Effects.flameWheel(battle, player, options); // TODO
600
- },
601
- .FlinchChance => {
602
- try Effects.flinchChance(battle, player, move, options); // TODO
603
- },
604
- .FlyDig => {
605
- try Effects.flyDig(battle, player, move, options); // TODO
606
- },
607
- .FocusEnergy => {
608
- try Effects.focusEnergy(battle, player, options); // TODO
609
- },
610
- .ForceSwitch => {
611
- try Effects.forceSwitch(battle, player, options); // TODO
612
- },
613
- .Foresight => {
614
- try Effects.foresight(battle, player, options); // TODO
615
- },
616
- .FreezeChance => {
617
- try Effects.freezeChance(battle, player, move, options); // TODO
618
- },
619
- .FuryCutter => {
620
- try Effects.furyCutter(battle, player, options); // TODO
621
- },
622
- .FutureSight => {
623
- try Effects.futureSight(battle, player, options); // TODO
624
- },
625
- .Return, .Frustration => {
626
- const bp = try Effects.happiness(battle, player, move); // TODO
627
- _ = bp;
628
- },
629
- .Haze => {
630
- try Effects.haze(battle, player, options); // TODO
631
- },
632
- .Heal => {
633
- try Effects.heal(battle, player, options); // TODO
634
- },
635
- .HealBell => {
636
- try Effects.healBell(battle, player, options); // TODO
637
- },
638
- .HiddenPower => {
639
- try Effects.hiddenPower(battle, player, options); // TODO
640
- },
641
- .HyperBeam => {
642
- try Effects.hyperBeam(battle, player, options); // TODO
643
- },
644
- .JumpKick => {
645
- try Effects.jumpKick(battle, player, options); // TODO
646
- },
647
- .LeechSeed => {
648
- try Effects.leechSeed(battle, player, options); // TODO
649
- },
650
- .LevelDamage => {
651
- try Effects.levelDamage(battle, player, options); // TODO
652
- },
653
- .LightScreen, .Reflect => {
654
- try Effects.screens(battle, player, move, options); // TODO
655
- },
656
- .LockOn => {
657
- try Effects.lockOn(battle, player, options); // TODO
658
- },
659
- .Magnitude => {
660
- try Effects.magnitude(battle, player, options); // TODO
661
- },
662
- .MeanLook => {
663
- try Effects.meanLook(battle, player, options); // TODO
664
- },
665
- .Metronome => {
666
- try Effects.metronome(battle, player, options); // TODO
667
- },
668
- .Mimic => {
669
- try Effects.mimic(battle, player, options); // TODO
670
- },
671
- .MirrorCoat => {
672
- try Effects.mirrorCoat(battle, player, options); // TODO
673
- },
674
- .MirrorMove => {
675
- try Effects.mirrorMove(battle, player, options); // TODO
676
- },
677
- .Mist => {
678
- try Effects.mist(battle, player, options); // TODO
679
- },
680
- .Moonlight, .MorningSun => {
681
- try Effects.weatherHeal(battle, player, options); // TODO
682
- },
683
- .MultiHit, .DoubleHit, .Twineedle => {
684
- try Effects.multiHit(battle, player, options); // TODO
685
- },
686
- .Nightmare => {
687
- try Effects.nightmare(battle, player, options); // TODO
688
- },
689
- .OHKO => {
690
- try Effects.ohko(battle, player, options); // TODO
691
- },
692
- .PainSplit => {
693
- try Effects.painSplit(battle, player, options); // TODO
694
- },
695
- .Paralyze => {
696
- try Effects.paralyze(battle, player, options); // TODO
697
- },
698
- .ParalyzeChance, .Thunder => {
699
- try Effects.paralyzeChance(battle, player, options); // TODO
700
- },
701
- .PayDay => {
702
- try Effects.payDay(battle, player, options); // TODO
703
- },
704
- .PerishSong => {
705
- try Effects.perishSong(battle, player, options); // TODO
706
- },
707
- .Poison, .PoisonChance, .Toxic => {
708
- try Effects.poison(battle, player, move, options); // TODO
709
- },
710
- .Present => {
711
- try Effects.present(battle, player, options); // TODO
712
- },
713
- .Protect => {
714
- try Effects.protect(battle, player, Move.Protect, first, options); // TODO Detect?
715
- },
716
- .PsychUp => {
717
- try Effects.psychUp(battle, player, options); // TODO
718
- },
719
- .Pursuit => {
720
- try Effects.pursuit(battle, player, options); // TODO
721
- },
722
- .Rage => {
723
- try Effects.rage(battle, player, options); // TODO
724
- },
725
- .RainDance => {
726
- try Effects.rainDance(battle, player, options); // TODOq
727
- },
728
- .RapidSpin => {
729
- try Effects.rapidSpin(battle, player, options); // TODO
730
- },
731
- .RazorWind => {
732
- try Effects.razorWind(battle, player, options); // TODO
733
- },
734
- .Recoil => {
735
- try Effects.recoil(battle, player, options); // TODO
736
- },
737
- .Reversal => {
738
- try Effects.reversal(battle, player, options); // TODO
739
- },
740
- .Rollout => {
741
- try Effects.rollout(battle, player, options); // TODO
742
- },
743
- .SacredFire => {
744
- try Effects.sacredFire(battle, player, options); // TODO
745
- },
746
- .Safeguard => {
747
- try Effects.safeguard(battle, player, options); // TODO
748
- },
749
- .Sandstorm => {
750
- try Effects.sandstorm(battle, player, options); // TODO
751
- },
752
- .Sketch => {
753
- try Effects.sketch(battle, player, options); // TODO
754
- },
755
- .SkullBash => {
756
- try Effects.skullBash(battle, player, options); // TODO
757
- },
758
- .SkyAttack => {
759
- try Effects.skyAttack(battle, player, options); // TODO
760
- },
761
- .Sleep => {
762
- try Effects.sleep(battle, player, options); // TODO
763
- },
764
- .SleepTalk => {
765
- try Effects.sleepTalk(battle, player, mslot, options); // TODO
766
- },
767
- .Snore => {
768
- try Effects.snore(battle, player, options); // TODO
769
- },
770
- .Solarbeam => {
771
- try Effects.solarBeam(battle, player, options); // TODO
772
- },
773
- .Spikes => {
774
- try Effects.spikes(battle, player, options); // TODO
775
- },
776
- .Spite => {
777
- try Effects.spite(battle, player, options); // TODO
778
- },
779
- .Splash => {
780
- try Effects.splash(battle, player, options); // TODO
781
- },
782
- .Stomp => {
783
- try Effects.stomp(battle, player, options); // TODO
784
- },
785
- .Substitute => {
786
- try Effects.substitute(battle, player, options); // TODO
787
- },
788
- .SunnyDay => {
789
- try Effects.sunnyDay(battle, player, options); // TODO
790
- },
791
- .Swagger => {
792
- try Effects.swagger(battle, player, options); // TODO
793
- },
794
- .Synthesis => {
795
- try Effects.synthesis(battle, player, options); // TODO
796
- },
797
- .Teleport => {
798
- try Effects.teleport(battle, player, options); // TODO
799
- },
800
- .Thief => {
801
- try Effects.thief(battle, player, options); // TODO
802
- },
803
- .Thrashing => {
804
- try Effects.thrashing(battle, player, options); // TODO
805
- },
806
- .Transform => {
807
- try Effects.transform(battle, player, options); // TODO
808
- },
809
- .TripleKick => {
810
- try Effects.tripleKick(battle, player, options); // TODO
811
- },
812
- .Twister => {
813
- try Effects.twister(battle, player, options); // TODO
814
- },
815
- // zig fmt: off
816
- .AllStatUpChance,
817
- .AttackUp1, .AttackUp2, .AttackUpChance,
818
- .DefenseUp1, .DefenseUp2, .DefenseUpChance,
819
- .EvasionUp1,
820
- .SpAtkUp1,
821
- .SpDefUp2,
822
- .SpeedUp2 => {
823
- // zig fmt: on
824
- try Effects.boost(battle, player, move, options); // TODO
825
- },
826
- // zig fmt: off
827
- .AccuracyDown1, .AccuracyDownChance,
828
- .AttackDown1, .AttackDown2, .AttackDownChance,
829
- .DefenseDown1, .DefenseDown2, .DefenseDownChance,
830
- .EvasionDown1,
831
- .SpDefDownChance,
832
- .SpeedDown1, .SpeedDown2, .SpeedDownChance => {
833
- // zig fmt: on
834
- try Effects.unboost(battle, player, move, options); // TODO
835
- },
836
- }
550
+ const Command: type =
551
+ fn (battle: anytype, player: Player, state: *State, options: anytype) anyerror!void;
552
+
553
+ // zig fmt: off
554
+ // const COMMANDS = [_][]Command{
555
+ // // None
556
+ // (if (showdown) &[_]Command{
557
+ // // checkHit, checkCriticalHit, calcDamage, adjustDamage, randomizeDamage,
558
+ // // applyDamage, buildRage, kingsRock,
559
+ // } else &[_]Command{
560
+ // // checkCriticalHit, calcDamage, adjustDamage, randomizeDamage,
561
+ // // checkHit, applyDamage, buildRage, kingsRock,
562
+ // }),
563
+ // };
564
+ // zig fmt: on
565
+
566
+ fn doMove(battle: anytype, player: Player, state: *State, options: anytype) !?Result {
567
+ // FIXME
568
+ // const commands = COMMANDS[@intFromEnum(Move.get(state.move).effect)];
569
+ // for (commands) |command| {
570
+ // if (!try command(battle, player, state, options)) return null;
571
+ // }
572
+ // return null;
573
+ try checkCriticalHit(battle, player, state, options);
574
+ try calcDamage(battle, player, state, options);
575
+ try adjustDamage(battle, player, state, options);
576
+ try randomizeDamage(battle, player, state, options);
577
+ try applyDamage(battle, player, state, options);
578
+ try buildRage(battle, player, state, options);
579
+ try kingsRock(battle, player, state, options);
580
+
581
+ try Effects.attract(battle, player, state, options);
582
+ try Effects.conversion(battle, player, state, options);
583
+ try Effects.conversion2(battle, player, state, options);
584
+ try Effects.destinyBond(battle, player, state, options);
585
+ try Effects.disable(battle, player, state, options);
586
+ try Effects.encore(battle, player, state, options);
587
+ try Effects.falseSwipe(battle, player, state, options);
588
+ try Effects.focusEnergy(battle, player, state, options);
589
+ try Effects.forceSwitch(battle, player, state, options);
590
+ try Effects.foresight(battle, player, state, options);
591
+ try Effects.happiness(battle, player, state, options);
592
+ try Effects.protect(battle, player, state, options);
593
+
837
594
  return null;
838
595
  }
839
596
 
840
- fn checkCriticalHit(battle: anytype, player: Player, move: Move.Data, options: anytype) !bool {
841
- if (move.bp == 0) return false;
597
+ fn checkCriticalHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
598
+ const move = Move.get(state.move);
599
+ if (move.bp == 0) return;
842
600
 
843
601
  const side = battle.side(player);
844
602
  const pokemon = side.stored();
@@ -853,41 +611,38 @@ fn checkCriticalHit(battle: anytype, player: Player, move: Move.Data, options: a
853
611
  }
854
612
  assert(stage <= 4);
855
613
 
856
- return Rolls.criticalHit(battle, player, CRITICAL_HITS[stage], options);
614
+ state.crit = try Rolls.criticalHit(battle, player, CRITICAL_HITS[stage], options);
857
615
  }
858
616
 
859
617
  fn calcDamage(
860
618
  battle: anytype,
861
619
  player: Player,
862
- target_player: Player,
863
- m: ?Move.Data,
864
- crit: bool,
865
- ) u16 {
620
+ state: *State,
621
+ options: anytype,
622
+ ) !void {
866
623
  _ = battle;
867
624
  _ = player;
868
- _ = target_player;
869
- _ = m;
870
- _ = crit;
871
- return 0;
625
+ _ = state;
626
+ _ = options;
872
627
  }
873
628
 
874
629
  const FORESIGHT: Types = .{ .type1 = .Normal, .type2 = .Fighting };
875
630
 
876
- fn adjustDamage(battle: anytype, player: Player, m: Move, damage: u16, effectiveness: *u16) u16 {
877
- if (m == .Struggle) return;
631
+ fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype) !void {
632
+ if (state.move == .Struggle) return;
878
633
 
879
634
  const side = battle.side(player);
880
635
  const foe = battle.foe(player);
881
636
  const types = foe.active.types;
882
- const move = Move.get(side.m);
637
+ const move = Move.get(state.move);
883
638
 
884
- var d = damage;
639
+ var d = state.damage;
885
640
  if ((move.type == .Water and battle.field.weather == .Rain) or
886
641
  (move.type == .Fire and battle.field.weather == .Sun))
887
642
  {
888
643
  d = d *% @intFromEnum(Effectiveness.Super) / 10;
889
644
  } else if ((move.type == .Water and battle.field.weather == .Sun) or
890
- ((move.type == .Fire or m == .SolarBeam) and battle.field.weather == .Rain))
645
+ ((move.type == .Fire or state.move == .SolarBeam) and battle.field.weather == .Rain))
891
646
  {
892
647
  d = d *% @intFromEnum(Effectiveness.Resisted) / 10;
893
648
  }
@@ -897,17 +652,17 @@ fn adjustDamage(battle: anytype, player: Player, m: Move, damage: u16, effective
897
652
  const neutral = @intFromEnum(Effectiveness.Neutral);
898
653
  const foresight = foe.active.volatiles.Foresight and FORESIGHT.includes(move.type);
899
654
  const eff1: u16 = if (foresight and types.type1 == .Ghost)
900
- 10
655
+ neutral
901
656
  else
902
657
  @intFromEnum(move.type.effectiveness(types.type1));
903
658
  const eff2: u16 = if (foresight and types.type2 == .Ghost)
904
- 10
659
+ neutral
905
660
  else
906
661
  @intFromEnum(move.type.effectiveness(types.type2));
907
662
 
908
663
  // Type effectiveness matchup precedence only matters with (NVE, SE)
909
664
  if (!showdown and (eff1 + eff2) == Effectiveness.mismatch and
910
- Type.precedence(move.type, types.type1) > Type.precedence(move.type, types.type2))
665
+ types.type1.precedence() > types.type2.precedence())
911
666
  {
912
667
  assert(eff2 != neutral);
913
668
  d = d *% eff2 / 10;
@@ -919,65 +674,79 @@ fn adjustDamage(battle: anytype, player: Player, m: Move, damage: u16, effective
919
674
  if (types.type1 != types.type2 and eff2 != neutral) d = d *% eff2 / 10;
920
675
  }
921
676
 
922
- effectiveness.* = if (types.type1 == types.type2) eff1 * neutral else eff1 * eff2;
923
- return d;
677
+ state.effectiveness = if (types.type1 == types.type2) eff1 * neutral else eff1 * eff2;
678
+ state.damage = d;
924
679
  }
925
680
 
926
- fn randomizeDamage(battle: anytype, player: Player, damage: u16, options: anytype) !u16 {
927
- if (damage <= 1) return;
681
+ fn randomizeDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
682
+ if (state.damage <= 1) return;
928
683
  const random = try Rolls.damage(battle, player, options);
929
- options.calc.base(player.foe(), damage);
930
- damage = @intCast(@as(u32, damage) *% random / 255);
931
- options.calc.final(player.foe(), damage);
932
- return damage;
684
+ options.calc.base(player.foe(), state.damage);
685
+ state.damage = @intCast(@as(u32, state.damage) *% random / 255);
686
+ options.calc.final(player.foe(), state.damage);
933
687
  }
934
688
 
935
689
  fn applyDamage(
936
690
  battle: anytype,
937
691
  player: Player,
692
+ state: *State,
938
693
  options: anytype,
939
- ) !bool {
694
+ ) !void {
940
695
  _ = battle;
941
696
  _ = player;
697
+ _ = state;
942
698
  _ = options;
943
- return false;
944
- }
945
-
946
- fn checkHit(battle: anytype, player: Player, move: Move.Data, options: anytype) !bool {
947
- if (try moveHit(battle, player, move, options)) return true;
948
-
949
- // try options.chance.commit(player, .miss);
950
- // try options.log.lastmiss();
951
- // try options.log.miss(battle.active(player));
952
-
953
- return false;
954
699
  }
955
700
 
956
701
  // TODO: set damage = 0 if missed and not (High) Jump Kick
957
- fn moveHit(
702
+ fn checkHit(
958
703
  battle: anytype,
959
704
  player: Player,
960
- move: Move.Data,
705
+ state: *State,
961
706
  options: anytype,
962
- ) !bool {
707
+ ) !void {
963
708
  const side = battle.side(player);
964
709
  const foe = battle.foe(player);
965
710
 
966
- if (move.effect == .DreamEater and !Status.is(foe.stored().status, .SLP)) return false;
967
- if (foe.active.volatiles.Protect) return false;
711
+ const move = Move.get(state.move);
712
+
713
+ if (move.effect == .DreamEater and !Status.is(foe.stored().status, .SLP)) {
714
+ state.miss = true;
715
+ return;
716
+ }
717
+ if (foe.active.volatiles.Protect) {
718
+ state.miss = true;
719
+ return;
720
+ }
968
721
  const drain = move.effect == .DrainHP or move.effect == .DreamEater;
969
- if (drain and foe.active.volatiles.Substitute) return false;
722
+ if (drain and foe.active.volatiles.Substitute) {
723
+ state.miss = true;
724
+ return;
725
+ }
970
726
  if (side.active.volatiles.LockOn) {
971
- return !(foe.active.volatiles.Flying and move.extra.undeground);
727
+ state.miss = !(foe.active.volatiles.Flying and move.extra.undeground);
728
+ return;
972
729
  }
973
730
  assert(!(foe.active.volatiles.Flying and foe.active.volatiles.Underground));
974
731
  if (foe.active.volatiles.Flying) {
975
- if (!move.extra.flying) return false;
732
+ if (!move.extra.flying) {
733
+ state.miss = true;
734
+ return;
735
+ }
976
736
  } else if (foe.active.volatiles.Underground) {
977
- if (!move.extra.underground) return false;
737
+ if (!move.extra.underground) {
738
+ state.miss = true;
739
+ return;
740
+ }
741
+ }
742
+ if (move.effect == .Thunder and battle.field.weather == .Rain) {
743
+ state.miss = false;
744
+ return;
745
+ }
746
+ if (move.effect == .AlwaysHit) {
747
+ state.miss = false;
748
+ return;
978
749
  }
979
- if (move.effect == .Thunder and battle.field.weather == .Rain) return true;
980
- if (move.effect == .AlwaysHit) return true;
981
750
 
982
751
  var accuracy: u16 = move.accuracy;
983
752
  if (foe.active.boosts.evasion <= side.active.boosts.accuracy or
@@ -995,17 +764,21 @@ fn moveHit(
995
764
  }
996
765
 
997
766
  // The accuracy roll is skipped entirely if maxed
998
- if (accuracy == 255) return true;
999
- return !try Rolls.hit(battle, player, @intCast(accuracy), options);
767
+ state.miss = if (accuracy == 255)
768
+ false
769
+ else
770
+ !try Rolls.hit(battle, player, @intCast(accuracy), options);
1000
771
  }
1001
772
 
1002
773
  fn checkFaint(
1003
774
  battle: anytype,
1004
775
  player: Player,
776
+ state: *State,
1005
777
  options: anytype,
1006
778
  ) @TypeOf(options.log).Error!?Result {
1007
779
  _ = battle;
1008
780
  _ = player;
781
+ _ = state;
1009
782
  return null;
1010
783
  }
1011
784
 
@@ -1067,20 +840,20 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
1067
840
  if (volatiles.Nightmare) {
1068
841
  stored.hp -= @max(stored.stats.hp / 4, 1);
1069
842
  // ibid
1070
- try options.log.damage(ident, stored, Damage.Nightmare);
843
+ try options.log.damage(ident, stored, .Nightmare);
1071
844
  if (stored.hp == 0) return;
1072
845
  }
1073
846
 
1074
847
  if (volatiles.Curse) {
1075
848
  stored.hp -= @max(stored.stats.hp / 4, 1);
1076
849
  // ibid
1077
- try options.log.damage(ident, stored, Damage.Curse);
850
+ try options.log.damage(ident, stored, .Curse);
1078
851
  if (stored.hp == 0) return;
1079
852
  }
1080
853
  }
1081
854
 
1082
- fn betweenTurns(battle: anytype, mslot: u3, options: anytype) !void {
1083
- const players = std.enums.values(Player);
855
+ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
856
+ const players = comptime std.enums.values(Player);
1084
857
 
1085
858
  // TODO checkFaintThen(p1, p2);
1086
859
 
@@ -1114,7 +887,7 @@ fn betweenTurns(battle: anytype, mslot: u3, options: anytype) !void {
1114
887
  inline for (players) |player| {
1115
888
  var side = battle.side(player);
1116
889
  const active = side.active;
1117
- if (!(active.volatiles.underground or active.types.sandstormImmune())) {
890
+ if (!(active.volatiles.Underground or active.types.sandstormImmune())) {
1118
891
  side.stored().hp -|= @max(side.stored().stats.hp / 8, 1);
1119
892
  try options.log.damage(battle.active(player), side.stored(), .Sandstorm);
1120
893
  }
@@ -1129,18 +902,18 @@ fn betweenTurns(battle: anytype, mslot: u3, options: anytype) !void {
1129
902
  var side = battle.side(player);
1130
903
  var volatiles = &side.active.volatiles;
1131
904
 
1132
- if (volatiles.bind > 0 and !volatiles.Substitute) {
1133
- volatiles.bind -= 1;
1134
- if (volatiles.bind == 0) {
1135
- const reason = @intFromEnum(End.Bind) + volatiles.bind_reason - 1;
905
+ if (volatiles.bind.duration > 0 and !volatiles.Substitute) {
906
+ volatiles.bind.duration -= 1;
907
+ if (volatiles.bind.duration == 0) {
908
+ const reason = @intFromEnum(End.Bind) + volatiles.bind.reason - 1;
1136
909
  try options.log.end(battle.active(player), @enumFromInt(reason));
1137
910
  } else {
1138
- var reason = @intFromEnum(Activate.Bind) + volatiles.bind_reason - 1;
911
+ var reason = @intFromEnum(Activate.Bind) + volatiles.bind.reason - 1;
1139
912
  try options.log.activate(battle.active(player), @enumFromInt(reason));
1140
913
 
1141
- reason = @intFromEnum(Activate.Damage) + volatiles.bind_reason - 1;
914
+ reason = @intFromEnum(Damage.Bind) + volatiles.bind.reason - 1;
1142
915
  side.stored().hp -|= @max(side.stored().stats.hp / 16, 1);
1143
- try options.log.damage(battle.active(player), side.stored(), reason);
916
+ try options.log.damage(battle.active(player), side.stored(), @enumFromInt(reason));
1144
917
  }
1145
918
  }
1146
919
  }
@@ -1157,7 +930,7 @@ fn betweenTurns(battle: anytype, mslot: u3, options: anytype) !void {
1157
930
 
1158
931
  assert(volatiles.perish_song < 3);
1159
932
  const reason = @intFromEnum(Start.PerishSong0) + volatiles.perish_song;
1160
- try options.log.start(battle.active(player), reason);
933
+ try options.log.start(battle.active(player), @enumFromInt(reason));
1161
934
 
1162
935
  if (volatiles.perish_song == 0) {
1163
936
  volatiles.PerishSong = false;
@@ -1203,22 +976,21 @@ fn betweenTurns(battle: anytype, mslot: u3, options: anytype) !void {
1203
976
  // Side Conditions
1204
977
  if (showdown) {
1205
978
  inline for (players) |player| {
1206
- updateSideCondition(battle, player, "Reflect", "reflect");
979
+ try updateSideCondition(battle, player, "Reflect", "reflect", options);
1207
980
  }
1208
981
  inline for (players) |player| {
1209
- updateSideCondition(battle, player, "LightScreen", "light_screen");
982
+ try updateSideCondition(battle, player, "LightScreen", "light_screen", options);
1210
983
  }
1211
984
  inline for (players) |player| {
1212
- updateSideCondition(battle, player, "Safeguard", "safeguard");
985
+ try updateSideCondition(battle, player, "Safeguard", "safeguard", options);
1213
986
  }
1214
987
  } else {
1215
988
  inline for (players) |player| {
1216
- updateSideCondition(battle, player, "Safeguard", "safeguard");
989
+ try updateSideCondition(battle, player, "Safeguard", "safeguard", options);
1217
990
  }
1218
-
1219
991
  inline for (players) |player| {
1220
- updateSideCondition(battle, player, "LightScreen", "light_screen");
1221
- updateSideCondition(battle, player, "Reflect", "reflect");
992
+ try updateSideCondition(battle, player, "LightScreen", "light_screen", options);
993
+ try updateSideCondition(battle, player, "Reflect", "reflect", options);
1222
994
  }
1223
995
  }
1224
996
 
@@ -1291,7 +1063,7 @@ fn betweenTurns(battle: anytype, mslot: u3, options: anytype) !void {
1291
1063
  assert(volatiles.encore > 0);
1292
1064
  volatiles.encore -= 1;
1293
1065
 
1294
- if (volatiles.encore == 0 or side.active.moves(mslot).pp == 0) {
1066
+ if (volatiles.encore == 0 or side.active.move(mslot).pp == 0) {
1295
1067
  volatiles.Encore = false;
1296
1068
  try options.log.end(battle.active(player), .Encore);
1297
1069
  }
@@ -1313,7 +1085,7 @@ inline fn updateSideCondition(
1313
1085
 
1314
1086
  if (@field(side.conditions, value) == 0) {
1315
1087
  @field(side.conditions, key) = false;
1316
- try options.log.sideend(battle.active(player), @field(protocol.Side, key));
1088
+ try options.log.sideend(player, @field(protocol.Side, key));
1317
1089
  }
1318
1090
  }
1319
1091
  }
@@ -1333,133 +1105,150 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
1333
1105
  return Result.Default;
1334
1106
  }
1335
1107
 
1336
- fn buildRage(battle: anytype, player: Player, options: anytype) !void {
1108
+ fn buildRage(battle: anytype, player: Player, state: *State, options: anytype) !void {
1337
1109
  var foe = battle.foe(player);
1338
1110
  if (foe.active.volatiles.Rage) {
1339
1111
  if (showdown) {
1340
1112
  // TODO if showdown boost
1341
1113
  if (foe.active.boosts.atk < 6) {
1342
- try Effects.boost(battle, player, Move.get(.Rage), options);
1114
+ try Effects.boost(battle, player, state, options);
1343
1115
  }
1344
1116
  } else {
1345
1117
  foe.active.volatiles.rage +|= 1;
1346
- try options.log.activate(battle.active(player, .Rage));
1118
+ try options.log.activate(battle.active(player), .Rage);
1347
1119
  }
1348
1120
  }
1349
1121
  }
1350
1122
 
1351
- fn kingsRock(battle: anytype, player: Player, options: anytype) !void {
1123
+ fn kingsRock(battle: anytype, player: Player, _: *State, options: anytype) !void {
1352
1124
  var foe = battle.foe(player);
1353
1125
  const flinch = battle.side(player).stored().item == .KingsRock and
1354
1126
  !foe.active.volatiles.Substitute and
1355
- try options.chance.item(battle, player, options);
1127
+ try Rolls.item(battle, player, options);
1356
1128
  if (flinch) {
1357
- foe.active.volatiles.Recharge = false;
1129
+ foe.active.volatiles.Recharging = false;
1358
1130
  foe.active.volatiles.Flinch = true;
1359
1131
  }
1360
1132
  }
1361
1133
 
1362
1134
  pub const Effects = struct {
1363
- fn alwaysHit(battle: anytype, player: Player, options: anytype) !void {
1364
- _ = .{ battle, player, options }; // TODO
1135
+ fn alwaysHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
1136
+ _ = .{ battle, player, state, options }; // TODO
1365
1137
  }
1366
1138
 
1367
- fn attract(battle: anytype, player: Player, options: anytype) !void {
1139
+ fn attract(battle: anytype, player: Player, _: *State, options: anytype) !void {
1368
1140
  const side = battle.side(player);
1369
1141
  var foe = battle.foe(player);
1370
1142
 
1371
1143
  const gender: u8 = @intFromEnum(side.stored().dvs.gender);
1372
1144
  const foe_gender: u8 = @intFromEnum(foe.stored().dvs.gender);
1373
1145
  if (gender + foe_gender != 1) {
1374
- return try options.log.immune(battle.active(player.foe()), .None);
1146
+ try options.log.immune(battle.active(player.foe()), .None);
1147
+ return;
1375
1148
  } else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
1376
1149
  try options.log.lastmiss();
1377
- return try options.log.miss(battle.active(player));
1150
+ try options.log.miss(battle.active(player));
1151
+ return;
1378
1152
  }
1379
1153
 
1380
1154
  foe.active.volatiles.Attract = true;
1381
1155
  try options.log.start(battle.active(player.foe()), .Attract);
1382
1156
  }
1383
1157
 
1384
- fn batonPass(battle: anytype, player: Player, options: anytype) !void {
1385
- _ = .{ battle, player, options }; // TODO
1158
+ fn batonPass(battle: anytype, player: Player, state: *State, options: anytype) !void {
1159
+ _ = .{ battle, player, state, options }; // TODO
1386
1160
  }
1387
1161
 
1388
- fn beatUp(battle: anytype, player: Player, options: anytype) !void {
1389
- _ = .{ battle, player, options }; // TODO
1162
+ fn beatUp(battle: anytype, player: Player, state: *State, options: anytype) !void {
1163
+ _ = .{ battle, player, state, options }; // TODO
1390
1164
  }
1391
1165
 
1392
- fn bellyDrum(battle: anytype, player: Player, options: anytype) !void {
1393
- _ = .{ battle, player, options }; // TODO
1166
+ fn bellyDrum(battle: anytype, player: Player, state: *State, options: anytype) !void {
1167
+ _ = .{ battle, player, state, options }; // TODO
1394
1168
  }
1395
1169
 
1396
- fn bide(battle: anytype, player: Player, options: anytype) !void {
1397
- _ = .{ battle, player, options }; // TODO
1170
+ fn bide(battle: anytype, player: Player, state: *State, options: anytype) !void {
1171
+ _ = .{ battle, player, state, options }; // TODO
1398
1172
  }
1399
1173
 
1400
- fn binding(battle: anytype, player: Player, options: anytype) !void {
1401
- _ = .{ battle, player, options }; // TODO
1174
+ fn binding(battle: anytype, player: Player, state: *State, options: anytype) !void {
1175
+ _ = .{ battle, player, state, options }; // TODO
1402
1176
  }
1403
1177
 
1404
- fn burnChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1405
- _ = .{ battle, player, move, options }; // TODO
1178
+ fn burnChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
1179
+ _ = .{ battle, player, state, options }; // TODO
1406
1180
  }
1407
1181
 
1408
- fn confusion(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1409
- _ = .{ battle, player, move, options }; // TODO
1182
+ fn confusion(battle: anytype, player: Player, state: *State, options: anytype) !void {
1183
+ _ = .{ battle, player, state, options }; // TODO
1410
1184
  }
1411
1185
 
1412
- fn conversion(battle: anytype, player: Player, options: anytype) !void {
1186
+ fn conversion(battle: anytype, player: Player, _: *State, options: anytype) !void {
1413
1187
  var side = battle.side(player);
1414
1188
 
1415
1189
  var n: u3 = 0;
1416
1190
  for (side.active.moves) |m| {
1417
1191
  if (convertible(side.active, m.id)) n += 1;
1418
1192
  }
1419
- if (n == 0) return try options.log.fail(battle.active(player), .None);
1193
+ if (n == 0) {
1194
+ try options.log.fail(battle.active(player), .None);
1195
+ return;
1196
+ }
1420
1197
 
1421
1198
  side.active.types.type1 = try Rolls.conversion(battle, player, n, options);
1422
1199
  side.active.types.type2 = side.active.types.type1;
1423
1200
  }
1424
1201
 
1425
- fn conversion2(battle: anytype, player: Player, options: anytype) !void {
1202
+ fn conversion2(battle: anytype, player: Player, _: *State, options: anytype) !void {
1426
1203
  var side = battle.side(player);
1427
1204
 
1428
1205
  const last = battle.foe(player).lastMove(false);
1429
- if (last == .None) return try options.log.fail(battle.active(player), .None);
1206
+ if (last == .None) {
1207
+ try options.log.fail(battle.active(player), .None);
1208
+ return;
1209
+ }
1430
1210
  const move = Move.get(last);
1431
- if (move.type == .@"???") return try options.log.fail(battle.active(player), .None);
1211
+ if (move.type == .@"???") {
1212
+ try options.log.fail(battle.active(player), .None);
1213
+ return;
1214
+ }
1432
1215
 
1433
1216
  side.active.types.type1 = try Rolls.conversion2(battle, player, move.type, options);
1434
1217
  side.active.types.type2 = side.active.types.type1;
1435
1218
  }
1436
1219
 
1437
- fn counter(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1438
- _ = .{ battle, player, move, options }; // TODO
1220
+ fn counter(battle: anytype, player: Player, state: *State, options: anytype) !void {
1221
+ _ = .{ battle, player, state, options }; // TODO
1439
1222
  }
1440
1223
 
1441
- fn curse(battle: anytype, player: Player, options: anytype) !void {
1442
- _ = .{ battle, player, options }; // TODO
1224
+ fn curse(battle: anytype, player: Player, state: *State, options: anytype) !void {
1225
+ _ = .{ battle, player, state, options }; // TODO
1443
1226
  }
1444
1227
 
1445
- fn defenseCurl(battle: anytype, player: Player, options: anytype) !void {
1446
- _ = .{ battle, player, options }; // TODO
1228
+ fn defenseCurl(battle: anytype, player: Player, state: *State, options: anytype) !void {
1229
+ _ = .{ battle, player, state, options }; // TODO
1447
1230
  }
1448
1231
 
1449
- fn destinyBond(battle: anytype, player: Player, options: anytype) !void {
1232
+ fn destinyBond(battle: anytype, player: Player, _: *State, options: anytype) !void {
1450
1233
  battle.side(player).active.volatiles.DestinyBond = true;
1451
1234
  try options.log.singlemove(battle.active(player), Move.DestinyBond);
1452
1235
  }
1453
1236
 
1454
- fn disable(battle: anytype, player: Player, options: anytype) !void {
1237
+ fn disable(battle: anytype, player: Player, _: *State, options: anytype) !void {
1455
1238
  var foe = battle.foe(player);
1456
1239
  var volatiles = &foe.active.volatiles;
1457
1240
  const foe_ident = battle.active(player.foe());
1458
1241
 
1459
- if (volatiles.disabled.move != 0) return try options.log.fail(foe_ident, .None);
1242
+ if (volatiles.disable.move != 0) {
1243
+ try options.log.fail(foe_ident, .None);
1244
+ return;
1245
+ }
1460
1246
 
1461
1247
  const last = battle.foe(player).lastMove(false);
1462
- if (last == .None or last == .Struggle) return try options.log.fail(foe_ident, .None);
1248
+ if (last == .None or last == .Struggle) {
1249
+ try options.log.fail(foe_ident, .None);
1250
+ return;
1251
+ }
1463
1252
 
1464
1253
  var slot: u3 = 0;
1465
1254
  for (foe.active.moves) |m| {
@@ -1468,23 +1257,26 @@ pub const Effects = struct {
1468
1257
  if (m.id == last) break;
1469
1258
  }
1470
1259
  const move = foe.active.move(slot);
1471
- if (move.pp == 0) return try options.log.fail(foe_ident, .None);
1260
+ if (move.pp == 0) {
1261
+ try options.log.fail(foe_ident, .None);
1262
+ return;
1263
+ }
1472
1264
 
1473
- volatiles.disabled.move = slot;
1474
- volatiles.disabled.duration = Rolls.disableDuration(battle, player, options);
1265
+ volatiles.disable.move = slot;
1266
+ volatiles.disable.duration = Rolls.disableDuration(battle, player, options);
1475
1267
 
1476
1268
  try options.log.startEffect(foe_ident, .Disable, move.id);
1477
1269
  }
1478
1270
 
1479
- fn doubleHit(battle: anytype, player: Player, options: anytype) !void {
1480
- _ = .{ battle, player, options }; // TODO
1271
+ fn doubleHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
1272
+ _ = .{ battle, player, state, options }; // TODO
1481
1273
  }
1482
1274
 
1483
- fn drainHP(battle: anytype, player: Player, options: anytype) !void {
1484
- _ = .{ battle, player, options }; // TODO
1275
+ fn drainHP(battle: anytype, player: Player, state: *State, options: anytype) !void {
1276
+ _ = .{ battle, player, state, options }; // TODO
1485
1277
  }
1486
1278
 
1487
- fn encore(battle: anytype, player: Player, first: bool, options: anytype) !void {
1279
+ fn encore(battle: anytype, player: Player, state: *State, options: anytype) !void {
1488
1280
  var foe = battle.foe(player);
1489
1281
  var volatiles = &foe.active.volatiles;
1490
1282
  const foe_ident = battle.active(player.foe());
@@ -1493,7 +1285,10 @@ pub const Effects = struct {
1493
1285
  const failed = volatiles.Encore or
1494
1286
  (last == .None or last == .Struggle or
1495
1287
  last == .Encore or .last == .MirrorMove);
1496
- if (failed) return try options.log.fail(foe_ident, .None);
1288
+ if (failed) {
1289
+ try options.log.fail(foe_ident, .None);
1290
+ return;
1291
+ }
1497
1292
 
1498
1293
  var slot: u3 = 0;
1499
1294
  for (foe.active.moves) |m| {
@@ -1502,49 +1297,53 @@ pub const Effects = struct {
1502
1297
  if (m.id == last) break;
1503
1298
  }
1504
1299
  const move = foe.active.move(slot);
1505
- if (move.pp == 0) return try options.log.fail(foe_ident, .None);
1300
+ if (move.pp == 0) {
1301
+ try options.log.fail(foe_ident, .None);
1302
+ return;
1303
+ }
1506
1304
 
1507
1305
  volatiles.Encore = true;
1508
1306
  volatiles.encore = Rolls.encoreDuration(battle, player, options);
1509
1307
 
1510
1308
  // TODO ???
1511
- _ = first;
1309
+ _ = state;
1512
1310
 
1513
1311
  try options.log.startEffect(foe_ident, .Encore, move.id);
1514
1312
  }
1515
1313
 
1516
- fn explode(battle: anytype, player: Player, options: anytype) !void {
1517
- _ = .{ battle, player, options }; // TODO
1314
+ fn explode(battle: anytype, player: Player, state: *State, options: anytype) !void {
1315
+ _ = .{ battle, player, state, options }; // TODO
1518
1316
  }
1519
1317
 
1520
- fn falseSwipe(battle: anytype, player: Player, damage: u16) !u16 {
1318
+ fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
1521
1319
  const foe = battle.foe(player);
1522
1320
  assert(foe.stored().hp > 0);
1523
1321
  // FIXME crit?
1524
- return if (damage > foe.stored().hp) foe.stored().hp - 1 else damage;
1322
+ if (state.damage > foe.stored().hp) state.damage = foe.stored().hp - 1;
1525
1323
  }
1526
1324
 
1527
- fn fixedDamage(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1528
- _ = .{ battle, player, move, options }; // TODO
1325
+ fn fixedDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
1326
+ _ = .{ battle, player, state, options }; // TODO
1529
1327
  }
1530
1328
 
1531
- fn flameWheel(battle: anytype, player: Player, options: anytype) !void {
1532
- _ = .{ battle, player, options }; // TODO
1329
+ fn flameWheel(battle: anytype, player: Player, state: *State, options: anytype) !void {
1330
+ _ = .{ battle, player, state, options }; // TODO
1533
1331
  }
1534
1332
 
1535
- fn flinchChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1536
- _ = .{ battle, player, move, options }; // TODO
1333
+ fn flinchChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
1334
+ _ = .{ battle, player, state, options }; // TODO
1537
1335
  }
1538
1336
 
1539
- fn flyDig(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1540
- _ = .{ battle, player, move, options }; // TODO
1337
+ fn flyDig(battle: anytype, player: Player, state: *State, options: anytype) !void {
1338
+ _ = .{ battle, player, state, options }; // TODO
1541
1339
  }
1542
1340
 
1543
- fn focusEnergy(battle: anytype, player: Player, options: anytype) !void {
1341
+ fn focusEnergy(battle: anytype, player: Player, _: *State, options: anytype) !void {
1544
1342
  var side = battle.side(player);
1545
1343
 
1546
1344
  if (side.active.volatiles.FocusEnergy) {
1547
- return if (!showdown) try options.log.fail(battle.active(player), .None);
1345
+ if (!showdown) try options.log.fail(battle.active(player), .None);
1346
+ return;
1548
1347
  }
1549
1348
 
1550
1349
  side.active.volatiles.FocusEnergy = true;
@@ -1553,7 +1352,7 @@ pub const Effects = struct {
1553
1352
 
1554
1353
  var SLOTS: [5]u4 = .{ 0, 0, 0, 0, 0 };
1555
1354
 
1556
- fn forceSwitch(battle: anytype, player: Player, options: anytype) !void {
1355
+ fn forceSwitch(battle: anytype, player: Player, _: *State, options: anytype) !void {
1557
1356
  const foe = battle.foe(player);
1558
1357
 
1559
1358
  var n: u4 = 0;
@@ -1568,334 +1367,341 @@ pub const Effects = struct {
1568
1367
  }
1569
1368
  }
1570
1369
 
1571
- if (n == 0) try options.log.fail(battle.active(player), .None);
1370
+ if (n == 0) {
1371
+ try options.log.fail(battle.active(player), .None);
1372
+ return;
1373
+ }
1572
1374
 
1573
1375
  _ = try Rolls.forceSwitch(battle, player, SLOTS[0..i], n, options); // TODO
1574
1376
  }
1575
1377
 
1576
- fn foresight(battle: anytype, player: Player, options: anytype) !void {
1378
+ fn foresight(battle: anytype, player: Player, _: *State, options: anytype) !void {
1577
1379
  var foe = battle.foe(player);
1578
1380
 
1579
1381
  if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
1580
1382
  try options.log.lastmiss();
1581
- return try options.log.miss(battle.active(player));
1383
+ try options.log.miss(battle.active(player));
1384
+ return;
1582
1385
  } else if (foe.active.volatiles.Foresight) {
1583
- return try options.log.fail(battle.active(player), .None);
1386
+ try options.log.fail(battle.active(player), .None);
1387
+ return;
1584
1388
  }
1585
1389
 
1586
1390
  foe.active.volatiles.Foresight = true;
1587
1391
  try options.log.start(battle.active(player.foe()), .FocusEnergy);
1588
1392
  }
1589
1393
 
1590
- fn freezeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1591
- _ = .{ battle, player, move, options }; // TODO
1394
+ fn freezeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
1395
+ _ = .{ battle, player, state, options }; // TODO
1592
1396
  }
1593
1397
 
1594
- fn furyCutter(battle: anytype, player: Player, options: anytype) !void {
1595
- _ = .{ battle, player, options }; // TODO
1398
+ fn furyCutter(battle: anytype, player: Player, state: *State, options: anytype) !void {
1399
+ _ = .{ battle, player, state, options }; // TODO
1596
1400
  }
1597
1401
 
1598
- fn futureSight(battle: anytype, player: Player, options: anytype) !void {
1599
- _ = .{ battle, player, options }; // TODO
1402
+ fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
1403
+ _ = .{ battle, player, state, options }; // TODO
1600
1404
  }
1601
1405
 
1602
- fn happiness(battle: anytype, player: Player, move: Move.Data) !u8 {
1406
+ fn happiness(battle: anytype, player: Player, state: *State, _: anytype) !void {
1603
1407
  const val = battle.side(player).stored().happiness;
1604
- return ((if (move.effect == .Frustration) 255 - val else val) * 10) / 25;
1408
+ const frustration = Move.get(state.move).effect == .Frustration;
1409
+ state.bp = ((if (frustration) 255 - val else val) * 10) / 25;
1605
1410
  }
1606
1411
 
1607
- fn haze(battle: anytype, player: Player, options: anytype) !void {
1608
- _ = .{ battle, player, options }; // TODO
1412
+ fn haze(battle: anytype, player: Player, state: *State, options: anytype) !void {
1413
+ _ = .{ battle, player, state, options }; // TODO
1609
1414
  }
1610
1415
 
1611
- fn heal(battle: anytype, player: Player, options: anytype) !void {
1612
- _ = .{ battle, player, options }; // TODO
1416
+ fn heal(battle: anytype, player: Player, state: *State, options: anytype) !void {
1417
+ _ = .{ battle, player, state, options }; // TODO
1613
1418
  }
1614
1419
 
1615
- fn healBell(battle: anytype, player: Player, options: anytype) !void {
1616
- _ = .{ battle, player, options }; // TODO
1420
+ fn healBell(battle: anytype, player: Player, state: *State, options: anytype) !void {
1421
+ _ = .{ battle, player, state, options }; // TODO
1617
1422
  }
1618
1423
 
1619
- fn hiddenPower(battle: anytype, player: Player, options: anytype) !void {
1620
- _ = .{ battle, player, options }; // TODO
1424
+ fn hiddenPower(battle: anytype, player: Player, state: *State, options: anytype) !void {
1425
+ _ = .{ battle, player, state, options }; // TODO
1621
1426
  }
1622
1427
 
1623
- fn hyperBeam(battle: anytype, player: Player, options: anytype) !void {
1624
- _ = .{ battle, player, options }; // TODO
1428
+ fn hyperBeam(battle: anytype, player: Player, state: *State, options: anytype) !void {
1429
+ _ = .{ battle, player, state, options }; // TODO
1625
1430
  }
1626
1431
 
1627
- fn jumpKick(battle: anytype, player: Player, options: anytype) !void {
1628
- _ = .{ battle, player, options }; // TODO
1432
+ fn jumpKick(battle: anytype, player: Player, state: *State, options: anytype) !void {
1433
+ _ = .{ battle, player, state, options }; // TODO
1629
1434
  }
1630
1435
 
1631
- fn leechSeed(battle: anytype, player: Player, options: anytype) !void {
1632
- _ = .{ battle, player, options }; // TODO
1436
+ fn leechSeed(battle: anytype, player: Player, state: *State, options: anytype) !void {
1437
+ _ = .{ battle, player, state, options }; // TODO
1633
1438
  }
1634
1439
 
1635
- fn levelDamage(battle: anytype, player: Player, options: anytype) !void {
1636
- _ = .{ battle, player, options }; // TODO
1440
+ fn levelDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
1441
+ _ = .{ battle, player, state, options }; // TODO
1637
1442
  }
1638
1443
 
1639
- fn lockOn(battle: anytype, player: Player, options: anytype) !void {
1640
- _ = .{ battle, player, options }; // TODO
1444
+ fn lockOn(battle: anytype, player: Player, state: *State, options: anytype) !void {
1445
+ _ = .{ battle, player, state, options }; // TODO
1641
1446
  }
1642
1447
 
1643
1448
  const MAGNITUDE_POWER = [_]u8{ 10, 30, 50, 70, 90, 110, 150 };
1644
1449
 
1645
- fn magnitude(battle: anytype, player: Player, options: anytype) !void {
1646
- _ = .{ battle, player, options }; // TODO
1450
+ fn magnitude(battle: anytype, player: Player, state: *State, options: anytype) !void {
1451
+ _ = .{ battle, player, state, options }; // TODO
1647
1452
  }
1648
1453
 
1649
- fn meanLook(battle: anytype, player: Player, options: anytype) !void {
1650
- _ = .{ battle, player, options }; // TODO
1454
+ fn meanLook(battle: anytype, player: Player, state: *State, options: anytype) !void {
1455
+ _ = .{ battle, player, state, options }; // TODO
1651
1456
  }
1652
1457
 
1653
- fn metronome(battle: anytype, player: Player, options: anytype) !void {
1654
- _ = .{ battle, player, options }; // TODO
1458
+ fn metronome(battle: anytype, player: Player, state: *State, options: anytype) !void {
1459
+ _ = .{ battle, player, state, options }; // TODO
1655
1460
  }
1656
1461
 
1657
- fn mimic(battle: anytype, player: Player, options: anytype) !void {
1658
- _ = .{ battle, player, options }; // TODO
1462
+ fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
1463
+ _ = .{ battle, player, state, options }; // TODO
1659
1464
  }
1660
1465
 
1661
- fn mirrorCoat(battle: anytype, player: Player, options: anytype) !void {
1662
- _ = .{ battle, player, options }; // TODO
1466
+ fn mirrorCoat(battle: anytype, player: Player, state: *State, options: anytype) !void {
1467
+ _ = .{ battle, player, state, options }; // TODO
1663
1468
  }
1664
1469
 
1665
- fn mirrorMove(battle: anytype, player: Player, options: anytype) !void {
1666
- _ = .{ battle, player, options }; // TODO
1470
+ fn mirrorMove(battle: anytype, player: Player, state: *State, options: anytype) !void {
1471
+ _ = .{ battle, player, state, options }; // TODO
1667
1472
  }
1668
1473
 
1669
- fn mist(battle: anytype, player: Player, options: anytype) !void {
1670
- _ = .{ battle, player, options }; // TODO
1474
+ fn mist(battle: anytype, player: Player, state: *State, options: anytype) !void {
1475
+ _ = .{ battle, player, state, options }; // TODO
1671
1476
  }
1672
1477
 
1673
- fn weatherHeal(battle: anytype, player: Player, options: anytype) !void {
1674
- _ = .{ battle, player, options }; // TODO
1478
+ fn weatherHeal(battle: anytype, player: Player, state: *State, options: anytype) !void {
1479
+ _ = .{ battle, player, state, options }; // TODO
1675
1480
  }
1676
1481
 
1677
- fn multiHit(battle: anytype, player: Player, options: anytype) !void {
1678
- _ = .{ battle, player, options }; // TODO
1482
+ fn multiHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
1483
+ _ = .{ battle, player, state, options }; // TODO
1679
1484
  }
1680
1485
 
1681
- fn nightmare(battle: anytype, player: Player, options: anytype) !void {
1682
- _ = .{ battle, player, options }; // TODO
1486
+ fn nightmare(battle: anytype, player: Player, state: *State, options: anytype) !void {
1487
+ _ = .{ battle, player, state, options }; // TODO
1683
1488
  }
1684
1489
 
1685
- fn ohko(battle: anytype, player: Player, options: anytype) !void {
1686
- _ = .{ battle, player, options }; // TODO
1490
+ fn ohko(battle: anytype, player: Player, state: *State, options: anytype) !void {
1491
+ _ = .{ battle, player, state, options }; // TODO
1687
1492
  }
1688
1493
 
1689
- fn painSplit(battle: anytype, player: Player, options: anytype) !void {
1690
- _ = .{ battle, player, options }; // TODO
1494
+ fn painSplit(battle: anytype, player: Player, state: *State, options: anytype) !void {
1495
+ _ = .{ battle, player, state, options }; // TODO
1691
1496
  }
1692
1497
 
1693
- fn paralyze(battle: anytype, player: Player, options: anytype) !void {
1694
- _ = .{ battle, player, options }; // TODO
1498
+ fn paralyze(battle: anytype, player: Player, state: *State, options: anytype) !void {
1499
+ _ = .{ battle, player, state, options }; // TODO
1695
1500
  }
1696
1501
 
1697
- fn paralyzeChance(battle: anytype, player: Player, options: anytype) !void {
1502
+ fn paralyzeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
1698
1503
  // TODO: Thunder does acc/checkHit/effectChance before stab + damage variation
1699
- _ = .{ battle, player, options }; // TODO
1504
+ _ = .{ battle, player, state, options }; // TODO
1700
1505
  }
1701
1506
 
1702
- fn payDay(battle: anytype, player: Player, options: anytype) !void {
1703
- _ = .{ battle, player, options }; // TODO
1507
+ fn payDay(battle: anytype, player: Player, state: *State, options: anytype) !void {
1508
+ _ = .{ battle, player, state, options }; // TODO
1704
1509
  }
1705
1510
 
1706
- fn perishSong(battle: anytype, player: Player, options: anytype) !void {
1707
- _ = .{ battle, player, options }; // TODO
1511
+ fn perishSong(battle: anytype, player: Player, state: *State, options: anytype) !void {
1512
+ _ = .{ battle, player, state, options }; // TODO
1708
1513
  }
1709
1514
 
1710
- fn poison(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1711
- _ = .{ battle, player, move, options }; // TODO
1515
+ fn poison(battle: anytype, player: Player, state: *State, options: anytype) !void {
1516
+ _ = .{ battle, player, state, options }; // TODO
1712
1517
  }
1713
1518
 
1714
- fn present(battle: anytype, player: Player, options: anytype) !void {
1715
- _ = .{ battle, player, options }; // TODO
1519
+ fn present(battle: anytype, player: Player, state: *State, options: anytype) !void {
1520
+ _ = .{ battle, player, state, options }; // TODO
1716
1521
  }
1717
1522
 
1718
- fn protect(
1719
- battle: anytype,
1720
- player: Player,
1721
- move: Move,
1722
- first: bool,
1723
- options: anytype,
1724
- ) !void {
1523
+ fn protect(battle: anytype, player: Player, state: *State, options: anytype) !void {
1725
1524
  var side = battle.side(player);
1726
1525
  var volatiles = &side.active.volatiles;
1727
1526
 
1728
- const failed = !first or volatiles.Substitute or
1527
+ const failed = !state.first or volatiles.Substitute or
1729
1528
  !try Rolls.protect(battle, player, volatiles.protect, options);
1730
- if (failed) return try options.log.fail(battle.active(player), .None);
1529
+ if (failed) {
1530
+ try options.log.fail(battle.active(player), .None);
1531
+ return;
1532
+ }
1731
1533
 
1732
1534
  volatiles.protect = @max(8, volatiles.protect + 1);
1733
- if (move == .Endure) {
1535
+ if (state.move == .Endure) {
1734
1536
  volatiles.Endure = true;
1735
1537
  } else {
1736
1538
  volatiles.Protect = true;
1737
1539
  }
1738
- try options.log.singleturn(battle.active(player), move);
1540
+ try options.log.singleturn(battle.active(player), state.move);
1739
1541
  }
1740
1542
 
1741
- fn psychUp(battle: anytype, player: Player, options: anytype) !void {
1742
- _ = .{ battle, player, options }; // TODO
1543
+ fn psychUp(battle: anytype, player: Player, state: *State, options: anytype) !void {
1544
+ _ = .{ battle, player, state, options }; // TODO
1743
1545
  }
1744
1546
 
1745
- fn pursuit(battle: anytype, player: Player, options: anytype) !void {
1746
- _ = .{ battle, player, options }; // TODO
1547
+ fn pursuit(battle: anytype, player: Player, state: *State, options: anytype) !void {
1548
+ _ = .{ battle, player, state, options }; // TODO
1747
1549
  }
1748
1550
 
1749
- fn rage(battle: anytype, player: Player, options: anytype) !void {
1750
- _ = .{ battle, player, options }; // TODO
1551
+ fn rage(battle: anytype, player: Player, state: *State, options: anytype) !void {
1552
+ _ = .{ battle, player, state, options }; // TODO
1751
1553
  }
1752
1554
 
1753
- fn rainDance(battle: anytype, player: Player, options: anytype) !void {
1754
- _ = .{ battle, player, options }; // TODO
1555
+ fn rainDance(battle: anytype, player: Player, state: *State, options: anytype) !void {
1556
+ _ = .{ battle, player, state, options }; // TODO
1755
1557
  }
1756
1558
 
1757
- fn rapidSpin(battle: anytype, player: Player, options: anytype) !void {
1758
- _ = .{ battle, player, options }; // TODO
1559
+ fn rapidSpin(battle: anytype, player: Player, state: *State, options: anytype) !void {
1560
+ _ = .{ battle, player, state, options }; // TODO
1759
1561
  }
1760
1562
 
1761
- fn razorWind(battle: anytype, player: Player, options: anytype) !void {
1762
- _ = .{ battle, player, options }; // TODO
1563
+ fn razorWind(battle: anytype, player: Player, state: *State, options: anytype) !void {
1564
+ _ = .{ battle, player, state, options }; // TODO
1763
1565
  }
1764
1566
 
1765
- fn recoil(battle: anytype, player: Player, options: anytype) !void {
1766
- _ = .{ battle, player, options }; // TODO
1567
+ fn recoil(battle: anytype, player: Player, state: *State, options: anytype) !void {
1568
+ _ = .{ battle, player, state, options }; // TODO
1767
1569
  }
1768
1570
 
1769
- fn reversal(battle: anytype, player: Player, options: anytype) !void {
1770
- _ = .{ battle, player, options }; // TODO
1571
+ fn reversal(battle: anytype, player: Player, state: *State, options: anytype) !void {
1572
+ _ = .{ battle, player, state, options }; // TODO
1771
1573
  }
1772
1574
 
1773
- fn rollout(battle: anytype, player: Player, options: anytype) !void {
1774
- _ = .{ battle, player, options }; // TODO
1575
+ fn rollout(battle: anytype, player: Player, state: *State, options: anytype) !void {
1576
+ _ = .{ battle, player, state, options }; // TODO
1775
1577
  }
1776
1578
 
1777
- fn sacredFire(battle: anytype, player: Player, options: anytype) !void {
1778
- _ = .{ battle, player, options }; // TODO
1579
+ fn sacredFire(battle: anytype, player: Player, state: *State, options: anytype) !void {
1580
+ _ = .{ battle, player, state, options }; // TODO
1779
1581
  }
1780
1582
 
1781
- fn safeguard(battle: anytype, player: Player, options: anytype) !void {
1782
- _ = .{ battle, player, options }; // TODO
1583
+ fn safeguard(battle: anytype, player: Player, state: *State, options: anytype) !void {
1584
+ _ = .{ battle, player, state, options }; // TODO
1783
1585
  }
1784
1586
 
1785
- fn sandstorm(battle: anytype, player: Player, options: anytype) !void {
1786
- _ = .{ battle, player, options }; // TODO
1587
+ fn sandstorm(battle: anytype, player: Player, state: *State, options: anytype) !void {
1588
+ _ = .{ battle, player, state, options }; // TODO
1787
1589
  }
1788
1590
 
1789
- fn screens(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1790
- _ = .{ battle, player, move, options }; // TODO
1591
+ fn screens(battle: anytype, player: Player, state: *State, options: anytype) !void {
1592
+ _ = .{ battle, player, state, options }; // TODO
1791
1593
  }
1792
1594
 
1793
- fn sketch(battle: anytype, player: Player, options: anytype) !void {
1794
- _ = .{ battle, player, options }; // TODO
1595
+ fn sketch(battle: anytype, player: Player, state: *State, options: anytype) !void {
1596
+ _ = .{ battle, player, state, options }; // TODO
1795
1597
  }
1796
1598
 
1797
- fn skullBash(battle: anytype, player: Player, options: anytype) !void {
1798
- _ = .{ battle, player, options }; // TODO
1599
+ fn skullBash(battle: anytype, player: Player, state: *State, options: anytype) !void {
1600
+ _ = .{ battle, player, state, options }; // TODO
1799
1601
  }
1800
1602
 
1801
- fn skyAttack(battle: anytype, player: Player, options: anytype) !void {
1802
- _ = .{ battle, player, options }; // TODO
1603
+ fn skyAttack(battle: anytype, player: Player, state: *State, options: anytype) !void {
1604
+ _ = .{ battle, player, state, options }; // TODO
1803
1605
  }
1804
1606
 
1805
- fn sleep(battle: anytype, player: Player, options: anytype) !void {
1806
- _ = .{ battle, player, options }; // TODO
1607
+ fn sleep(battle: anytype, player: Player, state: *State, options: anytype) !void {
1608
+ _ = .{ battle, player, state, options }; // TODO
1807
1609
  }
1808
1610
 
1809
- fn sleepTalk(battle: anytype, player: Player, mslot: u3, options: anytype) !void {
1611
+ fn sleepTalk(battle: anytype, player: Player, state: *State, options: anytype) !void {
1810
1612
  const side = battle.side(player);
1811
1613
 
1812
1614
  if (!Status.is(side.stored().status, .SLP)) {
1813
- return if (!showdown) try options.log.fail(battle.active(player), .None);
1615
+ if (!showdown) try options.log.fail(battle.active(player), .None);
1616
+ return;
1814
1617
  }
1815
1618
 
1816
1619
  var n: u3 = 0;
1817
1620
  for (side.active.moves, 0..) |m, i| {
1818
- if (mslot - 1 != i and Move.get(m.id).extra.sleep_talk) n += 1;
1621
+ if (state.mslot - 1 != i and Move.get(m.id).extra.sleep_talk) n += 1;
1622
+ }
1623
+ if (n == 0) {
1624
+ try options.log.fail(battle.active(player), .None);
1625
+ return;
1819
1626
  }
1820
- if (n == 0) return try options.log.fail(battle.active(player), .None);
1821
1627
 
1822
- _ = try Rolls.sleepTalk(battle, player, n, mslot, options); // TODO
1628
+ _ = try Rolls.sleepTalk(battle, player, n, state.mslot, options); // TODO
1823
1629
  }
1824
1630
 
1825
- fn snore(battle: anytype, player: Player, options: anytype) !void {
1826
- _ = .{ battle, player, options }; // TODO
1631
+ fn snore(battle: anytype, player: Player, state: *State, options: anytype) !void {
1632
+ _ = .{ battle, player, state, options }; // TODO
1827
1633
  }
1828
1634
 
1829
- fn solarBeam(battle: anytype, player: Player, options: anytype) !void {
1830
- _ = .{ battle, player, options }; // TODO
1635
+ fn solarBeam(battle: anytype, player: Player, state: *State, options: anytype) !void {
1636
+ _ = .{ battle, player, state, options }; // TODO
1831
1637
  }
1832
1638
 
1833
- fn spikes(battle: anytype, player: Player, options: anytype) !void {
1834
- _ = .{ battle, player, options }; // TODO
1639
+ fn spikes(battle: anytype, player: Player, state: *State, options: anytype) !void {
1640
+ _ = .{ battle, player, state, options }; // TODO
1835
1641
  }
1836
1642
 
1837
- fn spite(battle: anytype, player: Player, options: anytype) !void {
1838
- _ = .{ battle, player, options }; // TODO
1643
+ fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
1644
+ _ = .{ battle, player, state, options }; // TODO
1839
1645
  }
1840
1646
 
1841
- fn splash(battle: anytype, player: Player, options: anytype) !void {
1842
- _ = .{ battle, player, options }; // TODO
1647
+ fn splash(battle: anytype, player: Player, state: *State, options: anytype) !void {
1648
+ _ = .{ battle, player, state, options }; // TODO
1843
1649
  }
1844
1650
 
1845
- fn stomp(battle: anytype, player: Player, options: anytype) !void {
1846
- _ = .{ battle, player, options }; // TODO
1651
+ fn stomp(battle: anytype, player: Player, state: *State, options: anytype) !void {
1652
+ _ = .{ battle, player, state, options }; // TODO
1847
1653
  }
1848
1654
 
1849
- fn substitute(battle: anytype, player: Player, options: anytype) !void {
1850
- _ = .{ battle, player, options }; // TODO
1655
+ fn substitute(battle: anytype, player: Player, state: *State, options: anytype) !void {
1656
+ _ = .{ battle, player, state, options }; // TODO
1851
1657
  }
1852
1658
 
1853
- fn sunnyDay(battle: anytype, player: Player, options: anytype) !void {
1854
- _ = .{ battle, player, options }; // TODO
1659
+ fn sunnyDay(battle: anytype, player: Player, state: *State, options: anytype) !void {
1660
+ _ = .{ battle, player, state, options }; // TODO
1855
1661
  }
1856
1662
 
1857
- fn swagger(battle: anytype, player: Player, options: anytype) !void {
1858
- _ = .{ battle, player, options }; // TODO
1663
+ fn swagger(battle: anytype, player: Player, state: *State, options: anytype) !void {
1664
+ _ = .{ battle, player, state, options }; // TODO
1859
1665
  }
1860
1666
 
1861
- fn synthesis(battle: anytype, player: Player, options: anytype) !void {
1862
- _ = .{ battle, player, options }; // TODO
1667
+ fn synthesis(battle: anytype, player: Player, state: *State, options: anytype) !void {
1668
+ _ = .{ battle, player, state, options }; // TODO
1863
1669
  }
1864
1670
 
1865
- fn teleport(battle: anytype, player: Player, options: anytype) !void {
1866
- _ = .{ battle, player, options }; // TODO
1671
+ fn teleport(battle: anytype, player: Player, state: *State, options: anytype) !void {
1672
+ _ = .{ battle, player, state, options }; // TODO
1867
1673
  }
1868
1674
 
1869
- fn thief(battle: anytype, player: Player, options: anytype) !void {
1870
- _ = .{ battle, player, options }; // TODO
1675
+ fn thief(battle: anytype, player: Player, state: *State, options: anytype) !void {
1676
+ _ = .{ battle, player, state, options }; // TODO
1871
1677
  }
1872
1678
 
1873
- fn thrashing(battle: anytype, player: Player, options: anytype) !void {
1874
- _ = .{ battle, player, options }; // TODO
1679
+ fn thrashing(battle: anytype, player: Player, state: *State, options: anytype) !void {
1680
+ _ = .{ battle, player, state, options }; // TODO
1875
1681
  }
1876
1682
 
1877
- fn toxic(battle: anytype, player: Player, options: anytype) !void {
1878
- _ = .{ battle, player, options }; // TODO
1683
+ fn toxic(battle: anytype, player: Player, state: *State, options: anytype) !void {
1684
+ _ = .{ battle, player, state, options }; // TODO
1879
1685
  }
1880
1686
 
1881
- fn transform(battle: anytype, player: Player, options: anytype) !void {
1882
- _ = .{ battle, player, options }; // TODO
1687
+ fn transform(battle: anytype, player: Player, state: *State, options: anytype) !void {
1688
+ _ = .{ battle, player, state, options }; // TODO
1883
1689
  }
1884
1690
 
1885
- fn tripleKick(battle: anytype, player: Player, options: anytype) !void {
1886
- _ = .{ battle, player, options }; // TODO
1691
+ fn tripleKick(battle: anytype, player: Player, state: *State, options: anytype) !void {
1692
+ _ = .{ battle, player, state, options }; // TODO
1887
1693
  }
1888
1694
 
1889
- fn twister(battle: anytype, player: Player, options: anytype) !void {
1890
- _ = .{ battle, player, options }; // TODO
1695
+ fn twister(battle: anytype, player: Player, state: *State, options: anytype) !void {
1696
+ _ = .{ battle, player, state, options }; // TODO
1891
1697
  }
1892
1698
 
1893
- fn boost(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1894
- _ = .{ battle, player, move, options }; // TODO
1699
+ fn boost(battle: anytype, player: Player, state: *State, options: anytype) !void {
1700
+ _ = .{ battle, player, state, options }; // TODO
1895
1701
  }
1896
1702
 
1897
- fn unboost(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1898
- _ = .{ battle, player, move, options }; // TODO
1703
+ fn unboost(battle: anytype, player: Player, state: *State, options: anytype) !void {
1704
+ _ = .{ battle, player, state, options }; // TODO
1899
1705
  }
1900
1706
  };
1901
1707
 
@@ -2547,6 +2353,28 @@ test "Roll probabilities" {
2547
2353
  try expectEqualSlices(u8, &.{ 14, 26, 50, 77, 51, 25, 13 }, &magnitude);
2548
2354
  }
2549
2355
 
2356
+ test "Reasons" {
2357
+ const moves = [_]Move{ .Bind, .Wrap, .FireSpin, .Clamp, .Whirlpool };
2358
+ for (moves) |move| {
2359
+ const reason = Move.get(move).extra.protocol - 1;
2360
+
2361
+ const activate: Activate = @enumFromInt(@intFromEnum(Activate.Bind) + reason);
2362
+ try expectEqualStrings(@tagName(move), @tagName(activate));
2363
+
2364
+ const damage: Damage = @enumFromInt(@intFromEnum(Damage.Bind) + reason);
2365
+ try expectEqualStrings(@tagName(move), @tagName(damage));
2366
+
2367
+ const end: End = @enumFromInt(@intFromEnum(End.Bind) + reason);
2368
+ try expectEqualStrings(@tagName(move), @tagName(end));
2369
+ }
2370
+
2371
+ const perish_song = [_]Start{ .PerishSong0, .PerishSong1, .PerishSong2, .PerishSong3 };
2372
+ for (perish_song, 0..) |ps, i| {
2373
+ const reason: Start = @enumFromInt(@intFromEnum(Start.PerishSong0) + i);
2374
+ try expectEqualStrings(@tagName(ps), @tagName(reason));
2375
+ }
2376
+ }
2377
+
2550
2378
  pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Choice) u8 {
2551
2379
  var n: u8 = 0;
2552
2380
  switch (request) {
@@ -2625,7 +2453,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
2625
2453
  if (m.id == if (active.volatiles.Bide) .Bide else side.last_selected_move) {
2626
2454
  // Pokémon Showdown displays Struggle if limited to Bide but unable to pick
2627
2455
  const struggle =
2628
- m.id == .Bide and (m.pp == 0 or active.volatiles.disabled_move == slot);
2456
+ m.id == .Bide and (m.pp == 0 or active.volatiles.disable.move == slot);
2629
2457
  const s = if (struggle) 0 else slot;
2630
2458
  out[n] = .{ .type = .Move, .data = s };
2631
2459
  n += 1;
@@ -2640,7 +2468,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
2640
2468
  const m = active.moves[slot - 1];
2641
2469
  if (m.id == .None) break;
2642
2470
  if (m.pp == 0) continue;
2643
- if (active.volatiles.disabled_move == slot) continue;
2471
+ if (active.volatiles.disable.move == slot) continue;
2644
2472
  out[n] = .{ .type = .Move, .data = slot };
2645
2473
  n += 1;
2646
2474
  }