@pkmn/engine 0.1.0-dev.dd5b9081 → 0.1.0-dev.e0e37c1b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/build/pkg/data/ids.json +12 -12
- package/build/pkg/data/layout.json +2 -2
- package/build/pkg/data/protocol.json +69 -12
- package/build/pkg/data.d.ts +2 -2
- package/build/pkg/gen1.js +2 -2
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/protocol.js +56 -37
- package/build/pkg/protocol.js.map +1 -1
- package/build/tools/debug.js +3 -1
- package/build/tools/debug.js.map +1 -1
- package/build.zig +21 -15
- package/package.json +28 -16
- package/src/lib/binding/node.zig +1 -1
- package/src/lib/common/data.zig +5 -5
- package/src/lib/common/optional.zig +8 -1
- package/src/lib/common/protocol.zig +695 -270
- package/src/lib/common/rational.zig +5 -5
- package/src/lib/common/rng.zig +14 -5
- package/src/lib/gen1/README.md +28 -28
- package/src/lib/gen1/calc.zig +4 -4
- package/src/lib/gen1/chance.zig +53 -29
- package/src/lib/gen1/data/moves.zig +190 -176
- package/src/lib/gen1/data/species.zig +2 -2
- package/src/lib/gen1/data/types.zig +7 -7
- package/src/lib/gen1/data.zig +25 -25
- package/src/lib/gen1/helpers.zig +26 -41
- package/src/lib/gen1/mechanics.zig +283 -324
- package/src/lib/gen2/README.md +18 -72
- package/src/lib/gen2/calc.zig +143 -0
- package/src/lib/gen2/chance.zig +1314 -0
- package/src/lib/gen2/data/items.zig +12 -18
- package/src/lib/gen2/data/moves.zig +377 -344
- package/src/lib/gen2/data/species.zig +1 -1
- package/src/lib/gen2/data/types.zig +134 -31
- package/src/lib/gen2/data.zig +44 -36
- package/src/lib/gen2/generated.zig +808 -0
- package/src/lib/gen2/mechanics.zig +3000 -206
|
@@ -7,23 +7,22 @@ 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;
|
|
26
24
|
const Damage = protocol.Damage;
|
|
25
|
+
const Fail = protocol.Fail;
|
|
27
26
|
const Heal = protocol.Heal;
|
|
28
27
|
|
|
29
28
|
const Gen12 = rng.Gen12;
|
|
@@ -59,23 +58,15 @@ const BOOSTS = &[_][2]u8{
|
|
|
59
58
|
|
|
60
59
|
const MAX_STAT_VALUE = 999;
|
|
61
60
|
|
|
62
|
-
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
63
|
-
|
|
64
61
|
pub fn update(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Result {
|
|
65
|
-
// FIXME
|
|
66
|
-
// std.debug.print("\n", .{});
|
|
67
|
-
// DEBUG(battle.turn);
|
|
68
|
-
// std.debug.print("// ", .{});
|
|
69
62
|
assert(c1.type != .Pass or c2.type != .Pass or battle.turn == 0);
|
|
70
63
|
if (battle.turn == 0) return start(battle, options);
|
|
71
64
|
|
|
72
65
|
var s1 = false;
|
|
73
66
|
var s2 = false;
|
|
74
|
-
var f1: ?Move = null;
|
|
75
|
-
var f2: ?Move = null;
|
|
76
67
|
|
|
77
|
-
if (selectMove(battle, .P1, c1, c2, &s1
|
|
78
|
-
if (selectMove(battle, .P2, c2, c1, &s2
|
|
68
|
+
if (selectMove(battle, .P1, c1, c2, &s1)) |r| return r;
|
|
69
|
+
if (selectMove(battle, .P2, c2, c1, &s2)) |r| return r;
|
|
79
70
|
|
|
80
71
|
var p1 = battle.side(.P1);
|
|
81
72
|
var p2 = battle.side(.P2);
|
|
@@ -84,9 +75,9 @@ pub fn update(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Result
|
|
|
84
75
|
var r2 = showdown and p2.active.volatiles.Binding and c1.type == .Switch;
|
|
85
76
|
|
|
86
77
|
if (try turnOrder(battle, c1, c2, options) == .P1) {
|
|
87
|
-
if (try doTurn(battle, .P1, c1, r1, s1,
|
|
78
|
+
if (try doTurn(battle, .P1, c1, r1, s1, .P2, c2, r2, s2, options)) |r| return r;
|
|
88
79
|
} else {
|
|
89
|
-
if (try doTurn(battle, .P2, c2, r2, s2,
|
|
80
|
+
if (try doTurn(battle, .P2, c2, r2, s2, .P1, c1, r1, s1, options)) |r| return r;
|
|
90
81
|
}
|
|
91
82
|
|
|
92
83
|
if (p1.active.volatiles.attacks == 0) p1.active.volatiles.Binding = false;
|
|
@@ -124,7 +115,6 @@ fn selectMove(
|
|
|
124
115
|
choice: Choice,
|
|
125
116
|
foe_choice: Choice,
|
|
126
117
|
skip_turn: *bool,
|
|
127
|
-
from: *?Move,
|
|
128
118
|
) ?Result {
|
|
129
119
|
if (choice.type == .Pass) return null;
|
|
130
120
|
|
|
@@ -141,7 +131,6 @@ fn selectMove(
|
|
|
141
131
|
return null;
|
|
142
132
|
}
|
|
143
133
|
if (volatiles.Rage) {
|
|
144
|
-
from.* = side.last_used_move;
|
|
145
134
|
if (showdown) {
|
|
146
135
|
if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
|
|
147
136
|
saveMove(battle, player, null);
|
|
@@ -151,7 +140,6 @@ fn selectMove(
|
|
|
151
140
|
// Pokémon Showdown removes Flinch at the end-of-turn in its residual handler
|
|
152
141
|
if (!showdown) volatiles.Flinch = false;
|
|
153
142
|
if (volatiles.Thrashing or volatiles.Charging) {
|
|
154
|
-
from.* = side.last_selected_move;
|
|
155
143
|
if (showdown) {
|
|
156
144
|
if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
|
|
157
145
|
saveMove(battle, player, null);
|
|
@@ -172,9 +160,7 @@ fn selectMove(
|
|
|
172
160
|
return null;
|
|
173
161
|
}
|
|
174
162
|
if (volatiles.Binding) {
|
|
175
|
-
from.* = side.last_used_move;
|
|
176
163
|
if (showdown) {
|
|
177
|
-
if (foe_choice.type == .Switch) from.* = null;
|
|
178
164
|
// Pokémon Showdown overwrites Mirror Move with whatever was selected - really this
|
|
179
165
|
// should set side.last_selected_move = last.id to reuse Mirror Move and fail in order
|
|
180
166
|
// to satisfy the conditions of the Desync Clause Mod. However, because Binding is still
|
|
@@ -210,7 +196,7 @@ fn selectMove(
|
|
|
210
196
|
if (choice.data == 0) {
|
|
211
197
|
const struggle = ok: {
|
|
212
198
|
for (side.active.moves, 0..) |move, i| {
|
|
213
|
-
if (move.pp > 0 and volatiles.
|
|
199
|
+
if (move.pp > 0 and volatiles.disable_move != i + 1) break :ok false;
|
|
214
200
|
}
|
|
215
201
|
break :ok true;
|
|
216
202
|
};
|
|
@@ -229,7 +215,7 @@ fn saveMove(battle: anytype, player: Player, choice: ?Choice) void {
|
|
|
229
215
|
if (c.data == 0) {
|
|
230
216
|
side.last_selected_move = .Struggle;
|
|
231
217
|
} else {
|
|
232
|
-
assert(showdown or side.active.volatiles.
|
|
218
|
+
assert(showdown or side.active.volatiles.disable_move != c.data);
|
|
233
219
|
const move = side.active.move(c.data);
|
|
234
220
|
// You cannot *select* a move with 0 PP (except on Pokémon Showdown where that is
|
|
235
221
|
// sometimes required...), but a 0 PP move can be used automatically
|
|
@@ -270,14 +256,14 @@ fn switchIn(battle: anytype, player: Player, slot: u8, initial: bool, options: a
|
|
|
270
256
|
|
|
271
257
|
foe.active.volatiles.Binding = false;
|
|
272
258
|
|
|
273
|
-
try options.log.switched(battle.active(player), incoming);
|
|
259
|
+
try options.log.switched(.{ battle.active(player), incoming });
|
|
274
260
|
options.chance.switched(player, side.order[0], out);
|
|
275
261
|
|
|
276
262
|
if (showdown and incoming.status == Status.TOX) {
|
|
277
263
|
incoming.status = Status.init(.PSN);
|
|
278
264
|
// Technically, Pokémon Showdown adds these after *both* Pokémon have switched, but we'd
|
|
279
265
|
// rather not clutter up turnOrder just for this (incorrect) log message
|
|
280
|
-
try options.log.status(battle.active(player), incoming.status, .Silent);
|
|
266
|
+
try options.log.status(.{ battle.active(player), incoming.status, .Silent });
|
|
281
267
|
}
|
|
282
268
|
}
|
|
283
269
|
|
|
@@ -340,12 +326,10 @@ fn doTurn(
|
|
|
340
326
|
player_choice: Choice,
|
|
341
327
|
player_rewrap: bool,
|
|
342
328
|
player_skip: bool,
|
|
343
|
-
player_from: ?Move,
|
|
344
329
|
foe_player: Player,
|
|
345
330
|
foe_choice: Choice,
|
|
346
331
|
foe_rewrap: bool,
|
|
347
332
|
foe_skip: bool,
|
|
348
|
-
foe_from: ?Move,
|
|
349
333
|
options: anytype,
|
|
350
334
|
) !?Result {
|
|
351
335
|
assert(player_choice.type != .Pass);
|
|
@@ -358,7 +342,6 @@ fn doTurn(
|
|
|
358
342
|
player_choice,
|
|
359
343
|
player_rewrap,
|
|
360
344
|
player_skip,
|
|
361
|
-
player_from,
|
|
362
345
|
&residual,
|
|
363
346
|
options,
|
|
364
347
|
)) |r| return r;
|
|
@@ -381,7 +364,6 @@ fn doTurn(
|
|
|
381
364
|
foe_choice,
|
|
382
365
|
foe_rewrap,
|
|
383
366
|
foe_skip,
|
|
384
|
-
foe_from,
|
|
385
367
|
&residual,
|
|
386
368
|
options,
|
|
387
369
|
)) |r| return r;
|
|
@@ -409,7 +391,6 @@ fn executeMove(
|
|
|
409
391
|
choice: Choice,
|
|
410
392
|
rewrap: bool,
|
|
411
393
|
skip: bool,
|
|
412
|
-
from: ?Move,
|
|
413
394
|
residual: *bool,
|
|
414
395
|
options: anytype,
|
|
415
396
|
) !?Result {
|
|
@@ -426,7 +407,7 @@ fn executeMove(
|
|
|
426
407
|
if (side.last_selected_move == .SKIP_TURN) {
|
|
427
408
|
assert(!showdown);
|
|
428
409
|
if (battle.foe(player).active.volatiles.Binding) {
|
|
429
|
-
try options.log.cant(battle.active(player), .Bound);
|
|
410
|
+
try options.log.cant(.{ battle.active(player), .Bound });
|
|
430
411
|
}
|
|
431
412
|
return null;
|
|
432
413
|
}
|
|
@@ -444,8 +425,10 @@ fn executeMove(
|
|
|
444
425
|
mslot = @intCast(battle.lastMove(player).index);
|
|
445
426
|
const stored = side.stored();
|
|
446
427
|
// GLITCH: Struggle bypass PP underflow via Hyper Beam / Trapping-switch auto selection
|
|
447
|
-
auto = side.
|
|
448
|
-
|
|
428
|
+
auto = isForced(&side.active) or
|
|
429
|
+
side.active.volatiles.Binding or side.active.volatiles.Bide or
|
|
430
|
+
side.last_selected_move == .HyperBeam or
|
|
431
|
+
Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP);
|
|
449
432
|
// If it wasn't Hyper Beam or the continuation of a move effect then we must have just
|
|
450
433
|
// thawed, in which case we will desync unless the last_selected_move happened to be at
|
|
451
434
|
// index 1 and the current Pokémon has the same move in its first slot.
|
|
@@ -467,7 +450,7 @@ fn executeMove(
|
|
|
467
450
|
|
|
468
451
|
var skip_can = false;
|
|
469
452
|
var skip_pp = false;
|
|
470
|
-
switch (try beforeMove(battle, player, skip,
|
|
453
|
+
switch (try beforeMove(battle, player, skip, residual, options)) {
|
|
471
454
|
.done => return null,
|
|
472
455
|
.skip_can => skip_can = true,
|
|
473
456
|
.skip_pp => skip_pp = true,
|
|
@@ -475,9 +458,9 @@ fn executeMove(
|
|
|
475
458
|
.err => return @as(?Result, Result.Error),
|
|
476
459
|
}
|
|
477
460
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
461
|
+
const can = skip_can or
|
|
462
|
+
try canMove(battle, player, mslot, auto, skip_pp, .None, residual, options);
|
|
463
|
+
if (!can) return null;
|
|
481
464
|
|
|
482
465
|
return doMove(battle, player, mslot, rewrap, auto, residual, options);
|
|
483
466
|
}
|
|
@@ -488,7 +471,6 @@ fn beforeMove(
|
|
|
488
471
|
battle: anytype,
|
|
489
472
|
player: Player,
|
|
490
473
|
skip: bool,
|
|
491
|
-
from: ?Move,
|
|
492
474
|
residual: *bool,
|
|
493
475
|
options: anytype,
|
|
494
476
|
) !BeforeMove {
|
|
@@ -515,23 +497,23 @@ fn beforeMove(
|
|
|
515
497
|
}
|
|
516
498
|
|
|
517
499
|
if (duration == 0) {
|
|
518
|
-
try log.curestatus(ident, before, .Message);
|
|
500
|
+
try log.curestatus(.{ ident, before, .Message });
|
|
519
501
|
stored.status = 0; // clears EXT if present
|
|
520
502
|
} else {
|
|
521
|
-
try log.cant(ident, .Sleep);
|
|
503
|
+
try log.cant(.{ ident, .Sleep });
|
|
522
504
|
}
|
|
523
505
|
side.last_used_move = .None;
|
|
524
506
|
return .done;
|
|
525
507
|
}
|
|
526
508
|
|
|
527
509
|
if (Status.is(stored.status, .FRZ)) {
|
|
528
|
-
try log.cant(ident, .Freeze);
|
|
510
|
+
try log.cant(.{ ident, .Freeze });
|
|
529
511
|
side.last_used_move = .None;
|
|
530
512
|
return .done;
|
|
531
513
|
}
|
|
532
514
|
|
|
533
515
|
if (skip or foe.active.volatiles.Binding) {
|
|
534
|
-
try log.cant(ident, .Bound);
|
|
516
|
+
try log.cant(.{ ident, .Bound });
|
|
535
517
|
return .done;
|
|
536
518
|
}
|
|
537
519
|
|
|
@@ -539,27 +521,27 @@ fn beforeMove(
|
|
|
539
521
|
// Pokémon Showdown doesn't clear Flinch until its imaginary "residual" phase, meaning
|
|
540
522
|
// Pokémon can sometimes flinch multiple times from the same original hit
|
|
541
523
|
if (!showdown) volatiles.Flinch = false;
|
|
542
|
-
try log.cant(ident, .Flinch);
|
|
524
|
+
try log.cant(.{ ident, .Flinch });
|
|
543
525
|
return .done;
|
|
544
526
|
}
|
|
545
527
|
|
|
546
528
|
if (volatiles.Recharging) {
|
|
547
529
|
volatiles.Recharging = false;
|
|
548
|
-
try log.cant(ident, .Recharge);
|
|
530
|
+
try log.cant(.{ ident, .Recharge });
|
|
549
531
|
return .done;
|
|
550
532
|
}
|
|
551
533
|
|
|
552
|
-
if (volatiles.
|
|
534
|
+
if (volatiles.disable_duration > 0) {
|
|
553
535
|
if (options.calc.modify(player, .disable)) |extend| {
|
|
554
|
-
if (!extend) volatiles.
|
|
536
|
+
if (!extend) volatiles.disable_duration = 0;
|
|
555
537
|
} else {
|
|
556
|
-
volatiles.
|
|
538
|
+
volatiles.disable_duration -= 1;
|
|
557
539
|
}
|
|
558
|
-
try options.chance.disable(player, volatiles.
|
|
540
|
+
try options.chance.disable(player, volatiles.disable_duration);
|
|
559
541
|
|
|
560
|
-
if (volatiles.
|
|
561
|
-
volatiles.
|
|
562
|
-
try log.end(ident, .Disable);
|
|
542
|
+
if (volatiles.disable_duration == 0) {
|
|
543
|
+
volatiles.disable_move = 0;
|
|
544
|
+
try log.end(.{ ident, .Disable });
|
|
563
545
|
}
|
|
564
546
|
}
|
|
565
547
|
// Pokémon Showdown's disable condition has a single onBeforeMove handler
|
|
@@ -584,9 +566,9 @@ fn beforeMove(
|
|
|
584
566
|
|
|
585
567
|
if (volatiles.confusion == 0) {
|
|
586
568
|
volatiles.Confusion = false;
|
|
587
|
-
try log.end(ident, .Confusion);
|
|
569
|
+
try log.end(.{ ident, .Confusion });
|
|
588
570
|
} else {
|
|
589
|
-
try log.activate(ident, .Confusion);
|
|
571
|
+
try log.activate(.{ ident, .Confusion });
|
|
590
572
|
|
|
591
573
|
if (try Rolls.confused(battle, player, options)) {
|
|
592
574
|
assert(!volatiles.MultiHit);
|
|
@@ -637,7 +619,7 @@ fn beforeMove(
|
|
|
637
619
|
volatiles.Binding = false;
|
|
638
620
|
options.chance.clearDurations(player, null);
|
|
639
621
|
// GLITCH: Invulnerable is not cleared, resulting in permanent Fly/Dig invulnerability
|
|
640
|
-
try log.cant(ident, .Paralysis);
|
|
622
|
+
try log.cant(.{ ident, .Paralysis });
|
|
641
623
|
return .done;
|
|
642
624
|
}
|
|
643
625
|
|
|
@@ -663,18 +645,18 @@ fn beforeMove(
|
|
|
663
645
|
try options.chance.attacking(player, volatiles.attacks);
|
|
664
646
|
|
|
665
647
|
if (volatiles.attacks != 0) {
|
|
666
|
-
try log.activate(ident, .Bide);
|
|
648
|
+
try log.activate(.{ ident, .Bide });
|
|
667
649
|
return .done;
|
|
668
650
|
}
|
|
669
651
|
|
|
670
652
|
volatiles.Bide = false;
|
|
671
|
-
try log.end(ident, .Bide);
|
|
653
|
+
try log.end(.{ ident, .Bide });
|
|
672
654
|
|
|
673
655
|
battle.last_damage = volatiles.state *% 2;
|
|
674
656
|
volatiles.state = 0;
|
|
675
657
|
|
|
676
658
|
if (battle.last_damage == 0) {
|
|
677
|
-
try log.fail(ident, .None);
|
|
659
|
+
try log.fail(.{ ident, .None });
|
|
678
660
|
return .done;
|
|
679
661
|
}
|
|
680
662
|
|
|
@@ -695,7 +677,6 @@ fn beforeMove(
|
|
|
695
677
|
|
|
696
678
|
if (volatiles.Thrashing) {
|
|
697
679
|
assert(volatiles.attacks > 0);
|
|
698
|
-
assert(from != null);
|
|
699
680
|
|
|
700
681
|
if (options.calc.modify(player, .attacking)) |extend| {
|
|
701
682
|
if (!extend) volatiles.attacks = 0;
|
|
@@ -705,15 +686,15 @@ fn beforeMove(
|
|
|
705
686
|
try options.chance.attacking(player, volatiles.attacks);
|
|
706
687
|
|
|
707
688
|
if (!showdown and handleThrashing(battle, active, player, options)) {
|
|
708
|
-
try log.start(battle.active(player), .ConfusionSilent);
|
|
689
|
+
try log.start(.{ battle.active(player), .ConfusionSilent });
|
|
709
690
|
}
|
|
710
|
-
try log.move(ident, side.last_selected_move, battle.active(player.foe())
|
|
691
|
+
try log.move(.{ ident, side.last_selected_move, battle.active(player.foe()) });
|
|
711
692
|
if (showdown) {
|
|
712
693
|
// This shouldn't actually set last_used_move, but Pokémon Showdown sets last
|
|
713
694
|
// used in useMove and doesn't have the notion of skipping canMove semantics
|
|
714
695
|
side.last_used_move = side.last_selected_move;
|
|
715
696
|
if (handleThrashing(battle, active, player, options)) {
|
|
716
|
-
try log.start(battle.active(player), .ConfusionSilent);
|
|
697
|
+
try log.start(.{ battle.active(player), .ConfusionSilent });
|
|
717
698
|
}
|
|
718
699
|
}
|
|
719
700
|
return .skip_can;
|
|
@@ -729,12 +710,7 @@ fn beforeMove(
|
|
|
729
710
|
}
|
|
730
711
|
try options.chance.binding(player, volatiles.attacks);
|
|
731
712
|
|
|
732
|
-
try log.move(
|
|
733
|
-
battle.active(player),
|
|
734
|
-
side.last_selected_move,
|
|
735
|
-
battle.active(player.foe()),
|
|
736
|
-
side.last_selected_move,
|
|
737
|
-
);
|
|
713
|
+
try log.move(.{ ident, side.last_selected_move, battle.active(player.foe()) });
|
|
738
714
|
if (showdown or battle.last_damage != 0) {
|
|
739
715
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
740
716
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -754,7 +730,7 @@ fn canMove(
|
|
|
754
730
|
mslot: u4,
|
|
755
731
|
auto: bool,
|
|
756
732
|
skip_pp: bool,
|
|
757
|
-
from:
|
|
733
|
+
from: Move,
|
|
758
734
|
residual: *bool,
|
|
759
735
|
options: anytype,
|
|
760
736
|
) !bool {
|
|
@@ -762,33 +738,22 @@ fn canMove(
|
|
|
762
738
|
const player_ident = battle.active(player);
|
|
763
739
|
const move = Move.get(side.last_selected_move);
|
|
764
740
|
|
|
765
|
-
const foe_last = battle.foe(player).last_used_move;
|
|
766
|
-
const special = from != null and (from.? == .Metronome and
|
|
767
|
-
(from.? == .MirrorMove and !(foe_last == .None or foe_last == .MirrorMove)));
|
|
768
|
-
|
|
769
741
|
var skip = skip_pp;
|
|
770
742
|
if (side.active.volatiles.Charging) {
|
|
771
743
|
side.active.volatiles.Charging = false;
|
|
772
744
|
side.active.volatiles.Invulnerable = false;
|
|
773
745
|
} else if (move.effect == .Charge) {
|
|
774
|
-
try options.log.move(player_ident, side.last_selected_move,
|
|
746
|
+
try options.log.move(.{ player_ident, side.last_selected_move, ID{}, from });
|
|
775
747
|
setCounterable(battle, player, side, move);
|
|
776
748
|
try Effects.charge(battle, player, options);
|
|
777
749
|
return false;
|
|
778
750
|
}
|
|
779
751
|
|
|
780
|
-
|
|
752
|
+
side.last_used_move = side.last_selected_move;
|
|
781
753
|
if (!skip) decrementPP(side, mslot, auto);
|
|
782
754
|
|
|
783
755
|
const target = if (move.target == .Self) player else player.foe();
|
|
784
|
-
|
|
785
|
-
// don't have space to be able to track it in all circumstances and thus usually try to infer it
|
|
786
|
-
// from side.last_used_move. However, this gets reset by the opponent switching or fainting,
|
|
787
|
-
// meaning sometimes we lose this information. from is still set because it not being null is
|
|
788
|
-
// relevant for control flow, but we don't have anything useful to put in the log. This is
|
|
789
|
-
// regrettable, but this information is more "nice to have" than required
|
|
790
|
-
const f = if (from != null and from.? == .None) null else from;
|
|
791
|
-
try options.log.move(player_ident, side.last_selected_move, battle.active(target), f);
|
|
756
|
+
try options.log.move(.{ player_ident, side.last_selected_move, battle.active(target), from });
|
|
792
757
|
setCounterable(battle, player, side, move);
|
|
793
758
|
|
|
794
759
|
if (move.effect.onBegin()) {
|
|
@@ -826,12 +791,14 @@ fn decrementPP(side: *Side, mslot: u4, auto: bool) void {
|
|
|
826
791
|
assert(!volatiles.Rage and !volatiles.Thrashing and !volatiles.MultiHit);
|
|
827
792
|
if (volatiles.Bide) return;
|
|
828
793
|
|
|
829
|
-
|
|
830
|
-
|
|
794
|
+
var move_slot = active.move(mslot);
|
|
795
|
+
assert(move_slot.pp > 0 or auto);
|
|
796
|
+
move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
|
|
831
797
|
if (volatiles.Transform) return;
|
|
832
798
|
|
|
833
|
-
|
|
834
|
-
|
|
799
|
+
move_slot = side.stored().move(mslot);
|
|
800
|
+
assert(move_slot.pp > 0 or auto);
|
|
801
|
+
move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
|
|
835
802
|
assert(active.move(mslot).pp == side.stored().move(mslot).pp);
|
|
836
803
|
}
|
|
837
804
|
|
|
@@ -963,16 +930,16 @@ fn doMove(
|
|
|
963
930
|
ohko = (!showdown or (!immune and !invulnerable)) and
|
|
964
931
|
move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe;
|
|
965
932
|
if (ohko) {
|
|
966
|
-
try log.immune(foe_ident, .OHKO);
|
|
933
|
+
try log.immune(.{ foe_ident, .OHKO });
|
|
967
934
|
} else if (immune and !invulnerable and (!showdown or move.effect != .Binding)) {
|
|
968
|
-
try log.immune(foe_ident, .None);
|
|
935
|
+
try log.immune(.{ foe_ident, .None });
|
|
969
936
|
} else if (mist) {
|
|
970
|
-
if (!foe.active.volatiles.Substitute) try log.activate(foe_ident, .Mist);
|
|
971
|
-
try log.fail(foe_ident, .None);
|
|
937
|
+
if (!foe.active.volatiles.Substitute) try log.activate(.{ foe_ident, .Mist });
|
|
938
|
+
try log.fail(.{ foe_ident, .None });
|
|
972
939
|
} else {
|
|
973
940
|
if (showdown or !zero) try options.chance.commit(player, .miss);
|
|
974
|
-
try log.lastmiss();
|
|
975
|
-
try log.miss(battle.active(player));
|
|
941
|
+
try log.lastmiss(.{});
|
|
942
|
+
try log.miss(.{battle.active(player)});
|
|
976
943
|
}
|
|
977
944
|
if (move.effect == .JumpKick) {
|
|
978
945
|
// Recoil is supposed to be damage/8 but damage will always be 0 here
|
|
@@ -1008,15 +975,15 @@ fn doMove(
|
|
|
1008
975
|
var hit: u4 = 0;
|
|
1009
976
|
while (hit < hits) {
|
|
1010
977
|
if (hit == 0) {
|
|
1011
|
-
if (crit) try log.crit(battle.active(player.foe()));
|
|
978
|
+
if (crit) try log.crit(.{battle.active(player.foe())});
|
|
1012
979
|
if (effectiveness > Effectiveness.neutral) {
|
|
1013
|
-
try log.supereffective(battle.active(player.foe()));
|
|
980
|
+
try log.supereffective(.{battle.active(player.foe())});
|
|
1014
981
|
} else if (effectiveness < Effectiveness.neutral) {
|
|
1015
|
-
try log.resisted(battle.active(player.foe()));
|
|
982
|
+
try log.resisted(.{battle.active(player.foe())});
|
|
1016
983
|
}
|
|
1017
984
|
}
|
|
1018
985
|
if (!skip) nullified = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
1019
|
-
if (hit == 0 and ohko) try log.ohko();
|
|
986
|
+
if (hit == 0 and ohko) try log.ohko(.{});
|
|
1020
987
|
hit += 1;
|
|
1021
988
|
if (foe.stored().hp == 0) break;
|
|
1022
989
|
if (!late and (!sub or move.effect == .Explode)) {
|
|
@@ -1033,7 +1000,7 @@ fn doMove(
|
|
|
1033
1000
|
if (showdown and move.effect == .Twineedle and !nullified and foe.stored().hp > 0) {
|
|
1034
1001
|
try Effects.poison(battle, player, Move.get(.PoisonSting), options);
|
|
1035
1002
|
}
|
|
1036
|
-
try log.hitcount(battle.active(player.foe()), hit);
|
|
1003
|
+
try log.hitcount(.{ battle.active(player.foe()), hit });
|
|
1037
1004
|
} else if (showdown) {
|
|
1038
1005
|
// This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
|
|
1039
1006
|
if (move.effect == .Binding) {
|
|
@@ -1043,9 +1010,9 @@ fn doMove(
|
|
|
1043
1010
|
assert(foe.stored().hp > 0);
|
|
1044
1011
|
// Pokémon Showdown logs |-damage| here instead of |-immune| because logic...
|
|
1045
1012
|
if (sub) {
|
|
1046
|
-
try log.activate(battle.active(player.foe()), .Substitute);
|
|
1013
|
+
try log.activate(.{ battle.active(player.foe()), .Substitute });
|
|
1047
1014
|
} else {
|
|
1048
|
-
try log.damage(battle.active(player.foe()), foe.stored(), .None);
|
|
1015
|
+
try log.damage(.{ battle.active(player.foe()), foe.stored(), .None });
|
|
1049
1016
|
try buildRage(battle, player.foe(), options);
|
|
1050
1017
|
}
|
|
1051
1018
|
return null;
|
|
@@ -1243,7 +1210,7 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1243
1210
|
const foe = battle.foe(player);
|
|
1244
1211
|
|
|
1245
1212
|
if (battle.last_damage == 0) {
|
|
1246
|
-
try options.log.fail(battle.active(player), .None);
|
|
1213
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1247
1214
|
return null;
|
|
1248
1215
|
}
|
|
1249
1216
|
|
|
@@ -1261,14 +1228,14 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1261
1228
|
foe_last_selected_move.type == .Fighting);
|
|
1262
1229
|
|
|
1263
1230
|
if (!used and !selected) {
|
|
1264
|
-
try options.log.fail(battle.active(player), .None);
|
|
1231
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1265
1232
|
return null;
|
|
1266
1233
|
}
|
|
1267
1234
|
|
|
1268
1235
|
if (!used or !selected) {
|
|
1269
1236
|
// GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
|
|
1270
1237
|
if (!showdown) return Result.Error;
|
|
1271
|
-
try options.log.fail(battle.active(player), .None);
|
|
1238
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1272
1239
|
return null;
|
|
1273
1240
|
}
|
|
1274
1241
|
|
|
@@ -1304,20 +1271,20 @@ fn applyDamage(
|
|
|
1304
1271
|
subbed.active.volatiles.substitute = 0;
|
|
1305
1272
|
subbed.active.volatiles.Substitute = false;
|
|
1306
1273
|
// battle.last_damage is not updated with the amount of HP the Substitute had
|
|
1307
|
-
try options.log.end(battle.active(sub_player), .Substitute);
|
|
1274
|
+
try options.log.end(.{ battle.active(sub_player), .Substitute });
|
|
1308
1275
|
options.calc.capped(target_player);
|
|
1309
1276
|
return true;
|
|
1310
1277
|
} else {
|
|
1311
1278
|
// Safe to truncate since less than subbed.volatiles.substitute which is a u8
|
|
1312
1279
|
subbed.active.volatiles.substitute -= @intCast(battle.last_damage);
|
|
1313
|
-
try options.log.activate(battle.active(sub_player), .Substitute);
|
|
1280
|
+
try options.log.activate(.{ battle.active(sub_player), .Substitute });
|
|
1314
1281
|
return false;
|
|
1315
1282
|
}
|
|
1316
1283
|
}
|
|
1317
1284
|
|
|
1318
1285
|
if (battle.last_damage > target.stored().hp) battle.last_damage = target.stored().hp;
|
|
1319
1286
|
target.stored().hp -= battle.last_damage;
|
|
1320
|
-
try options.log.damage(battle.active(target_player), target.stored(), reason);
|
|
1287
|
+
try options.log.damage(.{ battle.active(target_player), target.stored(), reason });
|
|
1321
1288
|
return false;
|
|
1322
1289
|
}
|
|
1323
1290
|
|
|
@@ -1336,7 +1303,7 @@ fn mirrorMove(
|
|
|
1336
1303
|
side.last_selected_move = foe.last_used_move;
|
|
1337
1304
|
|
|
1338
1305
|
if (foe.last_used_move == .None or foe.last_used_move == .MirrorMove) {
|
|
1339
|
-
try options.log.fail(battle.active(player), .None);
|
|
1306
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1340
1307
|
return null;
|
|
1341
1308
|
} else if (!showdown or foe.last_used_move != .Struggle) {
|
|
1342
1309
|
incrementPP(side, mslot);
|
|
@@ -1378,12 +1345,12 @@ fn checkHit(battle: anytype, player: Player, move: Move.Data, options: anytype)
|
|
|
1378
1345
|
if (mist) {
|
|
1379
1346
|
assert(!showdown);
|
|
1380
1347
|
const foe_ident = battle.active(player.foe());
|
|
1381
|
-
try options.log.activate(foe_ident, .Mist);
|
|
1382
|
-
try options.log.fail(foe_ident, .None);
|
|
1348
|
+
try options.log.activate(.{ foe_ident, .Mist });
|
|
1349
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1383
1350
|
} else {
|
|
1384
1351
|
try options.chance.commit(player, .miss);
|
|
1385
|
-
try options.log.lastmiss();
|
|
1386
|
-
try options.log.miss(battle.active(player));
|
|
1352
|
+
try options.log.lastmiss(.{});
|
|
1353
|
+
try options.log.miss(.{battle.active(player)});
|
|
1387
1354
|
}
|
|
1388
1355
|
|
|
1389
1356
|
return false;
|
|
@@ -1474,9 +1441,8 @@ fn checkFaint(
|
|
|
1474
1441
|
const foe_fainted = foe.stored().hp == 0;
|
|
1475
1442
|
|
|
1476
1443
|
const player_out = findFirstAlive(side) == 0;
|
|
1477
|
-
const foe_out = findFirstAlive(foe) == 0;
|
|
1478
|
-
const
|
|
1479
|
-
const more = tie or player_out or foe_out;
|
|
1444
|
+
const foe_out = foe_fainted and findFirstAlive(foe) == 0;
|
|
1445
|
+
const more = player_out or foe_out;
|
|
1480
1446
|
|
|
1481
1447
|
if (try faint(battle, player, !(more or foe_fainted), options)) |r| return r;
|
|
1482
1448
|
if (foe_fainted) if (try faint(battle, player.foe(), !more, options)) |r| return r;
|
|
@@ -1484,20 +1450,20 @@ fn checkFaint(
|
|
|
1484
1450
|
assert(!side.active.volatiles.MultiHit);
|
|
1485
1451
|
assert(!foe.active.volatiles.MultiHit);
|
|
1486
1452
|
|
|
1487
|
-
if (
|
|
1488
|
-
try options.log.tie();
|
|
1453
|
+
if (player_out and foe_out) {
|
|
1454
|
+
try options.log.tie(.{});
|
|
1489
1455
|
return Result.Tie;
|
|
1490
1456
|
} else if (player_out) {
|
|
1491
|
-
try options.log.win(player.foe());
|
|
1457
|
+
try options.log.win(.{player.foe()});
|
|
1492
1458
|
return if (player == .P1) Result.Lose else Result.Win;
|
|
1493
1459
|
} else if (foe_out) {
|
|
1494
|
-
try options.log.win(player);
|
|
1460
|
+
try options.log.win(.{player});
|
|
1495
1461
|
return if (player == .P1) Result.Win else Result.Lose;
|
|
1496
1462
|
}
|
|
1497
1463
|
|
|
1498
1464
|
const foe_choice: Choice.Type = if (foe_fainted) .Switch else .Pass;
|
|
1499
|
-
if (player == .P1) return
|
|
1500
|
-
return
|
|
1465
|
+
if (player == .P1) return .{ .p1 = .Switch, .p2 = foe_choice };
|
|
1466
|
+
return .{ .p1 = foe_choice, .p2 = .Switch };
|
|
1501
1467
|
}
|
|
1502
1468
|
|
|
1503
1469
|
fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result {
|
|
@@ -1533,7 +1499,7 @@ fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result
|
|
|
1533
1499
|
side.stored().stats.spe;
|
|
1534
1500
|
}
|
|
1535
1501
|
|
|
1536
|
-
try options.log.faint(battle.active(player), done);
|
|
1502
|
+
try options.log.faint(.{ battle.active(player), done });
|
|
1537
1503
|
options.calc.capped(player);
|
|
1538
1504
|
return null;
|
|
1539
1505
|
}
|
|
@@ -1555,8 +1521,8 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1555
1521
|
|
|
1556
1522
|
if (!showdown or damage > 0) {
|
|
1557
1523
|
stored.hp -= damage;
|
|
1558
|
-
// Pokémon Showdown uses
|
|
1559
|
-
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
1524
|
+
// Pokémon Showdown uses damage [of] here but its not relevant in Generation I
|
|
1525
|
+
try options.log.damage(.{ ident, stored, if (brn) Damage.Burn else Damage.Poison });
|
|
1560
1526
|
}
|
|
1561
1527
|
}
|
|
1562
1528
|
|
|
@@ -1581,14 +1547,14 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1581
1547
|
const amount = @min(damage, stored.hp);
|
|
1582
1548
|
stored.hp -= amount;
|
|
1583
1549
|
|
|
1584
|
-
// As above, Pokémon Showdown uses
|
|
1585
|
-
if (amount > 0) try options.log.damage(ident, stored, .LeechSeed);
|
|
1550
|
+
// As above, Pokémon Showdown uses damage [of] but its not relevant
|
|
1551
|
+
if (amount > 0) try options.log.damage(.{ ident, stored, .LeechSeed });
|
|
1586
1552
|
|
|
1587
1553
|
const before = foe_stored.hp;
|
|
1588
1554
|
// Uncapped damage is added back to the foe
|
|
1589
1555
|
foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
|
|
1590
1556
|
// Pokémon Showdown uses the less specific heal here instead of drain... because reasons?
|
|
1591
|
-
if (foe_stored.hp > before) try options.log.heal(foe_ident, foe_stored, .Silent);
|
|
1557
|
+
if (foe_stored.hp > before) try options.log.heal(.{ foe_ident, foe_stored, .Silent });
|
|
1592
1558
|
}
|
|
1593
1559
|
}
|
|
1594
1560
|
|
|
@@ -1599,18 +1565,18 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1599
1565
|
battle.turn += 1;
|
|
1600
1566
|
|
|
1601
1567
|
if (showdown and pkmn.options.ebc and checkEBC(battle)) {
|
|
1602
|
-
try options.log.tie();
|
|
1568
|
+
try options.log.tie(.{});
|
|
1603
1569
|
return Result.Tie;
|
|
1604
1570
|
}
|
|
1605
1571
|
|
|
1606
1572
|
if (showdown and battle.turn >= 1000) {
|
|
1607
|
-
try options.log.tie();
|
|
1573
|
+
try options.log.tie(.{});
|
|
1608
1574
|
return Result.Tie;
|
|
1609
1575
|
} else if (battle.turn >= 65535) {
|
|
1610
1576
|
return Result.Error;
|
|
1611
1577
|
}
|
|
1612
1578
|
|
|
1613
|
-
try options.log.turn(battle.turn);
|
|
1579
|
+
try options.log.turn(.{battle.turn});
|
|
1614
1580
|
|
|
1615
1581
|
return Result.Default;
|
|
1616
1582
|
}
|
|
@@ -1670,9 +1636,9 @@ fn checkEBC(battle: anytype) bool {
|
|
|
1670
1636
|
}
|
|
1671
1637
|
|
|
1672
1638
|
fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
1673
|
-
if (side.active.volatiles.
|
|
1639
|
+
if (side.active.volatiles.disable_move != 0) {
|
|
1674
1640
|
// A Pokémon that transforms after being disabled may end up with less move slots
|
|
1675
|
-
const m = side.active.moves[side.active.volatiles.
|
|
1641
|
+
const m = side.active.moves[side.active.volatiles.disable_move - 1].id;
|
|
1676
1642
|
// side.last_selected_move can be Struggle here on Pokemon Showdown we need an extra check
|
|
1677
1643
|
const last = if (showdown and m == .Bide and side.active.volatiles.Bide)
|
|
1678
1644
|
m
|
|
@@ -1680,14 +1646,14 @@ fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
|
1680
1646
|
side.last_selected_move;
|
|
1681
1647
|
if (m != .None and m == last) {
|
|
1682
1648
|
side.active.volatiles.Charging = false;
|
|
1683
|
-
try options.log.
|
|
1649
|
+
try options.log.cant(.{ ident, .Disable, last });
|
|
1684
1650
|
return true;
|
|
1685
1651
|
}
|
|
1686
1652
|
}
|
|
1687
1653
|
return false;
|
|
1688
1654
|
}
|
|
1689
1655
|
|
|
1690
|
-
|
|
1656
|
+
fn buildRage(battle: anytype, who: Player, options: anytype) !void {
|
|
1691
1657
|
const side = battle.side(who);
|
|
1692
1658
|
if (side.active.volatiles.Rage and side.active.boosts.atk < 6) {
|
|
1693
1659
|
try Effects.boost(battle, who, Move.get(.Rage), options);
|
|
@@ -1705,7 +1671,7 @@ fn handleThrashing(battle: anytype, active: *ActivePokemon, player: Player, opti
|
|
|
1705
1671
|
return true;
|
|
1706
1672
|
}
|
|
1707
1673
|
|
|
1708
|
-
|
|
1674
|
+
fn onBegin(
|
|
1709
1675
|
battle: anytype,
|
|
1710
1676
|
player: Player,
|
|
1711
1677
|
move: Move.Data,
|
|
@@ -1735,7 +1701,7 @@ inline fn onBegin(
|
|
|
1735
1701
|
};
|
|
1736
1702
|
}
|
|
1737
1703
|
|
|
1738
|
-
|
|
1704
|
+
fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1739
1705
|
assert(move.effect.onEnd());
|
|
1740
1706
|
return switch (move.effect) {
|
|
1741
1707
|
.Bide => Effects.bide(battle, player, options),
|
|
@@ -1751,7 +1717,7 @@ inline fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anyty
|
|
|
1751
1717
|
};
|
|
1752
1718
|
}
|
|
1753
1719
|
|
|
1754
|
-
|
|
1720
|
+
fn alwaysHappens(
|
|
1755
1721
|
battle: anytype,
|
|
1756
1722
|
player: Player,
|
|
1757
1723
|
move: Move.Data,
|
|
@@ -1770,7 +1736,7 @@ inline fn alwaysHappens(
|
|
|
1770
1736
|
};
|
|
1771
1737
|
}
|
|
1772
1738
|
|
|
1773
|
-
|
|
1739
|
+
fn moveEffect(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1774
1740
|
return switch (move.effect) {
|
|
1775
1741
|
.BurnChance1, .BurnChance2 => Effects.burnChance(battle, player, move, options),
|
|
1776
1742
|
.ConfusionChance => Effects.confusion(battle, player, move, options),
|
|
@@ -1797,7 +1763,7 @@ pub const Effects = struct {
|
|
|
1797
1763
|
side.active.volatiles.state = 0;
|
|
1798
1764
|
side.active.volatiles.attacks = Rolls.attackingDuration(battle, player, options);
|
|
1799
1765
|
|
|
1800
|
-
try options.log.start(battle.active(player), .Bide);
|
|
1766
|
+
try options.log.start(.{ battle.active(player), .Bide });
|
|
1801
1767
|
}
|
|
1802
1768
|
|
|
1803
1769
|
fn burnChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1811,11 +1777,11 @@ pub const Effects = struct {
|
|
|
1811
1777
|
// GLITCH: Freeze top move selection desync can occur if thawed Pokémon is slower
|
|
1812
1778
|
if (Status.is(foe_stored.status, .FRZ)) {
|
|
1813
1779
|
assert(move.type == .Fire);
|
|
1814
|
-
try options.log.curestatus(
|
|
1780
|
+
try options.log.curestatus(.{
|
|
1815
1781
|
battle.active(player.foe()),
|
|
1816
1782
|
foe_stored.status,
|
|
1817
1783
|
.Message,
|
|
1818
|
-
);
|
|
1784
|
+
});
|
|
1819
1785
|
foe_stored.status = 0;
|
|
1820
1786
|
}
|
|
1821
1787
|
return;
|
|
@@ -1829,7 +1795,7 @@ pub const Effects = struct {
|
|
|
1829
1795
|
foe_stored.status = Status.init(.BRN);
|
|
1830
1796
|
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
1831
1797
|
|
|
1832
|
-
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
1798
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1833
1799
|
}
|
|
1834
1800
|
|
|
1835
1801
|
fn charge(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -1839,8 +1805,8 @@ pub const Effects = struct {
|
|
|
1839
1805
|
volatiles.Charging = true;
|
|
1840
1806
|
const move = side.last_selected_move;
|
|
1841
1807
|
if (move == .Fly or move == .Dig) volatiles.Invulnerable = true;
|
|
1842
|
-
try options.log.laststill();
|
|
1843
|
-
try options.log.prepare(battle.active(player), move);
|
|
1808
|
+
try options.log.laststill(.{});
|
|
1809
|
+
try options.log.prepare(.{ battle.active(player), move });
|
|
1844
1810
|
}
|
|
1845
1811
|
|
|
1846
1812
|
fn confusion(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1851,17 +1817,11 @@ pub const Effects = struct {
|
|
|
1851
1817
|
if (!try Rolls.confusionChance(battle, player, options)) return;
|
|
1852
1818
|
} else {
|
|
1853
1819
|
if (showdown) {
|
|
1854
|
-
if (!try checkHit(battle, player, move, options))
|
|
1855
|
-
|
|
1856
|
-
} else if (sub) {
|
|
1857
|
-
return options.log.fail(battle.active(player.foe()), .None);
|
|
1858
|
-
}
|
|
1820
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1821
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1859
1822
|
} else {
|
|
1860
|
-
if (sub) {
|
|
1861
|
-
|
|
1862
|
-
} else if (!try checkHit(battle, player, move, options)) {
|
|
1863
|
-
return;
|
|
1864
|
-
}
|
|
1823
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1824
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1865
1825
|
}
|
|
1866
1826
|
try options.chance.commit(player, .hit);
|
|
1867
1827
|
}
|
|
@@ -1870,23 +1830,24 @@ pub const Effects = struct {
|
|
|
1870
1830
|
foe.active.volatiles.Confusion = true;
|
|
1871
1831
|
foe.active.volatiles.confusion = Rolls.confusionDuration(battle, player, false, options);
|
|
1872
1832
|
|
|
1873
|
-
try options.log.start(battle.active(player.foe()), .Confusion);
|
|
1833
|
+
try options.log.start(.{ battle.active(player.foe()), .Confusion });
|
|
1874
1834
|
}
|
|
1875
1835
|
|
|
1876
1836
|
fn conversion(battle: anytype, player: Player, options: anytype) !void {
|
|
1877
1837
|
const foe = battle.foe(player);
|
|
1878
1838
|
|
|
1879
1839
|
if (foe.active.volatiles.Invulnerable) {
|
|
1880
|
-
try options.log.lastmiss();
|
|
1881
|
-
return options.log.miss(battle.active(player));
|
|
1840
|
+
try options.log.lastmiss(.{});
|
|
1841
|
+
return options.log.miss(.{battle.active(player)});
|
|
1882
1842
|
}
|
|
1883
1843
|
|
|
1884
1844
|
battle.side(player).active.types = foe.active.types;
|
|
1885
|
-
return options.log.
|
|
1845
|
+
return options.log.start(.{
|
|
1886
1846
|
battle.active(player),
|
|
1847
|
+
.TypeChange,
|
|
1887
1848
|
foe.active.types,
|
|
1888
1849
|
battle.active(player.foe()),
|
|
1889
|
-
);
|
|
1850
|
+
});
|
|
1890
1851
|
}
|
|
1891
1852
|
|
|
1892
1853
|
fn disable(battle: anytype, player: Player, move: Move.Data, options: anytype) !?Result {
|
|
@@ -1900,8 +1861,8 @@ pub const Effects = struct {
|
|
|
1900
1861
|
try options.chance.commit(player, .hit);
|
|
1901
1862
|
}
|
|
1902
1863
|
|
|
1903
|
-
if (volatiles.
|
|
1904
|
-
try options.log.fail(foe_ident, .None);
|
|
1864
|
+
if (volatiles.disable_move != 0) {
|
|
1865
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1905
1866
|
return null;
|
|
1906
1867
|
}
|
|
1907
1868
|
|
|
@@ -1917,7 +1878,7 @@ pub const Effects = struct {
|
|
|
1917
1878
|
// Technically this is still considered simply a "miss" on the cartridge,
|
|
1918
1879
|
// but diverging from Pokémon Showdown here would mostly just be pedantic
|
|
1919
1880
|
if (n == 0) {
|
|
1920
|
-
try options.log.fail(foe_ident, .None);
|
|
1881
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1921
1882
|
return null;
|
|
1922
1883
|
} else if (err) {
|
|
1923
1884
|
// GLITCH: Transform + Mirror Move / Metronome PP softlock
|
|
@@ -1925,18 +1886,18 @@ pub const Effects = struct {
|
|
|
1925
1886
|
return Result.Error;
|
|
1926
1887
|
}
|
|
1927
1888
|
|
|
1928
|
-
volatiles.
|
|
1889
|
+
volatiles.disable_move =
|
|
1929
1890
|
@intCast(try Rolls.moveSlot(battle, player, &foe.active.moves, n, options));
|
|
1930
|
-
volatiles.
|
|
1891
|
+
volatiles.disable_duration = Rolls.disableDuration(battle, player, options);
|
|
1931
1892
|
|
|
1932
|
-
const id = foe.active.move(volatiles.
|
|
1933
|
-
try options.log.
|
|
1893
|
+
const id = foe.active.move(volatiles.disable_move).id;
|
|
1894
|
+
try options.log.start(.{ foe_ident, .Disable, id });
|
|
1934
1895
|
return null;
|
|
1935
1896
|
}
|
|
1936
1897
|
|
|
1937
1898
|
fn drainHP(battle: anytype, player: Player, options: anytype) !void {
|
|
1938
|
-
var
|
|
1939
|
-
|
|
1899
|
+
var stored = battle.side(player).stored();
|
|
1900
|
+
const ident = battle.active(player);
|
|
1940
1901
|
|
|
1941
1902
|
if (battle.last_damage == 0) {
|
|
1942
1903
|
assert(showdown);
|
|
@@ -1949,7 +1910,7 @@ pub const Effects = struct {
|
|
|
1949
1910
|
if (stored.hp == stored.stats.hp) return;
|
|
1950
1911
|
stored.hp = @min(stored.stats.hp, stored.hp + drain);
|
|
1951
1912
|
|
|
1952
|
-
try options.log.
|
|
1913
|
+
try options.log.heal(.{ ident, stored, .Drain, battle.active(player.foe()) });
|
|
1953
1914
|
}
|
|
1954
1915
|
|
|
1955
1916
|
fn explode(battle: anytype, player: Player) !void {
|
|
@@ -1981,7 +1942,7 @@ pub const Effects = struct {
|
|
|
1981
1942
|
if (side.active.volatiles.FocusEnergy) return;
|
|
1982
1943
|
side.active.volatiles.FocusEnergy = true;
|
|
1983
1944
|
|
|
1984
|
-
try options.log.start(battle.active(player), .FocusEnergy);
|
|
1945
|
+
try options.log.start(.{ battle.active(player), .FocusEnergy });
|
|
1985
1946
|
}
|
|
1986
1947
|
|
|
1987
1948
|
fn freezeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2003,7 +1964,7 @@ pub const Effects = struct {
|
|
|
2003
1964
|
foe_stored.status = Status.init(.FRZ);
|
|
2004
1965
|
// GLITCH: Hyper Beam recharging status is not cleared
|
|
2005
1966
|
|
|
2006
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
1967
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2007
1968
|
}
|
|
2008
1969
|
|
|
2009
1970
|
fn haze(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -2023,8 +1984,8 @@ pub const Effects = struct {
|
|
|
2023
1984
|
side.active.stats = side_stored.stats;
|
|
2024
1985
|
foe.active.stats = foe_stored.stats;
|
|
2025
1986
|
|
|
2026
|
-
try log.activate(player_ident, .Haze);
|
|
2027
|
-
try log.clearallboost();
|
|
1987
|
+
try log.activate(.{ player_ident, .Haze });
|
|
1988
|
+
try log.clearallboost(.{});
|
|
2028
1989
|
|
|
2029
1990
|
// Pokémon Showdown clears P1 then P2 instead of status -> side -> foe
|
|
2030
1991
|
if (showdown) {
|
|
@@ -2032,11 +1993,11 @@ pub const Effects = struct {
|
|
|
2032
1993
|
const p: Player = @enumFromInt(i);
|
|
2033
1994
|
// Pokémon Showdown incorrectly does not prevent sleep/freeze from moving
|
|
2034
1995
|
if (p != player and Status.any(s.stored().status)) {
|
|
2035
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
1996
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2036
1997
|
s.stored().status = 0;
|
|
2037
1998
|
} else if (showdown and s.stored().status == Status.TOX) {
|
|
2038
1999
|
s.stored().status = Status.init(.PSN);
|
|
2039
|
-
try log.status(battle.active(p), s.stored().status, .None);
|
|
2000
|
+
try log.status(.{ battle.active(p), s.stored().status, .None });
|
|
2040
2001
|
}
|
|
2041
2002
|
try clearVolatiles(battle, p, p != player, options);
|
|
2042
2003
|
}
|
|
@@ -2045,7 +2006,7 @@ pub const Effects = struct {
|
|
|
2045
2006
|
if (Status.is(foe_stored.status, .FRZ) or Status.is(foe_stored.status, .SLP)) {
|
|
2046
2007
|
foe.last_selected_move = .SKIP_TURN;
|
|
2047
2008
|
}
|
|
2048
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
2009
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2049
2010
|
foe_stored.status = 0;
|
|
2050
2011
|
}
|
|
2051
2012
|
try clearVolatiles(battle, player, false, options);
|
|
@@ -2061,7 +2022,7 @@ pub const Effects = struct {
|
|
|
2061
2022
|
// GLITCH: HP recovery move failure glitches
|
|
2062
2023
|
const delta = stored.stats.hp - stored.hp;
|
|
2063
2024
|
if (delta == 0 or (delta & 255 == 255 and stored.hp % 256 != 0)) {
|
|
2064
|
-
return
|
|
2025
|
+
return options.log.fail(.{ ident, .None });
|
|
2065
2026
|
}
|
|
2066
2027
|
|
|
2067
2028
|
const rest = side.last_selected_move == .Rest;
|
|
@@ -2069,17 +2030,17 @@ pub const Effects = struct {
|
|
|
2069
2030
|
// Adding the sleep status runs the sleep condition handler to roll duration
|
|
2070
2031
|
if (showdown) battle.rng.advance(1);
|
|
2071
2032
|
stored.status = Status.slf(2);
|
|
2072
|
-
try options.log.
|
|
2033
|
+
try options.log.status(.{ ident, stored.status, .From, Move.Rest });
|
|
2073
2034
|
stored.hp = stored.stats.hp;
|
|
2074
2035
|
} else {
|
|
2075
2036
|
stored.hp = @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2));
|
|
2076
2037
|
}
|
|
2077
|
-
try options.log.heal(ident, stored, if (rest) Heal.Silent else Heal.None);
|
|
2038
|
+
try options.log.heal(.{ ident, stored, if (rest) Heal.Silent else Heal.None });
|
|
2078
2039
|
}
|
|
2079
2040
|
|
|
2080
2041
|
fn hyperBeam(battle: anytype, player: Player, options: anytype) !void {
|
|
2081
2042
|
battle.side(player).active.volatiles.Recharging = true;
|
|
2082
|
-
try options.log.mustrecharge(battle.active(player));
|
|
2043
|
+
try options.log.mustrecharge(.{battle.active(player)});
|
|
2083
2044
|
}
|
|
2084
2045
|
|
|
2085
2046
|
fn leechSeed(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2088,33 +2049,32 @@ pub const Effects = struct {
|
|
|
2088
2049
|
if (showdown) {
|
|
2089
2050
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2090
2051
|
if (!foe.active.volatiles.Invulnerable and foe.active.types.includes(.Grass)) {
|
|
2091
|
-
return options.log.immune(battle.active(player.foe()), .None);
|
|
2052
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
2092
2053
|
}
|
|
2093
2054
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2094
2055
|
if (foe.active.volatiles.LeechSeed) return;
|
|
2095
2056
|
} else {
|
|
2096
2057
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2097
2058
|
if (foe.active.types.includes(.Grass) or foe.active.volatiles.LeechSeed) {
|
|
2098
|
-
try options.log.lastmiss();
|
|
2099
|
-
return options.log.miss(battle.active(player));
|
|
2059
|
+
try options.log.lastmiss(.{});
|
|
2060
|
+
return options.log.miss(.{battle.active(player)});
|
|
2100
2061
|
}
|
|
2101
2062
|
}
|
|
2102
2063
|
|
|
2103
2064
|
try options.chance.commit(player, .hit);
|
|
2104
2065
|
foe.active.volatiles.LeechSeed = true;
|
|
2105
2066
|
|
|
2106
|
-
try options.log.start(battle.active(player.foe()), .LeechSeed);
|
|
2067
|
+
try options.log.start(.{ battle.active(player.foe()), .LeechSeed });
|
|
2107
2068
|
}
|
|
2108
2069
|
|
|
2109
2070
|
fn lightScreen(battle: anytype, player: Player, options: anytype) !void {
|
|
2110
2071
|
var side = battle.side(player);
|
|
2072
|
+
const ident = battle.active(player);
|
|
2111
2073
|
|
|
2112
|
-
if (side.active.volatiles.LightScreen) {
|
|
2113
|
-
return options.log.fail(battle.active(player), .None);
|
|
2114
|
-
}
|
|
2074
|
+
if (side.active.volatiles.LightScreen) return options.log.fail(.{ ident, .None });
|
|
2115
2075
|
side.active.volatiles.LightScreen = true;
|
|
2116
2076
|
|
|
2117
|
-
try options.log.start(
|
|
2077
|
+
try options.log.start(.{ ident, .LightScreen });
|
|
2118
2078
|
}
|
|
2119
2079
|
|
|
2120
2080
|
fn mimic(battle: anytype, player: Player, move: Move.Data, mslot: u8, options: anytype) !void {
|
|
@@ -2143,7 +2103,7 @@ pub const Effects = struct {
|
|
|
2143
2103
|
// Invulnerable foes or 1/256 miss can trigger |-miss| instead of |-fail|
|
|
2144
2104
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2145
2105
|
try options.chance.commit(player, .hit);
|
|
2146
|
-
return
|
|
2106
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2147
2107
|
}
|
|
2148
2108
|
}
|
|
2149
2109
|
if (!try checkHit(battle, player, move, options)) return;
|
|
@@ -2152,7 +2112,7 @@ pub const Effects = struct {
|
|
|
2152
2112
|
const rslot = try Rolls.moveSlot(battle, player, &foe.active.moves, 0, options);
|
|
2153
2113
|
side.active.move(oslot).id = foe.active.move(rslot).id;
|
|
2154
2114
|
|
|
2155
|
-
try options.log.
|
|
2115
|
+
try options.log.start(.{ battle.active(player), .Mimic, side.active.move(oslot).id });
|
|
2156
2116
|
}
|
|
2157
2117
|
|
|
2158
2118
|
fn mist(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -2161,7 +2121,7 @@ pub const Effects = struct {
|
|
|
2161
2121
|
if (side.active.volatiles.Mist) return;
|
|
2162
2122
|
side.active.volatiles.Mist = true;
|
|
2163
2123
|
|
|
2164
|
-
try options.log.start(battle.active(player), .Mist);
|
|
2124
|
+
try options.log.start(.{ battle.active(player), .Mist });
|
|
2165
2125
|
}
|
|
2166
2126
|
|
|
2167
2127
|
fn multiHit(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2187,17 +2147,18 @@ pub const Effects = struct {
|
|
|
2187
2147
|
|
|
2188
2148
|
if (showdown) {
|
|
2189
2149
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2190
|
-
if (immune and !foe.active.volatiles.Invulnerable)
|
|
2191
|
-
|
|
2150
|
+
if (immune and !foe.active.volatiles.Invulnerable) {
|
|
2151
|
+
return log.immune(.{ foe_ident, .None });
|
|
2152
|
+
} else if (!try checkHit(battle, player, move, options)) return;
|
|
2192
2153
|
}
|
|
2193
2154
|
if (Status.any(foe_stored.status)) {
|
|
2194
|
-
return log.fail(
|
|
2155
|
+
return log.fail(.{
|
|
2195
2156
|
foe_ident,
|
|
2196
|
-
if (Status.is(foe_stored.status, .PAR)) .Paralysis else .None,
|
|
2197
|
-
);
|
|
2157
|
+
if (Status.is(foe_stored.status, .PAR)) Fail.Paralysis else Fail.None,
|
|
2158
|
+
});
|
|
2198
2159
|
}
|
|
2199
2160
|
if (!showdown) {
|
|
2200
|
-
if (immune) return log.immune(foe_ident, .None);
|
|
2161
|
+
if (immune) return log.immune(.{ foe_ident, .None });
|
|
2201
2162
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2202
2163
|
}
|
|
2203
2164
|
try options.chance.commit(player, .hit);
|
|
@@ -2205,7 +2166,7 @@ pub const Effects = struct {
|
|
|
2205
2166
|
foe_stored.status = Status.init(.PAR);
|
|
2206
2167
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2207
2168
|
|
|
2208
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2169
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2209
2170
|
}
|
|
2210
2171
|
|
|
2211
2172
|
fn paralyzeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2227,12 +2188,12 @@ pub const Effects = struct {
|
|
|
2227
2188
|
foe_stored.status = Status.init(.PAR);
|
|
2228
2189
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2229
2190
|
|
|
2230
|
-
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
2191
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
2231
2192
|
}
|
|
2232
2193
|
|
|
2233
2194
|
fn payDay(battle: anytype, player: Player, options: anytype) !void {
|
|
2234
2195
|
if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
|
|
2235
|
-
try options.log.fieldactivate();
|
|
2196
|
+
try options.log.fieldactivate(.{});
|
|
2236
2197
|
}
|
|
2237
2198
|
}
|
|
2238
2199
|
|
|
@@ -2247,23 +2208,23 @@ pub const Effects = struct {
|
|
|
2247
2208
|
return;
|
|
2248
2209
|
} else if (foe.active.volatiles.Substitute) {
|
|
2249
2210
|
if (move.effect != .Poison) return;
|
|
2250
|
-
return log.fail(foe_ident, .None);
|
|
2211
|
+
return log.fail(.{ foe_ident, .None });
|
|
2251
2212
|
} else if (Status.any(foe_stored.status)) {
|
|
2252
2213
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2253
2214
|
// Pokémon Showdown considers Toxic to be a status even in Generation I and so
|
|
2254
2215
|
// will not include a fail reason for Toxic vs. Poison or vice-versa...
|
|
2255
|
-
return log.fail(foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2216
|
+
return log.fail(.{ foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2256
2217
|
if (!showdown)
|
|
2257
|
-
.Poison
|
|
2218
|
+
Fail.Poison
|
|
2258
2219
|
else if (toxic == (foe_stored.status == Status.TOX))
|
|
2259
|
-
if (toxic) .Toxic else .Poison
|
|
2220
|
+
if (toxic) Fail.Toxic else Fail.Poison
|
|
2260
2221
|
else
|
|
2261
|
-
.None
|
|
2222
|
+
Fail.None
|
|
2262
2223
|
else
|
|
2263
|
-
.None);
|
|
2224
|
+
Fail.None });
|
|
2264
2225
|
} else if (foe.active.types.includes(.Poison)) {
|
|
2265
2226
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2266
|
-
return log.immune(foe_ident, .None);
|
|
2227
|
+
return log.immune(.{ foe_ident, .None });
|
|
2267
2228
|
}
|
|
2268
2229
|
|
|
2269
2230
|
if (move.effect == .Poison) {
|
|
@@ -2282,7 +2243,7 @@ pub const Effects = struct {
|
|
|
2282
2243
|
foe.active.volatiles.toxic = 0;
|
|
2283
2244
|
}
|
|
2284
2245
|
|
|
2285
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2246
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2286
2247
|
}
|
|
2287
2248
|
|
|
2288
2249
|
fn rage(battle: anytype, player: Player) !void {
|
|
@@ -2302,22 +2263,23 @@ pub const Effects = struct {
|
|
|
2302
2263
|
@as(u8, if (side.last_selected_move == .Struggle) 2 else 4), 1));
|
|
2303
2264
|
stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
|
|
2304
2265
|
|
|
2305
|
-
try options.log.
|
|
2266
|
+
try options.log.damage(.{
|
|
2306
2267
|
battle.active(player),
|
|
2307
2268
|
stored,
|
|
2308
2269
|
.RecoilOf,
|
|
2309
2270
|
battle.active(player.foe()),
|
|
2310
|
-
);
|
|
2271
|
+
});
|
|
2311
2272
|
if (showdown and stored.hp == 0) residual.* = false;
|
|
2312
2273
|
}
|
|
2313
2274
|
|
|
2314
2275
|
fn reflect(battle: anytype, player: Player, options: anytype) !void {
|
|
2315
2276
|
var side = battle.side(player);
|
|
2277
|
+
const ident = battle.active(player);
|
|
2316
2278
|
|
|
2317
|
-
if (side.active.volatiles.Reflect) return options.log.fail(
|
|
2279
|
+
if (side.active.volatiles.Reflect) return options.log.fail(.{ ident, .None });
|
|
2318
2280
|
side.active.volatiles.Reflect = true;
|
|
2319
2281
|
|
|
2320
|
-
try options.log.start(
|
|
2282
|
+
try options.log.start(.{ ident, .Reflect });
|
|
2321
2283
|
}
|
|
2322
2284
|
|
|
2323
2285
|
fn sleep(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2331,10 +2293,10 @@ pub const Effects = struct {
|
|
|
2331
2293
|
// the Sleep Clause Mod might activate, causing us to not actually bypass
|
|
2332
2294
|
} else {
|
|
2333
2295
|
if (Status.any(foe_stored.status)) {
|
|
2334
|
-
return options.log.fail(
|
|
2296
|
+
return options.log.fail(.{
|
|
2335
2297
|
foe_ident,
|
|
2336
|
-
if (Status.is(foe_stored.status, .SLP)) .Sleep else .None,
|
|
2337
|
-
);
|
|
2298
|
+
if (Status.is(foe_stored.status, .SLP)) Fail.Sleep else Fail.None,
|
|
2299
|
+
});
|
|
2338
2300
|
}
|
|
2339
2301
|
// If checkHit in doMove didn't return true Pokémon Showdown wouldn't be in here
|
|
2340
2302
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
@@ -2351,18 +2313,17 @@ pub const Effects = struct {
|
|
|
2351
2313
|
|
|
2352
2314
|
foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
|
|
2353
2315
|
const last = battle.side(player).last_selected_move;
|
|
2354
|
-
try options.log.
|
|
2316
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .From, last });
|
|
2355
2317
|
}
|
|
2356
2318
|
|
|
2357
2319
|
fn splash(battle: anytype, player: Player, options: anytype) !void {
|
|
2358
|
-
try options.log.activate(battle.active(player), .Splash);
|
|
2320
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
2359
2321
|
}
|
|
2360
2322
|
|
|
2361
2323
|
fn substitute(battle: anytype, player: Player, residual: *bool, options: anytype) !void {
|
|
2362
2324
|
var side = battle.side(player);
|
|
2363
2325
|
if (side.active.volatiles.Substitute) {
|
|
2364
|
-
|
|
2365
|
-
return;
|
|
2326
|
+
return options.log.fail(.{ battle.active(player), .Substitute });
|
|
2366
2327
|
}
|
|
2367
2328
|
|
|
2368
2329
|
assert(side.stored().stats.hp <= 1023);
|
|
@@ -2374,17 +2335,16 @@ pub const Effects = struct {
|
|
|
2374
2335
|
// avoid having to convert to floating point)
|
|
2375
2336
|
const required_hp = if (showdown) @divFloor(side.stored().stats.hp - 1, 4) + 1 else hp;
|
|
2376
2337
|
if (side.stored().hp < required_hp) {
|
|
2377
|
-
|
|
2378
|
-
return;
|
|
2338
|
+
return options.log.fail(.{ battle.active(player), .Weak });
|
|
2379
2339
|
}
|
|
2380
2340
|
|
|
2381
2341
|
// GLITCH: can leave the user with 0 HP (faints later) because didn't check '<=' above
|
|
2382
2342
|
side.stored().hp -= hp;
|
|
2383
2343
|
side.active.volatiles.substitute = hp + 1;
|
|
2384
2344
|
side.active.volatiles.Substitute = true;
|
|
2385
|
-
try options.log.start(battle.active(player), .Substitute);
|
|
2345
|
+
try options.log.start(.{ battle.active(player), .Substitute });
|
|
2386
2346
|
if (hp > 0) {
|
|
2387
|
-
try options.log.damage(battle.active(player), side.stored(), .None);
|
|
2347
|
+
try options.log.damage(.{ battle.active(player), side.stored(), .None });
|
|
2388
2348
|
if (showdown and side.stored().hp == 0) residual.* = false;
|
|
2389
2349
|
}
|
|
2390
2350
|
}
|
|
@@ -2398,8 +2358,8 @@ pub const Effects = struct {
|
|
|
2398
2358
|
}
|
|
2399
2359
|
battle.last_damage = 0;
|
|
2400
2360
|
} else {
|
|
2401
|
-
try options.log.fail(battle.active(player), .None);
|
|
2402
|
-
try options.log.laststill();
|
|
2361
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2362
|
+
try options.log.laststill(.{});
|
|
2403
2363
|
}
|
|
2404
2364
|
}
|
|
2405
2365
|
|
|
@@ -2438,7 +2398,7 @@ pub const Effects = struct {
|
|
|
2438
2398
|
side.active.moves[i].pp = if (m.id != .None) 5 else 0;
|
|
2439
2399
|
}
|
|
2440
2400
|
|
|
2441
|
-
try options.log.transform(battle.active(player), foe_ident);
|
|
2401
|
+
try options.log.transform(.{ battle.active(player), foe_ident });
|
|
2442
2402
|
}
|
|
2443
2403
|
|
|
2444
2404
|
fn binding(battle: anytype, player: Player, rewrap: bool, options: anytype) !void {
|
|
@@ -2468,87 +2428,87 @@ pub const Effects = struct {
|
|
|
2468
2428
|
switch (move.effect) {
|
|
2469
2429
|
.AttackUp1, .AttackUp2, .Rage => {
|
|
2470
2430
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2471
|
-
if (boosts.atk == 6) return
|
|
2431
|
+
if (boosts.atk == 6) return log.fail(.{ ident, .None });
|
|
2472
2432
|
const n: u2 = if (move.effect == .AttackUp2) 2 else 1;
|
|
2473
2433
|
boosts.atk = @as(i4, @intCast(@min(6, @as(i8, boosts.atk) + n)));
|
|
2474
|
-
const reason = if (move.effect == .Rage)
|
|
2434
|
+
const reason: Boost = if (move.effect == .Rage) .Rage else .Attack;
|
|
2475
2435
|
if (stats.atk == MAX_STAT_VALUE) {
|
|
2476
2436
|
boosts.atk -= 1;
|
|
2477
2437
|
if (showdown) {
|
|
2478
|
-
try log.boost(ident, reason, n);
|
|
2479
|
-
try log.boost(ident,
|
|
2438
|
+
try log.boost(.{ ident, reason, n });
|
|
2439
|
+
try log.boost(.{ ident, .Attack, -1 });
|
|
2480
2440
|
if (move.effect == .Rage) return;
|
|
2481
2441
|
}
|
|
2482
|
-
return
|
|
2442
|
+
return log.fail(.{ ident, .None });
|
|
2483
2443
|
}
|
|
2484
2444
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2485
2445
|
const stat = unmodifiedStats(battle, side).atk;
|
|
2486
2446
|
stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2487
|
-
try log.boost(ident, reason, n);
|
|
2447
|
+
try log.boost(.{ ident, reason, n });
|
|
2488
2448
|
// Pokémon Showdown doesn't re-apply status modifiers after Rage boosts
|
|
2489
2449
|
if (showdown and move.effect == .Rage) return;
|
|
2490
2450
|
},
|
|
2491
2451
|
.DefenseUp1, .DefenseUp2 => {
|
|
2492
2452
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2493
|
-
if (boosts.def == 6) return
|
|
2453
|
+
if (boosts.def == 6) return log.fail(.{ ident, .None });
|
|
2494
2454
|
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
2495
2455
|
boosts.def = @intCast(@min(6, @as(i8, boosts.def) + n));
|
|
2496
2456
|
if (stats.def == MAX_STAT_VALUE) {
|
|
2497
2457
|
boosts.def -= 1;
|
|
2498
2458
|
if (showdown) {
|
|
2499
|
-
try log.boost(ident, .Defense, n);
|
|
2500
|
-
try log.boost(ident, .Defense, -1);
|
|
2459
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2460
|
+
try log.boost(.{ ident, .Defense, -1 });
|
|
2501
2461
|
}
|
|
2502
|
-
return
|
|
2462
|
+
return log.fail(.{ ident, .None });
|
|
2503
2463
|
}
|
|
2504
2464
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2505
2465
|
const stat = unmodifiedStats(battle, side).def;
|
|
2506
2466
|
stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2507
|
-
try log.boost(ident, .Defense, n);
|
|
2467
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2508
2468
|
},
|
|
2509
2469
|
.SpeedUp2 => {
|
|
2510
2470
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2511
|
-
if (boosts.spe == 6) return
|
|
2471
|
+
if (boosts.spe == 6) return log.fail(.{ ident, .None });
|
|
2512
2472
|
boosts.spe = @intCast(@min(6, @as(i8, boosts.spe) + 2));
|
|
2513
2473
|
if (stats.spe == MAX_STAT_VALUE) {
|
|
2514
2474
|
boosts.spe -= 1;
|
|
2515
2475
|
if (showdown) {
|
|
2516
|
-
try log.boost(ident, .Speed, 2);
|
|
2517
|
-
try log.boost(ident, .Speed, -1);
|
|
2476
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2477
|
+
try log.boost(.{ ident, .Speed, -1 });
|
|
2518
2478
|
}
|
|
2519
|
-
return
|
|
2479
|
+
return log.fail(.{ ident, .None });
|
|
2520
2480
|
}
|
|
2521
2481
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2522
2482
|
const stat = unmodifiedStats(battle, side).spe;
|
|
2523
2483
|
stats.spe = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2524
|
-
try log.boost(ident, .Speed, 2);
|
|
2484
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2525
2485
|
},
|
|
2526
2486
|
.SpecialUp1, .SpecialUp2 => {
|
|
2527
2487
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2528
|
-
if (boosts.spc == 6) return
|
|
2488
|
+
if (boosts.spc == 6) return log.fail(.{ ident, .None });
|
|
2529
2489
|
const n: u2 = if (move.effect == .SpecialUp2) 2 else 1;
|
|
2530
2490
|
boosts.spc = @intCast(@min(6, @as(i8, boosts.spc) + n));
|
|
2531
2491
|
if (stats.spc == MAX_STAT_VALUE) {
|
|
2532
2492
|
boosts.spc -= 1;
|
|
2533
2493
|
if (showdown) {
|
|
2534
|
-
try log.boost(ident, .SpecialAttack, n);
|
|
2535
|
-
try log.boost(ident, .SpecialAttack, -1);
|
|
2536
|
-
try log.boost(ident, .SpecialDefense, n);
|
|
2537
|
-
try log.boost(ident, .SpecialDefense, -1);
|
|
2494
|
+
try log.boost(.{ ident, .SpecialAttack, n });
|
|
2495
|
+
try log.boost(.{ ident, .SpecialAttack, -1 });
|
|
2496
|
+
try log.boost(.{ ident, .SpecialDefense, n });
|
|
2497
|
+
try log.boost(.{ ident, .SpecialDefense, -1 });
|
|
2538
2498
|
}
|
|
2539
|
-
return
|
|
2499
|
+
return log.fail(.{ ident, .None });
|
|
2540
2500
|
}
|
|
2541
2501
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2542
2502
|
const stat = unmodifiedStats(battle, side).spc;
|
|
2543
2503
|
stats.spc = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2544
|
-
try log.boost(ident, .SpecialAttack, n);
|
|
2545
|
-
try log.boost(ident, .SpecialDefense, n);
|
|
2504
|
+
try log.boost(.{ ident, .SpecialAttack, n });
|
|
2505
|
+
try log.boost(.{ ident, .SpecialDefense, n });
|
|
2546
2506
|
},
|
|
2547
2507
|
.EvasionUp1 => {
|
|
2548
2508
|
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2549
|
-
if (boosts.evasion == 6) return
|
|
2509
|
+
if (boosts.evasion == 6) return log.fail(.{ ident, .None });
|
|
2550
2510
|
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2551
|
-
try log.boost(ident, .Evasion, 1);
|
|
2511
|
+
try log.boost(.{ ident, .Evasion, 1 });
|
|
2552
2512
|
},
|
|
2553
2513
|
else => unreachable,
|
|
2554
2514
|
}
|
|
@@ -2563,7 +2523,7 @@ pub const Effects = struct {
|
|
|
2563
2523
|
const foe_ident = battle.active(player.foe());
|
|
2564
2524
|
const secondary = move.effect.isStatDownChance();
|
|
2565
2525
|
|
|
2566
|
-
if (foe.active.volatiles.Substitute) return if (!secondary) log.fail(foe_ident, .None);
|
|
2526
|
+
if (foe.active.volatiles.Substitute) return if (!secondary) log.fail(.{ foe_ident, .None });
|
|
2567
2527
|
|
|
2568
2528
|
if (secondary) {
|
|
2569
2529
|
const proc = try Rolls.unboost(battle, player, options);
|
|
@@ -2581,82 +2541,82 @@ pub const Effects = struct {
|
|
|
2581
2541
|
switch (move.effect) {
|
|
2582
2542
|
.AttackDown1, .AttackDownChance => {
|
|
2583
2543
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2584
|
-
if (boosts.atk == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2544
|
+
if (boosts.atk == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2585
2545
|
boosts.atk = @intCast(@max(-6, @as(i8, boosts.atk) - 1));
|
|
2586
2546
|
if (stats.atk == 1) {
|
|
2587
2547
|
boosts.atk += 1;
|
|
2588
2548
|
if (showdown) {
|
|
2589
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2590
|
-
try log.boost(foe_ident, .Attack, 1);
|
|
2549
|
+
try log.boost(.{ foe_ident, .Attack, -1 });
|
|
2550
|
+
try log.boost(.{ foe_ident, .Attack, 1 });
|
|
2591
2551
|
}
|
|
2592
|
-
return
|
|
2552
|
+
return log.fail(.{ foe_ident, .None });
|
|
2593
2553
|
}
|
|
2594
2554
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2595
2555
|
const stat = unmodifiedStats(battle, foe).atk;
|
|
2596
2556
|
stats.atk = @max(1, stat * mod[0] / mod[1]);
|
|
2597
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2557
|
+
try log.boost(.{ foe_ident, .Attack, -1 });
|
|
2598
2558
|
},
|
|
2599
2559
|
.DefenseDown1, .DefenseDown2, .DefenseDownChance => {
|
|
2600
2560
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2601
|
-
if (boosts.def == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2561
|
+
if (boosts.def == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2602
2562
|
const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
|
|
2603
2563
|
boosts.def = @intCast(@max(-6, @as(i8, boosts.def) - n));
|
|
2604
2564
|
if (stats.def == 1) {
|
|
2605
2565
|
boosts.def += 1;
|
|
2606
2566
|
if (showdown) {
|
|
2607
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2608
|
-
try log.boost(foe_ident, .Defense, 1);
|
|
2567
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2568
|
+
try log.boost(.{ foe_ident, .Defense, 1 });
|
|
2609
2569
|
}
|
|
2610
|
-
return
|
|
2570
|
+
return log.fail(.{ foe_ident, .None });
|
|
2611
2571
|
}
|
|
2612
2572
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2613
2573
|
const stat = unmodifiedStats(battle, foe).def;
|
|
2614
2574
|
stats.def = @max(1, stat * mod[0] / mod[1]);
|
|
2615
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2575
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2616
2576
|
},
|
|
2617
2577
|
.SpeedDown1, .SpeedDownChance => {
|
|
2618
2578
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2619
|
-
if (boosts.spe == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2579
|
+
if (boosts.spe == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2620
2580
|
boosts.spe = @intCast(@max(-6, @as(i8, boosts.spe) - 1));
|
|
2621
2581
|
if (stats.spe == 1) {
|
|
2622
2582
|
boosts.spe += 1;
|
|
2623
2583
|
if (showdown) {
|
|
2624
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2625
|
-
try log.boost(foe_ident, .Speed, 1);
|
|
2584
|
+
try log.boost(.{ foe_ident, .Speed, -1 });
|
|
2585
|
+
try log.boost(.{ foe_ident, .Speed, 1 });
|
|
2626
2586
|
}
|
|
2627
|
-
return
|
|
2587
|
+
return log.fail(.{ foe_ident, .None });
|
|
2628
2588
|
}
|
|
2629
2589
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2630
2590
|
const stat = unmodifiedStats(battle, foe).spe;
|
|
2631
2591
|
stats.spe = @max(1, stat * mod[0] / mod[1]);
|
|
2632
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2592
|
+
try log.boost(.{ foe_ident, .Speed, -1 });
|
|
2633
2593
|
assert(boosts.spe >= -6);
|
|
2634
2594
|
},
|
|
2635
2595
|
.SpecialDownChance => {
|
|
2636
2596
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2637
|
-
if (boosts.spc == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2597
|
+
if (boosts.spc == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2638
2598
|
boosts.spc = @intCast(@max(-6, @as(i8, boosts.spc) - 1));
|
|
2639
2599
|
if (stats.spc == 1) {
|
|
2640
2600
|
boosts.spc += 1;
|
|
2641
2601
|
if (showdown) {
|
|
2642
|
-
try log.boost(foe_ident, .SpecialAttack, -1);
|
|
2643
|
-
try log.boost(foe_ident, .SpecialAttack, 1);
|
|
2644
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2645
|
-
try log.boost(foe_ident, .SpecialDefense, 1);
|
|
2602
|
+
try log.boost(.{ foe_ident, .SpecialAttack, -1 });
|
|
2603
|
+
try log.boost(.{ foe_ident, .SpecialAttack, 1 });
|
|
2604
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2605
|
+
try log.boost(.{ foe_ident, .SpecialDefense, 1 });
|
|
2646
2606
|
}
|
|
2647
|
-
return
|
|
2607
|
+
return log.fail(.{ foe_ident, .None });
|
|
2648
2608
|
}
|
|
2649
2609
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2650
2610
|
const stat = unmodifiedStats(battle, foe).spc;
|
|
2651
2611
|
stats.spc = @max(1, stat * mod[0] / mod[1]);
|
|
2652
|
-
try log.boost(foe_ident, .SpecialAttack, -1);
|
|
2653
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2612
|
+
try log.boost(.{ foe_ident, .SpecialAttack, -1 });
|
|
2613
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2654
2614
|
},
|
|
2655
2615
|
.AccuracyDown1 => {
|
|
2656
2616
|
assert(boosts.accuracy >= -6 and boosts.accuracy <= 6);
|
|
2657
|
-
if (boosts.accuracy == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2617
|
+
if (boosts.accuracy == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2658
2618
|
boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
|
|
2659
|
-
try log.boost(foe_ident, .Accuracy, -1);
|
|
2619
|
+
try log.boost(.{ foe_ident, .Accuracy, -1 });
|
|
2660
2620
|
},
|
|
2661
2621
|
else => unreachable,
|
|
2662
2622
|
}
|
|
@@ -2680,7 +2640,7 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
2680
2640
|
}
|
|
2681
2641
|
}
|
|
2682
2642
|
|
|
2683
|
-
|
|
2643
|
+
fn isForced(active: anytype) bool {
|
|
2684
2644
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
2685
2645
|
active.volatiles.Thrashing or active.volatiles.Charging;
|
|
2686
2646
|
}
|
|
@@ -2693,27 +2653,27 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2693
2653
|
|
|
2694
2654
|
options.chance.clearDurations(who, clear);
|
|
2695
2655
|
|
|
2696
|
-
if (volatiles.
|
|
2697
|
-
volatiles.
|
|
2698
|
-
volatiles.
|
|
2699
|
-
try log.end(ident, .DisableSilent);
|
|
2656
|
+
if (volatiles.disable_move != 0) {
|
|
2657
|
+
volatiles.disable_move = 0;
|
|
2658
|
+
volatiles.disable_duration = 0;
|
|
2659
|
+
try log.end(.{ ident, .DisableSilent });
|
|
2700
2660
|
}
|
|
2701
2661
|
if (volatiles.Confusion) {
|
|
2702
2662
|
// volatiles.confusion is left unchanged
|
|
2703
2663
|
volatiles.Confusion = false;
|
|
2704
|
-
try log.end(ident, .ConfusionSilent);
|
|
2664
|
+
try log.end(.{ ident, .ConfusionSilent });
|
|
2705
2665
|
}
|
|
2706
2666
|
if (volatiles.Mist) {
|
|
2707
2667
|
volatiles.Mist = false;
|
|
2708
|
-
try log.end(ident, .
|
|
2668
|
+
try log.end(.{ ident, .MistSilent });
|
|
2709
2669
|
}
|
|
2710
2670
|
if (volatiles.FocusEnergy) {
|
|
2711
2671
|
volatiles.FocusEnergy = false;
|
|
2712
|
-
try log.end(ident, .
|
|
2672
|
+
try log.end(.{ ident, .FocusEnergySilent });
|
|
2713
2673
|
}
|
|
2714
2674
|
if (volatiles.LeechSeed) {
|
|
2715
2675
|
volatiles.LeechSeed = false;
|
|
2716
|
-
try log.end(ident, .
|
|
2676
|
+
try log.end(.{ ident, .LeechSeedSilent });
|
|
2717
2677
|
}
|
|
2718
2678
|
if (!showdown and volatiles.Toxic) {
|
|
2719
2679
|
volatiles.Toxic = false;
|
|
@@ -2721,22 +2681,22 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2721
2681
|
}
|
|
2722
2682
|
if (volatiles.LightScreen) {
|
|
2723
2683
|
volatiles.LightScreen = false;
|
|
2724
|
-
try log.end(ident, .
|
|
2684
|
+
try log.end(.{ ident, .LightScreenSilent });
|
|
2725
2685
|
}
|
|
2726
2686
|
if (volatiles.Reflect) {
|
|
2727
2687
|
volatiles.Reflect = false;
|
|
2728
|
-
try log.end(ident, .
|
|
2688
|
+
try log.end(.{ ident, .ReflectSilent });
|
|
2729
2689
|
}
|
|
2730
2690
|
if (showdown and volatiles.Toxic) {
|
|
2731
2691
|
volatiles.Toxic = false;
|
|
2732
2692
|
// Pokémon Showdown erroneously clears the toxic counter
|
|
2733
2693
|
volatiles.toxic = 0;
|
|
2734
|
-
try log.end(ident, .
|
|
2694
|
+
try log.end(.{ ident, .ToxicSilent });
|
|
2735
2695
|
}
|
|
2736
2696
|
}
|
|
2737
2697
|
|
|
2738
2698
|
pub const Rolls = struct {
|
|
2739
|
-
|
|
2699
|
+
fn speedTie(battle: anytype, options: anytype) !bool {
|
|
2740
2700
|
const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
|
|
2741
2701
|
player == .P1
|
|
2742
2702
|
else if (showdown)
|
|
@@ -2748,7 +2708,7 @@ pub const Rolls = struct {
|
|
|
2748
2708
|
return p1;
|
|
2749
2709
|
}
|
|
2750
2710
|
|
|
2751
|
-
|
|
2711
|
+
fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
|
|
2752
2712
|
const crit = if (options.calc.overridden(player, .critical_hit)) |val|
|
|
2753
2713
|
val == .true
|
|
2754
2714
|
else if (showdown)
|
|
@@ -2760,7 +2720,7 @@ pub const Rolls = struct {
|
|
|
2760
2720
|
return crit;
|
|
2761
2721
|
}
|
|
2762
2722
|
|
|
2763
|
-
|
|
2723
|
+
fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2764
2724
|
const roll = if (options.calc.overridden(player, .damage)) |val|
|
|
2765
2725
|
val
|
|
2766
2726
|
else roll: {
|
|
@@ -2776,7 +2736,7 @@ pub const Rolls = struct {
|
|
|
2776
2736
|
return roll;
|
|
2777
2737
|
}
|
|
2778
2738
|
|
|
2779
|
-
|
|
2739
|
+
fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) bool {
|
|
2780
2740
|
const ok = if (options.calc.overridden(player, .hit)) |val|
|
|
2781
2741
|
val == .true
|
|
2782
2742
|
else if (showdown)
|
|
@@ -2788,7 +2748,7 @@ pub const Rolls = struct {
|
|
|
2788
2748
|
return ok;
|
|
2789
2749
|
}
|
|
2790
2750
|
|
|
2791
|
-
|
|
2751
|
+
fn confused(battle: anytype, player: Player, options: anytype) !bool {
|
|
2792
2752
|
const cfz = if (options.calc.overridden(player, .confused)) |val|
|
|
2793
2753
|
val == .true
|
|
2794
2754
|
else if (showdown)
|
|
@@ -2800,7 +2760,7 @@ pub const Rolls = struct {
|
|
|
2800
2760
|
return cfz;
|
|
2801
2761
|
}
|
|
2802
2762
|
|
|
2803
|
-
|
|
2763
|
+
fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
|
|
2804
2764
|
const par = if (options.calc.overridden(player, .paralyzed)) |val|
|
|
2805
2765
|
val == .true
|
|
2806
2766
|
else if (showdown)
|
|
@@ -2812,7 +2772,7 @@ pub const Rolls = struct {
|
|
|
2812
2772
|
return par;
|
|
2813
2773
|
}
|
|
2814
2774
|
|
|
2815
|
-
|
|
2775
|
+
fn confusionChance(battle: anytype, player: Player, options: anytype) !bool {
|
|
2816
2776
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2817
2777
|
val == .true
|
|
2818
2778
|
else if (showdown)
|
|
@@ -2824,7 +2784,7 @@ pub const Rolls = struct {
|
|
|
2824
2784
|
return proc;
|
|
2825
2785
|
}
|
|
2826
2786
|
|
|
2827
|
-
|
|
2787
|
+
fn secondaryChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2828
2788
|
const rate: u8 = if (low) 26 else 77;
|
|
2829
2789
|
|
|
2830
2790
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
@@ -2838,7 +2798,7 @@ pub const Rolls = struct {
|
|
|
2838
2798
|
return proc;
|
|
2839
2799
|
}
|
|
2840
2800
|
|
|
2841
|
-
|
|
2801
|
+
fn poisonChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2842
2802
|
const rate: u8 = if (low) 52 else 103;
|
|
2843
2803
|
|
|
2844
2804
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
@@ -2852,7 +2812,7 @@ pub const Rolls = struct {
|
|
|
2852
2812
|
return proc;
|
|
2853
2813
|
}
|
|
2854
2814
|
|
|
2855
|
-
|
|
2815
|
+
fn unboost(battle: anytype, player: Player, options: anytype) !bool {
|
|
2856
2816
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2857
2817
|
val == .true
|
|
2858
2818
|
else if (showdown)
|
|
@@ -2864,14 +2824,12 @@ pub const Rolls = struct {
|
|
|
2864
2824
|
return proc;
|
|
2865
2825
|
}
|
|
2866
2826
|
|
|
2867
|
-
|
|
2827
|
+
fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
2868
2828
|
const move: Move = if (options.calc.overridden(player, .metronome)) |val|
|
|
2869
2829
|
val
|
|
2870
|
-
else if (showdown)
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
break :move @enumFromInt(r + mod);
|
|
2874
|
-
} else move: {
|
|
2830
|
+
else if (showdown)
|
|
2831
|
+
Move.METRONOME[battle.rng.range(u8, 0, Move.METRONOME.len)]
|
|
2832
|
+
else move: {
|
|
2875
2833
|
while (true) {
|
|
2876
2834
|
const r = battle.rng.next();
|
|
2877
2835
|
if (r == 0 or r == @intFromEnum(Move.Metronome)) continue;
|
|
@@ -2884,7 +2842,7 @@ pub const Rolls = struct {
|
|
|
2884
2842
|
return move;
|
|
2885
2843
|
}
|
|
2886
2844
|
|
|
2887
|
-
|
|
2845
|
+
fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
|
|
2888
2846
|
const power = if (options.calc.overridden(player, .psywave)) |val|
|
|
2889
2847
|
val - 1
|
|
2890
2848
|
else if (showdown)
|
|
@@ -2901,11 +2859,11 @@ pub const Rolls = struct {
|
|
|
2901
2859
|
return power;
|
|
2902
2860
|
}
|
|
2903
2861
|
|
|
2904
|
-
|
|
2862
|
+
fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2905
2863
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2906
2864
|
@intCast(val)
|
|
2907
2865
|
else if (showdown)
|
|
2908
|
-
|
|
2866
|
+
battle.rng.range(u3, 1, 8)
|
|
2909
2867
|
else duration: {
|
|
2910
2868
|
while (true) {
|
|
2911
2869
|
const r = battle.rng.next() & 7;
|
|
@@ -2918,11 +2876,11 @@ pub const Rolls = struct {
|
|
|
2918
2876
|
return duration;
|
|
2919
2877
|
}
|
|
2920
2878
|
|
|
2921
|
-
|
|
2879
|
+
fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
|
|
2922
2880
|
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2923
2881
|
val
|
|
2924
2882
|
else if (showdown)
|
|
2925
|
-
|
|
2883
|
+
battle.rng.range(u4, 1, 9)
|
|
2926
2884
|
else
|
|
2927
2885
|
@intCast((battle.rng.next() & 7) + 1);
|
|
2928
2886
|
|
|
@@ -2931,11 +2889,11 @@ pub const Rolls = struct {
|
|
|
2931
2889
|
return duration;
|
|
2932
2890
|
}
|
|
2933
2891
|
|
|
2934
|
-
|
|
2892
|
+
fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
|
|
2935
2893
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2936
2894
|
@intCast(val)
|
|
2937
2895
|
else if (showdown)
|
|
2938
|
-
|
|
2896
|
+
battle.rng.range(u3, 2, 6)
|
|
2939
2897
|
else
|
|
2940
2898
|
@intCast((battle.rng.next() & 3) + 2);
|
|
2941
2899
|
|
|
@@ -2944,11 +2902,11 @@ pub const Rolls = struct {
|
|
|
2944
2902
|
return duration;
|
|
2945
2903
|
}
|
|
2946
2904
|
|
|
2947
|
-
|
|
2905
|
+
fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2948
2906
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2949
2907
|
@intCast(val)
|
|
2950
2908
|
else if (showdown)
|
|
2951
|
-
|
|
2909
|
+
battle.rng.range(u3, 2, 4)
|
|
2952
2910
|
else
|
|
2953
2911
|
@intCast((battle.rng.next() & 1) + 2);
|
|
2954
2912
|
|
|
@@ -2957,7 +2915,9 @@ pub const Rolls = struct {
|
|
|
2957
2915
|
return duration;
|
|
2958
2916
|
}
|
|
2959
2917
|
|
|
2960
|
-
|
|
2918
|
+
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
2919
|
+
|
|
2920
|
+
fn distribution(
|
|
2961
2921
|
battle: anytype,
|
|
2962
2922
|
comptime effect: Move.Effect,
|
|
2963
2923
|
player: Player,
|
|
@@ -2967,7 +2927,7 @@ pub const Rolls = struct {
|
|
|
2967
2927
|
const n: u3 = if (options.calc.overridden(player, roll)) |val|
|
|
2968
2928
|
@intCast(val)
|
|
2969
2929
|
else if (showdown)
|
|
2970
|
-
DISTRIBUTION[battle.rng.range(
|
|
2930
|
+
DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
|
|
2971
2931
|
else n: {
|
|
2972
2932
|
const r = (battle.rng.next() & 3);
|
|
2973
2933
|
break :n @intCast((if (r < 2) r else battle.rng.next() & 3) + 2);
|
|
@@ -3014,10 +2974,9 @@ pub const Rolls = struct {
|
|
|
3014
2974
|
while (i < moves.len and r > 0) : (i += 1) {
|
|
3015
2975
|
if (moves[i].pp > 0) {
|
|
3016
2976
|
r -= 1;
|
|
3017
|
-
if (r == 0) break;
|
|
2977
|
+
if (r == 0) break :slot @intCast(i + 1);
|
|
3018
2978
|
}
|
|
3019
2979
|
}
|
|
3020
|
-
break :slot @intCast(i + 1);
|
|
3021
2980
|
}
|
|
3022
2981
|
}
|
|
3023
2982
|
|
|
@@ -3039,11 +2998,11 @@ test "RNG agreement" {
|
|
|
3039
2998
|
expected[i] = @intCast(i * 0x1000000);
|
|
3040
2999
|
}
|
|
3041
3000
|
|
|
3042
|
-
var spe
|
|
3043
|
-
var cfz
|
|
3044
|
-
var par
|
|
3045
|
-
var brn
|
|
3046
|
-
var eff
|
|
3001
|
+
var spe: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3002
|
+
var cfz: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3003
|
+
var par: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3004
|
+
var brn: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3005
|
+
var eff: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3047
3006
|
|
|
3048
3007
|
for (0..expected.len) |i| {
|
|
3049
3008
|
try expectEqual(spe.range(u8, 0, 2) == 0, i < Gen12.percent(50) + 1);
|
|
@@ -3133,7 +3092,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3133
3092
|
if (m.id == if (active.volatiles.Bide) .Bide else side.last_selected_move) {
|
|
3134
3093
|
// Pokémon Showdown displays Struggle if limited to Bide but unable to pick
|
|
3135
3094
|
const struggle =
|
|
3136
|
-
m.id == .Bide and (m.pp == 0 or active.volatiles.
|
|
3095
|
+
m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
|
|
3137
3096
|
const s = if (struggle) 0 else slot;
|
|
3138
3097
|
out[n] = .{ .type = .Move, .data = s };
|
|
3139
3098
|
n += 1;
|
|
@@ -3148,7 +3107,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3148
3107
|
const m = active.moves[slot - 1];
|
|
3149
3108
|
if (m.id == .None) break;
|
|
3150
3109
|
if (m.pp == 0) continue;
|
|
3151
|
-
if (active.volatiles.
|
|
3110
|
+
if (active.volatiles.disable_move == slot) continue;
|
|
3152
3111
|
out[n] = .{ .type = .Move, .data = slot };
|
|
3153
3112
|
n += 1;
|
|
3154
3113
|
}
|