@pkmn/engine 0.1.0-dev.6c5fdce2 → 0.1.0-dev.6d566713

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 (45) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +41 -14
  3. package/build/pkg/addon.js +6 -7
  4. package/build/pkg/addon.js.map +1 -1
  5. package/build/pkg/data.d.ts +4 -5
  6. package/build/pkg/gen1.js.map +1 -1
  7. package/build/pkg/index.d.ts +1 -1
  8. package/build/pkg/index.js.map +1 -1
  9. package/build/pkg/protocol.js +4 -4
  10. package/build/pkg/protocol.js.map +1 -1
  11. package/build/test/display.js +8 -3
  12. package/build/test/display.js.map +1 -1
  13. package/build/tools/debug.js +2 -3
  14. package/build/tools/debug.js.map +1 -1
  15. package/build.zig +203 -89
  16. package/package.json +32 -22
  17. package/src/bin/install-pkmn-engine +30 -20
  18. package/src/lib/common/array.zig +77 -0
  19. package/src/lib/common/data.zig +4 -4
  20. package/src/lib/common/debug.zig +2 -2
  21. package/src/lib/common/js.zig +4 -2
  22. package/src/lib/common/optional.zig +14 -10
  23. package/src/lib/common/options.zig +2 -0
  24. package/src/lib/common/protocol.zig +57 -57
  25. package/src/lib/common/rational.zig +114 -24
  26. package/src/lib/common/rng.zig +7 -7
  27. package/src/lib/common/util.zig +50 -1
  28. package/src/lib/gen1/calc.zig +506 -128
  29. package/src/lib/gen1/chance.zig +374 -267
  30. package/src/lib/gen1/data/species.zig +1 -1
  31. package/src/lib/gen1/data.zig +17 -16
  32. package/src/lib/gen1/helpers.zig +95 -538
  33. package/src/lib/gen1/mechanics.zig +268 -174
  34. package/src/lib/gen2/calc.zig +34 -16
  35. package/src/lib/gen2/chance.zig +84 -496
  36. package/src/lib/gen2/data.zig +7 -6
  37. package/src/lib/gen2/generated.zig +0 -2
  38. package/src/lib/gen2/helpers.zig +41 -13
  39. package/src/lib/gen2/mechanics.zig +158 -108
  40. package/src/lib/{binding/node.zig → node.zig} +3 -6
  41. package/src/lib/pkmn.zig +32 -15
  42. package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
  43. package/src/test/display/pkmn.html.tmpl +5 -5
  44. package/src/lib/gen1/README.md +0 -894
  45. package/src/lib/gen2/README.md +0 -329
@@ -6,7 +6,6 @@ const common = @import("../common/data.zig");
6
6
  const DEBUG = @import("../common/debug.zig").print;
7
7
  const protocol = @import("../common/protocol.zig");
8
8
  const rng = @import("../common/rng.zig");
9
- const util = @import("../common/util.zig");
10
9
 
11
10
  const data = @import("data.zig");
12
11
  const generated = @import("generated.zig");
@@ -34,17 +33,12 @@ const Start = protocol.Start;
34
33
 
35
34
  const Gen12 = rng.Gen12;
36
35
 
37
- const PointerType = util.PointerType;
38
-
39
36
  const ActivePokemon = data.ActivePokemon;
40
37
  const Boosts = data.Boosts;
41
38
  const Effectiveness = data.Effectiveness;
42
39
  const Item = data.Item;
43
40
  const Move = data.Move;
44
- const MoveSlot = data.MoveSlot;
45
- const Pokemon = data.Pokemon;
46
41
  const Side = data.Side;
47
- const Species = data.Species;
48
42
  const Stats = data.Stats;
49
43
  const Status = data.Status;
50
44
  const TriAttack = data.TriAttack;
