@pkmn/engine 0.1.0-dev.f3aaee39 → 0.1.0-dev.f4ba6ebd

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