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

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.
Files changed (79) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +41 -14
  3. package/build/pkg/addon/browser.d.ts +3 -0
  4. package/build/pkg/addon/browser.js +44 -0
  5. package/build/pkg/addon/browser.js.map +1 -0
  6. package/build/pkg/addon/node.d.ts +3 -0
  7. package/build/pkg/addon/node.js +97 -0
  8. package/build/pkg/addon/node.js.map +1 -0
  9. package/build/pkg/addon/wasm.d.ts +2 -0
  10. package/build/pkg/addon/wasm.js +21 -0
  11. package/build/pkg/addon/wasm.js.map +1 -0
  12. package/build/pkg/addon.d.ts +22 -0
  13. package/build/pkg/addon.js +35 -58
  14. package/build/pkg/addon.js.map +1 -1
  15. package/build/pkg/data/layout.json +15 -2
  16. package/build/pkg/data/protocol.json +23 -9
  17. package/build/pkg/data.d.ts +27 -5
  18. package/build/pkg/data.js.map +1 -1
  19. package/build/pkg/gen1.d.ts +10 -13
  20. package/build/pkg/gen1.js +1 -1
  21. package/build/pkg/gen1.js.map +1 -1
  22. package/build/pkg/index.d.ts +55 -13
  23. package/build/pkg/index.js +15 -10
  24. package/build/pkg/index.js.map +1 -1
  25. package/build/pkg/protocol.d.ts +20 -1
  26. package/build/pkg/protocol.js +109 -107
  27. package/build/pkg/protocol.js.map +1 -1
  28. package/build/tools/debug.js +36 -70
  29. package/build/tools/debug.js.map +1 -1
  30. package/build/tools/display/debug.jsx +141 -0
  31. package/build/tools/display/debug.jsx.map +1 -0
  32. package/build/tools/display/index.js +141 -0
  33. package/build/tools/display/index.js.map +1 -0
  34. package/build/tools/display/ui.jsx +349 -0
  35. package/build/tools/display/ui.jsx.map +1 -0
  36. package/build/tools/display/util.js +18 -0
  37. package/build/tools/display/util.js.map +1 -0
  38. package/build.zig +253 -89
  39. package/package.json +58 -30
  40. package/src/bin/install-pkmn-engine +29 -19
  41. package/src/bin/pkmn-debug +1 -1
  42. package/src/lib/common/array.zig +77 -0
  43. package/src/lib/common/data.zig +4 -4
  44. package/src/lib/common/debug.zig +39 -8
  45. package/src/lib/common/js.zig +4 -2
  46. package/src/lib/common/optional.zig +14 -10
  47. package/src/lib/common/options.zig +6 -0
  48. package/src/lib/common/protocol.zig +75 -79
  49. package/src/lib/common/rational.zig +128 -32
  50. package/src/lib/common/rng.zig +7 -9
  51. package/src/lib/common/util.zig +50 -1
  52. package/src/lib/gen1/calc.zig +686 -159
  53. package/src/lib/gen1/chance.zig +405 -280
  54. package/src/lib/gen1/data/moves.zig +2 -4
  55. package/src/lib/gen1/data/species.zig +2 -4
  56. package/src/lib/gen1/data/types.zig +1 -2
  57. package/src/lib/gen1/data.zig +24 -27
  58. package/src/lib/gen1/helpers.zig +92 -548
  59. package/src/lib/gen1/mechanics.zig +373 -257
  60. package/src/lib/gen2/calc.zig +37 -27
  61. package/src/lib/gen2/chance.zig +84 -496
  62. package/src/lib/gen2/data/items.zig +1 -3
  63. package/src/lib/gen2/data/moves.zig +2 -4
  64. package/src/lib/gen2/data/species.zig +1 -3
  65. package/src/lib/gen2/data/types.zig +1 -2
  66. package/src/lib/gen2/data.zig +19 -22
  67. package/src/lib/gen2/generated.zig +19 -26
  68. package/src/lib/gen2/helpers.zig +46 -25
  69. package/src/lib/gen2/mechanics.zig +200 -157
  70. package/src/lib/{binding/node.zig → node.zig} +8 -13
  71. package/src/lib/pkmn.zig +36 -15
  72. package/src/lib/{binding/wasm.zig → wasm.zig} +1 -1
  73. package/src/tools/display/dom.ts +96 -0
  74. package/src/tools/display/ui.css +354 -0
  75. package/build/test/display.js +0 -400
  76. package/build/test/display.js.map +0 -1
  77. package/src/lib/gen1/README.md +0 -894
  78. package/src/lib/gen2/README.md +0 -329
  79. package/src/test/display/pkmn.html.tmpl +0 -368
@@ -1,50 +1,36 @@
1
- const std = @import("std");
2
-
3
- const pkmn = @import("../pkmn.zig");
4
-
5
- const common = @import("../common/data.zig");
6
1
  const DEBUG = @import("../common/debug.zig").print;
7
- const protocol = @import("../common/protocol.zig");
8
- const rng = @import("../common/rng.zig");
9
- const util = @import("../common/util.zig");
10
2
 
3
+ const common = @import("../common/data.zig");
11
4
  const data = @import("data.zig");
12
5
  const generated = @import("generated.zig");
13
-
14
- const assert = std.debug.assert;
15
-
16
- const expectEqual = std.testing.expectEqual;
17
- const expectEqualSlices = std.testing.expectEqualSlices;
18
- const expectEqualStrings = std.testing.expectEqualStrings;
19
-
20
- const showdown = pkmn.options.showdown;
21
-
22
- const Choice = common.Choice;
23
- const ID = common.ID;
24
- const Player = common.Player;
25
- const Result = common.Result;
6
+ const pkmn = @import("../pkmn.zig");
7
+ const protocol = @import("../common/protocol.zig");
8
+ const rng = @import("../common/rng.zig");
9
+ const std = @import("std");
26
10
 
