@pkmn/engine 0.1.0-dev.bb8e286d → 0.1.0-dev.bce6e8a5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +46 -18
  3. package/build/pkg/addon/browser.d.ts +3 -0
  4. package/build/pkg/addon/browser.js +47 -0
  5. package/build/pkg/addon/browser.js.map +1 -0
  6. package/build/pkg/addon/node.d.ts +3 -0
  7. package/build/pkg/addon/node.js +110 -0
  8. package/build/pkg/addon/node.js.map +1 -0
  9. package/build/pkg/addon/wasm.d.ts +2 -0
  10. package/build/pkg/addon/wasm.js +53 -0
  11. package/build/pkg/addon/wasm.js.map +1 -0
  12. package/build/pkg/addon.d.ts +25 -3
  13. package/build/pkg/addon.js +39 -71
  14. package/build/pkg/addon.js.map +1 -1
  15. package/build/pkg/data/layout.json +15 -2
  16. package/build/pkg/data/protocol.json +23 -9
  17. package/build/pkg/data.d.ts +27 -6
  18. package/build/pkg/data.js.map +1 -1
  19. package/build/pkg/gen1.d.ts +15 -18
  20. package/build/pkg/gen1.js +28 -19
  21. package/build/pkg/gen1.js.map +1 -1
  22. package/build/pkg/index.d.ts +40 -90
  23. package/build/pkg/index.js +23 -70
  24. package/build/pkg/index.js.map +1 -1
  25. package/build/pkg/protocol.d.ts +20 -1
  26. package/build/pkg/protocol.js +109 -107
  27. package/build/pkg/protocol.js.map +1 -1
  28. package/build/tools/debug.js +53 -78
  29. package/build/tools/debug.js.map +1 -1
  30. package/build/tools/display/debug.jsx +152 -0
  31. package/build/tools/display/debug.jsx.map +1 -0
  32. package/build/tools/display/index.js +152 -0
  33. package/build/tools/display/index.js.map +1 -0
  34. package/build/tools/display/ui.jsx +359 -0
  35. package/build/tools/display/ui.jsx.map +1 -0
  36. package/build/tools/display/util.js +33 -0
  37. package/build/tools/display/util.js.map +1 -0
  38. package/build.zig +330 -104
  39. package/package.json +59 -133
  40. package/src/bin/install-pkmn-engine +32 -21
  41. package/src/bin/pkmn-debug +1 -1
  42. package/src/lib/bindings/c.zig +255 -0
  43. package/src/lib/{binding → bindings}/node.zig +2 -11
  44. package/src/lib/bindings/wasm.zig +59 -0
  45. package/src/lib/common/array.zig +77 -0
  46. package/src/lib/common/data.zig +4 -4
  47. package/src/lib/common/debug.zig +39 -8
  48. package/src/lib/common/js.zig +4 -2
  49. package/src/lib/common/optional.zig +14 -10
  50. package/src/lib/common/options.zig +6 -0
  51. package/src/lib/common/protocol.zig +60 -64
  52. package/src/lib/common/rational.zig +121 -29
  53. package/src/lib/common/rng.zig +7 -9
  54. package/src/lib/common/util.zig +50 -1
  55. package/src/lib/gen1/calc.zig +683 -156
  56. package/src/lib/gen1/chance.zig +405 -280
  57. package/src/lib/gen1/data/moves.zig +2 -4
  58. package/src/lib/gen1/data/species.zig +2 -4
  59. package/src/lib/gen1/data/types.zig +1 -2
  60. package/src/lib/gen1/data.zig +24 -27
  61. package/src/lib/gen1/helpers.zig +92 -548
  62. package/src/lib/gen1/mechanics.zig +365 -241
  63. package/src/lib/gen2/calc.zig +37 -27
  64. package/src/lib/gen2/chance.zig +84 -496
  65. package/src/lib/gen2/data/items.zig +1 -3
  66. package/src/lib/gen2/data/moves.zig +2 -4
  67. package/src/lib/gen2/data/species.zig +1 -3
  68. package/src/lib/gen2/data/types.zig +1 -2
  69. package/src/lib/gen2/data.zig +18 -21
  70. package/src/lib/gen2/generated.zig +18 -23
  71. package/src/lib/gen2/helpers.zig +46 -25
  72. package/src/lib/gen2/mechanics.zig +198 -140
  73. package/src/lib/node.zig +18 -0
  74. package/src/lib/pkmn.zig +37 -15
  75. package/src/lib/wasm.zig +17 -0
  76. package/src/tools/display/dom.ts +93 -0
  77. package/src/tools/display/ui.css +354 -0
  78. package/build/test/display.js +0 -400
  79. package/build/test/display.js.map +0 -1
  80. package/src/lib/binding/wasm.zig +0 -12
  81. package/src/lib/gen1/README.md +0 -894
  82. package/src/lib/gen2/README.md +0 -329
  83. package/src/test/display/pkmn.html.tmpl +0 -368
@@ -1,50 +1,37 @@
1
- const std = @import("std");
2
-
3
- const pkmn = @import("../pkmn.zig");
4
-
5
- const common = @import("../common/data.zig");
6
1
  const DEBUG = @import("../common/debug.zig").print;
