@pkmn/engine 0.1.0-dev.90d6c14c → 0.1.0-dev.9257876b
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 +52 -21
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/data/ids.json +12 -12
- package/build/pkg/data/layout.json +2 -2
- package/build/pkg/data/protocol.json +69 -12
- package/build/pkg/data.d.ts +3 -2
- package/build/pkg/data.js.map +1 -1
- package/build/pkg/gen1.js +2 -2
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/index.d.ts +2 -1
- package/build/pkg/index.js.map +1 -1
- package/build/pkg/protocol.js +57 -38
- package/build/pkg/protocol.js.map +1 -1
- package/build/test/display.js.map +1 -1
- package/build/tools/debug.js +3 -1
- package/build/tools/debug.js.map +1 -1
- package/build.zig +125 -102
- package/package.json +43 -23
- package/src/bin/install-pkmn-engine +27 -19
- package/src/lib/common/data.zig +12 -24
- package/src/lib/common/optional.zig +9 -2
- package/src/lib/common/protocol.zig +805 -327
- package/src/lib/common/rational.zig +12 -8
- package/src/lib/common/rng.zig +26 -17
- package/src/lib/gen1/calc.zig +9 -10
- package/src/lib/gen1/chance.zig +83 -66
- 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 +60 -67
- package/src/lib/gen1/helpers.zig +261 -123
- package/src/lib/gen1/mechanics.zig +370 -403
- 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 +8 -12
- package/src/lib/gen2/mechanics.zig +3587 -9
- package/src/lib/{binding/node.zig → node.zig} +10 -13
- package/src/lib/pkmn.zig +26 -3
- package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
- package/src/lib/gen1/README.md +0 -812
- 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,9 +160,7 @@ fn selectMove(
|
|
|
172
160
|
return null;
|
|
173
161
|
}
|
|
174
162
|
if (volatiles.Binding) {
|
|
175
|
-
from.* = side.last_used_move;
|
|
176
163
|
if (showdown) {
|
|
177
|
-
if (foe_choice.type == .Switch) from.* = null;
|
|
178
164
|
// Pokémon Showdown overwrites Mirror Move with whatever was selected - really this
|
|
179
165
|
// should set side.last_selected_move = last.id to reuse Mirror Move and fail in order
|
|
180
166
|
// to satisfy the conditions of the Desync Clause Mod. However, because Binding is still
|
|
@@ -186,10 +172,7 @@ fn selectMove(
|
|
|
186
172
|
assert(choice.data == 0);
|
|
187
173
|
// GLITCH: https://glitchcity.wiki/Partial_trapping_move_Mirror_Move_link_battle_glitch
|
|
188
174
|
if (foe_choice.type == .Switch) {
|
|
189
|
-
const last = side.active.move(
|
|
190
|
-
battle.last_moves.p1_index
|
|
191
|
-
else
|
|
192
|
-
battle.last_moves.p2_index);
|
|
175
|
+
const last = side.active.move(battle.lastMove(player).index);
|
|
193
176
|
if (last.id == .Metronome) side.last_selected_move = last.id;
|
|
194
177
|
if (last.id == .MirrorMove) return Result.Error;
|
|
195
178
|
}
|
|
@@ -213,16 +196,13 @@ fn selectMove(
|
|
|
213
196
|
if (choice.data == 0) {
|
|
214
197
|
const struggle = ok: {
|
|
215
198
|
for (side.active.moves, 0..) |move, i| {
|
|
216
|
-
if (move.pp > 0 and volatiles.
|
|
199
|
+
if (move.pp > 0 and volatiles.disable_move != i + 1) break :ok false;
|
|
217
200
|
}
|
|
218
201
|
break :ok true;
|
|
219
202
|
};
|
|
220
|
-
|
|
221
203
|
assert(struggle);
|
|
222
|
-
saveMove(battle, player, choice);
|
|
223
|
-
} else {
|
|
224
|
-
saveMove(battle, player, choice);
|
|
225
204
|
}
|
|
205
|
+
saveMove(battle, player, choice);
|
|
226
206
|
|
|
227
207
|
return null;
|
|
228
208
|
}
|
|
@@ -235,18 +215,14 @@ fn saveMove(battle: anytype, player: Player, choice: ?Choice) void {
|
|
|
235
215
|
if (c.data == 0) {
|
|
236
216
|
side.last_selected_move = .Struggle;
|
|
237
217
|
} else {
|
|
238
|
-
assert(showdown or side.active.volatiles.
|
|
218
|
+
assert(showdown or side.active.volatiles.disable_move != c.data);
|
|
239
219
|
const move = side.active.move(c.data);
|
|
240
220
|
// You cannot *select* a move with 0 PP (except on Pokémon Showdown where that is
|
|
241
221
|
// sometimes required...), but a 0 PP move can be used automatically
|
|
242
222
|
assert(showdown or move.pp != 0);
|
|
243
223
|
|
|
244
224
|
side.last_selected_move = move.id;
|
|
245
|
-
|
|
246
|
-
battle.last_moves.p1_index = @intCast(c.data);
|
|
247
|
-
} else {
|
|
248
|
-
battle.last_moves.p2_index = @intCast(c.data);
|
|
249
|
-
}
|
|
225
|
+
battle.lastMove(player).index = @intCast(c.data);
|
|
250
226
|
}
|
|
251
227
|
}
|
|
252
228
|
}
|
|
@@ -264,11 +240,7 @@ fn switchIn(battle: anytype, player: Player, slot: u8, initial: bool, options: a
|
|
|
264
240
|
side.order[0] = side.order[slot - 1];
|
|
265
241
|
side.order[slot - 1] = out;
|
|
266
242
|
|
|
267
|
-
|
|
268
|
-
battle.last_moves.p1_index = 1;
|
|
269
|
-
} else {
|
|
270
|
-
battle.last_moves.p2_index = 1;
|
|
271
|
-
}
|
|
243
|
+
battle.lastMove(player).index = 1;
|
|
272
244
|
|
|
273
245
|
side.last_used_move = .None;
|
|
274
246
|
foe.last_used_move = .None;
|
|
@@ -284,14 +256,14 @@ fn switchIn(battle: anytype, player: Player, slot: u8, initial: bool, options: a
|
|
|
284
256
|
|
|
285
257
|
foe.active.volatiles.Binding = false;
|
|
286
258
|
|
|
287
|
-
try options.log.switched(battle.active(player), incoming);
|
|
259
|
+
try options.log.switched(.{ battle.active(player), incoming });
|
|
288
260
|
options.chance.switched(player, side.order[0], out);
|
|
289
261
|
|
|
290
262
|
if (showdown and incoming.status == Status.TOX) {
|
|
291
263
|
incoming.status = Status.init(.PSN);
|
|
292
264
|
// Technically, Pokémon Showdown adds these after *both* Pokémon have switched, but we'd
|
|
293
265
|
// rather not clutter up turnOrder just for this (incorrect) log message
|
|
294
|
-
try options.log.status(battle.active(player), incoming.status, .Silent);
|
|
266
|
+
try options.log.status(.{ battle.active(player), incoming.status, .Silent });
|
|
295
267
|
}
|
|
296
268
|
}
|
|
297
269
|
|
|
@@ -354,12 +326,10 @@ fn doTurn(
|
|
|
354
326
|
player_choice: Choice,
|
|
355
327
|
player_rewrap: bool,
|
|
356
328
|
player_skip: bool,
|
|
357
|
-
player_from: ?Move,
|
|
358
329
|
foe_player: Player,
|
|
359
330
|
foe_choice: Choice,
|
|
360
331
|
foe_rewrap: bool,
|
|
361
332
|
foe_skip: bool,
|
|
362
|
-
foe_from: ?Move,
|
|
363
333
|
options: anytype,
|
|
364
334
|
) !?Result {
|
|
365
335
|
assert(player_choice.type != .Pass);
|
|
@@ -372,7 +342,6 @@ fn doTurn(
|
|
|
372
342
|
player_choice,
|
|
373
343
|
player_rewrap,
|
|
374
344
|
player_skip,
|
|
375
|
-
player_from,
|
|
376
345
|
&residual,
|
|
377
346
|
options,
|
|
378
347
|
)) |r| return r;
|
|
@@ -395,7 +364,6 @@ fn doTurn(
|
|
|
395
364
|
foe_choice,
|
|
396
365
|
foe_rewrap,
|
|
397
366
|
foe_skip,
|
|
398
|
-
foe_from,
|
|
399
367
|
&residual,
|
|
400
368
|
options,
|
|
401
369
|
)) |r| return r;
|
|
@@ -423,7 +391,6 @@ fn executeMove(
|
|
|
423
391
|
choice: Choice,
|
|
424
392
|
rewrap: bool,
|
|
425
393
|
skip: bool,
|
|
426
|
-
from: ?Move,
|
|
427
394
|
residual: *bool,
|
|
428
395
|
options: anytype,
|
|
429
396
|
) !?Result {
|
|
@@ -440,7 +407,7 @@ fn executeMove(
|
|
|
440
407
|
if (side.last_selected_move == .SKIP_TURN) {
|
|
441
408
|
assert(!showdown);
|
|
442
409
|
if (battle.foe(player).active.volatiles.Binding) {
|
|
443
|
-
try options.log.cant(battle.active(player), .Bound);
|
|
410
|
+
try options.log.cant(.{ battle.active(player), .Bound });
|
|
444
411
|
}
|
|
445
412
|
return null;
|
|
446
413
|
}
|
|
@@ -455,14 +422,13 @@ fn executeMove(
|
|
|
455
422
|
if (mslot == 0 and side.last_selected_move != .None and side.last_selected_move != .Struggle) {
|
|
456
423
|
// choice.data == 0 only happens with Struggle on Pokémon Showdown
|
|
457
424
|
assert(!showdown);
|
|
458
|
-
mslot = @intCast(
|
|
459
|
-
battle.last_moves.p1_index
|
|
460
|
-
else
|
|
461
|
-
battle.last_moves.p2_index);
|
|
425
|
+
mslot = @intCast(battle.lastMove(player).index);
|
|
462
426
|
const stored = side.stored();
|
|
463
427
|
// GLITCH: Struggle bypass PP underflow via Hyper Beam / Trapping-switch auto selection
|
|
464
|
-
auto = side.
|
|
465
|
-
|
|
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);
|
|
466
432
|
// If it wasn't Hyper Beam or the continuation of a move effect then we must have just
|
|
467
433
|
// thawed, in which case we will desync unless the last_selected_move happened to be at
|
|
468
434
|
// index 1 and the current Pokémon has the same move in its first slot.
|
|
@@ -479,15 +445,12 @@ fn executeMove(
|
|
|
479
445
|
} else if (showdown and side.active.volatiles.Charging) {
|
|
480
446
|
// Incorrect mslot with Pokémon Showdown choice semantics so we need to recover from index
|
|
481
447
|
assert(mslot == 1);
|
|
482
|
-
mslot = @intCast(
|
|
483
|
-
battle.last_moves.p1_index
|
|
484
|
-
else
|
|
485
|
-
battle.last_moves.p2_index);
|
|
448
|
+
mslot = @intCast(battle.lastMove(player).index);
|
|
486
449
|
}
|
|
487
450
|
|
|
488
451
|
var skip_can = false;
|
|
489
452
|
var skip_pp = false;
|
|
490
|
-
switch (try beforeMove(battle, player, skip,
|
|
453
|
+
switch (try beforeMove(battle, player, skip, residual, options)) {
|
|
491
454
|
.done => return null,
|
|
492
455
|
.skip_can => skip_can = true,
|
|
493
456
|
.skip_pp => skip_pp = true,
|
|
@@ -495,9 +458,9 @@ fn executeMove(
|
|
|
495
458
|
.err => return @as(?Result, Result.Error),
|
|
496
459
|
}
|
|
497
460
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
461
|
+
const can = skip_can or
|
|
462
|
+
try canMove(battle, player, mslot, auto, skip_pp, .None, residual, options);
|
|
463
|
+
if (!can) return null;
|
|
501
464
|
|
|
502
465
|
return doMove(battle, player, mslot, rewrap, auto, residual, options);
|
|
503
466
|
}
|
|
@@ -508,7 +471,6 @@ fn beforeMove(
|
|
|
508
471
|
battle: anytype,
|
|
509
472
|
player: Player,
|
|
510
473
|
skip: bool,
|
|
511
|
-
from: ?Move,
|
|
512
474
|
residual: *bool,
|
|
513
475
|
options: anytype,
|
|
514
476
|
) !BeforeMove {
|
|
@@ -535,23 +497,23 @@ fn beforeMove(
|
|
|
535
497
|
}
|
|
536
498
|
|
|
537
499
|
if (duration == 0) {
|
|
538
|
-
try log.curestatus(ident, before, .Message);
|
|
500
|
+
try log.curestatus(.{ ident, before, .Message });
|
|
539
501
|
stored.status = 0; // clears EXT if present
|
|
540
502
|
} else {
|
|
541
|
-
try log.cant(ident, .Sleep);
|
|
503
|
+
try log.cant(.{ ident, .Sleep });
|
|
542
504
|
}
|
|
543
505
|
side.last_used_move = .None;
|
|
544
506
|
return .done;
|
|
545
507
|
}
|
|
546
508
|
|
|
547
509
|
if (Status.is(stored.status, .FRZ)) {
|
|
548
|
-
try log.cant(ident, .Freeze);
|
|
510
|
+
try log.cant(.{ ident, .Freeze });
|
|
549
511
|
side.last_used_move = .None;
|
|
550
512
|
return .done;
|
|
551
513
|
}
|
|
552
514
|
|
|
553
515
|
if (skip or foe.active.volatiles.Binding) {
|
|
554
|
-
try log.cant(ident, .Bound);
|
|
516
|
+
try log.cant(.{ ident, .Bound });
|
|
555
517
|
return .done;
|
|
556
518
|
}
|
|
557
519
|
|
|
@@ -559,27 +521,27 @@ fn beforeMove(
|
|
|
559
521
|
// Pokémon Showdown doesn't clear Flinch until its imaginary "residual" phase, meaning
|
|
560
522
|
// Pokémon can sometimes flinch multiple times from the same original hit
|
|
561
523
|
if (!showdown) volatiles.Flinch = false;
|
|
562
|
-
try log.cant(ident, .Flinch);
|
|
524
|
+
try log.cant(.{ ident, .Flinch });
|
|
563
525
|
return .done;
|
|
564
526
|
}
|
|
565
527
|
|
|
566
528
|
if (volatiles.Recharging) {
|
|
567
529
|
volatiles.Recharging = false;
|
|
568
|
-
try log.cant(ident, .Recharge);
|
|
530
|
+
try log.cant(.{ ident, .Recharge });
|
|
569
531
|
return .done;
|
|
570
532
|
}
|
|
571
533
|
|
|
572
|
-
if (volatiles.
|
|
534
|
+
if (volatiles.disable_duration > 0) {
|
|
573
535
|
if (options.calc.modify(player, .disable)) |extend| {
|
|
574
|
-
if (!extend) volatiles.
|
|
536
|
+
if (!extend) volatiles.disable_duration = 0;
|
|
575
537
|
} else {
|
|
576
|
-
volatiles.
|
|
538
|
+
volatiles.disable_duration -= 1;
|
|
577
539
|
}
|
|
578
|
-
try options.chance.disable(player, volatiles.
|
|
540
|
+
try options.chance.disable(player, volatiles.disable_duration);
|
|
579
541
|
|
|
580
|
-
if (volatiles.
|
|
581
|
-
volatiles.
|
|
582
|
-
try log.end(ident, .Disable);
|
|
542
|
+
if (volatiles.disable_duration == 0) {
|
|
543
|
+
volatiles.disable_move = 0;
|
|
544
|
+
try log.end(.{ ident, .Disable });
|
|
583
545
|
}
|
|
584
546
|
}
|
|
585
547
|
// Pokémon Showdown's disable condition has a single onBeforeMove handler
|
|
@@ -604,9 +566,9 @@ fn beforeMove(
|
|
|
604
566
|
|
|
605
567
|
if (volatiles.confusion == 0) {
|
|
606
568
|
volatiles.Confusion = false;
|
|
607
|
-
try log.end(ident, .Confusion);
|
|
569
|
+
try log.end(.{ ident, .Confusion });
|
|
608
570
|
} else {
|
|
609
|
-
try log.activate(ident, .Confusion);
|
|
571
|
+
try log.activate(.{ ident, .Confusion });
|
|
610
572
|
|
|
611
573
|
if (try Rolls.confused(battle, player, options)) {
|
|
612
574
|
assert(!volatiles.MultiHit);
|
|
@@ -657,7 +619,7 @@ fn beforeMove(
|
|
|
657
619
|
volatiles.Binding = false;
|
|
658
620
|
options.chance.clearDurations(player, null);
|
|
659
621
|
// GLITCH: Invulnerable is not cleared, resulting in permanent Fly/Dig invulnerability
|
|
660
|
-
try log.cant(ident, .Paralysis);
|
|
622
|
+
try log.cant(.{ ident, .Paralysis });
|
|
661
623
|
return .done;
|
|
662
624
|
}
|
|
663
625
|
|
|
@@ -673,8 +635,6 @@ fn beforeMove(
|
|
|
673
635
|
volatiles.state +%= battle.last_damage;
|
|
674
636
|
}
|
|
675
637
|
|
|
676
|
-
assert(volatiles.attacks > 0);
|
|
677
|
-
|
|
678
638
|
if (options.calc.modify(player, .attacking)) |extend| {
|
|
679
639
|
if (!extend) volatiles.attacks = 0;
|
|
680
640
|
} else {
|
|
@@ -683,18 +643,18 @@ fn beforeMove(
|
|
|
683
643
|
try options.chance.attacking(player, volatiles.attacks);
|
|
684
644
|
|
|
685
645
|
if (volatiles.attacks != 0) {
|
|
686
|
-
try log.activate(ident, .Bide);
|
|
646
|
+
try log.activate(.{ ident, .Bide });
|
|
687
647
|
return .done;
|
|
688
648
|
}
|
|
689
649
|
|
|
690
650
|
volatiles.Bide = false;
|
|
691
|
-
try log.end(ident, .Bide);
|
|
651
|
+
try log.end(.{ ident, .Bide });
|
|
692
652
|
|
|
693
653
|
battle.last_damage = volatiles.state *% 2;
|
|
694
654
|
volatiles.state = 0;
|
|
695
655
|
|
|
696
656
|
if (battle.last_damage == 0) {
|
|
697
|
-
try log.fail(ident, .None);
|
|
657
|
+
try log.fail(.{ ident, .None });
|
|
698
658
|
return .done;
|
|
699
659
|
}
|
|
700
660
|
|
|
@@ -714,9 +674,6 @@ fn beforeMove(
|
|
|
714
674
|
}
|
|
715
675
|
|
|
716
676
|
if (volatiles.Thrashing) {
|
|
717
|
-
assert(volatiles.attacks > 0);
|
|
718
|
-
assert(from != null);
|
|
719
|
-
|
|
720
677
|
if (options.calc.modify(player, .attacking)) |extend| {
|
|
721
678
|
if (!extend) volatiles.attacks = 0;
|
|
722
679
|
} else {
|
|
@@ -725,23 +682,21 @@ fn beforeMove(
|
|
|
725
682
|
try options.chance.attacking(player, volatiles.attacks);
|
|
726
683
|
|
|
727
684
|
if (!showdown and handleThrashing(battle, active, player, options)) {
|
|
728
|
-
try log.start(battle.active(player), .ConfusionSilent);
|
|
685
|
+
try log.start(.{ battle.active(player), .ConfusionSilent });
|
|
729
686
|
}
|
|
730
|
-
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()) });
|
|
731
688
|
if (showdown) {
|
|
732
689
|
// This shouldn't actually set last_used_move, but Pokémon Showdown sets last
|
|
733
690
|
// used in useMove and doesn't have the notion of skipping canMove semantics
|
|
734
691
|
side.last_used_move = side.last_selected_move;
|
|
735
692
|
if (handleThrashing(battle, active, player, options)) {
|
|
736
|
-
try log.start(battle.active(player), .ConfusionSilent);
|
|
693
|
+
try log.start(.{ battle.active(player), .ConfusionSilent });
|
|
737
694
|
}
|
|
738
695
|
}
|
|
739
696
|
return .skip_can;
|
|
740
697
|
}
|
|
741
698
|
|
|
742
699
|
if (volatiles.Binding) {
|
|
743
|
-
assert(volatiles.attacks > 0);
|
|
744
|
-
|
|
745
700
|
if (options.calc.modify(player, .binding)) |extend| {
|
|
746
701
|
if (!extend) volatiles.attacks = 0;
|
|
747
702
|
} else {
|
|
@@ -749,12 +704,7 @@ fn beforeMove(
|
|
|
749
704
|
}
|
|
750
705
|
try options.chance.binding(player, volatiles.attacks);
|
|
751
706
|
|
|
752
|
-
try log.move(
|
|
753
|
-
battle.active(player),
|
|
754
|
-
side.last_selected_move,
|
|
755
|
-
battle.active(player.foe()),
|
|
756
|
-
side.last_selected_move,
|
|
757
|
-
);
|
|
707
|
+
try log.move(.{ ident, side.last_selected_move, battle.active(player.foe()) });
|
|
758
708
|
if (showdown or battle.last_damage != 0) {
|
|
759
709
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
760
710
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -774,7 +724,7 @@ fn canMove(
|
|
|
774
724
|
mslot: u4,
|
|
775
725
|
auto: bool,
|
|
776
726
|
skip_pp: bool,
|
|
777
|
-
from:
|
|
727
|
+
from: Move,
|
|
778
728
|
residual: *bool,
|
|
779
729
|
options: anytype,
|
|
780
730
|
) !bool {
|
|
@@ -782,33 +732,21 @@ fn canMove(
|
|
|
782
732
|
const player_ident = battle.active(player);
|
|
783
733
|
const move = Move.get(side.last_selected_move);
|
|
784
734
|
|
|
785
|
-
const foe_last = battle.foe(player).last_used_move;
|
|
786
|
-
const special = from != null and (from.? == .Metronome and
|
|
787
|
-
(from.? == .MirrorMove and !(foe_last == .None or foe_last == .MirrorMove)));
|
|
788
|
-
|
|
789
|
-
var skip = skip_pp;
|
|
790
735
|
if (side.active.volatiles.Charging) {
|
|
791
736
|
side.active.volatiles.Charging = false;
|
|
792
737
|
side.active.volatiles.Invulnerable = false;
|
|
793
738
|
} else if (move.effect == .Charge) {
|
|
794
|
-
try options.log.move(player_ident, side.last_selected_move,
|
|
739
|
+
try options.log.move(.{ player_ident, side.last_selected_move, ID{}, from });
|
|
795
740
|
setCounterable(battle, player, side, move);
|
|
796
741
|
try Effects.charge(battle, player, options);
|
|
797
742
|
return false;
|
|
798
743
|
}
|
|
799
744
|
|
|
800
|
-
|
|
801
|
-
if (!
|
|
745
|
+
side.last_used_move = side.last_selected_move;
|
|
746
|
+
if (!skip_pp) decrementPP(side, mslot, auto);
|
|
802
747
|
|
|
803
748
|
const target = if (move.target == .Self) player else player.foe();
|
|
804
|
-
|
|
805
|
-
// don't have space to be able to track it in all circumstances and thus usually try to infer it
|
|
806
|
-
// from side.last_used_move. However, this gets reset by the opponent switching or fainting,
|
|
807
|
-
// meaning sometimes we lose this information. from is still set because it not being null is
|
|
808
|
-
// relevant for control flow, but we don't have anything useful to put in the log. This is
|
|
809
|
-
// regrettable, but this information is more "nice to have" than required
|
|
810
|
-
const f = if (from != null and from.? == .None) null else from;
|
|
811
|
-
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 });
|
|
812
750
|
setCounterable(battle, player, side, move);
|
|
813
751
|
|
|
814
752
|
if (move.effect.onBegin()) {
|
|
@@ -834,11 +772,7 @@ fn setCounterable(battle: anytype, player: Player, side: *Side, move: Move.Data)
|
|
|
834
772
|
// as the desync results from actually having two separate battle states that subtly disagree
|
|
835
773
|
const counterable = side.last_selected_move != .Counter and move.bp > 0 and
|
|
836
774
|
(move.type == .Normal or move.type == .Fighting);
|
|
837
|
-
|
|
838
|
-
battle.last_moves.p1_counterable = @intFromBool(counterable);
|
|
839
|
-
} else {
|
|
840
|
-
battle.last_moves.p2_counterable = @intFromBool(counterable);
|
|
841
|
-
}
|
|
775
|
+
battle.lastMove(player).counterable = @intFromBool(counterable);
|
|
842
776
|
}
|
|
843
777
|
|
|
844
778
|
fn decrementPP(side: *Side, mslot: u4, auto: bool) void {
|
|
@@ -850,12 +784,14 @@ fn decrementPP(side: *Side, mslot: u4, auto: bool) void {
|
|
|
850
784
|
assert(!volatiles.Rage and !volatiles.Thrashing and !volatiles.MultiHit);
|
|
851
785
|
if (volatiles.Bide) return;
|
|
852
786
|
|
|
853
|
-
|
|
854
|
-
|
|
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;
|
|
855
790
|
if (volatiles.Transform) return;
|
|
856
791
|
|
|
857
|
-
|
|
858
|
-
|
|
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;
|
|
859
795
|
assert(active.move(mslot).pp == side.stored().move(mslot).pp);
|
|
860
796
|
}
|
|
861
797
|
|
|
@@ -959,7 +895,7 @@ fn doMove(
|
|
|
959
895
|
}
|
|
960
896
|
}
|
|
961
897
|
|
|
962
|
-
|
|
898
|
+
const zero = battle.last_damage == 0;
|
|
963
899
|
miss = if (showdown or skip)
|
|
964
900
|
miss
|
|
965
901
|
else
|
|
@@ -987,16 +923,16 @@ fn doMove(
|
|
|
987
923
|
ohko = (!showdown or (!immune and !invulnerable)) and
|
|
988
924
|
move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe;
|
|
989
925
|
if (ohko) {
|
|
990
|
-
try log.immune(foe_ident, .OHKO);
|
|
926
|
+
try log.immune(.{ foe_ident, .OHKO });
|
|
991
927
|
} else if (immune and !invulnerable and (!showdown or move.effect != .Binding)) {
|
|
992
|
-
try log.immune(foe_ident, .None);
|
|
928
|
+
try log.immune(.{ foe_ident, .None });
|
|
993
929
|
} else if (mist) {
|
|
994
|
-
if (!foe.active.volatiles.Substitute) try log.activate(foe_ident, .Mist);
|
|
995
|
-
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 });
|
|
996
932
|
} else {
|
|
997
933
|
if (showdown or !zero) try options.chance.commit(player, .miss);
|
|
998
|
-
try log.lastmiss();
|
|
999
|
-
try log.miss(battle.active(player));
|
|
934
|
+
try log.lastmiss(.{});
|
|
935
|
+
try log.miss(.{battle.active(player)});
|
|
1000
936
|
}
|
|
1001
937
|
if (move.effect == .JumpKick) {
|
|
1002
938
|
// Recoil is supposed to be damage/8 but damage will always be 0 here
|
|
@@ -1032,15 +968,15 @@ fn doMove(
|
|
|
1032
968
|
var hit: u4 = 0;
|
|
1033
969
|
while (hit < hits) {
|
|
1034
970
|
if (hit == 0) {
|
|
1035
|
-
if (crit) try log.crit(battle.active(player.foe()));
|
|
971
|
+
if (crit) try log.crit(.{battle.active(player.foe())});
|
|
1036
972
|
if (effectiveness > Effectiveness.neutral) {
|
|
1037
|
-
try log.supereffective(battle.active(player.foe()));
|
|
973
|
+
try log.supereffective(.{battle.active(player.foe())});
|
|
1038
974
|
} else if (effectiveness < Effectiveness.neutral) {
|
|
1039
|
-
try log.resisted(battle.active(player.foe()));
|
|
975
|
+
try log.resisted(.{battle.active(player.foe())});
|
|
1040
976
|
}
|
|
1041
977
|
}
|
|
1042
978
|
if (!skip) nullified = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
1043
|
-
if (hit == 0 and ohko) try log.ohko();
|
|
979
|
+
if (hit == 0 and ohko) try log.ohko(.{});
|
|
1044
980
|
hit += 1;
|
|
1045
981
|
if (foe.stored().hp == 0) break;
|
|
1046
982
|
if (!late and (!sub or move.effect == .Explode)) {
|
|
@@ -1057,7 +993,7 @@ fn doMove(
|
|
|
1057
993
|
if (showdown and move.effect == .Twineedle and !nullified and foe.stored().hp > 0) {
|
|
1058
994
|
try Effects.poison(battle, player, Move.get(.PoisonSting), options);
|
|
1059
995
|
}
|
|
1060
|
-
try log.hitcount(battle.active(player.foe()), hit);
|
|
996
|
+
try log.hitcount(.{ battle.active(player.foe()), hit });
|
|
1061
997
|
} else if (showdown) {
|
|
1062
998
|
// This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
|
|
1063
999
|
if (move.effect == .Binding) {
|
|
@@ -1067,9 +1003,9 @@ fn doMove(
|
|
|
1067
1003
|
assert(foe.stored().hp > 0);
|
|
1068
1004
|
// Pokémon Showdown logs |-damage| here instead of |-immune| because logic...
|
|
1069
1005
|
if (sub) {
|
|
1070
|
-
try log.activate(battle.active(player.foe()), .Substitute);
|
|
1006
|
+
try log.activate(.{ battle.active(player.foe()), .Substitute });
|
|
1071
1007
|
} else {
|
|
1072
|
-
try log.damage(battle.active(player.foe()), foe.stored(), .None);
|
|
1008
|
+
try log.damage(.{ battle.active(player.foe()), foe.stored(), .None });
|
|
1073
1009
|
try buildRage(battle, player.foe(), options);
|
|
1074
1010
|
}
|
|
1075
1011
|
return null;
|
|
@@ -1117,7 +1053,7 @@ fn checkCriticalHit(battle: anytype, player: Player, move: Move.Data, options: a
|
|
|
1117
1053
|
const side = battle.side(player);
|
|
1118
1054
|
|
|
1119
1055
|
// Base speed is used for the critical hit calculation, even when Transform-ed
|
|
1120
|
-
var rate =
|
|
1056
|
+
var rate: u16 = Species.chance(side.stored().species);
|
|
1121
1057
|
// GLITCH: Focus Energy reduces critical hit rate instead of increasing it
|
|
1122
1058
|
rate = if (side.active.volatiles.FocusEnergy) rate / 2 else @min(rate * 2, 255);
|
|
1123
1059
|
rate = if (move.effect == .HighCritical) @min(rate * 4, 255) else rate / 2;
|
|
@@ -1174,9 +1110,9 @@ fn calcDamage(
|
|
|
1174
1110
|
def = @max((def / 4) & 255, if (showdown) 1 else 0);
|
|
1175
1111
|
}
|
|
1176
1112
|
|
|
1177
|
-
const lvl =
|
|
1113
|
+
const lvl: u32 = side.stored().level * @as(u2, if (crit) 2 else 1);
|
|
1178
1114
|
|
|
1179
|
-
def =
|
|
1115
|
+
def = if (move.effect == .Explode) @max(def / 2, 1) else def;
|
|
1180
1116
|
|
|
1181
1117
|
if (def == 0) return false;
|
|
1182
1118
|
|
|
@@ -1267,7 +1203,7 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1267
1203
|
const foe = battle.foe(player);
|
|
1268
1204
|
|
|
1269
1205
|
if (battle.last_damage == 0) {
|
|
1270
|
-
try options.log.fail(battle.active(player), .None);
|
|
1206
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1271
1207
|
return null;
|
|
1272
1208
|
}
|
|
1273
1209
|
|
|
@@ -1278,25 +1214,21 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1278
1214
|
else
|
|
1279
1215
|
foe.last_selected_move);
|
|
1280
1216
|
|
|
1281
|
-
const used = (
|
|
1282
|
-
battle.last_moves.p1_counterable
|
|
1283
|
-
else
|
|
1284
|
-
battle.last_moves.p2_counterable) != 0;
|
|
1285
|
-
|
|
1217
|
+
const used = battle.lastMove(player.foe()).counterable != 0;
|
|
1286
1218
|
const selected = foe_last_selected_move.bp > 0 and
|
|
1287
1219
|
foe.last_selected_move != .Counter and
|
|
1288
1220
|
(foe_last_selected_move.type == .Normal or
|
|
1289
1221
|
foe_last_selected_move.type == .Fighting);
|
|
1290
1222
|
|
|
1291
1223
|
if (!used and !selected) {
|
|
1292
|
-
try options.log.fail(battle.active(player), .None);
|
|
1224
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1293
1225
|
return null;
|
|
1294
1226
|
}
|
|
1295
1227
|
|
|
1296
1228
|
if (!used or !selected) {
|
|
1297
1229
|
// GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
|
|
1298
1230
|
if (!showdown) return Result.Error;
|
|
1299
|
-
try options.log.fail(battle.active(player), .None);
|
|
1231
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1300
1232
|
return null;
|
|
1301
1233
|
}
|
|
1302
1234
|
|
|
@@ -1332,20 +1264,20 @@ fn applyDamage(
|
|
|
1332
1264
|
subbed.active.volatiles.substitute = 0;
|
|
1333
1265
|
subbed.active.volatiles.Substitute = false;
|
|
1334
1266
|
// battle.last_damage is not updated with the amount of HP the Substitute had
|
|
1335
|
-
try options.log.end(battle.active(sub_player), .Substitute);
|
|
1267
|
+
try options.log.end(.{ battle.active(sub_player), .Substitute });
|
|
1336
1268
|
options.calc.capped(target_player);
|
|
1337
1269
|
return true;
|
|
1338
1270
|
} else {
|
|
1339
1271
|
// Safe to truncate since less than subbed.volatiles.substitute which is a u8
|
|
1340
1272
|
subbed.active.volatiles.substitute -= @intCast(battle.last_damage);
|
|
1341
|
-
try options.log.activate(battle.active(sub_player), .Substitute);
|
|
1273
|
+
try options.log.activate(.{ battle.active(sub_player), .Substitute });
|
|
1342
1274
|
return false;
|
|
1343
1275
|
}
|
|
1344
1276
|
}
|
|
1345
1277
|
|
|
1346
1278
|
if (battle.last_damage > target.stored().hp) battle.last_damage = target.stored().hp;
|
|
1347
1279
|
target.stored().hp -= battle.last_damage;
|
|
1348
|
-
try options.log.damage(battle.active(target_player), target.stored(), reason);
|
|
1280
|
+
try options.log.damage(.{ battle.active(target_player), target.stored(), reason });
|
|
1349
1281
|
return false;
|
|
1350
1282
|
}
|
|
1351
1283
|
|
|
@@ -1357,14 +1289,14 @@ fn mirrorMove(
|
|
|
1357
1289
|
auto: bool,
|
|
1358
1290
|
residual: *bool,
|
|
1359
1291
|
options: anytype,
|
|
1360
|
-
) !?Result {
|
|
1292
|
+
) anyerror!?Result {
|
|
1361
1293
|
var side = battle.side(player);
|
|
1362
1294
|
const foe = battle.foe(player);
|
|
1363
1295
|
|
|
1364
1296
|
side.last_selected_move = foe.last_used_move;
|
|
1365
1297
|
|
|
1366
1298
|
if (foe.last_used_move == .None or foe.last_used_move == .MirrorMove) {
|
|
1367
|
-
try options.log.fail(battle.active(player), .None);
|
|
1299
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
1368
1300
|
return null;
|
|
1369
1301
|
} else if (!showdown or foe.last_used_move != .Struggle) {
|
|
1370
1302
|
incrementPP(side, mslot);
|
|
@@ -1384,7 +1316,7 @@ fn metronome(
|
|
|
1384
1316
|
auto: bool,
|
|
1385
1317
|
residual: *bool,
|
|
1386
1318
|
options: anytype,
|
|
1387
|
-
) !?Result {
|
|
1319
|
+
) anyerror!?Result {
|
|
1388
1320
|
var side = battle.side(player);
|
|
1389
1321
|
|
|
1390
1322
|
side.last_selected_move = try Rolls.metronome(battle, player, options);
|
|
@@ -1406,12 +1338,12 @@ fn checkHit(battle: anytype, player: Player, move: Move.Data, options: anytype)
|
|
|
1406
1338
|
if (mist) {
|
|
1407
1339
|
assert(!showdown);
|
|
1408
1340
|
const foe_ident = battle.active(player.foe());
|
|
1409
|
-
try options.log.activate(foe_ident, .Mist);
|
|
1410
|
-
try options.log.fail(foe_ident, .None);
|
|
1341
|
+
try options.log.activate(.{ foe_ident, .Mist });
|
|
1342
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1411
1343
|
} else {
|
|
1412
1344
|
try options.chance.commit(player, .miss);
|
|
1413
|
-
try options.log.lastmiss();
|
|
1414
|
-
try options.log.miss(battle.active(player));
|
|
1345
|
+
try options.log.lastmiss(.{});
|
|
1346
|
+
try options.log.miss(.{battle.active(player)});
|
|
1415
1347
|
}
|
|
1416
1348
|
|
|
1417
1349
|
return false;
|
|
@@ -1463,7 +1395,7 @@ fn moveHit(
|
|
|
1463
1395
|
const overwritten = overwrite and state > 0;
|
|
1464
1396
|
assert(!overwritten or (0 < state and state <= 255 and !side.active.volatiles.Bide));
|
|
1465
1397
|
|
|
1466
|
-
var accuracy = if (overwritten) state else
|
|
1398
|
+
var accuracy: u16 = if (overwritten) state else move.accuracy;
|
|
1467
1399
|
var boost = BOOSTS[@as(u4, @intCast(@as(i8, side.active.boosts.accuracy) + 6))];
|
|
1468
1400
|
accuracy = accuracy * boost[0] / boost[1];
|
|
1469
1401
|
boost = BOOSTS[@as(u4, @intCast(@as(i8, -foe.active.boosts.evasion) + 6))];
|
|
@@ -1502,9 +1434,8 @@ fn checkFaint(
|
|
|
1502
1434
|
const foe_fainted = foe.stored().hp == 0;
|
|
1503
1435
|
|
|
1504
1436
|
const player_out = findFirstAlive(side) == 0;
|
|
1505
|
-
const foe_out = findFirstAlive(foe) == 0;
|
|
1506
|
-
const
|
|
1507
|
-
const more = tie or player_out or foe_out;
|
|
1437
|
+
const foe_out = foe_fainted and findFirstAlive(foe) == 0;
|
|
1438
|
+
const more = player_out or foe_out;
|
|
1508
1439
|
|
|
1509
1440
|
if (try faint(battle, player, !(more or foe_fainted), options)) |r| return r;
|
|
1510
1441
|
if (foe_fainted) if (try faint(battle, player.foe(), !more, options)) |r| return r;
|
|
@@ -1512,20 +1443,20 @@ fn checkFaint(
|
|
|
1512
1443
|
assert(!side.active.volatiles.MultiHit);
|
|
1513
1444
|
assert(!foe.active.volatiles.MultiHit);
|
|
1514
1445
|
|
|
1515
|
-
if (
|
|
1516
|
-
try options.log.tie();
|
|
1446
|
+
if (player_out and foe_out) {
|
|
1447
|
+
try options.log.tie(.{});
|
|
1517
1448
|
return Result.Tie;
|
|
1518
1449
|
} else if (player_out) {
|
|
1519
|
-
try options.log.win(player.foe());
|
|
1450
|
+
try options.log.win(.{player.foe()});
|
|
1520
1451
|
return if (player == .P1) Result.Lose else Result.Win;
|
|
1521
1452
|
} else if (foe_out) {
|
|
1522
|
-
try options.log.win(player);
|
|
1453
|
+
try options.log.win(.{player});
|
|
1523
1454
|
return if (player == .P1) Result.Win else Result.Lose;
|
|
1524
1455
|
}
|
|
1525
1456
|
|
|
1526
1457
|
const foe_choice: Choice.Type = if (foe_fainted) .Switch else .Pass;
|
|
1527
|
-
if (player == .P1) return
|
|
1528
|
-
return
|
|
1458
|
+
if (player == .P1) return .{ .p1 = .Switch, .p2 = foe_choice };
|
|
1459
|
+
return .{ .p1 = foe_choice, .p2 = .Switch };
|
|
1529
1460
|
}
|
|
1530
1461
|
|
|
1531
1462
|
fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result {
|
|
@@ -1561,7 +1492,7 @@ fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result
|
|
|
1561
1492
|
side.stored().stats.spe;
|
|
1562
1493
|
}
|
|
1563
1494
|
|
|
1564
|
-
try options.log.faint(battle.active(player), done);
|
|
1495
|
+
try options.log.faint(.{ battle.active(player), done });
|
|
1565
1496
|
options.calc.capped(player);
|
|
1566
1497
|
return null;
|
|
1567
1498
|
}
|
|
@@ -1573,21 +1504,19 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1573
1504
|
var volatiles = &side.active.volatiles;
|
|
1574
1505
|
|
|
1575
1506
|
const brn = Status.is(stored.status, .BRN);
|
|
1576
|
-
if (brn or Status.is(stored.status, .PSN))
|
|
1507
|
+
if (brn or Status.is(stored.status, .PSN)) {
|
|
1577
1508
|
var damage = @max(stored.stats.hp / 16, 1);
|
|
1578
|
-
|
|
1579
1509
|
if (volatiles.Toxic) {
|
|
1580
1510
|
volatiles.toxic += 1;
|
|
1581
1511
|
damage *= volatiles.toxic;
|
|
1582
1512
|
}
|
|
1513
|
+
damage = @min(damage, stored.hp);
|
|
1583
1514
|
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
// Pokémon Showdown uses damageOf here but its not relevant in Generation I
|
|
1590
|
-
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
1515
|
+
if (!showdown or damage > 0) {
|
|
1516
|
+
stored.hp -= damage;
|
|
1517
|
+
// Pokémon Showdown uses damage [of] here but its not relevant in Generation I
|
|
1518
|
+
try options.log.damage(.{ ident, stored, if (brn) Damage.Burn else Damage.Poison });
|
|
1519
|
+
}
|
|
1591
1520
|
}
|
|
1592
1521
|
|
|
1593
1522
|
if (volatiles.LeechSeed) {
|
|
@@ -1611,14 +1540,14 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1611
1540
|
const amount = @min(damage, stored.hp);
|
|
1612
1541
|
stored.hp -= amount;
|
|
1613
1542
|
|
|
1614
|
-
// As above, Pokémon Showdown uses
|
|
1615
|
-
if (amount > 0) try options.log.damage(ident, stored, .LeechSeed);
|
|
1543
|
+
// As above, Pokémon Showdown uses damage [of] but its not relevant
|
|
1544
|
+
if (amount > 0) try options.log.damage(.{ ident, stored, .LeechSeed });
|
|
1616
1545
|
|
|
1617
1546
|
const before = foe_stored.hp;
|
|
1618
1547
|
// Uncapped damage is added back to the foe
|
|
1619
1548
|
foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
|
|
1620
1549
|
// Pokémon Showdown uses the less specific heal here instead of drain... because reasons?
|
|
1621
|
-
if (foe_stored.hp > before) try options.log.heal(foe_ident, foe_stored, .Silent);
|
|
1550
|
+
if (foe_stored.hp > before) try options.log.heal(.{ foe_ident, foe_stored, .Silent });
|
|
1622
1551
|
}
|
|
1623
1552
|
}
|
|
1624
1553
|
|
|
@@ -1629,18 +1558,18 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1629
1558
|
battle.turn += 1;
|
|
1630
1559
|
|
|
1631
1560
|
if (showdown and pkmn.options.ebc and checkEBC(battle)) {
|
|
1632
|
-
try options.log.tie();
|
|
1561
|
+
try options.log.tie(.{});
|
|
1633
1562
|
return Result.Tie;
|
|
1634
1563
|
}
|
|
1635
1564
|
|
|
1636
1565
|
if (showdown and battle.turn >= 1000) {
|
|
1637
|
-
try options.log.tie();
|
|
1566
|
+
try options.log.tie(.{});
|
|
1638
1567
|
return Result.Tie;
|
|
1639
1568
|
} else if (battle.turn >= 65535) {
|
|
1640
1569
|
return Result.Error;
|
|
1641
1570
|
}
|
|
1642
1571
|
|
|
1643
|
-
try options.log.turn(battle.turn);
|
|
1572
|
+
try options.log.turn(.{battle.turn});
|
|
1644
1573
|
|
|
1645
1574
|
return Result.Default;
|
|
1646
1575
|
}
|
|
@@ -1700,9 +1629,9 @@ fn checkEBC(battle: anytype) bool {
|
|
|
1700
1629
|
}
|
|
1701
1630
|
|
|
1702
1631
|
fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
1703
|
-
if (side.active.volatiles.
|
|
1632
|
+
if (side.active.volatiles.disable_move != 0) {
|
|
1704
1633
|
// A Pokémon that transforms after being disabled may end up with less move slots
|
|
1705
|
-
const m = side.active.moves[side.active.volatiles.
|
|
1634
|
+
const m = side.active.moves[side.active.volatiles.disable_move - 1].id;
|
|
1706
1635
|
// side.last_selected_move can be Struggle here on Pokemon Showdown we need an extra check
|
|
1707
1636
|
const last = if (showdown and m == .Bide and side.active.volatiles.Bide)
|
|
1708
1637
|
m
|
|
@@ -1710,14 +1639,14 @@ fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
|
1710
1639
|
side.last_selected_move;
|
|
1711
1640
|
if (m != .None and m == last) {
|
|
1712
1641
|
side.active.volatiles.Charging = false;
|
|
1713
|
-
try options.log.
|
|
1642
|
+
try options.log.cant(.{ ident, .Disable, last });
|
|
1714
1643
|
return true;
|
|
1715
1644
|
}
|
|
1716
1645
|
}
|
|
1717
1646
|
return false;
|
|
1718
1647
|
}
|
|
1719
1648
|
|
|
1720
|
-
|
|
1649
|
+
fn buildRage(battle: anytype, who: Player, options: anytype) !void {
|
|
1721
1650
|
const side = battle.side(who);
|
|
1722
1651
|
if (side.active.volatiles.Rage and side.active.boosts.atk < 6) {
|
|
1723
1652
|
try Effects.boost(battle, who, Move.get(.Rage), options);
|
|
@@ -1735,7 +1664,7 @@ fn handleThrashing(battle: anytype, active: *ActivePokemon, player: Player, opti
|
|
|
1735
1664
|
return true;
|
|
1736
1665
|
}
|
|
1737
1666
|
|
|
1738
|
-
|
|
1667
|
+
fn onBegin(
|
|
1739
1668
|
battle: anytype,
|
|
1740
1669
|
player: Player,
|
|
1741
1670
|
move: Move.Data,
|
|
@@ -1743,7 +1672,6 @@ inline fn onBegin(
|
|
|
1743
1672
|
residual: *bool,
|
|
1744
1673
|
options: anytype,
|
|
1745
1674
|
) !void {
|
|
1746
|
-
assert(move.effect.onBegin());
|
|
1747
1675
|
return switch (move.effect) {
|
|
1748
1676
|
.Confusion => Effects.confusion(battle, player, move, options),
|
|
1749
1677
|
.Conversion => Effects.conversion(battle, player, options),
|
|
@@ -1765,8 +1693,7 @@ inline fn onBegin(
|
|
|
1765
1693
|
};
|
|
1766
1694
|
}
|
|
1767
1695
|
|
|
1768
|
-
|
|
1769
|
-
assert(move.effect.onEnd());
|
|
1696
|
+
fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1770
1697
|
return switch (move.effect) {
|
|
1771
1698
|
.Bide => Effects.bide(battle, player, options),
|
|
1772
1699
|
// zig fmt: off
|
|
@@ -1781,14 +1708,13 @@ inline fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anyty
|
|
|
1781
1708
|
};
|
|
1782
1709
|
}
|
|
1783
1710
|
|
|
1784
|
-
|
|
1711
|
+
fn alwaysHappens(
|
|
1785
1712
|
battle: anytype,
|
|
1786
1713
|
player: Player,
|
|
1787
1714
|
move: Move.Data,
|
|
1788
1715
|
residual: *bool,
|
|
1789
1716
|
options: anytype,
|
|
1790
1717
|
) !void {
|
|
1791
|
-
assert(move.effect.alwaysHappens());
|
|
1792
1718
|
return switch (move.effect) {
|
|
1793
1719
|
.DrainHP, .DreamEater => Effects.drainHP(battle, player, options),
|
|
1794
1720
|
.Explode => Effects.explode(battle, player),
|
|
@@ -1800,7 +1726,7 @@ inline fn alwaysHappens(
|
|
|
1800
1726
|
};
|
|
1801
1727
|
}
|
|
1802
1728
|
|
|
1803
|
-
|
|
1729
|
+
fn moveEffect(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1804
1730
|
return switch (move.effect) {
|
|
1805
1731
|
.BurnChance1, .BurnChance2 => Effects.burnChance(battle, player, move, options),
|
|
1806
1732
|
.ConfusionChance => Effects.confusion(battle, player, move, options),
|
|
@@ -1827,7 +1753,7 @@ pub const Effects = struct {
|
|
|
1827
1753
|
side.active.volatiles.state = 0;
|
|
1828
1754
|
side.active.volatiles.attacks = Rolls.attackingDuration(battle, player, options);
|
|
1829
1755
|
|
|
1830
|
-
try options.log.start(battle.active(player), .Bide);
|
|
1756
|
+
try options.log.start(.{ battle.active(player), .Bide });
|
|
1831
1757
|
}
|
|
1832
1758
|
|
|
1833
1759
|
fn burnChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1841,11 +1767,11 @@ pub const Effects = struct {
|
|
|
1841
1767
|
// GLITCH: Freeze top move selection desync can occur if thawed Pokémon is slower
|
|
1842
1768
|
if (Status.is(foe_stored.status, .FRZ)) {
|
|
1843
1769
|
assert(move.type == .Fire);
|
|
1844
|
-
try options.log.curestatus(
|
|
1770
|
+
try options.log.curestatus(.{
|
|
1845
1771
|
battle.active(player.foe()),
|
|
1846
1772
|
foe_stored.status,
|
|
1847
1773
|
.Message,
|
|
1848
|
-
);
|
|
1774
|
+
});
|
|
1849
1775
|
foe_stored.status = 0;
|
|
1850
1776
|
}
|
|
1851
1777
|
return;
|
|
@@ -1859,7 +1785,7 @@ pub const Effects = struct {
|
|
|
1859
1785
|
foe_stored.status = Status.init(.BRN);
|
|
1860
1786
|
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
1861
1787
|
|
|
1862
|
-
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
1788
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1863
1789
|
}
|
|
1864
1790
|
|
|
1865
1791
|
fn charge(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -1869,8 +1795,8 @@ pub const Effects = struct {
|
|
|
1869
1795
|
volatiles.Charging = true;
|
|
1870
1796
|
const move = side.last_selected_move;
|
|
1871
1797
|
if (move == .Fly or move == .Dig) volatiles.Invulnerable = true;
|
|
1872
|
-
try options.log.laststill();
|
|
1873
|
-
try options.log.prepare(battle.active(player), move);
|
|
1798
|
+
try options.log.laststill(.{});
|
|
1799
|
+
try options.log.prepare(.{ battle.active(player), move });
|
|
1874
1800
|
}
|
|
1875
1801
|
|
|
1876
1802
|
fn confusion(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -1881,17 +1807,11 @@ pub const Effects = struct {
|
|
|
1881
1807
|
if (!try Rolls.confusionChance(battle, player, options)) return;
|
|
1882
1808
|
} else {
|
|
1883
1809
|
if (showdown) {
|
|
1884
|
-
if (!try checkHit(battle, player, move, options))
|
|
1885
|
-
|
|
1886
|
-
} else if (sub) {
|
|
1887
|
-
return options.log.fail(battle.active(player.foe()), .None);
|
|
1888
|
-
}
|
|
1810
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1811
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1889
1812
|
} else {
|
|
1890
|
-
if (sub) {
|
|
1891
|
-
|
|
1892
|
-
} else if (!try checkHit(battle, player, move, options)) {
|
|
1893
|
-
return;
|
|
1894
|
-
}
|
|
1813
|
+
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1814
|
+
if (!try checkHit(battle, player, move, options)) return;
|
|
1895
1815
|
}
|
|
1896
1816
|
try options.chance.commit(player, .hit);
|
|
1897
1817
|
}
|
|
@@ -1900,23 +1820,24 @@ pub const Effects = struct {
|
|
|
1900
1820
|
foe.active.volatiles.Confusion = true;
|
|
1901
1821
|
foe.active.volatiles.confusion = Rolls.confusionDuration(battle, player, false, options);
|
|
1902
1822
|
|
|
1903
|
-
try options.log.start(battle.active(player.foe()), .Confusion);
|
|
1823
|
+
try options.log.start(.{ battle.active(player.foe()), .Confusion });
|
|
1904
1824
|
}
|
|
1905
1825
|
|
|
1906
1826
|
fn conversion(battle: anytype, player: Player, options: anytype) !void {
|
|
1907
1827
|
const foe = battle.foe(player);
|
|
1908
1828
|
|
|
1909
1829
|
if (foe.active.volatiles.Invulnerable) {
|
|
1910
|
-
try options.log.lastmiss();
|
|
1911
|
-
return options.log.miss(battle.active(player));
|
|
1830
|
+
try options.log.lastmiss(.{});
|
|
1831
|
+
return options.log.miss(.{battle.active(player)});
|
|
1912
1832
|
}
|
|
1913
1833
|
|
|
1914
1834
|
battle.side(player).active.types = foe.active.types;
|
|
1915
|
-
return options.log.
|
|
1835
|
+
return options.log.start(.{
|
|
1916
1836
|
battle.active(player),
|
|
1837
|
+
.TypeChange,
|
|
1917
1838
|
foe.active.types,
|
|
1918
1839
|
battle.active(player.foe()),
|
|
1919
|
-
);
|
|
1840
|
+
});
|
|
1920
1841
|
}
|
|
1921
1842
|
|
|
1922
1843
|
fn disable(battle: anytype, player: Player, move: Move.Data, options: anytype) !?Result {
|
|
@@ -1930,8 +1851,8 @@ pub const Effects = struct {
|
|
|
1930
1851
|
try options.chance.commit(player, .hit);
|
|
1931
1852
|
}
|
|
1932
1853
|
|
|
1933
|
-
if (volatiles.
|
|
1934
|
-
try options.log.fail(foe_ident, .None);
|
|
1854
|
+
if (volatiles.disable_move != 0) {
|
|
1855
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1935
1856
|
return null;
|
|
1936
1857
|
}
|
|
1937
1858
|
|
|
@@ -1947,7 +1868,7 @@ pub const Effects = struct {
|
|
|
1947
1868
|
// Technically this is still considered simply a "miss" on the cartridge,
|
|
1948
1869
|
// but diverging from Pokémon Showdown here would mostly just be pedantic
|
|
1949
1870
|
if (n == 0) {
|
|
1950
|
-
try options.log.fail(foe_ident, .None);
|
|
1871
|
+
try options.log.fail(.{ foe_ident, .None });
|
|
1951
1872
|
return null;
|
|
1952
1873
|
} else if (err) {
|
|
1953
1874
|
// GLITCH: Transform + Mirror Move / Metronome PP softlock
|
|
@@ -1955,18 +1876,18 @@ pub const Effects = struct {
|
|
|
1955
1876
|
return Result.Error;
|
|
1956
1877
|
}
|
|
1957
1878
|
|
|
1958
|
-
volatiles.
|
|
1879
|
+
volatiles.disable_move =
|
|
1959
1880
|
@intCast(try Rolls.moveSlot(battle, player, &foe.active.moves, n, options));
|
|
1960
|
-
volatiles.
|
|
1881
|
+
volatiles.disable_duration = Rolls.disableDuration(battle, player, options);
|
|
1961
1882
|
|
|
1962
|
-
const id = foe.active.move(volatiles.
|
|
1963
|
-
try options.log.
|
|
1883
|
+
const id = foe.active.move(volatiles.disable_move).id;
|
|
1884
|
+
try options.log.start(.{ foe_ident, .Disable, id });
|
|
1964
1885
|
return null;
|
|
1965
1886
|
}
|
|
1966
1887
|
|
|
1967
1888
|
fn drainHP(battle: anytype, player: Player, options: anytype) !void {
|
|
1968
|
-
var
|
|
1969
|
-
|
|
1889
|
+
var stored = battle.side(player).stored();
|
|
1890
|
+
const ident = battle.active(player);
|
|
1970
1891
|
|
|
1971
1892
|
if (battle.last_damage == 0) {
|
|
1972
1893
|
assert(showdown);
|
|
@@ -1979,7 +1900,7 @@ pub const Effects = struct {
|
|
|
1979
1900
|
if (stored.hp == stored.stats.hp) return;
|
|
1980
1901
|
stored.hp = @min(stored.stats.hp, stored.hp + drain);
|
|
1981
1902
|
|
|
1982
|
-
try options.log.
|
|
1903
|
+
try options.log.heal(.{ ident, stored, .Drain, battle.active(player.foe()) });
|
|
1983
1904
|
}
|
|
1984
1905
|
|
|
1985
1906
|
fn explode(battle: anytype, player: Player) !void {
|
|
@@ -2011,7 +1932,7 @@ pub const Effects = struct {
|
|
|
2011
1932
|
if (side.active.volatiles.FocusEnergy) return;
|
|
2012
1933
|
side.active.volatiles.FocusEnergy = true;
|
|
2013
1934
|
|
|
2014
|
-
try options.log.start(battle.active(player), .FocusEnergy);
|
|
1935
|
+
try options.log.start(.{ battle.active(player), .FocusEnergy });
|
|
2015
1936
|
}
|
|
2016
1937
|
|
|
2017
1938
|
fn freezeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2033,7 +1954,7 @@ pub const Effects = struct {
|
|
|
2033
1954
|
foe_stored.status = Status.init(.FRZ);
|
|
2034
1955
|
// GLITCH: Hyper Beam recharging status is not cleared
|
|
2035
1956
|
|
|
2036
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
1957
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2037
1958
|
}
|
|
2038
1959
|
|
|
2039
1960
|
fn haze(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -2041,7 +1962,7 @@ pub const Effects = struct {
|
|
|
2041
1962
|
var side = battle.side(player);
|
|
2042
1963
|
var foe = battle.foe(player);
|
|
2043
1964
|
|
|
2044
|
-
|
|
1965
|
+
const side_stored = side.stored();
|
|
2045
1966
|
var foe_stored = foe.stored();
|
|
2046
1967
|
|
|
2047
1968
|
const player_ident = battle.active(player);
|
|
@@ -2053,8 +1974,8 @@ pub const Effects = struct {
|
|
|
2053
1974
|
side.active.stats = side_stored.stats;
|
|
2054
1975
|
foe.active.stats = foe_stored.stats;
|
|
2055
1976
|
|
|
2056
|
-
try log.activate(player_ident, .Haze);
|
|
2057
|
-
try log.clearallboost();
|
|
1977
|
+
try log.activate(.{ player_ident, .Haze });
|
|
1978
|
+
try log.clearallboost(.{});
|
|
2058
1979
|
|
|
2059
1980
|
// Pokémon Showdown clears P1 then P2 instead of status -> side -> foe
|
|
2060
1981
|
if (showdown) {
|
|
@@ -2062,11 +1983,11 @@ pub const Effects = struct {
|
|
|
2062
1983
|
const p: Player = @enumFromInt(i);
|
|
2063
1984
|
// Pokémon Showdown incorrectly does not prevent sleep/freeze from moving
|
|
2064
1985
|
if (p != player and Status.any(s.stored().status)) {
|
|
2065
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
1986
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2066
1987
|
s.stored().status = 0;
|
|
2067
1988
|
} else if (showdown and s.stored().status == Status.TOX) {
|
|
2068
1989
|
s.stored().status = Status.init(.PSN);
|
|
2069
|
-
try log.status(battle.active(p), s.stored().status, .None);
|
|
1990
|
+
try log.status(.{ battle.active(p), s.stored().status, .None });
|
|
2070
1991
|
}
|
|
2071
1992
|
try clearVolatiles(battle, p, p != player, options);
|
|
2072
1993
|
}
|
|
@@ -2075,7 +1996,7 @@ pub const Effects = struct {
|
|
|
2075
1996
|
if (Status.is(foe_stored.status, .FRZ) or Status.is(foe_stored.status, .SLP)) {
|
|
2076
1997
|
foe.last_selected_move = .SKIP_TURN;
|
|
2077
1998
|
}
|
|
2078
|
-
try log.curestatus(foe_ident, foe_stored.status, .Silent);
|
|
1999
|
+
try log.curestatus(.{ foe_ident, foe_stored.status, .Silent });
|
|
2079
2000
|
foe_stored.status = 0;
|
|
2080
2001
|
}
|
|
2081
2002
|
try clearVolatiles(battle, player, false, options);
|
|
@@ -2091,7 +2012,7 @@ pub const Effects = struct {
|
|
|
2091
2012
|
// GLITCH: HP recovery move failure glitches
|
|
2092
2013
|
const delta = stored.stats.hp - stored.hp;
|
|
2093
2014
|
if (delta == 0 or (delta & 255 == 255 and stored.hp % 256 != 0)) {
|
|
2094
|
-
return
|
|
2015
|
+
return options.log.fail(.{ ident, .None });
|
|
2095
2016
|
}
|
|
2096
2017
|
|
|
2097
2018
|
const rest = side.last_selected_move == .Rest;
|
|
@@ -2099,17 +2020,17 @@ pub const Effects = struct {
|
|
|
2099
2020
|
// Adding the sleep status runs the sleep condition handler to roll duration
|
|
2100
2021
|
if (showdown) battle.rng.advance(1);
|
|
2101
2022
|
stored.status = Status.slf(2);
|
|
2102
|
-
try options.log.
|
|
2023
|
+
try options.log.status(.{ ident, stored.status, .From, Move.Rest });
|
|
2103
2024
|
stored.hp = stored.stats.hp;
|
|
2104
2025
|
} else {
|
|
2105
2026
|
stored.hp = @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2));
|
|
2106
2027
|
}
|
|
2107
|
-
try options.log.heal(ident, stored, if (rest) Heal.Silent else Heal.None);
|
|
2028
|
+
try options.log.heal(.{ ident, stored, if (rest) Heal.Silent else Heal.None });
|
|
2108
2029
|
}
|
|
2109
2030
|
|
|
2110
2031
|
fn hyperBeam(battle: anytype, player: Player, options: anytype) !void {
|
|
2111
2032
|
battle.side(player).active.volatiles.Recharging = true;
|
|
2112
|
-
try options.log.mustrecharge(battle.active(player));
|
|
2033
|
+
try options.log.mustrecharge(.{battle.active(player)});
|
|
2113
2034
|
}
|
|
2114
2035
|
|
|
2115
2036
|
fn leechSeed(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2118,33 +2039,32 @@ pub const Effects = struct {
|
|
|
2118
2039
|
if (showdown) {
|
|
2119
2040
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2120
2041
|
if (!foe.active.volatiles.Invulnerable and foe.active.types.includes(.Grass)) {
|
|
2121
|
-
return options.log.immune(battle.active(player.foe()), .None);
|
|
2042
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
2122
2043
|
}
|
|
2123
2044
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2124
2045
|
if (foe.active.volatiles.LeechSeed) return;
|
|
2125
2046
|
} else {
|
|
2126
2047
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2127
2048
|
if (foe.active.types.includes(.Grass) or foe.active.volatiles.LeechSeed) {
|
|
2128
|
-
try options.log.lastmiss();
|
|
2129
|
-
return options.log.miss(battle.active(player));
|
|
2049
|
+
try options.log.lastmiss(.{});
|
|
2050
|
+
return options.log.miss(.{battle.active(player)});
|
|
2130
2051
|
}
|
|
2131
2052
|
}
|
|
2132
2053
|
|
|
2133
2054
|
try options.chance.commit(player, .hit);
|
|
2134
2055
|
foe.active.volatiles.LeechSeed = true;
|
|
2135
2056
|
|
|
2136
|
-
try options.log.start(battle.active(player.foe()), .LeechSeed);
|
|
2057
|
+
try options.log.start(.{ battle.active(player.foe()), .LeechSeed });
|
|
2137
2058
|
}
|
|
2138
2059
|
|
|
2139
2060
|
fn lightScreen(battle: anytype, player: Player, options: anytype) !void {
|
|
2140
2061
|
var side = battle.side(player);
|
|
2062
|
+
const ident = battle.active(player);
|
|
2141
2063
|
|
|
2142
|
-
if (side.active.volatiles.LightScreen) {
|
|
2143
|
-
return options.log.fail(battle.active(player), .None);
|
|
2144
|
-
}
|
|
2064
|
+
if (side.active.volatiles.LightScreen) return options.log.fail(.{ ident, .None });
|
|
2145
2065
|
side.active.volatiles.LightScreen = true;
|
|
2146
2066
|
|
|
2147
|
-
try options.log.start(
|
|
2067
|
+
try options.log.start(.{ ident, .LightScreen });
|
|
2148
2068
|
}
|
|
2149
2069
|
|
|
2150
2070
|
fn mimic(battle: anytype, player: Player, move: Move.Data, mslot: u8, options: anytype) !void {
|
|
@@ -2173,7 +2093,7 @@ pub const Effects = struct {
|
|
|
2173
2093
|
// Invulnerable foes or 1/256 miss can trigger |-miss| instead of |-fail|
|
|
2174
2094
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2175
2095
|
try options.chance.commit(player, .hit);
|
|
2176
|
-
return
|
|
2096
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2177
2097
|
}
|
|
2178
2098
|
}
|
|
2179
2099
|
if (!try checkHit(battle, player, move, options)) return;
|
|
@@ -2182,7 +2102,7 @@ pub const Effects = struct {
|
|
|
2182
2102
|
const rslot = try Rolls.moveSlot(battle, player, &foe.active.moves, 0, options);
|
|
2183
2103
|
side.active.move(oslot).id = foe.active.move(rslot).id;
|
|
2184
2104
|
|
|
2185
|
-
try options.log.
|
|
2105
|
+
try options.log.start(.{ battle.active(player), .Mimic, side.active.move(oslot).id });
|
|
2186
2106
|
}
|
|
2187
2107
|
|
|
2188
2108
|
fn mist(battle: anytype, player: Player, options: anytype) !void {
|
|
@@ -2191,7 +2111,7 @@ pub const Effects = struct {
|
|
|
2191
2111
|
if (side.active.volatiles.Mist) return;
|
|
2192
2112
|
side.active.volatiles.Mist = true;
|
|
2193
2113
|
|
|
2194
|
-
try options.log.start(battle.active(player), .Mist);
|
|
2114
|
+
try options.log.start(.{ battle.active(player), .Mist });
|
|
2195
2115
|
}
|
|
2196
2116
|
|
|
2197
2117
|
fn multiHit(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2217,17 +2137,18 @@ pub const Effects = struct {
|
|
|
2217
2137
|
|
|
2218
2138
|
if (showdown) {
|
|
2219
2139
|
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
2220
|
-
if (immune and !foe.active.volatiles.Invulnerable)
|
|
2221
|
-
|
|
2140
|
+
if (immune and !foe.active.volatiles.Invulnerable) {
|
|
2141
|
+
return log.immune(.{ foe_ident, .None });
|
|
2142
|
+
} else if (!try checkHit(battle, player, move, options)) return;
|
|
2222
2143
|
}
|
|
2223
2144
|
if (Status.any(foe_stored.status)) {
|
|
2224
|
-
return log.fail(
|
|
2145
|
+
return log.fail(.{
|
|
2225
2146
|
foe_ident,
|
|
2226
|
-
if (Status.is(foe_stored.status, .PAR)) .Paralysis else .None,
|
|
2227
|
-
);
|
|
2147
|
+
if (Status.is(foe_stored.status, .PAR)) Fail.Paralysis else Fail.None,
|
|
2148
|
+
});
|
|
2228
2149
|
}
|
|
2229
2150
|
if (!showdown) {
|
|
2230
|
-
if (immune) return log.immune(foe_ident, .None);
|
|
2151
|
+
if (immune) return log.immune(.{ foe_ident, .None });
|
|
2231
2152
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2232
2153
|
}
|
|
2233
2154
|
try options.chance.commit(player, .hit);
|
|
@@ -2235,7 +2156,7 @@ pub const Effects = struct {
|
|
|
2235
2156
|
foe_stored.status = Status.init(.PAR);
|
|
2236
2157
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2237
2158
|
|
|
2238
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2159
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2239
2160
|
}
|
|
2240
2161
|
|
|
2241
2162
|
fn paralyzeChance(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2257,12 +2178,12 @@ pub const Effects = struct {
|
|
|
2257
2178
|
foe_stored.status = Status.init(.PAR);
|
|
2258
2179
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2259
2180
|
|
|
2260
|
-
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
2181
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
2261
2182
|
}
|
|
2262
2183
|
|
|
2263
2184
|
fn payDay(battle: anytype, player: Player, options: anytype) !void {
|
|
2264
2185
|
if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
|
|
2265
|
-
try options.log.fieldactivate();
|
|
2186
|
+
try options.log.fieldactivate(.{});
|
|
2266
2187
|
}
|
|
2267
2188
|
}
|
|
2268
2189
|
|
|
@@ -2277,23 +2198,23 @@ pub const Effects = struct {
|
|
|
2277
2198
|
return;
|
|
2278
2199
|
} else if (foe.active.volatiles.Substitute) {
|
|
2279
2200
|
if (move.effect != .Poison) return;
|
|
2280
|
-
return log.fail(foe_ident, .None);
|
|
2201
|
+
return log.fail(.{ foe_ident, .None });
|
|
2281
2202
|
} else if (Status.any(foe_stored.status)) {
|
|
2282
2203
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2283
2204
|
// Pokémon Showdown considers Toxic to be a status even in Generation I and so
|
|
2284
2205
|
// will not include a fail reason for Toxic vs. Poison or vice-versa...
|
|
2285
|
-
return log.fail(foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2206
|
+
return log.fail(.{ foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2286
2207
|
if (!showdown)
|
|
2287
|
-
.Poison
|
|
2208
|
+
Fail.Poison
|
|
2288
2209
|
else if (toxic == (foe_stored.status == Status.TOX))
|
|
2289
|
-
if (toxic) .Toxic else .Poison
|
|
2210
|
+
if (toxic) Fail.Toxic else Fail.Poison
|
|
2290
2211
|
else
|
|
2291
|
-
.None
|
|
2212
|
+
Fail.None
|
|
2292
2213
|
else
|
|
2293
|
-
.None);
|
|
2214
|
+
Fail.None });
|
|
2294
2215
|
} else if (foe.active.types.includes(.Poison)) {
|
|
2295
2216
|
if (move.effect != .Poison) return if (showdown) battle.rng.advance(1);
|
|
2296
|
-
return log.immune(foe_ident, .None);
|
|
2217
|
+
return log.immune(.{ foe_ident, .None });
|
|
2297
2218
|
}
|
|
2298
2219
|
|
|
2299
2220
|
if (move.effect == .Poison) {
|
|
@@ -2312,7 +2233,7 @@ pub const Effects = struct {
|
|
|
2312
2233
|
foe.active.volatiles.toxic = 0;
|
|
2313
2234
|
}
|
|
2314
2235
|
|
|
2315
|
-
try log.status(foe_ident, foe_stored.status, .None);
|
|
2236
|
+
try log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2316
2237
|
}
|
|
2317
2238
|
|
|
2318
2239
|
fn rage(battle: anytype, player: Player) !void {
|
|
@@ -2332,22 +2253,23 @@ pub const Effects = struct {
|
|
|
2332
2253
|
@as(u8, if (side.last_selected_move == .Struggle) 2 else 4), 1));
|
|
2333
2254
|
stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
|
|
2334
2255
|
|
|
2335
|
-
try options.log.
|
|
2256
|
+
try options.log.damage(.{
|
|
2336
2257
|
battle.active(player),
|
|
2337
2258
|
stored,
|
|
2338
2259
|
.RecoilOf,
|
|
2339
2260
|
battle.active(player.foe()),
|
|
2340
|
-
);
|
|
2261
|
+
});
|
|
2341
2262
|
if (showdown and stored.hp == 0) residual.* = false;
|
|
2342
2263
|
}
|
|
2343
2264
|
|
|
2344
2265
|
fn reflect(battle: anytype, player: Player, options: anytype) !void {
|
|
2345
2266
|
var side = battle.side(player);
|
|
2267
|
+
const ident = battle.active(player);
|
|
2346
2268
|
|
|
2347
|
-
if (side.active.volatiles.Reflect) return options.log.fail(
|
|
2269
|
+
if (side.active.volatiles.Reflect) return options.log.fail(.{ ident, .None });
|
|
2348
2270
|
side.active.volatiles.Reflect = true;
|
|
2349
2271
|
|
|
2350
|
-
try options.log.start(
|
|
2272
|
+
try options.log.start(.{ ident, .Reflect });
|
|
2351
2273
|
}
|
|
2352
2274
|
|
|
2353
2275
|
fn sleep(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
@@ -2361,10 +2283,10 @@ pub const Effects = struct {
|
|
|
2361
2283
|
// the Sleep Clause Mod might activate, causing us to not actually bypass
|
|
2362
2284
|
} else {
|
|
2363
2285
|
if (Status.any(foe_stored.status)) {
|
|
2364
|
-
return options.log.fail(
|
|
2286
|
+
return options.log.fail(.{
|
|
2365
2287
|
foe_ident,
|
|
2366
|
-
if (Status.is(foe_stored.status, .SLP)) .Sleep else .None,
|
|
2367
|
-
);
|
|
2288
|
+
if (Status.is(foe_stored.status, .SLP)) Fail.Sleep else Fail.None,
|
|
2289
|
+
});
|
|
2368
2290
|
}
|
|
2369
2291
|
// If checkHit in doMove didn't return true Pokémon Showdown wouldn't be in here
|
|
2370
2292
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
@@ -2381,18 +2303,17 @@ pub const Effects = struct {
|
|
|
2381
2303
|
|
|
2382
2304
|
foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
|
|
2383
2305
|
const last = battle.side(player).last_selected_move;
|
|
2384
|
-
try options.log.
|
|
2306
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .From, last });
|
|
2385
2307
|
}
|
|
2386
2308
|
|
|
2387
2309
|
fn splash(battle: anytype, player: Player, options: anytype) !void {
|
|
2388
|
-
try options.log.activate(battle.active(player), .Splash);
|
|
2310
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
2389
2311
|
}
|
|
2390
2312
|
|
|
2391
2313
|
fn substitute(battle: anytype, player: Player, residual: *bool, options: anytype) !void {
|
|
2392
2314
|
var side = battle.side(player);
|
|
2393
2315
|
if (side.active.volatiles.Substitute) {
|
|
2394
|
-
|
|
2395
|
-
return;
|
|
2316
|
+
return options.log.fail(.{ battle.active(player), .Substitute });
|
|
2396
2317
|
}
|
|
2397
2318
|
|
|
2398
2319
|
assert(side.stored().stats.hp <= 1023);
|
|
@@ -2404,28 +2325,32 @@ pub const Effects = struct {
|
|
|
2404
2325
|
// avoid having to convert to floating point)
|
|
2405
2326
|
const required_hp = if (showdown) @divFloor(side.stored().stats.hp - 1, 4) + 1 else hp;
|
|
2406
2327
|
if (side.stored().hp < required_hp) {
|
|
2407
|
-
|
|
2408
|
-
return;
|
|
2328
|
+
return options.log.fail(.{ battle.active(player), .Weak });
|
|
2409
2329
|
}
|
|
2410
2330
|
|
|
2411
2331
|
// GLITCH: can leave the user with 0 HP (faints later) because didn't check '<=' above
|
|
2412
2332
|
side.stored().hp -= hp;
|
|
2413
2333
|
side.active.volatiles.substitute = hp + 1;
|
|
2414
2334
|
side.active.volatiles.Substitute = true;
|
|
2415
|
-
try options.log.start(battle.active(player), .Substitute);
|
|
2335
|
+
try options.log.start(.{ battle.active(player), .Substitute });
|
|
2416
2336
|
if (hp > 0) {
|
|
2417
|
-
try options.log.damage(battle.active(player), side.stored(), .None);
|
|
2337
|
+
try options.log.damage(.{ battle.active(player), side.stored(), .None });
|
|
2418
2338
|
if (showdown and side.stored().hp == 0) residual.* = false;
|
|
2419
2339
|
}
|
|
2420
2340
|
}
|
|
2421
2341
|
|
|
2422
2342
|
fn switchAndTeleport(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
2423
|
-
if (!showdown or battle.side(player).last_selected_move == .Teleport) return;
|
|
2424
|
-
|
|
2425
2343
|
// Whirlwind/Roar should not roll to hit/reset damage but Pokémon Showdown does anyway
|
|
2426
|
-
if (
|
|
2427
|
-
|
|
2428
|
-
|
|
2344
|
+
if (showdown) {
|
|
2345
|
+
if (battle.side(player).last_selected_move == .Teleport) return;
|
|
2346
|
+
if (try checkHit(battle, player, move, options)) {
|
|
2347
|
+
try options.chance.commit(player, .hit);
|
|
2348
|
+
}
|
|
2349
|
+
battle.last_damage = 0;
|
|
2350
|
+
} else {
|
|
2351
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2352
|
+
try options.log.laststill(.{});
|
|
2353
|
+
}
|
|
2429
2354
|
}
|
|
2430
2355
|
|
|
2431
2356
|
fn thrashing(battle: anytype, player: Player, options: anytype) void {
|
|
@@ -2463,7 +2388,7 @@ pub const Effects = struct {
|
|
|
2463
2388
|
side.active.moves[i].pp = if (m.id != .None) 5 else 0;
|
|
2464
2389
|
}
|
|
2465
2390
|
|
|
2466
|
-
try options.log.transform(battle.active(player), foe_ident);
|
|
2391
|
+
try options.log.transform(.{ battle.active(player), foe_ident });
|
|
2467
2392
|
}
|
|
2468
2393
|
|
|
2469
2394
|
fn binding(battle: anytype, player: Player, rewrap: bool, options: anytype) !void {
|
|
@@ -2493,87 +2418,87 @@ pub const Effects = struct {
|
|
|
2493
2418
|
switch (move.effect) {
|
|
2494
2419
|
.AttackUp1, .AttackUp2, .Rage => {
|
|
2495
2420
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2496
|
-
if (boosts.atk == 6) return
|
|
2421
|
+
if (boosts.atk == 6) return log.fail(.{ ident, .None });
|
|
2497
2422
|
const n: u2 = if (move.effect == .AttackUp2) 2 else 1;
|
|
2498
2423
|
boosts.atk = @as(i4, @intCast(@min(6, @as(i8, boosts.atk) + n)));
|
|
2499
|
-
const reason = if (move.effect == .Rage)
|
|
2424
|
+
const reason: Boost = if (move.effect == .Rage) .Rage else .Attack;
|
|
2500
2425
|
if (stats.atk == MAX_STAT_VALUE) {
|
|
2501
2426
|
boosts.atk -= 1;
|
|
2502
2427
|
if (showdown) {
|
|
2503
|
-
try log.boost(ident, reason, n);
|
|
2504
|
-
try log.boost(ident,
|
|
2428
|
+
try log.boost(.{ ident, reason, n });
|
|
2429
|
+
try log.boost(.{ ident, .Attack, -1 });
|
|
2505
2430
|
if (move.effect == .Rage) return;
|
|
2506
2431
|
}
|
|
2507
|
-
return
|
|
2432
|
+
return log.fail(.{ ident, .None });
|
|
2508
2433
|
}
|
|
2509
|
-
|
|
2434
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2510
2435
|
const stat = unmodifiedStats(battle, side).atk;
|
|
2511
2436
|
stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2512
|
-
try log.boost(ident, reason, n);
|
|
2437
|
+
try log.boost(.{ ident, reason, n });
|
|
2513
2438
|
// Pokémon Showdown doesn't re-apply status modifiers after Rage boosts
|
|
2514
2439
|
if (showdown and move.effect == .Rage) return;
|
|
2515
2440
|
},
|
|
2516
2441
|
.DefenseUp1, .DefenseUp2 => {
|
|
2517
2442
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2518
|
-
if (boosts.def == 6) return
|
|
2443
|
+
if (boosts.def == 6) return log.fail(.{ ident, .None });
|
|
2519
2444
|
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
2520
2445
|
boosts.def = @intCast(@min(6, @as(i8, boosts.def) + n));
|
|
2521
2446
|
if (stats.def == MAX_STAT_VALUE) {
|
|
2522
2447
|
boosts.def -= 1;
|
|
2523
2448
|
if (showdown) {
|
|
2524
|
-
try log.boost(ident, .Defense, n);
|
|
2525
|
-
try log.boost(ident, .Defense, -1);
|
|
2449
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2450
|
+
try log.boost(.{ ident, .Defense, -1 });
|
|
2526
2451
|
}
|
|
2527
|
-
return
|
|
2452
|
+
return log.fail(.{ ident, .None });
|
|
2528
2453
|
}
|
|
2529
|
-
|
|
2454
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2530
2455
|
const stat = unmodifiedStats(battle, side).def;
|
|
2531
2456
|
stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2532
|
-
try log.boost(ident, .Defense, n);
|
|
2457
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2533
2458
|
},
|
|
2534
2459
|
.SpeedUp2 => {
|
|
2535
2460
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2536
|
-
if (boosts.spe == 6) return
|
|
2461
|
+
if (boosts.spe == 6) return log.fail(.{ ident, .None });
|
|
2537
2462
|
boosts.spe = @intCast(@min(6, @as(i8, boosts.spe) + 2));
|
|
2538
2463
|
if (stats.spe == MAX_STAT_VALUE) {
|
|
2539
2464
|
boosts.spe -= 1;
|
|
2540
2465
|
if (showdown) {
|
|
2541
|
-
try log.boost(ident, .Speed, 2);
|
|
2542
|
-
try log.boost(ident, .Speed, -1);
|
|
2466
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2467
|
+
try log.boost(.{ ident, .Speed, -1 });
|
|
2543
2468
|
}
|
|
2544
|
-
return
|
|
2469
|
+
return log.fail(.{ ident, .None });
|
|
2545
2470
|
}
|
|
2546
|
-
|
|
2471
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2547
2472
|
const stat = unmodifiedStats(battle, side).spe;
|
|
2548
2473
|
stats.spe = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2549
|
-
try log.boost(ident, .Speed, 2);
|
|
2474
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2550
2475
|
},
|
|
2551
2476
|
.SpecialUp1, .SpecialUp2 => {
|
|
2552
2477
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2553
|
-
if (boosts.spc == 6) return
|
|
2478
|
+
if (boosts.spc == 6) return log.fail(.{ ident, .None });
|
|
2554
2479
|
const n: u2 = if (move.effect == .SpecialUp2) 2 else 1;
|
|
2555
2480
|
boosts.spc = @intCast(@min(6, @as(i8, boosts.spc) + n));
|
|
2556
2481
|
if (stats.spc == MAX_STAT_VALUE) {
|
|
2557
2482
|
boosts.spc -= 1;
|
|
2558
2483
|
if (showdown) {
|
|
2559
|
-
try log.boost(ident, .SpecialAttack, n);
|
|
2560
|
-
try log.boost(ident, .SpecialAttack, -1);
|
|
2561
|
-
try log.boost(ident, .SpecialDefense, n);
|
|
2562
|
-
try log.boost(ident, .SpecialDefense, -1);
|
|
2484
|
+
try log.boost(.{ ident, .SpecialAttack, n });
|
|
2485
|
+
try log.boost(.{ ident, .SpecialAttack, -1 });
|
|
2486
|
+
try log.boost(.{ ident, .SpecialDefense, n });
|
|
2487
|
+
try log.boost(.{ ident, .SpecialDefense, -1 });
|
|
2563
2488
|
}
|
|
2564
|
-
return
|
|
2489
|
+
return log.fail(.{ ident, .None });
|
|
2565
2490
|
}
|
|
2566
|
-
|
|
2491
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2567
2492
|
const stat = unmodifiedStats(battle, side).spc;
|
|
2568
2493
|
stats.spc = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2569
|
-
try log.boost(ident, .SpecialAttack, n);
|
|
2570
|
-
try log.boost(ident, .SpecialDefense, n);
|
|
2494
|
+
try log.boost(.{ ident, .SpecialAttack, n });
|
|
2495
|
+
try log.boost(.{ ident, .SpecialDefense, n });
|
|
2571
2496
|
},
|
|
2572
2497
|
.EvasionUp1 => {
|
|
2573
2498
|
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2574
|
-
if (boosts.evasion == 6) return
|
|
2499
|
+
if (boosts.evasion == 6) return log.fail(.{ ident, .None });
|
|
2575
2500
|
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2576
|
-
try log.boost(ident, .Evasion, 1);
|
|
2501
|
+
try log.boost(.{ ident, .Evasion, 1 });
|
|
2577
2502
|
},
|
|
2578
2503
|
else => unreachable,
|
|
2579
2504
|
}
|
|
@@ -2588,7 +2513,7 @@ pub const Effects = struct {
|
|
|
2588
2513
|
const foe_ident = battle.active(player.foe());
|
|
2589
2514
|
const secondary = move.effect.isStatDownChance();
|
|
2590
2515
|
|
|
2591
|
-
if (foe.active.volatiles.Substitute) return if (!secondary) log.fail(foe_ident, .None);
|
|
2516
|
+
if (foe.active.volatiles.Substitute) return if (!secondary) log.fail(.{ foe_ident, .None });
|
|
2592
2517
|
|
|
2593
2518
|
if (secondary) {
|
|
2594
2519
|
const proc = try Rolls.unboost(battle, player, options);
|
|
@@ -2606,82 +2531,82 @@ pub const Effects = struct {
|
|
|
2606
2531
|
switch (move.effect) {
|
|
2607
2532
|
.AttackDown1, .AttackDownChance => {
|
|
2608
2533
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2609
|
-
if (boosts.atk == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2534
|
+
if (boosts.atk == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2610
2535
|
boosts.atk = @intCast(@max(-6, @as(i8, boosts.atk) - 1));
|
|
2611
2536
|
if (stats.atk == 1) {
|
|
2612
2537
|
boosts.atk += 1;
|
|
2613
2538
|
if (showdown) {
|
|
2614
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2615
|
-
try log.boost(foe_ident, .Attack, 1);
|
|
2539
|
+
try log.boost(.{ foe_ident, .Attack, -1 });
|
|
2540
|
+
try log.boost(.{ foe_ident, .Attack, 1 });
|
|
2616
2541
|
}
|
|
2617
|
-
return
|
|
2542
|
+
return log.fail(.{ foe_ident, .None });
|
|
2618
2543
|
}
|
|
2619
|
-
|
|
2544
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2620
2545
|
const stat = unmodifiedStats(battle, foe).atk;
|
|
2621
2546
|
stats.atk = @max(1, stat * mod[0] / mod[1]);
|
|
2622
|
-
try log.boost(foe_ident, .Attack, -1);
|
|
2547
|
+
try log.boost(.{ foe_ident, .Attack, -1 });
|
|
2623
2548
|
},
|
|
2624
2549
|
.DefenseDown1, .DefenseDown2, .DefenseDownChance => {
|
|
2625
2550
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2626
|
-
if (boosts.def == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2551
|
+
if (boosts.def == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2627
2552
|
const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
|
|
2628
2553
|
boosts.def = @intCast(@max(-6, @as(i8, boosts.def) - n));
|
|
2629
2554
|
if (stats.def == 1) {
|
|
2630
2555
|
boosts.def += 1;
|
|
2631
2556
|
if (showdown) {
|
|
2632
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2633
|
-
try log.boost(foe_ident, .Defense, 1);
|
|
2557
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2558
|
+
try log.boost(.{ foe_ident, .Defense, 1 });
|
|
2634
2559
|
}
|
|
2635
|
-
return
|
|
2560
|
+
return log.fail(.{ foe_ident, .None });
|
|
2636
2561
|
}
|
|
2637
|
-
|
|
2562
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2638
2563
|
const stat = unmodifiedStats(battle, foe).def;
|
|
2639
2564
|
stats.def = @max(1, stat * mod[0] / mod[1]);
|
|
2640
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2565
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2641
2566
|
},
|
|
2642
2567
|
.SpeedDown1, .SpeedDownChance => {
|
|
2643
2568
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2644
|
-
if (boosts.spe == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2569
|
+
if (boosts.spe == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2645
2570
|
boosts.spe = @intCast(@max(-6, @as(i8, boosts.spe) - 1));
|
|
2646
2571
|
if (stats.spe == 1) {
|
|
2647
2572
|
boosts.spe += 1;
|
|
2648
2573
|
if (showdown) {
|
|
2649
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2650
|
-
try log.boost(foe_ident, .Speed, 1);
|
|
2574
|
+
try log.boost(.{ foe_ident, .Speed, -1 });
|
|
2575
|
+
try log.boost(.{ foe_ident, .Speed, 1 });
|
|
2651
2576
|
}
|
|
2652
|
-
return
|
|
2577
|
+
return log.fail(.{ foe_ident, .None });
|
|
2653
2578
|
}
|
|
2654
|
-
|
|
2579
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2655
2580
|
const stat = unmodifiedStats(battle, foe).spe;
|
|
2656
2581
|
stats.spe = @max(1, stat * mod[0] / mod[1]);
|
|
2657
|
-
try log.boost(foe_ident, .Speed, -1);
|
|
2582
|
+
try log.boost(.{ foe_ident, .Speed, -1 });
|
|
2658
2583
|
assert(boosts.spe >= -6);
|
|
2659
2584
|
},
|
|
2660
2585
|
.SpecialDownChance => {
|
|
2661
2586
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2662
|
-
if (boosts.spc == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2587
|
+
if (boosts.spc == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2663
2588
|
boosts.spc = @intCast(@max(-6, @as(i8, boosts.spc) - 1));
|
|
2664
2589
|
if (stats.spc == 1) {
|
|
2665
2590
|
boosts.spc += 1;
|
|
2666
2591
|
if (showdown) {
|
|
2667
|
-
try log.boost(foe_ident, .SpecialAttack, -1);
|
|
2668
|
-
try log.boost(foe_ident, .SpecialAttack, 1);
|
|
2669
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2670
|
-
try log.boost(foe_ident, .SpecialDefense, 1);
|
|
2592
|
+
try log.boost(.{ foe_ident, .SpecialAttack, -1 });
|
|
2593
|
+
try log.boost(.{ foe_ident, .SpecialAttack, 1 });
|
|
2594
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2595
|
+
try log.boost(.{ foe_ident, .SpecialDefense, 1 });
|
|
2671
2596
|
}
|
|
2672
|
-
return
|
|
2597
|
+
return log.fail(.{ foe_ident, .None });
|
|
2673
2598
|
}
|
|
2674
|
-
|
|
2599
|
+
const mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2675
2600
|
const stat = unmodifiedStats(battle, foe).spc;
|
|
2676
2601
|
stats.spc = @max(1, stat * mod[0] / mod[1]);
|
|
2677
|
-
try log.boost(foe_ident, .SpecialAttack, -1);
|
|
2678
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2602
|
+
try log.boost(.{ foe_ident, .SpecialAttack, -1 });
|
|
2603
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2679
2604
|
},
|
|
2680
2605
|
.AccuracyDown1 => {
|
|
2681
2606
|
assert(boosts.accuracy >= -6 and boosts.accuracy <= 6);
|
|
2682
|
-
if (boosts.accuracy == -6) return if (fail) try log.fail(foe_ident, .None);
|
|
2607
|
+
if (boosts.accuracy == -6) return if (fail) try log.fail(.{ foe_ident, .None });
|
|
2683
2608
|
boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
|
|
2684
|
-
try log.boost(foe_ident, .Accuracy, -1);
|
|
2609
|
+
try log.boost(.{ foe_ident, .Accuracy, -1 });
|
|
2685
2610
|
},
|
|
2686
2611
|
else => unreachable,
|
|
2687
2612
|
}
|
|
@@ -2705,7 +2630,7 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
2705
2630
|
}
|
|
2706
2631
|
}
|
|
2707
2632
|
|
|
2708
|
-
|
|
2633
|
+
fn isForced(active: anytype) bool {
|
|
2709
2634
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
2710
2635
|
active.volatiles.Thrashing or active.volatiles.Charging;
|
|
2711
2636
|
}
|
|
@@ -2718,27 +2643,27 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2718
2643
|
|
|
2719
2644
|
options.chance.clearDurations(who, clear);
|
|
2720
2645
|
|
|
2721
|
-
if (volatiles.
|
|
2722
|
-
volatiles.
|
|
2723
|
-
volatiles.
|
|
2724
|
-
try log.end(ident, .DisableSilent);
|
|
2646
|
+
if (volatiles.disable_move != 0) {
|
|
2647
|
+
volatiles.disable_move = 0;
|
|
2648
|
+
volatiles.disable_duration = 0;
|
|
2649
|
+
try log.end(.{ ident, .DisableSilent });
|
|
2725
2650
|
}
|
|
2726
2651
|
if (volatiles.Confusion) {
|
|
2727
2652
|
// volatiles.confusion is left unchanged
|
|
2728
2653
|
volatiles.Confusion = false;
|
|
2729
|
-
try log.end(ident, .ConfusionSilent);
|
|
2654
|
+
try log.end(.{ ident, .ConfusionSilent });
|
|
2730
2655
|
}
|
|
2731
2656
|
if (volatiles.Mist) {
|
|
2732
2657
|
volatiles.Mist = false;
|
|
2733
|
-
try log.end(ident, .
|
|
2658
|
+
try log.end(.{ ident, .MistSilent });
|
|
2734
2659
|
}
|
|
2735
2660
|
if (volatiles.FocusEnergy) {
|
|
2736
2661
|
volatiles.FocusEnergy = false;
|
|
2737
|
-
try log.end(ident, .
|
|
2662
|
+
try log.end(.{ ident, .FocusEnergySilent });
|
|
2738
2663
|
}
|
|
2739
2664
|
if (volatiles.LeechSeed) {
|
|
2740
2665
|
volatiles.LeechSeed = false;
|
|
2741
|
-
try log.end(ident, .
|
|
2666
|
+
try log.end(.{ ident, .LeechSeedSilent });
|
|
2742
2667
|
}
|
|
2743
2668
|
if (!showdown and volatiles.Toxic) {
|
|
2744
2669
|
volatiles.Toxic = false;
|
|
@@ -2746,24 +2671,26 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2746
2671
|
}
|
|
2747
2672
|
if (volatiles.LightScreen) {
|
|
2748
2673
|
volatiles.LightScreen = false;
|
|
2749
|
-
try log.end(ident, .
|
|
2674
|
+
try log.end(.{ ident, .LightScreenSilent });
|
|
2750
2675
|
}
|
|
2751
2676
|
if (volatiles.Reflect) {
|
|
2752
2677
|
volatiles.Reflect = false;
|
|
2753
|
-
try log.end(ident, .
|
|
2678
|
+
try log.end(.{ ident, .ReflectSilent });
|
|
2754
2679
|
}
|
|
2755
2680
|
if (showdown and volatiles.Toxic) {
|
|
2756
2681
|
volatiles.Toxic = false;
|
|
2757
2682
|
// Pokémon Showdown erroneously clears the toxic counter
|
|
2758
2683
|
volatiles.toxic = 0;
|
|
2759
|
-
try log.end(ident, .
|
|
2684
|
+
try log.end(.{ ident, .ToxicSilent });
|
|
2760
2685
|
}
|
|
2761
2686
|
}
|
|
2762
2687
|
|
|
2763
2688
|
pub const Rolls = struct {
|
|
2764
|
-
|
|
2689
|
+
fn speedTie(battle: anytype, options: anytype) !bool {
|
|
2765
2690
|
const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
|
|
2766
2691
|
player == .P1
|
|
2692
|
+
else if (@hasDecl(@TypeOf(battle.rng), "speedTie"))
|
|
2693
|
+
battle.rng.speedTie()
|
|
2767
2694
|
else if (showdown)
|
|
2768
2695
|
battle.rng.range(u8, 0, 2) == 0
|
|
2769
2696
|
else
|
|
@@ -2773,9 +2700,11 @@ pub const Rolls = struct {
|
|
|
2773
2700
|
return p1;
|
|
2774
2701
|
}
|
|
2775
2702
|
|
|
2776
|
-
|
|
2703
|
+
fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
|
|
2777
2704
|
const crit = if (options.calc.overridden(player, .critical_hit)) |val|
|
|
2778
2705
|
val == .true
|
|
2706
|
+
else if (@hasDecl(@TypeOf(battle.rng), "criticalHit"))
|
|
2707
|
+
battle.rng.criticalHit(player, rate)
|
|
2779
2708
|
else if (showdown)
|
|
2780
2709
|
battle.rng.chance(u8, rate, 256)
|
|
2781
2710
|
else
|
|
@@ -2785,9 +2714,11 @@ pub const Rolls = struct {
|
|
|
2785
2714
|
return crit;
|
|
2786
2715
|
}
|
|
2787
2716
|
|
|
2788
|
-
|
|
2717
|
+
fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2789
2718
|
const roll = if (options.calc.overridden(player, .damage)) |val|
|
|
2790
2719
|
val
|
|
2720
|
+
else if (@hasDecl(@TypeOf(battle.rng), "damage"))
|
|
2721
|
+
battle.rng.damage(player)
|
|
2791
2722
|
else roll: {
|
|
2792
2723
|
if (showdown) break :roll battle.rng.range(u8, 217, 256);
|
|
2793
2724
|
while (true) {
|
|
@@ -2796,13 +2727,16 @@ pub const Rolls = struct {
|
|
|
2796
2727
|
}
|
|
2797
2728
|
};
|
|
2798
2729
|
|
|
2730
|
+
assert(roll >= 217 and roll <= 255);
|
|
2799
2731
|
try options.chance.damage(player, roll);
|
|
2800
2732
|
return roll;
|
|
2801
2733
|
}
|
|
2802
2734
|
|
|
2803
|
-
|
|
2735
|
+
fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) bool {
|
|
2804
2736
|
const ok = if (options.calc.overridden(player, .hit)) |val|
|
|
2805
2737
|
val == .true
|
|
2738
|
+
else if (@hasDecl(@TypeOf(battle.rng), "hit"))
|
|
2739
|
+
battle.rng.hit(player, accuracy)
|
|
2806
2740
|
else if (showdown)
|
|
2807
2741
|
battle.rng.chance(u8, accuracy, 256)
|
|
2808
2742
|
else
|
|
@@ -2812,9 +2746,11 @@ pub const Rolls = struct {
|
|
|
2812
2746
|
return ok;
|
|
2813
2747
|
}
|
|
2814
2748
|
|
|
2815
|
-
|
|
2749
|
+
fn confused(battle: anytype, player: Player, options: anytype) !bool {
|
|
2816
2750
|
const cfz = if (options.calc.overridden(player, .confused)) |val|
|
|
2817
2751
|
val == .true
|
|
2752
|
+
else if (@hasDecl(@TypeOf(battle.rng), "confused"))
|
|
2753
|
+
battle.rng.confused(player)
|
|
2818
2754
|
else if (showdown)
|
|
2819
2755
|
!battle.rng.chance(u8, 128, 256)
|
|
2820
2756
|
else
|
|
@@ -2824,9 +2760,11 @@ pub const Rolls = struct {
|
|
|
2824
2760
|
return cfz;
|
|
2825
2761
|
}
|
|
2826
2762
|
|
|
2827
|
-
|
|
2763
|
+
fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
|
|
2828
2764
|
const par = if (options.calc.overridden(player, .paralyzed)) |val|
|
|
2829
2765
|
val == .true
|
|
2766
|
+
else if (@hasDecl(@TypeOf(battle.rng), "paralyzed"))
|
|
2767
|
+
battle.rng.paralyzed(player)
|
|
2830
2768
|
else if (showdown)
|
|
2831
2769
|
battle.rng.chance(u8, 63, 256)
|
|
2832
2770
|
else
|
|
@@ -2836,9 +2774,11 @@ pub const Rolls = struct {
|
|
|
2836
2774
|
return par;
|
|
2837
2775
|
}
|
|
2838
2776
|
|
|
2839
|
-
|
|
2777
|
+
fn confusionChance(battle: anytype, player: Player, options: anytype) !bool {
|
|
2840
2778
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2841
2779
|
val == .true
|
|
2780
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2781
|
+
battle.rng.secondaryChance(player, 25)
|
|
2842
2782
|
else if (showdown)
|
|
2843
2783
|
battle.rng.chance(u8, 25, 256)
|
|
2844
2784
|
else
|
|
@@ -2848,11 +2788,13 @@ pub const Rolls = struct {
|
|
|
2848
2788
|
return proc;
|
|
2849
2789
|
}
|
|
2850
2790
|
|
|
2851
|
-
|
|
2791
|
+
fn secondaryChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2852
2792
|
const rate: u8 = if (low) 26 else 77;
|
|
2853
2793
|
|
|
2854
2794
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2855
2795
|
val == .true
|
|
2796
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2797
|
+
battle.rng.secondaryChance(player, rate)
|
|
2856
2798
|
else if (showdown)
|
|
2857
2799
|
battle.rng.chance(u8, rate, 256)
|
|
2858
2800
|
else
|
|
@@ -2862,11 +2804,13 @@ pub const Rolls = struct {
|
|
|
2862
2804
|
return proc;
|
|
2863
2805
|
}
|
|
2864
2806
|
|
|
2865
|
-
|
|
2807
|
+
fn poisonChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2866
2808
|
const rate: u8 = if (low) 52 else 103;
|
|
2867
2809
|
|
|
2868
2810
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2869
2811
|
val == .true
|
|
2812
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2813
|
+
battle.rng.secondaryChance(player, rate)
|
|
2870
2814
|
else if (showdown)
|
|
2871
2815
|
battle.rng.chance(u8, rate, 256)
|
|
2872
2816
|
else
|
|
@@ -2876,9 +2820,11 @@ pub const Rolls = struct {
|
|
|
2876
2820
|
return proc;
|
|
2877
2821
|
}
|
|
2878
2822
|
|
|
2879
|
-
|
|
2823
|
+
fn unboost(battle: anytype, player: Player, options: anytype) !bool {
|
|
2880
2824
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2881
2825
|
val == .true
|
|
2826
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2827
|
+
battle.rng.secondaryChance(player, 85)
|
|
2882
2828
|
else if (showdown)
|
|
2883
2829
|
battle.rng.chance(u8, 85, 256)
|
|
2884
2830
|
else
|
|
@@ -2888,14 +2834,14 @@ pub const Rolls = struct {
|
|
|
2888
2834
|
return proc;
|
|
2889
2835
|
}
|
|
2890
2836
|
|
|
2891
|
-
|
|
2837
|
+
fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
2892
2838
|
const move: Move = if (options.calc.overridden(player, .metronome)) |val|
|
|
2893
2839
|
val
|
|
2894
|
-
else if (
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2840
|
+
else if (@hasDecl(@TypeOf(battle.rng), "metronome"))
|
|
2841
|
+
battle.rng.metronome(player)
|
|
2842
|
+
else if (showdown)
|
|
2843
|
+
Move.METRONOME[battle.rng.range(u8, 0, Move.METRONOME.len)]
|
|
2844
|
+
else move: {
|
|
2899
2845
|
while (true) {
|
|
2900
2846
|
const r = battle.rng.next();
|
|
2901
2847
|
if (r == 0 or r == @intFromEnum(Move.Metronome)) continue;
|
|
@@ -2908,9 +2854,11 @@ pub const Rolls = struct {
|
|
|
2908
2854
|
return move;
|
|
2909
2855
|
}
|
|
2910
2856
|
|
|
2911
|
-
|
|
2857
|
+
fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
|
|
2912
2858
|
const power = if (options.calc.overridden(player, .psywave)) |val|
|
|
2913
2859
|
val - 1
|
|
2860
|
+
else if (@hasDecl(@TypeOf(battle.rng), "psywave"))
|
|
2861
|
+
battle.rng.psywave(player)
|
|
2914
2862
|
else if (showdown)
|
|
2915
2863
|
battle.rng.range(u8, 0, max)
|
|
2916
2864
|
else power: {
|
|
@@ -2920,15 +2868,18 @@ pub const Rolls = struct {
|
|
|
2920
2868
|
}
|
|
2921
2869
|
};
|
|
2922
2870
|
|
|
2871
|
+
assert(power < max);
|
|
2923
2872
|
try options.chance.psywave(player, power, max);
|
|
2924
2873
|
return power;
|
|
2925
2874
|
}
|
|
2926
2875
|
|
|
2927
|
-
|
|
2876
|
+
fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2928
2877
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2929
2878
|
@intCast(val)
|
|
2879
|
+
else if (@hasDecl(@TypeOf(battle.rng), "sleepDuration"))
|
|
2880
|
+
battle.rng.sleepDuration(player)
|
|
2930
2881
|
else if (showdown)
|
|
2931
|
-
|
|
2882
|
+
battle.rng.range(u3, 1, 8)
|
|
2932
2883
|
else duration: {
|
|
2933
2884
|
while (true) {
|
|
2934
2885
|
const r = battle.rng.next() & 7;
|
|
@@ -2936,47 +2887,59 @@ pub const Rolls = struct {
|
|
|
2936
2887
|
}
|
|
2937
2888
|
};
|
|
2938
2889
|
|
|
2890
|
+
assert(duration >= 1 and duration <= 7);
|
|
2939
2891
|
options.chance.duration(.sleep, player, player.foe(), duration);
|
|
2940
2892
|
return duration;
|
|
2941
2893
|
}
|
|
2942
2894
|
|
|
2943
|
-
|
|
2895
|
+
fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
|
|
2944
2896
|
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2945
2897
|
val
|
|
2898
|
+
else if (@hasDecl(@TypeOf(battle.rng), "disableDuration"))
|
|
2899
|
+
battle.rng.disableDuration(player)
|
|
2946
2900
|
else if (showdown)
|
|
2947
|
-
|
|
2901
|
+
battle.rng.range(u4, 1, 9)
|
|
2948
2902
|
else
|
|
2949
2903
|
@intCast((battle.rng.next() & 7) + 1);
|
|
2950
2904
|
|
|
2905
|
+
assert(duration >= 1 and duration <= 8);
|
|
2951
2906
|
options.chance.duration(.disable, player, player.foe(), duration);
|
|
2952
2907
|
return duration;
|
|
2953
2908
|
}
|
|
2954
2909
|
|
|
2955
|
-
|
|
2910
|
+
fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
|
|
2956
2911
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2957
2912
|
@intCast(val)
|
|
2913
|
+
else if (@hasDecl(@TypeOf(battle.rng), "confusionDuration"))
|
|
2914
|
+
battle.rng.confusionDuration(player, self)
|
|
2958
2915
|
else if (showdown)
|
|
2959
|
-
|
|
2916
|
+
battle.rng.range(u3, 2, 6)
|
|
2960
2917
|
else
|
|
2961
2918
|
@intCast((battle.rng.next() & 3) + 2);
|
|
2962
2919
|
|
|
2920
|
+
assert(duration >= 2 and duration <= 5);
|
|
2963
2921
|
options.chance.duration(.confusion, player, if (self) player else player.foe(), duration);
|
|
2964
2922
|
return duration;
|
|
2965
2923
|
}
|
|
2966
2924
|
|
|
2967
|
-
|
|
2925
|
+
fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2968
2926
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2969
2927
|
@intCast(val)
|
|
2928
|
+
else if (@hasDecl(@TypeOf(battle.rng), "attackingDuration"))
|
|
2929
|
+
battle.rng.attackingDuration(player)
|
|
2970
2930
|
else if (showdown)
|
|
2971
|
-
|
|
2931
|
+
battle.rng.range(u3, 2, 4)
|
|
2972
2932
|
else
|
|
2973
2933
|
@intCast((battle.rng.next() & 1) + 2);
|
|
2974
2934
|
|
|
2935
|
+
assert(duration >= 2 and duration <= 3);
|
|
2975
2936
|
options.chance.duration(.attacking, player, player, duration);
|
|
2976
2937
|
return duration;
|
|
2977
2938
|
}
|
|
2978
2939
|
|
|
2979
|
-
|
|
2940
|
+
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
2941
|
+
|
|
2942
|
+
fn distribution(
|
|
2980
2943
|
battle: anytype,
|
|
2981
2944
|
comptime effect: Move.Effect,
|
|
2982
2945
|
player: Player,
|
|
@@ -2985,13 +2948,16 @@ pub const Rolls = struct {
|
|
|
2985
2948
|
const roll = if (effect == .Binding) .duration else .multi_hit;
|
|
2986
2949
|
const n: u3 = if (options.calc.overridden(player, roll)) |val|
|
|
2987
2950
|
@intCast(val)
|
|
2951
|
+
else if (@hasDecl(@TypeOf(battle.rng), "distribution"))
|
|
2952
|
+
battle.rng.distribution(player)
|
|
2988
2953
|
else if (showdown)
|
|
2989
|
-
DISTRIBUTION[battle.rng.range(
|
|
2954
|
+
DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
|
|
2990
2955
|
else n: {
|
|
2991
2956
|
const r = (battle.rng.next() & 3);
|
|
2992
2957
|
break :n @intCast((if (r < 2) r else battle.rng.next() & 3) + 2);
|
|
2993
2958
|
};
|
|
2994
2959
|
|
|
2960
|
+
assert(n >= 2 and n <= 5);
|
|
2995
2961
|
if (effect == .Binding) {
|
|
2996
2962
|
options.chance.duration(.binding, player, player, n);
|
|
2997
2963
|
} else {
|
|
@@ -3016,7 +2982,9 @@ pub const Rolls = struct {
|
|
|
3016
2982
|
else
|
|
3017
2983
|
null;
|
|
3018
2984
|
|
|
3019
|
-
const slot: u4 = overridden orelse
|
|
2985
|
+
const slot: u4 = overridden orelse if (@hasDecl(@TypeOf(battle.rng), "moveSlot"))
|
|
2986
|
+
battle.rng.moveSlot(player, moves, check_pp)
|
|
2987
|
+
else slot: {
|
|
3020
2988
|
if (showdown) {
|
|
3021
2989
|
if (check_pp == 0) {
|
|
3022
2990
|
var i: usize = moves.len;
|
|
@@ -3032,10 +3000,9 @@ pub const Rolls = struct {
|
|
|
3032
3000
|
while (i < moves.len and r > 0) : (i += 1) {
|
|
3033
3001
|
if (moves[i].pp > 0) {
|
|
3034
3002
|
r -= 1;
|
|
3035
|
-
if (r == 0) break;
|
|
3003
|
+
if (r == 0) break :slot @intCast(i + 1);
|
|
3036
3004
|
}
|
|
3037
3005
|
}
|
|
3038
|
-
break :slot @intCast(i + 1);
|
|
3039
3006
|
}
|
|
3040
3007
|
}
|
|
3041
3008
|
|
|
@@ -3057,11 +3024,11 @@ test "RNG agreement" {
|
|
|
3057
3024
|
expected[i] = @intCast(i * 0x1000000);
|
|
3058
3025
|
}
|
|
3059
3026
|
|
|
3060
|
-
var spe
|
|
3061
|
-
var cfz
|
|
3062
|
-
var par
|
|
3063
|
-
var brn
|
|
3064
|
-
var eff
|
|
3027
|
+
var spe: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3028
|
+
var cfz: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3029
|
+
var par: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3030
|
+
var brn: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3031
|
+
var eff: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3065
3032
|
|
|
3066
3033
|
for (0..expected.len) |i| {
|
|
3067
3034
|
try expectEqual(spe.range(u8, 0, 2) == 0, i < Gen12.percent(50) + 1);
|
|
@@ -3097,7 +3064,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3097
3064
|
const side = battle.side(player);
|
|
3098
3065
|
const foe = battle.foe(player);
|
|
3099
3066
|
|
|
3100
|
-
|
|
3067
|
+
const active = &side.active;
|
|
3101
3068
|
const stored = side.stored();
|
|
3102
3069
|
|
|
3103
3070
|
// While players are not given any input options on the cartridge in these cases,
|
|
@@ -3151,7 +3118,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3151
3118
|
if (m.id == if (active.volatiles.Bide) .Bide else side.last_selected_move) {
|
|
3152
3119
|
// Pokémon Showdown displays Struggle if limited to Bide but unable to pick
|
|
3153
3120
|
const struggle =
|
|
3154
|
-
m.id == .Bide and (m.pp == 0 or active.volatiles.
|
|
3121
|
+
m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
|
|
3155
3122
|
const s = if (struggle) 0 else slot;
|
|
3156
3123
|
out[n] = .{ .type = .Move, .data = s };
|
|
3157
3124
|
n += 1;
|
|
@@ -3166,7 +3133,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3166
3133
|
const m = active.moves[slot - 1];
|
|
3167
3134
|
if (m.id == .None) break;
|
|
3168
3135
|
if (m.pp == 0) continue;
|
|
3169
|
-
if (active.volatiles.
|
|
3136
|
+
if (active.volatiles.disable_move == slot) continue;
|
|
3170
3137
|
out[n] = .{ .type = .Move, .data = slot };
|
|
3171
3138
|
n += 1;
|
|
3172
3139
|
}
|