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

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.
@@ -89,8 +89,6 @@ const CRITICAL_HITS = &[_]u8{
89
89
 
90
90
  const MAX_STAT_VALUE = 999;
91
91
 
92
- const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
93
-
94
92
  pub fn update(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Result {
95
93
  assert(c1.type != .Pass or c2.type != .Pass or battle.turn == 0);
96
94
  if (battle.turn == 0) return start(battle, options);
@@ -317,13 +315,13 @@ fn doTurn(
317
315
  const foe_volatiles = &battle.foe(player).active.volatiles;
318
316
 
319
317
  volatiles.DestinyBond = false;
320
- if (try executeMove(battle, player, player_choice, options)) |r| return r;
318
+ if (try executeMove(battle, player, player_choice, true, options)) |r| return r;
321
319
  foe_volatiles.Protect = false;
322
320
  foe_volatiles.Endure = false;
323
321
  foe_volatiles.DestinyBond = false;
324
322
 
325
323
  // foe_volatiles.DestinyBond = false;
326
- if (try executeMove(battle, foe_player, foe_choice, options)) |r| return r;
324
+ if (try executeMove(battle, foe_player, foe_choice, false, options)) |r| return r;
327
325
  volatiles.Protect = false;
328
326
  volatiles.Endure = false;
329
327
  volatiles.DestinyBond = false;
@@ -331,20 +329,26 @@ fn doTurn(
331
329
  return null;
332
330
  }
333
331
 
334
- fn executeMove(battle: anytype, player: Player, choice: Choice, options: anytype) !?Result {
332
+ fn executeMove(
333
+ battle: anytype,
334
+ player: Player,
335
+ choice: Choice,
336
+ first: bool,
337
+ options: anytype,
338
+ ) !?Result {
335
339
  if (choice.type == .Switch) {
336
340
  try switchIn(battle, player, choice.data, false, options);
337
341
  return null;
338
342
  }
339
343
 
340
344
  assert(choice.type == .Move);
341
- var mslot: u4 = @intCast(choice.data);
345
+ var mslot: u3 = @intCast(choice.data);
342
346
 
343
347
  // const move = Move.get(side.last_selected_move);
344
348
 
345
349
  // CheckTurn
346
350
 
347
- return doMove(battle, player, options, mslot);
351
+ return doMove(battle, player, mslot, first, options);
348
352
  }
349
353
 
350
354
  fn beforeMove(battle: anytype, player: Player, options: anytype) !bool {
@@ -498,7 +502,7 @@ fn canMove(battle: anytype, player: Player, options: anytype) !bool {
498
502
  return false;
499
503
  }
500
504
 
501
- fn decrementPP(side: *Side, mslot: u4) void {
505
+ fn decrementPP(side: *Side, mslot: u3) void {
502
506
  var active = &side.active;
503
507
  const volatiles = &active.volatiles;
504
508
 
@@ -516,16 +520,319 @@ fn decrementPP(side: *Side, mslot: u4) void {
516
520
  assert(active.move(mslot).pp == side.stored().move(mslot).pp);
517
521
  }
518
522
 
519
- fn doMove(battle: anytype, player: Player, options: anytype, mslot: u4) !?Result {
520
- _ = options;
523
+ fn doMove(battle: anytype, player: Player, mslot: u3, first: bool, options: anytype) !?Result {
521
524
  var side = battle.side(player);
522
- const move = Move.get(.Teleport); // side.last_selected_move
525
+ var move = Move.get(.Teleport); // side.last_selected_move
526
+ _ = .{ side, mslot };
527
+
523
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
+ },
524
797
  .Teleport => {
525
- // side.useMove();
526
- decrementPP(side, mslot);
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
527
835
  },
528
- else => @panic("TODO"),
529
836
  }
530
837
  return null;
531
838
  }
@@ -772,7 +1079,7 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
772
1079
  }
773
1080
  }
774
1081
 
775
- fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
1082
+ fn betweenTurns(battle: anytype, mslot: u3, options: anytype) !void {
776
1083
  const players = std.enums.values(Player);
777
1084
 
778
1085
  // TODO checkFaintThen(p1, p2);
@@ -1027,24 +1334,543 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
1027
1334
  }
1028
1335
 
1029
1336
  pub const Effects = struct {
1030
- fn protect(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1337
+ fn alwaysHit(battle: anytype, player: Player, options: anytype) !void {
1338
+ _ = .{ battle, player, options }; // TODO
1339
+ }
1340
+
1341
+ fn attract(battle: anytype, player: Player, options: anytype) !void {
1342
+ const side = battle.side(player);
1343
+ var foe = battle.foe(player);
1344
+
1345
+ const gender: u8 = @intFromEnum(side.stored().dvs.gender);
1346
+ const foe_gender: u8 = @intFromEnum(foe.stored().dvs.gender);
1347
+ if (gender + foe_gender != 1) {
1348
+ return try options.log.immune(battle.active(player.foe()), .None);
1349
+ } else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
1350
+ try options.log.lastmiss();
1351
+ return try options.log.miss(battle.active(player));
1352
+ }
1353
+
1354
+ foe.active.volatiles.Attract = true;
1355
+ try options.log.start(battle.active(player.foe()), .Attract);
1356
+ }
1357
+
1358
+ fn batonPass(battle: anytype, player: Player, options: anytype) !void {
1359
+ _ = .{ battle, player, options }; // TODO
1360
+ }
1361
+
1362
+ fn beatUp(battle: anytype, player: Player, options: anytype) !void {
1363
+ _ = .{ battle, player, options }; // TODO
1364
+ }
1365
+
1366
+ fn bellyDrum(battle: anytype, player: Player, options: anytype) !void {
1367
+ _ = .{ battle, player, options }; // TODO
1368
+ }
1369
+
1370
+ fn bide(battle: anytype, player: Player, options: anytype) !void {
1371
+ _ = .{ battle, player, options }; // TODO
1372
+ }
1373
+
1374
+ fn binding(battle: anytype, player: Player, options: anytype) !void {
1375
+ _ = .{ battle, player, options }; // TODO
1376
+ }
1377
+
1378
+ fn burnChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1379
+ _ = .{ battle, player, move, options }; // TODO
1380
+ }
1381
+
1382
+ fn confusion(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1383
+ _ = .{ battle, player, move, options }; // TODO
1384
+ }
1385
+
1386
+ fn conversion(battle: anytype, player: Player, options: anytype) !void {
1031
1387
  var side = battle.side(player);
1032
- var volatiles = &side.active.volatiles;
1033
1388
 
1034
- // TODO or if we didn't go first
1035
- if (volatiles.Substitute) return try options.log.fail(battle.active(player), .None);
1389
+ var n: u3 = 0;
1390
+ for (side.active.moves) |m| {
1391
+ if (convertible(side.active, m.id)) n += 1;
1392
+ }
1393
+ if (n == 0) return try options.log.fail(battle.active(player), .None);
1394
+
1395
+ side.active.types.type1 = try Rolls.conversion(battle, player, n, options);
1396
+ side.active.types.type2 = side.active.types.type1;
1397
+ }
1398
+
1399
+ fn conversion2(battle: anytype, player: Player, options: anytype) !void {
1400
+ var side = battle.side(player);
1401
+
1402
+ const last = battle.foe(player).lastMove(false);
1403
+ if (last == .None) return try options.log.fail(battle.active(player), .None);
1404
+ const move = Move.get(last);
1405
+ if (move.type == .@"???") return try options.log.fail(battle.active(player), .None);
1406
+
1407
+ side.active.types.type1 = try Rolls.conversion2(battle, player, move.type, options);
1408
+ side.active.types.type2 = side.active.types.type1;
1409
+ }
1410
+
1411
+ fn counter(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1412
+ _ = .{ battle, player, move, options }; // TODO
1413
+ }
1414
+
1415
+ fn curse(battle: anytype, player: Player, options: anytype) !void {
1416
+ _ = .{ battle, player, options }; // TODO
1417
+ }
1418
+
1419
+ fn defenseCurl(battle: anytype, player: Player, options: anytype) !void {
1420
+ _ = .{ battle, player, options }; // TODO
1421
+ }
1422
+
1423
+ fn destinyBond(battle: anytype, player: Player, options: anytype) !void {
1424
+ battle.side(player).active.volatiles.DestinyBond = true;
1425
+ try options.log.singlemove(battle.active(player), Move.DestinyBond);
1426
+ }
1427
+
1428
+ fn disable(battle: anytype, player: Player, options: anytype) !void {
1429
+ var foe = battle.foe(player);
1430
+ var volatiles = &foe.active.volatiles;
1431
+ const foe_ident = battle.active(player.foe());
1432
+
1433
+ if (volatiles.disabled.move != 0) return try options.log.fail(foe_ident, .None);
1434
+
1435
+ const last = battle.foe(player).lastMove(false);
1436
+ if (last == .None or last == .Struggle) return try options.log.fail(foe_ident, .None);
1437
+
1438
+ var slot: u3 = 0;
1439
+ for (foe.active.moves) |m| {
1440
+ assert(m.id != .None);
1441
+ slot += 1;
1442
+ if (m.id == last) break;
1443
+ }
1444
+ const move = foe.active.move(slot);
1445
+ if (move.pp == 0) return try options.log.fail(foe_ident, .None);
1446
+
1447
+ volatiles.disabled.move = slot;
1448
+ volatiles.disabled.duration = Rolls.disableDuration(battle, player, options);
1449
+
1450
+ try options.log.startEffect(foe_ident, .Disable, move.id);
1451
+ }
1452
+
1453
+ fn doubleHit(battle: anytype, player: Player, options: anytype) !void {
1454
+ _ = .{ battle, player, options }; // TODO
1455
+ }
1456
+
1457
+ fn drainHP(battle: anytype, player: Player, options: anytype) !void {
1458
+ _ = .{ battle, player, options }; // TODO
1459
+ }
1460
+
1461
+ fn encore(battle: anytype, player: Player, first: bool, options: anytype) !void {
1462
+ var foe = battle.foe(player);
1463
+ var volatiles = &foe.active.volatiles;
1464
+ const foe_ident = battle.active(player.foe());
1465
+
1466
+ const last = battle.foe(player).lastMove(true);
1467
+ const failed = volatiles.Encore or
1468
+ (last == .None or last == .Struggle or
1469
+ last == .Encore or .last == .MirrorMove);
1470
+ if (failed) return try options.log.fail(foe_ident, .None);
1471
+
1472
+ var slot: u3 = 0;
1473
+ for (foe.active.moves) |m| {
1474
+ assert(m.id != .None);
1475
+ slot += 1;
1476
+ if (m.id == last) break;
1477
+ }
1478
+ const move = foe.active.move(slot);
1479
+ if (move.pp == 0) return try options.log.fail(foe_ident, .None);
1480
+
1481
+ volatiles.Encore = true;
1482
+ volatiles.encore = Rolls.encoreDuration(battle, player, options);
1483
+
1484
+ // TODO ???
1485
+ _ = first;
1486
+
1487
+ try options.log.startEffect(foe_ident, .Encore, move.id);
1488
+ }
1489
+
1490
+ fn explode(battle: anytype, player: Player, options: anytype) !void {
1491
+ _ = .{ battle, player, options }; // TODO
1492
+ }
1493
+
1494
+ fn falseSwipe(battle: anytype, player: Player, damage: u16) !u16 {
1495
+ const foe = battle.foe(player);
1496
+ assert(foe.stored().hp > 0);
1497
+ // FIXME crit?
1498
+ return if (damage > foe.stored().hp) foe.stored().hp - 1 else damage;
1499
+ }
1500
+
1501
+ fn fixedDamage(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1502
+ _ = .{ battle, player, move, options }; // TODO
1503
+ }
1504
+
1505
+ fn flameWheel(battle: anytype, player: Player, options: anytype) !void {
1506
+ _ = .{ battle, player, options }; // TODO
1507
+ }
1508
+
1509
+ fn flinchChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1510
+ _ = .{ battle, player, move, options }; // TODO
1511
+ }
1512
+
1513
+ fn flyDig(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1514
+ _ = .{ battle, player, move, options }; // TODO
1515
+ }
1516
+
1517
+ fn focusEnergy(battle: anytype, player: Player, options: anytype) !void {
1518
+ var side = battle.side(player);
1519
+
1520
+ if (side.active.volatiles.FocusEnergy) {
1521
+ return if (!showdown) try options.log.fail(battle.active(player), .None);
1522
+ }
1523
+
1524
+ side.active.volatiles.FocusEnergy = true;
1525
+ try options.log.start(battle.active(player), .FocusEnergy);
1526
+ }
1527
+
1528
+ var SLOTS: [5]u3 = .{ 0, 0, 0, 0, 0 };
1529
+
1530
+ fn forceSwitch(battle: anytype, player: Player, options: anytype) !void {
1531
+ const foe = battle.foe(player);
1532
+
1533
+ var n: u3 = 0;
1534
+ var i: u3 = 0;
1535
+ for (foe.pokemon, 0..) |pokemon, j| {
1536
+ if (pokemon.species == .None) break;
1537
+ n += 1;
1538
+ if (pokemon.position == 1) continue;
1539
+ if (pokemon.hp > 0) {
1540
+ SLOTS[i] = @intCast(foe.pokemon[j].position);
1541
+ i += 1;
1542
+ }
1543
+ }
1544
+
1545
+ if (n == 0) try options.log.fail(battle.active(player), .None);
1546
+
1547
+ _ = try Rolls.forceSwitch(battle, player, SLOTS[0..i], n, options); // TODO
1548
+ }
1549
+
1550
+ fn foresight(battle: anytype, player: Player, options: anytype) !void {
1551
+ var foe = battle.foe(player);
1552
+
1553
+ if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
1554
+ try options.log.lastmiss();
1555
+ return try options.log.miss(battle.active(player));
1556
+ } else if (foe.active.volatiles.Foresight) {
1557
+ return try options.log.fail(battle.active(player), .None);
1558
+ }
1559
+
1560
+ foe.active.volatiles.Foresight = true;
1561
+ try options.log.start(battle.active(player.foe()), .FocusEnergy);
1562
+ }
1563
+
1564
+ fn freezeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1565
+ _ = .{ battle, player, move, options }; // TODO
1566
+ }
1567
+
1568
+ fn furyCutter(battle: anytype, player: Player, options: anytype) !void {
1569
+ _ = .{ battle, player, options }; // TODO
1570
+ }
1571
+
1572
+ fn futureSight(battle: anytype, player: Player, options: anytype) !void {
1573
+ _ = .{ battle, player, options }; // TODO
1574
+ }
1575
+
1576
+ fn happiness(battle: anytype, player: Player, move: Move.Data) !u8 {
1577
+ const val = battle.side(player).stored().happiness;
1578
+ return ((if (move.effect == .Frustration) 255 - val else val) * 10) / 25;
1579
+ }
1580
+
1581
+ fn haze(battle: anytype, player: Player, options: anytype) !void {
1582
+ _ = .{ battle, player, options }; // TODO
1583
+ }
1584
+
1585
+ fn heal(battle: anytype, player: Player, options: anytype) !void {
1586
+ _ = .{ battle, player, options }; // TODO
1587
+ }
1588
+
1589
+ fn healBell(battle: anytype, player: Player, options: anytype) !void {
1590
+ _ = .{ battle, player, options }; // TODO
1591
+ }
1592
+
1593
+ fn hiddenPower(battle: anytype, player: Player, options: anytype) !void {
1594
+ _ = .{ battle, player, options }; // TODO
1595
+ }
1596
+
1597
+ fn hyperBeam(battle: anytype, player: Player, options: anytype) !void {
1598
+ _ = .{ battle, player, options }; // TODO
1599
+ }
1600
+
1601
+ fn jumpKick(battle: anytype, player: Player, options: anytype) !void {
1602
+ _ = .{ battle, player, options }; // TODO
1603
+ }
1604
+
1605
+ fn leechSeed(battle: anytype, player: Player, options: anytype) !void {
1606
+ _ = .{ battle, player, options }; // TODO
1607
+ }
1608
+
1609
+ fn levelDamage(battle: anytype, player: Player, options: anytype) !void {
1610
+ _ = .{ battle, player, options }; // TODO
1611
+ }
1612
+
1613
+ fn lockOn(battle: anytype, player: Player, options: anytype) !void {
1614
+ _ = .{ battle, player, options }; // TODO
1615
+ }
1616
+
1617
+ const MAGNITUDE_POWER = [_]u8{ 10, 30, 50, 70, 90, 110, 150 };
1618
+
1619
+ fn magnitude(battle: anytype, player: Player, options: anytype) !void {
1620
+ _ = .{ battle, player, options }; // TODO
1621
+ }
1622
+
1623
+ fn meanLook(battle: anytype, player: Player, options: anytype) !void {
1624
+ _ = .{ battle, player, options }; // TODO
1625
+ }
1626
+
1627
+ fn metronome(battle: anytype, player: Player, options: anytype) !void {
1628
+ _ = .{ battle, player, options }; // TODO
1629
+ }
1630
+
1631
+ fn mimic(battle: anytype, player: Player, options: anytype) !void {
1632
+ _ = .{ battle, player, options }; // TODO
1633
+ }
1634
+
1635
+ fn mirrorCoat(battle: anytype, player: Player, options: anytype) !void {
1636
+ _ = .{ battle, player, options }; // TODO
1637
+ }
1638
+
1639
+ fn mirrorMove(battle: anytype, player: Player, options: anytype) !void {
1640
+ _ = .{ battle, player, options }; // TODO
1641
+ }
1642
+
1643
+ fn mist(battle: anytype, player: Player, options: anytype) !void {
1644
+ _ = .{ battle, player, options }; // TODO
1645
+ }
1646
+
1647
+ fn weatherHeal(battle: anytype, player: Player, options: anytype) !void {
1648
+ _ = .{ battle, player, options }; // TODO
1649
+ }
1650
+
1651
+ fn multiHit(battle: anytype, player: Player, options: anytype) !void {
1652
+ _ = .{ battle, player, options }; // TODO
1653
+ }
1654
+
1655
+ fn nightmare(battle: anytype, player: Player, options: anytype) !void {
1656
+ _ = .{ battle, player, options }; // TODO
1657
+ }
1658
+
1659
+ fn ohko(battle: anytype, player: Player, options: anytype) !void {
1660
+ _ = .{ battle, player, options }; // TODO
1661
+ }
1662
+
1663
+ fn painSplit(battle: anytype, player: Player, options: anytype) !void {
1664
+ _ = .{ battle, player, options }; // TODO
1665
+ }
1666
+
1667
+ fn paralyze(battle: anytype, player: Player, options: anytype) !void {
1668
+ _ = .{ battle, player, options }; // TODO
1669
+ }
1670
+
1671
+ fn paralyzeChance(battle: anytype, player: Player, options: anytype) !void {
1672
+ // TODO: Thunder does acc/checkHit/effectChance before stab + damage variation
1673
+ _ = .{ battle, player, options }; // TODO
1674
+ }
1675
+
1676
+ fn payDay(battle: anytype, player: Player, options: anytype) !void {
1677
+ _ = .{ battle, player, options }; // TODO
1678
+ }
1036
1679
 
1037
- // TODO
1680
+ fn perishSong(battle: anytype, player: Player, options: anytype) !void {
1681
+ _ = .{ battle, player, options }; // TODO
1682
+ }
1038
1683
 
1039
- if (move.effect == .Endure) {
1684
+ fn poison(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1685
+ _ = .{ battle, player, move, options }; // TODO
1686
+ }
1687
+
1688
+ fn present(battle: anytype, player: Player, options: anytype) !void {
1689
+ _ = .{ battle, player, options }; // TODO
1690
+ }
1691
+
1692
+ fn protect(
1693
+ battle: anytype,
1694
+ player: Player,
1695
+ move: Move,
1696
+ first: bool,
1697
+ options: anytype,
1698
+ ) !void {
1699
+ var side = battle.side(player);
1700
+ var volatiles = &side.active.volatiles;
1701
+
1702
+ const failed = !first or volatiles.Substitute or
1703
+ !try Rolls.protect(battle, player, volatiles.protect, options);
1704
+ if (failed) return try options.log.fail(battle.active(player), .None);
1705
+
1706
+ volatiles.protect = @max(8, volatiles.protect + 1);
1707
+ if (move == .Endure) {
1040
1708
  volatiles.Endure = true;
1041
1709
  } else {
1042
1710
  volatiles.Protect = true;
1043
1711
  }
1044
- try options.log.singleturn(battle.active(player), move.id);
1712
+ try options.log.singleturn(battle.active(player), move);
1045
1713
  }
1046
1714
 
1047
- const MAGNITUDE_POWER = [_]u8{ 10, 30, 50, 70, 90, 110, 150 };
1715
+ fn psychUp(battle: anytype, player: Player, options: anytype) !void {
1716
+ _ = .{ battle, player, options }; // TODO
1717
+ }
1718
+
1719
+ fn pursuit(battle: anytype, player: Player, options: anytype) !void {
1720
+ _ = .{ battle, player, options }; // TODO
1721
+ }
1722
+
1723
+ fn rage(battle: anytype, player: Player, options: anytype) !void {
1724
+ _ = .{ battle, player, options }; // TODO
1725
+ }
1726
+
1727
+ fn rainDance(battle: anytype, player: Player, options: anytype) !void {
1728
+ _ = .{ battle, player, options }; // TODO
1729
+ }
1730
+
1731
+ fn rapidSpin(battle: anytype, player: Player, options: anytype) !void {
1732
+ _ = .{ battle, player, options }; // TODO
1733
+ }
1734
+
1735
+ fn razorWind(battle: anytype, player: Player, options: anytype) !void {
1736
+ _ = .{ battle, player, options }; // TODO
1737
+ }
1738
+
1739
+ fn recoil(battle: anytype, player: Player, options: anytype) !void {
1740
+ _ = .{ battle, player, options }; // TODO
1741
+ }
1742
+
1743
+ fn reversal(battle: anytype, player: Player, options: anytype) !void {
1744
+ _ = .{ battle, player, options }; // TODO
1745
+ }
1746
+
1747
+ fn rollout(battle: anytype, player: Player, options: anytype) !void {
1748
+ _ = .{ battle, player, options }; // TODO
1749
+ }
1750
+
1751
+ fn sacredFire(battle: anytype, player: Player, options: anytype) !void {
1752
+ _ = .{ battle, player, options }; // TODO
1753
+ }
1754
+
1755
+ fn safeguard(battle: anytype, player: Player, options: anytype) !void {
1756
+ _ = .{ battle, player, options }; // TODO
1757
+ }
1758
+
1759
+ fn sandstorm(battle: anytype, player: Player, options: anytype) !void {
1760
+ _ = .{ battle, player, options }; // TODO
1761
+ }
1762
+
1763
+ fn screens(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1764
+ _ = .{ battle, player, move, options }; // TODO
1765
+ }
1766
+
1767
+ fn sketch(battle: anytype, player: Player, options: anytype) !void {
1768
+ _ = .{ battle, player, options }; // TODO
1769
+ }
1770
+
1771
+ fn skullBash(battle: anytype, player: Player, options: anytype) !void {
1772
+ _ = .{ battle, player, options }; // TODO
1773
+ }
1774
+
1775
+ fn skyAttack(battle: anytype, player: Player, options: anytype) !void {
1776
+ _ = .{ battle, player, options }; // TODO
1777
+ }
1778
+
1779
+ fn sleep(battle: anytype, player: Player, options: anytype) !void {
1780
+ _ = .{ battle, player, options }; // TODO
1781
+ }
1782
+
1783
+ fn sleepTalk(battle: anytype, player: Player, mslot: u3, options: anytype) !void {
1784
+ const side = battle.side(player);
1785
+
1786
+ if (!Status.is(side.stored().status, .SLP)) {
1787
+ return if (!showdown) try options.log.fail(battle.active(player), .None);
1788
+ }
1789
+
1790
+ var n: u3 = 0;
1791
+ for (side.active.moves, 0..) |m, i| {
1792
+ if (mslot - 1 != i and Move.get(m.id).extra.sleep_talk) n += 1;
1793
+ }
1794
+ if (n == 0) return try options.log.fail(battle.active(player), .None);
1795
+
1796
+ _ = try Rolls.sleepTalk(battle, player, n, mslot, options); // TODO
1797
+ }
1798
+
1799
+ fn snore(battle: anytype, player: Player, options: anytype) !void {
1800
+ _ = .{ battle, player, options }; // TODO
1801
+ }
1802
+
1803
+ fn solarBeam(battle: anytype, player: Player, options: anytype) !void {
1804
+ _ = .{ battle, player, options }; // TODO
1805
+ }
1806
+
1807
+ fn spikes(battle: anytype, player: Player, options: anytype) !void {
1808
+ _ = .{ battle, player, options }; // TODO
1809
+ }
1810
+
1811
+ fn spite(battle: anytype, player: Player, options: anytype) !void {
1812
+ _ = .{ battle, player, options }; // TODO
1813
+ }
1814
+
1815
+ fn splash(battle: anytype, player: Player, options: anytype) !void {
1816
+ _ = .{ battle, player, options }; // TODO
1817
+ }
1818
+
1819
+ fn stomp(battle: anytype, player: Player, options: anytype) !void {
1820
+ _ = .{ battle, player, options }; // TODO
1821
+ }
1822
+
1823
+ fn substitute(battle: anytype, player: Player, options: anytype) !void {
1824
+ _ = .{ battle, player, options }; // TODO
1825
+ }
1826
+
1827
+ fn sunnyDay(battle: anytype, player: Player, options: anytype) !void {
1828
+ _ = .{ battle, player, options }; // TODO
1829
+ }
1830
+
1831
+ fn swagger(battle: anytype, player: Player, options: anytype) !void {
1832
+ _ = .{ battle, player, options }; // TODO
1833
+ }
1834
+
1835
+ fn synthesis(battle: anytype, player: Player, options: anytype) !void {
1836
+ _ = .{ battle, player, options }; // TODO
1837
+ }
1838
+
1839
+ fn teleport(battle: anytype, player: Player, options: anytype) !void {
1840
+ _ = .{ battle, player, options }; // TODO
1841
+ }
1842
+
1843
+ fn thief(battle: anytype, player: Player, options: anytype) !void {
1844
+ _ = .{ battle, player, options }; // TODO
1845
+ }
1846
+
1847
+ fn thrashing(battle: anytype, player: Player, options: anytype) !void {
1848
+ _ = .{ battle, player, options }; // TODO
1849
+ }
1850
+
1851
+ fn toxic(battle: anytype, player: Player, options: anytype) !void {
1852
+ _ = .{ battle, player, options }; // TODO
1853
+ }
1854
+
1855
+ fn transform(battle: anytype, player: Player, options: anytype) !void {
1856
+ _ = .{ battle, player, options }; // TODO
1857
+ }
1858
+
1859
+ fn tripleKick(battle: anytype, player: Player, options: anytype) !void {
1860
+ _ = .{ battle, player, options }; // TODO
1861
+ }
1862
+
1863
+ fn twister(battle: anytype, player: Player, options: anytype) !void {
1864
+ _ = .{ battle, player, options }; // TODO
1865
+ }
1866
+
1867
+ fn boost(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1868
+ _ = .{ battle, player, move, options }; // TODO
1869
+ }
1870
+
1871
+ fn unboost(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1872
+ _ = .{ battle, player, move, options }; // TODO
1873
+ }
1048
1874
  };
1049
1875
 
1050
1876
  fn statusModify(status: u8, stats: *Stats(u16)) void {
@@ -1061,6 +1887,12 @@ inline fn isForced(active: anytype) bool {
1061
1887
  active.volatiles.Rollout;
1062
1888
  }
1063
1889
 
1890
+ inline fn convertible(active: ActivePokemon, m: Move) bool {
1891
+ if (m == .None) return false;
1892
+ const t = Move.get(m).type;
1893
+ return t != .@"???" and !active.types.includes(t);
1894
+ }
1895
+
1064
1896
  pub const Rolls = struct {
1065
1897
  inline fn speedTie(battle: anytype, options: anytype) !bool {
1066
1898
  const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
@@ -1187,47 +2019,25 @@ pub const Rolls = struct {
1187
2019
  return proc;
1188
2020
  }
1189
2021
 
1190
- inline fn focusBand(battle: anytype, player: Player, options: anytype) !bool {
1191
- const proc = if (options.calc.overridden(player, .focus_band)) |val|
1192
- val == .true
1193
- else if (showdown)
1194
- battle.rng.chance(u8, 30, 256)
1195
- else
1196
- battle.rng.next() < 30;
1197
-
1198
- try options.chance.focusBand(player, proc);
1199
- return proc;
1200
- }
2022
+ inline fn item(battle: anytype, player: Player, options: anytype, effect: Item) !bool {
2023
+ assert(effect == .FocusBand or effect == .KingsRock);
2024
+ const roll = if (effect == .FocusBand) .focus_band else .kings_rock;
1201
2025
 
1202
- inline fn kingsRock(battle: anytype, player: Player, options: anytype) !bool {
1203
- const proc = if (options.calc.overridden(player, .kings_rock)) |val|
2026
+ const proc = if (options.calc.overridden(player, roll)) |val|
1204
2027
  val == .true
1205
2028
  else if (showdown)
1206
2029
  battle.rng.chance(u8, 30, 256)
1207
2030
  else
1208
2031
  battle.rng.next() < 30;
1209
2032
 
1210
- try options.chance.kingsRock(player, proc);
2033
+ if (effect == .FocusBand) {
2034
+ try options.chance.focusBand(player, proc);
2035
+ } else {
2036
+ try options.chance.kingsRock(player, proc);
2037
+ }
1211
2038
  return proc;
1212
2039
  }
1213
2040
 
1214
- inline fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
1215
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
1216
- @intCast(val)
1217
- else if (showdown)
1218
- @intCast(battle.rng.range(u8, 1, 7))
1219
- else duration: {
1220
- while (true) {
1221
- const r = battle.rng.next() & 7;
1222
- if (r != 0 and r != 7) break :duration @intCast(r);
1223
- }
1224
- };
1225
-
1226
- assert(duration >= 1 and duration <= 6);
1227
- options.chance.duration(.sleep, player, player.foe(), duration);
1228
- return duration + 1;
1229
- }
1230
-
1231
2041
  inline fn triAttack(battle: anytype, player: Player, options: anytype) !u8 {
1232
2042
  const status: TriAttack = if (options.calc.overridden(player, .tri_attack)) |val|
1233
2043
  val
@@ -1236,7 +2046,7 @@ pub const Rolls = struct {
1236
2046
  else loop: {
1237
2047
  while (true) {
1238
2048
  const r = std.math.rotr(u8, std.battle.rng.next(), 4) & 3;
1239
- if (r != 0) break :loop @intCast(r - 1);
2049
+ if (r != 0) break :loop @enumFromInt(r - 1);
1240
2050
  }
1241
2051
  };
1242
2052
 
@@ -1294,11 +2104,11 @@ pub const Rolls = struct {
1294
2104
  return num;
1295
2105
  }
1296
2106
 
1297
- inline fn tripleKick(battle: anytype, player: Player, options: anytype) !u8 {
2107
+ inline fn tripleKick(battle: anytype, player: Player, options: anytype) !u2 {
1298
2108
  const hits = if (options.calc.overridden(player, .triple_kick)) |val|
1299
2109
  val
1300
2110
  else if (showdown)
1301
- battle.rng.range(u8, 1, 4)
2111
+ battle.rng.range(u2, 1, 4)
1302
2112
  else power: {
1303
2113
  while (true) {
1304
2114
  const r = battle.rng.next() & 3;
@@ -1325,7 +2135,7 @@ pub const Rolls = struct {
1325
2135
  }
1326
2136
 
1327
2137
  // Conversion 2 can at most choose between 7 types (vs. a Grass-type attack)
1328
- const CONVERSION_2: [7]Type = [_]Type{.{}} ** 7;
2138
+ var CONVERSION_2: [7]Type = [_]Type{.@"???"} ** 7;
1329
2139
 
1330
2140
  inline fn conversion2(battle: anytype, player: Player, mtype: Type, options: anytype) !Type {
1331
2141
  var i: u8 = 0;
@@ -1349,7 +2159,7 @@ pub const Rolls = struct {
1349
2159
  if (r > 9 and r < 20) continue; // UNUSED_TYPES
1350
2160
  if (r > 27) continue; // TYPES_END
1351
2161
 
1352
- const t = Types.conversion2(r);
2162
+ const t = Type.conversion2(r);
1353
2163
  if (@intFromEnum(mtype.effectiveness(t)) < neutral) {
1354
2164
  break :ty t;
1355
2165
  }
@@ -1361,17 +2171,152 @@ pub const Rolls = struct {
1361
2171
  return ty;
1362
2172
  }
1363
2173
 
1364
- // TODO thrashingDuration
1365
- // TODO confusionDuraiton (from thrash vs. from move)
1366
- // TODO forceSwitch
1367
- // TODO bindingDuration
1368
- // TODO bideDuration
1369
- // TODO conversion moveSlot
1370
- // TODO disable duration
1371
- // TODO encore duration
1372
- // TODO metronome
1373
- // TODO protect
1374
- // TODO sleeptalk move slot
2174
+ inline fn protect(battle: anytype, player: Player, count: u8, options: anytype) !bool {
2175
+ assert(count <= 8);
2176
+ const num = if (count == 8) 0 else @as(u8, 255) >> @intCast(count);
2177
+
2178
+ const ok = if (options.calc.overridden(player, .protect)) |val|
2179
+ val == .true
2180
+ else if (showdown)
2181
+ battle.rng.chance(u8, num, 255)
2182
+ else protected: {
2183
+ while (true) {
2184
+ const r = battle.rng.next();
2185
+ if (r != 0) break :protected !(num <= r - 1);
2186
+ }
2187
+ };
2188
+
2189
+ try options.chance.protect(player, num, ok);
2190
+ return ok;
2191
+ }
2192
+
2193
+ fn conversion(battle: anytype, player: Player, n: u3, options: anytype) !Type {
2194
+ assert(n > 0);
2195
+
2196
+ const active = battle.side(player).active;
2197
+ const moves = active.moves;
2198
+
2199
+ // TODO: consider throwing error instead of rerolling?
2200
+ const overridden = if (options.calc.overridden(player, .move_slot)) |val|
2201
+ if (convertible(active, moves[val - 1].id)) val else null
2202
+ else
2203
+ null;
2204
+
2205
+ const slot: u3 = overridden orelse slot: {
2206
+ if (showdown) {
2207
+ var r = battle.rng.range(u3, 0, n) + 1;
2208
+ var i: usize = 0;
2209
+ while (i < moves.len and r > 0) : (i += 1) {
2210
+ if (convertible(active, moves[i].id)) {
2211
+ r -= 1;
2212
+ if (r == 0) break :slot @intCast(i + 1);
2213
+ }
2214
+ }
2215
+ break :slot @intCast(i + 1);
2216
+ } else {
2217
+ while (true) {
2218
+ const r: u3 = @intCast(battle.rng.next() & 3);
2219
+ if (convertible(active, moves[r].id)) break :slot @intCast(r + 1);
2220
+ }
2221
+ }
2222
+ };
2223
+
2224
+ assert(n >= 1 and n <= 4);
2225
+ assert(slot >= 1 and slot <= 4);
2226
+ try options.chance.moveSlot(player, slot, n);
2227
+ return Move.get(active.move(slot).id).type;
2228
+ }
2229
+
2230
+ fn sleepTalk(battle: anytype, player: Player, n: u3, mslot: u3, options: anytype) !u3 {
2231
+ assert(n > 0);
2232
+
2233
+ const active = battle.side(player).active;
2234
+ const moves = active.moves;
2235
+
2236
+ // TODO: consider throwing error instead of rerolling?
2237
+ const overridden = if (options.calc.overridden(player, .move_slot)) |val|
2238
+ if (val != mslot and Move.get(moves[val - 1].id).extra.sleep_talk) val else null
2239
+ else
2240
+ null;
2241
+
2242
+ const slot: u3 = overridden orelse slot: {
2243
+ if (showdown) {
2244
+ var r = battle.rng.range(u3, 0, n) + 1;
2245
+ var i: usize = 0;
2246
+ while (i < moves.len and r > 0) : (i += 1) {
2247
+ if (i + 1 != mslot and Move.get(moves[i].id).extra.sleep_talk) {
2248
+ r -= 1;
2249
+ if (r == 0) break :slot @intCast(i + 1);
2250
+ }
2251
+ }
2252
+ break :slot @intCast(i + 1);
2253
+ } else {
2254
+ while (true) {
2255
+ const r: u3 = @intCast(battle.rng.next() & 3);
2256
+ if (r + 1 == mslot or !Move.get(moves[r].id).extra.sleep_talk) continue;
2257
+ break :slot @intCast(r + 1);
2258
+ }
2259
+ }
2260
+ };
2261
+
2262
+ assert(n >= 1 and n <= 4);
2263
+ assert(slot >= 1 and slot <= 4);
2264
+ try options.chance.moveSlot(player, slot, n);
2265
+ return slot;
2266
+ }
2267
+
2268
+ inline fn forceSwitch(
2269
+ battle: anytype,
2270
+ player: Player,
2271
+ slots: []u3,
2272
+ n: u3,
2273
+ options: anytype,
2274
+ ) !u3 {
2275
+ const foe = battle.foe(player);
2276
+
2277
+ assert(slots.len < 5);
2278
+ assert(n > 0 and n <= 6);
2279
+
2280
+ // TODO: consider throwing error instead of rerolling?
2281
+ const overridden = if (options.calc.overridden(player, .force_switch)) |val| val: {
2282
+ for (slots) |slot| if (slot == val) break :val if (foe.get(slot).hp > 0) val else null;
2283
+ break :val null;
2284
+ } else null;
2285
+
2286
+ const slot: u3 = overridden orelse slot: {
2287
+ if (showdown) {
2288
+ break :slot slots[battle.rng.range(u3, 0, @as(u3, @intCast(slots.len)))];
2289
+ } else {
2290
+ while (true) {
2291
+ const r = battle.rng.next() & 7;
2292
+ if (r > n or r == 1) continue;
2293
+ if (foe.get(r).hp > 0) break :slot @intCast(r);
2294
+ }
2295
+ }
2296
+ };
2297
+
2298
+ assert(n >= 1 and n <= 6);
2299
+ assert(slot >= 1 and slot <= 6);
2300
+ try options.chance.forceSwitch(player, slot, n);
2301
+ return slot;
2302
+ }
2303
+
2304
+ const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
2305
+
2306
+ inline fn multiHit(battle: anytype, player: Player, options: anytype) !u3 {
2307
+ const n: u3 = if (options.calc.overridden(player, .multi_hit)) |val|
2308
+ @intCast(val)
2309
+ else if (showdown)
2310
+ DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
2311
+ else n: {
2312
+ const r = (battle.rng.next() & 3);
2313
+ break :n @intCast((if (r < 2) r else battle.rng.next() & 3) + 2);
2314
+ };
2315
+
2316
+ assert(n >= 2 and n <= 5);
2317
+ try options.chance.multiHit(player, n);
2318
+ return n;
2319
+ }
1375
2320
 
1376
2321
  inline fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
1377
2322
  const power = if (options.calc.overridden(player, .psywave)) |val|
@@ -1390,18 +2335,135 @@ pub const Rolls = struct {
1390
2335
  return power;
1391
2336
  }
1392
2337
 
1393
- const METRONOME = init: {
1394
- var num = 0;
1395
- var moves: [238]Move = undefined;
1396
- for (1..Move.size) |i| {
1397
- if (Move.get(@enumFromInt(i)).extra.metronome) {
1398
- moves[num] = @enumFromInt(i);
1399
- num += 1;
1400
- }
2338
+ inline fn metronome(battle: anytype, player: Player, options: anytype) !Move {
2339
+ const moves = battle.get(player).active.moves;
2340
+
2341
+ var n: u2 = 0;
2342
+ for (moves) |m| {
2343
+ if (m == .None) break;
2344
+ if (Move.get(m).metronome) n += 1;
1401
2345
  }
1402
- assert(num == moves.len);
1403
- break :init moves;
1404
- };
2346
+
2347
+ // TODO: consider throwing error instead of rerolling?
2348
+ const overridden = if (options.calc.overridden(player, .metronome)) |val| val: {
2349
+ for (moves) |m| if (m.id == val) break :val null;
2350
+ break :val val;
2351
+ } else null;
2352
+
2353
+ const move: Move = overridden orelse move: {
2354
+ if (showdown) {
2355
+ var r = battle.rng.range(u8, 0, Move.METRONOME.len - n);
2356
+ for (moves) |m| {
2357
+ if (m == .None or @intFromEnum(m) > @intFromEnum(Move.METRONOME[r])) break;
2358
+ if (Move.get(m).metronome) r -= 1;
2359
+ }
2360
+ break :move Move.METRONOME[r];
2361
+ } else {
2362
+ outer: while (true) {
2363
+ const r = battle.rng.next();
2364
+ if (r == 0 or r >= Move.size) continue;
2365
+ const selected: Move = @enumFromInt(r);
2366
+ if (!Move.get(selected).extra.metronome) continue;
2367
+ for (moves) |m| if (m.id == selected) continue :outer;
2368
+ break :move selected;
2369
+ }
2370
+ }
2371
+ };
2372
+
2373
+ assert(Move.get(move).metronome);
2374
+ try options.chance.metronome(player, move, n);
2375
+ return move;
2376
+ }
2377
+
2378
+ inline fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
2379
+ const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2380
+ @intCast(val)
2381
+ else if (showdown)
2382
+ battle.rng.range(u3, 1, 7)
2383
+ else duration: {
2384
+ while (true) {
2385
+ const r = battle.rng.next() & 7;
2386
+ if (r != 0 and r != 7) break :duration @intCast(r);
2387
+ }
2388
+ };
2389
+
2390
+ assert(duration >= 1 and duration <= 6);
2391
+ options.chance.duration(.sleep, player, player.foe(), duration);
2392
+ return duration + 1;
2393
+ }
2394
+
2395
+ inline fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
2396
+ const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2397
+ @intCast(val)
2398
+ else if (showdown)
2399
+ // Pokémon Showdown incorrectly uses the same duration for self-confusion
2400
+ battle.rng.range(u3, 2, 6)
2401
+ else
2402
+ @intCast((battle.rng.next() & if (self) 1 else 3) + 2);
2403
+
2404
+ assert(duration >= 2 and duration <= if (!showdown and self) 3 else 5);
2405
+ options.chance.duration(.confusion, player, if (self) player else player.foe(), duration);
2406
+ return duration;
2407
+ }
2408
+
2409
+ inline fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
2410
+ const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
2411
+ @intCast(val)
2412
+ else if (showdown)
2413
+ // Pokémon Showdown incorrectly inherits Generation III's Disable duration
2414
+ battle.rng.range(u4, 2, 6)
2415
+ else duration: {
2416
+ while (true) {
2417
+ const r = battle.rng.next() & 7;
2418
+ if (r != 0) break :duration @intCast(r + 1);
2419
+ }
2420
+ };
2421
+
2422
+ assert(duration >= 2 and duration <= (if (showdown) 5 else 7));
2423
+ options.chance.duration(.disable, player, player.foe(), duration);
2424
+ return duration;
2425
+ }
2426
+
2427
+ inline fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
2428
+ const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2429
+ @intCast(val)
2430
+ else if (showdown)
2431
+ battle.rng.range(u3, 2, 4)
2432
+ else
2433
+ @intCast((battle.rng.next() & 1) + 2); // BUG: thrash is + 1 not +2
2434
+
2435
+ assert(duration >= 2 and duration <= 3);
2436
+ options.chance.duration(.attacking, player, player, duration);
2437
+ return duration;
2438
+ }
2439
+
2440
+ inline fn bindingDuration(battle: anytype, player: Player, options: anytype) u3 {
2441
+ const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
2442
+ @intCast(val)
2443
+ else if (showdown)
2444
+ // TODO: PS does range(3, 6) here which after subtracting 1 is actually 2-4 not 2-5!
2445
+ battle.rng.range(u3, 2, 5)
2446
+ else
2447
+ @intCast(battle.rng.next() & 3 + 2));
2448
+
2449
+ assert(duration >= 2 and duration <= 5);
2450
+ options.chance.duration(.binding, player, player.foe(), duration);
2451
+ return duration + 1;
2452
+ }
2453
+
2454
+ // TODO: consider sharing implementation with bindingDuration
2455
+ inline fn encoreDuration(battle: anytype, player: Player, options: anytype) u3 {
2456
+ const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
2457
+ @intCast(val)
2458
+ else if (showdown)
2459
+ battle.rng.range(u3, 2, 6)
2460
+ else
2461
+ @intCast(battle.rng.next() & 3 + 2));
2462
+
2463
+ assert(duration >= 2 and duration <= 5);
2464
+ options.chance.duration(.encore, player, player.foe(), duration);
2465
+ return duration + 1;
2466
+ }
1405
2467
  };
1406
2468
 
1407
2469
  test "RNG agreement" {