@pkmn/engine 0.1.0-dev.bcb16a6c → 0.1.0-dev.c42deb42
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/package.json +1 -1
- package/src/lib/common/optional.zig +4 -6
- package/src/lib/common/protocol.zig +23 -18
- package/src/lib/gen1/chance.zig +43 -41
- package/src/lib/gen1/data/moves.zig +14 -0
- package/src/lib/gen1/helpers.zig +1 -16
- package/src/lib/gen1/mechanics.zig +29 -62
- package/src/lib/gen2/data/moves.zig +27 -10
- package/src/lib/gen2/data.zig +6 -2
- package/src/lib/gen2/mechanics.zig +1143 -81
package/package.json
CHANGED
|
@@ -15,19 +15,17 @@ pub fn Optional(comptime T: type) type {
|
|
|
15
15
|
|
|
16
16
|
enumFields[0] = .{ .name = "None", .value = 0 };
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
inline for (fields) |field| {
|
|
18
|
+
inline for (fields, 1..) |field, i| {
|
|
20
19
|
assert(!std.mem.eql(u8, field.name, "None"));
|
|
21
|
-
enumFields[i
|
|
20
|
+
enumFields[i] = .{
|
|
22
21
|
.name = field.name,
|
|
23
|
-
.value = i
|
|
22
|
+
.value = i,
|
|
24
23
|
};
|
|
25
|
-
i += 1;
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
return @Type(.{
|
|
29
27
|
.Enum = .{
|
|
30
|
-
.tag_type = std.math.IntFittingRange(0,
|
|
28
|
+
.tag_type = std.math.IntFittingRange(0, fields.len),
|
|
31
29
|
.fields = &enumFields,
|
|
32
30
|
.decls = &decls,
|
|
33
31
|
.is_exhaustive = true,
|
|
@@ -222,6 +222,7 @@ pub const Start = enum(u8) {
|
|
|
222
222
|
Disable,
|
|
223
223
|
Mimic,
|
|
224
224
|
|
|
225
|
+
Attract,
|
|
225
226
|
Nightmare,
|
|
226
227
|
Curse,
|
|
227
228
|
Foresight,
|
|
@@ -305,7 +306,7 @@ pub fn Log(comptime Writer: type) type {
|
|
|
305
306
|
|
|
306
307
|
pub const Error = Writer.Error;
|
|
307
308
|
|
|
308
|
-
pub fn move(self: Self, source: ID, m: anytype, target: ID
|
|
309
|
+
pub fn move(self: Self, source: ID, m: anytype, target: ID) Error!void {
|
|
309
310
|
if (!enabled) return;
|
|
310
311
|
|
|
311
312
|
assert(m != .None);
|
|
@@ -314,19 +315,24 @@ pub fn Log(comptime Writer: type) type {
|
|
|
314
315
|
@as(u8, @bitCast(source)),
|
|
315
316
|
@intFromEnum(m),
|
|
316
317
|
@as(u8, @bitCast(target)),
|
|
318
|
+
@intFromEnum(Move.None),
|
|
317
319
|
});
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
pub fn moveFrom(self: Self, source: ID, m: anytype, target: ID, from: anytype) Error!void {
|
|
323
|
+
if (!enabled) return;
|
|
324
|
+
|
|
325
|
+
assert(m != .None);
|
|
326
|
+
try self.writer.writeAll(&.{
|
|
327
|
+
@intFromEnum(ArgType.Move),
|
|
328
|
+
@as(u8, @bitCast(source)),
|
|
329
|
+
@intFromEnum(m),
|
|
330
|
+
@as(u8, @bitCast(target)),
|
|
329
331
|
});
|
|
332
|
+
try self.writer.writeAll(if (@intFromEnum(from) == 0)
|
|
333
|
+
&.{@intFromEnum(Move.None)}
|
|
334
|
+
else
|
|
335
|
+
&.{ @intFromEnum(Move.From), @intFromEnum(from) });
|
|
330
336
|
}
|
|
331
337
|
|
|
332
338
|
pub fn switched(self: Self, ident: ID, pokemon: anytype) Error!void {
|
|
@@ -1288,22 +1294,21 @@ fn formatter(comptime gen: anytype, kind: Kind, byte: u8) []const u8 {
|
|
|
1288
1294
|
}
|
|
1289
1295
|
|
|
1290
1296
|
test "|move|" {
|
|
1291
|
-
try log.move(p2.ident(4), M1.Thunderbolt, p1.ident(5)
|
|
1297
|
+
try log.move(p2.ident(4), M1.Thunderbolt, p1.ident(5));
|
|
1292
1298
|
try expectLog1(
|
|
1293
1299
|
&.{ N(ArgType.Move), 0b1100, N(M1.Thunderbolt), 0b0101, N(Move.None) },
|
|
1294
1300
|
buf[0..5],
|
|
1295
1301
|
);
|
|
1296
1302
|
stream.reset();
|
|
1297
1303
|
|
|
1298
|
-
try log.
|
|
1299
|
-
const wrap = N(M1.Wrap);
|
|
1304
|
+
try log.moveFrom(p2.ident(4), M1.Pound, p1.ident(5), M1.Metronome);
|
|
1300
1305
|
try expectLog1(
|
|
1301
|
-
&.{ N(ArgType.Move), 0b1100,
|
|
1306
|
+
&.{ N(ArgType.Move), 0b1100, N(M1.Pound), 0b0101, N(Move.From), N(M1.Metronome) },
|
|
1302
1307
|
buf[0..6],
|
|
1303
1308
|
);
|
|
1304
1309
|
stream.reset();
|
|
1305
1310
|
|
|
1306
|
-
try log.move(p2.ident(4), M1.SkullBash, .{}
|
|
1311
|
+
try log.move(p2.ident(4), M1.SkullBash, .{});
|
|
1307
1312
|
try log.laststill();
|
|
1308
1313
|
try expectLog1(
|
|
1309
1314
|
&.{
|
|
@@ -1318,7 +1323,7 @@ test "|move|" {
|
|
|
1318
1323
|
);
|
|
1319
1324
|
stream.reset();
|
|
1320
1325
|
|
|
1321
|
-
try log.move(p2.ident(4), M1.Tackle, p1.ident(5)
|
|
1326
|
+
try log.move(p2.ident(4), M1.Tackle, p1.ident(5));
|
|
1322
1327
|
try log.lastmiss();
|
|
1323
1328
|
try expectLog1(
|
|
1324
1329
|
&.{
|
package/src/lib/gen1/chance.zig
CHANGED
|
@@ -14,12 +14,14 @@ const util = @import("../common/util.zig");
|
|
|
14
14
|
const Player = @import("../common/data.zig").Player;
|
|
15
15
|
const Optional = @import("../common/optional.zig").Optional;
|
|
16
16
|
|
|
17
|
-
const
|
|
18
|
-
const MoveSlot = @import("data.zig").MoveSlot;
|
|
17
|
+
const data = @import("./data.zig");
|
|
19
18
|
|
|
20
19
|
const enabled = options.chance;
|
|
21
20
|
const showdown = options.showdown;
|
|
22
21
|
|
|
22
|
+
const Move = data.Move;
|
|
23
|
+
const MoveSlot = data.MoveSlot;
|
|
24
|
+
|
|
23
25
|
/// Actions taken by a hypothetical "chance player" that convey information about which RNG events
|
|
24
26
|
/// were observed during a Generation I battle `update`. This can additionally be provided as input
|
|
25
27
|
/// to the `update` call to override the normal behavior of the RNG in order to force specific
|
|
@@ -514,22 +516,6 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
514
516
|
}
|
|
515
517
|
}
|
|
516
518
|
|
|
517
|
-
pub fn disable(self: *Self, player: Player, turns: u4) Error!void {
|
|
518
|
-
if (!enabled) return;
|
|
519
|
-
|
|
520
|
-
var durations = &self.actions.get(player).durations;
|
|
521
|
-
const n = durations.disable;
|
|
522
|
-
if (turns == 0) {
|
|
523
|
-
assert(n >= 1 and n <= 8);
|
|
524
|
-
if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
|
|
525
|
-
durations.disable = 0;
|
|
526
|
-
} else {
|
|
527
|
-
assert(n >= 1 and n < 8);
|
|
528
|
-
try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
|
|
529
|
-
durations.disable += 1;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
519
|
pub fn confusion(self: *Self, player: Player, turns: u4) Error!void {
|
|
534
520
|
if (!enabled) return;
|
|
535
521
|
|
|
@@ -546,6 +532,22 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
546
532
|
}
|
|
547
533
|
}
|
|
548
534
|
|
|
535
|
+
pub fn disable(self: *Self, player: Player, turns: u4) Error!void {
|
|
536
|
+
if (!enabled) return;
|
|
537
|
+
|
|
538
|
+
var durations = &self.actions.get(player).durations;
|
|
539
|
+
const n = durations.disable;
|
|
540
|
+
if (turns == 0) {
|
|
541
|
+
assert(n >= 1 and n <= 8);
|
|
542
|
+
if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
|
|
543
|
+
durations.disable = 0;
|
|
544
|
+
} else {
|
|
545
|
+
assert(n >= 1 and n < 8);
|
|
546
|
+
try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
|
|
547
|
+
durations.disable += 1;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
549
551
|
pub fn attacking(self: *Self, player: Player, turns: u4) Error!void {
|
|
550
552
|
if (!enabled) return;
|
|
551
553
|
|
|
@@ -593,7 +595,7 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
593
595
|
pub fn metronome(self: *Self, player: Player, move: Move) Error!void {
|
|
594
596
|
if (!enabled) return;
|
|
595
597
|
|
|
596
|
-
try self.probability.update(1, Move.
|
|
598
|
+
try self.probability.update(1, Move.METRONOME.len);
|
|
597
599
|
self.actions.get(player).metronome = move;
|
|
598
600
|
}
|
|
599
601
|
};
|
|
@@ -832,28 +834,6 @@ test "Chance.sleep" {
|
|
|
832
834
|
}
|
|
833
835
|
}
|
|
834
836
|
|
|
835
|
-
test "Chance.disable" {
|
|
836
|
-
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
837
|
-
|
|
838
|
-
for ([_]u8{ 8, 7, 6, 5, 4, 3, 2, 1 }, 1..9) |d, i| {
|
|
839
|
-
chance.actions.p1.durations.disable = @intCast(i);
|
|
840
|
-
try chance.disable(.P1, 0);
|
|
841
|
-
try expectProbability(&chance.probability, 1, d);
|
|
842
|
-
try expectValue(@as(u4, 0), chance.actions.p1.durations.disable);
|
|
843
|
-
|
|
844
|
-
chance.reset();
|
|
845
|
-
|
|
846
|
-
if (i < 8) {
|
|
847
|
-
chance.actions.p1.durations.disable = @intCast(i);
|
|
848
|
-
try chance.disable(.P1, 1);
|
|
849
|
-
try expectProbability(&chance.probability, d - 1, d);
|
|
850
|
-
try expectValue(@as(u4, @intCast(i)) + 1, chance.actions.p1.durations.disable);
|
|
851
|
-
|
|
852
|
-
chance.reset();
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
|
|
857
837
|
test "Chance.confusion" {
|
|
858
838
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
859
839
|
|
|
@@ -878,6 +858,28 @@ test "Chance.confusion" {
|
|
|
878
858
|
}
|
|
879
859
|
}
|
|
880
860
|
|
|
861
|
+
test "Chance.disable" {
|
|
862
|
+
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
863
|
+
|
|
864
|
+
for ([_]u8{ 8, 7, 6, 5, 4, 3, 2, 1 }, 1..9) |d, i| {
|
|
865
|
+
chance.actions.p1.durations.disable = @intCast(i);
|
|
866
|
+
try chance.disable(.P1, 0);
|
|
867
|
+
try expectProbability(&chance.probability, 1, d);
|
|
868
|
+
try expectValue(@as(u4, 0), chance.actions.p1.durations.disable);
|
|
869
|
+
|
|
870
|
+
chance.reset();
|
|
871
|
+
|
|
872
|
+
if (i < 8) {
|
|
873
|
+
chance.actions.p1.durations.disable = @intCast(i);
|
|
874
|
+
try chance.disable(.P1, 1);
|
|
875
|
+
try expectProbability(&chance.probability, d - 1, d);
|
|
876
|
+
try expectValue(@as(u4, @intCast(i)) + 1, chance.actions.p1.durations.disable);
|
|
877
|
+
|
|
878
|
+
chance.reset();
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
881
883
|
test "Chance.attacking" {
|
|
882
884
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
883
885
|
|
|
@@ -1838,6 +1838,20 @@ pub const Move = enum(u8) {
|
|
|
1838
1838
|
/// The number of Pokémon moves in this generation.
|
|
1839
1839
|
pub const size = 165;
|
|
1840
1840
|
|
|
1841
|
+
/// Pokémon moves that can be called by Metronome in this generation.
|
|
1842
|
+
pub const METRONOME = init: {
|
|
1843
|
+
var num = 0;
|
|
1844
|
+
var moves: [163]Move = undefined;
|
|
1845
|
+
for (1..Move.size) |i| {
|
|
1846
|
+
if (i != @intFromEnum(Move.Metronome)) {
|
|
1847
|
+
moves[num] = @enumFromInt(i);
|
|
1848
|
+
num += 1;
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
assert(num == moves.len);
|
|
1852
|
+
break :init moves;
|
|
1853
|
+
};
|
|
1854
|
+
|
|
1841
1855
|
/// Returns the `Data` corresponding to the move.
|
|
1842
1856
|
pub inline fn get(id: Move) Data {
|
|
1843
1857
|
assert(id != .None and id != .SKIP_TURN);
|
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,8 +7,6 @@ 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;
|
|
@@ -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
|
|
@@ -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 {
|
|
@@ -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()) {
|
|
@@ -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
|
|