@pkmn/engine 0.1.0-dev.6195e056 → 0.1.0-dev.6e63f5e4

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 (57) hide show
  1. package/README.md +101 -62
  2. package/build/pkg/addon.d.ts +7 -0
  3. package/build/pkg/addon.js +112 -0
  4. package/build/pkg/addon.js.map +1 -0
  5. package/build/pkg/data/layout.json +25 -3
  6. package/build/pkg/data/protocol.json +22 -4
  7. package/build/pkg/data.d.ts +1 -2
  8. package/build/pkg/data.js +3 -5
  9. package/build/pkg/data.js.map +1 -1
  10. package/build/pkg/gen1.d.ts +5 -4
  11. package/build/pkg/gen1.js +47 -31
  12. package/build/pkg/gen1.js.map +1 -1
  13. package/build/pkg/index.d.ts +16 -13
  14. package/build/pkg/index.js +1 -1
  15. package/build/pkg/index.js.map +1 -1
  16. package/build/pkg/protocol.d.ts +4 -2
  17. package/build/pkg/protocol.js +5 -3
  18. package/build/pkg/protocol.js.map +1 -1
  19. package/build/test/display.js +400 -0
  20. package/build/test/display.js.map +1 -0
  21. package/build/tools/debug.js +133 -0
  22. package/build/tools/debug.js.map +1 -0
  23. package/build.zig +104 -90
  24. package/package.json +57 -36
  25. package/src/bin/install-pkmn-engine +68 -36
  26. package/src/bin/pkmn-debug +62 -0
  27. package/src/lib/binding/node.zig +47 -142
  28. package/src/lib/binding/wasm.zig +6 -4
  29. package/src/lib/common/battle.zig +17 -0
  30. package/src/lib/common/data.zig +15 -27
  31. package/src/lib/common/debug.zig +74 -0
  32. package/src/lib/common/js.zig +118 -0
  33. package/src/lib/common/optional.zig +60 -0
  34. package/src/lib/common/options.zig +12 -3
  35. package/src/lib/common/protocol.zig +416 -198
  36. package/src/lib/common/rational.zig +353 -0
  37. package/src/lib/common/rng.zig +15 -15
  38. package/src/lib/common/util.zig +12 -0
  39. package/src/lib/gen1/README.md +282 -165
  40. package/src/lib/gen1/calc.zig +426 -0
  41. package/src/lib/gen1/chance.zig +1058 -0
  42. package/src/lib/gen1/data/moves.zig +178 -180
  43. package/src/lib/gen1/data/species.zig +2 -2
  44. package/src/lib/gen1/data/types.zig +4 -4
  45. package/src/lib/gen1/data.zig +79 -65
  46. package/src/lib/gen1/helpers.zig +592 -36
  47. package/src/lib/gen1/mechanics.zig +1097 -662
  48. package/src/lib/gen2/README.md +87 -13
  49. package/src/lib/gen2/data/items.zig +3 -3
  50. package/src/lib/gen2/data/moves.zig +635 -563
  51. package/src/lib/gen2/data/species.zig +1 -1
  52. package/src/lib/gen2/data/types.zig +4 -4
  53. package/src/lib/gen2/data.zig +109 -77
  54. package/src/lib/gen2/helpers.zig +16 -22
  55. package/src/lib/gen2/mechanics.zig +1160 -11
  56. package/src/lib/pkmn.zig +68 -9
  57. package/src/test/display/pkmn.html.tmpl +368 -0
@@ -7,7 +7,7 @@ const options = @import("./options.zig");
7
7
  const assert = std.debug.assert;
8
8
  const print = std.debug.print;
9
9
 
10
- const trace = options.trace;
10
+ const enabled = options.log;
11
11
 
12
12
  const Player = data.Player;
13
13
  const ID = data.ID;
@@ -102,6 +102,9 @@ pub const ArgType = enum(u8) {
102
102
  // Gen 8
103
103
  CanDynamax,
104
104
  SwapSideConditions,
105
+
106
+ // Gen 9
107
+ Terastallize,
105
108
  };
106
109
 
