@pkmn/engine 0.1.0-dev.d716e940 → 0.1.0-dev.d73cd76c
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 +1 -3
- package/README.md +114 -69
- package/build/pkg/addon/browser.d.ts +3 -0
- package/build/pkg/addon/browser.js +47 -0
- package/build/pkg/addon/browser.js.map +1 -0
- package/build/pkg/addon/node.d.ts +3 -0
- package/build/pkg/addon/node.js +110 -0
- package/build/pkg/addon/node.js.map +1 -0
- package/build/pkg/addon/wasm.d.ts +2 -0
- package/build/pkg/addon/wasm.js +53 -0
- package/build/pkg/addon/wasm.js.map +1 -0
- package/build/pkg/addon.d.ts +26 -4
- package/build/pkg/addon.js +39 -70
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/data/ids.json +12 -12
- package/build/pkg/data/layout.json +39 -4
- package/build/pkg/data/protocol.json +84 -13
- package/build/pkg/data.d.ts +27 -5
- package/build/pkg/data.js.map +1 -1
- package/build/pkg/gen1.d.ts +15 -18
- package/build/pkg/gen1.js +43 -37
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/index.d.ts +42 -91
- package/build/pkg/index.js +23 -70
- package/build/pkg/index.js.map +1 -1
- package/build/pkg/protocol.d.ts +29 -1
- package/build/pkg/protocol.js +123 -102
- package/build/pkg/protocol.js.map +1 -1
- package/build/tools/debug.js +84 -73
- package/build/tools/debug.js.map +1 -1
- package/build/tools/display/debug.jsx +66 -0
- package/build/tools/display/debug.jsx.map +1 -0
- package/build/tools/display/index.js +152 -0
- package/build/tools/display/index.js.map +1 -0
- package/build/tools/display/ui.jsx +360 -0
- package/build/tools/display/ui.jsx.map +1 -0
- package/build/tools/display/util.js +157 -0
- package/build/tools/display/util.js.map +1 -0
- package/build.zig +428 -176
- package/package.json +72 -122
- package/src/bin/install-pkmn-engine +56 -25
- package/src/bin/pkmn-debug +1 -1
- package/src/lib/bindings/c.zig +201 -0
- package/src/lib/bindings/node.zig +117 -0
- package/src/lib/bindings/wasm.zig +51 -0
- package/src/lib/common/array.zig +70 -0
- package/src/lib/common/battle.zig +17 -0
- package/src/lib/common/data.zig +19 -31
- package/src/lib/common/debug.zig +58 -33
- package/src/lib/common/js.zig +114 -0
- package/src/lib/common/optional.zig +71 -0
- package/src/lib/common/options.zig +15 -0
- package/src/lib/common/protocol.zig +1037 -462
- package/src/lib/common/rational.zig +440 -0
- package/src/lib/common/rng.zig +31 -24
- package/src/lib/common/util.zig +64 -0
- package/src/lib/gen1/calc.zig +975 -0
- package/src/lib/gen1/chance.zig +1246 -0
- package/src/lib/gen1/data/moves.zig +203 -193
- package/src/lib/gen1/data/species.zig +6 -8
- package/src/lib/gen1/data/types.zig +12 -13
- package/src/lib/gen1/data.zig +98 -89
- package/src/lib/gen1/helpers.zig +159 -51
- package/src/lib/gen1/mechanics.zig +1211 -846
- package/src/lib/gen2/calc.zig +150 -0
- package/src/lib/gen2/chance.zig +914 -0
- package/src/lib/gen2/data/items.zig +15 -23
- package/src/lib/gen2/data/moves.zig +665 -573
- package/src/lib/gen2/data/species.zig +3 -5
- package/src/lib/gen2/data/types.zig +141 -36
- package/src/lib/gen2/data.zig +136 -100
- package/src/lib/gen2/generated.zig +801 -0
- package/src/lib/gen2/helpers.zig +81 -58
- package/src/lib/gen2/mechanics.zig +3582 -30
- package/src/lib/napi.h +2 -0
- package/src/lib/node.zig +14 -0
- package/src/lib/pkmn.zig +172 -17
- package/src/lib/wasm.zig +13 -0
- package/src/tools/display/dom.ts +108 -0
- package/src/tools/display/ui.css +354 -0
- package/build/test/display.js +0 -400
- package/build/test/display.js.map +0 -1
- package/src/lib/binding/node.zig +0 -229
- package/src/lib/binding/wasm.zig +0 -10
- package/src/lib/gen1/README.md +0 -812
- package/src/lib/gen2/README.md +0 -309
- package/src/test/display/pkmn.html.tmpl +0 -368
package/src/lib/gen1/helpers.zig
CHANGED
|
@@ -1,36 +1,40 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
1
|
+
const builtin = @import("builtin");
|
|
3
2
|
const common = @import("../common/data.zig");
|
|
3
|
+
const data = @import("data.zig");
|
|
4
4
|
const options = @import("../common/options.zig");
|
|
5
5
|
const protocol = @import("../common/protocol.zig");
|
|
6
6
|
const rng = @import("../common/rng.zig");
|
|
7
|
-
|
|
8
|
-
const
|
|
7
|
+
const std = @import("std");
|
|
8
|
+
const util = @import("../common/util.zig");
|
|
9
9
|
|
|
10
10
|
const assert = std.debug.assert;
|
|
11
|
-
|
|
12
|
-
const expectEqual = std.testing.expectEqual;
|
|
13
|
-
|
|
14
11
|
const Choice = common.Choice;
|
|
15
|
-
|
|
16
|
-
const showdown = options.showdown;
|
|
17
|
-
|
|
18
|
-
const PSRNG = rng.PSRNG;
|
|
19
|
-
|
|
20
12
|
const DVs = data.DVs;
|
|
21
13
|
const Move = data.Move;
|
|
22
14
|
const MoveSlot = data.MoveSlot;
|
|
15
|
+
const PSRNG = rng.PSRNG;
|
|
16
|
+
const showdown = options.showdown;
|
|
23
17
|
const Species = data.Species;
|
|
24
18
|
const Stats = data.Stats;
|
|
25
|
-
const Status = data.Status;
|
|
26
19
|
|
|
27
|
-
|
|
20
|
+
const endian = builtin.cpu.arch.endian();
|
|
21
|
+
|
|
22
|
+
/// Configuration options controlling the domain of valid Pokémon that can be generated by the
|
|
23
|
+
/// random generator helpers.
|
|
28
24
|
pub const Options = struct {
|
|
25
|
+
/// Whether to generate Pokémon adhering to "Cleric Clause".
|
|
29
26
|
cleric: bool = showdown,
|
|
27
|
+
/// Whether to generate Pokémon with moves that contain bugs on Pokémon Showdown that are
|
|
28
|
+
/// unimplementable in the engine.
|
|
30
29
|
block: bool = showdown,
|
|
30
|
+
// FIXME: remove once durations work with -Dchance
|
|
31
|
+
durations: bool = false,
|
|
31
32
|
};
|
|
32
33
|
|
|
34
|
+
/// Helpers to simplify initialization of a Generation I battle.
|
|
33
35
|
pub const Battle = struct {
|
|
36
|
+
/// Initializes a Generation I battle with the teams specified by `p1` and `p2`
|
|
37
|
+
/// and an RNG whose seed is derived from `seed`.
|
|
34
38
|
pub fn init(
|
|
35
39
|
seed: u64,
|
|
36
40
|
p1: []const Pokemon,
|
|
@@ -43,6 +47,8 @@ pub const Battle = struct {
|
|
|
43
47
|
};
|
|
44
48
|
}
|
|
45
49
|
|
|
50
|
+
/// Initializes a Generation I battle with the teams specified by `p1` and `p2`
|
|
51
|
+
/// and a FixedRNG that returns the provided `rolls`.
|
|
46
52
|
pub fn fixed(
|
|
47
53
|
comptime rolls: anytype,
|
|
48
54
|
p1: []const Pokemon,
|
|
@@ -54,84 +60,121 @@ pub const Battle = struct {
|
|
|
54
60
|
};
|
|
55
61
|
}
|
|
56
62
|
|
|
57
|
-
|
|
63
|
+
/// Returns a Generation I battle that is randomly generated based on the `rand` and `opts`.
|
|
64
|
+
pub fn random(rand: *PSRNG, opts: Options) data.Battle(data.PRNG) {
|
|
58
65
|
return .{
|
|
59
66
|
.rng = prng(rand),
|
|
60
67
|
.turn = 0,
|
|
61
68
|
.last_damage = 0,
|
|
62
|
-
.sides = .{ Side.random(rand,
|
|
69
|
+
.sides = .{ Side.random(rand, opts), Side.random(rand, opts) },
|
|
63
70
|
};
|
|
64
71
|
}
|
|
65
72
|
};
|
|
66
73
|
|
|
67
|
-
|
|
68
|
-
// GLITCH: initial bytes in seed can only range from 0-252, not 0-255
|
|
69
|
-
const max: u8 = 253;
|
|
74
|
+
fn prng(rand: *PSRNG) data.PRNG {
|
|
70
75
|
return .{
|
|
71
76
|
.src = .{
|
|
72
77
|
.seed = if (showdown)
|
|
73
78
|
rand.newSeed()
|
|
74
|
-
else
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
else seed: {
|
|
80
|
+
// GLITCH: The initial RNG seed in Gen I link battles is heavily constrained
|
|
81
|
+
// https://www.smogon.com/forums/threads/rng-in-rby-link-battles.3746779
|
|
82
|
+
var seed = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
83
|
+
|
|
84
|
+
// Bytes in seed can only range from 0-252, not 0-255
|
|
85
|
+
const max: u8 = 253;
|
|
86
|
+
// Instructions take multiples of 4 cycles to execute
|
|
87
|
+
var timer: u16 = 4 * rand.range(u16, 0, 16384);
|
|
88
|
+
var add: u8 = rand.range(u8, 0, 256);
|
|
89
|
+
var carry: u8 = 0;
|
|
90
|
+
|
|
91
|
+
var i: u8 = 0;
|
|
92
|
+
while (i < seed.len) {
|
|
93
|
+
const div: u8 = @intCast(timer >> 8);
|
|
94
|
+
add +%= div +% carry;
|
|
95
|
+
if (add < max) {
|
|
96
|
+
seed[i] = add;
|
|
97
|
+
i += 1;
|
|
98
|
+
timer +%= 472;
|
|
99
|
+
carry = 1;
|
|
100
|
+
} else {
|
|
101
|
+
timer +%= 452;
|
|
102
|
+
carry = 0;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
break :seed seed;
|
|
106
|
+
},
|
|
82
107
|
},
|
|
83
108
|
};
|
|
84
109
|
}
|
|
85
110
|
|
|
111
|
+
/// Helpers to simplify initialization of a Generation I side.
|
|
86
112
|
pub const Side = struct {
|
|
113
|
+
/// Initializes a Generation I side with the team specified by `ps`.
|
|
87
114
|
pub fn init(ps: []const Pokemon) data.Side {
|
|
88
115
|
assert(ps.len > 0 and ps.len <= 6);
|
|
89
116
|
var side = data.Side{};
|
|
90
117
|
|
|
91
118
|
for (0..ps.len) |i| {
|
|
92
119
|
side.pokemon[i] = Pokemon.init(ps[i]);
|
|
93
|
-
side.order[i] = @
|
|
120
|
+
side.order[i] = @as(u4, @intCast(i)) + 1;
|
|
94
121
|
}
|
|
95
122
|
return side;
|
|
96
123
|
}
|
|
97
124
|
|
|
98
|
-
|
|
125
|
+
/// Returns a Generation I side that is randomly generated based on the `rand` and `opts`.
|
|
126
|
+
pub fn random(rand: *PSRNG, opts: Options) data.Side {
|
|
99
127
|
const n = if (rand.chance(u8, 1, 100)) rand.range(u4, 1, 5 + 1) else 6;
|
|
100
128
|
var side = data.Side{};
|
|
101
129
|
|
|
102
130
|
for (0..n) |i| {
|
|
103
|
-
side.pokemon[i] = Pokemon.random(rand,
|
|
104
|
-
side.order[i] = @
|
|
131
|
+
side.pokemon[i] = Pokemon.random(rand, opts);
|
|
132
|
+
side.order[i] = @as(u4, @intCast(i)) + 1;
|
|
105
133
|
}
|
|
106
134
|
|
|
107
135
|
return side;
|
|
108
136
|
}
|
|
109
137
|
};
|
|
110
138
|
|
|
139
|
+
/// The maximum stat experience possible in Generation I.
|
|
111
140
|
pub const EXP = 0xFFFF;
|
|
112
141
|
|
|
142
|
+
/// Helpers to simplify initialization of a Generation I Pokémon.
|
|
113
143
|
pub const Pokemon = struct {
|
|
144
|
+
/// The Pokémon's species.
|
|
114
145
|
species: Species,
|
|
146
|
+
/// The Pokémon's moves (assumed to all have the max possible PP).
|
|
115
147
|
moves: []const Move,
|
|
148
|
+
/// The Pokémon's current HP (defaults to 100% if not specified).
|
|
116
149
|
hp: ?u16 = null,
|
|
150
|
+
/// The Pokémon's current status.
|
|
117
151
|
status: u8 = 0,
|
|
152
|
+
/// The Pokémon's level.
|
|
118
153
|
level: u8 = 100,
|
|
154
|
+
/// The Pokémon's DVs.
|
|
119
155
|
dvs: DVs = .{},
|
|
156
|
+
/// The Pokémon's stat experience.
|
|
120
157
|
stats: Stats(u16) = .{ .hp = EXP, .atk = EXP, .def = EXP, .spe = EXP, .spc = EXP },
|
|
121
158
|
|
|
159
|
+
/// Initializes a Generation I Pokémon based on the information in `p`.
|
|
122
160
|
pub fn init(p: Pokemon) data.Pokemon {
|
|
123
161
|
var pokemon = data.Pokemon{};
|
|
124
162
|
pokemon.species = p.species;
|
|
125
163
|
const species = Species.get(p.species);
|
|
126
|
-
inline for (@typeInfo(@TypeOf(pokemon.stats))
|
|
127
|
-
|
|
164
|
+
inline for (if (@hasField(@TypeOf(@typeInfo(@TypeOf(pokemon.stats)).@"struct"), "fields"))
|
|
165
|
+
@typeInfo(@TypeOf(pokemon.stats)).@"struct".fields
|
|
166
|
+
else
|
|
167
|
+
@typeInfo(@TypeOf(pokemon.stats)).@"struct".field_names) |field|
|
|
168
|
+
{
|
|
169
|
+
const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
|
|
170
|
+
const hp = comptime std.mem.eql(u8, field_name, "hp");
|
|
128
171
|
const spc =
|
|
129
|
-
comptime std.mem.eql(u8,
|
|
130
|
-
@field(pokemon.stats,
|
|
131
|
-
|
|
132
|
-
@field(species.stats,
|
|
133
|
-
if (hp) p.dvs.hp() else if (spc) p.dvs.spc else @field(p.dvs,
|
|
134
|
-
@field(p.stats,
|
|
172
|
+
comptime std.mem.eql(u8, field_name, "spa") or std.mem.eql(u8, field_name, "spd");
|
|
173
|
+
@field(pokemon.stats, field_name) = Stats(u16).calc(
|
|
174
|
+
field_name,
|
|
175
|
+
@field(species.stats, field_name),
|
|
176
|
+
if (hp) p.dvs.hp() else if (spc) p.dvs.spc else @field(p.dvs, field_name),
|
|
177
|
+
@field(p.stats, field_name),
|
|
135
178
|
p.level,
|
|
136
179
|
);
|
|
137
180
|
}
|
|
@@ -139,7 +182,7 @@ pub const Pokemon = struct {
|
|
|
139
182
|
for (p.moves, 0..) |m, j| {
|
|
140
183
|
pokemon.moves[j].id = m;
|
|
141
184
|
// NB: PP can be at most 61 legally (though can overflow to 63)
|
|
142
|
-
pokemon.moves[j].pp = @intCast(
|
|
185
|
+
pokemon.moves[j].pp = @intCast(@min(Move.pp(m) / 5 * 8, 61));
|
|
143
186
|
}
|
|
144
187
|
if (p.hp) |hp| {
|
|
145
188
|
pokemon.hp = hp;
|
|
@@ -152,39 +195,46 @@ pub const Pokemon = struct {
|
|
|
152
195
|
return pokemon;
|
|
153
196
|
}
|
|
154
197
|
|
|
198
|
+
/// Returns a Generation I Pokémon that is randomly generated based on the `rand` and `opts`.
|
|
155
199
|
pub fn random(rand: *PSRNG, opt: Options) data.Pokemon {
|
|
156
|
-
const s = @
|
|
200
|
+
const s: Species = @enumFromInt(rand.range(u8, 1, Species.size + 1));
|
|
157
201
|
const species = Species.get(s);
|
|
158
202
|
const lvl = if (rand.chance(u8, 1, 20)) rand.range(u8, 1, 99 + 1) else 100;
|
|
159
203
|
var stats: Stats(u16) = .{};
|
|
160
204
|
const dvs = DVs.random(rand);
|
|
161
|
-
inline for (@typeInfo(@TypeOf(stats))
|
|
162
|
-
@
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
205
|
+
inline for (if (@hasField(@TypeOf(@typeInfo(@TypeOf(stats)).@"struct"), "fields"))
|
|
206
|
+
@typeInfo(@TypeOf(stats)).@"struct".fields
|
|
207
|
+
else
|
|
208
|
+
@typeInfo(@TypeOf(stats)).@"struct".field_names) |field|
|
|
209
|
+
{
|
|
210
|
+
const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
|
|
211
|
+
@field(stats, field_name) = Stats(u16).calc(
|
|
212
|
+
field_name,
|
|
213
|
+
@field(species.stats, field_name),
|
|
214
|
+
if (comptime std.mem.eql(u8, field_name, "hp"))
|
|
166
215
|
dvs.hp()
|
|
167
216
|
else
|
|
168
|
-
@field(dvs,
|
|
217
|
+
@field(dvs, field_name),
|
|
169
218
|
if (rand.chance(u8, 1, 20)) rand.range(u16, 0, EXP + 1) else EXP,
|
|
170
219
|
lvl,
|
|
171
220
|
);
|
|
172
221
|
}
|
|
173
222
|
|
|
174
|
-
var ms
|
|
223
|
+
var ms: [4]MoveSlot = @splat(.{});
|
|
175
224
|
const n = if (rand.chance(u8, 1, 100)) rand.range(u4, 1, 3 + 1) else 4;
|
|
176
225
|
for (0..n) |i| {
|
|
177
226
|
var m: Move = .None;
|
|
178
227
|
sample: while (true) {
|
|
179
|
-
m = @
|
|
228
|
+
m = @enumFromInt(rand.range(u8, 1, Move.size - 1 + 1));
|
|
229
|
+
if (opt.durations and durations(m)) continue :sample;
|
|
180
230
|
if (opt.block and blocked(m)) continue :sample;
|
|
181
231
|
for (0..i) |j| if (ms[j].id == m) continue :sample;
|
|
182
232
|
break;
|
|
183
233
|
}
|
|
184
|
-
const pp_ups =
|
|
234
|
+
const pp_ups: u8 =
|
|
185
235
|
if (!opt.cleric and rand.chance(u8, 1, 10)) rand.range(u2, 0, 2 + 1) else 3;
|
|
186
236
|
// NB: PP can be at most 61 legally (though can overflow to 63)
|
|
187
|
-
const max_pp = @intCast(
|
|
237
|
+
const max_pp: u8 = @intCast(Move.pp(m) + pp_ups * @min(Move.pp(m) / 5, 7));
|
|
188
238
|
ms[i] = .{
|
|
189
239
|
.id = m,
|
|
190
240
|
.pp = if (opt.cleric) max_pp else rand.range(u8, 0, max_pp + 1),
|
|
@@ -198,6 +248,7 @@ pub const Pokemon = struct {
|
|
|
198
248
|
.stats = stats,
|
|
199
249
|
.hp = if (opt.cleric) stats.hp else rand.range(u16, 0, stats.hp + 1),
|
|
200
250
|
.status = if (!opt.cleric and rand.chance(u8, 1, 6 + 1))
|
|
251
|
+
// NB: This will only assign sleep as 2 or 4 when rolled
|
|
201
252
|
0 | (@as(u8, 1) << rand.range(u3, 1, 6 + 1))
|
|
202
253
|
else
|
|
203
254
|
0,
|
|
@@ -214,10 +265,67 @@ fn blocked(m: Move) bool {
|
|
|
214
265
|
};
|
|
215
266
|
}
|
|
216
267
|
|
|
268
|
+
fn durations(m: Move) bool {
|
|
269
|
+
// Any moves that have effects that result in duration tracking with -Dchance
|
|
270
|
+
return switch (Move.get(m).effect) {
|
|
271
|
+
.Haze,
|
|
272
|
+
.Metronome,
|
|
273
|
+
=> true,
|
|
274
|
+
else => false,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/// Convenience helper to create a move-type choice for the provided move slot.
|
|
217
279
|
pub fn move(slot: u4) Choice {
|
|
218
280
|
return .{ .type = .Move, .data = slot };
|
|
219
281
|
}
|
|
220
282
|
|
|
283
|
+
/// Convenience helper to create a switch-type choice for the provided team slot.
|
|
221
284
|
pub fn swtch(slot: u4) Choice {
|
|
222
285
|
return .{ .type = .Switch, .data = slot };
|
|
223
286
|
}
|
|
287
|
+
|
|
288
|
+
/// Empirically determined maximum number of bytes possibly required to store the diff between two
|
|
289
|
+
/// Generation I battles.
|
|
290
|
+
pub const MAX_DIFFS: usize = 120;
|
|
291
|
+
|
|
292
|
+
/// The optimal size in bytes required to hold the diff between two Generation I battles.
|
|
293
|
+
/// At least as large as MAX_DIFFS.
|
|
294
|
+
pub const DIFFS_SIZE = if (builtin.mode == .ReleaseSmall)
|
|
295
|
+
MAX_DIFFS
|
|
296
|
+
else
|
|
297
|
+
std.math.ceilPowerOfTwo(usize, MAX_DIFFS) catch unreachable;
|
|
298
|
+
|
|
299
|
+
/// TODO
|
|
300
|
+
pub fn diff(
|
|
301
|
+
a: *const data.Battle(data.PRNG),
|
|
302
|
+
b: *const data.Battle(data.PRNG),
|
|
303
|
+
w: *protocol.Writer,
|
|
304
|
+
) !usize {
|
|
305
|
+
const pos = w.pos;
|
|
306
|
+
const size = @sizeOf(data.Battle(data.PRNG)) - @sizeOf(data.PRNG);
|
|
307
|
+
assert(size % 2 == 0);
|
|
308
|
+
assert(size / 2 < 255);
|
|
309
|
+
|
|
310
|
+
var i: usize = 0;
|
|
311
|
+
while (i < size) : (i += 2) {
|
|
312
|
+
const val = std.mem.bytesAsValue(u16, std.mem.asBytes(a)[i .. i + 2]).*;
|
|
313
|
+
if (val != std.mem.bytesAsValue(u16, std.mem.asBytes(b)[i .. i + 2]).*) {
|
|
314
|
+
try w.writeByte(@intCast(i / 2));
|
|
315
|
+
try w.writeInt(u16, val, endian);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return w.pos - pos;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/// TODO
|
|
323
|
+
pub fn patch(battle: *data.Battle(data.PRNG), buf: []u8) void {
|
|
324
|
+
var i: usize = 0;
|
|
325
|
+
while (i < buf.len) : (i += 3) {
|
|
326
|
+
const off = @as(u16, @intCast(buf[i])) * 2;
|
|
327
|
+
const val = std.mem.readInt(u16, @as(*const [2]u8, @ptrCast(buf[i + 1 .. i + 3])), endian);
|
|
328
|
+
const bytes: *[2]u8 = @ptrCast(std.mem.asBytes(battle)[off .. off + 2]);
|
|
329
|
+
std.mem.writeInt(u16, bytes, val, endian);
|
|
330
|
+
}
|
|
331
|
+
}
|