@pkmn/engine 0.1.0-dev.e5b5a998 → 0.1.0-dev.f918c3bd

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 (40) hide show
  1. package/README.md +53 -42
  2. package/build/pkg/addon.js +3 -2
  3. package/build/pkg/addon.js.map +1 -1
  4. package/build/pkg/data/layout.json +24 -2
  5. package/build/pkg/data/protocol.json +69 -12
  6. package/build/pkg/gen1.js +13 -16
  7. package/build/pkg/gen1.js.map +1 -1
  8. package/build/pkg/protocol.js +56 -37
  9. package/build/pkg/protocol.js.map +1 -1
  10. package/build.zig +82 -57
  11. package/package.json +33 -15
  12. package/src/bin/install-pkmn-engine +10 -5
  13. package/src/lib/binding/node.zig +46 -141
  14. package/src/lib/binding/wasm.zig +4 -2
  15. package/src/lib/common/battle.zig +17 -0
  16. package/src/lib/common/data.zig +15 -27
  17. package/src/lib/common/debug.zig +20 -11
  18. package/src/lib/common/js.zig +118 -0
  19. package/src/lib/common/optional.zig +60 -0
  20. package/src/lib/common/options.zig +9 -0
  21. package/src/lib/common/protocol.zig +643 -138
  22. package/src/lib/common/rational.zig +353 -0
  23. package/src/lib/common/rng.zig +15 -15
  24. package/src/lib/common/util.zig +12 -0
  25. package/src/lib/gen1/README.md +257 -177
  26. package/src/lib/gen1/calc.zig +426 -0
  27. package/src/lib/gen1/chance.zig +1058 -0
  28. package/src/lib/gen1/data/moves.zig +168 -170
  29. package/src/lib/gen1/data/types.zig +2 -2
  30. package/src/lib/gen1/data.zig +61 -51
  31. package/src/lib/gen1/helpers.zig +588 -17
  32. package/src/lib/gen1/mechanics.zig +776 -502
  33. package/src/lib/gen2/README.md +102 -83
  34. package/src/lib/gen2/data/items.zig +1 -1
  35. package/src/lib/gen2/data/moves.zig +634 -562
  36. package/src/lib/gen2/data/types.zig +1 -1
  37. package/src/lib/gen2/data.zig +100 -67
  38. package/src/lib/gen2/helpers.zig +15 -21
  39. package/src/lib/gen2/mechanics.zig +1147 -11
  40. package/src/lib/pkmn.zig +61 -2
