@pkmn/engine 0.1.0-dev.46307385 → 0.1.0-dev.47dcb76c

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 (48) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +37 -14
  3. package/build/pkg/addon.js.map +1 -1
  4. package/build/pkg/data/ids.json +12 -12
  5. package/build/pkg/data/layout.json +2 -2
  6. package/build/pkg/data/protocol.json +69 -12
  7. package/build/pkg/data.d.ts +3 -2
  8. package/build/pkg/data.js.map +1 -1
  9. package/build/pkg/gen1.js +2 -2
  10. package/build/pkg/gen1.js.map +1 -1
  11. package/build/pkg/index.d.ts +2 -1
  12. package/build/pkg/index.js.map +1 -1
  13. package/build/pkg/protocol.js +57 -38
  14. package/build/pkg/protocol.js.map +1 -1
  15. package/build/test/display.js.map +1 -1
  16. package/build/tools/debug.js +3 -1
  17. package/build/tools/debug.js.map +1 -1
  18. package/build.zig +91 -73
  19. package/package.json +43 -23
  20. package/src/bin/install-pkmn-engine +14 -14
  21. package/src/lib/common/data.zig +12 -24
  22. package/src/lib/common/optional.zig +9 -2
  23. package/src/lib/common/protocol.zig +797 -327
  24. package/src/lib/common/rational.zig +12 -8
  25. package/src/lib/common/rng.zig +26 -17
  26. package/src/lib/gen1/calc.zig +9 -10
  27. package/src/lib/gen1/chance.zig +74 -58
  28. package/src/lib/gen1/data/moves.zig +192 -180
  29. package/src/lib/gen1/data/species.zig +3 -3
  30. package/src/lib/gen1/data/types.zig +7 -7
  31. package/src/lib/gen1/data.zig +47 -59
  32. package/src/lib/gen1/helpers.zig +33 -48
  33. package/src/lib/gen1/mechanics.zig +354 -369
  34. package/src/lib/gen2/calc.zig +143 -0
  35. package/src/lib/gen2/chance.zig +1314 -0
  36. package/src/lib/gen2/data/items.zig +12 -18
  37. package/src/lib/gen2/data/moves.zig +663 -569
  38. package/src/lib/gen2/data/species.zig +1 -1
  39. package/src/lib/gen2/data/types.zig +134 -31
  40. package/src/lib/gen2/data.zig +106 -79
  41. package/src/lib/gen2/generated.zig +806 -0
  42. package/src/lib/gen2/helpers.zig +8 -12
  43. package/src/lib/gen2/mechanics.zig +3502 -9
  44. package/src/lib/{binding/node.zig → node.zig} +10 -13
  45. package/src/lib/pkmn.zig +26 -3
  46. package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
  47. package/src/lib/gen1/README.md +0 -892
  48. package/src/lib/gen2/README.md +0 -308
@@ -1,8 +1,10 @@
1
1
  const std = @import("std");
2
2
  const builtin = @import("builtin");
3
3
 
4
- const data = @import("./data.zig");
5
- const options = @import("./options.zig");
4
+ const pkmn = @import("../pkmn.zig");
5
+
6
+ const data = @import("data.zig");
7
+ const options = @import("options.zig");
6
8
 
7
9
  const assert = std.debug.assert;
8
10
  const print = std.debug.print;
@@ -12,6 +14,8 @@ const enabled = options.log;
12
14
  const Player = data.Player;
13
15
  const ID = data.ID;
14
16
 
