@pkmn/engine 0.1.0-dev.437f6a52 → 0.1.0-dev.46307385
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 +45 -37
- package/build.zig +46 -41
- package/package.json +23 -14
- package/src/bin/install-pkmn-engine +2 -2
- package/src/lib/common/battle.zig +17 -0
- package/src/lib/common/js.zig +118 -0
- package/src/lib/common/optional.zig +60 -0
- package/src/lib/common/protocol.zig +3 -0
- package/src/lib/common/rational.zig +353 -0
- package/src/lib/common/util.zig +12 -0
- package/src/lib/gen1/README.md +243 -165
- package/src/lib/gen1/calc.zig +427 -0
- package/src/lib/gen1/chance.zig +1044 -0
- package/src/lib/gen1/data.zig +15 -10
- package/src/lib/gen1/helpers.zig +228 -75
- package/src/lib/gen1/mechanics.zig +18 -36
- package/src/lib/gen2/README.md +12 -13
package/src/lib/gen1/data.zig
CHANGED
|
@@ -65,7 +65,7 @@ pub fn Battle(comptime RNG: anytype) type {
|
|
|
65
65
|
/// The last damage dealt by either side.
|
|
66
66
|
last_damage: u16 = 0,
|
|
67
67
|
/// Details about the last move selected/used by each player required to detect desyncs.
|
|
68
|
-
last_moves: MoveDetails = .{},
|
|
68
|
+
last_moves: [2]MoveDetails = [_]MoveDetails{.{}} ** 2,
|
|
69
69
|
/// The pseudo random number generator.
|
|
70
70
|
rng: RNG,
|
|
71
71
|
|
|
@@ -86,6 +86,15 @@ pub fn Battle(comptime RNG: anytype) type {
|
|
|
86
86
|
return player.ident(@intCast(self.side(player).order[0]));
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/// Returns the `MoveDetails` for the given `player`.
|
|
90
|
+
pub inline fn lastMove(
|
|
91
|
+
self: anytype,
|
|
92
|
+
player: Player,
|
|
93
|
+
) PointerType(@TypeOf(self), MoveDetails) {
|
|
94
|
+
assert(@typeInfo(@TypeOf(self)).Pointer.child == Self);
|
|
95
|
+
return &self.last_moves[@intFromEnum(player)];
|
|
96
|
+
}
|
|
97
|
+
|
|
89
98
|
/// Returns the result of applying Player 1's choice `c1` and Player 2's choice `c2` to the
|
|
90
99
|
/// battle, optionally writing protocol message logs to `options.log` if `pkmn.options.log`
|
|
91
100
|
/// is enabled.
|
|
@@ -219,17 +228,13 @@ pub const MoveSlot = extern struct {
|
|
|
219
228
|
|
|
220
229
|
/// Details required to detect desyncs based on the move last selected/executed by players.
|
|
221
230
|
pub const MoveDetails = packed struct {
|
|
222
|
-
/// The move slot index of the last move selected in the battle menu
|
|
223
|
-
|
|
224
|
-
/// Whether the last move executed
|
|
225
|
-
|
|
226
|
-
/// The move slot index of the last move selected in the battle menu by Player 2
|
|
227
|
-
p2_index: if (showdown) u8 else u4 = 0,
|
|
228
|
-
/// Whether the last move executed by Player 2 was counterable.
|
|
229
|
-
p2_counterable: if (showdown) u8 else u4 = 0,
|
|
231
|
+
/// The move slot index of the last move selected in the battle menu.
|
|
232
|
+
index: if (showdown) u8 else u4 = 0,
|
|
233
|
+
/// Whether the last move executed was counterable.
|
|
234
|
+
counterable: if (showdown) u8 else u4 = 0,
|
|
230
235
|
|
|
231
236
|
comptime {
|
|
232
|
-
assert(@sizeOf(MoveDetails) == if (showdown)
|
|
237
|
+
assert(@sizeOf(MoveDetails) == if (showdown) 2 else 1);
|
|
233
238
|
}
|
|
234
239
|
};
|
|
235
240
|
|
package/src/lib/gen1/helpers.zig
CHANGED
|
@@ -350,47 +350,65 @@ pub const Rolls = struct {
|
|
|
350
350
|
const U3_NONE = [_]u3{0};
|
|
351
351
|
const U3_FORCED = [_]u3{1};
|
|
352
352
|
|
|
353
|
-
/// Returns a slice with a range of values for sleep given the `action`
|
|
353
|
+
/// Returns a slice with a range of values for sleep given the `action` and `origin` states.
|
|
354
354
|
pub inline fn sleep(action: Action, origin: Action) []const u3 {
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
355
|
+
const a = @field(action.durations, "sleep");
|
|
356
|
+
const o = @field(origin.durations, "sleep");
|
|
357
|
+
if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
|
|
358
|
+
assert(a == 0 or a == o + 1);
|
|
359
|
+
return if (o >= 7) &U3_NONE else &[_]u3{ 0, o + 1 };
|
|
358
360
|
}
|
|
359
361
|
|
|
360
362
|
const DISABLE_NONE = [_]u4{0};
|
|
361
363
|
|
|
362
|
-
/// Returns a slice with a range of values for disable given the `action`
|
|
364
|
+
/// Returns a slice with a range of values for disable given the `action` and `origin` states
|
|
363
365
|
/// and the state of the `parent` (the player's Pokémon's remaining sleep turns).
|
|
364
366
|
pub inline fn disable(action: Action, origin: Action, parent: u4) []const u4 {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
367
|
+
if (parent > 0) return &DISABLE_NONE;
|
|
368
|
+
const a = @field(action.durations, "disable");
|
|
369
|
+
const o = @field(origin.durations, "disable");
|
|
370
|
+
if (o == a) return if (o == 0) &DISABLE_NONE else &[_]u4{o};
|
|
371
|
+
assert(a == 0 or a == o + 1);
|
|
372
|
+
return if (o >= 8) &DISABLE_NONE else &[_]u4{ 0, o + 1 };
|
|
368
373
|
}
|
|
369
374
|
|
|
370
|
-
/// Returns a slice with a range of values for confusion given the `action`
|
|
375
|
+
/// Returns a slice with a range of values for confusion given the `action` and `origin` states
|
|
371
376
|
/// and the state of the `parent` (the player's remaining sleep turns).
|
|
372
377
|
pub inline fn confusion(action: Action, origin: Action, parent: u4) []const u3 {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
return if (
|
|
378
|
+
if (parent > 0) return &U3_NONE;
|
|
379
|
+
const a = @field(action.durations, "confusion");
|
|
380
|
+
const o = @field(origin.durations, "confusion");
|
|
381
|
+
if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
|
|
382
|
+
assert(a == 0 or a == o + 1);
|
|
383
|
+
return if (o >= 5) &U3_NONE else if (o < 2)
|
|
384
|
+
if (a == 0) &U3_NONE else &U3_FORCED
|
|
385
|
+
else
|
|
386
|
+
&[_]u3{ 0, o + 1 };
|
|
377
387
|
}
|
|
378
388
|
|
|
379
|
-
/// Returns a slice with a range of values for attacking given the `action`
|
|
389
|
+
/// Returns a slice with a range of values for attacking given the `action` and `origin` states
|
|
380
390
|
/// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
|
|
381
391
|
pub inline fn attacking(action: Action, origin: Action, parent: Optional(bool)) []const u3 {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
return if (
|
|
392
|
+
if (parent == .true) return &U3_NONE;
|
|
393
|
+
const a = @field(action.durations, "attacking");
|
|
394
|
+
const o = @field(origin.durations, "attacking");
|
|
395
|
+
if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
|
|
396
|
+
assert(a == 0 or a == o + 1);
|
|
397
|
+
return if (o >= 3) &U3_NONE else if (o < 2)
|
|
398
|
+
if (a == 0) &U3_NONE else &U3_FORCED
|
|
399
|
+
else
|
|
400
|
+
&[_]u3{ 0, o + 1 };
|
|
386
401
|
}
|
|
387
402
|
|
|
388
|
-
/// Returns a slice with a range of values for binding given the `action`
|
|
403
|
+
/// Returns a slice with a range of values for binding given the `action` and `origin` states
|
|
389
404
|
/// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
|
|
390
405
|
pub inline fn binding(action: Action, origin: Action, parent: Optional(bool)) []const u3 {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
406
|
+
if (parent == .true) return &U3_NONE;
|
|
407
|
+
const a = @field(action.durations, "binding");
|
|
408
|
+
const o = @field(origin.durations, "binding");
|
|
409
|
+
if (o == a) return if (o == 0) &U3_NONE else &[_]u3{o};
|
|
410
|
+
assert(a == 0 or a == o + 1);
|
|
411
|
+
return if (o >= 4) &U3_NONE else &[_]u3{ 0, o + 1 };
|
|
394
412
|
}
|
|
395
413
|
|
|
396
414
|
const SLOT_NONE = [_]u4{0};
|
|
@@ -535,72 +553,207 @@ test "Rolls.duration" {
|
|
|
535
553
|
try expectEqualSlices(u1, &.{0}, Rolls.duration(actions.p2, .false));
|
|
536
554
|
}
|
|
537
555
|
|
|
538
|
-
const FIXME: Action = .{};
|
|
539
|
-
|
|
540
556
|
test "Rolls.sleep" {
|
|
541
|
-
try expectEqualSlices(u3, &.{0}, Rolls.sleep(
|
|
542
|
-
|
|
543
|
-
|
|
557
|
+
try expectEqualSlices(u3, &.{0}, Rolls.sleep(
|
|
558
|
+
.{ .durations = .{ .sleep = 0 } },
|
|
559
|
+
.{ .durations = .{ .sleep = 0 } },
|
|
560
|
+
));
|
|
561
|
+
try expectEqualSlices(u3, &.{ 0, 1 }, Rolls.sleep(
|
|
562
|
+
.{ .durations = .{ .sleep = 1 } },
|
|
563
|
+
.{ .durations = .{ .sleep = 0 } },
|
|
564
|
+
));
|
|
565
|
+
try expectEqualSlices(u3, &.{ 0, 2 }, Rolls.sleep(
|
|
566
|
+
.{ .durations = .{ .sleep = 0 } },
|
|
567
|
+
.{ .durations = .{ .sleep = 1 } },
|
|
568
|
+
));
|
|
569
|
+
try expectEqualSlices(u3, &.{1}, Rolls.sleep(
|
|
570
|
+
.{ .durations = .{ .sleep = 1 } },
|
|
571
|
+
.{ .durations = .{ .sleep = 1 } },
|
|
572
|
+
));
|
|
573
|
+
try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.sleep(
|
|
574
|
+
.{ .durations = .{ .sleep = 0 } },
|
|
575
|
+
.{ .durations = .{ .sleep = 2 } },
|
|
576
|
+
));
|
|
577
|
+
try expectEqualSlices(u3, &.{ 0, 7 }, Rolls.sleep(
|
|
578
|
+
.{ .durations = .{ .sleep = 7 } },
|
|
579
|
+
.{ .durations = .{ .sleep = 6 } },
|
|
580
|
+
));
|
|
581
|
+
try expectEqualSlices(u3, &.{0}, Rolls.sleep(
|
|
582
|
+
.{ .durations = .{ .sleep = 0 } },
|
|
583
|
+
.{ .durations = .{ .sleep = 7 } },
|
|
584
|
+
));
|
|
544
585
|
}
|
|
545
586
|
|
|
546
587
|
test "Rolls.disable" {
|
|
547
|
-
try expectEqualSlices(u4, &.{0}, Rolls.disable(
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
588
|
+
try expectEqualSlices(u4, &.{0}, Rolls.disable(
|
|
589
|
+
.{ .durations = .{ .disable = 0 } },
|
|
590
|
+
.{ .durations = .{ .disable = 0 } },
|
|
591
|
+
0,
|
|
592
|
+
));
|
|
593
|
+
try expectEqualSlices(u4, &.{ 0, 1 }, Rolls.disable(
|
|
594
|
+
.{ .durations = .{ .disable = 1 } },
|
|
595
|
+
.{ .durations = .{ .disable = 0 } },
|
|
596
|
+
0,
|
|
597
|
+
));
|
|
598
|
+
try expectEqualSlices(u4, &.{ 0, 2 }, Rolls.disable(
|
|
599
|
+
.{ .durations = .{ .disable = 0 } },
|
|
600
|
+
.{ .durations = .{ .disable = 1 } },
|
|
601
|
+
0,
|
|
602
|
+
));
|
|
603
|
+
try expectEqualSlices(u4, &.{1}, Rolls.disable(
|
|
604
|
+
.{ .durations = .{ .disable = 1 } },
|
|
605
|
+
.{ .durations = .{ .disable = 1 } },
|
|
606
|
+
0,
|
|
607
|
+
));
|
|
608
|
+
try expectEqualSlices(u4, &.{ 0, 3 }, Rolls.disable(
|
|
609
|
+
.{ .durations = .{ .disable = 0 } },
|
|
610
|
+
.{ .durations = .{ .disable = 2 } },
|
|
611
|
+
0,
|
|
612
|
+
));
|
|
613
|
+
try expectEqualSlices(u4, &.{0}, Rolls.disable(
|
|
614
|
+
.{ .durations = .{ .disable = 0 } },
|
|
615
|
+
.{ .durations = .{ .disable = 2 } },
|
|
616
|
+
1,
|
|
617
|
+
));
|
|
618
|
+
try expectEqualSlices(u4, &.{ 0, 8 }, Rolls.disable(
|
|
619
|
+
.{ .durations = .{ .disable = 8 } },
|
|
620
|
+
.{ .durations = .{ .disable = 7 } },
|
|
621
|
+
0,
|
|
622
|
+
));
|
|
623
|
+
try expectEqualSlices(u4, &.{0}, Rolls.disable(
|
|
624
|
+
.{ .durations = .{ .disable = 0 } },
|
|
625
|
+
.{ .durations = .{ .disable = 8 } },
|
|
626
|
+
0,
|
|
627
|
+
));
|
|
553
628
|
}
|
|
554
629
|
|
|
555
630
|
test "Rolls.confusion" {
|
|
556
|
-
try expectEqualSlices(u3, &.{0}, Rolls.confusion(
|
|
557
|
-
.durations = .{ .confusion = 0 },
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
.durations = .{ .confusion =
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
631
|
+
try expectEqualSlices(u3, &.{0}, Rolls.confusion(
|
|
632
|
+
.{ .durations = .{ .confusion = 0 } },
|
|
633
|
+
.{ .durations = .{ .confusion = 0 } },
|
|
634
|
+
0,
|
|
635
|
+
));
|
|
636
|
+
try expectEqualSlices(u3, &.{1}, Rolls.confusion(
|
|
637
|
+
.{ .durations = .{ .confusion = 1 } },
|
|
638
|
+
.{ .durations = .{ .confusion = 0 } },
|
|
639
|
+
0,
|
|
640
|
+
));
|
|
641
|
+
try expectEqualSlices(u3, &.{0}, Rolls.confusion(
|
|
642
|
+
.{ .durations = .{ .confusion = 0 } },
|
|
643
|
+
.{ .durations = .{ .confusion = 1 } },
|
|
644
|
+
0,
|
|
645
|
+
));
|
|
646
|
+
try expectEqualSlices(u3, &.{1}, Rolls.confusion(
|
|
647
|
+
.{ .durations = .{ .confusion = 1 } },
|
|
648
|
+
.{ .durations = .{ .confusion = 1 } },
|
|
649
|
+
0,
|
|
650
|
+
));
|
|
651
|
+
try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.confusion(
|
|
652
|
+
.{ .durations = .{ .confusion = 0 } },
|
|
653
|
+
.{ .durations = .{ .confusion = 2 } },
|
|
654
|
+
0,
|
|
655
|
+
));
|
|
656
|
+
try expectEqualSlices(u3, &.{0}, Rolls.confusion(
|
|
657
|
+
.{ .durations = .{ .confusion = 0 } },
|
|
658
|
+
.{ .durations = .{ .confusion = 2 } },
|
|
659
|
+
1,
|
|
660
|
+
));
|
|
661
|
+
try expectEqualSlices(u3, &.{ 0, 5 }, Rolls.confusion(
|
|
662
|
+
.{ .durations = .{ .confusion = 5 } },
|
|
663
|
+
.{ .durations = .{ .confusion = 4 } },
|
|
664
|
+
0,
|
|
665
|
+
));
|
|
666
|
+
try expectEqualSlices(u3, &.{0}, Rolls.confusion(
|
|
667
|
+
.{ .durations = .{ .confusion = 0 } },
|
|
668
|
+
.{ .durations = .{ .confusion = 5 } },
|
|
669
|
+
0,
|
|
670
|
+
));
|
|
571
671
|
}
|
|
572
672
|
|
|
573
673
|
test "Rolls.attacking" {
|
|
574
|
-
try expectEqualSlices(u3, &.{0}, Rolls.attacking(
|
|
575
|
-
.durations = .{ .attacking = 0 },
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
.durations = .{ .attacking =
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
.
|
|
588
|
-
|
|
674
|
+
try expectEqualSlices(u3, &.{0}, Rolls.attacking(
|
|
675
|
+
.{ .durations = .{ .attacking = 0 } },
|
|
676
|
+
.{ .durations = .{ .attacking = 0 } },
|
|
677
|
+
.false,
|
|
678
|
+
));
|
|
679
|
+
try expectEqualSlices(u3, &.{1}, Rolls.attacking(
|
|
680
|
+
.{ .durations = .{ .attacking = 1 } },
|
|
681
|
+
.{ .durations = .{ .attacking = 0 } },
|
|
682
|
+
.false,
|
|
683
|
+
));
|
|
684
|
+
try expectEqualSlices(u3, &.{0}, Rolls.attacking(
|
|
685
|
+
.{ .durations = .{ .attacking = 0 } },
|
|
686
|
+
.{ .durations = .{ .attacking = 1 } },
|
|
687
|
+
.false,
|
|
688
|
+
));
|
|
689
|
+
try expectEqualSlices(u3, &.{1}, Rolls.attacking(
|
|
690
|
+
.{ .durations = .{ .attacking = 1 } },
|
|
691
|
+
.{ .durations = .{ .attacking = 1 } },
|
|
692
|
+
.false,
|
|
693
|
+
));
|
|
694
|
+
try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.attacking(
|
|
695
|
+
.{ .durations = .{ .attacking = 0 } },
|
|
696
|
+
.{ .durations = .{ .attacking = 2 } },
|
|
697
|
+
.false,
|
|
698
|
+
));
|
|
699
|
+
try expectEqualSlices(u3, &.{0}, Rolls.attacking(
|
|
700
|
+
.{ .durations = .{ .attacking = 0 } },
|
|
701
|
+
.{ .durations = .{ .attacking = 2 } },
|
|
702
|
+
.true,
|
|
703
|
+
));
|
|
704
|
+
try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.attacking(
|
|
705
|
+
.{ .durations = .{ .attacking = 3 } },
|
|
706
|
+
.{ .durations = .{ .attacking = 2 } },
|
|
707
|
+
.false,
|
|
708
|
+
));
|
|
709
|
+
try expectEqualSlices(u3, &.{0}, Rolls.attacking(
|
|
710
|
+
.{ .durations = .{ .attacking = 0 } },
|
|
711
|
+
.{ .durations = .{ .attacking = 3 } },
|
|
712
|
+
.false,
|
|
713
|
+
));
|
|
589
714
|
}
|
|
590
715
|
|
|
591
716
|
test "Rolls.binding" {
|
|
592
|
-
try expectEqualSlices(u3, &.{0}, Rolls.binding(
|
|
593
|
-
.durations = .{ .binding = 0 },
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
.durations = .{ .binding =
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
717
|
+
try expectEqualSlices(u3, &.{0}, Rolls.binding(
|
|
718
|
+
.{ .durations = .{ .binding = 0 } },
|
|
719
|
+
.{ .durations = .{ .binding = 0 } },
|
|
720
|
+
.false,
|
|
721
|
+
));
|
|
722
|
+
try expectEqualSlices(u3, &.{ 0, 1 }, Rolls.binding(
|
|
723
|
+
.{ .durations = .{ .binding = 1 } },
|
|
724
|
+
.{ .durations = .{ .binding = 0 } },
|
|
725
|
+
.false,
|
|
726
|
+
));
|
|
727
|
+
try expectEqualSlices(u3, &.{ 0, 2 }, Rolls.binding(
|
|
728
|
+
.{ .durations = .{ .binding = 0 } },
|
|
729
|
+
.{ .durations = .{ .binding = 1 } },
|
|
730
|
+
.false,
|
|
731
|
+
));
|
|
732
|
+
try expectEqualSlices(u3, &.{1}, Rolls.binding(
|
|
733
|
+
.{ .durations = .{ .binding = 1 } },
|
|
734
|
+
.{ .durations = .{ .binding = 1 } },
|
|
735
|
+
.false,
|
|
736
|
+
));
|
|
737
|
+
try expectEqualSlices(u3, &.{ 0, 3 }, Rolls.binding(
|
|
738
|
+
.{ .durations = .{ .binding = 0 } },
|
|
739
|
+
.{ .durations = .{ .binding = 2 } },
|
|
740
|
+
.false,
|
|
741
|
+
));
|
|
742
|
+
try expectEqualSlices(u3, &.{0}, Rolls.binding(
|
|
743
|
+
.{ .durations = .{ .binding = 0 } },
|
|
744
|
+
.{ .durations = .{ .binding = 2 } },
|
|
745
|
+
.true,
|
|
746
|
+
));
|
|
747
|
+
try expectEqualSlices(u3, &.{ 0, 4 }, Rolls.binding(
|
|
748
|
+
.{ .durations = .{ .binding = 4 } },
|
|
749
|
+
.{ .durations = .{ .binding = 3 } },
|
|
750
|
+
.false,
|
|
751
|
+
));
|
|
752
|
+
try expectEqualSlices(u3, &.{0}, Rolls.binding(
|
|
753
|
+
.{ .durations = .{ .binding = 0 } },
|
|
754
|
+
.{ .durations = .{ .binding = 4 } },
|
|
755
|
+
.false,
|
|
756
|
+
));
|
|
604
757
|
}
|
|
605
758
|
|
|
606
759
|
test "Rolls.moveSlot" {
|
|
@@ -186,10 +186,7 @@ fn selectMove(
|
|
|
186
186
|
assert(choice.data == 0);
|
|
187
187
|
// GLITCH: https://glitchcity.wiki/Partial_trapping_move_Mirror_Move_link_battle_glitch
|
|
188
188
|
if (foe_choice.type == .Switch) {
|
|
189
|
-
const last = side.active.move(
|
|
190
|
-
battle.last_moves.p1_index
|
|
191
|
-
else
|
|
192
|
-
battle.last_moves.p2_index);
|
|
189
|
+
const last = side.active.move(battle.lastMove(player).index);
|
|
193
190
|
if (last.id == .Metronome) side.last_selected_move = last.id;
|
|
194
191
|
if (last.id == .MirrorMove) return Result.Error;
|
|
195
192
|
}
|
|
@@ -242,11 +239,7 @@ fn saveMove(battle: anytype, player: Player, choice: ?Choice) void {
|
|
|
242
239
|
assert(showdown or move.pp != 0);
|
|
243
240
|
|
|
244
241
|
side.last_selected_move = move.id;
|
|
245
|
-
|
|
246
|
-
battle.last_moves.p1_index = @intCast(c.data);
|
|
247
|
-
} else {
|
|
248
|
-
battle.last_moves.p2_index = @intCast(c.data);
|
|
249
|
-
}
|
|
242
|
+
battle.lastMove(player).index = @intCast(c.data);
|
|
250
243
|
}
|
|
251
244
|
}
|
|
252
245
|
}
|
|
@@ -264,11 +257,7 @@ fn switchIn(battle: anytype, player: Player, slot: u8, initial: bool, options: a
|
|
|
264
257
|
side.order[0] = side.order[slot - 1];
|
|
265
258
|
side.order[slot - 1] = out;
|
|
266
259
|
|
|
267
|
-
|
|
268
|
-
battle.last_moves.p1_index = 1;
|
|
269
|
-
} else {
|
|
270
|
-
battle.last_moves.p2_index = 1;
|
|
271
|
-
}
|
|
260
|
+
battle.lastMove(player).index = 1;
|
|
272
261
|
|
|
273
262
|
side.last_used_move = .None;
|
|
274
263
|
foe.last_used_move = .None;
|
|
@@ -455,10 +444,7 @@ fn executeMove(
|
|
|
455
444
|
if (mslot == 0 and side.last_selected_move != .None and side.last_selected_move != .Struggle) {
|
|
456
445
|
// choice.data == 0 only happens with Struggle on Pokémon Showdown
|
|
457
446
|
assert(!showdown);
|
|
458
|
-
mslot = @intCast(
|
|
459
|
-
battle.last_moves.p1_index
|
|
460
|
-
else
|
|
461
|
-
battle.last_moves.p2_index);
|
|
447
|
+
mslot = @intCast(battle.lastMove(player).index);
|
|
462
448
|
const stored = side.stored();
|
|
463
449
|
// GLITCH: Struggle bypass PP underflow via Hyper Beam / Trapping-switch auto selection
|
|
464
450
|
auto = side.last_selected_move == .HyperBeam or side.active.volatiles.Bide or
|
|
@@ -479,10 +465,7 @@ fn executeMove(
|
|
|
479
465
|
} else if (showdown and side.active.volatiles.Charging) {
|
|
480
466
|
// Incorrect mslot with Pokémon Showdown choice semantics so we need to recover from index
|
|
481
467
|
assert(mslot == 1);
|
|
482
|
-
mslot = @intCast(
|
|
483
|
-
battle.last_moves.p1_index
|
|
484
|
-
else
|
|
485
|
-
battle.last_moves.p2_index);
|
|
468
|
+
mslot = @intCast(battle.lastMove(player).index);
|
|
486
469
|
}
|
|
487
470
|
|
|
488
471
|
var skip_can = false;
|
|
@@ -834,11 +817,7 @@ fn setCounterable(battle: anytype, player: Player, side: *Side, move: Move.Data)
|
|
|
834
817
|
// as the desync results from actually having two separate battle states that subtly disagree
|
|
835
818
|
const counterable = side.last_selected_move != .Counter and move.bp > 0 and
|
|
836
819
|
(move.type == .Normal or move.type == .Fighting);
|
|
837
|
-
|
|
838
|
-
battle.last_moves.p1_counterable = @intFromBool(counterable);
|
|
839
|
-
} else {
|
|
840
|
-
battle.last_moves.p2_counterable = @intFromBool(counterable);
|
|
841
|
-
}
|
|
820
|
+
battle.lastMove(player).counterable = @intFromBool(counterable);
|
|
842
821
|
}
|
|
843
822
|
|
|
844
823
|
fn decrementPP(side: *Side, mslot: u4, auto: bool) void {
|
|
@@ -1278,11 +1257,7 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1278
1257
|
else
|
|
1279
1258
|
foe.last_selected_move);
|
|
1280
1259
|
|
|
1281
|
-
const used = (
|
|
1282
|
-
battle.last_moves.p1_counterable
|
|
1283
|
-
else
|
|
1284
|
-
battle.last_moves.p2_counterable) != 0;
|
|
1285
|
-
|
|
1260
|
+
const used = battle.lastMove(player.foe()).counterable != 0;
|
|
1286
1261
|
const selected = foe_last_selected_move.bp > 0 and
|
|
1287
1262
|
foe.last_selected_move != .Counter and
|
|
1288
1263
|
(foe_last_selected_move.type == .Normal or
|
|
@@ -1357,7 +1332,7 @@ fn mirrorMove(
|
|
|
1357
1332
|
auto: bool,
|
|
1358
1333
|
residual: *bool,
|
|
1359
1334
|
options: anytype,
|
|
1360
|
-
) !?Result {
|
|
1335
|
+
) anyerror!?Result {
|
|
1361
1336
|
var side = battle.side(player);
|
|
1362
1337
|
const foe = battle.foe(player);
|
|
1363
1338
|
|
|
@@ -1384,7 +1359,7 @@ fn metronome(
|
|
|
1384
1359
|
auto: bool,
|
|
1385
1360
|
residual: *bool,
|
|
1386
1361
|
options: anytype,
|
|
1387
|
-
) !?Result {
|
|
1362
|
+
) anyerror!?Result {
|
|
1388
1363
|
var side = battle.side(player);
|
|
1389
1364
|
|
|
1390
1365
|
side.last_selected_move = try Rolls.metronome(battle, player, options);
|
|
@@ -2705,7 +2680,7 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
2705
2680
|
}
|
|
2706
2681
|
}
|
|
2707
2682
|
|
|
2708
|
-
inline fn isForced(active:
|
|
2683
|
+
inline fn isForced(active: anytype) bool {
|
|
2709
2684
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
2710
2685
|
active.volatiles.Thrashing or active.volatiles.Charging;
|
|
2711
2686
|
}
|
|
@@ -2796,6 +2771,7 @@ pub const Rolls = struct {
|
|
|
2796
2771
|
}
|
|
2797
2772
|
};
|
|
2798
2773
|
|
|
2774
|
+
assert(roll >= 217 and roll <= 255);
|
|
2799
2775
|
try options.chance.damage(player, roll);
|
|
2800
2776
|
return roll;
|
|
2801
2777
|
}
|
|
@@ -2920,6 +2896,7 @@ pub const Rolls = struct {
|
|
|
2920
2896
|
}
|
|
2921
2897
|
};
|
|
2922
2898
|
|
|
2899
|
+
assert(power < max);
|
|
2923
2900
|
try options.chance.psywave(player, power, max);
|
|
2924
2901
|
return power;
|
|
2925
2902
|
}
|
|
@@ -2936,6 +2913,7 @@ pub const Rolls = struct {
|
|
|
2936
2913
|
}
|
|
2937
2914
|
};
|
|
2938
2915
|
|
|
2916
|
+
assert(duration >= 1 and duration <= 7);
|
|
2939
2917
|
options.chance.duration(.sleep, player, player.foe(), duration);
|
|
2940
2918
|
return duration;
|
|
2941
2919
|
}
|
|
@@ -2948,6 +2926,7 @@ pub const Rolls = struct {
|
|
|
2948
2926
|
else
|
|
2949
2927
|
@intCast((battle.rng.next() & 7) + 1);
|
|
2950
2928
|
|
|
2929
|
+
assert(duration >= 1 and duration <= 8);
|
|
2951
2930
|
options.chance.duration(.disable, player, player.foe(), duration);
|
|
2952
2931
|
return duration;
|
|
2953
2932
|
}
|
|
@@ -2960,6 +2939,7 @@ pub const Rolls = struct {
|
|
|
2960
2939
|
else
|
|
2961
2940
|
@intCast((battle.rng.next() & 3) + 2);
|
|
2962
2941
|
|
|
2942
|
+
assert(duration >= 2 and duration <= 5);
|
|
2963
2943
|
options.chance.duration(.confusion, player, if (self) player else player.foe(), duration);
|
|
2964
2944
|
return duration;
|
|
2965
2945
|
}
|
|
@@ -2972,6 +2952,7 @@ pub const Rolls = struct {
|
|
|
2972
2952
|
else
|
|
2973
2953
|
@intCast((battle.rng.next() & 1) + 2);
|
|
2974
2954
|
|
|
2955
|
+
assert(duration >= 2 and duration <= 3);
|
|
2975
2956
|
options.chance.duration(.attacking, player, player, duration);
|
|
2976
2957
|
return duration;
|
|
2977
2958
|
}
|
|
@@ -2992,6 +2973,7 @@ pub const Rolls = struct {
|
|
|
2992
2973
|
break :n @intCast((if (r < 2) r else battle.rng.next() & 3) + 2);
|
|
2993
2974
|
};
|
|
2994
2975
|
|
|
2976
|
+
assert(n >= 2 and n <= 5);
|
|
2995
2977
|
if (effect == .Binding) {
|
|
2996
2978
|
options.chance.duration(.binding, player, player, n);
|
|
2997
2979
|
} else {
|
|
@@ -3097,7 +3079,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3097
3079
|
const side = battle.side(player);
|
|
3098
3080
|
const foe = battle.foe(player);
|
|
3099
3081
|
|
|
3100
|
-
var active = side.active;
|
|
3082
|
+
var active = &side.active;
|
|
3101
3083
|
const stored = side.stored();
|
|
3102
3084
|
|
|
3103
3085
|
// While players are not given any input options on the cartridge in these cases,
|
package/src/lib/gen2/README.md
CHANGED
|
@@ -54,13 +54,12 @@ The following information is required to simulate a Generation II Pokémon battl
|
|
|
54
54
|
| | | |
|
|
55
55
|
| `active.last_used_move` | `PlayerUsedMove` | `side.lastMove` |
|
|
56
56
|
|
|
57
|
-
- Pokémon Showdown
|
|
57
|
+
- Pokémon Showdown doesn't implement the correct Generation II RNG and as such its `seed` is
|
|
58
58
|
different
|
|
59
|
-
- `battle.turn` only needs to be tracked
|
|
60
|
-
protocol
|
|
59
|
+
- `battle.turn` only needs to be tracked to be compatible with the Pokémon Showdown protocol
|
|
61
60
|
- Battle results (win, lose, draw) or request states are communicated via the return code of
|
|
62
61
|
`Battle.update`
|
|
63
|
-
- Nicknames (`BattleMonNick`/`pokemon.name`)
|
|
62
|
+
- Nicknames (`BattleMonNick`/`pokemon.name`) aren't handled by the pkmn engine as they're expected
|
|
64
63
|
to be handled by driver code if required
|
|
65
64
|
|
|
66
65
|
==**TODO**==
|
|
@@ -77,9 +76,9 @@ between the data structure for the "active" Pokémon and its party members (see
|
|
|
77
76
|
|
|
78
77
|
### `Pokemon` / `ActivePokemon`
|
|
79
78
|
|
|
80
|
-
Similar to the cartridge,
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
Similar to the cartridge, to save space different information is stored depending on whether a
|
|
80
|
+
[Pokémon](https://pkmn.cc/bulba/Pok%c3%a9mon_data_structure_%28Generation_II%29) is actively
|
|
81
|
+
participating in battle vs. is switched out (pret's [`battle_struct` vs.
|
|
83
82
|
`party_struct`](https://pkmn.cc/pokecrystal/macros/ram.asm)). In Pokémon Showdown, all the Pokemon
|
|
84
83
|
are represented by the same
|
|
85
84
|
[`Pokemon`](https://github.com/smogon/pokemon-showdown/blob/master/sim/pokemon.ts) class, and static
|
|
@@ -93,14 +92,14 @@ A `MoveSlot` is a data-type for a `(move, current pp)` pair. A pared down versio
|
|
|
93
92
|
Showdown's `Pokemon#moveSlot`, it also stores data from the cartridge's `battle_struct::Move` macro
|
|
94
93
|
and can be used to replace the `PlayerMove*` data. Move PP is stored as a full byte instead of how
|
|
95
94
|
the cartridge (`battle_struct::PP`) stores it (6 bits for current PP and the remaining 2 bits used
|
|
96
|
-
to store the number of applied PP Ups). PP Up bits
|
|
95
|
+
to store the number of applied PP Ups). PP Up bits don't actually need to be stored on move slot
|
|
97
96
|
as max PP is never relevant in Generation II (the Mystery Berry always increases PP from 0 -> 5).
|
|
98
97
|
|
|
99
98
|
#### `Status`
|
|
100
99
|
|
|
101
100
|
Bitfield representation of a Pokémon's major [status
|
|
102
|
-
condition](https://pkmn.cc/bulba/Status_condition), mirroring how it
|
|
103
|
-
value of `0x00` means that the Pokémon
|
|
101
|
+
condition](https://pkmn.cc/bulba/Status_condition), mirroring how it's stored on the cartridge. A
|
|
102
|
+
value of `0x00` means that the Pokémon isn't affected by any major status, otherwise the lower 3
|
|
104
103
|
bits represent the remaining duration for Sleep. Other status are denoted by the presence of
|
|
105
104
|
individual bits - at most one status should be set at any given time.
|
|
106
105
|
|
|
@@ -161,7 +160,7 @@ boosts should always range from `-6`...`6` instead of `1`...`13` as on the cartr
|
|
|
161
160
|
|
|
162
161
|
`Moves` serves as an identifier for a unique [Pokémon move](https://pkmn.cc/bulba/Move) that can be
|
|
163
162
|
used to retrieve a `Move` with information regarding base power, accuracy and type. As covered
|
|
164
|
-
|
|
163
|
+
earlier, PP information isn't strictly necessary in Generation I, but fits neatly into the 4 bits of
|
|
165
164
|
padding after noticing that all PP values are multiples of 5. `Moves.None` exists as a special
|
|
166
165
|
sentinel value to indicate `null`.
|
|
167
166
|
|
|
@@ -215,8 +214,8 @@ entropy](https://en.wikipedia.org/wiki/Entropy_(information_theory))) is as foll
|
|
|
215
214
|
| **wrap** | 0...5 | 3 | | **perish song** | 0...4 | 3 |
|
|
216
215
|
| **item** | TODO | 5? | | **gender** | 0..1 | 1 |
|
|
217
216
|
|
|
218
|
-
From this
|
|
219
|
-
much overhead the representations
|
|
217
|
+
From this one can determine the minimum bits required to store each data structure to determine how
|
|
218
|
+
much overhead the preceding representations have after taking into consideration [alignment &
|
|
220
219
|
padding](https://en.wikipedia.org/wiki/Data_structure_alignment) and
|
|
221
220
|
[denormalization](https://en.wikipedia.org/wiki/Denormalization):
|
|
222
221
|
|