@pkmn/engine 0.1.0-dev.00879b8b

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