17
+ const endian = builtin.cpu.arch.endian();
18
+
15
19
  pub const ArgType = enum(u8) {
16
20
  None,
17
21
 
@@ -69,7 +73,7 @@ pub const ArgType = enum(u8) {
69
73
  EndAbility,
70
74
  ClearNegativeBoost,
71
75
  FormeChange,
72
- NoTarget,
76
+ LastNoTarget, // FIXME
73
77
 
74
78
  // Gen 4
75
79
  SwapBoost,
@@ -102,6 +106,7 @@ pub const ArgType = enum(u8) {
102
106
  // Gen 8
103
107
  CanDynamax,
104
108
  SwapSideConditions,
109
+ UpdatePoke,
105
110
 
106
111
  // Gen 9
107
112
  Terastallize,
@@ -121,12 +126,19 @@ pub const Cant = enum(u8) {
121
126
  Disable,
122
127
  Recharge,
123
128
  PP,
129
+
130
+ Attract,
124
131
  };
125
132
 
126
133
  pub const Heal = enum(u8) {
127
134
  None,
128
135
  Silent,
129
136
  Drain,
137
+
138
+ Leftovers,
139
+ Berry,
140
+ BerryJuice,
141
+ GoldBerry,
130
142
  };
131
143
 
132
144
  pub const Damage = enum(u8) {
@@ -136,6 +148,17 @@ pub const Damage = enum(u8) {
136
148
  Confusion,
137
149
  LeechSeed,
138
150
  RecoilOf,
151
+
152
+ Bind,
153
+ Wrap,
154
+ FireSpin,
155
+ Clamp,
156
+ Whirlpool,
157
+ Spikes,
158
+
159
+ Nightmare,
160
+ Curse,
161
+ Sandstorm,
139
162
  };
140
163
 
141
164
  pub const Status = enum(u8) {
@@ -178,8 +201,25 @@ pub const Activate = enum(u8) {
178
201
  Haze,
179
202
  Mist,
180
203
  Struggle,
181
- Substitute,
204
+ Substitute, // FIXME SubstituteDamage
182
205
  Splash,
206
+
207
+ Rage, // TODO
208
+
209
+ Attract,
210
+ LockOn,
211
+ Bind,
212
+ Wrap,
213
+ FireSpin,
214
+ Clamp,
215
+ Whirlpool,
216
+ SubstituteBlock,
217
+
218
+ DestinyBond,
219
+
220
+ BeatUp, // FIXME of
221
+
222
+ Magnitude,
183
223
  };
184
224
 
185
225
  pub const Start = enum(u8) {
@@ -197,6 +237,19 @@ pub const Start = enum(u8) {
197
237
 
198
238
  Disable,
199
239
  Mimic,
240
+
241
+ Attract,
242
+ Nightmare,
243
+ Curse,
244
+ Foresight,
245
+ Encore,
246
+ FutureSight,
247
+
248
+ PerishSong0,
249
+ PerishSong1,
250
+ PerishSong2,
251
+ PerishSong3,
252
+ PerishSong3Silent,
200
253
  };
201
254
 
202
255
  pub const End = enum(u8) {
@@ -205,16 +258,30 @@ pub const End = enum(u8) {
205
258
  Bide,
206
259
  Substitute,
207
260
 
208
- // Silent
261
+ Nightmare,
262
+ Curse,
263
+ Foresight,
264
+ Encore,
265
+ FutureSight,
266
+ LeechSeed,
267
+
268
+ Bind,
269
+ Wrap,
270
+ FireSpin,
271
+ Clamp,
272
+ Whirlpool,
273
+
209
274
  DisableSilent,
210
275
  ConfusionSilent,
211
-
212
- Mist,
213
- FocusEnergy,
214
- LeechSeed,
215
- Toxic,
216
- LightScreen,
217
- Reflect,
276
+ MistSilent,
277
+ FocusEnergySilent,
278
+ LeechSeedSilent,
279
+ ToxicSilent,
280
+ LightScreenSilent,
281
+ ReflectSilent,
282
+ BideSilent,
283
+
284
+ LeechSeedFrom,
218
285
  };
219
286
 
220
287
  pub const Immune = enum(u8) {
@@ -222,6 +289,28 @@ pub const Immune = enum(u8) {
222
289
  OHKO,
223
290
  };
224
291
 
292
+ pub const EndItem = enum(u8) {
293
+ None,
294
+ Eat,
295
+ };
296
+
297
+ pub const SetHP = enum(u8) {
298
+ None,
299
+ Silent,
300
+ };
301
+
302
+ pub const Side = enum(u8) {
303
+ Safeguard,
304
+ Reflect,
305
+ LightScreen,
306
+ Spikes,
307
+ };
308
+
309
+ pub const Weather = enum(u8) {
310
+ None,
311
+ Upkeep,
312
+ };
313
+
225
314
  /// Null object pattern implementation of `Log` backed by a `std.io.null_writer`.
226
315
  /// Ignores anything sent to it, though protocol logging should additionally be turned off
227
316
  /// entirely with `options.log`.
@@ -236,362 +325,484 @@ pub fn Log(comptime Writer: type) type {
236
325
 
237
326
  pub const Error = Writer.Error;
238
327
 
239
- pub fn move(self: Self, source: ID, m: anytype, target: ID, from: anytype) Error!void {
328
+ // source: ID, move: <Move>, target: ID, from?: <Move>
329
+ pub fn move(self: Self, args: anytype) Error!void {
240
330
  if (!enabled) return;
241
331
 
242
- assert(m != .None);
332
+ assert(args[1] != .None);
243
333
  try self.writer.writeAll(&.{
244
334
  @intFromEnum(ArgType.Move),
245
- @as(u8, @bitCast(source)),
246
- @intFromEnum(m),
247
- @as(u8, @bitCast(target)),
248
- });
249
- const none = &[_]u8{@intFromEnum(Move.None)};
250
- try self.writer.writeAll(switch (@typeInfo(@TypeOf(from))) {
251
- .Null => none,
252
- .Optional => if (from) |f| blk: {
253
- assert(@intFromEnum(f) != 0);
254
- break :blk &[_]u8{ @intFromEnum(Move.From), @intFromEnum(f) };
255
- } else none,
256
- else => blk: {
257
- assert(@intFromEnum(from) != 0);
258
- break :blk &[_]u8{ @intFromEnum(Move.From), @intFromEnum(from) };
259
- },
335
+ @as(u8, @bitCast(args[0])),
336
+ @intFromEnum(args[1]),
337
+ @as(u8, @bitCast(args[2])),
260
338
  });
339
+ try self.writer.writeAll(if (args.len < 4 or @intFromEnum(args[3]) == 0)
340
+ &.{@intFromEnum(Move.None)}
341
+ else
342
+ &.{ @intFromEnum(Move.From), @intFromEnum(args[3]) });
261
343
  }
262
344
 
263
- pub fn switched(self: Self, ident: ID, pokemon: anytype) Error!void {
264
- if (!enabled) return;
265
-
266
- try self.writer.writeAll(&.{ @intFromEnum(ArgType.Switch), @as(u8, @bitCast(ident)) });
267
- try self.writer.writeAll(&.{ @intFromEnum(pokemon.species), pokemon.level });
268
- try self.writer.writeIntNative(u16, pokemon.hp);
269
- try self.writer.writeIntNative(u16, pokemon.stats.hp);
270
- try self.writer.writeAll(&.{pokemon.status});
345
+ // ident: ID, pokemon: <Pokemon>
346
+ pub fn switched(self: Self, args: anytype) Error!void {
347
+ return switchDrag(self, .Switch, args);
271
348
  }
272
349
 
273
- pub fn cant(self: Self, ident: ID, reason: Cant) Error!void {
350
+ // ident: ID, pokemon: <Pokemon>
351
+ fn switchDrag(self: Self, arg: ArgType, args: anytype) Error!void {
274
352
  if (!enabled) return;
275
353
 
276
- assert(reason != .Disable);
277
- try self.writer.writeAll(&.{
278
- @intFromEnum(ArgType.Cant),
279
- @as(u8, @bitCast(ident)),
280
- @intFromEnum(reason),
281
- });
354
+ try self.writer.writeAll(&.{ @intFromEnum(arg), @as(u8, @bitCast(args[0])) });
355
+ if (@hasField(@TypeOf(args[1].*), "dvs")) {
356
+ try self.writer.writeAll(&.{
357
+ @intFromEnum(args[1].species),
358
+ @intFromEnum(args[1].dvs.gender),
359
+ args[1].level,
360
+ });
361
+ } else {
362
+ try self.writer.writeAll(&.{ @intFromEnum(args[1].species), args[1].level });
363
+ }
364
+ try self.writer.writeInt(u16, args[1].hp, endian);
365
+ try self.writer.writeInt(u16, args[1].stats.hp, endian);
366
+ try self.writer.writeAll(&.{args[1].status});
282
367
  }
283
368
 
284
- pub fn disabled(self: Self, ident: ID, m: anytype) Error!void {
369
+ // ident: ID, reason: Cant
370
+ // ident: ID, reason: .Disable, move: <Move>
371
+ pub fn cant(self: Self, args: anytype) Error!void {
285
372
  if (!enabled) return;
286
373
 
374
+ assert(args[1] != .Disable or args.len == 3);
287
375
  try self.writer.writeAll(&.{
288
376
  @intFromEnum(ArgType.Cant),
289
- @as(u8, @bitCast(ident)),
290
- @intFromEnum(Cant.Disable),
291
- @intFromEnum(m),
377
+ @as(u8, @bitCast(args[0])),
378
+ @intFromEnum(@as(Cant, args[1])),
292
379
  });
380
+ if (args.len == 3) try self.writer.writeByte(@intFromEnum(args[2]));
293
381
  }
294
382
 
295
- pub fn faint(self: Self, ident: ID, done: bool) Error!void {
383
+ // ident: ID, done: bool
384
+ pub fn faint(self: Self, args: anytype) Error!void {
296
385
  if (!enabled) return;
297
386
 
298
- try self.writer.writeAll(&.{ @intFromEnum(ArgType.Faint), @as(u8, @bitCast(ident)) });
299
- if (done) try self.writer.writeByte(@intFromEnum(ArgType.None));
387
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.Faint), @as(u8, @bitCast(args[0])) });
388
+ if (args[1]) try self.writer.writeByte(@intFromEnum(ArgType.None));
300
389
  }
301
390
 
302
- pub fn turn(self: Self, num: u16) Error!void {
391
+ // num: u16
392
+ pub fn turn(self: Self, args: anytype) Error!void {
303
393
  if (!enabled) return;
304
394
 
305
395
  try self.writer.writeByte(@intFromEnum(ArgType.Turn));
306
- try self.writer.writeIntNative(u16, num);
396
+ try self.writer.writeInt(u16, args[0], endian);
307
397
  try self.writer.writeByte(@intFromEnum(ArgType.None));
308
398
  }
309
399
 
310
- pub fn win(self: Self, player: Player) Error!void {
400
+ // player: Player
401
+ pub fn win(self: Self, args: anytype) Error!void {
311
402
  if (!enabled) return;
312
403
 
313
404
  try self.writer.writeAll(&.{
314
405
  @intFromEnum(ArgType.Win),
315
- @intFromEnum(player),
406
+ @intFromEnum(@as(Player, args[0])),
316
407
  @intFromEnum(ArgType.None),
317
408
  });
318
409
  }
319
410
 
320
- pub fn tie(self: Self) Error!void {
411
+ // -
412
+ pub fn tie(self: Self, _: anytype) Error!void {
321
413
  if (!enabled) return;
322
414
 
323
415
  try self.writer.writeAll(&.{ @intFromEnum(ArgType.Tie), @intFromEnum(ArgType.None) });
324
416
  }
325
417
 
326
- pub fn damage(self: Self, ident: ID, pokemon: anytype, reason: Damage) Error!void {
418
+ // ident: ID, pokemon: <Pokemon>, reason: Damage
419
+ // ident: ID, pokemon: <Pokemon>, reason: .RecoilOf, source: ID
420
+ pub fn damage(self: Self, args: anytype) Error!void {
421
+ if (!enabled) return;
422
+
423
+ assert(args[2] != .RecoilOf or args.len == 4);
424
+ try self.writer.writeAll(&.{
425
+ @intFromEnum(ArgType.Damage),
426
+ @as(u8, @bitCast(args[0])),
427
+ });
428
+ try self.writer.writeInt(u16, args[1].hp, endian);
429
+ try self.writer.writeInt(u16, args[1].stats.hp, endian);
430
+ try self.writer.writeAll(&.{ args[1].status, @intFromEnum(@as(Damage, args[2])) });
431
+ if (args.len == 4) try self.writer.writeByte(@as(u8, @bitCast(args[3])));
432
+ }
433
+
434
+ // ident: ID, pokemon: <Pokemon>, reason: Heal
435
+ // ident: ID, pokemon: <Pokemon>, reason: .Drain, target: ID
436
+ pub fn heal(self: Self, args: anytype) Error!void {
327
437
  if (!enabled) return;
328
438
 
329
- assert(@intFromEnum(reason) <= @intFromEnum(Damage.LeechSeed));
330
- try self.writer.writeAll(&.{ @intFromEnum(ArgType.Damage), @as(u8, @bitCast(ident)) });
331
- try self.writer.writeIntNative(u16, pokemon.hp);
332
- try self.writer.writeIntNative(u16, pokemon.stats.hp);
333
- try self.writer.writeAll(&.{ pokemon.status, @intFromEnum(reason) });
439
+ assert(args[2] != .Drain or args.len == 4);
440
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.Heal), @as(u8, @bitCast(args[0])) });
441
+ try self.writer.writeInt(u16, args[1].hp, endian);
442
+ try self.writer.writeInt(u16, args[1].stats.hp, endian);
443
+ try self.writer.writeAll(&.{ args[1].status, @intFromEnum(@as(Heal, args[2])) });
444
+ if (args.len == 4) try self.writer.writeByte(@as(u8, @bitCast(args[3])));
334
445
  }
335
446
 
336
- pub fn damageOf(
337
- self: Self,
338
- ident: ID,
339
- pokemon: anytype,
340
- reason: Damage,
341
- source: ID,
342
- ) Error!void {
447
+ // ident: ID, value: u8, reason: Status
448
+ // ident: ID, value: u8, reason: .From, m: <Move>
449
+ pub fn status(self: Self, args: anytype) Error!void {
343
450
  if (!enabled) return;
344
451
 
345
- assert(@intFromEnum(reason) == @intFromEnum(Damage.RecoilOf));
346
- try self.writer.writeAll(&.{ @intFromEnum(ArgType.Damage), @as(u8, @bitCast(ident)) });
347
- try self.writer.writeIntNative(u16, pokemon.hp);
348
- try self.writer.writeIntNative(u16, pokemon.stats.hp);
452
+ assert(args[2] != .From or args.len == 4);
349
453
  try self.writer.writeAll(&.{
350
- pokemon.status,
351
- @intFromEnum(reason),
352
- @as(u8, @bitCast(source)),
454
+ @intFromEnum(ArgType.Status),
455
+ @as(u8, @bitCast(args[0])),
456
+ args[1],
457
+ @intFromEnum(@as(Status, args[2])),
353
458
  });
459
+ if (args.len == 4) try self.writer.writeByte(@intFromEnum(args[3]));
354
460
  }
355
461
 
356
- pub fn heal(self: Self, ident: ID, pokemon: anytype, reason: Heal) Error!void {
462
+ // ident: ID, value: u8, reason: CureStatus
463
+ pub fn curestatus(self: Self, args: anytype) Error!void {
357
464
  if (!enabled) return;
358
465
 
359
- assert(reason != .Drain);
360
- try self.writer.writeAll(&.{ @intFromEnum(ArgType.Heal), @as(u8, @bitCast(ident)) });
361
- try self.writer.writeIntNative(u16, pokemon.hp);
362
- try self.writer.writeIntNative(u16, pokemon.stats.hp);
363
- try self.writer.writeAll(&.{ pokemon.status, @intFromEnum(reason) });
466
+ try self.writer.writeAll(&.{
467
+ @intFromEnum(ArgType.CureStatus),
468
+ @as(u8, @bitCast(args[0])),
469
+ args[1],
470
+ @intFromEnum(@as(CureStatus, args[2])),
471
+ });
364
472
  }
365
473
 
366
- pub fn drain(self: Self, source: ID, pokemon: anytype, target: ID) Error!void {
474
+ // ident: ID, reason: Boost, num: i8
475
+ pub fn boost(self: Self, args: anytype) Error!void {
367
476
  if (!enabled) return;
368
477
 
369
- try self.writer.writeAll(&.{ @intFromEnum(ArgType.Heal), @as(u8, @bitCast(source)) });
370
- try self.writer.writeIntNative(u16, pokemon.hp);
371
- try self.writer.writeIntNative(u16, pokemon.stats.hp);
478
+ assert(args[2] != 0 and (args[1] != .Rage or args[2] > 0));
372
479
  try self.writer.writeAll(&.{
373
- pokemon.status,
374
- @intFromEnum(Heal.Drain),
375
- @as(u8, @bitCast(target)),
480
+ @intFromEnum(ArgType.Boost),
481
+ @as(u8, @bitCast(args[0])),
482
+ @intFromEnum(@as(Boost, args[1])),
483
+ @as(u8, @intCast(@as(i8, args[2]) + 6)),
376
484
  });
377
485
  }
378
486
 
379
- pub fn status(self: Self, ident: ID, value: u8, reason: Status) Error!void {
487
+ // -
488
+ pub fn clearallboost(self: Self, _: anytype) Error!void {
380
489
  if (!enabled) return;
381
490
 
382
- assert(reason != .From);
383
- try self.writer.writeAll(&.{
384
- @intFromEnum(ArgType.Status),
385
- @as(u8, @bitCast(ident)),
386
- value,
387
- @intFromEnum(reason),
388
- });
491
+ try self.writer.writeAll(&.{@intFromEnum(ArgType.ClearAllBoost)});
389
492
  }
390
493
 
391
- pub fn statusFrom(self: Self, ident: ID, value: u8, m: anytype) Error!void {
494
+ // ident: ID, reason: Fail
495
+ pub fn fail(self: Self, args: anytype) Error!void {
392
496
  if (!enabled) return;
393
497
 
394
498
  try self.writer.writeAll(&.{
395
- @intFromEnum(ArgType.Status),
396
- @as(u8, @bitCast(ident)),
397
- value,
398
- @intFromEnum(Status.From),
399
- @intFromEnum(m),
499
+ @intFromEnum(ArgType.Fail),
500
+ @as(u8, @bitCast(args[0])),
501
+ @intFromEnum(@as(Fail, args[1])),
400
502
  });
401
503
  }
402
504
 
403
- pub fn curestatus(self: Self, ident: ID, value: u8, reason: CureStatus) Error!void {
505
+ // source: ID
506
+ pub fn miss(self: Self, args: anytype) Error!void {
507
+ if (!enabled) return;
508
+
509
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.Miss), @as(u8, @bitCast(args[0])) });
510
+ }
511
+
512
+ // ident: ID, num: u8
513
+ pub fn hitcount(self: Self, args: anytype) Error!void {
404
514
  if (!enabled) return;
405
515
 
406
516
  try self.writer.writeAll(&.{
407
- @intFromEnum(ArgType.CureStatus),
408
- @as(u8, @bitCast(ident)),
409
- value,
410
- @intFromEnum(reason),
517
+ @intFromEnum(ArgType.HitCount),
518
+ @as(u8, @bitCast(args[0])),
519
+ args[1],
411
520
  });
412
521
  }
413
522
 
414
- pub fn boost(self: Self, ident: ID, reason: Boost, num: i8) Error!void {
523
+ // source: ID, m: <Move>
524
+ pub fn prepare(self: Self, args: anytype) Error!void {
415
525
  if (!enabled) return;
416
526
 
417
- assert(num != 0 and (reason != .Rage or num > 0));
418
527
  try self.writer.writeAll(&.{
419
- @intFromEnum(ArgType.Boost),
420
- @as(u8, @bitCast(ident)),
421
- @intFromEnum(reason),
422
- @as(u8, @intCast(num + 6)),
528
+ @intFromEnum(ArgType.Prepare),
529
+ @as(u8, @bitCast(args[0])),
530
+ @intFromEnum(args[1]),
423
531
  });
424
532
  }
425
533
 
426
- pub fn clearallboost(self: Self) Error!void {
534
+ // ident: ID
535
+ pub fn mustrecharge(self: Self, args: anytype) Error!void {
427
536
  if (!enabled) return;
428
537
 
429
- try self.writer.writeAll(&.{@intFromEnum(ArgType.ClearAllBoost)});
538
+ try self.writer.writeAll(&.{
539
+ @intFromEnum(ArgType.MustRecharge),
540
+ @as(u8, @bitCast(args[0])),
541
+ });
430
542
  }
431
543
 
432
- pub fn fail(self: Self, ident: ID, reason: Fail) Error!void {
544
+ // ident: ID, reason: Activate
545
+ // ident: ID, reason: .SubstituteBlock, m: <Move>
546
+ // ident: ID, reason: .Magnitude, num: u8
547
+ pub fn activate(self: Self, args: anytype) Error!void {
433
548
  if (!enabled) return;
434
549
 
550
+ assert(args.len <= 4);
551
+ const reason: Activate = args[1];
435
552
  try self.writer.writeAll(&.{
436
- @intFromEnum(ArgType.Fail),
437
- @as(u8, @bitCast(ident)),
553
+ @intFromEnum(ArgType.Activate),
554
+ @as(u8, @bitCast(args[0])),
438
555
  @intFromEnum(reason),
439
556
  });
557
+ if (args.len == 3) {
558
+ if (reason == .SubstituteBlock) {
559
+ try self.writer.writeByte(@intFromEnum(args[2]));
560
+ } else {
561
+ assert(reason == .Magnitude);
562
+ try self.writer.writeByte(args[2]);
563
+ }
564
+ }
440
565
  }
441
566
 
442
- pub fn miss(self: Self, source: ID) Error!void {
567
+ // -
568
+ pub fn fieldactivate(self: Self, _: anytype) Error!void {
443
569
  if (!enabled) return;
444
570
 
445
- try self.writer.writeAll(&.{ @intFromEnum(ArgType.Miss), @as(u8, @bitCast(source)) });
571
+ try self.writer.writeAll(&.{@intFromEnum(ArgType.FieldActivate)});
446
572
  }
447
573
 
448
- pub fn hitcount(self: Self, ident: ID, num: u8) Error!void {
574
+ // ident: ID, reason: Start
575
+ // ident: ID, reason: .Disable | .Mimic | .Encore, m: <Move>
576
+ // ident: ID, reason: .TypeChange, types: <Types>, target: ID
577
+ pub fn start(self: Self, args: anytype) Error!void {
449
578
  if (!enabled) return;
450
579
 
580
+ assert(args.len <= 4);
581
+ const reason: Start = args[1];
451
582
  try self.writer.writeAll(&.{
452
- @intFromEnum(ArgType.HitCount),
453
- @as(u8, @bitCast(ident)),
454
- num,
583
+ @intFromEnum(ArgType.Start),
584
+ @as(u8, @bitCast(args[0])),
585
+ @intFromEnum(reason),
455
586
  });
587
+ if (args.len == 3) {
588
+ assert(reason == .Disable or reason == .Mimic or reason == .Encore);
589
+ try self.writer.writeByte(@intFromEnum(args[2]));
590
+ } else if (args.len == 4) {
591
+ assert(reason == .TypeChange);
592
+ // FIXME: u16 types in Generation II
593
+ try self.writer.writeAll(&.{
594
+ @as(u8, @bitCast(args[2])),
595
+ @as(u8, @bitCast(args[3])),
596
+ });
597
+ }
456
598
  }
457
599
 
458
- pub fn prepare(self: Self, source: ID, m: anytype) Error!void {
600
+ // ident: ID, reason: End
601
+ pub fn end(self: Self, args: anytype) Error!void {
459
602
  if (!enabled) return;
460
603
 
461
604
  try self.writer.writeAll(&.{
462
- @intFromEnum(ArgType.Prepare),
463
- @as(u8, @bitCast(source)),
464
- @intFromEnum(m),
605
+ @intFromEnum(ArgType.End),
606
+ @as(u8, @bitCast(args[0])),
607
+ @intFromEnum(@as(End, args[1])),
465
608
  });
466
609
  }
467
610
 
468
- pub fn mustrecharge(self: Self, ident: ID) Error!void {
611
+ // -
612
+ pub fn ohko(self: Self, _: anytype) Error!void {
613
+ if (!enabled) return;
614
+
615
+ try self.writer.writeAll(&.{@intFromEnum(ArgType.OHKO)});
616
+ }
617
+
618
+ // ident: ID
619
+ pub fn crit(self: Self, args: anytype) Error!void {
620
+ if (!enabled) return;
621
+
622
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.Crit), @as(u8, @bitCast(args[0])) });
623
+ }
624
+
625
+ // ident: ID
626
+ pub fn supereffective(self: Self, args: anytype) Error!void {
469
627
  if (!enabled) return;
470
628
 
471
629
  try self.writer.writeAll(&.{
472
- @intFromEnum(ArgType.MustRecharge),
473
- @as(u8, @bitCast(ident)),
630
+ @intFromEnum(ArgType.SuperEffective),
631
+ @as(u8, @bitCast(args[0])),
474
632
  });
475
633
  }
476
634
 
477
- pub fn activate(self: Self, ident: ID, reason: Activate) Error!void {
635
+ // ident: ID
636
+ pub fn resisted(self: Self, args: anytype) Error!void {
478
637
  if (!enabled) return;
479
638
 
480
639
  try self.writer.writeAll(&.{
481
- @intFromEnum(ArgType.Activate),
482
- @as(u8, @bitCast(ident)),
483
- @intFromEnum(reason),
640
+ @intFromEnum(ArgType.Resisted),
641
+ @as(u8, @bitCast(args[0])),
484
642
  });
485
643
  }
486
644
 
487
- pub fn fieldactivate(self: Self) Error!void {
645
+ // ident: ID, reason: Immune
646
+ pub fn immune(self: Self, args: anytype) Error!void {
488
647
  if (!enabled) return;
489
648
 
490
- try self.writer.writeAll(&.{@intFromEnum(ArgType.FieldActivate)});
649
+ try self.writer.writeAll(&.{
650
+ @intFromEnum(ArgType.Immune),
651
+ @as(u8, @bitCast(args[0])),
652
+ @intFromEnum(@as(Immune, args[1])),
653
+ });
491
654
  }
492
655
 
493
- pub fn start(self: Self, ident: ID, reason: Start) Error!void {
656
+ // source: ID, target: ID
657
+ pub fn transform(self: Self, args: anytype) Error!void {
494
658
  if (!enabled) return;
495
659
 
496
- assert(@intFromEnum(reason) < @intFromEnum(Start.TypeChange));
497
660
  try self.writer.writeAll(&.{
498
- @intFromEnum(ArgType.Start),
499
- @as(u8, @bitCast(ident)),
500
- @intFromEnum(reason),
661
+ @intFromEnum(ArgType.Transform),
662
+ @as(u8, @bitCast(args[0])),
663
+ @as(u8, @bitCast(args[1])),
501
664
  });
502
665
  }
503
666
 
504
- pub fn typechange(self: Self, source: ID, types: anytype, target: ID) Error!void {
667
+ // ident: ID, pokemon: <Pokemon>
668
+ pub fn drag(self: Self, args: anytype) Error!void {
669
+ return switchDrag(self, .Drag, args);
670
+ }
671
+
672
+ // target: ID, item: <Item>, source: ID
673
+ pub fn item(self: Self, args: anytype) Error!void {
505
674
  if (!enabled) return;
506
675
 
507
676
  try self.writer.writeAll(&.{
508
- @intFromEnum(ArgType.Start),
509
- @as(u8, @bitCast(source)),
510
- @intFromEnum(Start.TypeChange),
511
- @as(u8, @bitCast(types)),
512
- @as(u8, @bitCast(target)),
677
+ @intFromEnum(ArgType.Item),
678
+ @as(u8, @bitCast(args[0])),
679
+ @intFromEnum(args[1]),
680
+ @as(u8, @bitCast(args[2])),
513
681
  });
514
682
  }
515
683
 
516
- pub fn startEffect(self: Self, ident: ID, reason: Start, m: anytype) Error!void {
684
+ // ident: ID, item: <Item>, reason: EndItem
685
+ pub fn enditem(self: Self, args: anytype) Error!void {
517
686
  if (!enabled) return;
518
687
 
519
- assert(@intFromEnum(reason) > @intFromEnum(Start.TypeChange));
520
688
  try self.writer.writeAll(&.{
521
- @intFromEnum(ArgType.Start),
522
- @as(u8, @bitCast(ident)),
523
- @intFromEnum(reason),
524
- @intFromEnum(m),
689
+ @intFromEnum(ArgType.EndItem),
690
+ @as(u8, @bitCast(args[0])),
691
+ @intFromEnum(args[1]),
692
+ @intFromEnum(@as(EndItem, args[2])),
525
693
  });
526
694
  }
527
695
 
528
- pub fn end(self: Self, ident: ID, reason: End) Error!void {
696
+ // source: ID
697
+ pub fn cureteam(self: Self, args: anytype) Error!void {
529
698
  if (!enabled) return;
530
699
 
531
700
  try self.writer.writeAll(&.{
532
- @intFromEnum(ArgType.End),
533
- @as(u8, @bitCast(ident)),
534
- @intFromEnum(reason),
701
+ @intFromEnum(ArgType.CureTeam),
702
+ @as(u8, @bitCast(args[0])),
535
703
  });
536
704
  }
537
705
 
538
- pub fn ohko(self: Self) Error!void {
706
+ // ident: ID, pokemon: <Pokemon>, reason: SetHP
707
+ pub fn sethp(self: Self, args: anytype) Error!void {
539
708
  if (!enabled) return;
540
709
 
541
- try self.writer.writeAll(&.{@intFromEnum(ArgType.OHKO)});
710
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.SetHP), @as(u8, @bitCast(args[0])) });
711
+ try self.writer.writeInt(u16, args[1].hp, endian);
712
+ try self.writer.writeInt(u16, args[1].stats.hp, endian);
713
+ try self.writer.writeAll(&.{args[1].status});
714
+ try self.writer.writeByte(@intFromEnum(@as(SetHP, args[2])));
542
715
  }
543
716
 
544
- pub fn crit(self: Self, ident: ID) Error!void {
717
+ // ident: ID, num: i8
718
+ pub fn setboost(self: Self, args: anytype) Error!void {
545
719
  if (!enabled) return;
546
720
 
547
- try self.writer.writeAll(&.{ @intFromEnum(ArgType.Crit), @as(u8, @bitCast(ident)) });
721
+ try self.writer.writeAll(&.{
722
+ @intFromEnum(ArgType.SetBoost),
723
+ @as(u8, @bitCast(args[0])),
724
+ @as(u8, @intCast(args[1] + 6)),
725
+ });
548
726
  }
549
727
 
550
- pub fn supereffective(self: Self, ident: ID) Error!void {
728
+ // source: ID, target: ID
729
+ pub fn copyboost(self: Self, args: anytype) Error!void {
551
730
  if (!enabled) return;
552
731
 
553
732
  try self.writer.writeAll(&.{
554
- @intFromEnum(ArgType.SuperEffective),
555
- @as(u8, @bitCast(ident)),
733
+ @intFromEnum(ArgType.CopyBoost),
734
+ @as(u8, @bitCast(args[0])),
735
+ @as(u8, @bitCast(args[1])),
556
736
  });
557
737
  }
558
738
 
559
- pub fn resisted(self: Self, ident: ID) Error!void {
739
+ // player: Player, reason: Side
740
+ pub fn sidestart(self: Self, args: anytype) Error!void {
560
741
  if (!enabled) return;
561
742
 
562
743
  try self.writer.writeAll(&.{
563
- @intFromEnum(ArgType.Resisted),
564
- @as(u8, @bitCast(ident)),
744
+ @intFromEnum(ArgType.SideStart),
745
+ @intFromEnum(@as(Player, args[0])),
746
+ @intFromEnum(@as(Side, args[1])),
565
747
  });
566
748
  }
567
749
 
568
- pub fn immune(self: Self, ident: ID, reason: Immune) Error!void {
750
+ // player: Player, reason: Side
751
+ // player: Player, reason: .Spikes, source: ID
752
+ pub fn sideend(self: Self, args: anytype) Error!void {
569
753
  if (!enabled) return;
570
754
 
755
+ assert(args[1] != .Spikes or args.len == 3);
571
756
  try self.writer.writeAll(&.{
572
- @intFromEnum(ArgType.Immune),
573
- @as(u8, @bitCast(ident)),
574
- @intFromEnum(reason),
757
+ @intFromEnum(ArgType.SideEnd),
758
+ @intFromEnum(@as(Player, args[0])),
759
+ @intFromEnum(@as(Side, args[1])),
575
760
  });
761
+ if (args.len == 3) try self.writer.writeByte(@as(u8, @bitCast(args[2])));
762
+ }
763
+
764
+ // ident: ID, move: <Move>
765
+ pub fn singlemove(self: Self, args: anytype) Error!void {
766
+ return single(self, .SingleMove, args);
767
+ }
768
+
769
+ // ident: ID, move: <Move>
770
+ pub fn singleturn(self: Self, args: anytype) Error!void {
771
+ return single(self, .SingleTurn, args);
576
772
  }
577
773
 
578
- pub fn transform(self: Self, source: ID, target: ID) Error!void {
774
+ // ident: ID, move: <Move>
775
+ fn single(self: Self, arg: ArgType, args: anytype) Error!void {
579
776
  if (!enabled) return;
580
777
 
581
778
  try self.writer.writeAll(&.{
582
- @intFromEnum(ArgType.Transform),
583
- @as(u8, @bitCast(source)),
584
- @as(u8, @bitCast(target)),
779
+ @intFromEnum(arg),
780
+ @as(u8, @bitCast(args[0])),
781
+ @intFromEnum(args[1]),
585
782
  });
586
783
  }
587
784
 
588
- pub fn laststill(self: Self) Error!void {
785
+ // weather: <Weather>, reason: Weather
786
+ pub fn weather(self: Self, args: anytype) Error!void {
787
+ if (!enabled) return;
788
+
789
+ assert(!(args[0] == .None and args[1] == .Upkeep));
790
+ try self.writer.writeAll(&.{
791
+ @intFromEnum(ArgType.Weather),
792
+ @intFromEnum(args[0]),
793
+ @intFromEnum(@as(Weather, args[1])),
794
+ });
795
+ }
796
+
797
+ // -
798
+ pub fn laststill(self: Self, _: anytype) Error!void {
589
799
  if (!enabled) return;
590
800
 
591
801
  try self.writer.writeAll(&.{@intFromEnum(ArgType.LastStill)});
592
802
  }
593
803
 
594
- pub fn lastmiss(self: Self) Error!void {
804
+ // -
805
+ pub fn lastmiss(self: Self, _: anytype) Error!void {
595
806
  if (!enabled) return;
596
807
 
597
808
  try self.writer.writeAll(&.{@intFromEnum(ArgType.LastMiss)});
@@ -622,10 +833,10 @@ pub const ByteStream = struct {
622
833
  try self.stream.writeByte(byte);
623
834
  }
624
835
 
625
- pub fn writeIntNative(self: Writer, comptime T: type, value: T) Error!void {
836
+ pub fn writeInt(self: Writer, comptime T: type, v: T, end: std.builtin.Endian) Error!void {
626
837
  // TODO: rework this to write directly to the buffer?
627
- var bytes: [(@typeInfo(T).Int.bits + 7) / 8]u8 = undefined;
628
- std.mem.writeIntNative(T, &bytes, value);
838
+ var bytes: [@divExact(@typeInfo(T).Int.bits, 8)]u8 = undefined;
839
+ std.mem.writeInt(std.math.ByteAlignedInt(@TypeOf(v)), &bytes, v, end);
629
840
  return self.writeAll(&bytes);
630
841
  }
631
842
  };
@@ -645,10 +856,14 @@ pub const ByteStream = struct {
645
856
  }
646
857
  };
647
858
 
648
- pub const Kind = enum { Move, Species, Type, Status };
649
- pub const Formatter = fn (Kind, u8) []const u8;
859
+ pub const Kind = enum { Move, Species, Type, Status, Item, Weather };
650
860
 
651
- pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, color: bool) void {
861
+ pub fn format(
862
+ comptime gen: anytype,
863
+ a: []const u8,
864
+ b: ?[]const u8,
865
+ color: bool,
866
+ ) void {
652
867
  print("\n", .{});
653
868
 
654
869
  var i: usize = 0;
@@ -686,6 +901,18 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
686
901
  .Resisted => "|-resisted|",
687
902
  .Immune => "|-immune|",
688
903
  .Transform => "|-transform|",
904
+ .Drag => "|drag|",
905
+ .Item => "|-item|",
906
+ .EndItem => "|-enditem|",
907
+ .CureTeam => "|-cureteam|",
908
+ .SetHP => "|-sethp|",
909
+ .SetBoost => "|-setboost|",
910
+ .CopyBoost => "|-copyboost|",
911
+ .SideStart => "|-sidestart|",
912
+ .SideEnd => "|-sideend|",
913
+ .SingleMove => "|-singlemove|",
914
+ .SingleTurn => "|-singleturn|",
915
+ .Weather => "|-weather|",
689
916
  else => unreachable,
690
917
  };
691
918
  printc("{s}", .{name}, a, b, &i, 1, color);
@@ -701,33 +928,43 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
701
928
  .Move => {
702
929
  const source = ID.from(@intCast(a[i]));
703
930
  printc(" {s}({d})", .{ @tagName(source.player), source.id }, a, b, &i, 1, color);
704
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
931
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
705
932
  const target = ID.from(@intCast(a[i]));
706
933
  printc(" {s}({d})", .{ @tagName(target.player), target.id }, a, b, &i, 1, color);
707
934
  const reason: Move = @enumFromInt(a[i]);
708
935
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
709
- if (reason == .From) printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
936
+ if (reason == .From) {
937
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
938
+ }
710
939
  },
711
- .Switch => {
940
+ .Switch, .Drag => {
712
941
  const id = ID.from(@intCast(a[i]));
713
942
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
714
- printc(" {s}", .{formatter(.Species, a[i])}, a, b, &i, 1, color);
943
+ printc(" {s}", .{formatter(gen, .Species, a[i])}, a, b, &i, 1, color);
944
+ if (@hasDecl(gen, "Gender")) {
945
+ if (a[i] == N(gen.Gender.Unknown)) {
946
+ i += 1;
947
+ } else {
948
+ const gender = if (a[i] == N(gen.Gender.Male)) "M" else "F";
949
+ printc(", {s}", .{gender}, a, b, &i, 1, color);
950
+ }
951
+ }
715
952
  printc(" L{d}", .{a[i]}, a, b, &i, 1, color);
716
953
  switch (endian) {
717
- .Big => {
954
+ .big => {
718
955
  var hp = @as(u16, a[i]) << 8 | @as(u16, a[i + 1]);
719
956
  printc(" {d}", .{hp}, a, b, &i, 2, color);
720
957
  hp = @as(u16, a[i]) << 8 | @as(u16, a[i + 1]);
721
958
  printc("/{d}", .{hp}, a, b, &i, 2, color);
722
959
  },
723
- .Little => {
960
+ .little => {
724
961
  var hp = @as(u16, a[i + 1]) << 8 | @as(u16, a[i]);
725
962
  printc(" {d}", .{hp}, a, b, &i, 2, color);
726
963
  hp = @as(u16, a[i + 1]) << 8 | @as(u16, a[i]);
727
964
  printc("/{d}", .{hp}, a, b, &i, 2, color);
728
965
  },
729
966
  }
730
- printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
967
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
731
968
  },
732
969
  .Cant => {
733
970
  const id = ID.from(@intCast(a[i]));
@@ -735,7 +972,7 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
735
972
  const reason: Cant = @enumFromInt(a[i]);
736
973
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
737
974
  if (reason == .Disable) {
738
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
975
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
739
976
  }
740
977
  },
741
978
  .Faint,
@@ -744,14 +981,15 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
744
981
  .Crit,
745
982
  .SuperEffective,
746
983
  .Resisted,
984
+ .CureTeam,
747
985
  => {
748
986
  const id = ID.from(@intCast(a[i]));
749
987
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
750
988
  },
751
989
  .Turn => {
752
990
  const turn = switch (endian) {
753
- .Big => @as(u16, a[i]) << 8 | @as(u16, a[i + 1]),
754
- .Little => @as(u16, a[i + 1]) << 8 | @as(u16, a[i]),
991
+ .big => @as(u16, a[i]) << 8 | @as(u16, a[i + 1]),
992
+ .little => @as(u16, a[i + 1]) << 8 | @as(u16, a[i]),
755
993
  };
756
994
  printc(" {d}", .{turn}, a, b, &i, 2, color);
757
995
  },
@@ -762,20 +1000,20 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
762
1000
  var id = ID.from(@intCast(a[i]));
763
1001
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
764
1002
  switch (endian) {
765
- .Big => {
1003
+ .big => {
766
1004
  var hp = @as(u16, a[i]) << 8 | @as(u16, a[i + 1]);
767
1005
  printc(" {d}", .{hp}, a, b, &i, 2, color);
768
1006
  hp = @as(u16, a[i]) << 8 | @as(u16, a[i + 1]);
769
1007
  printc("/{d}", .{hp}, a, b, &i, 2, color);
770
1008
  },
771
- .Little => {
1009
+ .little => {
772
1010
  var hp = @as(u16, a[i + 1]) << 8 | @as(u16, a[i]);
773
1011
  printc(" {d}", .{hp}, a, b, &i, 2, color);
774
1012
  hp = @as(u16, a[i + 1]) << 8 | @as(u16, a[i]);
775
1013
  printc("/{d}", .{hp}, a, b, &i, 2, color);
776
1014
  },
777
1015
  }
778
- printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
1016
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
779
1017
  if (arg == .Damage) {
780
1018
  const reason: Damage = @enumFromInt(a[i]);
781
1019
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
@@ -795,15 +1033,17 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
795
1033
  .Status => {
796
1034
  const id = ID.from(@intCast(a[i]));
797
1035
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
798
- printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
1036
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
799
1037
  const reason: Status = @enumFromInt(a[i]);
800
1038
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
801
- if (reason == .From) printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
1039
+ if (reason == .From) {
1040
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
1041
+ }
802
1042
  },
803
1043
  .CureStatus => {
804
1044
  const id = ID.from(@intCast(a[i]));
805
1045
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
806
- printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
1046
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
807
1047
  const reason: CureStatus = @enumFromInt(a[i]);
808
1048
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
809
1049
  },
@@ -818,20 +1058,26 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
818
1058
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
819
1059
  printc(" {s}", .{@tagName(@as(Fail, @enumFromInt(a[i])))}, a, b, &i, 1, color);
820
1060
  },
821
- .HitCount => {
1061
+ .HitCount, .SetBoost => {
822
1062
  const id = ID.from(@intCast(a[i]));
823
1063
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
824
1064
  printc(" {d}", .{a[i]}, a, b, &i, 1, color);
825
1065
  },
826
- .Prepare => {
1066
+ .Prepare, .SingleMove, .SingleTurn => {
827
1067
  const id = ID.from(@intCast(a[i]));
828
1068
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
829
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
1069
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
830
1070
  },
831
1071
  .Activate => {
832
1072
  const id = ID.from(@intCast(a[i]));
833
1073
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
834
- printc(" {s}", .{@tagName(@as(Activate, @enumFromInt(a[i])))}, a, b, &i, 1, color);
1074
+ const reason: Activate = @enumFromInt(a[i]);
1075
+ printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
1076
+ if (reason == .SubstituteBlock) {
1077
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
1078
+ } else if (reason == .Magnitude) {
1079
+ printc(" {d}", .{a[i]}, a, b, &i, 1, color);
1080
+ }
835
1081
  },
836
1082
  .Start => {
837
1083
  const id = ID.from(@intCast(a[i]));
@@ -839,14 +1085,15 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
839
1085
  const reason = a[i];
840
1086
  printc(" {s}", .{@tagName(@as(Start, @enumFromInt(reason)))}, a, b, &i, 1, color);
841
1087
  if (@as(Start, @enumFromInt(reason)) == .TypeChange) {
1088
+ // FIXME
842
1089
  const types = @as(gen1.Types, @bitCast(a[i]));
843
1090
  const args = .{
844
- formatter(.Type, @intFromEnum(types.type1)),
845
- formatter(.Type, @intFromEnum(types.type2)),
1091
+ formatter(gen, .Type, @intFromEnum(types.type1)),
1092
+ formatter(gen, .Type, @intFromEnum(types.type2)),
846
1093
  };
847
1094
  printc(" {s}/{s}", args, a, b, &i, 1, color);
848
1095
  } else if (reason >= @intFromEnum(Start.Disable)) {
849
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
1096
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
850
1097
  }
851
1098
  },
852
1099
  .End => {
@@ -859,12 +1106,64 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
859
1106
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
860
1107
  printc(" {s}", .{@tagName(@as(Immune, @enumFromInt(a[i])))}, a, b, &i, 1, color);
861
1108
  },
862
- .Transform => {
1109
+ .Transform, .CopyBoost => {
863
1110
  const source = ID.from(@intCast(a[i]));
864
1111
  printc(" {s}({d})", .{ @tagName(source.player), source.id }, a, b, &i, 1, color);
865
1112
  const target = ID.from(@intCast(a[i]));
866
1113
  printc(" {s}({d})", .{ @tagName(target.player), target.id }, a, b, &i, 1, color);
867
1114
  },
1115
+ .Item, .EndItem => {
1116
+ var id = ID.from(@intCast(a[i]));
1117
+ printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
1118
+ printc(" {s}", .{formatter(gen, .Item, a[i])}, a, b, &i, 1, color);
1119
+ if (arg == .Item) {
1120
+ id = ID.from(@intCast(a[i]));
1121
+ printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
1122
+ } else {
1123
+ const reason: EndItem = @enumFromInt(a[i]);
1124
+ printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
1125
+ }
1126
+ },
1127
+ .SetHP => {
1128
+ const id = ID.from(@intCast(a[i]));
1129
+ printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
1130
+ switch (endian) {
1131
+ .big => {
1132
+ var hp = @as(u16, a[i]) << 8 | @as(u16, a[i + 1]);
1133
+ printc(" {d}", .{hp}, a, b, &i, 2, color);
1134
+ hp = @as(u16, a[i]) << 8 | @as(u16, a[i + 1]);
1135
+ printc("/{d}", .{hp}, a, b, &i, 2, color);
1136
+ },
1137
+ .little => {
1138
+ var hp = @as(u16, a[i + 1]) << 8 | @as(u16, a[i]);
1139
+ printc(" {d}", .{hp}, a, b, &i, 2, color);
1140
+ hp = @as(u16, a[i + 1]) << 8 | @as(u16, a[i]);
1141
+ printc("/{d}", .{hp}, a, b, &i, 2, color);
1142
+ },
1143
+ }
1144
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
1145
+ printc(" {s}", .{@tagName(@as(SetHP, @enumFromInt(a[i])))}, a, b, &i, 1, color);
1146
+ },
1147
+ .SideStart => {
1148
+ const id = ID.from(@intCast(a[i]));
1149
+ printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
1150
+ printc(" {s}", .{@tagName(@as(Side, @enumFromInt(a[i])))}, a, b, &i, 1, color);
1151
+ },
1152
+ .SideEnd => {
1153
+ var id = ID.from(@intCast(a[i]));
1154
+ printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
1155
+ const reason: Side = @enumFromInt(a[i]);
1156
+ printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
1157
+ if (reason == .Spikes) {
1158
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
1159
+ id = ID.from(@intCast(a[i]));
1160
+ printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
1161
+ }
1162
+ },
1163
+ .Weather => {
1164
+ printc(" {s}", .{formatter(gen, .Weather, a[i])}, a, b, &i, 1, color);
1165
+ printc(" {s}", .{@tagName(@as(Weather, @enumFromInt(a[i])))}, a, b, &i, 1, color);
1166
+ },
868
1167
  else => unreachable,
869
1168
  }
870
1169
  print("\n", .{});
@@ -896,27 +1195,24 @@ fn printc(
896
1195
  }
897
1196
 
898
1197
  pub fn expectLog(
899
- comptime formatter: Formatter,
1198
+ comptime gen: anytype,
900
1199
  expected: []const u8,
901
1200
  actual: []const u8,
902
1201
  offset: usize,
903
1202
  ) !void {
904
1203
  if (!enabled) return;
905
1204
 
906
- const color = color: {
907
- if (std.process.hasEnvVarConstant("ZIG_DEBUG_COLOR")) {
908
- break :color true;
909
- } else if (std.process.hasEnvVarConstant("NO_COLOR")) {
910
- break :color false;
911
- } else {
912
- break :color std.io.getStdErr().supportsAnsiEscapeCodes();
913
- }
1205
+ const color = if (std.process.hasEnvVarConstant("ZIG_DEBUG_COLOR"))
1206
+ true
1207
+ else switch (std.io.tty.detectConfig(std.io.getStdErr())) {
1208
+ .escape_codes => true,
1209
+ else => false,
914
1210
  };
915
1211
 
916
1212
  expectEqualBytes(expected, actual, offset) catch |err| switch (err) {
917
1213
  error.TestExpectedEqual => {
918
- format(formatter, expected, null, color);
919
- format(formatter, actual, expected, color);
1214
+ format(gen, expected, null, color);
1215
+ format(gen, actual, expected, color);
920
1216
  return err;
921
1217
  },
922
1218
  else => return err,
@@ -942,8 +1238,6 @@ fn expectEqualBytes(expected: []const u8, actual: []const u8, offset: usize) !vo
942
1238
  }
943
1239
  }
944
1240
 
945
- const endian = builtin.target.cpu.arch.endian();
946
-
947
1241
  fn N(e: anytype) u8 {
948
1242
  return @intFromEnum(e);
949
1243
  }
@@ -951,54 +1245,68 @@ fn N(e: anytype) u8 {
951
1245
  const p1 = Player.P1;
952
1246
  const p2 = Player.P2;
953
1247
 
954
- const gen1 = struct {
955
- pub usingnamespace @import("../gen1/data.zig");
956
- pub const helpers = @import("../gen1/helpers.zig");
957
- };
958
-
959
- var buf: [gen1.LOGS_SIZE]u8 = undefined;
960
- var stream = ByteStream{ .buffer = &buf };
1248
+ var buf: [pkmn.LOGS_SIZE]u8 = undefined;
1249
+ var stream: ByteStream = .{ .buffer = &buf };
961
1250
  var log: FixedLog = .{ .writer = stream.writer() };
962
1251
 
963
- const M = gen1.Move;
964
- const S = gen1.Species;
1252
+ const gen1 = pkmn.gen1;
1253
+ const gen2 = pkmn.gen2;
1254
+
1255
+ const M1 = gen1.Move;
1256
+ const S1 = gen1.Species;
1257
+
1258
+ const M2 = gen2.Move;
1259
+ const S2 = gen2.Species;
1260
+ const I2 = gen2.Item;
1261
+ const W2 = gen2.Weather;
965
1262
 
966
1263
  fn expectLog1(expected: []const u8, actual: []const u8) !void {
967
- return expectLog(gen1Formatter, expected, actual, 0);
1264
+ return expectLog(gen1, expected, actual, 0);
1265
+ }
1266
+
1267
+ fn expectLog2(expected: []const u8, actual: []const u8) !void {
1268
+ return expectLog(gen2, expected, actual, 0);
968
1269
  }
969
1270
 
970
- fn gen1Formatter(kind: Kind, byte: u8) []const u8 {
1271
+ fn formatter(comptime gen: anytype, kind: Kind, byte: u8) []const u8 {
971
1272
  return switch (kind) {
972
- .Move => @tagName(@as(M, @enumFromInt(byte))),
973
- .Species => @tagName(@as(S, @enumFromInt(byte))),
974
- .Type => @tagName(@as(gen1.Type, @enumFromInt(byte))),
975
- .Status => gen1.Status.name(byte),
1273
+ .Move => @tagName(@as(gen.Move, @enumFromInt(byte))),
1274
+ .Species => @tagName(@as(gen.Species, @enumFromInt(byte))),
1275
+ .Type => @tagName(@as(gen.Type, @enumFromInt(byte))),
1276
+ .Status => gen.Status.name(byte),
1277
+ .Item => if (@hasDecl(gen, "Item"))
1278
+ @tagName(@as(gen.Item, @enumFromInt(byte)))
1279
+ else
1280
+ unreachable,
1281
+ .Weather => if (@hasDecl(gen, "Weather"))
1282
+ @tagName(@as(gen.Weather, @enumFromInt(byte)))
1283
+ else
1284
+ unreachable,
976
1285
  };
977
1286
  }
978
1287
 
979
1288
  test "|move|" {
980
- try log.move(p2.ident(4), M.Thunderbolt, p1.ident(5), null);
1289
+ try log.move(.{ p2.ident(4), M1.Thunderbolt, p1.ident(5) });
981
1290
  try expectLog1(
982
- &.{ N(ArgType.Move), 0b1100, N(M.Thunderbolt), 0b0101, N(Move.None) },
1291
+ &.{ N(ArgType.Move), 0b1100, N(M1.Thunderbolt), 0b0101, N(Move.None) },
983
1292
  buf[0..5],
984
1293
  );
985
1294
  stream.reset();
986
1295
 
987
- try log.move(p2.ident(4), M.Wrap, p1.ident(5), M.Wrap);
988
- const wrap = N(M.Wrap);
1296
+ try log.move(.{ p2.ident(4), M1.Pound, p1.ident(5), M1.Metronome });
989
1297
  try expectLog1(
990
- &.{ N(ArgType.Move), 0b1100, wrap, 0b0101, N(Move.From), wrap },
1298
+ &.{ N(ArgType.Move), 0b1100, N(M1.Pound), 0b0101, N(Move.From), N(M1.Metronome) },
991
1299
  buf[0..6],
992
1300
  );
993
1301
  stream.reset();
994
1302
 
995
- try log.move(p2.ident(4), M.SkullBash, .{}, null);
996
- try log.laststill();
1303
+ try log.move(.{ p2.ident(4), M1.SkullBash, ID{} });
1304
+ try log.laststill(.{});
997
1305
  try expectLog1(
998
1306
  &.{
999
1307
  N(ArgType.Move),
1000
1308
  0b1100,
1001
- N(M.SkullBash),
1309
+ N(M1.SkullBash),
1002
1310
  0,
1003
1311
  N(Move.None),
1004
1312
  N(ArgType.LastStill),
@@ -1007,13 +1315,13 @@ test "|move|" {
1007
1315
  );
1008
1316
  stream.reset();
1009
1317
 
1010
- try log.move(p2.ident(4), M.Tackle, p1.ident(5), null);
1011
- try log.lastmiss();
1318
+ try log.move(.{ p2.ident(4), M1.Tackle, p1.ident(5) });
1319
+ try log.lastmiss(.{});
1012
1320
  try expectLog1(
1013
1321
  &.{
1014
1322
  N(ArgType.Move),
1015
1323
  0b1100,
1016
- N(M.Tackle),
1324
+ N(M1.Tackle),
1017
1325
  0b0101,
1018
1326
  N(Move.None),
1019
1327
  N(ArgType.LastMiss),
@@ -1029,11 +1337,11 @@ test "|switch|" {
1029
1337
  snorlax.hp = 200;
1030
1338
  snorlax.stats.hp = 400;
1031
1339
  snorlax.status = gen1.Status.init(.PAR);
1032
- try log.switched(p2.ident(3), &snorlax);
1340
+ try log.switched(.{ p2.ident(3), &snorlax });
1033
1341
  const par = 0b1000000;
1034
1342
  var expected: []const u8 = switch (endian) {
1035
- .Big => &.{ N(ArgType.Switch), 0b1011, N(S.Snorlax), 91, 0, 200, 1, 144, par },
1036
- .Little => &.{ N(ArgType.Switch), 0b1011, N(S.Snorlax), 91, 200, 0, 144, 1, par },
1343
+ .big => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 91, 0, 200, 1, 144, par },
1344
+ .little => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 91, 200, 0, 144, 1, par },
1037
1345
  };
1038
1346
  try expectLog1(expected, buf[0..9]);
1039
1347
  stream.reset();
@@ -1041,62 +1349,81 @@ test "|switch|" {
1041
1349
  snorlax.level = 100;
1042
1350
  snorlax.hp = 0;
1043
1351
  snorlax.status = 0;
1044
- try log.switched(p2.ident(3), &snorlax);
1352
+ try log.switched(.{ p2.ident(3), &snorlax });
1045
1353
  expected = switch (endian) {
1046
- .Big => &.{ N(ArgType.Switch), 0b1011, N(S.Snorlax), 100, 0, 0, 1, 144, 0 },
1047
- .Little => &.{ N(ArgType.Switch), 0b1011, N(S.Snorlax), 100, 0, 0, 144, 1, 0 },
1354
+ .big => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 100, 0, 0, 1, 144, 0 },
1355
+ .little => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 100, 0, 0, 144, 1, 0 },
1048
1356
  };
1049
1357
  try expectLog1(expected, buf[0..9]);
1050
1358
  stream.reset();
1051
1359
 
1052
1360
  snorlax.hp = 400;
1053
- try log.switched(p2.ident(3), &snorlax);
1361
+ try log.switched(.{ p2.ident(3), &snorlax });
1054
1362
  expected = switch (endian) {
1055
- .Big => &.{ N(ArgType.Switch), 0b1011, N(S.Snorlax), 100, 1, 144, 1, 144, 0 },
1056
- .Little => &.{ N(ArgType.Switch), 0b1011, N(S.Snorlax), 100, 144, 1, 144, 1, 0 },
1363
+ .big => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 100, 1, 144, 1, 144, 0 },
1364
+ .little => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 100, 144, 1, 144, 1, 0 },
1057
1365
  };
1058
1366
  try expectLog1(expected, buf[0..9]);
1059
1367
  stream.reset();
1368
+
1369
+ var blissey = gen2.helpers.Pokemon.init(.{ .species = .Blissey, .moves = &.{.Splash} });
1370
+ blissey.level = 91;
1371
+ blissey.hp = 200;
1372
+ blissey.stats.hp = 400;
1373
+ blissey.status = gen2.Status.init(.PAR);
1374
+ try log.switched(.{ p2.ident(3), &blissey });
1375
+ expected = &(.{
1376
+ N(ArgType.Switch),
1377
+ 0b1011,
1378
+ N(S2.Blissey),
1379
+ N(gen2.Gender.Female),
1380
+ 91,
1381
+ } ++ switch (endian) {
1382
+ .big => .{ 0, 200, 1, 144, par },
1383
+ .little => .{ 200, 0, 144, 1, par },
1384
+ });
1385
+ try expectLog2(expected, buf[0..10]);
1386
+ stream.reset();
1060
1387
  }
1061
1388
 
1062
1389
  test "|cant|" {
1063
- try log.cant(p2.ident(6), .Bound);
1390
+ try log.cant(.{ p2.ident(6), .Bound });
1064
1391
  try expectLog1(&.{ N(ArgType.Cant), 0b1110, N(Cant.Bound) }, buf[0..3]);
1065
1392
  stream.reset();
1066
1393
 
1067
- try log.disabled(p1.ident(2), M.Earthquake);
1068
- try expectLog1(&.{ N(ArgType.Cant), 2, N(Cant.Disable), N(M.Earthquake) }, buf[0..4]);
1394
+ try log.cant(.{ p1.ident(2), .Disable, M1.Earthquake });
1395
+ try expectLog1(&.{ N(ArgType.Cant), 2, N(Cant.Disable), N(M1.Earthquake) }, buf[0..4]);
1069
1396
  stream.reset();
1070
1397
  }
1071
1398
 
1072
1399
  test "|faint|" {
1073
- try log.faint(p2.ident(2), false);
1400
+ try log.faint(.{ p2.ident(2), false });
1074
1401
  try expectLog1(&.{ N(ArgType.Faint), 0b1010 }, buf[0..2]);
1075
1402
  stream.reset();
1076
1403
 
1077
- try log.faint(p2.ident(2), true);
1404
+ try log.faint(.{ p2.ident(2), true });
1078
1405
  try expectLog1(&.{ N(ArgType.Faint), 0b1010, N(ArgType.None) }, buf[0..3]);
1079
1406
  stream.reset();
1080
1407
  }
1081
1408
 
1082
1409
  test "|turn|" {
1083
- try log.turn(42);
1084
- var expected = switch (endian) {
1085
- .Big => &.{ N(ArgType.Turn), 0, 42, N(ArgType.None) },
1086
- .Little => &.{ N(ArgType.Turn), 42, 0, N(ArgType.None) },
1410
+ try log.turn(.{42});
1411
+ const expected = switch (endian) {
1412
+ .big => &.{ N(ArgType.Turn), 0, 42, N(ArgType.None) },
1413
+ .little => &.{ N(ArgType.Turn), 42, 0, N(ArgType.None) },
1087
1414
  };
1088
1415
  try expectLog1(expected, buf[0..4]);
1089
1416
  stream.reset();
1090
1417
  }
1091
1418
 
1092
1419
  test "|win|" {
1093
- try log.win(.P2);
1420
+ try log.win(.{.P2});
1094
1421
  try expectLog1(&.{ N(ArgType.Win), 1, N(ArgType.None) }, buf[0..3]);
1095
1422
  stream.reset();
1096
1423
  }
1097
1424
 
1098
1425
  test "|tie|" {
1099
- try log.tie();
1426
+ try log.tie(.{});
1100
1427
  try expectLog1(&.{ N(ArgType.Tie), N(ArgType.None) }, buf[0..2]);
1101
1428
  stream.reset();
1102
1429
  }
@@ -1105,10 +1432,10 @@ test "|-damage|" {
1105
1432
  var chansey = gen1.helpers.Pokemon.init(.{ .species = .Chansey, .moves = &.{.Splash} });
1106
1433
  chansey.hp = 612;
1107
1434
  chansey.status = gen1.Status.slp(1);
1108
- try log.damage(p2.ident(2), &chansey, .None);
1435
+ try log.damage(.{ p2.ident(2), &chansey, .None });
1109
1436
  var expected: []const u8 = switch (endian) {
1110
- .Big => &.{ N(ArgType.Damage), 0b1010, 2, 100, 2, 191, 1, N(Damage.None) },
1111
- .Little => &.{ N(ArgType.Damage), 0b1010, 100, 2, 191, 2, 1, N(Damage.None) },
1437
+ .big => &.{ N(ArgType.Damage), 0b1010, 2, 100, 2, 191, 1, N(Damage.None) },
1438
+ .little => &.{ N(ArgType.Damage), 0b1010, 100, 2, 191, 2, 1, N(Damage.None) },
1112
1439
  };
1113
1440
  try expectLog1(expected, buf[0..8]);
1114
1441
  stream.reset();
@@ -1116,19 +1443,19 @@ test "|-damage|" {
1116
1443
  chansey.hp = 100;
1117
1444
  chansey.stats.hp = 256;
1118
1445
  chansey.status = 0;
1119
- try log.damage(p2.ident(2), &chansey, .Confusion);
1446
+ try log.damage(.{ p2.ident(2), &chansey, .Confusion });
1120
1447
  expected = switch (endian) {
1121
- .Big => &.{ N(ArgType.Damage), 0b1010, 0, 100, 1, 0, 0, N(Damage.Confusion) },
1122
- .Little => &.{ N(ArgType.Damage), 0b1010, 100, 0, 0, 1, 0, N(Damage.Confusion) },
1448
+ .big => &.{ N(ArgType.Damage), 0b1010, 0, 100, 1, 0, 0, N(Damage.Confusion) },
1449
+ .little => &.{ N(ArgType.Damage), 0b1010, 100, 0, 0, 1, 0, N(Damage.Confusion) },
1123
1450
  };
1124
1451
  try expectLog1(expected, buf[0..8]);
1125
1452
  stream.reset();
1126
1453
 
1127
1454
  chansey.status = gen1.Status.init(.PSN);
1128
- try log.damageOf(p2.ident(2), &chansey, .RecoilOf, p1.ident(1));
1455
+ try log.damage(.{ p2.ident(2), &chansey, .RecoilOf, p1.ident(1) });
1129
1456
  expected = switch (endian) {
1130
- .Big => &.{ N(ArgType.Damage), 0b1010, 0, 100, 1, 0, 0b1000, N(Damage.RecoilOf), 1 },
1131
- .Little => &.{ N(ArgType.Damage), 0b1010, 100, 0, 0, 1, 0b1000, N(Damage.RecoilOf), 1 },
1457
+ .big => &.{ N(ArgType.Damage), 0b1010, 0, 100, 1, 0, 0b1000, N(Damage.RecoilOf), 1 },
1458
+ .little => &.{ N(ArgType.Damage), 0b1010, 100, 0, 0, 1, 0b1000, N(Damage.RecoilOf), 1 },
1132
1459
  };
1133
1460
  try expectLog1(expected, buf[0..9]);
1134
1461
  stream.reset();
@@ -1138,10 +1465,10 @@ test "|-heal|" {
1138
1465
  var chansey = gen1.helpers.Pokemon.init(.{ .species = .Chansey, .moves = &.{.Splash} });
1139
1466
  chansey.hp = 612;
1140
1467
  chansey.status = gen1.Status.slp(1);
1141
- try log.heal(p2.ident(2), &chansey, .None);
1468
+ try log.heal(.{ p2.ident(2), &chansey, .None });
1142
1469
  var expected: []const u8 = switch (endian) {
1143
- .Big => &.{ N(ArgType.Heal), 0b1010, 2, 100, 2, 191, 1, N(Heal.None) },
1144
- .Little => &.{ N(ArgType.Heal), 0b1010, 100, 2, 191, 2, 1, N(Heal.None) },
1470
+ .big => &.{ N(ArgType.Heal), 0b1010, 2, 100, 2, 191, 1, N(Heal.None) },
1471
+ .little => &.{ N(ArgType.Heal), 0b1010, 100, 2, 191, 2, 1, N(Heal.None) },
1145
1472
  };
1146
1473
  try expectLog1(expected, buf[0..8]);
1147
1474
  stream.reset();
@@ -1149,46 +1476,46 @@ test "|-heal|" {
1149
1476
  chansey.hp = 100;
1150
1477
  chansey.stats.hp = 256;
1151
1478
  chansey.status = 0;
1152
- try log.heal(p2.ident(2), &chansey, .Silent);
1479
+ try log.heal(.{ p2.ident(2), &chansey, .Silent });
1153
1480
  expected = switch (endian) {
1154
- .Big => &.{ N(ArgType.Heal), 0b1010, 0, 100, 1, 0, 0, N(Heal.Silent) },
1155
- .Little => &.{ N(ArgType.Heal), 0b1010, 100, 0, 0, 1, 0, N(Heal.Silent) },
1481
+ .big => &.{ N(ArgType.Heal), 0b1010, 0, 100, 1, 0, 0, N(Heal.Silent) },
1482
+ .little => &.{ N(ArgType.Heal), 0b1010, 100, 0, 0, 1, 0, N(Heal.Silent) },
1156
1483
  };
1157
1484
  try expectLog1(expected, buf[0..8]);
1158
1485
  stream.reset();
1159
1486
 
1160
- try log.drain(p2.ident(2), &chansey, p1.ident(1));
1487
+ try log.heal(.{ p2.ident(2), &chansey, .Drain, p1.ident(1) });
1161
1488
  expected = switch (endian) {
1162
- .Big => &.{ N(ArgType.Heal), 0b1010, 0, 100, 1, 0, 0, N(Heal.Drain), 1 },
1163
- .Little => &.{ N(ArgType.Heal), 0b1010, 100, 0, 0, 1, 0, N(Heal.Drain), 1 },
1489
+ .big => &.{ N(ArgType.Heal), 0b1010, 0, 100, 1, 0, 0, N(Heal.Drain), 1 },
1490
+ .little => &.{ N(ArgType.Heal), 0b1010, 100, 0, 0, 1, 0, N(Heal.Drain), 1 },
1164
1491
  };
1165
1492
  try expectLog1(expected, buf[0..9]);
1166
1493
  stream.reset();
1167
1494
  }
1168
1495
 
1169
1496
  test "|-status|" {
1170
- try log.status(p2.ident(6), gen1.Status.init(.BRN), .None);
1497
+ try log.status(.{ p2.ident(6), gen1.Status.init(.BRN), .None });
1171
1498
  try expectLog1(&.{ N(ArgType.Status), 0b1110, 0b10000, N(Status.None) }, buf[0..4]);
1172
1499
  stream.reset();
1173
1500
 
1174
- try log.status(p2.ident(2), gen1.Status.init(.PSN), .Silent);
1501
+ try log.status(.{ p2.ident(2), gen1.Status.init(.PSN), .Silent });
1175
1502
  try expectLog1(&.{ N(ArgType.Status), 0b1010, 0b1000, N(Status.Silent) }, buf[0..4]);
1176
1503
  stream.reset();
1177
1504
 
1178
- try log.statusFrom(p1.ident(1), gen1.Status.init(.PAR), M.BodySlam);
1505
+ try log.status(.{ p1.ident(1), gen1.Status.init(.PAR), .From, M1.BodySlam });
1179
1506
  try expectLog1(
1180
- &.{ N(ArgType.Status), 0b0001, 0b1000000, N(Status.From), N(M.BodySlam) },
1507
+ &.{ N(ArgType.Status), 0b0001, 0b1000000, N(Status.From), N(M1.BodySlam) },
1181
1508
  buf[0..5],
1182
1509
  );
1183
1510
  stream.reset();
1184
1511
  }
1185
1512
 
1186
1513
  test "|-curestatus|" {
1187
- try log.curestatus(p2.ident(6), gen1.Status.slp(7), .Message);
1514
+ try log.curestatus(.{ p2.ident(6), gen1.Status.slp(7), .Message });
1188
1515
  try expectLog1(&.{ N(ArgType.CureStatus), 0b1110, 0b111, N(CureStatus.Message) }, buf[0..4]);
1189
1516
  stream.reset();
1190
1517
 
1191
- try log.curestatus(p1.ident(2), gen1.Status.TOX, .Silent);
1518
+ try log.curestatus(.{ p1.ident(2), gen1.Status.TOX, .Silent });
1192
1519
  try expectLog1(&.{
1193
1520
  N(ArgType.CureStatus),
1194
1521
  0b0010,
@@ -1199,163 +1526,306 @@ test "|-curestatus|" {
1199
1526
  }
1200
1527
 
1201
1528
  test "|-boost|" {
1202
- try log.boost(p2.ident(6), .Speed, 2);
1529
+ try log.boost(.{ p2.ident(6), .Speed, 2 });
1203
1530
  try expectLog1(&.{ N(ArgType.Boost), 0b1110, N(Boost.Speed), 8 }, buf[0..4]);
1204
1531
  stream.reset();
1205
1532
 
1206
- try log.boost(p1.ident(2), .Rage, 1);
1533
+ try log.boost(.{ p1.ident(2), .Rage, 1 });
1207
1534
  try expectLog1(&.{ N(ArgType.Boost), 0b0010, N(Boost.Rage), 7 }, buf[0..4]);
1208
1535
  stream.reset();
1209
1536
 
1210
- try log.boost(p2.ident(3), .Defense, -2);
1537
+ try log.boost(.{ p2.ident(3), .Defense, -2 });
1211
1538
  try expectLog1(&.{ N(ArgType.Boost), 0b1011, N(Boost.Defense), 4 }, buf[0..4]);
1212
1539
  stream.reset();
1213
1540
  }
1214
1541
 
1215
1542
  test "|-clearallboost|" {
1216
- try log.clearallboost();
1543
+ try log.clearallboost(.{});
1217
1544
  try expectLog1(&.{N(ArgType.ClearAllBoost)}, buf[0..1]);
1218
1545
  stream.reset();
1219
1546
  }
1220
1547
 
1221
1548
  test "|-fail|" {
1222
- try log.fail(p2.ident(6), .None);
1549
+ try log.fail(.{ p2.ident(6), .None });
1223
1550
  try expectLog1(&.{ N(ArgType.Fail), 0b1110, N(Fail.None) }, buf[0..3]);
1224
1551
  stream.reset();
1225
1552
 
1226
- try log.fail(p2.ident(6), .Sleep);
1553
+ try log.fail(.{ p2.ident(6), .Sleep });
1227
1554
  try expectLog1(&.{ N(ArgType.Fail), 0b1110, N(Fail.Sleep) }, buf[0..3]);
1228
1555
  stream.reset();
1229
1556
 
1230
- try log.fail(p2.ident(6), .Substitute);
1557
+ try log.fail(.{ p2.ident(6), .Substitute });
1231
1558
  try expectLog1(&.{ N(ArgType.Fail), 0b1110, N(Fail.Substitute) }, buf[0..3]);
1232
1559
  stream.reset();
1233
1560
 
1234
- try log.fail(p2.ident(6), .Weak);
1561
+ try log.fail(.{ p2.ident(6), .Weak });
1235
1562
  try expectLog1(&.{ N(ArgType.Fail), 0b1110, N(Fail.Weak) }, buf[0..3]);
1236
1563
  stream.reset();
1237
1564
  }
1238
1565
 
1239
1566
  test "|-miss|" {
1240
- try log.miss(p2.ident(4));
1567
+ try log.miss(.{p2.ident(4)});
1241
1568
  try expectLog1(&.{ N(ArgType.Miss), 0b1100 }, buf[0..2]);
1242
1569
  stream.reset();
1243
1570
  }
1244
1571
  test "|-hitcount|" {
1245
- try log.hitcount(p2.ident(1), 5);
1572
+ try log.hitcount(.{ p2.ident(1), 5 });
1246
1573
  try expectLog1(&.{ N(ArgType.HitCount), 0b1001, 5 }, buf[0..3]);
1247
1574
  stream.reset();
1248
1575
  }
1249
1576
 
1250
1577
  test "|-prepare|" {
1251
- try log.prepare(p2.ident(2), M.Dig);
1252
- try expectLog1(&.{ N(ArgType.Prepare), 0b1010, N(M.Dig) }, buf[0..3]);
1578
+ try log.prepare(.{ p2.ident(2), M1.Dig });
1579
+ try expectLog1(&.{ N(ArgType.Prepare), 0b1010, N(M1.Dig) }, buf[0..3]);
1253
1580
  stream.reset();
1254
1581
  }
1255
1582
 
1256
1583
  test "|-mustrecharge|" {
1257
- try log.mustrecharge(p1.ident(6));
1584
+ try log.mustrecharge(.{p1.ident(6)});
1258
1585
  try expectLog1(&.{ N(ArgType.MustRecharge), 0b0110 }, buf[0..2]);
1259
1586
  stream.reset();
1260
1587
  }
1261
1588
 
1262
1589
  test "|-activate|" {
1263
- try log.activate(p1.ident(2), .Struggle);
1590
+ try log.activate(.{ p1.ident(2), .Struggle });
1264
1591
  try expectLog1(&.{ N(ArgType.Activate), 0b0010, N(Activate.Struggle) }, buf[0..3]);
1265
1592
  stream.reset();
1266
1593
 
1267
- try log.activate(p2.ident(4), .Mist);
1594
+ try log.activate(.{ p2.ident(4), .Mist });
1268
1595
  try expectLog1(&.{ N(ArgType.Activate), 0b1100, N(Activate.Mist) }, buf[0..3]);
1269
1596
  stream.reset();
1270
1597
 
1271
- try log.activate(p2.ident(6), .Substitute);
1598
+ try log.activate(.{ p2.ident(6), .Substitute });
1272
1599
  try expectLog1(&.{ N(ArgType.Activate), 0b1110, N(Activate.Substitute) }, buf[0..3]);
1273
1600
  stream.reset();
1274
1601
 
1275
- try log.activate(p1.ident(2), .Splash);
1602
+ try log.activate(.{ p1.ident(2), .Splash });
1276
1603
  try expectLog1(&.{ N(ArgType.Activate), 0b0010, N(Activate.Splash) }, buf[0..3]);
1277
1604
  stream.reset();
1605
+
1606
+ try log.activate(.{ p1.ident(4), .SubstituteBlock, M2.Spite });
1607
+ try expectLog1(&.{
1608
+ N(ArgType.Activate),
1609
+ 0b0100,
1610
+ N(Activate.SubstituteBlock),
1611
+ N(M2.Spite),
1612
+ }, buf[0..4]);
1613
+ stream.reset();
1614
+
1615
+ try log.activate(.{ p1.ident(3), .Magnitude, 5 });
1616
+ try expectLog1(&.{ N(ArgType.Activate), 0b0011, N(Activate.Magnitude), 5 }, buf[0..4]);
1617
+ stream.reset();
1278
1618
  }
1279
1619
 
1280
1620
  test "|-fieldactivate|" {
1281
- try log.fieldactivate();
1621
+ try log.fieldactivate(.{});
1282
1622
  try expectLog1(&.{N(ArgType.FieldActivate)}, buf[0..1]);
1283
1623
  stream.reset();
1284
1624
  }
1285
1625
 
1286
1626
  test "|-start|" {
1287
- try log.start(p2.ident(6), .Bide);
1627
+ try log.start(.{ p2.ident(6), .Bide });
1288
1628
  try expectLog1(&.{ N(ArgType.Start), 0b1110, N(Start.Bide) }, buf[0..3]);
1289
1629
  stream.reset();
1290
1630
 
1291
- try log.start(p1.ident(2), .ConfusionSilent);
1631
+ try log.start(.{ p1.ident(2), .ConfusionSilent });
1292
1632
  try expectLog1(&.{ N(ArgType.Start), 0b0010, N(Start.ConfusionSilent) }, buf[0..3]);
1293
1633
  stream.reset();
1294
1634
 
1295
- try log.typechange(p2.ident(6), gen1.Types{ .type1 = .Fire, .type2 = .Fire }, p2.ident(5));
1635
+ try log.start(.{
1636
+ p2.ident(6),
1637
+ .TypeChange,
1638
+ gen1.Types{ .type1 = .Fire, .type2 = .Fire },
1639
+ p2.ident(5),
1640
+ });
1296
1641
  try expectLog1(
1297
1642
  &.{ N(ArgType.Start), 0b1110, N(Start.TypeChange), 0b1000_1000, 0b1101 },
1298
1643
  buf[0..5],
1299
1644
  );
1300
1645
  stream.reset();
1301
1646
 
1302
- try log.typechange(p1.ident(2), gen1.Types{ .type1 = .Bug, .type2 = .Poison }, p2.ident(4));
1647
+ try log.start(.{
1648
+ p1.ident(2),
1649
+ .TypeChange,
1650
+ gen1.Types{ .type1 = .Bug, .type2 = .Poison },
1651
+ p2.ident(4),
1652
+ });
1303
1653
  try expectLog1(
1304
1654
  &.{ N(ArgType.Start), 0b0010, N(Start.TypeChange), 0b0011_0110, 0b1100 },
1305
1655
  buf[0..5],
1306
1656
  );
1307
1657
  stream.reset();
1308
1658
 
1309
- try log.startEffect(p1.ident(2), .Disable, M.Surf);
1310
- try expectLog1(&.{ N(ArgType.Start), 0b0010, N(Start.Disable), N(M.Surf) }, buf[0..4]);
1659
+ try log.start(.{ p1.ident(2), .Disable, M1.Surf });
1660
+ try expectLog1(&.{ N(ArgType.Start), 0b0010, N(Start.Disable), N(M1.Surf) }, buf[0..4]);
1311
1661
  stream.reset();
1312
1662
 
1313
- try log.startEffect(p1.ident(2), .Mimic, M.Surf);
1314
- try expectLog1(&.{ N(ArgType.Start), 0b0010, N(Start.Mimic), N(M.Surf) }, buf[0..4]);
1663
+ try log.start(.{ p1.ident(2), .Mimic, M1.Surf });
1664
+ try expectLog1(&.{ N(ArgType.Start), 0b0010, N(Start.Mimic), N(M1.Surf) }, buf[0..4]);
1315
1665
  stream.reset();
1316
1666
  }
1317
1667
 
1318
1668
  test "|-end|" {
1319
- try log.end(p2.ident(6), .Bide);
1669
+ try log.end(.{ p2.ident(6), .Bide });
1320
1670
  try expectLog1(&.{ N(ArgType.End), 0b1110, N(End.Bide) }, buf[0..3]);
1321
1671
  stream.reset();
1322
1672
 
1323
- try log.end(p1.ident(2), .ConfusionSilent);
1673
+ try log.end(.{ p1.ident(2), .ConfusionSilent });
1324
1674
  try expectLog1(&.{ N(ArgType.End), 0b0010, N(End.ConfusionSilent) }, buf[0..3]);
1325
1675
  stream.reset();
1326
1676
  }
1327
1677
 
1328
1678
  test "|-ohko|" {
1329
- try log.ohko();
1679
+ try log.ohko(.{});
1330
1680
  try expectLog1(&.{N(ArgType.OHKO)}, buf[0..1]);
1331
1681
  stream.reset();
1332
1682
  }
1683
+
1333
1684
  test "|-crit|" {
1334
- try log.crit(p2.ident(5));
1685
+ try log.crit(.{p2.ident(5)});
1335
1686
  try expectLog1(&.{ N(ArgType.Crit), 0b1101 }, buf[0..2]);
1336
1687
  stream.reset();
1337
1688
  }
1689
+
1338
1690
  test "|-supereffective|" {
1339
- try log.supereffective(p1.ident(1));
1691
+ try log.supereffective(.{p1.ident(1)});
1340
1692
  try expectLog1(&.{ N(ArgType.SuperEffective), 0b0001 }, buf[0..2]);
1341
1693
  stream.reset();
1342
1694
  }
1695
+
1343
1696
  test "|-resisted|" {
1344
- try log.resisted(p2.ident(2));
1697
+ try log.resisted(.{p2.ident(2)});
1345
1698
  try expectLog1(&.{ N(ArgType.Resisted), 0b1010 }, buf[0..2]);
1346
1699
  stream.reset();
1347
1700
  }
1701
+
1348
1702
  test "|-immune|" {
1349
- try log.immune(p1.ident(3), .None);
1703
+ try log.immune(.{ p1.ident(3), .None });
1350
1704
  try expectLog1(&.{ N(ArgType.Immune), 0b0011, N(Immune.None) }, buf[0..3]);
1351
1705
  stream.reset();
1352
1706
 
1353
- try log.immune(p2.ident(2), .OHKO);
1707
+ try log.immune(.{ p2.ident(2), .OHKO });
1354
1708
  try expectLog1(&.{ N(ArgType.Immune), 0b1010, N(Immune.OHKO) }, buf[0..3]);
1355
1709
  stream.reset();
1356
1710
  }
1711
+
1357
1712
  test "|-transform|" {
1358
- try log.transform(p2.ident(4), p1.ident(5));
1713
+ try log.transform(.{ p2.ident(4), p1.ident(5) });
1359
1714
  try expectLog1(&.{ N(ArgType.Transform), 0b1100, 0b0101 }, buf[0..3]);
1360
1715
  stream.reset();
1361
1716
  }
1717
+
1718
+ test "|drag|" {
1719
+ var blissey = gen2.helpers.Pokemon.init(.{ .species = .Blissey, .moves = &.{.Splash} });
1720
+ blissey.level = 91;
1721
+ blissey.hp = 200;
1722
+ blissey.stats.hp = 400;
1723
+ blissey.status = gen2.Status.init(.PAR);
1724
+ try log.drag(.{ p2.ident(3), &blissey });
1725
+ const par = 0b1000000;
1726
+ const expected = &(.{
1727
+ N(ArgType.Drag),
1728
+ 0b1011,
1729
+ N(S2.Blissey),
1730
+ N(gen2.Gender.Female),
1731
+ 91,
1732
+ } ++ switch (endian) {
1733
+ .big => .{ 0, 200, 1, 144, par },
1734
+ .little => .{ 200, 0, 144, 1, par },
1735
+ });
1736
+ try expectLog2(expected, buf[0..10]);
1737
+ stream.reset();
1738
+ }
1739
+
1740
+ test "|-item|" {
1741
+ try log.item(.{ p2.ident(2), I2.GoldBerry, p1.ident(4) });
1742
+ try expectLog2(&.{ N(ArgType.Item), 0b1010, N(I2.GoldBerry), 0b0100 }, buf[0..4]);
1743
+ stream.reset();
1744
+ }
1745
+
1746
+ test "|-enditem|" {
1747
+ try log.enditem(.{ p1.ident(1), I2.BerserkGene, .None });
1748
+ try expectLog2(&.{ N(ArgType.EndItem), 0b0001, N(I2.BerserkGene), N(EndItem.None) }, buf[0..4]);
1749
+ stream.reset();
1750
+
1751
+ try log.enditem(.{ p2.ident(4), I2.PRZCureBerry, .Eat });
1752
+ try expectLog2(&.{ N(ArgType.EndItem), 0b1100, N(I2.PRZCureBerry), N(EndItem.Eat) }, buf[0..4]);
1753
+ stream.reset();
1754
+ }
1755
+
1756
+ test "|-cureteam|" {
1757
+ try log.cureteam(.{p2.ident(5)});
1758
+ try expectLog2(&.{ N(ArgType.CureTeam), 0b1101 }, buf[0..2]);
1759
+ stream.reset();
1760
+ }
1761
+
1762
+ test "|-sethp|" {
1763
+ var blissey = gen2.helpers.Pokemon.init(.{ .species = .Blissey, .moves = &.{.PainSplit} });
1764
+ blissey.hp = 612;
1765
+ try log.sethp(.{ p2.ident(2), &blissey, .Silent });
1766
+ const expected: []const u8 = switch (endian) {
1767
+ .big => &.{ N(ArgType.SetHP), 0b1010, 2, 100, 2, 201, 0, N(SetHP.Silent) },
1768
+ .little => &.{ N(ArgType.SetHP), 0b1010, 100, 2, 201, 2, 0, N(SetHP.Silent) },
1769
+ };
1770
+ try expectLog2(expected, buf[0..8]);
1771
+ stream.reset();
1772
+ }
1773
+
1774
+ test "|-setboost|" {
1775
+ try log.setboost(.{ p2.ident(6), 6 });
1776
+ try expectLog2(&.{ N(ArgType.SetBoost), 0b1110, 12 }, buf[0..3]);
1777
+ stream.reset();
1778
+
1779
+ try log.setboost(.{ p2.ident(3), 2 });
1780
+ try expectLog2(&.{ N(ArgType.SetBoost), 0b1011, 8 }, buf[0..3]);
1781
+ stream.reset();
1782
+ }
1783
+
1784
+ test "|-copyboost|" {
1785
+ try log.copyboost(.{ p2.ident(3), p1.ident(6) });
1786
+ try expectLog2(&.{ N(ArgType.CopyBoost), 0b1011, 0b0110 }, buf[0..3]);
1787
+ stream.reset();
1788
+ }
1789
+
1790
+ test "|-sidestart|" {
1791
+ try log.sidestart(.{ .P2, .Reflect });
1792
+ try expectLog2(&.{ N(ArgType.SideStart), 1, N(Side.Reflect) }, buf[0..3]);
1793
+ stream.reset();
1794
+ }
1795
+
1796
+ test "|-sideend|" {
1797
+ try log.sideend(.{ .P2, .LightScreen });
1798
+ try expectLog2(&.{ N(ArgType.SideEnd), 1, N(Side.LightScreen) }, buf[0..3]);
1799
+ stream.reset();
1800
+
1801
+ try log.sideend(.{ .P1, .Spikes, p1.ident(3) });
1802
+ try expectLog2(&.{
1803
+ N(ArgType.SideEnd),
1804
+ 0,
1805
+ N(Side.Spikes),
1806
+ 0b0011,
1807
+ }, buf[0..4]);
1808
+ stream.reset();
1809
+ }
1810
+
1811
+ test "|-singlemove|" {
1812
+ try log.singlemove(.{ p2.ident(2), M2.DestinyBond });
1813
+ try expectLog2(&.{ N(ArgType.SingleMove), 0b1010, N(M2.DestinyBond) }, buf[0..3]);
1814
+ stream.reset();
1815
+ }
1816
+
1817
+ test "|-singleturn|" {
1818
+ try log.singleturn(.{ p1.ident(3), M2.Protect });
1819
+ try expectLog2(&.{ N(ArgType.SingleTurn), 0b0011, N(M2.Protect) }, buf[0..3]);
1820
+ stream.reset();
1821
+ }
1822
+
1823
+ test "|-weather|" {
1824
+ try log.weather(.{ W2.Rain, .Upkeep });
1825
+ try expectLog2(&.{ N(ArgType.Weather), N(W2.Rain), N(Weather.Upkeep) }, buf[0..3]);
1826
+ stream.reset();
1827
+
1828
+ try log.weather(.{ W2.None, .None });
1829
+ try expectLog2(&.{ N(ArgType.Weather), N(W2.None), N(Weather.None) }, buf[0..3]);
1830
+ stream.reset();
1831
+ }