@pkmn/engine 0.1.0-dev.0cabf16e → 0.1.0-dev.0f9e9648

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 (66) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +47 -4
  3. package/build/pkg/addon/browser.d.ts +1 -1
  4. package/build/pkg/addon/browser.js +6 -2
  5. package/build/pkg/addon/browser.js.map +1 -1
  6. package/build/pkg/addon/node.js +22 -8
  7. package/build/pkg/addon/node.js.map +1 -1
  8. package/build/pkg/addon/wasm.d.ts +2 -0
  9. package/build/pkg/addon/wasm.js +53 -0
  10. package/build/pkg/addon/wasm.js.map +1 -0
  11. package/build/pkg/addon.d.ts +24 -4
  12. package/build/pkg/addon.js +8 -17
  13. package/build/pkg/addon.js.map +1 -1
  14. package/build/pkg/data/layout.json +15 -2
  15. package/build/pkg/data/protocol.json +23 -9
  16. package/build/pkg/gen1.d.ts +15 -18
  17. package/build/pkg/gen1.js +28 -19
  18. package/build/pkg/gen1.js.map +1 -1
  19. package/build/pkg/index.d.ts +37 -100
  20. package/build/pkg/index.js +23 -75
  21. package/build/pkg/index.js.map +1 -1
  22. package/build/tools/debug.js +18 -9
  23. package/build/tools/debug.js.map +1 -1
  24. package/build/tools/display/debug.jsx +18 -7
  25. package/build/tools/display/debug.jsx.map +1 -1
  26. package/build/tools/display/index.js +35 -15
  27. package/build/tools/display/index.js.map +1 -1
  28. package/build/tools/display/ui.jsx +17 -7
  29. package/build/tools/display/ui.jsx.map +1 -1
  30. package/build/tools/display/util.js +16 -1
  31. package/build/tools/display/util.js.map +1 -1
  32. package/build.zig +196 -84
  33. package/package.json +29 -127
  34. package/src/bin/install-pkmn-engine +3 -2
  35. package/src/lib/bindings/c.zig +255 -0
  36. package/src/lib/bindings/node.zig +121 -0
  37. package/src/lib/bindings/wasm.zig +59 -0
  38. package/src/lib/common/array.zig +1 -1
  39. package/src/lib/common/optional.zig +1 -1
  40. package/src/lib/common/options.zig +2 -0
  41. package/src/lib/common/protocol.zig +4 -8
  42. package/src/lib/common/rational.zig +222 -147
  43. package/src/lib/common/rng.zig +1 -3
  44. package/src/lib/common/util.zig +17 -2
  45. package/src/lib/gen1/calc.zig +87 -97
  46. package/src/lib/gen1/chance.zig +34 -18
  47. package/src/lib/gen1/data/moves.zig +2 -4
  48. package/src/lib/gen1/data/species.zig +1 -3
  49. package/src/lib/gen1/data/types.zig +1 -2
  50. package/src/lib/gen1/data.zig +7 -11
  51. package/src/lib/gen1/helpers.zig +4 -11
  52. package/src/lib/gen1/mechanics.zig +66 -50
  53. package/src/lib/gen2/calc.zig +10 -18
  54. package/src/lib/gen2/data/items.zig +1 -3
  55. package/src/lib/gen2/data/moves.zig +2 -4
  56. package/src/lib/gen2/data/species.zig +1 -3
  57. package/src/lib/gen2/data/types.zig +1 -2
  58. package/src/lib/gen2/data.zig +11 -15
  59. package/src/lib/gen2/generated.zig +18 -23
  60. package/src/lib/gen2/helpers.zig +5 -12
  61. package/src/lib/gen2/mechanics.zig +41 -38
  62. package/src/lib/node.zig +3 -116
  63. package/src/lib/pkmn.zig +7 -2
  64. package/src/lib/wasm.zig +13 -9
  65. package/src/tools/display/dom.ts +5 -8
  66. package/src/tools/display/ui.css +0 -5
@@ -1,36 +1,29 @@
1
- const std = @import("std");
2
-
3
- const pkmn = @import("../pkmn.zig");
1
+ const DEBUG = @import("../common/debug.zig").print;
4
2
 
