@pkmn/engine 0.1.0-dev.8b83af6b → 0.1.0-dev.900756a9

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 (49) 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 +26 -4
  6. package/build/pkg/data/protocol.json +69 -12
  7. package/build/pkg/data.d.ts +2 -2
  8. package/build/pkg/gen1.js +15 -18
  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/tools/debug.js +3 -1
  13. package/build/tools/debug.js.map +1 -1
  14. package/build.zig +96 -65
  15. package/package.json +40 -19
  16. package/src/bin/install-pkmn-engine +10 -5
  17. package/src/lib/binding/node.zig +40 -132
  18. package/src/lib/binding/wasm.zig +2 -0
  19. package/src/lib/common/battle.zig +17 -0
  20. package/src/lib/common/data.zig +8 -20
  21. package/src/lib/common/debug.zig +20 -11
  22. package/src/lib/common/js.zig +118 -0
  23. package/src/lib/common/optional.zig +67 -0
  24. package/src/lib/common/options.zig +9 -0
  25. package/src/lib/common/protocol.zig +635 -88
  26. package/src/lib/common/rational.zig +353 -0
  27. package/src/lib/common/rng.zig +19 -10
  28. package/src/lib/common/util.zig +12 -0
  29. package/src/lib/gen1/README.md +284 -204
  30. package/src/lib/gen1/calc.zig +426 -0
  31. package/src/lib/gen1/chance.zig +1060 -0
  32. package/src/lib/gen1/data/moves.zig +192 -180
  33. package/src/lib/gen1/data/species.zig +2 -2
  34. package/src/lib/gen1/data/types.zig +7 -7
  35. package/src/lib/gen1/data.zig +67 -58
  36. package/src/lib/gen1/helpers.zig +566 -10
  37. package/src/lib/gen1/mechanics.zig +800 -563
  38. package/src/lib/gen2/README.md +105 -85
  39. package/src/lib/gen2/calc.zig +143 -0
  40. package/src/lib/gen2/chance.zig +1314 -0
  41. package/src/lib/gen2/data/items.zig +12 -18
  42. package/src/lib/gen2/data/moves.zig +663 -569
  43. package/src/lib/gen2/data/species.zig +1 -1
  44. package/src/lib/gen2/data/types.zig +134 -31
  45. package/src/lib/gen2/data.zig +115 -78
  46. package/src/lib/gen2/generated.zig +808 -0
  47. package/src/lib/gen2/helpers.zig +11 -17
  48. package/src/lib/gen2/mechanics.zig +3492 -12
  49. 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,17 @@ 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,
153
+
154
+ Nightmare,
155
+ Curse,
156
+ Sandstorm,
136
157
  };
137
158
 