107
110
  pub const Move = enum(u8) {
@@ -124,6 +127,8 @@ pub const Heal = enum(u8) {
124
127
  None,
125
128
  Silent,
126
129
  Drain,
130
+
131
+ Leftovers,
127
132
  };
128
133
 
129
134
  pub const Damage = enum(u8) {
@@ -133,6 +138,8 @@ pub const Damage = enum(u8) {
133
138
  Confusion,
134
139
  LeechSeed,
135
140
  RecoilOf,
141
+
142
+ Spikes,
136
143
  };
137
144
 
138
145
  pub const Status = enum(u8) {
@@ -177,6 +184,8 @@ pub const Activate = enum(u8) {
177
184
  Struggle,
178
185
  Substitute,
179
186
  Splash,
187
+
188
+ Attract,
180
189
  };
181
190
 
182
191
  pub const Start = enum(u8) {
@@ -202,6 +211,9 @@ pub const End = enum(u8) {
202
211
  Bide,
203
212
  Substitute,
204
213
 
214
+ FutureSight,
215
+ Encore,
216
+
205
217
  // Silent
206
218
  DisableSilent,
207
219
  ConfusionSilent,
@@ -219,8 +231,27 @@ pub const Immune = enum(u8) {
219
231
  OHKO,
220
232
  };
221
233
 
234
+ pub const SideStart = enum(u8) {
235
+ Safeguard,
236
+ Reflect,
237
+ LightScreen,
238
+ Spikes,
239
+ };
240
+
241
+ pub const SideEnd = enum(u8) {
242
+ Safeguard,
243
+ Reflect,
244
+ LightScreen,
245
+
246
+ Spikes,
247
+ };
248
+
249
+ /// Null object pattern implementation of `Log` backed by a `std.io.null_writer`.
250
+ /// Ignores anything sent to it, though protocol logging should additionally be turned off
251
+ /// entirely with `options.log`.
222
252
  pub const NULL = Log(@TypeOf(std.io.null_writer)){ .writer = std.io.null_writer };
223
253
 
254
+ /// Logs protocol information to its `Writer` during a battle update when `options.log` is enabled.
224
255
  pub fn Log(comptime Writer: type) type {
225
256
  return struct {
226
257
  const Self = @This();
@@ -230,91 +261,108 @@ pub fn Log(comptime Writer: type) type {
230
261
  pub const Error = Writer.Error;
231
262
 
232
263
  pub fn move(self: Self, source: ID, m: anytype, target: ID, from: anytype) Error!void {
233
- if (!trace) return;
264
+ if (!enabled) return;
265
+
234
266
  assert(m != .None);
235
267
  try self.writer.writeAll(&.{
236
- @enumToInt(ArgType.Move),
237
- @bitCast(u8, source),
238
- @enumToInt(m),
239
- @bitCast(u8, target),
268
+ @intFromEnum(ArgType.Move),
269
+ @as(u8, @bitCast(source)),
270
+ @intFromEnum(m),
271
+ @as(u8, @bitCast(target)),
240
272
  });
241
- const none = &[_]u8{@enumToInt(Move.None)};
273
+ const none = &[_]u8{@intFromEnum(Move.None)};
242
274
  try self.writer.writeAll(switch (@typeInfo(@TypeOf(from))) {
243
275
  .Null => none,
244
276
  .Optional => if (from) |f| blk: {
245
- assert(@enumToInt(f) != 0);
246
- break :blk &[_]u8{ @enumToInt(Move.From), @enumToInt(f) };
277
+ assert(@intFromEnum(f) != 0);
278
+ break :blk &[_]u8{ @intFromEnum(Move.From), @intFromEnum(f) };
247
279
  } else none,
248
280
  else => blk: {
249
- assert(@enumToInt(from) != 0);
250
- break :blk &[_]u8{ @enumToInt(Move.From), @enumToInt(from) };
281
+ assert(@intFromEnum(from) != 0);
282
+ break :blk &[_]u8{ @intFromEnum(Move.From), @intFromEnum(from) };
251
283
  },
252
284
  });
253
285
  }
254
286
 
255
287
  pub fn switched(self: Self, ident: ID, pokemon: anytype) Error!void {
256
- if (!trace) return;
257
- try self.writer.writeAll(&.{ @enumToInt(ArgType.Switch), @bitCast(u8, ident) });
258
- try self.writer.writeAll(&.{ @enumToInt(pokemon.species), pokemon.level });
288
+ if (!enabled) return;
289
+
290
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.Switch), @as(u8, @bitCast(ident)) });
291
+ if (@hasField(@TypeOf(pokemon.*), "dvs")) {
292
+ try self.writer.writeAll(&.{
293
+ @intFromEnum(pokemon.species),
294
+ @intFromEnum(pokemon.dvs.gender),
295
+ pokemon.level,
296
+ });
297
+ } else {
298
+ try self.writer.writeAll(&.{ @intFromEnum(pokemon.species), pokemon.level });
299
+ }
259
300
  try self.writer.writeIntNative(u16, pokemon.hp);
260
301
  try self.writer.writeIntNative(u16, pokemon.stats.hp);
261
302
  try self.writer.writeAll(&.{pokemon.status});
262
303
  }
263
304
 
264
305
  pub fn cant(self: Self, ident: ID, reason: Cant) Error!void {
265
- if (!trace) return;
306
+ if (!enabled) return;
307
+
266
308
  assert(reason != .Disable);
267
309
  try self.writer.writeAll(&.{
268
- @enumToInt(ArgType.Cant),
269
- @bitCast(u8, ident),
270
- @enumToInt(reason),
310
+ @intFromEnum(ArgType.Cant),
311
+ @as(u8, @bitCast(ident)),
312
+ @intFromEnum(reason),
271
313
  });
272
314
  }
273
315
 
274
316
  pub fn disabled(self: Self, ident: ID, m: anytype) Error!void {
275
- if (!trace) return;
317
+ if (!enabled) return;
318
+
276
319
  try self.writer.writeAll(&.{
277
- @enumToInt(ArgType.Cant),
278
- @bitCast(u8, ident),
279
- @enumToInt(Cant.Disable),
280
- @enumToInt(m),
320
+ @intFromEnum(ArgType.Cant),
321
+ @as(u8, @bitCast(ident)),
322
+ @intFromEnum(Cant.Disable),
323
+ @intFromEnum(m),
281
324
  });
282
325
  }
283
326
 
284
327
  pub fn faint(self: Self, ident: ID, done: bool) Error!void {
285
- if (!trace) return;
286
- try self.writer.writeAll(&.{ @enumToInt(ArgType.Faint), @bitCast(u8, ident) });
287
- if (done) try self.writer.writeByte(@enumToInt(ArgType.None));
328
+ if (!enabled) return;
329
+
330
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.Faint), @as(u8, @bitCast(ident)) });
331
+ if (done) try self.writer.writeByte(@intFromEnum(ArgType.None));
288
332
  }
289
333
 
290
334
  pub fn turn(self: Self, num: u16) Error!void {
291
- if (!trace) return;
292
- try self.writer.writeByte(@enumToInt(ArgType.Turn));
335
+ if (!enabled) return;
336
+
337
+ try self.writer.writeByte(@intFromEnum(ArgType.Turn));
293
338
  try self.writer.writeIntNative(u16, num);
294
- try self.writer.writeByte(@enumToInt(ArgType.None));
339
+ try self.writer.writeByte(@intFromEnum(ArgType.None));
295
340
  }
296
341
 
297
342
  pub fn win(self: Self, player: Player) Error!void {
298
- if (!trace) return;
343
+ if (!enabled) return;
344
+
299
345
  try self.writer.writeAll(&.{
300
- @enumToInt(ArgType.Win),
301
- @enumToInt(player),
302
- @enumToInt(ArgType.None),
346
+ @intFromEnum(ArgType.Win),
347
+ @intFromEnum(player),
348
+ @intFromEnum(ArgType.None),
303
349
  });
304
350
  }
305
351
 
306
352
  pub fn tie(self: Self) Error!void {
307
- if (!trace) return;
308
- try self.writer.writeAll(&.{ @enumToInt(ArgType.Tie), @enumToInt(ArgType.None) });
353
+ if (!enabled) return;
354
+
355
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.Tie), @intFromEnum(ArgType.None) });
309
356
  }
