@pkmn/engine 0.1.0-dev.6158d5ad → 0.1.0-dev.62399ed5

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 (53) 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/ids.json +12 -12
  6. package/build/pkg/data/layout.json +2 -2
  7. package/build/pkg/data/protocol.json +69 -12
  8. package/build/pkg/data.d.ts +4 -4
  9. package/build/pkg/data.js.map +1 -1
  10. package/build/pkg/gen1.js +2 -2
  11. package/build/pkg/gen1.js.map +1 -1
  12. package/build/pkg/index.d.ts +2 -1
  13. package/build/pkg/index.js.map +1 -1
  14. package/build/pkg/protocol.js +61 -42
  15. package/build/pkg/protocol.js.map +1 -1
  16. package/build/test/display.js +2 -2
  17. package/build/test/display.js.map +1 -1
  18. package/build/tools/debug.js +5 -4
  19. package/build/tools/debug.js.map +1 -1
  20. package/build.zig +102 -104
  21. package/package.json +43 -23
  22. package/src/bin/install-pkmn-engine +26 -18
  23. package/src/lib/common/data.zig +12 -24
  24. package/src/lib/common/debug.zig +2 -2
  25. package/src/lib/common/optional.zig +9 -2
  26. package/src/lib/common/options.zig +2 -0
  27. package/src/lib/common/protocol.zig +802 -327
  28. package/src/lib/common/rational.zig +113 -19
  29. package/src/lib/common/rng.zig +26 -17
  30. package/src/lib/gen1/calc.zig +219 -50
  31. package/src/lib/gen1/chance.zig +140 -125
  32. package/src/lib/gen1/data/moves.zig +192 -180
  33. package/src/lib/gen1/data/species.zig +3 -3
  34. package/src/lib/gen1/data/types.zig +7 -7
  35. package/src/lib/gen1/data.zig +50 -62
  36. package/src/lib/gen1/helpers.zig +148 -82
  37. package/src/lib/gen1/mechanics.zig +403 -417
  38. package/src/lib/gen2/calc.zig +143 -0
  39. package/src/lib/gen2/chance.zig +1315 -0
  40. package/src/lib/gen2/data/items.zig +12 -18
  41. package/src/lib/gen2/data/moves.zig +663 -569
  42. package/src/lib/gen2/data/species.zig +1 -1
  43. package/src/lib/gen2/data/types.zig +134 -31
  44. package/src/lib/gen2/data.zig +106 -79
  45. package/src/lib/gen2/generated.zig +806 -0
  46. package/src/lib/gen2/helpers.zig +45 -19
  47. package/src/lib/gen2/mechanics.zig +3587 -9
  48. package/src/lib/{binding/node.zig → node.zig} +10 -13
  49. package/src/lib/pkmn.zig +27 -12
  50. package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
  51. package/src/test/display/pkmn.html.tmpl +5 -5
  52. package/src/lib/gen1/README.md +0 -892
  53. package/src/lib/gen2/README.md +0 -308
@@ -14,12 +14,14 @@ const util = @import("../common/util.zig");
14
14
  const Player = @import("../common/data.zig").Player;
15
15
  const Optional = @import("../common/optional.zig").Optional;
16
16
 
17
- const Move = @import("data.zig").Move;
18
- const MoveSlot = @import("data.zig").MoveSlot;
17
+ const data = @import("data.zig");
19
18
 
20
19
  const enabled = options.chance;
21
20
  const showdown = options.showdown;
22
21
 
22
+ const Move = data.Move;
23
+ const MoveSlot = data.MoveSlot;
24
+
23
25
  /// Actions taken by a hypothetical "chance player" that convey information about which RNG events
24
26
  /// were observed during a Generation I battle `update`. This can additionally be provided as input
25
27
  /// to the `update` call to override the normal behavior of the RNG in order to force specific
@@ -35,19 +37,19 @@ pub const Actions = extern struct {
35
37
  }
36
38
 
37
39
  /// Returns the `Action` for the given `player`.