27
11
  const Activate = protocol.Activate;
12
+ const ActivePokemon = data.ActivePokemon;
13
+ const assert = std.debug.assert;
28
14
  const Boost = protocol.Boost;
15
+ const Boosts = data.Boosts;
16
+ const Choice = common.Choice;
29
17
  const Damage = protocol.Damage;
18
+ const Effectiveness = data.Effectiveness;
30
19
  const End = protocol.End;
20
+ const expectEqual = std.testing.expectEqual;
21
+ const expectEqualSlices = std.testing.expectEqualSlices;
22
+ const expectEqualStrings = std.testing.expectEqualStrings;
31
23
  const Fail = protocol.Fail;
32
- const Heal = protocol.Heal;
33
- const Start = protocol.Start;
34
-
35
24
  const Gen12 = rng.Gen12;
36
-
37
- const PointerType = util.PointerType;
38
-
39
- const ActivePokemon = data.ActivePokemon;
40
- const Boosts = data.Boosts;
41
- const Effectiveness = data.Effectiveness;
25
+ const Heal = protocol.Heal;
26
+ const ID = common.ID;
42
27
  const Item = data.Item;
43
28
  const Move = data.Move;
44
- const MoveSlot = data.MoveSlot;
45
- const Pokemon = data.Pokemon;
29
+ const Player = common.Player;
30
+ const Result = common.Result;
31
+ const showdown = pkmn.options.showdown;
46
32
  const Side = data.Side;
47
- const Species = data.Species;
33
+ const Start = protocol.Start;
48
34
  const Stats = data.Stats;
49
35
  const Status = data.Status;
50
36
  const TriAttack = data.TriAttack;
