@pkmn/engine 0.1.0-dev.14ca6108 → 0.1.0-dev.15f64810

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 (48) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +44 -19
  3. package/build/pkg/addon.js +6 -7
  4. package/build/pkg/addon.js.map +1 -1
  5. package/build/pkg/data.d.ts +4 -4
  6. package/build/pkg/data.js.map +1 -1
  7. package/build/pkg/gen1.js.map +1 -1
  8. package/build/pkg/index.d.ts +2 -1
  9. package/build/pkg/index.js.map +1 -1
  10. package/build/pkg/protocol.js +5 -5
  11. package/build/pkg/protocol.js.map +1 -1
  12. package/build/test/display.js +2 -2
  13. package/build/test/display.js.map +1 -1
  14. package/build/tools/debug.js +5 -4
  15. package/build/tools/debug.js.map +1 -1
  16. package/build.zig +100 -102
  17. package/package.json +34 -23
  18. package/src/bin/install-pkmn-engine +26 -18
  19. package/src/lib/common/data.zig +8 -8
  20. package/src/lib/common/debug.zig +2 -2
  21. package/src/lib/common/optional.zig +1 -1
  22. package/src/lib/common/options.zig +2 -0
  23. package/src/lib/common/protocol.zig +397 -384
  24. package/src/lib/common/rational.zig +11 -8
  25. package/src/lib/common/rng.zig +20 -11
  26. package/src/lib/gen1/calc.zig +132 -29
  27. package/src/lib/gen1/chance.zig +91 -92
  28. package/src/lib/gen1/data/moves.zig +10 -10
  29. package/src/lib/gen1/data/species.zig +3 -3
  30. package/src/lib/gen1/data/types.zig +5 -5
  31. package/src/lib/gen1/data.zig +35 -35
  32. package/src/lib/gen1/helpers.zig +136 -49
  33. package/src/lib/gen1/mechanics.zig +327 -307
  34. package/src/lib/gen2/calc.zig +143 -0
  35. package/src/lib/gen2/chance.zig +1315 -0
  36. package/src/lib/gen2/data/items.zig +3 -3
  37. package/src/lib/gen2/data/moves.zig +5 -5
  38. package/src/lib/gen2/data/species.zig +1 -1
  39. package/src/lib/gen2/data/types.zig +10 -10
  40. package/src/lib/gen2/data.zig +27 -37
  41. package/src/lib/gen2/generated.zig +806 -0
  42. package/src/lib/gen2/helpers.zig +37 -7
  43. package/src/lib/gen2/mechanics.zig +1633 -484
  44. package/src/lib/{binding/node.zig → node.zig} +9 -12
  45. package/src/lib/pkmn.zig +27 -12
  46. package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
  47. package/src/lib/gen1/README.md +0 -894
  48. package/src/lib/gen2/README.md +0 -330
@@ -22,11 +22,11 @@ const expect = std.testing.expect;
22
22
 
23
23
  /// The minimum size in bytes required to hold all Generation I choice options.
24
24
  pub const MAX_CHOICES: usize = 9; // move 1..4, switch 2..6
25
- /// The optimal size in bytes required to hold all Generation I choice options.
26
- /// At least as large as MAX_CHOICES.
25
+ /// The maximum number of bytes possibly logged by a single Generation I update.
27
26
  pub const MAX_LOGS: usize = 180;
28
27
 
29
- /// The maximum number of bytes possibly logged by a single Generation I update.
28
+ /// The optimal size in bytes required to hold all Generation I choice options.
29
+ /// At least as large as MAX_CHOICES.
30
30
  pub const CHOICES_SIZE = if (builtin.mode == .ReleaseSmall)
31
31
  MAX_CHOICES
32
32
  else
