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

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 (46) 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 -4
  6. package/build/pkg/data.js.map +1 -1
  7. package/build/pkg/gen1.js.map +1 -1
  8. package/build/pkg/index.d.ts +3 -2
  9. package/build/pkg/index.js.map +1 -1
  10. package/build/pkg/protocol.js +4 -4
  11. package/build/pkg/protocol.js.map +1 -1
  12. package/build/test/display.js +8 -3
  13. package/build/test/display.js.map +1 -1
  14. package/build/tools/debug.js +2 -3
  15. package/build/tools/debug.js.map +1 -1
  16. package/build.zig +208 -81
  17. package/package.json +32 -21
  18. package/src/bin/install-pkmn-engine +25 -17
  19. package/src/lib/common/array.zig +77 -0
  20. package/src/lib/common/data.zig +4 -4
  21. package/src/lib/common/debug.zig +2 -2
  22. package/src/lib/common/js.zig +4 -2
  23. package/src/lib/common/optional.zig +14 -10
  24. package/src/lib/common/options.zig +2 -0
  25. package/src/lib/common/protocol.zig +73 -73
  26. package/src/lib/common/rational.zig +121 -27
  27. package/src/lib/common/rng.zig +7 -7
  28. package/src/lib/common/util.zig +50 -1
  29. package/src/lib/gen1/calc.zig +511 -131
  30. package/src/lib/gen1/chance.zig +374 -267
  31. package/src/lib/gen1/data/species.zig +1 -1
  32. package/src/lib/gen1/data.zig +15 -14
  33. package/src/lib/gen1/helpers.zig +96 -536
  34. package/src/lib/gen1/mechanics.zig +285 -189
  35. package/src/lib/gen2/calc.zig +34 -16
  36. package/src/lib/gen2/chance.zig +85 -492
  37. package/src/lib/gen2/data.zig +8 -7
  38. package/src/lib/gen2/generated.zig +1 -3
  39. package/src/lib/gen2/helpers.zig +41 -9
  40. package/src/lib/gen2/mechanics.zig +178 -121
  41. package/src/lib/{binding/node.zig → node.zig} +9 -12
  42. package/src/lib/pkmn.zig +32 -15
  43. package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
  44. package/src/test/display/pkmn.html.tmpl +5 -5
  45. package/src/lib/gen1/README.md +0 -894
  46. package/src/lib/gen2/README.md +0 -329