310
357
 
311
358
  pub fn damage(self: Self, ident: ID, pokemon: anytype, reason: Damage) Error!void {
312
- if (!trace) return;
313
- assert(@enumToInt(reason) <= @enumToInt(Damage.LeechSeed));
314
- try self.writer.writeAll(&.{ @enumToInt(ArgType.Damage), @bitCast(u8, ident) });
359
+ if (!enabled) return;
360
+
361
+ assert(@intFromEnum(reason) <= @intFromEnum(Damage.LeechSeed));
362
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.Damage), @as(u8, @bitCast(ident)) });
315
363
  try self.writer.writeIntNative(u16, pokemon.hp);
316
364
  try self.writer.writeIntNative(u16, pokemon.stats.hp);
317
- try self.writer.writeAll(&.{ pokemon.status, @enumToInt(reason) });
365
+ try self.writer.writeAll(&.{ pokemon.status, @intFromEnum(reason) });
318
366
  }
319
367
 
320
368
  pub fn damageOf(
@@ -324,227 +372,311 @@ pub fn Log(comptime Writer: type) type {
324
372
  reason: Damage,
325
373
  source: ID,
326
374
  ) Error!void {
327
- if (!trace) return;
328
- assert(@enumToInt(reason) == @enumToInt(Damage.RecoilOf));
329
- try self.writer.writeAll(&.{ @enumToInt(ArgType.Damage), @bitCast(u8, ident) });
375
+ if (!enabled) return;
376
+
377
+ assert(@intFromEnum(reason) == @intFromEnum(Damage.RecoilOf));
378
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.Damage), @as(u8, @bitCast(ident)) });
330
379
  try self.writer.writeIntNative(u16, pokemon.hp);
331
380
  try self.writer.writeIntNative(u16, pokemon.stats.hp);
332
381
  try self.writer.writeAll(&.{
333
382
  pokemon.status,
334
- @enumToInt(reason),
335
- @bitCast(u8, source),
383
+ @intFromEnum(reason),
384
+ @as(u8, @bitCast(source)),
336
385
  });
337
386
  }
338
387
 
339
388
  pub fn heal(self: Self, ident: ID, pokemon: anytype, reason: Heal) Error!void {
340
- if (!trace) return;
389
+ if (!enabled) return;
390
+
341
391
  assert(reason != .Drain);
342
- try self.writer.writeAll(&.{ @enumToInt(ArgType.Heal), @bitCast(u8, ident) });
392
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.Heal), @as(u8, @bitCast(ident)) });
343
393
  try self.writer.writeIntNative(u16, pokemon.hp);
344
394
  try self.writer.writeIntNative(u16, pokemon.stats.hp);
345
- try self.writer.writeAll(&.{ pokemon.status, @enumToInt(reason) });
395
+ try self.writer.writeAll(&.{ pokemon.status, @intFromEnum(reason) });
346
396
  }
347
397
 
348
398
  pub fn drain(self: Self, source: ID, pokemon: anytype, target: ID) Error!void {
349
- if (!trace) return;
350
- try self.writer.writeAll(&.{ @enumToInt(ArgType.Heal), @bitCast(u8, source) });
399
+ if (!enabled) return;
400
+
401
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.Heal), @as(u8, @bitCast(source)) });
351
402
  try self.writer.writeIntNative(u16, pokemon.hp);
352
403
  try self.writer.writeIntNative(u16, pokemon.stats.hp);
353
404
  try self.writer.writeAll(&.{
354
405
  pokemon.status,
355
- @enumToInt(Heal.Drain),
356
- @bitCast(u8, target),
406
+ @intFromEnum(Heal.Drain),
407
+ @as(u8, @bitCast(target)),
357
408
  });
358
409
  }
359
410
 
360
411
  pub fn status(self: Self, ident: ID, value: u8, reason: Status) Error!void {
361
- if (!trace) return;
412
+ if (!enabled) return;
413
+
362
414
  assert(reason != .From);
363
415
  try self.writer.writeAll(&.{
364
- @enumToInt(ArgType.Status),
365
- @bitCast(u8, ident),
416
+ @intFromEnum(ArgType.Status),
417
+ @as(u8, @bitCast(ident)),
366
418
  value,
367
- @enumToInt(reason),
419
+ @intFromEnum(reason),
368
420
  });
369
421
  }
370
422
 
371
423
  pub fn statusFrom(self: Self, ident: ID, value: u8, m: anytype) Error!void {
372
- if (!trace) return;
424
+ if (!enabled) return;
425
+
373
426
  try self.writer.writeAll(&.{
374
- @enumToInt(ArgType.Status),
375
- @bitCast(u8, ident),
427
+ @intFromEnum(ArgType.Status),
428
+ @as(u8, @bitCast(ident)),
376
429
  value,
377
- @enumToInt(Status.From),
378
- @enumToInt(m),
430
+ @intFromEnum(Status.From),
431
+ @intFromEnum(m),
379
432
  });
380
433
  }
381
434
 
382
435
  pub fn curestatus(self: Self, ident: ID, value: u8, reason: CureStatus) Error!void {
383
- if (!trace) return;
436
+ if (!enabled) return;
437
+
384
438
  try self.writer.writeAll(&.{
385
- @enumToInt(ArgType.CureStatus),
386
- @bitCast(u8, ident),
439
+ @intFromEnum(ArgType.CureStatus),
440
+ @as(u8, @bitCast(ident)),
387
441
  value,
388
- @enumToInt(reason),
442
+ @intFromEnum(reason),
389
443
  });
390
444
  }
391
445
 