3
+ const builtin = @import("builtin");
4
+ const chance = @import("chance.zig");
5
5
  const common = @import("../common/data.zig");
6
- const DEBUG = @import("../common/debug.zig").print;
6
+ const data = @import("data.zig");
7
+ const pkmn = @import("../pkmn.zig");
7
8
  const protocol = @import("../common/protocol.zig");
8
9
  const rng = @import("../common/rng.zig");
9
-
10
- const data = @import("data.zig");
11
- const chance = @import("chance.zig");
10
+ const std = @import("std");
12
11
 
13
12
  const assert = std.debug.assert;
14
-
15
- const expectEqual = std.testing.expectEqual;
16
-
17
- const showdown = pkmn.options.showdown;
18
-
19
- const Choice = common.Choice;
20
- const ID = common.ID;
21
- const Player = common.Player;
22
- const Result = common.Result;
23
-
24
13
  const Boost = protocol.Boost;
14
+ const Choice = common.Choice;
25
15
  const Damage = protocol.Damage;
16
+ const Effectiveness = data.Effectiveness;
17
+ const expectEqual = std.testing.expectEqual;
26
18
  const Fail = protocol.Fail;
27
- const Heal = protocol.Heal;
28
-
29
19
  const Gen12 = rng.Gen12;
30
-
31
- const Effectiveness = data.Effectiveness;
20
+ const Heal = protocol.Heal;
21
+ const ID = common.ID;
32
22
  const Move = data.Move;
33
23
  const MoveSlot = data.MoveSlot;
24
+ const Player = common.Player;
25
+ const Result = common.Result;
26
+ const showdown = pkmn.options.showdown;
34
27
  const Side = data.Side;
35
28
  const Species = data.Species;
36
29
  const Stats = data.Stats;
@@ -173,7 +166,10 @@ fn selectMove(
173
166
  if (foe_choice.type == .Switch) {
174
167
  const last = side.active.move(battle.lastMove(player).index);
175
168
  if (last.id == .Metronome) side.last_selected_move = last.id;
176
- if (last.id == .MirrorMove) return Result.Error;
169
+ if (last.id == .MirrorMove) {
170
+ if (!pkmn.options.mod) return Result.Error;
171
+ side.last_selected_move = last.id;
172
+ }
177
173
  }
178
174
  }
179
175
  return null;
@@ -424,7 +420,7 @@ fn executeMove(
424
420
  mslot = @intCast(battle.lastMove(player).index);
425
421
  const stored = side.stored();
426
422
  // GLITCH: Struggle bypass PP underflow via Hyper Beam / Trapping-switch auto selection
427
- auto = isForced(&side.active) or
423
+ auto = pkmn.options.mod or isForced(&side.active) or
428
424
  side.active.volatiles.Binding or side.active.volatiles.Bide or
429
425
  side.last_selected_move == .HyperBeam or
430
426
  Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP);
@@ -457,6 +453,25 @@ fn executeMove(
457
453
  .err => return @as(?Result, Result.Error),
458
454
  }
459
455
 
456
+ // Pokémon Showdown incorrectly implements PP deduction when handling the Hyper Beam automatic
457
+ // selection glitch - if the move was proced due to Metronome / Mirror Move it either subtracts
458
+ // from the Hyper Beam slot if present or skips PP deduction entirely...
459
+ if (showdown and !skip_pp and side.last_selected_move == .HyperBeam and
460
+ side.last_selected_move != side.active.move(mslot).id)
461
+ {
462
+ assert(mslot == 1);
463
+ const has_beam = has_beam: {
464
+ for (side.active.moves, 0..) |m, i| {
465
+ if (m.id == .HyperBeam) {
466
+ mslot = @intCast(i + 1);
467
+ break :has_beam true;
468
+ }
469
+ }
470
+ break :has_beam false;
471
+ };
472
+ if (!has_beam) skip_pp = true;
473
+ }
474
+
460
475
  const can = skip_can or
461
476
  try canMove(battle, player, mslot, auto, skip_pp, .None, residual, options);
462
477
  if (!can) return null;
