@pkmn/engine 0.1.0-dev.21e728d9 → 0.1.0-dev.29e0e9e1

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.
@@ -26,9 +26,9 @@ const Result = common.Result;
26
26
 
27
27
  const Activate = protocol.Activate;
28
28
  const Boost = protocol.Boost;
29
- const Cant = protocol.Cant;
30
29
  const Damage = protocol.Damage;
31
30
  const End = protocol.End;
31
+ const Fail = protocol.Fail;
32
32
  const Heal = protocol.Heal;
33
33
  const Start = protocol.Start;
34
34
 
@@ -252,15 +252,15 @@ fn switchIn(battle: anytype, player: Player, slot: u8, kind: SwitchIn, options:
252
252
  statusModify(incoming.status, &active.stats);
253
253
 
254
254
  if (kind == .Drag) {
255
- try options.log.drag(battle.active(player), incoming);
255
+ try options.log.drag(.{ battle.active(player), incoming });
256
256
  } else {
257
- try options.log.switched(battle.active(player), incoming);
257
+ try options.log.switched(.{ battle.active(player), incoming });
258
258
  }
259
259
  options.chance.switched(player, side.pokemon[0].position, out);
260
260
 
261
261
  if (side.conditions.Spikes and !active.types.includes(.Flying)) {
262
262
  incoming.hp -|= @max(incoming.stats.hp / 8, 1);
263
- try options.log.damage(battle.active(player), incoming, .Spikes);
263
+ try options.log.damage(.{ battle.active(player), incoming, .Spikes });
264
264
  }
265
265
  }
266
266
 
@@ -433,7 +433,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
433
433
  if (volatiles.Recharging) {
434
434
  volatiles.Recharging = false;
435
435
  resetCant(volatiles);
436
- try log.cant(ident, .Recharge);
436
+ try log.cant(.{ ident, .Recharge });
437
437
  return false;
438
438
  }
439
439
 
@@ -452,12 +452,12 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
452
452
  }
453
453
 
454
454
  if (duration == 0) {
455
- try log.curestatus(ident, before, .Message);
455
+ try log.curestatus(.{ ident, before, .Message });
456
456
  stored.status = 0; // clears EXT if present
457
457
  resetCant(volatiles);
458
458
  volatiles.Nightmare = false;
459
459
  } else {
460
- try log.cant(ident, .Sleep);
460
+ try log.cant(.{ ident, .Sleep });
461
461
  if (move == .Snore or move == .SleepTalk) break :slp;
462
462
  resetCant(volatiles);
463
463
  return false;
@@ -466,7 +466,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
466
466
 
467
467
  if (Status.is(stored.status, .FRZ) and !(move == .FlameWheel or move == .SacredFire)) {
468
468
  resetCant(volatiles);
469
- try log.cant(ident, .Freeze);
469
+ try log.cant(.{ ident, .Freeze });
470
470
  return false;
471
471
  }
472
472
 
@@ -476,7 +476,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
476
476
  // if (!showdown) volatiles.Flinch = false;
477
477
  volatiles.Flinch = false;
478
478
  resetCant(volatiles);
479
- try log.cant(ident, .Flinch);
479
+ try log.cant(.{ ident, .Flinch });
480
480
  return false;
481
481
  }
482
482
 
@@ -490,7 +490,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
490
490
 
491
491
  if (volatiles.disable.duration == 0) {
492
492
  volatiles.disable.move = 0;
493
- try log.end(ident, .Disable);
493
+ try log.end(.{ ident, .Disable });
494
494
  }
495
495
  }
496
496
 
@@ -506,9 +506,9 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
506
506
 
507
507
  if (volatiles.confusion == 0) {
508
508
  volatiles.Confusion = false;
509
- try log.end(ident, .Confusion);
509
+ try log.end(.{ ident, .Confusion });
510
510
  } else {
511
- try log.activate(ident, .Confusion);
511
+ try log.activate(.{ ident, .Confusion });
512
512
 
513
513
  if (try Rolls.confused(battle, player, options)) {
514
514
  volatiles.BeatUp = false;
@@ -523,11 +523,11 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
523
523
  }
524
524
 
525
525
  if (volatiles.Attract) {
526
- try log.activate(ident, .Attract);
526
+ try log.activate(.{ ident, .Attract });
527
527
 
528
528
  if (try Rolls.attract(battle, player, options)) {
529
529
  resetCant(volatiles);
530
- try log.cant(ident, .Attract);
530
+ try log.cant(.{ ident, .Attract });
531
531
  return false;
532
532
  }
533
533
  }
@@ -538,14 +538,14 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
538
538
  if (m != .None and m == move) {
539
539
  side.active.volatiles.Charging = false;
540
540
  resetCant(volatiles);
541
- try options.log.disabled(ident, move);
541
+ try options.log.cant(.{ ident, .Disable, move });
542
542
  return false;
543
543
  }
544
544
  }
545
545
 
546
546
  if (Status.is(stored.status, .PAR) and try Rolls.paralyzed(battle, player, options)) {
547
547
  resetCant(volatiles);
548
- try log.cant(ident, .Paralysis);
548
+ try log.cant(.{ ident, .Paralysis });
549
549
  return false;
550
550
  }
551
551
 
@@ -569,7 +569,7 @@ pub fn canMove(battle: anytype, player: Player, state: *State, options: anytype)
569
569
 
570
570
  // FIXME: pass in target to avoid lookup?
571
571
  const target = if (Move.get(state.move).target == .Self) player else player.foe();
572
- try options.log.moveFrom(battle.active(player), state.move, battle.active(target), state.from);
572
+ try options.log.move(.{ battle.active(player), state.move, battle.active(target), state.from });
573
573
 
574
574
  const charging = false; // TODO charging uses doUse
575
575
  const skip_pp = charging or state.move == .Struggle or
@@ -849,13 +849,13 @@ fn checkFaint(
849
849
  if (foe_fainted) try faint(battle, player.foe(), !more, options);
850
850
 
851
851
  if (player_out and foe_out) {
852
- try options.log.tie();
852
+ try options.log.tie(.{});
853
853
  return Result.Tie;
854
854
  } else if (player_out) {
855
- try options.log.win(player.foe());
855
+ try options.log.win(.{player.foe()});
856
856
  return if (player == .P1) Result.Lose else Result.Win;
857
857
  } else if (foe_out) {
858
- try options.log.win(player);
858
+ try options.log.win(.{player});
859
859
  return if (player == .P1) Result.Win else Result.Lose;
860
860
  }
861
861
 
@@ -885,19 +885,19 @@ fn faint(battle: anytype, player: Player, done: bool, options: anytype) !void {
885
885
  side.stored().stats.spe;
886
886
  }
887
887
 
888
- try options.log.faint(battle.active(player), done);
888
+ try options.log.faint(.{ battle.active(player), done });
889
889
  options.calc.capped(player);
890
890
  }
891
891
 
892
892
  // TODO: see if we can move *before* applyDamage and include failuretext logic
893
893
  pub fn reportOutcome(battle: anytype, player: Player, state: *State, options: anytype) !void {
894
894
  const foe_ident = battle.active(player.foe());
895
- if (state.crit) try options.log.crit(foe_ident);
895
+ if (state.crit) try options.log.crit(.{foe_ident});
896
896
  if (!state.immune()) {
897
897
  if (state.effectiveness > Effectiveness.neutral) {
898
- try options.log.supereffective(foe_ident);
898
+ try options.log.supereffective(.{foe_ident});
899
899
  } else if (state.effectiveness < Effectiveness.neutral) {
900
- try options.log.resisted(foe_ident);
900
+ try options.log.resisted(.{foe_ident});
901
901
  }
902
902
  }
903
903
  }
@@ -930,8 +930,8 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !bool {
930
930
  }
931
931
 
932
932
  stored.hp -|= damage;
933
- // Pokémon Showdown uses damageOf here but its not relevant in Generation II
934
- try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
933
+ // Pokémon Showdown uses damage [of] here but its not relevant in Generation II
934
+ try options.log.damage(.{ ident, stored, if (brn) Damage.Burn else Damage.Poison });
935
935
  if (stored.hp == 0) return true;
936
936
  }
937
937
 
@@ -947,27 +947,27 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !bool {
947
947
 
948
948
  const damage = @max(stored.stats.hp / 8, 1);
949
949
  stored.hp -|= damage;
950
- // As above, Pokémon Showdown uses damageOf but its not relevant
951
- try options.log.damage(ident, stored, .LeechSeed);
950
+ // As above, Pokémon Showdown uses damage [of] but its not relevant
951
+ try options.log.damage(.{ ident, stored, .LeechSeed });
952
952
 
953
953
  const before = foe_stored.hp;
954
954
  foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
955
955
  // Pokémon Showdown uses the less specific heal here instead of drain... because reasons?
956
- if (foe_stored.hp > before) try options.log.heal(foe_ident, foe_stored, .Silent);
956
+ if (foe_stored.hp > before) try options.log.heal(.{ foe_ident, foe_stored, .Silent });
957
957
  if (stored.hp == 0) return true;
958
958
  }
959
959
 
960
960
  if (volatiles.Nightmare) {
961
961
  stored.hp -= @max(stored.stats.hp / 4, 1);
962
962
  // ibid
963
- try options.log.damage(ident, stored, .Nightmare);
963
+ try options.log.damage(.{ ident, stored, .Nightmare });
964
964
  if (stored.hp == 0) return true;
965
965
  }
966
966
 
967
967
  if (volatiles.Curse) {
968
968
  stored.hp -= @max(stored.stats.hp / 4, 1);
969
969
  // ibid
970
- try options.log.damage(ident, stored, .Curse);
970
+ try options.log.damage(.{ ident, stored, .Curse });
971
971
  if (stored.hp == 0) return true;
972
972
  }
973
973
 
@@ -988,7 +988,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
988
988
  future_sight.count -= 1;
989
989
  assert(future_sight.count != 0);
