@pkmn/engine 0.1.0-dev.d4119c62 → 0.1.0-dev.d4bd80b1

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 (83) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +46 -18
  3. package/build/pkg/addon/browser.d.ts +3 -0
  4. package/build/pkg/addon/browser.js +47 -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 +110 -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 +53 -0
  11. package/build/pkg/addon/wasm.js.map +1 -0
  12. package/build/pkg/addon.d.ts +25 -3
  13. package/build/pkg/addon.js +39 -71
  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 +15 -18
  20. package/build/pkg/gen1.js +28 -19
  21. package/build/pkg/gen1.js.map +1 -1
  22. package/build/pkg/index.d.ts +40 -90
  23. package/build/pkg/index.js +23 -70
  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 +53 -78
  29. package/build/tools/debug.js.map +1 -1
  30. package/build/tools/display/debug.jsx +152 -0
  31. package/build/tools/display/debug.jsx.map +1 -0
  32. package/build/tools/display/index.js +152 -0
  33. package/build/tools/display/index.js.map +1 -0
  34. package/build/tools/display/ui.jsx +359 -0
  35. package/build/tools/display/ui.jsx.map +1 -0
  36. package/build/tools/display/util.js +33 -0
  37. package/build/tools/display/util.js.map +1 -0
  38. package/build.zig +330 -104
  39. package/package.json +60 -134
  40. package/src/bin/install-pkmn-engine +32 -21
  41. package/src/bin/pkmn-debug +1 -1
  42. package/src/lib/bindings/c.zig +255 -0
  43. package/src/lib/{binding → bindings}/node.zig +8 -21
  44. package/src/lib/bindings/wasm.zig +59 -0
  45. package/src/lib/common/array.zig +77 -0
  46. package/src/lib/common/data.zig +4 -4
  47. package/src/lib/common/debug.zig +39 -8
  48. package/src/lib/common/js.zig +4 -2
  49. package/src/lib/common/optional.zig +14 -10
  50. package/src/lib/common/options.zig +6 -0
  51. package/src/lib/common/protocol.zig +63 -67
  52. package/src/lib/common/rational.zig +128 -32
  53. package/src/lib/common/rng.zig +7 -9
  54. package/src/lib/common/util.zig +50 -1
  55. package/src/lib/gen1/calc.zig +686 -159
  56. package/src/lib/gen1/chance.zig +405 -280
  57. package/src/lib/gen1/data/moves.zig +2 -4
  58. package/src/lib/gen1/data/species.zig +2 -4
  59. package/src/lib/gen1/data/types.zig +1 -2
  60. package/src/lib/gen1/data.zig +24 -27
  61. package/src/lib/gen1/helpers.zig +92 -548
  62. package/src/lib/gen1/mechanics.zig +379 -258
  63. package/src/lib/gen2/calc.zig +37 -27
  64. package/src/lib/gen2/chance.zig +84 -496
  65. package/src/lib/gen2/data/items.zig +1 -3
  66. package/src/lib/gen2/data/moves.zig +2 -4
  67. package/src/lib/gen2/data/species.zig +1 -3
  68. package/src/lib/gen2/data/types.zig +1 -2
  69. package/src/lib/gen2/data.zig +19 -22
  70. package/src/lib/gen2/generated.zig +19 -26
  71. package/src/lib/gen2/helpers.zig +46 -25
  72. package/src/lib/gen2/mechanics.zig +217 -163
  73. package/src/lib/node.zig +18 -0
  74. package/src/lib/pkmn.zig +37 -15
  75. package/src/lib/wasm.zig +17 -0
  76. package/src/tools/display/dom.ts +95 -0
  77. package/src/tools/display/ui.css +354 -0
  78. package/build/test/display.js +0 -400
  79. package/build/test/display.js.map +0 -1
  80. package/src/lib/binding/wasm.zig +0 -12
  81. package/src/lib/gen1/README.md +0 -894
  82. package/src/lib/gen2/README.md +0 -329
  83. package/src/test/display/pkmn.html.tmpl +0 -368
@@ -1,50 +1,37 @@
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 builtin = @import("builtin");
4
+ const common = @import("../common/data.zig");
11
5
  const data = @import("data.zig");
12
6
  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;
7
+ const pkmn = @import("../pkmn.zig");
8
+ const protocol = @import("../common/protocol.zig");
9
+ const rng = @import("../common/rng.zig");
10
+ const std = @import("std");
26
11
 
27
12
  const Activate = protocol.Activate;
13
+ const ActivePokemon = data.ActivePokemon;
14
+ const assert = std.debug.assert;
28
15
  const Boost = protocol.Boost;
16
+ const Boosts = data.Boosts;
17
+ const Choice = common.Choice;
29
18
  const Damage = protocol.Damage;
19
+ const Effectiveness = data.Effectiveness;
30
20
  const End = protocol.End;
21
+ const expectEqual = std.testing.expectEqual;
22
+ const expectEqualSlices = std.testing.expectEqualSlices;
23
+ const expectEqualStrings = std.testing.expectEqualStrings;
31
24
  const Fail = protocol.Fail;
32
- const Heal = protocol.Heal;
33
- const Start = protocol.Start;
34
-
35
25
  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;
26
+ const Heal = protocol.Heal;
27
+ const ID = common.ID;
42
28
  const Item = data.Item;
43
29
  const Move = data.Move;
44
- const MoveSlot = data.MoveSlot;
45
- const Pokemon = data.Pokemon;
30
+ const Player = common.Player;
31
+ const Result = common.Result;
32
+ const showdown = pkmn.options.showdown;
46
33
  const Side = data.Side;
47
- const Species = data.Species;
34
+ const Start = protocol.Start;
48
35
  const Stats = data.Stats;
49
36
  const Status = data.Status;
50
37
  const TriAttack = data.TriAttack;
