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

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.
Files changed (47) hide show
  1. package/README.md +55 -44
  2. package/build/pkg/addon.js +3 -2
  3. package/build/pkg/addon.js.map +1 -1
  4. package/build/pkg/data/layout.json +25 -3
  5. package/build/pkg/gen1.d.ts +2 -1
  6. package/build/pkg/gen1.js +36 -18
  7. package/build/pkg/gen1.js.map +1 -1
  8. package/build/pkg/index.d.ts +4 -2
  9. package/build/pkg/index.js.map +1 -1
  10. package/build/pkg/protocol.d.ts +4 -2
  11. package/build/pkg/protocol.js +4 -2
  12. package/build/pkg/protocol.js.map +1 -1
  13. package/build/test/display.js +7 -3
  14. package/build/test/display.js.map +1 -1
  15. package/build.zig +86 -62
  16. package/package.json +25 -16
  17. package/src/bin/install-pkmn-engine +10 -5
  18. package/src/lib/binding/node.zig +45 -140
  19. package/src/lib/binding/wasm.zig +4 -2
  20. package/src/lib/common/battle.zig +17 -0
  21. package/src/lib/common/data.zig +11 -11
  22. package/src/lib/common/debug.zig +20 -11
  23. package/src/lib/common/js.zig +118 -0
  24. package/src/lib/common/optional.zig +60 -0
  25. package/src/lib/common/options.zig +9 -0
  26. package/src/lib/common/protocol.zig +277 -159
  27. package/src/lib/common/rational.zig +353 -0
  28. package/src/lib/common/rng.zig +10 -10
  29. package/src/lib/common/util.zig +12 -0
  30. package/src/lib/gen1/README.md +239 -151
  31. package/src/lib/gen1/calc.zig +426 -0
  32. package/src/lib/gen1/chance.zig +1044 -0
  33. package/src/lib/gen1/data/moves.zig +10 -10
  34. package/src/lib/gen1/data/species.zig +2 -2
  35. package/src/lib/gen1/data/types.zig +3 -3
  36. package/src/lib/gen1/data.zig +64 -38
  37. package/src/lib/gen1/helpers.zig +592 -35
  38. package/src/lib/gen1/mechanics.zig +804 -500
  39. package/src/lib/gen2/README.md +87 -13
  40. package/src/lib/gen2/data/items.zig +3 -3
  41. package/src/lib/gen2/data/moves.zig +16 -3
  42. package/src/lib/gen2/data/species.zig +1 -1
  43. package/src/lib/gen2/data/types.zig +3 -3
  44. package/src/lib/gen2/data.zig +73 -38
  45. package/src/lib/gen2/helpers.zig +15 -16
  46. package/src/lib/gen2/mechanics.zig +476 -11
  47. package/src/lib/pkmn.zig +61 -2
@@ -54,13 +54,12 @@ The following information is required to simulate a Generation II Pokémon battl
54
54
  | | | |
55
55
  | `active.last_used_move` | `PlayerUsedMove` | `side.lastMove` |
56
56
 
57
- - Pokémon Showdown does not implement the correct Generation II RNG and as such its `seed` is
57
+ - Pokémon Showdown doesn't implement the correct Generation II RNG and as such its `seed` is
58
58
  different
59
- - `battle.turn` only needs to be tracked in order to be compatible with the Pokémon Showdown
60
- protocol
59
+ - `battle.turn` only needs to be tracked to be compatible with the Pokémon Showdown protocol
61
60
  - Battle results (win, lose, draw) or request states are communicated via the return code of
62
61
  `Battle.update`
63
- - Nicknames (`BattleMonNick`/`pokemon.name`) are not handled by the pkmn engine as they are expected
62
+ - Nicknames (`BattleMonNick`/`pokemon.name`) aren't handled by the pkmn engine as they're expected
64
63
  to be handled by driver code if required
65
64
 
66
65
  ==**TODO**==