990
990
  if (future_sight.count == 1) {
991
- try options.log.end(battle.active(player), .FutureSight);
991
+ try options.log.end(.{ battle.active(player), .FutureSight });
992
992
  // TODO doMove
993
993
  }
994
994
  }
@@ -1002,16 +1002,20 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1002
1002
 
1003
1003
  if (battle.field.weather_duration == 0) {
1004
1004
  battle.field.weather = .None;
1005
- try options.log.weather(battle.field.weather, .None);
1005
+ try options.log.weather(.{ battle.field.weather, protocol.Weather.None });
1006
1006
  } else {
1007
- try options.log.weather(battle.field.weather, .Upkeep);
1007
+ try options.log.weather(.{ battle.field.weather, protocol.Weather.Upkeep });
1008
1008
  if (battle.field.weather == .Sandstorm) {
1009
1009
  inline for (players) |player| {
1010
1010
  var side = battle.side(player);
1011
1011
  const active = side.active;
1012
1012
  if (!(active.volatiles.Underground or active.types.sandstormImmune())) {
1013
1013
  side.stored().hp -|= @max(side.stored().stats.hp / 8, 1);
1014
- try options.log.damage(battle.active(player), side.stored(), .Sandstorm);
1014
+ try options.log.damage(.{
1015
+ battle.active(player),
1016
+ side.stored(),
1017
+ .Sandstorm,
1018
+ });
1015
1019
  }
1016
1020
  }
1017
1021
  if (try checkFaint(battle, .P1, options)) |r| return r;
@@ -1023,19 +1027,24 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1023
1027
  inline for (players) |player| {
1024
1028
  var side = battle.side(player);
1025
1029
  var volatiles = &side.active.volatiles;
1030
+ const ident = battle.active(player);
1026
1031
 
1027
1032
  if (volatiles.bind.duration > 0 and !volatiles.Substitute) {
1028
1033
  volatiles.bind.duration -= 1;
1029
1034
  if (volatiles.bind.duration == 0) {
1030
1035
  const reason = @intFromEnum(End.Bind) + volatiles.bind.reason - 1;
1031
- try options.log.end(battle.active(player), @enumFromInt(reason));
1036
+ try options.log.end(.{ ident, @as(End, @enumFromInt(reason)) });
1032
1037
  } else {
1033
1038
  var reason = @intFromEnum(Activate.Bind) + volatiles.bind.reason - 1;
1034
- try options.log.activate(battle.active(player), @enumFromInt(reason));
1039
+ try options.log.activate(.{ ident, @as(Activate, @enumFromInt(reason)) });
1035
1040
 
1036
1041
  reason = @intFromEnum(Damage.Bind) + volatiles.bind.reason - 1;
1037
1042
  side.stored().hp -|= @max(side.stored().stats.hp / 16, 1);
1038
- try options.log.damage(battle.active(player), side.stored(), @enumFromInt(reason));
1043
+ try options.log.damage(.{
1044
+ ident,
1045
+ side.stored(),
1046
+ @as(Damage, @enumFromInt(reason)),
1047
+ });
1039
1048
  }
1040
1049
  }
1041
1050
  }
@@ -1052,7 +1061,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1052
1061
 
1053
1062
  assert(volatiles.perish_song < 3);
1054
1063
  const reason = @intFromEnum(Start.PerishSong0) + volatiles.perish_song;
1055
- try options.log.start(battle.active(player), @enumFromInt(reason));
1064
+ try options.log.start(.{ battle.active(player), @as(Start, @enumFromInt(reason)) });
1056
1065
 
1057
1066
  if (volatiles.perish_song == 0) {
1058
1067
  volatiles.PerishSong = false;
@@ -1069,7 +1078,9 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1069
1078
  if (stored.item == .Leftovers) {
1070
1079
  const before = stored.hp;
1071
1080
  stored.hp = @min(stored.hp + @max(stored.stats.hp / 16, 1), stored.stats.hp);
1072
- if (stored.hp > before) try options.log.heal(battle.active(player), stored, .Leftovers);
1081
+ if (stored.hp > before) {
1082
+ try options.log.heal(.{ battle.active(player), stored, .Leftovers });
1083
+ }
1073
1084
  }
1074
1085
  }
1075
1086
 
@@ -1090,7 +1101,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1090
1101
  !side.active.volatiles.frozen and
1091
1102
  try Rolls.defrost(battle, player, options))
1092
1103
  {
1093
- try options.log.curestatus(battle.active(player), side.stored().status, .Message);
1104
+ try options.log.curestatus(.{ battle.active(player), side.stored().status, .Message });
1094
1105
  side.stored().status = 0;
1095
1106
  }
1096
1107
  }
@@ -1131,14 +1142,14 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1131
1142
  stored.hp < stored.stats.hp;
1132
1143
  if (proc) {
1133
1144
  assert(stored.hp != 0);
1134
- try options.log.enditem(ident, stored.item, .Eat);
1145
+ try options.log.enditem(.{ ident, stored.item, .Eat });
1135
1146
  stored.item = .None;
1136
1147
 
1137
1148
  const offset = num - @intFromEnum(Item.Berry);
1138
1149
  assert(offset < 3);
1139
1150
  stored.hp = @min(stored.hp + ((offset + 1) * 10), stored.stats.hp);
1140
1151
  const reason: Heal = @enumFromInt(@intFromEnum(Heal.Berry) + offset);
1141
- try options.log.heal(ident, stored, reason);
1152
+ try options.log.heal(.{ ident, stored, reason });
1142
1153
  }
1143
1154
  } else if (num < @intFromEnum(Item.BitterBerry)) {
1144
1155
  const proc = if (stored.item == .MiracleBerry)
@@ -1148,7 +1159,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1148
1159
 
1149
1160
  if (proc) {
1150
1161
  assert(Status.any(stored.status));
1151
- try options.log.enditem(ident, stored.item, .Eat);
1162
+ try options.log.enditem(.{ ident, stored.item, .Eat });
1152
1163
  stored.item = .None;
1153
1164
 
1154
1165
  // FIXME wtf?
@@ -1156,21 +1167,21 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1156
1167
  // side.active.volatiles.Nightmare = false;
1157
1168
  if (stored.item == .MiracleBerry) {
1158
1169
  side.active.volatiles.Confusion = false;
1159
- try options.log.end(ident, .Confusion);
1170
+ try options.log.end(.{ ident, .Confusion });
1160
1171
  }
1161
1172
  stored.status = 0;
1162
1173
 
1163
1174
  // FIXME recalc stats
1164
1175
 
1165
- try options.log.curestatus(ident, stored.status, .Message);
1176
+ try options.log.curestatus(.{ ident, stored.status, .Message });
1166
1177
  }
1167
1178
  } else if (num == @intFromEnum(Item.BitterBerry)) {
1168
1179
  if (side.active.volatiles.Confusion) {
1169
- try options.log.enditem(ident, stored.item, .Eat);
1180
+ try options.log.enditem(.{ ident, stored.item, .Eat });
1170
1181
  stored.item = .None;
1171
1182
 
1172
1183
  side.active.volatiles.Confusion = false;
1173
- try options.log.end(ident, .Confusion);
1184
+ try options.log.end(.{ ident, .Confusion });
1174
1185
  }
1175
1186
  }
1176
1187
  }
@@ -1187,7 +1198,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1187
1198
 
1188
1199
  if (volatiles.encore == 0 or side.active.move(mslot).pp == 0) {
1189
1200
  volatiles.Encore = false;
1190
- try options.log.end(battle.active(player), .Encore);
1201
+ try options.log.end(.{ battle.active(player), .Encore });
1191
1202
  }
1192
1203
  }
1193
1204
  }
@@ -1209,7 +1220,7 @@ fn updateSideCondition(
1209
1220
 
1210
1221
  if (@field(side.conditions, value) == 0) {
1211
1222
  @field(side.conditions, key) = false;
1212
- try options.log.sideend(player, @field(protocol.Side, key));
1223
+ try options.log.sideend(.{ player, @field(protocol.Side, key) });
1213
1224
  }
1214
1225
  }
1215
1226
  }
@@ -1218,13 +1229,13 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
1218
1229
  battle.turn += 1;
1219
1230
 
1220
1231
  if (showdown and battle.turn >= 1000) {
1221
- try options.log.tie();
1232
+ try options.log.tie(.{});
1222
1233
  return Result.Tie;
1223
1234
  } else if (battle.turn >= 65535) {
1224
1235
  return Result.Error;
1225
1236
  }
1226
1237
 
1227
- try options.log.turn(battle.turn);
1238
+ try options.log.turn(.{battle.turn});
1228
1239
 
1229
1240
  return Result.Default;
1230
1241
  }
@@ -1240,7 +1251,7 @@ pub fn buildRage(battle: anytype, player: Player, state: *State, options: anytyp
1240
1251
  }
1241
1252
  } else {
1242
1253
  foe.active.volatiles.rage +|= 1;
1243
- try options.log.activate(battle.active(player), .Rage);
1254
+ try options.log.activate(.{ battle.active(player), .Rage });
1244
1255
  }
1245
1256
  }
1246
1257
  }
@@ -1263,7 +1274,7 @@ pub fn destinyBond(battle: anytype, player: Player, _: *State, options: anytype)
1263
1274
 
1264
1275
  if (foe.active.volatiles.DestinyBond) {
1265
1276
  battle.side(player).stored().hp = 0;
1266
- try options.log.activate(battle.active(player.foe()), .DestinyBond);
1277
+ try options.log.activate(.{ battle.active(player.foe()), .DestinyBond });
1267
1278
  }
1268
1279
  return true;
1269
1280
  }
@@ -1276,14 +1287,14 @@ pub const Effects = struct {
1276
1287
  const gender: u8 = @intFromEnum(side.stored().dvs.gender);
1277
1288
  const foe_gender: u8 = @intFromEnum(foe.stored().dvs.gender);
1278
1289
  if (gender + foe_gender != 1) {
1279
- return options.log.immune(battle.active(player.foe()), .None);
1290
+ return options.log.immune(.{ battle.active(player.foe()), .None });
1280
1291
  } else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
1281
- try options.log.lastmiss();
1282
- return options.log.miss(battle.active(player));
1292
+ try options.log.lastmiss(.{});
1293
+ return options.log.miss(.{battle.active(player)});
1283
1294
  }
