@pkmn/engine 0.1.0-dev.84cc1a02 → 0.1.0-dev.976cf3c3

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/build.zig CHANGED
@@ -28,7 +28,7 @@ pub fn build(b: *std.Build) !void {
28
28
  const strip = b.option(bool, "strip", "Strip debugging symbols from binary") orelse false;
29
29
  const pic = b.option(bool, "pic", "Force position independent code") orelse false;
30
30
 
31
- const cmd = b.findProgram(&[_][]const u8{"strip"}, &[_][]const u8{}) catch null;
31
+ const cmd = b.findProgram(&.{"strip"}, &.{}) catch null;
32
32
 
33
33
  var parser = std.json.Parser.init(b.allocator, false);
34
34
  defer parser.deinit();
@@ -72,7 +72,7 @@ pub fn build(b: *std.Build) !void {
72
72
  maybeStrip(b, lib, b.getInstallStep(), strip, cmd);
73
73
  if (pic) lib.force_pic = pic;
74
74
  // Always emit to build/lib because this is where the driver code expects to find it
75
- // TODO(ziglang/zig#2231): using the following used to work (though be hacky):
75
+ // TODO(ziglang/zig#2231): using the following used to work (though was hacky):
76
76
  //
77
77
  // lib.emit_bin = .{ .emit_to = b.fmt("build/lib/{s}", .{addon}) };
78
78
  // b.getInstallStep().dependOn(&lib.step);
@@ -96,13 +96,10 @@ pub fn build(b: *std.Build) !void {
96
96
  lib.rdynamic = true;
97
97
  lib.strip = strip;
98
98
  if (pic) lib.force_pic = pic;
99
- const opt = b.findProgram(
100
- &[_][]const u8{"wasm-opt"},
101
- &[_][]const u8{"./node_modules/.bin"},
102
- ) catch null;
99
+ const opt = b.findProgram(&.{"wasm-opt"}, &.{"./node_modules/.bin"}) catch null;
103
100
  if (optimize != .Debug and opt != null) {
104
101
  const out = b.fmt("build/lib/{s}.wasm", .{name});
105
- const sh = b.addSystemCommand(&[_][]const u8{ opt.?, "-O4" });
102
+ const sh = b.addSystemCommand(&.{ opt.?, "-O4" });
106
103
  sh.addArtifactArg(lib);
107
104
  sh.addArg("-o");
108
105
  sh.addFileSourceArg(.{ .path = out });
@@ -233,7 +230,7 @@ fn maybeStrip(
233
230
  if (mac and artifact.isDynamicLibrary()) return;
234
231
  // Assuming GNU strip, which complains "illegal pathname found in archive member"...
235
232
  if (!mac and artifact.isStaticLibrary()) return;
236
- const sh = b.addSystemCommand(&[_][]const u8{ cmd.?, if (mac) "-x" else "-s" });
233
+ const sh = b.addSystemCommand(&.{ cmd.?, if (mac) "-x" else "-s" });
237
234
  sh.addArtifactArg(artifact);
238
235
  step.dependOn(&sh.step);
239
236
  }
@@ -246,7 +243,7 @@ const Config = struct {
246
243
  cmd: ?[]const u8,
247
244
  };
248
245
 
249
- const TESTS = [_][]const u8{
246
+ const TESTS = .{
250
247
  "src/lib/common/test.zig",
251
248
  "src/lib/gen1/test.zig",
252
249
  "src/lib/gen2/test.zig",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pkmn/engine",
3
- "version": "0.1.0-dev.84cc1a02",
3
+ "version": "0.1.0-dev.976cf3c3",
4
4
  "description": "A minimal, complete, Pokémon battle simulation engine optimized for performance",
5
5
  "repository": "github:pkmn/engine",
6
6
  "license": "MIT",
@@ -57,14 +57,14 @@
57
57
  "access": "public"
58
58
  },
59
59
  "dependencies": {
60
- "@pkmn/data": "^0.7.35",
60
+ "@pkmn/data": "^0.7.36",
61
61
  "@pkmn/protocol": "^0.6.11"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@pkmn/eslint-config": "^3.0.0",
65
65
  "@pkmn/img": "^0.2.8",
66
66
  "@pkmn/sets": "^5.0.4",
67
- "@pkmn/sim": "0.7.35",
67
+ "@pkmn/sim": "0.7.36",
68
68
  "@types/html-minifier": "^4.0.2",
69
69
  "@types/jest": "^29.5.0",
70
70
  "@types/minimist": "^1.2.2",
@@ -15,8 +15,10 @@ const DEV = ZIG_VERSION.dev ? `-dev.${ZIG_VERSION.dev}` : '';
15
15
  const VERSION = `${ZIG_VERSION.major}.${ZIG_VERSION.minor}.${ZIG_VERSION.patch}${DEV}`;
16
16
  const INDEX = 'https://ziglang.org/download/index.json';
17
17
  const ROOT = path.resolve(__dirname, '..', '..');
18
+ let SILENT = false;
18
19
 
19
20
  const sh = (cmd, args) => execFileSync(cmd, args, {encoding: 'utf8', cwd: ROOT});
21
+ const log = (...args) => { if (!SILENT) console.log(...args); };
20
22
 
21
23
  // This is hardly the browser fetch API, but it works for our primitive needs
22
24
  const fetch = url => new Promise((resolve, reject) => {
@@ -61,7 +63,7 @@ const unpack7 = (input, output) => new Promise((resolve, reject) => {
61
63
  require.resolve('7zip-min');
62
64
  } catch (err) {
63
65
  if (err.code !== 'MODULE_NOT_FOUND') throw err;
64
- console.log('Installing 7zip-min package locally to unpack the archive...');
66
+ log('Installing 7zip-min package locally to unpack the archive...');
65
67
  sh('npm', ['install', '7zip-min', '--no-audit', '--no-save']);
66
68
  }
67
69
 
@@ -83,7 +85,7 @@ const unpack = async (input, output) => {
83
85
  sh('tar', ['xf', input, '-C', output]);
84
86
  return;
85
87
  } catch {
86
- console.log('tar command not found, falling back to 7z');
88
+ log('tar command not found, falling back to 7z');
87
89
  }
88
90
  }
89
91
  // Try to use a 7z binary if we can, and only install 7zip-min as a last resort
@@ -120,10 +122,10 @@ const find = zig => {
120
122
  (!ZIG_VERSION.dev || (dev && dev >= ZIG_VERSION.dev)));
121
123
 
122
124
  if (compatible) {
123
- console.log('Found existing compatible Zig executable:', zig_exe);
125
+ log('Found existing compatible Zig executable:', zig_exe);
124
126
  return zig_exe;
125
127
  } else {
126
- console.log(`Existing Zig executable is not compatible (${version} < ${VERSION}):`, zig_exe);
128
+ log(`Existing Zig executable is not compatible (${version} < ${VERSION}):`, zig_exe);
127
129
  }
128
130
  } catch (err) {
129
131
  if (err.code !== 'ENOENT') throw err;
@@ -168,11 +170,11 @@ const install = async () => {
168
170
  let base = path.basename(url);
169
171
  const archive = path.join(dir, path.basename(url));
170
172
 
171
- console.log(`Downloading Zig tarball from ${url} to ${archive}`);
173
+ log(`Downloading Zig tarball from ${url} to ${archive}`);
172
174
  try {
173
175
  await download(url, archive, release.shasum);
174
176
 
175
- console.log(`Extracting tarball and installing to ${dir}`);
177
+ log(`Extracting tarball and installing to ${dir}`);
176
178
  await unpack(archive, dir);
177
179
  if (archive.endsWith('.zip')) {
178
180
  base = base.slice(0, -4);
@@ -187,17 +189,17 @@ const install = async () => {
187
189
  } catch (err) {
188
190
  if (err.code !== 'ENOENT') throw err;
189
191
  }
190
- console.log(`Zig${version === 'master' ? '' : ` v${version}`} executable:`, zig,
192
+ log(`Zig${version === 'master' ? '' : ` v${version}`} executable:`, zig,
191
193
  '(this version of Zig will be retained to speed up future updates)');
192
194
  const [pre, post] = tty.isatty(1) ? ['\x1b[1m\x1b[35m', '\x1b[0m'] : ['', ''];
193
- console.log(
195
+ log(
194
196
  `${pre}Consider moving the ${path.dirname(zig)} directory and adding it to your PATH`,
195
197
  `to use Zig on other projects, or delete this if you wish to save space.${post}`
196
198
  );
197
199
 
198
200
  return zig;
199
201
  } finally {
200
- console.log(`Cleaning up ${archive}`);
202
+ log(`Cleaning up ${archive}`);
201
203
  try { fs.rmSync(archive); } catch {}
202
204
  }
203
205
  };
@@ -206,17 +208,17 @@ const install = async () => {
206
208
  const downloadHeaders = async () => {
207
209
  const include = path.resolve(process.execPath, '..', '..', 'include', 'node');
208
210
  if (fs.existsSync(path.join(include, 'node.h'))) {
209
- console.log('Node headers:', include);
211
+ log('Node headers:', include);
210
212
  return include;
211
213
  }
212
214
 
213
215
  const dir = path.join(ROOT, 'build', 'include');
214
216
  const dest = path.join(dir, 'node');
215
217
  if (fs.existsSync(path.join(dest, 'node.h'))) {
216
- console.log('Node headers:', dest);
218
+ log('Node headers:', dest);
217
219
  return dest;
218
220
  } else {
219
- console.log(`Could not find Node headers at ${include}, downloading...`);
221
+ log(`Could not find Node headers at ${include}, downloading...`);
220
222
  }
221
223
 
222
224
  try {
@@ -229,14 +231,14 @@ const downloadHeaders = async () => {
229
231
  const targz = path.join(dir, path.basename(url));
230
232
  const unpacked = path.join(dir, `node-${process.version}`, 'include');
231
233
 
232
- console.log(`Downloading Node headers from ${url} to ${targz}`);
234
+ log(`Downloading Node headers from ${url} to ${targz}`);
233
235
 
234
236
  try {
235
237
  await download(url, targz);
236
238
  await unpack(targz, dir);
237
239
  fs.renameSync(path.join(unpacked, 'node'), dest);
238
240
 
239
- console.log('Node headers:', dest);
241
+ log('Node headers:', dest);
240
242
 
241
243
  return dest;
242
244
  } finally {
@@ -248,7 +250,7 @@ const downloadHeaders = async () => {
248
250
  const downloadImportLib = async () => {
249
251
  const library = path.resolve(process.execPath, '..', 'node.lib');
250
252
  if (fs.existsSync(path.join(library, 'node.h'))) {
251
- console.log('Node import library:', library);
253
+ log('Node import library:', library);
252
254
  return library;
253
255
  }
254
256
 
@@ -256,10 +258,10 @@ const downloadImportLib = async () => {
256
258
  const url = new URL('./win-x64/node.lib', process.release.headersUrl).href;
257
259
  const dest = path.join(dir, path.basename(url));
258
260
  if (fs.existsSync(path.join(dest))) {
259
- console.log('Node import library:', dest);
261
+ log('Node import library:', dest);
260
262
  return dest;
261
263
  } else {
262
- console.log(`Could not find Node import library at ${library}, downloading...`);
264
+ log(`Could not find Node import library at ${library}, downloading...`);
263
265
  }
264
266
 
265
267
  try {
@@ -268,24 +270,41 @@ const downloadImportLib = async () => {
268
270
  if (err.code !== 'EEXIST') throw err;
269
271
  }
270
272
 
271
- console.log(`Downloading Node import library from ${url} to ${dest}`);
273
+ log(`Downloading Node import library from ${url} to ${dest}`);
272
274
  await download(url, dest);
273
- console.log('Node import library:', dest);
275
+ log('Node import library:', dest);
274
276
 
275
277
  return dest;
276
278
  };
277
279
 
278
280
  (async () => {
279
- const options = (process.argv[2] || '').startsWith('--options=')
280
- ? process.argv[2].slice(10).split(' ') : undefined;
281
- if (process.argv.length > 3 ||
282
- process.argv.length === 3 && !(process.argv[2] === '--zig' || options)) {
281
+ let only = false;
282
+ let options = undefined;
283
+ let error = false;
284
+ for (let i = 2; i < process.argv.length; i++) {
285
+ switch (process.argv[i]) {
286
+ case "--silent": {
287
+ SILENT = true;
288
+ break;
289
+ }
290
+ case "--zig": {
291
+ if (options || only) error = true;
292
+ only = true;
293
+ break;
294
+ }
295
+ default: {
296
+ if (options || only || !process.argv[i].startsWith('--options=')) error = true;
297
+ options = process.argv[i].slice(10).split(' ');
298
+ }
299
+ }
300
+ }
301
+ if (error) {
283
302
  console.error('Invalid arguments');
284
303
  process.exit(1);
285
304
  }
286
305
 
287
306
  const zig = await install();
288
- if (process.argv[2] === '--zig') process.exit(0);
307
+ if (only) process.exit(0);
289
308
 
290
309
  let tmp;
291
310
  try {
@@ -355,7 +374,7 @@ const downloadImportLib = async () => {
355
374
  opts.includes('-Dshowdown') || opts.includes('-Dshowdown=true') ? '-showdown' : '';
356
375
  const flags = opts.length ? ` (${opts.join(' ')})` : '';
357
376
  const artifact = path.join('build', 'lib', `pkmn${suffix}.${config.type}`);
358
- console.log(`Building ${artifact}${flags}`);
377
+ log(`Building ${artifact}${flags}`);
359
378
  sh(zig, args);
360
379
  // TODO: ziglang/zig#14647 broke emit_to so we get to rename the file ourselves...
361
380
  if (config.type === 'node') {
@@ -493,6 +493,10 @@ the pkmn engine, but the following moves have their broken behavior preserved in
493
493
  instead cleared in Pokémon Showdown's "residual" phase, meaning the flinch status gets erroneously
494
494
  preserved across fainting (as fainting triggers Pokémon Showdown's "instaswitch" behavior which
495
495
  skips end-of-turn residuals).
496
+ - **Rage**: Rage boosts should still result in burn/paralysis [stat modification
497
+ errors](https://pkmn.cc/bulba-glitch-1#Stat_modification_errors) but don't on Pokémon Showdown.
498
+ Furthermore, Pokémon Showdown only builds Rage for Disable/Explosion (hit/miss) when attacking
499
+ into a Substitute instead of all moves.
496
500
  - **Thrash** / **Petal Dance**: once a Pokémon is locked into a thrashing move they will lose all
497
501
  speed ties on Pokémon Showdown (due to the fact that if they win the speed tie their action will
498
502
  then get "changed" and inserted back into the queue after their opponent's action).
@@ -620,8 +620,9 @@ fn beforeMove(
620
620
  return .done;
621
621
  }
622
622
 
623
+ const sub = showdown and foe.active.volatiles.Substitute;
623
624
  _ = try applyDamage(battle, player.foe(), player.foe(), .None, log);
624
- if (foe.stored().hp > 0) try buildRage(battle, player.foe(), log);
625
+ if (foe.stored().hp > 0 and !sub) try buildRage(battle, player.foe(), log);
625
626
 
626
627
  // For reasons passing understanding, Pokémon Showdown still inflicts residual damage to
627
628
  // Bide's user even if the above damage has caused the foe to faint. It's simpler to always
@@ -894,6 +895,9 @@ fn doMove(
894
895
  hits = side.active.volatiles.attacks;
895
896
  }
896
897
 
898
+ // Pokémon Showdown only builds Rage for Disable/Explosion (hit/miss) when attacking into a sub
899
+ const sub = showdown and foe.active.volatiles.Substitute;
900
+
897
901
  var nullified = false;
898
902
  var hit: u4 = 0;
899
903
  while (hit < hits) {
@@ -909,7 +913,7 @@ fn doMove(
909
913
  if (hit == 0 and ohko) try log.ohko();
910
914
  hit += 1;
911
915
  if (foe.stored().hp == 0) break;
912
- if (!late) try buildRage(battle, player.foe(), log);
916
+ if (!late and !sub) try buildRage(battle, player.foe(), log);
913
917
  // If the substitute breaks during a multi-hit attack, the attack ends
914
918
  if (nullified) break;
915
919
  }
@@ -946,8 +950,8 @@ fn doMove(
946
950
 
947
951
  if (foe.stored().hp == 0) return null;
948
952
 
949
- // Pokémon Showdown builds Rage at the wrong time for non-MultiHit moves
950
- if (late and move.effect != .Disable) try buildRage(battle, player.foe(), log);
953
+ // Pokémon Showdown builds Rage at the wrong time for non-MultiHit move
954
+ if (late and !sub and move.effect != .Disable) try buildRage(battle, player.foe(), log);
951
955
 
952
956
  if (!move.effect.isSpecial()) {
953
957
  // On the cartridge Rage is not considered to be "special" and thus gets executed for a
@@ -1100,11 +1104,12 @@ fn randomizeDamage(battle: anytype) void {
1100
1104
 
1101
1105
  fn specialDamage(battle: anytype, player: Player, move: Move.Data, log: anytype) !?Result {
1102
1106
  const side = battle.side(player);
1107
+ const foe = battle.foe(player);
1103
1108
 
1104
1109
  if (!try checkHit(battle, player, move, log)) return null;
1105
1110
 
1106
1111
  battle.last_damage = switch (side.last_selected_move) {
1107
- .SuperFang => @max(battle.foe(player).stored().hp / 2, 1),
1112
+ .SuperFang => @max(foe.stored().hp / 2, 1),
1108
1113
  .SeismicToss, .NightShade => side.stored().level,
1109
1114
  .SonicBoom => 20,
1110
1115
  .DragonRage => 40,
@@ -1127,8 +1132,9 @@ fn specialDamage(battle: anytype, player: Player, move: Move.Data, log: anytype)
1127
1132
 
1128
1133
  if (battle.last_damage == 0) return if (showdown) null else Result.Error;
1129
1134
 
1135
+ const sub = showdown and foe.active.volatiles.Substitute;
1130
1136
  _ = try applyDamage(battle, player.foe(), player.foe(), .None, log);
1131
- if (battle.foe(player).stored().hp > 0) try buildRage(battle, player.foe(), log);
1137
+ if (battle.foe(player).stored().hp > 0 and !sub) try buildRage(battle, player.foe(), log);
1132
1138
 
1133
1139
  return null;
1134
1140
  }
@@ -2343,6 +2349,8 @@ pub const Effects = struct {
2343
2349
  const stat = unmodifiedStats(battle, player).atk;
2344
2350
  stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
2345
2351
  try log.boost(ident, reason, n);
2352
+ // Pokémon Showdown doesn't re-apply status modifiers after Rage boosts
2353
+ if (showdown and move.effect == .Rage) return;
2346
2354
  },
2347
2355
  .DefenseUp1, .DefenseUp2 => {
2348
2356
  assert(boosts.def >= -6 and boosts.def <= 6);
@@ -2593,9 +2601,14 @@ fn disabled(side: *Side, ident: ID, log: anytype) !bool {
2593
2601
  if (side.active.volatiles.disabled_move != 0) {
2594
2602
  // A Pokémon that transforms after being disabled may end up with less move slots
2595
2603
  const m = side.active.moves[side.active.volatiles.disabled_move - 1].id;
2596
- if (m != .None and m == side.last_selected_move) {
2604
+ // side.last_selected_move can be Struggle here on Pokemon Showdown we need an extra check
2605
+ const last = if (showdown and m == .Bide and side.active.volatiles.Bide)
2606
+ m
2607
+ else
2608
+ side.last_selected_move;
2609
+ if (m != .None and m == last) {
2597
2610
  side.active.volatiles.Charging = false;
2598
- try log.disabled(ident, side.last_selected_move);
2611
+ try log.disabled(ident, last);
2599
2612
  return true;
2600
2613
  }
2601
2614
  }
@@ -2757,7 +2770,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
2757
2770
  while (slot <= 4) : (slot += 1) {
2758
2771
  const m = active.moves[slot - 1];
2759
2772
  if (m.id == .None) break;
2760
- if (m.id == side.last_selected_move) {
2773
+ if (m.id == if (active.volatiles.Bide) .Bide else side.last_selected_move) {
2761
2774
  // Pokémon Showdown displays Struggle if limited to Bide but unable to pick
2762
2775
  const struggle =
2763
2776
  m.id == .Bide and (m.pp == 0 or active.volatiles.disabled_move == slot);