@pkmn/engine 0.1.0-dev.1220fbc5 → 0.1.0-dev.12e7292c
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/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/package.json +14 -13
- package/src/lib/common/protocol.zig +335 -333
- package/src/lib/common/rational.zig +1 -1
- package/src/lib/gen1/mechanics.zig +197 -201
- package/src/lib/gen2/README.md +0 -1
- package/src/lib/gen2/data.zig +1 -1
- package/src/lib/gen2/generated.zig +14 -21
- package/src/lib/gen2/mechanics.zig +660 -383
|
@@ -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,16 +1274,12 @@ 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
|
}
|
|
1261
1281
|
|
|
1262
1282
|
pub const Effects = struct {
|
|
1263
|
-
pub fn alwaysHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1264
|
-
_ = .{ battle, player, state, options }; // TODO
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
1283
|
pub fn attract(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1268
1284
|
const side = battle.side(player);
|
|
1269
1285
|
var foe = battle.foe(player);
|
|
@@ -1271,16 +1287,14 @@ pub const Effects = struct {
|
|
|
1271
1287
|
const gender: u8 = @intFromEnum(side.stored().dvs.gender);
|
|
1272
1288
|
const foe_gender: u8 = @intFromEnum(foe.stored().dvs.gender);
|
|
1273
1289
|
if (gender + foe_gender != 1) {
|
|
1274
|
-
|
|
1275
|
-
return;
|
|
1290
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
1276
1291
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1277
|
-
try options.log.lastmiss();
|
|
1278
|
-
|
|
1279
|
-
return;
|
|
1292
|
+
try options.log.lastmiss(.{});
|
|
1293
|
+
return options.log.miss(.{battle.active(player)});
|
|
1280
1294
|
}
|
|
1281
1295
|
|
|
1282
1296
|
foe.active.volatiles.Attract = true;
|
|
1283
|
-
try options.log.start(battle.active(player.foe()), .Attract);
|
|
1297
|
+
try options.log.start(.{ battle.active(player.foe()), .Attract });
|
|
1284
1298
|
}
|
|
1285
1299
|
|
|
1286
1300
|
pub fn batonPass(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1293,6 +1307,13 @@ pub const Effects = struct {
|
|
|
1293
1307
|
|
|
1294
1308
|
pub fn bellyDrum(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1295
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
|
+
//
|
|
1296
1317
|
}
|
|
1297
1318
|
|
|
1298
1319
|
pub const bide = struct {
|
|
@@ -1312,10 +1333,10 @@ pub const Effects = struct {
|
|
|
1312
1333
|
// }
|
|
1313
1334
|
try options.chance.attacking(player, volatiles.attacks);
|
|
1314
1335
|
|
|
1315
|
-
if (volatiles.attacks != 0) return options.log.activate(ident, .Bide);
|
|
1336
|
+
if (volatiles.attacks != 0) return options.log.activate(.{ ident, .Bide });
|
|
1316
1337
|
|
|
1317
1338
|
volatiles.Bide = false;
|
|
1318
|
-
try options.log.end(ident, .Bide);
|
|
1339
|
+
try options.log.end(.{ ident, .Bide });
|
|
1319
1340
|
|
|
1320
1341
|
state.damage = volatiles.bide *% 2;
|
|
1321
1342
|
volatiles.bide = 0;
|
|
@@ -1329,7 +1350,16 @@ pub const Effects = struct {
|
|
|
1329
1350
|
};
|
|
1330
1351
|
|
|
1331
1352
|
pub fn binding(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1332
|
-
|
|
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
|
+
};
|
|
1333
1363
|
}
|
|
1334
1364
|
|
|
1335
1365
|
pub fn burnChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1339,12 +1369,12 @@ pub const Effects = struct {
|
|
|
1339
1369
|
if (foe.active.volatiles.Substitute) return;
|
|
1340
1370
|
if (Status.any(foe_stored.status)) {
|
|
1341
1371
|
if (Status.is(foe_stored.status, .FRZ)) {
|
|
1342
|
-
assert(Move.get(state.move).type == .Fire);
|
|
1343
|
-
try options.log.curestatus(
|
|
1372
|
+
assert(Move.get(state.move).type == .Fire or state.move == .TriAttack);
|
|
1373
|
+
try options.log.curestatus(.{
|
|
1344
1374
|
battle.active(player.foe()),
|
|
1345
1375
|
foe_stored.status,
|
|
1346
1376
|
.Message,
|
|
1347
|
-
);
|
|
1377
|
+
});
|
|
1348
1378
|
foe_stored.status = 0;
|
|
1349
1379
|
}
|
|
1350
1380
|
return;
|
|
@@ -1353,11 +1383,14 @@ pub const Effects = struct {
|
|
|
1353
1383
|
if (foe.conditions.Safeguard) return;
|
|
1354
1384
|
// Nothing is immune to Fire-type, but Ghosts are immune to burnChance called by Tri Attack
|
|
1355
1385
|
if (state.immune()) return;
|
|
1356
|
-
if (foe.active.types.includes(Move.get(state.move).type))
|
|
1386
|
+
if (foe.active.types.includes(Move.get(state.move).type) and state.move != .TriAttack) {
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1357
1389
|
|
|
1358
1390
|
foe_stored.status = Status.init(.BRN);
|
|
1359
1391
|
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
1360
|
-
|
|
1392
|
+
|
|
1393
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1361
1394
|
|
|
1362
1395
|
// TODO: check for status berry
|
|
1363
1396
|
}
|
|
@@ -1377,10 +1410,7 @@ pub const Effects = struct {
|
|
|
1377
1410
|
for (side.active.moves) |m| {
|
|
1378
1411
|
if (convertible(side.active, m.id)) n += 1;
|
|
1379
1412
|
}
|
|
1380
|
-
if (n == 0) {
|
|
1381
|
-
try options.log.fail(battle.active(player), .None);
|
|
1382
|
-
return;
|
|
1383
|
-
}
|
|
1413
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
1384
1414
|
|
|
1385
1415
|
side.active.types.type1 = try Rolls.conversion(battle, player, n, options);
|
|
1386
1416
|
side.active.types.type2 = side.active.types.type1;
|
|
@@ -1390,26 +1420,87 @@ pub const Effects = struct {
|
|
|
1390
1420
|
var side = battle.side(player);
|
|
1391
1421
|
|
|
1392
1422
|
const last = battle.foe(player).lastMove(false);
|
|
1393
|
-
if (last == .None) {
|
|
1394
|
-
try options.log.fail(battle.active(player), .None);
|
|
1395
|
-
return;
|
|
1396
|
-
}
|
|
1423
|
+
if (last == .None) return options.log.fail(.{ battle.active(player), .None });
|
|
1397
1424
|
const move = Move.get(last);
|
|
1398
|
-
if (move.type == .@"???") {
|
|
1399
|
-
try options.log.fail(battle.active(player), .None);
|
|
1400
|
-
return;
|
|
1401
|
-
}
|
|
1425
|
+
if (move.type == .@"???") return options.log.fail(.{ battle.active(player), .None });
|
|
1402
1426
|
|
|
1403
1427
|
side.active.types.type1 = try Rolls.conversion2(battle, player, move.type, options);
|
|
1404
1428
|
side.active.types.type2 = side.active.types.type1;
|
|
1405
1429
|
}
|
|
1406
1430
|
|
|
1407
|
-
pub fn counter(battle: anytype, player: Player, state: *State,
|
|
1408
|
-
|
|
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;
|
|
1409
1456
|
}
|
|
1410
1457
|
|
|
1411
1458
|
pub fn curse(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1412
|
-
|
|
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
|
+
}
|
|
1413
1504
|
}
|
|
1414
1505
|
|
|
1415
1506
|
pub fn defenseCurl(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
@@ -1419,14 +1510,13 @@ pub const Effects = struct {
|
|
|
1419
1510
|
pub fn defrost(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1420
1511
|
var side = battle.side(player);
|
|
1421
1512
|
assert(Status.is(side.stored().status, .FRZ));
|
|
1422
|
-
|
|
1423
|
-
try options.log.curestatus(battle.active(player), side.stored().status, .Message);
|
|
1513
|
+
try options.log.curestatus(.{ battle.active(player), side.stored().status, .Message });
|
|
1424
1514
|
side.stored().status = 0;
|
|
1425
1515
|
}
|
|
1426
1516
|
|
|
1427
1517
|
pub fn destinyBond(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1428
1518
|
battle.side(player).active.volatiles.DestinyBond = true;
|
|
1429
|
-
try options.log.singlemove(battle.active(player), Move.DestinyBond);
|
|
1519
|
+
try options.log.singlemove(.{ battle.active(player), Move.DestinyBond });
|
|
1430
1520
|
}
|
|
1431
1521
|
|
|
1432
1522
|
pub fn disable(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
@@ -1434,16 +1524,10 @@ pub const Effects = struct {
|
|
|
1434
1524
|
var volatiles = &foe.active.volatiles;
|
|
1435
1525
|
const foe_ident = battle.active(player.foe());
|
|
1436
1526
|
|
|
1437
|
-
if (volatiles.disable.move != 0) {
|
|
1438
|
-
try options.log.fail(foe_ident, .None);
|
|
1439
|
-
return;
|
|
1440
|
-
}
|
|
1527
|
+
if (volatiles.disable.move != 0) return options.log.fail(.{ foe_ident, .None });
|
|
1441
1528
|
|
|
1442
1529
|
const last = battle.foe(player).lastMove(false);
|
|
1443
|
-
if (last == .None or last == .Struggle) {
|
|
1444
|
-
try options.log.fail(foe_ident, .None);
|
|
1445
|
-
return;
|
|
1446
|
-
}
|
|
1530
|
+
if (last == .None or last == .Struggle) return options.log.fail(.{ foe_ident, .None });
|
|
1447
1531
|
|
|
1448
1532
|
var slot: u3 = 0;
|
|
1449
1533
|
for (foe.active.moves) |m| {
|
|
@@ -1452,15 +1536,12 @@ pub const Effects = struct {
|
|
|
1452
1536
|
if (m.id == last) break;
|
|
1453
1537
|
}
|
|
1454
1538
|
const move = foe.active.move(slot);
|
|
1455
|
-
if (move.pp == 0) {
|
|
1456
|
-
try options.log.fail(foe_ident, .None);
|
|
1457
|
-
return;
|
|
1458
|
-
}
|
|
1539
|
+
if (move.pp == 0) return options.log.fail(.{ foe_ident, .None });
|
|
1459
1540
|
|
|
1460
1541
|
volatiles.disable.move = slot;
|
|
1461
1542
|
volatiles.disable.duration = Rolls.disableDuration(battle, player, options);
|
|
1462
1543
|
|
|
1463
|
-
try options.log.
|
|
1544
|
+
try options.log.start(.{ foe_ident, .Disable, move.id });
|
|
1464
1545
|
}
|
|
1465
1546
|
|
|
1466
1547
|
pub const double = struct {
|
|
@@ -1479,13 +1560,14 @@ pub const Effects = struct {
|
|
|
1479
1560
|
|
|
1480
1561
|
pub fn drainHP(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1481
1562
|
var stored = battle.side(player).stored();
|
|
1563
|
+
const ident = battle.active(player);
|
|
1482
1564
|
|
|
1483
1565
|
assert(state.damage != 0);
|
|
1484
1566
|
|
|
1485
1567
|
if (stored.hp == stored.stats.hp) return;
|
|
1486
1568
|
stored.hp = @min(stored.stats.hp, stored.hp + @max(state.damage / 2, 1));
|
|
1487
1569
|
|
|
1488
|
-
try options.log.
|
|
1570
|
+
try options.log.heal(.{ ident, stored, .Drain, battle.active(player.foe()) });
|
|
1489
1571
|
}
|
|
1490
1572
|
|
|
1491
1573
|
pub fn encore(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1497,10 +1579,7 @@ pub const Effects = struct {
|
|
|
1497
1579
|
const failed = volatiles.Encore or
|
|
1498
1580
|
(last == .None or last == .Struggle or
|
|
1499
1581
|
last == .Encore or .last == .MirrorMove);
|
|
1500
|
-
if (failed) {
|
|
1501
|
-
try options.log.fail(foe_ident, .None);
|
|
1502
|
-
return;
|
|
1503
|
-
}
|
|
1582
|
+
if (failed) return options.log.fail(.{ foe_ident, .None });
|
|
1504
1583
|
|
|
1505
1584
|
var slot: u3 = 0;
|
|
1506
1585
|
for (foe.active.moves) |m| {
|
|
@@ -1509,10 +1588,7 @@ pub const Effects = struct {
|
|
|
1509
1588
|
if (m.id == last) break;
|
|
1510
1589
|
}
|
|
1511
1590
|
const move = foe.active.move(slot);
|
|
1512
|
-
if (move.pp == 0) {
|
|
1513
|
-
try options.log.fail(foe_ident, .None);
|
|
1514
|
-
return;
|
|
1515
|
-
}
|
|
1591
|
+
if (move.pp == 0) return options.log.fail(.{ foe_ident, .None });
|
|
1516
1592
|
|
|
1517
1593
|
volatiles.Encore = true;
|
|
1518
1594
|
volatiles.encore = Rolls.encoreDuration(battle, player, options);
|
|
@@ -1520,7 +1596,7 @@ pub const Effects = struct {
|
|
|
1520
1596
|
// TODO ???
|
|
1521
1597
|
_ = state;
|
|
1522
1598
|
|
|
1523
|
-
try options.log.
|
|
1599
|
+
try options.log.start(.{ foe_ident, .Encore, move.id });
|
|
1524
1600
|
}
|
|
1525
1601
|
|
|
1526
1602
|
pub fn explode(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1552,10 +1628,6 @@ pub const Effects = struct {
|
|
|
1552
1628
|
};
|
|
1553
1629
|
}
|
|
1554
1630
|
|
|
1555
|
-
pub fn flameWheel(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1556
|
-
_ = .{ battle, player, state, options }; // TODO
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
1631
|
pub fn flinchChance(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1560
1632
|
const foe = battle.foe(player);
|
|
1561
1633
|
const status = foe.stored().status;
|
|
@@ -1578,12 +1650,11 @@ pub const Effects = struct {
|
|
|
1578
1650
|
var side = battle.side(player);
|
|
1579
1651
|
|
|
1580
1652
|
if (side.active.volatiles.FocusEnergy) {
|
|
1581
|
-
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
1582
|
-
return;
|
|
1653
|
+
return if (!showdown) try options.log.fail(.{ battle.active(player), .None });
|
|
1583
1654
|
}
|
|
1584
1655
|
|
|
1585
1656
|
side.active.volatiles.FocusEnergy = true;
|
|
1586
|
-
try options.log.start(battle.active(player), .FocusEnergy);
|
|
1657
|
+
try options.log.start(.{ battle.active(player), .FocusEnergy });
|
|
1587
1658
|
}
|
|
1588
1659
|
|
|
1589
1660
|
var SLOTS: [5]u4 = .{ 0, 0, 0, 0, 0 };
|
|
@@ -1593,7 +1664,7 @@ pub const Effects = struct {
|
|
|
1593
1664
|
|
|
1594
1665
|
const foe = battle.foe(player);
|
|
1595
1666
|
|
|
1596
|
-
if (state.first) return options.log.fail(battle.active(player), .None);
|
|
1667
|
+
if (state.first) return options.log.fail(.{ battle.active(player), .None });
|
|
1597
1668
|
|
|
1598
1669
|
var n: u4 = 0;
|
|
1599
1670
|
var i: u4 = 0;
|
|
@@ -1607,10 +1678,7 @@ pub const Effects = struct {
|
|
|
1607
1678
|
}
|
|
1608
1679
|
}
|
|
1609
1680
|
|
|
1610
|
-
if (n == 0) {
|
|
1611
|
-
try options.log.fail(battle.active(player), .None);
|
|
1612
|
-
return;
|
|
1613
|
-
}
|
|
1681
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
1614
1682
|
|
|
1615
1683
|
const slot = try Rolls.forceSwitch(battle, player, SLOTS[0..i], n, options);
|
|
1616
1684
|
try switchIn(battle, player.foe(), slot, .Drag, options);
|
|
@@ -1620,16 +1688,14 @@ pub const Effects = struct {
|
|
|
1620
1688
|
var foe = battle.foe(player);
|
|
1621
1689
|
|
|
1622
1690
|
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1623
|
-
try options.log.lastmiss();
|
|
1624
|
-
|
|
1625
|
-
return;
|
|
1691
|
+
try options.log.lastmiss(.{});
|
|
1692
|
+
return options.log.miss(.{battle.active(player)});
|
|
1626
1693
|
} else if (foe.active.volatiles.Foresight) {
|
|
1627
|
-
|
|
1628
|
-
return;
|
|
1694
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
1629
1695
|
}
|
|
1630
1696
|
|
|
1631
1697
|
foe.active.volatiles.Foresight = true;
|
|
1632
|
-
try options.log.start(battle.active(player.foe()), .FocusEnergy);
|
|
1698
|
+
try options.log.start(.{ battle.active(player.foe()), .FocusEnergy });
|
|
1633
1699
|
}
|
|
1634
1700
|
|
|
1635
1701
|
pub fn freezeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1641,15 +1707,18 @@ pub const Effects = struct {
|
|
|
1641
1707
|
if (Status.any(foe_stored.status)) return;
|
|
1642
1708
|
|
|
1643
1709
|
// Nothing is immune to Ice-type, but Ghosts are immune to freezeChance called by Tri Attack
|
|
1710
|
+
if (state.immune()) return;
|
|
1644
1711
|
if (battle.field.weather == .Sun) return;
|
|
1645
|
-
if (foe.active.types.includes(Move.get(state.move).type))
|
|
1712
|
+
if (foe.active.types.includes(Move.get(state.move).type) and state.move != .TriAttack) {
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1646
1715
|
if (!state.proc) return;
|
|
1647
1716
|
if (foe.conditions.Safeguard) return;
|
|
1648
1717
|
// Freeze Clause Mod
|
|
1649
1718
|
if (showdown) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
|
|
1650
1719
|
|
|
1651
1720
|
foe_stored.status = Status.init(.FRZ);
|
|
1652
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
1721
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
1653
1722
|
|
|
1654
1723
|
// TODO: check for status berry
|
|
1655
1724
|
|
|
@@ -1671,46 +1740,131 @@ pub const Effects = struct {
|
|
|
1671
1740
|
state.bp = ((if (frustration) 255 - val else val) * 10) / 25;
|
|
1672
1741
|
}
|
|
1673
1742
|
|
|
1674
|
-
pub fn haze(battle: anytype, player: Player,
|
|
1675
|
-
|
|
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(.{});
|
|
1676
1757
|
}
|
|
1677
1758
|
|
|
1678
1759
|
pub fn heal(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1679
|
-
|
|
1680
|
-
|
|
1760
|
+
var side = battle.side(player);
|
|
1761
|
+
var stored = side.stored();
|
|
1762
|
+
const ident = battle.active(player);
|
|
1681
1763
|
|
|
1682
|
-
|
|
1683
|
-
|
|
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 });
|
|
1684
1779
|
}
|
|
1685
1780
|
|
|
1686
|
-
pub fn
|
|
1687
|
-
|
|
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);
|
|
1688
1791
|
}
|
|
1689
1792
|
|
|
1690
|
-
pub fn
|
|
1691
|
-
|
|
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;
|
|
1692
1797
|
}
|
|
1693
1798
|
|
|
1694
|
-
pub fn
|
|
1695
|
-
|
|
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)});
|
|
1696
1802
|
}
|
|
1697
1803
|
|
|
1698
1804
|
pub fn leechSeed(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1699
|
-
|
|
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 });
|
|
1700
1832
|
}
|
|
1701
1833
|
|
|
1702
1834
|
pub fn lockOn(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1703
|
-
|
|
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 });
|
|
1704
1846
|
}
|
|
1705
1847
|
|
|
1706
1848
|
const MAGNITUDE_POWER = [_]u8{ 10, 30, 50, 70, 90, 110, 150 };
|
|
1707
1849
|
|
|
1708
1850
|
pub fn magnitude(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1709
|
-
|
|
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];
|
|
1710
1854
|
}
|
|
1711
1855
|
|
|
1712
|
-
pub fn meanLook(battle: anytype, player: Player,
|
|
1713
|
-
|
|
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;
|
|
1714
1868
|
}
|
|
1715
1869
|
|
|
1716
1870
|
pub fn metronome(
|
|
@@ -1730,17 +1884,13 @@ pub const Effects = struct {
|
|
|
1730
1884
|
// FIXME
|
|
1731
1885
|
state.move = try Rolls.metronome(battle, player, options);
|
|
1732
1886
|
state.from = .Metronome;
|
|
1733
|
-
return
|
|
1887
|
+
return generated.runMove(battle, player, state, options);
|
|
1734
1888
|
}
|
|
1735
1889
|
|
|
1736
1890
|
pub fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1737
1891
|
_ = .{ battle, player, state, options }; // TODO
|
|
1738
1892
|
}
|
|
1739
1893
|
|
|
1740
|
-
pub fn mirrorCoat(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1741
|
-
_ = .{ battle, player, state, options }; // TODO
|
|
1742
|
-
}
|
|
1743
|
-
|
|
1744
1894
|
pub fn mirrorMove(
|
|
1745
1895
|
battle: anytype,
|
|
1746
1896
|
player: Player,
|
|
@@ -1755,11 +1905,11 @@ pub const Effects = struct {
|
|
|
1755
1905
|
|
|
1756
1906
|
const last = foe.lastMove(false);
|
|
1757
1907
|
|
|
1758
|
-
if (last == .None) return options.log.fail(battle.active(player), .None);
|
|
1908
|
+
if (last == .None) return options.log.fail(.{ battle.active(player), .None });
|
|
1759
1909
|
const knows = knows: for (side.active.moves) |m| {
|
|
1760
1910
|
if (last == m.id) break :knows true;
|
|
1761
1911
|
} else false;
|
|
1762
|
-
if (knows) return options.log.fail(battle.active(player), .None);
|
|
1912
|
+
if (knows) return options.log.fail(.{ battle.active(player), .None });
|
|
1763
1913
|
|
|
1764
1914
|
// if (wPlayerCharging) ???
|
|
1765
1915
|
// wPlayerCharging = 1
|
|
@@ -1767,23 +1917,23 @@ pub const Effects = struct {
|
|
|
1767
1917
|
// FIXME
|
|
1768
1918
|
state.move = last;
|
|
1769
1919
|
state.from = .MirrorMove;
|
|
1770
|
-
return
|
|
1920
|
+
return generated.runMove(battle, player, state, options);
|
|
1771
1921
|
}
|
|
1772
1922
|
|
|
1773
1923
|
pub fn mist(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1774
1924
|
var side = battle.side(player);
|
|
1775
1925
|
|
|
1776
|
-
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 });
|
|
1777
1927
|
side.active.volatiles.Mist = true;
|
|
1778
1928
|
|
|
1779
|
-
try options.log.start(battle.active(player), .Mist);
|
|
1929
|
+
try options.log.start(.{ battle.active(player), .Mist });
|
|
1780
1930
|
}
|
|
1781
1931
|
|
|
1782
1932
|
pub fn weatherHeal(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1783
1933
|
var stored = battle.side(player).stored();
|
|
1784
1934
|
const ident = battle.active(player);
|
|
1785
1935
|
|
|
1786
|
-
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 });
|
|
1787
1937
|
|
|
1788
1938
|
stored.hp = switch (battle.field.weather) {
|
|
1789
1939
|
.Sun => stored.stats.hp,
|
|
@@ -1791,7 +1941,7 @@ pub const Effects = struct {
|
|
|
1791
1941
|
else => @min(stored.stats.hp, stored.hp + (stored.stats.hp / 4)),
|
|
1792
1942
|
};
|
|
1793
1943
|
|
|
1794
|
-
try options.log.heal(ident, stored,
|
|
1944
|
+
try options.log.heal(.{ ident, stored, .None });
|
|
1795
1945
|
}
|
|
1796
1946
|
|
|
1797
1947
|
pub fn multiHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1804,17 +1954,17 @@ pub const Effects = struct {
|
|
|
1804
1954
|
const foe_ident = battle.active(player.foe());
|
|
1805
1955
|
|
|
1806
1956
|
if (foe_volatiles.Flying or foe_volatiles.Underground) {
|
|
1807
|
-
if (!showdown) return options.log.fail(battle.active(player.foe()), .None);
|
|
1808
|
-
try options.log.lastmiss();
|
|
1809
|
-
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)});
|
|
1810
1960
|
} else if (foe_volatiles.Substitute) {
|
|
1811
|
-
return options.log.
|
|
1961
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1812
1962
|
} else if (!Status.is(foe.stored().status, .SLP) or foe_volatiles.Nightmare) {
|
|
1813
|
-
return options.log.fail(foe_ident, .None);
|
|
1963
|
+
return options.log.fail(.{ foe_ident, .None });
|
|
1814
1964
|
}
|
|
1815
1965
|
|
|
1816
1966
|
foe_volatiles.Nightmare = true;
|
|
1817
|
-
try options.log.start(battle.active(player.foe()), .Nightmare);
|
|
1967
|
+
try options.log.start(.{ battle.active(player.foe()), .Nightmare });
|
|
1818
1968
|
}
|
|
1819
1969
|
|
|
1820
1970
|
pub const ohko = struct {
|
|
@@ -1838,9 +1988,9 @@ pub const Effects = struct {
|
|
|
1838
1988
|
const foe_ident = battle.active(player.foe());
|
|
1839
1989
|
|
|
1840
1990
|
state.damage = 0;
|
|
1841
|
-
if (state.immune()) return options.log.immune(foe_ident, .None);
|
|
1991
|
+
if (state.immune()) return options.log.immune(.{ foe_ident, .None });
|
|
1842
1992
|
const delta = side.stored().level -| foe.stored().level;
|
|
1843
|
-
if (delta == 0) return options.log.immune(foe_ident, .OHKO);
|
|
1993
|
+
if (delta == 0) return options.log.immune(.{ foe_ident, .OHKO });
|
|
1844
1994
|
|
|
1845
1995
|
state.accuracy = 2 * delta +| Gen12.percent(30);
|
|
1846
1996
|
try checkHit(battle, player, state, options);
|
|
@@ -1856,21 +2006,39 @@ pub const Effects = struct {
|
|
|
1856
2006
|
const ident = battle.active(player);
|
|
1857
2007
|
const foe_ident = battle.active(player.foe());
|
|
1858
2008
|
|
|
1859
|
-
if (state.miss) return options.log.fail(foe_ident, .None);
|
|
2009
|
+
if (state.miss) return options.log.fail(.{ foe_ident, .None });
|
|
1860
2010
|
if (foe.active.volatiles.Substitute) {
|
|
1861
|
-
return options.log.
|
|
2011
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1862
2012
|
}
|
|
1863
2013
|
|
|
1864
2014
|
const avg = @max((foe.stored().hp + side.stored().hp) / 2, 1);
|
|
1865
2015
|
foe.stored().hp -= avg;
|
|
1866
2016
|
side.stored().hp = avg;
|
|
1867
2017
|
|
|
1868
|
-
try options.log.sethp(foe_ident, foe.stored(), .Silent);
|
|
1869
|
-
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 });
|
|
1870
2020
|
}
|
|
1871
2021
|
|
|
1872
2022
|
pub fn paralyze(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1873
|
-
|
|
2023
|
+
var foe = battle.foe(player);
|
|
2024
|
+
const foe_ident = battle.active(player.foe());
|
|
2025
|
+
|
|
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 });
|
|
2031
|
+
if (state.miss) {
|
|
2032
|
+
try options.log.lastmiss(.{});
|
|
2033
|
+
return options.log.miss(.{battle.active(player)});
|
|
2034
|
+
} else if (foe.active.volatiles.Substitute) {
|
|
2035
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
foe.stored().status = Status.init(.PAR);
|
|
2039
|
+
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2040
|
+
|
|
2041
|
+
try options.log.status(.{ foe_ident, foe.stored().status, .None });
|
|
1874
2042
|
}
|
|
1875
2043
|
|
|
1876
2044
|
pub fn paralyzeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1885,14 +2053,15 @@ pub const Effects = struct {
|
|
|
1885
2053
|
|
|
1886
2054
|
foe_stored.status = Status.init(.PAR);
|
|
1887
2055
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
1888
|
-
|
|
2056
|
+
|
|
2057
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1889
2058
|
|
|
1890
2059
|
// TODO: check for status berry
|
|
1891
2060
|
}
|
|
1892
2061
|
|
|
1893
2062
|
pub fn payDay(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1894
2063
|
if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
|
|
1895
|
-
try options.log.fieldactivate(); // FIXME .PayDay
|
|
2064
|
+
try options.log.fieldactivate(.{}); // FIXME .PayDay
|
|
1896
2065
|
}
|
|
1897
2066
|
}
|
|
1898
2067
|
|
|
@@ -1901,17 +2070,17 @@ pub const Effects = struct {
|
|
|
1901
2070
|
var foe = battle.foe(player);
|
|
1902
2071
|
|
|
1903
2072
|
if (foe.active.volatiles.PerishSong) {
|
|
1904
|
-
return options.log.fail(battle.active(player.foe()), .None);
|
|
2073
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1905
2074
|
} else if (!side.active.volatiles.PerishSong) {
|
|
1906
2075
|
side.active.volatiles.PerishSong = true;
|
|
1907
2076
|
side.active.volatiles.perish_song = 4;
|
|
1908
|
-
try options.log.start(battle.active(player), .PerishSong3Silent);
|
|
2077
|
+
try options.log.start(.{ battle.active(player), .PerishSong3Silent });
|
|
1909
2078
|
}
|
|
1910
2079
|
foe.active.volatiles.PerishSong = true;
|
|
1911
2080
|
foe.active.volatiles.perish_song = 4;
|
|
1912
|
-
try options.log.start(battle.active(player.foe()), .PerishSong3Silent);
|
|
2081
|
+
try options.log.start(.{ battle.active(player.foe()), .PerishSong3Silent });
|
|
1913
2082
|
|
|
1914
|
-
try options.log.fieldactivate(); // FIXME .PerishSong
|
|
2083
|
+
try options.log.fieldactivate(.{}); // FIXME .PerishSong
|
|
1915
2084
|
}
|
|
1916
2085
|
|
|
1917
2086
|
pub fn poison(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1921,24 +2090,24 @@ pub const Effects = struct {
|
|
|
1921
2090
|
|
|
1922
2091
|
const toxic = state.move == .Toxic;
|
|
1923
2092
|
if (state.immune() or foe.active.types.includes(.Poison)) {
|
|
1924
|
-
return options.log.immune(foe_ident, .None);
|
|
2093
|
+
return options.log.immune(.{ foe_ident, .None });
|
|
1925
2094
|
} else if (Status.any(foe_stored.status)) {
|
|
1926
2095
|
// Pokémon Showdown considers Toxic to be a status even in Generation II and so
|
|
1927
2096
|
// will not include a fail reason for Toxic vs. Poison or vice-versa...
|
|
1928
|
-
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))
|
|
1929
2098
|
if (!showdown)
|
|
1930
|
-
.Poison
|
|
2099
|
+
Fail.Poison
|
|
1931
2100
|
else if (toxic == (foe_stored.status == Status.TOX))
|
|
1932
|
-
if (toxic) .Toxic else .Poison
|
|
2101
|
+
if (toxic) Fail.Toxic else Fail.Poison
|
|
1933
2102
|
else
|
|
1934
|
-
.None
|
|
2103
|
+
Fail.None
|
|
1935
2104
|
else
|
|
1936
|
-
.None);
|
|
2105
|
+
Fail.None });
|
|
1937
2106
|
} else if (foe.active.volatiles.Substitute) {
|
|
1938
|
-
return options.log.
|
|
2107
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1939
2108
|
} else if (state.miss) {
|
|
1940
|
-
try options.log.lastmiss();
|
|
1941
|
-
|
|
2109
|
+
try options.log.lastmiss(.{});
|
|
2110
|
+
return options.log.miss(.{battle.active(player)});
|
|
1942
2111
|
}
|
|
1943
2112
|
|
|
1944
2113
|
foe_stored.status = Status.init(.PSN);
|
|
@@ -1948,7 +2117,7 @@ pub const Effects = struct {
|
|
|
1948
2117
|
foe.active.volatiles.toxic = 0;
|
|
1949
2118
|
}
|
|
1950
2119
|
|
|
1951
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
2120
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
1952
2121
|
|
|
1953
2122
|
// TODO: check for status berry
|
|
1954
2123
|
}
|
|
@@ -1964,7 +2133,8 @@ pub const Effects = struct {
|
|
|
1964
2133
|
if (foe.conditions.Safeguard) return;
|
|
1965
2134
|
|
|
1966
2135
|
foe_stored.status = Status.init(.PSN);
|
|
1967
|
-
|
|
2136
|
+
|
|
2137
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1968
2138
|
|
|
1969
2139
|
// TODO: check for status berry
|
|
1970
2140
|
}
|
|
@@ -1979,10 +2149,7 @@ pub const Effects = struct {
|
|
|
1979
2149
|
|
|
1980
2150
|
const failed = !state.first or volatiles.Substitute or
|
|
1981
2151
|
!try Rolls.protect(battle, player, volatiles.protect, options);
|
|
1982
|
-
if (failed) {
|
|
1983
|
-
try options.log.fail(battle.active(player), .None);
|
|
1984
|
-
return;
|
|
1985
|
-
}
|
|
2152
|
+
if (failed) return options.log.fail(.{ battle.active(player), .None });
|
|
1986
2153
|
|
|
1987
2154
|
volatiles.protect = @max(8, volatiles.protect + 1);
|
|
1988
2155
|
if (state.move == .Endure) {
|
|
@@ -1990,21 +2157,20 @@ pub const Effects = struct {
|
|
|
1990
2157
|
} else {
|
|
1991
2158
|
volatiles.Protect = true;
|
|
1992
2159
|
}
|
|
1993
|
-
try options.log.singleturn(battle.active(player), state.move);
|
|
2160
|
+
try options.log.singleturn(.{ battle.active(player), state.move });
|
|
1994
2161
|
}
|
|
1995
2162
|
|
|
1996
2163
|
pub fn psychUp(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
// const foe = battle.foe(player);
|
|
2164
|
+
var side = battle.side(player);
|
|
2165
|
+
const foe = battle.foe(player);
|
|
2000
2166
|
|
|
2001
|
-
//
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2167
|
+
// Pokémon Showdown should fail when the enemy has no boosts/unboosts but doesn't
|
|
2168
|
+
if (!showdown and @as(u32, @bitCast(foe.active.boosts)) == 0) {
|
|
2169
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
2170
|
+
}
|
|
2005
2171
|
|
|
2006
|
-
|
|
2007
|
-
|
|
2172
|
+
side.active.boosts = foe.active.boosts;
|
|
2173
|
+
try options.log.copyboost(.{ battle.active(player), battle.active(player.foe()) });
|
|
2008
2174
|
}
|
|
2009
2175
|
|
|
2010
2176
|
pub fn pursuit(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
@@ -2015,7 +2181,7 @@ pub const Effects = struct {
|
|
|
2015
2181
|
pub const rage = struct {
|
|
2016
2182
|
pub fn start(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2017
2183
|
battle.side(player).active.volatiles.Rage = true;
|
|
2018
|
-
try options.log.singlemove(battle.active(player), Move.Rage);
|
|
2184
|
+
try options.log.singlemove(.{ battle.active(player), Move.Rage });
|
|
2019
2185
|
}
|
|
2020
2186
|
|
|
2021
2187
|
pub fn damage(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
@@ -2031,16 +2197,37 @@ pub const Effects = struct {
|
|
|
2031
2197
|
return weather(battle, .Rain, options);
|
|
2032
2198
|
}
|
|
2033
2199
|
|
|
2034
|
-
pub fn rapidSpin(battle: anytype, player: Player,
|
|
2035
|
-
|
|
2036
|
-
}
|
|
2200
|
+
pub fn rapidSpin(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2201
|
+
var side = battle.side(player);
|
|
2037
2202
|
|
|
2038
|
-
|
|
2039
|
-
|
|
2203
|
+
if (side.active.volatiles.LeechSeed) {
|
|
2204
|
+
try options.log.end(.{ battle.active(player), .LeechSeedFrom });
|
|
2205
|
+
side.active.volatiles.LeechSeed = false;
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
if (side.conditions.Spikes) {
|
|
2209
|
+
try options.log.sideend(.{ player, protocol.Side.Spikes, battle.active(player) });
|
|
2210
|
+
side.conditions.Spikes = false;
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
if (side.active.volatiles.bind.duration > 0) {
|
|
2214
|
+
const reason = @intFromEnum(End.Bind) + side.active.volatiles.bind.reason - 1;
|
|
2215
|
+
try options.log.end(.{ battle.active(player), @as(End, @enumFromInt(reason)) });
|
|
2216
|
+
side.active.volatiles.bind = .{};
|
|
2217
|
+
}
|
|
2040
2218
|
}
|
|
2041
2219
|
|
|
2042
2220
|
pub fn recoil(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2043
|
-
|
|
2221
|
+
var side = battle.side(player);
|
|
2222
|
+
var stored = side.stored();
|
|
2223
|
+
const ident = battle.active(player);
|
|
2224
|
+
|
|
2225
|
+
assert(state.damage > 0);
|
|
2226
|
+
|
|
2227
|
+
const damage: i16 = @intCast(@max(state.damage / 4, 1));
|
|
2228
|
+
stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
|
|
2229
|
+
|
|
2230
|
+
try options.log.damage(.{ ident, stored, .RecoilOf, battle.active(player.foe()) });
|
|
2044
2231
|
}
|
|
2045
2232
|
|
|
2046
2233
|
pub fn reversal(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2068,25 +2255,72 @@ pub const Effects = struct {
|
|
|
2068
2255
|
try calcDamage(battle, player, state, options);
|
|
2069
2256
|
}
|
|
2070
2257
|
|
|
2071
|
-
pub
|
|
2072
|
-
|
|
2258
|
+
pub const rollout = struct {
|
|
2259
|
+
pub fn check(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
2260
|
+
var side = battle.side(player);
|
|
2261
|
+
|
|
2262
|
+
if (!side.active.volatiles.Rollout) {
|
|
2263
|
+
side.active.volatiles.attacks = 0;
|
|
2264
|
+
} else {
|
|
2265
|
+
// TODO SkipToBattleCommand(doturn_command)
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
pub fn power(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2270
|
+
var side = battle.side(player);
|
|
2271
|
+
|
|
2272
|
+
if (Status.is(side.stored().status, .SLP)) return;
|
|
2273
|
+
|
|
2274
|
+
if (side.active.volatiles.attacks > 0) {
|
|
2275
|
+
// TODO set wSomeoneIsRampaging
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
if (state.miss) {
|
|
2279
|
+
side.active.volatiles.Rollout = false;
|
|
2280
|
+
return;
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
assert(side.active.volatiles.attacks < 5);
|
|
2284
|
+
side.active.volatiles.attacks += 1;
|
|
2285
|
+
side.active.volatiles.Rollout = side.active.volatiles.attacks != 5;
|
|
2286
|
+
|
|
2287
|
+
if (side.active.volatiles.DefenseCurl) state.damage *|= 2;
|
|
2288
|
+
}
|
|
2289
|
+
};
|
|
2290
|
+
|
|
2291
|
+
pub fn sandstorm(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2292
|
+
if (battle.field.weather != .Sandstorm) return weather(battle, .Sandstorm, options);
|
|
2293
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
2073
2294
|
}
|
|
2074
2295
|
|
|
2075
|
-
pub fn
|
|
2076
|
-
|
|
2296
|
+
pub fn safeguard(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2297
|
+
return screens(battle, player, "Safeguard", "safeguard", options);
|
|
2077
2298
|
}
|
|
2078
2299
|
|
|
2079
|
-
pub fn
|
|
2080
|
-
|
|
2300
|
+
pub fn reflect(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2301
|
+
return screens(battle, player, "Reflect", "reflect", options);
|
|
2081
2302
|
}
|
|
2082
2303
|
|
|
2083
|
-
pub fn
|
|
2084
|
-
|
|
2085
|
-
return options.log.fail(battle.active(player), .None);
|
|
2304
|
+
pub fn lightScreen(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2305
|
+
return screens(battle, player, "LightScreen", "light_screen", options);
|
|
2086
2306
|
}
|
|
2087
2307
|
|
|
2088
|
-
|
|
2089
|
-
|
|
2308
|
+
fn screens(
|
|
2309
|
+
battle: anytype,
|
|
2310
|
+
player: Player,
|
|
2311
|
+
comptime key: []const u8,
|
|
2312
|
+
comptime value: []const u8,
|
|
2313
|
+
options: anytype,
|
|
2314
|
+
) !void {
|
|
2315
|
+
var side = battle.side(player);
|
|
2316
|
+
|
|
2317
|
+
if (@field(side.conditions, key)) {
|
|
2318
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
@field(side.conditions, key) = true;
|
|
2322
|
+
@field(side.conditions, value) = 5;
|
|
2323
|
+
try options.log.sidestart(.{ player, @field(protocol.Side, key) });
|
|
2090
2324
|
}
|
|
2091
2325
|
|
|
2092
2326
|
pub fn sketch(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
@@ -2097,34 +2331,26 @@ pub const Effects = struct {
|
|
|
2097
2331
|
|
|
2098
2332
|
if (showdown) {
|
|
2099
2333
|
// yes, actually "Splash". '|nothing' gets blindly mapped to '|-activate||move:Splash'
|
|
2100
|
-
try options.log.activate(battle.active(player), .Splash);
|
|
2334
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
2101
2335
|
} else {
|
|
2102
|
-
try options.log.fail(battle.active(player), .None);
|
|
2336
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2103
2337
|
}
|
|
2104
2338
|
}
|
|
2105
2339
|
|
|
2106
|
-
pub fn skullBash(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2107
|
-
_ = .{ battle, player, state, options }; // TODO
|
|
2108
|
-
}
|
|
2109
|
-
|
|
2110
|
-
pub fn skyAttack(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2111
|
-
_ = .{ battle, player, state, options }; // TODO
|
|
2112
|
-
}
|
|
2113
|
-
|
|
2114
2340
|
pub fn sleep(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2115
2341
|
var foe = battle.foe(player);
|
|
2116
2342
|
var foe_stored = foe.stored();
|
|
2117
2343
|
const foe_ident = battle.active(player.foe());
|
|
2118
2344
|
|
|
2119
2345
|
if (Status.any(foe_stored.status)) {
|
|
2120
|
-
return options.log.fail(
|
|
2346
|
+
return options.log.fail(.{
|
|
2121
2347
|
foe_ident,
|
|
2122
|
-
if (Status.is(foe_stored.status, .SLP)) .Sleep else .None,
|
|
2123
|
-
);
|
|
2348
|
+
if (Status.is(foe_stored.status, .SLP)) Fail.Sleep else Fail.None,
|
|
2349
|
+
});
|
|
2124
2350
|
}
|
|
2125
2351
|
|
|
2126
2352
|
if (foe.active.volatiles.Substitute) {
|
|
2127
|
-
return options.log.
|
|
2353
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
2128
2354
|
}
|
|
2129
2355
|
|
|
2130
2356
|
// Sleep Clause Mod
|
|
@@ -2135,25 +2361,21 @@ pub const Effects = struct {
|
|
|
2135
2361
|
}
|
|
2136
2362
|
|
|
2137
2363
|
foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
|
|
2138
|
-
try options.log.
|
|
2364
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .From, state.move });
|
|
2139
2365
|
}
|
|
2140
2366
|
|
|
2141
2367
|
pub fn sleepTalk(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2142
2368
|
const side = battle.side(player);
|
|
2143
2369
|
|
|
2144
2370
|
if (!Status.is(side.stored().status, .SLP)) {
|
|
2145
|
-
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
2146
|
-
return;
|
|
2371
|
+
return if (!showdown) try options.log.fail(.{ battle.active(player), .None });
|
|
2147
2372
|
}
|
|
2148
2373
|
|
|
2149
2374
|
var n: u3 = 0;
|
|
2150
2375
|
for (side.active.moves, 0..) |m, i| {
|
|
2151
2376
|
if (state.mslot - 1 != i and Move.get(m.id).extra.sleep_talk) n += 1;
|
|
2152
2377
|
}
|
|
2153
|
-
if (n == 0) {
|
|
2154
|
-
try options.log.fail(battle.active(player), .None);
|
|
2155
|
-
return;
|
|
2156
|
-
}
|
|
2378
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
2157
2379
|
|
|
2158
2380
|
_ = try Rolls.sleepTalk(battle, player, n, state.mslot, options); // TODO
|
|
2159
2381
|
}
|
|
@@ -2162,28 +2384,55 @@ pub const Effects = struct {
|
|
|
2162
2384
|
if (Status.is(battle.side(player).stored().status, .SLP)) return true;
|
|
2163
2385
|
state.damage = 0;
|
|
2164
2386
|
state.miss = true; // TODO is this necessary?
|
|
2165
|
-
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
2387
|
+
if (!showdown) try options.log.fail(.{ battle.active(player), .None });
|
|
2166
2388
|
return false;
|
|
2167
2389
|
}
|
|
2168
2390
|
|
|
2169
|
-
pub fn spikes(battle: anytype, player: Player,
|
|
2170
|
-
|
|
2391
|
+
pub fn spikes(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2392
|
+
var foe = battle.foe(player);
|
|
2393
|
+
if (foe.conditions.Spikes) return options.log.fail(.{ battle.active(player), .None });
|
|
2394
|
+
|
|
2395
|
+
foe.conditions.Spikes = true;
|
|
2396
|
+
try options.log.sidestart(.{ player.foe(), protocol.Side.Spikes });
|
|
2171
2397
|
}
|
|
2172
2398
|
|
|
2173
2399
|
pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2174
2400
|
_ = .{ battle, player, state, options }; // TODO
|
|
2401
|
+
// var foe = battle.foe(player);
|
|
2402
|
+
|
|
2403
|
+
// if (state.miss) return options.log.fail(.{ battle.active(player), .None });
|
|
2404
|
+
|
|
2405
|
+
// const last = foe.lastMove(false);
|
|
2406
|
+
// if (showdown) {
|
|
2407
|
+
// const roll = try Rolls.spite(battle, player, options);
|
|
2408
|
+
// } else {
|
|
2409
|
+
// if (last != .None) {}
|
|
2410
|
+
// }
|
|
2175
2411
|
}
|
|
2176
2412
|
|
|
2177
2413
|
pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2178
|
-
try options.log.activate(battle.active(player), .Splash);
|
|
2414
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
2179
2415
|
}
|
|
2180
2416
|
|
|
2181
|
-
pub fn
|
|
2182
|
-
|
|
2183
|
-
|
|
2417
|
+
pub fn substitute(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2418
|
+
var side = battle.side(player);
|
|
2419
|
+
if (side.active.volatiles.Substitute) {
|
|
2420
|
+
return options.log.fail(.{ battle.active(player), .Substitute });
|
|
2421
|
+
}
|
|
2184
2422
|
|
|
2185
|
-
|
|
2186
|
-
|
|
2423
|
+
assert(side.stored().stats.hp <= 1023);
|
|
2424
|
+
const hp: u8 = @intCast(side.stored().stats.hp / 4);
|
|
2425
|
+
|
|
2426
|
+
if (hp == 0 or side.stored().hp <= hp) {
|
|
2427
|
+
return options.log.fail(.{ battle.active(player), .Weak });
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
side.stored().hp -= hp;
|
|
2431
|
+
side.active.volatiles.substitute = hp;
|
|
2432
|
+
side.active.volatiles.Substitute = true;
|
|
2433
|
+
side.active.volatiles.bind = .{};
|
|
2434
|
+
try options.log.start(.{ battle.active(player), .Substitute });
|
|
2435
|
+
try options.log.damage(.{ battle.active(player), side.stored(), .None });
|
|
2187
2436
|
}
|
|
2188
2437
|
|
|
2189
2438
|
pub fn sunnyDay(battle: anytype, _: Player, _: *State, options: anytype) !void {
|
|
@@ -2193,18 +2442,14 @@ pub const Effects = struct {
|
|
|
2193
2442
|
fn weather(battle: anytype, w: Weather, options: anytype) !void {
|
|
2194
2443
|
battle.field.weather = w;
|
|
2195
2444
|
battle.field.weather_duration = 5;
|
|
2196
|
-
try options.log.weather(w, .None);
|
|
2197
|
-
}
|
|
2198
|
-
|
|
2199
|
-
pub fn swagger(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2200
|
-
_ = .{ battle, player, state, options }; // TODO
|
|
2445
|
+
try options.log.weather(.{ w, protocol.Weather.None });
|
|
2201
2446
|
}
|
|
2202
2447
|
|
|
2203
2448
|
pub fn teleport(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2204
2449
|
if (showdown) return;
|
|
2205
2450
|
|
|
2206
|
-
try options.log.fail(battle.active(player), .None);
|
|
2207
|
-
try options.log.laststill();
|
|
2451
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2452
|
+
try options.log.laststill(.{});
|
|
2208
2453
|
}
|
|
2209
2454
|
|
|
2210
2455
|
pub fn thief(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2219,15 +2464,21 @@ pub const Effects = struct {
|
|
|
2219
2464
|
side.stored().item = foe.stored().item;
|
|
2220
2465
|
foe.stored().item = .None;
|
|
2221
2466
|
|
|
2222
|
-
try options.log.item(
|
|
2467
|
+
try options.log.item(.{
|
|
2223
2468
|
battle.active(player),
|
|
2224
2469
|
side.stored().item,
|
|
2225
2470
|
battle.active(player.foe()),
|
|
2226
|
-
);
|
|
2471
|
+
});
|
|
2227
2472
|
}
|
|
2228
2473
|
|
|
2229
|
-
pub fn thrashing(battle: anytype, player: Player,
|
|
2230
|
-
|
|
2474
|
+
pub fn thrashing(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2475
|
+
var side = battle.side(player);
|
|
2476
|
+
if (Status.is(side.stored().status, .SLP)) return;
|
|
2477
|
+
|
|
2478
|
+
assert(!side.active.volatiles.Thrashing);
|
|
2479
|
+
side.active.volatiles.Thrashing = true;
|
|
2480
|
+
side.active.volatiles.attacks = Rolls.attackingDuration(battle, player, options);
|
|
2481
|
+
// TODO wSomeoneIsRampaging = true;
|
|
2231
2482
|
}
|
|
2232
2483
|
|
|
2233
2484
|
pub fn transform(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
@@ -2239,11 +2490,11 @@ pub const Effects = struct {
|
|
|
2239
2490
|
side.active.volatiles.dirty = false;
|
|
2240
2491
|
|
|
2241
2492
|
if (foe.active.volatiles.Transform) {
|
|
2242
|
-
return options.log.fail(battle.active(player), .None);
|
|
2493
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
2243
2494
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2244
|
-
if (!showdown) return options.log.fail(battle.active(player), .None);
|
|
2245
|
-
try options.log.lastmiss();
|
|
2246
|
-
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)});
|
|
2247
2498
|
}
|
|
2248
2499
|
|
|
2249
2500
|
side.active.volatiles.Transform = true;
|
|
@@ -2265,7 +2516,7 @@ pub const Effects = struct {
|
|
|
2265
2516
|
side.active.moves[i].pp = if (m.id != .None) if (m.id == .Sketch) 1 else 5 else 0;
|
|
2266
2517
|
}
|
|
2267
2518
|
|
|
2268
|
-
try options.log.transform(battle.active(player), foe_ident);
|
|
2519
|
+
try options.log.transform(.{ battle.active(player), foe_ident });
|
|
2269
2520
|
}
|
|
2270
2521
|
|
|
2271
2522
|
pub fn triAttack(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2284,8 +2535,7 @@ pub const Effects = struct {
|
|
|
2284
2535
|
_ = .{ battle, player, state, options }; // TODO
|
|
2285
2536
|
}
|
|
2286
2537
|
|
|
2287
|
-
const
|
|
2288
|
-
const ALL_STAT_UP_REASONS =
|
|
2538
|
+
const STAT_UP_REASONS =
|
|
2289
2539
|
[_]Boost{ .Attack, .Defense, .Speed, .SpecialAttack, .SpecialDefense };
|
|
2290
2540
|
|
|
2291
2541
|
pub fn allStatUpChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2295,20 +2545,15 @@ pub const Effects = struct {
|
|
|
2295
2545
|
var boosts = &side.active.boosts;
|
|
2296
2546
|
|
|
2297
2547
|
if (!state.proc) return;
|
|
2298
|
-
inline for (
|
|
2548
|
+
inline for (STATS, STAT_UP_REASONS) |s, r| {
|
|
2299
2549
|
assert(@field(boosts, s) >= -6 and @field(boosts, s) <= 6);
|
|
2300
2550
|
if (@field(boosts, s) == 6 or @field(stats, s) == MAX_STAT_VALUE) {
|
|
2301
|
-
try options.log.boost(battle.active(player), r, 0);
|
|
2551
|
+
try options.log.boost(.{ battle.active(player), r, 0 });
|
|
2302
2552
|
} else {
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2306
|
-
@field(stats, s) = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2307
|
-
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 });
|
|
2308
2555
|
}
|
|
2309
2556
|
}
|
|
2310
|
-
|
|
2311
|
-
statusModify(side.stored().status, stats);
|
|
2312
2557
|
}
|
|
2313
2558
|
|
|
2314
2559
|
pub fn boost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2316,72 +2561,65 @@ pub const Effects = struct {
|
|
|
2316
2561
|
var side = battle.side(player);
|
|
2317
2562
|
const ident = battle.active(player);
|
|
2318
2563
|
|
|
2319
|
-
if (state.miss) return log.fail(ident, .None);
|
|
2564
|
+
if (state.miss) return log.fail(.{ ident, .None });
|
|
2320
2565
|
|
|
2321
2566
|
var stats = &side.active.stats;
|
|
2322
2567
|
var boosts = &side.active.boosts;
|
|
2323
2568
|
|
|
2324
2569
|
const move = Move.get(state.move);
|
|
2325
2570
|
switch (move.effect) {
|
|
2326
|
-
.AttackUp1, .AttackUp2, .Rage => {
|
|
2571
|
+
.AttackUp1, .AttackUp2, .Rage, .Swagger => {
|
|
2327
2572
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2328
|
-
if (boosts.atk == 6 or stats.atk == MAX_STAT_VALUE)
|
|
2329
|
-
|
|
2330
|
-
|
|
2573
|
+
if (boosts.atk == 6 or stats.atk == MAX_STAT_VALUE) {
|
|
2574
|
+
return log.fail(.{ ident, .None });
|
|
2575
|
+
}
|
|
2576
|
+
const n: u2 = if (move.effect == .AttackUp2 or move.effect == .Swagger) 2 else 1;
|
|
2577
|
+
try boostStat(battle, side, stats, boosts, "atk", n);
|
|
2331
2578
|
const reason = if (showdown and move.effect == .Rage) Boost.Rage else Boost.Attack;
|
|
2332
|
-
|
|
2333
|
-
const stat = unmodifiedStats(battle, side).atk;
|
|
2334
|
-
stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2335
|
-
try log.boost(ident, reason, n);
|
|
2579
|
+
try log.boost(.{ ident, reason, n });
|
|
2336
2580
|
},
|
|
2337
|
-
.DefenseUp1, .DefenseUp2 => {
|
|
2581
|
+
.DefenseUp1, .DefenseUp2, .SkullBash => {
|
|
2338
2582
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2339
|
-
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
|
+
}
|
|
2340
2586
|
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
const stat = unmodifiedStats(battle, side).def;
|
|
2344
|
-
stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2345
|
-
try log.boost(ident, .Defense, n);
|
|
2587
|
+
try boostStat(battle, side, stats, boosts, "def", n);
|
|
2588
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2346
2589
|
},
|
|
2347
2590
|
.SpeedUp2 => {
|
|
2348
2591
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2349
|
-
if (boosts.spe == 6 or stats.spe == MAX_STAT_VALUE)
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
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 });
|
|
2355
2597
|
},
|
|
2356
2598
|
.SpAtkUp1 => {
|
|
2357
2599
|
assert(boosts.spa >= -6 and boosts.spa <= 6);
|
|
2358
|
-
if (boosts.spa == 6 or stats.spa == MAX_STAT_VALUE)
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
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 });
|
|
2364
2605
|
},
|
|
2365
2606
|
.SpDefUp2 => {
|
|
2366
2607
|
assert(boosts.spd >= -6 and boosts.spd <= 6);
|
|
2367
|
-
if (boosts.spd == 6 or stats.spd == MAX_STAT_VALUE)
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
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 });
|
|
2373
2613
|
},
|
|
2374
2614
|
.EvasionUp1 => {
|
|
2375
2615
|
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2376
|
-
if (boosts.evasion == 6) return log.fail(ident, .None);
|
|
2616
|
+
if (boosts.evasion == 6) return log.fail(.{ ident, .None });
|
|
2377
2617
|
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2378
|
-
try log.boost(ident, .Evasion, 1);
|
|
2618
|
+
try log.boost(.{ ident, .Evasion, 1 });
|
|
2379
2619
|
if (state.move == .Minimize) side.active.volatiles.minimized = true;
|
|
2380
2620
|
},
|
|
2381
2621
|
else => unreachable,
|
|
2382
2622
|
}
|
|
2383
|
-
|
|
2384
|
-
statusModify(side.stored().status, stats);
|
|
2385
2623
|
}
|
|
2386
2624
|
|
|
2387
2625
|
pub fn unboost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2390,8 +2628,8 @@ pub const Effects = struct {
|
|
|
2390
2628
|
const foe_ident = battle.active(player.foe());
|
|
2391
2629
|
|
|
2392
2630
|
if (foe.active.volatiles.Mist) {
|
|
2393
|
-
try log.activate(foe_ident, .Mist);
|
|
2394
|
-
return log.fail(foe_ident, .None);
|
|
2631
|
+
try log.activate(.{ foe_ident, .Mist });
|
|
2632
|
+
return log.fail(.{ foe_ident, .None });
|
|
2395
2633
|
}
|
|
2396
2634
|
|
|
2397
2635
|
const fail = foe.active.volatiles.Substitute or state.miss or !state.proc;
|
|
@@ -2400,92 +2638,76 @@ pub const Effects = struct {
|
|
|
2400
2638
|
var boosts = &foe.active.boosts;
|
|
2401
2639
|
|
|
2402
2640
|
const move = Move.get(state.move);
|
|
2641
|
+
const none: Fail = .None;
|
|
2403
2642
|
switch (move.effect) {
|
|
2404
2643
|
.AttackDown1, .AttackDown2, .AttackDownChance => {
|
|
2405
2644
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2406
2645
|
if (fail or boosts.atk == -6) {
|
|
2407
|
-
return if (move.effect != .AttackDownChance) try log.fail(foe_ident,
|
|
2646
|
+
return if (move.effect != .AttackDownChance) try log.fail(.{ foe_ident, none });
|
|
2408
2647
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2409
|
-
try options.log.lastmiss();
|
|
2410
|
-
|
|
2411
|
-
return;
|
|
2648
|
+
try options.log.lastmiss(.{});
|
|
2649
|
+
return options.log.miss(.{battle.active(player)});
|
|
2412
2650
|
} else if (stats.atk == 1) {
|
|
2413
|
-
return if (move.effect != .AttackDownChance) try log.fail(foe_ident,
|
|
2651
|
+
return if (move.effect != .AttackDownChance) try log.fail(.{ foe_ident, none });
|
|
2414
2652
|
}
|
|
2415
2653
|
const n: u2 = if (move.effect == .AttackDown2) 2 else 1;
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
const stat = unmodifiedStats(battle, foe).atk;
|
|
2419
|
-
stats.atk = @max(1, stat * mod[0] / mod[1]);
|
|
2420
|
-
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) });
|
|
2421
2656
|
},
|
|
2422
2657
|
.DefenseDown1, .DefenseDown2, .DefenseDownChance => {
|
|
2423
2658
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2424
2659
|
if (fail or boosts.def == -6) {
|
|
2425
|
-
|
|
2426
|
-
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2427
|
-
try options.log.lastmiss();
|
|
2428
|
-
try options.log.miss(battle.active(player));
|
|
2660
|
+
if (move.effect != .DefenseDownChance) try log.fail(.{ foe_ident, none });
|
|
2429
2661
|
return;
|
|
2662
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2663
|
+
try options.log.lastmiss(.{});
|
|
2664
|
+
return options.log.miss(.{battle.active(player)});
|
|
2430
2665
|
} else if (stats.def == 1) {
|
|
2431
|
-
|
|
2666
|
+
if (move.effect != .DefenseDownChance) try log.fail(.{ foe_ident, none });
|
|
2667
|
+
return;
|
|
2432
2668
|
}
|
|
2433
2669
|
const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
const stat = unmodifiedStats(battle, foe).def;
|
|
2437
|
-
stats.def = @max(1, stat * mod[0] / mod[1]);
|
|
2438
|
-
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) });
|
|
2439
2672
|
},
|
|
2440
2673
|
.SpeedDown1, .SpeedDown2, .SpeedDownChance => {
|
|
2441
2674
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2442
2675
|
if (fail or boosts.spe == -6) {
|
|
2443
|
-
return if (move.effect != .SpeedDownChance) try log.fail(foe_ident,
|
|
2676
|
+
return if (move.effect != .SpeedDownChance) try log.fail(.{ foe_ident, none });
|
|
2444
2677
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2445
|
-
try options.log.lastmiss();
|
|
2446
|
-
|
|
2447
|
-
return;
|
|
2678
|
+
try options.log.lastmiss(.{});
|
|
2679
|
+
return options.log.miss(.{battle.active(player)});
|
|
2448
2680
|
} else if (stats.spe == 1) {
|
|
2449
|
-
return if (move.effect != .SpeedDownChance) try log.fail(foe_ident,
|
|
2681
|
+
return if (move.effect != .SpeedDownChance) try log.fail(.{ foe_ident, none });
|
|
2450
2682
|
}
|
|
2451
2683
|
const n: u2 = if (move.effect == .SpeedDown2) 2 else 1;
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
const stat = unmodifiedStats(battle, foe).spe;
|
|
2455
|
-
stats.spe = @max(1, stat * mod[0] / mod[1]);
|
|
2456
|
-
try log.boost(foe_ident, .Speed, -@as(i8, n));
|
|
2457
|
-
assert(boosts.spe >= -6);
|
|
2684
|
+
try unboostStat(battle, foe, stats, boosts, "spe", n);
|
|
2685
|
+
try log.boost(.{ foe_ident, .Speed, -@as(i8, n) });
|
|
2458
2686
|
},
|
|
2459
2687
|
.SpDefDownChance => {
|
|
2460
2688
|
assert(boosts.spd >= -6 and boosts.spd <= 6);
|
|
2461
2689
|
if (fail or boosts.spd == -6) return;
|
|
2462
2690
|
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2463
|
-
try options.log.lastmiss();
|
|
2464
|
-
|
|
2465
|
-
return;
|
|
2691
|
+
try options.log.lastmiss(.{});
|
|
2692
|
+
return options.log.miss(.{battle.active(player)});
|
|
2466
2693
|
} else if (stats.spd == 1) return;
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
const stat = unmodifiedStats(battle, foe).spd;
|
|
2470
|
-
stats.spd = @max(1, stat * mod[0] / mod[1]);
|
|
2471
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2694
|
+
try unboostStat(battle, foe, stats, boosts, "spd", 1);
|
|
2695
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2472
2696
|
},
|
|
2473
2697
|
.AccuracyDown1, .AccuracyDownChance => {
|
|
2474
2698
|
assert(boosts.accuracy >= -6 and boosts.accuracy <= 6);
|
|
2475
2699
|
if (fail or boosts.accuracy == -6) {
|
|
2476
|
-
|
|
2477
|
-
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2478
|
-
try options.log.lastmiss();
|
|
2479
|
-
try options.log.miss(battle.active(player));
|
|
2700
|
+
if (move.effect != .AccuracyDownChance) try log.fail(.{ foe_ident, none });
|
|
2480
2701
|
return;
|
|
2702
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2703
|
+
try options.log.lastmiss(.{});
|
|
2704
|
+
return options.log.miss(.{battle.active(player)});
|
|
2481
2705
|
}
|
|
2482
2706
|
boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
|
|
2483
|
-
try log.boost(foe_ident, .Accuracy, -1);
|
|
2707
|
+
try log.boost(.{ foe_ident, .Accuracy, -1 });
|
|
2484
2708
|
},
|
|
2485
2709
|
else => unreachable,
|
|
2486
2710
|
}
|
|
2487
|
-
|
|
2488
|
-
statusModify(foe.stored().status, stats);
|
|
2489
2711
|
}
|
|
2490
2712
|
};
|
|
2491
2713
|
|
|
@@ -2495,6 +2717,15 @@ fn unmodifiedStats(battle: anytype, side: *Side) *Stats(u16) {
|
|
|
2495
2717
|
return &battle.side(id.player).pokemon[id.id - 1].stats;
|
|
2496
2718
|
}
|
|
2497
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
|
+
|
|
2498
2729
|
fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
2499
2730
|
if (Status.is(status, .PAR)) {
|
|
2500
2731
|
stats.spe = @max(stats.spe / 4, 1);
|
|
@@ -2503,6 +2734,52 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
2503
2734
|
}
|
|
2504
2735
|
}
|
|
2505
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
|
+
|
|
2506
2783
|
fn isForced(active: anytype) bool {
|
|
2507
2784
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
2508
2785
|
active.volatiles.Thrashing or active.volatiles.Charging or
|
|
@@ -2692,7 +2969,7 @@ pub const Rolls = struct {
|
|
|
2692
2969
|
}
|
|
2693
2970
|
|
|
2694
2971
|
fn magnitude(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2695
|
-
const num = if (options.calc.overridden(player, .magnitude)) |val|
|
|
2972
|
+
const num: u8 = if (options.calc.overridden(player, .magnitude)) |val|
|
|
2696
2973
|
val + 3
|
|
2697
2974
|
else if (showdown) num: {
|
|
2698
2975
|
const r = battle.rng.range(u8, 0, 100);
|
|
@@ -2967,7 +3244,7 @@ pub const Rolls = struct {
|
|
|
2967
3244
|
|
|
2968
3245
|
const move: Move = overridden orelse move: {
|
|
2969
3246
|
if (showdown) {
|
|
2970
|
-
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)));
|
|
2971
3248
|
for (moves) |m| {
|
|
2972
3249
|
if (m.id == .None) break;
|
|
2973
3250
|
if (@intFromEnum(m.id) > @intFromEnum(Move.METRONOME[r])) break;
|
|
@@ -3210,7 +3487,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3210
3487
|
}
|
|
3211
3488
|
|
|
3212
3489
|
var slot: u4 = 2;
|
|
3213
|
-
while (!active.volatiles.
|
|
3490
|
+
while (!active.volatiles.Trapped and slot <= 6) : (slot += 1) {
|
|
3214
3491
|
const id = side.order[slot - 1];
|
|
3215
3492
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
3216
3493
|
out[n] = .{ .type = .Switch, .data = slot };
|