@pkmn/engine 0.1.0-dev.437f6a52 → 0.1.0-dev.4438e918

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