@pkmn/engine 0.1.0-dev.5ec64ea5 → 0.1.0-dev.63cef311

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.
@@ -332,9 +332,7 @@ must be gleaned through the `Battle` state). Useful size and offset information
332
332
  the [`layout.json`](../../data/layout.json) which exists to simplify writing driver code.
333
333
 
334
334
  Documentation of the wire protocol used for logging traces when `-Dtrace` is enabled can be found in
335
- [PROTOCOL.md](../../../docs/PROTOCOL.md). Note that the
336
- [`pkmn-debug`](../../../README.md#pkmn-debug) tool exists to display the binary protocol and battle
337
- data in the browser for ease of debugging.
335
+ [PROTOCOL.md](../../../docs/PROTOCOL.md).
338
336
 
339
337
  ### `Battle`
340
338
 
@@ -469,7 +467,7 @@ data in the browser for ease of debugging.
469
467
 
470
468
  In addition to its alternative [RNG semantics](#rng), Pokémon Showdown's implementation of the first
471
469
  generation of Pokémon contains a number bugs, [many of which are known and have been documented on
472
- Smogon](https://www.smogon.com/forums/posts/5933177/show):
470
+ Smogon](https://www.smogon.com/forums/threads/rby-tradebacks-bug-report-thread.3524844/#post-5933177):
473
471
 
474
472
  - moves on Pokémon Showdown can do 0 damage instead of failing or causing a [division-by-zero
475
473
  freeze](https://pkmn.cc/bulba-glitch-1#Division_by_0).
@@ -484,21 +482,14 @@ Beyond the general bugs listed above, several move effects are implemented incor
484
482
  Showdown. Some of these moves are [too fundamentally broken to be implemented](#unimplementable) by
485
483
  the pkmn engine, but the following moves have their broken behavior preserved in `-Dshowdown` mode:
486
484
 
487
- - **Bide**: If the opponent faints after Bide inflicts damage on Pokémon Showdown residual damage
488
- incorrectly still gets applied to Bide's user.
489
485
  - **Leech Seed**: Leech Seed fails to heal its source side if a seeded target faints due to
490
- recoil/crash damage on Pokémon Showdown. Pokémon Showdown also updates the battle's last damage
491
- field with the uncapped Leech Seed damage which has consequences for Bide and Counter.
486
+ recoil/crash damage on Pokémon Showdown.
492
487
  - **Pay Day**: Pay Day should still scatter coins if it hits (but doesn't break) and opponent's
493
488
  Substitute but doesn't on Pokémon Showdown.
494
489
  - **Flinch**: Flinching does not get cleared during move selection on Pokémon Showdown and is
495
490
  instead cleared in Pokémon Showdown's "residual" phase, meaning the flinch status gets erroneously
496
491
  preserved across fainting (as fainting triggers Pokémon Showdown's "instaswitch" behavior which
497
492
  skips end-of-turn residuals).
498
- - **Rage**: Rage boosts should still result in burn/paralysis [stat modification
499
- errors](https://pkmn.cc/bulba-glitch-1#Stat_modification_errors) but don't on Pokémon Showdown.
500
- Furthermore, Pokémon Showdown only builds Rage for Disable/Explosion (hit/miss) when attacking
501
- into a Substitute instead of all moves.
502
493
  - **Thrash** / **Petal Dance**: once a Pokémon is locked into a thrashing move they will lose all
503
494
  speed ties on Pokémon Showdown (due to the fact that if they win the speed tie their action will
504
495
  then get "changed" and inserted back into the queue after their opponent's action).
@@ -517,8 +508,7 @@ the pkmn engine, but the following moves have their broken behavior preserved in
517
508
  implement by Pokémon Showdown.
518
509
  - **Roar** / **Whirlwind**: these moves can miss on Pokémon Showdown (and advance the RNG when
519
510
  checking) which is incorrect (these moves should always fail, but do not check accuracy or advance
520
- the RNG). More importantly, these moves should *not* cause the tracked last battle damage to be
521
- zeroed, but on Pokémon Showdown they do.
511
+ the RNG).
522
512
  - **Substitute**: in addition to the [Substitute + Confusion
523
513
  glitch](https://pkmn.cc/bulba-glitch-1#Substitute_.2B_Confusion_glitch) not being implemented
524
514
  correctly (covered above), the [Substitute 1/4
@@ -560,7 +550,7 @@ called out in the `|rule|` section at the beginning of a battle's log):
560
550
  `|rule|` section** in Generation I (or II) as the actual order of switches here does not have
561
551
  competitive implications like it does in Generation III, but it is still contrary to how the games
562
552
  work where the [host (Player 1)'s Pokemon would switch
563
- first](https://www.smogon.com/forums/posts/5989318/show).
553
+ first](https://www.smogon.com/forums/threads/gen-3-on-ps-final-fixes.3527268/post-5989318).
564
554
 
565
555
  Pokémon Showdown enforces several clauses *before* the battle: **Cleric Clause** (all Pokémon must
566
556
  have full HP and PP, and not have any status conditions prior to the battle), **Stat Tradeback
@@ -581,12 +571,13 @@ left up to a client to support.
581
571
 
582
572
  Numerous moves on Pokémon Showdown are broken not simply due to local issues in the implementation
583
573
  of their effects but instead due to **global issues** related to fundamental mechanics such as
584
- [broken move selection](https://www.smogon.com/forums/posts/8655897/show), using move name instead
585
- of slot to determine move identity, implementing volatile statuses incorrectly or not understanding
586
- how moves which call other moves work means that it isn't possible to completely implement Pokémon
587
- Showdown's behavior for the following moves (the pkmn engine attempts to reproduce as much of the
588
- behavior that can be reproduced without making data structure changes or dramatically deviating from
589
- the correct control flow):
574
+ [broken move
575
+ selection](https://www.smogon.com/forums/threads/rby-fight-button-simulation-improvements-fix-partial-trapping-improve-counter.3673280/#post-8655897),
576
+ using move name instead of slot to determine move identity, implementing volatile statuses
577
+ incorrectly or not understanding how moves which call other moves work means that it isn't possible
578
+ to completely implement Pokémon Showdown's behavior for the following moves (the pkmn engine
579
+ attempts to reproduce as much of the behavior that can be reproduced without making data structure
580
+ changes or dramatically deviating from the correct control flow):
590
581
 
591
582
  - **Wrap**: Binding moves like Wrap are implemented on Pokémon Showdown with an artificial
592
583
  `partialtrappinglock` volatile as opposed to how it works on the cartridge which simply relies on
@@ -675,10 +666,10 @@ compatibility mode, the implications of these differences are outlined below:
675
666
  Finally, the **initial 10-byte seed for link battles on Pokémon Red cannot include bytes larger than
676
667
  the `SERIAL_PREAMBLE_BYTE`**, so must be in the range $\left[0, 252\right]$. This has implications
677
668
  on the first 10 random numbers generated during the battle and has [**non-trivial competitive
678
- implications**](https://www.smogon.com/forums/posts/9068411/show) (at the start of the battle move
679
- effects become more likely, the ["1/256-miss" glitch](https://glitchcity.wiki/1/256_miss_glitch)
680
- cannot happen, Player 1 is more likely to win speed ties, etc) that Pokémon Showdown cannot
681
- replicate due to everything described above.
669
+ implications**](https://www.smogon.com/forums/threads/rby-tradebacks-bug-report-thread.3524844/page-16#post-9068411)
670
+ (at the start of the battle move effects become more likely, the ["1/256-miss"
671
+ glitch](https://glitchcity.wiki/1/256_miss_glitch) cannot happen, Player 1 is more likely to win
672
+ speed ties, etc) that Pokémon Showdown cannot replicate due to everything described above.
682
673
 
683
674
  All of places in the link battle code where randomness is required are outlined below:
684
675
 
@@ -777,8 +768,11 @@ The pkmn engine attempts to adhere to certain naming conventions:
777
768
  ## Resources
778
769
 
779
770
  - [pret/pokered](https://github.com/pret/pokered/) disassembly
780
- - [Gen I Main Battle Function](https://www.smogon.com/forums/posts/5878612/show) - Crystal_
771
+ - [Gen I Main Battle
772
+ Function](https://www.smogon.com/forums/threads/past-gens-research-thread.3506992/#post-5878612) -
773
+ Crystal_
781
774
  - [Pokemon Showdown!](https://github.com/smogon/pokemon-showdown)
782
775
  - [List of glitches (Generation
783
776
  I)](https://bulbapedia.bulbagarden.net/wiki/List_of_glitches_(Generation_I))
784
- - [Pokémon Showdown RBY Bugs](https://www.smogon.com/forums/posts/5933177/show)
777
+ - [Pokémon Showdown RBY
778
+ Bugs](https://www.smogon.com/forums/threads/rby-tradebacks-bug-report-thread.3524844/#post-5933177)
@@ -216,7 +216,11 @@ fn blocked(m: Move) bool {
216
216
  if (Move.get(m).effect == .Binding) return true;
217
217
  return switch (m) {
218
218
  .Counter,
219
+ // FIXME: test and see if this can be removed or document
220
+ .Rage,
219
221
  .Mimic,
222
+ // FIXME: test and see if this can be removed or document
223
+ .Bide,
220
224
  .Metronome,
221
225
  .MirrorMove,
222
226
  .Transform,
@@ -347,7 +347,6 @@ fn doTurn(
347
347
  if (try checkFaint(battle, player, log)) |r| return r;
348
348
  } else if (foe_choice.type == .Pass) return null;
349
349
 
350
- residual = true;
351
350
  replace = battle.side(foe_player).stored().hp == 0;
352
351
  if (try executeMove(battle, foe_player, foe_choice, foe_from, &residual, log)) |r| return r;
353
352
  if (!replace) {
@@ -435,7 +434,7 @@ fn executeMove(
435
434
 
436
435
  var skip_can = false;
437
436
  var skip_pp = false;
438
- switch (try beforeMove(battle, player, from, residual, log)) {
437
+ switch (try beforeMove(battle, player, from, log)) {
439
438
  .done => return null,
440
439
  .skip_can => skip_can = true,
441
440
  .skip_pp => skip_pp = true,
@@ -443,22 +442,14 @@ fn executeMove(
443
442
  .err => return @as(?Result, Result.Error),
444
443
  }
445
444
 
446
- if (!skip_can and !try canMove(battle, player, mslot, auto, skip_pp, from, residual, log)) {
447
- return null;
448
- }
445
+ if (!skip_can and !try canMove(battle, player, mslot, auto, skip_pp, from, log)) return null;
449
446
 
450
447
  return doMove(battle, player, mslot, auto, residual, log);
451
448
  }
452
449
 
453
450
  const BeforeMove = union(enum) { done, skip_can, skip_pp, ok, err };
454
451
 
455
- fn beforeMove(
456
- battle: anytype,
457
- player: Player,
458
- from: ?Move,
459
- residual: *bool,
460
- log: anytype,
461
- ) !BeforeMove {
452
+ fn beforeMove(battle: anytype, player: Player, from: ?Move, log: anytype) !BeforeMove {
462
453
  var side = battle.side(player);
463
454
  const foe = battle.foe(player);
464
455
  var active = &side.active;
@@ -544,7 +535,7 @@ fn beforeMove(
544
535
 
545
536
  if (confused) {
546
537
  assert(!volatiles.MultiHit);
547
- if (!volatiles.Rage) volatiles.state = 0;
538
+ volatiles.state = 0;
548
539
  volatiles.Bide = false;
549
540
  volatiles.Thrashing = false;
550
541
  volatiles.MultiHit = false;
@@ -585,7 +576,7 @@ fn beforeMove(
585
576
  battle.rng.next() < Gen12.percent(25);
586
577
 
587
578
  if (paralyzed) {
588
- if (!volatiles.Rage) volatiles.state = 0;
579
+ volatiles.state = 0;
589
580
  volatiles.Bide = false;
590
581
  volatiles.Thrashing = false;
591
582
  volatiles.Charging = false;
@@ -620,18 +611,7 @@ fn beforeMove(
620
611
  return .done;
621
612
  }
622
613
 
623
- const sub = showdown and foe.active.volatiles.Substitute;
624
614
  _ = try applyDamage(battle, player.foe(), player.foe(), .None, log);
625
- if (foe.stored().hp > 0 and !sub) try buildRage(battle, player.foe(), log);
626
-
627
- // For reasons passing understanding, Pokémon Showdown still inflicts residual damage to
628
- // Bide's user even if the above damage has caused the foe to faint. It's simpler to always
629
- // run residual here regardless of whether the foe fainted and opt-out of the default flow
630
- if (showdown) {
631
- residual.* = false;
632
- try handleResidual(battle, player, log);
633
- }
634
-
635
615
  return .done;
636
616
  }
637
617
 
@@ -674,7 +654,6 @@ fn canMove(
674
654
  auto: bool,
675
655
  skip_pp: bool,
676
656
  from: ?Move,
677
- residual: *bool,
678
657
  log: anytype,
679
658
  ) !bool {
680
659
  var side = battle.side(player);
@@ -709,7 +688,7 @@ fn canMove(
709
688
  try log.move(player_ident, side.last_selected_move, battle.active(target), f);
710
689
 
711
690
  if (move.effect.onBegin()) {
712
- try onBegin(battle, player, move, mslot, residual, log);
691
+ try onBegin(battle, player, move, mslot, log);
713
692
  return false;
714
693
  }
715
694
 
@@ -788,21 +767,16 @@ fn doMove(
788
767
  (@enumToInt(move.type.effectiveness(foe.active.types.type1)) == 0 or
789
768
  @enumToInt(move.type.effectiveness(foe.active.types.type2)) == 0);
790
769
  if (immune and move.effect != .Binding) break :miss true;
791
- if (move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe) {
792
- battle.last_damage = 0;
793
- break :miss true;
794
- }
770
+ if (move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe) break :miss true;
795
771
  break :miss move.target != .Self and !moveHit(battle, player, move, &immune, &mist);
796
772
  };
797
773
  assert(!immune or miss or (showdown and move.effect == .Binding));
798
774
 
799
- var late = showdown;
800
775
  const skip = status or immune;
801
776
  if ((!showdown or (!skip or counter)) and !miss) blk: {
802
777
  if (showdown and move.effect.isMulti()) {
803
778
  Effects.multiHit(battle, player, move);
804
779
  hits = side.active.volatiles.attacks;
805
- late = false;
806
780
  }
807
781
 
808
782
  // Cartridge rolls for crit even for moves that can't crit (Counter/Metronome/status/OHKO)
@@ -836,7 +810,7 @@ fn doMove(
836
810
  }
837
811
 
838
812
  // Due to control flow shenanigans we need to clear last_damage for Pokémon Showdown
839
- if (showdown and skip) battle.last_damage = 0;
813
+ if (showdown and skip and move.effect != .Bide) battle.last_damage = 0;
840
814
 
841
815
  miss = if (showdown or skip)
842
816
  miss
@@ -883,9 +857,15 @@ fn doMove(
883
857
  if (showdown and side.stored().hp == 0) residual.* = false;
884
858
  } else if (move.effect == .Explode) {
885
859
  try Effects.explode(battle, player);
886
- try buildRage(battle, player.foe(), log);
887
- } else if (showdown and move.effect == .Disable) {
888
- try buildRage(battle, player.foe(), log);
860
+ if (foe.active.volatiles.Rage and foe.active.boosts.atk < 6) {
861
+ try Effects.boost(battle, player.foe(), Move.get(.Rage), log);
862
+ }
863
+ } else if (showdown) {
864
+ if (move.effect == .Disable) {
865
+ if (foe.active.volatiles.Rage and foe.active.boosts.atk < 6) {
866
+ try Effects.boost(battle, player.foe(), Move.get(.Rage), log);
867
+ }
868
+ }
889
869
  }
890
870
  return null;
891
871
  }
@@ -898,9 +878,6 @@ fn doMove(
898
878
  hits = side.active.volatiles.attacks;
899
879
  }
900
880
 
901
- // Pokémon Showdown only builds Rage for Disable/Explosion (hit/miss) when attacking into a sub
902
- const sub = showdown and foe.active.volatiles.Substitute;
903
-
904
881
  var nullified = false;
905
882
  var hit: u4 = 0;
906
883
  while (hit < hits) {
@@ -916,7 +893,9 @@ fn doMove(
916
893
  if (hit == 0 and ohko) try log.ohko();
917
894
  hit += 1;
918
895
  if (foe.stored().hp == 0) break;
919
- if (!late and !sub) try buildRage(battle, player.foe(), log);
896
+ if (foe.active.volatiles.Rage and foe.active.boosts.atk < 6) {
897
+ try Effects.boost(battle, player.foe(), Move.get(.Rage), log);
898
+ }
920
899
  // If the substitute breaks during a multi-hit attack, the attack ends
921
900
  if (nullified) break;
922
901
  }
@@ -953,9 +932,6 @@ fn doMove(
953
932
 
954
933
  if (foe.stored().hp == 0) return null;
955
934
 
956
- // Pokémon Showdown builds Rage at the wrong time for non-MultiHit move
957
- if (late and !sub and move.effect != .Disable) try buildRage(battle, player.foe(), log);
958
-
959
935
  if (!move.effect.isSpecial()) {
960
936
  // On the cartridge Rage is not considered to be "special" and thus gets executed for a
961
937
  // second time here (after being executed in the "always happens" block above) but that
@@ -967,9 +943,7 @@ fn doMove(
967
943
  if (move.effect == .Twineedle) {
968
944
  if (!showdown) try Effects.poison(battle, player, Move.get(.PoisonSting), log);
969
945
  } else if (move.effect == .Disable) {
970
- const result = try Effects.disable(battle, player, move, log);
971
- if (showdown) try buildRage(battle, player.foe(), log);
972
- return result;
946
+ return Effects.disable(battle, player, move, log);
973
947
  } else {
974
948
  try moveEffect(battle, player, move, log);
975
949
  }
@@ -1107,12 +1081,11 @@ fn randomizeDamage(battle: anytype) void {
1107
1081
 
1108
1082
  fn specialDamage(battle: anytype, player: Player, move: Move.Data, log: anytype) !?Result {
1109
1083
  const side = battle.side(player);
1110
- const foe = battle.foe(player);
1111
1084
 
1112
1085
  if (!try checkHit(battle, player, move, log)) return null;
1113
1086
 
1114
1087
  battle.last_damage = switch (side.last_selected_move) {
1115
- .SuperFang => @max(foe.stored().hp / 2, 1),
1088
+ .SuperFang => @max(battle.foe(player).stored().hp / 2, 1),
1116
1089
  .SeismicToss, .NightShade => side.stored().level,
1117
1090
  .SonicBoom => 20,
1118
1091
  .DragonRage => 40,
@@ -1135,10 +1108,7 @@ fn specialDamage(battle: anytype, player: Player, move: Move.Data, log: anytype)
1135
1108
 
1136
1109
  if (battle.last_damage == 0) return if (showdown) null else Result.Error;
1137
1110
 
1138
- const sub = showdown and foe.active.volatiles.Substitute;
1139
1111
  _ = try applyDamage(battle, player.foe(), player.foe(), .None, log);
1140
- if (battle.foe(player).stored().hp > 0 and !sub) try buildRage(battle, player.foe(), log);
1141
-
1142
1112
  return null;
1143
1113
  }
1144
1114
 
@@ -1246,7 +1216,7 @@ fn mirrorMove(
1246
1216
 
1247
1217
  incrementPP(side, mslot);
1248
1218
 
1249
- if (!try canMove(battle, player, mslot, auto, false, .MirrorMove, residual, log)) return null;
1219
+ if (!try canMove(battle, player, mslot, auto, false, .MirrorMove, log)) return null;
1250
1220
  return doMove(battle, player, mslot, auto, residual, log);
1251
1221
  }
1252
1222
 
@@ -1275,7 +1245,7 @@ fn metronome(
1275
1245
 
1276
1246
  incrementPP(side, mslot);
1277
1247
 
1278
- if (!try canMove(battle, player, mslot, auto, false, .Metronome, residual, log)) return null;
1248
+ if (!try canMove(battle, player, mslot, auto, false, .Metronome, log)) return null;
1279
1249
  return doMove(battle, player, mslot, auto, residual, log);
1280
1250
  }
1281
1251
 
@@ -1461,15 +1431,6 @@ fn handleResidual(battle: anytype, player: Player, log: anytype) !void {
1461
1431
  }
1462
1432
 
1463
1433
  if (volatiles.LeechSeed) {
1464
- var foe = battle.foe(player);
1465
- var foe_stored = foe.stored();
1466
- const foe_ident = battle.active(player.foe());
1467
-
1468
- if (foe_stored.hp == 0) {
1469
- assert(showdown);
1470
- return;
1471
- }
1472
-
1473
1434
  var damage = @max(stored.stats.hp / 16, 1);
1474
1435
 
1475
1436
  // GLITCH: Leech Seed + Toxic glitch
@@ -1478,12 +1439,13 @@ fn handleResidual(battle: anytype, player: Player, log: anytype) !void {
1478
1439
  damage *= volatiles.toxic;
1479
1440
  }
1480
1441
 
1481
- // Pokémon Showdown erroneously updates last damage with uncapped Leech Seed damage
1482
- if (showdown) battle.last_damage = damage;
1483
-
1484
1442
  const amount = @min(damage, stored.hp);
1485
1443
  stored.hp -= amount;
1486
1444
 
1445
+ var foe = battle.foe(player);
1446
+ var foe_stored = foe.stored();
1447
+ const foe_ident = battle.active(player.foe());
1448
+
1487
1449
  // As above, Pokémon Showdown uses damageOf but its not relevant
1488
1450
  if (amount > 0) try log.damage(ident, stored, .LeechSeed);
1489
1451
 
@@ -1572,14 +1534,7 @@ fn checkEBC(battle: anytype) bool {
1572
1534
  return false;
1573
1535
  }
1574
1536
 
1575
- inline fn onBegin(
1576
- battle: anytype,
1577
- player: Player,
1578
- move: Move.Data,
1579
- mslot: u8,
1580
- residual: *bool,
1581
- log: anytype,
1582
- ) !void {
1537
+ inline fn onBegin(battle: anytype, player: Player, move: Move.Data, mslot: u8, log: anytype) !void {
1583
1538
  assert(move.effect.onBegin());
1584
1539
  return switch (move.effect) {
1585
1540
  .Confusion => Effects.confusion(battle, player, move, log),
@@ -1595,7 +1550,7 @@ inline fn onBegin(
1595
1550
  .Poison => Effects.poison(battle, player, move, log),
1596
1551
  .Reflect => Effects.reflect(battle, player, log),
1597
1552
  .Splash => Effects.splash(battle, player, log),
1598
- .Substitute => Effects.substitute(battle, player, residual, log),
1553
+ .Substitute => Effects.substitute(battle, player, log),
1599
1554
  .SwitchAndTeleport => Effects.switchAndTeleport(battle, player, move, log),
1600
1555
  .Transform => Effects.transform(battle, player, log),
1601
1556
  else => unreachable,
@@ -2231,7 +2186,7 @@ pub const Effects = struct {
2231
2186
  try log.activate(battle.active(player), .Splash);
2232
2187
  }
2233
2188
 
2234
- fn substitute(battle: anytype, player: Player, residual: *bool, log: anytype) !void {
2189
+ fn substitute(battle: anytype, player: Player, log: anytype) !void {
2235
2190
  var side = battle.side(player);
2236
2191
  if (side.active.volatiles.Substitute) {
2237
2192
  try log.fail(battle.active(player), .Substitute);
@@ -2256,18 +2211,14 @@ pub const Effects = struct {
2256
2211
  side.active.volatiles.substitute = hp + 1;
2257
2212
  side.active.volatiles.Substitute = true;
2258
2213
  try log.start(battle.active(player), .Substitute);
2259
- if (hp > 0) {
2260
- try log.damage(battle.active(player), side.stored(), .None);
2261
- if (showdown and side.stored().hp == 0) residual.* = false;
2262
- }
2214
+ if (hp > 0) try log.damage(battle.active(player), side.stored(), .None);
2263
2215
  }
2264
2216
 
2265
2217
  fn switchAndTeleport(battle: anytype, player: Player, move: Move.Data, log: anytype) !void {
2266
2218
  if (!showdown or battle.side(player).last_selected_move == .Teleport) return;
2267
2219
 
2268
- // Whirlwind/Roar should not roll to hit/reset damage but Pokémon Showdown does anyway
2220
+ // Whirlwind/Roar should not roll to hit but Pokémon Showdown does anyway
2269
2221
  _ = try checkHit(battle, player, move, log);
2270
- battle.last_damage = 0;
2271
2222
  }
2272
2223
 
2273
2224
  fn thrashing(battle: anytype, player: Player) void {
@@ -2352,8 +2303,6 @@ pub const Effects = struct {
2352
2303
  const stat = unmodifiedStats(battle, player).atk;
2353
2304
  stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
2354
2305
  try log.boost(ident, reason, n);
2355
- // Pokémon Showdown doesn't re-apply status modifiers after Rage boosts
2356
- if (showdown and move.effect == .Rage) return;
2357
2306
  },
2358
2307
  .DefenseUp1, .DefenseUp2 => {
2359
2308
  assert(boosts.def >= -6 and boosts.def <= 6);
@@ -2548,7 +2497,7 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
2548
2497
  }
2549
2498
  }
2550
2499
 
2551
- inline fn isForced(active: ActivePokemon) bool {
2500
+ fn isForced(active: ActivePokemon) bool {
2552
2501
  return active.volatiles.Recharging or active.volatiles.Rage or
2553
2502
  active.volatiles.Thrashing or active.volatiles.Charging;
2554
2503
  }
@@ -2604,27 +2553,15 @@ fn disabled(side: *Side, ident: ID, log: anytype) !bool {
2604
2553
  if (side.active.volatiles.disabled_move != 0) {
2605
2554
  // A Pokémon that transforms after being disabled may end up with less move slots
2606
2555
  const m = side.active.moves[side.active.volatiles.disabled_move - 1].id;
2607
- // side.last_selected_move can be Struggle here on Pokemon Showdown we need an extra check
2608
- const last = if (showdown and m == .Bide and side.active.volatiles.Bide)
2609
- m
2610
- else
2611
- side.last_selected_move;
2612
- if (m != .None and m == last) {
2556
+ if (m != .None and m == side.last_selected_move) {
2613
2557
  side.active.volatiles.Charging = false;
2614
- try log.disabled(ident, last);
2558
+ try log.disabled(ident, side.last_selected_move);
2615
2559
  return true;
2616
2560
  }
2617
2561
  }
2618
2562
  return false;
2619
2563
  }
2620
2564
 
2621
- inline fn buildRage(battle: anytype, who: Player, log: anytype) !void {
2622
- const side = battle.side(who);
2623
- if (side.active.volatiles.Rage and side.active.boosts.atk < 6) {
2624
- try Effects.boost(battle, who, Move.get(.Rage), log);
2625
- }
2626
- }
2627
-
2628
2565
  fn handleThrashing(battle: anytype, active: *ActivePokemon) bool {
2629
2566
  var volatiles = &active.volatiles;
2630
2567
  assert(volatiles.Thrashing);
@@ -2773,11 +2710,9 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
2773
2710
  while (slot <= 4) : (slot += 1) {
2774
2711
  const m = active.moves[slot - 1];
2775
2712
  if (m.id == .None) break;
2776
- if (m.id == if (active.volatiles.Bide) .Bide else side.last_selected_move) {
2777
- // Pokémon Showdown displays Struggle if limited to Bide but unable to pick
2778
- const struggle =
2779
- m.id == .Bide and (m.pp == 0 or active.volatiles.disabled_move == slot);
2780
- const s = if (struggle) 0 else slot;
2713
+ if (m.id == side.last_selected_move) {
2714
+ // Pokémon Showdown displays Struggle if limited to Bide when out of PP
2715
+ const s = if (m.pp == 0 and m.id == .Bide) 0 else slot;
2781
2716
  out[n] = .{ .type = .Move, .data = s };
2782
2717
  n += 1;
2783
2718
  return n;
@@ -1,7 +0,0 @@
1
- import { Choice, Player, Result } from '.';
2
- export declare function check(showdown: boolean): void;
3
- export declare function supports(showdown: boolean, trace?: boolean): boolean;
4
- export declare function update(index: number, showdown: boolean, battle: ArrayBuffer, c1?: Choice, c2?: Choice, log?: ArrayBuffer): Result;
5
- export declare function choices(index: number, showdown: boolean, battle: ArrayBuffer, player: Player, choice: Choice['type'], buf: ArrayBuffer): Choice[];
6
- export declare function choose(index: number, showdown: boolean, battle: ArrayBuffer, player: Player, choice: Choice['type'], buf: ArrayBuffer, fn: (n: number) => number): Choice;
7
- export declare function size(index: number, type: 'options' | 'log'): number;
@@ -1,111 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
- Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.size = exports.choose = exports.choices = exports.update = exports.supports = exports.check = void 0;
27
- const path = __importStar(require("path"));
28
- const _1 = require(".");
29
- const ROOT = path.join(__dirname, '..', '..');
30
- let ADDON = undefined;
31
- function load() {
32
- if (ADDON)
33
- return ADDON;
34
- let pkmn = undefined;
35
- let showdown = undefined;
36
- try {
37
- pkmn = require(path.join(ROOT, 'build', 'lib', 'pkmn.node'));
38
- }
39
- catch { }
40
- try {
41
- showdown = require(path.join(ROOT, 'build', 'lib', 'pkmn-showdown.node'));
42
- }
43
- catch { }
44
- if (!pkmn && !showdown) {
45
- throw new Error('Could not find native addons - did you run `npx install-pkmn-engine`?');
46
- }
47
- return (ADDON = [pkmn, showdown]);
48
- }
49
- function check(showdown) {
50
- if (!load()[+showdown]) {
51
- const opts = ADDON[+!showdown].options.trace ? ['-Dtrace'] : [];
52
- if (showdown)
53
- opts.push('-Dshowdown');
54
- throw new Error(`@pkmn/engine has ${showdown ? 'not' : 'only'} been configured to support Pokémon Showdown.` +
55
- `\n(running \`npx install-pkmn-engine --options='${opts.join(' ')}'\` can fix this issue).`);
56
- }
57
- }
58
- exports.check = check;
59
- function supports(showdown, trace) {
60
- if (!load()[+showdown])
61
- return false;
62
- if (trace === undefined)
63
- return true;
64
- return ADDON[+showdown].options.trace === trace;
65
- }
66
- exports.supports = supports;
67
- function update(index, showdown, battle, c1, c2, log) {
68
- return _1.Result.decode(ADDON[+showdown].bindings[index]
69
- .update(battle, _1.Choice.encode(c1), _1.Choice.encode(c2), log));
70
- }
71
- exports.update = update;
72
- function choices(index, showdown, battle, player, choice, buf) {
73
- const request = choice[0] === 'p' ? 0 : choice[0] === 'm' ? 1 : 2;
74
- const n = ADDON[+showdown].bindings[index].choices(battle, +(player !== 'p1'), request, buf);
75
- // The top-level API signature means our hands our tied with respect to
76
- // writing really fast bindings here. The simplest approach would be to return
77
- // the ArrayBuffer the bindings populate as well as its size and only decode a
78
- // Choice after the selection. However, given that we need to return
79
- // `Choices[]` we need to decode all of them even if they're not all being
80
- // used which is wasteful. This shouldn't be *that* bad as its a very small
81
- // list, but its still wasted work. Switching the top-level API to return an
82
- // Iterable<Choice> doesn't help as we need both the length and the ability to
83
- // randomly access it, so the best way to make the current API fast would be
84
- // to have the Zig bindings create Choice objects directly, only that won't
85
- // scale well as it would require us to basically rewrite the low-level
86
- // choices function for each generation within node.zig. We could do something
87
- // really galaxy-brained and return some sort of frankenstein subclass of
88
- // Array backed by ArrayBuffer which would lazily decode the Choice on access,
89
- // but thats ultimately not worth the effort. You can't have both a high-level
90
- // idiomatic API and performance here, hence why the choose function below
91
- // exists.
92
- const options = new Array(n);
93
- const data = new Uint8Array(buf);
94
- for (let i = 0; i < n; i++)
95
- options[i] = _1.Choice.decode(data[i]);
96
- return options;
97
- }
98
- exports.choices = choices;
99
- function choose(index, showdown, battle, player, choice, buf, fn) {
100
- const request = choice[0] === 'p' ? 0 : choice[0] === 'm' ? 1 : 2;
101
- const n = ADDON[+showdown].bindings[index].choices(battle, +(player !== 'p1'), request, buf);
102
- const data = new Uint8Array(buf);
103
- return _1.Choice.decode(data[fn(n)]);
104
- }
105
- exports.choose = choose;
106
- function size(index, type) {
107
- const bindings = (ADDON[1] ?? ADDON[0]).bindings[index];
108
- return type[0] === 'o' ? bindings.OPTIONS_SIZE : bindings.LOGS_SIZE;
109
- }
110
- exports.size = size;
111
- //# sourceMappingURL=addon.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"addon.js","sourceRoot":"","sources":["../../src/pkg/addon.ts"],"names":[],"mappings":"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"}