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

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.
@@ -13,15 +13,16 @@ const assert = std.debug.assert;
13
13
 
14
14
  const expectEqual = std.testing.expectEqual;
15
15
 
16
+ const showdown = pkmn.options.showdown;
17
+
16
18
  const Choice = common.Choice;
17
19
  const ID = common.ID;
18
20
  const Player = common.Player;
19
21
  const Result = common.Result;
20
22
 
21
- const showdown = pkmn.options.showdown;
22
-
23
23
  const Boost = protocol.Boost;
24
24
  const Damage = protocol.Damage;
25
+ const Fail = protocol.Fail;
25
26
  const Heal = protocol.Heal;
26
27
 
27
28
  const Gen12 = rng.Gen12;
@@ -195,7 +196,7 @@ fn selectMove(
195
196
  if (choice.data == 0) {
196
197
  const struggle = ok: {
197
198
  for (side.active.moves, 0..) |move, i| {
198
- if (move.pp > 0 and volatiles.disabled_move != i + 1) break :ok false;
199
+ if (move.pp > 0 and volatiles.disable_move != i + 1) break :ok false;
199
200
  }
200
201
  break :ok true;
201
202
  };
@@ -214,7 +215,7 @@ fn saveMove(battle: anytype, player: Player, choice: ?Choice) void {
214
215
  if (c.data == 0) {
215
216
  side.last_selected_move = .Struggle;
216
217
  } else {
217
- assert(showdown or side.active.volatiles.disabled_move != c.data);
218
+ assert(showdown or side.active.volatiles.disable_move != c.data);
218
219
  const move = side.active.move(c.data);
219
220
  // You cannot *select* a move with 0 PP (except on Pokémon Showdown where that is
220
221
  // sometimes required...), but a 0 PP move can be used automatically
@@ -255,14 +256,14 @@ fn switchIn(battle: anytype, player: Player, slot: u8, initial: bool, options: a
255
256
 
256
257
  foe.active.volatiles.Binding = false;
257
258
 
258
- try options.log.switched(battle.active(player), incoming);
259
+ try options.log.switched(.{ battle.active(player), incoming });
259
260
  options.chance.switched(player, side.order[0], out);
260
261
 
261
262
  if (showdown and incoming.status == Status.TOX) {
262
263
  incoming.status = Status.init(.PSN);
263
264
  // Technically, Pokémon Showdown adds these after *both* Pokémon have switched, but we'd
264
265
  // rather not clutter up turnOrder just for this (incorrect) log message
265
- try options.log.status(battle.active(player), incoming.status, .Silent);
266
+ try options.log.status(.{ battle.active(player), incoming.status, .Silent });
266
267
  }
267
268
  }
268
269
 
@@ -406,7 +407,7 @@ fn executeMove(
406
407
  if (side.last_selected_move == .SKIP_TURN) {
407
408
  assert(!showdown);
408
409
  if (battle.foe(player).active.volatiles.Binding) {
409
- try options.log.cant(battle.active(player), .Bound);
410
+ try options.log.cant(.{ battle.active(player), .Bound });
410
411
  }
411
412
  return null;
412
413
  }
@@ -496,23 +497,23 @@ fn beforeMove(
496
497
  }
497
498
 
498
499
  if (duration == 0) {
499
- try log.curestatus(ident, before, .Message);
500
+ try log.curestatus(.{ ident, before, .Message });
500
501
  stored.status = 0; // clears EXT if present
501
502
  } else {
502
- try log.cant(ident, .Sleep);
503
+ try log.cant(.{ ident, .Sleep });
503
504
  }
504
505
  side.last_used_move = .None;
505
506
  return .done;
506
507
  }
507
508
 
508
509
  if (Status.is(stored.status, .FRZ)) {
509
- try log.cant(ident, .Freeze);
510
+ try log.cant(.{ ident, .Freeze });
510
511
  side.last_used_move = .None;
511
512
  return .done;
512
513
  }
513
514
 
514
515
  if (skip or foe.active.volatiles.Binding) {
515
- try log.cant(ident, .Bound);
516
+ try log.cant(.{ ident, .Bound });
516
517
  return .done;
517
518
  }
518
519
 
@@ -520,27 +521,27 @@ fn beforeMove(
520
521
  // Pokémon Showdown doesn't clear Flinch until its imaginary "residual" phase, meaning
521
522
  // Pokémon can sometimes flinch multiple times from the same original hit
522
523
  if (!showdown) volatiles.Flinch = false;
523
- try log.cant(ident, .Flinch);
524
+ try log.cant(.{ ident, .Flinch });
524
525
  return .done;
525
526
  }
526
527
 
527
528
  if (volatiles.Recharging) {
528
529
  volatiles.Recharging = false;
529
- try log.cant(ident, .Recharge);
530
+ try log.cant(.{ ident, .Recharge });
530
531
  return .done;
531
532
  }
532
533
 
533
- if (volatiles.disabled_duration > 0) {
534
+ if (volatiles.disable_duration > 0) {
534
535
  if (options.calc.modify(player, .disable)) |extend| {
535
- if (!extend) volatiles.disabled_duration = 0;
536
+ if (!extend) volatiles.disable_duration = 0;
536
537
  } else {
537
- volatiles.disabled_duration -= 1;
538
+ volatiles.disable_duration -= 1;
538
539
  }
539
- try options.chance.disable(player, volatiles.disabled_duration);
540
+ try options.chance.disable(player, volatiles.disable_duration);
540
541
 
541
- if (volatiles.disabled_duration == 0) {
542
- volatiles.disabled_move = 0;
543
- try log.end(ident, .Disable);
542
+ if (volatiles.disable_duration == 0) {
543
+ volatiles.disable_move = 0;
544
+ try log.end(.{ ident, .Disable });
544
545
  }
545
546
  }
546
547
  // Pokémon Showdown's disable condition has a single onBeforeMove handler
@@ -565,9 +566,9 @@ fn beforeMove(
565
566
 
566
567
  if (volatiles.confusion == 0) {
567
568
  volatiles.Confusion = false;
568
- try log.end(ident, .Confusion);
569
+ try log.end(.{ ident, .Confusion });
569
570
  } else {
570
- try log.activate(ident, .Confusion);
571
+ try log.activate(.{ ident, .Confusion });
571
572
 
572
573
  if (try Rolls.confused(battle, player, options)) {
573
574
  assert(!volatiles.MultiHit);
@@ -618,7 +619,7 @@ fn beforeMove(
618
619
  volatiles.Binding = false;
619
620
  options.chance.clearDurations(player, null);
620
621
  // GLITCH: Invulnerable is not cleared, resulting in permanent Fly/Dig invulnerability
621
- try log.cant(ident, .Paralysis);
622
+ try log.cant(.{ ident, .Paralysis });
622
623
  return .done;
623
624
  }
624
625
 
@@ -644,18 +645,18 @@ fn beforeMove(
644
645
  try options.chance.attacking(player, volatiles.attacks);
645
646
 
646
647
  if (volatiles.attacks != 0) {
647
- try log.activate(ident, .Bide);
648
+ try log.activate(.{ ident, .Bide });
648
649
  return .done;
649
650
  }
650
651
 
651
652
  volatiles.Bide = false;
652
- try log.end(ident, .Bide);
653
+ try log.end(.{ ident, .Bide });
653
654
 
654
655
  battle.last_damage = volatiles.state *% 2;
655
656
  volatiles.state = 0;
656
657
 
657
658
  if (battle.last_damage == 0) {
658
- try log.fail(ident, .None);
659
+ try log.fail(.{ ident, .None });
659
660
  return .done;
660
661
  }
661
662
 
@@ -685,15 +686,15 @@ fn beforeMove(
685
686
  try options.chance.attacking(player, volatiles.attacks);
686
687
 
687
688
  if (!showdown and handleThrashing(battle, active, player, options)) {
688
- try log.start(battle.active(player), .ConfusionSilent);
689
+ try log.start(.{ battle.active(player), .ConfusionSilent });
689
690
  }
690
- try log.move(ident, side.last_selected_move, battle.active(player.foe()));
691
+ try log.move(.{ ident, side.last_selected_move, battle.active(player.foe()) });
691
692
  if (showdown) {
692
693
  // This shouldn't actually set last_used_move, but Pokémon Showdown sets last
693
694
  // used in useMove and doesn't have the notion of skipping canMove semantics
694
695
  side.last_used_move = side.last_selected_move;
695
696
  if (handleThrashing(battle, active, player, options)) {
696
- try log.start(battle.active(player), .ConfusionSilent);
697
+ try log.start(.{ battle.active(player), .ConfusionSilent });
697
698
  }
698
699
  }
699
700
  return .skip_can;
@@ -709,7 +710,7 @@ fn beforeMove(
709
710
  }
710
711
  try options.chance.binding(player, volatiles.attacks);
711
712
 
712
- try log.move(battle.active(player), side.last_selected_move, battle.active(player.foe()));
713
+ try log.move(.{ ident, side.last_selected_move, battle.active(player.foe()) });
713
714
  if (showdown or battle.last_damage != 0) {
714
715
  const sub = showdown and foe.active.volatiles.Substitute;
715
716
  _ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
@@ -742,7 +743,7 @@ fn canMove(
742
743
  side.active.volatiles.Charging = false;
743
744
  side.active.volatiles.Invulnerable = false;
744
745
  } else if (move.effect == .Charge) {
745
- try options.log.moveFrom(player_ident, side.last_selected_move, .{}, from);
746
+ try options.log.move(.{ player_ident, side.last_selected_move, ID{}, from });
746
747
  setCounterable(battle, player, side, move);
747
748
  try Effects.charge(battle, player, options);
748
749
  return false;
@@ -752,7 +753,7 @@ fn canMove(
752
753
  if (!skip) decrementPP(side, mslot, auto);
753
754
 
754
755
  const target = if (move.target == .Self) player else player.foe();
755
- try options.log.moveFrom(player_ident, side.last_selected_move, battle.active(target), from);
756
+ try options.log.move(.{ player_ident, side.last_selected_move, battle.active(target), from });
756
757
  setCounterable(battle, player, side, move);
757
758
 
758
759
  if (move.effect.onBegin()) {
@@ -790,12 +791,14 @@ fn decrementPP(side: *Side, mslot: u4, auto: bool) void {
790
791
  assert(!volatiles.Rage and !volatiles.Thrashing and !volatiles.MultiHit);
791
792
  if (volatiles.Bide) return;
792
793
 
793
- assert(active.move(mslot).pp > 0 or auto);
794
- active.move(mslot).pp = @as(u6, @intCast(active.move(mslot).pp)) -% 1;
794
+ var move_slot = active.move(mslot);
795
+ assert(move_slot.pp > 0 or auto);
796
+ move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
795
797
  if (volatiles.Transform) return;
796
798
 
797
- assert(side.stored().move(mslot).pp > 0 or auto);
798
- side.stored().move(mslot).pp = @as(u6, @intCast(side.stored().move(mslot).pp)) -% 1;
799
+ move_slot = side.stored().move(mslot);
800
+ assert(move_slot.pp > 0 or auto);
801
+ move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
799
802
  assert(active.move(mslot).pp == side.stored().move(mslot).pp);
800
803
  }
801
804
 
@@ -927,16 +930,16 @@ fn doMove(
927
930
  ohko = (!showdown or (!immune and !invulnerable)) and
928
931
  move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe;
929
932
  if (ohko) {
930
- try log.immune(foe_ident, .OHKO);
933
+ try log.immune(.{ foe_ident, .OHKO });
931
934
  } else if (immune and !invulnerable and (!showdown or move.effect != .Binding)) {
932
- try log.immune(foe_ident, .None);
935
+ try log.immune(.{ foe_ident, .None });
933
936
  } else if (mist) {
934
- if (!foe.active.volatiles.Substitute) try log.activate(foe_ident, .Mist);
935
- try log.fail(foe_ident, .None);
937
+ if (!foe.active.volatiles.Substitute) try log.activate(.{ foe_ident, .Mist });
938
+ try log.fail(.{ foe_ident, .None });
936
939
  } else {
937
940
  if (showdown or !zero) try options.chance.commit(player, .miss);
938
- try log.lastmiss();
939
- try log.miss(battle.active(player));
941
+ try log.lastmiss(.{});
942
+ try log.miss(.{battle.active(player)});
940
943
  }
941
944
  if (move.effect == .JumpKick) {
942
945
  // Recoil is supposed to be damage/8 but damage will always be 0 here
@@ -972,15 +975,15 @@ fn doMove(
972
975
  var hit: u4 = 0;
973
976
  while (hit < hits) {
974
977
  if (hit == 0) {
975
- if (crit) try log.crit(battle.active(player.foe()));
978
+ if (crit) try log.crit(.{battle.active(player.foe())});
976
979
  if (effectiveness > Effectiveness.neutral) {
977
- try log.supereffective(battle.active(player.foe()));
980
+ try log.supereffective(.{battle.active(player.foe())});
978
981
  } else if (effectiveness < Effectiveness.neutral) {
979
- try log.resisted(battle.active(player.foe()));
982
+ try log.resisted(.{battle.active(player.foe())});
980
983
  }
981
984
  }
982
985
  if (!skip) nullified = try applyDamage(battle, player.foe(), player.foe(), .None, options);
983
- if (hit == 0 and ohko) try log.ohko();
986
+ if (hit == 0 and ohko) try log.ohko(.{});
984
987
  hit += 1;
985
988
  if (foe.stored().hp == 0) break;
986
989
  if (!late and (!sub or move.effect == .Explode)) {
@@ -997,7 +1000,7 @@ fn doMove(
997
1000
  if (showdown and move.effect == .Twineedle and !nullified and foe.stored().hp > 0) {
998
1001
  try Effects.poison(battle, player, Move.get(.PoisonSting), options);
999
1002
  }
1000
- try log.hitcount(battle.active(player.foe()), hit);
1003
+ try log.hitcount(.{ battle.active(player.foe()), hit });
1001
1004
  } else if (showdown) {
1002
1005
  // This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
1003
1006
  if (move.effect == .Binding) {
@@ -1007,9 +1010,9 @@ fn doMove(
1007
1010
  assert(foe.stored().hp > 0);
1008
1011
  // Pokémon Showdown logs |-damage| here instead of |-immune| because logic...
1009
1012
  if (sub) {
1010
- try log.activate(battle.active(player.foe()), .Substitute);
1013
+ try log.activate(.{ battle.active(player.foe()), .Substitute });
1011
1014
  } else {
1012
- try log.damage(battle.active(player.foe()), foe.stored(), .None);
1015
+ try log.damage(.{ battle.active(player.foe()), foe.stored(), .None });
1013
1016
  try buildRage(battle, player.foe(), options);
1014
1017
  }
1015
1018
  return null;
@@ -1207,7 +1210,7 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
1207
1210
  const foe = battle.foe(player);
1208
1211
 
1209
1212
  if (battle.last_damage == 0) {
1210
- try options.log.fail(battle.active(player), .None);
1213
+ try options.log.fail(.{ battle.active(player), .None });
1211
1214
  return null;
1212
1215
  }
1213
1216
 
@@ -1225,14 +1228,14 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
1225
1228
  foe_last_selected_move.type == .Fighting);
1226
1229
 
1227
1230
  if (!used and !selected) {
1228
- try options.log.fail(battle.active(player), .None);
1231
+ try options.log.fail(.{ battle.active(player), .None });
1229
1232
  return null;
1230
1233
  }
1231
1234
 
1232
1235
  if (!used or !selected) {
1233
1236
  // GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
1234
1237
  if (!showdown) return Result.Error;
1235
- try options.log.fail(battle.active(player), .None);
1238
+ try options.log.fail(.{ battle.active(player), .None });
1236
1239
  return null;
1237
1240
  }
1238
1241
 
@@ -1268,20 +1271,20 @@ fn applyDamage(
1268
1271
  subbed.active.volatiles.substitute = 0;
1269
1272
  subbed.active.volatiles.Substitute = false;
1270
1273
  // battle.last_damage is not updated with the amount of HP the Substitute had
1271
- try options.log.end(battle.active(sub_player), .Substitute);
1274
+ try options.log.end(.{ battle.active(sub_player), .Substitute });
1272
1275
  options.calc.capped(target_player);
1273
1276
  return true;
1274
1277
  } else {
1275
1278
  // Safe to truncate since less than subbed.volatiles.substitute which is a u8
1276
1279
  subbed.active.volatiles.substitute -= @intCast(battle.last_damage);
1277
- try options.log.activate(battle.active(sub_player), .Substitute);
1280
+ try options.log.activate(.{ battle.active(sub_player), .Substitute });
1278
1281
  return false;
1279
1282
  }
1280
1283
  }
1281
1284
 
1282
1285
  if (battle.last_damage > target.stored().hp) battle.last_damage = target.stored().hp;
1283
1286
  target.stored().hp -= battle.last_damage;
1284
- try options.log.damage(battle.active(target_player), target.stored(), reason);
1287
+ try options.log.damage(.{ battle.active(target_player), target.stored(), reason });
1285
1288
  return false;
1286
1289
  }
1287
1290
 
@@ -1300,7 +1303,7 @@ fn mirrorMove(
1300
1303
  side.last_selected_move = foe.last_used_move;
1301
1304
 
1302
1305
  if (foe.last_used_move == .None or foe.last_used_move == .MirrorMove) {
1303
- try options.log.fail(battle.active(player), .None);
1306
+ try options.log.fail(.{ battle.active(player), .None });
1304
1307
  return null;
1305
1308
  } else if (!showdown or foe.last_used_move != .Struggle) {
1306
1309
  incrementPP(side, mslot);
@@ -1342,12 +1345,12 @@ fn checkHit(battle: anytype, player: Player, move: Move.Data, options: anytype)
1342
1345
  if (mist) {
1343
1346
  assert(!showdown);
1344
1347
  const foe_ident = battle.active(player.foe());
1345
- try options.log.activate(foe_ident, .Mist);
1346
- try options.log.fail(foe_ident, .None);
1348
+ try options.log.activate(.{ foe_ident, .Mist });
1349
+ try options.log.fail(.{ foe_ident, .None });
1347
1350
  } else {
1348
1351
  try options.chance.commit(player, .miss);
1349
- try options.log.lastmiss();
1350
- try options.log.miss(battle.active(player));
1352
+ try options.log.lastmiss(.{});
1353
+ try options.log.miss(.{battle.active(player)});
1351
1354
  }
1352
1355
 
1353
1356
  return false;
@@ -1438,9 +1441,8 @@ fn checkFaint(
1438
1441
  const foe_fainted = foe.stored().hp == 0;
1439
1442
 
1440
1443
  const player_out = findFirstAlive(side) == 0;
1441
- const foe_out = findFirstAlive(foe) == 0;
1442
- const tie = player_out and foe_out;
1443
- const more = tie or player_out or foe_out;
1444
+ const foe_out = foe_fainted and findFirstAlive(foe) == 0;
1445
+ const more = player_out or foe_out;
1444
1446
 
1445
1447
  if (try faint(battle, player, !(more or foe_fainted), options)) |r| return r;
1446
1448
  if (foe_fainted) if (try faint(battle, player.foe(), !more, options)) |r| return r;
@@ -1448,14 +1450,14 @@ fn checkFaint(
1448
1450
  assert(!side.active.volatiles.MultiHit);
1449
1451
  assert(!foe.active.volatiles.MultiHit);
1450
1452
 
1451
- if (tie) {
1452
- try options.log.tie();
1453
+ if (player_out and foe_out) {
1454
+ try options.log.tie(.{});
1453
1455
  return Result.Tie;
1454
1456
  } else if (player_out) {
1455
- try options.log.win(player.foe());
1457
+ try options.log.win(.{player.foe()});
1456
1458
  return if (player == .P1) Result.Lose else Result.Win;
1457
1459
  } else if (foe_out) {
1458
- try options.log.win(player);
1460
+ try options.log.win(.{player});
1459
1461
  return if (player == .P1) Result.Win else Result.Lose;
1460
1462
  }
1461
1463
 
@@ -1497,7 +1499,7 @@ fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result
1497
1499
  side.stored().stats.spe;
1498
1500
  }
1499
1501
 
1500
- try options.log.faint(battle.active(player), done);
1502
+ try options.log.faint(.{ battle.active(player), done });
1501
1503
  options.calc.capped(player);
1502
1504
  return null;
1503
1505
  }
@@ -1519,8 +1521,8 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
1519
1521
 
1520
1522
  if (!showdown or damage > 0) {
1521
1523
  stored.hp -= damage;
1522
- // Pokémon Showdown uses damageOf here but its not relevant in Generation I
1523
- try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
1524
+ // Pokémon Showdown uses damage [of] here but its not relevant in Generation I
1525
+ try options.log.damage(.{ ident, stored, if (brn) Damage.Burn else Damage.Poison });
1524
1526
  }
1525
1527
  }
1526
1528
 
@@ -1545,14 +1547,14 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
1545
1547
  const amount = @min(damage, stored.hp);
1546
1548
  stored.hp -= amount;
1547
1549
 
1548
- // As above, Pokémon Showdown uses damageOf but its not relevant
1549
- if (amount > 0) try options.log.damage(ident, stored, .LeechSeed);
1550
+ // As above, Pokémon Showdown uses damage [of] but its not relevant
1551
+ if (amount > 0) try options.log.damage(.{ ident, stored, .LeechSeed });
1550
1552
 
1551
1553
  const before = foe_stored.hp;
1552
1554
  // Uncapped damage is added back to the foe
1553
1555
  foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
1554
1556
  // Pokémon Showdown uses the less specific heal here instead of drain... because reasons?
1555
- if (foe_stored.hp > before) try options.log.heal(foe_ident, foe_stored, .Silent);
1557
+ if (foe_stored.hp > before) try options.log.heal(.{ foe_ident, foe_stored, .Silent });
1556
1558
  }
1557
1559
  }
1558
1560
 
@@ -1563,18 +1565,18 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
1563
1565
  battle.turn += 1;
1564
1566
 
1565
1567
  if (showdown and pkmn.options.ebc and checkEBC(battle)) {
1566
- try options.log.tie();
1568
+ try options.log.tie(.{});
1567
1569
  return Result.Tie;
1568
1570
  }
1569
1571
 
1570
1572
  if (showdown and battle.turn >= 1000) {
1571
- try options.log.tie();
1573
+ try options.log.tie(.{});
1572
1574
  return Result.Tie;
1573
1575
  } else if (battle.turn >= 65535) {
1574
1576
  return Result.Error;
1575
1577
  }
1576
1578
 
1577
- try options.log.turn(battle.turn);
1579
+ try options.log.turn(.{battle.turn});
1578
1580
 
1579
1581
  return Result.Default;
1580
1582
  }
@@ -1634,9 +1636,9 @@ fn checkEBC(battle: anytype) bool {
1634
1636
  }
1635
1637
 
1636
1638
  fn disabled(side: *Side, ident: ID, options: anytype) !bool {
1637
- if (side.active.volatiles.disabled_move != 0) {
1639
+ if (side.active.volatiles.disable_move != 0) {
1638
1640
  // A Pokémon that transforms after being disabled may end up with less move slots
1639
- const m = side.active.moves[side.active.volatiles.disabled_move - 1].id;
1641
+ const m = side.active.moves[side.active.volatiles.disable_move - 1].id;
1640
1642
  // side.last_selected_move can be Struggle here on Pokemon Showdown we need an extra check
1641
1643
  const last = if (showdown and m == .Bide and side.active.volatiles.Bide)
1642
1644
  m
@@ -1644,14 +1646,14 @@ fn disabled(side: *Side, ident: ID, options: anytype) !bool {
1644
1646
  side.last_selected_move;
1645
1647
  if (m != .None and m == last) {
1646
1648
  side.active.volatiles.Charging = false;
1647
- try options.log.disabled(ident, last);
1649
+ try options.log.cant(.{ ident, .Disable, last });
1648
1650
  return true;
1649
1651
  }
1650
1652
  }
1651
1653
  return false;
1652
1654
  }
1653
1655
 
1654
- inline fn buildRage(battle: anytype, who: Player, options: anytype) !void {
1656
+ fn buildRage(battle: anytype, who: Player, options: anytype) !void {
1655
1657
  const side = battle.side(who);
1656
1658
  if (side.active.volatiles.Rage and side.active.boosts.atk < 6) {
1657
1659
  try Effects.boost(battle, who, Move.get(.Rage), options);
@@ -1669,7 +1671,7 @@ fn handleThrashing(battle: anytype, active: *ActivePokemon, player: Player, opti
1669
1671
  return true;
1670
1672
  }
1671
1673
 
1672
- inline fn onBegin(
1674
+ fn onBegin(
1673
1675
  battle: anytype,
1674
1676
  player: Player,
1675
1677
  move: Move.Data,
@@ -1699,7 +1701,7 @@ inline fn onBegin(
1699
1701
  };
1700
1702
  }
1701
1703
 
1702
- inline fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1704
+ fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1703
1705
  assert(move.effect.onEnd());
1704
1706
  return switch (move.effect) {
1705
1707
  .Bide => Effects.bide(battle, player, options),
@@ -1715,7 +1717,7 @@ inline fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anyty
1715
1717
  };
1716
1718
  }
1717
1719
 
1718
- inline fn alwaysHappens(
1720
+ fn alwaysHappens(
1719
1721
  battle: anytype,
1720
1722
  player: Player,
1721
1723
  move: Move.Data,
@@ -1734,7 +1736,7 @@ inline fn alwaysHappens(
1734
1736
  };
1735
1737
  }
1736
1738
 
1737
- inline fn moveEffect(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1739
+ fn moveEffect(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
1738
1740
  return switch (move.effect) {
1739
1741
  .BurnChance1, .BurnChance2 => Effects.burnChance(battle, player, move, options),
1740
1742
  .ConfusionChance => Effects.confusion(battle, player, move, options),
@@ -1761,7 +1763,7 @@ pub const Effects = struct {
1761
1763
  side.active.volatiles.state = 0;
1762
1764
  side.active.volatiles.attacks = Rolls.attackingDuration(battle, player, options);
1763
1765
 
1764
- try options.log.start(battle.active(player), .Bide);
1766
+ try options.log.start(.{ battle.active(player), .Bide });
1765
1767
  }
1766
1768
 
1767
1769
  fn burnChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
@@ -1775,11 +1777,11 @@ pub const Effects = struct {
1775
1777
  // GLITCH: Freeze top move selection desync can occur if thawed Pokémon is slower
1776
1778
  if (Status.is(foe_stored.status, .FRZ)) {
1777
1779
  assert(move.type == .Fire);
1778
- try options.log.curestatus(
1780
+ try options.log.curestatus(.{
1779
1781
  battle.active(player.foe()),
1780
1782
  foe_stored.status,
1781
1783
  .Message,
1782
- );
1784
+ });
1783
1785
  foe_stored.status = 0;
1784
1786
  }
1785
1787
  return;
@@ -1793,7 +1795,7 @@ pub const Effects = struct {
1793
1795
  foe_stored.status = Status.init(.BRN);
1794
1796
  foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
1795
1797
 
1796
- try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
1798
+ try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
1797
1799
  }
1798
1800
 
1799
1801
  fn charge(battle: anytype, player: Player, options: anytype) !void {
@@ -1803,8 +1805,8 @@ pub const Effects = struct {
1803
1805
  volatiles.Charging = true;
1804
1806
  const move = side.last_selected_move;
1805
1807
  if (move == .Fly or move == .Dig) volatiles.Invulnerable = true;
1806
- try options.log.laststill();
1807
- try options.log.prepare(battle.active(player), move);
1808
+ try options.log.laststill(.{});
1809
+ try options.log.prepare(.{ battle.active(player), move });
1808
1810
  }
1809
1811
 
1810
1812
  fn confusion(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
@@ -1815,17 +1817,11 @@ pub const Effects = struct {
1815
1817
  if (!try Rolls.confusionChance(battle, player, options)) return;
1816
1818
  } else {
1817
1819
  if (showdown) {
1818
- if (!try checkHit(battle, player, move, options)) {
1819
- return;
1820
- } else if (sub) {
1821
- return options.log.fail(battle.active(player.foe()), .None);
1822
- }
1820
+ if (!try checkHit(battle, player, move, options)) return;
1821
+ if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
1823
1822
  } else {
1824
- if (sub) {
1825
- return options.log.fail(battle.active(player.foe()), .None);
1826
- } else if (!try checkHit(battle, player, move, options)) {
1827
- return;
1828
- }
1823
+ if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
1824
+ if (!try checkHit(battle, player, move, options)) return;
1829
1825
  }
1830
1826
  try options.chance.commit(player, .hit);
1831
1827
  }
@@ -1834,23 +1830,24 @@ pub const Effects = struct {
1834
1830
  foe.active.volatiles.Confusion = true;
1835
1831
  foe.active.volatiles.confusion = Rolls.confusionDuration(battle, player, false, options);
1836
1832
 
1837
- try options.log.start(battle.active(player.foe()), .Confusion);
1833
+ try options.log.start(.{ battle.active(player.foe()), .Confusion });
1838
1834
  }
1839
1835
 
1840
1836
  fn conversion(battle: anytype, player: Player, options: anytype) !void {
1841
1837
  const foe = battle.foe(player);
1842
1838
 
1843
1839
  if (foe.active.volatiles.Invulnerable) {
1844
- try options.log.lastmiss();
1845
- return options.log.miss(battle.active(player));
1840
+ try options.log.lastmiss(.{});
1841
+ return options.log.miss(.{battle.active(player)});
1846
1842
  }
1847
1843
 
1848
1844
  battle.side(player).active.types = foe.active.types;
1849
- return options.log.typechange(
1845
+ return options.log.start(.{
1850
1846
  battle.active(player),
1847
+ .TypeChange,
1851
1848
  foe.active.types,
1852
1849
  battle.active(player.foe()),
1853
- );
1850
+ });
1854
1851
  }
1855
1852
 
1856
1853
  fn disable(battle: anytype, player: Player, move: Move.Data, options: anytype) !?Result {
@@ -1864,8 +1861,8 @@ pub const Effects = struct {
1864
1861
  try options.chance.commit(player, .hit);
1865
1862
  }
1866
1863
 
1867
- if (volatiles.disabled_move != 0) {
1868
- try options.log.fail(foe_ident, .None);
1864
+ if (volatiles.disable_move != 0) {
1865
+ try options.log.fail(.{ foe_ident, .None });
1869
1866
  return null;
1870
1867
  }
1871
1868
 
@@ -1881,7 +1878,7 @@ pub const Effects = struct {
1881
1878
  // Technically this is still considered simply a "miss" on the cartridge,
1882
1879
  // but diverging from Pokémon Showdown here would mostly just be pedantic
1883
1880
  if (n == 0) {
1884
- try options.log.fail(foe_ident, .None);
1881
+ try options.log.fail(.{ foe_ident, .None });
1885
1882
  return null;
1886
1883
  } else if (err) {
1887
1884
  // GLITCH: Transform + Mirror Move / Metronome PP softlock
@@ -1889,18 +1886,18 @@ pub const Effects = struct {
1889
1886
  return Result.Error;
1890
1887
  }
1891
1888
 
1892
- volatiles.disabled_move =
1889
+ volatiles.disable_move =
1893
1890
  @intCast(try Rolls.moveSlot(battle, player, &foe.active.moves, n, options));
1894
- volatiles.disabled_duration = Rolls.disableDuration(battle, player, options);
1891
+ volatiles.disable_duration = Rolls.disableDuration(battle, player, options);
1895
1892
 
1896
- const id = foe.active.move(volatiles.disabled_move).id;
1897
- try options.log.startEffect(foe_ident, .Disable, id);
1893
+ const id = foe.active.move(volatiles.disable_move).id;
1894
+ try options.log.start(.{ foe_ident, .Disable, id });
1898
1895
  return null;
1899
1896
  }
1900
1897
 
1901
1898
  fn drainHP(battle: anytype, player: Player, options: anytype) !void {
1902
- var side = battle.side(player);
1903
- var stored = side.stored();
1899
+ var stored = battle.side(player).stored();
1900
+ const ident = battle.active(player);
1904
1901
 
1905
1902
  if (battle.last_damage == 0) {
1906
1903
  assert(showdown);
@@ -1913,7 +1910,7 @@ pub const Effects = struct {
1913
1910
  if (stored.hp == stored.stats.hp) return;
1914
1911
  stored.hp = @min(stored.stats.hp, stored.hp + drain);
1915
1912
 
1916
- try options.log.drain(battle.active(player), stored, battle.active(player.foe()));
1913
+ try options.log.heal(.{ ident, stored, .Drain, battle.active(player.foe()) });
1917
1914
  }
1918
1915
 
1919
1916
  fn explode(battle: anytype, player: Player) !void {
@@ -1945,7 +1942,7 @@ pub const Effects = struct {
1945
1942
  if (side.active.volatiles.FocusEnergy) return;
1946
1943
  side.active.volatiles.FocusEnergy = true;
1947
1944
 
1948
- try options.log.start(battle.active(player), .FocusEnergy);
1945
+ try options.log.start(.{ battle.active(player), .FocusEnergy });
1949
1946
  }
1950
1947
 
1951
1948
  fn freezeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
@@ -1967,7 +1964,7 @@ pub const Effects = struct {
1967
1964
  foe_stored.status = Status.init(.FRZ);
1968
1965
  // GLITCH: Hyper Beam recharging status is not cleared
1969
1966
 
1970
- try options.log.status(foe_ident, foe_stored.status, .None);
1967
+ try options.log.status(.{ foe_ident, foe_stored.status, .None });
1971
1968
  }
1972
1969
 
1973
1970
  fn haze(battle: anytype, player: Player, options: anytype) !void {
@@ -1987,8 +1984,8 @@ pub const Effects = struct {
1987
1984
  side.active.stats = side_stored.stats;
1988
1985
  foe.active.stats = foe_stored.stats;
1989
1986
 
1990
- try log.activate(player_ident, .Haze);
1991
- try log.clearallboost();
1987
+ try log.activate(.{ player_ident, .Haze });
1988
+ try log.clearallboost(.{});
1992
1989
 
1993
1990
  // Pokémon Showdown clears P1 then P2 instead of status -> side -> foe
1994
1991
  if (showdown) {
@@ -1996,11 +1993,11 @@ pub const Effects = struct {
1996
1993
  const p: Player = @enumFromInt(i);
1997
1994
  // Pokémon Showdown incorrectly does not prevent sleep/freeze from moving
1998
1995
  if (p != player and Status.any(s.stored().status)) {
1999
- try log.curestatus(foe_ident, foe_stored.status, .Silent);
1996
+ try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
2000
1997
  s.stored().status = 0;
2001
1998
  } else if (showdown and s.stored().status == Status.TOX) {
2002
1999
  s.stored().status = Status.init(.PSN);
2003
- try log.status(battle.active(p), s.stored().status, .None);
2000
+ try log.status(.{ battle.active(p), s.stored().status, .None });
2004
2001
  }
2005
2002
  try clearVolatiles(battle, p, p != player, options);
2006
2003
  }
@@ -2009,7 +2006,7 @@ pub const Effects = struct {
2009
2006
  if (Status.is(foe_stored.status, .FRZ) or Status.is(foe_stored.status, .SLP)) {
2010
2007
  foe.last_selected_move = .SKIP_TURN;
2011
2008
  }
2012
- try log.curestatus(foe_ident, foe_stored.status, .Silent);
2009
+ try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
2013
2010
  foe_stored.status = 0;
2014
2011
  }
2015
2012
  try clearVolatiles(battle, player, false, options);
@@ -2025,7 +2022,7 @@ pub const Effects = struct {
2025
2022
  // GLITCH: HP recovery move failure glitches
2026
2023
  const delta = stored.stats.hp - stored.hp;
2027
2024
  if (delta == 0 or (delta & 255 == 255 and stored.hp % 256 != 0)) {
2028
- return try options.log.fail(ident, .None);
2025
+ return options.log.fail(.{ ident, .None });
2029
2026
  }
2030
2027
 
2031
2028
  const rest = side.last_selected_move == .Rest;
@@ -2033,17 +2030,17 @@ pub const Effects = struct {
2033
2030
  // Adding the sleep status runs the sleep condition handler to roll duration
2034
2031
  if (showdown) battle.rng.advance(1);
2035
2032
  stored.status = Status.slf(2);
2036
- try options.log.statusFrom(ident, stored.status, Move.Rest);
2033
+ try options.log.status(.{ ident, stored.status, .From, Move.Rest });
2037
2034
  stored.hp = stored.stats.hp;
2038
2035
  } else {
2039
2036
  stored.hp = @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2));
2040
2037
  }
2041
- try options.log.heal(ident, stored, if (rest) Heal.Silent else Heal.None);
2038
+ try options.log.heal(.{ ident, stored, if (rest) Heal.Silent else Heal.None });
2042
2039
  }
2043
2040
 
2044
2041
  fn hyperBeam(battle: anytype, player: Player, options: anytype) !void {
2045
2042
  battle.side(player).active.volatiles.Recharging = true;
2046
- try options.log.mustrecharge(battle.active(player));
2043
+ try options.log.mustrecharge(.{battle.active(player)});
2047
2044
  }
2048
2045
 
2049
2046
  fn leechSeed(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
@@ -2052,33 +2049,32 @@ pub const Effects = struct {
2052
2049
  if (showdown) {
2053
2050
  // Invulnerability trumps type immunity on Pokémon Showdown
2054
2051
  if (!foe.active.volatiles.Invulnerable and foe.active.types.includes(.Grass)) {
2055
- return options.log.immune(battle.active(player.foe()), .None);
2052
+ return options.log.immune(.{ battle.active(player.foe()), .None });
2056
2053
  }
2057
2054
  if (!try checkHit(battle, player, move, options)) return;
2058
2055
  if (foe.active.volatiles.LeechSeed) return;
2059
2056
  } else {
2060
2057
  if (!try checkHit(battle, player, move, options)) return;
2061
2058
  if (foe.active.types.includes(.Grass) or foe.active.volatiles.LeechSeed) {
2062
- try options.log.lastmiss();
2063
- return options.log.miss(battle.active(player));
2059
+ try options.log.lastmiss(.{});
2060
+ return options.log.miss(.{battle.active(player)});
2064
2061
  }
2065
2062
  }
2066
2063
 
2067
2064
  try options.chance.commit(player, .hit);
2068
2065
  foe.active.volatiles.LeechSeed = true;
2069
2066
 
2070
- try options.log.start(battle.active(player.foe()), .LeechSeed);
2067
+ try options.log.start(.{ battle.active(player.foe()), .LeechSeed });
2071
2068
  }
2072
2069
 
2073
2070
  fn lightScreen(battle: anytype, player: Player, options: anytype) !void {
2074
2071
  var side = battle.side(player);
2072
+ const ident = battle.active(player);
2075
2073
 
2076
- if (side.active.volatiles.LightScreen) {
2077
- return options.log.fail(battle.active(player), .None);
2078
- }
2074
+ if (side.active.volatiles.LightScreen) return options.log.fail(.{ ident, .None });
2079
2075
  side.active.volatiles.LightScreen = true;
2080
2076
 
2081
- try options.log.start(battle.active(player), .LightScreen);
2077
+ try options.log.start(.{ ident, .LightScreen });
2082
2078
  }
2083
2079
 
2084
2080
  fn mimic(battle: anytype, player: Player, move: Move.Data, mslot: u8, options: anytype) !void {
@@ -2107,7 +2103,7 @@ pub const Effects = struct {
2107
2103
  // Invulnerable foes or 1/256 miss can trigger |-miss| instead of |-fail|
2108
2104
  if (!try checkHit(battle, player, move, options)) return;
2109
2105
  try options.chance.commit(player, .hit);
2110
- return try options.log.fail(battle.active(player.foe()), .None);
2106
+ return options.log.fail(.{ battle.active(player.foe()), .None });
2111
2107
  }
2112
2108
  }
2113
2109
  if (!try checkHit(battle, player, move, options)) return;
@@ -2116,7 +2112,7 @@ pub const Effects = struct {
2116
2112
  const rslot = try Rolls.moveSlot(battle, player, &foe.active.moves, 0, options);
2117
2113
  side.active.move(oslot).id = foe.active.move(rslot).id;
2118
2114
 
2119
- try options.log.startEffect(battle.active(player), .Mimic, side.active.move(oslot).id);
2115
+ try options.log.start(.{ battle.active(player), .Mimic, side.active.move(oslot).id });
2120
2116
  }
2121
2117
 
2122
2118
  fn mist(battle: anytype, player: Player, options: anytype) !void {
@@ -2125,7 +2121,7 @@ pub const Effects = struct {
2125
2121
  if (side.active.volatiles.Mist) return;
2126
2122
  side.active.volatiles.Mist = true;
2127
2123
 
2128
- try options.log.start(battle.active(player), .Mist);
2124
+ try options.log.start(.{ battle.active(player), .Mist });
2129
2125
  }
2130
2126
 
2131
2127
  fn multiHit(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
@@ -2151,17 +2147,18 @@ pub const Effects = struct {
2151
2147
 
2152
2148
  if (showdown) {
2153
2149
  // Invulnerability trumps type immunity on Pokémon Showdown
2154
- if (immune and !foe.active.volatiles.Invulnerable) return log.immune(foe_ident, .None);
2155
- if (!try checkHit(battle, player, move, options)) return;
2150
+ if (immune and !foe.active.volatiles.Invulnerable) {
2151
+ return log.immune(.{ foe_ident, .None });
2152
+ } else if (!try checkHit(battle, player, move, options)) return;
2156
2153
  }
2157
2154
  if (Status.any(foe_stored.status)) {
2158
- return log.fail(
2155
+ return log.fail(.{
2159
2156
  foe_ident,
2160
- if (Status.is(foe_stored.status, .PAR)) .Paralysis else .None,
2161
- );
2157
+ if (Status.is(foe_stored.status, .PAR)) Fail.Paralysis else Fail.None,
2158
+ });
2162
2159
  }
2163
2160
  if (!showdown) {
2164
- if (immune) return log.immune(foe_ident, .None);
2161
+ if (immune) return log.immune(.{ foe_ident, .None });
2165
2162
  if (!try checkHit(battle, player, move, options)) return;
2166
2163
  }
2167
2164
  try options.chance.commit(player, .hit);
@@ -2169,7 +2166,7 @@ pub const Effects = struct {
2169
2166
  foe_stored.status = Status.init(.PAR);
2170
2167
  foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
2171
2168
 
2172
- try log.status(foe_ident, foe_stored.status, .None);
2169
+ try log.status(.{ foe_ident, foe_stored.status, .None });
2173
2170
  }
2174
2171
 
2175
2172
  fn paralyzeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
@@ -2191,12 +2188,12 @@ pub const Effects = struct {
2191
2188
  foe_stored.status = Status.init(.PAR);
2192
2189
  foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
2193
2190
 
2194
- try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
2191
+ try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
2195
2192
  }
2196
2193
 
2197
2194
  fn payDay(battle: anytype, player: Player, options: anytype) !void {
2198
2195
  if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
2199
- try options.log.fieldactivate();
2196
+ try options.log.fieldactivate(.{});
2200
2197
  }
2201
2198
  }
2202
2199
 
@@ -2211,23 +2208,23 @@ pub const Effects = struct {
2211
2208
  return;
2212
2209
  } else if (foe.active.volatiles.Substitute) {
2213
2210
  if (move.effect != .Poison) return;
2214
- return log.fail(foe_ident, .None);
2211
+ return log.fail(.{ foe_ident, .None });
2215
2212
  } else if (Status.any(foe_stored.status)) {
2216
2213
  if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
2217
2214
  // Pokémon Showdown considers Toxic to be a status even in Generation I and so
2218
2215
  // will not include a fail reason for Toxic vs. Poison or vice-versa...
2219
- return log.fail(foe_ident, if (Status.is(foe_stored.status, .PSN))
2216
+ return log.fail(.{ foe_ident, if (Status.is(foe_stored.status, .PSN))
2220
2217
  if (!showdown)
2221
- .Poison
2218
+ Fail.Poison
2222
2219
  else if (toxic == (foe_stored.status == Status.TOX))
2223
- if (toxic) .Toxic else .Poison
2220
+ if (toxic) Fail.Toxic else Fail.Poison
2224
2221
  else
2225
- .None
2222
+ Fail.None
2226
2223
  else
2227
- .None);
2224
+ Fail.None });
2228
2225
  } else if (foe.active.types.includes(.Poison)) {
2229
2226
  if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
2230
- return log.immune(foe_ident, .None);
2227
+ return log.immune(.{ foe_ident, .None });
2231
2228
  }
2232
2229
 
2233
2230
  if (move.effect == .Poison) {
@@ -2246,7 +2243,7 @@ pub const Effects = struct {
2246
2243
  foe.active.volatiles.toxic = 0;
2247
2244
  }
2248
2245
 
2249
- try log.status(foe_ident, foe_stored.status, .None);
2246
+ try log.status(.{ foe_ident, foe_stored.status, .None });
2250
2247
  }
2251
2248
 
2252
2249
  fn rage(battle: anytype, player: Player) !void {
@@ -2266,22 +2263,23 @@ pub const Effects = struct {
2266
2263
  @as(u8, if (side.last_selected_move == .Struggle) 2 else 4), 1));
2267
2264
  stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
2268
2265
 
2269
- try options.log.damageOf(
2266
+ try options.log.damage(.{
2270
2267
  battle.active(player),
2271
2268
  stored,
2272
2269
  .RecoilOf,
2273
2270
  battle.active(player.foe()),
2274
- );
2271
+ });
2275
2272
  if (showdown and stored.hp == 0) residual.* = false;
2276
2273
  }
2277
2274
 
2278
2275
  fn reflect(battle: anytype, player: Player, options: anytype) !void {
2279
2276
  var side = battle.side(player);
2277
+ const ident = battle.active(player);
2280
2278
 
2281
- if (side.active.volatiles.Reflect) return options.log.fail(battle.active(player), .None);
2279
+ if (side.active.volatiles.Reflect) return options.log.fail(.{ ident, .None });
2282
2280
  side.active.volatiles.Reflect = true;
2283
2281
 
2284
- try options.log.start(battle.active(player), .Reflect);
2282
+ try options.log.start(.{ ident, .Reflect });
2285
2283
  }
2286
2284
 
2287
2285
  fn sleep(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
@@ -2295,10 +2293,10 @@ pub const Effects = struct {
2295
2293
  // the Sleep Clause Mod might activate, causing us to not actually bypass
2296
2294
  } else {
2297
2295
  if (Status.any(foe_stored.status)) {
2298
- return options.log.fail(
2296
+ return options.log.fail(.{
2299
2297
  foe_ident,
2300
- if (Status.is(foe_stored.status, .SLP)) .Sleep else .None,
2301
- );
2298
+ if (Status.is(foe_stored.status, .SLP)) Fail.Sleep else Fail.None,
2299
+ });
2302
2300
  }
2303
2301
  // If checkHit in doMove didn't return true Pokémon Showdown wouldn't be in here
2304
2302
  if (!showdown and !try checkHit(battle, player, move, options)) return;
@@ -2315,18 +2313,17 @@ pub const Effects = struct {
2315
2313
 
2316
2314
  foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
2317
2315
  const last = battle.side(player).last_selected_move;
2318
- try options.log.statusFrom(foe_ident, foe_stored.status, last);
2316
+ try options.log.status(.{ foe_ident, foe_stored.status, .From, last });
2319
2317
  }
2320
2318
 
2321
2319
  fn splash(battle: anytype, player: Player, options: anytype) !void {
2322
- try options.log.activate(battle.active(player), .Splash);
2320
+ try options.log.activate(.{ battle.active(player), .Splash });
2323
2321
  }
2324
2322
 
2325
2323
  fn substitute(battle: anytype, player: Player, residual: *bool, options: anytype) !void {
2326
2324
  var side = battle.side(player);
2327
2325
  if (side.active.volatiles.Substitute) {
2328
- try options.log.fail(battle.active(player), .Substitute);
2329
- return;
2326
+ return options.log.fail(.{ battle.active(player), .Substitute });
2330
2327
  }
2331
2328
 
2332
2329
  assert(side.stored().stats.hp <= 1023);
@@ -2338,17 +2335,16 @@ pub const Effects = struct {
2338
2335
  // avoid having to convert to floating point)
2339
2336
  const required_hp = if (showdown) @divFloor(side.stored().stats.hp - 1, 4) + 1 else hp;
2340
2337
  if (side.stored().hp < required_hp) {
2341
- try options.log.fail(battle.active(player), .Weak);
2342
- return;
2338
+ return options.log.fail(.{ battle.active(player), .Weak });
2343
2339
  }
2344
2340
 
2345
2341
  // GLITCH: can leave the user with 0 HP (faints later) because didn't check '<=' above
2346
2342
  side.stored().hp -= hp;
2347
2343
  side.active.volatiles.substitute = hp + 1;
2348
2344
  side.active.volatiles.Substitute = true;
2349
- try options.log.start(battle.active(player), .Substitute);
2345
+ try options.log.start(.{ battle.active(player), .Substitute });
2350
2346
  if (hp > 0) {
2351
- try options.log.damage(battle.active(player), side.stored(), .None);
2347
+ try options.log.damage(.{ battle.active(player), side.stored(), .None });
2352
2348
  if (showdown and side.stored().hp == 0) residual.* = false;
2353
2349
  }
2354
2350
  }
@@ -2362,8 +2358,8 @@ pub const Effects = struct {
2362
2358
  }
2363
2359
  battle.last_damage = 0;
2364
2360
  } else {
2365
- try options.log.fail(battle.active(player), .None);
2366
- try options.log.laststill();
2361
+ try options.log.fail(.{ battle.active(player), .None });
2362
+ try options.log.laststill(.{});
2367
2363
  }
2368
2364
  }
2369
2365
 
@@ -2402,7 +2398,7 @@ pub const Effects = struct {
2402
2398
  side.active.moves[i].pp = if (m.id != .None) 5 else 0;
2403
2399
  }
2404
2400
 
2405
- try options.log.transform(battle.active(player), foe_ident);
2401
+ try options.log.transform(.{ battle.active(player), foe_ident });
2406
2402
  }
2407
2403
 
2408
2404
  fn binding(battle: anytype, player: Player, rewrap: bool, options: anytype) !void {
@@ -2432,87 +2428,87 @@ pub const Effects = struct {
2432
2428
  switch (move.effect) {
2433
2429
  .AttackUp1, .AttackUp2, .Rage => {
2434
2430
  assert(boosts.atk >= -6 and boosts.atk <= 6);
2435
- if (boosts.atk == 6) return try log.fail(ident, .None);
2431
+ if (boosts.atk == 6) return log.fail(.{ ident, .None });
2436
2432
  const n: u2 = if (move.effect == .AttackUp2) 2 else 1;
2437
2433
  boosts.atk = @as(i4, @intCast(@min(6, @as(i8, boosts.atk) + n)));
2438
- const reason = if (move.effect == .Rage) Boost.Rage else Boost.Attack;
2434
+ const reason: Boost = if (move.effect == .Rage) .Rage else .Attack;
2439
2435
  if (stats.atk == MAX_STAT_VALUE) {
2440
2436
  boosts.atk -= 1;
2441
2437
  if (showdown) {
2442
- try log.boost(ident, reason, n);
2443
- try log.boost(ident, Boost.Attack, -1);
2438
+ try log.boost(.{ ident, reason, n });
2439
+ try log.boost(.{ ident, .Attack, -1 });
2444
2440
  if (move.effect == .Rage) return;
2445
2441
  }
2446
- return try log.fail(ident, .None);
2442
+ return log.fail(.{ ident, .None });
2447
2443
  }
2448
2444
  var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
2449
2445
  const stat = unmodifiedStats(battle, side).atk;
2450
2446
  stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
2451
- try log.boost(ident, reason, n);
2447
+ try log.boost(.{ ident, reason, n });
2452
2448
  // Pokémon Showdown doesn't re-apply status modifiers after Rage boosts
2453
2449
  if (showdown and move.effect == .Rage) return;
2454
2450
  },
2455
2451
  .DefenseUp1, .DefenseUp2 => {
2456
2452
  assert(boosts.def >= -6 and boosts.def <= 6);
2457
- if (boosts.def == 6) return try log.fail(ident, .None);
2453
+ if (boosts.def == 6) return log.fail(.{ ident, .None });
2458
2454
  const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
2459
2455
  boosts.def = @intCast(@min(6, @as(i8, boosts.def) + n));
2460
2456
  if (stats.def == MAX_STAT_VALUE) {
2461
2457
  boosts.def -= 1;
2462
2458
  if (showdown) {
2463
- try log.boost(ident, .Defense, n);
2464
- try log.boost(ident, .Defense, -1);
2459
+ try log.boost(.{ ident, .Defense, n });
2460
+ try log.boost(.{ ident, .Defense, -1 });
2465
2461
  }
2466
- return try log.fail(ident, .None);
2462
+ return log.fail(.{ ident, .None });
2467
2463
  }
2468
2464
  var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
2469
2465
  const stat = unmodifiedStats(battle, side).def;
2470
2466
  stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
2471
- try log.boost(ident, .Defense, n);
2467
+ try log.boost(.{ ident, .Defense, n });
2472
2468
  },
2473
2469
  .SpeedUp2 => {
2474
2470
  assert(boosts.spe >= -6 and boosts.spe <= 6);
2475
- if (boosts.spe == 6) return try log.fail(ident, .None);
2471
+ if (boosts.spe == 6) return log.fail(.{ ident, .None });
2476
2472
  boosts.spe = @intCast(@min(6, @as(i8, boosts.spe) + 2));
2477
2473
  if (stats.spe == MAX_STAT_VALUE) {
2478
2474
  boosts.spe -= 1;
2479
2475
  if (showdown) {
2480
- try log.boost(ident, .Speed, 2);
2481
- try log.boost(ident, .Speed, -1);
2476
+ try log.boost(.{ ident, .Speed, 2 });
2477
+ try log.boost(.{ ident, .Speed, -1 });
2482
2478
  }
2483
- return try log.fail(ident, .None);
2479
+ return log.fail(.{ ident, .None });
2484
2480
  }
2485
2481
  var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
2486
2482
  const stat = unmodifiedStats(battle, side).spe;
2487
2483
  stats.spe = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
2488
- try log.boost(ident, .Speed, 2);
2484
+ try log.boost(.{ ident, .Speed, 2 });
2489
2485
  },
2490
2486
  .SpecialUp1, .SpecialUp2 => {
2491
2487
  assert(boosts.spc >= -6 and boosts.spc <= 6);
2492
- if (boosts.spc == 6) return try log.fail(ident, .None);
2488
+ if (boosts.spc == 6) return log.fail(.{ ident, .None });
2493
2489
  const n: u2 = if (move.effect == .SpecialUp2) 2 else 1;
2494
2490
  boosts.spc = @intCast(@min(6, @as(i8, boosts.spc) + n));
2495
2491
  if (stats.spc == MAX_STAT_VALUE) {
2496
2492
  boosts.spc -= 1;
2497
2493
  if (showdown) {
2498
- try log.boost(ident, .SpecialAttack, n);
2499
- try log.boost(ident, .SpecialAttack, -1);
2500
- try log.boost(ident, .SpecialDefense, n);
2501
- try log.boost(ident, .SpecialDefense, -1);
2494
+ try log.boost(.{ ident, .SpecialAttack, n });
2495
+ try log.boost(.{ ident, .SpecialAttack, -1 });
2496
+ try log.boost(.{ ident, .SpecialDefense, n });
2497
+ try log.boost(.{ ident, .SpecialDefense, -1 });
2502
2498
  }
2503
- return try log.fail(ident, .None);
2499
+ return log.fail(.{ ident, .None });
2504
2500
  }
2505
2501
  var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
2506
2502
  const stat = unmodifiedStats(battle, side).spc;
2507
2503
  stats.spc = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
2508
- try log.boost(ident, .SpecialAttack, n);
2509
- try log.boost(ident, .SpecialDefense, n);
2504
+ try log.boost(.{ ident, .SpecialAttack, n });
2505
+ try log.boost(.{ ident, .SpecialDefense, n });
2510
2506
  },
2511
2507
  .EvasionUp1 => {
2512
2508
  assert(boosts.evasion >= -6 and boosts.evasion <= 6);
2513
- if (boosts.evasion == 6) return try log.fail(ident, .None);
2509
+ if (boosts.evasion == 6) return log.fail(.{ ident, .None });
2514
2510
  boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
2515
- try log.boost(ident, .Evasion, 1);
2511
+ try log.boost(.{ ident, .Evasion, 1 });
2516
2512
  },
2517
2513
  else => unreachable,
2518
2514
  }
@@ -2527,7 +2523,7 @@ pub const Effects = struct {
2527
2523
  const foe_ident = battle.active(player.foe());
2528
2524
  const secondary = move.effect.isStatDownChance();
2529
2525
 
2530
- if (foe.active.volatiles.Substitute) return if (!secondary) log.fail(foe_ident, .None);
2526
+ if (foe.active.volatiles.Substitute) return if (!secondary) log.fail(.{ foe_ident, .None });
2531
2527
 
2532
2528
  if (secondary) {
2533
2529
  const proc = try Rolls.unboost(battle, player, options);
@@ -2545,82 +2541,82 @@ pub const Effects = struct {
2545
2541
  switch (move.effect) {
2546
2542
  .AttackDown1, .AttackDownChance => {
2547
2543
  assert(boosts.atk >= -6 and boosts.atk <= 6);
2548
- if (boosts.atk == -6) return if (fail) try log.fail(foe_ident, .None);
2544
+ if (boosts.atk == -6) return if (fail) try log.fail(.{ foe_ident, .None });
2549
2545
  boosts.atk = @intCast(@max(-6, @as(i8, boosts.atk) - 1));
2550
2546
  if (stats.atk == 1) {
2551
2547
  boosts.atk += 1;
2552
2548
  if (showdown) {
2553
- try log.boost(foe_ident, .Attack, -1);
2554
- try log.boost(foe_ident, .Attack, 1);
2549
+ try log.boost(.{ foe_ident, .Attack, -1 });
2550
+ try log.boost(.{ foe_ident, .Attack, 1 });
2555
2551
  }
2556
- return try log.fail(foe_ident, .None);
2552
+ return log.fail(.{ foe_ident, .None });
2557
2553
  }
2558
2554
  var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
2559
2555
  const stat = unmodifiedStats(battle, foe).atk;
2560
2556
  stats.atk = @max(1, stat * mod[0] / mod[1]);
2561
- try log.boost(foe_ident, .Attack, -1);
2557
+ try log.boost(.{ foe_ident, .Attack, -1 });
2562
2558
  },
2563
2559
  .DefenseDown1, .DefenseDown2, .DefenseDownChance => {
2564
2560
  assert(boosts.def >= -6 and boosts.def <= 6);
2565
- if (boosts.def == -6) return if (fail) try log.fail(foe_ident, .None);
2561
+ if (boosts.def == -6) return if (fail) try log.fail(.{ foe_ident, .None });
2566
2562
  const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
2567
2563
  boosts.def = @intCast(@max(-6, @as(i8, boosts.def) - n));
2568
2564
  if (stats.def == 1) {
2569
2565
  boosts.def += 1;
2570
2566
  if (showdown) {
2571
- try log.boost(foe_ident, .Defense, -@as(i8, n));
2572
- try log.boost(foe_ident, .Defense, 1);
2567
+ try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
2568
+ try log.boost(.{ foe_ident, .Defense, 1 });
2573
2569
  }
2574
- return try log.fail(foe_ident, .None);
2570
+ return log.fail(.{ foe_ident, .None });
2575
2571
  }
2576
2572
  var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
2577
2573
  const stat = unmodifiedStats(battle, foe).def;
2578
2574
  stats.def = @max(1, stat * mod[0] / mod[1]);
2579
- try log.boost(foe_ident, .Defense, -@as(i8, n));
2575
+ try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
2580
2576
  },
2581
2577
  .SpeedDown1, .SpeedDownChance => {
2582
2578
  assert(boosts.spe >= -6 and boosts.spe <= 6);
2583
- if (boosts.spe == -6) return if (fail) try log.fail(foe_ident, .None);
2579
+ if (boosts.spe == -6) return if (fail) try log.fail(.{ foe_ident, .None });
2584
2580
  boosts.spe = @intCast(@max(-6, @as(i8, boosts.spe) - 1));
2585
2581
  if (stats.spe == 1) {
2586
2582
  boosts.spe += 1;
2587
2583
  if (showdown) {
2588
- try log.boost(foe_ident, .Speed, -1);
2589
- try log.boost(foe_ident, .Speed, 1);
2584
+ try log.boost(.{ foe_ident, .Speed, -1 });
2585
+ try log.boost(.{ foe_ident, .Speed, 1 });
2590
2586
  }
2591
- return try log.fail(foe_ident, .None);
2587
+ return log.fail(.{ foe_ident, .None });
2592
2588
  }
2593
2589
  var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
2594
2590
  const stat = unmodifiedStats(battle, foe).spe;
2595
2591
  stats.spe = @max(1, stat * mod[0] / mod[1]);
2596
- try log.boost(foe_ident, .Speed, -1);
2592
+ try log.boost(.{ foe_ident, .Speed, -1 });
2597
2593
  assert(boosts.spe >= -6);
2598
2594
  },
2599
2595
  .SpecialDownChance => {
2600
2596
  assert(boosts.spc >= -6 and boosts.spc <= 6);
2601
- if (boosts.spc == -6) return if (fail) try log.fail(foe_ident, .None);
2597
+ if (boosts.spc == -6) return if (fail) try log.fail(.{ foe_ident, .None });
2602
2598
  boosts.spc = @intCast(@max(-6, @as(i8, boosts.spc) - 1));
2603
2599
  if (stats.spc == 1) {
2604
2600
  boosts.spc += 1;
2605
2601
  if (showdown) {
2606
- try log.boost(foe_ident, .SpecialAttack, -1);
2607
- try log.boost(foe_ident, .SpecialAttack, 1);
2608
- try log.boost(foe_ident, .SpecialDefense, -1);
2609
- try log.boost(foe_ident, .SpecialDefense, 1);
2602
+ try log.boost(.{ foe_ident, .SpecialAttack, -1 });
2603
+ try log.boost(.{ foe_ident, .SpecialAttack, 1 });
2604
+ try log.boost(.{ foe_ident, .SpecialDefense, -1 });
2605
+ try log.boost(.{ foe_ident, .SpecialDefense, 1 });
2610
2606
  }
2611
- return try log.fail(foe_ident, .None);
2607
+ return log.fail(.{ foe_ident, .None });
2612
2608
  }
2613
2609
  var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
2614
2610
  const stat = unmodifiedStats(battle, foe).spc;
2615
2611
  stats.spc = @max(1, stat * mod[0] / mod[1]);
2616
- try log.boost(foe_ident, .SpecialAttack, -1);
2617
- try log.boost(foe_ident, .SpecialDefense, -1);
2612
+ try log.boost(.{ foe_ident, .SpecialAttack, -1 });
2613
+ try log.boost(.{ foe_ident, .SpecialDefense, -1 });
2618
2614
  },
2619
2615
  .AccuracyDown1 => {
2620
2616
  assert(boosts.accuracy >= -6 and boosts.accuracy <= 6);
2621
- if (boosts.accuracy == -6) return if (fail) try log.fail(foe_ident, .None);
2617
+ if (boosts.accuracy == -6) return if (fail) try log.fail(.{ foe_ident, .None });
2622
2618
  boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
2623
- try log.boost(foe_ident, .Accuracy, -1);
2619
+ try log.boost(.{ foe_ident, .Accuracy, -1 });
2624
2620
  },
2625
2621
  else => unreachable,
2626
2622
  }
@@ -2644,7 +2640,7 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
2644
2640
  }
2645
2641
  }
2646
2642
 
2647
- inline fn isForced(active: anytype) bool {
2643
+ fn isForced(active: anytype) bool {
2648
2644
  return active.volatiles.Recharging or active.volatiles.Rage or
2649
2645
  active.volatiles.Thrashing or active.volatiles.Charging;
2650
2646
  }
@@ -2657,27 +2653,27 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
2657
2653
 
2658
2654
  options.chance.clearDurations(who, clear);
2659
2655
 
2660
- if (volatiles.disabled_move != 0) {
2661
- volatiles.disabled_move = 0;
2662
- volatiles.disabled_duration = 0;
2663
- try log.end(ident, .DisableSilent);
2656
+ if (volatiles.disable_move != 0) {
2657
+ volatiles.disable_move = 0;
2658
+ volatiles.disable_duration = 0;
2659
+ try log.end(.{ ident, .DisableSilent });
2664
2660
  }
2665
2661
  if (volatiles.Confusion) {
2666
2662
  // volatiles.confusion is left unchanged
2667
2663
  volatiles.Confusion = false;
2668
- try log.end(ident, .ConfusionSilent);
2664
+ try log.end(.{ ident, .ConfusionSilent });
2669
2665
  }
2670
2666
  if (volatiles.Mist) {
2671
2667
  volatiles.Mist = false;
2672
- try log.end(ident, .MistSilent);
2668
+ try log.end(.{ ident, .MistSilent });
2673
2669
  }
2674
2670
  if (volatiles.FocusEnergy) {
2675
2671
  volatiles.FocusEnergy = false;
2676
- try log.end(ident, .FocusEnergySilent);
2672
+ try log.end(.{ ident, .FocusEnergySilent });
2677
2673
  }
2678
2674
  if (volatiles.LeechSeed) {
2679
2675
  volatiles.LeechSeed = false;
2680
- try log.end(ident, .LeechSeedSilent);
2676
+ try log.end(.{ ident, .LeechSeedSilent });
2681
2677
  }
2682
2678
  if (!showdown and volatiles.Toxic) {
2683
2679
  volatiles.Toxic = false;
@@ -2685,22 +2681,22 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
2685
2681
  }
2686
2682
  if (volatiles.LightScreen) {
2687
2683
  volatiles.LightScreen = false;
2688
- try log.end(ident, .LightScreenSilent);
2684
+ try log.end(.{ ident, .LightScreenSilent });
2689
2685
  }
2690
2686
  if (volatiles.Reflect) {
2691
2687
  volatiles.Reflect = false;
2692
- try log.end(ident, .ReflectSilent);
2688
+ try log.end(.{ ident, .ReflectSilent });
2693
2689
  }
2694
2690
  if (showdown and volatiles.Toxic) {
2695
2691
  volatiles.Toxic = false;
2696
2692
  // Pokémon Showdown erroneously clears the toxic counter
2697
2693
  volatiles.toxic = 0;
2698
- try log.end(ident, .ToxicSilent);
2694
+ try log.end(.{ ident, .ToxicSilent });
2699
2695
  }
2700
2696
  }
2701
2697
 
2702
2698
  pub const Rolls = struct {
2703
- inline fn speedTie(battle: anytype, options: anytype) !bool {
2699
+ fn speedTie(battle: anytype, options: anytype) !bool {
2704
2700
  const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
2705
2701
  player == .P1
2706
2702
  else if (showdown)
@@ -2712,7 +2708,7 @@ pub const Rolls = struct {
2712
2708
  return p1;
2713
2709
  }
2714
2710
 
2715
- inline fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
2711
+ fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
2716
2712
  const crit = if (options.calc.overridden(player, .critical_hit)) |val|
2717
2713
  val == .true
2718
2714
  else if (showdown)
@@ -2724,7 +2720,7 @@ pub const Rolls = struct {
2724
2720
  return crit;
2725
2721
  }
2726
2722
 
2727
- inline fn damage(battle: anytype, player: anytype, options: anytype) !u8 {
2723
+ fn damage(battle: anytype, player: Player, options: anytype) !u8 {
2728
2724
  const roll = if (options.calc.overridden(player, .damage)) |val|
2729
2725
  val
2730
2726
  else roll: {
@@ -2740,7 +2736,7 @@ pub const Rolls = struct {
2740
2736
  return roll;
2741
2737
  }
2742
2738
 
2743
- inline fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) bool {
2739
+ fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) bool {
2744
2740
  const ok = if (options.calc.overridden(player, .hit)) |val|
2745
2741
  val == .true
2746
2742
  else if (showdown)
@@ -2752,7 +2748,7 @@ pub const Rolls = struct {
2752
2748
  return ok;
2753
2749
  }
2754
2750
 
2755
- inline fn confused(battle: anytype, player: Player, options: anytype) !bool {
2751
+ fn confused(battle: anytype, player: Player, options: anytype) !bool {
2756
2752
  const cfz = if (options.calc.overridden(player, .confused)) |val|
2757
2753
  val == .true
2758
2754
  else if (showdown)
@@ -2764,7 +2760,7 @@ pub const Rolls = struct {
2764
2760
  return cfz;
2765
2761
  }
2766
2762
 
2767
- inline fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
2763
+ fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
2768
2764
  const par = if (options.calc.overridden(player, .paralyzed)) |val|
2769
2765
  val == .true
2770
2766
  else if (showdown)
@@ -2776,7 +2772,7 @@ pub const Rolls = struct {
2776
2772
  return par;
2777
2773
  }
2778
2774
 
2779
- inline fn confusionChance(battle: anytype, player: Player, options: anytype) !bool {
2775
+ fn confusionChance(battle: anytype, player: Player, options: anytype) !bool {
2780
2776
  const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
2781
2777
  val == .true
2782
2778
  else if (showdown)
@@ -2788,7 +2784,7 @@ pub const Rolls = struct {
2788
2784
  return proc;
2789
2785
  }
2790
2786
 
2791
- inline fn secondaryChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
2787
+ fn secondaryChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
2792
2788
  const rate: u8 = if (low) 26 else 77;
2793
2789
 
2794
2790
  const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
@@ -2802,7 +2798,7 @@ pub const Rolls = struct {
2802
2798
  return proc;
2803
2799
  }
2804
2800
 
2805
- inline fn poisonChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
2801
+ fn poisonChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
2806
2802
  const rate: u8 = if (low) 52 else 103;
2807
2803
 
2808
2804
  const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
@@ -2816,7 +2812,7 @@ pub const Rolls = struct {
2816
2812
  return proc;
2817
2813
  }
2818
2814
 
2819
- inline fn unboost(battle: anytype, player: Player, options: anytype) !bool {
2815
+ fn unboost(battle: anytype, player: Player, options: anytype) !bool {
2820
2816
  const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
2821
2817
  val == .true
2822
2818
  else if (showdown)
@@ -2828,7 +2824,7 @@ pub const Rolls = struct {
2828
2824
  return proc;
2829
2825
  }
2830
2826
 
2831
- inline fn metronome(battle: anytype, player: Player, options: anytype) !Move {
2827
+ fn metronome(battle: anytype, player: Player, options: anytype) !Move {
2832
2828
  const move: Move = if (options.calc.overridden(player, .metronome)) |val|
2833
2829
  val
2834
2830
  else if (showdown)
@@ -2846,7 +2842,7 @@ pub const Rolls = struct {
2846
2842
  return move;
2847
2843
  }
2848
2844
 
2849
- inline fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
2845
+ fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
2850
2846
  const power = if (options.calc.overridden(player, .psywave)) |val|
2851
2847
  val - 1
2852
2848
  else if (showdown)
@@ -2863,7 +2859,7 @@ pub const Rolls = struct {
2863
2859
  return power;
2864
2860
  }
2865
2861
 
2866
- inline fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
2862
+ fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
2867
2863
  const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2868
2864
  @intCast(val)
2869
2865
  else if (showdown)
@@ -2880,7 +2876,7 @@ pub const Rolls = struct {
2880
2876
  return duration;
2881
2877
  }
2882
2878
 
2883
- inline fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
2879
+ fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
2884
2880
  const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
2885
2881
  val
2886
2882
  else if (showdown)
@@ -2893,7 +2889,7 @@ pub const Rolls = struct {
2893
2889
  return duration;
2894
2890
  }
2895
2891
 
2896
- inline fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
2892
+ fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
2897
2893
  const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2898
2894
  @intCast(val)
2899
2895
  else if (showdown)
@@ -2906,7 +2902,7 @@ pub const Rolls = struct {
2906
2902
  return duration;
2907
2903
  }
2908
2904
 
2909
- inline fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
2905
+ fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
2910
2906
  const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2911
2907
  @intCast(val)
2912
2908
  else if (showdown)
@@ -2921,7 +2917,7 @@ pub const Rolls = struct {
2921
2917
 
2922
2918
  const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
2923
2919
 
2924
- inline fn distribution(
2920
+ fn distribution(
2925
2921
  battle: anytype,
2926
2922
  comptime effect: Move.Effect,
2927
2923
  player: Player,
@@ -3096,7 +3092,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3096
3092
  if (m.id == if (active.volatiles.Bide) .Bide else side.last_selected_move) {
3097
3093
  // Pokémon Showdown displays Struggle if limited to Bide but unable to pick
3098
3094
  const struggle =
3099
- m.id == .Bide and (m.pp == 0 or active.volatiles.disabled_move == slot);
3095
+ m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
3100
3096
  const s = if (struggle) 0 else slot;
3101
3097
  out[n] = .{ .type = .Move, .data = s };
3102
3098
  n += 1;
@@ -3111,7 +3107,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3111
3107
  const m = active.moves[slot - 1];
3112
3108
  if (m.id == .None) break;
3113
3109
  if (m.pp == 0) continue;
3114
- if (active.volatiles.disabled_move == slot) continue;
3110
+ if (active.volatiles.disable_move == slot) continue;
3115
3111
  out[n] = .{ .type = .Move, .data = slot };
3116
3112
  n += 1;
3117
3113
  }