@pkmn/engine 0.1.0-dev.89cc1c98 → 0.1.0-dev.8b192003
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 +31 -23
- package/src/bin/install-pkmn-engine +26 -18
- package/src/lib/common/data.zig +4 -4
- package/src/lib/common/debug.zig +2 -2
- package/src/lib/common/optional.zig +1 -1
- package/src/lib/common/options.zig +2 -0
- package/src/lib/common/protocol.zig +393 -396
- package/src/lib/common/rational.zig +109 -15
- package/src/lib/common/rng.zig +20 -11
- package/src/lib/gen1/calc.zig +478 -92
- package/src/lib/gen1/chance.zig +195 -428
- package/src/lib/gen1/data/species.zig +1 -1
- package/src/lib/gen1/data.zig +21 -21
- package/src/lib/gen1/helpers.zig +88 -534
- package/src/lib/gen1/mechanics.zig +377 -360
- package/src/lib/gen2/chance.zig +25 -457
- package/src/lib/gen2/data.zig +13 -11
- package/src/lib/gen2/generated.zig +27 -78
- package/src/lib/gen2/helpers.zig +37 -7
- package/src/lib/gen2/mechanics.zig +1022 -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
|
@@ -4,10 +4,12 @@ const pkmn = @import("../pkmn.zig");
|
|
|
4
4
|
|
|
5
5
|
const common = @import("../common/data.zig");
|
|
6
6
|
const DEBUG = @import("../common/debug.zig").print;
|
|
7
|
+
const optional = @import("../common/optional.zig");
|
|
7
8
|
const protocol = @import("../common/protocol.zig");
|
|
8
9
|
const rng = @import("../common/rng.zig");
|
|
9
10
|
|
|
10
11
|
const data = @import("data.zig");
|
|
12
|
+
const chance = @import("chance.zig");
|
|
11
13
|
|
|
12
14
|
const assert = std.debug.assert;
|
|
13
15
|
|
|
@@ -22,6 +24,7 @@ const Result = common.Result;
|
|
|
22
24
|
|
|
23
25
|
const Boost = protocol.Boost;
|
|
24
26
|
const Damage = protocol.Damage;
|
|
27
|
+
const Fail = protocol.Fail;
|
|
25
28
|
const Heal = protocol.Heal;
|
|
26
29
|
|
|
27
30
|
const Gen12 = rng.Gen12;
|
|
@@ -70,8 +73,8 @@ pub fn update(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Result
|
|
|
70
73
|
var p1 = battle.side(.P1);
|
|
71
74
|
var p2 = battle.side(.P2);
|
|
72
75
|
|
|
73
|
-
|
|
74
|
-
|
|
76
|
+
const r1 = showdown and p1.active.volatiles.Binding and c2.type == .Switch;
|
|
77
|
+
const r2 = showdown and p2.active.volatiles.Binding and c1.type == .Switch;
|
|
75
78
|
|
|
76
79
|
if (try turnOrder(battle, c1, c2, options) == .P1) {
|
|
77
80
|
if (try doTurn(battle, .P1, c1, r1, s1, .P2, c2, r2, s2, options)) |r| return r;
|
|
@@ -89,11 +92,11 @@ fn start(battle: anytype, options: anytype) !Result {
|
|
|
89
92
|
const p1 = battle.side(.P1);
|
|
90
93
|
const p2 = battle.side(.P2);
|
|
91
94
|
|
|
92
|
-
|
|
95
|
+
const p1_slot = findFirstAlive(p1);
|
|
93
96
|
assert(!showdown or p1_slot == 1);
|
|
94
97
|
if (p1_slot == 0) return if (findFirstAlive(p2) == 0) Result.Tie else Result.Lose;
|
|
95
98
|
|
|
96
|
-
|
|
99
|
+
const p2_slot = findFirstAlive(p2);
|
|
97
100
|
assert(!showdown or p2_slot == 1);
|
|
98
101
|
if (p2_slot == 0) return Result.Win;
|
|
99
102
|
|
|
@@ -165,7 +168,7 @@ fn selectMove(
|
|
|
165
168
|
// to satisfy the conditions of the Desync Clause Mod. However, because Binding is still
|
|
166
169
|
// set the selected move will not actually be used, it will just be reported as having
|
|
167
170
|
// been used (this differs from how Pokémon Showdown works, but its impossible to
|
|
168
|
-
// replicate the incorrect behavior with the correct mechanisms)
|
|
171
|
+
// replicate the incorrect behavior with the correct mechanisms)
|
|
169
172
|
saveMove(battle, player, choice);
|
|
170
173
|
} else {
|
|
171
174
|
assert(choice.data == 0);
|
|
@@ -255,14 +258,13 @@ fn switchIn(battle: anytype, player: Player, slot: u8, initial: bool, options: a
|
|
|
255
258
|
|
|
256
259
|
foe.active.volatiles.Binding = false;
|
|
257
260
|
|
|
258
|
-
try options.log.switched(battle.active(player), incoming);
|
|
259
|
-
options.chance.switched(player, side.order[0], out);
|
|
261
|
+
try options.log.switched(.{ battle.active(player), incoming });
|
|
260
262
|
|
|
261
263
|
if (showdown and incoming.status == Status.TOX) {
|
|
262
264
|
incoming.status = Status.init(.PSN);
|
|
263
265
|
// Technically, Pokémon Showdown adds these after *both* Pokémon have switched, but we'd
|
|
264
266
|
// rather not clutter up turnOrder just for this (incorrect) log message
|
|
265
|
-
try options.log.status(battle.active(player), incoming.status, .Silent);
|
|
267
|
+
try options.log.status(.{ battle.active(player), incoming.status, .Silent });
|
|
266
268
|
}
|
|
267
269
|
}
|
|
268
270
|
|
|
@@ -346,10 +348,10 @@ fn doTurn(
|
|
|
346
348
|
)) |r| return r;
|
|
347
349
|
if (!replace) {
|
|
348
350
|
if (player_choice.type != .Switch) {
|
|
349
|
-
if (try checkFaint(battle, foe_player, options)) |r| return r;
|
|
351
|
+
if (try checkFaint(battle, foe_player, true, options)) |r| return r;
|
|
350
352
|
}
|
|
351
353
|
if (residual) try handleResidual(battle, player, options);
|
|
352
|
-
if (try checkFaint(battle, player, options)) |r| return r;
|
|
354
|
+
if (try checkFaint(battle, player, false, options)) |r| return r;
|
|
353
355
|
} else if (foe_choice.type == .Pass) return null;
|
|
354
356
|
|
|
355
357
|
options.chance.clearPending();
|
|
@@ -368,10 +370,10 @@ fn doTurn(
|
|
|
368
370
|
)) |r| return r;
|
|
369
371
|
if (!replace) {
|
|
370
372
|
if (!calc and foe_choice.type != .Switch) {
|
|
371
|
-
if (try checkFaint(battle, player, options)) |r| return r;
|
|
373
|
+
if (try checkFaint(battle, player, true, options)) |r| return r;
|
|
372
374
|
}
|
|
373
375
|
if (residual) try handleResidual(battle, foe_player, options);
|
|
374
|
-
if (try checkFaint(battle, foe_player, options)) |r| return r;
|
|
376
|
+
if (try checkFaint(battle, foe_player, false, options)) |r| return r;
|
|
375
377
|
}
|
|
376
378
|
|
|
377
379
|
// Flinch is bugged on Pokémon Showdown because it gets implemented with a duration which causes
|
|
@@ -406,7 +408,7 @@ fn executeMove(
|
|
|
406
408
|
if (side.last_selected_move == .SKIP_TURN) {
|
|
407
409
|
assert(!showdown);
|
|
408
410
|
if (battle.foe(player).active.volatiles.Binding) {
|
|
409
|
-
try options.log.cant(battle.active(player), .Bound);
|
|
411
|
+
try options.log.cant(.{ battle.active(player), .Bound });
|
|
410
412
|
}
|
|
411
413
|
return null;
|
|
412
414
|
}
|
|
@@ -430,7 +432,7 @@ fn executeMove(
|
|
|
430
432
|
Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP);
|
|
431
433
|
// If it wasn't Hyper Beam or the continuation of a move effect then we must have just
|
|
432
434
|
// 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
|
|
435
|
+
// index 1 and the current Pokémon has the same move in its first slot
|
|
434
436
|
if (!auto) {
|
|
435
437
|
// side.active.moves(slot) is safe to check even though the slot in question might not
|
|
436
438
|
// technically be from this Pokémon because it must be exactly 1 to not desync and
|
|
@@ -484,35 +486,29 @@ fn beforeMove(
|
|
|
484
486
|
if (Status.is(stored.status, .SLP)) {
|
|
485
487
|
const before = stored.status;
|
|
486
488
|
// Even if the EXT bit is set this will still correctly modify the sleep duration
|
|
487
|
-
|
|
488
|
-
if (!extend) stored.status = 0;
|
|
489
|
-
} else {
|
|
490
|
-
stored.status -= 1;
|
|
491
|
-
}
|
|
489
|
+
stored.status -= 1;
|
|
492
490
|
|
|
493
491
|
const duration = Status.duration(stored.status);
|
|
494
|
-
|
|
495
|
-
try options.chance.sleep(player, duration);
|
|
496
|
-
}
|
|
492
|
+
options.chance.durations(.sleep, player, observation(duration));
|
|
497
493
|
|
|
498
494
|
if (duration == 0) {
|
|
499
|
-
try log.curestatus(ident, before, .Message);
|
|
495
|
+
try log.curestatus(.{ ident, before, .Message });
|
|
500
496
|
stored.status = 0; // clears EXT if present
|
|
501
497
|
} else {
|
|
502
|
-
try log.cant(ident, .Sleep);
|
|
498
|
+
try log.cant(.{ ident, .Sleep });
|
|
503
499
|
}
|
|
504
500
|
side.last_used_move = .None;
|
|
505
501
|
return .done;
|
|
506
502
|
}
|
|
507
503
|
|
|
508
504
|
if (Status.is(stored.status, .FRZ)) {
|
|
509
|
-
try log.cant(ident, .Freeze);
|
|
505
|
+
try log.cant(.{ ident, .Freeze });
|
|
510
506
|
side.last_used_move = .None;
|
|
511
507
|
return .done;
|
|
512
508
|
}
|
|
513
509
|
|
|
514
510
|
if (skip or foe.active.volatiles.Binding) {
|
|
515
|
-
try log.cant(ident, .Bound);
|
|
511
|
+
try log.cant(.{ ident, .Bound });
|
|
516
512
|
return .done;
|
|
517
513
|
}
|
|
518
514
|
|
|
@@ -520,27 +516,23 @@ fn beforeMove(
|
|
|
520
516
|
// Pokémon Showdown doesn't clear Flinch until its imaginary "residual" phase, meaning
|
|
521
517
|
// Pokémon can sometimes flinch multiple times from the same original hit
|
|
522
518
|
if (!showdown) volatiles.Flinch = false;
|
|
523
|
-
try log.cant(ident, .Flinch);
|
|
519
|
+
try log.cant(.{ ident, .Flinch });
|
|
524
520
|
return .done;
|
|
525
521
|
}
|
|
526
522
|
|
|
527
523
|
if (volatiles.Recharging) {
|
|
528
524
|
volatiles.Recharging = false;
|
|
529
|
-
try log.cant(ident, .Recharge);
|
|
525
|
+
try log.cant(.{ ident, .Recharge });
|
|
530
526
|
return .done;
|
|
531
527
|
}
|
|
532
528
|
|
|
533
529
|
if (volatiles.disable_duration > 0) {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
} else {
|
|
537
|
-
volatiles.disable_duration -= 1;
|
|
538
|
-
}
|
|
539
|
-
try options.chance.disable(player, volatiles.disable_duration);
|
|
530
|
+
volatiles.disable_duration -= 1;
|
|
531
|
+
options.chance.durations(.disable, player, observation(volatiles.disable_duration));
|
|
540
532
|
|
|
541
533
|
if (volatiles.disable_duration == 0) {
|
|
542
534
|
volatiles.disable_move = 0;
|
|
543
|
-
try log.end(ident, .Disable);
|
|
535
|
+
try log.end(.{ ident, .Disable });
|
|
544
536
|
}
|
|
545
537
|
}
|
|
546
538
|
// Pokémon Showdown's disable condition has a single onBeforeMove handler
|
|
@@ -555,19 +547,14 @@ fn beforeMove(
|
|
|
555
547
|
|
|
556
548
|
if (volatiles.Confusion) {
|
|
557
549
|
assert(volatiles.confusion > 0);
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
if (!extend) volatiles.confusion = 0;
|
|
561
|
-
} else {
|
|
562
|
-
volatiles.confusion -= 1;
|
|
563
|
-
}
|
|
564
|
-
try options.chance.confusion(player, volatiles.confusion);
|
|
550
|
+
volatiles.confusion -= 1;
|
|
551
|
+
options.chance.durations(.confusion, player, observation(volatiles.confusion));
|
|
565
552
|
|
|
566
553
|
if (volatiles.confusion == 0) {
|
|
567
554
|
volatiles.Confusion = false;
|
|
568
|
-
try log.end(ident, .Confusion);
|
|
555
|
+
try log.end(.{ ident, .Confusion });
|
|
569
556
|
} else {
|
|
570
|
-
try log.activate(ident, .Confusion);
|
|
557
|
+
try log.activate(.{ ident, .Confusion });
|
|
571
558
|
|
|
572
559
|
if (try Rolls.confused(battle, player, options)) {
|
|
573
560
|
assert(!volatiles.MultiHit);
|
|
@@ -579,7 +566,10 @@ fn beforeMove(
|
|
|
579
566
|
volatiles.Charging = false;
|
|
580
567
|
volatiles.Binding = false;
|
|
581
568
|
volatiles.Invulnerable = false;
|
|
582
|
-
|
|
569
|
+
|
|
570
|
+
options.chance.durations(.bide, player, .None);
|
|
571
|
+
options.chance.durations(.thrashing, player, .None);
|
|
572
|
+
options.chance.durations(.binding, player, .None);
|
|
583
573
|
{
|
|
584
574
|
// This feels (and is) disgusting but the cartridge literally just overwrites
|
|
585
575
|
// the opponent's defense with the user's defense and resets it after. As a
|
|
@@ -616,9 +606,13 @@ fn beforeMove(
|
|
|
616
606
|
volatiles.Thrashing = false;
|
|
617
607
|
volatiles.Charging = false;
|
|
618
608
|
volatiles.Binding = false;
|
|
619
|
-
|
|
609
|
+
|
|
610
|
+
options.chance.durations(.bide, player, .None);
|
|
611
|
+
options.chance.durations(.thrashing, player, .None);
|
|
612
|
+
options.chance.durations(.binding, player, .None);
|
|
613
|
+
|
|
620
614
|
// GLITCH: Invulnerable is not cleared, resulting in permanent Fly/Dig invulnerability
|
|
621
|
-
try log.cant(ident, .Paralysis);
|
|
615
|
+
try log.cant(.{ ident, .Paralysis });
|
|
622
616
|
return .done;
|
|
623
617
|
}
|
|
624
618
|
|
|
@@ -634,28 +628,22 @@ fn beforeMove(
|
|
|
634
628
|
volatiles.state +%= battle.last_damage;
|
|
635
629
|
}
|
|
636
630
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
if (options.calc.modify(player, .attacking)) |extend| {
|
|
640
|
-
if (!extend) volatiles.attacks = 0;
|
|
641
|
-
} else {
|
|
642
|
-
volatiles.attacks -= 1;
|
|
643
|
-
}
|
|
644
|
-
try options.chance.attacking(player, volatiles.attacks);
|
|
631
|
+
volatiles.attacks -= 1;
|
|
632
|
+
options.chance.durations(.bide, player, observation(volatiles.attacks));
|
|
645
633
|
|
|
646
634
|
if (volatiles.attacks != 0) {
|
|
647
|
-
try log.activate(ident, .Bide);
|
|
635
|
+
try log.activate(.{ ident, .Bide });
|
|
648
636
|
return .done;
|
|
649
637
|
}
|
|
650
638
|
|
|
651
639
|
volatiles.Bide = false;
|
|
652
|
-
try log.end(ident, .Bide);
|
|
640
|
+
try log.end(.{ ident, .Bide });
|
|
653
641
|
|
|
654
642
|
battle.last_damage = volatiles.state *% 2;
|
|
655
643
|
volatiles.state = 0;
|
|
656
644
|
|
|
657
645
|
if (battle.last_damage == 0) {
|
|
658
|
-
try log.fail(ident, .None);
|
|
646
|
+
try log.fail(.{ ident, .None });
|
|
659
647
|
return .done;
|
|
660
648
|
}
|
|
661
649
|
|
|
@@ -675,41 +663,39 @@ fn beforeMove(
|
|
|
675
663
|
}
|
|
676
664
|
|
|
677
665
|
if (volatiles.Thrashing) {
|
|
678
|
-
|
|
666
|
+
try log.move(.{ ident, side.last_selected_move, battle.active(player.foe()) });
|
|
679
667
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
668
|
+
volatiles.attacks -= 1;
|
|
669
|
+
options.chance.durations(
|
|
670
|
+
.thrashing,
|
|
671
|
+
player,
|
|
672
|
+
if (volatiles.attacks <= 1) .ended else .continuing,
|
|
673
|
+
);
|
|
686
674
|
|
|
687
|
-
if (
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
//
|
|
693
|
-
//
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
}
|
|
675
|
+
if (volatiles.attacks == 0) {
|
|
676
|
+
volatiles.Thrashing = false;
|
|
677
|
+
volatiles.Confusion = true;
|
|
678
|
+
volatiles.confusion = Rolls.confusionDuration(battle, player, options);
|
|
679
|
+
|
|
680
|
+
// On Pokémon Showdown this could leak information to the opponent that thrashing has
|
|
681
|
+
// ended (the engine's protocol is always from the omniscient perspective and clients
|
|
682
|
+
// can be expected to recognize this pattern and pass the information only to the
|
|
683
|
+
// correct player)
|
|
684
|
+
try log.start(.{ battle.active(player), .ConfusionSilent });
|
|
698
685
|
}
|
|
686
|
+
|
|
687
|
+
// This shouldn't actually set last_used_move, but Pokémon Showdown sets last
|
|
688
|
+
// used in useMove and doesn't have the notion of skipping canMove semantics
|
|
689
|
+
if (showdown) side.last_used_move = side.last_selected_move;
|
|
690
|
+
|
|
699
691
|
return .skip_can;
|
|
700
692
|
}
|
|
701
693
|
|
|
702
694
|
if (volatiles.Binding) {
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
if (options.calc.modify(player, .binding)) |extend| {
|
|
706
|
-
if (!extend) volatiles.attacks = 0;
|
|
707
|
-
} else {
|
|
708
|
-
volatiles.attacks -= 1;
|
|
709
|
-
}
|
|
710
|
-
try options.chance.binding(player, volatiles.attacks);
|
|
695
|
+
volatiles.attacks -= 1;
|
|
696
|
+
options.chance.durations(.binding, player, observation(volatiles.attacks));
|
|
711
697
|
|
|
712
|
-
try log.move(
|
|
698
|
+
try log.move(.{ ident, side.last_selected_move, battle.active(player.foe()) });
|
|
713
699
|
if (showdown or battle.last_damage != 0) {
|
|
714
700
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
715
701
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -737,22 +723,21 @@ fn canMove(
|
|
|
737
723
|
const player_ident = battle.active(player);
|
|
738
724
|
const move = Move.get(side.last_selected_move);
|
|
739
725
|
|
|
740
|
-
var skip = skip_pp;
|
|
741
726
|
if (side.active.volatiles.Charging) {
|
|
742
727
|
side.active.volatiles.Charging = false;
|
|
743
728
|
side.active.volatiles.Invulnerable = false;
|
|
744
729
|
} else if (move.effect == .Charge) {
|
|
745
|
-
try options.log.
|
|
730
|
+
try options.log.move(.{ player_ident, side.last_selected_move, ID{}, from });
|
|
746
731
|
setCounterable(battle, player, side, move);
|
|
747
732
|
try Effects.charge(battle, player, options);
|
|
748
733
|
return false;
|
|
749
734
|
}
|
|
750
735
|
|
|
751
736
|
side.last_used_move = side.last_selected_move;
|
|
752
|
-
if (!
|
|
737
|
+
if (!skip_pp) decrementPP(side, mslot, auto);
|
|
753
738
|
|
|
754
739
|
const target = if (move.target == .Self) player else player.foe();
|
|
755
|
-
try options.log.
|
|
740
|
+
try options.log.move(.{ player_ident, side.last_selected_move, battle.active(target), from });
|
|
756
741
|
setCounterable(battle, player, side, move);
|
|
757
742
|
|
|
758
743
|
if (move.effect.onBegin()) {
|
|
@@ -858,7 +843,7 @@ fn doMove(
|
|
|
858
843
|
break :miss true;
|
|
859
844
|
}
|
|
860
845
|
break :miss (move.target != .Self and
|
|
861
|
-
!moveHit(battle, player, move, &immune, &mist, options));
|
|
846
|
+
!try moveHit(battle, player, move, &immune, &mist, options));
|
|
862
847
|
};
|
|
863
848
|
assert(!immune or miss or (showdown and move.effect == .Binding));
|
|
864
849
|
|
|
@@ -901,11 +886,11 @@ fn doMove(
|
|
|
901
886
|
}
|
|
902
887
|
}
|
|
903
888
|
|
|
904
|
-
|
|
889
|
+
const zero = battle.last_damage == 0;
|
|
905
890
|
miss = if (showdown or skip)
|
|
906
891
|
miss
|
|
907
892
|
else
|
|
908
|
-
(!moveHit(battle, player, move, &immune, &mist, options) or zero);
|
|
893
|
+
(!try moveHit(battle, player, move, &immune, &mist, options) or zero);
|
|
909
894
|
|
|
910
895
|
assert(showdown or miss or battle.last_damage > 0 or skip);
|
|
911
896
|
assert((!showdown and miss) or !(ohko and immune));
|
|
@@ -929,16 +914,16 @@ fn doMove(
|
|
|
929
914
|
ohko = (!showdown or (!immune and !invulnerable)) and
|
|
930
915
|
move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe;
|
|
931
916
|
if (ohko) {
|
|
932
|
-
try log.immune(foe_ident, .OHKO);
|
|
917
|
+
try log.immune(.{ foe_ident, .OHKO });
|
|
933
918
|
} else if (immune and !invulnerable and (!showdown or move.effect != .Binding)) {
|
|
934
|
-
try log.immune(foe_ident, .None);
|
|
919
|
+
try log.immune(.{ foe_ident, .None });
|
|
935
920
|
} else if (mist) {
|
|
936
|
-
if (!foe.active.volatiles.Substitute) try log.activate(foe_ident, .Mist);
|
|
937
|
-
try log.fail(foe_ident, .None);
|
|
921
|
+
if (!foe.active.volatiles.Substitute) try log.activate(.{ foe_ident, .Mist });
|
|
922
|
+
try log.fail(.{ foe_ident, .None });
|
|
938
923
|
} else {
|
|
939
|
-
if (showdown
|
|
940
|
-
try log.lastmiss();
|
|
941
|
-
try log.miss(battle.active(player));
|
|
924
|
+
if (!showdown and !zero) try options.chance.commit(player, .miss);
|
|
925
|
+
try log.lastmiss(.{});
|
|
926
|
+
try log.miss(.{battle.active(player)});
|
|
942
927
|
}
|
|
943
928
|
if (move.effect == .JumpKick) {
|
|
944
929
|
// Recoil is supposed to be damage/8 but damage will always be 0 here
|
|
@@ -951,17 +936,15 @@ fn doMove(
|
|
|
951
936
|
try buildRage(battle, player.foe(), options);
|
|
952
937
|
} else if (showdown and move.effect == .Disable) {
|
|
953
938
|
try buildRage(battle, player.foe(), options);
|
|
954
|
-
} else if (!showdown and immune and move.effect == .Binding) {
|
|
955
|
-
try options.chance.commit(player, .binding);
|
|
956
939
|
}
|
|
957
940
|
return null;
|
|
958
|
-
} else {
|
|
941
|
+
} else if (!showdown) {
|
|
959
942
|
try options.chance.commit(player, .hit);
|
|
960
943
|
}
|
|
961
944
|
|
|
962
945
|
// On the cartridge MultiHit doesn't get set up until after damage has been applied for the
|
|
963
946
|
// first time but its more convenient and efficient to set it up here (Pokémon Showdown sets
|
|
964
|
-
// it up above before damage calculation)
|
|
947
|
+
// it up above before damage calculation)
|
|
965
948
|
if (!showdown and move.effect.isMulti()) {
|
|
966
949
|
try Effects.multiHit(battle, player, move, options);
|
|
967
950
|
hits = side.active.volatiles.attacks;
|
|
@@ -974,15 +957,15 @@ fn doMove(
|
|
|
974
957
|
var hit: u4 = 0;
|
|
975
958
|
while (hit < hits) {
|
|
976
959
|
if (hit == 0) {
|
|
977
|
-
if (crit) try log.crit(battle.active(player.foe()));
|
|
960
|
+
if (crit) try log.crit(.{battle.active(player.foe())});
|
|
978
961
|
if (effectiveness > Effectiveness.neutral) {
|
|
979
|
-
try log.supereffective(battle.active(player.foe()));
|
|
962
|
+
try log.supereffective(.{battle.active(player.foe())});
|
|
980
963
|
} else if (effectiveness < Effectiveness.neutral) {
|
|
981
|
-
try log.resisted(battle.active(player.foe()));
|
|
964
|
+
try log.resisted(.{battle.active(player.foe())});
|
|
982
965
|
}
|
|
983
966
|
}
|
|
984
967
|
if (!skip) nullified = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
985
|
-
if (hit == 0 and ohko) try log.ohko();
|
|
968
|
+
if (hit == 0 and ohko) try log.ohko(.{});
|
|
986
969
|
hit += 1;
|
|
987
970
|
if (foe.stored().hp == 0) break;
|
|
988
971
|
if (!late and (!sub or move.effect == .Explode)) {
|
|
@@ -999,7 +982,7 @@ fn doMove(
|
|
|
999
982
|
if (showdown and move.effect == .Twineedle and !nullified and foe.stored().hp > 0) {
|
|
1000
983
|
try Effects.poison(battle, player, Move.get(.PoisonSting), options);
|
|
1001
984
|
}
|
|
1002
|
-
try log.hitcount(battle.active(player.foe()), hit);
|
|
985
|
+
try log.hitcount(.{ battle.active(player.foe()), hit });
|
|
1003
986
|
} else if (showdown) {
|
|
1004
987
|
// This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
|
|
1005
988
|
if (move.effect == .Binding) {
|
|
@@ -1009,9 +992,9 @@ fn doMove(
|
|
|
1009
992
|
assert(foe.stored().hp > 0);
|
|
1010
993
|
// Pokémon Showdown logs |-damage| here instead of |-immune| because logic...
|
|
1011
994
|
if (sub) {
|
|
1012
|
-
try log.activate(battle.active(player.foe()), .Substitute);
|
|
995
|
+
try log.activate(.{ battle.active(player.foe()), .Substitute });
|
|
1013
996
|
} else {
|
|
1014
|
-
try log.damage(battle.active(player.foe()), foe.stored(), .None);
|
|
997
|
+
try log.damage(.{ battle.active(player.foe()), foe.stored(), .None });
|
|
1015
998
|
try buildRage(battle, player.foe(), options);
|
|
1016
999
|
}
|
|
1017
1000
|
return null;
|
|
@@ -1020,12 +1003,12 @@ fn doMove(
|
|
|
1020
1003
|
}
|
|
1021
1004
|
|
|
1022
1005
|
// 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
|
|
1006
|
+
// if an effect was intended to "always happen" it will still get skipped
|
|
1024
1007
|
if (nullified) return null;
|
|
1025
1008
|
|
|
1026
1009
|
// On the cartridge, "always happen" effect handlers are called in the applyDamage loop above,
|
|
1027
1010
|
// 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
|
|
1011
|
+
// setup before the loop means we can avoid having to waste time doing no-op handler searches
|
|
1029
1012
|
if (move.effect.alwaysHappens()) try alwaysHappens(battle, player, move, residual, options);
|
|
1030
1013
|
|
|
1031
1014
|
if (foe.stored().hp == 0) return null;
|
|
@@ -1040,7 +1023,7 @@ fn doMove(
|
|
|
1040
1023
|
// below). For Twineedle we change the data to that of one with PoisonChance1 given its
|
|
1041
1024
|
// MultiHit behavior is complete after the loop above, though Pokémon Showdown handles the
|
|
1042
1025
|
// Twineedle secondary effect in the MultiHit cleanup block above because it incorrectly
|
|
1043
|
-
// puts the |-status| message before |-hitcount| instead of after
|
|
1026
|
+
// puts the |-status| message before |-hitcount| instead of after
|
|
1044
1027
|
if (move.effect == .Twineedle) {
|
|
1045
1028
|
if (!showdown) try Effects.poison(battle, player, Move.get(.PoisonSting), options);
|
|
1046
1029
|
} else if (move.effect == .Disable) {
|
|
@@ -1059,12 +1042,14 @@ fn checkCriticalHit(battle: anytype, player: Player, move: Move.Data, options: a
|
|
|
1059
1042
|
const side = battle.side(player);
|
|
1060
1043
|
|
|
1061
1044
|
// Base speed is used for the critical hit calculation, even when Transform-ed
|
|
1062
|
-
var rate =
|
|
1045
|
+
var rate: u16 = Species.chance(side.stored().species);
|
|
1063
1046
|
// GLITCH: Focus Energy reduces critical hit rate instead of increasing it
|
|
1064
1047
|
rate = if (side.active.volatiles.FocusEnergy) rate / 2 else @min(rate * 2, 255);
|
|
1065
1048
|
rate = if (move.effect == .HighCritical) @min(rate * 4, 255) else rate / 2;
|
|
1066
1049
|
|
|
1067
|
-
|
|
1050
|
+
const spurious =
|
|
1051
|
+
!showdown and ((move.bp == 0 and move.effect != .OHKO) or move.target == .Depends);
|
|
1052
|
+
return Rolls.criticalHit(battle, player, @intCast(rate), spurious, options);
|
|
1068
1053
|
}
|
|
1069
1054
|
|
|
1070
1055
|
fn calcDamage(
|
|
@@ -1116,9 +1101,9 @@ fn calcDamage(
|
|
|
1116
1101
|
def = @max((def / 4) & 255, if (showdown) 1 else 0);
|
|
1117
1102
|
}
|
|
1118
1103
|
|
|
1119
|
-
const lvl =
|
|
1104
|
+
const lvl: u32 = side.stored().level * @as(u2, if (crit) 2 else 1);
|
|
1120
1105
|
|
|
1121
|
-
def =
|
|
1106
|
+
def = if (move.effect == .Explode) @max(def / 2, 1) else def;
|
|
1122
1107
|
|
|
1123
1108
|
if (def == 0) return false;
|
|
1124
1109
|
|
|
@@ -1179,7 +1164,6 @@ fn specialDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1179
1164
|
const foe = battle.foe(player);
|
|
1180
1165
|
|
|
1181
1166
|
if (!try checkHit(battle, player, move, options)) return null;
|
|
1182
|
-
try options.chance.commit(player, .hit);
|
|
1183
1167
|
|
|
1184
1168
|
battle.last_damage = switch (side.last_selected_move) {
|
|
1185
1169
|
.SuperFang => @max(foe.stored().hp / 2, 1),
|
|
@@ -1209,7 +1193,7 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1209
1193
|
const foe = battle.foe(player);
|
|
1210
1194
|
|
|
1211
1195
|
if (battle.last_damage == 0) {
|
|
1212
|
-
try options.log.fail(battle.active(player), .None);
|
|
1196
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1213
1197
|
return null;
|
|
1214
1198
|
}
|
|
1215
1199
|
|
|
@@ -1227,22 +1211,21 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1227
1211
|
foe_last_selected_move.type == .Fighting);
|
|
1228
1212
|
|
|
1229
1213
|
if (!used and !selected) {
|
|
1230
|
-
try options.log.fail(battle.active(player), .None);
|
|
1214
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1231
1215
|
return null;
|
|
1232
1216
|
}
|
|
1233
1217
|
|
|
1234
1218
|
if (!used or !selected) {
|
|
1235
1219
|
// GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
|
|
1236
1220
|
if (!showdown) return Result.Error;
|
|
1237
|
-
try options.log.fail(battle.active(player), .None);
|
|
1221
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1238
1222
|
return null;
|
|
1239
1223
|
}
|
|
1240
1224
|
|
|
1241
1225
|
battle.last_damage *|= 2;
|
|
1242
1226
|
|
|
1243
|
-
// Pokémon Showdown calls
|
|
1227
|
+
// Pokémon Showdown calls moveHit before Counter
|
|
1244
1228
|
if (!showdown and !try checkHit(battle, player, move, options)) return null;
|
|
1245
|
-
try options.chance.commit(player, .hit);
|
|
1246
1229
|
|
|
1247
1230
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
1248
1231
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -1270,20 +1253,20 @@ fn applyDamage(
|
|
|
1270
1253
|
subbed.active.volatiles.substitute = 0;
|
|
1271
1254
|
subbed.active.volatiles.Substitute = false;
|
|
1272
1255
|
// 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(
|
|
1256
|
+
try options.log.end(.{ battle.active(sub_player), .Substitute });
|
|
1257
|
+
options.calc.capped(sub_player);
|
|
1275
1258
|
return true;
|
|
1276
1259
|
} else {
|
|
1277
1260
|
// Safe to truncate since less than subbed.volatiles.substitute which is a u8
|
|
1278
1261
|
subbed.active.volatiles.substitute -= @intCast(battle.last_damage);
|
|
1279
|
-
try options.log.activate(battle.active(sub_player), .Substitute);
|
|
1262
|
+
try options.log.activate(.{ battle.active(sub_player), .Substitute });
|
|
1280
1263
|
return false;
|
|
1281
1264
|
}
|
|
1282
1265
|
}
|
|
1283
1266
|
|
|
1284
1267
|
if (battle.last_damage > target.stored().hp) battle.last_damage = target.stored().hp;
|
|
1285
1268
|
target.stored().hp -= battle.last_damage;
|
|
1286
|
-
try options.log.damage(battle.active(target_player), target.stored(), reason);
|
|
1269
|
+
try options.log.damage(.{ battle.active(target_player), target.stored(), reason });
|
|
1287
1270
|
return false;
|
|
1288
1271
|
}
|
|
1289
1272
|
|
|
@@ -1302,7 +1285,7 @@ fn mirrorMove(
|
|
|
1302
1285
|
side.last_selected_move = foe.last_used_move;
|
|
1303
1286
|
|
|
1304
1287
|
if (foe.last_used_move == .None or foe.last_used_move == .MirrorMove) {
|
|
1305
|
-
try options.log.fail(battle.active(player), .None);
|
|
1288
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1306
1289
|
return null;
|
|
1307
1290
|
} else if (!showdown or foe.last_used_move != .Struggle) {
|
|
1308
1291
|
incrementPP(side, mslot);
|
|
@@ -1338,20 +1321,23 @@ fn checkHit(battle: anytype, player: Player, move: Move.Data, options: anytype)
|
|
|
1338
1321
|
var immune = false;
|
|
1339
1322
|
var mist = false;
|
|
1340
1323
|
|
|
1341
|
-
if (moveHit(battle, player, move, &immune, &mist, options))
|
|
1324
|
+
if (try moveHit(battle, player, move, &immune, &mist, options)) {
|
|
1325
|
+
if (!showdown) try options.chance.commit(player, .hit);
|
|
1326
|
+
return true;
|
|
1327
|
+
}
|
|
1342
1328
|
|
|
1343
1329
|
assert(!immune);
|
|
1344
1330
|
if (mist) {
|
|
1345
1331
|
assert(!showdown);
|
|
1346
1332
|
const foe_ident = battle.active(player.foe());
|
|
1347
|
-
try options.log.activate(foe_ident, .Mist);
|
|
1348
|
-
try options.log.fail(foe_ident, .None);
|
|
1333
|
+
try options.log.activate(.{ foe_ident, .Mist });
|
|
1334
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1349
1335
|
} else {
|
|
1350
|
-
try options.
|
|
1351
|
-
try options.log.
|
|
1352
|
-
try options.log.miss(battle.active(player));
|
|
1336
|
+
try options.log.lastmiss(.{});
|
|
1337
|
+
try options.log.miss(.{battle.active(player)});
|
|
1353
1338
|
}
|
|
1354
1339
|
|
|
1340
|
+
if (!showdown) try options.chance.commit(player, .miss);
|
|
1355
1341
|
return false;
|
|
1356
1342
|
}
|
|
1357
1343
|
|
|
@@ -1362,7 +1348,7 @@ fn moveHit(
|
|
|
1362
1348
|
immune: *bool,
|
|
1363
1349
|
mist: *bool,
|
|
1364
1350
|
options: anytype,
|
|
1365
|
-
) bool {
|
|
1351
|
+
) !bool {
|
|
1366
1352
|
var side = battle.side(player);
|
|
1367
1353
|
const foe = battle.foe(player);
|
|
1368
1354
|
|
|
@@ -1401,7 +1387,7 @@ fn moveHit(
|
|
|
1401
1387
|
const overwritten = overwrite and state > 0;
|
|
1402
1388
|
assert(!overwritten or (0 < state and state <= 255 and !side.active.volatiles.Bide));
|
|
1403
1389
|
|
|
1404
|
-
var accuracy = if (overwritten) state else
|
|
1390
|
+
var accuracy: u16 = if (overwritten) state else move.accuracy;
|
|
1405
1391
|
var boost = BOOSTS[@as(u4, @intCast(@as(i8, side.active.boosts.accuracy) + 6))];
|
|
1406
1392
|
accuracy = accuracy * boost[0] / boost[1];
|
|
1407
1393
|
boost = BOOSTS[@as(u4, @intCast(@as(i8, -foe.active.boosts.evasion) + 6))];
|
|
@@ -1413,7 +1399,7 @@ fn moveHit(
|
|
|
1413
1399
|
if (overwrite) side.active.volatiles.state = accuracy;
|
|
1414
1400
|
|
|
1415
1401
|
// GLITCH: max accuracy is 255 so 1/256 chance of miss
|
|
1416
|
-
break :miss !Rolls.hit(battle, player, @intCast(accuracy), options);
|
|
1402
|
+
break :miss !try Rolls.hit(battle, player, @intCast(accuracy), options);
|
|
1417
1403
|
};
|
|
1418
1404
|
|
|
1419
1405
|
// Pokémon Showdown reports miss instead of fail for moves blocked by Mist that 1/256 miss
|
|
@@ -1431,6 +1417,7 @@ fn moveHit(
|
|
|
1431
1417
|
fn checkFaint(
|
|
1432
1418
|
battle: anytype,
|
|
1433
1419
|
player: Player,
|
|
1420
|
+
cap: bool,
|
|
1434
1421
|
options: anytype,
|
|
1435
1422
|
) @TypeOf(options.log).Error!?Result {
|
|
1436
1423
|
const side = battle.side(player);
|
|
@@ -1443,20 +1430,20 @@ fn checkFaint(
|
|
|
1443
1430
|
const foe_out = foe_fainted and findFirstAlive(foe) == 0;
|
|
1444
1431
|
const more = player_out or foe_out;
|
|
1445
1432
|
|
|
1446
|
-
if (try faint(battle, player, !(more or foe_fainted), options)) |r| return r;
|
|
1447
|
-
if (foe_fainted) if (try faint(battle, player.foe(), !more, options)) |r| return r;
|
|
1433
|
+
if (try faint(battle, player, !(more or foe_fainted), cap, options)) |r| return r;
|
|
1434
|
+
if (foe_fainted) if (try faint(battle, player.foe(), !more, false, options)) |r| return r;
|
|
1448
1435
|
|
|
1449
1436
|
assert(!side.active.volatiles.MultiHit);
|
|
1450
1437
|
assert(!foe.active.volatiles.MultiHit);
|
|
1451
1438
|
|
|
1452
1439
|
if (player_out and foe_out) {
|
|
1453
|
-
try options.log.tie();
|
|
1440
|
+
try options.log.tie(.{});
|
|
1454
1441
|
return Result.Tie;
|
|
1455
1442
|
} else if (player_out) {
|
|
1456
|
-
try options.log.win(player.foe());
|
|
1443
|
+
try options.log.win(.{player.foe()});
|
|
1457
1444
|
return if (player == .P1) Result.Lose else Result.Win;
|
|
1458
1445
|
} else if (foe_out) {
|
|
1459
|
-
try options.log.win(player);
|
|
1446
|
+
try options.log.win(.{player});
|
|
1460
1447
|
return if (player == .P1) Result.Win else Result.Lose;
|
|
1461
1448
|
}
|
|
1462
1449
|
|
|
@@ -1465,7 +1452,7 @@ fn checkFaint(
|
|
|
1465
1452
|
return .{ .p1 = foe_choice, .p2 = .Switch };
|
|
1466
1453
|
}
|
|
1467
1454
|
|
|
1468
|
-
fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result {
|
|
1455
|
+
fn faint(battle: anytype, player: Player, done: bool, cap: bool, options: anytype) !?Result {
|
|
1469
1456
|
var side = battle.side(player);
|
|
1470
1457
|
var foe = battle.foe(player);
|
|
1471
1458
|
assert(side.stored().hp == 0);
|
|
@@ -1498,8 +1485,10 @@ fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result
|
|
|
1498
1485
|
side.stored().stats.spe;
|
|
1499
1486
|
}
|
|
1500
1487
|
|
|
1501
|
-
try options.log.faint(battle.active(player), done);
|
|
1502
|
-
|
|
1488
|
+
try options.log.faint(.{ battle.active(player), done });
|
|
1489
|
+
// We can only safely set capped if the faint occured as the direct result of an opponent's
|
|
1490
|
+
// move (as opposed to due to residual damage, recoil, Explosion, etc)
|
|
1491
|
+
if (cap) options.calc.capped(player);
|
|
1503
1492
|
return null;
|
|
1504
1493
|
}
|
|
1505
1494
|
|
|
@@ -1520,8 +1509,8 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1520
1509
|
|
|
1521
1510
|
if (!showdown or damage > 0) {
|
|
1522
1511
|
stored.hp -= damage;
|
|
1523
|
-
// Pokémon Showdown uses
|
|
1524
|
-
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
1512
|
+
// Pokémon Showdown uses damage [of] here but its not relevant in Generation I
|
|
1513
|
+
try options.log.damage(.{ ident, stored, if (brn) Damage.Burn else Damage.Poison });
|
|
1525
1514
|
}
|
|
1526
1515
|
}
|
|
1527
1516
|
|
|
@@ -1546,14 +1535,14 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1546
1535
|
const amount = @min(damage, stored.hp);
|
|
1547
1536
|
stored.hp -= amount;
|
|
1548
1537
|
|
|
1549
|
-
// As above, Pokémon Showdown uses
|
|
1550
|
-
if (amount > 0) try options.log.damage(ident, stored, .LeechSeed);
|
|
1538
|
+
// As above, Pokémon Showdown uses damage [of] but its not relevant
|
|
1539
|
+
if (amount > 0) try options.log.damage(.{ ident, stored, .LeechSeed });
|
|
1551
1540
|
|
|
1552
1541
|
const before = foe_stored.hp;
|
|
1553
1542
|
// Uncapped damage is added back to the foe
|
|
1554
1543
|
foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
|
|
1555
1544
|
// Pokémon Showdown uses the less specific heal here instead of drain... because reasons?
|
|
1556
|
-
if (foe_stored.hp > before) try options.log.heal(foe_ident, foe_stored, .Silent);
|
|
1545
|
+
if (foe_stored.hp > before) try options.log.heal(.{ foe_ident, foe_stored, .Silent });
|
|
1557
1546
|
}
|
|
1558
1547
|
}
|
|
1559
1548
|
|
|
@@ -1564,18 +1553,18 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1564
1553
|
battle.turn += 1;
|
|
1565
1554
|
|
|
1566
1555
|
if (showdown and pkmn.options.ebc and checkEBC(battle)) {
|
|
1567
|
-
try options.log.tie();
|
|
1556
|
+
try options.log.tie(.{});
|
|
1568
1557
|
return Result.Tie;
|
|
1569
1558
|
}
|
|
1570
1559
|
|
|
1571
1560
|
if (showdown and battle.turn >= 1000) {
|
|
1572
|
-
try options.log.tie();
|
|
1561
|
+
try options.log.tie(.{});
|
|
1573
1562
|
return Result.Tie;
|
|
1574
1563
|
} else if (battle.turn >= 65535) {
|
|
1575
1564
|
return Result.Error;
|
|
1576
1565
|
}
|
|
1577
1566
|
|
|
1578
|
-
try options.log.turn(battle.turn);
|
|
1567
|
+
try options.log.turn(.{battle.turn});
|
|
1579
1568
|
|
|
1580
1569
|
return Result.Default;
|
|
1581
1570
|
}
|
|
@@ -1645,7 +1634,7 @@ fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
|
1645
1634
|
side.last_selected_move;
|
|
1646
1635
|
if (m != .None and m == last) {
|
|
1647
1636
|
side.active.volatiles.Charging = false;
|
|
1648
|
-
try options.log.
|
|
1637
|
+
try options.log.cant(.{ ident, .Disable, last });
|
|
1649
1638
|
return true;
|
|
1650
1639
|
}
|
|
1651
1640
|
}
|
|
@@ -1659,17 +1648,6 @@ fn buildRage(battle: anytype, who: Player, options: anytype) !void {
|
|
|
1659
1648
|
}
|
|
1660
1649
|
}
|
|
1661
1650
|
|
|
1662
|
-
fn handleThrashing(battle: anytype, active: *ActivePokemon, player: Player, options: anytype) bool {
|
|
1663
|
-
var volatiles = &active.volatiles;
|
|
1664
|
-
assert(volatiles.Thrashing);
|
|
1665
|
-
if (volatiles.attacks > 0) return false;
|
|
1666
|
-
|
|
1667
|
-
volatiles.Thrashing = false;
|
|
1668
|
-
volatiles.Confusion = true;
|
|
1669
|
-
volatiles.confusion = Rolls.confusionDuration(battle, player, true, options);
|
|
1670
|
-
return true;
|
|
1671
|
-
}
|
|
1672
|
-
|
|
1673
1651
|
fn onBegin(
|
|
1674
1652
|
battle: anytype,
|
|
1675
1653
|
player: Player,
|
|
@@ -1678,7 +1656,6 @@ fn onBegin(
|
|
|
1678
1656
|
residual: *bool,
|
|
1679
1657
|
options: anytype,
|
|
1680
1658
|
) !void {
|
|
1681
|
-
assert(move.effect.onBegin());
|
|
1682
1659
|
return switch (move.effect) {
|
|
1683
1660
|
.Confusion => Effects.confusion(battle, player, move, options),
|
|
1684
1661
|
.Conversion => Effects.conversion(battle, player, options),
|
|
@@ -1701,7 +1678,6 @@ fn onBegin(
|
|
|
1701
1678
|
}
|
|
1702
1679
|
|
|
1703
1680
|
fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1704
|
-
assert(move.effect.onEnd());
|
|
1705
1681
|
return switch (move.effect) {
|
|
1706
1682
|
.Bide => Effects.bide(battle, player, options),
|
|
1707
1683
|
// zig fmt: off
|
|
@@ -1723,7 +1699,6 @@ fn alwaysHappens(
|
|
|
1723
1699
|
residual: *bool,
|
|
1724
1700
|
options: anytype,
|
|
1725
1701
|
) !void {
|
|
1726
|
-
assert(move.effect.alwaysHappens());
|
|
1727
1702
|
return switch (move.effect) {
|
|
1728
1703
|
.DrainHP, .DreamEater => Effects.drainHP(battle, player, options),
|
|
1729
1704
|
.Explode => Effects.explode(battle, player),
|
|
@@ -1759,10 +1734,12 @@ pub const Effects = struct {
|
|
|
1759
1734
|
|
|
1760
1735
|
side.active.volatiles.Bide = true;
|
|
1761
1736
|
assert(!side.active.volatiles.Thrashing and !side.active.volatiles.Rage);
|
|
1737
|
+
|
|
1762
1738
|
side.active.volatiles.state = 0;
|
|
1763
1739
|
side.active.volatiles.attacks = Rolls.attackingDuration(battle, player, options);
|
|
1740
|
+
options.chance.durations(.bide, player, .started);
|
|
1764
1741
|
|
|
1765
|
-
try options.log.start(battle.active(player), .Bide);
|
|
1742
|
+
try options.log.start(.{ battle.active(player), .Bide });
|
|
1766
1743
|
}
|
|
1767
1744
|
|
|
1768
1745
|
fn burnChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1776,11 +1753,11 @@ pub const Effects = struct {
|
|
|
1776
1753
|
// GLITCH: Freeze top move selection desync can occur if thawed Pokémon is slower
|
|
1777
1754
|
if (Status.is(foe_stored.status, .FRZ)) {
|
|
1778
1755
|
assert(move.type == .Fire);
|
|
1779
|
-
try options.log.curestatus(
|
|
1756
|
+
try options.log.curestatus(.{
|
|
1780
1757
|
battle.active(player.foe()),
|
|
1781
1758
|
foe_stored.status,
|
|
1782
1759
|
.Message,
|
|
1783
|
-
);
|
|
1760
|
+
});
|
|
1784
1761
|
foe_stored.status = 0;
|
|
1785
1762
|
}
|
|
1786
1763
|
return;
|
|
@@ -1794,7 +1771,7 @@ pub const Effects = struct {
|
|
|
1794
1771
|
foe_stored.status = Status.init(.BRN);
|
|
1795
1772
|
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
1796
1773
|
|
|
1797
|
-
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
1774
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1798
1775
|
}
|
|
1799
1776
|
|
|
1800
1777
|
fn charge(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -1804,8 +1781,8 @@ pub const Effects = struct {
|
|
|
1804
1781
|
volatiles.Charging = true;
|
|
1805
1782
|
const move = side.last_selected_move;
|
|
1806
1783
|
if (move == .Fly or move == .Dig) volatiles.Invulnerable = true;
|
|
1807
|
-
try options.log.laststill();
|
|
1808
|
-
try options.log.prepare(battle.active(player), move);
|
|
1784
|
+
try options.log.laststill(.{});
|
|
1785
|
+
try options.log.prepare(.{ battle.active(player), move });
|
|
1809
1786
|
}
|
|
1810
1787
|
|
|
1811
1788
|
fn confusion(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1816,42 +1793,38 @@ pub const Effects = struct {
|
|
|
1816
1793
|
if (!try Rolls.confusionChance(battle, player, options)) return;
|
|
1817
1794
|
} else {
|
|
1818
1795
|
if (showdown) {
|
|
1819
|
-
if (!try checkHit(battle, player, move, options))
|
|
1820
|
-
|
|
1821
|
-
} else if (sub) {
|
|
1822
|
-
return options.log.fail(battle.active(player.foe()), .None);
|
|
1823
|
-
}
|
|
1796
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1797
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1824
1798
|
} else {
|
|
1825
|
-
if (sub) {
|
|
1826
|
-
|
|
1827
|
-
} else if (!try checkHit(battle, player, move, options)) {
|
|
1828
|
-
return;
|
|
1829
|
-
}
|
|
1799
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1800
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1830
1801
|
}
|
|
1831
|
-
try options.chance.commit(player, .hit);
|
|
1832
1802
|
}
|
|
1833
1803
|
|
|
1834
1804
|
if (foe.active.volatiles.Confusion) return;
|
|
1835
1805
|
foe.active.volatiles.Confusion = true;
|
|
1836
|
-
foe.active.volatiles.confusion = Rolls.confusionDuration(battle, player, false, options);
|
|
1837
1806
|
|
|
1838
|
-
|
|
1807
|
+
foe.active.volatiles.confusion = Rolls.confusionDuration(battle, player, options);
|
|
1808
|
+
options.chance.durations(.confusion, player.foe(), .started);
|
|
1809
|
+
|
|
1810
|
+
try options.log.start(.{ battle.active(player.foe()), .Confusion });
|
|
1839
1811
|
}
|
|
1840
1812
|
|
|
1841
1813
|
fn conversion(battle: anytype, player: Player, options: anytype) !void {
|
|
1842
1814
|
const foe = battle.foe(player);
|
|
1843
1815
|
|
|
1844
1816
|
if (foe.active.volatiles.Invulnerable) {
|
|
1845
|
-
try options.log.lastmiss();
|
|
1846
|
-
return options.log.miss(battle.active(player));
|
|
1817
|
+
try options.log.lastmiss(.{});
|
|
1818
|
+
return options.log.miss(.{battle.active(player)});
|
|
1847
1819
|
}
|
|
1848
1820
|
|
|
1849
1821
|
battle.side(player).active.types = foe.active.types;
|
|
1850
|
-
return options.log.
|
|
1822
|
+
return options.log.start(.{
|
|
1851
1823
|
battle.active(player),
|
|
1824
|
+
.TypeChange,
|
|
1852
1825
|
foe.active.types,
|
|
1853
1826
|
battle.active(player.foe()),
|
|
1854
|
-
);
|
|
1827
|
+
});
|
|
1855
1828
|
}
|
|
1856
1829
|
|
|
1857
1830
|
fn disable(battle: anytype, player: Player, move: Move.Data, options: anytype) !?Result {
|
|
@@ -1860,13 +1833,10 @@ pub const Effects = struct {
|
|
|
1860
1833
|
const foe_ident = battle.active(player.foe());
|
|
1861
1834
|
|
|
1862
1835
|
// Pokémon Showdown handles hit/miss earlier in doMove
|
|
1863
|
-
if (!showdown)
|
|
1864
|
-
if (!try checkHit(battle, player, move, options)) return null;
|
|
1865
|
-
try options.chance.commit(player, .hit);
|
|
1866
|
-
}
|
|
1836
|
+
if (!showdown and !try checkHit(battle, player, move, options)) return null;
|
|
1867
1837
|
|
|
1868
1838
|
if (volatiles.disable_move != 0) {
|
|
1869
|
-
try options.log.fail(foe_ident, .None);
|
|
1839
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1870
1840
|
return null;
|
|
1871
1841
|
}
|
|
1872
1842
|
|
|
@@ -1882,7 +1852,7 @@ pub const Effects = struct {
|
|
|
1882
1852
|
// Technically this is still considered simply a "miss" on the cartridge,
|
|
1883
1853
|
// but diverging from Pokémon Showdown here would mostly just be pedantic
|
|
1884
1854
|
if (n == 0) {
|
|
1885
|
-
try options.log.fail(foe_ident, .None);
|
|
1855
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1886
1856
|
return null;
|
|
1887
1857
|
} else if (err) {
|
|
1888
1858
|
// GLITCH: Transform + Mirror Move / Metronome PP softlock
|
|
@@ -1893,14 +1863,16 @@ pub const Effects = struct {
|
|
|
1893
1863
|
volatiles.disable_move =
|
|
1894
1864
|
@intCast(try Rolls.moveSlot(battle, player, &foe.active.moves, n, options));
|
|
1895
1865
|
volatiles.disable_duration = Rolls.disableDuration(battle, player, options);
|
|
1866
|
+
options.chance.durations(.disable, player.foe(), .started);
|
|
1896
1867
|
|
|
1897
1868
|
const id = foe.active.move(volatiles.disable_move).id;
|
|
1898
|
-
try options.log.
|
|
1869
|
+
try options.log.start(.{ foe_ident, .Disable, id });
|
|
1899
1870
|
return null;
|
|
1900
1871
|
}
|
|
1901
1872
|
|
|
1902
1873
|
fn drainHP(battle: anytype, player: Player, options: anytype) !void {
|
|
1903
1874
|
var stored = battle.side(player).stored();
|
|
1875
|
+
const ident = battle.active(player);
|
|
1904
1876
|
|
|
1905
1877
|
if (battle.last_damage == 0) {
|
|
1906
1878
|
assert(showdown);
|
|
@@ -1913,7 +1885,7 @@ pub const Effects = struct {
|
|
|
1913
1885
|
if (stored.hp == stored.stats.hp) return;
|
|
1914
1886
|
stored.hp = @min(stored.stats.hp, stored.hp + drain);
|
|
1915
1887
|
|
|
1916
|
-
try options.log.
|
|
1888
|
+
try options.log.heal(.{ ident, stored, .Drain, battle.active(player.foe()) });
|
|
1917
1889
|
}
|
|
1918
1890
|
|
|
1919
1891
|
fn explode(battle: anytype, player: Player) !void {
|
|
@@ -1945,7 +1917,7 @@ pub const Effects = struct {
|
|
|
1945
1917
|
if (side.active.volatiles.FocusEnergy) return;
|
|
1946
1918
|
side.active.volatiles.FocusEnergy = true;
|
|
1947
1919
|
|
|
1948
|
-
try options.log.start(battle.active(player), .FocusEnergy);
|
|
1920
|
+
try options.log.start(.{ battle.active(player), .FocusEnergy });
|
|
1949
1921
|
}
|
|
1950
1922
|
|
|
1951
1923
|
fn freezeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1967,7 +1939,7 @@ pub const Effects = struct {
|
|
|
1967
1939
|
foe_stored.status = Status.init(.FRZ);
|
|
1968
1940
|
// GLITCH: Hyper Beam recharging status is not cleared
|
|
1969
1941
|
|
|
1970
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
1942
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
1971
1943
|
}
|
|
1972
1944
|
|
|
1973
1945
|
fn haze(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -1975,7 +1947,7 @@ pub const Effects = struct {
|
|
|
1975
1947
|
var side = battle.side(player);
|
|
1976
1948
|
var foe = battle.foe(player);
|
|
1977
1949
|
|
|
1978
|
-
|
|
1950
|
+
const side_stored = side.stored();
|
|
1979
1951
|
var foe_stored = foe.stored();
|
|
1980
1952
|
|
|
1981
1953
|
const player_ident = battle.active(player);
|
|
@@ -1987,8 +1959,8 @@ pub const Effects = struct {
|
|
|
1987
1959
|
side.active.stats = side_stored.stats;
|
|
1988
1960
|
foe.active.stats = foe_stored.stats;
|
|
1989
1961
|
|
|
1990
|
-
try log.activate(player_ident, .Haze);
|
|
1991
|
-
try log.clearallboost();
|
|
1962
|
+
try log.activate(.{ player_ident, .Haze });
|
|
1963
|
+
try log.clearallboost(.{});
|
|
1992
1964
|
|
|
1993
1965
|
// Pokémon Showdown clears P1 then P2 instead of status -> side -> foe
|
|
1994
1966
|
if (showdown) {
|
|
@@ -1996,24 +1968,26 @@ pub const Effects = struct {
|
|
|
1996
1968
|
const p: Player = @enumFromInt(i);
|
|
1997
1969
|
// Pokémon Showdown incorrectly does not prevent sleep/freeze from moving
|
|
1998
1970
|
if (p != player and Status.any(s.stored().status)) {
|
|
1999
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
1971
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2000
1972
|
s.stored().status = 0;
|
|
1973
|
+
options.chance.durations(.sleep, p, .None);
|
|
2001
1974
|
} else if (showdown and s.stored().status == Status.TOX) {
|
|
2002
1975
|
s.stored().status = Status.init(.PSN);
|
|
2003
|
-
try log.status(battle.active(p), s.stored().status, .None);
|
|
1976
|
+
try log.status(.{ battle.active(p), s.stored().status, .None });
|
|
2004
1977
|
}
|
|
2005
|
-
try clearVolatiles(battle, p,
|
|
1978
|
+
try clearVolatiles(battle, p, options);
|
|
2006
1979
|
}
|
|
2007
1980
|
} else {
|
|
2008
1981
|
if (Status.any(foe_stored.status)) {
|
|
2009
1982
|
if (Status.is(foe_stored.status, .FRZ) or Status.is(foe_stored.status, .SLP)) {
|
|
2010
1983
|
foe.last_selected_move = .SKIP_TURN;
|
|
1984
|
+
options.chance.durations(.sleep, player.foe(), .None);
|
|
2011
1985
|
}
|
|
2012
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
1986
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2013
1987
|
foe_stored.status = 0;
|
|
2014
1988
|
}
|
|
2015
|
-
try clearVolatiles(battle, player,
|
|
2016
|
-
try clearVolatiles(battle, player.foe(),
|
|
1989
|
+
try clearVolatiles(battle, player, options);
|
|
1990
|
+
try clearVolatiles(battle, player.foe(), options);
|
|
2017
1991
|
}
|
|
2018
1992
|
}
|
|
2019
1993
|
|
|
@@ -2025,7 +1999,7 @@ pub const Effects = struct {
|
|
|
2025
1999
|
// GLITCH: HP recovery move failure glitches
|
|
2026
2000
|
const delta = stored.stats.hp - stored.hp;
|
|
2027
2001
|
if (delta == 0 or (delta & 255 == 255 and stored.hp % 256 != 0)) {
|
|
2028
|
-
return
|
|
2002
|
+
return options.log.fail(.{ ident, .None });
|
|
2029
2003
|
}
|
|
2030
2004
|
|
|
2031
2005
|
const rest = side.last_selected_move == .Rest;
|
|
@@ -2033,17 +2007,17 @@ pub const Effects = struct {
|
|
|
2033
2007
|
// Adding the sleep status runs the sleep condition handler to roll duration
|
|
2034
2008
|
if (showdown) battle.rng.advance(1);
|
|
2035
2009
|
stored.status = Status.slf(2);
|
|
2036
|
-
try options.log.
|
|
2010
|
+
try options.log.status(.{ ident, stored.status, .From, Move.Rest });
|
|
2037
2011
|
stored.hp = stored.stats.hp;
|
|
2038
2012
|
} else {
|
|
2039
2013
|
stored.hp = @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2));
|
|
2040
2014
|
}
|
|
2041
|
-
try options.log.heal(ident, stored, if (rest) Heal.Silent else Heal.None);
|
|
2015
|
+
try options.log.heal(.{ ident, stored, if (rest) Heal.Silent else Heal.None });
|
|
2042
2016
|
}
|
|
2043
2017
|
|
|
2044
2018
|
fn hyperBeam(battle: anytype, player: Player, options: anytype) !void {
|
|
2045
2019
|
battle.side(player).active.volatiles.Recharging = true;
|
|
2046
|
-
try options.log.mustrecharge(battle.active(player));
|
|
2020
|
+
try options.log.mustrecharge(.{battle.active(player)});
|
|
2047
2021
|
}
|
|
2048
2022
|
|
|
2049
2023
|
fn leechSeed(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2052,33 +2026,31 @@ pub const Effects = struct {
|
|
|
2052
2026
|
if (showdown) {
|
|
2053
2027
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2054
2028
|
if (!foe.active.volatiles.Invulnerable and foe.active.types.includes(.Grass)) {
|
|
2055
|
-
return options.log.immune(battle.active(player.foe()), .None);
|
|
2029
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
2056
2030
|
}
|
|
2057
2031
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2058
2032
|
if (foe.active.volatiles.LeechSeed) return;
|
|
2059
2033
|
} else {
|
|
2060
2034
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2061
2035
|
if (foe.active.types.includes(.Grass) or foe.active.volatiles.LeechSeed) {
|
|
2062
|
-
try options.log.lastmiss();
|
|
2063
|
-
return options.log.miss(battle.active(player));
|
|
2036
|
+
try options.log.lastmiss(.{});
|
|
2037
|
+
return options.log.miss(.{battle.active(player)});
|
|
2064
2038
|
}
|
|
2065
2039
|
}
|
|
2066
2040
|
|
|
2067
|
-
try options.chance.commit(player, .hit);
|
|
2068
2041
|
foe.active.volatiles.LeechSeed = true;
|
|
2069
2042
|
|
|
2070
|
-
try options.log.start(battle.active(player.foe()), .LeechSeed);
|
|
2043
|
+
try options.log.start(.{ battle.active(player.foe()), .LeechSeed });
|
|
2071
2044
|
}
|
|
2072
2045
|
|
|
2073
2046
|
fn lightScreen(battle: anytype, player: Player, options: anytype) !void {
|
|
2074
2047
|
var side = battle.side(player);
|
|
2048
|
+
const ident = battle.active(player);
|
|
2075
2049
|
|
|
2076
|
-
if (side.active.volatiles.LightScreen) {
|
|
2077
|
-
return options.log.fail(battle.active(player), .None);
|
|
2078
|
-
}
|
|
2050
|
+
if (side.active.volatiles.LightScreen) return options.log.fail(.{ ident, .None });
|
|
2079
2051
|
side.active.volatiles.LightScreen = true;
|
|
2080
2052
|
|
|
2081
|
-
try options.log.start(
|
|
2053
|
+
try options.log.start(.{ ident, .LightScreen });
|
|
2082
2054
|
}
|
|
2083
2055
|
|
|
2084
2056
|
fn mimic(battle: anytype, player: Player, move: Move.Data, mslot: u8, options: anytype) !void {
|
|
@@ -2106,17 +2078,15 @@ pub const Effects = struct {
|
|
|
2106
2078
|
if (!has_mimic) {
|
|
2107
2079
|
// Invulnerable foes or 1/256 miss can trigger |-miss| instead of |-fail|
|
|
2108
2080
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2109
|
-
|
|
2110
|
-
return try options.log.fail(battle.active(player.foe()), .None);
|
|
2081
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2111
2082
|
}
|
|
2112
2083
|
}
|
|
2113
2084
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2114
|
-
try options.chance.commit(player, .hit);
|
|
2115
2085
|
|
|
2116
2086
|
const rslot = try Rolls.moveSlot(battle, player, &foe.active.moves, 0, options);
|
|
2117
2087
|
side.active.move(oslot).id = foe.active.move(rslot).id;
|
|
2118
2088
|
|
|
2119
|
-
try options.log.
|
|
2089
|
+
try options.log.start(.{ battle.active(player), .Mimic, side.active.move(oslot).id });
|
|
2120
2090
|
}
|
|
2121
2091
|
|
|
2122
2092
|
fn mist(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -2125,7 +2095,7 @@ pub const Effects = struct {
|
|
|
2125
2095
|
if (side.active.volatiles.Mist) return;
|
|
2126
2096
|
side.active.volatiles.Mist = true;
|
|
2127
2097
|
|
|
2128
|
-
try options.log.start(battle.active(player), .Mist);
|
|
2098
|
+
try options.log.start(.{ battle.active(player), .Mist });
|
|
2129
2099
|
}
|
|
2130
2100
|
|
|
2131
2101
|
fn multiHit(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2151,25 +2121,25 @@ pub const Effects = struct {
|
|
|
2151
2121
|
|
|
2152
2122
|
if (showdown) {
|
|
2153
2123
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2154
|
-
if (immune and !foe.active.volatiles.Invulnerable)
|
|
2155
|
-
|
|
2124
|
+
if (immune and !foe.active.volatiles.Invulnerable) {
|
|
2125
|
+
return log.immune(.{ foe_ident, .None });
|
|
2126
|
+
} else if (!try checkHit(battle, player, move, options)) return;
|
|
2156
2127
|
}
|
|
2157
2128
|
if (Status.any(foe_stored.status)) {
|
|
2158
|
-
return log.fail(
|
|
2129
|
+
return log.fail(.{
|
|
2159
2130
|
foe_ident,
|
|
2160
|
-
if (Status.is(foe_stored.status, .PAR)) .Paralysis else .None,
|
|
2161
|
-
);
|
|
2131
|
+
if (Status.is(foe_stored.status, .PAR)) Fail.Paralysis else Fail.None,
|
|
2132
|
+
});
|
|
2162
2133
|
}
|
|
2163
2134
|
if (!showdown) {
|
|
2164
|
-
if (immune) return log.immune(foe_ident, .None);
|
|
2135
|
+
if (immune) return log.immune(.{ foe_ident, .None });
|
|
2165
2136
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2166
2137
|
}
|
|
2167
|
-
try options.chance.commit(player, .hit);
|
|
2168
2138
|
|
|
2169
2139
|
foe_stored.status = Status.init(.PAR);
|
|
2170
2140
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2171
2141
|
|
|
2172
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2142
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2173
2143
|
}
|
|
2174
2144
|
|
|
2175
2145
|
fn paralyzeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2191,12 +2161,12 @@ pub const Effects = struct {
|
|
|
2191
2161
|
foe_stored.status = Status.init(.PAR);
|
|
2192
2162
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2193
2163
|
|
|
2194
|
-
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
2164
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
2195
2165
|
}
|
|
2196
2166
|
|
|
2197
2167
|
fn payDay(battle: anytype, player: Player, options: anytype) !void {
|
|
2198
2168
|
if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
|
|
2199
|
-
try options.log.fieldactivate();
|
|
2169
|
+
try options.log.fieldactivate(.{});
|
|
2200
2170
|
}
|
|
2201
2171
|
}
|
|
2202
2172
|
|
|
@@ -2211,28 +2181,27 @@ pub const Effects = struct {
|
|
|
2211
2181
|
return;
|
|
2212
2182
|
} else if (foe.active.volatiles.Substitute) {
|
|
2213
2183
|
if (move.effect != .Poison) return;
|
|
2214
|
-
return log.fail(foe_ident, .None);
|
|
2184
|
+
return log.fail(.{ foe_ident, .None });
|
|
2215
2185
|
} else if (Status.any(foe_stored.status)) {
|
|
2216
2186
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2217
2187
|
// Pokémon Showdown considers Toxic to be a status even in Generation I and so
|
|
2218
2188
|
// will not include a fail reason for Toxic vs. Poison or vice-versa...
|
|
2219
|
-
return log.fail(foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2189
|
+
return log.fail(.{ foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2220
2190
|
if (!showdown)
|
|
2221
|
-
.Poison
|
|
2191
|
+
Fail.Poison
|
|
2222
2192
|
else if (toxic == (foe_stored.status == Status.TOX))
|
|
2223
|
-
if (toxic) .Toxic else .Poison
|
|
2193
|
+
if (toxic) Fail.Toxic else Fail.Poison
|
|
2224
2194
|
else
|
|
2225
|
-
.None
|
|
2195
|
+
Fail.None
|
|
2226
2196
|
else
|
|
2227
|
-
.None);
|
|
2197
|
+
Fail.None });
|
|
2228
2198
|
} else if (foe.active.types.includes(.Poison)) {
|
|
2229
2199
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2230
|
-
return log.immune(foe_ident, .None);
|
|
2200
|
+
return log.immune(.{ foe_ident, .None });
|
|
2231
2201
|
}
|
|
2232
2202
|
|
|
2233
2203
|
if (move.effect == .Poison) {
|
|
2234
2204
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
2235
|
-
try options.chance.commit(player, .hit);
|
|
2236
2205
|
} else {
|
|
2237
2206
|
if (!try Rolls.poisonChance(battle, player, move.effect == .PoisonChance1, options)) {
|
|
2238
2207
|
return;
|
|
@@ -2246,7 +2215,7 @@ pub const Effects = struct {
|
|
|
2246
2215
|
foe.active.volatiles.toxic = 0;
|
|
2247
2216
|
}
|
|
2248
2217
|
|
|
2249
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2218
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2250
2219
|
}
|
|
2251
2220
|
|
|
2252
2221
|
fn rage(battle: anytype, player: Player) !void {
|
|
@@ -2266,22 +2235,23 @@ pub const Effects = struct {
|
|
|
2266
2235
|
@as(u8, if (side.last_selected_move == .Struggle) 2 else 4), 1));
|
|
2267
2236
|
stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
|
|
2268
2237
|
|
|
2269
|
-
try options.log.
|
|
2238
|
+
try options.log.damage(.{
|
|
2270
2239
|
battle.active(player),
|
|
2271
2240
|
stored,
|
|
2272
2241
|
.RecoilOf,
|
|
2273
2242
|
battle.active(player.foe()),
|
|
2274
|
-
);
|
|
2243
|
+
});
|
|
2275
2244
|
if (showdown and stored.hp == 0) residual.* = false;
|
|
2276
2245
|
}
|
|
2277
2246
|
|
|
2278
2247
|
fn reflect(battle: anytype, player: Player, options: anytype) !void {
|
|
2279
2248
|
var side = battle.side(player);
|
|
2249
|
+
const ident = battle.active(player);
|
|
2280
2250
|
|
|
2281
|
-
if (side.active.volatiles.Reflect) return options.log.fail(
|
|
2251
|
+
if (side.active.volatiles.Reflect) return options.log.fail(.{ ident, .None });
|
|
2282
2252
|
side.active.volatiles.Reflect = true;
|
|
2283
2253
|
|
|
2284
|
-
try options.log.start(
|
|
2254
|
+
try options.log.start(.{ ident, .Reflect });
|
|
2285
2255
|
}
|
|
2286
2256
|
|
|
2287
2257
|
fn sleep(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2295,14 +2265,13 @@ pub const Effects = struct {
|
|
|
2295
2265
|
// the Sleep Clause Mod might activate, causing us to not actually bypass
|
|
2296
2266
|
} else {
|
|
2297
2267
|
if (Status.any(foe_stored.status)) {
|
|
2298
|
-
return options.log.fail(
|
|
2268
|
+
return options.log.fail(.{
|
|
2299
2269
|
foe_ident,
|
|
2300
|
-
if (Status.is(foe_stored.status, .SLP)) .Sleep else .None,
|
|
2301
|
-
);
|
|
2270
|
+
if (Status.is(foe_stored.status, .SLP)) Fail.Sleep else Fail.None,
|
|
2271
|
+
});
|
|
2302
2272
|
}
|
|
2303
|
-
// If
|
|
2273
|
+
// If moveHit in doMove didn't return true Pokémon Showdown wouldn't be in here
|
|
2304
2274
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
2305
|
-
try options.chance.commit(player, .hit);
|
|
2306
2275
|
}
|
|
2307
2276
|
|
|
2308
2277
|
// Sleep Clause Mod
|
|
@@ -2314,19 +2283,20 @@ pub const Effects = struct {
|
|
|
2314
2283
|
foe.active.volatiles.Recharging = false;
|
|
2315
2284
|
|
|
2316
2285
|
foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
|
|
2286
|
+
options.chance.durations(.sleep, player.foe(), .started);
|
|
2287
|
+
|
|
2317
2288
|
const last = battle.side(player).last_selected_move;
|
|
2318
|
-
try options.log.
|
|
2289
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .From, last });
|
|
2319
2290
|
}
|
|
2320
2291
|
|
|
2321
2292
|
fn splash(battle: anytype, player: Player, options: anytype) !void {
|
|
2322
|
-
try options.log.activate(battle.active(player), .Splash);
|
|
2293
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
2323
2294
|
}
|
|
2324
2295
|
|
|
2325
2296
|
fn substitute(battle: anytype, player: Player, residual: *bool, options: anytype) !void {
|
|
2326
2297
|
var side = battle.side(player);
|
|
2327
2298
|
if (side.active.volatiles.Substitute) {
|
|
2328
|
-
|
|
2329
|
-
return;
|
|
2299
|
+
return options.log.fail(.{ battle.active(player), .Substitute });
|
|
2330
2300
|
}
|
|
2331
2301
|
|
|
2332
2302
|
assert(side.stored().stats.hp <= 1023);
|
|
@@ -2338,17 +2308,16 @@ pub const Effects = struct {
|
|
|
2338
2308
|
// avoid having to convert to floating point)
|
|
2339
2309
|
const required_hp = if (showdown) @divFloor(side.stored().stats.hp - 1, 4) + 1 else hp;
|
|
2340
2310
|
if (side.stored().hp < required_hp) {
|
|
2341
|
-
|
|
2342
|
-
return;
|
|
2311
|
+
return options.log.fail(.{ battle.active(player), .Weak });
|
|
2343
2312
|
}
|
|
2344
2313
|
|
|
2345
2314
|
// GLITCH: can leave the user with 0 HP (faints later) because didn't check '<=' above
|
|
2346
2315
|
side.stored().hp -= hp;
|
|
2347
2316
|
side.active.volatiles.substitute = hp + 1;
|
|
2348
2317
|
side.active.volatiles.Substitute = true;
|
|
2349
|
-
try options.log.start(battle.active(player), .Substitute);
|
|
2318
|
+
try options.log.start(.{ battle.active(player), .Substitute });
|
|
2350
2319
|
if (hp > 0) {
|
|
2351
|
-
try options.log.damage(battle.active(player), side.stored(), .None);
|
|
2320
|
+
try options.log.damage(.{ battle.active(player), side.stored(), .None });
|
|
2352
2321
|
if (showdown and side.stored().hp == 0) residual.* = false;
|
|
2353
2322
|
}
|
|
2354
2323
|
}
|
|
@@ -2357,13 +2326,11 @@ pub const Effects = struct {
|
|
|
2357
2326
|
// Whirlwind/Roar should not roll to hit/reset damage but Pokémon Showdown does anyway
|
|
2358
2327
|
if (showdown) {
|
|
2359
2328
|
if (battle.side(player).last_selected_move == .Teleport) return;
|
|
2360
|
-
|
|
2361
|
-
try options.chance.commit(player, .hit);
|
|
2362
|
-
}
|
|
2329
|
+
_ = try checkHit(battle, player, move, options);
|
|
2363
2330
|
battle.last_damage = 0;
|
|
2364
2331
|
} else {
|
|
2365
|
-
try options.log.fail(battle.active(player), .None);
|
|
2366
|
-
try options.log.laststill();
|
|
2332
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2333
|
+
try options.log.laststill(.{});
|
|
2367
2334
|
}
|
|
2368
2335
|
}
|
|
2369
2336
|
|
|
@@ -2374,6 +2341,8 @@ pub const Effects = struct {
|
|
|
2374
2341
|
|
|
2375
2342
|
volatiles.Thrashing = true;
|
|
2376
2343
|
volatiles.attacks = Rolls.attackingDuration(battle, player, options);
|
|
2344
|
+
|
|
2345
|
+
options.chance.durations(.thrashing, player, .started);
|
|
2377
2346
|
}
|
|
2378
2347
|
|
|
2379
2348
|
fn transform(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -2402,7 +2371,7 @@ pub const Effects = struct {
|
|
|
2402
2371
|
side.active.moves[i].pp = if (m.id != .None) 5 else 0;
|
|
2403
2372
|
}
|
|
2404
2373
|
|
|
2405
|
-
try options.log.transform(battle.active(player), foe_ident);
|
|
2374
|
+
try options.log.transform(.{ battle.active(player), foe_ident });
|
|
2406
2375
|
}
|
|
2407
2376
|
|
|
2408
2377
|
fn binding(battle: anytype, player: Player, rewrap: bool, options: anytype) !void {
|
|
@@ -2419,6 +2388,7 @@ pub const Effects = struct {
|
|
|
2419
2388
|
|
|
2420
2389
|
side.active.volatiles.attacks =
|
|
2421
2390
|
try Rolls.distribution(battle, .Binding, player, options) - 1;
|
|
2391
|
+
options.chance.durations(.binding, player, .started);
|
|
2422
2392
|
}
|
|
2423
2393
|
|
|
2424
2394
|
fn boost(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2432,87 +2402,87 @@ pub const Effects = struct {
|
|
|
2432
2402
|
switch (move.effect) {
|
|
2433
2403
|
.AttackUp1, .AttackUp2, .Rage => {
|
|
2434
2404
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2435
|
-
if (boosts.atk == 6) return
|
|
2405
|
+
if (boosts.atk == 6) return log.fail(.{ ident, .None });
|
|
2436
2406
|
const n: u2 = if (move.effect == .AttackUp2) 2 else 1;
|
|
2437
2407
|
boosts.atk = @as(i4, @intCast(@min(6, @as(i8, boosts.atk) + n)));
|
|
2438
|
-
const reason = if (move.effect == .Rage)
|
|
2408
|
+
const reason: Boost = if (move.effect == .Rage) .Rage else .Attack;
|
|
2439
2409
|
if (stats.atk == MAX_STAT_VALUE) {
|
|
2440
2410
|
boosts.atk -= 1;
|
|
2441
2411
|
if (showdown) {
|
|
2442
|
-
try log.boost(ident, reason, n);
|
|
2443
|
-
try log.boost(ident,
|
|
2412
|
+
try log.boost(.{ ident, reason, n });
|
|
2413
|
+
try log.boost(.{ ident, .Attack, -1 });
|
|
2444
2414
|
if (move.effect == .Rage) return;
|
|
2445
2415
|
}
|
|
2446
|
-
return
|
|
2416
|
+
return log.fail(.{ ident, .None });
|
|
2447
2417
|
}
|
|
2448
|
-
|
|
2418
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2449
2419
|
const stat = unmodifiedStats(battle, side).atk;
|
|
2450
2420
|
stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2451
|
-
try log.boost(ident, reason, n);
|
|
2421
|
+
try log.boost(.{ ident, reason, n });
|
|
2452
2422
|
// Pokémon Showdown doesn't re-apply status modifiers after Rage boosts
|
|
2453
2423
|
if (showdown and move.effect == .Rage) return;
|
|
2454
2424
|
},
|
|
2455
2425
|
.DefenseUp1, .DefenseUp2 => {
|
|
2456
2426
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2457
|
-
if (boosts.def == 6) return
|
|
2427
|
+
if (boosts.def == 6) return log.fail(.{ ident, .None });
|
|
2458
2428
|
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
2459
2429
|
boosts.def = @intCast(@min(6, @as(i8, boosts.def) + n));
|
|
2460
2430
|
if (stats.def == MAX_STAT_VALUE) {
|
|
2461
2431
|
boosts.def -= 1;
|
|
2462
2432
|
if (showdown) {
|
|
2463
|
-
try log.boost(ident, .Defense, n);
|
|
2464
|
-
try log.boost(ident, .Defense, -1);
|
|
2433
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2434
|
+
try log.boost(.{ ident, .Defense, -1 });
|
|
2465
2435
|
}
|
|
2466
|
-
return
|
|
2436
|
+
return log.fail(.{ ident, .None });
|
|
2467
2437
|
}
|
|
2468
|
-
|
|
2438
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2469
2439
|
const stat = unmodifiedStats(battle, side).def;
|
|
2470
2440
|
stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2471
|
-
try log.boost(ident, .Defense, n);
|
|
2441
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2472
2442
|
},
|
|
2473
2443
|
.SpeedUp2 => {
|
|
2474
2444
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2475
|
-
if (boosts.spe == 6) return
|
|
2445
|
+
if (boosts.spe == 6) return log.fail(.{ ident, .None });
|
|
2476
2446
|
boosts.spe = @intCast(@min(6, @as(i8, boosts.spe) + 2));
|
|
2477
2447
|
if (stats.spe == MAX_STAT_VALUE) {
|
|
2478
2448
|
boosts.spe -= 1;
|
|
2479
2449
|
if (showdown) {
|
|
2480
|
-
try log.boost(ident, .Speed, 2);
|
|
2481
|
-
try log.boost(ident, .Speed, -1);
|
|
2450
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2451
|
+
try log.boost(.{ ident, .Speed, -1 });
|
|
2482
2452
|
}
|
|
2483
|
-
return
|
|
2453
|
+
return log.fail(.{ ident, .None });
|
|
2484
2454
|
}
|
|
2485
|
-
|
|
2455
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2486
2456
|
const stat = unmodifiedStats(battle, side).spe;
|
|
2487
2457
|
stats.spe = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2488
|
-
try log.boost(ident, .Speed, 2);
|
|
2458
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2489
2459
|
},
|
|
2490
2460
|
.SpecialUp1, .SpecialUp2 => {
|
|
2491
2461
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2492
|
-
if (boosts.spc == 6) return
|
|
2462
|
+
if (boosts.spc == 6) return log.fail(.{ ident, .None });
|
|
2493
2463
|
const n: u2 = if (move.effect == .SpecialUp2) 2 else 1;
|
|
2494
2464
|
boosts.spc = @intCast(@min(6, @as(i8, boosts.spc) + n));
|
|
2495
2465
|
if (stats.spc == MAX_STAT_VALUE) {
|
|
2496
2466
|
boosts.spc -= 1;
|
|
2497
2467
|
if (showdown) {
|
|
2498
|
-
try log.boost(ident, .SpecialAttack, n);
|
|
2499
|
-
try log.boost(ident, .SpecialAttack, -1);
|
|
2500
|
-
try log.boost(ident, .SpecialDefense, n);
|
|
2501
|
-
try log.boost(ident, .SpecialDefense, -1);
|
|
2468
|
+
try log.boost(.{ ident, .SpecialAttack, n });
|
|
2469
|
+
try log.boost(.{ ident, .SpecialAttack, -1 });
|
|
2470
|
+
try log.boost(.{ ident, .SpecialDefense, n });
|
|
2471
|
+
try log.boost(.{ ident, .SpecialDefense, -1 });
|
|
2502
2472
|
}
|
|
2503
|
-
return
|
|
2473
|
+
return log.fail(.{ ident, .None });
|
|
2504
2474
|
}
|
|
2505
|
-
|
|
2475
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2506
2476
|
const stat = unmodifiedStats(battle, side).spc;
|
|
2507
2477
|
stats.spc = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2508
|
-
try log.boost(ident, .SpecialAttack, n);
|
|
2509
|
-
try log.boost(ident, .SpecialDefense, n);
|
|
2478
|
+
try log.boost(.{ ident, .SpecialAttack, n });
|
|
2479
|
+
try log.boost(.{ ident, .SpecialDefense, n });
|
|
2510
2480
|
},
|
|
2511
2481
|
.EvasionUp1 => {
|
|
2512
2482
|
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2513
|
-
if (boosts.evasion == 6) return
|
|
2483
|
+
if (boosts.evasion == 6) return log.fail(.{ ident, .None });
|
|
2514
2484
|
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2515
|
-
try log.boost(ident, .Evasion, 1);
|
|
2485
|
+
try log.boost(.{ ident, .Evasion, 1 });
|
|
2516
2486
|
},
|
|
2517
2487
|
else => unreachable,
|
|
2518
2488
|
}
|
|
@@ -2527,7 +2497,7 @@ pub const Effects = struct {
|
|
|
2527
2497
|
const foe_ident = battle.active(player.foe());
|
|
2528
2498
|
const secondary = move.effect.isStatDownChance();
|
|
2529
2499
|
|
|
2530
|
-
if (foe.active.volatiles.Substitute) return if (!secondary) log.fail(foe_ident, .None);
|
|
2500
|
+
if (foe.active.volatiles.Substitute) return if (!secondary) log.fail(.{ foe_ident, .None });
|
|
2531
2501
|
|
|
2532
2502
|
if (secondary) {
|
|
2533
2503
|
const proc = try Rolls.unboost(battle, player, options);
|
|
@@ -2535,7 +2505,6 @@ pub const Effects = struct {
|
|
|
2535
2505
|
} else {
|
|
2536
2506
|
// checkHit already checks for Invulnerable
|
|
2537
2507
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
2538
|
-
try options.chance.commit(player, .hit);
|
|
2539
2508
|
}
|
|
2540
2509
|
|
|
2541
2510
|
var stats = &foe.active.stats;
|
|
@@ -2545,82 +2514,82 @@ pub const Effects = struct {
|
|
|
2545
2514
|
switch (move.effect) {
|
|
2546
2515
|
.AttackDown1, .AttackDownChance => {
|
|
2547
2516
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2548
|
-
if (boosts.atk == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2517
|
+
if (boosts.atk == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2549
2518
|
boosts.atk = @intCast(@max(-6, @as(i8, boosts.atk) - 1));
|
|
2550
2519
|
if (stats.atk == 1) {
|
|
2551
2520
|
boosts.atk += 1;
|
|
2552
2521
|
if (showdown) {
|
|
2553
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2554
|
-
try log.boost(foe_ident, .Attack, 1);
|
|
2522
|
+
try log.boost(.{ foe_ident, .Attack, -1 });
|
|
2523
|
+
try log.boost(.{ foe_ident, .Attack, 1 });
|
|
2555
2524
|
}
|
|
2556
|
-
return
|
|
2525
|
+
return log.fail(.{ foe_ident, .None });
|
|
2557
2526
|
}
|
|
2558
|
-
|
|
2527
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2559
2528
|
const stat = unmodifiedStats(battle, foe).atk;
|
|
2560
2529
|
stats.atk = @max(1, stat * mod[0] / mod[1]);
|
|
2561
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2530
|
+
try log.boost(.{ foe_ident, .Attack, -1 });
|
|
2562
2531
|
},
|
|
2563
2532
|
.DefenseDown1, .DefenseDown2, .DefenseDownChance => {
|
|
2564
2533
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2565
|
-
if (boosts.def == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2534
|
+
if (boosts.def == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2566
2535
|
const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
|
|
2567
2536
|
boosts.def = @intCast(@max(-6, @as(i8, boosts.def) - n));
|
|
2568
2537
|
if (stats.def == 1) {
|
|
2569
2538
|
boosts.def += 1;
|
|
2570
2539
|
if (showdown) {
|
|
2571
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2572
|
-
try log.boost(foe_ident, .Defense, 1);
|
|
2540
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2541
|
+
try log.boost(.{ foe_ident, .Defense, 1 });
|
|
2573
2542
|
}
|
|
2574
|
-
return
|
|
2543
|
+
return log.fail(.{ foe_ident, .None });
|
|
2575
2544
|
}
|
|
2576
|
-
|
|
2545
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2577
2546
|
const stat = unmodifiedStats(battle, foe).def;
|
|
2578
2547
|
stats.def = @max(1, stat * mod[0] / mod[1]);
|
|
2579
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2548
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2580
2549
|
},
|
|
2581
2550
|
.SpeedDown1, .SpeedDownChance => {
|
|
2582
2551
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2583
|
-
if (boosts.spe == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2552
|
+
if (boosts.spe == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2584
2553
|
boosts.spe = @intCast(@max(-6, @as(i8, boosts.spe) - 1));
|
|
2585
2554
|
if (stats.spe == 1) {
|
|
2586
2555
|
boosts.spe += 1;
|
|
2587
2556
|
if (showdown) {
|
|
2588
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2589
|
-
try log.boost(foe_ident, .Speed, 1);
|
|
2557
|
+
try log.boost(.{ foe_ident, .Speed, -1 });
|
|
2558
|
+
try log.boost(.{ foe_ident, .Speed, 1 });
|
|
2590
2559
|
}
|
|
2591
|
-
return
|
|
2560
|
+
return log.fail(.{ foe_ident, .None });
|
|
2592
2561
|
}
|
|
2593
|
-
|
|
2562
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2594
2563
|
const stat = unmodifiedStats(battle, foe).spe;
|
|
2595
2564
|
stats.spe = @max(1, stat * mod[0] / mod[1]);
|
|
2596
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2565
|
+
try log.boost(.{ foe_ident, .Speed, -1 });
|
|
2597
2566
|
assert(boosts.spe >= -6);
|
|
2598
2567
|
},
|
|
2599
2568
|
.SpecialDownChance => {
|
|
2600
2569
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2601
|
-
if (boosts.spc == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2570
|
+
if (boosts.spc == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2602
2571
|
boosts.spc = @intCast(@max(-6, @as(i8, boosts.spc) - 1));
|
|
2603
2572
|
if (stats.spc == 1) {
|
|
2604
2573
|
boosts.spc += 1;
|
|
2605
2574
|
if (showdown) {
|
|
2606
|
-
try log.boost(foe_ident, .SpecialAttack, -1);
|
|
2607
|
-
try log.boost(foe_ident, .SpecialAttack, 1);
|
|
2608
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2609
|
-
try log.boost(foe_ident, .SpecialDefense, 1);
|
|
2575
|
+
try log.boost(.{ foe_ident, .SpecialAttack, -1 });
|
|
2576
|
+
try log.boost(.{ foe_ident, .SpecialAttack, 1 });
|
|
2577
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2578
|
+
try log.boost(.{ foe_ident, .SpecialDefense, 1 });
|
|
2610
2579
|
}
|
|
2611
|
-
return
|
|
2580
|
+
return log.fail(.{ foe_ident, .None });
|
|
2612
2581
|
}
|
|
2613
|
-
|
|
2582
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2614
2583
|
const stat = unmodifiedStats(battle, foe).spc;
|
|
2615
2584
|
stats.spc = @max(1, stat * mod[0] / mod[1]);
|
|
2616
|
-
try log.boost(foe_ident, .SpecialAttack, -1);
|
|
2617
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2585
|
+
try log.boost(.{ foe_ident, .SpecialAttack, -1 });
|
|
2586
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2618
2587
|
},
|
|
2619
2588
|
.AccuracyDown1 => {
|
|
2620
2589
|
assert(boosts.accuracy >= -6 and boosts.accuracy <= 6);
|
|
2621
|
-
if (boosts.accuracy == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2590
|
+
if (boosts.accuracy == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2622
2591
|
boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
|
|
2623
|
-
try log.boost(foe_ident, .Accuracy, -1);
|
|
2592
|
+
try log.boost(.{ foe_ident, .Accuracy, -1 });
|
|
2624
2593
|
},
|
|
2625
2594
|
else => unreachable,
|
|
2626
2595
|
}
|
|
@@ -2649,35 +2618,35 @@ fn isForced(active: anytype) bool {
|
|
|
2649
2618
|
active.volatiles.Thrashing or active.volatiles.Charging;
|
|
2650
2619
|
}
|
|
2651
2620
|
|
|
2652
|
-
fn clearVolatiles(battle: anytype, who: Player,
|
|
2621
|
+
fn clearVolatiles(battle: anytype, who: Player, options: anytype) !void {
|
|
2653
2622
|
var log = options.log;
|
|
2654
2623
|
var side = battle.side(who);
|
|
2655
2624
|
var volatiles = &side.active.volatiles;
|
|
2656
2625
|
const ident = battle.active(who);
|
|
2657
2626
|
|
|
2658
|
-
options.chance.clearDurations(who, clear);
|
|
2659
|
-
|
|
2660
2627
|
if (volatiles.disable_move != 0) {
|
|
2661
2628
|
volatiles.disable_move = 0;
|
|
2662
2629
|
volatiles.disable_duration = 0;
|
|
2663
|
-
|
|
2630
|
+
options.chance.durations(.disable, who, .None);
|
|
2631
|
+
try log.end(.{ ident, .DisableSilent });
|
|
2664
2632
|
}
|
|
2665
2633
|
if (volatiles.Confusion) {
|
|
2666
2634
|
// volatiles.confusion is left unchanged
|
|
2667
2635
|
volatiles.Confusion = false;
|
|
2668
|
-
|
|
2636
|
+
options.chance.durations(.confusion, who, .None);
|
|
2637
|
+
try log.end(.{ ident, .ConfusionSilent });
|
|
2669
2638
|
}
|
|
2670
2639
|
if (volatiles.Mist) {
|
|
2671
2640
|
volatiles.Mist = false;
|
|
2672
|
-
try log.end(ident, .MistSilent);
|
|
2641
|
+
try log.end(.{ ident, .MistSilent });
|
|
2673
2642
|
}
|
|
2674
2643
|
if (volatiles.FocusEnergy) {
|
|
2675
2644
|
volatiles.FocusEnergy = false;
|
|
2676
|
-
try log.end(ident, .FocusEnergySilent);
|
|
2645
|
+
try log.end(.{ ident, .FocusEnergySilent });
|
|
2677
2646
|
}
|
|
2678
2647
|
if (volatiles.LeechSeed) {
|
|
2679
2648
|
volatiles.LeechSeed = false;
|
|
2680
|
-
try log.end(ident, .LeechSeedSilent);
|
|
2649
|
+
try log.end(.{ ident, .LeechSeedSilent });
|
|
2681
2650
|
}
|
|
2682
2651
|
if (!showdown and volatiles.Toxic) {
|
|
2683
2652
|
volatiles.Toxic = false;
|
|
@@ -2685,24 +2654,30 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2685
2654
|
}
|
|
2686
2655
|
if (volatiles.LightScreen) {
|
|
2687
2656
|
volatiles.LightScreen = false;
|
|
2688
|
-
try log.end(ident, .LightScreenSilent);
|
|
2657
|
+
try log.end(.{ ident, .LightScreenSilent });
|
|
2689
2658
|
}
|
|
2690
2659
|
if (volatiles.Reflect) {
|
|
2691
2660
|
volatiles.Reflect = false;
|
|
2692
|
-
try log.end(ident, .ReflectSilent);
|
|
2661
|
+
try log.end(.{ ident, .ReflectSilent });
|
|
2693
2662
|
}
|
|
2694
2663
|
if (showdown and volatiles.Toxic) {
|
|
2695
2664
|
volatiles.Toxic = false;
|
|
2696
2665
|
// Pokémon Showdown erroneously clears the toxic counter
|
|
2697
2666
|
volatiles.toxic = 0;
|
|
2698
|
-
try log.end(ident, .ToxicSilent);
|
|
2667
|
+
try log.end(.{ ident, .ToxicSilent });
|
|
2699
2668
|
}
|
|
2700
2669
|
}
|
|
2701
2670
|
|
|
2671
|
+
fn observation(n: anytype) optional.Optional(chance.Observation) {
|
|
2672
|
+
return if (n == 0) .ended else .continuing;
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2702
2675
|
pub const Rolls = struct {
|
|
2703
2676
|
fn speedTie(battle: anytype, options: anytype) !bool {
|
|
2704
2677
|
const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
|
|
2705
2678
|
player == .P1
|
|
2679
|
+
else if (@hasDecl(@TypeOf(battle.rng), "speedTie"))
|
|
2680
|
+
battle.rng.speedTie()
|
|
2706
2681
|
else if (showdown)
|
|
2707
2682
|
battle.rng.range(u8, 0, 2) == 0
|
|
2708
2683
|
else
|
|
@@ -2712,21 +2687,31 @@ pub const Rolls = struct {
|
|
|
2712
2687
|
return p1;
|
|
2713
2688
|
}
|
|
2714
2689
|
|
|
2715
|
-
fn criticalHit(
|
|
2690
|
+
fn criticalHit(
|
|
2691
|
+
battle: anytype,
|
|
2692
|
+
player: Player,
|
|
2693
|
+
rate: u8,
|
|
2694
|
+
spurious: bool,
|
|
2695
|
+
options: anytype,
|
|
2696
|
+
) !bool {
|
|
2716
2697
|
const crit = if (options.calc.overridden(player, .critical_hit)) |val|
|
|
2717
2698
|
val == .true
|
|
2699
|
+
else if (@hasDecl(@TypeOf(battle.rng), "criticalHit"))
|
|
2700
|
+
battle.rng.criticalHit(player, rate)
|
|
2718
2701
|
else if (showdown)
|
|
2719
2702
|
battle.rng.chance(u8, rate, 256)
|
|
2720
2703
|
else
|
|
2721
2704
|
std.math.rotl(u8, battle.rng.next(), 3) < rate;
|
|
2722
2705
|
|
|
2723
|
-
try options.chance.criticalHit(player, crit, rate);
|
|
2706
|
+
if (!spurious) try options.chance.criticalHit(player, crit, rate);
|
|
2724
2707
|
return crit;
|
|
2725
2708
|
}
|
|
2726
2709
|
|
|
2727
2710
|
fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2728
2711
|
const roll = if (options.calc.overridden(player, .damage)) |val|
|
|
2729
2712
|
val
|
|
2713
|
+
else if (@hasDecl(@TypeOf(battle.rng), "damage"))
|
|
2714
|
+
battle.rng.damage(player)
|
|
2730
2715
|
else roll: {
|
|
2731
2716
|
if (showdown) break :roll battle.rng.range(u8, 217, 256);
|
|
2732
2717
|
while (true) {
|
|
@@ -2740,21 +2725,27 @@ pub const Rolls = struct {
|
|
|
2740
2725
|
return roll;
|
|
2741
2726
|
}
|
|
2742
2727
|
|
|
2743
|
-
|
|
2728
|
+
const uN = if (pkmn.options.miss) u8 else u9;
|
|
2729
|
+
fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) !bool {
|
|
2730
|
+
const acc: uN = if (pkmn.options.miss) accuracy else @as(uN, accuracy) + 1;
|
|
2744
2731
|
const ok = if (options.calc.overridden(player, .hit)) |val|
|
|
2745
2732
|
val == .true
|
|
2733
|
+
else if (@hasDecl(@TypeOf(battle.rng), "hit"))
|
|
2734
|
+
battle.rng.hit(player, acc)
|
|
2746
2735
|
else if (showdown)
|
|
2747
|
-
battle.rng.chance(
|
|
2736
|
+
battle.rng.chance(uN, acc, 256)
|
|
2748
2737
|
else
|
|
2749
|
-
battle.rng.next() <
|
|
2738
|
+
battle.rng.next() < acc;
|
|
2750
2739
|
|
|
2751
|
-
options.chance.hit(ok,
|
|
2740
|
+
try options.chance.hit(player, ok, acc);
|
|
2752
2741
|
return ok;
|
|
2753
2742
|
}
|
|
2754
2743
|
|
|
2755
2744
|
fn confused(battle: anytype, player: Player, options: anytype) !bool {
|
|
2756
2745
|
const cfz = if (options.calc.overridden(player, .confused)) |val|
|
|
2757
2746
|
val == .true
|
|
2747
|
+
else if (@hasDecl(@TypeOf(battle.rng), "confused"))
|
|
2748
|
+
battle.rng.confused(player)
|
|
2758
2749
|
else if (showdown)
|
|
2759
2750
|
!battle.rng.chance(u8, 128, 256)
|
|
2760
2751
|
else
|
|
@@ -2767,6 +2758,8 @@ pub const Rolls = struct {
|
|
|
2767
2758
|
fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
|
|
2768
2759
|
const par = if (options.calc.overridden(player, .paralyzed)) |val|
|
|
2769
2760
|
val == .true
|
|
2761
|
+
else if (@hasDecl(@TypeOf(battle.rng), "paralyzed"))
|
|
2762
|
+
battle.rng.paralyzed(player)
|
|
2770
2763
|
else if (showdown)
|
|
2771
2764
|
battle.rng.chance(u8, 63, 256)
|
|
2772
2765
|
else
|
|
@@ -2779,6 +2772,8 @@ pub const Rolls = struct {
|
|
|
2779
2772
|
fn confusionChance(battle: anytype, player: Player, options: anytype) !bool {
|
|
2780
2773
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2781
2774
|
val == .true
|
|
2775
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2776
|
+
battle.rng.secondaryChance(player, 25)
|
|
2782
2777
|
else if (showdown)
|
|
2783
2778
|
battle.rng.chance(u8, 25, 256)
|
|
2784
2779
|
else
|
|
@@ -2793,6 +2788,8 @@ pub const Rolls = struct {
|
|
|
2793
2788
|
|
|
2794
2789
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2795
2790
|
val == .true
|
|
2791
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2792
|
+
battle.rng.secondaryChance(player, rate)
|
|
2796
2793
|
else if (showdown)
|
|
2797
2794
|
battle.rng.chance(u8, rate, 256)
|
|
2798
2795
|
else
|
|
@@ -2807,6 +2804,8 @@ pub const Rolls = struct {
|
|
|
2807
2804
|
|
|
2808
2805
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2809
2806
|
val == .true
|
|
2807
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2808
|
+
battle.rng.secondaryChance(player, rate)
|
|
2810
2809
|
else if (showdown)
|
|
2811
2810
|
battle.rng.chance(u8, rate, 256)
|
|
2812
2811
|
else
|
|
@@ -2819,6 +2818,8 @@ pub const Rolls = struct {
|
|
|
2819
2818
|
fn unboost(battle: anytype, player: Player, options: anytype) !bool {
|
|
2820
2819
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2821
2820
|
val == .true
|
|
2821
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2822
|
+
battle.rng.secondaryChance(player, 85)
|
|
2822
2823
|
else if (showdown)
|
|
2823
2824
|
battle.rng.chance(u8, 85, 256)
|
|
2824
2825
|
else
|
|
@@ -2831,6 +2832,8 @@ pub const Rolls = struct {
|
|
|
2831
2832
|
fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
2832
2833
|
const move: Move = if (options.calc.overridden(player, .metronome)) |val|
|
|
2833
2834
|
val
|
|
2835
|
+
else if (@hasDecl(@TypeOf(battle.rng), "metronome"))
|
|
2836
|
+
battle.rng.metronome(player)
|
|
2834
2837
|
else if (showdown)
|
|
2835
2838
|
Move.METRONOME[battle.rng.range(u8, 0, Move.METRONOME.len)]
|
|
2836
2839
|
else move: {
|
|
@@ -2849,6 +2852,8 @@ pub const Rolls = struct {
|
|
|
2849
2852
|
fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
|
|
2850
2853
|
const power = if (options.calc.overridden(player, .psywave)) |val|
|
|
2851
2854
|
val - 1
|
|
2855
|
+
else if (@hasDecl(@TypeOf(battle.rng), "psywave"))
|
|
2856
|
+
battle.rng.psywave(player)
|
|
2852
2857
|
else if (showdown)
|
|
2853
2858
|
battle.rng.range(u8, 0, max)
|
|
2854
2859
|
else power: {
|
|
@@ -2866,6 +2871,8 @@ pub const Rolls = struct {
|
|
|
2866
2871
|
fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2867
2872
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2868
2873
|
@intCast(val)
|
|
2874
|
+
else if (@hasDecl(@TypeOf(battle.rng), "sleepDuration"))
|
|
2875
|
+
battle.rng.sleepDuration(player)
|
|
2869
2876
|
else if (showdown)
|
|
2870
2877
|
battle.rng.range(u3, 1, 8)
|
|
2871
2878
|
else duration: {
|
|
@@ -2876,46 +2883,52 @@ pub const Rolls = struct {
|
|
|
2876
2883
|
};
|
|
2877
2884
|
|
|
2878
2885
|
assert(duration >= 1 and duration <= 7);
|
|
2879
|
-
options.chance.duration(
|
|
2886
|
+
options.chance.duration(player, duration);
|
|
2880
2887
|
return duration;
|
|
2881
2888
|
}
|
|
2882
2889
|
|
|
2883
2890
|
fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
|
|
2884
2891
|
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2885
2892
|
val
|
|
2893
|
+
else if (@hasDecl(@TypeOf(battle.rng), "disableDuration"))
|
|
2894
|
+
battle.rng.disableDuration(player)
|
|
2886
2895
|
else if (showdown)
|
|
2887
2896
|
battle.rng.range(u4, 1, 9)
|
|
2888
2897
|
else
|
|
2889
2898
|
@intCast((battle.rng.next() & 7) + 1);
|
|
2890
2899
|
|
|
2891
2900
|
assert(duration >= 1 and duration <= 8);
|
|
2892
|
-
options.chance.duration(
|
|
2901
|
+
options.chance.duration(player, duration);
|
|
2893
2902
|
return duration;
|
|
2894
2903
|
}
|
|
2895
2904
|
|
|
2896
|
-
fn confusionDuration(battle: anytype, player: Player,
|
|
2905
|
+
fn confusionDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2897
2906
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2898
2907
|
@intCast(val)
|
|
2908
|
+
else if (@hasDecl(@TypeOf(battle.rng), "confusionDuration"))
|
|
2909
|
+
battle.rng.confusionDuration(player)
|
|
2899
2910
|
else if (showdown)
|
|
2900
2911
|
battle.rng.range(u3, 2, 6)
|
|
2901
2912
|
else
|
|
2902
2913
|
@intCast((battle.rng.next() & 3) + 2);
|
|
2903
2914
|
|
|
2904
2915
|
assert(duration >= 2 and duration <= 5);
|
|
2905
|
-
options.chance.duration(
|
|
2916
|
+
options.chance.duration(player, duration);
|
|
2906
2917
|
return duration;
|
|
2907
2918
|
}
|
|
2908
2919
|
|
|
2909
2920
|
fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2910
2921
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2911
2922
|
@intCast(val)
|
|
2923
|
+
else if (@hasDecl(@TypeOf(battle.rng), "attackingDuration"))
|
|
2924
|
+
battle.rng.attackingDuration(player)
|
|
2912
2925
|
else if (showdown)
|
|
2913
2926
|
battle.rng.range(u3, 2, 4)
|
|
2914
2927
|
else
|
|
2915
2928
|
@intCast((battle.rng.next() & 1) + 2);
|
|
2916
2929
|
|
|
2917
2930
|
assert(duration >= 2 and duration <= 3);
|
|
2918
|
-
options.chance.duration(
|
|
2931
|
+
options.chance.duration(player, duration);
|
|
2919
2932
|
return duration;
|
|
2920
2933
|
}
|
|
2921
2934
|
|
|
@@ -2930,6 +2943,8 @@ pub const Rolls = struct {
|
|
|
2930
2943
|
const roll = if (effect == .Binding) .duration else .multi_hit;
|
|
2931
2944
|
const n: u3 = if (options.calc.overridden(player, roll)) |val|
|
|
2932
2945
|
@intCast(val)
|
|
2946
|
+
else if (@hasDecl(@TypeOf(battle.rng), "distribution"))
|
|
2947
|
+
battle.rng.distribution(player)
|
|
2933
2948
|
else if (showdown)
|
|
2934
2949
|
DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
|
|
2935
2950
|
else n: {
|
|
@@ -2939,7 +2954,7 @@ pub const Rolls = struct {
|
|
|
2939
2954
|
|
|
2940
2955
|
assert(n >= 2 and n <= 5);
|
|
2941
2956
|
if (effect == .Binding) {
|
|
2942
|
-
options.chance.duration(
|
|
2957
|
+
options.chance.duration(player, n);
|
|
2943
2958
|
} else {
|
|
2944
2959
|
try options.chance.multiHit(player, n);
|
|
2945
2960
|
}
|
|
@@ -2962,7 +2977,9 @@ pub const Rolls = struct {
|
|
|
2962
2977
|
else
|
|
2963
2978
|
null;
|
|
2964
2979
|
|
|
2965
|
-
const slot: u4 = overridden orelse
|
|
2980
|
+
const slot: u4 = overridden orelse if (@hasDecl(@TypeOf(battle.rng), "moveSlot"))
|
|
2981
|
+
battle.rng.moveSlot(player, moves, check_pp)
|
|
2982
|
+
else slot: {
|
|
2966
2983
|
if (showdown) {
|
|
2967
2984
|
if (check_pp == 0) {
|
|
2968
2985
|
var i: usize = moves.len;
|
|
@@ -3042,11 +3059,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3042
3059
|
const side = battle.side(player);
|
|
3043
3060
|
const foe = battle.foe(player);
|
|
3044
3061
|
|
|
3045
|
-
|
|
3062
|
+
const active = &side.active;
|
|
3046
3063
|
const stored = side.stored();
|
|
3047
3064
|
|
|
3048
3065
|
// While players are not given any input options on the cartridge in these cases,
|
|
3049
|
-
// Pokémon Showdown instead produces a list with a single move that must be chosen
|
|
3066
|
+
// Pokémon Showdown instead produces a list with a single move that must be chosen
|
|
3050
3067
|
//
|
|
3051
3068
|
// Given that no input is allowed on the cartridge 'Pass' seems like it would be logical
|
|
3052
3069
|
// here when not in compatibility mode, but the engine needs to be able to differentiate
|
|
@@ -3054,7 +3071,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3054
3071
|
// passing due to being forced into using a move. Instead of introducing another option
|
|
3055
3072
|
// we simply repurpose Move with no move slot, even though pedantically this is not
|
|
3056
3073
|
// strictly correct as the player would not have been presented the option to move or
|
|
3057
|
-
// switch at all
|
|
3074
|
+
// switch at all
|
|
3058
3075
|
if (isForced(active)) {
|
|
3059
3076
|
out[n] = .{ .type = .Move, .data = @intFromBool(showdown) };
|
|
3060
3077
|
n += 1;
|
|
@@ -3073,7 +3090,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3073
3090
|
// On the cartridge, all of these happen after "FIGHT" (indicating you are not
|
|
3074
3091
|
// switching) but before you are allowed to select a move. Pokémon Showdown instead
|
|
3075
3092
|
// either disables all other moves in the case of limited or requires you to select a
|
|
3076
|
-
// move normally if sleeping/frozen/bound
|
|
3093
|
+
// move normally if sleeping/frozen/bound
|
|
3077
3094
|
if (!showdown and (limited or foe.active.volatiles.Binding or
|
|
3078
3095
|
Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP)))
|
|
3079
3096
|
{
|
|
@@ -3086,7 +3103,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3086
3103
|
// Pokémon Showdown handles Bide and Binding moves by checking if the move in question
|
|
3087
3104
|
// is present in the Pokémon's moveset (which means moves called via Metronome / Mirror
|
|
3088
3105
|
// Move will not result in forcing the subsequent use unless the user also had the
|
|
3089
|
-
// proc-ed move in their moveset) and disabling all other moves
|
|
3106
|
+
// proc-ed move in their moveset) and disabling all other moves
|
|
3090
3107
|
if (limited) {
|
|
3091
3108
|
assert(showdown);
|
|
3092
3109
|
assert(side.last_selected_move != .None);
|