@pkmn/engine 0.1.0-dev.028ab165 → 0.1.0-dev.047d6e71

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.
@@ -14,7 +14,7 @@ const util = @import("../common/util.zig");
14
14
  const Player = @import("../common/data.zig").Player;
15
15
  const Optional = @import("../common/optional.zig").Optional;
16
16
 
17
- const data = @import("./data.zig");
17
+ const data = @import("data.zig");
18
18
 
19
19
  const enabled = options.chance;
20
20
  const showdown = options.showdown;
@@ -699,7 +699,7 @@ test "Chance.damage" {
699
699
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
700
700
 
701
701
  try chance.damage(.P1, 217);
702
- try expectValue(@as(u8, 217), chance.actions.p1.damage);
702
+ try expectValue(217, chance.actions.p1.damage);
703
703
  try expectProbability(&chance.probability, 1, 39);
704
704
  }
705
705
 
@@ -824,7 +824,7 @@ test "Chance.present" {
824
824
  } else {
825
825
  try expectProbability(&chance.probability, 25, 256);
826
826
  }
827
- try expectValue(@as(u3, 4), chance.actions.p2.present);
827
+ try expectValue(4, chance.actions.p2.present);
828
828
 
829
829
  chance.reset();
830
830
 
@@ -834,7 +834,7 @@ test "Chance.present" {
834
834
  } else {
835
835
  try expectProbability(&chance.probability, 51, 256);
836
836
  }
837
- try expectValue(@as(u3, 1), chance.actions.p1.present);
837
+ try expectValue(1, chance.actions.p1.present);
838
838
  }
839
839
 
840
840
  test "Chance.magnitude" {
@@ -846,7 +846,7 @@ test "Chance.magnitude" {
846
846
  } else {
847
847
  try expectProbability(&chance.probability, 13, 256);
848
848
  }
849
- try expectValue(@as(u3, 7), chance.actions.p2.magnitude);
849
+ try expectValue(7, chance.actions.p2.magnitude);
850
850
 
851
851
  chance.reset();
852
852
 
@@ -856,7 +856,7 @@ test "Chance.magnitude" {
856
856
  } else {
857
857
  try expectProbability(&chance.probability, 51, 256);
858
858
  }
859
- try expectValue(@as(u3, 5), chance.actions.p1.magnitude);
859
+ try expectValue(5, chance.actions.p1.magnitude);
860
860
  }
861
861
 
