@pkmn/engine 0.1.0-dev.6158d5ad → 0.1.0-dev.62399ed5
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 +44 -19
- package/build/pkg/addon.js +6 -7
- 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 +4 -4
- 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 +61 -42
- package/build/pkg/protocol.js.map +1 -1
- package/build/test/display.js +2 -2
- package/build/test/display.js.map +1 -1
- package/build/tools/debug.js +5 -4
- package/build/tools/debug.js.map +1 -1
- package/build.zig +102 -104
- package/package.json +43 -23
- package/src/bin/install-pkmn-engine +26 -18
- package/src/lib/common/data.zig +12 -24
- package/src/lib/common/debug.zig +2 -2
- package/src/lib/common/optional.zig +9 -2
- package/src/lib/common/options.zig +2 -0
- package/src/lib/common/protocol.zig +802 -327
- package/src/lib/common/rational.zig +113 -19
- package/src/lib/common/rng.zig +26 -17
- package/src/lib/gen1/calc.zig +219 -50
- package/src/lib/gen1/chance.zig +140 -125
- package/src/lib/gen1/data/moves.zig +192 -180
- package/src/lib/gen1/data/species.zig +3 -3
- package/src/lib/gen1/data/types.zig +7 -7
- package/src/lib/gen1/data.zig +50 -62
- package/src/lib/gen1/helpers.zig +148 -82
- package/src/lib/gen1/mechanics.zig +403 -417
- package/src/lib/gen2/calc.zig +143 -0
- package/src/lib/gen2/chance.zig +1315 -0
- package/src/lib/gen2/data/items.zig +12 -18
- package/src/lib/gen2/data/moves.zig +663 -569
- package/src/lib/gen2/data/species.zig +1 -1
- package/src/lib/gen2/data/types.zig +134 -31
- package/src/lib/gen2/data.zig +106 -79
- package/src/lib/gen2/generated.zig +806 -0
- package/src/lib/gen2/helpers.zig +45 -19
- package/src/lib/gen2/mechanics.zig +3587 -9
- package/src/lib/{binding/node.zig → node.zig} +10 -13
- package/src/lib/pkmn.zig +27 -12
- package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
- package/src/test/display/pkmn.html.tmpl +5 -5
- package/src/lib/gen1/README.md +0 -892
- package/src/lib/gen2/README.md +0 -308
|
@@ -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,15 +160,13 @@ 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
|
|
181
167
|
// set the selected move will not actually be used, it will just be reported as having
|
|
182
168
|
// been used (this differs from how Pokémon Showdown works, but its impossible to
|
|
183
|
-
// replicate the incorrect behavior with the correct mechanisms)
|
|
169
|
+
// replicate the incorrect behavior with the correct mechanisms)
|
|
184
170
|
saveMove(battle, player, choice);
|
|
185
171
|
} else {
|
|
186
172
|
assert(choice.data == 0);
|
|
@@ -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,16 +342,15 @@ 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;
|
|
368
348
|
if (!replace) {
|
|
369
349
|
if (player_choice.type != .Switch) {
|
|
370
|
-
if (try checkFaint(battle, foe_player, options)) |r| return r;
|
|
350
|
+
if (try checkFaint(battle, foe_player, true, options)) |r| return r;
|
|
371
351
|
}
|
|
372
352
|
if (residual) try handleResidual(battle, player, options);
|
|
373
|
-
if (try checkFaint(battle, player, options)) |r| return r;
|
|
353
|
+
if (try checkFaint(battle, player, false, options)) |r| return r;
|
|
374
354
|
} else if (foe_choice.type == .Pass) return null;
|
|
375
355
|
|
|
376
356
|
options.chance.clearPending();
|
|
@@ -384,16 +364,15 @@ 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;
|
|
391
370
|
if (!replace) {
|
|
392
371
|
if (!calc and foe_choice.type != .Switch) {
|
|
393
|
-
if (try checkFaint(battle, player, options)) |r| return r;
|
|
372
|
+
if (try checkFaint(battle, player, true, options)) |r| return r;
|
|
394
373
|
}
|
|
395
374
|
if (residual) try handleResidual(battle, foe_player, options);
|
|
396
|
-
if (try checkFaint(battle, foe_player, options)) |r| return r;
|
|
375
|
+
if (try checkFaint(battle, foe_player, false, options)) |r| return r;
|
|
397
376
|
}
|
|
398
377
|
|
|
399
378
|
// Flinch is bugged on Pokémon Showdown because it gets implemented with a duration which causes
|
|
@@ -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,11 +425,13 @@ 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
|
-
// index 1 and the current Pokémon has the same move in its first slot
|
|
434
|
+
// index 1 and the current Pokémon has the same move in its first slot
|
|
455
435
|
if (!auto) {
|
|
456
436
|
// side.active.moves(slot) is safe to check even though the slot in question might not
|
|
457
437
|
// technically be from this Pokémon because it must be exactly 1 to not desync and
|
|
@@ -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
|
|
|
@@ -895,7 +852,7 @@ fn doMove(
|
|
|
895
852
|
break :miss true;
|
|
896
853
|
}
|
|
897
854
|
break :miss (move.target != .Self and
|
|
898
|
-
!moveHit(battle, player, move, &immune, &mist, options));
|
|
855
|
+
!try moveHit(battle, player, move, &immune, &mist, options));
|
|
899
856
|
};
|
|
900
857
|
assert(!immune or miss or (showdown and move.effect == .Binding));
|
|
901
858
|
|
|
@@ -938,11 +895,11 @@ 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
|
|
945
|
-
(!moveHit(battle, player, move, &immune, &mist, options) or zero);
|
|
902
|
+
(!try moveHit(battle, player, move, &immune, &mist, options) or zero);
|
|
946
903
|
|
|
947
904
|
assert(showdown or miss or battle.last_damage > 0 or skip);
|
|
948
905
|
assert((!showdown and miss) or !(ohko and immune));
|
|
@@ -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
|
-
if (showdown
|
|
977
|
-
try log.lastmiss();
|
|
978
|
-
try log.miss(battle.active(player));
|
|
933
|
+
if (!showdown and !zero) try options.chance.commit(player, .miss);
|
|
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
|
|
@@ -992,13 +949,13 @@ fn doMove(
|
|
|
992
949
|
try options.chance.commit(player, .binding);
|
|
993
950
|
}
|
|
994
951
|
return null;
|
|
995
|
-
} else {
|
|
952
|
+
} else if (!showdown) {
|
|
996
953
|
try options.chance.commit(player, .hit);
|
|
997
954
|
}
|
|
998
955
|
|
|
999
956
|
// On the cartridge MultiHit doesn't get set up until after damage has been applied for the
|
|
1000
957
|
// first time but its more convenient and efficient to set it up here (Pokémon Showdown sets
|
|
1001
|
-
// it up above before damage calculation)
|
|
958
|
+
// it up above before damage calculation)
|
|
1002
959
|
if (!showdown and move.effect.isMulti()) {
|
|
1003
960
|
try Effects.multiHit(battle, player, move, options);
|
|
1004
961
|
hits = side.active.volatiles.attacks;
|
|
@@ -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;
|
|
@@ -1057,12 +1014,12 @@ fn doMove(
|
|
|
1057
1014
|
}
|
|
1058
1015
|
|
|
1059
1016
|
// Substitute being broken nullifies the move's effect completely so even
|
|
1060
|
-
// if an effect was intended to "always happen" it will still get skipped
|
|
1017
|
+
// if an effect was intended to "always happen" it will still get skipped
|
|
1061
1018
|
if (nullified) return null;
|
|
1062
1019
|
|
|
1063
1020
|
// On the cartridge, "always happen" effect handlers are called in the applyDamage loop above,
|
|
1064
1021
|
// but this is only done to setup the MultiHit looping in the first place. Moving the MultiHit
|
|
1065
|
-
// setup before the loop means we can avoid having to waste time doing no-op handler searches
|
|
1022
|
+
// setup before the loop means we can avoid having to waste time doing no-op handler searches
|
|
1066
1023
|
if (move.effect.alwaysHappens()) try alwaysHappens(battle, player, move, residual, options);
|
|
1067
1024
|
|
|
1068
1025
|
if (foe.stored().hp == 0) return null;
|
|
@@ -1077,7 +1034,7 @@ fn doMove(
|
|
|
1077
1034
|
// below). For Twineedle we change the data to that of one with PoisonChance1 given its
|
|
1078
1035
|
// MultiHit behavior is complete after the loop above, though Pokémon Showdown handles the
|
|
1079
1036
|
// Twineedle secondary effect in the MultiHit cleanup block above because it incorrectly
|
|
1080
|
-
// puts the |-status| message before |-hitcount| instead of after
|
|
1037
|
+
// puts the |-status| message before |-hitcount| instead of after
|
|
1081
1038
|
if (move.effect == .Twineedle) {
|
|
1082
1039
|
if (!showdown) try Effects.poison(battle, player, Move.get(.PoisonSting), options);
|
|
1083
1040
|
} else if (move.effect == .Disable) {
|
|
@@ -1096,12 +1053,14 @@ fn checkCriticalHit(battle: anytype, player: Player, move: Move.Data, options: a
|
|
|
1096
1053
|
const side = battle.side(player);
|
|
1097
1054
|
|
|
1098
1055
|
// Base speed is used for the critical hit calculation, even when Transform-ed
|
|
1099
|
-
var rate =
|
|
1056
|
+
var rate: u16 = Species.chance(side.stored().species);
|
|
1100
1057
|
// GLITCH: Focus Energy reduces critical hit rate instead of increasing it
|
|
1101
1058
|
rate = if (side.active.volatiles.FocusEnergy) rate / 2 else @min(rate * 2, 255);
|
|
1102
1059
|
rate = if (move.effect == .HighCritical) @min(rate * 4, 255) else rate / 2;
|
|
1103
1060
|
|
|
1104
|
-
|
|
1061
|
+
const spurious =
|
|
1062
|
+
!showdown and ((move.bp == 0 and move.effect != .OHKO) or move.target == .Depends);
|
|
1063
|
+
return Rolls.criticalHit(battle, player, @intCast(rate), spurious, options);
|
|
1105
1064
|
}
|
|
1106
1065
|
|
|
1107
1066
|
fn calcDamage(
|
|
@@ -1153,9 +1112,9 @@ fn calcDamage(
|
|
|
1153
1112
|
def = @max((def / 4) & 255, if (showdown) 1 else 0);
|
|
1154
1113
|
}
|
|
1155
1114
|
|
|
1156
|
-
const lvl =
|
|
1115
|
+
const lvl: u32 = side.stored().level * @as(u2, if (crit) 2 else 1);
|
|
1157
1116
|
|
|
1158
|
-
def =
|
|
1117
|
+
def = if (move.effect == .Explode) @max(def / 2, 1) else def;
|
|
1159
1118
|
|
|
1160
1119
|
if (def == 0) return false;
|
|
1161
1120
|
|
|
@@ -1216,7 +1175,6 @@ fn specialDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1216
1175
|
const foe = battle.foe(player);
|
|
1217
1176
|
|
|
1218
1177
|
if (!try checkHit(battle, player, move, options)) return null;
|
|
1219
|
-
try options.chance.commit(player, .hit);
|
|
1220
1178
|
|
|
1221
1179
|
battle.last_damage = switch (side.last_selected_move) {
|
|
1222
1180
|
.SuperFang => @max(foe.stored().hp / 2, 1),
|
|
@@ -1246,7 +1204,7 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1246
1204
|
const foe = battle.foe(player);
|
|
1247
1205
|
|
|
1248
1206
|
if (battle.last_damage == 0) {
|
|
1249
|
-
try options.log.fail(battle.active(player), .None);
|
|
1207
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1250
1208
|
return null;
|
|
1251
1209
|
}
|
|
1252
1210
|
|
|
@@ -1264,22 +1222,21 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1264
1222
|
foe_last_selected_move.type == .Fighting);
|
|
1265
1223
|
|
|
1266
1224
|
if (!used and !selected) {
|
|
1267
|
-
try options.log.fail(battle.active(player), .None);
|
|
1225
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1268
1226
|
return null;
|
|
1269
1227
|
}
|
|
1270
1228
|
|
|
1271
1229
|
if (!used or !selected) {
|
|
1272
1230
|
// GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
|
|
1273
1231
|
if (!showdown) return Result.Error;
|
|
1274
|
-
try options.log.fail(battle.active(player), .None);
|
|
1232
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1275
1233
|
return null;
|
|
1276
1234
|
}
|
|
1277
1235
|
|
|
1278
1236
|
battle.last_damage *|= 2;
|
|
1279
1237
|
|
|
1280
|
-
// Pokémon Showdown calls
|
|
1238
|
+
// Pokémon Showdown calls moveHit before Counter
|
|
1281
1239
|
if (!showdown and !try checkHit(battle, player, move, options)) return null;
|
|
1282
|
-
try options.chance.commit(player, .hit);
|
|
1283
1240
|
|
|
1284
1241
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
1285
1242
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -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);
|
|
1311
|
-
options.calc.capped(
|
|
1267
|
+
try options.log.end(.{ battle.active(sub_player), .Substitute });
|
|
1268
|
+
options.calc.capped(sub_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);
|
|
@@ -1375,20 +1332,23 @@ fn checkHit(battle: anytype, player: Player, move: Move.Data, options: anytype)
|
|
|
1375
1332
|
var immune = false;
|
|
1376
1333
|
var mist = false;
|
|
1377
1334
|
|
|
1378
|
-
if (moveHit(battle, player, move, &immune, &mist, options))
|
|
1335
|
+
if (try moveHit(battle, player, move, &immune, &mist, options)) {
|
|
1336
|
+
if (!showdown) try options.chance.commit(player, .hit);
|
|
1337
|
+
return true;
|
|
1338
|
+
}
|
|
1379
1339
|
|
|
1380
1340
|
assert(!immune);
|
|
1381
1341
|
if (mist) {
|
|
1382
1342
|
assert(!showdown);
|
|
1383
1343
|
const foe_ident = battle.active(player.foe());
|
|
1384
|
-
try options.log.activate(foe_ident, .Mist);
|
|
1385
|
-
try options.log.fail(foe_ident, .None);
|
|
1344
|
+
try options.log.activate(.{ foe_ident, .Mist });
|
|
1345
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1386
1346
|
} else {
|
|
1387
|
-
try options.
|
|
1388
|
-
try options.log.
|
|
1389
|
-
try options.log.miss(battle.active(player));
|
|
1347
|
+
try options.log.lastmiss(.{});
|
|
1348
|
+
try options.log.miss(.{battle.active(player)});
|
|
1390
1349
|
}
|
|
1391
1350
|
|
|
1351
|
+
if (!showdown) try options.chance.commit(player, .miss);
|
|
1392
1352
|
return false;
|
|
1393
1353
|
}
|
|
1394
1354
|
|
|
@@ -1399,7 +1359,7 @@ fn moveHit(
|
|
|
1399
1359
|
immune: *bool,
|
|
1400
1360
|
mist: *bool,
|
|
1401
1361
|
options: anytype,
|
|
1402
|
-
) bool {
|
|
1362
|
+
) !bool {
|
|
1403
1363
|
var side = battle.side(player);
|
|
1404
1364
|
const foe = battle.foe(player);
|
|
1405
1365
|
|
|
@@ -1438,7 +1398,7 @@ fn moveHit(
|
|
|
1438
1398
|
const overwritten = overwrite and state > 0;
|
|
1439
1399
|
assert(!overwritten or (0 < state and state <= 255 and !side.active.volatiles.Bide));
|
|
1440
1400
|
|
|
1441
|
-
var accuracy = if (overwritten) state else
|
|
1401
|
+
var accuracy: u16 = if (overwritten) state else move.accuracy;
|
|
1442
1402
|
var boost = BOOSTS[@as(u4, @intCast(@as(i8, side.active.boosts.accuracy) + 6))];
|
|
1443
1403
|
accuracy = accuracy * boost[0] / boost[1];
|
|
1444
1404
|
boost = BOOSTS[@as(u4, @intCast(@as(i8, -foe.active.boosts.evasion) + 6))];
|
|
@@ -1450,7 +1410,7 @@ fn moveHit(
|
|
|
1450
1410
|
if (overwrite) side.active.volatiles.state = accuracy;
|
|
1451
1411
|
|
|
1452
1412
|
// GLITCH: max accuracy is 255 so 1/256 chance of miss
|
|
1453
|
-
break :miss !Rolls.hit(battle, player, @intCast(accuracy), options);
|
|
1413
|
+
break :miss !try Rolls.hit(battle, player, @intCast(accuracy), options);
|
|
1454
1414
|
};
|
|
1455
1415
|
|
|
1456
1416
|
// Pokémon Showdown reports miss instead of fail for moves blocked by Mist that 1/256 miss
|
|
@@ -1468,6 +1428,7 @@ fn moveHit(
|
|
|
1468
1428
|
fn checkFaint(
|
|
1469
1429
|
battle: anytype,
|
|
1470
1430
|
player: Player,
|
|
1431
|
+
cap: bool,
|
|
1471
1432
|
options: anytype,
|
|
1472
1433
|
) @TypeOf(options.log).Error!?Result {
|
|
1473
1434
|
const side = battle.side(player);
|
|
@@ -1477,33 +1438,32 @@ fn checkFaint(
|
|
|
1477
1438
|
const foe_fainted = foe.stored().hp == 0;
|
|
1478
1439
|
|
|
1479
1440
|
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;
|
|
1441
|
+
const foe_out = foe_fainted and findFirstAlive(foe) == 0;
|
|
1442
|
+
const more = player_out or foe_out;
|
|
1483
1443
|
|
|
1484
|
-
if (try faint(battle, player, !(more or foe_fainted), options)) |r| return r;
|
|
1485
|
-
if (foe_fainted) if (try faint(battle, player.foe(), !more, options)) |r| return r;
|
|
1444
|
+
if (try faint(battle, player, !(more or foe_fainted), cap, options)) |r| return r;
|
|
1445
|
+
if (foe_fainted) if (try faint(battle, player.foe(), !more, false, options)) |r| return r;
|
|
1486
1446
|
|
|
1487
1447
|
assert(!side.active.volatiles.MultiHit);
|
|
1488
1448
|
assert(!foe.active.volatiles.MultiHit);
|
|
1489
1449
|
|
|
1490
|
-
if (
|
|
1491
|
-
try options.log.tie();
|
|
1450
|
+
if (player_out and foe_out) {
|
|
1451
|
+
try options.log.tie(.{});
|
|
1492
1452
|
return Result.Tie;
|
|
1493
1453
|
} else if (player_out) {
|
|
1494
|
-
try options.log.win(player.foe());
|
|
1454
|
+
try options.log.win(.{player.foe()});
|
|
1495
1455
|
return if (player == .P1) Result.Lose else Result.Win;
|
|
1496
1456
|
} else if (foe_out) {
|
|
1497
|
-
try options.log.win(player);
|
|
1457
|
+
try options.log.win(.{player});
|
|
1498
1458
|
return if (player == .P1) Result.Win else Result.Lose;
|
|
1499
1459
|
}
|
|
1500
1460
|
|
|
1501
1461
|
const foe_choice: Choice.Type = if (foe_fainted) .Switch else .Pass;
|
|
1502
|
-
if (player == .P1) return
|
|
1503
|
-
return
|
|
1462
|
+
if (player == .P1) return .{ .p1 = .Switch, .p2 = foe_choice };
|
|
1463
|
+
return .{ .p1 = foe_choice, .p2 = .Switch };
|
|
1504
1464
|
}
|
|
1505
1465
|
|
|
1506
|
-
fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result {
|
|
1466
|
+
fn faint(battle: anytype, player: Player, done: bool, cap: bool, options: anytype) !?Result {
|
|
1507
1467
|
var side = battle.side(player);
|
|
1508
1468
|
var foe = battle.foe(player);
|
|
1509
1469
|
assert(side.stored().hp == 0);
|
|
@@ -1536,8 +1496,10 @@ fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result
|
|
|
1536
1496
|
side.stored().stats.spe;
|
|
1537
1497
|
}
|
|
1538
1498
|
|
|
1539
|
-
try options.log.faint(battle.active(player), done);
|
|
1540
|
-
|
|
1499
|
+
try options.log.faint(.{ battle.active(player), done });
|
|
1500
|
+
// We can only safely set capped if the faint occured as the direct result of an opponent's
|
|
1501
|
+
// move (as opposed to due to residual damage, recoil, Explosion, etc)
|
|
1502
|
+
if (cap) options.calc.capped(player);
|
|
1541
1503
|
return null;
|
|
1542
1504
|
}
|
|
1543
1505
|
|
|
@@ -1548,21 +1510,19 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1548
1510
|
var volatiles = &side.active.volatiles;
|
|
1549
1511
|
|
|
1550
1512
|
const brn = Status.is(stored.status, .BRN);
|
|
1551
|
-
if (brn or Status.is(stored.status, .PSN))
|
|
1513
|
+
if (brn or Status.is(stored.status, .PSN)) {
|
|
1552
1514
|
var damage = @max(stored.stats.hp / 16, 1);
|
|
1553
|
-
|
|
1554
1515
|
if (volatiles.Toxic) {
|
|
1555
1516
|
volatiles.toxic += 1;
|
|
1556
1517
|
damage *= volatiles.toxic;
|
|
1557
1518
|
}
|
|
1519
|
+
damage = @min(damage, stored.hp);
|
|
1558
1520
|
|
|
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);
|
|
1521
|
+
if (!showdown or damage > 0) {
|
|
1522
|
+
stored.hp -= damage;
|
|
1523
|
+
// Pokémon Showdown uses damage [of] here but its not relevant in Generation I
|
|
1524
|
+
try options.log.damage(.{ ident, stored, if (brn) Damage.Burn else Damage.Poison });
|
|
1525
|
+
}
|
|
1566
1526
|
}
|
|
1567
1527
|
|
|
1568
1528
|
if (volatiles.LeechSeed) {
|
|
@@ -1586,14 +1546,14 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1586
1546
|
const amount = @min(damage, stored.hp);
|
|
1587
1547
|
stored.hp -= amount;
|
|
1588
1548
|
|
|
1589
|
-
// As above, Pokémon Showdown uses
|
|
1590
|
-
if (amount > 0) try options.log.damage(ident, stored, .LeechSeed);
|
|
1549
|
+
// As above, Pokémon Showdown uses damage [of] but its not relevant
|
|
1550
|
+
if (amount > 0) try options.log.damage(.{ ident, stored, .LeechSeed });
|
|
1591
1551
|
|
|
1592
1552
|
const before = foe_stored.hp;
|
|
1593
1553
|
// Uncapped damage is added back to the foe
|
|
1594
1554
|
foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
|
|
1595
1555
|
// 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);
|
|
1556
|
+
if (foe_stored.hp > before) try options.log.heal(.{ foe_ident, foe_stored, .Silent });
|
|
1597
1557
|
}
|
|
1598
1558
|
}
|
|
1599
1559
|
|
|
@@ -1604,18 +1564,18 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1604
1564
|
battle.turn += 1;
|
|
1605
1565
|
|
|
1606
1566
|
if (showdown and pkmn.options.ebc and checkEBC(battle)) {
|
|
1607
|
-
try options.log.tie();
|
|
1567
|
+
try options.log.tie(.{});
|
|
1608
1568
|
return Result.Tie;
|
|
1609
1569
|
}
|
|
1610
1570
|
|
|
1611
1571
|
if (showdown and battle.turn >= 1000) {
|
|
1612
|
-
try options.log.tie();
|
|
1572
|
+
try options.log.tie(.{});
|
|
1613
1573
|
return Result.Tie;
|
|
1614
1574
|
} else if (battle.turn >= 65535) {
|
|
1615
1575
|
return Result.Error;
|
|
1616
1576
|
}
|
|
1617
1577
|
|
|
1618
|
-
try options.log.turn(battle.turn);
|
|
1578
|
+
try options.log.turn(.{battle.turn});
|
|
1619
1579
|
|
|
1620
1580
|
return Result.Default;
|
|
1621
1581
|
}
|
|
@@ -1675,9 +1635,9 @@ fn checkEBC(battle: anytype) bool {
|
|
|
1675
1635
|
}
|
|
1676
1636
|
|
|
1677
1637
|
fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
1678
|
-
if (side.active.volatiles.
|
|
1638
|
+
if (side.active.volatiles.disable_move != 0) {
|
|
1679
1639
|
// A Pokémon that transforms after being disabled may end up with less move slots
|
|
1680
|
-
const m = side.active.moves[side.active.volatiles.
|
|
1640
|
+
const m = side.active.moves[side.active.volatiles.disable_move - 1].id;
|
|
1681
1641
|
// side.last_selected_move can be Struggle here on Pokemon Showdown we need an extra check
|
|
1682
1642
|
const last = if (showdown and m == .Bide and side.active.volatiles.Bide)
|
|
1683
1643
|
m
|
|
@@ -1685,14 +1645,14 @@ fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
|
1685
1645
|
side.last_selected_move;
|
|
1686
1646
|
if (m != .None and m == last) {
|
|
1687
1647
|
side.active.volatiles.Charging = false;
|
|
1688
|
-
try options.log.
|
|
1648
|
+
try options.log.cant(.{ ident, .Disable, last });
|
|
1689
1649
|
return true;
|
|
1690
1650
|
}
|
|
1691
1651
|
}
|
|
1692
1652
|
return false;
|
|
1693
1653
|
}
|
|
1694
1654
|
|
|
1695
|
-
|
|
1655
|
+
fn buildRage(battle: anytype, who: Player, options: anytype) !void {
|
|
1696
1656
|
const side = battle.side(who);
|
|
1697
1657
|
if (side.active.volatiles.Rage and side.active.boosts.atk < 6) {
|
|
1698
1658
|
try Effects.boost(battle, who, Move.get(.Rage), options);
|
|
@@ -1710,7 +1670,7 @@ fn handleThrashing(battle: anytype, active: *ActivePokemon, player: Player, opti
|
|
|
1710
1670
|
return true;
|
|
1711
1671
|
}
|
|
1712
1672
|
|
|
1713
|
-
|
|
1673
|
+
fn onBegin(
|
|
1714
1674
|
battle: anytype,
|
|
1715
1675
|
player: Player,
|
|
1716
1676
|
move: Move.Data,
|
|
@@ -1718,7 +1678,6 @@ inline fn onBegin(
|
|
|
1718
1678
|
residual: *bool,
|
|
1719
1679
|
options: anytype,
|
|
1720
1680
|
) !void {
|
|
1721
|
-
assert(move.effect.onBegin());
|
|
1722
1681
|
return switch (move.effect) {
|
|
1723
1682
|
.Confusion => Effects.confusion(battle, player, move, options),
|
|
1724
1683
|
.Conversion => Effects.conversion(battle, player, options),
|
|
@@ -1740,8 +1699,7 @@ inline fn onBegin(
|
|
|
1740
1699
|
};
|
|
1741
1700
|
}
|
|
1742
1701
|
|
|
1743
|
-
|
|
1744
|
-
assert(move.effect.onEnd());
|
|
1702
|
+
fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1745
1703
|
return switch (move.effect) {
|
|
1746
1704
|
.Bide => Effects.bide(battle, player, options),
|
|
1747
1705
|
// zig fmt: off
|
|
@@ -1756,14 +1714,13 @@ inline fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anyty
|
|
|
1756
1714
|
};
|
|
1757
1715
|
}
|
|
1758
1716
|
|
|
1759
|
-
|
|
1717
|
+
fn alwaysHappens(
|
|
1760
1718
|
battle: anytype,
|
|
1761
1719
|
player: Player,
|
|
1762
1720
|
move: Move.Data,
|
|
1763
1721
|
residual: *bool,
|
|
1764
1722
|
options: anytype,
|
|
1765
1723
|
) !void {
|
|
1766
|
-
assert(move.effect.alwaysHappens());
|
|
1767
1724
|
return switch (move.effect) {
|
|
1768
1725
|
.DrainHP, .DreamEater => Effects.drainHP(battle, player, options),
|
|
1769
1726
|
.Explode => Effects.explode(battle, player),
|
|
@@ -1775,7 +1732,7 @@ inline fn alwaysHappens(
|
|
|
1775
1732
|
};
|
|
1776
1733
|
}
|
|
1777
1734
|
|
|
1778
|
-
|
|
1735
|
+
fn moveEffect(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1779
1736
|
return switch (move.effect) {
|
|
1780
1737
|
.BurnChance1, .BurnChance2 => Effects.burnChance(battle, player, move, options),
|
|
1781
1738
|
.ConfusionChance => Effects.confusion(battle, player, move, options),
|
|
@@ -1802,7 +1759,7 @@ pub const Effects = struct {
|
|
|
1802
1759
|
side.active.volatiles.state = 0;
|
|
1803
1760
|
side.active.volatiles.attacks = Rolls.attackingDuration(battle, player, options);
|
|
1804
1761
|
|
|
1805
|
-
try options.log.start(battle.active(player), .Bide);
|
|
1762
|
+
try options.log.start(.{ battle.active(player), .Bide });
|
|
1806
1763
|
}
|
|
1807
1764
|
|
|
1808
1765
|
fn burnChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1816,11 +1773,11 @@ pub const Effects = struct {
|
|
|
1816
1773
|
// GLITCH: Freeze top move selection desync can occur if thawed Pokémon is slower
|
|
1817
1774
|
if (Status.is(foe_stored.status, .FRZ)) {
|
|
1818
1775
|
assert(move.type == .Fire);
|
|
1819
|
-
try options.log.curestatus(
|
|
1776
|
+
try options.log.curestatus(.{
|
|
1820
1777
|
battle.active(player.foe()),
|
|
1821
1778
|
foe_stored.status,
|
|
1822
1779
|
.Message,
|
|
1823
|
-
);
|
|
1780
|
+
});
|
|
1824
1781
|
foe_stored.status = 0;
|
|
1825
1782
|
}
|
|
1826
1783
|
return;
|
|
@@ -1834,7 +1791,7 @@ pub const Effects = struct {
|
|
|
1834
1791
|
foe_stored.status = Status.init(.BRN);
|
|
1835
1792
|
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
1836
1793
|
|
|
1837
|
-
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
1794
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1838
1795
|
}
|
|
1839
1796
|
|
|
1840
1797
|
fn charge(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -1844,8 +1801,8 @@ pub const Effects = struct {
|
|
|
1844
1801
|
volatiles.Charging = true;
|
|
1845
1802
|
const move = side.last_selected_move;
|
|
1846
1803
|
if (move == .Fly or move == .Dig) volatiles.Invulnerable = true;
|
|
1847
|
-
try options.log.laststill();
|
|
1848
|
-
try options.log.prepare(battle.active(player), move);
|
|
1804
|
+
try options.log.laststill(.{});
|
|
1805
|
+
try options.log.prepare(.{ battle.active(player), move });
|
|
1849
1806
|
}
|
|
1850
1807
|
|
|
1851
1808
|
fn confusion(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1856,42 +1813,36 @@ pub const Effects = struct {
|
|
|
1856
1813
|
if (!try Rolls.confusionChance(battle, player, options)) return;
|
|
1857
1814
|
} else {
|
|
1858
1815
|
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
|
-
}
|
|
1816
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1817
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1864
1818
|
} else {
|
|
1865
|
-
if (sub) {
|
|
1866
|
-
|
|
1867
|
-
} else if (!try checkHit(battle, player, move, options)) {
|
|
1868
|
-
return;
|
|
1869
|
-
}
|
|
1819
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1820
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1870
1821
|
}
|
|
1871
|
-
try options.chance.commit(player, .hit);
|
|
1872
1822
|
}
|
|
1873
1823
|
|
|
1874
1824
|
if (foe.active.volatiles.Confusion) return;
|
|
1875
1825
|
foe.active.volatiles.Confusion = true;
|
|
1876
1826
|
foe.active.volatiles.confusion = Rolls.confusionDuration(battle, player, false, options);
|
|
1877
1827
|
|
|
1878
|
-
try options.log.start(battle.active(player.foe()), .Confusion);
|
|
1828
|
+
try options.log.start(.{ battle.active(player.foe()), .Confusion });
|
|
1879
1829
|
}
|
|
1880
1830
|
|
|
1881
1831
|
fn conversion(battle: anytype, player: Player, options: anytype) !void {
|
|
1882
1832
|
const foe = battle.foe(player);
|
|
1883
1833
|
|
|
1884
1834
|
if (foe.active.volatiles.Invulnerable) {
|
|
1885
|
-
try options.log.lastmiss();
|
|
1886
|
-
return options.log.miss(battle.active(player));
|
|
1835
|
+
try options.log.lastmiss(.{});
|
|
1836
|
+
return options.log.miss(.{battle.active(player)});
|
|
1887
1837
|
}
|
|
1888
1838
|
|
|
1889
1839
|
battle.side(player).active.types = foe.active.types;
|
|
1890
|
-
return options.log.
|
|
1840
|
+
return options.log.start(.{
|
|
1891
1841
|
battle.active(player),
|
|
1842
|
+
.TypeChange,
|
|
1892
1843
|
foe.active.types,
|
|
1893
1844
|
battle.active(player.foe()),
|
|
1894
|
-
);
|
|
1845
|
+
});
|
|
1895
1846
|
}
|
|
1896
1847
|
|
|
1897
1848
|
fn disable(battle: anytype, player: Player, move: Move.Data, options: anytype) !?Result {
|
|
@@ -1900,13 +1851,10 @@ pub const Effects = struct {
|
|
|
1900
1851
|
const foe_ident = battle.active(player.foe());
|
|
1901
1852
|
|
|
1902
1853
|
// Pokémon Showdown handles hit/miss earlier in doMove
|
|
1903
|
-
if (!showdown)
|
|
1904
|
-
if (!try checkHit(battle, player, move, options)) return null;
|
|
1905
|
-
try options.chance.commit(player, .hit);
|
|
1906
|
-
}
|
|
1854
|
+
if (!showdown and !try checkHit(battle, player, move, options)) return null;
|
|
1907
1855
|
|
|
1908
|
-
if (volatiles.
|
|
1909
|
-
try options.log.fail(foe_ident, .None);
|
|
1856
|
+
if (volatiles.disable_move != 0) {
|
|
1857
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1910
1858
|
return null;
|
|
1911
1859
|
}
|
|
1912
1860
|
|
|
@@ -1922,7 +1870,7 @@ pub const Effects = struct {
|
|
|
1922
1870
|
// Technically this is still considered simply a "miss" on the cartridge,
|
|
1923
1871
|
// but diverging from Pokémon Showdown here would mostly just be pedantic
|
|
1924
1872
|
if (n == 0) {
|
|
1925
|
-
try options.log.fail(foe_ident, .None);
|
|
1873
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1926
1874
|
return null;
|
|
1927
1875
|
} else if (err) {
|
|
1928
1876
|
// GLITCH: Transform + Mirror Move / Metronome PP softlock
|
|
@@ -1930,18 +1878,18 @@ pub const Effects = struct {
|
|
|
1930
1878
|
return Result.Error;
|
|
1931
1879
|
}
|
|
1932
1880
|
|
|
1933
|
-
volatiles.
|
|
1881
|
+
volatiles.disable_move =
|
|
1934
1882
|
@intCast(try Rolls.moveSlot(battle, player, &foe.active.moves, n, options));
|
|
1935
|
-
volatiles.
|
|
1883
|
+
volatiles.disable_duration = Rolls.disableDuration(battle, player, options);
|
|
1936
1884
|
|
|
1937
|
-
const id = foe.active.move(volatiles.
|
|
1938
|
-
try options.log.
|
|
1885
|
+
const id = foe.active.move(volatiles.disable_move).id;
|
|
1886
|
+
try options.log.start(.{ foe_ident, .Disable, id });
|
|
1939
1887
|
return null;
|
|
1940
1888
|
}
|
|
1941
1889
|
|
|
1942
1890
|
fn drainHP(battle: anytype, player: Player, options: anytype) !void {
|
|
1943
|
-
var
|
|
1944
|
-
|
|
1891
|
+
var stored = battle.side(player).stored();
|
|
1892
|
+
const ident = battle.active(player);
|
|
1945
1893
|
|
|
1946
1894
|
if (battle.last_damage == 0) {
|
|
1947
1895
|
assert(showdown);
|
|
@@ -1954,7 +1902,7 @@ pub const Effects = struct {
|
|
|
1954
1902
|
if (stored.hp == stored.stats.hp) return;
|
|
1955
1903
|
stored.hp = @min(stored.stats.hp, stored.hp + drain);
|
|
1956
1904
|
|
|
1957
|
-
try options.log.
|
|
1905
|
+
try options.log.heal(.{ ident, stored, .Drain, battle.active(player.foe()) });
|
|
1958
1906
|
}
|
|
1959
1907
|
|
|
1960
1908
|
fn explode(battle: anytype, player: Player) !void {
|
|
@@ -1986,7 +1934,7 @@ pub const Effects = struct {
|
|
|
1986
1934
|
if (side.active.volatiles.FocusEnergy) return;
|
|
1987
1935
|
side.active.volatiles.FocusEnergy = true;
|
|
1988
1936
|
|
|
1989
|
-
try options.log.start(battle.active(player), .FocusEnergy);
|
|
1937
|
+
try options.log.start(.{ battle.active(player), .FocusEnergy });
|
|
1990
1938
|
}
|
|
1991
1939
|
|
|
1992
1940
|
fn freezeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2008,7 +1956,7 @@ pub const Effects = struct {
|
|
|
2008
1956
|
foe_stored.status = Status.init(.FRZ);
|
|
2009
1957
|
// GLITCH: Hyper Beam recharging status is not cleared
|
|
2010
1958
|
|
|
2011
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
1959
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2012
1960
|
}
|
|
2013
1961
|
|
|
2014
1962
|
fn haze(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -2016,7 +1964,7 @@ pub const Effects = struct {
|
|
|
2016
1964
|
var side = battle.side(player);
|
|
2017
1965
|
var foe = battle.foe(player);
|
|
2018
1966
|
|
|
2019
|
-
|
|
1967
|
+
const side_stored = side.stored();
|
|
2020
1968
|
var foe_stored = foe.stored();
|
|
2021
1969
|
|
|
2022
1970
|
const player_ident = battle.active(player);
|
|
@@ -2028,8 +1976,8 @@ pub const Effects = struct {
|
|
|
2028
1976
|
side.active.stats = side_stored.stats;
|
|
2029
1977
|
foe.active.stats = foe_stored.stats;
|
|
2030
1978
|
|
|
2031
|
-
try log.activate(player_ident, .Haze);
|
|
2032
|
-
try log.clearallboost();
|
|
1979
|
+
try log.activate(.{ player_ident, .Haze });
|
|
1980
|
+
try log.clearallboost(.{});
|
|
2033
1981
|
|
|
2034
1982
|
// Pokémon Showdown clears P1 then P2 instead of status -> side -> foe
|
|
2035
1983
|
if (showdown) {
|
|
@@ -2037,11 +1985,11 @@ pub const Effects = struct {
|
|
|
2037
1985
|
const p: Player = @enumFromInt(i);
|
|
2038
1986
|
// Pokémon Showdown incorrectly does not prevent sleep/freeze from moving
|
|
2039
1987
|
if (p != player and Status.any(s.stored().status)) {
|
|
2040
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
1988
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2041
1989
|
s.stored().status = 0;
|
|
2042
1990
|
} else if (showdown and s.stored().status == Status.TOX) {
|
|
2043
1991
|
s.stored().status = Status.init(.PSN);
|
|
2044
|
-
try log.status(battle.active(p), s.stored().status, .None);
|
|
1992
|
+
try log.status(.{ battle.active(p), s.stored().status, .None });
|
|
2045
1993
|
}
|
|
2046
1994
|
try clearVolatiles(battle, p, p != player, options);
|
|
2047
1995
|
}
|
|
@@ -2050,7 +1998,7 @@ pub const Effects = struct {
|
|
|
2050
1998
|
if (Status.is(foe_stored.status, .FRZ) or Status.is(foe_stored.status, .SLP)) {
|
|
2051
1999
|
foe.last_selected_move = .SKIP_TURN;
|
|
2052
2000
|
}
|
|
2053
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
2001
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2054
2002
|
foe_stored.status = 0;
|
|
2055
2003
|
}
|
|
2056
2004
|
try clearVolatiles(battle, player, false, options);
|
|
@@ -2066,7 +2014,7 @@ pub const Effects = struct {
|
|
|
2066
2014
|
// GLITCH: HP recovery move failure glitches
|
|
2067
2015
|
const delta = stored.stats.hp - stored.hp;
|
|
2068
2016
|
if (delta == 0 or (delta & 255 == 255 and stored.hp % 256 != 0)) {
|
|
2069
|
-
return
|
|
2017
|
+
return options.log.fail(.{ ident, .None });
|
|
2070
2018
|
}
|
|
2071
2019
|
|
|
2072
2020
|
const rest = side.last_selected_move == .Rest;
|
|
@@ -2074,17 +2022,17 @@ pub const Effects = struct {
|
|
|
2074
2022
|
// Adding the sleep status runs the sleep condition handler to roll duration
|
|
2075
2023
|
if (showdown) battle.rng.advance(1);
|
|
2076
2024
|
stored.status = Status.slf(2);
|
|
2077
|
-
try options.log.
|
|
2025
|
+
try options.log.status(.{ ident, stored.status, .From, Move.Rest });
|
|
2078
2026
|
stored.hp = stored.stats.hp;
|
|
2079
2027
|
} else {
|
|
2080
2028
|
stored.hp = @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2));
|
|
2081
2029
|
}
|
|
2082
|
-
try options.log.heal(ident, stored, if (rest) Heal.Silent else Heal.None);
|
|
2030
|
+
try options.log.heal(.{ ident, stored, if (rest) Heal.Silent else Heal.None });
|
|
2083
2031
|
}
|
|
2084
2032
|
|
|
2085
2033
|
fn hyperBeam(battle: anytype, player: Player, options: anytype) !void {
|
|
2086
2034
|
battle.side(player).active.volatiles.Recharging = true;
|
|
2087
|
-
try options.log.mustrecharge(battle.active(player));
|
|
2035
|
+
try options.log.mustrecharge(.{battle.active(player)});
|
|
2088
2036
|
}
|
|
2089
2037
|
|
|
2090
2038
|
fn leechSeed(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2093,33 +2041,31 @@ pub const Effects = struct {
|
|
|
2093
2041
|
if (showdown) {
|
|
2094
2042
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2095
2043
|
if (!foe.active.volatiles.Invulnerable and foe.active.types.includes(.Grass)) {
|
|
2096
|
-
return options.log.immune(battle.active(player.foe()), .None);
|
|
2044
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
2097
2045
|
}
|
|
2098
2046
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2099
2047
|
if (foe.active.volatiles.LeechSeed) return;
|
|
2100
2048
|
} else {
|
|
2101
2049
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2102
2050
|
if (foe.active.types.includes(.Grass) or foe.active.volatiles.LeechSeed) {
|
|
2103
|
-
try options.log.lastmiss();
|
|
2104
|
-
return options.log.miss(battle.active(player));
|
|
2051
|
+
try options.log.lastmiss(.{});
|
|
2052
|
+
return options.log.miss(.{battle.active(player)});
|
|
2105
2053
|
}
|
|
2106
2054
|
}
|
|
2107
2055
|
|
|
2108
|
-
try options.chance.commit(player, .hit);
|
|
2109
2056
|
foe.active.volatiles.LeechSeed = true;
|
|
2110
2057
|
|
|
2111
|
-
try options.log.start(battle.active(player.foe()), .LeechSeed);
|
|
2058
|
+
try options.log.start(.{ battle.active(player.foe()), .LeechSeed });
|
|
2112
2059
|
}
|
|
2113
2060
|
|
|
2114
2061
|
fn lightScreen(battle: anytype, player: Player, options: anytype) !void {
|
|
2115
2062
|
var side = battle.side(player);
|
|
2063
|
+
const ident = battle.active(player);
|
|
2116
2064
|
|
|
2117
|
-
if (side.active.volatiles.LightScreen) {
|
|
2118
|
-
return options.log.fail(battle.active(player), .None);
|
|
2119
|
-
}
|
|
2065
|
+
if (side.active.volatiles.LightScreen) return options.log.fail(.{ ident, .None });
|
|
2120
2066
|
side.active.volatiles.LightScreen = true;
|
|
2121
2067
|
|
|
2122
|
-
try options.log.start(
|
|
2068
|
+
try options.log.start(.{ ident, .LightScreen });
|
|
2123
2069
|
}
|
|
2124
2070
|
|
|
2125
2071
|
fn mimic(battle: anytype, player: Player, move: Move.Data, mslot: u8, options: anytype) !void {
|
|
@@ -2147,17 +2093,15 @@ pub const Effects = struct {
|
|
|
2147
2093
|
if (!has_mimic) {
|
|
2148
2094
|
// Invulnerable foes or 1/256 miss can trigger |-miss| instead of |-fail|
|
|
2149
2095
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2150
|
-
|
|
2151
|
-
return try options.log.fail(battle.active(player.foe()), .None);
|
|
2096
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2152
2097
|
}
|
|
2153
2098
|
}
|
|
2154
2099
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2155
|
-
try options.chance.commit(player, .hit);
|
|
2156
2100
|
|
|
2157
2101
|
const rslot = try Rolls.moveSlot(battle, player, &foe.active.moves, 0, options);
|
|
2158
2102
|
side.active.move(oslot).id = foe.active.move(rslot).id;
|
|
2159
2103
|
|
|
2160
|
-
try options.log.
|
|
2104
|
+
try options.log.start(.{ battle.active(player), .Mimic, side.active.move(oslot).id });
|
|
2161
2105
|
}
|
|
2162
2106
|
|
|
2163
2107
|
fn mist(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -2166,7 +2110,7 @@ pub const Effects = struct {
|
|
|
2166
2110
|
if (side.active.volatiles.Mist) return;
|
|
2167
2111
|
side.active.volatiles.Mist = true;
|
|
2168
2112
|
|
|
2169
|
-
try options.log.start(battle.active(player), .Mist);
|
|
2113
|
+
try options.log.start(.{ battle.active(player), .Mist });
|
|
2170
2114
|
}
|
|
2171
2115
|
|
|
2172
2116
|
fn multiHit(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2192,25 +2136,25 @@ pub const Effects = struct {
|
|
|
2192
2136
|
|
|
2193
2137
|
if (showdown) {
|
|
2194
2138
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2195
|
-
if (immune and !foe.active.volatiles.Invulnerable)
|
|
2196
|
-
|
|
2139
|
+
if (immune and !foe.active.volatiles.Invulnerable) {
|
|
2140
|
+
return log.immune(.{ foe_ident, .None });
|
|
2141
|
+
} else if (!try checkHit(battle, player, move, options)) return;
|
|
2197
2142
|
}
|
|
2198
2143
|
if (Status.any(foe_stored.status)) {
|
|
2199
|
-
return log.fail(
|
|
2144
|
+
return log.fail(.{
|
|
2200
2145
|
foe_ident,
|
|
2201
|
-
if (Status.is(foe_stored.status, .PAR)) .Paralysis else .None,
|
|
2202
|
-
);
|
|
2146
|
+
if (Status.is(foe_stored.status, .PAR)) Fail.Paralysis else Fail.None,
|
|
2147
|
+
});
|
|
2203
2148
|
}
|
|
2204
2149
|
if (!showdown) {
|
|
2205
|
-
if (immune) return log.immune(foe_ident, .None);
|
|
2150
|
+
if (immune) return log.immune(.{ foe_ident, .None });
|
|
2206
2151
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2207
2152
|
}
|
|
2208
|
-
try options.chance.commit(player, .hit);
|
|
2209
2153
|
|
|
2210
2154
|
foe_stored.status = Status.init(.PAR);
|
|
2211
2155
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2212
2156
|
|
|
2213
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2157
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2214
2158
|
}
|
|
2215
2159
|
|
|
2216
2160
|
fn paralyzeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2232,12 +2176,12 @@ pub const Effects = struct {
|
|
|
2232
2176
|
foe_stored.status = Status.init(.PAR);
|
|
2233
2177
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2234
2178
|
|
|
2235
|
-
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
2179
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
2236
2180
|
}
|
|
2237
2181
|
|
|
2238
2182
|
fn payDay(battle: anytype, player: Player, options: anytype) !void {
|
|
2239
2183
|
if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
|
|
2240
|
-
try options.log.fieldactivate();
|
|
2184
|
+
try options.log.fieldactivate(.{});
|
|
2241
2185
|
}
|
|
2242
2186
|
}
|
|
2243
2187
|
|
|
@@ -2252,28 +2196,27 @@ pub const Effects = struct {
|
|
|
2252
2196
|
return;
|
|
2253
2197
|
} else if (foe.active.volatiles.Substitute) {
|
|
2254
2198
|
if (move.effect != .Poison) return;
|
|
2255
|
-
return log.fail(foe_ident, .None);
|
|
2199
|
+
return log.fail(.{ foe_ident, .None });
|
|
2256
2200
|
} else if (Status.any(foe_stored.status)) {
|
|
2257
2201
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2258
2202
|
// Pokémon Showdown considers Toxic to be a status even in Generation I and so
|
|
2259
2203
|
// 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))
|
|
2204
|
+
return log.fail(.{ foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2261
2205
|
if (!showdown)
|
|
2262
|
-
.Poison
|
|
2206
|
+
Fail.Poison
|
|
2263
2207
|
else if (toxic == (foe_stored.status == Status.TOX))
|
|
2264
|
-
if (toxic) .Toxic else .Poison
|
|
2208
|
+
if (toxic) Fail.Toxic else Fail.Poison
|
|
2265
2209
|
else
|
|
2266
|
-
.None
|
|
2210
|
+
Fail.None
|
|
2267
2211
|
else
|
|
2268
|
-
.None);
|
|
2212
|
+
Fail.None });
|
|
2269
2213
|
} else if (foe.active.types.includes(.Poison)) {
|
|
2270
2214
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2271
|
-
return log.immune(foe_ident, .None);
|
|
2215
|
+
return log.immune(.{ foe_ident, .None });
|
|
2272
2216
|
}
|
|
2273
2217
|
|
|
2274
2218
|
if (move.effect == .Poison) {
|
|
2275
2219
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
2276
|
-
try options.chance.commit(player, .hit);
|
|
2277
2220
|
} else {
|
|
2278
2221
|
if (!try Rolls.poisonChance(battle, player, move.effect == .PoisonChance1, options)) {
|
|
2279
2222
|
return;
|
|
@@ -2287,7 +2230,7 @@ pub const Effects = struct {
|
|
|
2287
2230
|
foe.active.volatiles.toxic = 0;
|
|
2288
2231
|
}
|
|
2289
2232
|
|
|
2290
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2233
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2291
2234
|
}
|
|
2292
2235
|
|
|
2293
2236
|
fn rage(battle: anytype, player: Player) !void {
|
|
@@ -2307,22 +2250,23 @@ pub const Effects = struct {
|
|
|
2307
2250
|
@as(u8, if (side.last_selected_move == .Struggle) 2 else 4), 1));
|
|
2308
2251
|
stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
|
|
2309
2252
|
|
|
2310
|
-
try options.log.
|
|
2253
|
+
try options.log.damage(.{
|
|
2311
2254
|
battle.active(player),
|
|
2312
2255
|
stored,
|
|
2313
2256
|
.RecoilOf,
|
|
2314
2257
|
battle.active(player.foe()),
|
|
2315
|
-
);
|
|
2258
|
+
});
|
|
2316
2259
|
if (showdown and stored.hp == 0) residual.* = false;
|
|
2317
2260
|
}
|
|
2318
2261
|
|
|
2319
2262
|
fn reflect(battle: anytype, player: Player, options: anytype) !void {
|
|
2320
2263
|
var side = battle.side(player);
|
|
2264
|
+
const ident = battle.active(player);
|
|
2321
2265
|
|
|
2322
|
-
if (side.active.volatiles.Reflect) return options.log.fail(
|
|
2266
|
+
if (side.active.volatiles.Reflect) return options.log.fail(.{ ident, .None });
|
|
2323
2267
|
side.active.volatiles.Reflect = true;
|
|
2324
2268
|
|
|
2325
|
-
try options.log.start(
|
|
2269
|
+
try options.log.start(.{ ident, .Reflect });
|
|
2326
2270
|
}
|
|
2327
2271
|
|
|
2328
2272
|
fn sleep(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2336,14 +2280,13 @@ pub const Effects = struct {
|
|
|
2336
2280
|
// the Sleep Clause Mod might activate, causing us to not actually bypass
|
|
2337
2281
|
} else {
|
|
2338
2282
|
if (Status.any(foe_stored.status)) {
|
|
2339
|
-
return options.log.fail(
|
|
2283
|
+
return options.log.fail(.{
|
|
2340
2284
|
foe_ident,
|
|
2341
|
-
if (Status.is(foe_stored.status, .SLP)) .Sleep else .None,
|
|
2342
|
-
);
|
|
2285
|
+
if (Status.is(foe_stored.status, .SLP)) Fail.Sleep else Fail.None,
|
|
2286
|
+
});
|
|
2343
2287
|
}
|
|
2344
|
-
// If
|
|
2288
|
+
// If moveHit in doMove didn't return true Pokémon Showdown wouldn't be in here
|
|
2345
2289
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
2346
|
-
try options.chance.commit(player, .hit);
|
|
2347
2290
|
}
|
|
2348
2291
|
|
|
2349
2292
|
// Sleep Clause Mod
|
|
@@ -2356,18 +2299,17 @@ pub const Effects = struct {
|
|
|
2356
2299
|
|
|
2357
2300
|
foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
|
|
2358
2301
|
const last = battle.side(player).last_selected_move;
|
|
2359
|
-
try options.log.
|
|
2302
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .From, last });
|
|
2360
2303
|
}
|
|
2361
2304
|
|
|
2362
2305
|
fn splash(battle: anytype, player: Player, options: anytype) !void {
|
|
2363
|
-
try options.log.activate(battle.active(player), .Splash);
|
|
2306
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
2364
2307
|
}
|
|
2365
2308
|
|
|
2366
2309
|
fn substitute(battle: anytype, player: Player, residual: *bool, options: anytype) !void {
|
|
2367
2310
|
var side = battle.side(player);
|
|
2368
2311
|
if (side.active.volatiles.Substitute) {
|
|
2369
|
-
|
|
2370
|
-
return;
|
|
2312
|
+
return options.log.fail(.{ battle.active(player), .Substitute });
|
|
2371
2313
|
}
|
|
2372
2314
|
|
|
2373
2315
|
assert(side.stored().stats.hp <= 1023);
|
|
@@ -2379,28 +2321,30 @@ pub const Effects = struct {
|
|
|
2379
2321
|
// avoid having to convert to floating point)
|
|
2380
2322
|
const required_hp = if (showdown) @divFloor(side.stored().stats.hp - 1, 4) + 1 else hp;
|
|
2381
2323
|
if (side.stored().hp < required_hp) {
|
|
2382
|
-
|
|
2383
|
-
return;
|
|
2324
|
+
return options.log.fail(.{ battle.active(player), .Weak });
|
|
2384
2325
|
}
|
|
2385
2326
|
|
|
2386
2327
|
// GLITCH: can leave the user with 0 HP (faints later) because didn't check '<=' above
|
|
2387
2328
|
side.stored().hp -= hp;
|
|
2388
2329
|
side.active.volatiles.substitute = hp + 1;
|
|
2389
2330
|
side.active.volatiles.Substitute = true;
|
|
2390
|
-
try options.log.start(battle.active(player), .Substitute);
|
|
2331
|
+
try options.log.start(.{ battle.active(player), .Substitute });
|
|
2391
2332
|
if (hp > 0) {
|
|
2392
|
-
try options.log.damage(battle.active(player), side.stored(), .None);
|
|
2333
|
+
try options.log.damage(.{ battle.active(player), side.stored(), .None });
|
|
2393
2334
|
if (showdown and side.stored().hp == 0) residual.* = false;
|
|
2394
2335
|
}
|
|
2395
2336
|
}
|
|
2396
2337
|
|
|
2397
2338
|
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
2339
|
// Whirlwind/Roar should not roll to hit/reset damage but Pokémon Showdown does anyway
|
|
2401
|
-
if (
|
|
2402
|
-
|
|
2403
|
-
|
|
2340
|
+
if (showdown) {
|
|
2341
|
+
if (battle.side(player).last_selected_move == .Teleport) return;
|
|
2342
|
+
_ = try checkHit(battle, player, move, options);
|
|
2343
|
+
battle.last_damage = 0;
|
|
2344
|
+
} else {
|
|
2345
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2346
|
+
try options.log.laststill(.{});
|
|
2347
|
+
}
|
|
2404
2348
|
}
|
|
2405
2349
|
|
|
2406
2350
|
fn thrashing(battle: anytype, player: Player, options: anytype) void {
|
|
@@ -2438,7 +2382,7 @@ pub const Effects = struct {
|
|
|
2438
2382
|
side.active.moves[i].pp = if (m.id != .None) 5 else 0;
|
|
2439
2383
|
}
|
|
2440
2384
|
|
|
2441
|
-
try options.log.transform(battle.active(player), foe_ident);
|
|
2385
|
+
try options.log.transform(.{ battle.active(player), foe_ident });
|
|
2442
2386
|
}
|
|
2443
2387
|
|
|
2444
2388
|
fn binding(battle: anytype, player: Player, rewrap: bool, options: anytype) !void {
|
|
@@ -2468,87 +2412,87 @@ pub const Effects = struct {
|
|
|
2468
2412
|
switch (move.effect) {
|
|
2469
2413
|
.AttackUp1, .AttackUp2, .Rage => {
|
|
2470
2414
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2471
|
-
if (boosts.atk == 6) return
|
|
2415
|
+
if (boosts.atk == 6) return log.fail(.{ ident, .None });
|
|
2472
2416
|
const n: u2 = if (move.effect == .AttackUp2) 2 else 1;
|
|
2473
2417
|
boosts.atk = @as(i4, @intCast(@min(6, @as(i8, boosts.atk) + n)));
|
|
2474
|
-
const reason = if (move.effect == .Rage)
|
|
2418
|
+
const reason: Boost = if (move.effect == .Rage) .Rage else .Attack;
|
|
2475
2419
|
if (stats.atk == MAX_STAT_VALUE) {
|
|
2476
2420
|
boosts.atk -= 1;
|
|
2477
2421
|
if (showdown) {
|
|
2478
|
-
try log.boost(ident, reason, n);
|
|
2479
|
-
try log.boost(ident,
|
|
2422
|
+
try log.boost(.{ ident, reason, n });
|
|
2423
|
+
try log.boost(.{ ident, .Attack, -1 });
|
|
2480
2424
|
if (move.effect == .Rage) return;
|
|
2481
2425
|
}
|
|
2482
|
-
return
|
|
2426
|
+
return log.fail(.{ ident, .None });
|
|
2483
2427
|
}
|
|
2484
|
-
|
|
2428
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2485
2429
|
const stat = unmodifiedStats(battle, side).atk;
|
|
2486
2430
|
stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2487
|
-
try log.boost(ident, reason, n);
|
|
2431
|
+
try log.boost(.{ ident, reason, n });
|
|
2488
2432
|
// Pokémon Showdown doesn't re-apply status modifiers after Rage boosts
|
|
2489
2433
|
if (showdown and move.effect == .Rage) return;
|
|
2490
2434
|
},
|
|
2491
2435
|
.DefenseUp1, .DefenseUp2 => {
|
|
2492
2436
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2493
|
-
if (boosts.def == 6) return
|
|
2437
|
+
if (boosts.def == 6) return log.fail(.{ ident, .None });
|
|
2494
2438
|
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
2495
2439
|
boosts.def = @intCast(@min(6, @as(i8, boosts.def) + n));
|
|
2496
2440
|
if (stats.def == MAX_STAT_VALUE) {
|
|
2497
2441
|
boosts.def -= 1;
|
|
2498
2442
|
if (showdown) {
|
|
2499
|
-
try log.boost(ident, .Defense, n);
|
|
2500
|
-
try log.boost(ident, .Defense, -1);
|
|
2443
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2444
|
+
try log.boost(.{ ident, .Defense, -1 });
|
|
2501
2445
|
}
|
|
2502
|
-
return
|
|
2446
|
+
return log.fail(.{ ident, .None });
|
|
2503
2447
|
}
|
|
2504
|
-
|
|
2448
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2505
2449
|
const stat = unmodifiedStats(battle, side).def;
|
|
2506
2450
|
stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2507
|
-
try log.boost(ident, .Defense, n);
|
|
2451
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2508
2452
|
},
|
|
2509
2453
|
.SpeedUp2 => {
|
|
2510
2454
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2511
|
-
if (boosts.spe == 6) return
|
|
2455
|
+
if (boosts.spe == 6) return log.fail(.{ ident, .None });
|
|
2512
2456
|
boosts.spe = @intCast(@min(6, @as(i8, boosts.spe) + 2));
|
|
2513
2457
|
if (stats.spe == MAX_STAT_VALUE) {
|
|
2514
2458
|
boosts.spe -= 1;
|
|
2515
2459
|
if (showdown) {
|
|
2516
|
-
try log.boost(ident, .Speed, 2);
|
|
2517
|
-
try log.boost(ident, .Speed, -1);
|
|
2460
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2461
|
+
try log.boost(.{ ident, .Speed, -1 });
|
|
2518
2462
|
}
|
|
2519
|
-
return
|
|
2463
|
+
return log.fail(.{ ident, .None });
|
|
2520
2464
|
}
|
|
2521
|
-
|
|
2465
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2522
2466
|
const stat = unmodifiedStats(battle, side).spe;
|
|
2523
2467
|
stats.spe = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2524
|
-
try log.boost(ident, .Speed, 2);
|
|
2468
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2525
2469
|
},
|
|
2526
2470
|
.SpecialUp1, .SpecialUp2 => {
|
|
2527
2471
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2528
|
-
if (boosts.spc == 6) return
|
|
2472
|
+
if (boosts.spc == 6) return log.fail(.{ ident, .None });
|
|
2529
2473
|
const n: u2 = if (move.effect == .SpecialUp2) 2 else 1;
|
|
2530
2474
|
boosts.spc = @intCast(@min(6, @as(i8, boosts.spc) + n));
|
|
2531
2475
|
if (stats.spc == MAX_STAT_VALUE) {
|
|
2532
2476
|
boosts.spc -= 1;
|
|
2533
2477
|
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);
|
|
2478
|
+
try log.boost(.{ ident, .SpecialAttack, n });
|
|
2479
|
+
try log.boost(.{ ident, .SpecialAttack, -1 });
|
|
2480
|
+
try log.boost(.{ ident, .SpecialDefense, n });
|
|
2481
|
+
try log.boost(.{ ident, .SpecialDefense, -1 });
|
|
2538
2482
|
}
|
|
2539
|
-
return
|
|
2483
|
+
return log.fail(.{ ident, .None });
|
|
2540
2484
|
}
|
|
2541
|
-
|
|
2485
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2542
2486
|
const stat = unmodifiedStats(battle, side).spc;
|
|
2543
2487
|
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);
|
|
2488
|
+
try log.boost(.{ ident, .SpecialAttack, n });
|
|
2489
|
+
try log.boost(.{ ident, .SpecialDefense, n });
|
|
2546
2490
|
},
|
|
2547
2491
|
.EvasionUp1 => {
|
|
2548
2492
|
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2549
|
-
if (boosts.evasion == 6) return
|
|
2493
|
+
if (boosts.evasion == 6) return log.fail(.{ ident, .None });
|
|
2550
2494
|
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2551
|
-
try log.boost(ident, .Evasion, 1);
|
|
2495
|
+
try log.boost(.{ ident, .Evasion, 1 });
|
|
2552
2496
|
},
|
|
2553
2497
|
else => unreachable,
|
|
2554
2498
|
}
|
|
@@ -2563,7 +2507,7 @@ pub const Effects = struct {
|
|
|
2563
2507
|
const foe_ident = battle.active(player.foe());
|
|
2564
2508
|
const secondary = move.effect.isStatDownChance();
|
|
2565
2509
|
|
|
2566
|
-
if (foe.active.volatiles.Substitute) return if (!secondary) log.fail(foe_ident, .None);
|
|
2510
|
+
if (foe.active.volatiles.Substitute) return if (!secondary) log.fail(.{ foe_ident, .None });
|
|
2567
2511
|
|
|
2568
2512
|
if (secondary) {
|
|
2569
2513
|
const proc = try Rolls.unboost(battle, player, options);
|
|
@@ -2571,7 +2515,6 @@ pub const Effects = struct {
|
|
|
2571
2515
|
} else {
|
|
2572
2516
|
// checkHit already checks for Invulnerable
|
|
2573
2517
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
2574
|
-
try options.chance.commit(player, .hit);
|
|
2575
2518
|
}
|
|
2576
2519
|
|
|
2577
2520
|
var stats = &foe.active.stats;
|
|
@@ -2581,82 +2524,82 @@ pub const Effects = struct {
|
|
|
2581
2524
|
switch (move.effect) {
|
|
2582
2525
|
.AttackDown1, .AttackDownChance => {
|
|
2583
2526
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2584
|
-
if (boosts.atk == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2527
|
+
if (boosts.atk == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2585
2528
|
boosts.atk = @intCast(@max(-6, @as(i8, boosts.atk) - 1));
|
|
2586
2529
|
if (stats.atk == 1) {
|
|
2587
2530
|
boosts.atk += 1;
|
|
2588
2531
|
if (showdown) {
|
|
2589
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2590
|
-
try log.boost(foe_ident, .Attack, 1);
|
|
2532
|
+
try log.boost(.{ foe_ident, .Attack, -1 });
|
|
2533
|
+
try log.boost(.{ foe_ident, .Attack, 1 });
|
|
2591
2534
|
}
|
|
2592
|
-
return
|
|
2535
|
+
return log.fail(.{ foe_ident, .None });
|
|
2593
2536
|
}
|
|
2594
|
-
|
|
2537
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2595
2538
|
const stat = unmodifiedStats(battle, foe).atk;
|
|
2596
2539
|
stats.atk = @max(1, stat * mod[0] / mod[1]);
|
|
2597
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2540
|
+
try log.boost(.{ foe_ident, .Attack, -1 });
|
|
2598
2541
|
},
|
|
2599
2542
|
.DefenseDown1, .DefenseDown2, .DefenseDownChance => {
|
|
2600
2543
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2601
|
-
if (boosts.def == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2544
|
+
if (boosts.def == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2602
2545
|
const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
|
|
2603
2546
|
boosts.def = @intCast(@max(-6, @as(i8, boosts.def) - n));
|
|
2604
2547
|
if (stats.def == 1) {
|
|
2605
2548
|
boosts.def += 1;
|
|
2606
2549
|
if (showdown) {
|
|
2607
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2608
|
-
try log.boost(foe_ident, .Defense, 1);
|
|
2550
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2551
|
+
try log.boost(.{ foe_ident, .Defense, 1 });
|
|
2609
2552
|
}
|
|
2610
|
-
return
|
|
2553
|
+
return log.fail(.{ foe_ident, .None });
|
|
2611
2554
|
}
|
|
2612
|
-
|
|
2555
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2613
2556
|
const stat = unmodifiedStats(battle, foe).def;
|
|
2614
2557
|
stats.def = @max(1, stat * mod[0] / mod[1]);
|
|
2615
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2558
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2616
2559
|
},
|
|
2617
2560
|
.SpeedDown1, .SpeedDownChance => {
|
|
2618
2561
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2619
|
-
if (boosts.spe == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2562
|
+
if (boosts.spe == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2620
2563
|
boosts.spe = @intCast(@max(-6, @as(i8, boosts.spe) - 1));
|
|
2621
2564
|
if (stats.spe == 1) {
|
|
2622
2565
|
boosts.spe += 1;
|
|
2623
2566
|
if (showdown) {
|
|
2624
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2625
|
-
try log.boost(foe_ident, .Speed, 1);
|
|
2567
|
+
try log.boost(.{ foe_ident, .Speed, -1 });
|
|
2568
|
+
try log.boost(.{ foe_ident, .Speed, 1 });
|
|
2626
2569
|
}
|
|
2627
|
-
return
|
|
2570
|
+
return log.fail(.{ foe_ident, .None });
|
|
2628
2571
|
}
|
|
2629
|
-
|
|
2572
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2630
2573
|
const stat = unmodifiedStats(battle, foe).spe;
|
|
2631
2574
|
stats.spe = @max(1, stat * mod[0] / mod[1]);
|
|
2632
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2575
|
+
try log.boost(.{ foe_ident, .Speed, -1 });
|
|
2633
2576
|
assert(boosts.spe >= -6);
|
|
2634
2577
|
},
|
|
2635
2578
|
.SpecialDownChance => {
|
|
2636
2579
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2637
|
-
if (boosts.spc == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2580
|
+
if (boosts.spc == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2638
2581
|
boosts.spc = @intCast(@max(-6, @as(i8, boosts.spc) - 1));
|
|
2639
2582
|
if (stats.spc == 1) {
|
|
2640
2583
|
boosts.spc += 1;
|
|
2641
2584
|
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);
|
|
2585
|
+
try log.boost(.{ foe_ident, .SpecialAttack, -1 });
|
|
2586
|
+
try log.boost(.{ foe_ident, .SpecialAttack, 1 });
|
|
2587
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2588
|
+
try log.boost(.{ foe_ident, .SpecialDefense, 1 });
|
|
2646
2589
|
}
|
|
2647
|
-
return
|
|
2590
|
+
return log.fail(.{ foe_ident, .None });
|
|
2648
2591
|
}
|
|
2649
|
-
|
|
2592
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2650
2593
|
const stat = unmodifiedStats(battle, foe).spc;
|
|
2651
2594
|
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);
|
|
2595
|
+
try log.boost(.{ foe_ident, .SpecialAttack, -1 });
|
|
2596
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2654
2597
|
},
|
|
2655
2598
|
.AccuracyDown1 => {
|
|
2656
2599
|
assert(boosts.accuracy >= -6 and boosts.accuracy <= 6);
|
|
2657
|
-
if (boosts.accuracy == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2600
|
+
if (boosts.accuracy == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2658
2601
|
boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
|
|
2659
|
-
try log.boost(foe_ident, .Accuracy, -1);
|
|
2602
|
+
try log.boost(.{ foe_ident, .Accuracy, -1 });
|
|
2660
2603
|
},
|
|
2661
2604
|
else => unreachable,
|
|
2662
2605
|
}
|
|
@@ -2680,7 +2623,7 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
2680
2623
|
}
|
|
2681
2624
|
}
|
|
2682
2625
|
|
|
2683
|
-
|
|
2626
|
+
fn isForced(active: anytype) bool {
|
|
2684
2627
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
2685
2628
|
active.volatiles.Thrashing or active.volatiles.Charging;
|
|
2686
2629
|
}
|
|
@@ -2693,27 +2636,27 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2693
2636
|
|
|
2694
2637
|
options.chance.clearDurations(who, clear);
|
|
2695
2638
|
|
|
2696
|
-
if (volatiles.
|
|
2697
|
-
volatiles.
|
|
2698
|
-
volatiles.
|
|
2699
|
-
try log.end(ident, .DisableSilent);
|
|
2639
|
+
if (volatiles.disable_move != 0) {
|
|
2640
|
+
volatiles.disable_move = 0;
|
|
2641
|
+
volatiles.disable_duration = 0;
|
|
2642
|
+
try log.end(.{ ident, .DisableSilent });
|
|
2700
2643
|
}
|
|
2701
2644
|
if (volatiles.Confusion) {
|
|
2702
2645
|
// volatiles.confusion is left unchanged
|
|
2703
2646
|
volatiles.Confusion = false;
|
|
2704
|
-
try log.end(ident, .ConfusionSilent);
|
|
2647
|
+
try log.end(.{ ident, .ConfusionSilent });
|
|
2705
2648
|
}
|
|
2706
2649
|
if (volatiles.Mist) {
|
|
2707
2650
|
volatiles.Mist = false;
|
|
2708
|
-
try log.end(ident, .
|
|
2651
|
+
try log.end(.{ ident, .MistSilent });
|
|
2709
2652
|
}
|
|
2710
2653
|
if (volatiles.FocusEnergy) {
|
|
2711
2654
|
volatiles.FocusEnergy = false;
|
|
2712
|
-
try log.end(ident, .
|
|
2655
|
+
try log.end(.{ ident, .FocusEnergySilent });
|
|
2713
2656
|
}
|
|
2714
2657
|
if (volatiles.LeechSeed) {
|
|
2715
2658
|
volatiles.LeechSeed = false;
|
|
2716
|
-
try log.end(ident, .
|
|
2659
|
+
try log.end(.{ ident, .LeechSeedSilent });
|
|
2717
2660
|
}
|
|
2718
2661
|
if (!showdown and volatiles.Toxic) {
|
|
2719
2662
|
volatiles.Toxic = false;
|
|
@@ -2721,24 +2664,26 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2721
2664
|
}
|
|
2722
2665
|
if (volatiles.LightScreen) {
|
|
2723
2666
|
volatiles.LightScreen = false;
|
|
2724
|
-
try log.end(ident, .
|
|
2667
|
+
try log.end(.{ ident, .LightScreenSilent });
|
|
2725
2668
|
}
|
|
2726
2669
|
if (volatiles.Reflect) {
|
|
2727
2670
|
volatiles.Reflect = false;
|
|
2728
|
-
try log.end(ident, .
|
|
2671
|
+
try log.end(.{ ident, .ReflectSilent });
|
|
2729
2672
|
}
|
|
2730
2673
|
if (showdown and volatiles.Toxic) {
|
|
2731
2674
|
volatiles.Toxic = false;
|
|
2732
2675
|
// Pokémon Showdown erroneously clears the toxic counter
|
|
2733
2676
|
volatiles.toxic = 0;
|
|
2734
|
-
try log.end(ident, .
|
|
2677
|
+
try log.end(.{ ident, .ToxicSilent });
|
|
2735
2678
|
}
|
|
2736
2679
|
}
|
|
2737
2680
|
|
|
2738
2681
|
pub const Rolls = struct {
|
|
2739
|
-
|
|
2682
|
+
fn speedTie(battle: anytype, options: anytype) !bool {
|
|
2740
2683
|
const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
|
|
2741
2684
|
player == .P1
|
|
2685
|
+
else if (@hasDecl(@TypeOf(battle.rng), "speedTie"))
|
|
2686
|
+
battle.rng.speedTie()
|
|
2742
2687
|
else if (showdown)
|
|
2743
2688
|
battle.rng.range(u8, 0, 2) == 0
|
|
2744
2689
|
else
|
|
@@ -2748,21 +2693,31 @@ pub const Rolls = struct {
|
|
|
2748
2693
|
return p1;
|
|
2749
2694
|
}
|
|
2750
2695
|
|
|
2751
|
-
|
|
2696
|
+
fn criticalHit(
|
|
2697
|
+
battle: anytype,
|
|
2698
|
+
player: Player,
|
|
2699
|
+
rate: u8,
|
|
2700
|
+
spurious: bool,
|
|
2701
|
+
options: anytype,
|
|
2702
|
+
) !bool {
|
|
2752
2703
|
const crit = if (options.calc.overridden(player, .critical_hit)) |val|
|
|
2753
2704
|
val == .true
|
|
2705
|
+
else if (@hasDecl(@TypeOf(battle.rng), "criticalHit"))
|
|
2706
|
+
battle.rng.criticalHit(player, rate)
|
|
2754
2707
|
else if (showdown)
|
|
2755
2708
|
battle.rng.chance(u8, rate, 256)
|
|
2756
2709
|
else
|
|
2757
2710
|
std.math.rotl(u8, battle.rng.next(), 3) < rate;
|
|
2758
2711
|
|
|
2759
|
-
try options.chance.criticalHit(player, crit, rate);
|
|
2712
|
+
if (!spurious) try options.chance.criticalHit(player, crit, rate);
|
|
2760
2713
|
return crit;
|
|
2761
2714
|
}
|
|
2762
2715
|
|
|
2763
|
-
|
|
2716
|
+
fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2764
2717
|
const roll = if (options.calc.overridden(player, .damage)) |val|
|
|
2765
2718
|
val
|
|
2719
|
+
else if (@hasDecl(@TypeOf(battle.rng), "damage"))
|
|
2720
|
+
battle.rng.damage(player)
|
|
2766
2721
|
else roll: {
|
|
2767
2722
|
if (showdown) break :roll battle.rng.range(u8, 217, 256);
|
|
2768
2723
|
while (true) {
|
|
@@ -2776,21 +2731,27 @@ pub const Rolls = struct {
|
|
|
2776
2731
|
return roll;
|
|
2777
2732
|
}
|
|
2778
2733
|
|
|
2779
|
-
|
|
2734
|
+
const uN = if (pkmn.options.miss) u8 else u9;
|
|
2735
|
+
fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) !bool {
|
|
2736
|
+
const acc: uN = if (pkmn.options.miss) accuracy else @as(uN, accuracy) + 1;
|
|
2780
2737
|
const ok = if (options.calc.overridden(player, .hit)) |val|
|
|
2781
2738
|
val == .true
|
|
2739
|
+
else if (@hasDecl(@TypeOf(battle.rng), "hit"))
|
|
2740
|
+
battle.rng.hit(player, acc)
|
|
2782
2741
|
else if (showdown)
|
|
2783
|
-
battle.rng.chance(
|
|
2742
|
+
battle.rng.chance(uN, acc, 256)
|
|
2784
2743
|
else
|
|
2785
|
-
battle.rng.next() <
|
|
2744
|
+
battle.rng.next() < acc;
|
|
2786
2745
|
|
|
2787
|
-
options.chance.hit(ok,
|
|
2746
|
+
try options.chance.hit(player, ok, acc);
|
|
2788
2747
|
return ok;
|
|
2789
2748
|
}
|
|
2790
2749
|
|
|
2791
|
-
|
|
2750
|
+
fn confused(battle: anytype, player: Player, options: anytype) !bool {
|
|
2792
2751
|
const cfz = if (options.calc.overridden(player, .confused)) |val|
|
|
2793
2752
|
val == .true
|
|
2753
|
+
else if (@hasDecl(@TypeOf(battle.rng), "confused"))
|
|
2754
|
+
battle.rng.confused(player)
|
|
2794
2755
|
else if (showdown)
|
|
2795
2756
|
!battle.rng.chance(u8, 128, 256)
|
|
2796
2757
|
else
|
|
@@ -2800,9 +2761,11 @@ pub const Rolls = struct {
|
|
|
2800
2761
|
return cfz;
|
|
2801
2762
|
}
|
|
2802
2763
|
|
|
2803
|
-
|
|
2764
|
+
fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
|
|
2804
2765
|
const par = if (options.calc.overridden(player, .paralyzed)) |val|
|
|
2805
2766
|
val == .true
|
|
2767
|
+
else if (@hasDecl(@TypeOf(battle.rng), "paralyzed"))
|
|
2768
|
+
battle.rng.paralyzed(player)
|
|
2806
2769
|
else if (showdown)
|
|
2807
2770
|
battle.rng.chance(u8, 63, 256)
|
|
2808
2771
|
else
|
|
@@ -2812,9 +2775,11 @@ pub const Rolls = struct {
|
|
|
2812
2775
|
return par;
|
|
2813
2776
|
}
|
|
2814
2777
|
|
|
2815
|
-
|
|
2778
|
+
fn confusionChance(battle: anytype, player: Player, options: anytype) !bool {
|
|
2816
2779
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2817
2780
|
val == .true
|
|
2781
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2782
|
+
battle.rng.secondaryChance(player, 25)
|
|
2818
2783
|
else if (showdown)
|
|
2819
2784
|
battle.rng.chance(u8, 25, 256)
|
|
2820
2785
|
else
|
|
@@ -2824,11 +2789,13 @@ pub const Rolls = struct {
|
|
|
2824
2789
|
return proc;
|
|
2825
2790
|
}
|
|
2826
2791
|
|
|
2827
|
-
|
|
2792
|
+
fn secondaryChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2828
2793
|
const rate: u8 = if (low) 26 else 77;
|
|
2829
2794
|
|
|
2830
2795
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2831
2796
|
val == .true
|
|
2797
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2798
|
+
battle.rng.secondaryChance(player, rate)
|
|
2832
2799
|
else if (showdown)
|
|
2833
2800
|
battle.rng.chance(u8, rate, 256)
|
|
2834
2801
|
else
|
|
@@ -2838,11 +2805,13 @@ pub const Rolls = struct {
|
|
|
2838
2805
|
return proc;
|
|
2839
2806
|
}
|
|
2840
2807
|
|
|
2841
|
-
|
|
2808
|
+
fn poisonChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2842
2809
|
const rate: u8 = if (low) 52 else 103;
|
|
2843
2810
|
|
|
2844
2811
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2845
2812
|
val == .true
|
|
2813
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2814
|
+
battle.rng.secondaryChance(player, rate)
|
|
2846
2815
|
else if (showdown)
|
|
2847
2816
|
battle.rng.chance(u8, rate, 256)
|
|
2848
2817
|
else
|
|
@@ -2852,9 +2821,11 @@ pub const Rolls = struct {
|
|
|
2852
2821
|
return proc;
|
|
2853
2822
|
}
|
|
2854
2823
|
|
|
2855
|
-
|
|
2824
|
+
fn unboost(battle: anytype, player: Player, options: anytype) !bool {
|
|
2856
2825
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2857
2826
|
val == .true
|
|
2827
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2828
|
+
battle.rng.secondaryChance(player, 85)
|
|
2858
2829
|
else if (showdown)
|
|
2859
2830
|
battle.rng.chance(u8, 85, 256)
|
|
2860
2831
|
else
|
|
@@ -2864,14 +2835,14 @@ pub const Rolls = struct {
|
|
|
2864
2835
|
return proc;
|
|
2865
2836
|
}
|
|
2866
2837
|
|
|
2867
|
-
|
|
2838
|
+
fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
2868
2839
|
const move: Move = if (options.calc.overridden(player, .metronome)) |val|
|
|
2869
2840
|
val
|
|
2870
|
-
else if (
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2841
|
+
else if (@hasDecl(@TypeOf(battle.rng), "metronome"))
|
|
2842
|
+
battle.rng.metronome(player)
|
|
2843
|
+
else if (showdown)
|
|
2844
|
+
Move.METRONOME[battle.rng.range(u8, 0, Move.METRONOME.len)]
|
|
2845
|
+
else move: {
|
|
2875
2846
|
while (true) {
|
|
2876
2847
|
const r = battle.rng.next();
|
|
2877
2848
|
if (r == 0 or r == @intFromEnum(Move.Metronome)) continue;
|
|
@@ -2884,9 +2855,11 @@ pub const Rolls = struct {
|
|
|
2884
2855
|
return move;
|
|
2885
2856
|
}
|
|
2886
2857
|
|
|
2887
|
-
|
|
2858
|
+
fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
|
|
2888
2859
|
const power = if (options.calc.overridden(player, .psywave)) |val|
|
|
2889
2860
|
val - 1
|
|
2861
|
+
else if (@hasDecl(@TypeOf(battle.rng), "psywave"))
|
|
2862
|
+
battle.rng.psywave(player)
|
|
2890
2863
|
else if (showdown)
|
|
2891
2864
|
battle.rng.range(u8, 0, max)
|
|
2892
2865
|
else power: {
|
|
@@ -2901,11 +2874,13 @@ pub const Rolls = struct {
|
|
|
2901
2874
|
return power;
|
|
2902
2875
|
}
|
|
2903
2876
|
|
|
2904
|
-
|
|
2877
|
+
fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2905
2878
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2906
2879
|
@intCast(val)
|
|
2880
|
+
else if (@hasDecl(@TypeOf(battle.rng), "sleepDuration"))
|
|
2881
|
+
battle.rng.sleepDuration(player)
|
|
2907
2882
|
else if (showdown)
|
|
2908
|
-
|
|
2883
|
+
battle.rng.range(u3, 1, 8)
|
|
2909
2884
|
else duration: {
|
|
2910
2885
|
while (true) {
|
|
2911
2886
|
const r = battle.rng.next() & 7;
|
|
@@ -2918,11 +2893,13 @@ pub const Rolls = struct {
|
|
|
2918
2893
|
return duration;
|
|
2919
2894
|
}
|
|
2920
2895
|
|
|
2921
|
-
|
|
2896
|
+
fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
|
|
2922
2897
|
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2923
2898
|
val
|
|
2899
|
+
else if (@hasDecl(@TypeOf(battle.rng), "disableDuration"))
|
|
2900
|
+
battle.rng.disableDuration(player)
|
|
2924
2901
|
else if (showdown)
|
|
2925
|
-
|
|
2902
|
+
battle.rng.range(u4, 1, 9)
|
|
2926
2903
|
else
|
|
2927
2904
|
@intCast((battle.rng.next() & 7) + 1);
|
|
2928
2905
|
|
|
@@ -2931,11 +2908,13 @@ pub const Rolls = struct {
|
|
|
2931
2908
|
return duration;
|
|
2932
2909
|
}
|
|
2933
2910
|
|
|
2934
|
-
|
|
2911
|
+
fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
|
|
2935
2912
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2936
2913
|
@intCast(val)
|
|
2914
|
+
else if (@hasDecl(@TypeOf(battle.rng), "confusionDuration"))
|
|
2915
|
+
battle.rng.confusionDuration(player, self)
|
|
2937
2916
|
else if (showdown)
|
|
2938
|
-
|
|
2917
|
+
battle.rng.range(u3, 2, 6)
|
|
2939
2918
|
else
|
|
2940
2919
|
@intCast((battle.rng.next() & 3) + 2);
|
|
2941
2920
|
|
|
@@ -2944,11 +2923,13 @@ pub const Rolls = struct {
|
|
|
2944
2923
|
return duration;
|
|
2945
2924
|
}
|
|
2946
2925
|
|
|
2947
|
-
|
|
2926
|
+
fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2948
2927
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2949
2928
|
@intCast(val)
|
|
2929
|
+
else if (@hasDecl(@TypeOf(battle.rng), "attackingDuration"))
|
|
2930
|
+
battle.rng.attackingDuration(player)
|
|
2950
2931
|
else if (showdown)
|
|
2951
|
-
|
|
2932
|
+
battle.rng.range(u3, 2, 4)
|
|
2952
2933
|
else
|
|
2953
2934
|
@intCast((battle.rng.next() & 1) + 2);
|
|
2954
2935
|
|
|
@@ -2957,7 +2938,9 @@ pub const Rolls = struct {
|
|
|
2957
2938
|
return duration;
|
|
2958
2939
|
}
|
|
2959
2940
|
|
|
2960
|
-
|
|
2941
|
+
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
2942
|
+
|
|
2943
|
+
fn distribution(
|
|
2961
2944
|
battle: anytype,
|
|
2962
2945
|
comptime effect: Move.Effect,
|
|
2963
2946
|
player: Player,
|
|
@@ -2966,8 +2949,10 @@ pub const Rolls = struct {
|
|
|
2966
2949
|
const roll = if (effect == .Binding) .duration else .multi_hit;
|
|
2967
2950
|
const n: u3 = if (options.calc.overridden(player, roll)) |val|
|
|
2968
2951
|
@intCast(val)
|
|
2952
|
+
else if (@hasDecl(@TypeOf(battle.rng), "distribution"))
|
|
2953
|
+
battle.rng.distribution(player)
|
|
2969
2954
|
else if (showdown)
|
|
2970
|
-
DISTRIBUTION[battle.rng.range(
|
|
2955
|
+
DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
|
|
2971
2956
|
else n: {
|
|
2972
2957
|
const r = (battle.rng.next() & 3);
|
|
2973
2958
|
break :n @intCast((if (r < 2) r else battle.rng.next() & 3) + 2);
|
|
@@ -2998,7 +2983,9 @@ pub const Rolls = struct {
|
|
|
2998
2983
|
else
|
|
2999
2984
|
null;
|
|
3000
2985
|
|
|
3001
|
-
const slot: u4 = overridden orelse
|
|
2986
|
+
const slot: u4 = overridden orelse if (@hasDecl(@TypeOf(battle.rng), "moveSlot"))
|
|
2987
|
+
battle.rng.moveSlot(player, moves, check_pp)
|
|
2988
|
+
else slot: {
|
|
3002
2989
|
if (showdown) {
|
|
3003
2990
|
if (check_pp == 0) {
|
|
3004
2991
|
var i: usize = moves.len;
|
|
@@ -3014,10 +3001,9 @@ pub const Rolls = struct {
|
|
|
3014
3001
|
while (i < moves.len and r > 0) : (i += 1) {
|
|
3015
3002
|
if (moves[i].pp > 0) {
|
|
3016
3003
|
r -= 1;
|
|
3017
|
-
if (r == 0) break;
|
|
3004
|
+
if (r == 0) break :slot @intCast(i + 1);
|
|
3018
3005
|
}
|
|
3019
3006
|
}
|
|
3020
|
-
break :slot @intCast(i + 1);
|
|
3021
3007
|
}
|
|
3022
3008
|
}
|
|
3023
3009
|
|
|
@@ -3039,11 +3025,11 @@ test "RNG agreement" {
|
|
|
3039
3025
|
expected[i] = @intCast(i * 0x1000000);
|
|
3040
3026
|
}
|
|
3041
3027
|
|
|
3042
|
-
var spe
|
|
3043
|
-
var cfz
|
|
3044
|
-
var par
|
|
3045
|
-
var brn
|
|
3046
|
-
var eff
|
|
3028
|
+
var spe: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3029
|
+
var cfz: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3030
|
+
var par: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3031
|
+
var brn: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3032
|
+
var eff: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3047
3033
|
|
|
3048
3034
|
for (0..expected.len) |i| {
|
|
3049
3035
|
try expectEqual(spe.range(u8, 0, 2) == 0, i < Gen12.percent(50) + 1);
|
|
@@ -3079,11 +3065,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3079
3065
|
const side = battle.side(player);
|
|
3080
3066
|
const foe = battle.foe(player);
|
|
3081
3067
|
|
|
3082
|
-
|
|
3068
|
+
const active = &side.active;
|
|
3083
3069
|
const stored = side.stored();
|
|
3084
3070
|
|
|
3085
3071
|
// While players are not given any input options on the cartridge in these cases,
|
|
3086
|
-
// Pokémon Showdown instead produces a list with a single move that must be chosen
|
|
3072
|
+
// Pokémon Showdown instead produces a list with a single move that must be chosen
|
|
3087
3073
|
//
|
|
3088
3074
|
// Given that no input is allowed on the cartridge 'Pass' seems like it would be logical
|
|
3089
3075
|
// here when not in compatibility mode, but the engine needs to be able to differentiate
|
|
@@ -3091,7 +3077,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3091
3077
|
// passing due to being forced into using a move. Instead of introducing another option
|
|
3092
3078
|
// we simply repurpose Move with no move slot, even though pedantically this is not
|
|
3093
3079
|
// strictly correct as the player would not have been presented the option to move or
|
|
3094
|
-
// switch at all
|
|
3080
|
+
// switch at all
|
|
3095
3081
|
if (isForced(active)) {
|
|
3096
3082
|
out[n] = .{ .type = .Move, .data = @intFromBool(showdown) };
|
|
3097
3083
|
n += 1;
|
|
@@ -3110,7 +3096,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3110
3096
|
// On the cartridge, all of these happen after "FIGHT" (indicating you are not
|
|
3111
3097
|
// switching) but before you are allowed to select a move. Pokémon Showdown instead
|
|
3112
3098
|
// either disables all other moves in the case of limited or requires you to select a
|
|
3113
|
-
// move normally if sleeping/frozen/bound
|
|
3099
|
+
// move normally if sleeping/frozen/bound
|
|
3114
3100
|
if (!showdown and (limited or foe.active.volatiles.Binding or
|
|
3115
3101
|
Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP)))
|
|
3116
3102
|
{
|
|
@@ -3123,7 +3109,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3123
3109
|
// Pokémon Showdown handles Bide and Binding moves by checking if the move in question
|
|
3124
3110
|
// is present in the Pokémon's moveset (which means moves called via Metronome / Mirror
|
|
3125
3111
|
// Move will not result in forcing the subsequent use unless the user also had the
|
|
3126
|
-
// proc-ed move in their moveset) and disabling all other moves
|
|
3112
|
+
// proc-ed move in their moveset) and disabling all other moves
|
|
3127
3113
|
if (limited) {
|
|
3128
3114
|
assert(showdown);
|
|
3129
3115
|
assert(side.last_selected_move != .None);
|
|
@@ -3133,7 +3119,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3133
3119
|
if (m.id == if (active.volatiles.Bide) .Bide else side.last_selected_move) {
|
|
3134
3120
|
// Pokémon Showdown displays Struggle if limited to Bide but unable to pick
|
|
3135
3121
|
const struggle =
|
|
3136
|
-
m.id == .Bide and (m.pp == 0 or active.volatiles.
|
|
3122
|
+
m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
|
|
3137
3123
|
const s = if (struggle) 0 else slot;
|
|
3138
3124
|
out[n] = .{ .type = .Move, .data = s };
|
|
3139
3125
|
n += 1;
|
|
@@ -3148,7 +3134,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3148
3134
|
const m = active.moves[slot - 1];
|
|
3149
3135
|
if (m.id == .None) break;
|
|
3150
3136
|
if (m.pp == 0) continue;
|
|
3151
|
-
if (active.volatiles.
|
|
3137
|
+
if (active.volatiles.disable_move == slot) continue;
|
|
3152
3138
|
out[n] = .{ .type = .Move, .data = slot };
|
|
3153
3139
|
n += 1;
|
|
3154
3140
|
}
|