@pkmn/engine 0.1.0-dev.736f7a8b → 0.1.0-dev.77410aff
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -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 +40 -23
- package/package.json +30 -17
- package/src/bin/install-pkmn-engine +1 -1
- package/src/lib/binding/node.zig +7 -11
- package/src/lib/common/data.zig +8 -20
- package/src/lib/common/optional.zig +8 -1
- package/src/lib/common/protocol.zig +725 -299
- package/src/lib/common/rational.zig +12 -8
- package/src/lib/common/rng.zig +15 -6
- package/src/lib/gen1/README.md +31 -29
- package/src/lib/gen1/calc.zig +9 -9
- package/src/lib/gen1/chance.zig +55 -39
- package/src/lib/gen1/data/moves.zig +192 -180
- package/src/lib/gen1/data/species.zig +2 -2
- package/src/lib/gen1/data/types.zig +7 -7
- package/src/lib/gen1/data.zig +31 -43
- package/src/lib/gen1/helpers.zig +26 -41
- package/src/lib/gen1/mechanics.zig +314 -365
- package/src/lib/gen2/README.md +18 -72
- package/src/lib/gen2/calc.zig +143 -0
- package/src/lib/gen2/chance.zig +1314 -0
- package/src/lib/gen2/data/items.zig +12 -18
- package/src/lib/gen2/data/moves.zig +649 -568
- package/src/lib/gen2/data/species.zig +1 -1
- package/src/lib/gen2/data/types.zig +134 -31
- package/src/lib/gen2/data.zig +80 -68
- package/src/lib/gen2/generated.zig +806 -0
- package/src/lib/gen2/helpers.zig +4 -9
- package/src/lib/gen2/mechanics.zig +3238 -85
|
@@ -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,16 +196,13 @@ 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
|
};
|
|
217
|
-
|
|
218
203
|
assert(struggle);
|
|
219
|
-
saveMove(battle, player, choice);
|
|
220
|
-
} else {
|
|
221
|
-
saveMove(battle, player, choice);
|
|
222
204
|
}
|
|
205
|
+
saveMove(battle, player, choice);
|
|
223
206
|
|
|
224
207
|
return null;
|
|
225
208
|
}
|
|
@@ -232,7 +215,7 @@ fn saveMove(battle: anytype, player: Player, choice: ?Choice) void {
|
|
|
232
215
|
if (c.data == 0) {
|
|
233
216
|
side.last_selected_move = .Struggle;
|
|
234
217
|
} else {
|
|
235
|
-
assert(showdown or side.active.volatiles.
|
|
218
|
+
assert(showdown or side.active.volatiles.disable_move != c.data);
|
|
236
219
|
const move = side.active.move(c.data);
|
|
237
220
|
// You cannot *select* a move with 0 PP (except on Pokémon Showdown where that is
|
|
238
221
|
// sometimes required...), but a 0 PP move can be used automatically
|
|
@@ -273,14 +256,14 @@ fn switchIn(battle: anytype, player: Player, slot: u8, initial: bool, options: a
|
|
|
273
256
|
|
|
274
257
|
foe.active.volatiles.Binding = false;
|
|
275
258
|
|
|
276
|
-
try options.log.switched(battle.active(player), incoming);
|
|
259
|
+
try options.log.switched(.{ battle.active(player), incoming });
|
|
277
260
|
options.chance.switched(player, side.order[0], out);
|
|
278
261
|
|
|
279
262
|
if (showdown and incoming.status == Status.TOX) {
|
|
280
263
|
incoming.status = Status.init(.PSN);
|
|
281
264
|
// Technically, Pokémon Showdown adds these after *both* Pokémon have switched, but we'd
|
|
282
265
|
// rather not clutter up turnOrder just for this (incorrect) log message
|
|
283
|
-
try options.log.status(battle.active(player), incoming.status, .Silent);
|
|
266
|
+
try options.log.status(.{ battle.active(player), incoming.status, .Silent });
|
|
284
267
|
}
|
|
285
268
|
}
|
|
286
269
|
|
|
@@ -343,12 +326,10 @@ fn doTurn(
|
|
|
343
326
|
player_choice: Choice,
|
|
344
327
|
player_rewrap: bool,
|
|
345
328
|
player_skip: bool,
|
|
346
|
-
player_from: ?Move,
|
|
347
329
|
foe_player: Player,
|
|
348
330
|
foe_choice: Choice,
|
|
349
331
|
foe_rewrap: bool,
|
|
350
332
|
foe_skip: bool,
|
|
351
|
-
foe_from: ?Move,
|
|
352
333
|
options: anytype,
|
|
353
334
|
) !?Result {
|
|
354
335
|
assert(player_choice.type != .Pass);
|
|
@@ -361,7 +342,6 @@ fn doTurn(
|
|
|
361
342
|
player_choice,
|
|
362
343
|
player_rewrap,
|
|
363
344
|
player_skip,
|
|
364
|
-
player_from,
|
|
365
345
|
&residual,
|
|
366
346
|
options,
|
|
367
347
|
)) |r| return r;
|
|
@@ -384,7 +364,6 @@ fn doTurn(
|
|
|
384
364
|
foe_choice,
|
|
385
365
|
foe_rewrap,
|
|
386
366
|
foe_skip,
|
|
387
|
-
foe_from,
|
|
388
367
|
&residual,
|
|
389
368
|
options,
|
|
390
369
|
)) |r| return r;
|
|
@@ -412,7 +391,6 @@ fn executeMove(
|
|
|
412
391
|
choice: Choice,
|
|
413
392
|
rewrap: bool,
|
|
414
393
|
skip: bool,
|
|
415
|
-
from: ?Move,
|
|
416
394
|
residual: *bool,
|
|
417
395
|
options: anytype,
|
|
418
396
|
) !?Result {
|
|
@@ -429,7 +407,7 @@ fn executeMove(
|
|
|
429
407
|
if (side.last_selected_move == .SKIP_TURN) {
|
|
430
408
|
assert(!showdown);
|
|
431
409
|
if (battle.foe(player).active.volatiles.Binding) {
|
|
432
|
-
try options.log.cant(battle.active(player), .Bound);
|
|
410
|
+
try options.log.cant(.{ battle.active(player), .Bound });
|
|
433
411
|
}
|
|
434
412
|
return null;
|
|
435
413
|
}
|
|
@@ -447,8 +425,10 @@ fn executeMove(
|
|
|
447
425
|
mslot = @intCast(battle.lastMove(player).index);
|
|
448
426
|
const stored = side.stored();
|
|
449
427
|
// GLITCH: Struggle bypass PP underflow via Hyper Beam / Trapping-switch auto selection
|
|
450
|
-
auto = side.
|
|
451
|
-
|
|
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);
|
|
452
432
|
// If it wasn't Hyper Beam or the continuation of a move effect then we must have just
|
|
453
433
|
// thawed, in which case we will desync unless the last_selected_move happened to be at
|
|
454
434
|
// index 1 and the current Pokémon has the same move in its first slot.
|
|
@@ -470,7 +450,7 @@ fn executeMove(
|
|
|
470
450
|
|
|
471
451
|
var skip_can = false;
|
|
472
452
|
var skip_pp = false;
|
|
473
|
-
switch (try beforeMove(battle, player, skip,
|
|
453
|
+
switch (try beforeMove(battle, player, skip, residual, options)) {
|
|
474
454
|
.done => return null,
|
|
475
455
|
.skip_can => skip_can = true,
|
|
476
456
|
.skip_pp => skip_pp = true,
|
|
@@ -478,9 +458,9 @@ fn executeMove(
|
|
|
478
458
|
.err => return @as(?Result, Result.Error),
|
|
479
459
|
}
|
|
480
460
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
461
|
+
const can = skip_can or
|
|
462
|
+
try canMove(battle, player, mslot, auto, skip_pp, .None, residual, options);
|
|
463
|
+
if (!can) return null;
|
|
484
464
|
|
|
485
465
|
return doMove(battle, player, mslot, rewrap, auto, residual, options);
|
|
486
466
|
}
|
|
@@ -491,7 +471,6 @@ fn beforeMove(
|
|
|
491
471
|
battle: anytype,
|
|
492
472
|
player: Player,
|
|
493
473
|
skip: bool,
|
|
494
|
-
from: ?Move,
|
|
495
474
|
residual: *bool,
|
|
496
475
|
options: anytype,
|
|
497
476
|
) !BeforeMove {
|
|
@@ -518,23 +497,23 @@ fn beforeMove(
|
|
|
518
497
|
}
|
|
519
498
|
|
|
520
499
|
if (duration == 0) {
|
|
521
|
-
try log.curestatus(ident, before, .Message);
|
|
500
|
+
try log.curestatus(.{ ident, before, .Message });
|
|
522
501
|
stored.status = 0; // clears EXT if present
|
|
523
502
|
} else {
|
|
524
|
-
try log.cant(ident, .Sleep);
|
|
503
|
+
try log.cant(.{ ident, .Sleep });
|
|
525
504
|
}
|
|
526
505
|
side.last_used_move = .None;
|
|
527
506
|
return .done;
|
|
528
507
|
}
|
|
529
508
|
|
|
530
509
|
if (Status.is(stored.status, .FRZ)) {
|
|
531
|
-
try log.cant(ident, .Freeze);
|
|
510
|
+
try log.cant(.{ ident, .Freeze });
|
|
532
511
|
side.last_used_move = .None;
|
|
533
512
|
return .done;
|
|
534
513
|
}
|
|
535
514
|
|
|
536
515
|
if (skip or foe.active.volatiles.Binding) {
|
|
537
|
-
try log.cant(ident, .Bound);
|
|
516
|
+
try log.cant(.{ ident, .Bound });
|
|
538
517
|
return .done;
|
|
539
518
|
}
|
|
540
519
|
|
|
@@ -542,27 +521,27 @@ fn beforeMove(
|
|
|
542
521
|
// Pokémon Showdown doesn't clear Flinch until its imaginary "residual" phase, meaning
|
|
543
522
|
// Pokémon can sometimes flinch multiple times from the same original hit
|
|
544
523
|
if (!showdown) volatiles.Flinch = false;
|
|
545
|
-
try log.cant(ident, .Flinch);
|
|
524
|
+
try log.cant(.{ ident, .Flinch });
|
|
546
525
|
return .done;
|
|
547
526
|
}
|
|
548
527
|
|
|
549
528
|
if (volatiles.Recharging) {
|
|
550
529
|
volatiles.Recharging = false;
|
|
551
|
-
try log.cant(ident, .Recharge);
|
|
530
|
+
try log.cant(.{ ident, .Recharge });
|
|
552
531
|
return .done;
|
|
553
532
|
}
|
|
554
533
|
|
|
555
|
-
if (volatiles.
|
|
534
|
+
if (volatiles.disable_duration > 0) {
|
|
556
535
|
if (options.calc.modify(player, .disable)) |extend| {
|
|
557
|
-
if (!extend) volatiles.
|
|
536
|
+
if (!extend) volatiles.disable_duration = 0;
|
|
558
537
|
} else {
|
|
559
|
-
volatiles.
|
|
538
|
+
volatiles.disable_duration -= 1;
|
|
560
539
|
}
|
|
561
|
-
try options.chance.disable(player, volatiles.
|
|
540
|
+
try options.chance.disable(player, volatiles.disable_duration);
|
|
562
541
|
|
|
563
|
-
if (volatiles.
|
|
564
|
-
volatiles.
|
|
565
|
-
try log.end(ident, .Disable);
|
|
542
|
+
if (volatiles.disable_duration == 0) {
|
|
543
|
+
volatiles.disable_move = 0;
|
|
544
|
+
try log.end(.{ ident, .Disable });
|
|
566
545
|
}
|
|
567
546
|
}
|
|
568
547
|
// Pokémon Showdown's disable condition has a single onBeforeMove handler
|
|
@@ -587,9 +566,9 @@ fn beforeMove(
|
|
|
587
566
|
|
|
588
567
|
if (volatiles.confusion == 0) {
|
|
589
568
|
volatiles.Confusion = false;
|
|
590
|
-
try log.end(ident, .Confusion);
|
|
569
|
+
try log.end(.{ ident, .Confusion });
|
|
591
570
|
} else {
|
|
592
|
-
try log.activate(ident, .Confusion);
|
|
571
|
+
try log.activate(.{ ident, .Confusion });
|
|
593
572
|
|
|
594
573
|
if (try Rolls.confused(battle, player, options)) {
|
|
595
574
|
assert(!volatiles.MultiHit);
|
|
@@ -640,7 +619,7 @@ fn beforeMove(
|
|
|
640
619
|
volatiles.Binding = false;
|
|
641
620
|
options.chance.clearDurations(player, null);
|
|
642
621
|
// GLITCH: Invulnerable is not cleared, resulting in permanent Fly/Dig invulnerability
|
|
643
|
-
try log.cant(ident, .Paralysis);
|
|
622
|
+
try log.cant(.{ ident, .Paralysis });
|
|
644
623
|
return .done;
|
|
645
624
|
}
|
|
646
625
|
|
|
@@ -656,8 +635,6 @@ fn beforeMove(
|
|
|
656
635
|
volatiles.state +%= battle.last_damage;
|
|
657
636
|
}
|
|
658
637
|
|
|
659
|
-
assert(volatiles.attacks > 0);
|
|
660
|
-
|
|
661
638
|
if (options.calc.modify(player, .attacking)) |extend| {
|
|
662
639
|
if (!extend) volatiles.attacks = 0;
|
|
663
640
|
} else {
|
|
@@ -666,18 +643,18 @@ fn beforeMove(
|
|
|
666
643
|
try options.chance.attacking(player, volatiles.attacks);
|
|
667
644
|
|
|
668
645
|
if (volatiles.attacks != 0) {
|
|
669
|
-
try log.activate(ident, .Bide);
|
|
646
|
+
try log.activate(.{ ident, .Bide });
|
|
670
647
|
return .done;
|
|
671
648
|
}
|
|
672
649
|
|
|
673
650
|
volatiles.Bide = false;
|
|
674
|
-
try log.end(ident, .Bide);
|
|
651
|
+
try log.end(.{ ident, .Bide });
|
|
675
652
|
|
|
676
653
|
battle.last_damage = volatiles.state *% 2;
|
|
677
654
|
volatiles.state = 0;
|
|
678
655
|
|
|
679
656
|
if (battle.last_damage == 0) {
|
|
680
|
-
try log.fail(ident, .None);
|
|
657
|
+
try log.fail(.{ ident, .None });
|
|
681
658
|
return .done;
|
|
682
659
|
}
|
|
683
660
|
|
|
@@ -697,9 +674,6 @@ fn beforeMove(
|
|
|
697
674
|
}
|
|
698
675
|
|
|
699
676
|
if (volatiles.Thrashing) {
|
|
700
|
-
assert(volatiles.attacks > 0);
|
|
701
|
-
assert(from != null);
|
|
702
|
-
|
|
703
677
|
if (options.calc.modify(player, .attacking)) |extend| {
|
|
704
678
|
if (!extend) volatiles.attacks = 0;
|
|
705
679
|
} else {
|
|
@@ -708,23 +682,21 @@ fn beforeMove(
|
|
|
708
682
|
try options.chance.attacking(player, volatiles.attacks);
|
|
709
683
|
|
|
710
684
|
if (!showdown and handleThrashing(battle, active, player, options)) {
|
|
711
|
-
try log.start(battle.active(player), .ConfusionSilent);
|
|
685
|
+
try log.start(.{ battle.active(player), .ConfusionSilent });
|
|
712
686
|
}
|
|
713
|
-
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()) });
|
|
714
688
|
if (showdown) {
|
|
715
689
|
// This shouldn't actually set last_used_move, but Pokémon Showdown sets last
|
|
716
690
|
// used in useMove and doesn't have the notion of skipping canMove semantics
|
|
717
691
|
side.last_used_move = side.last_selected_move;
|
|
718
692
|
if (handleThrashing(battle, active, player, options)) {
|
|
719
|
-
try log.start(battle.active(player), .ConfusionSilent);
|
|
693
|
+
try log.start(.{ battle.active(player), .ConfusionSilent });
|
|
720
694
|
}
|
|
721
695
|
}
|
|
722
696
|
return .skip_can;
|
|
723
697
|
}
|
|
724
698
|
|
|
725
699
|
if (volatiles.Binding) {
|
|
726
|
-
assert(volatiles.attacks > 0);
|
|
727
|
-
|
|
728
700
|
if (options.calc.modify(player, .binding)) |extend| {
|
|
729
701
|
if (!extend) volatiles.attacks = 0;
|
|
730
702
|
} else {
|
|
@@ -732,12 +704,7 @@ fn beforeMove(
|
|
|
732
704
|
}
|
|
733
705
|
try options.chance.binding(player, volatiles.attacks);
|
|
734
706
|
|
|
735
|
-
try log.move(
|
|
736
|
-
battle.active(player),
|
|
737
|
-
side.last_selected_move,
|
|
738
|
-
battle.active(player.foe()),
|
|
739
|
-
side.last_selected_move,
|
|
740
|
-
);
|
|
707
|
+
try log.move(.{ ident, side.last_selected_move, battle.active(player.foe()) });
|
|
741
708
|
if (showdown or battle.last_damage != 0) {
|
|
742
709
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
743
710
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -757,7 +724,7 @@ fn canMove(
|
|
|
757
724
|
mslot: u4,
|
|
758
725
|
auto: bool,
|
|
759
726
|
skip_pp: bool,
|
|
760
|
-
from:
|
|
727
|
+
from: Move,
|
|
761
728
|
residual: *bool,
|
|
762
729
|
options: anytype,
|
|
763
730
|
) !bool {
|
|
@@ -765,33 +732,21 @@ fn canMove(
|
|
|
765
732
|
const player_ident = battle.active(player);
|
|
766
733
|
const move = Move.get(side.last_selected_move);
|
|
767
734
|
|
|
768
|
-
const foe_last = battle.foe(player).last_used_move;
|
|
769
|
-
const special = from != null and (from.? == .Metronome and
|
|
770
|
-
(from.? == .MirrorMove and !(foe_last == .None or foe_last == .MirrorMove)));
|
|
771
|
-
|
|
772
|
-
var skip = skip_pp;
|
|
773
735
|
if (side.active.volatiles.Charging) {
|
|
774
736
|
side.active.volatiles.Charging = false;
|
|
775
737
|
side.active.volatiles.Invulnerable = false;
|
|
776
738
|
} else if (move.effect == .Charge) {
|
|
777
|
-
try options.log.move(player_ident, side.last_selected_move,
|
|
739
|
+
try options.log.move(.{ player_ident, side.last_selected_move, ID{}, from });
|
|
778
740
|
setCounterable(battle, player, side, move);
|
|
779
741
|
try Effects.charge(battle, player, options);
|
|
780
742
|
return false;
|
|
781
743
|
}
|
|
782
744
|
|
|
783
|
-
|
|
784
|
-
if (!
|
|
745
|
+
side.last_used_move = side.last_selected_move;
|
|
746
|
+
if (!skip_pp) decrementPP(side, mslot, auto);
|
|
785
747
|
|
|
786
748
|
const target = if (move.target == .Self) player else player.foe();
|
|
787
|
-
|
|
788
|
-
// don't have space to be able to track it in all circumstances and thus usually try to infer it
|
|
789
|
-
// from side.last_used_move. However, this gets reset by the opponent switching or fainting,
|
|
790
|
-
// meaning sometimes we lose this information. from is still set because it not being null is
|
|
791
|
-
// relevant for control flow, but we don't have anything useful to put in the log. This is
|
|
792
|
-
// regrettable, but this information is more "nice to have" than required
|
|
793
|
-
const f = if (from != null and from.? == .None) null else from;
|
|
794
|
-
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 });
|
|
795
750
|
setCounterable(battle, player, side, move);
|
|
796
751
|
|
|
797
752
|
if (move.effect.onBegin()) {
|
|
@@ -829,12 +784,14 @@ fn decrementPP(side: *Side, mslot: u4, auto: bool) void {
|
|
|
829
784
|
assert(!volatiles.Rage and !volatiles.Thrashing and !volatiles.MultiHit);
|
|
830
785
|
if (volatiles.Bide) return;
|
|
831
786
|
|
|
832
|
-
|
|
833
|
-
|
|
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;
|
|
834
790
|
if (volatiles.Transform) return;
|
|
835
791
|
|
|
836
|
-
|
|
837
|
-
|
|
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;
|
|
838
795
|
assert(active.move(mslot).pp == side.stored().move(mslot).pp);
|
|
839
796
|
}
|
|
840
797
|
|
|
@@ -938,7 +895,7 @@ fn doMove(
|
|
|
938
895
|
}
|
|
939
896
|
}
|
|
940
897
|
|
|
941
|
-
|
|
898
|
+
const zero = battle.last_damage == 0;
|
|
942
899
|
miss = if (showdown or skip)
|
|
943
900
|
miss
|
|
944
901
|
else
|
|
@@ -966,16 +923,16 @@ fn doMove(
|
|
|
966
923
|
ohko = (!showdown or (!immune and !invulnerable)) and
|
|
967
924
|
move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe;
|
|
968
925
|
if (ohko) {
|
|
969
|
-
try log.immune(foe_ident, .OHKO);
|
|
926
|
+
try log.immune(.{ foe_ident, .OHKO });
|
|
970
927
|
} else if (immune and !invulnerable and (!showdown or move.effect != .Binding)) {
|
|
971
|
-
try log.immune(foe_ident, .None);
|
|
928
|
+
try log.immune(.{ foe_ident, .None });
|
|
972
929
|
} else if (mist) {
|
|
973
|
-
if (!foe.active.volatiles.Substitute) try log.activate(foe_ident, .Mist);
|
|
974
|
-
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 });
|
|
975
932
|
} else {
|
|
976
933
|
if (showdown or !zero) try options.chance.commit(player, .miss);
|
|
977
|
-
try log.lastmiss();
|
|
978
|
-
try log.miss(battle.active(player));
|
|
934
|
+
try log.lastmiss(.{});
|
|
935
|
+
try log.miss(.{battle.active(player)});
|
|
979
936
|
}
|
|
980
937
|
if (move.effect == .JumpKick) {
|
|
981
938
|
// Recoil is supposed to be damage/8 but damage will always be 0 here
|
|
@@ -1011,15 +968,15 @@ fn doMove(
|
|
|
1011
968
|
var hit: u4 = 0;
|
|
1012
969
|
while (hit < hits) {
|
|
1013
970
|
if (hit == 0) {
|
|
1014
|
-
if (crit) try log.crit(battle.active(player.foe()));
|
|
971
|
+
if (crit) try log.crit(.{battle.active(player.foe())});
|
|
1015
972
|
if (effectiveness > Effectiveness.neutral) {
|
|
1016
|
-
try log.supereffective(battle.active(player.foe()));
|
|
973
|
+
try log.supereffective(.{battle.active(player.foe())});
|
|
1017
974
|
} else if (effectiveness < Effectiveness.neutral) {
|
|
1018
|
-
try log.resisted(battle.active(player.foe()));
|
|
975
|
+
try log.resisted(.{battle.active(player.foe())});
|
|
1019
976
|
}
|
|
1020
977
|
}
|
|
1021
978
|
if (!skip) nullified = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
1022
|
-
if (hit == 0 and ohko) try log.ohko();
|
|
979
|
+
if (hit == 0 and ohko) try log.ohko(.{});
|
|
1023
980
|
hit += 1;
|
|
1024
981
|
if (foe.stored().hp == 0) break;
|
|
1025
982
|
if (!late and (!sub or move.effect == .Explode)) {
|
|
@@ -1036,7 +993,7 @@ fn doMove(
|
|
|
1036
993
|
if (showdown and move.effect == .Twineedle and !nullified and foe.stored().hp > 0) {
|
|
1037
994
|
try Effects.poison(battle, player, Move.get(.PoisonSting), options);
|
|
1038
995
|
}
|
|
1039
|
-
try log.hitcount(battle.active(player.foe()), hit);
|
|
996
|
+
try log.hitcount(.{ battle.active(player.foe()), hit });
|
|
1040
997
|
} else if (showdown) {
|
|
1041
998
|
// This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
|
|
1042
999
|
if (move.effect == .Binding) {
|
|
@@ -1046,9 +1003,9 @@ fn doMove(
|
|
|
1046
1003
|
assert(foe.stored().hp > 0);
|
|
1047
1004
|
// Pokémon Showdown logs |-damage| here instead of |-immune| because logic...
|
|
1048
1005
|
if (sub) {
|
|
1049
|
-
try log.activate(battle.active(player.foe()), .Substitute);
|
|
1006
|
+
try log.activate(.{ battle.active(player.foe()), .Substitute });
|
|
1050
1007
|
} else {
|
|
1051
|
-
try log.damage(battle.active(player.foe()), foe.stored(), .None);
|
|
1008
|
+
try log.damage(.{ battle.active(player.foe()), foe.stored(), .None });
|
|
1052
1009
|
try buildRage(battle, player.foe(), options);
|
|
1053
1010
|
}
|
|
1054
1011
|
return null;
|
|
@@ -1246,7 +1203,7 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1246
1203
|
const foe = battle.foe(player);
|
|
1247
1204
|
|
|
1248
1205
|
if (battle.last_damage == 0) {
|
|
1249
|
-
try options.log.fail(battle.active(player), .None);
|
|
1206
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1250
1207
|
return null;
|
|
1251
1208
|
}
|
|
1252
1209
|
|
|
@@ -1264,14 +1221,14 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1264
1221
|
foe_last_selected_move.type == .Fighting);
|
|
1265
1222
|
|
|
1266
1223
|
if (!used and !selected) {
|
|
1267
|
-
try options.log.fail(battle.active(player), .None);
|
|
1224
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1268
1225
|
return null;
|
|
1269
1226
|
}
|
|
1270
1227
|
|
|
1271
1228
|
if (!used or !selected) {
|
|
1272
1229
|
// GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
|
|
1273
1230
|
if (!showdown) return Result.Error;
|
|
1274
|
-
try options.log.fail(battle.active(player), .None);
|
|
1231
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1275
1232
|
return null;
|
|
1276
1233
|
}
|
|
1277
1234
|
|
|
@@ -1307,20 +1264,20 @@ fn applyDamage(
|
|
|
1307
1264
|
subbed.active.volatiles.substitute = 0;
|
|
1308
1265
|
subbed.active.volatiles.Substitute = false;
|
|
1309
1266
|
// battle.last_damage is not updated with the amount of HP the Substitute had
|
|
1310
|
-
try options.log.end(battle.active(sub_player), .Substitute);
|
|
1267
|
+
try options.log.end(.{ battle.active(sub_player), .Substitute });
|
|
1311
1268
|
options.calc.capped(target_player);
|
|
1312
1269
|
return true;
|
|
1313
1270
|
} else {
|
|
1314
1271
|
// Safe to truncate since less than subbed.volatiles.substitute which is a u8
|
|
1315
1272
|
subbed.active.volatiles.substitute -= @intCast(battle.last_damage);
|
|
1316
|
-
try options.log.activate(battle.active(sub_player), .Substitute);
|
|
1273
|
+
try options.log.activate(.{ battle.active(sub_player), .Substitute });
|
|
1317
1274
|
return false;
|
|
1318
1275
|
}
|
|
1319
1276
|
}
|
|
1320
1277
|
|
|
1321
1278
|
if (battle.last_damage > target.stored().hp) battle.last_damage = target.stored().hp;
|
|
1322
1279
|
target.stored().hp -= battle.last_damage;
|
|
1323
|
-
try options.log.damage(battle.active(target_player), target.stored(), reason);
|
|
1280
|
+
try options.log.damage(.{ battle.active(target_player), target.stored(), reason });
|
|
1324
1281
|
return false;
|
|
1325
1282
|
}
|
|
1326
1283
|
|
|
@@ -1339,7 +1296,7 @@ fn mirrorMove(
|
|
|
1339
1296
|
side.last_selected_move = foe.last_used_move;
|
|
1340
1297
|
|
|
1341
1298
|
if (foe.last_used_move == .None or foe.last_used_move == .MirrorMove) {
|
|
1342
|
-
try options.log.fail(battle.active(player), .None);
|
|
1299
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1343
1300
|
return null;
|
|
1344
1301
|
} else if (!showdown or foe.last_used_move != .Struggle) {
|
|
1345
1302
|
incrementPP(side, mslot);
|
|
@@ -1381,12 +1338,12 @@ fn checkHit(battle: anytype, player: Player, move: Move.Data, options: anytype)
|
|
|
1381
1338
|
if (mist) {
|
|
1382
1339
|
assert(!showdown);
|
|
1383
1340
|
const foe_ident = battle.active(player.foe());
|
|
1384
|
-
try options.log.activate(foe_ident, .Mist);
|
|
1385
|
-
try options.log.fail(foe_ident, .None);
|
|
1341
|
+
try options.log.activate(.{ foe_ident, .Mist });
|
|
1342
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1386
1343
|
} else {
|
|
1387
1344
|
try options.chance.commit(player, .miss);
|
|
1388
|
-
try options.log.lastmiss();
|
|
1389
|
-
try options.log.miss(battle.active(player));
|
|
1345
|
+
try options.log.lastmiss(.{});
|
|
1346
|
+
try options.log.miss(.{battle.active(player)});
|
|
1390
1347
|
}
|
|
1391
1348
|
|
|
1392
1349
|
return false;
|
|
@@ -1438,7 +1395,7 @@ fn moveHit(
|
|
|
1438
1395
|
const overwritten = overwrite and state > 0;
|
|
1439
1396
|
assert(!overwritten or (0 < state and state <= 255 and !side.active.volatiles.Bide));
|
|
1440
1397
|
|
|
1441
|
-
var accuracy = if (overwritten) state else @as(u16,
|
|
1398
|
+
var accuracy = if (overwritten) state else @as(u16, move.accuracy);
|
|
1442
1399
|
var boost = BOOSTS[@as(u4, @intCast(@as(i8, side.active.boosts.accuracy) + 6))];
|
|
1443
1400
|
accuracy = accuracy * boost[0] / boost[1];
|
|
1444
1401
|
boost = BOOSTS[@as(u4, @intCast(@as(i8, -foe.active.boosts.evasion) + 6))];
|
|
@@ -1477,9 +1434,8 @@ fn checkFaint(
|
|
|
1477
1434
|
const foe_fainted = foe.stored().hp == 0;
|
|
1478
1435
|
|
|
1479
1436
|
const player_out = findFirstAlive(side) == 0;
|
|
1480
|
-
const foe_out = findFirstAlive(foe) == 0;
|
|
1481
|
-
const
|
|
1482
|
-
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;
|
|
1483
1439
|
|
|
1484
1440
|
if (try faint(battle, player, !(more or foe_fainted), options)) |r| return r;
|
|
1485
1441
|
if (foe_fainted) if (try faint(battle, player.foe(), !more, options)) |r| return r;
|
|
@@ -1487,20 +1443,20 @@ fn checkFaint(
|
|
|
1487
1443
|
assert(!side.active.volatiles.MultiHit);
|
|
1488
1444
|
assert(!foe.active.volatiles.MultiHit);
|
|
1489
1445
|
|
|
1490
|
-
if (
|
|
1491
|
-
try options.log.tie();
|
|
1446
|
+
if (player_out and foe_out) {
|
|
1447
|
+
try options.log.tie(.{});
|
|
1492
1448
|
return Result.Tie;
|
|
1493
1449
|
} else if (player_out) {
|
|
1494
|
-
try options.log.win(player.foe());
|
|
1450
|
+
try options.log.win(.{player.foe()});
|
|
1495
1451
|
return if (player == .P1) Result.Lose else Result.Win;
|
|
1496
1452
|
} else if (foe_out) {
|
|
1497
|
-
try options.log.win(player);
|
|
1453
|
+
try options.log.win(.{player});
|
|
1498
1454
|
return if (player == .P1) Result.Win else Result.Lose;
|
|
1499
1455
|
}
|
|
1500
1456
|
|
|
1501
1457
|
const foe_choice: Choice.Type = if (foe_fainted) .Switch else .Pass;
|
|
1502
|
-
if (player == .P1) return
|
|
1503
|
-
return
|
|
1458
|
+
if (player == .P1) return .{ .p1 = .Switch, .p2 = foe_choice };
|
|
1459
|
+
return .{ .p1 = foe_choice, .p2 = .Switch };
|
|
1504
1460
|
}
|
|
1505
1461
|
|
|
1506
1462
|
fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result {
|
|
@@ -1536,7 +1492,7 @@ fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result
|
|
|
1536
1492
|
side.stored().stats.spe;
|
|
1537
1493
|
}
|
|
1538
1494
|
|
|
1539
|
-
try options.log.faint(battle.active(player), done);
|
|
1495
|
+
try options.log.faint(.{ battle.active(player), done });
|
|
1540
1496
|
options.calc.capped(player);
|
|
1541
1497
|
return null;
|
|
1542
1498
|
}
|
|
@@ -1548,21 +1504,19 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1548
1504
|
var volatiles = &side.active.volatiles;
|
|
1549
1505
|
|
|
1550
1506
|
const brn = Status.is(stored.status, .BRN);
|
|
1551
|
-
if (brn or Status.is(stored.status, .PSN))
|
|
1507
|
+
if (brn or Status.is(stored.status, .PSN)) {
|
|
1552
1508
|
var damage = @max(stored.stats.hp / 16, 1);
|
|
1553
|
-
|
|
1554
1509
|
if (volatiles.Toxic) {
|
|
1555
1510
|
volatiles.toxic += 1;
|
|
1556
1511
|
damage *= volatiles.toxic;
|
|
1557
1512
|
}
|
|
1513
|
+
damage = @min(damage, stored.hp);
|
|
1558
1514
|
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
// Pokémon Showdown uses damageOf here but its not relevant in Generation I
|
|
1565
|
-
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
1515
|
+
if (!showdown or damage > 0) {
|
|
1516
|
+
stored.hp -= damage;
|
|
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 });
|
|
1519
|
+
}
|
|
1566
1520
|
}
|
|
1567
1521
|
|
|
1568
1522
|
if (volatiles.LeechSeed) {
|
|
@@ -1586,14 +1540,14 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1586
1540
|
const amount = @min(damage, stored.hp);
|
|
1587
1541
|
stored.hp -= amount;
|
|
1588
1542
|
|
|
1589
|
-
// As above, Pokémon Showdown uses
|
|
1590
|
-
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 });
|
|
1591
1545
|
|
|
1592
1546
|
const before = foe_stored.hp;
|
|
1593
1547
|
// Uncapped damage is added back to the foe
|
|
1594
1548
|
foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
|
|
1595
1549
|
// Pokémon Showdown uses the less specific heal here instead of drain... because reasons?
|
|
1596
|
-
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 });
|
|
1597
1551
|
}
|
|
1598
1552
|
}
|
|
1599
1553
|
|
|
@@ -1604,18 +1558,18 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1604
1558
|
battle.turn += 1;
|
|
1605
1559
|
|
|
1606
1560
|
if (showdown and pkmn.options.ebc and checkEBC(battle)) {
|
|
1607
|
-
try options.log.tie();
|
|
1561
|
+
try options.log.tie(.{});
|
|
1608
1562
|
return Result.Tie;
|
|
1609
1563
|
}
|
|
1610
1564
|
|
|
1611
1565
|
if (showdown and battle.turn >= 1000) {
|
|
1612
|
-
try options.log.tie();
|
|
1566
|
+
try options.log.tie(.{});
|
|
1613
1567
|
return Result.Tie;
|
|
1614
1568
|
} else if (battle.turn >= 65535) {
|
|
1615
1569
|
return Result.Error;
|
|
1616
1570
|
}
|
|
1617
1571
|
|
|
1618
|
-
try options.log.turn(battle.turn);
|
|
1572
|
+
try options.log.turn(.{battle.turn});
|
|
1619
1573
|
|
|
1620
1574
|
return Result.Default;
|
|
1621
1575
|
}
|
|
@@ -1675,9 +1629,9 @@ fn checkEBC(battle: anytype) bool {
|
|
|
1675
1629
|
}
|
|
1676
1630
|
|
|
1677
1631
|
fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
1678
|
-
if (side.active.volatiles.
|
|
1632
|
+
if (side.active.volatiles.disable_move != 0) {
|
|
1679
1633
|
// A Pokémon that transforms after being disabled may end up with less move slots
|
|
1680
|
-
const m = side.active.moves[side.active.volatiles.
|
|
1634
|
+
const m = side.active.moves[side.active.volatiles.disable_move - 1].id;
|
|
1681
1635
|
// side.last_selected_move can be Struggle here on Pokemon Showdown we need an extra check
|
|
1682
1636
|
const last = if (showdown and m == .Bide and side.active.volatiles.Bide)
|
|
1683
1637
|
m
|
|
@@ -1685,14 +1639,14 @@ fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
|
1685
1639
|
side.last_selected_move;
|
|
1686
1640
|
if (m != .None and m == last) {
|
|
1687
1641
|
side.active.volatiles.Charging = false;
|
|
1688
|
-
try options.log.
|
|
1642
|
+
try options.log.cant(.{ ident, .Disable, last });
|
|
1689
1643
|
return true;
|
|
1690
1644
|
}
|
|
1691
1645
|
}
|
|
1692
1646
|
return false;
|
|
1693
1647
|
}
|
|
1694
1648
|
|
|
1695
|
-
|
|
1649
|
+
fn buildRage(battle: anytype, who: Player, options: anytype) !void {
|
|
1696
1650
|
const side = battle.side(who);
|
|
1697
1651
|
if (side.active.volatiles.Rage and side.active.boosts.atk < 6) {
|
|
1698
1652
|
try Effects.boost(battle, who, Move.get(.Rage), options);
|
|
@@ -1710,7 +1664,7 @@ fn handleThrashing(battle: anytype, active: *ActivePokemon, player: Player, opti
|
|
|
1710
1664
|
return true;
|
|
1711
1665
|
}
|
|
1712
1666
|
|
|
1713
|
-
|
|
1667
|
+
fn onBegin(
|
|
1714
1668
|
battle: anytype,
|
|
1715
1669
|
player: Player,
|
|
1716
1670
|
move: Move.Data,
|
|
@@ -1718,7 +1672,6 @@ inline fn onBegin(
|
|
|
1718
1672
|
residual: *bool,
|
|
1719
1673
|
options: anytype,
|
|
1720
1674
|
) !void {
|
|
1721
|
-
assert(move.effect.onBegin());
|
|
1722
1675
|
return switch (move.effect) {
|
|
1723
1676
|
.Confusion => Effects.confusion(battle, player, move, options),
|
|
1724
1677
|
.Conversion => Effects.conversion(battle, player, options),
|
|
@@ -1740,8 +1693,7 @@ inline fn onBegin(
|
|
|
1740
1693
|
};
|
|
1741
1694
|
}
|
|
1742
1695
|
|
|
1743
|
-
|
|
1744
|
-
assert(move.effect.onEnd());
|
|
1696
|
+
fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1745
1697
|
return switch (move.effect) {
|
|
1746
1698
|
.Bide => Effects.bide(battle, player, options),
|
|
1747
1699
|
// zig fmt: off
|
|
@@ -1756,14 +1708,13 @@ inline fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anyty
|
|
|
1756
1708
|
};
|
|
1757
1709
|
}
|
|
1758
1710
|
|
|
1759
|
-
|
|
1711
|
+
fn alwaysHappens(
|
|
1760
1712
|
battle: anytype,
|
|
1761
1713
|
player: Player,
|
|
1762
1714
|
move: Move.Data,
|
|
1763
1715
|
residual: *bool,
|
|
1764
1716
|
options: anytype,
|
|
1765
1717
|
) !void {
|
|
1766
|
-
assert(move.effect.alwaysHappens());
|
|
1767
1718
|
return switch (move.effect) {
|
|
1768
1719
|
.DrainHP, .DreamEater => Effects.drainHP(battle, player, options),
|
|
1769
1720
|
.Explode => Effects.explode(battle, player),
|
|
@@ -1775,7 +1726,7 @@ inline fn alwaysHappens(
|
|
|
1775
1726
|
};
|
|
1776
1727
|
}
|
|
1777
1728
|
|
|
1778
|
-
|
|
1729
|
+
fn moveEffect(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1779
1730
|
return switch (move.effect) {
|
|
1780
1731
|
.BurnChance1, .BurnChance2 => Effects.burnChance(battle, player, move, options),
|
|
1781
1732
|
.ConfusionChance => Effects.confusion(battle, player, move, options),
|
|
@@ -1802,7 +1753,7 @@ pub const Effects = struct {
|
|
|
1802
1753
|
side.active.volatiles.state = 0;
|
|
1803
1754
|
side.active.volatiles.attacks = Rolls.attackingDuration(battle, player, options);
|
|
1804
1755
|
|
|
1805
|
-
try options.log.start(battle.active(player), .Bide);
|
|
1756
|
+
try options.log.start(.{ battle.active(player), .Bide });
|
|
1806
1757
|
}
|
|
1807
1758
|
|
|
1808
1759
|
fn burnChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1816,11 +1767,11 @@ pub const Effects = struct {
|
|
|
1816
1767
|
// GLITCH: Freeze top move selection desync can occur if thawed Pokémon is slower
|
|
1817
1768
|
if (Status.is(foe_stored.status, .FRZ)) {
|
|
1818
1769
|
assert(move.type == .Fire);
|
|
1819
|
-
try options.log.curestatus(
|
|
1770
|
+
try options.log.curestatus(.{
|
|
1820
1771
|
battle.active(player.foe()),
|
|
1821
1772
|
foe_stored.status,
|
|
1822
1773
|
.Message,
|
|
1823
|
-
);
|
|
1774
|
+
});
|
|
1824
1775
|
foe_stored.status = 0;
|
|
1825
1776
|
}
|
|
1826
1777
|
return;
|
|
@@ -1834,7 +1785,7 @@ pub const Effects = struct {
|
|
|
1834
1785
|
foe_stored.status = Status.init(.BRN);
|
|
1835
1786
|
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
1836
1787
|
|
|
1837
|
-
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 });
|
|
1838
1789
|
}
|
|
1839
1790
|
|
|
1840
1791
|
fn charge(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -1844,8 +1795,8 @@ pub const Effects = struct {
|
|
|
1844
1795
|
volatiles.Charging = true;
|
|
1845
1796
|
const move = side.last_selected_move;
|
|
1846
1797
|
if (move == .Fly or move == .Dig) volatiles.Invulnerable = true;
|
|
1847
|
-
try options.log.laststill();
|
|
1848
|
-
try options.log.prepare(battle.active(player), move);
|
|
1798
|
+
try options.log.laststill(.{});
|
|
1799
|
+
try options.log.prepare(.{ battle.active(player), move });
|
|
1849
1800
|
}
|
|
1850
1801
|
|
|
1851
1802
|
fn confusion(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1856,17 +1807,11 @@ pub const Effects = struct {
|
|
|
1856
1807
|
if (!try Rolls.confusionChance(battle, player, options)) return;
|
|
1857
1808
|
} else {
|
|
1858
1809
|
if (showdown) {
|
|
1859
|
-
if (!try checkHit(battle, player, move, options))
|
|
1860
|
-
|
|
1861
|
-
} else if (sub) {
|
|
1862
|
-
return options.log.fail(battle.active(player.foe()), .None);
|
|
1863
|
-
}
|
|
1810
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1811
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1864
1812
|
} else {
|
|
1865
|
-
if (sub) {
|
|
1866
|
-
|
|
1867
|
-
} else if (!try checkHit(battle, player, move, options)) {
|
|
1868
|
-
return;
|
|
1869
|
-
}
|
|
1813
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1814
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1870
1815
|
}
|
|
1871
1816
|
try options.chance.commit(player, .hit);
|
|
1872
1817
|
}
|
|
@@ -1875,23 +1820,24 @@ pub const Effects = struct {
|
|
|
1875
1820
|
foe.active.volatiles.Confusion = true;
|
|
1876
1821
|
foe.active.volatiles.confusion = Rolls.confusionDuration(battle, player, false, options);
|
|
1877
1822
|
|
|
1878
|
-
try options.log.start(battle.active(player.foe()), .Confusion);
|
|
1823
|
+
try options.log.start(.{ battle.active(player.foe()), .Confusion });
|
|
1879
1824
|
}
|
|
1880
1825
|
|
|
1881
1826
|
fn conversion(battle: anytype, player: Player, options: anytype) !void {
|
|
1882
1827
|
const foe = battle.foe(player);
|
|
1883
1828
|
|
|
1884
1829
|
if (foe.active.volatiles.Invulnerable) {
|
|
1885
|
-
try options.log.lastmiss();
|
|
1886
|
-
return options.log.miss(battle.active(player));
|
|
1830
|
+
try options.log.lastmiss(.{});
|
|
1831
|
+
return options.log.miss(.{battle.active(player)});
|
|
1887
1832
|
}
|
|
1888
1833
|
|
|
1889
1834
|
battle.side(player).active.types = foe.active.types;
|
|
1890
|
-
return options.log.
|
|
1835
|
+
return options.log.start(.{
|
|
1891
1836
|
battle.active(player),
|
|
1837
|
+
.TypeChange,
|
|
1892
1838
|
foe.active.types,
|
|
1893
1839
|
battle.active(player.foe()),
|
|
1894
|
-
);
|
|
1840
|
+
});
|
|
1895
1841
|
}
|
|
1896
1842
|
|
|
1897
1843
|
fn disable(battle: anytype, player: Player, move: Move.Data, options: anytype) !?Result {
|
|
@@ -1905,8 +1851,8 @@ pub const Effects = struct {
|
|
|
1905
1851
|
try options.chance.commit(player, .hit);
|
|
1906
1852
|
}
|
|
1907
1853
|
|
|
1908
|
-
if (volatiles.
|
|
1909
|
-
try options.log.fail(foe_ident, .None);
|
|
1854
|
+
if (volatiles.disable_move != 0) {
|
|
1855
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1910
1856
|
return null;
|
|
1911
1857
|
}
|
|
1912
1858
|
|
|
@@ -1922,7 +1868,7 @@ pub const Effects = struct {
|
|
|
1922
1868
|
// Technically this is still considered simply a "miss" on the cartridge,
|
|
1923
1869
|
// but diverging from Pokémon Showdown here would mostly just be pedantic
|
|
1924
1870
|
if (n == 0) {
|
|
1925
|
-
try options.log.fail(foe_ident, .None);
|
|
1871
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1926
1872
|
return null;
|
|
1927
1873
|
} else if (err) {
|
|
1928
1874
|
// GLITCH: Transform + Mirror Move / Metronome PP softlock
|
|
@@ -1930,18 +1876,18 @@ pub const Effects = struct {
|
|
|
1930
1876
|
return Result.Error;
|
|
1931
1877
|
}
|
|
1932
1878
|
|
|
1933
|
-
volatiles.
|
|
1879
|
+
volatiles.disable_move =
|
|
1934
1880
|
@intCast(try Rolls.moveSlot(battle, player, &foe.active.moves, n, options));
|
|
1935
|
-
volatiles.
|
|
1881
|
+
volatiles.disable_duration = Rolls.disableDuration(battle, player, options);
|
|
1936
1882
|
|
|
1937
|
-
const id = foe.active.move(volatiles.
|
|
1938
|
-
try options.log.
|
|
1883
|
+
const id = foe.active.move(volatiles.disable_move).id;
|
|
1884
|
+
try options.log.start(.{ foe_ident, .Disable, id });
|
|
1939
1885
|
return null;
|
|
1940
1886
|
}
|
|
1941
1887
|
|
|
1942
1888
|
fn drainHP(battle: anytype, player: Player, options: anytype) !void {
|
|
1943
|
-
var
|
|
1944
|
-
|
|
1889
|
+
var stored = battle.side(player).stored();
|
|
1890
|
+
const ident = battle.active(player);
|
|
1945
1891
|
|
|
1946
1892
|
if (battle.last_damage == 0) {
|
|
1947
1893
|
assert(showdown);
|
|
@@ -1954,7 +1900,7 @@ pub const Effects = struct {
|
|
|
1954
1900
|
if (stored.hp == stored.stats.hp) return;
|
|
1955
1901
|
stored.hp = @min(stored.stats.hp, stored.hp + drain);
|
|
1956
1902
|
|
|
1957
|
-
try options.log.
|
|
1903
|
+
try options.log.heal(.{ ident, stored, .Drain, battle.active(player.foe()) });
|
|
1958
1904
|
}
|
|
1959
1905
|
|
|
1960
1906
|
fn explode(battle: anytype, player: Player) !void {
|
|
@@ -1986,7 +1932,7 @@ pub const Effects = struct {
|
|
|
1986
1932
|
if (side.active.volatiles.FocusEnergy) return;
|
|
1987
1933
|
side.active.volatiles.FocusEnergy = true;
|
|
1988
1934
|
|
|
1989
|
-
try options.log.start(battle.active(player), .FocusEnergy);
|
|
1935
|
+
try options.log.start(.{ battle.active(player), .FocusEnergy });
|
|
1990
1936
|
}
|
|
1991
1937
|
|
|
1992
1938
|
fn freezeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2008,7 +1954,7 @@ pub const Effects = struct {
|
|
|
2008
1954
|
foe_stored.status = Status.init(.FRZ);
|
|
2009
1955
|
// GLITCH: Hyper Beam recharging status is not cleared
|
|
2010
1956
|
|
|
2011
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
1957
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2012
1958
|
}
|
|
2013
1959
|
|
|
2014
1960
|
fn haze(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -2016,7 +1962,7 @@ pub const Effects = struct {
|
|
|
2016
1962
|
var side = battle.side(player);
|
|
2017
1963
|
var foe = battle.foe(player);
|
|
2018
1964
|
|
|
2019
|
-
|
|
1965
|
+
const side_stored = side.stored();
|
|
2020
1966
|
var foe_stored = foe.stored();
|
|
2021
1967
|
|
|
2022
1968
|
const player_ident = battle.active(player);
|
|
@@ -2028,8 +1974,8 @@ pub const Effects = struct {
|
|
|
2028
1974
|
side.active.stats = side_stored.stats;
|
|
2029
1975
|
foe.active.stats = foe_stored.stats;
|
|
2030
1976
|
|
|
2031
|
-
try log.activate(player_ident, .Haze);
|
|
2032
|
-
try log.clearallboost();
|
|
1977
|
+
try log.activate(.{ player_ident, .Haze });
|
|
1978
|
+
try log.clearallboost(.{});
|
|
2033
1979
|
|
|
2034
1980
|
// Pokémon Showdown clears P1 then P2 instead of status -> side -> foe
|
|
2035
1981
|
if (showdown) {
|
|
@@ -2037,11 +1983,11 @@ pub const Effects = struct {
|
|
|
2037
1983
|
const p: Player = @enumFromInt(i);
|
|
2038
1984
|
// Pokémon Showdown incorrectly does not prevent sleep/freeze from moving
|
|
2039
1985
|
if (p != player and Status.any(s.stored().status)) {
|
|
2040
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
1986
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2041
1987
|
s.stored().status = 0;
|
|
2042
1988
|
} else if (showdown and s.stored().status == Status.TOX) {
|
|
2043
1989
|
s.stored().status = Status.init(.PSN);
|
|
2044
|
-
try log.status(battle.active(p), s.stored().status, .None);
|
|
1990
|
+
try log.status(.{ battle.active(p), s.stored().status, .None });
|
|
2045
1991
|
}
|
|
2046
1992
|
try clearVolatiles(battle, p, p != player, options);
|
|
2047
1993
|
}
|
|
@@ -2050,7 +1996,7 @@ pub const Effects = struct {
|
|
|
2050
1996
|
if (Status.is(foe_stored.status, .FRZ) or Status.is(foe_stored.status, .SLP)) {
|
|
2051
1997
|
foe.last_selected_move = .SKIP_TURN;
|
|
2052
1998
|
}
|
|
2053
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
1999
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2054
2000
|
foe_stored.status = 0;
|
|
2055
2001
|
}
|
|
2056
2002
|
try clearVolatiles(battle, player, false, options);
|
|
@@ -2066,7 +2012,7 @@ pub const Effects = struct {
|
|
|
2066
2012
|
// GLITCH: HP recovery move failure glitches
|
|
2067
2013
|
const delta = stored.stats.hp - stored.hp;
|
|
2068
2014
|
if (delta == 0 or (delta & 255 == 255 and stored.hp % 256 != 0)) {
|
|
2069
|
-
return
|
|
2015
|
+
return options.log.fail(.{ ident, .None });
|
|
2070
2016
|
}
|
|
2071
2017
|
|
|
2072
2018
|
const rest = side.last_selected_move == .Rest;
|
|
@@ -2074,17 +2020,17 @@ pub const Effects = struct {
|
|
|
2074
2020
|
// Adding the sleep status runs the sleep condition handler to roll duration
|
|
2075
2021
|
if (showdown) battle.rng.advance(1);
|
|
2076
2022
|
stored.status = Status.slf(2);
|
|
2077
|
-
try options.log.
|
|
2023
|
+
try options.log.status(.{ ident, stored.status, .From, Move.Rest });
|
|
2078
2024
|
stored.hp = stored.stats.hp;
|
|
2079
2025
|
} else {
|
|
2080
2026
|
stored.hp = @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2));
|
|
2081
2027
|
}
|
|
2082
|
-
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 });
|
|
2083
2029
|
}
|
|
2084
2030
|
|
|
2085
2031
|
fn hyperBeam(battle: anytype, player: Player, options: anytype) !void {
|
|
2086
2032
|
battle.side(player).active.volatiles.Recharging = true;
|
|
2087
|
-
try options.log.mustrecharge(battle.active(player));
|
|
2033
|
+
try options.log.mustrecharge(.{battle.active(player)});
|
|
2088
2034
|
}
|
|
2089
2035
|
|
|
2090
2036
|
fn leechSeed(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2093,33 +2039,32 @@ pub const Effects = struct {
|
|
|
2093
2039
|
if (showdown) {
|
|
2094
2040
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2095
2041
|
if (!foe.active.volatiles.Invulnerable and foe.active.types.includes(.Grass)) {
|
|
2096
|
-
return options.log.immune(battle.active(player.foe()), .None);
|
|
2042
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
2097
2043
|
}
|
|
2098
2044
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2099
2045
|
if (foe.active.volatiles.LeechSeed) return;
|
|
2100
2046
|
} else {
|
|
2101
2047
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2102
2048
|
if (foe.active.types.includes(.Grass) or foe.active.volatiles.LeechSeed) {
|
|
2103
|
-
try options.log.lastmiss();
|
|
2104
|
-
return options.log.miss(battle.active(player));
|
|
2049
|
+
try options.log.lastmiss(.{});
|
|
2050
|
+
return options.log.miss(.{battle.active(player)});
|
|
2105
2051
|
}
|
|
2106
2052
|
}
|
|
2107
2053
|
|
|
2108
2054
|
try options.chance.commit(player, .hit);
|
|
2109
2055
|
foe.active.volatiles.LeechSeed = true;
|
|
2110
2056
|
|
|
2111
|
-
try options.log.start(battle.active(player.foe()), .LeechSeed);
|
|
2057
|
+
try options.log.start(.{ battle.active(player.foe()), .LeechSeed });
|
|
2112
2058
|
}
|
|
2113
2059
|
|
|
2114
2060
|
fn lightScreen(battle: anytype, player: Player, options: anytype) !void {
|
|
2115
2061
|
var side = battle.side(player);
|
|
2062
|
+
const ident = battle.active(player);
|
|
2116
2063
|
|
|
2117
|
-
if (side.active.volatiles.LightScreen) {
|
|
2118
|
-
return options.log.fail(battle.active(player), .None);
|
|
2119
|
-
}
|
|
2064
|
+
if (side.active.volatiles.LightScreen) return options.log.fail(.{ ident, .None });
|
|
2120
2065
|
side.active.volatiles.LightScreen = true;
|
|
2121
2066
|
|
|
2122
|
-
try options.log.start(
|
|
2067
|
+
try options.log.start(.{ ident, .LightScreen });
|
|
2123
2068
|
}
|
|
2124
2069
|
|
|
2125
2070
|
fn mimic(battle: anytype, player: Player, move: Move.Data, mslot: u8, options: anytype) !void {
|
|
@@ -2148,7 +2093,7 @@ pub const Effects = struct {
|
|
|
2148
2093
|
// Invulnerable foes or 1/256 miss can trigger |-miss| instead of |-fail|
|
|
2149
2094
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2150
2095
|
try options.chance.commit(player, .hit);
|
|
2151
|
-
return
|
|
2096
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2152
2097
|
}
|
|
2153
2098
|
}
|
|
2154
2099
|
if (!try checkHit(battle, player, move, options)) return;
|
|
@@ -2157,7 +2102,7 @@ pub const Effects = struct {
|
|
|
2157
2102
|
const rslot = try Rolls.moveSlot(battle, player, &foe.active.moves, 0, options);
|
|
2158
2103
|
side.active.move(oslot).id = foe.active.move(rslot).id;
|
|
2159
2104
|
|
|
2160
|
-
try options.log.
|
|
2105
|
+
try options.log.start(.{ battle.active(player), .Mimic, side.active.move(oslot).id });
|
|
2161
2106
|
}
|
|
2162
2107
|
|
|
2163
2108
|
fn mist(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -2166,7 +2111,7 @@ pub const Effects = struct {
|
|
|
2166
2111
|
if (side.active.volatiles.Mist) return;
|
|
2167
2112
|
side.active.volatiles.Mist = true;
|
|
2168
2113
|
|
|
2169
|
-
try options.log.start(battle.active(player), .Mist);
|
|
2114
|
+
try options.log.start(.{ battle.active(player), .Mist });
|
|
2170
2115
|
}
|
|
2171
2116
|
|
|
2172
2117
|
fn multiHit(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2192,17 +2137,18 @@ pub const Effects = struct {
|
|
|
2192
2137
|
|
|
2193
2138
|
if (showdown) {
|
|
2194
2139
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2195
|
-
if (immune and !foe.active.volatiles.Invulnerable)
|
|
2196
|
-
|
|
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;
|
|
2197
2143
|
}
|
|
2198
2144
|
if (Status.any(foe_stored.status)) {
|
|
2199
|
-
return log.fail(
|
|
2145
|
+
return log.fail(.{
|
|
2200
2146
|
foe_ident,
|
|
2201
|
-
if (Status.is(foe_stored.status, .PAR)) .Paralysis else .None,
|
|
2202
|
-
);
|
|
2147
|
+
if (Status.is(foe_stored.status, .PAR)) Fail.Paralysis else Fail.None,
|
|
2148
|
+
});
|
|
2203
2149
|
}
|
|
2204
2150
|
if (!showdown) {
|
|
2205
|
-
if (immune) return log.immune(foe_ident, .None);
|
|
2151
|
+
if (immune) return log.immune(.{ foe_ident, .None });
|
|
2206
2152
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2207
2153
|
}
|
|
2208
2154
|
try options.chance.commit(player, .hit);
|
|
@@ -2210,7 +2156,7 @@ pub const Effects = struct {
|
|
|
2210
2156
|
foe_stored.status = Status.init(.PAR);
|
|
2211
2157
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2212
2158
|
|
|
2213
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2159
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2214
2160
|
}
|
|
2215
2161
|
|
|
2216
2162
|
fn paralyzeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2232,12 +2178,12 @@ pub const Effects = struct {
|
|
|
2232
2178
|
foe_stored.status = Status.init(.PAR);
|
|
2233
2179
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2234
2180
|
|
|
2235
|
-
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 });
|
|
2236
2182
|
}
|
|
2237
2183
|
|
|
2238
2184
|
fn payDay(battle: anytype, player: Player, options: anytype) !void {
|
|
2239
2185
|
if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
|
|
2240
|
-
try options.log.fieldactivate();
|
|
2186
|
+
try options.log.fieldactivate(.{});
|
|
2241
2187
|
}
|
|
2242
2188
|
}
|
|
2243
2189
|
|
|
@@ -2252,23 +2198,23 @@ pub const Effects = struct {
|
|
|
2252
2198
|
return;
|
|
2253
2199
|
} else if (foe.active.volatiles.Substitute) {
|
|
2254
2200
|
if (move.effect != .Poison) return;
|
|
2255
|
-
return log.fail(foe_ident, .None);
|
|
2201
|
+
return log.fail(.{ foe_ident, .None });
|
|
2256
2202
|
} else if (Status.any(foe_stored.status)) {
|
|
2257
2203
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2258
2204
|
// Pokémon Showdown considers Toxic to be a status even in Generation I and so
|
|
2259
2205
|
// will not include a fail reason for Toxic vs. Poison or vice-versa...
|
|
2260
|
-
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))
|
|
2261
2207
|
if (!showdown)
|
|
2262
|
-
.Poison
|
|
2208
|
+
Fail.Poison
|
|
2263
2209
|
else if (toxic == (foe_stored.status == Status.TOX))
|
|
2264
|
-
if (toxic) .Toxic else .Poison
|
|
2210
|
+
if (toxic) Fail.Toxic else Fail.Poison
|
|
2265
2211
|
else
|
|
2266
|
-
.None
|
|
2212
|
+
Fail.None
|
|
2267
2213
|
else
|
|
2268
|
-
.None);
|
|
2214
|
+
Fail.None });
|
|
2269
2215
|
} else if (foe.active.types.includes(.Poison)) {
|
|
2270
2216
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2271
|
-
return log.immune(foe_ident, .None);
|
|
2217
|
+
return log.immune(.{ foe_ident, .None });
|
|
2272
2218
|
}
|
|
2273
2219
|
|
|
2274
2220
|
if (move.effect == .Poison) {
|
|
@@ -2287,7 +2233,7 @@ pub const Effects = struct {
|
|
|
2287
2233
|
foe.active.volatiles.toxic = 0;
|
|
2288
2234
|
}
|
|
2289
2235
|
|
|
2290
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2236
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2291
2237
|
}
|
|
2292
2238
|
|
|
2293
2239
|
fn rage(battle: anytype, player: Player) !void {
|
|
@@ -2307,22 +2253,23 @@ pub const Effects = struct {
|
|
|
2307
2253
|
@as(u8, if (side.last_selected_move == .Struggle) 2 else 4), 1));
|
|
2308
2254
|
stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
|
|
2309
2255
|
|
|
2310
|
-
try options.log.
|
|
2256
|
+
try options.log.damage(.{
|
|
2311
2257
|
battle.active(player),
|
|
2312
2258
|
stored,
|
|
2313
2259
|
.RecoilOf,
|
|
2314
2260
|
battle.active(player.foe()),
|
|
2315
|
-
);
|
|
2261
|
+
});
|
|
2316
2262
|
if (showdown and stored.hp == 0) residual.* = false;
|
|
2317
2263
|
}
|
|
2318
2264
|
|
|
2319
2265
|
fn reflect(battle: anytype, player: Player, options: anytype) !void {
|
|
2320
2266
|
var side = battle.side(player);
|
|
2267
|
+
const ident = battle.active(player);
|
|
2321
2268
|
|
|
2322
|
-
if (side.active.volatiles.Reflect) return options.log.fail(
|
|
2269
|
+
if (side.active.volatiles.Reflect) return options.log.fail(.{ ident, .None });
|
|
2323
2270
|
side.active.volatiles.Reflect = true;
|
|
2324
2271
|
|
|
2325
|
-
try options.log.start(
|
|
2272
|
+
try options.log.start(.{ ident, .Reflect });
|
|
2326
2273
|
}
|
|
2327
2274
|
|
|
2328
2275
|
fn sleep(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2336,10 +2283,10 @@ pub const Effects = struct {
|
|
|
2336
2283
|
// the Sleep Clause Mod might activate, causing us to not actually bypass
|
|
2337
2284
|
} else {
|
|
2338
2285
|
if (Status.any(foe_stored.status)) {
|
|
2339
|
-
return options.log.fail(
|
|
2286
|
+
return options.log.fail(.{
|
|
2340
2287
|
foe_ident,
|
|
2341
|
-
if (Status.is(foe_stored.status, .SLP)) .Sleep else .None,
|
|
2342
|
-
);
|
|
2288
|
+
if (Status.is(foe_stored.status, .SLP)) Fail.Sleep else Fail.None,
|
|
2289
|
+
});
|
|
2343
2290
|
}
|
|
2344
2291
|
// If checkHit in doMove didn't return true Pokémon Showdown wouldn't be in here
|
|
2345
2292
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
@@ -2356,18 +2303,17 @@ pub const Effects = struct {
|
|
|
2356
2303
|
|
|
2357
2304
|
foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
|
|
2358
2305
|
const last = battle.side(player).last_selected_move;
|
|
2359
|
-
try options.log.
|
|
2306
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .From, last });
|
|
2360
2307
|
}
|
|
2361
2308
|
|
|
2362
2309
|
fn splash(battle: anytype, player: Player, options: anytype) !void {
|
|
2363
|
-
try options.log.activate(battle.active(player), .Splash);
|
|
2310
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
2364
2311
|
}
|
|
2365
2312
|
|
|
2366
2313
|
fn substitute(battle: anytype, player: Player, residual: *bool, options: anytype) !void {
|
|
2367
2314
|
var side = battle.side(player);
|
|
2368
2315
|
if (side.active.volatiles.Substitute) {
|
|
2369
|
-
|
|
2370
|
-
return;
|
|
2316
|
+
return options.log.fail(.{ battle.active(player), .Substitute });
|
|
2371
2317
|
}
|
|
2372
2318
|
|
|
2373
2319
|
assert(side.stored().stats.hp <= 1023);
|
|
@@ -2379,28 +2325,32 @@ pub const Effects = struct {
|
|
|
2379
2325
|
// avoid having to convert to floating point)
|
|
2380
2326
|
const required_hp = if (showdown) @divFloor(side.stored().stats.hp - 1, 4) + 1 else hp;
|
|
2381
2327
|
if (side.stored().hp < required_hp) {
|
|
2382
|
-
|
|
2383
|
-
return;
|
|
2328
|
+
return options.log.fail(.{ battle.active(player), .Weak });
|
|
2384
2329
|
}
|
|
2385
2330
|
|
|
2386
2331
|
// GLITCH: can leave the user with 0 HP (faints later) because didn't check '<=' above
|
|
2387
2332
|
side.stored().hp -= hp;
|
|
2388
2333
|
side.active.volatiles.substitute = hp + 1;
|
|
2389
2334
|
side.active.volatiles.Substitute = true;
|
|
2390
|
-
try options.log.start(battle.active(player), .Substitute);
|
|
2335
|
+
try options.log.start(.{ battle.active(player), .Substitute });
|
|
2391
2336
|
if (hp > 0) {
|
|
2392
|
-
try options.log.damage(battle.active(player), side.stored(), .None);
|
|
2337
|
+
try options.log.damage(.{ battle.active(player), side.stored(), .None });
|
|
2393
2338
|
if (showdown and side.stored().hp == 0) residual.* = false;
|
|
2394
2339
|
}
|
|
2395
2340
|
}
|
|
2396
2341
|
|
|
2397
2342
|
fn switchAndTeleport(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
2398
|
-
if (!showdown or battle.side(player).last_selected_move == .Teleport) return;
|
|
2399
|
-
|
|
2400
2343
|
// Whirlwind/Roar should not roll to hit/reset damage but Pokémon Showdown does anyway
|
|
2401
|
-
if (
|
|
2402
|
-
|
|
2403
|
-
|
|
2344
|
+
if (showdown) {
|
|
2345
|
+
if (battle.side(player).last_selected_move == .Teleport) return;
|
|
2346
|
+
if (try checkHit(battle, player, move, options)) {
|
|
2347
|
+
try options.chance.commit(player, .hit);
|
|
2348
|
+
}
|
|
2349
|
+
battle.last_damage = 0;
|
|
2350
|
+
} else {
|
|
2351
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2352
|
+
try options.log.laststill(.{});
|
|
2353
|
+
}
|
|
2404
2354
|
}
|
|
2405
2355
|
|
|
2406
2356
|
fn thrashing(battle: anytype, player: Player, options: anytype) void {
|
|
@@ -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,22 +2671,22 @@ 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
|
|
2742
2692
|
else if (showdown)
|
|
@@ -2748,7 +2698,7 @@ pub const Rolls = struct {
|
|
|
2748
2698
|
return p1;
|
|
2749
2699
|
}
|
|
2750
2700
|
|
|
2751
|
-
|
|
2701
|
+
fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
|
|
2752
2702
|
const crit = if (options.calc.overridden(player, .critical_hit)) |val|
|
|
2753
2703
|
val == .true
|
|
2754
2704
|
else if (showdown)
|
|
@@ -2760,7 +2710,7 @@ pub const Rolls = struct {
|
|
|
2760
2710
|
return crit;
|
|
2761
2711
|
}
|
|
2762
2712
|
|
|
2763
|
-
|
|
2713
|
+
fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2764
2714
|
const roll = if (options.calc.overridden(player, .damage)) |val|
|
|
2765
2715
|
val
|
|
2766
2716
|
else roll: {
|
|
@@ -2776,7 +2726,7 @@ pub const Rolls = struct {
|
|
|
2776
2726
|
return roll;
|
|
2777
2727
|
}
|
|
2778
2728
|
|
|
2779
|
-
|
|
2729
|
+
fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) bool {
|
|
2780
2730
|
const ok = if (options.calc.overridden(player, .hit)) |val|
|
|
2781
2731
|
val == .true
|
|
2782
2732
|
else if (showdown)
|
|
@@ -2788,7 +2738,7 @@ pub const Rolls = struct {
|
|
|
2788
2738
|
return ok;
|
|
2789
2739
|
}
|
|
2790
2740
|
|
|
2791
|
-
|
|
2741
|
+
fn confused(battle: anytype, player: Player, options: anytype) !bool {
|
|
2792
2742
|
const cfz = if (options.calc.overridden(player, .confused)) |val|
|
|
2793
2743
|
val == .true
|
|
2794
2744
|
else if (showdown)
|
|
@@ -2800,7 +2750,7 @@ pub const Rolls = struct {
|
|
|
2800
2750
|
return cfz;
|
|
2801
2751
|
}
|
|
2802
2752
|
|
|
2803
|
-
|
|
2753
|
+
fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
|
|
2804
2754
|
const par = if (options.calc.overridden(player, .paralyzed)) |val|
|
|
2805
2755
|
val == .true
|
|
2806
2756
|
else if (showdown)
|
|
@@ -2812,7 +2762,7 @@ pub const Rolls = struct {
|
|
|
2812
2762
|
return par;
|
|
2813
2763
|
}
|
|
2814
2764
|
|
|
2815
|
-
|
|
2765
|
+
fn confusionChance(battle: anytype, player: Player, options: anytype) !bool {
|
|
2816
2766
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2817
2767
|
val == .true
|
|
2818
2768
|
else if (showdown)
|
|
@@ -2824,7 +2774,7 @@ pub const Rolls = struct {
|
|
|
2824
2774
|
return proc;
|
|
2825
2775
|
}
|
|
2826
2776
|
|
|
2827
|
-
|
|
2777
|
+
fn secondaryChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2828
2778
|
const rate: u8 = if (low) 26 else 77;
|
|
2829
2779
|
|
|
2830
2780
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
@@ -2838,7 +2788,7 @@ pub const Rolls = struct {
|
|
|
2838
2788
|
return proc;
|
|
2839
2789
|
}
|
|
2840
2790
|
|
|
2841
|
-
|
|
2791
|
+
fn poisonChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2842
2792
|
const rate: u8 = if (low) 52 else 103;
|
|
2843
2793
|
|
|
2844
2794
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
@@ -2852,7 +2802,7 @@ pub const Rolls = struct {
|
|
|
2852
2802
|
return proc;
|
|
2853
2803
|
}
|
|
2854
2804
|
|
|
2855
|
-
|
|
2805
|
+
fn unboost(battle: anytype, player: Player, options: anytype) !bool {
|
|
2856
2806
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2857
2807
|
val == .true
|
|
2858
2808
|
else if (showdown)
|
|
@@ -2864,14 +2814,12 @@ pub const Rolls = struct {
|
|
|
2864
2814
|
return proc;
|
|
2865
2815
|
}
|
|
2866
2816
|
|
|
2867
|
-
|
|
2817
|
+
fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
2868
2818
|
const move: Move = if (options.calc.overridden(player, .metronome)) |val|
|
|
2869
2819
|
val
|
|
2870
|
-
else if (showdown)
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
break :move @enumFromInt(r + mod);
|
|
2874
|
-
} else move: {
|
|
2820
|
+
else if (showdown)
|
|
2821
|
+
Move.METRONOME[battle.rng.range(u8, 0, Move.METRONOME.len)]
|
|
2822
|
+
else move: {
|
|
2875
2823
|
while (true) {
|
|
2876
2824
|
const r = battle.rng.next();
|
|
2877
2825
|
if (r == 0 or r == @intFromEnum(Move.Metronome)) continue;
|
|
@@ -2884,7 +2832,7 @@ pub const Rolls = struct {
|
|
|
2884
2832
|
return move;
|
|
2885
2833
|
}
|
|
2886
2834
|
|
|
2887
|
-
|
|
2835
|
+
fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
|
|
2888
2836
|
const power = if (options.calc.overridden(player, .psywave)) |val|
|
|
2889
2837
|
val - 1
|
|
2890
2838
|
else if (showdown)
|
|
@@ -2901,11 +2849,11 @@ pub const Rolls = struct {
|
|
|
2901
2849
|
return power;
|
|
2902
2850
|
}
|
|
2903
2851
|
|
|
2904
|
-
|
|
2852
|
+
fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2905
2853
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2906
2854
|
@intCast(val)
|
|
2907
2855
|
else if (showdown)
|
|
2908
|
-
|
|
2856
|
+
battle.rng.range(u3, 1, 8)
|
|
2909
2857
|
else duration: {
|
|
2910
2858
|
while (true) {
|
|
2911
2859
|
const r = battle.rng.next() & 7;
|
|
@@ -2918,11 +2866,11 @@ pub const Rolls = struct {
|
|
|
2918
2866
|
return duration;
|
|
2919
2867
|
}
|
|
2920
2868
|
|
|
2921
|
-
|
|
2869
|
+
fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
|
|
2922
2870
|
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2923
2871
|
val
|
|
2924
2872
|
else if (showdown)
|
|
2925
|
-
|
|
2873
|
+
battle.rng.range(u4, 1, 9)
|
|
2926
2874
|
else
|
|
2927
2875
|
@intCast((battle.rng.next() & 7) + 1);
|
|
2928
2876
|
|
|
@@ -2931,11 +2879,11 @@ pub const Rolls = struct {
|
|
|
2931
2879
|
return duration;
|
|
2932
2880
|
}
|
|
2933
2881
|
|
|
2934
|
-
|
|
2882
|
+
fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
|
|
2935
2883
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2936
2884
|
@intCast(val)
|
|
2937
2885
|
else if (showdown)
|
|
2938
|
-
|
|
2886
|
+
battle.rng.range(u3, 2, 6)
|
|
2939
2887
|
else
|
|
2940
2888
|
@intCast((battle.rng.next() & 3) + 2);
|
|
2941
2889
|
|
|
@@ -2944,11 +2892,11 @@ pub const Rolls = struct {
|
|
|
2944
2892
|
return duration;
|
|
2945
2893
|
}
|
|
2946
2894
|
|
|
2947
|
-
|
|
2895
|
+
fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2948
2896
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2949
2897
|
@intCast(val)
|
|
2950
2898
|
else if (showdown)
|
|
2951
|
-
|
|
2899
|
+
battle.rng.range(u3, 2, 4)
|
|
2952
2900
|
else
|
|
2953
2901
|
@intCast((battle.rng.next() & 1) + 2);
|
|
2954
2902
|
|
|
@@ -2957,7 +2905,9 @@ pub const Rolls = struct {
|
|
|
2957
2905
|
return duration;
|
|
2958
2906
|
}
|
|
2959
2907
|
|
|
2960
|
-
|
|
2908
|
+
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
2909
|
+
|
|
2910
|
+
fn distribution(
|
|
2961
2911
|
battle: anytype,
|
|
2962
2912
|
comptime effect: Move.Effect,
|
|
2963
2913
|
player: Player,
|
|
@@ -2967,7 +2917,7 @@ pub const Rolls = struct {
|
|
|
2967
2917
|
const n: u3 = if (options.calc.overridden(player, roll)) |val|
|
|
2968
2918
|
@intCast(val)
|
|
2969
2919
|
else if (showdown)
|
|
2970
|
-
DISTRIBUTION[battle.rng.range(
|
|
2920
|
+
DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
|
|
2971
2921
|
else n: {
|
|
2972
2922
|
const r = (battle.rng.next() & 3);
|
|
2973
2923
|
break :n @intCast((if (r < 2) r else battle.rng.next() & 3) + 2);
|
|
@@ -3014,10 +2964,9 @@ pub const Rolls = struct {
|
|
|
3014
2964
|
while (i < moves.len and r > 0) : (i += 1) {
|
|
3015
2965
|
if (moves[i].pp > 0) {
|
|
3016
2966
|
r -= 1;
|
|
3017
|
-
if (r == 0) break;
|
|
2967
|
+
if (r == 0) break :slot @intCast(i + 1);
|
|
3018
2968
|
}
|
|
3019
2969
|
}
|
|
3020
|
-
break :slot @intCast(i + 1);
|
|
3021
2970
|
}
|
|
3022
2971
|
}
|
|
3023
2972
|
|
|
@@ -3039,11 +2988,11 @@ test "RNG agreement" {
|
|
|
3039
2988
|
expected[i] = @intCast(i * 0x1000000);
|
|
3040
2989
|
}
|
|
3041
2990
|
|
|
3042
|
-
var spe
|
|
3043
|
-
var cfz
|
|
3044
|
-
var par
|
|
3045
|
-
var brn
|
|
3046
|
-
var eff
|
|
2991
|
+
var spe: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
2992
|
+
var cfz: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
2993
|
+
var par: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
2994
|
+
var brn: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
2995
|
+
var eff: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3047
2996
|
|
|
3048
2997
|
for (0..expected.len) |i| {
|
|
3049
2998
|
try expectEqual(spe.range(u8, 0, 2) == 0, i < Gen12.percent(50) + 1);
|
|
@@ -3079,7 +3028,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3079
3028
|
const side = battle.side(player);
|
|
3080
3029
|
const foe = battle.foe(player);
|
|
3081
3030
|
|
|
3082
|
-
|
|
3031
|
+
const active = &side.active;
|
|
3083
3032
|
const stored = side.stored();
|
|
3084
3033
|
|
|
3085
3034
|
// While players are not given any input options on the cartridge in these cases,
|
|
@@ -3133,7 +3082,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3133
3082
|
if (m.id == if (active.volatiles.Bide) .Bide else side.last_selected_move) {
|
|
3134
3083
|
// Pokémon Showdown displays Struggle if limited to Bide but unable to pick
|
|
3135
3084
|
const struggle =
|
|
3136
|
-
m.id == .Bide and (m.pp == 0 or active.volatiles.
|
|
3085
|
+
m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
|
|
3137
3086
|
const s = if (struggle) 0 else slot;
|
|
3138
3087
|
out[n] = .{ .type = .Move, .data = s };
|
|
3139
3088
|
n += 1;
|
|
@@ -3148,7 +3097,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3148
3097
|
const m = active.moves[slot - 1];
|
|
3149
3098
|
if (m.id == .None) break;
|
|
3150
3099
|
if (m.pp == 0) continue;
|
|
3151
|
-
if (active.volatiles.
|
|
3100
|
+
if (active.volatiles.disable_move == slot) continue;
|
|
3152
3101
|
out[n] = .{ .type = .Move, .data = slot };
|
|
3153
3102
|
n += 1;
|
|
3154
3103
|
}
|