@pkmn/engine 0.1.0-dev.9f75c7ae → 0.1.0-dev.a0167a9d
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -3
- package/README.md +44 -19
- package/build/pkg/addon.js +6 -7
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/data.d.ts +4 -4
- package/build/pkg/data.js.map +1 -1
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/index.d.ts +2 -1
- package/build/pkg/index.js.map +1 -1
- package/build/pkg/protocol.js +5 -5
- package/build/pkg/protocol.js.map +1 -1
- package/build/test/display.js +2 -2
- package/build/test/display.js.map +1 -1
- package/build/tools/debug.js +5 -4
- package/build/tools/debug.js.map +1 -1
- package/build.zig +82 -90
- package/package.json +31 -23
- package/src/bin/install-pkmn-engine +26 -18
- package/src/lib/common/data.zig +4 -4
- package/src/lib/common/debug.zig +2 -2
- package/src/lib/common/optional.zig +1 -1
- package/src/lib/common/options.zig +2 -0
- package/src/lib/common/protocol.zig +393 -396
- package/src/lib/common/rational.zig +109 -15
- package/src/lib/common/rng.zig +11 -11
- package/src/lib/gen1/calc.zig +269 -97
- package/src/lib/gen1/chance.zig +126 -160
- package/src/lib/gen1/data/species.zig +1 -1
- package/src/lib/gen1/data.zig +21 -21
- package/src/lib/gen1/helpers.zig +191 -206
- package/src/lib/gen1/mechanics.zig +308 -285
- package/src/lib/gen2/chance.zig +211 -121
- package/src/lib/gen2/data.zig +13 -11
- package/src/lib/gen2/generated.zig +15 -24
- package/src/lib/gen2/helpers.zig +37 -7
- package/src/lib/gen2/mechanics.zig +855 -413
- package/src/lib/{binding/node.zig → node.zig} +9 -12
- package/src/lib/pkmn.zig +27 -12
- package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
- package/src/test/display/pkmn.html.tmpl +5 -5
- package/src/lib/gen1/README.md +0 -894
- package/src/lib/gen2/README.md +0 -330
|
@@ -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 {
|
|
@@ -136,11 +140,11 @@ fn start(battle: anytype, options: anytype) !Result {
|
|
|
136
140
|
const p1 = battle.side(.P1);
|
|
137
141
|
const p2 = battle.side(.P2);
|
|
138
142
|
|
|
139
|
-
|
|
143
|
+
const p1_slot = findFirstAlive(p1);
|
|
140
144
|
assert(!showdown or p1_slot == 1);
|
|
141
145
|
if (p1_slot == 0) return if (findFirstAlive(p2) == 0) Result.Tie else Result.Lose;
|
|
142
146
|
|
|
143
|
-
|
|
147
|
+
const p2_slot = findFirstAlive(p2);
|
|
144
148
|
assert(!showdown or p2_slot == 1);
|
|
145
149
|
if (p2_slot == 0) return Result.Win;
|
|
146
150
|
|
|
@@ -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
|
|
|
@@ -399,14 +403,22 @@ fn executeMove(
|
|
|
399
403
|
|
|
400
404
|
assert(choice.type == .Move);
|
|
401
405
|
var mslot: u4 = @intCast(choice.data);
|
|
406
|
+
_ = &mslot;
|
|
402
407
|
var move = Move.Pound; // TODO
|
|
408
|
+
_ = &move;
|
|
403
409
|
|
|
404
410
|
if (!try beforeMove(battle, player, move, options)) return null;
|
|
405
411
|
|
|
406
412
|
// FIXME: need damage from doTurn
|
|
407
413
|
const m = Move.get(move);
|
|
408
|
-
var state: State =
|
|
409
|
-
.
|
|
414
|
+
var state: State = .{
|
|
415
|
+
.move = move,
|
|
416
|
+
.mslot = mslot,
|
|
417
|
+
.first = first,
|
|
418
|
+
.accuracy = m.accuracy,
|
|
419
|
+
.bp = m.bp,
|
|
420
|
+
.type = m.type,
|
|
421
|
+
};
|
|
410
422
|
try generated.runMove(battle, player, &state, options);
|
|
411
423
|
return null;
|
|
412
424
|
}
|
|
@@ -423,7 +435,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
423
435
|
if (volatiles.Recharging) {
|
|
424
436
|
volatiles.Recharging = false;
|
|
425
437
|
resetCant(volatiles);
|
|
426
|
-
try log.cant(ident, .Recharge);
|
|
438
|
+
try log.cant(.{ ident, .Recharge });
|
|
427
439
|
return false;
|
|
428
440
|
}
|
|
429
441
|
|
|
@@ -442,12 +454,12 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
442
454
|
}
|
|
443
455
|
|
|
444
456
|
if (duration == 0) {
|
|
445
|
-
try log.curestatus(ident, before, .Message);
|
|
457
|
+
try log.curestatus(.{ ident, before, .Message });
|
|
446
458
|
stored.status = 0; // clears EXT if present
|
|
447
459
|
resetCant(volatiles);
|
|
448
460
|
volatiles.Nightmare = false;
|
|
449
461
|
} else {
|
|
450
|
-
try log.cant(ident, .Sleep);
|
|
462
|
+
try log.cant(.{ ident, .Sleep });
|
|
451
463
|
if (move == .Snore or move == .SleepTalk) break :slp;
|
|
452
464
|
resetCant(volatiles);
|
|
453
465
|
return false;
|
|
@@ -456,7 +468,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
456
468
|
|
|
457
469
|
if (Status.is(stored.status, .FRZ) and !(move == .FlameWheel or move == .SacredFire)) {
|
|
458
470
|
resetCant(volatiles);
|
|
459
|
-
try log.cant(ident, .Freeze);
|
|
471
|
+
try log.cant(.{ ident, .Freeze });
|
|
460
472
|
return false;
|
|
461
473
|
}
|
|
462
474
|
|
|
@@ -466,7 +478,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
466
478
|
// if (!showdown) volatiles.Flinch = false;
|
|
467
479
|
volatiles.Flinch = false;
|
|
468
480
|
resetCant(volatiles);
|
|
469
|
-
try log.cant(ident, .Flinch);
|
|
481
|
+
try log.cant(.{ ident, .Flinch });
|
|
470
482
|
return false;
|
|
471
483
|
}
|
|
472
484
|
|
|
@@ -480,7 +492,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
480
492
|
|
|
481
493
|
if (volatiles.disable.duration == 0) {
|
|
482
494
|
volatiles.disable.move = 0;
|
|
483
|
-
try log.end(ident, .Disable);
|
|
495
|
+
try log.end(.{ ident, .Disable });
|
|
484
496
|
}
|
|
485
497
|
}
|
|
486
498
|
|
|
@@ -496,9 +508,9 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
496
508
|
|
|
497
509
|
if (volatiles.confusion == 0) {
|
|
498
510
|
volatiles.Confusion = false;
|
|
499
|
-
try log.end(ident, .Confusion);
|
|
511
|
+
try log.end(.{ ident, .Confusion });
|
|
500
512
|
} else {
|
|
501
|
-
try log.activate(ident, .Confusion);
|
|
513
|
+
try log.activate(.{ ident, .Confusion });
|
|
502
514
|
|
|
503
515
|
if (try Rolls.confused(battle, player, options)) {
|
|
504
516
|
volatiles.BeatUp = false;
|
|
@@ -513,11 +525,11 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
513
525
|
}
|
|
514
526
|
|
|
515
527
|
if (volatiles.Attract) {
|
|
516
|
-
try log.activate(ident, .Attract);
|
|
528
|
+
try log.activate(.{ ident, .Attract });
|
|
517
529
|
|
|
518
530
|
if (try Rolls.attract(battle, player, options)) {
|
|
519
531
|
resetCant(volatiles);
|
|
520
|
-
try log.cant(ident, .Attract);
|
|
532
|
+
try log.cant(.{ ident, .Attract });
|
|
521
533
|
return false;
|
|
522
534
|
}
|
|
523
535
|
}
|
|
@@ -528,14 +540,14 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
528
540
|
if (m != .None and m == move) {
|
|
529
541
|
side.active.volatiles.Charging = false;
|
|
530
542
|
resetCant(volatiles);
|
|
531
|
-
try options.log.
|
|
543
|
+
try options.log.cant(.{ ident, .Disable, move });
|
|
532
544
|
return false;
|
|
533
545
|
}
|
|
534
546
|
}
|
|
535
547
|
|
|
536
548
|
if (Status.is(stored.status, .PAR) and try Rolls.paralyzed(battle, player, options)) {
|
|
537
549
|
resetCant(volatiles);
|
|
538
|
-
try log.cant(ident, .Paralysis);
|
|
550
|
+
try log.cant(.{ ident, .Paralysis });
|
|
539
551
|
return false;
|
|
540
552
|
}
|
|
541
553
|
|
|
@@ -555,11 +567,11 @@ fn resetCant(volatiles: *align(8) Volatiles) void {
|
|
|
555
567
|
|
|
556
568
|
pub fn canMove(battle: anytype, player: Player, state: *State, options: anytype) !bool {
|
|
557
569
|
var side = battle.side(player);
|
|
558
|
-
|
|
570
|
+
const volatiles = &side.active.volatiles;
|
|
559
571
|
|
|
560
572
|
// FIXME: pass in target to avoid lookup?
|
|
561
573
|
const target = if (Move.get(state.move).target == .Self) player else player.foe();
|
|
562
|
-
try options.log.
|
|
574
|
+
try options.log.move(.{ battle.active(player), state.move, battle.active(target), state.from });
|
|
563
575
|
|
|
564
576
|
const charging = false; // TODO charging uses doUse
|
|
565
577
|
const skip_pp = charging or state.move == .Struggle or
|
|
@@ -638,7 +650,7 @@ pub fn afterMove(
|
|
|
638
650
|
|
|
639
651
|
pub fn checkCriticalHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
640
652
|
const move = Move.get(state.move);
|
|
641
|
-
if (
|
|
653
|
+
if (state.bp == 0) return;
|
|
642
654
|
|
|
643
655
|
const side = battle.side(player);
|
|
644
656
|
const pokemon = side.stored();
|
|
@@ -676,31 +688,30 @@ pub fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype)
|
|
|
676
688
|
const side = battle.side(player);
|
|
677
689
|
const foe = battle.foe(player);
|
|
678
690
|
const types = foe.active.types;
|
|
679
|
-
const move = Move.get(state.move);
|
|
680
691
|
|
|
681
692
|
var d = state.damage;
|
|
682
|
-
if ((
|
|
683
|
-
(
|
|
693
|
+
if ((state.type == .Water and battle.field.weather == .Rain) or
|
|
694
|
+
(state.type == .Fire and battle.field.weather == .Sun))
|
|
684
695
|
{
|
|
685
696
|
d = d *% @intFromEnum(Effectiveness.Super) / 10;
|
|
686
|
-
} else if ((
|
|
687
|
-
((
|
|
697
|
+
} else if ((state.type == .Water and battle.field.weather == .Sun) or
|
|
698
|
+
((state.type == .Fire or state.move == .SolarBeam) and battle.field.weather == .Rain))
|
|
688
699
|
{
|
|
689
700
|
d = d *% @intFromEnum(Effectiveness.Resisted) / 10;
|
|
690
701
|
}
|
|
691
702
|
|
|
692
|
-
if (side.active.types.includes(
|
|
703
|
+
if (side.active.types.includes(state.type)) d +%= d / 2;
|
|
693
704
|
|
|
694
705
|
const neutral = @intFromEnum(Effectiveness.Neutral);
|
|
695
|
-
const foresight = foe.active.volatiles.Foresight and FORESIGHT.includes(
|
|
706
|
+
const foresight = foe.active.volatiles.Foresight and FORESIGHT.includes(state.type);
|
|
696
707
|
const eff1: u16 = if (foresight and types.type1 == .Ghost)
|
|
697
708
|
neutral
|
|
698
709
|
else
|
|
699
|
-
@intFromEnum(
|
|
710
|
+
@intFromEnum(state.type.effectiveness(types.type1));
|
|
700
711
|
const eff2: u16 = if (foresight and types.type2 == .Ghost)
|
|
701
712
|
neutral
|
|
702
713
|
else
|
|
703
|
-
@intFromEnum(
|
|
714
|
+
@intFromEnum(state.type.effectiveness(types.type2));
|
|
704
715
|
|
|
705
716
|
// Type effectiveness matchup precedence only matters with (NVE, SE)
|
|
706
717
|
if (!showdown and (eff1 + eff2) == Effectiveness.mismatch and
|
|
@@ -840,13 +851,13 @@ fn checkFaint(
|
|
|
840
851
|
if (foe_fainted) try faint(battle, player.foe(), !more, options);
|
|
841
852
|
|
|
842
853
|
if (player_out and foe_out) {
|
|
843
|
-
try options.log.tie();
|
|
854
|
+
try options.log.tie(.{});
|
|
844
855
|
return Result.Tie;
|
|
845
856
|
} else if (player_out) {
|
|
846
|
-
try options.log.win(player.foe());
|
|
857
|
+
try options.log.win(.{player.foe()});
|
|
847
858
|
return if (player == .P1) Result.Lose else Result.Win;
|
|
848
859
|
} else if (foe_out) {
|
|
849
|
-
try options.log.win(player);
|
|
860
|
+
try options.log.win(.{player});
|
|
850
861
|
return if (player == .P1) Result.Win else Result.Lose;
|
|
851
862
|
}
|
|
852
863
|
|
|
@@ -876,19 +887,19 @@ fn faint(battle: anytype, player: Player, done: bool, options: anytype) !void {
|
|
|
876
887
|
side.stored().stats.spe;
|
|
877
888
|
}
|
|
878
889
|
|
|
879
|
-
try options.log.faint(battle.active(player), done);
|
|
890
|
+
try options.log.faint(.{ battle.active(player), done });
|
|
880
891
|
options.calc.capped(player);
|
|
881
892
|
}
|
|
882
893
|
|
|
883
894
|
// TODO: see if we can move *before* applyDamage and include failuretext logic
|
|
884
895
|
pub fn reportOutcome(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
885
896
|
const foe_ident = battle.active(player.foe());
|
|
886
|
-
if (state.crit) try options.log.crit(foe_ident);
|
|
897
|
+
if (state.crit) try options.log.crit(.{foe_ident});
|
|
887
898
|
if (!state.immune()) {
|
|
888
899
|
if (state.effectiveness > Effectiveness.neutral) {
|
|
889
|
-
try options.log.supereffective(foe_ident);
|
|
900
|
+
try options.log.supereffective(.{foe_ident});
|
|
890
901
|
} else if (state.effectiveness < Effectiveness.neutral) {
|
|
891
|
-
try options.log.resisted(foe_ident);
|
|
902
|
+
try options.log.resisted(.{foe_ident});
|
|
892
903
|
}
|
|
893
904
|
}
|
|
894
905
|
}
|
|
@@ -921,8 +932,8 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !bool {
|
|
|
921
932
|
}
|
|
922
933
|
|
|
923
934
|
stored.hp -|= damage;
|
|
924
|
-
// Pokémon Showdown uses
|
|
925
|
-
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
935
|
+
// Pokémon Showdown uses damage [of] here but its not relevant in Generation II
|
|
936
|
+
try options.log.damage(.{ ident, stored, if (brn) Damage.Burn else Damage.Poison });
|
|
926
937
|
if (stored.hp == 0) return true;
|
|
927
938
|
}
|
|
928
939
|
|
|
@@ -938,27 +949,27 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !bool {
|
|
|
938
949
|
|
|
939
950
|
const damage = @max(stored.stats.hp / 8, 1);
|
|
940
951
|
stored.hp -|= damage;
|
|
941
|
-
// As above, Pokémon Showdown uses
|
|
942
|
-
try options.log.damage(ident, stored, .LeechSeed);
|
|
952
|
+
// As above, Pokémon Showdown uses damage [of] but its not relevant
|
|
953
|
+
try options.log.damage(.{ ident, stored, .LeechSeed });
|
|
943
954
|
|
|
944
955
|
const before = foe_stored.hp;
|
|
945
956
|
foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
|
|
946
957
|
// 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);
|
|
958
|
+
if (foe_stored.hp > before) try options.log.heal(.{ foe_ident, foe_stored, .Silent });
|
|
948
959
|
if (stored.hp == 0) return true;
|
|
949
960
|
}
|
|
950
961
|
|
|
951
962
|
if (volatiles.Nightmare) {
|
|
952
963
|
stored.hp -= @max(stored.stats.hp / 4, 1);
|
|
953
964
|
// ibid
|
|
954
|
-
try options.log.damage(ident, stored, .Nightmare);
|
|
965
|
+
try options.log.damage(.{ ident, stored, .Nightmare });
|
|
955
966
|
if (stored.hp == 0) return true;
|
|
956
967
|
}
|
|
957
968
|
|
|
958
969
|
if (volatiles.Curse) {
|
|
959
970
|
stored.hp -= @max(stored.stats.hp / 4, 1);
|
|
960
971
|
// ibid
|
|
961
|
-
try options.log.damage(ident, stored, .Curse);
|
|
972
|
+
try options.log.damage(.{ ident, stored, .Curse });
|
|
962
973
|
if (stored.hp == 0) return true;
|
|
963
974
|
}
|
|
964
975
|
|
|
@@ -979,7 +990,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
979
990
|
future_sight.count -= 1;
|
|
980
991
|
assert(future_sight.count != 0);
|
|
981
992
|
if (future_sight.count == 1) {
|
|
982
|
-
try options.log.end(battle.active(player), .FutureSight);
|
|
993
|
+
try options.log.end(.{ battle.active(player), .FutureSight });
|
|
983
994
|
// TODO doMove
|
|
984
995
|
}
|
|
985
996
|
}
|
|
@@ -988,21 +999,24 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
988
999
|
|
|
989
1000
|
// Weather
|
|
990
1001
|
if (battle.field.weather != .None) {
|
|
991
|
-
assert(battle.field.weather_duration > 0);
|
|
992
1002
|
battle.field.weather_duration -= 1;
|
|
993
1003
|
|
|
994
1004
|
if (battle.field.weather_duration == 0) {
|
|
995
1005
|
battle.field.weather = .None;
|
|
996
|
-
try options.log.weather(battle.field.weather, .None);
|
|
1006
|
+
try options.log.weather(.{ battle.field.weather, protocol.Weather.None });
|
|
997
1007
|
} else {
|
|
998
|
-
try options.log.weather(battle.field.weather, .Upkeep);
|
|
1008
|
+
try options.log.weather(.{ battle.field.weather, protocol.Weather.Upkeep });
|
|
999
1009
|
if (battle.field.weather == .Sandstorm) {
|
|
1000
1010
|
inline for (players) |player| {
|
|
1001
1011
|
var side = battle.side(player);
|
|
1002
1012
|
const active = side.active;
|
|
1003
1013
|
if (!(active.volatiles.Underground or active.types.sandstormImmune())) {
|
|
1004
1014
|
side.stored().hp -|= @max(side.stored().stats.hp / 8, 1);
|
|
1005
|
-
try options.log.damage(
|
|
1015
|
+
try options.log.damage(.{
|
|
1016
|
+
battle.active(player),
|
|
1017
|
+
side.stored(),
|
|
1018
|
+
.Sandstorm,
|
|
1019
|
+
});
|
|
1006
1020
|
}
|
|
1007
1021
|
}
|
|
1008
1022
|
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
@@ -1014,19 +1028,24 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1014
1028
|
inline for (players) |player| {
|
|
1015
1029
|
var side = battle.side(player);
|
|
1016
1030
|
var volatiles = &side.active.volatiles;
|
|
1031
|
+
const ident = battle.active(player);
|
|
1017
1032
|
|
|
1018
1033
|
if (volatiles.bind.duration > 0 and !volatiles.Substitute) {
|
|
1019
1034
|
volatiles.bind.duration -= 1;
|
|
1020
1035
|
if (volatiles.bind.duration == 0) {
|
|
1021
1036
|
const reason = @intFromEnum(End.Bind) + volatiles.bind.reason - 1;
|
|
1022
|
-
try options.log.end(
|
|
1037
|
+
try options.log.end(.{ ident, @as(End, @enumFromInt(reason)) });
|
|
1023
1038
|
} else {
|
|
1024
1039
|
var reason = @intFromEnum(Activate.Bind) + volatiles.bind.reason - 1;
|
|
1025
|
-
try options.log.activate(
|
|
1040
|
+
try options.log.activate(.{ ident, @as(Activate, @enumFromInt(reason)) });
|
|
1026
1041
|
|
|
1027
1042
|
reason = @intFromEnum(Damage.Bind) + volatiles.bind.reason - 1;
|
|
1028
1043
|
side.stored().hp -|= @max(side.stored().stats.hp / 16, 1);
|
|
1029
|
-
try options.log.damage(
|
|
1044
|
+
try options.log.damage(.{
|
|
1045
|
+
ident,
|
|
1046
|
+
side.stored(),
|
|
1047
|
+
@as(Damage, @enumFromInt(reason)),
|
|
1048
|
+
});
|
|
1030
1049
|
}
|
|
1031
1050
|
}
|
|
1032
1051
|
}
|
|
@@ -1038,12 +1057,11 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1038
1057
|
var volatiles = &side.active.volatiles;
|
|
1039
1058
|
|
|
1040
1059
|
if (volatiles.PerishSong) {
|
|
1041
|
-
assert(volatiles.perish_song > 0);
|
|
1042
1060
|
volatiles.perish_song -= 1;
|
|
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,13 +1078,15 @@ 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
|
|
|
1067
1087
|
// Mystery Berry
|
|
1068
1088
|
inline for (players) |player| {
|
|
1069
|
-
|
|
1089
|
+
const stored = battle.side(player).stored();
|
|
1070
1090
|
|
|
1071
1091
|
if (stored.item == .MysteryBerry) {
|
|
1072
1092
|
// TODO stored and active moves, transform, etc
|
|
@@ -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,29 +1159,29 @@ 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?
|
|
1146
1166
|
// side.active.volatiles.Toxic = false;
|
|
1147
|
-
// side.active.volatiles.
|
|
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
|
}
|
|
@@ -1173,12 +1193,11 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1173
1193
|
var volatiles = &side.active.volatiles;
|
|
1174
1194
|
|
|
1175
1195
|
if (volatiles.Encore) {
|
|
1176
|
-
assert(volatiles.encore > 0);
|
|
1177
1196
|
volatiles.encore -= 1;
|
|
1178
1197
|
|
|
1179
1198
|
if (volatiles.encore == 0 or side.active.move(mslot).pp == 0) {
|
|
1180
1199
|
volatiles.Encore = false;
|
|
1181
|
-
try options.log.end(battle.active(player), .Encore);
|
|
1200
|
+
try options.log.end(.{ battle.active(player), .Encore });
|
|
1182
1201
|
}
|
|
1183
1202
|
}
|
|
1184
1203
|
}
|
|
@@ -1195,12 +1214,11 @@ fn updateSideCondition(
|
|
|
1195
1214
|
) !void {
|
|
1196
1215
|
var side = battle.side(player);
|
|
1197
1216
|
if (@field(side.conditions, key)) {
|
|
1198
|
-
assert(@field(side.conditions, value) > 0);
|
|
1199
1217
|
@field(side.conditions, value) -= 1;
|
|
1200
1218
|
|
|
1201
1219
|
if (@field(side.conditions, value) == 0) {
|
|
1202
1220
|
@field(side.conditions, key) = false;
|
|
1203
|
-
try options.log.sideend(player, @field(protocol.Side, key));
|
|
1221
|
+
try options.log.sideend(.{ player, @field(protocol.Side, key) });
|
|
1204
1222
|
}
|
|
1205
1223
|
}
|
|
1206
1224
|
}
|
|
@@ -1209,13 +1227,13 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1209
1227
|
battle.turn += 1;
|
|
1210
1228
|
|
|
1211
1229
|
if (showdown and battle.turn >= 1000) {
|
|
1212
|
-
try options.log.tie();
|
|
1230
|
+
try options.log.tie(.{});
|
|
1213
1231
|
return Result.Tie;
|
|
1214
1232
|
} else if (battle.turn >= 65535) {
|
|
1215
1233
|
return Result.Error;
|
|
1216
1234
|
}
|
|
1217
1235
|
|
|
1218
|
-
try options.log.turn(battle.turn);
|
|
1236
|
+
try options.log.turn(.{battle.turn});
|
|
1219
1237
|
|
|
1220
1238
|
return Result.Default;
|
|
1221
1239
|
}
|
|
@@ -1231,7 +1249,7 @@ pub fn buildRage(battle: anytype, player: Player, state: *State, options: anytyp
|
|
|
1231
1249
|
}
|
|
1232
1250
|
} else {
|
|
1233
1251
|
foe.active.volatiles.rage +|= 1;
|
|
1234
|
-
try options.log.activate(battle.active(player), .Rage);
|
|
1252
|
+
try options.log.activate(.{ battle.active(player), .Rage });
|
|
1235
1253
|
}
|
|
1236
1254
|
}
|
|
1237
1255
|
}
|
|
@@ -1254,16 +1272,12 @@ pub fn destinyBond(battle: anytype, player: Player, _: *State, options: anytype)
|
|
|
1254
1272
|
|
|
1255
1273
|
if (foe.active.volatiles.DestinyBond) {
|
|
1256
1274
|
battle.side(player).stored().hp = 0;
|
|
1257
|
-
try options.log.activate(battle.active(player.foe()), .DestinyBond);
|
|
1275
|
+
try options.log.activate(.{ battle.active(player.foe()), .DestinyBond });
|
|
1258
1276
|
}
|
|
1259
1277
|
return true;
|
|
1260
1278
|
}
|
|
1261
1279
|
|
|
1262
1280
|
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
1281
|
pub fn attract(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1268
1282
|
const side = battle.side(player);
|
|
1269
1283
|
var foe = battle.foe(player);
|
|
@@ -1271,16 +1285,14 @@ pub const Effects = struct {
|
|
|
1271
1285
|
const gender: u8 = @intFromEnum(side.stored().dvs.gender);
|
|
1272
1286
|
const foe_gender: u8 = @intFromEnum(foe.stored().dvs.gender);
|
|
1273
1287
|
if (gender + foe_gender != 1) {
|
|
1274
|
-
|
|
1275
|
-
return;
|
|
1288
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
1276
1289
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1277
|
-
try options.log.lastmiss();
|
|
1278
|
-
|
|
1279
|
-
return;
|
|
1290
|
+
try options.log.lastmiss(.{});
|
|
1291
|
+
return options.log.miss(.{battle.active(player)});
|
|
1280
1292
|
}
|
|
1281
1293
|
|
|
1282
1294
|
foe.active.volatiles.Attract = true;
|
|
1283
|
-
try options.log.start(battle.active(player.foe()), .Attract);
|
|
1295
|
+
try options.log.start(.{ battle.active(player.foe()), .Attract });
|
|
1284
1296
|
}
|
|
1285
1297
|
|
|
1286
1298
|
pub fn batonPass(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1292,7 +1304,32 @@ pub const Effects = struct {
|
|
|
1292
1304
|
}
|
|
1293
1305
|
|
|
1294
1306
|
pub fn bellyDrum(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1295
|
-
|
|
1307
|
+
assert(!state.miss);
|
|
1308
|
+
var side = battle.side(player);
|
|
1309
|
+
|
|
1310
|
+
const ident = battle.active(player);
|
|
1311
|
+
|
|
1312
|
+
assert(side.active.boosts.atk >= -6 and side.active.boosts.atk <= 6);
|
|
1313
|
+
if (side.active.boosts.atk == 6 or side.active.boosts.atk == MAX_STAT_VALUE) {
|
|
1314
|
+
return options.log.fail(.{ ident, .None });
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
const hp = @max(side.stored().stats.hp / 2, 1);
|
|
1318
|
+
if (side.stored().hp <= hp) {
|
|
1319
|
+
try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
|
|
1320
|
+
try options.log.boost(.{ ident, .Attack, @as(i8, 2), .Silent });
|
|
1321
|
+
return options.log.fail(.{ ident, .None });
|
|
1322
|
+
} else {
|
|
1323
|
+
side.stored().hp -= hp;
|
|
1324
|
+
try options.log.damage(.{ ident, side.stored(), .None });
|
|
1325
|
+
|
|
1326
|
+
const before = side.active.boosts.atk;
|
|
1327
|
+
var i: usize = 0;
|
|
1328
|
+
while (i < 6 and side.active.stats.atk < MAX_STAT_VALUE) : (i += 1) {
|
|
1329
|
+
try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
|
|
1330
|
+
}
|
|
1331
|
+
try options.log.boost(.{ ident, .Attack, @as(i8, side.active.boosts.atk - before) });
|
|
1332
|
+
}
|
|
1296
1333
|
}
|
|
1297
1334
|
|
|
1298
1335
|
pub const bide = struct {
|
|
@@ -1304,7 +1341,6 @@ pub const Effects = struct {
|
|
|
1304
1341
|
|
|
1305
1342
|
if (!volatiles.Bide) return;
|
|
1306
1343
|
|
|
1307
|
-
assert(volatiles.attacks > 0);
|
|
1308
1344
|
// if (options.calc.modify(player, .attacking)) |extend| {
|
|
1309
1345
|
// if (!extend) volatiles.attacks = 0;
|
|
1310
1346
|
// } else {
|
|
@@ -1312,10 +1348,10 @@ pub const Effects = struct {
|
|
|
1312
1348
|
// }
|
|
1313
1349
|
try options.chance.attacking(player, volatiles.attacks);
|
|
1314
1350
|
|
|
1315
|
-
if (volatiles.attacks != 0) return
|
|
1351
|
+
if (volatiles.attacks != 0) return options.log.activate(.{ ident, .Bide });
|
|
1316
1352
|
|
|
1317
1353
|
volatiles.Bide = false;
|
|
1318
|
-
try options.log.end(ident, .Bide);
|
|
1354
|
+
try options.log.end(.{ ident, .Bide });
|
|
1319
1355
|
|
|
1320
1356
|
state.damage = volatiles.bide *% 2;
|
|
1321
1357
|
volatiles.bide = 0;
|
|
@@ -1329,7 +1365,16 @@ pub const Effects = struct {
|
|
|
1329
1365
|
};
|
|
1330
1366
|
|
|
1331
1367
|
pub fn binding(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1332
|
-
|
|
1368
|
+
var foe = battle.foe(player);
|
|
1369
|
+
|
|
1370
|
+
if (state.miss) return;
|
|
1371
|
+
if (foe.active.volatiles.bind.duration > 0) return;
|
|
1372
|
+
if (foe.active.volatiles.Substitute) return;
|
|
1373
|
+
|
|
1374
|
+
foe.active.volatiles.bind = .{
|
|
1375
|
+
.duration = Rolls.bindingDuration(battle, player, options),
|
|
1376
|
+
.reason = Move.get(state.move).extra.protocol,
|
|
1377
|
+
};
|
|
1333
1378
|
}
|
|
1334
1379
|
|
|
1335
1380
|
pub fn burnChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1339,12 +1384,12 @@ pub const Effects = struct {
|
|
|
1339
1384
|
if (foe.active.volatiles.Substitute) return;
|
|
1340
1385
|
if (Status.any(foe_stored.status)) {
|
|
1341
1386
|
if (Status.is(foe_stored.status, .FRZ)) {
|
|
1342
|
-
assert(Move.get(state.move).type == .Fire);
|
|
1343
|
-
try options.log.curestatus(
|
|
1387
|
+
assert(Move.get(state.move).type == .Fire or state.move == .TriAttack);
|
|
1388
|
+
try options.log.curestatus(.{
|
|
1344
1389
|
battle.active(player.foe()),
|
|
1345
1390
|
foe_stored.status,
|
|
1346
1391
|
.Message,
|
|
1347
|
-
);
|
|
1392
|
+
});
|
|
1348
1393
|
foe_stored.status = 0;
|
|
1349
1394
|
}
|
|
1350
1395
|
return;
|
|
@@ -1353,11 +1398,14 @@ pub const Effects = struct {
|
|
|
1353
1398
|
if (foe.conditions.Safeguard) return;
|
|
1354
1399
|
// Nothing is immune to Fire-type, but Ghosts are immune to burnChance called by Tri Attack
|
|
1355
1400
|
if (state.immune()) return;
|
|
1356
|
-
if (foe.active.types.includes(Move.get(state.move).type))
|
|
1401
|
+
if (foe.active.types.includes(Move.get(state.move).type) and state.move != .TriAttack) {
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1357
1404
|
|
|
1358
1405
|
foe_stored.status = Status.init(.BRN);
|
|
1359
1406
|
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
1360
|
-
|
|
1407
|
+
|
|
1408
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1361
1409
|
|
|
1362
1410
|
// TODO: check for status berry
|
|
1363
1411
|
}
|
|
@@ -1377,10 +1425,7 @@ pub const Effects = struct {
|
|
|
1377
1425
|
for (side.active.moves) |m| {
|
|
1378
1426
|
if (convertible(side.active, m.id)) n += 1;
|
|
1379
1427
|
}
|
|
1380
|
-
if (n == 0) {
|
|
1381
|
-
try options.log.fail(battle.active(player), .None);
|
|
1382
|
-
return;
|
|
1383
|
-
}
|
|
1428
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
1384
1429
|
|
|
1385
1430
|
side.active.types.type1 = try Rolls.conversion(battle, player, n, options);
|
|
1386
1431
|
side.active.types.type2 = side.active.types.type1;
|
|
@@ -1390,26 +1435,87 @@ pub const Effects = struct {
|
|
|
1390
1435
|
var side = battle.side(player);
|
|
1391
1436
|
|
|
1392
1437
|
const last = battle.foe(player).lastMove(false);
|
|
1393
|
-
if (last == .None) {
|
|
1394
|
-
try options.log.fail(battle.active(player), .None);
|
|
1395
|
-
return;
|
|
1396
|
-
}
|
|
1438
|
+
if (last == .None) return options.log.fail(.{ battle.active(player), .None });
|
|
1397
1439
|
const move = Move.get(last);
|
|
1398
|
-
if (move.type == .@"???") {
|
|
1399
|
-
try options.log.fail(battle.active(player), .None);
|
|
1400
|
-
return;
|
|
1401
|
-
}
|
|
1440
|
+
if (move.type == .@"???") return options.log.fail(.{ battle.active(player), .None });
|
|
1402
1441
|
|
|
1403
1442
|
side.active.types.type1 = try Rolls.conversion2(battle, player, move.type, options);
|
|
1404
1443
|
side.active.types.type2 = side.active.types.type1;
|
|
1405
1444
|
}
|
|
1406
1445
|
|
|
1407
|
-
pub fn counter(battle: anytype, player: Player, state: *State,
|
|
1408
|
-
|
|
1446
|
+
pub fn counter(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1447
|
+
assert(state.move == .Counter or state.move == .MirrorCoat);
|
|
1448
|
+
|
|
1449
|
+
const foe = battle.foe(player);
|
|
1450
|
+
|
|
1451
|
+
state.miss = true;
|
|
1452
|
+
const last = foe.lastMove(false);
|
|
1453
|
+
|
|
1454
|
+
if (last == .None) return;
|
|
1455
|
+
if (last == state.move) return;
|
|
1456
|
+
|
|
1457
|
+
const immune = state.immune();
|
|
1458
|
+
state.effectiveness = Effectiveness.neutral;
|
|
1459
|
+
if (immune) {
|
|
1460
|
+
state.damage = 0;
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
const move = Move.get(last);
|
|
1465
|
+
if (state.bp == 0) return;
|
|
1466
|
+
if (move.type.special() != (last == .MirrorCoat)) return;
|
|
1467
|
+
if (state.damage == 0) return;
|
|
1468
|
+
|
|
1469
|
+
state.damage *|= 2;
|
|
1470
|
+
state.miss = false;
|
|
1409
1471
|
}
|
|
1410
1472
|
|
|
1411
1473
|
pub fn curse(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1412
|
-
|
|
1474
|
+
var side = battle.side(player);
|
|
1475
|
+
var foe = battle.foe(player);
|
|
1476
|
+
|
|
1477
|
+
const ident = battle.active(player);
|
|
1478
|
+
const foe_ident = battle.active(player.foe());
|
|
1479
|
+
|
|
1480
|
+
if (side.active.types.includes(.Ghost)) {
|
|
1481
|
+
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1482
|
+
try options.log.lastmiss(.{});
|
|
1483
|
+
return options.log.miss(.{battle.active(player)});
|
|
1484
|
+
} else if (foe.active.volatiles.Substitute) {
|
|
1485
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1486
|
+
} else if (foe.active.volatiles.Curse) {
|
|
1487
|
+
return options.log.fail(.{ foe_ident, .None });
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
foe.active.volatiles.Curse = true;
|
|
1491
|
+
try options.log.start(.{ foe_ident, .Curse });
|
|
1492
|
+
|
|
1493
|
+
side.stored().hp -= @max(side.stored().stats.hp / 2, 1);
|
|
1494
|
+
try options.log.damage(.{ ident, side.stored(), .Curse });
|
|
1495
|
+
} else {
|
|
1496
|
+
const stats = &side.active.stats;
|
|
1497
|
+
const boosts = &side.active.boosts;
|
|
1498
|
+
|
|
1499
|
+
const max_atk = boosts.atk == 6 or stats.atk == MAX_STAT_VALUE;
|
|
1500
|
+
const max_def = boosts.def == 6 or stats.def == MAX_STAT_VALUE;
|
|
1501
|
+
if (max_atk and max_def) return options.log.fail(.{ ident, .None });
|
|
1502
|
+
|
|
1503
|
+
if (!(boosts.spe == -6 or stats.spe == 1)) {
|
|
1504
|
+
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
1505
|
+
try unboostStat(battle, side, stats, boosts, "spe", 1);
|
|
1506
|
+
try options.log.boost(.{ ident, .Speed, @as(i8, -1) });
|
|
1507
|
+
}
|
|
1508
|
+
if (!max_atk) {
|
|
1509
|
+
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
1510
|
+
try boostStat(battle, side, stats, boosts, "atk", 1);
|
|
1511
|
+
try options.log.boost(.{ ident, .Attack, @as(i8, 1) });
|
|
1512
|
+
}
|
|
1513
|
+
if (!max_def) {
|
|
1514
|
+
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
1515
|
+
try boostStat(battle, side, stats, boosts, "def", 1);
|
|
1516
|
+
try options.log.boost(.{ ident, .Defense, @as(i8, 1) });
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1413
1519
|
}
|
|
1414
1520
|
|
|
1415
1521
|
pub fn defenseCurl(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
@@ -1419,14 +1525,13 @@ pub const Effects = struct {
|
|
|
1419
1525
|
pub fn defrost(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1420
1526
|
var side = battle.side(player);
|
|
1421
1527
|
assert(Status.is(side.stored().status, .FRZ));
|
|
1422
|
-
|
|
1423
|
-
try options.log.curestatus(battle.active(player), side.stored().status, .Message);
|
|
1528
|
+
try options.log.curestatus(.{ battle.active(player), side.stored().status, .Message });
|
|
1424
1529
|
side.stored().status = 0;
|
|
1425
1530
|
}
|
|
1426
1531
|
|
|
1427
1532
|
pub fn destinyBond(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1428
1533
|
battle.side(player).active.volatiles.DestinyBond = true;
|
|
1429
|
-
try options.log.singlemove(battle.active(player), Move.DestinyBond);
|
|
1534
|
+
try options.log.singlemove(.{ battle.active(player), Move.DestinyBond });
|
|
1430
1535
|
}
|
|
1431
1536
|
|
|
1432
1537
|
pub fn disable(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
@@ -1434,16 +1539,10 @@ pub const Effects = struct {
|
|
|
1434
1539
|
var volatiles = &foe.active.volatiles;
|
|
1435
1540
|
const foe_ident = battle.active(player.foe());
|
|
1436
1541
|
|
|
1437
|
-
if (volatiles.disable.move != 0) {
|
|
1438
|
-
try options.log.fail(foe_ident, .None);
|
|
1439
|
-
return;
|
|
1440
|
-
}
|
|
1542
|
+
if (volatiles.disable.move != 0) return options.log.fail(.{ foe_ident, .None });
|
|
1441
1543
|
|
|
1442
1544
|
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
|
-
}
|
|
1545
|
+
if (last == .None or last == .Struggle) return options.log.fail(.{ foe_ident, .None });
|
|
1447
1546
|
|
|
1448
1547
|
var slot: u3 = 0;
|
|
1449
1548
|
for (foe.active.moves) |m| {
|
|
@@ -1452,15 +1551,12 @@ pub const Effects = struct {
|
|
|
1452
1551
|
if (m.id == last) break;
|
|
1453
1552
|
}
|
|
1454
1553
|
const move = foe.active.move(slot);
|
|
1455
|
-
if (move.pp == 0) {
|
|
1456
|
-
try options.log.fail(foe_ident, .None);
|
|
1457
|
-
return;
|
|
1458
|
-
}
|
|
1554
|
+
if (move.pp == 0) return options.log.fail(.{ foe_ident, .None });
|
|
1459
1555
|
|
|
1460
1556
|
volatiles.disable.move = slot;
|
|
1461
1557
|
volatiles.disable.duration = Rolls.disableDuration(battle, player, options);
|
|
1462
1558
|
|
|
1463
|
-
try options.log.
|
|
1559
|
+
try options.log.start(.{ foe_ident, .Disable, move.id });
|
|
1464
1560
|
}
|
|
1465
1561
|
|
|
1466
1562
|
pub const double = struct {
|
|
@@ -1479,13 +1575,14 @@ pub const Effects = struct {
|
|
|
1479
1575
|
|
|
1480
1576
|
pub fn drainHP(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1481
1577
|
var stored = battle.side(player).stored();
|
|
1578
|
+
const ident = battle.active(player);
|
|
1482
1579
|
|
|
1483
1580
|
assert(state.damage != 0);
|
|
1484
1581
|
|
|
1485
1582
|
if (stored.hp == stored.stats.hp) return;
|
|
1486
1583
|
stored.hp = @min(stored.stats.hp, stored.hp + @max(state.damage / 2, 1));
|
|
1487
1584
|
|
|
1488
|
-
try options.log.
|
|
1585
|
+
try options.log.heal(.{ ident, stored, .Drain, battle.active(player.foe()) });
|
|
1489
1586
|
}
|
|
1490
1587
|
|
|
1491
1588
|
pub fn encore(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1497,10 +1594,7 @@ pub const Effects = struct {
|
|
|
1497
1594
|
const failed = volatiles.Encore or
|
|
1498
1595
|
(last == .None or last == .Struggle or
|
|
1499
1596
|
last == .Encore or .last == .MirrorMove);
|
|
1500
|
-
if (failed) {
|
|
1501
|
-
try options.log.fail(foe_ident, .None);
|
|
1502
|
-
return;
|
|
1503
|
-
}
|
|
1597
|
+
if (failed) return options.log.fail(.{ foe_ident, .None });
|
|
1504
1598
|
|
|
1505
1599
|
var slot: u3 = 0;
|
|
1506
1600
|
for (foe.active.moves) |m| {
|
|
@@ -1509,10 +1603,7 @@ pub const Effects = struct {
|
|
|
1509
1603
|
if (m.id == last) break;
|
|
1510
1604
|
}
|
|
1511
1605
|
const move = foe.active.move(slot);
|
|
1512
|
-
if (move.pp == 0) {
|
|
1513
|
-
try options.log.fail(foe_ident, .None);
|
|
1514
|
-
return;
|
|
1515
|
-
}
|
|
1606
|
+
if (move.pp == 0) return options.log.fail(.{ foe_ident, .None });
|
|
1516
1607
|
|
|
1517
1608
|
volatiles.Encore = true;
|
|
1518
1609
|
volatiles.encore = Rolls.encoreDuration(battle, player, options);
|
|
@@ -1520,11 +1611,19 @@ pub const Effects = struct {
|
|
|
1520
1611
|
// TODO ???
|
|
1521
1612
|
_ = state;
|
|
1522
1613
|
|
|
1523
|
-
try options.log.
|
|
1614
|
+
try options.log.start(.{ foe_ident, .Encore, move.id });
|
|
1524
1615
|
}
|
|
1525
1616
|
|
|
1526
|
-
pub fn explode(battle: anytype, player: Player,
|
|
1527
|
-
|
|
1617
|
+
pub fn explode(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
1618
|
+
var side = battle.side(player);
|
|
1619
|
+
var stored = side.stored();
|
|
1620
|
+
|
|
1621
|
+
stored.hp = 0;
|
|
1622
|
+
// Pokémon Showdown sets the status to 0 on faint(), and we need to do the same to be able
|
|
1623
|
+
// to correctly implement Pokémon Showdown's dumb speed-based switch mechanics
|
|
1624
|
+
if (!showdown) stored.status = 0;
|
|
1625
|
+
side.active.volatiles.LeechSeed = false;
|
|
1626
|
+
side.active.volatiles.DestinyBond = false;
|
|
1528
1627
|
}
|
|
1529
1628
|
|
|
1530
1629
|
pub fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
@@ -1552,10 +1651,6 @@ pub const Effects = struct {
|
|
|
1552
1651
|
};
|
|
1553
1652
|
}
|
|
1554
1653
|
|
|
1555
|
-
pub fn flameWheel(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1556
|
-
_ = .{ battle, player, state, options }; // TODO
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
1654
|
pub fn flinchChance(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1560
1655
|
const foe = battle.foe(player);
|
|
1561
1656
|
const status = foe.stored().status;
|
|
@@ -1578,12 +1673,11 @@ pub const Effects = struct {
|
|
|
1578
1673
|
var side = battle.side(player);
|
|
1579
1674
|
|
|
1580
1675
|
if (side.active.volatiles.FocusEnergy) {
|
|
1581
|
-
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
1582
|
-
return;
|
|
1676
|
+
return if (!showdown) try options.log.fail(.{ battle.active(player), .None });
|
|
1583
1677
|
}
|
|
1584
1678
|
|
|
1585
1679
|
side.active.volatiles.FocusEnergy = true;
|
|
1586
|
-
try options.log.start(battle.active(player), .FocusEnergy);
|
|
1680
|
+
try options.log.start(.{ battle.active(player), .FocusEnergy });
|
|
1587
1681
|
}
|
|
1588
1682
|
|
|
1589
1683
|
var SLOTS: [5]u4 = .{ 0, 0, 0, 0, 0 };
|
|
@@ -1593,7 +1687,7 @@ pub const Effects = struct {
|
|
|
1593
1687
|
|
|
1594
1688
|
const foe = battle.foe(player);
|
|
1595
1689
|
|
|
1596
|
-
if (state.first) return
|
|
1690
|
+
if (state.first) return options.log.fail(.{ battle.active(player), .None });
|
|
1597
1691
|
|
|
1598
1692
|
var n: u4 = 0;
|
|
1599
1693
|
var i: u4 = 0;
|
|
@@ -1607,10 +1701,7 @@ pub const Effects = struct {
|
|
|
1607
1701
|
}
|
|
1608
1702
|
}
|
|
1609
1703
|
|
|
1610
|
-
if (n == 0) {
|
|
1611
|
-
try options.log.fail(battle.active(player), .None);
|
|
1612
|
-
return;
|
|
1613
|
-
}
|
|
1704
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
1614
1705
|
|
|
1615
1706
|
const slot = try Rolls.forceSwitch(battle, player, SLOTS[0..i], n, options);
|
|
1616
1707
|
try switchIn(battle, player.foe(), slot, .Drag, options);
|
|
@@ -1620,16 +1711,14 @@ pub const Effects = struct {
|
|
|
1620
1711
|
var foe = battle.foe(player);
|
|
1621
1712
|
|
|
1622
1713
|
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1623
|
-
try options.log.lastmiss();
|
|
1624
|
-
|
|
1625
|
-
return;
|
|
1714
|
+
try options.log.lastmiss(.{});
|
|
1715
|
+
return options.log.miss(.{battle.active(player)});
|
|
1626
1716
|
} else if (foe.active.volatiles.Foresight) {
|
|
1627
|
-
|
|
1628
|
-
return;
|
|
1717
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
1629
1718
|
}
|
|
1630
1719
|
|
|
1631
1720
|
foe.active.volatiles.Foresight = true;
|
|
1632
|
-
try options.log.start(battle.active(player.foe()), .FocusEnergy);
|
|
1721
|
+
try options.log.start(.{ battle.active(player.foe()), .FocusEnergy });
|
|
1633
1722
|
}
|
|
1634
1723
|
|
|
1635
1724
|
pub fn freezeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1641,15 +1730,18 @@ pub const Effects = struct {
|
|
|
1641
1730
|
if (Status.any(foe_stored.status)) return;
|
|
1642
1731
|
|
|
1643
1732
|
// Nothing is immune to Ice-type, but Ghosts are immune to freezeChance called by Tri Attack
|
|
1733
|
+
if (state.immune()) return;
|
|
1644
1734
|
if (battle.field.weather == .Sun) return;
|
|
1645
|
-
if (foe.active.types.includes(Move.get(state.move).type))
|
|
1735
|
+
if (foe.active.types.includes(Move.get(state.move).type) and state.move != .TriAttack) {
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1646
1738
|
if (!state.proc) return;
|
|
1647
1739
|
if (foe.conditions.Safeguard) return;
|
|
1648
1740
|
// Freeze Clause Mod
|
|
1649
1741
|
if (showdown) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
|
|
1650
1742
|
|
|
1651
1743
|
foe_stored.status = Status.init(.FRZ);
|
|
1652
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
1744
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
1653
1745
|
|
|
1654
1746
|
// TODO: check for status berry
|
|
1655
1747
|
|
|
@@ -1657,8 +1749,26 @@ pub const Effects = struct {
|
|
|
1657
1749
|
foe.active.volatiles.frozen = true;
|
|
1658
1750
|
}
|
|
1659
1751
|
|
|
1660
|
-
|
|
1661
|
-
|
|
1752
|
+
// TODO FIXME showdown calls this before calcDamage (after checkHit) and modifies state.bp!
|
|
1753
|
+
pub fn furyCutter(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1754
|
+
var side = battle.side(player);
|
|
1755
|
+
var volatiles = &side.active.volatiles;
|
|
1756
|
+
|
|
1757
|
+
if (state.miss) {
|
|
1758
|
+
volatiles.fury_cutter = 0;
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
assert(volatiles.fury_cutter >= 0 and volatiles.fury_cutter <= 5);
|
|
1763
|
+
if (volatiles.fury_cutter == 5) return;
|
|
1764
|
+
volatiles.fury_cutter += 1;
|
|
1765
|
+
|
|
1766
|
+
if (showdown) {
|
|
1767
|
+
state.bp *= 2;
|
|
1768
|
+
assert(state.bp <= 160);
|
|
1769
|
+
} else {
|
|
1770
|
+
state.damage *|= 2;
|
|
1771
|
+
}
|
|
1662
1772
|
}
|
|
1663
1773
|
|
|
1664
1774
|
pub fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1671,73 +1781,222 @@ pub const Effects = struct {
|
|
|
1671
1781
|
state.bp = ((if (frustration) 255 - val else val) * 10) / 25;
|
|
1672
1782
|
}
|
|
1673
1783
|
|
|
1674
|
-
pub fn haze(battle: anytype, player: Player,
|
|
1675
|
-
|
|
1784
|
+
pub fn haze(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1785
|
+
var side = battle.side(player);
|
|
1786
|
+
var stats = unmodifiedStats(battle, side);
|
|
1787
|
+
side.active.boosts = .{};
|
|
1788
|
+
inline for (STATS) |s| @field(side.active.stats, s) = @field(stats, s);
|
|
1789
|
+
statusModify(side.stored().status, &side.active.stats);
|
|
1790
|
+
|
|
1791
|
+
var foe = battle.foe(player);
|
|
1792
|
+
stats = unmodifiedStats(battle, foe);
|
|
1793
|
+
foe.active.boosts = .{};
|
|
1794
|
+
inline for (STATS) |s| @field(foe.active.stats, s) = @field(stats, s);
|
|
1795
|
+
statusModify(foe.stored().status, &foe.active.stats);
|
|
1796
|
+
|
|
1797
|
+
try options.log.clearallboost(.{});
|
|
1676
1798
|
}
|
|
1677
1799
|
|
|
1678
1800
|
pub fn heal(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1679
|
-
|
|
1680
|
-
|
|
1801
|
+
var side = battle.side(player);
|
|
1802
|
+
var stored = side.stored();
|
|
1803
|
+
const ident = battle.active(player);
|
|
1681
1804
|
|
|
1682
|
-
|
|
1683
|
-
|
|
1805
|
+
if (stored.stats.hp == stored.hp) return options.log.fail(.{ ident, .None });
|
|
1806
|
+
|
|
1807
|
+
const rest = state.move == .Rest;
|
|
1808
|
+
if (rest) {
|
|
1809
|
+
// Adding the sleep status runs the sleep condition handler to roll duration
|
|
1810
|
+
if (showdown) battle.rng.advance(1);
|
|
1811
|
+
side.active.volatiles.Toxic = false;
|
|
1812
|
+
stored.status = Status.slf(2);
|
|
1813
|
+
recomputeStats(battle, side);
|
|
1814
|
+
try options.log.status(.{ ident, stored.status, .From, Move.Rest });
|
|
1815
|
+
stored.hp = stored.stats.hp;
|
|
1816
|
+
} else {
|
|
1817
|
+
stored.hp = @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2));
|
|
1818
|
+
}
|
|
1819
|
+
try options.log.heal(.{ ident, stored, if (rest) Heal.Silent else Heal.None });
|
|
1684
1820
|
}
|
|
1685
1821
|
|
|
1686
|
-
pub fn
|
|
1687
|
-
|
|
1822
|
+
pub fn healBell(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1823
|
+
var side = battle.side(player);
|
|
1824
|
+
side.active.volatiles.Nightmare = false;
|
|
1825
|
+
|
|
1826
|
+
try options.log.cureteam(.{battle.active(player)});
|
|
1827
|
+
for (&side.pokemon) |*p| p.status = 0;
|
|
1828
|
+
|
|
1829
|
+
// We technically only need to recompute stats if the active Pokémon was burned/paralyzed,
|
|
1830
|
+
// but adding a branch to check for that is probably more expensive than just recomputing
|
|
1831
|
+
recomputeStats(battle, side);
|
|
1688
1832
|
}
|
|
1689
1833
|
|
|
1690
|
-
pub fn
|
|
1691
|
-
|
|
1834
|
+
pub fn hiddenPower(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1835
|
+
const side = battle.side(player);
|
|
1836
|
+
state.bp = side.stored().dvs.power();
|
|
1837
|
+
state.type = side.stored().dvs.type;
|
|
1692
1838
|
}
|
|
1693
1839
|
|
|
1694
|
-
pub fn
|
|
1695
|
-
|
|
1840
|
+
pub fn hyperBeam(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1841
|
+
battle.side(player).active.volatiles.Recharging = true;
|
|
1842
|
+
try options.log.mustrecharge(.{battle.active(player)});
|
|
1696
1843
|
}
|
|
1697
1844
|
|
|
1698
1845
|
pub fn leechSeed(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1699
|
-
|
|
1846
|
+
var foe = battle.foe(player);
|
|
1847
|
+
|
|
1848
|
+
if (showdown) {
|
|
1849
|
+
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
1850
|
+
const invulnerable = foe.active.volatiles.Underground or foe.active.volatiles.Flying;
|
|
1851
|
+
if (!invulnerable and foe.active.types.includes(.Grass)) {
|
|
1852
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
1853
|
+
}
|
|
1854
|
+
try checkHit(battle, player, state, options);
|
|
1855
|
+
if (state.miss) {
|
|
1856
|
+
try options.log.lastmiss(.{});
|
|
1857
|
+
return options.log.miss(.{battle.active(player)});
|
|
1858
|
+
}
|
|
1859
|
+
if (foe.active.volatiles.LeechSeed) return;
|
|
1860
|
+
} else {
|
|
1861
|
+
try checkHit(battle, player, state, options);
|
|
1862
|
+
const miss =
|
|
1863
|
+
state.miss or foe.active.types.includes(.Grass) or foe.active.volatiles.LeechSeed;
|
|
1864
|
+
if (miss) {
|
|
1865
|
+
try options.log.lastmiss(.{});
|
|
1866
|
+
return options.log.miss(.{battle.active(player)});
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
foe.active.volatiles.LeechSeed = true;
|
|
1871
|
+
|
|
1872
|
+
try options.log.start(.{ battle.active(player.foe()), .LeechSeed });
|
|
1700
1873
|
}
|
|
1701
1874
|
|
|
1702
1875
|
pub fn lockOn(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1703
|
-
|
|
1876
|
+
var foe = battle.foe(player);
|
|
1877
|
+
const foe_ident = battle.active(player.foe());
|
|
1878
|
+
|
|
1879
|
+
if (foe.active.volatiles.Substitute) {
|
|
1880
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1881
|
+
} else if (state.miss) {
|
|
1882
|
+
return options.log.fail(.{ foe_ident, .None });
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
foe.active.volatiles.LockOn = true;
|
|
1886
|
+
try options.log.activate(.{ battle.active(player), .LockOn });
|
|
1704
1887
|
}
|
|
1705
1888
|
|
|
1706
1889
|
const MAGNITUDE_POWER = [_]u8{ 10, 30, 50, 70, 90, 110, 150 };
|
|
1707
1890
|
|
|
1708
1891
|
pub fn magnitude(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1709
|
-
|
|
1892
|
+
const num = try Rolls.magnitude(battle, player, options);
|
|
1893
|
+
try options.log.activate(.{ battle.active(player), .Magnitude, num });
|
|
1894
|
+
state.bp = MAGNITUDE_POWER[num - 4];
|
|
1710
1895
|
}
|
|
1711
1896
|
|
|
1712
|
-
pub fn meanLook(battle: anytype, player: Player,
|
|
1713
|
-
|
|
1897
|
+
pub fn meanLook(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1898
|
+
var foe = battle.foe(player);
|
|
1899
|
+
|
|
1900
|
+
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1901
|
+
if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1902
|
+
try options.log.lastmiss(.{});
|
|
1903
|
+
return options.log.miss(.{battle.active(player)});
|
|
1904
|
+
} else if (foe.active.volatiles.Trapped) {
|
|
1905
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
foe.active.volatiles.Trapped = true;
|
|
1714
1909
|
}
|
|
1715
1910
|
|
|
1716
|
-
pub fn metronome(
|
|
1717
|
-
|
|
1911
|
+
pub fn metronome(
|
|
1912
|
+
battle: anytype,
|
|
1913
|
+
player: Player,
|
|
1914
|
+
state: *State,
|
|
1915
|
+
options: anytype,
|
|
1916
|
+
) anyerror!void {
|
|
1917
|
+
var side = battle.side(player);
|
|
1918
|
+
|
|
1919
|
+
side.last_used_move = .None;
|
|
1920
|
+
side.active.volatiles.dirty = false;
|
|
1921
|
+
|
|
1922
|
+
// if (wPlayerCharging) ???
|
|
1923
|
+
// wPlayerCharging = 1
|
|
1924
|
+
|
|
1925
|
+
// FIXME
|
|
1926
|
+
state.move = try Rolls.metronome(battle, player, options);
|
|
1927
|
+
state.from = .Metronome;
|
|
1928
|
+
return generated.runMove(battle, player, state, options);
|
|
1718
1929
|
}
|
|
1719
1930
|
|
|
1720
1931
|
pub fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1721
|
-
|
|
1722
|
-
}
|
|
1932
|
+
var side = battle.side(player);
|
|
1723
1933
|
|
|
1724
|
-
|
|
1725
|
-
|
|
1934
|
+
if (state.miss) {
|
|
1935
|
+
if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1936
|
+
try options.log.lastmiss(.{});
|
|
1937
|
+
return options.log.miss(.{battle.active(player)});
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
const move = battle.foe(player).lastMove(false);
|
|
1941
|
+
if (move == .None or move == .Struggle) {
|
|
1942
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
var slot: u4 = 0;
|
|
1946
|
+
for (side.active.moves, 0..) |m, i| {
|
|
1947
|
+
if (m.id == move) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1948
|
+
if (m.id == .Mimic) slot = @intCast(i + 1);
|
|
1949
|
+
}
|
|
1950
|
+
assert(slot != 0 and slot == state.mslot);
|
|
1951
|
+
|
|
1952
|
+
side.active.move(slot).id = move;
|
|
1953
|
+
side.active.move(slot).pp = 5;
|
|
1954
|
+
try options.log.start(.{ battle.active(player), .Mimic, move });
|
|
1726
1955
|
}
|
|
1727
1956
|
|
|
1728
|
-
pub fn mirrorMove(
|
|
1729
|
-
|
|
1957
|
+
pub fn mirrorMove(
|
|
1958
|
+
battle: anytype,
|
|
1959
|
+
player: Player,
|
|
1960
|
+
state: *State,
|
|
1961
|
+
options: anytype,
|
|
1962
|
+
) anyerror!void {
|
|
1963
|
+
var side = battle.side(player);
|
|
1964
|
+
const foe = battle.foe(player);
|
|
1965
|
+
|
|
1966
|
+
side.last_used_move = .None;
|
|
1967
|
+
side.active.volatiles.dirty = false;
|
|
1968
|
+
|
|
1969
|
+
const last = foe.lastMove(false);
|
|
1970
|
+
|
|
1971
|
+
if (last == .None) return options.log.fail(.{ battle.active(player), .None });
|
|
1972
|
+
const knows = knows: for (side.active.moves) |m| {
|
|
1973
|
+
if (last == m.id) break :knows true;
|
|
1974
|
+
} else false;
|
|
1975
|
+
if (knows) return options.log.fail(.{ battle.active(player), .None });
|
|
1976
|
+
|
|
1977
|
+
// if (wPlayerCharging) ???
|
|
1978
|
+
// wPlayerCharging = 1
|
|
1979
|
+
|
|
1980
|
+
// FIXME
|
|
1981
|
+
state.move = last;
|
|
1982
|
+
state.from = .MirrorMove;
|
|
1983
|
+
return generated.runMove(battle, player, state, options);
|
|
1730
1984
|
}
|
|
1731
1985
|
|
|
1732
|
-
pub fn mist(battle: anytype, player: Player,
|
|
1733
|
-
|
|
1986
|
+
pub fn mist(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1987
|
+
var side = battle.side(player);
|
|
1988
|
+
|
|
1989
|
+
if (side.active.volatiles.Mist) return options.log.fail(.{ battle.active(player), .None });
|
|
1990
|
+
side.active.volatiles.Mist = true;
|
|
1991
|
+
|
|
1992
|
+
try options.log.start(.{ battle.active(player), .Mist });
|
|
1734
1993
|
}
|
|
1735
1994
|
|
|
1736
1995
|
pub fn weatherHeal(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1737
1996
|
var stored = battle.side(player).stored();
|
|
1738
1997
|
const ident = battle.active(player);
|
|
1739
1998
|
|
|
1740
|
-
if (stored.hp == stored.stats.hp) return
|
|
1999
|
+
if (stored.hp == stored.stats.hp) return options.log.fail(.{ ident, .None });
|
|
1741
2000
|
|
|
1742
2001
|
stored.hp = switch (battle.field.weather) {
|
|
1743
2002
|
.Sun => stored.stats.hp,
|
|
@@ -1745,7 +2004,7 @@ pub const Effects = struct {
|
|
|
1745
2004
|
else => @min(stored.stats.hp, stored.hp + (stored.stats.hp / 4)),
|
|
1746
2005
|
};
|
|
1747
2006
|
|
|
1748
|
-
try options.log.heal(ident, stored,
|
|
2007
|
+
try options.log.heal(.{ ident, stored, .None });
|
|
1749
2008
|
}
|
|
1750
2009
|
|
|
1751
2010
|
pub fn multiHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1753,7 +2012,22 @@ pub const Effects = struct {
|
|
|
1753
2012
|
}
|
|
1754
2013
|
|
|
1755
2014
|
pub fn nightmare(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1756
|
-
|
|
2015
|
+
var foe = battle.foe(player);
|
|
2016
|
+
var foe_volatiles = &foe.active.volatiles;
|
|
2017
|
+
const foe_ident = battle.active(player.foe());
|
|
2018
|
+
|
|
2019
|
+
if (foe_volatiles.Flying or foe_volatiles.Underground) {
|
|
2020
|
+
if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2021
|
+
try options.log.lastmiss(.{});
|
|
2022
|
+
return options.log.miss(.{battle.active(player)});
|
|
2023
|
+
} else if (foe_volatiles.Substitute) {
|
|
2024
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
2025
|
+
} else if (!Status.is(foe.stored().status, .SLP) or foe_volatiles.Nightmare) {
|
|
2026
|
+
return options.log.fail(.{ foe_ident, .None });
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
foe_volatiles.Nightmare = true;
|
|
2030
|
+
try options.log.start(.{ battle.active(player.foe()), .Nightmare });
|
|
1757
2031
|
}
|
|
1758
2032
|
|
|
1759
2033
|
pub const ohko = struct {
|
|
@@ -1777,9 +2051,9 @@ pub const Effects = struct {
|
|
|
1777
2051
|
const foe_ident = battle.active(player.foe());
|
|
1778
2052
|
|
|
1779
2053
|
state.damage = 0;
|
|
1780
|
-
if (state.immune()) return
|
|
2054
|
+
if (state.immune()) return options.log.immune(.{ foe_ident, .None });
|
|
1781
2055
|
const delta = side.stored().level -| foe.stored().level;
|
|
1782
|
-
if (delta == 0) return
|
|
2056
|
+
if (delta == 0) return options.log.immune(.{ foe_ident, .OHKO });
|
|
1783
2057
|
|
|
1784
2058
|
state.accuracy = 2 * delta +| Gen12.percent(30);
|
|
1785
2059
|
try checkHit(battle, player, state, options);
|
|
@@ -1789,11 +2063,45 @@ pub const Effects = struct {
|
|
|
1789
2063
|
};
|
|
1790
2064
|
|
|
1791
2065
|
pub fn painSplit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1792
|
-
|
|
2066
|
+
var side = battle.side(player);
|
|
2067
|
+
var foe = battle.foe(player);
|
|
2068
|
+
|
|
2069
|
+
const ident = battle.active(player);
|
|
2070
|
+
const foe_ident = battle.active(player.foe());
|
|
2071
|
+
|
|
2072
|
+
if (state.miss) return options.log.fail(.{ foe_ident, .None });
|
|
2073
|
+
if (foe.active.volatiles.Substitute) {
|
|
2074
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
const avg = @max((foe.stored().hp + side.stored().hp) / 2, 1);
|
|
2078
|
+
foe.stored().hp -= avg;
|
|
2079
|
+
side.stored().hp = avg;
|
|
2080
|
+
|
|
2081
|
+
try options.log.sethp(.{ foe_ident, foe.stored(), .Silent });
|
|
2082
|
+
try options.log.sethp(.{ ident, side.stored(), .None });
|
|
1793
2083
|
}
|
|
1794
2084
|
|
|
1795
2085
|
pub fn paralyze(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1796
|
-
|
|
2086
|
+
var foe = battle.foe(player);
|
|
2087
|
+
const foe_ident = battle.active(player.foe());
|
|
2088
|
+
|
|
2089
|
+
if (Status.is(foe.stored().status, .PAR)) {
|
|
2090
|
+
return options.log.fail(.{ foe_ident, .Paralysis });
|
|
2091
|
+
}
|
|
2092
|
+
if (state.immune()) return options.log.immune(.{ foe_ident, .None });
|
|
2093
|
+
if (Status.any(foe.stored().status)) return options.log.fail(.{ foe_ident, .None });
|
|
2094
|
+
if (state.miss) {
|
|
2095
|
+
try options.log.lastmiss(.{});
|
|
2096
|
+
return options.log.miss(.{battle.active(player)});
|
|
2097
|
+
} else if (foe.active.volatiles.Substitute) {
|
|
2098
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
foe.stored().status = Status.init(.PAR);
|
|
2102
|
+
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2103
|
+
|
|
2104
|
+
try options.log.status(.{ foe_ident, foe.stored().status, .None });
|
|
1797
2105
|
}
|
|
1798
2106
|
|
|
1799
2107
|
pub fn paralyzeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1808,17 +2116,34 @@ pub const Effects = struct {
|
|
|
1808
2116
|
|
|
1809
2117
|
foe_stored.status = Status.init(.PAR);
|
|
1810
2118
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
1811
|
-
|
|
2119
|
+
|
|
2120
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1812
2121
|
|
|
1813
2122
|
// TODO: check for status berry
|
|
1814
2123
|
}
|
|
1815
2124
|
|
|
1816
|
-
pub fn payDay(battle: anytype, player: Player,
|
|
1817
|
-
|
|
2125
|
+
pub fn payDay(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2126
|
+
if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
|
|
2127
|
+
try options.log.fieldactivate(.{}); // FIXME .PayDay
|
|
2128
|
+
}
|
|
1818
2129
|
}
|
|
1819
2130
|
|
|
1820
|
-
pub fn perishSong(battle: anytype, player: Player,
|
|
1821
|
-
|
|
2131
|
+
pub fn perishSong(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2132
|
+
var side = battle.side(player);
|
|
2133
|
+
var foe = battle.foe(player);
|
|
2134
|
+
|
|
2135
|
+
if (foe.active.volatiles.PerishSong) {
|
|
2136
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2137
|
+
} else if (!side.active.volatiles.PerishSong) {
|
|
2138
|
+
side.active.volatiles.PerishSong = true;
|
|
2139
|
+
side.active.volatiles.perish_song = 4;
|
|
2140
|
+
try options.log.start(.{ battle.active(player), .PerishSong3Silent });
|
|
2141
|
+
}
|
|
2142
|
+
foe.active.volatiles.PerishSong = true;
|
|
2143
|
+
foe.active.volatiles.perish_song = 4;
|
|
2144
|
+
try options.log.start(.{ battle.active(player.foe()), .PerishSong3Silent });
|
|
2145
|
+
|
|
2146
|
+
try options.log.fieldactivate(.{}); // FIXME .PerishSong
|
|
1822
2147
|
}
|
|
1823
2148
|
|
|
1824
2149
|
pub fn poison(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1828,24 +2153,24 @@ pub const Effects = struct {
|
|
|
1828
2153
|
|
|
1829
2154
|
const toxic = state.move == .Toxic;
|
|
1830
2155
|
if (state.immune() or foe.active.types.includes(.Poison)) {
|
|
1831
|
-
return options.log.immune(foe_ident, .None);
|
|
2156
|
+
return options.log.immune(.{ foe_ident, .None });
|
|
1832
2157
|
} else if (Status.any(foe_stored.status)) {
|
|
1833
2158
|
// Pokémon Showdown considers Toxic to be a status even in Generation II and so
|
|
1834
2159
|
// will not include a fail reason for Toxic vs. Poison or vice-versa...
|
|
1835
|
-
return options.log.fail(foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2160
|
+
return options.log.fail(.{ foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
1836
2161
|
if (!showdown)
|
|
1837
|
-
.Poison
|
|
2162
|
+
Fail.Poison
|
|
1838
2163
|
else if (toxic == (foe_stored.status == Status.TOX))
|
|
1839
|
-
if (toxic) .Toxic else .Poison
|
|
2164
|
+
if (toxic) Fail.Toxic else Fail.Poison
|
|
1840
2165
|
else
|
|
1841
|
-
.None
|
|
2166
|
+
Fail.None
|
|
1842
2167
|
else
|
|
1843
|
-
.None);
|
|
2168
|
+
Fail.None });
|
|
1844
2169
|
} else if (foe.active.volatiles.Substitute) {
|
|
1845
|
-
return options.log.
|
|
2170
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1846
2171
|
} else if (state.miss) {
|
|
1847
|
-
try options.log.lastmiss();
|
|
1848
|
-
|
|
2172
|
+
try options.log.lastmiss(.{});
|
|
2173
|
+
return options.log.miss(.{battle.active(player)});
|
|
1849
2174
|
}
|
|
1850
2175
|
|
|
1851
2176
|
foe_stored.status = Status.init(.PSN);
|
|
@@ -1855,7 +2180,7 @@ pub const Effects = struct {
|
|
|
1855
2180
|
foe.active.volatiles.toxic = 0;
|
|
1856
2181
|
}
|
|
1857
2182
|
|
|
1858
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
2183
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
1859
2184
|
|
|
1860
2185
|
// TODO: check for status berry
|
|
1861
2186
|
}
|
|
@@ -1871,7 +2196,8 @@ pub const Effects = struct {
|
|
|
1871
2196
|
if (foe.conditions.Safeguard) return;
|
|
1872
2197
|
|
|
1873
2198
|
foe_stored.status = Status.init(.PSN);
|
|
1874
|
-
|
|
2199
|
+
|
|
2200
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1875
2201
|
|
|
1876
2202
|
// TODO: check for status berry
|
|
1877
2203
|
}
|
|
@@ -1886,10 +2212,7 @@ pub const Effects = struct {
|
|
|
1886
2212
|
|
|
1887
2213
|
const failed = !state.first or volatiles.Substitute or
|
|
1888
2214
|
!try Rolls.protect(battle, player, volatiles.protect, options);
|
|
1889
|
-
if (failed) {
|
|
1890
|
-
try options.log.fail(battle.active(player), .None);
|
|
1891
|
-
return;
|
|
1892
|
-
}
|
|
2215
|
+
if (failed) return options.log.fail(.{ battle.active(player), .None });
|
|
1893
2216
|
|
|
1894
2217
|
volatiles.protect = @max(8, volatiles.protect + 1);
|
|
1895
2218
|
if (state.move == .Endure) {
|
|
@@ -1897,11 +2220,20 @@ pub const Effects = struct {
|
|
|
1897
2220
|
} else {
|
|
1898
2221
|
volatiles.Protect = true;
|
|
1899
2222
|
}
|
|
1900
|
-
try options.log.singleturn(battle.active(player), state.move);
|
|
2223
|
+
try options.log.singleturn(.{ battle.active(player), state.move });
|
|
1901
2224
|
}
|
|
1902
2225
|
|
|
1903
|
-
pub fn psychUp(battle: anytype, player: Player,
|
|
1904
|
-
|
|
2226
|
+
pub fn psychUp(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2227
|
+
var side = battle.side(player);
|
|
2228
|
+
const foe = battle.foe(player);
|
|
2229
|
+
|
|
2230
|
+
// Pokémon Showdown should fail when the enemy has no boosts/unboosts but doesn't
|
|
2231
|
+
if (!showdown and @as(u32, @bitCast(foe.active.boosts)) == 0) {
|
|
2232
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
side.active.boosts = foe.active.boosts;
|
|
2236
|
+
try options.log.copyboost(.{ battle.active(player), battle.active(player.foe()) });
|
|
1905
2237
|
}
|
|
1906
2238
|
|
|
1907
2239
|
pub fn pursuit(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
@@ -1912,15 +2244,14 @@ pub const Effects = struct {
|
|
|
1912
2244
|
pub const rage = struct {
|
|
1913
2245
|
pub fn start(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1914
2246
|
battle.side(player).active.volatiles.Rage = true;
|
|
1915
|
-
try options.log.singlemove(battle.active(player), Move.Rage);
|
|
2247
|
+
try options.log.singlemove(.{ battle.active(player), Move.Rage });
|
|
1916
2248
|
}
|
|
1917
2249
|
|
|
1918
2250
|
pub fn damage(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1919
|
-
|
|
1920
|
-
var volatiles = &side.active.volatiles;
|
|
2251
|
+
const side = battle.side(player);
|
|
1921
2252
|
|
|
1922
|
-
assert(volatiles.Rage);
|
|
1923
|
-
state.damage *|= (volatiles.rage +| 1);
|
|
2253
|
+
assert(side.active.volatiles.Rage);
|
|
2254
|
+
state.damage *|= (side.active.volatiles.rage +| 1);
|
|
1924
2255
|
}
|
|
1925
2256
|
};
|
|
1926
2257
|
|
|
@@ -1928,16 +2259,37 @@ pub const Effects = struct {
|
|
|
1928
2259
|
return weather(battle, .Rain, options);
|
|
1929
2260
|
}
|
|
1930
2261
|
|
|
1931
|
-
pub fn rapidSpin(battle: anytype, player: Player,
|
|
1932
|
-
|
|
1933
|
-
}
|
|
2262
|
+
pub fn rapidSpin(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2263
|
+
var side = battle.side(player);
|
|
1934
2264
|
|
|
1935
|
-
|
|
1936
|
-
|
|
2265
|
+
if (side.active.volatiles.LeechSeed) {
|
|
2266
|
+
try options.log.end(.{ battle.active(player), .LeechSeedFrom });
|
|
2267
|
+
side.active.volatiles.LeechSeed = false;
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
if (side.conditions.Spikes) {
|
|
2271
|
+
try options.log.sideend(.{ player, protocol.Side.Spikes, battle.active(player) });
|
|
2272
|
+
side.conditions.Spikes = false;
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
if (side.active.volatiles.bind.duration > 0) {
|
|
2276
|
+
const reason = @intFromEnum(End.Bind) + side.active.volatiles.bind.reason - 1;
|
|
2277
|
+
try options.log.end(.{ battle.active(player), @as(End, @enumFromInt(reason)) });
|
|
2278
|
+
side.active.volatiles.bind = .{};
|
|
2279
|
+
}
|
|
1937
2280
|
}
|
|
1938
2281
|
|
|
1939
2282
|
pub fn recoil(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1940
|
-
|
|
2283
|
+
var side = battle.side(player);
|
|
2284
|
+
var stored = side.stored();
|
|
2285
|
+
const ident = battle.active(player);
|
|
2286
|
+
|
|
2287
|
+
assert(state.damage > 0);
|
|
2288
|
+
|
|
2289
|
+
const damage: i16 = @intCast(@max(state.damage / 4, 1));
|
|
2290
|
+
stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
|
|
2291
|
+
|
|
2292
|
+
try options.log.damage(.{ ident, stored, .RecoilOf, battle.active(player.foe()) });
|
|
1941
2293
|
}
|
|
1942
2294
|
|
|
1943
2295
|
pub fn reversal(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1965,25 +2317,72 @@ pub const Effects = struct {
|
|
|
1965
2317
|
try calcDamage(battle, player, state, options);
|
|
1966
2318
|
}
|
|
1967
2319
|
|
|
1968
|
-
pub
|
|
1969
|
-
|
|
2320
|
+
pub const rollout = struct {
|
|
2321
|
+
pub fn check(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
2322
|
+
var side = battle.side(player);
|
|
2323
|
+
|
|
2324
|
+
if (!side.active.volatiles.Rollout) {
|
|
2325
|
+
side.active.volatiles.attacks = 0;
|
|
2326
|
+
} else {
|
|
2327
|
+
// TODO SkipToBattleCommand(doturn_command)
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2331
|
+
pub fn power(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2332
|
+
var side = battle.side(player);
|
|
2333
|
+
|
|
2334
|
+
if (Status.is(side.stored().status, .SLP)) return;
|
|
2335
|
+
|
|
2336
|
+
if (side.active.volatiles.attacks > 0) {
|
|
2337
|
+
// TODO set wSomeoneIsRampaging
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
if (state.miss) {
|
|
2341
|
+
side.active.volatiles.Rollout = false;
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
assert(side.active.volatiles.attacks < 5);
|
|
2346
|
+
side.active.volatiles.attacks += 1;
|
|
2347
|
+
side.active.volatiles.Rollout = side.active.volatiles.attacks != 5;
|
|
2348
|
+
|
|
2349
|
+
if (side.active.volatiles.DefenseCurl) state.damage *|= 2;
|
|
2350
|
+
}
|
|
2351
|
+
};
|
|
2352
|
+
|
|
2353
|
+
pub fn sandstorm(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2354
|
+
if (battle.field.weather != .Sandstorm) return weather(battle, .Sandstorm, options);
|
|
2355
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
1970
2356
|
}
|
|
1971
2357
|
|
|
1972
|
-
pub fn
|
|
1973
|
-
|
|
2358
|
+
pub fn safeguard(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2359
|
+
return screens(battle, player, "Safeguard", "safeguard", options);
|
|
1974
2360
|
}
|
|
1975
2361
|
|
|
1976
|
-
pub fn
|
|
1977
|
-
|
|
2362
|
+
pub fn reflect(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2363
|
+
return screens(battle, player, "Reflect", "reflect", options);
|
|
1978
2364
|
}
|
|
1979
2365
|
|
|
1980
|
-
pub fn
|
|
1981
|
-
|
|
1982
|
-
return try options.log.fail(battle.active(player), .None);
|
|
2366
|
+
pub fn lightScreen(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2367
|
+
return screens(battle, player, "LightScreen", "light_screen", options);
|
|
1983
2368
|
}
|
|
1984
2369
|
|
|
1985
|
-
|
|
1986
|
-
|
|
2370
|
+
fn screens(
|
|
2371
|
+
battle: anytype,
|
|
2372
|
+
player: Player,
|
|
2373
|
+
comptime key: []const u8,
|
|
2374
|
+
comptime value: []const u8,
|
|
2375
|
+
options: anytype,
|
|
2376
|
+
) !void {
|
|
2377
|
+
var side = battle.side(player);
|
|
2378
|
+
|
|
2379
|
+
if (@field(side.conditions, key)) {
|
|
2380
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
@field(side.conditions, key) = true;
|
|
2384
|
+
@field(side.conditions, value) = 5;
|
|
2385
|
+
try options.log.sidestart(.{ player, @field(protocol.Side, key) });
|
|
1987
2386
|
}
|
|
1988
2387
|
|
|
1989
2388
|
pub fn sketch(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
@@ -1994,34 +2393,26 @@ pub const Effects = struct {
|
|
|
1994
2393
|
|
|
1995
2394
|
if (showdown) {
|
|
1996
2395
|
// yes, actually "Splash". '|nothing' gets blindly mapped to '|-activate||move:Splash'
|
|
1997
|
-
try options.log.activate(battle.active(player), .Splash);
|
|
2396
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
1998
2397
|
} else {
|
|
1999
|
-
try options.log.fail(battle.active(player), .None);
|
|
2398
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2000
2399
|
}
|
|
2001
2400
|
}
|
|
2002
2401
|
|
|
2003
|
-
pub fn skullBash(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2004
|
-
_ = .{ battle, player, state, options }; // TODO
|
|
2005
|
-
}
|
|
2006
|
-
|
|
2007
|
-
pub fn skyAttack(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2008
|
-
_ = .{ battle, player, state, options }; // TODO
|
|
2009
|
-
}
|
|
2010
|
-
|
|
2011
2402
|
pub fn sleep(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2012
2403
|
var foe = battle.foe(player);
|
|
2013
2404
|
var foe_stored = foe.stored();
|
|
2014
2405
|
const foe_ident = battle.active(player.foe());
|
|
2015
2406
|
|
|
2016
2407
|
if (Status.any(foe_stored.status)) {
|
|
2017
|
-
return options.log.fail(
|
|
2408
|
+
return options.log.fail(.{
|
|
2018
2409
|
foe_ident,
|
|
2019
|
-
if (Status.is(foe_stored.status, .SLP)) .Sleep else .None,
|
|
2020
|
-
);
|
|
2410
|
+
if (Status.is(foe_stored.status, .SLP)) Fail.Sleep else Fail.None,
|
|
2411
|
+
});
|
|
2021
2412
|
}
|
|
2022
2413
|
|
|
2023
2414
|
if (foe.active.volatiles.Substitute) {
|
|
2024
|
-
return options.log.
|
|
2415
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
2025
2416
|
}
|
|
2026
2417
|
|
|
2027
2418
|
// Sleep Clause Mod
|
|
@@ -2032,25 +2423,21 @@ pub const Effects = struct {
|
|
|
2032
2423
|
}
|
|
2033
2424
|
|
|
2034
2425
|
foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
|
|
2035
|
-
try options.log.
|
|
2426
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .From, state.move });
|
|
2036
2427
|
}
|
|
2037
2428
|
|
|
2038
2429
|
pub fn sleepTalk(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2039
2430
|
const side = battle.side(player);
|
|
2040
2431
|
|
|
2041
2432
|
if (!Status.is(side.stored().status, .SLP)) {
|
|
2042
|
-
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
2043
|
-
return;
|
|
2433
|
+
return if (!showdown) try options.log.fail(.{ battle.active(player), .None });
|
|
2044
2434
|
}
|
|
2045
2435
|
|
|
2046
2436
|
var n: u3 = 0;
|
|
2047
2437
|
for (side.active.moves, 0..) |m, i| {
|
|
2048
2438
|
if (state.mslot - 1 != i and Move.get(m.id).extra.sleep_talk) n += 1;
|
|
2049
2439
|
}
|
|
2050
|
-
if (n == 0) {
|
|
2051
|
-
try options.log.fail(battle.active(player), .None);
|
|
2052
|
-
return;
|
|
2053
|
-
}
|
|
2440
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
2054
2441
|
|
|
2055
2442
|
_ = try Rolls.sleepTalk(battle, player, n, state.mslot, options); // TODO
|
|
2056
2443
|
}
|
|
@@ -2059,28 +2446,75 @@ pub const Effects = struct {
|
|
|
2059
2446
|
if (Status.is(battle.side(player).stored().status, .SLP)) return true;
|
|
2060
2447
|
state.damage = 0;
|
|
2061
2448
|
state.miss = true; // TODO is this necessary?
|
|
2062
|
-
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
2449
|
+
if (!showdown) try options.log.fail(.{ battle.active(player), .None });
|
|
2063
2450
|
return false;
|
|
2064
2451
|
}
|
|
2065
2452
|
|
|
2066
|
-
pub fn spikes(battle: anytype, player: Player,
|
|
2067
|
-
|
|
2453
|
+
pub fn spikes(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2454
|
+
var foe = battle.foe(player);
|
|
2455
|
+
if (foe.conditions.Spikes) return options.log.fail(.{ battle.active(player), .None });
|
|
2456
|
+
|
|
2457
|
+
foe.conditions.Spikes = true;
|
|
2458
|
+
try options.log.sidestart(.{ player.foe(), protocol.Side.Spikes });
|
|
2068
2459
|
}
|
|
2069
2460
|
|
|
2070
2461
|
pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2071
|
-
|
|
2462
|
+
var foe = battle.foe(player);
|
|
2463
|
+
|
|
2464
|
+
const last = foe.lastMove(false);
|
|
2465
|
+
if (state.miss or last == .None or last == .Struggle) {
|
|
2466
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2469
|
+
var slot: u4 = 0;
|
|
2470
|
+
for (foe.active.moves, 0..) |m, i| {
|
|
2471
|
+
if (m.id == last) slot = @intCast(i + 1);
|
|
2472
|
+
}
|
|
2473
|
+
// If the move was called via Metronome it might not exist
|
|
2474
|
+
if (slot == 0 or foe.active.move(slot).pp == 0) {
|
|
2475
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
// Pokémon Showdown always deducts 4 instead of randomizing
|
|
2479
|
+
const pp = @min(
|
|
2480
|
+
foe.active.move(slot).pp,
|
|
2481
|
+
if (showdown) 4 else try Rolls.spite(battle, player, options),
|
|
2482
|
+
);
|
|
2483
|
+
foe.active.move(slot).pp -= pp;
|
|
2484
|
+
if (!foe.active.volatiles.Transform) {
|
|
2485
|
+
var move_slot = foe.stored().move(slot);
|
|
2486
|
+
assert(move_slot.pp > 0);
|
|
2487
|
+
|
|
2488
|
+
move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
|
|
2489
|
+
assert(foe.active.move(slot).pp == foe.stored().move(slot).pp);
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
try options.log.activate(.{ battle.active(player.foe()), .Spite, last, pp });
|
|
2072
2493
|
}
|
|
2073
2494
|
|
|
2074
2495
|
pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2075
|
-
try options.log.activate(battle.active(player), .Splash);
|
|
2496
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
2076
2497
|
}
|
|
2077
2498
|
|
|
2078
|
-
pub fn
|
|
2079
|
-
|
|
2080
|
-
|
|
2499
|
+
pub fn substitute(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2500
|
+
var side = battle.side(player);
|
|
2501
|
+
if (side.active.volatiles.Substitute) {
|
|
2502
|
+
return options.log.fail(.{ battle.active(player), .Substitute });
|
|
2503
|
+
}
|
|
2081
2504
|
|
|
2082
|
-
|
|
2083
|
-
|
|
2505
|
+
assert(side.stored().stats.hp <= 1023);
|
|
2506
|
+
const hp: u8 = @intCast(side.stored().stats.hp / 4);
|
|
2507
|
+
|
|
2508
|
+
if (hp == 0 or side.stored().hp <= hp) {
|
|
2509
|
+
return options.log.fail(.{ battle.active(player), .Weak });
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
side.stored().hp -= hp;
|
|
2513
|
+
side.active.volatiles.substitute = hp;
|
|
2514
|
+
side.active.volatiles.Substitute = true;
|
|
2515
|
+
side.active.volatiles.bind = .{};
|
|
2516
|
+
try options.log.start(.{ battle.active(player), .Substitute });
|
|
2517
|
+
try options.log.damage(.{ battle.active(player), side.stored(), .None });
|
|
2084
2518
|
}
|
|
2085
2519
|
|
|
2086
2520
|
pub fn sunnyDay(battle: anytype, _: Player, _: *State, options: anytype) !void {
|
|
@@ -2090,22 +2524,14 @@ pub const Effects = struct {
|
|
|
2090
2524
|
fn weather(battle: anytype, w: Weather, options: anytype) !void {
|
|
2091
2525
|
battle.field.weather = w;
|
|
2092
2526
|
battle.field.weather_duration = 5;
|
|
2093
|
-
try options.log.weather(w, .None);
|
|
2094
|
-
}
|
|
2095
|
-
|
|
2096
|
-
pub fn swagger(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2097
|
-
_ = .{ battle, player, state, options }; // TODO
|
|
2098
|
-
}
|
|
2099
|
-
|
|
2100
|
-
pub fn synthesis(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2101
|
-
_ = .{ battle, player, state, options }; // TODO
|
|
2527
|
+
try options.log.weather(.{ w, protocol.Weather.None });
|
|
2102
2528
|
}
|
|
2103
2529
|
|
|
2104
2530
|
pub fn teleport(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2105
2531
|
if (showdown) return;
|
|
2106
2532
|
|
|
2107
|
-
try options.log.fail(battle.active(player), .None);
|
|
2108
|
-
try options.log.laststill();
|
|
2533
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2534
|
+
try options.log.laststill(.{});
|
|
2109
2535
|
}
|
|
2110
2536
|
|
|
2111
2537
|
pub fn thief(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2120,15 +2546,21 @@ pub const Effects = struct {
|
|
|
2120
2546
|
side.stored().item = foe.stored().item;
|
|
2121
2547
|
foe.stored().item = .None;
|
|
2122
2548
|
|
|
2123
|
-
try options.log.item(
|
|
2549
|
+
try options.log.item(.{
|
|
2124
2550
|
battle.active(player),
|
|
2125
2551
|
side.stored().item,
|
|
2126
2552
|
battle.active(player.foe()),
|
|
2127
|
-
);
|
|
2553
|
+
});
|
|
2128
2554
|
}
|
|
2129
2555
|
|
|
2130
|
-
pub fn thrashing(battle: anytype, player: Player,
|
|
2131
|
-
|
|
2556
|
+
pub fn thrashing(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2557
|
+
var side = battle.side(player);
|
|
2558
|
+
if (Status.is(side.stored().status, .SLP)) return;
|
|
2559
|
+
|
|
2560
|
+
assert(!side.active.volatiles.Thrashing);
|
|
2561
|
+
side.active.volatiles.Thrashing = true;
|
|
2562
|
+
side.active.volatiles.attacks = Rolls.attackingDuration(battle, player, options);
|
|
2563
|
+
// TODO wSomeoneIsRampaging = true;
|
|
2132
2564
|
}
|
|
2133
2565
|
|
|
2134
2566
|
pub fn transform(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
@@ -2140,11 +2572,11 @@ pub const Effects = struct {
|
|
|
2140
2572
|
side.active.volatiles.dirty = false;
|
|
2141
2573
|
|
|
2142
2574
|
if (foe.active.volatiles.Transform) {
|
|
2143
|
-
return
|
|
2575
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
2144
2576
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2145
|
-
if (!showdown) return
|
|
2146
|
-
try options.log.lastmiss();
|
|
2147
|
-
return
|
|
2577
|
+
if (!showdown) return options.log.fail(.{ battle.active(player), .None });
|
|
2578
|
+
try options.log.lastmiss(.{});
|
|
2579
|
+
return options.log.miss(.{battle.active(player)});
|
|
2148
2580
|
}
|
|
2149
2581
|
|
|
2150
2582
|
side.active.volatiles.Transform = true;
|
|
@@ -2166,7 +2598,7 @@ pub const Effects = struct {
|
|
|
2166
2598
|
side.active.moves[i].pp = if (m.id != .None) if (m.id == .Sketch) 1 else 5 else 0;
|
|
2167
2599
|
}
|
|
2168
2600
|
|
|
2169
|
-
try options.log.transform(battle.active(player), foe_ident);
|
|
2601
|
+
try options.log.transform(.{ battle.active(player), foe_ident });
|
|
2170
2602
|
}
|
|
2171
2603
|
|
|
2172
2604
|
pub fn triAttack(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2185,35 +2617,25 @@ pub const Effects = struct {
|
|
|
2185
2617
|
_ = .{ battle, player, state, options }; // TODO
|
|
2186
2618
|
}
|
|
2187
2619
|
|
|
2188
|
-
|
|
2189
|
-
_ = .{ battle, player, state, options }; // TODO
|
|
2190
|
-
}
|
|
2191
|
-
|
|
2192
|
-
const ALL_STAT_UP_STATS = [_][]const u8{ "atk", "def", "spe", "spa", "spd" };
|
|
2193
|
-
const ALL_STAT_UP_REASONS =
|
|
2620
|
+
const STAT_UP_REASONS =
|
|
2194
2621
|
[_]Boost{ .Attack, .Defense, .Speed, .SpecialAttack, .SpecialDefense };
|
|
2195
2622
|
|
|
2196
2623
|
pub fn allStatUpChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2197
2624
|
var side = battle.side(player);
|
|
2198
2625
|
|
|
2199
|
-
|
|
2200
|
-
|
|
2626
|
+
const stats = &side.active.stats;
|
|
2627
|
+
const boosts = &side.active.boosts;
|
|
2201
2628
|
|
|
2202
2629
|
if (!state.proc) return;
|
|
2203
|
-
inline for (
|
|
2630
|
+
inline for (STATS, STAT_UP_REASONS) |s, r| {
|
|
2204
2631
|
assert(@field(boosts, s) >= -6 and @field(boosts, s) <= 6);
|
|
2205
2632
|
if (@field(boosts, s) == 6 or @field(stats, s) == MAX_STAT_VALUE) {
|
|
2206
|
-
try options.log.boost(battle.active(player), r, 0);
|
|
2633
|
+
try options.log.boost(.{ battle.active(player), r, 0 });
|
|
2207
2634
|
} else {
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2211
|
-
@field(stats, s) = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2212
|
-
try options.log.boost(battle.active(player), r, 1);
|
|
2635
|
+
try boostStat(battle, side, stats, boosts, s, 1);
|
|
2636
|
+
try options.log.boost(.{ battle.active(player), r, 1 });
|
|
2213
2637
|
}
|
|
2214
2638
|
}
|
|
2215
|
-
|
|
2216
|
-
statusModify(side.stored().status, stats);
|
|
2217
2639
|
}
|
|
2218
2640
|
|
|
2219
2641
|
pub fn boost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2221,84 +2643,65 @@ pub const Effects = struct {
|
|
|
2221
2643
|
var side = battle.side(player);
|
|
2222
2644
|
const ident = battle.active(player);
|
|
2223
2645
|
|
|
2224
|
-
if (state.miss) return
|
|
2646
|
+
if (state.miss) return log.fail(.{ ident, .None });
|
|
2225
2647
|
|
|
2226
|
-
|
|
2648
|
+
const stats = &side.active.stats;
|
|
2227
2649
|
var boosts = &side.active.boosts;
|
|
2228
2650
|
|
|
2229
2651
|
const move = Move.get(state.move);
|
|
2230
2652
|
switch (move.effect) {
|
|
2231
|
-
.AttackUp1, .AttackUp2, .Rage => {
|
|
2653
|
+
.AttackUp1, .AttackUp2, .Rage, .Swagger => {
|
|
2232
2654
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2233
2655
|
if (boosts.atk == 6 or stats.atk == MAX_STAT_VALUE) {
|
|
2234
|
-
return
|
|
2656
|
+
return log.fail(.{ ident, .None });
|
|
2235
2657
|
}
|
|
2236
|
-
const n: u2 = if (move.effect == .AttackUp2) 2 else 1;
|
|
2237
|
-
|
|
2658
|
+
const n: u2 = if (move.effect == .AttackUp2 or move.effect == .Swagger) 2 else 1;
|
|
2659
|
+
try boostStat(battle, side, stats, boosts, "atk", n);
|
|
2238
2660
|
const reason = if (showdown and move.effect == .Rage) Boost.Rage else Boost.Attack;
|
|
2239
|
-
|
|
2240
|
-
const stat = unmodifiedStats(battle, side).atk;
|
|
2241
|
-
stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2242
|
-
try log.boost(ident, reason, n);
|
|
2661
|
+
try log.boost(.{ ident, reason, n });
|
|
2243
2662
|
},
|
|
2244
|
-
.DefenseUp1, .DefenseUp2 => {
|
|
2663
|
+
.DefenseUp1, .DefenseUp2, .SkullBash => {
|
|
2245
2664
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2246
2665
|
if (boosts.def == 6 or stats.def == MAX_STAT_VALUE) {
|
|
2247
|
-
return
|
|
2666
|
+
return log.fail(.{ ident, .None });
|
|
2248
2667
|
}
|
|
2249
2668
|
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
const stat = unmodifiedStats(battle, side).def;
|
|
2253
|
-
stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2254
|
-
try log.boost(ident, .Defense, n);
|
|
2669
|
+
try boostStat(battle, side, stats, boosts, "def", n);
|
|
2670
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2255
2671
|
},
|
|
2256
2672
|
.SpeedUp2 => {
|
|
2257
2673
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2258
2674
|
if (boosts.spe == 6 or stats.spe == MAX_STAT_VALUE) {
|
|
2259
|
-
return
|
|
2675
|
+
return log.fail(.{ ident, .None });
|
|
2260
2676
|
}
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
const stat = unmodifiedStats(battle, side).spe;
|
|
2264
|
-
stats.spe = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2265
|
-
try log.boost(ident, .Speed, 2);
|
|
2677
|
+
try boostStat(battle, side, stats, boosts, "spe", 2);
|
|
2678
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2266
2679
|
},
|
|
2267
2680
|
.SpAtkUp1 => {
|
|
2268
2681
|
assert(boosts.spa >= -6 and boosts.spa <= 6);
|
|
2269
2682
|
if (boosts.spa == 6 or stats.spa == MAX_STAT_VALUE) {
|
|
2270
|
-
return
|
|
2683
|
+
return log.fail(.{ ident, .None });
|
|
2271
2684
|
}
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
const stat = unmodifiedStats(battle, side).spa;
|
|
2275
|
-
stats.spa = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2276
|
-
try log.boost(ident, .SpecialAttack, 1);
|
|
2685
|
+
try boostStat(battle, side, stats, boosts, "spa", 1);
|
|
2686
|
+
try log.boost(.{ ident, .SpecialAttack, 1 });
|
|
2277
2687
|
},
|
|
2278
2688
|
.SpDefUp2 => {
|
|
2279
2689
|
assert(boosts.spd >= -6 and boosts.spd <= 6);
|
|
2280
2690
|
if (boosts.spd == 6 or stats.spd == MAX_STAT_VALUE) {
|
|
2281
|
-
return
|
|
2691
|
+
return log.fail(.{ ident, .None });
|
|
2282
2692
|
}
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
const stat = unmodifiedStats(battle, side).spd;
|
|
2286
|
-
stats.spd = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2287
|
-
try log.boost(ident, .SpecialDefense, 2);
|
|
2693
|
+
try boostStat(battle, side, stats, boosts, "spd", 2);
|
|
2694
|
+
try log.boost(.{ ident, .SpecialDefense, 2 });
|
|
2288
2695
|
},
|
|
2289
2696
|
.EvasionUp1 => {
|
|
2290
2697
|
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2291
|
-
if (boosts.evasion == 6) {
|
|
2292
|
-
return try log.fail(ident, .None);
|
|
2293
|
-
}
|
|
2698
|
+
if (boosts.evasion == 6) return log.fail(.{ ident, .None });
|
|
2294
2699
|
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2295
|
-
try log.boost(ident, .Evasion, 1);
|
|
2700
|
+
try log.boost(.{ ident, .Evasion, 1 });
|
|
2296
2701
|
if (state.move == .Minimize) side.active.volatiles.minimized = true;
|
|
2297
2702
|
},
|
|
2298
2703
|
else => unreachable,
|
|
2299
2704
|
}
|
|
2300
|
-
|
|
2301
|
-
statusModify(side.stored().status, stats);
|
|
2302
2705
|
}
|
|
2303
2706
|
|
|
2304
2707
|
pub fn unboost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2307,102 +2710,86 @@ pub const Effects = struct {
|
|
|
2307
2710
|
const foe_ident = battle.active(player.foe());
|
|
2308
2711
|
|
|
2309
2712
|
if (foe.active.volatiles.Mist) {
|
|
2310
|
-
try log.activate(foe_ident, .Mist);
|
|
2311
|
-
return
|
|
2713
|
+
try log.activate(.{ foe_ident, .Mist });
|
|
2714
|
+
return log.fail(.{ foe_ident, .None });
|
|
2312
2715
|
}
|
|
2313
2716
|
|
|
2314
2717
|
const fail = foe.active.volatiles.Substitute or state.miss or !state.proc;
|
|
2315
2718
|
|
|
2316
|
-
|
|
2719
|
+
const stats = &foe.active.stats;
|
|
2317
2720
|
var boosts = &foe.active.boosts;
|
|
2318
2721
|
|
|
2319
2722
|
const move = Move.get(state.move);
|
|
2723
|
+
const none: Fail = .None;
|
|
2320
2724
|
switch (move.effect) {
|
|
2321
2725
|
.AttackDown1, .AttackDown2, .AttackDownChance => {
|
|
2322
2726
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2323
2727
|
if (fail or boosts.atk == -6) {
|
|
2324
|
-
return if (move.effect != .AttackDownChance) try log.fail(foe_ident,
|
|
2728
|
+
return if (move.effect != .AttackDownChance) try log.fail(.{ foe_ident, none });
|
|
2325
2729
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2326
|
-
try options.log.lastmiss();
|
|
2327
|
-
|
|
2328
|
-
return;
|
|
2730
|
+
try options.log.lastmiss(.{});
|
|
2731
|
+
return options.log.miss(.{battle.active(player)});
|
|
2329
2732
|
} else if (stats.atk == 1) {
|
|
2330
|
-
return if (move.effect != .AttackDownChance) try log.fail(foe_ident,
|
|
2733
|
+
return if (move.effect != .AttackDownChance) try log.fail(.{ foe_ident, none });
|
|
2331
2734
|
}
|
|
2332
2735
|
const n: u2 = if (move.effect == .AttackDown2) 2 else 1;
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
const stat = unmodifiedStats(battle, foe).atk;
|
|
2336
|
-
stats.atk = @max(1, stat * mod[0] / mod[1]);
|
|
2337
|
-
try log.boost(foe_ident, .Attack, -@as(i8, n));
|
|
2736
|
+
try unboostStat(battle, foe, stats, boosts, "atk", n);
|
|
2737
|
+
try log.boost(.{ foe_ident, .Attack, -@as(i8, n) });
|
|
2338
2738
|
},
|
|
2339
2739
|
.DefenseDown1, .DefenseDown2, .DefenseDownChance => {
|
|
2340
2740
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2341
2741
|
if (fail or boosts.def == -6) {
|
|
2342
|
-
|
|
2343
|
-
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2344
|
-
try options.log.lastmiss();
|
|
2345
|
-
try options.log.miss(battle.active(player));
|
|
2742
|
+
if (move.effect != .DefenseDownChance) try log.fail(.{ foe_ident, none });
|
|
2346
2743
|
return;
|
|
2744
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2745
|
+
try options.log.lastmiss(.{});
|
|
2746
|
+
return options.log.miss(.{battle.active(player)});
|
|
2347
2747
|
} else if (stats.def == 1) {
|
|
2348
|
-
|
|
2748
|
+
if (move.effect != .DefenseDownChance) try log.fail(.{ foe_ident, none });
|
|
2749
|
+
return;
|
|
2349
2750
|
}
|
|
2350
2751
|
const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
const stat = unmodifiedStats(battle, foe).def;
|
|
2354
|
-
stats.def = @max(1, stat * mod[0] / mod[1]);
|
|
2355
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2752
|
+
try unboostStat(battle, foe, stats, boosts, "def", n);
|
|
2753
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2356
2754
|
},
|
|
2357
2755
|
.SpeedDown1, .SpeedDown2, .SpeedDownChance => {
|
|
2358
2756
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2359
2757
|
if (fail or boosts.spe == -6) {
|
|
2360
|
-
return if (move.effect != .SpeedDownChance) try log.fail(foe_ident,
|
|
2758
|
+
return if (move.effect != .SpeedDownChance) try log.fail(.{ foe_ident, none });
|
|
2361
2759
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2362
|
-
try options.log.lastmiss();
|
|
2363
|
-
|
|
2364
|
-
return;
|
|
2760
|
+
try options.log.lastmiss(.{});
|
|
2761
|
+
return options.log.miss(.{battle.active(player)});
|
|
2365
2762
|
} else if (stats.spe == 1) {
|
|
2366
|
-
return if (move.effect != .SpeedDownChance) try log.fail(foe_ident,
|
|
2763
|
+
return if (move.effect != .SpeedDownChance) try log.fail(.{ foe_ident, none });
|
|
2367
2764
|
}
|
|
2368
2765
|
const n: u2 = if (move.effect == .SpeedDown2) 2 else 1;
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
const stat = unmodifiedStats(battle, foe).spe;
|
|
2372
|
-
stats.spe = @max(1, stat * mod[0] / mod[1]);
|
|
2373
|
-
try log.boost(foe_ident, .Speed, -@as(i8, n));
|
|
2374
|
-
assert(boosts.spe >= -6);
|
|
2766
|
+
try unboostStat(battle, foe, stats, boosts, "spe", n);
|
|
2767
|
+
try log.boost(.{ foe_ident, .Speed, -@as(i8, n) });
|
|
2375
2768
|
},
|
|
2376
2769
|
.SpDefDownChance => {
|
|
2377
2770
|
assert(boosts.spd >= -6 and boosts.spd <= 6);
|
|
2378
2771
|
if (fail or boosts.spd == -6) return;
|
|
2379
2772
|
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2380
|
-
try options.log.lastmiss();
|
|
2381
|
-
|
|
2382
|
-
return;
|
|
2773
|
+
try options.log.lastmiss(.{});
|
|
2774
|
+
return options.log.miss(.{battle.active(player)});
|
|
2383
2775
|
} else if (stats.spd == 1) return;
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
const stat = unmodifiedStats(battle, foe).spd;
|
|
2387
|
-
stats.spd = @max(1, stat * mod[0] / mod[1]);
|
|
2388
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2776
|
+
try unboostStat(battle, foe, stats, boosts, "spd", 1);
|
|
2777
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2389
2778
|
},
|
|
2390
2779
|
.AccuracyDown1, .AccuracyDownChance => {
|
|
2391
2780
|
assert(boosts.accuracy >= -6 and boosts.accuracy <= 6);
|
|
2392
2781
|
if (fail or boosts.accuracy == -6) {
|
|
2393
|
-
|
|
2394
|
-
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2395
|
-
try options.log.lastmiss();
|
|
2396
|
-
try options.log.miss(battle.active(player));
|
|
2782
|
+
if (move.effect != .AccuracyDownChance) try log.fail(.{ foe_ident, none });
|
|
2397
2783
|
return;
|
|
2784
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2785
|
+
try options.log.lastmiss(.{});
|
|
2786
|
+
return options.log.miss(.{battle.active(player)});
|
|
2398
2787
|
}
|
|
2399
2788
|
boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
|
|
2400
|
-
try log.boost(foe_ident, .Accuracy, -1);
|
|
2789
|
+
try log.boost(.{ foe_ident, .Accuracy, -1 });
|
|
2401
2790
|
},
|
|
2402
2791
|
else => unreachable,
|
|
2403
2792
|
}
|
|
2404
|
-
|
|
2405
|
-
statusModify(foe.stored().status, stats);
|
|
2406
2793
|
}
|
|
2407
2794
|
};
|
|
2408
2795
|
|
|
@@ -2412,6 +2799,15 @@ fn unmodifiedStats(battle: anytype, side: *Side) *Stats(u16) {
|
|
|
2412
2799
|
return &battle.side(id.player).pokemon[id.id - 1].stats;
|
|
2413
2800
|
}
|
|
2414
2801
|
|
|
2802
|
+
fn recomputeStats(battle: anytype, side: *Side) void {
|
|
2803
|
+
const stats = unmodifiedStats(battle, side);
|
|
2804
|
+
inline for (STATS) |s| {
|
|
2805
|
+
const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(side.active.boosts, s)) + 6))];
|
|
2806
|
+
const val = @field(stats, s) * mod[0] / mod[1];
|
|
2807
|
+
@field(side.active.stats, s) = @max(1, @min(MAX_STAT_VALUE, val));
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2415
2811
|
fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
2416
2812
|
if (Status.is(status, .PAR)) {
|
|
2417
2813
|
stats.spe = @max(stats.spe / 4, 1);
|
|
@@ -2420,6 +2816,52 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
2420
2816
|
}
|
|
2421
2817
|
}
|
|
2422
2818
|
|
|
2819
|
+
fn boostStat(
|
|
2820
|
+
battle: anytype,
|
|
2821
|
+
side: *Side,
|
|
2822
|
+
stats: *Stats(u16),
|
|
2823
|
+
boosts: *Boosts,
|
|
2824
|
+
comptime s: []const u8,
|
|
2825
|
+
n: u2,
|
|
2826
|
+
) !void {
|
|
2827
|
+
@field(boosts, s) = @intCast(@min(6, @as(i8, @field(boosts, s)) + n));
|
|
2828
|
+
const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
|
|
2829
|
+
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2830
|
+
@field(stats, s) = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2831
|
+
if (comptime std.mem.eql(u8, s, "atk")) {
|
|
2832
|
+
if (Status.is(side.stored().status, .BRN)) {
|
|
2833
|
+
@field(stats, s) = @max(@field(stats, s) / 2, 1);
|
|
2834
|
+
}
|
|
2835
|
+
} else if (comptime std.mem.eql(u8, s, "spe")) {
|
|
2836
|
+
if (Status.is(side.stored().status, .PAR)) {
|
|
2837
|
+
@field(stats, s) = @max(@field(stats, s) / 4, 1);
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
fn unboostStat(
|
|
2843
|
+
battle: anytype,
|
|
2844
|
+
side: *Side,
|
|
2845
|
+
stats: *Stats(u16),
|
|
2846
|
+
boosts: *Boosts,
|
|
2847
|
+
comptime s: []const u8,
|
|
2848
|
+
n: u2,
|
|
2849
|
+
) !void {
|
|
2850
|
+
@field(boosts, s) = @intCast(@max(-6, @as(i8, @field(boosts, s)) - n));
|
|
2851
|
+
const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
|
|
2852
|
+
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2853
|
+
@field(stats, s) = @max(1, stat * mod[0] / mod[1]);
|
|
2854
|
+
if (comptime std.mem.eql(u8, s, "atk")) {
|
|
2855
|
+
if (Status.is(side.stored().status, .BRN)) {
|
|
2856
|
+
@field(stats, s) = @max(@field(stats, s) / 2, 1);
|
|
2857
|
+
}
|
|
2858
|
+
} else if (comptime std.mem.eql(u8, s, "spe")) {
|
|
2859
|
+
if (Status.is(side.stored().status, .PAR)) {
|
|
2860
|
+
@field(stats, s) = @max(@field(stats, s) / 4, 1);
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2423
2865
|
fn isForced(active: anytype) bool {
|
|
2424
2866
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
2425
2867
|
active.volatiles.Thrashing or active.volatiles.Charging or
|
|
@@ -2609,7 +3051,7 @@ pub const Rolls = struct {
|
|
|
2609
3051
|
}
|
|
2610
3052
|
|
|
2611
3053
|
fn magnitude(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2612
|
-
const num = if (options.calc.overridden(player, .magnitude)) |val|
|
|
3054
|
+
const num: u8 = if (options.calc.overridden(player, .magnitude)) |val|
|
|
2613
3055
|
val + 3
|
|
2614
3056
|
else if (showdown) num: {
|
|
2615
3057
|
const r = battle.rng.range(u8, 0, 100);
|
|
@@ -2653,13 +3095,12 @@ pub const Rolls = struct {
|
|
|
2653
3095
|
return hits;
|
|
2654
3096
|
}
|
|
2655
3097
|
|
|
2656
|
-
fn spite(battle: anytype, player: Player, options: anytype) !
|
|
2657
|
-
|
|
3098
|
+
fn spite(battle: anytype, player: Player, options: anytype) !u3 {
|
|
3099
|
+
assert(!showdown);
|
|
3100
|
+
const pp: u3 = if (options.calc.overridden(player, .spite)) |val|
|
|
2658
3101
|
val + 1
|
|
2659
|
-
else if (showdown)
|
|
2660
|
-
battle.rng.range(u8, 2, 6)
|
|
2661
3102
|
else
|
|
2662
|
-
(battle.rng.next() & 3) + 2;
|
|
3103
|
+
@intCast((battle.rng.next() & 3) + 2);
|
|
2663
3104
|
|
|
2664
3105
|
assert(pp >= 2 and pp <= 5);
|
|
2665
3106
|
try options.chance.spite(player, pp);
|
|
@@ -2868,12 +3309,12 @@ pub const Rolls = struct {
|
|
|
2868
3309
|
}
|
|
2869
3310
|
|
|
2870
3311
|
fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
2871
|
-
const moves = battle.
|
|
3312
|
+
const moves = battle.side(player).active.moves;
|
|
2872
3313
|
|
|
2873
3314
|
var n: u2 = 0;
|
|
2874
3315
|
for (moves) |m| {
|
|
2875
|
-
if (m == .None) break;
|
|
2876
|
-
if (Move.get(m).metronome) n += 1;
|
|
3316
|
+
if (m.id == .None) break;
|
|
3317
|
+
if (Move.get(m.id).extra.metronome) n += 1;
|
|
2877
3318
|
}
|
|
2878
3319
|
|
|
2879
3320
|
// TODO: consider throwing error instead of rerolling?
|
|
@@ -2884,10 +3325,11 @@ pub const Rolls = struct {
|
|
|
2884
3325
|
|
|
2885
3326
|
const move: Move = overridden orelse move: {
|
|
2886
3327
|
if (showdown) {
|
|
2887
|
-
var r = battle.rng.range(u8, 0, Move.METRONOME.len - n);
|
|
3328
|
+
var r = battle.rng.range(u8, 0, @as(u8, @intCast(Move.METRONOME.len - n)));
|
|
2888
3329
|
for (moves) |m| {
|
|
2889
|
-
if (m == .None
|
|
2890
|
-
if (
|
|
3330
|
+
if (m.id == .None) break;
|
|
3331
|
+
if (@intFromEnum(m.id) > @intFromEnum(Move.METRONOME[r])) break;
|
|
3332
|
+
if (Move.get(m.id).extra.metronome) r -= 1;
|
|
2891
3333
|
}
|
|
2892
3334
|
break :move Move.METRONOME[r];
|
|
2893
3335
|
} else {
|
|
@@ -2902,7 +3344,7 @@ pub const Rolls = struct {
|
|
|
2902
3344
|
}
|
|
2903
3345
|
};
|
|
2904
3346
|
|
|
2905
|
-
assert(Move.get(move).metronome);
|
|
3347
|
+
assert(Move.get(move).extra.metronome);
|
|
2906
3348
|
try options.chance.metronome(player, move, n);
|
|
2907
3349
|
return move;
|
|
2908
3350
|
}
|
|
@@ -3106,7 +3548,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3106
3548
|
.Move => {
|
|
3107
3549
|
const side = battle.side(player);
|
|
3108
3550
|
|
|
3109
|
-
|
|
3551
|
+
const active = &side.active;
|
|
3110
3552
|
const stored = side.stored();
|
|
3111
3553
|
|
|
3112
3554
|
// While players are not given any input options on the cartridge in these cases,
|
|
@@ -3126,7 +3568,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3126
3568
|
}
|
|
3127
3569
|
|
|
3128
3570
|
var slot: u4 = 2;
|
|
3129
|
-
while (!active.volatiles.
|
|
3571
|
+
while (!active.volatiles.Trapped and slot <= 6) : (slot += 1) {
|
|
3130
3572
|
const id = side.order[slot - 1];
|
|
3131
3573
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
3132
3574
|
out[n] = .{ .type = .Switch, .data = slot };
|