@pkmn/engine 0.1.0-dev.2c13d3ec → 0.1.0-dev.305a932f

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,10 +62,6 @@ const MAX_STAT_VALUE = 999;
62
62
  const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
63
63
 
64
64
  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
65
  assert(c1.type != .Pass or c2.type != .Pass or battle.turn == 0);
70
66
  if (battle.turn == 0) return start(battle, options);
71
67
 
@@ -186,10 +182,7 @@ fn selectMove(
186
182
  assert(choice.data == 0);
187
183
  // GLITCH: https://glitchcity.wiki/Partial_trapping_move_Mirror_Move_link_battle_glitch
188
184
  if (foe_choice.type == .Switch) {
189
- const last = side.active.move(if (player == .P1)
190
- battle.last_moves.p1_index
191
- else
192
- battle.last_moves.p2_index);
185
+ const last = side.active.move(battle.lastMove(player).index);
193
186
  if (last.id == .Metronome) side.last_selected_move = last.id;
194
187
  if (last.id == .MirrorMove) return Result.Error;
195
188
  }
@@ -217,12 +210,9 @@ fn selectMove(
217
210
  }
218
211
  break :ok true;
219
212
  };
220
-
221
213
  assert(struggle);
222
- saveMove(battle, player, choice);
223
- } else {
224
- saveMove(battle, player, choice);
225
214
  }
215
+ saveMove(battle, player, choice);
226
216
 
227
217
  return null;
228
218
  }
@@ -242,11 +232,7 @@ fn saveMove(battle: anytype, player: Player, choice: ?Choice) void {
242
232
  assert(showdown or move.pp != 0);
243
233
 
244
234
  side.last_selected_move = move.id;
245
- if (player == .P1) {
246
- battle.last_moves.p1_index = @intCast(c.data);
247
- } else {
248
- battle.last_moves.p2_index = @intCast(c.data);
249
- }
235
+ battle.lastMove(player).index = @intCast(c.data);
250
236
  }
251
237
  }
252
238
  }