1284
1295
 
1285
1296
  foe.active.volatiles.Attract = true;
1286
- try options.log.start(battle.active(player.foe()), .Attract);
1297
+ try options.log.start(.{ battle.active(player.foe()), .Attract });
1287
1298
  }
1288
1299
 
1289
1300
  pub fn batonPass(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -1322,10 +1333,10 @@ pub const Effects = struct {
1322
1333
  // }
1323
1334
  try options.chance.attacking(player, volatiles.attacks);
1324
1335
 
1325
- if (volatiles.attacks != 0) return options.log.activate(ident, .Bide);
1336
+ if (volatiles.attacks != 0) return options.log.activate(.{ ident, .Bide });
1326
1337
 
1327
1338
  volatiles.Bide = false;
1328
- try options.log.end(ident, .Bide);
1339
+ try options.log.end(.{ ident, .Bide });
1329
1340
 
1330
1341
  state.damage = volatiles.bide *% 2;
1331
1342
  volatiles.bide = 0;
@@ -1359,11 +1370,11 @@ pub const Effects = struct {
1359
1370
  if (Status.any(foe_stored.status)) {
1360
1371
  if (Status.is(foe_stored.status, .FRZ)) {
1361
1372
  assert(Move.get(state.move).type == .Fire or state.move == .TriAttack);
1362
- try options.log.curestatus(
1373
+ try options.log.curestatus(.{
1363
1374
  battle.active(player.foe()),
1364
1375
  foe_stored.status,
1365
1376
  .Message,
1366
- );
1377
+ });
1367
1378
  foe_stored.status = 0;
1368
1379
  }
1369
1380
  return;
@@ -1378,7 +1389,8 @@ pub const Effects = struct {
1378
1389
 
1379
1390
  foe_stored.status = Status.init(.BRN);
1380
1391
  foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
1381
- try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
1392
+
1393
+ try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
1382
1394
 
1383
1395
  // TODO: check for status berry
1384
1396
  }
@@ -1398,7 +1410,7 @@ pub const Effects = struct {
1398
1410
  for (side.active.moves) |m| {
1399
1411
  if (convertible(side.active, m.id)) n += 1;
1400
1412
  }
1401
- if (n == 0) return options.log.fail(battle.active(player), .None);
1413
+ if (n == 0) return options.log.fail(.{ battle.active(player), .None });
1402
1414
 
1403
1415
  side.active.types.type1 = try Rolls.conversion(battle, player, n, options);
1404
1416
  side.active.types.type2 = side.active.types.type1;
@@ -1408,9 +1420,9 @@ pub const Effects = struct {
1408
1420
  var side = battle.side(player);
1409
1421
 
1410
1422
  const last = battle.foe(player).lastMove(false);
1411
- if (last == .None) return options.log.fail(battle.active(player), .None);
1423
+ if (last == .None) return options.log.fail(.{ battle.active(player), .None });
1412
1424
  const move = Move.get(last);
1413
- if (move.type == .@"???") return options.log.fail(battle.active(player), .None);
1425
+ if (move.type == .@"???") return options.log.fail(.{ battle.active(player), .None });
1414
1426
 
1415
1427
  side.active.types.type1 = try Rolls.conversion2(battle, player, move.type, options);
1416
1428
  side.active.types.type2 = side.active.types.type1;
@@ -1452,41 +1464,41 @@ pub const Effects = struct {
1452
1464
 
1453
1465
  if (side.active.types.includes(.Ghost)) {
1454
1466
  if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
1455
- try options.log.lastmiss();
1456
- return options.log.miss(battle.active(player));
1467
+ try options.log.lastmiss(.{});
1468
+ return options.log.miss(.{battle.active(player)});
1457
1469
  } else if (foe.active.volatiles.Substitute) {
1458
- return options.log.activateMove(foe_ident, .SubstituteBlock, state.move);
1470
+ return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
1459
1471
  } else if (foe.active.volatiles.Curse) {
1460
- return options.log.fail(foe_ident, .None);
1472
+ return options.log.fail(.{ foe_ident, .None });
1461
1473
  }
1462
1474
 
1463
1475
  foe.active.volatiles.Curse = true;
1464
- try options.log.start(foe_ident, .Curse);
1476
+ try options.log.start(.{ foe_ident, .Curse });
1465
1477
 
1466
1478
  side.stored().hp -= @max(side.stored().stats.hp / 2, 1);
1467
- try options.log.damage(ident, side.stored(), .Curse);
1479
+ try options.log.damage(.{ ident, side.stored(), .Curse });
1468
1480
  } else {
1469
1481
  var stats = &side.active.stats;
1470
1482
  var boosts = &side.active.boosts;
1471
1483
 
1472
1484
  const max_atk = boosts.atk == 6 or stats.atk == MAX_STAT_VALUE;
1473
1485
  const max_def = boosts.def == 6 or stats.def == MAX_STAT_VALUE;
1474
- if (max_atk and max_def) return options.log.fail(ident, .None);
1486
+ if (max_atk and max_def) return options.log.fail(.{ ident, .None });
1475
1487
 
1476
1488
  if (!(boosts.spe == -6 or stats.spe == 1)) {
1477
1489
  assert(boosts.spe >= -6 and boosts.spe <= 6);
1478
1490
  try unboostStat(battle, side, stats, boosts, "spe", 1);
1479
- try options.log.boost(ident, .Speed, @as(i8, -1));
1491
+ try options.log.boost(.{ ident, .Speed, @as(i8, -1) });
1480
1492
  }
1481
1493
  if (!max_atk) {
1482
1494
  assert(boosts.atk >= -6 and boosts.atk <= 6);
1483
1495
  try boostStat(battle, side, stats, boosts, "atk", 1);
1484
- try options.log.boost(ident, .Attack, @as(i8, 1));
1496
+ try options.log.boost(.{ ident, .Attack, @as(i8, 1) });
1485
1497
  }
1486
1498
  if (!max_def) {
1487
1499
  assert(boosts.def >= -6 and boosts.def <= 6);
1488
1500
  try boostStat(battle, side, stats, boosts, "def", 1);
1489
- try options.log.boost(ident, .Defense, @as(i8, 1));
1501
+ try options.log.boost(.{ ident, .Defense, @as(i8, 1) });
1490
1502
  }
1491
1503
  }
1492
1504
  }
@@ -1498,14 +1510,13 @@ pub const Effects = struct {
1498
1510
  pub fn defrost(battle: anytype, player: Player, _: *State, options: anytype) !void {
1499
1511
  var side = battle.side(player);
1500
1512
  assert(Status.is(side.stored().status, .FRZ));
1501
-
1502
- try options.log.curestatus(battle.active(player), side.stored().status, .Message);
1513
+ try options.log.curestatus(.{ battle.active(player), side.stored().status, .Message });
1503
1514
  side.stored().status = 0;
1504
1515
  }
1505
1516
 
1506
1517
  pub fn destinyBond(battle: anytype, player: Player, _: *State, options: anytype) !void {
1507
1518
  battle.side(player).active.volatiles.DestinyBond = true;
1508
- try options.log.singlemove(battle.active(player), Move.DestinyBond);
1519
+ try options.log.singlemove(.{ battle.active(player), Move.DestinyBond });
1509
1520
  }
1510
1521
 
1511
1522
  pub fn disable(battle: anytype, player: Player, _: *State, options: anytype) !void {
@@ -1513,14 +1524,10 @@ pub const Effects = struct {
1513
1524
  var volatiles = &foe.active.volatiles;
1514
1525
  const foe_ident = battle.active(player.foe());
1515
1526
 
1516
- if (volatiles.disable.move != 0) {
1517
- return options.log.fail(foe_ident, .None);
1518
- }
1527
+ if (volatiles.disable.move != 0) return options.log.fail(.{ foe_ident, .None });
1519
1528
 
1520
1529
  const last = battle.foe(player).lastMove(false);
1521
- if (last == .None or last == .Struggle) {
1522
- return options.log.fail(foe_ident, .None);
1523
- }
1530
+ if (last == .None or last == .Struggle) return options.log.fail(.{ foe_ident, .None });
1524
1531
 
1525
1532
  var slot: u3 = 0;
1526
1533
  for (foe.active.moves) |m| {
@@ -1529,12 +1536,12 @@ pub const Effects = struct {
1529
1536
  if (m.id == last) break;
1530
1537
  }
1531
1538
  const move = foe.active.move(slot);
1532
- if (move.pp == 0) return options.log.fail(foe_ident, .None);
1539
+ if (move.pp == 0) return options.log.fail(.{ foe_ident, .None });
1533
1540
 
1534
1541
  volatiles.disable.move = slot;
1535
1542
  volatiles.disable.duration = Rolls.disableDuration(battle, player, options);
1536
1543
 
1537
- try options.log.startEffect(foe_ident, .Disable, move.id);
1544
+ try options.log.start(.{ foe_ident, .Disable, move.id });
1538
1545
  }
1539
1546
 
1540
1547
  pub const double = struct {
@@ -1553,13 +1560,14 @@ pub const Effects = struct {
1553
1560
 
1554
1561
  pub fn drainHP(battle: anytype, player: Player, state: *State, options: anytype) !void {
1555
1562
  var stored = battle.side(player).stored();
1563
+ const ident = battle.active(player);
1556
1564
 
1557
1565
  assert(state.damage != 0);
1558
1566
 
1559
1567
  if (stored.hp == stored.stats.hp) return;
1560
1568
  stored.hp = @min(stored.stats.hp, stored.hp + @max(state.damage / 2, 1));
1561
1569
 
1562
- try options.log.drain(battle.active(player), stored, battle.active(player.foe()));
1570
+ try options.log.heal(.{ ident, stored, .Drain, battle.active(player.foe()) });
1563
1571
  }
1564
1572
 
1565
1573
  pub fn encore(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -1571,7 +1579,7 @@ pub const Effects = struct {
1571
1579
  const failed = volatiles.Encore or
1572
1580
  (last == .None or last == .Struggle or
1573
1581
  last == .Encore or .last == .MirrorMove);
1574
- if (failed) return options.log.fail(foe_ident, .None);
1582
+ if (failed) return options.log.fail(.{ foe_ident, .None });
1575
1583
 
1576
1584
  var slot: u3 = 0;
1577
1585
  for (foe.active.moves) |m| {
@@ -1580,7 +1588,7 @@ pub const Effects = struct {
1580
1588
  if (m.id == last) break;
1581
1589
  }
1582
1590
  const move = foe.active.move(slot);
1583
- if (move.pp == 0) return options.log.fail(foe_ident, .None);
1591
+ if (move.pp == 0) return options.log.fail(.{ foe_ident, .None });
1584
1592
 
1585
1593
  volatiles.Encore = true;
1586
1594
  volatiles.encore = Rolls.encoreDuration(battle, player, options);
@@ -1588,7 +1596,7 @@ pub const Effects = struct {
1588
1596
  // TODO ???
1589
1597
  _ = state;
1590
1598
 
1591
- try options.log.startEffect(foe_ident, .Encore, move.id);
1599
+ try options.log.start(.{ foe_ident, .Encore, move.id });
1592
1600
  }
1593
1601
 
1594
1602
  pub fn explode(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -1642,12 +1650,11 @@ pub const Effects = struct {
1642
1650
  var side = battle.side(player);
1643
1651
 
1644
1652
  if (side.active.volatiles.FocusEnergy) {
1645
- if (!showdown) try options.log.fail(battle.active(player), .None);
1646
- return;
1653
+ return if (!showdown) try options.log.fail(.{ battle.active(player), .None });
1647
1654
  }
1648
1655
 
1649
1656
  side.active.volatiles.FocusEnergy = true;
1650
- try options.log.start(battle.active(player), .FocusEnergy);
1657
+ try options.log.start(.{ battle.active(player), .FocusEnergy });
1651
1658
  }
1652
1659
 
1653
1660
  var SLOTS: [5]u4 = .{ 0, 0, 0, 0, 0 };
@@ -1657,7 +1664,7 @@ pub const Effects = struct {
1657
1664
 
1658
1665
  const foe = battle.foe(player);
1659
1666
 
1660
- if (state.first) return options.log.fail(battle.active(player), .None);
1667
+ if (state.first) return options.log.fail(.{ battle.active(player), .None });
1661
1668
 
1662
1669
  var n: u4 = 0;
1663
1670
  var i: u4 = 0;
@@ -1671,7 +1678,7 @@ pub const Effects = struct {
1671
1678
  }
1672
1679
  }
1673
1680
 
1674
- if (n == 0) return options.log.fail(battle.active(player), .None);
1681
+ if (n == 0) return options.log.fail(.{ battle.active(player), .None });
1675
1682
 
1676
1683
  const slot = try Rolls.forceSwitch(battle, player, SLOTS[0..i], n, options);
1677
1684
  try switchIn(battle, player.foe(), slot, .Drag, options);
@@ -1681,14 +1688,14 @@ pub const Effects = struct {
1681
1688
  var foe = battle.foe(player);
1682
1689
 
1683
1690
  if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
1684
- try options.log.lastmiss();
1685
- return options.log.miss(battle.active(player));
1691
+ try options.log.lastmiss(.{});
1692
+ return options.log.miss(.{battle.active(player)});
1686
1693
  } else if (foe.active.volatiles.Foresight) {
1687
- return options.log.fail(battle.active(player), .None);
1694
+ return options.log.fail(.{ battle.active(player), .None });
1688
1695
  }
1689
1696
 
1690
1697
  foe.active.volatiles.Foresight = true;
1691
- try options.log.start(battle.active(player.foe()), .FocusEnergy);
1698
+ try options.log.start(.{ battle.active(player.foe()), .FocusEnergy });
1692
1699
  }
1693
1700
 
1694
1701
  pub fn freezeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -1711,7 +1718,7 @@ pub const Effects = struct {
1711
1718
  if (showdown) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
1712
1719
 
1713
1720
  foe_stored.status = Status.init(.FRZ);
1714
- try options.log.status(foe_ident, foe_stored.status, .None);
1721
+ try options.log.status(.{ foe_ident, foe_stored.status, .None });
1715
1722
 
1716
1723
  // TODO: check for status berry
1717
1724
 
@@ -1746,7 +1753,7 @@ pub const Effects = struct {
1746
1753
  inline for (STATS) |s| @field(foe.active.stats, s) = @field(stats, s);
1747
1754
  statusModify(foe.stored().status, &foe.active.stats);
1748
1755
 
1749
- try options.log.clearallboost();
1756
+ try options.log.clearallboost(.{});
1750
1757
  }
1751
1758
 
1752
1759
  pub fn heal(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -1754,7 +1761,7 @@ pub const Effects = struct {
1754
1761
  var stored = side.stored();
1755
1762
  const ident = battle.active(player);
1756
1763
 
1757
- if (stored.stats.hp == stored.hp) return options.log.fail(ident, .None);
1764
+ if (stored.stats.hp == stored.hp) return options.log.fail(.{ ident, .None });
1758
1765
 
1759
1766
  const rest = state.move == .Rest;
1760
1767
  if (rest) {
@@ -1763,19 +1770,19 @@ pub const Effects = struct {
1763
1770
  side.active.volatiles.Toxic = false;
1764
1771
  stored.status = Status.slf(2);
1765
1772
  recomputeStats(battle, side);
1766
- try options.log.statusFrom(ident, stored.status, Move.Rest);
1773
+ try options.log.status(.{ ident, stored.status, .From, Move.Rest });
1767
1774
  stored.hp = stored.stats.hp;
1768
1775
  } else {
1769
1776
  stored.hp = @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2));
1770
1777
  }
1771
- try options.log.heal(ident, stored, if (rest) Heal.Silent else Heal.None);
1778
+ try options.log.heal(.{ ident, stored, if (rest) Heal.Silent else Heal.None });
1772
1779
  }
1773
1780
 
1774
1781
  pub fn healBell(battle: anytype, player: Player, _: *State, options: anytype) !void {
1775
1782
  var side = battle.side(player);
1776
1783
  side.active.volatiles.Nightmare = false;
1777
1784
 
1778
- try options.log.cureteam(battle.active(player));
1785
+ try options.log.cureteam(.{battle.active(player)});
1779
1786
  for (&side.pokemon) |*p| p.status = 0;
1780
1787
 
1781
1788
  // We technically only need to recompute stats if the active Pokémon was burned/paralyzed,
@@ -1791,7 +1798,7 @@ pub const Effects = struct {
1791
1798
 
1792
1799
  pub fn hyperBeam(battle: anytype, player: Player, _: *State, options: anytype) !void {
1793
1800
  battle.side(player).active.volatiles.Recharging = true;
1794
- try options.log.mustrecharge(battle.active(player));
1801
+ try options.log.mustrecharge(.{battle.active(player)});
1795
1802
  }
1796
1803
 
1797
1804
  pub fn leechSeed(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -1801,12 +1808,12 @@ pub const Effects = struct {
1801
1808
  // Invulnerability trumps type immunity on Pokémon Showdown
1802
1809
  const invulnerable = foe.active.volatiles.Underground or foe.active.volatiles.Flying;
1803
1810
  if (!invulnerable and foe.active.types.includes(.Grass)) {
1804
- return options.log.immune(battle.active(player.foe()), .None);
1811
+ return options.log.immune(.{ battle.active(player.foe()), .None });
1805
1812
  }
1806
1813
  try checkHit(battle, player, state, options);
1807
1814
  if (state.miss) {
1808
- try options.log.lastmiss();
1809
- return options.log.miss(battle.active(player));
1815
+ try options.log.lastmiss(.{});
1816
+ return options.log.miss(.{battle.active(player)});
1810
1817
  }
1811
1818
  if (foe.active.volatiles.LeechSeed) return;
1812
1819
  } else {
@@ -1814,14 +1821,14 @@ pub const Effects = struct {
1814
1821
  const miss =
1815
1822
  state.miss or foe.active.types.includes(.Grass) or foe.active.volatiles.LeechSeed;
1816
1823
  if (miss) {
1817
- try options.log.lastmiss();
1818
- return options.log.miss(battle.active(player));
1824
+ try options.log.lastmiss(.{});
1825
+ return options.log.miss(.{battle.active(player)});
1819
1826
  }
1820
1827
  }
1821
1828
 
1822
1829
  foe.active.volatiles.LeechSeed = true;
1823
1830
 
1824
- try options.log.start(battle.active(player.foe()), .LeechSeed);
1831
+ try options.log.start(.{ battle.active(player.foe()), .LeechSeed });
1825
1832
  }
1826
1833
 
1827
1834
  pub fn lockOn(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -1829,20 +1836,20 @@ pub const Effects = struct {
1829
1836
  const foe_ident = battle.active(player.foe());
1830
1837
 
1831
1838
  if (foe.active.volatiles.Substitute) {
1832
- return options.log.activateMove(foe_ident, .SubstituteBlock, state.move);
1839
+ return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
1833
1840
  } else if (state.miss) {
1834
- return options.log.fail(foe_ident, .None);
1841
+ return options.log.fail(.{ foe_ident, .None });
1835
1842
  }
1836
1843
 
1837
1844
  foe.active.volatiles.LockOn = true;
1838
- try options.log.activate(battle.active(player), .LockOn);
1845
+ try options.log.activate(.{ battle.active(player), .LockOn });
1839
1846
  }
1840
1847
 
1841
1848
  const MAGNITUDE_POWER = [_]u8{ 10, 30, 50, 70, 90, 110, 150 };
1842
1849
 
1843
1850
  pub fn magnitude(battle: anytype, player: Player, state: *State, options: anytype) !void {
1844
1851
  const num = try Rolls.magnitude(battle, player, options);
1845
- try options.log.magnitude(battle.active(player), num);
1852
+ try options.log.activate(.{ battle.active(player), .Magnitude, num });
1846
1853
  state.bp = MAGNITUDE_POWER[num - 4];
1847
1854
  }
1848
1855
 
@@ -1850,11 +1857,11 @@ pub const Effects = struct {
1850
1857
  var foe = battle.foe(player);
1851
1858
 
1852
1859
  if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
1853
- if (!showdown) return options.log.fail(battle.active(player.foe()), .None);
1854
- try options.log.lastmiss();
1855
- return options.log.miss(battle.active(player));
1860
+ if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
1861
+ try options.log.lastmiss(.{});
1862
+ return options.log.miss(.{battle.active(player)});
1856
1863
  } else if (foe.active.volatiles.Trapped) {
1857
- return options.log.fail(battle.active(player.foe()), .None);
1864
+ return options.log.fail(.{ battle.active(player.foe()), .None });
1858
1865
  }
1859
1866
 
1860
1867
  foe.active.volatiles.Trapped = true;
@@ -1898,11 +1905,11 @@ pub const Effects = struct {
1898
1905
 
1899
1906
  const last = foe.lastMove(false);
1900
1907
 
1901
- if (last == .None) return options.log.fail(battle.active(player), .None);
1908
+ if (last == .None) return options.log.fail(.{ battle.active(player), .None });
1902
1909
  const knows = knows: for (side.active.moves) |m| {
1903
1910
  if (last == m.id) break :knows true;
1904
1911
  } else false;
1905
- if (knows) return options.log.fail(battle.active(player), .None);
1912
+ if (knows) return options.log.fail(.{ battle.active(player), .None });
1906
1913
 
1907
1914
  // if (wPlayerCharging) ???
1908
1915
  // wPlayerCharging = 1
@@ -1916,17 +1923,17 @@ pub const Effects = struct {
1916
1923
  pub fn mist(battle: anytype, player: Player, _: *State, options: anytype) !void {
1917
1924
  var side = battle.side(player);
1918
1925
 
1919
- if (side.active.volatiles.Mist) return options.log.fail(battle.active(player), .None);
1926
+ if (side.active.volatiles.Mist) return options.log.fail(.{ battle.active(player), .None });
1920
1927
  side.active.volatiles.Mist = true;
1921
1928
 
1922
- try options.log.start(battle.active(player), .Mist);
1929
+ try options.log.start(.{ battle.active(player), .Mist });
1923
1930
  }
1924
1931
 
1925
1932
  pub fn weatherHeal(battle: anytype, player: Player, _: *State, options: anytype) !void {
1926
1933
  var stored = battle.side(player).stored();
1927
1934
  const ident = battle.active(player);
1928
1935
 
1929
- if (stored.hp == stored.stats.hp) return options.log.fail(ident, .None);
1936
+ if (stored.hp == stored.stats.hp) return options.log.fail(.{ ident, .None });
1930
1937
 
1931
1938
  stored.hp = switch (battle.field.weather) {
1932
1939
  .Sun => stored.stats.hp,
@@ -1934,7 +1941,7 @@ pub const Effects = struct {
1934
1941
  else => @min(stored.stats.hp, stored.hp + (stored.stats.hp / 4)),
1935
1942
  };
1936
1943
 
1937
- try options.log.heal(ident, stored, Heal.None);
1944
+ try options.log.heal(.{ ident, stored, .None });
1938
1945
  }
1939
1946
 
1940
1947
  pub fn multiHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -1947,17 +1954,17 @@ pub const Effects = struct {
1947
1954
  const foe_ident = battle.active(player.foe());
1948
1955
 
1949
1956
  if (foe_volatiles.Flying or foe_volatiles.Underground) {
1950
- if (!showdown) return options.log.fail(battle.active(player.foe()), .None);
1951
- try options.log.lastmiss();
1952
- return options.log.miss(battle.active(player));
1957
+ if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
1958
+ try options.log.lastmiss(.{});
1959
+ return options.log.miss(.{battle.active(player)});
1953
1960
  } else if (foe_volatiles.Substitute) {
1954
- return options.log.activateMove(foe_ident, .SubstituteBlock, state.move);
1961
+ return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
1955
1962
  } else if (!Status.is(foe.stored().status, .SLP) or foe_volatiles.Nightmare) {
1956
- return options.log.fail(foe_ident, .None);
1963
+ return options.log.fail(.{ foe_ident, .None });
1957
1964
  }
1958
1965
 
1959
1966
  foe_volatiles.Nightmare = true;
1960
- try options.log.start(battle.active(player.foe()), .Nightmare);
1967
+ try options.log.start(.{ battle.active(player.foe()), .Nightmare });
1961
1968
  }
1962
1969
 
1963
1970
  pub const ohko = struct {
@@ -1981,9 +1988,9 @@ pub const Effects = struct {
1981
1988
  const foe_ident = battle.active(player.foe());
1982
1989
 
1983
1990
  state.damage = 0;
1984
- if (state.immune()) return options.log.immune(foe_ident, .None);
1991
+ if (state.immune()) return options.log.immune(.{ foe_ident, .None });
1985
1992
  const delta = side.stored().level -| foe.stored().level;
1986
- if (delta == 0) return options.log.immune(foe_ident, .OHKO);
1993
+ if (delta == 0) return options.log.immune(.{ foe_ident, .OHKO });
1987
1994
 
1988
1995
  state.accuracy = 2 * delta +| Gen12.percent(30);
1989
1996
  try checkHit(battle, player, state, options);
@@ -1999,37 +2006,39 @@ pub const Effects = struct {
1999
2006
  const ident = battle.active(player);
2000
2007
  const foe_ident = battle.active(player.foe());
2001
2008
 
2002
- if (state.miss) return options.log.fail(foe_ident, .None);
2009
+ if (state.miss) return options.log.fail(.{ foe_ident, .None });
2003
2010
  if (foe.active.volatiles.Substitute) {
2004
- return options.log.activateMove(foe_ident, .SubstituteBlock, state.move);
2011
+ return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
2005
2012
  }
2006
2013
 
2007
2014
  const avg = @max((foe.stored().hp + side.stored().hp) / 2, 1);
2008
2015
  foe.stored().hp -= avg;
2009
2016
  side.stored().hp = avg;
2010
2017
 
2011
- try options.log.sethp(foe_ident, foe.stored(), .Silent);
2012
- try options.log.sethp(ident, side.stored(), .None);
2018
+ try options.log.sethp(.{ foe_ident, foe.stored(), .Silent });
2019
+ try options.log.sethp(.{ ident, side.stored(), .None });
2013
2020
  }
2014
2021
 
2015
2022
  pub fn paralyze(battle: anytype, player: Player, state: *State, options: anytype) !void {
2016
2023
  var foe = battle.foe(player);
2017
2024
  const foe_ident = battle.active(player.foe());
2018
2025
 
2019
- if (Status.is(foe.stored().status, .PAR)) return options.log.fail(foe_ident, .Paralysis);
2020
- if (state.immune()) return options.log.immune(foe_ident, .None);
2021
- if (Status.any(foe.stored().status)) return options.log.fail(foe_ident, .None);
2026
+ if (Status.is(foe.stored().status, .PAR)) {
2027
+ return options.log.fail(.{ foe_ident, .Paralysis });
2028
+ }
2029
+ if (state.immune()) return options.log.immune(.{ foe_ident, .None });
2030
+ if (Status.any(foe.stored().status)) return options.log.fail(.{ foe_ident, .None });
2022
2031
  if (state.miss) {
2023
- try options.log.lastmiss();
2024
- return options.log.miss(battle.active(player));
2032
+ try options.log.lastmiss(.{});
2033
+ return options.log.miss(.{battle.active(player)});
2025
2034
  } else if (foe.active.volatiles.Substitute) {
2026
- return options.log.activateMove(foe_ident, .SubstituteBlock, state.move);
2035
+ return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
2027
2036
  }
2028
2037
 
2029
2038
  foe.stored().status = Status.init(.PAR);
2030
2039
  foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
2031
2040
 
2032
- try options.log.status(foe_ident, foe.stored().status, .None);
2041
+ try options.log.status(.{ foe_ident, foe.stored().status, .None });
2033
2042
  }
2034
2043
 
2035
2044
  pub fn paralyzeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -2044,14 +2053,15 @@ pub const Effects = struct {
2044
2053
 
2045
2054
  foe_stored.status = Status.init(.PAR);
2046
2055
  foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
2047
- try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
2056
+
2057
+ try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
2048
2058
 
2049
2059
  // TODO: check for status berry
2050
2060
  }
2051
2061
 
2052
2062
  pub fn payDay(battle: anytype, player: Player, _: *State, options: anytype) !void {
2053
2063
  if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
2054
- try options.log.fieldactivate(); // FIXME .PayDay
2064
+ try options.log.fieldactivate(.{}); // FIXME .PayDay
2055
2065
  }
2056
2066
  }
2057
2067
 
@@ -2060,17 +2070,17 @@ pub const Effects = struct {
2060
2070
  var foe = battle.foe(player);
2061
2071
 
2062
2072
  if (foe.active.volatiles.PerishSong) {
2063
- return options.log.fail(battle.active(player.foe()), .None);
2073
+ return options.log.fail(.{ battle.active(player.foe()), .None });
2064
2074
  } else if (!side.active.volatiles.PerishSong) {
2065
2075
  side.active.volatiles.PerishSong = true;
2066
2076
  side.active.volatiles.perish_song = 4;
2067
- try options.log.start(battle.active(player), .PerishSong3Silent);
2077
+ try options.log.start(.{ battle.active(player), .PerishSong3Silent });
2068
2078
  }
2069
2079
  foe.active.volatiles.PerishSong = true;
2070
2080
  foe.active.volatiles.perish_song = 4;
2071
- try options.log.start(battle.active(player.foe()), .PerishSong3Silent);
2081
+ try options.log.start(.{ battle.active(player.foe()), .PerishSong3Silent });
2072
2082
 
2073
- try options.log.fieldactivate(); // FIXME .PerishSong
2083
+ try options.log.fieldactivate(.{}); // FIXME .PerishSong
2074
2084
  }
2075
2085
 
2076
2086
  pub fn poison(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -2080,24 +2090,24 @@ pub const Effects = struct {
2080
2090
 
2081
2091
  const toxic = state.move == .Toxic;
2082
2092
  if (state.immune() or foe.active.types.includes(.Poison)) {
2083
- return options.log.immune(foe_ident, .None);
2093
+ return options.log.immune(.{ foe_ident, .None });
2084
2094
  } else if (Status.any(foe_stored.status)) {
2085
2095
  // Pokémon Showdown considers Toxic to be a status even in Generation II and so
2086
2096
  // will not include a fail reason for Toxic vs. Poison or vice-versa...
2087
- return options.log.fail(foe_ident, if (Status.is(foe_stored.status, .PSN))
2097
+ return options.log.fail(.{ foe_ident, if (Status.is(foe_stored.status, .PSN))
2088
2098
  if (!showdown)
2089
- .Poison
2099
+ Fail.Poison
2090
2100
  else if (toxic == (foe_stored.status == Status.TOX))
2091
- if (toxic) .Toxic else .Poison
2101
+ if (toxic) Fail.Toxic else Fail.Poison
2092
2102
  else
2093
- .None
2103
+ Fail.None
2094
2104
  else
2095
- .None);
2105
+ Fail.None });
2096
2106
  } else if (foe.active.volatiles.Substitute) {
2097
- return options.log.activateMove(foe_ident, .SubstituteBlock, state.move);
2107
+ return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
2098
2108
  } else if (state.miss) {
2099
- try options.log.lastmiss();
2100
- return options.log.miss(battle.active(player));
2109
+ try options.log.lastmiss(.{});
2110
+ return options.log.miss(.{battle.active(player)});
2101
2111
  }
2102
2112
 
2103
2113
  foe_stored.status = Status.init(.PSN);
@@ -2107,7 +2117,7 @@ pub const Effects = struct {
2107
2117
  foe.active.volatiles.toxic = 0;
2108
2118
  }
2109
2119
 
2110
- try options.log.status(foe_ident, foe_stored.status, .None);
2120
+ try options.log.status(.{ foe_ident, foe_stored.status, .None });
2111
2121
 
2112
2122
  // TODO: check for status berry
2113
2123
  }
@@ -2123,7 +2133,8 @@ pub const Effects = struct {
2123
2133
  if (foe.conditions.Safeguard) return;
2124
2134
 
2125
2135
  foe_stored.status = Status.init(.PSN);
2126
- try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
2136
+
2137
+ try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
2127
2138
 
2128
2139
  // TODO: check for status berry
2129
2140
  }
@@ -2138,9 +2149,7 @@ pub const Effects = struct {
2138
2149
 
2139
2150
  const failed = !state.first or volatiles.Substitute or
2140
2151
  !try Rolls.protect(battle, player, volatiles.protect, options);
2141
- if (failed) {
2142
- return options.log.fail(battle.active(player), .None);
2143
- }
2152
+ if (failed) return options.log.fail(.{ battle.active(player), .None });
2144
2153
 
2145
2154
  volatiles.protect = @max(8, volatiles.protect + 1);
2146
2155
  if (state.move == .Endure) {
@@ -2148,7 +2157,7 @@ pub const Effects = struct {
2148
2157
  } else {
2149
2158
  volatiles.Protect = true;
2150
2159
  }
2151
- try options.log.singleturn(battle.active(player), state.move);
2160
+ try options.log.singleturn(.{ battle.active(player), state.move });
2152
2161
  }
2153
2162
 
2154
2163
  pub fn psychUp(battle: anytype, player: Player, _: *State, options: anytype) !void {
@@ -2157,11 +2166,11 @@ pub const Effects = struct {
2157
2166
 
2158
2167
  // Pokémon Showdown should fail when the enemy has no boosts/unboosts but doesn't
2159
2168
  if (!showdown and @as(u32, @bitCast(foe.active.boosts)) == 0) {
2160
- return options.log.fail(battle.active(player), .None);
2169
+ return options.log.fail(.{ battle.active(player), .None });
2161
2170
  }
2162
2171
 
2163
2172
  side.active.boosts = foe.active.boosts;
2164
- try options.log.copyboost(battle.active(player), battle.active(player.foe()));
2173
+ try options.log.copyboost(.{ battle.active(player), battle.active(player.foe()) });
2165
2174
  }
2166
2175
 
2167
2176
  pub fn pursuit(battle: anytype, player: Player, state: *State, _: anytype) !void {
@@ -2172,7 +2181,7 @@ pub const Effects = struct {
2172
2181
  pub const rage = struct {
2173
2182
  pub fn start(battle: anytype, player: Player, _: *State, options: anytype) !void {
2174
2183
  battle.side(player).active.volatiles.Rage = true;
2175
- try options.log.singlemove(battle.active(player), Move.Rage);
2184
+ try options.log.singlemove(.{ battle.active(player), Move.Rage });
2176
2185
  }
2177
2186
 
2178
2187
  pub fn damage(battle: anytype, player: Player, state: *State, _: anytype) !void {
@@ -2192,18 +2201,18 @@ pub const Effects = struct {
2192
2201
  var side = battle.side(player);
2193
2202
 
2194
2203
  if (side.active.volatiles.LeechSeed) {
2195
- try options.log.end(battle.active(player), .LeechSeedFrom);
2204
+ try options.log.end(.{ battle.active(player), .LeechSeedFrom });
2196
2205
  side.active.volatiles.LeechSeed = false;
2197
2206
  }
2198
2207
 
2199
2208
  if (side.conditions.Spikes) {
2200
- try options.log.spikesend(battle.active(player));
2209
+ try options.log.sideend(.{ player, protocol.Side.Spikes, battle.active(player) });
2201
2210
  side.conditions.Spikes = false;
2202
2211
  }
2203
2212
 
2204
2213
  if (side.active.volatiles.bind.duration > 0) {
2205
2214
  const reason = @intFromEnum(End.Bind) + side.active.volatiles.bind.reason - 1;
2206
- try options.log.end(battle.active(player), @enumFromInt(reason));
2215
+ try options.log.end(.{ battle.active(player), @as(End, @enumFromInt(reason)) });
2207
2216
  side.active.volatiles.bind = .{};
2208
2217
  }
2209
2218
  }
@@ -2211,18 +2220,14 @@ pub const Effects = struct {
2211
2220
  pub fn recoil(battle: anytype, player: Player, state: *State, options: anytype) !void {
2212
2221
  var side = battle.side(player);
2213
2222
  var stored = side.stored();
2223
+ const ident = battle.active(player);
2214
2224
 
2215
2225
  assert(state.damage > 0);
2216
2226
 
2217
2227
  const damage: i16 = @intCast(@max(state.damage / 4, 1));
2218
2228
  stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
2219
2229
 
2220
- try options.log.damageOf(
2221
- battle.active(player),
2222
- stored,
2223
- .RecoilOf,
2224
- battle.active(player.foe()),
2225
- );
2230
+ try options.log.damage(.{ ident, stored, .RecoilOf, battle.active(player.foe()) });
2226
2231
  }
2227
2232
 
2228
2233
  pub fn reversal(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -2285,7 +2290,7 @@ pub const Effects = struct {
2285
2290
 
2286
2291
  pub fn sandstorm(battle: anytype, player: Player, _: *State, options: anytype) !void {
2287
2292
  if (battle.field.weather != .Sandstorm) return weather(battle, .Sandstorm, options);
2288
- return options.log.fail(battle.active(player), .None);
2293
+ return options.log.fail(.{ battle.active(player), .None });
2289
2294
  }
2290
2295
 
2291
2296
  pub fn safeguard(battle: anytype, player: Player, _: *State, options: anytype) !void {
@@ -2309,11 +2314,13 @@ pub const Effects = struct {
2309
2314
  ) !void {
2310
2315
  var side = battle.side(player);
2311
2316
 
2312
- if (@field(side.conditions, key)) return options.log.fail(battle.active(player), .None);
2317
+ if (@field(side.conditions, key)) {
2318
+ return options.log.fail(.{ battle.active(player), .None });
2319
+ }
2313
2320
 
2314
2321
  @field(side.conditions, key) = true;
2315
2322
  @field(side.conditions, value) = 5;
2316
- try options.log.sidestart(player, @field(protocol.Side, key));
2323
+ try options.log.sidestart(.{ player, @field(protocol.Side, key) });
2317
2324
  }
2318
2325
 
2319
2326
  pub fn sketch(battle: anytype, player: Player, _: *State, options: anytype) !void {
@@ -2324,9 +2331,9 @@ pub const Effects = struct {
2324
2331
 
2325
2332
  if (showdown) {
2326
2333
  // yes, actually "Splash". '|nothing' gets blindly mapped to '|-activate||move:Splash'
2327
- try options.log.activate(battle.active(player), .Splash);
2334
+ try options.log.activate(.{ battle.active(player), .Splash });
2328
2335
  } else {
2329
- try options.log.fail(battle.active(player), .None);
2336
+ try options.log.fail(.{ battle.active(player), .None });
2330
2337
  }
2331
2338
  }
2332
2339
 
@@ -2336,14 +2343,14 @@ pub const Effects = struct {
2336
2343
  const foe_ident = battle.active(player.foe());
2337
2344
 
2338
2345
  if (Status.any(foe_stored.status)) {
2339
- return options.log.fail(
2346
+ return options.log.fail(.{
2340
2347
  foe_ident,
2341
- if (Status.is(foe_stored.status, .SLP)) .Sleep else .None,
2342
- );
2348
+ if (Status.is(foe_stored.status, .SLP)) Fail.Sleep else Fail.None,
2349
+ });
2343
2350
  }
2344
2351
 
2345
2352
  if (foe.active.volatiles.Substitute) {
2346
- return options.log.activateMove(foe_ident, .SubstituteBlock, state.move);
2353
+ return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
2347
2354
  }
2348
2355
 
2349
2356
  // Sleep Clause Mod
@@ -2354,22 +2361,21 @@ pub const Effects = struct {
2354
2361
  }
2355
2362
 
2356
2363
  foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
2357
- try options.log.statusFrom(foe_ident, foe_stored.status, state.move);
2364
+ try options.log.status(.{ foe_ident, foe_stored.status, .From, state.move });
2358
2365
  }
2359
2366
 
2360
2367
  pub fn sleepTalk(battle: anytype, player: Player, state: *State, options: anytype) !void {
2361
2368
  const side = battle.side(player);
2362
2369
 
2363
2370
  if (!Status.is(side.stored().status, .SLP)) {
2364
- if (!showdown) try options.log.fail(battle.active(player), .None);
2365
- return;
2371
+ return if (!showdown) try options.log.fail(.{ battle.active(player), .None });
2366
2372
  }
2367
2373
 
2368
2374
  var n: u3 = 0;
2369
2375
  for (side.active.moves, 0..) |m, i| {
2370
2376
  if (state.mslot - 1 != i and Move.get(m.id).extra.sleep_talk) n += 1;
2371
2377
  }
2372
- if (n == 0) return options.log.fail(battle.active(player), .None);
2378
+ if (n == 0) return options.log.fail(.{ battle.active(player), .None });
2373
2379
 
2374
2380
  _ = try Rolls.sleepTalk(battle, player, n, state.mslot, options); // TODO
2375
2381
  }
@@ -2378,23 +2384,23 @@ pub const Effects = struct {
2378
2384
  if (Status.is(battle.side(player).stored().status, .SLP)) return true;
2379
2385
  state.damage = 0;
2380
2386
  state.miss = true; // TODO is this necessary?
2381
- if (!showdown) try options.log.fail(battle.active(player), .None);
2387
+ if (!showdown) try options.log.fail(.{ battle.active(player), .None });
2382
2388
  return false;
2383
2389
  }
2384
2390
 
2385
2391
  pub fn spikes(battle: anytype, player: Player, _: *State, options: anytype) !void {
2386
2392
  var foe = battle.foe(player);
2387
- if (foe.conditions.Spikes) return options.log.fail(battle.active(player), .None);
2393
+ if (foe.conditions.Spikes) return options.log.fail(.{ battle.active(player), .None });
2388
2394
 
2389
2395
  foe.conditions.Spikes = true;
2390
- try options.log.sidestart(player.foe(), .Spikes);
2396
+ try options.log.sidestart(.{ player.foe(), protocol.Side.Spikes });
2391
2397
  }
2392
2398
 
2393
2399
  pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
2394
2400
  _ = .{ battle, player, state, options }; // TODO
2395
2401
  // var foe = battle.foe(player);
2396
2402
 
2397
- // if (state.miss) return options.log.fail(battle.active(player), .None);
2403
+ // if (state.miss) return options.log.fail(.{ battle.active(player), .None });
2398
2404
 
2399
2405
  // const last = foe.lastMove(false);
2400
2406
  // if (showdown) {
@@ -2405,28 +2411,28 @@ pub const Effects = struct {
2405
2411
  }
2406
2412
 
2407
2413
  pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
2408
- try options.log.activate(battle.active(player), .Splash);
2414
+ try options.log.activate(.{ battle.active(player), .Splash });
2409
2415
  }
2410
2416
 
2411
2417
  pub fn substitute(battle: anytype, player: Player, _: *State, options: anytype) !void {
2412
2418
  var side = battle.side(player);
2413
2419
  if (side.active.volatiles.Substitute) {
2414
- return options.log.fail(battle.active(player), .Substitute);
2420
+ return options.log.fail(.{ battle.active(player), .Substitute });
2415
2421
  }
2416
2422
 
2417
2423
  assert(side.stored().stats.hp <= 1023);
2418
2424
  const hp: u8 = @intCast(side.stored().stats.hp / 4);
2419
2425
 
2420
2426
  if (hp == 0 or side.stored().hp <= hp) {
2421
- return options.log.fail(battle.active(player), .Weak);
2427
+ return options.log.fail(.{ battle.active(player), .Weak });
2422
2428
  }
2423
2429
 
2424
2430
  side.stored().hp -= hp;
2425
2431
  side.active.volatiles.substitute = hp;
2426
2432
  side.active.volatiles.Substitute = true;
2427
2433
  side.active.volatiles.bind = .{};
2428
- try options.log.start(battle.active(player), .Substitute);
2429
- try options.log.damage(battle.active(player), side.stored(), .None);
2434
+ try options.log.start(.{ battle.active(player), .Substitute });
2435
+ try options.log.damage(.{ battle.active(player), side.stored(), .None });
2430
2436
  }
2431
2437
 
2432
2438
  pub fn sunnyDay(battle: anytype, _: Player, _: *State, options: anytype) !void {
@@ -2436,14 +2442,14 @@ pub const Effects = struct {
2436
2442
  fn weather(battle: anytype, w: Weather, options: anytype) !void {
2437
2443
  battle.field.weather = w;
2438
2444
  battle.field.weather_duration = 5;
2439
- try options.log.weather(w, .None);
2445
+ try options.log.weather(.{ w, protocol.Weather.None });
2440
2446
  }
2441
2447
 
2442
2448
  pub fn teleport(battle: anytype, player: Player, _: *State, options: anytype) !void {
2443
2449
  if (showdown) return;
2444
2450
 
2445
- try options.log.fail(battle.active(player), .None);
2446
- try options.log.laststill();
2451
+ try options.log.fail(.{ battle.active(player), .None });
2452
+ try options.log.laststill(.{});
2447
2453
  }
2448
2454
 
2449
2455
  pub fn thief(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -2458,11 +2464,11 @@ pub const Effects = struct {
2458
2464
  side.stored().item = foe.stored().item;
2459
2465
  foe.stored().item = .None;
2460
2466
 
2461
- try options.log.item(
2467
+ try options.log.item(.{
2462
2468
  battle.active(player),
2463
2469
  side.stored().item,
2464
2470
  battle.active(player.foe()),
2465
- );
2471
+ });
2466
2472
  }
2467
2473
 
2468
2474
  pub fn thrashing(battle: anytype, player: Player, _: *State, options: anytype) !void {
@@ -2484,11 +2490,11 @@ pub const Effects = struct {
2484
2490
  side.active.volatiles.dirty = false;
2485
2491
 
2486
2492
  if (foe.active.volatiles.Transform) {
2487
- return options.log.fail(battle.active(player), .None);
2493
+ return options.log.fail(.{ battle.active(player), .None });
2488
2494
  } else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
2489
- if (!showdown) return options.log.fail(battle.active(player), .None);
2490
- try options.log.lastmiss();
2491
- return options.log.miss(battle.active(player));
2495
+ if (!showdown) return options.log.fail(.{ battle.active(player), .None });
2496
+ try options.log.lastmiss(.{});
2497
+ return options.log.miss(.{battle.active(player)});
2492
2498
  }
2493
2499
 
2494
2500
  side.active.volatiles.Transform = true;
@@ -2510,7 +2516,7 @@ pub const Effects = struct {
2510
2516
  side.active.moves[i].pp = if (m.id != .None) if (m.id == .Sketch) 1 else 5 else 0;
2511
2517
  }
2512
2518
 
2513
- try options.log.transform(battle.active(player), foe_ident);
2519
+ try options.log.transform(.{ battle.active(player), foe_ident });
2514
2520
  }
2515
2521
 
2516
2522
  pub fn triAttack(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -2542,10 +2548,10 @@ pub const Effects = struct {
2542
2548
  inline for (STATS, STAT_UP_REASONS) |s, r| {
2543
2549
  assert(@field(boosts, s) >= -6 and @field(boosts, s) <= 6);
2544
2550
  if (@field(boosts, s) == 6 or @field(stats, s) == MAX_STAT_VALUE) {
2545
- try options.log.boost(battle.active(player), r, 0);
2551
+ try options.log.boost(.{ battle.active(player), r, 0 });
2546
2552
  } else {
2547
2553
  try boostStat(battle, side, stats, boosts, s, 1);
2548
- try options.log.boost(battle.active(player), r, 1);
2554
+ try options.log.boost(.{ battle.active(player), r, 1 });
2549
2555
  }
2550
2556
  }
2551
2557
  }
@@ -2555,7 +2561,7 @@ pub const Effects = struct {
2555
2561
  var side = battle.side(player);
2556
2562
  const ident = battle.active(player);
2557
2563
 
2558
- if (state.miss) return log.fail(ident, .None);
2564
+ if (state.miss) return log.fail(.{ ident, .None });
2559
2565
 
2560
2566
  var stats = &side.active.stats;
2561
2567
  var boosts = &side.active.boosts;
@@ -2564,42 +2570,52 @@ pub const Effects = struct {
2564
2570
  switch (move.effect) {
2565
2571
  .AttackUp1, .AttackUp2, .Rage, .Swagger => {
2566
2572
  assert(boosts.atk >= -6 and boosts.atk <= 6);
2567
- if (boosts.atk == 6 or stats.atk == MAX_STAT_VALUE) return log.fail(ident, .None);
2573
+ if (boosts.atk == 6 or stats.atk == MAX_STAT_VALUE) {
2574
+ return log.fail(.{ ident, .None });
2575
+ }
2568
2576
  const n: u2 = if (move.effect == .AttackUp2 or move.effect == .Swagger) 2 else 1;
2569
2577
  try boostStat(battle, side, stats, boosts, "atk", n);
2570
2578
  const reason = if (showdown and move.effect == .Rage) Boost.Rage else Boost.Attack;
2571
- try log.boost(ident, reason, n);
2579
+ try log.boost(.{ ident, reason, n });
2572
2580
  },
2573
2581
  .DefenseUp1, .DefenseUp2, .SkullBash => {
2574
2582
  assert(boosts.def >= -6 and boosts.def <= 6);
2575
- if (boosts.def == 6 or stats.def == MAX_STAT_VALUE) return log.fail(ident, .None);
2583
+ if (boosts.def == 6 or stats.def == MAX_STAT_VALUE) {
2584
+ return log.fail(.{ ident, .None });
2585
+ }
2576
2586
  const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
2577
2587
  try boostStat(battle, side, stats, boosts, "def", n);
2578
- try log.boost(ident, .Defense, n);
2588
+ try log.boost(.{ ident, .Defense, n });
2579
2589
  },
2580
2590
  .SpeedUp2 => {
2581
2591
  assert(boosts.spe >= -6 and boosts.spe <= 6);
2582
- if (boosts.spe == 6 or stats.spe == MAX_STAT_VALUE) return log.fail(ident, .None);
2592
+ if (boosts.spe == 6 or stats.spe == MAX_STAT_VALUE) {
2593
+ return log.fail(.{ ident, .None });
2594
+ }
2583
2595
  try boostStat(battle, side, stats, boosts, "spe", 2);
2584
- try log.boost(ident, .Speed, 2);
2596
+ try log.boost(.{ ident, .Speed, 2 });
2585
2597
  },
2586
2598
  .SpAtkUp1 => {
2587
2599
  assert(boosts.spa >= -6 and boosts.spa <= 6);
2588
- if (boosts.spa == 6 or stats.spa == MAX_STAT_VALUE) return log.fail(ident, .None);
2600
+ if (boosts.spa == 6 or stats.spa == MAX_STAT_VALUE) {
2601
+ return log.fail(.{ ident, .None });
2602
+ }
2589
2603
  try boostStat(battle, side, stats, boosts, "spa", 1);
2590
- try log.boost(ident, .SpecialAttack, 1);
2604
+ try log.boost(.{ ident, .SpecialAttack, 1 });
2591
2605
  },
2592
2606
  .SpDefUp2 => {
2593
2607
  assert(boosts.spd >= -6 and boosts.spd <= 6);
2594
- if (boosts.spd == 6 or stats.spd == MAX_STAT_VALUE) return log.fail(ident, .None);
2608
+ if (boosts.spd == 6 or stats.spd == MAX_STAT_VALUE) {
2609
+ return log.fail(.{ ident, .None });
2610
+ }
2595
2611
  try boostStat(battle, side, stats, boosts, "spd", 2);
2596
- try log.boost(ident, .SpecialDefense, 2);
2612
+ try log.boost(.{ ident, .SpecialDefense, 2 });
2597
2613
  },
2598
2614
  .EvasionUp1 => {
2599
2615
  assert(boosts.evasion >= -6 and boosts.evasion <= 6);
2600
- if (boosts.evasion == 6) return log.fail(ident, .None);
2616
+ if (boosts.evasion == 6) return log.fail(.{ ident, .None });
2601
2617
  boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
2602
- try log.boost(ident, .Evasion, 1);
2618
+ try log.boost(.{ ident, .Evasion, 1 });
2603
2619
  if (state.move == .Minimize) side.active.volatiles.minimized = true;
2604
2620
  },
2605
2621
  else => unreachable,
@@ -2612,8 +2628,8 @@ pub const Effects = struct {
2612
2628
  const foe_ident = battle.active(player.foe());
2613
2629
 
2614
2630
  if (foe.active.volatiles.Mist) {
2615
- try log.activate(foe_ident, .Mist);
2616
- return log.fail(foe_ident, .None);
2631
+ try log.activate(.{ foe_ident, .Mist });
2632
+ return log.fail(.{ foe_ident, .None });
2617
2633
  }
2618
2634
 
2619
2635
  const fail = foe.active.volatiles.Substitute or state.miss or !state.proc;
@@ -2622,69 +2638,73 @@ pub const Effects = struct {
2622
2638
  var boosts = &foe.active.boosts;
2623
2639
 
2624
2640
  const move = Move.get(state.move);
2641
+ const none: Fail = .None;
2625
2642
  switch (move.effect) {
2626
2643
  .AttackDown1, .AttackDown2, .AttackDownChance => {
2627
2644
  assert(boosts.atk >= -6 and boosts.atk <= 6);
2628
2645
  if (fail or boosts.atk == -6) {
2629
- return if (move.effect != .AttackDownChance) try log.fail(foe_ident, .None);
2646
+ return if (move.effect != .AttackDownChance) try log.fail(.{ foe_ident, none });
2630
2647
  } else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
2631
- try options.log.lastmiss();
2632
- return options.log.miss(battle.active(player));
2648
+ try options.log.lastmiss(.{});
2649
+ return options.log.miss(.{battle.active(player)});
2633
2650
  } else if (stats.atk == 1) {
2634
- return if (move.effect != .AttackDownChance) try log.fail(foe_ident, .None);
2651
+ return if (move.effect != .AttackDownChance) try log.fail(.{ foe_ident, none });
2635
2652
  }
2636
2653
  const n: u2 = if (move.effect == .AttackDown2) 2 else 1;
2637
2654
  try unboostStat(battle, foe, stats, boosts, "atk", n);
2638
- try log.boost(foe_ident, .Attack, -@as(i8, n));
2655
+ try log.boost(.{ foe_ident, .Attack, -@as(i8, n) });
2639
2656
  },
2640
2657
  .DefenseDown1, .DefenseDown2, .DefenseDownChance => {
2641
2658
  assert(boosts.def >= -6 and boosts.def <= 6);
2642
2659
  if (fail or boosts.def == -6) {
2643
- return if (move.effect != .DefenseDownChance) try log.fail(foe_ident, .None);
2660
+ if (move.effect != .DefenseDownChance) try log.fail(.{ foe_ident, none });
2661
+ return;
2644
2662
  } else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
2645
- try options.log.lastmiss();
2646
- return options.log.miss(battle.active(player));
2663
+ try options.log.lastmiss(.{});
2664
+ return options.log.miss(.{battle.active(player)});
2647
2665
  } else if (stats.def == 1) {
2648
- return if (move.effect != .DefenseDownChance) try log.fail(foe_ident, .None);
2666
+ if (move.effect != .DefenseDownChance) try log.fail(.{ foe_ident, none });
2667
+ return;
2649
2668
  }
2650
2669
  const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
2651
2670
  try unboostStat(battle, foe, stats, boosts, "def", n);
2652
- try log.boost(foe_ident, .Defense, -@as(i8, n));
2671
+ try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
2653
2672
  },
2654
2673
  .SpeedDown1, .SpeedDown2, .SpeedDownChance => {
2655
2674
  assert(boosts.spe >= -6 and boosts.spe <= 6);
2656
2675
  if (fail or boosts.spe == -6) {
2657
- return if (move.effect != .SpeedDownChance) try log.fail(foe_ident, .None);
2676
+ return if (move.effect != .SpeedDownChance) try log.fail(.{ foe_ident, none });
2658
2677
  } else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
2659
- try options.log.lastmiss();
2660
- return options.log.miss(battle.active(player));
2678
+ try options.log.lastmiss(.{});
2679
+ return options.log.miss(.{battle.active(player)});
2661
2680
  } else if (stats.spe == 1) {
2662
- return if (move.effect != .SpeedDownChance) try log.fail(foe_ident, .None);
2681
+ return if (move.effect != .SpeedDownChance) try log.fail(.{ foe_ident, none });
2663
2682
  }
2664
2683
  const n: u2 = if (move.effect == .SpeedDown2) 2 else 1;
2665
2684
  try unboostStat(battle, foe, stats, boosts, "spe", n);
2666
- try log.boost(foe_ident, .Speed, -@as(i8, n));
2685
+ try log.boost(.{ foe_ident, .Speed, -@as(i8, n) });
2667
2686
  },
2668
2687
  .SpDefDownChance => {
2669
2688
  assert(boosts.spd >= -6 and boosts.spd <= 6);
2670
2689
  if (fail or boosts.spd == -6) return;
2671
2690
  if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
2672
- try options.log.lastmiss();
2673
- return options.log.miss(battle.active(player));
2691
+ try options.log.lastmiss(.{});
2692
+ return options.log.miss(.{battle.active(player)});
2674
2693
  } else if (stats.spd == 1) return;
2675
2694
  try unboostStat(battle, foe, stats, boosts, "spd", 1);
2676
- try log.boost(foe_ident, .SpecialDefense, -1);
2695
+ try log.boost(.{ foe_ident, .SpecialDefense, -1 });
2677
2696
  },
2678
2697
  .AccuracyDown1, .AccuracyDownChance => {
2679
2698
  assert(boosts.accuracy >= -6 and boosts.accuracy <= 6);
2680
2699
  if (fail or boosts.accuracy == -6) {
2681
- return if (move.effect != .AccuracyDownChance) try log.fail(foe_ident, .None);
2700
+ if (move.effect != .AccuracyDownChance) try log.fail(.{ foe_ident, none });
2701
+ return;
2682
2702
  } else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
2683
- try options.log.lastmiss();
2684
- return options.log.miss(battle.active(player));
2703
+ try options.log.lastmiss(.{});
2704
+ return options.log.miss(.{battle.active(player)});
2685
2705
  }
2686
2706
  boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
2687
- try log.boost(foe_ident, .Accuracy, -1);
2707
+ try log.boost(.{ foe_ident, .Accuracy, -1 });
2688
2708
  },
2689
2709
  else => unreachable,
2690
2710
  }
@@ -3224,7 +3244,7 @@ pub const Rolls = struct {
3224
3244
 
3225
3245
  const move: Move = overridden orelse move: {
3226
3246
  if (showdown) {
3227
- var r = battle.rng.range(u8, 0, @intCast(Move.METRONOME.len - n));
3247
+ var r = battle.rng.range(u8, 0, @as(u8, @intCast(Move.METRONOME.len - n)));
3228
3248
  for (moves) |m| {
3229
3249
  if (m.id == .None) break;
3230
3250
  if (@intFromEnum(m.id) > @intFromEnum(Move.METRONOME[r])) break;