@pkmn/engine 0.1.0-dev.a93847b2 → 0.1.0-dev.abd68573
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 +39 -16
- 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 +90 -76
- package/package.json +31 -23
- package/src/bin/install-pkmn-engine +26 -18
- package/src/lib/common/data.zig +8 -8
- package/src/lib/common/optional.zig +1 -1
- package/src/lib/common/protocol.zig +393 -396
- package/src/lib/common/rational.zig +11 -7
- package/src/lib/common/rng.zig +20 -11
- package/src/lib/gen1/calc.zig +7 -7
- package/src/lib/gen1/chance.zig +31 -30
- package/src/lib/gen1/data/moves.zig +9 -9
- package/src/lib/gen1/data/species.zig +2 -2
- package/src/lib/gen1/data/types.zig +4 -4
- package/src/lib/gen1/data.zig +32 -32
- package/src/lib/gen1/helpers.zig +48 -28
- package/src/lib/gen1/mechanics.zig +277 -255
- package/src/lib/gen2/calc.zig +1 -1
- package/src/lib/gen2/chance.zig +42 -41
- package/src/lib/gen2/data/items.zig +2 -2
- package/src/lib/gen2/data/moves.zig +2 -2
- package/src/lib/gen2/data/types.zig +9 -9
- package/src/lib/gen2/data.zig +23 -21
- package/src/lib/gen2/generated.zig +27 -78
- package/src/lib/gen2/mechanics.zig +1052 -459
- 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
|
|
|
@@ -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
|
}
|
|
@@ -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()) {
|
|
@@ -901,7 +895,7 @@ fn doMove(
|
|
|
901
895
|
}
|
|
902
896
|
}
|
|
903
897
|
|
|
904
|
-
|
|
898
|
+
const zero = battle.last_damage == 0;
|
|
905
899
|
miss = if (showdown or skip)
|
|
906
900
|
miss
|
|
907
901
|
else
|
|
@@ -929,16 +923,16 @@ fn doMove(
|
|
|
929
923
|
ohko = (!showdown or (!immune and !invulnerable)) and
|
|
930
924
|
move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe;
|
|
931
925
|
if (ohko) {
|
|
932
|
-
try log.immune(foe_ident, .OHKO);
|
|
926
|
+
try log.immune(.{ foe_ident, .OHKO });
|
|
933
927
|
} else if (immune and !invulnerable and (!showdown or move.effect != .Binding)) {
|
|
934
|
-
try log.immune(foe_ident, .None);
|
|
928
|
+
try log.immune(.{ foe_ident, .None });
|
|
935
929
|
} else if (mist) {
|
|
936
|
-
if (!foe.active.volatiles.Substitute) try log.activate(foe_ident, .Mist);
|
|
937
|
-
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 });
|
|
938
932
|
} else {
|
|
939
933
|
if (showdown or !zero) try options.chance.commit(player, .miss);
|
|
940
|
-
try log.lastmiss();
|
|
941
|
-
try log.miss(battle.active(player));
|
|
934
|
+
try log.lastmiss(.{});
|
|
935
|
+
try log.miss(.{battle.active(player)});
|
|
942
936
|
}
|
|
943
937
|
if (move.effect == .JumpKick) {
|
|
944
938
|
// Recoil is supposed to be damage/8 but damage will always be 0 here
|
|
@@ -974,15 +968,15 @@ fn doMove(
|
|
|
974
968
|
var hit: u4 = 0;
|
|
975
969
|
while (hit < hits) {
|
|
976
970
|
if (hit == 0) {
|
|
977
|
-
if (crit) try log.crit(battle.active(player.foe()));
|
|
971
|
+
if (crit) try log.crit(.{battle.active(player.foe())});
|
|
978
972
|
if (effectiveness > Effectiveness.neutral) {
|
|
979
|
-
try log.supereffective(battle.active(player.foe()));
|
|
973
|
+
try log.supereffective(.{battle.active(player.foe())});
|
|
980
974
|
} else if (effectiveness < Effectiveness.neutral) {
|
|
981
|
-
try log.resisted(battle.active(player.foe()));
|
|
975
|
+
try log.resisted(.{battle.active(player.foe())});
|
|
982
976
|
}
|
|
983
977
|
}
|
|
984
978
|
if (!skip) nullified = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
985
|
-
if (hit == 0 and ohko) try log.ohko();
|
|
979
|
+
if (hit == 0 and ohko) try log.ohko(.{});
|
|
986
980
|
hit += 1;
|
|
987
981
|
if (foe.stored().hp == 0) break;
|
|
988
982
|
if (!late and (!sub or move.effect == .Explode)) {
|
|
@@ -999,7 +993,7 @@ fn doMove(
|
|
|
999
993
|
if (showdown and move.effect == .Twineedle and !nullified and foe.stored().hp > 0) {
|
|
1000
994
|
try Effects.poison(battle, player, Move.get(.PoisonSting), options);
|
|
1001
995
|
}
|
|
1002
|
-
try log.hitcount(battle.active(player.foe()), hit);
|
|
996
|
+
try log.hitcount(.{ battle.active(player.foe()), hit });
|
|
1003
997
|
} else if (showdown) {
|
|
1004
998
|
// This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
|
|
1005
999
|
if (move.effect == .Binding) {
|
|
@@ -1009,9 +1003,9 @@ fn doMove(
|
|
|
1009
1003
|
assert(foe.stored().hp > 0);
|
|
1010
1004
|
// Pokémon Showdown logs |-damage| here instead of |-immune| because logic...
|
|
1011
1005
|
if (sub) {
|
|
1012
|
-
try log.activate(battle.active(player.foe()), .Substitute);
|
|
1006
|
+
try log.activate(.{ battle.active(player.foe()), .Substitute });
|
|
1013
1007
|
} else {
|
|
1014
|
-
try log.damage(battle.active(player.foe()), foe.stored(), .None);
|
|
1008
|
+
try log.damage(.{ battle.active(player.foe()), foe.stored(), .None });
|
|
1015
1009
|
try buildRage(battle, player.foe(), options);
|
|
1016
1010
|
}
|
|
1017
1011
|
return null;
|
|
@@ -1059,7 +1053,7 @@ fn checkCriticalHit(battle: anytype, player: Player, move: Move.Data, options: a
|
|
|
1059
1053
|
const side = battle.side(player);
|
|
1060
1054
|
|
|
1061
1055
|
// Base speed is used for the critical hit calculation, even when Transform-ed
|
|
1062
|
-
var rate =
|
|
1056
|
+
var rate: u16 = Species.chance(side.stored().species);
|
|
1063
1057
|
// GLITCH: Focus Energy reduces critical hit rate instead of increasing it
|
|
1064
1058
|
rate = if (side.active.volatiles.FocusEnergy) rate / 2 else @min(rate * 2, 255);
|
|
1065
1059
|
rate = if (move.effect == .HighCritical) @min(rate * 4, 255) else rate / 2;
|
|
@@ -1116,9 +1110,9 @@ fn calcDamage(
|
|
|
1116
1110
|
def = @max((def / 4) & 255, if (showdown) 1 else 0);
|
|
1117
1111
|
}
|
|
1118
1112
|
|
|
1119
|
-
const lvl =
|
|
1113
|
+
const lvl: u32 = side.stored().level * @as(u2, if (crit) 2 else 1);
|
|
1120
1114
|
|
|
1121
|
-
def =
|
|
1115
|
+
def = if (move.effect == .Explode) @max(def / 2, 1) else def;
|
|
1122
1116
|
|
|
1123
1117
|
if (def == 0) return false;
|
|
1124
1118
|
|
|
@@ -1209,7 +1203,7 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1209
1203
|
const foe = battle.foe(player);
|
|
1210
1204
|
|
|
1211
1205
|
if (battle.last_damage == 0) {
|
|
1212
|
-
try options.log.fail(battle.active(player), .None);
|
|
1206
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1213
1207
|
return null;
|
|
1214
1208
|
}
|
|
1215
1209
|
|
|
@@ -1227,14 +1221,14 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1227
1221
|
foe_last_selected_move.type == .Fighting);
|
|
1228
1222
|
|
|
1229
1223
|
if (!used and !selected) {
|
|
1230
|
-
try options.log.fail(battle.active(player), .None);
|
|
1224
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1231
1225
|
return null;
|
|
1232
1226
|
}
|
|
1233
1227
|
|
|
1234
1228
|
if (!used or !selected) {
|
|
1235
1229
|
// GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
|
|
1236
1230
|
if (!showdown) return Result.Error;
|
|
1237
|
-
try options.log.fail(battle.active(player), .None);
|
|
1231
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1238
1232
|
return null;
|
|
1239
1233
|
}
|
|
1240
1234
|
|
|
@@ -1270,20 +1264,20 @@ fn applyDamage(
|
|
|
1270
1264
|
subbed.active.volatiles.substitute = 0;
|
|
1271
1265
|
subbed.active.volatiles.Substitute = false;
|
|
1272
1266
|
// battle.last_damage is not updated with the amount of HP the Substitute had
|
|
1273
|
-
try options.log.end(battle.active(sub_player), .Substitute);
|
|
1267
|
+
try options.log.end(.{ battle.active(sub_player), .Substitute });
|
|
1274
1268
|
options.calc.capped(target_player);
|
|
1275
1269
|
return true;
|
|
1276
1270
|
} else {
|
|
1277
1271
|
// Safe to truncate since less than subbed.volatiles.substitute which is a u8
|
|
1278
1272
|
subbed.active.volatiles.substitute -= @intCast(battle.last_damage);
|
|
1279
|
-
try options.log.activate(battle.active(sub_player), .Substitute);
|
|
1273
|
+
try options.log.activate(.{ battle.active(sub_player), .Substitute });
|
|
1280
1274
|
return false;
|
|
1281
1275
|
}
|
|
1282
1276
|
}
|
|
1283
1277
|
|
|
1284
1278
|
if (battle.last_damage > target.stored().hp) battle.last_damage = target.stored().hp;
|
|
1285
1279
|
target.stored().hp -= battle.last_damage;
|
|
1286
|
-
try options.log.damage(battle.active(target_player), target.stored(), reason);
|
|
1280
|
+
try options.log.damage(.{ battle.active(target_player), target.stored(), reason });
|
|
1287
1281
|
return false;
|
|
1288
1282
|
}
|
|
1289
1283
|
|
|
@@ -1302,7 +1296,7 @@ fn mirrorMove(
|
|
|
1302
1296
|
side.last_selected_move = foe.last_used_move;
|
|
1303
1297
|
|
|
1304
1298
|
if (foe.last_used_move == .None or foe.last_used_move == .MirrorMove) {
|
|
1305
|
-
try options.log.fail(battle.active(player), .None);
|
|
1299
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1306
1300
|
return null;
|
|
1307
1301
|
} else if (!showdown or foe.last_used_move != .Struggle) {
|
|
1308
1302
|
incrementPP(side, mslot);
|
|
@@ -1344,12 +1338,12 @@ fn checkHit(battle: anytype, player: Player, move: Move.Data, options: anytype)
|
|
|
1344
1338
|
if (mist) {
|
|
1345
1339
|
assert(!showdown);
|
|
1346
1340
|
const foe_ident = battle.active(player.foe());
|
|
1347
|
-
try options.log.activate(foe_ident, .Mist);
|
|
1348
|
-
try options.log.fail(foe_ident, .None);
|
|
1341
|
+
try options.log.activate(.{ foe_ident, .Mist });
|
|
1342
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1349
1343
|
} else {
|
|
1350
1344
|
try options.chance.commit(player, .miss);
|
|
1351
|
-
try options.log.lastmiss();
|
|
1352
|
-
try options.log.miss(battle.active(player));
|
|
1345
|
+
try options.log.lastmiss(.{});
|
|
1346
|
+
try options.log.miss(.{battle.active(player)});
|
|
1353
1347
|
}
|
|
1354
1348
|
|
|
1355
1349
|
return false;
|
|
@@ -1401,7 +1395,7 @@ fn moveHit(
|
|
|
1401
1395
|
const overwritten = overwrite and state > 0;
|
|
1402
1396
|
assert(!overwritten or (0 < state and state <= 255 and !side.active.volatiles.Bide));
|
|
1403
1397
|
|
|
1404
|
-
var accuracy = if (overwritten) state else
|
|
1398
|
+
var accuracy: u16 = if (overwritten) state else move.accuracy;
|
|
1405
1399
|
var boost = BOOSTS[@as(u4, @intCast(@as(i8, side.active.boosts.accuracy) + 6))];
|
|
1406
1400
|
accuracy = accuracy * boost[0] / boost[1];
|
|
1407
1401
|
boost = BOOSTS[@as(u4, @intCast(@as(i8, -foe.active.boosts.evasion) + 6))];
|
|
@@ -1450,13 +1444,13 @@ fn checkFaint(
|
|
|
1450
1444
|
assert(!foe.active.volatiles.MultiHit);
|
|
1451
1445
|
|
|
1452
1446
|
if (player_out and foe_out) {
|
|
1453
|
-
try options.log.tie();
|
|
1447
|
+
try options.log.tie(.{});
|
|
1454
1448
|
return Result.Tie;
|
|
1455
1449
|
} else if (player_out) {
|
|
1456
|
-
try options.log.win(player.foe());
|
|
1450
|
+
try options.log.win(.{player.foe()});
|
|
1457
1451
|
return if (player == .P1) Result.Lose else Result.Win;
|
|
1458
1452
|
} else if (foe_out) {
|
|
1459
|
-
try options.log.win(player);
|
|
1453
|
+
try options.log.win(.{player});
|
|
1460
1454
|
return if (player == .P1) Result.Win else Result.Lose;
|
|
1461
1455
|
}
|
|
1462
1456
|
|
|
@@ -1498,7 +1492,7 @@ fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result
|
|
|
1498
1492
|
side.stored().stats.spe;
|
|
1499
1493
|
}
|
|
1500
1494
|
|
|
1501
|
-
try options.log.faint(battle.active(player), done);
|
|
1495
|
+
try options.log.faint(.{ battle.active(player), done });
|
|
1502
1496
|
options.calc.capped(player);
|
|
1503
1497
|
return null;
|
|
1504
1498
|
}
|
|
@@ -1520,8 +1514,8 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1520
1514
|
|
|
1521
1515
|
if (!showdown or damage > 0) {
|
|
1522
1516
|
stored.hp -= damage;
|
|
1523
|
-
// Pokémon Showdown uses
|
|
1524
|
-
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
1517
|
+
// Pokémon Showdown uses damage [of] here but its not relevant in Generation I
|
|
1518
|
+
try options.log.damage(.{ ident, stored, if (brn) Damage.Burn else Damage.Poison });
|
|
1525
1519
|
}
|
|
1526
1520
|
}
|
|
1527
1521
|
|
|
@@ -1546,14 +1540,14 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1546
1540
|
const amount = @min(damage, stored.hp);
|
|
1547
1541
|
stored.hp -= amount;
|
|
1548
1542
|
|
|
1549
|
-
// As above, Pokémon Showdown uses
|
|
1550
|
-
if (amount > 0) try options.log.damage(ident, stored, .LeechSeed);
|
|
1543
|
+
// As above, Pokémon Showdown uses damage [of] but its not relevant
|
|
1544
|
+
if (amount > 0) try options.log.damage(.{ ident, stored, .LeechSeed });
|
|
1551
1545
|
|
|
1552
1546
|
const before = foe_stored.hp;
|
|
1553
1547
|
// Uncapped damage is added back to the foe
|
|
1554
1548
|
foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
|
|
1555
1549
|
// Pokémon Showdown uses the less specific heal here instead of drain... because reasons?
|
|
1556
|
-
if (foe_stored.hp > before) try options.log.heal(foe_ident, foe_stored, .Silent);
|
|
1550
|
+
if (foe_stored.hp > before) try options.log.heal(.{ foe_ident, foe_stored, .Silent });
|
|
1557
1551
|
}
|
|
1558
1552
|
}
|
|
1559
1553
|
|
|
@@ -1564,18 +1558,18 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1564
1558
|
battle.turn += 1;
|
|
1565
1559
|
|
|
1566
1560
|
if (showdown and pkmn.options.ebc and checkEBC(battle)) {
|
|
1567
|
-
try options.log.tie();
|
|
1561
|
+
try options.log.tie(.{});
|
|
1568
1562
|
return Result.Tie;
|
|
1569
1563
|
}
|
|
1570
1564
|
|
|
1571
1565
|
if (showdown and battle.turn >= 1000) {
|
|
1572
|
-
try options.log.tie();
|
|
1566
|
+
try options.log.tie(.{});
|
|
1573
1567
|
return Result.Tie;
|
|
1574
1568
|
} else if (battle.turn >= 65535) {
|
|
1575
1569
|
return Result.Error;
|
|
1576
1570
|
}
|
|
1577
1571
|
|
|
1578
|
-
try options.log.turn(battle.turn);
|
|
1572
|
+
try options.log.turn(.{battle.turn});
|
|
1579
1573
|
|
|
1580
1574
|
return Result.Default;
|
|
1581
1575
|
}
|
|
@@ -1645,14 +1639,14 @@ fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
|
1645
1639
|
side.last_selected_move;
|
|
1646
1640
|
if (m != .None and m == last) {
|
|
1647
1641
|
side.active.volatiles.Charging = false;
|
|
1648
|
-
try options.log.
|
|
1642
|
+
try options.log.cant(.{ ident, .Disable, last });
|
|
1649
1643
|
return true;
|
|
1650
1644
|
}
|
|
1651
1645
|
}
|
|
1652
1646
|
return false;
|
|
1653
1647
|
}
|
|
1654
1648
|
|
|
1655
|
-
|
|
1649
|
+
fn buildRage(battle: anytype, who: Player, options: anytype) !void {
|
|
1656
1650
|
const side = battle.side(who);
|
|
1657
1651
|
if (side.active.volatiles.Rage and side.active.boosts.atk < 6) {
|
|
1658
1652
|
try Effects.boost(battle, who, Move.get(.Rage), options);
|
|
@@ -1670,7 +1664,7 @@ fn handleThrashing(battle: anytype, active: *ActivePokemon, player: Player, opti
|
|
|
1670
1664
|
return true;
|
|
1671
1665
|
}
|
|
1672
1666
|
|
|
1673
|
-
|
|
1667
|
+
fn onBegin(
|
|
1674
1668
|
battle: anytype,
|
|
1675
1669
|
player: Player,
|
|
1676
1670
|
move: Move.Data,
|
|
@@ -1678,7 +1672,6 @@ inline fn onBegin(
|
|
|
1678
1672
|
residual: *bool,
|
|
1679
1673
|
options: anytype,
|
|
1680
1674
|
) !void {
|
|
1681
|
-
assert(move.effect.onBegin());
|
|
1682
1675
|
return switch (move.effect) {
|
|
1683
1676
|
.Confusion => Effects.confusion(battle, player, move, options),
|
|
1684
1677
|
.Conversion => Effects.conversion(battle, player, options),
|
|
@@ -1700,8 +1693,7 @@ inline fn onBegin(
|
|
|
1700
1693
|
};
|
|
1701
1694
|
}
|
|
1702
1695
|
|
|
1703
|
-
|
|
1704
|
-
assert(move.effect.onEnd());
|
|
1696
|
+
fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1705
1697
|
return switch (move.effect) {
|
|
1706
1698
|
.Bide => Effects.bide(battle, player, options),
|
|
1707
1699
|
// zig fmt: off
|
|
@@ -1716,14 +1708,13 @@ inline fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anyty
|
|
|
1716
1708
|
};
|
|
1717
1709
|
}
|
|
1718
1710
|
|
|
1719
|
-
|
|
1711
|
+
fn alwaysHappens(
|
|
1720
1712
|
battle: anytype,
|
|
1721
1713
|
player: Player,
|
|
1722
1714
|
move: Move.Data,
|
|
1723
1715
|
residual: *bool,
|
|
1724
1716
|
options: anytype,
|
|
1725
1717
|
) !void {
|
|
1726
|
-
assert(move.effect.alwaysHappens());
|
|
1727
1718
|
return switch (move.effect) {
|
|
1728
1719
|
.DrainHP, .DreamEater => Effects.drainHP(battle, player, options),
|
|
1729
1720
|
.Explode => Effects.explode(battle, player),
|
|
@@ -1735,7 +1726,7 @@ inline fn alwaysHappens(
|
|
|
1735
1726
|
};
|
|
1736
1727
|
}
|
|
1737
1728
|
|
|
1738
|
-
|
|
1729
|
+
fn moveEffect(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1739
1730
|
return switch (move.effect) {
|
|
1740
1731
|
.BurnChance1, .BurnChance2 => Effects.burnChance(battle, player, move, options),
|
|
1741
1732
|
.ConfusionChance => Effects.confusion(battle, player, move, options),
|
|
@@ -1762,7 +1753,7 @@ pub const Effects = struct {
|
|
|
1762
1753
|
side.active.volatiles.state = 0;
|
|
1763
1754
|
side.active.volatiles.attacks = Rolls.attackingDuration(battle, player, options);
|
|
1764
1755
|
|
|
1765
|
-
try options.log.start(battle.active(player), .Bide);
|
|
1756
|
+
try options.log.start(.{ battle.active(player), .Bide });
|
|
1766
1757
|
}
|
|
1767
1758
|
|
|
1768
1759
|
fn burnChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1776,11 +1767,11 @@ pub const Effects = struct {
|
|
|
1776
1767
|
// GLITCH: Freeze top move selection desync can occur if thawed Pokémon is slower
|
|
1777
1768
|
if (Status.is(foe_stored.status, .FRZ)) {
|
|
1778
1769
|
assert(move.type == .Fire);
|
|
1779
|
-
try options.log.curestatus(
|
|
1770
|
+
try options.log.curestatus(.{
|
|
1780
1771
|
battle.active(player.foe()),
|
|
1781
1772
|
foe_stored.status,
|
|
1782
1773
|
.Message,
|
|
1783
|
-
);
|
|
1774
|
+
});
|
|
1784
1775
|
foe_stored.status = 0;
|
|
1785
1776
|
}
|
|
1786
1777
|
return;
|
|
@@ -1794,7 +1785,7 @@ pub const Effects = struct {
|
|
|
1794
1785
|
foe_stored.status = Status.init(.BRN);
|
|
1795
1786
|
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
1796
1787
|
|
|
1797
|
-
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
1788
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1798
1789
|
}
|
|
1799
1790
|
|
|
1800
1791
|
fn charge(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -1804,8 +1795,8 @@ pub const Effects = struct {
|
|
|
1804
1795
|
volatiles.Charging = true;
|
|
1805
1796
|
const move = side.last_selected_move;
|
|
1806
1797
|
if (move == .Fly or move == .Dig) volatiles.Invulnerable = true;
|
|
1807
|
-
try options.log.laststill();
|
|
1808
|
-
try options.log.prepare(battle.active(player), move);
|
|
1798
|
+
try options.log.laststill(.{});
|
|
1799
|
+
try options.log.prepare(.{ battle.active(player), move });
|
|
1809
1800
|
}
|
|
1810
1801
|
|
|
1811
1802
|
fn confusion(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1816,17 +1807,11 @@ pub const Effects = struct {
|
|
|
1816
1807
|
if (!try Rolls.confusionChance(battle, player, options)) return;
|
|
1817
1808
|
} else {
|
|
1818
1809
|
if (showdown) {
|
|
1819
|
-
if (!try checkHit(battle, player, move, options))
|
|
1820
|
-
|
|
1821
|
-
} else if (sub) {
|
|
1822
|
-
return options.log.fail(battle.active(player.foe()), .None);
|
|
1823
|
-
}
|
|
1810
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1811
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1824
1812
|
} else {
|
|
1825
|
-
if (sub) {
|
|
1826
|
-
|
|
1827
|
-
} else if (!try checkHit(battle, player, move, options)) {
|
|
1828
|
-
return;
|
|
1829
|
-
}
|
|
1813
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1814
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1830
1815
|
}
|
|
1831
1816
|
try options.chance.commit(player, .hit);
|
|
1832
1817
|
}
|
|
@@ -1835,23 +1820,24 @@ pub const Effects = struct {
|
|
|
1835
1820
|
foe.active.volatiles.Confusion = true;
|
|
1836
1821
|
foe.active.volatiles.confusion = Rolls.confusionDuration(battle, player, false, options);
|
|
1837
1822
|
|
|
1838
|
-
try options.log.start(battle.active(player.foe()), .Confusion);
|
|
1823
|
+
try options.log.start(.{ battle.active(player.foe()), .Confusion });
|
|
1839
1824
|
}
|
|
1840
1825
|
|
|
1841
1826
|
fn conversion(battle: anytype, player: Player, options: anytype) !void {
|
|
1842
1827
|
const foe = battle.foe(player);
|
|
1843
1828
|
|
|
1844
1829
|
if (foe.active.volatiles.Invulnerable) {
|
|
1845
|
-
try options.log.lastmiss();
|
|
1846
|
-
return options.log.miss(battle.active(player));
|
|
1830
|
+
try options.log.lastmiss(.{});
|
|
1831
|
+
return options.log.miss(.{battle.active(player)});
|
|
1847
1832
|
}
|
|
1848
1833
|
|
|
1849
1834
|
battle.side(player).active.types = foe.active.types;
|
|
1850
|
-
return options.log.
|
|
1835
|
+
return options.log.start(.{
|
|
1851
1836
|
battle.active(player),
|
|
1837
|
+
.TypeChange,
|
|
1852
1838
|
foe.active.types,
|
|
1853
1839
|
battle.active(player.foe()),
|
|
1854
|
-
);
|
|
1840
|
+
});
|
|
1855
1841
|
}
|
|
1856
1842
|
|
|
1857
1843
|
fn disable(battle: anytype, player: Player, move: Move.Data, options: anytype) !?Result {
|
|
@@ -1866,7 +1852,7 @@ pub const Effects = struct {
|
|
|
1866
1852
|
}
|
|
1867
1853
|
|
|
1868
1854
|
if (volatiles.disable_move != 0) {
|
|
1869
|
-
try options.log.fail(foe_ident, .None);
|
|
1855
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1870
1856
|
return null;
|
|
1871
1857
|
}
|
|
1872
1858
|
|
|
@@ -1882,7 +1868,7 @@ pub const Effects = struct {
|
|
|
1882
1868
|
// Technically this is still considered simply a "miss" on the cartridge,
|
|
1883
1869
|
// but diverging from Pokémon Showdown here would mostly just be pedantic
|
|
1884
1870
|
if (n == 0) {
|
|
1885
|
-
try options.log.fail(foe_ident, .None);
|
|
1871
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1886
1872
|
return null;
|
|
1887
1873
|
} else if (err) {
|
|
1888
1874
|
// GLITCH: Transform + Mirror Move / Metronome PP softlock
|
|
@@ -1895,12 +1881,13 @@ pub const Effects = struct {
|
|
|
1895
1881
|
volatiles.disable_duration = Rolls.disableDuration(battle, player, options);
|
|
1896
1882
|
|
|
1897
1883
|
const id = foe.active.move(volatiles.disable_move).id;
|
|
1898
|
-
try options.log.
|
|
1884
|
+
try options.log.start(.{ foe_ident, .Disable, id });
|
|
1899
1885
|
return null;
|
|
1900
1886
|
}
|
|
1901
1887
|
|
|
1902
1888
|
fn drainHP(battle: anytype, player: Player, options: anytype) !void {
|
|
1903
1889
|
var stored = battle.side(player).stored();
|
|
1890
|
+
const ident = battle.active(player);
|
|
1904
1891
|
|
|
1905
1892
|
if (battle.last_damage == 0) {
|
|
1906
1893
|
assert(showdown);
|
|
@@ -1913,7 +1900,7 @@ pub const Effects = struct {
|
|
|
1913
1900
|
if (stored.hp == stored.stats.hp) return;
|
|
1914
1901
|
stored.hp = @min(stored.stats.hp, stored.hp + drain);
|
|
1915
1902
|
|
|
1916
|
-
try options.log.
|
|
1903
|
+
try options.log.heal(.{ ident, stored, .Drain, battle.active(player.foe()) });
|
|
1917
1904
|
}
|
|
1918
1905
|
|
|
1919
1906
|
fn explode(battle: anytype, player: Player) !void {
|
|
@@ -1945,7 +1932,7 @@ pub const Effects = struct {
|
|
|
1945
1932
|
if (side.active.volatiles.FocusEnergy) return;
|
|
1946
1933
|
side.active.volatiles.FocusEnergy = true;
|
|
1947
1934
|
|
|
1948
|
-
try options.log.start(battle.active(player), .FocusEnergy);
|
|
1935
|
+
try options.log.start(.{ battle.active(player), .FocusEnergy });
|
|
1949
1936
|
}
|
|
1950
1937
|
|
|
1951
1938
|
fn freezeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1967,7 +1954,7 @@ pub const Effects = struct {
|
|
|
1967
1954
|
foe_stored.status = Status.init(.FRZ);
|
|
1968
1955
|
// GLITCH: Hyper Beam recharging status is not cleared
|
|
1969
1956
|
|
|
1970
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
1957
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
1971
1958
|
}
|
|
1972
1959
|
|
|
1973
1960
|
fn haze(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -1975,7 +1962,7 @@ pub const Effects = struct {
|
|
|
1975
1962
|
var side = battle.side(player);
|
|
1976
1963
|
var foe = battle.foe(player);
|
|
1977
1964
|
|
|
1978
|
-
|
|
1965
|
+
const side_stored = side.stored();
|
|
1979
1966
|
var foe_stored = foe.stored();
|
|
1980
1967
|
|
|
1981
1968
|
const player_ident = battle.active(player);
|
|
@@ -1987,8 +1974,8 @@ pub const Effects = struct {
|
|
|
1987
1974
|
side.active.stats = side_stored.stats;
|
|
1988
1975
|
foe.active.stats = foe_stored.stats;
|
|
1989
1976
|
|
|
1990
|
-
try log.activate(player_ident, .Haze);
|
|
1991
|
-
try log.clearallboost();
|
|
1977
|
+
try log.activate(.{ player_ident, .Haze });
|
|
1978
|
+
try log.clearallboost(.{});
|
|
1992
1979
|
|
|
1993
1980
|
// Pokémon Showdown clears P1 then P2 instead of status -> side -> foe
|
|
1994
1981
|
if (showdown) {
|
|
@@ -1996,11 +1983,11 @@ pub const Effects = struct {
|
|
|
1996
1983
|
const p: Player = @enumFromInt(i);
|
|
1997
1984
|
// Pokémon Showdown incorrectly does not prevent sleep/freeze from moving
|
|
1998
1985
|
if (p != player and Status.any(s.stored().status)) {
|
|
1999
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
1986
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2000
1987
|
s.stored().status = 0;
|
|
2001
1988
|
} else if (showdown and s.stored().status == Status.TOX) {
|
|
2002
1989
|
s.stored().status = Status.init(.PSN);
|
|
2003
|
-
try log.status(battle.active(p), s.stored().status, .None);
|
|
1990
|
+
try log.status(.{ battle.active(p), s.stored().status, .None });
|
|
2004
1991
|
}
|
|
2005
1992
|
try clearVolatiles(battle, p, p != player, options);
|
|
2006
1993
|
}
|
|
@@ -2009,7 +1996,7 @@ pub const Effects = struct {
|
|
|
2009
1996
|
if (Status.is(foe_stored.status, .FRZ) or Status.is(foe_stored.status, .SLP)) {
|
|
2010
1997
|
foe.last_selected_move = .SKIP_TURN;
|
|
2011
1998
|
}
|
|
2012
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
1999
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2013
2000
|
foe_stored.status = 0;
|
|
2014
2001
|
}
|
|
2015
2002
|
try clearVolatiles(battle, player, false, options);
|
|
@@ -2025,7 +2012,7 @@ pub const Effects = struct {
|
|
|
2025
2012
|
// GLITCH: HP recovery move failure glitches
|
|
2026
2013
|
const delta = stored.stats.hp - stored.hp;
|
|
2027
2014
|
if (delta == 0 or (delta & 255 == 255 and stored.hp % 256 != 0)) {
|
|
2028
|
-
return
|
|
2015
|
+
return options.log.fail(.{ ident, .None });
|
|
2029
2016
|
}
|
|
2030
2017
|
|
|
2031
2018
|
const rest = side.last_selected_move == .Rest;
|
|
@@ -2033,17 +2020,17 @@ pub const Effects = struct {
|
|
|
2033
2020
|
// Adding the sleep status runs the sleep condition handler to roll duration
|
|
2034
2021
|
if (showdown) battle.rng.advance(1);
|
|
2035
2022
|
stored.status = Status.slf(2);
|
|
2036
|
-
try options.log.
|
|
2023
|
+
try options.log.status(.{ ident, stored.status, .From, Move.Rest });
|
|
2037
2024
|
stored.hp = stored.stats.hp;
|
|
2038
2025
|
} else {
|
|
2039
2026
|
stored.hp = @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2));
|
|
2040
2027
|
}
|
|
2041
|
-
try options.log.heal(ident, stored, if (rest) Heal.Silent else Heal.None);
|
|
2028
|
+
try options.log.heal(.{ ident, stored, if (rest) Heal.Silent else Heal.None });
|
|
2042
2029
|
}
|
|
2043
2030
|
|
|
2044
2031
|
fn hyperBeam(battle: anytype, player: Player, options: anytype) !void {
|
|
2045
2032
|
battle.side(player).active.volatiles.Recharging = true;
|
|
2046
|
-
try options.log.mustrecharge(battle.active(player));
|
|
2033
|
+
try options.log.mustrecharge(.{battle.active(player)});
|
|
2047
2034
|
}
|
|
2048
2035
|
|
|
2049
2036
|
fn leechSeed(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2052,33 +2039,32 @@ pub const Effects = struct {
|
|
|
2052
2039
|
if (showdown) {
|
|
2053
2040
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2054
2041
|
if (!foe.active.volatiles.Invulnerable and foe.active.types.includes(.Grass)) {
|
|
2055
|
-
return options.log.immune(battle.active(player.foe()), .None);
|
|
2042
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
2056
2043
|
}
|
|
2057
2044
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2058
2045
|
if (foe.active.volatiles.LeechSeed) return;
|
|
2059
2046
|
} else {
|
|
2060
2047
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2061
2048
|
if (foe.active.types.includes(.Grass) or foe.active.volatiles.LeechSeed) {
|
|
2062
|
-
try options.log.lastmiss();
|
|
2063
|
-
return options.log.miss(battle.active(player));
|
|
2049
|
+
try options.log.lastmiss(.{});
|
|
2050
|
+
return options.log.miss(.{battle.active(player)});
|
|
2064
2051
|
}
|
|
2065
2052
|
}
|
|
2066
2053
|
|
|
2067
2054
|
try options.chance.commit(player, .hit);
|
|
2068
2055
|
foe.active.volatiles.LeechSeed = true;
|
|
2069
2056
|
|
|
2070
|
-
try options.log.start(battle.active(player.foe()), .LeechSeed);
|
|
2057
|
+
try options.log.start(.{ battle.active(player.foe()), .LeechSeed });
|
|
2071
2058
|
}
|
|
2072
2059
|
|
|
2073
2060
|
fn lightScreen(battle: anytype, player: Player, options: anytype) !void {
|
|
2074
2061
|
var side = battle.side(player);
|
|
2062
|
+
const ident = battle.active(player);
|
|
2075
2063
|
|
|
2076
|
-
if (side.active.volatiles.LightScreen) {
|
|
2077
|
-
return options.log.fail(battle.active(player), .None);
|
|
2078
|
-
}
|
|
2064
|
+
if (side.active.volatiles.LightScreen) return options.log.fail(.{ ident, .None });
|
|
2079
2065
|
side.active.volatiles.LightScreen = true;
|
|
2080
2066
|
|
|
2081
|
-
try options.log.start(
|
|
2067
|
+
try options.log.start(.{ ident, .LightScreen });
|
|
2082
2068
|
}
|
|
2083
2069
|
|
|
2084
2070
|
fn mimic(battle: anytype, player: Player, move: Move.Data, mslot: u8, options: anytype) !void {
|
|
@@ -2107,7 +2093,7 @@ pub const Effects = struct {
|
|
|
2107
2093
|
// Invulnerable foes or 1/256 miss can trigger |-miss| instead of |-fail|
|
|
2108
2094
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2109
2095
|
try options.chance.commit(player, .hit);
|
|
2110
|
-
return
|
|
2096
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2111
2097
|
}
|
|
2112
2098
|
}
|
|
2113
2099
|
if (!try checkHit(battle, player, move, options)) return;
|
|
@@ -2116,7 +2102,7 @@ pub const Effects = struct {
|
|
|
2116
2102
|
const rslot = try Rolls.moveSlot(battle, player, &foe.active.moves, 0, options);
|
|
2117
2103
|
side.active.move(oslot).id = foe.active.move(rslot).id;
|
|
2118
2104
|
|
|
2119
|
-
try options.log.
|
|
2105
|
+
try options.log.start(.{ battle.active(player), .Mimic, side.active.move(oslot).id });
|
|
2120
2106
|
}
|
|
2121
2107
|
|
|
2122
2108
|
fn mist(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -2125,7 +2111,7 @@ pub const Effects = struct {
|
|
|
2125
2111
|
if (side.active.volatiles.Mist) return;
|
|
2126
2112
|
side.active.volatiles.Mist = true;
|
|
2127
2113
|
|
|
2128
|
-
try options.log.start(battle.active(player), .Mist);
|
|
2114
|
+
try options.log.start(.{ battle.active(player), .Mist });
|
|
2129
2115
|
}
|
|
2130
2116
|
|
|
2131
2117
|
fn multiHit(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2151,17 +2137,18 @@ pub const Effects = struct {
|
|
|
2151
2137
|
|
|
2152
2138
|
if (showdown) {
|
|
2153
2139
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2154
|
-
if (immune and !foe.active.volatiles.Invulnerable)
|
|
2155
|
-
|
|
2140
|
+
if (immune and !foe.active.volatiles.Invulnerable) {
|
|
2141
|
+
return log.immune(.{ foe_ident, .None });
|
|
2142
|
+
} else if (!try checkHit(battle, player, move, options)) return;
|
|
2156
2143
|
}
|
|
2157
2144
|
if (Status.any(foe_stored.status)) {
|
|
2158
|
-
return log.fail(
|
|
2145
|
+
return log.fail(.{
|
|
2159
2146
|
foe_ident,
|
|
2160
|
-
if (Status.is(foe_stored.status, .PAR)) .Paralysis else .None,
|
|
2161
|
-
);
|
|
2147
|
+
if (Status.is(foe_stored.status, .PAR)) Fail.Paralysis else Fail.None,
|
|
2148
|
+
});
|
|
2162
2149
|
}
|
|
2163
2150
|
if (!showdown) {
|
|
2164
|
-
if (immune) return log.immune(foe_ident, .None);
|
|
2151
|
+
if (immune) return log.immune(.{ foe_ident, .None });
|
|
2165
2152
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2166
2153
|
}
|
|
2167
2154
|
try options.chance.commit(player, .hit);
|
|
@@ -2169,7 +2156,7 @@ pub const Effects = struct {
|
|
|
2169
2156
|
foe_stored.status = Status.init(.PAR);
|
|
2170
2157
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2171
2158
|
|
|
2172
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2159
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2173
2160
|
}
|
|
2174
2161
|
|
|
2175
2162
|
fn paralyzeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2191,12 +2178,12 @@ pub const Effects = struct {
|
|
|
2191
2178
|
foe_stored.status = Status.init(.PAR);
|
|
2192
2179
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2193
2180
|
|
|
2194
|
-
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
2181
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
2195
2182
|
}
|
|
2196
2183
|
|
|
2197
2184
|
fn payDay(battle: anytype, player: Player, options: anytype) !void {
|
|
2198
2185
|
if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
|
|
2199
|
-
try options.log.fieldactivate();
|
|
2186
|
+
try options.log.fieldactivate(.{});
|
|
2200
2187
|
}
|
|
2201
2188
|
}
|
|
2202
2189
|
|
|
@@ -2211,23 +2198,23 @@ pub const Effects = struct {
|
|
|
2211
2198
|
return;
|
|
2212
2199
|
} else if (foe.active.volatiles.Substitute) {
|
|
2213
2200
|
if (move.effect != .Poison) return;
|
|
2214
|
-
return log.fail(foe_ident, .None);
|
|
2201
|
+
return log.fail(.{ foe_ident, .None });
|
|
2215
2202
|
} else if (Status.any(foe_stored.status)) {
|
|
2216
2203
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2217
2204
|
// Pokémon Showdown considers Toxic to be a status even in Generation I and so
|
|
2218
2205
|
// 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))
|
|
2206
|
+
return log.fail(.{ foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2220
2207
|
if (!showdown)
|
|
2221
|
-
.Poison
|
|
2208
|
+
Fail.Poison
|
|
2222
2209
|
else if (toxic == (foe_stored.status == Status.TOX))
|
|
2223
|
-
if (toxic) .Toxic else .Poison
|
|
2210
|
+
if (toxic) Fail.Toxic else Fail.Poison
|
|
2224
2211
|
else
|
|
2225
|
-
.None
|
|
2212
|
+
Fail.None
|
|
2226
2213
|
else
|
|
2227
|
-
.None);
|
|
2214
|
+
Fail.None });
|
|
2228
2215
|
} else if (foe.active.types.includes(.Poison)) {
|
|
2229
2216
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2230
|
-
return log.immune(foe_ident, .None);
|
|
2217
|
+
return log.immune(.{ foe_ident, .None });
|
|
2231
2218
|
}
|
|
2232
2219
|
|
|
2233
2220
|
if (move.effect == .Poison) {
|
|
@@ -2246,7 +2233,7 @@ pub const Effects = struct {
|
|
|
2246
2233
|
foe.active.volatiles.toxic = 0;
|
|
2247
2234
|
}
|
|
2248
2235
|
|
|
2249
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2236
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2250
2237
|
}
|
|
2251
2238
|
|
|
2252
2239
|
fn rage(battle: anytype, player: Player) !void {
|
|
@@ -2266,22 +2253,23 @@ pub const Effects = struct {
|
|
|
2266
2253
|
@as(u8, if (side.last_selected_move == .Struggle) 2 else 4), 1));
|
|
2267
2254
|
stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
|
|
2268
2255
|
|
|
2269
|
-
try options.log.
|
|
2256
|
+
try options.log.damage(.{
|
|
2270
2257
|
battle.active(player),
|
|
2271
2258
|
stored,
|
|
2272
2259
|
.RecoilOf,
|
|
2273
2260
|
battle.active(player.foe()),
|
|
2274
|
-
);
|
|
2261
|
+
});
|
|
2275
2262
|
if (showdown and stored.hp == 0) residual.* = false;
|
|
2276
2263
|
}
|
|
2277
2264
|
|
|
2278
2265
|
fn reflect(battle: anytype, player: Player, options: anytype) !void {
|
|
2279
2266
|
var side = battle.side(player);
|
|
2267
|
+
const ident = battle.active(player);
|
|
2280
2268
|
|
|
2281
|
-
if (side.active.volatiles.Reflect) return options.log.fail(
|
|
2269
|
+
if (side.active.volatiles.Reflect) return options.log.fail(.{ ident, .None });
|
|
2282
2270
|
side.active.volatiles.Reflect = true;
|
|
2283
2271
|
|
|
2284
|
-
try options.log.start(
|
|
2272
|
+
try options.log.start(.{ ident, .Reflect });
|
|
2285
2273
|
}
|
|
2286
2274
|
|
|
2287
2275
|
fn sleep(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2295,10 +2283,10 @@ pub const Effects = struct {
|
|
|
2295
2283
|
// the Sleep Clause Mod might activate, causing us to not actually bypass
|
|
2296
2284
|
} else {
|
|
2297
2285
|
if (Status.any(foe_stored.status)) {
|
|
2298
|
-
return options.log.fail(
|
|
2286
|
+
return options.log.fail(.{
|
|
2299
2287
|
foe_ident,
|
|
2300
|
-
if (Status.is(foe_stored.status, .SLP)) .Sleep else .None,
|
|
2301
|
-
);
|
|
2288
|
+
if (Status.is(foe_stored.status, .SLP)) Fail.Sleep else Fail.None,
|
|
2289
|
+
});
|
|
2302
2290
|
}
|
|
2303
2291
|
// If checkHit in doMove didn't return true Pokémon Showdown wouldn't be in here
|
|
2304
2292
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
@@ -2315,18 +2303,17 @@ pub const Effects = struct {
|
|
|
2315
2303
|
|
|
2316
2304
|
foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
|
|
2317
2305
|
const last = battle.side(player).last_selected_move;
|
|
2318
|
-
try options.log.
|
|
2306
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .From, last });
|
|
2319
2307
|
}
|
|
2320
2308
|
|
|
2321
2309
|
fn splash(battle: anytype, player: Player, options: anytype) !void {
|
|
2322
|
-
try options.log.activate(battle.active(player), .Splash);
|
|
2310
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
2323
2311
|
}
|
|
2324
2312
|
|
|
2325
2313
|
fn substitute(battle: anytype, player: Player, residual: *bool, options: anytype) !void {
|
|
2326
2314
|
var side = battle.side(player);
|
|
2327
2315
|
if (side.active.volatiles.Substitute) {
|
|
2328
|
-
|
|
2329
|
-
return;
|
|
2316
|
+
return options.log.fail(.{ battle.active(player), .Substitute });
|
|
2330
2317
|
}
|
|
2331
2318
|
|
|
2332
2319
|
assert(side.stored().stats.hp <= 1023);
|
|
@@ -2338,17 +2325,16 @@ pub const Effects = struct {
|
|
|
2338
2325
|
// avoid having to convert to floating point)
|
|
2339
2326
|
const required_hp = if (showdown) @divFloor(side.stored().stats.hp - 1, 4) + 1 else hp;
|
|
2340
2327
|
if (side.stored().hp < required_hp) {
|
|
2341
|
-
|
|
2342
|
-
return;
|
|
2328
|
+
return options.log.fail(.{ battle.active(player), .Weak });
|
|
2343
2329
|
}
|
|
2344
2330
|
|
|
2345
2331
|
// GLITCH: can leave the user with 0 HP (faints later) because didn't check '<=' above
|
|
2346
2332
|
side.stored().hp -= hp;
|
|
2347
2333
|
side.active.volatiles.substitute = hp + 1;
|
|
2348
2334
|
side.active.volatiles.Substitute = true;
|
|
2349
|
-
try options.log.start(battle.active(player), .Substitute);
|
|
2335
|
+
try options.log.start(.{ battle.active(player), .Substitute });
|
|
2350
2336
|
if (hp > 0) {
|
|
2351
|
-
try options.log.damage(battle.active(player), side.stored(), .None);
|
|
2337
|
+
try options.log.damage(.{ battle.active(player), side.stored(), .None });
|
|
2352
2338
|
if (showdown and side.stored().hp == 0) residual.* = false;
|
|
2353
2339
|
}
|
|
2354
2340
|
}
|
|
@@ -2362,8 +2348,8 @@ pub const Effects = struct {
|
|
|
2362
2348
|
}
|
|
2363
2349
|
battle.last_damage = 0;
|
|
2364
2350
|
} else {
|
|
2365
|
-
try options.log.fail(battle.active(player), .None);
|
|
2366
|
-
try options.log.laststill();
|
|
2351
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2352
|
+
try options.log.laststill(.{});
|
|
2367
2353
|
}
|
|
2368
2354
|
}
|
|
2369
2355
|
|
|
@@ -2402,7 +2388,7 @@ pub const Effects = struct {
|
|
|
2402
2388
|
side.active.moves[i].pp = if (m.id != .None) 5 else 0;
|
|
2403
2389
|
}
|
|
2404
2390
|
|
|
2405
|
-
try options.log.transform(battle.active(player), foe_ident);
|
|
2391
|
+
try options.log.transform(.{ battle.active(player), foe_ident });
|
|
2406
2392
|
}
|
|
2407
2393
|
|
|
2408
2394
|
fn binding(battle: anytype, player: Player, rewrap: bool, options: anytype) !void {
|
|
@@ -2432,87 +2418,87 @@ pub const Effects = struct {
|
|
|
2432
2418
|
switch (move.effect) {
|
|
2433
2419
|
.AttackUp1, .AttackUp2, .Rage => {
|
|
2434
2420
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2435
|
-
if (boosts.atk == 6) return
|
|
2421
|
+
if (boosts.atk == 6) return log.fail(.{ ident, .None });
|
|
2436
2422
|
const n: u2 = if (move.effect == .AttackUp2) 2 else 1;
|
|
2437
2423
|
boosts.atk = @as(i4, @intCast(@min(6, @as(i8, boosts.atk) + n)));
|
|
2438
|
-
const reason = if (move.effect == .Rage)
|
|
2424
|
+
const reason: Boost = if (move.effect == .Rage) .Rage else .Attack;
|
|
2439
2425
|
if (stats.atk == MAX_STAT_VALUE) {
|
|
2440
2426
|
boosts.atk -= 1;
|
|
2441
2427
|
if (showdown) {
|
|
2442
|
-
try log.boost(ident, reason, n);
|
|
2443
|
-
try log.boost(ident,
|
|
2428
|
+
try log.boost(.{ ident, reason, n });
|
|
2429
|
+
try log.boost(.{ ident, .Attack, -1 });
|
|
2444
2430
|
if (move.effect == .Rage) return;
|
|
2445
2431
|
}
|
|
2446
|
-
return
|
|
2432
|
+
return log.fail(.{ ident, .None });
|
|
2447
2433
|
}
|
|
2448
|
-
|
|
2434
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2449
2435
|
const stat = unmodifiedStats(battle, side).atk;
|
|
2450
2436
|
stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2451
|
-
try log.boost(ident, reason, n);
|
|
2437
|
+
try log.boost(.{ ident, reason, n });
|
|
2452
2438
|
// Pokémon Showdown doesn't re-apply status modifiers after Rage boosts
|
|
2453
2439
|
if (showdown and move.effect == .Rage) return;
|
|
2454
2440
|
},
|
|
2455
2441
|
.DefenseUp1, .DefenseUp2 => {
|
|
2456
2442
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2457
|
-
if (boosts.def == 6) return
|
|
2443
|
+
if (boosts.def == 6) return log.fail(.{ ident, .None });
|
|
2458
2444
|
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
2459
2445
|
boosts.def = @intCast(@min(6, @as(i8, boosts.def) + n));
|
|
2460
2446
|
if (stats.def == MAX_STAT_VALUE) {
|
|
2461
2447
|
boosts.def -= 1;
|
|
2462
2448
|
if (showdown) {
|
|
2463
|
-
try log.boost(ident, .Defense, n);
|
|
2464
|
-
try log.boost(ident, .Defense, -1);
|
|
2449
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2450
|
+
try log.boost(.{ ident, .Defense, -1 });
|
|
2465
2451
|
}
|
|
2466
|
-
return
|
|
2452
|
+
return log.fail(.{ ident, .None });
|
|
2467
2453
|
}
|
|
2468
|
-
|
|
2454
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2469
2455
|
const stat = unmodifiedStats(battle, side).def;
|
|
2470
2456
|
stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2471
|
-
try log.boost(ident, .Defense, n);
|
|
2457
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2472
2458
|
},
|
|
2473
2459
|
.SpeedUp2 => {
|
|
2474
2460
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2475
|
-
if (boosts.spe == 6) return
|
|
2461
|
+
if (boosts.spe == 6) return log.fail(.{ ident, .None });
|
|
2476
2462
|
boosts.spe = @intCast(@min(6, @as(i8, boosts.spe) + 2));
|
|
2477
2463
|
if (stats.spe == MAX_STAT_VALUE) {
|
|
2478
2464
|
boosts.spe -= 1;
|
|
2479
2465
|
if (showdown) {
|
|
2480
|
-
try log.boost(ident, .Speed, 2);
|
|
2481
|
-
try log.boost(ident, .Speed, -1);
|
|
2466
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2467
|
+
try log.boost(.{ ident, .Speed, -1 });
|
|
2482
2468
|
}
|
|
2483
|
-
return
|
|
2469
|
+
return log.fail(.{ ident, .None });
|
|
2484
2470
|
}
|
|
2485
|
-
|
|
2471
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2486
2472
|
const stat = unmodifiedStats(battle, side).spe;
|
|
2487
2473
|
stats.spe = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2488
|
-
try log.boost(ident, .Speed, 2);
|
|
2474
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2489
2475
|
},
|
|
2490
2476
|
.SpecialUp1, .SpecialUp2 => {
|
|
2491
2477
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2492
|
-
if (boosts.spc == 6) return
|
|
2478
|
+
if (boosts.spc == 6) return log.fail(.{ ident, .None });
|
|
2493
2479
|
const n: u2 = if (move.effect == .SpecialUp2) 2 else 1;
|
|
2494
2480
|
boosts.spc = @intCast(@min(6, @as(i8, boosts.spc) + n));
|
|
2495
2481
|
if (stats.spc == MAX_STAT_VALUE) {
|
|
2496
2482
|
boosts.spc -= 1;
|
|
2497
2483
|
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);
|
|
2484
|
+
try log.boost(.{ ident, .SpecialAttack, n });
|
|
2485
|
+
try log.boost(.{ ident, .SpecialAttack, -1 });
|
|
2486
|
+
try log.boost(.{ ident, .SpecialDefense, n });
|
|
2487
|
+
try log.boost(.{ ident, .SpecialDefense, -1 });
|
|
2502
2488
|
}
|
|
2503
|
-
return
|
|
2489
|
+
return log.fail(.{ ident, .None });
|
|
2504
2490
|
}
|
|
2505
|
-
|
|
2491
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2506
2492
|
const stat = unmodifiedStats(battle, side).spc;
|
|
2507
2493
|
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);
|
|
2494
|
+
try log.boost(.{ ident, .SpecialAttack, n });
|
|
2495
|
+
try log.boost(.{ ident, .SpecialDefense, n });
|
|
2510
2496
|
},
|
|
2511
2497
|
.EvasionUp1 => {
|
|
2512
2498
|
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2513
|
-
if (boosts.evasion == 6) return
|
|
2499
|
+
if (boosts.evasion == 6) return log.fail(.{ ident, .None });
|
|
2514
2500
|
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2515
|
-
try log.boost(ident, .Evasion, 1);
|
|
2501
|
+
try log.boost(.{ ident, .Evasion, 1 });
|
|
2516
2502
|
},
|
|
2517
2503
|
else => unreachable,
|
|
2518
2504
|
}
|
|
@@ -2527,7 +2513,7 @@ pub const Effects = struct {
|
|
|
2527
2513
|
const foe_ident = battle.active(player.foe());
|
|
2528
2514
|
const secondary = move.effect.isStatDownChance();
|
|
2529
2515
|
|
|
2530
|
-
if (foe.active.volatiles.Substitute) return if (!secondary) log.fail(foe_ident, .None);
|
|
2516
|
+
if (foe.active.volatiles.Substitute) return if (!secondary) log.fail(.{ foe_ident, .None });
|
|
2531
2517
|
|
|
2532
2518
|
if (secondary) {
|
|
2533
2519
|
const proc = try Rolls.unboost(battle, player, options);
|
|
@@ -2545,82 +2531,82 @@ pub const Effects = struct {
|
|
|
2545
2531
|
switch (move.effect) {
|
|
2546
2532
|
.AttackDown1, .AttackDownChance => {
|
|
2547
2533
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2548
|
-
if (boosts.atk == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2534
|
+
if (boosts.atk == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2549
2535
|
boosts.atk = @intCast(@max(-6, @as(i8, boosts.atk) - 1));
|
|
2550
2536
|
if (stats.atk == 1) {
|
|
2551
2537
|
boosts.atk += 1;
|
|
2552
2538
|
if (showdown) {
|
|
2553
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2554
|
-
try log.boost(foe_ident, .Attack, 1);
|
|
2539
|
+
try log.boost(.{ foe_ident, .Attack, -1 });
|
|
2540
|
+
try log.boost(.{ foe_ident, .Attack, 1 });
|
|
2555
2541
|
}
|
|
2556
|
-
return
|
|
2542
|
+
return log.fail(.{ foe_ident, .None });
|
|
2557
2543
|
}
|
|
2558
|
-
|
|
2544
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2559
2545
|
const stat = unmodifiedStats(battle, foe).atk;
|
|
2560
2546
|
stats.atk = @max(1, stat * mod[0] / mod[1]);
|
|
2561
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2547
|
+
try log.boost(.{ foe_ident, .Attack, -1 });
|
|
2562
2548
|
},
|
|
2563
2549
|
.DefenseDown1, .DefenseDown2, .DefenseDownChance => {
|
|
2564
2550
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2565
|
-
if (boosts.def == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2551
|
+
if (boosts.def == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2566
2552
|
const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
|
|
2567
2553
|
boosts.def = @intCast(@max(-6, @as(i8, boosts.def) - n));
|
|
2568
2554
|
if (stats.def == 1) {
|
|
2569
2555
|
boosts.def += 1;
|
|
2570
2556
|
if (showdown) {
|
|
2571
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2572
|
-
try log.boost(foe_ident, .Defense, 1);
|
|
2557
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2558
|
+
try log.boost(.{ foe_ident, .Defense, 1 });
|
|
2573
2559
|
}
|
|
2574
|
-
return
|
|
2560
|
+
return log.fail(.{ foe_ident, .None });
|
|
2575
2561
|
}
|
|
2576
|
-
|
|
2562
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2577
2563
|
const stat = unmodifiedStats(battle, foe).def;
|
|
2578
2564
|
stats.def = @max(1, stat * mod[0] / mod[1]);
|
|
2579
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2565
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2580
2566
|
},
|
|
2581
2567
|
.SpeedDown1, .SpeedDownChance => {
|
|
2582
2568
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2583
|
-
if (boosts.spe == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2569
|
+
if (boosts.spe == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2584
2570
|
boosts.spe = @intCast(@max(-6, @as(i8, boosts.spe) - 1));
|
|
2585
2571
|
if (stats.spe == 1) {
|
|
2586
2572
|
boosts.spe += 1;
|
|
2587
2573
|
if (showdown) {
|
|
2588
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2589
|
-
try log.boost(foe_ident, .Speed, 1);
|
|
2574
|
+
try log.boost(.{ foe_ident, .Speed, -1 });
|
|
2575
|
+
try log.boost(.{ foe_ident, .Speed, 1 });
|
|
2590
2576
|
}
|
|
2591
|
-
return
|
|
2577
|
+
return log.fail(.{ foe_ident, .None });
|
|
2592
2578
|
}
|
|
2593
|
-
|
|
2579
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2594
2580
|
const stat = unmodifiedStats(battle, foe).spe;
|
|
2595
2581
|
stats.spe = @max(1, stat * mod[0] / mod[1]);
|
|
2596
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2582
|
+
try log.boost(.{ foe_ident, .Speed, -1 });
|
|
2597
2583
|
assert(boosts.spe >= -6);
|
|
2598
2584
|
},
|
|
2599
2585
|
.SpecialDownChance => {
|
|
2600
2586
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2601
|
-
if (boosts.spc == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2587
|
+
if (boosts.spc == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2602
2588
|
boosts.spc = @intCast(@max(-6, @as(i8, boosts.spc) - 1));
|
|
2603
2589
|
if (stats.spc == 1) {
|
|
2604
2590
|
boosts.spc += 1;
|
|
2605
2591
|
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);
|
|
2592
|
+
try log.boost(.{ foe_ident, .SpecialAttack, -1 });
|
|
2593
|
+
try log.boost(.{ foe_ident, .SpecialAttack, 1 });
|
|
2594
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2595
|
+
try log.boost(.{ foe_ident, .SpecialDefense, 1 });
|
|
2610
2596
|
}
|
|
2611
|
-
return
|
|
2597
|
+
return log.fail(.{ foe_ident, .None });
|
|
2612
2598
|
}
|
|
2613
|
-
|
|
2599
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2614
2600
|
const stat = unmodifiedStats(battle, foe).spc;
|
|
2615
2601
|
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);
|
|
2602
|
+
try log.boost(.{ foe_ident, .SpecialAttack, -1 });
|
|
2603
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2618
2604
|
},
|
|
2619
2605
|
.AccuracyDown1 => {
|
|
2620
2606
|
assert(boosts.accuracy >= -6 and boosts.accuracy <= 6);
|
|
2621
|
-
if (boosts.accuracy == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2607
|
+
if (boosts.accuracy == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2622
2608
|
boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
|
|
2623
|
-
try log.boost(foe_ident, .Accuracy, -1);
|
|
2609
|
+
try log.boost(.{ foe_ident, .Accuracy, -1 });
|
|
2624
2610
|
},
|
|
2625
2611
|
else => unreachable,
|
|
2626
2612
|
}
|
|
@@ -2644,7 +2630,7 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
2644
2630
|
}
|
|
2645
2631
|
}
|
|
2646
2632
|
|
|
2647
|
-
|
|
2633
|
+
fn isForced(active: anytype) bool {
|
|
2648
2634
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
2649
2635
|
active.volatiles.Thrashing or active.volatiles.Charging;
|
|
2650
2636
|
}
|
|
@@ -2660,24 +2646,24 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2660
2646
|
if (volatiles.disable_move != 0) {
|
|
2661
2647
|
volatiles.disable_move = 0;
|
|
2662
2648
|
volatiles.disable_duration = 0;
|
|
2663
|
-
try log.end(ident, .DisableSilent);
|
|
2649
|
+
try log.end(.{ ident, .DisableSilent });
|
|
2664
2650
|
}
|
|
2665
2651
|
if (volatiles.Confusion) {
|
|
2666
2652
|
// volatiles.confusion is left unchanged
|
|
2667
2653
|
volatiles.Confusion = false;
|
|
2668
|
-
try log.end(ident, .ConfusionSilent);
|
|
2654
|
+
try log.end(.{ ident, .ConfusionSilent });
|
|
2669
2655
|
}
|
|
2670
2656
|
if (volatiles.Mist) {
|
|
2671
2657
|
volatiles.Mist = false;
|
|
2672
|
-
try log.end(ident, .MistSilent);
|
|
2658
|
+
try log.end(.{ ident, .MistSilent });
|
|
2673
2659
|
}
|
|
2674
2660
|
if (volatiles.FocusEnergy) {
|
|
2675
2661
|
volatiles.FocusEnergy = false;
|
|
2676
|
-
try log.end(ident, .FocusEnergySilent);
|
|
2662
|
+
try log.end(.{ ident, .FocusEnergySilent });
|
|
2677
2663
|
}
|
|
2678
2664
|
if (volatiles.LeechSeed) {
|
|
2679
2665
|
volatiles.LeechSeed = false;
|
|
2680
|
-
try log.end(ident, .LeechSeedSilent);
|
|
2666
|
+
try log.end(.{ ident, .LeechSeedSilent });
|
|
2681
2667
|
}
|
|
2682
2668
|
if (!showdown and volatiles.Toxic) {
|
|
2683
2669
|
volatiles.Toxic = false;
|
|
@@ -2685,24 +2671,26 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2685
2671
|
}
|
|
2686
2672
|
if (volatiles.LightScreen) {
|
|
2687
2673
|
volatiles.LightScreen = false;
|
|
2688
|
-
try log.end(ident, .LightScreenSilent);
|
|
2674
|
+
try log.end(.{ ident, .LightScreenSilent });
|
|
2689
2675
|
}
|
|
2690
2676
|
if (volatiles.Reflect) {
|
|
2691
2677
|
volatiles.Reflect = false;
|
|
2692
|
-
try log.end(ident, .ReflectSilent);
|
|
2678
|
+
try log.end(.{ ident, .ReflectSilent });
|
|
2693
2679
|
}
|
|
2694
2680
|
if (showdown and volatiles.Toxic) {
|
|
2695
2681
|
volatiles.Toxic = false;
|
|
2696
2682
|
// Pokémon Showdown erroneously clears the toxic counter
|
|
2697
2683
|
volatiles.toxic = 0;
|
|
2698
|
-
try log.end(ident, .ToxicSilent);
|
|
2684
|
+
try log.end(.{ ident, .ToxicSilent });
|
|
2699
2685
|
}
|
|
2700
2686
|
}
|
|
2701
2687
|
|
|
2702
2688
|
pub const Rolls = struct {
|
|
2703
|
-
|
|
2689
|
+
fn speedTie(battle: anytype, options: anytype) !bool {
|
|
2704
2690
|
const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
|
|
2705
2691
|
player == .P1
|
|
2692
|
+
else if (@hasDecl(@TypeOf(battle.rng), "speedTie"))
|
|
2693
|
+
battle.rng.speedTie()
|
|
2706
2694
|
else if (showdown)
|
|
2707
2695
|
battle.rng.range(u8, 0, 2) == 0
|
|
2708
2696
|
else
|
|
@@ -2712,9 +2700,11 @@ pub const Rolls = struct {
|
|
|
2712
2700
|
return p1;
|
|
2713
2701
|
}
|
|
2714
2702
|
|
|
2715
|
-
|
|
2703
|
+
fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
|
|
2716
2704
|
const crit = if (options.calc.overridden(player, .critical_hit)) |val|
|
|
2717
2705
|
val == .true
|
|
2706
|
+
else if (@hasDecl(@TypeOf(battle.rng), "criticalHit"))
|
|
2707
|
+
battle.rng.criticalHit(player, rate)
|
|
2718
2708
|
else if (showdown)
|
|
2719
2709
|
battle.rng.chance(u8, rate, 256)
|
|
2720
2710
|
else
|
|
@@ -2724,9 +2714,11 @@ pub const Rolls = struct {
|
|
|
2724
2714
|
return crit;
|
|
2725
2715
|
}
|
|
2726
2716
|
|
|
2727
|
-
|
|
2717
|
+
fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2728
2718
|
const roll = if (options.calc.overridden(player, .damage)) |val|
|
|
2729
2719
|
val
|
|
2720
|
+
else if (@hasDecl(@TypeOf(battle.rng), "damage"))
|
|
2721
|
+
battle.rng.damage(player)
|
|
2730
2722
|
else roll: {
|
|
2731
2723
|
if (showdown) break :roll battle.rng.range(u8, 217, 256);
|
|
2732
2724
|
while (true) {
|
|
@@ -2740,9 +2732,11 @@ pub const Rolls = struct {
|
|
|
2740
2732
|
return roll;
|
|
2741
2733
|
}
|
|
2742
2734
|
|
|
2743
|
-
|
|
2735
|
+
fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) bool {
|
|
2744
2736
|
const ok = if (options.calc.overridden(player, .hit)) |val|
|
|
2745
2737
|
val == .true
|
|
2738
|
+
else if (@hasDecl(@TypeOf(battle.rng), "hit"))
|
|
2739
|
+
battle.rng.hit(player, accuracy)
|
|
2746
2740
|
else if (showdown)
|
|
2747
2741
|
battle.rng.chance(u8, accuracy, 256)
|
|
2748
2742
|
else
|
|
@@ -2752,9 +2746,11 @@ pub const Rolls = struct {
|
|
|
2752
2746
|
return ok;
|
|
2753
2747
|
}
|
|
2754
2748
|
|
|
2755
|
-
|
|
2749
|
+
fn confused(battle: anytype, player: Player, options: anytype) !bool {
|
|
2756
2750
|
const cfz = if (options.calc.overridden(player, .confused)) |val|
|
|
2757
2751
|
val == .true
|
|
2752
|
+
else if (@hasDecl(@TypeOf(battle.rng), "confused"))
|
|
2753
|
+
battle.rng.confused(player)
|
|
2758
2754
|
else if (showdown)
|
|
2759
2755
|
!battle.rng.chance(u8, 128, 256)
|
|
2760
2756
|
else
|
|
@@ -2764,9 +2760,11 @@ pub const Rolls = struct {
|
|
|
2764
2760
|
return cfz;
|
|
2765
2761
|
}
|
|
2766
2762
|
|
|
2767
|
-
|
|
2763
|
+
fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
|
|
2768
2764
|
const par = if (options.calc.overridden(player, .paralyzed)) |val|
|
|
2769
2765
|
val == .true
|
|
2766
|
+
else if (@hasDecl(@TypeOf(battle.rng), "paralyzed"))
|
|
2767
|
+
battle.rng.paralyzed(player)
|
|
2770
2768
|
else if (showdown)
|
|
2771
2769
|
battle.rng.chance(u8, 63, 256)
|
|
2772
2770
|
else
|
|
@@ -2776,9 +2774,11 @@ pub const Rolls = struct {
|
|
|
2776
2774
|
return par;
|
|
2777
2775
|
}
|
|
2778
2776
|
|
|
2779
|
-
|
|
2777
|
+
fn confusionChance(battle: anytype, player: Player, options: anytype) !bool {
|
|
2780
2778
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2781
2779
|
val == .true
|
|
2780
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2781
|
+
battle.rng.secondaryChance(player, 25)
|
|
2782
2782
|
else if (showdown)
|
|
2783
2783
|
battle.rng.chance(u8, 25, 256)
|
|
2784
2784
|
else
|
|
@@ -2788,11 +2788,13 @@ pub const Rolls = struct {
|
|
|
2788
2788
|
return proc;
|
|
2789
2789
|
}
|
|
2790
2790
|
|
|
2791
|
-
|
|
2791
|
+
fn secondaryChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2792
2792
|
const rate: u8 = if (low) 26 else 77;
|
|
2793
2793
|
|
|
2794
2794
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2795
2795
|
val == .true
|
|
2796
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2797
|
+
battle.rng.secondaryChance(player, rate)
|
|
2796
2798
|
else if (showdown)
|
|
2797
2799
|
battle.rng.chance(u8, rate, 256)
|
|
2798
2800
|
else
|
|
@@ -2802,11 +2804,13 @@ pub const Rolls = struct {
|
|
|
2802
2804
|
return proc;
|
|
2803
2805
|
}
|
|
2804
2806
|
|
|
2805
|
-
|
|
2807
|
+
fn poisonChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2806
2808
|
const rate: u8 = if (low) 52 else 103;
|
|
2807
2809
|
|
|
2808
2810
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2809
2811
|
val == .true
|
|
2812
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2813
|
+
battle.rng.secondaryChance(player, rate)
|
|
2810
2814
|
else if (showdown)
|
|
2811
2815
|
battle.rng.chance(u8, rate, 256)
|
|
2812
2816
|
else
|
|
@@ -2816,9 +2820,11 @@ pub const Rolls = struct {
|
|
|
2816
2820
|
return proc;
|
|
2817
2821
|
}
|
|
2818
2822
|
|
|
2819
|
-
|
|
2823
|
+
fn unboost(battle: anytype, player: Player, options: anytype) !bool {
|
|
2820
2824
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2821
2825
|
val == .true
|
|
2826
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2827
|
+
battle.rng.secondaryChance(player, 85)
|
|
2822
2828
|
else if (showdown)
|
|
2823
2829
|
battle.rng.chance(u8, 85, 256)
|
|
2824
2830
|
else
|
|
@@ -2828,9 +2834,11 @@ pub const Rolls = struct {
|
|
|
2828
2834
|
return proc;
|
|
2829
2835
|
}
|
|
2830
2836
|
|
|
2831
|
-
|
|
2837
|
+
fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
2832
2838
|
const move: Move = if (options.calc.overridden(player, .metronome)) |val|
|
|
2833
2839
|
val
|
|
2840
|
+
else if (@hasDecl(@TypeOf(battle.rng), "metronome"))
|
|
2841
|
+
battle.rng.metronome(player)
|
|
2834
2842
|
else if (showdown)
|
|
2835
2843
|
Move.METRONOME[battle.rng.range(u8, 0, Move.METRONOME.len)]
|
|
2836
2844
|
else move: {
|
|
@@ -2846,9 +2854,11 @@ pub const Rolls = struct {
|
|
|
2846
2854
|
return move;
|
|
2847
2855
|
}
|
|
2848
2856
|
|
|
2849
|
-
|
|
2857
|
+
fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
|
|
2850
2858
|
const power = if (options.calc.overridden(player, .psywave)) |val|
|
|
2851
2859
|
val - 1
|
|
2860
|
+
else if (@hasDecl(@TypeOf(battle.rng), "psywave"))
|
|
2861
|
+
battle.rng.psywave(player)
|
|
2852
2862
|
else if (showdown)
|
|
2853
2863
|
battle.rng.range(u8, 0, max)
|
|
2854
2864
|
else power: {
|
|
@@ -2863,9 +2873,11 @@ pub const Rolls = struct {
|
|
|
2863
2873
|
return power;
|
|
2864
2874
|
}
|
|
2865
2875
|
|
|
2866
|
-
|
|
2876
|
+
fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2867
2877
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2868
2878
|
@intCast(val)
|
|
2879
|
+
else if (@hasDecl(@TypeOf(battle.rng), "sleepDuration"))
|
|
2880
|
+
battle.rng.sleepDuration(player)
|
|
2869
2881
|
else if (showdown)
|
|
2870
2882
|
battle.rng.range(u3, 1, 8)
|
|
2871
2883
|
else duration: {
|
|
@@ -2880,9 +2892,11 @@ pub const Rolls = struct {
|
|
|
2880
2892
|
return duration;
|
|
2881
2893
|
}
|
|
2882
2894
|
|
|
2883
|
-
|
|
2895
|
+
fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
|
|
2884
2896
|
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2885
2897
|
val
|
|
2898
|
+
else if (@hasDecl(@TypeOf(battle.rng), "disableDuration"))
|
|
2899
|
+
battle.rng.disableDuration(player)
|
|
2886
2900
|
else if (showdown)
|
|
2887
2901
|
battle.rng.range(u4, 1, 9)
|
|
2888
2902
|
else
|
|
@@ -2893,9 +2907,11 @@ pub const Rolls = struct {
|
|
|
2893
2907
|
return duration;
|
|
2894
2908
|
}
|
|
2895
2909
|
|
|
2896
|
-
|
|
2910
|
+
fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
|
|
2897
2911
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2898
2912
|
@intCast(val)
|
|
2913
|
+
else if (@hasDecl(@TypeOf(battle.rng), "confusionDuration"))
|
|
2914
|
+
battle.rng.confusionDuration(player, self)
|
|
2899
2915
|
else if (showdown)
|
|
2900
2916
|
battle.rng.range(u3, 2, 6)
|
|
2901
2917
|
else
|
|
@@ -2906,9 +2922,11 @@ pub const Rolls = struct {
|
|
|
2906
2922
|
return duration;
|
|
2907
2923
|
}
|
|
2908
2924
|
|
|
2909
|
-
|
|
2925
|
+
fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2910
2926
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2911
2927
|
@intCast(val)
|
|
2928
|
+
else if (@hasDecl(@TypeOf(battle.rng), "attackingDuration"))
|
|
2929
|
+
battle.rng.attackingDuration(player)
|
|
2912
2930
|
else if (showdown)
|
|
2913
2931
|
battle.rng.range(u3, 2, 4)
|
|
2914
2932
|
else
|
|
@@ -2921,7 +2939,7 @@ pub const Rolls = struct {
|
|
|
2921
2939
|
|
|
2922
2940
|
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
2923
2941
|
|
|
2924
|
-
|
|
2942
|
+
fn distribution(
|
|
2925
2943
|
battle: anytype,
|
|
2926
2944
|
comptime effect: Move.Effect,
|
|
2927
2945
|
player: Player,
|
|
@@ -2930,6 +2948,8 @@ pub const Rolls = struct {
|
|
|
2930
2948
|
const roll = if (effect == .Binding) .duration else .multi_hit;
|
|
2931
2949
|
const n: u3 = if (options.calc.overridden(player, roll)) |val|
|
|
2932
2950
|
@intCast(val)
|
|
2951
|
+
else if (@hasDecl(@TypeOf(battle.rng), "distribution"))
|
|
2952
|
+
battle.rng.distribution(player)
|
|
2933
2953
|
else if (showdown)
|
|
2934
2954
|
DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
|
|
2935
2955
|
else n: {
|
|
@@ -2962,7 +2982,9 @@ pub const Rolls = struct {
|
|
|
2962
2982
|
else
|
|
2963
2983
|
null;
|
|
2964
2984
|
|
|
2965
|
-
const slot: u4 = overridden orelse
|
|
2985
|
+
const slot: u4 = overridden orelse if (@hasDecl(@TypeOf(battle.rng), "moveSlot"))
|
|
2986
|
+
battle.rng.moveSlot(player, moves, check_pp)
|
|
2987
|
+
else slot: {
|
|
2966
2988
|
if (showdown) {
|
|
2967
2989
|
if (check_pp == 0) {
|
|
2968
2990
|
var i: usize = moves.len;
|
|
@@ -3042,7 +3064,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3042
3064
|
const side = battle.side(player);
|
|
3043
3065
|
const foe = battle.foe(player);
|
|
3044
3066
|
|
|
3045
|
-
|
|
3067
|
+
const active = &side.active;
|
|
3046
3068
|
const stored = side.stored();
|
|
3047
3069
|
|
|
3048
3070
|
// While players are not given any input options on the cartridge in these cases,
|