@pkmn/engine 0.1.0-dev.46307385 → 0.1.0-dev.4c173f03
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 +7 -3
- package/package.json +27 -15
- package/src/lib/binding/node.zig +1 -1
- package/src/lib/common/data.zig +4 -16
- package/src/lib/common/optional.zig +8 -1
- package/src/lib/common/protocol.zig +558 -80
- package/src/lib/common/rational.zig +1 -1
- package/src/lib/common/rng.zig +6 -6
- package/src/lib/gen1/README.md +31 -29
- package/src/lib/gen1/calc.zig +2 -3
- package/src/lib/gen1/chance.zig +51 -35
- package/src/lib/gen1/data/moves.zig +183 -171
- package/src/lib/gen1/data/species.zig +1 -1
- package/src/lib/gen1/data/types.zig +3 -3
- package/src/lib/gen1/data.zig +17 -29
- package/src/lib/gen1/helpers.zig +13 -28
- package/src/lib/gen1/mechanics.zig +93 -130
- package/src/lib/gen2/README.md +94 -72
- package/src/lib/gen2/calc.zig +143 -0
- package/src/lib/gen2/chance.zig +1314 -0
- package/src/lib/gen2/data/items.zig +11 -17
- package/src/lib/gen2/data/moves.zig +660 -566
- package/src/lib/gen2/data/species.zig +1 -1
- package/src/lib/gen2/data/types.zig +129 -26
- package/src/lib/gen2/data.zig +103 -64
- package/src/lib/gen2/generated.zig +1023 -0
- package/src/lib/gen2/helpers.zig +8 -12
- package/src/lib/gen2/mechanics.zig +2906 -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
|
|
|
@@ -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
|
|
@@ -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);
|
|
@@ -2395,12 +2354,17 @@ pub const Effects = struct {
|
|
|
2395
2354
|
}
|
|
2396
2355
|
|
|
2397
2356
|
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
2357
|
// Whirlwind/Roar should not roll to hit/reset damage but Pokémon Showdown does anyway
|
|
2401
|
-
if (
|
|
2402
|
-
|
|
2403
|
-
|
|
2358
|
+
if (showdown) {
|
|
2359
|
+
if (battle.side(player).last_selected_move == .Teleport) return;
|
|
2360
|
+
if (try checkHit(battle, player, move, options)) {
|
|
2361
|
+
try options.chance.commit(player, .hit);
|
|
2362
|
+
}
|
|
2363
|
+
battle.last_damage = 0;
|
|
2364
|
+
} else {
|
|
2365
|
+
try options.log.fail(battle.active(player), .None);
|
|
2366
|
+
try options.log.laststill();
|
|
2367
|
+
}
|
|
2404
2368
|
}
|
|
2405
2369
|
|
|
2406
2370
|
fn thrashing(battle: anytype, player: Player, options: anytype) void {
|
|
@@ -2693,9 +2657,9 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2693
2657
|
|
|
2694
2658
|
options.chance.clearDurations(who, clear);
|
|
2695
2659
|
|
|
2696
|
-
if (volatiles.
|
|
2697
|
-
volatiles.
|
|
2698
|
-
volatiles.
|
|
2660
|
+
if (volatiles.disable_move != 0) {
|
|
2661
|
+
volatiles.disable_move = 0;
|
|
2662
|
+
volatiles.disable_duration = 0;
|
|
2699
2663
|
try log.end(ident, .DisableSilent);
|
|
2700
2664
|
}
|
|
2701
2665
|
if (volatiles.Confusion) {
|
|
@@ -2705,15 +2669,15 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2705
2669
|
}
|
|
2706
2670
|
if (volatiles.Mist) {
|
|
2707
2671
|
volatiles.Mist = false;
|
|
2708
|
-
try log.end(ident, .
|
|
2672
|
+
try log.end(ident, .MistSilent);
|
|
2709
2673
|
}
|
|
2710
2674
|
if (volatiles.FocusEnergy) {
|
|
2711
2675
|
volatiles.FocusEnergy = false;
|
|
2712
|
-
try log.end(ident, .
|
|
2676
|
+
try log.end(ident, .FocusEnergySilent);
|
|
2713
2677
|
}
|
|
2714
2678
|
if (volatiles.LeechSeed) {
|
|
2715
2679
|
volatiles.LeechSeed = false;
|
|
2716
|
-
try log.end(ident, .
|
|
2680
|
+
try log.end(ident, .LeechSeedSilent);
|
|
2717
2681
|
}
|
|
2718
2682
|
if (!showdown and volatiles.Toxic) {
|
|
2719
2683
|
volatiles.Toxic = false;
|
|
@@ -2721,17 +2685,17 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2721
2685
|
}
|
|
2722
2686
|
if (volatiles.LightScreen) {
|
|
2723
2687
|
volatiles.LightScreen = false;
|
|
2724
|
-
try log.end(ident, .
|
|
2688
|
+
try log.end(ident, .LightScreenSilent);
|
|
2725
2689
|
}
|
|
2726
2690
|
if (volatiles.Reflect) {
|
|
2727
2691
|
volatiles.Reflect = false;
|
|
2728
|
-
try log.end(ident, .
|
|
2692
|
+
try log.end(ident, .ReflectSilent);
|
|
2729
2693
|
}
|
|
2730
2694
|
if (showdown and volatiles.Toxic) {
|
|
2731
2695
|
volatiles.Toxic = false;
|
|
2732
2696
|
// Pokémon Showdown erroneously clears the toxic counter
|
|
2733
2697
|
volatiles.toxic = 0;
|
|
2734
|
-
try log.end(ident, .
|
|
2698
|
+
try log.end(ident, .ToxicSilent);
|
|
2735
2699
|
}
|
|
2736
2700
|
}
|
|
2737
2701
|
|
|
@@ -2760,7 +2724,7 @@ pub const Rolls = struct {
|
|
|
2760
2724
|
return crit;
|
|
2761
2725
|
}
|
|
2762
2726
|
|
|
2763
|
-
inline fn damage(battle: anytype, player:
|
|
2727
|
+
inline fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2764
2728
|
const roll = if (options.calc.overridden(player, .damage)) |val|
|
|
2765
2729
|
val
|
|
2766
2730
|
else roll: {
|
|
@@ -2867,11 +2831,9 @@ pub const Rolls = struct {
|
|
|
2867
2831
|
inline fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
2868
2832
|
const move: Move = if (options.calc.overridden(player, .metronome)) |val|
|
|
2869
2833
|
val
|
|
2870
|
-
else if (showdown)
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
break :move @enumFromInt(r + mod);
|
|
2874
|
-
} else move: {
|
|
2834
|
+
else if (showdown)
|
|
2835
|
+
Move.METRONOME[battle.rng.range(u8, 0, Move.METRONOME.len)]
|
|
2836
|
+
else move: {
|
|
2875
2837
|
while (true) {
|
|
2876
2838
|
const r = battle.rng.next();
|
|
2877
2839
|
if (r == 0 or r == @intFromEnum(Move.Metronome)) continue;
|
|
@@ -2905,7 +2867,7 @@ pub const Rolls = struct {
|
|
|
2905
2867
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2906
2868
|
@intCast(val)
|
|
2907
2869
|
else if (showdown)
|
|
2908
|
-
|
|
2870
|
+
battle.rng.range(u3, 1, 8)
|
|
2909
2871
|
else duration: {
|
|
2910
2872
|
while (true) {
|
|
2911
2873
|
const r = battle.rng.next() & 7;
|
|
@@ -2922,7 +2884,7 @@ pub const Rolls = struct {
|
|
|
2922
2884
|
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2923
2885
|
val
|
|
2924
2886
|
else if (showdown)
|
|
2925
|
-
|
|
2887
|
+
battle.rng.range(u4, 1, 9)
|
|
2926
2888
|
else
|
|
2927
2889
|
@intCast((battle.rng.next() & 7) + 1);
|
|
2928
2890
|
|
|
@@ -2935,7 +2897,7 @@ pub const Rolls = struct {
|
|
|
2935
2897
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2936
2898
|
@intCast(val)
|
|
2937
2899
|
else if (showdown)
|
|
2938
|
-
|
|
2900
|
+
battle.rng.range(u3, 2, 6)
|
|
2939
2901
|
else
|
|
2940
2902
|
@intCast((battle.rng.next() & 3) + 2);
|
|
2941
2903
|
|
|
@@ -2948,7 +2910,7 @@ pub const Rolls = struct {
|
|
|
2948
2910
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2949
2911
|
@intCast(val)
|
|
2950
2912
|
else if (showdown)
|
|
2951
|
-
|
|
2913
|
+
battle.rng.range(u3, 2, 4)
|
|
2952
2914
|
else
|
|
2953
2915
|
@intCast((battle.rng.next() & 1) + 2);
|
|
2954
2916
|
|
|
@@ -2957,6 +2919,8 @@ pub const Rolls = struct {
|
|
|
2957
2919
|
return duration;
|
|
2958
2920
|
}
|
|
2959
2921
|
|
|
2922
|
+
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
2923
|
+
|
|
2960
2924
|
inline fn distribution(
|
|
2961
2925
|
battle: anytype,
|
|
2962
2926
|
comptime effect: Move.Effect,
|
|
@@ -2967,7 +2931,7 @@ pub const Rolls = struct {
|
|
|
2967
2931
|
const n: u3 = if (options.calc.overridden(player, roll)) |val|
|
|
2968
2932
|
@intCast(val)
|
|
2969
2933
|
else if (showdown)
|
|
2970
|
-
DISTRIBUTION[battle.rng.range(
|
|
2934
|
+
DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
|
|
2971
2935
|
else n: {
|
|
2972
2936
|
const r = (battle.rng.next() & 3);
|
|
2973
2937
|
break :n @intCast((if (r < 2) r else battle.rng.next() & 3) + 2);
|
|
@@ -3014,10 +2978,9 @@ pub const Rolls = struct {
|
|
|
3014
2978
|
while (i < moves.len and r > 0) : (i += 1) {
|
|
3015
2979
|
if (moves[i].pp > 0) {
|
|
3016
2980
|
r -= 1;
|
|
3017
|
-
if (r == 0) break;
|
|
2981
|
+
if (r == 0) break :slot @intCast(i + 1);
|
|
3018
2982
|
}
|
|
3019
2983
|
}
|
|
3020
|
-
break :slot @intCast(i + 1);
|
|
3021
2984
|
}
|
|
3022
2985
|
}
|
|
3023
2986
|
|
|
@@ -3039,11 +3002,11 @@ test "RNG agreement" {
|
|
|
3039
3002
|
expected[i] = @intCast(i * 0x1000000);
|
|
3040
3003
|
}
|
|
3041
3004
|
|
|
3042
|
-
var spe
|
|
3043
|
-
var cfz
|
|
3044
|
-
var par
|
|
3045
|
-
var brn
|
|
3046
|
-
var eff
|
|
3005
|
+
var spe: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3006
|
+
var cfz: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3007
|
+
var par: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3008
|
+
var brn: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3009
|
+
var eff: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3047
3010
|
|
|
3048
3011
|
for (0..expected.len) |i| {
|
|
3049
3012
|
try expectEqual(spe.range(u8, 0, 2) == 0, i < Gen12.percent(50) + 1);
|
|
@@ -3133,7 +3096,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3133
3096
|
if (m.id == if (active.volatiles.Bide) .Bide else side.last_selected_move) {
|
|
3134
3097
|
// Pokémon Showdown displays Struggle if limited to Bide but unable to pick
|
|
3135
3098
|
const struggle =
|
|
3136
|
-
m.id == .Bide and (m.pp == 0 or active.volatiles.
|
|
3099
|
+
m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
|
|
3137
3100
|
const s = if (struggle) 0 else slot;
|
|
3138
3101
|
out[n] = .{ .type = .Move, .data = s };
|
|
3139
3102
|
n += 1;
|
|
@@ -3148,7 +3111,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3148
3111
|
const m = active.moves[slot - 1];
|
|
3149
3112
|
if (m.id == .None) break;
|
|
3150
3113
|
if (m.pp == 0) continue;
|
|
3151
|
-
if (active.volatiles.
|
|
3114
|
+
if (active.volatiles.disable_move == slot) continue;
|
|
3152
3115
|
out[n] = .{ .type = .Move, .data = slot };
|
|
3153
3116
|
n += 1;
|
|
3154
3117
|
}
|