@@ -106,6 +100,7 @@ pub const State = struct {
106
100
  type: Type, // BATTLE_VARS_MOVE_TYPE
107
101
  accuracy: u8, // wEnemyMoveStruct + MOVE_ACC
108
102
  move: Move, // wCurPlayerMove
103
+ hit: u3 = 0,
109
104
  from: Move = .None,
110
105
  mslot: u4, // wCurMoveNum
111
106
  first: bool, // wEnemyGoesFirst
@@ -256,7 +251,6 @@ fn switchIn(battle: anytype, player: Player, slot: u8, kind: SwitchIn, options:
256
251
  } else {
257
252
  try options.log.switched(.{ battle.active(player), incoming });
258
253
  }
259
- options.chance.switched(player, side.pokemon[0].position, out);
260
254
 
261
255
  if (side.conditions.Spikes and !active.types.includes(.Flying)) {
262
256
  incoming.hp -|= @max(incoming.stats.hp / 8, 1);
@@ -442,18 +436,9 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
442
436
  if (Status.is(stored.status, .SLP)) slp: {
443
437
  const before = stored.status;
444
438
  // Even if the EXT bit is set this will still correctly modify the sleep duration
445
- // if (options.calc.modify(player, .sleep)) |extend| {
446
- // if (!extend) stored.status = 0;
447
- // } else {
448
439
  stored.status -= 1;
449
- // }
450
440
 
451
- const duration = Status.duration(stored.status);
452
- if (!Status.is(stored.status, .EXT)) {
453
- try options.chance.sleep(player, duration);
454
- }
455
-
456
- if (duration == 0) {
441
+ if (Status.duration(stored.status) == 0) {
457
442
  try log.curestatus(.{ ident, before, .Message });
458
443
  stored.status = 0; // clears EXT if present
459
444
  resetCant(volatiles);
@@ -483,12 +468,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
483
468
  }
484
469
 
485
470
  if (volatiles.disable.duration > 0) {
486
- // if (options.calc.modify(player, .disable)) |extend| {
487
- // if (!extend) volatiles.disable.duration = 0;
488
- // } else {
489
471
  volatiles.disable.duration -= 1;
490
- // }
491
- try options.chance.disable(player, volatiles.disable.duration);
492
472
 
493
473
  if (volatiles.disable.duration == 0) {
494
474
  volatiles.disable.move = 0;
@@ -498,13 +478,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
498
478
 
499
479
  if (volatiles.Confusion) {
500
480
  assert(volatiles.confusion > 0);
501
-
502
- // if (options.calc.modify(player, .confusion)) |extend| {
503
- // if (!extend) volatiles.confusion = 0;
504
- // } else {
505
481
  volatiles.confusion -= 1;
506
- // }
507
- try options.chance.confusion(player, volatiles.confusion);
508
482
 
509
483
  if (volatiles.confusion == 0) {
510
484
  volatiles.Confusion = false;
@@ -665,7 +639,7 @@ pub fn checkCriticalHit(battle: anytype, player: Player, state: *State, options:
665
639
  }
666
640
  assert(stage <= 4);
667
641
 
668
- state.crit = try Rolls.criticalHit(battle, player, CRITICAL_HITS[stage], options);
642
+ state.crit = try Rolls.criticalHit(battle, player, state.hit, CRITICAL_HITS[stage], options);
669
643
  }
670
644
 
671
645
  pub fn calcDamage(
@@ -733,7 +707,7 @@ pub fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype)
733
707
 
734
708
  pub fn randomizeDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
735
709
  if (state.damage <= 1) return;
736
- const random = try Rolls.damage(battle, player, options);
710
+ const random = try Rolls.damage(battle, player, state.hit, options);
737
711
  options.calc.base(player.foe(), state.damage);
738
712
  state.damage = @intCast(@as(u32, state.damage) *% random / 255);
739
713
  options.calc.final(player.foe(), state.damage);
@@ -999,7 +973,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
999
973
 
1000
974
  // Weather
1001
975
  if (battle.field.weather != .None) {
1002
- assert(battle.field.weather_duration > 0);
1003
976
  battle.field.weather_duration -= 1;
1004
977
 
1005
978
  if (battle.field.weather_duration == 0) {
@@ -1058,7 +1031,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1058
1031
  var volatiles = &side.active.volatiles;
1059
1032
 
1060
1033
  if (volatiles.PerishSong) {
1061
- assert(volatiles.perish_song > 0);
1062
1034
  volatiles.perish_song -= 1;
1063
1035
 
1064
1036
  assert(volatiles.perish_song < 3);
@@ -1195,7 +1167,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1195
1167
  var volatiles = &side.active.volatiles;
1196
1168
 
1197
1169
  if (volatiles.Encore) {
1198
- assert(volatiles.encore > 0);
1199
1170
  volatiles.encore -= 1;
1200
1171
 
1201
1172
  if (volatiles.encore == 0 or side.active.move(mslot).pp == 0) {
@@ -1217,7 +1188,6 @@ fn updateSideCondition(
1217
1188
  ) !void {
1218
1189
  var side = battle.side(player);
1219
1190
  if (@field(side.conditions, key)) {
1220
- assert(@field(side.conditions, value) > 0);
1221
1191
  @field(side.conditions, value) -= 1;
1222
1192
 
1223
1193
  if (@field(side.conditions, value) == 0) {
@@ -1308,14 +1278,32 @@ pub const Effects = struct {
1308
1278
  }
1309
1279
 
1310
1280
  pub fn bellyDrum(battle: anytype, player: Player, state: *State, options: anytype) !void {
1311
- _ = .{ battle, player, state, options }; // TODO
1312
- // assert(!state.miss);
1313
- // var side = battle.side(player);
1314
- // try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
1315
- // TODO
1316
- // |-boost|p1a: Poliwag|atk|2|[silent]
1317
- // |-fail|p1a: Poliwag
1318
- //
1281
+ assert(!state.miss);
1282
+ var side = battle.side(player);
1283
+
1284
+ const ident = battle.active(player);
1285
+
1286
+ assert(side.active.boosts.atk >= -6 and side.active.boosts.atk <= 6);
1287
+ if (side.active.boosts.atk == 6 or side.active.boosts.atk == MAX_STAT_VALUE) {
1288
+ return options.log.fail(.{ ident, .None });
1289
+ }
1290
+
1291
+ const hp = @max(side.stored().stats.hp / 2, 1);
1292
+ if (side.stored().hp <= hp) {
1293
+ try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
1294
+ try options.log.boost(.{ ident, .Attack, @as(i8, 2), .Silent });
1295
+ return options.log.fail(.{ ident, .None });
1296
+ } else {
1297
+ side.stored().hp -= hp;
1298
+ try options.log.damage(.{ ident, side.stored(), .None });
1299
+
1300
+ const before = side.active.boosts.atk;
1301
+ var i: usize = 0;
1302
+ while (i < 6 and side.active.stats.atk < MAX_STAT_VALUE) : (i += 1) {
1303
+ try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
1304
+ }
1305
+ try options.log.boost(.{ ident, .Attack, @as(i8, side.active.boosts.atk - before) });
1306
+ }
1319
1307
  }
1320
1308
 
1321
1309
  pub const bide = struct {
@@ -1327,14 +1315,7 @@ pub const Effects = struct {
1327
1315
 
1328
1316
  if (!volatiles.Bide) return;
1329
1317
 
1330
- assert(volatiles.attacks > 0);
1331
- // if (options.calc.modify(player, .attacking)) |extend| {
1332
- // if (!extend) volatiles.attacks = 0;
1333
- // } else {
1334
1318
  volatiles.attacks -= 1;
1335
- // }
1336
- try options.chance.attacking(player, volatiles.attacks);
1337
-
1338
1319
  if (volatiles.attacks != 0) return options.log.activate(.{ ident, .Bide });
1339
1320
 
1340
1321
  volatiles.Bide = false;
@@ -1601,8 +1582,16 @@ pub const Effects = struct {
1601
1582
  try options.log.start(.{ foe_ident, .Encore, move.id });
1602
1583
  }
1603
1584
 
1604
- pub fn explode(battle: anytype, player: Player, state: *State, options: anytype) !void {
1605
- _ = .{ battle, player, state, options }; // TODO
1585
+ pub fn explode(battle: anytype, player: Player, _: *State, _: anytype) !void {
1586
+ var side = battle.side(player);
1587
+ var stored = side.stored();
1588
+
1589
+ stored.hp = 0;
1590
+ // Pokémon Showdown sets the status to 0 on faint(), and we need to do the same to be able
1591
+ // to correctly implement Pokémon Showdown's dumb speed-based switch mechanics
1592
+ if (!showdown) stored.status = 0;
1593
+ side.active.volatiles.LeechSeed = false;
1594
+ side.active.volatiles.DestinyBond = false;
1606
1595
  }
1607
1596
 
1608
1597
  pub fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
@@ -1728,8 +1717,26 @@ pub const Effects = struct {
1728
1717
  foe.active.volatiles.frozen = true;
1729
1718
  }
1730
1719
 
1731
- pub fn furyCutter(battle: anytype, player: Player, state: *State, options: anytype) !void {
1732
- _ = .{ battle, player, state, options }; // TODO
1720
+ // TODO FIXME showdown calls this before calcDamage (after checkHit) and modifies state.bp!
1721
+ pub fn furyCutter(battle: anytype, player: Player, state: *State, _: anytype) !void {
1722
+ var side = battle.side(player);
1723
+ var volatiles = &side.active.volatiles;
1724
+
1725
+ if (state.miss) {
1726
+ volatiles.fury_cutter = 0;
1727
+ return;
1728
+ }
1729
+
1730
+ assert(volatiles.fury_cutter >= 0 and volatiles.fury_cutter <= 5);
1731
+ if (volatiles.fury_cutter == 5) return;
1732
+ volatiles.fury_cutter += 1;
1733
+
1734
+ if (showdown) {
1735
+ state.bp *= 2;
1736
+ assert(state.bp <= 160);
1737
+ } else {
1738
+ state.damage *|= 2;
1739
+ }
1733
1740
  }
1734
1741
 
1735
1742
  pub fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -1890,7 +1897,29 @@ pub const Effects = struct {
1890
1897
  }
1891
1898
 
1892
1899
  pub fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
1893
- _ = .{ battle, player, state, options }; // TODO
1900
+ var side = battle.side(player);
1901
+
1902
+ if (state.miss) {
1903
+ if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
1904
+ try options.log.lastmiss(.{});
1905
+ return options.log.miss(.{battle.active(player)});
1906
+ }
1907
+
1908
+ const move = battle.foe(player).lastMove(false);
1909
+ if (move == .None or move == .Struggle) {
1910
+ return options.log.fail(.{ battle.active(player.foe()), .None });
1911
+ }
1912
+
1913
+ var slot: u4 = 0;
1914
+ for (side.active.moves, 0..) |m, i| {
1915
+ if (m.id == move) return options.log.fail(.{ battle.active(player.foe()), .None });
1916
+ if (m.id == .Mimic) slot = @intCast(i + 1);
1917
+ }
1918
+ assert(slot != 0 and slot == state.mslot);
1919
+
1920
+ side.active.move(slot).id = move;
1921
+ side.active.move(slot).pp = 5;
1922
+ try options.log.start(.{ battle.active(player), .Mimic, move });
1894
1923
  }
1895
1924
 
1896
1925
  pub fn mirrorMove(
@@ -2398,17 +2427,37 @@ pub const Effects = struct {
2398
2427
  }
2399
2428
 
2400
2429
  pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
2401
- _ = .{ battle, player, state, options }; // TODO
2402
- // var foe = battle.foe(player);
2430
+ var foe = battle.foe(player);
2403
2431
 
2404
- // if (state.miss) return options.log.fail(.{ battle.active(player), .None });
2432
+ const last = foe.lastMove(false);
2433
+ if (state.miss or last == .None or last == .Struggle) {
2434
+ return options.log.fail(.{ battle.active(player.foe()), .None });
2435
+ }
2405
2436
 
2406
- // const last = foe.lastMove(false);
2407
- // if (showdown) {
2408
- // const roll = try Rolls.spite(battle, player, options);
2409
- // } else {
2410
- // if (last != .None) {}
2411
- // }
2437
+ var slot: u4 = 0;
2438
+ for (foe.active.moves, 0..) |m, i| {
2439
+ if (m.id == last) slot = @intCast(i + 1);
2440
+ }
2441
+ // If the move was called via Metronome it might not exist
2442
+ if (slot == 0 or foe.active.move(slot).pp == 0) {
2443
+ return options.log.fail(.{ battle.active(player.foe()), .None });
2444
+ }
2445
+
2446
+ // Pokémon Showdown always deducts 4 instead of randomizing
2447
+ const pp = @min(
2448
+ foe.active.move(slot).pp,
2449
+ if (showdown) 4 else try Rolls.spite(battle, player, options),
2450
+ );
2451
+ foe.active.move(slot).pp -= pp;
2452
+ if (!foe.active.volatiles.Transform) {
2453
+ var move_slot = foe.stored().move(slot);
2454
+ assert(move_slot.pp > 0);
2455
+
2456
+ move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
2457
+ assert(foe.active.move(slot).pp == foe.stored().move(slot).pp);
2458
+ }
2459
+
2460
+ try options.log.activate(.{ battle.active(player.foe()), .Spite, last, pp });
2412
2461
  }
2413
2462
 
2414
2463
  pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
@@ -2795,7 +2844,7 @@ fn convertible(active: ActivePokemon, m: Move) bool {
2795
2844
 
2796
2845
  pub const Rolls = struct {
2797
2846
  fn speedTie(battle: anytype, options: anytype) !bool {
2798
- const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
2847
+ const p1 = if (options.calc.overridden(.P1, 0, .speed_tie)) |player|
2799
2848
  player == .P1
2800
2849
  else if (showdown)
2801
2850
  battle.rng.range(u8, 0, 2) == 0
@@ -2807,7 +2856,7 @@ pub const Rolls = struct {
2807
2856
  }
2808
2857
 
2809
2858
  fn quickClaw(battle: anytype, player: Player, options: anytype) !bool {
2810
- const qkc = if (options.calc.overridden(player, .quick_claw)) |val|
2859
+ const qkc = if (options.calc.overridden(player, 0, .quick_claw)) |val|
2811
2860
  val == .true
2812
2861
  else if (showdown)
2813
2862
  battle.rng.chance(u8, 60, 256)
@@ -2819,7 +2868,7 @@ pub const Rolls = struct {
2819
2868
  }
2820
2869
 
2821
2870
  fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) !bool {
2822
- const ok = if (options.calc.overridden(player, .hit)) |val|
2871
+ const ok = if (options.calc.overridden(player, 0, .hit)) |val|
2823
2872
  val == .true
2824
2873
  else if (showdown)
2825
2874
  battle.rng.chance(u8, accuracy, 256)
@@ -2830,20 +2879,22 @@ pub const Rolls = struct {
2830
2879
  return ok;
2831
2880
  }
2832
2881
 
2833
- fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
2834
- const crit = if (options.calc.overridden(player, .critical_hit)) |val|
2882
+ fn criticalHit(battle: anytype, player: Player, i: u3, rate: u8, options: anytype) !bool {
2883
+ _ = .{ player, options };
2884
+ const crit = if (options.calc.overridden(player, i, .critical_hits)) |val|
2835
2885
  val == .true
2836
2886
  else if (showdown)
2837
2887
  battle.rng.chance(u8, rate, 256)
2838
2888
  else
2839
2889
  battle.rng.next() < rate;
2840
2890
 
2841
- try options.chance.criticalHit(player, crit, rate);
2891
+ try options.chance.criticalHit(player, i, crit, rate);
2842
2892
  return crit;
2843
2893
  }
2844
2894
 
2845
- fn damage(battle: anytype, player: Player, options: anytype) !u8 {
2846
- const roll = if (options.calc.overridden(player, .damage)) |val|
2895
+ fn damage(battle: anytype, player: Player, i: u3, options: anytype) !u8 {
2896
+ _ = .{ player, options };
2897
+ const roll = if (options.calc.overridden(player, i, .damages)) |val|
2847
2898
  val
2848
2899
  else roll: {
2849
2900
  if (showdown) break :roll battle.rng.range(u8, 217, 256);
@@ -2854,12 +2905,12 @@ pub const Rolls = struct {
2854
2905
  };
2855
2906
 
2856
2907
  assert(roll >= 217 and roll <= 255);
2857
- try options.chance.damage(player, roll);
2908
+ try options.chance.damage(player, i, roll);
2858
2909
  return roll;
2859
2910
  }
2860
2911
 
2861
2912
  fn confused(battle: anytype, player: Player, options: anytype) !bool {
2862
- const cfz = if (options.calc.overridden(player, .confused)) |val|
2913
+ const cfz = if (options.calc.overridden(player, 0, .confused)) |val|
2863
2914
  val == .true
2864
2915
  else if (showdown)
2865
2916
  !battle.rng.chance(u8, 128, 256)
@@ -2871,7 +2922,7 @@ pub const Rolls = struct {
2871
2922
  }
2872
2923
 
2873
2924
  fn attract(battle: anytype, player: Player, options: anytype) !bool {
2874
- const cant = if (options.calc.overridden(player, .attract)) |val|
2925
+ const cant = if (options.calc.overridden(player, 0, .attract)) |val|
2875
2926
  val == .true
2876
2927
  else if (showdown)
2877
2928
  battle.rng.chance(u8, 1, 2)
@@ -2883,7 +2934,7 @@ pub const Rolls = struct {
2883
2934
  }
2884
2935
 
2885
2936
  fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
2886
- const par = if (options.calc.overridden(player, .paralyzed)) |val|
2937
+ const par = if (options.calc.overridden(player, 0, .paralyzed)) |val|
2887
2938
  val == .true
2888
2939
  else if (showdown)
2889
2940
  battle.rng.chance(u8, 63, 256)
@@ -2895,7 +2946,7 @@ pub const Rolls = struct {
2895
2946
  }
2896
2947
 
2897
2948
  fn defrost(battle: anytype, player: Player, options: anytype) !bool {
2898
- const thaw = if (options.calc.overridden(player, .defrost)) |val|
2949
+ const thaw = if (options.calc.overridden(player, 0, .defrost)) |val|
2899
2950
  val == .true
2900
2951
  else if (showdown)
2901
2952
  battle.rng.chance(u8, 25, 256)
@@ -2908,7 +2959,7 @@ pub const Rolls = struct {
2908
2959
 
2909
2960
  fn secondaryChance(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
2910
2961
  // GLITCH: 100% secondary chance will still fail 1/256 of the time
2911
- const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
2962
+ const proc = if (options.calc.overridden(player, 0, .secondary_chance)) |val|
2912
2963
  val == .true
2913
2964
  else if (showdown)
2914
2965
  battle.rng.chance(u8, rate, 256)
@@ -2920,7 +2971,7 @@ pub const Rolls = struct {
2920
2971
  }
2921
2972
 
2922
2973
  fn item(battle: anytype, player: Player, options: anytype) !bool {
2923
- const proc = if (options.calc.overridden(player, .item)) |val|
2974
+ const proc = if (options.calc.overridden(player, 0, .item)) |val|
2924
2975
  val == .true
2925
2976
  else if (showdown)
2926
2977
  battle.rng.chance(u8, 30, 256)
@@ -2932,7 +2983,7 @@ pub const Rolls = struct {
2932
2983
  }
2933
2984
 
2934
2985
  fn triAttack(battle: anytype, player: Player, options: anytype) !TriAttack {
2935
- const status: TriAttack = if (options.calc.overridden(player, .tri_attack)) |val|
2986
+ const status: TriAttack = if (options.calc.overridden(player, 0, .tri_attack)) |val|
2936
2987
  @enumFromInt(@intFromEnum(val) - 1)
2937
2988
  else if (showdown)
2938
2989
  @enumFromInt(battle.rng.range(u2, 0, 2))
@@ -2948,7 +2999,7 @@ pub const Rolls = struct {
2948
2999
  }
2949
3000
 
2950
3001
  fn present(battle: anytype, player: Player, options: anytype) !u8 {
2951
- const power = if (options.calc.overridden(player, .present)) |val|
3002
+ const power = if (options.calc.overridden(player, 0, .present)) |val|
2952
3003
  (val - 1) * 40
2953
3004
  else if (showdown) power: {
2954
3005
  const r = battle.rng.range(u8, 0, 10);
@@ -2970,7 +3021,7 @@ pub const Rolls = struct {
2970
3021
  }
2971
3022
 
2972
3023
  fn magnitude(battle: anytype, player: Player, options: anytype) !u8 {
2973
- const num: u8 = if (options.calc.overridden(player, .magnitude)) |val|
3024
+ const num: u8 = if (options.calc.overridden(player, 0, .magnitude)) |val|
2974
3025
  val + 3
2975
3026
  else if (showdown) num: {
2976
3027
  const r = battle.rng.range(u8, 0, 100);
@@ -2998,7 +3049,7 @@ pub const Rolls = struct {
2998
3049
  }
2999
3050
 
3000
3051
  fn tripleKick(battle: anytype, player: Player, options: anytype) !u2 {
3001
- const hits = if (options.calc.overridden(player, .triple_kick)) |val|
3052
+ const hits = if (options.calc.overridden(player, 0, .triple_kick)) |val|
3002
3053
  val
3003
3054
  else if (showdown)
3004
3055
  battle.rng.range(u2, 1, 4)
@@ -3014,13 +3065,12 @@ pub const Rolls = struct {
3014
3065
  return hits;
3015
3066
  }
3016
3067
 
3017
- fn spite(battle: anytype, player: Player, options: anytype) !u8 {
3018
- const pp = if (options.calc.overridden(player, .spite)) |val|
3068
+ fn spite(battle: anytype, player: Player, options: anytype) !u3 {
3069
+ assert(!showdown);
3070
+ const pp: u3 = if (options.calc.overridden(player, 0, .spite)) |val|
3019
3071
  val + 1
3020
- else if (showdown)
3021
- battle.rng.range(u8, 2, 6)
3022
3072
  else
3023
- (battle.rng.next() & 3) + 2;
3073
+ @intCast((battle.rng.next() & 3) + 2);
3024
3074
 
3025
3075
  assert(pp >= 2 and pp <= 5);
3026
3076
  try options.chance.spite(player, pp);
@@ -3034,7 +3084,7 @@ pub const Rolls = struct {
3034
3084
  var i: u8 = 0;
3035
3085
  const neutral = @intFromEnum(Effectiveness.Neutral);
3036
3086
  assert(mtype != .@"???");
3037
- const ty: Type = if (options.calc.overridden(player, .conversion_2)) |val|
3087
+ const ty: Type = if (options.calc.overridden(player, 0, .conversion_2)) |val|
3038
3088
  @enumFromInt(@intFromEnum(val) - 1)
3039
3089
  else if (showdown) ty: {
3040
3090
  for (Type.SHOWDOWN) |t| {
@@ -3068,7 +3118,7 @@ pub const Rolls = struct {
3068
3118
  assert(count <= 8);
3069
3119
  const num = if (count == 8) 0 else @as(u8, 255) >> @intCast(count);
3070
3120
 
3071
- const ok = if (options.calc.overridden(player, .protect)) |val|
3121
+ const ok = if (options.calc.overridden(player, 0, .protect)) |val|
3072
3122
  val == .true
3073
3123
  else if (showdown)
3074
3124
  battle.rng.chance(u8, num, 255)
@@ -3090,7 +3140,7 @@ pub const Rolls = struct {
3090
3140
  const moves = active.moves;
3091
3141
 
3092
3142
  // TODO: consider throwing error instead of rerolling?
3093
- const overridden = if (options.calc.overridden(player, .move_slot)) |val|
3143
+ const overridden = if (options.calc.overridden(player, 0, .move_slot)) |val|
3094
3144
  if (convertible(active, moves[val - 1].id)) val else null
3095
3145
  else
3096
3146
  null;
@@ -3127,7 +3177,7 @@ pub const Rolls = struct {
3127
3177
  const moves = active.moves;
3128
3178
 
3129
3179
  // TODO: consider throwing error instead of rerolling?
3130
- const overridden = if (options.calc.overridden(player, .move_slot)) |val|
3180
+ const overridden = if (options.calc.overridden(player, 0, .move_slot)) |val|
3131
3181
  if (val != mslot and Move.get(moves[val - 1].id).extra.sleep_talk) val else null
3132
3182
  else
3133
3183
  null;
@@ -3171,7 +3221,7 @@ pub const Rolls = struct {
3171
3221
  assert(n > 0 and n <= 6);
3172
3222
 
3173
3223
  // TODO: consider throwing error instead of rerolling?
3174
- const overridden = if (options.calc.overridden(player, .force_switch)) |val| val: {
3224
+ const overridden = if (options.calc.overridden(player, 0, .force_switch)) |val| val: {
3175
3225
  for (slots) |slot| if (slot == val) break :val if (foe.get(slot).hp > 0) val else null;
3176
3226
  break :val null;
3177
3227
  } else null;
@@ -3197,7 +3247,7 @@ pub const Rolls = struct {
3197
3247
  const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
3198
3248
 
3199
3249
  fn multiHit(battle: anytype, player: Player, options: anytype) !u3 {
3200
- const n: u3 = if (options.calc.overridden(player, .multi_hit)) |val|
3250
+ const n: u3 = if (options.calc.overridden(player, 0, .multi_hit)) |val|
3201
3251
  @intCast(val)
3202
3252
  else if (showdown)
3203
3253
  DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
@@ -3212,7 +3262,7 @@ pub const Rolls = struct {
3212
3262
  }
3213
3263
 
3214
3264
  fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
3215
- const power = if (options.calc.overridden(player, .psywave)) |val|
3265
+ const power = if (options.calc.overridden(player, 0, .psywave)) |val|
3216
3266
  val
3217
3267
  else if (showdown)
3218
3268
  battle.rng.range(u8, 1, max)
@@ -3238,7 +3288,7 @@ pub const Rolls = struct {
3238
3288
  }
3239
3289
 
3240
3290
  // TODO: consider throwing error instead of rerolling?
3241
- const overridden = if (options.calc.overridden(player, .metronome)) |val| val: {
3291
+ const overridden = if (options.calc.overridden(player, 0, .metronome)) |val| val: {
3242
3292
  for (moves) |m| if (m.id == val) break :val null;
3243
3293
  break :val val;
3244
3294
  } else null;
@@ -3270,7 +3320,7 @@ pub const Rolls = struct {
3270
3320
  }
3271
3321
 
3272
3322
  fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
3273
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
3323
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3274
3324
  @intCast(val)
3275
3325
  else if (showdown)
3276
3326
  battle.rng.range(u3, 1, 7)
@@ -3282,12 +3332,12 @@ pub const Rolls = struct {
3282
3332
  };
3283
3333
 
3284
3334
  assert(duration >= 1 and duration <= 6);
3285
- options.chance.duration(.sleep, player, player.foe(), duration);
3335
+ options.chance.duration(player, duration);
3286
3336
  return duration + 1;
3287
3337
  }
3288
3338
 
3289
3339
  fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
3290
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
3340
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3291
3341
  @intCast(val)
3292
3342
  else if (showdown)
3293
3343
  // Pokémon Showdown incorrectly uses the same duration for self-confusion
@@ -3296,16 +3346,16 @@ pub const Rolls = struct {
3296
3346
  @intCast((battle.rng.next() & if (self) 1 else 3) + 2);
3297
3347
 
3298
3348
  assert(duration >= 2 and duration <= if (!showdown and self) 3 else 5);
3299
- options.chance.duration(.confusion, player, if (self) player else player.foe(), duration);
3349
+ options.chance.duration(player, duration);
3300
3350
  return duration;
3301
3351
  }
3302
3352
 
3303
3353
  fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
3304
- const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
3354
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3305
3355
  @intCast(val)
3306
3356
  else if (showdown)
3307
3357
  // Pokémon Showdown incorrectly inherits Generation III's Disable duration
3308
- battle.rng.range(u4, 2, 6)
3358
+ battle.rng.range(u3, 2, 6)
3309
3359
  else duration: {
3310
3360
  while (true) {
3311
3361
  const r = battle.rng.next() & 7;
@@ -3314,12 +3364,12 @@ pub const Rolls = struct {
3314
3364
  };
3315
3365
 
3316
3366
  assert(duration >= 2 and duration <= (if (showdown) 5 else 7));
3317
- options.chance.duration(.disable, player, player.foe(), duration);
3367
+ options.chance.duration(player, duration);
3318
3368
  return duration;
3319
3369
  }
3320
3370
 
3321
3371
  fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
3322
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
3372
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3323
3373
  @intCast(val)
3324
3374
  else if (showdown)
3325
3375
  battle.rng.range(u3, 2, 4)
@@ -3327,12 +3377,12 @@ pub const Rolls = struct {
3327
3377
  @intCast((battle.rng.next() & 1) + 2); // BUG: thrash is + 1 not +2
3328
3378
 
3329
3379
  assert(duration >= 2 and duration <= 3);
3330
- options.chance.duration(.attacking, player, player, duration);
3380
+ options.chance.duration(player, duration);
3331
3381
  return duration;
3332
3382
  }
3333
3383
 
3334
3384
  fn bindingDuration(battle: anytype, player: Player, options: anytype) u3 {
3335
- const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
3385
+ const duration: u3 = (if (options.calc.overridden(player, 0, .duration)) |val|
3336
3386
  @intCast(val)
3337
3387
  else if (showdown)
3338
3388
  // TODO: PS does range(3, 6) here which after subtracting 1 is actually 2-4 not 2-5!
@@ -3341,13 +3391,13 @@ pub const Rolls = struct {
3341
3391
  @intCast(battle.rng.next() & 3 + 2));
3342
3392
 
3343
3393
  assert(duration >= 2 and duration <= 5);
3344
- options.chance.duration(.binding, player, player.foe(), duration);
3394
+ options.chance.duration(player, duration);
3345
3395
  return duration + 1;
3346
3396
  }
3347
3397
 
3348
3398
  // TODO: consider sharing implementation with bindingDuration
3349
3399
  fn encoreDuration(battle: anytype, player: Player, options: anytype) u3 {
3350
- const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
3400
+ const duration: u3 = (if (options.calc.overridden(player, 0, .duration)) |val|
3351
3401
  @intCast(val)
3352
3402
  else if (showdown)
3353
3403
  battle.rng.range(u3, 2, 6)
@@ -3355,7 +3405,7 @@ pub const Rolls = struct {
3355
3405
  @intCast(battle.rng.next() & 3 + 2));
3356
3406
 
3357
3407
  assert(duration >= 2 and duration <= 5);
3358
- options.chance.duration(.encore, player, player.foe(), duration);
3408
+ options.chance.duration(player, duration);
3359
3409
  return duration + 1;
3360
3410
  }
3361
3411
  };