@pkmn/engine 0.1.0-dev.00879b8b

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