@pkmn/engine 0.1.0-dev.c42deb42 → 0.1.0-dev.c544e10e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/build/pkg/data/layout.json +2 -2
- package/build/pkg/gen1.js +2 -2
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/protocol.js +1 -1
- package/build/pkg/protocol.js.map +1 -1
- package/build/tools/debug.js +3 -1
- package/build/tools/debug.js.map +1 -1
- package/build.zig +19 -13
- package/package.json +19 -16
- package/src/lib/common/data.zig +4 -4
- package/src/lib/common/protocol.zig +345 -321
- package/src/lib/common/rational.zig +4 -4
- package/src/lib/common/rng.zig +9 -0
- package/src/lib/gen1/README.md +28 -28
- package/src/lib/gen1/calc.zig +2 -2
- package/src/lib/gen1/chance.zig +4 -4
- package/src/lib/gen1/data/moves.zig +10 -10
- package/src/lib/gen1/data/species.zig +2 -2
- package/src/lib/gen1/data/types.zig +5 -5
- package/src/lib/gen1/data.zig +20 -20
- package/src/lib/gen1/helpers.zig +13 -13
- package/src/lib/gen1/mechanics.zig +251 -255
- package/src/lib/gen2/README.md +1 -2
- package/src/lib/gen2/calc.zig +143 -0
- package/src/lib/gen2/chance.zig +1314 -0
- package/src/lib/gen2/data/items.zig +3 -3
- package/src/lib/gen2/data/moves.zig +5 -5
- package/src/lib/gen2/data/species.zig +1 -1
- package/src/lib/gen2/data/types.zig +12 -11
- package/src/lib/gen2/data.zig +20 -30
- package/src/lib/gen2/generated.zig +808 -0
- package/src/lib/gen2/mechanics.zig +1751 -836
|
@@ -13,15 +13,16 @@ const assert = std.debug.assert;
|
|
|
13
13
|
|
|
14
14
|
const expectEqual = std.testing.expectEqual;
|
|
15
15
|
|
|
16
|
+
const showdown = pkmn.options.showdown;
|
|
17
|
+
|
|
16
18
|
const Choice = common.Choice;
|
|
17
19
|
const ID = common.ID;
|
|
18
20
|
const Player = common.Player;
|
|
19
21
|
const Result = common.Result;
|
|
20
22
|
|
|
21
|
-
const showdown = pkmn.options.showdown;
|
|
22
|
-
|
|
23
23
|
const Boost = protocol.Boost;
|
|
24
24
|
const Damage = protocol.Damage;
|
|
25
|
+
const Fail = protocol.Fail;
|
|
25
26
|
const Heal = protocol.Heal;
|
|
26
27
|
|
|
27
28
|
const Gen12 = rng.Gen12;
|
|
@@ -195,7 +196,7 @@ fn selectMove(
|
|
|
195
196
|
if (choice.data == 0) {
|
|
196
197
|
const struggle = ok: {
|
|
197
198
|
for (side.active.moves, 0..) |move, i| {
|
|
198
|
-
if (move.pp > 0 and volatiles.
|
|
199
|
+
if (move.pp > 0 and volatiles.disable_move != i + 1) break :ok false;
|
|
199
200
|
}
|
|
200
201
|
break :ok true;
|
|
201
202
|
};
|
|
@@ -214,7 +215,7 @@ fn saveMove(battle: anytype, player: Player, choice: ?Choice) void {
|
|
|
214
215
|
if (c.data == 0) {
|
|
215
216
|
side.last_selected_move = .Struggle;
|
|
216
217
|
} else {
|
|
217
|
-
assert(showdown or side.active.volatiles.
|
|
218
|
+
assert(showdown or side.active.volatiles.disable_move != c.data);
|
|
218
219
|
const move = side.active.move(c.data);
|
|
219
220
|
// You cannot *select* a move with 0 PP (except on Pokémon Showdown where that is
|
|
220
221
|
// sometimes required...), but a 0 PP move can be used automatically
|
|
@@ -255,14 +256,14 @@ fn switchIn(battle: anytype, player: Player, slot: u8, initial: bool, options: a
|
|
|
255
256
|
|
|
256
257
|
foe.active.volatiles.Binding = false;
|
|
257
258
|
|
|
258
|
-
try options.log.switched(battle.active(player), incoming);
|
|
259
|
+
try options.log.switched(.{ battle.active(player), incoming });
|
|
259
260
|
options.chance.switched(player, side.order[0], out);
|
|
260
261
|
|
|
261
262
|
if (showdown and incoming.status == Status.TOX) {
|
|
262
263
|
incoming.status = Status.init(.PSN);
|
|
263
264
|
// Technically, Pokémon Showdown adds these after *both* Pokémon have switched, but we'd
|
|
264
265
|
// rather not clutter up turnOrder just for this (incorrect) log message
|
|
265
|
-
try options.log.status(battle.active(player), incoming.status, .Silent);
|
|
266
|
+
try options.log.status(.{ battle.active(player), incoming.status, .Silent });
|
|
266
267
|
}
|
|
267
268
|
}
|
|
268
269
|
|
|
@@ -406,7 +407,7 @@ fn executeMove(
|
|
|
406
407
|
if (side.last_selected_move == .SKIP_TURN) {
|
|
407
408
|
assert(!showdown);
|
|
408
409
|
if (battle.foe(player).active.volatiles.Binding) {
|
|
409
|
-
try options.log.cant(battle.active(player), .Bound);
|
|
410
|
+
try options.log.cant(.{ battle.active(player), .Bound });
|
|
410
411
|
}
|
|
411
412
|
return null;
|
|
412
413
|
}
|
|
@@ -496,23 +497,23 @@ fn beforeMove(
|
|
|
496
497
|
}
|
|
497
498
|
|
|
498
499
|
if (duration == 0) {
|
|
499
|
-
try log.curestatus(ident, before, .Message);
|
|
500
|
+
try log.curestatus(.{ ident, before, .Message });
|
|
500
501
|
stored.status = 0; // clears EXT if present
|
|
501
502
|
} else {
|
|
502
|
-
try log.cant(ident, .Sleep);
|
|
503
|
+
try log.cant(.{ ident, .Sleep });
|
|
503
504
|
}
|
|
504
505
|
side.last_used_move = .None;
|
|
505
506
|
return .done;
|
|
506
507
|
}
|
|
507
508
|
|
|
508
509
|
if (Status.is(stored.status, .FRZ)) {
|
|
509
|
-
try log.cant(ident, .Freeze);
|
|
510
|
+
try log.cant(.{ ident, .Freeze });
|
|
510
511
|
side.last_used_move = .None;
|
|
511
512
|
return .done;
|
|
512
513
|
}
|
|
513
514
|
|
|
514
515
|
if (skip or foe.active.volatiles.Binding) {
|
|
515
|
-
try log.cant(ident, .Bound);
|
|
516
|
+
try log.cant(.{ ident, .Bound });
|
|
516
517
|
return .done;
|
|
517
518
|
}
|
|
518
519
|
|
|
@@ -520,27 +521,27 @@ fn beforeMove(
|
|
|
520
521
|
// Pokémon Showdown doesn't clear Flinch until its imaginary "residual" phase, meaning
|
|
521
522
|
// Pokémon can sometimes flinch multiple times from the same original hit
|
|
522
523
|
if (!showdown) volatiles.Flinch = false;
|
|
523
|
-
try log.cant(ident, .Flinch);
|
|
524
|
+
try log.cant(.{ ident, .Flinch });
|
|
524
525
|
return .done;
|
|
525
526
|
}
|
|
526
527
|
|
|
527
528
|
if (volatiles.Recharging) {
|
|
528
529
|
volatiles.Recharging = false;
|
|
529
|
-
try log.cant(ident, .Recharge);
|
|
530
|
+
try log.cant(.{ ident, .Recharge });
|
|
530
531
|
return .done;
|
|
531
532
|
}
|
|
532
533
|
|
|
533
|
-
if (volatiles.
|
|
534
|
+
if (volatiles.disable_duration > 0) {
|
|
534
535
|
if (options.calc.modify(player, .disable)) |extend| {
|
|
535
|
-
if (!extend) volatiles.
|
|
536
|
+
if (!extend) volatiles.disable_duration = 0;
|
|
536
537
|
} else {
|
|
537
|
-
volatiles.
|
|
538
|
+
volatiles.disable_duration -= 1;
|
|
538
539
|
}
|
|
539
|
-
try options.chance.disable(player, volatiles.
|
|
540
|
+
try options.chance.disable(player, volatiles.disable_duration);
|
|
540
541
|
|
|
541
|
-
if (volatiles.
|
|
542
|
-
volatiles.
|
|
543
|
-
try log.end(ident, .Disable);
|
|
542
|
+
if (volatiles.disable_duration == 0) {
|
|
543
|
+
volatiles.disable_move = 0;
|
|
544
|
+
try log.end(.{ ident, .Disable });
|
|
544
545
|
}
|
|
545
546
|
}
|
|
546
547
|
// Pokémon Showdown's disable condition has a single onBeforeMove handler
|
|
@@ -565,9 +566,9 @@ fn beforeMove(
|
|
|
565
566
|
|
|
566
567
|
if (volatiles.confusion == 0) {
|
|
567
568
|
volatiles.Confusion = false;
|
|
568
|
-
try log.end(ident, .Confusion);
|
|
569
|
+
try log.end(.{ ident, .Confusion });
|
|
569
570
|
} else {
|
|
570
|
-
try log.activate(ident, .Confusion);
|
|
571
|
+
try log.activate(.{ ident, .Confusion });
|
|
571
572
|
|
|
572
573
|
if (try Rolls.confused(battle, player, options)) {
|
|
573
574
|
assert(!volatiles.MultiHit);
|
|
@@ -618,7 +619,7 @@ fn beforeMove(
|
|
|
618
619
|
volatiles.Binding = false;
|
|
619
620
|
options.chance.clearDurations(player, null);
|
|
620
621
|
// GLITCH: Invulnerable is not cleared, resulting in permanent Fly/Dig invulnerability
|
|
621
|
-
try log.cant(ident, .Paralysis);
|
|
622
|
+
try log.cant(.{ ident, .Paralysis });
|
|
622
623
|
return .done;
|
|
623
624
|
}
|
|
624
625
|
|
|
@@ -644,18 +645,18 @@ fn beforeMove(
|
|
|
644
645
|
try options.chance.attacking(player, volatiles.attacks);
|
|
645
646
|
|
|
646
647
|
if (volatiles.attacks != 0) {
|
|
647
|
-
try log.activate(ident, .Bide);
|
|
648
|
+
try log.activate(.{ ident, .Bide });
|
|
648
649
|
return .done;
|
|
649
650
|
}
|
|
650
651
|
|
|
651
652
|
volatiles.Bide = false;
|
|
652
|
-
try log.end(ident, .Bide);
|
|
653
|
+
try log.end(.{ ident, .Bide });
|
|
653
654
|
|
|
654
655
|
battle.last_damage = volatiles.state *% 2;
|
|
655
656
|
volatiles.state = 0;
|
|
656
657
|
|
|
657
658
|
if (battle.last_damage == 0) {
|
|
658
|
-
try log.fail(ident, .None);
|
|
659
|
+
try log.fail(.{ ident, .None });
|
|
659
660
|
return .done;
|
|
660
661
|
}
|
|
661
662
|
|
|
@@ -685,15 +686,15 @@ fn beforeMove(
|
|
|
685
686
|
try options.chance.attacking(player, volatiles.attacks);
|
|
686
687
|
|
|
687
688
|
if (!showdown and handleThrashing(battle, active, player, options)) {
|
|
688
|
-
try log.start(battle.active(player), .ConfusionSilent);
|
|
689
|
+
try log.start(.{ battle.active(player), .ConfusionSilent });
|
|
689
690
|
}
|
|
690
|
-
try log.move(ident, side.last_selected_move, battle.active(player.foe()));
|
|
691
|
+
try log.move(.{ ident, side.last_selected_move, battle.active(player.foe()) });
|
|
691
692
|
if (showdown) {
|
|
692
693
|
// This shouldn't actually set last_used_move, but Pokémon Showdown sets last
|
|
693
694
|
// used in useMove and doesn't have the notion of skipping canMove semantics
|
|
694
695
|
side.last_used_move = side.last_selected_move;
|
|
695
696
|
if (handleThrashing(battle, active, player, options)) {
|
|
696
|
-
try log.start(battle.active(player), .ConfusionSilent);
|
|
697
|
+
try log.start(.{ battle.active(player), .ConfusionSilent });
|
|
697
698
|
}
|
|
698
699
|
}
|
|
699
700
|
return .skip_can;
|
|
@@ -709,7 +710,7 @@ fn beforeMove(
|
|
|
709
710
|
}
|
|
710
711
|
try options.chance.binding(player, volatiles.attacks);
|
|
711
712
|
|
|
712
|
-
try log.move(
|
|
713
|
+
try log.move(.{ ident, side.last_selected_move, battle.active(player.foe()) });
|
|
713
714
|
if (showdown or battle.last_damage != 0) {
|
|
714
715
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
715
716
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -742,7 +743,7 @@ fn canMove(
|
|
|
742
743
|
side.active.volatiles.Charging = false;
|
|
743
744
|
side.active.volatiles.Invulnerable = false;
|
|
744
745
|
} else if (move.effect == .Charge) {
|
|
745
|
-
try options.log.
|
|
746
|
+
try options.log.move(.{ player_ident, side.last_selected_move, ID{}, from });
|
|
746
747
|
setCounterable(battle, player, side, move);
|
|
747
748
|
try Effects.charge(battle, player, options);
|
|
748
749
|
return false;
|
|
@@ -752,7 +753,7 @@ fn canMove(
|
|
|
752
753
|
if (!skip) decrementPP(side, mslot, auto);
|
|
753
754
|
|
|
754
755
|
const target = if (move.target == .Self) player else player.foe();
|
|
755
|
-
try options.log.
|
|
756
|
+
try options.log.move(.{ player_ident, side.last_selected_move, battle.active(target), from });
|
|
756
757
|
setCounterable(battle, player, side, move);
|
|
757
758
|
|
|
758
759
|
if (move.effect.onBegin()) {
|
|
@@ -790,12 +791,14 @@ fn decrementPP(side: *Side, mslot: u4, auto: bool) void {
|
|
|
790
791
|
assert(!volatiles.Rage and !volatiles.Thrashing and !volatiles.MultiHit);
|
|
791
792
|
if (volatiles.Bide) return;
|
|
792
793
|
|
|
793
|
-
|
|
794
|
-
|
|
794
|
+
var move_slot = active.move(mslot);
|
|
795
|
+
assert(move_slot.pp > 0 or auto);
|
|
796
|
+
move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
|
|
795
797
|
if (volatiles.Transform) return;
|
|
796
798
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
+
move_slot = side.stored().move(mslot);
|
|
800
|
+
assert(move_slot.pp > 0 or auto);
|
|
801
|
+
move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
|
|
799
802
|
assert(active.move(mslot).pp == side.stored().move(mslot).pp);
|
|
800
803
|
}
|
|
801
804
|
|
|
@@ -927,16 +930,16 @@ fn doMove(
|
|
|
927
930
|
ohko = (!showdown or (!immune and !invulnerable)) and
|
|
928
931
|
move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe;
|
|
929
932
|
if (ohko) {
|
|
930
|
-
try log.immune(foe_ident, .OHKO);
|
|
933
|
+
try log.immune(.{ foe_ident, .OHKO });
|
|
931
934
|
} else if (immune and !invulnerable and (!showdown or move.effect != .Binding)) {
|
|
932
|
-
try log.immune(foe_ident, .None);
|
|
935
|
+
try log.immune(.{ foe_ident, .None });
|
|
933
936
|
} else if (mist) {
|
|
934
|
-
if (!foe.active.volatiles.Substitute) try log.activate(foe_ident, .Mist);
|
|
935
|
-
try log.fail(foe_ident, .None);
|
|
937
|
+
if (!foe.active.volatiles.Substitute) try log.activate(.{ foe_ident, .Mist });
|
|
938
|
+
try log.fail(.{ foe_ident, .None });
|
|
936
939
|
} else {
|
|
937
940
|
if (showdown or !zero) try options.chance.commit(player, .miss);
|
|
938
|
-
try log.lastmiss();
|
|
939
|
-
try log.miss(battle.active(player));
|
|
941
|
+
try log.lastmiss(.{});
|
|
942
|
+
try log.miss(.{battle.active(player)});
|
|
940
943
|
}
|
|
941
944
|
if (move.effect == .JumpKick) {
|
|
942
945
|
// Recoil is supposed to be damage/8 but damage will always be 0 here
|
|
@@ -972,15 +975,15 @@ fn doMove(
|
|
|
972
975
|
var hit: u4 = 0;
|
|
973
976
|
while (hit < hits) {
|
|
974
977
|
if (hit == 0) {
|
|
975
|
-
if (crit) try log.crit(battle.active(player.foe()));
|
|
978
|
+
if (crit) try log.crit(.{battle.active(player.foe())});
|
|
976
979
|
if (effectiveness > Effectiveness.neutral) {
|
|
977
|
-
try log.supereffective(battle.active(player.foe()));
|
|
980
|
+
try log.supereffective(.{battle.active(player.foe())});
|
|
978
981
|
} else if (effectiveness < Effectiveness.neutral) {
|
|
979
|
-
try log.resisted(battle.active(player.foe()));
|
|
982
|
+
try log.resisted(.{battle.active(player.foe())});
|
|
980
983
|
}
|
|
981
984
|
}
|
|
982
985
|
if (!skip) nullified = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
983
|
-
if (hit == 0 and ohko) try log.ohko();
|
|
986
|
+
if (hit == 0 and ohko) try log.ohko(.{});
|
|
984
987
|
hit += 1;
|
|
985
988
|
if (foe.stored().hp == 0) break;
|
|
986
989
|
if (!late and (!sub or move.effect == .Explode)) {
|
|
@@ -997,7 +1000,7 @@ fn doMove(
|
|
|
997
1000
|
if (showdown and move.effect == .Twineedle and !nullified and foe.stored().hp > 0) {
|
|
998
1001
|
try Effects.poison(battle, player, Move.get(.PoisonSting), options);
|
|
999
1002
|
}
|
|
1000
|
-
try log.hitcount(battle.active(player.foe()), hit);
|
|
1003
|
+
try log.hitcount(.{ battle.active(player.foe()), hit });
|
|
1001
1004
|
} else if (showdown) {
|
|
1002
1005
|
// This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
|
|
1003
1006
|
if (move.effect == .Binding) {
|
|
@@ -1007,9 +1010,9 @@ fn doMove(
|
|
|
1007
1010
|
assert(foe.stored().hp > 0);
|
|
1008
1011
|
// Pokémon Showdown logs |-damage| here instead of |-immune| because logic...
|
|
1009
1012
|
if (sub) {
|
|
1010
|
-
try log.activate(battle.active(player.foe()), .Substitute);
|
|
1013
|
+
try log.activate(.{ battle.active(player.foe()), .Substitute });
|
|
1011
1014
|
} else {
|
|
1012
|
-
try log.damage(battle.active(player.foe()), foe.stored(), .None);
|
|
1015
|
+
try log.damage(.{ battle.active(player.foe()), foe.stored(), .None });
|
|
1013
1016
|
try buildRage(battle, player.foe(), options);
|
|
1014
1017
|
}
|
|
1015
1018
|
return null;
|
|
@@ -1207,7 +1210,7 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1207
1210
|
const foe = battle.foe(player);
|
|
1208
1211
|
|
|
1209
1212
|
if (battle.last_damage == 0) {
|
|
1210
|
-
try options.log.fail(battle.active(player), .None);
|
|
1213
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1211
1214
|
return null;
|
|
1212
1215
|
}
|
|
1213
1216
|
|
|
@@ -1225,14 +1228,14 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1225
1228
|
foe_last_selected_move.type == .Fighting);
|
|
1226
1229
|
|
|
1227
1230
|
if (!used and !selected) {
|
|
1228
|
-
try options.log.fail(battle.active(player), .None);
|
|
1231
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1229
1232
|
return null;
|
|
1230
1233
|
}
|
|
1231
1234
|
|
|
1232
1235
|
if (!used or !selected) {
|
|
1233
1236
|
// GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
|
|
1234
1237
|
if (!showdown) return Result.Error;
|
|
1235
|
-
try options.log.fail(battle.active(player), .None);
|
|
1238
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1236
1239
|
return null;
|
|
1237
1240
|
}
|
|
1238
1241
|
|
|
@@ -1268,20 +1271,20 @@ fn applyDamage(
|
|
|
1268
1271
|
subbed.active.volatiles.substitute = 0;
|
|
1269
1272
|
subbed.active.volatiles.Substitute = false;
|
|
1270
1273
|
// battle.last_damage is not updated with the amount of HP the Substitute had
|
|
1271
|
-
try options.log.end(battle.active(sub_player), .Substitute);
|
|
1274
|
+
try options.log.end(.{ battle.active(sub_player), .Substitute });
|
|
1272
1275
|
options.calc.capped(target_player);
|
|
1273
1276
|
return true;
|
|
1274
1277
|
} else {
|
|
1275
1278
|
// Safe to truncate since less than subbed.volatiles.substitute which is a u8
|
|
1276
1279
|
subbed.active.volatiles.substitute -= @intCast(battle.last_damage);
|
|
1277
|
-
try options.log.activate(battle.active(sub_player), .Substitute);
|
|
1280
|
+
try options.log.activate(.{ battle.active(sub_player), .Substitute });
|
|
1278
1281
|
return false;
|
|
1279
1282
|
}
|
|
1280
1283
|
}
|
|
1281
1284
|
|
|
1282
1285
|
if (battle.last_damage > target.stored().hp) battle.last_damage = target.stored().hp;
|
|
1283
1286
|
target.stored().hp -= battle.last_damage;
|
|
1284
|
-
try options.log.damage(battle.active(target_player), target.stored(), reason);
|
|
1287
|
+
try options.log.damage(.{ battle.active(target_player), target.stored(), reason });
|
|
1285
1288
|
return false;
|
|
1286
1289
|
}
|
|
1287
1290
|
|
|
@@ -1300,7 +1303,7 @@ fn mirrorMove(
|
|
|
1300
1303
|
side.last_selected_move = foe.last_used_move;
|
|
1301
1304
|
|
|
1302
1305
|
if (foe.last_used_move == .None or foe.last_used_move == .MirrorMove) {
|
|
1303
|
-
try options.log.fail(battle.active(player), .None);
|
|
1306
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1304
1307
|
return null;
|
|
1305
1308
|
} else if (!showdown or foe.last_used_move != .Struggle) {
|
|
1306
1309
|
incrementPP(side, mslot);
|
|
@@ -1342,12 +1345,12 @@ fn checkHit(battle: anytype, player: Player, move: Move.Data, options: anytype)
|
|
|
1342
1345
|
if (mist) {
|
|
1343
1346
|
assert(!showdown);
|
|
1344
1347
|
const foe_ident = battle.active(player.foe());
|
|
1345
|
-
try options.log.activate(foe_ident, .Mist);
|
|
1346
|
-
try options.log.fail(foe_ident, .None);
|
|
1348
|
+
try options.log.activate(.{ foe_ident, .Mist });
|
|
1349
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1347
1350
|
} else {
|
|
1348
1351
|
try options.chance.commit(player, .miss);
|
|
1349
|
-
try options.log.lastmiss();
|
|
1350
|
-
try options.log.miss(battle.active(player));
|
|
1352
|
+
try options.log.lastmiss(.{});
|
|
1353
|
+
try options.log.miss(.{battle.active(player)});
|
|
1351
1354
|
}
|
|
1352
1355
|
|
|
1353
1356
|
return false;
|
|
@@ -1438,9 +1441,8 @@ fn checkFaint(
|
|
|
1438
1441
|
const foe_fainted = foe.stored().hp == 0;
|
|
1439
1442
|
|
|
1440
1443
|
const player_out = findFirstAlive(side) == 0;
|
|
1441
|
-
const foe_out = findFirstAlive(foe) == 0;
|
|
1442
|
-
const
|
|
1443
|
-
const more = tie or player_out or foe_out;
|
|
1444
|
+
const foe_out = foe_fainted and findFirstAlive(foe) == 0;
|
|
1445
|
+
const more = player_out or foe_out;
|
|
1444
1446
|
|
|
1445
1447
|
if (try faint(battle, player, !(more or foe_fainted), options)) |r| return r;
|
|
1446
1448
|
if (foe_fainted) if (try faint(battle, player.foe(), !more, options)) |r| return r;
|
|
@@ -1448,14 +1450,14 @@ fn checkFaint(
|
|
|
1448
1450
|
assert(!side.active.volatiles.MultiHit);
|
|
1449
1451
|
assert(!foe.active.volatiles.MultiHit);
|
|
1450
1452
|
|
|
1451
|
-
if (
|
|
1452
|
-
try options.log.tie();
|
|
1453
|
+
if (player_out and foe_out) {
|
|
1454
|
+
try options.log.tie(.{});
|
|
1453
1455
|
return Result.Tie;
|
|
1454
1456
|
} else if (player_out) {
|
|
1455
|
-
try options.log.win(player.foe());
|
|
1457
|
+
try options.log.win(.{player.foe()});
|
|
1456
1458
|
return if (player == .P1) Result.Lose else Result.Win;
|
|
1457
1459
|
} else if (foe_out) {
|
|
1458
|
-
try options.log.win(player);
|
|
1460
|
+
try options.log.win(.{player});
|
|
1459
1461
|
return if (player == .P1) Result.Win else Result.Lose;
|
|
1460
1462
|
}
|
|
1461
1463
|
|
|
@@ -1497,7 +1499,7 @@ fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result
|
|
|
1497
1499
|
side.stored().stats.spe;
|
|
1498
1500
|
}
|
|
1499
1501
|
|
|
1500
|
-
try options.log.faint(battle.active(player), done);
|
|
1502
|
+
try options.log.faint(.{ battle.active(player), done });
|
|
1501
1503
|
options.calc.capped(player);
|
|
1502
1504
|
return null;
|
|
1503
1505
|
}
|
|
@@ -1519,8 +1521,8 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1519
1521
|
|
|
1520
1522
|
if (!showdown or damage > 0) {
|
|
1521
1523
|
stored.hp -= damage;
|
|
1522
|
-
// Pokémon Showdown uses
|
|
1523
|
-
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
1524
|
+
// Pokémon Showdown uses damage [of] here but its not relevant in Generation I
|
|
1525
|
+
try options.log.damage(.{ ident, stored, if (brn) Damage.Burn else Damage.Poison });
|
|
1524
1526
|
}
|
|
1525
1527
|
}
|
|
1526
1528
|
|
|
@@ -1545,14 +1547,14 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1545
1547
|
const amount = @min(damage, stored.hp);
|
|
1546
1548
|
stored.hp -= amount;
|
|
1547
1549
|
|
|
1548
|
-
// As above, Pokémon Showdown uses
|
|
1549
|
-
if (amount > 0) try options.log.damage(ident, stored, .LeechSeed);
|
|
1550
|
+
// As above, Pokémon Showdown uses damage [of] but its not relevant
|
|
1551
|
+
if (amount > 0) try options.log.damage(.{ ident, stored, .LeechSeed });
|
|
1550
1552
|
|
|
1551
1553
|
const before = foe_stored.hp;
|
|
1552
1554
|
// Uncapped damage is added back to the foe
|
|
1553
1555
|
foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
|
|
1554
1556
|
// Pokémon Showdown uses the less specific heal here instead of drain... because reasons?
|
|
1555
|
-
if (foe_stored.hp > before) try options.log.heal(foe_ident, foe_stored, .Silent);
|
|
1557
|
+
if (foe_stored.hp > before) try options.log.heal(.{ foe_ident, foe_stored, .Silent });
|
|
1556
1558
|
}
|
|
1557
1559
|
}
|
|
1558
1560
|
|
|
@@ -1563,18 +1565,18 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1563
1565
|
battle.turn += 1;
|
|
1564
1566
|
|
|
1565
1567
|
if (showdown and pkmn.options.ebc and checkEBC(battle)) {
|
|
1566
|
-
try options.log.tie();
|
|
1568
|
+
try options.log.tie(.{});
|
|
1567
1569
|
return Result.Tie;
|
|
1568
1570
|
}
|
|
1569
1571
|
|
|
1570
1572
|
if (showdown and battle.turn >= 1000) {
|
|
1571
|
-
try options.log.tie();
|
|
1573
|
+
try options.log.tie(.{});
|
|
1572
1574
|
return Result.Tie;
|
|
1573
1575
|
} else if (battle.turn >= 65535) {
|
|
1574
1576
|
return Result.Error;
|
|
1575
1577
|
}
|
|
1576
1578
|
|
|
1577
|
-
try options.log.turn(battle.turn);
|
|
1579
|
+
try options.log.turn(.{battle.turn});
|
|
1578
1580
|
|
|
1579
1581
|
return Result.Default;
|
|
1580
1582
|
}
|
|
@@ -1634,9 +1636,9 @@ fn checkEBC(battle: anytype) bool {
|
|
|
1634
1636
|
}
|
|
1635
1637
|
|
|
1636
1638
|
fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
1637
|
-
if (side.active.volatiles.
|
|
1639
|
+
if (side.active.volatiles.disable_move != 0) {
|
|
1638
1640
|
// A Pokémon that transforms after being disabled may end up with less move slots
|
|
1639
|
-
const m = side.active.moves[side.active.volatiles.
|
|
1641
|
+
const m = side.active.moves[side.active.volatiles.disable_move - 1].id;
|
|
1640
1642
|
// side.last_selected_move can be Struggle here on Pokemon Showdown we need an extra check
|
|
1641
1643
|
const last = if (showdown and m == .Bide and side.active.volatiles.Bide)
|
|
1642
1644
|
m
|
|
@@ -1644,14 +1646,14 @@ fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
|
1644
1646
|
side.last_selected_move;
|
|
1645
1647
|
if (m != .None and m == last) {
|
|
1646
1648
|
side.active.volatiles.Charging = false;
|
|
1647
|
-
try options.log.
|
|
1649
|
+
try options.log.cant(.{ ident, .Disable, last });
|
|
1648
1650
|
return true;
|
|
1649
1651
|
}
|
|
1650
1652
|
}
|
|
1651
1653
|
return false;
|
|
1652
1654
|
}
|
|
1653
1655
|
|
|
1654
|
-
|
|
1656
|
+
fn buildRage(battle: anytype, who: Player, options: anytype) !void {
|
|
1655
1657
|
const side = battle.side(who);
|
|
1656
1658
|
if (side.active.volatiles.Rage and side.active.boosts.atk < 6) {
|
|
1657
1659
|
try Effects.boost(battle, who, Move.get(.Rage), options);
|
|
@@ -1669,7 +1671,7 @@ fn handleThrashing(battle: anytype, active: *ActivePokemon, player: Player, opti
|
|
|
1669
1671
|
return true;
|
|
1670
1672
|
}
|
|
1671
1673
|
|
|
1672
|
-
|
|
1674
|
+
fn onBegin(
|
|
1673
1675
|
battle: anytype,
|
|
1674
1676
|
player: Player,
|
|
1675
1677
|
move: Move.Data,
|
|
@@ -1699,7 +1701,7 @@ inline fn onBegin(
|
|
|
1699
1701
|
};
|
|
1700
1702
|
}
|
|
1701
1703
|
|
|
1702
|
-
|
|
1704
|
+
fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1703
1705
|
assert(move.effect.onEnd());
|
|
1704
1706
|
return switch (move.effect) {
|
|
1705
1707
|
.Bide => Effects.bide(battle, player, options),
|
|
@@ -1715,7 +1717,7 @@ inline fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anyty
|
|
|
1715
1717
|
};
|
|
1716
1718
|
}
|
|
1717
1719
|
|
|
1718
|
-
|
|
1720
|
+
fn alwaysHappens(
|
|
1719
1721
|
battle: anytype,
|
|
1720
1722
|
player: Player,
|
|
1721
1723
|
move: Move.Data,
|
|
@@ -1734,7 +1736,7 @@ inline fn alwaysHappens(
|
|
|
1734
1736
|
};
|
|
1735
1737
|
}
|
|
1736
1738
|
|
|
1737
|
-
|
|
1739
|
+
fn moveEffect(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1738
1740
|
return switch (move.effect) {
|
|
1739
1741
|
.BurnChance1, .BurnChance2 => Effects.burnChance(battle, player, move, options),
|
|
1740
1742
|
.ConfusionChance => Effects.confusion(battle, player, move, options),
|
|
@@ -1761,7 +1763,7 @@ pub const Effects = struct {
|
|
|
1761
1763
|
side.active.volatiles.state = 0;
|
|
1762
1764
|
side.active.volatiles.attacks = Rolls.attackingDuration(battle, player, options);
|
|
1763
1765
|
|
|
1764
|
-
try options.log.start(battle.active(player), .Bide);
|
|
1766
|
+
try options.log.start(.{ battle.active(player), .Bide });
|
|
1765
1767
|
}
|
|
1766
1768
|
|
|
1767
1769
|
fn burnChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1775,11 +1777,11 @@ pub const Effects = struct {
|
|
|
1775
1777
|
// GLITCH: Freeze top move selection desync can occur if thawed Pokémon is slower
|
|
1776
1778
|
if (Status.is(foe_stored.status, .FRZ)) {
|
|
1777
1779
|
assert(move.type == .Fire);
|
|
1778
|
-
try options.log.curestatus(
|
|
1780
|
+
try options.log.curestatus(.{
|
|
1779
1781
|
battle.active(player.foe()),
|
|
1780
1782
|
foe_stored.status,
|
|
1781
1783
|
.Message,
|
|
1782
|
-
);
|
|
1784
|
+
});
|
|
1783
1785
|
foe_stored.status = 0;
|
|
1784
1786
|
}
|
|
1785
1787
|
return;
|
|
@@ -1793,7 +1795,7 @@ pub const Effects = struct {
|
|
|
1793
1795
|
foe_stored.status = Status.init(.BRN);
|
|
1794
1796
|
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
1795
1797
|
|
|
1796
|
-
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
1798
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1797
1799
|
}
|
|
1798
1800
|
|
|
1799
1801
|
fn charge(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -1803,8 +1805,8 @@ pub const Effects = struct {
|
|
|
1803
1805
|
volatiles.Charging = true;
|
|
1804
1806
|
const move = side.last_selected_move;
|
|
1805
1807
|
if (move == .Fly or move == .Dig) volatiles.Invulnerable = true;
|
|
1806
|
-
try options.log.laststill();
|
|
1807
|
-
try options.log.prepare(battle.active(player), move);
|
|
1808
|
+
try options.log.laststill(.{});
|
|
1809
|
+
try options.log.prepare(.{ battle.active(player), move });
|
|
1808
1810
|
}
|
|
1809
1811
|
|
|
1810
1812
|
fn confusion(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1815,17 +1817,11 @@ pub const Effects = struct {
|
|
|
1815
1817
|
if (!try Rolls.confusionChance(battle, player, options)) return;
|
|
1816
1818
|
} else {
|
|
1817
1819
|
if (showdown) {
|
|
1818
|
-
if (!try checkHit(battle, player, move, options))
|
|
1819
|
-
|
|
1820
|
-
} else if (sub) {
|
|
1821
|
-
return options.log.fail(battle.active(player.foe()), .None);
|
|
1822
|
-
}
|
|
1820
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1821
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1823
1822
|
} else {
|
|
1824
|
-
if (sub) {
|
|
1825
|
-
|
|
1826
|
-
} else if (!try checkHit(battle, player, move, options)) {
|
|
1827
|
-
return;
|
|
1828
|
-
}
|
|
1823
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1824
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1829
1825
|
}
|
|
1830
1826
|
try options.chance.commit(player, .hit);
|
|
1831
1827
|
}
|
|
@@ -1834,23 +1830,24 @@ pub const Effects = struct {
|
|
|
1834
1830
|
foe.active.volatiles.Confusion = true;
|
|
1835
1831
|
foe.active.volatiles.confusion = Rolls.confusionDuration(battle, player, false, options);
|
|
1836
1832
|
|
|
1837
|
-
try options.log.start(battle.active(player.foe()), .Confusion);
|
|
1833
|
+
try options.log.start(.{ battle.active(player.foe()), .Confusion });
|
|
1838
1834
|
}
|
|
1839
1835
|
|
|
1840
1836
|
fn conversion(battle: anytype, player: Player, options: anytype) !void {
|
|
1841
1837
|
const foe = battle.foe(player);
|
|
1842
1838
|
|
|
1843
1839
|
if (foe.active.volatiles.Invulnerable) {
|
|
1844
|
-
try options.log.lastmiss();
|
|
1845
|
-
return options.log.miss(battle.active(player));
|
|
1840
|
+
try options.log.lastmiss(.{});
|
|
1841
|
+
return options.log.miss(.{battle.active(player)});
|
|
1846
1842
|
}
|
|
1847
1843
|
|
|
1848
1844
|
battle.side(player).active.types = foe.active.types;
|
|
1849
|
-
return options.log.
|
|
1845
|
+
return options.log.start(.{
|
|
1850
1846
|
battle.active(player),
|
|
1847
|
+
.TypeChange,
|
|
1851
1848
|
foe.active.types,
|
|
1852
1849
|
battle.active(player.foe()),
|
|
1853
|
-
);
|
|
1850
|
+
});
|
|
1854
1851
|
}
|
|
1855
1852
|
|
|
1856
1853
|
fn disable(battle: anytype, player: Player, move: Move.Data, options: anytype) !?Result {
|
|
@@ -1864,8 +1861,8 @@ pub const Effects = struct {
|
|
|
1864
1861
|
try options.chance.commit(player, .hit);
|
|
1865
1862
|
}
|
|
1866
1863
|
|
|
1867
|
-
if (volatiles.
|
|
1868
|
-
try options.log.fail(foe_ident, .None);
|
|
1864
|
+
if (volatiles.disable_move != 0) {
|
|
1865
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1869
1866
|
return null;
|
|
1870
1867
|
}
|
|
1871
1868
|
|
|
@@ -1881,7 +1878,7 @@ pub const Effects = struct {
|
|
|
1881
1878
|
// Technically this is still considered simply a "miss" on the cartridge,
|
|
1882
1879
|
// but diverging from Pokémon Showdown here would mostly just be pedantic
|
|
1883
1880
|
if (n == 0) {
|
|
1884
|
-
try options.log.fail(foe_ident, .None);
|
|
1881
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1885
1882
|
return null;
|
|
1886
1883
|
} else if (err) {
|
|
1887
1884
|
// GLITCH: Transform + Mirror Move / Metronome PP softlock
|
|
@@ -1889,18 +1886,18 @@ pub const Effects = struct {
|
|
|
1889
1886
|
return Result.Error;
|
|
1890
1887
|
}
|
|
1891
1888
|
|
|
1892
|
-
volatiles.
|
|
1889
|
+
volatiles.disable_move =
|
|
1893
1890
|
@intCast(try Rolls.moveSlot(battle, player, &foe.active.moves, n, options));
|
|
1894
|
-
volatiles.
|
|
1891
|
+
volatiles.disable_duration = Rolls.disableDuration(battle, player, options);
|
|
1895
1892
|
|
|
1896
|
-
const id = foe.active.move(volatiles.
|
|
1897
|
-
try options.log.
|
|
1893
|
+
const id = foe.active.move(volatiles.disable_move).id;
|
|
1894
|
+
try options.log.start(.{ foe_ident, .Disable, id });
|
|
1898
1895
|
return null;
|
|
1899
1896
|
}
|
|
1900
1897
|
|
|
1901
1898
|
fn drainHP(battle: anytype, player: Player, options: anytype) !void {
|
|
1902
|
-
var
|
|
1903
|
-
|
|
1899
|
+
var stored = battle.side(player).stored();
|
|
1900
|
+
const ident = battle.active(player);
|
|
1904
1901
|
|
|
1905
1902
|
if (battle.last_damage == 0) {
|
|
1906
1903
|
assert(showdown);
|
|
@@ -1913,7 +1910,7 @@ pub const Effects = struct {
|
|
|
1913
1910
|
if (stored.hp == stored.stats.hp) return;
|
|
1914
1911
|
stored.hp = @min(stored.stats.hp, stored.hp + drain);
|
|
1915
1912
|
|
|
1916
|
-
try options.log.
|
|
1913
|
+
try options.log.heal(.{ ident, stored, .Drain, battle.active(player.foe()) });
|
|
1917
1914
|
}
|
|
1918
1915
|
|
|
1919
1916
|
fn explode(battle: anytype, player: Player) !void {
|
|
@@ -1945,7 +1942,7 @@ pub const Effects = struct {
|
|
|
1945
1942
|
if (side.active.volatiles.FocusEnergy) return;
|
|
1946
1943
|
side.active.volatiles.FocusEnergy = true;
|
|
1947
1944
|
|
|
1948
|
-
try options.log.start(battle.active(player), .FocusEnergy);
|
|
1945
|
+
try options.log.start(.{ battle.active(player), .FocusEnergy });
|
|
1949
1946
|
}
|
|
1950
1947
|
|
|
1951
1948
|
fn freezeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1967,7 +1964,7 @@ pub const Effects = struct {
|
|
|
1967
1964
|
foe_stored.status = Status.init(.FRZ);
|
|
1968
1965
|
// GLITCH: Hyper Beam recharging status is not cleared
|
|
1969
1966
|
|
|
1970
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
1967
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
1971
1968
|
}
|
|
1972
1969
|
|
|
1973
1970
|
fn haze(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -1987,8 +1984,8 @@ pub const Effects = struct {
|
|
|
1987
1984
|
side.active.stats = side_stored.stats;
|
|
1988
1985
|
foe.active.stats = foe_stored.stats;
|
|
1989
1986
|
|
|
1990
|
-
try log.activate(player_ident, .Haze);
|
|
1991
|
-
try log.clearallboost();
|
|
1987
|
+
try log.activate(.{ player_ident, .Haze });
|
|
1988
|
+
try log.clearallboost(.{});
|
|
1992
1989
|
|
|
1993
1990
|
// Pokémon Showdown clears P1 then P2 instead of status -> side -> foe
|
|
1994
1991
|
if (showdown) {
|
|
@@ -1996,11 +1993,11 @@ pub const Effects = struct {
|
|
|
1996
1993
|
const p: Player = @enumFromInt(i);
|
|
1997
1994
|
// Pokémon Showdown incorrectly does not prevent sleep/freeze from moving
|
|
1998
1995
|
if (p != player and Status.any(s.stored().status)) {
|
|
1999
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
1996
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2000
1997
|
s.stored().status = 0;
|
|
2001
1998
|
} else if (showdown and s.stored().status == Status.TOX) {
|
|
2002
1999
|
s.stored().status = Status.init(.PSN);
|
|
2003
|
-
try log.status(battle.active(p), s.stored().status, .None);
|
|
2000
|
+
try log.status(.{ battle.active(p), s.stored().status, .None });
|
|
2004
2001
|
}
|
|
2005
2002
|
try clearVolatiles(battle, p, p != player, options);
|
|
2006
2003
|
}
|
|
@@ -2009,7 +2006,7 @@ pub const Effects = struct {
|
|
|
2009
2006
|
if (Status.is(foe_stored.status, .FRZ) or Status.is(foe_stored.status, .SLP)) {
|
|
2010
2007
|
foe.last_selected_move = .SKIP_TURN;
|
|
2011
2008
|
}
|
|
2012
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
2009
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2013
2010
|
foe_stored.status = 0;
|
|
2014
2011
|
}
|
|
2015
2012
|
try clearVolatiles(battle, player, false, options);
|
|
@@ -2025,7 +2022,7 @@ pub const Effects = struct {
|
|
|
2025
2022
|
// GLITCH: HP recovery move failure glitches
|
|
2026
2023
|
const delta = stored.stats.hp - stored.hp;
|
|
2027
2024
|
if (delta == 0 or (delta & 255 == 255 and stored.hp % 256 != 0)) {
|
|
2028
|
-
return
|
|
2025
|
+
return options.log.fail(.{ ident, .None });
|
|
2029
2026
|
}
|
|
2030
2027
|
|
|
2031
2028
|
const rest = side.last_selected_move == .Rest;
|
|
@@ -2033,17 +2030,17 @@ pub const Effects = struct {
|
|
|
2033
2030
|
// Adding the sleep status runs the sleep condition handler to roll duration
|
|
2034
2031
|
if (showdown) battle.rng.advance(1);
|
|
2035
2032
|
stored.status = Status.slf(2);
|
|
2036
|
-
try options.log.
|
|
2033
|
+
try options.log.status(.{ ident, stored.status, .From, Move.Rest });
|
|
2037
2034
|
stored.hp = stored.stats.hp;
|
|
2038
2035
|
} else {
|
|
2039
2036
|
stored.hp = @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2));
|
|
2040
2037
|
}
|
|
2041
|
-
try options.log.heal(ident, stored, if (rest) Heal.Silent else Heal.None);
|
|
2038
|
+
try options.log.heal(.{ ident, stored, if (rest) Heal.Silent else Heal.None });
|
|
2042
2039
|
}
|
|
2043
2040
|
|
|
2044
2041
|
fn hyperBeam(battle: anytype, player: Player, options: anytype) !void {
|
|
2045
2042
|
battle.side(player).active.volatiles.Recharging = true;
|
|
2046
|
-
try options.log.mustrecharge(battle.active(player));
|
|
2043
|
+
try options.log.mustrecharge(.{battle.active(player)});
|
|
2047
2044
|
}
|
|
2048
2045
|
|
|
2049
2046
|
fn leechSeed(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2052,33 +2049,32 @@ pub const Effects = struct {
|
|
|
2052
2049
|
if (showdown) {
|
|
2053
2050
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2054
2051
|
if (!foe.active.volatiles.Invulnerable and foe.active.types.includes(.Grass)) {
|
|
2055
|
-
return options.log.immune(battle.active(player.foe()), .None);
|
|
2052
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
2056
2053
|
}
|
|
2057
2054
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2058
2055
|
if (foe.active.volatiles.LeechSeed) return;
|
|
2059
2056
|
} else {
|
|
2060
2057
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2061
2058
|
if (foe.active.types.includes(.Grass) or foe.active.volatiles.LeechSeed) {
|
|
2062
|
-
try options.log.lastmiss();
|
|
2063
|
-
return options.log.miss(battle.active(player));
|
|
2059
|
+
try options.log.lastmiss(.{});
|
|
2060
|
+
return options.log.miss(.{battle.active(player)});
|
|
2064
2061
|
}
|
|
2065
2062
|
}
|
|
2066
2063
|
|
|
2067
2064
|
try options.chance.commit(player, .hit);
|
|
2068
2065
|
foe.active.volatiles.LeechSeed = true;
|
|
2069
2066
|
|
|
2070
|
-
try options.log.start(battle.active(player.foe()), .LeechSeed);
|
|
2067
|
+
try options.log.start(.{ battle.active(player.foe()), .LeechSeed });
|
|
2071
2068
|
}
|
|
2072
2069
|
|
|
2073
2070
|
fn lightScreen(battle: anytype, player: Player, options: anytype) !void {
|
|
2074
2071
|
var side = battle.side(player);
|
|
2072
|
+
const ident = battle.active(player);
|
|
2075
2073
|
|
|
2076
|
-
if (side.active.volatiles.LightScreen) {
|
|
2077
|
-
return options.log.fail(battle.active(player), .None);
|
|
2078
|
-
}
|
|
2074
|
+
if (side.active.volatiles.LightScreen) return options.log.fail(.{ ident, .None });
|
|
2079
2075
|
side.active.volatiles.LightScreen = true;
|
|
2080
2076
|
|
|
2081
|
-
try options.log.start(
|
|
2077
|
+
try options.log.start(.{ ident, .LightScreen });
|
|
2082
2078
|
}
|
|
2083
2079
|
|
|
2084
2080
|
fn mimic(battle: anytype, player: Player, move: Move.Data, mslot: u8, options: anytype) !void {
|
|
@@ -2107,7 +2103,7 @@ pub const Effects = struct {
|
|
|
2107
2103
|
// Invulnerable foes or 1/256 miss can trigger |-miss| instead of |-fail|
|
|
2108
2104
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2109
2105
|
try options.chance.commit(player, .hit);
|
|
2110
|
-
return
|
|
2106
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2111
2107
|
}
|
|
2112
2108
|
}
|
|
2113
2109
|
if (!try checkHit(battle, player, move, options)) return;
|
|
@@ -2116,7 +2112,7 @@ pub const Effects = struct {
|
|
|
2116
2112
|
const rslot = try Rolls.moveSlot(battle, player, &foe.active.moves, 0, options);
|
|
2117
2113
|
side.active.move(oslot).id = foe.active.move(rslot).id;
|
|
2118
2114
|
|
|
2119
|
-
try options.log.
|
|
2115
|
+
try options.log.start(.{ battle.active(player), .Mimic, side.active.move(oslot).id });
|
|
2120
2116
|
}
|
|
2121
2117
|
|
|
2122
2118
|
fn mist(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -2125,7 +2121,7 @@ pub const Effects = struct {
|
|
|
2125
2121
|
if (side.active.volatiles.Mist) return;
|
|
2126
2122
|
side.active.volatiles.Mist = true;
|
|
2127
2123
|
|
|
2128
|
-
try options.log.start(battle.active(player), .Mist);
|
|
2124
|
+
try options.log.start(.{ battle.active(player), .Mist });
|
|
2129
2125
|
}
|
|
2130
2126
|
|
|
2131
2127
|
fn multiHit(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2151,17 +2147,18 @@ pub const Effects = struct {
|
|
|
2151
2147
|
|
|
2152
2148
|
if (showdown) {
|
|
2153
2149
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2154
|
-
if (immune and !foe.active.volatiles.Invulnerable)
|
|
2155
|
-
|
|
2150
|
+
if (immune and !foe.active.volatiles.Invulnerable) {
|
|
2151
|
+
return log.immune(.{ foe_ident, .None });
|
|
2152
|
+
} else if (!try checkHit(battle, player, move, options)) return;
|
|
2156
2153
|
}
|
|
2157
2154
|
if (Status.any(foe_stored.status)) {
|
|
2158
|
-
return log.fail(
|
|
2155
|
+
return log.fail(.{
|
|
2159
2156
|
foe_ident,
|
|
2160
|
-
if (Status.is(foe_stored.status, .PAR)) .Paralysis else .None,
|
|
2161
|
-
);
|
|
2157
|
+
if (Status.is(foe_stored.status, .PAR)) Fail.Paralysis else Fail.None,
|
|
2158
|
+
});
|
|
2162
2159
|
}
|
|
2163
2160
|
if (!showdown) {
|
|
2164
|
-
if (immune) return log.immune(foe_ident, .None);
|
|
2161
|
+
if (immune) return log.immune(.{ foe_ident, .None });
|
|
2165
2162
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2166
2163
|
}
|
|
2167
2164
|
try options.chance.commit(player, .hit);
|
|
@@ -2169,7 +2166,7 @@ pub const Effects = struct {
|
|
|
2169
2166
|
foe_stored.status = Status.init(.PAR);
|
|
2170
2167
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2171
2168
|
|
|
2172
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2169
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2173
2170
|
}
|
|
2174
2171
|
|
|
2175
2172
|
fn paralyzeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2191,12 +2188,12 @@ pub const Effects = struct {
|
|
|
2191
2188
|
foe_stored.status = Status.init(.PAR);
|
|
2192
2189
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2193
2190
|
|
|
2194
|
-
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
2191
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
2195
2192
|
}
|
|
2196
2193
|
|
|
2197
2194
|
fn payDay(battle: anytype, player: Player, options: anytype) !void {
|
|
2198
2195
|
if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
|
|
2199
|
-
try options.log.fieldactivate();
|
|
2196
|
+
try options.log.fieldactivate(.{});
|
|
2200
2197
|
}
|
|
2201
2198
|
}
|
|
2202
2199
|
|
|
@@ -2211,23 +2208,23 @@ pub const Effects = struct {
|
|
|
2211
2208
|
return;
|
|
2212
2209
|
} else if (foe.active.volatiles.Substitute) {
|
|
2213
2210
|
if (move.effect != .Poison) return;
|
|
2214
|
-
return log.fail(foe_ident, .None);
|
|
2211
|
+
return log.fail(.{ foe_ident, .None });
|
|
2215
2212
|
} else if (Status.any(foe_stored.status)) {
|
|
2216
2213
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2217
2214
|
// Pokémon Showdown considers Toxic to be a status even in Generation I and so
|
|
2218
2215
|
// 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))
|
|
2216
|
+
return log.fail(.{ foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2220
2217
|
if (!showdown)
|
|
2221
|
-
.Poison
|
|
2218
|
+
Fail.Poison
|
|
2222
2219
|
else if (toxic == (foe_stored.status == Status.TOX))
|
|
2223
|
-
if (toxic) .Toxic else .Poison
|
|
2220
|
+
if (toxic) Fail.Toxic else Fail.Poison
|
|
2224
2221
|
else
|
|
2225
|
-
.None
|
|
2222
|
+
Fail.None
|
|
2226
2223
|
else
|
|
2227
|
-
.None);
|
|
2224
|
+
Fail.None });
|
|
2228
2225
|
} else if (foe.active.types.includes(.Poison)) {
|
|
2229
2226
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2230
|
-
return log.immune(foe_ident, .None);
|
|
2227
|
+
return log.immune(.{ foe_ident, .None });
|
|
2231
2228
|
}
|
|
2232
2229
|
|
|
2233
2230
|
if (move.effect == .Poison) {
|
|
@@ -2246,7 +2243,7 @@ pub const Effects = struct {
|
|
|
2246
2243
|
foe.active.volatiles.toxic = 0;
|
|
2247
2244
|
}
|
|
2248
2245
|
|
|
2249
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2246
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2250
2247
|
}
|
|
2251
2248
|
|
|
2252
2249
|
fn rage(battle: anytype, player: Player) !void {
|
|
@@ -2266,22 +2263,23 @@ pub const Effects = struct {
|
|
|
2266
2263
|
@as(u8, if (side.last_selected_move == .Struggle) 2 else 4), 1));
|
|
2267
2264
|
stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
|
|
2268
2265
|
|
|
2269
|
-
try options.log.
|
|
2266
|
+
try options.log.damage(.{
|
|
2270
2267
|
battle.active(player),
|
|
2271
2268
|
stored,
|
|
2272
2269
|
.RecoilOf,
|
|
2273
2270
|
battle.active(player.foe()),
|
|
2274
|
-
);
|
|
2271
|
+
});
|
|
2275
2272
|
if (showdown and stored.hp == 0) residual.* = false;
|
|
2276
2273
|
}
|
|
2277
2274
|
|
|
2278
2275
|
fn reflect(battle: anytype, player: Player, options: anytype) !void {
|
|
2279
2276
|
var side = battle.side(player);
|
|
2277
|
+
const ident = battle.active(player);
|
|
2280
2278
|
|
|
2281
|
-
if (side.active.volatiles.Reflect) return options.log.fail(
|
|
2279
|
+
if (side.active.volatiles.Reflect) return options.log.fail(.{ ident, .None });
|
|
2282
2280
|
side.active.volatiles.Reflect = true;
|
|
2283
2281
|
|
|
2284
|
-
try options.log.start(
|
|
2282
|
+
try options.log.start(.{ ident, .Reflect });
|
|
2285
2283
|
}
|
|
2286
2284
|
|
|
2287
2285
|
fn sleep(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2295,10 +2293,10 @@ pub const Effects = struct {
|
|
|
2295
2293
|
// the Sleep Clause Mod might activate, causing us to not actually bypass
|
|
2296
2294
|
} else {
|
|
2297
2295
|
if (Status.any(foe_stored.status)) {
|
|
2298
|
-
return options.log.fail(
|
|
2296
|
+
return options.log.fail(.{
|
|
2299
2297
|
foe_ident,
|
|
2300
|
-
if (Status.is(foe_stored.status, .SLP)) .Sleep else .None,
|
|
2301
|
-
);
|
|
2298
|
+
if (Status.is(foe_stored.status, .SLP)) Fail.Sleep else Fail.None,
|
|
2299
|
+
});
|
|
2302
2300
|
}
|
|
2303
2301
|
// If checkHit in doMove didn't return true Pokémon Showdown wouldn't be in here
|
|
2304
2302
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
@@ -2315,18 +2313,17 @@ pub const Effects = struct {
|
|
|
2315
2313
|
|
|
2316
2314
|
foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
|
|
2317
2315
|
const last = battle.side(player).last_selected_move;
|
|
2318
|
-
try options.log.
|
|
2316
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .From, last });
|
|
2319
2317
|
}
|
|
2320
2318
|
|
|
2321
2319
|
fn splash(battle: anytype, player: Player, options: anytype) !void {
|
|
2322
|
-
try options.log.activate(battle.active(player), .Splash);
|
|
2320
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
2323
2321
|
}
|
|
2324
2322
|
|
|
2325
2323
|
fn substitute(battle: anytype, player: Player, residual: *bool, options: anytype) !void {
|
|
2326
2324
|
var side = battle.side(player);
|
|
2327
2325
|
if (side.active.volatiles.Substitute) {
|
|
2328
|
-
|
|
2329
|
-
return;
|
|
2326
|
+
return options.log.fail(.{ battle.active(player), .Substitute });
|
|
2330
2327
|
}
|
|
2331
2328
|
|
|
2332
2329
|
assert(side.stored().stats.hp <= 1023);
|
|
@@ -2338,17 +2335,16 @@ pub const Effects = struct {
|
|
|
2338
2335
|
// avoid having to convert to floating point)
|
|
2339
2336
|
const required_hp = if (showdown) @divFloor(side.stored().stats.hp - 1, 4) + 1 else hp;
|
|
2340
2337
|
if (side.stored().hp < required_hp) {
|
|
2341
|
-
|
|
2342
|
-
return;
|
|
2338
|
+
return options.log.fail(.{ battle.active(player), .Weak });
|
|
2343
2339
|
}
|
|
2344
2340
|
|
|
2345
2341
|
// GLITCH: can leave the user with 0 HP (faints later) because didn't check '<=' above
|
|
2346
2342
|
side.stored().hp -= hp;
|
|
2347
2343
|
side.active.volatiles.substitute = hp + 1;
|
|
2348
2344
|
side.active.volatiles.Substitute = true;
|
|
2349
|
-
try options.log.start(battle.active(player), .Substitute);
|
|
2345
|
+
try options.log.start(.{ battle.active(player), .Substitute });
|
|
2350
2346
|
if (hp > 0) {
|
|
2351
|
-
try options.log.damage(battle.active(player), side.stored(), .None);
|
|
2347
|
+
try options.log.damage(.{ battle.active(player), side.stored(), .None });
|
|
2352
2348
|
if (showdown and side.stored().hp == 0) residual.* = false;
|
|
2353
2349
|
}
|
|
2354
2350
|
}
|
|
@@ -2362,8 +2358,8 @@ pub const Effects = struct {
|
|
|
2362
2358
|
}
|
|
2363
2359
|
battle.last_damage = 0;
|
|
2364
2360
|
} else {
|
|
2365
|
-
try options.log.fail(battle.active(player), .None);
|
|
2366
|
-
try options.log.laststill();
|
|
2361
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2362
|
+
try options.log.laststill(.{});
|
|
2367
2363
|
}
|
|
2368
2364
|
}
|
|
2369
2365
|
|
|
@@ -2402,7 +2398,7 @@ pub const Effects = struct {
|
|
|
2402
2398
|
side.active.moves[i].pp = if (m.id != .None) 5 else 0;
|
|
2403
2399
|
}
|
|
2404
2400
|
|
|
2405
|
-
try options.log.transform(battle.active(player), foe_ident);
|
|
2401
|
+
try options.log.transform(.{ battle.active(player), foe_ident });
|
|
2406
2402
|
}
|
|
2407
2403
|
|
|
2408
2404
|
fn binding(battle: anytype, player: Player, rewrap: bool, options: anytype) !void {
|
|
@@ -2432,87 +2428,87 @@ pub const Effects = struct {
|
|
|
2432
2428
|
switch (move.effect) {
|
|
2433
2429
|
.AttackUp1, .AttackUp2, .Rage => {
|
|
2434
2430
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2435
|
-
if (boosts.atk == 6) return
|
|
2431
|
+
if (boosts.atk == 6) return log.fail(.{ ident, .None });
|
|
2436
2432
|
const n: u2 = if (move.effect == .AttackUp2) 2 else 1;
|
|
2437
2433
|
boosts.atk = @as(i4, @intCast(@min(6, @as(i8, boosts.atk) + n)));
|
|
2438
|
-
const reason = if (move.effect == .Rage)
|
|
2434
|
+
const reason: Boost = if (move.effect == .Rage) .Rage else .Attack;
|
|
2439
2435
|
if (stats.atk == MAX_STAT_VALUE) {
|
|
2440
2436
|
boosts.atk -= 1;
|
|
2441
2437
|
if (showdown) {
|
|
2442
|
-
try log.boost(ident, reason, n);
|
|
2443
|
-
try log.boost(ident,
|
|
2438
|
+
try log.boost(.{ ident, reason, n });
|
|
2439
|
+
try log.boost(.{ ident, .Attack, -1 });
|
|
2444
2440
|
if (move.effect == .Rage) return;
|
|
2445
2441
|
}
|
|
2446
|
-
return
|
|
2442
|
+
return log.fail(.{ ident, .None });
|
|
2447
2443
|
}
|
|
2448
2444
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2449
2445
|
const stat = unmodifiedStats(battle, side).atk;
|
|
2450
2446
|
stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2451
|
-
try log.boost(ident, reason, n);
|
|
2447
|
+
try log.boost(.{ ident, reason, n });
|
|
2452
2448
|
// Pokémon Showdown doesn't re-apply status modifiers after Rage boosts
|
|
2453
2449
|
if (showdown and move.effect == .Rage) return;
|
|
2454
2450
|
},
|
|
2455
2451
|
.DefenseUp1, .DefenseUp2 => {
|
|
2456
2452
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2457
|
-
if (boosts.def == 6) return
|
|
2453
|
+
if (boosts.def == 6) return log.fail(.{ ident, .None });
|
|
2458
2454
|
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
2459
2455
|
boosts.def = @intCast(@min(6, @as(i8, boosts.def) + n));
|
|
2460
2456
|
if (stats.def == MAX_STAT_VALUE) {
|
|
2461
2457
|
boosts.def -= 1;
|
|
2462
2458
|
if (showdown) {
|
|
2463
|
-
try log.boost(ident, .Defense, n);
|
|
2464
|
-
try log.boost(ident, .Defense, -1);
|
|
2459
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2460
|
+
try log.boost(.{ ident, .Defense, -1 });
|
|
2465
2461
|
}
|
|
2466
|
-
return
|
|
2462
|
+
return log.fail(.{ ident, .None });
|
|
2467
2463
|
}
|
|
2468
2464
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2469
2465
|
const stat = unmodifiedStats(battle, side).def;
|
|
2470
2466
|
stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2471
|
-
try log.boost(ident, .Defense, n);
|
|
2467
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2472
2468
|
},
|
|
2473
2469
|
.SpeedUp2 => {
|
|
2474
2470
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2475
|
-
if (boosts.spe == 6) return
|
|
2471
|
+
if (boosts.spe == 6) return log.fail(.{ ident, .None });
|
|
2476
2472
|
boosts.spe = @intCast(@min(6, @as(i8, boosts.spe) + 2));
|
|
2477
2473
|
if (stats.spe == MAX_STAT_VALUE) {
|
|
2478
2474
|
boosts.spe -= 1;
|
|
2479
2475
|
if (showdown) {
|
|
2480
|
-
try log.boost(ident, .Speed, 2);
|
|
2481
|
-
try log.boost(ident, .Speed, -1);
|
|
2476
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2477
|
+
try log.boost(.{ ident, .Speed, -1 });
|
|
2482
2478
|
}
|
|
2483
|
-
return
|
|
2479
|
+
return log.fail(.{ ident, .None });
|
|
2484
2480
|
}
|
|
2485
2481
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2486
2482
|
const stat = unmodifiedStats(battle, side).spe;
|
|
2487
2483
|
stats.spe = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2488
|
-
try log.boost(ident, .Speed, 2);
|
|
2484
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2489
2485
|
},
|
|
2490
2486
|
.SpecialUp1, .SpecialUp2 => {
|
|
2491
2487
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2492
|
-
if (boosts.spc == 6) return
|
|
2488
|
+
if (boosts.spc == 6) return log.fail(.{ ident, .None });
|
|
2493
2489
|
const n: u2 = if (move.effect == .SpecialUp2) 2 else 1;
|
|
2494
2490
|
boosts.spc = @intCast(@min(6, @as(i8, boosts.spc) + n));
|
|
2495
2491
|
if (stats.spc == MAX_STAT_VALUE) {
|
|
2496
2492
|
boosts.spc -= 1;
|
|
2497
2493
|
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);
|
|
2494
|
+
try log.boost(.{ ident, .SpecialAttack, n });
|
|
2495
|
+
try log.boost(.{ ident, .SpecialAttack, -1 });
|
|
2496
|
+
try log.boost(.{ ident, .SpecialDefense, n });
|
|
2497
|
+
try log.boost(.{ ident, .SpecialDefense, -1 });
|
|
2502
2498
|
}
|
|
2503
|
-
return
|
|
2499
|
+
return log.fail(.{ ident, .None });
|
|
2504
2500
|
}
|
|
2505
2501
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2506
2502
|
const stat = unmodifiedStats(battle, side).spc;
|
|
2507
2503
|
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);
|
|
2504
|
+
try log.boost(.{ ident, .SpecialAttack, n });
|
|
2505
|
+
try log.boost(.{ ident, .SpecialDefense, n });
|
|
2510
2506
|
},
|
|
2511
2507
|
.EvasionUp1 => {
|
|
2512
2508
|
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2513
|
-
if (boosts.evasion == 6) return
|
|
2509
|
+
if (boosts.evasion == 6) return log.fail(.{ ident, .None });
|
|
2514
2510
|
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2515
|
-
try log.boost(ident, .Evasion, 1);
|
|
2511
|
+
try log.boost(.{ ident, .Evasion, 1 });
|
|
2516
2512
|
},
|
|
2517
2513
|
else => unreachable,
|
|
2518
2514
|
}
|
|
@@ -2527,7 +2523,7 @@ pub const Effects = struct {
|
|
|
2527
2523
|
const foe_ident = battle.active(player.foe());
|
|
2528
2524
|
const secondary = move.effect.isStatDownChance();
|
|
2529
2525
|
|
|
2530
|
-
if (foe.active.volatiles.Substitute) return if (!secondary) log.fail(foe_ident, .None);
|
|
2526
|
+
if (foe.active.volatiles.Substitute) return if (!secondary) log.fail(.{ foe_ident, .None });
|
|
2531
2527
|
|
|
2532
2528
|
if (secondary) {
|
|
2533
2529
|
const proc = try Rolls.unboost(battle, player, options);
|
|
@@ -2545,82 +2541,82 @@ pub const Effects = struct {
|
|
|
2545
2541
|
switch (move.effect) {
|
|
2546
2542
|
.AttackDown1, .AttackDownChance => {
|
|
2547
2543
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2548
|
-
if (boosts.atk == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2544
|
+
if (boosts.atk == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2549
2545
|
boosts.atk = @intCast(@max(-6, @as(i8, boosts.atk) - 1));
|
|
2550
2546
|
if (stats.atk == 1) {
|
|
2551
2547
|
boosts.atk += 1;
|
|
2552
2548
|
if (showdown) {
|
|
2553
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2554
|
-
try log.boost(foe_ident, .Attack, 1);
|
|
2549
|
+
try log.boost(.{ foe_ident, .Attack, -1 });
|
|
2550
|
+
try log.boost(.{ foe_ident, .Attack, 1 });
|
|
2555
2551
|
}
|
|
2556
|
-
return
|
|
2552
|
+
return log.fail(.{ foe_ident, .None });
|
|
2557
2553
|
}
|
|
2558
2554
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2559
2555
|
const stat = unmodifiedStats(battle, foe).atk;
|
|
2560
2556
|
stats.atk = @max(1, stat * mod[0] / mod[1]);
|
|
2561
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2557
|
+
try log.boost(.{ foe_ident, .Attack, -1 });
|
|
2562
2558
|
},
|
|
2563
2559
|
.DefenseDown1, .DefenseDown2, .DefenseDownChance => {
|
|
2564
2560
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2565
|
-
if (boosts.def == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2561
|
+
if (boosts.def == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2566
2562
|
const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
|
|
2567
2563
|
boosts.def = @intCast(@max(-6, @as(i8, boosts.def) - n));
|
|
2568
2564
|
if (stats.def == 1) {
|
|
2569
2565
|
boosts.def += 1;
|
|
2570
2566
|
if (showdown) {
|
|
2571
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2572
|
-
try log.boost(foe_ident, .Defense, 1);
|
|
2567
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2568
|
+
try log.boost(.{ foe_ident, .Defense, 1 });
|
|
2573
2569
|
}
|
|
2574
|
-
return
|
|
2570
|
+
return log.fail(.{ foe_ident, .None });
|
|
2575
2571
|
}
|
|
2576
2572
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2577
2573
|
const stat = unmodifiedStats(battle, foe).def;
|
|
2578
2574
|
stats.def = @max(1, stat * mod[0] / mod[1]);
|
|
2579
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2575
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2580
2576
|
},
|
|
2581
2577
|
.SpeedDown1, .SpeedDownChance => {
|
|
2582
2578
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2583
|
-
if (boosts.spe == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2579
|
+
if (boosts.spe == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2584
2580
|
boosts.spe = @intCast(@max(-6, @as(i8, boosts.spe) - 1));
|
|
2585
2581
|
if (stats.spe == 1) {
|
|
2586
2582
|
boosts.spe += 1;
|
|
2587
2583
|
if (showdown) {
|
|
2588
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2589
|
-
try log.boost(foe_ident, .Speed, 1);
|
|
2584
|
+
try log.boost(.{ foe_ident, .Speed, -1 });
|
|
2585
|
+
try log.boost(.{ foe_ident, .Speed, 1 });
|
|
2590
2586
|
}
|
|
2591
|
-
return
|
|
2587
|
+
return log.fail(.{ foe_ident, .None });
|
|
2592
2588
|
}
|
|
2593
2589
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2594
2590
|
const stat = unmodifiedStats(battle, foe).spe;
|
|
2595
2591
|
stats.spe = @max(1, stat * mod[0] / mod[1]);
|
|
2596
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2592
|
+
try log.boost(.{ foe_ident, .Speed, -1 });
|
|
2597
2593
|
assert(boosts.spe >= -6);
|
|
2598
2594
|
},
|
|
2599
2595
|
.SpecialDownChance => {
|
|
2600
2596
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2601
|
-
if (boosts.spc == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2597
|
+
if (boosts.spc == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2602
2598
|
boosts.spc = @intCast(@max(-6, @as(i8, boosts.spc) - 1));
|
|
2603
2599
|
if (stats.spc == 1) {
|
|
2604
2600
|
boosts.spc += 1;
|
|
2605
2601
|
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);
|
|
2602
|
+
try log.boost(.{ foe_ident, .SpecialAttack, -1 });
|
|
2603
|
+
try log.boost(.{ foe_ident, .SpecialAttack, 1 });
|
|
2604
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2605
|
+
try log.boost(.{ foe_ident, .SpecialDefense, 1 });
|
|
2610
2606
|
}
|
|
2611
|
-
return
|
|
2607
|
+
return log.fail(.{ foe_ident, .None });
|
|
2612
2608
|
}
|
|
2613
2609
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2614
2610
|
const stat = unmodifiedStats(battle, foe).spc;
|
|
2615
2611
|
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);
|
|
2612
|
+
try log.boost(.{ foe_ident, .SpecialAttack, -1 });
|
|
2613
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2618
2614
|
},
|
|
2619
2615
|
.AccuracyDown1 => {
|
|
2620
2616
|
assert(boosts.accuracy >= -6 and boosts.accuracy <= 6);
|
|
2621
|
-
if (boosts.accuracy == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2617
|
+
if (boosts.accuracy == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2622
2618
|
boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
|
|
2623
|
-
try log.boost(foe_ident, .Accuracy, -1);
|
|
2619
|
+
try log.boost(.{ foe_ident, .Accuracy, -1 });
|
|
2624
2620
|
},
|
|
2625
2621
|
else => unreachable,
|
|
2626
2622
|
}
|
|
@@ -2644,7 +2640,7 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
2644
2640
|
}
|
|
2645
2641
|
}
|
|
2646
2642
|
|
|
2647
|
-
|
|
2643
|
+
fn isForced(active: anytype) bool {
|
|
2648
2644
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
2649
2645
|
active.volatiles.Thrashing or active.volatiles.Charging;
|
|
2650
2646
|
}
|
|
@@ -2657,27 +2653,27 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2657
2653
|
|
|
2658
2654
|
options.chance.clearDurations(who, clear);
|
|
2659
2655
|
|
|
2660
|
-
if (volatiles.
|
|
2661
|
-
volatiles.
|
|
2662
|
-
volatiles.
|
|
2663
|
-
try log.end(ident, .DisableSilent);
|
|
2656
|
+
if (volatiles.disable_move != 0) {
|
|
2657
|
+
volatiles.disable_move = 0;
|
|
2658
|
+
volatiles.disable_duration = 0;
|
|
2659
|
+
try log.end(.{ ident, .DisableSilent });
|
|
2664
2660
|
}
|
|
2665
2661
|
if (volatiles.Confusion) {
|
|
2666
2662
|
// volatiles.confusion is left unchanged
|
|
2667
2663
|
volatiles.Confusion = false;
|
|
2668
|
-
try log.end(ident, .ConfusionSilent);
|
|
2664
|
+
try log.end(.{ ident, .ConfusionSilent });
|
|
2669
2665
|
}
|
|
2670
2666
|
if (volatiles.Mist) {
|
|
2671
2667
|
volatiles.Mist = false;
|
|
2672
|
-
try log.end(ident, .MistSilent);
|
|
2668
|
+
try log.end(.{ ident, .MistSilent });
|
|
2673
2669
|
}
|
|
2674
2670
|
if (volatiles.FocusEnergy) {
|
|
2675
2671
|
volatiles.FocusEnergy = false;
|
|
2676
|
-
try log.end(ident, .FocusEnergySilent);
|
|
2672
|
+
try log.end(.{ ident, .FocusEnergySilent });
|
|
2677
2673
|
}
|
|
2678
2674
|
if (volatiles.LeechSeed) {
|
|
2679
2675
|
volatiles.LeechSeed = false;
|
|
2680
|
-
try log.end(ident, .LeechSeedSilent);
|
|
2676
|
+
try log.end(.{ ident, .LeechSeedSilent });
|
|
2681
2677
|
}
|
|
2682
2678
|
if (!showdown and volatiles.Toxic) {
|
|
2683
2679
|
volatiles.Toxic = false;
|
|
@@ -2685,22 +2681,22 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2685
2681
|
}
|
|
2686
2682
|
if (volatiles.LightScreen) {
|
|
2687
2683
|
volatiles.LightScreen = false;
|
|
2688
|
-
try log.end(ident, .LightScreenSilent);
|
|
2684
|
+
try log.end(.{ ident, .LightScreenSilent });
|
|
2689
2685
|
}
|
|
2690
2686
|
if (volatiles.Reflect) {
|
|
2691
2687
|
volatiles.Reflect = false;
|
|
2692
|
-
try log.end(ident, .ReflectSilent);
|
|
2688
|
+
try log.end(.{ ident, .ReflectSilent });
|
|
2693
2689
|
}
|
|
2694
2690
|
if (showdown and volatiles.Toxic) {
|
|
2695
2691
|
volatiles.Toxic = false;
|
|
2696
2692
|
// Pokémon Showdown erroneously clears the toxic counter
|
|
2697
2693
|
volatiles.toxic = 0;
|
|
2698
|
-
try log.end(ident, .ToxicSilent);
|
|
2694
|
+
try log.end(.{ ident, .ToxicSilent });
|
|
2699
2695
|
}
|
|
2700
2696
|
}
|
|
2701
2697
|
|
|
2702
2698
|
pub const Rolls = struct {
|
|
2703
|
-
|
|
2699
|
+
fn speedTie(battle: anytype, options: anytype) !bool {
|
|
2704
2700
|
const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
|
|
2705
2701
|
player == .P1
|
|
2706
2702
|
else if (showdown)
|
|
@@ -2712,7 +2708,7 @@ pub const Rolls = struct {
|
|
|
2712
2708
|
return p1;
|
|
2713
2709
|
}
|
|
2714
2710
|
|
|
2715
|
-
|
|
2711
|
+
fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
|
|
2716
2712
|
const crit = if (options.calc.overridden(player, .critical_hit)) |val|
|
|
2717
2713
|
val == .true
|
|
2718
2714
|
else if (showdown)
|
|
@@ -2724,7 +2720,7 @@ pub const Rolls = struct {
|
|
|
2724
2720
|
return crit;
|
|
2725
2721
|
}
|
|
2726
2722
|
|
|
2727
|
-
|
|
2723
|
+
fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2728
2724
|
const roll = if (options.calc.overridden(player, .damage)) |val|
|
|
2729
2725
|
val
|
|
2730
2726
|
else roll: {
|
|
@@ -2740,7 +2736,7 @@ pub const Rolls = struct {
|
|
|
2740
2736
|
return roll;
|
|
2741
2737
|
}
|
|
2742
2738
|
|
|
2743
|
-
|
|
2739
|
+
fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) bool {
|
|
2744
2740
|
const ok = if (options.calc.overridden(player, .hit)) |val|
|
|
2745
2741
|
val == .true
|
|
2746
2742
|
else if (showdown)
|
|
@@ -2752,7 +2748,7 @@ pub const Rolls = struct {
|
|
|
2752
2748
|
return ok;
|
|
2753
2749
|
}
|
|
2754
2750
|
|
|
2755
|
-
|
|
2751
|
+
fn confused(battle: anytype, player: Player, options: anytype) !bool {
|
|
2756
2752
|
const cfz = if (options.calc.overridden(player, .confused)) |val|
|
|
2757
2753
|
val == .true
|
|
2758
2754
|
else if (showdown)
|
|
@@ -2764,7 +2760,7 @@ pub const Rolls = struct {
|
|
|
2764
2760
|
return cfz;
|
|
2765
2761
|
}
|
|
2766
2762
|
|
|
2767
|
-
|
|
2763
|
+
fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
|
|
2768
2764
|
const par = if (options.calc.overridden(player, .paralyzed)) |val|
|
|
2769
2765
|
val == .true
|
|
2770
2766
|
else if (showdown)
|
|
@@ -2776,7 +2772,7 @@ pub const Rolls = struct {
|
|
|
2776
2772
|
return par;
|
|
2777
2773
|
}
|
|
2778
2774
|
|
|
2779
|
-
|
|
2775
|
+
fn confusionChance(battle: anytype, player: Player, options: anytype) !bool {
|
|
2780
2776
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2781
2777
|
val == .true
|
|
2782
2778
|
else if (showdown)
|
|
@@ -2788,7 +2784,7 @@ pub const Rolls = struct {
|
|
|
2788
2784
|
return proc;
|
|
2789
2785
|
}
|
|
2790
2786
|
|
|
2791
|
-
|
|
2787
|
+
fn secondaryChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2792
2788
|
const rate: u8 = if (low) 26 else 77;
|
|
2793
2789
|
|
|
2794
2790
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
@@ -2802,7 +2798,7 @@ pub const Rolls = struct {
|
|
|
2802
2798
|
return proc;
|
|
2803
2799
|
}
|
|
2804
2800
|
|
|
2805
|
-
|
|
2801
|
+
fn poisonChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2806
2802
|
const rate: u8 = if (low) 52 else 103;
|
|
2807
2803
|
|
|
2808
2804
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
@@ -2816,7 +2812,7 @@ pub const Rolls = struct {
|
|
|
2816
2812
|
return proc;
|
|
2817
2813
|
}
|
|
2818
2814
|
|
|
2819
|
-
|
|
2815
|
+
fn unboost(battle: anytype, player: Player, options: anytype) !bool {
|
|
2820
2816
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2821
2817
|
val == .true
|
|
2822
2818
|
else if (showdown)
|
|
@@ -2828,7 +2824,7 @@ pub const Rolls = struct {
|
|
|
2828
2824
|
return proc;
|
|
2829
2825
|
}
|
|
2830
2826
|
|
|
2831
|
-
|
|
2827
|
+
fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
2832
2828
|
const move: Move = if (options.calc.overridden(player, .metronome)) |val|
|
|
2833
2829
|
val
|
|
2834
2830
|
else if (showdown)
|
|
@@ -2846,7 +2842,7 @@ pub const Rolls = struct {
|
|
|
2846
2842
|
return move;
|
|
2847
2843
|
}
|
|
2848
2844
|
|
|
2849
|
-
|
|
2845
|
+
fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
|
|
2850
2846
|
const power = if (options.calc.overridden(player, .psywave)) |val|
|
|
2851
2847
|
val - 1
|
|
2852
2848
|
else if (showdown)
|
|
@@ -2863,7 +2859,7 @@ pub const Rolls = struct {
|
|
|
2863
2859
|
return power;
|
|
2864
2860
|
}
|
|
2865
2861
|
|
|
2866
|
-
|
|
2862
|
+
fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2867
2863
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2868
2864
|
@intCast(val)
|
|
2869
2865
|
else if (showdown)
|
|
@@ -2880,7 +2876,7 @@ pub const Rolls = struct {
|
|
|
2880
2876
|
return duration;
|
|
2881
2877
|
}
|
|
2882
2878
|
|
|
2883
|
-
|
|
2879
|
+
fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
|
|
2884
2880
|
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2885
2881
|
val
|
|
2886
2882
|
else if (showdown)
|
|
@@ -2893,7 +2889,7 @@ pub const Rolls = struct {
|
|
|
2893
2889
|
return duration;
|
|
2894
2890
|
}
|
|
2895
2891
|
|
|
2896
|
-
|
|
2892
|
+
fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
|
|
2897
2893
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2898
2894
|
@intCast(val)
|
|
2899
2895
|
else if (showdown)
|
|
@@ -2906,7 +2902,7 @@ pub const Rolls = struct {
|
|
|
2906
2902
|
return duration;
|
|
2907
2903
|
}
|
|
2908
2904
|
|
|
2909
|
-
|
|
2905
|
+
fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2910
2906
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2911
2907
|
@intCast(val)
|
|
2912
2908
|
else if (showdown)
|
|
@@ -2921,7 +2917,7 @@ pub const Rolls = struct {
|
|
|
2921
2917
|
|
|
2922
2918
|
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
2923
2919
|
|
|
2924
|
-
|
|
2920
|
+
fn distribution(
|
|
2925
2921
|
battle: anytype,
|
|
2926
2922
|
comptime effect: Move.Effect,
|
|
2927
2923
|
player: Player,
|
|
@@ -3096,7 +3092,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3096
3092
|
if (m.id == if (active.volatiles.Bide) .Bide else side.last_selected_move) {
|
|
3097
3093
|
// Pokémon Showdown displays Struggle if limited to Bide but unable to pick
|
|
3098
3094
|
const struggle =
|
|
3099
|
-
m.id == .Bide and (m.pp == 0 or active.volatiles.
|
|
3095
|
+
m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
|
|
3100
3096
|
const s = if (struggle) 0 else slot;
|
|
3101
3097
|
out[n] = .{ .type = .Move, .data = s };
|
|
3102
3098
|
n += 1;
|
|
@@ -3111,7 +3107,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3111
3107
|
const m = active.moves[slot - 1];
|
|
3112
3108
|
if (m.id == .None) break;
|
|
3113
3109
|
if (m.pp == 0) continue;
|
|
3114
|
-
if (active.volatiles.
|
|
3110
|
+
if (active.volatiles.disable_move == slot) continue;
|
|
3115
3111
|
out[n] = .{ .type = .Move, .data = slot };
|
|
3116
3112
|
n += 1;
|
|
3117
3113
|
}
|