@@ -264,11 +250,7 @@ fn switchIn(battle: anytype, player: Player, slot: u8, initial: bool, options: a
264
250
  side.order[0] = side.order[slot - 1];
265
251
  side.order[slot - 1] = out;
266
252
 
267
- if (player == .P1) {
268
- battle.last_moves.p1_index = 1;
269
- } else {
270
- battle.last_moves.p2_index = 1;
271
- }
253
+ battle.lastMove(player).index = 1;
272
254
 
273
255
  side.last_used_move = .None;
274
256
  foe.last_used_move = .None;
@@ -455,10 +437,7 @@ fn executeMove(
455
437
  if (mslot == 0 and side.last_selected_move != .None and side.last_selected_move != .Struggle) {
456
438
  // choice.data == 0 only happens with Struggle on Pokémon Showdown
457
439
  assert(!showdown);
458
- mslot = @intCast(if (player == .P1)
459
- battle.last_moves.p1_index
460
- else
461
- battle.last_moves.p2_index);
440
+ mslot = @intCast(battle.lastMove(player).index);
462
441
  const stored = side.stored();
463
442
  // GLITCH: Struggle bypass PP underflow via Hyper Beam / Trapping-switch auto selection
464
443
  auto = side.last_selected_move == .HyperBeam or side.active.volatiles.Bide or
@@ -479,10 +458,7 @@ fn executeMove(
479
458
  } else if (showdown and side.active.volatiles.Charging) {
480
459
  // Incorrect mslot with Pokémon Showdown choice semantics so we need to recover from index
481
460
  assert(mslot == 1);
482
- mslot = @intCast(if (player == .P1)
483
- battle.last_moves.p1_index
484
- else
485
- battle.last_moves.p2_index);
461
+ mslot = @intCast(battle.lastMove(player).index);
486
462
  }
487
463
 
488
464
  var skip_can = false;
@@ -834,11 +810,7 @@ fn setCounterable(battle: anytype, player: Player, side: *Side, move: Move.Data)
834
810
  // as the desync results from actually having two separate battle states that subtly disagree
835
811
  const counterable = side.last_selected_move != .Counter and move.bp > 0 and
836
812
  (move.type == .Normal or move.type == .Fighting);
837
- if (player == .P1) {
838
- battle.last_moves.p1_counterable = @intFromBool(counterable);
839
- } else {
840
- battle.last_moves.p2_counterable = @intFromBool(counterable);
841
- }
813
+ battle.lastMove(player).counterable = @intFromBool(counterable);
842
814
  }
843
815
 
844
816
  fn decrementPP(side: *Side, mslot: u4, auto: bool) void {
@@ -1278,11 +1250,7 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
1278
1250
  else
1279
1251
  foe.last_selected_move);
1280
1252
 
1281
- const used = (if (player.foe() == .P1)
1282
- battle.last_moves.p1_counterable
1283
- else
1284
- battle.last_moves.p2_counterable) != 0;
1285
-
1253
+ const used = battle.lastMove(player.foe()).counterable != 0;
1286
1254
  const selected = foe_last_selected_move.bp > 0 and
1287
1255
  foe.last_selected_move != .Counter and
1288
1256
  (foe_last_selected_move.type == .Normal or
@@ -1357,7 +1325,7 @@ fn mirrorMove(
1357
1325
  auto: bool,
1358
1326
  residual: *bool,
1359
1327
  options: anytype,
1360
- ) !?Result {
1328
+ ) anyerror!?Result {
1361
1329
  var side = battle.side(player);
1362
1330
  const foe = battle.foe(player);
1363
1331
 
@@ -1384,7 +1352,7 @@ fn metronome(
1384
1352
  auto: bool,
1385
1353
  residual: *bool,
1386
1354
  options: anytype,
1387
- ) !?Result {
1355
+ ) anyerror!?Result {
1388
1356
  var side = battle.side(player);
1389
1357
 
1390
1358
  side.last_selected_move = try Rolls.metronome(battle, player, options);
@@ -1463,7 +1431,7 @@ fn moveHit(
1463
1431
  const overwritten = overwrite and state > 0;
1464
1432
  assert(!overwritten or (0 < state and state <= 255 and !side.active.volatiles.Bide));
1465
1433
 
1466
- var accuracy = if (overwritten) state else @as(u16, Gen12.percent(move.accuracy));
1434
+ var accuracy = if (overwritten) state else @as(u16, move.accuracy);
1467
1435
  var boost = BOOSTS[@as(u4, @intCast(@as(i8, side.active.boosts.accuracy) + 6))];
1468
1436
  accuracy = accuracy * boost[0] / boost[1];
1469
1437
  boost = BOOSTS[@as(u4, @intCast(@as(i8, -foe.active.boosts.evasion) + 6))];
@@ -1524,8 +1492,8 @@ fn checkFaint(
1524
1492
  }
1525
1493
 
1526
1494
  const foe_choice: Choice.Type = if (foe_fainted) .Switch else .Pass;
1527
- if (player == .P1) return Result{ .p1 = .Switch, .p2 = foe_choice };
1528
- return Result{ .p1 = foe_choice, .p2 = .Switch };
1495
+ if (player == .P1) return .{ .p1 = .Switch, .p2 = foe_choice };
1496
+ return .{ .p1 = foe_choice, .p2 = .Switch };
1529
1497
  }
1530
1498
 
1531
1499
  fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result {
@@ -1573,21 +1541,19 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
1573
1541
  var volatiles = &side.active.volatiles;
1574
1542
 
1575
1543
  const brn = Status.is(stored.status, .BRN);
1576
- if (brn or Status.is(stored.status, .PSN)) blk: {
1544
+ if (brn or Status.is(stored.status, .PSN)) {
1577
1545
  var damage = @max(stored.stats.hp / 16, 1);
1578
-
1579
1546
  if (volatiles.Toxic) {
1580
1547
  volatiles.toxic += 1;
1581
1548
  damage *= volatiles.toxic;
1582
1549
  }
1550
+ damage = @min(damage, stored.hp);
1583
1551
 
1584
- const amount = @min(damage, stored.hp);
1585
- if (showdown and amount == 0) break :blk;
1586
-
1587
- stored.hp -= amount;
1588
-
1589
- // Pokémon Showdown uses damageOf here but its not relevant in Generation I
1590
- try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
1552
+ if (!showdown or damage > 0) {
1553
+ stored.hp -= damage;
1554
+ // Pokémon Showdown uses damageOf here but its not relevant in Generation I
1555
+ try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
1556
+ }
1591
1557
  }
1592
1558
 
1593
1559
  if (volatiles.LeechSeed) {
@@ -2420,12 +2386,17 @@ pub const Effects = struct {
2420
2386
  }
2421
2387
 
2422
2388
  fn switchAndTeleport(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
2423
- if (!showdown or battle.side(player).last_selected_move == .Teleport) return;
2424
-
2425
2389
  // Whirlwind/Roar should not roll to hit/reset damage but Pokémon Showdown does anyway
2426
- if (try checkHit(battle, player, move, options)) try options.chance.commit(player, .hit);
2427
-
2428
- battle.last_damage = 0;
2390
+ if (showdown) {
2391
+ if (battle.side(player).last_selected_move == .Teleport) return;
2392
+ if (try checkHit(battle, player, move, options)) {
2393
+ try options.chance.commit(player, .hit);
2394
+ }
2395
+ battle.last_damage = 0;
2396
+ } else {
2397
+ try options.log.fail(battle.active(player), .None);
2398
+ try options.log.laststill();
2399
+ }
2429
2400
  }
2430
2401
 
2431
2402
  fn thrashing(battle: anytype, player: Player, options: anytype) void {
@@ -2705,7 +2676,7 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
2705
2676
  }
2706
2677
  }
2707
2678
 
2708
- inline fn isForced(active: ActivePokemon) bool {
2679
+ inline fn isForced(active: anytype) bool {
2709
2680
  return active.volatiles.Recharging or active.volatiles.Rage or
2710
2681
  active.volatiles.Thrashing or active.volatiles.Charging;
2711
2682
  }
@@ -2730,15 +2701,15 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
2730
2701
  }
2731
2702
  if (volatiles.Mist) {
2732
2703
  volatiles.Mist = false;
2733
- try log.end(ident, .Mist);
2704
+ try log.end(ident, .MistSilent);
2734
2705
  }
2735
2706
  if (volatiles.FocusEnergy) {
2736
2707
  volatiles.FocusEnergy = false;
2737
- try log.end(ident, .FocusEnergy);
2708
+ try log.end(ident, .FocusEnergySilent);
2738
2709
  }
2739
2710
  if (volatiles.LeechSeed) {
2740
2711
  volatiles.LeechSeed = false;
2741
- try log.end(ident, .LeechSeed);
2712
+ try log.end(ident, .LeechSeedSilent);
2742
2713
  }
2743
2714
  if (!showdown and volatiles.Toxic) {
2744
2715
  volatiles.Toxic = false;
@@ -2746,17 +2717,17 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
2746
2717
  }
2747
2718
  if (volatiles.LightScreen) {
2748
2719
  volatiles.LightScreen = false;
2749
- try log.end(ident, .LightScreen);
2720
+ try log.end(ident, .LightScreenSilent);
2750
2721
  }
2751
2722
  if (volatiles.Reflect) {
2752
2723
  volatiles.Reflect = false;
2753
- try log.end(ident, .Reflect);
2724
+ try log.end(ident, .ReflectSilent);
2754
2725
  }
2755
2726
  if (showdown and volatiles.Toxic) {
2756
2727
  volatiles.Toxic = false;
2757
2728
  // Pokémon Showdown erroneously clears the toxic counter
2758
2729
  volatiles.toxic = 0;
2759
- try log.end(ident, .Toxic);
2730
+ try log.end(ident, .ToxicSilent);
2760
2731
  }
2761
2732
  }
2762
2733
 
@@ -2796,6 +2767,7 @@ pub const Rolls = struct {
2796
2767
  }
2797
2768
  };
2798
2769
 
2770
+ assert(roll >= 217 and roll <= 255);
2799
2771
  try options.chance.damage(player, roll);
2800
2772
  return roll;
2801
2773
  }
@@ -2920,6 +2892,7 @@ pub const Rolls = struct {
2920
2892
  }
2921
2893
  };
2922
2894
 
2895
+ assert(power < max);
2923
2896
  try options.chance.psywave(player, power, max);
2924
2897
  return power;
2925
2898
  }
@@ -2936,6 +2909,7 @@ pub const Rolls = struct {
2936
2909
  }
2937
2910
  };
2938
2911
 
2912
+ assert(duration >= 1 and duration <= 7);
2939
2913
  options.chance.duration(.sleep, player, player.foe(), duration);
2940
2914
  return duration;
2941
2915
  }
