@pkmn/engine 0.1.0-dev.8b83af6b → 0.1.0-dev.900756a9

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 (49) hide show
  1. package/README.md +53 -42
  2. package/build/pkg/addon.js +3 -2
  3. package/build/pkg/addon.js.map +1 -1
  4. package/build/pkg/data/ids.json +12 -12
  5. package/build/pkg/data/layout.json +26 -4
  6. package/build/pkg/data/protocol.json +69 -12
  7. package/build/pkg/data.d.ts +2 -2
  8. package/build/pkg/gen1.js +15 -18
  9. package/build/pkg/gen1.js.map +1 -1
  10. package/build/pkg/protocol.js +56 -37
  11. package/build/pkg/protocol.js.map +1 -1
  12. package/build/tools/debug.js +3 -1
  13. package/build/tools/debug.js.map +1 -1
  14. package/build.zig +96 -65
  15. package/package.json +40 -19
  16. package/src/bin/install-pkmn-engine +10 -5
  17. package/src/lib/binding/node.zig +40 -132
  18. package/src/lib/binding/wasm.zig +2 -0
  19. package/src/lib/common/battle.zig +17 -0
  20. package/src/lib/common/data.zig +8 -20
  21. package/src/lib/common/debug.zig +20 -11
  22. package/src/lib/common/js.zig +118 -0
  23. package/src/lib/common/optional.zig +67 -0
  24. package/src/lib/common/options.zig +9 -0
  25. package/src/lib/common/protocol.zig +635 -88
  26. package/src/lib/common/rational.zig +353 -0
  27. package/src/lib/common/rng.zig +19 -10
  28. package/src/lib/common/util.zig +12 -0
  29. package/src/lib/gen1/README.md +284 -204
  30. package/src/lib/gen1/calc.zig +426 -0
  31. package/src/lib/gen1/chance.zig +1060 -0
  32. package/src/lib/gen1/data/moves.zig +192 -180
  33. package/src/lib/gen1/data/species.zig +2 -2
  34. package/src/lib/gen1/data/types.zig +7 -7
  35. package/src/lib/gen1/data.zig +67 -58
  36. package/src/lib/gen1/helpers.zig +566 -10
  37. package/src/lib/gen1/mechanics.zig +800 -563
  38. package/src/lib/gen2/README.md +105 -85
  39. package/src/lib/gen2/calc.zig +143 -0
  40. package/src/lib/gen2/chance.zig +1314 -0
  41. package/src/lib/gen2/data/items.zig +12 -18
  42. package/src/lib/gen2/data/moves.zig +663 -569
  43. package/src/lib/gen2/data/species.zig +1 -1
  44. package/src/lib/gen2/data/types.zig +134 -31
  45. package/src/lib/gen2/data.zig +115 -78
  46. package/src/lib/gen2/generated.zig +808 -0
  47. package/src/lib/gen2/helpers.zig +11 -17
  48. package/src/lib/gen2/mechanics.zig +3492 -12
  49. package/src/lib/pkmn.zig +61 -2
@@ -1,4 +1,4 @@
1
- //! Code generated by `tools/generate` - manual edits will be overwritten
1
+ //! Code generated by `tools/generate` - manual edits will be overwritten.
2
2
 
3
3
  const std = @import("std");
4
4
 
@@ -1,4 +1,4 @@
1
- //! Code generated by `tools/generate` - manual edits will be overwritten
1
+ //! Code generated by `tools/generate` - manual edits will be overwritten.
2
2
 
3
3
  const std = @import("std");
4
4
 
@@ -14,15 +14,15 @@ const I = Effectiveness.Immune;
14
14
 
15
15
  /// Representation of a Generation II type in Pokémon.