138
159
  pub const Status = enum(u8) {
@@ -175,8 +196,25 @@ pub const Activate = enum(u8) {
175
196
  Haze,
176
197
  Mist,
177
198
  Struggle,
178
- Substitute,
199
+ Substitute, // FIXME SubstituteDamage
179
200
  Splash,
201
+
202
+ Rage, // TODO
203
+
204
+ Attract,
205
+ LockOn,
206
+ Bind,
207
+ Wrap,
208
+ FireSpin,
209
+ Clamp,
210
+ Whirlpool,
211
+ SubstituteBlock,
212
+
213
+ DestinyBond,
214
+
215
+ BeatUp, // FIXME of
216
+
217
+ Magnitude,
180
218
  };
181
219
 
182
220
  pub const Start = enum(u8) {
@@ -194,6 +232,19 @@ pub const Start = enum(u8) {
194
232
 
195
233
  Disable,
196
234
  Mimic,
235
+
236
+ Attract,
237
+ Nightmare,
238
+ Curse,
239
+ Foresight,
240
+ Encore,
241
+ FutureSight,
242
+
243
+ PerishSong0,
244
+ PerishSong1,
245
+ PerishSong2,
246
+ PerishSong3,
247
+ PerishSong3Silent,
197
248
  };
198
249
 
199
250
  pub const End = enum(u8) {
@@ -202,16 +253,30 @@ pub const End = enum(u8) {
202
253
  Bide,
203
254
  Substitute,
204
255
 
205
- // Silent
256
+ Nightmare,
257
+ Curse,
258
+ Foresight,
259
+ Encore,
260
+ FutureSight,
261
+ LeechSeed,
262
+
263
+ Bind,
264
+ Wrap,
265
+ FireSpin,
266
+ Clamp,
267
+ Whirlpool,
268
+
206
269
  DisableSilent,
207
270
  ConfusionSilent,
208
-
209
- Mist,
210
- FocusEnergy,
211
- LeechSeed,
212
- Toxic,
213
- LightScreen,
214
- Reflect,
271
+ MistSilent,
272
+ FocusEnergySilent,
273
+ LeechSeedSilent,
274
+ ToxicSilent,
275
+ LightScreenSilent,
276
+ ReflectSilent,
277
+ BideSilent,
278
+
279
+ LeechSeedFrom,
215
280
  };
216
281
 
217
282
  pub const Immune = enum(u8) {
@@ -219,8 +284,34 @@ pub const Immune = enum(u8) {
219
284
  OHKO,
220
285
  };
221
286
 
287
+ pub const EndItem = enum(u8) {
288
+ None,
289
+ Eat,
290
+ };
291
+
292
+ pub const SetHP = enum(u8) {
293
+ None,
294
+ Silent,
295
+ };
296
+
297
+ pub const Side = enum(u8) {
298
+ Safeguard,
299
+ Reflect,
300
+ LightScreen,
301
+ Spikes,
302
+ };
303
+
304
+ pub const Weather = enum(u8) {
305
+ None,
306
+ Upkeep,
307
+ };
308
+
309
+ /// Null object pattern implementation of `Log` backed by a `std.io.null_writer`.
310
+ /// Ignores anything sent to it, though protocol logging should additionally be turned off
311
+ /// entirely with `options.log`.
222
312
  pub const NULL = Log(@TypeOf(std.io.null_writer)){ .writer = std.io.null_writer };
223
313
 
314
+ /// Logs protocol information to its `Writer` during a battle update when `options.log` is enabled.
224
315
  pub fn Log(comptime Writer: type) type {
225
316
  return struct {
226
317
  const Self = @This();
@@ -229,33 +320,52 @@ pub fn Log(comptime Writer: type) type {
229
320
 
230
321
  pub const Error = Writer.Error;
231
322
 
232
- pub fn move(self: Self, source: ID, m: anytype, target: ID, from: anytype) Error!void {
323
+ pub fn move(self: Self, source: ID, m: anytype, target: ID) Error!void {
233
324
  if (!enabled) return;
325
+
234
326
  assert(m != .None);
235
327
  try self.writer.writeAll(&.{
236
328
  @intFromEnum(ArgType.Move),
237
329
  @as(u8, @bitCast(source)),
238
330
  @intFromEnum(m),
239
331
  @as(u8, @bitCast(target)),
332
+ @intFromEnum(Move.None),
240
333
  });
241
- const none = &[_]u8{@intFromEnum(Move.None)};
242
- try self.writer.writeAll(switch (@typeInfo(@TypeOf(from))) {
243
- .Null => none,
244
- .Optional => if (from) |f| blk: {
245
- assert(@intFromEnum(f) != 0);
246
- break :blk &[_]u8{ @intFromEnum(Move.From), @intFromEnum(f) };
247
- } else none,
248
- else => blk: {
249
- assert(@intFromEnum(from) != 0);
250
- break :blk &[_]u8{ @intFromEnum(Move.From), @intFromEnum(from) };
251
- },
334
+ }
335
+
336
+ pub fn moveFrom(self: Self, source: ID, m: anytype, target: ID, from: anytype) Error!void {
337
+ if (!enabled) return;
338
+
339
+ assert(m != .None);
340
+ try self.writer.writeAll(&.{
341
+ @intFromEnum(ArgType.Move),
342
+ @as(u8, @bitCast(source)),
343
+ @intFromEnum(m),
344
+ @as(u8, @bitCast(target)),
252
345
  });
346
+ try self.writer.writeAll(if (@intFromEnum(from) == 0)
347
+ &.{@intFromEnum(Move.None)}
348
+ else
349
+ &.{ @intFromEnum(Move.From), @intFromEnum(from) });
253
350
  }
254
351
 
255
352
  pub fn switched(self: Self, ident: ID, pokemon: anytype) Error!void {
353
+ return switchDrag(self, .Switch, ident, pokemon);
354
+ }
355
+
356
+ fn switchDrag(self: Self, arg: ArgType, ident: ID, pokemon: anytype) Error!void {
256
357
  if (!enabled) return;
257
- try self.writer.writeAll(&.{ @intFromEnum(ArgType.Switch), @as(u8, @bitCast(ident)) });
258
- try self.writer.writeAll(&.{ @intFromEnum(pokemon.species), pokemon.level });
358
+
359
+ try self.writer.writeAll(&.{ @intFromEnum(arg), @as(u8, @bitCast(ident)) });
360
+ if (@hasField(@TypeOf(pokemon.*), "dvs")) {
361
+ try self.writer.writeAll(&.{
362
+ @intFromEnum(pokemon.species),
363
+ @intFromEnum(pokemon.dvs.gender),
364
+ pokemon.level,
365
+ });
366
+ } else {
367
+ try self.writer.writeAll(&.{ @intFromEnum(pokemon.species), pokemon.level });
368
+ }
259
369
  try self.writer.writeIntNative(u16, pokemon.hp);
260
370
  try self.writer.writeIntNative(u16, pokemon.stats.hp);
261
371
  try self.writer.writeAll(&.{pokemon.status});
@@ -263,6 +373,7 @@ pub fn Log(comptime Writer: type) type {
263
373
 
264
374
  pub fn cant(self: Self, ident: ID, reason: Cant) Error!void {
265
375
  if (!enabled) return;
376
+
266
377
  assert(reason != .Disable);
267
378
  try self.writer.writeAll(&.{
268
379
  @intFromEnum(ArgType.Cant),
@@ -273,6 +384,7 @@ pub fn Log(comptime Writer: type) type {
273
384
 
274
385
  pub fn disabled(self: Self, ident: ID, m: anytype) Error!void {
275
386
  if (!enabled) return;
387
+
276
388
  try self.writer.writeAll(&.{
277
389
  @intFromEnum(ArgType.Cant),
278
390
  @as(u8, @bitCast(ident)),
@@ -283,12 +395,14 @@ pub fn Log(comptime Writer: type) type {
283
395
 
284
396
  pub fn faint(self: Self, ident: ID, done: bool) Error!void {
285
397
  if (!enabled) return;
398
+
286
399
  try self.writer.writeAll(&.{ @intFromEnum(ArgType.Faint), @as(u8, @bitCast(ident)) });
287
400
  if (done) try self.writer.writeByte(@intFromEnum(ArgType.None));
288
401
  }
289
402
 
290
403
  pub fn turn(self: Self, num: u16) Error!void {
291
404
  if (!enabled) return;
405
+
292
406
  try self.writer.writeByte(@intFromEnum(ArgType.Turn));
293
407
  try self.writer.writeIntNative(u16, num);
294
408
  try self.writer.writeByte(@intFromEnum(ArgType.None));
@@ -296,6 +410,7 @@ pub fn Log(comptime Writer: type) type {
296
410
 
297
411
  pub fn win(self: Self, player: Player) Error!void {
298
412
  if (!enabled) return;
413
+
299
414
  try self.writer.writeAll(&.{
300
415
  @intFromEnum(ArgType.Win),
301
416
  @intFromEnum(player),
@@ -305,11 +420,13 @@ pub fn Log(comptime Writer: type) type {
305
420
 
306
421
  pub fn tie(self: Self) Error!void {
307
422
  if (!enabled) return;
423
+
308
424
  try self.writer.writeAll(&.{ @intFromEnum(ArgType.Tie), @intFromEnum(ArgType.None) });
309
425
  }
310
426
 
311
427
  pub fn damage(self: Self, ident: ID, pokemon: anytype, reason: Damage) Error!void {
312
428
  if (!enabled) return;
429
+
313
430
  assert(@intFromEnum(reason) <= @intFromEnum(Damage.LeechSeed));
314
431
  try self.writer.writeAll(&.{ @intFromEnum(ArgType.Damage), @as(u8, @bitCast(ident)) });
315
432
  try self.writer.writeIntNative(u16, pokemon.hp);
@@ -325,6 +442,7 @@ pub fn Log(comptime Writer: type) type {
325
442
  source: ID,
326
443
  ) Error!void {
327
444
  if (!enabled) return;
445
+
328
446
  assert(@intFromEnum(reason) == @intFromEnum(Damage.RecoilOf));
329
447
  try self.writer.writeAll(&.{ @intFromEnum(ArgType.Damage), @as(u8, @bitCast(ident)) });
330
448
  try self.writer.writeIntNative(u16, pokemon.hp);
@@ -336,17 +454,20 @@ pub fn Log(comptime Writer: type) type {
336
454
  });
337
455
  }
338
456
 
339
- pub fn heal(self: Self, ident: ID, pokemon: anytype, reason: Heal) Error!void {
457
+ // args: ident: ID, pokemon: anytype, reason: Heal
458
+ pub fn heal(self: Self, args: anytype) Error!void {
340
459
  if (!enabled) return;
341
- assert(reason != .Drain);
342
- try self.writer.writeAll(&.{ @intFromEnum(ArgType.Heal), @as(u8, @bitCast(ident)) });
343
- try self.writer.writeIntNative(u16, pokemon.hp);
344
- try self.writer.writeIntNative(u16, pokemon.stats.hp);
345
- try self.writer.writeAll(&.{ pokemon.status, @intFromEnum(reason) });
460
+
461
+ assert(args[2] != .Drain);
462
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.Heal), @as(u8, @bitCast(args[0])) });
463
+ try self.writer.writeIntNative(u16, args[1].hp);
464
+ try self.writer.writeIntNative(u16, args[1].stats.hp);
465
+ try self.writer.writeAll(&.{ args[1].status, @intFromEnum(args[2]) });
346
466
  }
347
467
 
348
468
  pub fn drain(self: Self, source: ID, pokemon: anytype, target: ID) Error!void {
349
469
  if (!enabled) return;
470
+
350
471
  try self.writer.writeAll(&.{ @intFromEnum(ArgType.Heal), @as(u8, @bitCast(source)) });
351
472
  try self.writer.writeIntNative(u16, pokemon.hp);
352
473
  try self.writer.writeIntNative(u16, pokemon.stats.hp);
@@ -359,6 +480,7 @@ pub fn Log(comptime Writer: type) type {
359
480
 
360
481
  pub fn status(self: Self, ident: ID, value: u8, reason: Status) Error!void {
361
482
  if (!enabled) return;
483
+
362
484
  assert(reason != .From);
363
485
  try self.writer.writeAll(&.{
364
486
  @intFromEnum(ArgType.Status),
@@ -370,6 +492,7 @@ pub fn Log(comptime Writer: type) type {
370
492
 
371
493
  pub fn statusFrom(self: Self, ident: ID, value: u8, m: anytype) Error!void {
372
494
  if (!enabled) return;
495
+
373
496
  try self.writer.writeAll(&.{
374
497
  @intFromEnum(ArgType.Status),
375
498
  @as(u8, @bitCast(ident)),
@@ -381,6 +504,7 @@ pub fn Log(comptime Writer: type) type {
381
504
 
382
505
  pub fn curestatus(self: Self, ident: ID, value: u8, reason: CureStatus) Error!void {
383
506
  if (!enabled) return;
507
+
384
508
  try self.writer.writeAll(&.{
385
509
  @intFromEnum(ArgType.CureStatus),
386
510
  @as(u8, @bitCast(ident)),
@@ -391,6 +515,7 @@ pub fn Log(comptime Writer: type) type {
391
515
 
392
516
  pub fn boost(self: Self, ident: ID, reason: Boost, num: i8) Error!void {
393
517
  if (!enabled) return;
518
+
394
519
  assert(num != 0 and (reason != .Rage or num > 0));
395
520
  try self.writer.writeAll(&.{
396
521
  @intFromEnum(ArgType.Boost),
@@ -402,11 +527,13 @@ pub fn Log(comptime Writer: type) type {
402
527
 
403
528
  pub fn clearallboost(self: Self) Error!void {
404
529
  if (!enabled) return;
530
+
405
531
  try self.writer.writeAll(&.{@intFromEnum(ArgType.ClearAllBoost)});
406
532
  }
407
533
 
408
534
  pub fn fail(self: Self, ident: ID, reason: Fail) Error!void {
409
535
  if (!enabled) return;
536
+
410
537
  try self.writer.writeAll(&.{
411
538
  @intFromEnum(ArgType.Fail),
412
539
  @as(u8, @bitCast(ident)),
@@ -416,11 +543,13 @@ pub fn Log(comptime Writer: type) type {
416
543
 
417
544
  pub fn miss(self: Self, source: ID) Error!void {
418
545
  if (!enabled) return;
546
+
419
547
  try self.writer.writeAll(&.{ @intFromEnum(ArgType.Miss), @as(u8, @bitCast(source)) });
420
548
  }
421
549
 
422
550
  pub fn hitcount(self: Self, ident: ID, num: u8) Error!void {
423
551
  if (!enabled) return;
552
+
424
553
  try self.writer.writeAll(&.{
425
554
  @intFromEnum(ArgType.HitCount),
426
555
  @as(u8, @bitCast(ident)),
@@ -430,6 +559,7 @@ pub fn Log(comptime Writer: type) type {
430
559
 
431
560
  pub fn prepare(self: Self, source: ID, m: anytype) Error!void {
432
561
  if (!enabled) return;
562
+
433
563
  try self.writer.writeAll(&.{
434
564
  @intFromEnum(ArgType.Prepare),
435
565
  @as(u8, @bitCast(source)),
@@ -439,6 +569,7 @@ pub fn Log(comptime Writer: type) type {
439
569
 
440
570
  pub fn mustrecharge(self: Self, ident: ID) Error!void {
441
571
  if (!enabled) return;
572
+
442
573
  try self.writer.writeAll(&.{
443
574
  @intFromEnum(ArgType.MustRecharge),
444
575
  @as(u8, @bitCast(ident)),
@@ -447,20 +578,47 @@ pub fn Log(comptime Writer: type) type {
447
578
 
448
579
  pub fn activate(self: Self, ident: ID, reason: Activate) Error!void {
449
580
  if (!enabled) return;
581
+
582
+ assert(reason != .SubstituteBlock); // TODO
583
+ try self.writer.writeAll(&.{
584
+ @intFromEnum(ArgType.Activate),
585
+ @as(u8, @bitCast(ident)),
586
+ @intFromEnum(reason),
587
+ });
588
+ }
589
+
590
+ pub fn activateMove(self: Self, ident: ID, reason: Activate, m: anytype) Error!void {
591
+ if (!enabled) return;
592
+
593
+ assert(reason == .SubstituteBlock); // TODO
450
594
  try self.writer.writeAll(&.{
451
595
  @intFromEnum(ArgType.Activate),
452
596
  @as(u8, @bitCast(ident)),
453
597
  @intFromEnum(reason),
598
+ @intFromEnum(m),
599
+ });
600
+ }
601
+
602
+ pub fn magnitude(self: Self, ident: ID, num: u8) Error!void {
603
+ if (!enabled) return;
604
+
605
+ try self.writer.writeAll(&.{
606
+ @intFromEnum(ArgType.Activate),
607
+ @as(u8, @bitCast(ident)),
608
+ @intFromEnum(Activate.Magnitude),
609
+ num,
454
610
  });
455
611
  }
456
612
 
457
613
  pub fn fieldactivate(self: Self) Error!void {
458
614
  if (!enabled) return;
615
+
459
616
  try self.writer.writeAll(&.{@intFromEnum(ArgType.FieldActivate)});
460
617
  }
461
618
 
462
619
  pub fn start(self: Self, ident: ID, reason: Start) Error!void {
463
620
  if (!enabled) return;
621
+
464
622
  assert(@intFromEnum(reason) < @intFromEnum(Start.TypeChange));
465
623
  try self.writer.writeAll(&.{
466
624
  @intFromEnum(ArgType.Start),
@@ -471,6 +629,7 @@ pub fn Log(comptime Writer: type) type {
471
629
 
472
630
  pub fn typechange(self: Self, source: ID, types: anytype, target: ID) Error!void {
473
631
  if (!enabled) return;
632
+
474
633
  try self.writer.writeAll(&.{
475
634
  @intFromEnum(ArgType.Start),
476
635
  @as(u8, @bitCast(source)),
@@ -482,6 +641,7 @@ pub fn Log(comptime Writer: type) type {
482
641
 
483
642
  pub fn startEffect(self: Self, ident: ID, reason: Start, m: anytype) Error!void {
484
643
  if (!enabled) return;
644
+
485
645
  assert(@intFromEnum(reason) > @intFromEnum(Start.TypeChange));
486
646
  try self.writer.writeAll(&.{
487
647
  @intFromEnum(ArgType.Start),
@@ -493,6 +653,7 @@ pub fn Log(comptime Writer: type) type {
493
653
 
494
654
  pub fn end(self: Self, ident: ID, reason: End) Error!void {
495
655
  if (!enabled) return;
656
+
496
657
  try self.writer.writeAll(&.{
497
658
  @intFromEnum(ArgType.End),
498
659
  @as(u8, @bitCast(ident)),
@@ -502,16 +663,19 @@ pub fn Log(comptime Writer: type) type {
502
663
 
503
664
  pub fn ohko(self: Self) Error!void {
504
665
  if (!enabled) return;
666
+
505
667
  try self.writer.writeAll(&.{@intFromEnum(ArgType.OHKO)});
506
668
  }
507
669
 
508
670
  pub fn crit(self: Self, ident: ID) Error!void {
509
671
  if (!enabled) return;
672
+
510
673
  try self.writer.writeAll(&.{ @intFromEnum(ArgType.Crit), @as(u8, @bitCast(ident)) });
511
674
  }
512
675
 
513
676
  pub fn supereffective(self: Self, ident: ID) Error!void {
514
677
  if (!enabled) return;
678
+
515
679
  try self.writer.writeAll(&.{
516
680
  @intFromEnum(ArgType.SuperEffective),
517
681
  @as(u8, @bitCast(ident)),
@@ -520,6 +684,7 @@ pub fn Log(comptime Writer: type) type {
520
684
 
521
685
  pub fn resisted(self: Self, ident: ID) Error!void {
522
686
  if (!enabled) return;
687
+
523
688
  try self.writer.writeAll(&.{
524
689
  @intFromEnum(ArgType.Resisted),
525
690
  @as(u8, @bitCast(ident)),
@@ -528,6 +693,7 @@ pub fn Log(comptime Writer: type) type {
528
693
 
529
694
  pub fn immune(self: Self, ident: ID, reason: Immune) Error!void {
530
695
  if (!enabled) return;
696
+
531
697
  try self.writer.writeAll(&.{
532
698
  @intFromEnum(ArgType.Immune),
533
699
  @as(u8, @bitCast(ident)),
@@ -537,6 +703,7 @@ pub fn Log(comptime Writer: type) type {
537
703
 
538
704
  pub fn transform(self: Self, source: ID, target: ID) Error!void {
539
705
  if (!enabled) return;
706
+
540
707
  try self.writer.writeAll(&.{
541
708
  @intFromEnum(ArgType.Transform),
542
709
  @as(u8, @bitCast(source)),
@@ -544,20 +711,152 @@ pub fn Log(comptime Writer: type) type {
544
711
  });
545
712
  }
546
713
 
714
+ pub fn drag(self: Self, ident: ID, pokemon: anytype) Error!void {
715
+ return switchDrag(self, .Drag, ident, pokemon);
716
+ }
717
+
718
+ pub fn item(self: Self, target: ID, i: anytype, source: ID) Error!void {
719
+ if (!enabled) return;
720
+
721
+ try self.writer.writeAll(&.{
722
+ @intFromEnum(ArgType.Item),
723
+ @as(u8, @bitCast(target)),
724
+ @intFromEnum(i),
725
+ @as(u8, @bitCast(source)),
726
+ });
727
+ }
728
+
729
+ pub fn enditem(self: Self, ident: ID, i: anytype, reason: EndItem) Error!void {
730
+ if (!enabled) return;
731
+
732
+ try self.writer.writeAll(&.{
733
+ @intFromEnum(ArgType.EndItem),
734
+ @as(u8, @bitCast(ident)),
735
+ @intFromEnum(i),
736
+ @intFromEnum(reason),
737
+ });
738
+ }
739
+
740
+ pub fn cureteam(self: Self, source: ID) Error!void {
741
+ if (!enabled) return;
742
+
743
+ try self.writer.writeAll(&.{
744
+ @intFromEnum(ArgType.CureTeam),
745
+ @as(u8, @bitCast(source)),
746
+ });
747
+ }
748
+
749
+ pub fn sethp(self: Self, ident: ID, pokemon: anytype, reason: SetHP) Error!void {
750
+ if (!enabled) return;
751
+
752
+ try self.writer.writeAll(&.{ @intFromEnum(ArgType.SetHP), @as(u8, @bitCast(ident)) });
753
+ try self.writer.writeIntNative(u16, pokemon.hp);
754
+ try self.writer.writeIntNative(u16, pokemon.stats.hp);
755
+ try self.writer.writeAll(&.{pokemon.status});
756
+ try self.writer.writeByte(@intFromEnum(reason));
757
+ }
758
+
759
+ pub fn setboost(self: Self, ident: ID, num: i8) Error!void {
760
+ if (!enabled) return;
761
+
762
+ try self.writer.writeAll(&.{
763
+ @intFromEnum(ArgType.SetBoost),
764
+ @as(u8, @bitCast(ident)),
765
+ @as(u8, @intCast(num + 6)),
766
+ });
767
+ }
768
+
769
+ pub fn copyboost(self: Self, source: ID, target: ID) Error!void {
770
+ if (!enabled) return;
771
+
772
+ try self.writer.writeAll(&.{
773
+ @intFromEnum(ArgType.CopyBoost),
774
+ @as(u8, @bitCast(source)),
775
+ @as(u8, @bitCast(target)),
776
+ });
777
+ }
778
+
779
+ pub fn sidestart(self: Self, player: Player, reason: Side) Error!void {
780
+ if (!enabled) return;
781
+
782
+ try self.writer.writeAll(&.{
783
+ @intFromEnum(ArgType.SideStart),
784
+ @intFromEnum(player),
785
+ @intFromEnum(reason),
786
+ });
787
+ }
788
+
789
+ pub fn sideend(self: Self, player: Player, reason: Side) Error!void {
790
+ if (!enabled) return;
791
+
792
+ assert(@intFromEnum(reason) != @intFromEnum(Side.Spikes));
793
+ try self.writer.writeAll(&.{
794
+ @intFromEnum(ArgType.SideEnd),
795
+ @intFromEnum(player),
796
+ @intFromEnum(reason),
797
+ });
798
+ }
799
+
800
+ pub fn spikesend(self: Self, source: ID) Error!void {
801
+ if (!enabled) return;
802
+
803
+ try self.writer.writeAll(&.{
804
+ @intFromEnum(ArgType.SideEnd),
805
+ @intFromEnum(source.player),
806
+ @intFromEnum(Side.Spikes),
807
+ @as(u8, @bitCast(source)),
808
+ });
809
+ }
810
+
811
+ pub fn singlemove(self: Self, ident: ID, m: anytype) Error!void {
812
+ return single(self, .SingleMove, ident, m);
813
+ }
814
+
815
+ pub fn singleturn(self: Self, ident: ID, m: anytype) Error!void {
816
+ return single(self, .SingleTurn, ident, m);
817
+ }
818
+
819
+ fn single(self: Self, arg: ArgType, ident: ID, m: anytype) Error!void {
820
+ if (!enabled) return;
821
+
822
+ try self.writer.writeAll(&.{
823
+ @intFromEnum(arg),
824
+ @as(u8, @bitCast(ident)),
825
+ @intFromEnum(m),
826
+ });
827
+ }
828
+
829
+ pub fn weather(self: Self, w: anytype, reason: Weather) Error!void {
830
+ if (!enabled) return;
831
+
832
+ assert(!(w == .None and reason == .Upkeep));
833
+ try self.writer.writeAll(&.{
834
+ @intFromEnum(ArgType.Weather),
835
+ @intFromEnum(w),
836
+ @intFromEnum(reason),
837
+ });
838
+ }
839
+
547
840
  pub fn laststill(self: Self) Error!void {
548
841
  if (!enabled) return;
842
+
549
843
  try self.writer.writeAll(&.{@intFromEnum(ArgType.LastStill)});
550
844
  }
551
845
 
552
846
  pub fn lastmiss(self: Self) Error!void {
553
847
  if (!enabled) return;
848
+
554
849
  try self.writer.writeAll(&.{@intFromEnum(ArgType.LastMiss)});
555
850
  }
556
851
  };
557
852
  }
558
853
 
854
+ /// `Log` type backed by the optimized `ByteStream.Writer`.
559
855
  pub const FixedLog = Log(ByteStream.Writer);
560
856
 
857
+ /// Stripped down version of `std.io.FixedBufferStream` optimized for efficiently writing the
858
+ /// individual protocol bytes. Note that the `ByteStream.Writer` is **not** a `std.io.Writer` and
859
+ /// should not be used for general purpose writing.
561
860
  pub const ByteStream = struct {
562
861
  buffer: []u8,
563
862
  pos: usize = 0,
@@ -598,10 +897,14 @@ pub const ByteStream = struct {
598
897
  }
599
898
  };
600
899
 
601
- pub const Kind = enum { Move, Species, Type, Status };
602
- pub const Formatter = fn (Kind, u8) []const u8;
900
+ pub const Kind = enum { Move, Species, Type, Status, Item, Weather };
603
901
 
604
- pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, color: bool) void {
902
+ pub fn format(
903
+ comptime gen: anytype,
904
+ a: []const u8,
905
+ b: ?[]const u8,
906
+ color: bool,
907
+ ) void {
605
908
  print("\n", .{});
606
909
 
607
910
  var i: usize = 0;
@@ -639,6 +942,18 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
639
942
  .Resisted => "|-resisted|",
640
943
  .Immune => "|-immune|",
641
944
  .Transform => "|-transform|",
945
+ .Drag => "|drag|",
946
+ .Item => "|-item|",
947
+ .EndItem => "|-enditem|",
948
+ .CureTeam => "|-cureteam|",
949
+ .SetHP => "|-sethp|",
950
+ .SetBoost => "|-setboost|",
951
+ .CopyBoost => "|-copyboost|",
952
+ .SideStart => "|-sidestart|",
953
+ .SideEnd => "|-sideend|",
954
+ .SingleMove => "|-singlemove|",
955
+ .SingleTurn => "|-singleturn|",
956
+ .Weather => "|-weather|",
642
957
  else => unreachable,
643
958
  };
644
959
  printc("{s}", .{name}, a, b, &i, 1, color);
@@ -654,17 +969,27 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
654
969
  .Move => {
655
970
  const source = ID.from(@intCast(a[i]));
656
971
  printc(" {s}({d})", .{ @tagName(source.player), source.id }, a, b, &i, 1, color);
657
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
972
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
658
973
  const target = ID.from(@intCast(a[i]));
659
974
  printc(" {s}({d})", .{ @tagName(target.player), target.id }, a, b, &i, 1, color);
660
975
  const reason: Move = @enumFromInt(a[i]);
661
976
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
662
- if (reason == .From) printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
977
+ if (reason == .From) {
978
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
979
+ }
663
980
  },
664
- .Switch => {
981
+ .Switch, .Drag => {
665
982
  const id = ID.from(@intCast(a[i]));
666
983
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
667
- printc(" {s}", .{formatter(.Species, a[i])}, a, b, &i, 1, color);
984
+ printc(" {s}", .{formatter(gen, .Species, a[i])}, a, b, &i, 1, color);
985
+ if (@hasDecl(gen, "Gender")) {
986
+ if (a[i] == N(gen.Gender.Unknown)) {
987
+ i += 1;
988
+ } else {
989
+ var gender = if (a[i] == N(gen.Gender.Male)) "M" else "F";
990
+ printc(", {s}", .{gender}, a, b, &i, 1, color);
991
+ }
992
+ }
668
993
  printc(" L{d}", .{a[i]}, a, b, &i, 1, color);
669
994
  switch (endian) {
670
995
  .Big => {
@@ -680,7 +1005,7 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
680
1005
  printc("/{d}", .{hp}, a, b, &i, 2, color);
681
1006
  },
682
1007
  }
683
- printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
1008
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
684
1009
  },
685
1010
  .Cant => {
686
1011
  const id = ID.from(@intCast(a[i]));
@@ -688,7 +1013,7 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
688
1013
  const reason: Cant = @enumFromInt(a[i]);
689
1014
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
690
1015
  if (reason == .Disable) {
691
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
1016
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
692
1017
  }
693
1018
  },
694
1019
  .Faint,
@@ -697,6 +1022,7 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
697
1022
  .Crit,
698
1023
  .SuperEffective,
699
1024
  .Resisted,
1025
+ .CureTeam,
700
1026
  => {
701
1027
  const id = ID.from(@intCast(a[i]));
702
1028
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
@@ -728,7 +1054,7 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
728
1054
  printc("/{d}", .{hp}, a, b, &i, 2, color);
729
1055
  },
730
1056
  }
731
- printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
1057
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
732
1058
  if (arg == .Damage) {
733
1059
  const reason: Damage = @enumFromInt(a[i]);
734
1060
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
@@ -748,15 +1074,17 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
748
1074
  .Status => {
749
1075
  const id = ID.from(@intCast(a[i]));
750
1076
  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);
1077
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
752
1078
  const reason: Status = @enumFromInt(a[i]);
753
1079
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
754
- if (reason == .From) printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
1080
+ if (reason == .From) {
1081
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
1082
+ }
755
1083
  },
756
1084
  .CureStatus => {
757
1085
  const id = ID.from(@intCast(a[i]));
758
1086
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
759
- printc(" {s}", .{formatter(.Status, a[i])}, a, b, &i, 1, color);
1087
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
760
1088
  const reason: CureStatus = @enumFromInt(a[i]);
761
1089
  printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
762
1090
  },
@@ -771,15 +1099,15 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
771
1099
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
772
1100
  printc(" {s}", .{@tagName(@as(Fail, @enumFromInt(a[i])))}, a, b, &i, 1, color);
773
1101
  },
774
- .HitCount => {
1102
+ .HitCount, .SetBoost => {
775
1103
  const id = ID.from(@intCast(a[i]));
776
1104
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
777
1105
  printc(" {d}", .{a[i]}, a, b, &i, 1, color);
778
1106
  },
779
- .Prepare => {
1107
+ .Prepare, .SingleMove, .SingleTurn => {
780
1108
  const id = ID.from(@intCast(a[i]));
781
1109
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
782
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
1110
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
783
1111
  },
784
1112
  .Activate => {
785
1113
  const id = ID.from(@intCast(a[i]));
@@ -794,12 +1122,12 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
794
1122
  if (@as(Start, @enumFromInt(reason)) == .TypeChange) {
795
1123
  const types = @as(gen1.Types, @bitCast(a[i]));
796
1124
  const args = .{
797
- formatter(.Type, @intFromEnum(types.type1)),
798
- formatter(.Type, @intFromEnum(types.type2)),
1125
+ formatter(gen, .Type, @intFromEnum(types.type1)),
1126
+ formatter(gen, .Type, @intFromEnum(types.type2)),
799
1127
  };
800
1128
  printc(" {s}/{s}", args, a, b, &i, 1, color);
801
1129
  } else if (reason >= @intFromEnum(Start.Disable)) {
802
- printc(" {s}", .{formatter(.Move, a[i])}, a, b, &i, 1, color);
1130
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
803
1131
  }
804
1132
  },
805
1133
  .End => {
@@ -812,12 +1140,64 @@ pub fn format(comptime formatter: Formatter, a: []const u8, b: ?[]const u8, colo
812
1140
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
813
1141
  printc(" {s}", .{@tagName(@as(Immune, @enumFromInt(a[i])))}, a, b, &i, 1, color);
814
1142
  },
815
- .Transform => {
1143
+ .Transform, .CopyBoost => {
816
1144
  const source = ID.from(@intCast(a[i]));
817
1145
  printc(" {s}({d})", .{ @tagName(source.player), source.id }, a, b, &i, 1, color);
818
1146
  const target = ID.from(@intCast(a[i]));
819
1147
  printc(" {s}({d})", .{ @tagName(target.player), target.id }, a, b, &i, 1, color);
820
1148
  },
1149
+ .Item, .EndItem => {
1150
+ var id = ID.from(@intCast(a[i]));
1151
+ printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
1152
+ printc(" {s}", .{formatter(gen, .Item, a[i])}, a, b, &i, 1, color);
1153
+ if (arg == .Item) {
1154
+ id = ID.from(@intCast(a[i]));
1155
+ printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
1156
+ } else {
1157
+ const reason: EndItem = @enumFromInt(a[i]);
1158
+ printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
1159
+ }
1160
+ },
1161
+ .SetHP => {
1162
+ const id = ID.from(@intCast(a[i]));
1163
+ printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
1164
+ switch (endian) {
1165
+ .Big => {
1166
+ var hp = @as(u16, a[i]) << 8 | @as(u16, a[i + 1]);
1167
+ printc(" {d}", .{hp}, a, b, &i, 2, color);
1168
+ hp = @as(u16, a[i]) << 8 | @as(u16, a[i + 1]);
1169
+ printc("/{d}", .{hp}, a, b, &i, 2, color);
1170
+ },
1171
+ .Little => {
1172
+ var hp = @as(u16, a[i + 1]) << 8 | @as(u16, a[i]);
1173
+ printc(" {d}", .{hp}, a, b, &i, 2, color);
1174
+ hp = @as(u16, a[i + 1]) << 8 | @as(u16, a[i]);
1175
+ printc("/{d}", .{hp}, a, b, &i, 2, color);
1176
+ },
1177
+ }
1178
+ printc(" {s}", .{formatter(gen, .Status, a[i])}, a, b, &i, 1, color);
1179
+ printc(" {s}", .{@tagName(@as(SetHP, @enumFromInt(a[i])))}, a, b, &i, 1, color);
1180
+ },
1181
+ .SideStart => {
1182
+ const id = ID.from(@intCast(a[i]));
1183
+ printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
1184
+ printc(" {s}", .{@tagName(@as(Side, @enumFromInt(a[i])))}, a, b, &i, 1, color);
1185
+ },
1186
+ .SideEnd => {
1187
+ var id = ID.from(@intCast(a[i]));
1188
+ printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
1189
+ const reason: Side = @enumFromInt(a[i]);
1190
+ printc(" {s}", .{@tagName(reason)}, a, b, &i, 1, color);
1191
+ if (reason == .Spikes) {
1192
+ printc(" {s}", .{formatter(gen, .Move, a[i])}, a, b, &i, 1, color);
1193
+ id = ID.from(@intCast(a[i]));
1194
+ printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
1195
+ }
1196
+ },
1197
+ .Weather => {
1198
+ printc(" {s}", .{formatter(gen, .Weather, a[i])}, a, b, &i, 1, color);
1199
+ printc(" {s}", .{@tagName(@as(Weather, @enumFromInt(a[i])))}, a, b, &i, 1, color);
1200
+ },
821
1201
  else => unreachable,
822
1202
  }
823
1203
  print("\n", .{});
@@ -849,12 +1229,13 @@ fn printc(
849
1229
  }
850
1230
 
851
1231
  pub fn expectLog(
852
- comptime formatter: Formatter,
1232
+ comptime gen: anytype,
853
1233
  expected: []const u8,
854
1234
  actual: []const u8,
855
1235
  offset: usize,
856
1236
  ) !void {
857
1237
  if (!enabled) return;
1238
+
858
1239
  const color = color: {
859
1240
  if (std.process.hasEnvVarConstant("ZIG_DEBUG_COLOR")) {
860
1241
  break :color true;
@@ -867,8 +1248,8 @@ pub fn expectLog(
867
1248
 
868
1249
  expectEqualBytes(expected, actual, offset) catch |err| switch (err) {
869
1250
  error.TestExpectedEqual => {
870
- format(formatter, expected, null, color);
871
- format(formatter, actual, expected, color);
1251
+ format(gen, expected, null, color);
1252
+ format(gen, actual, expected, color);
872
1253
  return err;
873
1254
  },
874
1255
  else => return err,
@@ -908,49 +1289,70 @@ const gen1 = struct {
908
1289
  pub const helpers = @import("../gen1/helpers.zig");
909
1290
  };
910
1291
 
1292
+ const gen2 = struct {
1293
+ pub usingnamespace @import("../gen2/data.zig");
1294
+ pub const helpers = @import("../gen2/helpers.zig");
1295
+ };
1296
+
911
1297
  var buf: [gen1.LOGS_SIZE]u8 = undefined;
912
- var stream = ByteStream{ .buffer = &buf };
1298
+ var stream: ByteStream = .{ .buffer = &buf };
913
1299
  var log: FixedLog = .{ .writer = stream.writer() };
914
1300
 
915
- const M = gen1.Move;
916
- const S = gen1.Species;
1301
+ const M1 = gen1.Move;
1302
+ const S1 = gen1.Species;
1303
+
1304
+ const M2 = gen2.Move;
1305
+ const S2 = gen2.Species;
1306
+ const I2 = gen2.Item;
1307
+ const W2 = gen2.Weather;
917
1308
 
918
1309
  fn expectLog1(expected: []const u8, actual: []const u8) !void {
919
- return expectLog(gen1Formatter, expected, actual, 0);
1310
+ return expectLog(gen1, expected, actual, 0);
920
1311
  }
921
1312
 
922
- fn gen1Formatter(kind: Kind, byte: u8) []const u8 {
1313
+ fn expectLog2(expected: []const u8, actual: []const u8) !void {
1314
+ return expectLog(gen2, expected, actual, 0);
1315
+ }
1316
+
1317
+ fn formatter(comptime gen: anytype, kind: Kind, byte: u8) []const u8 {
923
1318
  return switch (kind) {
924
- .Move => @tagName(@as(M, @enumFromInt(byte))),
925
- .Species => @tagName(@as(S, @enumFromInt(byte))),
926
- .Type => @tagName(@as(gen1.Type, @enumFromInt(byte))),
927
- .Status => gen1.Status.name(byte),
1319
+ .Move => @tagName(@as(gen.Move, @enumFromInt(byte))),
1320
+ .Species => @tagName(@as(gen.Species, @enumFromInt(byte))),
1321
+ .Type => @tagName(@as(gen.Type, @enumFromInt(byte))),
1322
+ .Status => gen.Status.name(byte),
1323
+ .Item => if (@hasDecl(gen, "Item"))
1324
+ @tagName(@as(gen.Item, @enumFromInt(byte)))
1325
+ else
1326
+ unreachable,
1327
+ .Weather => if (@hasDecl(gen, "Weather"))
1328
+ @tagName(@as(gen.Weather, @enumFromInt(byte)))
1329
+ else
1330
+ unreachable,
928
1331
  };
929
1332
  }
930
1333
 
931
1334
  test "|move|" {
932
- try log.move(p2.ident(4), M.Thunderbolt, p1.ident(5), null);
1335
+ try log.move(p2.ident(4), M1.Thunderbolt, p1.ident(5));
933
1336
  try expectLog1(
934
- &.{ N(ArgType.Move), 0b1100, N(M.Thunderbolt), 0b0101, N(Move.None) },
1337
+ &.{ N(ArgType.Move), 0b1100, N(M1.Thunderbolt), 0b0101, N(Move.None) },
935
1338
  buf[0..5],
936
1339
  );
937
1340
  stream.reset();
938
1341
 
939
- try log.move(p2.ident(4), M.Wrap, p1.ident(5), M.Wrap);
940
- const wrap = N(M.Wrap);
1342
+ try log.moveFrom(p2.ident(4), M1.Pound, p1.ident(5), M1.Metronome);
941
1343
  try expectLog1(
942
- &.{ N(ArgType.Move), 0b1100, wrap, 0b0101, N(Move.From), wrap },
1344
+ &.{ N(ArgType.Move), 0b1100, N(M1.Pound), 0b0101, N(Move.From), N(M1.Metronome) },
943
1345
  buf[0..6],
944
1346
  );
945
1347
  stream.reset();
946
1348
 
947
- try log.move(p2.ident(4), M.SkullBash, .{}, null);
1349
+ try log.move(p2.ident(4), M1.SkullBash, .{});
948
1350
  try log.laststill();
949
1351
  try expectLog1(
950
1352
  &.{
951
1353
  N(ArgType.Move),
952
1354
  0b1100,
953
- N(M.SkullBash),
1355
+ N(M1.SkullBash),
954
1356
  0,
955
1357
  N(Move.None),
956
1358
  N(ArgType.LastStill),
@@ -959,13 +1361,13 @@ test "|move|" {
959
1361
  );
960
1362
  stream.reset();
961
1363
 
962
- try log.move(p2.ident(4), M.Tackle, p1.ident(5), null);
1364
+ try log.move(p2.ident(4), M1.Tackle, p1.ident(5));
963
1365
  try log.lastmiss();
964
1366
  try expectLog1(
965
1367
  &.{
966
1368
  N(ArgType.Move),
967
1369
  0b1100,
968
- N(M.Tackle),
1370
+ N(M1.Tackle),
969
1371
  0b0101,
970
1372
  N(Move.None),
971
1373
  N(ArgType.LastMiss),
@@ -984,8 +1386,8 @@ test "|switch|" {
984
1386
  try log.switched(p2.ident(3), &snorlax);
985
1387
  const par = 0b1000000;
986
1388
  var expected: []const u8 = switch (endian) {
987
- .Big => &.{ N(ArgType.Switch), 0b1011, N(S.Snorlax), 91, 0, 200, 1, 144, par },
988
- .Little => &.{ N(ArgType.Switch), 0b1011, N(S.Snorlax), 91, 200, 0, 144, 1, par },
1389
+ .Big => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 91, 0, 200, 1, 144, par },
1390
+ .Little => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 91, 200, 0, 144, 1, par },
989
1391
  };
990
1392
  try expectLog1(expected, buf[0..9]);
991
1393
  stream.reset();
@@ -995,8 +1397,8 @@ test "|switch|" {
995
1397
  snorlax.status = 0;
996
1398
  try log.switched(p2.ident(3), &snorlax);
997
1399
  expected = switch (endian) {
998
- .Big => &.{ N(ArgType.Switch), 0b1011, N(S.Snorlax), 100, 0, 0, 1, 144, 0 },
999
- .Little => &.{ N(ArgType.Switch), 0b1011, N(S.Snorlax), 100, 0, 0, 144, 1, 0 },
1400
+ .Big => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 100, 0, 0, 1, 144, 0 },
1401
+ .Little => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 100, 0, 0, 144, 1, 0 },
1000
1402
  };
1001
1403
  try expectLog1(expected, buf[0..9]);
1002
1404
  stream.reset();
@@ -1004,11 +1406,30 @@ test "|switch|" {
1004
1406
  snorlax.hp = 400;
1005
1407
  try log.switched(p2.ident(3), &snorlax);
1006
1408
  expected = switch (endian) {
1007
- .Big => &.{ N(ArgType.Switch), 0b1011, N(S.Snorlax), 100, 1, 144, 1, 144, 0 },
1008
- .Little => &.{ N(ArgType.Switch), 0b1011, N(S.Snorlax), 100, 144, 1, 144, 1, 0 },
1409
+ .Big => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 100, 1, 144, 1, 144, 0 },
1410
+ .Little => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 100, 144, 1, 144, 1, 0 },
1009
1411
  };
1010
1412
  try expectLog1(expected, buf[0..9]);
1011
1413
  stream.reset();
1414
+
1415
+ var blissey = gen2.helpers.Pokemon.init(.{ .species = .Blissey, .moves = &.{.Splash} });
1416
+ blissey.level = 91;
1417
+ blissey.hp = 200;
1418
+ blissey.stats.hp = 400;
1419
+ blissey.status = gen2.Status.init(.PAR);
1420
+ try log.switched(p2.ident(3), &blissey);
1421
+ expected = &(.{
1422
+ N(ArgType.Switch),
1423
+ 0b1011,
1424
+ N(S2.Blissey),
1425
+ N(gen2.Gender.Female),
1426
+ 91,
1427
+ } ++ switch (endian) {
1428
+ .Big => .{ 0, 200, 1, 144, par },
1429
+ .Little => .{ 200, 0, 144, 1, par },
1430
+ });
1431
+ try expectLog2(expected, buf[0..10]);
1432
+ stream.reset();
1012
1433
  }
1013
1434
 
1014
1435
  test "|cant|" {
@@ -1016,8 +1437,8 @@ test "|cant|" {
1016
1437
  try expectLog1(&.{ N(ArgType.Cant), 0b1110, N(Cant.Bound) }, buf[0..3]);
1017
1438
  stream.reset();
1018
1439
 
1019
- try log.disabled(p1.ident(2), M.Earthquake);
1020
- try expectLog1(&.{ N(ArgType.Cant), 2, N(Cant.Disable), N(M.Earthquake) }, buf[0..4]);
1440
+ try log.disabled(p1.ident(2), M1.Earthquake);
1441
+ try expectLog1(&.{ N(ArgType.Cant), 2, N(Cant.Disable), N(M1.Earthquake) }, buf[0..4]);
1021
1442
  stream.reset();
1022
1443
  }
1023
1444
 
@@ -1090,7 +1511,7 @@ test "|-heal|" {
1090
1511
  var chansey = gen1.helpers.Pokemon.init(.{ .species = .Chansey, .moves = &.{.Splash} });
1091
1512
  chansey.hp = 612;
1092
1513
  chansey.status = gen1.Status.slp(1);
1093
- try log.heal(p2.ident(2), &chansey, .None);
1514
+ try log.heal(.{ p2.ident(2), &chansey, Heal.None });
1094
1515
  var expected: []const u8 = switch (endian) {
1095
1516
  .Big => &.{ N(ArgType.Heal), 0b1010, 2, 100, 2, 191, 1, N(Heal.None) },
1096
1517
  .Little => &.{ N(ArgType.Heal), 0b1010, 100, 2, 191, 2, 1, N(Heal.None) },
@@ -1101,7 +1522,7 @@ test "|-heal|" {
1101
1522
  chansey.hp = 100;
1102
1523
  chansey.stats.hp = 256;
1103
1524
  chansey.status = 0;
1104
- try log.heal(p2.ident(2), &chansey, .Silent);
1525
+ try log.heal(.{ p2.ident(2), &chansey, Heal.Silent });
1105
1526
  expected = switch (endian) {
1106
1527
  .Big => &.{ N(ArgType.Heal), 0b1010, 0, 100, 1, 0, 0, N(Heal.Silent) },
1107
1528
  .Little => &.{ N(ArgType.Heal), 0b1010, 100, 0, 0, 1, 0, N(Heal.Silent) },
@@ -1127,9 +1548,9 @@ test "|-status|" {
1127
1548
  try expectLog1(&.{ N(ArgType.Status), 0b1010, 0b1000, N(Status.Silent) }, buf[0..4]);
1128
1549
  stream.reset();
1129
1550
 
1130
- try log.statusFrom(p1.ident(1), gen1.Status.init(.PAR), M.BodySlam);
1551
+ try log.statusFrom(p1.ident(1), gen1.Status.init(.PAR), M1.BodySlam);
1131
1552
  try expectLog1(
1132
- &.{ N(ArgType.Status), 0b0001, 0b1000000, N(Status.From), N(M.BodySlam) },
1553
+ &.{ N(ArgType.Status), 0b0001, 0b1000000, N(Status.From), N(M1.BodySlam) },
1133
1554
  buf[0..5],
1134
1555
  );
1135
1556
  stream.reset();
@@ -1200,8 +1621,8 @@ test "|-hitcount|" {
1200
1621
  }
1201
1622
 
1202
1623
  test "|-prepare|" {
1203
- try log.prepare(p2.ident(2), M.Dig);
1204
- try expectLog1(&.{ N(ArgType.Prepare), 0b1010, N(M.Dig) }, buf[0..3]);
1624
+ try log.prepare(p2.ident(2), M1.Dig);
1625
+ try expectLog1(&.{ N(ArgType.Prepare), 0b1010, N(M1.Dig) }, buf[0..3]);
1205
1626
  stream.reset();
1206
1627
  }
1207
1628
 
@@ -1227,6 +1648,12 @@ test "|-activate|" {
1227
1648
  try log.activate(p1.ident(2), .Splash);
1228
1649
  try expectLog1(&.{ N(ArgType.Activate), 0b0010, N(Activate.Splash) }, buf[0..3]);
1229
1650
  stream.reset();
1651
+
1652
+ // TODO activateMove
1653
+
1654
+ try log.magnitude(p1.ident(3), 5);
1655
+ try expectLog1(&.{ N(ArgType.Activate), 0b0011, N(Activate.Magnitude), 5 }, buf[0..4]);
1656
+ stream.reset();
1230
1657
  }
1231
1658
 
1232
1659
  test "|-fieldactivate|" {
@@ -1258,12 +1685,12 @@ test "|-start|" {
1258
1685
  );
1259
1686
  stream.reset();
1260
1687
 
1261
- try log.startEffect(p1.ident(2), .Disable, M.Surf);
1262
- try expectLog1(&.{ N(ArgType.Start), 0b0010, N(Start.Disable), N(M.Surf) }, buf[0..4]);
1688
+ try log.startEffect(p1.ident(2), .Disable, M1.Surf);
1689
+ try expectLog1(&.{ N(ArgType.Start), 0b0010, N(Start.Disable), N(M1.Surf) }, buf[0..4]);
1263
1690
  stream.reset();
1264
1691
 
1265
- try log.startEffect(p1.ident(2), .Mimic, M.Surf);
1266
- try expectLog1(&.{ N(ArgType.Start), 0b0010, N(Start.Mimic), N(M.Surf) }, buf[0..4]);
1692
+ try log.startEffect(p1.ident(2), .Mimic, M1.Surf);
1693
+ try expectLog1(&.{ N(ArgType.Start), 0b0010, N(Start.Mimic), N(M1.Surf) }, buf[0..4]);
1267
1694
  stream.reset();
1268
1695
  }
1269
1696
 
@@ -1282,21 +1709,25 @@ test "|-ohko|" {
1282
1709
  try expectLog1(&.{N(ArgType.OHKO)}, buf[0..1]);
1283
1710
  stream.reset();
1284
1711
  }
1712
+
1285
1713
  test "|-crit|" {
1286
1714
  try log.crit(p2.ident(5));
1287
1715
  try expectLog1(&.{ N(ArgType.Crit), 0b1101 }, buf[0..2]);
1288
1716
  stream.reset();
1289
1717
  }
1718
+
1290
1719
  test "|-supereffective|" {
1291
1720
  try log.supereffective(p1.ident(1));
1292
1721
  try expectLog1(&.{ N(ArgType.SuperEffective), 0b0001 }, buf[0..2]);
1293
1722
  stream.reset();
1294
1723
  }
1724
+
1295
1725
  test "|-resisted|" {
1296
1726
  try log.resisted(p2.ident(2));
1297
1727
  try expectLog1(&.{ N(ArgType.Resisted), 0b1010 }, buf[0..2]);
1298
1728
  stream.reset();
1299
1729
  }
1730
+
1300
1731
  test "|-immune|" {
1301
1732
  try log.immune(p1.ident(3), .None);
1302
1733
  try expectLog1(&.{ N(ArgType.Immune), 0b0011, N(Immune.None) }, buf[0..3]);
@@ -1306,8 +1737,124 @@ test "|-immune|" {
1306
1737
  try expectLog1(&.{ N(ArgType.Immune), 0b1010, N(Immune.OHKO) }, buf[0..3]);
1307
1738
  stream.reset();
1308
1739
  }
1740
+
1309
1741
  test "|-transform|" {
1310
1742
  try log.transform(p2.ident(4), p1.ident(5));
1311
1743
  try expectLog1(&.{ N(ArgType.Transform), 0b1100, 0b0101 }, buf[0..3]);
1312
1744
  stream.reset();
1313
1745
  }
1746
+
1747
+ test "|drag|" {
1748
+ var blissey = gen2.helpers.Pokemon.init(.{ .species = .Blissey, .moves = &.{.Splash} });
1749
+ blissey.level = 91;
1750
+ blissey.hp = 200;
1751
+ blissey.stats.hp = 400;
1752
+ blissey.status = gen2.Status.init(.PAR);
1753
+ try log.drag(p2.ident(3), &blissey);
1754
+ const par = 0b1000000;
1755
+ const expected = &(.{
1756
+ N(ArgType.Drag),
1757
+ 0b1011,
1758
+ N(S2.Blissey),
1759
+ N(gen2.Gender.Female),
1760
+ 91,
1761
+ } ++ switch (endian) {
1762
+ .Big => .{ 0, 200, 1, 144, par },
1763
+ .Little => .{ 200, 0, 144, 1, par },
1764
+ });
1765
+ try expectLog2(expected, buf[0..10]);
1766
+ stream.reset();
1767
+ }
1768
+
1769
+ test "|-item|" {
1770
+ try log.item(p2.ident(2), I2.GoldBerry, p1.ident(4));
1771
+ try expectLog2(&.{ N(ArgType.Item), 0b1010, N(I2.GoldBerry), 0b0100 }, buf[0..4]);
1772
+ stream.reset();
1773
+ }
1774
+
1775
+ test "|-enditem|" {
1776
+ try log.enditem(p1.ident(1), I2.BerserkGene, .None);
1777
+ try expectLog2(&.{ N(ArgType.EndItem), 0b0001, N(I2.BerserkGene), N(EndItem.None) }, buf[0..4]);
1778
+ stream.reset();
1779
+
1780
+ try log.enditem(p2.ident(4), I2.PRZCureBerry, .Eat);
1781
+ try expectLog2(&.{ N(ArgType.EndItem), 0b1100, N(I2.PRZCureBerry), N(EndItem.Eat) }, buf[0..4]);
1782
+ stream.reset();
1783
+ }
1784
+
1785
+ test "|-cureteam|" {
1786
+ try log.cureteam(p2.ident(5));
1787
+ try expectLog2(&.{ N(ArgType.CureTeam), 0b1101 }, buf[0..2]);
1788
+ stream.reset();
1789
+ }
1790
+
1791
+ test "|-sethp|" {
1792
+ var blissey = gen2.helpers.Pokemon.init(.{ .species = .Blissey, .moves = &.{.PainSplit} });
1793
+ blissey.hp = 612;
1794
+ try log.sethp(p2.ident(2), &blissey, .Silent);
1795
+ var expected: []const u8 = switch (endian) {
1796
+ .Big => &.{ N(ArgType.SetHP), 0b1010, 2, 100, 2, 201, 0, N(SetHP.Silent) },
1797
+ .Little => &.{ N(ArgType.SetHP), 0b1010, 100, 2, 201, 2, 0, N(SetHP.Silent) },
1798
+ };
1799
+ try expectLog2(expected, buf[0..8]);
1800
+ stream.reset();
1801
+ }
1802
+
1803
+ test "|-setboost|" {
1804
+ try log.setboost(p2.ident(6), 6);
1805
+ try expectLog2(&.{ N(ArgType.SetBoost), 0b1110, 12 }, buf[0..3]);
1806
+ stream.reset();
1807
+
1808
+ try log.setboost(p2.ident(3), 2);
1809
+ try expectLog2(&.{ N(ArgType.SetBoost), 0b1011, 8 }, buf[0..3]);
1810
+ stream.reset();
1811
+ }
1812
+
1813
+ test "|-copyboost|" {
1814
+ try log.copyboost(p2.ident(3), p1.ident(6));
1815
+ try expectLog2(&.{ N(ArgType.CopyBoost), 0b1011, 0b0110 }, buf[0..3]);
1816
+ stream.reset();
1817
+ }
1818
+
1819
+ test "|-sidestart|" {
1820
+ try log.sidestart(.P2, .Reflect);
1821
+ try expectLog2(&.{ N(ArgType.SideStart), 1, N(Side.Reflect) }, buf[0..3]);
1822
+ stream.reset();
1823
+ }
1824
+
1825
+ test "|-sideend|" {
1826
+ try log.sideend(.P2, .LightScreen);
1827
+ try expectLog2(&.{ N(ArgType.SideEnd), 1, N(Side.LightScreen) }, buf[0..3]);
1828
+ stream.reset();
1829
+
1830
+ try log.spikesend(p1.ident(3));
1831
+ try expectLog2(&.{
1832
+ N(ArgType.SideEnd),
1833
+ 0,
1834
+ N(Side.Spikes),
1835
+ 0b0011,
1836
+ }, buf[0..4]);
1837
+ stream.reset();
1838
+ }
1839
+
1840
+ test "|-singlemove|" {
1841
+ try log.singlemove(p2.ident(2), M2.DestinyBond);
1842
+ try expectLog2(&.{ N(ArgType.SingleMove), 0b1010, N(M2.DestinyBond) }, buf[0..3]);
1843
+ stream.reset();
1844
+ }
1845
+
1846
+ test "|-singleturn|" {
1847
+ try log.singleturn(p1.ident(3), M2.Protect);
1848
+ try expectLog2(&.{ N(ArgType.SingleTurn), 0b0011, N(M2.Protect) }, buf[0..3]);
1849
+ stream.reset();
1850
+ }
1851
+
1852
+ test "|-weather|" {
1853
+ try log.weather(W2.Rain, .Upkeep);
1854
+ try expectLog2(&.{ N(ArgType.Weather), N(W2.Rain), N(Weather.Upkeep) }, buf[0..3]);
1855
+ stream.reset();
1856
+
1857
+ try log.weather(W2.None, .None);
1858
+ try expectLog2(&.{ N(ArgType.Weather), N(W2.None), N(Weather.None) }, buf[0..3]);
1859
+ stream.reset();
1860
+ }