862
862
  test "Chance.tripleKick" {
@@ -864,7 +864,7 @@ test "Chance.tripleKick" {
864
864
 
865
865
  try chance.tripleKick(.P1, 2);
866
866
  try expectProbability(&chance.probability, 1, 3);
867
- try expectValue(@as(u2, 2), chance.actions.p1.triple_kick);
867
+ try expectValue(2, chance.actions.p1.triple_kick);
868
868
  }
869
869
 
870
870
  test "Chance.spite" {
@@ -872,7 +872,7 @@ test "Chance.spite" {
872
872
 
873
873
  try chance.spite(.P1, 3);
874
874
  try expectProbability(&chance.probability, 1, 4);
875
- try expectValue(@as(u3, 3), chance.actions.p1.spite);
875
+ try expectValue(3, chance.actions.p1.spite);
876
876
  }
877
877
 
878
878
  test "Chance.conversion2" {
@@ -899,7 +899,7 @@ test "Chance.psywave" {
899
899
 
900
900
  try chance.psywave(.P2, 100, 150);
901
901
  try expectProbability(&chance.probability, 1, 150);
902
- try expectValue(@as(u8, 100), chance.actions.p2.psywave);
902
+ try expectValue(100, chance.actions.p2.psywave);
903
903
  }
904
904
 
905
905
  test "Chance.metronome" {
@@ -915,19 +915,19 @@ test "Chance.moveSlot" {
915
915
 
916
916
  try chance.moveSlot(.P2, 2, 2);
917
917
  try expectProbability(&chance.probability, 1, 2);
918
- try expectValue(@as(u4, 2), chance.actions.p2.move_slot);
918
+ try expectValue(2, chance.actions.p2.move_slot);
919
919
 
920
920
  chance.reset();
921
921
 
922
922
  try chance.moveSlot(.P1, 1, 3);
923
923
  try expectProbability(&chance.probability, 1, 3);
924
- try expectValue(@as(u4, 1), chance.actions.p1.move_slot);
924
+ try expectValue(1, chance.actions.p1.move_slot);
925
925
 
926
926
  chance.reset();
927
927
 
928
928
  try chance.moveSlot(.P1, 4, 1);
929
929
  try expectProbability(&chance.probability, 1, 1);
930
- try expectValue(@as(u4, 4), chance.actions.p1.move_slot);
930
+ try expectValue(4, chance.actions.p1.move_slot);
931
931
  }
932
932
 
933
933
  test "Chance.forceSwitch" {
@@ -935,19 +935,19 @@ test "Chance.forceSwitch" {
935
935
 
936
936
  try chance.forceSwitch(.P2, 2, 2);
937
937
  try expectProbability(&chance.probability, 1, 2);
938
- try expectValue(@as(u4, 2), chance.actions.p2.force_switch);
938
+ try expectValue(2, chance.actions.p2.force_switch);
939
939
 
940
940
  chance.reset();
941
941
 
942
942
  try chance.forceSwitch(.P1, 1, 3);
943
943
  try expectProbability(&chance.probability, 1, 3);
944
- try expectValue(@as(u4, 1), chance.actions.p1.force_switch);
944
+ try expectValue(1, chance.actions.p1.force_switch);
945
945
 
946
946
  chance.reset();
947
947
 
948
948
  try chance.forceSwitch(.P1, 4, 1);
949
949
  try expectProbability(&chance.probability, 1, 1);
950
- try expectValue(@as(u4, 4), chance.actions.p1.force_switch);
950
+ try expectValue(4, chance.actions.p1.force_switch);
951
951
  }
952
952
 
953
953
  test "Chance.multiHit" {
@@ -955,21 +955,21 @@ test "Chance.multiHit" {
955
955
 
956
956
  try chance.multiHit(.P1, 3);
957
957
  try expectProbability(&chance.probability, 3, 8);
958
- try expectValue(@as(u4, 3), chance.actions.p1.multi_hit);
958
+ try expectValue(3, chance.actions.p1.multi_hit);
959
959
 
960
960
  chance.reset();
961
961
 
962
962
  try chance.multiHit(.P2, 5);
963
963
  try expectProbability(&chance.probability, 1, 8);
964
- try expectValue(@as(u4, 5), chance.actions.p2.multi_hit);
964
+ try expectValue(5, chance.actions.p2.multi_hit);
965
965
  }
966
966
 
967
967
  test "Chance.duration" {
968
968
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
969
969
 
970
970
  chance.duration(.sleep, .P1, .P2, 2);
971
- try expectValue(@as(u4, 2), chance.actions.p1.duration);
972
- try expectValue(@as(u3, 1), chance.actions.p2.durations.sleep);
971
+ try expectValue(2, chance.actions.p1.duration);
972
+ try expectValue(1, chance.actions.p2.durations.sleep);
973
973
  }
974
974
 
975
975
  test "Chance.sleep" {
@@ -979,7 +979,7 @@ test "Chance.sleep" {
979
979
  chance.actions.p1.durations.sleep = @intCast(i);
980
980
  try chance.sleep(.P1, 0);
981
981
  try expectProbability(&chance.probability, 1, d);
982
- try expectValue(@as(u3, 0), chance.actions.p1.durations.sleep);
982
+ try expectValue(0, chance.actions.p1.durations.sleep);
983
983
 
984
984
  chance.reset();
985
985
 
@@ -1003,7 +1003,7 @@ test "Chance.confusion" {
1003
1003
  chance.actions.p2.durations.confusion = @intCast(i);
1004
1004
  try chance.confusion(.P2, 0);
1005
1005
  try expectProbability(&chance.probability, 1, d);
1006
- try expectValue(@as(u3, 0), chance.actions.p2.durations.confusion);
1006
+ try expectValue(0, chance.actions.p2.durations.confusion);
1007
1007
  try expectValue(false, chance.actions.p2.durations.thrash);
1008
1008
 
1009
1009
  chance.reset();
@@ -1028,7 +1028,7 @@ test "Chance.confusion" {
1028
1028
  chance.actions.p2.durations.confusion = @intCast(i);
1029
1029
  try chance.confusion(.P2, 0);
1030
1030
  try expectProbability(&chance.probability, 1, d);
1031
- try expectValue(@as(u3, 0), chance.actions.p2.durations.confusion);
1031
+ try expectValue(0, chance.actions.p2.durations.confusion);
1032
1032
  try expectValue(true, chance.actions.p2.durations.thrash);
1033
1033
 
1034
1034
  chance.reset();
@@ -1054,7 +1054,7 @@ test "Chance.disable" {
1054
1054
  chance.actions.p2.durations.disable = @intCast(i);
1055
1055
  try chance.disable(.P2, 0);
1056
1056
  try expectProbability(&chance.probability, 1, d);
1057
- try expectValue(@as(u4, 0), chance.actions.p2.durations.disable);
1057
+ try expectValue(0, chance.actions.p2.durations.disable);
1058
1058
 
1059
1059
  chance.reset();
1060
1060
  }
@@ -1074,7 +1074,7 @@ test "Chance.disable" {
1074
1074
  chance.actions.p2.durations.disable = @intCast(i);
1075
1075
  try chance.disable(.P2, 0);
1076
1076
  try expectProbability(&chance.probability, 1, d);
1077
- try expectValue(@as(u4, 0), chance.actions.p2.durations.disable);
1077
+ try expectValue(0, chance.actions.p2.durations.disable);
1078
1078
 
1079
1079
  chance.reset();
1080
1080
  }
@@ -1099,7 +1099,7 @@ test "Chance.attacking" {
1099
1099
  chance.actions.p2.durations.attacking = @intCast(i);
1100
1100
  try chance.attacking(.P2, 0);
1101
1101
  try expectProbability(&chance.probability, 1, d);
1102
- try expectValue(@as(u3, 0), chance.actions.p2.durations.attacking);
1102
+ try expectValue(0, chance.actions.p2.durations.attacking);
1103
1103
 
1104
1104
  chance.reset();
1105
1105
  }
@@ -1123,7 +1123,7 @@ test "Chance.binding" {
1123
1123
  chance.actions.p2.durations.binding = @intCast(i);
1124
1124
  try chance.binding(.P2, 0);
1125
1125
  try expectProbability(&chance.probability, 1, d);
1126
- try expectValue(@as(u3, 0), chance.actions.p2.durations.binding);
1126
+ try expectValue(0, chance.actions.p2.durations.binding);
1127
1127
 
1128
1128
  chance.reset();
1129
1129
  }
@@ -1147,7 +1147,7 @@ test "Chance.encore" {
1147
1147
  chance.actions.p2.durations.encore = @intCast(i);
1148
1148
  try chance.encore(.P2, 0);
1149
1149
  try expectProbability(&chance.probability, 1, d);
1150
- try expectValue(@as(u3, 0), chance.actions.p2.durations.encore);
1150
+ try expectValue(0, chance.actions.p2.durations.encore);
1151
1151
 
1152
1152
  chance.reset();
1153
1153
  }
@@ -239,7 +239,7 @@ pub const DVs = packed struct(u16) {
239
239
  };
240
240
  const p: u6 = @intCast(1 + ((5 * @as(u8, (dvs.atk & 0b1000) | ((dvs.def & 0b1000)) >> 1 |
241
241
  ((dvs.spe & 0b1000)) >> 2 | ((dvs.spc & 0b1000)) >> 3) + (dvs.spc & 0b0011)) >> 1));
242
- var t = @as(u8, dvs.def & 0b0011) + (@as(u8, dvs.atk & 0b0011) << 2);
242
+ const t = @as(u8, dvs.def & 0b0011) + (@as(u8, dvs.atk & 0b0011) << 2);
243
243
  return .{ .gender = g, .pow = p, .type = Type.hiddenPower(t) };
244
244
  }
245
245
  };
@@ -248,12 +248,12 @@ test DVs {
248
248
  var dvs = DVs.from(.Mewtwo, .{ .def = 13 });
249
249
  try expectEqual(Gender.Unknown, dvs.gender);
250
250
  try expectEqual(Type.Ice, dvs.type);
251
- try expectEqual(@as(u8, 70), dvs.power());
251
+ try expectEqual(70, dvs.power());
252
252
 
253
253
  dvs = DVs.from(.Pikachu, .{ .spc = 14 });
254
254
  try expectEqual(Gender.Male, dvs.gender);
255
255
  try expectEqual(Type.Dark, dvs.type);
256
- try expectEqual(@as(u8, 69), dvs.power());
256
+ try expectEqual(69, dvs.power());
257
257
 
258
258
  dvs = DVs.from(.Cyndaquil, .{ .atk = 1, .def = 3, .spe = 10, .spc = 9 });
259
259
  try expectEqual(Gender.Female, dvs.gender);
@@ -380,9 +380,9 @@ test Stats {
380
380
  try expectEqual(0, stats.def);
381
381
 
382
382
  var base = Species.get(.Gyarados).stats;
383
- try expectEqual(@as(u16, 127), Stats(u16).calc("hp", base.hp, 0, 5120, 38));
383
+ try expectEqual(127, Stats(u16).calc("hp", base.hp, 0, 5120, 38));
384
384
  base = Species.get(.Pidgeot).stats;
385
- try expectEqual(@as(u16, 279), Stats(u16).calc("spe", base.spe, 15, 63001, 100));
385
+ try expectEqual(279, Stats(u16).calc("spe", base.spe, 15, 63001, 100));
386
386
  }
387
387
 
388
388
  pub const Boosts = packed struct(u32) {
@@ -428,8 +428,8 @@ test Move {
428
428
  try expectEqual(Move.Effect.ConfusionChance, move.effect);
429
429
  try expectEqual(rng.Gen12.percent(50), move.accuracy);
430
430
  try expectEqual(rng.Gen12.percent(100), move.chance);
431
- try expectEqual(@as(u8, 5), Move.pp(.DynamicPunch));
432
- try expectEqual(@as(i8, -1), Move.get(.Counter).priority);
431
+ try expectEqual(5, Move.pp(.DynamicPunch));
432
+ try expectEqual(-1, Move.get(.Counter).priority);
433
433
  try expect(!move.effect.isHighCritical());
434
434
  try expect(Move.get(.KarateChop).effect.isHighCritical());
435
435
  try expect(Move.get(.RazorWind).effect.isHighCritical());
@@ -439,7 +439,7 @@ pub const Species = species.Species;
439
439
 
440
440
  test Species {
441
441
  try expectEqual(152, @intFromEnum(Species.Chikorita));
442
- try expectEqual(@as(u8, 100), Species.get(.Celebi).stats.spd);
442
+ try expectEqual(100, Species.get(.Celebi).stats.spd);
443
443
  }
444
444
 
445
445
  pub const Type = types.Type;
@@ -452,7 +452,7 @@ test Types {
452
452
  try expect(!Type.Steel.special());
453
453
  try expect(Type.Dark.special());
454
454
 
455
- try expectEqual(@as(u8, 3), Type.Water.precedence());
455
+ try expectEqual(3, Type.Water.precedence());
456
456
  try expect(Type.Bug.precedence() > Type.Poison.precedence());
457
457
 
458
458
  try expectEqual(Effectiveness.Super, Type.effectiveness(.Ghost, .Psychic));
@@ -7,8 +7,6 @@ const common = @import("../common/data.zig");
7
7
  const data = @import("data.zig");
8
8
  const mechanics = @import("mechanics.zig");
9
9
 
10
- const assert = std.debug.assert;
11
-
12
10
  const Player = common.Player;
13
11
  const Result = common.Result;
14
12
 
@@ -42,7 +40,7 @@ pub fn runMove(battle: anytype, player: Player, state: *State, options: anytype)
42
40
  var log = options.log;
43
41
 
44
42
  var side = battle.side(player);
45
- var volatiles = &side.active.volatiles;
43
+ const volatiles = &side.active.volatiles;
46
44
 
47
45
  const ident = battle.active(player);
48
46
  const foe_ident = battle.active(player.foe());
@@ -140,11 +140,11 @@ fn start(battle: anytype, options: anytype) !Result {
140
140
  const p1 = battle.side(.P1);
141
141
  const p2 = battle.side(.P2);
142
142
 
143
- var p1_slot = findFirstAlive(p1);
143
+ const p1_slot = findFirstAlive(p1);
144
144
  assert(!showdown or p1_slot == 1);
145
145
  if (p1_slot == 0) return if (findFirstAlive(p2) == 0) Result.Tie else Result.Lose;
146
146
 
147
- var p2_slot = findFirstAlive(p2);
147
+ const p2_slot = findFirstAlive(p2);
148
148
  assert(!showdown or p2_slot == 1);
149
149
  if (p2_slot == 0) return Result.Win;
150
150
 
@@ -403,7 +403,9 @@ fn executeMove(
403
403
 
404
404
  assert(choice.type == .Move);
405
405
  var mslot: u4 = @intCast(choice.data);
406
+ _ = &mslot;
406
407
  var move = Move.Pound; // TODO
408
+ _ = &move;
407
409
 
408
410
  if (!try beforeMove(battle, player, move, options)) return null;
409
411
 
@@ -565,7 +567,7 @@ fn resetCant(volatiles: *align(8) Volatiles) void {
565
567
 
566
568
  pub fn canMove(battle: anytype, player: Player, state: *State, options: anytype) !bool {
567
569
  var side = battle.side(player);
568
- var volatiles = &side.active.volatiles;
570
+ const volatiles = &side.active.volatiles;
569
571
 
570
572
  // FIXME: pass in target to avoid lookup?
571
573
  const target = if (Move.get(state.move).target == .Self) player else player.foe();
@@ -997,7 +999,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
997
999
 
998
1000
  // Weather
999
1001
  if (battle.field.weather != .None) {
1000
- assert(battle.field.weather_duration > 0);
1001
1002
  battle.field.weather_duration -= 1;
1002
1003
 
1003
1004
  if (battle.field.weather_duration == 0) {
@@ -1056,7 +1057,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1056
1057
  var volatiles = &side.active.volatiles;
1057
1058
 
1058
1059
  if (volatiles.PerishSong) {
1059
- assert(volatiles.perish_song > 0);
1060
1060
  volatiles.perish_song -= 1;
1061
1061
 
1062
1062
  assert(volatiles.perish_song < 3);
@@ -1086,7 +1086,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1086
1086
 
1087
1087
  // Mystery Berry
1088
1088
  inline for (players) |player| {
1089
- var stored = battle.side(player).stored();
1089
+ const stored = battle.side(player).stored();
1090
1090
 
1091
1091
  if (stored.item == .MysteryBerry) {
1092
1092
  // TODO stored and active moves, transform, etc
@@ -1193,7 +1193,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1193
1193
  var volatiles = &side.active.volatiles;
1194
1194
 
1195
1195
  if (volatiles.Encore) {
1196
- assert(volatiles.encore > 0);
1197
1196
  volatiles.encore -= 1;
1198
1197
 
1199
1198
  if (volatiles.encore == 0 or side.active.move(mslot).pp == 0) {
@@ -1215,7 +1214,6 @@ fn updateSideCondition(
1215
1214
  ) !void {
1216
1215
  var side = battle.side(player);
1217
1216
  if (@field(side.conditions, key)) {
1218
- assert(@field(side.conditions, value) > 0);
1219
1217
  @field(side.conditions, value) -= 1;
1220
1218
 
1221
1219
  if (@field(side.conditions, value) == 0) {
@@ -1306,14 +1304,32 @@ pub const Effects = struct {
1306
1304
  }
1307
1305
 
1308
1306
  pub fn bellyDrum(battle: anytype, player: Player, state: *State, options: anytype) !void {
1309
- _ = .{ battle, player, state, options }; // TODO
1310
- // assert(!state.miss);
1311
- // var side = battle.side(player);
1312
- // try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
1313
- // TODO
1314
- // |-boost|p1a: Poliwag|atk|2|[silent]
1315
- // |-fail|p1a: Poliwag
1316
- //
1307
+ assert(!state.miss);
1308
+ var side = battle.side(player);
1309
+
1310
+ const ident = battle.active(player);
1311
+
1312
+ assert(side.active.boosts.atk >= -6 and side.active.boosts.atk <= 6);
1313
+ if (side.active.boosts.atk == 6 or side.active.boosts.atk == MAX_STAT_VALUE) {
1314
+ return options.log.fail(.{ ident, .None });
1315
+ }
1316
+
1317
+ const hp = @max(side.stored().stats.hp / 2, 1);
1318
+ if (side.stored().hp <= hp) {
1319
+ try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
1320
+ try options.log.boost(.{ ident, .Attack, @as(i8, 2), .Silent });
1321
+ return options.log.fail(.{ ident, .None });
1322
+ } else {
1323
+ side.stored().hp -= hp;
1324
+ try options.log.damage(.{ ident, side.stored(), .None });
1325
+
1326
+ const before = side.active.boosts.atk;
1327
+ var i: usize = 0;
1328
+ while (i < 6 and side.active.stats.atk < MAX_STAT_VALUE) : (i += 1) {
1329
+ try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
1330
+ }
1331
+ try options.log.boost(.{ ident, .Attack, @as(i8, side.active.boosts.atk - before) });
1332
+ }
1317
1333
  }
1318
1334
 
1319
1335
  pub const bide = struct {
@@ -1325,7 +1341,6 @@ pub const Effects = struct {
1325
1341
 
1326
1342
  if (!volatiles.Bide) return;
1327
1343
 
1328
- assert(volatiles.attacks > 0);
1329
1344
  // if (options.calc.modify(player, .attacking)) |extend| {
1330
1345
  // if (!extend) volatiles.attacks = 0;
1331
1346
  // } else {
@@ -1478,8 +1493,8 @@ pub const Effects = struct {
1478
1493
  side.stored().hp -= @max(side.stored().stats.hp / 2, 1);
1479
1494
  try options.log.damage(.{ ident, side.stored(), .Curse });
1480
1495
  } else {
1481
- var stats = &side.active.stats;
1482
- var boosts = &side.active.boosts;
1496
+ const stats = &side.active.stats;
1497
+ const boosts = &side.active.boosts;
1483
1498
 
1484
1499
  const max_atk = boosts.atk == 6 or stats.atk == MAX_STAT_VALUE;
1485
1500
  const max_def = boosts.def == 6 or stats.def == MAX_STAT_VALUE;
@@ -1599,8 +1614,16 @@ pub const Effects = struct {
1599
1614
  try options.log.start(.{ foe_ident, .Encore, move.id });
1600
1615
  }
1601
1616
 
1602
- pub fn explode(battle: anytype, player: Player, state: *State, options: anytype) !void {
1603
- _ = .{ battle, player, state, options }; // TODO
1617
+ pub fn explode(battle: anytype, player: Player, _: *State, _: anytype) !void {
1618
+ var side = battle.side(player);
1619
+ var stored = side.stored();
1620
+
1621
+ stored.hp = 0;
1622
+ // Pokémon Showdown sets the status to 0 on faint(), and we need to do the same to be able
1623
+ // to correctly implement Pokémon Showdown's dumb speed-based switch mechanics
1624
+ if (!showdown) stored.status = 0;
1625
+ side.active.volatiles.LeechSeed = false;
1626
+ side.active.volatiles.DestinyBond = false;
1604
1627
  }
1605
1628
 
1606
1629
  pub fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
@@ -1726,8 +1749,26 @@ pub const Effects = struct {
1726
1749
  foe.active.volatiles.frozen = true;
1727
1750
  }
1728
1751
 
1729
- pub fn furyCutter(battle: anytype, player: Player, state: *State, options: anytype) !void {
1730
- _ = .{ battle, player, state, options }; // TODO
1752
+ // TODO FIXME showdown calls this before calcDamage (after checkHit) and modifies state.bp!
1753
+ pub fn furyCutter(battle: anytype, player: Player, state: *State, _: anytype) !void {
1754
+ var side = battle.side(player);
1755
+ var volatiles = &side.active.volatiles;
1756
+
1757
+ if (state.miss) {
1758
+ volatiles.fury_cutter = 0;
1759
+ return;
1760
+ }
1761
+
1762
+ assert(volatiles.fury_cutter >= 0 and volatiles.fury_cutter <= 5);
1763
+ if (volatiles.fury_cutter == 5) return;
1764
+ volatiles.fury_cutter += 1;
1765
+
1766
+ if (showdown) {
1767
+ state.bp *= 2;
1768
+ assert(state.bp <= 160);
1769
+ } else {
1770
+ state.damage *|= 2;
1771
+ }
1731
1772
  }
1732
1773
 
1733
1774
  pub fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -1888,7 +1929,29 @@ pub const Effects = struct {
1888
1929
  }
1889
1930
 
1890
1931
  pub fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
1891
- _ = .{ battle, player, state, options }; // TODO
1932
+ var side = battle.side(player);
1933
+
1934
+ if (state.miss) {
1935
+ if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
1936
+ try options.log.lastmiss(.{});
1937
+ return options.log.miss(.{battle.active(player)});
1938
+ }
1939
+
1940
+ const move = battle.foe(player).lastMove(false);
1941
+ if (move == .None or move == .Struggle) {
1942
+ return options.log.fail(.{ battle.active(player.foe()), .None });
1943
+ }
1944
+
1945
+ var slot: u4 = 0;
1946
+ for (side.active.moves, 0..) |m, i| {
1947
+ if (m.id == move) return options.log.fail(.{ battle.active(player.foe()), .None });
1948
+ if (m.id == .Mimic) slot = @intCast(i + 1);
1949
+ }
1950
+ assert(slot != 0 and slot == state.mslot);
1951
+
1952
+ side.active.move(slot).id = move;
1953
+ side.active.move(slot).pp = 5;
1954
+ try options.log.start(.{ battle.active(player), .Mimic, move });
1892
1955
  }
1893
1956
 
1894
1957
  pub fn mirrorMove(
@@ -2185,11 +2248,10 @@ pub const Effects = struct {
2185
2248
  }
2186
2249
 
2187
2250
  pub fn damage(battle: anytype, player: Player, state: *State, _: anytype) !void {
2188
- var side = battle.side(player);
2189
- var volatiles = &side.active.volatiles;
2251
+ const side = battle.side(player);
2190
2252
 
2191
- assert(volatiles.Rage);
2192
- state.damage *|= (volatiles.rage +| 1);
2253
+ assert(side.active.volatiles.Rage);
2254
+ state.damage *|= (side.active.volatiles.rage +| 1);
2193
2255
  }
2194
2256
  };
2195
2257
 
@@ -2397,17 +2459,37 @@ pub const Effects = struct {
2397
2459
  }
2398
2460
 
2399
2461
  pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
2400
- _ = .{ battle, player, state, options }; // TODO
2401
- // var foe = battle.foe(player);
2462
+ var foe = battle.foe(player);
2402
2463
 
2403
- // if (state.miss) return options.log.fail(.{ battle.active(player), .None });
2464
+ const last = foe.lastMove(false);
2465
+ if (state.miss or last == .None or last == .Struggle) {
2466
+ return options.log.fail(.{ battle.active(player.foe()), .None });
2467
+ }
2404
2468
 
2405
- // const last = foe.lastMove(false);
2406
- // if (showdown) {
2407
- // const roll = try Rolls.spite(battle, player, options);
2408
- // } else {
2409
- // if (last != .None) {}
2410
- // }
2469
+ var slot: u4 = 0;
2470
+ for (foe.active.moves, 0..) |m, i| {
2471
+ if (m.id == last) slot = @intCast(i + 1);
2472
+ }
2473
+ // If the move was called via Metronome it might not exist
2474
+ if (slot == 0 or foe.active.move(slot).pp == 0) {
2475
+ return options.log.fail(.{ battle.active(player.foe()), .None });
2476
+ }
2477
+
2478
+ // Pokémon Showdown always deducts 4 instead of randomizing
2479
+ const pp = @min(
2480
+ foe.active.move(slot).pp,
2481
+ if (showdown) 4 else try Rolls.spite(battle, player, options),
2482
+ );
2483
+ foe.active.move(slot).pp -= pp;
2484
+ if (!foe.active.volatiles.Transform) {
2485
+ var move_slot = foe.stored().move(slot);
2486
+ assert(move_slot.pp > 0);
2487
+
2488
+ move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
2489
+ assert(foe.active.move(slot).pp == foe.stored().move(slot).pp);
2490
+ }
2491
+
2492
+ try options.log.activate(.{ battle.active(player.foe()), .Spite, last, pp });
2411
2493
  }
2412
2494
 
2413
2495
  pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
@@ -2541,8 +2623,8 @@ pub const Effects = struct {
2541
2623
  pub fn allStatUpChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
2542
2624
  var side = battle.side(player);
2543
2625
 
2544
- var stats = &side.active.stats;
2545
- var boosts = &side.active.boosts;
2626
+ const stats = &side.active.stats;
2627
+ const boosts = &side.active.boosts;
2546
2628
 
2547
2629
  if (!state.proc) return;
2548
2630
  inline for (STATS, STAT_UP_REASONS) |s, r| {
@@ -2563,7 +2645,7 @@ pub const Effects = struct {
2563
2645
 
2564
2646
  if (state.miss) return log.fail(.{ ident, .None });
2565
2647
 
2566
- var stats = &side.active.stats;
2648
+ const stats = &side.active.stats;
2567
2649
  var boosts = &side.active.boosts;
2568
2650
 
2569
2651
  const move = Move.get(state.move);
@@ -2634,7 +2716,7 @@ pub const Effects = struct {
2634
2716
 
2635
2717
  const fail = foe.active.volatiles.Substitute or state.miss or !state.proc;
2636
2718
 
2637
- var stats = &foe.active.stats;
2719
+ const stats = &foe.active.stats;
2638
2720
  var boosts = &foe.active.boosts;
2639
2721
 
2640
2722
  const move = Move.get(state.move);
@@ -2718,9 +2800,9 @@ fn unmodifiedStats(battle: anytype, side: *Side) *Stats(u16) {
2718
2800
  }
2719
2801
 
2720
2802
  fn recomputeStats(battle: anytype, side: *Side) void {
2721
- var stats = unmodifiedStats(battle, side);
2803
+ const stats = unmodifiedStats(battle, side);
2722
2804
  inline for (STATS) |s| {
2723
- var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(side.active.boosts, s)) + 6))];
2805
+ const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(side.active.boosts, s)) + 6))];
2724
2806
  const val = @field(stats, s) * mod[0] / mod[1];
2725
2807
  @field(side.active.stats, s) = @max(1, @min(MAX_STAT_VALUE, val));
2726
2808
  }
@@ -2743,7 +2825,7 @@ fn boostStat(
2743
2825
  n: u2,
2744
2826
  ) !void {
2745
2827
  @field(boosts, s) = @intCast(@min(6, @as(i8, @field(boosts, s)) + n));
2746
- var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
2828
+ const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
2747
2829
  const stat = @field(unmodifiedStats(battle, side), s);
2748
2830
  @field(stats, s) = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
2749
2831
  if (comptime std.mem.eql(u8, s, "atk")) {
@@ -2766,7 +2848,7 @@ fn unboostStat(
2766
2848
  n: u2,
2767
2849
  ) !void {
2768
2850
  @field(boosts, s) = @intCast(@max(-6, @as(i8, @field(boosts, s)) - n));
2769
- var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
2851
+ const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
2770
2852
  const stat = @field(unmodifiedStats(battle, side), s);
2771
2853
  @field(stats, s) = @max(1, stat * mod[0] / mod[1]);
2772
2854
  if (comptime std.mem.eql(u8, s, "atk")) {
@@ -3013,13 +3095,12 @@ pub const Rolls = struct {
3013
3095
  return hits;
3014
3096
  }
3015
3097
 
3016
- fn spite(battle: anytype, player: Player, options: anytype) !u8 {
3017
- const pp = if (options.calc.overridden(player, .spite)) |val|
3098
+ fn spite(battle: anytype, player: Player, options: anytype) !u3 {
3099
+ assert(!showdown);
3100
+ const pp: u3 = if (options.calc.overridden(player, .spite)) |val|
3018
3101
  val + 1
3019
- else if (showdown)
3020
- battle.rng.range(u8, 2, 6)
3021
3102
  else
3022
- (battle.rng.next() & 3) + 2;
3103
+ @intCast((battle.rng.next() & 3) + 2);
3023
3104
 
3024
3105
  assert(pp >= 2 and pp <= 5);
3025
3106
  try options.chance.spite(player, pp);
@@ -3467,7 +3548,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3467
3548
  .Move => {
3468
3549
  const side = battle.side(player);
3469
3550
 
3470
- var active = &side.active;
3551
+ const active = &side.active;
3471
3552
  const stored = side.stored();
3472
3553
 
3473
3554
  // While players are not given any input options on the cartridge in these cases,