@@ -554,7 +569,7 @@ fn beforeMove(
554
569
  // This can only happen if a Pokémon started the battle frozen/sleeping and was thawed/woken
555
570
  // before the side had a selected a move - we simply need to assume this leads to a desync
556
571
  if (side.last_selected_move == .None) {
557
- assert(!showdown);
572
+ assert(!pkmn.options.mod);
558
573
  return .err;
559
574
  }
560
575
 
@@ -603,17 +618,10 @@ fn beforeMove(
603
618
  return .err;
604
619
  }
605
620
  }
606
- // Pokémon Showdown incorrectly changes the "target" of the confusion self-hit based
607
- // on the targeting behavior of the confused Pokémon's selected move which results
608
- // in the wrong behavior with respect to the Substitute + Confusion glitch
609
- const target = if (showdown and Move.get(side.last_selected_move).target == .Self)
610
- player
611
- else
612
- player.foe();
613
621
 
614
622
  const uncapped = battle.last_damage;
615
623
  // Skipping adjustDamage / randomizeDamage / checkHit
616
- _ = try applyDamage(battle, player, target, .Confusion, options);
624
+ _ = try applyDamage(battle, player, player.foe(), .Confusion, options);
617
625
  // Pokémon Showdown thinks that confusion damage is uncapped ¯\_(ツ)_/¯
618
626
  if (showdown) battle.last_damage = uncapped;
619
627
 