@@ -106,6 +92,7 @@ pub const State = struct {
106
92
  type: Type, // BATTLE_VARS_MOVE_TYPE
107
93
  accuracy: u8, // wEnemyMoveStruct + MOVE_ACC
108
94
  move: Move, // wCurPlayerMove
95
+ hit: u3 = 0,
109
96
  from: Move = .None,
110
97
  mslot: u4, // wCurMoveNum
111
98
  first: bool, // wEnemyGoesFirst
@@ -140,11 +127,11 @@ fn start(battle: anytype, options: anytype) !Result {
140
127
  const p1 = battle.side(.P1);
141
128
  const p2 = battle.side(.P2);
142
129
 
143
- var p1_slot = findFirstAlive(p1);
130
+ const p1_slot = findFirstAlive(p1);
144
131
  assert(!showdown or p1_slot == 1);
145
132
  if (p1_slot == 0) return if (findFirstAlive(p2) == 0) Result.Tie else Result.Lose;
146
133
 
147
- var p2_slot = findFirstAlive(p2);
134
+ const p2_slot = findFirstAlive(p2);
148
135
  assert(!showdown or p2_slot == 1);
149
136
  if (p2_slot == 0) return Result.Win;
150
137
 
@@ -256,7 +243,6 @@ fn switchIn(battle: anytype, player: Player, slot: u8, kind: SwitchIn, options:
256
243
  } else {
257
244
  try options.log.switched(.{ battle.active(player), incoming });
258
245
  }
259
- options.chance.switched(player, side.pokemon[0].position, out);
260
246
 
261
247
  if (side.conditions.Spikes and !active.types.includes(.Flying)) {
262
248
  incoming.hp -|= @max(incoming.stats.hp / 8, 1);
@@ -403,7 +389,9 @@ fn executeMove(
403
389
 
404
390
  assert(choice.type == .Move);
405
391
  var mslot: u4 = @intCast(choice.data);
392
+ _ = &mslot;
406
393
  var move = Move.Pound; // TODO
394
+ _ = &move;
407
395
 
408
396
  if (!try beforeMove(battle, player, move, options)) return null;
409
397
 
@@ -440,18 +428,9 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
440
428
  if (Status.is(stored.status, .SLP)) slp: {
441
429
  const before = stored.status;
442
430
  // Even if the EXT bit is set this will still correctly modify the sleep duration
443
- // if (options.calc.modify(player, .sleep)) |extend| {
444
- // if (!extend) stored.status = 0;
445
- // } else {
446
431
  stored.status -= 1;
447
- // }
448
-
449
- const duration = Status.duration(stored.status);
450
- if (!Status.is(stored.status, .EXT)) {
451
- try options.chance.sleep(player, duration);
452
- }
453
432
 
454
- if (duration == 0) {
433
+ if (Status.duration(stored.status) == 0) {
455
434
  try log.curestatus(.{ ident, before, .Message });
456
435
  stored.status = 0; // clears EXT if present
457
436
  resetCant(volatiles);
@@ -481,12 +460,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
481
460
  }
482
461
 
483
462
  if (volatiles.disable.duration > 0) {
484
- // if (options.calc.modify(player, .disable)) |extend| {
485
- // if (!extend) volatiles.disable.duration = 0;
486
- // } else {
487
463
  volatiles.disable.duration -= 1;
488
- // }
489
- try options.chance.disable(player, volatiles.disable.duration);
490
464
 
491
465
  if (volatiles.disable.duration == 0) {
492
466
  volatiles.disable.move = 0;
@@ -496,13 +470,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
496
470
 
497
471
  if (volatiles.Confusion) {
498
472
  assert(volatiles.confusion > 0);
499
-
500
- // if (options.calc.modify(player, .confusion)) |extend| {
501
- // if (!extend) volatiles.confusion = 0;
502
- // } else {
503
473
  volatiles.confusion -= 1;
504
- // }
505
- try options.chance.confusion(player, volatiles.confusion);
506
474
 
507
475
  if (volatiles.confusion == 0) {
508
476
  volatiles.Confusion = false;
@@ -565,7 +533,7 @@ fn resetCant(volatiles: *align(8) Volatiles) void {
565
533
 
566
534
  pub fn canMove(battle: anytype, player: Player, state: *State, options: anytype) !bool {
567
535
  var side = battle.side(player);
568
- var volatiles = &side.active.volatiles;
536
+ const volatiles = &side.active.volatiles;
569
537
 
570
538
  // FIXME: pass in target to avoid lookup?
571
539
  const target = if (Move.get(state.move).target == .Self) player else player.foe();
@@ -663,7 +631,7 @@ pub fn checkCriticalHit(battle: anytype, player: Player, state: *State, options:
663
631
  }
664
632
  assert(stage <= 4);
665
633
 
666
- state.crit = try Rolls.criticalHit(battle, player, CRITICAL_HITS[stage], options);
634
+ state.crit = try Rolls.criticalHit(battle, player, state.hit, CRITICAL_HITS[stage], options);
667
635
  }
668
636
 
669
637
  pub fn calcDamage(
@@ -731,7 +699,7 @@ pub fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype)
731
699
 
732
700
  pub fn randomizeDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
733
701
  if (state.damage <= 1) return;
734
- const random = try Rolls.damage(battle, player, options);
702
+ const random = try Rolls.damage(battle, player, state.hit, options);
735
703
  options.calc.base(player.foe(), state.damage);
736
704
  state.damage = @intCast(@as(u32, state.damage) *% random / 255);
737
705
  options.calc.final(player.foe(), state.damage);
@@ -997,7 +965,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
997
965
 
998
966
  // Weather
999
967
  if (battle.field.weather != .None) {
1000
- assert(battle.field.weather_duration > 0);
1001
968
  battle.field.weather_duration -= 1;
1002
969
 
1003
970
  if (battle.field.weather_duration == 0) {
@@ -1056,7 +1023,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1056
1023
  var volatiles = &side.active.volatiles;
1057
1024
 
1058
1025
  if (volatiles.PerishSong) {
1059
- assert(volatiles.perish_song > 0);
1060
1026
  volatiles.perish_song -= 1;
1061
1027
 
1062
1028
  assert(volatiles.perish_song < 3);
@@ -1086,7 +1052,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1086
1052
 
1087
1053
  // Mystery Berry
1088
1054
  inline for (players) |player| {
1089
- var stored = battle.side(player).stored();
1055
+ const stored = battle.side(player).stored();
1090
1056
 
1091
1057
  if (stored.item == .MysteryBerry) {
1092
1058
  // TODO stored and active moves, transform, etc
@@ -1193,7 +1159,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1193
1159
  var volatiles = &side.active.volatiles;
1194
1160
 
1195
1161
  if (volatiles.Encore) {
1196
- assert(volatiles.encore > 0);
1197
1162
  volatiles.encore -= 1;
1198
1163
 
1199
1164
  if (volatiles.encore == 0 or side.active.move(mslot).pp == 0) {
@@ -1215,7 +1180,6 @@ fn updateSideCondition(
1215
1180
  ) !void {
1216
1181
  var side = battle.side(player);
1217
1182
  if (@field(side.conditions, key)) {
1218
- assert(@field(side.conditions, value) > 0);
1219
1183
  @field(side.conditions, value) -= 1;
1220
1184
 
1221
1185
  if (@field(side.conditions, value) == 0) {
@@ -1228,7 +1192,7 @@ fn updateSideCondition(
1228
1192
  fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result {
1229
1193
  battle.turn += 1;
1230
1194
 
1231
- if (showdown and battle.turn >= 1000) {
1195
+ if (pkmn.options.mod and battle.turn >= 1000) {
1232
1196
  try options.log.tie(.{});
1233
1197
  return Result.Tie;
1234
1198
  } else if (battle.turn >= 65535) {
@@ -1306,14 +1270,32 @@ pub const Effects = struct {
1306
1270
  }
1307
1271
 
1308
1272
  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
- //
1273
+ assert(!state.miss);
1274
+ var side = battle.side(player);
1275
+
1276
+ const ident = battle.active(player);
1277
+
1278
+ assert(side.active.boosts.atk >= -6 and side.active.boosts.atk <= 6);
1279
+ if (side.active.boosts.atk == 6 or side.active.boosts.atk == MAX_STAT_VALUE) {
1280
+ return options.log.fail(.{ ident, .None });
1281
+ }
1282
+
1283
+ const hp = @max(side.stored().stats.hp / 2, 1);
1284
+ if (side.stored().hp <= hp) {
1285
+ try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
1286
+ try options.log.boost(.{ ident, .Attack, @as(i8, 2), .Silent });
1287
+ return options.log.fail(.{ ident, .None });
1288
+ } else {
1289
+ side.stored().hp -= hp;
1290
+ try options.log.damage(.{ ident, side.stored(), .None });
1291
+
1292
+ const before = side.active.boosts.atk;
1293
+ var i: usize = 0;
1294
+ while (i < 6 and side.active.stats.atk < MAX_STAT_VALUE) : (i += 1) {
1295
+ try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
1296
+ }
1297
+ try options.log.boost(.{ ident, .Attack, @as(i8, side.active.boosts.atk - before) });
1298
+ }
1317
1299
  }
1318
1300
 
1319
1301
  pub const bide = struct {
@@ -1325,14 +1307,7 @@ pub const Effects = struct {
1325
1307
 
1326
1308
  if (!volatiles.Bide) return;
1327
1309
 
1328
- assert(volatiles.attacks > 0);
1329
- // if (options.calc.modify(player, .attacking)) |extend| {
1330
- // if (!extend) volatiles.attacks = 0;
1331
- // } else {
1332
1310
  volatiles.attacks -= 1;
1333
- // }
1334
- try options.chance.attacking(player, volatiles.attacks);
1335
-
1336
1311
  if (volatiles.attacks != 0) return options.log.activate(.{ ident, .Bide });
1337
1312
 
1338
1313
  volatiles.Bide = false;
@@ -1478,8 +1453,8 @@ pub const Effects = struct {
1478
1453
  side.stored().hp -= @max(side.stored().stats.hp / 2, 1);
1479
1454
  try options.log.damage(.{ ident, side.stored(), .Curse });
1480
1455
  } else {
1481
- var stats = &side.active.stats;
1482
- var boosts = &side.active.boosts;
1456
+ const stats = &side.active.stats;
1457
+ const boosts = &side.active.boosts;
1483
1458
 
1484
1459
  const max_atk = boosts.atk == 6 or stats.atk == MAX_STAT_VALUE;
1485
1460
  const max_def = boosts.def == 6 or stats.def == MAX_STAT_VALUE;
@@ -1599,8 +1574,16 @@ pub const Effects = struct {
1599
1574
  try options.log.start(.{ foe_ident, .Encore, move.id });
1600
1575
  }
1601
1576
 
1602
- pub fn explode(battle: anytype, player: Player, state: *State, options: anytype) !void {
1603
- _ = .{ battle, player, state, options }; // TODO
1577
+ pub fn explode(battle: anytype, player: Player, _: *State, _: anytype) !void {
1578
+ var side = battle.side(player);
1579
+ var stored = side.stored();
1580
+
1581
+ stored.hp = 0;
1582
+ // Pokémon Showdown sets the status to 0 on faint(), and we need to do the same to be able
1583
+ // to correctly implement Pokémon Showdown's dumb speed-based switch mechanics
1584
+ if (!showdown) stored.status = 0;
1585
+ side.active.volatiles.LeechSeed = false;
1586
+ side.active.volatiles.DestinyBond = false;
1604
1587
  }
1605
1588
 
1606
1589
  pub fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
@@ -1715,7 +1698,7 @@ pub const Effects = struct {
1715
1698
  if (!state.proc) return;
1716
1699
  if (foe.conditions.Safeguard) return;
1717
1700
  // Freeze Clause Mod
1718
- if (showdown) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
1701
+ if (pkmn.options.mod) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
1719
1702
 
1720
1703
  foe_stored.status = Status.init(.FRZ);
1721
1704
  try options.log.status(.{ foe_ident, foe_stored.status, .None });
@@ -1726,8 +1709,26 @@ pub const Effects = struct {
1726
1709
  foe.active.volatiles.frozen = true;
1727
1710
  }
1728
1711
 
1729
- pub fn furyCutter(battle: anytype, player: Player, state: *State, options: anytype) !void {
1730
- _ = .{ battle, player, state, options }; // TODO
1712
+ // TODO FIXME showdown calls this before calcDamage (after checkHit) and modifies state.bp!
1713
+ pub fn furyCutter(battle: anytype, player: Player, state: *State, _: anytype) !void {
1714
+ var side = battle.side(player);
1715
+ var volatiles = &side.active.volatiles;
1716
+
1717
+ if (state.miss) {
1718
+ volatiles.fury_cutter = 0;
1719
+ return;
1720
+ }
1721
+
1722
+ assert(volatiles.fury_cutter >= 0 and volatiles.fury_cutter <= 5);
1723
+ if (volatiles.fury_cutter == 5) return;
1724
+ volatiles.fury_cutter += 1;
1725
+
1726
+ if (showdown) {
1727
+ state.bp *= 2;
1728
+ assert(state.bp <= 160);
1729
+ } else {
1730
+ state.damage *|= 2;
1731
+ }
1731
1732
  }
1732
1733
 
1733
1734
  pub fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -1888,7 +1889,29 @@ pub const Effects = struct {
1888
1889
  }
1889
1890
 
1890
1891
  pub fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
1891
- _ = .{ battle, player, state, options }; // TODO
1892
+ var side = battle.side(player);
1893
+
1894
+ if (state.miss) {
1895
+ if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
1896
+ try options.log.lastmiss(.{});
1897
+ return options.log.miss(.{battle.active(player)});
1898
+ }
1899
+
1900
+ const move = battle.foe(player).lastMove(false);
1901
+ if (move == .None or move == .Struggle) {
1902
+ return options.log.fail(.{ battle.active(player.foe()), .None });
1903
+ }
1904
+
1905
+ var slot: u4 = 0;
1906
+ for (side.active.moves, 0..) |m, i| {
1907
+ if (m.id == move) return options.log.fail(.{ battle.active(player.foe()), .None });
1908
+ if (m.id == .Mimic) slot = @intCast(i + 1);
1909
+ }
1910
+ assert(slot != 0 and slot == state.mslot);
1911
+
1912
+ side.active.move(slot).id = move;
1913
+ side.active.move(slot).pp = 5;
1914
+ try options.log.start(.{ battle.active(player), .Mimic, move });
1892
1915
  }
1893
1916
 
1894
1917
  pub fn mirrorMove(
@@ -2185,11 +2208,10 @@ pub const Effects = struct {
2185
2208
  }
2186
2209
 
2187
2210
  pub fn damage(battle: anytype, player: Player, state: *State, _: anytype) !void {
2188
- var side = battle.side(player);
2189
- var volatiles = &side.active.volatiles;
2211
+ const side = battle.side(player);
2190
2212
 
2191
- assert(volatiles.Rage);
2192
- state.damage *|= (volatiles.rage +| 1);
2213
+ assert(side.active.volatiles.Rage);
2214
+ state.damage *|= (side.active.volatiles.rage +| 1);
2193
2215
  }
2194
2216
  };
2195
2217
 
@@ -2354,7 +2376,7 @@ pub const Effects = struct {
2354
2376
  }
2355
2377
 
2356
2378
  // Sleep Clause Mod
2357
- if (showdown) {
2379
+ if (pkmn.options.mod) {
2358
2380
  for (foe.pokemon) |p| {
2359
2381
  if (Status.is(p.status, .SLP) and !Status.is(p.status, .EXT)) return;
2360
2382
  }
@@ -2397,17 +2419,37 @@ pub const Effects = struct {
2397
2419
  }
2398
2420
 
2399
2421
  pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
2400
- _ = .{ battle, player, state, options }; // TODO
2401
- // var foe = battle.foe(player);
2422
+ var foe = battle.foe(player);
2402
2423
 
2403
- // if (state.miss) return options.log.fail(.{ battle.active(player), .None });
2424
+ const last = foe.lastMove(false);
2425
+ if (state.miss or last == .None or last == .Struggle) {
2426
+ return options.log.fail(.{ battle.active(player.foe()), .None });
2427
+ }
2404
2428
 
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
- // }
2429
+ var slot: u4 = 0;
2430
+ for (foe.active.moves, 0..) |m, i| {
2431
+ if (m.id == last) slot = @intCast(i + 1);
2432
+ }
2433
+ // If the move was called via Metronome it might not exist
2434
+ if (slot == 0 or foe.active.move(slot).pp == 0) {
2435
+ return options.log.fail(.{ battle.active(player.foe()), .None });
2436
+ }
2437
+
2438
+ // Pokémon Showdown always deducts 4 instead of randomizing
2439
+ const pp = @min(
2440
+ foe.active.move(slot).pp,
2441
+ if (showdown) 4 else try Rolls.spite(battle, player, options),
2442
+ );
2443
+ foe.active.move(slot).pp -= pp;
2444
+ if (!foe.active.volatiles.Transform) {
2445
+ var move_slot = foe.stored().move(slot);
2446
+ assert(move_slot.pp > 0);
2447
+
2448
+ move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
2449
+ assert(foe.active.move(slot).pp == foe.stored().move(slot).pp);
2450
+ }
2451
+
2452
+ try options.log.activate(.{ battle.active(player.foe()), .Spite, last, pp });
2411
2453
  }
2412
2454
 
2413
2455
  pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
@@ -2541,8 +2583,8 @@ pub const Effects = struct {
2541
2583
  pub fn allStatUpChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
2542
2584
  var side = battle.side(player);
2543
2585
 
2544
- var stats = &side.active.stats;
2545
- var boosts = &side.active.boosts;
2586
+ const stats = &side.active.stats;
2587
+ const boosts = &side.active.boosts;
2546
2588
 
2547
2589
  if (!state.proc) return;
2548
2590
  inline for (STATS, STAT_UP_REASONS) |s, r| {
@@ -2563,7 +2605,7 @@ pub const Effects = struct {
2563
2605
 
2564
2606
  if (state.miss) return log.fail(.{ ident, .None });
2565
2607
 
2566
- var stats = &side.active.stats;
2608
+ const stats = &side.active.stats;
2567
2609
  var boosts = &side.active.boosts;
2568
2610
 
2569
2611
  const move = Move.get(state.move);
@@ -2634,7 +2676,7 @@ pub const Effects = struct {
2634
2676
 
2635
2677
  const fail = foe.active.volatiles.Substitute or state.miss or !state.proc;
2636
2678
 
2637
- var stats = &foe.active.stats;
2679
+ const stats = &foe.active.stats;
2638
2680
  var boosts = &foe.active.boosts;
2639
2681
 
2640
2682
  const move = Move.get(state.move);
@@ -2718,9 +2760,9 @@ fn unmodifiedStats(battle: anytype, side: *Side) *Stats(u16) {
2718
2760
  }
2719
2761
 
2720
2762
  fn recomputeStats(battle: anytype, side: *Side) void {
2721
- var stats = unmodifiedStats(battle, side);
2763
+ const stats = unmodifiedStats(battle, side);
2722
2764
  inline for (STATS) |s| {
2723
- var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(side.active.boosts, s)) + 6))];
2765
+ const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(side.active.boosts, s)) + 6))];
2724
2766
  const val = @field(stats, s) * mod[0] / mod[1];
2725
2767
  @field(side.active.stats, s) = @max(1, @min(MAX_STAT_VALUE, val));
2726
2768
  }
@@ -2743,7 +2785,7 @@ fn boostStat(
2743
2785
  n: u2,
2744
2786
  ) !void {
2745
2787
  @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))];
2788
+ const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
2747
2789
  const stat = @field(unmodifiedStats(battle, side), s);
2748
2790
  @field(stats, s) = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
2749
2791
  if (comptime std.mem.eql(u8, s, "atk")) {
@@ -2766,7 +2808,7 @@ fn unboostStat(
2766
2808
  n: u2,
2767
2809
  ) !void {
2768
2810
  @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))];
2811
+ const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
2770
2812
  const stat = @field(unmodifiedStats(battle, side), s);
2771
2813
  @field(stats, s) = @max(1, stat * mod[0] / mod[1]);
2772
2814
  if (comptime std.mem.eql(u8, s, "atk")) {
@@ -2794,7 +2836,7 @@ fn convertible(active: ActivePokemon, m: Move) bool {
2794
2836
 
2795
2837
  pub const Rolls = struct {
2796
2838
  fn speedTie(battle: anytype, options: anytype) !bool {
2797
- const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
2839
+ const p1 = if (options.calc.overridden(.P1, 0, .speed_tie)) |player|
2798
2840
  player == .P1
2799
2841
  else if (showdown)
2800
2842
  battle.rng.range(u8, 0, 2) == 0
@@ -2806,7 +2848,7 @@ pub const Rolls = struct {
2806
2848
  }
2807
2849
 
2808
2850
  fn quickClaw(battle: anytype, player: Player, options: anytype) !bool {
2809
- const qkc = if (options.calc.overridden(player, .quick_claw)) |val|
2851
+ const qkc = if (options.calc.overridden(player, 0, .quick_claw)) |val|
2810
2852
  val == .true
2811
2853
  else if (showdown)
2812
2854
  battle.rng.chance(u8, 60, 256)
@@ -2818,7 +2860,7 @@ pub const Rolls = struct {
2818
2860
  }
2819
2861
 
2820
2862
  fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) !bool {
2821
- const ok = if (options.calc.overridden(player, .hit)) |val|
2863
+ const ok = if (options.calc.overridden(player, 0, .hit)) |val|
2822
2864
  val == .true
2823
2865
  else if (showdown)
2824
2866
  battle.rng.chance(u8, accuracy, 256)
@@ -2829,20 +2871,22 @@ pub const Rolls = struct {
2829
2871
  return ok;
2830
2872
  }
2831
2873
 
2832
- fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
2833
- const crit = if (options.calc.overridden(player, .critical_hit)) |val|
2874
+ fn criticalHit(battle: anytype, player: Player, i: u3, rate: u8, options: anytype) !bool {
2875
+ _ = .{ player, options };
2876
+ const crit = if (options.calc.overridden(player, i, .critical_hits)) |val|
2834
2877
  val == .true
2835
2878
  else if (showdown)
2836
2879
  battle.rng.chance(u8, rate, 256)
2837
2880
  else
2838
2881
  battle.rng.next() < rate;
2839
2882
 
2840
- try options.chance.criticalHit(player, crit, rate);
2883
+ try options.chance.criticalHit(player, i, crit, rate);
2841
2884
  return crit;
2842
2885
  }
2843
2886
 
2844
- fn damage(battle: anytype, player: Player, options: anytype) !u8 {
2845
- const roll = if (options.calc.overridden(player, .damage)) |val|
2887
+ fn damage(battle: anytype, player: Player, i: u3, options: anytype) !u8 {
2888
+ _ = .{ player, options };
2889
+ const roll = if (options.calc.overridden(player, i, .damages)) |val|
2846
2890
  val
2847
2891
  else roll: {
2848
2892
  if (showdown) break :roll battle.rng.range(u8, 217, 256);
@@ -2853,12 +2897,12 @@ pub const Rolls = struct {
2853
2897
  };
2854
2898
 
2855
2899
  assert(roll >= 217 and roll <= 255);
2856
- try options.chance.damage(player, roll);
2900
+ try options.chance.damage(player, i, roll);
2857
2901
  return roll;
2858
2902
  }
2859
2903
 
2860
2904
  fn confused(battle: anytype, player: Player, options: anytype) !bool {
2861
- const cfz = if (options.calc.overridden(player, .confused)) |val|
2905
+ const cfz = if (options.calc.overridden(player, 0, .confused)) |val|
2862
2906
  val == .true
2863
2907
  else if (showdown)
2864
2908
  !battle.rng.chance(u8, 128, 256)
@@ -2870,7 +2914,7 @@ pub const Rolls = struct {
2870
2914
  }
2871
2915
 
2872
2916
  fn attract(battle: anytype, player: Player, options: anytype) !bool {
2873
- const cant = if (options.calc.overridden(player, .attract)) |val|
2917
+ const cant = if (options.calc.overridden(player, 0, .attract)) |val|
2874
2918
  val == .true
2875
2919
  else if (showdown)
2876
2920
  battle.rng.chance(u8, 1, 2)
@@ -2882,7 +2926,7 @@ pub const Rolls = struct {
2882
2926
  }
2883
2927
 
2884
2928
  fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
2885
- const par = if (options.calc.overridden(player, .paralyzed)) |val|
2929
+ const par = if (options.calc.overridden(player, 0, .paralyzed)) |val|
2886
2930
  val == .true
2887
2931
  else if (showdown)
2888
2932
  battle.rng.chance(u8, 63, 256)
@@ -2894,7 +2938,7 @@ pub const Rolls = struct {
2894
2938
  }
2895
2939
 
2896
2940
  fn defrost(battle: anytype, player: Player, options: anytype) !bool {
2897
- const thaw = if (options.calc.overridden(player, .defrost)) |val|
2941
+ const thaw = if (options.calc.overridden(player, 0, .defrost)) |val|
2898
2942
  val == .true
2899
2943
  else if (showdown)
2900
2944
  battle.rng.chance(u8, 25, 256)
@@ -2907,7 +2951,7 @@ pub const Rolls = struct {
2907
2951
 
2908
2952
  fn secondaryChance(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
2909
2953
  // GLITCH: 100% secondary chance will still fail 1/256 of the time
2910
- const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
2954
+ const proc = if (options.calc.overridden(player, 0, .secondary_chance)) |val|
2911
2955
  val == .true
2912
2956
  else if (showdown)
2913
2957
  battle.rng.chance(u8, rate, 256)
@@ -2919,7 +2963,7 @@ pub const Rolls = struct {
2919
2963
  }
2920
2964
 
2921
2965
  fn item(battle: anytype, player: Player, options: anytype) !bool {
2922
- const proc = if (options.calc.overridden(player, .item)) |val|
2966
+ const proc = if (options.calc.overridden(player, 0, .item)) |val|
2923
2967
  val == .true
2924
2968
  else if (showdown)
2925
2969
  battle.rng.chance(u8, 30, 256)
@@ -2931,7 +2975,7 @@ pub const Rolls = struct {
2931
2975
  }
2932
2976
 
2933
2977
  fn triAttack(battle: anytype, player: Player, options: anytype) !TriAttack {
2934
- const status: TriAttack = if (options.calc.overridden(player, .tri_attack)) |val|
2978
+ const status: TriAttack = if (options.calc.overridden(player, 0, .tri_attack)) |val|
2935
2979
  @enumFromInt(@intFromEnum(val) - 1)
2936
2980
  else if (showdown)
2937
2981
  @enumFromInt(battle.rng.range(u2, 0, 2))
@@ -2947,7 +2991,7 @@ pub const Rolls = struct {
2947
2991
  }
2948
2992
 
2949
2993
  fn present(battle: anytype, player: Player, options: anytype) !u8 {
2950
- const power = if (options.calc.overridden(player, .present)) |val|
2994
+ const power = if (options.calc.overridden(player, 0, .present)) |val|
2951
2995
  (val - 1) * 40
2952
2996
  else if (showdown) power: {
2953
2997
  const r = battle.rng.range(u8, 0, 10);
@@ -2969,7 +3013,7 @@ pub const Rolls = struct {
2969
3013
  }
2970
3014
 
2971
3015
  fn magnitude(battle: anytype, player: Player, options: anytype) !u8 {
2972
- const num: u8 = if (options.calc.overridden(player, .magnitude)) |val|
3016
+ const num: u8 = if (options.calc.overridden(player, 0, .magnitude)) |val|
2973
3017
  val + 3
2974
3018
  else if (showdown) num: {
2975
3019
  const r = battle.rng.range(u8, 0, 100);
@@ -2997,7 +3041,7 @@ pub const Rolls = struct {
2997
3041
  }
2998
3042
 
2999
3043
  fn tripleKick(battle: anytype, player: Player, options: anytype) !u2 {
3000
- const hits = if (options.calc.overridden(player, .triple_kick)) |val|
3044
+ const hits = if (options.calc.overridden(player, 0, .triple_kick)) |val|
3001
3045
  val
3002
3046
  else if (showdown)
3003
3047
  battle.rng.range(u2, 1, 4)
@@ -3013,13 +3057,12 @@ pub const Rolls = struct {
3013
3057
  return hits;
3014
3058
  }
3015
3059
 
3016
- fn spite(battle: anytype, player: Player, options: anytype) !u8 {
3017
- const pp = if (options.calc.overridden(player, .spite)) |val|
3060
+ fn spite(battle: anytype, player: Player, options: anytype) !u3 {
3061
+ assert(!showdown);
3062
+ const pp: u3 = if (options.calc.overridden(player, 0, .spite)) |val|
3018
3063
  val + 1
3019
- else if (showdown)
3020
- battle.rng.range(u8, 2, 6)
3021
3064
  else
3022
- (battle.rng.next() & 3) + 2;
3065
+ @intCast((battle.rng.next() & 3) + 2);
3023
3066
 
3024
3067
  assert(pp >= 2 and pp <= 5);
3025
3068
  try options.chance.spite(player, pp);
@@ -3033,7 +3076,7 @@ pub const Rolls = struct {
3033
3076
  var i: u8 = 0;
3034
3077
  const neutral = @intFromEnum(Effectiveness.Neutral);
3035
3078
  assert(mtype != .@"???");
3036
- const ty: Type = if (options.calc.overridden(player, .conversion_2)) |val|
3079
+ const ty: Type = if (options.calc.overridden(player, 0, .conversion_2)) |val|
3037
3080
  @enumFromInt(@intFromEnum(val) - 1)
3038
3081
  else if (showdown) ty: {
3039
3082
  for (Type.SHOWDOWN) |t| {
@@ -3067,7 +3110,7 @@ pub const Rolls = struct {
3067
3110
  assert(count <= 8);
3068
3111
  const num = if (count == 8) 0 else @as(u8, 255) >> @intCast(count);
3069
3112
 
3070
- const ok = if (options.calc.overridden(player, .protect)) |val|
3113
+ const ok = if (options.calc.overridden(player, 0, .protect)) |val|
3071
3114
  val == .true
3072
3115
  else if (showdown)
3073
3116
  battle.rng.chance(u8, num, 255)
@@ -3089,7 +3132,7 @@ pub const Rolls = struct {
3089
3132
  const moves = active.moves;
3090
3133
 
3091
3134
  // TODO: consider throwing error instead of rerolling?
3092
- const overridden = if (options.calc.overridden(player, .move_slot)) |val|
3135
+ const overridden = if (options.calc.overridden(player, 0, .move_slot)) |val|
3093
3136
  if (convertible(active, moves[val - 1].id)) val else null
3094
3137
  else
3095
3138
  null;
@@ -3126,7 +3169,7 @@ pub const Rolls = struct {
3126
3169
  const moves = active.moves;
3127
3170
 
3128
3171
  // TODO: consider throwing error instead of rerolling?
3129
- const overridden = if (options.calc.overridden(player, .move_slot)) |val|
3172
+ const overridden = if (options.calc.overridden(player, 0, .move_slot)) |val|
3130
3173
  if (val != mslot and Move.get(moves[val - 1].id).extra.sleep_talk) val else null
3131
3174
  else
3132
3175
  null;
@@ -3170,7 +3213,7 @@ pub const Rolls = struct {
3170
3213
  assert(n > 0 and n <= 6);
3171
3214
 
3172
3215
  // TODO: consider throwing error instead of rerolling?
3173
- const overridden = if (options.calc.overridden(player, .force_switch)) |val| val: {
3216
+ const overridden = if (options.calc.overridden(player, 0, .force_switch)) |val| val: {
3174
3217
  for (slots) |slot| if (slot == val) break :val if (foe.get(slot).hp > 0) val else null;
3175
3218
  break :val null;
3176
3219
  } else null;
@@ -3196,7 +3239,7 @@ pub const Rolls = struct {
3196
3239
  const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
3197
3240
 
3198
3241
  fn multiHit(battle: anytype, player: Player, options: anytype) !u3 {
3199
- const n: u3 = if (options.calc.overridden(player, .multi_hit)) |val|
3242
+ const n: u3 = if (options.calc.overridden(player, 0, .multi_hit)) |val|
3200
3243
  @intCast(val)
3201
3244
  else if (showdown)
3202
3245
  DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
@@ -3211,7 +3254,7 @@ pub const Rolls = struct {
3211
3254
  }
3212
3255
 
3213
3256
  fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
3214
- const power = if (options.calc.overridden(player, .psywave)) |val|
3257
+ const power = if (options.calc.overridden(player, 0, .psywave)) |val|
3215
3258
  val
3216
3259
  else if (showdown)
3217
3260
  battle.rng.range(u8, 1, max)
@@ -3237,7 +3280,7 @@ pub const Rolls = struct {
3237
3280
  }
3238
3281
 
3239
3282
  // TODO: consider throwing error instead of rerolling?
3240
- const overridden = if (options.calc.overridden(player, .metronome)) |val| val: {
3283
+ const overridden = if (options.calc.overridden(player, 0, .metronome)) |val| val: {
3241
3284
  for (moves) |m| if (m.id == val) break :val null;
3242
3285
  break :val val;
3243
3286
  } else null;
@@ -3269,7 +3312,7 @@ pub const Rolls = struct {
3269
3312
  }
3270
3313
 
3271
3314
  fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
3272
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
3315
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3273
3316
  @intCast(val)
3274
3317
  else if (showdown)
3275
3318
  battle.rng.range(u3, 1, 7)
@@ -3281,12 +3324,12 @@ pub const Rolls = struct {
3281
3324
  };
3282
3325
 
3283
3326
  assert(duration >= 1 and duration <= 6);
3284
- options.chance.duration(.sleep, player, player.foe(), duration);
3327
+ options.chance.duration(player, duration);
3285
3328
  return duration + 1;
3286
3329
  }
3287
3330
 
3288
3331
  fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
3289
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
3332
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3290
3333
  @intCast(val)
3291
3334
  else if (showdown)
3292
3335
  // Pokémon Showdown incorrectly uses the same duration for self-confusion
@@ -3295,16 +3338,16 @@ pub const Rolls = struct {
3295
3338
  @intCast((battle.rng.next() & if (self) 1 else 3) + 2);
3296
3339
 
3297
3340
  assert(duration >= 2 and duration <= if (!showdown and self) 3 else 5);
3298
- options.chance.duration(.confusion, player, if (self) player else player.foe(), duration);
3341
+ options.chance.duration(player, duration);
3299
3342
  return duration;
3300
3343
  }
3301
3344
 
3302
3345
  fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
3303
- const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
3346
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3304
3347
  @intCast(val)
3305
3348
  else if (showdown)
3306
3349
  // Pokémon Showdown incorrectly inherits Generation III's Disable duration
3307
- battle.rng.range(u4, 2, 6)
3350
+ battle.rng.range(u3, 2, 6)
3308
3351
  else duration: {
3309
3352
  while (true) {
3310
3353
  const r = battle.rng.next() & 7;
@@ -3313,12 +3356,12 @@ pub const Rolls = struct {
3313
3356
  };
3314
3357
 
3315
3358
  assert(duration >= 2 and duration <= (if (showdown) 5 else 7));
3316
- options.chance.duration(.disable, player, player.foe(), duration);
3359
+ options.chance.duration(player, duration);
3317
3360
  return duration;
3318
3361
  }
3319
3362
 
3320
3363
  fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
3321
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
3364
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3322
3365
  @intCast(val)
3323
3366
  else if (showdown)
3324
3367
  battle.rng.range(u3, 2, 4)
@@ -3326,12 +3369,12 @@ pub const Rolls = struct {
3326
3369
  @intCast((battle.rng.next() & 1) + 2); // BUG: thrash is + 1 not +2
3327
3370
 
3328
3371
  assert(duration >= 2 and duration <= 3);
3329
- options.chance.duration(.attacking, player, player, duration);
3372
+ options.chance.duration(player, duration);
3330
3373
  return duration;
3331
3374
  }
3332
3375
 
3333
3376
  fn bindingDuration(battle: anytype, player: Player, options: anytype) u3 {
3334
- const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
3377
+ const duration: u3 = (if (options.calc.overridden(player, 0, .duration)) |val|
3335
3378
  @intCast(val)
3336
3379
  else if (showdown)
3337
3380
  // TODO: PS does range(3, 6) here which after subtracting 1 is actually 2-4 not 2-5!
@@ -3340,13 +3383,13 @@ pub const Rolls = struct {
3340
3383
  @intCast(battle.rng.next() & 3 + 2));
3341
3384
 
3342
3385
  assert(duration >= 2 and duration <= 5);
3343
- options.chance.duration(.binding, player, player.foe(), duration);
3386
+ options.chance.duration(player, duration);
3344
3387
  return duration + 1;
3345
3388
  }
3346
3389
 
3347
3390
  // TODO: consider sharing implementation with bindingDuration
3348
3391
  fn encoreDuration(battle: anytype, player: Player, options: anytype) u3 {
3349
- const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
3392
+ const duration: u3 = (if (options.calc.overridden(player, 0, .duration)) |val|
3350
3393
  @intCast(val)
3351
3394
  else if (showdown)
3352
3395
  battle.rng.range(u3, 2, 6)
@@ -3354,7 +3397,7 @@ pub const Rolls = struct {
3354
3397
  @intCast(battle.rng.next() & 3 + 2));
3355
3398
 
3356
3399
  assert(duration >= 2 and duration <= 5);
3357
- options.chance.duration(.encore, player, player.foe(), duration);
3400
+ options.chance.duration(player, duration);
3358
3401
  return duration + 1;
3359
3402
  }
3360
3403
  };
@@ -3467,7 +3510,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3467
3510
  .Move => {
3468
3511
  const side = battle.side(player);
3469
3512
 
3470
- var active = &side.active;
3513
+ const active = &side.active;
3471
3514
  const stored = side.stored();
3472
3515
 
3473
3516
  // While players are not given any input options on the cartridge in these cases,