16
16
  pub const Type = enum(u8) {
17
+ Ground,
18
+ Rock,
19
+ Steel,
17
20
  Normal,
18
21
  Fighting,
19
22
  Flying,
20
23
  Poison,
21
- Ground,
22
- Rock,
23
24
  Bug,
24
25
  Ghost,
25
- Steel,
26
26
  @"???",
27
27
  Fire,
28
28
  Water,
@@ -33,37 +33,37 @@ pub const Type = enum(u8) {
33
33
  Dragon,
34
34
  Dark,
35
35
 
36
- const CHART = [18][18]Effectiveness{
37
- [_]Effectiveness{ N, N, N, N, N, R, N, I, R, N, N, N, N, N, N, N, N, N }, // Normal
38
- [_]Effectiveness{ S, N, R, R, N, S, R, I, S, N, N, N, N, N, R, S, N, S }, // Fighting
39
- [_]Effectiveness{ N, S, N, N, N, R, S, N, R, N, N, N, S, R, N, N, N, N }, // Flying
40
- [_]Effectiveness{ N, N, N, R, R, R, N, R, I, N, N, N, S, N, N, N, N, N }, // Poison
41
- [_]Effectiveness{ N, N, I, S, N, S, R, N, S, N, S, N, R, S, N, N, N, N }, // Ground
42
- [_]Effectiveness{ N, R, S, N, R, N, S, N, R, N, S, N, N, N, N, S, N, N }, // Rock
43
- [_]Effectiveness{ N, R, R, R, N, N, N, R, R, N, R, N, S, N, S, N, N, S }, // Bug
44
- [_]Effectiveness{ I, N, N, N, N, N, N, S, R, N, N, N, N, N, S, N, N, R }, // Ghost
45
- [_]Effectiveness{ N, N, N, N, N, S, N, N, R, N, R, R, N, R, N, S, N, N }, // Steel
36
+ const CHART: [18][18]Effectiveness = .{
37
+ [_]Effectiveness{ N, S, S, N, N, I, S, R, N, N, S, N, R, S, N, N, N, N }, // Ground
38
+ [_]Effectiveness{ R, N, R, N, R, S, N, S, N, N, S, N, N, N, N, S, N, N }, // Rock
39
+ [_]Effectiveness{ N, S, R, N, N, N, N, N, N, N, R, R, N, R, N, S, N, N }, // Steel
40
+ [_]Effectiveness{ N, R, R, N, N, N, N, N, I, N, N, N, N, N, N, N, N, N }, // Normal
41
+ [_]Effectiveness{ N, S, S, S, N, R, R, R, I, N, N, N, N, N, R, S, N, S }, // Fighting
42
+ [_]Effectiveness{ N, R, R, N, S, N, N, S, N, N, N, N, S, R, N, N, N, N }, // Flying
43
+ [_]Effectiveness{ R, R, I, N, N, N, R, N, R, N, N, N, S, N, N, N, N, N }, // Poison
44
+ [_]Effectiveness{ N, N, R, N, R, R, R, N, R, N, R, N, S, N, S, N, N, S }, // Bug
45
+ [_]Effectiveness{ N, N, R, I, N, N, N, N, S, N, N, N, N, N, S, N, N, R }, // Ghost
46
46
  [_]Effectiveness{ N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N }, // ???
47
- [_]Effectiveness{ N, N, N, N, N, R, S, N, S, N, R, R, S, N, N, S, R, N }, // Fire
48
- [_]Effectiveness{ N, N, N, N, S, S, N, N, N, N, S, R, R, N, N, N, R, N }, // Water
49
- [_]Effectiveness{ N, N, R, R, S, S, R, N, R, N, R, S, R, N, N, N, R, N }, // Grass
50
- [_]Effectiveness{ N, N, S, N, I, N, N, N, N, N, N, S, R, R, N, N, R, N }, // Electric
51
- [_]Effectiveness{ N, S, N, S, N, N, N, N, R, N, N, N, N, N, R, N, N, I }, // Psychic
52
- [_]Effectiveness{ N, N, S, N, S, N, N, N, R, N, R, R, S, N, N, R, S, N }, // Ice
53
- [_]Effectiveness{ N, N, N, N, N, N, N, N, R, N, N, N, N, N, N, N, S, N }, // Dragon
54
- [_]Effectiveness{ N, R, N, N, N, N, N, S, R, N, N, N, N, N, S, N, N, R }, // Dark
47
+ [_]Effectiveness{ N, R, S, N, N, N, N, S, N, N, R, R, S, N, N, S, R, N }, // Fire
48
+ [_]Effectiveness{ S, S, N, N, N, N, N, N, N, N, S, R, R, N, N, N, R, N }, // Water
49
+ [_]Effectiveness{ S, S, R, N, N, R, R, R, N, N, R, S, R, N, N, N, R, N }, // Grass
50
+ [_]Effectiveness{ I, N, N, N, N, S, N, N, N, N, N, S, R, R, N, N, R, N }, // Electric
51
+ [_]Effectiveness{ N, N, R, N, S, N, S, N, N, N, N, N, N, N, R, N, N, I }, // Psychic
52
+ [_]Effectiveness{ S, N, R, N, N, S, N, N, N, N, R, R, S, N, N, R, S, N }, // Ice
53
+ [_]Effectiveness{ N, N, R, N, N, N, N, N, N, N, N, N, N, N, N, N, S, N }, // Dragon
54
+ [_]Effectiveness{ N, N, R, N, R, N, N, N, S, N, N, N, N, N, S, N, N, R }, // Dark
55
55
  };
56
56
 
57
57
  const PRECEDENCE = [_]u8{
58
+ 9, // Ground
59
+ 13, // Rock
60
+ 17, // Steel
58
61
  1, // Normal
59
62
  7, // Fighting
60
63
  10, // Flying
61
64
  8, // Poison
62
- 9, // Ground
63
- 13, // Rock
64
65
  12, // Bug
65
66
  14, // Ghost
66
- 17, // Steel
67
67
  0, // ???
68
68
  2, // Fire
69
69
  3, // Water
@@ -75,29 +75,126 @@ pub const Type = enum(u8) {
75
75
  16, // Dark
76
76
  };
77
77
 
78
+ const INDEXES = [_]u8{
79
+ 4, // Ground
80
+ 5, // Rock
81
+ 9, // Steel
82
+ 0, // Normal
83
+ 1, // Fighting
84
+ 2, // Flying
85
+ 3, // Poison
86
+ 7, // Bug
87
+ 8, // Ghost
88
+ 19, // ???
89
+ 20, // Fire
90
+ 21, // Water
91
+ 22, // Grass
92
+ 23, // Electric
93
+ 24, // Psychic
94
+ 25, // Ice
95
+ 26, // Dragon
96
+ 27, // Dark
97
+ };
98
+
99
+ const CONVERSION_2 = [_]Type{
100
+ .Normal,
101
+ .Fighting,
102
+ .Flying,
103
+ .Poison,
104
+ .Ground,
105
+ .Rock,
106
+ .@"???", // placeholder
107
+ .Bug,
108
+ .Ghost,
109
+ .Steel,
110
+ };
111
+
112
+ const HIDDEN_POWER = [_]Type{
113
+ .Fighting,
114
+ .Flying,
115
+ .Poison,
116
+ .Ground,
117
+ .Rock,
118
+ .Bug,
119
+ .Ghost,
120
+ .Steel,
121
+ .Fire,
122
+ .Water,
123
+ .Grass,
124
+ .Electric,
125
+ .Psychic,
126
+ .Ice,
127
+ .Dragon,
128
+ .Dark,
129
+ };
130
+
131
+ /// Order of Pokémon Showdown's types.
132
+ pub const SHOWDOWN = [_]Type{
133
+ .Fire,
134
+ .Ice,
135
+ .Steel,
136
+ .Electric,
137
+ .Ghost,
138
+ .Grass,
139
+ .Dark,
140
+ .Bug,
141
+ .Dragon,
142
+ .Fighting,
143
+ .Flying,
144
+ .Ground,
145
+ .Normal,
146
+ .Poison,
147
+ .Psychic,
148
+ .Rock,
149
+ .Water,
150
+ .@"???",
151
+ };
152
+
78
153
  comptime {
79
154
  assert(@bitSizeOf(Type) == 8);
80
155
  assert(@sizeOf(@TypeOf(CHART)) == 324);
81
156
  assert(@sizeOf(@TypeOf(PRECEDENCE)) == 18);
157
+ assert(@sizeOf(@TypeOf(INDEXES)) == 18);
158
+ assert(@sizeOf(@TypeOf(CONVERSION_2)) == 10);
159
+ assert(@sizeOf(@TypeOf(HIDDEN_POWER)) == 16);
160
+ assert(@sizeOf(@TypeOf(SHOWDOWN)) == 18);
82
161
  }
83
162
 
84
163
  /// The number of types in this generation.
85
164
  pub const size = 18;
86
165
 
87
166
  /// Whether or not this type is considered to be special as opposed to physical.
88
- pub inline fn special(self: Type) bool {
167
+ pub fn special(self: Type) bool {
89
168
  return @intFromEnum(self) >= @intFromEnum(Type.Fire);
90
169
  }
91
170
 
92
171
  /// The `Effectiveness` of type `t2` vs. type `t1`.
93
- pub inline fn effectiveness(t1: Type, t2: Type) Effectiveness {
172
+ pub fn effectiveness(t1: Type, t2: Type) Effectiveness {
94
173
  return CHART[@intFromEnum(t1)][@intFromEnum(t2)];
95
174
  }
96
175
 
97
- /// The precedence order of type type.
98
- pub inline fn precedence(self: Type) u8 {
176
+ /// The precedence order of Type `type`.
177
+ pub fn precedence(self: Type) u8 {
99
178
  return PRECEDENCE[@intFromEnum(self)];
100
179
  }
180
+
181
+ /// The internal index of this Type used by Present.
182
+ pub fn present(self: Type) u8 {
183
+ return INDEXES[@intFromEnum(self)];
184
+ }
185
+
186
+ /// The Type corresponding to a random roll of `num` for Conversion 2.
187
+ pub fn conversion2(num: u8) Type {
188
+ assert(num != 6);
189
+ assert(num < 10 or num >= 20);
190
+ assert(num <= 27);
191
+ return if (num < 10) CONVERSION_2[num] else @enumFromInt(num - 10);
192
+ }
193
+
194
+ /// The Type corresponding to a Hidden Power `index`.
195
+ pub fn hiddenPower(index: u8) Type {
196
+ return HIDDEN_POWER[index];
197
+ }
101
198
  };
102
199
 
103
200
  /// Representation of a Pokémon's typing.
@@ -112,12 +209,18 @@ pub const Types = extern struct {
112
209
  }
113
210
 
114
211
  /// Whether this typing is immune to type `t`.
115
- pub inline fn immune(self: Types, t: Type) bool {
212
+ pub fn immune(self: Types, t: Type) bool {
116
213
  return t.effectiveness(self.type1) == I or t.effectiveness(self.type2) == I;
117
214
  }
118
215
 
119
216
  /// Whether this typing includes type `t`.
120
- pub inline fn includes(self: Types, t: Type) bool {
217
+ pub fn includes(self: Types, t: Type) bool {
121
218
  return self.type1 == t or self.type2 == t;
122
219
  }
220
+
221
+ /// Whether this typing is immune to damage from Sandstorm.
222
+ pub fn sandstormImmune(self: Types) bool {
223
+ return @intFromEnum(self.type1) <= @intFromEnum(Type.Steel) or
224
+ @intFromEnum(self.type2) <= @intFromEnum(Type.Steel);
225
+ }
123
226
  };
@@ -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,16 +36,22 @@ 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
 
43
53
  pub fn Battle(comptime RNG: anytype) type {
54
+ const padding = if (!showdown) 3 else 5;
44
55
  return extern struct {
45
56
  const Self = @This();
46
57
 
@@ -49,20 +60,24 @@ pub fn Battle(comptime RNG: anytype) type {
49
60
  turn: u16 = 0,
50
61
  field: Field = .{},
51
62
 
52
- pub inline fn side(self: *Self, player: Player) *Side {
63
+ _: [padding]u8 = .{0} ** padding,
64
+
65
+ pub fn side(self: anytype, player: Player) PointerType(@TypeOf(self), Side) {
66
+ assert(@typeInfo(@TypeOf(self)).Pointer.child == Self);
53
67
  return &self.sides[@intFromEnum(player)];
54
68
  }
55
69
 
56
- pub inline fn foe(self: *Self, player: Player) *Side {
70
+ pub fn foe(self: anytype, player: Player) PointerType(@TypeOf(self), Side) {
71
+ assert(@typeInfo(@TypeOf(self)).Pointer.child == Self);
57
72
  return &self.sides[@intFromEnum(player.foe())];
58
73
  }
59
74
 
60
- pub inline fn active(self: *Self, player: Player) ID {
75
+ pub fn active(self: *const Self, player: Player) ID {
61
76
  return player.ident(@intCast(self.side(player).pokemon[0].position));
62
77
  }
63
78
 
64
- pub fn update(self: *Self, c1: Choice, c2: Choice, log: anytype) !Result {
65
- return mechanics.update(self, c1, c2, log);
79
+ pub fn update(self: *Self, c1: Choice, c2: Choice, options: anytype) !Result {
80
+ return mechanics.update(self, c1, c2, options);
66
81
  }
67
82
 
68
83
  pub fn choices(self: *Self, player: Player, request: Choice.Type, out: []Choice) u8 {
@@ -71,35 +86,32 @@ pub fn Battle(comptime RNG: anytype) type {
71
86
  };
72
87
  }
73
88
 
74
- // TODO
75
89
  test Battle {
76
- try expectEqual(496, @sizeOf(Battle(PRNG)));
90
+ try expectEqual(512, @sizeOf(Battle(PRNG)));
77
91
  }
78
92
 
79
- const Field = packed struct {
93
+ pub const Field = packed struct(u8) {
80
94
  weather: Weather = .None,
81
95
  weather_duration: u4 = 0,
82
-
83
- comptime {
84
- assert(@sizeOf(Field) == 1);
85
- }
86
96
  };
87
97
 
88
- const Weather = enum(u4) {
98
+ pub const Weather = enum(u4) {
89
99
  None,
90
100
  Rain,
91
101
  Sun,
92
102
  Sandstorm,
93
103
  };
94
104
 
95
- const Side = extern struct {
105
+ pub const Side = extern struct {
96
106
  pokemon: [6]Pokemon = [_]Pokemon{.{}} ** 6,
97
- active: ActivePokemon = .{},
98
- conditions: Conditions,
107
+ active: ActivePokemon align(8) = .{},
108
+ conditions: Conditions = .{},
109
+ // NB: wLastPlayerCounterMove / wLastEnemyCounterMove (wLastPlayerMove / wLastEnemyMove)
99
110
  last_used_move: Move = .None,
100
- _: u8 = 0,
101
111
 
102
- const Conditions = packed struct {
112
+ _: [7]u8 = .{0} ** 7,
113
+
114
+ const Conditions = packed struct(u32) {
103
115
  Spikes: bool = false,
104
116
  Safeguard: bool = false,
105
117
  LightScreen: bool = false,
@@ -109,50 +121,69 @@ const Side = extern struct {
109
121
  light_screen: u4 = 0,
110
122
  reflect: u4 = 0,
111
123
 
112
- comptime {
113
- assert(@sizeOf(Conditions) == 2);
114
- }
124
+ future_sight: FutureSight = .{},
125
+
126
+ const FutureSight = packed struct(u16) {
127
+ damage: u12 = 0,
128
+ count: u4 = 0,
129
+ };
115
130
  };
116
131
 
117
132
  comptime {
118
- assert(@sizeOf(Side) == 240);
133
+ assert(@sizeOf(Side) == 248);
119
134
  }
120
135
 
121
- pub inline fn get(self: *const Side, slot: u8) *Pokemon {
136
+ pub fn get(self: anytype, slot: u8) PointerType(@TypeOf(self), Pokemon) {
137
+ assert(@typeInfo(@TypeOf(self)).Pointer.child == Side);
122
138
  assert(slot > 0 and slot <= 6);
123
139
  const id = self.pokemon[slot - 1].position;
124
140
  assert(id > 0 and id <= 6);
125
141
  return &self.pokemon[id - 1];
126
142
  }
127
143
 
128
- pub inline fn stored(self: *Side) *Pokemon {
144
+ pub fn stored(self: anytype) PointerType(@TypeOf(self), Pokemon) {
145
+ assert(@typeInfo(@TypeOf(self)).Pointer.child == Side);
129
146
  return self.get(1);
130
147
  }
148
+
149
+ pub fn lastMove(self: anytype, encore: bool) Move {
150
+ assert(self.last_used_move != .None or !self.active.volatiles.dirty);
151
+ if (encore) return self.last_used_move;
152
+ return if (self.active.volatiles.dirty) .None else self.last_used_move;
153
+ }
154
+
155
+ // TODO
156
+ // pub fn useMove(self: anytype) void {
157
+ // assert(side.last_selected_move != .None);
158
+ // self.last_used_move = side.last_selected_move;
159
+ // self.active.volatiles.dirty = false;
160
+ // }
131
161
  };
132
162
 
133
163
  // NOTE: DVs (Gender & Hidden Power) and Happiness are stored only in Pokemon
134
- const ActivePokemon = extern struct {
135
- volatiles: Volatile align(4) = .{},
164
+ pub const ActivePokemon = extern struct {
165
+ volatiles: Volatiles align(4) = .{},
136
166
  stats: Stats(u16) = .{},
137
167
  moves: [4]MoveSlot = [_]MoveSlot{.{}} ** 4,
138
168
  boosts: Boosts = .{},
169
+ types: Types = .{},
139
170
  species: Species = .None,
140
- item: Item = .None,
141
- last_move: Move = .None,
171
+
142
172
  _: u8 = 0,
143
173
 
144
174
  comptime {
145
175
  assert(@sizeOf(ActivePokemon) == 44);
146
176
  }
147
177
 
148
- pub inline fn move(self: *ActivePokemon, mslot: u8) *MoveSlot {
178
+ pub fn move(self: anytype, mslot: u8) PointerType(@TypeOf(self), MoveSlot) {
179
+ assert(@typeInfo(@TypeOf(self)).Pointer.child == Pokemon);
149
180
  assert(mslot > 0 and mslot <= 4);
150
181
  assert(self.moves[mslot - 1].id != .None);
151
182
  return &self.moves[mslot - 1];
152
183
  }
153
184
  };
154
185
 
155
- const Pokemon = extern struct {
186
+ pub const Pokemon = extern struct {
156
187
  stats: Stats(u16) = .{},
157
188
  moves: [4]MoveSlot = [_]MoveSlot{.{}} ** 4,
158
189
  types: Types = .{},
@@ -169,7 +200,8 @@ const Pokemon = extern struct {
169
200
  assert(@sizeOf(Pokemon) == 32);
170
201
  }
171
202
 
172
- pub inline fn move(self: *Pokemon, mslot: u8) *MoveSlot {
203
+ pub fn move(self: anytype, mslot: u8) PointerType(@TypeOf(self), MoveSlot) {
204
+ assert(@typeInfo(@TypeOf(self)).Pointer.child == Pokemon);
173
205
  assert(mslot > 0 and mslot <= 4);
174
206
  assert(self.moves[mslot - 1].id != .None);
175
207
  return &self.moves[mslot - 1];
@@ -182,7 +214,7 @@ pub const Gender = enum(u2) {
182
214
  Unknown,
183
215
  };
184
216
 
185
- const DVs = packed struct {
217
+ pub const DVs = packed struct(u16) {
186
218
  gender: Gender = .Male,
187
219
  pow: u6 = 40,
188
220
  type: Type = .Dark,
@@ -190,10 +222,10 @@ const DVs = packed struct {
190
222
  pub fn init(g: Gender, p: u6, t: Type) DVs {
191
223
  assert(p >= 1 and p <= 40);
192
224
  assert(t != .Normal and t != .@"???");
193
- return DVs{ .gender = g, .pow = p, .type = t };
225
+ return .{ .gender = g, .pow = p, .type = t };
194
226
  }
195
227
 
196
- pub inline fn power(dvs: DVs) u8 {
228
+ pub fn power(dvs: DVs) u8 {
197
229
  return @as(u8, dvs.pow) + 30;
198
230
  }
199
231
 
@@ -207,13 +239,8 @@ const DVs = packed struct {
207
239
  };
208
240
  const p: u6 = @intCast(1 + ((5 * @as(u8, (dvs.atk & 0b1000) | ((dvs.def & 0b1000)) >> 1 |
209
241
  ((dvs.spe & 0b1000)) >> 2 | ((dvs.spc & 0b1000)) >> 3) + (dvs.spc & 0b0011)) >> 1));
210
- var t = @as(u8, dvs.def & 0b0011) + (@as(u8, dvs.atk & 0b0011) << 2) + 1;
211
- if (t >= @intFromEnum(Type.@"???")) t = t + 1;
212
- return DVs{ .gender = g, .pow = p, .type = @enumFromInt(t) };
213
- }
214
-
215
- comptime {
216
- assert(@sizeOf(DVs) == 2);
242
+ var t = @as(u8, dvs.def & 0b0011) + (@as(u8, dvs.atk & 0b0011) << 2);
243
+ return .{ .gender = g, .pow = p, .type = Type.hiddenPower(t) };
217
244
  }
218
245
  };
219
246
 
@@ -238,10 +265,12 @@ test DVs {
238
265
  try expectEqual(Gender.Female, dvs.gender);
239
266
  }
240
267
 
241
- const MoveSlot = packed struct {
268
+ /// Representation of a Generation II Pokémon's move slot in a battle.
269
+ pub const MoveSlot = extern struct {
270
+ /// The identifier for the move.
242
271
  id: Move = .None,
243
- pp_ups: u2 = 3,
244
- pp: u6 = 0,
272
+ /// The remaining PP of the move.
273
+ pp: u8 = 0,
245
274
 
246
275
  comptime {
247
276
  assert(@sizeOf(MoveSlot) == 2);
@@ -250,14 +279,19 @@ const MoveSlot = packed struct {
250
279
 
251
280
  pub const Status = gen1.Status;
252
281
 
253
- const Volatile = packed struct {
282
+ /// TODO
283
+ pub const TriAttack = enum(u2) { PAR, FRZ, BRN };
284
+
285
+ pub const Volatiles = packed struct(u128) {
254
286
  Bide: bool = false,
255
287
  Thrashing: bool = false,
288
+ BeatUp: bool = false,
256
289
  Flinch: bool = false,
257
290
  Charging: bool = false,
258
291
  Underground: bool = false,
259
292
  Flying: bool = false,
260
293
  Confusion: bool = false,
294
+
261
295
  Mist: bool = false,
262
296
  FocusEnergy: bool = false,
263
297
  Substitute: bool = false,
@@ -268,52 +302,55 @@ const Volatile = packed struct {
268
302
  Transform: bool = false,
269
303
 
270
304
  Nightmare: bool = false,
271
- Cure: bool = false,
305
+ Curse: bool = false,
272
306
  Protect: bool = false,
273
307
  Foresight: bool = false,
274
308
  PerishSong: bool = false,
275
309
  Endure: bool = false,
276
310
  Rollout: bool = false,
277
311
  Attract: bool = false,
312
+
278
313
  DefenseCurl: bool = false,
279
314
  Encore: bool = false,
280
315
  LockOn: bool = false,
281
316
  DestinyBond: bool = false,
282
- BeatUp: bool = false,
317
+ Trapped: bool = false,
283
318
 
284
- trapped: bool = false,
285
319
  switched: bool = false,
320
+ // TODO: replace with simply looking at Choice on Battle
286
321
  switching: bool = false,
322
+ // TODO: simply store both last moves on side
323
+ dirty: bool = false,
287
324
 
288
- _: u5 = 0,
325
+ frozen: bool = false,
326
+ minimized: bool = false,
327
+ _: u10 = 0,
328
+ // TODO: is this needed for unmodified stats?
329
+ transform: u4 = 0,
289
330
 
290
- bind: u4 = 0,
291
- future_sight: FutureSight = .{},
292
331
  bide: u16 = 0,
293
- disabled: Disabled = .{},
332
+ bind: Bind = .{},
333
+ disable: Disable = .{},
294
334
  rage: u8 = 0,
295
335
  substitute: u8 = 0,
296
336
  toxic: u8 = 0,
297
337
  confusion: u4 = 0,
298
338
  encore: u4 = 0,
339
+ // TODO merge with attacks (ie rollout/thrashing)? need to be zeroed at same time
299
340
  fury_cutter: u4 = 0,
300
341
  perish_song: u4 = 0,
301
342
  protect: u4 = 0,
302
- rollout: u4 = 0,
343
+ attacks: u4 = 0, // bide/thrashing/rollout turns
303
344
 
304
- const Disabled = packed struct {
305
- move: u4 = 0,
345
+ const Bind = packed struct(u8) {
306
346
  duration: u4 = 0,
347
+ reason: u4 = 0,
307
348
  };
308
349
 
309
- const FutureSight = packed struct {
310
- damage: u12 = 0,
311
- count: u4 = 0,
350
+ const Disable = packed struct(u8) {
351
+ move: u4 = 0,
352
+ duration: u4 = 0,
312
353
  };
313
-
314
- comptime {
315
- assert(@sizeOf(Volatile) == 16);
316
- }
317
354
  };
318
355
 
319
356
  pub fn Stats(comptime T: type) type {
@@ -328,7 +365,7 @@ pub fn Stats(comptime T: type) type {
328
365
  pub fn calc(comptime stat: []const u8, base: T, dv: u4, exp: u16, level: u8) T {
329
366
  assert(level > 0 and level <= 100);
330
367
  const float: f32 = @floatFromInt(exp);
331
- const evs = @min(255, @as(u16, @intFromFloat(@ceil(@sqrt(float)))));
368
+ const evs: u16 = @min(255, @as(u16, @intFromFloat(@ceil(@sqrt(float)))));
332
369
  const core: u32 = (2 *% (@as(u32, base) +% dv)) +% (evs / 4);
333
370
  const factor: u32 = if (std.mem.eql(u8, stat, "hp")) level + 10 else 5;
334
371
  return @intCast(core *% @as(u32, level) / 100 +% factor);
@@ -348,7 +385,7 @@ test Stats {
348
385
  try expectEqual(@as(u16, 279), Stats(u16).calc("spe", base.spe, 15, 63001, 100));
349
386
  }
350
387
 
351
- pub const Boosts = packed struct {
388
+ pub const Boosts = packed struct(u32) {
352
389
  atk: i4 = 0,
353
390
  def: i4 = 0,
354
391
  spe: i4 = 0,
@@ -364,7 +401,7 @@ pub const Boosts = packed struct {
364
401
  };
365
402
 
366
403
  test Boosts {
367
- const boosts = Boosts{ .spd = -6 };
404
+ const boosts: Boosts = .{ .spd = -6 };
368
405
  try expectEqual(0, boosts.atk);
369
406
  try expectEqual(-6, boosts.spd);
370
407
  }
@@ -377,15 +414,10 @@ test Item {
377
414
  try expectEqual(Type.Normal, Item.boost(.PolkadotBow).?);
378
415
  try expectEqual(Type.Dark, Item.boost(.BlackGlasses).?);
379
416
 
380
- try expect(!Item.mail(.UpGrade));
381
- try expect(Item.mail(.FlowerMail));
382
- try expect(Item.mail(.MirageMail));
383
- try expect(!Item.mail(.TownMap));
384
-
385
- try expect(!Item.berry(.LightBall));
386
- try expect(Item.berry(.PSNCureBerry));
387
- try expect(Item.berry(.GoldBerry));
388
- try expect(!Item.berry(.MasterBall));
417
+ try expect(!Item.isMail(.UpGrade));
418
+ try expect(Item.isMail(.FlowerMail));
419
+ try expect(Item.isMail(.MirageMail));
420
+ try expect(!Item.isMail(.TownMap));
389
421
  }
390
422
 
391
423
  pub const Move = moves.Move;
@@ -394,8 +426,13 @@ test Move {
394
426
  try expectEqual(251, @intFromEnum(Move.BeatUp));
395
427
  const move = Move.get(.DynamicPunch);
396
428
  try expectEqual(Move.Effect.ConfusionChance, move.effect);
397
- try expectEqual(@as(u8, 50), move.accuracy);
398
- try expectEqual(@as(u8, 5), move.pp);
429
+ try expectEqual(rng.Gen12.percent(50), move.accuracy);
430
+ try expectEqual(rng.Gen12.percent(100), move.chance);
431
+ try expectEqual(@as(u8, 5), Move.pp(.DynamicPunch));
432
+ try expectEqual(@as(i8, -1), Move.get(.Counter).priority);
433
+ try expect(!move.effect.isHighCritical());
434
+ try expect(Move.get(.KarateChop).effect.isHighCritical());
435
+ try expect(Move.get(.RazorWind).effect.isHighCritical());
399
436
  }
400
437
 
401
438
  pub const Species = species.Species;