@pkmn/engine 0.1.0-dev.7cd30bd9 → 0.1.0-dev.7f2014fa

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.
@@ -62,26 +62,28 @@ pub fn update(battle: anytype, c1: Choice, c2: Choice, log: anytype) !Result {
62
62
  assert(c1.type != .Pass or c2.type != .Pass or battle.turn == 0);
63
63
  if (battle.turn == 0) return start(battle, log);
64
64
 
65
+ var s1 = false;
66
+ var s2 = false;
65
67
  var f1: ?Move = null;
66
68
  var f2: ?Move = null;
67
69
 
68
- if (selectMove(battle, .P1, c1, c2, &f1)) |r| return r;
69
- if (selectMove(battle, .P2, c2, c1, &f2)) |r| return r;
70
+ if (selectMove(battle, .P1, c1, c2, &s1, &f1)) |r| return r;
71
+ if (selectMove(battle, .P2, c2, c1, &s2, &f2)) |r| return r;
72
+
73
+ var p1 = battle.side(.P1);
74
+ var p2 = battle.side(.P2);
75
+
76
+ var r1 = showdown and p1.active.volatiles.Binding and c2.type == .Switch;
77
+ var r2 = showdown and p2.active.volatiles.Binding and c1.type == .Switch;
70
78
 
71
79
  if (turnOrder(battle, c1, c2) == .P1) {
72
- if (try doTurn(battle, .P1, c1, f1, .P2, c2, f2, log)) |r| return r;
80
+ if (try doTurn(battle, .P1, c1, r1, s1, f1, .P2, c2, r2, s2, f2, log)) |r| return r;
73
81
  } else {
74
- if (try doTurn(battle, .P2, c2, f2, .P1, c1, f1, log)) |r| return r;
82
+ if (try doTurn(battle, .P2, c2, r2, s2, f2, .P1, c1, r1, s1, f1, log)) |r| return r;
75
83
  }
76
84
 
77
- var p1 = battle.side(.P1);
78
- if (p1.active.volatiles.attacks == 0) {
79
- p1.active.volatiles.Binding = false;
80
- }
81
- var p2 = battle.side(.P2);
82
- if (p2.active.volatiles.attacks == 0) {
83
- p2.active.volatiles.Binding = false;
84
- }
85
+ if (p1.active.volatiles.attacks == 0) p1.active.volatiles.Binding = false;
86
+ if (p2.active.volatiles.attacks == 0) p2.active.volatiles.Binding = false;
85
87
 
86
88
  return endTurn(battle, log);
87
89
  }
