@pkmn/engine 0.1.0-dev.305a932f → 0.1.0-dev.32adfe77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -3
- package/README.md +37 -14
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/data/layout.json +2 -2
- package/build/pkg/data.d.ts +3 -2
- package/build/pkg/data.js.map +1 -1
- 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 +1 -1
- package/build/pkg/protocol.js.map +1 -1
- package/build/test/display.js.map +1 -1
- package/build/tools/debug.js +3 -1
- package/build/tools/debug.js.map +1 -1
- package/build.zig +78 -68
- package/package.json +34 -23
- package/src/bin/install-pkmn-engine +14 -14
- package/src/lib/common/data.zig +8 -8
- package/src/lib/common/optional.zig +9 -2
- package/src/lib/common/protocol.zig +402 -383
- package/src/lib/common/rational.zig +11 -7
- package/src/lib/common/rng.zig +20 -11
- package/src/lib/gen1/README.md +28 -28
- package/src/lib/gen1/calc.zig +7 -7
- package/src/lib/gen1/chance.zig +70 -68
- package/src/lib/gen1/data/moves.zig +24 -10
- package/src/lib/gen1/data/species.zig +3 -3
- package/src/lib/gen1/data/types.zig +5 -5
- package/src/lib/gen1/data.zig +36 -36
- package/src/lib/gen1/helpers.zig +21 -36
- package/src/lib/gen1/mechanics.zig +333 -344
- package/src/lib/gen2/README.md +1 -2
- package/src/lib/gen2/calc.zig +143 -0
- package/src/lib/gen2/chance.zig +1314 -0
- package/src/lib/gen2/data/items.zig +3 -3
- package/src/lib/gen2/data/moves.zig +32 -15
- package/src/lib/gen2/data/species.zig +1 -1
- package/src/lib/gen2/data/types.zig +75 -28
- package/src/lib/gen2/data.zig +37 -25
- package/src/lib/gen2/generated.zig +806 -0
- package/src/lib/gen2/mechanics.zig +2612 -349
- package/src/lib/{binding/node.zig → node.zig} +9 -12
- package/src/lib/pkmn.zig +26 -3
- package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
|
@@ -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,30 +58,26 @@ 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
62
|
assert(c1.type != .Pass or c2.type != .Pass or battle.turn == 0);
|
|
66
63
|
if (battle.turn == 0) return start(battle, options);
|
|
67
64
|
|
|
68
65
|
var s1 = false;
|
|
69
66
|
var s2 = false;
|
|
70
|
-
var f1: ?Move = null;
|
|
71
|
-
var f2: ?Move = null;
|
|
72
67
|
|
|
73
|
-
if (selectMove(battle, .P1, c1, c2, &s1
|
|
74
|
-
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;
|
|
75
70
|
|
|
76
71
|
var p1 = battle.side(.P1);
|
|
77
72
|
var p2 = battle.side(.P2);
|
|
78
73
|
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
const r1 = showdown and p1.active.volatiles.Binding and c2.type == .Switch;
|
|
75
|
+
const r2 = showdown and p2.active.volatiles.Binding and c1.type == .Switch;
|
|
81
76
|
|
|
82
77
|
if (try turnOrder(battle, c1, c2, options) == .P1) {
|
|
83
|
-
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;
|
|
84
79
|
} else {
|
|
85
|
-
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;
|
|
86
81
|
}
|
|
87
82
|
|
|
88
83
|
if (p1.active.volatiles.attacks == 0) p1.active.volatiles.Binding = false;
|
|
@@ -95,11 +90,11 @@ fn start(battle: anytype, options: anytype) !Result {
|
|
|
95
90
|
const p1 = battle.side(.P1);
|
|
96
91
|
const p2 = battle.side(.P2);
|
|
97
92
|
|
|
98
|
-
|
|
93
|
+
const p1_slot = findFirstAlive(p1);
|
|
99
94
|
assert(!showdown or p1_slot == 1);
|
|
100
95
|
if (p1_slot == 0) return if (findFirstAlive(p2) == 0) Result.Tie else Result.Lose;
|
|
101
96
|
|
|
102
|
-
|
|
97
|
+
const p2_slot = findFirstAlive(p2);
|
|
103
98
|
assert(!showdown or p2_slot == 1);
|
|
104
99
|
if (p2_slot == 0) return Result.Win;
|
|
105
100
|
|
|
@@ -120,7 +115,6 @@ fn selectMove(
|
|
|
120
115
|
choice: Choice,
|
|
121
116
|
foe_choice: Choice,
|
|
122
117
|
skip_turn: *bool,
|
|
123
|
-
from: *?Move,
|
|
124
118
|
) ?Result {
|
|
125
119
|
if (choice.type == .Pass) return null;
|
|
126
120
|
|
|
@@ -137,7 +131,6 @@ fn selectMove(
|
|
|
137
131
|
return null;
|
|
138
132
|
}
|
|
139
133
|
if (volatiles.Rage) {
|
|
140
|
-
from.* = side.last_used_move;
|
|
141
134
|
if (showdown) {
|
|
142
135
|
if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
|
|
143
136
|
saveMove(battle, player, null);
|
|
@@ -147,7 +140,6 @@ fn selectMove(
|
|
|
147
140
|
// Pokémon Showdown removes Flinch at the end-of-turn in its residual handler
|
|
148
141
|
if (!showdown) volatiles.Flinch = false;
|
|
149
142
|
if (volatiles.Thrashing or volatiles.Charging) {
|
|
150
|
-
from.* = side.last_selected_move;
|
|
151
143
|
if (showdown) {
|
|
152
144
|
if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
|
|
153
145
|
saveMove(battle, player, null);
|
|
@@ -168,9 +160,7 @@ fn selectMove(
|
|
|
168
160
|
return null;
|
|
169
161
|
}
|
|
170
162
|
if (volatiles.Binding) {
|
|
171
|
-
from.* = side.last_used_move;
|
|
172
163
|
if (showdown) {
|
|
173
|
-
if (foe_choice.type == .Switch) from.* = null;
|
|
174
164
|
// Pokémon Showdown overwrites Mirror Move with whatever was selected - really this
|
|
175
165
|
// should set side.last_selected_move = last.id to reuse Mirror Move and fail in order
|
|
176
166
|
// to satisfy the conditions of the Desync Clause Mod. However, because Binding is still
|
|
@@ -206,7 +196,7 @@ fn selectMove(
|
|
|
206
196
|
if (choice.data == 0) {
|
|
207
197
|
const struggle = ok: {
|
|
208
198
|
for (side.active.moves, 0..) |move, i| {
|
|
209
|
-
if (move.pp > 0 and volatiles.
|
|
199
|
+
if (move.pp > 0 and volatiles.disable_move != i + 1) break :ok false;
|
|
210
200
|
}
|
|
211
201
|
break :ok true;
|
|
212
202
|
};
|
|
@@ -225,7 +215,7 @@ fn saveMove(battle: anytype, player: Player, choice: ?Choice) void {
|
|
|
225
215
|
if (c.data == 0) {
|
|
226
216
|
side.last_selected_move = .Struggle;
|
|
227
217
|
} else {
|
|
228
|
-
assert(showdown or side.active.volatiles.
|
|
218
|
+
assert(showdown or side.active.volatiles.disable_move != c.data);
|
|
229
219
|
const move = side.active.move(c.data);
|
|
230
220
|
// You cannot *select* a move with 0 PP (except on Pokémon Showdown where that is
|
|
231
221
|
// sometimes required...), but a 0 PP move can be used automatically
|
|
@@ -266,14 +256,14 @@ fn switchIn(battle: anytype, player: Player, slot: u8, initial: bool, options: a
|
|
|
266
256
|
|
|
267
257
|
foe.active.volatiles.Binding = false;
|
|
268
258
|
|
|
269
|
-
try options.log.switched(battle.active(player), incoming);
|
|
259
|
+
try options.log.switched(.{ battle.active(player), incoming });
|
|
270
260
|
options.chance.switched(player, side.order[0], out);
|
|
271
261
|
|
|
272
262
|
if (showdown and incoming.status == Status.TOX) {
|
|
273
263
|
incoming.status = Status.init(.PSN);
|
|
274
264
|
// Technically, Pokémon Showdown adds these after *both* Pokémon have switched, but we'd
|
|
275
265
|
// rather not clutter up turnOrder just for this (incorrect) log message
|
|
276
|
-
try options.log.status(battle.active(player), incoming.status, .Silent);
|
|
266
|
+
try options.log.status(.{ battle.active(player), incoming.status, .Silent });
|
|
277
267
|
}
|
|
278
268
|
}
|
|
279
269
|
|
|
@@ -336,12 +326,10 @@ fn doTurn(
|
|
|
336
326
|
player_choice: Choice,
|
|
337
327
|
player_rewrap: bool,
|
|
338
328
|
player_skip: bool,
|
|
339
|
-
player_from: ?Move,
|
|
340
329
|
foe_player: Player,
|
|
341
330
|
foe_choice: Choice,
|
|
342
331
|
foe_rewrap: bool,
|
|
343
332
|
foe_skip: bool,
|
|
344
|
-
foe_from: ?Move,
|
|
345
333
|
options: anytype,
|
|
346
334
|
) !?Result {
|
|
347
335
|
assert(player_choice.type != .Pass);
|
|
@@ -354,7 +342,6 @@ fn doTurn(
|
|
|
354
342
|
player_choice,
|
|
355
343
|
player_rewrap,
|
|
356
344
|
player_skip,
|
|
357
|
-
player_from,
|
|
358
345
|
&residual,
|
|
359
346
|
options,
|
|
360
347
|
)) |r| return r;
|
|
@@ -377,7 +364,6 @@ fn doTurn(
|
|
|
377
364
|
foe_choice,
|
|
378
365
|
foe_rewrap,
|
|
379
366
|
foe_skip,
|
|
380
|
-
foe_from,
|
|
381
367
|
&residual,
|
|
382
368
|
options,
|
|
383
369
|
)) |r| return r;
|
|
@@ -405,7 +391,6 @@ fn executeMove(
|
|
|
405
391
|
choice: Choice,
|
|
406
392
|
rewrap: bool,
|
|
407
393
|
skip: bool,
|
|
408
|
-
from: ?Move,
|
|
409
394
|
residual: *bool,
|
|
410
395
|
options: anytype,
|
|
411
396
|
) !?Result {
|
|
@@ -422,7 +407,7 @@ fn executeMove(
|
|
|
422
407
|
if (side.last_selected_move == .SKIP_TURN) {
|
|
423
408
|
assert(!showdown);
|
|
424
409
|
if (battle.foe(player).active.volatiles.Binding) {
|
|
425
|
-
try options.log.cant(battle.active(player), .Bound);
|
|
410
|
+
try options.log.cant(.{ battle.active(player), .Bound });
|
|
426
411
|
}
|
|
427
412
|
return null;
|
|
428
413
|
}
|
|
@@ -440,8 +425,10 @@ fn executeMove(
|
|
|
440
425
|
mslot = @intCast(battle.lastMove(player).index);
|
|
441
426
|
const stored = side.stored();
|
|
442
427
|
// GLITCH: Struggle bypass PP underflow via Hyper Beam / Trapping-switch auto selection
|
|
443
|
-
auto = side.
|
|
444
|
-
|
|
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);
|
|
445
432
|
// If it wasn't Hyper Beam or the continuation of a move effect then we must have just
|
|
446
433
|
// thawed, in which case we will desync unless the last_selected_move happened to be at
|
|
447
434
|
// index 1 and the current Pokémon has the same move in its first slot.
|
|
@@ -463,7 +450,7 @@ fn executeMove(
|
|
|
463
450
|
|
|
464
451
|
var skip_can = false;
|
|
465
452
|
var skip_pp = false;
|
|
466
|
-
switch (try beforeMove(battle, player, skip,
|
|
453
|
+
switch (try beforeMove(battle, player, skip, residual, options)) {
|
|
467
454
|
.done => return null,
|
|
468
455
|
.skip_can => skip_can = true,
|
|
469
456
|
.skip_pp => skip_pp = true,
|
|
@@ -471,9 +458,9 @@ fn executeMove(
|
|
|
471
458
|
.err => return @as(?Result, Result.Error),
|
|
472
459
|
}
|
|
473
460
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
461
|
+
const can = skip_can or
|
|
462
|
+
try canMove(battle, player, mslot, auto, skip_pp, .None, residual, options);
|
|
463
|
+
if (!can) return null;
|
|
477
464
|
|
|
478
465
|
return doMove(battle, player, mslot, rewrap, auto, residual, options);
|
|
479
466
|
}
|
|
@@ -484,7 +471,6 @@ fn beforeMove(
|
|
|
484
471
|
battle: anytype,
|
|
485
472
|
player: Player,
|
|
486
473
|
skip: bool,
|
|
487
|
-
from: ?Move,
|
|
488
474
|
residual: *bool,
|
|
489
475
|
options: anytype,
|
|
490
476
|
) !BeforeMove {
|
|
@@ -511,23 +497,23 @@ fn beforeMove(
|
|
|
511
497
|
}
|
|
512
498
|
|
|
513
499
|
if (duration == 0) {
|
|
514
|
-
try log.curestatus(ident, before, .Message);
|
|
500
|
+
try log.curestatus(.{ ident, before, .Message });
|
|
515
501
|
stored.status = 0; // clears EXT if present
|
|
516
502
|
} else {
|
|
517
|
-
try log.cant(ident, .Sleep);
|
|
503
|
+
try log.cant(.{ ident, .Sleep });
|
|
518
504
|
}
|
|
519
505
|
side.last_used_move = .None;
|
|
520
506
|
return .done;
|
|
521
507
|
}
|
|
522
508
|
|
|
523
509
|
if (Status.is(stored.status, .FRZ)) {
|
|
524
|
-
try log.cant(ident, .Freeze);
|
|
510
|
+
try log.cant(.{ ident, .Freeze });
|
|
525
511
|
side.last_used_move = .None;
|
|
526
512
|
return .done;
|
|
527
513
|
}
|
|
528
514
|
|
|
529
515
|
if (skip or foe.active.volatiles.Binding) {
|
|
530
|
-
try log.cant(ident, .Bound);
|
|
516
|
+
try log.cant(.{ ident, .Bound });
|
|
531
517
|
return .done;
|
|
532
518
|
}
|
|
533
519
|
|
|
@@ -535,27 +521,27 @@ fn beforeMove(
|
|
|
535
521
|
// Pokémon Showdown doesn't clear Flinch until its imaginary "residual" phase, meaning
|
|
536
522
|
// Pokémon can sometimes flinch multiple times from the same original hit
|
|
537
523
|
if (!showdown) volatiles.Flinch = false;
|
|
538
|
-
try log.cant(ident, .Flinch);
|
|
524
|
+
try log.cant(.{ ident, .Flinch });
|
|
539
525
|
return .done;
|
|
540
526
|
}
|
|
541
527
|
|
|
542
528
|
if (volatiles.Recharging) {
|
|
543
529
|
volatiles.Recharging = false;
|
|
544
|
-
try log.cant(ident, .Recharge);
|
|
530
|
+
try log.cant(.{ ident, .Recharge });
|
|
545
531
|
return .done;
|
|
546
532
|
}
|
|
547
533
|
|
|
548
|
-
if (volatiles.
|
|
534
|
+
if (volatiles.disable_duration > 0) {
|
|
549
535
|
if (options.calc.modify(player, .disable)) |extend| {
|
|
550
|
-
if (!extend) volatiles.
|
|
536
|
+
if (!extend) volatiles.disable_duration = 0;
|
|
551
537
|
} else {
|
|
552
|
-
volatiles.
|
|
538
|
+
volatiles.disable_duration -= 1;
|
|
553
539
|
}
|
|
554
|
-
try options.chance.disable(player, volatiles.
|
|
540
|
+
try options.chance.disable(player, volatiles.disable_duration);
|
|
555
541
|
|
|
556
|
-
if (volatiles.
|
|
557
|
-
volatiles.
|
|
558
|
-
try log.end(ident, .Disable);
|
|
542
|
+
if (volatiles.disable_duration == 0) {
|
|
543
|
+
volatiles.disable_move = 0;
|
|
544
|
+
try log.end(.{ ident, .Disable });
|
|
559
545
|
}
|
|
560
546
|
}
|
|
561
547
|
// Pokémon Showdown's disable condition has a single onBeforeMove handler
|
|
@@ -580,9 +566,9 @@ fn beforeMove(
|
|
|
580
566
|
|
|
581
567
|
if (volatiles.confusion == 0) {
|
|
582
568
|
volatiles.Confusion = false;
|
|
583
|
-
try log.end(ident, .Confusion);
|
|
569
|
+
try log.end(.{ ident, .Confusion });
|
|
584
570
|
} else {
|
|
585
|
-
try log.activate(ident, .Confusion);
|
|
571
|
+
try log.activate(.{ ident, .Confusion });
|
|
586
572
|
|
|
587
573
|
if (try Rolls.confused(battle, player, options)) {
|
|
588
574
|
assert(!volatiles.MultiHit);
|
|
@@ -633,7 +619,7 @@ fn beforeMove(
|
|
|
633
619
|
volatiles.Binding = false;
|
|
634
620
|
options.chance.clearDurations(player, null);
|
|
635
621
|
// GLITCH: Invulnerable is not cleared, resulting in permanent Fly/Dig invulnerability
|
|
636
|
-
try log.cant(ident, .Paralysis);
|
|
622
|
+
try log.cant(.{ ident, .Paralysis });
|
|
637
623
|
return .done;
|
|
638
624
|
}
|
|
639
625
|
|
|
@@ -649,8 +635,6 @@ fn beforeMove(
|
|
|
649
635
|
volatiles.state +%= battle.last_damage;
|
|
650
636
|
}
|
|
651
637
|
|
|
652
|
-
assert(volatiles.attacks > 0);
|
|
653
|
-
|
|
654
638
|
if (options.calc.modify(player, .attacking)) |extend| {
|
|
655
639
|
if (!extend) volatiles.attacks = 0;
|
|
656
640
|
} else {
|
|
@@ -659,18 +643,18 @@ fn beforeMove(
|
|
|
659
643
|
try options.chance.attacking(player, volatiles.attacks);
|
|
660
644
|
|
|
661
645
|
if (volatiles.attacks != 0) {
|
|
662
|
-
try log.activate(ident, .Bide);
|
|
646
|
+
try log.activate(.{ ident, .Bide });
|
|
663
647
|
return .done;
|
|
664
648
|
}
|
|
665
649
|
|
|
666
650
|
volatiles.Bide = false;
|
|
667
|
-
try log.end(ident, .Bide);
|
|
651
|
+
try log.end(.{ ident, .Bide });
|
|
668
652
|
|
|
669
653
|
battle.last_damage = volatiles.state *% 2;
|
|
670
654
|
volatiles.state = 0;
|
|
671
655
|
|
|
672
656
|
if (battle.last_damage == 0) {
|
|
673
|
-
try log.fail(ident, .None);
|
|
657
|
+
try log.fail(.{ ident, .None });
|
|
674
658
|
return .done;
|
|
675
659
|
}
|
|
676
660
|
|
|
@@ -690,9 +674,6 @@ fn beforeMove(
|
|
|
690
674
|
}
|
|
691
675
|
|
|
692
676
|
if (volatiles.Thrashing) {
|
|
693
|
-
assert(volatiles.attacks > 0);
|
|
694
|
-
assert(from != null);
|
|
695
|
-
|
|
696
677
|
if (options.calc.modify(player, .attacking)) |extend| {
|
|
697
678
|
if (!extend) volatiles.attacks = 0;
|
|
698
679
|
} else {
|
|
@@ -701,23 +682,21 @@ fn beforeMove(
|
|
|
701
682
|
try options.chance.attacking(player, volatiles.attacks);
|
|
702
683
|
|
|
703
684
|
if (!showdown and handleThrashing(battle, active, player, options)) {
|
|
704
|
-
try log.start(battle.active(player), .ConfusionSilent);
|
|
685
|
+
try log.start(.{ battle.active(player), .ConfusionSilent });
|
|
705
686
|
}
|
|
706
|
-
try log.move(ident, side.last_selected_move, battle.active(player.foe())
|
|
687
|
+
try log.move(.{ ident, side.last_selected_move, battle.active(player.foe()) });
|
|
707
688
|
if (showdown) {
|
|
708
689
|
// This shouldn't actually set last_used_move, but Pokémon Showdown sets last
|
|
709
690
|
// used in useMove and doesn't have the notion of skipping canMove semantics
|
|
710
691
|
side.last_used_move = side.last_selected_move;
|
|
711
692
|
if (handleThrashing(battle, active, player, options)) {
|
|
712
|
-
try log.start(battle.active(player), .ConfusionSilent);
|
|
693
|
+
try log.start(.{ battle.active(player), .ConfusionSilent });
|
|
713
694
|
}
|
|
714
695
|
}
|
|
715
696
|
return .skip_can;
|
|
716
697
|
}
|
|
717
698
|
|
|
718
699
|
if (volatiles.Binding) {
|
|
719
|
-
assert(volatiles.attacks > 0);
|
|
720
|
-
|
|
721
700
|
if (options.calc.modify(player, .binding)) |extend| {
|
|
722
701
|
if (!extend) volatiles.attacks = 0;
|
|
723
702
|
} else {
|
|
@@ -725,12 +704,7 @@ fn beforeMove(
|
|
|
725
704
|
}
|
|
726
705
|
try options.chance.binding(player, volatiles.attacks);
|
|
727
706
|
|
|
728
|
-
try log.move(
|
|
729
|
-
battle.active(player),
|
|
730
|
-
side.last_selected_move,
|
|
731
|
-
battle.active(player.foe()),
|
|
732
|
-
side.last_selected_move,
|
|
733
|
-
);
|
|
707
|
+
try log.move(.{ ident, side.last_selected_move, battle.active(player.foe()) });
|
|
734
708
|
if (showdown or battle.last_damage != 0) {
|
|
735
709
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
736
710
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -750,7 +724,7 @@ fn canMove(
|
|
|
750
724
|
mslot: u4,
|
|
751
725
|
auto: bool,
|
|
752
726
|
skip_pp: bool,
|
|
753
|
-
from:
|
|
727
|
+
from: Move,
|
|
754
728
|
residual: *bool,
|
|
755
729
|
options: anytype,
|
|
756
730
|
) !bool {
|
|
@@ -758,33 +732,21 @@ fn canMove(
|
|
|
758
732
|
const player_ident = battle.active(player);
|
|
759
733
|
const move = Move.get(side.last_selected_move);
|
|
760
734
|
|
|
761
|
-
const foe_last = battle.foe(player).last_used_move;
|
|
762
|
-
const special = from != null and (from.? == .Metronome and
|
|
763
|
-
(from.? == .MirrorMove and !(foe_last == .None or foe_last == .MirrorMove)));
|
|
764
|
-
|
|
765
|
-
var skip = skip_pp;
|
|
766
735
|
if (side.active.volatiles.Charging) {
|
|
767
736
|
side.active.volatiles.Charging = false;
|
|
768
737
|
side.active.volatiles.Invulnerable = false;
|
|
769
738
|
} else if (move.effect == .Charge) {
|
|
770
|
-
try options.log.move(player_ident, side.last_selected_move,
|
|
739
|
+
try options.log.move(.{ player_ident, side.last_selected_move, ID{}, from });
|
|
771
740
|
setCounterable(battle, player, side, move);
|
|
772
741
|
try Effects.charge(battle, player, options);
|
|
773
742
|
return false;
|
|
774
743
|
}
|
|
775
744
|
|
|
776
|
-
|
|
777
|
-
if (!
|
|
745
|
+
side.last_used_move = side.last_selected_move;
|
|
746
|
+
if (!skip_pp) decrementPP(side, mslot, auto);
|
|
778
747
|
|
|
779
748
|
const target = if (move.target == .Self) player else player.foe();
|
|
780
|
-
|
|
781
|
-
// don't have space to be able to track it in all circumstances and thus usually try to infer it
|
|
782
|
-
// from side.last_used_move. However, this gets reset by the opponent switching or fainting,
|
|
783
|
-
// meaning sometimes we lose this information. from is still set because it not being null is
|
|
784
|
-
// relevant for control flow, but we don't have anything useful to put in the log. This is
|
|
785
|
-
// regrettable, but this information is more "nice to have" than required
|
|
786
|
-
const f = if (from != null and from.? == .None) null else from;
|
|
787
|
-
try options.log.move(player_ident, side.last_selected_move, battle.active(target), f);
|
|
749
|
+
try options.log.move(.{ player_ident, side.last_selected_move, battle.active(target), from });
|
|
788
750
|
setCounterable(battle, player, side, move);
|
|
789
751
|
|
|
790
752
|
if (move.effect.onBegin()) {
|
|
@@ -822,12 +784,14 @@ fn decrementPP(side: *Side, mslot: u4, auto: bool) void {
|
|
|
822
784
|
assert(!volatiles.Rage and !volatiles.Thrashing and !volatiles.MultiHit);
|
|
823
785
|
if (volatiles.Bide) return;
|
|
824
786
|
|
|
825
|
-
|
|
826
|
-
|
|
787
|
+
var move_slot = active.move(mslot);
|
|
788
|
+
assert(move_slot.pp > 0 or auto);
|
|
789
|
+
move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
|
|
827
790
|
if (volatiles.Transform) return;
|
|
828
791
|
|
|
829
|
-
|
|
830
|
-
|
|
792
|
+
move_slot = side.stored().move(mslot);
|
|
793
|
+
assert(move_slot.pp > 0 or auto);
|
|
794
|
+
move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
|
|
831
795
|
assert(active.move(mslot).pp == side.stored().move(mslot).pp);
|
|
832
796
|
}
|
|
833
797
|
|
|
@@ -931,7 +895,7 @@ fn doMove(
|
|
|
931
895
|
}
|
|
932
896
|
}
|
|
933
897
|
|
|
934
|
-
|
|
898
|
+
const zero = battle.last_damage == 0;
|
|
935
899
|
miss = if (showdown or skip)
|
|
936
900
|
miss
|
|
937
901
|
else
|
|
@@ -959,16 +923,16 @@ fn doMove(
|
|
|
959
923
|
ohko = (!showdown or (!immune and !invulnerable)) and
|
|
960
924
|
move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe;
|
|
961
925
|
if (ohko) {
|
|
962
|
-
try log.immune(foe_ident, .OHKO);
|
|
926
|
+
try log.immune(.{ foe_ident, .OHKO });
|
|
963
927
|
} else if (immune and !invulnerable and (!showdown or move.effect != .Binding)) {
|
|
964
|
-
try log.immune(foe_ident, .None);
|
|
928
|
+
try log.immune(.{ foe_ident, .None });
|
|
965
929
|
} else if (mist) {
|
|
966
|
-
if (!foe.active.volatiles.Substitute) try log.activate(foe_ident, .Mist);
|
|
967
|
-
try log.fail(foe_ident, .None);
|
|
930
|
+
if (!foe.active.volatiles.Substitute) try log.activate(.{ foe_ident, .Mist });
|
|
931
|
+
try log.fail(.{ foe_ident, .None });
|
|
968
932
|
} else {
|
|
969
933
|
if (showdown or !zero) try options.chance.commit(player, .miss);
|
|
970
|
-
try log.lastmiss();
|
|
971
|
-
try log.miss(battle.active(player));
|
|
934
|
+
try log.lastmiss(.{});
|
|
935
|
+
try log.miss(.{battle.active(player)});
|
|
972
936
|
}
|
|
973
937
|
if (move.effect == .JumpKick) {
|
|
974
938
|
// Recoil is supposed to be damage/8 but damage will always be 0 here
|
|
@@ -1004,15 +968,15 @@ fn doMove(
|
|
|
1004
968
|
var hit: u4 = 0;
|
|
1005
969
|
while (hit < hits) {
|
|
1006
970
|
if (hit == 0) {
|
|
1007
|
-
if (crit) try log.crit(battle.active(player.foe()));
|
|
971
|
+
if (crit) try log.crit(.{battle.active(player.foe())});
|
|
1008
972
|
if (effectiveness > Effectiveness.neutral) {
|
|
1009
|
-
try log.supereffective(battle.active(player.foe()));
|
|
973
|
+
try log.supereffective(.{battle.active(player.foe())});
|
|
1010
974
|
} else if (effectiveness < Effectiveness.neutral) {
|
|
1011
|
-
try log.resisted(battle.active(player.foe()));
|
|
975
|
+
try log.resisted(.{battle.active(player.foe())});
|
|
1012
976
|
}
|
|
1013
977
|
}
|
|
1014
978
|
if (!skip) nullified = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
1015
|
-
if (hit == 0 and ohko) try log.ohko();
|
|
979
|
+
if (hit == 0 and ohko) try log.ohko(.{});
|
|
1016
980
|
hit += 1;
|
|
1017
981
|
if (foe.stored().hp == 0) break;
|
|
1018
982
|
if (!late and (!sub or move.effect == .Explode)) {
|
|
@@ -1029,7 +993,7 @@ fn doMove(
|
|
|
1029
993
|
if (showdown and move.effect == .Twineedle and !nullified and foe.stored().hp > 0) {
|
|
1030
994
|
try Effects.poison(battle, player, Move.get(.PoisonSting), options);
|
|
1031
995
|
}
|
|
1032
|
-
try log.hitcount(battle.active(player.foe()), hit);
|
|
996
|
+
try log.hitcount(.{ battle.active(player.foe()), hit });
|
|
1033
997
|
} else if (showdown) {
|
|
1034
998
|
// This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
|
|
1035
999
|
if (move.effect == .Binding) {
|
|
@@ -1039,9 +1003,9 @@ fn doMove(
|
|
|
1039
1003
|
assert(foe.stored().hp > 0);
|
|
1040
1004
|
// Pokémon Showdown logs |-damage| here instead of |-immune| because logic...
|
|
1041
1005
|
if (sub) {
|
|
1042
|
-
try log.activate(battle.active(player.foe()), .Substitute);
|
|
1006
|
+
try log.activate(.{ battle.active(player.foe()), .Substitute });
|
|
1043
1007
|
} else {
|
|
1044
|
-
try log.damage(battle.active(player.foe()), foe.stored(), .None);
|
|
1008
|
+
try log.damage(.{ battle.active(player.foe()), foe.stored(), .None });
|
|
1045
1009
|
try buildRage(battle, player.foe(), options);
|
|
1046
1010
|
}
|
|
1047
1011
|
return null;
|
|
@@ -1089,7 +1053,7 @@ fn checkCriticalHit(battle: anytype, player: Player, move: Move.Data, options: a
|
|
|
1089
1053
|
const side = battle.side(player);
|
|
1090
1054
|
|
|
1091
1055
|
// Base speed is used for the critical hit calculation, even when Transform-ed
|
|
1092
|
-
var rate =
|
|
1056
|
+
var rate: u16 = Species.chance(side.stored().species);
|
|
1093
1057
|
// GLITCH: Focus Energy reduces critical hit rate instead of increasing it
|
|
1094
1058
|
rate = if (side.active.volatiles.FocusEnergy) rate / 2 else @min(rate * 2, 255);
|
|
1095
1059
|
rate = if (move.effect == .HighCritical) @min(rate * 4, 255) else rate / 2;
|
|
@@ -1146,9 +1110,9 @@ fn calcDamage(
|
|
|
1146
1110
|
def = @max((def / 4) & 255, if (showdown) 1 else 0);
|
|
1147
1111
|
}
|
|
1148
1112
|
|
|
1149
|
-
const lvl =
|
|
1113
|
+
const lvl: u32 = side.stored().level * @as(u2, if (crit) 2 else 1);
|
|
1150
1114
|
|
|
1151
|
-
def =
|
|
1115
|
+
def = if (move.effect == .Explode) @max(def / 2, 1) else def;
|
|
1152
1116
|
|
|
1153
1117
|
if (def == 0) return false;
|
|
1154
1118
|
|
|
@@ -1239,7 +1203,7 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1239
1203
|
const foe = battle.foe(player);
|
|
1240
1204
|
|
|
1241
1205
|
if (battle.last_damage == 0) {
|
|
1242
|
-
try options.log.fail(battle.active(player), .None);
|
|
1206
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1243
1207
|
return null;
|
|
1244
1208
|
}
|
|
1245
1209
|
|
|
@@ -1257,14 +1221,14 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1257
1221
|
foe_last_selected_move.type == .Fighting);
|
|
1258
1222
|
|
|
1259
1223
|
if (!used and !selected) {
|
|
1260
|
-
try options.log.fail(battle.active(player), .None);
|
|
1224
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1261
1225
|
return null;
|
|
1262
1226
|
}
|
|
1263
1227
|
|
|
1264
1228
|
if (!used or !selected) {
|
|
1265
1229
|
// GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
|
|
1266
1230
|
if (!showdown) return Result.Error;
|
|
1267
|
-
try options.log.fail(battle.active(player), .None);
|
|
1231
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1268
1232
|
return null;
|
|
1269
1233
|
}
|
|
1270
1234
|
|
|
@@ -1300,20 +1264,20 @@ fn applyDamage(
|
|
|
1300
1264
|
subbed.active.volatiles.substitute = 0;
|
|
1301
1265
|
subbed.active.volatiles.Substitute = false;
|
|
1302
1266
|
// battle.last_damage is not updated with the amount of HP the Substitute had
|
|
1303
|
-
try options.log.end(battle.active(sub_player), .Substitute);
|
|
1267
|
+
try options.log.end(.{ battle.active(sub_player), .Substitute });
|
|
1304
1268
|
options.calc.capped(target_player);
|
|
1305
1269
|
return true;
|
|
1306
1270
|
} else {
|
|
1307
1271
|
// Safe to truncate since less than subbed.volatiles.substitute which is a u8
|
|
1308
1272
|
subbed.active.volatiles.substitute -= @intCast(battle.last_damage);
|
|
1309
|
-
try options.log.activate(battle.active(sub_player), .Substitute);
|
|
1273
|
+
try options.log.activate(.{ battle.active(sub_player), .Substitute });
|
|
1310
1274
|
return false;
|
|
1311
1275
|
}
|
|
1312
1276
|
}
|
|
1313
1277
|
|
|
1314
1278
|
if (battle.last_damage > target.stored().hp) battle.last_damage = target.stored().hp;
|
|
1315
1279
|
target.stored().hp -= battle.last_damage;
|
|
1316
|
-
try options.log.damage(battle.active(target_player), target.stored(), reason);
|
|
1280
|
+
try options.log.damage(.{ battle.active(target_player), target.stored(), reason });
|
|
1317
1281
|
return false;
|
|
1318
1282
|
}
|
|
1319
1283
|
|
|
@@ -1332,7 +1296,7 @@ fn mirrorMove(
|
|
|
1332
1296
|
side.last_selected_move = foe.last_used_move;
|
|
1333
1297
|
|
|
1334
1298
|
if (foe.last_used_move == .None or foe.last_used_move == .MirrorMove) {
|
|
1335
|
-
try options.log.fail(battle.active(player), .None);
|
|
1299
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1336
1300
|
return null;
|
|
1337
1301
|
} else if (!showdown or foe.last_used_move != .Struggle) {
|
|
1338
1302
|
incrementPP(side, mslot);
|
|
@@ -1374,12 +1338,12 @@ fn checkHit(battle: anytype, player: Player, move: Move.Data, options: anytype)
|
|
|
1374
1338
|
if (mist) {
|
|
1375
1339
|
assert(!showdown);
|
|
1376
1340
|
const foe_ident = battle.active(player.foe());
|
|
1377
|
-
try options.log.activate(foe_ident, .Mist);
|
|
1378
|
-
try options.log.fail(foe_ident, .None);
|
|
1341
|
+
try options.log.activate(.{ foe_ident, .Mist });
|
|
1342
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1379
1343
|
} else {
|
|
1380
1344
|
try options.chance.commit(player, .miss);
|
|
1381
|
-
try options.log.lastmiss();
|
|
1382
|
-
try options.log.miss(battle.active(player));
|
|
1345
|
+
try options.log.lastmiss(.{});
|
|
1346
|
+
try options.log.miss(.{battle.active(player)});
|
|
1383
1347
|
}
|
|
1384
1348
|
|
|
1385
1349
|
return false;
|
|
@@ -1431,7 +1395,7 @@ fn moveHit(
|
|
|
1431
1395
|
const overwritten = overwrite and state > 0;
|
|
1432
1396
|
assert(!overwritten or (0 < state and state <= 255 and !side.active.volatiles.Bide));
|
|
1433
1397
|
|
|
1434
|
-
var accuracy = if (overwritten) state else
|
|
1398
|
+
var accuracy: u16 = if (overwritten) state else move.accuracy;
|
|
1435
1399
|
var boost = BOOSTS[@as(u4, @intCast(@as(i8, side.active.boosts.accuracy) + 6))];
|
|
1436
1400
|
accuracy = accuracy * boost[0] / boost[1];
|
|
1437
1401
|
boost = BOOSTS[@as(u4, @intCast(@as(i8, -foe.active.boosts.evasion) + 6))];
|
|
@@ -1470,9 +1434,8 @@ fn checkFaint(
|
|
|
1470
1434
|
const foe_fainted = foe.stored().hp == 0;
|
|
1471
1435
|
|
|
1472
1436
|
const player_out = findFirstAlive(side) == 0;
|
|
1473
|
-
const foe_out = findFirstAlive(foe) == 0;
|
|
1474
|
-
const
|
|
1475
|
-
const more = tie or player_out or foe_out;
|
|
1437
|
+
const foe_out = foe_fainted and findFirstAlive(foe) == 0;
|
|
1438
|
+
const more = player_out or foe_out;
|
|
1476
1439
|
|
|
1477
1440
|
if (try faint(battle, player, !(more or foe_fainted), options)) |r| return r;
|
|
1478
1441
|
if (foe_fainted) if (try faint(battle, player.foe(), !more, options)) |r| return r;
|
|
@@ -1480,14 +1443,14 @@ fn checkFaint(
|
|
|
1480
1443
|
assert(!side.active.volatiles.MultiHit);
|
|
1481
1444
|
assert(!foe.active.volatiles.MultiHit);
|
|
1482
1445
|
|
|
1483
|
-
if (
|
|
1484
|
-
try options.log.tie();
|
|
1446
|
+
if (player_out and foe_out) {
|
|
1447
|
+
try options.log.tie(.{});
|
|
1485
1448
|
return Result.Tie;
|
|
1486
1449
|
} else if (player_out) {
|
|
1487
|
-
try options.log.win(player.foe());
|
|
1450
|
+
try options.log.win(.{player.foe()});
|
|
1488
1451
|
return if (player == .P1) Result.Lose else Result.Win;
|
|
1489
1452
|
} else if (foe_out) {
|
|
1490
|
-
try options.log.win(player);
|
|
1453
|
+
try options.log.win(.{player});
|
|
1491
1454
|
return if (player == .P1) Result.Win else Result.Lose;
|
|
1492
1455
|
}
|
|
1493
1456
|
|
|
@@ -1529,7 +1492,7 @@ fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result
|
|
|
1529
1492
|
side.stored().stats.spe;
|
|
1530
1493
|
}
|
|
1531
1494
|
|
|
1532
|
-
try options.log.faint(battle.active(player), done);
|
|
1495
|
+
try options.log.faint(.{ battle.active(player), done });
|
|
1533
1496
|
options.calc.capped(player);
|
|
1534
1497
|
return null;
|
|
1535
1498
|
}
|
|
@@ -1551,8 +1514,8 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1551
1514
|
|
|
1552
1515
|
if (!showdown or damage > 0) {
|
|
1553
1516
|
stored.hp -= damage;
|
|
1554
|
-
// Pokémon Showdown uses
|
|
1555
|
-
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
1517
|
+
// Pokémon Showdown uses damage [of] here but its not relevant in Generation I
|
|
1518
|
+
try options.log.damage(.{ ident, stored, if (brn) Damage.Burn else Damage.Poison });
|
|
1556
1519
|
}
|
|
1557
1520
|
}
|
|
1558
1521
|
|
|
@@ -1577,14 +1540,14 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1577
1540
|
const amount = @min(damage, stored.hp);
|
|
1578
1541
|
stored.hp -= amount;
|
|
1579
1542
|
|
|
1580
|
-
// As above, Pokémon Showdown uses
|
|
1581
|
-
if (amount > 0) try options.log.damage(ident, stored, .LeechSeed);
|
|
1543
|
+
// As above, Pokémon Showdown uses damage [of] but its not relevant
|
|
1544
|
+
if (amount > 0) try options.log.damage(.{ ident, stored, .LeechSeed });
|
|
1582
1545
|
|
|
1583
1546
|
const before = foe_stored.hp;
|
|
1584
1547
|
// Uncapped damage is added back to the foe
|
|
1585
1548
|
foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
|
|
1586
1549
|
// Pokémon Showdown uses the less specific heal here instead of drain... because reasons?
|
|
1587
|
-
if (foe_stored.hp > before) try options.log.heal(foe_ident, foe_stored, .Silent);
|
|
1550
|
+
if (foe_stored.hp > before) try options.log.heal(.{ foe_ident, foe_stored, .Silent });
|
|
1588
1551
|
}
|
|
1589
1552
|
}
|
|
1590
1553
|
|
|
@@ -1595,18 +1558,18 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1595
1558
|
battle.turn += 1;
|
|
1596
1559
|
|
|
1597
1560
|
if (showdown and pkmn.options.ebc and checkEBC(battle)) {
|
|
1598
|
-
try options.log.tie();
|
|
1561
|
+
try options.log.tie(.{});
|
|
1599
1562
|
return Result.Tie;
|
|
1600
1563
|
}
|
|
1601
1564
|
|
|
1602
1565
|
if (showdown and battle.turn >= 1000) {
|
|
1603
|
-
try options.log.tie();
|
|
1566
|
+
try options.log.tie(.{});
|
|
1604
1567
|
return Result.Tie;
|
|
1605
1568
|
} else if (battle.turn >= 65535) {
|
|
1606
1569
|
return Result.Error;
|
|
1607
1570
|
}
|
|
1608
1571
|
|
|
1609
|
-
try options.log.turn(battle.turn);
|
|
1572
|
+
try options.log.turn(.{battle.turn});
|
|
1610
1573
|
|
|
1611
1574
|
return Result.Default;
|
|
1612
1575
|
}
|
|
@@ -1666,9 +1629,9 @@ fn checkEBC(battle: anytype) bool {
|
|
|
1666
1629
|
}
|
|
1667
1630
|
|
|
1668
1631
|
fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
1669
|
-
if (side.active.volatiles.
|
|
1632
|
+
if (side.active.volatiles.disable_move != 0) {
|
|
1670
1633
|
// A Pokémon that transforms after being disabled may end up with less move slots
|
|
1671
|
-
const m = side.active.moves[side.active.volatiles.
|
|
1634
|
+
const m = side.active.moves[side.active.volatiles.disable_move - 1].id;
|
|
1672
1635
|
// side.last_selected_move can be Struggle here on Pokemon Showdown we need an extra check
|
|
1673
1636
|
const last = if (showdown and m == .Bide and side.active.volatiles.Bide)
|
|
1674
1637
|
m
|
|
@@ -1676,14 +1639,14 @@ fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
|
1676
1639
|
side.last_selected_move;
|
|
1677
1640
|
if (m != .None and m == last) {
|
|
1678
1641
|
side.active.volatiles.Charging = false;
|
|
1679
|
-
try options.log.
|
|
1642
|
+
try options.log.cant(.{ ident, .Disable, last });
|
|
1680
1643
|
return true;
|
|
1681
1644
|
}
|
|
1682
1645
|
}
|
|
1683
1646
|
return false;
|
|
1684
1647
|
}
|
|
1685
1648
|
|
|
1686
|
-
|
|
1649
|
+
fn buildRage(battle: anytype, who: Player, options: anytype) !void {
|
|
1687
1650
|
const side = battle.side(who);
|
|
1688
1651
|
if (side.active.volatiles.Rage and side.active.boosts.atk < 6) {
|
|
1689
1652
|
try Effects.boost(battle, who, Move.get(.Rage), options);
|
|
@@ -1701,7 +1664,7 @@ fn handleThrashing(battle: anytype, active: *ActivePokemon, player: Player, opti
|
|
|
1701
1664
|
return true;
|
|
1702
1665
|
}
|
|
1703
1666
|
|
|
1704
|
-
|
|
1667
|
+
fn onBegin(
|
|
1705
1668
|
battle: anytype,
|
|
1706
1669
|
player: Player,
|
|
1707
1670
|
move: Move.Data,
|
|
@@ -1709,7 +1672,6 @@ inline fn onBegin(
|
|
|
1709
1672
|
residual: *bool,
|
|
1710
1673
|
options: anytype,
|
|
1711
1674
|
) !void {
|
|
1712
|
-
assert(move.effect.onBegin());
|
|
1713
1675
|
return switch (move.effect) {
|
|
1714
1676
|
.Confusion => Effects.confusion(battle, player, move, options),
|
|
1715
1677
|
.Conversion => Effects.conversion(battle, player, options),
|
|
@@ -1731,8 +1693,7 @@ inline fn onBegin(
|
|
|
1731
1693
|
};
|
|
1732
1694
|
}
|
|
1733
1695
|
|
|
1734
|
-
|
|
1735
|
-
assert(move.effect.onEnd());
|
|
1696
|
+
fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1736
1697
|
return switch (move.effect) {
|
|
1737
1698
|
.Bide => Effects.bide(battle, player, options),
|
|
1738
1699
|
// zig fmt: off
|
|
@@ -1747,14 +1708,13 @@ inline fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anyty
|
|
|
1747
1708
|
};
|
|
1748
1709
|
}
|
|
1749
1710
|
|
|
1750
|
-
|
|
1711
|
+
fn alwaysHappens(
|
|
1751
1712
|
battle: anytype,
|
|
1752
1713
|
player: Player,
|
|
1753
1714
|
move: Move.Data,
|
|
1754
1715
|
residual: *bool,
|
|
1755
1716
|
options: anytype,
|
|
1756
1717
|
) !void {
|
|
1757
|
-
assert(move.effect.alwaysHappens());
|
|
1758
1718
|
return switch (move.effect) {
|
|
1759
1719
|
.DrainHP, .DreamEater => Effects.drainHP(battle, player, options),
|
|
1760
1720
|
.Explode => Effects.explode(battle, player),
|
|
@@ -1766,7 +1726,7 @@ inline fn alwaysHappens(
|
|
|
1766
1726
|
};
|
|
1767
1727
|
}
|
|
1768
1728
|
|
|
1769
|
-
|
|
1729
|
+
fn moveEffect(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1770
1730
|
return switch (move.effect) {
|
|
1771
1731
|
.BurnChance1, .BurnChance2 => Effects.burnChance(battle, player, move, options),
|
|
1772
1732
|
.ConfusionChance => Effects.confusion(battle, player, move, options),
|
|
@@ -1793,7 +1753,7 @@ pub const Effects = struct {
|
|
|
1793
1753
|
side.active.volatiles.state = 0;
|
|
1794
1754
|
side.active.volatiles.attacks = Rolls.attackingDuration(battle, player, options);
|
|
1795
1755
|
|
|
1796
|
-
try options.log.start(battle.active(player), .Bide);
|
|
1756
|
+
try options.log.start(.{ battle.active(player), .Bide });
|
|
1797
1757
|
}
|
|
1798
1758
|
|
|
1799
1759
|
fn burnChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1807,11 +1767,11 @@ pub const Effects = struct {
|
|
|
1807
1767
|
// GLITCH: Freeze top move selection desync can occur if thawed Pokémon is slower
|
|
1808
1768
|
if (Status.is(foe_stored.status, .FRZ)) {
|
|
1809
1769
|
assert(move.type == .Fire);
|
|
1810
|
-
try options.log.curestatus(
|
|
1770
|
+
try options.log.curestatus(.{
|
|
1811
1771
|
battle.active(player.foe()),
|
|
1812
1772
|
foe_stored.status,
|
|
1813
1773
|
.Message,
|
|
1814
|
-
);
|
|
1774
|
+
});
|
|
1815
1775
|
foe_stored.status = 0;
|
|
1816
1776
|
}
|
|
1817
1777
|
return;
|
|
@@ -1825,7 +1785,7 @@ pub const Effects = struct {
|
|
|
1825
1785
|
foe_stored.status = Status.init(.BRN);
|
|
1826
1786
|
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
1827
1787
|
|
|
1828
|
-
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
1788
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1829
1789
|
}
|
|
1830
1790
|
|
|
1831
1791
|
fn charge(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -1835,8 +1795,8 @@ pub const Effects = struct {
|
|
|
1835
1795
|
volatiles.Charging = true;
|
|
1836
1796
|
const move = side.last_selected_move;
|
|
1837
1797
|
if (move == .Fly or move == .Dig) volatiles.Invulnerable = true;
|
|
1838
|
-
try options.log.laststill();
|
|
1839
|
-
try options.log.prepare(battle.active(player), move);
|
|
1798
|
+
try options.log.laststill(.{});
|
|
1799
|
+
try options.log.prepare(.{ battle.active(player), move });
|
|
1840
1800
|
}
|
|
1841
1801
|
|
|
1842
1802
|
fn confusion(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1847,17 +1807,11 @@ pub const Effects = struct {
|
|
|
1847
1807
|
if (!try Rolls.confusionChance(battle, player, options)) return;
|
|
1848
1808
|
} else {
|
|
1849
1809
|
if (showdown) {
|
|
1850
|
-
if (!try checkHit(battle, player, move, options))
|
|
1851
|
-
|
|
1852
|
-
} else if (sub) {
|
|
1853
|
-
return options.log.fail(battle.active(player.foe()), .None);
|
|
1854
|
-
}
|
|
1810
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1811
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1855
1812
|
} else {
|
|
1856
|
-
if (sub) {
|
|
1857
|
-
|
|
1858
|
-
} else if (!try checkHit(battle, player, move, options)) {
|
|
1859
|
-
return;
|
|
1860
|
-
}
|
|
1813
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1814
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1861
1815
|
}
|
|
1862
1816
|
try options.chance.commit(player, .hit);
|
|
1863
1817
|
}
|
|
@@ -1866,23 +1820,24 @@ pub const Effects = struct {
|
|
|
1866
1820
|
foe.active.volatiles.Confusion = true;
|
|
1867
1821
|
foe.active.volatiles.confusion = Rolls.confusionDuration(battle, player, false, options);
|
|
1868
1822
|
|
|
1869
|
-
try options.log.start(battle.active(player.foe()), .Confusion);
|
|
1823
|
+
try options.log.start(.{ battle.active(player.foe()), .Confusion });
|
|
1870
1824
|
}
|
|
1871
1825
|
|
|
1872
1826
|
fn conversion(battle: anytype, player: Player, options: anytype) !void {
|
|
1873
1827
|
const foe = battle.foe(player);
|
|
1874
1828
|
|
|
1875
1829
|
if (foe.active.volatiles.Invulnerable) {
|
|
1876
|
-
try options.log.lastmiss();
|
|
1877
|
-
return options.log.miss(battle.active(player));
|
|
1830
|
+
try options.log.lastmiss(.{});
|
|
1831
|
+
return options.log.miss(.{battle.active(player)});
|
|
1878
1832
|
}
|
|
1879
1833
|
|
|
1880
1834
|
battle.side(player).active.types = foe.active.types;
|
|
1881
|
-
return options.log.
|
|
1835
|
+
return options.log.start(.{
|
|
1882
1836
|
battle.active(player),
|
|
1837
|
+
.TypeChange,
|
|
1883
1838
|
foe.active.types,
|
|
1884
1839
|
battle.active(player.foe()),
|
|
1885
|
-
);
|
|
1840
|
+
});
|
|
1886
1841
|
}
|
|
1887
1842
|
|
|
1888
1843
|
fn disable(battle: anytype, player: Player, move: Move.Data, options: anytype) !?Result {
|
|
@@ -1896,8 +1851,8 @@ pub const Effects = struct {
|
|
|
1896
1851
|
try options.chance.commit(player, .hit);
|
|
1897
1852
|
}
|
|
1898
1853
|
|
|
1899
|
-
if (volatiles.
|
|
1900
|
-
try options.log.fail(foe_ident, .None);
|
|
1854
|
+
if (volatiles.disable_move != 0) {
|
|
1855
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1901
1856
|
return null;
|
|
1902
1857
|
}
|
|
1903
1858
|
|
|
@@ -1913,7 +1868,7 @@ pub const Effects = struct {
|
|
|
1913
1868
|
// Technically this is still considered simply a "miss" on the cartridge,
|
|
1914
1869
|
// but diverging from Pokémon Showdown here would mostly just be pedantic
|
|
1915
1870
|
if (n == 0) {
|
|
1916
|
-
try options.log.fail(foe_ident, .None);
|
|
1871
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1917
1872
|
return null;
|
|
1918
1873
|
} else if (err) {
|
|
1919
1874
|
// GLITCH: Transform + Mirror Move / Metronome PP softlock
|
|
@@ -1921,18 +1876,18 @@ pub const Effects = struct {
|
|
|
1921
1876
|
return Result.Error;
|
|
1922
1877
|
}
|
|
1923
1878
|
|
|
1924
|
-
volatiles.
|
|
1879
|
+
volatiles.disable_move =
|
|
1925
1880
|
@intCast(try Rolls.moveSlot(battle, player, &foe.active.moves, n, options));
|
|
1926
|
-
volatiles.
|
|
1881
|
+
volatiles.disable_duration = Rolls.disableDuration(battle, player, options);
|
|
1927
1882
|
|
|
1928
|
-
const id = foe.active.move(volatiles.
|
|
1929
|
-
try options.log.
|
|
1883
|
+
const id = foe.active.move(volatiles.disable_move).id;
|
|
1884
|
+
try options.log.start(.{ foe_ident, .Disable, id });
|
|
1930
1885
|
return null;
|
|
1931
1886
|
}
|
|
1932
1887
|
|
|
1933
1888
|
fn drainHP(battle: anytype, player: Player, options: anytype) !void {
|
|
1934
|
-
var
|
|
1935
|
-
|
|
1889
|
+
var stored = battle.side(player).stored();
|
|
1890
|
+
const ident = battle.active(player);
|
|
1936
1891
|
|
|
1937
1892
|
if (battle.last_damage == 0) {
|
|
1938
1893
|
assert(showdown);
|
|
@@ -1945,7 +1900,7 @@ pub const Effects = struct {
|
|
|
1945
1900
|
if (stored.hp == stored.stats.hp) return;
|
|
1946
1901
|
stored.hp = @min(stored.stats.hp, stored.hp + drain);
|
|
1947
1902
|
|
|
1948
|
-
try options.log.
|
|
1903
|
+
try options.log.heal(.{ ident, stored, .Drain, battle.active(player.foe()) });
|
|
1949
1904
|
}
|
|
1950
1905
|
|
|
1951
1906
|
fn explode(battle: anytype, player: Player) !void {
|
|
@@ -1977,7 +1932,7 @@ pub const Effects = struct {
|
|
|
1977
1932
|
if (side.active.volatiles.FocusEnergy) return;
|
|
1978
1933
|
side.active.volatiles.FocusEnergy = true;
|
|
1979
1934
|
|
|
1980
|
-
try options.log.start(battle.active(player), .FocusEnergy);
|
|
1935
|
+
try options.log.start(.{ battle.active(player), .FocusEnergy });
|
|
1981
1936
|
}
|
|
1982
1937
|
|
|
1983
1938
|
fn freezeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1999,7 +1954,7 @@ pub const Effects = struct {
|
|
|
1999
1954
|
foe_stored.status = Status.init(.FRZ);
|
|
2000
1955
|
// GLITCH: Hyper Beam recharging status is not cleared
|
|
2001
1956
|
|
|
2002
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
1957
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2003
1958
|
}
|
|
2004
1959
|
|
|
2005
1960
|
fn haze(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -2007,7 +1962,7 @@ pub const Effects = struct {
|
|
|
2007
1962
|
var side = battle.side(player);
|
|
2008
1963
|
var foe = battle.foe(player);
|
|
2009
1964
|
|
|
2010
|
-
|
|
1965
|
+
const side_stored = side.stored();
|
|
2011
1966
|
var foe_stored = foe.stored();
|
|
2012
1967
|
|
|
2013
1968
|
const player_ident = battle.active(player);
|
|
@@ -2019,8 +1974,8 @@ pub const Effects = struct {
|
|
|
2019
1974
|
side.active.stats = side_stored.stats;
|
|
2020
1975
|
foe.active.stats = foe_stored.stats;
|
|
2021
1976
|
|
|
2022
|
-
try log.activate(player_ident, .Haze);
|
|
2023
|
-
try log.clearallboost();
|
|
1977
|
+
try log.activate(.{ player_ident, .Haze });
|
|
1978
|
+
try log.clearallboost(.{});
|
|
2024
1979
|
|
|
2025
1980
|
// Pokémon Showdown clears P1 then P2 instead of status -> side -> foe
|
|
2026
1981
|
if (showdown) {
|
|
@@ -2028,11 +1983,11 @@ pub const Effects = struct {
|
|
|
2028
1983
|
const p: Player = @enumFromInt(i);
|
|
2029
1984
|
// Pokémon Showdown incorrectly does not prevent sleep/freeze from moving
|
|
2030
1985
|
if (p != player and Status.any(s.stored().status)) {
|
|
2031
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
1986
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2032
1987
|
s.stored().status = 0;
|
|
2033
1988
|
} else if (showdown and s.stored().status == Status.TOX) {
|
|
2034
1989
|
s.stored().status = Status.init(.PSN);
|
|
2035
|
-
try log.status(battle.active(p), s.stored().status, .None);
|
|
1990
|
+
try log.status(.{ battle.active(p), s.stored().status, .None });
|
|
2036
1991
|
}
|
|
2037
1992
|
try clearVolatiles(battle, p, p != player, options);
|
|
2038
1993
|
}
|
|
@@ -2041,7 +1996,7 @@ pub const Effects = struct {
|
|
|
2041
1996
|
if (Status.is(foe_stored.status, .FRZ) or Status.is(foe_stored.status, .SLP)) {
|
|
2042
1997
|
foe.last_selected_move = .SKIP_TURN;
|
|
2043
1998
|
}
|
|
2044
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
1999
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2045
2000
|
foe_stored.status = 0;
|
|
2046
2001
|
}
|
|
2047
2002
|
try clearVolatiles(battle, player, false, options);
|
|
@@ -2057,7 +2012,7 @@ pub const Effects = struct {
|
|
|
2057
2012
|
// GLITCH: HP recovery move failure glitches
|
|
2058
2013
|
const delta = stored.stats.hp - stored.hp;
|
|
2059
2014
|
if (delta == 0 or (delta & 255 == 255 and stored.hp % 256 != 0)) {
|
|
2060
|
-
return
|
|
2015
|
+
return options.log.fail(.{ ident, .None });
|
|
2061
2016
|
}
|
|
2062
2017
|
|
|
2063
2018
|
const rest = side.last_selected_move == .Rest;
|
|
@@ -2065,17 +2020,17 @@ pub const Effects = struct {
|
|
|
2065
2020
|
// Adding the sleep status runs the sleep condition handler to roll duration
|
|
2066
2021
|
if (showdown) battle.rng.advance(1);
|
|
2067
2022
|
stored.status = Status.slf(2);
|
|
2068
|
-
try options.log.
|
|
2023
|
+
try options.log.status(.{ ident, stored.status, .From, Move.Rest });
|
|
2069
2024
|
stored.hp = stored.stats.hp;
|
|
2070
2025
|
} else {
|
|
2071
2026
|
stored.hp = @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2));
|
|
2072
2027
|
}
|
|
2073
|
-
try options.log.heal(ident, stored, if (rest) Heal.Silent else Heal.None);
|
|
2028
|
+
try options.log.heal(.{ ident, stored, if (rest) Heal.Silent else Heal.None });
|
|
2074
2029
|
}
|
|
2075
2030
|
|
|
2076
2031
|
fn hyperBeam(battle: anytype, player: Player, options: anytype) !void {
|
|
2077
2032
|
battle.side(player).active.volatiles.Recharging = true;
|
|
2078
|
-
try options.log.mustrecharge(battle.active(player));
|
|
2033
|
+
try options.log.mustrecharge(.{battle.active(player)});
|
|
2079
2034
|
}
|
|
2080
2035
|
|
|
2081
2036
|
fn leechSeed(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2084,33 +2039,32 @@ pub const Effects = struct {
|
|
|
2084
2039
|
if (showdown) {
|
|
2085
2040
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2086
2041
|
if (!foe.active.volatiles.Invulnerable and foe.active.types.includes(.Grass)) {
|
|
2087
|
-
return options.log.immune(battle.active(player.foe()), .None);
|
|
2042
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
2088
2043
|
}
|
|
2089
2044
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2090
2045
|
if (foe.active.volatiles.LeechSeed) return;
|
|
2091
2046
|
} else {
|
|
2092
2047
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2093
2048
|
if (foe.active.types.includes(.Grass) or foe.active.volatiles.LeechSeed) {
|
|
2094
|
-
try options.log.lastmiss();
|
|
2095
|
-
return options.log.miss(battle.active(player));
|
|
2049
|
+
try options.log.lastmiss(.{});
|
|
2050
|
+
return options.log.miss(.{battle.active(player)});
|
|
2096
2051
|
}
|
|
2097
2052
|
}
|
|
2098
2053
|
|
|
2099
2054
|
try options.chance.commit(player, .hit);
|
|
2100
2055
|
foe.active.volatiles.LeechSeed = true;
|
|
2101
2056
|
|
|
2102
|
-
try options.log.start(battle.active(player.foe()), .LeechSeed);
|
|
2057
|
+
try options.log.start(.{ battle.active(player.foe()), .LeechSeed });
|
|
2103
2058
|
}
|
|
2104
2059
|
|
|
2105
2060
|
fn lightScreen(battle: anytype, player: Player, options: anytype) !void {
|
|
2106
2061
|
var side = battle.side(player);
|
|
2062
|
+
const ident = battle.active(player);
|
|
2107
2063
|
|
|
2108
|
-
if (side.active.volatiles.LightScreen) {
|
|
2109
|
-
return options.log.fail(battle.active(player), .None);
|
|
2110
|
-
}
|
|
2064
|
+
if (side.active.volatiles.LightScreen) return options.log.fail(.{ ident, .None });
|
|
2111
2065
|
side.active.volatiles.LightScreen = true;
|
|
2112
2066
|
|
|
2113
|
-
try options.log.start(
|
|
2067
|
+
try options.log.start(.{ ident, .LightScreen });
|
|
2114
2068
|
}
|
|
2115
2069
|
|
|
2116
2070
|
fn mimic(battle: anytype, player: Player, move: Move.Data, mslot: u8, options: anytype) !void {
|
|
@@ -2139,7 +2093,7 @@ pub const Effects = struct {
|
|
|
2139
2093
|
// Invulnerable foes or 1/256 miss can trigger |-miss| instead of |-fail|
|
|
2140
2094
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2141
2095
|
try options.chance.commit(player, .hit);
|
|
2142
|
-
return
|
|
2096
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2143
2097
|
}
|
|
2144
2098
|
}
|
|
2145
2099
|
if (!try checkHit(battle, player, move, options)) return;
|
|
@@ -2148,7 +2102,7 @@ pub const Effects = struct {
|
|
|
2148
2102
|
const rslot = try Rolls.moveSlot(battle, player, &foe.active.moves, 0, options);
|
|
2149
2103
|
side.active.move(oslot).id = foe.active.move(rslot).id;
|
|
2150
2104
|
|
|
2151
|
-
try options.log.
|
|
2105
|
+
try options.log.start(.{ battle.active(player), .Mimic, side.active.move(oslot).id });
|
|
2152
2106
|
}
|
|
2153
2107
|
|
|
2154
2108
|
fn mist(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -2157,7 +2111,7 @@ pub const Effects = struct {
|
|
|
2157
2111
|
if (side.active.volatiles.Mist) return;
|
|
2158
2112
|
side.active.volatiles.Mist = true;
|
|
2159
2113
|
|
|
2160
|
-
try options.log.start(battle.active(player), .Mist);
|
|
2114
|
+
try options.log.start(.{ battle.active(player), .Mist });
|
|
2161
2115
|
}
|
|
2162
2116
|
|
|
2163
2117
|
fn multiHit(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2183,17 +2137,18 @@ pub const Effects = struct {
|
|
|
2183
2137
|
|
|
2184
2138
|
if (showdown) {
|
|
2185
2139
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2186
|
-
if (immune and !foe.active.volatiles.Invulnerable)
|
|
2187
|
-
|
|
2140
|
+
if (immune and !foe.active.volatiles.Invulnerable) {
|
|
2141
|
+
return log.immune(.{ foe_ident, .None });
|
|
2142
|
+
} else if (!try checkHit(battle, player, move, options)) return;
|
|
2188
2143
|
}
|
|
2189
2144
|
if (Status.any(foe_stored.status)) {
|
|
2190
|
-
return log.fail(
|
|
2145
|
+
return log.fail(.{
|
|
2191
2146
|
foe_ident,
|
|
2192
|
-
if (Status.is(foe_stored.status, .PAR)) .Paralysis else .None,
|
|
2193
|
-
);
|
|
2147
|
+
if (Status.is(foe_stored.status, .PAR)) Fail.Paralysis else Fail.None,
|
|
2148
|
+
});
|
|
2194
2149
|
}
|
|
2195
2150
|
if (!showdown) {
|
|
2196
|
-
if (immune) return log.immune(foe_ident, .None);
|
|
2151
|
+
if (immune) return log.immune(.{ foe_ident, .None });
|
|
2197
2152
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2198
2153
|
}
|
|
2199
2154
|
try options.chance.commit(player, .hit);
|
|
@@ -2201,7 +2156,7 @@ pub const Effects = struct {
|
|
|
2201
2156
|
foe_stored.status = Status.init(.PAR);
|
|
2202
2157
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2203
2158
|
|
|
2204
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2159
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2205
2160
|
}
|
|
2206
2161
|
|
|
2207
2162
|
fn paralyzeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2223,12 +2178,12 @@ pub const Effects = struct {
|
|
|
2223
2178
|
foe_stored.status = Status.init(.PAR);
|
|
2224
2179
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2225
2180
|
|
|
2226
|
-
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
2181
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
2227
2182
|
}
|
|
2228
2183
|
|
|
2229
2184
|
fn payDay(battle: anytype, player: Player, options: anytype) !void {
|
|
2230
2185
|
if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
|
|
2231
|
-
try options.log.fieldactivate();
|
|
2186
|
+
try options.log.fieldactivate(.{});
|
|
2232
2187
|
}
|
|
2233
2188
|
}
|
|
2234
2189
|
|
|
@@ -2243,23 +2198,23 @@ pub const Effects = struct {
|
|
|
2243
2198
|
return;
|
|
2244
2199
|
} else if (foe.active.volatiles.Substitute) {
|
|
2245
2200
|
if (move.effect != .Poison) return;
|
|
2246
|
-
return log.fail(foe_ident, .None);
|
|
2201
|
+
return log.fail(.{ foe_ident, .None });
|
|
2247
2202
|
} else if (Status.any(foe_stored.status)) {
|
|
2248
2203
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2249
2204
|
// Pokémon Showdown considers Toxic to be a status even in Generation I and so
|
|
2250
2205
|
// will not include a fail reason for Toxic vs. Poison or vice-versa...
|
|
2251
|
-
return log.fail(foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2206
|
+
return log.fail(.{ foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2252
2207
|
if (!showdown)
|
|
2253
|
-
.Poison
|
|
2208
|
+
Fail.Poison
|
|
2254
2209
|
else if (toxic == (foe_stored.status == Status.TOX))
|
|
2255
|
-
if (toxic) .Toxic else .Poison
|
|
2210
|
+
if (toxic) Fail.Toxic else Fail.Poison
|
|
2256
2211
|
else
|
|
2257
|
-
.None
|
|
2212
|
+
Fail.None
|
|
2258
2213
|
else
|
|
2259
|
-
.None);
|
|
2214
|
+
Fail.None });
|
|
2260
2215
|
} else if (foe.active.types.includes(.Poison)) {
|
|
2261
2216
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2262
|
-
return log.immune(foe_ident, .None);
|
|
2217
|
+
return log.immune(.{ foe_ident, .None });
|
|
2263
2218
|
}
|
|
2264
2219
|
|
|
2265
2220
|
if (move.effect == .Poison) {
|
|
@@ -2278,7 +2233,7 @@ pub const Effects = struct {
|
|
|
2278
2233
|
foe.active.volatiles.toxic = 0;
|
|
2279
2234
|
}
|
|
2280
2235
|
|
|
2281
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2236
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2282
2237
|
}
|
|
2283
2238
|
|
|
2284
2239
|
fn rage(battle: anytype, player: Player) !void {
|
|
@@ -2298,22 +2253,23 @@ pub const Effects = struct {
|
|
|
2298
2253
|
@as(u8, if (side.last_selected_move == .Struggle) 2 else 4), 1));
|
|
2299
2254
|
stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
|
|
2300
2255
|
|
|
2301
|
-
try options.log.
|
|
2256
|
+
try options.log.damage(.{
|
|
2302
2257
|
battle.active(player),
|
|
2303
2258
|
stored,
|
|
2304
2259
|
.RecoilOf,
|
|
2305
2260
|
battle.active(player.foe()),
|
|
2306
|
-
);
|
|
2261
|
+
});
|
|
2307
2262
|
if (showdown and stored.hp == 0) residual.* = false;
|
|
2308
2263
|
}
|
|
2309
2264
|
|
|
2310
2265
|
fn reflect(battle: anytype, player: Player, options: anytype) !void {
|
|
2311
2266
|
var side = battle.side(player);
|
|
2267
|
+
const ident = battle.active(player);
|
|
2312
2268
|
|
|
2313
|
-
if (side.active.volatiles.Reflect) return options.log.fail(
|
|
2269
|
+
if (side.active.volatiles.Reflect) return options.log.fail(.{ ident, .None });
|
|
2314
2270
|
side.active.volatiles.Reflect = true;
|
|
2315
2271
|
|
|
2316
|
-
try options.log.start(
|
|
2272
|
+
try options.log.start(.{ ident, .Reflect });
|
|
2317
2273
|
}
|
|
2318
2274
|
|
|
2319
2275
|
fn sleep(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2327,10 +2283,10 @@ pub const Effects = struct {
|
|
|
2327
2283
|
// the Sleep Clause Mod might activate, causing us to not actually bypass
|
|
2328
2284
|
} else {
|
|
2329
2285
|
if (Status.any(foe_stored.status)) {
|
|
2330
|
-
return options.log.fail(
|
|
2286
|
+
return options.log.fail(.{
|
|
2331
2287
|
foe_ident,
|
|
2332
|
-
if (Status.is(foe_stored.status, .SLP)) .Sleep else .None,
|
|
2333
|
-
);
|
|
2288
|
+
if (Status.is(foe_stored.status, .SLP)) Fail.Sleep else Fail.None,
|
|
2289
|
+
});
|
|
2334
2290
|
}
|
|
2335
2291
|
// If checkHit in doMove didn't return true Pokémon Showdown wouldn't be in here
|
|
2336
2292
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
@@ -2347,18 +2303,17 @@ pub const Effects = struct {
|
|
|
2347
2303
|
|
|
2348
2304
|
foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
|
|
2349
2305
|
const last = battle.side(player).last_selected_move;
|
|
2350
|
-
try options.log.
|
|
2306
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .From, last });
|
|
2351
2307
|
}
|
|
2352
2308
|
|
|
2353
2309
|
fn splash(battle: anytype, player: Player, options: anytype) !void {
|
|
2354
|
-
try options.log.activate(battle.active(player), .Splash);
|
|
2310
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
2355
2311
|
}
|
|
2356
2312
|
|
|
2357
2313
|
fn substitute(battle: anytype, player: Player, residual: *bool, options: anytype) !void {
|
|
2358
2314
|
var side = battle.side(player);
|
|
2359
2315
|
if (side.active.volatiles.Substitute) {
|
|
2360
|
-
|
|
2361
|
-
return;
|
|
2316
|
+
return options.log.fail(.{ battle.active(player), .Substitute });
|
|
2362
2317
|
}
|
|
2363
2318
|
|
|
2364
2319
|
assert(side.stored().stats.hp <= 1023);
|
|
@@ -2370,17 +2325,16 @@ pub const Effects = struct {
|
|
|
2370
2325
|
// avoid having to convert to floating point)
|
|
2371
2326
|
const required_hp = if (showdown) @divFloor(side.stored().stats.hp - 1, 4) + 1 else hp;
|
|
2372
2327
|
if (side.stored().hp < required_hp) {
|
|
2373
|
-
|
|
2374
|
-
return;
|
|
2328
|
+
return options.log.fail(.{ battle.active(player), .Weak });
|
|
2375
2329
|
}
|
|
2376
2330
|
|
|
2377
2331
|
// GLITCH: can leave the user with 0 HP (faints later) because didn't check '<=' above
|
|
2378
2332
|
side.stored().hp -= hp;
|
|
2379
2333
|
side.active.volatiles.substitute = hp + 1;
|
|
2380
2334
|
side.active.volatiles.Substitute = true;
|
|
2381
|
-
try options.log.start(battle.active(player), .Substitute);
|
|
2335
|
+
try options.log.start(.{ battle.active(player), .Substitute });
|
|
2382
2336
|
if (hp > 0) {
|
|
2383
|
-
try options.log.damage(battle.active(player), side.stored(), .None);
|
|
2337
|
+
try options.log.damage(.{ battle.active(player), side.stored(), .None });
|
|
2384
2338
|
if (showdown and side.stored().hp == 0) residual.* = false;
|
|
2385
2339
|
}
|
|
2386
2340
|
}
|
|
@@ -2394,8 +2348,8 @@ pub const Effects = struct {
|
|
|
2394
2348
|
}
|
|
2395
2349
|
battle.last_damage = 0;
|
|
2396
2350
|
} else {
|
|
2397
|
-
try options.log.fail(battle.active(player), .None);
|
|
2398
|
-
try options.log.laststill();
|
|
2351
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2352
|
+
try options.log.laststill(.{});
|
|
2399
2353
|
}
|
|
2400
2354
|
}
|
|
2401
2355
|
|
|
@@ -2434,7 +2388,7 @@ pub const Effects = struct {
|
|
|
2434
2388
|
side.active.moves[i].pp = if (m.id != .None) 5 else 0;
|
|
2435
2389
|
}
|
|
2436
2390
|
|
|
2437
|
-
try options.log.transform(battle.active(player), foe_ident);
|
|
2391
|
+
try options.log.transform(.{ battle.active(player), foe_ident });
|
|
2438
2392
|
}
|
|
2439
2393
|
|
|
2440
2394
|
fn binding(battle: anytype, player: Player, rewrap: bool, options: anytype) !void {
|
|
@@ -2464,87 +2418,87 @@ pub const Effects = struct {
|
|
|
2464
2418
|
switch (move.effect) {
|
|
2465
2419
|
.AttackUp1, .AttackUp2, .Rage => {
|
|
2466
2420
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2467
|
-
if (boosts.atk == 6) return
|
|
2421
|
+
if (boosts.atk == 6) return log.fail(.{ ident, .None });
|
|
2468
2422
|
const n: u2 = if (move.effect == .AttackUp2) 2 else 1;
|
|
2469
2423
|
boosts.atk = @as(i4, @intCast(@min(6, @as(i8, boosts.atk) + n)));
|
|
2470
|
-
const reason = if (move.effect == .Rage)
|
|
2424
|
+
const reason: Boost = if (move.effect == .Rage) .Rage else .Attack;
|
|
2471
2425
|
if (stats.atk == MAX_STAT_VALUE) {
|
|
2472
2426
|
boosts.atk -= 1;
|
|
2473
2427
|
if (showdown) {
|
|
2474
|
-
try log.boost(ident, reason, n);
|
|
2475
|
-
try log.boost(ident,
|
|
2428
|
+
try log.boost(.{ ident, reason, n });
|
|
2429
|
+
try log.boost(.{ ident, .Attack, -1 });
|
|
2476
2430
|
if (move.effect == .Rage) return;
|
|
2477
2431
|
}
|
|
2478
|
-
return
|
|
2432
|
+
return log.fail(.{ ident, .None });
|
|
2479
2433
|
}
|
|
2480
|
-
|
|
2434
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2481
2435
|
const stat = unmodifiedStats(battle, side).atk;
|
|
2482
2436
|
stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2483
|
-
try log.boost(ident, reason, n);
|
|
2437
|
+
try log.boost(.{ ident, reason, n });
|
|
2484
2438
|
// Pokémon Showdown doesn't re-apply status modifiers after Rage boosts
|
|
2485
2439
|
if (showdown and move.effect == .Rage) return;
|
|
2486
2440
|
},
|
|
2487
2441
|
.DefenseUp1, .DefenseUp2 => {
|
|
2488
2442
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2489
|
-
if (boosts.def == 6) return
|
|
2443
|
+
if (boosts.def == 6) return log.fail(.{ ident, .None });
|
|
2490
2444
|
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
2491
2445
|
boosts.def = @intCast(@min(6, @as(i8, boosts.def) + n));
|
|
2492
2446
|
if (stats.def == MAX_STAT_VALUE) {
|
|
2493
2447
|
boosts.def -= 1;
|
|
2494
2448
|
if (showdown) {
|
|
2495
|
-
try log.boost(ident, .Defense, n);
|
|
2496
|
-
try log.boost(ident, .Defense, -1);
|
|
2449
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2450
|
+
try log.boost(.{ ident, .Defense, -1 });
|
|
2497
2451
|
}
|
|
2498
|
-
return
|
|
2452
|
+
return log.fail(.{ ident, .None });
|
|
2499
2453
|
}
|
|
2500
|
-
|
|
2454
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2501
2455
|
const stat = unmodifiedStats(battle, side).def;
|
|
2502
2456
|
stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2503
|
-
try log.boost(ident, .Defense, n);
|
|
2457
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2504
2458
|
},
|
|
2505
2459
|
.SpeedUp2 => {
|
|
2506
2460
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2507
|
-
if (boosts.spe == 6) return
|
|
2461
|
+
if (boosts.spe == 6) return log.fail(.{ ident, .None });
|
|
2508
2462
|
boosts.spe = @intCast(@min(6, @as(i8, boosts.spe) + 2));
|
|
2509
2463
|
if (stats.spe == MAX_STAT_VALUE) {
|
|
2510
2464
|
boosts.spe -= 1;
|
|
2511
2465
|
if (showdown) {
|
|
2512
|
-
try log.boost(ident, .Speed, 2);
|
|
2513
|
-
try log.boost(ident, .Speed, -1);
|
|
2466
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2467
|
+
try log.boost(.{ ident, .Speed, -1 });
|
|
2514
2468
|
}
|
|
2515
|
-
return
|
|
2469
|
+
return log.fail(.{ ident, .None });
|
|
2516
2470
|
}
|
|
2517
|
-
|
|
2471
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2518
2472
|
const stat = unmodifiedStats(battle, side).spe;
|
|
2519
2473
|
stats.spe = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2520
|
-
try log.boost(ident, .Speed, 2);
|
|
2474
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2521
2475
|
},
|
|
2522
2476
|
.SpecialUp1, .SpecialUp2 => {
|
|
2523
2477
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2524
|
-
if (boosts.spc == 6) return
|
|
2478
|
+
if (boosts.spc == 6) return log.fail(.{ ident, .None });
|
|
2525
2479
|
const n: u2 = if (move.effect == .SpecialUp2) 2 else 1;
|
|
2526
2480
|
boosts.spc = @intCast(@min(6, @as(i8, boosts.spc) + n));
|
|
2527
2481
|
if (stats.spc == MAX_STAT_VALUE) {
|
|
2528
2482
|
boosts.spc -= 1;
|
|
2529
2483
|
if (showdown) {
|
|
2530
|
-
try log.boost(ident, .SpecialAttack, n);
|
|
2531
|
-
try log.boost(ident, .SpecialAttack, -1);
|
|
2532
|
-
try log.boost(ident, .SpecialDefense, n);
|
|
2533
|
-
try log.boost(ident, .SpecialDefense, -1);
|
|
2484
|
+
try log.boost(.{ ident, .SpecialAttack, n });
|
|
2485
|
+
try log.boost(.{ ident, .SpecialAttack, -1 });
|
|
2486
|
+
try log.boost(.{ ident, .SpecialDefense, n });
|
|
2487
|
+
try log.boost(.{ ident, .SpecialDefense, -1 });
|
|
2534
2488
|
}
|
|
2535
|
-
return
|
|
2489
|
+
return log.fail(.{ ident, .None });
|
|
2536
2490
|
}
|
|
2537
|
-
|
|
2491
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2538
2492
|
const stat = unmodifiedStats(battle, side).spc;
|
|
2539
2493
|
stats.spc = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2540
|
-
try log.boost(ident, .SpecialAttack, n);
|
|
2541
|
-
try log.boost(ident, .SpecialDefense, n);
|
|
2494
|
+
try log.boost(.{ ident, .SpecialAttack, n });
|
|
2495
|
+
try log.boost(.{ ident, .SpecialDefense, n });
|
|
2542
2496
|
},
|
|
2543
2497
|
.EvasionUp1 => {
|
|
2544
2498
|
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2545
|
-
if (boosts.evasion == 6) return
|
|
2499
|
+
if (boosts.evasion == 6) return log.fail(.{ ident, .None });
|
|
2546
2500
|
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2547
|
-
try log.boost(ident, .Evasion, 1);
|
|
2501
|
+
try log.boost(.{ ident, .Evasion, 1 });
|
|
2548
2502
|
},
|
|
2549
2503
|
else => unreachable,
|
|
2550
2504
|
}
|
|
@@ -2559,7 +2513,7 @@ pub const Effects = struct {
|
|
|
2559
2513
|
const foe_ident = battle.active(player.foe());
|
|
2560
2514
|
const secondary = move.effect.isStatDownChance();
|
|
2561
2515
|
|
|
2562
|
-
if (foe.active.volatiles.Substitute) return if (!secondary) log.fail(foe_ident, .None);
|
|
2516
|
+
if (foe.active.volatiles.Substitute) return if (!secondary) log.fail(.{ foe_ident, .None });
|
|
2563
2517
|
|
|
2564
2518
|
if (secondary) {
|
|
2565
2519
|
const proc = try Rolls.unboost(battle, player, options);
|
|
@@ -2577,82 +2531,82 @@ pub const Effects = struct {
|
|
|
2577
2531
|
switch (move.effect) {
|
|
2578
2532
|
.AttackDown1, .AttackDownChance => {
|
|
2579
2533
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2580
|
-
if (boosts.atk == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2534
|
+
if (boosts.atk == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2581
2535
|
boosts.atk = @intCast(@max(-6, @as(i8, boosts.atk) - 1));
|
|
2582
2536
|
if (stats.atk == 1) {
|
|
2583
2537
|
boosts.atk += 1;
|
|
2584
2538
|
if (showdown) {
|
|
2585
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2586
|
-
try log.boost(foe_ident, .Attack, 1);
|
|
2539
|
+
try log.boost(.{ foe_ident, .Attack, -1 });
|
|
2540
|
+
try log.boost(.{ foe_ident, .Attack, 1 });
|
|
2587
2541
|
}
|
|
2588
|
-
return
|
|
2542
|
+
return log.fail(.{ foe_ident, .None });
|
|
2589
2543
|
}
|
|
2590
|
-
|
|
2544
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2591
2545
|
const stat = unmodifiedStats(battle, foe).atk;
|
|
2592
2546
|
stats.atk = @max(1, stat * mod[0] / mod[1]);
|
|
2593
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2547
|
+
try log.boost(.{ foe_ident, .Attack, -1 });
|
|
2594
2548
|
},
|
|
2595
2549
|
.DefenseDown1, .DefenseDown2, .DefenseDownChance => {
|
|
2596
2550
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2597
|
-
if (boosts.def == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2551
|
+
if (boosts.def == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2598
2552
|
const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
|
|
2599
2553
|
boosts.def = @intCast(@max(-6, @as(i8, boosts.def) - n));
|
|
2600
2554
|
if (stats.def == 1) {
|
|
2601
2555
|
boosts.def += 1;
|
|
2602
2556
|
if (showdown) {
|
|
2603
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2604
|
-
try log.boost(foe_ident, .Defense, 1);
|
|
2557
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2558
|
+
try log.boost(.{ foe_ident, .Defense, 1 });
|
|
2605
2559
|
}
|
|
2606
|
-
return
|
|
2560
|
+
return log.fail(.{ foe_ident, .None });
|
|
2607
2561
|
}
|
|
2608
|
-
|
|
2562
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2609
2563
|
const stat = unmodifiedStats(battle, foe).def;
|
|
2610
2564
|
stats.def = @max(1, stat * mod[0] / mod[1]);
|
|
2611
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2565
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2612
2566
|
},
|
|
2613
2567
|
.SpeedDown1, .SpeedDownChance => {
|
|
2614
2568
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2615
|
-
if (boosts.spe == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2569
|
+
if (boosts.spe == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2616
2570
|
boosts.spe = @intCast(@max(-6, @as(i8, boosts.spe) - 1));
|
|
2617
2571
|
if (stats.spe == 1) {
|
|
2618
2572
|
boosts.spe += 1;
|
|
2619
2573
|
if (showdown) {
|
|
2620
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2621
|
-
try log.boost(foe_ident, .Speed, 1);
|
|
2574
|
+
try log.boost(.{ foe_ident, .Speed, -1 });
|
|
2575
|
+
try log.boost(.{ foe_ident, .Speed, 1 });
|
|
2622
2576
|
}
|
|
2623
|
-
return
|
|
2577
|
+
return log.fail(.{ foe_ident, .None });
|
|
2624
2578
|
}
|
|
2625
|
-
|
|
2579
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2626
2580
|
const stat = unmodifiedStats(battle, foe).spe;
|
|
2627
2581
|
stats.spe = @max(1, stat * mod[0] / mod[1]);
|
|
2628
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2582
|
+
try log.boost(.{ foe_ident, .Speed, -1 });
|
|
2629
2583
|
assert(boosts.spe >= -6);
|
|
2630
2584
|
},
|
|
2631
2585
|
.SpecialDownChance => {
|
|
2632
2586
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2633
|
-
if (boosts.spc == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2587
|
+
if (boosts.spc == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2634
2588
|
boosts.spc = @intCast(@max(-6, @as(i8, boosts.spc) - 1));
|
|
2635
2589
|
if (stats.spc == 1) {
|
|
2636
2590
|
boosts.spc += 1;
|
|
2637
2591
|
if (showdown) {
|
|
2638
|
-
try log.boost(foe_ident, .SpecialAttack, -1);
|
|
2639
|
-
try log.boost(foe_ident, .SpecialAttack, 1);
|
|
2640
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2641
|
-
try log.boost(foe_ident, .SpecialDefense, 1);
|
|
2592
|
+
try log.boost(.{ foe_ident, .SpecialAttack, -1 });
|
|
2593
|
+
try log.boost(.{ foe_ident, .SpecialAttack, 1 });
|
|
2594
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2595
|
+
try log.boost(.{ foe_ident, .SpecialDefense, 1 });
|
|
2642
2596
|
}
|
|
2643
|
-
return
|
|
2597
|
+
return log.fail(.{ foe_ident, .None });
|
|
2644
2598
|
}
|
|
2645
|
-
|
|
2599
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2646
2600
|
const stat = unmodifiedStats(battle, foe).spc;
|
|
2647
2601
|
stats.spc = @max(1, stat * mod[0] / mod[1]);
|
|
2648
|
-
try log.boost(foe_ident, .SpecialAttack, -1);
|
|
2649
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2602
|
+
try log.boost(.{ foe_ident, .SpecialAttack, -1 });
|
|
2603
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2650
2604
|
},
|
|
2651
2605
|
.AccuracyDown1 => {
|
|
2652
2606
|
assert(boosts.accuracy >= -6 and boosts.accuracy <= 6);
|
|
2653
|
-
if (boosts.accuracy == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2607
|
+
if (boosts.accuracy == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2654
2608
|
boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
|
|
2655
|
-
try log.boost(foe_ident, .Accuracy, -1);
|
|
2609
|
+
try log.boost(.{ foe_ident, .Accuracy, -1 });
|
|
2656
2610
|
},
|
|
2657
2611
|
else => unreachable,
|
|
2658
2612
|
}
|
|
@@ -2676,7 +2630,7 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
2676
2630
|
}
|
|
2677
2631
|
}
|
|
2678
2632
|
|
|
2679
|
-
|
|
2633
|
+
fn isForced(active: anytype) bool {
|
|
2680
2634
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
2681
2635
|
active.volatiles.Thrashing or active.volatiles.Charging;
|
|
2682
2636
|
}
|
|
@@ -2689,27 +2643,27 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2689
2643
|
|
|
2690
2644
|
options.chance.clearDurations(who, clear);
|
|
2691
2645
|
|
|
2692
|
-
if (volatiles.
|
|
2693
|
-
volatiles.
|
|
2694
|
-
volatiles.
|
|
2695
|
-
try log.end(ident, .DisableSilent);
|
|
2646
|
+
if (volatiles.disable_move != 0) {
|
|
2647
|
+
volatiles.disable_move = 0;
|
|
2648
|
+
volatiles.disable_duration = 0;
|
|
2649
|
+
try log.end(.{ ident, .DisableSilent });
|
|
2696
2650
|
}
|
|
2697
2651
|
if (volatiles.Confusion) {
|
|
2698
2652
|
// volatiles.confusion is left unchanged
|
|
2699
2653
|
volatiles.Confusion = false;
|
|
2700
|
-
try log.end(ident, .ConfusionSilent);
|
|
2654
|
+
try log.end(.{ ident, .ConfusionSilent });
|
|
2701
2655
|
}
|
|
2702
2656
|
if (volatiles.Mist) {
|
|
2703
2657
|
volatiles.Mist = false;
|
|
2704
|
-
try log.end(ident, .MistSilent);
|
|
2658
|
+
try log.end(.{ ident, .MistSilent });
|
|
2705
2659
|
}
|
|
2706
2660
|
if (volatiles.FocusEnergy) {
|
|
2707
2661
|
volatiles.FocusEnergy = false;
|
|
2708
|
-
try log.end(ident, .FocusEnergySilent);
|
|
2662
|
+
try log.end(.{ ident, .FocusEnergySilent });
|
|
2709
2663
|
}
|
|
2710
2664
|
if (volatiles.LeechSeed) {
|
|
2711
2665
|
volatiles.LeechSeed = false;
|
|
2712
|
-
try log.end(ident, .LeechSeedSilent);
|
|
2666
|
+
try log.end(.{ ident, .LeechSeedSilent });
|
|
2713
2667
|
}
|
|
2714
2668
|
if (!showdown and volatiles.Toxic) {
|
|
2715
2669
|
volatiles.Toxic = false;
|
|
@@ -2717,24 +2671,26 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2717
2671
|
}
|
|
2718
2672
|
if (volatiles.LightScreen) {
|
|
2719
2673
|
volatiles.LightScreen = false;
|
|
2720
|
-
try log.end(ident, .LightScreenSilent);
|
|
2674
|
+
try log.end(.{ ident, .LightScreenSilent });
|
|
2721
2675
|
}
|
|
2722
2676
|
if (volatiles.Reflect) {
|
|
2723
2677
|
volatiles.Reflect = false;
|
|
2724
|
-
try log.end(ident, .ReflectSilent);
|
|
2678
|
+
try log.end(.{ ident, .ReflectSilent });
|
|
2725
2679
|
}
|
|
2726
2680
|
if (showdown and volatiles.Toxic) {
|
|
2727
2681
|
volatiles.Toxic = false;
|
|
2728
2682
|
// Pokémon Showdown erroneously clears the toxic counter
|
|
2729
2683
|
volatiles.toxic = 0;
|
|
2730
|
-
try log.end(ident, .ToxicSilent);
|
|
2684
|
+
try log.end(.{ ident, .ToxicSilent });
|
|
2731
2685
|
}
|
|
2732
2686
|
}
|
|
2733
2687
|
|
|
2734
2688
|
pub const Rolls = struct {
|
|
2735
|
-
|
|
2689
|
+
fn speedTie(battle: anytype, options: anytype) !bool {
|
|
2736
2690
|
const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
|
|
2737
2691
|
player == .P1
|
|
2692
|
+
else if (@hasDecl(@TypeOf(battle.rng), "speedTie"))
|
|
2693
|
+
battle.rng.speedTie()
|
|
2738
2694
|
else if (showdown)
|
|
2739
2695
|
battle.rng.range(u8, 0, 2) == 0
|
|
2740
2696
|
else
|
|
@@ -2744,9 +2700,11 @@ pub const Rolls = struct {
|
|
|
2744
2700
|
return p1;
|
|
2745
2701
|
}
|
|
2746
2702
|
|
|
2747
|
-
|
|
2703
|
+
fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
|
|
2748
2704
|
const crit = if (options.calc.overridden(player, .critical_hit)) |val|
|
|
2749
2705
|
val == .true
|
|
2706
|
+
else if (@hasDecl(@TypeOf(battle.rng), "criticalHit"))
|
|
2707
|
+
battle.rng.criticalHit(player, rate)
|
|
2750
2708
|
else if (showdown)
|
|
2751
2709
|
battle.rng.chance(u8, rate, 256)
|
|
2752
2710
|
else
|
|
@@ -2756,9 +2714,11 @@ pub const Rolls = struct {
|
|
|
2756
2714
|
return crit;
|
|
2757
2715
|
}
|
|
2758
2716
|
|
|
2759
|
-
|
|
2717
|
+
fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2760
2718
|
const roll = if (options.calc.overridden(player, .damage)) |val|
|
|
2761
2719
|
val
|
|
2720
|
+
else if (@hasDecl(@TypeOf(battle.rng), "damage"))
|
|
2721
|
+
battle.rng.damage(player)
|
|
2762
2722
|
else roll: {
|
|
2763
2723
|
if (showdown) break :roll battle.rng.range(u8, 217, 256);
|
|
2764
2724
|
while (true) {
|
|
@@ -2772,9 +2732,11 @@ pub const Rolls = struct {
|
|
|
2772
2732
|
return roll;
|
|
2773
2733
|
}
|
|
2774
2734
|
|
|
2775
|
-
|
|
2735
|
+
fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) bool {
|
|
2776
2736
|
const ok = if (options.calc.overridden(player, .hit)) |val|
|
|
2777
2737
|
val == .true
|
|
2738
|
+
else if (@hasDecl(@TypeOf(battle.rng), "hit"))
|
|
2739
|
+
battle.rng.hit(player, accuracy)
|
|
2778
2740
|
else if (showdown)
|
|
2779
2741
|
battle.rng.chance(u8, accuracy, 256)
|
|
2780
2742
|
else
|
|
@@ -2784,9 +2746,11 @@ pub const Rolls = struct {
|
|
|
2784
2746
|
return ok;
|
|
2785
2747
|
}
|
|
2786
2748
|
|
|
2787
|
-
|
|
2749
|
+
fn confused(battle: anytype, player: Player, options: anytype) !bool {
|
|
2788
2750
|
const cfz = if (options.calc.overridden(player, .confused)) |val|
|
|
2789
2751
|
val == .true
|
|
2752
|
+
else if (@hasDecl(@TypeOf(battle.rng), "confused"))
|
|
2753
|
+
battle.rng.confused(player)
|
|
2790
2754
|
else if (showdown)
|
|
2791
2755
|
!battle.rng.chance(u8, 128, 256)
|
|
2792
2756
|
else
|
|
@@ -2796,9 +2760,11 @@ pub const Rolls = struct {
|
|
|
2796
2760
|
return cfz;
|
|
2797
2761
|
}
|
|
2798
2762
|
|
|
2799
|
-
|
|
2763
|
+
fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
|
|
2800
2764
|
const par = if (options.calc.overridden(player, .paralyzed)) |val|
|
|
2801
2765
|
val == .true
|
|
2766
|
+
else if (@hasDecl(@TypeOf(battle.rng), "paralyzed"))
|
|
2767
|
+
battle.rng.paralyzed(player)
|
|
2802
2768
|
else if (showdown)
|
|
2803
2769
|
battle.rng.chance(u8, 63, 256)
|
|
2804
2770
|
else
|
|
@@ -2808,9 +2774,11 @@ pub const Rolls = struct {
|
|
|
2808
2774
|
return par;
|
|
2809
2775
|
}
|
|
2810
2776
|
|
|
2811
|
-
|
|
2777
|
+
fn confusionChance(battle: anytype, player: Player, options: anytype) !bool {
|
|
2812
2778
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2813
2779
|
val == .true
|
|
2780
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2781
|
+
battle.rng.secondaryChance(player, 25)
|
|
2814
2782
|
else if (showdown)
|
|
2815
2783
|
battle.rng.chance(u8, 25, 256)
|
|
2816
2784
|
else
|
|
@@ -2820,11 +2788,13 @@ pub const Rolls = struct {
|
|
|
2820
2788
|
return proc;
|
|
2821
2789
|
}
|
|
2822
2790
|
|
|
2823
|
-
|
|
2791
|
+
fn secondaryChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2824
2792
|
const rate: u8 = if (low) 26 else 77;
|
|
2825
2793
|
|
|
2826
2794
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2827
2795
|
val == .true
|
|
2796
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2797
|
+
battle.rng.secondaryChance(player, rate)
|
|
2828
2798
|
else if (showdown)
|
|
2829
2799
|
battle.rng.chance(u8, rate, 256)
|
|
2830
2800
|
else
|
|
@@ -2834,11 +2804,13 @@ pub const Rolls = struct {
|
|
|
2834
2804
|
return proc;
|
|
2835
2805
|
}
|
|
2836
2806
|
|
|
2837
|
-
|
|
2807
|
+
fn poisonChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2838
2808
|
const rate: u8 = if (low) 52 else 103;
|
|
2839
2809
|
|
|
2840
2810
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2841
2811
|
val == .true
|
|
2812
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2813
|
+
battle.rng.secondaryChance(player, rate)
|
|
2842
2814
|
else if (showdown)
|
|
2843
2815
|
battle.rng.chance(u8, rate, 256)
|
|
2844
2816
|
else
|
|
@@ -2848,9 +2820,11 @@ pub const Rolls = struct {
|
|
|
2848
2820
|
return proc;
|
|
2849
2821
|
}
|
|
2850
2822
|
|
|
2851
|
-
|
|
2823
|
+
fn unboost(battle: anytype, player: Player, options: anytype) !bool {
|
|
2852
2824
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2853
2825
|
val == .true
|
|
2826
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2827
|
+
battle.rng.secondaryChance(player, 85)
|
|
2854
2828
|
else if (showdown)
|
|
2855
2829
|
battle.rng.chance(u8, 85, 256)
|
|
2856
2830
|
else
|
|
@@ -2860,14 +2834,14 @@ pub const Rolls = struct {
|
|
|
2860
2834
|
return proc;
|
|
2861
2835
|
}
|
|
2862
2836
|
|
|
2863
|
-
|
|
2837
|
+
fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
2864
2838
|
const move: Move = if (options.calc.overridden(player, .metronome)) |val|
|
|
2865
2839
|
val
|
|
2866
|
-
else if (
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2840
|
+
else if (@hasDecl(@TypeOf(battle.rng), "metronome"))
|
|
2841
|
+
battle.rng.metronome(player)
|
|
2842
|
+
else if (showdown)
|
|
2843
|
+
Move.METRONOME[battle.rng.range(u8, 0, Move.METRONOME.len)]
|
|
2844
|
+
else move: {
|
|
2871
2845
|
while (true) {
|
|
2872
2846
|
const r = battle.rng.next();
|
|
2873
2847
|
if (r == 0 or r == @intFromEnum(Move.Metronome)) continue;
|
|
@@ -2880,9 +2854,11 @@ pub const Rolls = struct {
|
|
|
2880
2854
|
return move;
|
|
2881
2855
|
}
|
|
2882
2856
|
|
|
2883
|
-
|
|
2857
|
+
fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
|
|
2884
2858
|
const power = if (options.calc.overridden(player, .psywave)) |val|
|
|
2885
2859
|
val - 1
|
|
2860
|
+
else if (@hasDecl(@TypeOf(battle.rng), "psywave"))
|
|
2861
|
+
battle.rng.psywave(player)
|
|
2886
2862
|
else if (showdown)
|
|
2887
2863
|
battle.rng.range(u8, 0, max)
|
|
2888
2864
|
else power: {
|
|
@@ -2897,11 +2873,13 @@ pub const Rolls = struct {
|
|
|
2897
2873
|
return power;
|
|
2898
2874
|
}
|
|
2899
2875
|
|
|
2900
|
-
|
|
2876
|
+
fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2901
2877
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2902
2878
|
@intCast(val)
|
|
2879
|
+
else if (@hasDecl(@TypeOf(battle.rng), "sleepDuration"))
|
|
2880
|
+
battle.rng.sleepDuration(player)
|
|
2903
2881
|
else if (showdown)
|
|
2904
|
-
|
|
2882
|
+
battle.rng.range(u3, 1, 8)
|
|
2905
2883
|
else duration: {
|
|
2906
2884
|
while (true) {
|
|
2907
2885
|
const r = battle.rng.next() & 7;
|
|
@@ -2914,11 +2892,13 @@ pub const Rolls = struct {
|
|
|
2914
2892
|
return duration;
|
|
2915
2893
|
}
|
|
2916
2894
|
|
|
2917
|
-
|
|
2895
|
+
fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
|
|
2918
2896
|
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2919
2897
|
val
|
|
2898
|
+
else if (@hasDecl(@TypeOf(battle.rng), "disableDuration"))
|
|
2899
|
+
battle.rng.disableDuration(player)
|
|
2920
2900
|
else if (showdown)
|
|
2921
|
-
|
|
2901
|
+
battle.rng.range(u4, 1, 9)
|
|
2922
2902
|
else
|
|
2923
2903
|
@intCast((battle.rng.next() & 7) + 1);
|
|
2924
2904
|
|
|
@@ -2927,11 +2907,13 @@ pub const Rolls = struct {
|
|
|
2927
2907
|
return duration;
|
|
2928
2908
|
}
|
|
2929
2909
|
|
|
2930
|
-
|
|
2910
|
+
fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
|
|
2931
2911
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2932
2912
|
@intCast(val)
|
|
2913
|
+
else if (@hasDecl(@TypeOf(battle.rng), "confusionDuration"))
|
|
2914
|
+
battle.rng.confusionDuration(player, self)
|
|
2933
2915
|
else if (showdown)
|
|
2934
|
-
|
|
2916
|
+
battle.rng.range(u3, 2, 6)
|
|
2935
2917
|
else
|
|
2936
2918
|
@intCast((battle.rng.next() & 3) + 2);
|
|
2937
2919
|
|
|
@@ -2940,11 +2922,13 @@ pub const Rolls = struct {
|
|
|
2940
2922
|
return duration;
|
|
2941
2923
|
}
|
|
2942
2924
|
|
|
2943
|
-
|
|
2925
|
+
fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2944
2926
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2945
2927
|
@intCast(val)
|
|
2928
|
+
else if (@hasDecl(@TypeOf(battle.rng), "attackingDuration"))
|
|
2929
|
+
battle.rng.attackingDuration(player)
|
|
2946
2930
|
else if (showdown)
|
|
2947
|
-
|
|
2931
|
+
battle.rng.range(u3, 2, 4)
|
|
2948
2932
|
else
|
|
2949
2933
|
@intCast((battle.rng.next() & 1) + 2);
|
|
2950
2934
|
|
|
@@ -2953,7 +2937,9 @@ pub const Rolls = struct {
|
|
|
2953
2937
|
return duration;
|
|
2954
2938
|
}
|
|
2955
2939
|
|
|
2956
|
-
|
|
2940
|
+
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
2941
|
+
|
|
2942
|
+
fn distribution(
|
|
2957
2943
|
battle: anytype,
|
|
2958
2944
|
comptime effect: Move.Effect,
|
|
2959
2945
|
player: Player,
|
|
@@ -2962,8 +2948,10 @@ pub const Rolls = struct {
|
|
|
2962
2948
|
const roll = if (effect == .Binding) .duration else .multi_hit;
|
|
2963
2949
|
const n: u3 = if (options.calc.overridden(player, roll)) |val|
|
|
2964
2950
|
@intCast(val)
|
|
2951
|
+
else if (@hasDecl(@TypeOf(battle.rng), "distribution"))
|
|
2952
|
+
battle.rng.distribution(player)
|
|
2965
2953
|
else if (showdown)
|
|
2966
|
-
DISTRIBUTION[battle.rng.range(
|
|
2954
|
+
DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
|
|
2967
2955
|
else n: {
|
|
2968
2956
|
const r = (battle.rng.next() & 3);
|
|
2969
2957
|
break :n @intCast((if (r < 2) r else battle.rng.next() & 3) + 2);
|
|
@@ -2994,7 +2982,9 @@ pub const Rolls = struct {
|
|
|
2994
2982
|
else
|
|
2995
2983
|
null;
|
|
2996
2984
|
|
|
2997
|
-
const slot: u4 = overridden orelse
|
|
2985
|
+
const slot: u4 = overridden orelse if (@hasDecl(@TypeOf(battle.rng), "moveSlot"))
|
|
2986
|
+
battle.rng.moveSlot(player, moves, check_pp)
|
|
2987
|
+
else slot: {
|
|
2998
2988
|
if (showdown) {
|
|
2999
2989
|
if (check_pp == 0) {
|
|
3000
2990
|
var i: usize = moves.len;
|
|
@@ -3010,10 +3000,9 @@ pub const Rolls = struct {
|
|
|
3010
3000
|
while (i < moves.len and r > 0) : (i += 1) {
|
|
3011
3001
|
if (moves[i].pp > 0) {
|
|
3012
3002
|
r -= 1;
|
|
3013
|
-
if (r == 0) break;
|
|
3003
|
+
if (r == 0) break :slot @intCast(i + 1);
|
|
3014
3004
|
}
|
|
3015
3005
|
}
|
|
3016
|
-
break :slot @intCast(i + 1);
|
|
3017
3006
|
}
|
|
3018
3007
|
}
|
|
3019
3008
|
|
|
@@ -3075,7 +3064,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3075
3064
|
const side = battle.side(player);
|
|
3076
3065
|
const foe = battle.foe(player);
|
|
3077
3066
|
|
|
3078
|
-
|
|
3067
|
+
const active = &side.active;
|
|
3079
3068
|
const stored = side.stored();
|
|
3080
3069
|
|
|
3081
3070
|
// While players are not given any input options on the cartridge in these cases,
|
|
@@ -3129,7 +3118,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3129
3118
|
if (m.id == if (active.volatiles.Bide) .Bide else side.last_selected_move) {
|
|
3130
3119
|
// Pokémon Showdown displays Struggle if limited to Bide but unable to pick
|
|
3131
3120
|
const struggle =
|
|
3132
|
-
m.id == .Bide and (m.pp == 0 or active.volatiles.
|
|
3121
|
+
m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
|
|
3133
3122
|
const s = if (struggle) 0 else slot;
|
|
3134
3123
|
out[n] = .{ .type = .Move, .data = s };
|
|
3135
3124
|
n += 1;
|
|
@@ -3144,7 +3133,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3144
3133
|
const m = active.moves[slot - 1];
|
|
3145
3134
|
if (m.id == .None) break;
|
|
3146
3135
|
if (m.pp == 0) continue;
|
|
3147
|
-
if (active.volatiles.
|
|
3136
|
+
if (active.volatiles.disable_move == slot) continue;
|
|
3148
3137
|
out[n] = .{ .type = .Move, .data = slot };
|
|
3149
3138
|
n += 1;
|
|
3150
3139
|
}
|