@pkmn/engine 0.1.0-dev.21a51fe4 → 0.1.0-dev.21e728d9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -3
- 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 +2 -2
- package/build/pkg/gen1.js +2 -2
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/protocol.js +56 -37
- package/build/pkg/protocol.js.map +1 -1
- package/build/tools/debug.js +3 -1
- package/build/tools/debug.js.map +1 -1
- package/build.zig +72 -57
- package/package.json +27 -15
- package/src/bin/install-pkmn-engine +1 -1
- package/src/lib/binding/node.zig +1 -1
- package/src/lib/common/data.zig +8 -20
- package/src/lib/common/optional.zig +8 -1
- package/src/lib/common/protocol.zig +578 -80
- package/src/lib/common/rational.zig +5 -5
- package/src/lib/common/rng.zig +15 -6
- package/src/lib/gen1/README.md +114 -32
- package/src/lib/gen1/calc.zig +4 -5
- package/src/lib/gen1/chance.zig +58 -42
- package/src/lib/gen1/data/moves.zig +192 -180
- package/src/lib/gen1/data/species.zig +2 -2
- package/src/lib/gen1/data/types.zig +7 -7
- package/src/lib/gen1/data.zig +31 -43
- package/src/lib/gen1/helpers.zig +254 -116
- package/src/lib/gen1/mechanics.zig +142 -176
- package/src/lib/gen2/README.md +93 -72
- package/src/lib/gen2/calc.zig +143 -0
- package/src/lib/gen2/chance.zig +1314 -0
- package/src/lib/gen2/data/items.zig +12 -18
- package/src/lib/gen2/data/moves.zig +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 +100 -73
- package/src/lib/gen2/generated.zig +808 -0
- package/src/lib/gen2/helpers.zig +8 -12
- package/src/lib/gen2/mechanics.zig +3486 -9
|
@@ -7,21 +7,19 @@ 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;
|
|
27
25
|
const Heal = protocol.Heal;
|
|
@@ -59,23 +57,15 @@ const BOOSTS = &[_][2]u8{
|
|
|
59
57
|
|
|
60
58
|
const MAX_STAT_VALUE = 999;
|
|
61
59
|
|
|
62
|
-
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
63
|
-
|
|
64
60
|
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
61
|
assert(c1.type != .Pass or c2.type != .Pass or battle.turn == 0);
|
|
70
62
|
if (battle.turn == 0) return start(battle, options);
|
|
71
63
|
|
|
72
64
|
var s1 = false;
|
|
73
65
|
var s2 = false;
|
|
74
|
-
var f1: ?Move = null;
|
|
75
|
-
var f2: ?Move = null;
|
|
76
66
|
|
|
77
|
-
if (selectMove(battle, .P1, c1, c2, &s1
|
|
78
|
-
if (selectMove(battle, .P2, c2, c1, &s2
|
|
67
|
+
if (selectMove(battle, .P1, c1, c2, &s1)) |r| return r;
|
|
68
|
+
if (selectMove(battle, .P2, c2, c1, &s2)) |r| return r;
|
|
79
69
|
|
|
80
70
|
var p1 = battle.side(.P1);
|
|
81
71
|
var p2 = battle.side(.P2);
|
|
@@ -84,9 +74,9 @@ pub fn update(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Result
|
|
|
84
74
|
var r2 = showdown and p2.active.volatiles.Binding and c1.type == .Switch;
|
|
85
75
|
|
|
86
76
|
if (try turnOrder(battle, c1, c2, options) == .P1) {
|
|
87
|
-
if (try doTurn(battle, .P1, c1, r1, s1,
|
|
77
|
+
if (try doTurn(battle, .P1, c1, r1, s1, .P2, c2, r2, s2, options)) |r| return r;
|
|
88
78
|
} else {
|
|
89
|
-
if (try doTurn(battle, .P2, c2, r2, s2,
|
|
79
|
+
if (try doTurn(battle, .P2, c2, r2, s2, .P1, c1, r1, s1, options)) |r| return r;
|
|
90
80
|
}
|
|
91
81
|
|
|
92
82
|
if (p1.active.volatiles.attacks == 0) p1.active.volatiles.Binding = false;
|
|
@@ -124,7 +114,6 @@ fn selectMove(
|
|
|
124
114
|
choice: Choice,
|
|
125
115
|
foe_choice: Choice,
|
|
126
116
|
skip_turn: *bool,
|
|
127
|
-
from: *?Move,
|
|
128
117
|
) ?Result {
|
|
129
118
|
if (choice.type == .Pass) return null;
|
|
130
119
|
|
|
@@ -141,7 +130,6 @@ fn selectMove(
|
|
|
141
130
|
return null;
|
|
142
131
|
}
|
|
143
132
|
if (volatiles.Rage) {
|
|
144
|
-
from.* = side.last_used_move;
|
|
145
133
|
if (showdown) {
|
|
146
134
|
if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
|
|
147
135
|
saveMove(battle, player, null);
|
|
@@ -151,7 +139,6 @@ fn selectMove(
|
|
|
151
139
|
// Pokémon Showdown removes Flinch at the end-of-turn in its residual handler
|
|
152
140
|
if (!showdown) volatiles.Flinch = false;
|
|
153
141
|
if (volatiles.Thrashing or volatiles.Charging) {
|
|
154
|
-
from.* = side.last_selected_move;
|
|
155
142
|
if (showdown) {
|
|
156
143
|
if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
|
|
157
144
|
saveMove(battle, player, null);
|
|
@@ -172,9 +159,7 @@ fn selectMove(
|
|
|
172
159
|
return null;
|
|
173
160
|
}
|
|
174
161
|
if (volatiles.Binding) {
|
|
175
|
-
from.* = side.last_used_move;
|
|
176
162
|
if (showdown) {
|
|
177
|
-
if (foe_choice.type == .Switch) from.* = null;
|
|
178
163
|
// Pokémon Showdown overwrites Mirror Move with whatever was selected - really this
|
|
179
164
|
// should set side.last_selected_move = last.id to reuse Mirror Move and fail in order
|
|
180
165
|
// to satisfy the conditions of the Desync Clause Mod. However, because Binding is still
|
|
@@ -210,16 +195,13 @@ fn selectMove(
|
|
|
210
195
|
if (choice.data == 0) {
|
|
211
196
|
const struggle = ok: {
|
|
212
197
|
for (side.active.moves, 0..) |move, i| {
|
|
213
|
-
if (move.pp > 0 and volatiles.
|
|
198
|
+
if (move.pp > 0 and volatiles.disable_move != i + 1) break :ok false;
|
|
214
199
|
}
|
|
215
200
|
break :ok true;
|
|
216
201
|
};
|
|
217
|
-
|
|
218
202
|
assert(struggle);
|
|
219
|
-
saveMove(battle, player, choice);
|
|
220
|
-
} else {
|
|
221
|
-
saveMove(battle, player, choice);
|
|
222
203
|
}
|
|
204
|
+
saveMove(battle, player, choice);
|
|
223
205
|
|
|
224
206
|
return null;
|
|
225
207
|
}
|
|
@@ -232,7 +214,7 @@ fn saveMove(battle: anytype, player: Player, choice: ?Choice) void {
|
|
|
232
214
|
if (c.data == 0) {
|
|
233
215
|
side.last_selected_move = .Struggle;
|
|
234
216
|
} else {
|
|
235
|
-
assert(showdown or side.active.volatiles.
|
|
217
|
+
assert(showdown or side.active.volatiles.disable_move != c.data);
|
|
236
218
|
const move = side.active.move(c.data);
|
|
237
219
|
// You cannot *select* a move with 0 PP (except on Pokémon Showdown where that is
|
|
238
220
|
// sometimes required...), but a 0 PP move can be used automatically
|
|
@@ -343,12 +325,10 @@ fn doTurn(
|
|
|
343
325
|
player_choice: Choice,
|
|
344
326
|
player_rewrap: bool,
|
|
345
327
|
player_skip: bool,
|
|
346
|
-
player_from: ?Move,
|
|
347
328
|
foe_player: Player,
|
|
348
329
|
foe_choice: Choice,
|
|
349
330
|
foe_rewrap: bool,
|
|
350
331
|
foe_skip: bool,
|
|
351
|
-
foe_from: ?Move,
|
|
352
332
|
options: anytype,
|
|
353
333
|
) !?Result {
|
|
354
334
|
assert(player_choice.type != .Pass);
|
|
@@ -361,7 +341,6 @@ fn doTurn(
|
|
|
361
341
|
player_choice,
|
|
362
342
|
player_rewrap,
|
|
363
343
|
player_skip,
|
|
364
|
-
player_from,
|
|
365
344
|
&residual,
|
|
366
345
|
options,
|
|
367
346
|
)) |r| return r;
|
|
@@ -384,7 +363,6 @@ fn doTurn(
|
|
|
384
363
|
foe_choice,
|
|
385
364
|
foe_rewrap,
|
|
386
365
|
foe_skip,
|
|
387
|
-
foe_from,
|
|
388
366
|
&residual,
|
|
389
367
|
options,
|
|
390
368
|
)) |r| return r;
|
|
@@ -412,7 +390,6 @@ fn executeMove(
|
|
|
412
390
|
choice: Choice,
|
|
413
391
|
rewrap: bool,
|
|
414
392
|
skip: bool,
|
|
415
|
-
from: ?Move,
|
|
416
393
|
residual: *bool,
|
|
417
394
|
options: anytype,
|
|
418
395
|
) !?Result {
|
|
@@ -447,8 +424,10 @@ fn executeMove(
|
|
|
447
424
|
mslot = @intCast(battle.lastMove(player).index);
|
|
448
425
|
const stored = side.stored();
|
|
449
426
|
// GLITCH: Struggle bypass PP underflow via Hyper Beam / Trapping-switch auto selection
|
|
450
|
-
auto = side.
|
|
451
|
-
|
|
427
|
+
auto = isForced(&side.active) or
|
|
428
|
+
side.active.volatiles.Binding or side.active.volatiles.Bide or
|
|
429
|
+
side.last_selected_move == .HyperBeam or
|
|
430
|
+
Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP);
|
|
452
431
|
// If it wasn't Hyper Beam or the continuation of a move effect then we must have just
|
|
453
432
|
// thawed, in which case we will desync unless the last_selected_move happened to be at
|
|
454
433
|
// index 1 and the current Pokémon has the same move in its first slot.
|
|
@@ -470,7 +449,7 @@ fn executeMove(
|
|
|
470
449
|
|
|
471
450
|
var skip_can = false;
|
|
472
451
|
var skip_pp = false;
|
|
473
|
-
switch (try beforeMove(battle, player, skip,
|
|
452
|
+
switch (try beforeMove(battle, player, skip, residual, options)) {
|
|
474
453
|
.done => return null,
|
|
475
454
|
.skip_can => skip_can = true,
|
|
476
455
|
.skip_pp => skip_pp = true,
|
|
@@ -478,9 +457,9 @@ fn executeMove(
|
|
|
478
457
|
.err => return @as(?Result, Result.Error),
|
|
479
458
|
}
|
|
480
459
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
460
|
+
const can = skip_can or
|
|
461
|
+
try canMove(battle, player, mslot, auto, skip_pp, .None, residual, options);
|
|
462
|
+
if (!can) return null;
|
|
484
463
|
|
|
485
464
|
return doMove(battle, player, mslot, rewrap, auto, residual, options);
|
|
486
465
|
}
|
|
@@ -491,7 +470,6 @@ fn beforeMove(
|
|
|
491
470
|
battle: anytype,
|
|
492
471
|
player: Player,
|
|
493
472
|
skip: bool,
|
|
494
|
-
from: ?Move,
|
|
495
473
|
residual: *bool,
|
|
496
474
|
options: anytype,
|
|
497
475
|
) !BeforeMove {
|
|
@@ -552,16 +530,16 @@ fn beforeMove(
|
|
|
552
530
|
return .done;
|
|
553
531
|
}
|
|
554
532
|
|
|
555
|
-
if (volatiles.
|
|
533
|
+
if (volatiles.disable_duration > 0) {
|
|
556
534
|
if (options.calc.modify(player, .disable)) |extend| {
|
|
557
|
-
if (!extend) volatiles.
|
|
535
|
+
if (!extend) volatiles.disable_duration = 0;
|
|
558
536
|
} else {
|
|
559
|
-
volatiles.
|
|
537
|
+
volatiles.disable_duration -= 1;
|
|
560
538
|
}
|
|
561
|
-
try options.chance.disable(player, volatiles.
|
|
539
|
+
try options.chance.disable(player, volatiles.disable_duration);
|
|
562
540
|
|
|
563
|
-
if (volatiles.
|
|
564
|
-
volatiles.
|
|
541
|
+
if (volatiles.disable_duration == 0) {
|
|
542
|
+
volatiles.disable_move = 0;
|
|
565
543
|
try log.end(ident, .Disable);
|
|
566
544
|
}
|
|
567
545
|
}
|
|
@@ -698,7 +676,6 @@ fn beforeMove(
|
|
|
698
676
|
|
|
699
677
|
if (volatiles.Thrashing) {
|
|
700
678
|
assert(volatiles.attacks > 0);
|
|
701
|
-
assert(from != null);
|
|
702
679
|
|
|
703
680
|
if (options.calc.modify(player, .attacking)) |extend| {
|
|
704
681
|
if (!extend) volatiles.attacks = 0;
|
|
@@ -710,7 +687,7 @@ fn beforeMove(
|
|
|
710
687
|
if (!showdown and handleThrashing(battle, active, player, options)) {
|
|
711
688
|
try log.start(battle.active(player), .ConfusionSilent);
|
|
712
689
|
}
|
|
713
|
-
try log.move(ident, side.last_selected_move, battle.active(player.foe())
|
|
690
|
+
try log.move(ident, side.last_selected_move, battle.active(player.foe()));
|
|
714
691
|
if (showdown) {
|
|
715
692
|
// This shouldn't actually set last_used_move, but Pokémon Showdown sets last
|
|
716
693
|
// used in useMove and doesn't have the notion of skipping canMove semantics
|
|
@@ -732,12 +709,7 @@ fn beforeMove(
|
|
|
732
709
|
}
|
|
733
710
|
try options.chance.binding(player, volatiles.attacks);
|
|
734
711
|
|
|
735
|
-
try log.move(
|
|
736
|
-
battle.active(player),
|
|
737
|
-
side.last_selected_move,
|
|
738
|
-
battle.active(player.foe()),
|
|
739
|
-
side.last_selected_move,
|
|
740
|
-
);
|
|
712
|
+
try log.move(battle.active(player), side.last_selected_move, battle.active(player.foe()));
|
|
741
713
|
if (showdown or battle.last_damage != 0) {
|
|
742
714
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
743
715
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -757,7 +729,7 @@ fn canMove(
|
|
|
757
729
|
mslot: u4,
|
|
758
730
|
auto: bool,
|
|
759
731
|
skip_pp: bool,
|
|
760
|
-
from:
|
|
732
|
+
from: Move,
|
|
761
733
|
residual: *bool,
|
|
762
734
|
options: anytype,
|
|
763
735
|
) !bool {
|
|
@@ -765,33 +737,22 @@ fn canMove(
|
|
|
765
737
|
const player_ident = battle.active(player);
|
|
766
738
|
const move = Move.get(side.last_selected_move);
|
|
767
739
|
|
|
768
|
-
const foe_last = battle.foe(player).last_used_move;
|
|
769
|
-
const special = from != null and (from.? == .Metronome and
|
|
770
|
-
(from.? == .MirrorMove and !(foe_last == .None or foe_last == .MirrorMove)));
|
|
771
|
-
|
|
772
740
|
var skip = skip_pp;
|
|
773
741
|
if (side.active.volatiles.Charging) {
|
|
774
742
|
side.active.volatiles.Charging = false;
|
|
775
743
|
side.active.volatiles.Invulnerable = false;
|
|
776
744
|
} else if (move.effect == .Charge) {
|
|
777
|
-
try options.log.
|
|
745
|
+
try options.log.moveFrom(player_ident, side.last_selected_move, .{}, from);
|
|
778
746
|
setCounterable(battle, player, side, move);
|
|
779
747
|
try Effects.charge(battle, player, options);
|
|
780
748
|
return false;
|
|
781
749
|
}
|
|
782
750
|
|
|
783
|
-
|
|
751
|
+
side.last_used_move = side.last_selected_move;
|
|
784
752
|
if (!skip) decrementPP(side, mslot, auto);
|
|
785
753
|
|
|
786
754
|
const target = if (move.target == .Self) player else player.foe();
|
|
787
|
-
|
|
788
|
-
// don't have space to be able to track it in all circumstances and thus usually try to infer it
|
|
789
|
-
// from side.last_used_move. However, this gets reset by the opponent switching or fainting,
|
|
790
|
-
// meaning sometimes we lose this information. from is still set because it not being null is
|
|
791
|
-
// relevant for control flow, but we don't have anything useful to put in the log. This is
|
|
792
|
-
// regrettable, but this information is more "nice to have" than required
|
|
793
|
-
const f = if (from != null and from.? == .None) null else from;
|
|
794
|
-
try options.log.move(player_ident, side.last_selected_move, battle.active(target), f);
|
|
755
|
+
try options.log.moveFrom(player_ident, side.last_selected_move, battle.active(target), from);
|
|
795
756
|
setCounterable(battle, player, side, move);
|
|
796
757
|
|
|
797
758
|
if (move.effect.onBegin()) {
|
|
@@ -829,12 +790,14 @@ fn decrementPP(side: *Side, mslot: u4, auto: bool) void {
|
|
|
829
790
|
assert(!volatiles.Rage and !volatiles.Thrashing and !volatiles.MultiHit);
|
|
830
791
|
if (volatiles.Bide) return;
|
|
831
792
|
|
|
832
|
-
|
|
833
|
-
|
|
793
|
+
var move_slot = active.move(mslot);
|
|
794
|
+
assert(move_slot.pp > 0 or auto);
|
|
795
|
+
move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
|
|
834
796
|
if (volatiles.Transform) return;
|
|
835
797
|
|
|
836
|
-
|
|
837
|
-
|
|
798
|
+
move_slot = side.stored().move(mslot);
|
|
799
|
+
assert(move_slot.pp > 0 or auto);
|
|
800
|
+
move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
|
|
838
801
|
assert(active.move(mslot).pp == side.stored().move(mslot).pp);
|
|
839
802
|
}
|
|
840
803
|
|
|
@@ -1332,7 +1295,7 @@ fn mirrorMove(
|
|
|
1332
1295
|
auto: bool,
|
|
1333
1296
|
residual: *bool,
|
|
1334
1297
|
options: anytype,
|
|
1335
|
-
) !?Result {
|
|
1298
|
+
) anyerror!?Result {
|
|
1336
1299
|
var side = battle.side(player);
|
|
1337
1300
|
const foe = battle.foe(player);
|
|
1338
1301
|
|
|
@@ -1359,7 +1322,7 @@ fn metronome(
|
|
|
1359
1322
|
auto: bool,
|
|
1360
1323
|
residual: *bool,
|
|
1361
1324
|
options: anytype,
|
|
1362
|
-
) !?Result {
|
|
1325
|
+
) anyerror!?Result {
|
|
1363
1326
|
var side = battle.side(player);
|
|
1364
1327
|
|
|
1365
1328
|
side.last_selected_move = try Rolls.metronome(battle, player, options);
|
|
@@ -1438,7 +1401,7 @@ fn moveHit(
|
|
|
1438
1401
|
const overwritten = overwrite and state > 0;
|
|
1439
1402
|
assert(!overwritten or (0 < state and state <= 255 and !side.active.volatiles.Bide));
|
|
1440
1403
|
|
|
1441
|
-
var accuracy = if (overwritten) state else @as(u16,
|
|
1404
|
+
var accuracy = if (overwritten) state else @as(u16, move.accuracy);
|
|
1442
1405
|
var boost = BOOSTS[@as(u4, @intCast(@as(i8, side.active.boosts.accuracy) + 6))];
|
|
1443
1406
|
accuracy = accuracy * boost[0] / boost[1];
|
|
1444
1407
|
boost = BOOSTS[@as(u4, @intCast(@as(i8, -foe.active.boosts.evasion) + 6))];
|
|
@@ -1477,9 +1440,8 @@ fn checkFaint(
|
|
|
1477
1440
|
const foe_fainted = foe.stored().hp == 0;
|
|
1478
1441
|
|
|
1479
1442
|
const player_out = findFirstAlive(side) == 0;
|
|
1480
|
-
const foe_out = findFirstAlive(foe) == 0;
|
|
1481
|
-
const
|
|
1482
|
-
const more = tie or player_out or foe_out;
|
|
1443
|
+
const foe_out = foe_fainted and findFirstAlive(foe) == 0;
|
|
1444
|
+
const more = player_out or foe_out;
|
|
1483
1445
|
|
|
1484
1446
|
if (try faint(battle, player, !(more or foe_fainted), options)) |r| return r;
|
|
1485
1447
|
if (foe_fainted) if (try faint(battle, player.foe(), !more, options)) |r| return r;
|
|
@@ -1487,7 +1449,7 @@ fn checkFaint(
|
|
|
1487
1449
|
assert(!side.active.volatiles.MultiHit);
|
|
1488
1450
|
assert(!foe.active.volatiles.MultiHit);
|
|
1489
1451
|
|
|
1490
|
-
if (
|
|
1452
|
+
if (player_out and foe_out) {
|
|
1491
1453
|
try options.log.tie();
|
|
1492
1454
|
return Result.Tie;
|
|
1493
1455
|
} else if (player_out) {
|
|
@@ -1499,8 +1461,8 @@ fn checkFaint(
|
|
|
1499
1461
|
}
|
|
1500
1462
|
|
|
1501
1463
|
const foe_choice: Choice.Type = if (foe_fainted) .Switch else .Pass;
|
|
1502
|
-
if (player == .P1) return
|
|
1503
|
-
return
|
|
1464
|
+
if (player == .P1) return .{ .p1 = .Switch, .p2 = foe_choice };
|
|
1465
|
+
return .{ .p1 = foe_choice, .p2 = .Switch };
|
|
1504
1466
|
}
|
|
1505
1467
|
|
|
1506
1468
|
fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result {
|
|
@@ -1548,21 +1510,19 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1548
1510
|
var volatiles = &side.active.volatiles;
|
|
1549
1511
|
|
|
1550
1512
|
const brn = Status.is(stored.status, .BRN);
|
|
1551
|
-
if (brn or Status.is(stored.status, .PSN))
|
|
1513
|
+
if (brn or Status.is(stored.status, .PSN)) {
|
|
1552
1514
|
var damage = @max(stored.stats.hp / 16, 1);
|
|
1553
|
-
|
|
1554
1515
|
if (volatiles.Toxic) {
|
|
1555
1516
|
volatiles.toxic += 1;
|
|
1556
1517
|
damage *= volatiles.toxic;
|
|
1557
1518
|
}
|
|
1519
|
+
damage = @min(damage, stored.hp);
|
|
1558
1520
|
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
// Pokémon Showdown uses damageOf here but its not relevant in Generation I
|
|
1565
|
-
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
1521
|
+
if (!showdown or damage > 0) {
|
|
1522
|
+
stored.hp -= damage;
|
|
1523
|
+
// Pokémon Showdown uses damageOf here but its not relevant in Generation I
|
|
1524
|
+
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
1525
|
+
}
|
|
1566
1526
|
}
|
|
1567
1527
|
|
|
1568
1528
|
if (volatiles.LeechSeed) {
|
|
@@ -1675,9 +1635,9 @@ fn checkEBC(battle: anytype) bool {
|
|
|
1675
1635
|
}
|
|
1676
1636
|
|
|
1677
1637
|
fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
1678
|
-
if (side.active.volatiles.
|
|
1638
|
+
if (side.active.volatiles.disable_move != 0) {
|
|
1679
1639
|
// A Pokémon that transforms after being disabled may end up with less move slots
|
|
1680
|
-
const m = side.active.moves[side.active.volatiles.
|
|
1640
|
+
const m = side.active.moves[side.active.volatiles.disable_move - 1].id;
|
|
1681
1641
|
// side.last_selected_move can be Struggle here on Pokemon Showdown we need an extra check
|
|
1682
1642
|
const last = if (showdown and m == .Bide and side.active.volatiles.Bide)
|
|
1683
1643
|
m
|
|
@@ -1692,7 +1652,7 @@ fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
|
1692
1652
|
return false;
|
|
1693
1653
|
}
|
|
1694
1654
|
|
|
1695
|
-
|
|
1655
|
+
fn buildRage(battle: anytype, who: Player, options: anytype) !void {
|
|
1696
1656
|
const side = battle.side(who);
|
|
1697
1657
|
if (side.active.volatiles.Rage and side.active.boosts.atk < 6) {
|
|
1698
1658
|
try Effects.boost(battle, who, Move.get(.Rage), options);
|
|
@@ -1710,7 +1670,7 @@ fn handleThrashing(battle: anytype, active: *ActivePokemon, player: Player, opti
|
|
|
1710
1670
|
return true;
|
|
1711
1671
|
}
|
|
1712
1672
|
|
|
1713
|
-
|
|
1673
|
+
fn onBegin(
|
|
1714
1674
|
battle: anytype,
|
|
1715
1675
|
player: Player,
|
|
1716
1676
|
move: Move.Data,
|
|
@@ -1740,7 +1700,7 @@ inline fn onBegin(
|
|
|
1740
1700
|
};
|
|
1741
1701
|
}
|
|
1742
1702
|
|
|
1743
|
-
|
|
1703
|
+
fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1744
1704
|
assert(move.effect.onEnd());
|
|
1745
1705
|
return switch (move.effect) {
|
|
1746
1706
|
.Bide => Effects.bide(battle, player, options),
|
|
@@ -1756,7 +1716,7 @@ inline fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anyty
|
|
|
1756
1716
|
};
|
|
1757
1717
|
}
|
|
1758
1718
|
|
|
1759
|
-
|
|
1719
|
+
fn alwaysHappens(
|
|
1760
1720
|
battle: anytype,
|
|
1761
1721
|
player: Player,
|
|
1762
1722
|
move: Move.Data,
|
|
@@ -1775,7 +1735,7 @@ inline fn alwaysHappens(
|
|
|
1775
1735
|
};
|
|
1776
1736
|
}
|
|
1777
1737
|
|
|
1778
|
-
|
|
1738
|
+
fn moveEffect(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1779
1739
|
return switch (move.effect) {
|
|
1780
1740
|
.BurnChance1, .BurnChance2 => Effects.burnChance(battle, player, move, options),
|
|
1781
1741
|
.ConfusionChance => Effects.confusion(battle, player, move, options),
|
|
@@ -1905,7 +1865,7 @@ pub const Effects = struct {
|
|
|
1905
1865
|
try options.chance.commit(player, .hit);
|
|
1906
1866
|
}
|
|
1907
1867
|
|
|
1908
|
-
if (volatiles.
|
|
1868
|
+
if (volatiles.disable_move != 0) {
|
|
1909
1869
|
try options.log.fail(foe_ident, .None);
|
|
1910
1870
|
return null;
|
|
1911
1871
|
}
|
|
@@ -1930,18 +1890,17 @@ pub const Effects = struct {
|
|
|
1930
1890
|
return Result.Error;
|
|
1931
1891
|
}
|
|
1932
1892
|
|
|
1933
|
-
volatiles.
|
|
1893
|
+
volatiles.disable_move =
|
|
1934
1894
|
@intCast(try Rolls.moveSlot(battle, player, &foe.active.moves, n, options));
|
|
1935
|
-
volatiles.
|
|
1895
|
+
volatiles.disable_duration = Rolls.disableDuration(battle, player, options);
|
|
1936
1896
|
|
|
1937
|
-
const id = foe.active.move(volatiles.
|
|
1897
|
+
const id = foe.active.move(volatiles.disable_move).id;
|
|
1938
1898
|
try options.log.startEffect(foe_ident, .Disable, id);
|
|
1939
1899
|
return null;
|
|
1940
1900
|
}
|
|
1941
1901
|
|
|
1942
1902
|
fn drainHP(battle: anytype, player: Player, options: anytype) !void {
|
|
1943
|
-
var
|
|
1944
|
-
var stored = side.stored();
|
|
1903
|
+
var stored = battle.side(player).stored();
|
|
1945
1904
|
|
|
1946
1905
|
if (battle.last_damage == 0) {
|
|
1947
1906
|
assert(showdown);
|
|
@@ -2066,7 +2025,7 @@ pub const Effects = struct {
|
|
|
2066
2025
|
// GLITCH: HP recovery move failure glitches
|
|
2067
2026
|
const delta = stored.stats.hp - stored.hp;
|
|
2068
2027
|
if (delta == 0 or (delta & 255 == 255 and stored.hp % 256 != 0)) {
|
|
2069
|
-
return
|
|
2028
|
+
return options.log.fail(ident, .None);
|
|
2070
2029
|
}
|
|
2071
2030
|
|
|
2072
2031
|
const rest = side.last_selected_move == .Rest;
|
|
@@ -2148,7 +2107,7 @@ pub const Effects = struct {
|
|
|
2148
2107
|
// Invulnerable foes or 1/256 miss can trigger |-miss| instead of |-fail|
|
|
2149
2108
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2150
2109
|
try options.chance.commit(player, .hit);
|
|
2151
|
-
return
|
|
2110
|
+
return options.log.fail(battle.active(player.foe()), .None);
|
|
2152
2111
|
}
|
|
2153
2112
|
}
|
|
2154
2113
|
if (!try checkHit(battle, player, move, options)) return;
|
|
@@ -2366,8 +2325,7 @@ pub const Effects = struct {
|
|
|
2366
2325
|
fn substitute(battle: anytype, player: Player, residual: *bool, options: anytype) !void {
|
|
2367
2326
|
var side = battle.side(player);
|
|
2368
2327
|
if (side.active.volatiles.Substitute) {
|
|
2369
|
-
|
|
2370
|
-
return;
|
|
2328
|
+
return options.log.fail(battle.active(player), .Substitute);
|
|
2371
2329
|
}
|
|
2372
2330
|
|
|
2373
2331
|
assert(side.stored().stats.hp <= 1023);
|
|
@@ -2378,10 +2336,7 @@ pub const Effects = struct {
|
|
|
2378
2336
|
// the target's HP is exactly divisible by 4 (here we're using an inlined divCeil routine to
|
|
2379
2337
|
// avoid having to convert to floating point)
|
|
2380
2338
|
const required_hp = if (showdown) @divFloor(side.stored().stats.hp - 1, 4) + 1 else hp;
|
|
2381
|
-
if (side.stored().hp < required_hp)
|
|
2382
|
-
try options.log.fail(battle.active(player), .Weak);
|
|
2383
|
-
return;
|
|
2384
|
-
}
|
|
2339
|
+
if (side.stored().hp < required_hp) return options.log.fail(battle.active(player), .Weak);
|
|
2385
2340
|
|
|
2386
2341
|
// GLITCH: can leave the user with 0 HP (faints later) because didn't check '<=' above
|
|
2387
2342
|
side.stored().hp -= hp;
|
|
@@ -2395,12 +2350,17 @@ pub const Effects = struct {
|
|
|
2395
2350
|
}
|
|
2396
2351
|
|
|
2397
2352
|
fn switchAndTeleport(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
2398
|
-
if (!showdown or battle.side(player).last_selected_move == .Teleport) return;
|
|
2399
|
-
|
|
2400
2353
|
// Whirlwind/Roar should not roll to hit/reset damage but Pokémon Showdown does anyway
|
|
2401
|
-
if (
|
|
2402
|
-
|
|
2403
|
-
|
|
2354
|
+
if (showdown) {
|
|
2355
|
+
if (battle.side(player).last_selected_move == .Teleport) return;
|
|
2356
|
+
if (try checkHit(battle, player, move, options)) {
|
|
2357
|
+
try options.chance.commit(player, .hit);
|
|
2358
|
+
}
|
|
2359
|
+
battle.last_damage = 0;
|
|
2360
|
+
} else {
|
|
2361
|
+
try options.log.fail(battle.active(player), .None);
|
|
2362
|
+
try options.log.laststill();
|
|
2363
|
+
}
|
|
2404
2364
|
}
|
|
2405
2365
|
|
|
2406
2366
|
fn thrashing(battle: anytype, player: Player, options: anytype) void {
|
|
@@ -2468,7 +2428,7 @@ pub const Effects = struct {
|
|
|
2468
2428
|
switch (move.effect) {
|
|
2469
2429
|
.AttackUp1, .AttackUp2, .Rage => {
|
|
2470
2430
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2471
|
-
if (boosts.atk == 6) return
|
|
2431
|
+
if (boosts.atk == 6) return log.fail(ident, .None);
|
|
2472
2432
|
const n: u2 = if (move.effect == .AttackUp2) 2 else 1;
|
|
2473
2433
|
boosts.atk = @as(i4, @intCast(@min(6, @as(i8, boosts.atk) + n)));
|
|
2474
2434
|
const reason = if (move.effect == .Rage) Boost.Rage else Boost.Attack;
|
|
@@ -2479,7 +2439,7 @@ pub const Effects = struct {
|
|
|
2479
2439
|
try log.boost(ident, Boost.Attack, -1);
|
|
2480
2440
|
if (move.effect == .Rage) return;
|
|
2481
2441
|
}
|
|
2482
|
-
return
|
|
2442
|
+
return log.fail(ident, .None);
|
|
2483
2443
|
}
|
|
2484
2444
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2485
2445
|
const stat = unmodifiedStats(battle, side).atk;
|
|
@@ -2490,7 +2450,7 @@ pub const Effects = struct {
|
|
|
2490
2450
|
},
|
|
2491
2451
|
.DefenseUp1, .DefenseUp2 => {
|
|
2492
2452
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2493
|
-
if (boosts.def == 6) return
|
|
2453
|
+
if (boosts.def == 6) return log.fail(ident, .None);
|
|
2494
2454
|
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
2495
2455
|
boosts.def = @intCast(@min(6, @as(i8, boosts.def) + n));
|
|
2496
2456
|
if (stats.def == MAX_STAT_VALUE) {
|
|
@@ -2499,7 +2459,7 @@ pub const Effects = struct {
|
|
|
2499
2459
|
try log.boost(ident, .Defense, n);
|
|
2500
2460
|
try log.boost(ident, .Defense, -1);
|
|
2501
2461
|
}
|
|
2502
|
-
return
|
|
2462
|
+
return log.fail(ident, .None);
|
|
2503
2463
|
}
|
|
2504
2464
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2505
2465
|
const stat = unmodifiedStats(battle, side).def;
|
|
@@ -2508,7 +2468,7 @@ pub const Effects = struct {
|
|
|
2508
2468
|
},
|
|
2509
2469
|
.SpeedUp2 => {
|
|
2510
2470
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2511
|
-
if (boosts.spe == 6) return
|
|
2471
|
+
if (boosts.spe == 6) return log.fail(ident, .None);
|
|
2512
2472
|
boosts.spe = @intCast(@min(6, @as(i8, boosts.spe) + 2));
|
|
2513
2473
|
if (stats.spe == MAX_STAT_VALUE) {
|
|
2514
2474
|
boosts.spe -= 1;
|
|
@@ -2516,7 +2476,7 @@ pub const Effects = struct {
|
|
|
2516
2476
|
try log.boost(ident, .Speed, 2);
|
|
2517
2477
|
try log.boost(ident, .Speed, -1);
|
|
2518
2478
|
}
|
|
2519
|
-
return
|
|
2479
|
+
return log.fail(ident, .None);
|
|
2520
2480
|
}
|
|
2521
2481
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2522
2482
|
const stat = unmodifiedStats(battle, side).spe;
|
|
@@ -2525,7 +2485,7 @@ pub const Effects = struct {
|
|
|
2525
2485
|
},
|
|
2526
2486
|
.SpecialUp1, .SpecialUp2 => {
|
|
2527
2487
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2528
|
-
if (boosts.spc == 6) return
|
|
2488
|
+
if (boosts.spc == 6) return log.fail(ident, .None);
|
|
2529
2489
|
const n: u2 = if (move.effect == .SpecialUp2) 2 else 1;
|
|
2530
2490
|
boosts.spc = @intCast(@min(6, @as(i8, boosts.spc) + n));
|
|
2531
2491
|
if (stats.spc == MAX_STAT_VALUE) {
|
|
@@ -2536,7 +2496,7 @@ pub const Effects = struct {
|
|
|
2536
2496
|
try log.boost(ident, .SpecialDefense, n);
|
|
2537
2497
|
try log.boost(ident, .SpecialDefense, -1);
|
|
2538
2498
|
}
|
|
2539
|
-
return
|
|
2499
|
+
return log.fail(ident, .None);
|
|
2540
2500
|
}
|
|
2541
2501
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2542
2502
|
const stat = unmodifiedStats(battle, side).spc;
|
|
@@ -2546,7 +2506,7 @@ pub const Effects = struct {
|
|
|
2546
2506
|
},
|
|
2547
2507
|
.EvasionUp1 => {
|
|
2548
2508
|
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2549
|
-
if (boosts.evasion == 6) return
|
|
2509
|
+
if (boosts.evasion == 6) return log.fail(ident, .None);
|
|
2550
2510
|
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2551
2511
|
try log.boost(ident, .Evasion, 1);
|
|
2552
2512
|
},
|
|
@@ -2589,7 +2549,7 @@ pub const Effects = struct {
|
|
|
2589
2549
|
try log.boost(foe_ident, .Attack, -1);
|
|
2590
2550
|
try log.boost(foe_ident, .Attack, 1);
|
|
2591
2551
|
}
|
|
2592
|
-
return
|
|
2552
|
+
return log.fail(foe_ident, .None);
|
|
2593
2553
|
}
|
|
2594
2554
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2595
2555
|
const stat = unmodifiedStats(battle, foe).atk;
|
|
@@ -2607,7 +2567,7 @@ pub const Effects = struct {
|
|
|
2607
2567
|
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2608
2568
|
try log.boost(foe_ident, .Defense, 1);
|
|
2609
2569
|
}
|
|
2610
|
-
return
|
|
2570
|
+
return log.fail(foe_ident, .None);
|
|
2611
2571
|
}
|
|
2612
2572
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2613
2573
|
const stat = unmodifiedStats(battle, foe).def;
|
|
@@ -2624,7 +2584,7 @@ pub const Effects = struct {
|
|
|
2624
2584
|
try log.boost(foe_ident, .Speed, -1);
|
|
2625
2585
|
try log.boost(foe_ident, .Speed, 1);
|
|
2626
2586
|
}
|
|
2627
|
-
return
|
|
2587
|
+
return log.fail(foe_ident, .None);
|
|
2628
2588
|
}
|
|
2629
2589
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2630
2590
|
const stat = unmodifiedStats(battle, foe).spe;
|
|
@@ -2644,7 +2604,7 @@ pub const Effects = struct {
|
|
|
2644
2604
|
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2645
2605
|
try log.boost(foe_ident, .SpecialDefense, 1);
|
|
2646
2606
|
}
|
|
2647
|
-
return
|
|
2607
|
+
return log.fail(foe_ident, .None);
|
|
2648
2608
|
}
|
|
2649
2609
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2650
2610
|
const stat = unmodifiedStats(battle, foe).spc;
|
|
@@ -2680,7 +2640,7 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
2680
2640
|
}
|
|
2681
2641
|
}
|
|
2682
2642
|
|
|
2683
|
-
|
|
2643
|
+
fn isForced(active: anytype) bool {
|
|
2684
2644
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
2685
2645
|
active.volatiles.Thrashing or active.volatiles.Charging;
|
|
2686
2646
|
}
|
|
@@ -2693,9 +2653,9 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2693
2653
|
|
|
2694
2654
|
options.chance.clearDurations(who, clear);
|
|
2695
2655
|
|
|
2696
|
-
if (volatiles.
|
|
2697
|
-
volatiles.
|
|
2698
|
-
volatiles.
|
|
2656
|
+
if (volatiles.disable_move != 0) {
|
|
2657
|
+
volatiles.disable_move = 0;
|
|
2658
|
+
volatiles.disable_duration = 0;
|
|
2699
2659
|
try log.end(ident, .DisableSilent);
|
|
2700
2660
|
}
|
|
2701
2661
|
if (volatiles.Confusion) {
|
|
@@ -2705,15 +2665,15 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2705
2665
|
}
|
|
2706
2666
|
if (volatiles.Mist) {
|
|
2707
2667
|
volatiles.Mist = false;
|
|
2708
|
-
try log.end(ident, .
|
|
2668
|
+
try log.end(ident, .MistSilent);
|
|
2709
2669
|
}
|
|
2710
2670
|
if (volatiles.FocusEnergy) {
|
|
2711
2671
|
volatiles.FocusEnergy = false;
|
|
2712
|
-
try log.end(ident, .
|
|
2672
|
+
try log.end(ident, .FocusEnergySilent);
|
|
2713
2673
|
}
|
|
2714
2674
|
if (volatiles.LeechSeed) {
|
|
2715
2675
|
volatiles.LeechSeed = false;
|
|
2716
|
-
try log.end(ident, .
|
|
2676
|
+
try log.end(ident, .LeechSeedSilent);
|
|
2717
2677
|
}
|
|
2718
2678
|
if (!showdown and volatiles.Toxic) {
|
|
2719
2679
|
volatiles.Toxic = false;
|
|
@@ -2721,22 +2681,22 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2721
2681
|
}
|
|
2722
2682
|
if (volatiles.LightScreen) {
|
|
2723
2683
|
volatiles.LightScreen = false;
|
|
2724
|
-
try log.end(ident, .
|
|
2684
|
+
try log.end(ident, .LightScreenSilent);
|
|
2725
2685
|
}
|
|
2726
2686
|
if (volatiles.Reflect) {
|
|
2727
2687
|
volatiles.Reflect = false;
|
|
2728
|
-
try log.end(ident, .
|
|
2688
|
+
try log.end(ident, .ReflectSilent);
|
|
2729
2689
|
}
|
|
2730
2690
|
if (showdown and volatiles.Toxic) {
|
|
2731
2691
|
volatiles.Toxic = false;
|
|
2732
2692
|
// Pokémon Showdown erroneously clears the toxic counter
|
|
2733
2693
|
volatiles.toxic = 0;
|
|
2734
|
-
try log.end(ident, .
|
|
2694
|
+
try log.end(ident, .ToxicSilent);
|
|
2735
2695
|
}
|
|
2736
2696
|
}
|
|
2737
2697
|
|
|
2738
2698
|
pub const Rolls = struct {
|
|
2739
|
-
|
|
2699
|
+
fn speedTie(battle: anytype, options: anytype) !bool {
|
|
2740
2700
|
const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
|
|
2741
2701
|
player == .P1
|
|
2742
2702
|
else if (showdown)
|
|
@@ -2748,7 +2708,7 @@ pub const Rolls = struct {
|
|
|
2748
2708
|
return p1;
|
|
2749
2709
|
}
|
|
2750
2710
|
|
|
2751
|
-
|
|
2711
|
+
fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
|
|
2752
2712
|
const crit = if (options.calc.overridden(player, .critical_hit)) |val|
|
|
2753
2713
|
val == .true
|
|
2754
2714
|
else if (showdown)
|
|
@@ -2760,7 +2720,7 @@ pub const Rolls = struct {
|
|
|
2760
2720
|
return crit;
|
|
2761
2721
|
}
|
|
2762
2722
|
|
|
2763
|
-
|
|
2723
|
+
fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2764
2724
|
const roll = if (options.calc.overridden(player, .damage)) |val|
|
|
2765
2725
|
val
|
|
2766
2726
|
else roll: {
|
|
@@ -2771,11 +2731,12 @@ pub const Rolls = struct {
|
|
|
2771
2731
|
}
|
|
2772
2732
|
};
|
|
2773
2733
|
|
|
2734
|
+
assert(roll >= 217 and roll <= 255);
|
|
2774
2735
|
try options.chance.damage(player, roll);
|
|
2775
2736
|
return roll;
|
|
2776
2737
|
}
|
|
2777
2738
|
|
|
2778
|
-
|
|
2739
|
+
fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) bool {
|
|
2779
2740
|
const ok = if (options.calc.overridden(player, .hit)) |val|
|
|
2780
2741
|
val == .true
|
|
2781
2742
|
else if (showdown)
|
|
@@ -2787,7 +2748,7 @@ pub const Rolls = struct {
|
|
|
2787
2748
|
return ok;
|
|
2788
2749
|
}
|
|
2789
2750
|
|
|
2790
|
-
|
|
2751
|
+
fn confused(battle: anytype, player: Player, options: anytype) !bool {
|
|
2791
2752
|
const cfz = if (options.calc.overridden(player, .confused)) |val|
|
|
2792
2753
|
val == .true
|
|
2793
2754
|
else if (showdown)
|
|
@@ -2799,7 +2760,7 @@ pub const Rolls = struct {
|
|
|
2799
2760
|
return cfz;
|
|
2800
2761
|
}
|
|
2801
2762
|
|
|
2802
|
-
|
|
2763
|
+
fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
|
|
2803
2764
|
const par = if (options.calc.overridden(player, .paralyzed)) |val|
|
|
2804
2765
|
val == .true
|
|
2805
2766
|
else if (showdown)
|
|
@@ -2811,7 +2772,7 @@ pub const Rolls = struct {
|
|
|
2811
2772
|
return par;
|
|
2812
2773
|
}
|
|
2813
2774
|
|
|
2814
|
-
|
|
2775
|
+
fn confusionChance(battle: anytype, player: Player, options: anytype) !bool {
|
|
2815
2776
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2816
2777
|
val == .true
|
|
2817
2778
|
else if (showdown)
|
|
@@ -2823,7 +2784,7 @@ pub const Rolls = struct {
|
|
|
2823
2784
|
return proc;
|
|
2824
2785
|
}
|
|
2825
2786
|
|
|
2826
|
-
|
|
2787
|
+
fn secondaryChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2827
2788
|
const rate: u8 = if (low) 26 else 77;
|
|
2828
2789
|
|
|
2829
2790
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
@@ -2837,7 +2798,7 @@ pub const Rolls = struct {
|
|
|
2837
2798
|
return proc;
|
|
2838
2799
|
}
|
|
2839
2800
|
|
|
2840
|
-
|
|
2801
|
+
fn poisonChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2841
2802
|
const rate: u8 = if (low) 52 else 103;
|
|
2842
2803
|
|
|
2843
2804
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
@@ -2851,7 +2812,7 @@ pub const Rolls = struct {
|
|
|
2851
2812
|
return proc;
|
|
2852
2813
|
}
|
|
2853
2814
|
|
|
2854
|
-
|
|
2815
|
+
fn unboost(battle: anytype, player: Player, options: anytype) !bool {
|
|
2855
2816
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2856
2817
|
val == .true
|
|
2857
2818
|
else if (showdown)
|
|
@@ -2863,14 +2824,12 @@ pub const Rolls = struct {
|
|
|
2863
2824
|
return proc;
|
|
2864
2825
|
}
|
|
2865
2826
|
|
|
2866
|
-
|
|
2827
|
+
fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
2867
2828
|
const move: Move = if (options.calc.overridden(player, .metronome)) |val|
|
|
2868
2829
|
val
|
|
2869
|
-
else if (showdown)
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
break :move @enumFromInt(r + mod);
|
|
2873
|
-
} else move: {
|
|
2830
|
+
else if (showdown)
|
|
2831
|
+
Move.METRONOME[battle.rng.range(u8, 0, Move.METRONOME.len)]
|
|
2832
|
+
else move: {
|
|
2874
2833
|
while (true) {
|
|
2875
2834
|
const r = battle.rng.next();
|
|
2876
2835
|
if (r == 0 or r == @intFromEnum(Move.Metronome)) continue;
|
|
@@ -2883,7 +2842,7 @@ pub const Rolls = struct {
|
|
|
2883
2842
|
return move;
|
|
2884
2843
|
}
|
|
2885
2844
|
|
|
2886
|
-
|
|
2845
|
+
fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
|
|
2887
2846
|
const power = if (options.calc.overridden(player, .psywave)) |val|
|
|
2888
2847
|
val - 1
|
|
2889
2848
|
else if (showdown)
|
|
@@ -2895,15 +2854,16 @@ pub const Rolls = struct {
|
|
|
2895
2854
|
}
|
|
2896
2855
|
};
|
|
2897
2856
|
|
|
2857
|
+
assert(power < max);
|
|
2898
2858
|
try options.chance.psywave(player, power, max);
|
|
2899
2859
|
return power;
|
|
2900
2860
|
}
|
|
2901
2861
|
|
|
2902
|
-
|
|
2862
|
+
fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2903
2863
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2904
2864
|
@intCast(val)
|
|
2905
2865
|
else if (showdown)
|
|
2906
|
-
|
|
2866
|
+
battle.rng.range(u3, 1, 8)
|
|
2907
2867
|
else duration: {
|
|
2908
2868
|
while (true) {
|
|
2909
2869
|
const r = battle.rng.next() & 7;
|
|
@@ -2911,47 +2871,53 @@ pub const Rolls = struct {
|
|
|
2911
2871
|
}
|
|
2912
2872
|
};
|
|
2913
2873
|
|
|
2874
|
+
assert(duration >= 1 and duration <= 7);
|
|
2914
2875
|
options.chance.duration(.sleep, player, player.foe(), duration);
|
|
2915
2876
|
return duration;
|
|
2916
2877
|
}
|
|
2917
2878
|
|
|
2918
|
-
|
|
2879
|
+
fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
|
|
2919
2880
|
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2920
2881
|
val
|
|
2921
2882
|
else if (showdown)
|
|
2922
|
-
|
|
2883
|
+
battle.rng.range(u4, 1, 9)
|
|
2923
2884
|
else
|
|
2924
2885
|
@intCast((battle.rng.next() & 7) + 1);
|
|
2925
2886
|
|
|
2887
|
+
assert(duration >= 1 and duration <= 8);
|
|
2926
2888
|
options.chance.duration(.disable, player, player.foe(), duration);
|
|
2927
2889
|
return duration;
|
|
2928
2890
|
}
|
|
2929
2891
|
|
|
2930
|
-
|
|
2892
|
+
fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
|
|
2931
2893
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2932
2894
|
@intCast(val)
|
|
2933
2895
|
else if (showdown)
|
|
2934
|
-
|
|
2896
|
+
battle.rng.range(u3, 2, 6)
|
|
2935
2897
|
else
|
|
2936
2898
|
@intCast((battle.rng.next() & 3) + 2);
|
|
2937
2899
|
|
|
2900
|
+
assert(duration >= 2 and duration <= 5);
|
|
2938
2901
|
options.chance.duration(.confusion, player, if (self) player else player.foe(), duration);
|
|
2939
2902
|
return duration;
|
|
2940
2903
|
}
|
|
2941
2904
|
|
|
2942
|
-
|
|
2905
|
+
fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2943
2906
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2944
2907
|
@intCast(val)
|
|
2945
2908
|
else if (showdown)
|
|
2946
|
-
|
|
2909
|
+
battle.rng.range(u3, 2, 4)
|
|
2947
2910
|
else
|
|
2948
2911
|
@intCast((battle.rng.next() & 1) + 2);
|
|
2949
2912
|
|
|
2913
|
+
assert(duration >= 2 and duration <= 3);
|
|
2950
2914
|
options.chance.duration(.attacking, player, player, duration);
|
|
2951
2915
|
return duration;
|
|
2952
2916
|
}
|
|
2953
2917
|
|
|
2954
|
-
|
|
2918
|
+
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
2919
|
+
|
|
2920
|
+
fn distribution(
|
|
2955
2921
|
battle: anytype,
|
|
2956
2922
|
comptime effect: Move.Effect,
|
|
2957
2923
|
player: Player,
|
|
@@ -2961,12 +2927,13 @@ pub const Rolls = struct {
|
|
|
2961
2927
|
const n: u3 = if (options.calc.overridden(player, roll)) |val|
|
|
2962
2928
|
@intCast(val)
|
|
2963
2929
|
else if (showdown)
|
|
2964
|
-
DISTRIBUTION[battle.rng.range(
|
|
2930
|
+
DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
|
|
2965
2931
|
else n: {
|
|
2966
2932
|
const r = (battle.rng.next() & 3);
|
|
2967
2933
|
break :n @intCast((if (r < 2) r else battle.rng.next() & 3) + 2);
|
|
2968
2934
|
};
|
|
2969
2935
|
|
|
2936
|
+
assert(n >= 2 and n <= 5);
|
|
2970
2937
|
if (effect == .Binding) {
|
|
2971
2938
|
options.chance.duration(.binding, player, player, n);
|
|
2972
2939
|
} else {
|
|
@@ -3007,10 +2974,9 @@ pub const Rolls = struct {
|
|
|
3007
2974
|
while (i < moves.len and r > 0) : (i += 1) {
|
|
3008
2975
|
if (moves[i].pp > 0) {
|
|
3009
2976
|
r -= 1;
|
|
3010
|
-
if (r == 0) break;
|
|
2977
|
+
if (r == 0) break :slot @intCast(i + 1);
|
|
3011
2978
|
}
|
|
3012
2979
|
}
|
|
3013
|
-
break :slot @intCast(i + 1);
|
|
3014
2980
|
}
|
|
3015
2981
|
}
|
|
3016
2982
|
|
|
@@ -3032,11 +2998,11 @@ test "RNG agreement" {
|
|
|
3032
2998
|
expected[i] = @intCast(i * 0x1000000);
|
|
3033
2999
|
}
|
|
3034
3000
|
|
|
3035
|
-
var spe
|
|
3036
|
-
var cfz
|
|
3037
|
-
var par
|
|
3038
|
-
var brn
|
|
3039
|
-
var eff
|
|
3001
|
+
var spe: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3002
|
+
var cfz: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3003
|
+
var par: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3004
|
+
var brn: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3005
|
+
var eff: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3040
3006
|
|
|
3041
3007
|
for (0..expected.len) |i| {
|
|
3042
3008
|
try expectEqual(spe.range(u8, 0, 2) == 0, i < Gen12.percent(50) + 1);
|
|
@@ -3072,7 +3038,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3072
3038
|
const side = battle.side(player);
|
|
3073
3039
|
const foe = battle.foe(player);
|
|
3074
3040
|
|
|
3075
|
-
var active = side.active;
|
|
3041
|
+
var active = &side.active;
|
|
3076
3042
|
const stored = side.stored();
|
|
3077
3043
|
|
|
3078
3044
|
// While players are not given any input options on the cartridge in these cases,
|
|
@@ -3126,7 +3092,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3126
3092
|
if (m.id == if (active.volatiles.Bide) .Bide else side.last_selected_move) {
|
|
3127
3093
|
// Pokémon Showdown displays Struggle if limited to Bide but unable to pick
|
|
3128
3094
|
const struggle =
|
|
3129
|
-
m.id == .Bide and (m.pp == 0 or active.volatiles.
|
|
3095
|
+
m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
|
|
3130
3096
|
const s = if (struggle) 0 else slot;
|
|
3131
3097
|
out[n] = .{ .type = .Move, .data = s };
|
|
3132
3098
|
n += 1;
|
|
@@ -3141,7 +3107,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3141
3107
|
const m = active.moves[slot - 1];
|
|
3142
3108
|
if (m.id == .None) break;
|
|
3143
3109
|
if (m.pp == 0) continue;
|
|
3144
|
-
if (active.volatiles.
|
|
3110
|
+
if (active.volatiles.disable_move == slot) continue;
|
|
3145
3111
|
out[n] = .{ .type = .Move, .data = slot };
|
|
3146
3112
|
n += 1;
|
|
3147
3113
|
}
|