@@ -70,24 +70,24 @@ pub fn Battle(comptime RNG: anytype) type {
70
70
  rng: RNG,
71
71
 
72
72
  /// Returns the `Side` for the given `player`.
73
- pub inline fn side(self: anytype, player: Player) PointerType(@TypeOf(self), Side) {
73
+ pub fn side(self: anytype, player: Player) PointerType(@TypeOf(self), Side) {
74
74
  assert(@typeInfo(@TypeOf(self)).Pointer.child == Self);
75
75
  return &self.sides[@intFromEnum(player)];
76
76
  }
77
77
 
78
78
  /// Returns the `Side` of the opponent for the given `player`
79
- pub inline fn foe(self: anytype, player: Player) PointerType(@TypeOf(self), Side) {
79
+ pub fn foe(self: anytype, player: Player) PointerType(@TypeOf(self), Side) {
80
80
  assert(@typeInfo(@TypeOf(self)).Pointer.child == Self);
81
81
  return &self.sides[@intFromEnum(player.foe())];
82
82
  }
83
83
 
84
84
  /// Returns an identifier for the active Pokémon of `player`.
85
- pub inline fn active(self: *const Self, player: Player) ID {
85
+ pub fn active(self: *const Self, player: Player) ID {
86
86
  return player.ident(@intCast(self.side(player).order[0]));
87
87
  }
88
88
 
89
89
  /// Returns the `MoveDetails` for the given `player`.
90
- pub inline fn lastMove(
90
+ pub fn lastMove(
91
91
  self: anytype,
92
92
  player: Player,
93
93
  ) PointerType(@TypeOf(self), MoveDetails) {
@@ -112,7 +112,7 @@ pub fn Battle(comptime RNG: anytype) type {
112
112
  /// make (i.e. on the cartridge a soft-lock occurs).
113
113
  ///
114
114
  /// This function will always return choices > 0 if `pkmn.options.showdown` is true.
115
- pub fn choices(self: *Self, player: Player, request: Choice.Type, out: []Choice) u8 {
115
+ pub fn choices(self: *const Self, player: Player, request: Choice.Type, out: []Choice) u8 {
116
116
  return mechanics.choices(self, player, request, out);
117
117
  }
118
118
  };
@@ -141,7 +141,7 @@ pub const Side = extern struct {
141
141
  }
142
142
 
143
143
  /// Returns the stored `Pokemon` corresponding to the one-indexed party `slot`.
144
- pub inline fn get(self: anytype, slot: u8) PointerType(@TypeOf(self), Pokemon) {
144
+ pub fn get(self: anytype, slot: u8) PointerType(@TypeOf(self), Pokemon) {
145
145
  assert(@typeInfo(@TypeOf(self)).Pointer.child == Side);
146
146
  assert(slot > 0 and slot <= 6);
147
147
  const id = self.order[slot - 1];
@@ -150,7 +150,7 @@ pub const Side = extern struct {
150
150
  }
151
151
 
152
152
  /// Returns the stored `Pokemon` corresponding to the `active` Pokémon (slot 1).
153
- pub inline fn stored(self: anytype) PointerType(@TypeOf(self), Pokemon) {
153
+ pub fn stored(self: anytype) PointerType(@TypeOf(self), Pokemon) {
154
154
  assert(@typeInfo(@TypeOf(self)).Pointer.child == Side);
155
155
  return self.get(1);
156
156
  }
@@ -176,7 +176,7 @@ pub const ActivePokemon = extern struct {
176
176
  }
177
177
 
178
178
  /// Returns the active Pokémon's current move slot located at the one-indexed `mslot`.
179
- pub inline fn move(self: anytype, mslot: u8) PointerType(@TypeOf(self), MoveSlot) {
179
+ pub fn move(self: anytype, mslot: u8) PointerType(@TypeOf(self), MoveSlot) {
180
180
  assert(@typeInfo(@TypeOf(self)).Pointer.child == ActivePokemon);
181
181
  assert(mslot > 0 and mslot <= 4);
182
182
  assert(self.moves[mslot - 1].id != .None);
@@ -206,7 +206,7 @@ pub const Pokemon = extern struct {
206
206
  }
207
207
 
208
208
  /// Returns the Pokémon's original move slot located at the one-indexed `mslot`.
209
- pub inline fn move(self: anytype, mslot: u8) PointerType(@TypeOf(self), MoveSlot) {
209
+ pub fn move(self: anytype, mslot: u8) PointerType(@TypeOf(self), MoveSlot) {
210
210
  assert(@typeInfo(@TypeOf(self)).Pointer.child == Pokemon);
211
211
  assert(mslot > 0 and mslot <= 4);
212
212
  assert(self.moves[mslot - 1].id != .None);
@@ -258,36 +258,36 @@ pub const Status = enum(u8) {
258
258
  const SLP = 0b111;
259
259
 
260
260
  /// Whether or not the status `num` is the same as `status`.
261
- pub inline fn is(num: u8, status: Status) bool {
261
+ pub fn is(num: u8, status: Status) bool {
262
262
  if (status == .SLP) return Status.duration(num) > 0;
263
263
  return ((num >> @intCast(@intFromEnum(status))) & 1) != 0;
264
264
  }
265
265
 
266
266
  /// Initializes a non-sleep `status`. Use `slp` or `slf` to initialize a sleep status.
267
- pub inline fn init(status: Status) u8 {
267
+ pub fn init(status: Status) u8 {
268
268
  assert(status != .SLP and status != .EXT);
269
269
  return @as(u8, 1) << @intCast(@intFromEnum(status));
270
270
  }
271
271
 
272
272
  /// Initializes a non self-inflicted sleep status with duration `dur`.
273
- pub inline fn slp(dur: u3) u8 {
273
+ pub fn slp(dur: u3) u8 {
274
274
  assert(dur > 0);
275
275
  return @as(u8, dur);
276
276
  }
277
277
 
278
278
  /// Initializes a self-inflicted sleep status with duration `dur`.
279
- pub inline fn slf(dur: u3) u8 {
279
+ pub fn slf(dur: u3) u8 {
280
280
  assert(dur > 0);
281
281
  return 0x80 | slp(dur);
282
282
  }
283
283
 
284
284
  /// Returns the duration of a sleep status.
285
- pub inline fn duration(num: u8) u3 {
285
+ pub fn duration(num: u8) u3 {
286
286
  return @intCast(num & SLP);
287
287
  }
288
288
 
289
289
  /// Returns whether `num` reflects any status.
290
- pub inline fn any(num: u8) bool {
290
+ pub fn any(num: u8) bool {
291
291
  return num > 0;
292
292
  }
293
293
 
@@ -316,12 +316,12 @@ test Status {
316
316
  try expect(Status.is(Status.slp(5), .SLP));
317
317
  try expect(!Status.is(Status.slp(5), .EXT));
318
318
  try expect(!Status.is(Status.slp(7), .PSN));
319
- try expectEqual(@as(u3, 5), Status.duration(Status.slp(5)));
319
+ try expectEqual(5, Status.duration(Status.slp(5)));
320
320
  try expect(Status.is(Status.slf(2), .SLP));
321
321
  try expect(Status.is(Status.slf(2), .EXT));
322
322
  try expect(Status.is(Status.TOX, .PSN));
323
323
  try expect(Status.is(Status.TOX, .EXT));
324
- try expectEqual(@as(u3, 1), Status.duration(Status.slp(1)));
324
+ try expectEqual(1, Status.duration(Status.slp(1)));
325
325
  }
326
326
 
327
327
  /// Bitfield representation of volatile statuses and associated data in Generation I.
@@ -405,13 +405,13 @@ test Volatiles {
405
405
  try expect(!volatiles.Transform);
406
406
  try expect(!volatiles.MultiHit);
407
407
 
408
- try expectEqual(@as(u16, 235), volatiles.state);
409
- try expectEqual(@as(u8, 42), volatiles.substitute);
410
- try expectEqual(@as(u4, 2), volatiles.disable_move);
411
- try expectEqual(@as(u4, 4), volatiles.disable_duration);
412
- try expectEqual(@as(u4, 2), volatiles.confusion);
413
- try expectEqual(@as(u5, 4), volatiles.toxic);
414
- try expectEqual(@as(u4, 3), volatiles.attacks);
408
+ try expectEqual(235, volatiles.state);
409
+ try expectEqual(42, volatiles.substitute);
410
+ try expectEqual(2, volatiles.disable_move);
411
+ try expectEqual(4, volatiles.disable_duration);
412
+ try expectEqual(2, volatiles.confusion);
413
+ try expectEqual(4, volatiles.toxic);
414
+ try expectEqual(3, volatiles.attacks);
415
415
  }
416
416
 
417
417
  /// Representaiton of a Pokémon's stats in Generation I.
@@ -442,9 +442,9 @@ test Stats {
442
442
  try expectEqual(0, stats.def);
443
443
 
444
444
  var base = Species.get(.Gyarados).stats;
445
- try expectEqual(@as(u16, 127), Stats(u16).calc("hp", base.hp, 0, 5120, 38));
445
+ try expectEqual(127, Stats(u16).calc("hp", base.hp, 0, 5120, 38));
446
446
  base = Species.get(.Pidgeot).stats;
447
- try expectEqual(@as(u16, 279), Stats(u16).calc("spe", base.spe, 15, 63001, 100));
447
+ try expectEqual(279, Stats(u16).calc("spe", base.spe, 15, 63001, 100));
448
448
  }
449
449
 
450
450
  /// Representation of a Pokémon's boosts in Generation I.
@@ -537,7 +537,7 @@ pub const Species = species.Species;
537
537
 
538
538
  test Species {
539
539
  try expectEqual(2, @intFromEnum(Species.Ivysaur));
540
- try expectEqual(@as(u8, 100), Species.get(.Mew).stats.def);
540
+ try expectEqual(100, Species.get(.Mew).stats.def);
541
541
  }
542
542
 
543
543
  /// Representation of a Generation I type in Pokémon.
@@ -576,8 +576,8 @@ test Types {
576
576
  try expectEqual(Effectiveness.Resisted, Type.effectiveness(.Fire, .Water));
577
577
  try expectEqual(Effectiveness.Neutral, Type.effectiveness(.Normal, .Grass));
578
578
 
579
- try expectEqual(@as(u8, 3), Type.precedence(.Water, .Water));
580
- try expectEqual(@as(u8, 26), Type.precedence(.Bug, .Ghost));
579
+ try expectEqual(3, Type.precedence(.Water, .Water));
580
+ try expectEqual(26, Type.precedence(.Bug, .Ghost));
581
581
  try expect(Type.precedence(.Poison, .Bug) > Type.precedence(.Poison, .Poison));
582
582
 
583
583
  const t: Types = .{ .type1 = .Rock, .type2 = .Ground };
@@ -617,18 +617,18 @@ pub const DVs = struct {
617
617
 
618
618
  test DVs {
619
619
  var dvs: DVs = .{ .spc = 15, .spe = 15 };
620
- try expectEqual(@as(u4, 15), dvs.hp());
620
+ try expectEqual(15, dvs.hp());
621
621
  dvs = .{
622
622
  .atk = 5,
623
623
  .def = 15,
624
624
  .spe = 13,
625
625
  .spc = 13,
626
626
  };
627
- try expectEqual(@as(u4, 15), dvs.hp());
627
+ try expectEqual(15, dvs.hp());
628
628
  dvs = .{
629
629
  .def = 3,
630
630
  .spe = 10,
631
631
  .spc = 11,
632
632
  };
633
- try expectEqual(@as(u4, 13), dvs.hp());
633
+ try expectEqual(13, dvs.hp());
634
634
  }
@@ -1,4 +1,5 @@
1
1
  const std = @import("std");
2
+ const builtin = @import("builtin");
2
3
 
3
4
  const common = @import("../common/data.zig");
4
5
  const optional = @import("../common/optional.zig");
@@ -35,6 +36,8 @@ const Status = data.Status;
35
36
  const Action = chance.Action;
36
37
  const Actions = chance.Actions;
37
38
 
39
+ const endian = builtin.cpu.arch.endian();
40
+
38
41
  /// Configuration options controlling the domain of valid Pokémon that can be generated by the
39
42
  /// random generator helpers.
40
43
  pub const Options = struct {
@@ -43,6 +46,8 @@ pub const Options = struct {
43
46
  /// Whether to generate Pokémon with moves that contain bugs on Pokémon Showdown that are
44
47
  /// unimplementable in the engine.
45
48
  block: bool = showdown,
49
+ // FIXME: remove once durations work with -Dchance
50
+ durations: bool = false,
46
51
  };
47
52
 
48
53
  /// Helpers to simplify initialization of a Generation I battle.
@@ -86,18 +91,38 @@ pub const Battle = struct {
86
91
  };
87
92
 
88
93
  fn prng(rand: *PSRNG) data.PRNG {
89
- // GLITCH: initial bytes in seed can only range from 0-252, not 0-255
90
- const max: u8 = 253;
91
94
  return .{
92
95
  .src = .{
93
96
  .seed = if (showdown)
94
97
  rand.newSeed()
95
- else
96
- .{
97
- rand.range(u8, 0, max), rand.range(u8, 0, max), rand.range(u8, 0, max),
98
- rand.range(u8, 0, max), rand.range(u8, 0, max), rand.range(u8, 0, max),
99
- rand.range(u8, 0, max), rand.range(u8, 0, max), rand.range(u8, 0, max),
100
- },
98
+ else seed: {
99
+ // GLITCH: The initial RNG seed in Gen I link battles is heavily constrained
100
+ // https://www.smogon.com/forums/threads/rng-in-rby-link-battles.3746779
101
+ var seed = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0 };
102
+
103
+ // Bytes in seed can only range from 0-252, not 0-255
104
+ const max: u8 = 253;
105
+ // Instructions take multiples of 4 cycles to execute
106
+ var timer: u16 = 4 * rand.range(u16, 0, 16384);
107
+ var add: u8 = rand.range(u8, 0, 256);
108
+ var carry: u8 = 0;
109
+
110
+ var i: u8 = 0;
111
+ while (i < seed.len) {
112
+ const div: u8 = @intCast(timer >> 8);
113
+ add +%= div +% carry;
114
+ if (add < max) {
115
+ seed[i] = add;
116
+ i += 1;
117
+ timer +%= 472;
118
+ carry = 1;
119
+ } else {
120
+ timer +%= 452;
121
+ carry = 0;
122
+ }
123
+ }
124
+ break :seed seed;
125
+ },
101
126
  },
102
127
  };
103
128
  }
@@ -210,6 +235,7 @@ pub const Pokemon = struct {
210
235
  var m: Move = .None;
211
236
  sample: while (true) {
212
237
  m = @enumFromInt(rand.range(u8, 1, Move.size - 1 + 1));
238
+ if (opt.durations and durations(m)) continue :sample;
213
239
  if (opt.block and blocked(m)) continue :sample;
214
240
  for (0..i) |j| if (ms[j].id == m) continue :sample;
215
241
  break;
@@ -247,6 +273,23 @@ fn blocked(m: Move) bool {
247
273
  };
248
274
  }
249
275
 
276
+ fn durations(m: Move) bool {
277
+ // Any moves that have effects that result in duration tracking with -Dchance
278
+ return switch (Move.get(m).effect) {
279
+ .Confusion,
280
+ .Bide,
281
+ .Sleep,
282
+ .Rage,
283
+ .Binding,
284
+ .Thrashing,
285
+ .ConfusionChance,
286
+ .Disable,
287
+ .Metronome,
288
+ => true,
289
+ else => false,
290
+ };
291
+ }
292
+
250
293
  /// Convenience helper to create a move-type choice for the provided move slot.
251
294
  pub fn move(slot: u4) Choice {
252
295
  return .{ .type = .Move, .data = slot };
@@ -266,8 +309,8 @@ pub const Rolls = struct {
266
309
  const PLAYERS = [_]Optional(Player){ .P1, .P2 };
267
310
 
268
311
  /// Returns a slice with the correct range of values for speed ties given the `action` state.
269
- pub inline fn speedTie(action: Action) []const Optional(Player) {
270
- return if (@field(action, "speed_tie") == .None) &PLAYER_NONE else &PLAYERS;
312
+ pub fn speedTie(action: Action) []const Optional(Player) {
313
+ return if (action.speed_tie == .None) &PLAYER_NONE else &PLAYERS;
271
314
  }
272
315
 
273
316
  const BOOL_NONE = [_]Optional(bool){.None};
@@ -275,24 +318,24 @@ pub const Rolls = struct {
275
318
 
276
319
  /// Returns a slice with the correct range of values for hits given the `action` state
277
320
  /// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
278
- pub inline fn hit(action: Action, parent: Optional(bool)) []const Optional(bool) {
321
+ pub fn hit(action: Action, parent: Optional(bool)) []const Optional(bool) {
279
322
  if (parent == .true) return &BOOL_NONE;
280
- return if (@field(action, "hit") == .None) &BOOL_NONE else &BOOLS;
323
+ return if (action.hit == .None) &BOOL_NONE else &BOOLS;
281
324
  }
282
325
 
283
326
  /// Returns a slice with the correct range of values for critical hits given the `action` state
284
327
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
285
- pub inline fn criticalHit(
328
+ pub fn criticalHit(
286
329
  action: Action,
287
330
  parent: Optional(bool),
288
331
  ) []const Optional(bool) {
289
332
  if (parent == .false) return &BOOL_NONE;
290
- return if (@field(action, "critical_hit") == .None) &BOOL_NONE else &BOOLS;
333
+ return if (action.critical_hit == .None) &BOOL_NONE else &BOOLS;
291
334
  }
292
335
 
293
336
  /// Returns a slice with the correct range of values for secondary chances hits given the
294
337
  /// `action` state and the state of the `parent` (whether the player's Pokémon's move hit).
295
- pub inline fn secondaryChance(
338
+ pub fn secondaryChance(
296
339
  action: Action,
297
340
  parent: Optional(bool),
298
341
  ) []const Optional(bool) {
@@ -305,8 +348,8 @@ pub const Rolls = struct {
305
348
 
306
349
  /// Returns the range bounding damage rolls given the `action` state and the state of
307
350
  /// the `parent` (whether the player's Pokémon's move hit).
308
- pub inline fn damage(action: Action, parent: Optional(bool)) Range {
309
- return if (parent == .false or @field(action, "damage") == 0)
351
+ pub fn damage(action: Action, parent: Optional(bool)) Range {
352
+ return if (parent == .false or action.damage == 0)
310
353
  .{ .min = 0, .max = 1 }
311
354
  else
312
355
  .{ .min = 217, .max = 256 };
@@ -314,7 +357,7 @@ pub const Rolls = struct {
314
357
 
315
358
  /// Returns the max damage roll which will produce the same damage as `roll`
316
359
  /// given the base damage in `summaries`.
317
- pub inline fn coalesce(player: Player, roll: u8, summaries: *calc.Summaries, cap: bool) !u8 {
360
+ pub fn coalesce(player: Player, roll: u8, summaries: *calc.Summaries, cap: bool) !u8 {
318
361
  if (roll == 0) return roll;
319
362
 
320
363
  const dmg = summaries.get(player.foe()).damage;
@@ -326,15 +369,15 @@ pub const Rolls = struct {
326
369
 
327
370
  /// Returns a slice with the correct range of values for confused given the `action` state
328
371
  /// and the state of the `parent` (the player's Pokémon's remaining confusion turns).
329
- pub inline fn confused(action: Action, parent: u4) []const Optional(bool) {
330
- return if (parent == 0 or @field(action, "confused") == .None) &BOOL_NONE else &BOOLS;
372
+ pub fn confused(action: Action, parent: u4) []const Optional(bool) {
373
+ return if (parent == 0 or action.confused == .None) &BOOL_NONE else &BOOLS;
331
374
  }
332
375
 
333
376
  /// Returns a slice with the correct range of values for paralysis given the `action` state
334
377
  /// and the state of the `parent` (whether the player's Pokémon was confused).
335
- pub inline fn paralyzed(action: Action, parent: Optional(bool)) []const Optional(bool) {
378
+ pub fn paralyzed(action: Action, parent: Optional(bool)) []const Optional(bool) {
336
379
  if (parent == .true) return &BOOL_NONE;
337
- return if (@field(action, "paralyzed") == .None) &BOOL_NONE else &BOOLS;
380
+ return if (action.paralyzed == .None) &BOOL_NONE else &BOOLS;
338
381
  }
339
382
 
340
383
  const DURATION_NONE = [_]u1{0};
@@ -342,9 +385,9 @@ pub const Rolls = struct {
342
385
 
343
386
  /// Returns a slice with a range of values for duration given the `action` state
344
387
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
345
- pub inline fn duration(action: Action, parent: Optional(bool)) []const u1 {
388
+ pub fn duration(action: Action, parent: Optional(bool)) []const u1 {
346
389
  if (parent == .false) return &DURATION_NONE;
347
- return if (@field(action, "duration") == 0) &DURATION_NONE else &DURATION;
390
+ return if (action.duration == 0) &DURATION_NONE else &DURATION;
348
391
  }
349
392
 
350
393
  const U3_NONE = [_]u3{0};
@@ -352,8 +395,8 @@ pub const Rolls = struct {
352
395
 
353
396
  /// Returns a slice with a range of values for sleep given the `action` and `origin` states.
354
397
  pub inline fn sleep(action: Action, origin: Action) []const u3 {
355
- const a = @field(action.durations, "sleep");
356
- const o = @field(origin.durations, "sleep");
398
+ const a = action.durations.sleep;
399
+ const o = origin.durations.sleep;
357
400
  if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
358
401
  assert(a == 0 or a == o + 1);
359
402
  return if (o >= 7) &U3_NONE else &[_]u3{ 0, o + 1 };
@@ -365,8 +408,8 @@ pub const Rolls = struct {
365
408
  /// and the state of the `parent` (the player's Pokémon's remaining sleep turns).
366
409
  pub inline fn disable(action: Action, origin: Action, parent: u4) []const u4 {
367
410
  if (parent > 0) return &DISABLE_NONE;
368
- const a = @field(action.durations, "disable");
369
- const o = @field(origin.durations, "disable");
411
+ const a = action.durations.disable;
412
+ const o = origin.durations.disable;
370
413
  if (o == a) return if (o == 0) &DISABLE_NONE else &[_]u4{o};
371
414
  assert(a == 0 or a == o + 1);
372
415
  return if (o >= 8) &DISABLE_NONE else &[_]u4{ 0, o + 1 };
@@ -376,8 +419,8 @@ pub const Rolls = struct {
376
419
  /// and the state of the `parent` (the player's remaining sleep turns).
377
420
  pub inline fn confusion(action: Action, origin: Action, parent: u4) []const u3 {
378
421
  if (parent > 0) return &U3_NONE;
379
- const a = @field(action.durations, "confusion");
380
- const o = @field(origin.durations, "confusion");
422
+ const a = action.durations.confusion;
423
+ const o = origin.durations.confusion;
381
424
  if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
382
425
  assert(a == 0 or a == o + 1);
383
426
  return if (o >= 5) &U3_NONE else if (o < 2)
@@ -390,8 +433,8 @@ pub const Rolls = struct {
390
433
  /// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
391
434
  pub inline fn attacking(action: Action, origin: Action, parent: Optional(bool)) []const u3 {
392
435
  if (parent == .true) return &U3_NONE;
393
- const a = @field(action.durations, "attacking");
394
- const o = @field(origin.durations, "attacking");
436
+ const a = action.durations.attacking;
437
+ const o = origin.durations.attacking;
395
438
  if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
396
439
  assert(a == 0 or a == o + 1);
397
440
  return if (o >= 3) &U3_NONE else if (o < 2)
@@ -404,8 +447,8 @@ pub const Rolls = struct {
404
447
  /// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
405
448
  pub inline fn binding(action: Action, origin: Action, parent: Optional(bool)) []const u3 {
406
449
  if (parent == .true) return &U3_NONE;
407
- const a = @field(action.durations, "binding");
408
- const o = @field(origin.durations, "binding");
450
+ const a = action.durations.binding;
451
+ const o = origin.durations.binding;
409
452
  if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
410
453
  assert(a == 0 or a == o + 1);
411
454
  return if (o >= 4) &U3_NONE else &[_]u3{ 0, o + 1 };
@@ -418,9 +461,9 @@ pub const Rolls = struct {
418
461
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
419
462
  ///
420
463
  /// These slots may or **may not be valid** as slots may be unset / have 0 PP.
421
- pub inline fn moveSlot(action: Action, parent: Optional(bool)) []const u4 {
464
+ pub fn moveSlot(action: Action, parent: Optional(bool)) []const u4 {
422
465
  if (parent == .false) return &SLOT_NONE;
423
- return if (@field(action, "move_slot") == 0) &SLOT_NONE else &SLOT;
466
+ return if (action.move_slot == 0) &SLOT_NONE else &SLOT;
424
467
  }
425
468
 
426
469
  const MULTI_NONE = [_]u4{0};
@@ -428,9 +471,9 @@ pub const Rolls = struct {
428
471
 
429
472
  /// Returns a slice with the correct range of values for multi hit given the `action` state
430
473
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
431
- pub inline fn multiHit(action: Action, parent: Optional(bool)) []const u4 {
474
+ pub fn multiHit(action: Action, parent: Optional(bool)) []const u4 {
432
475
  if (parent == .false) return &MULTI_NONE;
433
- return if (@field(action, "multi_hit") == 0) &MULTI_NONE else &MULTI;
476
+ return if (action.multi_hit == 0) &MULTI_NONE else &MULTI;
434
477
  }
435
478
 
436
479
  const PSYWAVE_NONE = [_]u8{0};
@@ -442,13 +485,13 @@ pub const Rolls = struct {
442
485
 
443
486
  /// Returns a slice with the correct range of values for psywave given the `action` state,
444
487
  /// the `side`, and the state of the `parent` (whether the player's Pokémon's move hit).
445
- pub inline fn psywave(
488
+ pub fn psywave(
446
489
  action: Action,
447
490
  side: *data.Side,
448
491
  parent: Optional(bool),
449
492
  ) []const u8 {
450
493
  if (parent == .false) return &PSYWAVE_NONE;
451
- return if (@field(action, "psywave") == 0)
494
+ return if (action.psywave == 0)
452
495
  &PSYWAVE_NONE
453
496
  else
454
497
  PSYWAVE[0 .. @as(u16, side.stored().level) * 3 / 2];
@@ -457,8 +500,8 @@ pub const Rolls = struct {
457
500
  const MOVE_NONE = [_]Move{.None};
458
501
 
459
502
  /// Returns a slice with the correct range of values for metronome given the `action` state.
460
- pub inline fn metronome(action: Action) []const Move {
461
- return if (@field(action, "metronome") == .None) &MOVE_NONE else &Move.METRONOME;
503
+ pub fn metronome(action: Action) []const Move {
504
+ return if (action.metronome == .None) &MOVE_NONE else &Move.METRONOME;
462
505
  }
463
506
  };
464
507
 
@@ -478,12 +521,12 @@ test "Rolls.damage" {
478
521
  test "Rolls.coalesce" {
479
522
  var summaries =
480
523
  calc.Summaries{ .p1 = .{ .damage = .{ .base = 74, .final = 69, .capped = true } } };
481
- try expectEqual(@as(u8, 0), try Rolls.coalesce(.P2, 0, &summaries, false));
482
- try expectEqual(@as(u8, 241), try Rolls.coalesce(.P2, 238, &summaries, false));
483
- try expectEqual(@as(u8, 255), try Rolls.coalesce(.P2, 238, &summaries, true));
524
+ try expectEqual(0, try Rolls.coalesce(.P2, 0, &summaries, false));
525
+ try expectEqual(241, try Rolls.coalesce(.P2, 238, &summaries, false));
526
+ try expectEqual(255, try Rolls.coalesce(.P2, 238, &summaries, true));
484
527
  summaries.p1.damage.final = 74;
485
- try expectEqual(@as(u8, 217), try Rolls.coalesce(.P2, 217, &summaries, false));
486
- try expectEqual(@as(u8, 255), try Rolls.coalesce(.P2, 217, &summaries, true));
528
+ try expectEqual(217, try Rolls.coalesce(.P2, 217, &summaries, false));
529
+ try expectEqual(255, try Rolls.coalesce(.P2, 217, &summaries, true));
487
530
  }
488
531
 
489
532
  test "Rolls.hit" {
@@ -771,9 +814,53 @@ test "Rolls.psywave" {
771
814
 
772
815
  try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p1, &side, .None));
773
816
  var rolls = Rolls.psywave(actions.p2, &side, .None);
774
- try expectEqual(@as(u8, 150), rolls[rolls.len - 1]);
817
+ try expectEqual(150, rolls[rolls.len - 1]);
775
818
  side.stored().level = 81;
776
819
  rolls = Rolls.psywave(actions.p2, &side, .None);
777
- try expectEqual(@as(u8, 121), rolls[rolls.len - 1]);
820
+ try expectEqual(121, rolls[rolls.len - 1]);
778
821
  try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p2, &side, .false));
779
822
  }
823
+
824
+ /// Empirically determined maximum number of bytes possibly required to store the diff between two
825
+ /// Generation I battles.
826
+ pub const MAX_DIFFS: usize = 117;
827
+
828
+ /// The optimal size in bytes required to hold the diff between two Generation I battles.
829
+ /// At least as large as MAX_DIFFS.
830
+ pub const DIFFS_SIZE = if (builtin.mode == .ReleaseSmall)
831
+ MAX_DIFFS
832
+ else
833
+ std.math.ceilPowerOfTwo(usize, MAX_DIFFS) catch unreachable;
834
+
835
+ /// TODO
836
+ pub fn diff(
837
+ a: *const data.Battle(data.PRNG),
838
+ b: *const data.Battle(data.PRNG),
839
+ w: protocol.ByteStream.Writer,
840
+ ) !usize {
841
+ const pos = w.stream.pos;
842
+ const size = @sizeOf(data.Battle(data.PRNG)) - @sizeOf(data.PRNG);
843
+ assert(size % 2 == 0);
844
+ assert(size / 2 < 255);
845
+
846
+ var i: usize = 0;
847
+ while (i < size) : (i += 2) {
848
+ const val = std.mem.bytesAsValue(u16, std.mem.asBytes(a)[i .. i + 2]).*;
849
+ if (val != std.mem.bytesAsValue(u16, std.mem.asBytes(b)[i .. i + 2]).*) {
850
+ try w.writeByte(@intCast(i / 2));
851
+ try w.writeInt(u16, val, endian);
852
+ }
853
+ }
854
+
855
+ return w.stream.pos - pos;
856
+ }
857
+
858
+ /// TODO
859
+ pub fn patch(battle: *data.Battle(data.PRNG), buf: []u8) void {
860
+ var i: usize = 0;
861
+ while (i < buf.len) : (i += 3) {
862
+ const off = @as(u16, @intCast(buf[i])) * 2;
863
+ const val = std.mem.readInt(u16, @ptrCast(buf[i + 1 .. i + 3]), endian);
864
+ std.mem.writeInt(u16, @ptrCast(std.mem.asBytes(battle)[off .. off + 2]), val, endian);
865
+ }
866
+ }