@pkmn/engine 0.1.0-dev.dfd46bbb → 0.1.0-dev.e0e37c1b
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/tools/debug.js +3 -1
- package/build/tools/debug.js.map +1 -1
- package/package.json +3 -3
- package/src/lib/common/protocol.zig +331 -331
- package/src/lib/gen1/mechanics.zig +199 -199
- package/src/lib/gen2/README.md +0 -1
- package/src/lib/gen2/data.zig +1 -1
- package/src/lib/gen2/generated.zig +5 -11
- package/src/lib/gen2/mechanics.zig +522 -308
|
@@ -26,9 +26,9 @@ const Result = common.Result;
|
|
|
26
26
|
|
|
27
27
|
const Activate = protocol.Activate;
|
|
28
28
|
const Boost = protocol.Boost;
|
|
29
|
-
const Cant = protocol.Cant;
|
|
30
29
|
const Damage = protocol.Damage;
|
|
31
30
|
const End = protocol.End;
|
|
31
|
+
const Fail = protocol.Fail;
|
|
32
32
|
const Heal = protocol.Heal;
|
|
33
33
|
const Start = protocol.Start;
|
|
34
34
|
|
|
@@ -37,6 +37,7 @@ const Gen12 = rng.Gen12;
|
|
|
37
37
|
const PointerType = util.PointerType;
|
|
38
38
|
|
|
39
39
|
const ActivePokemon = data.ActivePokemon;
|
|
40
|
+
const Boosts = data.Boosts;
|
|
40
41
|
const Effectiveness = data.Effectiveness;
|
|
41
42
|
const Item = data.Item;
|
|
42
43
|
const Move = data.Move;
|
|
@@ -96,10 +97,13 @@ const CRITICAL_HITS = &[_]u8{
|
|
|
96
97
|
|
|
97
98
|
const MAX_STAT_VALUE = 999;
|
|
98
99
|
|
|
100
|
+
const STATS = [_][]const u8{ "atk", "def", "spe", "spa", "spd" };
|
|
101
|
+
|
|
99
102
|
pub const State = struct {
|
|
100
103
|
damage: u16 = 0, // wCurDamage
|
|
101
104
|
effectiveness: u16 = Effectiveness.neutral, // wTypeModifier
|
|
102
105
|
bp: u8, // wPlayerMoveStructPower
|
|
106
|
+
type: Type, // BATTLE_VARS_MOVE_TYPE
|
|
103
107
|
accuracy: u8, // wEnemyMoveStruct + MOVE_ACC
|
|
104
108
|
move: Move, // wCurPlayerMove
|
|
105
109
|
from: Move = .None,
|
|
@@ -111,7 +115,7 @@ pub const State = struct {
|
|
|
111
115
|
proc: bool = true, // wEffectFailed
|
|
112
116
|
|
|
113
117
|
comptime {
|
|
114
|
-
assert(@sizeOf(State) ==
|
|
118
|
+
assert(@sizeOf(State) == 16);
|
|
115
119
|
}
|
|
116
120
|
|
|
117
121
|
pub fn immune(self: *State) bool {
|
|
@@ -248,15 +252,15 @@ fn switchIn(battle: anytype, player: Player, slot: u8, kind: SwitchIn, options:
|
|
|
248
252
|
statusModify(incoming.status, &active.stats);
|
|
249
253
|
|
|
250
254
|
if (kind == .Drag) {
|
|
251
|
-
try options.log.drag(battle.active(player), incoming);
|
|
255
|
+
try options.log.drag(.{ battle.active(player), incoming });
|
|
252
256
|
} else {
|
|
253
|
-
try options.log.switched(battle.active(player), incoming);
|
|
257
|
+
try options.log.switched(.{ battle.active(player), incoming });
|
|
254
258
|
}
|
|
255
259
|
options.chance.switched(player, side.pokemon[0].position, out);
|
|
256
260
|
|
|
257
261
|
if (side.conditions.Spikes and !active.types.includes(.Flying)) {
|
|
258
262
|
incoming.hp -|= @max(incoming.stats.hp / 8, 1);
|
|
259
|
-
try options.log.damage(battle.active(player), incoming, .Spikes);
|
|
263
|
+
try options.log.damage(.{ battle.active(player), incoming, .Spikes });
|
|
260
264
|
}
|
|
261
265
|
}
|
|
262
266
|
|
|
@@ -405,8 +409,14 @@ fn executeMove(
|
|
|
405
409
|
|
|
406
410
|
// FIXME: need damage from doTurn
|
|
407
411
|
const m = Move.get(move);
|
|
408
|
-
var state: State =
|
|
409
|
-
.
|
|
412
|
+
var state: State = .{
|
|
413
|
+
.move = move,
|
|
414
|
+
.mslot = mslot,
|
|
415
|
+
.first = first,
|
|
416
|
+
.accuracy = m.accuracy,
|
|
417
|
+
.bp = m.bp,
|
|
418
|
+
.type = m.type,
|
|
419
|
+
};
|
|
410
420
|
try generated.runMove(battle, player, &state, options);
|
|
411
421
|
return null;
|
|
412
422
|
}
|
|
@@ -423,7 +433,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
423
433
|
if (volatiles.Recharging) {
|
|
424
434
|
volatiles.Recharging = false;
|
|
425
435
|
resetCant(volatiles);
|
|
426
|
-
try log.cant(ident, .Recharge);
|
|
436
|
+
try log.cant(.{ ident, .Recharge });
|
|
427
437
|
return false;
|
|
428
438
|
}
|
|
429
439
|
|
|
@@ -442,12 +452,12 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
442
452
|
}
|
|
443
453
|
|
|
444
454
|
if (duration == 0) {
|
|
445
|
-
try log.curestatus(ident, before, .Message);
|
|
455
|
+
try log.curestatus(.{ ident, before, .Message });
|
|
446
456
|
stored.status = 0; // clears EXT if present
|
|
447
457
|
resetCant(volatiles);
|
|
448
458
|
volatiles.Nightmare = false;
|
|
449
459
|
} else {
|
|
450
|
-
try log.cant(ident, .Sleep);
|
|
460
|
+
try log.cant(.{ ident, .Sleep });
|
|
451
461
|
if (move == .Snore or move == .SleepTalk) break :slp;
|
|
452
462
|
resetCant(volatiles);
|
|
453
463
|
return false;
|
|
@@ -456,7 +466,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
456
466
|
|
|
457
467
|
if (Status.is(stored.status, .FRZ) and !(move == .FlameWheel or move == .SacredFire)) {
|
|
458
468
|
resetCant(volatiles);
|
|
459
|
-
try log.cant(ident, .Freeze);
|
|
469
|
+
try log.cant(.{ ident, .Freeze });
|
|
460
470
|
return false;
|
|
461
471
|
}
|
|
462
472
|
|
|
@@ -466,7 +476,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
466
476
|
// if (!showdown) volatiles.Flinch = false;
|
|
467
477
|
volatiles.Flinch = false;
|
|
468
478
|
resetCant(volatiles);
|
|
469
|
-
try log.cant(ident, .Flinch);
|
|
479
|
+
try log.cant(.{ ident, .Flinch });
|
|
470
480
|
return false;
|
|
471
481
|
}
|
|
472
482
|
|
|
@@ -480,7 +490,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
480
490
|
|
|
481
491
|
if (volatiles.disable.duration == 0) {
|
|
482
492
|
volatiles.disable.move = 0;
|
|
483
|
-
try log.end(ident, .Disable);
|
|
493
|
+
try log.end(.{ ident, .Disable });
|
|
484
494
|
}
|
|
485
495
|
}
|
|
486
496
|
|
|
@@ -496,9 +506,9 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
496
506
|
|
|
497
507
|
if (volatiles.confusion == 0) {
|
|
498
508
|
volatiles.Confusion = false;
|
|
499
|
-
try log.end(ident, .Confusion);
|
|
509
|
+
try log.end(.{ ident, .Confusion });
|
|
500
510
|
} else {
|
|
501
|
-
try log.activate(ident, .Confusion);
|
|
511
|
+
try log.activate(.{ ident, .Confusion });
|
|
502
512
|
|
|
503
513
|
if (try Rolls.confused(battle, player, options)) {
|
|
504
514
|
volatiles.BeatUp = false;
|
|
@@ -513,11 +523,11 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
513
523
|
}
|
|
514
524
|
|
|
515
525
|
if (volatiles.Attract) {
|
|
516
|
-
try log.activate(ident, .Attract);
|
|
526
|
+
try log.activate(.{ ident, .Attract });
|
|
517
527
|
|
|
518
528
|
if (try Rolls.attract(battle, player, options)) {
|
|
519
529
|
resetCant(volatiles);
|
|
520
|
-
try log.cant(ident, .Attract);
|
|
530
|
+
try log.cant(.{ ident, .Attract });
|
|
521
531
|
return false;
|
|
522
532
|
}
|
|
523
533
|
}
|
|
@@ -528,14 +538,14 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
528
538
|
if (m != .None and m == move) {
|
|
529
539
|
side.active.volatiles.Charging = false;
|
|
530
540
|
resetCant(volatiles);
|
|
531
|
-
try options.log.
|
|
541
|
+
try options.log.cant(.{ ident, .Disable, move });
|
|
532
542
|
return false;
|
|
533
543
|
}
|
|
534
544
|
}
|
|
535
545
|
|
|
536
546
|
if (Status.is(stored.status, .PAR) and try Rolls.paralyzed(battle, player, options)) {
|
|
537
547
|
resetCant(volatiles);
|
|
538
|
-
try log.cant(ident, .Paralysis);
|
|
548
|
+
try log.cant(.{ ident, .Paralysis });
|
|
539
549
|
return false;
|
|
540
550
|
}
|
|
541
551
|
|
|
@@ -559,7 +569,7 @@ pub fn canMove(battle: anytype, player: Player, state: *State, options: anytype)
|
|
|
559
569
|
|
|
560
570
|
// FIXME: pass in target to avoid lookup?
|
|
561
571
|
const target = if (Move.get(state.move).target == .Self) player else player.foe();
|
|
562
|
-
try options.log.
|
|
572
|
+
try options.log.move(.{ battle.active(player), state.move, battle.active(target), state.from });
|
|
563
573
|
|
|
564
574
|
const charging = false; // TODO charging uses doUse
|
|
565
575
|
const skip_pp = charging or state.move == .Struggle or
|
|
@@ -638,7 +648,7 @@ pub fn afterMove(
|
|
|
638
648
|
|
|
639
649
|
pub fn checkCriticalHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
640
650
|
const move = Move.get(state.move);
|
|
641
|
-
if (
|
|
651
|
+
if (state.bp == 0) return;
|
|
642
652
|
|
|
643
653
|
const side = battle.side(player);
|
|
644
654
|
const pokemon = side.stored();
|
|
@@ -676,31 +686,30 @@ pub fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype)
|
|
|
676
686
|
const side = battle.side(player);
|
|
677
687
|
const foe = battle.foe(player);
|
|
678
688
|
const types = foe.active.types;
|
|
679
|
-
const move = Move.get(state.move);
|
|
680
689
|
|
|
681
690
|
var d = state.damage;
|
|
682
|
-
if ((
|
|
683
|
-
(
|
|
691
|
+
if ((state.type == .Water and battle.field.weather == .Rain) or
|
|
692
|
+
(state.type == .Fire and battle.field.weather == .Sun))
|
|
684
693
|
{
|
|
685
694
|
d = d *% @intFromEnum(Effectiveness.Super) / 10;
|
|
686
|
-
} else if ((
|
|
687
|
-
((
|
|
695
|
+
} else if ((state.type == .Water and battle.field.weather == .Sun) or
|
|
696
|
+
((state.type == .Fire or state.move == .SolarBeam) and battle.field.weather == .Rain))
|
|
688
697
|
{
|
|
689
698
|
d = d *% @intFromEnum(Effectiveness.Resisted) / 10;
|
|
690
699
|
}
|
|
691
700
|
|
|
692
|
-
if (side.active.types.includes(
|
|
701
|
+
if (side.active.types.includes(state.type)) d +%= d / 2;
|
|
693
702
|
|
|
694
703
|
const neutral = @intFromEnum(Effectiveness.Neutral);
|
|
695
|
-
const foresight = foe.active.volatiles.Foresight and FORESIGHT.includes(
|
|
704
|
+
const foresight = foe.active.volatiles.Foresight and FORESIGHT.includes(state.type);
|
|
696
705
|
const eff1: u16 = if (foresight and types.type1 == .Ghost)
|
|
697
706
|
neutral
|
|
698
707
|
else
|
|
699
|
-
@intFromEnum(
|
|
708
|
+
@intFromEnum(state.type.effectiveness(types.type1));
|
|
700
709
|
const eff2: u16 = if (foresight and types.type2 == .Ghost)
|
|
701
710
|
neutral
|
|
702
711
|
else
|
|
703
|
-
@intFromEnum(
|
|
712
|
+
@intFromEnum(state.type.effectiveness(types.type2));
|
|
704
713
|
|
|
705
714
|
// Type effectiveness matchup precedence only matters with (NVE, SE)
|
|
706
715
|
if (!showdown and (eff1 + eff2) == Effectiveness.mismatch and
|
|
@@ -840,13 +849,13 @@ fn checkFaint(
|
|
|
840
849
|
if (foe_fainted) try faint(battle, player.foe(), !more, options);
|
|
841
850
|
|
|
842
851
|
if (player_out and foe_out) {
|
|
843
|
-
try options.log.tie();
|
|
852
|
+
try options.log.tie(.{});
|
|
844
853
|
return Result.Tie;
|
|
845
854
|
} else if (player_out) {
|
|
846
|
-
try options.log.win(player.foe());
|
|
855
|
+
try options.log.win(.{player.foe()});
|
|
847
856
|
return if (player == .P1) Result.Lose else Result.Win;
|
|
848
857
|
} else if (foe_out) {
|
|
849
|
-
try options.log.win(player);
|
|
858
|
+
try options.log.win(.{player});
|
|
850
859
|
return if (player == .P1) Result.Win else Result.Lose;
|
|
851
860
|
}
|
|
852
861
|
|
|
@@ -876,19 +885,19 @@ fn faint(battle: anytype, player: Player, done: bool, options: anytype) !void {
|
|
|
876
885
|
side.stored().stats.spe;
|
|
877
886
|
}
|
|
878
887
|
|
|
879
|
-
try options.log.faint(battle.active(player), done);
|
|
888
|
+
try options.log.faint(.{ battle.active(player), done });
|
|
880
889
|
options.calc.capped(player);
|
|
881
890
|
}
|
|
882
891
|
|
|
883
892
|
// TODO: see if we can move *before* applyDamage and include failuretext logic
|
|
884
893
|
pub fn reportOutcome(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
885
894
|
const foe_ident = battle.active(player.foe());
|
|
886
|
-
if (state.crit) try options.log.crit(foe_ident);
|
|
895
|
+
if (state.crit) try options.log.crit(.{foe_ident});
|
|
887
896
|
if (!state.immune()) {
|
|
888
897
|
if (state.effectiveness > Effectiveness.neutral) {
|
|
889
|
-
try options.log.supereffective(foe_ident);
|
|
898
|
+
try options.log.supereffective(.{foe_ident});
|
|
890
899
|
} else if (state.effectiveness < Effectiveness.neutral) {
|
|
891
|
-
try options.log.resisted(foe_ident);
|
|
900
|
+
try options.log.resisted(.{foe_ident});
|
|
892
901
|
}
|
|
893
902
|
}
|
|
894
903
|
}
|
|
@@ -921,8 +930,8 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !bool {
|
|
|
921
930
|
}
|
|
922
931
|
|
|
923
932
|
stored.hp -|= damage;
|
|
924
|
-
// Pokémon Showdown uses
|
|
925
|
-
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
933
|
+
// Pokémon Showdown uses damage [of] here but its not relevant in Generation II
|
|
934
|
+
try options.log.damage(.{ ident, stored, if (brn) Damage.Burn else Damage.Poison });
|
|
926
935
|
if (stored.hp == 0) return true;
|
|
927
936
|
}
|
|
928
937
|
|
|
@@ -938,27 +947,27 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !bool {
|
|
|
938
947
|
|
|
939
948
|
const damage = @max(stored.stats.hp / 8, 1);
|
|
940
949
|
stored.hp -|= damage;
|
|
941
|
-
// As above, Pokémon Showdown uses
|
|
942
|
-
try options.log.damage(ident, stored, .LeechSeed);
|
|
950
|
+
// As above, Pokémon Showdown uses damage [of] but its not relevant
|
|
951
|
+
try options.log.damage(.{ ident, stored, .LeechSeed });
|
|
943
952
|
|
|
944
953
|
const before = foe_stored.hp;
|
|
945
954
|
foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
|
|
946
955
|
// Pokémon Showdown uses the less specific heal here instead of drain... because reasons?
|
|
947
|
-
if (foe_stored.hp > before) try options.log.heal(foe_ident, foe_stored, .Silent);
|
|
956
|
+
if (foe_stored.hp > before) try options.log.heal(.{ foe_ident, foe_stored, .Silent });
|
|
948
957
|
if (stored.hp == 0) return true;
|
|
949
958
|
}
|
|
950
959
|
|
|
951
960
|
if (volatiles.Nightmare) {
|
|
952
961
|
stored.hp -= @max(stored.stats.hp / 4, 1);
|
|
953
962
|
// ibid
|
|
954
|
-
try options.log.damage(ident, stored, .Nightmare);
|
|
963
|
+
try options.log.damage(.{ ident, stored, .Nightmare });
|
|
955
964
|
if (stored.hp == 0) return true;
|
|
956
965
|
}
|
|
957
966
|
|
|
958
967
|
if (volatiles.Curse) {
|
|
959
968
|
stored.hp -= @max(stored.stats.hp / 4, 1);
|
|
960
969
|
// ibid
|
|
961
|
-
try options.log.damage(ident, stored, .Curse);
|
|
970
|
+
try options.log.damage(.{ ident, stored, .Curse });
|
|
962
971
|
if (stored.hp == 0) return true;
|
|
963
972
|
}
|
|
964
973
|
|
|
@@ -979,7 +988,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
979
988
|
future_sight.count -= 1;
|
|
980
989
|
assert(future_sight.count != 0);
|
|
981
990
|
if (future_sight.count == 1) {
|
|
982
|
-
try options.log.end(battle.active(player), .FutureSight);
|
|
991
|
+
try options.log.end(.{ battle.active(player), .FutureSight });
|
|
983
992
|
// TODO doMove
|
|
984
993
|
}
|
|
985
994
|
}
|
|
@@ -993,16 +1002,20 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
993
1002
|
|
|
994
1003
|
if (battle.field.weather_duration == 0) {
|
|
995
1004
|
battle.field.weather = .None;
|
|
996
|
-
try options.log.weather(battle.field.weather, .None);
|
|
1005
|
+
try options.log.weather(.{ battle.field.weather, protocol.Weather.None });
|
|
997
1006
|
} else {
|
|
998
|
-
try options.log.weather(battle.field.weather, .Upkeep);
|
|
1007
|
+
try options.log.weather(.{ battle.field.weather, protocol.Weather.Upkeep });
|
|
999
1008
|
if (battle.field.weather == .Sandstorm) {
|
|
1000
1009
|
inline for (players) |player| {
|
|
1001
1010
|
var side = battle.side(player);
|
|
1002
1011
|
const active = side.active;
|
|
1003
1012
|
if (!(active.volatiles.Underground or active.types.sandstormImmune())) {
|
|
1004
1013
|
side.stored().hp -|= @max(side.stored().stats.hp / 8, 1);
|
|
1005
|
-
try options.log.damage(
|
|
1014
|
+
try options.log.damage(.{
|
|
1015
|
+
battle.active(player),
|
|
1016
|
+
side.stored(),
|
|
1017
|
+
.Sandstorm,
|
|
1018
|
+
});
|
|
1006
1019
|
}
|
|
1007
1020
|
}
|
|
1008
1021
|
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
@@ -1014,19 +1027,24 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1014
1027
|
inline for (players) |player| {
|
|
1015
1028
|
var side = battle.side(player);
|
|
1016
1029
|
var volatiles = &side.active.volatiles;
|
|
1030
|
+
const ident = battle.active(player);
|
|
1017
1031
|
|
|
1018
1032
|
if (volatiles.bind.duration > 0 and !volatiles.Substitute) {
|
|
1019
1033
|
volatiles.bind.duration -= 1;
|
|
1020
1034
|
if (volatiles.bind.duration == 0) {
|
|
1021
1035
|
const reason = @intFromEnum(End.Bind) + volatiles.bind.reason - 1;
|
|
1022
|
-
try options.log.end(
|
|
1036
|
+
try options.log.end(.{ ident, @as(End, @enumFromInt(reason)) });
|
|
1023
1037
|
} else {
|
|
1024
1038
|
var reason = @intFromEnum(Activate.Bind) + volatiles.bind.reason - 1;
|
|
1025
|
-
try options.log.activate(
|
|
1039
|
+
try options.log.activate(.{ ident, @as(Activate, @enumFromInt(reason)) });
|
|
1026
1040
|
|
|
1027
1041
|
reason = @intFromEnum(Damage.Bind) + volatiles.bind.reason - 1;
|
|
1028
1042
|
side.stored().hp -|= @max(side.stored().stats.hp / 16, 1);
|
|
1029
|
-
try options.log.damage(
|
|
1043
|
+
try options.log.damage(.{
|
|
1044
|
+
ident,
|
|
1045
|
+
side.stored(),
|
|
1046
|
+
@as(Damage, @enumFromInt(reason)),
|
|
1047
|
+
});
|
|
1030
1048
|
}
|
|
1031
1049
|
}
|
|
1032
1050
|
}
|
|
@@ -1043,7 +1061,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1043
1061
|
|
|
1044
1062
|
assert(volatiles.perish_song < 3);
|
|
1045
1063
|
const reason = @intFromEnum(Start.PerishSong0) + volatiles.perish_song;
|
|
1046
|
-
try options.log.start(battle.active(player), @enumFromInt(reason));
|
|
1064
|
+
try options.log.start(.{ battle.active(player), @as(Start, @enumFromInt(reason)) });
|
|
1047
1065
|
|
|
1048
1066
|
if (volatiles.perish_song == 0) {
|
|
1049
1067
|
volatiles.PerishSong = false;
|
|
@@ -1060,7 +1078,9 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1060
1078
|
if (stored.item == .Leftovers) {
|
|
1061
1079
|
const before = stored.hp;
|
|
1062
1080
|
stored.hp = @min(stored.hp + @max(stored.stats.hp / 16, 1), stored.stats.hp);
|
|
1063
|
-
if (stored.hp > before)
|
|
1081
|
+
if (stored.hp > before) {
|
|
1082
|
+
try options.log.heal(.{ battle.active(player), stored, .Leftovers });
|
|
1083
|
+
}
|
|
1064
1084
|
}
|
|
1065
1085
|
}
|
|
1066
1086
|
|
|
@@ -1081,7 +1101,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1081
1101
|
!side.active.volatiles.frozen and
|
|
1082
1102
|
try Rolls.defrost(battle, player, options))
|
|
1083
1103
|
{
|
|
1084
|
-
try options.log.curestatus(battle.active(player), side.stored().status, .Message);
|
|
1104
|
+
try options.log.curestatus(.{ battle.active(player), side.stored().status, .Message });
|
|
1085
1105
|
side.stored().status = 0;
|
|
1086
1106
|
}
|
|
1087
1107
|
}
|
|
@@ -1122,14 +1142,14 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1122
1142
|
stored.hp < stored.stats.hp;
|
|
1123
1143
|
if (proc) {
|
|
1124
1144
|
assert(stored.hp != 0);
|
|
1125
|
-
try options.log.enditem(ident, stored.item, .Eat);
|
|
1145
|
+
try options.log.enditem(.{ ident, stored.item, .Eat });
|
|
1126
1146
|
stored.item = .None;
|
|
1127
1147
|
|
|
1128
1148
|
const offset = num - @intFromEnum(Item.Berry);
|
|
1129
1149
|
assert(offset < 3);
|
|
1130
1150
|
stored.hp = @min(stored.hp + ((offset + 1) * 10), stored.stats.hp);
|
|
1131
1151
|
const reason: Heal = @enumFromInt(@intFromEnum(Heal.Berry) + offset);
|
|
1132
|
-
try options.log.heal(ident, stored, reason);
|
|
1152
|
+
try options.log.heal(.{ ident, stored, reason });
|
|
1133
1153
|
}
|
|
1134
1154
|
} else if (num < @intFromEnum(Item.BitterBerry)) {
|
|
1135
1155
|
const proc = if (stored.item == .MiracleBerry)
|
|
@@ -1139,7 +1159,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1139
1159
|
|
|
1140
1160
|
if (proc) {
|
|
1141
1161
|
assert(Status.any(stored.status));
|
|
1142
|
-
try options.log.enditem(ident, stored.item, .Eat);
|
|
1162
|
+
try options.log.enditem(.{ ident, stored.item, .Eat });
|
|
1143
1163
|
stored.item = .None;
|
|
1144
1164
|
|
|
1145
1165
|
// FIXME wtf?
|
|
@@ -1147,21 +1167,21 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1147
1167
|
// side.active.volatiles.Nightmare = false;
|
|
1148
1168
|
if (stored.item == .MiracleBerry) {
|
|
1149
1169
|
side.active.volatiles.Confusion = false;
|
|
1150
|
-
try options.log.end(ident, .Confusion);
|
|
1170
|
+
try options.log.end(.{ ident, .Confusion });
|
|
1151
1171
|
}
|
|
1152
1172
|
stored.status = 0;
|
|
1153
1173
|
|
|
1154
1174
|
// FIXME recalc stats
|
|
1155
1175
|
|
|
1156
|
-
try options.log.curestatus(ident, stored.status, .Message);
|
|
1176
|
+
try options.log.curestatus(.{ ident, stored.status, .Message });
|
|
1157
1177
|
}
|
|
1158
1178
|
} else if (num == @intFromEnum(Item.BitterBerry)) {
|
|
1159
1179
|
if (side.active.volatiles.Confusion) {
|
|
1160
|
-
try options.log.enditem(ident, stored.item, .Eat);
|
|
1180
|
+
try options.log.enditem(.{ ident, stored.item, .Eat });
|
|
1161
1181
|
stored.item = .None;
|
|
1162
1182
|
|
|
1163
1183
|
side.active.volatiles.Confusion = false;
|
|
1164
|
-
try options.log.end(ident, .Confusion);
|
|
1184
|
+
try options.log.end(.{ ident, .Confusion });
|
|
1165
1185
|
}
|
|
1166
1186
|
}
|
|
1167
1187
|
}
|
|
@@ -1178,7 +1198,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1178
1198
|
|
|
1179
1199
|
if (volatiles.encore == 0 or side.active.move(mslot).pp == 0) {
|
|
1180
1200
|
volatiles.Encore = false;
|
|
1181
|
-
try options.log.end(battle.active(player), .Encore);
|
|
1201
|
+
try options.log.end(.{ battle.active(player), .Encore });
|
|
1182
1202
|
}
|
|
1183
1203
|
}
|
|
1184
1204
|
}
|
|
@@ -1200,7 +1220,7 @@ fn updateSideCondition(
|
|
|
1200
1220
|
|
|
1201
1221
|
if (@field(side.conditions, value) == 0) {
|
|
1202
1222
|
@field(side.conditions, key) = false;
|
|
1203
|
-
try options.log.sideend(player, @field(protocol.Side, key));
|
|
1223
|
+
try options.log.sideend(.{ player, @field(protocol.Side, key) });
|
|
1204
1224
|
}
|
|
1205
1225
|
}
|
|
1206
1226
|
}
|
|
@@ -1209,13 +1229,13 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1209
1229
|
battle.turn += 1;
|
|
1210
1230
|
|
|
1211
1231
|
if (showdown and battle.turn >= 1000) {
|
|
1212
|
-
try options.log.tie();
|
|
1232
|
+
try options.log.tie(.{});
|
|
1213
1233
|
return Result.Tie;
|
|
1214
1234
|
} else if (battle.turn >= 65535) {
|
|
1215
1235
|
return Result.Error;
|
|
1216
1236
|
}
|
|
1217
1237
|
|
|
1218
|
-
try options.log.turn(battle.turn);
|
|
1238
|
+
try options.log.turn(.{battle.turn});
|
|
1219
1239
|
|
|
1220
1240
|
return Result.Default;
|
|
1221
1241
|
}
|
|
@@ -1231,7 +1251,7 @@ pub fn buildRage(battle: anytype, player: Player, state: *State, options: anytyp
|
|
|
1231
1251
|
}
|
|
1232
1252
|
} else {
|
|
1233
1253
|
foe.active.volatiles.rage +|= 1;
|
|
1234
|
-
try options.log.activate(battle.active(player), .Rage);
|
|
1254
|
+
try options.log.activate(.{ battle.active(player), .Rage });
|
|
1235
1255
|
}
|
|
1236
1256
|
}
|
|
1237
1257
|
}
|
|
@@ -1254,7 +1274,7 @@ pub fn destinyBond(battle: anytype, player: Player, _: *State, options: anytype)
|
|
|
1254
1274
|
|
|
1255
1275
|
if (foe.active.volatiles.DestinyBond) {
|
|
1256
1276
|
battle.side(player).stored().hp = 0;
|
|
1257
|
-
try options.log.activate(battle.active(player.foe()), .DestinyBond);
|
|
1277
|
+
try options.log.activate(.{ battle.active(player.foe()), .DestinyBond });
|
|
1258
1278
|
}
|
|
1259
1279
|
return true;
|
|
1260
1280
|
}
|
|
@@ -1267,14 +1287,14 @@ pub const Effects = struct {
|
|
|
1267
1287
|
const gender: u8 = @intFromEnum(side.stored().dvs.gender);
|
|
1268
1288
|
const foe_gender: u8 = @intFromEnum(foe.stored().dvs.gender);
|
|
1269
1289
|
if (gender + foe_gender != 1) {
|
|
1270
|
-
return options.log.immune(battle.active(player.foe()), .None);
|
|
1290
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
1271
1291
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1272
|
-
try options.log.lastmiss();
|
|
1273
|
-
return options.log.miss(battle.active(player));
|
|
1292
|
+
try options.log.lastmiss(.{});
|
|
1293
|
+
return options.log.miss(.{battle.active(player)});
|
|
1274
1294
|
}
|
|
1275
1295
|
|
|
1276
1296
|
foe.active.volatiles.Attract = true;
|
|
1277
|
-
try options.log.start(battle.active(player.foe()), .Attract);
|
|
1297
|
+
try options.log.start(.{ battle.active(player.foe()), .Attract });
|
|
1278
1298
|
}
|
|
1279
1299
|
|
|
1280
1300
|
pub fn batonPass(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1287,6 +1307,13 @@ pub const Effects = struct {
|
|
|
1287
1307
|
|
|
1288
1308
|
pub fn bellyDrum(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1289
1309
|
_ = .{ battle, player, state, options }; // TODO
|
|
1310
|
+
// assert(!state.miss);
|
|
1311
|
+
// var side = battle.side(player);
|
|
1312
|
+
// try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
|
|
1313
|
+
// TODO
|
|
1314
|
+
// |-boost|p1a: Poliwag|atk|2|[silent]
|
|
1315
|
+
// |-fail|p1a: Poliwag
|
|
1316
|
+
//
|
|
1290
1317
|
}
|
|
1291
1318
|
|
|
1292
1319
|
pub const bide = struct {
|
|
@@ -1306,10 +1333,10 @@ pub const Effects = struct {
|
|
|
1306
1333
|
// }
|
|
1307
1334
|
try options.chance.attacking(player, volatiles.attacks);
|
|
1308
1335
|
|
|
1309
|
-
if (volatiles.attacks != 0) return options.log.activate(ident, .Bide);
|
|
1336
|
+
if (volatiles.attacks != 0) return options.log.activate(.{ ident, .Bide });
|
|
1310
1337
|
|
|
1311
1338
|
volatiles.Bide = false;
|
|
1312
|
-
try options.log.end(ident, .Bide);
|
|
1339
|
+
try options.log.end(.{ ident, .Bide });
|
|
1313
1340
|
|
|
1314
1341
|
state.damage = volatiles.bide *% 2;
|
|
1315
1342
|
volatiles.bide = 0;
|
|
@@ -1323,7 +1350,16 @@ pub const Effects = struct {
|
|
|
1323
1350
|
};
|
|
1324
1351
|
|
|
1325
1352
|
pub fn binding(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1326
|
-
|
|
1353
|
+
var foe = battle.foe(player);
|
|
1354
|
+
|
|
1355
|
+
if (state.miss) return;
|
|
1356
|
+
if (foe.active.volatiles.bind.duration > 0) return;
|
|
1357
|
+
if (foe.active.volatiles.Substitute) return;
|
|
1358
|
+
|
|
1359
|
+
foe.active.volatiles.bind = .{
|
|
1360
|
+
.duration = Rolls.bindingDuration(battle, player, options),
|
|
1361
|
+
.reason = Move.get(state.move).extra.protocol,
|
|
1362
|
+
};
|
|
1327
1363
|
}
|
|
1328
1364
|
|
|
1329
1365
|
pub fn burnChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1334,11 +1370,11 @@ pub const Effects = struct {
|
|
|
1334
1370
|
if (Status.any(foe_stored.status)) {
|
|
1335
1371
|
if (Status.is(foe_stored.status, .FRZ)) {
|
|
1336
1372
|
assert(Move.get(state.move).type == .Fire or state.move == .TriAttack);
|
|
1337
|
-
try options.log.curestatus(
|
|
1373
|
+
try options.log.curestatus(.{
|
|
1338
1374
|
battle.active(player.foe()),
|
|
1339
1375
|
foe_stored.status,
|
|
1340
1376
|
.Message,
|
|
1341
|
-
);
|
|
1377
|
+
});
|
|
1342
1378
|
foe_stored.status = 0;
|
|
1343
1379
|
}
|
|
1344
1380
|
return;
|
|
@@ -1353,7 +1389,8 @@ pub const Effects = struct {
|
|
|
1353
1389
|
|
|
1354
1390
|
foe_stored.status = Status.init(.BRN);
|
|
1355
1391
|
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
1356
|
-
|
|
1392
|
+
|
|
1393
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1357
1394
|
|
|
1358
1395
|
// TODO: check for status berry
|
|
1359
1396
|
}
|
|
@@ -1373,7 +1410,7 @@ pub const Effects = struct {
|
|
|
1373
1410
|
for (side.active.moves) |m| {
|
|
1374
1411
|
if (convertible(side.active, m.id)) n += 1;
|
|
1375
1412
|
}
|
|
1376
|
-
if (n == 0) return options.log.fail(battle.active(player), .None);
|
|
1413
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
1377
1414
|
|
|
1378
1415
|
side.active.types.type1 = try Rolls.conversion(battle, player, n, options);
|
|
1379
1416
|
side.active.types.type2 = side.active.types.type1;
|
|
@@ -1383,20 +1420,87 @@ pub const Effects = struct {
|
|
|
1383
1420
|
var side = battle.side(player);
|
|
1384
1421
|
|
|
1385
1422
|
const last = battle.foe(player).lastMove(false);
|
|
1386
|
-
if (last == .None) return options.log.fail(battle.active(player), .None);
|
|
1423
|
+
if (last == .None) return options.log.fail(.{ battle.active(player), .None });
|
|
1387
1424
|
const move = Move.get(last);
|
|
1388
|
-
if (move.type == .@"???") return options.log.fail(battle.active(player), .None);
|
|
1425
|
+
if (move.type == .@"???") return options.log.fail(.{ battle.active(player), .None });
|
|
1389
1426
|
|
|
1390
1427
|
side.active.types.type1 = try Rolls.conversion2(battle, player, move.type, options);
|
|
1391
1428
|
side.active.types.type2 = side.active.types.type1;
|
|
1392
1429
|
}
|
|
1393
1430
|
|
|
1394
|
-
pub fn counter(battle: anytype, player: Player, state: *State,
|
|
1395
|
-
|
|
1431
|
+
pub fn counter(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1432
|
+
assert(state.move == .Counter or state.move == .MirrorCoat);
|
|
1433
|
+
|
|
1434
|
+
const foe = battle.foe(player);
|
|
1435
|
+
|
|
1436
|
+
state.miss = true;
|
|
1437
|
+
const last = foe.lastMove(false);
|
|
1438
|
+
|
|
1439
|
+
if (last == .None) return;
|
|
1440
|
+
if (last == state.move) return;
|
|
1441
|
+
|
|
1442
|
+
const immune = state.immune();
|
|
1443
|
+
state.effectiveness = Effectiveness.neutral;
|
|
1444
|
+
if (immune) {
|
|
1445
|
+
state.damage = 0;
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
const move = Move.get(last);
|
|
1450
|
+
if (state.bp == 0) return;
|
|
1451
|
+
if (move.type.special() != (last == .MirrorCoat)) return;
|
|
1452
|
+
if (state.damage == 0) return;
|
|
1453
|
+
|
|
1454
|
+
state.damage *|= 2;
|
|
1455
|
+
state.miss = false;
|
|
1396
1456
|
}
|
|
1397
1457
|
|
|
1398
1458
|
pub fn curse(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1399
|
-
|
|
1459
|
+
var side = battle.side(player);
|
|
1460
|
+
var foe = battle.foe(player);
|
|
1461
|
+
|
|
1462
|
+
const ident = battle.active(player);
|
|
1463
|
+
const foe_ident = battle.active(player.foe());
|
|
1464
|
+
|
|
1465
|
+
if (side.active.types.includes(.Ghost)) {
|
|
1466
|
+
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1467
|
+
try options.log.lastmiss(.{});
|
|
1468
|
+
return options.log.miss(.{battle.active(player)});
|
|
1469
|
+
} else if (foe.active.volatiles.Substitute) {
|
|
1470
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1471
|
+
} else if (foe.active.volatiles.Curse) {
|
|
1472
|
+
return options.log.fail(.{ foe_ident, .None });
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
foe.active.volatiles.Curse = true;
|
|
1476
|
+
try options.log.start(.{ foe_ident, .Curse });
|
|
1477
|
+
|
|
1478
|
+
side.stored().hp -= @max(side.stored().stats.hp / 2, 1);
|
|
1479
|
+
try options.log.damage(.{ ident, side.stored(), .Curse });
|
|
1480
|
+
} else {
|
|
1481
|
+
var stats = &side.active.stats;
|
|
1482
|
+
var boosts = &side.active.boosts;
|
|
1483
|
+
|
|
1484
|
+
const max_atk = boosts.atk == 6 or stats.atk == MAX_STAT_VALUE;
|
|
1485
|
+
const max_def = boosts.def == 6 or stats.def == MAX_STAT_VALUE;
|
|
1486
|
+
if (max_atk and max_def) return options.log.fail(.{ ident, .None });
|
|
1487
|
+
|
|
1488
|
+
if (!(boosts.spe == -6 or stats.spe == 1)) {
|
|
1489
|
+
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
1490
|
+
try unboostStat(battle, side, stats, boosts, "spe", 1);
|
|
1491
|
+
try options.log.boost(.{ ident, .Speed, @as(i8, -1) });
|
|
1492
|
+
}
|
|
1493
|
+
if (!max_atk) {
|
|
1494
|
+
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
1495
|
+
try boostStat(battle, side, stats, boosts, "atk", 1);
|
|
1496
|
+
try options.log.boost(.{ ident, .Attack, @as(i8, 1) });
|
|
1497
|
+
}
|
|
1498
|
+
if (!max_def) {
|
|
1499
|
+
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
1500
|
+
try boostStat(battle, side, stats, boosts, "def", 1);
|
|
1501
|
+
try options.log.boost(.{ ident, .Defense, @as(i8, 1) });
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1400
1504
|
}
|
|
1401
1505
|
|
|
1402
1506
|
pub fn defenseCurl(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
@@ -1406,14 +1510,13 @@ pub const Effects = struct {
|
|
|
1406
1510
|
pub fn defrost(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1407
1511
|
var side = battle.side(player);
|
|
1408
1512
|
assert(Status.is(side.stored().status, .FRZ));
|
|
1409
|
-
|
|
1410
|
-
try options.log.curestatus(battle.active(player), side.stored().status, .Message);
|
|
1513
|
+
try options.log.curestatus(.{ battle.active(player), side.stored().status, .Message });
|
|
1411
1514
|
side.stored().status = 0;
|
|
1412
1515
|
}
|
|
1413
1516
|
|
|
1414
1517
|
pub fn destinyBond(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1415
1518
|
battle.side(player).active.volatiles.DestinyBond = true;
|
|
1416
|
-
try options.log.singlemove(battle.active(player), Move.DestinyBond);
|
|
1519
|
+
try options.log.singlemove(.{ battle.active(player), Move.DestinyBond });
|
|
1417
1520
|
}
|
|
1418
1521
|
|
|
1419
1522
|
pub fn disable(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
@@ -1421,14 +1524,10 @@ pub const Effects = struct {
|
|
|
1421
1524
|
var volatiles = &foe.active.volatiles;
|
|
1422
1525
|
const foe_ident = battle.active(player.foe());
|
|
1423
1526
|
|
|
1424
|
-
if (volatiles.disable.move != 0) {
|
|
1425
|
-
return options.log.fail(foe_ident, .None);
|
|
1426
|
-
}
|
|
1527
|
+
if (volatiles.disable.move != 0) return options.log.fail(.{ foe_ident, .None });
|
|
1427
1528
|
|
|
1428
1529
|
const last = battle.foe(player).lastMove(false);
|
|
1429
|
-
if (last == .None or last == .Struggle) {
|
|
1430
|
-
return options.log.fail(foe_ident, .None);
|
|
1431
|
-
}
|
|
1530
|
+
if (last == .None or last == .Struggle) return options.log.fail(.{ foe_ident, .None });
|
|
1432
1531
|
|
|
1433
1532
|
var slot: u3 = 0;
|
|
1434
1533
|
for (foe.active.moves) |m| {
|
|
@@ -1437,12 +1536,12 @@ pub const Effects = struct {
|
|
|
1437
1536
|
if (m.id == last) break;
|
|
1438
1537
|
}
|
|
1439
1538
|
const move = foe.active.move(slot);
|
|
1440
|
-
if (move.pp == 0) return options.log.fail(foe_ident, .None);
|
|
1539
|
+
if (move.pp == 0) return options.log.fail(.{ foe_ident, .None });
|
|
1441
1540
|
|
|
1442
1541
|
volatiles.disable.move = slot;
|
|
1443
1542
|
volatiles.disable.duration = Rolls.disableDuration(battle, player, options);
|
|
1444
1543
|
|
|
1445
|
-
try options.log.
|
|
1544
|
+
try options.log.start(.{ foe_ident, .Disable, move.id });
|
|
1446
1545
|
}
|
|
1447
1546
|
|
|
1448
1547
|
pub const double = struct {
|
|
@@ -1461,13 +1560,14 @@ pub const Effects = struct {
|
|
|
1461
1560
|
|
|
1462
1561
|
pub fn drainHP(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1463
1562
|
var stored = battle.side(player).stored();
|
|
1563
|
+
const ident = battle.active(player);
|
|
1464
1564
|
|
|
1465
1565
|
assert(state.damage != 0);
|
|
1466
1566
|
|
|
1467
1567
|
if (stored.hp == stored.stats.hp) return;
|
|
1468
1568
|
stored.hp = @min(stored.stats.hp, stored.hp + @max(state.damage / 2, 1));
|
|
1469
1569
|
|
|
1470
|
-
try options.log.
|
|
1570
|
+
try options.log.heal(.{ ident, stored, .Drain, battle.active(player.foe()) });
|
|
1471
1571
|
}
|
|
1472
1572
|
|
|
1473
1573
|
pub fn encore(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1479,7 +1579,7 @@ pub const Effects = struct {
|
|
|
1479
1579
|
const failed = volatiles.Encore or
|
|
1480
1580
|
(last == .None or last == .Struggle or
|
|
1481
1581
|
last == .Encore or .last == .MirrorMove);
|
|
1482
|
-
if (failed) return options.log.fail(foe_ident, .None);
|
|
1582
|
+
if (failed) return options.log.fail(.{ foe_ident, .None });
|
|
1483
1583
|
|
|
1484
1584
|
var slot: u3 = 0;
|
|
1485
1585
|
for (foe.active.moves) |m| {
|
|
@@ -1488,7 +1588,7 @@ pub const Effects = struct {
|
|
|
1488
1588
|
if (m.id == last) break;
|
|
1489
1589
|
}
|
|
1490
1590
|
const move = foe.active.move(slot);
|
|
1491
|
-
if (move.pp == 0) return options.log.fail(foe_ident, .None);
|
|
1591
|
+
if (move.pp == 0) return options.log.fail(.{ foe_ident, .None });
|
|
1492
1592
|
|
|
1493
1593
|
volatiles.Encore = true;
|
|
1494
1594
|
volatiles.encore = Rolls.encoreDuration(battle, player, options);
|
|
@@ -1496,7 +1596,7 @@ pub const Effects = struct {
|
|
|
1496
1596
|
// TODO ???
|
|
1497
1597
|
_ = state;
|
|
1498
1598
|
|
|
1499
|
-
try options.log.
|
|
1599
|
+
try options.log.start(.{ foe_ident, .Encore, move.id });
|
|
1500
1600
|
}
|
|
1501
1601
|
|
|
1502
1602
|
pub fn explode(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1550,12 +1650,11 @@ pub const Effects = struct {
|
|
|
1550
1650
|
var side = battle.side(player);
|
|
1551
1651
|
|
|
1552
1652
|
if (side.active.volatiles.FocusEnergy) {
|
|
1553
|
-
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
1554
|
-
return;
|
|
1653
|
+
return if (!showdown) try options.log.fail(.{ battle.active(player), .None });
|
|
1555
1654
|
}
|
|
1556
1655
|
|
|
1557
1656
|
side.active.volatiles.FocusEnergy = true;
|
|
1558
|
-
try options.log.start(battle.active(player), .FocusEnergy);
|
|
1657
|
+
try options.log.start(.{ battle.active(player), .FocusEnergy });
|
|
1559
1658
|
}
|
|
1560
1659
|
|
|
1561
1660
|
var SLOTS: [5]u4 = .{ 0, 0, 0, 0, 0 };
|
|
@@ -1565,7 +1664,7 @@ pub const Effects = struct {
|
|
|
1565
1664
|
|
|
1566
1665
|
const foe = battle.foe(player);
|
|
1567
1666
|
|
|
1568
|
-
if (state.first) return options.log.fail(battle.active(player), .None);
|
|
1667
|
+
if (state.first) return options.log.fail(.{ battle.active(player), .None });
|
|
1569
1668
|
|
|
1570
1669
|
var n: u4 = 0;
|
|
1571
1670
|
var i: u4 = 0;
|
|
@@ -1579,7 +1678,7 @@ pub const Effects = struct {
|
|
|
1579
1678
|
}
|
|
1580
1679
|
}
|
|
1581
1680
|
|
|
1582
|
-
if (n == 0) return options.log.fail(battle.active(player), .None);
|
|
1681
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
1583
1682
|
|
|
1584
1683
|
const slot = try Rolls.forceSwitch(battle, player, SLOTS[0..i], n, options);
|
|
1585
1684
|
try switchIn(battle, player.foe(), slot, .Drag, options);
|
|
@@ -1589,14 +1688,14 @@ pub const Effects = struct {
|
|
|
1589
1688
|
var foe = battle.foe(player);
|
|
1590
1689
|
|
|
1591
1690
|
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1592
|
-
try options.log.lastmiss();
|
|
1593
|
-
return options.log.miss(battle.active(player));
|
|
1691
|
+
try options.log.lastmiss(.{});
|
|
1692
|
+
return options.log.miss(.{battle.active(player)});
|
|
1594
1693
|
} else if (foe.active.volatiles.Foresight) {
|
|
1595
|
-
return options.log.fail(battle.active(player), .None);
|
|
1694
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
1596
1695
|
}
|
|
1597
1696
|
|
|
1598
1697
|
foe.active.volatiles.Foresight = true;
|
|
1599
|
-
try options.log.start(battle.active(player.foe()), .FocusEnergy);
|
|
1698
|
+
try options.log.start(.{ battle.active(player.foe()), .FocusEnergy });
|
|
1600
1699
|
}
|
|
1601
1700
|
|
|
1602
1701
|
pub fn freezeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1619,7 +1718,7 @@ pub const Effects = struct {
|
|
|
1619
1718
|
if (showdown) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
|
|
1620
1719
|
|
|
1621
1720
|
foe_stored.status = Status.init(.FRZ);
|
|
1622
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
1721
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
1623
1722
|
|
|
1624
1723
|
// TODO: check for status berry
|
|
1625
1724
|
|
|
@@ -1641,42 +1740,131 @@ pub const Effects = struct {
|
|
|
1641
1740
|
state.bp = ((if (frustration) 255 - val else val) * 10) / 25;
|
|
1642
1741
|
}
|
|
1643
1742
|
|
|
1644
|
-
pub fn haze(battle: anytype, player: Player,
|
|
1645
|
-
|
|
1743
|
+
pub fn haze(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1744
|
+
var side = battle.side(player);
|
|
1745
|
+
var stats = unmodifiedStats(battle, side);
|
|
1746
|
+
side.active.boosts = .{};
|
|
1747
|
+
inline for (STATS) |s| @field(side.active.stats, s) = @field(stats, s);
|
|
1748
|
+
statusModify(side.stored().status, &side.active.stats);
|
|
1749
|
+
|
|
1750
|
+
var foe = battle.foe(player);
|
|
1751
|
+
stats = unmodifiedStats(battle, foe);
|
|
1752
|
+
foe.active.boosts = .{};
|
|
1753
|
+
inline for (STATS) |s| @field(foe.active.stats, s) = @field(stats, s);
|
|
1754
|
+
statusModify(foe.stored().status, &foe.active.stats);
|
|
1755
|
+
|
|
1756
|
+
try options.log.clearallboost(.{});
|
|
1646
1757
|
}
|
|
1647
1758
|
|
|
1648
1759
|
pub fn heal(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1649
|
-
|
|
1760
|
+
var side = battle.side(player);
|
|
1761
|
+
var stored = side.stored();
|
|
1762
|
+
const ident = battle.active(player);
|
|
1763
|
+
|
|
1764
|
+
if (stored.stats.hp == stored.hp) return options.log.fail(.{ ident, .None });
|
|
1765
|
+
|
|
1766
|
+
const rest = state.move == .Rest;
|
|
1767
|
+
if (rest) {
|
|
1768
|
+
// Adding the sleep status runs the sleep condition handler to roll duration
|
|
1769
|
+
if (showdown) battle.rng.advance(1);
|
|
1770
|
+
side.active.volatiles.Toxic = false;
|
|
1771
|
+
stored.status = Status.slf(2);
|
|
1772
|
+
recomputeStats(battle, side);
|
|
1773
|
+
try options.log.status(.{ ident, stored.status, .From, Move.Rest });
|
|
1774
|
+
stored.hp = stored.stats.hp;
|
|
1775
|
+
} else {
|
|
1776
|
+
stored.hp = @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2));
|
|
1777
|
+
}
|
|
1778
|
+
try options.log.heal(.{ ident, stored, if (rest) Heal.Silent else Heal.None });
|
|
1650
1779
|
}
|
|
1651
1780
|
|
|
1652
|
-
pub fn healBell(battle: anytype, player: Player,
|
|
1653
|
-
|
|
1781
|
+
pub fn healBell(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1782
|
+
var side = battle.side(player);
|
|
1783
|
+
side.active.volatiles.Nightmare = false;
|
|
1784
|
+
|
|
1785
|
+
try options.log.cureteam(.{battle.active(player)});
|
|
1786
|
+
for (&side.pokemon) |*p| p.status = 0;
|
|
1787
|
+
|
|
1788
|
+
// We technically only need to recompute stats if the active Pokémon was burned/paralyzed,
|
|
1789
|
+
// but adding a branch to check for that is probably more expensive than just recomputing
|
|
1790
|
+
recomputeStats(battle, side);
|
|
1654
1791
|
}
|
|
1655
1792
|
|
|
1656
|
-
pub fn hiddenPower(battle: anytype, player: Player, state: *State,
|
|
1657
|
-
|
|
1793
|
+
pub fn hiddenPower(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1794
|
+
const side = battle.side(player);
|
|
1795
|
+
state.bp = side.stored().dvs.power();
|
|
1796
|
+
state.type = side.stored().dvs.type;
|
|
1658
1797
|
}
|
|
1659
1798
|
|
|
1660
|
-
pub fn hyperBeam(battle: anytype, player: Player,
|
|
1661
|
-
|
|
1799
|
+
pub fn hyperBeam(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1800
|
+
battle.side(player).active.volatiles.Recharging = true;
|
|
1801
|
+
try options.log.mustrecharge(.{battle.active(player)});
|
|
1662
1802
|
}
|
|
1663
1803
|
|
|
1664
1804
|
pub fn leechSeed(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1665
|
-
|
|
1805
|
+
var foe = battle.foe(player);
|
|
1806
|
+
|
|
1807
|
+
if (showdown) {
|
|
1808
|
+
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
1809
|
+
const invulnerable = foe.active.volatiles.Underground or foe.active.volatiles.Flying;
|
|
1810
|
+
if (!invulnerable and foe.active.types.includes(.Grass)) {
|
|
1811
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
1812
|
+
}
|
|
1813
|
+
try checkHit(battle, player, state, options);
|
|
1814
|
+
if (state.miss) {
|
|
1815
|
+
try options.log.lastmiss(.{});
|
|
1816
|
+
return options.log.miss(.{battle.active(player)});
|
|
1817
|
+
}
|
|
1818
|
+
if (foe.active.volatiles.LeechSeed) return;
|
|
1819
|
+
} else {
|
|
1820
|
+
try checkHit(battle, player, state, options);
|
|
1821
|
+
const miss =
|
|
1822
|
+
state.miss or foe.active.types.includes(.Grass) or foe.active.volatiles.LeechSeed;
|
|
1823
|
+
if (miss) {
|
|
1824
|
+
try options.log.lastmiss(.{});
|
|
1825
|
+
return options.log.miss(.{battle.active(player)});
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
foe.active.volatiles.LeechSeed = true;
|
|
1830
|
+
|
|
1831
|
+
try options.log.start(.{ battle.active(player.foe()), .LeechSeed });
|
|
1666
1832
|
}
|
|
1667
1833
|
|
|
1668
1834
|
pub fn lockOn(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1669
|
-
|
|
1835
|
+
var foe = battle.foe(player);
|
|
1836
|
+
const foe_ident = battle.active(player.foe());
|
|
1837
|
+
|
|
1838
|
+
if (foe.active.volatiles.Substitute) {
|
|
1839
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1840
|
+
} else if (state.miss) {
|
|
1841
|
+
return options.log.fail(.{ foe_ident, .None });
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
foe.active.volatiles.LockOn = true;
|
|
1845
|
+
try options.log.activate(.{ battle.active(player), .LockOn });
|
|
1670
1846
|
}
|
|
1671
1847
|
|
|
1672
1848
|
const MAGNITUDE_POWER = [_]u8{ 10, 30, 50, 70, 90, 110, 150 };
|
|
1673
1849
|
|
|
1674
1850
|
pub fn magnitude(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1675
|
-
|
|
1851
|
+
const num = try Rolls.magnitude(battle, player, options);
|
|
1852
|
+
try options.log.activate(.{ battle.active(player), .Magnitude, num });
|
|
1853
|
+
state.bp = MAGNITUDE_POWER[num - 4];
|
|
1676
1854
|
}
|
|
1677
1855
|
|
|
1678
|
-
pub fn meanLook(battle: anytype, player: Player,
|
|
1679
|
-
|
|
1856
|
+
pub fn meanLook(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1857
|
+
var foe = battle.foe(player);
|
|
1858
|
+
|
|
1859
|
+
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1860
|
+
if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1861
|
+
try options.log.lastmiss(.{});
|
|
1862
|
+
return options.log.miss(.{battle.active(player)});
|
|
1863
|
+
} else if (foe.active.volatiles.Trapped) {
|
|
1864
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
foe.active.volatiles.Trapped = true;
|
|
1680
1868
|
}
|
|
1681
1869
|
|
|
1682
1870
|
pub fn metronome(
|
|
@@ -1703,10 +1891,6 @@ pub const Effects = struct {
|
|
|
1703
1891
|
_ = .{ battle, player, state, options }; // TODO
|
|
1704
1892
|
}
|
|
1705
1893
|
|
|
1706
|
-
pub fn mirrorCoat(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1707
|
-
_ = .{ battle, player, state, options }; // TODO
|
|
1708
|
-
}
|
|
1709
|
-
|
|
1710
1894
|
pub fn mirrorMove(
|
|
1711
1895
|
battle: anytype,
|
|
1712
1896
|
player: Player,
|
|
@@ -1721,11 +1905,11 @@ pub const Effects = struct {
|
|
|
1721
1905
|
|
|
1722
1906
|
const last = foe.lastMove(false);
|
|
1723
1907
|
|
|
1724
|
-
if (last == .None) return options.log.fail(battle.active(player), .None);
|
|
1908
|
+
if (last == .None) return options.log.fail(.{ battle.active(player), .None });
|
|
1725
1909
|
const knows = knows: for (side.active.moves) |m| {
|
|
1726
1910
|
if (last == m.id) break :knows true;
|
|
1727
1911
|
} else false;
|
|
1728
|
-
if (knows) return options.log.fail(battle.active(player), .None);
|
|
1912
|
+
if (knows) return options.log.fail(.{ battle.active(player), .None });
|
|
1729
1913
|
|
|
1730
1914
|
// if (wPlayerCharging) ???
|
|
1731
1915
|
// wPlayerCharging = 1
|
|
@@ -1739,17 +1923,17 @@ pub const Effects = struct {
|
|
|
1739
1923
|
pub fn mist(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1740
1924
|
var side = battle.side(player);
|
|
1741
1925
|
|
|
1742
|
-
if (side.active.volatiles.Mist) return options.log.fail(battle.active(player), .None);
|
|
1926
|
+
if (side.active.volatiles.Mist) return options.log.fail(.{ battle.active(player), .None });
|
|
1743
1927
|
side.active.volatiles.Mist = true;
|
|
1744
1928
|
|
|
1745
|
-
try options.log.start(battle.active(player), .Mist);
|
|
1929
|
+
try options.log.start(.{ battle.active(player), .Mist });
|
|
1746
1930
|
}
|
|
1747
1931
|
|
|
1748
1932
|
pub fn weatherHeal(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1749
1933
|
var stored = battle.side(player).stored();
|
|
1750
1934
|
const ident = battle.active(player);
|
|
1751
1935
|
|
|
1752
|
-
if (stored.hp == stored.stats.hp) return options.log.fail(ident, .None);
|
|
1936
|
+
if (stored.hp == stored.stats.hp) return options.log.fail(.{ ident, .None });
|
|
1753
1937
|
|
|
1754
1938
|
stored.hp = switch (battle.field.weather) {
|
|
1755
1939
|
.Sun => stored.stats.hp,
|
|
@@ -1757,7 +1941,7 @@ pub const Effects = struct {
|
|
|
1757
1941
|
else => @min(stored.stats.hp, stored.hp + (stored.stats.hp / 4)),
|
|
1758
1942
|
};
|
|
1759
1943
|
|
|
1760
|
-
try options.log.heal(ident, stored,
|
|
1944
|
+
try options.log.heal(.{ ident, stored, .None });
|
|
1761
1945
|
}
|
|
1762
1946
|
|
|
1763
1947
|
pub fn multiHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1770,17 +1954,17 @@ pub const Effects = struct {
|
|
|
1770
1954
|
const foe_ident = battle.active(player.foe());
|
|
1771
1955
|
|
|
1772
1956
|
if (foe_volatiles.Flying or foe_volatiles.Underground) {
|
|
1773
|
-
if (!showdown) return options.log.fail(battle.active(player.foe()), .None);
|
|
1774
|
-
try options.log.lastmiss();
|
|
1775
|
-
return options.log.miss(battle.active(player));
|
|
1957
|
+
if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1958
|
+
try options.log.lastmiss(.{});
|
|
1959
|
+
return options.log.miss(.{battle.active(player)});
|
|
1776
1960
|
} else if (foe_volatiles.Substitute) {
|
|
1777
|
-
return options.log.
|
|
1961
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1778
1962
|
} else if (!Status.is(foe.stored().status, .SLP) or foe_volatiles.Nightmare) {
|
|
1779
|
-
return options.log.fail(foe_ident, .None);
|
|
1963
|
+
return options.log.fail(.{ foe_ident, .None });
|
|
1780
1964
|
}
|
|
1781
1965
|
|
|
1782
1966
|
foe_volatiles.Nightmare = true;
|
|
1783
|
-
try options.log.start(battle.active(player.foe()), .Nightmare);
|
|
1967
|
+
try options.log.start(.{ battle.active(player.foe()), .Nightmare });
|
|
1784
1968
|
}
|
|
1785
1969
|
|
|
1786
1970
|
pub const ohko = struct {
|
|
@@ -1804,9 +1988,9 @@ pub const Effects = struct {
|
|
|
1804
1988
|
const foe_ident = battle.active(player.foe());
|
|
1805
1989
|
|
|
1806
1990
|
state.damage = 0;
|
|
1807
|
-
if (state.immune()) return options.log.immune(foe_ident, .None);
|
|
1991
|
+
if (state.immune()) return options.log.immune(.{ foe_ident, .None });
|
|
1808
1992
|
const delta = side.stored().level -| foe.stored().level;
|
|
1809
|
-
if (delta == 0) return options.log.immune(foe_ident, .OHKO);
|
|
1993
|
+
if (delta == 0) return options.log.immune(.{ foe_ident, .OHKO });
|
|
1810
1994
|
|
|
1811
1995
|
state.accuracy = 2 * delta +| Gen12.percent(30);
|
|
1812
1996
|
try checkHit(battle, player, state, options);
|
|
@@ -1822,37 +2006,39 @@ pub const Effects = struct {
|
|
|
1822
2006
|
const ident = battle.active(player);
|
|
1823
2007
|
const foe_ident = battle.active(player.foe());
|
|
1824
2008
|
|
|
1825
|
-
if (state.miss) return options.log.fail(foe_ident, .None);
|
|
2009
|
+
if (state.miss) return options.log.fail(.{ foe_ident, .None });
|
|
1826
2010
|
if (foe.active.volatiles.Substitute) {
|
|
1827
|
-
return options.log.
|
|
2011
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1828
2012
|
}
|
|
1829
2013
|
|
|
1830
2014
|
const avg = @max((foe.stored().hp + side.stored().hp) / 2, 1);
|
|
1831
2015
|
foe.stored().hp -= avg;
|
|
1832
2016
|
side.stored().hp = avg;
|
|
1833
2017
|
|
|
1834
|
-
try options.log.sethp(foe_ident, foe.stored(), .Silent);
|
|
1835
|
-
try options.log.sethp(ident, side.stored(), .None);
|
|
2018
|
+
try options.log.sethp(.{ foe_ident, foe.stored(), .Silent });
|
|
2019
|
+
try options.log.sethp(.{ ident, side.stored(), .None });
|
|
1836
2020
|
}
|
|
1837
2021
|
|
|
1838
2022
|
pub fn paralyze(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1839
2023
|
var foe = battle.foe(player);
|
|
1840
2024
|
const foe_ident = battle.active(player.foe());
|
|
1841
2025
|
|
|
1842
|
-
if (Status.is(foe.stored().status, .PAR))
|
|
1843
|
-
|
|
1844
|
-
|
|
2026
|
+
if (Status.is(foe.stored().status, .PAR)) {
|
|
2027
|
+
return options.log.fail(.{ foe_ident, .Paralysis });
|
|
2028
|
+
}
|
|
2029
|
+
if (state.immune()) return options.log.immune(.{ foe_ident, .None });
|
|
2030
|
+
if (Status.any(foe.stored().status)) return options.log.fail(.{ foe_ident, .None });
|
|
1845
2031
|
if (state.miss) {
|
|
1846
|
-
try options.log.lastmiss();
|
|
1847
|
-
return options.log.miss(battle.active(player));
|
|
2032
|
+
try options.log.lastmiss(.{});
|
|
2033
|
+
return options.log.miss(.{battle.active(player)});
|
|
1848
2034
|
} else if (foe.active.volatiles.Substitute) {
|
|
1849
|
-
return options.log.
|
|
2035
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1850
2036
|
}
|
|
1851
2037
|
|
|
1852
2038
|
foe.stored().status = Status.init(.PAR);
|
|
1853
2039
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
1854
2040
|
|
|
1855
|
-
try options.log.status(foe_ident, foe.stored().status, .None);
|
|
2041
|
+
try options.log.status(.{ foe_ident, foe.stored().status, .None });
|
|
1856
2042
|
}
|
|
1857
2043
|
|
|
1858
2044
|
pub fn paralyzeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1867,14 +2053,15 @@ pub const Effects = struct {
|
|
|
1867
2053
|
|
|
1868
2054
|
foe_stored.status = Status.init(.PAR);
|
|
1869
2055
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
1870
|
-
|
|
2056
|
+
|
|
2057
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1871
2058
|
|
|
1872
2059
|
// TODO: check for status berry
|
|
1873
2060
|
}
|
|
1874
2061
|
|
|
1875
2062
|
pub fn payDay(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1876
2063
|
if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
|
|
1877
|
-
try options.log.fieldactivate(); // FIXME .PayDay
|
|
2064
|
+
try options.log.fieldactivate(.{}); // FIXME .PayDay
|
|
1878
2065
|
}
|
|
1879
2066
|
}
|
|
1880
2067
|
|
|
@@ -1883,17 +2070,17 @@ pub const Effects = struct {
|
|
|
1883
2070
|
var foe = battle.foe(player);
|
|
1884
2071
|
|
|
1885
2072
|
if (foe.active.volatiles.PerishSong) {
|
|
1886
|
-
return options.log.fail(battle.active(player.foe()), .None);
|
|
2073
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1887
2074
|
} else if (!side.active.volatiles.PerishSong) {
|
|
1888
2075
|
side.active.volatiles.PerishSong = true;
|
|
1889
2076
|
side.active.volatiles.perish_song = 4;
|
|
1890
|
-
try options.log.start(battle.active(player), .PerishSong3Silent);
|
|
2077
|
+
try options.log.start(.{ battle.active(player), .PerishSong3Silent });
|
|
1891
2078
|
}
|
|
1892
2079
|
foe.active.volatiles.PerishSong = true;
|
|
1893
2080
|
foe.active.volatiles.perish_song = 4;
|
|
1894
|
-
try options.log.start(battle.active(player.foe()), .PerishSong3Silent);
|
|
2081
|
+
try options.log.start(.{ battle.active(player.foe()), .PerishSong3Silent });
|
|
1895
2082
|
|
|
1896
|
-
try options.log.fieldactivate(); // FIXME .PerishSong
|
|
2083
|
+
try options.log.fieldactivate(.{}); // FIXME .PerishSong
|
|
1897
2084
|
}
|
|
1898
2085
|
|
|
1899
2086
|
pub fn poison(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1903,24 +2090,24 @@ pub const Effects = struct {
|
|
|
1903
2090
|
|
|
1904
2091
|
const toxic = state.move == .Toxic;
|
|
1905
2092
|
if (state.immune() or foe.active.types.includes(.Poison)) {
|
|
1906
|
-
return options.log.immune(foe_ident, .None);
|
|
2093
|
+
return options.log.immune(.{ foe_ident, .None });
|
|
1907
2094
|
} else if (Status.any(foe_stored.status)) {
|
|
1908
2095
|
// Pokémon Showdown considers Toxic to be a status even in Generation II and so
|
|
1909
2096
|
// will not include a fail reason for Toxic vs. Poison or vice-versa...
|
|
1910
|
-
return options.log.fail(foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2097
|
+
return options.log.fail(.{ foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
1911
2098
|
if (!showdown)
|
|
1912
|
-
.Poison
|
|
2099
|
+
Fail.Poison
|
|
1913
2100
|
else if (toxic == (foe_stored.status == Status.TOX))
|
|
1914
|
-
if (toxic) .Toxic else .Poison
|
|
2101
|
+
if (toxic) Fail.Toxic else Fail.Poison
|
|
1915
2102
|
else
|
|
1916
|
-
.None
|
|
2103
|
+
Fail.None
|
|
1917
2104
|
else
|
|
1918
|
-
.None);
|
|
2105
|
+
Fail.None });
|
|
1919
2106
|
} else if (foe.active.volatiles.Substitute) {
|
|
1920
|
-
return options.log.
|
|
2107
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1921
2108
|
} else if (state.miss) {
|
|
1922
|
-
try options.log.lastmiss();
|
|
1923
|
-
return options.log.miss(battle.active(player));
|
|
2109
|
+
try options.log.lastmiss(.{});
|
|
2110
|
+
return options.log.miss(.{battle.active(player)});
|
|
1924
2111
|
}
|
|
1925
2112
|
|
|
1926
2113
|
foe_stored.status = Status.init(.PSN);
|
|
@@ -1930,7 +2117,7 @@ pub const Effects = struct {
|
|
|
1930
2117
|
foe.active.volatiles.toxic = 0;
|
|
1931
2118
|
}
|
|
1932
2119
|
|
|
1933
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
2120
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
1934
2121
|
|
|
1935
2122
|
// TODO: check for status berry
|
|
1936
2123
|
}
|
|
@@ -1946,7 +2133,8 @@ pub const Effects = struct {
|
|
|
1946
2133
|
if (foe.conditions.Safeguard) return;
|
|
1947
2134
|
|
|
1948
2135
|
foe_stored.status = Status.init(.PSN);
|
|
1949
|
-
|
|
2136
|
+
|
|
2137
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1950
2138
|
|
|
1951
2139
|
// TODO: check for status berry
|
|
1952
2140
|
}
|
|
@@ -1961,9 +2149,7 @@ pub const Effects = struct {
|
|
|
1961
2149
|
|
|
1962
2150
|
const failed = !state.first or volatiles.Substitute or
|
|
1963
2151
|
!try Rolls.protect(battle, player, volatiles.protect, options);
|
|
1964
|
-
if (failed) {
|
|
1965
|
-
return options.log.fail(battle.active(player), .None);
|
|
1966
|
-
}
|
|
2152
|
+
if (failed) return options.log.fail(.{ battle.active(player), .None });
|
|
1967
2153
|
|
|
1968
2154
|
volatiles.protect = @max(8, volatiles.protect + 1);
|
|
1969
2155
|
if (state.move == .Endure) {
|
|
@@ -1971,7 +2157,7 @@ pub const Effects = struct {
|
|
|
1971
2157
|
} else {
|
|
1972
2158
|
volatiles.Protect = true;
|
|
1973
2159
|
}
|
|
1974
|
-
try options.log.singleturn(battle.active(player), state.move);
|
|
2160
|
+
try options.log.singleturn(.{ battle.active(player), state.move });
|
|
1975
2161
|
}
|
|
1976
2162
|
|
|
1977
2163
|
pub fn psychUp(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
@@ -1980,11 +2166,11 @@ pub const Effects = struct {
|
|
|
1980
2166
|
|
|
1981
2167
|
// Pokémon Showdown should fail when the enemy has no boosts/unboosts but doesn't
|
|
1982
2168
|
if (!showdown and @as(u32, @bitCast(foe.active.boosts)) == 0) {
|
|
1983
|
-
return options.log.fail(battle.active(player), .None);
|
|
2169
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
1984
2170
|
}
|
|
1985
2171
|
|
|
1986
2172
|
side.active.boosts = foe.active.boosts;
|
|
1987
|
-
try options.log.copyboost(battle.active(player), battle.active(player.foe()));
|
|
2173
|
+
try options.log.copyboost(.{ battle.active(player), battle.active(player.foe()) });
|
|
1988
2174
|
}
|
|
1989
2175
|
|
|
1990
2176
|
pub fn pursuit(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
@@ -1995,7 +2181,7 @@ pub const Effects = struct {
|
|
|
1995
2181
|
pub const rage = struct {
|
|
1996
2182
|
pub fn start(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1997
2183
|
battle.side(player).active.volatiles.Rage = true;
|
|
1998
|
-
try options.log.singlemove(battle.active(player), Move.Rage);
|
|
2184
|
+
try options.log.singlemove(.{ battle.active(player), Move.Rage });
|
|
1999
2185
|
}
|
|
2000
2186
|
|
|
2001
2187
|
pub fn damage(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
@@ -2015,18 +2201,18 @@ pub const Effects = struct {
|
|
|
2015
2201
|
var side = battle.side(player);
|
|
2016
2202
|
|
|
2017
2203
|
if (side.active.volatiles.LeechSeed) {
|
|
2018
|
-
try options.log.end(battle.active(player), .LeechSeedFrom);
|
|
2204
|
+
try options.log.end(.{ battle.active(player), .LeechSeedFrom });
|
|
2019
2205
|
side.active.volatiles.LeechSeed = false;
|
|
2020
2206
|
}
|
|
2021
2207
|
|
|
2022
2208
|
if (side.conditions.Spikes) {
|
|
2023
|
-
try options.log.
|
|
2209
|
+
try options.log.sideend(.{ player, protocol.Side.Spikes, battle.active(player) });
|
|
2024
2210
|
side.conditions.Spikes = false;
|
|
2025
2211
|
}
|
|
2026
2212
|
|
|
2027
2213
|
if (side.active.volatiles.bind.duration > 0) {
|
|
2028
2214
|
const reason = @intFromEnum(End.Bind) + side.active.volatiles.bind.reason - 1;
|
|
2029
|
-
try options.log.end(battle.active(player), @enumFromInt(reason));
|
|
2215
|
+
try options.log.end(.{ battle.active(player), @as(End, @enumFromInt(reason)) });
|
|
2030
2216
|
side.active.volatiles.bind = .{};
|
|
2031
2217
|
}
|
|
2032
2218
|
}
|
|
@@ -2034,18 +2220,14 @@ pub const Effects = struct {
|
|
|
2034
2220
|
pub fn recoil(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2035
2221
|
var side = battle.side(player);
|
|
2036
2222
|
var stored = side.stored();
|
|
2223
|
+
const ident = battle.active(player);
|
|
2037
2224
|
|
|
2038
2225
|
assert(state.damage > 0);
|
|
2039
2226
|
|
|
2040
2227
|
const damage: i16 = @intCast(@max(state.damage / 4, 1));
|
|
2041
2228
|
stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
|
|
2042
2229
|
|
|
2043
|
-
try options.log.
|
|
2044
|
-
battle.active(player),
|
|
2045
|
-
stored,
|
|
2046
|
-
.RecoilOf,
|
|
2047
|
-
battle.active(player.foe()),
|
|
2048
|
-
);
|
|
2230
|
+
try options.log.damage(.{ ident, stored, .RecoilOf, battle.active(player.foe()) });
|
|
2049
2231
|
}
|
|
2050
2232
|
|
|
2051
2233
|
pub fn reversal(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2108,7 +2290,7 @@ pub const Effects = struct {
|
|
|
2108
2290
|
|
|
2109
2291
|
pub fn sandstorm(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2110
2292
|
if (battle.field.weather != .Sandstorm) return weather(battle, .Sandstorm, options);
|
|
2111
|
-
return options.log.fail(battle.active(player), .None);
|
|
2293
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
2112
2294
|
}
|
|
2113
2295
|
|
|
2114
2296
|
pub fn safeguard(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
@@ -2132,11 +2314,13 @@ pub const Effects = struct {
|
|
|
2132
2314
|
) !void {
|
|
2133
2315
|
var side = battle.side(player);
|
|
2134
2316
|
|
|
2135
|
-
if (@field(side.conditions, key))
|
|
2317
|
+
if (@field(side.conditions, key)) {
|
|
2318
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
2319
|
+
}
|
|
2136
2320
|
|
|
2137
2321
|
@field(side.conditions, key) = true;
|
|
2138
2322
|
@field(side.conditions, value) = 5;
|
|
2139
|
-
try options.log.sidestart(player, @field(protocol.Side, key));
|
|
2323
|
+
try options.log.sidestart(.{ player, @field(protocol.Side, key) });
|
|
2140
2324
|
}
|
|
2141
2325
|
|
|
2142
2326
|
pub fn sketch(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
@@ -2147,9 +2331,9 @@ pub const Effects = struct {
|
|
|
2147
2331
|
|
|
2148
2332
|
if (showdown) {
|
|
2149
2333
|
// yes, actually "Splash". '|nothing' gets blindly mapped to '|-activate||move:Splash'
|
|
2150
|
-
try options.log.activate(battle.active(player), .Splash);
|
|
2334
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
2151
2335
|
} else {
|
|
2152
|
-
try options.log.fail(battle.active(player), .None);
|
|
2336
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2153
2337
|
}
|
|
2154
2338
|
}
|
|
2155
2339
|
|
|
@@ -2159,14 +2343,14 @@ pub const Effects = struct {
|
|
|
2159
2343
|
const foe_ident = battle.active(player.foe());
|
|
2160
2344
|
|
|
2161
2345
|
if (Status.any(foe_stored.status)) {
|
|
2162
|
-
return options.log.fail(
|
|
2346
|
+
return options.log.fail(.{
|
|
2163
2347
|
foe_ident,
|
|
2164
|
-
if (Status.is(foe_stored.status, .SLP)) .Sleep else .None,
|
|
2165
|
-
);
|
|
2348
|
+
if (Status.is(foe_stored.status, .SLP)) Fail.Sleep else Fail.None,
|
|
2349
|
+
});
|
|
2166
2350
|
}
|
|
2167
2351
|
|
|
2168
2352
|
if (foe.active.volatiles.Substitute) {
|
|
2169
|
-
return options.log.
|
|
2353
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
2170
2354
|
}
|
|
2171
2355
|
|
|
2172
2356
|
// Sleep Clause Mod
|
|
@@ -2177,22 +2361,21 @@ pub const Effects = struct {
|
|
|
2177
2361
|
}
|
|
2178
2362
|
|
|
2179
2363
|
foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
|
|
2180
|
-
try options.log.
|
|
2364
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .From, state.move });
|
|
2181
2365
|
}
|
|
2182
2366
|
|
|
2183
2367
|
pub fn sleepTalk(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2184
2368
|
const side = battle.side(player);
|
|
2185
2369
|
|
|
2186
2370
|
if (!Status.is(side.stored().status, .SLP)) {
|
|
2187
|
-
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
2188
|
-
return;
|
|
2371
|
+
return if (!showdown) try options.log.fail(.{ battle.active(player), .None });
|
|
2189
2372
|
}
|
|
2190
2373
|
|
|
2191
2374
|
var n: u3 = 0;
|
|
2192
2375
|
for (side.active.moves, 0..) |m, i| {
|
|
2193
2376
|
if (state.mslot - 1 != i and Move.get(m.id).extra.sleep_talk) n += 1;
|
|
2194
2377
|
}
|
|
2195
|
-
if (n == 0) return options.log.fail(battle.active(player), .None);
|
|
2378
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
2196
2379
|
|
|
2197
2380
|
_ = try Rolls.sleepTalk(battle, player, n, state.mslot, options); // TODO
|
|
2198
2381
|
}
|
|
@@ -2201,23 +2384,23 @@ pub const Effects = struct {
|
|
|
2201
2384
|
if (Status.is(battle.side(player).stored().status, .SLP)) return true;
|
|
2202
2385
|
state.damage = 0;
|
|
2203
2386
|
state.miss = true; // TODO is this necessary?
|
|
2204
|
-
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
2387
|
+
if (!showdown) try options.log.fail(.{ battle.active(player), .None });
|
|
2205
2388
|
return false;
|
|
2206
2389
|
}
|
|
2207
2390
|
|
|
2208
2391
|
pub fn spikes(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2209
2392
|
var foe = battle.foe(player);
|
|
2210
|
-
if (foe.conditions.Spikes) return options.log.fail(battle.active(player), .None);
|
|
2393
|
+
if (foe.conditions.Spikes) return options.log.fail(.{ battle.active(player), .None });
|
|
2211
2394
|
|
|
2212
2395
|
foe.conditions.Spikes = true;
|
|
2213
|
-
try options.log.sidestart(player.foe(), .Spikes);
|
|
2396
|
+
try options.log.sidestart(.{ player.foe(), protocol.Side.Spikes });
|
|
2214
2397
|
}
|
|
2215
2398
|
|
|
2216
2399
|
pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2217
2400
|
_ = .{ battle, player, state, options }; // TODO
|
|
2218
2401
|
// var foe = battle.foe(player);
|
|
2219
2402
|
|
|
2220
|
-
// if (state.miss) return options.log.fail(battle.active(player), .None);
|
|
2403
|
+
// if (state.miss) return options.log.fail(.{ battle.active(player), .None });
|
|
2221
2404
|
|
|
2222
2405
|
// const last = foe.lastMove(false);
|
|
2223
2406
|
// if (showdown) {
|
|
@@ -2228,28 +2411,28 @@ pub const Effects = struct {
|
|
|
2228
2411
|
}
|
|
2229
2412
|
|
|
2230
2413
|
pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2231
|
-
try options.log.activate(battle.active(player), .Splash);
|
|
2414
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
2232
2415
|
}
|
|
2233
2416
|
|
|
2234
2417
|
pub fn substitute(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2235
2418
|
var side = battle.side(player);
|
|
2236
2419
|
if (side.active.volatiles.Substitute) {
|
|
2237
|
-
return options.log.fail(battle.active(player), .Substitute);
|
|
2420
|
+
return options.log.fail(.{ battle.active(player), .Substitute });
|
|
2238
2421
|
}
|
|
2239
2422
|
|
|
2240
2423
|
assert(side.stored().stats.hp <= 1023);
|
|
2241
2424
|
const hp: u8 = @intCast(side.stored().stats.hp / 4);
|
|
2242
2425
|
|
|
2243
2426
|
if (hp == 0 or side.stored().hp <= hp) {
|
|
2244
|
-
return options.log.fail(battle.active(player), .Weak);
|
|
2427
|
+
return options.log.fail(.{ battle.active(player), .Weak });
|
|
2245
2428
|
}
|
|
2246
2429
|
|
|
2247
2430
|
side.stored().hp -= hp;
|
|
2248
2431
|
side.active.volatiles.substitute = hp;
|
|
2249
2432
|
side.active.volatiles.Substitute = true;
|
|
2250
2433
|
side.active.volatiles.bind = .{};
|
|
2251
|
-
try options.log.start(battle.active(player), .Substitute);
|
|
2252
|
-
try options.log.damage(battle.active(player), side.stored(), .None);
|
|
2434
|
+
try options.log.start(.{ battle.active(player), .Substitute });
|
|
2435
|
+
try options.log.damage(.{ battle.active(player), side.stored(), .None });
|
|
2253
2436
|
}
|
|
2254
2437
|
|
|
2255
2438
|
pub fn sunnyDay(battle: anytype, _: Player, _: *State, options: anytype) !void {
|
|
@@ -2259,14 +2442,14 @@ pub const Effects = struct {
|
|
|
2259
2442
|
fn weather(battle: anytype, w: Weather, options: anytype) !void {
|
|
2260
2443
|
battle.field.weather = w;
|
|
2261
2444
|
battle.field.weather_duration = 5;
|
|
2262
|
-
try options.log.weather(w, .None);
|
|
2445
|
+
try options.log.weather(.{ w, protocol.Weather.None });
|
|
2263
2446
|
}
|
|
2264
2447
|
|
|
2265
2448
|
pub fn teleport(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2266
2449
|
if (showdown) return;
|
|
2267
2450
|
|
|
2268
|
-
try options.log.fail(battle.active(player), .None);
|
|
2269
|
-
try options.log.laststill();
|
|
2451
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2452
|
+
try options.log.laststill(.{});
|
|
2270
2453
|
}
|
|
2271
2454
|
|
|
2272
2455
|
pub fn thief(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2281,11 +2464,11 @@ pub const Effects = struct {
|
|
|
2281
2464
|
side.stored().item = foe.stored().item;
|
|
2282
2465
|
foe.stored().item = .None;
|
|
2283
2466
|
|
|
2284
|
-
try options.log.item(
|
|
2467
|
+
try options.log.item(.{
|
|
2285
2468
|
battle.active(player),
|
|
2286
2469
|
side.stored().item,
|
|
2287
2470
|
battle.active(player.foe()),
|
|
2288
|
-
);
|
|
2471
|
+
});
|
|
2289
2472
|
}
|
|
2290
2473
|
|
|
2291
2474
|
pub fn thrashing(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
@@ -2307,11 +2490,11 @@ pub const Effects = struct {
|
|
|
2307
2490
|
side.active.volatiles.dirty = false;
|
|
2308
2491
|
|
|
2309
2492
|
if (foe.active.volatiles.Transform) {
|
|
2310
|
-
return options.log.fail(battle.active(player), .None);
|
|
2493
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
2311
2494
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2312
|
-
if (!showdown) return options.log.fail(battle.active(player), .None);
|
|
2313
|
-
try options.log.lastmiss();
|
|
2314
|
-
return options.log.miss(battle.active(player));
|
|
2495
|
+
if (!showdown) return options.log.fail(.{ battle.active(player), .None });
|
|
2496
|
+
try options.log.lastmiss(.{});
|
|
2497
|
+
return options.log.miss(.{battle.active(player)});
|
|
2315
2498
|
}
|
|
2316
2499
|
|
|
2317
2500
|
side.active.volatiles.Transform = true;
|
|
@@ -2333,7 +2516,7 @@ pub const Effects = struct {
|
|
|
2333
2516
|
side.active.moves[i].pp = if (m.id != .None) if (m.id == .Sketch) 1 else 5 else 0;
|
|
2334
2517
|
}
|
|
2335
2518
|
|
|
2336
|
-
try options.log.transform(battle.active(player), foe_ident);
|
|
2519
|
+
try options.log.transform(.{ battle.active(player), foe_ident });
|
|
2337
2520
|
}
|
|
2338
2521
|
|
|
2339
2522
|
pub fn triAttack(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2352,8 +2535,7 @@ pub const Effects = struct {
|
|
|
2352
2535
|
_ = .{ battle, player, state, options }; // TODO
|
|
2353
2536
|
}
|
|
2354
2537
|
|
|
2355
|
-
const
|
|
2356
|
-
const ALL_STAT_UP_REASONS =
|
|
2538
|
+
const STAT_UP_REASONS =
|
|
2357
2539
|
[_]Boost{ .Attack, .Defense, .Speed, .SpecialAttack, .SpecialDefense };
|
|
2358
2540
|
|
|
2359
2541
|
pub fn allStatUpChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2363,20 +2545,15 @@ pub const Effects = struct {
|
|
|
2363
2545
|
var boosts = &side.active.boosts;
|
|
2364
2546
|
|
|
2365
2547
|
if (!state.proc) return;
|
|
2366
|
-
inline for (
|
|
2548
|
+
inline for (STATS, STAT_UP_REASONS) |s, r| {
|
|
2367
2549
|
assert(@field(boosts, s) >= -6 and @field(boosts, s) <= 6);
|
|
2368
2550
|
if (@field(boosts, s) == 6 or @field(stats, s) == MAX_STAT_VALUE) {
|
|
2369
|
-
try options.log.boost(battle.active(player), r, 0);
|
|
2551
|
+
try options.log.boost(.{ battle.active(player), r, 0 });
|
|
2370
2552
|
} else {
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2374
|
-
@field(stats, s) = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2375
|
-
try options.log.boost(battle.active(player), r, 1);
|
|
2553
|
+
try boostStat(battle, side, stats, boosts, s, 1);
|
|
2554
|
+
try options.log.boost(.{ battle.active(player), r, 1 });
|
|
2376
2555
|
}
|
|
2377
2556
|
}
|
|
2378
|
-
|
|
2379
|
-
statusModify(side.stored().status, stats);
|
|
2380
2557
|
}
|
|
2381
2558
|
|
|
2382
2559
|
pub fn boost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2384,7 +2561,7 @@ pub const Effects = struct {
|
|
|
2384
2561
|
var side = battle.side(player);
|
|
2385
2562
|
const ident = battle.active(player);
|
|
2386
2563
|
|
|
2387
|
-
if (state.miss) return log.fail(ident, .None);
|
|
2564
|
+
if (state.miss) return log.fail(.{ ident, .None });
|
|
2388
2565
|
|
|
2389
2566
|
var stats = &side.active.stats;
|
|
2390
2567
|
var boosts = &side.active.boosts;
|
|
@@ -2393,63 +2570,56 @@ pub const Effects = struct {
|
|
|
2393
2570
|
switch (move.effect) {
|
|
2394
2571
|
.AttackUp1, .AttackUp2, .Rage, .Swagger => {
|
|
2395
2572
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2396
|
-
if (boosts.atk == 6 or stats.atk == MAX_STAT_VALUE)
|
|
2573
|
+
if (boosts.atk == 6 or stats.atk == MAX_STAT_VALUE) {
|
|
2574
|
+
return log.fail(.{ ident, .None });
|
|
2575
|
+
}
|
|
2397
2576
|
const n: u2 = if (move.effect == .AttackUp2 or move.effect == .Swagger) 2 else 1;
|
|
2398
|
-
|
|
2577
|
+
try boostStat(battle, side, stats, boosts, "atk", n);
|
|
2399
2578
|
const reason = if (showdown and move.effect == .Rage) Boost.Rage else Boost.Attack;
|
|
2400
|
-
|
|
2401
|
-
const stat = unmodifiedStats(battle, side).atk;
|
|
2402
|
-
stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2403
|
-
try log.boost(ident, reason, n);
|
|
2579
|
+
try log.boost(.{ ident, reason, n });
|
|
2404
2580
|
},
|
|
2405
2581
|
.DefenseUp1, .DefenseUp2, .SkullBash => {
|
|
2406
2582
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2407
|
-
if (boosts.def == 6 or stats.def == MAX_STAT_VALUE)
|
|
2583
|
+
if (boosts.def == 6 or stats.def == MAX_STAT_VALUE) {
|
|
2584
|
+
return log.fail(.{ ident, .None });
|
|
2585
|
+
}
|
|
2408
2586
|
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
const stat = unmodifiedStats(battle, side).def;
|
|
2412
|
-
stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2413
|
-
try log.boost(ident, .Defense, n);
|
|
2587
|
+
try boostStat(battle, side, stats, boosts, "def", n);
|
|
2588
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2414
2589
|
},
|
|
2415
2590
|
.SpeedUp2 => {
|
|
2416
2591
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2417
|
-
if (boosts.spe == 6 or stats.spe == MAX_STAT_VALUE)
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
try log.boost(ident, .Speed, 2);
|
|
2592
|
+
if (boosts.spe == 6 or stats.spe == MAX_STAT_VALUE) {
|
|
2593
|
+
return log.fail(.{ ident, .None });
|
|
2594
|
+
}
|
|
2595
|
+
try boostStat(battle, side, stats, boosts, "spe", 2);
|
|
2596
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2423
2597
|
},
|
|
2424
2598
|
.SpAtkUp1 => {
|
|
2425
2599
|
assert(boosts.spa >= -6 and boosts.spa <= 6);
|
|
2426
|
-
if (boosts.spa == 6 or stats.spa == MAX_STAT_VALUE)
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
try log.boost(ident, .SpecialAttack, 1);
|
|
2600
|
+
if (boosts.spa == 6 or stats.spa == MAX_STAT_VALUE) {
|
|
2601
|
+
return log.fail(.{ ident, .None });
|
|
2602
|
+
}
|
|
2603
|
+
try boostStat(battle, side, stats, boosts, "spa", 1);
|
|
2604
|
+
try log.boost(.{ ident, .SpecialAttack, 1 });
|
|
2432
2605
|
},
|
|
2433
2606
|
.SpDefUp2 => {
|
|
2434
2607
|
assert(boosts.spd >= -6 and boosts.spd <= 6);
|
|
2435
|
-
if (boosts.spd == 6 or stats.spd == MAX_STAT_VALUE)
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
try log.boost(ident, .SpecialDefense, 2);
|
|
2608
|
+
if (boosts.spd == 6 or stats.spd == MAX_STAT_VALUE) {
|
|
2609
|
+
return log.fail(.{ ident, .None });
|
|
2610
|
+
}
|
|
2611
|
+
try boostStat(battle, side, stats, boosts, "spd", 2);
|
|
2612
|
+
try log.boost(.{ ident, .SpecialDefense, 2 });
|
|
2441
2613
|
},
|
|
2442
2614
|
.EvasionUp1 => {
|
|
2443
2615
|
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2444
|
-
if (boosts.evasion == 6) return log.fail(ident, .None);
|
|
2616
|
+
if (boosts.evasion == 6) return log.fail(.{ ident, .None });
|
|
2445
2617
|
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2446
|
-
try log.boost(ident, .Evasion, 1);
|
|
2618
|
+
try log.boost(.{ ident, .Evasion, 1 });
|
|
2447
2619
|
if (state.move == .Minimize) side.active.volatiles.minimized = true;
|
|
2448
2620
|
},
|
|
2449
2621
|
else => unreachable,
|
|
2450
2622
|
}
|
|
2451
|
-
|
|
2452
|
-
statusModify(side.stored().status, stats);
|
|
2453
2623
|
}
|
|
2454
2624
|
|
|
2455
2625
|
pub fn unboost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2458,8 +2628,8 @@ pub const Effects = struct {
|
|
|
2458
2628
|
const foe_ident = battle.active(player.foe());
|
|
2459
2629
|
|
|
2460
2630
|
if (foe.active.volatiles.Mist) {
|
|
2461
|
-
try log.activate(foe_ident, .Mist);
|
|
2462
|
-
return log.fail(foe_ident, .None);
|
|
2631
|
+
try log.activate(.{ foe_ident, .Mist });
|
|
2632
|
+
return log.fail(.{ foe_ident, .None });
|
|
2463
2633
|
}
|
|
2464
2634
|
|
|
2465
2635
|
const fail = foe.active.volatiles.Substitute or state.miss or !state.proc;
|
|
@@ -2468,87 +2638,76 @@ pub const Effects = struct {
|
|
|
2468
2638
|
var boosts = &foe.active.boosts;
|
|
2469
2639
|
|
|
2470
2640
|
const move = Move.get(state.move);
|
|
2641
|
+
const none: Fail = .None;
|
|
2471
2642
|
switch (move.effect) {
|
|
2472
2643
|
.AttackDown1, .AttackDown2, .AttackDownChance => {
|
|
2473
2644
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2474
2645
|
if (fail or boosts.atk == -6) {
|
|
2475
|
-
return if (move.effect != .AttackDownChance) try log.fail(foe_ident,
|
|
2646
|
+
return if (move.effect != .AttackDownChance) try log.fail(.{ foe_ident, none });
|
|
2476
2647
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2477
|
-
try options.log.lastmiss();
|
|
2478
|
-
return options.log.miss(battle.active(player));
|
|
2648
|
+
try options.log.lastmiss(.{});
|
|
2649
|
+
return options.log.miss(.{battle.active(player)});
|
|
2479
2650
|
} else if (stats.atk == 1) {
|
|
2480
|
-
return if (move.effect != .AttackDownChance) try log.fail(foe_ident,
|
|
2651
|
+
return if (move.effect != .AttackDownChance) try log.fail(.{ foe_ident, none });
|
|
2481
2652
|
}
|
|
2482
2653
|
const n: u2 = if (move.effect == .AttackDown2) 2 else 1;
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
const stat = unmodifiedStats(battle, foe).atk;
|
|
2486
|
-
stats.atk = @max(1, stat * mod[0] / mod[1]);
|
|
2487
|
-
try log.boost(foe_ident, .Attack, -@as(i8, n));
|
|
2654
|
+
try unboostStat(battle, foe, stats, boosts, "atk", n);
|
|
2655
|
+
try log.boost(.{ foe_ident, .Attack, -@as(i8, n) });
|
|
2488
2656
|
},
|
|
2489
2657
|
.DefenseDown1, .DefenseDown2, .DefenseDownChance => {
|
|
2490
2658
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2491
2659
|
if (fail or boosts.def == -6) {
|
|
2492
|
-
|
|
2660
|
+
if (move.effect != .DefenseDownChance) try log.fail(.{ foe_ident, none });
|
|
2661
|
+
return;
|
|
2493
2662
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2494
|
-
try options.log.lastmiss();
|
|
2495
|
-
return options.log.miss(battle.active(player));
|
|
2663
|
+
try options.log.lastmiss(.{});
|
|
2664
|
+
return options.log.miss(.{battle.active(player)});
|
|
2496
2665
|
} else if (stats.def == 1) {
|
|
2497
|
-
|
|
2666
|
+
if (move.effect != .DefenseDownChance) try log.fail(.{ foe_ident, none });
|
|
2667
|
+
return;
|
|
2498
2668
|
}
|
|
2499
2669
|
const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
const stat = unmodifiedStats(battle, foe).def;
|
|
2503
|
-
stats.def = @max(1, stat * mod[0] / mod[1]);
|
|
2504
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2670
|
+
try unboostStat(battle, foe, stats, boosts, "def", n);
|
|
2671
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2505
2672
|
},
|
|
2506
2673
|
.SpeedDown1, .SpeedDown2, .SpeedDownChance => {
|
|
2507
2674
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2508
2675
|
if (fail or boosts.spe == -6) {
|
|
2509
|
-
return if (move.effect != .SpeedDownChance) try log.fail(foe_ident,
|
|
2676
|
+
return if (move.effect != .SpeedDownChance) try log.fail(.{ foe_ident, none });
|
|
2510
2677
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2511
|
-
try options.log.lastmiss();
|
|
2512
|
-
return options.log.miss(battle.active(player));
|
|
2678
|
+
try options.log.lastmiss(.{});
|
|
2679
|
+
return options.log.miss(.{battle.active(player)});
|
|
2513
2680
|
} else if (stats.spe == 1) {
|
|
2514
|
-
return if (move.effect != .SpeedDownChance) try log.fail(foe_ident,
|
|
2681
|
+
return if (move.effect != .SpeedDownChance) try log.fail(.{ foe_ident, none });
|
|
2515
2682
|
}
|
|
2516
2683
|
const n: u2 = if (move.effect == .SpeedDown2) 2 else 1;
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
const stat = unmodifiedStats(battle, foe).spe;
|
|
2520
|
-
stats.spe = @max(1, stat * mod[0] / mod[1]);
|
|
2521
|
-
try log.boost(foe_ident, .Speed, -@as(i8, n));
|
|
2522
|
-
assert(boosts.spe >= -6);
|
|
2684
|
+
try unboostStat(battle, foe, stats, boosts, "spe", n);
|
|
2685
|
+
try log.boost(.{ foe_ident, .Speed, -@as(i8, n) });
|
|
2523
2686
|
},
|
|
2524
2687
|
.SpDefDownChance => {
|
|
2525
2688
|
assert(boosts.spd >= -6 and boosts.spd <= 6);
|
|
2526
2689
|
if (fail or boosts.spd == -6) return;
|
|
2527
2690
|
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2528
|
-
try options.log.lastmiss();
|
|
2529
|
-
return options.log.miss(battle.active(player));
|
|
2691
|
+
try options.log.lastmiss(.{});
|
|
2692
|
+
return options.log.miss(.{battle.active(player)});
|
|
2530
2693
|
} else if (stats.spd == 1) return;
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
const stat = unmodifiedStats(battle, foe).spd;
|
|
2534
|
-
stats.spd = @max(1, stat * mod[0] / mod[1]);
|
|
2535
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2694
|
+
try unboostStat(battle, foe, stats, boosts, "spd", 1);
|
|
2695
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2536
2696
|
},
|
|
2537
2697
|
.AccuracyDown1, .AccuracyDownChance => {
|
|
2538
2698
|
assert(boosts.accuracy >= -6 and boosts.accuracy <= 6);
|
|
2539
2699
|
if (fail or boosts.accuracy == -6) {
|
|
2540
|
-
|
|
2700
|
+
if (move.effect != .AccuracyDownChance) try log.fail(.{ foe_ident, none });
|
|
2701
|
+
return;
|
|
2541
2702
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2542
|
-
try options.log.lastmiss();
|
|
2543
|
-
return options.log.miss(battle.active(player));
|
|
2703
|
+
try options.log.lastmiss(.{});
|
|
2704
|
+
return options.log.miss(.{battle.active(player)});
|
|
2544
2705
|
}
|
|
2545
2706
|
boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
|
|
2546
|
-
try log.boost(foe_ident, .Accuracy, -1);
|
|
2707
|
+
try log.boost(.{ foe_ident, .Accuracy, -1 });
|
|
2547
2708
|
},
|
|
2548
2709
|
else => unreachable,
|
|
2549
2710
|
}
|
|
2550
|
-
|
|
2551
|
-
statusModify(foe.stored().status, stats);
|
|
2552
2711
|
}
|
|
2553
2712
|
};
|
|
2554
2713
|
|
|
@@ -2558,6 +2717,15 @@ fn unmodifiedStats(battle: anytype, side: *Side) *Stats(u16) {
|
|
|
2558
2717
|
return &battle.side(id.player).pokemon[id.id - 1].stats;
|
|
2559
2718
|
}
|
|
2560
2719
|
|
|
2720
|
+
fn recomputeStats(battle: anytype, side: *Side) void {
|
|
2721
|
+
var stats = unmodifiedStats(battle, side);
|
|
2722
|
+
inline for (STATS) |s| {
|
|
2723
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(side.active.boosts, s)) + 6))];
|
|
2724
|
+
const val = @field(stats, s) * mod[0] / mod[1];
|
|
2725
|
+
@field(side.active.stats, s) = @max(1, @min(MAX_STAT_VALUE, val));
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2561
2729
|
fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
2562
2730
|
if (Status.is(status, .PAR)) {
|
|
2563
2731
|
stats.spe = @max(stats.spe / 4, 1);
|
|
@@ -2566,6 +2734,52 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
2566
2734
|
}
|
|
2567
2735
|
}
|
|
2568
2736
|
|
|
2737
|
+
fn boostStat(
|
|
2738
|
+
battle: anytype,
|
|
2739
|
+
side: *Side,
|
|
2740
|
+
stats: *Stats(u16),
|
|
2741
|
+
boosts: *Boosts,
|
|
2742
|
+
comptime s: []const u8,
|
|
2743
|
+
n: u2,
|
|
2744
|
+
) !void {
|
|
2745
|
+
@field(boosts, s) = @intCast(@min(6, @as(i8, @field(boosts, s)) + n));
|
|
2746
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
|
|
2747
|
+
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2748
|
+
@field(stats, s) = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2749
|
+
if (comptime std.mem.eql(u8, s, "atk")) {
|
|
2750
|
+
if (Status.is(side.stored().status, .BRN)) {
|
|
2751
|
+
@field(stats, s) = @max(@field(stats, s) / 2, 1);
|
|
2752
|
+
}
|
|
2753
|
+
} else if (comptime std.mem.eql(u8, s, "spe")) {
|
|
2754
|
+
if (Status.is(side.stored().status, .PAR)) {
|
|
2755
|
+
@field(stats, s) = @max(@field(stats, s) / 4, 1);
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
fn unboostStat(
|
|
2761
|
+
battle: anytype,
|
|
2762
|
+
side: *Side,
|
|
2763
|
+
stats: *Stats(u16),
|
|
2764
|
+
boosts: *Boosts,
|
|
2765
|
+
comptime s: []const u8,
|
|
2766
|
+
n: u2,
|
|
2767
|
+
) !void {
|
|
2768
|
+
@field(boosts, s) = @intCast(@max(-6, @as(i8, @field(boosts, s)) - n));
|
|
2769
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
|
|
2770
|
+
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2771
|
+
@field(stats, s) = @max(1, stat * mod[0] / mod[1]);
|
|
2772
|
+
if (comptime std.mem.eql(u8, s, "atk")) {
|
|
2773
|
+
if (Status.is(side.stored().status, .BRN)) {
|
|
2774
|
+
@field(stats, s) = @max(@field(stats, s) / 2, 1);
|
|
2775
|
+
}
|
|
2776
|
+
} else if (comptime std.mem.eql(u8, s, "spe")) {
|
|
2777
|
+
if (Status.is(side.stored().status, .PAR)) {
|
|
2778
|
+
@field(stats, s) = @max(@field(stats, s) / 4, 1);
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
|
|
2569
2783
|
fn isForced(active: anytype) bool {
|
|
2570
2784
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
2571
2785
|
active.volatiles.Thrashing or active.volatiles.Charging or
|
|
@@ -2755,7 +2969,7 @@ pub const Rolls = struct {
|
|
|
2755
2969
|
}
|
|
2756
2970
|
|
|
2757
2971
|
fn magnitude(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2758
|
-
const num = if (options.calc.overridden(player, .magnitude)) |val|
|
|
2972
|
+
const num: u8 = if (options.calc.overridden(player, .magnitude)) |val|
|
|
2759
2973
|
val + 3
|
|
2760
2974
|
else if (showdown) num: {
|
|
2761
2975
|
const r = battle.rng.range(u8, 0, 100);
|
|
@@ -3030,7 +3244,7 @@ pub const Rolls = struct {
|
|
|
3030
3244
|
|
|
3031
3245
|
const move: Move = overridden orelse move: {
|
|
3032
3246
|
if (showdown) {
|
|
3033
|
-
var r = battle.rng.range(u8, 0, @intCast(Move.METRONOME.len - n));
|
|
3247
|
+
var r = battle.rng.range(u8, 0, @as(u8, @intCast(Move.METRONOME.len - n)));
|
|
3034
3248
|
for (moves) |m| {
|
|
3035
3249
|
if (m.id == .None) break;
|
|
3036
3250
|
if (@intFromEnum(m.id) > @intFromEnum(Move.METRONOME[r])) break;
|
|
@@ -3273,7 +3487,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3273
3487
|
}
|
|
3274
3488
|
|
|
3275
3489
|
var slot: u4 = 2;
|
|
3276
|
-
while (!active.volatiles.
|
|
3490
|
+
while (!active.volatiles.Trapped and slot <= 6) : (slot += 1) {
|
|
3277
3491
|
const id = side.order[slot - 1];
|
|
3278
3492
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
3279
3493
|
out[n] = .{ .type = .Switch, .data = slot };
|