@pkmn/engine 0.1.0-dev.b3f3e3a6 → 0.1.0-dev.b9015a94

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 (67) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +41 -14
  3. package/build/pkg/addon/browser.d.ts +3 -0
  4. package/build/pkg/addon/browser.js +43 -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 +96 -0
  8. package/build/pkg/addon/node.js.map +1 -0
  9. package/build/pkg/addon.d.ts +2 -0
  10. package/build/pkg/addon.js +35 -58
  11. package/build/pkg/addon.js.map +1 -1
  12. package/build/pkg/data.d.ts +27 -5
  13. package/build/pkg/data.js.map +1 -1
  14. package/build/pkg/gen1.js +1 -1
  15. package/build/pkg/gen1.js.map +1 -1
  16. package/build/pkg/index.d.ts +24 -10
  17. package/build/pkg/index.js +15 -10
  18. package/build/pkg/index.js.map +1 -1
  19. package/build/pkg/protocol.d.ts +20 -1
  20. package/build/pkg/protocol.js +109 -107
  21. package/build/pkg/protocol.js.map +1 -1
  22. package/build/tools/debug.js +35 -69
  23. package/build/tools/debug.js.map +1 -1
  24. package/build/tools/display/debug.jsx +128 -0
  25. package/build/tools/display/debug.jsx.map +1 -0
  26. package/build/tools/display/index.js +132 -0
  27. package/build/tools/display/index.js.map +1 -0
  28. package/build/tools/display/ui.jsx +349 -0
  29. package/build/tools/display/ui.jsx.map +1 -0
  30. package/build/tools/display/util.js +18 -0
  31. package/build/tools/display/util.js.map +1 -0
  32. package/build.zig +206 -81
  33. package/package.json +49 -28
  34. package/src/bin/install-pkmn-engine +29 -19
  35. package/src/bin/pkmn-debug +1 -1
  36. package/src/lib/common/array.zig +77 -0
  37. package/src/lib/common/data.zig +4 -4
  38. package/src/lib/common/debug.zig +2 -2
  39. package/src/lib/common/js.zig +4 -2
  40. package/src/lib/common/optional.zig +14 -10
  41. package/src/lib/common/options.zig +2 -0
  42. package/src/lib/common/protocol.zig +75 -74
  43. package/src/lib/common/rational.zig +121 -27
  44. package/src/lib/common/rng.zig +7 -7
  45. package/src/lib/common/util.zig +50 -1
  46. package/src/lib/gen1/calc.zig +509 -131
  47. package/src/lib/gen1/chance.zig +374 -267
  48. package/src/lib/gen1/data/species.zig +1 -1
  49. package/src/lib/gen1/data.zig +17 -16
  50. package/src/lib/gen1/helpers.zig +95 -538
  51. package/src/lib/gen1/mechanics.zig +284 -191
  52. package/src/lib/gen2/calc.zig +34 -16
  53. package/src/lib/gen2/chance.zig +84 -496
  54. package/src/lib/gen2/data.zig +8 -7
  55. package/src/lib/gen2/generated.zig +1 -3
  56. package/src/lib/gen2/helpers.zig +41 -13
  57. package/src/lib/gen2/mechanics.zig +178 -127
  58. package/src/lib/{binding/node.zig → node.zig} +9 -12
  59. package/src/lib/pkmn.zig +32 -15
  60. package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
  61. package/src/tools/display/dom.ts +96 -0
  62. package/src/tools/display/ui.css +351 -0
  63. package/build/test/display.js +0 -400
  64. package/build/test/display.js.map +0 -1
  65. package/src/lib/gen1/README.md +0 -894
  66. package/src/lib/gen2/README.md +0 -329
  67. package/src/test/display/pkmn.html.tmpl +0 -368
