@pkmn/engine 0.1.0-dev.645b4750 → 0.1.0-dev.6ba42cd5

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.
@@ -35,9 +35,6 @@ const Sleeps = chance.Sleeps;
35
35
  const tty = true; // DEBUG
36
36
  const summary = false; // DEBUG
37
37
 
38
- const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
39
- const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
40
-
41
38
  /// Information relevant to damage calculation that occured during a Generation I battle `update`.
42
39
  pub const Summaries = extern struct {
43
40
  /// Relevant information for Player 1.
@@ -99,13 +96,13 @@ pub const Calc = struct {
99
96
  self: Calc,
100
97
  player: Player,
101
98
  comptime field: Action.Field,
102
- ) ?util.FieldType(Action, field) {
99
+ ) ?@FieldType(Action, @tagName(field)) {
103
100
  if (!enabled) return null;
104
101
 
105
102
  const val = @field(self.overrides.get(player), @tagName(field));
106
103
  return if (switch (@typeInfo(@TypeOf(val))) {
107
- Enum => val != .None,
108
- Int => val != 0,
104
+ .@"enum" => val != .None,
105
+ .int => val != 0,
109
106
  else => unreachable,
110
107
  }) val else null;
111
108
  }
@@ -146,7 +143,7 @@ const Null = struct {
146
143
  self: Null,
147
144
  player: Player,
148
145
  comptime field: Action.Field,
149
- ) ?util.FieldType(Action, field) {
146
+ ) ?@FieldType(Action, @tagName(field)) {
150
147
  _ = .{ self, player };
151
148
  return null;
152
149
  }
@@ -182,14 +179,15 @@ pub const Options = struct {
182
179
  metronome: bool = false,
183
180
  };
184
181
 
185
- pub const MAX_FRONTIER = 81;
182
+ pub const MAX_FRONTIER = 83;
186
183
 
187
184
  pub fn transitions(
188
185
  battle: anytype,
189
186
  c1: Choice,
190
187
  c2: Choice,
191
188
  allocator: std.mem.Allocator,
192
- writer: anytype,
189
+ out: *std.Io.Writer,
190
+ err: *std.Io.Writer,
193
191
  options: Options,
194
192
  ) !?Stats {
195
193
  var stats: Stats = .{};
@@ -198,8 +196,8 @@ pub fn transitions(
198
196
 
199
197
  var seen = std.AutoHashMap(Actions, void).init(allocator);
200
198
  defer seen.deinit();
201
- var frontier = std.ArrayList(Actions).init(allocator);
202
- defer frontier.deinit();
199
+ var frontier: std.array_list.Aligned(Actions, null) = .empty;
200
+ defer frontier.deinit(allocator);
203
201
 
204
202
  const d = options.durations;
205
203
 
@@ -217,7 +215,7 @@ pub fn transitions(
217
215
  const p2 = b.side(.P2);
218
216
 
219
217
  var p: Rational(u256) = .{ .p = 0, .q = 1 };
220
- try frontier.append(opts.chance.actions);
218
+ try frontier.append(allocator, opts.chance.actions);
221
219
 
222
220
  // zig fmt: off
223
221
  for (Rolls.metronome(frontier.items[0].p1)) |p1_move| {
@@ -233,7 +231,7 @@ pub fn transitions(
233
231
 
234
232
  var r: Rational(u256) = .{ .p = 0, .q = 1 };
235
233
 
236
- try debug(writer, f, .{
234
+ try debug(out, f, .{
237
235
  .shape = true,
238
236
  .color = i,
239
237
  .bold = true,
@@ -280,8 +278,8 @@ pub fn transitions(
280
278
 
281
279
  var p2_dmg = Rolls.damage(f.p2, p2_hit);
282
280
 
283
- const p1_min: u9 = p1_dmg.min;
284
- const p2_min: u9 = p2_dmg.min;
281
+ const p1_min: u9 = @intCast(p1_dmg.min);
282
+ const p2_min: u9 = @intCast(p2_dmg.min);
285
283
 
286
284
  while (p2_dmg.min < p2_dmg.max) : (p2_dmg.min += 1) {
287
285
  a.p2.damage = @intCast(p2_dmg.min);
@@ -298,7 +296,7 @@ pub fn transitions(
298
296
 
299
297
  const summaries = &opts.calc.summaries;
300
298
  const p1_max: u9 = if (p2_dmg.min != p2_min)
301
- p1_dmg.min
299
+ @intCast(p1_dmg.min)
302
300
  else
303
301
  Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), summaries, cap);
304
302
  const p2_max: u9 =
@@ -307,7 +305,7 @@ pub fn transitions(
307
305
  if (opts.chance.actions.matches(f)) {
308
306
  if (!opts.chance.actions.relax().eql(a)) {
309
307
  if (!summary) {
310
- try debug(writer, opts.chance.actions, .{
308
+ try debug(out, opts.chance.actions, .{
311
309
  .p1_max = p1_max,
312
310
  .p2_max = p2_max,
313
311
  .color = i,
@@ -321,7 +319,7 @@ pub fn transitions(
321
319
  }
322
320
 
323
321
  if (!summary) {
324
- try debug(writer, opts.chance.actions, .{
322
+ try debug(out, opts.chance.actions, .{
325
323
  .p1_max = p1_max,
326
324
  .p2_max = p2_max,
327
325
  .color = i,
@@ -334,7 +332,7 @@ pub fn transitions(
334
332
  acts.p1.damage = @intCast(p1d);
335
333
  acts.p2.damage = @intCast(p2d);
336
334
  if ((try seen.getOrPut(acts)).found_existing) {
337
- err("already seen {}", .{acts}, options.seed);
335
+ die(err, "already seen {f}", .{acts}, options.seed);
338
336
  return error.TestUnexpectedResult;
339
337
  }
340
338
  }
@@ -351,21 +349,21 @@ pub fn transitions(
351
349
  stats.saved += 1;
352
350
 
353
351
  if (p.q < p.p) {
354
- err("improper fraction {}", .{p}, options.seed);
352
+ die(err, "improper fraction {f}", .{p}, options.seed);
355
353
  return error.TestUnexpectedResult;
356
354
  }
357
355
  } else {
358
356
  if (!opts.chance.actions.matchesAny(frontier.items, i)) {
359
- try frontier.append(opts.chance.actions);
357
+ try frontier.append(allocator, opts.chance.actions);
360
358
 
361
- try debug(writer, opts.chance.actions, .{
359
+ try debug(out, opts.chance.actions, .{
362
360
  .p1_max = p1_max,
363
361
  .p2_max = p2_max,
364
362
  .dim = true,
365
363
  .newline = false,
366
364
  });
367
- try writer.writeAll(" → ");
368
- try debug(writer, opts.chance.actions, .{
365
+ try out.writeAll(" → ");
366
+ try debug(out, opts.chance.actions, .{
369
367
  .shape = true,
370
368
  .color = frontier.items.len - 1,
371
369
  .dim = true,
@@ -373,7 +371,7 @@ pub fn transitions(
373
371
  .indent = false,
374
372
  });
375
373
  } else if (!summary) {
376
- try debug(writer, opts.chance.actions, .{
374
+ try debug(out, opts.chance.actions, .{
377
375
  .p1_max = p1_max,
378
376
  .p2_max = p2_max,
379
377
  .dim = true,
@@ -388,15 +386,12 @@ pub fn transitions(
388
386
  }}}}}}}}}}}}}}}}}}}}}}}}}}}}
389
387
 
390
388
  assert(stats.saved > saved);
391
- // TODO: ziglang/zig#22667
392
- // const old = @hasDecl(std.fmt, "formatFloatDecimal");
393
- // if (!old and @TypeOf(writer) != @TypeOf(std.io.null_writer)) {
394
- // try writer.print(
395
- // " {} ({d:.2}%)\n = ──────────\n {} ({d:.2}%)\n\n",
396
- // .{r, 100 * @as(f128, @floatFromInt(r.p)) / @as(f128, @floatFromInt(r.q)),
397
- // p, 100 * @as(f128, @floatFromInt(p.p)) / @as(f128, @floatFromInt(p.q))},
398
- // );
399
- // }
389
+ // TODO: LLVM - float_from_int from 'u256' without intrinsics
390
+ // try out.print(
391
+ // " {f} ({d:.2}%)\n = ──────────\n {f} ({d:.2}%)\n\n",
392
+ // .{r, 100 * @as(f128, @floatFromInt(r.p)) / @as(f128, @floatFromInt(r.q)),
393
+ // p, 100 * @as(f128, @floatFromInt(p.p)) / @as(f128, @floatFromInt(p.q))},
394
+ // );
400
395
 
401
396
  }
402
397
 
@@ -411,7 +406,7 @@ pub fn transitions(
411
406
 
412
407
  p.reduce();
413
408
  if (p.p != 1 or p.q != 1) {
414
- err("expected 1, found {}", .{p}, options.seed);
409
+ die(err, "expected 1, found {f}", .{p}, options.seed);
415
410
  return error.TestExpectedEqual;
416
411
  }
417
412
 
@@ -424,7 +419,8 @@ pub fn update(
424
419
  c2: Choice,
425
420
  options: anytype,
426
421
  allocator: std.mem.Allocator,
427
- writer: anytype,
422
+ out: *std.Io.Writer,
423
+ err: *std.Io.Writer,
428
424
  transition: bool,
429
425
  ) !Result {
430
426
  const durations = options.chance.durations;
@@ -442,8 +438,8 @@ pub fn update(
442
438
  if (@TypeOf(battle.rng) == data.PRNG) {
443
439
  // Ensure we can encode the diffs in less than MAX_DIFFS bytes.
444
440
  var buf: [helpers.MAX_DIFFS]u8 = undefined;
445
- var stream: protocol.ByteStream = .{ .buffer = &buf };
446
- const n = try helpers.diff(&original, battle, stream.writer());
441
+ var pw: protocol.Writer = .{ .buffer = &buf };
442
+ const n = try helpers.diff(&original, battle, &pw);
447
443
 
448
444
  // Applying the diff to the battle should take us back to the original copy
449
445
  // of the battle (ignoring the RNG).
@@ -457,7 +453,7 @@ pub fn update(
457
453
  // Ensure we can generate all transitions from the same original state
458
454
  // (we must change the battle's RNG from a FixedRNG to a PRNG because
459
455
  // the transitions function relies on RNG for discovery of states)
460
- if (try transitions(unfix(original), c1, c2, allocator, writer, .{
456
+ if (try transitions(unfix(original), c1, c2, allocator, out, err, .{
461
457
  .durations = durations,
462
458
  .cap = true,
463
459
  })) |stats| try expect(stats.frontier <= MAX_FRONTIER);
@@ -504,10 +500,9 @@ pub fn update(
504
500
  fn expectEqualActions(expected: Actions, actual: Actions) !void {
505
501
  return expectEqual(expected, actual) catch |e| switch (e) {
506
502
  error.TestExpectedEqual => {
507
- std.debug.print("expected {}, found {}\n", .{ expected, actual });
503
+ std.debug.print("expected {f}, found {f}\n", .{ expected, actual });
508
504
  return e;
509
505
  },
510
- else => return e,
511
506
  };
512
507
  }
513
508
 
@@ -526,10 +521,9 @@ fn unfix(actual: anytype) data.Battle(data.PRNG) {
526
521
  };
527
522
  }
528
523
 
529
- fn err(comptime fmt: []const u8, v: anytype, seed: ?u64) void {
530
- const w = std.io.getStdErr().writer();
524
+ fn die(w: *std.Io.Writer, comptime fmt: []const u8, v: anytype, seed: ?u64) void {
531
525
  w.print(fmt, v) catch return;
532
- if (seed) |s| return w.print("{}\n", .{s}) catch return;
526
+ if (seed) |s| return w.print("{d}\n", .{s}) catch return;
533
527
  return w.writeByte('\n') catch return;
534
528
  }
535
529
 
@@ -545,7 +539,7 @@ const Style = struct {
545
539
  indent: bool = true,
546
540
  };
547
541
 
548
- fn debug(writer: anytype, actions: Actions, style: Style) !void {
542
+ fn debug(writer: *std.Io.Writer, actions: Actions, style: Style) !void {
549
543
  if (style.indent) try writer.writeAll(" ");
550
544
  if (tty) {
551
545
  const mod: usize = if (style.dim) 2 else 1;
@@ -570,13 +564,13 @@ fn debug(writer: anytype, actions: Actions, style: Style) !void {
570
564
  if (style.newline) try writer.writeByte('\n');
571
565
  }
572
566
 
573
- fn format(writer: anytype, actions: Actions, p1: u9, p2: u9) !void {
567
+ fn format(writer: *std.Io.Writer, actions: Actions, p1: u9, p2: u9) !void {
574
568
  var input: [1024]u8 = undefined;
575
569
  var output: [1024]u8 = undefined;
576
570
 
577
- var stream = std.io.fixedBufferStream(&input);
578
- try actions.fmt(&stream.writer(), false);
579
- var len = stream.getWritten().len;
571
+ var w = std.Io.Writer.fixed(&input);
572
+ try actions.fmt(&w, false);
573
+ var len = w.buffered().len;
580
574
  @memcpy(output[0..len], input[0..len]);
581
575
 
582
576
  var find: [16]u8 = undefined;
@@ -649,7 +643,7 @@ pub const Rolls = struct {
649
643
  };
650
644
  }
651
645
 
652
- /// FIXME
646
+ /// FIXME document
653
647
  pub fn attacking(
654
648
  action: Action,
655
649
  duration: Duration,
@@ -675,7 +669,7 @@ pub const Rolls = struct {
675
669
  const CFZ = [_]Optional(Confusion){ .continuing, .ended };
676
670
  const CFZ_TIE = [_]Optional(Confusion){ .started, .continuing, .ended };
677
671
 
678
- /// FIXME
672
+ /// FIXME document
679
673
  pub fn confusion(
680
674
  action: Action,
681
675
  duration: Duration,
@@ -740,7 +734,7 @@ pub const Rolls = struct {
740
734
  }
741
735
 
742
736
  /// The min and max bounds on iteration over damage rolls.
743
- pub const Range = struct { min: u9, max: u9 };
737
+ pub const Range = struct { min: usize, max: usize };
744
738
 
745
739
  /// Returns the range bounding damage rolls given the `action` state and the state of
746
740
  /// the `parent` (whether the player's Pokémon's move hit).
@@ -23,10 +23,6 @@ const PointerType = util.PointerType;
23
23
  const print = std.debug.print;
24
24
  const showdown = options.showdown;
25
25
 
26
- const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
27
- const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
28
- const Struct = if (@hasField(std.builtin.Type, "struct")) .@"struct" else .Struct;
29
-
30
26
  /// Actions taken by a hypothetical "chance player" that convey information about which RNG events
31
27
  /// were observed during a Generation I battle `update`. This can additionally be provided as input
32
28
  /// to the `update` call to override the normal behavior of the RNG in order to force specific
@@ -56,15 +52,15 @@ pub const Actions = extern struct {
56
52
  /// same shape if they have the same fields set (though those fields need not necessarily be
57
53
  /// set to the same value).
58
54
  pub fn matches(a: Actions, b: Actions) bool {
59
- inline for (@field(@typeInfo(Actions), @tagName(Struct)).fields) |player| {
60
- inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
55
+ inline for (@typeInfo(Actions).@"struct".fields) |player| {
56
+ inline for (@typeInfo(Action).@"struct".fields) |field| {
61
57
  const a_val = @field(@field(a, player.name), field.name);
62
58
  const b_val = @field(@field(b, player.name), field.name);
63
59
 
64
60
  switch (@typeInfo(@TypeOf(a_val))) {
65
- Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
61
+ .@"enum" => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
66
62
  return false,
67
- Int => if ((a_val > 0) != (b_val > 0)) return false,
63
+ .int => if ((a_val > 0) != (b_val > 0)) return false,
68
64
  else => unreachable,
69
65
  }
70
66
  }
@@ -91,7 +87,7 @@ pub const Actions = extern struct {
91
87
  return a;
92
88
  }
93
89
 
94
- pub fn fmt(self: Actions, writer: anytype, shape: bool) !void {
90
+ pub fn fmt(self: Actions, writer: *std.Io.Writer, shape: bool) !void {
95
91
  try writer.writeAll("<P1 = ");
96
92
  try self.p1.fmt(writer, shape);
97
93
  try writer.writeAll(", P2 = ");
@@ -99,8 +95,7 @@ pub const Actions = extern struct {
99
95
  try writer.writeAll(">");
100
96
  }
101
97
 
102
- pub fn format(a: Actions, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
103
- _ = .{ f, o };
98
+ pub fn format(a: Actions, w: *std.Io.Writer) !void {
104
99
  try fmt(a, w, false);
105
100
  }
106
101
  };
@@ -191,18 +186,17 @@ pub const Action = packed struct(u64) {
191
186
 
192
187
  pub const Field = std.meta.FieldEnum(Action);
193
188
 
194
- pub fn format(a: Action, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
195
- _ = .{ f, o };
189
+ pub fn format(a: Action, w: *std.Io.Writer) !void {
196
190
  try fmt(a, w, false);
197
191
  }
198
192
 
199
- pub fn fmt(self: Action, writer: anytype, shape: bool) !void {
193
+ pub fn fmt(self: Action, writer: *std.Io.Writer, shape: bool) !void {
200
194
  try writer.writeByte('(');
201
195
  var printed = false;
202
- inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
196
+ inline for (@typeInfo(Action).@"struct".fields) |field| {
203
197
  const val = @field(self, field.name);
204
198
  switch (@typeInfo(@TypeOf(val))) {
205
- Enum => if (val != .None) {
199
+ .@"enum" => if (val != .None) {
206
200
  if (printed) try writer.writeAll(", ");
207
201
  if (shape) {
208
202
  try writer.print("{s}:?", .{field.name});
@@ -223,7 +217,7 @@ pub const Action = packed struct(u64) {
223
217
  }
224
218
  printed = true;
225
219
  },
226
- Int => if (val != 0) {
220
+ .int => if (val != 0) {
227
221
  if (printed) try writer.writeAll(", ");
228
222
  if (shape) {
229
223
  try writer.print("{s}:?", .{field.name});
@@ -256,14 +250,8 @@ pub const Durations = extern struct {
256
250
  return if (player == .P1) &self.p1 else &self.p2;
257
251
  }
258
252
 
259
- pub fn format(
260
- self: Durations,
261
- comptime fmt: []const u8,
262
- opts: std.fmt.FormatOptions,
263
- writer: anytype,
264
- ) !void {
265
- _ = .{ fmt, opts };
266
- try writer.print("[P1 = {}, P2 = {}]", .{ self.p1, self.p2 });
253
+ pub fn format(self: Durations, writer: *std.Io.Writer) !void {
254
+ try writer.print("[P1 = {f}, P2 = {f}]", .{ self.p1, self.p2 });
267
255
  }
268
256
  };
269
257
 
@@ -281,17 +269,14 @@ pub const Duration = packed struct(u32) {
281
269
 
282
270
  pub fn format(
283
271
  self: Duration,
284
- comptime fmt: []const u8,
285
- opts: std.fmt.FormatOptions,
286
- writer: anytype,
272
+ writer: *std.Io.Writer,
287
273
  ) !void {
288
- _ = .{ fmt, opts };
289
274
  try writer.writeByte('(');
290
275
  var printed = false;
291
- inline for (@field(@typeInfo(Duration), @tagName(Struct)).fields) |field| {
276
+ inline for (@typeInfo(Duration).@"struct".fields) |field| {
292
277
  const val = @field(self, field.name);
293
278
  switch (@typeInfo(@TypeOf(val))) {
294
- Int => if (val != 0) {
279
+ .int => if (val != 0) {
295
280
  if (printed) try writer.writeAll(", ");
296
281
  if (comptime std.mem.eql(u8, field.name, "sleeps")) {
297
282
  try writer.print("{s}:[", .{field.name});
@@ -1067,7 +1052,7 @@ pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
1067
1052
 
1068
1053
  r.reduce();
1069
1054
  if (r.p != p or r.q != q) {
1070
- print("expected {d}/{d}, found {}\n", .{ p, q, r });
1055
+ print("expected {d}/{d}, found {f}\n", .{ p, q, r });
1071
1056
  return error.TestExpectedEqual;
1072
1057
  }
1073
1058
  }
@@ -98,7 +98,7 @@ pub const Type = enum(u4) {
98
98
  return CHART[@intFromEnum(t1)][@intFromEnum(t2)];
99
99
  }
100
100
 
101
- /// The precedence order of Type `t2` vs. Type `t1`.
101
+ /// The precedence order of Type `t1` vs. Type `t2`.
102
102
  pub fn precedence(t1: Type, t2: Type) u8 {
103
103
  for (PRECEDENCE, 0..) |matchup, i| {
104
104
  if (matchup.type1 == t1 and matchup.type2 == t2) return @intCast(i);
@@ -108,7 +108,7 @@ pub const Type = enum(u4) {
108
108
  };
109
109
 
110
110
  /// Representation of a Pokémon's typing.
111
- pub const Types = packed struct {
111
+ pub const Types = packed struct(u8) {
112
112
  /// A Pokémon's primary type.
113
113
  type1: Type = .Normal,
114
114
  /// A Pokémon's secondary type (may be identical to its primary type).
@@ -62,7 +62,7 @@ pub fn Battle(comptime RNG: anytype) type {
62
62
  /// The last damage dealt by either side.
63
63
  last_damage: u16 = 0,
64
64
  /// Details about the last move selected/used by each player required to detect desyncs.
65
- last_moves: [2]MoveDetails = [_]MoveDetails{.{}} ** 2,
65
+ last_moves: [2]MoveDetails = @splat(.{}),
66
66
  /// The pseudo random number generator.
67
67
  rng: RNG,
68
68
 
@@ -122,12 +122,12 @@ test Battle {
122
122
  /// Representation of one side of a Generation I Pokémon battle.
123
123
  pub const Side = extern struct {
124
124
  /// The player's party in its original order
125
- pokemon: [6]Pokemon = [_]Pokemon{.{}} ** 6,
125
+ pokemon: [6]Pokemon = @splat(.{}),
126
126
  /// The active Pokémon for the side, zero initialized if the battle has yet to start.
127
127
  /// Note that fainted Pokémon are still consider "active" until their replacement switches in.
128
128
  active: ActivePokemon = .{},
129
129
  /// One-based slot indexes reflecting the current order of the player's party.
130
- order: [6]u8 = [_]u8{0} ** 6,
130
+ order: [6]u8 = @splat(0),
131
131
  /// The last move the player selected.
132
132
  last_selected_move: Move = .None,
133
133
  /// The last move the player used.
@@ -166,7 +166,7 @@ pub const ActivePokemon = extern struct {
166
166
  /// The active Pokémon's volatile statuses and associated data.
167
167
  volatiles: Volatiles = .{},
168
168
  /// The active Pokémon's current move slots.
169
- moves: [4]MoveSlot = [_]MoveSlot{.{}} ** 4,
169
+ moves: [4]MoveSlot = @splat(.{}),
170
170
 
171
171
  comptime {
172
172
  assert(@sizeOf(ActivePokemon) == 32);
@@ -186,7 +186,7 @@ pub const Pokemon = extern struct {
186
186
  /// The Pokémon's unmodified stats.
187
187
  stats: Stats(u16) = .{},
188
188
  /// The Pokémon's original stored move slots.
189
- moves: [4]MoveSlot = [_]MoveSlot{.{}} ** 4,
189
+ moves: [4]MoveSlot = @splat(.{}),
190
190
  /// The Pokémon's current HP.
191
191
  hp: u16 = 0,
192
192
  /// The Pokémon's current status.
@@ -19,8 +19,6 @@ const Stats = data.Stats;
19
19
 
20
20
  const endian = builtin.cpu.arch.endian();
21
21
 
22
- const Struct = if (@hasField(std.builtin.Type, "struct")) .@"struct" else .Struct;
23
-
24
22
  /// Configuration options controlling the domain of valid Pokémon that can be generated by the
25
23
  /// random generator helpers.
26
24
  pub const Options = struct {
@@ -163,7 +161,7 @@ pub const Pokemon = struct {
163
161
  var pokemon = data.Pokemon{};
164
162
  pokemon.species = p.species;
165
163
  const species = Species.get(p.species);
166
- inline for (@field(@typeInfo(@TypeOf(pokemon.stats)), @tagName(Struct)).fields) |field| {
164
+ inline for (@typeInfo(@TypeOf(pokemon.stats)).@"struct".fields) |field| {
167
165
  const hp = comptime std.mem.eql(u8, field.name, "hp");
168
166
  const spc =
169
167
  comptime std.mem.eql(u8, field.name, "spa") or std.mem.eql(u8, field.name, "spd");
@@ -199,7 +197,7 @@ pub const Pokemon = struct {
199
197
  const lvl = if (rand.chance(u8, 1, 20)) rand.range(u8, 1, 99 + 1) else 100;
200
198
  var stats: Stats(u16) = .{};
201
199
  const dvs = DVs.random(rand);
202
- inline for (@field(@typeInfo(@TypeOf(stats)), @tagName(Struct)).fields) |field| {
200
+ inline for (@typeInfo(@TypeOf(stats)).@"struct".fields) |field| {
203
201
  @field(stats, field.name) = Stats(u16).calc(
204
202
  field.name,
205
203
  @field(species.stats, field.name),
@@ -212,7 +210,7 @@ pub const Pokemon = struct {
212
210
  );
213
211
  }
214
212
 
215
- var ms = [_]MoveSlot{.{}} ** 4;
213
+ var ms: [4]MoveSlot = @splat(.{});
216
214
  const n = if (rand.chance(u8, 1, 100)) rand.range(u4, 1, 3 + 1) else 4;
217
215
  for (0..n) |i| {
218
216
  var m: Move = .None;
@@ -292,23 +290,23 @@ else
292
290
  pub fn diff(
293
291
  a: *const data.Battle(data.PRNG),
294
292
  b: *const data.Battle(data.PRNG),
295
- w: protocol.ByteStream.Writer,
293
+ w: *protocol.Writer,
296
294
  ) !usize {
297
- const pos = w.stream.pos;
295
+ const pos = w.pos;
298
296
  const size = @sizeOf(data.Battle(data.PRNG)) - @sizeOf(data.PRNG);
299
297
  assert(size % 2 == 0);
300
298
  assert(size / 2 < 255);
301
299
 
302
300
  var i: usize = 0;
303
301
  while (i < size) : (i += 2) {
304
- const val = util.bytesAsValue(u16, std.mem.asBytes(a)[i .. i + 2]).*;
305
- if (val != util.bytesAsValue(u16, std.mem.asBytes(b)[i .. i + 2]).*) {
302
+ const val = std.mem.bytesAsValue(u16, std.mem.asBytes(a)[i .. i + 2]).*;
303
+ if (val != std.mem.bytesAsValue(u16, std.mem.asBytes(b)[i .. i + 2]).*) {
306
304
  try w.writeByte(@intCast(i / 2));
307
305
  try w.writeInt(u16, val, endian);
308
306
  }
309
307
  }
310
308
 
311
- return w.stream.pos - pos;
309
+ return w.pos - pos;
312
310
  }
313
311
 
314
312
  /// TODO
@@ -878,7 +878,7 @@ fn doMove(
878
878
  var miss = showdown and miss: {
879
879
  immune = move.target != .Self and !status and !counter and
880
880
  (@intFromEnum(move.type.effectiveness(foe.active.types.type1)) == 0 or
881
- @intFromEnum(move.type.effectiveness(foe.active.types.type2)) == 0);
881
+ @intFromEnum(move.type.effectiveness(foe.active.types.type2)) == 0);
882
882
  if (immune and move.effect != .Binding) break :miss true;
883
883
  if (move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe) {
884
884
  battle.last_damage = 0;
@@ -1019,7 +1019,7 @@ fn doMove(
1019
1019
  const sub = showdown and foe.active.volatiles.Substitute;
1020
1020
 
1021
1021
  var nullified = false;
1022
- var hit: u4 = 0;
1022
+ var hit: usize = 0;
1023
1023
  while (hit < hits) {
1024
1024
  if (hit == 0) {
1025
1025
  if (crit) try log.crit(.{battle.active(player.foe())});
@@ -1047,7 +1047,7 @@ fn doMove(
1047
1047
  if (showdown and move.effect == .Twineedle and !nullified and foe.stored().hp > 0) {
1048
1048
  try Effects.poison(battle, player, Move.get(.PoisonSting), options);
1049
1049
  }
1050
- try log.hitcount(.{ battle.active(player.foe()), hit });
1050
+ try log.hitcount(.{ battle.active(player.foe()), @as(u4, @intCast(hit)) });
1051
1051
  } else if (showdown and move.effect == .Binding) {
1052
1052
  // This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
1053
1053
  try Effects.binding(battle, player, rewrap, options);
@@ -1274,15 +1274,15 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
1274
1274
  // Pretend Splash was used as a stand-in when no move has been used to fail below with 0 BP
1275
1275
  const foe_last_selected_move =
1276
1276
  Move.get(if (foe.last_selected_move == .None or foe.last_selected_move == .SKIP_TURN)
1277
- .Splash
1278
- else
1279
- foe.last_selected_move);
1277
+ .Splash
1278
+ else
1279
+ foe.last_selected_move);
1280
1280
 
1281
1281
  const used = battle.lastMove(player.foe()).counterable != 0;
1282
1282
  const selected = foe_last_selected_move.bp > 0 and
1283
1283
  foe.last_selected_move != .Counter and
1284
1284
  (foe_last_selected_move.type == .Normal or
1285
- foe_last_selected_move.type == .Fighting);
1285
+ foe_last_selected_move.type == .Fighting);
1286
1286
 
1287
1287
  if (!used and !selected) {
1288
1288
  try options.log.fail(.{ battle.active(player), .None });
@@ -1446,7 +1446,16 @@ fn moveHit(
1446
1446
  break :miss true;
1447
1447
  }
1448
1448
  if (!showdown) {
1449
- if (move.effect == .Swift) return true;
1449
+ if (move.effect == .Swift) {
1450
+ // If Swift could cause a division-by-zero freeze (which doesn't exist on Pokémon
1451
+ // Showdown, hence why the block above doesn't include this code), we need the
1452
+ // damage roll to be committed. However, the commit code will not save this roll if
1453
+ // the glitch occurs and the move was not marked as having hit. To work around this
1454
+ // we flag that the move hit (as Swift trivially does), but pass the probability of
1455
+ // the hit as zero to ensure it doesn't actually get saved.
1456
+ try options.chance.hit(player, true, 0);
1457
+ return true;
1458
+ }
1450
1459
  if (foe.active.volatiles.Invulnerable) break :miss true;
1451
1460
  }
1452
1461
 
@@ -3136,11 +3145,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3136
3145
  },
3137
3146
  .Switch => {
3138
3147
  const side = battle.side(player);
3139
- var slot: u4 = 2;
3148
+ var slot: usize = 2;
3140
3149
  while (slot <= 6) : (slot += 1) {
3141
3150
  const id = side.order[slot - 1];
3142
3151
  if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
3143
- out[n] = .{ .type = .Switch, .data = slot };
3152
+ out[n] = .{ .type = .Switch, .data = @intCast(slot) };
3144
3153
  n += 1;
3145
3154
  }
3146
3155
  if (n == 0) {
@@ -3171,11 +3180,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3171
3180
  return n;
3172
3181
  }
3173
3182
 
3174
- var slot: u4 = 2;
3183
+ var slot: usize = 2;
3175
3184
  while (slot <= 6) : (slot += 1) {
3176
3185
  const id = side.order[slot - 1];
3177
3186
  if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
3178
- out[n] = .{ .type = .Switch, .data = slot };
3187
+ out[n] = .{ .type = .Switch, .data = @intCast(slot) };
3179
3188
  n += 1;
3180
3189
  }
3181
3190
 
@@ -3208,7 +3217,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3208
3217
  const struggle =
3209
3218
  m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
3210
3219
  const s = if (struggle) 0 else slot;
3211
- out[n] = .{ .type = .Move, .data = s };
3220
+ out[n] = .{ .type = .Move, .data = @intCast(s) };
3212
3221
  n += 1;
3213
3222
  return n;
3214
3223
  }
@@ -3222,7 +3231,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3222
3231
  if (m.id == .None) break;
3223
3232
  if (m.pp == 0) continue;
3224
3233
  if (active.volatiles.disable_move == slot) continue;
3225
- out[n] = .{ .type = .Move, .data = slot };
3234
+ out[n] = .{ .type = .Move, .data = @intCast(slot) };
3226
3235
  n += 1;
3227
3236
  }
3228
3237
  // Struggle (Pokémon Showdown would use 'move 1' here)