@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
|
@@ -22,6 +22,7 @@ const Result = common.Result;
|
|
|
22
22
|
|
|
23
23
|
const Boost = protocol.Boost;
|
|
24
24
|
const Damage = protocol.Damage;
|
|
25
|
+
const Fail = protocol.Fail;
|
|
25
26
|
const Heal = protocol.Heal;
|
|
26
27
|
|
|
27
28
|
const Gen12 = rng.Gen12;
|
|
@@ -70,8 +71,8 @@ pub fn update(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Result
|
|
|
70
71
|
var p1 = battle.side(.P1);
|
|
71
72
|
var p2 = battle.side(.P2);
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
const r1 = showdown and p1.active.volatiles.Binding and c2.type == .Switch;
|
|
75
|
+
const r2 = showdown and p2.active.volatiles.Binding and c1.type == .Switch;
|
|
75
76
|
|
|
76
77
|
if (try turnOrder(battle, c1, c2, options) == .P1) {
|
|
77
78
|
if (try doTurn(battle, .P1, c1, r1, s1, .P2, c2, r2, s2, options)) |r| return r;
|
|
@@ -89,11 +90,11 @@ fn start(battle: anytype, options: anytype) !Result {
|
|
|
89
90
|
const p1 = battle.side(.P1);
|
|
90
91
|
const p2 = battle.side(.P2);
|
|
91
92
|
|
|
92
|
-
|
|
93
|
+
const p1_slot = findFirstAlive(p1);
|
|
93
94
|
assert(!showdown or p1_slot == 1);
|
|
94
95
|
if (p1_slot == 0) return if (findFirstAlive(p2) == 0) Result.Tie else Result.Lose;
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
const p2_slot = findFirstAlive(p2);
|
|
97
98
|
assert(!showdown or p2_slot == 1);
|
|
98
99
|
if (p2_slot == 0) return Result.Win;
|
|
99
100
|
|
|
@@ -165,7 +166,7 @@ fn selectMove(
|
|
|
165
166
|
// to satisfy the conditions of the Desync Clause Mod. However, because Binding is still
|
|
166
167
|
// set the selected move will not actually be used, it will just be reported as having
|
|
167
168
|
// been used (this differs from how Pokémon Showdown works, but its impossible to
|
|
168
|
-
// replicate the incorrect behavior with the correct mechanisms)
|
|
169
|
+
// replicate the incorrect behavior with the correct mechanisms)
|
|
169
170
|
saveMove(battle, player, choice);
|
|
170
171
|
} else {
|
|
171
172
|
assert(choice.data == 0);
|
|
@@ -255,14 +256,14 @@ fn switchIn(battle: anytype, player: Player, slot: u8, initial: bool, options: a
|
|
|
255
256
|
|
|
256
257
|
foe.active.volatiles.Binding = false;
|
|
257
258
|
|
|
258
|
-
try options.log.switched(battle.active(player), incoming);
|
|
259
|
+
try options.log.switched(.{ battle.active(player), incoming });
|
|
259
260
|
options.chance.switched(player, side.order[0], out);
|
|
260
261
|
|
|
261
262
|
if (showdown and incoming.status == Status.TOX) {
|
|
262
263
|
incoming.status = Status.init(.PSN);
|
|
263
264
|
// Technically, Pokémon Showdown adds these after *both* Pokémon have switched, but we'd
|
|
264
265
|
// rather not clutter up turnOrder just for this (incorrect) log message
|
|
265
|
-
try options.log.status(battle.active(player), incoming.status, .Silent);
|
|
266
|
+
try options.log.status(.{ battle.active(player), incoming.status, .Silent });
|
|
266
267
|
}
|
|
267
268
|
}
|
|
268
269
|
|
|
@@ -406,7 +407,7 @@ fn executeMove(
|
|
|
406
407
|
if (side.last_selected_move == .SKIP_TURN) {
|
|
407
408
|
assert(!showdown);
|
|
408
409
|
if (battle.foe(player).active.volatiles.Binding) {
|
|
409
|
-
try options.log.cant(battle.active(player), .Bound);
|
|
410
|
+
try options.log.cant(.{ battle.active(player), .Bound });
|
|
410
411
|
}
|
|
411
412
|
return null;
|
|
412
413
|
}
|
|
@@ -430,7 +431,7 @@ fn executeMove(
|
|
|
430
431
|
Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP);
|
|
431
432
|
// If it wasn't Hyper Beam or the continuation of a move effect then we must have just
|
|
432
433
|
// thawed, in which case we will desync unless the last_selected_move happened to be at
|
|
433
|
-
// index 1 and the current Pokémon has the same move in its first slot
|
|
434
|
+
// index 1 and the current Pokémon has the same move in its first slot
|
|
434
435
|
if (!auto) {
|
|
435
436
|
// side.active.moves(slot) is safe to check even though the slot in question might not
|
|
436
437
|
// technically be from this Pokémon because it must be exactly 1 to not desync and
|
|
@@ -496,23 +497,23 @@ fn beforeMove(
|
|
|
496
497
|
}
|
|
497
498
|
|
|
498
499
|
if (duration == 0) {
|
|
499
|
-
try log.curestatus(ident, before, .Message);
|
|
500
|
+
try log.curestatus(.{ ident, before, .Message });
|
|
500
501
|
stored.status = 0; // clears EXT if present
|
|
501
502
|
} else {
|
|
502
|
-
try log.cant(ident, .Sleep);
|
|
503
|
+
try log.cant(.{ ident, .Sleep });
|
|
503
504
|
}
|
|
504
505
|
side.last_used_move = .None;
|
|
505
506
|
return .done;
|
|
506
507
|
}
|
|
507
508
|
|
|
508
509
|
if (Status.is(stored.status, .FRZ)) {
|
|
509
|
-
try log.cant(ident, .Freeze);
|
|
510
|
+
try log.cant(.{ ident, .Freeze });
|
|
510
511
|
side.last_used_move = .None;
|
|
511
512
|
return .done;
|
|
512
513
|
}
|
|
513
514
|
|
|
514
515
|
if (skip or foe.active.volatiles.Binding) {
|
|
515
|
-
try log.cant(ident, .Bound);
|
|
516
|
+
try log.cant(.{ ident, .Bound });
|
|
516
517
|
return .done;
|
|
517
518
|
}
|
|
518
519
|
|
|
@@ -520,13 +521,13 @@ fn beforeMove(
|
|
|
520
521
|
// Pokémon Showdown doesn't clear Flinch until its imaginary "residual" phase, meaning
|
|
521
522
|
// Pokémon can sometimes flinch multiple times from the same original hit
|
|
522
523
|
if (!showdown) volatiles.Flinch = false;
|
|
523
|
-
try log.cant(ident, .Flinch);
|
|
524
|
+
try log.cant(.{ ident, .Flinch });
|
|
524
525
|
return .done;
|
|
525
526
|
}
|
|
526
527
|
|
|
527
528
|
if (volatiles.Recharging) {
|
|
528
529
|
volatiles.Recharging = false;
|
|
529
|
-
try log.cant(ident, .Recharge);
|
|
530
|
+
try log.cant(.{ ident, .Recharge });
|
|
530
531
|
return .done;
|
|
531
532
|
}
|
|
532
533
|
|
|
@@ -540,7 +541,7 @@ fn beforeMove(
|
|
|
540
541
|
|
|
541
542
|
if (volatiles.disable_duration == 0) {
|
|
542
543
|
volatiles.disable_move = 0;
|
|
543
|
-
try log.end(ident, .Disable);
|
|
544
|
+
try log.end(.{ ident, .Disable });
|
|
544
545
|
}
|
|
545
546
|
}
|
|
546
547
|
// Pokémon Showdown's disable condition has a single onBeforeMove handler
|
|
@@ -565,9 +566,9 @@ fn beforeMove(
|
|
|
565
566
|
|
|
566
567
|
if (volatiles.confusion == 0) {
|
|
567
568
|
volatiles.Confusion = false;
|
|
568
|
-
try log.end(ident, .Confusion);
|
|
569
|
+
try log.end(.{ ident, .Confusion });
|
|
569
570
|
} else {
|
|
570
|
-
try log.activate(ident, .Confusion);
|
|
571
|
+
try log.activate(.{ ident, .Confusion });
|
|
571
572
|
|
|
572
573
|
if (try Rolls.confused(battle, player, options)) {
|
|
573
574
|
assert(!volatiles.MultiHit);
|
|
@@ -618,7 +619,7 @@ fn beforeMove(
|
|
|
618
619
|
volatiles.Binding = false;
|
|
619
620
|
options.chance.clearDurations(player, null);
|
|
620
621
|
// GLITCH: Invulnerable is not cleared, resulting in permanent Fly/Dig invulnerability
|
|
621
|
-
try log.cant(ident, .Paralysis);
|
|
622
|
+
try log.cant(.{ ident, .Paralysis });
|
|
622
623
|
return .done;
|
|
623
624
|
}
|
|
624
625
|
|
|
@@ -634,8 +635,6 @@ fn beforeMove(
|
|
|
634
635
|
volatiles.state +%= battle.last_damage;
|
|
635
636
|
}
|
|
636
637
|
|
|
637
|
-
assert(volatiles.attacks > 0);
|
|
638
|
-
|
|
639
638
|
if (options.calc.modify(player, .attacking)) |extend| {
|
|
640
639
|
if (!extend) volatiles.attacks = 0;
|
|
641
640
|
} else {
|
|
@@ -644,18 +643,18 @@ fn beforeMove(
|
|
|
644
643
|
try options.chance.attacking(player, volatiles.attacks);
|
|
645
644
|
|
|
646
645
|
if (volatiles.attacks != 0) {
|
|
647
|
-
try log.activate(ident, .Bide);
|
|
646
|
+
try log.activate(.{ ident, .Bide });
|
|
648
647
|
return .done;
|
|
649
648
|
}
|
|
650
649
|
|
|
651
650
|
volatiles.Bide = false;
|
|
652
|
-
try log.end(ident, .Bide);
|
|
651
|
+
try log.end(.{ ident, .Bide });
|
|
653
652
|
|
|
654
653
|
battle.last_damage = volatiles.state *% 2;
|
|
655
654
|
volatiles.state = 0;
|
|
656
655
|
|
|
657
656
|
if (battle.last_damage == 0) {
|
|
658
|
-
try log.fail(ident, .None);
|
|
657
|
+
try log.fail(.{ ident, .None });
|
|
659
658
|
return .done;
|
|
660
659
|
}
|
|
661
660
|
|
|
@@ -675,8 +674,6 @@ fn beforeMove(
|
|
|
675
674
|
}
|
|
676
675
|
|
|
677
676
|
if (volatiles.Thrashing) {
|
|
678
|
-
assert(volatiles.attacks > 0);
|
|
679
|
-
|
|
680
677
|
if (options.calc.modify(player, .attacking)) |extend| {
|
|
681
678
|
if (!extend) volatiles.attacks = 0;
|
|
682
679
|
} else {
|
|
@@ -685,23 +682,21 @@ fn beforeMove(
|
|
|
685
682
|
try options.chance.attacking(player, volatiles.attacks);
|
|
686
683
|
|
|
687
684
|
if (!showdown and handleThrashing(battle, active, player, options)) {
|
|
688
|
-
try log.start(battle.active(player), .ConfusionSilent);
|
|
685
|
+
try log.start(.{ battle.active(player), .ConfusionSilent });
|
|
689
686
|
}
|
|
690
|
-
try log.move(ident, side.last_selected_move, battle.active(player.foe()));
|
|
687
|
+
try log.move(.{ ident, side.last_selected_move, battle.active(player.foe()) });
|
|
691
688
|
if (showdown) {
|
|
692
689
|
// This shouldn't actually set last_used_move, but Pokémon Showdown sets last
|
|
693
690
|
// used in useMove and doesn't have the notion of skipping canMove semantics
|
|
694
691
|
side.last_used_move = side.last_selected_move;
|
|
695
692
|
if (handleThrashing(battle, active, player, options)) {
|
|
696
|
-
try log.start(battle.active(player), .ConfusionSilent);
|
|
693
|
+
try log.start(.{ battle.active(player), .ConfusionSilent });
|
|
697
694
|
}
|
|
698
695
|
}
|
|
699
696
|
return .skip_can;
|
|
700
697
|
}
|
|
701
698
|
|
|
702
699
|
if (volatiles.Binding) {
|
|
703
|
-
assert(volatiles.attacks > 0);
|
|
704
|
-
|
|
705
700
|
if (options.calc.modify(player, .binding)) |extend| {
|
|
706
701
|
if (!extend) volatiles.attacks = 0;
|
|
707
702
|
} else {
|
|
@@ -709,7 +704,7 @@ fn beforeMove(
|
|
|
709
704
|
}
|
|
710
705
|
try options.chance.binding(player, volatiles.attacks);
|
|
711
706
|
|
|
712
|
-
try log.move(
|
|
707
|
+
try log.move(.{ ident, side.last_selected_move, battle.active(player.foe()) });
|
|
713
708
|
if (showdown or battle.last_damage != 0) {
|
|
714
709
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
715
710
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -737,22 +732,21 @@ fn canMove(
|
|
|
737
732
|
const player_ident = battle.active(player);
|
|
738
733
|
const move = Move.get(side.last_selected_move);
|
|
739
734
|
|
|
740
|
-
var skip = skip_pp;
|
|
741
735
|
if (side.active.volatiles.Charging) {
|
|
742
736
|
side.active.volatiles.Charging = false;
|
|
743
737
|
side.active.volatiles.Invulnerable = false;
|
|
744
738
|
} else if (move.effect == .Charge) {
|
|
745
|
-
try options.log.
|
|
739
|
+
try options.log.move(.{ player_ident, side.last_selected_move, ID{}, from });
|
|
746
740
|
setCounterable(battle, player, side, move);
|
|
747
741
|
try Effects.charge(battle, player, options);
|
|
748
742
|
return false;
|
|
749
743
|
}
|
|
750
744
|
|
|
751
745
|
side.last_used_move = side.last_selected_move;
|
|
752
|
-
if (!
|
|
746
|
+
if (!skip_pp) decrementPP(side, mslot, auto);
|
|
753
747
|
|
|
754
748
|
const target = if (move.target == .Self) player else player.foe();
|
|
755
|
-
try options.log.
|
|
749
|
+
try options.log.move(.{ player_ident, side.last_selected_move, battle.active(target), from });
|
|
756
750
|
setCounterable(battle, player, side, move);
|
|
757
751
|
|
|
758
752
|
if (move.effect.onBegin()) {
|
|
@@ -790,12 +784,14 @@ fn decrementPP(side: *Side, mslot: u4, auto: bool) void {
|
|
|
790
784
|
assert(!volatiles.Rage and !volatiles.Thrashing and !volatiles.MultiHit);
|
|
791
785
|
if (volatiles.Bide) return;
|
|
792
786
|
|
|
793
|
-
|
|
794
|
-
|
|
787
|
+
var move_slot = active.move(mslot);
|
|
788
|
+
assert(move_slot.pp > 0 or auto);
|
|
789
|
+
move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
|
|
795
790
|
if (volatiles.Transform) return;
|
|
796
791
|
|
|
797
|
-
|
|
798
|
-
|
|
792
|
+
move_slot = side.stored().move(mslot);
|
|
793
|
+
assert(move_slot.pp > 0 or auto);
|
|
794
|
+
move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
|
|
799
795
|
assert(active.move(mslot).pp == side.stored().move(mslot).pp);
|
|
800
796
|
}
|
|
801
797
|
|
|
@@ -856,7 +852,7 @@ fn doMove(
|
|
|
856
852
|
break :miss true;
|
|
857
853
|
}
|
|
858
854
|
break :miss (move.target != .Self and
|
|
859
|
-
!moveHit(battle, player, move, &immune, &mist, options));
|
|
855
|
+
!try moveHit(battle, player, move, &immune, &mist, options));
|
|
860
856
|
};
|
|
861
857
|
assert(!immune or miss or (showdown and move.effect == .Binding));
|
|
862
858
|
|
|
@@ -899,11 +895,11 @@ fn doMove(
|
|
|
899
895
|
}
|
|
900
896
|
}
|
|
901
897
|
|
|
902
|
-
|
|
898
|
+
const zero = battle.last_damage == 0;
|
|
903
899
|
miss = if (showdown or skip)
|
|
904
900
|
miss
|
|
905
901
|
else
|
|
906
|
-
(!moveHit(battle, player, move, &immune, &mist, options) or zero);
|
|
902
|
+
(!try moveHit(battle, player, move, &immune, &mist, options) or zero);
|
|
907
903
|
|
|
908
904
|
assert(showdown or miss or battle.last_damage > 0 or skip);
|
|
909
905
|
assert((!showdown and miss) or !(ohko and immune));
|
|
@@ -927,16 +923,16 @@ fn doMove(
|
|
|
927
923
|
ohko = (!showdown or (!immune and !invulnerable)) and
|
|
928
924
|
move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe;
|
|
929
925
|
if (ohko) {
|
|
930
|
-
try log.immune(foe_ident, .OHKO);
|
|
926
|
+
try log.immune(.{ foe_ident, .OHKO });
|
|
931
927
|
} else if (immune and !invulnerable and (!showdown or move.effect != .Binding)) {
|
|
932
|
-
try log.immune(foe_ident, .None);
|
|
928
|
+
try log.immune(.{ foe_ident, .None });
|
|
933
929
|
} else if (mist) {
|
|
934
|
-
if (!foe.active.volatiles.Substitute) try log.activate(foe_ident, .Mist);
|
|
935
|
-
try log.fail(foe_ident, .None);
|
|
930
|
+
if (!foe.active.volatiles.Substitute) try log.activate(.{ foe_ident, .Mist });
|
|
931
|
+
try log.fail(.{ foe_ident, .None });
|
|
936
932
|
} else {
|
|
937
|
-
if (showdown
|
|
938
|
-
try log.lastmiss();
|
|
939
|
-
try log.miss(battle.active(player));
|
|
933
|
+
if (!showdown and !zero) try options.chance.commit(player, .miss);
|
|
934
|
+
try log.lastmiss(.{});
|
|
935
|
+
try log.miss(.{battle.active(player)});
|
|
940
936
|
}
|
|
941
937
|
if (move.effect == .JumpKick) {
|
|
942
938
|
// Recoil is supposed to be damage/8 but damage will always be 0 here
|
|
@@ -953,13 +949,13 @@ fn doMove(
|
|
|
953
949
|
try options.chance.commit(player, .binding);
|
|
954
950
|
}
|
|
955
951
|
return null;
|
|
956
|
-
} else {
|
|
952
|
+
} else if (!showdown) {
|
|
957
953
|
try options.chance.commit(player, .hit);
|
|
958
954
|
}
|
|
959
955
|
|
|
960
956
|
// On the cartridge MultiHit doesn't get set up until after damage has been applied for the
|
|
961
957
|
// first time but its more convenient and efficient to set it up here (Pokémon Showdown sets
|
|
962
|
-
// it up above before damage calculation)
|
|
958
|
+
// it up above before damage calculation)
|
|
963
959
|
if (!showdown and move.effect.isMulti()) {
|
|
964
960
|
try Effects.multiHit(battle, player, move, options);
|
|
965
961
|
hits = side.active.volatiles.attacks;
|
|
@@ -972,15 +968,15 @@ fn doMove(
|
|
|
972
968
|
var hit: u4 = 0;
|
|
973
969
|
while (hit < hits) {
|
|
974
970
|
if (hit == 0) {
|
|
975
|
-
if (crit) try log.crit(battle.active(player.foe()));
|
|
971
|
+
if (crit) try log.crit(.{battle.active(player.foe())});
|
|
976
972
|
if (effectiveness > Effectiveness.neutral) {
|
|
977
|
-
try log.supereffective(battle.active(player.foe()));
|
|
973
|
+
try log.supereffective(.{battle.active(player.foe())});
|
|
978
974
|
} else if (effectiveness < Effectiveness.neutral) {
|
|
979
|
-
try log.resisted(battle.active(player.foe()));
|
|
975
|
+
try log.resisted(.{battle.active(player.foe())});
|
|
980
976
|
}
|
|
981
977
|
}
|
|
982
978
|
if (!skip) nullified = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
983
|
-
if (hit == 0 and ohko) try log.ohko();
|
|
979
|
+
if (hit == 0 and ohko) try log.ohko(.{});
|
|
984
980
|
hit += 1;
|
|
985
981
|
if (foe.stored().hp == 0) break;
|
|
986
982
|
if (!late and (!sub or move.effect == .Explode)) {
|
|
@@ -997,7 +993,7 @@ fn doMove(
|
|
|
997
993
|
if (showdown and move.effect == .Twineedle and !nullified and foe.stored().hp > 0) {
|
|
998
994
|
try Effects.poison(battle, player, Move.get(.PoisonSting), options);
|
|
999
995
|
}
|
|
1000
|
-
try log.hitcount(battle.active(player.foe()), hit);
|
|
996
|
+
try log.hitcount(.{ battle.active(player.foe()), hit });
|
|
1001
997
|
} else if (showdown) {
|
|
1002
998
|
// This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
|
|
1003
999
|
if (move.effect == .Binding) {
|
|
@@ -1007,9 +1003,9 @@ fn doMove(
|
|
|
1007
1003
|
assert(foe.stored().hp > 0);
|
|
1008
1004
|
// Pokémon Showdown logs |-damage| here instead of |-immune| because logic...
|
|
1009
1005
|
if (sub) {
|
|
1010
|
-
try log.activate(battle.active(player.foe()), .Substitute);
|
|
1006
|
+
try log.activate(.{ battle.active(player.foe()), .Substitute });
|
|
1011
1007
|
} else {
|
|
1012
|
-
try log.damage(battle.active(player.foe()), foe.stored(), .None);
|
|
1008
|
+
try log.damage(.{ battle.active(player.foe()), foe.stored(), .None });
|
|
1013
1009
|
try buildRage(battle, player.foe(), options);
|
|
1014
1010
|
}
|
|
1015
1011
|
return null;
|
|
@@ -1018,12 +1014,12 @@ fn doMove(
|
|
|
1018
1014
|
}
|
|
1019
1015
|
|
|
1020
1016
|
// Substitute being broken nullifies the move's effect completely so even
|
|
1021
|
-
// if an effect was intended to "always happen" it will still get skipped
|
|
1017
|
+
// if an effect was intended to "always happen" it will still get skipped
|
|
1022
1018
|
if (nullified) return null;
|
|
1023
1019
|
|
|
1024
1020
|
// On the cartridge, "always happen" effect handlers are called in the applyDamage loop above,
|
|
1025
1021
|
// but this is only done to setup the MultiHit looping in the first place. Moving the MultiHit
|
|
1026
|
-
// setup before the loop means we can avoid having to waste time doing no-op handler searches
|
|
1022
|
+
// setup before the loop means we can avoid having to waste time doing no-op handler searches
|
|
1027
1023
|
if (move.effect.alwaysHappens()) try alwaysHappens(battle, player, move, residual, options);
|
|
1028
1024
|
|
|
1029
1025
|
if (foe.stored().hp == 0) return null;
|
|
@@ -1038,7 +1034,7 @@ fn doMove(
|
|
|
1038
1034
|
// below). For Twineedle we change the data to that of one with PoisonChance1 given its
|
|
1039
1035
|
// MultiHit behavior is complete after the loop above, though Pokémon Showdown handles the
|
|
1040
1036
|
// Twineedle secondary effect in the MultiHit cleanup block above because it incorrectly
|
|
1041
|
-
// puts the |-status| message before |-hitcount| instead of after
|
|
1037
|
+
// puts the |-status| message before |-hitcount| instead of after
|
|
1042
1038
|
if (move.effect == .Twineedle) {
|
|
1043
1039
|
if (!showdown) try Effects.poison(battle, player, Move.get(.PoisonSting), options);
|
|
1044
1040
|
} else if (move.effect == .Disable) {
|
|
@@ -1057,12 +1053,14 @@ fn checkCriticalHit(battle: anytype, player: Player, move: Move.Data, options: a
|
|
|
1057
1053
|
const side = battle.side(player);
|
|
1058
1054
|
|
|
1059
1055
|
// Base speed is used for the critical hit calculation, even when Transform-ed
|
|
1060
|
-
var rate =
|
|
1056
|
+
var rate: u16 = Species.chance(side.stored().species);
|
|
1061
1057
|
// GLITCH: Focus Energy reduces critical hit rate instead of increasing it
|
|
1062
1058
|
rate = if (side.active.volatiles.FocusEnergy) rate / 2 else @min(rate * 2, 255);
|
|
1063
1059
|
rate = if (move.effect == .HighCritical) @min(rate * 4, 255) else rate / 2;
|
|
1064
1060
|
|
|
1065
|
-
|
|
1061
|
+
const spurious =
|
|
1062
|
+
!showdown and ((move.bp == 0 and move.effect != .OHKO) or move.target == .Depends);
|
|
1063
|
+
return Rolls.criticalHit(battle, player, @intCast(rate), spurious, options);
|
|
1066
1064
|
}
|
|
1067
1065
|
|
|
1068
1066
|
fn calcDamage(
|
|
@@ -1114,9 +1112,9 @@ fn calcDamage(
|
|
|
1114
1112
|
def = @max((def / 4) & 255, if (showdown) 1 else 0);
|
|
1115
1113
|
}
|
|
1116
1114
|
|
|
1117
|
-
const lvl =
|
|
1115
|
+
const lvl: u32 = side.stored().level * @as(u2, if (crit) 2 else 1);
|
|
1118
1116
|
|
|
1119
|
-
def =
|
|
1117
|
+
def = if (move.effect == .Explode) @max(def / 2, 1) else def;
|
|
1120
1118
|
|
|
1121
1119
|
if (def == 0) return false;
|
|
1122
1120
|
|
|
@@ -1177,7 +1175,6 @@ fn specialDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1177
1175
|
const foe = battle.foe(player);
|
|
1178
1176
|
|
|
1179
1177
|
if (!try checkHit(battle, player, move, options)) return null;
|
|
1180
|
-
try options.chance.commit(player, .hit);
|
|
1181
1178
|
|
|
1182
1179
|
battle.last_damage = switch (side.last_selected_move) {
|
|
1183
1180
|
.SuperFang => @max(foe.stored().hp / 2, 1),
|
|
@@ -1207,7 +1204,7 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1207
1204
|
const foe = battle.foe(player);
|
|
1208
1205
|
|
|
1209
1206
|
if (battle.last_damage == 0) {
|
|
1210
|
-
try options.log.fail(battle.active(player), .None);
|
|
1207
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1211
1208
|
return null;
|
|
1212
1209
|
}
|
|
1213
1210
|
|
|
@@ -1225,22 +1222,21 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1225
1222
|
foe_last_selected_move.type == .Fighting);
|
|
1226
1223
|
|
|
1227
1224
|
if (!used and !selected) {
|
|
1228
|
-
try options.log.fail(battle.active(player), .None);
|
|
1225
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1229
1226
|
return null;
|
|
1230
1227
|
}
|
|
1231
1228
|
|
|
1232
1229
|
if (!used or !selected) {
|
|
1233
1230
|
// GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
|
|
1234
1231
|
if (!showdown) return Result.Error;
|
|
1235
|
-
try options.log.fail(battle.active(player), .None);
|
|
1232
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1236
1233
|
return null;
|
|
1237
1234
|
}
|
|
1238
1235
|
|
|
1239
1236
|
battle.last_damage *|= 2;
|
|
1240
1237
|
|
|
1241
|
-
// Pokémon Showdown calls
|
|
1238
|
+
// Pokémon Showdown calls moveHit before Counter
|
|
1242
1239
|
if (!showdown and !try checkHit(battle, player, move, options)) return null;
|
|
1243
|
-
try options.chance.commit(player, .hit);
|
|
1244
1240
|
|
|
1245
1241
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
1246
1242
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -1268,20 +1264,20 @@ fn applyDamage(
|
|
|
1268
1264
|
subbed.active.volatiles.substitute = 0;
|
|
1269
1265
|
subbed.active.volatiles.Substitute = false;
|
|
1270
1266
|
// battle.last_damage is not updated with the amount of HP the Substitute had
|
|
1271
|
-
try options.log.end(battle.active(sub_player), .Substitute);
|
|
1267
|
+
try options.log.end(.{ battle.active(sub_player), .Substitute });
|
|
1272
1268
|
options.calc.capped(target_player);
|
|
1273
1269
|
return true;
|
|
1274
1270
|
} else {
|
|
1275
1271
|
// Safe to truncate since less than subbed.volatiles.substitute which is a u8
|
|
1276
1272
|
subbed.active.volatiles.substitute -= @intCast(battle.last_damage);
|
|
1277
|
-
try options.log.activate(battle.active(sub_player), .Substitute);
|
|
1273
|
+
try options.log.activate(.{ battle.active(sub_player), .Substitute });
|
|
1278
1274
|
return false;
|
|
1279
1275
|
}
|
|
1280
1276
|
}
|
|
1281
1277
|
|
|
1282
1278
|
if (battle.last_damage > target.stored().hp) battle.last_damage = target.stored().hp;
|
|
1283
1279
|
target.stored().hp -= battle.last_damage;
|
|
1284
|
-
try options.log.damage(battle.active(target_player), target.stored(), reason);
|
|
1280
|
+
try options.log.damage(.{ battle.active(target_player), target.stored(), reason });
|
|
1285
1281
|
return false;
|
|
1286
1282
|
}
|
|
1287
1283
|
|
|
@@ -1300,7 +1296,7 @@ fn mirrorMove(
|
|
|
1300
1296
|
side.last_selected_move = foe.last_used_move;
|
|
1301
1297
|
|
|
1302
1298
|
if (foe.last_used_move == .None or foe.last_used_move == .MirrorMove) {
|
|
1303
|
-
try options.log.fail(battle.active(player), .None);
|
|
1299
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1304
1300
|
return null;
|
|
1305
1301
|
} else if (!showdown or foe.last_used_move != .Struggle) {
|
|
1306
1302
|
incrementPP(side, mslot);
|
|
@@ -1336,20 +1332,23 @@ fn checkHit(battle: anytype, player: Player, move: Move.Data, options: anytype)
|
|
|
1336
1332
|
var immune = false;
|
|
1337
1333
|
var mist = false;
|
|
1338
1334
|
|
|
1339
|
-
if (moveHit(battle, player, move, &immune, &mist, options))
|
|
1335
|
+
if (try moveHit(battle, player, move, &immune, &mist, options)) {
|
|
1336
|
+
if (!showdown) try options.chance.commit(player, .hit);
|
|
1337
|
+
return true;
|
|
1338
|
+
}
|
|
1340
1339
|
|
|
1341
1340
|
assert(!immune);
|
|
1342
1341
|
if (mist) {
|
|
1343
1342
|
assert(!showdown);
|
|
1344
1343
|
const foe_ident = battle.active(player.foe());
|
|
1345
|
-
try options.log.activate(foe_ident, .Mist);
|
|
1346
|
-
try options.log.fail(foe_ident, .None);
|
|
1344
|
+
try options.log.activate(.{ foe_ident, .Mist });
|
|
1345
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1347
1346
|
} else {
|
|
1348
|
-
try options.
|
|
1349
|
-
try options.log.
|
|
1350
|
-
try options.log.miss(battle.active(player));
|
|
1347
|
+
try options.log.lastmiss(.{});
|
|
1348
|
+
try options.log.miss(.{battle.active(player)});
|
|
1351
1349
|
}
|
|
1352
1350
|
|
|
1351
|
+
if (!showdown) try options.chance.commit(player, .miss);
|
|
1353
1352
|
return false;
|
|
1354
1353
|
}
|
|
1355
1354
|
|
|
@@ -1360,7 +1359,7 @@ fn moveHit(
|
|
|
1360
1359
|
immune: *bool,
|
|
1361
1360
|
mist: *bool,
|
|
1362
1361
|
options: anytype,
|
|
1363
|
-
) bool {
|
|
1362
|
+
) !bool {
|
|
1364
1363
|
var side = battle.side(player);
|
|
1365
1364
|
const foe = battle.foe(player);
|
|
1366
1365
|
|
|
@@ -1399,7 +1398,7 @@ fn moveHit(
|
|
|
1399
1398
|
const overwritten = overwrite and state > 0;
|
|
1400
1399
|
assert(!overwritten or (0 < state and state <= 255 and !side.active.volatiles.Bide));
|
|
1401
1400
|
|
|
1402
|
-
var accuracy = if (overwritten) state else
|
|
1401
|
+
var accuracy: u16 = if (overwritten) state else move.accuracy;
|
|
1403
1402
|
var boost = BOOSTS[@as(u4, @intCast(@as(i8, side.active.boosts.accuracy) + 6))];
|
|
1404
1403
|
accuracy = accuracy * boost[0] / boost[1];
|
|
1405
1404
|
boost = BOOSTS[@as(u4, @intCast(@as(i8, -foe.active.boosts.evasion) + 6))];
|
|
@@ -1411,7 +1410,7 @@ fn moveHit(
|
|
|
1411
1410
|
if (overwrite) side.active.volatiles.state = accuracy;
|
|
1412
1411
|
|
|
1413
1412
|
// GLITCH: max accuracy is 255 so 1/256 chance of miss
|
|
1414
|
-
break :miss !Rolls.hit(battle, player, @intCast(accuracy), options);
|
|
1413
|
+
break :miss !try Rolls.hit(battle, player, @intCast(accuracy), options);
|
|
1415
1414
|
};
|
|
1416
1415
|
|
|
1417
1416
|
// Pokémon Showdown reports miss instead of fail for moves blocked by Mist that 1/256 miss
|
|
@@ -1438,9 +1437,8 @@ fn checkFaint(
|
|
|
1438
1437
|
const foe_fainted = foe.stored().hp == 0;
|
|
1439
1438
|
|
|
1440
1439
|
const player_out = findFirstAlive(side) == 0;
|
|
1441
|
-
const foe_out = findFirstAlive(foe) == 0;
|
|
1442
|
-
const
|
|
1443
|
-
const more = tie or player_out or foe_out;
|
|
1440
|
+
const foe_out = foe_fainted and findFirstAlive(foe) == 0;
|
|
1441
|
+
const more = player_out or foe_out;
|
|
1444
1442
|
|
|
1445
1443
|
if (try faint(battle, player, !(more or foe_fainted), options)) |r| return r;
|
|
1446
1444
|
if (foe_fainted) if (try faint(battle, player.foe(), !more, options)) |r| return r;
|
|
@@ -1448,14 +1446,14 @@ fn checkFaint(
|
|
|
1448
1446
|
assert(!side.active.volatiles.MultiHit);
|
|
1449
1447
|
assert(!foe.active.volatiles.MultiHit);
|
|
1450
1448
|
|
|
1451
|
-
if (
|
|
1452
|
-
try options.log.tie();
|
|
1449
|
+
if (player_out and foe_out) {
|
|
1450
|
+
try options.log.tie(.{});
|
|
1453
1451
|
return Result.Tie;
|
|
1454
1452
|
} else if (player_out) {
|
|
1455
|
-
try options.log.win(player.foe());
|
|
1453
|
+
try options.log.win(.{player.foe()});
|
|
1456
1454
|
return if (player == .P1) Result.Lose else Result.Win;
|
|
1457
1455
|
} else if (foe_out) {
|
|
1458
|
-
try options.log.win(player);
|
|
1456
|
+
try options.log.win(.{player});
|
|
1459
1457
|
return if (player == .P1) Result.Win else Result.Lose;
|
|
1460
1458
|
}
|
|
1461
1459
|
|
|
@@ -1497,7 +1495,7 @@ fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result
|
|
|
1497
1495
|
side.stored().stats.spe;
|
|
1498
1496
|
}
|
|
1499
1497
|
|
|
1500
|
-
try options.log.faint(battle.active(player), done);
|
|
1498
|
+
try options.log.faint(.{ battle.active(player), done });
|
|
1501
1499
|
options.calc.capped(player);
|
|
1502
1500
|
return null;
|
|
1503
1501
|
}
|
|
@@ -1519,8 +1517,8 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1519
1517
|
|
|
1520
1518
|
if (!showdown or damage > 0) {
|
|
1521
1519
|
stored.hp -= damage;
|
|
1522
|
-
// Pokémon Showdown uses
|
|
1523
|
-
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
1520
|
+
// Pokémon Showdown uses damage [of] here but its not relevant in Generation I
|
|
1521
|
+
try options.log.damage(.{ ident, stored, if (brn) Damage.Burn else Damage.Poison });
|
|
1524
1522
|
}
|
|
1525
1523
|
}
|
|
1526
1524
|
|
|
@@ -1545,14 +1543,14 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1545
1543
|
const amount = @min(damage, stored.hp);
|
|
1546
1544
|
stored.hp -= amount;
|
|
1547
1545
|
|
|
1548
|
-
// As above, Pokémon Showdown uses
|
|
1549
|
-
if (amount > 0) try options.log.damage(ident, stored, .LeechSeed);
|
|
1546
|
+
// As above, Pokémon Showdown uses damage [of] but its not relevant
|
|
1547
|
+
if (amount > 0) try options.log.damage(.{ ident, stored, .LeechSeed });
|
|
1550
1548
|
|
|
1551
1549
|
const before = foe_stored.hp;
|
|
1552
1550
|
// Uncapped damage is added back to the foe
|
|
1553
1551
|
foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
|
|
1554
1552
|
// Pokémon Showdown uses the less specific heal here instead of drain... because reasons?
|
|
1555
|
-
if (foe_stored.hp > before) try options.log.heal(foe_ident, foe_stored, .Silent);
|
|
1553
|
+
if (foe_stored.hp > before) try options.log.heal(.{ foe_ident, foe_stored, .Silent });
|
|
1556
1554
|
}
|
|
1557
1555
|
}
|
|
1558
1556
|
|
|
@@ -1563,18 +1561,18 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1563
1561
|
battle.turn += 1;
|
|
1564
1562
|
|
|
1565
1563
|
if (showdown and pkmn.options.ebc and checkEBC(battle)) {
|
|
1566
|
-
try options.log.tie();
|
|
1564
|
+
try options.log.tie(.{});
|
|
1567
1565
|
return Result.Tie;
|
|
1568
1566
|
}
|
|
1569
1567
|
|
|
1570
1568
|
if (showdown and battle.turn >= 1000) {
|
|
1571
|
-
try options.log.tie();
|
|
1569
|
+
try options.log.tie(.{});
|
|
1572
1570
|
return Result.Tie;
|
|
1573
1571
|
} else if (battle.turn >= 65535) {
|
|
1574
1572
|
return Result.Error;
|
|
1575
1573
|
}
|
|
1576
1574
|
|
|
1577
|
-
try options.log.turn(battle.turn);
|
|
1575
|
+
try options.log.turn(.{battle.turn});
|
|
1578
1576
|
|
|
1579
1577
|
return Result.Default;
|
|
1580
1578
|
}
|
|
@@ -1644,14 +1642,14 @@ fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
|
1644
1642
|
side.last_selected_move;
|
|
1645
1643
|
if (m != .None and m == last) {
|
|
1646
1644
|
side.active.volatiles.Charging = false;
|
|
1647
|
-
try options.log.
|
|
1645
|
+
try options.log.cant(.{ ident, .Disable, last });
|
|
1648
1646
|
return true;
|
|
1649
1647
|
}
|
|
1650
1648
|
}
|
|
1651
1649
|
return false;
|
|
1652
1650
|
}
|
|
1653
1651
|
|
|
1654
|
-
|
|
1652
|
+
fn buildRage(battle: anytype, who: Player, options: anytype) !void {
|
|
1655
1653
|
const side = battle.side(who);
|
|
1656
1654
|
if (side.active.volatiles.Rage and side.active.boosts.atk < 6) {
|
|
1657
1655
|
try Effects.boost(battle, who, Move.get(.Rage), options);
|
|
@@ -1669,7 +1667,7 @@ fn handleThrashing(battle: anytype, active: *ActivePokemon, player: Player, opti
|
|
|
1669
1667
|
return true;
|
|
1670
1668
|
}
|
|
1671
1669
|
|
|
1672
|
-
|
|
1670
|
+
fn onBegin(
|
|
1673
1671
|
battle: anytype,
|
|
1674
1672
|
player: Player,
|
|
1675
1673
|
move: Move.Data,
|
|
@@ -1677,7 +1675,6 @@ inline fn onBegin(
|
|
|
1677
1675
|
residual: *bool,
|
|
1678
1676
|
options: anytype,
|
|
1679
1677
|
) !void {
|
|
1680
|
-
assert(move.effect.onBegin());
|
|
1681
1678
|
return switch (move.effect) {
|
|
1682
1679
|
.Confusion => Effects.confusion(battle, player, move, options),
|
|
1683
1680
|
.Conversion => Effects.conversion(battle, player, options),
|
|
@@ -1699,8 +1696,7 @@ inline fn onBegin(
|
|
|
1699
1696
|
};
|
|
1700
1697
|
}
|
|
1701
1698
|
|
|
1702
|
-
|
|
1703
|
-
assert(move.effect.onEnd());
|
|
1699
|
+
fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1704
1700
|
return switch (move.effect) {
|
|
1705
1701
|
.Bide => Effects.bide(battle, player, options),
|
|
1706
1702
|
// zig fmt: off
|
|
@@ -1715,14 +1711,13 @@ inline fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anyty
|
|
|
1715
1711
|
};
|
|
1716
1712
|
}
|
|
1717
1713
|
|
|
1718
|
-
|
|
1714
|
+
fn alwaysHappens(
|
|
1719
1715
|
battle: anytype,
|
|
1720
1716
|
player: Player,
|
|
1721
1717
|
move: Move.Data,
|
|
1722
1718
|
residual: *bool,
|
|
1723
1719
|
options: anytype,
|
|
1724
1720
|
) !void {
|
|
1725
|
-
assert(move.effect.alwaysHappens());
|
|
1726
1721
|
return switch (move.effect) {
|
|
1727
1722
|
.DrainHP, .DreamEater => Effects.drainHP(battle, player, options),
|
|
1728
1723
|
.Explode => Effects.explode(battle, player),
|
|
@@ -1734,7 +1729,7 @@ inline fn alwaysHappens(
|
|
|
1734
1729
|
};
|
|
1735
1730
|
}
|
|
1736
1731
|
|
|
1737
|
-
|
|
1732
|
+
fn moveEffect(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1738
1733
|
return switch (move.effect) {
|
|
1739
1734
|
.BurnChance1, .BurnChance2 => Effects.burnChance(battle, player, move, options),
|
|
1740
1735
|
.ConfusionChance => Effects.confusion(battle, player, move, options),
|
|
@@ -1761,7 +1756,7 @@ pub const Effects = struct {
|
|
|
1761
1756
|
side.active.volatiles.state = 0;
|
|
1762
1757
|
side.active.volatiles.attacks = Rolls.attackingDuration(battle, player, options);
|
|
1763
1758
|
|
|
1764
|
-
try options.log.start(battle.active(player), .Bide);
|
|
1759
|
+
try options.log.start(.{ battle.active(player), .Bide });
|
|
1765
1760
|
}
|
|
1766
1761
|
|
|
1767
1762
|
fn burnChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1775,11 +1770,11 @@ pub const Effects = struct {
|
|
|
1775
1770
|
// GLITCH: Freeze top move selection desync can occur if thawed Pokémon is slower
|
|
1776
1771
|
if (Status.is(foe_stored.status, .FRZ)) {
|
|
1777
1772
|
assert(move.type == .Fire);
|
|
1778
|
-
try options.log.curestatus(
|
|
1773
|
+
try options.log.curestatus(.{
|
|
1779
1774
|
battle.active(player.foe()),
|
|
1780
1775
|
foe_stored.status,
|
|
1781
1776
|
.Message,
|
|
1782
|
-
);
|
|
1777
|
+
});
|
|
1783
1778
|
foe_stored.status = 0;
|
|
1784
1779
|
}
|
|
1785
1780
|
return;
|
|
@@ -1793,7 +1788,7 @@ pub const Effects = struct {
|
|
|
1793
1788
|
foe_stored.status = Status.init(.BRN);
|
|
1794
1789
|
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
1795
1790
|
|
|
1796
|
-
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
1791
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1797
1792
|
}
|
|
1798
1793
|
|
|
1799
1794
|
fn charge(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -1803,8 +1798,8 @@ pub const Effects = struct {
|
|
|
1803
1798
|
volatiles.Charging = true;
|
|
1804
1799
|
const move = side.last_selected_move;
|
|
1805
1800
|
if (move == .Fly or move == .Dig) volatiles.Invulnerable = true;
|
|
1806
|
-
try options.log.laststill();
|
|
1807
|
-
try options.log.prepare(battle.active(player), move);
|
|
1801
|
+
try options.log.laststill(.{});
|
|
1802
|
+
try options.log.prepare(.{ battle.active(player), move });
|
|
1808
1803
|
}
|
|
1809
1804
|
|
|
1810
1805
|
fn confusion(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1815,42 +1810,36 @@ pub const Effects = struct {
|
|
|
1815
1810
|
if (!try Rolls.confusionChance(battle, player, options)) return;
|
|
1816
1811
|
} else {
|
|
1817
1812
|
if (showdown) {
|
|
1818
|
-
if (!try checkHit(battle, player, move, options))
|
|
1819
|
-
|
|
1820
|
-
} else if (sub) {
|
|
1821
|
-
return options.log.fail(battle.active(player.foe()), .None);
|
|
1822
|
-
}
|
|
1813
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1814
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1823
1815
|
} else {
|
|
1824
|
-
if (sub) {
|
|
1825
|
-
|
|
1826
|
-
} else if (!try checkHit(battle, player, move, options)) {
|
|
1827
|
-
return;
|
|
1828
|
-
}
|
|
1816
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1817
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1829
1818
|
}
|
|
1830
|
-
try options.chance.commit(player, .hit);
|
|
1831
1819
|
}
|
|
1832
1820
|
|
|
1833
1821
|
if (foe.active.volatiles.Confusion) return;
|
|
1834
1822
|
foe.active.volatiles.Confusion = true;
|
|
1835
1823
|
foe.active.volatiles.confusion = Rolls.confusionDuration(battle, player, false, options);
|
|
1836
1824
|
|
|
1837
|
-
try options.log.start(battle.active(player.foe()), .Confusion);
|
|
1825
|
+
try options.log.start(.{ battle.active(player.foe()), .Confusion });
|
|
1838
1826
|
}
|
|
1839
1827
|
|
|
1840
1828
|
fn conversion(battle: anytype, player: Player, options: anytype) !void {
|
|
1841
1829
|
const foe = battle.foe(player);
|
|
1842
1830
|
|
|
1843
1831
|
if (foe.active.volatiles.Invulnerable) {
|
|
1844
|
-
try options.log.lastmiss();
|
|
1845
|
-
return options.log.miss(battle.active(player));
|
|
1832
|
+
try options.log.lastmiss(.{});
|
|
1833
|
+
return options.log.miss(.{battle.active(player)});
|
|
1846
1834
|
}
|
|
1847
1835
|
|
|
1848
1836
|
battle.side(player).active.types = foe.active.types;
|
|
1849
|
-
return options.log.
|
|
1837
|
+
return options.log.start(.{
|
|
1850
1838
|
battle.active(player),
|
|
1839
|
+
.TypeChange,
|
|
1851
1840
|
foe.active.types,
|
|
1852
1841
|
battle.active(player.foe()),
|
|
1853
|
-
);
|
|
1842
|
+
});
|
|
1854
1843
|
}
|
|
1855
1844
|
|
|
1856
1845
|
fn disable(battle: anytype, player: Player, move: Move.Data, options: anytype) !?Result {
|
|
@@ -1859,13 +1848,10 @@ pub const Effects = struct {
|
|
|
1859
1848
|
const foe_ident = battle.active(player.foe());
|
|
1860
1849
|
|
|
1861
1850
|
// Pokémon Showdown handles hit/miss earlier in doMove
|
|
1862
|
-
if (!showdown)
|
|
1863
|
-
if (!try checkHit(battle, player, move, options)) return null;
|
|
1864
|
-
try options.chance.commit(player, .hit);
|
|
1865
|
-
}
|
|
1851
|
+
if (!showdown and !try checkHit(battle, player, move, options)) return null;
|
|
1866
1852
|
|
|
1867
1853
|
if (volatiles.disable_move != 0) {
|
|
1868
|
-
try options.log.fail(foe_ident, .None);
|
|
1854
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1869
1855
|
return null;
|
|
1870
1856
|
}
|
|
1871
1857
|
|
|
@@ -1881,7 +1867,7 @@ pub const Effects = struct {
|
|
|
1881
1867
|
// Technically this is still considered simply a "miss" on the cartridge,
|
|
1882
1868
|
// but diverging from Pokémon Showdown here would mostly just be pedantic
|
|
1883
1869
|
if (n == 0) {
|
|
1884
|
-
try options.log.fail(foe_ident, .None);
|
|
1870
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1885
1871
|
return null;
|
|
1886
1872
|
} else if (err) {
|
|
1887
1873
|
// GLITCH: Transform + Mirror Move / Metronome PP softlock
|
|
@@ -1894,13 +1880,13 @@ pub const Effects = struct {
|
|
|
1894
1880
|
volatiles.disable_duration = Rolls.disableDuration(battle, player, options);
|
|
1895
1881
|
|
|
1896
1882
|
const id = foe.active.move(volatiles.disable_move).id;
|
|
1897
|
-
try options.log.
|
|
1883
|
+
try options.log.start(.{ foe_ident, .Disable, id });
|
|
1898
1884
|
return null;
|
|
1899
1885
|
}
|
|
1900
1886
|
|
|
1901
1887
|
fn drainHP(battle: anytype, player: Player, options: anytype) !void {
|
|
1902
|
-
var
|
|
1903
|
-
|
|
1888
|
+
var stored = battle.side(player).stored();
|
|
1889
|
+
const ident = battle.active(player);
|
|
1904
1890
|
|
|
1905
1891
|
if (battle.last_damage == 0) {
|
|
1906
1892
|
assert(showdown);
|
|
@@ -1913,7 +1899,7 @@ pub const Effects = struct {
|
|
|
1913
1899
|
if (stored.hp == stored.stats.hp) return;
|
|
1914
1900
|
stored.hp = @min(stored.stats.hp, stored.hp + drain);
|
|
1915
1901
|
|
|
1916
|
-
try options.log.
|
|
1902
|
+
try options.log.heal(.{ ident, stored, .Drain, battle.active(player.foe()) });
|
|
1917
1903
|
}
|
|
1918
1904
|
|
|
1919
1905
|
fn explode(battle: anytype, player: Player) !void {
|
|
@@ -1945,7 +1931,7 @@ pub const Effects = struct {
|
|
|
1945
1931
|
if (side.active.volatiles.FocusEnergy) return;
|
|
1946
1932
|
side.active.volatiles.FocusEnergy = true;
|
|
1947
1933
|
|
|
1948
|
-
try options.log.start(battle.active(player), .FocusEnergy);
|
|
1934
|
+
try options.log.start(.{ battle.active(player), .FocusEnergy });
|
|
1949
1935
|
}
|
|
1950
1936
|
|
|
1951
1937
|
fn freezeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1967,7 +1953,7 @@ pub const Effects = struct {
|
|
|
1967
1953
|
foe_stored.status = Status.init(.FRZ);
|
|
1968
1954
|
// GLITCH: Hyper Beam recharging status is not cleared
|
|
1969
1955
|
|
|
1970
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
1956
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
1971
1957
|
}
|
|
1972
1958
|
|
|
1973
1959
|
fn haze(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -1975,7 +1961,7 @@ pub const Effects = struct {
|
|
|
1975
1961
|
var side = battle.side(player);
|
|
1976
1962
|
var foe = battle.foe(player);
|
|
1977
1963
|
|
|
1978
|
-
|
|
1964
|
+
const side_stored = side.stored();
|
|
1979
1965
|
var foe_stored = foe.stored();
|
|
1980
1966
|
|
|
1981
1967
|
const player_ident = battle.active(player);
|
|
@@ -1987,8 +1973,8 @@ pub const Effects = struct {
|
|
|
1987
1973
|
side.active.stats = side_stored.stats;
|
|
1988
1974
|
foe.active.stats = foe_stored.stats;
|
|
1989
1975
|
|
|
1990
|
-
try log.activate(player_ident, .Haze);
|
|
1991
|
-
try log.clearallboost();
|
|
1976
|
+
try log.activate(.{ player_ident, .Haze });
|
|
1977
|
+
try log.clearallboost(.{});
|
|
1992
1978
|
|
|
1993
1979
|
// Pokémon Showdown clears P1 then P2 instead of status -> side -> foe
|
|
1994
1980
|
if (showdown) {
|
|
@@ -1996,11 +1982,11 @@ pub const Effects = struct {
|
|
|
1996
1982
|
const p: Player = @enumFromInt(i);
|
|
1997
1983
|
// Pokémon Showdown incorrectly does not prevent sleep/freeze from moving
|
|
1998
1984
|
if (p != player and Status.any(s.stored().status)) {
|
|
1999
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
1985
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2000
1986
|
s.stored().status = 0;
|
|
2001
1987
|
} else if (showdown and s.stored().status == Status.TOX) {
|
|
2002
1988
|
s.stored().status = Status.init(.PSN);
|
|
2003
|
-
try log.status(battle.active(p), s.stored().status, .None);
|
|
1989
|
+
try log.status(.{ battle.active(p), s.stored().status, .None });
|
|
2004
1990
|
}
|
|
2005
1991
|
try clearVolatiles(battle, p, p != player, options);
|
|
2006
1992
|
}
|
|
@@ -2009,7 +1995,7 @@ pub const Effects = struct {
|
|
|
2009
1995
|
if (Status.is(foe_stored.status, .FRZ) or Status.is(foe_stored.status, .SLP)) {
|
|
2010
1996
|
foe.last_selected_move = .SKIP_TURN;
|
|
2011
1997
|
}
|
|
2012
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
1998
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2013
1999
|
foe_stored.status = 0;
|
|
2014
2000
|
}
|
|
2015
2001
|
try clearVolatiles(battle, player, false, options);
|
|
@@ -2025,7 +2011,7 @@ pub const Effects = struct {
|
|
|
2025
2011
|
// GLITCH: HP recovery move failure glitches
|
|
2026
2012
|
const delta = stored.stats.hp - stored.hp;
|
|
2027
2013
|
if (delta == 0 or (delta & 255 == 255 and stored.hp % 256 != 0)) {
|
|
2028
|
-
return
|
|
2014
|
+
return options.log.fail(.{ ident, .None });
|
|
2029
2015
|
}
|
|
2030
2016
|
|
|
2031
2017
|
const rest = side.last_selected_move == .Rest;
|
|
@@ -2033,17 +2019,17 @@ pub const Effects = struct {
|
|
|
2033
2019
|
// Adding the sleep status runs the sleep condition handler to roll duration
|
|
2034
2020
|
if (showdown) battle.rng.advance(1);
|
|
2035
2021
|
stored.status = Status.slf(2);
|
|
2036
|
-
try options.log.
|
|
2022
|
+
try options.log.status(.{ ident, stored.status, .From, Move.Rest });
|
|
2037
2023
|
stored.hp = stored.stats.hp;
|
|
2038
2024
|
} else {
|
|
2039
2025
|
stored.hp = @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2));
|
|
2040
2026
|
}
|
|
2041
|
-
try options.log.heal(ident, stored, if (rest) Heal.Silent else Heal.None);
|
|
2027
|
+
try options.log.heal(.{ ident, stored, if (rest) Heal.Silent else Heal.None });
|
|
2042
2028
|
}
|
|
2043
2029
|
|
|
2044
2030
|
fn hyperBeam(battle: anytype, player: Player, options: anytype) !void {
|
|
2045
2031
|
battle.side(player).active.volatiles.Recharging = true;
|
|
2046
|
-
try options.log.mustrecharge(battle.active(player));
|
|
2032
|
+
try options.log.mustrecharge(.{battle.active(player)});
|
|
2047
2033
|
}
|
|
2048
2034
|
|
|
2049
2035
|
fn leechSeed(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2052,33 +2038,31 @@ pub const Effects = struct {
|
|
|
2052
2038
|
if (showdown) {
|
|
2053
2039
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2054
2040
|
if (!foe.active.volatiles.Invulnerable and foe.active.types.includes(.Grass)) {
|
|
2055
|
-
return options.log.immune(battle.active(player.foe()), .None);
|
|
2041
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
2056
2042
|
}
|
|
2057
2043
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2058
2044
|
if (foe.active.volatiles.LeechSeed) return;
|
|
2059
2045
|
} else {
|
|
2060
2046
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2061
2047
|
if (foe.active.types.includes(.Grass) or foe.active.volatiles.LeechSeed) {
|
|
2062
|
-
try options.log.lastmiss();
|
|
2063
|
-
return options.log.miss(battle.active(player));
|
|
2048
|
+
try options.log.lastmiss(.{});
|
|
2049
|
+
return options.log.miss(.{battle.active(player)});
|
|
2064
2050
|
}
|
|
2065
2051
|
}
|
|
2066
2052
|
|
|
2067
|
-
try options.chance.commit(player, .hit);
|
|
2068
2053
|
foe.active.volatiles.LeechSeed = true;
|
|
2069
2054
|
|
|
2070
|
-
try options.log.start(battle.active(player.foe()), .LeechSeed);
|
|
2055
|
+
try options.log.start(.{ battle.active(player.foe()), .LeechSeed });
|
|
2071
2056
|
}
|
|
2072
2057
|
|
|
2073
2058
|
fn lightScreen(battle: anytype, player: Player, options: anytype) !void {
|
|
2074
2059
|
var side = battle.side(player);
|
|
2060
|
+
const ident = battle.active(player);
|
|
2075
2061
|
|
|
2076
|
-
if (side.active.volatiles.LightScreen) {
|
|
2077
|
-
return options.log.fail(battle.active(player), .None);
|
|
2078
|
-
}
|
|
2062
|
+
if (side.active.volatiles.LightScreen) return options.log.fail(.{ ident, .None });
|
|
2079
2063
|
side.active.volatiles.LightScreen = true;
|
|
2080
2064
|
|
|
2081
|
-
try options.log.start(
|
|
2065
|
+
try options.log.start(.{ ident, .LightScreen });
|
|
2082
2066
|
}
|
|
2083
2067
|
|
|
2084
2068
|
fn mimic(battle: anytype, player: Player, move: Move.Data, mslot: u8, options: anytype) !void {
|
|
@@ -2106,17 +2090,15 @@ pub const Effects = struct {
|
|
|
2106
2090
|
if (!has_mimic) {
|
|
2107
2091
|
// Invulnerable foes or 1/256 miss can trigger |-miss| instead of |-fail|
|
|
2108
2092
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2109
|
-
|
|
2110
|
-
return try options.log.fail(battle.active(player.foe()), .None);
|
|
2093
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2111
2094
|
}
|
|
2112
2095
|
}
|
|
2113
2096
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2114
|
-
try options.chance.commit(player, .hit);
|
|
2115
2097
|
|
|
2116
2098
|
const rslot = try Rolls.moveSlot(battle, player, &foe.active.moves, 0, options);
|
|
2117
2099
|
side.active.move(oslot).id = foe.active.move(rslot).id;
|
|
2118
2100
|
|
|
2119
|
-
try options.log.
|
|
2101
|
+
try options.log.start(.{ battle.active(player), .Mimic, side.active.move(oslot).id });
|
|
2120
2102
|
}
|
|
2121
2103
|
|
|
2122
2104
|
fn mist(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -2125,7 +2107,7 @@ pub const Effects = struct {
|
|
|
2125
2107
|
if (side.active.volatiles.Mist) return;
|
|
2126
2108
|
side.active.volatiles.Mist = true;
|
|
2127
2109
|
|
|
2128
|
-
try options.log.start(battle.active(player), .Mist);
|
|
2110
|
+
try options.log.start(.{ battle.active(player), .Mist });
|
|
2129
2111
|
}
|
|
2130
2112
|
|
|
2131
2113
|
fn multiHit(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2151,25 +2133,25 @@ pub const Effects = struct {
|
|
|
2151
2133
|
|
|
2152
2134
|
if (showdown) {
|
|
2153
2135
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2154
|
-
if (immune and !foe.active.volatiles.Invulnerable)
|
|
2155
|
-
|
|
2136
|
+
if (immune and !foe.active.volatiles.Invulnerable) {
|
|
2137
|
+
return log.immune(.{ foe_ident, .None });
|
|
2138
|
+
} else if (!try checkHit(battle, player, move, options)) return;
|
|
2156
2139
|
}
|
|
2157
2140
|
if (Status.any(foe_stored.status)) {
|
|
2158
|
-
return log.fail(
|
|
2141
|
+
return log.fail(.{
|
|
2159
2142
|
foe_ident,
|
|
2160
|
-
if (Status.is(foe_stored.status, .PAR)) .Paralysis else .None,
|
|
2161
|
-
);
|
|
2143
|
+
if (Status.is(foe_stored.status, .PAR)) Fail.Paralysis else Fail.None,
|
|
2144
|
+
});
|
|
2162
2145
|
}
|
|
2163
2146
|
if (!showdown) {
|
|
2164
|
-
if (immune) return log.immune(foe_ident, .None);
|
|
2147
|
+
if (immune) return log.immune(.{ foe_ident, .None });
|
|
2165
2148
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2166
2149
|
}
|
|
2167
|
-
try options.chance.commit(player, .hit);
|
|
2168
2150
|
|
|
2169
2151
|
foe_stored.status = Status.init(.PAR);
|
|
2170
2152
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2171
2153
|
|
|
2172
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2154
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2173
2155
|
}
|
|
2174
2156
|
|
|
2175
2157
|
fn paralyzeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2191,12 +2173,12 @@ pub const Effects = struct {
|
|
|
2191
2173
|
foe_stored.status = Status.init(.PAR);
|
|
2192
2174
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2193
2175
|
|
|
2194
|
-
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
2176
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
2195
2177
|
}
|
|
2196
2178
|
|
|
2197
2179
|
fn payDay(battle: anytype, player: Player, options: anytype) !void {
|
|
2198
2180
|
if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
|
|
2199
|
-
try options.log.fieldactivate();
|
|
2181
|
+
try options.log.fieldactivate(.{});
|
|
2200
2182
|
}
|
|
2201
2183
|
}
|
|
2202
2184
|
|
|
@@ -2211,28 +2193,27 @@ pub const Effects = struct {
|
|
|
2211
2193
|
return;
|
|
2212
2194
|
} else if (foe.active.volatiles.Substitute) {
|
|
2213
2195
|
if (move.effect != .Poison) return;
|
|
2214
|
-
return log.fail(foe_ident, .None);
|
|
2196
|
+
return log.fail(.{ foe_ident, .None });
|
|
2215
2197
|
} else if (Status.any(foe_stored.status)) {
|
|
2216
2198
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2217
2199
|
// Pokémon Showdown considers Toxic to be a status even in Generation I and so
|
|
2218
2200
|
// will not include a fail reason for Toxic vs. Poison or vice-versa...
|
|
2219
|
-
return log.fail(foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2201
|
+
return log.fail(.{ foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2220
2202
|
if (!showdown)
|
|
2221
|
-
.Poison
|
|
2203
|
+
Fail.Poison
|
|
2222
2204
|
else if (toxic == (foe_stored.status == Status.TOX))
|
|
2223
|
-
if (toxic) .Toxic else .Poison
|
|
2205
|
+
if (toxic) Fail.Toxic else Fail.Poison
|
|
2224
2206
|
else
|
|
2225
|
-
.None
|
|
2207
|
+
Fail.None
|
|
2226
2208
|
else
|
|
2227
|
-
.None);
|
|
2209
|
+
Fail.None });
|
|
2228
2210
|
} else if (foe.active.types.includes(.Poison)) {
|
|
2229
2211
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2230
|
-
return log.immune(foe_ident, .None);
|
|
2212
|
+
return log.immune(.{ foe_ident, .None });
|
|
2231
2213
|
}
|
|
2232
2214
|
|
|
2233
2215
|
if (move.effect == .Poison) {
|
|
2234
2216
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
2235
|
-
try options.chance.commit(player, .hit);
|
|
2236
2217
|
} else {
|
|
2237
2218
|
if (!try Rolls.poisonChance(battle, player, move.effect == .PoisonChance1, options)) {
|
|
2238
2219
|
return;
|
|
@@ -2246,7 +2227,7 @@ pub const Effects = struct {
|
|
|
2246
2227
|
foe.active.volatiles.toxic = 0;
|
|
2247
2228
|
}
|
|
2248
2229
|
|
|
2249
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2230
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2250
2231
|
}
|
|
2251
2232
|
|
|
2252
2233
|
fn rage(battle: anytype, player: Player) !void {
|
|
@@ -2266,22 +2247,23 @@ pub const Effects = struct {
|
|
|
2266
2247
|
@as(u8, if (side.last_selected_move == .Struggle) 2 else 4), 1));
|
|
2267
2248
|
stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
|
|
2268
2249
|
|
|
2269
|
-
try options.log.
|
|
2250
|
+
try options.log.damage(.{
|
|
2270
2251
|
battle.active(player),
|
|
2271
2252
|
stored,
|
|
2272
2253
|
.RecoilOf,
|
|
2273
2254
|
battle.active(player.foe()),
|
|
2274
|
-
);
|
|
2255
|
+
});
|
|
2275
2256
|
if (showdown and stored.hp == 0) residual.* = false;
|
|
2276
2257
|
}
|
|
2277
2258
|
|
|
2278
2259
|
fn reflect(battle: anytype, player: Player, options: anytype) !void {
|
|
2279
2260
|
var side = battle.side(player);
|
|
2261
|
+
const ident = battle.active(player);
|
|
2280
2262
|
|
|
2281
|
-
if (side.active.volatiles.Reflect) return options.log.fail(
|
|
2263
|
+
if (side.active.volatiles.Reflect) return options.log.fail(.{ ident, .None });
|
|
2282
2264
|
side.active.volatiles.Reflect = true;
|
|
2283
2265
|
|
|
2284
|
-
try options.log.start(
|
|
2266
|
+
try options.log.start(.{ ident, .Reflect });
|
|
2285
2267
|
}
|
|
2286
2268
|
|
|
2287
2269
|
fn sleep(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2295,14 +2277,13 @@ pub const Effects = struct {
|
|
|
2295
2277
|
// the Sleep Clause Mod might activate, causing us to not actually bypass
|
|
2296
2278
|
} else {
|
|
2297
2279
|
if (Status.any(foe_stored.status)) {
|
|
2298
|
-
return options.log.fail(
|
|
2280
|
+
return options.log.fail(.{
|
|
2299
2281
|
foe_ident,
|
|
2300
|
-
if (Status.is(foe_stored.status, .SLP)) .Sleep else .None,
|
|
2301
|
-
);
|
|
2282
|
+
if (Status.is(foe_stored.status, .SLP)) Fail.Sleep else Fail.None,
|
|
2283
|
+
});
|
|
2302
2284
|
}
|
|
2303
|
-
// If
|
|
2285
|
+
// If moveHit in doMove didn't return true Pokémon Showdown wouldn't be in here
|
|
2304
2286
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
2305
|
-
try options.chance.commit(player, .hit);
|
|
2306
2287
|
}
|
|
2307
2288
|
|
|
2308
2289
|
// Sleep Clause Mod
|
|
@@ -2315,18 +2296,17 @@ pub const Effects = struct {
|
|
|
2315
2296
|
|
|
2316
2297
|
foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
|
|
2317
2298
|
const last = battle.side(player).last_selected_move;
|
|
2318
|
-
try options.log.
|
|
2299
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .From, last });
|
|
2319
2300
|
}
|
|
2320
2301
|
|
|
2321
2302
|
fn splash(battle: anytype, player: Player, options: anytype) !void {
|
|
2322
|
-
try options.log.activate(battle.active(player), .Splash);
|
|
2303
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
2323
2304
|
}
|
|
2324
2305
|
|
|
2325
2306
|
fn substitute(battle: anytype, player: Player, residual: *bool, options: anytype) !void {
|
|
2326
2307
|
var side = battle.side(player);
|
|
2327
2308
|
if (side.active.volatiles.Substitute) {
|
|
2328
|
-
|
|
2329
|
-
return;
|
|
2309
|
+
return options.log.fail(.{ battle.active(player), .Substitute });
|
|
2330
2310
|
}
|
|
2331
2311
|
|
|
2332
2312
|
assert(side.stored().stats.hp <= 1023);
|
|
@@ -2338,17 +2318,16 @@ pub const Effects = struct {
|
|
|
2338
2318
|
// avoid having to convert to floating point)
|
|
2339
2319
|
const required_hp = if (showdown) @divFloor(side.stored().stats.hp - 1, 4) + 1 else hp;
|
|
2340
2320
|
if (side.stored().hp < required_hp) {
|
|
2341
|
-
|
|
2342
|
-
return;
|
|
2321
|
+
return options.log.fail(.{ battle.active(player), .Weak });
|
|
2343
2322
|
}
|
|
2344
2323
|
|
|
2345
2324
|
// GLITCH: can leave the user with 0 HP (faints later) because didn't check '<=' above
|
|
2346
2325
|
side.stored().hp -= hp;
|
|
2347
2326
|
side.active.volatiles.substitute = hp + 1;
|
|
2348
2327
|
side.active.volatiles.Substitute = true;
|
|
2349
|
-
try options.log.start(battle.active(player), .Substitute);
|
|
2328
|
+
try options.log.start(.{ battle.active(player), .Substitute });
|
|
2350
2329
|
if (hp > 0) {
|
|
2351
|
-
try options.log.damage(battle.active(player), side.stored(), .None);
|
|
2330
|
+
try options.log.damage(.{ battle.active(player), side.stored(), .None });
|
|
2352
2331
|
if (showdown and side.stored().hp == 0) residual.* = false;
|
|
2353
2332
|
}
|
|
2354
2333
|
}
|
|
@@ -2357,13 +2336,11 @@ pub const Effects = struct {
|
|
|
2357
2336
|
// Whirlwind/Roar should not roll to hit/reset damage but Pokémon Showdown does anyway
|
|
2358
2337
|
if (showdown) {
|
|
2359
2338
|
if (battle.side(player).last_selected_move == .Teleport) return;
|
|
2360
|
-
|
|
2361
|
-
try options.chance.commit(player, .hit);
|
|
2362
|
-
}
|
|
2339
|
+
_ = try checkHit(battle, player, move, options);
|
|
2363
2340
|
battle.last_damage = 0;
|
|
2364
2341
|
} else {
|
|
2365
|
-
try options.log.fail(battle.active(player), .None);
|
|
2366
|
-
try options.log.laststill();
|
|
2342
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2343
|
+
try options.log.laststill(.{});
|
|
2367
2344
|
}
|
|
2368
2345
|
}
|
|
2369
2346
|
|
|
@@ -2402,7 +2379,7 @@ pub const Effects = struct {
|
|
|
2402
2379
|
side.active.moves[i].pp = if (m.id != .None) 5 else 0;
|
|
2403
2380
|
}
|
|
2404
2381
|
|
|
2405
|
-
try options.log.transform(battle.active(player), foe_ident);
|
|
2382
|
+
try options.log.transform(.{ battle.active(player), foe_ident });
|
|
2406
2383
|
}
|
|
2407
2384
|
|
|
2408
2385
|
fn binding(battle: anytype, player: Player, rewrap: bool, options: anytype) !void {
|
|
@@ -2432,87 +2409,87 @@ pub const Effects = struct {
|
|
|
2432
2409
|
switch (move.effect) {
|
|
2433
2410
|
.AttackUp1, .AttackUp2, .Rage => {
|
|
2434
2411
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2435
|
-
if (boosts.atk == 6) return
|
|
2412
|
+
if (boosts.atk == 6) return log.fail(.{ ident, .None });
|
|
2436
2413
|
const n: u2 = if (move.effect == .AttackUp2) 2 else 1;
|
|
2437
2414
|
boosts.atk = @as(i4, @intCast(@min(6, @as(i8, boosts.atk) + n)));
|
|
2438
|
-
const reason = if (move.effect == .Rage)
|
|
2415
|
+
const reason: Boost = if (move.effect == .Rage) .Rage else .Attack;
|
|
2439
2416
|
if (stats.atk == MAX_STAT_VALUE) {
|
|
2440
2417
|
boosts.atk -= 1;
|
|
2441
2418
|
if (showdown) {
|
|
2442
|
-
try log.boost(ident, reason, n);
|
|
2443
|
-
try log.boost(ident,
|
|
2419
|
+
try log.boost(.{ ident, reason, n });
|
|
2420
|
+
try log.boost(.{ ident, .Attack, -1 });
|
|
2444
2421
|
if (move.effect == .Rage) return;
|
|
2445
2422
|
}
|
|
2446
|
-
return
|
|
2423
|
+
return log.fail(.{ ident, .None });
|
|
2447
2424
|
}
|
|
2448
|
-
|
|
2425
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2449
2426
|
const stat = unmodifiedStats(battle, side).atk;
|
|
2450
2427
|
stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2451
|
-
try log.boost(ident, reason, n);
|
|
2428
|
+
try log.boost(.{ ident, reason, n });
|
|
2452
2429
|
// Pokémon Showdown doesn't re-apply status modifiers after Rage boosts
|
|
2453
2430
|
if (showdown and move.effect == .Rage) return;
|
|
2454
2431
|
},
|
|
2455
2432
|
.DefenseUp1, .DefenseUp2 => {
|
|
2456
2433
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2457
|
-
if (boosts.def == 6) return
|
|
2434
|
+
if (boosts.def == 6) return log.fail(.{ ident, .None });
|
|
2458
2435
|
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
2459
2436
|
boosts.def = @intCast(@min(6, @as(i8, boosts.def) + n));
|
|
2460
2437
|
if (stats.def == MAX_STAT_VALUE) {
|
|
2461
2438
|
boosts.def -= 1;
|
|
2462
2439
|
if (showdown) {
|
|
2463
|
-
try log.boost(ident, .Defense, n);
|
|
2464
|
-
try log.boost(ident, .Defense, -1);
|
|
2440
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2441
|
+
try log.boost(.{ ident, .Defense, -1 });
|
|
2465
2442
|
}
|
|
2466
|
-
return
|
|
2443
|
+
return log.fail(.{ ident, .None });
|
|
2467
2444
|
}
|
|
2468
|
-
|
|
2445
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2469
2446
|
const stat = unmodifiedStats(battle, side).def;
|
|
2470
2447
|
stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2471
|
-
try log.boost(ident, .Defense, n);
|
|
2448
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2472
2449
|
},
|
|
2473
2450
|
.SpeedUp2 => {
|
|
2474
2451
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2475
|
-
if (boosts.spe == 6) return
|
|
2452
|
+
if (boosts.spe == 6) return log.fail(.{ ident, .None });
|
|
2476
2453
|
boosts.spe = @intCast(@min(6, @as(i8, boosts.spe) + 2));
|
|
2477
2454
|
if (stats.spe == MAX_STAT_VALUE) {
|
|
2478
2455
|
boosts.spe -= 1;
|
|
2479
2456
|
if (showdown) {
|
|
2480
|
-
try log.boost(ident, .Speed, 2);
|
|
2481
|
-
try log.boost(ident, .Speed, -1);
|
|
2457
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2458
|
+
try log.boost(.{ ident, .Speed, -1 });
|
|
2482
2459
|
}
|
|
2483
|
-
return
|
|
2460
|
+
return log.fail(.{ ident, .None });
|
|
2484
2461
|
}
|
|
2485
|
-
|
|
2462
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2486
2463
|
const stat = unmodifiedStats(battle, side).spe;
|
|
2487
2464
|
stats.spe = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2488
|
-
try log.boost(ident, .Speed, 2);
|
|
2465
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2489
2466
|
},
|
|
2490
2467
|
.SpecialUp1, .SpecialUp2 => {
|
|
2491
2468
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2492
|
-
if (boosts.spc == 6) return
|
|
2469
|
+
if (boosts.spc == 6) return log.fail(.{ ident, .None });
|
|
2493
2470
|
const n: u2 = if (move.effect == .SpecialUp2) 2 else 1;
|
|
2494
2471
|
boosts.spc = @intCast(@min(6, @as(i8, boosts.spc) + n));
|
|
2495
2472
|
if (stats.spc == MAX_STAT_VALUE) {
|
|
2496
2473
|
boosts.spc -= 1;
|
|
2497
2474
|
if (showdown) {
|
|
2498
|
-
try log.boost(ident, .SpecialAttack, n);
|
|
2499
|
-
try log.boost(ident, .SpecialAttack, -1);
|
|
2500
|
-
try log.boost(ident, .SpecialDefense, n);
|
|
2501
|
-
try log.boost(ident, .SpecialDefense, -1);
|
|
2475
|
+
try log.boost(.{ ident, .SpecialAttack, n });
|
|
2476
|
+
try log.boost(.{ ident, .SpecialAttack, -1 });
|
|
2477
|
+
try log.boost(.{ ident, .SpecialDefense, n });
|
|
2478
|
+
try log.boost(.{ ident, .SpecialDefense, -1 });
|
|
2502
2479
|
}
|
|
2503
|
-
return
|
|
2480
|
+
return log.fail(.{ ident, .None });
|
|
2504
2481
|
}
|
|
2505
|
-
|
|
2482
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2506
2483
|
const stat = unmodifiedStats(battle, side).spc;
|
|
2507
2484
|
stats.spc = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2508
|
-
try log.boost(ident, .SpecialAttack, n);
|
|
2509
|
-
try log.boost(ident, .SpecialDefense, n);
|
|
2485
|
+
try log.boost(.{ ident, .SpecialAttack, n });
|
|
2486
|
+
try log.boost(.{ ident, .SpecialDefense, n });
|
|
2510
2487
|
},
|
|
2511
2488
|
.EvasionUp1 => {
|
|
2512
2489
|
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2513
|
-
if (boosts.evasion == 6) return
|
|
2490
|
+
if (boosts.evasion == 6) return log.fail(.{ ident, .None });
|
|
2514
2491
|
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2515
|
-
try log.boost(ident, .Evasion, 1);
|
|
2492
|
+
try log.boost(.{ ident, .Evasion, 1 });
|
|
2516
2493
|
},
|
|
2517
2494
|
else => unreachable,
|
|
2518
2495
|
}
|
|
@@ -2527,7 +2504,7 @@ pub const Effects = struct {
|
|
|
2527
2504
|
const foe_ident = battle.active(player.foe());
|
|
2528
2505
|
const secondary = move.effect.isStatDownChance();
|
|
2529
2506
|
|
|
2530
|
-
if (foe.active.volatiles.Substitute) return if (!secondary) log.fail(foe_ident, .None);
|
|
2507
|
+
if (foe.active.volatiles.Substitute) return if (!secondary) log.fail(.{ foe_ident, .None });
|
|
2531
2508
|
|
|
2532
2509
|
if (secondary) {
|
|
2533
2510
|
const proc = try Rolls.unboost(battle, player, options);
|
|
@@ -2535,7 +2512,6 @@ pub const Effects = struct {
|
|
|
2535
2512
|
} else {
|
|
2536
2513
|
// checkHit already checks for Invulnerable
|
|
2537
2514
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
2538
|
-
try options.chance.commit(player, .hit);
|
|
2539
2515
|
}
|
|
2540
2516
|
|
|
2541
2517
|
var stats = &foe.active.stats;
|
|
@@ -2545,82 +2521,82 @@ pub const Effects = struct {
|
|
|
2545
2521
|
switch (move.effect) {
|
|
2546
2522
|
.AttackDown1, .AttackDownChance => {
|
|
2547
2523
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2548
|
-
if (boosts.atk == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2524
|
+
if (boosts.atk == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2549
2525
|
boosts.atk = @intCast(@max(-6, @as(i8, boosts.atk) - 1));
|
|
2550
2526
|
if (stats.atk == 1) {
|
|
2551
2527
|
boosts.atk += 1;
|
|
2552
2528
|
if (showdown) {
|
|
2553
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2554
|
-
try log.boost(foe_ident, .Attack, 1);
|
|
2529
|
+
try log.boost(.{ foe_ident, .Attack, -1 });
|
|
2530
|
+
try log.boost(.{ foe_ident, .Attack, 1 });
|
|
2555
2531
|
}
|
|
2556
|
-
return
|
|
2532
|
+
return log.fail(.{ foe_ident, .None });
|
|
2557
2533
|
}
|
|
2558
|
-
|
|
2534
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2559
2535
|
const stat = unmodifiedStats(battle, foe).atk;
|
|
2560
2536
|
stats.atk = @max(1, stat * mod[0] / mod[1]);
|
|
2561
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2537
|
+
try log.boost(.{ foe_ident, .Attack, -1 });
|
|
2562
2538
|
},
|
|
2563
2539
|
.DefenseDown1, .DefenseDown2, .DefenseDownChance => {
|
|
2564
2540
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2565
|
-
if (boosts.def == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2541
|
+
if (boosts.def == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2566
2542
|
const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
|
|
2567
2543
|
boosts.def = @intCast(@max(-6, @as(i8, boosts.def) - n));
|
|
2568
2544
|
if (stats.def == 1) {
|
|
2569
2545
|
boosts.def += 1;
|
|
2570
2546
|
if (showdown) {
|
|
2571
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2572
|
-
try log.boost(foe_ident, .Defense, 1);
|
|
2547
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2548
|
+
try log.boost(.{ foe_ident, .Defense, 1 });
|
|
2573
2549
|
}
|
|
2574
|
-
return
|
|
2550
|
+
return log.fail(.{ foe_ident, .None });
|
|
2575
2551
|
}
|
|
2576
|
-
|
|
2552
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2577
2553
|
const stat = unmodifiedStats(battle, foe).def;
|
|
2578
2554
|
stats.def = @max(1, stat * mod[0] / mod[1]);
|
|
2579
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2555
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2580
2556
|
},
|
|
2581
2557
|
.SpeedDown1, .SpeedDownChance => {
|
|
2582
2558
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2583
|
-
if (boosts.spe == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2559
|
+
if (boosts.spe == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2584
2560
|
boosts.spe = @intCast(@max(-6, @as(i8, boosts.spe) - 1));
|
|
2585
2561
|
if (stats.spe == 1) {
|
|
2586
2562
|
boosts.spe += 1;
|
|
2587
2563
|
if (showdown) {
|
|
2588
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2589
|
-
try log.boost(foe_ident, .Speed, 1);
|
|
2564
|
+
try log.boost(.{ foe_ident, .Speed, -1 });
|
|
2565
|
+
try log.boost(.{ foe_ident, .Speed, 1 });
|
|
2590
2566
|
}
|
|
2591
|
-
return
|
|
2567
|
+
return log.fail(.{ foe_ident, .None });
|
|
2592
2568
|
}
|
|
2593
|
-
|
|
2569
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2594
2570
|
const stat = unmodifiedStats(battle, foe).spe;
|
|
2595
2571
|
stats.spe = @max(1, stat * mod[0] / mod[1]);
|
|
2596
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2572
|
+
try log.boost(.{ foe_ident, .Speed, -1 });
|
|
2597
2573
|
assert(boosts.spe >= -6);
|
|
2598
2574
|
},
|
|
2599
2575
|
.SpecialDownChance => {
|
|
2600
2576
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2601
|
-
if (boosts.spc == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2577
|
+
if (boosts.spc == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2602
2578
|
boosts.spc = @intCast(@max(-6, @as(i8, boosts.spc) - 1));
|
|
2603
2579
|
if (stats.spc == 1) {
|
|
2604
2580
|
boosts.spc += 1;
|
|
2605
2581
|
if (showdown) {
|
|
2606
|
-
try log.boost(foe_ident, .SpecialAttack, -1);
|
|
2607
|
-
try log.boost(foe_ident, .SpecialAttack, 1);
|
|
2608
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2609
|
-
try log.boost(foe_ident, .SpecialDefense, 1);
|
|
2582
|
+
try log.boost(.{ foe_ident, .SpecialAttack, -1 });
|
|
2583
|
+
try log.boost(.{ foe_ident, .SpecialAttack, 1 });
|
|
2584
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2585
|
+
try log.boost(.{ foe_ident, .SpecialDefense, 1 });
|
|
2610
2586
|
}
|
|
2611
|
-
return
|
|
2587
|
+
return log.fail(.{ foe_ident, .None });
|
|
2612
2588
|
}
|
|
2613
|
-
|
|
2589
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2614
2590
|
const stat = unmodifiedStats(battle, foe).spc;
|
|
2615
2591
|
stats.spc = @max(1, stat * mod[0] / mod[1]);
|
|
2616
|
-
try log.boost(foe_ident, .SpecialAttack, -1);
|
|
2617
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2592
|
+
try log.boost(.{ foe_ident, .SpecialAttack, -1 });
|
|
2593
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2618
2594
|
},
|
|
2619
2595
|
.AccuracyDown1 => {
|
|
2620
2596
|
assert(boosts.accuracy >= -6 and boosts.accuracy <= 6);
|
|
2621
|
-
if (boosts.accuracy == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2597
|
+
if (boosts.accuracy == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2622
2598
|
boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
|
|
2623
|
-
try log.boost(foe_ident, .Accuracy, -1);
|
|
2599
|
+
try log.boost(.{ foe_ident, .Accuracy, -1 });
|
|
2624
2600
|
},
|
|
2625
2601
|
else => unreachable,
|
|
2626
2602
|
}
|
|
@@ -2644,7 +2620,7 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
2644
2620
|
}
|
|
2645
2621
|
}
|
|
2646
2622
|
|
|
2647
|
-
|
|
2623
|
+
fn isForced(active: anytype) bool {
|
|
2648
2624
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
2649
2625
|
active.volatiles.Thrashing or active.volatiles.Charging;
|
|
2650
2626
|
}
|
|
@@ -2660,24 +2636,24 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2660
2636
|
if (volatiles.disable_move != 0) {
|
|
2661
2637
|
volatiles.disable_move = 0;
|
|
2662
2638
|
volatiles.disable_duration = 0;
|
|
2663
|
-
try log.end(ident, .DisableSilent);
|
|
2639
|
+
try log.end(.{ ident, .DisableSilent });
|
|
2664
2640
|
}
|
|
2665
2641
|
if (volatiles.Confusion) {
|
|
2666
2642
|
// volatiles.confusion is left unchanged
|
|
2667
2643
|
volatiles.Confusion = false;
|
|
2668
|
-
try log.end(ident, .ConfusionSilent);
|
|
2644
|
+
try log.end(.{ ident, .ConfusionSilent });
|
|
2669
2645
|
}
|
|
2670
2646
|
if (volatiles.Mist) {
|
|
2671
2647
|
volatiles.Mist = false;
|
|
2672
|
-
try log.end(ident, .MistSilent);
|
|
2648
|
+
try log.end(.{ ident, .MistSilent });
|
|
2673
2649
|
}
|
|
2674
2650
|
if (volatiles.FocusEnergy) {
|
|
2675
2651
|
volatiles.FocusEnergy = false;
|
|
2676
|
-
try log.end(ident, .FocusEnergySilent);
|
|
2652
|
+
try log.end(.{ ident, .FocusEnergySilent });
|
|
2677
2653
|
}
|
|
2678
2654
|
if (volatiles.LeechSeed) {
|
|
2679
2655
|
volatiles.LeechSeed = false;
|
|
2680
|
-
try log.end(ident, .LeechSeedSilent);
|
|
2656
|
+
try log.end(.{ ident, .LeechSeedSilent });
|
|
2681
2657
|
}
|
|
2682
2658
|
if (!showdown and volatiles.Toxic) {
|
|
2683
2659
|
volatiles.Toxic = false;
|
|
@@ -2685,24 +2661,26 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2685
2661
|
}
|
|
2686
2662
|
if (volatiles.LightScreen) {
|
|
2687
2663
|
volatiles.LightScreen = false;
|
|
2688
|
-
try log.end(ident, .LightScreenSilent);
|
|
2664
|
+
try log.end(.{ ident, .LightScreenSilent });
|
|
2689
2665
|
}
|
|
2690
2666
|
if (volatiles.Reflect) {
|
|
2691
2667
|
volatiles.Reflect = false;
|
|
2692
|
-
try log.end(ident, .ReflectSilent);
|
|
2668
|
+
try log.end(.{ ident, .ReflectSilent });
|
|
2693
2669
|
}
|
|
2694
2670
|
if (showdown and volatiles.Toxic) {
|
|
2695
2671
|
volatiles.Toxic = false;
|
|
2696
2672
|
// Pokémon Showdown erroneously clears the toxic counter
|
|
2697
2673
|
volatiles.toxic = 0;
|
|
2698
|
-
try log.end(ident, .ToxicSilent);
|
|
2674
|
+
try log.end(.{ ident, .ToxicSilent });
|
|
2699
2675
|
}
|
|
2700
2676
|
}
|
|
2701
2677
|
|
|
2702
2678
|
pub const Rolls = struct {
|
|
2703
|
-
|
|
2679
|
+
fn speedTie(battle: anytype, options: anytype) !bool {
|
|
2704
2680
|
const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
|
|
2705
2681
|
player == .P1
|
|
2682
|
+
else if (@hasDecl(@TypeOf(battle.rng), "speedTie"))
|
|
2683
|
+
battle.rng.speedTie()
|
|
2706
2684
|
else if (showdown)
|
|
2707
2685
|
battle.rng.range(u8, 0, 2) == 0
|
|
2708
2686
|
else
|
|
@@ -2712,21 +2690,31 @@ pub const Rolls = struct {
|
|
|
2712
2690
|
return p1;
|
|
2713
2691
|
}
|
|
2714
2692
|
|
|
2715
|
-
|
|
2693
|
+
fn criticalHit(
|
|
2694
|
+
battle: anytype,
|
|
2695
|
+
player: Player,
|
|
2696
|
+
rate: u8,
|
|
2697
|
+
spurious: bool,
|
|
2698
|
+
options: anytype,
|
|
2699
|
+
) !bool {
|
|
2716
2700
|
const crit = if (options.calc.overridden(player, .critical_hit)) |val|
|
|
2717
2701
|
val == .true
|
|
2702
|
+
else if (@hasDecl(@TypeOf(battle.rng), "criticalHit"))
|
|
2703
|
+
battle.rng.criticalHit(player, rate)
|
|
2718
2704
|
else if (showdown)
|
|
2719
2705
|
battle.rng.chance(u8, rate, 256)
|
|
2720
2706
|
else
|
|
2721
2707
|
std.math.rotl(u8, battle.rng.next(), 3) < rate;
|
|
2722
2708
|
|
|
2723
|
-
try options.chance.criticalHit(player, crit, rate);
|
|
2709
|
+
if (!spurious) try options.chance.criticalHit(player, crit, rate);
|
|
2724
2710
|
return crit;
|
|
2725
2711
|
}
|
|
2726
2712
|
|
|
2727
|
-
|
|
2713
|
+
fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2728
2714
|
const roll = if (options.calc.overridden(player, .damage)) |val|
|
|
2729
2715
|
val
|
|
2716
|
+
else if (@hasDecl(@TypeOf(battle.rng), "damage"))
|
|
2717
|
+
battle.rng.damage(player)
|
|
2730
2718
|
else roll: {
|
|
2731
2719
|
if (showdown) break :roll battle.rng.range(u8, 217, 256);
|
|
2732
2720
|
while (true) {
|
|
@@ -2740,21 +2728,27 @@ pub const Rolls = struct {
|
|
|
2740
2728
|
return roll;
|
|
2741
2729
|
}
|
|
2742
2730
|
|
|
2743
|
-
|
|
2731
|
+
const uN = if (pkmn.options.miss) u8 else u9;
|
|
2732
|
+
fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) !bool {
|
|
2733
|
+
const acc: uN = if (pkmn.options.miss) accuracy else @as(uN, accuracy) + 1;
|
|
2744
2734
|
const ok = if (options.calc.overridden(player, .hit)) |val|
|
|
2745
2735
|
val == .true
|
|
2736
|
+
else if (@hasDecl(@TypeOf(battle.rng), "hit"))
|
|
2737
|
+
battle.rng.hit(player, acc)
|
|
2746
2738
|
else if (showdown)
|
|
2747
|
-
battle.rng.chance(
|
|
2739
|
+
battle.rng.chance(uN, acc, 256)
|
|
2748
2740
|
else
|
|
2749
|
-
battle.rng.next() <
|
|
2741
|
+
battle.rng.next() < acc;
|
|
2750
2742
|
|
|
2751
|
-
options.chance.hit(ok,
|
|
2743
|
+
try options.chance.hit(player, ok, acc);
|
|
2752
2744
|
return ok;
|
|
2753
2745
|
}
|
|
2754
2746
|
|
|
2755
|
-
|
|
2747
|
+
fn confused(battle: anytype, player: Player, options: anytype) !bool {
|
|
2756
2748
|
const cfz = if (options.calc.overridden(player, .confused)) |val|
|
|
2757
2749
|
val == .true
|
|
2750
|
+
else if (@hasDecl(@TypeOf(battle.rng), "confused"))
|
|
2751
|
+
battle.rng.confused(player)
|
|
2758
2752
|
else if (showdown)
|
|
2759
2753
|
!battle.rng.chance(u8, 128, 256)
|
|
2760
2754
|
else
|
|
@@ -2764,9 +2758,11 @@ pub const Rolls = struct {
|
|
|
2764
2758
|
return cfz;
|
|
2765
2759
|
}
|
|
2766
2760
|
|
|
2767
|
-
|
|
2761
|
+
fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
|
|
2768
2762
|
const par = if (options.calc.overridden(player, .paralyzed)) |val|
|
|
2769
2763
|
val == .true
|
|
2764
|
+
else if (@hasDecl(@TypeOf(battle.rng), "paralyzed"))
|
|
2765
|
+
battle.rng.paralyzed(player)
|
|
2770
2766
|
else if (showdown)
|
|
2771
2767
|
battle.rng.chance(u8, 63, 256)
|
|
2772
2768
|
else
|
|
@@ -2776,9 +2772,11 @@ pub const Rolls = struct {
|
|
|
2776
2772
|
return par;
|
|
2777
2773
|
}
|
|
2778
2774
|
|
|
2779
|
-
|
|
2775
|
+
fn confusionChance(battle: anytype, player: Player, options: anytype) !bool {
|
|
2780
2776
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2781
2777
|
val == .true
|
|
2778
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2779
|
+
battle.rng.secondaryChance(player, 25)
|
|
2782
2780
|
else if (showdown)
|
|
2783
2781
|
battle.rng.chance(u8, 25, 256)
|
|
2784
2782
|
else
|
|
@@ -2788,11 +2786,13 @@ pub const Rolls = struct {
|
|
|
2788
2786
|
return proc;
|
|
2789
2787
|
}
|
|
2790
2788
|
|
|
2791
|
-
|
|
2789
|
+
fn secondaryChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2792
2790
|
const rate: u8 = if (low) 26 else 77;
|
|
2793
2791
|
|
|
2794
2792
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2795
2793
|
val == .true
|
|
2794
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2795
|
+
battle.rng.secondaryChance(player, rate)
|
|
2796
2796
|
else if (showdown)
|
|
2797
2797
|
battle.rng.chance(u8, rate, 256)
|
|
2798
2798
|
else
|
|
@@ -2802,11 +2802,13 @@ pub const Rolls = struct {
|
|
|
2802
2802
|
return proc;
|
|
2803
2803
|
}
|
|
2804
2804
|
|
|
2805
|
-
|
|
2805
|
+
fn poisonChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2806
2806
|
const rate: u8 = if (low) 52 else 103;
|
|
2807
2807
|
|
|
2808
2808
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2809
2809
|
val == .true
|
|
2810
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2811
|
+
battle.rng.secondaryChance(player, rate)
|
|
2810
2812
|
else if (showdown)
|
|
2811
2813
|
battle.rng.chance(u8, rate, 256)
|
|
2812
2814
|
else
|
|
@@ -2816,9 +2818,11 @@ pub const Rolls = struct {
|
|
|
2816
2818
|
return proc;
|
|
2817
2819
|
}
|
|
2818
2820
|
|
|
2819
|
-
|
|
2821
|
+
fn unboost(battle: anytype, player: Player, options: anytype) !bool {
|
|
2820
2822
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2821
2823
|
val == .true
|
|
2824
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2825
|
+
battle.rng.secondaryChance(player, 85)
|
|
2822
2826
|
else if (showdown)
|
|
2823
2827
|
battle.rng.chance(u8, 85, 256)
|
|
2824
2828
|
else
|
|
@@ -2828,9 +2832,11 @@ pub const Rolls = struct {
|
|
|
2828
2832
|
return proc;
|
|
2829
2833
|
}
|
|
2830
2834
|
|
|
2831
|
-
|
|
2835
|
+
fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
2832
2836
|
const move: Move = if (options.calc.overridden(player, .metronome)) |val|
|
|
2833
2837
|
val
|
|
2838
|
+
else if (@hasDecl(@TypeOf(battle.rng), "metronome"))
|
|
2839
|
+
battle.rng.metronome(player)
|
|
2834
2840
|
else if (showdown)
|
|
2835
2841
|
Move.METRONOME[battle.rng.range(u8, 0, Move.METRONOME.len)]
|
|
2836
2842
|
else move: {
|
|
@@ -2846,9 +2852,11 @@ pub const Rolls = struct {
|
|
|
2846
2852
|
return move;
|
|
2847
2853
|
}
|
|
2848
2854
|
|
|
2849
|
-
|
|
2855
|
+
fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
|
|
2850
2856
|
const power = if (options.calc.overridden(player, .psywave)) |val|
|
|
2851
2857
|
val - 1
|
|
2858
|
+
else if (@hasDecl(@TypeOf(battle.rng), "psywave"))
|
|
2859
|
+
battle.rng.psywave(player)
|
|
2852
2860
|
else if (showdown)
|
|
2853
2861
|
battle.rng.range(u8, 0, max)
|
|
2854
2862
|
else power: {
|
|
@@ -2863,9 +2871,11 @@ pub const Rolls = struct {
|
|
|
2863
2871
|
return power;
|
|
2864
2872
|
}
|
|
2865
2873
|
|
|
2866
|
-
|
|
2874
|
+
fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2867
2875
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2868
2876
|
@intCast(val)
|
|
2877
|
+
else if (@hasDecl(@TypeOf(battle.rng), "sleepDuration"))
|
|
2878
|
+
battle.rng.sleepDuration(player)
|
|
2869
2879
|
else if (showdown)
|
|
2870
2880
|
battle.rng.range(u3, 1, 8)
|
|
2871
2881
|
else duration: {
|
|
@@ -2880,9 +2890,11 @@ pub const Rolls = struct {
|
|
|
2880
2890
|
return duration;
|
|
2881
2891
|
}
|
|
2882
2892
|
|
|
2883
|
-
|
|
2893
|
+
fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
|
|
2884
2894
|
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2885
2895
|
val
|
|
2896
|
+
else if (@hasDecl(@TypeOf(battle.rng), "disableDuration"))
|
|
2897
|
+
battle.rng.disableDuration(player)
|
|
2886
2898
|
else if (showdown)
|
|
2887
2899
|
battle.rng.range(u4, 1, 9)
|
|
2888
2900
|
else
|
|
@@ -2893,9 +2905,11 @@ pub const Rolls = struct {
|
|
|
2893
2905
|
return duration;
|
|
2894
2906
|
}
|
|
2895
2907
|
|
|
2896
|
-
|
|
2908
|
+
fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
|
|
2897
2909
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2898
2910
|
@intCast(val)
|
|
2911
|
+
else if (@hasDecl(@TypeOf(battle.rng), "confusionDuration"))
|
|
2912
|
+
battle.rng.confusionDuration(player, self)
|
|
2899
2913
|
else if (showdown)
|
|
2900
2914
|
battle.rng.range(u3, 2, 6)
|
|
2901
2915
|
else
|
|
@@ -2906,9 +2920,11 @@ pub const Rolls = struct {
|
|
|
2906
2920
|
return duration;
|
|
2907
2921
|
}
|
|
2908
2922
|
|
|
2909
|
-
|
|
2923
|
+
fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2910
2924
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2911
2925
|
@intCast(val)
|
|
2926
|
+
else if (@hasDecl(@TypeOf(battle.rng), "attackingDuration"))
|
|
2927
|
+
battle.rng.attackingDuration(player)
|
|
2912
2928
|
else if (showdown)
|
|
2913
2929
|
battle.rng.range(u3, 2, 4)
|
|
2914
2930
|
else
|
|
@@ -2921,7 +2937,7 @@ pub const Rolls = struct {
|
|
|
2921
2937
|
|
|
2922
2938
|
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
2923
2939
|
|
|
2924
|
-
|
|
2940
|
+
fn distribution(
|
|
2925
2941
|
battle: anytype,
|
|
2926
2942
|
comptime effect: Move.Effect,
|
|
2927
2943
|
player: Player,
|
|
@@ -2930,6 +2946,8 @@ pub const Rolls = struct {
|
|
|
2930
2946
|
const roll = if (effect == .Binding) .duration else .multi_hit;
|
|
2931
2947
|
const n: u3 = if (options.calc.overridden(player, roll)) |val|
|
|
2932
2948
|
@intCast(val)
|
|
2949
|
+
else if (@hasDecl(@TypeOf(battle.rng), "distribution"))
|
|
2950
|
+
battle.rng.distribution(player)
|
|
2933
2951
|
else if (showdown)
|
|
2934
2952
|
DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
|
|
2935
2953
|
else n: {
|
|
@@ -2962,7 +2980,9 @@ pub const Rolls = struct {
|
|
|
2962
2980
|
else
|
|
2963
2981
|
null;
|
|
2964
2982
|
|
|
2965
|
-
const slot: u4 = overridden orelse
|
|
2983
|
+
const slot: u4 = overridden orelse if (@hasDecl(@TypeOf(battle.rng), "moveSlot"))
|
|
2984
|
+
battle.rng.moveSlot(player, moves, check_pp)
|
|
2985
|
+
else slot: {
|
|
2966
2986
|
if (showdown) {
|
|
2967
2987
|
if (check_pp == 0) {
|
|
2968
2988
|
var i: usize = moves.len;
|
|
@@ -3042,11 +3062,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3042
3062
|
const side = battle.side(player);
|
|
3043
3063
|
const foe = battle.foe(player);
|
|
3044
3064
|
|
|
3045
|
-
|
|
3065
|
+
const active = &side.active;
|
|
3046
3066
|
const stored = side.stored();
|
|
3047
3067
|
|
|
3048
3068
|
// While players are not given any input options on the cartridge in these cases,
|
|
3049
|
-
// Pokémon Showdown instead produces a list with a single move that must be chosen
|
|
3069
|
+
// Pokémon Showdown instead produces a list with a single move that must be chosen
|
|
3050
3070
|
//
|
|
3051
3071
|
// Given that no input is allowed on the cartridge 'Pass' seems like it would be logical
|
|
3052
3072
|
// here when not in compatibility mode, but the engine needs to be able to differentiate
|
|
@@ -3054,7 +3074,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3054
3074
|
// passing due to being forced into using a move. Instead of introducing another option
|
|
3055
3075
|
// we simply repurpose Move with no move slot, even though pedantically this is not
|
|
3056
3076
|
// strictly correct as the player would not have been presented the option to move or
|
|
3057
|
-
// switch at all
|
|
3077
|
+
// switch at all
|
|
3058
3078
|
if (isForced(active)) {
|
|
3059
3079
|
out[n] = .{ .type = .Move, .data = @intFromBool(showdown) };
|
|
3060
3080
|
n += 1;
|
|
@@ -3073,7 +3093,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3073
3093
|
// On the cartridge, all of these happen after "FIGHT" (indicating you are not
|
|
3074
3094
|
// switching) but before you are allowed to select a move. Pokémon Showdown instead
|
|
3075
3095
|
// either disables all other moves in the case of limited or requires you to select a
|
|
3076
|
-
// move normally if sleeping/frozen/bound
|
|
3096
|
+
// move normally if sleeping/frozen/bound
|
|
3077
3097
|
if (!showdown and (limited or foe.active.volatiles.Binding or
|
|
3078
3098
|
Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP)))
|
|
3079
3099
|
{
|
|
@@ -3086,7 +3106,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3086
3106
|
// Pokémon Showdown handles Bide and Binding moves by checking if the move in question
|
|
3087
3107
|
// is present in the Pokémon's moveset (which means moves called via Metronome / Mirror
|
|
3088
3108
|
// Move will not result in forcing the subsequent use unless the user also had the
|
|
3089
|
-
// proc-ed move in their moveset) and disabling all other moves
|
|
3109
|
+
// proc-ed move in their moveset) and disabling all other moves
|
|
3090
3110
|
if (limited) {
|
|
3091
3111
|
assert(showdown);
|
|
3092
3112
|
assert(side.last_selected_move != .None);
|