@pkmn/engine 0.1.0-dev.dc33f678 → 0.1.0-dev.deda8593
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 +44 -19
- package/build/pkg/addon.js +6 -7
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/data.d.ts +4 -4
- package/build/pkg/data.js.map +1 -1
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/index.d.ts +2 -1
- package/build/pkg/index.js.map +1 -1
- package/build/pkg/protocol.js +5 -5
- package/build/pkg/protocol.js.map +1 -1
- package/build/test/display.js +2 -2
- package/build/test/display.js.map +1 -1
- package/build/tools/debug.js +5 -4
- package/build/tools/debug.js.map +1 -1
- package/build.zig +100 -102
- package/package.json +33 -22
- package/src/bin/install-pkmn-engine +26 -18
- package/src/lib/common/data.zig +8 -8
- package/src/lib/common/debug.zig +2 -2
- package/src/lib/common/optional.zig +1 -1
- package/src/lib/common/options.zig +2 -0
- package/src/lib/common/protocol.zig +397 -384
- package/src/lib/common/rational.zig +11 -8
- package/src/lib/common/rng.zig +20 -11
- package/src/lib/gen1/calc.zig +217 -47
- package/src/lib/gen1/chance.zig +91 -92
- package/src/lib/gen1/data/moves.zig +10 -10
- package/src/lib/gen1/data/species.zig +3 -3
- package/src/lib/gen1/data/types.zig +5 -5
- package/src/lib/gen1/data.zig +35 -35
- package/src/lib/gen1/helpers.zig +136 -55
- package/src/lib/gen1/mechanics.zig +333 -312
- package/src/lib/gen2/calc.zig +143 -0
- package/src/lib/gen2/chance.zig +1315 -0
- package/src/lib/gen2/data/items.zig +3 -3
- package/src/lib/gen2/data/moves.zig +5 -5
- package/src/lib/gen2/data/species.zig +1 -1
- package/src/lib/gen2/data/types.zig +10 -10
- package/src/lib/gen2/data.zig +27 -37
- package/src/lib/gen2/generated.zig +806 -0
- package/src/lib/gen2/helpers.zig +37 -7
- package/src/lib/gen2/mechanics.zig +1621 -456
- package/src/lib/{binding/node.zig → node.zig} +9 -12
- package/src/lib/pkmn.zig +27 -12
- package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
- package/src/test/display/pkmn.html.tmpl +5 -5
- package/src/lib/gen1/README.md +0 -894
- package/src/lib/gen2/README.md +0 -330
package/src/lib/gen1/data.zig
CHANGED
|
@@ -22,11 +22,11 @@ const expect = std.testing.expect;
|
|
|
22
22
|
|
|
23
23
|
/// The minimum size in bytes required to hold all Generation I choice options.
|
|
24
24
|
pub const MAX_CHOICES: usize = 9; // move 1..4, switch 2..6
|
|
25
|
-
/// The
|
|
26
|
-
/// At least as large as MAX_CHOICES.
|
|
25
|
+
/// The maximum number of bytes possibly logged by a single Generation I update.
|
|
27
26
|
pub const MAX_LOGS: usize = 180;
|
|
28
27
|
|
|
29
|
-
/// The
|
|
28
|
+
/// The optimal size in bytes required to hold all Generation I choice options.
|
|
29
|
+
/// At least as large as MAX_CHOICES.
|
|
30
30
|
pub const CHOICES_SIZE = if (builtin.mode == .ReleaseSmall)
|
|
31
31
|
MAX_CHOICES
|
|
32
32
|
else
|
|
@@ -70,24 +70,24 @@ pub fn Battle(comptime RNG: anytype) type {
|
|
|
70
70
|
rng: RNG,
|
|
71
71
|
|
|
72
72
|
/// Returns the `Side` for the given `player`.
|
|
73
|
-
pub
|
|
73
|
+
pub fn side(self: anytype, player: Player) PointerType(@TypeOf(self), Side) {
|
|
74
74
|
assert(@typeInfo(@TypeOf(self)).Pointer.child == Self);
|
|
75
75
|
return &self.sides[@intFromEnum(player)];
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/// Returns the `Side` of the opponent for the given `player`
|
|
79
|
-
pub
|
|
79
|
+
pub fn foe(self: anytype, player: Player) PointerType(@TypeOf(self), Side) {
|
|
80
80
|
assert(@typeInfo(@TypeOf(self)).Pointer.child == Self);
|
|
81
81
|
return &self.sides[@intFromEnum(player.foe())];
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/// Returns an identifier for the active Pokémon of `player`.
|
|
85
|
-
pub
|
|
85
|
+
pub fn active(self: *const Self, player: Player) ID {
|
|
86
86
|
return player.ident(@intCast(self.side(player).order[0]));
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/// Returns the `MoveDetails` for the given `player`.
|
|
90
|
-
pub
|
|
90
|
+
pub fn lastMove(
|
|
91
91
|
self: anytype,
|
|
92
92
|
player: Player,
|
|
93
93
|
) PointerType(@TypeOf(self), MoveDetails) {
|
|
@@ -112,7 +112,7 @@ pub fn Battle(comptime RNG: anytype) type {
|
|
|
112
112
|
/// make (i.e. on the cartridge a soft-lock occurs).
|
|
113
113
|
///
|
|
114
114
|
/// This function will always return choices > 0 if `pkmn.options.showdown` is true.
|
|
115
|
-
pub fn choices(self: *Self, player: Player, request: Choice.Type, out: []Choice) u8 {
|
|
115
|
+
pub fn choices(self: *const Self, player: Player, request: Choice.Type, out: []Choice) u8 {
|
|
116
116
|
return mechanics.choices(self, player, request, out);
|
|
117
117
|
}
|
|
118
118
|
};
|
|
@@ -141,7 +141,7 @@ pub const Side = extern struct {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
/// Returns the stored `Pokemon` corresponding to the one-indexed party `slot`.
|
|
144
|
-
pub
|
|
144
|
+
pub fn get(self: anytype, slot: u8) PointerType(@TypeOf(self), Pokemon) {
|
|
145
145
|
assert(@typeInfo(@TypeOf(self)).Pointer.child == Side);
|
|
146
146
|
assert(slot > 0 and slot <= 6);
|
|
147
147
|
const id = self.order[slot - 1];
|
|
@@ -150,7 +150,7 @@ pub const Side = extern struct {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
/// Returns the stored `Pokemon` corresponding to the `active` Pokémon (slot 1).
|
|
153
|
-
pub
|
|
153
|
+
pub fn stored(self: anytype) PointerType(@TypeOf(self), Pokemon) {
|
|
154
154
|
assert(@typeInfo(@TypeOf(self)).Pointer.child == Side);
|
|
155
155
|
return self.get(1);
|
|
156
156
|
}
|
|
@@ -176,7 +176,7 @@ pub const ActivePokemon = extern struct {
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
/// Returns the active Pokémon's current move slot located at the one-indexed `mslot`.
|
|
179
|
-
pub
|
|
179
|
+
pub fn move(self: anytype, mslot: u8) PointerType(@TypeOf(self), MoveSlot) {
|
|
180
180
|
assert(@typeInfo(@TypeOf(self)).Pointer.child == ActivePokemon);
|
|
181
181
|
assert(mslot > 0 and mslot <= 4);
|
|
182
182
|
assert(self.moves[mslot - 1].id != .None);
|
|
@@ -206,7 +206,7 @@ pub const Pokemon = extern struct {
|
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
/// Returns the Pokémon's original move slot located at the one-indexed `mslot`.
|
|
209
|
-
pub
|
|
209
|
+
pub fn move(self: anytype, mslot: u8) PointerType(@TypeOf(self), MoveSlot) {
|
|
210
210
|
assert(@typeInfo(@TypeOf(self)).Pointer.child == Pokemon);
|
|
211
211
|
assert(mslot > 0 and mslot <= 4);
|
|
212
212
|
assert(self.moves[mslot - 1].id != .None);
|
|
@@ -258,36 +258,36 @@ pub const Status = enum(u8) {
|
|
|
258
258
|
const SLP = 0b111;
|
|
259
259
|
|
|
260
260
|
/// Whether or not the status `num` is the same as `status`.
|
|
261
|
-
pub
|
|
261
|
+
pub fn is(num: u8, status: Status) bool {
|
|
262
262
|
if (status == .SLP) return Status.duration(num) > 0;
|
|
263
263
|
return ((num >> @intCast(@intFromEnum(status))) & 1) != 0;
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
/// Initializes a non-sleep `status`. Use `slp` or `slf` to initialize a sleep status.
|
|
267
|
-
pub
|
|
267
|
+
pub fn init(status: Status) u8 {
|
|
268
268
|
assert(status != .SLP and status != .EXT);
|
|
269
269
|
return @as(u8, 1) << @intCast(@intFromEnum(status));
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
/// Initializes a non self-inflicted sleep status with duration `dur`.
|
|
273
|
-
pub
|
|
273
|
+
pub fn slp(dur: u3) u8 {
|
|
274
274
|
assert(dur > 0);
|
|
275
275
|
return @as(u8, dur);
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
/// Initializes a self-inflicted sleep status with duration `dur`.
|
|
279
|
-
pub
|
|
279
|
+
pub fn slf(dur: u3) u8 {
|
|
280
280
|
assert(dur > 0);
|
|
281
281
|
return 0x80 | slp(dur);
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
/// Returns the duration of a sleep status.
|
|
285
|
-
pub
|
|
285
|
+
pub fn duration(num: u8) u3 {
|
|
286
286
|
return @intCast(num & SLP);
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
/// Returns whether `num` reflects any status.
|
|
290
|
-
pub
|
|
290
|
+
pub fn any(num: u8) bool {
|
|
291
291
|
return num > 0;
|
|
292
292
|
}
|
|
293
293
|
|
|
@@ -316,12 +316,12 @@ test Status {
|
|
|
316
316
|
try expect(Status.is(Status.slp(5), .SLP));
|
|
317
317
|
try expect(!Status.is(Status.slp(5), .EXT));
|
|
318
318
|
try expect(!Status.is(Status.slp(7), .PSN));
|
|
319
|
-
try expectEqual(
|
|
319
|
+
try expectEqual(5, Status.duration(Status.slp(5)));
|
|
320
320
|
try expect(Status.is(Status.slf(2), .SLP));
|
|
321
321
|
try expect(Status.is(Status.slf(2), .EXT));
|
|
322
322
|
try expect(Status.is(Status.TOX, .PSN));
|
|
323
323
|
try expect(Status.is(Status.TOX, .EXT));
|
|
324
|
-
try expectEqual(
|
|
324
|
+
try expectEqual(1, Status.duration(Status.slp(1)));
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
/// Bitfield representation of volatile statuses and associated data in Generation I.
|
|
@@ -405,13 +405,13 @@ test Volatiles {
|
|
|
405
405
|
try expect(!volatiles.Transform);
|
|
406
406
|
try expect(!volatiles.MultiHit);
|
|
407
407
|
|
|
408
|
-
try expectEqual(
|
|
409
|
-
try expectEqual(
|
|
410
|
-
try expectEqual(
|
|
411
|
-
try expectEqual(
|
|
412
|
-
try expectEqual(
|
|
413
|
-
try expectEqual(
|
|
414
|
-
try expectEqual(
|
|
408
|
+
try expectEqual(235, volatiles.state);
|
|
409
|
+
try expectEqual(42, volatiles.substitute);
|
|
410
|
+
try expectEqual(2, volatiles.disable_move);
|
|
411
|
+
try expectEqual(4, volatiles.disable_duration);
|
|
412
|
+
try expectEqual(2, volatiles.confusion);
|
|
413
|
+
try expectEqual(4, volatiles.toxic);
|
|
414
|
+
try expectEqual(3, volatiles.attacks);
|
|
415
415
|
}
|
|
416
416
|
|
|
417
417
|
/// Representaiton of a Pokémon's stats in Generation I.
|
|
@@ -442,9 +442,9 @@ test Stats {
|
|
|
442
442
|
try expectEqual(0, stats.def);
|
|
443
443
|
|
|
444
444
|
var base = Species.get(.Gyarados).stats;
|
|
445
|
-
try expectEqual(
|
|
445
|
+
try expectEqual(127, Stats(u16).calc("hp", base.hp, 0, 5120, 38));
|
|
446
446
|
base = Species.get(.Pidgeot).stats;
|
|
447
|
-
try expectEqual(
|
|
447
|
+
try expectEqual(279, Stats(u16).calc("spe", base.spe, 15, 63001, 100));
|
|
448
448
|
}
|
|
449
449
|
|
|
450
450
|
/// Representation of a Pokémon's boosts in Generation I.
|
|
@@ -537,7 +537,7 @@ pub const Species = species.Species;
|
|
|
537
537
|
|
|
538
538
|
test Species {
|
|
539
539
|
try expectEqual(2, @intFromEnum(Species.Ivysaur));
|
|
540
|
-
try expectEqual(
|
|
540
|
+
try expectEqual(100, Species.get(.Mew).stats.def);
|
|
541
541
|
}
|
|
542
542
|
|
|
543
543
|
/// Representation of a Generation I type in Pokémon.
|
|
@@ -576,8 +576,8 @@ test Types {
|
|
|
576
576
|
try expectEqual(Effectiveness.Resisted, Type.effectiveness(.Fire, .Water));
|
|
577
577
|
try expectEqual(Effectiveness.Neutral, Type.effectiveness(.Normal, .Grass));
|
|
578
578
|
|
|
579
|
-
try expectEqual(
|
|
580
|
-
try expectEqual(
|
|
579
|
+
try expectEqual(3, Type.precedence(.Water, .Water));
|
|
580
|
+
try expectEqual(26, Type.precedence(.Bug, .Ghost));
|
|
581
581
|
try expect(Type.precedence(.Poison, .Bug) > Type.precedence(.Poison, .Poison));
|
|
582
582
|
|
|
583
583
|
const t: Types = .{ .type1 = .Rock, .type2 = .Ground };
|
|
@@ -617,18 +617,18 @@ pub const DVs = struct {
|
|
|
617
617
|
|
|
618
618
|
test DVs {
|
|
619
619
|
var dvs: DVs = .{ .spc = 15, .spe = 15 };
|
|
620
|
-
try expectEqual(
|
|
620
|
+
try expectEqual(15, dvs.hp());
|
|
621
621
|
dvs = .{
|
|
622
622
|
.atk = 5,
|
|
623
623
|
.def = 15,
|
|
624
624
|
.spe = 13,
|
|
625
625
|
.spc = 13,
|
|
626
626
|
};
|
|
627
|
-
try expectEqual(
|
|
627
|
+
try expectEqual(15, dvs.hp());
|
|
628
628
|
dvs = .{
|
|
629
629
|
.def = 3,
|
|
630
630
|
.spe = 10,
|
|
631
631
|
.spc = 11,
|
|
632
632
|
};
|
|
633
|
-
try expectEqual(
|
|
633
|
+
try expectEqual(13, dvs.hp());
|
|
634
634
|
}
|
package/src/lib/gen1/helpers.zig
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const std = @import("std");
|
|
2
|
+
const builtin = @import("builtin");
|
|
2
3
|
|
|
3
4
|
const common = @import("../common/data.zig");
|
|
4
5
|
const optional = @import("../common/optional.zig");
|
|
@@ -35,6 +36,8 @@ const Status = data.Status;
|
|
|
35
36
|
const Action = chance.Action;
|
|
36
37
|
const Actions = chance.Actions;
|
|
37
38
|
|
|
39
|
+
const endian = builtin.cpu.arch.endian();
|
|
40
|
+
|
|
38
41
|
/// Configuration options controlling the domain of valid Pokémon that can be generated by the
|
|
39
42
|
/// random generator helpers.
|
|
40
43
|
pub const Options = struct {
|
|
@@ -43,6 +46,8 @@ pub const Options = struct {
|
|
|
43
46
|
/// Whether to generate Pokémon with moves that contain bugs on Pokémon Showdown that are
|
|
44
47
|
/// unimplementable in the engine.
|
|
45
48
|
block: bool = showdown,
|
|
49
|
+
// FIXME: remove once durations work with -Dchance
|
|
50
|
+
durations: bool = false,
|
|
46
51
|
};
|
|
47
52
|
|
|
48
53
|
/// Helpers to simplify initialization of a Generation I battle.
|
|
@@ -86,18 +91,38 @@ pub const Battle = struct {
|
|
|
86
91
|
};
|
|
87
92
|
|
|
88
93
|
fn prng(rand: *PSRNG) data.PRNG {
|
|
89
|
-
// GLITCH: initial bytes in seed can only range from 0-252, not 0-255
|
|
90
|
-
const max: u8 = 253;
|
|
91
94
|
return .{
|
|
92
95
|
.src = .{
|
|
93
96
|
.seed = if (showdown)
|
|
94
97
|
rand.newSeed()
|
|
95
|
-
else
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
else seed: {
|
|
99
|
+
// GLITCH: The initial RNG seed in Gen I link battles is heavily constrained
|
|
100
|
+
// https://www.smogon.com/forums/threads/rng-in-rby-link-battles.3746779
|
|
101
|
+
var seed = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
102
|
+
|
|
103
|
+
// Bytes in seed can only range from 0-252, not 0-255
|
|
104
|
+
const max: u8 = 253;
|
|
105
|
+
// Instructions take multiples of 4 cycles to execute
|
|
106
|
+
var timer: u16 = 4 * rand.range(u16, 0, 16384);
|
|
107
|
+
var add: u8 = rand.range(u8, 0, 256);
|
|
108
|
+
var carry: u8 = 0;
|
|
109
|
+
|
|
110
|
+
var i: u8 = 0;
|
|
111
|
+
while (i < seed.len) {
|
|
112
|
+
const div: u8 = @intCast(timer >> 8);
|
|
113
|
+
add +%= div +% carry;
|
|
114
|
+
if (add < max) {
|
|
115
|
+
seed[i] = add;
|
|
116
|
+
i += 1;
|
|
117
|
+
timer +%= 472;
|
|
118
|
+
carry = 1;
|
|
119
|
+
} else {
|
|
120
|
+
timer +%= 452;
|
|
121
|
+
carry = 0;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
break :seed seed;
|
|
125
|
+
},
|
|
101
126
|
},
|
|
102
127
|
};
|
|
103
128
|
}
|
|
@@ -210,6 +235,7 @@ pub const Pokemon = struct {
|
|
|
210
235
|
var m: Move = .None;
|
|
211
236
|
sample: while (true) {
|
|
212
237
|
m = @enumFromInt(rand.range(u8, 1, Move.size - 1 + 1));
|
|
238
|
+
if (opt.durations and durations(m)) continue :sample;
|
|
213
239
|
if (opt.block and blocked(m)) continue :sample;
|
|
214
240
|
for (0..i) |j| if (ms[j].id == m) continue :sample;
|
|
215
241
|
break;
|
|
@@ -247,6 +273,23 @@ fn blocked(m: Move) bool {
|
|
|
247
273
|
};
|
|
248
274
|
}
|
|
249
275
|
|
|
276
|
+
fn durations(m: Move) bool {
|
|
277
|
+
// Any moves that have effects that result in duration tracking with -Dchance
|
|
278
|
+
return switch (Move.get(m).effect) {
|
|
279
|
+
.Confusion,
|
|
280
|
+
.Bide,
|
|
281
|
+
.Sleep,
|
|
282
|
+
.Rage,
|
|
283
|
+
.Binding,
|
|
284
|
+
.Thrashing,
|
|
285
|
+
.ConfusionChance,
|
|
286
|
+
.Disable,
|
|
287
|
+
.Metronome,
|
|
288
|
+
=> true,
|
|
289
|
+
else => false,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
250
293
|
/// Convenience helper to create a move-type choice for the provided move slot.
|
|
251
294
|
pub fn move(slot: u4) Choice {
|
|
252
295
|
return .{ .type = .Move, .data = slot };
|
|
@@ -266,8 +309,8 @@ pub const Rolls = struct {
|
|
|
266
309
|
const PLAYERS = [_]Optional(Player){ .P1, .P2 };
|
|
267
310
|
|
|
268
311
|
/// Returns a slice with the correct range of values for speed ties given the `action` state.
|
|
269
|
-
pub
|
|
270
|
-
return if (
|
|
312
|
+
pub fn speedTie(action: Action) []const Optional(Player) {
|
|
313
|
+
return if (action.speed_tie == .None) &PLAYER_NONE else &PLAYERS;
|
|
271
314
|
}
|
|
272
315
|
|
|
273
316
|
const BOOL_NONE = [_]Optional(bool){.None};
|
|
@@ -275,27 +318,21 @@ pub const Rolls = struct {
|
|
|
275
318
|
|
|
276
319
|
/// Returns a slice with the correct range of values for hits given the `action` state
|
|
277
320
|
/// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
|
|
278
|
-
pub
|
|
321
|
+
pub fn hit(action: Action, parent: Optional(bool)) []const Optional(bool) {
|
|
279
322
|
if (parent == .true) return &BOOL_NONE;
|
|
280
|
-
return if (
|
|
323
|
+
return if (action.hit == .None) &BOOL_NONE else &BOOLS;
|
|
281
324
|
}
|
|
282
325
|
|
|
283
326
|
/// Returns a slice with the correct range of values for critical hits given the `action` state
|
|
284
327
|
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
285
|
-
pub
|
|
286
|
-
action: Action,
|
|
287
|
-
parent: Optional(bool),
|
|
288
|
-
) []const Optional(bool) {
|
|
328
|
+
pub fn criticalHit(action: Action, parent: Optional(bool)) []const Optional(bool) {
|
|
289
329
|
if (parent == .false) return &BOOL_NONE;
|
|
290
|
-
return if (
|
|
330
|
+
return if (action.critical_hit == .None) &BOOL_NONE else &BOOLS;
|
|
291
331
|
}
|
|
292
332
|
|
|
293
333
|
/// Returns a slice with the correct range of values for secondary chances hits given the
|
|
294
334
|
/// `action` state and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
295
|
-
pub
|
|
296
|
-
action: Action,
|
|
297
|
-
parent: Optional(bool),
|
|
298
|
-
) []const Optional(bool) {
|
|
335
|
+
pub fn secondaryChance(action: Action, parent: Optional(bool)) []const Optional(bool) {
|
|
299
336
|
if (parent == .false) return &BOOL_NONE;
|
|
300
337
|
return if (@field(action, "secondary_chance") == .None) &BOOL_NONE else &BOOLS;
|
|
301
338
|
}
|
|
@@ -305,8 +342,8 @@ pub const Rolls = struct {
|
|
|
305
342
|
|
|
306
343
|
/// Returns the range bounding damage rolls given the `action` state and the state of
|
|
307
344
|
/// the `parent` (whether the player's Pokémon's move hit).
|
|
308
|
-
pub
|
|
309
|
-
return if (parent == .false or
|
|
345
|
+
pub fn damage(action: Action, parent: Optional(bool)) Range {
|
|
346
|
+
return if (parent == .false or action.damage == 0)
|
|
310
347
|
.{ .min = 0, .max = 1 }
|
|
311
348
|
else
|
|
312
349
|
.{ .min = 217, .max = 256 };
|
|
@@ -314,7 +351,7 @@ pub const Rolls = struct {
|
|
|
314
351
|
|
|
315
352
|
/// Returns the max damage roll which will produce the same damage as `roll`
|
|
316
353
|
/// given the base damage in `summaries`.
|
|
317
|
-
pub
|
|
354
|
+
pub fn coalesce(player: Player, roll: u8, summaries: *calc.Summaries, cap: bool) !u8 {
|
|
318
355
|
if (roll == 0) return roll;
|
|
319
356
|
|
|
320
357
|
const dmg = summaries.get(player.foe()).damage;
|
|
@@ -326,15 +363,15 @@ pub const Rolls = struct {
|
|
|
326
363
|
|
|
327
364
|
/// Returns a slice with the correct range of values for confused given the `action` state
|
|
328
365
|
/// and the state of the `parent` (the player's Pokémon's remaining confusion turns).
|
|
329
|
-
pub
|
|
330
|
-
return if (parent == 0 or
|
|
366
|
+
pub fn confused(action: Action, parent: u4) []const Optional(bool) {
|
|
367
|
+
return if (parent == 0 or action.confused == .None) &BOOL_NONE else &BOOLS;
|
|
331
368
|
}
|
|
332
369
|
|
|
333
370
|
/// Returns a slice with the correct range of values for paralysis given the `action` state
|
|
334
371
|
/// and the state of the `parent` (whether the player's Pokémon was confused).
|
|
335
|
-
pub
|
|
372
|
+
pub fn paralyzed(action: Action, parent: Optional(bool)) []const Optional(bool) {
|
|
336
373
|
if (parent == .true) return &BOOL_NONE;
|
|
337
|
-
return if (
|
|
374
|
+
return if (action.paralyzed == .None) &BOOL_NONE else &BOOLS;
|
|
338
375
|
}
|
|
339
376
|
|
|
340
377
|
const DURATION_NONE = [_]u1{0};
|
|
@@ -342,9 +379,9 @@ pub const Rolls = struct {
|
|
|
342
379
|
|
|
343
380
|
/// Returns a slice with a range of values for duration given the `action` state
|
|
344
381
|
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
345
|
-
pub
|
|
382
|
+
pub fn duration(action: Action, parent: Optional(bool)) []const u1 {
|
|
346
383
|
if (parent == .false) return &DURATION_NONE;
|
|
347
|
-
return if (
|
|
384
|
+
return if (action.duration == 0) &DURATION_NONE else &DURATION;
|
|
348
385
|
}
|
|
349
386
|
|
|
350
387
|
const U3_NONE = [_]u3{0};
|
|
@@ -352,8 +389,8 @@ pub const Rolls = struct {
|
|
|
352
389
|
|
|
353
390
|
/// Returns a slice with a range of values for sleep given the `action` and `origin` states.
|
|
354
391
|
pub inline fn sleep(action: Action, origin: Action) []const u3 {
|
|
355
|
-
const a =
|
|
356
|
-
const o =
|
|
392
|
+
const a = action.durations.sleep;
|
|
393
|
+
const o = origin.durations.sleep;
|
|
357
394
|
if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
|
|
358
395
|
assert(a == 0 or a == o + 1);
|
|
359
396
|
return if (o >= 7) &U3_NONE else &[_]u3{ 0, o + 1 };
|
|
@@ -365,8 +402,8 @@ pub const Rolls = struct {
|
|
|
365
402
|
/// and the state of the `parent` (the player's Pokémon's remaining sleep turns).
|
|
366
403
|
pub inline fn disable(action: Action, origin: Action, parent: u4) []const u4 {
|
|
367
404
|
if (parent > 0) return &DISABLE_NONE;
|
|
368
|
-
const a =
|
|
369
|
-
const o =
|
|
405
|
+
const a = action.durations.disable;
|
|
406
|
+
const o = origin.durations.disable;
|
|
370
407
|
if (o == a) return if (o == 0) &DISABLE_NONE else &[_]u4{o};
|
|
371
408
|
assert(a == 0 or a == o + 1);
|
|
372
409
|
return if (o >= 8) &DISABLE_NONE else &[_]u4{ 0, o + 1 };
|
|
@@ -376,8 +413,8 @@ pub const Rolls = struct {
|
|
|
376
413
|
/// and the state of the `parent` (the player's remaining sleep turns).
|
|
377
414
|
pub inline fn confusion(action: Action, origin: Action, parent: u4) []const u3 {
|
|
378
415
|
if (parent > 0) return &U3_NONE;
|
|
379
|
-
const a =
|
|
380
|
-
const o =
|
|
416
|
+
const a = action.durations.confusion;
|
|
417
|
+
const o = origin.durations.confusion;
|
|
381
418
|
if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
|
|
382
419
|
assert(a == 0 or a == o + 1);
|
|
383
420
|
return if (o >= 5) &U3_NONE else if (o < 2)
|
|
@@ -390,8 +427,8 @@ pub const Rolls = struct {
|
|
|
390
427
|
/// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
|
|
391
428
|
pub inline fn attacking(action: Action, origin: Action, parent: Optional(bool)) []const u3 {
|
|
392
429
|
if (parent == .true) return &U3_NONE;
|
|
393
|
-
const a =
|
|
394
|
-
const o =
|
|
430
|
+
const a = action.durations.attacking;
|
|
431
|
+
const o = origin.durations.attacking;
|
|
395
432
|
if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
|
|
396
433
|
assert(a == 0 or a == o + 1);
|
|
397
434
|
return if (o >= 3) &U3_NONE else if (o < 2)
|
|
@@ -404,8 +441,8 @@ pub const Rolls = struct {
|
|
|
404
441
|
/// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
|
|
405
442
|
pub inline fn binding(action: Action, origin: Action, parent: Optional(bool)) []const u3 {
|
|
406
443
|
if (parent == .true) return &U3_NONE;
|
|
407
|
-
const a =
|
|
408
|
-
const o =
|
|
444
|
+
const a = action.durations.binding;
|
|
445
|
+
const o = origin.durations.binding;
|
|
409
446
|
if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
|
|
410
447
|
assert(a == 0 or a == o + 1);
|
|
411
448
|
return if (o >= 4) &U3_NONE else &[_]u3{ 0, o + 1 };
|
|
@@ -418,9 +455,9 @@ pub const Rolls = struct {
|
|
|
418
455
|
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
419
456
|
///
|
|
420
457
|
/// These slots may or **may not be valid** as slots may be unset / have 0 PP.
|
|
421
|
-
pub
|
|
458
|
+
pub fn moveSlot(action: Action, parent: Optional(bool)) []const u4 {
|
|
422
459
|
if (parent == .false) return &SLOT_NONE;
|
|
423
|
-
return if (
|
|
460
|
+
return if (action.move_slot == 0) &SLOT_NONE else &SLOT;
|
|
424
461
|
}
|
|
425
462
|
|
|
426
463
|
const MULTI_NONE = [_]u4{0};
|
|
@@ -428,9 +465,9 @@ pub const Rolls = struct {
|
|
|
428
465
|
|
|
429
466
|
/// Returns a slice with the correct range of values for multi hit given the `action` state
|
|
430
467
|
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
431
|
-
pub
|
|
468
|
+
pub fn multiHit(action: Action, parent: Optional(bool)) []const u4 {
|
|
432
469
|
if (parent == .false) return &MULTI_NONE;
|
|
433
|
-
return if (
|
|
470
|
+
return if (action.multi_hit == 0) &MULTI_NONE else &MULTI;
|
|
434
471
|
}
|
|
435
472
|
|
|
436
473
|
const PSYWAVE_NONE = [_]u8{0};
|
|
@@ -442,13 +479,13 @@ pub const Rolls = struct {
|
|
|
442
479
|
|
|
443
480
|
/// Returns a slice with the correct range of values for psywave given the `action` state,
|
|
444
481
|
/// the `side`, and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
445
|
-
pub
|
|
482
|
+
pub fn psywave(
|
|
446
483
|
action: Action,
|
|
447
484
|
side: *data.Side,
|
|
448
485
|
parent: Optional(bool),
|
|
449
486
|
) []const u8 {
|
|
450
487
|
if (parent == .false) return &PSYWAVE_NONE;
|
|
451
|
-
return if (
|
|
488
|
+
return if (action.psywave == 0)
|
|
452
489
|
&PSYWAVE_NONE
|
|
453
490
|
else
|
|
454
491
|
PSYWAVE[0 .. @as(u16, side.stored().level) * 3 / 2];
|
|
@@ -457,8 +494,8 @@ pub const Rolls = struct {
|
|
|
457
494
|
const MOVE_NONE = [_]Move{.None};
|
|
458
495
|
|
|
459
496
|
/// Returns a slice with the correct range of values for metronome given the `action` state.
|
|
460
|
-
pub
|
|
461
|
-
return if (
|
|
497
|
+
pub fn metronome(action: Action) []const Move {
|
|
498
|
+
return if (action.metronome == .None) &MOVE_NONE else &Move.METRONOME;
|
|
462
499
|
}
|
|
463
500
|
};
|
|
464
501
|
|
|
@@ -478,12 +515,12 @@ test "Rolls.damage" {
|
|
|
478
515
|
test "Rolls.coalesce" {
|
|
479
516
|
var summaries =
|
|
480
517
|
calc.Summaries{ .p1 = .{ .damage = .{ .base = 74, .final = 69, .capped = true } } };
|
|
481
|
-
try expectEqual(
|
|
482
|
-
try expectEqual(
|
|
483
|
-
try expectEqual(
|
|
518
|
+
try expectEqual(0, try Rolls.coalesce(.P2, 0, &summaries, false));
|
|
519
|
+
try expectEqual(241, try Rolls.coalesce(.P2, 238, &summaries, false));
|
|
520
|
+
try expectEqual(255, try Rolls.coalesce(.P2, 238, &summaries, true));
|
|
484
521
|
summaries.p1.damage.final = 74;
|
|
485
|
-
try expectEqual(
|
|
486
|
-
try expectEqual(
|
|
522
|
+
try expectEqual(217, try Rolls.coalesce(.P2, 217, &summaries, false));
|
|
523
|
+
try expectEqual(255, try Rolls.coalesce(.P2, 217, &summaries, true));
|
|
487
524
|
}
|
|
488
525
|
|
|
489
526
|
test "Rolls.hit" {
|
|
@@ -771,9 +808,53 @@ test "Rolls.psywave" {
|
|
|
771
808
|
|
|
772
809
|
try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p1, &side, .None));
|
|
773
810
|
var rolls = Rolls.psywave(actions.p2, &side, .None);
|
|
774
|
-
try expectEqual(
|
|
811
|
+
try expectEqual(150, rolls[rolls.len - 1]);
|
|
775
812
|
side.stored().level = 81;
|
|
776
813
|
rolls = Rolls.psywave(actions.p2, &side, .None);
|
|
777
|
-
try expectEqual(
|
|
814
|
+
try expectEqual(121, rolls[rolls.len - 1]);
|
|
778
815
|
try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p2, &side, .false));
|
|
779
816
|
}
|
|
817
|
+
|
|
818
|
+
/// Empirically determined maximum number of bytes possibly required to store the diff between two
|
|
819
|
+
/// Generation I battles.
|
|
820
|
+
pub const MAX_DIFFS: usize = 117;
|
|
821
|
+
|
|
822
|
+
/// The optimal size in bytes required to hold the diff between two Generation I battles.
|
|
823
|
+
/// At least as large as MAX_DIFFS.
|
|
824
|
+
pub const DIFFS_SIZE = if (builtin.mode == .ReleaseSmall)
|
|
825
|
+
MAX_DIFFS
|
|
826
|
+
else
|
|
827
|
+
std.math.ceilPowerOfTwo(usize, MAX_DIFFS) catch unreachable;
|
|
828
|
+
|
|
829
|
+
/// TODO
|
|
830
|
+
pub fn diff(
|
|
831
|
+
a: *const data.Battle(data.PRNG),
|
|
832
|
+
b: *const data.Battle(data.PRNG),
|
|
833
|
+
w: protocol.ByteStream.Writer,
|
|
834
|
+
) !usize {
|
|
835
|
+
const pos = w.stream.pos;
|
|
836
|
+
const size = @sizeOf(data.Battle(data.PRNG)) - @sizeOf(data.PRNG);
|
|
837
|
+
assert(size % 2 == 0);
|
|
838
|
+
assert(size / 2 < 255);
|
|
839
|
+
|
|
840
|
+
var i: usize = 0;
|
|
841
|
+
while (i < size) : (i += 2) {
|
|
842
|
+
const val = std.mem.bytesAsValue(u16, std.mem.asBytes(a)[i .. i + 2]).*;
|
|
843
|
+
if (val != std.mem.bytesAsValue(u16, std.mem.asBytes(b)[i .. i + 2]).*) {
|
|
844
|
+
try w.writeByte(@intCast(i / 2));
|
|
845
|
+
try w.writeInt(u16, val, endian);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
return w.stream.pos - pos;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/// TODO
|
|
853
|
+
pub fn patch(battle: *data.Battle(data.PRNG), buf: []u8) void {
|
|
854
|
+
var i: usize = 0;
|
|
855
|
+
while (i < buf.len) : (i += 3) {
|
|
856
|
+
const off = @as(u16, @intCast(buf[i])) * 2;
|
|
857
|
+
const val = std.mem.readInt(u16, @ptrCast(buf[i + 1 .. i + 3]), endian);
|
|
858
|
+
std.mem.writeInt(u16, @ptrCast(std.mem.asBytes(battle)[off .. off + 2]), val, endian);
|
|
859
|
+
}
|
|
860
|
+
}
|