@pkmn/engine 0.1.0-dev.3a62fd35 → 0.1.0-dev.3ed876db

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 (76) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +15 -11
  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.d.ts +25 -4
  16. package/build/pkg/data.js.map +1 -1
  17. package/build/pkg/gen1.js +1 -1
  18. package/build/pkg/gen1.js.map +1 -1
  19. package/build/pkg/index.d.ts +23 -9
  20. package/build/pkg/index.js +17 -11
  21. package/build/pkg/index.js.map +1 -1
  22. package/build/pkg/protocol.d.ts +20 -1
  23. package/build/pkg/protocol.js +109 -107
  24. package/build/pkg/protocol.js.map +1 -1
  25. package/build/tools/debug.js +36 -70
  26. package/build/tools/debug.js.map +1 -1
  27. package/build/tools/display/debug.jsx +141 -0
  28. package/build/tools/display/debug.jsx.map +1 -0
  29. package/build/tools/display/index.js +141 -0
  30. package/build/tools/display/index.js.map +1 -0
  31. package/build/tools/display/ui.jsx +349 -0
  32. package/build/tools/display/ui.jsx.map +1 -0
  33. package/build/tools/display/util.js +18 -0
  34. package/build/tools/display/util.js.map +1 -0
  35. package/build.zig +237 -83
  36. package/package.json +53 -26
  37. package/src/bin/install-pkmn-engine +17 -7
  38. package/src/bin/pkmn-debug +1 -1
  39. package/src/lib/common/array.zig +77 -0
  40. package/src/lib/common/data.zig +8 -8
  41. package/src/lib/common/debug.zig +39 -8
  42. package/src/lib/common/js.zig +4 -2
  43. package/src/lib/common/optional.zig +13 -9
  44. package/src/lib/common/options.zig +6 -0
  45. package/src/lib/common/protocol.zig +55 -52
  46. package/src/lib/common/rational.zig +121 -29
  47. package/src/lib/common/rng.zig +11 -13
  48. package/src/lib/common/util.zig +50 -1
  49. package/src/lib/gen1/calc.zig +683 -156
  50. package/src/lib/gen1/chance.zig +415 -290
  51. package/src/lib/gen1/data/moves.zig +2 -4
  52. package/src/lib/gen1/data/species.zig +2 -4
  53. package/src/lib/gen1/data/types.zig +1 -2
  54. package/src/lib/gen1/data.zig +38 -41
  55. package/src/lib/gen1/helpers.zig +92 -548
  56. package/src/lib/gen1/mechanics.zig +354 -228
  57. package/src/lib/gen2/calc.zig +37 -27
  58. package/src/lib/gen2/chance.zig +90 -502
  59. package/src/lib/gen2/data/items.zig +1 -3
  60. package/src/lib/gen2/data/moves.zig +2 -4
  61. package/src/lib/gen2/data/species.zig +1 -3
  62. package/src/lib/gen2/data/types.zig +1 -2
  63. package/src/lib/gen2/data.zig +26 -29
  64. package/src/lib/gen2/generated.zig +18 -23
  65. package/src/lib/gen2/helpers.zig +46 -25
  66. package/src/lib/gen2/mechanics.zig +180 -133
  67. package/src/lib/node.zig +2 -4
  68. package/src/lib/pkmn.zig +14 -16
  69. package/src/lib/wasm.zig +1 -2
  70. package/src/tools/display/dom.ts +96 -0
  71. package/src/tools/display/ui.css +354 -0
  72. package/build/test/display.js +0 -400
  73. package/build/test/display.js.map +0 -1
  74. package/src/lib/gen1/README.md +0 -894
  75. package/src/lib/gen2/README.md +0 -329
  76. 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