@@ -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
@@ -140,11 +135,11 @@ fn start(battle: anytype, options: anytype) !Result {
140
135
  const p1 = battle.side(.P1);
141
136
  const p2 = battle.side(.P2);
142
137
 
143
- var p1_slot = findFirstAlive(p1);
138
+ const p1_slot = findFirstAlive(p1);
144
139
  assert(!showdown or p1_slot == 1);
145
140
  if (p1_slot == 0) return if (findFirstAlive(p2) == 0) Result.Tie else Result.Lose;
146
141
 
147
- var p2_slot = findFirstAlive(p2);
142
+ const p2_slot = findFirstAlive(p2);
148
143
  assert(!showdown or p2_slot == 1);
149
144
  if (p2_slot == 0) return Result.Win;
150
145
 
@@ -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);
@@ -403,7 +397,9 @@ fn executeMove(
403
397
 
404
398
  assert(choice.type == .Move);
405
399
  var mslot: u4 = @intCast(choice.data);
400
+ _ = &mslot;
406
401
  var move = Move.Pound; // TODO
402
+ _ = &move;
407
403
 
408
404
  if (!try beforeMove(battle, player, move, options)) return null;
409
405
 
@@ -440,18 +436,9 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
440
436
  if (Status.is(stored.status, .SLP)) slp: {
441
437
  const before = stored.status;
442
438
  // 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
439
  stored.status -= 1;
447
- // }
448
440
 
449
- const duration = Status.duration(stored.status);
450
- if (!Status.is(stored.status, .EXT)) {
451
- try options.chance.sleep(player, duration);
452
- }
453
-
454
- if (duration == 0) {
441
+ if (Status.duration(stored.status) == 0) {
455
442
  try log.curestatus(.{ ident, before, .Message });
456
443
  stored.status = 0; // clears EXT if present
457
444
  resetCant(volatiles);
@@ -481,12 +468,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
481
468
  }
482
469
 
483
470
  if (volatiles.disable.duration > 0) {
484
- // if (options.calc.modify(player, .disable)) |extend| {
485
- // if (!extend) volatiles.disable.duration = 0;
486
- // } else {
487
471
  volatiles.disable.duration -= 1;
488
- // }
489
- try options.chance.disable(player, volatiles.disable.duration);
490
472
 
491
473
  if (volatiles.disable.duration == 0) {
492
474
  volatiles.disable.move = 0;
@@ -496,13 +478,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
496
478
 
497
479
  if (volatiles.Confusion) {
498
480
  assert(volatiles.confusion > 0);
499
-
500
- // if (options.calc.modify(player, .confusion)) |extend| {
501
- // if (!extend) volatiles.confusion = 0;
502
- // } else {
503
481
  volatiles.confusion -= 1;
504
- // }
505
- try options.chance.confusion(player, volatiles.confusion);
506
482
 
507
483
  if (volatiles.confusion == 0) {
508
484
  volatiles.Confusion = false;
@@ -565,7 +541,7 @@ fn resetCant(volatiles: *align(8) Volatiles) void {
565
541
 
566
542
  pub fn canMove(battle: anytype, player: Player, state: *State, options: anytype) !bool {
567
543
  var side = battle.side(player);
568
- var volatiles = &side.active.volatiles;
544
+ const volatiles = &side.active.volatiles;
569
545
 
570
546
  // FIXME: pass in target to avoid lookup?
571
547
  const target = if (Move.get(state.move).target == .Self) player else player.foe();
@@ -663,7 +639,7 @@ pub fn checkCriticalHit(battle: anytype, player: Player, state: *State, options:
663
639
  }
664
640
  assert(stage <= 4);
665
641
 
666
- 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);
667
643
  }
668
644
 
669
645
  pub fn calcDamage(
@@ -731,7 +707,7 @@ pub fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype)
731
707
 
732
708
  pub fn randomizeDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
733
709
  if (state.damage <= 1) return;
734
- const random = try Rolls.damage(battle, player, options);
710
+ const random = try Rolls.damage(battle, player, state.hit, options);
735
711
  options.calc.base(player.foe(), state.damage);
736
712
  state.damage = @intCast(@as(u32, state.damage) *% random / 255);
737
713
  options.calc.final(player.foe(), state.damage);
@@ -997,7 +973,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
997
973
 
998
974
  // Weather
999
975
  if (battle.field.weather != .None) {
1000
- assert(battle.field.weather_duration > 0);
1001
976
  battle.field.weather_duration -= 1;
1002
977
 
1003
978
  if (battle.field.weather_duration == 0) {
@@ -1056,7 +1031,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1056
1031
  var volatiles = &side.active.volatiles;
1057
1032
 
1058
1033
  if (volatiles.PerishSong) {
1059
- assert(volatiles.perish_song > 0);
1060
1034
  volatiles.perish_song -= 1;
1061
1035
 
1062
1036
  assert(volatiles.perish_song < 3);
@@ -1086,7 +1060,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1086
1060
 
1087
1061
  // Mystery Berry
1088
1062
  inline for (players) |player| {
1089
- var stored = battle.side(player).stored();
1063
+ const stored = battle.side(player).stored();
1090
1064
 
1091
1065
  if (stored.item == .MysteryBerry) {
1092
1066
  // TODO stored and active moves, transform, etc
@@ -1193,7 +1167,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1193
1167
  var volatiles = &side.active.volatiles;
1194
1168
 
1195
1169
  if (volatiles.Encore) {
1196
- assert(volatiles.encore > 0);
1197
1170
  volatiles.encore -= 1;
1198
1171
 
1199
1172
  if (volatiles.encore == 0 or side.active.move(mslot).pp == 0) {
@@ -1215,7 +1188,6 @@ fn updateSideCondition(
1215
1188
  ) !void {
1216
1189
  var side = battle.side(player);
1217
1190
  if (@field(side.conditions, key)) {
1218
- assert(@field(side.conditions, value) > 0);
1219
1191
  @field(side.conditions, value) -= 1;
1220
1192
 
1221
1193
  if (@field(side.conditions, value) == 0) {
@@ -1306,14 +1278,32 @@ pub const Effects = struct {
1306
1278
  }
1307
1279
 
1308
1280
  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
- //
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
+ }
1317
1307
  }
1318
1308
 
1319
1309
  pub const bide = struct {
@@ -1325,14 +1315,7 @@ pub const Effects = struct {
1325
1315
 
1326
1316
  if (!volatiles.Bide) return;
1327
1317
 
1328
- assert(volatiles.attacks > 0);
1329
- // if (options.calc.modify(player, .attacking)) |extend| {
1330
- // if (!extend) volatiles.attacks = 0;
1331
- // } else {
1332
1318
  volatiles.attacks -= 1;
1333
- // }
1334
- try options.chance.attacking(player, volatiles.attacks);
1335
-
1336
1319
  if (volatiles.attacks != 0) return options.log.activate(.{ ident, .Bide });
1337
1320
 
1338
1321
  volatiles.Bide = false;
@@ -1478,8 +1461,8 @@ pub const Effects = struct {
1478
1461
  side.stored().hp -= @max(side.stored().stats.hp / 2, 1);
1479
1462
  try options.log.damage(.{ ident, side.stored(), .Curse });
1480
1463
  } else {
1481
- var stats = &side.active.stats;
1482
- var boosts = &side.active.boosts;
1464
+ const stats = &side.active.stats;
1465
+ const boosts = &side.active.boosts;
1483
1466
 
1484
1467
  const max_atk = boosts.atk == 6 or stats.atk == MAX_STAT_VALUE;
1485
1468
  const max_def = boosts.def == 6 or stats.def == MAX_STAT_VALUE;
@@ -1599,8 +1582,16 @@ pub const Effects = struct {
1599
1582
  try options.log.start(.{ foe_ident, .Encore, move.id });
1600
1583
  }
1601
1584
 
1602
- pub fn explode(battle: anytype, player: Player, state: *State, options: anytype) !void {
1603
- _ = .{ 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;
1604
1595
  }
1605
1596
 
1606
1597
  pub fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
@@ -1726,8 +1717,26 @@ pub const Effects = struct {
1726
1717
  foe.active.volatiles.frozen = true;
1727
1718
  }
1728
1719
 
1729
- pub fn furyCutter(battle: anytype, player: Player, state: *State, options: anytype) !void {
1730
- _ = .{ 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
+ }
1731
1740
  }
1732
1741
 
1733
1742
  pub fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -1888,7 +1897,29 @@ pub const Effects = struct {
1888
1897
  }
1889
1898
 
1890
1899
  pub fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
1891
- _ = .{ 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 });
1892
1923
  }
1893
1924
 
1894
1925
  pub fn mirrorMove(
@@ -2185,11 +2216,10 @@ pub const Effects = struct {
2185
2216
  }
2186
2217
 
2187
2218
  pub fn damage(battle: anytype, player: Player, state: *State, _: anytype) !void {
2188
- var side = battle.side(player);
2189
- var volatiles = &side.active.volatiles;
2219
+ const side = battle.side(player);
2190
2220
 
2191
- assert(volatiles.Rage);
2192
- state.damage *|= (volatiles.rage +| 1);
2221
+ assert(side.active.volatiles.Rage);
2222
+ state.damage *|= (side.active.volatiles.rage +| 1);
2193
2223
  }
2194
2224
  };
2195
2225
 
@@ -2397,17 +2427,37 @@ pub const Effects = struct {
2397
2427
  }
2398
2428
 
2399
2429
  pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
2400
- _ = .{ battle, player, state, options }; // TODO
2401
- // var foe = battle.foe(player);
2430
+ var foe = battle.foe(player);
2402
2431
 
2403
- // 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
+ }
2404
2436
 
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
- // }
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 });
2411
2461
  }
2412
2462
 
2413
2463
  pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
@@ -2541,8 +2591,8 @@ pub const Effects = struct {
2541
2591
  pub fn allStatUpChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
2542
2592
  var side = battle.side(player);
2543
2593
 
2544
- var stats = &side.active.stats;
2545
- var boosts = &side.active.boosts;
2594
+ const stats = &side.active.stats;
2595
+ const boosts = &side.active.boosts;
2546
2596
 
2547
2597
  if (!state.proc) return;
2548
2598
  inline for (STATS, STAT_UP_REASONS) |s, r| {
@@ -2563,7 +2613,7 @@ pub const Effects = struct {
2563
2613
 
2564
2614
  if (state.miss) return log.fail(.{ ident, .None });
2565
2615
 
2566
- var stats = &side.active.stats;
2616
+ const stats = &side.active.stats;
2567
2617
  var boosts = &side.active.boosts;
2568
2618
 
2569
2619
  const move = Move.get(state.move);
@@ -2634,7 +2684,7 @@ pub const Effects = struct {
2634
2684
 
2635
2685
  const fail = foe.active.volatiles.Substitute or state.miss or !state.proc;
2636
2686
 
2637
- var stats = &foe.active.stats;
2687
+ const stats = &foe.active.stats;
2638
2688
  var boosts = &foe.active.boosts;
2639
2689
 
2640
2690
  const move = Move.get(state.move);
@@ -2718,9 +2768,9 @@ fn unmodifiedStats(battle: anytype, side: *Side) *Stats(u16) {
2718
2768
  }
2719
2769
 
2720
2770
  fn recomputeStats(battle: anytype, side: *Side) void {
2721
- var stats = unmodifiedStats(battle, side);
2771
+ const stats = unmodifiedStats(battle, side);
2722
2772
  inline for (STATS) |s| {
2723
- var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(side.active.boosts, s)) + 6))];
2773
+ const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(side.active.boosts, s)) + 6))];
2724
2774
  const val = @field(stats, s) * mod[0] / mod[1];
2725
2775
  @field(side.active.stats, s) = @max(1, @min(MAX_STAT_VALUE, val));
2726
2776
  }
@@ -2743,7 +2793,7 @@ fn boostStat(
2743
2793
  n: u2,
2744
2794
  ) !void {
2745
2795
  @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))];
2796
+ const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
2747
2797
  const stat = @field(unmodifiedStats(battle, side), s);
2748
2798
  @field(stats, s) = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
2749
2799
  if (comptime std.mem.eql(u8, s, "atk")) {
@@ -2766,7 +2816,7 @@ fn unboostStat(
2766
2816
  n: u2,
2767
2817
  ) !void {
2768
2818
  @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))];
2819
+ const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
2770
2820
  const stat = @field(unmodifiedStats(battle, side), s);
2771
2821
  @field(stats, s) = @max(1, stat * mod[0] / mod[1]);
2772
2822
  if (comptime std.mem.eql(u8, s, "atk")) {
@@ -2794,7 +2844,7 @@ fn convertible(active: ActivePokemon, m: Move) bool {
2794
2844
 
2795
2845
  pub const Rolls = struct {
2796
2846
  fn speedTie(battle: anytype, options: anytype) !bool {
2797
- const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
2847
+ const p1 = if (options.calc.overridden(.P1, 0, .speed_tie)) |player|
2798
2848
  player == .P1
2799
2849
  else if (showdown)
2800
2850
  battle.rng.range(u8, 0, 2) == 0
@@ -2806,7 +2856,7 @@ pub const Rolls = struct {
2806
2856
  }
2807
2857
 
2808
2858
  fn quickClaw(battle: anytype, player: Player, options: anytype) !bool {
2809
- const qkc = if (options.calc.overridden(player, .quick_claw)) |val|
2859
+ const qkc = if (options.calc.overridden(player, 0, .quick_claw)) |val|
2810
2860
  val == .true
2811
2861
  else if (showdown)
2812
2862
  battle.rng.chance(u8, 60, 256)
@@ -2818,7 +2868,7 @@ pub const Rolls = struct {
2818
2868
  }
2819
2869
 
2820
2870
  fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) !bool {
2821
- const ok = if (options.calc.overridden(player, .hit)) |val|
2871
+ const ok = if (options.calc.overridden(player, 0, .hit)) |val|
2822
2872
  val == .true
2823
2873
  else if (showdown)
2824
2874
  battle.rng.chance(u8, accuracy, 256)
@@ -2829,20 +2879,22 @@ pub const Rolls = struct {
2829
2879
  return ok;
2830
2880
  }
2831
2881
 
2832
- fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
2833
- 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|
2834
2885
  val == .true
2835
2886
  else if (showdown)
2836
2887
  battle.rng.chance(u8, rate, 256)
2837
2888
  else
2838
2889
  battle.rng.next() < rate;
2839
2890
 
2840
- try options.chance.criticalHit(player, crit, rate);
2891
+ try options.chance.criticalHit(player, i, crit, rate);
2841
2892
  return crit;
2842
2893
  }
2843
2894
 
2844
- fn damage(battle: anytype, player: Player, options: anytype) !u8 {
2845
- 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|
2846
2898
  val
2847
2899
  else roll: {
2848
2900
  if (showdown) break :roll battle.rng.range(u8, 217, 256);
@@ -2853,12 +2905,12 @@ pub const Rolls = struct {
2853
2905
  };
2854
2906
 
2855
2907
  assert(roll >= 217 and roll <= 255);
2856
- try options.chance.damage(player, roll);
2908
+ try options.chance.damage(player, i, roll);
2857
2909
  return roll;
2858
2910
  }
2859
2911
 
2860
2912
  fn confused(battle: anytype, player: Player, options: anytype) !bool {
2861
- const cfz = if (options.calc.overridden(player, .confused)) |val|
2913
+ const cfz = if (options.calc.overridden(player, 0, .confused)) |val|
2862
2914
  val == .true
2863
2915
  else if (showdown)
2864
2916
  !battle.rng.chance(u8, 128, 256)
@@ -2870,7 +2922,7 @@ pub const Rolls = struct {
2870
2922
  }
2871
2923
 
2872
2924
  fn attract(battle: anytype, player: Player, options: anytype) !bool {
2873
- const cant = if (options.calc.overridden(player, .attract)) |val|
2925
+ const cant = if (options.calc.overridden(player, 0, .attract)) |val|
2874
2926
  val == .true
2875
2927
  else if (showdown)
2876
2928
  battle.rng.chance(u8, 1, 2)
@@ -2882,7 +2934,7 @@ pub const Rolls = struct {
2882
2934
  }
2883
2935
 
2884
2936
  fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
2885
- const par = if (options.calc.overridden(player, .paralyzed)) |val|
2937
+ const par = if (options.calc.overridden(player, 0, .paralyzed)) |val|
2886
2938
  val == .true
2887
2939
  else if (showdown)
2888
2940
  battle.rng.chance(u8, 63, 256)
@@ -2894,7 +2946,7 @@ pub const Rolls = struct {
2894
2946
  }
2895
2947
 
2896
2948
  fn defrost(battle: anytype, player: Player, options: anytype) !bool {
2897
- const thaw = if (options.calc.overridden(player, .defrost)) |val|
2949
+ const thaw = if (options.calc.overridden(player, 0, .defrost)) |val|
2898
2950
  val == .true
2899
2951
  else if (showdown)
2900
2952
  battle.rng.chance(u8, 25, 256)
@@ -2907,7 +2959,7 @@ pub const Rolls = struct {
2907
2959
 
2908
2960
  fn secondaryChance(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
2909
2961
  // GLITCH: 100% secondary chance will still fail 1/256 of the time
2910
- const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
2962
+ const proc = if (options.calc.overridden(player, 0, .secondary_chance)) |val|
2911
2963
  val == .true
2912
2964
  else if (showdown)
2913
2965
  battle.rng.chance(u8, rate, 256)
@@ -2919,7 +2971,7 @@ pub const Rolls = struct {
2919
2971
  }
2920
2972
 
2921
2973
  fn item(battle: anytype, player: Player, options: anytype) !bool {
2922
- const proc = if (options.calc.overridden(player, .item)) |val|
2974
+ const proc = if (options.calc.overridden(player, 0, .item)) |val|
2923
2975
  val == .true
2924
2976
  else if (showdown)
2925
2977
  battle.rng.chance(u8, 30, 256)
@@ -2931,7 +2983,7 @@ pub const Rolls = struct {
2931
2983
  }
2932
2984
 
2933
2985
  fn triAttack(battle: anytype, player: Player, options: anytype) !TriAttack {
2934
- const status: TriAttack = if (options.calc.overridden(player, .tri_attack)) |val|
2986
+ const status: TriAttack = if (options.calc.overridden(player, 0, .tri_attack)) |val|
2935
2987
  @enumFromInt(@intFromEnum(val) - 1)
2936
2988
  else if (showdown)
2937
2989
  @enumFromInt(battle.rng.range(u2, 0, 2))
@@ -2947,7 +2999,7 @@ pub const Rolls = struct {
2947
2999
  }
2948
3000
 
2949
3001
  fn present(battle: anytype, player: Player, options: anytype) !u8 {
2950
- const power = if (options.calc.overridden(player, .present)) |val|
3002
+ const power = if (options.calc.overridden(player, 0, .present)) |val|
2951
3003
  (val - 1) * 40
2952
3004
  else if (showdown) power: {
2953
3005
  const r = battle.rng.range(u8, 0, 10);
@@ -2969,7 +3021,7 @@ pub const Rolls = struct {
2969
3021
  }
2970
3022
 
2971
3023
  fn magnitude(battle: anytype, player: Player, options: anytype) !u8 {
2972
- const num: u8 = if (options.calc.overridden(player, .magnitude)) |val|
3024
+ const num: u8 = if (options.calc.overridden(player, 0, .magnitude)) |val|
2973
3025
  val + 3
2974
3026
  else if (showdown) num: {
2975
3027
  const r = battle.rng.range(u8, 0, 100);
@@ -2997,7 +3049,7 @@ pub const Rolls = struct {
2997
3049
  }
2998
3050
 
2999
3051
  fn tripleKick(battle: anytype, player: Player, options: anytype) !u2 {
3000
- const hits = if (options.calc.overridden(player, .triple_kick)) |val|
3052
+ const hits = if (options.calc.overridden(player, 0, .triple_kick)) |val|
3001
3053
  val
3002
3054
  else if (showdown)
3003
3055
  battle.rng.range(u2, 1, 4)
@@ -3013,13 +3065,12 @@ pub const Rolls = struct {
3013
3065
  return hits;
3014
3066
  }
3015
3067
 
3016
- fn spite(battle: anytype, player: Player, options: anytype) !u8 {
3017
- 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|
3018
3071
  val + 1
3019
- else if (showdown)
3020
- battle.rng.range(u8, 2, 6)
3021
3072
  else
3022
- (battle.rng.next() & 3) + 2;
3073
+ @intCast((battle.rng.next() & 3) + 2);
3023
3074
 
3024
3075
  assert(pp >= 2 and pp <= 5);
3025
3076
  try options.chance.spite(player, pp);
@@ -3033,7 +3084,7 @@ pub const Rolls = struct {
3033
3084
  var i: u8 = 0;
3034
3085
  const neutral = @intFromEnum(Effectiveness.Neutral);
3035
3086
  assert(mtype != .@"???");
3036
- const ty: Type = if (options.calc.overridden(player, .conversion_2)) |val|
3087
+ const ty: Type = if (options.calc.overridden(player, 0, .conversion_2)) |val|
3037
3088
  @enumFromInt(@intFromEnum(val) - 1)
3038
3089
  else if (showdown) ty: {
3039
3090
  for (Type.SHOWDOWN) |t| {
@@ -3067,7 +3118,7 @@ pub const Rolls = struct {
3067
3118
  assert(count <= 8);
3068
3119
  const num = if (count == 8) 0 else @as(u8, 255) >> @intCast(count);
3069
3120
 
3070
- const ok = if (options.calc.overridden(player, .protect)) |val|
3121
+ const ok = if (options.calc.overridden(player, 0, .protect)) |val|
3071
3122
  val == .true
3072
3123
  else if (showdown)
3073
3124
  battle.rng.chance(u8, num, 255)
@@ -3089,7 +3140,7 @@ pub const Rolls = struct {
3089
3140
  const moves = active.moves;
3090
3141
 
3091
3142
  // TODO: consider throwing error instead of rerolling?
3092
- const overridden = if (options.calc.overridden(player, .move_slot)) |val|
3143
+ const overridden = if (options.calc.overridden(player, 0, .move_slot)) |val|
3093
3144
  if (convertible(active, moves[val - 1].id)) val else null
3094
3145
  else
3095
3146
  null;
@@ -3126,7 +3177,7 @@ pub const Rolls = struct {
3126
3177
  const moves = active.moves;
3127
3178
 
3128
3179
  // TODO: consider throwing error instead of rerolling?
3129
- const overridden = if (options.calc.overridden(player, .move_slot)) |val|
3180
+ const overridden = if (options.calc.overridden(player, 0, .move_slot)) |val|
3130
3181
  if (val != mslot and Move.get(moves[val - 1].id).extra.sleep_talk) val else null
3131
3182
  else
3132
3183
  null;
@@ -3170,7 +3221,7 @@ pub const Rolls = struct {
3170
3221
  assert(n > 0 and n <= 6);
3171
3222
 
3172
3223
  // TODO: consider throwing error instead of rerolling?
3173
- const overridden = if (options.calc.overridden(player, .force_switch)) |val| val: {
3224
+ const overridden = if (options.calc.overridden(player, 0, .force_switch)) |val| val: {
3174
3225
  for (slots) |slot| if (slot == val) break :val if (foe.get(slot).hp > 0) val else null;
3175
3226
  break :val null;
3176
3227
  } else null;
@@ -3196,7 +3247,7 @@ pub const Rolls = struct {
3196
3247
  const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
3197
3248
 
3198
3249
  fn multiHit(battle: anytype, player: Player, options: anytype) !u3 {
3199
- const n: u3 = if (options.calc.overridden(player, .multi_hit)) |val|
3250
+ const n: u3 = if (options.calc.overridden(player, 0, .multi_hit)) |val|
3200
3251
  @intCast(val)
3201
3252
  else if (showdown)
3202
3253
  DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
@@ -3211,7 +3262,7 @@ pub const Rolls = struct {
3211
3262
  }
3212
3263
 
3213
3264
  fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
3214
- const power = if (options.calc.overridden(player, .psywave)) |val|
3265
+ const power = if (options.calc.overridden(player, 0, .psywave)) |val|
3215
3266
  val
3216
3267
  else if (showdown)
3217
3268
  battle.rng.range(u8, 1, max)
@@ -3237,7 +3288,7 @@ pub const Rolls = struct {
3237
3288
  }
3238
3289
 
3239
3290
  // TODO: consider throwing error instead of rerolling?
3240
- const overridden = if (options.calc.overridden(player, .metronome)) |val| val: {
3291
+ const overridden = if (options.calc.overridden(player, 0, .metronome)) |val| val: {
3241
3292
  for (moves) |m| if (m.id == val) break :val null;
3242
3293
  break :val val;
3243
3294
  } else null;
@@ -3269,7 +3320,7 @@ pub const Rolls = struct {
3269
3320
  }
3270
3321
 
3271
3322
  fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
3272
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
3323
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3273
3324
  @intCast(val)
3274
3325
  else if (showdown)
3275
3326
  battle.rng.range(u3, 1, 7)
@@ -3281,12 +3332,12 @@ pub const Rolls = struct {
3281
3332
  };
3282
3333
 
3283
3334
  assert(duration >= 1 and duration <= 6);
3284
- options.chance.duration(.sleep, player, player.foe(), duration);
3335
+ options.chance.duration(player, duration);
3285
3336
  return duration + 1;
3286
3337
  }
3287
3338
 
3288
3339
  fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
3289
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
3340
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3290
3341
  @intCast(val)
3291
3342
  else if (showdown)
3292
3343
  // Pokémon Showdown incorrectly uses the same duration for self-confusion
@@ -3295,16 +3346,16 @@ pub const Rolls = struct {
3295
3346
  @intCast((battle.rng.next() & if (self) 1 else 3) + 2);
3296
3347
 
3297
3348
  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);
3349
+ options.chance.duration(player, duration);
3299
3350
  return duration;
3300
3351
  }
3301
3352
 
3302
3353
  fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
3303
- const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
3354
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3304
3355
  @intCast(val)
3305
3356
  else if (showdown)
3306
3357
  // Pokémon Showdown incorrectly inherits Generation III's Disable duration
3307
- battle.rng.range(u4, 2, 6)
3358
+ battle.rng.range(u3, 2, 6)
3308
3359
  else duration: {
3309
3360
  while (true) {
3310
3361
  const r = battle.rng.next() & 7;
@@ -3313,12 +3364,12 @@ pub const Rolls = struct {
3313
3364
  };
3314
3365
 
3315
3366
  assert(duration >= 2 and duration <= (if (showdown) 5 else 7));
3316
- options.chance.duration(.disable, player, player.foe(), duration);
3367
+ options.chance.duration(player, duration);
3317
3368
  return duration;
3318
3369
  }
3319
3370
 
3320
3371
  fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
3321
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
3372
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3322
3373
  @intCast(val)
3323
3374
  else if (showdown)
3324
3375
  battle.rng.range(u3, 2, 4)
@@ -3326,12 +3377,12 @@ pub const Rolls = struct {
3326
3377
  @intCast((battle.rng.next() & 1) + 2); // BUG: thrash is + 1 not +2
3327
3378
 
3328
3379
  assert(duration >= 2 and duration <= 3);
3329
- options.chance.duration(.attacking, player, player, duration);
3380
+ options.chance.duration(player, duration);
3330
3381
  return duration;
3331
3382
  }
3332
3383
 
3333
3384
  fn bindingDuration(battle: anytype, player: Player, options: anytype) u3 {
3334
- const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
3385
+ const duration: u3 = (if (options.calc.overridden(player, 0, .duration)) |val|
3335
3386
  @intCast(val)
3336
3387
  else if (showdown)
3337
3388
  // TODO: PS does range(3, 6) here which after subtracting 1 is actually 2-4 not 2-5!
@@ -3340,13 +3391,13 @@ pub const Rolls = struct {
3340
3391
  @intCast(battle.rng.next() & 3 + 2));
3341
3392
 
3342
3393
  assert(duration >= 2 and duration <= 5);
3343
- options.chance.duration(.binding, player, player.foe(), duration);
3394
+ options.chance.duration(player, duration);
3344
3395
  return duration + 1;
3345
3396
  }
3346
3397
 
3347
3398
  // TODO: consider sharing implementation with bindingDuration
3348
3399
  fn encoreDuration(battle: anytype, player: Player, options: anytype) u3 {
3349
- const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
3400
+ const duration: u3 = (if (options.calc.overridden(player, 0, .duration)) |val|
3350
3401
  @intCast(val)
3351
3402
  else if (showdown)
3352
3403
  battle.rng.range(u3, 2, 6)
@@ -3354,7 +3405,7 @@ pub const Rolls = struct {
3354
3405
  @intCast(battle.rng.next() & 3 + 2));
3355
3406
 
3356
3407
  assert(duration >= 2 and duration <= 5);
3357
- options.chance.duration(.encore, player, player.foe(), duration);
3408
+ options.chance.duration(player, duration);
3358
3409
  return duration + 1;
3359
3410
  }
3360
3411
  };
@@ -3467,7 +3518,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3467
3518
  .Move => {
3468
3519
  const side = battle.side(player);
3469
3520
 
3470
- var active = &side.active;
3521
+ const active = &side.active;
3471
3522
  const stored = side.stored();
3472
3523
 
3473
3524
  // While players are not given any input options on the cartridge in these cases,