@pkmn/engine 0.1.0-dev.d3e796d5 → 0.1.0-dev.d4119c62
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 +8 -8
- 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/index.d.ts +2 -1
- package/build/pkg/index.js.map +1 -1
- package/build/pkg/protocol.js +57 -38
- 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 +40 -23
- package/package.json +31 -18
- package/src/bin/install-pkmn-engine +1 -1
- package/src/lib/binding/node.zig +1 -1
- package/src/lib/common/data.zig +8 -20
- package/src/lib/common/optional.zig +8 -1
- package/src/lib/common/protocol.zig +722 -296
- package/src/lib/common/rational.zig +5 -5
- package/src/lib/common/rng.zig +15 -6
- package/src/lib/gen1/README.md +31 -29
- package/src/lib/gen1/calc.zig +4 -4
- package/src/lib/gen1/chance.zig +55 -39
- package/src/lib/gen1/data/moves.zig +192 -180
- package/src/lib/gen1/data/species.zig +2 -2
- package/src/lib/gen1/data/types.zig +7 -7
- package/src/lib/gen1/data.zig +31 -43
- package/src/lib/gen1/helpers.zig +26 -41
- package/src/lib/gen1/mechanics.zig +297 -335
- 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 +649 -568
- package/src/lib/gen2/data/species.zig +1 -1
- package/src/lib/gen2/data/types.zig +134 -31
- package/src/lib/gen2/data.zig +70 -67
- package/src/lib/gen2/generated.zig +808 -0
- package/src/lib/gen2/helpers.zig +4 -9
- package/src/lib/gen2/mechanics.zig +3135 -102
|
@@ -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;
|
|
@@ -1435,7 +1402,7 @@ fn moveHit(
|
|
|
1435
1402
|
const overwritten = overwrite and state > 0;
|
|
1436
1403
|
assert(!overwritten or (0 < state and state <= 255 and !side.active.volatiles.Bide));
|
|
1437
1404
|
|
|
1438
|
-
var accuracy = if (overwritten) state else @as(u16,
|
|
1405
|
+
var accuracy = if (overwritten) state else @as(u16, move.accuracy);
|
|
1439
1406
|
var boost = BOOSTS[@as(u4, @intCast(@as(i8, side.active.boosts.accuracy) + 6))];
|
|
1440
1407
|
accuracy = accuracy * boost[0] / boost[1];
|
|
1441
1408
|
boost = BOOSTS[@as(u4, @intCast(@as(i8, -foe.active.boosts.evasion) + 6))];
|
|
@@ -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
|
}
|
|
@@ -1545,21 +1511,19 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1545
1511
|
var volatiles = &side.active.volatiles;
|
|
1546
1512
|
|
|
1547
1513
|
const brn = Status.is(stored.status, .BRN);
|
|
1548
|
-
if (brn or Status.is(stored.status, .PSN))
|
|
1514
|
+
if (brn or Status.is(stored.status, .PSN)) {
|
|
1549
1515
|
var damage = @max(stored.stats.hp / 16, 1);
|
|
1550
|
-
|
|
1551
1516
|
if (volatiles.Toxic) {
|
|
1552
1517
|
volatiles.toxic += 1;
|
|
1553
1518
|
damage *= volatiles.toxic;
|
|
1554
1519
|
}
|
|
1520
|
+
damage = @min(damage, stored.hp);
|
|
1555
1521
|
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
// Pokémon Showdown uses damageOf here but its not relevant in Generation I
|
|
1562
|
-
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
1522
|
+
if (!showdown or damage > 0) {
|
|
1523
|
+
stored.hp -= damage;
|
|
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 });
|
|
1526
|
+
}
|
|
1563
1527
|
}
|
|
1564
1528
|
|
|
1565
1529
|
if (volatiles.LeechSeed) {
|
|
@@ -1583,14 +1547,14 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1583
1547
|
const amount = @min(damage, stored.hp);
|
|
1584
1548
|
stored.hp -= amount;
|
|
1585
1549
|
|
|
1586
|
-
// As above, Pokémon Showdown uses
|
|
1587
|
-
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 });
|
|
1588
1552
|
|
|
1589
1553
|
const before = foe_stored.hp;
|
|
1590
1554
|
// Uncapped damage is added back to the foe
|
|
1591
1555
|
foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
|
|
1592
1556
|
// Pokémon Showdown uses the less specific heal here instead of drain... because reasons?
|
|
1593
|
-
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 });
|
|
1594
1558
|
}
|
|
1595
1559
|
}
|
|
1596
1560
|
|
|
@@ -1601,18 +1565,18 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1601
1565
|
battle.turn += 1;
|
|
1602
1566
|
|
|
1603
1567
|
if (showdown and pkmn.options.ebc and checkEBC(battle)) {
|
|
1604
|
-
try options.log.tie();
|
|
1568
|
+
try options.log.tie(.{});
|
|
1605
1569
|
return Result.Tie;
|
|
1606
1570
|
}
|
|
1607
1571
|
|
|
1608
1572
|
if (showdown and battle.turn >= 1000) {
|
|
1609
|
-
try options.log.tie();
|
|
1573
|
+
try options.log.tie(.{});
|
|
1610
1574
|
return Result.Tie;
|
|
1611
1575
|
} else if (battle.turn >= 65535) {
|
|
1612
1576
|
return Result.Error;
|
|
1613
1577
|
}
|
|
1614
1578
|
|
|
1615
|
-
try options.log.turn(battle.turn);
|
|
1579
|
+
try options.log.turn(.{battle.turn});
|
|
1616
1580
|
|
|
1617
1581
|
return Result.Default;
|
|
1618
1582
|
}
|
|
@@ -1672,9 +1636,9 @@ fn checkEBC(battle: anytype) bool {
|
|
|
1672
1636
|
}
|
|
1673
1637
|
|
|
1674
1638
|
fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
1675
|
-
if (side.active.volatiles.
|
|
1639
|
+
if (side.active.volatiles.disable_move != 0) {
|
|
1676
1640
|
// A Pokémon that transforms after being disabled may end up with less move slots
|
|
1677
|
-
const m = side.active.moves[side.active.volatiles.
|
|
1641
|
+
const m = side.active.moves[side.active.volatiles.disable_move - 1].id;
|
|
1678
1642
|
// side.last_selected_move can be Struggle here on Pokemon Showdown we need an extra check
|
|
1679
1643
|
const last = if (showdown and m == .Bide and side.active.volatiles.Bide)
|
|
1680
1644
|
m
|
|
@@ -1682,14 +1646,14 @@ fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
|
1682
1646
|
side.last_selected_move;
|
|
1683
1647
|
if (m != .None and m == last) {
|
|
1684
1648
|
side.active.volatiles.Charging = false;
|
|
1685
|
-
try options.log.
|
|
1649
|
+
try options.log.cant(.{ ident, .Disable, last });
|
|
1686
1650
|
return true;
|
|
1687
1651
|
}
|
|
1688
1652
|
}
|
|
1689
1653
|
return false;
|
|
1690
1654
|
}
|
|
1691
1655
|
|
|
1692
|
-
|
|
1656
|
+
fn buildRage(battle: anytype, who: Player, options: anytype) !void {
|
|
1693
1657
|
const side = battle.side(who);
|
|
1694
1658
|
if (side.active.volatiles.Rage and side.active.boosts.atk < 6) {
|
|
1695
1659
|
try Effects.boost(battle, who, Move.get(.Rage), options);
|
|
@@ -1707,7 +1671,7 @@ fn handleThrashing(battle: anytype, active: *ActivePokemon, player: Player, opti
|
|
|
1707
1671
|
return true;
|
|
1708
1672
|
}
|
|
1709
1673
|
|
|
1710
|
-
|
|
1674
|
+
fn onBegin(
|
|
1711
1675
|
battle: anytype,
|
|
1712
1676
|
player: Player,
|
|
1713
1677
|
move: Move.Data,
|
|
@@ -1737,7 +1701,7 @@ inline fn onBegin(
|
|
|
1737
1701
|
};
|
|
1738
1702
|
}
|
|
1739
1703
|
|
|
1740
|
-
|
|
1704
|
+
fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1741
1705
|
assert(move.effect.onEnd());
|
|
1742
1706
|
return switch (move.effect) {
|
|
1743
1707
|
.Bide => Effects.bide(battle, player, options),
|
|
@@ -1753,7 +1717,7 @@ inline fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anyty
|
|
|
1753
1717
|
};
|
|
1754
1718
|
}
|
|
1755
1719
|
|
|
1756
|
-
|
|
1720
|
+
fn alwaysHappens(
|
|
1757
1721
|
battle: anytype,
|
|
1758
1722
|
player: Player,
|
|
1759
1723
|
move: Move.Data,
|
|
@@ -1772,7 +1736,7 @@ inline fn alwaysHappens(
|
|
|
1772
1736
|
};
|
|
1773
1737
|
}
|
|
1774
1738
|
|
|
1775
|
-
|
|
1739
|
+
fn moveEffect(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1776
1740
|
return switch (move.effect) {
|
|
1777
1741
|
.BurnChance1, .BurnChance2 => Effects.burnChance(battle, player, move, options),
|
|
1778
1742
|
.ConfusionChance => Effects.confusion(battle, player, move, options),
|
|
@@ -1799,7 +1763,7 @@ pub const Effects = struct {
|
|
|
1799
1763
|
side.active.volatiles.state = 0;
|
|
1800
1764
|
side.active.volatiles.attacks = Rolls.attackingDuration(battle, player, options);
|
|
1801
1765
|
|
|
1802
|
-
try options.log.start(battle.active(player), .Bide);
|
|
1766
|
+
try options.log.start(.{ battle.active(player), .Bide });
|
|
1803
1767
|
}
|
|
1804
1768
|
|
|
1805
1769
|
fn burnChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1813,11 +1777,11 @@ pub const Effects = struct {
|
|
|
1813
1777
|
// GLITCH: Freeze top move selection desync can occur if thawed Pokémon is slower
|
|
1814
1778
|
if (Status.is(foe_stored.status, .FRZ)) {
|
|
1815
1779
|
assert(move.type == .Fire);
|
|
1816
|
-
try options.log.curestatus(
|
|
1780
|
+
try options.log.curestatus(.{
|
|
1817
1781
|
battle.active(player.foe()),
|
|
1818
1782
|
foe_stored.status,
|
|
1819
1783
|
.Message,
|
|
1820
|
-
);
|
|
1784
|
+
});
|
|
1821
1785
|
foe_stored.status = 0;
|
|
1822
1786
|
}
|
|
1823
1787
|
return;
|
|
@@ -1831,7 +1795,7 @@ pub const Effects = struct {
|
|
|
1831
1795
|
foe_stored.status = Status.init(.BRN);
|
|
1832
1796
|
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
1833
1797
|
|
|
1834
|
-
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 });
|
|
1835
1799
|
}
|
|
1836
1800
|
|
|
1837
1801
|
fn charge(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -1841,8 +1805,8 @@ pub const Effects = struct {
|
|
|
1841
1805
|
volatiles.Charging = true;
|
|
1842
1806
|
const move = side.last_selected_move;
|
|
1843
1807
|
if (move == .Fly or move == .Dig) volatiles.Invulnerable = true;
|
|
1844
|
-
try options.log.laststill();
|
|
1845
|
-
try options.log.prepare(battle.active(player), move);
|
|
1808
|
+
try options.log.laststill(.{});
|
|
1809
|
+
try options.log.prepare(.{ battle.active(player), move });
|
|
1846
1810
|
}
|
|
1847
1811
|
|
|
1848
1812
|
fn confusion(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1853,17 +1817,11 @@ pub const Effects = struct {
|
|
|
1853
1817
|
if (!try Rolls.confusionChance(battle, player, options)) return;
|
|
1854
1818
|
} else {
|
|
1855
1819
|
if (showdown) {
|
|
1856
|
-
if (!try checkHit(battle, player, move, options))
|
|
1857
|
-
|
|
1858
|
-
} else if (sub) {
|
|
1859
|
-
return options.log.fail(battle.active(player.foe()), .None);
|
|
1860
|
-
}
|
|
1820
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1821
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1861
1822
|
} else {
|
|
1862
|
-
if (sub) {
|
|
1863
|
-
|
|
1864
|
-
} else if (!try checkHit(battle, player, move, options)) {
|
|
1865
|
-
return;
|
|
1866
|
-
}
|
|
1823
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1824
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1867
1825
|
}
|
|
1868
1826
|
try options.chance.commit(player, .hit);
|
|
1869
1827
|
}
|
|
@@ -1872,23 +1830,24 @@ pub const Effects = struct {
|
|
|
1872
1830
|
foe.active.volatiles.Confusion = true;
|
|
1873
1831
|
foe.active.volatiles.confusion = Rolls.confusionDuration(battle, player, false, options);
|
|
1874
1832
|
|
|
1875
|
-
try options.log.start(battle.active(player.foe()), .Confusion);
|
|
1833
|
+
try options.log.start(.{ battle.active(player.foe()), .Confusion });
|
|
1876
1834
|
}
|
|
1877
1835
|
|
|
1878
1836
|
fn conversion(battle: anytype, player: Player, options: anytype) !void {
|
|
1879
1837
|
const foe = battle.foe(player);
|
|
1880
1838
|
|
|
1881
1839
|
if (foe.active.volatiles.Invulnerable) {
|
|
1882
|
-
try options.log.lastmiss();
|
|
1883
|
-
return options.log.miss(battle.active(player));
|
|
1840
|
+
try options.log.lastmiss(.{});
|
|
1841
|
+
return options.log.miss(.{battle.active(player)});
|
|
1884
1842
|
}
|
|
1885
1843
|
|
|
1886
1844
|
battle.side(player).active.types = foe.active.types;
|
|
1887
|
-
return options.log.
|
|
1845
|
+
return options.log.start(.{
|
|
1888
1846
|
battle.active(player),
|
|
1847
|
+
.TypeChange,
|
|
1889
1848
|
foe.active.types,
|
|
1890
1849
|
battle.active(player.foe()),
|
|
1891
|
-
);
|
|
1850
|
+
});
|
|
1892
1851
|
}
|
|
1893
1852
|
|
|
1894
1853
|
fn disable(battle: anytype, player: Player, move: Move.Data, options: anytype) !?Result {
|
|
@@ -1902,8 +1861,8 @@ pub const Effects = struct {
|
|
|
1902
1861
|
try options.chance.commit(player, .hit);
|
|
1903
1862
|
}
|
|
1904
1863
|
|
|
1905
|
-
if (volatiles.
|
|
1906
|
-
try options.log.fail(foe_ident, .None);
|
|
1864
|
+
if (volatiles.disable_move != 0) {
|
|
1865
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1907
1866
|
return null;
|
|
1908
1867
|
}
|
|
1909
1868
|
|
|
@@ -1919,7 +1878,7 @@ pub const Effects = struct {
|
|
|
1919
1878
|
// Technically this is still considered simply a "miss" on the cartridge,
|
|
1920
1879
|
// but diverging from Pokémon Showdown here would mostly just be pedantic
|
|
1921
1880
|
if (n == 0) {
|
|
1922
|
-
try options.log.fail(foe_ident, .None);
|
|
1881
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1923
1882
|
return null;
|
|
1924
1883
|
} else if (err) {
|
|
1925
1884
|
// GLITCH: Transform + Mirror Move / Metronome PP softlock
|
|
@@ -1927,18 +1886,18 @@ pub const Effects = struct {
|
|
|
1927
1886
|
return Result.Error;
|
|
1928
1887
|
}
|
|
1929
1888
|
|
|
1930
|
-
volatiles.
|
|
1889
|
+
volatiles.disable_move =
|
|
1931
1890
|
@intCast(try Rolls.moveSlot(battle, player, &foe.active.moves, n, options));
|
|
1932
|
-
volatiles.
|
|
1891
|
+
volatiles.disable_duration = Rolls.disableDuration(battle, player, options);
|
|
1933
1892
|
|
|
1934
|
-
const id = foe.active.move(volatiles.
|
|
1935
|
-
try options.log.
|
|
1893
|
+
const id = foe.active.move(volatiles.disable_move).id;
|
|
1894
|
+
try options.log.start(.{ foe_ident, .Disable, id });
|
|
1936
1895
|
return null;
|
|
1937
1896
|
}
|
|
1938
1897
|
|
|
1939
1898
|
fn drainHP(battle: anytype, player: Player, options: anytype) !void {
|
|
1940
|
-
var
|
|
1941
|
-
|
|
1899
|
+
var stored = battle.side(player).stored();
|
|
1900
|
+
const ident = battle.active(player);
|
|
1942
1901
|
|
|
1943
1902
|
if (battle.last_damage == 0) {
|
|
1944
1903
|
assert(showdown);
|
|
@@ -1951,7 +1910,7 @@ pub const Effects = struct {
|
|
|
1951
1910
|
if (stored.hp == stored.stats.hp) return;
|
|
1952
1911
|
stored.hp = @min(stored.stats.hp, stored.hp + drain);
|
|
1953
1912
|
|
|
1954
|
-
try options.log.
|
|
1913
|
+
try options.log.heal(.{ ident, stored, .Drain, battle.active(player.foe()) });
|
|
1955
1914
|
}
|
|
1956
1915
|
|
|
1957
1916
|
fn explode(battle: anytype, player: Player) !void {
|
|
@@ -1983,7 +1942,7 @@ pub const Effects = struct {
|
|
|
1983
1942
|
if (side.active.volatiles.FocusEnergy) return;
|
|
1984
1943
|
side.active.volatiles.FocusEnergy = true;
|
|
1985
1944
|
|
|
1986
|
-
try options.log.start(battle.active(player), .FocusEnergy);
|
|
1945
|
+
try options.log.start(.{ battle.active(player), .FocusEnergy });
|
|
1987
1946
|
}
|
|
1988
1947
|
|
|
1989
1948
|
fn freezeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2005,7 +1964,7 @@ pub const Effects = struct {
|
|
|
2005
1964
|
foe_stored.status = Status.init(.FRZ);
|
|
2006
1965
|
// GLITCH: Hyper Beam recharging status is not cleared
|
|
2007
1966
|
|
|
2008
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
1967
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2009
1968
|
}
|
|
2010
1969
|
|
|
2011
1970
|
fn haze(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -2025,8 +1984,8 @@ pub const Effects = struct {
|
|
|
2025
1984
|
side.active.stats = side_stored.stats;
|
|
2026
1985
|
foe.active.stats = foe_stored.stats;
|
|
2027
1986
|
|
|
2028
|
-
try log.activate(player_ident, .Haze);
|
|
2029
|
-
try log.clearallboost();
|
|
1987
|
+
try log.activate(.{ player_ident, .Haze });
|
|
1988
|
+
try log.clearallboost(.{});
|
|
2030
1989
|
|
|
2031
1990
|
// Pokémon Showdown clears P1 then P2 instead of status -> side -> foe
|
|
2032
1991
|
if (showdown) {
|
|
@@ -2034,11 +1993,11 @@ pub const Effects = struct {
|
|
|
2034
1993
|
const p: Player = @enumFromInt(i);
|
|
2035
1994
|
// Pokémon Showdown incorrectly does not prevent sleep/freeze from moving
|
|
2036
1995
|
if (p != player and Status.any(s.stored().status)) {
|
|
2037
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
1996
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2038
1997
|
s.stored().status = 0;
|
|
2039
1998
|
} else if (showdown and s.stored().status == Status.TOX) {
|
|
2040
1999
|
s.stored().status = Status.init(.PSN);
|
|
2041
|
-
try log.status(battle.active(p), s.stored().status, .None);
|
|
2000
|
+
try log.status(.{ battle.active(p), s.stored().status, .None });
|
|
2042
2001
|
}
|
|
2043
2002
|
try clearVolatiles(battle, p, p != player, options);
|
|
2044
2003
|
}
|
|
@@ -2047,7 +2006,7 @@ pub const Effects = struct {
|
|
|
2047
2006
|
if (Status.is(foe_stored.status, .FRZ) or Status.is(foe_stored.status, .SLP)) {
|
|
2048
2007
|
foe.last_selected_move = .SKIP_TURN;
|
|
2049
2008
|
}
|
|
2050
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
2009
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2051
2010
|
foe_stored.status = 0;
|
|
2052
2011
|
}
|
|
2053
2012
|
try clearVolatiles(battle, player, false, options);
|
|
@@ -2063,7 +2022,7 @@ pub const Effects = struct {
|
|
|
2063
2022
|
// GLITCH: HP recovery move failure glitches
|
|
2064
2023
|
const delta = stored.stats.hp - stored.hp;
|
|
2065
2024
|
if (delta == 0 or (delta & 255 == 255 and stored.hp % 256 != 0)) {
|
|
2066
|
-
return
|
|
2025
|
+
return options.log.fail(.{ ident, .None });
|
|
2067
2026
|
}
|
|
2068
2027
|
|
|
2069
2028
|
const rest = side.last_selected_move == .Rest;
|
|
@@ -2071,17 +2030,17 @@ pub const Effects = struct {
|
|
|
2071
2030
|
// Adding the sleep status runs the sleep condition handler to roll duration
|
|
2072
2031
|
if (showdown) battle.rng.advance(1);
|
|
2073
2032
|
stored.status = Status.slf(2);
|
|
2074
|
-
try options.log.
|
|
2033
|
+
try options.log.status(.{ ident, stored.status, .From, Move.Rest });
|
|
2075
2034
|
stored.hp = stored.stats.hp;
|
|
2076
2035
|
} else {
|
|
2077
2036
|
stored.hp = @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2));
|
|
2078
2037
|
}
|
|
2079
|
-
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 });
|
|
2080
2039
|
}
|
|
2081
2040
|
|
|
2082
2041
|
fn hyperBeam(battle: anytype, player: Player, options: anytype) !void {
|
|
2083
2042
|
battle.side(player).active.volatiles.Recharging = true;
|
|
2084
|
-
try options.log.mustrecharge(battle.active(player));
|
|
2043
|
+
try options.log.mustrecharge(.{battle.active(player)});
|
|
2085
2044
|
}
|
|
2086
2045
|
|
|
2087
2046
|
fn leechSeed(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2090,33 +2049,32 @@ pub const Effects = struct {
|
|
|
2090
2049
|
if (showdown) {
|
|
2091
2050
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2092
2051
|
if (!foe.active.volatiles.Invulnerable and foe.active.types.includes(.Grass)) {
|
|
2093
|
-
return options.log.immune(battle.active(player.foe()), .None);
|
|
2052
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
2094
2053
|
}
|
|
2095
2054
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2096
2055
|
if (foe.active.volatiles.LeechSeed) return;
|
|
2097
2056
|
} else {
|
|
2098
2057
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2099
2058
|
if (foe.active.types.includes(.Grass) or foe.active.volatiles.LeechSeed) {
|
|
2100
|
-
try options.log.lastmiss();
|
|
2101
|
-
return options.log.miss(battle.active(player));
|
|
2059
|
+
try options.log.lastmiss(.{});
|
|
2060
|
+
return options.log.miss(.{battle.active(player)});
|
|
2102
2061
|
}
|
|
2103
2062
|
}
|
|
2104
2063
|
|
|
2105
2064
|
try options.chance.commit(player, .hit);
|
|
2106
2065
|
foe.active.volatiles.LeechSeed = true;
|
|
2107
2066
|
|
|
2108
|
-
try options.log.start(battle.active(player.foe()), .LeechSeed);
|
|
2067
|
+
try options.log.start(.{ battle.active(player.foe()), .LeechSeed });
|
|
2109
2068
|
}
|
|
2110
2069
|
|
|
2111
2070
|
fn lightScreen(battle: anytype, player: Player, options: anytype) !void {
|
|
2112
2071
|
var side = battle.side(player);
|
|
2072
|
+
const ident = battle.active(player);
|
|
2113
2073
|
|
|
2114
|
-
if (side.active.volatiles.LightScreen) {
|
|
2115
|
-
return options.log.fail(battle.active(player), .None);
|
|
2116
|
-
}
|
|
2074
|
+
if (side.active.volatiles.LightScreen) return options.log.fail(.{ ident, .None });
|
|
2117
2075
|
side.active.volatiles.LightScreen = true;
|
|
2118
2076
|
|
|
2119
|
-
try options.log.start(
|
|
2077
|
+
try options.log.start(.{ ident, .LightScreen });
|
|
2120
2078
|
}
|
|
2121
2079
|
|
|
2122
2080
|
fn mimic(battle: anytype, player: Player, move: Move.Data, mslot: u8, options: anytype) !void {
|
|
@@ -2145,7 +2103,7 @@ pub const Effects = struct {
|
|
|
2145
2103
|
// Invulnerable foes or 1/256 miss can trigger |-miss| instead of |-fail|
|
|
2146
2104
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2147
2105
|
try options.chance.commit(player, .hit);
|
|
2148
|
-
return
|
|
2106
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2149
2107
|
}
|
|
2150
2108
|
}
|
|
2151
2109
|
if (!try checkHit(battle, player, move, options)) return;
|
|
@@ -2154,7 +2112,7 @@ pub const Effects = struct {
|
|
|
2154
2112
|
const rslot = try Rolls.moveSlot(battle, player, &foe.active.moves, 0, options);
|
|
2155
2113
|
side.active.move(oslot).id = foe.active.move(rslot).id;
|
|
2156
2114
|
|
|
2157
|
-
try options.log.
|
|
2115
|
+
try options.log.start(.{ battle.active(player), .Mimic, side.active.move(oslot).id });
|
|
2158
2116
|
}
|
|
2159
2117
|
|
|
2160
2118
|
fn mist(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -2163,7 +2121,7 @@ pub const Effects = struct {
|
|
|
2163
2121
|
if (side.active.volatiles.Mist) return;
|
|
2164
2122
|
side.active.volatiles.Mist = true;
|
|
2165
2123
|
|
|
2166
|
-
try options.log.start(battle.active(player), .Mist);
|
|
2124
|
+
try options.log.start(.{ battle.active(player), .Mist });
|
|
2167
2125
|
}
|
|
2168
2126
|
|
|
2169
2127
|
fn multiHit(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2189,17 +2147,18 @@ pub const Effects = struct {
|
|
|
2189
2147
|
|
|
2190
2148
|
if (showdown) {
|
|
2191
2149
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2192
|
-
if (immune and !foe.active.volatiles.Invulnerable)
|
|
2193
|
-
|
|
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;
|
|
2194
2153
|
}
|
|
2195
2154
|
if (Status.any(foe_stored.status)) {
|
|
2196
|
-
return log.fail(
|
|
2155
|
+
return log.fail(.{
|
|
2197
2156
|
foe_ident,
|
|
2198
|
-
if (Status.is(foe_stored.status, .PAR)) .Paralysis else .None,
|
|
2199
|
-
);
|
|
2157
|
+
if (Status.is(foe_stored.status, .PAR)) Fail.Paralysis else Fail.None,
|
|
2158
|
+
});
|
|
2200
2159
|
}
|
|
2201
2160
|
if (!showdown) {
|
|
2202
|
-
if (immune) return log.immune(foe_ident, .None);
|
|
2161
|
+
if (immune) return log.immune(.{ foe_ident, .None });
|
|
2203
2162
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2204
2163
|
}
|
|
2205
2164
|
try options.chance.commit(player, .hit);
|
|
@@ -2207,7 +2166,7 @@ pub const Effects = struct {
|
|
|
2207
2166
|
foe_stored.status = Status.init(.PAR);
|
|
2208
2167
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2209
2168
|
|
|
2210
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2169
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2211
2170
|
}
|
|
2212
2171
|
|
|
2213
2172
|
fn paralyzeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2229,12 +2188,12 @@ pub const Effects = struct {
|
|
|
2229
2188
|
foe_stored.status = Status.init(.PAR);
|
|
2230
2189
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2231
2190
|
|
|
2232
|
-
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 });
|
|
2233
2192
|
}
|
|
2234
2193
|
|
|
2235
2194
|
fn payDay(battle: anytype, player: Player, options: anytype) !void {
|
|
2236
2195
|
if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
|
|
2237
|
-
try options.log.fieldactivate();
|
|
2196
|
+
try options.log.fieldactivate(.{});
|
|
2238
2197
|
}
|
|
2239
2198
|
}
|
|
2240
2199
|
|
|
@@ -2249,23 +2208,23 @@ pub const Effects = struct {
|
|
|
2249
2208
|
return;
|
|
2250
2209
|
} else if (foe.active.volatiles.Substitute) {
|
|
2251
2210
|
if (move.effect != .Poison) return;
|
|
2252
|
-
return log.fail(foe_ident, .None);
|
|
2211
|
+
return log.fail(.{ foe_ident, .None });
|
|
2253
2212
|
} else if (Status.any(foe_stored.status)) {
|
|
2254
2213
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2255
2214
|
// Pokémon Showdown considers Toxic to be a status even in Generation I and so
|
|
2256
2215
|
// will not include a fail reason for Toxic vs. Poison or vice-versa...
|
|
2257
|
-
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))
|
|
2258
2217
|
if (!showdown)
|
|
2259
|
-
.Poison
|
|
2218
|
+
Fail.Poison
|
|
2260
2219
|
else if (toxic == (foe_stored.status == Status.TOX))
|
|
2261
|
-
if (toxic) .Toxic else .Poison
|
|
2220
|
+
if (toxic) Fail.Toxic else Fail.Poison
|
|
2262
2221
|
else
|
|
2263
|
-
.None
|
|
2222
|
+
Fail.None
|
|
2264
2223
|
else
|
|
2265
|
-
.None);
|
|
2224
|
+
Fail.None });
|
|
2266
2225
|
} else if (foe.active.types.includes(.Poison)) {
|
|
2267
2226
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2268
|
-
return log.immune(foe_ident, .None);
|
|
2227
|
+
return log.immune(.{ foe_ident, .None });
|
|
2269
2228
|
}
|
|
2270
2229
|
|
|
2271
2230
|
if (move.effect == .Poison) {
|
|
@@ -2284,7 +2243,7 @@ pub const Effects = struct {
|
|
|
2284
2243
|
foe.active.volatiles.toxic = 0;
|
|
2285
2244
|
}
|
|
2286
2245
|
|
|
2287
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2246
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2288
2247
|
}
|
|
2289
2248
|
|
|
2290
2249
|
fn rage(battle: anytype, player: Player) !void {
|
|
@@ -2304,22 +2263,23 @@ pub const Effects = struct {
|
|
|
2304
2263
|
@as(u8, if (side.last_selected_move == .Struggle) 2 else 4), 1));
|
|
2305
2264
|
stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
|
|
2306
2265
|
|
|
2307
|
-
try options.log.
|
|
2266
|
+
try options.log.damage(.{
|
|
2308
2267
|
battle.active(player),
|
|
2309
2268
|
stored,
|
|
2310
2269
|
.RecoilOf,
|
|
2311
2270
|
battle.active(player.foe()),
|
|
2312
|
-
);
|
|
2271
|
+
});
|
|
2313
2272
|
if (showdown and stored.hp == 0) residual.* = false;
|
|
2314
2273
|
}
|
|
2315
2274
|
|
|
2316
2275
|
fn reflect(battle: anytype, player: Player, options: anytype) !void {
|
|
2317
2276
|
var side = battle.side(player);
|
|
2277
|
+
const ident = battle.active(player);
|
|
2318
2278
|
|
|
2319
|
-
if (side.active.volatiles.Reflect) return options.log.fail(
|
|
2279
|
+
if (side.active.volatiles.Reflect) return options.log.fail(.{ ident, .None });
|
|
2320
2280
|
side.active.volatiles.Reflect = true;
|
|
2321
2281
|
|
|
2322
|
-
try options.log.start(
|
|
2282
|
+
try options.log.start(.{ ident, .Reflect });
|
|
2323
2283
|
}
|
|
2324
2284
|
|
|
2325
2285
|
fn sleep(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2333,10 +2293,10 @@ pub const Effects = struct {
|
|
|
2333
2293
|
// the Sleep Clause Mod might activate, causing us to not actually bypass
|
|
2334
2294
|
} else {
|
|
2335
2295
|
if (Status.any(foe_stored.status)) {
|
|
2336
|
-
return options.log.fail(
|
|
2296
|
+
return options.log.fail(.{
|
|
2337
2297
|
foe_ident,
|
|
2338
|
-
if (Status.is(foe_stored.status, .SLP)) .Sleep else .None,
|
|
2339
|
-
);
|
|
2298
|
+
if (Status.is(foe_stored.status, .SLP)) Fail.Sleep else Fail.None,
|
|
2299
|
+
});
|
|
2340
2300
|
}
|
|
2341
2301
|
// If checkHit in doMove didn't return true Pokémon Showdown wouldn't be in here
|
|
2342
2302
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
@@ -2353,18 +2313,17 @@ pub const Effects = struct {
|
|
|
2353
2313
|
|
|
2354
2314
|
foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
|
|
2355
2315
|
const last = battle.side(player).last_selected_move;
|
|
2356
|
-
try options.log.
|
|
2316
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .From, last });
|
|
2357
2317
|
}
|
|
2358
2318
|
|
|
2359
2319
|
fn splash(battle: anytype, player: Player, options: anytype) !void {
|
|
2360
|
-
try options.log.activate(battle.active(player), .Splash);
|
|
2320
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
2361
2321
|
}
|
|
2362
2322
|
|
|
2363
2323
|
fn substitute(battle: anytype, player: Player, residual: *bool, options: anytype) !void {
|
|
2364
2324
|
var side = battle.side(player);
|
|
2365
2325
|
if (side.active.volatiles.Substitute) {
|
|
2366
|
-
|
|
2367
|
-
return;
|
|
2326
|
+
return options.log.fail(.{ battle.active(player), .Substitute });
|
|
2368
2327
|
}
|
|
2369
2328
|
|
|
2370
2329
|
assert(side.stored().stats.hp <= 1023);
|
|
@@ -2376,28 +2335,32 @@ pub const Effects = struct {
|
|
|
2376
2335
|
// avoid having to convert to floating point)
|
|
2377
2336
|
const required_hp = if (showdown) @divFloor(side.stored().stats.hp - 1, 4) + 1 else hp;
|
|
2378
2337
|
if (side.stored().hp < required_hp) {
|
|
2379
|
-
|
|
2380
|
-
return;
|
|
2338
|
+
return options.log.fail(.{ battle.active(player), .Weak });
|
|
2381
2339
|
}
|
|
2382
2340
|
|
|
2383
2341
|
// GLITCH: can leave the user with 0 HP (faints later) because didn't check '<=' above
|
|
2384
2342
|
side.stored().hp -= hp;
|
|
2385
2343
|
side.active.volatiles.substitute = hp + 1;
|
|
2386
2344
|
side.active.volatiles.Substitute = true;
|
|
2387
|
-
try options.log.start(battle.active(player), .Substitute);
|
|
2345
|
+
try options.log.start(.{ battle.active(player), .Substitute });
|
|
2388
2346
|
if (hp > 0) {
|
|
2389
|
-
try options.log.damage(battle.active(player), side.stored(), .None);
|
|
2347
|
+
try options.log.damage(.{ battle.active(player), side.stored(), .None });
|
|
2390
2348
|
if (showdown and side.stored().hp == 0) residual.* = false;
|
|
2391
2349
|
}
|
|
2392
2350
|
}
|
|
2393
2351
|
|
|
2394
2352
|
fn switchAndTeleport(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
2395
|
-
if (!showdown or battle.side(player).last_selected_move == .Teleport) return;
|
|
2396
|
-
|
|
2397
2353
|
// Whirlwind/Roar should not roll to hit/reset damage but Pokémon Showdown does anyway
|
|
2398
|
-
if (
|
|
2399
|
-
|
|
2400
|
-
|
|
2354
|
+
if (showdown) {
|
|
2355
|
+
if (battle.side(player).last_selected_move == .Teleport) return;
|
|
2356
|
+
if (try checkHit(battle, player, move, options)) {
|
|
2357
|
+
try options.chance.commit(player, .hit);
|
|
2358
|
+
}
|
|
2359
|
+
battle.last_damage = 0;
|
|
2360
|
+
} else {
|
|
2361
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2362
|
+
try options.log.laststill(.{});
|
|
2363
|
+
}
|
|
2401
2364
|
}
|
|
2402
2365
|
|
|
2403
2366
|
fn thrashing(battle: anytype, player: Player, options: anytype) void {
|
|
@@ -2435,7 +2398,7 @@ pub const Effects = struct {
|
|
|
2435
2398
|
side.active.moves[i].pp = if (m.id != .None) 5 else 0;
|
|
2436
2399
|
}
|
|
2437
2400
|
|
|
2438
|
-
try options.log.transform(battle.active(player), foe_ident);
|
|
2401
|
+
try options.log.transform(.{ battle.active(player), foe_ident });
|
|
2439
2402
|
}
|
|
2440
2403
|
|
|
2441
2404
|
fn binding(battle: anytype, player: Player, rewrap: bool, options: anytype) !void {
|
|
@@ -2465,87 +2428,87 @@ pub const Effects = struct {
|
|
|
2465
2428
|
switch (move.effect) {
|
|
2466
2429
|
.AttackUp1, .AttackUp2, .Rage => {
|
|
2467
2430
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2468
|
-
if (boosts.atk == 6) return
|
|
2431
|
+
if (boosts.atk == 6) return log.fail(.{ ident, .None });
|
|
2469
2432
|
const n: u2 = if (move.effect == .AttackUp2) 2 else 1;
|
|
2470
2433
|
boosts.atk = @as(i4, @intCast(@min(6, @as(i8, boosts.atk) + n)));
|
|
2471
|
-
const reason = if (move.effect == .Rage)
|
|
2434
|
+
const reason: Boost = if (move.effect == .Rage) .Rage else .Attack;
|
|
2472
2435
|
if (stats.atk == MAX_STAT_VALUE) {
|
|
2473
2436
|
boosts.atk -= 1;
|
|
2474
2437
|
if (showdown) {
|
|
2475
|
-
try log.boost(ident, reason, n);
|
|
2476
|
-
try log.boost(ident,
|
|
2438
|
+
try log.boost(.{ ident, reason, n });
|
|
2439
|
+
try log.boost(.{ ident, .Attack, -1 });
|
|
2477
2440
|
if (move.effect == .Rage) return;
|
|
2478
2441
|
}
|
|
2479
|
-
return
|
|
2442
|
+
return log.fail(.{ ident, .None });
|
|
2480
2443
|
}
|
|
2481
2444
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2482
2445
|
const stat = unmodifiedStats(battle, side).atk;
|
|
2483
2446
|
stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2484
|
-
try log.boost(ident, reason, n);
|
|
2447
|
+
try log.boost(.{ ident, reason, n });
|
|
2485
2448
|
// Pokémon Showdown doesn't re-apply status modifiers after Rage boosts
|
|
2486
2449
|
if (showdown and move.effect == .Rage) return;
|
|
2487
2450
|
},
|
|
2488
2451
|
.DefenseUp1, .DefenseUp2 => {
|
|
2489
2452
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2490
|
-
if (boosts.def == 6) return
|
|
2453
|
+
if (boosts.def == 6) return log.fail(.{ ident, .None });
|
|
2491
2454
|
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
2492
2455
|
boosts.def = @intCast(@min(6, @as(i8, boosts.def) + n));
|
|
2493
2456
|
if (stats.def == MAX_STAT_VALUE) {
|
|
2494
2457
|
boosts.def -= 1;
|
|
2495
2458
|
if (showdown) {
|
|
2496
|
-
try log.boost(ident, .Defense, n);
|
|
2497
|
-
try log.boost(ident, .Defense, -1);
|
|
2459
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2460
|
+
try log.boost(.{ ident, .Defense, -1 });
|
|
2498
2461
|
}
|
|
2499
|
-
return
|
|
2462
|
+
return log.fail(.{ ident, .None });
|
|
2500
2463
|
}
|
|
2501
2464
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2502
2465
|
const stat = unmodifiedStats(battle, side).def;
|
|
2503
2466
|
stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2504
|
-
try log.boost(ident, .Defense, n);
|
|
2467
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2505
2468
|
},
|
|
2506
2469
|
.SpeedUp2 => {
|
|
2507
2470
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2508
|
-
if (boosts.spe == 6) return
|
|
2471
|
+
if (boosts.spe == 6) return log.fail(.{ ident, .None });
|
|
2509
2472
|
boosts.spe = @intCast(@min(6, @as(i8, boosts.spe) + 2));
|
|
2510
2473
|
if (stats.spe == MAX_STAT_VALUE) {
|
|
2511
2474
|
boosts.spe -= 1;
|
|
2512
2475
|
if (showdown) {
|
|
2513
|
-
try log.boost(ident, .Speed, 2);
|
|
2514
|
-
try log.boost(ident, .Speed, -1);
|
|
2476
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2477
|
+
try log.boost(.{ ident, .Speed, -1 });
|
|
2515
2478
|
}
|
|
2516
|
-
return
|
|
2479
|
+
return log.fail(.{ ident, .None });
|
|
2517
2480
|
}
|
|
2518
2481
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2519
2482
|
const stat = unmodifiedStats(battle, side).spe;
|
|
2520
2483
|
stats.spe = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2521
|
-
try log.boost(ident, .Speed, 2);
|
|
2484
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2522
2485
|
},
|
|
2523
2486
|
.SpecialUp1, .SpecialUp2 => {
|
|
2524
2487
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2525
|
-
if (boosts.spc == 6) return
|
|
2488
|
+
if (boosts.spc == 6) return log.fail(.{ ident, .None });
|
|
2526
2489
|
const n: u2 = if (move.effect == .SpecialUp2) 2 else 1;
|
|
2527
2490
|
boosts.spc = @intCast(@min(6, @as(i8, boosts.spc) + n));
|
|
2528
2491
|
if (stats.spc == MAX_STAT_VALUE) {
|
|
2529
2492
|
boosts.spc -= 1;
|
|
2530
2493
|
if (showdown) {
|
|
2531
|
-
try log.boost(ident, .SpecialAttack, n);
|
|
2532
|
-
try log.boost(ident, .SpecialAttack, -1);
|
|
2533
|
-
try log.boost(ident, .SpecialDefense, n);
|
|
2534
|
-
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 });
|
|
2535
2498
|
}
|
|
2536
|
-
return
|
|
2499
|
+
return log.fail(.{ ident, .None });
|
|
2537
2500
|
}
|
|
2538
2501
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2539
2502
|
const stat = unmodifiedStats(battle, side).spc;
|
|
2540
2503
|
stats.spc = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2541
|
-
try log.boost(ident, .SpecialAttack, n);
|
|
2542
|
-
try log.boost(ident, .SpecialDefense, n);
|
|
2504
|
+
try log.boost(.{ ident, .SpecialAttack, n });
|
|
2505
|
+
try log.boost(.{ ident, .SpecialDefense, n });
|
|
2543
2506
|
},
|
|
2544
2507
|
.EvasionUp1 => {
|
|
2545
2508
|
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2546
|
-
if (boosts.evasion == 6) return
|
|
2509
|
+
if (boosts.evasion == 6) return log.fail(.{ ident, .None });
|
|
2547
2510
|
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2548
|
-
try log.boost(ident, .Evasion, 1);
|
|
2511
|
+
try log.boost(.{ ident, .Evasion, 1 });
|
|
2549
2512
|
},
|
|
2550
2513
|
else => unreachable,
|
|
2551
2514
|
}
|
|
@@ -2560,7 +2523,7 @@ pub const Effects = struct {
|
|
|
2560
2523
|
const foe_ident = battle.active(player.foe());
|
|
2561
2524
|
const secondary = move.effect.isStatDownChance();
|
|
2562
2525
|
|
|
2563
|
-
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 });
|
|
2564
2527
|
|
|
2565
2528
|
if (secondary) {
|
|
2566
2529
|
const proc = try Rolls.unboost(battle, player, options);
|
|
@@ -2578,82 +2541,82 @@ pub const Effects = struct {
|
|
|
2578
2541
|
switch (move.effect) {
|
|
2579
2542
|
.AttackDown1, .AttackDownChance => {
|
|
2580
2543
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2581
|
-
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 });
|
|
2582
2545
|
boosts.atk = @intCast(@max(-6, @as(i8, boosts.atk) - 1));
|
|
2583
2546
|
if (stats.atk == 1) {
|
|
2584
2547
|
boosts.atk += 1;
|
|
2585
2548
|
if (showdown) {
|
|
2586
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2587
|
-
try log.boost(foe_ident, .Attack, 1);
|
|
2549
|
+
try log.boost(.{ foe_ident, .Attack, -1 });
|
|
2550
|
+
try log.boost(.{ foe_ident, .Attack, 1 });
|
|
2588
2551
|
}
|
|
2589
|
-
return
|
|
2552
|
+
return log.fail(.{ foe_ident, .None });
|
|
2590
2553
|
}
|
|
2591
2554
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2592
2555
|
const stat = unmodifiedStats(battle, foe).atk;
|
|
2593
2556
|
stats.atk = @max(1, stat * mod[0] / mod[1]);
|
|
2594
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2557
|
+
try log.boost(.{ foe_ident, .Attack, -1 });
|
|
2595
2558
|
},
|
|
2596
2559
|
.DefenseDown1, .DefenseDown2, .DefenseDownChance => {
|
|
2597
2560
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2598
|
-
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 });
|
|
2599
2562
|
const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
|
|
2600
2563
|
boosts.def = @intCast(@max(-6, @as(i8, boosts.def) - n));
|
|
2601
2564
|
if (stats.def == 1) {
|
|
2602
2565
|
boosts.def += 1;
|
|
2603
2566
|
if (showdown) {
|
|
2604
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2605
|
-
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 });
|
|
2606
2569
|
}
|
|
2607
|
-
return
|
|
2570
|
+
return log.fail(.{ foe_ident, .None });
|
|
2608
2571
|
}
|
|
2609
2572
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2610
2573
|
const stat = unmodifiedStats(battle, foe).def;
|
|
2611
2574
|
stats.def = @max(1, stat * mod[0] / mod[1]);
|
|
2612
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2575
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2613
2576
|
},
|
|
2614
2577
|
.SpeedDown1, .SpeedDownChance => {
|
|
2615
2578
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2616
|
-
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 });
|
|
2617
2580
|
boosts.spe = @intCast(@max(-6, @as(i8, boosts.spe) - 1));
|
|
2618
2581
|
if (stats.spe == 1) {
|
|
2619
2582
|
boosts.spe += 1;
|
|
2620
2583
|
if (showdown) {
|
|
2621
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2622
|
-
try log.boost(foe_ident, .Speed, 1);
|
|
2584
|
+
try log.boost(.{ foe_ident, .Speed, -1 });
|
|
2585
|
+
try log.boost(.{ foe_ident, .Speed, 1 });
|
|
2623
2586
|
}
|
|
2624
|
-
return
|
|
2587
|
+
return log.fail(.{ foe_ident, .None });
|
|
2625
2588
|
}
|
|
2626
2589
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2627
2590
|
const stat = unmodifiedStats(battle, foe).spe;
|
|
2628
2591
|
stats.spe = @max(1, stat * mod[0] / mod[1]);
|
|
2629
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2592
|
+
try log.boost(.{ foe_ident, .Speed, -1 });
|
|
2630
2593
|
assert(boosts.spe >= -6);
|
|
2631
2594
|
},
|
|
2632
2595
|
.SpecialDownChance => {
|
|
2633
2596
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2634
|
-
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 });
|
|
2635
2598
|
boosts.spc = @intCast(@max(-6, @as(i8, boosts.spc) - 1));
|
|
2636
2599
|
if (stats.spc == 1) {
|
|
2637
2600
|
boosts.spc += 1;
|
|
2638
2601
|
if (showdown) {
|
|
2639
|
-
try log.boost(foe_ident, .SpecialAttack, -1);
|
|
2640
|
-
try log.boost(foe_ident, .SpecialAttack, 1);
|
|
2641
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2642
|
-
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 });
|
|
2643
2606
|
}
|
|
2644
|
-
return
|
|
2607
|
+
return log.fail(.{ foe_ident, .None });
|
|
2645
2608
|
}
|
|
2646
2609
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2647
2610
|
const stat = unmodifiedStats(battle, foe).spc;
|
|
2648
2611
|
stats.spc = @max(1, stat * mod[0] / mod[1]);
|
|
2649
|
-
try log.boost(foe_ident, .SpecialAttack, -1);
|
|
2650
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2612
|
+
try log.boost(.{ foe_ident, .SpecialAttack, -1 });
|
|
2613
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2651
2614
|
},
|
|
2652
2615
|
.AccuracyDown1 => {
|
|
2653
2616
|
assert(boosts.accuracy >= -6 and boosts.accuracy <= 6);
|
|
2654
|
-
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 });
|
|
2655
2618
|
boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
|
|
2656
|
-
try log.boost(foe_ident, .Accuracy, -1);
|
|
2619
|
+
try log.boost(.{ foe_ident, .Accuracy, -1 });
|
|
2657
2620
|
},
|
|
2658
2621
|
else => unreachable,
|
|
2659
2622
|
}
|
|
@@ -2677,7 +2640,7 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
2677
2640
|
}
|
|
2678
2641
|
}
|
|
2679
2642
|
|
|
2680
|
-
|
|
2643
|
+
fn isForced(active: anytype) bool {
|
|
2681
2644
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
2682
2645
|
active.volatiles.Thrashing or active.volatiles.Charging;
|
|
2683
2646
|
}
|
|
@@ -2690,27 +2653,27 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2690
2653
|
|
|
2691
2654
|
options.chance.clearDurations(who, clear);
|
|
2692
2655
|
|
|
2693
|
-
if (volatiles.
|
|
2694
|
-
volatiles.
|
|
2695
|
-
volatiles.
|
|
2696
|
-
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 });
|
|
2697
2660
|
}
|
|
2698
2661
|
if (volatiles.Confusion) {
|
|
2699
2662
|
// volatiles.confusion is left unchanged
|
|
2700
2663
|
volatiles.Confusion = false;
|
|
2701
|
-
try log.end(ident, .ConfusionSilent);
|
|
2664
|
+
try log.end(.{ ident, .ConfusionSilent });
|
|
2702
2665
|
}
|
|
2703
2666
|
if (volatiles.Mist) {
|
|
2704
2667
|
volatiles.Mist = false;
|
|
2705
|
-
try log.end(ident, .
|
|
2668
|
+
try log.end(.{ ident, .MistSilent });
|
|
2706
2669
|
}
|
|
2707
2670
|
if (volatiles.FocusEnergy) {
|
|
2708
2671
|
volatiles.FocusEnergy = false;
|
|
2709
|
-
try log.end(ident, .
|
|
2672
|
+
try log.end(.{ ident, .FocusEnergySilent });
|
|
2710
2673
|
}
|
|
2711
2674
|
if (volatiles.LeechSeed) {
|
|
2712
2675
|
volatiles.LeechSeed = false;
|
|
2713
|
-
try log.end(ident, .
|
|
2676
|
+
try log.end(.{ ident, .LeechSeedSilent });
|
|
2714
2677
|
}
|
|
2715
2678
|
if (!showdown and volatiles.Toxic) {
|
|
2716
2679
|
volatiles.Toxic = false;
|
|
@@ -2718,22 +2681,22 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2718
2681
|
}
|
|
2719
2682
|
if (volatiles.LightScreen) {
|
|
2720
2683
|
volatiles.LightScreen = false;
|
|
2721
|
-
try log.end(ident, .
|
|
2684
|
+
try log.end(.{ ident, .LightScreenSilent });
|
|
2722
2685
|
}
|
|
2723
2686
|
if (volatiles.Reflect) {
|
|
2724
2687
|
volatiles.Reflect = false;
|
|
2725
|
-
try log.end(ident, .
|
|
2688
|
+
try log.end(.{ ident, .ReflectSilent });
|
|
2726
2689
|
}
|
|
2727
2690
|
if (showdown and volatiles.Toxic) {
|
|
2728
2691
|
volatiles.Toxic = false;
|
|
2729
2692
|
// Pokémon Showdown erroneously clears the toxic counter
|
|
2730
2693
|
volatiles.toxic = 0;
|
|
2731
|
-
try log.end(ident, .
|
|
2694
|
+
try log.end(.{ ident, .ToxicSilent });
|
|
2732
2695
|
}
|
|
2733
2696
|
}
|
|
2734
2697
|
|
|
2735
2698
|
pub const Rolls = struct {
|
|
2736
|
-
|
|
2699
|
+
fn speedTie(battle: anytype, options: anytype) !bool {
|
|
2737
2700
|
const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
|
|
2738
2701
|
player == .P1
|
|
2739
2702
|
else if (showdown)
|
|
@@ -2745,7 +2708,7 @@ pub const Rolls = struct {
|
|
|
2745
2708
|
return p1;
|
|
2746
2709
|
}
|
|
2747
2710
|
|
|
2748
|
-
|
|
2711
|
+
fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
|
|
2749
2712
|
const crit = if (options.calc.overridden(player, .critical_hit)) |val|
|
|
2750
2713
|
val == .true
|
|
2751
2714
|
else if (showdown)
|
|
@@ -2757,7 +2720,7 @@ pub const Rolls = struct {
|
|
|
2757
2720
|
return crit;
|
|
2758
2721
|
}
|
|
2759
2722
|
|
|
2760
|
-
|
|
2723
|
+
fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2761
2724
|
const roll = if (options.calc.overridden(player, .damage)) |val|
|
|
2762
2725
|
val
|
|
2763
2726
|
else roll: {
|
|
@@ -2773,7 +2736,7 @@ pub const Rolls = struct {
|
|
|
2773
2736
|
return roll;
|
|
2774
2737
|
}
|
|
2775
2738
|
|
|
2776
|
-
|
|
2739
|
+
fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) bool {
|
|
2777
2740
|
const ok = if (options.calc.overridden(player, .hit)) |val|
|
|
2778
2741
|
val == .true
|
|
2779
2742
|
else if (showdown)
|
|
@@ -2785,7 +2748,7 @@ pub const Rolls = struct {
|
|
|
2785
2748
|
return ok;
|
|
2786
2749
|
}
|
|
2787
2750
|
|
|
2788
|
-
|
|
2751
|
+
fn confused(battle: anytype, player: Player, options: anytype) !bool {
|
|
2789
2752
|
const cfz = if (options.calc.overridden(player, .confused)) |val|
|
|
2790
2753
|
val == .true
|
|
2791
2754
|
else if (showdown)
|
|
@@ -2797,7 +2760,7 @@ pub const Rolls = struct {
|
|
|
2797
2760
|
return cfz;
|
|
2798
2761
|
}
|
|
2799
2762
|
|
|
2800
|
-
|
|
2763
|
+
fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
|
|
2801
2764
|
const par = if (options.calc.overridden(player, .paralyzed)) |val|
|
|
2802
2765
|
val == .true
|
|
2803
2766
|
else if (showdown)
|
|
@@ -2809,7 +2772,7 @@ pub const Rolls = struct {
|
|
|
2809
2772
|
return par;
|
|
2810
2773
|
}
|
|
2811
2774
|
|
|
2812
|
-
|
|
2775
|
+
fn confusionChance(battle: anytype, player: Player, options: anytype) !bool {
|
|
2813
2776
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2814
2777
|
val == .true
|
|
2815
2778
|
else if (showdown)
|
|
@@ -2821,7 +2784,7 @@ pub const Rolls = struct {
|
|
|
2821
2784
|
return proc;
|
|
2822
2785
|
}
|
|
2823
2786
|
|
|
2824
|
-
|
|
2787
|
+
fn secondaryChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2825
2788
|
const rate: u8 = if (low) 26 else 77;
|
|
2826
2789
|
|
|
2827
2790
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
@@ -2835,7 +2798,7 @@ pub const Rolls = struct {
|
|
|
2835
2798
|
return proc;
|
|
2836
2799
|
}
|
|
2837
2800
|
|
|
2838
|
-
|
|
2801
|
+
fn poisonChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2839
2802
|
const rate: u8 = if (low) 52 else 103;
|
|
2840
2803
|
|
|
2841
2804
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
@@ -2849,7 +2812,7 @@ pub const Rolls = struct {
|
|
|
2849
2812
|
return proc;
|
|
2850
2813
|
}
|
|
2851
2814
|
|
|
2852
|
-
|
|
2815
|
+
fn unboost(battle: anytype, player: Player, options: anytype) !bool {
|
|
2853
2816
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2854
2817
|
val == .true
|
|
2855
2818
|
else if (showdown)
|
|
@@ -2861,14 +2824,12 @@ pub const Rolls = struct {
|
|
|
2861
2824
|
return proc;
|
|
2862
2825
|
}
|
|
2863
2826
|
|
|
2864
|
-
|
|
2827
|
+
fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
2865
2828
|
const move: Move = if (options.calc.overridden(player, .metronome)) |val|
|
|
2866
2829
|
val
|
|
2867
|
-
else if (showdown)
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
break :move @enumFromInt(r + mod);
|
|
2871
|
-
} else move: {
|
|
2830
|
+
else if (showdown)
|
|
2831
|
+
Move.METRONOME[battle.rng.range(u8, 0, Move.METRONOME.len)]
|
|
2832
|
+
else move: {
|
|
2872
2833
|
while (true) {
|
|
2873
2834
|
const r = battle.rng.next();
|
|
2874
2835
|
if (r == 0 or r == @intFromEnum(Move.Metronome)) continue;
|
|
@@ -2881,7 +2842,7 @@ pub const Rolls = struct {
|
|
|
2881
2842
|
return move;
|
|
2882
2843
|
}
|
|
2883
2844
|
|
|
2884
|
-
|
|
2845
|
+
fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
|
|
2885
2846
|
const power = if (options.calc.overridden(player, .psywave)) |val|
|
|
2886
2847
|
val - 1
|
|
2887
2848
|
else if (showdown)
|
|
@@ -2898,11 +2859,11 @@ pub const Rolls = struct {
|
|
|
2898
2859
|
return power;
|
|
2899
2860
|
}
|
|
2900
2861
|
|
|
2901
|
-
|
|
2862
|
+
fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2902
2863
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2903
2864
|
@intCast(val)
|
|
2904
2865
|
else if (showdown)
|
|
2905
|
-
|
|
2866
|
+
battle.rng.range(u3, 1, 8)
|
|
2906
2867
|
else duration: {
|
|
2907
2868
|
while (true) {
|
|
2908
2869
|
const r = battle.rng.next() & 7;
|
|
@@ -2915,11 +2876,11 @@ pub const Rolls = struct {
|
|
|
2915
2876
|
return duration;
|
|
2916
2877
|
}
|
|
2917
2878
|
|
|
2918
|
-
|
|
2879
|
+
fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
|
|
2919
2880
|
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2920
2881
|
val
|
|
2921
2882
|
else if (showdown)
|
|
2922
|
-
|
|
2883
|
+
battle.rng.range(u4, 1, 9)
|
|
2923
2884
|
else
|
|
2924
2885
|
@intCast((battle.rng.next() & 7) + 1);
|
|
2925
2886
|
|
|
@@ -2928,11 +2889,11 @@ pub const Rolls = struct {
|
|
|
2928
2889
|
return duration;
|
|
2929
2890
|
}
|
|
2930
2891
|
|
|
2931
|
-
|
|
2892
|
+
fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
|
|
2932
2893
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2933
2894
|
@intCast(val)
|
|
2934
2895
|
else if (showdown)
|
|
2935
|
-
|
|
2896
|
+
battle.rng.range(u3, 2, 6)
|
|
2936
2897
|
else
|
|
2937
2898
|
@intCast((battle.rng.next() & 3) + 2);
|
|
2938
2899
|
|
|
@@ -2941,11 +2902,11 @@ pub const Rolls = struct {
|
|
|
2941
2902
|
return duration;
|
|
2942
2903
|
}
|
|
2943
2904
|
|
|
2944
|
-
|
|
2905
|
+
fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2945
2906
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2946
2907
|
@intCast(val)
|
|
2947
2908
|
else if (showdown)
|
|
2948
|
-
|
|
2909
|
+
battle.rng.range(u3, 2, 4)
|
|
2949
2910
|
else
|
|
2950
2911
|
@intCast((battle.rng.next() & 1) + 2);
|
|
2951
2912
|
|
|
@@ -2954,7 +2915,9 @@ pub const Rolls = struct {
|
|
|
2954
2915
|
return duration;
|
|
2955
2916
|
}
|
|
2956
2917
|
|
|
2957
|
-
|
|
2918
|
+
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
2919
|
+
|
|
2920
|
+
fn distribution(
|
|
2958
2921
|
battle: anytype,
|
|
2959
2922
|
comptime effect: Move.Effect,
|
|
2960
2923
|
player: Player,
|
|
@@ -2964,7 +2927,7 @@ pub const Rolls = struct {
|
|
|
2964
2927
|
const n: u3 = if (options.calc.overridden(player, roll)) |val|
|
|
2965
2928
|
@intCast(val)
|
|
2966
2929
|
else if (showdown)
|
|
2967
|
-
DISTRIBUTION[battle.rng.range(
|
|
2930
|
+
DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
|
|
2968
2931
|
else n: {
|
|
2969
2932
|
const r = (battle.rng.next() & 3);
|
|
2970
2933
|
break :n @intCast((if (r < 2) r else battle.rng.next() & 3) + 2);
|
|
@@ -3011,10 +2974,9 @@ pub const Rolls = struct {
|
|
|
3011
2974
|
while (i < moves.len and r > 0) : (i += 1) {
|
|
3012
2975
|
if (moves[i].pp > 0) {
|
|
3013
2976
|
r -= 1;
|
|
3014
|
-
if (r == 0) break;
|
|
2977
|
+
if (r == 0) break :slot @intCast(i + 1);
|
|
3015
2978
|
}
|
|
3016
2979
|
}
|
|
3017
|
-
break :slot @intCast(i + 1);
|
|
3018
2980
|
}
|
|
3019
2981
|
}
|
|
3020
2982
|
|
|
@@ -3036,11 +2998,11 @@ test "RNG agreement" {
|
|
|
3036
2998
|
expected[i] = @intCast(i * 0x1000000);
|
|
3037
2999
|
}
|
|
3038
3000
|
|
|
3039
|
-
var spe
|
|
3040
|
-
var cfz
|
|
3041
|
-
var par
|
|
3042
|
-
var brn
|
|
3043
|
-
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 };
|
|
3044
3006
|
|
|
3045
3007
|
for (0..expected.len) |i| {
|
|
3046
3008
|
try expectEqual(spe.range(u8, 0, 2) == 0, i < Gen12.percent(50) + 1);
|
|
@@ -3130,7 +3092,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3130
3092
|
if (m.id == if (active.volatiles.Bide) .Bide else side.last_selected_move) {
|
|
3131
3093
|
// Pokémon Showdown displays Struggle if limited to Bide but unable to pick
|
|
3132
3094
|
const struggle =
|
|
3133
|
-
m.id == .Bide and (m.pp == 0 or active.volatiles.
|
|
3095
|
+
m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
|
|
3134
3096
|
const s = if (struggle) 0 else slot;
|
|
3135
3097
|
out[n] = .{ .type = .Move, .data = s };
|
|
3136
3098
|
n += 1;
|
|
@@ -3145,7 +3107,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3145
3107
|
const m = active.moves[slot - 1];
|
|
3146
3108
|
if (m.id == .None) break;
|
|
3147
3109
|
if (m.pp == 0) continue;
|
|
3148
|
-
if (active.volatiles.
|
|
3110
|
+
if (active.volatiles.disable_move == slot) continue;
|
|
3149
3111
|
out[n] = .{ .type = .Move, .data = slot };
|
|
3150
3112
|
n += 1;
|
|
3151
3113
|
}
|