@pkmn/engine 0.1.0-dev.c4f94762 → 0.1.0-dev.c4ffaf39
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 +13 -11
- package/build/pkg/addon.js +6 -7
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/data.d.ts +2 -3
- package/build/pkg/protocol.js +4 -4
- 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 +2 -3
- package/build/tools/debug.js.map +1 -1
- package/build.zig +67 -86
- package/package.json +26 -19
- package/src/bin/install-pkmn-engine +13 -5
- package/src/lib/common/data.zig +4 -4
- 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 +15 -20
- package/src/lib/common/rng.zig +11 -11
- package/src/lib/gen1/calc.zig +65 -0
- package/src/lib/gen1/chance.zig +33 -30
- package/src/lib/gen1/data/species.zig +1 -1
- package/src/lib/gen1/data.zig +18 -18
- package/src/lib/gen1/helpers.zig +55 -15
- package/src/lib/gen1/mechanics.zig +46 -8
- package/src/lib/gen2/chance.zig +38 -37
- package/src/lib/gen2/data.zig +8 -8
- package/src/lib/gen2/helpers.zig +37 -7
- package/src/lib/gen2/mechanics.zig +112 -27
- package/src/lib/{binding/node.zig → node.zig} +3 -2
- 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 -329
package/src/lib/gen1/calc.zig
CHANGED
|
@@ -387,6 +387,71 @@ pub fn transitions(
|
|
|
387
387
|
return stats;
|
|
388
388
|
}
|
|
389
389
|
|
|
390
|
+
pub fn update(
|
|
391
|
+
battle: anytype,
|
|
392
|
+
c1: Choice,
|
|
393
|
+
c2: Choice,
|
|
394
|
+
options: anytype,
|
|
395
|
+
allocator: std.mem.Allocator,
|
|
396
|
+
writer: anytype,
|
|
397
|
+
transition: bool,
|
|
398
|
+
) !Result {
|
|
399
|
+
options.chance.reset();
|
|
400
|
+
|
|
401
|
+
if (!pkmn.options.chance or !pkmn.options.calc) return battle.update(c1, c2, options);
|
|
402
|
+
|
|
403
|
+
var copy = battle.*;
|
|
404
|
+
const actions = options.chance.actions;
|
|
405
|
+
|
|
406
|
+
// Perfom the actual update
|
|
407
|
+
const result = battle.update(c1, c2, options);
|
|
408
|
+
|
|
409
|
+
if (transition) {
|
|
410
|
+
// Ensure we can generate all transitions from the same original state
|
|
411
|
+
// (we must change the battle's RNG from a FixedRNG to a PRNG because
|
|
412
|
+
// the transitions function relies on RNG for discovery of states)
|
|
413
|
+
const stats = try transitions(unfix(copy), c1, c2, allocator, writer, .{
|
|
414
|
+
.actions = actions,
|
|
415
|
+
.cap = true,
|
|
416
|
+
});
|
|
417
|
+
DEBUG(stats);
|
|
418
|
+
// TODO try expect(stats.frontier < pkmn.gen1.MAX_FRONTIER_SIZE);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Demonstrate that we can produce the same state by forcing the RNG to behave the
|
|
422
|
+
// same as we observed - note that because we do not pass in a durations override
|
|
423
|
+
// mask none of the durations will be extended.
|
|
424
|
+
var override = pkmn.battle.options(
|
|
425
|
+
protocol.NULL,
|
|
426
|
+
chance.NULL,
|
|
427
|
+
Calc{ .overrides = .{ .actions = actions } },
|
|
428
|
+
);
|
|
429
|
+
const overridden = copy.update(c1, c2, &override);
|
|
430
|
+
try expectEqual(result, overridden);
|
|
431
|
+
|
|
432
|
+
// The actual battle excluding its RNG field should match a copy updated with
|
|
433
|
+
// overridden RNG (the copy RNG may have advanced because of no-ops)
|
|
434
|
+
copy.rng = battle.rng;
|
|
435
|
+
try expectEqual(copy, battle.*);
|
|
436
|
+
|
|
437
|
+
return result;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
fn unfix(actual: anytype) data.Battle(data.PRNG) {
|
|
441
|
+
return .{
|
|
442
|
+
.sides = actual.sides,
|
|
443
|
+
.turn = actual.turn,
|
|
444
|
+
.last_damage = actual.last_damage,
|
|
445
|
+
.last_moves = actual.last_moves,
|
|
446
|
+
.rng = .{ .src = .{
|
|
447
|
+
.seed = if (pkmn.options.showdown)
|
|
448
|
+
0x12345678
|
|
449
|
+
else
|
|
450
|
+
.{ 123, 234, 56, 78, 9, 101, 112, 131, 4 },
|
|
451
|
+
} },
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
390
455
|
fn matches(actions: Actions, i: usize, frontier: []Actions) bool {
|
|
391
456
|
for (frontier, 0..) |f, j| {
|
|
392
457
|
// TODO: is skipping this redundant check worth it?
|
package/src/lib/gen1/chance.zig
CHANGED
|
@@ -14,7 +14,7 @@ const util = @import("../common/util.zig");
|
|
|
14
14
|
const Player = @import("../common/data.zig").Player;
|
|
15
15
|
const Optional = @import("../common/optional.zig").Optional;
|
|
16
16
|
|
|
17
|
-
const data = @import("
|
|
17
|
+
const data = @import("data.zig");
|
|
18
18
|
|
|
19
19
|
const enabled = options.chance;
|
|
20
20
|
const showdown = options.showdown;
|
|
@@ -92,13 +92,14 @@ pub const Actions = extern struct {
|
|
|
92
92
|
}
|
|
93
93
|
};
|
|
94
94
|
|
|
95
|
+
// TODO: ziglang/zig#19730
|
|
95
96
|
test Actions {
|
|
96
97
|
const a: Actions = .{ .p1 = .{ .hit = .true, .critical_hit = .false, .damage = 245 } };
|
|
97
98
|
const b: Actions = .{ .p1 = .{ .hit = .false, .critical_hit = .true, .damage = 246 } };
|
|
98
99
|
const c: Actions = .{ .p1 = .{ .hit = .true } };
|
|
99
|
-
const d: Actions = .{ .p2 = .{ .hit = .true, .durations = .{ .sleep = 2 } } };
|
|
100
|
-
const e: Actions = .{ .p2 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
|
|
101
|
-
const f: Actions = .{ .p1 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
|
|
100
|
+
// const d: Actions = .{ .p2 = .{ .hit = .true, .durations = .{ .sleep = 2 } } };
|
|
101
|
+
// const e: Actions = .{ .p2 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
|
|
102
|
+
// const f: Actions = .{ .p1 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
|
|
102
103
|
|
|
103
104
|
try expect(a.eql(a));
|
|
104
105
|
try expect(!a.eql(b));
|
|
@@ -111,8 +112,8 @@ test Actions {
|
|
|
111
112
|
try expect(b.matches(a));
|
|
112
113
|
try expect(!a.matches(c));
|
|
113
114
|
try expect(!c.matches(a));
|
|
114
|
-
try expect(d.matches(e));
|
|
115
|
-
try expect(!d.matches(f));
|
|
115
|
+
// try expect(d.matches(e));
|
|
116
|
+
// try expect(!d.matches(f));
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
/// Information about the RNG that was observed during a Generation I battle `update` for a
|
|
@@ -254,6 +255,8 @@ pub const Duration = packed struct(u16) {
|
|
|
254
255
|
|
|
255
256
|
pub const Commit = enum { hit, miss, binding };
|
|
256
257
|
|
|
258
|
+
const uN = if (options.miss) u8 else u9;
|
|
259
|
+
|
|
257
260
|
/// Tracks chance actions and their associated probability during a Generation I battle update when
|
|
258
261
|
/// `options.chance` is enabled.
|
|
259
262
|
pub fn Chance(comptime Rational: type) type {
|
|
@@ -292,13 +295,13 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
292
295
|
// shared structure to track this information (though it needs to be cleared appropriately)
|
|
293
296
|
pending: if (showdown) struct {
|
|
294
297
|
hit: bool = false,
|
|
295
|
-
hit_probablity:
|
|
298
|
+
hit_probablity: uN = 0,
|
|
296
299
|
} else struct {
|
|
297
300
|
crit: bool = false,
|
|
298
301
|
crit_probablity: u8 = 0,
|
|
299
302
|
damage_roll: u8 = 0,
|
|
300
303
|
hit: bool = false,
|
|
301
|
-
hit_probablity:
|
|
304
|
+
hit_probablity: uN = 0,
|
|
302
305
|
binding: u8 = 0,
|
|
303
306
|
} = .{},
|
|
304
307
|
|
|
@@ -397,7 +400,7 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
397
400
|
self.actions.p2.speed_tie = self.actions.p1.speed_tie;
|
|
398
401
|
}
|
|
399
402
|
|
|
400
|
-
pub fn hit(self: *Self, ok: bool, accuracy:
|
|
403
|
+
pub fn hit(self: *Self, ok: bool, accuracy: uN) void {
|
|
401
404
|
if (!enabled) return;
|
|
402
405
|
|
|
403
406
|
const p = if (ok) accuracy else @as(u8, @intCast(256 - @as(u9, accuracy)));
|
|
@@ -706,19 +709,19 @@ test "Chance.damage" {
|
|
|
706
709
|
|
|
707
710
|
try chance.damage(.P1, 217);
|
|
708
711
|
if (showdown) {
|
|
709
|
-
try expectValue(
|
|
712
|
+
try expectValue(217, chance.actions.p1.damage);
|
|
710
713
|
try expectProbability(&chance.probability, 1, 39);
|
|
711
714
|
} else {
|
|
712
715
|
try expectProbability(&chance.probability, 1, 1);
|
|
713
|
-
try expectValue(
|
|
716
|
+
try expectValue(0, chance.actions.p1.damage);
|
|
714
717
|
|
|
715
718
|
try chance.commit(.P1, .miss);
|
|
716
719
|
try expectProbability(&chance.probability, 1, 1);
|
|
717
|
-
try expectValue(
|
|
720
|
+
try expectValue(0, chance.actions.p1.damage);
|
|
718
721
|
|
|
719
722
|
try chance.commit(.P1, .hit);
|
|
720
723
|
try expectProbability(&chance.probability, 1, 39);
|
|
721
|
-
try expectValue(
|
|
724
|
+
try expectValue(217, chance.actions.p1.damage);
|
|
722
725
|
}
|
|
723
726
|
}
|
|
724
727
|
|
|
@@ -770,13 +773,13 @@ test "Chance.moveSlot" {
|
|
|
770
773
|
|
|
771
774
|
try chance.moveSlot(.P2, 2, &ms, 2);
|
|
772
775
|
try expectProbability(&chance.probability, 1, 2);
|
|
773
|
-
try expectValue(
|
|
776
|
+
try expectValue(2, chance.actions.p2.move_slot);
|
|
774
777
|
|
|
775
778
|
chance.reset();
|
|
776
779
|
|
|
777
780
|
try chance.moveSlot(.P1, 1, &ms, 0);
|
|
778
781
|
try expectProbability(&chance.probability, 1, 3);
|
|
779
|
-
try expectValue(
|
|
782
|
+
try expectValue(1, chance.actions.p1.move_slot);
|
|
780
783
|
}
|
|
781
784
|
|
|
782
785
|
test "Chance.multiHit" {
|
|
@@ -784,32 +787,32 @@ test "Chance.multiHit" {
|
|
|
784
787
|
|
|
785
788
|
try chance.multiHit(.P1, 3);
|
|
786
789
|
try expectProbability(&chance.probability, 3, 8);
|
|
787
|
-
try expectValue(
|
|
790
|
+
try expectValue(3, chance.actions.p1.multi_hit);
|
|
788
791
|
|
|
789
792
|
chance.reset();
|
|
790
793
|
|
|
791
794
|
try chance.multiHit(.P2, 5);
|
|
792
795
|
try expectProbability(&chance.probability, 1, 8);
|
|
793
|
-
try expectValue(
|
|
796
|
+
try expectValue(5, chance.actions.p2.multi_hit);
|
|
794
797
|
}
|
|
795
798
|
|
|
796
799
|
test "Chance.duration" {
|
|
797
800
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
798
801
|
|
|
799
802
|
chance.duration(.sleep, .P1, .P2, 2);
|
|
800
|
-
try expectValue(
|
|
801
|
-
try expectValue(
|
|
803
|
+
try expectValue(2, chance.actions.p1.duration);
|
|
804
|
+
try expectValue(1, chance.actions.p2.durations.sleep);
|
|
802
805
|
|
|
803
806
|
chance.reset();
|
|
804
807
|
|
|
805
808
|
chance.duration(.binding, .P2, .P2, 4);
|
|
806
809
|
if (!showdown) {
|
|
807
|
-
try expectValue(
|
|
808
|
-
try expectValue(
|
|
810
|
+
try expectValue(0, chance.actions.p2.duration);
|
|
811
|
+
try expectValue(0, chance.actions.p2.durations.binding);
|
|
809
812
|
try chance.commit(.P2, .hit);
|
|
810
813
|
}
|
|
811
|
-
try expectValue(
|
|
812
|
-
try expectValue(
|
|
814
|
+
try expectValue(4, chance.actions.p2.duration);
|
|
815
|
+
try expectValue(1, chance.actions.p2.durations.binding);
|
|
813
816
|
}
|
|
814
817
|
|
|
815
818
|
test "Chance.sleep" {
|
|
@@ -819,7 +822,7 @@ test "Chance.sleep" {
|
|
|
819
822
|
chance.actions.p1.durations.sleep = @intCast(i);
|
|
820
823
|
try chance.sleep(.P1, 0);
|
|
821
824
|
try expectProbability(&chance.probability, 1, d);
|
|
822
|
-
try expectValue(
|
|
825
|
+
try expectValue(0, chance.actions.p1.durations.sleep);
|
|
823
826
|
|
|
824
827
|
chance.reset();
|
|
825
828
|
|
|
@@ -842,7 +845,7 @@ test "Chance.confusion" {
|
|
|
842
845
|
chance.actions.p2.durations.confusion = @intCast(i);
|
|
843
846
|
try chance.confusion(.P2, 0);
|
|
844
847
|
try expectProbability(&chance.probability, 1, d);
|
|
845
|
-
try expectValue(
|
|
848
|
+
try expectValue(0, chance.actions.p2.durations.confusion);
|
|
846
849
|
|
|
847
850
|
chance.reset();
|
|
848
851
|
}
|
|
@@ -865,7 +868,7 @@ test "Chance.disable" {
|
|
|
865
868
|
chance.actions.p1.durations.disable = @intCast(i);
|
|
866
869
|
try chance.disable(.P1, 0);
|
|
867
870
|
try expectProbability(&chance.probability, 1, d);
|
|
868
|
-
try expectValue(
|
|
871
|
+
try expectValue(0, chance.actions.p1.durations.disable);
|
|
869
872
|
|
|
870
873
|
chance.reset();
|
|
871
874
|
|
|
@@ -888,7 +891,7 @@ test "Chance.attacking" {
|
|
|
888
891
|
chance.actions.p2.durations.attacking = @intCast(i);
|
|
889
892
|
try chance.attacking(.P2, 0);
|
|
890
893
|
try expectProbability(&chance.probability, 1, d);
|
|
891
|
-
try expectValue(
|
|
894
|
+
try expectValue(0, chance.actions.p2.durations.attacking);
|
|
892
895
|
|
|
893
896
|
chance.reset();
|
|
894
897
|
}
|
|
@@ -914,7 +917,7 @@ test "Chance.binding" {
|
|
|
914
917
|
chance.actions.p1.durations.binding = @intCast(i);
|
|
915
918
|
try chance.binding(.P1, 0);
|
|
916
919
|
try expectProbability(&chance.probability, p, q);
|
|
917
|
-
try expectValue(
|
|
920
|
+
try expectValue(0, chance.actions.p1.durations.binding);
|
|
918
921
|
|
|
919
922
|
chance.reset();
|
|
920
923
|
|
|
@@ -934,7 +937,7 @@ test "Chance.psywave" {
|
|
|
934
937
|
|
|
935
938
|
try chance.psywave(.P2, 100, 150);
|
|
936
939
|
try expectProbability(&chance.probability, 1, 150);
|
|
937
|
-
try expectValue(
|
|
940
|
+
try expectValue(101, chance.actions.p2.psywave);
|
|
938
941
|
}
|
|
939
942
|
|
|
940
943
|
test "Chance.metronome" {
|
|
@@ -988,7 +991,7 @@ const Null = struct {
|
|
|
988
991
|
_ = .{ self, p1 };
|
|
989
992
|
}
|
|
990
993
|
|
|
991
|
-
pub fn hit(self: Null, ok: bool, accuracy:
|
|
994
|
+
pub fn hit(self: Null, ok: bool, accuracy: uN) void {
|
|
992
995
|
_ = .{ self, ok, accuracy };
|
|
993
996
|
}
|
|
994
997
|
|
|
@@ -1091,7 +1091,7 @@ pub const Species = enum(u8) {
|
|
|
1091
1091
|
/// The number of Pokémon species in this generation.
|
|
1092
1092
|
pub const size = 151;
|
|
1093
1093
|
|
|
1094
|
-
/// The Pokémon's critical hit
|
|
1094
|
+
/// The Pokémon's critical hit ratio out of 256.
|
|
1095
1095
|
pub fn chance(id: Species) u8 {
|
|
1096
1096
|
assert(id != .None);
|
|
1097
1097
|
return CHANCES[@intFromEnum(id) - 1];
|
package/src/lib/gen1/data.zig
CHANGED
|
@@ -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
|
};
|
|
@@ -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
|
@@ -43,6 +43,8 @@ pub const Options = struct {
|
|
|
43
43
|
/// Whether to generate Pokémon with moves that contain bugs on Pokémon Showdown that are
|
|
44
44
|
/// unimplementable in the engine.
|
|
45
45
|
block: bool = showdown,
|
|
46
|
+
// FIXME: remove once durations work with -Dchance
|
|
47
|
+
durations: bool = false,
|
|
46
48
|
};
|
|
47
49
|
|
|
48
50
|
/// Helpers to simplify initialization of a Generation I battle.
|
|
@@ -86,18 +88,38 @@ pub const Battle = struct {
|
|
|
86
88
|
};
|
|
87
89
|
|
|
88
90
|
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
91
|
return .{
|
|
92
92
|
.src = .{
|
|
93
93
|
.seed = if (showdown)
|
|
94
94
|
rand.newSeed()
|
|
95
|
-
else
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
95
|
+
else seed: {
|
|
96
|
+
// GLITCH: The initial RNG seed in Gen I link battles is heavily constrained
|
|
97
|
+
// https://www.smogon.com/forums/threads/rng-in-rby-link-battles.3746779
|
|
98
|
+
var seed = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
99
|
+
|
|
100
|
+
// Bytes in seed can only range from 0-252, not 0-255
|
|
101
|
+
const max: u8 = 253;
|
|
102
|
+
// Instructions take multiples of 4 cycles to execute
|
|
103
|
+
var timer: u16 = 4 * rand.range(u16, 0, 16384);
|
|
104
|
+
var add: u8 = rand.range(u8, 0, 256);
|
|
105
|
+
var carry: u8 = 0;
|
|
106
|
+
|
|
107
|
+
var i: u8 = 0;
|
|
108
|
+
while (i < seed.len) {
|
|
109
|
+
const div: u8 = @intCast(timer >> 8);
|
|
110
|
+
add +%= div +% carry;
|
|
111
|
+
if (add < max) {
|
|
112
|
+
seed[i] = add;
|
|
113
|
+
i += 1;
|
|
114
|
+
timer +%= 472;
|
|
115
|
+
carry = 1;
|
|
116
|
+
} else {
|
|
117
|
+
timer +%= 452;
|
|
118
|
+
carry = 0;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
break :seed seed;
|
|
122
|
+
},
|
|
101
123
|
},
|
|
102
124
|
};
|
|
103
125
|
}
|
|
@@ -210,6 +232,7 @@ pub const Pokemon = struct {
|
|
|
210
232
|
var m: Move = .None;
|
|
211
233
|
sample: while (true) {
|
|
212
234
|
m = @enumFromInt(rand.range(u8, 1, Move.size - 1 + 1));
|
|
235
|
+
if (opt.durations and durations(m)) continue :sample;
|
|
213
236
|
if (opt.block and blocked(m)) continue :sample;
|
|
214
237
|
for (0..i) |j| if (ms[j].id == m) continue :sample;
|
|
215
238
|
break;
|
|
@@ -247,6 +270,23 @@ fn blocked(m: Move) bool {
|
|
|
247
270
|
};
|
|
248
271
|
}
|
|
249
272
|
|
|
273
|
+
fn durations(m: Move) bool {
|
|
274
|
+
// Any moves that have effects that result in duration tracking with -Dchance
|
|
275
|
+
return switch (Move.get(m).effect) {
|
|
276
|
+
.Confusion,
|
|
277
|
+
.Bide,
|
|
278
|
+
.Sleep,
|
|
279
|
+
.Rage,
|
|
280
|
+
.Binding,
|
|
281
|
+
.Thrashing,
|
|
282
|
+
.ConfusionChance,
|
|
283
|
+
.Disable,
|
|
284
|
+
.Metronome,
|
|
285
|
+
=> true,
|
|
286
|
+
else => false,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
250
290
|
/// Convenience helper to create a move-type choice for the provided move slot.
|
|
251
291
|
pub fn move(slot: u4) Choice {
|
|
252
292
|
return .{ .type = .Move, .data = slot };
|
|
@@ -478,12 +518,12 @@ test "Rolls.damage" {
|
|
|
478
518
|
test "Rolls.coalesce" {
|
|
479
519
|
var summaries =
|
|
480
520
|
calc.Summaries{ .p1 = .{ .damage = .{ .base = 74, .final = 69, .capped = true } } };
|
|
481
|
-
try expectEqual(
|
|
482
|
-
try expectEqual(
|
|
483
|
-
try expectEqual(
|
|
521
|
+
try expectEqual(0, try Rolls.coalesce(.P2, 0, &summaries, false));
|
|
522
|
+
try expectEqual(241, try Rolls.coalesce(.P2, 238, &summaries, false));
|
|
523
|
+
try expectEqual(255, try Rolls.coalesce(.P2, 238, &summaries, true));
|
|
484
524
|
summaries.p1.damage.final = 74;
|
|
485
|
-
try expectEqual(
|
|
486
|
-
try expectEqual(
|
|
525
|
+
try expectEqual(217, try Rolls.coalesce(.P2, 217, &summaries, false));
|
|
526
|
+
try expectEqual(255, try Rolls.coalesce(.P2, 217, &summaries, true));
|
|
487
527
|
}
|
|
488
528
|
|
|
489
529
|
test "Rolls.hit" {
|
|
@@ -771,9 +811,9 @@ test "Rolls.psywave" {
|
|
|
771
811
|
|
|
772
812
|
try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p1, &side, .None));
|
|
773
813
|
var rolls = Rolls.psywave(actions.p2, &side, .None);
|
|
774
|
-
try expectEqual(
|
|
814
|
+
try expectEqual(150, rolls[rolls.len - 1]);
|
|
775
815
|
side.stored().level = 81;
|
|
776
816
|
rolls = Rolls.psywave(actions.p2, &side, .None);
|
|
777
|
-
try expectEqual(
|
|
817
|
+
try expectEqual(121, rolls[rolls.len - 1]);
|
|
778
818
|
try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p2, &side, .false));
|
|
779
819
|
}
|