@@ -106,6 +106,7 @@ pub const State = struct {
106
106
  type: Type, // BATTLE_VARS_MOVE_TYPE
107
107
  accuracy: u8, // wEnemyMoveStruct + MOVE_ACC
108
108
  move: Move, // wCurPlayerMove
109
+ hit: u3 = 0,
109
110
  from: Move = .None,
110
111
  mslot: u4, // wCurMoveNum
111
112
  first: bool, // wEnemyGoesFirst
@@ -140,11 +141,11 @@ fn start(battle: anytype, options: anytype) !Result {
140
141
  const p1 = battle.side(.P1);
141
142
  const p2 = battle.side(.P2);
142
143
 
143
- var p1_slot = findFirstAlive(p1);
144
+ const p1_slot = findFirstAlive(p1);
144
145
  assert(!showdown or p1_slot == 1);
145
146
  if (p1_slot == 0) return if (findFirstAlive(p2) == 0) Result.Tie else Result.Lose;
146
147
 
147
- var p2_slot = findFirstAlive(p2);
148
+ const p2_slot = findFirstAlive(p2);
148
149
  assert(!showdown or p2_slot == 1);
149
150
  if (p2_slot == 0) return Result.Win;
150
151
 
@@ -256,7 +257,6 @@ fn switchIn(battle: anytype, player: Player, slot: u8, kind: SwitchIn, options:
256
257
  } else {
257
258
  try options.log.switched(.{ battle.active(player), incoming });
258
259
  }
259
- options.chance.switched(player, side.pokemon[0].position, out);
260
260
 
261
261
  if (side.conditions.Spikes and !active.types.includes(.Flying)) {
262
262
  incoming.hp -|= @max(incoming.stats.hp / 8, 1);
@@ -403,7 +403,9 @@ fn executeMove(
403
403
 
404
404
  assert(choice.type == .Move);
405
405
  var mslot: u4 = @intCast(choice.data);
406
+ _ = &mslot;
406
407
  var move = Move.Pound; // TODO
408
+ _ = &move;
407
409
 
408
410
  if (!try beforeMove(battle, player, move, options)) return null;
409
411
 
@@ -440,18 +442,9 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
440
442
  if (Status.is(stored.status, .SLP)) slp: {
441
443
  const before = stored.status;
442
444
  // 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
445
  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
446
 
454
- if (duration == 0) {
447
+ if (Status.duration(stored.status) == 0) {
455
448
  try log.curestatus(.{ ident, before, .Message });
456
449
  stored.status = 0; // clears EXT if present
457
450
  resetCant(volatiles);
@@ -481,12 +474,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
481
474
  }
482
475
 
483
476
  if (volatiles.disable.duration > 0) {
484
- // if (options.calc.modify(player, .disable)) |extend| {
485
- // if (!extend) volatiles.disable.duration = 0;
486
- // } else {
487
477
  volatiles.disable.duration -= 1;
488
- // }
489
- try options.chance.disable(player, volatiles.disable.duration);
490
478
 
491
479
  if (volatiles.disable.duration == 0) {
492
480
  volatiles.disable.move = 0;
@@ -496,13 +484,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
496
484
 
497
485
  if (volatiles.Confusion) {
498
486
  assert(volatiles.confusion > 0);
499
-
500
- // if (options.calc.modify(player, .confusion)) |extend| {
501
- // if (!extend) volatiles.confusion = 0;
502
- // } else {
503
487
  volatiles.confusion -= 1;
504
- // }
505
- try options.chance.confusion(player, volatiles.confusion);
506
488
 
507
489
  if (volatiles.confusion == 0) {
508
490
  volatiles.Confusion = false;
@@ -565,7 +547,7 @@ fn resetCant(volatiles: *align(8) Volatiles) void {
565
547
 
566
548
  pub fn canMove(battle: anytype, player: Player, state: *State, options: anytype) !bool {
567
549
  var side = battle.side(player);
568
- var volatiles = &side.active.volatiles;
550
+ const volatiles = &side.active.volatiles;
569
551
 
570
552
  // FIXME: pass in target to avoid lookup?
571
553
  const target = if (Move.get(state.move).target == .Self) player else player.foe();
@@ -663,7 +645,7 @@ pub fn checkCriticalHit(battle: anytype, player: Player, state: *State, options:
663
645
  }
664
646
  assert(stage <= 4);
665
647
 
666
- state.crit = try Rolls.criticalHit(battle, player, CRITICAL_HITS[stage], options);
648
+ state.crit = try Rolls.criticalHit(battle, player, state.hit, CRITICAL_HITS[stage], options);
667
649
  }
668
650
 
669
651
  pub fn calcDamage(
@@ -731,7 +713,7 @@ pub fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype)
731
713
 
732
714
  pub fn randomizeDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
733
715
  if (state.damage <= 1) return;
734
- const random = try Rolls.damage(battle, player, options);
716
+ const random = try Rolls.damage(battle, player, state.hit, options);
735
717
  options.calc.base(player.foe(), state.damage);
736
718
  state.damage = @intCast(@as(u32, state.damage) *% random / 255);
737
719
  options.calc.final(player.foe(), state.damage);
@@ -997,7 +979,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
997
979
 
998
980
  // Weather
999
981
  if (battle.field.weather != .None) {
1000
- assert(battle.field.weather_duration > 0);
1001
982
  battle.field.weather_duration -= 1;
1002
983
 
1003
984
  if (battle.field.weather_duration == 0) {
@@ -1056,7 +1037,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1056
1037
  var volatiles = &side.active.volatiles;
1057
1038
 
1058
1039
  if (volatiles.PerishSong) {
1059
- assert(volatiles.perish_song > 0);
1060
1040
  volatiles.perish_song -= 1;
1061
1041
 
1062
1042
  assert(volatiles.perish_song < 3);
@@ -1086,7 +1066,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1086
1066
 
1087
1067
  // Mystery Berry
1088
1068
  inline for (players) |player| {
1089
- var stored = battle.side(player).stored();
1069
+ const stored = battle.side(player).stored();
1090
1070
 
1091
1071
  if (stored.item == .MysteryBerry) {
1092
1072
  // TODO stored and active moves, transform, etc
@@ -1193,7 +1173,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1193
1173
  var volatiles = &side.active.volatiles;
1194
1174
 
1195
1175
  if (volatiles.Encore) {
1196
- assert(volatiles.encore > 0);
1197
1176
  volatiles.encore -= 1;
1198
1177
 
1199
1178
  if (volatiles.encore == 0 or side.active.move(mslot).pp == 0) {
@@ -1215,7 +1194,6 @@ fn updateSideCondition(
1215
1194
  ) !void {
1216
1195
  var side = battle.side(player);
1217
1196
  if (@field(side.conditions, key)) {
1218
- assert(@field(side.conditions, value) > 0);
1219
1197
  @field(side.conditions, value) -= 1;
1220
1198
 
1221
1199
  if (@field(side.conditions, value) == 0) {
@@ -1306,14 +1284,32 @@ pub const Effects = struct {
1306
1284
  }
1307
1285
 
1308
1286
  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
- //
1287
+ assert(!state.miss);
1288
+ var side = battle.side(player);
1289
+
1290
+ const ident = battle.active(player);
1291
+
1292
+ assert(side.active.boosts.atk >= -6 and side.active.boosts.atk <= 6);
1293
+ if (side.active.boosts.atk == 6 or side.active.boosts.atk == MAX_STAT_VALUE) {
1294
+ return options.log.fail(.{ ident, .None });
1295
+ }
1296
+
1297
+ const hp = @max(side.stored().stats.hp / 2, 1);
1298
+ if (side.stored().hp <= hp) {
1299
+ try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
1300
+ try options.log.boost(.{ ident, .Attack, @as(i8, 2), .Silent });
1301
+ return options.log.fail(.{ ident, .None });
1302
+ } else {
1303
+ side.stored().hp -= hp;
1304
+ try options.log.damage(.{ ident, side.stored(), .None });
1305
+
1306
+ const before = side.active.boosts.atk;
1307
+ var i: usize = 0;
1308
+ while (i < 6 and side.active.stats.atk < MAX_STAT_VALUE) : (i += 1) {
1309
+ try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
1310
+ }
1311
+ try options.log.boost(.{ ident, .Attack, @as(i8, side.active.boosts.atk - before) });
1312
+ }
1317
1313
  }
1318
1314
 
1319
1315
  pub const bide = struct {
@@ -1325,14 +1321,7 @@ pub const Effects = struct {
1325
1321
 
1326
1322
  if (!volatiles.Bide) return;
1327
1323
 
1328
- assert(volatiles.attacks > 0);
1329
- // if (options.calc.modify(player, .attacking)) |extend| {
1330
- // if (!extend) volatiles.attacks = 0;
1331
- // } else {
1332
1324
  volatiles.attacks -= 1;
1333
- // }
1334
- try options.chance.attacking(player, volatiles.attacks);
1335
-
1336
1325
  if (volatiles.attacks != 0) return options.log.activate(.{ ident, .Bide });
1337
1326
 
1338
1327
  volatiles.Bide = false;
@@ -1478,8 +1467,8 @@ pub const Effects = struct {
1478
1467
  side.stored().hp -= @max(side.stored().stats.hp / 2, 1);
1479
1468
  try options.log.damage(.{ ident, side.stored(), .Curse });
1480
1469
  } else {
1481
- var stats = &side.active.stats;
1482
- var boosts = &side.active.boosts;
1470
+ const stats = &side.active.stats;
1471
+ const boosts = &side.active.boosts;
1483
1472
 
1484
1473
  const max_atk = boosts.atk == 6 or stats.atk == MAX_STAT_VALUE;
1485
1474
  const max_def = boosts.def == 6 or stats.def == MAX_STAT_VALUE;
@@ -1599,8 +1588,16 @@ pub const Effects = struct {
1599
1588
  try options.log.start(.{ foe_ident, .Encore, move.id });
1600
1589
  }
1601
1590
 
1602
- pub fn explode(battle: anytype, player: Player, state: *State, options: anytype) !void {
1603
- _ = .{ battle, player, state, options }; // TODO
1591
+ pub fn explode(battle: anytype, player: Player, _: *State, _: anytype) !void {
1592
+ var side = battle.side(player);
1593
+ var stored = side.stored();
1594
+
1595
+ stored.hp = 0;
1596
+ // Pokémon Showdown sets the status to 0 on faint(), and we need to do the same to be able
1597
+ // to correctly implement Pokémon Showdown's dumb speed-based switch mechanics
1598
+ if (!showdown) stored.status = 0;
1599
+ side.active.volatiles.LeechSeed = false;
1600
+ side.active.volatiles.DestinyBond = false;
1604
1601
  }
1605
1602
 
1606
1603
  pub fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
@@ -1726,8 +1723,26 @@ pub const Effects = struct {
1726
1723
  foe.active.volatiles.frozen = true;
1727
1724
  }
1728
1725
 
1729
- pub fn furyCutter(battle: anytype, player: Player, state: *State, options: anytype) !void {
1730
- _ = .{ battle, player, state, options }; // TODO
1726
+ // TODO FIXME showdown calls this before calcDamage (after checkHit) and modifies state.bp!
1727
+ pub fn furyCutter(battle: anytype, player: Player, state: *State, _: anytype) !void {
1728
+ var side = battle.side(player);
1729
+ var volatiles = &side.active.volatiles;
1730
+
1731
+ if (state.miss) {
1732
+ volatiles.fury_cutter = 0;
1733
+ return;
1734
+ }
1735
+
1736
+ assert(volatiles.fury_cutter >= 0 and volatiles.fury_cutter <= 5);
1737
+ if (volatiles.fury_cutter == 5) return;
1738
+ volatiles.fury_cutter += 1;
1739
+
1740
+ if (showdown) {
1741
+ state.bp *= 2;
1742
+ assert(state.bp <= 160);
1743
+ } else {
1744
+ state.damage *|= 2;
1745
+ }
1731
1746
  }
1732
1747
 
1733
1748
  pub fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -1888,7 +1903,29 @@ pub const Effects = struct {
1888
1903
  }
1889
1904
 
1890
1905
  pub fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
1891
- _ = .{ battle, player, state, options }; // TODO
1906
+ var side = battle.side(player);
1907
+
1908
+ if (state.miss) {
1909
+ if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
1910
+ try options.log.lastmiss(.{});
1911
+ return options.log.miss(.{battle.active(player)});
1912
+ }
1913
+
1914
+ const move = battle.foe(player).lastMove(false);
1915
+ if (move == .None or move == .Struggle) {
1916
+ return options.log.fail(.{ battle.active(player.foe()), .None });
1917
+ }
1918
+
1919
+ var slot: u4 = 0;
1920
+ for (side.active.moves, 0..) |m, i| {
1921
+ if (m.id == move) return options.log.fail(.{ battle.active(player.foe()), .None });
1922
+ if (m.id == .Mimic) slot = @intCast(i + 1);
1923
+ }
1924
+ assert(slot != 0 and slot == state.mslot);
1925
+
1926
+ side.active.move(slot).id = move;
1927
+ side.active.move(slot).pp = 5;
1928
+ try options.log.start(.{ battle.active(player), .Mimic, move });
1892
1929
  }
1893
1930
 
1894
1931
  pub fn mirrorMove(
@@ -2185,11 +2222,10 @@ pub const Effects = struct {
2185
2222
  }
2186
2223
 
2187
2224
  pub fn damage(battle: anytype, player: Player, state: *State, _: anytype) !void {
2188
- var side = battle.side(player);
2189
- var volatiles = &side.active.volatiles;
2225
+ const side = battle.side(player);
2190
2226
 
2191
- assert(volatiles.Rage);
2192
- state.damage *|= (volatiles.rage +| 1);
2227
+ assert(side.active.volatiles.Rage);
2228
+ state.damage *|= (side.active.volatiles.rage +| 1);
2193
2229
  }
2194
2230
  };
2195
2231
 
@@ -2397,17 +2433,37 @@ pub const Effects = struct {
2397
2433
  }
2398
2434
 
2399
2435
  pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
2400
- _ = .{ battle, player, state, options }; // TODO
2401
- // var foe = battle.foe(player);
2436
+ var foe = battle.foe(player);
2402
2437
 
2403
- // if (state.miss) return options.log.fail(.{ battle.active(player), .None });
2438
+ const last = foe.lastMove(false);
2439
+ if (state.miss or last == .None or last == .Struggle) {
2440
+ return options.log.fail(.{ battle.active(player.foe()), .None });
2441
+ }
2404
2442
 
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
- // }
2443
+ var slot: u4 = 0;
2444
+ for (foe.active.moves, 0..) |m, i| {
2445
+ if (m.id == last) slot = @intCast(i + 1);
2446
+ }
2447
+ // If the move was called via Metronome it might not exist
2448
+ if (slot == 0 or foe.active.move(slot).pp == 0) {
2449
+ return options.log.fail(.{ battle.active(player.foe()), .None });
2450
+ }
2451
+
2452
+ // Pokémon Showdown always deducts 4 instead of randomizing
2453
+ const pp = @min(
2454
+ foe.active.move(slot).pp,
2455
+ if (showdown) 4 else try Rolls.spite(battle, player, options),
2456
+ );
2457
+ foe.active.move(slot).pp -= pp;
2458
+ if (!foe.active.volatiles.Transform) {
2459
+ var move_slot = foe.stored().move(slot);
2460
+ assert(move_slot.pp > 0);
2461
+
2462
+ move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
2463
+ assert(foe.active.move(slot).pp == foe.stored().move(slot).pp);
2464
+ }
2465
+
2466
+ try options.log.activate(.{ battle.active(player.foe()), .Spite, last, pp });
2411
2467
  }
2412
2468
 
2413
2469
  pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
@@ -2541,8 +2597,8 @@ pub const Effects = struct {
2541
2597
  pub fn allStatUpChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
2542
2598
  var side = battle.side(player);
2543
2599
 
2544
- var stats = &side.active.stats;
2545
- var boosts = &side.active.boosts;
2600
+ const stats = &side.active.stats;
2601
+ const boosts = &side.active.boosts;
2546
2602
 
2547
2603
  if (!state.proc) return;
2548
2604
  inline for (STATS, STAT_UP_REASONS) |s, r| {
@@ -2563,7 +2619,7 @@ pub const Effects = struct {
2563
2619
 
2564
2620
  if (state.miss) return log.fail(.{ ident, .None });
2565
2621
 
2566
- var stats = &side.active.stats;
2622
+ const stats = &side.active.stats;
2567
2623
  var boosts = &side.active.boosts;
2568
2624
 
2569
2625
  const move = Move.get(state.move);
@@ -2634,7 +2690,7 @@ pub const Effects = struct {
2634
2690
 
2635
2691
  const fail = foe.active.volatiles.Substitute or state.miss or !state.proc;
2636
2692
 
2637
- var stats = &foe.active.stats;
2693
+ const stats = &foe.active.stats;
2638
2694
  var boosts = &foe.active.boosts;
2639
2695
 
2640
2696
  const move = Move.get(state.move);
@@ -2718,9 +2774,9 @@ fn unmodifiedStats(battle: anytype, side: *Side) *Stats(u16) {
2718
2774
  }
2719
2775
 
2720
2776
  fn recomputeStats(battle: anytype, side: *Side) void {
2721
- var stats = unmodifiedStats(battle, side);
2777
+ const stats = unmodifiedStats(battle, side);
2722
2778
  inline for (STATS) |s| {
2723
- var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(side.active.boosts, s)) + 6))];
2779
+ const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(side.active.boosts, s)) + 6))];
2724
2780
  const val = @field(stats, s) * mod[0] / mod[1];
2725
2781
  @field(side.active.stats, s) = @max(1, @min(MAX_STAT_VALUE, val));
2726
2782
  }
@@ -2743,7 +2799,7 @@ fn boostStat(
2743
2799
  n: u2,
2744
2800
  ) !void {
2745
2801
  @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))];
2802
+ const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
2747
2803
  const stat = @field(unmodifiedStats(battle, side), s);
2748
2804
  @field(stats, s) = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
2749
2805
  if (comptime std.mem.eql(u8, s, "atk")) {
@@ -2766,7 +2822,7 @@ fn unboostStat(
2766
2822
  n: u2,
2767
2823
  ) !void {
2768
2824
  @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))];
2825
+ const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
2770
2826
  const stat = @field(unmodifiedStats(battle, side), s);
2771
2827
  @field(stats, s) = @max(1, stat * mod[0] / mod[1]);
2772
2828
  if (comptime std.mem.eql(u8, s, "atk")) {
@@ -2794,7 +2850,7 @@ fn convertible(active: ActivePokemon, m: Move) bool {
2794
2850
 
2795
2851
  pub const Rolls = struct {
2796
2852
  fn speedTie(battle: anytype, options: anytype) !bool {
2797
- const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
2853
+ const p1 = if (options.calc.overridden(.P1, 0, .speed_tie)) |player|
2798
2854
  player == .P1
2799
2855
  else if (showdown)
2800
2856
  battle.rng.range(u8, 0, 2) == 0
@@ -2806,7 +2862,7 @@ pub const Rolls = struct {
2806
2862
  }
2807
2863
 
2808
2864
  fn quickClaw(battle: anytype, player: Player, options: anytype) !bool {
2809
- const qkc = if (options.calc.overridden(player, .quick_claw)) |val|
2865
+ const qkc = if (options.calc.overridden(player, 0, .quick_claw)) |val|
2810
2866
  val == .true
2811
2867
  else if (showdown)
2812
2868
  battle.rng.chance(u8, 60, 256)
@@ -2818,7 +2874,7 @@ pub const Rolls = struct {
2818
2874
  }
2819
2875
 
2820
2876
  fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) !bool {
2821
- const ok = if (options.calc.overridden(player, .hit)) |val|
2877
+ const ok = if (options.calc.overridden(player, 0, .hit)) |val|
2822
2878
  val == .true
2823
2879
  else if (showdown)
2824
2880
  battle.rng.chance(u8, accuracy, 256)
@@ -2829,21 +2885,23 @@ pub const Rolls = struct {
2829
2885
  return ok;
2830
2886
  }
2831
2887
 
2832
- fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
2833
- const crit = if (options.calc.overridden(player, .critical_hit)) |val|
2888
+ fn criticalHit(battle: anytype, player: Player, i: u3, rate: u8, options: anytype) !bool {
2889
+ _ = .{ player, options };
2890
+ const crit = if (options.calc.overridden(player, i, .critical_hits)) |val|
2834
2891
  val == .true
2835
2892
  else if (showdown)
2836
2893
  battle.rng.chance(u8, rate, 256)
2837
2894
  else
2838
2895
  battle.rng.next() < rate;
2839
2896
 
2840
- try options.chance.criticalHit(player, crit, rate);
2897
+ try options.chance.criticalHit(player, i, crit, rate);
2841
2898
  return crit;
2842
2899
  }
2843
2900
 
2844
- fn damage(battle: anytype, player: Player, options: anytype) !u8 {
2845
- const roll = if (options.calc.overridden(player, .damage)) |val|
2846
- val
2901
+ fn damage(battle: anytype, player: Player, i: u3, options: anytype) !u8 {
2902
+ _ = .{ player, options };
2903
+ const roll = if (options.calc.overridden(player, i, .damages)) |val|
2904
+ @as(u8, val) + 216
2847
2905
  else roll: {
2848
2906
  if (showdown) break :roll battle.rng.range(u8, 217, 256);
2849
2907
  while (true) {
@@ -2853,12 +2911,12 @@ pub const Rolls = struct {
2853
2911
  };
2854
2912
 
2855
2913
  assert(roll >= 217 and roll <= 255);
2856
- try options.chance.damage(player, roll);
2914
+ try options.chance.damage(player, i, roll);
2857
2915
  return roll;
2858
2916
  }
2859
2917
 
2860
2918
  fn confused(battle: anytype, player: Player, options: anytype) !bool {
2861
- const cfz = if (options.calc.overridden(player, .confused)) |val|
2919
+ const cfz = if (options.calc.overridden(player, 0, .confused)) |val|
2862
2920
  val == .true
2863
2921
  else if (showdown)
2864
2922
  !battle.rng.chance(u8, 128, 256)
@@ -2870,7 +2928,7 @@ pub const Rolls = struct {
2870
2928
  }
2871
2929
 
2872
2930
  fn attract(battle: anytype, player: Player, options: anytype) !bool {
2873
- const cant = if (options.calc.overridden(player, .attract)) |val|
2931
+ const cant = if (options.calc.overridden(player, 0, .attract)) |val|
2874
2932
  val == .true
2875
2933
  else if (showdown)
2876
2934
  battle.rng.chance(u8, 1, 2)
@@ -2882,7 +2940,7 @@ pub const Rolls = struct {
2882
2940
  }
2883
2941
 
2884
2942
  fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
2885
- const par = if (options.calc.overridden(player, .paralyzed)) |val|
2943
+ const par = if (options.calc.overridden(player, 0, .paralyzed)) |val|
2886
2944
  val == .true
2887
2945
  else if (showdown)
2888
2946
  battle.rng.chance(u8, 63, 256)
@@ -2894,7 +2952,7 @@ pub const Rolls = struct {
2894
2952
  }
2895
2953
 
2896
2954
  fn defrost(battle: anytype, player: Player, options: anytype) !bool {
2897
- const thaw = if (options.calc.overridden(player, .defrost)) |val|
2955
+ const thaw = if (options.calc.overridden(player, 0, .defrost)) |val|
2898
2956
  val == .true
2899
2957
  else if (showdown)
2900
2958
  battle.rng.chance(u8, 25, 256)
@@ -2907,7 +2965,7 @@ pub const Rolls = struct {
2907
2965
 
2908
2966
  fn secondaryChance(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
2909
2967
  // GLITCH: 100% secondary chance will still fail 1/256 of the time
2910
- const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
2968
+ const proc = if (options.calc.overridden(player, 0, .secondary_chance)) |val|
2911
2969
  val == .true
2912
2970
  else if (showdown)
2913
2971
  battle.rng.chance(u8, rate, 256)
@@ -2919,7 +2977,7 @@ pub const Rolls = struct {
2919
2977
  }
2920
2978
 
2921
2979
  fn item(battle: anytype, player: Player, options: anytype) !bool {
2922
- const proc = if (options.calc.overridden(player, .item)) |val|
2980
+ const proc = if (options.calc.overridden(player, 0, .item)) |val|
2923
2981
  val == .true
2924
2982
  else if (showdown)
2925
2983
  battle.rng.chance(u8, 30, 256)
@@ -2931,7 +2989,7 @@ pub const Rolls = struct {
2931
2989
  }
2932
2990
 
2933
2991
  fn triAttack(battle: anytype, player: Player, options: anytype) !TriAttack {
2934
- const status: TriAttack = if (options.calc.overridden(player, .tri_attack)) |val|
2992
+ const status: TriAttack = if (options.calc.overridden(player, 0, .tri_attack)) |val|
2935
2993
  @enumFromInt(@intFromEnum(val) - 1)
2936
2994
  else if (showdown)
2937
2995
  @enumFromInt(battle.rng.range(u2, 0, 2))
@@ -2947,7 +3005,7 @@ pub const Rolls = struct {
2947
3005
  }
2948
3006
 
2949
3007
  fn present(battle: anytype, player: Player, options: anytype) !u8 {
2950
- const power = if (options.calc.overridden(player, .present)) |val|
3008
+ const power = if (options.calc.overridden(player, 0, .present)) |val|
2951
3009
  (val - 1) * 40
2952
3010
  else if (showdown) power: {
2953
3011
  const r = battle.rng.range(u8, 0, 10);
@@ -2969,7 +3027,7 @@ pub const Rolls = struct {
2969
3027
  }
2970
3028
 
2971
3029
  fn magnitude(battle: anytype, player: Player, options: anytype) !u8 {
2972
- const num: u8 = if (options.calc.overridden(player, .magnitude)) |val|
3030
+ const num: u8 = if (options.calc.overridden(player, 0, .magnitude)) |val|
2973
3031
  val + 3
2974
3032
  else if (showdown) num: {
2975
3033
  const r = battle.rng.range(u8, 0, 100);
@@ -2997,7 +3055,7 @@ pub const Rolls = struct {
2997
3055
  }
2998
3056
 
2999
3057
  fn tripleKick(battle: anytype, player: Player, options: anytype) !u2 {
3000
- const hits = if (options.calc.overridden(player, .triple_kick)) |val|
3058
+ const hits = if (options.calc.overridden(player, 0, .triple_kick)) |val|
3001
3059
  val
3002
3060
  else if (showdown)
3003
3061
  battle.rng.range(u2, 1, 4)
@@ -3013,13 +3071,12 @@ pub const Rolls = struct {
3013
3071
  return hits;
3014
3072
  }
3015
3073
 
3016
- fn spite(battle: anytype, player: Player, options: anytype) !u8 {
3017
- const pp = if (options.calc.overridden(player, .spite)) |val|
3074
+ fn spite(battle: anytype, player: Player, options: anytype) !u3 {
3075
+ assert(!showdown);
3076
+ const pp: u3 = if (options.calc.overridden(player, 0, .spite)) |val|
3018
3077
  val + 1
3019
- else if (showdown)
3020
- battle.rng.range(u8, 2, 6)
3021
3078
  else
3022
- (battle.rng.next() & 3) + 2;
3079
+ @intCast((battle.rng.next() & 3) + 2);
3023
3080
 
3024
3081
  assert(pp >= 2 and pp <= 5);
3025
3082
  try options.chance.spite(player, pp);
@@ -3033,7 +3090,7 @@ pub const Rolls = struct {
3033
3090
  var i: u8 = 0;
3034
3091
  const neutral = @intFromEnum(Effectiveness.Neutral);
3035
3092
  assert(mtype != .@"???");
3036
- const ty: Type = if (options.calc.overridden(player, .conversion_2)) |val|
3093
+ const ty: Type = if (options.calc.overridden(player, 0, .conversion_2)) |val|
3037
3094
  @enumFromInt(@intFromEnum(val) - 1)
3038
3095
  else if (showdown) ty: {
3039
3096
  for (Type.SHOWDOWN) |t| {
@@ -3067,7 +3124,7 @@ pub const Rolls = struct {
3067
3124
  assert(count <= 8);
3068
3125
  const num = if (count == 8) 0 else @as(u8, 255) >> @intCast(count);
3069
3126
 
3070
- const ok = if (options.calc.overridden(player, .protect)) |val|
3127
+ const ok = if (options.calc.overridden(player, 0, .protect)) |val|
3071
3128
  val == .true
3072
3129
  else if (showdown)
3073
3130
  battle.rng.chance(u8, num, 255)
@@ -3089,7 +3146,7 @@ pub const Rolls = struct {
3089
3146
  const moves = active.moves;
3090
3147
 
3091
3148
  // TODO: consider throwing error instead of rerolling?
3092
- const overridden = if (options.calc.overridden(player, .move_slot)) |val|
3149
+ const overridden = if (options.calc.overridden(player, 0, .move_slot)) |val|
3093
3150
  if (convertible(active, moves[val - 1].id)) val else null
3094
3151
  else
3095
3152
  null;
@@ -3126,7 +3183,7 @@ pub const Rolls = struct {
3126
3183
  const moves = active.moves;
3127
3184
 
3128
3185
  // TODO: consider throwing error instead of rerolling?
3129
- const overridden = if (options.calc.overridden(player, .move_slot)) |val|
3186
+ const overridden = if (options.calc.overridden(player, 0, .move_slot)) |val|
3130
3187
  if (val != mslot and Move.get(moves[val - 1].id).extra.sleep_talk) val else null
3131
3188
  else
3132
3189
  null;
@@ -3170,7 +3227,7 @@ pub const Rolls = struct {
3170
3227
  assert(n > 0 and n <= 6);
3171
3228
 
3172
3229
  // TODO: consider throwing error instead of rerolling?
3173
- const overridden = if (options.calc.overridden(player, .force_switch)) |val| val: {
3230
+ const overridden = if (options.calc.overridden(player, 0, .force_switch)) |val| val: {
3174
3231
  for (slots) |slot| if (slot == val) break :val if (foe.get(slot).hp > 0) val else null;
3175
3232
  break :val null;
3176
3233
  } else null;
@@ -3196,7 +3253,7 @@ pub const Rolls = struct {
3196
3253
  const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
3197
3254
 
3198
3255
  fn multiHit(battle: anytype, player: Player, options: anytype) !u3 {
3199
- const n: u3 = if (options.calc.overridden(player, .multi_hit)) |val|
3256
+ const n: u3 = if (options.calc.overridden(player, 0, .multi_hit)) |val|
3200
3257
  @intCast(val)
3201
3258
  else if (showdown)
3202
3259
  DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
@@ -3211,7 +3268,7 @@ pub const Rolls = struct {
3211
3268
  }
3212
3269
 
3213
3270
  fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
3214
- const power = if (options.calc.overridden(player, .psywave)) |val|
3271
+ const power = if (options.calc.overridden(player, 0, .psywave)) |val|
3215
3272
  val
3216
3273
  else if (showdown)
3217
3274
  battle.rng.range(u8, 1, max)
@@ -3237,7 +3294,7 @@ pub const Rolls = struct {
3237
3294
  }
3238
3295
 
3239
3296
  // TODO: consider throwing error instead of rerolling?
3240
- const overridden = if (options.calc.overridden(player, .metronome)) |val| val: {
3297
+ const overridden = if (options.calc.overridden(player, 0, .metronome)) |val| val: {
3241
3298
  for (moves) |m| if (m.id == val) break :val null;
3242
3299
  break :val val;
3243
3300
  } else null;
@@ -3269,7 +3326,7 @@ pub const Rolls = struct {
3269
3326
  }
3270
3327
 
3271
3328
  fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
3272
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
3329
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3273
3330
  @intCast(val)
3274
3331
  else if (showdown)
3275
3332
  battle.rng.range(u3, 1, 7)
@@ -3281,12 +3338,12 @@ pub const Rolls = struct {
3281
3338
  };
3282
3339
 
3283
3340
  assert(duration >= 1 and duration <= 6);
3284
- options.chance.duration(.sleep, player, player.foe(), duration);
3341
+ options.chance.duration(player, duration);
3285
3342
  return duration + 1;
3286
3343
  }
3287
3344
 
3288
3345
  fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
3289
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
3346
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3290
3347
  @intCast(val)
3291
3348
  else if (showdown)
3292
3349
  // Pokémon Showdown incorrectly uses the same duration for self-confusion
@@ -3295,12 +3352,12 @@ pub const Rolls = struct {
3295
3352
  @intCast((battle.rng.next() & if (self) 1 else 3) + 2);
3296
3353
 
3297
3354
  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);
3355
+ options.chance.duration(player, duration);
3299
3356
  return duration;
3300
3357
  }
3301
3358
 
3302
3359
  fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
3303
- const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
3360
+ const duration: u4 = if (options.calc.overridden(player, 0, .duration)) |val|
3304
3361
  @intCast(val)
3305
3362
  else if (showdown)
3306
3363
  // Pokémon Showdown incorrectly inherits Generation III's Disable duration
@@ -3313,12 +3370,12 @@ pub const Rolls = struct {
3313
3370
  };
3314
3371
 
3315
3372
  assert(duration >= 2 and duration <= (if (showdown) 5 else 7));
3316
- options.chance.duration(.disable, player, player.foe(), duration);
3373
+ options.chance.duration(player, duration);
3317
3374
  return duration;
3318
3375
  }
3319
3376
 
3320
3377
  fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
3321
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
3378
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3322
3379
  @intCast(val)
3323
3380
  else if (showdown)
3324
3381
  battle.rng.range(u3, 2, 4)
@@ -3326,12 +3383,12 @@ pub const Rolls = struct {
3326
3383
  @intCast((battle.rng.next() & 1) + 2); // BUG: thrash is + 1 not +2
3327
3384
 
3328
3385
  assert(duration >= 2 and duration <= 3);
3329
- options.chance.duration(.attacking, player, player, duration);
3386
+ options.chance.duration(player, duration);
3330
3387
  return duration;
3331
3388
  }
3332
3389
 
3333
3390
  fn bindingDuration(battle: anytype, player: Player, options: anytype) u3 {
3334
- const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
3391
+ const duration: u3 = (if (options.calc.overridden(player, 0, .duration)) |val|
3335
3392
  @intCast(val)
3336
3393
  else if (showdown)
3337
3394
  // TODO: PS does range(3, 6) here which after subtracting 1 is actually 2-4 not 2-5!
@@ -3340,13 +3397,13 @@ pub const Rolls = struct {
3340
3397
  @intCast(battle.rng.next() & 3 + 2));
3341
3398
 
3342
3399
  assert(duration >= 2 and duration <= 5);
3343
- options.chance.duration(.binding, player, player.foe(), duration);
3400
+ options.chance.duration(player, duration);
3344
3401
  return duration + 1;
3345
3402
  }
3346
3403
 
3347
3404
  // TODO: consider sharing implementation with bindingDuration
3348
3405
  fn encoreDuration(battle: anytype, player: Player, options: anytype) u3 {
3349
- const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
3406
+ const duration: u3 = (if (options.calc.overridden(player, 0, .duration)) |val|
3350
3407
  @intCast(val)
3351
3408
  else if (showdown)
3352
3409
  battle.rng.range(u3, 2, 6)
@@ -3354,7 +3411,7 @@ pub const Rolls = struct {
3354
3411
  @intCast(battle.rng.next() & 3 + 2));
3355
3412
 
3356
3413
  assert(duration >= 2 and duration <= 5);
3357
- options.chance.duration(.encore, player, player.foe(), duration);
3414
+ options.chance.duration(player, duration);
3358
3415
  return duration + 1;
3359
3416
  }
3360
3417
  };
@@ -3467,7 +3524,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3467
3524
  .Move => {
3468
3525
  const side = battle.side(player);
3469
3526
 
3470
- var active = &side.active;
3527
+ const active = &side.active;
3471
3528
  const stored = side.stored();
3472
3529
 
3473
3530
  // While players are not given any input options on the cartridge in these cases,