@pkmn/engine 0.1.0-dev.cf6f5d2c → 0.1.0-dev.d97c6e03

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;
@@ -129,26 +126,17 @@ fn selectMove(
129
126
  (choice.type == .Move and choice.data == @boolToInt(showdown)));
130
127
 
131
128
  // pre-battle menu
132
- if (volatiles.Recharging) {
133
- if (showdown and battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
134
- return null;
135
- }
129
+ if (volatiles.Recharging) return null;
136
130
  if (volatiles.Rage) {
137
131
  from.* = side.last_used_move;
138
- if (showdown) {
139
- if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
140
- saveMove(battle, player, null);
141
- }
132
+ if (showdown) saveMove(battle, player, null);
142
133
  return null;
143
134
  }
144
135
  // Pokémon Showdown removes Flinch at the end-of-turn in its residual handler
145
136
  if (!showdown) volatiles.Flinch = false;
146
137
  if (volatiles.Thrashing or volatiles.Charging) {
147
138
  from.* = side.last_selected_move;
148
- if (showdown) {
149
- if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
150
- saveMove(battle, player, null);
151
- }
139
+ if (showdown) saveMove(battle, player, null);
152
140
  return null;
153
141
  }
154
142
 
@@ -158,10 +146,7 @@ fn selectMove(
158
146
  // pre-move select
159
147
  if (Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP) or volatiles.Bide) {
160
148
  assert(showdown or choice.data == 0);
161
- if (showdown) {
162
- if (volatiles.Bide and battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
163
- saveMove(battle, player, choice);
164
- }
149
+ if (showdown) saveMove(battle, player, choice);
165
150
  return null;
166
151
  }
167
152
  if (volatiles.Binding) {
@@ -192,7 +177,6 @@ fn selectMove(
192
177
  }
193
178
 
194
179
  if (battle.foe(player).active.volatiles.Binding) {
195
- skip_turn.* = true;
196
180
  if (showdown) {
197
181
  saveMove(battle, player, choice);
198
182
  } else {
@@ -343,13 +327,9 @@ fn doTurn(
343
327
  battle: anytype,
344
328
  player: Player,
345
329
  player_choice: Choice,
346
- player_rewrap: bool,
347
- player_skip: bool,
348
330
  player_from: ?Move,
349
331
  foe_player: Player,
350
332
  foe_choice: Choice,
351
- foe_rewrap: bool,
352
- foe_skip: bool,
353
333
  foe_from: ?Move,
354
334
  log: anytype,
355
335
  ) !?Result {
@@ -357,16 +337,7 @@ fn doTurn(
357
337
 
358
338
  var residual = true;
359
339
  var replace = battle.side(player).stored().hp == 0;
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;
340
+ if (try executeMove(battle, player, player_choice, player_from, &residual, log)) |r| return r;
370
341
  if (!replace) {
371
342
  if (player_choice.type != .Switch) {
372
343
  if (try checkFaint(battle, foe_player, log)) |r| return r;
@@ -377,16 +348,7 @@ fn doTurn(
377
348
 
378
349
  residual = true;
379
350
  replace = battle.side(foe_player).stored().hp == 0;
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;
351
+ if (try executeMove(battle, foe_player, foe_choice, foe_from, &residual, log)) |r| return r;
390
352
  if (!replace) {
391
353
  if (foe_choice.type != .Switch) {
392
354
  if (try checkFaint(battle, player, log)) |r| return r;
@@ -409,8 +371,6 @@ fn executeMove(
409
371
  battle: anytype,
410
372
  player: Player,
411
373
  choice: Choice,
412
- rewrap: bool,
413
- skip: bool,
414
374
  from: ?Move,
415
375
  residual: *bool,
416
376
  log: anytype,
@@ -422,10 +382,9 @@ fn executeMove(
422
382
  return null;
423
383
  }
424
384
 
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
428
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
429
388
  assert(!showdown);
430
389
  if (battle.foe(player).active.volatiles.Binding) {
431
390
  try log.cant(battle.active(player), .Bound);
@@ -475,7 +434,7 @@ fn executeMove(
475
434
 
476
435
  var skip_can = false;
477
436
  var skip_pp = false;
478
- switch (try beforeMove(battle, player, skip, from, residual, log)) {
437
+ switch (try beforeMove(battle, player, from, residual, log)) {
479
438
  .done => return null,
480
439
  .skip_can => skip_can = true,
481
440
  .skip_pp => skip_pp = true,
@@ -487,7 +446,7 @@ fn executeMove(
487
446
  return null;
488
447
  }
489
448
 
490
- return doMove(battle, player, mslot, rewrap, auto, residual, log);
449
+ return doMove(battle, player, mslot, auto, residual, log);
491
450
  }
492
451
 
493
452
  const BeforeMove = enum { done, skip_can, skip_pp, ok, err };
@@ -495,7 +454,6 @@ const BeforeMove = enum { done, skip_can, skip_pp, ok, err };
495
454
  fn beforeMove(
496
455
  battle: anytype,
497
456
  player: Player,
498
- skip: bool,
499
457
  from: ?Move,
500
458
  residual: *bool,
501
459
  log: anytype,
@@ -527,15 +485,13 @@ fn beforeMove(
527
485
  return .done;
528
486
  }
529
487
 
530
- if (skip or foe.active.volatiles.Binding) {
488
+ if (foe.active.volatiles.Binding) {
531
489
  try log.cant(ident, .Bound);
532
490
  return .done;
533
491
  }
534
492
 
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;
493
+ if (!showdown and volatiles.Flinch) {
494
+ volatiles.Flinch = false;
539
495
  try log.cant(ident, .Flinch);
540
496
  return .done;
541
497
  }
@@ -556,6 +512,14 @@ fn beforeMove(
556
512
  // Pokémon Showdown's disable condition has a single onBeforeMove handler
557
513
  if (showdown and try disabled(side, ident, log)) return .done;
558
514
 
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
+
559
523
  // This can only happen if a Pokémon started the battle frozen/sleeping and was thawed/woken
560
524
  // before the side had a selected a move - we simply need to assume this leads to a desync
561
525
  if (side.last_selected_move == .None) {
@@ -671,13 +635,8 @@ fn beforeMove(
671
635
  try log.start(battle.active(player), .ConfusionSilent);
672
636
  }
673
637
  try log.move(ident, side.last_selected_move, battle.active(player.foe()), from);
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
- }
638
+ if (showdown and handleThrashing(battle, active)) {
639
+ try log.start(battle.active(player), .ConfusionSilent);
681
640
  }
682
641
  return .skip_can;
683
642
  }
@@ -692,11 +651,7 @@ fn beforeMove(
692
651
  side.last_selected_move,
693
652
  );
694
653
  if (showdown or battle.last_damage != 0) {
695
- const sub = showdown and foe.active.volatiles.Substitute;
696
654
  _ = 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
- }
700
655
  }
701
656
  return .done;
702
657
  }
@@ -754,7 +709,7 @@ fn canMove(
754
709
  Effects.thrashing(battle, player);
755
710
  } else if (!showdown and move.effect == .Binding) {
756
711
  // Pokémon Showdown handles this after hit/miss checks and damage calculation
757
- Effects.binding(battle, player, false);
712
+ Effects.binding(battle, player);
758
713
  }
759
714
 
760
715
  return true;
@@ -795,7 +750,6 @@ fn doMove(
795
750
  battle: anytype,
796
751
  player: Player,
797
752
  mslot: u4,
798
- rewrap: bool,
799
753
  auto: bool,
800
754
  residual: *bool,
801
755
  log: anytype,
@@ -887,9 +841,9 @@ fn doMove(
887
841
 
888
842
  if (!showdown or !miss) {
889
843
  if (move.effect == .MirrorMove) {
890
- return mirrorMove(battle, player, mslot, rewrap, auto, residual, log);
844
+ return mirrorMove(battle, player, mslot, auto, residual, log);
891
845
  } else if (move.effect == .Metronome) {
892
- return metronome(battle, player, mslot, rewrap, auto, residual, log);
846
+ return metronome(battle, player, mslot, auto, residual, log);
893
847
  } else if (move.effect.onEnd()) {
894
848
  try onEnd(battle, player, move, log);
895
849
  return null;
@@ -904,7 +858,7 @@ fn doMove(
904
858
  move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe;
905
859
  if (ohko) {
906
860
  try log.immune(foe_ident, .OHKO);
907
- } else if (immune and !invulnerable and (!showdown or move.effect != .Binding)) {
861
+ } else if (immune and !invulnerable) {
908
862
  try log.immune(foe_ident, .None);
909
863
  } else if (mist) {
910
864
  if (!foe.active.volatiles.Substitute) try log.activate(foe_ident, .Mist);
@@ -970,17 +924,11 @@ fn doMove(
970
924
  } else if (showdown) {
971
925
  // This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
972
926
  if (move.effect == .Binding) {
973
- Effects.binding(battle, player, rewrap);
927
+ Effects.binding(battle, player);
974
928
  if (immune) {
975
929
  battle.last_damage = 0;
976
- assert(foe.stored().hp > 0);
977
930
  // Pokémon Showdown logs |-damage| here instead of |-immune| because logic...
978
- if (sub) {
979
- try log.activate(battle.active(player.foe()), .Substitute);
980
- } else {
981
- try log.damage(battle.active(player.foe()), foe.stored(), .None);
982
- try buildRage(battle, player.foe(), log);
983
- }
931
+ try log.damage(battle.active(player.foe()), foe.stored(), .None);
984
932
  return null;
985
933
  }
986
934
  }
@@ -1213,9 +1161,7 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, log: anytype)
1213
1161
  // Pokémon Showdown calls checkHit before Counter
1214
1162
  if (!showdown and !try checkHit(battle, player, move, log)) return null;
1215
1163
 
1216
- const sub = showdown and foe.active.volatiles.Substitute;
1217
1164
  _ = try applyDamage(battle, player.foe(), player.foe(), .None, log);
1218
- if (battle.foe(player).stored().hp > 0 and !sub) try buildRage(battle, player.foe(), log);
1219
1165
  return null;
1220
1166
  }
1221
1167
 
@@ -1259,7 +1205,6 @@ fn mirrorMove(
1259
1205
  battle: anytype,
1260
1206
  player: Player,
1261
1207
  mslot: u4,
1262
- rewrap: bool,
1263
1208
  auto: bool,
1264
1209
  residual: *bool,
1265
1210
  log: anytype,
@@ -1272,19 +1217,18 @@ fn mirrorMove(
1272
1217
  if (foe.last_used_move == .None or foe.last_used_move == .MirrorMove) {
1273
1218
  try log.fail(battle.active(player), .None);
1274
1219
  return null;
1275
- } else if (!showdown or foe.last_used_move != .Struggle) {
1276
- incrementPP(side, mslot);
1277
1220
  }
1278
1221
 
1222
+ incrementPP(side, mslot);
1223
+
1279
1224
  if (!try canMove(battle, player, mslot, auto, false, .MirrorMove, residual, log)) return null;
1280
- return doMove(battle, player, mslot, rewrap, auto, residual, log);
1225
+ return doMove(battle, player, mslot, auto, residual, log);
1281
1226
  }
1282
1227
 
1283
1228
  fn metronome(
1284
1229
  battle: anytype,
1285
1230
  player: Player,
1286
1231
  mslot: u4,
1287
- rewrap: bool,
1288
1232
  auto: bool,
1289
1233
  residual: *bool,
1290
1234
  log: anytype,
@@ -1295,7 +1239,7 @@ fn metronome(
1295
1239
  incrementPP(side, mslot);
1296
1240
 
1297
1241
  if (!try canMove(battle, player, mslot, auto, false, .Metronome, residual, log)) return null;
1298
- return doMove(battle, player, mslot, rewrap, auto, residual, log);
1242
+ return doMove(battle, player, mslot, auto, residual, log);
1299
1243
  }
1300
1244
 
1301
1245
  fn checkHit(battle: anytype, player: Player, move: Move.Data, log: anytype) !bool {
@@ -1502,7 +1446,11 @@ fn handleResidual(battle: anytype, player: Player, log: anytype) !void {
1502
1446
  stored.hp -= amount;
1503
1447
 
1504
1448
  // As above, Pokémon Showdown uses damageOf but its not relevant
1505
- if (amount > 0) try log.damage(ident, stored, .LeechSeed);
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
+ }
1506
1454
 
1507
1455
  const before = foe_stored.hp;
1508
1456
  // Uncapped damage is added back to the foe
@@ -2035,9 +1983,9 @@ pub const Effects = struct {
2035
1983
  }
2036
1984
  break :has_mimic false;
2037
1985
  };
2038
- if (!has_mimic) {
2039
- // Invulnerable foes or 1/256 miss can trigger |-miss| instead of |-fail|
2040
- if (!try checkHit(battle, player, move, log)) return;
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) {
2041
1989
  return try log.fail(battle.active(player.foe()), .None);
2042
1990
  }
2043
1991
  }
@@ -2305,7 +2253,7 @@ pub const Effects = struct {
2305
2253
  try log.transform(battle.active(player), foe_ident);
2306
2254
  }
2307
2255
 
2308
- fn binding(battle: anytype, player: Player, rewrap: bool) void {
2256
+ fn binding(battle: anytype, player: Player) void {
2309
2257
  var side = battle.side(player);
2310
2258
  var foe = battle.foe(player);
2311
2259
 
@@ -2313,9 +2261,7 @@ pub const Effects = struct {
2313
2261
  side.active.volatiles.Binding = true;
2314
2262
  // GLITCH: Hyper Beam automatic selection glitch if Recharging gets cleared on miss
2315
2263
  // (Pokémon Showdown unitentionally patches this glitch, preventing automatic selection)
2316
- if (!showdown) {
2317
- foe.active.volatiles.Recharging = false;
2318
- } else if (foe.stored().hp == 0) return if (!rewrap) battle.rng.advance(1);
2264
+ if (!showdown) foe.active.volatiles.Recharging = false;
2319
2265
 
2320
2266
  side.active.volatiles.attacks = Rolls.distribution(battle) - 1;
2321
2267
  }
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