7
- const protocol = @import("../common/protocol.zig");
8
- const rng = @import("../common/rng.zig");
9
- const util = @import("../common/util.zig");
10
2
 
3
+ const builtin = @import("builtin");
4
+ const common = @import("../common/data.zig");
11
5
  const data = @import("data.zig");
12
6
  const generated = @import("generated.zig");
13
-
14
- const assert = std.debug.assert;
15
-
16
- const expectEqual = std.testing.expectEqual;
17
- const expectEqualSlices = std.testing.expectEqualSlices;
18
- const expectEqualStrings = std.testing.expectEqualStrings;
19
-
20
- const showdown = pkmn.options.showdown;
21
-
22
- const Choice = common.Choice;
23
- const ID = common.ID;
24
- const Player = common.Player;
25
- const Result = common.Result;
7
+ const pkmn = @import("../pkmn.zig");
8
+ const protocol = @import("../common/protocol.zig");
9
+ const rng = @import("../common/rng.zig");
10
+ const std = @import("std");
26
11
 
27
12
  const Activate = protocol.Activate;
13
+ const ActivePokemon = data.ActivePokemon;
14
+ const assert = std.debug.assert;
28
15
  const Boost = protocol.Boost;
16
+ const Boosts = data.Boosts;
17
+ const Choice = common.Choice;
29
18
  const Damage = protocol.Damage;
19
+ const Effectiveness = data.Effectiveness;
30
20
  const End = protocol.End;
21
+ const expectEqual = std.testing.expectEqual;
22
+ const expectEqualSlices = std.testing.expectEqualSlices;
23
+ const expectEqualStrings = std.testing.expectEqualStrings;
31
24
  const Fail = protocol.Fail;
32
- const Heal = protocol.Heal;
33
- const Start = protocol.Start;
34
-
35
25
  const Gen12 = rng.Gen12;
36
-
37
- const PointerType = util.PointerType;
38
-
39
- const ActivePokemon = data.ActivePokemon;
40
- const Boosts = data.Boosts;
41
- const Effectiveness = data.Effectiveness;
26
+ const Heal = protocol.Heal;
27
+ const ID = common.ID;
42
28
  const Item = data.Item;
43
29
  const Move = data.Move;
44
- const MoveSlot = data.MoveSlot;
45
- const Pokemon = data.Pokemon;
30
+ const Player = common.Player;
31
+ const Result = common.Result;
32
+ const showdown = pkmn.options.showdown;
46
33
  const Side = data.Side;
47
- const Species = data.Species;
34
+ const Start = protocol.Start;
48
35
  const Stats = data.Stats;
49
36
  const Status = data.Status;
50
37
  const TriAttack = data.TriAttack;
