@pkmn/engine 0.1.0-dev.47feedd9 → 0.1.0-dev.4eeb346e

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,28 +62,26 @@ 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;
67
65
  var f1: ?Move = null;
68
66
  var f2: ?Move = null;
69
67
 
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;
68
+ if (selectMove(battle, .P1, c1, c2, &f1)) |r| return r;
69
+ if (selectMove(battle, .P2, c2, c1, &f2)) |r| return r;
78
70
 
79
71
  if (turnOrder(battle, c1, c2) == .P1) {
80
- if (try doTurn(battle, .P1, c1, r1, s1, f1, .P2, c2, r2, s2, f2, log)) |r| return r;
72
+ if (try doTurn(battle, .P1, c1, f1, .P2, c2, f2, log)) |r| return r;
81
73
  } else {
82
- if (try doTurn(battle, .P2, c2, r2, s2, f2, .P1, c1, r1, s1, f1, log)) |r| return r;
74
+ if (try doTurn(battle, .P2, c2, f2, .P1, c1, f1, log)) |r| return r;
83
75
  }
84
76
 
85
- if (p1.active.volatiles.attacks == 0) p1.active.volatiles.Binding = false;
86
- if (p2.active.volatiles.attacks == 0) p2.active.volatiles.Binding = false;
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
+ }
87
85
 
88
86
  return endTurn(battle, log);
89
87
  }
