@pkmn/engine 0.1.0-dev.6195e056 → 0.1.0-dev.63cef311

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.
package/README.md CHANGED
@@ -22,7 +22,7 @@ implementation of both Pokémon battles as defined by the original game code, an
22
22
  Showdown](https://pokemonshowdown.com/)[^1] simulator which represents Pokémon battling as practically
23
23
  interpreted online.
24
24
 
25
- The pkmn engine is up to [**XXXX× faster**](docs/TESTING.md#results) than the
25
+ The pkmn engine is [**more than 1000× faster**](docs/TESTING.md#results) than the
26
26
  [patched](docs/TESTING.md#patches) [Pokémon Showdown simulator
27
27
  code](https://github.com/smogon/pokemon-showdown) when playing out supported formats in
28
28
  compatibility mode and is extensively [tested](docs/TESTING.md) and [documented](docs). Note,
@@ -362,7 +362,7 @@ export declare class Choice {
362
362
  * Only numeric choice data is supported.
363
363
  */
364
364
  static parse(choice: string): Choice;
365
- /** Formats a `Choice` ino a Pokémon Showdown compatible choice string. */
365
+ /** Formats a `Choice` into a Pokémon Showdown compatible choice string. */
366
366
  static format(choice: Choice): string;
367
367
  /** Returns the "pass" `Choice`. */
368
368
  static pass(): Choice;
@@ -80,7 +80,7 @@ class Choice {
80
80
  const data = +(m[4] ?? m[7] ?? 0);
81
81
  return { type, data };
82
82
  }
83
- /** Formats a `Choice` ino a Pokémon Showdown compatible choice string. */
83
+ /** Formats a `Choice` into a Pokémon Showdown compatible choice string. */
84
84
  static format(choice) {
85
85
  return choice.type === 'pass' ? choice.type : `${choice.type} ${choice.data}`;
86
86
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/pkg/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAIA,+CAAiC;AACjC,iCAA8B;AAC9B,6CAA+B;AA8V/B,2CAA2C;AAC9B,QAAA,MAAM,GAAG,IAAI;IACxB,2EAA2E;IAC3E,MAAM,CAAC,GAAe,EAAE,OAAsB;QAC5C,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAChC,MAAM,MAAM,GAAG,aAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,QAAQ,GAAG,CAAC,GAAG,EAAE;YACjB,KAAK,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;YACxD,OAAO,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,mBAAmB,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;SACtD;IACH,CAAC;IAED;;;OAGG;IACH,OAAO,CAAC,GAAe,EAAE,MAAc,EAAE,OAAuB;QAC9D,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAChC,MAAM,MAAM,GAAG,aAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,QAAQ,GAAG,CAAC,GAAG,EAAE;YACjB,KAAK,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;YACjE,OAAO,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,mBAAmB,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;SACtD;IACH,CAAC;CACF,CAAC;AAEF,mDAAmD;AACnD,MAAa,MAAM;IACjB,+BAA+B;IAC/B,MAAM,CAAC,KAAK,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAU,CAAC;IAEzC,MAAM,CAAC,IAAI,GAAG,EAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAU,CAAC;IAC/C,MAAM,CAAC,KAAK,GAAG,kDAAkD,CAAC;IAE5E,gBAAuB,CAAC;IAExB,sDAAsD;IACtD,MAAM,CAAC,MAAM,CAAC,IAAY;QACxB,OAAO,EAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,EAAC,CAAC;IAC5D,CAAC;IAED,oDAAoD;IACpD,MAAM,CAAC,MAAM,CAAC,MAAe;QAC3B,OAAO,CAAC,MAAM;YACZ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACpF,CAAC,CAAC,CAAC,CAAC,CAAC;IACT,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,KAAK,CAAC,MAAc;QACzB,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACpC,IAAI,CAAC,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,MAAM,GAAG,CAAC,CAAC;QACvD,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAmB,CAAC;QACtD,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAClC,OAAO,EAAC,IAAI,EAAE,IAAI,EAAC,CAAC;IACtB,CAAC;IAED,0EAA0E;IAC1E,MAAM,CAAC,MAAM,CAAC,MAAc;QAC1B,OAAO,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;IAChF,CAAC;IAED,mCAAmC;IACnC,MAAM,CAAC,IAAI;QACT,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;IAED,wDAAwD;IACxD,MAAM,CAAC,IAAI,CAAC,IAAuB;QACjC,OAAO,EAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAC,CAAC;IAC9B,CAAC;IAED,0DAA0D;IAC1D,MAAM,CAAC,MAAM,CAAC,IAAuB;QACnC,OAAO,EAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAC,CAAC;IAChC,CAAC;;AAnDU,wBAAM;AAsDnB,kDAAkD;AAClD,MAAa,MAAM;IACjB,8BAA8B;IAC9B,MAAM,CAAC,KAAK,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAU,CAAC;IAEnE,gBAAuB,CAAC;IAExB,wDAAwD;IACxD,MAAM,CAAC,MAAM,CAAC,IAAY;QACxB,OAAO;YACL,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,MAAM,CAAC;YACjC,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;YACpC,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC;SAC5B,CAAC;IACJ,CAAC;;AAbU,wBAAM;AAgBnB,uCAAwE;AAApD,gGAAA,IAAI,OAAA;AAAE,oGAAA,QAAQ,OAAA;AAAe,+FAAA,GAAG,OAAA;AACpD,+BAA8B;AAAtB,8FAAA,MAAM,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/pkg/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pkmn/engine",
3
- "version": "0.1.0-dev.6195e056",
3
+ "version": "0.1.0-dev.63cef311",
4
4
  "description": "A minimal, complete, Pokémon battle simulation engine optimized for performance",
5
5
  "repository": "github:pkmn/engine",
6
6
  "license": "MIT",
@@ -13,6 +13,7 @@
13
13
  "src/lib/binding/node.zig",
14
14
  "src/lib/binding/wasm.zig",
15
15
  "src/lib/common/data.zig",
16
+ "src/lib/common/debug.zig",
16
17
  "src/lib/common/options.zig",
17
18
  "src/lib/common/protocol.zig",
18
19
  "src/lib/common/rng.zig",
@@ -56,14 +57,14 @@
56
57
  "access": "public"
57
58
  },
58
59
  "dependencies": {
59
- "@pkmn/data": "^0.7.33",
60
+ "@pkmn/data": "^0.7.34",
60
61
  "@pkmn/protocol": "^0.6.11"
61
62
  },
62
63
  "devDependencies": {
63
64
  "@pkmn/eslint-config": "^3.0.0-alpha.0",
64
65
  "@pkmn/img": "^0.2.8",
65
66
  "@pkmn/sets": "^5.0.4",
66
- "@pkmn/sim": "0.7.33",
67
+ "@pkmn/sim": "0.7.34",
67
68
  "@types/html-minifier": "^4.0.2",
68
69
  "@types/jest": "^29.5.0",
69
70
  "@types/minimist": "^1.2.2",
@@ -0,0 +1,64 @@
1
+ const std = @import("std");
2
+
3
+ const debug = std.debug;
4
+ const io = std.io;
5
+
6
+ pub fn print(value: anytype) void {
7
+ debug.getStderrMutex().lock();
8
+ defer debug.getStderrMutex().unlock();
9
+ const stderr = io.getStdErr().writer();
10
+
11
+ nosuspend {
12
+ stderr.writeAll("\x1b[41m") catch return;
13
+ switch (@typeInfo(@TypeOf(value))) {
14
+ .Struct => |info| {
15
+ if (info.is_tuple) {
16
+ inline for (info.fields, 0..) |f, i| {
17
+ inspect(@field(value, f.name));
18
+ if (i < info.fields.len - 1) stderr.writeAll(" ") catch return;
19
+ }
20
+ } else {
21
+ inspect(value);
22
+ }
23
+ },
24
+ else => inspect(value),
25
+ }
26
+ stderr.writeAll("\x1b[K\x1b[0m\n") catch return;
27
+ }
28
+ }
29
+
30
+ fn inspect(value: anytype) void {
31
+ const stderr = io.getStdErr().writer();
32
+
33
+ nosuspend {
34
+ const err = "Unable to format type '" ++ @typeName(@TypeOf(value)) ++ "'";
35
+ switch (@typeInfo(@TypeOf(value))) {
36
+ .Array => |info| {
37
+ if (info.child == u8) return stderr.print("{s}", .{value}) catch return;
38
+ @compileError(err);
39
+ },
40
+ .Pointer => |ptr_info| switch (ptr_info.size) {
41
+ .One => switch (@typeInfo(ptr_info.child)) {
42
+ .Array => |info| {
43
+ if (info.child == u8) return stderr.print("{s}", .{value}) catch return;
44
+ @compileError(err);
45
+ },
46
+ .Enum, .Union, .Struct => return inspect(value.*),
47
+ else => @compileError(err),
48
+ },
49
+ .Many, .C => {
50
+ if (ptr_info.sentinel) |_| return inspect(std.mem.span(value));
51
+ if (ptr_info.child == u8) {
52
+ return stderr.print("{s}", .{std.mem.span(value)}) catch return;
53
+ }
54
+ @compileError(err);
55
+ },
56
+ .Slice => {
57
+ if (ptr_info.child == u8) return stderr.print("{s}", .{value}) catch return;
58
+ @compileError(err);
59
+ },
60
+ },
61
+ else => stderr.print("{}", .{value}) catch return,
62
+ }
63
+ }
64
+ }
@@ -515,7 +515,8 @@ the pkmn engine, but the following moves have their broken behavior preserved in
515
515
  glitch](https://glitchcity.wiki/Substitute_%C2%BC_HP_glitch) also fails in many cases due to
516
516
  Pokémon Showdown implementing the health check based on floating point division instead of integer
517
517
  divison like on the cartridge (meaning the Substitute 1/4 glitch will only occur if the Pokémon's
518
- maximum HP is evenly divisible by 4).
518
+ maximum HP is evenly divisible by 4). Finally, Substitute also incorrectly blocks Dream Eater on
519
+ Pokémon Showdown.
519
520
 
520
521
  In addition to numerous cases where Pokémon Showdown uses the wrong type of message (e.g. `|-fail`
521
522
  vs. `|-miss|` vs. `|-immune|`, e.g. in the case of Leech Seed) which are not documented here, Pokémon
@@ -272,7 +272,7 @@ pub const Status = enum(u8) {
272
272
  return num > 0;
273
273
  }
274
274
 
275
- /// Retunrns a human-readable representation of the status `num`.
275
+ /// Returns a human-readable representation of the status `num`.
276
276
  pub fn name(num: u8) []const u8 {
277
277
  if (Status.is(num, .EXT)) return if (num == Status.TOX) "TOX" else "SLF";
278
278
  if (Status.is(num, .SLP)) return "SLP";
@@ -1278,7 +1278,9 @@ fn moveHit(battle: anytype, player: Player, move: Move.Data, immune: *bool, mist
1278
1278
  if (move.effect == .Swift) return true;
1279
1279
  if (foe.active.volatiles.Invulnerable) break :miss true;
1280
1280
  }
1281
- if (move.effect == .DreamEater and !Status.is(foe.stored().status, .SLP)) {
1281
+ if (move.effect == .DreamEater and (!Status.is(foe.stored().status, .SLP) or
1282
+ (showdown and foe.active.volatiles.Substitute)))
1283
+ {
1282
1284
  immune.* = true;
1283
1285
  if (showdown) return false;
1284
1286
  break :miss true;
@@ -1480,16 +1482,20 @@ fn endTurn(battle: anytype, log: anytype) @TypeOf(log).Error!Result {
1480
1482
 
1481
1483
  fn checkEBC(battle: anytype) bool {
1482
1484
  for (battle.sides, 0..) |side, i| {
1485
+ const foe = battle.sides[~@intCast(u1, i)];
1486
+
1483
1487
  var foe_all_ghosts = true;
1484
1488
  var foe_all_transform = true;
1489
+ for (foe.order, 0..) |id, j| {
1490
+ if (id == 0) break;
1491
+ const active = j == 0;
1492
+ const pokemon = foe.pokemon[id - 1];
1485
1493
 
1486
- for (battle.sides[~@intCast(u1, i)].pokemon) |pokemon| {
1487
- if (pokemon.species == .None) continue;
1488
-
1489
- const ghost = pokemon.hp == 0 or pokemon.types.includes(.Ghost);
1494
+ const ghost = pokemon.hp == 0 or
1495
+ (if (active) foe.active.types else pokemon.types).includes(.Ghost);
1490
1496
  foe_all_ghosts = foe_all_ghosts and ghost;
1491
1497
  foe_all_transform = foe_all_transform and pokemon.hp == 0 or transform: {
1492
- for (pokemon.moves) |m| {
1498
+ for (if (active) foe.active.moves else pokemon.moves) |m| {
1493
1499
  if (m.id == .None) break :transform true;
1494
1500
  if (m.id != .Transform) break :transform false;
1495
1501
  }
@@ -1497,10 +1503,14 @@ fn checkEBC(battle: anytype) bool {
1497
1503
  };
1498
1504
  }
1499
1505
 
1500
- for (side.pokemon) |pokemon| {
1506
+ for (side.order, 0..) |id, j| {
1507
+ if (id == 0) break;
1508
+ const active = j == 0;
1509
+ const pokemon = side.pokemon[id - 1];
1510
+
1501
1511
  if (pokemon.hp == 0 or Status.is(pokemon.status, .FRZ)) continue;
1502
1512
  const transform = foe_all_transform and transform: {
1503
- for (pokemon.moves) |m| {
1513
+ for (if (active) side.active.moves else pokemon.moves) |m| {
1504
1514
  if (m.id == .None) break :transform true;
1505
1515
  if (m.id != .Transform) break :transform false;
1506
1516
  }
@@ -1508,7 +1518,7 @@ fn checkEBC(battle: anytype) bool {
1508
1518
  };
1509
1519
  if (transform) continue;
1510
1520
  const no_pp = foe_all_ghosts and no_pp: {
1511
- for (pokemon.moves) |m| {
1521
+ for (if (active) side.active.moves else pokemon.moves) |m| {
1512
1522
  if (m.pp != 0) break :no_pp false;
1513
1523
  }
1514
1524
  break :no_pp true;
@@ -1753,6 +1763,11 @@ pub const Effects = struct {
1753
1763
  var side = battle.side(player);
1754
1764
  var stored = side.stored();
1755
1765
 
1766
+ if (battle.last_damage == 0) {
1767
+ assert(showdown);
1768
+ return;
1769
+ }
1770
+
1756
1771
  const drain = @max(battle.last_damage / 2, 1);
1757
1772
  battle.last_damage = drain;
1758
1773
 
@@ -2063,7 +2078,7 @@ pub const Effects = struct {
2063
2078
  return log.fail(foe_ident, if (Status.is(foe_stored.status, .PSN))
2064
2079
  if (!showdown)
2065
2080
  .Poison
2066
- else if (toxic == foe.active.volatiles.Toxic)
2081
+ else if (toxic == (foe_stored.status == Status.TOX))
2067
2082
  if (toxic) .Toxic else .Poison
2068
2083
  else
2069
2084
  .None
@@ -2514,13 +2529,9 @@ fn clearVolatiles(battle: anytype, who: Player, log: anytype) !void {
2514
2529
  volatiles.LeechSeed = false;
2515
2530
  try log.end(ident, .LeechSeed);
2516
2531
  }
2517
- if (volatiles.Toxic) {
2532
+ if (!showdown and volatiles.Toxic) {
2518
2533
  volatiles.Toxic = false;
2519
- // volatiles.toxic is left unchanged, except on Pokémon Showdown which clears it
2520
- if (showdown) {
2521
- volatiles.toxic = 0;
2522
- try log.end(ident, .Toxic);
2523
- }
2534
+ // volatiles.toxic is left unchanged, except on Pokémon Showdown which clears it (below)
2524
2535
  }
2525
2536
  if (volatiles.LightScreen) {
2526
2537
  volatiles.LightScreen = false;
@@ -2530,6 +2541,12 @@ fn clearVolatiles(battle: anytype, who: Player, log: anytype) !void {
2530
2541
  volatiles.Reflect = false;
2531
2542
  try log.end(ident, .Reflect);
2532
2543
  }
2544
+ if (showdown and volatiles.Toxic) {
2545
+ volatiles.Toxic = false;
2546
+ // Pokémon Showdown erroneously clears the toxic counter
2547
+ volatiles.toxic = 0;
2548
+ try log.end(ident, .Toxic);
2549
+ }
2533
2550
  }
2534
2551
 
2535
2552
  fn disabled(side: *Side, ident: ID, log: anytype) !bool {