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

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