@@ -116,7 +114,6 @@ fn selectMove(
116
114
  player: Player,
117
115
  choice: Choice,
118
116
  foe_choice: Choice,
119
- skip_turn: *bool,
120
117
  from: *?Move,
121
118
  ) ?Result {
122
119
  if (choice.type == .Pass) return null;
@@ -132,20 +129,14 @@ fn selectMove(
132
129
  if (volatiles.Recharging) return null;
133
130
  if (volatiles.Rage) {
134
131
  from.* = side.last_used_move;
135
- if (showdown) {
136
- if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
137
- saveMove(battle, player, null);
138
- }
132
+ if (showdown) saveMove(battle, player, null);
139
133
  return null;
140
134
  }
141
135
  // Pokémon Showdown removes Flinch at the end-of-turn in its residual handler
142
136
  if (!showdown) volatiles.Flinch = false;
143
137
  if (volatiles.Thrashing or volatiles.Charging) {
144
138
  from.* = side.last_selected_move;
145
- if (showdown) {
146
- if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
147
- saveMove(battle, player, null);
148
- }
139
+ if (showdown) saveMove(battle, player, null);
149
140
  return null;
150
141
  }
151
142
 
@@ -155,10 +146,7 @@ fn selectMove(
155
146
  // pre-move select
156
147
  if (Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP) or volatiles.Bide) {
157
148
  assert(showdown or choice.data == 0);
158
- if (showdown) {
159
- if (volatiles.Bide and battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
160
- saveMove(battle, player, choice);
161
- }
149
+ if (showdown) saveMove(battle, player, choice);
162
150
  return null;
163
151
  }
164
152
  if (volatiles.Binding) {
@@ -189,7 +177,6 @@ fn selectMove(
189
177
  }
190
178
 
191
179
  if (battle.foe(player).active.volatiles.Binding) {
192
- skip_turn.* = true;
193
180
  if (showdown) {
194
181
  saveMove(battle, player, choice);
195
182
  } else {
@@ -340,13 +327,9 @@ fn doTurn(
340
327
  battle: anytype,
341
328
  player: Player,
342
329
  player_choice: Choice,
343
- player_rewrap: bool,
344
- player_skip: bool,
345
330
  player_from: ?Move,
346
331
  foe_player: Player,
347
332
  foe_choice: Choice,
348
- foe_rewrap: bool,
349
- foe_skip: bool,
350
333
  foe_from: ?Move,
351
334
  log: anytype,
352
335
  ) !?Result {
@@ -354,16 +337,7 @@ fn doTurn(
354
337
 
355
338
  var residual = true;
356
339
  var replace = battle.side(player).stored().hp == 0;
357
- if (try executeMove(
358
- battle,
359
- player,
360
- player_choice,
361
- player_rewrap,
362
- player_skip,
363
- player_from,
364
- &residual,
365
- log,
366
- )) |r| return r;
340
+ if (try executeMove(battle, player, player_choice, player_from, &residual, log)) |r| return r;
367
341
  if (!replace) {
368
342
  if (player_choice.type != .Switch) {
369
343
  if (try checkFaint(battle, foe_player, log)) |r| return r;
@@ -374,16 +348,7 @@ fn doTurn(
374
348
 
375
349
  residual = true;
376
350
  replace = battle.side(foe_player).stored().hp == 0;
377
- if (try executeMove(
378
- battle,
379
- foe_player,
380
- foe_choice,
381
- foe_rewrap,
382
- foe_skip,
383
- foe_from,
384
- &residual,
385
- log,
386
- )) |r| return r;
351
+ if (try executeMove(battle, foe_player, foe_choice, foe_from, &residual, log)) |r| return r;
387
352
  if (!replace) {
388
353
  if (foe_choice.type != .Switch) {
389
354
  if (try checkFaint(battle, player, log)) |r| return r;
@@ -406,8 +371,6 @@ fn executeMove(
406
371
  battle: anytype,
407
372
  player: Player,
408
373
  choice: Choice,
409
- rewrap: bool,
410
- skip: bool,
411
374
  from: ?Move,
412
375
  residual: *bool,
413
376
  log: anytype,
@@ -419,10 +382,9 @@ fn executeMove(
419
382
  return null;
420
383
  }
421
384
 
422
- // This is the correct place to check for SKIP_TURN and abort early, however since Pokémon
423
- // Showdown overwrites the SKIP_TURN sentinel with its botched move select we need to add an
424
- // additional skip boolean to accomplish the same thing in the Binding check of BeforeMove
425
385
  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
426
388
  assert(!showdown);
427
389
  if (battle.foe(player).active.volatiles.Binding) {
428
390
  try log.cant(battle.active(player), .Bound);
@@ -472,7 +434,7 @@ fn executeMove(
472
434
 
473
435
  var skip_can = false;
474
436
  var skip_pp = false;
475
- switch (try beforeMove(battle, player, skip, from, residual, log)) {
437
+ switch (try beforeMove(battle, player, from, residual, log)) {
476
438
  .done => return null,
477
439
  .skip_can => skip_can = true,
478
440
  .skip_pp => skip_pp = true,
@@ -484,7 +446,7 @@ fn executeMove(
484
446
  return null;
485
447
  }
486
448
 
487
- return doMove(battle, player, mslot, rewrap, auto, residual, log);
449
+ return doMove(battle, player, mslot, auto, residual, log);
488
450
  }
489
451
 
490
452
  const BeforeMove = enum { done, skip_can, skip_pp, ok, err };
@@ -492,7 +454,6 @@ const BeforeMove = enum { done, skip_can, skip_pp, ok, err };
492
454
  fn beforeMove(
493
455
  battle: anytype,
494
456
  player: Player,
495
- skip: bool,
496
457
  from: ?Move,
497
458
  residual: *bool,
498
459
  log: anytype,
@@ -524,15 +485,13 @@ fn beforeMove(
524
485
  return .done;
525
486
  }
526
487
 
527
- if (skip or foe.active.volatiles.Binding) {
488
+ if (foe.active.volatiles.Binding) {
528
489
  try log.cant(ident, .Bound);
529
490
  return .done;
530
491
  }
531
492
 
532
- if (volatiles.Flinch) {
533
- // Pokémon Showdown doesn't clear Flinch until its imaginary "residual" phase, meaning
534
- // Pokémon can sometimes flinch multiple times from the same original hit
535
- if (!showdown) volatiles.Flinch = false;
493
+ if (!showdown and volatiles.Flinch) {
494
+ volatiles.Flinch = false;
536
495
  try log.cant(ident, .Flinch);
537
496
  return .done;
538
497
  }
@@ -553,6 +512,13 @@ fn beforeMove(
553
512
  // Pokémon Showdown's disable condition has a single onBeforeMove handler
554
513
  if (showdown and try disabled(side, ident, log)) return .done;
555
514
 
515
+ // Pokémon Showdown checks for Flinch *after* instead of before
516
+ if (showdown and volatiles.Flinch) {
517
+ volatiles.Flinch = false;
518
+ try log.cant(ident, .Flinch);
519
+ return .done;
520
+ }
521
+
556
522
  // This can only happen if a Pokémon started the battle frozen/sleeping and was thawed/woken
557
523
  // before the side had a selected a move - we simply need to assume this leads to a desync
558
524
  if (side.last_selected_move == .None) {
@@ -668,13 +634,8 @@ fn beforeMove(
668
634
  try log.start(battle.active(player), .ConfusionSilent);
669
635
  }
670
636
  try log.move(ident, side.last_selected_move, battle.active(player.foe()), from);
671
- if (showdown) {
672
- // This shouldn't actually set last_used_move, but Pokémon Showdown sets last
673
- // used in useMove and doesn't have the notion of skipping canMove semantics
674
- side.last_used_move = side.last_selected_move;
675
- if (handleThrashing(battle, active)) {
676
- try log.start(battle.active(player), .ConfusionSilent);
677
- }
637
+ if (showdown and handleThrashing(battle, active)) {
638
+ try log.start(battle.active(player), .ConfusionSilent);
678
639
  }
679
640
  return .skip_can;
680
641
  }
@@ -689,11 +650,7 @@ fn beforeMove(
689
650
  side.last_selected_move,
690
651
  );
691
652
  if (showdown or battle.last_damage != 0) {
692
- const sub = showdown and foe.active.volatiles.Substitute;
693
653
  _ = try applyDamage(battle, player.foe(), player.foe(), .None, log);
694
- if (battle.foe(player).stored().hp > 0 and !sub) {
695
- try buildRage(battle, player.foe(), log);
696
- }
697
654
  }
698
655
  return .done;
699
656
  }
@@ -751,7 +708,7 @@ fn canMove(
751
708
  Effects.thrashing(battle, player);
752
709
  } else if (!showdown and move.effect == .Binding) {
753
710
  // Pokémon Showdown handles this after hit/miss checks and damage calculation
754
- Effects.binding(battle, player, false);
711
+ Effects.binding(battle, player);
755
712
  }
756
713
 
757
714
  return true;
@@ -792,7 +749,6 @@ fn doMove(
792
749
  battle: anytype,
793
750
  player: Player,
794
751
  mslot: u4,
795
- rewrap: bool,
796
752
  auto: bool,
797
753
  residual: *bool,
798
754
  log: anytype,
@@ -884,9 +840,9 @@ fn doMove(
884
840
 
885
841
  if (!showdown or !miss) {
886
842
  if (move.effect == .MirrorMove) {
887
- return mirrorMove(battle, player, mslot, rewrap, auto, residual, log);
843
+ return mirrorMove(battle, player, mslot, auto, residual, log);
888
844
  } else if (move.effect == .Metronome) {
889
- return metronome(battle, player, mslot, rewrap, auto, residual, log);
845
+ return metronome(battle, player, mslot, auto, residual, log);
890
846
  } else if (move.effect.onEnd()) {
891
847
  try onEnd(battle, player, move, log);
892
848
  return null;
@@ -901,7 +857,7 @@ fn doMove(
901
857
  move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe;
902
858
  if (ohko) {
903
859
  try log.immune(foe_ident, .OHKO);
904
- } else if (immune and !invulnerable and (!showdown or move.effect != .Binding)) {
860
+ } else if (immune and !invulnerable) {
905
861
  try log.immune(foe_ident, .None);
906
862
  } else if (mist) {
907
863
  if (!foe.active.volatiles.Substitute) try log.activate(foe_ident, .Mist);
@@ -967,7 +923,7 @@ fn doMove(
967
923
  } else if (showdown) {
968
924
  // This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
969
925
  if (move.effect == .Binding) {
970
- Effects.binding(battle, player, rewrap);
926
+ Effects.binding(battle, player);
971
927
  if (immune) {
972
928
  battle.last_damage = 0;
973
929
  // Pokémon Showdown logs |-damage| here instead of |-immune| because logic...
@@ -1204,9 +1160,7 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, log: anytype)
1204
1160
  // Pokémon Showdown calls checkHit before Counter
1205
1161
  if (!showdown and !try checkHit(battle, player, move, log)) return null;
1206
1162
 
1207
- const sub = showdown and foe.active.volatiles.Substitute;
1208
1163
  _ = try applyDamage(battle, player.foe(), player.foe(), .None, log);
1209
- if (battle.foe(player).stored().hp > 0 and !sub) try buildRage(battle, player.foe(), log);
1210
1164
  return null;
1211
1165
  }
1212
1166
 
@@ -1250,7 +1204,6 @@ fn mirrorMove(
1250
1204
  battle: anytype,
1251
1205
  player: Player,
1252
1206
  mslot: u4,
1253
- rewrap: bool,
1254
1207
  auto: bool,
1255
1208
  residual: *bool,
1256
1209
  log: anytype,
@@ -1263,19 +1216,18 @@ fn mirrorMove(
1263
1216
  if (foe.last_used_move == .None or foe.last_used_move == .MirrorMove) {
1264
1217
  try log.fail(battle.active(player), .None);
1265
1218
  return null;
1266
- } else if (!showdown or foe.last_used_move != .Struggle) {
1267
- incrementPP(side, mslot);
1268
1219
  }
1269
1220
 
1221
+ incrementPP(side, mslot);
1222
+
1270
1223
  if (!try canMove(battle, player, mslot, auto, false, .MirrorMove, residual, log)) return null;
1271
- return doMove(battle, player, mslot, rewrap, auto, residual, log);
1224
+ return doMove(battle, player, mslot, auto, residual, log);
1272
1225
  }
1273
1226
 
1274
1227
  fn metronome(
1275
1228
  battle: anytype,
1276
1229
  player: Player,
1277
1230
  mslot: u4,
1278
- rewrap: bool,
1279
1231
  auto: bool,
1280
1232
  residual: *bool,
1281
1233
  log: anytype,
@@ -1286,7 +1238,7 @@ fn metronome(
1286
1238
  incrementPP(side, mslot);
1287
1239
 
1288
1240
  if (!try canMove(battle, player, mslot, auto, false, .Metronome, residual, log)) return null;
1289
- return doMove(battle, player, mslot, rewrap, auto, residual, log);
1241
+ return doMove(battle, player, mslot, auto, residual, log);
1290
1242
  }
1291
1243
 
1292
1244
  fn checkHit(battle: anytype, player: Player, move: Move.Data, log: anytype) !bool {
@@ -1493,7 +1445,11 @@ fn handleResidual(battle: anytype, player: Player, log: anytype) !void {
1493
1445
  stored.hp -= amount;
1494
1446
 
1495
1447
  // As above, Pokémon Showdown uses damageOf but its not relevant
1496
- if (amount > 0) try log.damage(ident, stored, .LeechSeed);
1448
+ if (amount > 0) {
1449
+ try log.damage(ident, stored, .LeechSeed);
1450
+ // Pokémon Showdown erroneously updates last damage with uncapped Leech Seed damage
1451
+ if (showdown) battle.last_damage = damage;
1452
+ }
1497
1453
 
1498
1454
  const before = foe_stored.hp;
1499
1455
  // Uncapped damage is added back to the foe
@@ -2026,9 +1982,9 @@ pub const Effects = struct {
2026
1982
  }
2027
1983
  break :has_mimic false;
2028
1984
  };
2029
- if (!has_mimic) {
2030
- // Invulnerable foes or 1/256 miss can trigger |-miss| instead of |-fail|
2031
- if (!try checkHit(battle, player, move, log)) return;
1985
+ // If the foe is Invulnerable we still want to fall through to checkHit to be able to
1986
+ // trigger |-miss| instead of |-fail|
1987
+ if (!has_mimic and !foe.active.volatiles.Invulnerable) {
2032
1988
  return try log.fail(battle.active(player.foe()), .None);
2033
1989
  }
2034
1990
  }
@@ -2296,7 +2252,7 @@ pub const Effects = struct {
2296
2252
  try log.transform(battle.active(player), foe_ident);
2297
2253
  }
2298
2254
 
2299
- fn binding(battle: anytype, player: Player, rewrap: bool) void {
2255
+ fn binding(battle: anytype, player: Player) void {
2300
2256
  var side = battle.side(player);
2301
2257
  var foe = battle.foe(player);
2302
2258
 
@@ -2304,9 +2260,7 @@ pub const Effects = struct {
2304
2260
  side.active.volatiles.Binding = true;
2305
2261
  // GLITCH: Hyper Beam automatic selection glitch if Recharging gets cleared on miss
2306
2262
  // (Pokémon Showdown unitentionally patches this glitch, preventing automatic selection)
2307
- if (!showdown) {
2308
- foe.active.volatiles.Recharging = false;
2309
- } else if (foe.stored().hp == 0) return if (!rewrap) battle.rng.advance(1);
2263
+ if (!showdown) foe.active.volatiles.Recharging = false;
2310
2264
 
2311
2265
  side.active.volatiles.attacks = Rolls.distribution(battle) - 1;
2312
2266
  }
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 message logging.
28
+ /// Namespace for helpers related to protocol trace 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.log` is enabled.
33
+ /// `options.trace` 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 protocol
45
- /// logging should additionally be turned off entirely with `options.log`.
44
+ /// `std.io.null_writer`. Ignores anything sent to it, though trace logging
45
+ /// should addtionally be turned off entirely by using `options.trace`.
46
46
  pub const NULL = @import("common/protocol.zig").NULL;
47
47
  };
48
48