@pkmn/engine 0.1.0-dev.bcb16a6c → 0.1.0-dev.be951af1
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/layout.json +2 -2
- package/build/pkg/gen1.js +2 -2
- package/build/pkg/gen1.js.map +1 -1
- package/package.json +17 -14
- package/src/lib/common/optional.zig +4 -6
- package/src/lib/common/protocol.zig +46 -19
- package/src/lib/gen1/README.md +28 -28
- package/src/lib/gen1/chance.zig +43 -41
- package/src/lib/gen1/data/moves.zig +14 -0
- package/src/lib/gen1/data.zig +6 -6
- package/src/lib/gen1/helpers.zig +1 -16
- package/src/lib/gen1/mechanics.zig +61 -94
- package/src/lib/gen2/README.md +1 -1
- package/src/lib/gen2/calc.zig +143 -0
- package/src/lib/gen2/chance.zig +1314 -0
- package/src/lib/gen2/data/moves.zig +27 -10
- package/src/lib/gen2/data/types.zig +3 -2
- package/src/lib/gen2/data.zig +11 -5
- package/src/lib/gen2/generated.zig +1239 -0
- package/src/lib/gen2/mechanics.zig +1712 -329
package/src/lib/gen1/data.zig
CHANGED
|
@@ -378,9 +378,9 @@ pub const Volatiles = packed struct(u64) {
|
|
|
378
378
|
/// The identity of whom the active Pokémon is transformed into.
|
|
379
379
|
transform: u4 = 0,
|
|
380
380
|
/// The remaining turns the move is disabled.
|
|
381
|
-
|
|
381
|
+
disable_duration: u4 = 0,
|
|
382
382
|
/// The move slot (1-4) that is disabled.
|
|
383
|
-
|
|
383
|
+
disable_move: u3 = 0,
|
|
384
384
|
/// The number of turns toxic damage has been accumulating.
|
|
385
385
|
toxic: u5 = 0,
|
|
386
386
|
};
|
|
@@ -395,8 +395,8 @@ test Volatiles {
|
|
|
395
395
|
volatiles.Substitute = true;
|
|
396
396
|
volatiles.substitute = 42;
|
|
397
397
|
volatiles.toxic = 4;
|
|
398
|
-
volatiles.
|
|
399
|
-
volatiles.
|
|
398
|
+
volatiles.disable_move = 2;
|
|
399
|
+
volatiles.disable_duration = 4;
|
|
400
400
|
|
|
401
401
|
try expect(volatiles.Confusion);
|
|
402
402
|
try expect(volatiles.Thrashing);
|
|
@@ -407,8 +407,8 @@ test Volatiles {
|
|
|
407
407
|
|
|
408
408
|
try expectEqual(@as(u16, 235), volatiles.state);
|
|
409
409
|
try expectEqual(@as(u8, 42), volatiles.substitute);
|
|
410
|
-
try expectEqual(@as(u4, 2), volatiles.
|
|
411
|
-
try expectEqual(@as(u4, 4), volatiles.
|
|
410
|
+
try expectEqual(@as(u4, 2), volatiles.disable_move);
|
|
411
|
+
try expectEqual(@as(u4, 4), volatiles.disable_duration);
|
|
412
412
|
try expectEqual(@as(u4, 2), volatiles.confusion);
|
|
413
413
|
try expectEqual(@as(u5, 4), volatiles.toxic);
|
|
414
414
|
try expectEqual(@as(u4, 3), volatiles.attacks);
|
package/src/lib/gen1/helpers.zig
CHANGED
|
@@ -455,25 +455,10 @@ pub const Rolls = struct {
|
|
|
455
455
|
}
|
|
456
456
|
|
|
457
457
|
const MOVE_NONE = [_]Move{.None};
|
|
458
|
-
const MOVES = init: {
|
|
459
|
-
var moves: [Move.size - 2]Move = undefined;
|
|
460
|
-
var i: usize = 0;
|
|
461
|
-
for (@typeInfo(Move).Enum.fields) |f| {
|
|
462
|
-
if (!(std.mem.eql(u8, f.name, "None") or
|
|
463
|
-
std.mem.eql(u8, f.name, "Metronome") or
|
|
464
|
-
std.mem.eql(u8, f.name, "Struggle") or
|
|
465
|
-
std.mem.eql(u8, f.name, "SKIP_TURN")))
|
|
466
|
-
{
|
|
467
|
-
moves[i] = @field(Move, f.name);
|
|
468
|
-
i += 1;
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
break :init moves;
|
|
472
|
-
};
|
|
473
458
|
|
|
474
459
|
/// Returns a slice with the correct range of values for metronome given the `action` state.
|
|
475
460
|
pub inline fn metronome(action: Action) []const Move {
|
|
476
|
-
return if (@field(action, "metronome") == .None) &MOVE_NONE else &
|
|
461
|
+
return if (@field(action, "metronome") == .None) &MOVE_NONE else &Move.METRONOME;
|
|
477
462
|
}
|
|
478
463
|
};
|
|
479
464
|
|
|
@@ -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,19 +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
61
|
assert(c1.type != .Pass or c2.type != .Pass or battle.turn == 0);
|
|
66
62
|
if (battle.turn == 0) return start(battle, options);
|
|
67
63
|
|
|
68
64
|
var s1 = false;
|
|
69
65
|
var s2 = false;
|
|
70
|
-
var f1: ?Move = null;
|
|
71
|
-
var f2: ?Move = null;
|
|
72
66
|
|
|
73
|
-
if (selectMove(battle, .P1, c1, c2, &s1
|
|
74
|
-
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;
|
|
75
69
|
|
|
76
70
|
var p1 = battle.side(.P1);
|
|
77
71
|
var p2 = battle.side(.P2);
|
|
@@ -80,9 +74,9 @@ pub fn update(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Result
|
|
|
80
74
|
var r2 = showdown and p2.active.volatiles.Binding and c1.type == .Switch;
|
|
81
75
|
|
|
82
76
|
if (try turnOrder(battle, c1, c2, options) == .P1) {
|
|
83
|
-
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;
|
|
84
78
|
} else {
|
|
85
|
-
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;
|
|
86
80
|
}
|
|
87
81
|
|
|
88
82
|
if (p1.active.volatiles.attacks == 0) p1.active.volatiles.Binding = false;
|
|
@@ -120,7 +114,6 @@ fn selectMove(
|
|
|
120
114
|
choice: Choice,
|
|
121
115
|
foe_choice: Choice,
|
|
122
116
|
skip_turn: *bool,
|
|
123
|
-
from: *?Move,
|
|
124
117
|
) ?Result {
|
|
125
118
|
if (choice.type == .Pass) return null;
|
|
126
119
|
|
|
@@ -137,7 +130,6 @@ fn selectMove(
|
|
|
137
130
|
return null;
|
|
138
131
|
}
|
|
139
132
|
if (volatiles.Rage) {
|
|
140
|
-
from.* = side.last_used_move;
|
|
141
133
|
if (showdown) {
|
|
142
134
|
if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
|
|
143
135
|
saveMove(battle, player, null);
|
|
@@ -147,7 +139,6 @@ fn selectMove(
|
|
|
147
139
|
// Pokémon Showdown removes Flinch at the end-of-turn in its residual handler
|
|
148
140
|
if (!showdown) volatiles.Flinch = false;
|
|
149
141
|
if (volatiles.Thrashing or volatiles.Charging) {
|
|
150
|
-
from.* = side.last_selected_move;
|
|
151
142
|
if (showdown) {
|
|
152
143
|
if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
|
|
153
144
|
saveMove(battle, player, null);
|
|
@@ -168,9 +159,7 @@ fn selectMove(
|
|
|
168
159
|
return null;
|
|
169
160
|
}
|
|
170
161
|
if (volatiles.Binding) {
|
|
171
|
-
from.* = side.last_used_move;
|
|
172
162
|
if (showdown) {
|
|
173
|
-
if (foe_choice.type == .Switch) from.* = null;
|
|
174
163
|
// Pokémon Showdown overwrites Mirror Move with whatever was selected - really this
|
|
175
164
|
// should set side.last_selected_move = last.id to reuse Mirror Move and fail in order
|
|
176
165
|
// to satisfy the conditions of the Desync Clause Mod. However, because Binding is still
|
|
@@ -206,7 +195,7 @@ fn selectMove(
|
|
|
206
195
|
if (choice.data == 0) {
|
|
207
196
|
const struggle = ok: {
|
|
208
197
|
for (side.active.moves, 0..) |move, i| {
|
|
209
|
-
if (move.pp > 0 and volatiles.
|
|
198
|
+
if (move.pp > 0 and volatiles.disable_move != i + 1) break :ok false;
|
|
210
199
|
}
|
|
211
200
|
break :ok true;
|
|
212
201
|
};
|
|
@@ -225,7 +214,7 @@ fn saveMove(battle: anytype, player: Player, choice: ?Choice) void {
|
|
|
225
214
|
if (c.data == 0) {
|
|
226
215
|
side.last_selected_move = .Struggle;
|
|
227
216
|
} else {
|
|
228
|
-
assert(showdown or side.active.volatiles.
|
|
217
|
+
assert(showdown or side.active.volatiles.disable_move != c.data);
|
|
229
218
|
const move = side.active.move(c.data);
|
|
230
219
|
// You cannot *select* a move with 0 PP (except on Pokémon Showdown where that is
|
|
231
220
|
// sometimes required...), but a 0 PP move can be used automatically
|
|
@@ -336,12 +325,10 @@ fn doTurn(
|
|
|
336
325
|
player_choice: Choice,
|
|
337
326
|
player_rewrap: bool,
|
|
338
327
|
player_skip: bool,
|
|
339
|
-
player_from: ?Move,
|
|
340
328
|
foe_player: Player,
|
|
341
329
|
foe_choice: Choice,
|
|
342
330
|
foe_rewrap: bool,
|
|
343
331
|
foe_skip: bool,
|
|
344
|
-
foe_from: ?Move,
|
|
345
332
|
options: anytype,
|
|
346
333
|
) !?Result {
|
|
347
334
|
assert(player_choice.type != .Pass);
|
|
@@ -354,7 +341,6 @@ fn doTurn(
|
|
|
354
341
|
player_choice,
|
|
355
342
|
player_rewrap,
|
|
356
343
|
player_skip,
|
|
357
|
-
player_from,
|
|
358
344
|
&residual,
|
|
359
345
|
options,
|
|
360
346
|
)) |r| return r;
|
|
@@ -377,7 +363,6 @@ fn doTurn(
|
|
|
377
363
|
foe_choice,
|
|
378
364
|
foe_rewrap,
|
|
379
365
|
foe_skip,
|
|
380
|
-
foe_from,
|
|
381
366
|
&residual,
|
|
382
367
|
options,
|
|
383
368
|
)) |r| return r;
|
|
@@ -405,7 +390,6 @@ fn executeMove(
|
|
|
405
390
|
choice: Choice,
|
|
406
391
|
rewrap: bool,
|
|
407
392
|
skip: bool,
|
|
408
|
-
from: ?Move,
|
|
409
393
|
residual: *bool,
|
|
410
394
|
options: anytype,
|
|
411
395
|
) !?Result {
|
|
@@ -440,8 +424,10 @@ fn executeMove(
|
|
|
440
424
|
mslot = @intCast(battle.lastMove(player).index);
|
|
441
425
|
const stored = side.stored();
|
|
442
426
|
// GLITCH: Struggle bypass PP underflow via Hyper Beam / Trapping-switch auto selection
|
|
443
|
-
auto = side.
|
|
444
|
-
|
|
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);
|
|
445
431
|
// If it wasn't Hyper Beam or the continuation of a move effect then we must have just
|
|
446
432
|
// thawed, in which case we will desync unless the last_selected_move happened to be at
|
|
447
433
|
// index 1 and the current Pokémon has the same move in its first slot.
|
|
@@ -463,7 +449,7 @@ fn executeMove(
|
|
|
463
449
|
|
|
464
450
|
var skip_can = false;
|
|
465
451
|
var skip_pp = false;
|
|
466
|
-
switch (try beforeMove(battle, player, skip,
|
|
452
|
+
switch (try beforeMove(battle, player, skip, residual, options)) {
|
|
467
453
|
.done => return null,
|
|
468
454
|
.skip_can => skip_can = true,
|
|
469
455
|
.skip_pp => skip_pp = true,
|
|
@@ -471,9 +457,9 @@ fn executeMove(
|
|
|
471
457
|
.err => return @as(?Result, Result.Error),
|
|
472
458
|
}
|
|
473
459
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
460
|
+
const can = skip_can or
|
|
461
|
+
try canMove(battle, player, mslot, auto, skip_pp, .None, residual, options);
|
|
462
|
+
if (!can) return null;
|
|
477
463
|
|
|
478
464
|
return doMove(battle, player, mslot, rewrap, auto, residual, options);
|
|
479
465
|
}
|
|
@@ -484,7 +470,6 @@ fn beforeMove(
|
|
|
484
470
|
battle: anytype,
|
|
485
471
|
player: Player,
|
|
486
472
|
skip: bool,
|
|
487
|
-
from: ?Move,
|
|
488
473
|
residual: *bool,
|
|
489
474
|
options: anytype,
|
|
490
475
|
) !BeforeMove {
|
|
@@ -545,16 +530,16 @@ fn beforeMove(
|
|
|
545
530
|
return .done;
|
|
546
531
|
}
|
|
547
532
|
|
|
548
|
-
if (volatiles.
|
|
533
|
+
if (volatiles.disable_duration > 0) {
|
|
549
534
|
if (options.calc.modify(player, .disable)) |extend| {
|
|
550
|
-
if (!extend) volatiles.
|
|
535
|
+
if (!extend) volatiles.disable_duration = 0;
|
|
551
536
|
} else {
|
|
552
|
-
volatiles.
|
|
537
|
+
volatiles.disable_duration -= 1;
|
|
553
538
|
}
|
|
554
|
-
try options.chance.disable(player, volatiles.
|
|
539
|
+
try options.chance.disable(player, volatiles.disable_duration);
|
|
555
540
|
|
|
556
|
-
if (volatiles.
|
|
557
|
-
volatiles.
|
|
541
|
+
if (volatiles.disable_duration == 0) {
|
|
542
|
+
volatiles.disable_move = 0;
|
|
558
543
|
try log.end(ident, .Disable);
|
|
559
544
|
}
|
|
560
545
|
}
|
|
@@ -691,7 +676,6 @@ fn beforeMove(
|
|
|
691
676
|
|
|
692
677
|
if (volatiles.Thrashing) {
|
|
693
678
|
assert(volatiles.attacks > 0);
|
|
694
|
-
assert(from != null);
|
|
695
679
|
|
|
696
680
|
if (options.calc.modify(player, .attacking)) |extend| {
|
|
697
681
|
if (!extend) volatiles.attacks = 0;
|
|
@@ -703,7 +687,7 @@ fn beforeMove(
|
|
|
703
687
|
if (!showdown and handleThrashing(battle, active, player, options)) {
|
|
704
688
|
try log.start(battle.active(player), .ConfusionSilent);
|
|
705
689
|
}
|
|
706
|
-
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()));
|
|
707
691
|
if (showdown) {
|
|
708
692
|
// This shouldn't actually set last_used_move, but Pokémon Showdown sets last
|
|
709
693
|
// used in useMove and doesn't have the notion of skipping canMove semantics
|
|
@@ -725,12 +709,7 @@ fn beforeMove(
|
|
|
725
709
|
}
|
|
726
710
|
try options.chance.binding(player, volatiles.attacks);
|
|
727
711
|
|
|
728
|
-
try log.move(
|
|
729
|
-
battle.active(player),
|
|
730
|
-
side.last_selected_move,
|
|
731
|
-
battle.active(player.foe()),
|
|
732
|
-
side.last_selected_move,
|
|
733
|
-
);
|
|
712
|
+
try log.move(battle.active(player), side.last_selected_move, battle.active(player.foe()));
|
|
734
713
|
if (showdown or battle.last_damage != 0) {
|
|
735
714
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
736
715
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -750,7 +729,7 @@ fn canMove(
|
|
|
750
729
|
mslot: u4,
|
|
751
730
|
auto: bool,
|
|
752
731
|
skip_pp: bool,
|
|
753
|
-
from:
|
|
732
|
+
from: Move,
|
|
754
733
|
residual: *bool,
|
|
755
734
|
options: anytype,
|
|
756
735
|
) !bool {
|
|
@@ -758,33 +737,22 @@ fn canMove(
|
|
|
758
737
|
const player_ident = battle.active(player);
|
|
759
738
|
const move = Move.get(side.last_selected_move);
|
|
760
739
|
|
|
761
|
-
const foe_last = battle.foe(player).last_used_move;
|
|
762
|
-
const special = from != null and (from.? == .Metronome and
|
|
763
|
-
(from.? == .MirrorMove and !(foe_last == .None or foe_last == .MirrorMove)));
|
|
764
|
-
|
|
765
740
|
var skip = skip_pp;
|
|
766
741
|
if (side.active.volatiles.Charging) {
|
|
767
742
|
side.active.volatiles.Charging = false;
|
|
768
743
|
side.active.volatiles.Invulnerable = false;
|
|
769
744
|
} else if (move.effect == .Charge) {
|
|
770
|
-
try options.log.
|
|
745
|
+
try options.log.moveFrom(player_ident, side.last_selected_move, .{}, from);
|
|
771
746
|
setCounterable(battle, player, side, move);
|
|
772
747
|
try Effects.charge(battle, player, options);
|
|
773
748
|
return false;
|
|
774
749
|
}
|
|
775
750
|
|
|
776
|
-
|
|
751
|
+
side.last_used_move = side.last_selected_move;
|
|
777
752
|
if (!skip) decrementPP(side, mslot, auto);
|
|
778
753
|
|
|
779
754
|
const target = if (move.target == .Self) player else player.foe();
|
|
780
|
-
|
|
781
|
-
// don't have space to be able to track it in all circumstances and thus usually try to infer it
|
|
782
|
-
// from side.last_used_move. However, this gets reset by the opponent switching or fainting,
|
|
783
|
-
// meaning sometimes we lose this information. from is still set because it not being null is
|
|
784
|
-
// relevant for control flow, but we don't have anything useful to put in the log. This is
|
|
785
|
-
// regrettable, but this information is more "nice to have" than required
|
|
786
|
-
const f = if (from != null and from.? == .None) null else from;
|
|
787
|
-
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);
|
|
788
756
|
setCounterable(battle, player, side, move);
|
|
789
757
|
|
|
790
758
|
if (move.effect.onBegin()) {
|
|
@@ -822,12 +790,14 @@ fn decrementPP(side: *Side, mslot: u4, auto: bool) void {
|
|
|
822
790
|
assert(!volatiles.Rage and !volatiles.Thrashing and !volatiles.MultiHit);
|
|
823
791
|
if (volatiles.Bide) return;
|
|
824
792
|
|
|
825
|
-
|
|
826
|
-
|
|
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;
|
|
827
796
|
if (volatiles.Transform) return;
|
|
828
797
|
|
|
829
|
-
|
|
830
|
-
|
|
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;
|
|
831
801
|
assert(active.move(mslot).pp == side.stored().move(mslot).pp);
|
|
832
802
|
}
|
|
833
803
|
|
|
@@ -1470,9 +1440,8 @@ fn checkFaint(
|
|
|
1470
1440
|
const foe_fainted = foe.stored().hp == 0;
|
|
1471
1441
|
|
|
1472
1442
|
const player_out = findFirstAlive(side) == 0;
|
|
1473
|
-
const foe_out = findFirstAlive(foe) == 0;
|
|
1474
|
-
const
|
|
1475
|
-
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;
|
|
1476
1445
|
|
|
1477
1446
|
if (try faint(battle, player, !(more or foe_fainted), options)) |r| return r;
|
|
1478
1447
|
if (foe_fainted) if (try faint(battle, player.foe(), !more, options)) |r| return r;
|
|
@@ -1480,7 +1449,7 @@ fn checkFaint(
|
|
|
1480
1449
|
assert(!side.active.volatiles.MultiHit);
|
|
1481
1450
|
assert(!foe.active.volatiles.MultiHit);
|
|
1482
1451
|
|
|
1483
|
-
if (
|
|
1452
|
+
if (player_out and foe_out) {
|
|
1484
1453
|
try options.log.tie();
|
|
1485
1454
|
return Result.Tie;
|
|
1486
1455
|
} else if (player_out) {
|
|
@@ -1666,9 +1635,9 @@ fn checkEBC(battle: anytype) bool {
|
|
|
1666
1635
|
}
|
|
1667
1636
|
|
|
1668
1637
|
fn disabled(side: *Side, ident: ID, options: anytype) !bool {
|
|
1669
|
-
if (side.active.volatiles.
|
|
1638
|
+
if (side.active.volatiles.disable_move != 0) {
|
|
1670
1639
|
// A Pokémon that transforms after being disabled may end up with less move slots
|
|
1671
|
-
const m = side.active.moves[side.active.volatiles.
|
|
1640
|
+
const m = side.active.moves[side.active.volatiles.disable_move - 1].id;
|
|
1672
1641
|
// side.last_selected_move can be Struggle here on Pokemon Showdown we need an extra check
|
|
1673
1642
|
const last = if (showdown and m == .Bide and side.active.volatiles.Bide)
|
|
1674
1643
|
m
|
|
@@ -1896,7 +1865,7 @@ pub const Effects = struct {
|
|
|
1896
1865
|
try options.chance.commit(player, .hit);
|
|
1897
1866
|
}
|
|
1898
1867
|
|
|
1899
|
-
if (volatiles.
|
|
1868
|
+
if (volatiles.disable_move != 0) {
|
|
1900
1869
|
try options.log.fail(foe_ident, .None);
|
|
1901
1870
|
return null;
|
|
1902
1871
|
}
|
|
@@ -1921,18 +1890,17 @@ pub const Effects = struct {
|
|
|
1921
1890
|
return Result.Error;
|
|
1922
1891
|
}
|
|
1923
1892
|
|
|
1924
|
-
volatiles.
|
|
1893
|
+
volatiles.disable_move =
|
|
1925
1894
|
@intCast(try Rolls.moveSlot(battle, player, &foe.active.moves, n, options));
|
|
1926
|
-
volatiles.
|
|
1895
|
+
volatiles.disable_duration = Rolls.disableDuration(battle, player, options);
|
|
1927
1896
|
|
|
1928
|
-
const id = foe.active.move(volatiles.
|
|
1897
|
+
const id = foe.active.move(volatiles.disable_move).id;
|
|
1929
1898
|
try options.log.startEffect(foe_ident, .Disable, id);
|
|
1930
1899
|
return null;
|
|
1931
1900
|
}
|
|
1932
1901
|
|
|
1933
1902
|
fn drainHP(battle: anytype, player: Player, options: anytype) !void {
|
|
1934
|
-
var
|
|
1935
|
-
var stored = side.stored();
|
|
1903
|
+
var stored = battle.side(player).stored();
|
|
1936
1904
|
|
|
1937
1905
|
if (battle.last_damage == 0) {
|
|
1938
1906
|
assert(showdown);
|
|
@@ -2689,9 +2657,9 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2689
2657
|
|
|
2690
2658
|
options.chance.clearDurations(who, clear);
|
|
2691
2659
|
|
|
2692
|
-
if (volatiles.
|
|
2693
|
-
volatiles.
|
|
2694
|
-
volatiles.
|
|
2660
|
+
if (volatiles.disable_move != 0) {
|
|
2661
|
+
volatiles.disable_move = 0;
|
|
2662
|
+
volatiles.disable_duration = 0;
|
|
2695
2663
|
try log.end(ident, .DisableSilent);
|
|
2696
2664
|
}
|
|
2697
2665
|
if (volatiles.Confusion) {
|
|
@@ -2756,7 +2724,7 @@ pub const Rolls = struct {
|
|
|
2756
2724
|
return crit;
|
|
2757
2725
|
}
|
|
2758
2726
|
|
|
2759
|
-
inline fn damage(battle: anytype, player:
|
|
2727
|
+
inline fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2760
2728
|
const roll = if (options.calc.overridden(player, .damage)) |val|
|
|
2761
2729
|
val
|
|
2762
2730
|
else roll: {
|
|
@@ -2863,11 +2831,9 @@ pub const Rolls = struct {
|
|
|
2863
2831
|
inline fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
2864
2832
|
const move: Move = if (options.calc.overridden(player, .metronome)) |val|
|
|
2865
2833
|
val
|
|
2866
|
-
else if (showdown)
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
break :move @enumFromInt(r + mod);
|
|
2870
|
-
} else move: {
|
|
2834
|
+
else if (showdown)
|
|
2835
|
+
Move.METRONOME[battle.rng.range(u8, 0, Move.METRONOME.len)]
|
|
2836
|
+
else move: {
|
|
2871
2837
|
while (true) {
|
|
2872
2838
|
const r = battle.rng.next();
|
|
2873
2839
|
if (r == 0 or r == @intFromEnum(Move.Metronome)) continue;
|
|
@@ -2901,7 +2867,7 @@ pub const Rolls = struct {
|
|
|
2901
2867
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2902
2868
|
@intCast(val)
|
|
2903
2869
|
else if (showdown)
|
|
2904
|
-
|
|
2870
|
+
battle.rng.range(u3, 1, 8)
|
|
2905
2871
|
else duration: {
|
|
2906
2872
|
while (true) {
|
|
2907
2873
|
const r = battle.rng.next() & 7;
|
|
@@ -2918,7 +2884,7 @@ pub const Rolls = struct {
|
|
|
2918
2884
|
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2919
2885
|
val
|
|
2920
2886
|
else if (showdown)
|
|
2921
|
-
|
|
2887
|
+
battle.rng.range(u4, 1, 9)
|
|
2922
2888
|
else
|
|
2923
2889
|
@intCast((battle.rng.next() & 7) + 1);
|
|
2924
2890
|
|
|
@@ -2931,7 +2897,7 @@ pub const Rolls = struct {
|
|
|
2931
2897
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2932
2898
|
@intCast(val)
|
|
2933
2899
|
else if (showdown)
|
|
2934
|
-
|
|
2900
|
+
battle.rng.range(u3, 2, 6)
|
|
2935
2901
|
else
|
|
2936
2902
|
@intCast((battle.rng.next() & 3) + 2);
|
|
2937
2903
|
|
|
@@ -2944,7 +2910,7 @@ pub const Rolls = struct {
|
|
|
2944
2910
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2945
2911
|
@intCast(val)
|
|
2946
2912
|
else if (showdown)
|
|
2947
|
-
|
|
2913
|
+
battle.rng.range(u3, 2, 4)
|
|
2948
2914
|
else
|
|
2949
2915
|
@intCast((battle.rng.next() & 1) + 2);
|
|
2950
2916
|
|
|
@@ -2953,6 +2919,8 @@ pub const Rolls = struct {
|
|
|
2953
2919
|
return duration;
|
|
2954
2920
|
}
|
|
2955
2921
|
|
|
2922
|
+
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
2923
|
+
|
|
2956
2924
|
inline fn distribution(
|
|
2957
2925
|
battle: anytype,
|
|
2958
2926
|
comptime effect: Move.Effect,
|
|
@@ -2963,7 +2931,7 @@ pub const Rolls = struct {
|
|
|
2963
2931
|
const n: u3 = if (options.calc.overridden(player, roll)) |val|
|
|
2964
2932
|
@intCast(val)
|
|
2965
2933
|
else if (showdown)
|
|
2966
|
-
DISTRIBUTION[battle.rng.range(
|
|
2934
|
+
DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
|
|
2967
2935
|
else n: {
|
|
2968
2936
|
const r = (battle.rng.next() & 3);
|
|
2969
2937
|
break :n @intCast((if (r < 2) r else battle.rng.next() & 3) + 2);
|
|
@@ -3010,10 +2978,9 @@ pub const Rolls = struct {
|
|
|
3010
2978
|
while (i < moves.len and r > 0) : (i += 1) {
|
|
3011
2979
|
if (moves[i].pp > 0) {
|
|
3012
2980
|
r -= 1;
|
|
3013
|
-
if (r == 0) break;
|
|
2981
|
+
if (r == 0) break :slot @intCast(i + 1);
|
|
3014
2982
|
}
|
|
3015
2983
|
}
|
|
3016
|
-
break :slot @intCast(i + 1);
|
|
3017
2984
|
}
|
|
3018
2985
|
}
|
|
3019
2986
|
|
|
@@ -3129,7 +3096,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3129
3096
|
if (m.id == if (active.volatiles.Bide) .Bide else side.last_selected_move) {
|
|
3130
3097
|
// Pokémon Showdown displays Struggle if limited to Bide but unable to pick
|
|
3131
3098
|
const struggle =
|
|
3132
|
-
m.id == .Bide and (m.pp == 0 or active.volatiles.
|
|
3099
|
+
m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
|
|
3133
3100
|
const s = if (struggle) 0 else slot;
|
|
3134
3101
|
out[n] = .{ .type = .Move, .data = s };
|
|
3135
3102
|
n += 1;
|
|
@@ -3144,7 +3111,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3144
3111
|
const m = active.moves[slot - 1];
|
|
3145
3112
|
if (m.id == .None) break;
|
|
3146
3113
|
if (m.pp == 0) continue;
|
|
3147
|
-
if (active.volatiles.
|
|
3114
|
+
if (active.volatiles.disable_move == slot) continue;
|
|
3148
3115
|
out[n] = .{ .type = .Move, .data = slot };
|
|
3149
3116
|
n += 1;
|
|
3150
3117
|
}
|
package/src/lib/gen2/README.md
CHANGED
|
@@ -39,7 +39,7 @@ The following information is required to simulate a Generation II Pokémon battl
|
|
|
39
39
|
| `volatiles.rollout` | `PlayerRolloutCount` | `volatiles.rollout.hitCount` |
|
|
40
40
|
| `volatiles.confusion` | `PlayerConfuseCount` | `volatiles.confusion.duration` |
|
|
41
41
|
| `volatiles.toxic` | `PlayerToxicCount` | `volatiles.residualdmg.counter` |
|
|
42
|
-
| `volatiles.
|
|
42
|
+
| `volatiles.disable` | `PlayerDisableCount` | `moveSlots.disabled` |
|
|
43
43
|
| `volatiles.encore` | `PlayerEncoreCount` | `volatiles.encore.duration` |
|
|
44
44
|
| `volatiles.perish_song` | `PlayerPerishCount` | `volatiles.perishsong.duration` |
|
|
45
45
|
| `volatiles.fury_cutter` | `PlayerFuryCutterCount` | `volatiles.furycutter.multiplier` |
|