@pkmn/engine 0.1.0-dev.14ca6108 → 0.1.0-dev.15f64810
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 +34 -23
- 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 +132 -29
- 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 -49
- package/src/lib/gen1/mechanics.zig +327 -307
- 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 +1633 -484
- 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/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,24 +318,24 @@ 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
|
|
328
|
+
pub fn criticalHit(
|
|
286
329
|
action: Action,
|
|
287
330
|
parent: Optional(bool),
|
|
288
331
|
) []const Optional(bool) {
|
|
289
332
|
if (parent == .false) return &BOOL_NONE;
|
|
290
|
-
return if (
|
|
333
|
+
return if (action.critical_hit == .None) &BOOL_NONE else &BOOLS;
|
|
291
334
|
}
|
|
292
335
|
|
|
293
336
|
/// Returns a slice with the correct range of values for secondary chances hits given the
|
|
294
337
|
/// `action` state and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
295
|
-
pub
|
|
338
|
+
pub fn secondaryChance(
|
|
296
339
|
action: Action,
|
|
297
340
|
parent: Optional(bool),
|
|
298
341
|
) []const Optional(bool) {
|
|
@@ -305,8 +348,8 @@ pub const Rolls = struct {
|
|
|
305
348
|
|
|
306
349
|
/// Returns the range bounding damage rolls given the `action` state and the state of
|
|
307
350
|
/// the `parent` (whether the player's Pokémon's move hit).
|
|
308
|
-
pub
|
|
309
|
-
return if (parent == .false or
|
|
351
|
+
pub fn damage(action: Action, parent: Optional(bool)) Range {
|
|
352
|
+
return if (parent == .false or action.damage == 0)
|
|
310
353
|
.{ .min = 0, .max = 1 }
|
|
311
354
|
else
|
|
312
355
|
.{ .min = 217, .max = 256 };
|
|
@@ -314,7 +357,7 @@ pub const Rolls = struct {
|
|
|
314
357
|
|
|
315
358
|
/// Returns the max damage roll which will produce the same damage as `roll`
|
|
316
359
|
/// given the base damage in `summaries`.
|
|
317
|
-
pub
|
|
360
|
+
pub fn coalesce(player: Player, roll: u8, summaries: *calc.Summaries, cap: bool) !u8 {
|
|
318
361
|
if (roll == 0) return roll;
|
|
319
362
|
|
|
320
363
|
const dmg = summaries.get(player.foe()).damage;
|
|
@@ -326,15 +369,15 @@ pub const Rolls = struct {
|
|
|
326
369
|
|
|
327
370
|
/// Returns a slice with the correct range of values for confused given the `action` state
|
|
328
371
|
/// and the state of the `parent` (the player's Pokémon's remaining confusion turns).
|
|
329
|
-
pub
|
|
330
|
-
return if (parent == 0 or
|
|
372
|
+
pub fn confused(action: Action, parent: u4) []const Optional(bool) {
|
|
373
|
+
return if (parent == 0 or action.confused == .None) &BOOL_NONE else &BOOLS;
|
|
331
374
|
}
|
|
332
375
|
|
|
333
376
|
/// Returns a slice with the correct range of values for paralysis given the `action` state
|
|
334
377
|
/// and the state of the `parent` (whether the player's Pokémon was confused).
|
|
335
|
-
pub
|
|
378
|
+
pub fn paralyzed(action: Action, parent: Optional(bool)) []const Optional(bool) {
|
|
336
379
|
if (parent == .true) return &BOOL_NONE;
|
|
337
|
-
return if (
|
|
380
|
+
return if (action.paralyzed == .None) &BOOL_NONE else &BOOLS;
|
|
338
381
|
}
|
|
339
382
|
|
|
340
383
|
const DURATION_NONE = [_]u1{0};
|
|
@@ -342,9 +385,9 @@ pub const Rolls = struct {
|
|
|
342
385
|
|
|
343
386
|
/// Returns a slice with a range of values for duration given the `action` state
|
|
344
387
|
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
345
|
-
pub
|
|
388
|
+
pub fn duration(action: Action, parent: Optional(bool)) []const u1 {
|
|
346
389
|
if (parent == .false) return &DURATION_NONE;
|
|
347
|
-
return if (
|
|
390
|
+
return if (action.duration == 0) &DURATION_NONE else &DURATION;
|
|
348
391
|
}
|
|
349
392
|
|
|
350
393
|
const U3_NONE = [_]u3{0};
|
|
@@ -352,8 +395,8 @@ pub const Rolls = struct {
|
|
|
352
395
|
|
|
353
396
|
/// Returns a slice with a range of values for sleep given the `action` and `origin` states.
|
|
354
397
|
pub inline fn sleep(action: Action, origin: Action) []const u3 {
|
|
355
|
-
const a =
|
|
356
|
-
const o =
|
|
398
|
+
const a = action.durations.sleep;
|
|
399
|
+
const o = origin.durations.sleep;
|
|
357
400
|
if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
|
|
358
401
|
assert(a == 0 or a == o + 1);
|
|
359
402
|
return if (o >= 7) &U3_NONE else &[_]u3{ 0, o + 1 };
|
|
@@ -365,8 +408,8 @@ pub const Rolls = struct {
|
|
|
365
408
|
/// and the state of the `parent` (the player's Pokémon's remaining sleep turns).
|
|
366
409
|
pub inline fn disable(action: Action, origin: Action, parent: u4) []const u4 {
|
|
367
410
|
if (parent > 0) return &DISABLE_NONE;
|
|
368
|
-
const a =
|
|
369
|
-
const o =
|
|
411
|
+
const a = action.durations.disable;
|
|
412
|
+
const o = origin.durations.disable;
|
|
370
413
|
if (o == a) return if (o == 0) &DISABLE_NONE else &[_]u4{o};
|
|
371
414
|
assert(a == 0 or a == o + 1);
|
|
372
415
|
return if (o >= 8) &DISABLE_NONE else &[_]u4{ 0, o + 1 };
|
|
@@ -376,8 +419,8 @@ pub const Rolls = struct {
|
|
|
376
419
|
/// and the state of the `parent` (the player's remaining sleep turns).
|
|
377
420
|
pub inline fn confusion(action: Action, origin: Action, parent: u4) []const u3 {
|
|
378
421
|
if (parent > 0) return &U3_NONE;
|
|
379
|
-
const a =
|
|
380
|
-
const o =
|
|
422
|
+
const a = action.durations.confusion;
|
|
423
|
+
const o = origin.durations.confusion;
|
|
381
424
|
if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
|
|
382
425
|
assert(a == 0 or a == o + 1);
|
|
383
426
|
return if (o >= 5) &U3_NONE else if (o < 2)
|
|
@@ -390,8 +433,8 @@ pub const Rolls = struct {
|
|
|
390
433
|
/// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
|
|
391
434
|
pub inline fn attacking(action: Action, origin: Action, parent: Optional(bool)) []const u3 {
|
|
392
435
|
if (parent == .true) return &U3_NONE;
|
|
393
|
-
const a =
|
|
394
|
-
const o =
|
|
436
|
+
const a = action.durations.attacking;
|
|
437
|
+
const o = origin.durations.attacking;
|
|
395
438
|
if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
|
|
396
439
|
assert(a == 0 or a == o + 1);
|
|
397
440
|
return if (o >= 3) &U3_NONE else if (o < 2)
|
|
@@ -404,8 +447,8 @@ pub const Rolls = struct {
|
|
|
404
447
|
/// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
|
|
405
448
|
pub inline fn binding(action: Action, origin: Action, parent: Optional(bool)) []const u3 {
|
|
406
449
|
if (parent == .true) return &U3_NONE;
|
|
407
|
-
const a =
|
|
408
|
-
const o =
|
|
450
|
+
const a = action.durations.binding;
|
|
451
|
+
const o = origin.durations.binding;
|
|
409
452
|
if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
|
|
410
453
|
assert(a == 0 or a == o + 1);
|
|
411
454
|
return if (o >= 4) &U3_NONE else &[_]u3{ 0, o + 1 };
|
|
@@ -418,9 +461,9 @@ pub const Rolls = struct {
|
|
|
418
461
|
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
419
462
|
///
|
|
420
463
|
/// These slots may or **may not be valid** as slots may be unset / have 0 PP.
|
|
421
|
-
pub
|
|
464
|
+
pub fn moveSlot(action: Action, parent: Optional(bool)) []const u4 {
|
|
422
465
|
if (parent == .false) return &SLOT_NONE;
|
|
423
|
-
return if (
|
|
466
|
+
return if (action.move_slot == 0) &SLOT_NONE else &SLOT;
|
|
424
467
|
}
|
|
425
468
|
|
|
426
469
|
const MULTI_NONE = [_]u4{0};
|
|
@@ -428,9 +471,9 @@ pub const Rolls = struct {
|
|
|
428
471
|
|
|
429
472
|
/// Returns a slice with the correct range of values for multi hit given the `action` state
|
|
430
473
|
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
431
|
-
pub
|
|
474
|
+
pub fn multiHit(action: Action, parent: Optional(bool)) []const u4 {
|
|
432
475
|
if (parent == .false) return &MULTI_NONE;
|
|
433
|
-
return if (
|
|
476
|
+
return if (action.multi_hit == 0) &MULTI_NONE else &MULTI;
|
|
434
477
|
}
|
|
435
478
|
|
|
436
479
|
const PSYWAVE_NONE = [_]u8{0};
|
|
@@ -442,13 +485,13 @@ pub const Rolls = struct {
|
|
|
442
485
|
|
|
443
486
|
/// Returns a slice with the correct range of values for psywave given the `action` state,
|
|
444
487
|
/// the `side`, and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
445
|
-
pub
|
|
488
|
+
pub fn psywave(
|
|
446
489
|
action: Action,
|
|
447
490
|
side: *data.Side,
|
|
448
491
|
parent: Optional(bool),
|
|
449
492
|
) []const u8 {
|
|
450
493
|
if (parent == .false) return &PSYWAVE_NONE;
|
|
451
|
-
return if (
|
|
494
|
+
return if (action.psywave == 0)
|
|
452
495
|
&PSYWAVE_NONE
|
|
453
496
|
else
|
|
454
497
|
PSYWAVE[0 .. @as(u16, side.stored().level) * 3 / 2];
|
|
@@ -457,8 +500,8 @@ pub const Rolls = struct {
|
|
|
457
500
|
const MOVE_NONE = [_]Move{.None};
|
|
458
501
|
|
|
459
502
|
/// Returns a slice with the correct range of values for metronome given the `action` state.
|
|
460
|
-
pub
|
|
461
|
-
return if (
|
|
503
|
+
pub fn metronome(action: Action) []const Move {
|
|
504
|
+
return if (action.metronome == .None) &MOVE_NONE else &Move.METRONOME;
|
|
462
505
|
}
|
|
463
506
|
};
|
|
464
507
|
|
|
@@ -478,12 +521,12 @@ test "Rolls.damage" {
|
|
|
478
521
|
test "Rolls.coalesce" {
|
|
479
522
|
var summaries =
|
|
480
523
|
calc.Summaries{ .p1 = .{ .damage = .{ .base = 74, .final = 69, .capped = true } } };
|
|
481
|
-
try expectEqual(
|
|
482
|
-
try expectEqual(
|
|
483
|
-
try expectEqual(
|
|
524
|
+
try expectEqual(0, try Rolls.coalesce(.P2, 0, &summaries, false));
|
|
525
|
+
try expectEqual(241, try Rolls.coalesce(.P2, 238, &summaries, false));
|
|
526
|
+
try expectEqual(255, try Rolls.coalesce(.P2, 238, &summaries, true));
|
|
484
527
|
summaries.p1.damage.final = 74;
|
|
485
|
-
try expectEqual(
|
|
486
|
-
try expectEqual(
|
|
528
|
+
try expectEqual(217, try Rolls.coalesce(.P2, 217, &summaries, false));
|
|
529
|
+
try expectEqual(255, try Rolls.coalesce(.P2, 217, &summaries, true));
|
|
487
530
|
}
|
|
488
531
|
|
|
489
532
|
test "Rolls.hit" {
|
|
@@ -771,9 +814,53 @@ test "Rolls.psywave" {
|
|
|
771
814
|
|
|
772
815
|
try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p1, &side, .None));
|
|
773
816
|
var rolls = Rolls.psywave(actions.p2, &side, .None);
|
|
774
|
-
try expectEqual(
|
|
817
|
+
try expectEqual(150, rolls[rolls.len - 1]);
|
|
775
818
|
side.stored().level = 81;
|
|
776
819
|
rolls = Rolls.psywave(actions.p2, &side, .None);
|
|
777
|
-
try expectEqual(
|
|
820
|
+
try expectEqual(121, rolls[rolls.len - 1]);
|
|
778
821
|
try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p2, &side, .false));
|
|
779
822
|
}
|
|
823
|
+
|
|
824
|
+
/// Empirically determined maximum number of bytes possibly required to store the diff between two
|
|
825
|
+
/// Generation I battles.
|
|
826
|
+
pub const MAX_DIFFS: usize = 117;
|
|
827
|
+
|
|
828
|
+
/// The optimal size in bytes required to hold the diff between two Generation I battles.
|
|
829
|
+
/// At least as large as MAX_DIFFS.
|
|
830
|
+
pub const DIFFS_SIZE = if (builtin.mode == .ReleaseSmall)
|
|
831
|
+
MAX_DIFFS
|
|
832
|
+
else
|
|
833
|
+
std.math.ceilPowerOfTwo(usize, MAX_DIFFS) catch unreachable;
|
|
834
|
+
|
|
835
|
+
/// TODO
|
|
836
|
+
pub fn diff(
|
|
837
|
+
a: *const data.Battle(data.PRNG),
|
|
838
|
+
b: *const data.Battle(data.PRNG),
|
|
839
|
+
w: protocol.ByteStream.Writer,
|
|
840
|
+
) !usize {
|
|
841
|
+
const pos = w.stream.pos;
|
|
842
|
+
const size = @sizeOf(data.Battle(data.PRNG)) - @sizeOf(data.PRNG);
|
|
843
|
+
assert(size % 2 == 0);
|
|
844
|
+
assert(size / 2 < 255);
|
|
845
|
+
|
|
846
|
+
var i: usize = 0;
|
|
847
|
+
while (i < size) : (i += 2) {
|
|
848
|
+
const val = std.mem.bytesAsValue(u16, std.mem.asBytes(a)[i .. i + 2]).*;
|
|
849
|
+
if (val != std.mem.bytesAsValue(u16, std.mem.asBytes(b)[i .. i + 2]).*) {
|
|
850
|
+
try w.writeByte(@intCast(i / 2));
|
|
851
|
+
try w.writeInt(u16, val, endian);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
return w.stream.pos - pos;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/// TODO
|
|
859
|
+
pub fn patch(battle: *data.Battle(data.PRNG), buf: []u8) void {
|
|
860
|
+
var i: usize = 0;
|
|
861
|
+
while (i < buf.len) : (i += 3) {
|
|
862
|
+
const off = @as(u16, @intCast(buf[i])) * 2;
|
|
863
|
+
const val = std.mem.readInt(u16, @ptrCast(buf[i + 1 .. i + 3]), endian);
|
|
864
|
+
std.mem.writeInt(u16, @ptrCast(std.mem.asBytes(battle)[off .. off + 2]), val, endian);
|
|
865
|
+
}
|
|
866
|
+
}
|