@pkmn/engine 0.1.0-dev.04c4d944 → 0.1.0-dev.1336408

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.
@@ -7,8 +7,6 @@ const DEBUG = @import("../common/debug.zig").print;
7
7
  const protocol = @import("../common/protocol.zig");
8
8
  const rng = @import("../common/rng.zig");
9
9
 
10
- const Action = @import("chance.zig").Action;
11
-
12
10
  const data = @import("data.zig");
13
11
 
14
12
  const assert = std.debug.assert;
@@ -59,23 +57,15 @@ const BOOSTS = &[_][2]u8{
59
57
 
60
58
  const MAX_STAT_VALUE = 999;
61
59
 
62
- const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
63
-
64
60
  pub fn update(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Result {
65
- // FIXME
66
- // std.debug.print("\n", .{});
67
- // DEBUG(battle.turn);
68
- // std.debug.print("// ", .{});
69
61
  assert(c1.type != .Pass or c2.type != .Pass or battle.turn == 0);
70
62
  if (battle.turn == 0) return start(battle, options);
71
63
 
72
64
  var s1 = false;
73
65
  var s2 = false;
74
- var f1: ?Move = null;
75
- var f2: ?Move = null;
76
66
 
77
- if (selectMove(battle, .P1, c1, c2, &s1, &f1)) |r| return r;
78
- if (selectMove(battle, .P2, c2, c1, &s2, &f2)) |r| return r;
67
+ if (selectMove(battle, .P1, c1, c2, &s1)) |r| return r;
68
+ if (selectMove(battle, .P2, c2, c1, &s2)) |r| return r;
79
69
 
80
70
  var p1 = battle.side(.P1);
81
71
  var p2 = battle.side(.P2);
@@ -84,9 +74,9 @@ pub fn update(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Result
84
74
  var r2 = showdown and p2.active.volatiles.Binding and c1.type == .Switch;
85
75
 
86
76
  if (try turnOrder(battle, c1, c2, options) == .P1) {
87
- if (try doTurn(battle, .P1, c1, r1, s1, f1, .P2, c2, r2, s2, f2, options)) |r| return r;
77
+ if (try doTurn(battle, .P1, c1, r1, s1, .P2, c2, r2, s2, options)) |r| return r;
88
78
  } else {
89
- if (try doTurn(battle, .P2, c2, r2, s2, f2, .P1, c1, r1, s1, f1, options)) |r| return r;
79
+ if (try doTurn(battle, .P2, c2, r2, s2, .P1, c1, r1, s1, options)) |r| return r;
90
80
  }
91
81
 
92
82
  if (p1.active.volatiles.attacks == 0) p1.active.volatiles.Binding = false;
@@ -124,7 +114,6 @@ fn selectMove(
124
114
  choice: Choice,
125
115
  foe_choice: Choice,
126
116
  skip_turn: *bool,
127
- from: *?Move,
128
117
  ) ?Result {
129
118
  if (choice.type == .Pass) return null;
130
119
 
@@ -141,7 +130,6 @@ fn selectMove(
141
130
  return null;
142
131
  }
143
132
  if (volatiles.Rage) {
144
- from.* = side.last_used_move;
145
133
  if (showdown) {
146
134
  if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
147
135
  saveMove(battle, player, null);
@@ -151,7 +139,6 @@ fn selectMove(
151
139
  // Pokémon Showdown removes Flinch at the end-of-turn in its residual handler
152
140
  if (!showdown) volatiles.Flinch = false;
153
141
  if (volatiles.Thrashing or volatiles.Charging) {
154
- from.* = side.last_selected_move;
155
142
  if (showdown) {
156
143
  if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
157
144
  saveMove(battle, player, null);
@@ -172,9 +159,7 @@ fn selectMove(
172
159
  return null;
173
160
  }
174
161
  if (volatiles.Binding) {
175
- from.* = side.last_used_move;
176
162
  if (showdown) {
177
- if (foe_choice.type == .Switch) from.* = null;
178
163
  // Pokémon Showdown overwrites Mirror Move with whatever was selected - really this
179
164
  // should set side.last_selected_move = last.id to reuse Mirror Move and fail in order
180
165
  // to satisfy the conditions of the Desync Clause Mod. However, because Binding is still
@@ -214,12 +199,9 @@ fn selectMove(
214
199
  }
215
200
  break :ok true;
216
201
  };
217
-
218
202
  assert(struggle);
219
- saveMove(battle, player, choice);
220
- } else {
221
- saveMove(battle, player, choice);
222
203
  }
204
+ saveMove(battle, player, choice);
223
205
 
224
206
  return null;
225
207
  }
@@ -343,12 +325,10 @@ fn doTurn(
343
325
  player_choice: Choice,
344
326
  player_rewrap: bool,
345
327
  player_skip: bool,
346
- player_from: ?Move,
347
328
  foe_player: Player,
348
329
  foe_choice: Choice,
349
330
  foe_rewrap: bool,
350
331
  foe_skip: bool,
351
- foe_from: ?Move,
352
332
  options: anytype,
353
333
  ) !?Result {
354
334
  assert(player_choice.type != .Pass);
@@ -361,7 +341,6 @@ fn doTurn(
361
341
  player_choice,
362
342
  player_rewrap,
363
343
  player_skip,
364
- player_from,
365
344
  &residual,
366
345
  options,
367
346
  )) |r| return r;
@@ -384,7 +363,6 @@ fn doTurn(
384
363
  foe_choice,
385
364
  foe_rewrap,
386
365
  foe_skip,
387
- foe_from,
388
366
  &residual,
389
367
  options,
390
368
  )) |r| return r;
@@ -412,7 +390,6 @@ fn executeMove(
412
390
  choice: Choice,
413
391
  rewrap: bool,
414
392
  skip: bool,
415
- from: ?Move,
416
393
  residual: *bool,
417
394
  options: anytype,
418
395
  ) !?Result {
@@ -447,8 +424,10 @@ fn executeMove(
447
424
  mslot = @intCast(battle.lastMove(player).index);
448
425
  const stored = side.stored();
449
426
  // GLITCH: Struggle bypass PP underflow via Hyper Beam / Trapping-switch auto selection
450
- auto = side.last_selected_move == .HyperBeam or side.active.volatiles.Bide or
451
- from != null or Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP);
427
+ auto = isForced(&side.active) or
428
+ side.active.volatiles.Binding or side.active.volatiles.Bide or
429
+ side.last_selected_move == .HyperBeam or
430
+ Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP);
452
431
  // If it wasn't Hyper Beam or the continuation of a move effect then we must have just
453
432
  // thawed, in which case we will desync unless the last_selected_move happened to be at
454
433
  // index 1 and the current Pokémon has the same move in its first slot.
@@ -470,7 +449,7 @@ fn executeMove(
470
449
 
471
450
  var skip_can = false;
472
451
  var skip_pp = false;
473
- switch (try beforeMove(battle, player, skip, from, residual, options)) {
452
+ switch (try beforeMove(battle, player, skip, residual, options)) {
474
453
  .done => return null,
475
454
  .skip_can => skip_can = true,
476
455
  .skip_pp => skip_pp = true,
@@ -478,9 +457,9 @@ fn executeMove(
478
457
  .err => return @as(?Result, Result.Error),
479
458
  }
480
459
 
481
- if (!skip_can and !try canMove(battle, player, mslot, auto, skip_pp, from, residual, options)) {
482
- return null;
483
- }
460
+ const can = skip_can or
461
+ try canMove(battle, player, mslot, auto, skip_pp, .None, residual, options);
462
+ if (!can) return null;
484
463
 
485
464
  return doMove(battle, player, mslot, rewrap, auto, residual, options);
486
465
  }
@@ -491,7 +470,6 @@ fn beforeMove(
491
470
  battle: anytype,
492
471
  player: Player,
493
472
  skip: bool,
494
- from: ?Move,
495
473
  residual: *bool,
496
474
  options: anytype,
497
475
  ) !BeforeMove {
@@ -698,7 +676,6 @@ fn beforeMove(
698
676
 
699
677
  if (volatiles.Thrashing) {
700
678
  assert(volatiles.attacks > 0);
701
- assert(from != null);
702
679
 
703
680
  if (options.calc.modify(player, .attacking)) |extend| {
704
681
  if (!extend) volatiles.attacks = 0;
@@ -710,7 +687,7 @@ fn beforeMove(
710
687
  if (!showdown and handleThrashing(battle, active, player, options)) {
711
688
  try log.start(battle.active(player), .ConfusionSilent);
712
689
  }
713
- try log.move(ident, side.last_selected_move, battle.active(player.foe()), from);
690
+ try log.move(ident, side.last_selected_move, battle.active(player.foe()));
714
691
  if (showdown) {
715
692
  // This shouldn't actually set last_used_move, but Pokémon Showdown sets last
716
693
  // used in useMove and doesn't have the notion of skipping canMove semantics
@@ -732,12 +709,7 @@ fn beforeMove(
732
709
  }
733
710
  try options.chance.binding(player, volatiles.attacks);
734
711
 
735
- try log.move(
736
- battle.active(player),
737
- side.last_selected_move,
738
- battle.active(player.foe()),
739
- side.last_selected_move,
740
- );
712
+ try log.move(battle.active(player), side.last_selected_move, battle.active(player.foe()));
741
713
  if (showdown or battle.last_damage != 0) {
742
714
  const sub = showdown and foe.active.volatiles.Substitute;
743
715
  _ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
@@ -757,7 +729,7 @@ fn canMove(
757
729
  mslot: u4,
758
730
  auto: bool,
759
731
  skip_pp: bool,
760
- from: ?Move,
732
+ from: Move,
761
733
  residual: *bool,
762
734
  options: anytype,
763
735
  ) !bool {
@@ -765,33 +737,22 @@ fn canMove(
765
737
  const player_ident = battle.active(player);
766
738
  const move = Move.get(side.last_selected_move);
767
739
 
768
- const foe_last = battle.foe(player).last_used_move;
769
- const special = from != null and (from.? == .Metronome and
770
- (from.? == .MirrorMove and !(foe_last == .None or foe_last == .MirrorMove)));
771
-
772
740
  var skip = skip_pp;
773
741
  if (side.active.volatiles.Charging) {
774
742
  side.active.volatiles.Charging = false;
775
743
  side.active.volatiles.Invulnerable = false;
776
744
  } else if (move.effect == .Charge) {
777
- try options.log.move(player_ident, side.last_selected_move, .{}, from);
745
+ try options.log.moveFrom(player_ident, side.last_selected_move, .{}, from);
778
746
  setCounterable(battle, player, side, move);
779
747
  try Effects.charge(battle, player, options);
780
748
  return false;
781
749
  }
782
750
 
783
- if (!showdown or !special) side.last_used_move = side.last_selected_move;
751
+ side.last_used_move = side.last_selected_move;
784
752
  if (!skip) decrementPP(side, mslot, auto);
785
753
 
786
754
  const target = if (move.target == .Self) player else player.foe();
787
- // Pokémon Showdown's protocol for Rage and Binding moves should come with a [from], though we
788
- // don't have space to be able to track it in all circumstances and thus usually try to infer it
789
- // from side.last_used_move. However, this gets reset by the opponent switching or fainting,
790
- // meaning sometimes we lose this information. from is still set because it not being null is
791
- // relevant for control flow, but we don't have anything useful to put in the log. This is
792
- // regrettable, but this information is more "nice to have" than required
793
- const f = if (from != null and from.? == .None) null else from;
794
- try options.log.move(player_ident, side.last_selected_move, battle.active(target), f);
755
+ try options.log.moveFrom(player_ident, side.last_selected_move, battle.active(target), from);
795
756
  setCounterable(battle, player, side, move);
796
757
 
797
758
  if (move.effect.onBegin()) {
@@ -1438,7 +1399,7 @@ fn moveHit(
1438
1399
  const overwritten = overwrite and state > 0;
1439
1400
  assert(!overwritten or (0 < state and state <= 255 and !side.active.volatiles.Bide));
1440
1401
 
1441
- var accuracy = if (overwritten) state else @as(u16, Gen12.percent(move.accuracy));
1402
+ var accuracy = if (overwritten) state else @as(u16, move.accuracy);
1442
1403
  var boost = BOOSTS[@as(u4, @intCast(@as(i8, side.active.boosts.accuracy) + 6))];
1443
1404
  accuracy = accuracy * boost[0] / boost[1];
1444
1405
  boost = BOOSTS[@as(u4, @intCast(@as(i8, -foe.active.boosts.evasion) + 6))];
@@ -1499,8 +1460,8 @@ fn checkFaint(
1499
1460
  }
1500
1461
 
1501
1462
  const foe_choice: Choice.Type = if (foe_fainted) .Switch else .Pass;
1502
- if (player == .P1) return Result{ .p1 = .Switch, .p2 = foe_choice };
1503
- return Result{ .p1 = foe_choice, .p2 = .Switch };
1463
+ if (player == .P1) return .{ .p1 = .Switch, .p2 = foe_choice };
1464
+ return .{ .p1 = foe_choice, .p2 = .Switch };
1504
1465
  }
1505
1466
 
1506
1467
  fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result {
@@ -1548,21 +1509,19 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
1548
1509
  var volatiles = &side.active.volatiles;
1549
1510
 
1550
1511
  const brn = Status.is(stored.status, .BRN);
1551
- if (brn or Status.is(stored.status, .PSN)) blk: {
1512
+ if (brn or Status.is(stored.status, .PSN)) {
1552
1513
  var damage = @max(stored.stats.hp / 16, 1);
1553
-
1554
1514
  if (volatiles.Toxic) {
1555
1515
  volatiles.toxic += 1;
1556
1516
  damage *= volatiles.toxic;
1557
1517
  }
1518
+ damage = @min(damage, stored.hp);
1558
1519
 
1559
- const amount = @min(damage, stored.hp);
1560
- if (showdown and amount == 0) break :blk;
1561
-
1562
- stored.hp -= amount;
1563
-
1564
- // Pokémon Showdown uses damageOf here but its not relevant in Generation I
1565
- try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
1520
+ if (!showdown or damage > 0) {
1521
+ stored.hp -= damage;
1522
+ // Pokémon Showdown uses damageOf here but its not relevant in Generation I
1523
+ try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
1524
+ }
1566
1525
  }
1567
1526
 
1568
1527
  if (volatiles.LeechSeed) {
@@ -2395,12 +2354,17 @@ pub const Effects = struct {
2395
2354
  }
2396
2355
 
2397
2356
  fn switchAndTeleport(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
2398
- if (!showdown or battle.side(player).last_selected_move == .Teleport) return;
2399
-
2400
2357
  // Whirlwind/Roar should not roll to hit/reset damage but Pokémon Showdown does anyway
2401
- if (try checkHit(battle, player, move, options)) try options.chance.commit(player, .hit);
2402
-
2403
- battle.last_damage = 0;
2358
+ if (showdown) {
2359
+ if (battle.side(player).last_selected_move == .Teleport) return;
2360
+ if (try checkHit(battle, player, move, options)) {
2361
+ try options.chance.commit(player, .hit);
2362
+ }
2363
+ battle.last_damage = 0;
2364
+ } else {
2365
+ try options.log.fail(battle.active(player), .None);
2366
+ try options.log.laststill();
2367
+ }
2404
2368
  }
2405
2369
 
2406
2370
  fn thrashing(battle: anytype, player: Player, options: anytype) void {
@@ -2705,15 +2669,15 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
2705
2669
  }
2706
2670
  if (volatiles.Mist) {
2707
2671
  volatiles.Mist = false;
2708
- try log.end(ident, .Mist);
2672
+ try log.end(ident, .MistSilent);
2709
2673
  }
2710
2674
  if (volatiles.FocusEnergy) {
2711
2675
  volatiles.FocusEnergy = false;
2712
- try log.end(ident, .FocusEnergy);
2676
+ try log.end(ident, .FocusEnergySilent);
2713
2677
  }
2714
2678
  if (volatiles.LeechSeed) {
2715
2679
  volatiles.LeechSeed = false;
2716
- try log.end(ident, .LeechSeed);
2680
+ try log.end(ident, .LeechSeedSilent);
2717
2681
  }
2718
2682
  if (!showdown and volatiles.Toxic) {
2719
2683
  volatiles.Toxic = false;
@@ -2721,17 +2685,17 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
2721
2685
  }
2722
2686
  if (volatiles.LightScreen) {
2723
2687
  volatiles.LightScreen = false;
2724
- try log.end(ident, .LightScreen);
2688
+ try log.end(ident, .LightScreenSilent);
2725
2689
  }
2726
2690
  if (volatiles.Reflect) {
2727
2691
  volatiles.Reflect = false;
2728
- try log.end(ident, .Reflect);
2692
+ try log.end(ident, .ReflectSilent);
2729
2693
  }
2730
2694
  if (showdown and volatiles.Toxic) {
2731
2695
  volatiles.Toxic = false;
2732
2696
  // Pokémon Showdown erroneously clears the toxic counter
2733
2697
  volatiles.toxic = 0;
2734
- try log.end(ident, .Toxic);
2698
+ try log.end(ident, .ToxicSilent);
2735
2699
  }
2736
2700
  }
2737
2701
 
@@ -2867,11 +2831,9 @@ pub const Rolls = struct {
2867
2831
  inline fn metronome(battle: anytype, player: Player, options: anytype) !Move {
2868
2832
  const move: Move = if (options.calc.overridden(player, .metronome)) |val|
2869
2833
  val
2870
- else if (showdown) move: {
2871
- const r = battle.rng.range(u8, 0, @intFromEnum(Move.Struggle) - 2);
2872
- const mod = @as(u2, (if (r < @intFromEnum(Move.Metronome) - 1) 1 else 2));
2873
- break :move @enumFromInt(r + mod);
2874
- } else move: {
2834
+ else if (showdown)
2835
+ Move.METRONOME[battle.rng.range(u8, 0, Move.METRONOME.len)]
2836
+ else move: {
2875
2837
  while (true) {
2876
2838
  const r = battle.rng.next();
2877
2839
  if (r == 0 or r == @intFromEnum(Move.Metronome)) continue;
@@ -2905,7 +2867,7 @@ pub const Rolls = struct {
2905
2867
  const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2906
2868
  @intCast(val)
2907
2869
  else if (showdown)
2908
- @intCast(battle.rng.range(u8, 1, 8))
2870
+ battle.rng.range(u3, 1, 8)
2909
2871
  else duration: {
2910
2872
  while (true) {
2911
2873
  const r = battle.rng.next() & 7;
@@ -2922,7 +2884,7 @@ pub const Rolls = struct {
2922
2884
  const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
2923
2885
  val
2924
2886
  else if (showdown)
2925
- @intCast(battle.rng.range(u8, 1, 9))
2887
+ battle.rng.range(u4, 1, 9)
2926
2888
  else
2927
2889
  @intCast((battle.rng.next() & 7) + 1);
2928
2890
 
@@ -2935,7 +2897,7 @@ pub const Rolls = struct {
2935
2897
  const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2936
2898
  @intCast(val)
2937
2899
  else if (showdown)
2938
- @intCast(battle.rng.range(u8, 2, 6))
2900
+ battle.rng.range(u3, 2, 6)
2939
2901
  else
2940
2902
  @intCast((battle.rng.next() & 3) + 2);
2941
2903
 
@@ -2948,7 +2910,7 @@ pub const Rolls = struct {
2948
2910
  const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
2949
2911
  @intCast(val)
2950
2912
  else if (showdown)
2951
- @intCast(battle.rng.range(u4, 2, 4))
2913
+ battle.rng.range(u3, 2, 4)
2952
2914
  else
2953
2915
  @intCast((battle.rng.next() & 1) + 2);
2954
2916
 
@@ -2957,6 +2919,8 @@ pub const Rolls = struct {
2957
2919
  return duration;
2958
2920
  }
2959
2921
 
2922
+ const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
2923
+
2960
2924
  inline fn distribution(
2961
2925
  battle: anytype,
2962
2926
  comptime effect: Move.Effect,
@@ -2967,7 +2931,7 @@ pub const Rolls = struct {
2967
2931
  const n: u3 = if (options.calc.overridden(player, roll)) |val|
2968
2932
  @intCast(val)
2969
2933
  else if (showdown)
2970
- DISTRIBUTION[battle.rng.range(u8, 0, DISTRIBUTION.len)]
2934
+ DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
2971
2935
  else n: {
2972
2936
  const r = (battle.rng.next() & 3);
2973
2937
  break :n @intCast((if (r < 2) r else battle.rng.next() & 3) + 2);
@@ -3014,10 +2978,9 @@ pub const Rolls = struct {
3014
2978
  while (i < moves.len and r > 0) : (i += 1) {
3015
2979
  if (moves[i].pp > 0) {
3016
2980
  r -= 1;
3017
- if (r == 0) break;
2981
+ if (r == 0) break :slot @intCast(i + 1);
3018
2982
  }
3019
2983
  }
3020
- break :slot @intCast(i + 1);
3021
2984
  }
3022
2985
  }
3023
2986
 
@@ -3039,11 +3002,11 @@ test "RNG agreement" {
3039
3002
  expected[i] = @intCast(i * 0x1000000);
3040
3003
  }
3041
3004
 
3042
- var spe = rng.FixedRNG(1, expected.len){ .rolls = expected };
3043
- var cfz = rng.FixedRNG(1, expected.len){ .rolls = expected };
3044
- var par = rng.FixedRNG(1, expected.len){ .rolls = expected };
3045
- var brn = rng.FixedRNG(1, expected.len){ .rolls = expected };
3046
- var eff = rng.FixedRNG(1, expected.len){ .rolls = expected };
3005
+ var spe: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
3006
+ var cfz: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
3007
+ var par: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
3008
+ var brn: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
3009
+ var eff: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
3047
3010
 
3048
3011
  for (0..expected.len) |i| {
3049
3012
  try expectEqual(spe.range(u8, 0, 2) == 0, i < Gen12.percent(50) + 1);
@@ -32,25 +32,27 @@ The following information is required to simulate a Generation II Pokémon battl
32
32
  | `pokemon.species` | `party_struct.Species` | `pokemon.baseSpecies` |
33
33
  | `active.species` | `battle_struct.Species` | `pokemon.species` |
34
34
  | `active.stats` | `TODO modified` | `pokemon.storedStats` |
35
- | `active.trapped FIXME` | `PlayerTrappingMove` | `pokemon.trapped` |
36
- | `active.switching` | `PlayerIsSwitching` | `pokemon.switchFlag` |
37
35
  | `active.boosts` | `PlayerStatLevels` | `pokemon.boosts` |
38
36
  | `active.volatiles` | `PlayerBattleStatus{1,2,3}` | `pokemon.volatiles` |
39
- | `volatiles.data.bide` | `PlayerDamageTaken` | `volatiles.bide.totalDamage` |
40
- | `volatiles.data.substitute` | `PlayerSubstituteHP` | `volatiles.substitute.hp` |
41
- | `volatiles.data.rollout` | `PlayerRolloutCount` | `volatiles.rollout.hitCount` |
42
- | `volatiles.data.confusion` | `PlayerConfuseCount` | `volatiles.confusion.duration` |
43
- | `volatiles.data.toxic` | `PlayerToxicCount` | `volatiles.residualdmg.counter` |
44
- | `volatiles.data.disabled` | `PlayerDisableCount` | `moveSlots.disabled` |
45
- | `volatiles.data.encore` | `PlayerEncoreCount` | `volatiles.encore.duration` |
46
- | `volatiles.data.perish_song` | `PlayerPerishCount` | `volatiles.perishsong.duration` |
47
- | `volatiles.data.fury_cutter` | `PlayerFuryCutterCount` | `volatiles.furycutter.multiplier` |
48
- | `volatiles.data.protect` | `PlayerProtectCount` | `volatiles.stall.counter` |
49
- | `volatiles.data.future_sight` | `PlayerFutureSight{Damage,Count}` | `slotConditions[i].futuremove` |
50
- | `volatiles.data.rage` | `PlayerRageCounter` | **BUG** |
51
- | `volatiles.data.bind` | `PlayerWrapCount` | `volatiles.partiallytrapped.duration` |
37
+ | `volatiles.bide` | `PlayerDamageTaken` | `volatiles.bide.totalDamage` |
38
+ | `volatiles.substitute` | `PlayerSubstituteHP` | `volatiles.substitute.hp` |
39
+ | `volatiles.rollout` | `PlayerRolloutCount` | `volatiles.rollout.hitCount` |
40
+ | `volatiles.confusion` | `PlayerConfuseCount` | `volatiles.confusion.duration` |
41
+ | `volatiles.toxic` | `PlayerToxicCount` | `volatiles.residualdmg.counter` |
42
+ | `volatiles.disabled` | `PlayerDisableCount` | `moveSlots.disabled` |
43
+ | `volatiles.encore` | `PlayerEncoreCount` | `volatiles.encore.duration` |
44
+ | `volatiles.perish_song` | `PlayerPerishCount` | `volatiles.perishsong.duration` |
45
+ | `volatiles.fury_cutter` | `PlayerFuryCutterCount` | `volatiles.furycutter.multiplier` |
46
+ | `volatiles.protect` | `PlayerProtectCount` | `volatiles.stall.counter` |
47
+ | `volatiles.future_sight` | `PlayerFutureSight{Damage,Count}` | `slotConditions[i].futuremove` |
48
+ | `volatiles.rage` | `PlayerRageCounter` | **BUG** |
49
+ | `volatiles.bind` | `PlayerWrapCount` | `volatiles.partiallytrapped.duration` |
50
+ | `volatiles.bind_reason` | `PlayerTrappingMove` | `volatiles.effectState.sourceEffect` |
52
51
  | | `PlayerCharging` | |
53
- | | `PlayerJustGotFrozen` | |
52
+ | `volatiles.trapped FIXME` | `FIXME` | `pokemon.trapped` |
53
+ | `volatiles.switched` | `TODO` | `pokemon.switchFlag` |
54
+ | `volatiles.switching` | `PlayerIsSwitching` | `pokemon.switchFlag` |
55
+ | `volatiles.frozen` | `PlayerJustGotFrozen` | `volatiles.frozen` |
54
56
  | | | |
55
57
  | `active.last_used_move` | `PlayerUsedMove` | `side.lastMove` |
56
58
 
@@ -219,6 +221,81 @@ much overhead the preceding representations have after taking into consideration
219
221
  padding](https://en.wikipedia.org/wiki/Data_structure_alignment) and
220
222
  [denormalization](https://en.wikipedia.org/wiki/Denormalization):
221
223
 
224
+ ## Details
225
+
226
+ | pkmn | Pokémon Crystal (pret) |
227
+ | ------------------------- | ------------------------------------------------- |
228
+ | `start` | `DoBattle` |
229
+ | `update` | `BattleTurn` |
230
+ | `selectMove` / `saveMove` | `CheckPlayerLockedIn` / `ParsePlayerAction` |
231
+ | `switchIn` | `SwitchPlayerMon` / `SendOutPlayerMon` |
232
+ | `turnOrder` | `DetermineMoveOrder` |
233
+ | `doTurn` | `DoTurn` |
234
+ | `executeMove` | `DoMove` |
235
+ | `beforeMove` | `CheckTurn` |
236
+ | `canMove` | |
237
+ | `decrementPP` | |
238
+ | `doMove` | |
239
+ | `checkCriticalHit` | `BattleCommand_Critical` |
240
+ | `calcDamage` | `BattleCommand_DamageCalc` / `PlayerAttackDamage` |
241
+ | `adjustDamage` | `BattleCommand_Stab` |
242
+ | `randomizeDamage` | `BattleCommand_DamageVariation` |
243
+ | `specialDamage` | `BattleCommand_ConstantDamage` |
244
+ | `applyDamage` | `BattleCommand_ApplyDamage` |
245
+ | `checkHit` | `BattleCommand_CheckHit` |
246
+ | `checkFaint` | `CheckFaint` |
247
+ | `handleResidual` | `ResidualDamage` |
248
+ | `endTurn` | `EndTurn` |
249
+
250
+ DoBattle
251
+ BattleTurn
252
+ - HandleBerserkGene
253
+ - CheckPlayerLockedIn
254
+ - BattleMenu
255
+ - ParsePlayerAction
256
+ - DetermineMoveOrder
257
+ - CompareMovePriority
258
+ - GetMovePriority
259
+ - Battle_*
260
+ - Handle*Switch
261
+ - SwitchPlayerMon
262
+ - BreakAttraction
263
+ - SendOutPlayerMon
264
+ - SpikesDamage
265
+ - *Turn_EndOpponentProtectEndureDestinyBond
266
+ - EndUserDestinyBond
267
+ - Do*Turn
268
+ - EndOpponentProtectEndureDestinyBond
269
+ - ResidualDamage
270
+ - CheckFaint_*
271
+ - HandleBetweenTurnEffects
272
+ - HandleFutureSight
273
+ - HandleWeather
274
+ - HandleWrap
275
+ - HandlePerishSong
276
+ - HandleLeftovers
277
+ - HandleMysteryBerry
278
+ - HandleDefrost
279
+ - HandleSafeguard
280
+ - HandleScreens
281
+ - HandleStatBoostingHeldItems
282
+ - HandleHealingItems
283
+ - HandleEncore
284
+
285
+ DoTurn
286
+ DoMove = executeMove
287
+ CheckTurn = beforeMove
288
+ BattleCommand_Critical = checkCriticalHit
289
+ BattleCommand_Stab = adjustDamage
290
+ BattleCommand_DamageVariation = randomizeDamage
291
+ BattleCommand_CheckHit = checkHit
292
+ BattleCommand_ApplyDamage = applyDamage
293
+ BattleCommand_CheckFaint
294
+ BattleCommand_BuildOpponentRage = buildRage
295
+ BattleCommand_DamageCalc (PlayerAttackDamage / EnemyAttackDamage) = calcDamage
296
+ BattleCommand_ConstantDamage = specialDamage
297
+ DoEnemyDamage / DoPlayerDamage / DoSubstituteDamage
298
+
222
299
  ==**TODO**==
223
300
 
224
301
  ## Protocol
@@ -226,62 +303,7 @@ padding](https://en.wikipedia.org/wiki/Data_structure_alignment) and
226
303
  TODO
227
304
 
228
305
  ```txt
229
- -activate: _, damage, move, name, number, of
230
- -anim: _
231
- -block: _
232
- -boost: _, from, silent
233
- -clearallboost: _
234
- -copyboost: from
235
- -crit: _
236
- -curestatus: msg
237
- -cureteam: from
238
- -damage: _, from, of, partiallytrapped
239
- -end: _, from, of, partiallytrapped, silent
240
- -enditem: _, eat
241
- -fail: _, block, weak
242
- -fieldactivate: _
243
- -heal: _, from, of, silent
244
- -hint: _
245
- -hitcount: _
246
- -immune: _, ohko
247
- -item: from, of
248
- -miss: _
249
- -mustrecharge: _
250
- -ohko: _
251
- -prepare: _
252
- -resisted: _
253
- -setboost: from
254
- -sethp: from, silent
255
- -sideend: _, from, of
256
- -sidestart: _
257
- -singlemove: _
258
- -singleturn: _
259
- -start: _, of, silent
260
- -status: _, from, silent
261
- -supereffective: _
262
- -transform: _
263
- -unboost: _
264
- -weather: _, upkeep
265
- cant: _
266
- debug: _
267
- done: _
268
- drag: _
269
- faint: _
270
- gametype: _
271
- gen: _
272
- move: _, from, miss, still
273
- player: _
274
- request: _
275
- rule: _
276
- start: _
277
- switch: _, from
278
- t:: _
279
- teamsize: _
280
- tie: _
281
- tier: _
282
- turn: _
283
- upkeep: _
284
- win: _
306
+ -activate: _, move, name, number
285
307
  ```
286
308
 
287
309
  ## Bugs