@@ -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) {
@@ -118,12 +121,16 @@ pub const Cant = enum(u8) {
118
121
  Disable,
119
122
  Recharge,
120
123
  PP,
124
+
125
+ Attract,
121
126
  };
122
127
 
123
128
  pub const Heal = enum(u8) {
124
129
  None,
125
130
  Silent,
126
131
  Drain,
132
+
133
+ Leftovers,
127
134
  };
128
135
 
129
136
  pub const Damage = enum(u8) {
@@ -133,6 +140,13 @@ pub const Damage = enum(u8) {
133
140
  Confusion,
134
141
  LeechSeed,
135
142
  RecoilOf,
143
+
144
+ Bind,
145
+ Wrap,
146
+ FireSpin,
147
+ Clamp,
148
+ Whirlpool,
149
+ Spikes,
136
150
  };
137
151
 
138
152
  pub const Status = enum(u8) {
@@ -177,6 +191,16 @@ pub const Activate = enum(u8) {
177
191
  Struggle,
178
192
  Substitute,
179
193
  Splash,
194
+
195
+ Attract,
196
+ LockOn,
197
+ Bind,
198
+ Wrap,
199
+ FireSpin,
200
+ Clamp,
201
+ Whirlpool,
202
+
203
+ BeatUp, // FIXME of
180
204
  };
181
205
 
182
206
  pub const Start = enum(u8) {
@@ -194,6 +218,18 @@ pub const Start = enum(u8) {
194
218
 
195
219
  Disable,
196
220
  Mimic,
221
+
222
+ Nightmare,
223
+ Curse,
224
+ Foresight,
225
+ Encore,
226
+ FutureSight,
227
+
228
+ PerishSong3Silent,
229
+ PerishSong3,
230
+ PerishSong2,
231
+ PerishSong1,
232
+ PerishSong0,
197
233
  };
198
234
 
199
235
  pub const End = enum(u8) {
@@ -202,16 +238,27 @@ pub const End = enum(u8) {
202
238
  Bide,
203
239
  Substitute,
204
240
 
205
- // Silent
241
+ Nightmare,
242
+ Curse,
243
+ Foresight,
244
+ Encore,
245
+ FutureSight,
246
+ LeechSeed,
247
+ Bind,
248
+ Wrap,
249
+ FireSpin,
250
+ Clamp,
251
+ Whirlpool,
252
+
206
253
  DisableSilent,
207
254
  ConfusionSilent,
208
-
209
- Mist,
210
- FocusEnergy,
211
- LeechSeed,
212
- Toxic,
213
- LightScreen,
214
- Reflect,
255
+ MistSilent,
256
+ FocusEnergySilent,
257
+ LeechSeedSilent,
258
+ ToxicSilent,
259
+ LightScreenSilent,
260
+ ReflectSilent,
261
+ BideSilent,
215
262
  };
216
263
 
217
264
  pub const Immune = enum(u8) {
@@ -219,8 +266,34 @@ pub const Immune = enum(u8) {
219
266
  OHKO,
220
267
  };
221
268
 
269
+ pub const EndItem = enum(u8) {
270
+ None,
271
+ Eat,
272
+ };
273
+
274
+ pub const SetHP = enum(u8) {
275
+ None,
276
+ Silent,
277
+ };
278
+
279
+ pub const Side = enum(u8) {
280
+ Safeguard,
281
+ Reflect,
282
+ LightScreen,
283
+ Spikes,
284
+ };
285
+
286
+ pub const Weather = enum(u8) {
287
+ None,
288
+ Upkeep,
289
+ };
290
+
291
+ /// Null object pattern implementation of `Log` backed by a `std.io.null_writer`.
292
+ /// Ignores anything sent to it, though protocol logging should additionally be turned off
293
+ /// entirely with `options.log`.
222
294
  pub const NULL = Log(@TypeOf(std.io.null_writer)){ .writer = std.io.null_writer };
223
295
 
296
+ /// Logs protocol information to its `Writer` during a battle update when `options.log` is enabled.
224
297
  pub fn Log(comptime Writer: type) type {
225
298
  return struct {
226
299
  const Self = @This();
@@ -231,12 +304,13 @@ pub fn Log(comptime Writer: type) type {
231
304
 
232
305
  pub fn move(self: Self, source: ID, m: anytype, target: ID, from: anytype) Error!void {
233
306
  if (!enabled) return;
307
+
234
308
  assert(m != .None);
235
309
  try self.writer.writeAll(&.{
236
310
  @intFromEnum(ArgType.Move),
237
- @bitCast(u8, source),
311
+ @as(u8, @bitCast(source)),
238
312
  @intFromEnum(m),
239
- @bitCast(u8, target),
313
+ @as(u8, @bitCast(target)),
240
314
  });
241
315
  const none = &[_]u8{@intFromEnum(Move.None)};
242
316
  try self.writer.writeAll(switch (@typeInfo(@TypeOf(from))) {
@@ -253,9 +327,22 @@ pub fn Log(comptime Writer: type) type {
253
327
  }
254
328
 
255
329
  pub fn switched(self: Self, ident: ID, pokemon: anytype) Error!void {
330
+ return switchDrag(self, .Switch, ident, pokemon);
331
+ }
332
+
333
+ fn switchDrag(self: Self, arg: ArgType, ident: ID, pokemon: anytype) Error!void {
256
334
  if (!enabled) return;
257
- try self.writer.writeAll(&.{ @intFromEnum(ArgType.Switch), @bitCast(u8, ident) });
258
- try self.writer.writeAll(&.{ @intFromEnum(pokemon.species), pokemon.level });
335
+
336
+ try self.writer.writeAll(&.{ @intFromEnum(arg), @as(u8, @bitCast(ident)) });
337
+ if (@hasField(@TypeOf(pokemon.*), "dvs")) {
338
+ try self.writer.writeAll(&.{
339
+ @intFromEnum(pokemon.species),
340
+ @intFromEnum(pokemon.dvs.gender),
341
+ pokemon.level,
342
+ });
343
+ } else {
344
+ try self.writer.writeAll(&.{ @intFromEnum(pokemon.species), pokemon.level });
345
+ }
259
346
  try self.writer.writeIntNative(u16, pokemon.hp);
260
347
  try self.writer.writeIntNative(u16, pokemon.stats.hp);
261
348
  try self.writer.writeAll(&.{pokemon.status});
@@ -263,19 +350,21 @@ pub fn Log(comptime Writer: type) type {
263
350
 
264
351
  pub fn cant(self: Self, ident: ID, reason: Cant) Error!void {
265
352
  if (!enabled) return;
353
+
266
354
  assert(reason != .Disable);
267
355
  try self.writer.writeAll(&.{
268
356
  @intFromEnum(ArgType.Cant),
269
- @bitCast(u8, ident),
357
+ @as(u8, @bitCast(ident)),
270
358
  @intFromEnum(reason),
271
359
  });
272
360
  }
273
361
 
274
362
  pub fn disabled(self: Self, ident: ID, m: anytype) Error!void {
275
363
  if (!enabled) return;
364
+
276
365
  try self.writer.writeAll(&.{
277
366
  @intFromEnum(ArgType.Cant),
278
- @bitCast(u8, ident),
367
+ @as(u8, @bitCast(ident)),
279
368
  @intFromEnum(Cant.Disable),
280
369
  @intFromEnum(m),
281
370
  });
@@ -283,12 +372,14 @@ pub fn Log(comptime Writer: type) type {
283
372
 
284
373
  pub fn faint(self: Self, ident: ID, done: bool) Error!void {
285
374
  if (!enabled) return;
286
- try self.writer.writeAll(&.{ @intFromEnum(ArgType.Faint), @bitCast(u8, ident) });
375
+
376
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.Faint), @as(u8, @bitCast(ident)) });
287
377
  if (done) try self.writer.writeByte(@intFromEnum(ArgType.None));
288
378
  }
289
379
 
290
380
  pub fn turn(self: Self, num: u16) Error!void {
291
381
  if (!enabled) return;
382
+
292
383
  try self.writer.writeByte(@intFromEnum(ArgType.Turn));
293
384
  try self.writer.writeIntNative(u16, num);
294
385
  try self.writer.writeByte(@intFromEnum(ArgType.None));
@@ -296,6 +387,7 @@ pub fn Log(comptime Writer: type) type {
296
387
 
297
388
  pub fn win(self: Self, player: Player) Error!void {
298
389
  if (!enabled) return;
390
+
299
391
  try self.writer.writeAll(&.{
300
392
  @intFromEnum(ArgType.Win),
301
393
  @intFromEnum(player),
@@ -305,13 +397,15 @@ pub fn Log(comptime Writer: type) type {
305
397
 
306
398
  pub fn tie(self: Self) Error!void {
307
399
  if (!enabled) return;
400
+
308
401
  try self.writer.writeAll(&.{ @intFromEnum(ArgType.Tie), @intFromEnum(ArgType.None) });
309
402
  }
310
403
 
311
404
  pub fn damage(self: Self, ident: ID, pokemon: anytype, reason: Damage) Error!void {
312
405
  if (!enabled) return;
406
+
313
407
  assert(@intFromEnum(reason) <= @intFromEnum(Damage.LeechSeed));
314
- try self.writer.writeAll(&.{ @intFromEnum(ArgType.Damage), @bitCast(u8, ident) });
408
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.Damage), @as(u8, @bitCast(ident)) });
315
409
  try self.writer.writeIntNative(u16, pokemon.hp);
316
410
  try self.writer.writeIntNative(u16, pokemon.stats.hp);
317
411
  try self.writer.writeAll(&.{ pokemon.status, @intFromEnum(reason) });
@@ -325,21 +419,23 @@ pub fn Log(comptime Writer: type) type {
325
419
  source: ID,
326
420
  ) Error!void {
327
421
  if (!enabled) return;
422
+
328
423
  assert(@intFromEnum(reason) == @intFromEnum(Damage.RecoilOf));
329
- try self.writer.writeAll(&.{ @intFromEnum(ArgType.Damage), @bitCast(u8, ident) });
424
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.Damage), @as(u8, @bitCast(ident)) });
330
425
  try self.writer.writeIntNative(u16, pokemon.hp);
331
426
  try self.writer.writeIntNative(u16, pokemon.stats.hp);
332
427
  try self.writer.writeAll(&.{
333
428
  pokemon.status,
334
429
  @intFromEnum(reason),
335
- @bitCast(u8, source),
430
+ @as(u8, @bitCast(source)),
336
431
  });
337
432
  }
338
433
 
339
434
  pub fn heal(self: Self, ident: ID, pokemon: anytype, reason: Heal) Error!void {
340
435
  if (!enabled) return;
436
+
341
437
  assert(reason != .Drain);
342
- try self.writer.writeAll(&.{ @intFromEnum(ArgType.Heal), @bitCast(u8, ident) });
438
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.Heal), @as(u8, @bitCast(ident)) });
343
439
  try self.writer.writeIntNative(u16, pokemon.hp);
344
440
  try self.writer.writeIntNative(u16, pokemon.stats.hp);
345
441
  try self.writer.writeAll(&.{ pokemon.status, @intFromEnum(reason) });
@@ -347,22 +443,24 @@ pub fn Log(comptime Writer: type) type {
347
443
 
348
444
  pub fn drain(self: Self, source: ID, pokemon: anytype, target: ID) Error!void {
349
445
  if (!enabled) return;
350
- try self.writer.writeAll(&.{ @intFromEnum(ArgType.Heal), @bitCast(u8, source) });
446
+
447
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.Heal), @as(u8, @bitCast(source)) });
351
448
  try self.writer.writeIntNative(u16, pokemon.hp);
352
449
  try self.writer.writeIntNative(u16, pokemon.stats.hp);
353
450
  try self.writer.writeAll(&.{
354
451
  pokemon.status,
355
452
  @intFromEnum(Heal.Drain),
356
- @bitCast(u8, target),
453
+ @as(u8, @bitCast(target)),
357
454
  });
358
455
  }
359
456
 
360
457
  pub fn status(self: Self, ident: ID, value: u8, reason: Status) Error!void {
361
458
  if (!enabled) return;
459
+
362
460
  assert(reason != .From);
363
461
  try self.writer.writeAll(&.{
364
462
  @intFromEnum(ArgType.Status),
365
- @bitCast(u8, ident),
463
+ @as(u8, @bitCast(ident)),
366
464
  value,
367
465
  @intFromEnum(reason),
368
466
  });
@@ -370,9 +468,10 @@ pub fn Log(comptime Writer: type) type {
370
468
 
371
469
  pub fn statusFrom(self: Self, ident: ID, value: u8, m: anytype) Error!void {
372
470
  if (!enabled) return;
471
+
373
472
  try self.writer.writeAll(&.{
374
473
  @intFromEnum(ArgType.Status),
375
- @bitCast(u8, ident),
474
+ @as(u8, @bitCast(ident)),
376
475
  value,
377
476
  @intFromEnum(Status.From),
378
477
  @intFromEnum(m),
@@ -381,9 +480,10 @@ pub fn Log(comptime Writer: type) type {
381
480
 
382
481
  pub fn curestatus(self: Self, ident: ID, value: u8, reason: CureStatus) Error!void {
383
482
  if (!enabled) return;
483
+
384
484
  try self.writer.writeAll(&.{
385
485
  @intFromEnum(ArgType.CureStatus),
386
- @bitCast(u8, ident),
486
+ @as(u8, @bitCast(ident)),
387
487
  value,
388
488
  @intFromEnum(reason),
389
489
  });
@@ -391,98 +491,113 @@ pub fn Log(comptime Writer: type) type {
391
491
 
392
492
  pub fn boost(self: Self, ident: ID, reason: Boost, num: i8) Error!void {
393
493
  if (!enabled) return;
494
+
394
495
  assert(num != 0 and (reason != .Rage or num > 0));
395
496
  try self.writer.writeAll(&.{
396
497
  @intFromEnum(ArgType.Boost),
397
- @bitCast(u8, ident),
498
+ @as(u8, @bitCast(ident)),
398
499
  @intFromEnum(reason),
399
- @intCast(u8, num + 6),
500
+ @as(u8, @intCast(num + 6)),
400
501
  });
401
502
  }
402
503
 
403
504
  pub fn clearallboost(self: Self) Error!void {
404
505
  if (!enabled) return;
506
+
405
507
  try self.writer.writeAll(&.{@intFromEnum(ArgType.ClearAllBoost)});
406
508
  }
407
509
 
408
510
  pub fn fail(self: Self, ident: ID, reason: Fail) Error!void {
409
511
  if (!enabled) return;
512
+
410
513
  try self.writer.writeAll(&.{
411
514
  @intFromEnum(ArgType.Fail),
412
- @bitCast(u8, ident),
515
+ @as(u8, @bitCast(ident)),
413
516
  @intFromEnum(reason),
414
517
  });
415
518
  }
416
519
 
417
520
  pub fn miss(self: Self, source: ID) Error!void {
418
521
  if (!enabled) return;
419
- try self.writer.writeAll(&.{ @intFromEnum(ArgType.Miss), @bitCast(u8, source) });
522
+
523
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.Miss), @as(u8, @bitCast(source)) });
420
524
  }
421
525
 
422
526
  pub fn hitcount(self: Self, ident: ID, num: u8) Error!void {
423
527
  if (!enabled) return;
528
+
424
529
  try self.writer.writeAll(&.{
425
530
  @intFromEnum(ArgType.HitCount),
426
- @bitCast(u8, ident),
531
+ @as(u8, @bitCast(ident)),
427
532
  num,
428
533
  });
429
534
  }
430
535
 
431
536
  pub fn prepare(self: Self, source: ID, m: anytype) Error!void {
432
537
  if (!enabled) return;
538
+
433
539
  try self.writer.writeAll(&.{
434
540
  @intFromEnum(ArgType.Prepare),
435
- @bitCast(u8, source),
541
+ @as(u8, @bitCast(source)),
436
542
  @intFromEnum(m),
437
543
  });
438
544
  }
439
545
 
440
546
  pub fn mustrecharge(self: Self, ident: ID) Error!void {
441
547
  if (!enabled) return;
442
- try self.writer.writeAll(&.{ @intFromEnum(ArgType.MustRecharge), @bitCast(u8, ident) });
548
+
549
+ try self.writer.writeAll(&.{
550
+ @intFromEnum(ArgType.MustRecharge),
551
+ @as(u8, @bitCast(ident)),
552
+ });
443
553
  }
444
554
 
445
555
  pub fn activate(self: Self, ident: ID, reason: Activate) Error!void {
446
556
  if (!enabled) return;
557
+
447
558
  try self.writer.writeAll(&.{
448
559
  @intFromEnum(ArgType.Activate),
449
- @bitCast(u8, ident),
560
+ @as(u8, @bitCast(ident)),
450
561
  @intFromEnum(reason),
451
562
  });
452
563
  }
453
564
 
454
565
  pub fn fieldactivate(self: Self) Error!void {
455
566
  if (!enabled) return;
567
+
456
568
  try self.writer.writeAll(&.{@intFromEnum(ArgType.FieldActivate)});
457
569
  }
458
570
 
459
571
  pub fn start(self: Self, ident: ID, reason: Start) Error!void {
460
572
  if (!enabled) return;
573
+
461
574
  assert(@intFromEnum(reason) < @intFromEnum(Start.TypeChange));
462
575
  try self.writer.writeAll(&.{
463
576
  @intFromEnum(ArgType.Start),
464
- @bitCast(u8, ident),
577
+ @as(u8, @bitCast(ident)),
465
578
  @intFromEnum(reason),
466
579
  });
467
580
  }
468
581
 
469
582
  pub fn typechange(self: Self, source: ID, types: anytype, target: ID) Error!void {
470
583
  if (!enabled) return;
584
+
471
585
  try self.writer.writeAll(&.{
472
586
  @intFromEnum(ArgType.Start),
473
- @bitCast(u8, source),
587
+ @as(u8, @bitCast(source)),
474
588
  @intFromEnum(Start.TypeChange),
475
- @bitCast(u8, types),
476
- @bitCast(u8, target),
589
+ @as(u8, @bitCast(types)),
590
+ @as(u8, @bitCast(target)),
477
591
  });
478
592
  }
479
593
 
480
594
  pub fn startEffect(self: Self, ident: ID, reason: Start, m: anytype) Error!void {
481
595
  if (!enabled) return;
596
+
482
597
  assert(@intFromEnum(reason) > @intFromEnum(Start.TypeChange));
483
598
  try self.writer.writeAll(&.{
484
599
  @intFromEnum(ArgType.Start),
485
- @bitCast(u8, ident),
600
+ @as(u8, @bitCast(ident)),
486
601
  @intFromEnum(reason),
487
602
  @intFromEnum(m),
488
603
  });
@@ -490,68 +605,211 @@ pub fn Log(comptime Writer: type) type {
490
605
 
491
606
  pub fn end(self: Self, ident: ID, reason: End) Error!void {
492
607
  if (!enabled) return;
608
+
493
609
  try self.writer.writeAll(&.{
494
610
  @intFromEnum(ArgType.End),
495
- @bitCast(u8, ident),
611
+ @as(u8, @bitCast(ident)),
496
612
  @intFromEnum(reason),
497
613
  });
498
614
  }
499
615
 
500
616
  pub fn ohko(self: Self) Error!void {
501
617
  if (!enabled) return;
618
+
502
619
  try self.writer.writeAll(&.{@intFromEnum(ArgType.OHKO)});
503
620
  }
504
621
 
505
622
  pub fn crit(self: Self, ident: ID) Error!void {
506
623
  if (!enabled) return;
507
- try self.writer.writeAll(&.{ @intFromEnum(ArgType.Crit), @bitCast(u8, ident) });
624
+
625
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.Crit), @as(u8, @bitCast(ident)) });
508
626
  }
509
627
 
510
628
  pub fn supereffective(self: Self, ident: ID) Error!void {
511
629
  if (!enabled) return;
630
+
512
631
  try self.writer.writeAll(&.{
513
632
  @intFromEnum(ArgType.SuperEffective),
514
- @bitCast(u8, ident),
633
+ @as(u8, @bitCast(ident)),
515
634
  });
516
635
  }
517
636
 
518
637
  pub fn resisted(self: Self, ident: ID) Error!void {
519
638
  if (!enabled) return;
520
- try self.writer.writeAll(&.{ @intFromEnum(ArgType.Resisted), @bitCast(u8, ident) });
639
+
640
+ try self.writer.writeAll(&.{
641
+ @intFromEnum(ArgType.Resisted),
642
+ @as(u8, @bitCast(ident)),
643
+ });
521
644
  }
522
645
 
523
646
  pub fn immune(self: Self, ident: ID, reason: Immune) Error!void {
524
647
  if (!enabled) return;
648
+
525
649
  try self.writer.writeAll(&.{
526
650
  @intFromEnum(ArgType.Immune),
527
- @bitCast(u8, ident),
651
+ @as(u8, @bitCast(ident)),
528
652
  @intFromEnum(reason),
529
653
  });
530
654
  }
531
655
 
532
656
  pub fn transform(self: Self, source: ID, target: ID) Error!void {
533
657
  if (!enabled) return;
658
+
534
659
  try self.writer.writeAll(&.{
535
660
  @intFromEnum(ArgType.Transform),
536
- @bitCast(u8, source),
537
- @bitCast(u8, target),
661
+ @as(u8, @bitCast(source)),
662
+ @as(u8, @bitCast(target)),
663
+ });
664
+ }
665
+
666
+ pub fn drag(self: Self, ident: ID, pokemon: anytype) Error!void {
667
+ return switchDrag(self, .Drag, ident, pokemon);
668
+ }
669
+
670
+ pub fn item(self: Self, target: ID, i: anytype, source: ID) Error!void {
671
+ if (!enabled) return;
672
+
673
+ try self.writer.writeAll(&.{
674
+ @intFromEnum(ArgType.Item),
675
+ @as(u8, @bitCast(target)),
676
+ @intFromEnum(i),
677
+ @as(u8, @bitCast(source)),
678
+ });
679
+ }
680
+
681
+ pub fn enditem(self: Self, ident: ID, i: anytype, reason: EndItem) Error!void {
682
+ if (!enabled) return;
683
+
684
+ try self.writer.writeAll(&.{
685
+ @intFromEnum(ArgType.EndItem),
686
+ @as(u8, @bitCast(ident)),
687
+ @intFromEnum(i),
688
+ @intFromEnum(reason),
689
+ });
690
+ }
691
+
692
+ pub fn cureteam(self: Self, source: ID) Error!void {
693
+ if (!enabled) return;
694
+
695
+ try self.writer.writeAll(&.{
696
+ @intFromEnum(ArgType.CureTeam),
697
+ @as(u8, @bitCast(source)),
698
+ });
699
+ }
700
+
701
+ pub fn sethp(self: Self, ident: ID, pokemon: anytype, reason: SetHP) Error!void {
702
+ if (!enabled) return;
703
+
704
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.SetHP), @as(u8, @bitCast(ident)) });
705
+ try self.writer.writeIntNative(u16, pokemon.hp);
706
+ try self.writer.writeIntNative(u16, pokemon.stats.hp);
707
+ try self.writer.writeAll(&.{pokemon.status});
708
+ try self.writer.writeByte(@intFromEnum(reason));
709
+ }
710
+
711
+ pub fn setboost(self: Self, ident: ID, num: i8) Error!void {
712
+ if (!enabled) return;
713
+
714
+ try self.writer.writeAll(&.{
715
+ @intFromEnum(ArgType.SetBoost),
716
+ @as(u8, @bitCast(ident)),
717
+ @as(u8, @intCast(num + 6)),
718
+ });
719
+ }
720
+
721
+ pub fn copyboost(self: Self, source: ID, target: ID) Error!void {
722
+ if (!enabled) return;
723
+
724
+ try self.writer.writeAll(&.{
725
+ @intFromEnum(ArgType.CopyBoost),
726
+ @as(u8, @bitCast(source)),
727
+ @as(u8, @bitCast(target)),
728
+ });
729
+ }
730
+
731
+ pub fn sidestart(self: Self, player: Player, reason: Side) Error!void {
732
+ if (!enabled) return;
733
+
734
+ try self.writer.writeAll(&.{
735
+ @intFromEnum(ArgType.SideStart),
736
+ @intFromEnum(player),
737
+ @intFromEnum(reason),
738
+ });
739
+ }
740
+
741
+ pub fn sideend(self: Self, player: Player, reason: Side) Error!void {
742
+ if (!enabled) return;
743
+
744
+ assert(@intFromEnum(reason) != @intFromEnum(Side.Spikes));
745
+ try self.writer.writeAll(&.{
746
+ @intFromEnum(ArgType.SideEnd),
747
+ @intFromEnum(player),
748
+ @intFromEnum(reason),
749
+ });
750
+ }
751
+
752
+ pub fn spikesend(self: Self, player: Player, m: anytype, source: ID) Error!void {
753
+ if (!enabled) return;
754
+
755
+ try self.writer.writeAll(&.{
756
+ @intFromEnum(ArgType.SideEnd),
757
+ @intFromEnum(player),
758
+ @intFromEnum(Side.Spikes),
759
+ @intFromEnum(m),
760
+ @as(u8, @bitCast(source)),
761
+ });
762
+ }
763
+
764
+ pub fn singlemove(self: Self, ident: ID, m: anytype) Error!void {
765
+ return single(self, .SingleMove, ident, m);
766
+ }
767
+
768
+ pub fn singleturn(self: Self, ident: ID, m: anytype) Error!void {
769
+ return single(self, .SingleTurn, ident, m);
770
+ }
771
+
772
+ fn single(self: Self, arg: ArgType, ident: ID, m: anytype) Error!void {
773
+ if (!enabled) return;
774
+
775
+ try self.writer.writeAll(&.{
776
+ @intFromEnum(arg),
777
+ @as(u8, @bitCast(ident)),
778
+ @intFromEnum(m),
779
+ });
780
+ }
781
+
782
+ pub fn weather(self: Self, w: anytype, reason: Weather) Error!void {
783
+ if (!enabled) return;
784
+
785
+ assert(!(w == .None and reason == .Upkeep));
786
+ try self.writer.writeAll(&.{
787
+ @intFromEnum(ArgType.Weather),
788
+ @intFromEnum(w),
789
+ @intFromEnum(reason),
538
790
  });
539
791
  }
540
792
 
541
793
  pub fn laststill(self: Self) Error!void {
542
794
  if (!enabled) return;
795
+
543
796
  try self.writer.writeAll(&.{@intFromEnum(ArgType.LastStill)});
544
797
  }
545
798
 
546
799
  pub fn lastmiss(self: Self) Error!void {
547
800
  if (!enabled) return;
801
+
548
802
  try self.writer.writeAll(&.{@intFromEnum(ArgType.LastMiss)});
549
803
  }
550
804
  };
551
805
  }
552
806
 
807
+ /// `Log` type backed by the optimized `ByteStream.Writer`.
553
808
  pub const FixedLog = Log(ByteStream.Writer);
554
809
 
810
+ /// Stripped down version of `std.io.FixedBufferStream` optimized for efficiently writing the
811
+ /// individual protocol bytes. Note that the `ByteStream.Writer` is **not** a `std.io.Writer` and
812
+ /// should not be used for general purpose writing.
555
813
  pub const ByteStream = struct {
556
814
  buffer: []u8,
557
815
  pos: usize = 0,
@@ -592,15 +850,19 @@ pub const ByteStream = struct {
592
850
  }
593
851
  };
594
852
 
595
- pub const Kind = enum { Move, Species, Type, Status };
596
- pub const Formatter = fn (Kind, u8) []const u8;
853
+ pub const Kind = enum { Move, Species, Type, Status, Item, Weather };
597
854
 
598
- pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, color: bool) void {
855
+ pub fn format(
856
+ comptime gen: anytype,
857
+ a: []const u8,
858
+ b: ?[]const u8,
859
+ color: bool,
860
+ ) void {
599
861
  print("\n", .{});
600
862
 
601
863
  var i: usize = 0;
602
864
  while (i < a.len) {
603
- const arg = @enumFromInt(ArgType, a[i]);
865
+ const arg: ArgType = @enumFromInt(a[i]);
604
866
  const name = switch (arg) {
605
867
  .None => if (color) "\x1b[2m-\x1b[0m" else "-",
606
868
  .LastStill => "|[still]",
@@ -633,6 +895,18 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
633
895
  .Resisted => "|-resisted|",
634
896
  .Immune => "|-immune|",
635
897
  .Transform => "|-transform|",
898
+ .Drag => "|drag|",
899
+ .Item => "|-item|",
900
+ .EndItem => "|-enditem|",
901
+ .CureTeam => "|-cureteam|",
902
+ .SetHP => "|-sethp|",
903
+ .SetBoost => "|-setboost|",
904
+ .CopyBoost => "|-copyboost|",
905
+ .SideStart => "|-sidestart|",
906
+ .SideEnd => "|-sideend|",
907
+ .SingleMove => "|-singlemove|",
908
+ .SingleTurn => "|-singleturn|",
909
+ .Weather => "|-weather|",
636
910
  else => unreachable,
637
911
  };
638
912
  printc("{s}", .{name}, a, b, &i, 1, color);
@@ -646,19 +920,29 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
646
920
  .OHKO,
647
921
  => {},
648
922
  .Move => {
649
- const source = ID.from(@intCast(u4, a[i]));
923
+ const source = ID.from(@intCast(a[i]));
650
924
  printc(" {s}({d})", .{ @tagName(source.player), source.id }, a, b, &i, 1, color);
651
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
652
- const target = ID.from(@intCast(u4, a[i]));
925
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
926
+ const target = ID.from(@intCast(a[i]));
653
927
  printc(" {s}({d})", .{ @tagName(target.player), target.id }, a, b, &i, 1, color);
654
- const reason = @enumFromInt(Move, a[i]);
928
+ const reason: Move = @enumFromInt(a[i]);
655
929
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
656
- if (reason == .From) printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
930
+ if (reason == .From) {
931
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
932
+ }
657
933
  },
658
- .Switch => {
659
- const id = ID.from(@intCast(u4, a[i]));
934
+ .Switch, .Drag => {
935
+ const id = ID.from(@intCast(a[i]));
660
936
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
661
- printc(" {s}", .{formatter(.Species, a[i])}, a, b, &i, 1, color);
937
+ printc(" {s}", .{formatter(gen, .Species, a[i])}, a, b, &i, 1, color);
938
+ if (@hasDecl(gen, "Gender")) {
939
+ if (a[i] == N(gen.Gender.Unknown)) {
940
+ i += 1;
941
+ } else {
942
+ var gender = if (a[i] == N(gen.Gender.Male)) "M" else "F";
943
+ printc(", {s}", .{gender}, a, b, &i, 1, color);
944
+ }
945
+ }
662
946
  printc(" L{d}", .{a[i]}, a, b, &i, 1, color);
663
947
  switch (endian) {
664
948
  .Big => {
@@ -674,15 +958,15 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
674
958
  printc("/{d}", .{hp}, a, b, &i, 2, color);
675
959
  },
676
960
  }
677
- printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
961
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
678
962
  },
679
963
  .Cant => {
680
- const id = ID.from(@intCast(u4, a[i]));
964
+ const id = ID.from(@intCast(a[i]));
681
965
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
682
- const reason = @enumFromInt(Cant, a[i]);
966
+ const reason: Cant = @enumFromInt(a[i]);
683
967
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
684
968
  if (reason == .Disable) {
685
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
969
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
686
970
  }
687
971
  },
688
972
  .Faint,
@@ -691,8 +975,9 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
691
975
  .Crit,
692
976
  .SuperEffective,
693
977
  .Resisted,
978
+ .CureTeam,
694
979
  => {
695
- const id = ID.from(@intCast(u4, a[i]));
980
+ const id = ID.from(@intCast(a[i]));
696
981
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
697
982
  },
698
983
  .Turn => {
@@ -702,9 +987,11 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
702
987
  };
703
988
  printc(" {d}", .{turn}, a, b, &i, 2, color);
704
989
  },
705
- .Win => printc(" {s}", .{@tagName(@enumFromInt(Player, a[i]))}, a, b, &i, 1, color),
990
+ .Win => {
991
+ printc(" {s}", .{@tagName(@as(Player, @enumFromInt(a[i])))}, a, b, &i, 1, color);
992
+ },
706
993
  .Damage, .Heal => {
707
- var id = ID.from(@intCast(u4, a[i]));
994
+ var id = ID.from(@intCast(a[i]));
708
995
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
709
996
  switch (endian) {
710
997
  .Big => {
@@ -720,95 +1007,150 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
720
1007
  printc("/{d}", .{hp}, a, b, &i, 2, color);
721
1008
  },
722
1009
  }
723
- printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
1010
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
724
1011
  if (arg == .Damage) {
725
- const reason = a[i];
726
- printc(" {s}", .{@tagName(@enumFromInt(Damage, reason))}, a, b, &i, 1, color);
727
- if (reason == @intFromEnum(Damage.RecoilOf)) {
728
- id = ID.from(@intCast(u4, a[i]));
1012
+ const reason: Damage = @enumFromInt(a[i]);
1013
+ printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
1014
+ if (reason == .RecoilOf) {
1015
+ id = ID.from(@intCast(a[i]));
729
1016
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
730
1017
  }
731
1018
  } else {
732
- const reason = @enumFromInt(Heal, a[i]);
1019
+ const reason: Heal = @enumFromInt(a[i]);
733
1020
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
734
1021
  if (reason == .Drain) {
735
- id = ID.from(@intCast(u4, a[i]));
1022
+ id = ID.from(@intCast(a[i]));
736
1023
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
737
1024
  }
738
1025
  }
739
1026
  },
740
1027
  .Status => {
741
- const id = ID.from(@intCast(u4, a[i]));
1028
+ const id = ID.from(@intCast(a[i]));
742
1029
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
743
- printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
744
- const reason = @enumFromInt(Status, a[i]);
1030
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
1031
+ const reason: Status = @enumFromInt(a[i]);
745
1032
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
746
- if (reason == .From) printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
1033
+ if (reason == .From) {
1034
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
1035
+ }
747
1036
  },
748
1037
  .CureStatus => {
749
- const id = ID.from(@intCast(u4, a[i]));
1038
+ const id = ID.from(@intCast(a[i]));
750
1039
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
751
- printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
752
- printc(" {s}", .{@tagName(@enumFromInt(CureStatus, a[i]))}, a, b, &i, 1, color);
1040
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
1041
+ const reason: CureStatus = @enumFromInt(a[i]);
1042
+ printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
753
1043
  },
754
1044
  .Boost => {
755
- const id = ID.from(@intCast(u4, a[i]));
1045
+ const id = ID.from(@intCast(a[i]));
756
1046
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
757
- printc(" {s}", .{@tagName(@enumFromInt(Boost, a[i]))}, a, b, &i, 1, color);
1047
+ printc(" {s}", .{@tagName(@as(Boost, @enumFromInt(a[i])))}, a, b, &i, 1, color);
758
1048
  printc(" {d}", .{a[i]}, a, b, &i, 1, color);
759
1049
  },
760
1050
  .Fail => {
761
- const id = ID.from(@intCast(u4, a[i]));
1051
+ const id = ID.from(@intCast(a[i]));
762
1052
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
763
- printc(" {s}", .{@tagName(@enumFromInt(Fail, a[i]))}, a, b, &i, 1, color);
1053
+ printc(" {s}", .{@tagName(@as(Fail, @enumFromInt(a[i])))}, a, b, &i, 1, color);
764
1054
  },
765
- .HitCount => {
766
- const id = ID.from(@intCast(u4, a[i]));
1055
+ .HitCount, .SetBoost => {
1056
+ const id = ID.from(@intCast(a[i]));
767
1057
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
768
1058
  printc(" {d}", .{a[i]}, a, b, &i, 1, color);
769
1059
  },
770
- .Prepare => {
771
- const id = ID.from(@intCast(u4, a[i]));
1060
+ .Prepare, .SingleMove, .SingleTurn => {
1061
+ const id = ID.from(@intCast(a[i]));
772
1062
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
773
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
1063
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
774
1064
  },
775
1065
  .Activate => {
776
- const id = ID.from(@intCast(u4, a[i]));
1066
+ const id = ID.from(@intCast(a[i]));
777
1067
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
778
- printc(" {s}", .{@tagName(@enumFromInt(Activate, a[i]))}, a, b, &i, 1, color);
1068
+ printc(" {s}", .{@tagName(@as(Activate, @enumFromInt(a[i])))}, a, b, &i, 1, color);
779
1069
  },
780
1070
  .Start => {
781
- const id = ID.from(@intCast(u4, a[i]));
1071
+ const id = ID.from(@intCast(a[i]));
782
1072
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
783
1073
  const reason = a[i];
784
- printc(" {s}", .{@tagName(@enumFromInt(Start, reason))}, a, b, &i, 1, color);
785
- if (@enumFromInt(Start, reason) == .TypeChange) {
786
- const types = @bitCast(gen1.Types, a[i]);
1074
+ printc(" {s}", .{@tagName(@as(Start, @enumFromInt(reason)))}, a, b, &i, 1, color);
1075
+ if (@as(Start, @enumFromInt(reason)) == .TypeChange) {
1076
+ const types = @as(gen1.Types, @bitCast(a[i]));
787
1077
  const args = .{
788
- formatter(.Type, @intFromEnum(types.type1)),
789
- formatter(.Type, @intFromEnum(types.type2)),
1078
+ formatter(gen, .Type, @intFromEnum(types.type1)),
1079
+ formatter(gen, .Type, @intFromEnum(types.type2)),
790
1080
  };
791
1081
  printc(" {s}/{s}", args, a, b, &i, 1, color);
792
1082
  } else if (reason >= @intFromEnum(Start.Disable)) {
793
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
1083
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
794
1084
  }
795
1085
  },
796
1086
  .End => {
797
- const id = ID.from(@intCast(u4, a[i]));
1087
+ const id = ID.from(@intCast(a[i]));
798
1088
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
799
- printc(" {s}", .{@tagName(@enumFromInt(End, a[i]))}, a, b, &i, 1, color);
1089
+ printc(" {s}", .{@tagName(@as(End, @enumFromInt(a[i])))}, a, b, &i, 1, color);
800
1090
  },
801
1091
  .Immune => {
802
- const id = ID.from(@intCast(u4, a[i]));
1092
+ const id = ID.from(@intCast(a[i]));
803
1093
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
804
- printc(" {s}", .{@tagName(@enumFromInt(Immune, a[i]))}, a, b, &i, 1, color);
1094
+ printc(" {s}", .{@tagName(@as(Immune, @enumFromInt(a[i])))}, a, b, &i, 1, color);
805
1095
  },
806
- .Transform => {
807
- const source = ID.from(@intCast(u4, a[i]));
1096
+ .Transform, .CopyBoost => {
1097
+ const source = ID.from(@intCast(a[i]));
808
1098
  printc(" {s}({d})", .{ @tagName(source.player), source.id }, a, b, &i, 1, color);
809
- const target = ID.from(@intCast(u4, a[i]));
1099
+ const target = ID.from(@intCast(a[i]));
810
1100
  printc(" {s}({d})", .{ @tagName(target.player), target.id }, a, b, &i, 1, color);
811
1101
  },
1102
+ .Item, .EndItem => {
1103
+ var id = ID.from(@intCast(a[i]));
1104
+ printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
1105
+ printc(" {s}", .{formatter(gen, .Item, a[i])}, a, b, &i, 1, color);
1106
+ if (arg == .Item) {
1107
+ id = ID.from(@intCast(a[i]));
1108
+ printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
1109
+ } else {
1110
+ const reason: EndItem = @enumFromInt(a[i]);
1111
+ printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
1112
+ }
1113
+ },
1114
+ .SetHP => {
1115
+ const id = ID.from(@intCast(a[i]));
1116
+ printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
1117
+ switch (endian) {
1118
+ .Big => {
1119
+ var hp = @as(u16, a[i]) << 8 | @as(u16, a[i + 1]);
1120
+ printc(" {d}", .{hp}, a, b, &i, 2, color);
1121
+ hp = @as(u16, a[i]) << 8 | @as(u16, a[i + 1]);
1122
+ printc("/{d}", .{hp}, a, b, &i, 2, color);
1123
+ },
1124
+ .Little => {
1125
+ var hp = @as(u16, a[i + 1]) << 8 | @as(u16, a[i]);
1126
+ printc(" {d}", .{hp}, a, b, &i, 2, color);
1127
+ hp = @as(u16, a[i + 1]) << 8 | @as(u16, a[i]);
1128
+ printc("/{d}", .{hp}, a, b, &i, 2, color);
1129
+ },
1130
+ }
1131
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
1132
+ printc(" {s}", .{@tagName(@as(SetHP, @enumFromInt(a[i])))}, a, b, &i, 1, color);
1133
+ },
1134
+ .SideStart => {
1135
+ const id = ID.from(@intCast(a[i]));
1136
+ printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
1137
+ printc(" {s}", .{@tagName(@as(Side, @enumFromInt(a[i])))}, a, b, &i, 1, color);
1138
+ },
1139
+ .SideEnd => {
1140
+ var id = ID.from(@intCast(a[i]));
1141
+ printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
1142
+ const reason: Side = @enumFromInt(a[i]);
1143
+ printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
1144
+ if (reason == .Spikes) {
1145
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
1146
+ id = ID.from(@intCast(a[i]));
1147
+ printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
1148
+ }
1149
+ },
1150
+ .Weather => {
1151
+ printc(" {s}", .{formatter(gen, .Weather, a[i])}, a, b, &i, 1, color);
1152
+ printc(" {s}", .{@tagName(@as(Weather, @enumFromInt(a[i])))}, a, b, &i, 1, color);
1153
+ },
812
1154
  else => unreachable,
813
1155
  }
814
1156
  print("\n", .{});
@@ -840,12 +1182,13 @@ fn printc(
840
1182
  }
841
1183
 
842
1184
  pub fn expectLog(
843
- comptime formatter: Formatter,
1185
+ comptime gen: anytype,
844
1186
  expected: []const u8,
845
1187
  actual: []const u8,
846
1188
  offset: usize,
847
1189
  ) !void {
848
1190
  if (!enabled) return;
1191
+
849
1192
  const color = color: {
850
1193
  if (std.process.hasEnvVarConstant("ZIG_DEBUG_COLOR")) {
851
1194
  break :color true;
@@ -858,8 +1201,8 @@ pub fn expectLog(
858
1201
 
859
1202
  expectEqualBytes(expected, actual, offset) catch |err| switch (err) {
860
1203
  error.TestExpectedEqual => {
861
- format(formatter, expected, null, color);
862
- format(formatter, actual, expected, color);
1204
+ format(gen, expected, null, color);
1205
+ format(gen, actual, expected, color);
863
1206
  return err;
864
1207
  },
865
1208
  else => return err,
@@ -899,49 +1242,71 @@ const gen1 = struct {
899
1242
  pub const helpers = @import("../gen1/helpers.zig");
900
1243
  };
901
1244
 
1245
+ const gen2 = struct {
1246
+ pub usingnamespace @import("../gen2/data.zig");
1247
+ pub const helpers = @import("../gen2/helpers.zig");
1248
+ };
1249
+
902
1250
  var buf: [gen1.LOGS_SIZE]u8 = undefined;
903
- var stream = ByteStream{ .buffer = &buf };
1251
+ var stream: ByteStream = .{ .buffer = &buf };
904
1252
  var log: FixedLog = .{ .writer = stream.writer() };
905
1253
 
906
- const M = gen1.Move;
907
- const S = gen1.Species;
1254
+ const M1 = gen1.Move;
1255
+ const S1 = gen1.Species;
1256
+
1257
+ const M2 = gen2.Move;
1258
+ const S2 = gen2.Species;
1259
+ const I2 = gen2.Item;
1260
+ const W2 = gen2.Weather;
908
1261
 
909
1262
  fn expectLog1(expected: []const u8, actual: []const u8) !void {
910
- return expectLog(gen1Formatter, expected, actual, 0);
1263
+ return expectLog(gen1, expected, actual, 0);
1264
+ }
1265
+
1266
+ fn expectLog2(expected: []const u8, actual: []const u8) !void {
1267
+ return expectLog(gen2, expected, actual, 0);
911
1268
  }
912
1269
 
913
- fn gen1Formatter(kind: Kind, byte: u8) []const u8 {
1270
+ fn formatter(comptime gen: anytype, kind: Kind, byte: u8) []const u8 {
914
1271
  return switch (kind) {
915
- .Move => @tagName(@enumFromInt(M, byte)),
916
- .Species => @tagName(@enumFromInt(S, byte)),
917
- .Type => @tagName(@enumFromInt(gen1.Type, byte)),
918
- .Status => gen1.Status.name(byte),
1272
+ .Move => @tagName(@as(gen.Move, @enumFromInt(byte))),
1273
+ .Species => @tagName(@as(gen.Species, @enumFromInt(byte))),
1274
+ .Type => @tagName(@as(gen.Type, @enumFromInt(byte))),
1275
+ .Status => gen.Status.name(byte),
1276
+ .Item => if (@hasDecl(gen, "Item"))
1277
+ @tagName(@as(gen.Item, @enumFromInt(byte)))
1278
+ else
1279
+ unreachable,
1280
+ .Weather => if (@hasDecl(gen, "Weather"))
1281
+ @tagName(@as(gen.Weather, @enumFromInt(byte)))
1282
+ else
1283
+ unreachable,
919
1284
  };
920
1285
  }
921
1286
 
922
1287
  test "|move|" {
923
- try log.move(p2.ident(4), M.Thunderbolt, p1.ident(5), null);
1288
+ try log.move(p2.ident(4), M1.Thunderbolt, p1.ident(5), null);
924
1289
  try expectLog1(
925
- &.{ N(ArgType.Move), 0b1100, N(M.Thunderbolt), 0b0101, N(Move.None) },
1290
+ &.{ N(ArgType.Move), 0b1100, N(M1.Thunderbolt), 0b0101, N(Move.None) },
926
1291
  buf[0..5],
927
1292
  );
928
1293
  stream.reset();
929
1294
 
930
- try log.move(p2.ident(4), M.Wrap, p1.ident(5), M.Wrap);
931
- const wrap = N(M.Wrap);
1295
+ try log.move(p2.ident(4), M1.Wrap, p1.ident(5), M1.Wrap);
1296
+ const wrap = N(M1.Wrap);
932
1297
  try expectLog1(
933
1298
  &.{ N(ArgType.Move), 0b1100, wrap, 0b0101, N(Move.From), wrap },
934
1299
  buf[0..6],
935
1300
  );
936
1301
  stream.reset();
937
1302
 
938
- try log.move(p2.ident(4), M.SkullBash, .{}, null);
1303
+ try log.move(p2.ident(4), M1.SkullBash, .{}, null);
939
1304
  try log.laststill();
940
1305
  try expectLog1(
941
1306
  &.{
942
1307
  N(ArgType.Move),
943
1308
  0b1100,
944
- N(M.SkullBash),
1309
+ N(M1.SkullBash),
945
1310
  0,
946
1311
  N(Move.None),
947
1312
  N(ArgType.LastStill),
@@ -950,13 +1315,13 @@ test "|move|" {
950
1315
  );
951
1316
  stream.reset();
952
1317
 
953
- try log.move(p2.ident(4), M.Tackle, p1.ident(5), null);
1318
+ try log.move(p2.ident(4), M1.Tackle, p1.ident(5), null);
954
1319
  try log.lastmiss();
955
1320
  try expectLog1(
956
1321
  &.{
957
1322
  N(ArgType.Move),
958
1323
  0b1100,
959
- N(M.Tackle),
1324
+ N(M1.Tackle),
960
1325
  0b0101,
961
1326
  N(Move.None),
962
1327
  N(ArgType.LastMiss),
@@ -975,8 +1340,8 @@ test "|switch|" {
975
1340
  try log.switched(p2.ident(3), &snorlax);
976
1341
  const par = 0b1000000;
977
1342
  var expected: []const u8 = switch (endian) {
978
- .Big => &.{ N(ArgType.Switch), 0b1011, N(S.Snorlax), 91, 0, 200, 1, 144, par },
979
- .Little => &.{ N(ArgType.Switch), 0b1011, N(S.Snorlax), 91, 200, 0, 144, 1, par },
1343
+ .Big => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 91, 0, 200, 1, 144, par },
1344
+ .Little => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 91, 200, 0, 144, 1, par },
980
1345
  };
981
1346
  try expectLog1(expected, buf[0..9]);
982
1347
  stream.reset();
@@ -986,8 +1351,8 @@ test "|switch|" {
986
1351
  snorlax.status = 0;
987
1352
  try log.switched(p2.ident(3), &snorlax);
988
1353
  expected = switch (endian) {
989
- .Big => &.{ N(ArgType.Switch), 0b1011, N(S.Snorlax), 100, 0, 0, 1, 144, 0 },
990
- .Little => &.{ N(ArgType.Switch), 0b1011, N(S.Snorlax), 100, 0, 0, 144, 1, 0 },
1354
+ .Big => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 100, 0, 0, 1, 144, 0 },
1355
+ .Little => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 100, 0, 0, 144, 1, 0 },
991
1356
  };
992
1357
  try expectLog1(expected, buf[0..9]);
993
1358
  stream.reset();
@@ -995,11 +1360,30 @@ test "|switch|" {
995
1360
  snorlax.hp = 400;
996
1361
  try log.switched(p2.ident(3), &snorlax);
997
1362
  expected = switch (endian) {
998
- .Big => &.{ N(ArgType.Switch), 0b1011, N(S.Snorlax), 100, 1, 144, 1, 144, 0 },
999
- .Little => &.{ N(ArgType.Switch), 0b1011, N(S.Snorlax), 100, 144, 1, 144, 1, 0 },
1363
+ .Big => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 100, 1, 144, 1, 144, 0 },
1364
+ .Little => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 100, 144, 1, 144, 1, 0 },
1000
1365
  };
1001
1366
  try expectLog1(expected, buf[0..9]);
1002
1367
  stream.reset();
1368
+
1369
+ var blissey = gen2.helpers.Pokemon.init(.{ .species = .Blissey, .moves = &.{.Splash} });
1370
+ blissey.level = 91;
1371
+ blissey.hp = 200;
1372
+ blissey.stats.hp = 400;
1373
+ blissey.status = gen2.Status.init(.PAR);
1374
+ try log.switched(p2.ident(3), &blissey);
1375
+ expected = &(.{
1376
+ N(ArgType.Switch),
1377
+ 0b1011,
1378
+ N(S2.Blissey),
1379
+ N(gen2.Gender.Female),
1380
+ 91,
1381
+ } ++ switch (endian) {
1382
+ .Big => .{ 0, 200, 1, 144, par },
1383
+ .Little => .{ 200, 0, 144, 1, par },
1384
+ });
1385
+ try expectLog2(expected, buf[0..10]);
1386
+ stream.reset();
1003
1387
  }
1004
1388
 
1005
1389
  test "|cant|" {
@@ -1007,8 +1391,8 @@ test "|cant|" {
1007
1391
  try expectLog1(&.{ N(ArgType.Cant), 0b1110, N(Cant.Bound) }, buf[0..3]);
1008
1392
  stream.reset();
1009
1393
 
1010
- try log.disabled(p1.ident(2), M.Earthquake);
1011
- try expectLog1(&.{ N(ArgType.Cant), 2, N(Cant.Disable), N(M.Earthquake) }, buf[0..4]);
1394
+ try log.disabled(p1.ident(2), M1.Earthquake);
1395
+ try expectLog1(&.{ N(ArgType.Cant), 2, N(Cant.Disable), N(M1.Earthquake) }, buf[0..4]);
1012
1396
  stream.reset();
1013
1397
  }
1014
1398
 
@@ -1118,9 +1502,9 @@ test "|-status|" {
1118
1502
  try expectLog1(&.{ N(ArgType.Status), 0b1010, 0b1000, N(Status.Silent) }, buf[0..4]);
1119
1503
  stream.reset();
1120
1504
 
1121
- try log.statusFrom(p1.ident(1), gen1.Status.init(.PAR), M.BodySlam);
1505
+ try log.statusFrom(p1.ident(1), gen1.Status.init(.PAR), M1.BodySlam);
1122
1506
  try expectLog1(
1123
- &.{ N(ArgType.Status), 0b0001, 0b1000000, N(Status.From), N(M.BodySlam) },
1507
+ &.{ N(ArgType.Status), 0b0001, 0b1000000, N(Status.From), N(M1.BodySlam) },
1124
1508
  buf[0..5],
1125
1509
  );
1126
1510
  stream.reset();
@@ -1191,8 +1575,8 @@ test "|-hitcount|" {
1191
1575
  }
1192
1576
 
1193
1577
  test "|-prepare|" {
1194
- try log.prepare(p2.ident(2), M.Dig);
1195
- try expectLog1(&.{ N(ArgType.Prepare), 0b1010, N(M.Dig) }, buf[0..3]);
1578
+ try log.prepare(p2.ident(2), M1.Dig);
1579
+ try expectLog1(&.{ N(ArgType.Prepare), 0b1010, N(M1.Dig) }, buf[0..3]);
1196
1580
  stream.reset();
1197
1581
  }
1198
1582
 
@@ -1249,12 +1633,12 @@ test "|-start|" {
1249
1633
  );
1250
1634
  stream.reset();
1251
1635
 
1252
- try log.startEffect(p1.ident(2), .Disable, M.Surf);
1253
- try expectLog1(&.{ N(ArgType.Start), 0b0010, N(Start.Disable), N(M.Surf) }, buf[0..4]);
1636
+ try log.startEffect(p1.ident(2), .Disable, M1.Surf);
1637
+ try expectLog1(&.{ N(ArgType.Start), 0b0010, N(Start.Disable), N(M1.Surf) }, buf[0..4]);
1254
1638
  stream.reset();
1255
1639
 
1256
- try log.startEffect(p1.ident(2), .Mimic, M.Surf);
1257
- try expectLog1(&.{ N(ArgType.Start), 0b0010, N(Start.Mimic), N(M.Surf) }, buf[0..4]);
1640
+ try log.startEffect(p1.ident(2), .Mimic, M1.Surf);
1641
+ try expectLog1(&.{ N(ArgType.Start), 0b0010, N(Start.Mimic), N(M1.Surf) }, buf[0..4]);
1258
1642
  stream.reset();
1259
1643
  }
1260
1644
 
@@ -1273,21 +1657,25 @@ test "|-ohko|" {
1273
1657
  try expectLog1(&.{N(ArgType.OHKO)}, buf[0..1]);
1274
1658
  stream.reset();
1275
1659
  }
1660
+
1276
1661
  test "|-crit|" {
1277
1662
  try log.crit(p2.ident(5));
1278
1663
  try expectLog1(&.{ N(ArgType.Crit), 0b1101 }, buf[0..2]);
1279
1664
  stream.reset();
1280
1665
  }
1666
+
1281
1667
  test "|-supereffective|" {
1282
1668
  try log.supereffective(p1.ident(1));
1283
1669
  try expectLog1(&.{ N(ArgType.SuperEffective), 0b0001 }, buf[0..2]);
1284
1670
  stream.reset();
1285
1671
  }
1672
+
1286
1673
  test "|-resisted|" {
1287
1674
  try log.resisted(p2.ident(2));
1288
1675
  try expectLog1(&.{ N(ArgType.Resisted), 0b1010 }, buf[0..2]);
1289
1676
  stream.reset();
1290
1677
  }
1678
+
1291
1679
  test "|-immune|" {
1292
1680
  try log.immune(p1.ident(3), .None);
1293
1681
  try expectLog1(&.{ N(ArgType.Immune), 0b0011, N(Immune.None) }, buf[0..3]);
@@ -1297,8 +1685,125 @@ test "|-immune|" {
1297
1685
  try expectLog1(&.{ N(ArgType.Immune), 0b1010, N(Immune.OHKO) }, buf[0..3]);
1298
1686
  stream.reset();
1299
1687
  }
1688
+
1300
1689
  test "|-transform|" {
1301
1690
  try log.transform(p2.ident(4), p1.ident(5));
1302
1691
  try expectLog1(&.{ N(ArgType.Transform), 0b1100, 0b0101 }, buf[0..3]);
1303
1692
  stream.reset();
1304
1693
  }
1694
+
1695
+ test "|drag|" {
1696
+ var blissey = gen2.helpers.Pokemon.init(.{ .species = .Blissey, .moves = &.{.Splash} });
1697
+ blissey.level = 91;
1698
+ blissey.hp = 200;
1699
+ blissey.stats.hp = 400;
1700
+ blissey.status = gen2.Status.init(.PAR);
1701
+ try log.drag(p2.ident(3), &blissey);
1702
+ const par = 0b1000000;
1703
+ const expected = &(.{
1704
+ N(ArgType.Drag),
1705
+ 0b1011,
1706
+ N(S2.Blissey),
1707
+ N(gen2.Gender.Female),
1708
+ 91,
1709
+ } ++ switch (endian) {
1710
+ .Big => .{ 0, 200, 1, 144, par },
1711
+ .Little => .{ 200, 0, 144, 1, par },
1712
+ });
1713
+ try expectLog2(expected, buf[0..10]);
1714
+ stream.reset();
1715
+ }
1716
+
1717
+ test "|-item|" {
1718
+ try log.item(p2.ident(2), I2.GoldBerry, p1.ident(4));
1719
+ try expectLog2(&.{ N(ArgType.Item), 0b1010, N(I2.GoldBerry), 0b0100 }, buf[0..4]);
1720
+ stream.reset();
1721
+ }
1722
+
1723
+ test "|-enditem|" {
1724
+ try log.enditem(p1.ident(1), I2.BerserkGene, .None);
1725
+ try expectLog2(&.{ N(ArgType.EndItem), 0b0001, N(I2.BerserkGene), N(EndItem.None) }, buf[0..4]);
1726
+ stream.reset();
1727
+
1728
+ try log.enditem(p2.ident(4), I2.PRZCureBerry, .Eat);
1729
+ try expectLog2(&.{ N(ArgType.EndItem), 0b1100, N(I2.PRZCureBerry), N(EndItem.Eat) }, buf[0..4]);
1730
+ stream.reset();
1731
+ }
1732
+
1733
+ test "|-cureteam|" {
1734
+ try log.cureteam(p2.ident(5));
1735
+ try expectLog2(&.{ N(ArgType.CureTeam), 0b1101 }, buf[0..2]);
1736
+ stream.reset();
1737
+ }
1738
+
1739
+ test "|-sethp|" {
1740
+ var blissey = gen2.helpers.Pokemon.init(.{ .species = .Blissey, .moves = &.{.PainSplit} });
1741
+ blissey.hp = 612;
1742
+ try log.sethp(p2.ident(2), &blissey, .Silent);
1743
+ var expected: []const u8 = switch (endian) {
1744
+ .Big => &.{ N(ArgType.SetHP), 0b1010, 2, 100, 2, 201, 0, N(SetHP.Silent) },
1745
+ .Little => &.{ N(ArgType.SetHP), 0b1010, 100, 2, 201, 2, 0, N(SetHP.Silent) },
1746
+ };
1747
+ try expectLog2(expected, buf[0..8]);
1748
+ stream.reset();
1749
+ }
1750
+
1751
+ test "|-setboost|" {
1752
+ try log.setboost(p2.ident(6), 6);
1753
+ try expectLog2(&.{ N(ArgType.SetBoost), 0b1110, 12 }, buf[0..3]);
1754
+ stream.reset();
1755
+
1756
+ try log.setboost(p2.ident(3), 2);
1757
+ try expectLog2(&.{ N(ArgType.SetBoost), 0b1011, 8 }, buf[0..3]);
1758
+ stream.reset();
1759
+ }
1760
+
1761
+ test "|-copyboost|" {
1762
+ try log.copyboost(p2.ident(3), p1.ident(6));
1763
+ try expectLog2(&.{ N(ArgType.CopyBoost), 0b1011, 0b0110 }, buf[0..3]);
1764
+ stream.reset();
1765
+ }
1766
+
1767
+ test "|-sidestart|" {
1768
+ try log.sidestart(.P2, .Reflect);
1769
+ try expectLog2(&.{ N(ArgType.SideStart), 1, N(Side.Reflect) }, buf[0..3]);
1770
+ stream.reset();
1771
+ }
1772
+
1773
+ test "|-sideend|" {
1774
+ try log.sideend(.P2, .LightScreen);
1775
+ try expectLog2(&.{ N(ArgType.SideEnd), 1, N(Side.LightScreen) }, buf[0..3]);
1776
+ stream.reset();
1777
+
1778
+ try log.spikesend(.P1, M2.RapidSpin, p1.ident(3));
1779
+ try expectLog2(&.{
1780
+ N(ArgType.SideEnd),
1781
+ 0,
1782
+ N(Side.Spikes),
1783
+ N(M2.RapidSpin),
1784
+ 0b0011,
1785
+ }, buf[0..5]);
1786
+ stream.reset();
1787
+ }
1788
+
1789
+ test "|-singlemove|" {
1790
+ try log.singlemove(p2.ident(2), M2.DestinyBond);
1791
+ try expectLog2(&.{ N(ArgType.SingleMove), 0b1010, N(M2.DestinyBond) }, buf[0..3]);
1792
+ stream.reset();
1793
+ }
1794
+
1795
+ test "|-singleturn|" {
1796
+ try log.singleturn(p1.ident(3), M2.Protect);
1797
+ try expectLog2(&.{ N(ArgType.SingleTurn), 0b0011, N(M2.Protect) }, buf[0..3]);
1798
+ stream.reset();
1799
+ }
1800
+
1801
+ test "|-weather|" {
1802
+ try log.weather(W2.Rain, .Upkeep);
1803
+ try expectLog2(&.{ N(ArgType.Weather), N(W2.Rain), N(Weather.Upkeep) }, buf[0..3]);
1804
+ stream.reset();
1805
+
1806
+ try log.weather(W2.None, .None);
1807
+ try expectLog2(&.{ N(ArgType.Weather), N(W2.None), N(Weather.None) }, buf[0..3]);
1808
+ stream.reset();
1809
+ }