@pkmn/engine 0.1.0-dev.abd68573 → 0.1.0-dev.ac9225d5
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/README.md +8 -6
- package/build.zig +13 -29
- package/package.json +9 -9
- package/src/lib/common/debug.zig +2 -2
- package/src/lib/common/options.zig +2 -0
- package/src/lib/common/rational.zig +69 -12
- package/src/lib/gen1/calc.zig +210 -40
- package/src/lib/gen1/chance.zig +61 -63
- package/src/lib/gen1/data.zig +3 -3
- package/src/lib/gen1/helpers.zig +90 -29
- package/src/lib/gen1/mechanics.zig +55 -54
- package/src/lib/gen2/helpers.zig +37 -7
- package/src/test/display/pkmn.html.tmpl +5 -5
package/src/lib/gen1/chance.zig
CHANGED
|
@@ -255,6 +255,8 @@ pub const Duration = packed struct(u16) {
|
|
|
255
255
|
|
|
256
256
|
pub const Commit = enum { hit, miss, binding };
|
|
257
257
|
|
|
258
|
+
const uN = if (options.miss) u8 else u9;
|
|
259
|
+
|
|
258
260
|
/// Tracks chance actions and their associated probability during a Generation I battle update when
|
|
259
261
|
/// `options.chance` is enabled.
|
|
260
262
|
pub fn Chance(comptime Rational: type) type {
|
|
@@ -291,15 +293,12 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
291
293
|
//
|
|
292
294
|
// Finally, because each player's actions are processed sequentially we only need a single
|
|
293
295
|
// shared structure to track this information (though it needs to be cleared appropriately)
|
|
294
|
-
pending: if (showdown) struct {
|
|
295
|
-
hit: bool = false,
|
|
296
|
-
hit_probablity: u8 = 0,
|
|
297
|
-
} else struct {
|
|
296
|
+
pending: if (showdown) struct {} else struct {
|
|
298
297
|
crit: bool = false,
|
|
299
298
|
crit_probablity: u8 = 0,
|
|
300
299
|
damage_roll: u8 = 0,
|
|
301
300
|
hit: bool = false,
|
|
302
|
-
hit_probablity:
|
|
301
|
+
hit_probablity: uN = 0,
|
|
303
302
|
binding: u8 = 0,
|
|
304
303
|
} = .{},
|
|
305
304
|
|
|
@@ -308,25 +307,29 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
308
307
|
|
|
309
308
|
/// Convenience helper to clear fields which typically should be cleared between updates.
|
|
310
309
|
pub fn reset(self: *Self) void {
|
|
310
|
+
if (!enabled) return;
|
|
311
|
+
|
|
311
312
|
self.probability.reset();
|
|
312
313
|
self.actions.reset();
|
|
313
314
|
self.pending = .{};
|
|
314
315
|
}
|
|
315
316
|
|
|
316
317
|
pub fn commit(self: *Self, player: Player, kind: Commit) Error!void {
|
|
318
|
+
if (!enabled) return;
|
|
319
|
+
|
|
320
|
+
assert(!showdown);
|
|
321
|
+
|
|
317
322
|
var action = self.actions.get(player);
|
|
318
323
|
|
|
319
324
|
// We need to handle binding specially because we still need to commit it when attacking
|
|
320
325
|
// into a Pokémon that is immune
|
|
321
|
-
if (
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (kind == .binding) return;
|
|
329
|
-
} else assert(kind != .binding);
|
|
326
|
+
if (kind != .miss and self.pending.binding != 0) {
|
|
327
|
+
assert(action.duration == 0);
|
|
328
|
+
action.duration = @intCast(self.pending.binding);
|
|
329
|
+
assert(action.durations.binding == 0);
|
|
330
|
+
action.durations.binding = 1;
|
|
331
|
+
}
|
|
332
|
+
if (kind == .binding) return;
|
|
330
333
|
|
|
331
334
|
// Always commit the hit result if we make it here (commit won't be called at all if the
|
|
332
335
|
// target is immune/behind a sub/etc)
|
|
@@ -335,19 +338,19 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
335
338
|
action.hit = if (self.pending.hit) .true else .false;
|
|
336
339
|
}
|
|
337
340
|
|
|
338
|
-
|
|
341
|
+
// If the move actually lands we can commit any past critical hit / damage rolls. Note
|
|
342
|
+
// we can't rely on the presence or absence of a damage roll to determine whether a
|
|
343
|
+
// "critical hit" was spurious or not as moves that do 1 or less damage don't rol for
|
|
344
|
+
// damage but can still crit (instead, whether or not a roll is deemed to be a no-op is
|
|
345
|
+
// passed to the critical hit helper)
|
|
346
|
+
if (kind != .hit) return assert(!self.pending.crit or self.pending.crit_probablity > 0);
|
|
339
347
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
assert(!self.pending.crit or self.pending.crit_probablity > 0);
|
|
345
|
-
|
|
346
|
-
if (self.pending.crit_probablity != 0) {
|
|
347
|
-
try self.probability.update(self.pending.crit_probablity, 256);
|
|
348
|
-
action.critical_hit = if (self.pending.crit) .true else .false;
|
|
349
|
-
}
|
|
348
|
+
if (self.pending.crit_probablity != 0) {
|
|
349
|
+
try self.probability.update(self.pending.crit_probablity, 256);
|
|
350
|
+
action.critical_hit = if (self.pending.crit) .true else .false;
|
|
351
|
+
}
|
|
350
352
|
|
|
353
|
+
if (self.pending.damage_roll > 0) {
|
|
351
354
|
try self.probability.update(1, 39);
|
|
352
355
|
action.damage = self.pending.damage_roll;
|
|
353
356
|
}
|
|
@@ -398,12 +401,17 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
398
401
|
self.actions.p2.speed_tie = self.actions.p1.speed_tie;
|
|
399
402
|
}
|
|
400
403
|
|
|
401
|
-
pub fn hit(self: *Self, ok: bool, accuracy:
|
|
404
|
+
pub fn hit(self: *Self, player: Player, ok: bool, accuracy: uN) Error!void {
|
|
402
405
|
if (!enabled) return;
|
|
403
406
|
|
|
404
407
|
const p = if (ok) accuracy else @as(u8, @intCast(256 - @as(u9, accuracy)));
|
|
405
|
-
|
|
406
|
-
|
|
408
|
+
if (showdown) {
|
|
409
|
+
try self.probability.update(p, 256);
|
|
410
|
+
self.actions.get(player).hit = if (ok) .true else .false;
|
|
411
|
+
} else {
|
|
412
|
+
self.pending.hit = ok;
|
|
413
|
+
self.pending.hit_probablity = p;
|
|
414
|
+
}
|
|
407
415
|
}
|
|
408
416
|
|
|
409
417
|
pub fn criticalHit(self: *Self, player: Player, crit: bool, rate: u8) Error!void {
|
|
@@ -621,19 +629,25 @@ test "Chance.speedTie" {
|
|
|
621
629
|
test "Chance.hit" {
|
|
622
630
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
623
631
|
|
|
624
|
-
chance.hit(true, 229);
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
632
|
+
try chance.hit(.P1, true, 229);
|
|
633
|
+
if (!showdown) {
|
|
634
|
+
try expectProbability(&chance.probability, 1, 1);
|
|
635
|
+
try expectValue(Optional(bool).None, chance.actions.p1.hit);
|
|
636
|
+
|
|
637
|
+
try chance.commit(.P1, .hit);
|
|
638
|
+
}
|
|
628
639
|
try expectValue(Optional(bool).true, chance.actions.p1.hit);
|
|
629
640
|
try expectProbability(&chance.probability, 229, 256);
|
|
630
641
|
|
|
631
642
|
chance.reset();
|
|
632
643
|
|
|
633
|
-
chance.hit(false, 255);
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
644
|
+
try chance.hit(.P2, false, 255);
|
|
645
|
+
if (!showdown) {
|
|
646
|
+
try expectProbability(&chance.probability, 1, 1);
|
|
647
|
+
try expectValue(Optional(bool).None, chance.actions.p2.hit);
|
|
648
|
+
|
|
649
|
+
try chance.commit(.P2, .miss);
|
|
650
|
+
}
|
|
637
651
|
try expectValue(Optional(bool).false, chance.actions.p2.hit);
|
|
638
652
|
try expectProbability(&chance.probability, 1, 256);
|
|
639
653
|
}
|
|
@@ -649,26 +663,18 @@ test "Chance.criticalHit" {
|
|
|
649
663
|
try expectProbability(&chance.probability, 1, 1);
|
|
650
664
|
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
651
665
|
|
|
652
|
-
try chance.
|
|
653
|
-
try expectProbability(&chance.probability, 1, 1);
|
|
654
|
-
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
655
|
-
|
|
656
|
-
chance.hit(true, 128);
|
|
666
|
+
try chance.hit(.P1, true, 128);
|
|
657
667
|
|
|
658
|
-
try chance.
|
|
659
|
-
try expectProbability(&chance.probability, 1, 2);
|
|
668
|
+
try expectProbability(&chance.probability, 1, 1);
|
|
660
669
|
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
661
670
|
|
|
662
|
-
chance.probability.reset();
|
|
663
671
|
try chance.damage(.P1, 217);
|
|
664
672
|
|
|
665
|
-
try chance.
|
|
666
|
-
try expectProbability(&chance.probability, 1, 2);
|
|
673
|
+
try expectProbability(&chance.probability, 1, 1);
|
|
667
674
|
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
668
675
|
|
|
669
|
-
chance.probability.reset();
|
|
670
|
-
|
|
671
676
|
try chance.commit(.P1, .hit);
|
|
677
|
+
|
|
672
678
|
try expectProbability(&chance.probability, 17, 19968); // (1/2) * (17/256) * (1/39)
|
|
673
679
|
try expectValue(Optional(bool).true, chance.actions.p1.critical_hit);
|
|
674
680
|
}
|
|
@@ -683,20 +689,14 @@ test "Chance.criticalHit" {
|
|
|
683
689
|
try expectProbability(&chance.probability, 1, 1);
|
|
684
690
|
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
685
691
|
|
|
686
|
-
try chance.
|
|
687
|
-
try expectProbability(&chance.probability, 1, 1);
|
|
688
|
-
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
689
|
-
|
|
690
|
-
chance.hit(true, 128);
|
|
692
|
+
try chance.hit(.P1, true, 128);
|
|
691
693
|
|
|
692
|
-
try chance.
|
|
693
|
-
try expectProbability(&chance.probability, 1, 2);
|
|
694
|
+
try expectProbability(&chance.probability, 1, 1);
|
|
694
695
|
try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
|
|
695
696
|
|
|
696
|
-
chance.probability.reset();
|
|
697
697
|
try chance.damage(.P1, 217);
|
|
698
|
-
|
|
699
698
|
try chance.commit(.P1, .hit);
|
|
699
|
+
|
|
700
700
|
try expectProbability(&chance.probability, 251, 19968); // (1/2) * (251/256) * (1/39)
|
|
701
701
|
try expectValue(Optional(bool).false, chance.actions.p1.critical_hit);
|
|
702
702
|
}
|
|
@@ -713,11 +713,8 @@ test "Chance.damage" {
|
|
|
713
713
|
try expectProbability(&chance.probability, 1, 1);
|
|
714
714
|
try expectValue(0, chance.actions.p1.damage);
|
|
715
715
|
|
|
716
|
-
try chance.commit(.P1, .miss);
|
|
717
|
-
try expectProbability(&chance.probability, 1, 1);
|
|
718
|
-
try expectValue(0, chance.actions.p1.damage);
|
|
719
|
-
|
|
720
716
|
try chance.commit(.P1, .hit);
|
|
717
|
+
|
|
721
718
|
try expectProbability(&chance.probability, 1, 39);
|
|
722
719
|
try expectValue(217, chance.actions.p1.damage);
|
|
723
720
|
}
|
|
@@ -807,6 +804,7 @@ test "Chance.duration" {
|
|
|
807
804
|
if (!showdown) {
|
|
808
805
|
try expectValue(0, chance.actions.p2.duration);
|
|
809
806
|
try expectValue(0, chance.actions.p2.durations.binding);
|
|
807
|
+
|
|
810
808
|
try chance.commit(.P2, .hit);
|
|
811
809
|
}
|
|
812
810
|
try expectValue(4, chance.actions.p2.duration);
|
|
@@ -989,8 +987,8 @@ const Null = struct {
|
|
|
989
987
|
_ = .{ self, p1 };
|
|
990
988
|
}
|
|
991
989
|
|
|
992
|
-
pub fn hit(self: Null, ok: bool, accuracy:
|
|
993
|
-
_ = .{ self, ok, accuracy };
|
|
990
|
+
pub fn hit(self: Null, player: Player, ok: bool, accuracy: uN) Error!void {
|
|
991
|
+
_ = .{ self, player, ok, accuracy };
|
|
994
992
|
}
|
|
995
993
|
|
|
996
994
|
pub fn criticalHit(self: Null, player: Player, crit: bool, rate: u8) Error!void {
|
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
|
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.
|
|
@@ -230,6 +235,7 @@ pub const Pokemon = struct {
|
|
|
230
235
|
var m: Move = .None;
|
|
231
236
|
sample: while (true) {
|
|
232
237
|
m = @enumFromInt(rand.range(u8, 1, Move.size - 1 + 1));
|
|
238
|
+
if (opt.durations and durations(m)) continue :sample;
|
|
233
239
|
if (opt.block and blocked(m)) continue :sample;
|
|
234
240
|
for (0..i) |j| if (ms[j].id == m) continue :sample;
|
|
235
241
|
break;
|
|
@@ -267,6 +273,23 @@ fn blocked(m: Move) bool {
|
|
|
267
273
|
};
|
|
268
274
|
}
|
|
269
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
|
+
|
|
270
293
|
/// Convenience helper to create a move-type choice for the provided move slot.
|
|
271
294
|
pub fn move(slot: u4) Choice {
|
|
272
295
|
return .{ .type = .Move, .data = slot };
|
|
@@ -287,7 +310,7 @@ pub const Rolls = struct {
|
|
|
287
310
|
|
|
288
311
|
/// Returns a slice with the correct range of values for speed ties given the `action` state.
|
|
289
312
|
pub fn speedTie(action: Action) []const Optional(Player) {
|
|
290
|
-
return if (
|
|
313
|
+
return if (action.speed_tie == .None) &PLAYER_NONE else &PLAYERS;
|
|
291
314
|
}
|
|
292
315
|
|
|
293
316
|
const BOOL_NONE = [_]Optional(bool){.None};
|
|
@@ -297,25 +320,19 @@ pub const Rolls = struct {
|
|
|
297
320
|
/// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
|
|
298
321
|
pub fn hit(action: Action, parent: Optional(bool)) []const Optional(bool) {
|
|
299
322
|
if (parent == .true) return &BOOL_NONE;
|
|
300
|
-
return if (
|
|
323
|
+
return if (action.hit == .None) &BOOL_NONE else &BOOLS;
|
|
301
324
|
}
|
|
302
325
|
|
|
303
326
|
/// Returns a slice with the correct range of values for critical hits given the `action` state
|
|
304
327
|
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
305
|
-
pub fn criticalHit(
|
|
306
|
-
action: Action,
|
|
307
|
-
parent: Optional(bool),
|
|
308
|
-
) []const Optional(bool) {
|
|
328
|
+
pub fn criticalHit(action: Action, parent: Optional(bool)) []const Optional(bool) {
|
|
309
329
|
if (parent == .false) return &BOOL_NONE;
|
|
310
|
-
return if (
|
|
330
|
+
return if (action.critical_hit == .None) &BOOL_NONE else &BOOLS;
|
|
311
331
|
}
|
|
312
332
|
|
|
313
333
|
/// Returns a slice with the correct range of values for secondary chances hits given the
|
|
314
334
|
/// `action` state and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
315
|
-
pub fn secondaryChance(
|
|
316
|
-
action: Action,
|
|
317
|
-
parent: Optional(bool),
|
|
318
|
-
) []const Optional(bool) {
|
|
335
|
+
pub fn secondaryChance(action: Action, parent: Optional(bool)) []const Optional(bool) {
|
|
319
336
|
if (parent == .false) return &BOOL_NONE;
|
|
320
337
|
return if (@field(action, "secondary_chance") == .None) &BOOL_NONE else &BOOLS;
|
|
321
338
|
}
|
|
@@ -326,7 +343,7 @@ pub const Rolls = struct {
|
|
|
326
343
|
/// Returns the range bounding damage rolls given the `action` state and the state of
|
|
327
344
|
/// the `parent` (whether the player's Pokémon's move hit).
|
|
328
345
|
pub fn damage(action: Action, parent: Optional(bool)) Range {
|
|
329
|
-
return if (parent == .false or
|
|
346
|
+
return if (parent == .false or action.damage == 0)
|
|
330
347
|
.{ .min = 0, .max = 1 }
|
|
331
348
|
else
|
|
332
349
|
.{ .min = 217, .max = 256 };
|
|
@@ -347,14 +364,14 @@ pub const Rolls = struct {
|
|
|
347
364
|
/// Returns a slice with the correct range of values for confused given the `action` state
|
|
348
365
|
/// and the state of the `parent` (the player's Pokémon's remaining confusion turns).
|
|
349
366
|
pub fn confused(action: Action, parent: u4) []const Optional(bool) {
|
|
350
|
-
return if (parent == 0 or
|
|
367
|
+
return if (parent == 0 or action.confused == .None) &BOOL_NONE else &BOOLS;
|
|
351
368
|
}
|
|
352
369
|
|
|
353
370
|
/// Returns a slice with the correct range of values for paralysis given the `action` state
|
|
354
371
|
/// and the state of the `parent` (whether the player's Pokémon was confused).
|
|
355
372
|
pub fn paralyzed(action: Action, parent: Optional(bool)) []const Optional(bool) {
|
|
356
373
|
if (parent == .true) return &BOOL_NONE;
|
|
357
|
-
return if (
|
|
374
|
+
return if (action.paralyzed == .None) &BOOL_NONE else &BOOLS;
|
|
358
375
|
}
|
|
359
376
|
|
|
360
377
|
const DURATION_NONE = [_]u1{0};
|
|
@@ -364,7 +381,7 @@ pub const Rolls = struct {
|
|
|
364
381
|
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
365
382
|
pub fn duration(action: Action, parent: Optional(bool)) []const u1 {
|
|
366
383
|
if (parent == .false) return &DURATION_NONE;
|
|
367
|
-
return if (
|
|
384
|
+
return if (action.duration == 0) &DURATION_NONE else &DURATION;
|
|
368
385
|
}
|
|
369
386
|
|
|
370
387
|
const U3_NONE = [_]u3{0};
|
|
@@ -372,8 +389,8 @@ pub const Rolls = struct {
|
|
|
372
389
|
|
|
373
390
|
/// Returns a slice with a range of values for sleep given the `action` and `origin` states.
|
|
374
391
|
pub inline fn sleep(action: Action, origin: Action) []const u3 {
|
|
375
|
-
const a =
|
|
376
|
-
const o =
|
|
392
|
+
const a = action.durations.sleep;
|
|
393
|
+
const o = origin.durations.sleep;
|
|
377
394
|
if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
|
|
378
395
|
assert(a == 0 or a == o + 1);
|
|
379
396
|
return if (o >= 7) &U3_NONE else &[_]u3{ 0, o + 1 };
|
|
@@ -385,8 +402,8 @@ pub const Rolls = struct {
|
|
|
385
402
|
/// and the state of the `parent` (the player's Pokémon's remaining sleep turns).
|
|
386
403
|
pub inline fn disable(action: Action, origin: Action, parent: u4) []const u4 {
|
|
387
404
|
if (parent > 0) return &DISABLE_NONE;
|
|
388
|
-
const a =
|
|
389
|
-
const o =
|
|
405
|
+
const a = action.durations.disable;
|
|
406
|
+
const o = origin.durations.disable;
|
|
390
407
|
if (o == a) return if (o == 0) &DISABLE_NONE else &[_]u4{o};
|
|
391
408
|
assert(a == 0 or a == o + 1);
|
|
392
409
|
return if (o >= 8) &DISABLE_NONE else &[_]u4{ 0, o + 1 };
|
|
@@ -396,8 +413,8 @@ pub const Rolls = struct {
|
|
|
396
413
|
/// and the state of the `parent` (the player's remaining sleep turns).
|
|
397
414
|
pub inline fn confusion(action: Action, origin: Action, parent: u4) []const u3 {
|
|
398
415
|
if (parent > 0) return &U3_NONE;
|
|
399
|
-
const a =
|
|
400
|
-
const o =
|
|
416
|
+
const a = action.durations.confusion;
|
|
417
|
+
const o = origin.durations.confusion;
|
|
401
418
|
if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
|
|
402
419
|
assert(a == 0 or a == o + 1);
|
|
403
420
|
return if (o >= 5) &U3_NONE else if (o < 2)
|
|
@@ -410,8 +427,8 @@ pub const Rolls = struct {
|
|
|
410
427
|
/// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
|
|
411
428
|
pub inline fn attacking(action: Action, origin: Action, parent: Optional(bool)) []const u3 {
|
|
412
429
|
if (parent == .true) return &U3_NONE;
|
|
413
|
-
const a =
|
|
414
|
-
const o =
|
|
430
|
+
const a = action.durations.attacking;
|
|
431
|
+
const o = origin.durations.attacking;
|
|
415
432
|
if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
|
|
416
433
|
assert(a == 0 or a == o + 1);
|
|
417
434
|
return if (o >= 3) &U3_NONE else if (o < 2)
|
|
@@ -424,8 +441,8 @@ pub const Rolls = struct {
|
|
|
424
441
|
/// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
|
|
425
442
|
pub inline fn binding(action: Action, origin: Action, parent: Optional(bool)) []const u3 {
|
|
426
443
|
if (parent == .true) return &U3_NONE;
|
|
427
|
-
const a =
|
|
428
|
-
const o =
|
|
444
|
+
const a = action.durations.binding;
|
|
445
|
+
const o = origin.durations.binding;
|
|
429
446
|
if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
|
|
430
447
|
assert(a == 0 or a == o + 1);
|
|
431
448
|
return if (o >= 4) &U3_NONE else &[_]u3{ 0, o + 1 };
|
|
@@ -440,7 +457,7 @@ pub const Rolls = struct {
|
|
|
440
457
|
/// These slots may or **may not be valid** as slots may be unset / have 0 PP.
|
|
441
458
|
pub fn moveSlot(action: Action, parent: Optional(bool)) []const u4 {
|
|
442
459
|
if (parent == .false) return &SLOT_NONE;
|
|
443
|
-
return if (
|
|
460
|
+
return if (action.move_slot == 0) &SLOT_NONE else &SLOT;
|
|
444
461
|
}
|
|
445
462
|
|
|
446
463
|
const MULTI_NONE = [_]u4{0};
|
|
@@ -450,7 +467,7 @@ pub const Rolls = struct {
|
|
|
450
467
|
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
451
468
|
pub fn multiHit(action: Action, parent: Optional(bool)) []const u4 {
|
|
452
469
|
if (parent == .false) return &MULTI_NONE;
|
|
453
|
-
return if (
|
|
470
|
+
return if (action.multi_hit == 0) &MULTI_NONE else &MULTI;
|
|
454
471
|
}
|
|
455
472
|
|
|
456
473
|
const PSYWAVE_NONE = [_]u8{0};
|
|
@@ -468,7 +485,7 @@ pub const Rolls = struct {
|
|
|
468
485
|
parent: Optional(bool),
|
|
469
486
|
) []const u8 {
|
|
470
487
|
if (parent == .false) return &PSYWAVE_NONE;
|
|
471
|
-
return if (
|
|
488
|
+
return if (action.psywave == 0)
|
|
472
489
|
&PSYWAVE_NONE
|
|
473
490
|
else
|
|
474
491
|
PSYWAVE[0 .. @as(u16, side.stored().level) * 3 / 2];
|
|
@@ -478,7 +495,7 @@ pub const Rolls = struct {
|
|
|
478
495
|
|
|
479
496
|
/// Returns a slice with the correct range of values for metronome given the `action` state.
|
|
480
497
|
pub fn metronome(action: Action) []const Move {
|
|
481
|
-
return if (
|
|
498
|
+
return if (action.metronome == .None) &MOVE_NONE else &Move.METRONOME;
|
|
482
499
|
}
|
|
483
500
|
};
|
|
484
501
|
|
|
@@ -797,3 +814,47 @@ test "Rolls.psywave" {
|
|
|
797
814
|
try expectEqual(121, rolls[rolls.len - 1]);
|
|
798
815
|
try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p2, &side, .false));
|
|
799
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
|
+
}
|