392
446
  pub fn boost(self: Self, ident: ID, reason: Boost, num: i8) Error!void {
393
- if (!trace) return;
447
+ if (!enabled) return;
448
+
394
449
  assert(num != 0 and (reason != .Rage or num > 0));
395
450
  try self.writer.writeAll(&.{
396
- @enumToInt(ArgType.Boost),
397
- @bitCast(u8, ident),
398
- @enumToInt(reason),
399
- @intCast(u8, num + 6),
451
+ @intFromEnum(ArgType.Boost),
452
+ @as(u8, @bitCast(ident)),
453
+ @intFromEnum(reason),
454
+ @as(u8, @intCast(num + 6)),
400
455
  });
401
456
  }
402
457
 
403
458
  pub fn clearallboost(self: Self) Error!void {
404
- if (!trace) return;
405
- try self.writer.writeAll(&.{@enumToInt(ArgType.ClearAllBoost)});
459
+ if (!enabled) return;
460
+
461
+ try self.writer.writeAll(&.{@intFromEnum(ArgType.ClearAllBoost)});
406
462
  }
407
463
 
408
464
  pub fn fail(self: Self, ident: ID, reason: Fail) Error!void {
409
- if (!trace) return;
465
+ if (!enabled) return;
466
+
410
467
  try self.writer.writeAll(&.{
411
- @enumToInt(ArgType.Fail),
412
- @bitCast(u8, ident),
413
- @enumToInt(reason),
468
+ @intFromEnum(ArgType.Fail),
469
+ @as(u8, @bitCast(ident)),
470
+ @intFromEnum(reason),
414
471
  });
415
472
  }
416
473
 
417
474
  pub fn miss(self: Self, source: ID) Error!void {
418
- if (!trace) return;
419
- try self.writer.writeAll(&.{ @enumToInt(ArgType.Miss), @bitCast(u8, source) });
475
+ if (!enabled) return;
476
+
477
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.Miss), @as(u8, @bitCast(source)) });
420
478
  }
421
479
 
422
480
  pub fn hitcount(self: Self, ident: ID, num: u8) Error!void {
423
- if (!trace) return;
424
- try self.writer.writeAll(&.{ @enumToInt(ArgType.HitCount), @bitCast(u8, ident), num });
481
+ if (!enabled) return;
482
+
483
+ try self.writer.writeAll(&.{
484
+ @intFromEnum(ArgType.HitCount),
485
+ @as(u8, @bitCast(ident)),
486
+ num,
487
+ });
425
488
  }
426
489
 
427
490
  pub fn prepare(self: Self, source: ID, m: anytype) Error!void {
428
- if (!trace) return;
491
+ if (!enabled) return;
492
+
429
493
  try self.writer.writeAll(&.{
430
- @enumToInt(ArgType.Prepare),
431
- @bitCast(u8, source),
432
- @enumToInt(m),
494
+ @intFromEnum(ArgType.Prepare),
495
+ @as(u8, @bitCast(source)),
496
+ @intFromEnum(m),
433
497
  });
434
498
  }
435
499
 
436
500
  pub fn mustrecharge(self: Self, ident: ID) Error!void {
437
- if (!trace) return;
438
- try self.writer.writeAll(&.{ @enumToInt(ArgType.MustRecharge), @bitCast(u8, ident) });
501
+ if (!enabled) return;
502
+
503
+ try self.writer.writeAll(&.{
504
+ @intFromEnum(ArgType.MustRecharge),
505
+ @as(u8, @bitCast(ident)),
506
+ });
439
507
  }
440
508
 
441
509
  pub fn activate(self: Self, ident: ID, reason: Activate) Error!void {
442
- if (!trace) return;
510
+ if (!enabled) return;
511
+
443
512
  try self.writer.writeAll(&.{
444
- @enumToInt(ArgType.Activate),
445
- @bitCast(u8, ident),
446
- @enumToInt(reason),
513
+ @intFromEnum(ArgType.Activate),
514
+ @as(u8, @bitCast(ident)),
515
+ @intFromEnum(reason),
447
516
  });
448
517
  }
449
518
 
450
519
  pub fn fieldactivate(self: Self) Error!void {
451
- if (!trace) return;
452
- try self.writer.writeAll(&.{@enumToInt(ArgType.FieldActivate)});
520
+ if (!enabled) return;
521
+
522
+ try self.writer.writeAll(&.{@intFromEnum(ArgType.FieldActivate)});
453
523
  }
454
524
 
455
525
  pub fn start(self: Self, ident: ID, reason: Start) Error!void {
456
- if (!trace) return;
457
- assert(@enumToInt(reason) < @enumToInt(Start.TypeChange));
526
+ if (!enabled) return;
527
+
528
+ assert(@intFromEnum(reason) < @intFromEnum(Start.TypeChange));
458
529
  try self.writer.writeAll(&.{
459
- @enumToInt(ArgType.Start),
460
- @bitCast(u8, ident),
461
- @enumToInt(reason),
530
+ @intFromEnum(ArgType.Start),
531
+ @as(u8, @bitCast(ident)),
532
+ @intFromEnum(reason),
462
533
  });
463
534
  }
464
535
 
465
536
  pub fn typechange(self: Self, source: ID, types: anytype, target: ID) Error!void {
466
- if (!trace) return;
537
+ if (!enabled) return;
538
+
467
539
  try self.writer.writeAll(&.{
468
- @enumToInt(ArgType.Start),
469
- @bitCast(u8, source),
470
- @enumToInt(Start.TypeChange),
471
- @bitCast(u8, types),
472
- @bitCast(u8, target),
540
+ @intFromEnum(ArgType.Start),
541
+ @as(u8, @bitCast(source)),
542
+ @intFromEnum(Start.TypeChange),
543
+ @as(u8, @bitCast(types)),
544
+ @as(u8, @bitCast(target)),
473
545
  });
474
546
  }
475
547
 
476
548
  pub fn startEffect(self: Self, ident: ID, reason: Start, m: anytype) Error!void {
477
- if (!trace) return;
478
- assert(@enumToInt(reason) > @enumToInt(Start.TypeChange));
549
+ if (!enabled) return;
550
+
551
+ assert(@intFromEnum(reason) > @intFromEnum(Start.TypeChange));
479
552
  try self.writer.writeAll(&.{
480
- @enumToInt(ArgType.Start),
481
- @bitCast(u8, ident),
482
- @enumToInt(reason),
483
- @enumToInt(m),
553
+ @intFromEnum(ArgType.Start),
554
+ @as(u8, @bitCast(ident)),
555
+ @intFromEnum(reason),
556
+ @intFromEnum(m),
484
557
  });
485
558
  }