@@ -114,6 +116,7 @@ fn selectMove(
114
116
  player: Player,
115
117
  choice: Choice,
116
118
  foe_choice: Choice,
119
+ skip_turn: *bool,
117
120
  from: *?Move,
118
121
  ) ?Result {
119
122
  if (choice.type == .Pass) return null;
@@ -126,17 +129,26 @@ fn selectMove(
126
129
  (choice.type == .Move and choice.data == @boolToInt(showdown)));
127
130
 
128
131
  // pre-battle menu
129
- if (volatiles.Recharging) return null;
132
+ if (volatiles.Recharging) {
133
+ if (showdown and battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
134
+ return null;
135
+ }
130
136
  if (volatiles.Rage) {
131
137
  from.* = side.last_used_move;
132
- if (showdown) saveMove(battle, player, null);
138
+ if (showdown) {
139
+ if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
140
+ saveMove(battle, player, null);
141
+ }
133
142
  return null;
134
143
  }
135
144
  // Pokémon Showdown removes Flinch at the end-of-turn in its residual handler
136
145
  if (!showdown) volatiles.Flinch = false;
137
146
  if (volatiles.Thrashing or volatiles.Charging) {
138
147
  from.* = side.last_selected_move;
139
- if (showdown) saveMove(battle, player, null);
148
+ if (showdown) {
149
+ if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
150
+ saveMove(battle, player, null);
151
+ }
140
152
  return null;
141
153
  }
142
154
 
@@ -146,7 +158,10 @@ fn selectMove(
146
158
  // pre-move select
147
159
  if (Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP) or volatiles.Bide) {
148
160
  assert(showdown or choice.data == 0);
149
- if (showdown) saveMove(battle, player, choice);
161
+ if (showdown) {
162
+ if (volatiles.Bide and battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
163
+ saveMove(battle, player, choice);
164
+ }
150
165
  return null;
151
166
  }
152
167
  if (volatiles.Binding) {
@@ -165,9 +180,9 @@ fn selectMove(
165
180
  // GLITCH: https://glitchcity.wiki/Partial_trapping_move_Mirror_Move_link_battle_glitch
166
181
  if (foe_choice.type == .Switch) {
167
182
  const slot = if (player == .P1)
168
- battle.last_selected_indexes.p1
183
+ battle.last_moves.p1_index
169
184
  else
170
- battle.last_selected_indexes.p2;
185
+ battle.last_moves.p2_index;
171
186
  const last = side.active.move(@intCast(u4, slot));
172
187
  if (last.id == .Metronome) side.last_selected_move = last.id;
173
188
  if (last.id == .MirrorMove) return Result.Error;
@@ -177,6 +192,7 @@ fn selectMove(
177
192
  }
178
193
 
179
194
  if (battle.foe(player).active.volatiles.Binding) {
195
+ skip_turn.* = true;
180
196
  if (showdown) {
181
197
  saveMove(battle, player, choice);
182
198
  } else {
@@ -221,9 +237,9 @@ fn saveMove(battle: anytype, player: Player, choice: ?Choice) void {
221
237
 
222
238
  side.last_selected_move = move.id;
223
239
  if (player == .P1) {
224
- battle.last_selected_indexes.p1 = @intCast(u4, c.data);
240
+ battle.last_moves.p1_index = @intCast(u3, c.data);
225
241
  } else {
226
- battle.last_selected_indexes.p2 = @intCast(u4, c.data);
242
+ battle.last_moves.p2_index = @intCast(u3, c.data);
227
243
  }
228
244
  }
229
245
  }
@@ -243,9 +259,9 @@ fn switchIn(battle: anytype, player: Player, slot: u8, initial: bool, log: anyty
243
259
  side.order[slot - 1] = out;
244
260
 
245
261
  if (player == .P1) {
246
- battle.last_selected_indexes.p1 = 1;
262
+ battle.last_moves.p1_index = 1;
247
263
  } else {
248
- battle.last_selected_indexes.p2 = 1;
264
+ battle.last_moves.p2_index = 1;
249
265
  }
250
266
 
251
267
  side.last_used_move = .None;
@@ -327,9 +343,13 @@ fn doTurn(
327
343
  battle: anytype,
328
344
  player: Player,
329
345
  player_choice: Choice,
346
+ player_rewrap: bool,
347
+ player_skip: bool,
330
348
  player_from: ?Move,
331
349
  foe_player: Player,
332
350
  foe_choice: Choice,
351
+ foe_rewrap: bool,
352
+ foe_skip: bool,
333
353
  foe_from: ?Move,
334
354
  log: anytype,
335
355
  ) !?Result {
@@ -337,7 +357,16 @@ fn doTurn(
337
357
 
338
358
  var residual = true;
339
359
  var replace = battle.side(player).stored().hp == 0;
340
- if (try executeMove(battle, player, player_choice, player_from, &residual, log)) |r| return r;
360
+ if (try executeMove(
361
+ battle,
362
+ player,
363
+ player_choice,
364
+ player_rewrap,
365
+ player_skip,
366
+ player_from,
367
+ &residual,
368
+ log,
369
+ )) |r| return r;
341
370
  if (!replace) {
342
371
  if (player_choice.type != .Switch) {
343
372
  if (try checkFaint(battle, foe_player, log)) |r| return r;
@@ -348,7 +377,16 @@ fn doTurn(
348
377
 
349
378
  residual = true;
350
379
  replace = battle.side(foe_player).stored().hp == 0;
351
- if (try executeMove(battle, foe_player, foe_choice, foe_from, &residual, log)) |r| return r;
380
+ if (try executeMove(
381
+ battle,
382
+ foe_player,
383
+ foe_choice,
384
+ foe_rewrap,
385
+ foe_skip,
386
+ foe_from,
387
+ &residual,
388
+ log,
389
+ )) |r| return r;
352
390
  if (!replace) {
353
391
  if (foe_choice.type != .Switch) {
354
392
  if (try checkFaint(battle, player, log)) |r| return r;
@@ -371,6 +409,8 @@ fn executeMove(
371
409
  battle: anytype,
372
410
  player: Player,
373
411
  choice: Choice,
412
+ rewrap: bool,
413
+ skip: bool,
374
414
  from: ?Move,
375
415
  residual: *bool,
376
416
  log: anytype,
@@ -382,9 +422,10 @@ fn executeMove(
382
422
  return null;
383
423
  }
384
424
 
425
+ // This is the correct place to check for SKIP_TURN and abort early, however since Pokémon
426
+ // Showdown overwrites the SKIP_TURN sentinel with its botched move select we need to add an
427
+ // additional skip boolean to accomplish the same thing in the Binding check of BeforeMove
385
428
  if (side.last_selected_move == .SKIP_TURN) {
386
- // Pokémon Showdown overwrites the SKIP_TURN sentinel with its botched move select,
387
- // Binding instead gets handled in beforeMove after sleep and freeze
388
429
  assert(!showdown);
389
430
  if (battle.foe(player).active.volatiles.Binding) {
390
431
  try log.cant(battle.active(player), .Bound);
@@ -403,9 +444,9 @@ fn executeMove(
403
444
  // choice.data == 0 only happens with Struggle on Pokémon Showdown
404
445
  assert(!showdown);
405
446
  mslot = @intCast(u4, if (player == .P1)
406
- battle.last_selected_indexes.p1
447
+ battle.last_moves.p1_index
407
448
  else
408
- battle.last_selected_indexes.p2);
449
+ battle.last_moves.p2_index);
409
450
  const stored = side.stored();
410
451
  // GLITCH: Struggle bypass PP underflow via Hyper Beam / Trapping-switch auto selection
411
452
  auto = side.last_selected_move == .HyperBeam or side.active.volatiles.Bide or
@@ -427,14 +468,14 @@ fn executeMove(
427
468
  // Incorrect mslot with Pokémon Showdown choice semantics so we need to recover from index
428
469
  assert(mslot == 1);
429
470
  mslot = @intCast(u4, if (player == .P1)
430
- battle.last_selected_indexes.p1
471
+ battle.last_moves.p1_index
431
472
  else
432
- battle.last_selected_indexes.p2);
473
+ battle.last_moves.p2_index);
433
474
  }
434
475
 
435
476
  var skip_can = false;
436
477
  var skip_pp = false;
437
- switch (try beforeMove(battle, player, from, residual, log)) {
478
+ switch (try beforeMove(battle, player, skip, from, residual, log)) {
438
479
  .done => return null,
439
480
  .skip_can => skip_can = true,
440
481
  .skip_pp => skip_pp = true,
@@ -446,7 +487,7 @@ fn executeMove(
446
487
  return null;
447
488
  }
448
489
 
449
- return doMove(battle, player, mslot, auto, residual, log);
490
+ return doMove(battle, player, mslot, rewrap, auto, residual, log);
450
491
  }
451
492
 
452
493
  const BeforeMove = enum { done, skip_can, skip_pp, ok, err };
@@ -454,6 +495,7 @@ const BeforeMove = enum { done, skip_can, skip_pp, ok, err };
454
495
  fn beforeMove(
455
496
  battle: anytype,
456
497
  player: Player,
498
+ skip: bool,
457
499
  from: ?Move,
458
500
  residual: *bool,
459
501
  log: anytype,
@@ -485,13 +527,15 @@ fn beforeMove(
485
527
  return .done;
486
528
  }
487
529
 
488
- if (foe.active.volatiles.Binding) {
530
+ if (skip or foe.active.volatiles.Binding) {
489
531
  try log.cant(ident, .Bound);
490
532
  return .done;
491
533
  }
492
534
 
493
- if (!showdown and volatiles.Flinch) {
494
- volatiles.Flinch = false;
535
+ if (volatiles.Flinch) {
536
+ // Pokémon Showdown doesn't clear Flinch until its imaginary "residual" phase, meaning
537
+ // Pokémon can sometimes flinch multiple times from the same original hit
538
+ if (!showdown) volatiles.Flinch = false;
495
539
  try log.cant(ident, .Flinch);
496
540
  return .done;
497
541
  }
@@ -512,14 +556,6 @@ fn beforeMove(
512
556
  // Pokémon Showdown's disable condition has a single onBeforeMove handler
513
557
  if (showdown and try disabled(side, ident, log)) return .done;
514
558
 
515
- // Pokémon Showdown checks for Flinch *after* instead of before... and doesn't clear Flinch
516
- // until its imaginary "residual" phase, meaning Pokémon can sometimes flinch multiple times
517
- // from the same original hit
518
- if (showdown and volatiles.Flinch) {
519
- try log.cant(ident, .Flinch);
520
- return .done;
521
- }
522
-
523
559
  // This can only happen if a Pokémon started the battle frozen/sleeping and was thawed/woken
524
560
  // before the side had a selected a move - we simply need to assume this leads to a desync
525
561
  if (side.last_selected_move == .None) {
@@ -635,8 +671,13 @@ fn beforeMove(
635
671
  try log.start(battle.active(player), .ConfusionSilent);
636
672
  }
637
673
  try log.move(ident, side.last_selected_move, battle.active(player.foe()), from);
638
- if (showdown and handleThrashing(battle, active)) {
639
- try log.start(battle.active(player), .ConfusionSilent);
674
+ if (showdown) {
675
+ // This shouldn't actually set last_used_move, but Pokémon Showdown sets last
676
+ // used in useMove and doesn't have the notion of skipping canMove semantics
677
+ side.last_used_move = side.last_selected_move;
678
+ if (handleThrashing(battle, active)) {
679
+ try log.start(battle.active(player), .ConfusionSilent);
680
+ }
640
681
  }
641
682
  return .skip_can;
642
683
  }
@@ -651,7 +692,11 @@ fn beforeMove(
651
692
  side.last_selected_move,
652
693
  );
653
694
  if (showdown or battle.last_damage != 0) {
695
+ const sub = showdown and foe.active.volatiles.Substitute;
654
696
  _ = try applyDamage(battle, player.foe(), player.foe(), .None, log);
697
+ if (battle.foe(player).stored().hp > 0 and !sub) {
698
+ try buildRage(battle, player.foe(), log);
699
+ }
655
700
  }
656
701
  return .done;
657
702
  }
@@ -700,6 +745,19 @@ fn canMove(
700
745
  const f = if (from != null and from.? == .None) null else from;
701
746
  try log.move(player_ident, side.last_selected_move, battle.active(target), f);
702
747
 
748
+ // The Counter desync is caused by the cartridge not calling GetCurrentMove until now, meaning
749
+ // in cases where an early return happens the data for a players last selected move does not get
750
+ // reloaded and HandleCounterMove actually bases its success/failure off of stale information.
751
+ // This boolean state we track here doesn't exist on the cartridge because it instead manifests
752
+ // as the desync results from actually having two separate battle states that subtly disagree.
753
+ const counterable = side.last_selected_move != .Counter and move.bp > 0 and
754
+ (move.type == .Normal or move.type == .Fighting);
755
+ if (player == .P1) {
756
+ battle.last_moves.p1_counterable = @boolToInt(counterable);
757
+ } else {
758
+ battle.last_moves.p2_counterable = @boolToInt(counterable);
759
+ }
760
+
703
761
  if (move.effect.onBegin()) {
704
762
  try onBegin(battle, player, move, mslot, residual, log);
705
763
  return false;
@@ -709,7 +767,7 @@ fn canMove(
709
767
  Effects.thrashing(battle, player);
710
768
  } else if (!showdown and move.effect == .Binding) {
711
769
  // Pokémon Showdown handles this after hit/miss checks and damage calculation
712
- Effects.binding(battle, player);
770
+ Effects.binding(battle, player, false);
713
771
  }
714
772
 
715
773
  return true;
@@ -750,6 +808,7 @@ fn doMove(
750
808
  battle: anytype,
751
809
  player: Player,
752
810
  mslot: u4,
811
+ rewrap: bool,
753
812
  auto: bool,
754
813
  residual: *bool,
755
814
  log: anytype,
@@ -841,9 +900,9 @@ fn doMove(
841
900
 
842
901
  if (!showdown or !miss) {
843
902
  if (move.effect == .MirrorMove) {
844
- return mirrorMove(battle, player, mslot, auto, residual, log);
903
+ return mirrorMove(battle, player, mslot, rewrap, auto, residual, log);
845
904
  } else if (move.effect == .Metronome) {
846
- return metronome(battle, player, mslot, auto, residual, log);
905
+ return metronome(battle, player, mslot, rewrap, auto, residual, log);
847
906
  } else if (move.effect.onEnd()) {
848
907
  try onEnd(battle, player, move, log);
849
908
  return null;
@@ -858,7 +917,7 @@ fn doMove(
858
917
  move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe;
859
918
  if (ohko) {
860
919
  try log.immune(foe_ident, .OHKO);
861
- } else if (immune and !invulnerable) {
920
+ } else if (immune and !invulnerable and (!showdown or move.effect != .Binding)) {
862
921
  try log.immune(foe_ident, .None);
863
922
  } else if (mist) {
864
923
  if (!foe.active.volatiles.Substitute) try log.activate(foe_ident, .Mist);
@@ -924,11 +983,17 @@ fn doMove(
924
983
  } else if (showdown) {
925
984
  // This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
926
985
  if (move.effect == .Binding) {
927
- Effects.binding(battle, player);
986
+ Effects.binding(battle, player, rewrap);
928
987
  if (immune) {
929
988
  battle.last_damage = 0;
989
+ assert(foe.stored().hp > 0);
930
990
  // Pokémon Showdown logs |-damage| here instead of |-immune| because logic...
931
- try log.damage(battle.active(player.foe()), foe.stored(), .None);
991
+ if (sub) {
992
+ try log.activate(battle.active(player.foe()), .Substitute);
993
+ } else {
994
+ try log.damage(battle.active(player.foe()), foe.stored(), .None);
995
+ try buildRage(battle, player.foe(), log);
996
+ }
932
997
  return null;
933
998
  }
934
999
  }
@@ -1125,19 +1190,17 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, log: anytype)
1125
1190
  return null;
1126
1191
  }
1127
1192
 
1128
- // Pretend Counter was used as a stand-in when no move has been used to fail below with 0 BP
1129
- const foe_last_used_move =
1130
- Move.get(if (foe.last_used_move == .None) .Counter else foe.last_used_move);
1193
+ // Pretend Splash was used as a stand-in when no move has been used to fail below with 0 BP
1131
1194
  const foe_last_selected_move =
1132
1195
  Move.get(if (foe.last_selected_move == .None or foe.last_selected_move == .SKIP_TURN)
1133
- .Counter
1196
+ .Splash
1134
1197
  else
1135
1198
  foe.last_selected_move);
1136
1199
 
1137
- const used = foe_last_used_move.bp > 0 and
1138
- foe.last_used_move != .Counter and
1139
- (foe_last_used_move.type == .Normal or
1140
- foe_last_used_move.type == .Fighting);
1200
+ const used = (if (player.foe() == .P1)
1201
+ battle.last_moves.p1_counterable
1202
+ else
1203
+ battle.last_moves.p2_counterable) != 0;
1141
1204
 
1142
1205
  const selected = foe_last_selected_move.bp > 0 and
1143
1206
  foe.last_selected_move != .Counter and
@@ -1161,7 +1224,9 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, log: anytype)
1161
1224
  // Pokémon Showdown calls checkHit before Counter
1162
1225
  if (!showdown and !try checkHit(battle, player, move, log)) return null;
1163
1226
 
1227
+ const sub = showdown and foe.active.volatiles.Substitute;
1164
1228
  _ = try applyDamage(battle, player.foe(), player.foe(), .None, log);
1229
+ if (battle.foe(player).stored().hp > 0 and !sub) try buildRage(battle, player.foe(), log);
1165
1230
  return null;
1166
1231
  }
1167
1232
 
@@ -1205,6 +1270,7 @@ fn mirrorMove(
1205
1270
  battle: anytype,
1206
1271
  player: Player,
1207
1272
  mslot: u4,
1273
+ rewrap: bool,
1208
1274
  auto: bool,
1209
1275
  residual: *bool,
1210
1276
  log: anytype,
@@ -1217,18 +1283,19 @@ fn mirrorMove(
1217
1283
  if (foe.last_used_move == .None or foe.last_used_move == .MirrorMove) {
1218
1284
  try log.fail(battle.active(player), .None);
1219
1285
  return null;
1286
+ } else if (!showdown or foe.last_used_move != .Struggle) {
1287
+ incrementPP(side, mslot);
1220
1288
  }
1221
1289
 
1222
- incrementPP(side, mslot);
1223
-
1224
1290
  if (!try canMove(battle, player, mslot, auto, false, .MirrorMove, residual, log)) return null;
1225
- return doMove(battle, player, mslot, auto, residual, log);
1291
+ return doMove(battle, player, mslot, rewrap, auto, residual, log);
1226
1292
  }
1227
1293
 
1228
1294
  fn metronome(
1229
1295
  battle: anytype,
1230
1296
  player: Player,
1231
1297
  mslot: u4,
1298
+ rewrap: bool,
1232
1299
  auto: bool,
1233
1300
  residual: *bool,
1234
1301
  log: anytype,
@@ -1239,7 +1306,7 @@ fn metronome(
1239
1306
  incrementPP(side, mslot);
1240
1307
 
1241
1308
  if (!try canMove(battle, player, mslot, auto, false, .Metronome, residual, log)) return null;
1242
- return doMove(battle, player, mslot, auto, residual, log);
1309
+ return doMove(battle, player, mslot, rewrap, auto, residual, log);
1243
1310
  }
1244
1311
 
1245
1312
  fn checkHit(battle: anytype, player: Player, move: Move.Data, log: anytype) !bool {
@@ -1446,11 +1513,7 @@ fn handleResidual(battle: anytype, player: Player, log: anytype) !void {
1446
1513
  stored.hp -= amount;
1447
1514
 
1448
1515
  // As above, Pokémon Showdown uses damageOf but its not relevant
1449
- if (amount > 0) {
1450
- try log.damage(ident, stored, .LeechSeed);
1451
- // Pokémon Showdown erroneously updates last damage with uncapped Leech Seed damage
1452
- if (showdown) battle.last_damage = damage;
1453
- }
1516
+ if (amount > 0) try log.damage(ident, stored, .LeechSeed);
1454
1517
 
1455
1518
  const before = foe_stored.hp;
1456
1519
  // Uncapped damage is added back to the foe
@@ -1983,9 +2046,9 @@ pub const Effects = struct {
1983
2046
  }
1984
2047
  break :has_mimic false;
1985
2048
  };
1986
- // If the foe is Invulnerable we still want to fall through to checkHit to be able to
1987
- // trigger |-miss| instead of |-fail|
1988
- if (!has_mimic and !foe.active.volatiles.Invulnerable) {
2049
+ if (!has_mimic) {
2050
+ // Invulnerable foes or 1/256 miss can trigger |-miss| instead of |-fail|
2051
+ if (!try checkHit(battle, player, move, log)) return;
1989
2052
  return try log.fail(battle.active(player.foe()), .None);
1990
2053
  }
1991
2054
  }
@@ -2253,7 +2316,7 @@ pub const Effects = struct {
2253
2316
  try log.transform(battle.active(player), foe_ident);
2254
2317
  }
2255
2318
 
2256
- fn binding(battle: anytype, player: Player) void {
2319
+ fn binding(battle: anytype, player: Player, rewrap: bool) void {
2257
2320
  var side = battle.side(player);
2258
2321
  var foe = battle.foe(player);
2259
2322
 
@@ -2261,7 +2324,9 @@ pub const Effects = struct {
2261
2324
  side.active.volatiles.Binding = true;
2262
2325
  // GLITCH: Hyper Beam automatic selection glitch if Recharging gets cleared on miss
2263
2326
  // (Pokémon Showdown unitentionally patches this glitch, preventing automatic selection)
2264
- if (!showdown) foe.active.volatiles.Recharging = false;
2327
+ if (!showdown) {
2328
+ foe.active.volatiles.Recharging = false;
2329
+ } else if (foe.stored().hp == 0) return if (!rewrap) battle.rng.advance(1);
2265
2330
 
2266
2331
  side.active.volatiles.attacks = Rolls.distribution(battle) - 1;
2267
2332
  }
package/src/lib/pkmn.zig CHANGED
@@ -25,12 +25,12 @@ pub const Result = @import("common/data.zig").Result;
25
25
  /// Pokémon Showdown's RNG (backed by a Generation V & VI RNG).
26
26
  pub const PSRNG = @import("common/rng.zig").PSRNG;
27
27
 
28
- /// Namespace for helpers related to protocol trace logging.
28
+ /// Namespace for helpers related to protocol message logging.
29
29
  pub const protocol = if (options.internal) struct {
30
30
  pub usingnamespace @import("common/protocol.zig");
31
31
  } else struct {
32
32
  /// Logs protocol information to its `Writer` during a battle update when
33
- /// `options.trace` is enabled.
33
+ /// `options.log` is enabled.
34
34
  pub const Log = @import("common/protocol.zig").Log;
35
35
  /// Stripped down version of `std.io.FixedBufferStream` optimized for
36
36
  /// efficiently writing the individual protocol bytes. Note that the
@@ -41,8 +41,8 @@ pub const protocol = if (options.internal) struct {
41
41
  /// intialized with a `LOGS_SIZE`-sized buffer.
42
42
  pub const FixedLog = @import("common/protocol.zig").FixedLog;
43
43
  /// Null object pattern implementation of `Log` backed by a
44
- /// `std.io.null_writer`. Ignores anything sent to it, though trace logging
45
- /// should addtionally be turned off entirely by using `options.trace`.
44
+ /// `std.io.null_writer`. Ignores anything sent to it, though protocol
45
+ /// logging should additionally be turned off entirely with `options.log`.
46
46
  pub const NULL = @import("common/protocol.zig").NULL;
47
47
  };
48
48