@@ -77,9 +76,9 @@ between the data structure for the "active" Pokémon and its party members (see
77
76
 
78
77
  ### `Pokemon` / `ActivePokemon`
79
78
 
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.
79
+ Similar to the cartridge, to save space different information is stored depending on whether a
80
+ [Pokémon](https://pkmn.cc/bulba/Pok%c3%a9mon_data_structure_%28Generation_II%29) is actively
81
+ participating in battle vs. is switched out (pret's [`battle_struct` vs.
83
82
  `party_struct`](https://pkmn.cc/pokecrystal/macros/ram.asm)). In Pokémon Showdown, all the Pokemon
84
83
  are represented by the same
85
84
  [`Pokemon`](https://github.com/smogon/pokemon-showdown/blob/master/sim/pokemon.ts) class, and static
@@ -93,14 +92,14 @@ A `MoveSlot` is a data-type for a `(move, current pp)` pair. A pared down versio
93
92
  Showdown's `Pokemon#moveSlot`, it also stores data from the cartridge's `battle_struct::Move` macro
94
93
  and can be used to replace the `PlayerMove*` data. Move PP is stored as a full byte instead of how
95
94
  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
95
+ to store the number of applied PP Ups). PP Up bits don't actually need to be stored on move slot
97
96
  as max PP is never relevant in Generation II (the Mystery Berry always increases PP from 0 -> 5).
98
97
 
99
98
  #### `Status`
100
99
 
101
100
  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
101
+ condition](https://pkmn.cc/bulba/Status_condition), mirroring how it's stored on the cartridge. A
102
+ value of `0x00` means that the Pokémon isn't affected by any major status, otherwise the lower 3
104
103
  bits represent the remaining duration for Sleep. Other status are denoted by the presence of
105
104
  individual bits - at most one status should be set at any given time.
106
105
 
@@ -161,7 +160,7 @@ boosts should always range from `-6`...`6` instead of `1`...`13` as on the cartr
161
160
 
162
161
  `Moves` serves as an identifier for a unique [Pokémon move](https://pkmn.cc/bulba/Move) that can be
163
162
  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
163
+ earlier, PP information isn't strictly necessary in Generation I, but fits neatly into the 4 bits of
165
164
  padding after noticing that all PP values are multiples of 5. `Moves.None` exists as a special
166
165
  sentinel value to indicate `null`.
167
166
 
@@ -215,11 +214,86 @@ entropy](https://en.wikipedia.org/wiki/Entropy_(information_theory))) is as foll
215
214
  | **wrap** | 0...5 | 3 | | **perish song** | 0...4 | 3 |
216
215
  | **item** | TODO | 5? | | **gender** | 0..1 | 1 |
217
216
 
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 &
217
+ From this one can determine the minimum bits required to store each data structure to determine how
218
+ much overhead the preceding representations have after taking into consideration [alignment &
220
219
  padding](https://en.wikipedia.org/wiki/Data_structure_alignment) and
221
220
  [denormalization](https://en.wikipedia.org/wiki/Denormalization):
222
221
 
222
+ ## Details
223
+
224
+ | pkmn | Pokémon Crystal (pret) |
225
+ | ------------------------- | ------------------------------------------------- |
226
+ | `start` | `DoBattle` |
227
+ | `update` | `BattleTurn` |
228
+ | `selectMove` / `saveMove` | `CheckPlayerLockedIn` / `ParsePlayerAction` |
229
+ | `switchIn` | `SwitchPlayerMon` / `SendOutPlayerMon` |
230
+ | `turnOrder` | `DetermineMoveOrder` |
231
+ | `doTurn` | `DoTurn` |
232
+ | `executeMove` | `DoMove` |
233
+ | `beforeMove` | `CheckTurn` |
234
+ | `canMove` | |
235
+ | `decrementPP` | |
236
+ | `doMove` | |
237
+ | `checkCriticalHit` | `BattleCommand_Critical` |
238
+ | `calcDamage` | `BattleCommand_DamageCalc` / `PlayerAttackDamage` |
239
+ | `adjustDamage` | `BattleCommand_Stab` |
240
+ | `randomizeDamage` | `BattleCommand_DamageVariation` |
241
+ | `specialDamage` | `BattleCommand_ConstantDamage` |
242
+ | `applyDamage` | `BattleCommand_ApplyDamage` |
243
+ | `checkHit` | `BattleCommand_CheckHit` |
244
+ | `checkFaint` | `CheckFaint` |
245
+ | `handleResidual` | `ResidualDamage` |
246
+ | `endTurn` | `EndTurn` |
247
+
248
+ DoBattle
249
+ BattleTurn
250
+ - HandleBerserkGene
251
+ - CheckPlayerLockedIn
252
+ - BattleMenu
253
+ - ParsePlayerAction
254
+ - DetermineMoveOrder
255
+ - CompareMovePriority
256
+ - GetMovePriority
257
+ - Battle_*
258
+ - Handle*Switch
259
+ - SwitchPlayerMon
260
+ - BreakAttraction
261
+ - SendOutPlayerMon
262
+ - SpikesDamage
263
+ - *Turn_EndOpponentProtectEndureDestinyBond
264
+ - EndUserDestinyBond
265
+ - Do*Turn
266
+ - EndOpponentProtectEndureDestinyBond
267
+ - ResidualDamage
268
+ - CheckFaint_*
269
+ - HandleBetweenTurnEffects
270
+ - HandleFutureSight
271
+ - HandleWeather
272
+ - HandleWrap
273
+ - HandlePerishSong
274
+ - HandleLeftovers
275
+ - HandleMysteryBerry
276
+ - HandleDefrost
277
+ - HandleSafeguard
278
+ - HandleScreens
279
+ - HandleStatBoostingHeldItems
280
+ - HandleHealingItems
281
+ - HandleEncore
282
+
283
+ DoTurn
284
+ DoMove = executeMove
285
+ CheckTurn = beforeMove
286
+ BattleCommand_Critical = checkCriticalHit
287
+ BattleCommand_Stab = adjustDamage
288
+ BattleCommand_DamageVariation = randomizeDamage
289
+ BattleCommand_CheckHit = checkHit
290
+ BattleCommand_ApplyDamage = applyDamage
291
+ BattleCommand_CheckFaint
292
+ BattleCommand_BuildOpponentRage = buildRage
293
+ BattleCommand_DamageCalc (PlayerAttackDamage / EnemyAttackDamage) = calcDamage
294
+ BattleCommand_ConstantDamage = specialDamage
295
+ DoEnemyDamage / DoPlayerDamage / DoSubstituteDamage
296
+
223
297
  ==**TODO**==
224
298
 
225
299
  ## Protocol
@@ -219,18 +219,18 @@ pub const Item = enum(u8) {
219
219
  pub inline fn boost(item: Item) ?Type {
220
220
  assert(item != .None);
221
221
  if (item == .PolkadotBow) return .Normal;
222
- return if (@enumToInt(item) <= 18) @intToEnum(Type, @enumToInt(item) - 1) else null;
222
+ return if (@intFromEnum(item) <= 18) @enumFromInt(@intFromEnum(item) - 1) else null;
223
223
  }
224
224
 
225
225
  /// Whether or not this item is a Berry.
226
226
  pub inline fn berry(item: Item) bool {
227
227
  assert(item != .None);
228
- return @enumToInt(item) > 30 and @enumToInt(item) <= 40;
228
+ return @intFromEnum(item) > 30 and @intFromEnum(item) <= 40;
229
229
  }
230
230
 
231
231
  /// Whether or not this item is Mail.
232
232
  pub inline fn mail(item: Item) bool {
233
233
  assert(item != .None);
234
- return @enumToInt(item) > 60 and @enumToInt(item) <= 70;
234
+ return @intFromEnum(item) > 60 and @intFromEnum(item) <= 70;
235
235
  }
236
236
  };
@@ -279,9 +279,11 @@ pub const Move = enum(u8) {
279
279
  target: Target,
280
280
  /// The chance of the move's secondary effect occurring.
281
281
  chance: u8 = 0,
282
+ /// The priority of the move
283
+ priority: i8 = 0,
282
284
 
283
285
  comptime {
284
- assert(@sizeOf(Data) == 7);
286
+ assert(@sizeOf(Data) == 8);
285
287
  }
286
288
  };
287
289
 
@@ -450,6 +452,7 @@ pub const Move = enum(u8) {
450
452
  .pp = 20,
451
453
  .accuracy = 100,
452
454
  .target = .Other,
455
+ .priority = -1,
453
456
  },
454
457
  // Fly
455
458
  .{
@@ -709,6 +712,7 @@ pub const Move = enum(u8) {
709
712
  .pp = 20,
710
713
  .accuracy = 100,
711
714
  .target = .Other,
715
+ .priority = -1,
712
716
  },
713
717
  // Sing
714
718
  .{
@@ -916,6 +920,7 @@ pub const Move = enum(u8) {
916
920
  .pp = 20,
917
921
  .accuracy = 100,
918
922
  .target = .Depends,
923
+ .priority = -1,
919
924
  },
920
925
  // SeismicToss
921
926
  .{
@@ -1191,6 +1196,7 @@ pub const Move = enum(u8) {
1191
1196
  .pp = 30,
1192
1197
  .accuracy = 100,
1193
1198
  .target = .Other,
1199
+ .priority = 1,
1194
1200
  },
1195
1201
  // Rage
1196
1202
  .{
@@ -1962,6 +1968,7 @@ pub const Move = enum(u8) {
1962
1968
  .pp = 10,
1963
1969
  .accuracy = 100,
1964
1970
  .target = .Self,
1971
+ .priority = 2,
1965
1972
  },
1966
1973
  // MachPunch
1967
1974
  .{
@@ -1971,6 +1978,7 @@ pub const Move = enum(u8) {
1971
1978
  .pp = 30,
1972
1979
  .accuracy = 100,
1973
1980
  .target = .Other,
1981
+ .priority = 1,
1974
1982
  },
1975
1983
  // ScaryFace
1976
1984
  .{
@@ -2102,6 +2110,7 @@ pub const Move = enum(u8) {
2102
2110
  .pp = 5,
2103
2111
  .accuracy = 100,
2104
2112
  .target = .Self,
2113
+ .priority = 2,
2105
2114
  },
2106
2115
  // BoneRush
2107
2116
  .{
@@ -2156,6 +2165,7 @@ pub const Move = enum(u8) {
2156
2165
  .pp = 10,
2157
2166
  .accuracy = 100,
2158
2167
  .target = .Self,
2168
+ .priority = 2,
2159
2169
  },
2160
2170
  // Charm
2161
2171
  .{
@@ -2433,6 +2443,7 @@ pub const Move = enum(u8) {
2433
2443
  .pp = 10,
2434
2444
  .accuracy = 100,
2435
2445
  .target = .Other,
2446
+ .priority = -1,
2436
2447
  },
2437
2448
  // MorningSun
2438
2449
  .{
@@ -2525,6 +2536,7 @@ pub const Move = enum(u8) {
2525
2536
  .pp = 20,
2526
2537
  .accuracy = 100,
2527
2538
  .target = .Depends,
2539
+ .priority = -1,
2528
2540
  },
2529
2541
  // PsychUp
2530
2542
  .{
@@ -2543,6 +2555,7 @@ pub const Move = enum(u8) {
2543
2555
  .pp = 5,
2544
2556
  .accuracy = 100,
2545
2557
  .target = .Other,
2558
+ .priority = 1,
2546
2559
  },
2547
2560
  // AncientPower
2548
2561
  .{
@@ -2770,7 +2783,7 @@ pub const Move = enum(u8) {
2770
2783
 
2771
2784
  comptime {
2772
2785
  assert(@sizeOf(Move) == 1);
2773
- assert(@sizeOf(@TypeOf(DATA)) == 1757);
2786
+ assert(@sizeOf(@TypeOf(DATA)) == 2008);
2774
2787
  }
2775
2788
 
2776
2789
  /// The number of Pokémon moves in this generation.
@@ -2779,6 +2792,6 @@ pub const Move = enum(u8) {
2779
2792
  /// Returns the `Data` corresponding to the move.
2780
2793
  pub inline fn get(id: Move) Data {
2781
2794
  assert(id != .None);
2782
- return DATA[@enumToInt(id) - 1];
2795
+ return DATA[@intFromEnum(id) - 1];
2783
2796
  }
2784
2797
  };
@@ -1793,6 +1793,6 @@ pub const Species = enum(u8) {
1793
1793
  /// Returns the `Data` corresponding to the species.
1794
1794
  pub fn get(id: Species) Data {
1795
1795
  assert(id != .None);
1796
- return DATA[@enumToInt(id) - 1];
1796
+ return DATA[@intFromEnum(id) - 1];
1797
1797
  }
1798
1798
  };
@@ -86,17 +86,17 @@ pub const Type = enum(u8) {
86
86
 
87
87
  /// Whether or not this type is considered to be special as opposed to physical.
88
88
  pub inline fn special(self: Type) bool {
89
- return @enumToInt(self) >= @enumToInt(Type.Fire);
89
+ return @intFromEnum(self) >= @intFromEnum(Type.Fire);
90
90
  }
91
91
 
92
92
  /// The `Effectiveness` of type `t2` vs. type `t1`.
93
93
  pub inline fn effectiveness(t1: Type, t2: Type) Effectiveness {
94
- return CHART[@enumToInt(t1)][@enumToInt(t2)];
94
+ return CHART[@intFromEnum(t1)][@intFromEnum(t2)];
95
95
  }
96
96
 
97
97
  /// The precedence order of type type.
98
98
  pub inline fn precedence(self: Type) u8 {
99
- return PRECEDENCE[@enumToInt(self)];
99
+ return PRECEDENCE[@intFromEnum(self)];
100
100
  }
101
101
  };
102
102
 
@@ -1,10 +1,13 @@
1
1
  const std = @import("std");
2
2
  const builtin = @import("builtin");
3
3
 
4
+ const pkmn = @import("../pkmn.zig");
5
+
4
6
  const data = @import("../common/data.zig");
5
7
  const DEBUG = @import("../common/debug.zig").print;
6
- const options = @import("../common/options.zig");
8
+ const protocol = @import("../common/protocol.zig");
7
9
  const rng = @import("../common/rng.zig");
10
+ const util = @import("../common/util.zig");
8
11
 
9
12
  const items = @import("data/items.zig");
10
13
  const moves = @import("data/moves.zig");
@@ -13,6 +16,8 @@ const types = @import("data/types.zig");
13
16
 
14
17
  const gen1 = @import("../gen1/data.zig");
15
18
 
19
+ const calc = @import("calc.zig");
20
+ const chance = @import("chance.zig");
16
21
  const mechanics = @import("mechanics.zig");
17
22
 
18
23
  const assert = std.debug.assert;
@@ -31,12 +36,17 @@ pub const LOGS_SIZE = if (builtin.mode == .ReleaseSmall)
31
36
  else
32
37
  std.math.ceilPowerOfTwo(usize, MAX_LOGS) catch unreachable;
33
38
 
39
+ /// Null object pattern implementation of pkmn.battle.Options for Generation II.
40
+ pub const NULL = pkmn.battle.options(protocol.NULL, chance.NULL, calc.NULL);
41
+
34
42
  const Choice = data.Choice;
35
43
  const ID = data.ID;
36
44
  const Player = data.Player;
37
45
  const Result = data.Result;
38
46
 
39
- const showdown = options.showdown;
47
+ const PointerType = util.PointerType;
48
+
49
+ const showdown = pkmn.options.showdown;
40
50
 
41
51
  pub const PRNG = rng.PRNG(2);
42
52
 
@@ -49,20 +59,22 @@ pub fn Battle(comptime RNG: anytype) type {
49
59
  turn: u16 = 0,
50
60
  field: Field = .{},
51
61
 
52
- pub inline fn side(self: *Self, player: Player) *Side {
53
- return &self.sides[@enumToInt(player)];
62
+ pub inline fn side(self: anytype, player: Player) PointerType(@TypeOf(self), Side) {
63
+ assert(@typeInfo(@TypeOf(self)).Pointer.child == Self);
64
+ return &self.sides[@intFromEnum(player)];
54
65
  }
55
66
 
56
- pub inline fn foe(self: *Self, player: Player) *Side {
57
- return &self.sides[@enumToInt(player.foe())];
67
+ pub inline fn foe(self: anytype, player: Player) PointerType(@TypeOf(self), Side) {
68
+ assert(@typeInfo(@TypeOf(self)).Pointer.child == Self);
69
+ return &self.sides[@intFromEnum(player.foe())];
58
70
  }
59
71
 
60
- pub inline fn active(self: *Self, player: Player) ID {
61
- return player.ident(@intCast(u3, self.side(player).pokemon[0].position));
72
+ pub inline fn active(self: *const Self, player: Player) ID {
73
+ return player.ident(@intCast(self.side(player).pokemon[0].position));
62
74
  }
63
75
 
64
- pub fn update(self: *Self, c1: Choice, c2: Choice, log: anytype) !Result {
65
- return mechanics.update(self, c1, c2, log);
76
+ pub fn update(self: *Self, c1: Choice, c2: Choice, options: anytype) !Result {
77
+ return mechanics.update(self, c1, c2, options);
66
78
  }
67
79
 
68
80
  pub fn choices(self: *Self, player: Player, request: Choice.Type, out: []Choice) u8 {
@@ -76,7 +88,7 @@ test Battle {
76
88
  try expectEqual(496, @sizeOf(Battle(PRNG)));
77
89
  }
78
90
 
79
- const Field = packed struct {
91
+ pub const Field = packed struct {
80
92
  weather: Weather = .None,
81
93
  weather_duration: u4 = 0,
82
94
 
@@ -85,19 +97,21 @@ const Field = packed struct {
85
97
  }
86
98
  };
87
99
 
88
- const Weather = enum(u4) {
100
+ pub const Weather = enum(u4) {
89
101
  None,
90
102
  Rain,
91
103
  Sun,
92
104
  Sandstorm,
93
105
  };
94
106
 
95
- const Side = extern struct {
107
+ pub const Side = extern struct {
96
108
  pokemon: [6]Pokemon = [_]Pokemon{.{}} ** 6,
97
109
  active: ActivePokemon = .{},
98
- conditions: Conditions,
99
- last_used_move: Move = .None,
110
+ conditions: Conditions = .{},
111
+ // NB: wCurPlayerMove / wCurEnemyMove (?)
100
112
  _: u8 = 0,
113
+ // NB: wLastPlayerCounterMove / wLastEnemyCounterMove (wLastPlayerMove / wLastEnemyMove)
114
+ last_used_move: Move = .None,
101
115
 
102
116
  const Conditions = packed struct {
103
117
  Spikes: bool = false,
@@ -118,41 +132,57 @@ const Side = extern struct {
118
132
  assert(@sizeOf(Side) == 240);
119
133
  }
120
134
 
121
- pub inline fn get(self: *const Side, slot: u8) *Pokemon {
135
+ pub inline fn get(self: anytype, slot: u8) PointerType(@TypeOf(self), Pokemon) {
136
+ assert(@typeInfo(@TypeOf(self)).Pointer.child == Side);
122
137
  assert(slot > 0 and slot <= 6);
123
138
  const id = self.pokemon[slot - 1].position;
124
139
  assert(id > 0 and id <= 6);
125
140
  return &self.pokemon[id - 1];
126
141
  }
127
142
 
128
- pub inline fn stored(self: *Side) *Pokemon {
143
+ pub inline fn stored(self: anytype) PointerType(@TypeOf(self), Pokemon) {
144
+ assert(@typeInfo(@TypeOf(self)).Pointer.child == Side);
129
145
  return self.get(1);
130
146
  }
147
+
148
+ pub inline fn lastMove(self: anytype, encore: bool) Move {
149
+ assert(self.last_used_move != .None or !self.active.volatiles.dirty);
150
+ if (encore) return self.last_used_move;
151
+ return if (self.active.volatiles.dirty) .None else self.last_used_move;
152
+ }
153
+
154
+ // TODO
155
+ // pub inline fn useMove(self: anytype) void {
156
+ // assert(side.last_selected_move != .None);
157
+ // self.last_used_move = side.last_selected_move;
158
+ // self.active.volatiles.dirty = false;
159
+ // }
131
160
  };
132
161
 
133
162
  // NOTE: DVs (Gender & Hidden Power) and Happiness are stored only in Pokemon
134
- const ActivePokemon = extern struct {
135
- volatiles: Volatile align(4) = .{},
163
+ pub const ActivePokemon = extern struct {
164
+ volatiles: Volatiles align(4) = .{},
136
165
  stats: Stats(u16) = .{},
137
166
  moves: [4]MoveSlot = [_]MoveSlot{.{}} ** 4,
138
167
  boosts: Boosts = .{},
168
+ types: Types = .{},
139
169
  species: Species = .None,
140
- item: Item = .None,
141
- last_move: Move = .None,
170
+
142
171
  _: u8 = 0,
143
172
 
144
173
  comptime {
145
174
  assert(@sizeOf(ActivePokemon) == 44);
146
175
  }
147
176
 
148
- pub inline fn move(self: *ActivePokemon, mslot: u8) *MoveSlot {
177
+ pub inline fn move(self: anytype, mslot: u8) PointerType(@TypeOf(self), MoveSlot) {
178
+ assert(@typeInfo(@TypeOf(self)).Pointer.child == Pokemon);
149
179
  assert(mslot > 0 and mslot <= 4);
150
180
  assert(self.moves[mslot - 1].id != .None);
151
181
  return &self.moves[mslot - 1];
152
182
  }
153
183
  };
154
184
 
155
- const Pokemon = extern struct {
185
+ pub const Pokemon = extern struct {
156
186
  stats: Stats(u16) = .{},
157
187
  moves: [4]MoveSlot = [_]MoveSlot{.{}} ** 4,
158
188
  types: Types = .{},
@@ -169,7 +199,8 @@ const Pokemon = extern struct {
169
199
  assert(@sizeOf(Pokemon) == 32);
170
200
  }
171
201
 
172
- pub inline fn move(self: *Pokemon, mslot: u8) *MoveSlot {
202
+ pub inline fn move(self: anytype, mslot: u8) PointerType(@TypeOf(self), MoveSlot) {
203
+ assert(@typeInfo(@TypeOf(self)).Pointer.child == Pokemon);
173
204
  assert(mslot > 0 and mslot <= 4);
174
205
  assert(self.moves[mslot - 1].id != .None);
175
206
  return &self.moves[mslot - 1];
@@ -182,7 +213,7 @@ pub const Gender = enum(u2) {
182
213
  Unknown,
183
214
  };
184
215
 
185
- const DVs = packed struct {
216
+ pub const DVs = packed struct {
186
217
  gender: Gender = .Male,
187
218
  pow: u6 = 40,
188
219
  type: Type = .Dark,
@@ -205,11 +236,11 @@ const DVs = packed struct {
205
236
  0xFF => .Unknown,
206
237
  else => if ((@as(u8, dvs.atk) << 4 | dvs.spe) < ratio) Gender.Female else Gender.Male,
207
238
  };
208
- const p = @intCast(u6, 1 + ((5 * @as(u8, (dvs.atk & 0b1000) | ((dvs.def & 0b1000)) >> 1 |
239
+ const p: u6 = @intCast(1 + ((5 * @as(u8, (dvs.atk & 0b1000) | ((dvs.def & 0b1000)) >> 1 |
209
240
  ((dvs.spe & 0b1000)) >> 2 | ((dvs.spc & 0b1000)) >> 3) + (dvs.spc & 0b0011)) >> 1));
210
241
  var t = @as(u8, dvs.def & 0b0011) + (@as(u8, dvs.atk & 0b0011) << 2) + 1;
211
- if (t >= @enumToInt(Type.@"???")) t = t + 1;
212
- return DVs{ .gender = g, .pow = p, .type = @intToEnum(Type, t) };
242
+ if (t >= @intFromEnum(Type.@"???")) t = t + 1;
243
+ return DVs{ .gender = g, .pow = p, .type = @enumFromInt(t) };
213
244
  }
214
245
 
215
246
  comptime {
@@ -238,7 +269,7 @@ test DVs {
238
269
  try expectEqual(Gender.Female, dvs.gender);
239
270
  }
240
271
 
241
- const MoveSlot = packed struct {
272
+ pub const MoveSlot = packed struct {
242
273
  id: Move = .None,
243
274
  pp_ups: u2 = 3,
244
275
  pp: u6 = 0,
@@ -250,7 +281,7 @@ const MoveSlot = packed struct {
250
281
 
251
282
  pub const Status = gen1.Status;
252
283
 
253
- const Volatile = packed struct {
284
+ pub const Volatiles = packed struct {
254
285
  Bide: bool = false,
255
286
  Thrashing: bool = false,
256
287
  Flinch: bool = false,
@@ -284,8 +315,9 @@ const Volatile = packed struct {
284
315
  trapped: bool = false,
285
316
  switched: bool = false,
286
317
  switching: bool = false,
318
+ dirty: bool = false,
287
319
 
288
- _: u5 = 0,
320
+ _: u4 = 0,
289
321
 
290
322
  bind: u4 = 0,
291
323
  future_sight: FutureSight = .{},
@@ -296,10 +328,11 @@ const Volatile = packed struct {
296
328
  toxic: u8 = 0,
297
329
  confusion: u4 = 0,
298
330
  encore: u4 = 0,
331
+ // TODO merge with attacks (ie rollout/thrashing)? need to be zeroed at same time
299
332
  fury_cutter: u4 = 0,
300
333
  perish_song: u4 = 0,
301
334
  protect: u4 = 0,
302
- rollout: u4 = 0,
335
+ attacks: u4 = 0, // bide/thrashing/rollout turns
303
336
 
304
337
  const Disabled = packed struct {
305
338
  move: u4 = 0,
@@ -312,7 +345,7 @@ const Volatile = packed struct {
312
345
  };
313
346
 
314
347
  comptime {
315
- assert(@sizeOf(Volatile) == 16);
348
+ assert(@sizeOf(Volatiles) == 16);
316
349
  }
317
350
  };
318
351
 
@@ -327,10 +360,11 @@ pub fn Stats(comptime T: type) type {
327
360
 
328
361
  pub fn calc(comptime stat: []const u8, base: T, dv: u4, exp: u16, level: u8) T {
329
362
  assert(level > 0 and level <= 100);
330
- const evs = @min(255, @floatToInt(u16, @ceil(@sqrt(@intToFloat(f32, exp)))));
363
+ const float: f32 = @floatFromInt(exp);
364
+ const evs: u16 = @min(255, @as(u16, @intFromFloat(@ceil(@sqrt(float)))));
331
365
  const core: u32 = (2 *% (@as(u32, base) +% dv)) +% (evs / 4);
332
366
  const factor: u32 = if (std.mem.eql(u8, stat, "hp")) level + 10 else 5;
333
- return @intCast(T, core *% @as(u32, level) / 100 +% factor);
367
+ return @intCast(core *% @as(u32, level) / 100 +% factor);
334
368
  }
335
369
  };
336
370
  }
@@ -390,17 +424,18 @@ test Item {
390
424
  pub const Move = moves.Move;
391
425
 
392
426
  test Move {
393
- try expectEqual(251, @enumToInt(Move.BeatUp));
427
+ try expectEqual(251, @intFromEnum(Move.BeatUp));
394
428
  const move = Move.get(.DynamicPunch);
395
429
  try expectEqual(Move.Effect.ConfusionChance, move.effect);
396
430
  try expectEqual(@as(u8, 50), move.accuracy);
397
431
  try expectEqual(@as(u8, 5), move.pp);
432
+ try expectEqual(@as(i8, -1), Move.get(.Counter).priority);
398
433
  }
399
434
 
400
435
  pub const Species = species.Species;
401
436
 
402
437
  test Species {
403
- try expectEqual(152, @enumToInt(Species.Chikorita));
438
+ try expectEqual(152, @intFromEnum(Species.Chikorita));
404
439
  try expectEqual(@as(u8, 100), Species.get(.Celebi).stats.spd);
405
440
  }
406
441
 
@@ -409,7 +444,7 @@ pub const Types = types.Types;
409
444
  pub const Effectiveness = gen1.Effectiveness;
410
445
 
411
446
  test Types {
412
- try expectEqual(13, @enumToInt(Type.Electric));
447
+ try expectEqual(13, @intFromEnum(Type.Electric));
413
448
 
414
449
  try expect(!Type.Steel.special());
415
450
  try expect(Type.Dark.special());