@@ -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);
@@ -442,18 +428,9 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
442
428
  if (Status.is(stored.status, .SLP)) slp: {
443
429
  const before = stored.status;
444
430
  // 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
431
  stored.status -= 1;
449
- // }
450
-
451
- const duration = Status.duration(stored.status);
452
- if (!Status.is(stored.status, .EXT)) {
453
- try options.chance.sleep(player, duration);
454
- }
455
432
 
456
- if (duration == 0) {
433
+ if (Status.duration(stored.status) == 0) {
457
434
  try log.curestatus(.{ ident, before, .Message });
458
435
  stored.status = 0; // clears EXT if present
459
436
  resetCant(volatiles);
@@ -483,12 +460,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
483
460
  }
484
461
 
485
462
  if (volatiles.disable.duration > 0) {
486
- // if (options.calc.modify(player, .disable)) |extend| {
487
- // if (!extend) volatiles.disable.duration = 0;
488
- // } else {
489
463
  volatiles.disable.duration -= 1;
490
- // }
491
- try options.chance.disable(player, volatiles.disable.duration);
492
464
 
493
465
  if (volatiles.disable.duration == 0) {
494
466
  volatiles.disable.move = 0;
@@ -498,13 +470,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
498
470
 
499
471
  if (volatiles.Confusion) {
500
472
  assert(volatiles.confusion > 0);
501
-
502
- // if (options.calc.modify(player, .confusion)) |extend| {
503
- // if (!extend) volatiles.confusion = 0;
504
- // } else {
505
473
  volatiles.confusion -= 1;
506
- // }
507
- try options.chance.confusion(player, volatiles.confusion);
508
474
 
509
475
  if (volatiles.confusion == 0) {
510
476
  volatiles.Confusion = false;
@@ -665,7 +631,7 @@ pub fn checkCriticalHit(battle: anytype, player: Player, state: *State, options:
665
631
  }
666
632
  assert(stage <= 4);
667
633
 
668
- 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);
669
635
  }
670
636
 
671
637
  pub fn calcDamage(
@@ -733,7 +699,7 @@ pub fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype)
733
699
 
734
700
  pub fn randomizeDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
735
701
  if (state.damage <= 1) return;
736
- const random = try Rolls.damage(battle, player, options);
702
+ const random = try Rolls.damage(battle, player, state.hit, options);
737
703
  options.calc.base(player.foe(), state.damage);
738
704
  state.damage = @intCast(@as(u32, state.damage) *% random / 255);
739
705
  options.calc.final(player.foe(), state.damage);
@@ -1226,7 +1192,7 @@ fn updateSideCondition(
1226
1192
  fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result {
1227
1193
  battle.turn += 1;
1228
1194
 
1229
- if (showdown and battle.turn >= 1000) {
1195
+ if (pkmn.options.mod and battle.turn >= 1000) {
1230
1196
  try options.log.tie(.{});
1231
1197
  return Result.Tie;
1232
1198
  } else if (battle.turn >= 65535) {
@@ -1304,14 +1270,32 @@ pub const Effects = struct {
1304
1270
  }
1305
1271
 
1306
1272
  pub fn bellyDrum(battle: anytype, player: Player, state: *State, options: anytype) !void {
1307
- _ = .{ battle, player, state, options }; // TODO
1308
- // assert(!state.miss);
1309
- // var side = battle.side(player);
1310
- // try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
1311
- // TODO
1312
- // |-boost|p1a: Poliwag|atk|2|[silent]
1313
- // |-fail|p1a: Poliwag
1314
- //
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
+ }
1315
1299
  }
1316
1300
 
1317
1301
  pub const bide = struct {
@@ -1323,13 +1307,7 @@ pub const Effects = struct {
1323
1307
 
1324
1308
  if (!volatiles.Bide) return;
1325
1309
 
1326
- // if (options.calc.modify(player, .attacking)) |extend| {
1327
- // if (!extend) volatiles.attacks = 0;
1328
- // } else {
1329
1310
  volatiles.attacks -= 1;
1330
- // }
1331
- try options.chance.attacking(player, volatiles.attacks);
1332
-
1333
1311
  if (volatiles.attacks != 0) return options.log.activate(.{ ident, .Bide });
1334
1312
 
1335
1313
  volatiles.Bide = false;
@@ -1596,8 +1574,16 @@ pub const Effects = struct {
1596
1574
  try options.log.start(.{ foe_ident, .Encore, move.id });
1597
1575
  }
1598
1576
 
1599
- pub fn explode(battle: anytype, player: Player, state: *State, options: anytype) !void {
1600
- _ = .{ 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;
1601
1587
  }
1602
1588
 
1603
1589
  pub fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
@@ -1712,7 +1698,7 @@ pub const Effects = struct {
1712
1698
  if (!state.proc) return;
1713
1699
  if (foe.conditions.Safeguard) return;
1714
1700
  // Freeze Clause Mod
1715
- 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;
1716
1702
 
1717
1703
  foe_stored.status = Status.init(.FRZ);
1718
1704
  try options.log.status(.{ foe_ident, foe_stored.status, .None });
@@ -1723,8 +1709,26 @@ pub const Effects = struct {
1723
1709
  foe.active.volatiles.frozen = true;
1724
1710
  }
1725
1711
 
1726
- pub fn furyCutter(battle: anytype, player: Player, state: *State, options: anytype) !void {
1727
- _ = .{ 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
+ }
1728
1732
  }
1729
1733
 
1730
1734
  pub fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -1885,7 +1889,29 @@ pub const Effects = struct {
1885
1889
  }
1886
1890
 
1887
1891
  pub fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
1888
- _ = .{ 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 });
1889
1915
  }
1890
1916
 
1891
1917
  pub fn mirrorMove(
@@ -2350,7 +2376,7 @@ pub const Effects = struct {
2350
2376
  }
2351
2377
 
2352
2378
  // Sleep Clause Mod
2353
- if (showdown) {
2379
+ if (pkmn.options.mod) {
2354
2380
  for (foe.pokemon) |p| {
2355
2381
  if (Status.is(p.status, .SLP) and !Status.is(p.status, .EXT)) return;
2356
2382
  }
@@ -2393,17 +2419,37 @@ pub const Effects = struct {
2393
2419
  }
2394
2420
 
2395
2421
  pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
2396
- _ = .{ battle, player, state, options }; // TODO
2397
- // var foe = battle.foe(player);
2422
+ var foe = battle.foe(player);
2398
2423
 
2399
- // 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
+ }
2400
2428
 
2401
- // const last = foe.lastMove(false);
2402
- // if (showdown) {
2403
- // const roll = try Rolls.spite(battle, player, options);
2404
- // } else {
2405
- // if (last != .None) {}
2406
- // }
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 });
2407
2453
  }
2408
2454
 
2409
2455
  pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
@@ -2790,7 +2836,7 @@ fn convertible(active: ActivePokemon, m: Move) bool {
2790
2836
 
2791
2837
  pub const Rolls = struct {
2792
2838
  fn speedTie(battle: anytype, options: anytype) !bool {
2793
- const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
2839
+ const p1 = if (options.calc.overridden(.P1, 0, .speed_tie)) |player|
2794
2840
  player == .P1
2795
2841
  else if (showdown)
2796
2842
  battle.rng.range(u8, 0, 2) == 0
@@ -2802,7 +2848,7 @@ pub const Rolls = struct {
2802
2848
  }
2803
2849
 
2804
2850
  fn quickClaw(battle: anytype, player: Player, options: anytype) !bool {
2805
- const qkc = if (options.calc.overridden(player, .quick_claw)) |val|
2851
+ const qkc = if (options.calc.overridden(player, 0, .quick_claw)) |val|
2806
2852
  val == .true
2807
2853
  else if (showdown)
2808
2854
  battle.rng.chance(u8, 60, 256)
@@ -2814,7 +2860,7 @@ pub const Rolls = struct {
2814
2860
  }
2815
2861
 
2816
2862
  fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) !bool {
2817
- const ok = if (options.calc.overridden(player, .hit)) |val|
2863
+ const ok = if (options.calc.overridden(player, 0, .hit)) |val|
2818
2864
  val == .true
2819
2865
  else if (showdown)
2820
2866
  battle.rng.chance(u8, accuracy, 256)
@@ -2825,20 +2871,22 @@ pub const Rolls = struct {
2825
2871
  return ok;
2826
2872
  }
2827
2873
 
2828
- fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
2829
- 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|
2830
2877
  val == .true
2831
2878
  else if (showdown)
2832
2879
  battle.rng.chance(u8, rate, 256)
2833
2880
  else
2834
2881
  battle.rng.next() < rate;
2835
2882
 
2836
- try options.chance.criticalHit(player, crit, rate);
2883
+ try options.chance.criticalHit(player, i, crit, rate);
2837
2884
  return crit;
2838
2885
  }
2839
2886
 
2840
- fn damage(battle: anytype, player: Player, options: anytype) !u8 {
2841
- 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|
2842
2890
  val
2843
2891
  else roll: {
2844
2892
  if (showdown) break :roll battle.rng.range(u8, 217, 256);
@@ -2849,12 +2897,12 @@ pub const Rolls = struct {
2849
2897
  };
2850
2898
 
2851
2899
  assert(roll >= 217 and roll <= 255);
2852
- try options.chance.damage(player, roll);
2900
+ try options.chance.damage(player, i, roll);
2853
2901
  return roll;
2854
2902
  }
2855
2903
 
2856
2904
  fn confused(battle: anytype, player: Player, options: anytype) !bool {
2857
- const cfz = if (options.calc.overridden(player, .confused)) |val|
2905
+ const cfz = if (options.calc.overridden(player, 0, .confused)) |val|
2858
2906
  val == .true
2859
2907
  else if (showdown)
2860
2908
  !battle.rng.chance(u8, 128, 256)
@@ -2866,7 +2914,7 @@ pub const Rolls = struct {
2866
2914
  }
2867
2915
 
2868
2916
  fn attract(battle: anytype, player: Player, options: anytype) !bool {
2869
- const cant = if (options.calc.overridden(player, .attract)) |val|
2917
+ const cant = if (options.calc.overridden(player, 0, .attract)) |val|
2870
2918
  val == .true
2871
2919
  else if (showdown)
2872
2920
  battle.rng.chance(u8, 1, 2)
@@ -2878,7 +2926,7 @@ pub const Rolls = struct {
2878
2926
  }
2879
2927
 
2880
2928
  fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
2881
- const par = if (options.calc.overridden(player, .paralyzed)) |val|
2929
+ const par = if (options.calc.overridden(player, 0, .paralyzed)) |val|
2882
2930
  val == .true
2883
2931
  else if (showdown)
2884
2932
  battle.rng.chance(u8, 63, 256)
@@ -2890,7 +2938,7 @@ pub const Rolls = struct {
2890
2938
  }
2891
2939
 
2892
2940
  fn defrost(battle: anytype, player: Player, options: anytype) !bool {
2893
- const thaw = if (options.calc.overridden(player, .defrost)) |val|
2941
+ const thaw = if (options.calc.overridden(player, 0, .defrost)) |val|
2894
2942
  val == .true
2895
2943
  else if (showdown)
2896
2944
  battle.rng.chance(u8, 25, 256)
@@ -2903,7 +2951,7 @@ pub const Rolls = struct {
2903
2951
 
2904
2952
  fn secondaryChance(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
2905
2953
  // GLITCH: 100% secondary chance will still fail 1/256 of the time
2906
- const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
2954
+ const proc = if (options.calc.overridden(player, 0, .secondary_chance)) |val|
2907
2955
  val == .true
2908
2956
  else if (showdown)
2909
2957
  battle.rng.chance(u8, rate, 256)
@@ -2915,7 +2963,7 @@ pub const Rolls = struct {
2915
2963
  }
2916
2964
 
2917
2965
  fn item(battle: anytype, player: Player, options: anytype) !bool {
2918
- const proc = if (options.calc.overridden(player, .item)) |val|
2966
+ const proc = if (options.calc.overridden(player, 0, .item)) |val|
2919
2967
  val == .true
2920
2968
  else if (showdown)
2921
2969
  battle.rng.chance(u8, 30, 256)
@@ -2927,7 +2975,7 @@ pub const Rolls = struct {
2927
2975
  }
2928
2976
 
2929
2977
  fn triAttack(battle: anytype, player: Player, options: anytype) !TriAttack {
2930
- const status: TriAttack = if (options.calc.overridden(player, .tri_attack)) |val|
2978
+ const status: TriAttack = if (options.calc.overridden(player, 0, .tri_attack)) |val|
2931
2979
  @enumFromInt(@intFromEnum(val) - 1)
2932
2980
  else if (showdown)
2933
2981
  @enumFromInt(battle.rng.range(u2, 0, 2))
@@ -2943,7 +2991,7 @@ pub const Rolls = struct {
2943
2991
  }
2944
2992
 
2945
2993
  fn present(battle: anytype, player: Player, options: anytype) !u8 {
2946
- const power = if (options.calc.overridden(player, .present)) |val|
2994
+ const power = if (options.calc.overridden(player, 0, .present)) |val|
2947
2995
  (val - 1) * 40
2948
2996
  else if (showdown) power: {
2949
2997
  const r = battle.rng.range(u8, 0, 10);
@@ -2965,7 +3013,7 @@ pub const Rolls = struct {
2965
3013
  }
2966
3014
 
2967
3015
  fn magnitude(battle: anytype, player: Player, options: anytype) !u8 {
2968
- const num: u8 = if (options.calc.overridden(player, .magnitude)) |val|
3016
+ const num: u8 = if (options.calc.overridden(player, 0, .magnitude)) |val|
2969
3017
  val + 3
2970
3018
  else if (showdown) num: {
2971
3019
  const r = battle.rng.range(u8, 0, 100);
@@ -2993,7 +3041,7 @@ pub const Rolls = struct {
2993
3041
  }
2994
3042
 
2995
3043
  fn tripleKick(battle: anytype, player: Player, options: anytype) !u2 {
2996
- const hits = if (options.calc.overridden(player, .triple_kick)) |val|
3044
+ const hits = if (options.calc.overridden(player, 0, .triple_kick)) |val|
2997
3045
  val
2998
3046
  else if (showdown)
2999
3047
  battle.rng.range(u2, 1, 4)
@@ -3009,13 +3057,12 @@ pub const Rolls = struct {
3009
3057
  return hits;
3010
3058
  }
3011
3059
 
3012
- fn spite(battle: anytype, player: Player, options: anytype) !u8 {
3013
- 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|
3014
3063
  val + 1
3015
- else if (showdown)
3016
- battle.rng.range(u8, 2, 6)
3017
3064
  else
3018
- (battle.rng.next() & 3) + 2;
3065
+ @intCast((battle.rng.next() & 3) + 2);
3019
3066
 
3020
3067
  assert(pp >= 2 and pp <= 5);
3021
3068
  try options.chance.spite(player, pp);
@@ -3029,7 +3076,7 @@ pub const Rolls = struct {
3029
3076
  var i: u8 = 0;
3030
3077
  const neutral = @intFromEnum(Effectiveness.Neutral);
3031
3078
  assert(mtype != .@"???");
3032
- const ty: Type = if (options.calc.overridden(player, .conversion_2)) |val|
3079
+ const ty: Type = if (options.calc.overridden(player, 0, .conversion_2)) |val|
3033
3080
  @enumFromInt(@intFromEnum(val) - 1)
3034
3081
  else if (showdown) ty: {
3035
3082
  for (Type.SHOWDOWN) |t| {
@@ -3063,7 +3110,7 @@ pub const Rolls = struct {
3063
3110
  assert(count <= 8);
3064
3111
  const num = if (count == 8) 0 else @as(u8, 255) >> @intCast(count);
3065
3112
 
3066
- const ok = if (options.calc.overridden(player, .protect)) |val|
3113
+ const ok = if (options.calc.overridden(player, 0, .protect)) |val|
3067
3114
  val == .true
3068
3115
  else if (showdown)
3069
3116
  battle.rng.chance(u8, num, 255)
@@ -3085,7 +3132,7 @@ pub const Rolls = struct {
3085
3132
  const moves = active.moves;
3086
3133
 
3087
3134
  // TODO: consider throwing error instead of rerolling?
3088
- const overridden = if (options.calc.overridden(player, .move_slot)) |val|
3135
+ const overridden = if (options.calc.overridden(player, 0, .move_slot)) |val|
3089
3136
  if (convertible(active, moves[val - 1].id)) val else null
3090
3137
  else
3091
3138
  null;
@@ -3122,7 +3169,7 @@ pub const Rolls = struct {
3122
3169
  const moves = active.moves;
3123
3170
 
3124
3171
  // TODO: consider throwing error instead of rerolling?
3125
- const overridden = if (options.calc.overridden(player, .move_slot)) |val|
3172
+ const overridden = if (options.calc.overridden(player, 0, .move_slot)) |val|
3126
3173
  if (val != mslot and Move.get(moves[val - 1].id).extra.sleep_talk) val else null
3127
3174
  else
3128
3175
  null;
@@ -3166,7 +3213,7 @@ pub const Rolls = struct {
3166
3213
  assert(n > 0 and n <= 6);
3167
3214
 
3168
3215
  // TODO: consider throwing error instead of rerolling?
3169
- const overridden = if (options.calc.overridden(player, .force_switch)) |val| val: {
3216
+ const overridden = if (options.calc.overridden(player, 0, .force_switch)) |val| val: {
3170
3217
  for (slots) |slot| if (slot == val) break :val if (foe.get(slot).hp > 0) val else null;
3171
3218
  break :val null;
3172
3219
  } else null;
@@ -3192,7 +3239,7 @@ pub const Rolls = struct {
3192
3239
  const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
3193
3240
 
3194
3241
  fn multiHit(battle: anytype, player: Player, options: anytype) !u3 {
3195
- const n: u3 = if (options.calc.overridden(player, .multi_hit)) |val|
3242
+ const n: u3 = if (options.calc.overridden(player, 0, .multi_hit)) |val|
3196
3243
  @intCast(val)
3197
3244
  else if (showdown)
3198
3245
  DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
@@ -3207,7 +3254,7 @@ pub const Rolls = struct {
3207
3254
  }
3208
3255
 
3209
3256
  fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
3210
- const power = if (options.calc.overridden(player, .psywave)) |val|
3257
+ const power = if (options.calc.overridden(player, 0, .psywave)) |val|
3211
3258
  val
3212
3259
  else if (showdown)
3213
3260
  battle.rng.range(u8, 1, max)
@@ -3233,7 +3280,7 @@ pub const Rolls = struct {
3233
3280
  }
3234
3281
 
3235
3282
  // TODO: consider throwing error instead of rerolling?
3236
- const overridden = if (options.calc.overridden(player, .metronome)) |val| val: {
3283
+ const overridden = if (options.calc.overridden(player, 0, .metronome)) |val| val: {
3237
3284
  for (moves) |m| if (m.id == val) break :val null;
3238
3285
  break :val val;
3239
3286
  } else null;
@@ -3265,7 +3312,7 @@ pub const Rolls = struct {
3265
3312
  }
3266
3313
 
3267
3314
  fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
3268
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
3315
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3269
3316
  @intCast(val)
3270
3317
  else if (showdown)
3271
3318
  battle.rng.range(u3, 1, 7)
@@ -3277,12 +3324,12 @@ pub const Rolls = struct {
3277
3324
  };
3278
3325
 
3279
3326
  assert(duration >= 1 and duration <= 6);
3280
- options.chance.duration(.sleep, player, player.foe(), duration);
3327
+ options.chance.duration(player, duration);
3281
3328
  return duration + 1;
3282
3329
  }
3283
3330
 
3284
3331
  fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
3285
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
3332
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3286
3333
  @intCast(val)
3287
3334
  else if (showdown)
3288
3335
  // Pokémon Showdown incorrectly uses the same duration for self-confusion
@@ -3291,16 +3338,16 @@ pub const Rolls = struct {
3291
3338
  @intCast((battle.rng.next() & if (self) 1 else 3) + 2);
3292
3339
 
3293
3340
  assert(duration >= 2 and duration <= if (!showdown and self) 3 else 5);
3294
- options.chance.duration(.confusion, player, if (self) player else player.foe(), duration);
3341
+ options.chance.duration(player, duration);
3295
3342
  return duration;
3296
3343
  }
3297
3344
 
3298
3345
  fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
3299
- const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
3346
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3300
3347
  @intCast(val)
3301
3348
  else if (showdown)
3302
3349
  // Pokémon Showdown incorrectly inherits Generation III's Disable duration
3303
- battle.rng.range(u4, 2, 6)
3350
+ battle.rng.range(u3, 2, 6)
3304
3351
  else duration: {
3305
3352
  while (true) {
3306
3353
  const r = battle.rng.next() & 7;
@@ -3309,12 +3356,12 @@ pub const Rolls = struct {
3309
3356
  };
3310
3357
 
3311
3358
  assert(duration >= 2 and duration <= (if (showdown) 5 else 7));
3312
- options.chance.duration(.disable, player, player.foe(), duration);
3359
+ options.chance.duration(player, duration);
3313
3360
  return duration;
3314
3361
  }
3315
3362
 
3316
3363
  fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
3317
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
3364
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3318
3365
  @intCast(val)
3319
3366
  else if (showdown)
3320
3367
  battle.rng.range(u3, 2, 4)
@@ -3322,12 +3369,12 @@ pub const Rolls = struct {
3322
3369
  @intCast((battle.rng.next() & 1) + 2); // BUG: thrash is + 1 not +2
3323
3370
 
3324
3371
  assert(duration >= 2 and duration <= 3);
3325
- options.chance.duration(.attacking, player, player, duration);
3372
+ options.chance.duration(player, duration);
3326
3373
  return duration;
3327
3374
  }
3328
3375
 
3329
3376
  fn bindingDuration(battle: anytype, player: Player, options: anytype) u3 {
3330
- const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
3377
+ const duration: u3 = (if (options.calc.overridden(player, 0, .duration)) |val|
3331
3378
  @intCast(val)
3332
3379
  else if (showdown)
3333
3380
  // TODO: PS does range(3, 6) here which after subtracting 1 is actually 2-4 not 2-5!
@@ -3336,13 +3383,13 @@ pub const Rolls = struct {
3336
3383
  @intCast(battle.rng.next() & 3 + 2));
3337
3384
 
3338
3385
  assert(duration >= 2 and duration <= 5);
3339
- options.chance.duration(.binding, player, player.foe(), duration);
3386
+ options.chance.duration(player, duration);
3340
3387
  return duration + 1;
3341
3388
  }
3342
3389
 
3343
3390
  // TODO: consider sharing implementation with bindingDuration
3344
3391
  fn encoreDuration(battle: anytype, player: Player, options: anytype) u3 {
3345
- const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
3392
+ const duration: u3 = (if (options.calc.overridden(player, 0, .duration)) |val|
3346
3393
  @intCast(val)
3347
3394
  else if (showdown)
3348
3395
  battle.rng.range(u3, 2, 6)
@@ -3350,7 +3397,7 @@ pub const Rolls = struct {
3350
3397
  @intCast(battle.rng.next() & 3 + 2));
3351
3398
 
3352
3399
  assert(duration >= 2 and duration <= 5);
3353
- options.chance.duration(.encore, player, player.foe(), duration);
3400
+ options.chance.duration(player, duration);
3354
3401
  return duration + 1;
3355
3402
  }
3356
3403
  };