486
559
 
487
560
  pub fn end(self: Self, ident: ID, reason: End) Error!void {
488
- if (!trace) return;
561
+ if (!enabled) return;
562
+
489
563
  try self.writer.writeAll(&.{
490
- @enumToInt(ArgType.End),
491
- @bitCast(u8, ident),
492
- @enumToInt(reason),
564
+ @intFromEnum(ArgType.End),
565
+ @as(u8, @bitCast(ident)),
566
+ @intFromEnum(reason),
493
567
  });
494
568
  }
495
569
 
496
570
  pub fn ohko(self: Self) Error!void {
497
- if (!trace) return;
498
- try self.writer.writeAll(&.{@enumToInt(ArgType.OHKO)});
571
+ if (!enabled) return;
572
+
573
+ try self.writer.writeAll(&.{@intFromEnum(ArgType.OHKO)});
499
574
  }
500
575
 
501
576
  pub fn crit(self: Self, ident: ID) Error!void {
502
- if (!trace) return;
503
- try self.writer.writeAll(&.{ @enumToInt(ArgType.Crit), @bitCast(u8, ident) });
577
+ if (!enabled) return;
578
+
579
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.Crit), @as(u8, @bitCast(ident)) });
504
580
  }
505
581
 
506
582
  pub fn supereffective(self: Self, ident: ID) Error!void {
507
- if (!trace) return;
508
- try self.writer.writeAll(&.{ @enumToInt(ArgType.SuperEffective), @bitCast(u8, ident) });
583
+ if (!enabled) return;
584
+
585
+ try self.writer.writeAll(&.{
586
+ @intFromEnum(ArgType.SuperEffective),
587
+ @as(u8, @bitCast(ident)),
588
+ });
509
589
  }
510
590
 
511
591
  pub fn resisted(self: Self, ident: ID) Error!void {
512
- if (!trace) return;
513
- try self.writer.writeAll(&.{ @enumToInt(ArgType.Resisted), @bitCast(u8, ident) });
592
+ if (!enabled) return;
593
+
594
+ try self.writer.writeAll(&.{
595
+ @intFromEnum(ArgType.Resisted),
596
+ @as(u8, @bitCast(ident)),
597
+ });
514
598
  }
515
599
 
516
600
  pub fn immune(self: Self, ident: ID, reason: Immune) Error!void {
517
- if (!trace) return;
601
+ if (!enabled) return;
602
+
518
603
  try self.writer.writeAll(&.{
519
- @enumToInt(ArgType.Immune),
520
- @bitCast(u8, ident),
521
- @enumToInt(reason),
604
+ @intFromEnum(ArgType.Immune),
605
+ @as(u8, @bitCast(ident)),
606
+ @intFromEnum(reason),
522
607
  });
523
608
  }
524
609
 
525
610
  pub fn transform(self: Self, source: ID, target: ID) Error!void {
526
- if (!trace) return;
611
+ if (!enabled) return;
612
+
613
+ try self.writer.writeAll(&.{
614
+ @intFromEnum(ArgType.Transform),
615
+ @as(u8, @bitCast(source)),
616
+ @as(u8, @bitCast(target)),
617
+ });
618
+ }
619
+
620
+ pub fn sidestart(self: Self, ident: ID, reason: SideStart) Error!void {
621
+ if (!enabled) return;
622
+
623
+ try self.writer.writeAll(&.{
624
+ @intFromEnum(ArgType.SideStart),
625
+ @as(u8, @bitCast(ident)),
626
+ @intFromEnum(reason),
627
+ });
628
+ }
629
+
630
+ pub fn sideend(self: Self, ident: ID, reason: SideEnd) Error!void {
631
+ if (!enabled) return;
632
+
633
+ assert(@intFromEnum(reason) != @intFromEnum(SideEnd.Spikes));
527
634
  try self.writer.writeAll(&.{
528
- @enumToInt(ArgType.Transform),
529
- @bitCast(u8, source),
530
- @bitCast(u8, target),
635
+ @intFromEnum(ArgType.SideEnd),
636
+ @as(u8, @bitCast(ident)),
637
+ @intFromEnum(reason),
638
+ });
639
+ }
640
+
641
+ pub fn sideendFromOf(
642
+ self: Self,
643
+ ident: ID,
644
+ reason: SideEnd,
645
+ m: anytype,
646
+ source: ID,
647
+ ) Error!void {
648
+ if (!enabled) return;
649
+
650
+ assert(@intFromEnum(reason) == @intFromEnum(SideEnd.Spikes));
651
+ try self.writer.writeAll(&.{
652
+ @intFromEnum(ArgType.SideEnd),
653
+ @as(u8, @bitCast(ident)),
654
+ @intFromEnum(reason),
655
+ @intFromEnum(m),
656
+ @as(u8, @bitCast(source)),
531
657
  });
532
658
  }
533
659
 
534
660
  pub fn laststill(self: Self) Error!void {
535
- if (!trace) return;
536
- try self.writer.writeAll(&.{@enumToInt(ArgType.LastStill)});
661
+ if (!enabled) return;
662
+
663
+ try self.writer.writeAll(&.{@intFromEnum(ArgType.LastStill)});
537
664
  }
538
665
 
539
666
  pub fn lastmiss(self: Self) Error!void {
540
- if (!trace) return;
541
- try self.writer.writeAll(&.{@enumToInt(ArgType.LastMiss)});
667
+ if (!enabled) return;
668
+
669
+ try self.writer.writeAll(&.{@intFromEnum(ArgType.LastMiss)});
542
670
  }
543
671
  };
544
672
  }
545
673
 
674
+ /// `Log` type backed by the optimized `ByteStream.Writer`.
546
675
  pub const FixedLog = Log(ByteStream.Writer);
547
676
 
