@pkmn/engine 0.1.0-dev.954f650d → 0.1.0-dev.95e81bfe
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/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 +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 +57 -38
- 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 +80 -70
- package/package.json +43 -23
- package/src/bin/install-pkmn-engine +14 -14
- package/src/lib/common/data.zig +9 -9
- package/src/lib/common/optional.zig +9 -2
- package/src/lib/common/protocol.zig +734 -318
- package/src/lib/common/rational.zig +12 -8
- package/src/lib/common/rng.zig +25 -16
- package/src/lib/gen1/README.md +28 -28
- package/src/lib/gen1/calc.zig +9 -9
- package/src/lib/gen1/chance.zig +72 -48
- 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 +7 -7
- package/src/lib/gen1/data.zig +41 -41
- package/src/lib/gen1/helpers.zig +33 -48
- package/src/lib/gen1/mechanics.zig +340 -355
- 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 +69 -36
- package/src/lib/gen2/data/species.zig +1 -1
- package/src/lib/gen2/data/types.zig +134 -31
- package/src/lib/gen2/data.zig +52 -44
- package/src/lib/gen2/generated.zig +806 -0
- package/src/lib/gen2/mechanics.zig +2935 -244
- package/src/lib/{binding/node.zig → node.zig} +10 -13
- 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,34 +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
|
-
// 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);
|
|
82
73
|
|
|
83
|
-
|
|
84
|
-
|
|
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;
|
|
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;
|
|
@@ -99,11 +90,11 @@ fn start(battle: anytype, options: anytype) !Result {
|
|
|
99
90
|
const p1 = battle.side(.P1);
|
|
100
91
|
const p2 = battle.side(.P2);
|
|
101
92
|
|
|
102
|
-
|
|
93
|
+
const p1_slot = findFirstAlive(p1);
|
|
103
94
|
assert(!showdown or p1_slot == 1);
|
|
104
95
|
if (p1_slot == 0) return if (findFirstAlive(p2) == 0) Result.Tie else Result.Lose;
|
|
105
96
|
|
|
106
|
-
|
|
97
|
+
const p2_slot = findFirstAlive(p2);
|
|
107
98
|
assert(!showdown or p2_slot == 1);
|
|
108
99
|
if (p2_slot == 0) return Result.Win;
|
|
109
100
|
|
|
@@ -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
|
|
|
@@ -653,8 +635,6 @@ fn beforeMove(
|
|
|
653
635
|
volatiles.state +%= battle.last_damage;
|
|
654
636
|
}
|
|
655
637
|
|
|
656
|
-
assert(volatiles.attacks > 0);
|
|
657
|
-
|
|
658
638
|
if (options.calc.modify(player, .attacking)) |extend| {
|
|
659
639
|
if (!extend) volatiles.attacks = 0;
|
|
660
640
|
} else {
|
|
@@ -663,18 +643,18 @@ fn beforeMove(
|
|
|
663
643
|
try options.chance.attacking(player, volatiles.attacks);
|
|
664
644
|
|
|
665
645
|
if (volatiles.attacks != 0) {
|
|
666
|
-
try log.activate(ident, .Bide);
|
|
646
|
+
try log.activate(.{ ident, .Bide });
|
|
667
647
|
return .done;
|
|
668
648
|
}
|
|
669
649
|
|
|
670
650
|
volatiles.Bide = false;
|
|
671
|
-
try log.end(ident, .Bide);
|
|
651
|
+
try log.end(.{ ident, .Bide });
|
|
672
652
|
|
|
673
653
|
battle.last_damage = volatiles.state *% 2;
|
|
674
654
|
volatiles.state = 0;
|
|
675
655
|
|
|
676
656
|
if (battle.last_damage == 0) {
|
|
677
|
-
try log.fail(ident, .None);
|
|
657
|
+
try log.fail(.{ ident, .None });
|
|
678
658
|
return .done;
|
|
679
659
|
}
|
|
680
660
|
|
|
@@ -694,9 +674,6 @@ fn beforeMove(
|
|
|
694
674
|
}
|
|
695
675
|
|
|
696
676
|
if (volatiles.Thrashing) {
|
|
697
|
-
assert(volatiles.attacks > 0);
|
|
698
|
-
assert(from != null);
|
|
699
|
-
|
|
700
677
|
if (options.calc.modify(player, .attacking)) |extend| {
|
|
701
678
|
if (!extend) volatiles.attacks = 0;
|
|
702
679
|
} else {
|
|
@@ -705,23 +682,21 @@ fn beforeMove(
|
|
|
705
682
|
try options.chance.attacking(player, volatiles.attacks);
|
|
706
683
|
|
|
707
684
|
if (!showdown and handleThrashing(battle, active, player, options)) {
|
|
708
|
-
try log.start(battle.active(player), .ConfusionSilent);
|
|
685
|
+
try log.start(.{ battle.active(player), .ConfusionSilent });
|
|
709
686
|
}
|
|
710
|
-
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()) });
|
|
711
688
|
if (showdown) {
|
|
712
689
|
// This shouldn't actually set last_used_move, but Pokémon Showdown sets last
|
|
713
690
|
// used in useMove and doesn't have the notion of skipping canMove semantics
|
|
714
691
|
side.last_used_move = side.last_selected_move;
|
|
715
692
|
if (handleThrashing(battle, active, player, options)) {
|
|
716
|
-
try log.start(battle.active(player), .ConfusionSilent);
|
|
693
|
+
try log.start(.{ battle.active(player), .ConfusionSilent });
|
|
717
694
|
}
|
|
718
695
|
}
|
|
719
696
|
return .skip_can;
|
|
720
697
|
}
|
|
721
698
|
|
|
722
699
|
if (volatiles.Binding) {
|
|
723
|
-
assert(volatiles.attacks > 0);
|
|
724
|
-
|
|
725
700
|
if (options.calc.modify(player, .binding)) |extend| {
|
|
726
701
|
if (!extend) volatiles.attacks = 0;
|
|
727
702
|
} else {
|
|
@@ -729,12 +704,7 @@ fn beforeMove(
|
|
|
729
704
|
}
|
|
730
705
|
try options.chance.binding(player, volatiles.attacks);
|
|
731
706
|
|
|
732
|
-
try log.move(
|
|
733
|
-
battle.active(player),
|
|
734
|
-
side.last_selected_move,
|
|
735
|
-
battle.active(player.foe()),
|
|
736
|
-
side.last_selected_move,
|
|
737
|
-
);
|
|
707
|
+
try log.move(.{ ident, side.last_selected_move, battle.active(player.foe()) });
|
|
738
708
|
if (showdown or battle.last_damage != 0) {
|
|
739
709
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
740
710
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -754,7 +724,7 @@ fn canMove(
|
|
|
754
724
|
mslot: u4,
|
|
755
725
|
auto: bool,
|
|
756
726
|
skip_pp: bool,
|
|
757
|
-
from:
|
|
727
|
+
from: Move,
|
|
758
728
|
residual: *bool,
|
|
759
729
|
options: anytype,
|
|
760
730
|
) !bool {
|
|
@@ -762,33 +732,21 @@ fn canMove(
|
|
|
762
732
|
const player_ident = battle.active(player);
|
|
763
733
|
const move = Move.get(side.last_selected_move);
|
|
764
734
|
|
|
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
|
-
var skip = skip_pp;
|
|
770
735
|
if (side.active.volatiles.Charging) {
|
|
771
736
|
side.active.volatiles.Charging = false;
|
|
772
737
|
side.active.volatiles.Invulnerable = false;
|
|
773
738
|
} else if (move.effect == .Charge) {
|
|
774
|
-
try options.log.move(player_ident, side.last_selected_move,
|
|
739
|
+
try options.log.move(.{ player_ident, side.last_selected_move, ID{}, from });
|
|
775
740
|
setCounterable(battle, player, side, move);
|
|
776
741
|
try Effects.charge(battle, player, options);
|
|
777
742
|
return false;
|
|
778
743
|
}
|
|
779
744
|
|
|
780
|
-
|
|
781
|
-
if (!
|
|
745
|
+
side.last_used_move = side.last_selected_move;
|
|
746
|
+
if (!skip_pp) decrementPP(side, mslot, auto);
|
|
782
747
|
|
|
783
748
|
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);
|
|
749
|
+
try options.log.move(.{ player_ident, side.last_selected_move, battle.active(target), from });
|
|
792
750
|
setCounterable(battle, player, side, move);
|
|
793
751
|
|
|
794
752
|
if (move.effect.onBegin()) {
|
|
@@ -826,12 +784,14 @@ fn decrementPP(side: *Side, mslot: u4, auto: bool) void {
|
|
|
826
784
|
assert(!volatiles.Rage and !volatiles.Thrashing and !volatiles.MultiHit);
|
|
827
785
|
if (volatiles.Bide) return;
|
|
828
786
|
|
|
829
|
-
|
|
830
|
-
|
|
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;
|
|
831
790
|
if (volatiles.Transform) return;
|
|
832
791
|
|
|
833
|
-
|
|
834
|
-
|
|
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;
|
|
835
795
|
assert(active.move(mslot).pp == side.stored().move(mslot).pp);
|
|
836
796
|
}
|
|
837
797
|
|
|
@@ -935,7 +895,7 @@ fn doMove(
|
|
|
935
895
|
}
|
|
936
896
|
}
|
|
937
897
|
|
|
938
|
-
|
|
898
|
+
const zero = battle.last_damage == 0;
|
|
939
899
|
miss = if (showdown or skip)
|
|
940
900
|
miss
|
|
941
901
|
else
|
|
@@ -963,16 +923,16 @@ fn doMove(
|
|
|
963
923
|
ohko = (!showdown or (!immune and !invulnerable)) and
|
|
964
924
|
move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe;
|
|
965
925
|
if (ohko) {
|
|
966
|
-
try log.immune(foe_ident, .OHKO);
|
|
926
|
+
try log.immune(.{ foe_ident, .OHKO });
|
|
967
927
|
} else if (immune and !invulnerable and (!showdown or move.effect != .Binding)) {
|
|
968
|
-
try log.immune(foe_ident, .None);
|
|
928
|
+
try log.immune(.{ foe_ident, .None });
|
|
969
929
|
} else if (mist) {
|
|
970
|
-
if (!foe.active.volatiles.Substitute) try log.activate(foe_ident, .Mist);
|
|
971
|
-
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 });
|
|
972
932
|
} else {
|
|
973
933
|
if (showdown or !zero) try options.chance.commit(player, .miss);
|
|
974
|
-
try log.lastmiss();
|
|
975
|
-
try log.miss(battle.active(player));
|
|
934
|
+
try log.lastmiss(.{});
|
|
935
|
+
try log.miss(.{battle.active(player)});
|
|
976
936
|
}
|
|
977
937
|
if (move.effect == .JumpKick) {
|
|
978
938
|
// Recoil is supposed to be damage/8 but damage will always be 0 here
|
|
@@ -1008,15 +968,15 @@ fn doMove(
|
|
|
1008
968
|
var hit: u4 = 0;
|
|
1009
969
|
while (hit < hits) {
|
|
1010
970
|
if (hit == 0) {
|
|
1011
|
-
if (crit) try log.crit(battle.active(player.foe()));
|
|
971
|
+
if (crit) try log.crit(.{battle.active(player.foe())});
|
|
1012
972
|
if (effectiveness > Effectiveness.neutral) {
|
|
1013
|
-
try log.supereffective(battle.active(player.foe()));
|
|
973
|
+
try log.supereffective(.{battle.active(player.foe())});
|
|
1014
974
|
} else if (effectiveness < Effectiveness.neutral) {
|
|
1015
|
-
try log.resisted(battle.active(player.foe()));
|
|
975
|
+
try log.resisted(.{battle.active(player.foe())});
|
|
1016
976
|
}
|
|
1017
977
|
}
|
|
1018
978
|
if (!skip) nullified = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
1019
|
-
if (hit == 0 and ohko) try log.ohko();
|
|
979
|
+
if (hit == 0 and ohko) try log.ohko(.{});
|
|
1020
980
|
hit += 1;
|
|
1021
981
|
if (foe.stored().hp == 0) break;
|
|
1022
982
|
if (!late and (!sub or move.effect == .Explode)) {
|
|
@@ -1033,7 +993,7 @@ fn doMove(
|
|
|
1033
993
|
if (showdown and move.effect == .Twineedle and !nullified and foe.stored().hp > 0) {
|
|
1034
994
|
try Effects.poison(battle, player, Move.get(.PoisonSting), options);
|
|
1035
995
|
}
|
|
1036
|
-
try log.hitcount(battle.active(player.foe()), hit);
|
|
996
|
+
try log.hitcount(.{ battle.active(player.foe()), hit });
|
|
1037
997
|
} else if (showdown) {
|
|
1038
998
|
// This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
|
|
1039
999
|
if (move.effect == .Binding) {
|
|
@@ -1043,9 +1003,9 @@ fn doMove(
|
|
|
1043
1003
|
assert(foe.stored().hp > 0);
|
|
1044
1004
|
// Pokémon Showdown logs |-damage| here instead of |-immune| because logic...
|
|
1045
1005
|
if (sub) {
|
|
1046
|
-
try log.activate(battle.active(player.foe()), .Substitute);
|
|
1006
|
+
try log.activate(.{ battle.active(player.foe()), .Substitute });
|
|
1047
1007
|
} else {
|
|
1048
|
-
try log.damage(battle.active(player.foe()), foe.stored(), .None);
|
|
1008
|
+
try log.damage(.{ battle.active(player.foe()), foe.stored(), .None });
|
|
1049
1009
|
try buildRage(battle, player.foe(), options);
|
|
1050
1010
|
}
|
|
1051
1011
|
return null;
|
|
@@ -1093,7 +1053,7 @@ fn checkCriticalHit(battle: anytype, player: Player, move: Move.Data, options: a
|
|
|
1093
1053
|
const side = battle.side(player);
|
|
1094
1054
|
|
|
1095
1055
|
// Base speed is used for the critical hit calculation, even when Transform-ed
|
|
1096
|
-
var rate =
|
|
1056
|
+
var rate: u16 = Species.chance(side.stored().species);
|
|
1097
1057
|
// GLITCH: Focus Energy reduces critical hit rate instead of increasing it
|
|
1098
1058
|
rate = if (side.active.volatiles.FocusEnergy) rate / 2 else @min(rate * 2, 255);
|
|
1099
1059
|
rate = if (move.effect == .HighCritical) @min(rate * 4, 255) else rate / 2;
|
|
@@ -1150,9 +1110,9 @@ fn calcDamage(
|
|
|
1150
1110
|
def = @max((def / 4) & 255, if (showdown) 1 else 0);
|
|
1151
1111
|
}
|
|
1152
1112
|
|
|
1153
|
-
const lvl =
|
|
1113
|
+
const lvl: u32 = side.stored().level * @as(u2, if (crit) 2 else 1);
|
|
1154
1114
|
|
|
1155
|
-
def =
|
|
1115
|
+
def = if (move.effect == .Explode) @max(def / 2, 1) else def;
|
|
1156
1116
|
|
|
1157
1117
|
if (def == 0) return false;
|
|
1158
1118
|
|
|
@@ -1243,7 +1203,7 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1243
1203
|
const foe = battle.foe(player);
|
|
1244
1204
|
|
|
1245
1205
|
if (battle.last_damage == 0) {
|
|
1246
|
-
try options.log.fail(battle.active(player), .None);
|
|
1206
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1247
1207
|
return null;
|
|
1248
1208
|
}
|
|
1249
1209
|
|
|
@@ -1261,14 +1221,14 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1261
1221
|
foe_last_selected_move.type == .Fighting);
|
|
1262
1222
|
|
|
1263
1223
|
if (!used and !selected) {
|
|
1264
|
-
try options.log.fail(battle.active(player), .None);
|
|
1224
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1265
1225
|
return null;
|
|
1266
1226
|
}
|
|
1267
1227
|
|
|
1268
1228
|
if (!used or !selected) {
|
|
1269
1229
|
// GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
|
|
1270
1230
|
if (!showdown) return Result.Error;
|
|
1271
|
-
try options.log.fail(battle.active(player), .None);
|
|
1231
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1272
1232
|
return null;
|
|
1273
1233
|
}
|
|
1274
1234
|
|
|
@@ -1304,20 +1264,20 @@ fn applyDamage(
|
|
|
1304
1264
|
subbed.active.volatiles.substitute = 0;
|
|
1305
1265
|
subbed.active.volatiles.Substitute = false;
|
|
1306
1266
|
// battle.last_damage is not updated with the amount of HP the Substitute had
|
|
1307
|
-
try options.log.end(battle.active(sub_player), .Substitute);
|
|
1267
|
+
try options.log.end(.{ battle.active(sub_player), .Substitute });
|
|
1308
1268
|
options.calc.capped(target_player);
|
|
1309
1269
|
return true;
|
|
1310
1270
|
} else {
|
|
1311
1271
|
// Safe to truncate since less than subbed.volatiles.substitute which is a u8
|
|
1312
1272
|
subbed.active.volatiles.substitute -= @intCast(battle.last_damage);
|
|
1313
|
-
try options.log.activate(battle.active(sub_player), .Substitute);
|
|
1273
|
+
try options.log.activate(.{ battle.active(sub_player), .Substitute });
|
|
1314
1274
|
return false;
|
|
1315
1275
|
}
|
|
1316
1276
|
}
|
|
1317
1277
|
|
|
1318
1278
|
if (battle.last_damage > target.stored().hp) battle.last_damage = target.stored().hp;
|
|
1319
1279
|
target.stored().hp -= battle.last_damage;
|
|
1320
|
-
try options.log.damage(battle.active(target_player), target.stored(), reason);
|
|
1280
|
+
try options.log.damage(.{ battle.active(target_player), target.stored(), reason });
|
|
1321
1281
|
return false;
|
|
1322
1282
|
}
|
|
1323
1283
|
|
|
@@ -1336,7 +1296,7 @@ fn mirrorMove(
|
|
|
1336
1296
|
side.last_selected_move = foe.last_used_move;
|
|
1337
1297
|
|
|
1338
1298
|
if (foe.last_used_move == .None or foe.last_used_move == .MirrorMove) {
|
|
1339
|
-
try options.log.fail(battle.active(player), .None);
|
|
1299
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1340
1300
|
return null;
|
|
1341
1301
|
} else if (!showdown or foe.last_used_move != .Struggle) {
|
|
1342
1302
|
incrementPP(side, mslot);
|
|
@@ -1378,12 +1338,12 @@ fn checkHit(battle: anytype, player: Player, move: Move.Data, options: anytype)
|
|
|
1378
1338
|
if (mist) {
|
|
1379
1339
|
assert(!showdown);
|
|
1380
1340
|
const foe_ident = battle.active(player.foe());
|
|
1381
|
-
try options.log.activate(foe_ident, .Mist);
|
|
1382
|
-
try options.log.fail(foe_ident, .None);
|
|
1341
|
+
try options.log.activate(.{ foe_ident, .Mist });
|
|
1342
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1383
1343
|
} else {
|
|
1384
1344
|
try options.chance.commit(player, .miss);
|
|
1385
|
-
try options.log.lastmiss();
|
|
1386
|
-
try options.log.miss(battle.active(player));
|
|
1345
|
+
try options.log.lastmiss(.{});
|
|
1346
|
+
try options.log.miss(.{battle.active(player)});
|
|
1387
1347
|
}
|
|
1388
1348
|
|
|
1389
1349
|
return false;
|
|
@@ -1435,7 +1395,7 @@ fn moveHit(
|
|
|
1435
1395
|
const overwritten = overwrite and state > 0;
|
|
1436
1396
|
assert(!overwritten or (0 < state and state <= 255 and !side.active.volatiles.Bide));
|
|
1437
1397
|
|
|
1438
|
-
var accuracy = if (overwritten) state else
|
|
1398
|
+
var accuracy: u16 = if (overwritten) state else move.accuracy;
|
|
1439
1399
|
var boost = BOOSTS[@as(u4, @intCast(@as(i8, side.active.boosts.accuracy) + 6))];
|
|
1440
1400
|
accuracy = accuracy * boost[0] / boost[1];
|
|
1441
1401
|
boost = BOOSTS[@as(u4, @intCast(@as(i8, -foe.active.boosts.evasion) + 6))];
|
|
@@ -1474,9 +1434,8 @@ fn checkFaint(
|
|
|
1474
1434
|
const foe_fainted = foe.stored().hp == 0;
|
|
1475
1435
|
|
|
1476
1436
|
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;
|
|
1437
|
+
const foe_out = foe_fainted and findFirstAlive(foe) == 0;
|
|
1438
|
+
const more = player_out or foe_out;
|
|
1480
1439
|
|
|
1481
1440
|
if (try faint(battle, player, !(more or foe_fainted), options)) |r| return r;
|
|
1482
1441
|
if (foe_fainted) if (try faint(battle, player.foe(), !more, options)) |r| return r;
|
|
@@ -1484,20 +1443,20 @@ fn checkFaint(
|
|
|
1484
1443
|
assert(!side.active.volatiles.MultiHit);
|
|
1485
1444
|
assert(!foe.active.volatiles.MultiHit);
|
|
1486
1445
|
|
|
1487
|
-
if (
|
|
1488
|
-
try options.log.tie();
|
|
1446
|
+
if (player_out and foe_out) {
|
|
1447
|
+
try options.log.tie(.{});
|
|
1489
1448
|
return Result.Tie;
|
|
1490
1449
|
} else if (player_out) {
|
|
1491
|
-
try options.log.win(player.foe());
|
|
1450
|
+
try options.log.win(.{player.foe()});
|
|
1492
1451
|
return if (player == .P1) Result.Lose else Result.Win;
|
|
1493
1452
|
} else if (foe_out) {
|
|
1494
|
-
try options.log.win(player);
|
|
1453
|
+
try options.log.win(.{player});
|
|
1495
1454
|
return if (player == .P1) Result.Win else Result.Lose;
|
|
1496
1455
|
}
|
|
1497
1456
|
|
|
1498
1457
|
const foe_choice: Choice.Type = if (foe_fainted) .Switch else .Pass;
|
|
1499
|
-
if (player == .P1) return
|
|
1500
|
-
return
|
|
1458
|
+
if (player == .P1) return .{ .p1 = .Switch, .p2 = foe_choice };
|
|
1459
|
+
return .{ .p1 = foe_choice, .p2 = .Switch };
|
|
1501
1460
|
}
|
|
1502
1461
|
|
|
1503
1462
|
fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result {
|
|
@@ -1533,7 +1492,7 @@ fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result
|
|
|
1533
1492
|
side.stored().stats.spe;
|
|
1534
1493
|
}
|
|
1535
1494
|
|
|
1536
|
-
try options.log.faint(battle.active(player), done);
|
|
1495
|
+
try options.log.faint(.{ battle.active(player), done });
|
|
1537
1496
|
options.calc.capped(player);
|
|
1538
1497
|
return null;
|
|
1539
1498
|
}
|
|
@@ -1555,8 +1514,8 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1555
1514
|
|
|
1556
1515
|
if (!showdown or damage > 0) {
|
|
1557
1516
|
stored.hp -= damage;
|
|
1558
|
-
// Pokémon Showdown uses
|
|
1559
|
-
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 });
|
|
1560
1519
|
}
|
|
1561
1520
|
}
|
|
1562
1521
|
|
|
@@ -1581,14 +1540,14 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1581
1540
|
const amount = @min(damage, stored.hp);
|
|
1582
1541
|
stored.hp -= amount;
|
|
1583
1542
|
|
|
1584
|
-
// As above, Pokémon Showdown uses
|
|
1585
|
-
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 });
|
|
1586
1545
|
|
|
1587
1546
|
const before = foe_stored.hp;
|
|
1588
1547
|
// Uncapped damage is added back to the foe
|
|
1589
1548
|
foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
|
|
1590
1549
|
// Pokémon Showdown uses the less specific heal here instead of drain... because reasons?
|
|
1591
|
-
if (foe_stored.hp > before) try options.log.heal(foe_ident, foe_stored, .Silent);
|
|
1550
|
+
if (foe_stored.hp > before) try options.log.heal(.{ foe_ident, foe_stored, .Silent });
|
|
1592
1551
|
}
|
|
1593
1552
|
}
|
|
1594
1553
|
|
|
@@ -1599,18 +1558,18 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1599
1558
|
battle.turn += 1;
|
|
1600
1559
|
|
|
1601
1560
|
if (showdown and pkmn.options.ebc and checkEBC(battle)) {
|
|
1602
|
-
try options.log.tie();
|
|
1561
|
+
try options.log.tie(.{});
|
|
1603
1562
|
return Result.Tie;
|
|
1604
1563
|
}
|
|
1605
1564
|
|
|
1606
1565
|
if (showdown and battle.turn >= 1000) {
|
|
1607
|
-
try options.log.tie();
|
|
1566
|
+
try options.log.tie(.{});
|
|
1608
1567
|
return Result.Tie;
|
|
1609
1568
|
} else if (battle.turn >= 65535) {
|
|
1610
1569
|
return Result.Error;
|
|
1611
1570
|
}
|
|
1612
1571
|
|
|
1613
|
-
try options.log.turn(battle.turn);
|
|
1572
|
+
try options.log.turn(.{battle.turn});
|
|
1614
1573
|
|
|
1615
1574
|
return Result.Default;
|
|
1616
1575
|
}
|
|
@@ -1670,9 +1629,9 @@ fn checkEBC(battle: anytype) bool {
|
|
|
1670
1629
|
}
|
|
1671
1630
|
|
|
1672
1631
|
fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
1673
|
-
if (side.active.volatiles.
|
|
1632
|
+
if (side.active.volatiles.disable_move != 0) {
|
|
1674
1633
|
// A Pokémon that transforms after being disabled may end up with less move slots
|
|
1675
|
-
const m = side.active.moves[side.active.volatiles.
|
|
1634
|
+
const m = side.active.moves[side.active.volatiles.disable_move - 1].id;
|
|
1676
1635
|
// side.last_selected_move can be Struggle here on Pokemon Showdown we need an extra check
|
|
1677
1636
|
const last = if (showdown and m == .Bide and side.active.volatiles.Bide)
|
|
1678
1637
|
m
|
|
@@ -1680,14 +1639,14 @@ fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
|
1680
1639
|
side.last_selected_move;
|
|
1681
1640
|
if (m != .None and m == last) {
|
|
1682
1641
|
side.active.volatiles.Charging = false;
|
|
1683
|
-
try options.log.
|
|
1642
|
+
try options.log.cant(.{ ident, .Disable, last });
|
|
1684
1643
|
return true;
|
|
1685
1644
|
}
|
|
1686
1645
|
}
|
|
1687
1646
|
return false;
|
|
1688
1647
|
}
|
|
1689
1648
|
|
|
1690
|
-
|
|
1649
|
+
fn buildRage(battle: anytype, who: Player, options: anytype) !void {
|
|
1691
1650
|
const side = battle.side(who);
|
|
1692
1651
|
if (side.active.volatiles.Rage and side.active.boosts.atk < 6) {
|
|
1693
1652
|
try Effects.boost(battle, who, Move.get(.Rage), options);
|
|
@@ -1705,7 +1664,7 @@ fn handleThrashing(battle: anytype, active: *ActivePokemon, player: Player, opti
|
|
|
1705
1664
|
return true;
|
|
1706
1665
|
}
|
|
1707
1666
|
|
|
1708
|
-
|
|
1667
|
+
fn onBegin(
|
|
1709
1668
|
battle: anytype,
|
|
1710
1669
|
player: Player,
|
|
1711
1670
|
move: Move.Data,
|
|
@@ -1713,7 +1672,6 @@ inline fn onBegin(
|
|
|
1713
1672
|
residual: *bool,
|
|
1714
1673
|
options: anytype,
|
|
1715
1674
|
) !void {
|
|
1716
|
-
assert(move.effect.onBegin());
|
|
1717
1675
|
return switch (move.effect) {
|
|
1718
1676
|
.Confusion => Effects.confusion(battle, player, move, options),
|
|
1719
1677
|
.Conversion => Effects.conversion(battle, player, options),
|
|
@@ -1735,8 +1693,7 @@ inline fn onBegin(
|
|
|
1735
1693
|
};
|
|
1736
1694
|
}
|
|
1737
1695
|
|
|
1738
|
-
|
|
1739
|
-
assert(move.effect.onEnd());
|
|
1696
|
+
fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1740
1697
|
return switch (move.effect) {
|
|
1741
1698
|
.Bide => Effects.bide(battle, player, options),
|
|
1742
1699
|
// zig fmt: off
|
|
@@ -1751,14 +1708,13 @@ inline fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anyty
|
|
|
1751
1708
|
};
|
|
1752
1709
|
}
|
|
1753
1710
|
|
|
1754
|
-
|
|
1711
|
+
fn alwaysHappens(
|
|
1755
1712
|
battle: anytype,
|
|
1756
1713
|
player: Player,
|
|
1757
1714
|
move: Move.Data,
|
|
1758
1715
|
residual: *bool,
|
|
1759
1716
|
options: anytype,
|
|
1760
1717
|
) !void {
|
|
1761
|
-
assert(move.effect.alwaysHappens());
|
|
1762
1718
|
return switch (move.effect) {
|
|
1763
1719
|
.DrainHP, .DreamEater => Effects.drainHP(battle, player, options),
|
|
1764
1720
|
.Explode => Effects.explode(battle, player),
|
|
@@ -1770,7 +1726,7 @@ inline fn alwaysHappens(
|
|
|
1770
1726
|
};
|
|
1771
1727
|
}
|
|
1772
1728
|
|
|
1773
|
-
|
|
1729
|
+
fn moveEffect(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1774
1730
|
return switch (move.effect) {
|
|
1775
1731
|
.BurnChance1, .BurnChance2 => Effects.burnChance(battle, player, move, options),
|
|
1776
1732
|
.ConfusionChance => Effects.confusion(battle, player, move, options),
|
|
@@ -1797,7 +1753,7 @@ pub const Effects = struct {
|
|
|
1797
1753
|
side.active.volatiles.state = 0;
|
|
1798
1754
|
side.active.volatiles.attacks = Rolls.attackingDuration(battle, player, options);
|
|
1799
1755
|
|
|
1800
|
-
try options.log.start(battle.active(player), .Bide);
|
|
1756
|
+
try options.log.start(.{ battle.active(player), .Bide });
|
|
1801
1757
|
}
|
|
1802
1758
|
|
|
1803
1759
|
fn burnChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1811,11 +1767,11 @@ pub const Effects = struct {
|
|
|
1811
1767
|
// GLITCH: Freeze top move selection desync can occur if thawed Pokémon is slower
|
|
1812
1768
|
if (Status.is(foe_stored.status, .FRZ)) {
|
|
1813
1769
|
assert(move.type == .Fire);
|
|
1814
|
-
try options.log.curestatus(
|
|
1770
|
+
try options.log.curestatus(.{
|
|
1815
1771
|
battle.active(player.foe()),
|
|
1816
1772
|
foe_stored.status,
|
|
1817
1773
|
.Message,
|
|
1818
|
-
);
|
|
1774
|
+
});
|
|
1819
1775
|
foe_stored.status = 0;
|
|
1820
1776
|
}
|
|
1821
1777
|
return;
|
|
@@ -1829,7 +1785,7 @@ pub const Effects = struct {
|
|
|
1829
1785
|
foe_stored.status = Status.init(.BRN);
|
|
1830
1786
|
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
1831
1787
|
|
|
1832
|
-
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 });
|
|
1833
1789
|
}
|
|
1834
1790
|
|
|
1835
1791
|
fn charge(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -1839,8 +1795,8 @@ pub const Effects = struct {
|
|
|
1839
1795
|
volatiles.Charging = true;
|
|
1840
1796
|
const move = side.last_selected_move;
|
|
1841
1797
|
if (move == .Fly or move == .Dig) volatiles.Invulnerable = true;
|
|
1842
|
-
try options.log.laststill();
|
|
1843
|
-
try options.log.prepare(battle.active(player), move);
|
|
1798
|
+
try options.log.laststill(.{});
|
|
1799
|
+
try options.log.prepare(.{ battle.active(player), move });
|
|
1844
1800
|
}
|
|
1845
1801
|
|
|
1846
1802
|
fn confusion(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1851,17 +1807,11 @@ pub const Effects = struct {
|
|
|
1851
1807
|
if (!try Rolls.confusionChance(battle, player, options)) return;
|
|
1852
1808
|
} else {
|
|
1853
1809
|
if (showdown) {
|
|
1854
|
-
if (!try checkHit(battle, player, move, options))
|
|
1855
|
-
|
|
1856
|
-
} else if (sub) {
|
|
1857
|
-
return options.log.fail(battle.active(player.foe()), .None);
|
|
1858
|
-
}
|
|
1810
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1811
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1859
1812
|
} else {
|
|
1860
|
-
if (sub) {
|
|
1861
|
-
|
|
1862
|
-
} else if (!try checkHit(battle, player, move, options)) {
|
|
1863
|
-
return;
|
|
1864
|
-
}
|
|
1813
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1814
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1865
1815
|
}
|
|
1866
1816
|
try options.chance.commit(player, .hit);
|
|
1867
1817
|
}
|
|
@@ -1870,23 +1820,24 @@ pub const Effects = struct {
|
|
|
1870
1820
|
foe.active.volatiles.Confusion = true;
|
|
1871
1821
|
foe.active.volatiles.confusion = Rolls.confusionDuration(battle, player, false, options);
|
|
1872
1822
|
|
|
1873
|
-
try options.log.start(battle.active(player.foe()), .Confusion);
|
|
1823
|
+
try options.log.start(.{ battle.active(player.foe()), .Confusion });
|
|
1874
1824
|
}
|
|
1875
1825
|
|
|
1876
1826
|
fn conversion(battle: anytype, player: Player, options: anytype) !void {
|
|
1877
1827
|
const foe = battle.foe(player);
|
|
1878
1828
|
|
|
1879
1829
|
if (foe.active.volatiles.Invulnerable) {
|
|
1880
|
-
try options.log.lastmiss();
|
|
1881
|
-
return options.log.miss(battle.active(player));
|
|
1830
|
+
try options.log.lastmiss(.{});
|
|
1831
|
+
return options.log.miss(.{battle.active(player)});
|
|
1882
1832
|
}
|
|
1883
1833
|
|
|
1884
1834
|
battle.side(player).active.types = foe.active.types;
|
|
1885
|
-
return options.log.
|
|
1835
|
+
return options.log.start(.{
|
|
1886
1836
|
battle.active(player),
|
|
1837
|
+
.TypeChange,
|
|
1887
1838
|
foe.active.types,
|
|
1888
1839
|
battle.active(player.foe()),
|
|
1889
|
-
);
|
|
1840
|
+
});
|
|
1890
1841
|
}
|
|
1891
1842
|
|
|
1892
1843
|
fn disable(battle: anytype, player: Player, move: Move.Data, options: anytype) !?Result {
|
|
@@ -1900,8 +1851,8 @@ pub const Effects = struct {
|
|
|
1900
1851
|
try options.chance.commit(player, .hit);
|
|
1901
1852
|
}
|
|
1902
1853
|
|
|
1903
|
-
if (volatiles.
|
|
1904
|
-
try options.log.fail(foe_ident, .None);
|
|
1854
|
+
if (volatiles.disable_move != 0) {
|
|
1855
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1905
1856
|
return null;
|
|
1906
1857
|
}
|
|
1907
1858
|
|
|
@@ -1917,7 +1868,7 @@ pub const Effects = struct {
|
|
|
1917
1868
|
// Technically this is still considered simply a "miss" on the cartridge,
|
|
1918
1869
|
// but diverging from Pokémon Showdown here would mostly just be pedantic
|
|
1919
1870
|
if (n == 0) {
|
|
1920
|
-
try options.log.fail(foe_ident, .None);
|
|
1871
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1921
1872
|
return null;
|
|
1922
1873
|
} else if (err) {
|
|
1923
1874
|
// GLITCH: Transform + Mirror Move / Metronome PP softlock
|
|
@@ -1925,18 +1876,18 @@ pub const Effects = struct {
|
|
|
1925
1876
|
return Result.Error;
|
|
1926
1877
|
}
|
|
1927
1878
|
|
|
1928
|
-
volatiles.
|
|
1879
|
+
volatiles.disable_move =
|
|
1929
1880
|
@intCast(try Rolls.moveSlot(battle, player, &foe.active.moves, n, options));
|
|
1930
|
-
volatiles.
|
|
1881
|
+
volatiles.disable_duration = Rolls.disableDuration(battle, player, options);
|
|
1931
1882
|
|
|
1932
|
-
const id = foe.active.move(volatiles.
|
|
1933
|
-
try options.log.
|
|
1883
|
+
const id = foe.active.move(volatiles.disable_move).id;
|
|
1884
|
+
try options.log.start(.{ foe_ident, .Disable, id });
|
|
1934
1885
|
return null;
|
|
1935
1886
|
}
|
|
1936
1887
|
|
|
1937
1888
|
fn drainHP(battle: anytype, player: Player, options: anytype) !void {
|
|
1938
|
-
var
|
|
1939
|
-
|
|
1889
|
+
var stored = battle.side(player).stored();
|
|
1890
|
+
const ident = battle.active(player);
|
|
1940
1891
|
|
|
1941
1892
|
if (battle.last_damage == 0) {
|
|
1942
1893
|
assert(showdown);
|
|
@@ -1949,7 +1900,7 @@ pub const Effects = struct {
|
|
|
1949
1900
|
if (stored.hp == stored.stats.hp) return;
|
|
1950
1901
|
stored.hp = @min(stored.stats.hp, stored.hp + drain);
|
|
1951
1902
|
|
|
1952
|
-
try options.log.
|
|
1903
|
+
try options.log.heal(.{ ident, stored, .Drain, battle.active(player.foe()) });
|
|
1953
1904
|
}
|
|
1954
1905
|
|
|
1955
1906
|
fn explode(battle: anytype, player: Player) !void {
|
|
@@ -1981,7 +1932,7 @@ pub const Effects = struct {
|
|
|
1981
1932
|
if (side.active.volatiles.FocusEnergy) return;
|
|
1982
1933
|
side.active.volatiles.FocusEnergy = true;
|
|
1983
1934
|
|
|
1984
|
-
try options.log.start(battle.active(player), .FocusEnergy);
|
|
1935
|
+
try options.log.start(.{ battle.active(player), .FocusEnergy });
|
|
1985
1936
|
}
|
|
1986
1937
|
|
|
1987
1938
|
fn freezeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2003,7 +1954,7 @@ pub const Effects = struct {
|
|
|
2003
1954
|
foe_stored.status = Status.init(.FRZ);
|
|
2004
1955
|
// GLITCH: Hyper Beam recharging status is not cleared
|
|
2005
1956
|
|
|
2006
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
1957
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2007
1958
|
}
|
|
2008
1959
|
|
|
2009
1960
|
fn haze(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -2011,7 +1962,7 @@ pub const Effects = struct {
|
|
|
2011
1962
|
var side = battle.side(player);
|
|
2012
1963
|
var foe = battle.foe(player);
|
|
2013
1964
|
|
|
2014
|
-
|
|
1965
|
+
const side_stored = side.stored();
|
|
2015
1966
|
var foe_stored = foe.stored();
|
|
2016
1967
|
|
|
2017
1968
|
const player_ident = battle.active(player);
|
|
@@ -2023,8 +1974,8 @@ pub const Effects = struct {
|
|
|
2023
1974
|
side.active.stats = side_stored.stats;
|
|
2024
1975
|
foe.active.stats = foe_stored.stats;
|
|
2025
1976
|
|
|
2026
|
-
try log.activate(player_ident, .Haze);
|
|
2027
|
-
try log.clearallboost();
|
|
1977
|
+
try log.activate(.{ player_ident, .Haze });
|
|
1978
|
+
try log.clearallboost(.{});
|
|
2028
1979
|
|
|
2029
1980
|
// Pokémon Showdown clears P1 then P2 instead of status -> side -> foe
|
|
2030
1981
|
if (showdown) {
|
|
@@ -2032,11 +1983,11 @@ pub const Effects = struct {
|
|
|
2032
1983
|
const p: Player = @enumFromInt(i);
|
|
2033
1984
|
// Pokémon Showdown incorrectly does not prevent sleep/freeze from moving
|
|
2034
1985
|
if (p != player and Status.any(s.stored().status)) {
|
|
2035
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
1986
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2036
1987
|
s.stored().status = 0;
|
|
2037
1988
|
} else if (showdown and s.stored().status == Status.TOX) {
|
|
2038
1989
|
s.stored().status = Status.init(.PSN);
|
|
2039
|
-
try log.status(battle.active(p), s.stored().status, .None);
|
|
1990
|
+
try log.status(.{ battle.active(p), s.stored().status, .None });
|
|
2040
1991
|
}
|
|
2041
1992
|
try clearVolatiles(battle, p, p != player, options);
|
|
2042
1993
|
}
|
|
@@ -2045,7 +1996,7 @@ pub const Effects = struct {
|
|
|
2045
1996
|
if (Status.is(foe_stored.status, .FRZ) or Status.is(foe_stored.status, .SLP)) {
|
|
2046
1997
|
foe.last_selected_move = .SKIP_TURN;
|
|
2047
1998
|
}
|
|
2048
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
1999
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2049
2000
|
foe_stored.status = 0;
|
|
2050
2001
|
}
|
|
2051
2002
|
try clearVolatiles(battle, player, false, options);
|
|
@@ -2061,7 +2012,7 @@ pub const Effects = struct {
|
|
|
2061
2012
|
// GLITCH: HP recovery move failure glitches
|
|
2062
2013
|
const delta = stored.stats.hp - stored.hp;
|
|
2063
2014
|
if (delta == 0 or (delta & 255 == 255 and stored.hp % 256 != 0)) {
|
|
2064
|
-
return
|
|
2015
|
+
return options.log.fail(.{ ident, .None });
|
|
2065
2016
|
}
|
|
2066
2017
|
|
|
2067
2018
|
const rest = side.last_selected_move == .Rest;
|
|
@@ -2069,17 +2020,17 @@ pub const Effects = struct {
|
|
|
2069
2020
|
// Adding the sleep status runs the sleep condition handler to roll duration
|
|
2070
2021
|
if (showdown) battle.rng.advance(1);
|
|
2071
2022
|
stored.status = Status.slf(2);
|
|
2072
|
-
try options.log.
|
|
2023
|
+
try options.log.status(.{ ident, stored.status, .From, Move.Rest });
|
|
2073
2024
|
stored.hp = stored.stats.hp;
|
|
2074
2025
|
} else {
|
|
2075
2026
|
stored.hp = @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2));
|
|
2076
2027
|
}
|
|
2077
|
-
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 });
|
|
2078
2029
|
}
|
|
2079
2030
|
|
|
2080
2031
|
fn hyperBeam(battle: anytype, player: Player, options: anytype) !void {
|
|
2081
2032
|
battle.side(player).active.volatiles.Recharging = true;
|
|
2082
|
-
try options.log.mustrecharge(battle.active(player));
|
|
2033
|
+
try options.log.mustrecharge(.{battle.active(player)});
|
|
2083
2034
|
}
|
|
2084
2035
|
|
|
2085
2036
|
fn leechSeed(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2088,33 +2039,32 @@ pub const Effects = struct {
|
|
|
2088
2039
|
if (showdown) {
|
|
2089
2040
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2090
2041
|
if (!foe.active.volatiles.Invulnerable and foe.active.types.includes(.Grass)) {
|
|
2091
|
-
return options.log.immune(battle.active(player.foe()), .None);
|
|
2042
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
2092
2043
|
}
|
|
2093
2044
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2094
2045
|
if (foe.active.volatiles.LeechSeed) return;
|
|
2095
2046
|
} else {
|
|
2096
2047
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2097
2048
|
if (foe.active.types.includes(.Grass) or foe.active.volatiles.LeechSeed) {
|
|
2098
|
-
try options.log.lastmiss();
|
|
2099
|
-
return options.log.miss(battle.active(player));
|
|
2049
|
+
try options.log.lastmiss(.{});
|
|
2050
|
+
return options.log.miss(.{battle.active(player)});
|
|
2100
2051
|
}
|
|
2101
2052
|
}
|
|
2102
2053
|
|
|
2103
2054
|
try options.chance.commit(player, .hit);
|
|
2104
2055
|
foe.active.volatiles.LeechSeed = true;
|
|
2105
2056
|
|
|
2106
|
-
try options.log.start(battle.active(player.foe()), .LeechSeed);
|
|
2057
|
+
try options.log.start(.{ battle.active(player.foe()), .LeechSeed });
|
|
2107
2058
|
}
|
|
2108
2059
|
|
|
2109
2060
|
fn lightScreen(battle: anytype, player: Player, options: anytype) !void {
|
|
2110
2061
|
var side = battle.side(player);
|
|
2062
|
+
const ident = battle.active(player);
|
|
2111
2063
|
|
|
2112
|
-
if (side.active.volatiles.LightScreen) {
|
|
2113
|
-
return options.log.fail(battle.active(player), .None);
|
|
2114
|
-
}
|
|
2064
|
+
if (side.active.volatiles.LightScreen) return options.log.fail(.{ ident, .None });
|
|
2115
2065
|
side.active.volatiles.LightScreen = true;
|
|
2116
2066
|
|
|
2117
|
-
try options.log.start(
|
|
2067
|
+
try options.log.start(.{ ident, .LightScreen });
|
|
2118
2068
|
}
|
|
2119
2069
|
|
|
2120
2070
|
fn mimic(battle: anytype, player: Player, move: Move.Data, mslot: u8, options: anytype) !void {
|
|
@@ -2143,7 +2093,7 @@ pub const Effects = struct {
|
|
|
2143
2093
|
// Invulnerable foes or 1/256 miss can trigger |-miss| instead of |-fail|
|
|
2144
2094
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2145
2095
|
try options.chance.commit(player, .hit);
|
|
2146
|
-
return
|
|
2096
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2147
2097
|
}
|
|
2148
2098
|
}
|
|
2149
2099
|
if (!try checkHit(battle, player, move, options)) return;
|
|
@@ -2152,7 +2102,7 @@ pub const Effects = struct {
|
|
|
2152
2102
|
const rslot = try Rolls.moveSlot(battle, player, &foe.active.moves, 0, options);
|
|
2153
2103
|
side.active.move(oslot).id = foe.active.move(rslot).id;
|
|
2154
2104
|
|
|
2155
|
-
try options.log.
|
|
2105
|
+
try options.log.start(.{ battle.active(player), .Mimic, side.active.move(oslot).id });
|
|
2156
2106
|
}
|
|
2157
2107
|
|
|
2158
2108
|
fn mist(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -2161,7 +2111,7 @@ pub const Effects = struct {
|
|
|
2161
2111
|
if (side.active.volatiles.Mist) return;
|
|
2162
2112
|
side.active.volatiles.Mist = true;
|
|
2163
2113
|
|
|
2164
|
-
try options.log.start(battle.active(player), .Mist);
|
|
2114
|
+
try options.log.start(.{ battle.active(player), .Mist });
|
|
2165
2115
|
}
|
|
2166
2116
|
|
|
2167
2117
|
fn multiHit(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2187,17 +2137,18 @@ pub const Effects = struct {
|
|
|
2187
2137
|
|
|
2188
2138
|
if (showdown) {
|
|
2189
2139
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2190
|
-
if (immune and !foe.active.volatiles.Invulnerable)
|
|
2191
|
-
|
|
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;
|
|
2192
2143
|
}
|
|
2193
2144
|
if (Status.any(foe_stored.status)) {
|
|
2194
|
-
return log.fail(
|
|
2145
|
+
return log.fail(.{
|
|
2195
2146
|
foe_ident,
|
|
2196
|
-
if (Status.is(foe_stored.status, .PAR)) .Paralysis else .None,
|
|
2197
|
-
);
|
|
2147
|
+
if (Status.is(foe_stored.status, .PAR)) Fail.Paralysis else Fail.None,
|
|
2148
|
+
});
|
|
2198
2149
|
}
|
|
2199
2150
|
if (!showdown) {
|
|
2200
|
-
if (immune) return log.immune(foe_ident, .None);
|
|
2151
|
+
if (immune) return log.immune(.{ foe_ident, .None });
|
|
2201
2152
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2202
2153
|
}
|
|
2203
2154
|
try options.chance.commit(player, .hit);
|
|
@@ -2205,7 +2156,7 @@ pub const Effects = struct {
|
|
|
2205
2156
|
foe_stored.status = Status.init(.PAR);
|
|
2206
2157
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2207
2158
|
|
|
2208
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2159
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2209
2160
|
}
|
|
2210
2161
|
|
|
2211
2162
|
fn paralyzeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2227,12 +2178,12 @@ pub const Effects = struct {
|
|
|
2227
2178
|
foe_stored.status = Status.init(.PAR);
|
|
2228
2179
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2229
2180
|
|
|
2230
|
-
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 });
|
|
2231
2182
|
}
|
|
2232
2183
|
|
|
2233
2184
|
fn payDay(battle: anytype, player: Player, options: anytype) !void {
|
|
2234
2185
|
if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
|
|
2235
|
-
try options.log.fieldactivate();
|
|
2186
|
+
try options.log.fieldactivate(.{});
|
|
2236
2187
|
}
|
|
2237
2188
|
}
|
|
2238
2189
|
|
|
@@ -2247,23 +2198,23 @@ pub const Effects = struct {
|
|
|
2247
2198
|
return;
|
|
2248
2199
|
} else if (foe.active.volatiles.Substitute) {
|
|
2249
2200
|
if (move.effect != .Poison) return;
|
|
2250
|
-
return log.fail(foe_ident, .None);
|
|
2201
|
+
return log.fail(.{ foe_ident, .None });
|
|
2251
2202
|
} else if (Status.any(foe_stored.status)) {
|
|
2252
2203
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2253
2204
|
// Pokémon Showdown considers Toxic to be a status even in Generation I and so
|
|
2254
2205
|
// will not include a fail reason for Toxic vs. Poison or vice-versa...
|
|
2255
|
-
return log.fail(foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2206
|
+
return log.fail(.{ foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2256
2207
|
if (!showdown)
|
|
2257
|
-
.Poison
|
|
2208
|
+
Fail.Poison
|
|
2258
2209
|
else if (toxic == (foe_stored.status == Status.TOX))
|
|
2259
|
-
if (toxic) .Toxic else .Poison
|
|
2210
|
+
if (toxic) Fail.Toxic else Fail.Poison
|
|
2260
2211
|
else
|
|
2261
|
-
.None
|
|
2212
|
+
Fail.None
|
|
2262
2213
|
else
|
|
2263
|
-
.None);
|
|
2214
|
+
Fail.None });
|
|
2264
2215
|
} else if (foe.active.types.includes(.Poison)) {
|
|
2265
2216
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2266
|
-
return log.immune(foe_ident, .None);
|
|
2217
|
+
return log.immune(.{ foe_ident, .None });
|
|
2267
2218
|
}
|
|
2268
2219
|
|
|
2269
2220
|
if (move.effect == .Poison) {
|
|
@@ -2282,7 +2233,7 @@ pub const Effects = struct {
|
|
|
2282
2233
|
foe.active.volatiles.toxic = 0;
|
|
2283
2234
|
}
|
|
2284
2235
|
|
|
2285
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2236
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2286
2237
|
}
|
|
2287
2238
|
|
|
2288
2239
|
fn rage(battle: anytype, player: Player) !void {
|
|
@@ -2302,22 +2253,23 @@ pub const Effects = struct {
|
|
|
2302
2253
|
@as(u8, if (side.last_selected_move == .Struggle) 2 else 4), 1));
|
|
2303
2254
|
stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
|
|
2304
2255
|
|
|
2305
|
-
try options.log.
|
|
2256
|
+
try options.log.damage(.{
|
|
2306
2257
|
battle.active(player),
|
|
2307
2258
|
stored,
|
|
2308
2259
|
.RecoilOf,
|
|
2309
2260
|
battle.active(player.foe()),
|
|
2310
|
-
);
|
|
2261
|
+
});
|
|
2311
2262
|
if (showdown and stored.hp == 0) residual.* = false;
|
|
2312
2263
|
}
|
|
2313
2264
|
|
|
2314
2265
|
fn reflect(battle: anytype, player: Player, options: anytype) !void {
|
|
2315
2266
|
var side = battle.side(player);
|
|
2267
|
+
const ident = battle.active(player);
|
|
2316
2268
|
|
|
2317
|
-
if (side.active.volatiles.Reflect) return options.log.fail(
|
|
2269
|
+
if (side.active.volatiles.Reflect) return options.log.fail(.{ ident, .None });
|
|
2318
2270
|
side.active.volatiles.Reflect = true;
|
|
2319
2271
|
|
|
2320
|
-
try options.log.start(
|
|
2272
|
+
try options.log.start(.{ ident, .Reflect });
|
|
2321
2273
|
}
|
|
2322
2274
|
|
|
2323
2275
|
fn sleep(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2331,10 +2283,10 @@ pub const Effects = struct {
|
|
|
2331
2283
|
// the Sleep Clause Mod might activate, causing us to not actually bypass
|
|
2332
2284
|
} else {
|
|
2333
2285
|
if (Status.any(foe_stored.status)) {
|
|
2334
|
-
return options.log.fail(
|
|
2286
|
+
return options.log.fail(.{
|
|
2335
2287
|
foe_ident,
|
|
2336
|
-
if (Status.is(foe_stored.status, .SLP)) .Sleep else .None,
|
|
2337
|
-
);
|
|
2288
|
+
if (Status.is(foe_stored.status, .SLP)) Fail.Sleep else Fail.None,
|
|
2289
|
+
});
|
|
2338
2290
|
}
|
|
2339
2291
|
// If checkHit in doMove didn't return true Pokémon Showdown wouldn't be in here
|
|
2340
2292
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
@@ -2351,18 +2303,17 @@ pub const Effects = struct {
|
|
|
2351
2303
|
|
|
2352
2304
|
foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
|
|
2353
2305
|
const last = battle.side(player).last_selected_move;
|
|
2354
|
-
try options.log.
|
|
2306
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .From, last });
|
|
2355
2307
|
}
|
|
2356
2308
|
|
|
2357
2309
|
fn splash(battle: anytype, player: Player, options: anytype) !void {
|
|
2358
|
-
try options.log.activate(battle.active(player), .Splash);
|
|
2310
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
2359
2311
|
}
|
|
2360
2312
|
|
|
2361
2313
|
fn substitute(battle: anytype, player: Player, residual: *bool, options: anytype) !void {
|
|
2362
2314
|
var side = battle.side(player);
|
|
2363
2315
|
if (side.active.volatiles.Substitute) {
|
|
2364
|
-
|
|
2365
|
-
return;
|
|
2316
|
+
return options.log.fail(.{ battle.active(player), .Substitute });
|
|
2366
2317
|
}
|
|
2367
2318
|
|
|
2368
2319
|
assert(side.stored().stats.hp <= 1023);
|
|
@@ -2374,17 +2325,16 @@ pub const Effects = struct {
|
|
|
2374
2325
|
// avoid having to convert to floating point)
|
|
2375
2326
|
const required_hp = if (showdown) @divFloor(side.stored().stats.hp - 1, 4) + 1 else hp;
|
|
2376
2327
|
if (side.stored().hp < required_hp) {
|
|
2377
|
-
|
|
2378
|
-
return;
|
|
2328
|
+
return options.log.fail(.{ battle.active(player), .Weak });
|
|
2379
2329
|
}
|
|
2380
2330
|
|
|
2381
2331
|
// GLITCH: can leave the user with 0 HP (faints later) because didn't check '<=' above
|
|
2382
2332
|
side.stored().hp -= hp;
|
|
2383
2333
|
side.active.volatiles.substitute = hp + 1;
|
|
2384
2334
|
side.active.volatiles.Substitute = true;
|
|
2385
|
-
try options.log.start(battle.active(player), .Substitute);
|
|
2335
|
+
try options.log.start(.{ battle.active(player), .Substitute });
|
|
2386
2336
|
if (hp > 0) {
|
|
2387
|
-
try options.log.damage(battle.active(player), side.stored(), .None);
|
|
2337
|
+
try options.log.damage(.{ battle.active(player), side.stored(), .None });
|
|
2388
2338
|
if (showdown and side.stored().hp == 0) residual.* = false;
|
|
2389
2339
|
}
|
|
2390
2340
|
}
|
|
@@ -2398,8 +2348,8 @@ pub const Effects = struct {
|
|
|
2398
2348
|
}
|
|
2399
2349
|
battle.last_damage = 0;
|
|
2400
2350
|
} else {
|
|
2401
|
-
try options.log.fail(battle.active(player), .None);
|
|
2402
|
-
try options.log.laststill();
|
|
2351
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2352
|
+
try options.log.laststill(.{});
|
|
2403
2353
|
}
|
|
2404
2354
|
}
|
|
2405
2355
|
|
|
@@ -2438,7 +2388,7 @@ pub const Effects = struct {
|
|
|
2438
2388
|
side.active.moves[i].pp = if (m.id != .None) 5 else 0;
|
|
2439
2389
|
}
|
|
2440
2390
|
|
|
2441
|
-
try options.log.transform(battle.active(player), foe_ident);
|
|
2391
|
+
try options.log.transform(.{ battle.active(player), foe_ident });
|
|
2442
2392
|
}
|
|
2443
2393
|
|
|
2444
2394
|
fn binding(battle: anytype, player: Player, rewrap: bool, options: anytype) !void {
|
|
@@ -2468,87 +2418,87 @@ pub const Effects = struct {
|
|
|
2468
2418
|
switch (move.effect) {
|
|
2469
2419
|
.AttackUp1, .AttackUp2, .Rage => {
|
|
2470
2420
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2471
|
-
if (boosts.atk == 6) return
|
|
2421
|
+
if (boosts.atk == 6) return log.fail(.{ ident, .None });
|
|
2472
2422
|
const n: u2 = if (move.effect == .AttackUp2) 2 else 1;
|
|
2473
2423
|
boosts.atk = @as(i4, @intCast(@min(6, @as(i8, boosts.atk) + n)));
|
|
2474
|
-
const reason = if (move.effect == .Rage)
|
|
2424
|
+
const reason: Boost = if (move.effect == .Rage) .Rage else .Attack;
|
|
2475
2425
|
if (stats.atk == MAX_STAT_VALUE) {
|
|
2476
2426
|
boosts.atk -= 1;
|
|
2477
2427
|
if (showdown) {
|
|
2478
|
-
try log.boost(ident, reason, n);
|
|
2479
|
-
try log.boost(ident,
|
|
2428
|
+
try log.boost(.{ ident, reason, n });
|
|
2429
|
+
try log.boost(.{ ident, .Attack, -1 });
|
|
2480
2430
|
if (move.effect == .Rage) return;
|
|
2481
2431
|
}
|
|
2482
|
-
return
|
|
2432
|
+
return log.fail(.{ ident, .None });
|
|
2483
2433
|
}
|
|
2484
|
-
|
|
2434
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2485
2435
|
const stat = unmodifiedStats(battle, side).atk;
|
|
2486
2436
|
stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2487
|
-
try log.boost(ident, reason, n);
|
|
2437
|
+
try log.boost(.{ ident, reason, n });
|
|
2488
2438
|
// Pokémon Showdown doesn't re-apply status modifiers after Rage boosts
|
|
2489
2439
|
if (showdown and move.effect == .Rage) return;
|
|
2490
2440
|
},
|
|
2491
2441
|
.DefenseUp1, .DefenseUp2 => {
|
|
2492
2442
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2493
|
-
if (boosts.def == 6) return
|
|
2443
|
+
if (boosts.def == 6) return log.fail(.{ ident, .None });
|
|
2494
2444
|
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
2495
2445
|
boosts.def = @intCast(@min(6, @as(i8, boosts.def) + n));
|
|
2496
2446
|
if (stats.def == MAX_STAT_VALUE) {
|
|
2497
2447
|
boosts.def -= 1;
|
|
2498
2448
|
if (showdown) {
|
|
2499
|
-
try log.boost(ident, .Defense, n);
|
|
2500
|
-
try log.boost(ident, .Defense, -1);
|
|
2449
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2450
|
+
try log.boost(.{ ident, .Defense, -1 });
|
|
2501
2451
|
}
|
|
2502
|
-
return
|
|
2452
|
+
return log.fail(.{ ident, .None });
|
|
2503
2453
|
}
|
|
2504
|
-
|
|
2454
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2505
2455
|
const stat = unmodifiedStats(battle, side).def;
|
|
2506
2456
|
stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2507
|
-
try log.boost(ident, .Defense, n);
|
|
2457
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2508
2458
|
},
|
|
2509
2459
|
.SpeedUp2 => {
|
|
2510
2460
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2511
|
-
if (boosts.spe == 6) return
|
|
2461
|
+
if (boosts.spe == 6) return log.fail(.{ ident, .None });
|
|
2512
2462
|
boosts.spe = @intCast(@min(6, @as(i8, boosts.spe) + 2));
|
|
2513
2463
|
if (stats.spe == MAX_STAT_VALUE) {
|
|
2514
2464
|
boosts.spe -= 1;
|
|
2515
2465
|
if (showdown) {
|
|
2516
|
-
try log.boost(ident, .Speed, 2);
|
|
2517
|
-
try log.boost(ident, .Speed, -1);
|
|
2466
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2467
|
+
try log.boost(.{ ident, .Speed, -1 });
|
|
2518
2468
|
}
|
|
2519
|
-
return
|
|
2469
|
+
return log.fail(.{ ident, .None });
|
|
2520
2470
|
}
|
|
2521
|
-
|
|
2471
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2522
2472
|
const stat = unmodifiedStats(battle, side).spe;
|
|
2523
2473
|
stats.spe = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2524
|
-
try log.boost(ident, .Speed, 2);
|
|
2474
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2525
2475
|
},
|
|
2526
2476
|
.SpecialUp1, .SpecialUp2 => {
|
|
2527
2477
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2528
|
-
if (boosts.spc == 6) return
|
|
2478
|
+
if (boosts.spc == 6) return log.fail(.{ ident, .None });
|
|
2529
2479
|
const n: u2 = if (move.effect == .SpecialUp2) 2 else 1;
|
|
2530
2480
|
boosts.spc = @intCast(@min(6, @as(i8, boosts.spc) + n));
|
|
2531
2481
|
if (stats.spc == MAX_STAT_VALUE) {
|
|
2532
2482
|
boosts.spc -= 1;
|
|
2533
2483
|
if (showdown) {
|
|
2534
|
-
try log.boost(ident, .SpecialAttack, n);
|
|
2535
|
-
try log.boost(ident, .SpecialAttack, -1);
|
|
2536
|
-
try log.boost(ident, .SpecialDefense, n);
|
|
2537
|
-
try log.boost(ident, .SpecialDefense, -1);
|
|
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 });
|
|
2538
2488
|
}
|
|
2539
|
-
return
|
|
2489
|
+
return log.fail(.{ ident, .None });
|
|
2540
2490
|
}
|
|
2541
|
-
|
|
2491
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2542
2492
|
const stat = unmodifiedStats(battle, side).spc;
|
|
2543
2493
|
stats.spc = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2544
|
-
try log.boost(ident, .SpecialAttack, n);
|
|
2545
|
-
try log.boost(ident, .SpecialDefense, n);
|
|
2494
|
+
try log.boost(.{ ident, .SpecialAttack, n });
|
|
2495
|
+
try log.boost(.{ ident, .SpecialDefense, n });
|
|
2546
2496
|
},
|
|
2547
2497
|
.EvasionUp1 => {
|
|
2548
2498
|
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2549
|
-
if (boosts.evasion == 6) return
|
|
2499
|
+
if (boosts.evasion == 6) return log.fail(.{ ident, .None });
|
|
2550
2500
|
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2551
|
-
try log.boost(ident, .Evasion, 1);
|
|
2501
|
+
try log.boost(.{ ident, .Evasion, 1 });
|
|
2552
2502
|
},
|
|
2553
2503
|
else => unreachable,
|
|
2554
2504
|
}
|
|
@@ -2563,7 +2513,7 @@ pub const Effects = struct {
|
|
|
2563
2513
|
const foe_ident = battle.active(player.foe());
|
|
2564
2514
|
const secondary = move.effect.isStatDownChance();
|
|
2565
2515
|
|
|
2566
|
-
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 });
|
|
2567
2517
|
|
|
2568
2518
|
if (secondary) {
|
|
2569
2519
|
const proc = try Rolls.unboost(battle, player, options);
|
|
@@ -2581,82 +2531,82 @@ pub const Effects = struct {
|
|
|
2581
2531
|
switch (move.effect) {
|
|
2582
2532
|
.AttackDown1, .AttackDownChance => {
|
|
2583
2533
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2584
|
-
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 });
|
|
2585
2535
|
boosts.atk = @intCast(@max(-6, @as(i8, boosts.atk) - 1));
|
|
2586
2536
|
if (stats.atk == 1) {
|
|
2587
2537
|
boosts.atk += 1;
|
|
2588
2538
|
if (showdown) {
|
|
2589
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2590
|
-
try log.boost(foe_ident, .Attack, 1);
|
|
2539
|
+
try log.boost(.{ foe_ident, .Attack, -1 });
|
|
2540
|
+
try log.boost(.{ foe_ident, .Attack, 1 });
|
|
2591
2541
|
}
|
|
2592
|
-
return
|
|
2542
|
+
return log.fail(.{ foe_ident, .None });
|
|
2593
2543
|
}
|
|
2594
|
-
|
|
2544
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2595
2545
|
const stat = unmodifiedStats(battle, foe).atk;
|
|
2596
2546
|
stats.atk = @max(1, stat * mod[0] / mod[1]);
|
|
2597
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2547
|
+
try log.boost(.{ foe_ident, .Attack, -1 });
|
|
2598
2548
|
},
|
|
2599
2549
|
.DefenseDown1, .DefenseDown2, .DefenseDownChance => {
|
|
2600
2550
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2601
|
-
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 });
|
|
2602
2552
|
const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
|
|
2603
2553
|
boosts.def = @intCast(@max(-6, @as(i8, boosts.def) - n));
|
|
2604
2554
|
if (stats.def == 1) {
|
|
2605
2555
|
boosts.def += 1;
|
|
2606
2556
|
if (showdown) {
|
|
2607
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2608
|
-
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 });
|
|
2609
2559
|
}
|
|
2610
|
-
return
|
|
2560
|
+
return log.fail(.{ foe_ident, .None });
|
|
2611
2561
|
}
|
|
2612
|
-
|
|
2562
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2613
2563
|
const stat = unmodifiedStats(battle, foe).def;
|
|
2614
2564
|
stats.def = @max(1, stat * mod[0] / mod[1]);
|
|
2615
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2565
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2616
2566
|
},
|
|
2617
2567
|
.SpeedDown1, .SpeedDownChance => {
|
|
2618
2568
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2619
|
-
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 });
|
|
2620
2570
|
boosts.spe = @intCast(@max(-6, @as(i8, boosts.spe) - 1));
|
|
2621
2571
|
if (stats.spe == 1) {
|
|
2622
2572
|
boosts.spe += 1;
|
|
2623
2573
|
if (showdown) {
|
|
2624
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2625
|
-
try log.boost(foe_ident, .Speed, 1);
|
|
2574
|
+
try log.boost(.{ foe_ident, .Speed, -1 });
|
|
2575
|
+
try log.boost(.{ foe_ident, .Speed, 1 });
|
|
2626
2576
|
}
|
|
2627
|
-
return
|
|
2577
|
+
return log.fail(.{ foe_ident, .None });
|
|
2628
2578
|
}
|
|
2629
|
-
|
|
2579
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2630
2580
|
const stat = unmodifiedStats(battle, foe).spe;
|
|
2631
2581
|
stats.spe = @max(1, stat * mod[0] / mod[1]);
|
|
2632
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2582
|
+
try log.boost(.{ foe_ident, .Speed, -1 });
|
|
2633
2583
|
assert(boosts.spe >= -6);
|
|
2634
2584
|
},
|
|
2635
2585
|
.SpecialDownChance => {
|
|
2636
2586
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2637
|
-
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 });
|
|
2638
2588
|
boosts.spc = @intCast(@max(-6, @as(i8, boosts.spc) - 1));
|
|
2639
2589
|
if (stats.spc == 1) {
|
|
2640
2590
|
boosts.spc += 1;
|
|
2641
2591
|
if (showdown) {
|
|
2642
|
-
try log.boost(foe_ident, .SpecialAttack, -1);
|
|
2643
|
-
try log.boost(foe_ident, .SpecialAttack, 1);
|
|
2644
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2645
|
-
try log.boost(foe_ident, .SpecialDefense, 1);
|
|
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 });
|
|
2646
2596
|
}
|
|
2647
|
-
return
|
|
2597
|
+
return log.fail(.{ foe_ident, .None });
|
|
2648
2598
|
}
|
|
2649
|
-
|
|
2599
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2650
2600
|
const stat = unmodifiedStats(battle, foe).spc;
|
|
2651
2601
|
stats.spc = @max(1, stat * mod[0] / mod[1]);
|
|
2652
|
-
try log.boost(foe_ident, .SpecialAttack, -1);
|
|
2653
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2602
|
+
try log.boost(.{ foe_ident, .SpecialAttack, -1 });
|
|
2603
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2654
2604
|
},
|
|
2655
2605
|
.AccuracyDown1 => {
|
|
2656
2606
|
assert(boosts.accuracy >= -6 and boosts.accuracy <= 6);
|
|
2657
|
-
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 });
|
|
2658
2608
|
boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
|
|
2659
|
-
try log.boost(foe_ident, .Accuracy, -1);
|
|
2609
|
+
try log.boost(.{ foe_ident, .Accuracy, -1 });
|
|
2660
2610
|
},
|
|
2661
2611
|
else => unreachable,
|
|
2662
2612
|
}
|
|
@@ -2680,7 +2630,7 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
2680
2630
|
}
|
|
2681
2631
|
}
|
|
2682
2632
|
|
|
2683
|
-
|
|
2633
|
+
fn isForced(active: anytype) bool {
|
|
2684
2634
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
2685
2635
|
active.volatiles.Thrashing or active.volatiles.Charging;
|
|
2686
2636
|
}
|
|
@@ -2693,27 +2643,27 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2693
2643
|
|
|
2694
2644
|
options.chance.clearDurations(who, clear);
|
|
2695
2645
|
|
|
2696
|
-
if (volatiles.
|
|
2697
|
-
volatiles.
|
|
2698
|
-
volatiles.
|
|
2699
|
-
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 });
|
|
2700
2650
|
}
|
|
2701
2651
|
if (volatiles.Confusion) {
|
|
2702
2652
|
// volatiles.confusion is left unchanged
|
|
2703
2653
|
volatiles.Confusion = false;
|
|
2704
|
-
try log.end(ident, .ConfusionSilent);
|
|
2654
|
+
try log.end(.{ ident, .ConfusionSilent });
|
|
2705
2655
|
}
|
|
2706
2656
|
if (volatiles.Mist) {
|
|
2707
2657
|
volatiles.Mist = false;
|
|
2708
|
-
try log.end(ident, .
|
|
2658
|
+
try log.end(.{ ident, .MistSilent });
|
|
2709
2659
|
}
|
|
2710
2660
|
if (volatiles.FocusEnergy) {
|
|
2711
2661
|
volatiles.FocusEnergy = false;
|
|
2712
|
-
try log.end(ident, .
|
|
2662
|
+
try log.end(.{ ident, .FocusEnergySilent });
|
|
2713
2663
|
}
|
|
2714
2664
|
if (volatiles.LeechSeed) {
|
|
2715
2665
|
volatiles.LeechSeed = false;
|
|
2716
|
-
try log.end(ident, .
|
|
2666
|
+
try log.end(.{ ident, .LeechSeedSilent });
|
|
2717
2667
|
}
|
|
2718
2668
|
if (!showdown and volatiles.Toxic) {
|
|
2719
2669
|
volatiles.Toxic = false;
|
|
@@ -2721,24 +2671,26 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2721
2671
|
}
|
|
2722
2672
|
if (volatiles.LightScreen) {
|
|
2723
2673
|
volatiles.LightScreen = false;
|
|
2724
|
-
try log.end(ident, .
|
|
2674
|
+
try log.end(.{ ident, .LightScreenSilent });
|
|
2725
2675
|
}
|
|
2726
2676
|
if (volatiles.Reflect) {
|
|
2727
2677
|
volatiles.Reflect = false;
|
|
2728
|
-
try log.end(ident, .
|
|
2678
|
+
try log.end(.{ ident, .ReflectSilent });
|
|
2729
2679
|
}
|
|
2730
2680
|
if (showdown and volatiles.Toxic) {
|
|
2731
2681
|
volatiles.Toxic = false;
|
|
2732
2682
|
// Pokémon Showdown erroneously clears the toxic counter
|
|
2733
2683
|
volatiles.toxic = 0;
|
|
2734
|
-
try log.end(ident, .
|
|
2684
|
+
try log.end(.{ ident, .ToxicSilent });
|
|
2735
2685
|
}
|
|
2736
2686
|
}
|
|
2737
2687
|
|
|
2738
2688
|
pub const Rolls = struct {
|
|
2739
|
-
|
|
2689
|
+
fn speedTie(battle: anytype, options: anytype) !bool {
|
|
2740
2690
|
const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
|
|
2741
2691
|
player == .P1
|
|
2692
|
+
else if (@hasDecl(@TypeOf(battle.rng), "speedTie"))
|
|
2693
|
+
battle.rng.speedTie()
|
|
2742
2694
|
else if (showdown)
|
|
2743
2695
|
battle.rng.range(u8, 0, 2) == 0
|
|
2744
2696
|
else
|
|
@@ -2748,9 +2700,11 @@ pub const Rolls = struct {
|
|
|
2748
2700
|
return p1;
|
|
2749
2701
|
}
|
|
2750
2702
|
|
|
2751
|
-
|
|
2703
|
+
fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
|
|
2752
2704
|
const crit = if (options.calc.overridden(player, .critical_hit)) |val|
|
|
2753
2705
|
val == .true
|
|
2706
|
+
else if (@hasDecl(@TypeOf(battle.rng), "criticalHit"))
|
|
2707
|
+
battle.rng.criticalHit(player, rate)
|
|
2754
2708
|
else if (showdown)
|
|
2755
2709
|
battle.rng.chance(u8, rate, 256)
|
|
2756
2710
|
else
|
|
@@ -2760,9 +2714,11 @@ pub const Rolls = struct {
|
|
|
2760
2714
|
return crit;
|
|
2761
2715
|
}
|
|
2762
2716
|
|
|
2763
|
-
|
|
2717
|
+
fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2764
2718
|
const roll = if (options.calc.overridden(player, .damage)) |val|
|
|
2765
2719
|
val
|
|
2720
|
+
else if (@hasDecl(@TypeOf(battle.rng), "damage"))
|
|
2721
|
+
battle.rng.damage(player)
|
|
2766
2722
|
else roll: {
|
|
2767
2723
|
if (showdown) break :roll battle.rng.range(u8, 217, 256);
|
|
2768
2724
|
while (true) {
|
|
@@ -2776,9 +2732,11 @@ pub const Rolls = struct {
|
|
|
2776
2732
|
return roll;
|
|
2777
2733
|
}
|
|
2778
2734
|
|
|
2779
|
-
|
|
2735
|
+
fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) bool {
|
|
2780
2736
|
const ok = if (options.calc.overridden(player, .hit)) |val|
|
|
2781
2737
|
val == .true
|
|
2738
|
+
else if (@hasDecl(@TypeOf(battle.rng), "hit"))
|
|
2739
|
+
battle.rng.hit(player, accuracy)
|
|
2782
2740
|
else if (showdown)
|
|
2783
2741
|
battle.rng.chance(u8, accuracy, 256)
|
|
2784
2742
|
else
|
|
@@ -2788,9 +2746,11 @@ pub const Rolls = struct {
|
|
|
2788
2746
|
return ok;
|
|
2789
2747
|
}
|
|
2790
2748
|
|
|
2791
|
-
|
|
2749
|
+
fn confused(battle: anytype, player: Player, options: anytype) !bool {
|
|
2792
2750
|
const cfz = if (options.calc.overridden(player, .confused)) |val|
|
|
2793
2751
|
val == .true
|
|
2752
|
+
else if (@hasDecl(@TypeOf(battle.rng), "confused"))
|
|
2753
|
+
battle.rng.confused(player)
|
|
2794
2754
|
else if (showdown)
|
|
2795
2755
|
!battle.rng.chance(u8, 128, 256)
|
|
2796
2756
|
else
|
|
@@ -2800,9 +2760,11 @@ pub const Rolls = struct {
|
|
|
2800
2760
|
return cfz;
|
|
2801
2761
|
}
|
|
2802
2762
|
|
|
2803
|
-
|
|
2763
|
+
fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
|
|
2804
2764
|
const par = if (options.calc.overridden(player, .paralyzed)) |val|
|
|
2805
2765
|
val == .true
|
|
2766
|
+
else if (@hasDecl(@TypeOf(battle.rng), "paralyzed"))
|
|
2767
|
+
battle.rng.paralyzed(player)
|
|
2806
2768
|
else if (showdown)
|
|
2807
2769
|
battle.rng.chance(u8, 63, 256)
|
|
2808
2770
|
else
|
|
@@ -2812,9 +2774,11 @@ pub const Rolls = struct {
|
|
|
2812
2774
|
return par;
|
|
2813
2775
|
}
|
|
2814
2776
|
|
|
2815
|
-
|
|
2777
|
+
fn confusionChance(battle: anytype, player: Player, options: anytype) !bool {
|
|
2816
2778
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2817
2779
|
val == .true
|
|
2780
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2781
|
+
battle.rng.secondaryChance(player, 25)
|
|
2818
2782
|
else if (showdown)
|
|
2819
2783
|
battle.rng.chance(u8, 25, 256)
|
|
2820
2784
|
else
|
|
@@ -2824,11 +2788,13 @@ pub const Rolls = struct {
|
|
|
2824
2788
|
return proc;
|
|
2825
2789
|
}
|
|
2826
2790
|
|
|
2827
|
-
|
|
2791
|
+
fn secondaryChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2828
2792
|
const rate: u8 = if (low) 26 else 77;
|
|
2829
2793
|
|
|
2830
2794
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2831
2795
|
val == .true
|
|
2796
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2797
|
+
battle.rng.secondaryChance(player, rate)
|
|
2832
2798
|
else if (showdown)
|
|
2833
2799
|
battle.rng.chance(u8, rate, 256)
|
|
2834
2800
|
else
|
|
@@ -2838,11 +2804,13 @@ pub const Rolls = struct {
|
|
|
2838
2804
|
return proc;
|
|
2839
2805
|
}
|
|
2840
2806
|
|
|
2841
|
-
|
|
2807
|
+
fn poisonChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2842
2808
|
const rate: u8 = if (low) 52 else 103;
|
|
2843
2809
|
|
|
2844
2810
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2845
2811
|
val == .true
|
|
2812
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2813
|
+
battle.rng.secondaryChance(player, rate)
|
|
2846
2814
|
else if (showdown)
|
|
2847
2815
|
battle.rng.chance(u8, rate, 256)
|
|
2848
2816
|
else
|
|
@@ -2852,9 +2820,11 @@ pub const Rolls = struct {
|
|
|
2852
2820
|
return proc;
|
|
2853
2821
|
}
|
|
2854
2822
|
|
|
2855
|
-
|
|
2823
|
+
fn unboost(battle: anytype, player: Player, options: anytype) !bool {
|
|
2856
2824
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2857
2825
|
val == .true
|
|
2826
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2827
|
+
battle.rng.secondaryChance(player, 85)
|
|
2858
2828
|
else if (showdown)
|
|
2859
2829
|
battle.rng.chance(u8, 85, 256)
|
|
2860
2830
|
else
|
|
@@ -2864,14 +2834,14 @@ pub const Rolls = struct {
|
|
|
2864
2834
|
return proc;
|
|
2865
2835
|
}
|
|
2866
2836
|
|
|
2867
|
-
|
|
2837
|
+
fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
2868
2838
|
const move: Move = if (options.calc.overridden(player, .metronome)) |val|
|
|
2869
2839
|
val
|
|
2870
|
-
else if (
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
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: {
|
|
2875
2845
|
while (true) {
|
|
2876
2846
|
const r = battle.rng.next();
|
|
2877
2847
|
if (r == 0 or r == @intFromEnum(Move.Metronome)) continue;
|
|
@@ -2884,9 +2854,11 @@ pub const Rolls = struct {
|
|
|
2884
2854
|
return move;
|
|
2885
2855
|
}
|
|
2886
2856
|
|
|
2887
|
-
|
|
2857
|
+
fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
|
|
2888
2858
|
const power = if (options.calc.overridden(player, .psywave)) |val|
|
|
2889
2859
|
val - 1
|
|
2860
|
+
else if (@hasDecl(@TypeOf(battle.rng), "psywave"))
|
|
2861
|
+
battle.rng.psywave(player)
|
|
2890
2862
|
else if (showdown)
|
|
2891
2863
|
battle.rng.range(u8, 0, max)
|
|
2892
2864
|
else power: {
|
|
@@ -2901,11 +2873,13 @@ pub const Rolls = struct {
|
|
|
2901
2873
|
return power;
|
|
2902
2874
|
}
|
|
2903
2875
|
|
|
2904
|
-
|
|
2876
|
+
fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2905
2877
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2906
2878
|
@intCast(val)
|
|
2879
|
+
else if (@hasDecl(@TypeOf(battle.rng), "sleepDuration"))
|
|
2880
|
+
battle.rng.sleepDuration(player)
|
|
2907
2881
|
else if (showdown)
|
|
2908
|
-
|
|
2882
|
+
battle.rng.range(u3, 1, 8)
|
|
2909
2883
|
else duration: {
|
|
2910
2884
|
while (true) {
|
|
2911
2885
|
const r = battle.rng.next() & 7;
|
|
@@ -2918,11 +2892,13 @@ pub const Rolls = struct {
|
|
|
2918
2892
|
return duration;
|
|
2919
2893
|
}
|
|
2920
2894
|
|
|
2921
|
-
|
|
2895
|
+
fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
|
|
2922
2896
|
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2923
2897
|
val
|
|
2898
|
+
else if (@hasDecl(@TypeOf(battle.rng), "disableDuration"))
|
|
2899
|
+
battle.rng.disableDuration(player)
|
|
2924
2900
|
else if (showdown)
|
|
2925
|
-
|
|
2901
|
+
battle.rng.range(u4, 1, 9)
|
|
2926
2902
|
else
|
|
2927
2903
|
@intCast((battle.rng.next() & 7) + 1);
|
|
2928
2904
|
|
|
@@ -2931,11 +2907,13 @@ pub const Rolls = struct {
|
|
|
2931
2907
|
return duration;
|
|
2932
2908
|
}
|
|
2933
2909
|
|
|
2934
|
-
|
|
2910
|
+
fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
|
|
2935
2911
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2936
2912
|
@intCast(val)
|
|
2913
|
+
else if (@hasDecl(@TypeOf(battle.rng), "confusionDuration"))
|
|
2914
|
+
battle.rng.confusionDuration(player, self)
|
|
2937
2915
|
else if (showdown)
|
|
2938
|
-
|
|
2916
|
+
battle.rng.range(u3, 2, 6)
|
|
2939
2917
|
else
|
|
2940
2918
|
@intCast((battle.rng.next() & 3) + 2);
|
|
2941
2919
|
|
|
@@ -2944,11 +2922,13 @@ pub const Rolls = struct {
|
|
|
2944
2922
|
return duration;
|
|
2945
2923
|
}
|
|
2946
2924
|
|
|
2947
|
-
|
|
2925
|
+
fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2948
2926
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2949
2927
|
@intCast(val)
|
|
2928
|
+
else if (@hasDecl(@TypeOf(battle.rng), "attackingDuration"))
|
|
2929
|
+
battle.rng.attackingDuration(player)
|
|
2950
2930
|
else if (showdown)
|
|
2951
|
-
|
|
2931
|
+
battle.rng.range(u3, 2, 4)
|
|
2952
2932
|
else
|
|
2953
2933
|
@intCast((battle.rng.next() & 1) + 2);
|
|
2954
2934
|
|
|
@@ -2957,7 +2937,9 @@ pub const Rolls = struct {
|
|
|
2957
2937
|
return duration;
|
|
2958
2938
|
}
|
|
2959
2939
|
|
|
2960
|
-
|
|
2940
|
+
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
2941
|
+
|
|
2942
|
+
fn distribution(
|
|
2961
2943
|
battle: anytype,
|
|
2962
2944
|
comptime effect: Move.Effect,
|
|
2963
2945
|
player: Player,
|
|
@@ -2966,8 +2948,10 @@ pub const Rolls = struct {
|
|
|
2966
2948
|
const roll = if (effect == .Binding) .duration else .multi_hit;
|
|
2967
2949
|
const n: u3 = if (options.calc.overridden(player, roll)) |val|
|
|
2968
2950
|
@intCast(val)
|
|
2951
|
+
else if (@hasDecl(@TypeOf(battle.rng), "distribution"))
|
|
2952
|
+
battle.rng.distribution(player)
|
|
2969
2953
|
else if (showdown)
|
|
2970
|
-
DISTRIBUTION[battle.rng.range(
|
|
2954
|
+
DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
|
|
2971
2955
|
else n: {
|
|
2972
2956
|
const r = (battle.rng.next() & 3);
|
|
2973
2957
|
break :n @intCast((if (r < 2) r else battle.rng.next() & 3) + 2);
|
|
@@ -2998,7 +2982,9 @@ pub const Rolls = struct {
|
|
|
2998
2982
|
else
|
|
2999
2983
|
null;
|
|
3000
2984
|
|
|
3001
|
-
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: {
|
|
3002
2988
|
if (showdown) {
|
|
3003
2989
|
if (check_pp == 0) {
|
|
3004
2990
|
var i: usize = moves.len;
|
|
@@ -3014,10 +3000,9 @@ pub const Rolls = struct {
|
|
|
3014
3000
|
while (i < moves.len and r > 0) : (i += 1) {
|
|
3015
3001
|
if (moves[i].pp > 0) {
|
|
3016
3002
|
r -= 1;
|
|
3017
|
-
if (r == 0) break;
|
|
3003
|
+
if (r == 0) break :slot @intCast(i + 1);
|
|
3018
3004
|
}
|
|
3019
3005
|
}
|
|
3020
|
-
break :slot @intCast(i + 1);
|
|
3021
3006
|
}
|
|
3022
3007
|
}
|
|
3023
3008
|
|
|
@@ -3039,11 +3024,11 @@ test "RNG agreement" {
|
|
|
3039
3024
|
expected[i] = @intCast(i * 0x1000000);
|
|
3040
3025
|
}
|
|
3041
3026
|
|
|
3042
|
-
var spe
|
|
3043
|
-
var cfz
|
|
3044
|
-
var par
|
|
3045
|
-
var brn
|
|
3046
|
-
var eff
|
|
3027
|
+
var spe: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3028
|
+
var cfz: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3029
|
+
var par: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3030
|
+
var brn: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3031
|
+
var eff: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3047
3032
|
|
|
3048
3033
|
for (0..expected.len) |i| {
|
|
3049
3034
|
try expectEqual(spe.range(u8, 0, 2) == 0, i < Gen12.percent(50) + 1);
|
|
@@ -3079,7 +3064,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3079
3064
|
const side = battle.side(player);
|
|
3080
3065
|
const foe = battle.foe(player);
|
|
3081
3066
|
|
|
3082
|
-
|
|
3067
|
+
const active = &side.active;
|
|
3083
3068
|
const stored = side.stored();
|
|
3084
3069
|
|
|
3085
3070
|
// While players are not given any input options on the cartridge in these cases,
|
|
@@ -3133,7 +3118,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3133
3118
|
if (m.id == if (active.volatiles.Bide) .Bide else side.last_selected_move) {
|
|
3134
3119
|
// Pokémon Showdown displays Struggle if limited to Bide but unable to pick
|
|
3135
3120
|
const struggle =
|
|
3136
|
-
m.id == .Bide and (m.pp == 0 or active.volatiles.
|
|
3121
|
+
m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
|
|
3137
3122
|
const s = if (struggle) 0 else slot;
|
|
3138
3123
|
out[n] = .{ .type = .Move, .data = s };
|
|
3139
3124
|
n += 1;
|
|
@@ -3148,7 +3133,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3148
3133
|
const m = active.moves[slot - 1];
|
|
3149
3134
|
if (m.id == .None) break;
|
|
3150
3135
|
if (m.pp == 0) continue;
|
|
3151
|
-
if (active.volatiles.
|
|
3136
|
+
if (active.volatiles.disable_move == slot) continue;
|
|
3152
3137
|
out[n] = .{ .type = .Move, .data = slot };
|
|
3153
3138
|
n += 1;
|
|
3154
3139
|
}
|