@@ -106,6 +93,7 @@ pub const State = struct {
106
93
  type: Type, // BATTLE_VARS_MOVE_TYPE
107
94
  accuracy: u8, // wEnemyMoveStruct + MOVE_ACC
108
95
  move: Move, // wCurPlayerMove
96
+ hit: u3 = 0,
109
97
  from: Move = .None,
110
98
  mslot: u4, // wCurMoveNum
111
99
  first: bool, // wEnemyGoesFirst
@@ -256,7 +244,6 @@ fn switchIn(battle: anytype, player: Player, slot: u8, kind: SwitchIn, options:
256
244
  } else {
257
245
  try options.log.switched(.{ battle.active(player), incoming });
258
246
  }
259
- options.chance.switched(player, side.pokemon[0].position, out);
260
247
 
261
248
  if (side.conditions.Spikes and !active.types.includes(.Flying)) {
262
249
  incoming.hp -|= @max(incoming.stats.hp / 8, 1);
@@ -442,18 +429,9 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
442
429
  if (Status.is(stored.status, .SLP)) slp: {
443
430
  const before = stored.status;
444
431
  // 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
432
  stored.status -= 1;
449
- // }
450
433
 
451
- const duration = Status.duration(stored.status);
452
- if (!Status.is(stored.status, .EXT)) {
453
- try options.chance.sleep(player, duration);
454
- }
455
-
456
- if (duration == 0) {
434
+ if (Status.duration(stored.status) == 0) {
457
435
  try log.curestatus(.{ ident, before, .Message });
458
436
  stored.status = 0; // clears EXT if present
459
437
  resetCant(volatiles);
@@ -483,12 +461,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
483
461
  }
484
462
 
485
463
  if (volatiles.disable.duration > 0) {
486
- // if (options.calc.modify(player, .disable)) |extend| {
487
- // if (!extend) volatiles.disable.duration = 0;
488
- // } else {
489
464
  volatiles.disable.duration -= 1;
490
- // }
491
- try options.chance.disable(player, volatiles.disable.duration);
492
465
 
493
466
  if (volatiles.disable.duration == 0) {
494
467
  volatiles.disable.move = 0;
@@ -498,13 +471,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
498
471
 
499
472
  if (volatiles.Confusion) {
500
473
  assert(volatiles.confusion > 0);
501
-
502
- // if (options.calc.modify(player, .confusion)) |extend| {
503
- // if (!extend) volatiles.confusion = 0;
504
- // } else {
505
474
  volatiles.confusion -= 1;
506
- // }
507
- try options.chance.confusion(player, volatiles.confusion);
508
475
 
509
476
  if (volatiles.confusion == 0) {
510
477
  volatiles.Confusion = false;
@@ -665,7 +632,7 @@ pub fn checkCriticalHit(battle: anytype, player: Player, state: *State, options:
665
632
  }
666
633
  assert(stage <= 4);
667
634
 
668
- state.crit = try Rolls.criticalHit(battle, player, CRITICAL_HITS[stage], options);
635
+ state.crit = try Rolls.criticalHit(battle, player, state.hit, CRITICAL_HITS[stage], options);
669
636
  }
670
637
 
671
638
  pub fn calcDamage(
@@ -733,7 +700,7 @@ pub fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype)
733
700
 
734
701
  pub fn randomizeDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
735
702
  if (state.damage <= 1) return;
736
- const random = try Rolls.damage(battle, player, options);
703
+ const random = try Rolls.damage(battle, player, state.hit, options);
737
704
  options.calc.base(player.foe(), state.damage);
738
705
  state.damage = @intCast(@as(u32, state.damage) *% random / 255);
739
706
  options.calc.final(player.foe(), state.damage);
@@ -1226,7 +1193,7 @@ fn updateSideCondition(
1226
1193
  fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result {
1227
1194
  battle.turn += 1;
1228
1195
 
1229
- if (showdown and battle.turn >= 1000) {
1196
+ if (pkmn.options.mod and battle.turn >= 1000) {
1230
1197
  try options.log.tie(.{});
1231
1198
  return Result.Tie;
1232
1199
  } else if (battle.turn >= 65535) {
@@ -1304,14 +1271,32 @@ pub const Effects = struct {
1304
1271
  }
1305
1272
 
1306
1273
  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
- //
1274
+ assert(!state.miss);
1275
+ var side = battle.side(player);
1276
+
1277
+ const ident = battle.active(player);
1278
+
1279
+ assert(side.active.boosts.atk >= -6 and side.active.boosts.atk <= 6);
1280
+ if (side.active.boosts.atk == 6 or side.active.boosts.atk == MAX_STAT_VALUE) {
1281
+ return options.log.fail(.{ ident, .None });
1282
+ }
1283
+
1284
+ const hp = @max(side.stored().stats.hp / 2, 1);
1285
+ if (side.stored().hp <= hp) {
1286
+ try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
1287
+ try options.log.boost(.{ ident, .Attack, @as(i8, 2), .Silent });
1288
+ return options.log.fail(.{ ident, .None });
1289
+ } else {
1290
+ side.stored().hp -= hp;
1291
+ try options.log.damage(.{ ident, side.stored(), .None });
1292
+
1293
+ const before = side.active.boosts.atk;
1294
+ var i: usize = 0;
1295
+ while (i < 6 and side.active.stats.atk < MAX_STAT_VALUE) : (i += 1) {
1296
+ try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
1297
+ }
1298
+ try options.log.boost(.{ ident, .Attack, @as(i8, side.active.boosts.atk - before) });
1299
+ }
1315
1300
  }
1316
1301
 
1317
1302
  pub const bide = struct {
@@ -1323,13 +1308,7 @@ pub const Effects = struct {
1323
1308
 
1324
1309
  if (!volatiles.Bide) return;
1325
1310
 
1326
- // if (options.calc.modify(player, .attacking)) |extend| {
1327
- // if (!extend) volatiles.attacks = 0;
1328
- // } else {
1329
1311
  volatiles.attacks -= 1;
1330
- // }
1331
- try options.chance.attacking(player, volatiles.attacks);
1332
-
1333
1312
  if (volatiles.attacks != 0) return options.log.activate(.{ ident, .Bide });
1334
1313
 
1335
1314
  volatiles.Bide = false;
@@ -1448,7 +1427,8 @@ pub const Effects = struct {
1448
1427
  if (move.type.special() != (last == .MirrorCoat)) return;
1449
1428
  if (state.damage == 0) return;
1450
1429
 
1451
- state.damage *|= 2;
1430
+ // NOTE: llvm/llvm-project#58557
1431
+ state.damage = smul(state.damage, 2);
1452
1432
  state.miss = false;
1453
1433
  }
1454
1434
 
@@ -1543,15 +1523,18 @@ pub const Effects = struct {
1543
1523
 
1544
1524
  pub const double = struct {
1545
1525
  pub fn flying(battle: anytype, player: Player, state: *State, _: anytype) !void {
1546
- if (battle.foe(player).active.volatiles.Flying) state.damage *|= 2;
1526
+ if (!battle.foe(player).active.volatiles.Flying) return;
1527
+ state.damage = smul(state.damage, 2);
1547
1528
  }
1548
1529
 
1549
1530
  pub fn underground(battle: anytype, player: Player, state: *State, _: anytype) !void {
1550
- if (battle.foe(player).active.volatiles.Underground) state.damage *|= 2;
1531
+ if (!battle.foe(player).active.volatiles.Underground) return;
1532
+ state.damage = smul(state.damage, 2);
1551
1533
  }
1552
1534
 
1553
1535
  pub fn minimize(battle: anytype, player: Player, state: *State, _: anytype) !void {
1554
- if (battle.foe(player).active.volatiles.minimized) state.damage *|= 2;
1536
+ if (!battle.foe(player).active.volatiles.minimized) return;
1537
+ state.damage = smul(state.damage, 2);
1555
1538
  }
1556
1539
  };
1557
1540
 
@@ -1596,8 +1579,16 @@ pub const Effects = struct {
1596
1579
  try options.log.start(.{ foe_ident, .Encore, move.id });
1597
1580
  }
1598
1581
 
1599
- pub fn explode(battle: anytype, player: Player, state: *State, options: anytype) !void {
1600
- _ = .{ battle, player, state, options }; // TODO
1582
+ pub fn explode(battle: anytype, player: Player, _: *State, _: anytype) !void {
1583
+ var side = battle.side(player);
1584
+ var stored = side.stored();
1585
+
1586
+ stored.hp = 0;
1587
+ // Pokémon Showdown sets the status to 0 on faint(), and we need to do the same to be able
1588
+ // to correctly implement Pokémon Showdown's dumb speed-based switch mechanics
1589
+ if (!showdown) stored.status = 0;
1590
+ side.active.volatiles.LeechSeed = false;
1591
+ side.active.volatiles.DestinyBond = false;
1601
1592
  }
1602
1593
 
1603
1594
  pub fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
@@ -1712,7 +1703,7 @@ pub const Effects = struct {
1712
1703
  if (!state.proc) return;
1713
1704
  if (foe.conditions.Safeguard) return;
1714
1705
  // Freeze Clause Mod
1715
- if (showdown) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
1706
+ if (pkmn.options.mod) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
1716
1707
 
1717
1708
  foe_stored.status = Status.init(.FRZ);
1718
1709
  try options.log.status(.{ foe_ident, foe_stored.status, .None });
@@ -1723,8 +1714,26 @@ pub const Effects = struct {
1723
1714
  foe.active.volatiles.frozen = true;
1724
1715
  }
1725
1716
 
1726
- pub fn furyCutter(battle: anytype, player: Player, state: *State, options: anytype) !void {
1727
- _ = .{ battle, player, state, options }; // TODO
1717
+ // TODO FIXME showdown calls this before calcDamage (after checkHit) and modifies state.bp!
1718
+ pub fn furyCutter(battle: anytype, player: Player, state: *State, _: anytype) !void {
1719
+ var side = battle.side(player);
1720
+ var volatiles = &side.active.volatiles;
1721
+
1722
+ if (state.miss) {
1723
+ volatiles.fury_cutter = 0;
1724
+ return;
1725
+ }
1726
+
1727
+ assert(volatiles.fury_cutter >= 0 and volatiles.fury_cutter <= 5);
1728
+ if (volatiles.fury_cutter == 5) return;
1729
+ volatiles.fury_cutter += 1;
1730
+
1731
+ if (showdown) {
1732
+ state.bp *= 2;
1733
+ assert(state.bp <= 160);
1734
+ } else {
1735
+ state.damage = smul(state.damage, 2);
1736
+ }
1728
1737
  }
1729
1738
 
1730
1739
  pub fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -1885,7 +1894,29 @@ pub const Effects = struct {
1885
1894
  }
1886
1895
 
1887
1896
  pub fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
1888
- _ = .{ battle, player, state, options }; // TODO
1897
+ var side = battle.side(player);
1898
+
1899
+ if (state.miss) {
1900
+ if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
1901
+ try options.log.lastmiss(.{});
1902
+ return options.log.miss(.{battle.active(player)});
1903
+ }
1904
+
1905
+ const move = battle.foe(player).lastMove(false);
1906
+ if (move == .None or move == .Struggle) {
1907
+ return options.log.fail(.{ battle.active(player.foe()), .None });
1908
+ }
1909
+
1910
+ var slot: u4 = 0;
1911
+ for (side.active.moves, 0..) |m, i| {
1912
+ if (m.id == move) return options.log.fail(.{ battle.active(player.foe()), .None });
1913
+ if (m.id == .Mimic) slot = @intCast(i + 1);
1914
+ }
1915
+ assert(slot != 0 and slot == state.mslot);
1916
+
1917
+ side.active.move(slot).id = move;
1918
+ side.active.move(slot).pp = 5;
1919
+ try options.log.start(.{ battle.active(player), .Mimic, move });
1889
1920
  }
1890
1921
 
1891
1922
  pub fn mirrorMove(
@@ -2172,7 +2203,7 @@ pub const Effects = struct {
2172
2203
 
2173
2204
  pub fn pursuit(battle: anytype, player: Player, state: *State, _: anytype) !void {
2174
2205
  // TODO: remove switching variable and just look at Choice saved in Battle struct?
2175
- if (battle.foe(player).active.volatiles.switching) state.damage *|= 2;
2206
+ if (battle.foe(player).active.volatiles.switching) state.damage = smul(state.damage, 2);
2176
2207
  }
2177
2208
 
2178
2209
  pub const rage = struct {
@@ -2185,7 +2216,7 @@ pub const Effects = struct {
2185
2216
  const side = battle.side(player);
2186
2217
 
2187
2218
  assert(side.active.volatiles.Rage);
2188
- state.damage *|= (side.active.volatiles.rage +| 1);
2219
+ state.damage = smul(state.damage, (side.active.volatiles.rage +| 1)); // TODO: |+
2189
2220
  }
2190
2221
  };
2191
2222
 
@@ -2280,7 +2311,7 @@ pub const Effects = struct {
2280
2311
  side.active.volatiles.attacks += 1;
2281
2312
  side.active.volatiles.Rollout = side.active.volatiles.attacks != 5;
2282
2313
 
2283
- if (side.active.volatiles.DefenseCurl) state.damage *|= 2;
2314
+ if (side.active.volatiles.DefenseCurl) state.damage = smul(state.damage, 2);
2284
2315
  }
2285
2316
  };
2286
2317
 
@@ -2350,7 +2381,7 @@ pub const Effects = struct {
2350
2381
  }
2351
2382
 
2352
2383
  // Sleep Clause Mod
2353
- if (showdown) {
2384
+ if (pkmn.options.mod) {
2354
2385
  for (foe.pokemon) |p| {
2355
2386
  if (Status.is(p.status, .SLP) and !Status.is(p.status, .EXT)) return;
2356
2387
  }
@@ -2393,17 +2424,37 @@ pub const Effects = struct {
2393
2424
  }
2394
2425
 
2395
2426
  pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
2396
- _ = .{ battle, player, state, options }; // TODO
2397
- // var foe = battle.foe(player);
2427
+ var foe = battle.foe(player);
2428
+
2429
+ const last = foe.lastMove(false);
2430
+ if (state.miss or last == .None or last == .Struggle) {
2431
+ return options.log.fail(.{ battle.active(player.foe()), .None });
2432
+ }
2398
2433
 
2399
- // if (state.miss) return options.log.fail(.{ battle.active(player), .None });
2434
+ var slot: u4 = 0;
2435
+ for (foe.active.moves, 0..) |m, i| {
2436
+ if (m.id == last) slot = @intCast(i + 1);
2437
+ }
2438
+ // If the move was called via Metronome it might not exist
2439
+ if (slot == 0 or foe.active.move(slot).pp == 0) {
2440
+ return options.log.fail(.{ battle.active(player.foe()), .None });
2441
+ }
2400
2442
 
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
- // }
2443
+ // Pokémon Showdown always deducts 4 instead of randomizing
2444
+ const pp = @min(
2445
+ foe.active.move(slot).pp,
2446
+ if (showdown) 4 else try Rolls.spite(battle, player, options),
2447
+ );
2448
+ foe.active.move(slot).pp -= pp;
2449
+ if (!foe.active.volatiles.Transform) {
2450
+ var move_slot = foe.stored().move(slot);
2451
+ assert(move_slot.pp > 0);
2452
+
2453
+ move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
2454
+ assert(foe.active.move(slot).pp == foe.stored().move(slot).pp);
2455
+ }
2456
+
2457
+ try options.log.activate(.{ battle.active(player.foe()), .Spite, last, pp });
2407
2458
  }
2408
2459
 
2409
2460
  pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
@@ -2788,9 +2839,15 @@ fn convertible(active: ActivePokemon, m: Move) bool {
2788
2839
  return t != .@"???" and !active.types.includes(t);
2789
2840
  }
2790
2841
 
2842
+ // NOTE: llvm/llvm-project#58557
2843
+ fn smul(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
2844
+ if (!comptime builtin.target.isWasm()) return a *| b;
2845
+ return @max(a *% b, std.math.maxInt(@TypeOf(a)));
2846
+ }
2847
+
2791
2848
  pub const Rolls = struct {
2792
2849
  fn speedTie(battle: anytype, options: anytype) !bool {
2793
- const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
2850
+ const p1 = if (options.calc.overridden(.P1, 0, .speed_tie)) |player|
2794
2851
  player == .P1
2795
2852
  else if (showdown)
2796
2853
  battle.rng.range(u8, 0, 2) == 0
@@ -2802,7 +2859,7 @@ pub const Rolls = struct {
2802
2859
  }
2803
2860
 
2804
2861
  fn quickClaw(battle: anytype, player: Player, options: anytype) !bool {
2805
- const qkc = if (options.calc.overridden(player, .quick_claw)) |val|
2862
+ const qkc = if (options.calc.overridden(player, 0, .quick_claw)) |val|
2806
2863
  val == .true
2807
2864
  else if (showdown)
2808
2865
  battle.rng.chance(u8, 60, 256)
@@ -2814,7 +2871,7 @@ pub const Rolls = struct {
2814
2871
  }
2815
2872
 
2816
2873
  fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) !bool {
2817
- const ok = if (options.calc.overridden(player, .hit)) |val|
2874
+ const ok = if (options.calc.overridden(player, 0, .hit)) |val|
2818
2875
  val == .true
2819
2876
  else if (showdown)
2820
2877
  battle.rng.chance(u8, accuracy, 256)
@@ -2825,20 +2882,22 @@ pub const Rolls = struct {
2825
2882
  return ok;
2826
2883
  }
2827
2884
 
2828
- fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
2829
- const crit = if (options.calc.overridden(player, .critical_hit)) |val|
2885
+ fn criticalHit(battle: anytype, player: Player, i: u3, rate: u8, options: anytype) !bool {
2886
+ _ = .{ player, options };
2887
+ const crit = if (options.calc.overridden(player, i, .critical_hits)) |val|
2830
2888
  val == .true
2831
2889
  else if (showdown)
2832
2890
  battle.rng.chance(u8, rate, 256)
2833
2891
  else
2834
2892
  battle.rng.next() < rate;
2835
2893
 
2836
- try options.chance.criticalHit(player, crit, rate);
2894
+ try options.chance.criticalHit(player, i, crit, rate);
2837
2895
  return crit;
2838
2896
  }
2839
2897
 
2840
- fn damage(battle: anytype, player: Player, options: anytype) !u8 {
2841
- const roll = if (options.calc.overridden(player, .damage)) |val|
2898
+ fn damage(battle: anytype, player: Player, i: u3, options: anytype) !u8 {
2899
+ _ = .{ player, options };
2900
+ const roll = if (options.calc.overridden(player, i, .damages)) |val|
2842
2901
  val
2843
2902
  else roll: {
2844
2903
  if (showdown) break :roll battle.rng.range(u8, 217, 256);
@@ -2849,12 +2908,12 @@ pub const Rolls = struct {
2849
2908
  };
2850
2909
 
2851
2910
  assert(roll >= 217 and roll <= 255);
2852
- try options.chance.damage(player, roll);
2911
+ try options.chance.damage(player, i, roll);
2853
2912
  return roll;
2854
2913
  }
2855
2914
 
2856
2915
  fn confused(battle: anytype, player: Player, options: anytype) !bool {
2857
- const cfz = if (options.calc.overridden(player, .confused)) |val|
2916
+ const cfz = if (options.calc.overridden(player, 0, .confused)) |val|
2858
2917
  val == .true
2859
2918
  else if (showdown)
2860
2919
  !battle.rng.chance(u8, 128, 256)
@@ -2866,7 +2925,7 @@ pub const Rolls = struct {
2866
2925
  }
2867
2926
 
2868
2927
  fn attract(battle: anytype, player: Player, options: anytype) !bool {
2869
- const cant = if (options.calc.overridden(player, .attract)) |val|
2928
+ const cant = if (options.calc.overridden(player, 0, .attract)) |val|
2870
2929
  val == .true
2871
2930
  else if (showdown)
2872
2931
  battle.rng.chance(u8, 1, 2)
@@ -2878,7 +2937,7 @@ pub const Rolls = struct {
2878
2937
  }
2879
2938
 
2880
2939
  fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
2881
- const par = if (options.calc.overridden(player, .paralyzed)) |val|
2940
+ const par = if (options.calc.overridden(player, 0, .paralyzed)) |val|
2882
2941
  val == .true
2883
2942
  else if (showdown)
2884
2943
  battle.rng.chance(u8, 63, 256)
@@ -2890,7 +2949,7 @@ pub const Rolls = struct {
2890
2949
  }
2891
2950
 
2892
2951
  fn defrost(battle: anytype, player: Player, options: anytype) !bool {
2893
- const thaw = if (options.calc.overridden(player, .defrost)) |val|
2952
+ const thaw = if (options.calc.overridden(player, 0, .defrost)) |val|
2894
2953
  val == .true
2895
2954
  else if (showdown)
2896
2955
  battle.rng.chance(u8, 25, 256)
@@ -2903,7 +2962,7 @@ pub const Rolls = struct {
2903
2962
 
2904
2963
  fn secondaryChance(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
2905
2964
  // GLITCH: 100% secondary chance will still fail 1/256 of the time
2906
- const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
2965
+ const proc = if (options.calc.overridden(player, 0, .secondary_chance)) |val|
2907
2966
  val == .true
2908
2967
  else if (showdown)
2909
2968
  battle.rng.chance(u8, rate, 256)
@@ -2915,7 +2974,7 @@ pub const Rolls = struct {
2915
2974
  }
2916
2975
 
2917
2976
  fn item(battle: anytype, player: Player, options: anytype) !bool {
2918
- const proc = if (options.calc.overridden(player, .item)) |val|
2977
+ const proc = if (options.calc.overridden(player, 0, .item)) |val|
2919
2978
  val == .true
2920
2979
  else if (showdown)
2921
2980
  battle.rng.chance(u8, 30, 256)
@@ -2927,7 +2986,7 @@ pub const Rolls = struct {
2927
2986
  }
2928
2987
 
2929
2988
  fn triAttack(battle: anytype, player: Player, options: anytype) !TriAttack {
2930
- const status: TriAttack = if (options.calc.overridden(player, .tri_attack)) |val|
2989
+ const status: TriAttack = if (options.calc.overridden(player, 0, .tri_attack)) |val|
2931
2990
  @enumFromInt(@intFromEnum(val) - 1)
2932
2991
  else if (showdown)
2933
2992
  @enumFromInt(battle.rng.range(u2, 0, 2))
@@ -2943,7 +3002,7 @@ pub const Rolls = struct {
2943
3002
  }
2944
3003
 
2945
3004
  fn present(battle: anytype, player: Player, options: anytype) !u8 {
2946
- const power = if (options.calc.overridden(player, .present)) |val|
3005
+ const power = if (options.calc.overridden(player, 0, .present)) |val|
2947
3006
  (val - 1) * 40
2948
3007
  else if (showdown) power: {
2949
3008
  const r = battle.rng.range(u8, 0, 10);
@@ -2965,7 +3024,7 @@ pub const Rolls = struct {
2965
3024
  }
2966
3025
 
2967
3026
  fn magnitude(battle: anytype, player: Player, options: anytype) !u8 {
2968
- const num: u8 = if (options.calc.overridden(player, .magnitude)) |val|
3027
+ const num: u8 = if (options.calc.overridden(player, 0, .magnitude)) |val|
2969
3028
  val + 3
2970
3029
  else if (showdown) num: {
2971
3030
  const r = battle.rng.range(u8, 0, 100);
@@ -2993,7 +3052,7 @@ pub const Rolls = struct {
2993
3052
  }
2994
3053
 
2995
3054
  fn tripleKick(battle: anytype, player: Player, options: anytype) !u2 {
2996
- const hits = if (options.calc.overridden(player, .triple_kick)) |val|
3055
+ const hits = if (options.calc.overridden(player, 0, .triple_kick)) |val|
2997
3056
  val
2998
3057
  else if (showdown)
2999
3058
  battle.rng.range(u2, 1, 4)
@@ -3009,13 +3068,12 @@ pub const Rolls = struct {
3009
3068
  return hits;
3010
3069
  }
3011
3070
 
3012
- fn spite(battle: anytype, player: Player, options: anytype) !u8 {
3013
- const pp = if (options.calc.overridden(player, .spite)) |val|
3071
+ fn spite(battle: anytype, player: Player, options: anytype) !u3 {
3072
+ assert(!showdown);
3073
+ const pp: u3 = if (options.calc.overridden(player, 0, .spite)) |val|
3014
3074
  val + 1
3015
- else if (showdown)
3016
- battle.rng.range(u8, 2, 6)
3017
3075
  else
3018
- (battle.rng.next() & 3) + 2;
3076
+ @intCast((battle.rng.next() & 3) + 2);
3019
3077
 
3020
3078
  assert(pp >= 2 and pp <= 5);
3021
3079
  try options.chance.spite(player, pp);
@@ -3029,7 +3087,7 @@ pub const Rolls = struct {
3029
3087
  var i: u8 = 0;
3030
3088
  const neutral = @intFromEnum(Effectiveness.Neutral);
3031
3089
  assert(mtype != .@"???");
3032
- const ty: Type = if (options.calc.overridden(player, .conversion_2)) |val|
3090
+ const ty: Type = if (options.calc.overridden(player, 0, .conversion_2)) |val|
3033
3091
  @enumFromInt(@intFromEnum(val) - 1)
3034
3092
  else if (showdown) ty: {
3035
3093
  for (Type.SHOWDOWN) |t| {
@@ -3063,7 +3121,7 @@ pub const Rolls = struct {
3063
3121
  assert(count <= 8);
3064
3122
  const num = if (count == 8) 0 else @as(u8, 255) >> @intCast(count);
3065
3123
 
3066
- const ok = if (options.calc.overridden(player, .protect)) |val|
3124
+ const ok = if (options.calc.overridden(player, 0, .protect)) |val|
3067
3125
  val == .true
3068
3126
  else if (showdown)
3069
3127
  battle.rng.chance(u8, num, 255)
@@ -3085,7 +3143,7 @@ pub const Rolls = struct {
3085
3143
  const moves = active.moves;
3086
3144
 
3087
3145
  // TODO: consider throwing error instead of rerolling?
3088
- const overridden = if (options.calc.overridden(player, .move_slot)) |val|
3146
+ const overridden = if (options.calc.overridden(player, 0, .move_slot)) |val|
3089
3147
  if (convertible(active, moves[val - 1].id)) val else null
3090
3148
  else
3091
3149
  null;
@@ -3122,7 +3180,7 @@ pub const Rolls = struct {
3122
3180
  const moves = active.moves;
3123
3181
 
3124
3182
  // TODO: consider throwing error instead of rerolling?
3125
- const overridden = if (options.calc.overridden(player, .move_slot)) |val|
3183
+ const overridden = if (options.calc.overridden(player, 0, .move_slot)) |val|
3126
3184
  if (val != mslot and Move.get(moves[val - 1].id).extra.sleep_talk) val else null
3127
3185
  else
3128
3186
  null;
@@ -3166,7 +3224,7 @@ pub const Rolls = struct {
3166
3224
  assert(n > 0 and n <= 6);
3167
3225
 
3168
3226
  // TODO: consider throwing error instead of rerolling?
3169
- const overridden = if (options.calc.overridden(player, .force_switch)) |val| val: {
3227
+ const overridden = if (options.calc.overridden(player, 0, .force_switch)) |val| val: {
3170
3228
  for (slots) |slot| if (slot == val) break :val if (foe.get(slot).hp > 0) val else null;
3171
3229
  break :val null;
3172
3230
  } else null;
@@ -3192,7 +3250,7 @@ pub const Rolls = struct {
3192
3250
  const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
3193
3251
 
3194
3252
  fn multiHit(battle: anytype, player: Player, options: anytype) !u3 {
3195
- const n: u3 = if (options.calc.overridden(player, .multi_hit)) |val|
3253
+ const n: u3 = if (options.calc.overridden(player, 0, .multi_hit)) |val|
3196
3254
  @intCast(val)
3197
3255
  else if (showdown)
3198
3256
  DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
@@ -3207,7 +3265,7 @@ pub const Rolls = struct {
3207
3265
  }
3208
3266
 
3209
3267
  fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
3210
- const power = if (options.calc.overridden(player, .psywave)) |val|
3268
+ const power = if (options.calc.overridden(player, 0, .psywave)) |val|
3211
3269
  val
3212
3270
  else if (showdown)
3213
3271
  battle.rng.range(u8, 1, max)
@@ -3233,7 +3291,7 @@ pub const Rolls = struct {
3233
3291
  }
3234
3292
 
3235
3293
  // TODO: consider throwing error instead of rerolling?
3236
- const overridden = if (options.calc.overridden(player, .metronome)) |val| val: {
3294
+ const overridden = if (options.calc.overridden(player, 0, .metronome)) |val| val: {
3237
3295
  for (moves) |m| if (m.id == val) break :val null;
3238
3296
  break :val val;
3239
3297
  } else null;
@@ -3265,7 +3323,7 @@ pub const Rolls = struct {
3265
3323
  }
3266
3324
 
3267
3325
  fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
3268
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
3326
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3269
3327
  @intCast(val)
3270
3328
  else if (showdown)
3271
3329
  battle.rng.range(u3, 1, 7)
@@ -3277,12 +3335,12 @@ pub const Rolls = struct {
3277
3335
  };
3278
3336
 
3279
3337
  assert(duration >= 1 and duration <= 6);
3280
- options.chance.duration(.sleep, player, player.foe(), duration);
3338
+ options.chance.duration(player, duration);
3281
3339
  return duration + 1;
3282
3340
  }
3283
3341
 
3284
3342
  fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
3285
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
3343
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3286
3344
  @intCast(val)
3287
3345
  else if (showdown)
3288
3346
  // Pokémon Showdown incorrectly uses the same duration for self-confusion
@@ -3291,16 +3349,16 @@ pub const Rolls = struct {
3291
3349
  @intCast((battle.rng.next() & if (self) 1 else 3) + 2);
3292
3350
 
3293
3351
  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);
3352
+ options.chance.duration(player, duration);
3295
3353
  return duration;
3296
3354
  }
3297
3355
 
3298
3356
  fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
3299
- const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
3357
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3300
3358
  @intCast(val)
3301
3359
  else if (showdown)
3302
3360
  // Pokémon Showdown incorrectly inherits Generation III's Disable duration
3303
- battle.rng.range(u4, 2, 6)
3361
+ battle.rng.range(u3, 2, 6)
3304
3362
  else duration: {
3305
3363
  while (true) {
3306
3364
  const r = battle.rng.next() & 7;
@@ -3309,12 +3367,12 @@ pub const Rolls = struct {
3309
3367
  };
3310
3368
 
3311
3369
  assert(duration >= 2 and duration <= (if (showdown) 5 else 7));
3312
- options.chance.duration(.disable, player, player.foe(), duration);
3370
+ options.chance.duration(player, duration);
3313
3371
  return duration;
3314
3372
  }
3315
3373
 
3316
3374
  fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
3317
- const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
3375
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3318
3376
  @intCast(val)
3319
3377
  else if (showdown)
3320
3378
  battle.rng.range(u3, 2, 4)
@@ -3322,12 +3380,12 @@ pub const Rolls = struct {
3322
3380
  @intCast((battle.rng.next() & 1) + 2); // BUG: thrash is + 1 not +2
3323
3381
 
3324
3382
  assert(duration >= 2 and duration <= 3);
3325
- options.chance.duration(.attacking, player, player, duration);
3383
+ options.chance.duration(player, duration);
3326
3384
  return duration;
3327
3385
  }
3328
3386
 
3329
3387
  fn bindingDuration(battle: anytype, player: Player, options: anytype) u3 {
3330
- const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
3388
+ const duration: u3 = (if (options.calc.overridden(player, 0, .duration)) |val|
3331
3389
  @intCast(val)
3332
3390
  else if (showdown)
3333
3391
  // TODO: PS does range(3, 6) here which after subtracting 1 is actually 2-4 not 2-5!
@@ -3336,13 +3394,13 @@ pub const Rolls = struct {
3336
3394
  @intCast(battle.rng.next() & 3 + 2));
3337
3395
 
3338
3396
  assert(duration >= 2 and duration <= 5);
3339
- options.chance.duration(.binding, player, player.foe(), duration);
3397
+ options.chance.duration(player, duration);
3340
3398
  return duration + 1;
3341
3399
  }
3342
3400
 
3343
3401
  // TODO: consider sharing implementation with bindingDuration
3344
3402
  fn encoreDuration(battle: anytype, player: Player, options: anytype) u3 {
3345
- const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
3403
+ const duration: u3 = (if (options.calc.overridden(player, 0, .duration)) |val|
3346
3404
  @intCast(val)
3347
3405
  else if (showdown)
3348
3406
  battle.rng.range(u3, 2, 6)
@@ -3350,7 +3408,7 @@ pub const Rolls = struct {
3350
3408
  @intCast(battle.rng.next() & 3 + 2));
3351
3409
 
3352
3410
  assert(duration >= 2 and duration <= 5);
3353
- options.chance.duration(.encore, player, player.foe(), duration);
3411
+ options.chance.duration(player, duration);
3354
3412
  return duration + 1;
3355
3413
  }
3356
3414
  };