@pkmn/engine 0.1.0-dev.305a932f → 0.1.0-dev.30c63015
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/build/pkg/data/layout.json +2 -2
- package/build/pkg/gen1.js +2 -2
- package/build/pkg/gen1.js.map +1 -1
- package/build/tools/debug.js +3 -1
- package/build/tools/debug.js.map +1 -1
- package/build.zig +19 -13
- package/package.json +19 -16
- package/src/lib/common/data.zig +4 -4
- package/src/lib/common/optional.zig +8 -1
- package/src/lib/common/protocol.zig +295 -278
- package/src/lib/common/rational.zig +4 -4
- package/src/lib/common/rng.zig +9 -0
- package/src/lib/gen1/README.md +28 -28
- package/src/lib/gen1/calc.zig +2 -2
- package/src/lib/gen1/chance.zig +51 -49
- package/src/lib/gen1/data/moves.zig +24 -10
- package/src/lib/gen1/data/species.zig +2 -2
- package/src/lib/gen1/data/types.zig +5 -5
- package/src/lib/gen1/data.zig +20 -20
- package/src/lib/gen1/helpers.zig +14 -29
- package/src/lib/gen1/mechanics.zig +260 -282
- package/src/lib/gen2/README.md +1 -2
- package/src/lib/gen2/calc.zig +143 -0
- package/src/lib/gen2/chance.zig +1314 -0
- package/src/lib/gen2/data/items.zig +3 -3
- package/src/lib/gen2/data/moves.zig +32 -15
- package/src/lib/gen2/data/species.zig +1 -1
- package/src/lib/gen2/data/types.zig +75 -28
- package/src/lib/gen2/data.zig +28 -16
- package/src/lib/gen2/generated.zig +808 -0
- package/src/lib/gen2/mechanics.zig +2630 -340
|
@@ -7,24 +7,27 @@ const DEBUG = @import("../common/debug.zig").print;
|
|
|
7
7
|
const protocol = @import("../common/protocol.zig");
|
|
8
8
|
const rng = @import("../common/rng.zig");
|
|
9
9
|
|
|
10
|
-
const Action = @import("chance.zig").Action;
|
|
11
|
-
|
|
12
10
|
const data = @import("data.zig");
|
|
13
11
|
|
|
14
12
|
const assert = std.debug.assert;
|
|
15
13
|
|
|
16
14
|
const expectEqual = std.testing.expectEqual;
|
|
17
15
|
|
|
16
|
+
const showdown = pkmn.options.showdown;
|
|
17
|
+
|
|
18
18
|
const Choice = common.Choice;
|
|
19
19
|
const ID = common.ID;
|
|
20
20
|
const Player = common.Player;
|
|
21
21
|
const Result = common.Result;
|
|
22
22
|
|
|
23
|
-
const showdown = pkmn.options.showdown;
|
|
24
|
-
|
|
25
23
|
const Boost = protocol.Boost;
|
|
24
|
+
const Cant = protocol.Cant;
|
|
25
|
+
const CureStatus = protocol.CureStatus;
|
|
26
26
|
const Damage = protocol.Damage;
|
|
27
|
+
const End = protocol.End;
|
|
28
|
+
const Fail = protocol.Fail;
|
|
27
29
|
const Heal = protocol.Heal;
|
|
30
|
+
const Immune = protocol.Immune;
|
|
28
31
|
|
|
29
32
|
const Gen12 = rng.Gen12;
|
|
30
33
|
|
|
@@ -59,19 +62,15 @@ const BOOSTS = &[_][2]u8{
|
|
|
59
62
|
|
|
60
63
|
const MAX_STAT_VALUE = 999;
|
|
61
64
|
|
|
62
|
-
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
63
|
-
|
|
64
65
|
pub fn update(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Result {
|
|
65
66
|
assert(c1.type != .Pass or c2.type != .Pass or battle.turn == 0);
|
|
66
67
|
if (battle.turn == 0) return start(battle, options);
|
|
67
68
|
|
|
68
69
|
var s1 = false;
|
|
69
70
|
var s2 = false;
|
|
70
|
-
var f1: ?Move = null;
|
|
71
|
-
var f2: ?Move = null;
|
|
72
71
|
|
|
73
|
-
if (selectMove(battle, .P1, c1, c2, &s1
|
|
74
|
-
if (selectMove(battle, .P2, c2, c1, &s2
|
|
72
|
+
if (selectMove(battle, .P1, c1, c2, &s1)) |r| return r;
|
|
73
|
+
if (selectMove(battle, .P2, c2, c1, &s2)) |r| return r;
|
|
75
74
|
|
|
76
75
|
var p1 = battle.side(.P1);
|
|
77
76
|
var p2 = battle.side(.P2);
|
|
@@ -80,9 +79,9 @@ pub fn update(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Result
|
|
|
80
79
|
var r2 = showdown and p2.active.volatiles.Binding and c1.type == .Switch;
|
|
81
80
|
|
|
82
81
|
if (try turnOrder(battle, c1, c2, options) == .P1) {
|
|
83
|
-
if (try doTurn(battle, .P1, c1, r1, s1,
|
|
82
|
+
if (try doTurn(battle, .P1, c1, r1, s1, .P2, c2, r2, s2, options)) |r| return r;
|
|
84
83
|
} else {
|
|
85
|
-
if (try doTurn(battle, .P2, c2, r2, s2,
|
|
84
|
+
if (try doTurn(battle, .P2, c2, r2, s2, .P1, c1, r1, s1, options)) |r| return r;
|
|
86
85
|
}
|
|
87
86
|
|
|
88
87
|
if (p1.active.volatiles.attacks == 0) p1.active.volatiles.Binding = false;
|
|
@@ -120,7 +119,6 @@ fn selectMove(
|
|
|
120
119
|
choice: Choice,
|
|
121
120
|
foe_choice: Choice,
|
|
122
121
|
skip_turn: *bool,
|
|
123
|
-
from: *?Move,
|
|
124
122
|
) ?Result {
|
|
125
123
|
if (choice.type == .Pass) return null;
|
|
126
124
|
|
|
@@ -137,7 +135,6 @@ fn selectMove(
|
|
|
137
135
|
return null;
|
|
138
136
|
}
|
|
139
137
|
if (volatiles.Rage) {
|
|
140
|
-
from.* = side.last_used_move;
|
|
141
138
|
if (showdown) {
|
|
142
139
|
if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
|
|
143
140
|
saveMove(battle, player, null);
|
|
@@ -147,7 +144,6 @@ fn selectMove(
|
|
|
147
144
|
// Pokémon Showdown removes Flinch at the end-of-turn in its residual handler
|
|
148
145
|
if (!showdown) volatiles.Flinch = false;
|
|
149
146
|
if (volatiles.Thrashing or volatiles.Charging) {
|
|
150
|
-
from.* = side.last_selected_move;
|
|
151
147
|
if (showdown) {
|
|
152
148
|
if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
|
|
153
149
|
saveMove(battle, player, null);
|
|
@@ -168,9 +164,7 @@ fn selectMove(
|
|
|
168
164
|
return null;
|
|
169
165
|
}
|
|
170
166
|
if (volatiles.Binding) {
|
|
171
|
-
from.* = side.last_used_move;
|
|
172
167
|
if (showdown) {
|
|
173
|
-
if (foe_choice.type == .Switch) from.* = null;
|
|
174
168
|
// Pokémon Showdown overwrites Mirror Move with whatever was selected - really this
|
|
175
169
|
// should set side.last_selected_move = last.id to reuse Mirror Move and fail in order
|
|
176
170
|
// to satisfy the conditions of the Desync Clause Mod. However, because Binding is still
|
|
@@ -206,7 +200,7 @@ fn selectMove(
|
|
|
206
200
|
if (choice.data == 0) {
|
|
207
201
|
const struggle = ok: {
|
|
208
202
|
for (side.active.moves, 0..) |move, i| {
|
|
209
|
-
if (move.pp > 0 and volatiles.
|
|
203
|
+
if (move.pp > 0 and volatiles.disable_move != i + 1) break :ok false;
|
|
210
204
|
}
|
|
211
205
|
break :ok true;
|
|
212
206
|
};
|
|
@@ -225,7 +219,7 @@ fn saveMove(battle: anytype, player: Player, choice: ?Choice) void {
|
|
|
225
219
|
if (c.data == 0) {
|
|
226
220
|
side.last_selected_move = .Struggle;
|
|
227
221
|
} else {
|
|
228
|
-
assert(showdown or side.active.volatiles.
|
|
222
|
+
assert(showdown or side.active.volatiles.disable_move != c.data);
|
|
229
223
|
const move = side.active.move(c.data);
|
|
230
224
|
// You cannot *select* a move with 0 PP (except on Pokémon Showdown where that is
|
|
231
225
|
// sometimes required...), but a 0 PP move can be used automatically
|
|
@@ -266,14 +260,14 @@ fn switchIn(battle: anytype, player: Player, slot: u8, initial: bool, options: a
|
|
|
266
260
|
|
|
267
261
|
foe.active.volatiles.Binding = false;
|
|
268
262
|
|
|
269
|
-
try options.log.switched(battle.active(player), incoming);
|
|
263
|
+
try options.log.switched(.{ battle.active(player), incoming });
|
|
270
264
|
options.chance.switched(player, side.order[0], out);
|
|
271
265
|
|
|
272
266
|
if (showdown and incoming.status == Status.TOX) {
|
|
273
267
|
incoming.status = Status.init(.PSN);
|
|
274
268
|
// Technically, Pokémon Showdown adds these after *both* Pokémon have switched, but we'd
|
|
275
269
|
// rather not clutter up turnOrder just for this (incorrect) log message
|
|
276
|
-
try options.log.status(battle.active(player), incoming.status, .Silent);
|
|
270
|
+
try options.log.status(.{ battle.active(player), incoming.status, protocol.Status.Silent });
|
|
277
271
|
}
|
|
278
272
|
}
|
|
279
273
|
|
|
@@ -336,12 +330,10 @@ fn doTurn(
|
|
|
336
330
|
player_choice: Choice,
|
|
337
331
|
player_rewrap: bool,
|
|
338
332
|
player_skip: bool,
|
|
339
|
-
player_from: ?Move,
|
|
340
333
|
foe_player: Player,
|
|
341
334
|
foe_choice: Choice,
|
|
342
335
|
foe_rewrap: bool,
|
|
343
336
|
foe_skip: bool,
|
|
344
|
-
foe_from: ?Move,
|
|
345
337
|
options: anytype,
|
|
346
338
|
) !?Result {
|
|
347
339
|
assert(player_choice.type != .Pass);
|
|
@@ -354,7 +346,6 @@ fn doTurn(
|
|
|
354
346
|
player_choice,
|
|
355
347
|
player_rewrap,
|
|
356
348
|
player_skip,
|
|
357
|
-
player_from,
|
|
358
349
|
&residual,
|
|
359
350
|
options,
|
|
360
351
|
)) |r| return r;
|
|
@@ -377,7 +368,6 @@ fn doTurn(
|
|
|
377
368
|
foe_choice,
|
|
378
369
|
foe_rewrap,
|
|
379
370
|
foe_skip,
|
|
380
|
-
foe_from,
|
|
381
371
|
&residual,
|
|
382
372
|
options,
|
|
383
373
|
)) |r| return r;
|
|
@@ -405,7 +395,6 @@ fn executeMove(
|
|
|
405
395
|
choice: Choice,
|
|
406
396
|
rewrap: bool,
|
|
407
397
|
skip: bool,
|
|
408
|
-
from: ?Move,
|
|
409
398
|
residual: *bool,
|
|
410
399
|
options: anytype,
|
|
411
400
|
) !?Result {
|
|
@@ -422,7 +411,7 @@ fn executeMove(
|
|
|
422
411
|
if (side.last_selected_move == .SKIP_TURN) {
|
|
423
412
|
assert(!showdown);
|
|
424
413
|
if (battle.foe(player).active.volatiles.Binding) {
|
|
425
|
-
try options.log.cant(battle.active(player), .Bound);
|
|
414
|
+
try options.log.cant(.{ battle.active(player), Cant.Bound });
|
|
426
415
|
}
|
|
427
416
|
return null;
|
|
428
417
|
}
|
|
@@ -440,8 +429,10 @@ fn executeMove(
|
|
|
440
429
|
mslot = @intCast(battle.lastMove(player).index);
|
|
441
430
|
const stored = side.stored();
|
|
442
431
|
// GLITCH: Struggle bypass PP underflow via Hyper Beam / Trapping-switch auto selection
|
|
443
|
-
auto = side.
|
|
444
|
-
|
|
432
|
+
auto = isForced(&side.active) or
|
|
433
|
+
side.active.volatiles.Binding or side.active.volatiles.Bide or
|
|
434
|
+
side.last_selected_move == .HyperBeam or
|
|
435
|
+
Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP);
|
|
445
436
|
// If it wasn't Hyper Beam or the continuation of a move effect then we must have just
|
|
446
437
|
// thawed, in which case we will desync unless the last_selected_move happened to be at
|
|
447
438
|
// index 1 and the current Pokémon has the same move in its first slot.
|
|
@@ -463,7 +454,7 @@ fn executeMove(
|
|
|
463
454
|
|
|
464
455
|
var skip_can = false;
|
|
465
456
|
var skip_pp = false;
|
|
466
|
-
switch (try beforeMove(battle, player, skip,
|
|
457
|
+
switch (try beforeMove(battle, player, skip, residual, options)) {
|
|
467
458
|
.done => return null,
|
|
468
459
|
.skip_can => skip_can = true,
|
|
469
460
|
.skip_pp => skip_pp = true,
|
|
@@ -471,9 +462,9 @@ fn executeMove(
|
|
|
471
462
|
.err => return @as(?Result, Result.Error),
|
|
472
463
|
}
|
|
473
464
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
465
|
+
const can = skip_can or
|
|
466
|
+
try canMove(battle, player, mslot, auto, skip_pp, .None, residual, options);
|
|
467
|
+
if (!can) return null;
|
|
477
468
|
|
|
478
469
|
return doMove(battle, player, mslot, rewrap, auto, residual, options);
|
|
479
470
|
}
|
|
@@ -484,7 +475,6 @@ fn beforeMove(
|
|
|
484
475
|
battle: anytype,
|
|
485
476
|
player: Player,
|
|
486
477
|
skip: bool,
|
|
487
|
-
from: ?Move,
|
|
488
478
|
residual: *bool,
|
|
489
479
|
options: anytype,
|
|
490
480
|
) !BeforeMove {
|
|
@@ -511,23 +501,23 @@ fn beforeMove(
|
|
|
511
501
|
}
|
|
512
502
|
|
|
513
503
|
if (duration == 0) {
|
|
514
|
-
try log.curestatus(ident, before, .Message);
|
|
504
|
+
try log.curestatus(.{ ident, before, CureStatus.Message });
|
|
515
505
|
stored.status = 0; // clears EXT if present
|
|
516
506
|
} else {
|
|
517
|
-
try log.cant(ident, .Sleep);
|
|
507
|
+
try log.cant(.{ ident, Cant.Sleep });
|
|
518
508
|
}
|
|
519
509
|
side.last_used_move = .None;
|
|
520
510
|
return .done;
|
|
521
511
|
}
|
|
522
512
|
|
|
523
513
|
if (Status.is(stored.status, .FRZ)) {
|
|
524
|
-
try log.cant(ident, .Freeze);
|
|
514
|
+
try log.cant(.{ ident, Cant.Freeze });
|
|
525
515
|
side.last_used_move = .None;
|
|
526
516
|
return .done;
|
|
527
517
|
}
|
|
528
518
|
|
|
529
519
|
if (skip or foe.active.volatiles.Binding) {
|
|
530
|
-
try log.cant(ident, .Bound);
|
|
520
|
+
try log.cant(.{ ident, Cant.Bound });
|
|
531
521
|
return .done;
|
|
532
522
|
}
|
|
533
523
|
|
|
@@ -535,27 +525,27 @@ fn beforeMove(
|
|
|
535
525
|
// Pokémon Showdown doesn't clear Flinch until its imaginary "residual" phase, meaning
|
|
536
526
|
// Pokémon can sometimes flinch multiple times from the same original hit
|
|
537
527
|
if (!showdown) volatiles.Flinch = false;
|
|
538
|
-
try log.cant(ident, .Flinch);
|
|
528
|
+
try log.cant(.{ ident, Cant.Flinch });
|
|
539
529
|
return .done;
|
|
540
530
|
}
|
|
541
531
|
|
|
542
532
|
if (volatiles.Recharging) {
|
|
543
533
|
volatiles.Recharging = false;
|
|
544
|
-
try log.cant(ident, .Recharge);
|
|
534
|
+
try log.cant(.{ ident, Cant.Recharge });
|
|
545
535
|
return .done;
|
|
546
536
|
}
|
|
547
537
|
|
|
548
|
-
if (volatiles.
|
|
538
|
+
if (volatiles.disable_duration > 0) {
|
|
549
539
|
if (options.calc.modify(player, .disable)) |extend| {
|
|
550
|
-
if (!extend) volatiles.
|
|
540
|
+
if (!extend) volatiles.disable_duration = 0;
|
|
551
541
|
} else {
|
|
552
|
-
volatiles.
|
|
542
|
+
volatiles.disable_duration -= 1;
|
|
553
543
|
}
|
|
554
|
-
try options.chance.disable(player, volatiles.
|
|
544
|
+
try options.chance.disable(player, volatiles.disable_duration);
|
|
555
545
|
|
|
556
|
-
if (volatiles.
|
|
557
|
-
volatiles.
|
|
558
|
-
try log.end(ident, .Disable);
|
|
546
|
+
if (volatiles.disable_duration == 0) {
|
|
547
|
+
volatiles.disable_move = 0;
|
|
548
|
+
try log.end(.{ ident, End.Disable });
|
|
559
549
|
}
|
|
560
550
|
}
|
|
561
551
|
// Pokémon Showdown's disable condition has a single onBeforeMove handler
|
|
@@ -580,7 +570,7 @@ fn beforeMove(
|
|
|
580
570
|
|
|
581
571
|
if (volatiles.confusion == 0) {
|
|
582
572
|
volatiles.Confusion = false;
|
|
583
|
-
try log.end(ident, .Confusion);
|
|
573
|
+
try log.end(.{ ident, End.Confusion });
|
|
584
574
|
} else {
|
|
585
575
|
try log.activate(ident, .Confusion);
|
|
586
576
|
|
|
@@ -633,7 +623,7 @@ fn beforeMove(
|
|
|
633
623
|
volatiles.Binding = false;
|
|
634
624
|
options.chance.clearDurations(player, null);
|
|
635
625
|
// GLITCH: Invulnerable is not cleared, resulting in permanent Fly/Dig invulnerability
|
|
636
|
-
try log.cant(ident, .Paralysis);
|
|
626
|
+
try log.cant(.{ ident, Cant.Paralysis });
|
|
637
627
|
return .done;
|
|
638
628
|
}
|
|
639
629
|
|
|
@@ -664,13 +654,13 @@ fn beforeMove(
|
|
|
664
654
|
}
|
|
665
655
|
|
|
666
656
|
volatiles.Bide = false;
|
|
667
|
-
try log.end(ident, .Bide);
|
|
657
|
+
try log.end(.{ ident, End.Bide });
|
|
668
658
|
|
|
669
659
|
battle.last_damage = volatiles.state *% 2;
|
|
670
660
|
volatiles.state = 0;
|
|
671
661
|
|
|
672
662
|
if (battle.last_damage == 0) {
|
|
673
|
-
try log.fail(ident, .None);
|
|
663
|
+
try log.fail(.{ ident, Fail.None });
|
|
674
664
|
return .done;
|
|
675
665
|
}
|
|
676
666
|
|
|
@@ -691,7 +681,6 @@ fn beforeMove(
|
|
|
691
681
|
|
|
692
682
|
if (volatiles.Thrashing) {
|
|
693
683
|
assert(volatiles.attacks > 0);
|
|
694
|
-
assert(from != null);
|
|
695
684
|
|
|
696
685
|
if (options.calc.modify(player, .attacking)) |extend| {
|
|
697
686
|
if (!extend) volatiles.attacks = 0;
|
|
@@ -703,7 +692,7 @@ fn beforeMove(
|
|
|
703
692
|
if (!showdown and handleThrashing(battle, active, player, options)) {
|
|
704
693
|
try log.start(battle.active(player), .ConfusionSilent);
|
|
705
694
|
}
|
|
706
|
-
try log.move(ident, side.last_selected_move, battle.active(player.foe())
|
|
695
|
+
try log.move(.{ ident, side.last_selected_move, battle.active(player.foe()) });
|
|
707
696
|
if (showdown) {
|
|
708
697
|
// This shouldn't actually set last_used_move, but Pokémon Showdown sets last
|
|
709
698
|
// used in useMove and doesn't have the notion of skipping canMove semantics
|
|
@@ -725,12 +714,7 @@ fn beforeMove(
|
|
|
725
714
|
}
|
|
726
715
|
try options.chance.binding(player, volatiles.attacks);
|
|
727
716
|
|
|
728
|
-
try log.move(
|
|
729
|
-
battle.active(player),
|
|
730
|
-
side.last_selected_move,
|
|
731
|
-
battle.active(player.foe()),
|
|
732
|
-
side.last_selected_move,
|
|
733
|
-
);
|
|
717
|
+
try log.move(.{ ident, side.last_selected_move, battle.active(player.foe()) });
|
|
734
718
|
if (showdown or battle.last_damage != 0) {
|
|
735
719
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
736
720
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -750,7 +734,7 @@ fn canMove(
|
|
|
750
734
|
mslot: u4,
|
|
751
735
|
auto: bool,
|
|
752
736
|
skip_pp: bool,
|
|
753
|
-
from:
|
|
737
|
+
from: Move,
|
|
754
738
|
residual: *bool,
|
|
755
739
|
options: anytype,
|
|
756
740
|
) !bool {
|
|
@@ -758,33 +742,22 @@ fn canMove(
|
|
|
758
742
|
const player_ident = battle.active(player);
|
|
759
743
|
const move = Move.get(side.last_selected_move);
|
|
760
744
|
|
|
761
|
-
const foe_last = battle.foe(player).last_used_move;
|
|
762
|
-
const special = from != null and (from.? == .Metronome and
|
|
763
|
-
(from.? == .MirrorMove and !(foe_last == .None or foe_last == .MirrorMove)));
|
|
764
|
-
|
|
765
745
|
var skip = skip_pp;
|
|
766
746
|
if (side.active.volatiles.Charging) {
|
|
767
747
|
side.active.volatiles.Charging = false;
|
|
768
748
|
side.active.volatiles.Invulnerable = false;
|
|
769
749
|
} else if (move.effect == .Charge) {
|
|
770
|
-
try options.log.move(player_ident, side.last_selected_move,
|
|
750
|
+
try options.log.move(.{ player_ident, side.last_selected_move, ID{}, from });
|
|
771
751
|
setCounterable(battle, player, side, move);
|
|
772
752
|
try Effects.charge(battle, player, options);
|
|
773
753
|
return false;
|
|
774
754
|
}
|
|
775
755
|
|
|
776
|
-
|
|
756
|
+
side.last_used_move = side.last_selected_move;
|
|
777
757
|
if (!skip) decrementPP(side, mslot, auto);
|
|
778
758
|
|
|
779
759
|
const target = if (move.target == .Self) player else player.foe();
|
|
780
|
-
|
|
781
|
-
// don't have space to be able to track it in all circumstances and thus usually try to infer it
|
|
782
|
-
// from side.last_used_move. However, this gets reset by the opponent switching or fainting,
|
|
783
|
-
// meaning sometimes we lose this information. from is still set because it not being null is
|
|
784
|
-
// relevant for control flow, but we don't have anything useful to put in the log. This is
|
|
785
|
-
// regrettable, but this information is more "nice to have" than required
|
|
786
|
-
const f = if (from != null and from.? == .None) null else from;
|
|
787
|
-
try options.log.move(player_ident, side.last_selected_move, battle.active(target), f);
|
|
760
|
+
try options.log.move(.{ player_ident, side.last_selected_move, battle.active(target), from });
|
|
788
761
|
setCounterable(battle, player, side, move);
|
|
789
762
|
|
|
790
763
|
if (move.effect.onBegin()) {
|
|
@@ -822,12 +795,14 @@ fn decrementPP(side: *Side, mslot: u4, auto: bool) void {
|
|
|
822
795
|
assert(!volatiles.Rage and !volatiles.Thrashing and !volatiles.MultiHit);
|
|
823
796
|
if (volatiles.Bide) return;
|
|
824
797
|
|
|
825
|
-
|
|
826
|
-
|
|
798
|
+
var move_slot = active.move(mslot);
|
|
799
|
+
assert(move_slot.pp > 0 or auto);
|
|
800
|
+
move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
|
|
827
801
|
if (volatiles.Transform) return;
|
|
828
802
|
|
|
829
|
-
|
|
830
|
-
|
|
803
|
+
move_slot = side.stored().move(mslot);
|
|
804
|
+
assert(move_slot.pp > 0 or auto);
|
|
805
|
+
move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
|
|
831
806
|
assert(active.move(mslot).pp == side.stored().move(mslot).pp);
|
|
832
807
|
}
|
|
833
808
|
|
|
@@ -959,16 +934,16 @@ fn doMove(
|
|
|
959
934
|
ohko = (!showdown or (!immune and !invulnerable)) and
|
|
960
935
|
move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe;
|
|
961
936
|
if (ohko) {
|
|
962
|
-
try log.immune(foe_ident, .OHKO);
|
|
937
|
+
try log.immune(.{ foe_ident, Immune.OHKO });
|
|
963
938
|
} else if (immune and !invulnerable and (!showdown or move.effect != .Binding)) {
|
|
964
|
-
try log.immune(foe_ident, .None);
|
|
939
|
+
try log.immune(.{ foe_ident, Immune.None });
|
|
965
940
|
} else if (mist) {
|
|
966
941
|
if (!foe.active.volatiles.Substitute) try log.activate(foe_ident, .Mist);
|
|
967
|
-
try log.fail(foe_ident, .None);
|
|
942
|
+
try log.fail(.{ foe_ident, Fail.None });
|
|
968
943
|
} else {
|
|
969
944
|
if (showdown or !zero) try options.chance.commit(player, .miss);
|
|
970
|
-
try log.lastmiss();
|
|
971
|
-
try log.miss(battle.active(player));
|
|
945
|
+
try log.lastmiss(.{});
|
|
946
|
+
try log.miss(.{battle.active(player)});
|
|
972
947
|
}
|
|
973
948
|
if (move.effect == .JumpKick) {
|
|
974
949
|
// Recoil is supposed to be damage/8 but damage will always be 0 here
|
|
@@ -1004,15 +979,15 @@ fn doMove(
|
|
|
1004
979
|
var hit: u4 = 0;
|
|
1005
980
|
while (hit < hits) {
|
|
1006
981
|
if (hit == 0) {
|
|
1007
|
-
if (crit) try log.crit(battle.active(player.foe()));
|
|
982
|
+
if (crit) try log.crit(.{battle.active(player.foe())});
|
|
1008
983
|
if (effectiveness > Effectiveness.neutral) {
|
|
1009
|
-
try log.supereffective(battle.active(player.foe()));
|
|
984
|
+
try log.supereffective(.{battle.active(player.foe())});
|
|
1010
985
|
} else if (effectiveness < Effectiveness.neutral) {
|
|
1011
|
-
try log.resisted(battle.active(player.foe()));
|
|
986
|
+
try log.resisted(.{battle.active(player.foe())});
|
|
1012
987
|
}
|
|
1013
988
|
}
|
|
1014
989
|
if (!skip) nullified = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
1015
|
-
if (hit == 0 and ohko) try log.ohko();
|
|
990
|
+
if (hit == 0 and ohko) try log.ohko(.{});
|
|
1016
991
|
hit += 1;
|
|
1017
992
|
if (foe.stored().hp == 0) break;
|
|
1018
993
|
if (!late and (!sub or move.effect == .Explode)) {
|
|
@@ -1029,7 +1004,7 @@ fn doMove(
|
|
|
1029
1004
|
if (showdown and move.effect == .Twineedle and !nullified and foe.stored().hp > 0) {
|
|
1030
1005
|
try Effects.poison(battle, player, Move.get(.PoisonSting), options);
|
|
1031
1006
|
}
|
|
1032
|
-
try log.hitcount(battle.active(player.foe()), hit);
|
|
1007
|
+
try log.hitcount(.{ battle.active(player.foe()), hit });
|
|
1033
1008
|
} else if (showdown) {
|
|
1034
1009
|
// This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
|
|
1035
1010
|
if (move.effect == .Binding) {
|
|
@@ -1041,7 +1016,7 @@ fn doMove(
|
|
|
1041
1016
|
if (sub) {
|
|
1042
1017
|
try log.activate(battle.active(player.foe()), .Substitute);
|
|
1043
1018
|
} else {
|
|
1044
|
-
try log.damage(battle.active(player.foe()), foe.stored(), .None);
|
|
1019
|
+
try log.damage(.{ battle.active(player.foe()), foe.stored(), Damage.None });
|
|
1045
1020
|
try buildRage(battle, player.foe(), options);
|
|
1046
1021
|
}
|
|
1047
1022
|
return null;
|
|
@@ -1239,7 +1214,7 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1239
1214
|
const foe = battle.foe(player);
|
|
1240
1215
|
|
|
1241
1216
|
if (battle.last_damage == 0) {
|
|
1242
|
-
try options.log.fail(battle.active(player), .None);
|
|
1217
|
+
try options.log.fail(.{ battle.active(player), Fail.None });
|
|
1243
1218
|
return null;
|
|
1244
1219
|
}
|
|
1245
1220
|
|
|
@@ -1257,14 +1232,14 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1257
1232
|
foe_last_selected_move.type == .Fighting);
|
|
1258
1233
|
|
|
1259
1234
|
if (!used and !selected) {
|
|
1260
|
-
try options.log.fail(battle.active(player), .None);
|
|
1235
|
+
try options.log.fail(.{ battle.active(player), Fail.None });
|
|
1261
1236
|
return null;
|
|
1262
1237
|
}
|
|
1263
1238
|
|
|
1264
1239
|
if (!used or !selected) {
|
|
1265
1240
|
// GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
|
|
1266
1241
|
if (!showdown) return Result.Error;
|
|
1267
|
-
try options.log.fail(battle.active(player), .None);
|
|
1242
|
+
try options.log.fail(.{ battle.active(player), Fail.None });
|
|
1268
1243
|
return null;
|
|
1269
1244
|
}
|
|
1270
1245
|
|
|
@@ -1300,7 +1275,7 @@ fn applyDamage(
|
|
|
1300
1275
|
subbed.active.volatiles.substitute = 0;
|
|
1301
1276
|
subbed.active.volatiles.Substitute = false;
|
|
1302
1277
|
// battle.last_damage is not updated with the amount of HP the Substitute had
|
|
1303
|
-
try options.log.end(battle.active(sub_player), .Substitute);
|
|
1278
|
+
try options.log.end(.{ battle.active(sub_player), End.Substitute });
|
|
1304
1279
|
options.calc.capped(target_player);
|
|
1305
1280
|
return true;
|
|
1306
1281
|
} else {
|
|
@@ -1313,7 +1288,7 @@ fn applyDamage(
|
|
|
1313
1288
|
|
|
1314
1289
|
if (battle.last_damage > target.stored().hp) battle.last_damage = target.stored().hp;
|
|
1315
1290
|
target.stored().hp -= battle.last_damage;
|
|
1316
|
-
try options.log.damage(battle.active(target_player), target.stored(), reason);
|
|
1291
|
+
try options.log.damage(.{ battle.active(target_player), target.stored(), reason });
|
|
1317
1292
|
return false;
|
|
1318
1293
|
}
|
|
1319
1294
|
|
|
@@ -1332,7 +1307,7 @@ fn mirrorMove(
|
|
|
1332
1307
|
side.last_selected_move = foe.last_used_move;
|
|
1333
1308
|
|
|
1334
1309
|
if (foe.last_used_move == .None or foe.last_used_move == .MirrorMove) {
|
|
1335
|
-
try options.log.fail(battle.active(player), .None);
|
|
1310
|
+
try options.log.fail(.{ battle.active(player), Fail.None });
|
|
1336
1311
|
return null;
|
|
1337
1312
|
} else if (!showdown or foe.last_used_move != .Struggle) {
|
|
1338
1313
|
incrementPP(side, mslot);
|
|
@@ -1375,11 +1350,11 @@ fn checkHit(battle: anytype, player: Player, move: Move.Data, options: anytype)
|
|
|
1375
1350
|
assert(!showdown);
|
|
1376
1351
|
const foe_ident = battle.active(player.foe());
|
|
1377
1352
|
try options.log.activate(foe_ident, .Mist);
|
|
1378
|
-
try options.log.fail(foe_ident, .None);
|
|
1353
|
+
try options.log.fail(.{ foe_ident, Fail.None });
|
|
1379
1354
|
} else {
|
|
1380
1355
|
try options.chance.commit(player, .miss);
|
|
1381
|
-
try options.log.lastmiss();
|
|
1382
|
-
try options.log.miss(battle.active(player));
|
|
1356
|
+
try options.log.lastmiss(.{});
|
|
1357
|
+
try options.log.miss(.{battle.active(player)});
|
|
1383
1358
|
}
|
|
1384
1359
|
|
|
1385
1360
|
return false;
|
|
@@ -1470,9 +1445,8 @@ fn checkFaint(
|
|
|
1470
1445
|
const foe_fainted = foe.stored().hp == 0;
|
|
1471
1446
|
|
|
1472
1447
|
const player_out = findFirstAlive(side) == 0;
|
|
1473
|
-
const foe_out = findFirstAlive(foe) == 0;
|
|
1474
|
-
const
|
|
1475
|
-
const more = tie or player_out or foe_out;
|
|
1448
|
+
const foe_out = foe_fainted and findFirstAlive(foe) == 0;
|
|
1449
|
+
const more = player_out or foe_out;
|
|
1476
1450
|
|
|
1477
1451
|
if (try faint(battle, player, !(more or foe_fainted), options)) |r| return r;
|
|
1478
1452
|
if (foe_fainted) if (try faint(battle, player.foe(), !more, options)) |r| return r;
|
|
@@ -1480,14 +1454,14 @@ fn checkFaint(
|
|
|
1480
1454
|
assert(!side.active.volatiles.MultiHit);
|
|
1481
1455
|
assert(!foe.active.volatiles.MultiHit);
|
|
1482
1456
|
|
|
1483
|
-
if (
|
|
1484
|
-
try options.log.tie();
|
|
1457
|
+
if (player_out and foe_out) {
|
|
1458
|
+
try options.log.tie(.{});
|
|
1485
1459
|
return Result.Tie;
|
|
1486
1460
|
} else if (player_out) {
|
|
1487
|
-
try options.log.win(player.foe());
|
|
1461
|
+
try options.log.win(.{player.foe()});
|
|
1488
1462
|
return if (player == .P1) Result.Lose else Result.Win;
|
|
1489
1463
|
} else if (foe_out) {
|
|
1490
|
-
try options.log.win(player);
|
|
1464
|
+
try options.log.win(.{player});
|
|
1491
1465
|
return if (player == .P1) Result.Win else Result.Lose;
|
|
1492
1466
|
}
|
|
1493
1467
|
|
|
@@ -1529,7 +1503,7 @@ fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result
|
|
|
1529
1503
|
side.stored().stats.spe;
|
|
1530
1504
|
}
|
|
1531
1505
|
|
|
1532
|
-
try options.log.faint(battle.active(player), done);
|
|
1506
|
+
try options.log.faint(.{ battle.active(player), done });
|
|
1533
1507
|
options.calc.capped(player);
|
|
1534
1508
|
return null;
|
|
1535
1509
|
}
|
|
@@ -1551,8 +1525,8 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1551
1525
|
|
|
1552
1526
|
if (!showdown or damage > 0) {
|
|
1553
1527
|
stored.hp -= damage;
|
|
1554
|
-
// Pokémon Showdown uses
|
|
1555
|
-
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
1528
|
+
// Pokémon Showdown uses damage [of] here but its not relevant in Generation I
|
|
1529
|
+
try options.log.damage(.{ ident, stored, if (brn) Damage.Burn else Damage.Poison });
|
|
1556
1530
|
}
|
|
1557
1531
|
}
|
|
1558
1532
|
|
|
@@ -1577,14 +1551,14 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1577
1551
|
const amount = @min(damage, stored.hp);
|
|
1578
1552
|
stored.hp -= amount;
|
|
1579
1553
|
|
|
1580
|
-
// As above, Pokémon Showdown uses
|
|
1581
|
-
if (amount > 0) try options.log.damage(ident, stored, .LeechSeed);
|
|
1554
|
+
// As above, Pokémon Showdown uses damage [of] but its not relevant
|
|
1555
|
+
if (amount > 0) try options.log.damage(.{ ident, stored, Damage.LeechSeed });
|
|
1582
1556
|
|
|
1583
1557
|
const before = foe_stored.hp;
|
|
1584
1558
|
// Uncapped damage is added back to the foe
|
|
1585
1559
|
foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
|
|
1586
1560
|
// Pokémon Showdown uses the less specific heal here instead of drain... because reasons?
|
|
1587
|
-
if (foe_stored.hp > before) try options.log.heal(foe_ident, foe_stored, .Silent);
|
|
1561
|
+
if (foe_stored.hp > before) try options.log.heal(.{ foe_ident, foe_stored, Heal.Silent });
|
|
1588
1562
|
}
|
|
1589
1563
|
}
|
|
1590
1564
|
|
|
@@ -1595,18 +1569,18 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1595
1569
|
battle.turn += 1;
|
|
1596
1570
|
|
|
1597
1571
|
if (showdown and pkmn.options.ebc and checkEBC(battle)) {
|
|
1598
|
-
try options.log.tie();
|
|
1572
|
+
try options.log.tie(.{});
|
|
1599
1573
|
return Result.Tie;
|
|
1600
1574
|
}
|
|
1601
1575
|
|
|
1602
1576
|
if (showdown and battle.turn >= 1000) {
|
|
1603
|
-
try options.log.tie();
|
|
1577
|
+
try options.log.tie(.{});
|
|
1604
1578
|
return Result.Tie;
|
|
1605
1579
|
} else if (battle.turn >= 65535) {
|
|
1606
1580
|
return Result.Error;
|
|
1607
1581
|
}
|
|
1608
1582
|
|
|
1609
|
-
try options.log.turn(battle.turn);
|
|
1583
|
+
try options.log.turn(.{battle.turn});
|
|
1610
1584
|
|
|
1611
1585
|
return Result.Default;
|
|
1612
1586
|
}
|
|
@@ -1666,9 +1640,9 @@ fn checkEBC(battle: anytype) bool {
|
|
|
1666
1640
|
}
|
|
1667
1641
|
|
|
1668
1642
|
fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
1669
|
-
if (side.active.volatiles.
|
|
1643
|
+
if (side.active.volatiles.disable_move != 0) {
|
|
1670
1644
|
// A Pokémon that transforms after being disabled may end up with less move slots
|
|
1671
|
-
const m = side.active.moves[side.active.volatiles.
|
|
1645
|
+
const m = side.active.moves[side.active.volatiles.disable_move - 1].id;
|
|
1672
1646
|
// side.last_selected_move can be Struggle here on Pokemon Showdown we need an extra check
|
|
1673
1647
|
const last = if (showdown and m == .Bide and side.active.volatiles.Bide)
|
|
1674
1648
|
m
|
|
@@ -1676,14 +1650,14 @@ fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
|
1676
1650
|
side.last_selected_move;
|
|
1677
1651
|
if (m != .None and m == last) {
|
|
1678
1652
|
side.active.volatiles.Charging = false;
|
|
1679
|
-
try options.log.
|
|
1653
|
+
try options.log.cant(.{ ident, Cant.Disable, last });
|
|
1680
1654
|
return true;
|
|
1681
1655
|
}
|
|
1682
1656
|
}
|
|
1683
1657
|
return false;
|
|
1684
1658
|
}
|
|
1685
1659
|
|
|
1686
|
-
|
|
1660
|
+
fn buildRage(battle: anytype, who: Player, options: anytype) !void {
|
|
1687
1661
|
const side = battle.side(who);
|
|
1688
1662
|
if (side.active.volatiles.Rage and side.active.boosts.atk < 6) {
|
|
1689
1663
|
try Effects.boost(battle, who, Move.get(.Rage), options);
|
|
@@ -1701,7 +1675,7 @@ fn handleThrashing(battle: anytype, active: *ActivePokemon, player: Player, opti
|
|
|
1701
1675
|
return true;
|
|
1702
1676
|
}
|
|
1703
1677
|
|
|
1704
|
-
|
|
1678
|
+
fn onBegin(
|
|
1705
1679
|
battle: anytype,
|
|
1706
1680
|
player: Player,
|
|
1707
1681
|
move: Move.Data,
|
|
@@ -1731,7 +1705,7 @@ inline fn onBegin(
|
|
|
1731
1705
|
};
|
|
1732
1706
|
}
|
|
1733
1707
|
|
|
1734
|
-
|
|
1708
|
+
fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1735
1709
|
assert(move.effect.onEnd());
|
|
1736
1710
|
return switch (move.effect) {
|
|
1737
1711
|
.Bide => Effects.bide(battle, player, options),
|
|
@@ -1747,7 +1721,7 @@ inline fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anyty
|
|
|
1747
1721
|
};
|
|
1748
1722
|
}
|
|
1749
1723
|
|
|
1750
|
-
|
|
1724
|
+
fn alwaysHappens(
|
|
1751
1725
|
battle: anytype,
|
|
1752
1726
|
player: Player,
|
|
1753
1727
|
move: Move.Data,
|
|
@@ -1766,7 +1740,7 @@ inline fn alwaysHappens(
|
|
|
1766
1740
|
};
|
|
1767
1741
|
}
|
|
1768
1742
|
|
|
1769
|
-
|
|
1743
|
+
fn moveEffect(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1770
1744
|
return switch (move.effect) {
|
|
1771
1745
|
.BurnChance1, .BurnChance2 => Effects.burnChance(battle, player, move, options),
|
|
1772
1746
|
.ConfusionChance => Effects.confusion(battle, player, move, options),
|
|
@@ -1807,11 +1781,11 @@ pub const Effects = struct {
|
|
|
1807
1781
|
// GLITCH: Freeze top move selection desync can occur if thawed Pokémon is slower
|
|
1808
1782
|
if (Status.is(foe_stored.status, .FRZ)) {
|
|
1809
1783
|
assert(move.type == .Fire);
|
|
1810
|
-
try options.log.curestatus(
|
|
1784
|
+
try options.log.curestatus(.{
|
|
1811
1785
|
battle.active(player.foe()),
|
|
1812
1786
|
foe_stored.status,
|
|
1813
|
-
.Message,
|
|
1814
|
-
);
|
|
1787
|
+
CureStatus.Message,
|
|
1788
|
+
});
|
|
1815
1789
|
foe_stored.status = 0;
|
|
1816
1790
|
}
|
|
1817
1791
|
return;
|
|
@@ -1825,7 +1799,8 @@ pub const Effects = struct {
|
|
|
1825
1799
|
foe_stored.status = Status.init(.BRN);
|
|
1826
1800
|
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
1827
1801
|
|
|
1828
|
-
|
|
1802
|
+
const reason: protocol.Status = .None;
|
|
1803
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, reason });
|
|
1829
1804
|
}
|
|
1830
1805
|
|
|
1831
1806
|
fn charge(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -1835,8 +1810,8 @@ pub const Effects = struct {
|
|
|
1835
1810
|
volatiles.Charging = true;
|
|
1836
1811
|
const move = side.last_selected_move;
|
|
1837
1812
|
if (move == .Fly or move == .Dig) volatiles.Invulnerable = true;
|
|
1838
|
-
try options.log.laststill();
|
|
1839
|
-
try options.log.prepare(battle.active(player), move);
|
|
1813
|
+
try options.log.laststill(.{});
|
|
1814
|
+
try options.log.prepare(.{ battle.active(player), move });
|
|
1840
1815
|
}
|
|
1841
1816
|
|
|
1842
1817
|
fn confusion(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1850,11 +1825,11 @@ pub const Effects = struct {
|
|
|
1850
1825
|
if (!try checkHit(battle, player, move, options)) {
|
|
1851
1826
|
return;
|
|
1852
1827
|
} else if (sub) {
|
|
1853
|
-
return options.log.fail(battle.active(player.foe()), .None);
|
|
1828
|
+
return options.log.fail(.{ battle.active(player.foe()), Fail.None });
|
|
1854
1829
|
}
|
|
1855
1830
|
} else {
|
|
1856
1831
|
if (sub) {
|
|
1857
|
-
return options.log.fail(battle.active(player.foe()), .None);
|
|
1832
|
+
return options.log.fail(.{ battle.active(player.foe()), Fail.None });
|
|
1858
1833
|
} else if (!try checkHit(battle, player, move, options)) {
|
|
1859
1834
|
return;
|
|
1860
1835
|
}
|
|
@@ -1873,8 +1848,8 @@ pub const Effects = struct {
|
|
|
1873
1848
|
const foe = battle.foe(player);
|
|
1874
1849
|
|
|
1875
1850
|
if (foe.active.volatiles.Invulnerable) {
|
|
1876
|
-
try options.log.lastmiss();
|
|
1877
|
-
return options.log.miss(battle.active(player));
|
|
1851
|
+
try options.log.lastmiss(.{});
|
|
1852
|
+
return options.log.miss(.{battle.active(player)});
|
|
1878
1853
|
}
|
|
1879
1854
|
|
|
1880
1855
|
battle.side(player).active.types = foe.active.types;
|
|
@@ -1896,8 +1871,8 @@ pub const Effects = struct {
|
|
|
1896
1871
|
try options.chance.commit(player, .hit);
|
|
1897
1872
|
}
|
|
1898
1873
|
|
|
1899
|
-
if (volatiles.
|
|
1900
|
-
try options.log.fail(foe_ident, .None);
|
|
1874
|
+
if (volatiles.disable_move != 0) {
|
|
1875
|
+
try options.log.fail(.{ foe_ident, Fail.None });
|
|
1901
1876
|
return null;
|
|
1902
1877
|
}
|
|
1903
1878
|
|
|
@@ -1913,7 +1888,7 @@ pub const Effects = struct {
|
|
|
1913
1888
|
// Technically this is still considered simply a "miss" on the cartridge,
|
|
1914
1889
|
// but diverging from Pokémon Showdown here would mostly just be pedantic
|
|
1915
1890
|
if (n == 0) {
|
|
1916
|
-
try options.log.fail(foe_ident, .None);
|
|
1891
|
+
try options.log.fail(.{ foe_ident, Fail.None });
|
|
1917
1892
|
return null;
|
|
1918
1893
|
} else if (err) {
|
|
1919
1894
|
// GLITCH: Transform + Mirror Move / Metronome PP softlock
|
|
@@ -1921,18 +1896,18 @@ pub const Effects = struct {
|
|
|
1921
1896
|
return Result.Error;
|
|
1922
1897
|
}
|
|
1923
1898
|
|
|
1924
|
-
volatiles.
|
|
1899
|
+
volatiles.disable_move =
|
|
1925
1900
|
@intCast(try Rolls.moveSlot(battle, player, &foe.active.moves, n, options));
|
|
1926
|
-
volatiles.
|
|
1901
|
+
volatiles.disable_duration = Rolls.disableDuration(battle, player, options);
|
|
1927
1902
|
|
|
1928
|
-
const id = foe.active.move(volatiles.
|
|
1903
|
+
const id = foe.active.move(volatiles.disable_move).id;
|
|
1929
1904
|
try options.log.startEffect(foe_ident, .Disable, id);
|
|
1930
1905
|
return null;
|
|
1931
1906
|
}
|
|
1932
1907
|
|
|
1933
1908
|
fn drainHP(battle: anytype, player: Player, options: anytype) !void {
|
|
1934
|
-
var
|
|
1935
|
-
|
|
1909
|
+
var stored = battle.side(player).stored();
|
|
1910
|
+
const ident = battle.active(player);
|
|
1936
1911
|
|
|
1937
1912
|
if (battle.last_damage == 0) {
|
|
1938
1913
|
assert(showdown);
|
|
@@ -1945,7 +1920,7 @@ pub const Effects = struct {
|
|
|
1945
1920
|
if (stored.hp == stored.stats.hp) return;
|
|
1946
1921
|
stored.hp = @min(stored.stats.hp, stored.hp + drain);
|
|
1947
1922
|
|
|
1948
|
-
try options.log.
|
|
1923
|
+
try options.log.heal(.{ ident, stored, Heal.Drain, battle.active(player.foe()) });
|
|
1949
1924
|
}
|
|
1950
1925
|
|
|
1951
1926
|
fn explode(battle: anytype, player: Player) !void {
|
|
@@ -1999,7 +1974,7 @@ pub const Effects = struct {
|
|
|
1999
1974
|
foe_stored.status = Status.init(.FRZ);
|
|
2000
1975
|
// GLITCH: Hyper Beam recharging status is not cleared
|
|
2001
1976
|
|
|
2002
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
1977
|
+
try options.log.status(.{ foe_ident, foe_stored.status, protocol.Status.None });
|
|
2003
1978
|
}
|
|
2004
1979
|
|
|
2005
1980
|
fn haze(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -2020,7 +1995,7 @@ pub const Effects = struct {
|
|
|
2020
1995
|
foe.active.stats = foe_stored.stats;
|
|
2021
1996
|
|
|
2022
1997
|
try log.activate(player_ident, .Haze);
|
|
2023
|
-
try log.clearallboost();
|
|
1998
|
+
try log.clearallboost(.{});
|
|
2024
1999
|
|
|
2025
2000
|
// Pokémon Showdown clears P1 then P2 instead of status -> side -> foe
|
|
2026
2001
|
if (showdown) {
|
|
@@ -2028,11 +2003,11 @@ pub const Effects = struct {
|
|
|
2028
2003
|
const p: Player = @enumFromInt(i);
|
|
2029
2004
|
// Pokémon Showdown incorrectly does not prevent sleep/freeze from moving
|
|
2030
2005
|
if (p != player and Status.any(s.stored().status)) {
|
|
2031
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
2006
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, CureStatus.Silent });
|
|
2032
2007
|
s.stored().status = 0;
|
|
2033
2008
|
} else if (showdown and s.stored().status == Status.TOX) {
|
|
2034
2009
|
s.stored().status = Status.init(.PSN);
|
|
2035
|
-
try log.status(battle.active(p), s.stored().status, .None);
|
|
2010
|
+
try log.status(.{ battle.active(p), s.stored().status, protocol.Status.None });
|
|
2036
2011
|
}
|
|
2037
2012
|
try clearVolatiles(battle, p, p != player, options);
|
|
2038
2013
|
}
|
|
@@ -2041,7 +2016,7 @@ pub const Effects = struct {
|
|
|
2041
2016
|
if (Status.is(foe_stored.status, .FRZ) or Status.is(foe_stored.status, .SLP)) {
|
|
2042
2017
|
foe.last_selected_move = .SKIP_TURN;
|
|
2043
2018
|
}
|
|
2044
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
2019
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, CureStatus.Silent });
|
|
2045
2020
|
foe_stored.status = 0;
|
|
2046
2021
|
}
|
|
2047
2022
|
try clearVolatiles(battle, player, false, options);
|
|
@@ -2057,7 +2032,7 @@ pub const Effects = struct {
|
|
|
2057
2032
|
// GLITCH: HP recovery move failure glitches
|
|
2058
2033
|
const delta = stored.stats.hp - stored.hp;
|
|
2059
2034
|
if (delta == 0 or (delta & 255 == 255 and stored.hp % 256 != 0)) {
|
|
2060
|
-
return
|
|
2035
|
+
return options.log.fail(.{ ident, Fail.None });
|
|
2061
2036
|
}
|
|
2062
2037
|
|
|
2063
2038
|
const rest = side.last_selected_move == .Rest;
|
|
@@ -2065,17 +2040,17 @@ pub const Effects = struct {
|
|
|
2065
2040
|
// Adding the sleep status runs the sleep condition handler to roll duration
|
|
2066
2041
|
if (showdown) battle.rng.advance(1);
|
|
2067
2042
|
stored.status = Status.slf(2);
|
|
2068
|
-
try options.log.
|
|
2043
|
+
try options.log.status(.{ ident, stored.status, protocol.Status.From, Move.Rest });
|
|
2069
2044
|
stored.hp = stored.stats.hp;
|
|
2070
2045
|
} else {
|
|
2071
2046
|
stored.hp = @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2));
|
|
2072
2047
|
}
|
|
2073
|
-
try options.log.heal(ident, stored, if (rest) Heal.Silent else Heal.None);
|
|
2048
|
+
try options.log.heal(.{ ident, stored, if (rest) Heal.Silent else Heal.None });
|
|
2074
2049
|
}
|
|
2075
2050
|
|
|
2076
2051
|
fn hyperBeam(battle: anytype, player: Player, options: anytype) !void {
|
|
2077
2052
|
battle.side(player).active.volatiles.Recharging = true;
|
|
2078
|
-
try options.log.mustrecharge(battle.active(player));
|
|
2053
|
+
try options.log.mustrecharge(.{battle.active(player)});
|
|
2079
2054
|
}
|
|
2080
2055
|
|
|
2081
2056
|
fn leechSeed(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2084,15 +2059,15 @@ pub const Effects = struct {
|
|
|
2084
2059
|
if (showdown) {
|
|
2085
2060
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2086
2061
|
if (!foe.active.volatiles.Invulnerable and foe.active.types.includes(.Grass)) {
|
|
2087
|
-
return options.log.immune(battle.active(player.foe()), .None);
|
|
2062
|
+
return options.log.immune(.{ battle.active(player.foe()), Immune.None });
|
|
2088
2063
|
}
|
|
2089
2064
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2090
2065
|
if (foe.active.volatiles.LeechSeed) return;
|
|
2091
2066
|
} else {
|
|
2092
2067
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2093
2068
|
if (foe.active.types.includes(.Grass) or foe.active.volatiles.LeechSeed) {
|
|
2094
|
-
try options.log.lastmiss();
|
|
2095
|
-
return options.log.miss(battle.active(player));
|
|
2069
|
+
try options.log.lastmiss(.{});
|
|
2070
|
+
return options.log.miss(.{battle.active(player)});
|
|
2096
2071
|
}
|
|
2097
2072
|
}
|
|
2098
2073
|
|
|
@@ -2106,7 +2081,7 @@ pub const Effects = struct {
|
|
|
2106
2081
|
var side = battle.side(player);
|
|
2107
2082
|
|
|
2108
2083
|
if (side.active.volatiles.LightScreen) {
|
|
2109
|
-
return options.log.fail(battle.active(player), .None);
|
|
2084
|
+
return options.log.fail(.{ battle.active(player), Fail.None });
|
|
2110
2085
|
}
|
|
2111
2086
|
side.active.volatiles.LightScreen = true;
|
|
2112
2087
|
|
|
@@ -2139,7 +2114,7 @@ pub const Effects = struct {
|
|
|
2139
2114
|
// Invulnerable foes or 1/256 miss can trigger |-miss| instead of |-fail|
|
|
2140
2115
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2141
2116
|
try options.chance.commit(player, .hit);
|
|
2142
|
-
return
|
|
2117
|
+
return options.log.fail(.{ battle.active(player.foe()), Fail.None });
|
|
2143
2118
|
}
|
|
2144
2119
|
}
|
|
2145
2120
|
if (!try checkHit(battle, player, move, options)) return;
|
|
@@ -2183,17 +2158,18 @@ pub const Effects = struct {
|
|
|
2183
2158
|
|
|
2184
2159
|
if (showdown) {
|
|
2185
2160
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2186
|
-
if (immune and !foe.active.volatiles.Invulnerable)
|
|
2187
|
-
|
|
2161
|
+
if (immune and !foe.active.volatiles.Invulnerable) {
|
|
2162
|
+
return log.immune(.{ foe_ident, Immune.None });
|
|
2163
|
+
} else if (!try checkHit(battle, player, move, options)) return;
|
|
2188
2164
|
}
|
|
2189
2165
|
if (Status.any(foe_stored.status)) {
|
|
2190
|
-
return log.fail(
|
|
2166
|
+
return log.fail(.{
|
|
2191
2167
|
foe_ident,
|
|
2192
|
-
if (Status.is(foe_stored.status, .PAR)) .Paralysis else .None,
|
|
2193
|
-
);
|
|
2168
|
+
if (Status.is(foe_stored.status, .PAR)) Fail.Paralysis else Fail.None,
|
|
2169
|
+
});
|
|
2194
2170
|
}
|
|
2195
2171
|
if (!showdown) {
|
|
2196
|
-
if (immune) return log.immune(foe_ident, .None);
|
|
2172
|
+
if (immune) return log.immune(.{ foe_ident, Immune.None });
|
|
2197
2173
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2198
2174
|
}
|
|
2199
2175
|
try options.chance.commit(player, .hit);
|
|
@@ -2201,7 +2177,7 @@ pub const Effects = struct {
|
|
|
2201
2177
|
foe_stored.status = Status.init(.PAR);
|
|
2202
2178
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2203
2179
|
|
|
2204
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2180
|
+
try log.status(.{ foe_ident, foe_stored.status, protocol.Status.None });
|
|
2205
2181
|
}
|
|
2206
2182
|
|
|
2207
2183
|
fn paralyzeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2223,12 +2199,13 @@ pub const Effects = struct {
|
|
|
2223
2199
|
foe_stored.status = Status.init(.PAR);
|
|
2224
2200
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2225
2201
|
|
|
2226
|
-
|
|
2202
|
+
const reason: protocol.Status = .None;
|
|
2203
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, reason });
|
|
2227
2204
|
}
|
|
2228
2205
|
|
|
2229
2206
|
fn payDay(battle: anytype, player: Player, options: anytype) !void {
|
|
2230
2207
|
if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
|
|
2231
|
-
try options.log.fieldactivate();
|
|
2208
|
+
try options.log.fieldactivate(.{});
|
|
2232
2209
|
}
|
|
2233
2210
|
}
|
|
2234
2211
|
|
|
@@ -2243,23 +2220,23 @@ pub const Effects = struct {
|
|
|
2243
2220
|
return;
|
|
2244
2221
|
} else if (foe.active.volatiles.Substitute) {
|
|
2245
2222
|
if (move.effect != .Poison) return;
|
|
2246
|
-
return log.fail(foe_ident, .None);
|
|
2223
|
+
return log.fail(.{ foe_ident, Fail.None });
|
|
2247
2224
|
} else if (Status.any(foe_stored.status)) {
|
|
2248
2225
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2249
2226
|
// Pokémon Showdown considers Toxic to be a status even in Generation I and so
|
|
2250
2227
|
// will not include a fail reason for Toxic vs. Poison or vice-versa...
|
|
2251
|
-
return log.fail(foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2228
|
+
return log.fail(.{ foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2252
2229
|
if (!showdown)
|
|
2253
|
-
.Poison
|
|
2230
|
+
Fail.Poison
|
|
2254
2231
|
else if (toxic == (foe_stored.status == Status.TOX))
|
|
2255
|
-
if (toxic) .Toxic else .Poison
|
|
2232
|
+
if (toxic) Fail.Toxic else Fail.Poison
|
|
2256
2233
|
else
|
|
2257
|
-
.None
|
|
2234
|
+
Fail.None
|
|
2258
2235
|
else
|
|
2259
|
-
.None);
|
|
2236
|
+
Fail.None });
|
|
2260
2237
|
} else if (foe.active.types.includes(.Poison)) {
|
|
2261
2238
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2262
|
-
return log.immune(foe_ident, .None);
|
|
2239
|
+
return log.immune(.{ foe_ident, Immune.None });
|
|
2263
2240
|
}
|
|
2264
2241
|
|
|
2265
2242
|
if (move.effect == .Poison) {
|
|
@@ -2278,7 +2255,7 @@ pub const Effects = struct {
|
|
|
2278
2255
|
foe.active.volatiles.toxic = 0;
|
|
2279
2256
|
}
|
|
2280
2257
|
|
|
2281
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2258
|
+
try log.status(.{ foe_ident, foe_stored.status, protocol.Status.None });
|
|
2282
2259
|
}
|
|
2283
2260
|
|
|
2284
2261
|
fn rage(battle: anytype, player: Player) !void {
|
|
@@ -2298,19 +2275,21 @@ pub const Effects = struct {
|
|
|
2298
2275
|
@as(u8, if (side.last_selected_move == .Struggle) 2 else 4), 1));
|
|
2299
2276
|
stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
|
|
2300
2277
|
|
|
2301
|
-
try options.log.
|
|
2278
|
+
try options.log.damage(.{
|
|
2302
2279
|
battle.active(player),
|
|
2303
2280
|
stored,
|
|
2304
|
-
.RecoilOf,
|
|
2281
|
+
Damage.RecoilOf,
|
|
2305
2282
|
battle.active(player.foe()),
|
|
2306
|
-
);
|
|
2283
|
+
});
|
|
2307
2284
|
if (showdown and stored.hp == 0) residual.* = false;
|
|
2308
2285
|
}
|
|
2309
2286
|
|
|
2310
2287
|
fn reflect(battle: anytype, player: Player, options: anytype) !void {
|
|
2311
2288
|
var side = battle.side(player);
|
|
2312
2289
|
|
|
2313
|
-
if (side.active.volatiles.Reflect)
|
|
2290
|
+
if (side.active.volatiles.Reflect) {
|
|
2291
|
+
return options.log.fail(.{ battle.active(player), Fail.None });
|
|
2292
|
+
}
|
|
2314
2293
|
side.active.volatiles.Reflect = true;
|
|
2315
2294
|
|
|
2316
2295
|
try options.log.start(battle.active(player), .Reflect);
|
|
@@ -2327,10 +2306,10 @@ pub const Effects = struct {
|
|
|
2327
2306
|
// the Sleep Clause Mod might activate, causing us to not actually bypass
|
|
2328
2307
|
} else {
|
|
2329
2308
|
if (Status.any(foe_stored.status)) {
|
|
2330
|
-
return options.log.fail(
|
|
2309
|
+
return options.log.fail(.{
|
|
2331
2310
|
foe_ident,
|
|
2332
|
-
if (Status.is(foe_stored.status, .SLP)) .Sleep else .None,
|
|
2333
|
-
);
|
|
2311
|
+
if (Status.is(foe_stored.status, .SLP)) Fail.Sleep else Fail.None,
|
|
2312
|
+
});
|
|
2334
2313
|
}
|
|
2335
2314
|
// If checkHit in doMove didn't return true Pokémon Showdown wouldn't be in here
|
|
2336
2315
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
@@ -2347,7 +2326,7 @@ pub const Effects = struct {
|
|
|
2347
2326
|
|
|
2348
2327
|
foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
|
|
2349
2328
|
const last = battle.side(player).last_selected_move;
|
|
2350
|
-
try options.log.
|
|
2329
|
+
try options.log.status(.{ foe_ident, foe_stored.status, protocol.Status.From, last });
|
|
2351
2330
|
}
|
|
2352
2331
|
|
|
2353
2332
|
fn splash(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -2357,8 +2336,7 @@ pub const Effects = struct {
|
|
|
2357
2336
|
fn substitute(battle: anytype, player: Player, residual: *bool, options: anytype) !void {
|
|
2358
2337
|
var side = battle.side(player);
|
|
2359
2338
|
if (side.active.volatiles.Substitute) {
|
|
2360
|
-
|
|
2361
|
-
return;
|
|
2339
|
+
return options.log.fail(.{ battle.active(player), Fail.Substitute });
|
|
2362
2340
|
}
|
|
2363
2341
|
|
|
2364
2342
|
assert(side.stored().stats.hp <= 1023);
|
|
@@ -2370,8 +2348,7 @@ pub const Effects = struct {
|
|
|
2370
2348
|
// avoid having to convert to floating point)
|
|
2371
2349
|
const required_hp = if (showdown) @divFloor(side.stored().stats.hp - 1, 4) + 1 else hp;
|
|
2372
2350
|
if (side.stored().hp < required_hp) {
|
|
2373
|
-
|
|
2374
|
-
return;
|
|
2351
|
+
return options.log.fail(.{ battle.active(player), Fail.Weak });
|
|
2375
2352
|
}
|
|
2376
2353
|
|
|
2377
2354
|
// GLITCH: can leave the user with 0 HP (faints later) because didn't check '<=' above
|
|
@@ -2380,7 +2357,7 @@ pub const Effects = struct {
|
|
|
2380
2357
|
side.active.volatiles.Substitute = true;
|
|
2381
2358
|
try options.log.start(battle.active(player), .Substitute);
|
|
2382
2359
|
if (hp > 0) {
|
|
2383
|
-
try options.log.damage(battle.active(player), side.stored(), .None);
|
|
2360
|
+
try options.log.damage(.{ battle.active(player), side.stored(), Damage.None });
|
|
2384
2361
|
if (showdown and side.stored().hp == 0) residual.* = false;
|
|
2385
2362
|
}
|
|
2386
2363
|
}
|
|
@@ -2394,8 +2371,8 @@ pub const Effects = struct {
|
|
|
2394
2371
|
}
|
|
2395
2372
|
battle.last_damage = 0;
|
|
2396
2373
|
} else {
|
|
2397
|
-
try options.log.fail(battle.active(player), .None);
|
|
2398
|
-
try options.log.laststill();
|
|
2374
|
+
try options.log.fail(.{ battle.active(player), Fail.None });
|
|
2375
|
+
try options.log.laststill(.{});
|
|
2399
2376
|
}
|
|
2400
2377
|
}
|
|
2401
2378
|
|
|
@@ -2434,7 +2411,7 @@ pub const Effects = struct {
|
|
|
2434
2411
|
side.active.moves[i].pp = if (m.id != .None) 5 else 0;
|
|
2435
2412
|
}
|
|
2436
2413
|
|
|
2437
|
-
try options.log.transform(battle.active(player), foe_ident);
|
|
2414
|
+
try options.log.transform(.{ battle.active(player), foe_ident });
|
|
2438
2415
|
}
|
|
2439
2416
|
|
|
2440
2417
|
fn binding(battle: anytype, player: Player, rewrap: bool, options: anytype) !void {
|
|
@@ -2464,87 +2441,87 @@ pub const Effects = struct {
|
|
|
2464
2441
|
switch (move.effect) {
|
|
2465
2442
|
.AttackUp1, .AttackUp2, .Rage => {
|
|
2466
2443
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2467
|
-
if (boosts.atk == 6) return
|
|
2444
|
+
if (boosts.atk == 6) return log.fail(.{ ident, Fail.None });
|
|
2468
2445
|
const n: u2 = if (move.effect == .AttackUp2) 2 else 1;
|
|
2469
2446
|
boosts.atk = @as(i4, @intCast(@min(6, @as(i8, boosts.atk) + n)));
|
|
2470
2447
|
const reason = if (move.effect == .Rage) Boost.Rage else Boost.Attack;
|
|
2471
2448
|
if (stats.atk == MAX_STAT_VALUE) {
|
|
2472
2449
|
boosts.atk -= 1;
|
|
2473
2450
|
if (showdown) {
|
|
2474
|
-
try log.boost(ident, reason, n);
|
|
2475
|
-
try log.boost(ident, Boost.Attack, -1);
|
|
2451
|
+
try log.boost(.{ ident, reason, n });
|
|
2452
|
+
try log.boost(.{ ident, Boost.Attack, -1 });
|
|
2476
2453
|
if (move.effect == .Rage) return;
|
|
2477
2454
|
}
|
|
2478
|
-
return
|
|
2455
|
+
return log.fail(.{ ident, Fail.None });
|
|
2479
2456
|
}
|
|
2480
2457
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2481
2458
|
const stat = unmodifiedStats(battle, side).atk;
|
|
2482
2459
|
stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2483
|
-
try log.boost(ident, reason, n);
|
|
2460
|
+
try log.boost(.{ ident, reason, n });
|
|
2484
2461
|
// Pokémon Showdown doesn't re-apply status modifiers after Rage boosts
|
|
2485
2462
|
if (showdown and move.effect == .Rage) return;
|
|
2486
2463
|
},
|
|
2487
2464
|
.DefenseUp1, .DefenseUp2 => {
|
|
2488
2465
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2489
|
-
if (boosts.def == 6) return
|
|
2466
|
+
if (boosts.def == 6) return log.fail(.{ ident, Fail.None });
|
|
2490
2467
|
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
2491
2468
|
boosts.def = @intCast(@min(6, @as(i8, boosts.def) + n));
|
|
2492
2469
|
if (stats.def == MAX_STAT_VALUE) {
|
|
2493
2470
|
boosts.def -= 1;
|
|
2494
2471
|
if (showdown) {
|
|
2495
|
-
try log.boost(ident, .Defense, n);
|
|
2496
|
-
try log.boost(ident, .Defense, -1);
|
|
2472
|
+
try log.boost(.{ ident, Boost.Defense, n });
|
|
2473
|
+
try log.boost(.{ ident, Boost.Defense, -1 });
|
|
2497
2474
|
}
|
|
2498
|
-
return
|
|
2475
|
+
return log.fail(.{ ident, Fail.None });
|
|
2499
2476
|
}
|
|
2500
2477
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2501
2478
|
const stat = unmodifiedStats(battle, side).def;
|
|
2502
2479
|
stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2503
|
-
try log.boost(ident, .Defense, n);
|
|
2480
|
+
try log.boost(.{ ident, Boost.Defense, n });
|
|
2504
2481
|
},
|
|
2505
2482
|
.SpeedUp2 => {
|
|
2506
2483
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2507
|
-
if (boosts.spe == 6) return
|
|
2484
|
+
if (boosts.spe == 6) return log.fail(.{ ident, Fail.None });
|
|
2508
2485
|
boosts.spe = @intCast(@min(6, @as(i8, boosts.spe) + 2));
|
|
2509
2486
|
if (stats.spe == MAX_STAT_VALUE) {
|
|
2510
2487
|
boosts.spe -= 1;
|
|
2511
2488
|
if (showdown) {
|
|
2512
|
-
try log.boost(ident, .Speed, 2);
|
|
2513
|
-
try log.boost(ident, .Speed, -1);
|
|
2489
|
+
try log.boost(.{ ident, Boost.Speed, 2 });
|
|
2490
|
+
try log.boost(.{ ident, Boost.Speed, -1 });
|
|
2514
2491
|
}
|
|
2515
|
-
return
|
|
2492
|
+
return log.fail(.{ ident, Fail.None });
|
|
2516
2493
|
}
|
|
2517
2494
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2518
2495
|
const stat = unmodifiedStats(battle, side).spe;
|
|
2519
2496
|
stats.spe = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2520
|
-
try log.boost(ident, .Speed, 2);
|
|
2497
|
+
try log.boost(.{ ident, Boost.Speed, 2 });
|
|
2521
2498
|
},
|
|
2522
2499
|
.SpecialUp1, .SpecialUp2 => {
|
|
2523
2500
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2524
|
-
if (boosts.spc == 6) return
|
|
2501
|
+
if (boosts.spc == 6) return log.fail(.{ ident, Fail.None });
|
|
2525
2502
|
const n: u2 = if (move.effect == .SpecialUp2) 2 else 1;
|
|
2526
2503
|
boosts.spc = @intCast(@min(6, @as(i8, boosts.spc) + n));
|
|
2527
2504
|
if (stats.spc == MAX_STAT_VALUE) {
|
|
2528
2505
|
boosts.spc -= 1;
|
|
2529
2506
|
if (showdown) {
|
|
2530
|
-
try log.boost(ident, .SpecialAttack, n);
|
|
2531
|
-
try log.boost(ident, .SpecialAttack, -1);
|
|
2532
|
-
try log.boost(ident, .SpecialDefense, n);
|
|
2533
|
-
try log.boost(ident, .SpecialDefense, -1);
|
|
2507
|
+
try log.boost(.{ ident, Boost.SpecialAttack, n });
|
|
2508
|
+
try log.boost(.{ ident, Boost.SpecialAttack, -1 });
|
|
2509
|
+
try log.boost(.{ ident, Boost.SpecialDefense, n });
|
|
2510
|
+
try log.boost(.{ ident, Boost.SpecialDefense, -1 });
|
|
2534
2511
|
}
|
|
2535
|
-
return
|
|
2512
|
+
return log.fail(.{ ident, Fail.None });
|
|
2536
2513
|
}
|
|
2537
2514
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2538
2515
|
const stat = unmodifiedStats(battle, side).spc;
|
|
2539
2516
|
stats.spc = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2540
|
-
try log.boost(ident, .SpecialAttack, n);
|
|
2541
|
-
try log.boost(ident, .SpecialDefense, n);
|
|
2517
|
+
try log.boost(.{ ident, Boost.SpecialAttack, n });
|
|
2518
|
+
try log.boost(.{ ident, Boost.SpecialDefense, n });
|
|
2542
2519
|
},
|
|
2543
2520
|
.EvasionUp1 => {
|
|
2544
2521
|
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2545
|
-
if (boosts.evasion == 6) return
|
|
2522
|
+
if (boosts.evasion == 6) return log.fail(.{ ident, Fail.None });
|
|
2546
2523
|
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2547
|
-
try log.boost(ident, .Evasion, 1);
|
|
2524
|
+
try log.boost(.{ ident, Boost.Evasion, 1 });
|
|
2548
2525
|
},
|
|
2549
2526
|
else => unreachable,
|
|
2550
2527
|
}
|
|
@@ -2559,7 +2536,9 @@ pub const Effects = struct {
|
|
|
2559
2536
|
const foe_ident = battle.active(player.foe());
|
|
2560
2537
|
const secondary = move.effect.isStatDownChance();
|
|
2561
2538
|
|
|
2562
|
-
if (foe.active.volatiles.Substitute)
|
|
2539
|
+
if (foe.active.volatiles.Substitute) {
|
|
2540
|
+
return if (!secondary) log.fail(.{ foe_ident, Fail.None });
|
|
2541
|
+
}
|
|
2563
2542
|
|
|
2564
2543
|
if (secondary) {
|
|
2565
2544
|
const proc = try Rolls.unboost(battle, player, options);
|
|
@@ -2577,82 +2556,82 @@ pub const Effects = struct {
|
|
|
2577
2556
|
switch (move.effect) {
|
|
2578
2557
|
.AttackDown1, .AttackDownChance => {
|
|
2579
2558
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2580
|
-
if (boosts.atk == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2559
|
+
if (boosts.atk == -6) return if (fail) try log.fail(.{ foe_ident, Fail.None });
|
|
2581
2560
|
boosts.atk = @intCast(@max(-6, @as(i8, boosts.atk) - 1));
|
|
2582
2561
|
if (stats.atk == 1) {
|
|
2583
2562
|
boosts.atk += 1;
|
|
2584
2563
|
if (showdown) {
|
|
2585
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2586
|
-
try log.boost(foe_ident, .Attack, 1);
|
|
2564
|
+
try log.boost(.{ foe_ident, Boost.Attack, -1 });
|
|
2565
|
+
try log.boost(.{ foe_ident, Boost.Attack, 1 });
|
|
2587
2566
|
}
|
|
2588
|
-
return
|
|
2567
|
+
return log.fail(.{ foe_ident, Fail.None });
|
|
2589
2568
|
}
|
|
2590
2569
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2591
2570
|
const stat = unmodifiedStats(battle, foe).atk;
|
|
2592
2571
|
stats.atk = @max(1, stat * mod[0] / mod[1]);
|
|
2593
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2572
|
+
try log.boost(.{ foe_ident, Boost.Attack, -1 });
|
|
2594
2573
|
},
|
|
2595
2574
|
.DefenseDown1, .DefenseDown2, .DefenseDownChance => {
|
|
2596
2575
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2597
|
-
if (boosts.def == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2576
|
+
if (boosts.def == -6) return if (fail) try log.fail(.{ foe_ident, Fail.None });
|
|
2598
2577
|
const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
|
|
2599
2578
|
boosts.def = @intCast(@max(-6, @as(i8, boosts.def) - n));
|
|
2600
2579
|
if (stats.def == 1) {
|
|
2601
2580
|
boosts.def += 1;
|
|
2602
2581
|
if (showdown) {
|
|
2603
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2604
|
-
try log.boost(foe_ident, .Defense, 1);
|
|
2582
|
+
try log.boost(.{ foe_ident, Boost.Defense, -@as(i8, n) });
|
|
2583
|
+
try log.boost(.{ foe_ident, Boost.Defense, 1 });
|
|
2605
2584
|
}
|
|
2606
|
-
return
|
|
2585
|
+
return log.fail(.{ foe_ident, Fail.None });
|
|
2607
2586
|
}
|
|
2608
2587
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2609
2588
|
const stat = unmodifiedStats(battle, foe).def;
|
|
2610
2589
|
stats.def = @max(1, stat * mod[0] / mod[1]);
|
|
2611
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2590
|
+
try log.boost(.{ foe_ident, Boost.Defense, -@as(i8, n) });
|
|
2612
2591
|
},
|
|
2613
2592
|
.SpeedDown1, .SpeedDownChance => {
|
|
2614
2593
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2615
|
-
if (boosts.spe == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2594
|
+
if (boosts.spe == -6) return if (fail) try log.fail(.{ foe_ident, Fail.None });
|
|
2616
2595
|
boosts.spe = @intCast(@max(-6, @as(i8, boosts.spe) - 1));
|
|
2617
2596
|
if (stats.spe == 1) {
|
|
2618
2597
|
boosts.spe += 1;
|
|
2619
2598
|
if (showdown) {
|
|
2620
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2621
|
-
try log.boost(foe_ident, .Speed, 1);
|
|
2599
|
+
try log.boost(.{ foe_ident, Boost.Speed, -1 });
|
|
2600
|
+
try log.boost(.{ foe_ident, Boost.Speed, 1 });
|
|
2622
2601
|
}
|
|
2623
|
-
return
|
|
2602
|
+
return log.fail(.{ foe_ident, Fail.None });
|
|
2624
2603
|
}
|
|
2625
2604
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2626
2605
|
const stat = unmodifiedStats(battle, foe).spe;
|
|
2627
2606
|
stats.spe = @max(1, stat * mod[0] / mod[1]);
|
|
2628
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2607
|
+
try log.boost(.{ foe_ident, Boost.Speed, -1 });
|
|
2629
2608
|
assert(boosts.spe >= -6);
|
|
2630
2609
|
},
|
|
2631
2610
|
.SpecialDownChance => {
|
|
2632
2611
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2633
|
-
if (boosts.spc == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2612
|
+
if (boosts.spc == -6) return if (fail) try log.fail(.{ foe_ident, Fail.None });
|
|
2634
2613
|
boosts.spc = @intCast(@max(-6, @as(i8, boosts.spc) - 1));
|
|
2635
2614
|
if (stats.spc == 1) {
|
|
2636
2615
|
boosts.spc += 1;
|
|
2637
2616
|
if (showdown) {
|
|
2638
|
-
try log.boost(foe_ident, .SpecialAttack, -1);
|
|
2639
|
-
try log.boost(foe_ident, .SpecialAttack, 1);
|
|
2640
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2641
|
-
try log.boost(foe_ident, .SpecialDefense, 1);
|
|
2617
|
+
try log.boost(.{ foe_ident, Boost.SpecialAttack, -1 });
|
|
2618
|
+
try log.boost(.{ foe_ident, Boost.SpecialAttack, 1 });
|
|
2619
|
+
try log.boost(.{ foe_ident, Boost.SpecialDefense, -1 });
|
|
2620
|
+
try log.boost(.{ foe_ident, Boost.SpecialDefense, 1 });
|
|
2642
2621
|
}
|
|
2643
|
-
return
|
|
2622
|
+
return log.fail(.{ foe_ident, Fail.None });
|
|
2644
2623
|
}
|
|
2645
2624
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2646
2625
|
const stat = unmodifiedStats(battle, foe).spc;
|
|
2647
2626
|
stats.spc = @max(1, stat * mod[0] / mod[1]);
|
|
2648
|
-
try log.boost(foe_ident, .SpecialAttack, -1);
|
|
2649
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2627
|
+
try log.boost(.{ foe_ident, Boost.SpecialAttack, -1 });
|
|
2628
|
+
try log.boost(.{ foe_ident, Boost.SpecialDefense, -1 });
|
|
2650
2629
|
},
|
|
2651
2630
|
.AccuracyDown1 => {
|
|
2652
2631
|
assert(boosts.accuracy >= -6 and boosts.accuracy <= 6);
|
|
2653
|
-
if (boosts.accuracy == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2632
|
+
if (boosts.accuracy == -6) return if (fail) try log.fail(.{ foe_ident, Fail.None });
|
|
2654
2633
|
boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
|
|
2655
|
-
try log.boost(foe_ident, .Accuracy, -1);
|
|
2634
|
+
try log.boost(.{ foe_ident, Boost.Accuracy, -1 });
|
|
2656
2635
|
},
|
|
2657
2636
|
else => unreachable,
|
|
2658
2637
|
}
|
|
@@ -2676,7 +2655,7 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
2676
2655
|
}
|
|
2677
2656
|
}
|
|
2678
2657
|
|
|
2679
|
-
|
|
2658
|
+
fn isForced(active: anytype) bool {
|
|
2680
2659
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
2681
2660
|
active.volatiles.Thrashing or active.volatiles.Charging;
|
|
2682
2661
|
}
|
|
@@ -2689,27 +2668,27 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2689
2668
|
|
|
2690
2669
|
options.chance.clearDurations(who, clear);
|
|
2691
2670
|
|
|
2692
|
-
if (volatiles.
|
|
2693
|
-
volatiles.
|
|
2694
|
-
volatiles.
|
|
2695
|
-
try log.end(ident, .DisableSilent);
|
|
2671
|
+
if (volatiles.disable_move != 0) {
|
|
2672
|
+
volatiles.disable_move = 0;
|
|
2673
|
+
volatiles.disable_duration = 0;
|
|
2674
|
+
try log.end(.{ ident, End.DisableSilent });
|
|
2696
2675
|
}
|
|
2697
2676
|
if (volatiles.Confusion) {
|
|
2698
2677
|
// volatiles.confusion is left unchanged
|
|
2699
2678
|
volatiles.Confusion = false;
|
|
2700
|
-
try log.end(ident, .ConfusionSilent);
|
|
2679
|
+
try log.end(.{ ident, End.ConfusionSilent });
|
|
2701
2680
|
}
|
|
2702
2681
|
if (volatiles.Mist) {
|
|
2703
2682
|
volatiles.Mist = false;
|
|
2704
|
-
try log.end(ident, .MistSilent);
|
|
2683
|
+
try log.end(.{ ident, End.MistSilent });
|
|
2705
2684
|
}
|
|
2706
2685
|
if (volatiles.FocusEnergy) {
|
|
2707
2686
|
volatiles.FocusEnergy = false;
|
|
2708
|
-
try log.end(ident, .FocusEnergySilent);
|
|
2687
|
+
try log.end(.{ ident, End.FocusEnergySilent });
|
|
2709
2688
|
}
|
|
2710
2689
|
if (volatiles.LeechSeed) {
|
|
2711
2690
|
volatiles.LeechSeed = false;
|
|
2712
|
-
try log.end(ident, .LeechSeedSilent);
|
|
2691
|
+
try log.end(.{ ident, End.LeechSeedSilent });
|
|
2713
2692
|
}
|
|
2714
2693
|
if (!showdown and volatiles.Toxic) {
|
|
2715
2694
|
volatiles.Toxic = false;
|
|
@@ -2717,22 +2696,22 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2717
2696
|
}
|
|
2718
2697
|
if (volatiles.LightScreen) {
|
|
2719
2698
|
volatiles.LightScreen = false;
|
|
2720
|
-
try log.end(ident, .LightScreenSilent);
|
|
2699
|
+
try log.end(.{ ident, End.LightScreenSilent });
|
|
2721
2700
|
}
|
|
2722
2701
|
if (volatiles.Reflect) {
|
|
2723
2702
|
volatiles.Reflect = false;
|
|
2724
|
-
try log.end(ident, .ReflectSilent);
|
|
2703
|
+
try log.end(.{ ident, End.ReflectSilent });
|
|
2725
2704
|
}
|
|
2726
2705
|
if (showdown and volatiles.Toxic) {
|
|
2727
2706
|
volatiles.Toxic = false;
|
|
2728
2707
|
// Pokémon Showdown erroneously clears the toxic counter
|
|
2729
2708
|
volatiles.toxic = 0;
|
|
2730
|
-
try log.end(ident, .ToxicSilent);
|
|
2709
|
+
try log.end(.{ ident, End.ToxicSilent });
|
|
2731
2710
|
}
|
|
2732
2711
|
}
|
|
2733
2712
|
|
|
2734
2713
|
pub const Rolls = struct {
|
|
2735
|
-
|
|
2714
|
+
fn speedTie(battle: anytype, options: anytype) !bool {
|
|
2736
2715
|
const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
|
|
2737
2716
|
player == .P1
|
|
2738
2717
|
else if (showdown)
|
|
@@ -2744,7 +2723,7 @@ pub const Rolls = struct {
|
|
|
2744
2723
|
return p1;
|
|
2745
2724
|
}
|
|
2746
2725
|
|
|
2747
|
-
|
|
2726
|
+
fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
|
|
2748
2727
|
const crit = if (options.calc.overridden(player, .critical_hit)) |val|
|
|
2749
2728
|
val == .true
|
|
2750
2729
|
else if (showdown)
|
|
@@ -2756,7 +2735,7 @@ pub const Rolls = struct {
|
|
|
2756
2735
|
return crit;
|
|
2757
2736
|
}
|
|
2758
2737
|
|
|
2759
|
-
|
|
2738
|
+
fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2760
2739
|
const roll = if (options.calc.overridden(player, .damage)) |val|
|
|
2761
2740
|
val
|
|
2762
2741
|
else roll: {
|
|
@@ -2772,7 +2751,7 @@ pub const Rolls = struct {
|
|
|
2772
2751
|
return roll;
|
|
2773
2752
|
}
|
|
2774
2753
|
|
|
2775
|
-
|
|
2754
|
+
fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) bool {
|
|
2776
2755
|
const ok = if (options.calc.overridden(player, .hit)) |val|
|
|
2777
2756
|
val == .true
|
|
2778
2757
|
else if (showdown)
|
|
@@ -2784,7 +2763,7 @@ pub const Rolls = struct {
|
|
|
2784
2763
|
return ok;
|
|
2785
2764
|
}
|
|
2786
2765
|
|
|
2787
|
-
|
|
2766
|
+
fn confused(battle: anytype, player: Player, options: anytype) !bool {
|
|
2788
2767
|
const cfz = if (options.calc.overridden(player, .confused)) |val|
|
|
2789
2768
|
val == .true
|
|
2790
2769
|
else if (showdown)
|
|
@@ -2796,7 +2775,7 @@ pub const Rolls = struct {
|
|
|
2796
2775
|
return cfz;
|
|
2797
2776
|
}
|
|
2798
2777
|
|
|
2799
|
-
|
|
2778
|
+
fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
|
|
2800
2779
|
const par = if (options.calc.overridden(player, .paralyzed)) |val|
|
|
2801
2780
|
val == .true
|
|
2802
2781
|
else if (showdown)
|
|
@@ -2808,7 +2787,7 @@ pub const Rolls = struct {
|
|
|
2808
2787
|
return par;
|
|
2809
2788
|
}
|
|
2810
2789
|
|
|
2811
|
-
|
|
2790
|
+
fn confusionChance(battle: anytype, player: Player, options: anytype) !bool {
|
|
2812
2791
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2813
2792
|
val == .true
|
|
2814
2793
|
else if (showdown)
|
|
@@ -2820,7 +2799,7 @@ pub const Rolls = struct {
|
|
|
2820
2799
|
return proc;
|
|
2821
2800
|
}
|
|
2822
2801
|
|
|
2823
|
-
|
|
2802
|
+
fn secondaryChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2824
2803
|
const rate: u8 = if (low) 26 else 77;
|
|
2825
2804
|
|
|
2826
2805
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
@@ -2834,7 +2813,7 @@ pub const Rolls = struct {
|
|
|
2834
2813
|
return proc;
|
|
2835
2814
|
}
|
|
2836
2815
|
|
|
2837
|
-
|
|
2816
|
+
fn poisonChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2838
2817
|
const rate: u8 = if (low) 52 else 103;
|
|
2839
2818
|
|
|
2840
2819
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
@@ -2848,7 +2827,7 @@ pub const Rolls = struct {
|
|
|
2848
2827
|
return proc;
|
|
2849
2828
|
}
|
|
2850
2829
|
|
|
2851
|
-
|
|
2830
|
+
fn unboost(battle: anytype, player: Player, options: anytype) !bool {
|
|
2852
2831
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2853
2832
|
val == .true
|
|
2854
2833
|
else if (showdown)
|
|
@@ -2860,14 +2839,12 @@ pub const Rolls = struct {
|
|
|
2860
2839
|
return proc;
|
|
2861
2840
|
}
|
|
2862
2841
|
|
|
2863
|
-
|
|
2842
|
+
fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
2864
2843
|
const move: Move = if (options.calc.overridden(player, .metronome)) |val|
|
|
2865
2844
|
val
|
|
2866
|
-
else if (showdown)
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
break :move @enumFromInt(r + mod);
|
|
2870
|
-
} else move: {
|
|
2845
|
+
else if (showdown)
|
|
2846
|
+
Move.METRONOME[battle.rng.range(u8, 0, Move.METRONOME.len)]
|
|
2847
|
+
else move: {
|
|
2871
2848
|
while (true) {
|
|
2872
2849
|
const r = battle.rng.next();
|
|
2873
2850
|
if (r == 0 or r == @intFromEnum(Move.Metronome)) continue;
|
|
@@ -2880,7 +2857,7 @@ pub const Rolls = struct {
|
|
|
2880
2857
|
return move;
|
|
2881
2858
|
}
|
|
2882
2859
|
|
|
2883
|
-
|
|
2860
|
+
fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
|
|
2884
2861
|
const power = if (options.calc.overridden(player, .psywave)) |val|
|
|
2885
2862
|
val - 1
|
|
2886
2863
|
else if (showdown)
|
|
@@ -2897,11 +2874,11 @@ pub const Rolls = struct {
|
|
|
2897
2874
|
return power;
|
|
2898
2875
|
}
|
|
2899
2876
|
|
|
2900
|
-
|
|
2877
|
+
fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2901
2878
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2902
2879
|
@intCast(val)
|
|
2903
2880
|
else if (showdown)
|
|
2904
|
-
|
|
2881
|
+
battle.rng.range(u3, 1, 8)
|
|
2905
2882
|
else duration: {
|
|
2906
2883
|
while (true) {
|
|
2907
2884
|
const r = battle.rng.next() & 7;
|
|
@@ -2914,11 +2891,11 @@ pub const Rolls = struct {
|
|
|
2914
2891
|
return duration;
|
|
2915
2892
|
}
|
|
2916
2893
|
|
|
2917
|
-
|
|
2894
|
+
fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
|
|
2918
2895
|
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2919
2896
|
val
|
|
2920
2897
|
else if (showdown)
|
|
2921
|
-
|
|
2898
|
+
battle.rng.range(u4, 1, 9)
|
|
2922
2899
|
else
|
|
2923
2900
|
@intCast((battle.rng.next() & 7) + 1);
|
|
2924
2901
|
|
|
@@ -2927,11 +2904,11 @@ pub const Rolls = struct {
|
|
|
2927
2904
|
return duration;
|
|
2928
2905
|
}
|
|
2929
2906
|
|
|
2930
|
-
|
|
2907
|
+
fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
|
|
2931
2908
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2932
2909
|
@intCast(val)
|
|
2933
2910
|
else if (showdown)
|
|
2934
|
-
|
|
2911
|
+
battle.rng.range(u3, 2, 6)
|
|
2935
2912
|
else
|
|
2936
2913
|
@intCast((battle.rng.next() & 3) + 2);
|
|
2937
2914
|
|
|
@@ -2940,11 +2917,11 @@ pub const Rolls = struct {
|
|
|
2940
2917
|
return duration;
|
|
2941
2918
|
}
|
|
2942
2919
|
|
|
2943
|
-
|
|
2920
|
+
fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2944
2921
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2945
2922
|
@intCast(val)
|
|
2946
2923
|
else if (showdown)
|
|
2947
|
-
|
|
2924
|
+
battle.rng.range(u3, 2, 4)
|
|
2948
2925
|
else
|
|
2949
2926
|
@intCast((battle.rng.next() & 1) + 2);
|
|
2950
2927
|
|
|
@@ -2953,7 +2930,9 @@ pub const Rolls = struct {
|
|
|
2953
2930
|
return duration;
|
|
2954
2931
|
}
|
|
2955
2932
|
|
|
2956
|
-
|
|
2933
|
+
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
2934
|
+
|
|
2935
|
+
fn distribution(
|
|
2957
2936
|
battle: anytype,
|
|
2958
2937
|
comptime effect: Move.Effect,
|
|
2959
2938
|
player: Player,
|
|
@@ -2963,7 +2942,7 @@ pub const Rolls = struct {
|
|
|
2963
2942
|
const n: u3 = if (options.calc.overridden(player, roll)) |val|
|
|
2964
2943
|
@intCast(val)
|
|
2965
2944
|
else if (showdown)
|
|
2966
|
-
DISTRIBUTION[battle.rng.range(
|
|
2945
|
+
DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
|
|
2967
2946
|
else n: {
|
|
2968
2947
|
const r = (battle.rng.next() & 3);
|
|
2969
2948
|
break :n @intCast((if (r < 2) r else battle.rng.next() & 3) + 2);
|
|
@@ -3010,10 +2989,9 @@ pub const Rolls = struct {
|
|
|
3010
2989
|
while (i < moves.len and r > 0) : (i += 1) {
|
|
3011
2990
|
if (moves[i].pp > 0) {
|
|
3012
2991
|
r -= 1;
|
|
3013
|
-
if (r == 0) break;
|
|
2992
|
+
if (r == 0) break :slot @intCast(i + 1);
|
|
3014
2993
|
}
|
|
3015
2994
|
}
|
|
3016
|
-
break :slot @intCast(i + 1);
|
|
3017
2995
|
}
|
|
3018
2996
|
}
|
|
3019
2997
|
|
|
@@ -3129,7 +3107,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3129
3107
|
if (m.id == if (active.volatiles.Bide) .Bide else side.last_selected_move) {
|
|
3130
3108
|
// Pokémon Showdown displays Struggle if limited to Bide but unable to pick
|
|
3131
3109
|
const struggle =
|
|
3132
|
-
m.id == .Bide and (m.pp == 0 or active.volatiles.
|
|
3110
|
+
m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
|
|
3133
3111
|
const s = if (struggle) 0 else slot;
|
|
3134
3112
|
out[n] = .{ .type = .Move, .data = s };
|
|
3135
3113
|
n += 1;
|
|
@@ -3144,7 +3122,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3144
3122
|
const m = active.moves[slot - 1];
|
|
3145
3123
|
if (m.id == .None) break;
|
|
3146
3124
|
if (m.pp == 0) continue;
|
|
3147
|
-
if (active.volatiles.
|
|
3125
|
+
if (active.volatiles.disable_move == slot) continue;
|
|
3148
3126
|
out[n] = .{ .type = .Move, .data = slot };
|
|
3149
3127
|
n += 1;
|
|
3150
3128
|
}
|