677
+ /// Stripped down version of `std.io.FixedBufferStream` optimized for efficiently writing the
678
+ /// individual protocol bytes. Note that the `ByteStream.Writer` is **not** a `std.io.Writer` and
679
+ /// should not be used for general purpose writing.
548
680
  pub const ByteStream = struct {
549
681
  buffer: []u8,
550
682
  pos: usize = 0,
@@ -586,14 +718,18 @@ pub const ByteStream = struct {
586
718
  };
587
719
 
588
720
  pub const Kind = enum { Move, Species, Type, Status };
589
- pub const Formatter = fn (Kind, u8) []const u8;
590
721
 
591
- pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, color: bool) void {
722
+ pub fn format(
723
+ comptime gen: anytype,
724
+ a: []const u8,
725
+ b: ?[]const u8,
726
+ color: bool,
727
+ ) void {
592
728
  print("\n", .{});
593
729
 
594
730
  var i: usize = 0;
595
731
  while (i < a.len) {
596
- const arg = @intToEnum(ArgType, a[i]);
732
+ const arg: ArgType = @enumFromInt(a[i]);
597
733
  const name = switch (arg) {
598
734
  .None => if (color) "\x1b[2m-\x1b[0m" else "-",
599
735
  .LastStill => "|[still]",
@@ -626,6 +762,8 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
626
762
  .Resisted => "|-resisted|",
627
763
  .Immune => "|-immune|",
628
764
  .Transform => "|-transform|",
765
+ .SideStart => "|-sidestart|",
766
+ .SideEnd => "|-sideend|",
629
767
  else => unreachable,
630
768
  };
631
769
  printc("{s}", .{name}, a, b, &i, 1, color);
@@ -639,19 +777,29 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
639
777
  .OHKO,
640
778
  => {},
641
779
  .Move => {
642
- const source = ID.from(@intCast(u4, a[i]));
780
+ const source = ID.from(@intCast(a[i]));
643
781
  printc(" {s}({d})", .{ @tagName(source.player), source.id }, a, b, &i, 1, color);
644
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
645
- const target = ID.from(@intCast(u4, a[i]));
782
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
783
+ const target = ID.from(@intCast(a[i]));
646
784
  printc(" {s}({d})", .{ @tagName(target.player), target.id }, a, b, &i, 1, color);
647
- const reason = @intToEnum(Move, a[i]);
785
+ const reason: Move = @enumFromInt(a[i]);
648
786
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
649
- if (reason == .From) printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
787
+ if (reason == .From) {
788
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
789
+ }
650
790
  },
651
791
  .Switch => {
652
- const id = ID.from(@intCast(u4, a[i]));
792
+ const id = ID.from(@intCast(a[i]));
653
793
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
654
- printc(" {s}", .{formatter(.Species, a[i])}, a, b, &i, 1, color);
794
+ printc(" {s}", .{formatter(gen, .Species, a[i])}, a, b, &i, 1, color);
795
+ if (@hasDecl(gen, "Gender")) {
796
+ if (a[i] == N(gen.Gender.Unknown)) {
797
+ i += 1;
798
+ } else {
799
+ var gender = if (a[i] == N(gen.Gender.Male)) "M" else "F";
800
+ printc(", {s}", .{gender}, a, b, &i, 1, color);
801
+ }
802
+ }
655
803
  printc(" L{d}", .{a[i]}, a, b, &i, 1, color);
656
804
  switch (endian) {
657
805
  .Big => {
@@ -667,15 +815,15 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
667
815
  printc("/{d}", .{hp}, a, b, &i, 2, color);
668
816
  },
669
817
  }
670
- printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
818
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
671
819
  },
672
820
  .Cant => {
673
- const id = ID.from(@intCast(u4, a[i]));
821
+ const id = ID.from(@intCast(a[i]));
674
822
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
675
- const reason = @intToEnum(Cant, a[i]);
823
+ const reason: Cant = @enumFromInt(a[i]);
676
824
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
677
825
  if (reason == .Disable) {
678
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
826
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
679
827
  }
680
828
  },
681
829
  .Faint,
@@ -685,7 +833,7 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
685
833
  .SuperEffective,
686
834
  .Resisted,
687
835
  => {
688
- const id = ID.from(@intCast(u4, a[i]));
836
+ const id = ID.from(@intCast(a[i]));
689
837
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
690
838
  },
691
839
  .Turn => {
@@ -695,9 +843,11 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
695
843
  };
696
844
  printc(" {d}", .{turn}, a, b, &i, 2, color);
697
845
  },
698
- .Win => printc(" {s}", .{@tagName(@intToEnum(Player, a[i]))}, a, b, &i, 1, color),
846
+ .Win => {
847
+ printc(" {s}", .{@tagName(@as(Player, @enumFromInt(a[i])))}, a, b, &i, 1, color);
848
+ },
699
849
  .Damage, .Heal => {
700
- var id = ID.from(@intCast(u4, a[i]));
850
+ var id = ID.from(@intCast(a[i]));
701
851
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
702
852
  switch (endian) {
703
853
  .Big => {
@@ -713,95 +863,114 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
713
863
  printc("/{d}", .{hp}, a, b, &i, 2, color);
714
864
  },
715
865
  }
716
- printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
866
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
717
867
  if (arg == .Damage) {
718
- const reason = a[i];
719
- printc(" {s}", .{@tagName(@intToEnum(Damage, reason))}, a, b, &i, 1, color);
720
- if (reason == @enumToInt(Damage.RecoilOf)) {
721
- id = ID.from(@intCast(u4, a[i]));
868
+ const reason: Damage = @enumFromInt(a[i]);
869
+ printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
870
+ if (reason == .RecoilOf) {
871
+ id = ID.from(@intCast(a[i]));
722
872
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
723
873
  }
724
874
  } else {
725
- const reason = @intToEnum(Heal, a[i]);
875
+ const reason: Heal = @enumFromInt(a[i]);
726
876
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
727
877
  if (reason == .Drain) {
728
- id = ID.from(@intCast(u4, a[i]));
878
+ id = ID.from(@intCast(a[i]));
729
879
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
730
880
  }
731
881
  }
732
882
  },
733
883
  .Status => {
734
- const id = ID.from(@intCast(u4, a[i]));
884
+ const id = ID.from(@intCast(a[i]));
735
885
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
736
- printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
737
- const reason = @intToEnum(Status, a[i]);
886
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
887
+ const reason: Status = @enumFromInt(a[i]);
738
888
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
739
- if (reason == .From) printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
889
+ if (reason == .From) {
890
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
891
+ }
740
892
  },
741
893
  .CureStatus => {
742
- const id = ID.from(@intCast(u4, a[i]));
894
+ const id = ID.from(@intCast(a[i]));
743
895
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
744
- printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
745
- printc(" {s}", .{@tagName(@intToEnum(CureStatus, a[i]))}, a, b, &i, 1, color);
896
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
897
+ const reason: CureStatus = @enumFromInt(a[i]);
898
+ printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
746
899
  },
747
900
  .Boost => {
748
- const id = ID.from(@intCast(u4, a[i]));
901
+ const id = ID.from(@intCast(a[i]));
749
902
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
750
- printc(" {s}", .{@tagName(@intToEnum(Boost, a[i]))}, a, b, &i, 1, color);
903
+ printc(" {s}", .{@tagName(@as(Boost, @enumFromInt(a[i])))}, a, b, &i, 1, color);
751
904
  printc(" {d}", .{a[i]}, a, b, &i, 1, color);
752
905
  },
753
906
  .Fail => {
754
- const id = ID.from(@intCast(u4, a[i]));
907
+ const id = ID.from(@intCast(a[i]));
755
908
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
756
- printc(" {s}", .{@tagName(@intToEnum(Fail, a[i]))}, a, b, &i, 1, color);
909
+ printc(" {s}", .{@tagName(@as(Fail, @enumFromInt(a[i])))}, a, b, &i, 1, color);
757
910
  },
758
911
  .HitCount => {
759
- const id = ID.from(@intCast(u4, a[i]));
912
+ const id = ID.from(@intCast(a[i]));
760
913
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
761
914
  printc(" {d}", .{a[i]}, a, b, &i, 1, color);
762
915
  },
763
916
  .Prepare => {
764
- const id = ID.from(@intCast(u4, a[i]));
917
+ const id = ID.from(@intCast(a[i]));
765
918
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
766
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
919
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
767
920
  },
768
921
  .Activate => {
769
- const id = ID.from(@intCast(u4, a[i]));
922
+ const id = ID.from(@intCast(a[i]));
770
923
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
771
- printc(" {s}", .{@tagName(@intToEnum(Activate, a[i]))}, a, b, &i, 1, color);
924
+ printc(" {s}", .{@tagName(@as(Activate, @enumFromInt(a[i])))}, a, b, &i, 1, color);
772
925
  },
773
926
  .Start => {
774
- const id = ID.from(@intCast(u4, a[i]));
927
+ const id = ID.from(@intCast(a[i]));
775
928
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
776
929
  const reason = a[i];
777
- printc(" {s}", .{@tagName(@intToEnum(Start, reason))}, a, b, &i, 1, color);
778
- if (@intToEnum(Start, reason) == .TypeChange) {
779
- const types = @bitCast(gen1.Types, a[i]);
930
+ printc(" {s}", .{@tagName(@as(Start, @enumFromInt(reason)))}, a, b, &i, 1, color);
931
+ if (@as(Start, @enumFromInt(reason)) == .TypeChange) {
932
+ const types = @as(gen1.Types, @bitCast(a[i]));
780
933
  const args = .{
781
- formatter(.Type, @enumToInt(types.type1)),
782
- formatter(.Type, @enumToInt(types.type2)),
934
+ formatter(gen, .Type, @intFromEnum(types.type1)),
935
+ formatter(gen, .Type, @intFromEnum(types.type2)),
783
936
  };
784
937
  printc(" {s}/{s}", args, a, b, &i, 1, color);
785
- } else if (reason >= @enumToInt(Start.Disable)) {
786
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
938
+ } else if (reason >= @intFromEnum(Start.Disable)) {
939
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
787
940
  }
788
941
  },
789
942
  .End => {
790
- const id = ID.from(@intCast(u4, a[i]));
943
+ const id = ID.from(@intCast(a[i]));
791
944
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
792
- printc(" {s}", .{@tagName(@intToEnum(End, a[i]))}, a, b, &i, 1, color);
945
+ printc(" {s}", .{@tagName(@as(End, @enumFromInt(a[i])))}, a, b, &i, 1, color);
793
946
  },
794
947
  .Immune => {
795
- const id = ID.from(@intCast(u4, a[i]));
948
+ const id = ID.from(@intCast(a[i]));
796
949
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
797
- printc(" {s}", .{@tagName(@intToEnum(Immune, a[i]))}, a, b, &i, 1, color);
950
+ printc(" {s}", .{@tagName(@as(Immune, @enumFromInt(a[i])))}, a, b, &i, 1, color);
798
951
  },
799
952
  .Transform => {
800
- const source = ID.from(@intCast(u4, a[i]));
953
+ const source = ID.from(@intCast(a[i]));
801
954
  printc(" {s}({d})", .{ @tagName(source.player), source.id }, a, b, &i, 1, color);
802
- const target = ID.from(@intCast(u4, a[i]));
955
+ const target = ID.from(@intCast(a[i]));
803
956
  printc(" {s}({d})", .{ @tagName(target.player), target.id }, a, b, &i, 1, color);
804
957
  },
958
+ .SideStart => {
959
+ const id = ID.from(@intCast(a[i]));
960
+ printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
961
+ printc(" {s}", .{@tagName(@as(SideStart, @enumFromInt(a[i])))}, a, b, &i, 1, color);
962
+ },
963
+ .SideEnd => {
964
+ var id = ID.from(@intCast(a[i]));
965
+ printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
966
+ const reason: SideEnd = @enumFromInt(a[i]);
967
+ printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
968
+ if (reason == .Spikes) {
969
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
970
+ id = ID.from(@intCast(a[i]));
971
+ printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
972
+ }
973
+ },
805
974
  else => unreachable,
806
975
  }
807
976
  print("\n", .{});
@@ -833,12 +1002,13 @@ fn printc(
833
1002
  }
834
1003
 
835
1004
  pub fn expectLog(
836
- comptime formatter: Formatter,
1005
+ comptime gen: anytype,
837
1006
  expected: []const u8,
838
1007
  actual: []const u8,
839
1008
  offset: usize,
840
1009
  ) !void {
841
- if (!trace) return;
1010
+ if (!enabled) return;
1011
+
842
1012
  const color = color: {
843
1013
  if (std.process.hasEnvVarConstant("ZIG_DEBUG_COLOR")) {
844
1014
  break :color true;
@@ -851,8 +1021,8 @@ pub fn expectLog(
851
1021
 
852
1022
  expectEqualBytes(expected, actual, offset) catch |err| switch (err) {
853
1023
  error.TestExpectedEqual => {
854
- format(formatter, expected, null, color);
855
- format(formatter, actual, expected, color);
1024
+ format(gen, expected, null, color);
1025
+ format(gen, actual, expected, color);
856
1026
  return err;
857
1027
  },
858
1028
  else => return err,
@@ -881,7 +1051,7 @@ fn expectEqualBytes(expected: []const u8, actual: []const u8, offset: usize) !vo
881
1051
  const endian = builtin.target.cpu.arch.endian();
882
1052
 
883
1053
  fn N(e: anytype) u8 {
884
- return @enumToInt(e);
1054
+ return @intFromEnum(e);
885
1055
  }
886
1056
 
887
1057
  const p1 = Player.P1;
@@ -892,23 +1062,32 @@ const gen1 = struct {
892
1062
  pub const helpers = @import("../gen1/helpers.zig");
893
1063
  };
894
1064
 
1065
+ const gen2 = struct {
1066
+ pub usingnamespace @import("../gen2/data.zig");
1067
+ pub const helpers = @import("../gen2/helpers.zig");
1068
+ };
1069
+
895
1070
  var buf: [gen1.LOGS_SIZE]u8 = undefined;
896
- var stream = ByteStream{ .buffer = &buf };
1071
+ var stream: ByteStream = .{ .buffer = &buf };
897
1072
  var log: FixedLog = .{ .writer = stream.writer() };
898
1073
 
899
1074
  const M = gen1.Move;
900
1075
  const S = gen1.Species;
901
1076
 
902
1077
  fn expectLog1(expected: []const u8, actual: []const u8) !void {
903
- return expectLog(gen1Formatter, expected, actual, 0);
1078
+ return expectLog(gen1, expected, actual, 0);
1079
+ }
1080
+
1081
+ fn expectLog2(expected: []const u8, actual: []const u8) !void {
1082
+ return expectLog(gen2, expected, actual, 0);
904
1083
  }
905
1084
 
906
- fn gen1Formatter(kind: Kind, byte: u8) []const u8 {
1085
+ fn formatter(comptime gen: anytype, kind: Kind, byte: u8) []const u8 {
907
1086
  return switch (kind) {
908
- .Move => @tagName(@intToEnum(M, byte)),
909
- .Species => @tagName(@intToEnum(S, byte)),
910
- .Type => @tagName(@intToEnum(gen1.Type, byte)),
911
- .Status => gen1.Status.name(byte),
1087
+ .Move => @tagName(@as(gen.Move, @enumFromInt(byte))),
1088
+ .Species => @tagName(@as(gen.Species, @enumFromInt(byte))),
1089
+ .Type => @tagName(@as(gen.Type, @enumFromInt(byte))),
1090
+ .Status => gen.Status.name(byte),
912
1091
  };
913
1092
  }
914
1093
 
@@ -993,6 +1172,25 @@ test "|switch|" {
993
1172
  };
994
1173
  try expectLog1(expected, buf[0..9]);
995
1174
  stream.reset();
1175
+
1176
+ var blissey = gen2.helpers.Pokemon.init(.{ .species = .Blissey, .moves = &.{.Splash} });
1177
+ blissey.level = 91;
1178
+ blissey.hp = 200;
1179
+ blissey.stats.hp = 400;
1180
+ blissey.status = gen2.Status.init(.PAR);
1181
+ try log.switched(p2.ident(3), &blissey);
1182
+ expected = &(.{
1183
+ N(ArgType.Switch),
1184
+ 0b1011,
1185
+ N(gen2.Species.Blissey),
1186
+ N(gen2.Gender.Female),
1187
+ 91,
1188
+ } ++ switch (endian) {
1189
+ .Big => .{ 0, 200, 1, 144, par },
1190
+ .Little => .{ 200, 0, 144, 1, par },
1191
+ });
1192
+ try expectLog2(expected, buf[0..10]);
1193
+ stream.reset();
996
1194
  }
997
1195
 
998
1196
  test "|cant|" {
@@ -1295,3 +1493,23 @@ test "|-transform|" {
1295
1493
  try expectLog1(&.{ N(ArgType.Transform), 0b1100, 0b0101 }, buf[0..3]);
1296
1494
  stream.reset();
1297
1495
  }
1496
+ test "|-sidestart|" {
1497
+ try log.sidestart(p2.ident(6), .Reflect);
1498
+ try expectLog2(&.{ N(ArgType.SideStart), 0b1110, N(SideStart.Reflect) }, buf[0..3]);
1499
+ stream.reset();
1500
+ }
1501
+ test "|-sideend|" {
1502
+ try log.sideend(p2.ident(6), .LightScreen);
1503
+ try expectLog2(&.{ N(ArgType.SideEnd), 0b1110, N(SideEnd.LightScreen) }, buf[0..3]);
1504
+ stream.reset();
1505
+
1506
+ try log.sideendFromOf(p1.ident(2), .Spikes, gen2.Move.RapidSpin, p1.ident(3));
1507
+ try expectLog2(&.{
1508
+ N(ArgType.SideEnd),
1509
+ 0b0010,
1510
+ N(SideEnd.Spikes),
1511
+ N(gen2.Move.RapidSpin),
1512
+ 0b0011,
1513
+ }, buf[0..5]);
1514
+ stream.reset();
1515
+ }