@pkmn/engine 0.1.0-dev.dc33f678 → 0.1.0-dev.deda8593
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 +33 -22
- 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 +217 -47
- 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 -55
- package/src/lib/gen1/mechanics.zig +333 -312
- 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 +1621 -456
- 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/test/display/pkmn.html.tmpl +5 -5
- 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
|
|
|
@@ -346,10 +347,10 @@ fn doTurn(
|
|
|
346
347
|
)) |r| return r;
|
|
347
348
|
if (!replace) {
|
|
348
349
|
if (player_choice.type != .Switch) {
|
|
349
|
-
if (try checkFaint(battle, foe_player, options)) |r| return r;
|
|
350
|
+
if (try checkFaint(battle, foe_player, true, options)) |r| return r;
|
|
350
351
|
}
|
|
351
352
|
if (residual) try handleResidual(battle, player, options);
|
|
352
|
-
if (try checkFaint(battle, player, options)) |r| return r;
|
|
353
|
+
if (try checkFaint(battle, player, false, options)) |r| return r;
|
|
353
354
|
} else if (foe_choice.type == .Pass) return null;
|
|
354
355
|
|
|
355
356
|
options.chance.clearPending();
|
|
@@ -368,10 +369,10 @@ fn doTurn(
|
|
|
368
369
|
)) |r| return r;
|
|
369
370
|
if (!replace) {
|
|
370
371
|
if (!calc and foe_choice.type != .Switch) {
|
|
371
|
-
if (try checkFaint(battle, player, options)) |r| return r;
|
|
372
|
+
if (try checkFaint(battle, player, true, options)) |r| return r;
|
|
372
373
|
}
|
|
373
374
|
if (residual) try handleResidual(battle, foe_player, options);
|
|
374
|
-
if (try checkFaint(battle, foe_player, options)) |r| return r;
|
|
375
|
+
if (try checkFaint(battle, foe_player, false, options)) |r| return r;
|
|
375
376
|
}
|
|
376
377
|
|
|
377
378
|
// Flinch is bugged on Pokémon Showdown because it gets implemented with a duration which causes
|
|
@@ -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()) {
|
|
@@ -858,7 +852,7 @@ fn doMove(
|
|
|
858
852
|
break :miss true;
|
|
859
853
|
}
|
|
860
854
|
break :miss (move.target != .Self and
|
|
861
|
-
!moveHit(battle, player, move, &immune, &mist, options));
|
|
855
|
+
!try moveHit(battle, player, move, &immune, &mist, options));
|
|
862
856
|
};
|
|
863
857
|
assert(!immune or miss or (showdown and move.effect == .Binding));
|
|
864
858
|
|
|
@@ -901,11 +895,11 @@ 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
|
|
908
|
-
(!moveHit(battle, player, move, &immune, &mist, options) or zero);
|
|
902
|
+
(!try moveHit(battle, player, move, &immune, &mist, options) or zero);
|
|
909
903
|
|
|
910
904
|
assert(showdown or miss or battle.last_damage > 0 or skip);
|
|
911
905
|
assert((!showdown and miss) or !(ohko and immune));
|
|
@@ -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
|
-
if (showdown
|
|
940
|
-
try log.lastmiss();
|
|
941
|
-
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)});
|
|
942
936
|
}
|
|
943
937
|
if (move.effect == .JumpKick) {
|
|
944
938
|
// Recoil is supposed to be damage/8 but damage will always be 0 here
|
|
@@ -955,13 +949,13 @@ fn doMove(
|
|
|
955
949
|
try options.chance.commit(player, .binding);
|
|
956
950
|
}
|
|
957
951
|
return null;
|
|
958
|
-
} else {
|
|
952
|
+
} else if (!showdown) {
|
|
959
953
|
try options.chance.commit(player, .hit);
|
|
960
954
|
}
|
|
961
955
|
|
|
962
956
|
// On the cartridge MultiHit doesn't get set up until after damage has been applied for the
|
|
963
957
|
// first time but its more convenient and efficient to set it up here (Pokémon Showdown sets
|
|
964
|
-
// it up above before damage calculation)
|
|
958
|
+
// it up above before damage calculation)
|
|
965
959
|
if (!showdown and move.effect.isMulti()) {
|
|
966
960
|
try Effects.multiHit(battle, player, move, options);
|
|
967
961
|
hits = side.active.volatiles.attacks;
|
|
@@ -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;
|
|
@@ -1020,12 +1014,12 @@ fn doMove(
|
|
|
1020
1014
|
}
|
|
1021
1015
|
|
|
1022
1016
|
// Substitute being broken nullifies the move's effect completely so even
|
|
1023
|
-
// 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
|
|
1024
1018
|
if (nullified) return null;
|
|
1025
1019
|
|
|
1026
1020
|
// On the cartridge, "always happen" effect handlers are called in the applyDamage loop above,
|
|
1027
1021
|
// but this is only done to setup the MultiHit looping in the first place. Moving the MultiHit
|
|
1028
|
-
// 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
|
|
1029
1023
|
if (move.effect.alwaysHappens()) try alwaysHappens(battle, player, move, residual, options);
|
|
1030
1024
|
|
|
1031
1025
|
if (foe.stored().hp == 0) return null;
|
|
@@ -1040,7 +1034,7 @@ fn doMove(
|
|
|
1040
1034
|
// below). For Twineedle we change the data to that of one with PoisonChance1 given its
|
|
1041
1035
|
// MultiHit behavior is complete after the loop above, though Pokémon Showdown handles the
|
|
1042
1036
|
// Twineedle secondary effect in the MultiHit cleanup block above because it incorrectly
|
|
1043
|
-
// puts the |-status| message before |-hitcount| instead of after
|
|
1037
|
+
// puts the |-status| message before |-hitcount| instead of after
|
|
1044
1038
|
if (move.effect == .Twineedle) {
|
|
1045
1039
|
if (!showdown) try Effects.poison(battle, player, Move.get(.PoisonSting), options);
|
|
1046
1040
|
} else if (move.effect == .Disable) {
|
|
@@ -1059,12 +1053,14 @@ 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;
|
|
1066
1060
|
|
|
1067
|
-
|
|
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);
|
|
1068
1064
|
}
|
|
1069
1065
|
|
|
1070
1066
|
fn calcDamage(
|
|
@@ -1116,9 +1112,9 @@ fn calcDamage(
|
|
|
1116
1112
|
def = @max((def / 4) & 255, if (showdown) 1 else 0);
|
|
1117
1113
|
}
|
|
1118
1114
|
|
|
1119
|
-
const lvl =
|
|
1115
|
+
const lvl: u32 = side.stored().level * @as(u2, if (crit) 2 else 1);
|
|
1120
1116
|
|
|
1121
|
-
def =
|
|
1117
|
+
def = if (move.effect == .Explode) @max(def / 2, 1) else def;
|
|
1122
1118
|
|
|
1123
1119
|
if (def == 0) return false;
|
|
1124
1120
|
|
|
@@ -1179,7 +1175,6 @@ fn specialDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1179
1175
|
const foe = battle.foe(player);
|
|
1180
1176
|
|
|
1181
1177
|
if (!try checkHit(battle, player, move, options)) return null;
|
|
1182
|
-
try options.chance.commit(player, .hit);
|
|
1183
1178
|
|
|
1184
1179
|
battle.last_damage = switch (side.last_selected_move) {
|
|
1185
1180
|
.SuperFang => @max(foe.stored().hp / 2, 1),
|
|
@@ -1209,7 +1204,7 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1209
1204
|
const foe = battle.foe(player);
|
|
1210
1205
|
|
|
1211
1206
|
if (battle.last_damage == 0) {
|
|
1212
|
-
try options.log.fail(battle.active(player), .None);
|
|
1207
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1213
1208
|
return null;
|
|
1214
1209
|
}
|
|
1215
1210
|
|
|
@@ -1227,22 +1222,21 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1227
1222
|
foe_last_selected_move.type == .Fighting);
|
|
1228
1223
|
|
|
1229
1224
|
if (!used and !selected) {
|
|
1230
|
-
try options.log.fail(battle.active(player), .None);
|
|
1225
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1231
1226
|
return null;
|
|
1232
1227
|
}
|
|
1233
1228
|
|
|
1234
1229
|
if (!used or !selected) {
|
|
1235
1230
|
// GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
|
|
1236
1231
|
if (!showdown) return Result.Error;
|
|
1237
|
-
try options.log.fail(battle.active(player), .None);
|
|
1232
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1238
1233
|
return null;
|
|
1239
1234
|
}
|
|
1240
1235
|
|
|
1241
1236
|
battle.last_damage *|= 2;
|
|
1242
1237
|
|
|
1243
|
-
// Pokémon Showdown calls
|
|
1238
|
+
// Pokémon Showdown calls moveHit before Counter
|
|
1244
1239
|
if (!showdown and !try checkHit(battle, player, move, options)) return null;
|
|
1245
|
-
try options.chance.commit(player, .hit);
|
|
1246
1240
|
|
|
1247
1241
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
1248
1242
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -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);
|
|
1274
|
-
options.calc.capped(
|
|
1267
|
+
try options.log.end(.{ battle.active(sub_player), .Substitute });
|
|
1268
|
+
options.calc.capped(sub_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);
|
|
@@ -1338,20 +1332,23 @@ fn checkHit(battle: anytype, player: Player, move: Move.Data, options: anytype)
|
|
|
1338
1332
|
var immune = false;
|
|
1339
1333
|
var mist = false;
|
|
1340
1334
|
|
|
1341
|
-
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
|
+
}
|
|
1342
1339
|
|
|
1343
1340
|
assert(!immune);
|
|
1344
1341
|
if (mist) {
|
|
1345
1342
|
assert(!showdown);
|
|
1346
1343
|
const foe_ident = battle.active(player.foe());
|
|
1347
|
-
try options.log.activate(foe_ident, .Mist);
|
|
1348
|
-
try options.log.fail(foe_ident, .None);
|
|
1344
|
+
try options.log.activate(.{ foe_ident, .Mist });
|
|
1345
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1349
1346
|
} else {
|
|
1350
|
-
try options.
|
|
1351
|
-
try options.log.
|
|
1352
|
-
try options.log.miss(battle.active(player));
|
|
1347
|
+
try options.log.lastmiss(.{});
|
|
1348
|
+
try options.log.miss(.{battle.active(player)});
|
|
1353
1349
|
}
|
|
1354
1350
|
|
|
1351
|
+
if (!showdown) try options.chance.commit(player, .miss);
|
|
1355
1352
|
return false;
|
|
1356
1353
|
}
|
|
1357
1354
|
|
|
@@ -1362,7 +1359,7 @@ fn moveHit(
|
|
|
1362
1359
|
immune: *bool,
|
|
1363
1360
|
mist: *bool,
|
|
1364
1361
|
options: anytype,
|
|
1365
|
-
) bool {
|
|
1362
|
+
) !bool {
|
|
1366
1363
|
var side = battle.side(player);
|
|
1367
1364
|
const foe = battle.foe(player);
|
|
1368
1365
|
|
|
@@ -1401,7 +1398,7 @@ fn moveHit(
|
|
|
1401
1398
|
const overwritten = overwrite and state > 0;
|
|
1402
1399
|
assert(!overwritten or (0 < state and state <= 255 and !side.active.volatiles.Bide));
|
|
1403
1400
|
|
|
1404
|
-
var accuracy = if (overwritten) state else
|
|
1401
|
+
var accuracy: u16 = if (overwritten) state else move.accuracy;
|
|
1405
1402
|
var boost = BOOSTS[@as(u4, @intCast(@as(i8, side.active.boosts.accuracy) + 6))];
|
|
1406
1403
|
accuracy = accuracy * boost[0] / boost[1];
|
|
1407
1404
|
boost = BOOSTS[@as(u4, @intCast(@as(i8, -foe.active.boosts.evasion) + 6))];
|
|
@@ -1413,7 +1410,7 @@ fn moveHit(
|
|
|
1413
1410
|
if (overwrite) side.active.volatiles.state = accuracy;
|
|
1414
1411
|
|
|
1415
1412
|
// GLITCH: max accuracy is 255 so 1/256 chance of miss
|
|
1416
|
-
break :miss !Rolls.hit(battle, player, @intCast(accuracy), options);
|
|
1413
|
+
break :miss !try Rolls.hit(battle, player, @intCast(accuracy), options);
|
|
1417
1414
|
};
|
|
1418
1415
|
|
|
1419
1416
|
// Pokémon Showdown reports miss instead of fail for moves blocked by Mist that 1/256 miss
|
|
@@ -1431,6 +1428,7 @@ fn moveHit(
|
|
|
1431
1428
|
fn checkFaint(
|
|
1432
1429
|
battle: anytype,
|
|
1433
1430
|
player: Player,
|
|
1431
|
+
cap: bool,
|
|
1434
1432
|
options: anytype,
|
|
1435
1433
|
) @TypeOf(options.log).Error!?Result {
|
|
1436
1434
|
const side = battle.side(player);
|
|
@@ -1440,24 +1438,23 @@ fn checkFaint(
|
|
|
1440
1438
|
const foe_fainted = foe.stored().hp == 0;
|
|
1441
1439
|
|
|
1442
1440
|
const player_out = findFirstAlive(side) == 0;
|
|
1443
|
-
const foe_out = findFirstAlive(foe) == 0;
|
|
1444
|
-
const
|
|
1445
|
-
const more = tie or player_out or foe_out;
|
|
1441
|
+
const foe_out = foe_fainted and findFirstAlive(foe) == 0;
|
|
1442
|
+
const more = player_out or foe_out;
|
|
1446
1443
|
|
|
1447
|
-
if (try faint(battle, player, !(more or foe_fainted), options)) |r| return r;
|
|
1448
|
-
if (foe_fainted) if (try faint(battle, player.foe(), !more, options)) |r| return r;
|
|
1444
|
+
if (try faint(battle, player, !(more or foe_fainted), cap, options)) |r| return r;
|
|
1445
|
+
if (foe_fainted) if (try faint(battle, player.foe(), !more, false, options)) |r| return r;
|
|
1449
1446
|
|
|
1450
1447
|
assert(!side.active.volatiles.MultiHit);
|
|
1451
1448
|
assert(!foe.active.volatiles.MultiHit);
|
|
1452
1449
|
|
|
1453
|
-
if (
|
|
1454
|
-
try options.log.tie();
|
|
1450
|
+
if (player_out and foe_out) {
|
|
1451
|
+
try options.log.tie(.{});
|
|
1455
1452
|
return Result.Tie;
|
|
1456
1453
|
} else if (player_out) {
|
|
1457
|
-
try options.log.win(player.foe());
|
|
1454
|
+
try options.log.win(.{player.foe()});
|
|
1458
1455
|
return if (player == .P1) Result.Lose else Result.Win;
|
|
1459
1456
|
} else if (foe_out) {
|
|
1460
|
-
try options.log.win(player);
|
|
1457
|
+
try options.log.win(.{player});
|
|
1461
1458
|
return if (player == .P1) Result.Win else Result.Lose;
|
|
1462
1459
|
}
|
|
1463
1460
|
|
|
@@ -1466,7 +1463,7 @@ fn checkFaint(
|
|
|
1466
1463
|
return .{ .p1 = foe_choice, .p2 = .Switch };
|
|
1467
1464
|
}
|
|
1468
1465
|
|
|
1469
|
-
fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result {
|
|
1466
|
+
fn faint(battle: anytype, player: Player, done: bool, cap: bool, options: anytype) !?Result {
|
|
1470
1467
|
var side = battle.side(player);
|
|
1471
1468
|
var foe = battle.foe(player);
|
|
1472
1469
|
assert(side.stored().hp == 0);
|
|
@@ -1499,8 +1496,10 @@ fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result
|
|
|
1499
1496
|
side.stored().stats.spe;
|
|
1500
1497
|
}
|
|
1501
1498
|
|
|
1502
|
-
try options.log.faint(battle.active(player), done);
|
|
1503
|
-
|
|
1499
|
+
try options.log.faint(.{ battle.active(player), done });
|
|
1500
|
+
// We can only safely set capped if the faint occured as the direct result of an opponent's
|
|
1501
|
+
// move (as opposed to due to residual damage, recoil, Explosion, etc)
|
|
1502
|
+
if (cap) options.calc.capped(player);
|
|
1504
1503
|
return null;
|
|
1505
1504
|
}
|
|
1506
1505
|
|
|
@@ -1521,8 +1520,8 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1521
1520
|
|
|
1522
1521
|
if (!showdown or damage > 0) {
|
|
1523
1522
|
stored.hp -= damage;
|
|
1524
|
-
// Pokémon Showdown uses
|
|
1525
|
-
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
1523
|
+
// Pokémon Showdown uses damage [of] here but its not relevant in Generation I
|
|
1524
|
+
try options.log.damage(.{ ident, stored, if (brn) Damage.Burn else Damage.Poison });
|
|
1526
1525
|
}
|
|
1527
1526
|
}
|
|
1528
1527
|
|
|
@@ -1547,14 +1546,14 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1547
1546
|
const amount = @min(damage, stored.hp);
|
|
1548
1547
|
stored.hp -= amount;
|
|
1549
1548
|
|
|
1550
|
-
// As above, Pokémon Showdown uses
|
|
1551
|
-
if (amount > 0) try options.log.damage(ident, stored, .LeechSeed);
|
|
1549
|
+
// As above, Pokémon Showdown uses damage [of] but its not relevant
|
|
1550
|
+
if (amount > 0) try options.log.damage(.{ ident, stored, .LeechSeed });
|
|
1552
1551
|
|
|
1553
1552
|
const before = foe_stored.hp;
|
|
1554
1553
|
// Uncapped damage is added back to the foe
|
|
1555
1554
|
foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
|
|
1556
1555
|
// Pokémon Showdown uses the less specific heal here instead of drain... because reasons?
|
|
1557
|
-
if (foe_stored.hp > before) try options.log.heal(foe_ident, foe_stored, .Silent);
|
|
1556
|
+
if (foe_stored.hp > before) try options.log.heal(.{ foe_ident, foe_stored, .Silent });
|
|
1558
1557
|
}
|
|
1559
1558
|
}
|
|
1560
1559
|
|
|
@@ -1565,18 +1564,18 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1565
1564
|
battle.turn += 1;
|
|
1566
1565
|
|
|
1567
1566
|
if (showdown and pkmn.options.ebc and checkEBC(battle)) {
|
|
1568
|
-
try options.log.tie();
|
|
1567
|
+
try options.log.tie(.{});
|
|
1569
1568
|
return Result.Tie;
|
|
1570
1569
|
}
|
|
1571
1570
|
|
|
1572
1571
|
if (showdown and battle.turn >= 1000) {
|
|
1573
|
-
try options.log.tie();
|
|
1572
|
+
try options.log.tie(.{});
|
|
1574
1573
|
return Result.Tie;
|
|
1575
1574
|
} else if (battle.turn >= 65535) {
|
|
1576
1575
|
return Result.Error;
|
|
1577
1576
|
}
|
|
1578
1577
|
|
|
1579
|
-
try options.log.turn(battle.turn);
|
|
1578
|
+
try options.log.turn(.{battle.turn});
|
|
1580
1579
|
|
|
1581
1580
|
return Result.Default;
|
|
1582
1581
|
}
|
|
@@ -1646,14 +1645,14 @@ fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
|
1646
1645
|
side.last_selected_move;
|
|
1647
1646
|
if (m != .None and m == last) {
|
|
1648
1647
|
side.active.volatiles.Charging = false;
|
|
1649
|
-
try options.log.
|
|
1648
|
+
try options.log.cant(.{ ident, .Disable, last });
|
|
1650
1649
|
return true;
|
|
1651
1650
|
}
|
|
1652
1651
|
}
|
|
1653
1652
|
return false;
|
|
1654
1653
|
}
|
|
1655
1654
|
|
|
1656
|
-
|
|
1655
|
+
fn buildRage(battle: anytype, who: Player, options: anytype) !void {
|
|
1657
1656
|
const side = battle.side(who);
|
|
1658
1657
|
if (side.active.volatiles.Rage and side.active.boosts.atk < 6) {
|
|
1659
1658
|
try Effects.boost(battle, who, Move.get(.Rage), options);
|
|
@@ -1671,7 +1670,7 @@ fn handleThrashing(battle: anytype, active: *ActivePokemon, player: Player, opti
|
|
|
1671
1670
|
return true;
|
|
1672
1671
|
}
|
|
1673
1672
|
|
|
1674
|
-
|
|
1673
|
+
fn onBegin(
|
|
1675
1674
|
battle: anytype,
|
|
1676
1675
|
player: Player,
|
|
1677
1676
|
move: Move.Data,
|
|
@@ -1679,7 +1678,6 @@ inline fn onBegin(
|
|
|
1679
1678
|
residual: *bool,
|
|
1680
1679
|
options: anytype,
|
|
1681
1680
|
) !void {
|
|
1682
|
-
assert(move.effect.onBegin());
|
|
1683
1681
|
return switch (move.effect) {
|
|
1684
1682
|
.Confusion => Effects.confusion(battle, player, move, options),
|
|
1685
1683
|
.Conversion => Effects.conversion(battle, player, options),
|
|
@@ -1701,8 +1699,7 @@ inline fn onBegin(
|
|
|
1701
1699
|
};
|
|
1702
1700
|
}
|
|
1703
1701
|
|
|
1704
|
-
|
|
1705
|
-
assert(move.effect.onEnd());
|
|
1702
|
+
fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1706
1703
|
return switch (move.effect) {
|
|
1707
1704
|
.Bide => Effects.bide(battle, player, options),
|
|
1708
1705
|
// zig fmt: off
|
|
@@ -1717,14 +1714,13 @@ inline fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anyty
|
|
|
1717
1714
|
};
|
|
1718
1715
|
}
|
|
1719
1716
|
|
|
1720
|
-
|
|
1717
|
+
fn alwaysHappens(
|
|
1721
1718
|
battle: anytype,
|
|
1722
1719
|
player: Player,
|
|
1723
1720
|
move: Move.Data,
|
|
1724
1721
|
residual: *bool,
|
|
1725
1722
|
options: anytype,
|
|
1726
1723
|
) !void {
|
|
1727
|
-
assert(move.effect.alwaysHappens());
|
|
1728
1724
|
return switch (move.effect) {
|
|
1729
1725
|
.DrainHP, .DreamEater => Effects.drainHP(battle, player, options),
|
|
1730
1726
|
.Explode => Effects.explode(battle, player),
|
|
@@ -1736,7 +1732,7 @@ inline fn alwaysHappens(
|
|
|
1736
1732
|
};
|
|
1737
1733
|
}
|
|
1738
1734
|
|
|
1739
|
-
|
|
1735
|
+
fn moveEffect(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1740
1736
|
return switch (move.effect) {
|
|
1741
1737
|
.BurnChance1, .BurnChance2 => Effects.burnChance(battle, player, move, options),
|
|
1742
1738
|
.ConfusionChance => Effects.confusion(battle, player, move, options),
|
|
@@ -1763,7 +1759,7 @@ pub const Effects = struct {
|
|
|
1763
1759
|
side.active.volatiles.state = 0;
|
|
1764
1760
|
side.active.volatiles.attacks = Rolls.attackingDuration(battle, player, options);
|
|
1765
1761
|
|
|
1766
|
-
try options.log.start(battle.active(player), .Bide);
|
|
1762
|
+
try options.log.start(.{ battle.active(player), .Bide });
|
|
1767
1763
|
}
|
|
1768
1764
|
|
|
1769
1765
|
fn burnChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1777,11 +1773,11 @@ pub const Effects = struct {
|
|
|
1777
1773
|
// GLITCH: Freeze top move selection desync can occur if thawed Pokémon is slower
|
|
1778
1774
|
if (Status.is(foe_stored.status, .FRZ)) {
|
|
1779
1775
|
assert(move.type == .Fire);
|
|
1780
|
-
try options.log.curestatus(
|
|
1776
|
+
try options.log.curestatus(.{
|
|
1781
1777
|
battle.active(player.foe()),
|
|
1782
1778
|
foe_stored.status,
|
|
1783
1779
|
.Message,
|
|
1784
|
-
);
|
|
1780
|
+
});
|
|
1785
1781
|
foe_stored.status = 0;
|
|
1786
1782
|
}
|
|
1787
1783
|
return;
|
|
@@ -1795,7 +1791,7 @@ pub const Effects = struct {
|
|
|
1795
1791
|
foe_stored.status = Status.init(.BRN);
|
|
1796
1792
|
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
1797
1793
|
|
|
1798
|
-
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
1794
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1799
1795
|
}
|
|
1800
1796
|
|
|
1801
1797
|
fn charge(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -1805,8 +1801,8 @@ pub const Effects = struct {
|
|
|
1805
1801
|
volatiles.Charging = true;
|
|
1806
1802
|
const move = side.last_selected_move;
|
|
1807
1803
|
if (move == .Fly or move == .Dig) volatiles.Invulnerable = true;
|
|
1808
|
-
try options.log.laststill();
|
|
1809
|
-
try options.log.prepare(battle.active(player), move);
|
|
1804
|
+
try options.log.laststill(.{});
|
|
1805
|
+
try options.log.prepare(.{ battle.active(player), move });
|
|
1810
1806
|
}
|
|
1811
1807
|
|
|
1812
1808
|
fn confusion(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1817,42 +1813,36 @@ pub const Effects = struct {
|
|
|
1817
1813
|
if (!try Rolls.confusionChance(battle, player, options)) return;
|
|
1818
1814
|
} else {
|
|
1819
1815
|
if (showdown) {
|
|
1820
|
-
if (!try checkHit(battle, player, move, options))
|
|
1821
|
-
|
|
1822
|
-
} else if (sub) {
|
|
1823
|
-
return options.log.fail(battle.active(player.foe()), .None);
|
|
1824
|
-
}
|
|
1816
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1817
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1825
1818
|
} else {
|
|
1826
|
-
if (sub) {
|
|
1827
|
-
|
|
1828
|
-
} else if (!try checkHit(battle, player, move, options)) {
|
|
1829
|
-
return;
|
|
1830
|
-
}
|
|
1819
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1820
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1831
1821
|
}
|
|
1832
|
-
try options.chance.commit(player, .hit);
|
|
1833
1822
|
}
|
|
1834
1823
|
|
|
1835
1824
|
if (foe.active.volatiles.Confusion) return;
|
|
1836
1825
|
foe.active.volatiles.Confusion = true;
|
|
1837
1826
|
foe.active.volatiles.confusion = Rolls.confusionDuration(battle, player, false, options);
|
|
1838
1827
|
|
|
1839
|
-
try options.log.start(battle.active(player.foe()), .Confusion);
|
|
1828
|
+
try options.log.start(.{ battle.active(player.foe()), .Confusion });
|
|
1840
1829
|
}
|
|
1841
1830
|
|
|
1842
1831
|
fn conversion(battle: anytype, player: Player, options: anytype) !void {
|
|
1843
1832
|
const foe = battle.foe(player);
|
|
1844
1833
|
|
|
1845
1834
|
if (foe.active.volatiles.Invulnerable) {
|
|
1846
|
-
try options.log.lastmiss();
|
|
1847
|
-
return options.log.miss(battle.active(player));
|
|
1835
|
+
try options.log.lastmiss(.{});
|
|
1836
|
+
return options.log.miss(.{battle.active(player)});
|
|
1848
1837
|
}
|
|
1849
1838
|
|
|
1850
1839
|
battle.side(player).active.types = foe.active.types;
|
|
1851
|
-
return options.log.
|
|
1840
|
+
return options.log.start(.{
|
|
1852
1841
|
battle.active(player),
|
|
1842
|
+
.TypeChange,
|
|
1853
1843
|
foe.active.types,
|
|
1854
1844
|
battle.active(player.foe()),
|
|
1855
|
-
);
|
|
1845
|
+
});
|
|
1856
1846
|
}
|
|
1857
1847
|
|
|
1858
1848
|
fn disable(battle: anytype, player: Player, move: Move.Data, options: anytype) !?Result {
|
|
@@ -1861,13 +1851,10 @@ pub const Effects = struct {
|
|
|
1861
1851
|
const foe_ident = battle.active(player.foe());
|
|
1862
1852
|
|
|
1863
1853
|
// Pokémon Showdown handles hit/miss earlier in doMove
|
|
1864
|
-
if (!showdown)
|
|
1865
|
-
if (!try checkHit(battle, player, move, options)) return null;
|
|
1866
|
-
try options.chance.commit(player, .hit);
|
|
1867
|
-
}
|
|
1854
|
+
if (!showdown and !try checkHit(battle, player, move, options)) return null;
|
|
1868
1855
|
|
|
1869
1856
|
if (volatiles.disable_move != 0) {
|
|
1870
|
-
try options.log.fail(foe_ident, .None);
|
|
1857
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1871
1858
|
return null;
|
|
1872
1859
|
}
|
|
1873
1860
|
|
|
@@ -1883,7 +1870,7 @@ pub const Effects = struct {
|
|
|
1883
1870
|
// Technically this is still considered simply a "miss" on the cartridge,
|
|
1884
1871
|
// but diverging from Pokémon Showdown here would mostly just be pedantic
|
|
1885
1872
|
if (n == 0) {
|
|
1886
|
-
try options.log.fail(foe_ident, .None);
|
|
1873
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1887
1874
|
return null;
|
|
1888
1875
|
} else if (err) {
|
|
1889
1876
|
// GLITCH: Transform + Mirror Move / Metronome PP softlock
|
|
@@ -1896,13 +1883,13 @@ pub const Effects = struct {
|
|
|
1896
1883
|
volatiles.disable_duration = Rolls.disableDuration(battle, player, options);
|
|
1897
1884
|
|
|
1898
1885
|
const id = foe.active.move(volatiles.disable_move).id;
|
|
1899
|
-
try options.log.
|
|
1886
|
+
try options.log.start(.{ foe_ident, .Disable, id });
|
|
1900
1887
|
return null;
|
|
1901
1888
|
}
|
|
1902
1889
|
|
|
1903
1890
|
fn drainHP(battle: anytype, player: Player, options: anytype) !void {
|
|
1904
|
-
var
|
|
1905
|
-
|
|
1891
|
+
var stored = battle.side(player).stored();
|
|
1892
|
+
const ident = battle.active(player);
|
|
1906
1893
|
|
|
1907
1894
|
if (battle.last_damage == 0) {
|
|
1908
1895
|
assert(showdown);
|
|
@@ -1915,7 +1902,7 @@ pub const Effects = struct {
|
|
|
1915
1902
|
if (stored.hp == stored.stats.hp) return;
|
|
1916
1903
|
stored.hp = @min(stored.stats.hp, stored.hp + drain);
|
|
1917
1904
|
|
|
1918
|
-
try options.log.
|
|
1905
|
+
try options.log.heal(.{ ident, stored, .Drain, battle.active(player.foe()) });
|
|
1919
1906
|
}
|
|
1920
1907
|
|
|
1921
1908
|
fn explode(battle: anytype, player: Player) !void {
|
|
@@ -1947,7 +1934,7 @@ pub const Effects = struct {
|
|
|
1947
1934
|
if (side.active.volatiles.FocusEnergy) return;
|
|
1948
1935
|
side.active.volatiles.FocusEnergy = true;
|
|
1949
1936
|
|
|
1950
|
-
try options.log.start(battle.active(player), .FocusEnergy);
|
|
1937
|
+
try options.log.start(.{ battle.active(player), .FocusEnergy });
|
|
1951
1938
|
}
|
|
1952
1939
|
|
|
1953
1940
|
fn freezeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1969,7 +1956,7 @@ pub const Effects = struct {
|
|
|
1969
1956
|
foe_stored.status = Status.init(.FRZ);
|
|
1970
1957
|
// GLITCH: Hyper Beam recharging status is not cleared
|
|
1971
1958
|
|
|
1972
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
1959
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
1973
1960
|
}
|
|
1974
1961
|
|
|
1975
1962
|
fn haze(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -1977,7 +1964,7 @@ pub const Effects = struct {
|
|
|
1977
1964
|
var side = battle.side(player);
|
|
1978
1965
|
var foe = battle.foe(player);
|
|
1979
1966
|
|
|
1980
|
-
|
|
1967
|
+
const side_stored = side.stored();
|
|
1981
1968
|
var foe_stored = foe.stored();
|
|
1982
1969
|
|
|
1983
1970
|
const player_ident = battle.active(player);
|
|
@@ -1989,8 +1976,8 @@ pub const Effects = struct {
|
|
|
1989
1976
|
side.active.stats = side_stored.stats;
|
|
1990
1977
|
foe.active.stats = foe_stored.stats;
|
|
1991
1978
|
|
|
1992
|
-
try log.activate(player_ident, .Haze);
|
|
1993
|
-
try log.clearallboost();
|
|
1979
|
+
try log.activate(.{ player_ident, .Haze });
|
|
1980
|
+
try log.clearallboost(.{});
|
|
1994
1981
|
|
|
1995
1982
|
// Pokémon Showdown clears P1 then P2 instead of status -> side -> foe
|
|
1996
1983
|
if (showdown) {
|
|
@@ -1998,11 +1985,11 @@ pub const Effects = struct {
|
|
|
1998
1985
|
const p: Player = @enumFromInt(i);
|
|
1999
1986
|
// Pokémon Showdown incorrectly does not prevent sleep/freeze from moving
|
|
2000
1987
|
if (p != player and Status.any(s.stored().status)) {
|
|
2001
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
1988
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2002
1989
|
s.stored().status = 0;
|
|
2003
1990
|
} else if (showdown and s.stored().status == Status.TOX) {
|
|
2004
1991
|
s.stored().status = Status.init(.PSN);
|
|
2005
|
-
try log.status(battle.active(p), s.stored().status, .None);
|
|
1992
|
+
try log.status(.{ battle.active(p), s.stored().status, .None });
|
|
2006
1993
|
}
|
|
2007
1994
|
try clearVolatiles(battle, p, p != player, options);
|
|
2008
1995
|
}
|
|
@@ -2011,7 +1998,7 @@ pub const Effects = struct {
|
|
|
2011
1998
|
if (Status.is(foe_stored.status, .FRZ) or Status.is(foe_stored.status, .SLP)) {
|
|
2012
1999
|
foe.last_selected_move = .SKIP_TURN;
|
|
2013
2000
|
}
|
|
2014
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
2001
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2015
2002
|
foe_stored.status = 0;
|
|
2016
2003
|
}
|
|
2017
2004
|
try clearVolatiles(battle, player, false, options);
|
|
@@ -2027,7 +2014,7 @@ pub const Effects = struct {
|
|
|
2027
2014
|
// GLITCH: HP recovery move failure glitches
|
|
2028
2015
|
const delta = stored.stats.hp - stored.hp;
|
|
2029
2016
|
if (delta == 0 or (delta & 255 == 255 and stored.hp % 256 != 0)) {
|
|
2030
|
-
return
|
|
2017
|
+
return options.log.fail(.{ ident, .None });
|
|
2031
2018
|
}
|
|
2032
2019
|
|
|
2033
2020
|
const rest = side.last_selected_move == .Rest;
|
|
@@ -2035,17 +2022,17 @@ pub const Effects = struct {
|
|
|
2035
2022
|
// Adding the sleep status runs the sleep condition handler to roll duration
|
|
2036
2023
|
if (showdown) battle.rng.advance(1);
|
|
2037
2024
|
stored.status = Status.slf(2);
|
|
2038
|
-
try options.log.
|
|
2025
|
+
try options.log.status(.{ ident, stored.status, .From, Move.Rest });
|
|
2039
2026
|
stored.hp = stored.stats.hp;
|
|
2040
2027
|
} else {
|
|
2041
2028
|
stored.hp = @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2));
|
|
2042
2029
|
}
|
|
2043
|
-
try options.log.heal(ident, stored, if (rest) Heal.Silent else Heal.None);
|
|
2030
|
+
try options.log.heal(.{ ident, stored, if (rest) Heal.Silent else Heal.None });
|
|
2044
2031
|
}
|
|
2045
2032
|
|
|
2046
2033
|
fn hyperBeam(battle: anytype, player: Player, options: anytype) !void {
|
|
2047
2034
|
battle.side(player).active.volatiles.Recharging = true;
|
|
2048
|
-
try options.log.mustrecharge(battle.active(player));
|
|
2035
|
+
try options.log.mustrecharge(.{battle.active(player)});
|
|
2049
2036
|
}
|
|
2050
2037
|
|
|
2051
2038
|
fn leechSeed(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2054,33 +2041,31 @@ pub const Effects = struct {
|
|
|
2054
2041
|
if (showdown) {
|
|
2055
2042
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2056
2043
|
if (!foe.active.volatiles.Invulnerable and foe.active.types.includes(.Grass)) {
|
|
2057
|
-
return options.log.immune(battle.active(player.foe()), .None);
|
|
2044
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
2058
2045
|
}
|
|
2059
2046
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2060
2047
|
if (foe.active.volatiles.LeechSeed) return;
|
|
2061
2048
|
} else {
|
|
2062
2049
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2063
2050
|
if (foe.active.types.includes(.Grass) or foe.active.volatiles.LeechSeed) {
|
|
2064
|
-
try options.log.lastmiss();
|
|
2065
|
-
return options.log.miss(battle.active(player));
|
|
2051
|
+
try options.log.lastmiss(.{});
|
|
2052
|
+
return options.log.miss(.{battle.active(player)});
|
|
2066
2053
|
}
|
|
2067
2054
|
}
|
|
2068
2055
|
|
|
2069
|
-
try options.chance.commit(player, .hit);
|
|
2070
2056
|
foe.active.volatiles.LeechSeed = true;
|
|
2071
2057
|
|
|
2072
|
-
try options.log.start(battle.active(player.foe()), .LeechSeed);
|
|
2058
|
+
try options.log.start(.{ battle.active(player.foe()), .LeechSeed });
|
|
2073
2059
|
}
|
|
2074
2060
|
|
|
2075
2061
|
fn lightScreen(battle: anytype, player: Player, options: anytype) !void {
|
|
2076
2062
|
var side = battle.side(player);
|
|
2063
|
+
const ident = battle.active(player);
|
|
2077
2064
|
|
|
2078
|
-
if (side.active.volatiles.LightScreen) {
|
|
2079
|
-
return options.log.fail(battle.active(player), .None);
|
|
2080
|
-
}
|
|
2065
|
+
if (side.active.volatiles.LightScreen) return options.log.fail(.{ ident, .None });
|
|
2081
2066
|
side.active.volatiles.LightScreen = true;
|
|
2082
2067
|
|
|
2083
|
-
try options.log.start(
|
|
2068
|
+
try options.log.start(.{ ident, .LightScreen });
|
|
2084
2069
|
}
|
|
2085
2070
|
|
|
2086
2071
|
fn mimic(battle: anytype, player: Player, move: Move.Data, mslot: u8, options: anytype) !void {
|
|
@@ -2108,17 +2093,15 @@ pub const Effects = struct {
|
|
|
2108
2093
|
if (!has_mimic) {
|
|
2109
2094
|
// Invulnerable foes or 1/256 miss can trigger |-miss| instead of |-fail|
|
|
2110
2095
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2111
|
-
|
|
2112
|
-
return try options.log.fail(battle.active(player.foe()), .None);
|
|
2096
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2113
2097
|
}
|
|
2114
2098
|
}
|
|
2115
2099
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2116
|
-
try options.chance.commit(player, .hit);
|
|
2117
2100
|
|
|
2118
2101
|
const rslot = try Rolls.moveSlot(battle, player, &foe.active.moves, 0, options);
|
|
2119
2102
|
side.active.move(oslot).id = foe.active.move(rslot).id;
|
|
2120
2103
|
|
|
2121
|
-
try options.log.
|
|
2104
|
+
try options.log.start(.{ battle.active(player), .Mimic, side.active.move(oslot).id });
|
|
2122
2105
|
}
|
|
2123
2106
|
|
|
2124
2107
|
fn mist(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -2127,7 +2110,7 @@ pub const Effects = struct {
|
|
|
2127
2110
|
if (side.active.volatiles.Mist) return;
|
|
2128
2111
|
side.active.volatiles.Mist = true;
|
|
2129
2112
|
|
|
2130
|
-
try options.log.start(battle.active(player), .Mist);
|
|
2113
|
+
try options.log.start(.{ battle.active(player), .Mist });
|
|
2131
2114
|
}
|
|
2132
2115
|
|
|
2133
2116
|
fn multiHit(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2153,25 +2136,25 @@ pub const Effects = struct {
|
|
|
2153
2136
|
|
|
2154
2137
|
if (showdown) {
|
|
2155
2138
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2156
|
-
if (immune and !foe.active.volatiles.Invulnerable)
|
|
2157
|
-
|
|
2139
|
+
if (immune and !foe.active.volatiles.Invulnerable) {
|
|
2140
|
+
return log.immune(.{ foe_ident, .None });
|
|
2141
|
+
} else if (!try checkHit(battle, player, move, options)) return;
|
|
2158
2142
|
}
|
|
2159
2143
|
if (Status.any(foe_stored.status)) {
|
|
2160
|
-
return log.fail(
|
|
2144
|
+
return log.fail(.{
|
|
2161
2145
|
foe_ident,
|
|
2162
|
-
if (Status.is(foe_stored.status, .PAR)) .Paralysis else .None,
|
|
2163
|
-
);
|
|
2146
|
+
if (Status.is(foe_stored.status, .PAR)) Fail.Paralysis else Fail.None,
|
|
2147
|
+
});
|
|
2164
2148
|
}
|
|
2165
2149
|
if (!showdown) {
|
|
2166
|
-
if (immune) return log.immune(foe_ident, .None);
|
|
2150
|
+
if (immune) return log.immune(.{ foe_ident, .None });
|
|
2167
2151
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2168
2152
|
}
|
|
2169
|
-
try options.chance.commit(player, .hit);
|
|
2170
2153
|
|
|
2171
2154
|
foe_stored.status = Status.init(.PAR);
|
|
2172
2155
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2173
2156
|
|
|
2174
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2157
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2175
2158
|
}
|
|
2176
2159
|
|
|
2177
2160
|
fn paralyzeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2193,12 +2176,12 @@ pub const Effects = struct {
|
|
|
2193
2176
|
foe_stored.status = Status.init(.PAR);
|
|
2194
2177
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2195
2178
|
|
|
2196
|
-
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
2179
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
2197
2180
|
}
|
|
2198
2181
|
|
|
2199
2182
|
fn payDay(battle: anytype, player: Player, options: anytype) !void {
|
|
2200
2183
|
if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
|
|
2201
|
-
try options.log.fieldactivate();
|
|
2184
|
+
try options.log.fieldactivate(.{});
|
|
2202
2185
|
}
|
|
2203
2186
|
}
|
|
2204
2187
|
|
|
@@ -2213,28 +2196,27 @@ pub const Effects = struct {
|
|
|
2213
2196
|
return;
|
|
2214
2197
|
} else if (foe.active.volatiles.Substitute) {
|
|
2215
2198
|
if (move.effect != .Poison) return;
|
|
2216
|
-
return log.fail(foe_ident, .None);
|
|
2199
|
+
return log.fail(.{ foe_ident, .None });
|
|
2217
2200
|
} else if (Status.any(foe_stored.status)) {
|
|
2218
2201
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2219
2202
|
// Pokémon Showdown considers Toxic to be a status even in Generation I and so
|
|
2220
2203
|
// will not include a fail reason for Toxic vs. Poison or vice-versa...
|
|
2221
|
-
return log.fail(foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2204
|
+
return log.fail(.{ foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2222
2205
|
if (!showdown)
|
|
2223
|
-
.Poison
|
|
2206
|
+
Fail.Poison
|
|
2224
2207
|
else if (toxic == (foe_stored.status == Status.TOX))
|
|
2225
|
-
if (toxic) .Toxic else .Poison
|
|
2208
|
+
if (toxic) Fail.Toxic else Fail.Poison
|
|
2226
2209
|
else
|
|
2227
|
-
.None
|
|
2210
|
+
Fail.None
|
|
2228
2211
|
else
|
|
2229
|
-
.None);
|
|
2212
|
+
Fail.None });
|
|
2230
2213
|
} else if (foe.active.types.includes(.Poison)) {
|
|
2231
2214
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2232
|
-
return log.immune(foe_ident, .None);
|
|
2215
|
+
return log.immune(.{ foe_ident, .None });
|
|
2233
2216
|
}
|
|
2234
2217
|
|
|
2235
2218
|
if (move.effect == .Poison) {
|
|
2236
2219
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
2237
|
-
try options.chance.commit(player, .hit);
|
|
2238
2220
|
} else {
|
|
2239
2221
|
if (!try Rolls.poisonChance(battle, player, move.effect == .PoisonChance1, options)) {
|
|
2240
2222
|
return;
|
|
@@ -2248,7 +2230,7 @@ pub const Effects = struct {
|
|
|
2248
2230
|
foe.active.volatiles.toxic = 0;
|
|
2249
2231
|
}
|
|
2250
2232
|
|
|
2251
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2233
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2252
2234
|
}
|
|
2253
2235
|
|
|
2254
2236
|
fn rage(battle: anytype, player: Player) !void {
|
|
@@ -2268,22 +2250,23 @@ pub const Effects = struct {
|
|
|
2268
2250
|
@as(u8, if (side.last_selected_move == .Struggle) 2 else 4), 1));
|
|
2269
2251
|
stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
|
|
2270
2252
|
|
|
2271
|
-
try options.log.
|
|
2253
|
+
try options.log.damage(.{
|
|
2272
2254
|
battle.active(player),
|
|
2273
2255
|
stored,
|
|
2274
2256
|
.RecoilOf,
|
|
2275
2257
|
battle.active(player.foe()),
|
|
2276
|
-
);
|
|
2258
|
+
});
|
|
2277
2259
|
if (showdown and stored.hp == 0) residual.* = false;
|
|
2278
2260
|
}
|
|
2279
2261
|
|
|
2280
2262
|
fn reflect(battle: anytype, player: Player, options: anytype) !void {
|
|
2281
2263
|
var side = battle.side(player);
|
|
2264
|
+
const ident = battle.active(player);
|
|
2282
2265
|
|
|
2283
|
-
if (side.active.volatiles.Reflect) return options.log.fail(
|
|
2266
|
+
if (side.active.volatiles.Reflect) return options.log.fail(.{ ident, .None });
|
|
2284
2267
|
side.active.volatiles.Reflect = true;
|
|
2285
2268
|
|
|
2286
|
-
try options.log.start(
|
|
2269
|
+
try options.log.start(.{ ident, .Reflect });
|
|
2287
2270
|
}
|
|
2288
2271
|
|
|
2289
2272
|
fn sleep(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2297,14 +2280,13 @@ pub const Effects = struct {
|
|
|
2297
2280
|
// the Sleep Clause Mod might activate, causing us to not actually bypass
|
|
2298
2281
|
} else {
|
|
2299
2282
|
if (Status.any(foe_stored.status)) {
|
|
2300
|
-
return options.log.fail(
|
|
2283
|
+
return options.log.fail(.{
|
|
2301
2284
|
foe_ident,
|
|
2302
|
-
if (Status.is(foe_stored.status, .SLP)) .Sleep else .None,
|
|
2303
|
-
);
|
|
2285
|
+
if (Status.is(foe_stored.status, .SLP)) Fail.Sleep else Fail.None,
|
|
2286
|
+
});
|
|
2304
2287
|
}
|
|
2305
|
-
// If
|
|
2288
|
+
// If moveHit in doMove didn't return true Pokémon Showdown wouldn't be in here
|
|
2306
2289
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
2307
|
-
try options.chance.commit(player, .hit);
|
|
2308
2290
|
}
|
|
2309
2291
|
|
|
2310
2292
|
// Sleep Clause Mod
|
|
@@ -2317,18 +2299,17 @@ pub const Effects = struct {
|
|
|
2317
2299
|
|
|
2318
2300
|
foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
|
|
2319
2301
|
const last = battle.side(player).last_selected_move;
|
|
2320
|
-
try options.log.
|
|
2302
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .From, last });
|
|
2321
2303
|
}
|
|
2322
2304
|
|
|
2323
2305
|
fn splash(battle: anytype, player: Player, options: anytype) !void {
|
|
2324
|
-
try options.log.activate(battle.active(player), .Splash);
|
|
2306
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
2325
2307
|
}
|
|
2326
2308
|
|
|
2327
2309
|
fn substitute(battle: anytype, player: Player, residual: *bool, options: anytype) !void {
|
|
2328
2310
|
var side = battle.side(player);
|
|
2329
2311
|
if (side.active.volatiles.Substitute) {
|
|
2330
|
-
|
|
2331
|
-
return;
|
|
2312
|
+
return options.log.fail(.{ battle.active(player), .Substitute });
|
|
2332
2313
|
}
|
|
2333
2314
|
|
|
2334
2315
|
assert(side.stored().stats.hp <= 1023);
|
|
@@ -2340,17 +2321,16 @@ pub const Effects = struct {
|
|
|
2340
2321
|
// avoid having to convert to floating point)
|
|
2341
2322
|
const required_hp = if (showdown) @divFloor(side.stored().stats.hp - 1, 4) + 1 else hp;
|
|
2342
2323
|
if (side.stored().hp < required_hp) {
|
|
2343
|
-
|
|
2344
|
-
return;
|
|
2324
|
+
return options.log.fail(.{ battle.active(player), .Weak });
|
|
2345
2325
|
}
|
|
2346
2326
|
|
|
2347
2327
|
// GLITCH: can leave the user with 0 HP (faints later) because didn't check '<=' above
|
|
2348
2328
|
side.stored().hp -= hp;
|
|
2349
2329
|
side.active.volatiles.substitute = hp + 1;
|
|
2350
2330
|
side.active.volatiles.Substitute = true;
|
|
2351
|
-
try options.log.start(battle.active(player), .Substitute);
|
|
2331
|
+
try options.log.start(.{ battle.active(player), .Substitute });
|
|
2352
2332
|
if (hp > 0) {
|
|
2353
|
-
try options.log.damage(battle.active(player), side.stored(), .None);
|
|
2333
|
+
try options.log.damage(.{ battle.active(player), side.stored(), .None });
|
|
2354
2334
|
if (showdown and side.stored().hp == 0) residual.* = false;
|
|
2355
2335
|
}
|
|
2356
2336
|
}
|
|
@@ -2359,13 +2339,11 @@ pub const Effects = struct {
|
|
|
2359
2339
|
// Whirlwind/Roar should not roll to hit/reset damage but Pokémon Showdown does anyway
|
|
2360
2340
|
if (showdown) {
|
|
2361
2341
|
if (battle.side(player).last_selected_move == .Teleport) return;
|
|
2362
|
-
|
|
2363
|
-
try options.chance.commit(player, .hit);
|
|
2364
|
-
}
|
|
2342
|
+
_ = try checkHit(battle, player, move, options);
|
|
2365
2343
|
battle.last_damage = 0;
|
|
2366
2344
|
} else {
|
|
2367
|
-
try options.log.fail(battle.active(player), .None);
|
|
2368
|
-
try options.log.laststill();
|
|
2345
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2346
|
+
try options.log.laststill(.{});
|
|
2369
2347
|
}
|
|
2370
2348
|
}
|
|
2371
2349
|
|
|
@@ -2404,7 +2382,7 @@ pub const Effects = struct {
|
|
|
2404
2382
|
side.active.moves[i].pp = if (m.id != .None) 5 else 0;
|
|
2405
2383
|
}
|
|
2406
2384
|
|
|
2407
|
-
try options.log.transform(battle.active(player), foe_ident);
|
|
2385
|
+
try options.log.transform(.{ battle.active(player), foe_ident });
|
|
2408
2386
|
}
|
|
2409
2387
|
|
|
2410
2388
|
fn binding(battle: anytype, player: Player, rewrap: bool, options: anytype) !void {
|
|
@@ -2434,87 +2412,87 @@ pub const Effects = struct {
|
|
|
2434
2412
|
switch (move.effect) {
|
|
2435
2413
|
.AttackUp1, .AttackUp2, .Rage => {
|
|
2436
2414
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2437
|
-
if (boosts.atk == 6) return
|
|
2415
|
+
if (boosts.atk == 6) return log.fail(.{ ident, .None });
|
|
2438
2416
|
const n: u2 = if (move.effect == .AttackUp2) 2 else 1;
|
|
2439
2417
|
boosts.atk = @as(i4, @intCast(@min(6, @as(i8, boosts.atk) + n)));
|
|
2440
|
-
const reason = if (move.effect == .Rage)
|
|
2418
|
+
const reason: Boost = if (move.effect == .Rage) .Rage else .Attack;
|
|
2441
2419
|
if (stats.atk == MAX_STAT_VALUE) {
|
|
2442
2420
|
boosts.atk -= 1;
|
|
2443
2421
|
if (showdown) {
|
|
2444
|
-
try log.boost(ident, reason, n);
|
|
2445
|
-
try log.boost(ident,
|
|
2422
|
+
try log.boost(.{ ident, reason, n });
|
|
2423
|
+
try log.boost(.{ ident, .Attack, -1 });
|
|
2446
2424
|
if (move.effect == .Rage) return;
|
|
2447
2425
|
}
|
|
2448
|
-
return
|
|
2426
|
+
return log.fail(.{ ident, .None });
|
|
2449
2427
|
}
|
|
2450
|
-
|
|
2428
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2451
2429
|
const stat = unmodifiedStats(battle, side).atk;
|
|
2452
2430
|
stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2453
|
-
try log.boost(ident, reason, n);
|
|
2431
|
+
try log.boost(.{ ident, reason, n });
|
|
2454
2432
|
// Pokémon Showdown doesn't re-apply status modifiers after Rage boosts
|
|
2455
2433
|
if (showdown and move.effect == .Rage) return;
|
|
2456
2434
|
},
|
|
2457
2435
|
.DefenseUp1, .DefenseUp2 => {
|
|
2458
2436
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2459
|
-
if (boosts.def == 6) return
|
|
2437
|
+
if (boosts.def == 6) return log.fail(.{ ident, .None });
|
|
2460
2438
|
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
2461
2439
|
boosts.def = @intCast(@min(6, @as(i8, boosts.def) + n));
|
|
2462
2440
|
if (stats.def == MAX_STAT_VALUE) {
|
|
2463
2441
|
boosts.def -= 1;
|
|
2464
2442
|
if (showdown) {
|
|
2465
|
-
try log.boost(ident, .Defense, n);
|
|
2466
|
-
try log.boost(ident, .Defense, -1);
|
|
2443
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2444
|
+
try log.boost(.{ ident, .Defense, -1 });
|
|
2467
2445
|
}
|
|
2468
|
-
return
|
|
2446
|
+
return log.fail(.{ ident, .None });
|
|
2469
2447
|
}
|
|
2470
|
-
|
|
2448
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2471
2449
|
const stat = unmodifiedStats(battle, side).def;
|
|
2472
2450
|
stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2473
|
-
try log.boost(ident, .Defense, n);
|
|
2451
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2474
2452
|
},
|
|
2475
2453
|
.SpeedUp2 => {
|
|
2476
2454
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2477
|
-
if (boosts.spe == 6) return
|
|
2455
|
+
if (boosts.spe == 6) return log.fail(.{ ident, .None });
|
|
2478
2456
|
boosts.spe = @intCast(@min(6, @as(i8, boosts.spe) + 2));
|
|
2479
2457
|
if (stats.spe == MAX_STAT_VALUE) {
|
|
2480
2458
|
boosts.spe -= 1;
|
|
2481
2459
|
if (showdown) {
|
|
2482
|
-
try log.boost(ident, .Speed, 2);
|
|
2483
|
-
try log.boost(ident, .Speed, -1);
|
|
2460
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2461
|
+
try log.boost(.{ ident, .Speed, -1 });
|
|
2484
2462
|
}
|
|
2485
|
-
return
|
|
2463
|
+
return log.fail(.{ ident, .None });
|
|
2486
2464
|
}
|
|
2487
|
-
|
|
2465
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2488
2466
|
const stat = unmodifiedStats(battle, side).spe;
|
|
2489
2467
|
stats.spe = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2490
|
-
try log.boost(ident, .Speed, 2);
|
|
2468
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2491
2469
|
},
|
|
2492
2470
|
.SpecialUp1, .SpecialUp2 => {
|
|
2493
2471
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2494
|
-
if (boosts.spc == 6) return
|
|
2472
|
+
if (boosts.spc == 6) return log.fail(.{ ident, .None });
|
|
2495
2473
|
const n: u2 = if (move.effect == .SpecialUp2) 2 else 1;
|
|
2496
2474
|
boosts.spc = @intCast(@min(6, @as(i8, boosts.spc) + n));
|
|
2497
2475
|
if (stats.spc == MAX_STAT_VALUE) {
|
|
2498
2476
|
boosts.spc -= 1;
|
|
2499
2477
|
if (showdown) {
|
|
2500
|
-
try log.boost(ident, .SpecialAttack, n);
|
|
2501
|
-
try log.boost(ident, .SpecialAttack, -1);
|
|
2502
|
-
try log.boost(ident, .SpecialDefense, n);
|
|
2503
|
-
try log.boost(ident, .SpecialDefense, -1);
|
|
2478
|
+
try log.boost(.{ ident, .SpecialAttack, n });
|
|
2479
|
+
try log.boost(.{ ident, .SpecialAttack, -1 });
|
|
2480
|
+
try log.boost(.{ ident, .SpecialDefense, n });
|
|
2481
|
+
try log.boost(.{ ident, .SpecialDefense, -1 });
|
|
2504
2482
|
}
|
|
2505
|
-
return
|
|
2483
|
+
return log.fail(.{ ident, .None });
|
|
2506
2484
|
}
|
|
2507
|
-
|
|
2485
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2508
2486
|
const stat = unmodifiedStats(battle, side).spc;
|
|
2509
2487
|
stats.spc = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2510
|
-
try log.boost(ident, .SpecialAttack, n);
|
|
2511
|
-
try log.boost(ident, .SpecialDefense, n);
|
|
2488
|
+
try log.boost(.{ ident, .SpecialAttack, n });
|
|
2489
|
+
try log.boost(.{ ident, .SpecialDefense, n });
|
|
2512
2490
|
},
|
|
2513
2491
|
.EvasionUp1 => {
|
|
2514
2492
|
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2515
|
-
if (boosts.evasion == 6) return
|
|
2493
|
+
if (boosts.evasion == 6) return log.fail(.{ ident, .None });
|
|
2516
2494
|
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2517
|
-
try log.boost(ident, .Evasion, 1);
|
|
2495
|
+
try log.boost(.{ ident, .Evasion, 1 });
|
|
2518
2496
|
},
|
|
2519
2497
|
else => unreachable,
|
|
2520
2498
|
}
|
|
@@ -2529,7 +2507,7 @@ pub const Effects = struct {
|
|
|
2529
2507
|
const foe_ident = battle.active(player.foe());
|
|
2530
2508
|
const secondary = move.effect.isStatDownChance();
|
|
2531
2509
|
|
|
2532
|
-
if (foe.active.volatiles.Substitute) return if (!secondary) log.fail(foe_ident, .None);
|
|
2510
|
+
if (foe.active.volatiles.Substitute) return if (!secondary) log.fail(.{ foe_ident, .None });
|
|
2533
2511
|
|
|
2534
2512
|
if (secondary) {
|
|
2535
2513
|
const proc = try Rolls.unboost(battle, player, options);
|
|
@@ -2537,7 +2515,6 @@ pub const Effects = struct {
|
|
|
2537
2515
|
} else {
|
|
2538
2516
|
// checkHit already checks for Invulnerable
|
|
2539
2517
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
2540
|
-
try options.chance.commit(player, .hit);
|
|
2541
2518
|
}
|
|
2542
2519
|
|
|
2543
2520
|
var stats = &foe.active.stats;
|
|
@@ -2547,82 +2524,82 @@ pub const Effects = struct {
|
|
|
2547
2524
|
switch (move.effect) {
|
|
2548
2525
|
.AttackDown1, .AttackDownChance => {
|
|
2549
2526
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2550
|
-
if (boosts.atk == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2527
|
+
if (boosts.atk == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2551
2528
|
boosts.atk = @intCast(@max(-6, @as(i8, boosts.atk) - 1));
|
|
2552
2529
|
if (stats.atk == 1) {
|
|
2553
2530
|
boosts.atk += 1;
|
|
2554
2531
|
if (showdown) {
|
|
2555
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2556
|
-
try log.boost(foe_ident, .Attack, 1);
|
|
2532
|
+
try log.boost(.{ foe_ident, .Attack, -1 });
|
|
2533
|
+
try log.boost(.{ foe_ident, .Attack, 1 });
|
|
2557
2534
|
}
|
|
2558
|
-
return
|
|
2535
|
+
return log.fail(.{ foe_ident, .None });
|
|
2559
2536
|
}
|
|
2560
|
-
|
|
2537
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2561
2538
|
const stat = unmodifiedStats(battle, foe).atk;
|
|
2562
2539
|
stats.atk = @max(1, stat * mod[0] / mod[1]);
|
|
2563
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2540
|
+
try log.boost(.{ foe_ident, .Attack, -1 });
|
|
2564
2541
|
},
|
|
2565
2542
|
.DefenseDown1, .DefenseDown2, .DefenseDownChance => {
|
|
2566
2543
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2567
|
-
if (boosts.def == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2544
|
+
if (boosts.def == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2568
2545
|
const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
|
|
2569
2546
|
boosts.def = @intCast(@max(-6, @as(i8, boosts.def) - n));
|
|
2570
2547
|
if (stats.def == 1) {
|
|
2571
2548
|
boosts.def += 1;
|
|
2572
2549
|
if (showdown) {
|
|
2573
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2574
|
-
try log.boost(foe_ident, .Defense, 1);
|
|
2550
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2551
|
+
try log.boost(.{ foe_ident, .Defense, 1 });
|
|
2575
2552
|
}
|
|
2576
|
-
return
|
|
2553
|
+
return log.fail(.{ foe_ident, .None });
|
|
2577
2554
|
}
|
|
2578
|
-
|
|
2555
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2579
2556
|
const stat = unmodifiedStats(battle, foe).def;
|
|
2580
2557
|
stats.def = @max(1, stat * mod[0] / mod[1]);
|
|
2581
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2558
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2582
2559
|
},
|
|
2583
2560
|
.SpeedDown1, .SpeedDownChance => {
|
|
2584
2561
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2585
|
-
if (boosts.spe == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2562
|
+
if (boosts.spe == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2586
2563
|
boosts.spe = @intCast(@max(-6, @as(i8, boosts.spe) - 1));
|
|
2587
2564
|
if (stats.spe == 1) {
|
|
2588
2565
|
boosts.spe += 1;
|
|
2589
2566
|
if (showdown) {
|
|
2590
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2591
|
-
try log.boost(foe_ident, .Speed, 1);
|
|
2567
|
+
try log.boost(.{ foe_ident, .Speed, -1 });
|
|
2568
|
+
try log.boost(.{ foe_ident, .Speed, 1 });
|
|
2592
2569
|
}
|
|
2593
|
-
return
|
|
2570
|
+
return log.fail(.{ foe_ident, .None });
|
|
2594
2571
|
}
|
|
2595
|
-
|
|
2572
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2596
2573
|
const stat = unmodifiedStats(battle, foe).spe;
|
|
2597
2574
|
stats.spe = @max(1, stat * mod[0] / mod[1]);
|
|
2598
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2575
|
+
try log.boost(.{ foe_ident, .Speed, -1 });
|
|
2599
2576
|
assert(boosts.spe >= -6);
|
|
2600
2577
|
},
|
|
2601
2578
|
.SpecialDownChance => {
|
|
2602
2579
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2603
|
-
if (boosts.spc == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2580
|
+
if (boosts.spc == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2604
2581
|
boosts.spc = @intCast(@max(-6, @as(i8, boosts.spc) - 1));
|
|
2605
2582
|
if (stats.spc == 1) {
|
|
2606
2583
|
boosts.spc += 1;
|
|
2607
2584
|
if (showdown) {
|
|
2608
|
-
try log.boost(foe_ident, .SpecialAttack, -1);
|
|
2609
|
-
try log.boost(foe_ident, .SpecialAttack, 1);
|
|
2610
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2611
|
-
try log.boost(foe_ident, .SpecialDefense, 1);
|
|
2585
|
+
try log.boost(.{ foe_ident, .SpecialAttack, -1 });
|
|
2586
|
+
try log.boost(.{ foe_ident, .SpecialAttack, 1 });
|
|
2587
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2588
|
+
try log.boost(.{ foe_ident, .SpecialDefense, 1 });
|
|
2612
2589
|
}
|
|
2613
|
-
return
|
|
2590
|
+
return log.fail(.{ foe_ident, .None });
|
|
2614
2591
|
}
|
|
2615
|
-
|
|
2592
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2616
2593
|
const stat = unmodifiedStats(battle, foe).spc;
|
|
2617
2594
|
stats.spc = @max(1, stat * mod[0] / mod[1]);
|
|
2618
|
-
try log.boost(foe_ident, .SpecialAttack, -1);
|
|
2619
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2595
|
+
try log.boost(.{ foe_ident, .SpecialAttack, -1 });
|
|
2596
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2620
2597
|
},
|
|
2621
2598
|
.AccuracyDown1 => {
|
|
2622
2599
|
assert(boosts.accuracy >= -6 and boosts.accuracy <= 6);
|
|
2623
|
-
if (boosts.accuracy == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2600
|
+
if (boosts.accuracy == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2624
2601
|
boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
|
|
2625
|
-
try log.boost(foe_ident, .Accuracy, -1);
|
|
2602
|
+
try log.boost(.{ foe_ident, .Accuracy, -1 });
|
|
2626
2603
|
},
|
|
2627
2604
|
else => unreachable,
|
|
2628
2605
|
}
|
|
@@ -2646,7 +2623,7 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
2646
2623
|
}
|
|
2647
2624
|
}
|
|
2648
2625
|
|
|
2649
|
-
|
|
2626
|
+
fn isForced(active: anytype) bool {
|
|
2650
2627
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
2651
2628
|
active.volatiles.Thrashing or active.volatiles.Charging;
|
|
2652
2629
|
}
|
|
@@ -2662,24 +2639,24 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2662
2639
|
if (volatiles.disable_move != 0) {
|
|
2663
2640
|
volatiles.disable_move = 0;
|
|
2664
2641
|
volatiles.disable_duration = 0;
|
|
2665
|
-
try log.end(ident, .DisableSilent);
|
|
2642
|
+
try log.end(.{ ident, .DisableSilent });
|
|
2666
2643
|
}
|
|
2667
2644
|
if (volatiles.Confusion) {
|
|
2668
2645
|
// volatiles.confusion is left unchanged
|
|
2669
2646
|
volatiles.Confusion = false;
|
|
2670
|
-
try log.end(ident, .ConfusionSilent);
|
|
2647
|
+
try log.end(.{ ident, .ConfusionSilent });
|
|
2671
2648
|
}
|
|
2672
2649
|
if (volatiles.Mist) {
|
|
2673
2650
|
volatiles.Mist = false;
|
|
2674
|
-
try log.end(ident, .MistSilent);
|
|
2651
|
+
try log.end(.{ ident, .MistSilent });
|
|
2675
2652
|
}
|
|
2676
2653
|
if (volatiles.FocusEnergy) {
|
|
2677
2654
|
volatiles.FocusEnergy = false;
|
|
2678
|
-
try log.end(ident, .FocusEnergySilent);
|
|
2655
|
+
try log.end(.{ ident, .FocusEnergySilent });
|
|
2679
2656
|
}
|
|
2680
2657
|
if (volatiles.LeechSeed) {
|
|
2681
2658
|
volatiles.LeechSeed = false;
|
|
2682
|
-
try log.end(ident, .LeechSeedSilent);
|
|
2659
|
+
try log.end(.{ ident, .LeechSeedSilent });
|
|
2683
2660
|
}
|
|
2684
2661
|
if (!showdown and volatiles.Toxic) {
|
|
2685
2662
|
volatiles.Toxic = false;
|
|
@@ -2687,24 +2664,26 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2687
2664
|
}
|
|
2688
2665
|
if (volatiles.LightScreen) {
|
|
2689
2666
|
volatiles.LightScreen = false;
|
|
2690
|
-
try log.end(ident, .LightScreenSilent);
|
|
2667
|
+
try log.end(.{ ident, .LightScreenSilent });
|
|
2691
2668
|
}
|
|
2692
2669
|
if (volatiles.Reflect) {
|
|
2693
2670
|
volatiles.Reflect = false;
|
|
2694
|
-
try log.end(ident, .ReflectSilent);
|
|
2671
|
+
try log.end(.{ ident, .ReflectSilent });
|
|
2695
2672
|
}
|
|
2696
2673
|
if (showdown and volatiles.Toxic) {
|
|
2697
2674
|
volatiles.Toxic = false;
|
|
2698
2675
|
// Pokémon Showdown erroneously clears the toxic counter
|
|
2699
2676
|
volatiles.toxic = 0;
|
|
2700
|
-
try log.end(ident, .ToxicSilent);
|
|
2677
|
+
try log.end(.{ ident, .ToxicSilent });
|
|
2701
2678
|
}
|
|
2702
2679
|
}
|
|
2703
2680
|
|
|
2704
2681
|
pub const Rolls = struct {
|
|
2705
|
-
|
|
2682
|
+
fn speedTie(battle: anytype, options: anytype) !bool {
|
|
2706
2683
|
const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
|
|
2707
2684
|
player == .P1
|
|
2685
|
+
else if (@hasDecl(@TypeOf(battle.rng), "speedTie"))
|
|
2686
|
+
battle.rng.speedTie()
|
|
2708
2687
|
else if (showdown)
|
|
2709
2688
|
battle.rng.range(u8, 0, 2) == 0
|
|
2710
2689
|
else
|
|
@@ -2714,21 +2693,31 @@ pub const Rolls = struct {
|
|
|
2714
2693
|
return p1;
|
|
2715
2694
|
}
|
|
2716
2695
|
|
|
2717
|
-
|
|
2696
|
+
fn criticalHit(
|
|
2697
|
+
battle: anytype,
|
|
2698
|
+
player: Player,
|
|
2699
|
+
rate: u8,
|
|
2700
|
+
spurious: bool,
|
|
2701
|
+
options: anytype,
|
|
2702
|
+
) !bool {
|
|
2718
2703
|
const crit = if (options.calc.overridden(player, .critical_hit)) |val|
|
|
2719
2704
|
val == .true
|
|
2705
|
+
else if (@hasDecl(@TypeOf(battle.rng), "criticalHit"))
|
|
2706
|
+
battle.rng.criticalHit(player, rate)
|
|
2720
2707
|
else if (showdown)
|
|
2721
2708
|
battle.rng.chance(u8, rate, 256)
|
|
2722
2709
|
else
|
|
2723
2710
|
std.math.rotl(u8, battle.rng.next(), 3) < rate;
|
|
2724
2711
|
|
|
2725
|
-
try options.chance.criticalHit(player, crit, rate);
|
|
2712
|
+
if (!spurious) try options.chance.criticalHit(player, crit, rate);
|
|
2726
2713
|
return crit;
|
|
2727
2714
|
}
|
|
2728
2715
|
|
|
2729
|
-
|
|
2716
|
+
fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2730
2717
|
const roll = if (options.calc.overridden(player, .damage)) |val|
|
|
2731
2718
|
val
|
|
2719
|
+
else if (@hasDecl(@TypeOf(battle.rng), "damage"))
|
|
2720
|
+
battle.rng.damage(player)
|
|
2732
2721
|
else roll: {
|
|
2733
2722
|
if (showdown) break :roll battle.rng.range(u8, 217, 256);
|
|
2734
2723
|
while (true) {
|
|
@@ -2742,21 +2731,27 @@ pub const Rolls = struct {
|
|
|
2742
2731
|
return roll;
|
|
2743
2732
|
}
|
|
2744
2733
|
|
|
2745
|
-
|
|
2734
|
+
const uN = if (pkmn.options.miss) u8 else u9;
|
|
2735
|
+
fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) !bool {
|
|
2736
|
+
const acc: uN = if (pkmn.options.miss) accuracy else @as(uN, accuracy) + 1;
|
|
2746
2737
|
const ok = if (options.calc.overridden(player, .hit)) |val|
|
|
2747
2738
|
val == .true
|
|
2739
|
+
else if (@hasDecl(@TypeOf(battle.rng), "hit"))
|
|
2740
|
+
battle.rng.hit(player, acc)
|
|
2748
2741
|
else if (showdown)
|
|
2749
|
-
battle.rng.chance(
|
|
2742
|
+
battle.rng.chance(uN, acc, 256)
|
|
2750
2743
|
else
|
|
2751
|
-
battle.rng.next() <
|
|
2744
|
+
battle.rng.next() < acc;
|
|
2752
2745
|
|
|
2753
|
-
options.chance.hit(ok,
|
|
2746
|
+
try options.chance.hit(player, ok, acc);
|
|
2754
2747
|
return ok;
|
|
2755
2748
|
}
|
|
2756
2749
|
|
|
2757
|
-
|
|
2750
|
+
fn confused(battle: anytype, player: Player, options: anytype) !bool {
|
|
2758
2751
|
const cfz = if (options.calc.overridden(player, .confused)) |val|
|
|
2759
2752
|
val == .true
|
|
2753
|
+
else if (@hasDecl(@TypeOf(battle.rng), "confused"))
|
|
2754
|
+
battle.rng.confused(player)
|
|
2760
2755
|
else if (showdown)
|
|
2761
2756
|
!battle.rng.chance(u8, 128, 256)
|
|
2762
2757
|
else
|
|
@@ -2766,9 +2761,11 @@ pub const Rolls = struct {
|
|
|
2766
2761
|
return cfz;
|
|
2767
2762
|
}
|
|
2768
2763
|
|
|
2769
|
-
|
|
2764
|
+
fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
|
|
2770
2765
|
const par = if (options.calc.overridden(player, .paralyzed)) |val|
|
|
2771
2766
|
val == .true
|
|
2767
|
+
else if (@hasDecl(@TypeOf(battle.rng), "paralyzed"))
|
|
2768
|
+
battle.rng.paralyzed(player)
|
|
2772
2769
|
else if (showdown)
|
|
2773
2770
|
battle.rng.chance(u8, 63, 256)
|
|
2774
2771
|
else
|
|
@@ -2778,9 +2775,11 @@ pub const Rolls = struct {
|
|
|
2778
2775
|
return par;
|
|
2779
2776
|
}
|
|
2780
2777
|
|
|
2781
|
-
|
|
2778
|
+
fn confusionChance(battle: anytype, player: Player, options: anytype) !bool {
|
|
2782
2779
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2783
2780
|
val == .true
|
|
2781
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2782
|
+
battle.rng.secondaryChance(player, 25)
|
|
2784
2783
|
else if (showdown)
|
|
2785
2784
|
battle.rng.chance(u8, 25, 256)
|
|
2786
2785
|
else
|
|
@@ -2790,11 +2789,13 @@ pub const Rolls = struct {
|
|
|
2790
2789
|
return proc;
|
|
2791
2790
|
}
|
|
2792
2791
|
|
|
2793
|
-
|
|
2792
|
+
fn secondaryChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2794
2793
|
const rate: u8 = if (low) 26 else 77;
|
|
2795
2794
|
|
|
2796
2795
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2797
2796
|
val == .true
|
|
2797
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2798
|
+
battle.rng.secondaryChance(player, rate)
|
|
2798
2799
|
else if (showdown)
|
|
2799
2800
|
battle.rng.chance(u8, rate, 256)
|
|
2800
2801
|
else
|
|
@@ -2804,11 +2805,13 @@ pub const Rolls = struct {
|
|
|
2804
2805
|
return proc;
|
|
2805
2806
|
}
|
|
2806
2807
|
|
|
2807
|
-
|
|
2808
|
+
fn poisonChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2808
2809
|
const rate: u8 = if (low) 52 else 103;
|
|
2809
2810
|
|
|
2810
2811
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2811
2812
|
val == .true
|
|
2813
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2814
|
+
battle.rng.secondaryChance(player, rate)
|
|
2812
2815
|
else if (showdown)
|
|
2813
2816
|
battle.rng.chance(u8, rate, 256)
|
|
2814
2817
|
else
|
|
@@ -2818,9 +2821,11 @@ pub const Rolls = struct {
|
|
|
2818
2821
|
return proc;
|
|
2819
2822
|
}
|
|
2820
2823
|
|
|
2821
|
-
|
|
2824
|
+
fn unboost(battle: anytype, player: Player, options: anytype) !bool {
|
|
2822
2825
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2823
2826
|
val == .true
|
|
2827
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2828
|
+
battle.rng.secondaryChance(player, 85)
|
|
2824
2829
|
else if (showdown)
|
|
2825
2830
|
battle.rng.chance(u8, 85, 256)
|
|
2826
2831
|
else
|
|
@@ -2830,9 +2835,11 @@ pub const Rolls = struct {
|
|
|
2830
2835
|
return proc;
|
|
2831
2836
|
}
|
|
2832
2837
|
|
|
2833
|
-
|
|
2838
|
+
fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
2834
2839
|
const move: Move = if (options.calc.overridden(player, .metronome)) |val|
|
|
2835
2840
|
val
|
|
2841
|
+
else if (@hasDecl(@TypeOf(battle.rng), "metronome"))
|
|
2842
|
+
battle.rng.metronome(player)
|
|
2836
2843
|
else if (showdown)
|
|
2837
2844
|
Move.METRONOME[battle.rng.range(u8, 0, Move.METRONOME.len)]
|
|
2838
2845
|
else move: {
|
|
@@ -2848,9 +2855,11 @@ pub const Rolls = struct {
|
|
|
2848
2855
|
return move;
|
|
2849
2856
|
}
|
|
2850
2857
|
|
|
2851
|
-
|
|
2858
|
+
fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
|
|
2852
2859
|
const power = if (options.calc.overridden(player, .psywave)) |val|
|
|
2853
2860
|
val - 1
|
|
2861
|
+
else if (@hasDecl(@TypeOf(battle.rng), "psywave"))
|
|
2862
|
+
battle.rng.psywave(player)
|
|
2854
2863
|
else if (showdown)
|
|
2855
2864
|
battle.rng.range(u8, 0, max)
|
|
2856
2865
|
else power: {
|
|
@@ -2865,9 +2874,11 @@ pub const Rolls = struct {
|
|
|
2865
2874
|
return power;
|
|
2866
2875
|
}
|
|
2867
2876
|
|
|
2868
|
-
|
|
2877
|
+
fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2869
2878
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2870
2879
|
@intCast(val)
|
|
2880
|
+
else if (@hasDecl(@TypeOf(battle.rng), "sleepDuration"))
|
|
2881
|
+
battle.rng.sleepDuration(player)
|
|
2871
2882
|
else if (showdown)
|
|
2872
2883
|
battle.rng.range(u3, 1, 8)
|
|
2873
2884
|
else duration: {
|
|
@@ -2882,9 +2893,11 @@ pub const Rolls = struct {
|
|
|
2882
2893
|
return duration;
|
|
2883
2894
|
}
|
|
2884
2895
|
|
|
2885
|
-
|
|
2896
|
+
fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
|
|
2886
2897
|
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2887
2898
|
val
|
|
2899
|
+
else if (@hasDecl(@TypeOf(battle.rng), "disableDuration"))
|
|
2900
|
+
battle.rng.disableDuration(player)
|
|
2888
2901
|
else if (showdown)
|
|
2889
2902
|
battle.rng.range(u4, 1, 9)
|
|
2890
2903
|
else
|
|
@@ -2895,9 +2908,11 @@ pub const Rolls = struct {
|
|
|
2895
2908
|
return duration;
|
|
2896
2909
|
}
|
|
2897
2910
|
|
|
2898
|
-
|
|
2911
|
+
fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
|
|
2899
2912
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2900
2913
|
@intCast(val)
|
|
2914
|
+
else if (@hasDecl(@TypeOf(battle.rng), "confusionDuration"))
|
|
2915
|
+
battle.rng.confusionDuration(player, self)
|
|
2901
2916
|
else if (showdown)
|
|
2902
2917
|
battle.rng.range(u3, 2, 6)
|
|
2903
2918
|
else
|
|
@@ -2908,9 +2923,11 @@ pub const Rolls = struct {
|
|
|
2908
2923
|
return duration;
|
|
2909
2924
|
}
|
|
2910
2925
|
|
|
2911
|
-
|
|
2926
|
+
fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2912
2927
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2913
2928
|
@intCast(val)
|
|
2929
|
+
else if (@hasDecl(@TypeOf(battle.rng), "attackingDuration"))
|
|
2930
|
+
battle.rng.attackingDuration(player)
|
|
2914
2931
|
else if (showdown)
|
|
2915
2932
|
battle.rng.range(u3, 2, 4)
|
|
2916
2933
|
else
|
|
@@ -2923,7 +2940,7 @@ pub const Rolls = struct {
|
|
|
2923
2940
|
|
|
2924
2941
|
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
2925
2942
|
|
|
2926
|
-
|
|
2943
|
+
fn distribution(
|
|
2927
2944
|
battle: anytype,
|
|
2928
2945
|
comptime effect: Move.Effect,
|
|
2929
2946
|
player: Player,
|
|
@@ -2932,6 +2949,8 @@ pub const Rolls = struct {
|
|
|
2932
2949
|
const roll = if (effect == .Binding) .duration else .multi_hit;
|
|
2933
2950
|
const n: u3 = if (options.calc.overridden(player, roll)) |val|
|
|
2934
2951
|
@intCast(val)
|
|
2952
|
+
else if (@hasDecl(@TypeOf(battle.rng), "distribution"))
|
|
2953
|
+
battle.rng.distribution(player)
|
|
2935
2954
|
else if (showdown)
|
|
2936
2955
|
DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
|
|
2937
2956
|
else n: {
|
|
@@ -2964,7 +2983,9 @@ pub const Rolls = struct {
|
|
|
2964
2983
|
else
|
|
2965
2984
|
null;
|
|
2966
2985
|
|
|
2967
|
-
const slot: u4 = overridden orelse
|
|
2986
|
+
const slot: u4 = overridden orelse if (@hasDecl(@TypeOf(battle.rng), "moveSlot"))
|
|
2987
|
+
battle.rng.moveSlot(player, moves, check_pp)
|
|
2988
|
+
else slot: {
|
|
2968
2989
|
if (showdown) {
|
|
2969
2990
|
if (check_pp == 0) {
|
|
2970
2991
|
var i: usize = moves.len;
|
|
@@ -3044,11 +3065,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3044
3065
|
const side = battle.side(player);
|
|
3045
3066
|
const foe = battle.foe(player);
|
|
3046
3067
|
|
|
3047
|
-
|
|
3068
|
+
const active = &side.active;
|
|
3048
3069
|
const stored = side.stored();
|
|
3049
3070
|
|
|
3050
3071
|
// While players are not given any input options on the cartridge in these cases,
|
|
3051
|
-
// Pokémon Showdown instead produces a list with a single move that must be chosen
|
|
3072
|
+
// Pokémon Showdown instead produces a list with a single move that must be chosen
|
|
3052
3073
|
//
|
|
3053
3074
|
// Given that no input is allowed on the cartridge 'Pass' seems like it would be logical
|
|
3054
3075
|
// here when not in compatibility mode, but the engine needs to be able to differentiate
|
|
@@ -3056,7 +3077,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3056
3077
|
// passing due to being forced into using a move. Instead of introducing another option
|
|
3057
3078
|
// we simply repurpose Move with no move slot, even though pedantically this is not
|
|
3058
3079
|
// strictly correct as the player would not have been presented the option to move or
|
|
3059
|
-
// switch at all
|
|
3080
|
+
// switch at all
|
|
3060
3081
|
if (isForced(active)) {
|
|
3061
3082
|
out[n] = .{ .type = .Move, .data = @intFromBool(showdown) };
|
|
3062
3083
|
n += 1;
|
|
@@ -3075,7 +3096,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3075
3096
|
// On the cartridge, all of these happen after "FIGHT" (indicating you are not
|
|
3076
3097
|
// switching) but before you are allowed to select a move. Pokémon Showdown instead
|
|
3077
3098
|
// either disables all other moves in the case of limited or requires you to select a
|
|
3078
|
-
// move normally if sleeping/frozen/bound
|
|
3099
|
+
// move normally if sleeping/frozen/bound
|
|
3079
3100
|
if (!showdown and (limited or foe.active.volatiles.Binding or
|
|
3080
3101
|
Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP)))
|
|
3081
3102
|
{
|
|
@@ -3088,7 +3109,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3088
3109
|
// Pokémon Showdown handles Bide and Binding moves by checking if the move in question
|
|
3089
3110
|
// is present in the Pokémon's moveset (which means moves called via Metronome / Mirror
|
|
3090
3111
|
// Move will not result in forcing the subsequent use unless the user also had the
|
|
3091
|
-
// proc-ed move in their moveset) and disabling all other moves
|
|
3112
|
+
// proc-ed move in their moveset) and disabling all other moves
|
|
3092
3113
|
if (limited) {
|
|
3093
3114
|
assert(showdown);
|
|
3094
3115
|
assert(side.last_selected_move != .None);
|