@@ -2948,6 +2922,7 @@ pub const Rolls = struct {
2948
2922
  else
2949
2923
  @intCast((battle.rng.next() & 7) + 1);
2950
2924
 
2925
+ assert(duration >= 1 and duration <= 8);
2951
2926
  options.chance.duration(.disable, player, player.foe(), duration);
2952
2927
  return duration;
2953
2928
  }
@@ -2960,6 +2935,7 @@ pub const Rolls = struct {
2960
2935
  else
2961
2936
  @intCast((battle.rng.next() & 3) + 2);
2962
2937
 
2938
+ assert(duration >= 2 and duration <= 5);
2963
2939
  options.chance.duration(.confusion, player, if (self) player else player.foe(), duration);
2964
2940
  return duration;
2965
2941
  }
@@ -2972,6 +2948,7 @@ pub const Rolls = struct {
2972
2948
  else
2973
2949
  @intCast((battle.rng.next() & 1) + 2);
2974
2950
 
2951
+ assert(duration >= 2 and duration <= 3);
2975
2952
  options.chance.duration(.attacking, player, player, duration);
2976
2953
  return duration;
2977
2954
  }
@@ -2992,6 +2969,7 @@ pub const Rolls = struct {
2992
2969
  break :n @intCast((if (r < 2) r else battle.rng.next() & 3) + 2);
2993
2970
  };
