@pkmn/engine 0.1.0-dev.028ab165
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.
- package/LICENSE +20 -0
- package/README.md +343 -0
- package/build/pkg/addon.d.ts +7 -0
- package/build/pkg/addon.js +112 -0
- package/build/pkg/addon.js.map +1 -0
- package/build/pkg/data/ids.json +240 -0
- package/build/pkg/data/layout.json +108 -0
- package/build/pkg/data/protocol.json +230 -0
- package/build/pkg/data.d.ts +91 -0
- package/build/pkg/data.js +150 -0
- package/build/pkg/data.js.map +1 -0
- package/build/pkg/gen1.d.ts +110 -0
- package/build/pkg/gen1.js +684 -0
- package/build/pkg/gen1.js.map +1 -0
- package/build/pkg/index.d.ts +386 -0
- package/build/pkg/index.js +122 -0
- package/build/pkg/index.js.map +1 -0
- package/build/pkg/protocol.d.ts +73 -0
- package/build/pkg/protocol.js +473 -0
- package/build/pkg/protocol.js.map +1 -0
- package/build/test/display.js +400 -0
- package/build/test/display.js.map +1 -0
- package/build/tools/debug.js +135 -0
- package/build/tools/debug.js.map +1 -0
- package/build.zig +371 -0
- package/package.json +224 -0
- package/src/bin/install-pkmn-engine +396 -0
- package/src/bin/pkmn-debug +62 -0
- package/src/lib/binding/node.zig +134 -0
- package/src/lib/binding/wasm.zig +12 -0
- package/src/lib/common/battle.zig +17 -0
- package/src/lib/common/data.zig +117 -0
- package/src/lib/common/debug.zig +74 -0
- package/src/lib/common/js.zig +118 -0
- package/src/lib/common/optional.zig +67 -0
- package/src/lib/common/options.zig +49 -0
- package/src/lib/common/protocol.zig +1841 -0
- package/src/lib/common/rational.zig +353 -0
- package/src/lib/common/rng.zig +243 -0
- package/src/lib/common/util.zig +12 -0
- package/src/lib/gen1/README.md +894 -0
- package/src/lib/gen1/calc.zig +426 -0
- package/src/lib/gen1/chance.zig +1060 -0
- package/src/lib/gen1/data/moves.zig +1866 -0
- package/src/lib/gen1/data/species.zig +1105 -0
- package/src/lib/gen1/data/types.zig +131 -0
- package/src/lib/gen1/data.zig +634 -0
- package/src/lib/gen1/helpers.zig +779 -0
- package/src/lib/gen1/mechanics.zig +3128 -0
- package/src/lib/gen2/README.md +329 -0
- package/src/lib/gen2/calc.zig +143 -0
- package/src/lib/gen2/chance.zig +1314 -0
- package/src/lib/gen2/data/items.zig +230 -0
- package/src/lib/gen2/data/moves.zig +2878 -0
- package/src/lib/gen2/data/species.zig +1798 -0
- package/src/lib/gen2/data/types.zig +226 -0
- package/src/lib/gen2/data.zig +463 -0
- package/src/lib/gen2/generated.zig +808 -0
- package/src/lib/gen2/helpers.zig +219 -0
- package/src/lib/gen2/mechanics.zig +3551 -0
- package/src/lib/pkmn.zig +113 -0
- 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
|
+
var 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(@as(u8, 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(@as(u8, 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(@as(u16, 127), Stats(u16).calc("hp", base.hp, 0, 5120, 38));
|
|
384
|
+
base = Species.get(.Pidgeot).stats;
|
|
385
|
+
try expectEqual(@as(u16, 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(@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());
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
pub const Species = species.Species;
|
|
439
|
+
|
|
440
|
+
test Species {
|
|
441
|
+
try expectEqual(152, @intFromEnum(Species.Chikorita));
|
|
442
|
+
try expectEqual(@as(u8, 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(@as(u8, 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
|
+
}
|