@@ -773,9 +781,14 @@ fn canMove(
773
781
 
774
782
  if (move.effect == .Thrashing) {
775
783
  Effects.thrashing(battle, player, options);
776
- } else if (!showdown and move.effect == .Binding) {
777
- // Pokémon Showdown handles this after hit/miss checks and damage calculation
778
- try Effects.binding(battle, player, false, options);
784
+ } else if (move.effect == .Binding) {
785
+ // Pokémon Showdown handles this after hit/miss checks and damage calculation, though clears
786
+ // Recharging in onTryMove (required for the Hyper Beam automatic selection glitch)
787
+ if (showdown) {
788
+ battle.foe(player).active.volatiles.Recharging = false;
789
+ } else {
790
+ try Effects.binding(battle, player, false, options);
791
+ }
779
792
  }
780
793
 
781
794
  return true;
@@ -1129,7 +1142,7 @@ fn calcDamage(
1129
1142
  if ((!showdown or !cfz) and (atk > 255 or def > 255)) {
1130
1143
  atk = @max((atk / 4) & 255, 1);
1131
1144
  // GLITCH: not adjusted to be a min of 1 on cartridge (can lead to division-by-zero freeze)
1132
- def = @max((def / 4) & 255, if (showdown) 1 else 0);
1145
+ def = @max((def / 4) & 255, if (pkmn.options.mod) 1 else 0);
1133
1146
  }
1134
1147
 
1135
1148
  const lvl: u32 = side.stored().level * @as(u2, if (crit) 2 else 1);
@@ -1150,7 +1163,7 @@ fn calcDamage(
1150
1163
  // encountered so we need to make sure that in the event of a miss we commit the status of the
1151
1164
  // crit roll. The alternative to doing this extra work is to *always* commit the crit roll but
1152
1165
  // that results in counter intuitive probabilities in the common case
1153
- if (pkmn.options.chance and !showdown) {
1166
+ if (pkmn.options.chance and !pkmn.options.mod and move.effect != .Explode) {
1154
1167
  atk = attack(side, !crit, special);
1155
1168
  def = defense(target, !crit, special, false);
1156
1169
  if ((atk > 255 or def > 255) and (def / 4) & 255 == 0) options.chance.glitch();
@@ -1235,13 +1248,13 @@ fn specialDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
1235
1248
  .Psywave => power: {
1236
1249
  const max: u8 = @intCast(@as(u16, side.stored().level) * 3 / 2);
1237
1250
  // GLITCH: Psywave infinite glitch loop
1238
- if (!showdown and max <= 1) return Result.Error;
1251
+ if (!pkmn.options.mod and max <= 1) return Result.Error;
1239
1252
  break :power try Rolls.psywave(battle, player, max, options);
1240
1253
  },
1241
1254
  else => unreachable,
1242
1255
  };
1243
1256
 
1244
- if (battle.last_damage == 0) return if (showdown) null else Result.Error;
1257
+ if (battle.last_damage == 0) return if (pkmn.options.mod) null else Result.Error;
1245
1258
 
1246
1259
  const sub = showdown and foe.active.volatiles.Substitute;
1247
1260
  _ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
@@ -1278,12 +1291,16 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
1278
1291
 
1279
1292
  if (!used or !selected) {
1280
1293
  // GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
1281
- if (!showdown) return Result.Error;
1294
+ if (!pkmn.options.mod) return Result.Error;
1282
1295
  try options.log.fail(.{ battle.active(player), .None });
1283
1296
  return null;
1284
1297
  }
1285
1298
 
1286
- battle.last_damage *|= 2;
1299
+ // NOTE: llvm/llvm-project#58557
1300
+ battle.last_damage = if (comptime builtin.target.cpu.arch.isWasm())
1301
+ @max(battle.last_damage *% 2, std.math.maxInt(u16))
1302
+ else
1303
+ battle.last_damage *| 2;
1287
1304
 
1288
1305
  // Pokémon Showdown calls moveHit before Counter
1289
1306
  if (!showdown and !try checkHit(battle, player, move, options)) return null;
@@ -1525,7 +1542,7 @@ fn faint(battle: anytype, player: Player, done: bool, cap: bool, options: anytyp
1525
1542
  assert(!foe_volatiles.MultiHit);
1526
1543
  if (foe_volatiles.Bide) {
1527
1544
  assert(!foe_volatiles.Thrashing and !foe_volatiles.Rage);
1528
- foe_volatiles.state = if (showdown) 0 else foe_volatiles.state & 255;
1545
+ foe_volatiles.state = if (pkmn.options.mod) 0 else foe_volatiles.state & 255;
1529
1546
  if (foe_volatiles.state != 0) return Result.Error;
1530
1547
  }
1531
1548
 
@@ -1616,12 +1633,12 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
1616
1633
 
1617
1634
  battle.turn += 1;
1618
1635
 
1619
- if (showdown and pkmn.options.ebc and checkEBC(battle)) {
1636
+ if (pkmn.options.mod and pkmn.options.ebc and checkEBC(battle)) {
1620
1637
  try options.log.tie(.{});
1621
1638
  return Result.Tie;
1622
1639
  }
1623
1640
 
1624
- if (showdown and battle.turn >= 1000) {
1641
+ if (pkmn.options.mod and battle.turn >= 1000) {
1625
1642
  try options.log.tie(.{});
1626
1643
  return Result.Tie;
1627
1644
  } else if (battle.turn >= 65535) {
@@ -1916,12 +1933,11 @@ pub const Effects = struct {
1916
1933
 
1917
1934
  // Technically this is still considered simply a "miss" on the cartridge,
1918
1935
  // but diverging from Pokémon Showdown here would mostly just be pedantic
1919
- if (n == 0) {
1936
+ if (n == 0 or (pkmn.options.mod and err)) {
1920
1937
  try options.log.fail(.{ foe_ident, .None });
1921
1938
  return null;
1922
1939
  } else if (err) {
1923
1940
  // GLITCH: Transform + Mirror Move / Metronome PP softlock
1924
- assert(!showdown);
1925
1941
  return Result.Error;
1926
1942
  }
1927
1943
 
@@ -1999,7 +2015,7 @@ pub const Effects = struct {
1999
2015
  if (foe.active.types.includes(move.type)) return;
2000
2016
  if (!try Rolls.secondaryChance(battle, player, true, options)) return;
2001
2017
  // Freeze Clause Mod
2002
- if (showdown) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
2018
+ if (pkmn.options.mod) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
2003
2019
 
2004
2020
  foe_stored.status = Status.init(.FRZ);
2005
2021
  // GLITCH: Hyper Beam recharging status is not cleared
@@ -2340,7 +2356,7 @@ pub const Effects = struct {
2340
2356
  }
2341
2357
 
2342
2358
  // Sleep Clause Mod
2343
- if (showdown) {
2359
+ if (pkmn.options.mod) {
2344
2360
  for (foe.pokemon) |p| {
2345
2361
  if (Status.is(p.status, .SLP) and !Status.is(p.status, .EXT)) return;
2346
2362
  }
@@ -2446,7 +2462,7 @@ pub const Effects = struct {
2446
2462
  if (side.active.volatiles.Binding) return;
2447
2463
  side.active.volatiles.Binding = true;
2448
2464
  // GLITCH: Hyper Beam automatic selection glitch if Recharging gets cleared on miss
2449
- // (Pokémon Showdown unitentionally patches this glitch, preventing automatic selection)
2465
+ // (Pokémon Showdown clears Recharging in onTryMove instead)
2450
2466
  if (!showdown) {
2451
2467
  foe.active.volatiles.Recharging = false;
2452
2468
  } else if (foe.stored().hp == 0) return if (!rewrap) battle.rng.advance(1);
@@ -3212,7 +3228,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3212
3228
  // Struggle (Pokémon Showdown would use 'move 1' here)
3213
3229
  if (n == before) {
3214
3230
  // GLITCH: Transform + Mirror Move / Metronome PP softlock
3215
- if (!showdown) {
3231
+ if (!pkmn.options.mod) {
3216
3232
  while (slot <= 4) : (slot += 1) {
3217
3233
  if (active.moves[slot - 1].pp != 0) return n;
3218
3234
  }
@@ -1,27 +1,19 @@
1
- const std = @import("std");
2
-
3
- const pkmn = @import("../pkmn.zig");
4
-
1
+ const chance = @import("chance.zig");
5
2
  const common = @import("../common/data.zig");
6
- const util = @import("../common/util.zig");
7
-
8
3
  const Optional = @import("../common/optional.zig").Optional;
4
+ const pkmn = @import("../pkmn.zig");
5
+ const std = @import("std");
6
+ const util = @import("../common/util.zig");
9
7
 
10
- const chance = @import("chance.zig");
11
-
8
+ const Action = chance.Action;
9
+ const Actions = chance.Actions;
12
10
  const assert = std.debug.assert;
13
-
11
+ const Criticals = chance.Criticals;
12
+ const Damages = chance.Damages;
14
13
  const enabled = pkmn.options.calc;
15
-
14
+ const isPointerTo = util.isPointerTo;
16
15
  const Player = common.Player;
17
-
18
16
  const PointerType = util.PointerType;
19
- const isPointerTo = util.isPointerTo;
20
-
21
- const Actions = chance.Actions;
22
- const Action = chance.Action;
23
- const Criticals = chance.Criticals;
24
- const Damages = chance.Damages;
25
17
 
26
18
  const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
27
19
  const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
@@ -156,6 +148,6 @@ fn ReturnType(comptime field: Action.Field) type {
156
148
  return ?(switch (field) {
157
149
  .damages => u8,
158
150
  .critical_hits => Optional(bool),
159
- else => std.meta.FieldType(Action, field),
151
+ else => util.FieldType(Action, field),
160
152
  });
161
153
  }
@@ -1,11 +1,9 @@
1
1
  //! Code generated by `tools/generate` - manual edits will be overwritten.
2
2
 
3
- const std = @import("std");
4
-
5
3
  const data = @import("../../gen2/data.zig");
4
+ const std = @import("std");
6
5
 
7
6
  const assert = std.debug.assert;
8
-
9
7
  const Type = data.Type;
10
8
 
11
9
  /// Representation of a held item in Generation II Pokémon.
@@ -1,12 +1,10 @@
1
1
  //! Code generated by `tools/generate` - manual edits will be overwritten.
2
2
 
3
- const std = @import("std");
4
-
5
- const rng = @import("../../common/rng.zig");
6
3
  const gen2 = @import("../../gen2/data.zig");
4
+ const rng = @import("../../common/rng.zig");
5
+ const std = @import("std");
7
6
 
8
7
  const assert = std.debug.assert;
9
-
10
8
  const percent = rng.Gen12.percent;
11
9
  const Type = gen2.Type;
12
10
 
@@ -1,11 +1,9 @@
1
1
  //! Code generated by `tools/generate` - manual edits will be overwritten.
2
2
 
3
- const std = @import("std");
4
-
5
3
  const gen2 = @import("../../gen2/data.zig");
4
+ const std = @import("std");
6
5
 
7
6
  const assert = std.debug.assert;
8
-
9
7
  const Stats = gen2.Stats;
10
8
  const Types = gen2.Types;
11
9
 
@@ -1,8 +1,7 @@
1
1
  //! Code generated by `tools/generate` - manual edits will be overwritten.
2
2
 
3
- const std = @import("std");
4
-
5
3
  const gen1 = @import("../../gen1/data.zig");
4
+ const std = @import("std");
6
5
 
7
6
  const assert = std.debug.assert;
8
7
  const Effectiveness = gen1.Effectiveness;
@@ -1,24 +1,20 @@
1
- const std = @import("std");
2
- const builtin = @import("builtin");
3
-
4
- const pkmn = @import("../pkmn.zig");
5
-
6
- const data = @import("../common/data.zig");
7
1
  const DEBUG = @import("../common/debug.zig").print;
8
- const protocol = @import("../common/protocol.zig");
9
- const rng = @import("../common/rng.zig");
10
- const util = @import("../common/util.zig");
11
2
 
3
+ const builtin = @import("builtin");
4
+ const calc = @import("calc.zig");
5
+ const chance = @import("chance.zig");
6
+ const data = @import("../common/data.zig");
7
+ const gen1 = @import("../gen1/data.zig");
12
8
  const items = @import("data/items.zig");
9
+ const mechanics = @import("mechanics.zig");
13
10
  const moves = @import("data/moves.zig");
11
+ const pkmn = @import("../pkmn.zig");
12
+ const protocol = @import("../common/protocol.zig");
13
+ const rng = @import("../common/rng.zig");
14
14
  const species = @import("data/species.zig");
15
+ const std = @import("std");
15
16
  const types = @import("data/types.zig");
16
-
17
- const gen1 = @import("../gen1/data.zig");
18
-
19
- const calc = @import("calc.zig");
20
- const chance = @import("chance.zig");
21
- const mechanics = @import("mechanics.zig");
17
+ const util = @import("../common/util.zig");
22
18
 
23
19
  const assert = std.debug.assert;
24
20
  const expectEqual = std.testing.expectEqual;
@@ -1,37 +1,32 @@
1
1
  //! Code generated by `tools/generate` - manual edits will be overwritten.
2
2
 
3
- const std = @import("std");
4
-
5
3
  const common = @import("../common/data.zig");
6
-
7
4
  const data = @import("data.zig");
8
5
  const mechanics = @import("mechanics.zig");
6
+ const std = @import("std");
9
7
 
10
- const Player = common.Player;
11
- const Result = common.Result;
12
-
13
- const Effectiveness = data.Effectiveness;
14
- const Move = data.Move;
15
-
16
- const Effects = mechanics.Effects;
17
- const State = mechanics.State;
18
-
19
- const canMove = mechanics.canMove;
8
+ const adjustDamage = mechanics.adjustDamage;
9
+ const afterMove = mechanics.afterMove;
10
+ const applyDamage = mechanics.applyDamage;
11
+ const buildRage = mechanics.buildRage;
12
+ const calcDamage = mechanics.calcDamage;
20
13
  const canCharge = mechanics.canCharge;
14
+ const canMove = mechanics.canMove;
15
+ const checkCriticalHit = mechanics.checkCriticalHit;
16
+ const checkHit = mechanics.checkHit;
21
17
  const decrementPP = mechanics.decrementPP;
18
+ const destinyBond = mechanics.destinyBond;
22
19
  const doMove = mechanics.doMove;
23
- const checkHit = mechanics.checkHit;
24
- const checkCriticalHit = mechanics.checkCriticalHit;
25
- const calcDamage = mechanics.calcDamage;
26
- const adjustDamage = mechanics.adjustDamage;
27
- const randomizeDamage = mechanics.randomizeDamage;
28
- const applyDamage = mechanics.applyDamage;
29
- const reportOutcome = mechanics.reportOutcome;
30
20
  const effectChance = mechanics.effectChance;
31
- const afterMove = mechanics.afterMove;
32
- const buildRage = mechanics.buildRage;
21
+ const Effectiveness = data.Effectiveness;
22
+ const Effects = mechanics.Effects;
33
23
  const kingsRock = mechanics.kingsRock;
34
- const destinyBond = mechanics.destinyBond;
24
+ const Move = data.Move;
25
+ const Player = common.Player;
26
+ const randomizeDamage = mechanics.randomizeDamage;
27
+ const reportOutcome = mechanics.reportOutcome;
28
+ const Result = common.Result;
29
+ const State = mechanics.State;
35
30
 
36
31
  const SECONDARY = true;
37
32
  const KINGS = true;
@@ -1,25 +1,18 @@
1
- const std = @import("std");
2
-
3
1
  const common = @import("../common/data.zig");
2
+ const data = @import("data.zig");
3
+ const gen1 = @import("../gen1/data.zig");
4
4
  const options = @import("../common/options.zig");
5
5
  const rng = @import("../common/rng.zig");
6
-
7
- const gen1 = @import("../gen1/data.zig");
8
-
9
- const data = @import("data.zig");
6
+ const std = @import("std");
10
7
 
11
8
  const assert = std.debug.assert;
12
-
13
9
  const Choice = common.Choice;
14
-
15
- const showdown = options.showdown;
16
-
17
- const PSRNG = rng.PSRNG;
18
-
19
10
  const DVs = data.DVs;
20
11
  const Item = data.Item;
21
12
  const Move = data.Move;
22
13
  const MoveSlot = data.MoveSlot;
14
+ const PSRNG = rng.PSRNG;
15
+ const showdown = options.showdown;
23
16
  const Species = data.Species;
24
17
  const Stats = data.Stats;
25
18
 
@@ -1,44 +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
2
 
3
+ const builtin = @import("builtin");
4
+ const common = @import("../common/data.zig");
10
5
  const data = @import("data.zig");
11
6
  const generated = @import("generated.zig");
12
-
13
- const assert = std.debug.assert;
14
-
15
- const expectEqual = std.testing.expectEqual;
16
- const expectEqualSlices = std.testing.expectEqualSlices;
17
- const expectEqualStrings = std.testing.expectEqualStrings;
18
-
19
- const showdown = pkmn.options.showdown;
20
-
21
- const Choice = common.Choice;
22
- const ID = common.ID;
23
- const Player = common.Player;
24
- 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");
25
11
 
26
12
  const Activate = protocol.Activate;
13
+ const ActivePokemon = data.ActivePokemon;
14
+ const assert = std.debug.assert;
27
15
  const Boost = protocol.Boost;
16
+ const Boosts = data.Boosts;
17
+ const Choice = common.Choice;
28
18
  const Damage = protocol.Damage;
19
+ const Effectiveness = data.Effectiveness;
29
20
  const End = protocol.End;
21
+ const expectEqual = std.testing.expectEqual;
22
+ const expectEqualSlices = std.testing.expectEqualSlices;
23
+ const expectEqualStrings = std.testing.expectEqualStrings;
30
24
  const Fail = protocol.Fail;
31
- const Heal = protocol.Heal;
32
- const Start = protocol.Start;
33
-
34
25
  const Gen12 = rng.Gen12;
35
-
36
- const ActivePokemon = data.ActivePokemon;
37
- const Boosts = data.Boosts;
38
- const Effectiveness = data.Effectiveness;
26
+ const Heal = protocol.Heal;
27
+ const ID = common.ID;
39
28
  const Item = data.Item;
40
29
  const Move = data.Move;
30
+ const Player = common.Player;
31
+ const Result = common.Result;
32
+ const showdown = pkmn.options.showdown;
41
33
  const Side = data.Side;
34
+ const Start = protocol.Start;
42
35
  const Stats = data.Stats;
43
36
  const Status = data.Status;
44
37
  const TriAttack = data.TriAttack;
@@ -1200,7 +1193,7 @@ fn updateSideCondition(
1200
1193
  fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result {
1201
1194
  battle.turn += 1;
1202
1195
 
1203
- if (showdown and battle.turn >= 1000) {
1196
+ if (pkmn.options.mod and battle.turn >= 1000) {
1204
1197
  try options.log.tie(.{});
1205
1198
  return Result.Tie;
1206
1199
  } else if (battle.turn >= 65535) {
@@ -1434,7 +1427,8 @@ pub const Effects = struct {
1434
1427
  if (move.type.special() != (last == .MirrorCoat)) return;
1435
1428
  if (state.damage == 0) return;
1436
1429
 
1437
- state.damage *|= 2;
1430
+ // NOTE: llvm/llvm-project#58557
1431
+ state.damage = smul(state.damage, 2);
1438
1432
  state.miss = false;
1439
1433
  }
1440
1434
 
@@ -1529,15 +1523,18 @@ pub const Effects = struct {
1529
1523
 
1530
1524
  pub const double = struct {
1531
1525
  pub fn flying(battle: anytype, player: Player, state: *State, _: anytype) !void {
1532
- 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);
1533
1528
  }
1534
1529
 
1535
1530
  pub fn underground(battle: anytype, player: Player, state: *State, _: anytype) !void {
1536
- 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);
1537
1533
  }
1538
1534
 
1539
1535
  pub fn minimize(battle: anytype, player: Player, state: *State, _: anytype) !void {
1540
- 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);
1541
1538
  }
1542
1539
  };
1543
1540
 
@@ -1706,7 +1703,7 @@ pub const Effects = struct {
1706
1703
  if (!state.proc) return;
1707
1704
  if (foe.conditions.Safeguard) return;
1708
1705
  // Freeze Clause Mod
1709
- 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;
1710
1707
 
1711
1708
  foe_stored.status = Status.init(.FRZ);
1712
1709
  try options.log.status(.{ foe_ident, foe_stored.status, .None });
@@ -1735,7 +1732,7 @@ pub const Effects = struct {
1735
1732
  state.bp *= 2;
1736
1733
  assert(state.bp <= 160);
1737
1734
  } else {
1738
- state.damage *|= 2;
1735
+ state.damage = smul(state.damage, 2);
1739
1736
  }
1740
1737
  }
1741
1738
 
@@ -2206,7 +2203,7 @@ pub const Effects = struct {
2206
2203
 
2207
2204
  pub fn pursuit(battle: anytype, player: Player, state: *State, _: anytype) !void {
2208
2205
  // TODO: remove switching variable and just look at Choice saved in Battle struct?
2209
- if (battle.foe(player).active.volatiles.switching) state.damage *|= 2;
2206
+ if (battle.foe(player).active.volatiles.switching) state.damage = smul(state.damage, 2);
2210
2207
  }
2211
2208
 
2212
2209
  pub const rage = struct {
@@ -2219,7 +2216,7 @@ pub const Effects = struct {
2219
2216
  const side = battle.side(player);
2220
2217
 
2221
2218
  assert(side.active.volatiles.Rage);
2222
- state.damage *|= (side.active.volatiles.rage +| 1);
2219
+ state.damage = smul(state.damage, (side.active.volatiles.rage +| 1)); // TODO: |+
2223
2220
  }
2224
2221
  };
2225
2222
 
@@ -2314,7 +2311,7 @@ pub const Effects = struct {
2314
2311
  side.active.volatiles.attacks += 1;
2315
2312
  side.active.volatiles.Rollout = side.active.volatiles.attacks != 5;
2316
2313
 
2317
- if (side.active.volatiles.DefenseCurl) state.damage *|= 2;
2314
+ if (side.active.volatiles.DefenseCurl) state.damage = smul(state.damage, 2);
2318
2315
  }
2319
2316
  };
2320
2317
 
@@ -2384,7 +2381,7 @@ pub const Effects = struct {
2384
2381
  }
2385
2382
 
2386
2383
  // Sleep Clause Mod
2387
- if (showdown) {
2384
+ if (pkmn.options.mod) {
2388
2385
  for (foe.pokemon) |p| {
2389
2386
  if (Status.is(p.status, .SLP) and !Status.is(p.status, .EXT)) return;
2390
2387
  }
@@ -2842,6 +2839,12 @@ fn convertible(active: ActivePokemon, m: Move) bool {
2842
2839
  return t != .@"???" and !active.types.includes(t);
2843
2840
  }
2844
2841
 
2842
+ // NOTE: llvm/llvm-project#58557
2843
+ fn smul(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
2844
+ if (!comptime builtin.target.cpu.arch.isWasm()) return a *| b;
2845
+ return @max(a *% b, std.math.maxInt(@TypeOf(a)));
2846
+ }
2847
+
2845
2848
  pub const Rolls = struct {
2846
2849
  fn speedTie(battle: anytype, options: anytype) !bool {
2847
2850
  const p1 = if (options.calc.overridden(.P1, 0, .speed_tie)) |player|