2994
2971
 
2972
+ assert(n >= 2 and n <= 5);
2995
2973
  if (effect == .Binding) {
2996
2974
  options.chance.duration(.binding, player, player, n);
2997
2975
  } else {
@@ -3057,11 +3035,11 @@ test "RNG agreement" {
3057
3035
  expected[i] = @intCast(i * 0x1000000);
3058
3036
  }
3059
3037
 
3060
- var spe = rng.FixedRNG(1, expected.len){ .rolls = expected };
3061
- var cfz = rng.FixedRNG(1, expected.len){ .rolls = expected };
3062
- var par = rng.FixedRNG(1, expected.len){ .rolls = expected };
3063
- var brn = rng.FixedRNG(1, expected.len){ .rolls = expected };
3064
- var eff = rng.FixedRNG(1, expected.len){ .rolls = expected };
3038
+ var spe: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
3039
+ var cfz: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
3040
+ var par: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
3041
+ var brn: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
3042
+ var eff: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
3065
3043
 
3066
3044
  for (0..expected.len) |i| {
3067
3045
  try expectEqual(spe.range(u8, 0, 2) == 0, i < Gen12.percent(50) + 1);
@@ -3097,7 +3075,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
3097
3075
  const side = battle.side(player);
3098
3076
  const foe = battle.foe(player);
3099
3077
 
3100
- var active = side.active;
3078
+ var active = &side.active;
3101
3079
  const stored = side.stored();
3102
3080
 
3103
3081
  // While players are not given any input options on the cartridge in these cases,
@@ -32,35 +32,36 @@ 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
 
57
- - Pokémon Showdown does not implement the correct Generation II RNG and as such its `seed` is
59
+ - Pokémon Showdown doesn't implement the correct Generation II RNG and as such its `seed` is
58
60
  different
59
- - `battle.turn` only needs to be tracked in order to be compatible with the Pokémon Showdown
60
- protocol
61
+ - `battle.turn` only needs to be tracked to be compatible with the Pokémon Showdown protocol
61
62
  - Battle results (win, lose, draw) or request states are communicated via the return code of
62
63
  `Battle.update`
63
- - Nicknames (`BattleMonNick`/`pokemon.name`) are not handled by the pkmn engine as they are expected
64
+ - Nicknames (`BattleMonNick`/`pokemon.name`) aren't handled by the pkmn engine as they're expected
64
65
  to be handled by driver code if required
65
66
 
66
67
  ==**TODO**==
@@ -77,9 +78,9 @@ between the data structure for the "active" Pokémon and its party members (see
77
78
 
78
79
  ### `Pokemon` / `ActivePokemon`
79
80
 
80
- Similar to the cartridge, in order to save space different information is stored depending on
81
- whether a [Pokémon](https://pkmn.cc/bulba/Pok%c3%a9mon_data_structure_%28Generation_II%29) is
82
- actively participating in battle vs. is switched out (pret's [`battle_struct` vs.
81
+ Similar to the cartridge, to save space different information is stored depending on whether a
82
+ [Pokémon](https://pkmn.cc/bulba/Pok%c3%a9mon_data_structure_%28Generation_II%29) is actively
83
+ participating in battle vs. is switched out (pret's [`battle_struct` vs.
83
84
  `party_struct`](https://pkmn.cc/pokecrystal/macros/ram.asm)). In Pokémon Showdown, all the Pokemon
84
85
  are represented by the same
85
86
  [`Pokemon`](https://github.com/smogon/pokemon-showdown/blob/master/sim/pokemon.ts) class, and static
@@ -93,14 +94,14 @@ A `MoveSlot` is a data-type for a `(move, current pp)` pair. A pared down versio
93
94
  Showdown's `Pokemon#moveSlot`, it also stores data from the cartridge's `battle_struct::Move` macro
94
95
  and can be used to replace the `PlayerMove*` data. Move PP is stored as a full byte instead of how
95
96
  the cartridge (`battle_struct::PP`) stores it (6 bits for current PP and the remaining 2 bits used
96
- to store the number of applied PP Ups). PP Up bits do not actually need to be stored on move slot
97
+ to store the number of applied PP Ups). PP Up bits don't actually need to be stored on move slot
97
98
  as max PP is never relevant in Generation II (the Mystery Berry always increases PP from 0 -> 5).
98
99
 
99
100
  #### `Status`
100
101
 
101
102
  Bitfield representation of a Pokémon's major [status
102
- condition](https://pkmn.cc/bulba/Status_condition), mirroring how it is stored on the cartridge. A
103
- value of `0x00` means that the Pokémon is not affected by any major status, otherwise the lower 3
103
+ condition](https://pkmn.cc/bulba/Status_condition), mirroring how it's stored on the cartridge. A
104
+ value of `0x00` means that the Pokémon isn't affected by any major status, otherwise the lower 3
104
105
  bits represent the remaining duration for Sleep. Other status are denoted by the presence of
105
106
  individual bits - at most one status should be set at any given time.
106
107
 
@@ -161,7 +162,7 @@ boosts should always range from `-6`...`6` instead of `1`...`13` as on the cartr
161
162
 
162
163
  `Moves` serves as an identifier for a unique [Pokémon move](https://pkmn.cc/bulba/Move) that can be
163
164
  used to retrieve a `Move` with information regarding base power, accuracy and type. As covered
164
- above, PP information isn't strictly necessary in Generation I, but fits neatly into the 4 bits of
165
+ earlier, PP information isn't strictly necessary in Generation I, but fits neatly into the 4 bits of
165
166
  padding after noticing that all PP values are multiples of 5. `Moves.None` exists as a special
166
167
  sentinel value to indicate `null`.
167
168
 
@@ -215,11 +216,86 @@ entropy](https://en.wikipedia.org/wiki/Entropy_(information_theory))) is as foll
215
216
  | **wrap** | 0...5 | 3 | | **perish song** | 0...4 | 3 |
216
217
  | **item** | TODO | 5? | | **gender** | 0..1 | 1 |
217
218
 
218
- From this we can determine the minimum bits required to store each data structure to determine how
219
- much overhead the representations above have after taking into consideration [alignment &
219
+ From this one can determine the minimum bits required to store each data structure to determine how
220
+ much overhead the preceding representations have after taking into consideration [alignment &
220
221
  padding](https://en.wikipedia.org/wiki/Data_structure_alignment) and
221
222
  [denormalization](https://en.wikipedia.org/wiki/Denormalization):
222
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
+
223
299
  ==**TODO**==
224
300
 
225
301
  ## Protocol
@@ -227,62 +303,7 @@ padding](https://en.wikipedia.org/wiki/Data_structure_alignment) and
227
303
  TODO
228
304
 
229
305
  ```txt
230
- -activate: _, damage, move, name, number, of
231
- -anim: _
232
- -block: _
233
- -boost: _, from, silent
234
- -clearallboost: _
235
- -copyboost: from
236
- -crit: _
237
- -curestatus: msg
238
- -cureteam: from
239
- -damage: _, from, of, partiallytrapped
240
- -end: _, from, of, partiallytrapped, silent
241
- -enditem: _, eat
242
- -fail: _, block, weak
243
- -fieldactivate: _
244
- -heal: _, from, of, silent
245
- -hint: _
246
- -hitcount: _
247
- -immune: _, ohko
248
- -item: from, of
249
- -miss: _
250
- -mustrecharge: _
251
- -ohko: _
252
- -prepare: _
253
- -resisted: _
254
- -setboost: from
255
- -sethp: from, silent
256
- -sideend: _, from, of
257
- -sidestart: _
258
- -singlemove: _
259
- -singleturn: _
260
- -start: _, of, silent
261
- -status: _, from, silent
262
- -supereffective: _
263
- -transform: _
264
- -unboost: _
265
- -weather: _, upkeep
266
- cant: _
267
- debug: _
268
- done: _
269
- drag: _
270
- faint: _
271
- gametype: _
272
- gen: _
273
- move: _, from, miss, still
274
- player: _
275
- request: _
276
- rule: _
277
- start: _
278
- switch: _, from
279
- t:: _
280
- teamsize: _
281
- tie: _
282
- tier: _
283
- turn: _
284
- upkeep: _
285
- win: _
306
+ -activate: _, move, name, number
286
307
  ```
287
308
 
288
309
  ## Bugs
@@ -11,15 +11,15 @@ const Type = data.Type;
11
11
  /// Representation of a held item in Generation II Pokémon.
12
12
  pub const Item = enum(u8) {
13
13
  None,
14
+ SoftSand, // Ground
15
+ HardStone, // Rock
16
+ MetalCoat, // Steel
14
17
  PinkBow, // Normal
15
18
  BlackBelt, // Fighting
16
19
  SharpBeak, // Flying
17
20
  PoisonBarb, // Poison
18
- SoftSand, // Ground
19
- HardStone, // Rock
20
21
  SilverPowder, // Bug
21
22
  SpellTag, // Ghost
22
- MetalCoat, // Steel
23
23
  PolkadotBow, // ??? (Normal)
24
24
  Charcoal, // Fire
25
25
  MysticWater, // Water
@@ -36,21 +36,21 @@ pub const Item = enum(u8) {
36
36
  Stick,
37
37
  ThickClub,
38
38
  FocusBand, // FocusBand
39
- BerryJuice, // Berry
40
39
  ScopeLens, // CriticalUp
41
40
  Leftovers, // Leftovers
42
41
  BerserkGene,
43
42
  LightBall,
43
+ Berry, // Berry
44
+ BerryJuice, // Berry
45
+ GoldBerry, // Berry
46
+ MintBerry, // HealSleep
44
47
  PSNCureBerry, // HealPoison
45
48
  PRZCureBerry, // HealParalyze
46
- BurntBerry, // HealFreeze
47
49
  IceBerry, // HealBurn
48
- BitterBerry, // HealConfusion
49
- MintBerry, // HealSleep
50
+ BurntBerry, // HealFreeze
50
51
  MiracleBerry, // HealStatus
52
+ BitterBerry, // HealConfusion
51
53
  MysteryBerry, // RestorePP
52
- Berry, // Berry
53
- GoldBerry, // Berry
54
54
  MasterBall,
55
55
  UltraBall,
56
56
  GreatBall,
@@ -222,14 +222,8 @@ pub const Item = enum(u8) {
222
222
  return if (@intFromEnum(item) <= 18) @enumFromInt(@intFromEnum(item) - 1) else null;
223
223
  }
224
224
 
225
- /// Whether or not this item is a Berry.
226
- pub inline fn berry(item: Item) bool {
227
- assert(item != .None);
228
- return @intFromEnum(item) > 30 and @intFromEnum(item) <= 40;
229
- }
230
-
231
225
  /// Whether or not this item is Mail.
232
- pub inline fn mail(item: Item) bool {
226
+ pub inline fn isMail(item: Item) bool {
233
227
  assert(item != .None);
234
228
  return @intFromEnum(item) > 60 and @intFromEnum(item) <= 70;
235
229
  }