@pkmn/engine 0.1.0-dev.83de29ee → 0.1.0-dev.8b83af6b

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.
@@ -47,7 +47,7 @@ pub const PSRNG = extern struct {
47
47
  }
48
48
 
49
49
  pub fn range(self: *PSRNG, comptime T: type, from: T, to: Bound(T)) T {
50
- return @intCast(T, @as(u64, self.src.next()) * (to - from) / divisor + from);
50
+ return @intCast(@as(u64, self.src.next()) * (to - from) / divisor + from);
51
51
  }
52
52
 
53
53
  pub fn chance(self: *PSRNG, comptime T: type, numerator: T, denominator: Bound(T)) bool {
@@ -80,7 +80,7 @@ pub const Gen12 = extern struct {
80
80
  }
81
81
 
82
82
  pub fn percent(p: u8) u8 {
83
- return @intCast(u8, (@as(u16, p) * 0xFF) / 100);
83
+ return @intCast((@as(u16, p) * 0xFF) / 100);
84
84
  }
85
85
 
86
86
  pub fn next(self: *Gen12) u8 {
@@ -114,7 +114,7 @@ pub const Gen34 = extern struct {
114
114
 
115
115
  pub fn next(self: *Gen34) u16 {
116
116
  self.advance();
117
- return @intCast(u16, self.seed >> 16);
117
+ return @intCast(self.seed >> 16);
118
118
  }
119
119
 
120
120
  pub fn advance(self: *Gen34) void {
@@ -144,7 +144,7 @@ pub const Gen56 = extern struct {
144
144
 
145
145
  pub fn next(self: *Gen56) u32 {
146
146
  self.advance();
147
- return @intCast(u32, self.seed >> 32);
147
+ return @intCast(self.seed >> 32);
148
148
  }
149
149
 
150
150
  pub fn advance(self: *Gen56) void {
@@ -190,7 +190,7 @@ pub fn FixedRNG(comptime gen: comptime_int, comptime len: usize) type {
190
190
 
191
191
  pub fn range(self: *Self, comptime T: type, from: T, to: Bound(T)) T {
192
192
  assert(showdown);
193
- return @intCast(T, @as(u64, self.next()) * (to - from) / divisor + from);
193
+ return @intCast(@as(u64, self.next()) * (to - from) / divisor + from);
194
194
  }
195
195
 
196
196
  pub fn chance(
@@ -352,13 +352,16 @@ data in the browser for ease of debugging.
352
352
  | 373-376 | 384 | `rng` | The RNG state |
353
353
 
354
354
  - the current `turn` is 2 bytes, written in native-endianness
355
- - `last_moves` layout depends on whether or not Pokémon Showdown compatibility mode is
356
- enabled (`-Dshowdown`). In either case it stores a move "slot", where 0 is used as an empty value and
357
- 1-4 represent actual moves:
358
- - if `showdown` is enabled bytes 372 and 373 are used to store the last selected move index of
359
- `side[0]` and bytes 374 and 375 store the last selected move index of `side[1]`
360
- - otherwise byte 372 stores the last selected move index of `side[0]` in its first 4 bits and
361
- `side[1]`'s in its second 4 bits
355
+ - `last_moves` layout depends on whether or not Pokémon Showdown compatibility mode is enabled
356
+ (`-Dshowdown`). In either case it stores the last selected move index and last executed move for
357
+ each player (required to detect desyncs). In the case of the move index, 0 is used as an empty
358
+ value and 1-4 represent actual moves:
359
+ - if `showdown` is enabled byte 372 is used to store the last selected move index of `side[0]` and
360
+ byte 373 is used to store whether or not the side's last executed move would have been considered
361
+ "counterable"; and bytes 374 and 375 store the same information for `side[1]`
362
+ - otherwise the first 3 bits of byte 372 stores the last selected move index of `side[0]` and the
363
+ next bit stores whether that side's last executed move would have been considered "counterable",
364
+ with the same information for `side[1]`'s being stored in the second 4 bits
362
365
  - the `rng` depends on whether or not Pokémon Showdown compatibility mode is enabled (`-Dshowdown`):
363
366
  - if `showdown` is enabled, the RNG state begins on byte 376 and consists of a 64-bit seed,
364
367
  written in native-endianness
@@ -489,8 +492,14 @@ Beyond the general bugs listed above, several move effects are implemented incor
489
492
  Showdown. Some of these moves are [too fundamentally broken to be implemented](#unimplementable) by
490
493
  the pkmn engine, but the following moves have their broken behavior preserved in `-Dshowdown` mode:
491
494
 
495
+ - **Bide**: Bide's damage can overflow if OHKO moves are involved because OHKO moves work by setting
496
+ the damage to 65535 - Pokémon Showdown doesn't implement this overflow and instead lets Bide's
497
+ damage grow unbounded. Additionally, if the opponent faints after Bide inflicts damage on Pokémon
498
+ Showdown residual damage incorrectly still gets applied to Bide's user.
492
499
  - **Counter**: On Pokémon Showdown choices made while sleeping (which should not have been
493
- registered) can erroneously cause Counter to trigger Desync Clause Mod behavior.
500
+ registered) can erroneously cause Counter to trigger Desync Clause Mod behavior. Additionally,
501
+ because Pokémon Showdown neglects to zero the last battle damage if a move misses due to immunity
502
+ or invulnerability Counter will occasionally work on Pokémon Showdown when it should fail.
494
503
  - **Leech Seed**: Leech Seed fails to heal its source side if a seeded target faints due to
495
504
  recoil/crash damage on Pokémon Showdown.
496
505
  - **Pay Day**: Pay Day should still scatter coins if it hits (but doesn't break) and opponent's
@@ -601,10 +610,6 @@ Showdown's behavior for the following moves (the pkmn engine attempts to reprodu
601
610
  behavior that can be reproduced without making data structure changes or dramatically deviating from
602
611
  the correct control flow):
603
612
 
604
- - **Bide**: Bide's damage can overflow if OHKO moves are involved because OHKO moves work by setting
605
- the damage to 65535 - Pokémon Showdown doesn't implement this overflow and instead lets Bide's
606
- damage grow unbounded. Additionally, if the opponent faints after Bide inflicts damage on Pokémon
607
- Showdown residual damage incorrectly still gets applied to Bide's user.
608
613
  - **Wrap**: Binding moves like Wrap are implemented on Pokémon Showdown with an artificial
609
614
  `partialtrappinglock` volatile as opposed to how it works on the cartridge which simply relies on
610
615
  the `Binding` volatile of the opponent. This mistake results in choice locking not being reported
@@ -623,7 +628,8 @@ the correct control flow):
623
628
  because Pokémon Showdown deducts PP based on a move's name instead of slot, if Mimic copies a move
624
629
  the Pokémon already knows, PP deduction for using either the original move of the mimicked move
625
630
  will instead deduct PP for whichever appears at the lower move slot index and the PP will be
626
- allowed to go negative (effectively allowing for infinite PP use).
631
+ allowed to go negative (effectively allowing for infinite PP use). Pokémon Showdown also doesn't
632
+ deduct PP from using Transform if it was copied by Mimic.
627
633
  - **Mirror Move**: As covered above, both Substitute and Binding moves misbehave when used via
628
634
  Mirror Move (though Pokémon Showdown has its own weird behavior and does not implement the
629
635
  [partial trapping move Mirror Move
@@ -640,7 +646,7 @@ the correct control flow):
640
646
  specific *slot* (e.g. the 2nd move slot), and a Pokémon's moves can change after Transform (this
641
647
  is not an issue with Disable + Mimic because Mimic happens to replace the same slot). Transform
642
648
  also has a bad interaction with Pokémon Showdown's buggy Haze implementation, as Haze on Pokémon
643
- Showdown does not copy unmodified stats, leaving the Transformed Pokémon with inccorect stats.
649
+ Showdown does not copy unmodified stats, leaving the Transformed Pokémon with incorrect stats.
644
650
  Furthermore, transforming and then using Mirror Move / Metronome can result in [glitchy behavior
645
651
  and
646
652
  softlocks](https://pkmn.cc/bulba/Transform_glitches#Transform_.2B_Mirror_Move.2FMetronome_PP_error)
@@ -1602,44 +1602,44 @@ pub const Move = enum(u8) {
1602
1602
 
1603
1603
  /// Whether this effect activates during the "begin" step of move execution.
1604
1604
  pub inline fn onBegin(effect: Effect) bool {
1605
- return @enumToInt(effect) > 0 and @enumToInt(effect) <= 16;
1605
+ return @intFromEnum(effect) > 0 and @intFromEnum(effect) <= 16;
1606
1606
  }
1607
1607
 
1608
1608
  /// Whether this effect lowers stats.
1609
1609
  pub inline fn isStatDown(effect: Effect) bool {
1610
- return @enumToInt(effect) > 16 and @enumToInt(effect) <= 21;
1610
+ return @intFromEnum(effect) > 16 and @intFromEnum(effect) <= 21;
1611
1611
  }
1612
1612
 
1613
1613
  /// Whether this effect activates during the "end" step of move execution.
1614
1614
  pub inline fn onEnd(effect: Effect) bool {
1615
- return @enumToInt(effect) > 16 and @enumToInt(effect) <= 31;
1615
+ return @intFromEnum(effect) > 16 and @intFromEnum(effect) <= 31;
1616
1616
  }
1617
1617
 
1618
1618
  /// Whether this effect is considered to "always happen".
1619
1619
  pub inline fn alwaysHappens(effect: Effect) bool {
1620
- return @enumToInt(effect) > 31 and @enumToInt(effect) <= 38;
1620
+ return @intFromEnum(effect) > 31 and @intFromEnum(effect) <= 38;
1621
1621
  }
1622
1622
 
1623
1623
  /// Whether this effect is handled specially by the engine.
1624
1624
  pub inline fn isSpecial(effect: Effect) bool {
1625
1625
  // NB: isSpecial includes isMulti up to Twineedle
1626
- return @enumToInt(effect) > 31 and @enumToInt(effect) <= 46;
1626
+ return @intFromEnum(effect) > 31 and @intFromEnum(effect) <= 46;
1627
1627
  }
1628
1628
 
1629
1629
  /// Whether this effect is a multi-hit effect.
1630
1630
  pub inline fn isMulti(effect: Effect) bool {
1631
- return @enumToInt(effect) > 44 and @enumToInt(effect) <= 47;
1631
+ return @intFromEnum(effect) > 44 and @intFromEnum(effect) <= 47;
1632
1632
  }
1633
1633
 
1634
1634
  /// Whether this effect is has chance of lowering stats.
1635
1635
  pub inline fn isStatDownChance(effect: Effect) bool {
1636
- return @enumToInt(effect) > 47 and @enumToInt(effect) <= 51;
1636
+ return @intFromEnum(effect) > 47 and @intFromEnum(effect) <= 51;
1637
1637
  }
1638
1638
 
1639
1639
  /// Whether this effect has a secondary chance.
1640
1640
  pub inline fn isSecondaryChance(effect: Effect) bool {
1641
1641
  // NB: isSecondaryChance includes isStatDownChance as well as Twineedle
1642
- return (@enumToInt(effect) > 46 and @enumToInt(effect) <= 61);
1642
+ return (@intFromEnum(effect) > 46 and @intFromEnum(effect) <= 61);
1643
1643
  }
1644
1644
  };
1645
1645
 
@@ -1843,12 +1843,12 @@ pub const Move = enum(u8) {
1843
1843
  /// Returns the `Data` corresponding to the move.
1844
1844
  pub inline fn get(id: Move) Data {
1845
1845
  assert(id != .None and id != .SKIP_TURN);
1846
- return DATA[@enumToInt(id) - 1];
1846
+ return DATA[@intFromEnum(id) - 1];
1847
1847
  }
1848
1848
 
1849
1849
  /// Returns the base PP of the move.
1850
1850
  pub fn pp(id: Move) u8 {
1851
1851
  assert(id != .None);
1852
- return PP[@enumToInt(id) - 1];
1852
+ return PP[@intFromEnum(id) - 1];
1853
1853
  }
1854
1854
  };
@@ -1094,12 +1094,12 @@ pub const Species = enum(u8) {
1094
1094
  /// The Pokémon's critical hit rate=io out of 256.
1095
1095
  pub inline fn chance(id: Species) u8 {
1096
1096
  assert(id != .None);
1097
- return CHANCES[@enumToInt(id) - 1];
1097
+ return CHANCES[@intFromEnum(id) - 1];
1098
1098
  }
1099
1099
 
1100
1100
  /// Returns the `Data` corresponding to the species.
1101
1101
  pub fn get(id: Species) Data {
1102
1102
  assert(id != .None);
1103
- return DATA[@enumToInt(id) - 1];
1103
+ return DATA[@intFromEnum(id) - 1];
1104
1104
  }
1105
1105
  };
@@ -91,18 +91,18 @@ pub const Type = enum(u4) {
91
91
 
92
92
  /// Whether or not this type is considered to be special as opposed to physical.
93
93
  pub inline fn special(self: Type) bool {
94
- return @enumToInt(self) >= @enumToInt(Type.Fire);
94
+ return @intFromEnum(self) >= @intFromEnum(Type.Fire);
95
95
  }
96
96
 
97
97
  /// The `Effectiveness` of type `t2` vs. type `t1`.
98
98
  pub inline fn effectiveness(t1: Type, t2: Type) Effectiveness {
99
- return CHART[@enumToInt(t1)][@enumToInt(t2)];
99
+ return CHART[@intFromEnum(t1)][@intFromEnum(t2)];
100
100
  }
101
101
 
102
102
  /// The precedence order of type `t2` vs. type `t1`.
103
103
  pub fn precedence(t1: Type, t2: Type) u8 {
104
104
  for (PRECEDENCE, 0..) |matchup, i| {
105
- if (matchup.type1 == t1 and matchup.type2 == t2) return @intCast(u8, i);
105
+ if (matchup.type1 == t1 and matchup.type2 == t2) return @intCast(i);
106
106
  }
107
107
  unreachable;
108
108
  }
@@ -55,24 +55,24 @@ pub fn Battle(comptime RNG: anytype) type {
55
55
  turn: u16 = 0,
56
56
  /// The last damage dealt by either side.
57
57
  last_damage: u16 = 0,
58
- /// TODO
58
+ /// Details about the last move selected/used by each player required to detect desyncs.
59
59
  last_moves: MoveDetails = .{},
60
60
  /// The pseudo random number generator.
61
61
  rng: RNG,
62
62
 
63
63
  /// Returns the `Side` for the given `player`.
64
64
  pub inline fn side(self: *Self, player: Player) *Side {
65
- return &self.sides[@enumToInt(player)];
65
+ return &self.sides[@intFromEnum(player)];
66
66
  }
67
67
 
68
68
  /// Returns the `Side` of the opponent for the given `player`
69
69
  pub inline fn foe(self: *Self, player: Player) *Side {
70
- return &self.sides[@enumToInt(player.foe())];
70
+ return &self.sides[@intFromEnum(player.foe())];
71
71
  }
72
72
 
73
73
  /// Returns an identifier for the active Pokémon of `player`.
74
74
  pub inline fn active(self: *Self, player: Player) ID {
75
- return player.ident(@intCast(u3, self.side(player).order[0]));
75
+ return player.ident(@intCast(self.side(player).order[0]));
76
76
  }
77
77
 
78
78
  /// Returns the result of applying Player 1's choice `c1` and Player 2's choice `c2` to the
@@ -201,7 +201,7 @@ pub const MoveSlot = extern struct {
201
201
  }
202
202
  };
203
203
 
204
- /// TODO
204
+ /// Details required to detect desyncs based on the move last selected/executed by players.
205
205
  pub const MoveDetails = packed struct {
206
206
  /// The move slot index of the last move selected in the battle menu by Player 1
207
207
  p1_index: if (showdown) u8 else u3 = 0,
@@ -243,13 +243,13 @@ pub const Status = enum(u8) {
243
243
  /// Whether or not the status `num` is the same as `status`.
244
244
  pub inline fn is(num: u8, status: Status) bool {
245
245
  if (status == .SLP) return Status.duration(num) > 0;
246
- return ((num >> @intCast(u3, @enumToInt(status))) & 1) != 0;
246
+ return ((num >> @intCast(@intFromEnum(status))) & 1) != 0;
247
247
  }
248
248
 
249
249
  /// Initializes a non-sleep `status`. Use `slp` or `slf` to initialize a sleep status.
250
250
  pub inline fn init(status: Status) u8 {
251
251
  assert(status != .SLP and status != .EXT);
252
- return @as(u8, 1) << @intCast(u3, @enumToInt(status));
252
+ return @as(u8, 1) << @intCast(@intFromEnum(status));
253
253
  }
254
254
 
255
255
  /// Initializes a non self-inflicted sleep status with duration `dur`.
@@ -266,7 +266,7 @@ pub const Status = enum(u8) {
266
266
 
267
267
  /// Returns the duration of a sleep status.
268
268
  pub inline fn duration(num: u8) u3 {
269
- return @intCast(u3, num & SLP);
269
+ return @intCast(num & SLP);
270
270
  }
271
271
 
272
272
  /// Returns whether `num` reflects any status.
@@ -413,10 +413,11 @@ pub fn Stats(comptime T: type) type {
413
413
  /// Computes the value of `stat` given a `base`, `dv`, stat `exp` and `level`.
414
414
  pub fn calc(comptime stat: []const u8, base: T, dv: u4, exp: u16, level: u8) T {
415
415
  assert(level > 0 and level <= 100);
416
- const evs = @min(255, @floatToInt(u16, @ceil(@sqrt(@intToFloat(f32, exp)))));
416
+ const float: f32 = @floatFromInt(exp);
417
+ const evs = @min(255, @as(u16, @intFromFloat(@ceil(@sqrt(float)))));
417
418
  const core: u32 = (2 *% (@as(u32, base) +% dv)) +% (evs / 4);
418
419
  const factor: u32 = if (std.mem.eql(u8, stat, "hp")) level + 10 else 5;
419
- return @intCast(T, core *% @as(u32, level) / 100 +% factor);
420
+ return @intCast(core *% @as(u32, level) / 100 +% factor);
420
421
  }
421
422
  };
422
423
  }
@@ -465,7 +466,7 @@ test Boosts {
465
466
  pub const Move = moves.Move;
466
467
 
467
468
  test Move {
468
- try expectEqual(2, @enumToInt(Move.KarateChop));
469
+ try expectEqual(2, @intFromEnum(Move.KarateChop));
469
470
  const move = Move.get(.Fissure);
470
471
  try expectEqual(Move.Effect.OHKO, move.effect);
471
472
  try expectEqual(@as(u8, 30), move.accuracy);
@@ -526,7 +527,7 @@ test Move {
526
527
  pub const Species = species.Species;
527
528
 
528
529
  test Species {
529
- try expectEqual(2, @enumToInt(Species.Ivysaur));
530
+ try expectEqual(2, @intFromEnum(Species.Ivysaur));
530
531
  try expectEqual(@as(u8, 100), Species.get(.Mew).stats.def);
531
532
  }
532
533
 
@@ -543,9 +544,11 @@ pub const Effectiveness = enum(u8) {
543
544
  Super = 20,
544
545
 
545
546
  /// Used to determine the effectiness of a neutral damage hit.
546
- pub const neutral: u16 = @enumToInt(Effectiveness.Neutral) * @enumToInt(Effectiveness.Neutral);
547
+ pub const neutral: u16 =
548
+ @intFromEnum(Effectiveness.Neutral) * @intFromEnum(Effectiveness.Neutral);
547
549
  /// Used to detect whether there is a mismatch in type effectivness (relevant for precedence).
548
- pub const mismatch: u16 = @enumToInt(Effectiveness.Resisted) + @enumToInt(Effectiveness.Super);
550
+ pub const mismatch: u16 =
551
+ @intFromEnum(Effectiveness.Resisted) + @intFromEnum(Effectiveness.Super);
549
552
 
550
553
  comptime {
551
554
  assert(@sizeOf(Effectiveness) == 1);
@@ -553,8 +556,8 @@ pub const Effectiveness = enum(u8) {
553
556
  };
554
557
 
555
558
  test Types {
556
- try expectEqual(14, @enumToInt(Type.Dragon));
557
- try expectEqual(20, @enumToInt(Effectiveness.Super));
559
+ try expectEqual(14, @intFromEnum(Type.Dragon));
560
+ try expectEqual(20, @intFromEnum(Effectiveness.Super));
558
561
 
559
562
  try expect(!Type.Ghost.special());
560
563
  try expect(Type.Dragon.special());
@@ -64,7 +64,7 @@ pub const Battle = struct {
64
64
  }
65
65
  };
66
66
 
67
- pub fn prng(rand: *PSRNG) data.PRNG {
67
+ fn prng(rand: *PSRNG) data.PRNG {
68
68
  // GLITCH: initial bytes in seed can only range from 0-252, not 0-255
69
69
  const max: u8 = 253;
70
70
  return .{
@@ -90,7 +90,7 @@ pub const Side = struct {
90
90
 
91
91
  for (0..ps.len) |i| {
92
92
  side.pokemon[i] = Pokemon.init(ps[i]);
93
- side.order[i] = @intCast(u4, i) + 1;
93
+ side.order[i] = @as(u4, @intCast(i)) + 1;
94
94
  }
95
95
  return side;
96
96
  }
@@ -101,7 +101,7 @@ pub const Side = struct {
101
101
 
102
102
  for (0..n) |i| {
103
103
  side.pokemon[i] = Pokemon.random(rand, opt);
104
- side.order[i] = @intCast(u4, i) + 1;
104
+ side.order[i] = @as(u4, @intCast(i)) + 1;
105
105
  }
106
106
 
107
107
  return side;
@@ -139,7 +139,7 @@ pub const Pokemon = struct {
139
139
  for (p.moves, 0..) |m, j| {
140
140
  pokemon.moves[j].id = m;
141
141
  // NB: PP can be at most 61 legally (though can overflow to 63)
142
- pokemon.moves[j].pp = @intCast(u8, @min(Move.pp(m) / 5 * 8, 61));
142
+ pokemon.moves[j].pp = @intCast(@min(Move.pp(m) / 5 * 8, 61));
143
143
  }
144
144
  if (p.hp) |hp| {
145
145
  pokemon.hp = hp;
@@ -153,7 +153,7 @@ pub const Pokemon = struct {
153
153
  }
154
154
 
155
155
  pub fn random(rand: *PSRNG, opt: Options) data.Pokemon {
156
- const s = @intToEnum(Species, rand.range(u8, 1, Species.size + 1));
156
+ const s: Species = @enumFromInt(rand.range(u8, 1, Species.size + 1));
157
157
  const species = Species.get(s);
158
158
  const lvl = if (rand.chance(u8, 1, 20)) rand.range(u8, 1, 99 + 1) else 100;
159
159
  var stats: Stats(u16) = .{};
@@ -176,15 +176,15 @@ pub const Pokemon = struct {
176
176
  for (0..n) |i| {
177
177
  var m: Move = .None;
178
178
  sample: while (true) {
179
- m = @intToEnum(Move, rand.range(u8, 1, Move.size - 1 + 1));
179
+ m = @enumFromInt(rand.range(u8, 1, Move.size - 1 + 1));
180
180
  if (opt.block and blocked(m)) continue :sample;
181
181
  for (0..i) |j| if (ms[j].id == m) continue :sample;
182
182
  break;
183
183
  }
184
- const pp_ups =
184
+ const pp_ups: u8 =
185
185
  if (!opt.cleric and rand.chance(u8, 1, 10)) rand.range(u2, 0, 2 + 1) else 3;
186
186
  // NB: PP can be at most 61 legally (though can overflow to 63)
187
- const max_pp = @intCast(u8, Move.pp(m) + @as(u8, pp_ups) * @min(Move.pp(m) / 5, 7));
187
+ const max_pp: u8 = @intCast(Move.pp(m) + pp_ups * @min(Move.pp(m) / 5, 7));
188
188
  ms[i] = .{
189
189
  .id = m,
190
190
  .pp = if (opt.cleric) max_pp else rand.range(u8, 0, max_pp + 1),
@@ -206,19 +206,10 @@ pub const Pokemon = struct {
206
206
  }
207
207
  };
208
208
 
209
- // TODO: uncomment once integration tests pass
210
- // fn blocked(m: Move) bool {
211
- // // Binding moves are borked but only via Mirror Move / Metronome which are already blocked
212
- // return switch (m) {
213
- // .Mimic, .Metronome, .MirrorMove, .Transform => true,
214
- // else => false,
215
- // };
216
- // }
217
-
218
209
  fn blocked(m: Move) bool {
219
- if (Move.get(m).effect == .Binding) return true;
210
+ // Binding moves are borked but only via Mirror Move / Metronome which are already blocked
220
211
  return switch (m) {
221
- .Counter, .Mimic, .Bide, .Metronome, .MirrorMove, .Transform => true,
212
+ .Mimic, .Metronome, .MirrorMove, .Transform => true,
222
213
  else => false,
223
214
  };
224
215
  }