@pkmn/engine 0.1.0-dev.d3e796d5 → 0.1.0-dev.d56bdadd
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/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.zig +21 -15
- package/package.json +26 -14
- 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 +500 -56
- package/src/lib/common/rational.zig +5 -5
- package/src/lib/common/rng.zig +15 -6
- package/src/lib/gen1/README.md +31 -29
- package/src/lib/gen1/calc.zig +4 -4
- package/src/lib/gen1/chance.zig +55 -39
- package/src/lib/gen1/data/moves.zig +192 -180
- package/src/lib/gen1/data/species.zig +2 -2
- package/src/lib/gen1/data/types.zig +7 -7
- package/src/lib/gen1/data.zig +31 -43
- package/src/lib/gen1/helpers.zig +26 -41
- package/src/lib/gen1/mechanics.zig +131 -169
- package/src/lib/gen2/README.md +18 -72
- package/src/lib/gen2/calc.zig +143 -0
- package/src/lib/gen2/chance.zig +1314 -0
- package/src/lib/gen2/data/items.zig +12 -18
- package/src/lib/gen2/data/moves.zig +649 -568
- package/src/lib/gen2/data/species.zig +1 -1
- package/src/lib/gen2/data/types.zig +134 -31
- package/src/lib/gen2/data.zig +70 -67
- package/src/lib/gen2/generated.zig +808 -0
- package/src/lib/gen2/helpers.zig +4 -9
- package/src/lib/gen2/mechanics.zig +3113 -100
|
@@ -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,7 +195,7 @@ 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
|
};
|
|
@@ -229,7 +214,7 @@ fn saveMove(battle: anytype, player: Player, choice: ?Choice) void {
|
|
|
229
214
|
if (c.data == 0) {
|
|
230
215
|
side.last_selected_move = .Struggle;
|
|
231
216
|
} else {
|
|
232
|
-
assert(showdown or side.active.volatiles.
|
|
217
|
+
assert(showdown or side.active.volatiles.disable_move != c.data);
|
|
233
218
|
const move = side.active.move(c.data);
|
|
234
219
|
// You cannot *select* a move with 0 PP (except on Pokémon Showdown where that is
|
|
235
220
|
// sometimes required...), but a 0 PP move can be used automatically
|
|
@@ -340,12 +325,10 @@ fn doTurn(
|
|
|
340
325
|
player_choice: Choice,
|
|
341
326
|
player_rewrap: bool,
|
|
342
327
|
player_skip: bool,
|
|
343
|
-
player_from: ?Move,
|
|
344
328
|
foe_player: Player,
|
|
345
329
|
foe_choice: Choice,
|
|
346
330
|
foe_rewrap: bool,
|
|
347
331
|
foe_skip: bool,
|
|
348
|
-
foe_from: ?Move,
|
|
349
332
|
options: anytype,
|
|
350
333
|
) !?Result {
|
|
351
334
|
assert(player_choice.type != .Pass);
|
|
@@ -358,7 +341,6 @@ fn doTurn(
|
|
|
358
341
|
player_choice,
|
|
359
342
|
player_rewrap,
|
|
360
343
|
player_skip,
|
|
361
|
-
player_from,
|
|
362
344
|
&residual,
|
|
363
345
|
options,
|
|
364
346
|
)) |r| return r;
|
|
@@ -381,7 +363,6 @@ fn doTurn(
|
|
|
381
363
|
foe_choice,
|
|
382
364
|
foe_rewrap,
|
|
383
365
|
foe_skip,
|
|
384
|
-
foe_from,
|
|
385
366
|
&residual,
|
|
386
367
|
options,
|
|
387
368
|
)) |r| return r;
|
|
@@ -409,7 +390,6 @@ fn executeMove(
|
|
|
409
390
|
choice: Choice,
|
|
410
391
|
rewrap: bool,
|
|
411
392
|
skip: bool,
|
|
412
|
-
from: ?Move,
|
|
413
393
|
residual: *bool,
|
|
414
394
|
options: anytype,
|
|
415
395
|
) !?Result {
|
|
@@ -444,8 +424,10 @@ fn executeMove(
|
|
|
444
424
|
mslot = @intCast(battle.lastMove(player).index);
|
|
445
425
|
const stored = side.stored();
|
|
446
426
|
// GLITCH: Struggle bypass PP underflow via Hyper Beam / Trapping-switch auto selection
|
|
447
|
-
auto = side.
|
|
448
|
-
|
|
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);
|
|
449
431
|
// If it wasn't Hyper Beam or the continuation of a move effect then we must have just
|
|
450
432
|
// thawed, in which case we will desync unless the last_selected_move happened to be at
|
|
451
433
|
// index 1 and the current Pokémon has the same move in its first slot.
|
|
@@ -467,7 +449,7 @@ fn executeMove(
|
|
|
467
449
|
|
|
468
450
|
var skip_can = false;
|
|
469
451
|
var skip_pp = false;
|
|
470
|
-
switch (try beforeMove(battle, player, skip,
|
|
452
|
+
switch (try beforeMove(battle, player, skip, residual, options)) {
|
|
471
453
|
.done => return null,
|
|
472
454
|
.skip_can => skip_can = true,
|
|
473
455
|
.skip_pp => skip_pp = true,
|
|
@@ -475,9 +457,9 @@ fn executeMove(
|
|
|
475
457
|
.err => return @as(?Result, Result.Error),
|
|
476
458
|
}
|
|
477
459
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
460
|
+
const can = skip_can or
|
|
461
|
+
try canMove(battle, player, mslot, auto, skip_pp, .None, residual, options);
|
|
462
|
+
if (!can) return null;
|
|
481
463
|
|
|
482
464
|
return doMove(battle, player, mslot, rewrap, auto, residual, options);
|
|
483
465
|
}
|
|
@@ -488,7 +470,6 @@ fn beforeMove(
|
|
|
488
470
|
battle: anytype,
|
|
489
471
|
player: Player,
|
|
490
472
|
skip: bool,
|
|
491
|
-
from: ?Move,
|
|
492
473
|
residual: *bool,
|
|
493
474
|
options: anytype,
|
|
494
475
|
) !BeforeMove {
|
|
@@ -549,16 +530,16 @@ fn beforeMove(
|
|
|
549
530
|
return .done;
|
|
550
531
|
}
|
|
551
532
|
|
|
552
|
-
if (volatiles.
|
|
533
|
+
if (volatiles.disable_duration > 0) {
|
|
553
534
|
if (options.calc.modify(player, .disable)) |extend| {
|
|
554
|
-
if (!extend) volatiles.
|
|
535
|
+
if (!extend) volatiles.disable_duration = 0;
|
|
555
536
|
} else {
|
|
556
|
-
volatiles.
|
|
537
|
+
volatiles.disable_duration -= 1;
|
|
557
538
|
}
|
|
558
|
-
try options.chance.disable(player, volatiles.
|
|
539
|
+
try options.chance.disable(player, volatiles.disable_duration);
|
|
559
540
|
|
|
560
|
-
if (volatiles.
|
|
561
|
-
volatiles.
|
|
541
|
+
if (volatiles.disable_duration == 0) {
|
|
542
|
+
volatiles.disable_move = 0;
|
|
562
543
|
try log.end(ident, .Disable);
|
|
563
544
|
}
|
|
564
545
|
}
|
|
@@ -695,7 +676,6 @@ fn beforeMove(
|
|
|
695
676
|
|
|
696
677
|
if (volatiles.Thrashing) {
|
|
697
678
|
assert(volatiles.attacks > 0);
|
|
698
|
-
assert(from != null);
|
|
699
679
|
|
|
700
680
|
if (options.calc.modify(player, .attacking)) |extend| {
|
|
701
681
|
if (!extend) volatiles.attacks = 0;
|
|
@@ -707,7 +687,7 @@ fn beforeMove(
|
|
|
707
687
|
if (!showdown and handleThrashing(battle, active, player, options)) {
|
|
708
688
|
try log.start(battle.active(player), .ConfusionSilent);
|
|
709
689
|
}
|
|
710
|
-
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()));
|
|
711
691
|
if (showdown) {
|
|
712
692
|
// This shouldn't actually set last_used_move, but Pokémon Showdown sets last
|
|
713
693
|
// used in useMove and doesn't have the notion of skipping canMove semantics
|
|
@@ -729,12 +709,7 @@ fn beforeMove(
|
|
|
729
709
|
}
|
|
730
710
|
try options.chance.binding(player, volatiles.attacks);
|
|
731
711
|
|
|
732
|
-
try log.move(
|
|
733
|
-
battle.active(player),
|
|
734
|
-
side.last_selected_move,
|
|
735
|
-
battle.active(player.foe()),
|
|
736
|
-
side.last_selected_move,
|
|
737
|
-
);
|
|
712
|
+
try log.move(battle.active(player), side.last_selected_move, battle.active(player.foe()));
|
|
738
713
|
if (showdown or battle.last_damage != 0) {
|
|
739
714
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
740
715
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -754,7 +729,7 @@ fn canMove(
|
|
|
754
729
|
mslot: u4,
|
|
755
730
|
auto: bool,
|
|
756
731
|
skip_pp: bool,
|
|
757
|
-
from:
|
|
732
|
+
from: Move,
|
|
758
733
|
residual: *bool,
|
|
759
734
|
options: anytype,
|
|
760
735
|
) !bool {
|
|
@@ -762,33 +737,22 @@ fn canMove(
|
|
|
762
737
|
const player_ident = battle.active(player);
|
|
763
738
|
const move = Move.get(side.last_selected_move);
|
|
764
739
|
|
|
765
|
-
const foe_last = battle.foe(player).last_used_move;
|
|
766
|
-
const special = from != null and (from.? == .Metronome and
|
|
767
|
-
(from.? == .MirrorMove and !(foe_last == .None or foe_last == .MirrorMove)));
|
|
768
|
-
|
|
769
740
|
var skip = skip_pp;
|
|
770
741
|
if (side.active.volatiles.Charging) {
|
|
771
742
|
side.active.volatiles.Charging = false;
|
|
772
743
|
side.active.volatiles.Invulnerable = false;
|
|
773
744
|
} else if (move.effect == .Charge) {
|
|
774
|
-
try options.log.
|
|
745
|
+
try options.log.moveFrom(player_ident, side.last_selected_move, .{}, from);
|
|
775
746
|
setCounterable(battle, player, side, move);
|
|
776
747
|
try Effects.charge(battle, player, options);
|
|
777
748
|
return false;
|
|
778
749
|
}
|
|
779
750
|
|
|
780
|
-
|
|
751
|
+
side.last_used_move = side.last_selected_move;
|
|
781
752
|
if (!skip) decrementPP(side, mslot, auto);
|
|
782
753
|
|
|
783
754
|
const target = if (move.target == .Self) player else player.foe();
|
|
784
|
-
|
|
785
|
-
// don't have space to be able to track it in all circumstances and thus usually try to infer it
|
|
786
|
-
// from side.last_used_move. However, this gets reset by the opponent switching or fainting,
|
|
787
|
-
// meaning sometimes we lose this information. from is still set because it not being null is
|
|
788
|
-
// relevant for control flow, but we don't have anything useful to put in the log. This is
|
|
789
|
-
// regrettable, but this information is more "nice to have" than required
|
|
790
|
-
const f = if (from != null and from.? == .None) null else from;
|
|
791
|
-
try options.log.move(player_ident, side.last_selected_move, battle.active(target), f);
|
|
755
|
+
try options.log.moveFrom(player_ident, side.last_selected_move, battle.active(target), from);
|
|
792
756
|
setCounterable(battle, player, side, move);
|
|
793
757
|
|
|
794
758
|
if (move.effect.onBegin()) {
|
|
@@ -826,12 +790,14 @@ fn decrementPP(side: *Side, mslot: u4, auto: bool) void {
|
|
|
826
790
|
assert(!volatiles.Rage and !volatiles.Thrashing and !volatiles.MultiHit);
|
|
827
791
|
if (volatiles.Bide) return;
|
|
828
792
|
|
|
829
|
-
|
|
830
|
-
|
|
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;
|
|
831
796
|
if (volatiles.Transform) return;
|
|
832
797
|
|
|
833
|
-
|
|
834
|
-
|
|
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;
|
|
835
801
|
assert(active.move(mslot).pp == side.stored().move(mslot).pp);
|
|
836
802
|
}
|
|
837
803
|
|
|
@@ -1435,7 +1401,7 @@ fn moveHit(
|
|
|
1435
1401
|
const overwritten = overwrite and state > 0;
|
|
1436
1402
|
assert(!overwritten or (0 < state and state <= 255 and !side.active.volatiles.Bide));
|
|
1437
1403
|
|
|
1438
|
-
var accuracy = if (overwritten) state else @as(u16,
|
|
1404
|
+
var accuracy = if (overwritten) state else @as(u16, move.accuracy);
|
|
1439
1405
|
var boost = BOOSTS[@as(u4, @intCast(@as(i8, side.active.boosts.accuracy) + 6))];
|
|
1440
1406
|
accuracy = accuracy * boost[0] / boost[1];
|
|
1441
1407
|
boost = BOOSTS[@as(u4, @intCast(@as(i8, -foe.active.boosts.evasion) + 6))];
|
|
@@ -1474,9 +1440,8 @@ fn checkFaint(
|
|
|
1474
1440
|
const foe_fainted = foe.stored().hp == 0;
|
|
1475
1441
|
|
|
1476
1442
|
const player_out = findFirstAlive(side) == 0;
|
|
1477
|
-
const foe_out = findFirstAlive(foe) == 0;
|
|
1478
|
-
const
|
|
1479
|
-
const more = tie or player_out or foe_out;
|
|
1443
|
+
const foe_out = foe_fainted and findFirstAlive(foe) == 0;
|
|
1444
|
+
const more = player_out or foe_out;
|
|
1480
1445
|
|
|
1481
1446
|
if (try faint(battle, player, !(more or foe_fainted), options)) |r| return r;
|
|
1482
1447
|
if (foe_fainted) if (try faint(battle, player.foe(), !more, options)) |r| return r;
|
|
@@ -1484,7 +1449,7 @@ fn checkFaint(
|
|
|
1484
1449
|
assert(!side.active.volatiles.MultiHit);
|
|
1485
1450
|
assert(!foe.active.volatiles.MultiHit);
|
|
1486
1451
|
|
|
1487
|
-
if (
|
|
1452
|
+
if (player_out and foe_out) {
|
|
1488
1453
|
try options.log.tie();
|
|
1489
1454
|
return Result.Tie;
|
|
1490
1455
|
} else if (player_out) {
|
|
@@ -1496,8 +1461,8 @@ fn checkFaint(
|
|
|
1496
1461
|
}
|
|
1497
1462
|
|
|
1498
1463
|
const foe_choice: Choice.Type = if (foe_fainted) .Switch else .Pass;
|
|
1499
|
-
if (player == .P1) return
|
|
1500
|
-
return
|
|
1464
|
+
if (player == .P1) return .{ .p1 = .Switch, .p2 = foe_choice };
|
|
1465
|
+
return .{ .p1 = foe_choice, .p2 = .Switch };
|
|
1501
1466
|
}
|
|
1502
1467
|
|
|
1503
1468
|
fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result {
|
|
@@ -1545,21 +1510,19 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1545
1510
|
var volatiles = &side.active.volatiles;
|
|
1546
1511
|
|
|
1547
1512
|
const brn = Status.is(stored.status, .BRN);
|
|
1548
|
-
if (brn or Status.is(stored.status, .PSN))
|
|
1513
|
+
if (brn or Status.is(stored.status, .PSN)) {
|
|
1549
1514
|
var damage = @max(stored.stats.hp / 16, 1);
|
|
1550
|
-
|
|
1551
1515
|
if (volatiles.Toxic) {
|
|
1552
1516
|
volatiles.toxic += 1;
|
|
1553
1517
|
damage *= volatiles.toxic;
|
|
1554
1518
|
}
|
|
1519
|
+
damage = @min(damage, stored.hp);
|
|
1555
1520
|
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
// Pokémon Showdown uses damageOf here but its not relevant in Generation I
|
|
1562
|
-
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
|
+
}
|
|
1563
1526
|
}
|
|
1564
1527
|
|
|
1565
1528
|
if (volatiles.LeechSeed) {
|
|
@@ -1672,9 +1635,9 @@ fn checkEBC(battle: anytype) bool {
|
|
|
1672
1635
|
}
|
|
1673
1636
|
|
|
1674
1637
|
fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
1675
|
-
if (side.active.volatiles.
|
|
1638
|
+
if (side.active.volatiles.disable_move != 0) {
|
|
1676
1639
|
// A Pokémon that transforms after being disabled may end up with less move slots
|
|
1677
|
-
const m = side.active.moves[side.active.volatiles.
|
|
1640
|
+
const m = side.active.moves[side.active.volatiles.disable_move - 1].id;
|
|
1678
1641
|
// side.last_selected_move can be Struggle here on Pokemon Showdown we need an extra check
|
|
1679
1642
|
const last = if (showdown and m == .Bide and side.active.volatiles.Bide)
|
|
1680
1643
|
m
|
|
@@ -1689,7 +1652,7 @@ fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
|
1689
1652
|
return false;
|
|
1690
1653
|
}
|
|
1691
1654
|
|
|
1692
|
-
|
|
1655
|
+
fn buildRage(battle: anytype, who: Player, options: anytype) !void {
|
|
1693
1656
|
const side = battle.side(who);
|
|
1694
1657
|
if (side.active.volatiles.Rage and side.active.boosts.atk < 6) {
|
|
1695
1658
|
try Effects.boost(battle, who, Move.get(.Rage), options);
|
|
@@ -1707,7 +1670,7 @@ fn handleThrashing(battle: anytype, active: *ActivePokemon, player: Player, opti
|
|
|
1707
1670
|
return true;
|
|
1708
1671
|
}
|
|
1709
1672
|
|
|
1710
|
-
|
|
1673
|
+
fn onBegin(
|
|
1711
1674
|
battle: anytype,
|
|
1712
1675
|
player: Player,
|
|
1713
1676
|
move: Move.Data,
|
|
@@ -1737,7 +1700,7 @@ inline fn onBegin(
|
|
|
1737
1700
|
};
|
|
1738
1701
|
}
|
|
1739
1702
|
|
|
1740
|
-
|
|
1703
|
+
fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1741
1704
|
assert(move.effect.onEnd());
|
|
1742
1705
|
return switch (move.effect) {
|
|
1743
1706
|
.Bide => Effects.bide(battle, player, options),
|
|
@@ -1753,7 +1716,7 @@ inline fn onEnd(battle: anytype, player: Player, move: Move.Data, options: anyty
|
|
|
1753
1716
|
};
|
|
1754
1717
|
}
|
|
1755
1718
|
|
|
1756
|
-
|
|
1719
|
+
fn alwaysHappens(
|
|
1757
1720
|
battle: anytype,
|
|
1758
1721
|
player: Player,
|
|
1759
1722
|
move: Move.Data,
|
|
@@ -1772,7 +1735,7 @@ inline fn alwaysHappens(
|
|
|
1772
1735
|
};
|
|
1773
1736
|
}
|
|
1774
1737
|
|
|
1775
|
-
|
|
1738
|
+
fn moveEffect(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
1776
1739
|
return switch (move.effect) {
|
|
1777
1740
|
.BurnChance1, .BurnChance2 => Effects.burnChance(battle, player, move, options),
|
|
1778
1741
|
.ConfusionChance => Effects.confusion(battle, player, move, options),
|
|
@@ -1902,7 +1865,7 @@ pub const Effects = struct {
|
|
|
1902
1865
|
try options.chance.commit(player, .hit);
|
|
1903
1866
|
}
|
|
1904
1867
|
|
|
1905
|
-
if (volatiles.
|
|
1868
|
+
if (volatiles.disable_move != 0) {
|
|
1906
1869
|
try options.log.fail(foe_ident, .None);
|
|
1907
1870
|
return null;
|
|
1908
1871
|
}
|
|
@@ -1927,18 +1890,17 @@ pub const Effects = struct {
|
|
|
1927
1890
|
return Result.Error;
|
|
1928
1891
|
}
|
|
1929
1892
|
|
|
1930
|
-
volatiles.
|
|
1893
|
+
volatiles.disable_move =
|
|
1931
1894
|
@intCast(try Rolls.moveSlot(battle, player, &foe.active.moves, n, options));
|
|
1932
|
-
volatiles.
|
|
1895
|
+
volatiles.disable_duration = Rolls.disableDuration(battle, player, options);
|
|
1933
1896
|
|
|
1934
|
-
const id = foe.active.move(volatiles.
|
|
1897
|
+
const id = foe.active.move(volatiles.disable_move).id;
|
|
1935
1898
|
try options.log.startEffect(foe_ident, .Disable, id);
|
|
1936
1899
|
return null;
|
|
1937
1900
|
}
|
|
1938
1901
|
|
|
1939
1902
|
fn drainHP(battle: anytype, player: Player, options: anytype) !void {
|
|
1940
|
-
var
|
|
1941
|
-
var stored = side.stored();
|
|
1903
|
+
var stored = battle.side(player).stored();
|
|
1942
1904
|
|
|
1943
1905
|
if (battle.last_damage == 0) {
|
|
1944
1906
|
assert(showdown);
|
|
@@ -2063,7 +2025,7 @@ pub const Effects = struct {
|
|
|
2063
2025
|
// GLITCH: HP recovery move failure glitches
|
|
2064
2026
|
const delta = stored.stats.hp - stored.hp;
|
|
2065
2027
|
if (delta == 0 or (delta & 255 == 255 and stored.hp % 256 != 0)) {
|
|
2066
|
-
return
|
|
2028
|
+
return options.log.fail(ident, .None);
|
|
2067
2029
|
}
|
|
2068
2030
|
|
|
2069
2031
|
const rest = side.last_selected_move == .Rest;
|
|
@@ -2145,7 +2107,7 @@ pub const Effects = struct {
|
|
|
2145
2107
|
// Invulnerable foes or 1/256 miss can trigger |-miss| instead of |-fail|
|
|
2146
2108
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2147
2109
|
try options.chance.commit(player, .hit);
|
|
2148
|
-
return
|
|
2110
|
+
return options.log.fail(battle.active(player.foe()), .None);
|
|
2149
2111
|
}
|
|
2150
2112
|
}
|
|
2151
2113
|
if (!try checkHit(battle, player, move, options)) return;
|
|
@@ -2363,8 +2325,7 @@ pub const Effects = struct {
|
|
|
2363
2325
|
fn substitute(battle: anytype, player: Player, residual: *bool, options: anytype) !void {
|
|
2364
2326
|
var side = battle.side(player);
|
|
2365
2327
|
if (side.active.volatiles.Substitute) {
|
|
2366
|
-
|
|
2367
|
-
return;
|
|
2328
|
+
return options.log.fail(battle.active(player), .Substitute);
|
|
2368
2329
|
}
|
|
2369
2330
|
|
|
2370
2331
|
assert(side.stored().stats.hp <= 1023);
|
|
@@ -2375,10 +2336,7 @@ pub const Effects = struct {
|
|
|
2375
2336
|
// the target's HP is exactly divisible by 4 (here we're using an inlined divCeil routine to
|
|
2376
2337
|
// avoid having to convert to floating point)
|
|
2377
2338
|
const required_hp = if (showdown) @divFloor(side.stored().stats.hp - 1, 4) + 1 else hp;
|
|
2378
|
-
if (side.stored().hp < required_hp)
|
|
2379
|
-
try options.log.fail(battle.active(player), .Weak);
|
|
2380
|
-
return;
|
|
2381
|
-
}
|
|
2339
|
+
if (side.stored().hp < required_hp) return options.log.fail(battle.active(player), .Weak);
|
|
2382
2340
|
|
|
2383
2341
|
// GLITCH: can leave the user with 0 HP (faints later) because didn't check '<=' above
|
|
2384
2342
|
side.stored().hp -= hp;
|
|
@@ -2392,12 +2350,17 @@ pub const Effects = struct {
|
|
|
2392
2350
|
}
|
|
2393
2351
|
|
|
2394
2352
|
fn switchAndTeleport(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
2395
|
-
if (!showdown or battle.side(player).last_selected_move == .Teleport) return;
|
|
2396
|
-
|
|
2397
2353
|
// Whirlwind/Roar should not roll to hit/reset damage but Pokémon Showdown does anyway
|
|
2398
|
-
if (
|
|
2399
|
-
|
|
2400
|
-
|
|
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
|
+
}
|
|
2401
2364
|
}
|
|
2402
2365
|
|
|
2403
2366
|
fn thrashing(battle: anytype, player: Player, options: anytype) void {
|
|
@@ -2465,7 +2428,7 @@ pub const Effects = struct {
|
|
|
2465
2428
|
switch (move.effect) {
|
|
2466
2429
|
.AttackUp1, .AttackUp2, .Rage => {
|
|
2467
2430
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2468
|
-
if (boosts.atk == 6) return
|
|
2431
|
+
if (boosts.atk == 6) return log.fail(ident, .None);
|
|
2469
2432
|
const n: u2 = if (move.effect == .AttackUp2) 2 else 1;
|
|
2470
2433
|
boosts.atk = @as(i4, @intCast(@min(6, @as(i8, boosts.atk) + n)));
|
|
2471
2434
|
const reason = if (move.effect == .Rage) Boost.Rage else Boost.Attack;
|
|
@@ -2476,7 +2439,7 @@ pub const Effects = struct {
|
|
|
2476
2439
|
try log.boost(ident, Boost.Attack, -1);
|
|
2477
2440
|
if (move.effect == .Rage) return;
|
|
2478
2441
|
}
|
|
2479
|
-
return
|
|
2442
|
+
return log.fail(ident, .None);
|
|
2480
2443
|
}
|
|
2481
2444
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2482
2445
|
const stat = unmodifiedStats(battle, side).atk;
|
|
@@ -2487,7 +2450,7 @@ pub const Effects = struct {
|
|
|
2487
2450
|
},
|
|
2488
2451
|
.DefenseUp1, .DefenseUp2 => {
|
|
2489
2452
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2490
|
-
if (boosts.def == 6) return
|
|
2453
|
+
if (boosts.def == 6) return log.fail(ident, .None);
|
|
2491
2454
|
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
2492
2455
|
boosts.def = @intCast(@min(6, @as(i8, boosts.def) + n));
|
|
2493
2456
|
if (stats.def == MAX_STAT_VALUE) {
|
|
@@ -2496,7 +2459,7 @@ pub const Effects = struct {
|
|
|
2496
2459
|
try log.boost(ident, .Defense, n);
|
|
2497
2460
|
try log.boost(ident, .Defense, -1);
|
|
2498
2461
|
}
|
|
2499
|
-
return
|
|
2462
|
+
return log.fail(ident, .None);
|
|
2500
2463
|
}
|
|
2501
2464
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2502
2465
|
const stat = unmodifiedStats(battle, side).def;
|
|
@@ -2505,7 +2468,7 @@ pub const Effects = struct {
|
|
|
2505
2468
|
},
|
|
2506
2469
|
.SpeedUp2 => {
|
|
2507
2470
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2508
|
-
if (boosts.spe == 6) return
|
|
2471
|
+
if (boosts.spe == 6) return log.fail(ident, .None);
|
|
2509
2472
|
boosts.spe = @intCast(@min(6, @as(i8, boosts.spe) + 2));
|
|
2510
2473
|
if (stats.spe == MAX_STAT_VALUE) {
|
|
2511
2474
|
boosts.spe -= 1;
|
|
@@ -2513,7 +2476,7 @@ pub const Effects = struct {
|
|
|
2513
2476
|
try log.boost(ident, .Speed, 2);
|
|
2514
2477
|
try log.boost(ident, .Speed, -1);
|
|
2515
2478
|
}
|
|
2516
|
-
return
|
|
2479
|
+
return log.fail(ident, .None);
|
|
2517
2480
|
}
|
|
2518
2481
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2519
2482
|
const stat = unmodifiedStats(battle, side).spe;
|
|
@@ -2522,7 +2485,7 @@ pub const Effects = struct {
|
|
|
2522
2485
|
},
|
|
2523
2486
|
.SpecialUp1, .SpecialUp2 => {
|
|
2524
2487
|
assert(boosts.spc >= -6 and boosts.spc <= 6);
|
|
2525
|
-
if (boosts.spc == 6) return
|
|
2488
|
+
if (boosts.spc == 6) return log.fail(ident, .None);
|
|
2526
2489
|
const n: u2 = if (move.effect == .SpecialUp2) 2 else 1;
|
|
2527
2490
|
boosts.spc = @intCast(@min(6, @as(i8, boosts.spc) + n));
|
|
2528
2491
|
if (stats.spc == MAX_STAT_VALUE) {
|
|
@@ -2533,7 +2496,7 @@ pub const Effects = struct {
|
|
|
2533
2496
|
try log.boost(ident, .SpecialDefense, n);
|
|
2534
2497
|
try log.boost(ident, .SpecialDefense, -1);
|
|
2535
2498
|
}
|
|
2536
|
-
return
|
|
2499
|
+
return log.fail(ident, .None);
|
|
2537
2500
|
}
|
|
2538
2501
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2539
2502
|
const stat = unmodifiedStats(battle, side).spc;
|
|
@@ -2543,7 +2506,7 @@ pub const Effects = struct {
|
|
|
2543
2506
|
},
|
|
2544
2507
|
.EvasionUp1 => {
|
|
2545
2508
|
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2546
|
-
if (boosts.evasion == 6) return
|
|
2509
|
+
if (boosts.evasion == 6) return log.fail(ident, .None);
|
|
2547
2510
|
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2548
2511
|
try log.boost(ident, .Evasion, 1);
|
|
2549
2512
|
},
|
|
@@ -2586,7 +2549,7 @@ pub const Effects = struct {
|
|
|
2586
2549
|
try log.boost(foe_ident, .Attack, -1);
|
|
2587
2550
|
try log.boost(foe_ident, .Attack, 1);
|
|
2588
2551
|
}
|
|
2589
|
-
return
|
|
2552
|
+
return log.fail(foe_ident, .None);
|
|
2590
2553
|
}
|
|
2591
2554
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2592
2555
|
const stat = unmodifiedStats(battle, foe).atk;
|
|
@@ -2604,7 +2567,7 @@ pub const Effects = struct {
|
|
|
2604
2567
|
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2605
2568
|
try log.boost(foe_ident, .Defense, 1);
|
|
2606
2569
|
}
|
|
2607
|
-
return
|
|
2570
|
+
return log.fail(foe_ident, .None);
|
|
2608
2571
|
}
|
|
2609
2572
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2610
2573
|
const stat = unmodifiedStats(battle, foe).def;
|
|
@@ -2621,7 +2584,7 @@ pub const Effects = struct {
|
|
|
2621
2584
|
try log.boost(foe_ident, .Speed, -1);
|
|
2622
2585
|
try log.boost(foe_ident, .Speed, 1);
|
|
2623
2586
|
}
|
|
2624
|
-
return
|
|
2587
|
+
return log.fail(foe_ident, .None);
|
|
2625
2588
|
}
|
|
2626
2589
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2627
2590
|
const stat = unmodifiedStats(battle, foe).spe;
|
|
@@ -2641,7 +2604,7 @@ pub const Effects = struct {
|
|
|
2641
2604
|
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2642
2605
|
try log.boost(foe_ident, .SpecialDefense, 1);
|
|
2643
2606
|
}
|
|
2644
|
-
return
|
|
2607
|
+
return log.fail(foe_ident, .None);
|
|
2645
2608
|
}
|
|
2646
2609
|
var mod = BOOSTS[@as(u4, @intCast(@as(i8, boosts.spc) + 6))];
|
|
2647
2610
|
const stat = unmodifiedStats(battle, foe).spc;
|
|
@@ -2677,7 +2640,7 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
2677
2640
|
}
|
|
2678
2641
|
}
|
|
2679
2642
|
|
|
2680
|
-
|
|
2643
|
+
fn isForced(active: anytype) bool {
|
|
2681
2644
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
2682
2645
|
active.volatiles.Thrashing or active.volatiles.Charging;
|
|
2683
2646
|
}
|
|
@@ -2690,9 +2653,9 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2690
2653
|
|
|
2691
2654
|
options.chance.clearDurations(who, clear);
|
|
2692
2655
|
|
|
2693
|
-
if (volatiles.
|
|
2694
|
-
volatiles.
|
|
2695
|
-
volatiles.
|
|
2656
|
+
if (volatiles.disable_move != 0) {
|
|
2657
|
+
volatiles.disable_move = 0;
|
|
2658
|
+
volatiles.disable_duration = 0;
|
|
2696
2659
|
try log.end(ident, .DisableSilent);
|
|
2697
2660
|
}
|
|
2698
2661
|
if (volatiles.Confusion) {
|
|
@@ -2702,15 +2665,15 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2702
2665
|
}
|
|
2703
2666
|
if (volatiles.Mist) {
|
|
2704
2667
|
volatiles.Mist = false;
|
|
2705
|
-
try log.end(ident, .
|
|
2668
|
+
try log.end(ident, .MistSilent);
|
|
2706
2669
|
}
|
|
2707
2670
|
if (volatiles.FocusEnergy) {
|
|
2708
2671
|
volatiles.FocusEnergy = false;
|
|
2709
|
-
try log.end(ident, .
|
|
2672
|
+
try log.end(ident, .FocusEnergySilent);
|
|
2710
2673
|
}
|
|
2711
2674
|
if (volatiles.LeechSeed) {
|
|
2712
2675
|
volatiles.LeechSeed = false;
|
|
2713
|
-
try log.end(ident, .
|
|
2676
|
+
try log.end(ident, .LeechSeedSilent);
|
|
2714
2677
|
}
|
|
2715
2678
|
if (!showdown and volatiles.Toxic) {
|
|
2716
2679
|
volatiles.Toxic = false;
|
|
@@ -2718,22 +2681,22 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2718
2681
|
}
|
|
2719
2682
|
if (volatiles.LightScreen) {
|
|
2720
2683
|
volatiles.LightScreen = false;
|
|
2721
|
-
try log.end(ident, .
|
|
2684
|
+
try log.end(ident, .LightScreenSilent);
|
|
2722
2685
|
}
|
|
2723
2686
|
if (volatiles.Reflect) {
|
|
2724
2687
|
volatiles.Reflect = false;
|
|
2725
|
-
try log.end(ident, .
|
|
2688
|
+
try log.end(ident, .ReflectSilent);
|
|
2726
2689
|
}
|
|
2727
2690
|
if (showdown and volatiles.Toxic) {
|
|
2728
2691
|
volatiles.Toxic = false;
|
|
2729
2692
|
// Pokémon Showdown erroneously clears the toxic counter
|
|
2730
2693
|
volatiles.toxic = 0;
|
|
2731
|
-
try log.end(ident, .
|
|
2694
|
+
try log.end(ident, .ToxicSilent);
|
|
2732
2695
|
}
|
|
2733
2696
|
}
|
|
2734
2697
|
|
|
2735
2698
|
pub const Rolls = struct {
|
|
2736
|
-
|
|
2699
|
+
fn speedTie(battle: anytype, options: anytype) !bool {
|
|
2737
2700
|
const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
|
|
2738
2701
|
player == .P1
|
|
2739
2702
|
else if (showdown)
|
|
@@ -2745,7 +2708,7 @@ pub const Rolls = struct {
|
|
|
2745
2708
|
return p1;
|
|
2746
2709
|
}
|
|
2747
2710
|
|
|
2748
|
-
|
|
2711
|
+
fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
|
|
2749
2712
|
const crit = if (options.calc.overridden(player, .critical_hit)) |val|
|
|
2750
2713
|
val == .true
|
|
2751
2714
|
else if (showdown)
|
|
@@ -2757,7 +2720,7 @@ pub const Rolls = struct {
|
|
|
2757
2720
|
return crit;
|
|
2758
2721
|
}
|
|
2759
2722
|
|
|
2760
|
-
|
|
2723
|
+
fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2761
2724
|
const roll = if (options.calc.overridden(player, .damage)) |val|
|
|
2762
2725
|
val
|
|
2763
2726
|
else roll: {
|
|
@@ -2773,7 +2736,7 @@ pub const Rolls = struct {
|
|
|
2773
2736
|
return roll;
|
|
2774
2737
|
}
|
|
2775
2738
|
|
|
2776
|
-
|
|
2739
|
+
fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) bool {
|
|
2777
2740
|
const ok = if (options.calc.overridden(player, .hit)) |val|
|
|
2778
2741
|
val == .true
|
|
2779
2742
|
else if (showdown)
|
|
@@ -2785,7 +2748,7 @@ pub const Rolls = struct {
|
|
|
2785
2748
|
return ok;
|
|
2786
2749
|
}
|
|
2787
2750
|
|
|
2788
|
-
|
|
2751
|
+
fn confused(battle: anytype, player: Player, options: anytype) !bool {
|
|
2789
2752
|
const cfz = if (options.calc.overridden(player, .confused)) |val|
|
|
2790
2753
|
val == .true
|
|
2791
2754
|
else if (showdown)
|
|
@@ -2797,7 +2760,7 @@ pub const Rolls = struct {
|
|
|
2797
2760
|
return cfz;
|
|
2798
2761
|
}
|
|
2799
2762
|
|
|
2800
|
-
|
|
2763
|
+
fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
|
|
2801
2764
|
const par = if (options.calc.overridden(player, .paralyzed)) |val|
|
|
2802
2765
|
val == .true
|
|
2803
2766
|
else if (showdown)
|
|
@@ -2809,7 +2772,7 @@ pub const Rolls = struct {
|
|
|
2809
2772
|
return par;
|
|
2810
2773
|
}
|
|
2811
2774
|
|
|
2812
|
-
|
|
2775
|
+
fn confusionChance(battle: anytype, player: Player, options: anytype) !bool {
|
|
2813
2776
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2814
2777
|
val == .true
|
|
2815
2778
|
else if (showdown)
|
|
@@ -2821,7 +2784,7 @@ pub const Rolls = struct {
|
|
|
2821
2784
|
return proc;
|
|
2822
2785
|
}
|
|
2823
2786
|
|
|
2824
|
-
|
|
2787
|
+
fn secondaryChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2825
2788
|
const rate: u8 = if (low) 26 else 77;
|
|
2826
2789
|
|
|
2827
2790
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
@@ -2835,7 +2798,7 @@ pub const Rolls = struct {
|
|
|
2835
2798
|
return proc;
|
|
2836
2799
|
}
|
|
2837
2800
|
|
|
2838
|
-
|
|
2801
|
+
fn poisonChance(battle: anytype, player: Player, low: bool, options: anytype) !bool {
|
|
2839
2802
|
const rate: u8 = if (low) 52 else 103;
|
|
2840
2803
|
|
|
2841
2804
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
@@ -2849,7 +2812,7 @@ pub const Rolls = struct {
|
|
|
2849
2812
|
return proc;
|
|
2850
2813
|
}
|
|
2851
2814
|
|
|
2852
|
-
|
|
2815
|
+
fn unboost(battle: anytype, player: Player, options: anytype) !bool {
|
|
2853
2816
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2854
2817
|
val == .true
|
|
2855
2818
|
else if (showdown)
|
|
@@ -2861,14 +2824,12 @@ pub const Rolls = struct {
|
|
|
2861
2824
|
return proc;
|
|
2862
2825
|
}
|
|
2863
2826
|
|
|
2864
|
-
|
|
2827
|
+
fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
2865
2828
|
const move: Move = if (options.calc.overridden(player, .metronome)) |val|
|
|
2866
2829
|
val
|
|
2867
|
-
else if (showdown)
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
break :move @enumFromInt(r + mod);
|
|
2871
|
-
} else move: {
|
|
2830
|
+
else if (showdown)
|
|
2831
|
+
Move.METRONOME[battle.rng.range(u8, 0, Move.METRONOME.len)]
|
|
2832
|
+
else move: {
|
|
2872
2833
|
while (true) {
|
|
2873
2834
|
const r = battle.rng.next();
|
|
2874
2835
|
if (r == 0 or r == @intFromEnum(Move.Metronome)) continue;
|
|
@@ -2881,7 +2842,7 @@ pub const Rolls = struct {
|
|
|
2881
2842
|
return move;
|
|
2882
2843
|
}
|
|
2883
2844
|
|
|
2884
|
-
|
|
2845
|
+
fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
|
|
2885
2846
|
const power = if (options.calc.overridden(player, .psywave)) |val|
|
|
2886
2847
|
val - 1
|
|
2887
2848
|
else if (showdown)
|
|
@@ -2898,11 +2859,11 @@ pub const Rolls = struct {
|
|
|
2898
2859
|
return power;
|
|
2899
2860
|
}
|
|
2900
2861
|
|
|
2901
|
-
|
|
2862
|
+
fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2902
2863
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2903
2864
|
@intCast(val)
|
|
2904
2865
|
else if (showdown)
|
|
2905
|
-
|
|
2866
|
+
battle.rng.range(u3, 1, 8)
|
|
2906
2867
|
else duration: {
|
|
2907
2868
|
while (true) {
|
|
2908
2869
|
const r = battle.rng.next() & 7;
|
|
@@ -2915,11 +2876,11 @@ pub const Rolls = struct {
|
|
|
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
|
|
|
@@ -2928,11 +2889,11 @@ pub const Rolls = struct {
|
|
|
2928
2889
|
return duration;
|
|
2929
2890
|
}
|
|
2930
2891
|
|
|
2931
|
-
|
|
2892
|
+
fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
|
|
2932
2893
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2933
2894
|
@intCast(val)
|
|
2934
2895
|
else if (showdown)
|
|
2935
|
-
|
|
2896
|
+
battle.rng.range(u3, 2, 6)
|
|
2936
2897
|
else
|
|
2937
2898
|
@intCast((battle.rng.next() & 3) + 2);
|
|
2938
2899
|
|
|
@@ -2941,11 +2902,11 @@ pub const Rolls = struct {
|
|
|
2941
2902
|
return duration;
|
|
2942
2903
|
}
|
|
2943
2904
|
|
|
2944
|
-
|
|
2905
|
+
fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2945
2906
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2946
2907
|
@intCast(val)
|
|
2947
2908
|
else if (showdown)
|
|
2948
|
-
|
|
2909
|
+
battle.rng.range(u3, 2, 4)
|
|
2949
2910
|
else
|
|
2950
2911
|
@intCast((battle.rng.next() & 1) + 2);
|
|
2951
2912
|
|
|
@@ -2954,7 +2915,9 @@ pub const Rolls = struct {
|
|
|
2954
2915
|
return duration;
|
|
2955
2916
|
}
|
|
2956
2917
|
|
|
2957
|
-
|
|
2918
|
+
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
2919
|
+
|
|
2920
|
+
fn distribution(
|
|
2958
2921
|
battle: anytype,
|
|
2959
2922
|
comptime effect: Move.Effect,
|
|
2960
2923
|
player: Player,
|
|
@@ -2964,7 +2927,7 @@ pub const Rolls = struct {
|
|
|
2964
2927
|
const n: u3 = if (options.calc.overridden(player, roll)) |val|
|
|
2965
2928
|
@intCast(val)
|
|
2966
2929
|
else if (showdown)
|
|
2967
|
-
DISTRIBUTION[battle.rng.range(
|
|
2930
|
+
DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
|
|
2968
2931
|
else n: {
|
|
2969
2932
|
const r = (battle.rng.next() & 3);
|
|
2970
2933
|
break :n @intCast((if (r < 2) r else battle.rng.next() & 3) + 2);
|
|
@@ -3011,10 +2974,9 @@ pub const Rolls = struct {
|
|
|
3011
2974
|
while (i < moves.len and r > 0) : (i += 1) {
|
|
3012
2975
|
if (moves[i].pp > 0) {
|
|
3013
2976
|
r -= 1;
|
|
3014
|
-
if (r == 0) break;
|
|
2977
|
+
if (r == 0) break :slot @intCast(i + 1);
|
|
3015
2978
|
}
|
|
3016
2979
|
}
|
|
3017
|
-
break :slot @intCast(i + 1);
|
|
3018
2980
|
}
|
|
3019
2981
|
}
|
|
3020
2982
|
|
|
@@ -3036,11 +2998,11 @@ test "RNG agreement" {
|
|
|
3036
2998
|
expected[i] = @intCast(i * 0x1000000);
|
|
3037
2999
|
}
|
|
3038
3000
|
|
|
3039
|
-
var spe
|
|
3040
|
-
var cfz
|
|
3041
|
-
var par
|
|
3042
|
-
var brn
|
|
3043
|
-
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 };
|
|
3044
3006
|
|
|
3045
3007
|
for (0..expected.len) |i| {
|
|
3046
3008
|
try expectEqual(spe.range(u8, 0, 2) == 0, i < Gen12.percent(50) + 1);
|
|
@@ -3130,7 +3092,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3130
3092
|
if (m.id == if (active.volatiles.Bide) .Bide else side.last_selected_move) {
|
|
3131
3093
|
// Pokémon Showdown displays Struggle if limited to Bide but unable to pick
|
|
3132
3094
|
const struggle =
|
|
3133
|
-
m.id == .Bide and (m.pp == 0 or active.volatiles.
|
|
3095
|
+
m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
|
|
3134
3096
|
const s = if (struggle) 0 else slot;
|
|
3135
3097
|
out[n] = .{ .type = .Move, .data = s };
|
|
3136
3098
|
n += 1;
|
|
@@ -3145,7 +3107,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3145
3107
|
const m = active.moves[slot - 1];
|
|
3146
3108
|
if (m.id == .None) break;
|
|
3147
3109
|
if (m.pp == 0) continue;
|
|
3148
|
-
if (active.volatiles.
|
|
3110
|
+
if (active.volatiles.disable_move == slot) continue;
|
|
3149
3111
|
out[n] = .{ .type = .Move, .data = slot };
|
|
3150
3112
|
n += 1;
|
|
3151
3113
|
}
|