@@ -106,6 +93,7 @@ pub const State = struct {
106
93
  type: Type, // BATTLE_VARS_MOVE_TYPE
107
94
  accuracy: u8, // wEnemyMoveStruct + MOVE_ACC
108
95
  move: Move, // wCurPlayerMove
96
+ hit: u3 = 0,
109
97
  from: Move = .None,
110
98
  mslot: u4, // wCurMoveNum
111
99
  first: bool, // wEnemyGoesFirst
@@ -140,11 +128,11 @@ fn start(battle: anytype, options: anytype) !Result {
140
128
  const p1 = battle.side(.P1);
141
129
  const p2 = battle.side(.P2);
142
130
 
143
- var p1_slot = findFirstAlive(p1);
131
+ const p1_slot = findFirstAlive(p1);
144
132
  assert(!showdown or p1_slot == 1);
145
133
  if (p1_slot == 0) return if (findFirstAlive(p2) == 0) Result.Tie else Result.Lose;
146
134
 
147
- var p2_slot = findFirstAlive(p2);
135
+ const p2_slot = findFirstAlive(p2);
148
136
  assert(!showdown or p2_slot == 1);
149
137
  if (p2_slot == 0) return Result.Win;
150
138
 
@@ -256,7 +244,6 @@ fn switchIn(battle: anytype, player: Player, slot: u8, kind: SwitchIn, options:
256
244
  } else {
257
245
  try options.log.switched(.{ battle.active(player), incoming });
258
246
  }
259
- options.chance.switched(player, side.pokemon[0].position, out);
260
247
 
261
248
  if (side.conditions.Spikes and !active.types.includes(.Flying)) {
262
249
  incoming.hp -|= @max(incoming.stats.hp / 8, 1);
@@ -403,7 +390,9 @@ fn executeMove(
403
390
 
404
391
  assert(choice.type == .Move);
405
392
  var mslot: u4 = @intCast(choice.data);
393
+ _ = &mslot;
406
394
  var move = Move.Pound; // TODO
395
+ _ = &move;
407
396
 
408
397
  if (!try beforeMove(battle, player, move, options)) return null;
409
398
 
@@ -440,18 +429,9 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
440
429
  if (Status.is(stored.status, .SLP)) slp: {
441
430
  const before = stored.status;
442
431
  // 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
432
  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
433
 
454
- if (duration == 0) {
434
+ if (Status.duration(stored.status) == 0) {
455
435
  try log.curestatus(.{ ident, before, .Message });
456
436
  stored.status = 0; // clears EXT if present
457
437
  resetCant(volatiles);
@@ -481,12 +461,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
481
461
  }
482
462
 
483
463
  if (volatiles.disable.duration > 0) {
484
- // if (options.calc.modify(player, .disable)) |extend| {
485
- // if (!extend) volatiles.disable.duration = 0;
486
- // } else {
487
464
  volatiles.disable.duration -= 1;
488
- // }
489
- try options.chance.disable(player, volatiles.disable.duration);
490
465
 
491
466
  if (volatiles.disable.duration == 0) {
492
467
  volatiles.disable.move = 0;
@@ -496,13 +471,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
496
471
 
497
472
  if (volatiles.Confusion) {
498
473
  assert(volatiles.confusion > 0);
499
-
500
- // if (options.calc.modify(player, .confusion)) |extend| {
501
- // if (!extend) volatiles.confusion = 0;
502
- // } else {
503
474
  volatiles.confusion -= 1;
504
- // }
505
- try options.chance.confusion(player, volatiles.confusion);
506
475
 
507
476
  if (volatiles.confusion == 0) {
508
477
  volatiles.Confusion = false;
@@ -565,7 +534,7 @@ fn resetCant(volatiles: *align(8) Volatiles) void {
565
534
 
566
535
  pub fn canMove(battle: anytype, player: Player, state: *State, options: anytype) !bool {
567
536
  var side = battle.side(player);
568
- var volatiles = &side.active.volatiles;
537
+ const volatiles = &side.active.volatiles;
569
538
 
570
539
  // FIXME: pass in target to avoid lookup?
571
540
  const target = if (Move.get(state.move).target == .Self) player else player.foe();
@@ -663,7 +632,7 @@ pub fn checkCriticalHit(battle: anytype, player: Player, state: *State, options:
663
632
  }
664
633
  assert(stage <= 4);
665
634
 
666
- state.crit = try Rolls.criticalHit(battle, player, CRITICAL_HITS[stage], options);
635
+ state.crit = try Rolls.criticalHit(battle, player, state.hit, CRITICAL_HITS[stage], options);
667
636
  }
668
637
 
669
638
  pub fn calcDamage(
@@ -731,7 +700,7 @@ pub fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype)
731
700
 
732
701
  pub fn randomizeDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
733
702
  if (state.damage <= 1) return;
734
- const random = try Rolls.damage(battle, player, options);
703
+ const random = try Rolls.damage(battle, player, state.hit, options);
735
704
  options.calc.base(player.foe(), state.damage);
736
705
  state.damage = @intCast(@as(u32, state.damage) *% random / 255);
737
706
  options.calc.final(player.foe(), state.damage);
@@ -997,7 +966,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
997
966
 
998
967
  // Weather
999
968
  if (battle.field.weather != .None) {
1000
- assert(battle.field.weather_duration > 0);
1001
969
  battle.field.weather_duration -= 1;
1002
970
 
1003
971
  if (battle.field.weather_duration == 0) {
@@ -1056,7 +1024,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1056
1024
  var volatiles = &side.active.volatiles;
1057
1025
 
1058
1026
  if (volatiles.PerishSong) {
1059
- assert(volatiles.perish_song > 0);
1060
1027
  volatiles.perish_song -= 1;
1061
1028
 
1062
1029
  assert(volatiles.perish_song < 3);
@@ -1086,7 +1053,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1086
1053
 
1087
1054
  // Mystery Berry
1088
1055
  inline for (players) |player| {
1089
- var stored = battle.side(player).stored();
1056
+ const stored = battle.side(player).stored();
1090
1057
 
1091
1058
  if (stored.item == .MysteryBerry) {
1092
1059
  // TODO stored and active moves, transform, etc
@@ -1193,7 +1160,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1193
1160
  var volatiles = &side.active.volatiles;
1194
1161
 
1195
1162
  if (volatiles.Encore) {
1196
- assert(volatiles.encore > 0);
1197
1163
  volatiles.encore -= 1;
1198
1164
 
1199
1165
  if (volatiles.encore == 0 or side.active.move(mslot).pp == 0) {
@@ -1215,7 +1181,6 @@ fn updateSideCondition(
1215
1181
  ) !void {
1216
1182
  var side = battle.side(player);
1217
1183
  if (@field(side.conditions, key)) {
1218
- assert(@field(side.conditions, value) > 0);
1219
1184
  @field(side.conditions, value) -= 1;
1220
1185
 
1221
1186
  if (@field(side.conditions, value) == 0) {
@@ -1228,7 +1193,7 @@ fn updateSideCondition(
1228
1193
  fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result {
1229
1194
  battle.turn += 1;
1230
1195
 
1231
- if (showdown and battle.turn >= 1000) {
1196
+ if (pkmn.options.mod and battle.turn >= 1000) {
1232
1197
  try options.log.tie(.{});
1233
1198
  return Result.Tie;
1234
1199
  } else if (battle.turn >= 65535) {
@@ -1306,14 +1271,32 @@ pub const Effects = struct {
1306
1271
  }
1307
1272
 
1308
1273
  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
- //
1274
+ assert(!state.miss);
1275
+ var side = battle.side(player);
1276
+
1277
+ const ident = battle.active(player);
1278
+
1279
+ assert(side.active.boosts.atk >= -6 and side.active.boosts.atk <= 6);
1280
+ if (side.active.boosts.atk == 6 or side.active.boosts.atk == MAX_STAT_VALUE) {
1281
+ return options.log.fail(.{ ident, .None });
1282
+ }
1283
+
1284
+ const hp = @max(side.stored().stats.hp / 2, 1);
1285
+ if (side.stored().hp <= hp) {
1286
+ try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
1287
+ try options.log.boost(.{ ident, .Attack, @as(i8, 2), .Silent });
1288
+ return options.log.fail(.{ ident, .None });
1289
+ } else {
1290
+ side.stored().hp -= hp;
1291
+ try options.log.damage(.{ ident, side.stored(), .None });
1292
+
1293
+ const before = side.active.boosts.atk;
1294
+ var i: usize = 0;
1295
+ while (i < 6 and side.active.stats.atk < MAX_STAT_VALUE) : (i += 1) {
1296
+ try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
1297
+ }
1298
+ try options.log.boost(.{ ident, .Attack, @as(i8, side.active.boosts.atk - before) });
1299
+ }
1317
1300
  }
1318
1301
 
1319
1302
  pub const bide = struct {
@@ -1325,14 +1308,7 @@ pub const Effects = struct {
1325
1308
 
1326
1309
  if (!volatiles.Bide) return;
1327
1310
 
1328
- assert(volatiles.attacks > 0);
1329
- // if (options.calc.modify(player, .attacking)) |extend| {
1330
- // if (!extend) volatiles.attacks = 0;
1331
- // } else {
1332
1311
  volatiles.attacks -= 1;
1333
- // }
1334
- try options.chance.attacking(player, volatiles.attacks);
1335
-
1336
1312
  if (volatiles.attacks != 0) return options.log.activate(.{ ident, .Bide });
1337
1313
 
1338
1314
  volatiles.Bide = false;
@@ -1451,7 +1427,8 @@ pub const Effects = struct {
1451
1427
  if (move.type.special() != (last == .MirrorCoat)) return;
1452
1428
  if (state.damage == 0) return;
1453
1429
 
1454
- state.damage *|= 2;
1430
+ // NOTE: llvm/llvm-project#58557
1431
+ state.damage = smul(state.damage, 2);
1455
1432
  state.miss = false;
1456
1433
  }
1457
1434
 
@@ -1478,8 +1455,8 @@ pub const Effects = struct {
1478
1455
  side.stored().hp -= @max(side.stored().stats.hp / 2, 1);
1479
1456
  try options.log.damage(.{ ident, side.stored(), .Curse });
1480
1457
  } else {
1481
- var stats = &side.active.stats;
1482
- var boosts = &side.active.boosts;
1458
+ const stats = &side.active.stats;
1459
+ const boosts = &side.active.boosts;
1483
1460
 
1484
1461
  const max_atk = boosts.atk == 6 or stats.atk == MAX_STAT_VALUE;
1485
1462
  const max_def = boosts.def == 6 or stats.def == MAX_STAT_VALUE;
@@ -1546,15 +1523,18 @@ pub const Effects = struct {
1546
1523
 
1547
1524
  pub const double = struct {
1548
1525
  pub fn flying(battle: anytype, player: Player, state: *State, _: anytype) !void {
1549
- if (battle.foe(player).active.volatiles.Flying) state.damage *|= 2;
1526
+ if (!battle.foe(player).active.volatiles.Flying) return;
1527
+ state.damage = smul(state.damage, 2);
1550
1528
  }
1551
1529
 
1552
1530
  pub fn underground(battle: anytype, player: Player, state: *State, _: anytype) !void {
1553
- if (battle.foe(player).active.volatiles.Underground) state.damage *|= 2;
1531
+ if (!battle.foe(player).active.volatiles.Underground) return;
1532
+ state.damage = smul(state.damage, 2);
1554
1533
  }
1555
1534
 
1556
1535
  pub fn minimize(battle: anytype, player: Player, state: *State, _: anytype) !void {
1557
- if (battle.foe(player).active.volatiles.minimized) state.damage *|= 2;
1536
+ if (!battle.foe(player).active.volatiles.minimized) return;
1537
+ state.damage = smul(state.damage, 2);
1558
1538
  }
1559
1539
  };
1560
1540
 
@@ -1599,8 +1579,16 @@ pub const Effects = struct {
1599
1579
  try options.log.start(.{ foe_ident, .Encore, move.id });
1600
1580
  }
1601
1581
 
1602
- pub fn explode(battle: anytype, player: Player, state: *State, options: anytype) !void {
1603
- _ = .{ battle, player, state, options }; // TODO
1582
+ pub fn explode(battle: anytype, player: Player, _: *State, _: anytype) !void {
1583
+ var side = battle.side(player);
1584
+ var stored = side.stored();
1585
+
1586
+ stored.hp = 0;
1587
+ // Pokémon Showdown sets the status to 0 on faint(), and we need to do the same to be able
1588
+ // to correctly implement Pokémon Showdown's dumb speed-based switch mechanics
1589
+ if (!showdown) stored.status = 0;
1590
+ side.active.volatiles.LeechSeed = false;
1591
+ side.active.volatiles.DestinyBond = false;
1604
1592
  }
1605
1593
 
1606
1594
  pub fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
@@ -1715,7 +1703,7 @@ pub const Effects = struct {
1715
1703
  if (!state.proc) return;
1716
1704
  if (foe.conditions.Safeguard) return;
1717
1705
  // Freeze Clause Mod
1718
- if (showdown) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
1706
+ if (pkmn.options.mod) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
1719
1707
 
1720
1708
  foe_stored.status = Status.init(.FRZ);
1721
1709
  try options.log.status(.{ foe_ident, foe_stored.status, .None });
@@ -1726,8 +1714,26 @@ pub const Effects = struct {
1726
1714
  foe.active.volatiles.frozen = true;
1727
1715
  }
1728
1716
 
1729
- pub fn furyCutter(battle: anytype, player: Player, state: *State, options: anytype) !void {
1730
- _ = .{ battle, player, state, options }; // TODO
1717
+ // TODO FIXME showdown calls this before calcDamage (after checkHit) and modifies state.bp!
1718
+ pub fn furyCutter(battle: anytype, player: Player, state: *State, _: anytype) !void {
1719
+ var side = battle.side(player);
1720
+ var volatiles = &side.active.volatiles;
1721
+
1722
+ if (state.miss) {
1723
+ volatiles.fury_cutter = 0;
1724
+ return;
1725
+ }
1726
+
1727
+ assert(volatiles.fury_cutter >= 0 and volatiles.fury_cutter <= 5);
1728
+ if (volatiles.fury_cutter == 5) return;
1729
+ volatiles.fury_cutter += 1;
1730
+
1731
+ if (showdown) {
1732
+ state.bp *= 2;
1733
+ assert(state.bp <= 160);
1734
+ } else {
1735
+ state.damage = smul(state.damage, 2);
1736
+ }
1731
1737
  }
1732
1738
 
1733
1739
  pub fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -1888,7 +1894,29 @@ pub const Effects = struct {
1888
1894
  }
1889
1895
 
1890
1896
  pub fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
1891
- _ = .{ battle, player, state, options }; // TODO
1897
+ var side = battle.side(player);
1898
+
1899
+ if (state.miss) {
1900
+ if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
1901
+ try options.log.lastmiss(.{});
1902
+ return options.log.miss(.{battle.active(player)});
1903
+ }
1904
+
1905
+ const move = battle.foe(player).lastMove(false);
1906
+ if (move == .None or move == .Struggle) {
1907
+ return options.log.fail(.{ battle.active(player.foe()), .None });
1908
+ }
1909
+
1910
+ var slot: u4 = 0;
1911
+ for (side.active.moves, 0..) |m, i| {
1912
+ if (m.id == move) return options.log.fail(.{ battle.active(player.foe()), .None });
1913
+ if (m.id == .Mimic) slot = @intCast(i + 1);
1914
+ }
1915
+ assert(slot != 0 and slot == state.mslot);
1916
+
1917
+ side.active.move(slot).id = move;
1918
+ side.active.move(slot).pp = 5;
1919
+ try options.log.start(.{ battle.active(player), .Mimic, move });
1892
1920
  }
1893
1921
 
1894
1922
  pub fn mirrorMove(
@@ -2175,7 +2203,7 @@ pub const Effects = struct {
2175
2203
 
2176
2204
  pub fn pursuit(battle: anytype, player: Player, state: *State, _: anytype) !void {
2177
2205
  // TODO: remove switching variable and just look at Choice saved in Battle struct?
2178
- if (battle.foe(player).active.volatiles.switching) state.damage *|= 2;
2206
+ if (battle.foe(player).active.volatiles.switching) state.damage = smul(state.damage, 2);
2179
2207
  }
2180
2208
 
2181
2209
  pub const rage = struct {
@@ -2185,11 +2213,10 @@ pub const Effects = struct {
2185
2213
  }
2186
2214
 
2187
2215
  pub fn damage(battle: anytype, player: Player, state: *State, _: anytype) !void {
2188
- var side = battle.side(player);
2189
- var volatiles = &side.active.volatiles;
2216
+ const side = battle.side(player);
2190
2217
 
2191
- assert(volatiles.Rage);
2192
- state.damage *|= (volatiles.rage +| 1);
2218
+ assert(side.active.volatiles.Rage);
2219
+ state.damage = smul(state.damage, (side.active.volatiles.rage +| 1)); // TODO: |+
2193
2220
  }
2194
2221
  };
2195
2222
 
@@ -2284,7 +2311,7 @@ pub const Effects = struct {
2284
2311
  side.active.volatiles.attacks += 1;
2285
2312
  side.active.volatiles.Rollout = side.active.volatiles.attacks != 5;
2286
2313
 
2287
- if (side.active.volatiles.DefenseCurl) state.damage *|= 2;
2314
+ if (side.active.volatiles.DefenseCurl) state.damage = smul(state.damage, 2);
2288
2315
  }
2289
2316
  };
2290
2317
 
@@ -2354,7 +2381,7 @@ pub const Effects = struct {
2354
2381
  }
2355
2382
 
2356
2383
  // Sleep Clause Mod
2357
- if (showdown) {
2384
+ if (pkmn.options.mod) {
2358
2385
  for (foe.pokemon) |p| {
2359
2386
  if (Status.is(p.status, .SLP) and !Status.is(p.status, .EXT)) return;
2360
2387
  }
@@ -2397,17 +2424,37 @@ pub const Effects = struct {
2397
2424
  }
2398
2425
 
2399
2426
  pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
2400
- _ = .{ battle, player, state, options }; // TODO
2401
- // var foe = battle.foe(player);
2427
+ var foe = battle.foe(player);
2428
+
2429
+ const last = foe.lastMove(false);
2430
+ if (state.miss or last == .None or last == .Struggle) {
2431
+ return options.log.fail(.{ battle.active(player.foe()), .None });
2432
+ }
2433
+
2434
+ var slot: u4 = 0;
2435
+ for (foe.active.moves, 0..) |m, i| {
2436
+ if (m.id == last) slot = @intCast(i + 1);
2437
+ }
2438
+ // If the move was called via Metronome it might not exist
2439
+ if (slot == 0 or foe.active.move(slot).pp == 0) {
2440
+ return options.log.fail(.{ battle.active(player.foe()), .None });
2441
+ }
2402
2442
 
2403
- // if (state.miss) return options.log.fail(.{ battle.active(player), .None });
2443
+ // Pokémon Showdown always deducts 4 instead of randomizing
2444
+ const pp = @min(
2445
+ foe.active.move(slot).pp,
2446
+ if (showdown) 4 else try Rolls.spite(battle, player, options),
2447
+ );
2448
+ foe.active.move(slot).pp -= pp;
2449
+ if (!foe.active.volatiles.Transform) {
2450
+ var move_slot = foe.stored().move(slot);
2451
+ assert(move_slot.pp > 0);
2404
2452
 
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
- // }
2453
+ move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
2454
+ assert(foe.active.move(slot).pp == foe.stored().move(slot).pp);
2455
+ }
2456
+
2457
+ try options.log.activate(.{ battle.active(player.foe()), .Spite, last, pp });
2411
2458
  }
2412
2459
 
2413
2460
  pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
@@ -2541,8 +2588,8 @@ pub const Effects = struct {
2541
2588
  pub fn allStatUpChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
2542
2589
  var side = battle.side(player);
2543
2590
 
2544
- var stats = &side.active.stats;
2545
- var boosts = &side.active.boosts;
2591
+ const stats = &side.active.stats;
2592
+ const boosts = &side.active.boosts;
2546
2593
 
2547
2594
  if (!state.proc) return;
2548
2595
  inline for (STATS, STAT_UP_REASONS) |s, r| {
@@ -2563,7 +2610,7 @@ pub const Effects = struct {
2563
2610
 
2564
2611
  if (state.miss) return log.fail(.{ ident, .None });
2565
2612
 
2566
- var stats = &side.active.stats;
2613
+ const stats = &side.active.stats;
2567
2614
  var boosts = &side.active.boosts;
2568
2615
 
2569
2616
  const move = Move.get(state.move);
@@ -2634,7 +2681,7 @@ pub const Effects = struct {
2634
2681
 
2635
2682
  const fail = foe.active.volatiles.Substitute or state.miss or !state.proc;
2636
2683
 
2637
- var stats = &foe.active.stats;
2684
+ const stats = &foe.active.stats;
2638
2685
  var boosts = &foe.active.boosts;
2639
2686
 
2640
2687
  const move = Move.get(state.move);
@@ -2718,9 +2765,9 @@ fn unmodifiedStats(battle: anytype, side: *Side) *Stats(u16) {
2718
2765
  }
2719
2766
 
2720
2767
  fn recomputeStats(battle: anytype, side: *Side) void {
2721
- var stats = unmodifiedStats(battle, side);
2768
+ const stats = unmodifiedStats(battle, side);
2722
2769
  inline for (STATS) |s| {
2723
- var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(side.active.boosts, s)) + 6))];
2770
+ const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(side.active.boosts, s)) + 6))];
2724
2771
  const val = @field(stats, s) * mod[0] / mod[1];
2725
2772
  @field(side.active.stats, s) = @max(1, @min(MAX_STAT_VALUE, val));
2726
2773
  }
@@ -2743,7 +2790,7 @@ fn boostStat(
2743
2790
  n: u2,
2744
2791
  ) !void {
2745
2792
  @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))];
2793
+ const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
2747
2794
  const stat = @field(unmodifiedStats(battle, side), s);
2748
2795
  @field(stats, s) = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
2749
2796
  if (comptime std.mem.eql(u8, s, "atk")) {
@@ -2766,7 +2813,7 @@ fn unboostStat(
2766
2813
  n: u2,
2767
2814
  ) !void {
2768
2815
  @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))];
2816
+ const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
2770
2817
  const stat = @field(unmodifiedStats(battle, side), s);
2771
2818
  @field(stats, s) = @max(1, stat * mod[0] / mod[1]);
2772
2819
  if (comptime std.mem.eql(u8, s, "atk")) {
@@ -2792,9 +2839,15 @@ fn convertible(active: ActivePokemon, m: Move) bool {
2792
2839
  return t != .@"???" and !active.types.includes(t);
2793
2840
  }
2794
2841
 
2842
+ // NOTE: llvm/llvm-project#58557
2843
+ fn smul(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
2844
+ if (!comptime builtin.target.isWasm()) return a *| b;
2845
+ return @max(a *% b, std.math.maxInt(@TypeOf(a)));
2846
+ }
2847
+
2795
2848
  pub const Rolls = struct {
2796
2849
  fn speedTie(battle: anytype, options: anytype) !bool {
2797
- const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
2850
+ const p1 = if (options.calc.overridden(.P1, 0, .speed_tie)) |player|
2798
2851
  player == .P1
2799
2852
  else if (showdown)
2800
2853
  battle.rng.range(u8, 0, 2) == 0
@@ -2806,7 +2859,7 @@ pub const Rolls = struct {
2806
2859
  }
2807
2860
 
2808
2861
  fn quickClaw(battle: anytype, player: Player, options: anytype) !bool {
2809
- const qkc = if (options.calc.overridden(player, .quick_claw)) |val|
2862
+ const qkc = if (options.calc.overridden(player, 0, .quick_claw)) |val|
2810
2863
  val == .true
2811
2864
  else if (showdown)
2812
2865
  battle.rng.chance(u8, 60, 256)
@@ -2818,7 +2871,7 @@ pub const Rolls = struct {
2818
2871
  }
2819
2872
 
2820
2873
  fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) !bool {
2821
- const ok = if (options.calc.overridden(player, .hit)) |val|
2874
+ const ok = if (options.calc.overridden(player, 0, .hit)) |val|
2822
2875
  val == .true
2823
2876
  else if (showdown)
2824
2877
  battle.rng.chance(u8, accuracy, 256)
@@ -2829,20 +2882,22 @@ pub const Rolls = struct {
2829
2882
  return ok;
2830
2883
  }
2831
2884
 
2832
- fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
2833
- const crit = if (options.calc.overridden(player, .critical_hit)) |val|
2885
+ fn criticalHit(battle: anytype, player: Player, i: u3, rate: u8, options: anytype) !bool {
2886
+ _ = .{ player, options };
2887
+ const crit = if (options.calc.overridden(player, i, .critical_hits)) |val|
2834
2888
  val == .true
2835
2889
  else if (showdown)
2836
2890
  battle.rng.chance(u8, rate, 256)
2837
2891
  else
2838
2892
  battle.rng.next() < rate;
2839
2893
 
2840
- try options.chance.criticalHit(player, crit, rate);
2894
+ try options.chance.criticalHit(player, i, crit, rate);
2841
2895
  return crit;
2842
2896
  }
2843
2897
 
2844
- fn damage(battle: anytype, player: Player, options: anytype) !u8 {
2845
- const roll = if (options.calc.overridden(player, .damage)) |val|
2898
+ fn damage(battle: anytype, player: Player, i: u3, options: anytype) !u8 {
2899
+ _ = .{ player, options };
2900
+ const roll = if (options.calc.overridden(player, i, .damages)) |val|
2846
2901
  val
2847
2902
  else roll: {
2848
2903
  if (showdown) break :roll battle.rng.range(u8, 217, 256);
@@ -2853,12 +2908,12 @@ pub const Rolls = struct {
2853
2908
  };
2854
2909
 
2855
2910
  assert(roll >= 217 and roll <= 255);
2856
- try options.chance.damage(player, roll);
2911
+ try options.chance.damage(player, i, roll);
2857
2912
  return roll;
2858
2913
  }
2859
2914
 
2860
2915
  fn confused(battle: anytype, player: Player, options: anytype) !bool {
2861
- const cfz = if (options.calc.overridden(player, .confused)) |val|
2916
+ const cfz = if (options.calc.overridden(player, 0, .confused)) |val|
2862
2917
  val == .true
2863
2918
  else if (showdown)
2864
2919
  !battle.rng.chance(u8, 128, 256)
@@ -2870,7 +2925,7 @@ pub const Rolls = struct {
2870
2925
  }
2871
2926
 
2872
2927
  fn attract(battle: anytype, player: Player, options: anytype) !bool {
2873
- const cant = if (options.calc.overridden(player, .attract)) |val|
2928
+ const cant = if (options.calc.overridden(player, 0, .attract)) |val|
2874
2929
  val == .true
2875
2930
  else if (showdown)
2876
2931
  battle.rng.chance(u8, 1, 2)
@@ -2882,7 +2937,7 @@ pub const Rolls = struct {
2882
2937
  }
2883
2938
 
2884
2939
  fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
2885
- const par = if (options.calc.overridden(player, .paralyzed)) |val|
2940
+ const par = if (options.calc.overridden(player, 0, .paralyzed)) |val|
2886
2941
  val == .true
2887
2942
  else if (showdown)
2888
2943
  battle.rng.chance(u8, 63, 256)
@@ -2894,7 +2949,7 @@ pub const Rolls = struct {
2894
2949
  }
2895
2950
 
2896
2951
  fn defrost(battle: anytype, player: Player, options: anytype) !bool {
2897
- const thaw = if (options.calc.overridden(player, .defrost)) |val|
2952
+ const thaw = if (options.calc.overridden(player, 0, .defrost)) |val|
2898
2953
  val == .true
2899
2954
  else if (showdown)
2900
2955
  battle.rng.chance(u8, 25, 256)
@@ -2907,7 +2962,7 @@ pub const Rolls = struct {
2907
2962
 
2908
2963
  fn secondaryChance(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
2909
2964
  // GLITCH: 100% secondary chance will still fail 1/256 of the time
2910
- const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
2965
+ const proc = if (options.calc.overridden(player, 0, .secondary_chance)) |val|
2911
2966
  val == .true
2912
2967
  else if (showdown)
2913
2968
  battle.rng.chance(u8, rate, 256)
@@ -2919,7 +2974,7 @@ pub const Rolls = struct {
2919
2974
  }
2920
2975
 
2921
2976
  fn item(battle: anytype, player: Player, options: anytype) !bool {
2922
- const proc = if (options.calc.overridden(player, .item)) |val|
2977
+ const proc = if (options.calc.overridden(player, 0, .item)) |val|
2923
2978
  val == .true
2924
2979
  else if (showdown)
2925
2980
  battle.rng.chance(u8, 30, 256)
@@ -2931,7 +2986,7 @@ pub const Rolls = struct {
2931
2986
  }
2932
2987
 
2933
2988
  fn triAttack(battle: anytype, player: Player, options: anytype) !TriAttack {
2934
- const status: TriAttack = if (options.calc.overridden(player, .tri_attack)) |val|
2989
+ const status: TriAttack = if (options.calc.overridden(player, 0, .tri_attack)) |val|
2935
2990
  @enumFromInt(@intFromEnum(val) - 1)
2936
2991
  else if (showdown)
2937
2992
  @enumFromInt(battle.rng.range(u2, 0, 2))
@@ -2947,7 +3002,7 @@ pub const Rolls = struct {
2947
3002
  }
2948
3003
 
2949
3004
  fn present(battle: anytype, player: Player, options: anytype) !u8 {
2950
- const power = if (options.calc.overridden(player, .present)) |val|
3005
+ const power = if (options.calc.overridden(player, 0, .present)) |val|
2951
3006
  (val - 1) * 40
2952
3007
  else if (showdown) power: {
2953
3008
  const r = battle.rng.range(u8, 0, 10);
@@ -2969,7 +3024,7 @@ pub const Rolls = struct {
2969
3024
  }
2970
3025
 
2971
3026
  fn magnitude(battle: anytype, player: Player, options: anytype) !u8 {
2972
- const num: u8 = if (options.calc.overridden(player, .magnitude)) |val|
3027
+ const num: u8 = if (options.calc.overridden(player, 0, .magnitude)) |val|
2973
3028
  val + 3
2974
3029
  else if (showdown) num: {
2975
3030
  const r = battle.rng.range(u8, 0, 100);
@@ -2997,7 +3052,7 @@ pub const Rolls = struct {
2997
3052
  }
2998
3053
 
2999
3054
  fn tripleKick(battle: anytype, player: Player, options: anytype) !u2 {
3000
- const hits = if (options.calc.overridden(player, .triple_kick)) |val|
3055
+ const hits = if (options.calc.overridden(player, 0, .triple_kick)) |val|
3001
3056
  val
3002
3057
  else if (showdown)
3003
3058
  battle.rng.range(u2, 1, 4)
@@ -3013,13 +3068,12 @@ pub const Rolls = struct {
3013
3068
  return hits;
3014
3069
  }
3015
3070
 
3016
- fn spite(battle: anytype, player: Player, options: anytype) !u8 {
3017
- const pp = if (options.calc.overridden(player, .spite)) |val|
3071
+ fn spite(battle: anytype, player: Player, options: anytype) !u3 {
3072
+ assert(!showdown);
3073
+ const pp: u3 = if (options.calc.overridden(player, 0, .spite)) |val|
3018
3074
  val + 1
3019
- else if (showdown)
3020
- battle.rng.range(u8, 2, 6)
3021
3075
  else
3022
- (battle.rng.next() & 3) + 2;
3076
+ @intCast((battle.rng.next() & 3) + 2);
3023
3077
 
3024
3078
  assert(pp >= 2 and pp <= 5);
3025
3079
  try options.chance.spite(player, pp);
@@ -3033,7 +3087,7 @@ pub const Rolls = struct {
3033
3087
  var i: u8 = 0;
3034
3088
  const neutral = @intFromEnum(Effectiveness.Neutral);
3035
3089
  assert(mtype != .@"???");
3036
- const ty: Type = if (options.calc.overridden(player, .conversion_2)) |val|
3090
+ const ty: Type = if (options.calc.overridden(player, 0, .conversion_2)) |val|
3037
3091
  @enumFromInt(@intFromEnum(val) - 1)
3038
3092
  else if (showdown) ty: {
3039
3093
  for (Type.SHOWDOWN) |t| {
@@ -3067,7 +3121,7 @@ pub const Rolls = struct {
3067
3121
  assert(count <= 8);
3068
3122
  const num = if (count == 8) 0 else @as(u8, 255) >> @intCast(count);
3069
3123
 
3070
- const ok = if (options.calc.overridden(player, .protect)) |val|
3124
+ const ok = if (options.calc.overridden(player, 0, .protect)) |val|
3071
3125
  val == .true
3072
3126
  else if (showdown)
3073
3127
  battle.rng.chance(u8, num, 255)
@@ -3089,7 +3143,7 @@ pub const Rolls = struct {
3089
3143
  const moves = active.moves;
3090
3144
 
3091
3145
  // TODO: consider throwing error instead of rerolling?
3092
- const overridden = if (options.calc.overridden(player, .move_slot)) |val|
3146
+ const overridden = if (options.calc.overridden(player, 0, .move_slot)) |val|
3093
3147
  if (convertible(active, moves[val - 1].id)) val else null
3094
3148
  else
3095
3149
  null;
@@ -3126,7 +3180,7 @@ pub const Rolls = struct {
3126
3180
  const moves = active.moves;
3127
3181
 
3128
3182
  // TODO: consider throwing error instead of rerolling?
3129
- const overridden = if (options.calc.overridden(player, .move_slot)) |val|
3183
+ const overridden = if (options.calc.overridden(player, 0, .move_slot)) |val|
3130
3184
  if (val != mslot and Move.get(moves[val - 1].id).extra.sleep_talk) val else null
3131
3185
  else
3132
3186
  null;
@@ -3170,7 +3224,7 @@ pub const Rolls = struct {
3170
3224
  assert(n > 0 and n <= 6);
3171
3225
 
3172
3226
  // TODO: consider throwing error instead of rerolling?
3173
- const overridden = if (options.calc.overridden(player, .force_switch)) |val| val: {
3227
+ const overridden = if (options.calc.overridden(player, 0, .force_switch)) |val| val: {
3174
3228
  for (slots) |slot| if (slot == val) break :val if (foe.get(slot).hp > 0) val else null;
3175
3229
  break :val null;
3176
3230
  } else null;
@@ -3196,7 +3250,7 @@ pub const Rolls = struct {
3196
3250
  const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
3197
3251
 
3198
3252
  fn multiHit(battle: anytype, player: Player, options: anytype) !u3 {
3199
- const n: u3 = if (options.calc.overridden(player, .multi_hit)) |val|
3253
+ const n: u3 = if (options.calc.overridden(player, 0, .multi_hit)) |val|
3200
3254
  @intCast(val)
3201
3255
  else if (showdown)
3202
3256
  DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
@@ -3211,7 +3265,7 @@ pub const Rolls = struct {
3211
3265
  }
3212
3266
 
3213
3267
  fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
3214
- const power = if (options.calc.overridden(player, .psywave)) |val|
3268
+ const power = if (options.calc.overridden(player, 0, .psywave)) |val|
3215
3269
  val
3216
3270
  else if (showdown)
3217
3271
  battle.rng.range(u8, 1, max)
@@ -3237,7 +3291,7 @@ pub const Rolls = struct {
3237
3291
  }
3238
3292
 
3239
3293
  // TODO: consider throwing error instead of rerolling?
3240
- const overridden = if (options.calc.overridden(player, .metronome)) |val| val: {
3294
+ const overridden = if (options.calc.overridden(player, 0, .metronome)) |val| val: {
3241
3295
  for (moves) |m| if (m.id == val) break :val null;
3242
3296
  break :val val;
3243
3297
  } else null;
@@ -3269,7 +3323,7 @@ pub const Rolls = struct {
3269
3323
  }
3270
3324
 
3271
3325
  fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
3272
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
3326
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3273
3327
  @intCast(val)
3274
3328
  else if (showdown)
3275
3329
  battle.rng.range(u3, 1, 7)
@@ -3281,12 +3335,12 @@ pub const Rolls = struct {
3281
3335
  };
3282
3336
 
3283
3337
  assert(duration >= 1 and duration <= 6);
3284
- options.chance.duration(.sleep, player, player.foe(), duration);
3338
+ options.chance.duration(player, duration);
3285
3339
  return duration + 1;
3286
3340
  }
3287
3341
 
3288
3342
  fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
3289
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
3343
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3290
3344
  @intCast(val)
3291
3345
  else if (showdown)
3292
3346
  // Pokémon Showdown incorrectly uses the same duration for self-confusion
@@ -3295,16 +3349,16 @@ pub const Rolls = struct {
3295
3349
  @intCast((battle.rng.next() & if (self) 1 else 3) + 2);
3296
3350
 
3297
3351
  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);
3352
+ options.chance.duration(player, duration);
3299
3353
  return duration;
3300
3354
  }
3301
3355
 
3302
3356
  fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
3303
- const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
3357
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3304
3358
  @intCast(val)
3305
3359
  else if (showdown)
3306
3360
  // Pokémon Showdown incorrectly inherits Generation III's Disable duration
3307
- battle.rng.range(u4, 2, 6)
3361
+ battle.rng.range(u3, 2, 6)
3308
3362
  else duration: {
3309
3363
  while (true) {
3310
3364
  const r = battle.rng.next() & 7;
@@ -3313,12 +3367,12 @@ pub const Rolls = struct {
3313
3367
  };
3314
3368
 
3315
3369
  assert(duration >= 2 and duration <= (if (showdown) 5 else 7));
3316
- options.chance.duration(.disable, player, player.foe(), duration);
3370
+ options.chance.duration(player, duration);
3317
3371
  return duration;
3318
3372
  }
3319
3373
 
3320
3374
  fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
3321
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
3375
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3322
3376
  @intCast(val)
3323
3377
  else if (showdown)
3324
3378
  battle.rng.range(u3, 2, 4)
@@ -3326,12 +3380,12 @@ pub const Rolls = struct {
3326
3380
  @intCast((battle.rng.next() & 1) + 2); // BUG: thrash is + 1 not +2
3327
3381
 
3328
3382
  assert(duration >= 2 and duration <= 3);
3329
- options.chance.duration(.attacking, player, player, duration);
3383
+ options.chance.duration(player, duration);
3330
3384
  return duration;
3331
3385
  }
3332
3386
 
3333
3387
  fn bindingDuration(battle: anytype, player: Player, options: anytype) u3 {
3334
- const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
3388
+ const duration: u3 = (if (options.calc.overridden(player, 0, .duration)) |val|
3335
3389
  @intCast(val)
3336
3390
  else if (showdown)
3337
3391
  // TODO: PS does range(3, 6) here which after subtracting 1 is actually 2-4 not 2-5!
@@ -3340,13 +3394,13 @@ pub const Rolls = struct {
3340
3394
  @intCast(battle.rng.next() & 3 + 2));
3341
3395
 
3342
3396
  assert(duration >= 2 and duration <= 5);
3343
- options.chance.duration(.binding, player, player.foe(), duration);
3397
+ options.chance.duration(player, duration);
3344
3398
  return duration + 1;
3345
3399
  }
3346
3400
 
3347
3401
  // TODO: consider sharing implementation with bindingDuration
3348
3402
  fn encoreDuration(battle: anytype, player: Player, options: anytype) u3 {
3349
- const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
3403
+ const duration: u3 = (if (options.calc.overridden(player, 0, .duration)) |val|
3350
3404
  @intCast(val)
3351
3405
  else if (showdown)
3352
3406
  battle.rng.range(u3, 2, 6)
@@ -3354,7 +3408,7 @@ pub const Rolls = struct {
3354
3408
  @intCast(battle.rng.next() & 3 + 2));
3355
3409
 
3356
3410
  assert(duration >= 2 and duration <= 5);
3357
- options.chance.duration(.encore, player, player.foe(), duration);
3411
+ options.chance.duration(player, duration);
3358
3412
  return duration + 1;
3359
3413
  }
3360
3414
  };
@@ -3467,7 +3521,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3467
3521
  .Move => {
3468
3522
  const side = battle.side(player);
3469
3523
 
3470
- var active = &side.active;
3524
+ const active = &side.active;
3471
3525
  const stored = side.stored();
3472
3526
 
3473
3527
  // While players are not given any input options on the cartridge in these cases,