@pkmn/engine 0.1.0-dev.028ab165

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