38
- pub inline fn get(self: anytype, player: Player) util.PointerType(@TypeOf(self), Action) {
40
+ pub fn get(self: anytype, player: Player) util.PointerType(@TypeOf(self), Action) {
39
41
  assert(@typeInfo(@TypeOf(self)).Pointer.child == Actions);
40
42
  return if (player == .P1) &self.p1 else &self.p2;
41
43
  }
42
44
 
43
45
  /// Perform a reset by clearing fields which should not persist across updates.
44
- pub inline fn reset(self: *Actions) void {
46
+ pub fn reset(self: *Actions) void {
45
47
  self.p1.reset();
46
48
  self.p2.reset();
47
49
  }
48
50
 
49
51
  /// Returns true if `a` is equal to `b`.
50
- pub inline fn eql(a: Actions, b: Actions) bool {
52
+ pub fn eql(a: Actions, b: Actions) bool {
51
53
  return @as(u128, @bitCast(a)) == @as(u128, @bitCast(b));
52
54
  }
53
55
 
@@ -90,13 +92,14 @@ pub const Actions = extern struct {
90
92
  }
91
93
  };
92
94
 
95
+ // TODO: ziglang/zig#19730
93
96
  test Actions {
94
97
  const a: Actions = .{ .p1 = .{ .hit = .true, .critical_hit = .false, .damage = 245 } };
95
98
  const b: Actions = .{ .p1 = .{ .hit = .false, .critical_hit = .true, .damage = 246 } };
96
99
  const c: Actions = .{ .p1 = .{ .hit = .true } };
97
- const d: Actions = .{ .p2 = .{ .hit = .true, .durations = .{ .sleep = 2 } } };
98
- const e: Actions = .{ .p2 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
99
- const f: Actions = .{ .p1 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
100
+ // const d: Actions = .{ .p2 = .{ .hit = .true, .durations = .{ .sleep = 2 } } };
101
+ // const e: Actions = .{ .p2 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
102
+ // const f: Actions = .{ .p1 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
100
103
 
101
104
  try expect(a.eql(a));
102
105
  try expect(!a.eql(b));
@@ -109,13 +112,13 @@ test Actions {
109
112
  try expect(b.matches(a));
110
113
  try expect(!a.matches(c));
111
114
  try expect(!c.matches(a));
112
- try expect(d.matches(e));
113
- try expect(!d.matches(f));
115
+ // try expect(d.matches(e));
116
+ // try expect(!d.matches(f));
114
117
  }
115
118
 
116
119
  /// Information about the RNG that was observed during a Generation I battle `update` for a
117
120
  /// single player.
118
- pub const Action = packed struct {
121
+ pub const Action = packed struct(u64) {
119
122
  /// If not 0, the roll to be returned Rolls.damage.
120
123
  damage: u8 = 0,
121
124
 
@@ -156,12 +159,8 @@ pub const Action = packed struct {
156
159
 
157
160
  pub const Field = std.meta.FieldEnum(Action);
158
161
 
159
- comptime {
160
- assert(@sizeOf(Action) == 8);
161
- }
162
-
163
162
  /// Perform a reset by clearing fields which should not persist across updates.
164
- pub inline fn reset(self: *Action) void {
163
+ pub fn reset(self: *Action) void {
165
164
  self.* = @bitCast(@as(u64, @bitCast(self.*)) & DURATIONS);
166
165
  }
167
166
 
@@ -219,7 +218,7 @@ pub const Action = packed struct {
219
218
  };
220
219
 
221
220
  test Action {
222
- var a = Action{ .hit = .true, .durations = .{ .sleep = 3 }, .damage = 225 };
221
+ var a: Action = .{ .hit = .true, .durations = .{ .sleep = 3 }, .damage = 225 };
223
222
  a.reset();
224
223
 
225
224
  try expectEqual(Action{ .hit = .None, .durations = .{ .sleep = 3 }, .damage = 0 }, a);
@@ -239,7 +238,7 @@ pub const Durations = extern struct {
239
238
 
240
239
  /// Observed values for various durations that need to be tracked in order to properly
241
240
  /// deduplicate transitions with a primary key. TODO
242
- pub const Duration = packed struct {
241
+ pub const Duration = packed struct(u16) {
243
242
  /// The number of turns a Pokémon has been observed to be sleeping.
244
243
  sleep: u3 = 0,
245
244
  /// The number of turns a Pokémon has been observed to be confused.
@@ -252,14 +251,12 @@ pub const Duration = packed struct {
252
251
  binding: u3 = 0,
253
252
 
254
253
  pub const Field = std.meta.FieldEnum(Duration);
255
-
256
- comptime {
257
- assert(@sizeOf(Duration) == 2);
258
- }
259
254
  };
260
255
 
261
256
  pub const Commit = enum { hit, miss, binding };
262
257
 
258
+ const uN = if (options.miss) u8 else u9;
259
+
263
260
  /// Tracks chance actions and their associated probability during a Generation I battle update when
264
261
  /// `options.chance` is enabled.
265
262
  pub fn Chance(comptime Rational: type) type {
@@ -296,15 +293,12 @@ pub fn Chance(comptime Rational: type) type {
296
293
  //
297
294
  // Finally, because each player's actions are processed sequentially we only need a single
298
295
  // shared structure to track this information (though it needs to be cleared appropriately)
299
- pending: if (showdown) struct {
300
- hit: bool = false,
301
- hit_probablity: u8 = 0,
302
- } else struct {
296
+ pending: if (showdown) struct {} else struct {
303
297
  crit: bool = false,
304
298
  crit_probablity: u8 = 0,
305
299
  damage_roll: u8 = 0,
306
300
  hit: bool = false,
307
- hit_probablity: u8 = 0,
301
+ hit_probablity: uN = 0,
308
302
  binding: u8 = 0,
309
303
  } = .{},
310
304
 
@@ -313,25 +307,29 @@ pub fn Chance(comptime Rational: type) type {
313
307
 
314
308
  /// Convenience helper to clear fields which typically should be cleared between updates.
315
309
  pub fn reset(self: *Self) void {
310
+ if (!enabled) return;
311
+
316
312
  self.probability.reset();
317
313
  self.actions.reset();
318
314
  self.pending = .{};
319
315
  }
320
316
 
321
317
  pub fn commit(self: *Self, player: Player, kind: Commit) Error!void {
318
+ if (!enabled) return;
319
+
320
+ assert(!showdown);
321
+
322
322
  var action = self.actions.get(player);
323
323
 
324
324
  // We need to handle binding specially because we still need to commit it when attacking
325
325
  // into a Pokémon that is immune
326
- if (!showdown) {
327
- if (kind != .miss and self.pending.binding != 0) {
328
- assert(action.duration == 0);
329
- action.duration = @intCast(self.pending.binding);
330
- assert(action.durations.binding == 0);
331
- action.durations.binding = 1;
332
- }
333
- if (kind == .binding) return;
334
- } else assert(kind != .binding);
326
+ if (kind != .miss and self.pending.binding != 0) {
327
+ assert(action.duration == 0);
328
+ action.duration = @intCast(self.pending.binding);
329
+ assert(action.durations.binding == 0);
330
+ action.durations.binding = 1;
331
+ }
332
+ if (kind == .binding) return;
335
333
 
336
334
  // Always commit the hit result if we make it here (commit won't be called at all if the
337
335
  // target is immune/behind a sub/etc)
@@ -340,19 +338,19 @@ pub fn Chance(comptime Rational: type) type {
340
338
  action.hit = if (self.pending.hit) .true else .false;
341
339
  }
342
340
 
343
- if (showdown) return;
341
+ // If the move actually lands we can commit any past critical hit / damage rolls. Note
342
+ // we can't rely on the presence or absence of a damage roll to determine whether a
343
+ // "critical hit" was spurious or not as moves that do 1 or less damage don't rol for
344
+ // damage but can still crit (instead, whether or not a roll is deemed to be a no-op is
345
+ // passed to the critical hit helper)
346
+ if (kind != .hit) return assert(!self.pending.crit or self.pending.crit_probablity > 0);
344
347
 
345
- // If the move actually lands we can commit any past critical hit / damage rolls. We
346
- // avoid updating anything if there wasn't a damage roll as any "critical hit" not tied
347
- // to a damage roll is actually a no-op
348
- if (kind == .hit and self.pending.damage_roll > 0) {
349
- assert(!self.pending.crit or self.pending.crit_probablity > 0);
350
-
351
- if (self.pending.crit_probablity != 0) {
352
- try self.probability.update(self.pending.crit_probablity, 256);
353
- action.critical_hit = if (self.pending.crit) .true else .false;
354
- }
348
+ if (self.pending.crit_probablity != 0) {
349
+ try self.probability.update(self.pending.crit_probablity, 256);
350
+ action.critical_hit = if (self.pending.crit) .true else .false;
351
+ }
355
352
 
353
+ if (self.pending.damage_roll > 0) {
356
354
  try self.probability.update(1, 39);
357
355
  action.damage = self.pending.damage_roll;
358
356
  }
@@ -403,12 +401,17 @@ pub fn Chance(comptime Rational: type) type {
403
401
  self.actions.p2.speed_tie = self.actions.p1.speed_tie;
404
402
  }
405
403
 
406
- pub fn hit(self: *Self, ok: bool, accuracy: u8) void {
404
+ pub fn hit(self: *Self, player: Player, ok: bool, accuracy: uN) Error!void {
407
405
  if (!enabled) return;
408
406
 
409
407
  const p = if (ok) accuracy else @as(u8, @intCast(256 - @as(u9, accuracy)));
410
- self.pending.hit = ok;
411
- self.pending.hit_probablity = p;
408
+ if (showdown) {
409
+ try self.probability.update(p, 256);
410
+ self.actions.get(player).hit = if (ok) .true else .false;
411
+ } else {
412
+ self.pending.hit = ok;
413
+ self.pending.hit_probablity = p;
414
+ }
412
415
  }
413
416
 
414
417
  pub fn criticalHit(self: *Self, player: Player, crit: bool, rate: u8) Error!void {
@@ -522,22 +525,6 @@ pub fn Chance(comptime Rational: type) type {
522
525
  }
523
526
  }
524
527
 
525
- pub fn disable(self: *Self, player: Player, turns: u4) Error!void {
526
- if (!enabled) return;
527
-
528
- var durations = &self.actions.get(player).durations;
529
- const n = durations.disable;
530
- if (turns == 0) {
531
- assert(n >= 1 and n <= 8);
532
- if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
533
- durations.disable = 0;
534
- } else {
535
- assert(n >= 1 and n < 8);
536
- try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
537
- durations.disable += 1;
538
- }
539
- }
540
-
541
528
  pub fn confusion(self: *Self, player: Player, turns: u4) Error!void {
542
529
  if (!enabled) return;
543
530
 
@@ -554,6 +541,22 @@ pub fn Chance(comptime Rational: type) type {
554
541
  }
555
542
  }
556
543
 
544
+ pub fn disable(self: *Self, player: Player, turns: u4) Error!void {
545
+ if (!enabled) return;
546
+
547
+ var durations = &self.actions.get(player).durations;
548
+ const n = durations.disable;
549
+ if (turns == 0) {
550
+ assert(n >= 1 and n <= 8);
551
+ if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
552
+ durations.disable = 0;
553
+ } else {
554
+ assert(n >= 1 and n < 8);
555
+ try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
556
+ durations.disable += 1;
557
+ }
558
+ }
559
+
557
560
  pub fn attacking(self: *Self, player: Player, turns: u4) Error!void {
558
561
  if (!enabled) return;
559
562
 
@@ -601,7 +604,7 @@ pub fn Chance(comptime Rational: type) type {
601
604
  pub fn metronome(self: *Self, player: Player, move: Move) Error!void {
602
605
  if (!enabled) return;
603
606
 
604
- try self.probability.update(1, Move.size - 2);
607
+ try self.probability.update(1, Move.METRONOME.len);
605
608
  self.actions.get(player).metronome = move;
606
609
  }
607
610
  };
@@ -626,19 +629,25 @@ test "Chance.speedTie" {
626
629
  test "Chance.hit" {
627
630
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
628
631
 
629
- chance.hit(true, 229);
630
- try expectProbability(&chance.probability, 1, 1);
631
- try expectValue(Optional(bool).None, chance.actions.p1.hit);
632
- try chance.commit(.P1, .hit);
632
+ try chance.hit(.P1, true, 229);
633
+ if (!showdown) {
634
+ try expectProbability(&chance.probability, 1, 1);
635
+ try expectValue(Optional(bool).None, chance.actions.p1.hit);
636
+
637
+ try chance.commit(.P1, .hit);
638
+ }
633
639
  try expectValue(Optional(bool).true, chance.actions.p1.hit);
634
640
  try expectProbability(&chance.probability, 229, 256);
635
641
 
636
642
  chance.reset();
637
643
 
638
- chance.hit(false, 255);
639
- try expectProbability(&chance.probability, 1, 1);
640
- try expectValue(Optional(bool).None, chance.actions.p2.hit);
641
- try chance.commit(.P2, .miss);
644
+ try chance.hit(.P2, false, 255);
645
+ if (!showdown) {
646
+ try expectProbability(&chance.probability, 1, 1);
647
+ try expectValue(Optional(bool).None, chance.actions.p2.hit);
648
+
649
+ try chance.commit(.P2, .miss);
650
+ }
642
651
  try expectValue(Optional(bool).false, chance.actions.p2.hit);
643
652
  try expectProbability(&chance.probability, 1, 256);
644
653
  }
@@ -654,26 +663,18 @@ test "Chance.criticalHit" {
654
663
  try expectProbability(&chance.probability, 1, 1);
655
664
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
656
665
 
657
- try chance.commit(.P1, .hit);
658
- try expectProbability(&chance.probability, 1, 1);
659
- try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
660
-
661
- chance.hit(true, 128);
666
+ try chance.hit(.P1, true, 128);
662
667
 
663
- try chance.commit(.P1, .hit);
664
- try expectProbability(&chance.probability, 1, 2);
668
+ try expectProbability(&chance.probability, 1, 1);
665
669
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
666
670
 
667
- chance.probability.reset();
668
671
  try chance.damage(.P1, 217);
669
672
 
670
- try chance.commit(.P1, .miss);
671
- try expectProbability(&chance.probability, 1, 2);
673
+ try expectProbability(&chance.probability, 1, 1);
672
674
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
673
675
 
674
- chance.probability.reset();
675
-
676
676
  try chance.commit(.P1, .hit);
677
+
677
678
  try expectProbability(&chance.probability, 17, 19968); // (1/2) * (17/256) * (1/39)
678
679
  try expectValue(Optional(bool).true, chance.actions.p1.critical_hit);
679
680
  }
@@ -688,20 +689,14 @@ test "Chance.criticalHit" {
688
689
  try expectProbability(&chance.probability, 1, 1);
689
690
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
690
691
 
691
- try chance.commit(.P1, .hit);
692
- try expectProbability(&chance.probability, 1, 1);
693
- try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
694
-
695
- chance.hit(true, 128);
692
+ try chance.hit(.P1, true, 128);
696
693
 
697
- try chance.commit(.P1, .hit);
698
- try expectProbability(&chance.probability, 1, 2);
694
+ try expectProbability(&chance.probability, 1, 1);
699
695
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
700
696
 
701
- chance.probability.reset();
702
697
  try chance.damage(.P1, 217);
703
-
704
698
  try chance.commit(.P1, .hit);
699
+
705
700
  try expectProbability(&chance.probability, 251, 19968); // (1/2) * (251/256) * (1/39)
706
701
  try expectValue(Optional(bool).false, chance.actions.p1.critical_hit);
707
702
  }
@@ -712,19 +707,16 @@ test "Chance.damage" {
712
707
 
713
708
  try chance.damage(.P1, 217);
714
709
  if (showdown) {
715
- try expectValue(@as(u8, 217), chance.actions.p1.damage);
710
+ try expectValue(217, chance.actions.p1.damage);
716
711
  try expectProbability(&chance.probability, 1, 39);
717
712
  } else {
718
713
  try expectProbability(&chance.probability, 1, 1);
719
- try expectValue(@as(u8, 0), chance.actions.p1.damage);
720
-
721
- try chance.commit(.P1, .miss);
722
- try expectProbability(&chance.probability, 1, 1);
723
- try expectValue(@as(u8, 0), chance.actions.p1.damage);
714
+ try expectValue(0, chance.actions.p1.damage);
724
715
 
725
716
  try chance.commit(.P1, .hit);
717
+
726
718
  try expectProbability(&chance.probability, 1, 39);
727
- try expectValue(@as(u8, 217), chance.actions.p1.damage);
719
+ try expectValue(217, chance.actions.p1.damage);
728
720
  }
729
721
  }
730
722
 
@@ -776,13 +768,13 @@ test "Chance.moveSlot" {
776
768
 
777
769
  try chance.moveSlot(.P2, 2, &ms, 2);
778
770
  try expectProbability(&chance.probability, 1, 2);
779
- try expectValue(@as(u4, 2), chance.actions.p2.move_slot);
771
+ try expectValue(2, chance.actions.p2.move_slot);
780
772
 
781
773
  chance.reset();
782
774
 
783
775
  try chance.moveSlot(.P1, 1, &ms, 0);
784
776
  try expectProbability(&chance.probability, 1, 3);
785
- try expectValue(@as(u4, 1), chance.actions.p1.move_slot);
777
+ try expectValue(1, chance.actions.p1.move_slot);
786
778
  }
787
779
 
788
780
  test "Chance.multiHit" {
@@ -790,32 +782,33 @@ test "Chance.multiHit" {
790
782
 
791
783
  try chance.multiHit(.P1, 3);
792
784
  try expectProbability(&chance.probability, 3, 8);
793
- try expectValue(@as(u4, 3), chance.actions.p1.multi_hit);
785
+ try expectValue(3, chance.actions.p1.multi_hit);
794
786
 
795
787
  chance.reset();
796
788
 
797
789
  try chance.multiHit(.P2, 5);
798
790
  try expectProbability(&chance.probability, 1, 8);
799
- try expectValue(@as(u4, 5), chance.actions.p2.multi_hit);
791
+ try expectValue(5, chance.actions.p2.multi_hit);
800
792
  }
801
793
 
802
794
  test "Chance.duration" {
803
795
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
804
796
 
805
797
  chance.duration(.sleep, .P1, .P2, 2);
806
- try expectValue(@as(u4, 2), chance.actions.p1.duration);
807
- try expectValue(@as(u3, 1), chance.actions.p2.durations.sleep);
798
+ try expectValue(2, chance.actions.p1.duration);
799
+ try expectValue(1, chance.actions.p2.durations.sleep);
808
800
 
809
801
  chance.reset();
810
802
 
811
803
  chance.duration(.binding, .P2, .P2, 4);
812
804
  if (!showdown) {
813
- try expectValue(@as(u4, 0), chance.actions.p2.duration);
814
- try expectValue(@as(u3, 0), chance.actions.p2.durations.binding);
805
+ try expectValue(0, chance.actions.p2.duration);
806
+ try expectValue(0, chance.actions.p2.durations.binding);
807
+
815
808
  try chance.commit(.P2, .hit);
816
809
  }
817
- try expectValue(@as(u4, 4), chance.actions.p2.duration);
818
- try expectValue(@as(u3, 1), chance.actions.p2.durations.binding);
810
+ try expectValue(4, chance.actions.p2.duration);
811
+ try expectValue(1, chance.actions.p2.durations.binding);
819
812
  }
820
813
 
821
814
  test "Chance.sleep" {
@@ -825,7 +818,7 @@ test "Chance.sleep" {
825
818
  chance.actions.p1.durations.sleep = @intCast(i);
826
819
  try chance.sleep(.P1, 0);
827
820
  try expectProbability(&chance.probability, 1, d);
828
- try expectValue(@as(u3, 0), chance.actions.p1.durations.sleep);
821
+ try expectValue(0, chance.actions.p1.durations.sleep);
829
822
 
830
823
  chance.reset();
831
824
 
@@ -848,7 +841,7 @@ test "Chance.confusion" {
848
841
  chance.actions.p2.durations.confusion = @intCast(i);
849
842
  try chance.confusion(.P2, 0);
850
843
  try expectProbability(&chance.probability, 1, d);
851
- try expectValue(@as(u3, 0), chance.actions.p2.durations.confusion);
844
+ try expectValue(0, chance.actions.p2.durations.confusion);
852
845
 
853
846
  chance.reset();
854
847
  }
@@ -864,6 +857,28 @@ test "Chance.confusion" {
864
857
  }
865
858
  }
866
859
 
860
+ test "Chance.disable" {
861
+ var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
862
+
863
+ for ([_]u8{ 8, 7, 6, 5, 4, 3, 2, 1 }, 1..9) |d, i| {
864
+ chance.actions.p1.durations.disable = @intCast(i);
865
+ try chance.disable(.P1, 0);
866
+ try expectProbability(&chance.probability, 1, d);
867
+ try expectValue(0, chance.actions.p1.durations.disable);
868
+
869
+ chance.reset();
870
+
871
+ if (i < 8) {
872
+ chance.actions.p1.durations.disable = @intCast(i);
873
+ try chance.disable(.P1, 1);
874
+ try expectProbability(&chance.probability, d - 1, d);
875
+ try expectValue(@as(u4, @intCast(i)) + 1, chance.actions.p1.durations.disable);
876
+
877
+ chance.reset();
878
+ }
879
+ }
880
+ }
881
+
867
882
  test "Chance.attacking" {
868
883
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
869
884
 
@@ -872,7 +887,7 @@ test "Chance.attacking" {
872
887
  chance.actions.p2.durations.attacking = @intCast(i);
873
888
  try chance.attacking(.P2, 0);
874
889
  try expectProbability(&chance.probability, 1, d);
875
- try expectValue(@as(u3, 0), chance.actions.p2.durations.attacking);
890
+ try expectValue(0, chance.actions.p2.durations.attacking);
876
891
 
877
892
  chance.reset();
878
893
  }
@@ -898,7 +913,7 @@ test "Chance.binding" {
898
913
  chance.actions.p1.durations.binding = @intCast(i);
899
914
  try chance.binding(.P1, 0);
900
915
  try expectProbability(&chance.probability, p, q);
901
- try expectValue(@as(u3, 0), chance.actions.p1.durations.binding);
916
+ try expectValue(0, chance.actions.p1.durations.binding);
902
917
 
903
918
  chance.reset();
904
919
 
@@ -918,7 +933,7 @@ test "Chance.psywave" {
918
933
 
919
934
  try chance.psywave(.P2, 100, 150);
920
935
  try expectProbability(&chance.probability, 1, 150);
921
- try expectValue(@as(u8, 101), chance.actions.p2.psywave);
936
+ try expectValue(101, chance.actions.p2.psywave);
922
937
  }
923
938
 
924
939
  test "Chance.metronome" {
@@ -947,7 +962,7 @@ pub fn expectValue(a: anytype, b: anytype) !void {
947
962
 
948
963
  /// Null object pattern implementation of Generation I `Chance` which does nothing, though chance
949
964
  /// tracking should additionally be turned off entirely via `options.chance`.
950
- pub const NULL = Null{};
965
+ pub const NULL: Null = .{};
951
966
 
952
967
  const Null = struct {
953
968
  pub const Error = error{};
@@ -972,8 +987,8 @@ const Null = struct {
972
987
  _ = .{ self, p1 };
973
988
  }
974
989
 
975
- pub fn hit(self: Null, ok: bool, accuracy: u8) void {
976
- _ = .{ self, ok, accuracy };
990
+ pub fn hit(self: Null, player: Player, ok: bool, accuracy: uN) Error!void {
991
+ _ = .{ self, player, ok, accuracy };
977
992
  }
978
993
 
979
994
  pub fn criticalHit(self: Null, player: Player, crit: bool, rate: u8) Error!void {
@@ -984,10 +999,6 @@ const Null = struct {
984
999
  _ = .{ self, player, roll };
985
1000
  }
986
1001
 
987
- pub fn secondaryChance(self: Null, player: Player, proc: bool, rate: u8) Error!void {
988
- _ = .{ self, player, proc, rate };
989
- }
990
-
991
1002
  pub fn confused(self: Null, player: Player, ok: bool) Error!void {
992
1003
  _ = .{ self, player, ok };
993
1004
  }
@@ -996,6 +1007,10 @@ const Null = struct {
996
1007
  _ = .{ self, player, ok };
997
1008
  }
998
1009
 
1010
+ pub fn secondaryChance(self: Null, player: Player, proc: bool, rate: u8) Error!void {
1011
+ _ = .{ self, player, proc, rate };
1012
+ }
1013
+
999
1014
  pub fn moveSlot(self: Null, player: Player, slot: u4, ms: []const MoveSlot, n: u4) Error!void {
1000
1015
  _ = .{ self, player, slot, ms, n };
1001
1016
  }