@pkmn/engine 0.1.0-dev.b3197aa3 → 0.1.0-dev.b396399c
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/LICENSE +1 -1
- package/README.md +17 -53
- package/build/pkg/addon/wasm.js.map +1 -1
- package/build/pkg/addon.d.ts +4 -4
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/data.js.map +1 -1
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/protocol.d.ts +11 -2
- package/build/pkg/protocol.js.map +1 -1
- package/build/tools/debug.js +37 -3
- package/build/tools/debug.js.map +1 -1
- package/build/tools/display/debug.jsx +1 -87
- package/build/tools/display/debug.jsx.map +1 -1
- package/build/tools/display/ui.jsx +3 -2
- package/build/tools/display/ui.jsx.map +1 -1
- package/build/tools/display/util.js +125 -1
- package/build/tools/display/util.js.map +1 -1
- package/build.zig +141 -232
- package/package.json +22 -21
- package/src/bin/install-pkmn-engine +22 -7
- package/src/lib/bindings/c.zig +27 -82
- package/src/lib/bindings/node.zig +7 -7
- package/src/lib/bindings/wasm.zig +6 -15
- package/src/lib/common/array.zig +5 -12
- package/src/lib/common/debug.zig +33 -59
- package/src/lib/common/js.zig +3 -5
- package/src/lib/common/optional.zig +11 -21
- package/src/lib/common/protocol.zig +224 -189
- package/src/lib/common/rational.zig +18 -38
- package/src/lib/common/util.zig +14 -46
- package/src/lib/gen1/calc.zig +121 -108
- package/src/lib/gen1/chance.zig +112 -75
- package/src/lib/gen1/data/types.zig +1 -1
- package/src/lib/gen1/data.zig +5 -5
- package/src/lib/gen1/helpers.zig +8 -10
- package/src/lib/gen1/mechanics.zig +31 -31
- package/src/lib/gen2/calc.zig +3 -6
- package/src/lib/gen2/chance.zig +12 -18
- package/src/lib/gen2/data.zig +5 -5
- package/src/lib/gen2/helpers.zig +4 -6
- package/src/lib/gen2/mechanics.zig +16 -16
- package/src/lib/napi.h +2 -0
- package/src/lib/node.zig +1 -1
- package/src/lib/pkmn.zig +91 -17
- package/src/lib/wasm.zig +2 -6
- package/src/tools/display/dom.ts +40 -25
- package/src/lib/common/napi.zig +0 -4
|
@@ -84,11 +84,11 @@ fn start(battle: anytype, options: anytype) !Result {
|
|
|
84
84
|
|
|
85
85
|
const p1_slot = findFirstAlive(p1);
|
|
86
86
|
assert(!showdown or p1_slot == 1);
|
|
87
|
-
if (p1_slot == 0) return if (findFirstAlive(p2) == 0)
|
|
87
|
+
if (p1_slot == 0) return if (findFirstAlive(p2) == 0) .Tie else .Lose;
|
|
88
88
|
|
|
89
89
|
const p2_slot = findFirstAlive(p2);
|
|
90
90
|
assert(!showdown or p2_slot == 1);
|
|
91
|
-
if (p2_slot == 0) return
|
|
91
|
+
if (p2_slot == 0) return .Win;
|
|
92
92
|
|
|
93
93
|
try switchIn(battle, .P1, p1_slot, true, options);
|
|
94
94
|
try switchIn(battle, .P2, p2_slot, true, options);
|
|
@@ -167,7 +167,7 @@ fn selectMove(
|
|
|
167
167
|
const last = side.active.move(battle.lastMove(player).index);
|
|
168
168
|
if (last.id == .Metronome) side.last_selected_move = last.id;
|
|
169
169
|
if (last.id == .MirrorMove) {
|
|
170
|
-
if (!pkmn.options.mod) return
|
|
170
|
+
if (!pkmn.options.mod) return .Error;
|
|
171
171
|
side.last_selected_move = last.id;
|
|
172
172
|
}
|
|
173
173
|
}
|
|
@@ -432,7 +432,7 @@ fn executeMove(
|
|
|
432
432
|
// technically be from this Pokémon because it must be exactly 1 to not desync and
|
|
433
433
|
// every Pokémon must have at least one move
|
|
434
434
|
if (mslot != 1 or side.active.move(mslot).id != side.last_selected_move) {
|
|
435
|
-
return
|
|
435
|
+
return .Error;
|
|
436
436
|
} else {
|
|
437
437
|
auto = true;
|
|
438
438
|
}
|
|
@@ -450,7 +450,7 @@ fn executeMove(
|
|
|
450
450
|
.skip_can => skip_can = true,
|
|
451
451
|
.skip_pp => skip_pp = true,
|
|
452
452
|
.ok => {},
|
|
453
|
-
.err => return
|
|
453
|
+
.err => return .Error,
|
|
454
454
|
}
|
|
455
455
|
|
|
456
456
|
// Pokémon Showdown incorrectly implements PP deduction when handling the Hyper Beam automatic
|
|
@@ -878,7 +878,7 @@ fn doMove(
|
|
|
878
878
|
var miss = showdown and miss: {
|
|
879
879
|
immune = move.target != .Self and !status and !counter and
|
|
880
880
|
(@intFromEnum(move.type.effectiveness(foe.active.types.type1)) == 0 or
|
|
881
|
-
|
|
881
|
+
@intFromEnum(move.type.effectiveness(foe.active.types.type2)) == 0);
|
|
882
882
|
if (immune and move.effect != .Binding) break :miss true;
|
|
883
883
|
if (move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe) {
|
|
884
884
|
battle.last_damage = 0;
|
|
@@ -916,7 +916,7 @@ fn doMove(
|
|
|
916
916
|
if (showdown) break :damage; // skip adjustDamage / randomizeDamage
|
|
917
917
|
} else if (!calcDamage(battle, player, player.foe(), move, crit, options)) {
|
|
918
918
|
if (!showdown) try options.chance.commit(player, .err);
|
|
919
|
-
return
|
|
919
|
+
return .Error;
|
|
920
920
|
}
|
|
921
921
|
if (battle.last_damage == 0) {
|
|
922
922
|
immune = true;
|
|
@@ -1019,7 +1019,7 @@ fn doMove(
|
|
|
1019
1019
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
1020
1020
|
|
|
1021
1021
|
var nullified = false;
|
|
1022
|
-
var hit:
|
|
1022
|
+
var hit: usize = 0;
|
|
1023
1023
|
while (hit < hits) {
|
|
1024
1024
|
if (hit == 0) {
|
|
1025
1025
|
if (crit) try log.crit(.{battle.active(player.foe())});
|
|
@@ -1047,7 +1047,7 @@ fn doMove(
|
|
|
1047
1047
|
if (showdown and move.effect == .Twineedle and !nullified and foe.stored().hp > 0) {
|
|
1048
1048
|
try Effects.poison(battle, player, Move.get(.PoisonSting), options);
|
|
1049
1049
|
}
|
|
1050
|
-
try log.hitcount(.{ battle.active(player.foe()), hit });
|
|
1050
|
+
try log.hitcount(.{ battle.active(player.foe()), @as(u4, @intCast(hit)) });
|
|
1051
1051
|
} else if (showdown and move.effect == .Binding) {
|
|
1052
1052
|
// This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
|
|
1053
1053
|
try Effects.binding(battle, player, rewrap, options);
|
|
@@ -1248,13 +1248,13 @@ fn specialDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1248
1248
|
.Psywave => power: {
|
|
1249
1249
|
const max: u8 = @intCast(@as(u16, side.stored().level) * 3 / 2);
|
|
1250
1250
|
// GLITCH: Psywave infinite glitch loop
|
|
1251
|
-
if (!pkmn.options.mod and max <= 1) return
|
|
1251
|
+
if (!pkmn.options.mod and max <= 1) return .Error;
|
|
1252
1252
|
break :power try Rolls.psywave(battle, player, max, options);
|
|
1253
1253
|
},
|
|
1254
1254
|
else => unreachable,
|
|
1255
1255
|
};
|
|
1256
1256
|
|
|
1257
|
-
if (battle.last_damage == 0) return if (pkmn.options.mod) null else
|
|
1257
|
+
if (battle.last_damage == 0) return if (pkmn.options.mod) null else .Error;
|
|
1258
1258
|
|
|
1259
1259
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
1260
1260
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -1274,15 +1274,15 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1274
1274
|
// Pretend Splash was used as a stand-in when no move has been used to fail below with 0 BP
|
|
1275
1275
|
const foe_last_selected_move =
|
|
1276
1276
|
Move.get(if (foe.last_selected_move == .None or foe.last_selected_move == .SKIP_TURN)
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1277
|
+
.Splash
|
|
1278
|
+
else
|
|
1279
|
+
foe.last_selected_move);
|
|
1280
1280
|
|
|
1281
1281
|
const used = battle.lastMove(player.foe()).counterable != 0;
|
|
1282
1282
|
const selected = foe_last_selected_move.bp > 0 and
|
|
1283
1283
|
foe.last_selected_move != .Counter and
|
|
1284
1284
|
(foe_last_selected_move.type == .Normal or
|
|
1285
|
-
|
|
1285
|
+
foe_last_selected_move.type == .Fighting);
|
|
1286
1286
|
|
|
1287
1287
|
if (!used and !selected) {
|
|
1288
1288
|
try options.log.fail(.{ battle.active(player), .None });
|
|
@@ -1291,7 +1291,7 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1291
1291
|
|
|
1292
1292
|
if (!used or !selected) {
|
|
1293
1293
|
// GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
|
|
1294
|
-
if (!pkmn.options.mod) return
|
|
1294
|
+
if (!pkmn.options.mod) return .Error;
|
|
1295
1295
|
try options.log.fail(.{ battle.active(player), .None });
|
|
1296
1296
|
return null;
|
|
1297
1297
|
}
|
|
@@ -1528,13 +1528,13 @@ fn checkFaint(
|
|
|
1528
1528
|
|
|
1529
1529
|
if (player_out and foe_out) {
|
|
1530
1530
|
try options.log.tie(.{});
|
|
1531
|
-
return
|
|
1531
|
+
return .Tie;
|
|
1532
1532
|
} else if (player_out) {
|
|
1533
1533
|
try options.log.win(.{player.foe()});
|
|
1534
|
-
return if (player == .P1)
|
|
1534
|
+
return if (player == .P1) .Lose else .Win;
|
|
1535
1535
|
} else if (foe_out) {
|
|
1536
1536
|
try options.log.win(.{player});
|
|
1537
|
-
return if (player == .P1)
|
|
1537
|
+
return if (player == .P1) .Win else .Lose;
|
|
1538
1538
|
}
|
|
1539
1539
|
|
|
1540
1540
|
const foe_choice: Choice.Type = if (foe_fainted) .Switch else .Pass;
|
|
@@ -1552,7 +1552,7 @@ fn faint(battle: anytype, player: Player, done: bool, cap: bool, options: anytyp
|
|
|
1552
1552
|
if (foe_volatiles.Bide) {
|
|
1553
1553
|
assert(!foe_volatiles.Thrashing and !foe_volatiles.Rage);
|
|
1554
1554
|
foe_volatiles.state = if (pkmn.options.mod) 0 else foe_volatiles.state & 255;
|
|
1555
|
-
if (foe_volatiles.state != 0) return
|
|
1555
|
+
if (foe_volatiles.state != 0) return .Error;
|
|
1556
1556
|
}
|
|
1557
1557
|
|
|
1558
1558
|
// Clearing these is not strictly necessary as provided the battle hasn't ended the side that
|
|
@@ -1644,19 +1644,19 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1644
1644
|
|
|
1645
1645
|
if (pkmn.options.mod and pkmn.options.ebc and checkEBC(battle)) {
|
|
1646
1646
|
try options.log.tie(.{});
|
|
1647
|
-
return
|
|
1647
|
+
return .Tie;
|
|
1648
1648
|
}
|
|
1649
1649
|
|
|
1650
1650
|
if (pkmn.options.mod and battle.turn >= 1000) {
|
|
1651
1651
|
try options.log.tie(.{});
|
|
1652
|
-
return
|
|
1652
|
+
return .Tie;
|
|
1653
1653
|
} else if (battle.turn >= 65535) {
|
|
1654
|
-
return
|
|
1654
|
+
return .Error;
|
|
1655
1655
|
}
|
|
1656
1656
|
|
|
1657
1657
|
try options.log.turn(.{battle.turn});
|
|
1658
1658
|
|
|
1659
|
-
return
|
|
1659
|
+
return .Default;
|
|
1660
1660
|
}
|
|
1661
1661
|
|
|
1662
1662
|
fn checkEBC(battle: anytype) bool {
|
|
@@ -1947,7 +1947,7 @@ pub const Effects = struct {
|
|
|
1947
1947
|
return null;
|
|
1948
1948
|
} else if (err) {
|
|
1949
1949
|
// GLITCH: Transform + Mirror Move / Metronome PP softlock
|
|
1950
|
-
return
|
|
1950
|
+
return .Error;
|
|
1951
1951
|
}
|
|
1952
1952
|
|
|
1953
1953
|
volatiles.disable_move =
|
|
@@ -3145,11 +3145,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3145
3145
|
},
|
|
3146
3146
|
.Switch => {
|
|
3147
3147
|
const side = battle.side(player);
|
|
3148
|
-
var slot:
|
|
3148
|
+
var slot: usize = 2;
|
|
3149
3149
|
while (slot <= 6) : (slot += 1) {
|
|
3150
3150
|
const id = side.order[slot - 1];
|
|
3151
3151
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
3152
|
-
out[n] = .{ .type = .Switch, .data = slot };
|
|
3152
|
+
out[n] = .{ .type = .Switch, .data = @intCast(slot) };
|
|
3153
3153
|
n += 1;
|
|
3154
3154
|
}
|
|
3155
3155
|
if (n == 0) {
|
|
@@ -3180,11 +3180,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3180
3180
|
return n;
|
|
3181
3181
|
}
|
|
3182
3182
|
|
|
3183
|
-
var slot:
|
|
3183
|
+
var slot: usize = 2;
|
|
3184
3184
|
while (slot <= 6) : (slot += 1) {
|
|
3185
3185
|
const id = side.order[slot - 1];
|
|
3186
3186
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
3187
|
-
out[n] = .{ .type = .Switch, .data = slot };
|
|
3187
|
+
out[n] = .{ .type = .Switch, .data = @intCast(slot) };
|
|
3188
3188
|
n += 1;
|
|
3189
3189
|
}
|
|
3190
3190
|
|
|
@@ -3217,7 +3217,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3217
3217
|
const struggle =
|
|
3218
3218
|
m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
|
|
3219
3219
|
const s = if (struggle) 0 else slot;
|
|
3220
|
-
out[n] = .{ .type = .Move, .data = s };
|
|
3220
|
+
out[n] = .{ .type = .Move, .data = @intCast(s) };
|
|
3221
3221
|
n += 1;
|
|
3222
3222
|
return n;
|
|
3223
3223
|
}
|
|
@@ -3231,7 +3231,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3231
3231
|
if (m.id == .None) break;
|
|
3232
3232
|
if (m.pp == 0) continue;
|
|
3233
3233
|
if (active.volatiles.disable_move == slot) continue;
|
|
3234
|
-
out[n] = .{ .type = .Move, .data = slot };
|
|
3234
|
+
out[n] = .{ .type = .Move, .data = @intCast(slot) };
|
|
3235
3235
|
n += 1;
|
|
3236
3236
|
}
|
|
3237
3237
|
// Struggle (Pokémon Showdown would use 'move 1' here)
|
package/src/lib/gen2/calc.zig
CHANGED
|
@@ -15,9 +15,6 @@ const isPointerTo = util.isPointerTo;
|
|
|
15
15
|
const Player = common.Player;
|
|
16
16
|
const PointerType = util.PointerType;
|
|
17
17
|
|
|
18
|
-
const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
|
|
19
|
-
const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
|
|
20
|
-
|
|
21
18
|
/// Information relevant to damage calculation that occured during a Generation II battle `update`.
|
|
22
19
|
pub const Summaries = extern struct {
|
|
23
20
|
/// Relevant information for Player 1.
|
|
@@ -91,8 +88,8 @@ pub const Calc = struct {
|
|
|
91
88
|
};
|
|
92
89
|
|
|
93
90
|
return if (switch (@typeInfo(@TypeOf(val))) {
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
.@"enum" => val != .None,
|
|
92
|
+
.int => val != 0,
|
|
96
93
|
else => unreachable,
|
|
97
94
|
}) val else null;
|
|
98
95
|
}
|
|
@@ -148,6 +145,6 @@ fn ReturnType(comptime field: Action.Field) type {
|
|
|
148
145
|
return ?(switch (field) {
|
|
149
146
|
.damages => u8,
|
|
150
147
|
.critical_hits => Optional(bool),
|
|
151
|
-
else =>
|
|
148
|
+
else => @FieldType(Action, @tagName(field)),
|
|
152
149
|
});
|
|
153
150
|
}
|
package/src/lib/gen2/chance.zig
CHANGED
|
@@ -27,10 +27,6 @@ const Type = data.Type;
|
|
|
27
27
|
const Effectiveness = data.Effectiveness;
|
|
28
28
|
const TriAttack = data.TriAttack;
|
|
29
29
|
|
|
30
|
-
const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
|
|
31
|
-
const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
|
|
32
|
-
const Struct = if (@hasField(std.builtin.Type, "struct")) .@"struct" else .Struct;
|
|
33
|
-
|
|
34
30
|
/// Actions taken by a hypothetical "chance player" that convey information about which RNG events
|
|
35
31
|
/// were observed during a Generation II battle `update`. This can additionally be provided as input
|
|
36
32
|
/// to the `update` call to override the normal behavior of the RNG in order to force specific
|
|
@@ -57,15 +53,15 @@ pub const Actions = extern struct {
|
|
|
57
53
|
/// same shape if they have the same fields set (though those fields need not necessarily be
|
|
58
54
|
/// set to the same value).
|
|
59
55
|
pub fn matches(a: Actions, b: Actions) bool {
|
|
60
|
-
inline for (@
|
|
61
|
-
inline for (@
|
|
56
|
+
inline for (@typeInfo(Actions).@"struct".fields) |player| {
|
|
57
|
+
inline for (@typeInfo(Action).@"struct".fields) |field| {
|
|
62
58
|
const a_val = @field(@field(a, player.name), field.name);
|
|
63
59
|
const b_val = @field(@field(b, player.name), field.name);
|
|
64
60
|
|
|
65
61
|
switch (@typeInfo(@TypeOf(a_val))) {
|
|
66
|
-
|
|
62
|
+
.@"enum" => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
|
|
67
63
|
return false,
|
|
68
|
-
|
|
64
|
+
.int => if ((a_val > 0) != (b_val > 0)) return false,
|
|
69
65
|
else => unreachable,
|
|
70
66
|
}
|
|
71
67
|
}
|
|
@@ -73,7 +69,7 @@ pub const Actions = extern struct {
|
|
|
73
69
|
return true;
|
|
74
70
|
}
|
|
75
71
|
|
|
76
|
-
pub fn fmt(self: Actions, writer:
|
|
72
|
+
pub fn fmt(self: Actions, writer: *std.Io.Writer, shape: bool) !void {
|
|
77
73
|
try writer.writeAll("<P1 = ");
|
|
78
74
|
try self.p1.fmt(writer, shape);
|
|
79
75
|
try writer.writeAll(", P2 = ");
|
|
@@ -81,8 +77,7 @@ pub const Actions = extern struct {
|
|
|
81
77
|
try writer.writeAll(">");
|
|
82
78
|
}
|
|
83
79
|
|
|
84
|
-
pub fn format(a: Actions,
|
|
85
|
-
_ = .{ f, o };
|
|
80
|
+
pub fn format(a: Actions, w: *std.Io.Writer) !void {
|
|
86
81
|
try fmt(a, w, false);
|
|
87
82
|
}
|
|
88
83
|
};
|
|
@@ -186,18 +181,17 @@ pub const Action = packed struct(u128) {
|
|
|
186
181
|
|
|
187
182
|
pub const Field = std.meta.FieldEnum(Action);
|
|
188
183
|
|
|
189
|
-
pub fn format(a: Action,
|
|
190
|
-
_ = .{ f, o };
|
|
184
|
+
pub fn format(a: Action, w: *std.Io.Writer) !void {
|
|
191
185
|
try fmt(a, w, false);
|
|
192
186
|
}
|
|
193
187
|
|
|
194
|
-
pub fn fmt(self: Action, writer:
|
|
188
|
+
pub fn fmt(self: Action, writer: *std.Io.Writer, shape: bool) !void {
|
|
195
189
|
try writer.writeByte('(');
|
|
196
190
|
var printed = false;
|
|
197
|
-
inline for (@
|
|
191
|
+
inline for (@typeInfo(Action).@"struct".fields) |field| {
|
|
198
192
|
const val = @field(self, field.name);
|
|
199
193
|
switch (@typeInfo(@TypeOf(val))) {
|
|
200
|
-
|
|
194
|
+
.@"enum" => if (val != .None) {
|
|
201
195
|
if (printed) try writer.writeAll(", ");
|
|
202
196
|
if (shape) {
|
|
203
197
|
try writer.print("{s}:?", .{field.name});
|
|
@@ -211,7 +205,7 @@ pub const Action = packed struct(u128) {
|
|
|
211
205
|
}
|
|
212
206
|
printed = true;
|
|
213
207
|
},
|
|
214
|
-
|
|
208
|
+
.int => if (val != 0) {
|
|
215
209
|
if (printed) try writer.writeAll(", ");
|
|
216
210
|
if (shape) {
|
|
217
211
|
try writer.print("{s}:?", .{field.name});
|
|
@@ -786,7 +780,7 @@ pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
|
|
|
786
780
|
|
|
787
781
|
r.reduce();
|
|
788
782
|
if (r.p != p or r.q != q) {
|
|
789
|
-
print("expected {d}/{d}, found {}\n", .{ p, q, r });
|
|
783
|
+
print("expected {d}/{d}, found {f}\n", .{ p, q, r });
|
|
790
784
|
return error.TestExpectedEqual;
|
|
791
785
|
}
|
|
792
786
|
}
|
package/src/lib/gen2/data.zig
CHANGED
|
@@ -57,7 +57,7 @@ pub fn Battle(comptime RNG: anytype) type {
|
|
|
57
57
|
turn: u16 = 0,
|
|
58
58
|
field: Field = .{},
|
|
59
59
|
|
|
60
|
-
_: [padding]u8 =
|
|
60
|
+
_: [padding]u8 = @splat(0),
|
|
61
61
|
|
|
62
62
|
pub fn side(self: anytype, player: Player) PointerType(@TypeOf(self), Side) {
|
|
63
63
|
assert(isPointerTo(self, Self));
|
|
@@ -100,13 +100,13 @@ pub const Weather = enum(u4) {
|
|
|
100
100
|
};
|
|
101
101
|
|
|
102
102
|
pub const Side = extern struct {
|
|
103
|
-
pokemon: [6]Pokemon =
|
|
103
|
+
pokemon: [6]Pokemon = @splat(.{}),
|
|
104
104
|
active: ActivePokemon align(8) = .{},
|
|
105
105
|
conditions: Conditions = .{},
|
|
106
106
|
// NB: wLastPlayerCounterMove / wLastEnemyCounterMove (wLastPlayerMove / wLastEnemyMove)
|
|
107
107
|
last_used_move: Move = .None,
|
|
108
108
|
|
|
109
|
-
_: [7]u8 =
|
|
109
|
+
_: [7]u8 = @splat(0),
|
|
110
110
|
|
|
111
111
|
const Conditions = packed struct(u32) {
|
|
112
112
|
Spikes: bool = false,
|
|
@@ -161,7 +161,7 @@ pub const Side = extern struct {
|
|
|
161
161
|
pub const ActivePokemon = extern struct {
|
|
162
162
|
volatiles: Volatiles align(4) = .{},
|
|
163
163
|
stats: Stats(u16) = .{},
|
|
164
|
-
moves: [4]MoveSlot =
|
|
164
|
+
moves: [4]MoveSlot = @splat(.{}),
|
|
165
165
|
boosts: Boosts = .{},
|
|
166
166
|
types: Types = .{},
|
|
167
167
|
species: Species = .None,
|
|
@@ -182,7 +182,7 @@ pub const ActivePokemon = extern struct {
|
|
|
182
182
|
|
|
183
183
|
pub const Pokemon = extern struct {
|
|
184
184
|
stats: Stats(u16) = .{},
|
|
185
|
-
moves: [4]MoveSlot =
|
|
185
|
+
moves: [4]MoveSlot = @splat(.{}),
|
|
186
186
|
types: Types = .{},
|
|
187
187
|
dvs: DVs = .{},
|
|
188
188
|
hp: u16 = 0,
|
package/src/lib/gen2/helpers.zig
CHANGED
|
@@ -16,8 +16,6 @@ const showdown = options.showdown;
|
|
|
16
16
|
const Species = data.Species;
|
|
17
17
|
const Stats = data.Stats;
|
|
18
18
|
|
|
19
|
-
const Struct = if (@hasField(std.builtin.Type, "struct")) .@"struct" else .Struct;
|
|
20
|
-
|
|
21
19
|
pub const Options = struct {
|
|
22
20
|
cleric: bool = showdown,
|
|
23
21
|
};
|
|
@@ -74,7 +72,7 @@ pub fn prng(rand: *PSRNG) data.PRNG {
|
|
|
74
72
|
var carry: u8 = 0;
|
|
75
73
|
var div: u8 = 0;
|
|
76
74
|
|
|
77
|
-
var i:
|
|
75
|
+
var i: usize = 0;
|
|
78
76
|
while (i < seed.len) {
|
|
79
77
|
div = @intCast(timer >> 8);
|
|
80
78
|
// This is effectively @addWithOverflow but with three operands
|
|
@@ -142,7 +140,7 @@ pub const Pokemon = struct {
|
|
|
142
140
|
var pokemon = data.Pokemon{};
|
|
143
141
|
pokemon.species = p.species;
|
|
144
142
|
const species = Species.get(p.species);
|
|
145
|
-
inline for (@
|
|
143
|
+
inline for (@typeInfo(@TypeOf(pokemon.stats)).@"struct".fields) |field| {
|
|
146
144
|
const hp = comptime std.mem.eql(u8, field.name, "hp");
|
|
147
145
|
const spc =
|
|
148
146
|
comptime std.mem.eql(u8, field.name, "spa") or std.mem.eql(u8, field.name, "spd");
|
|
@@ -183,7 +181,7 @@ pub const Pokemon = struct {
|
|
|
183
181
|
|
|
184
182
|
var stats: Stats(u16) = .{};
|
|
185
183
|
const dvs = gen1.DVs.random(rand);
|
|
186
|
-
inline for (@
|
|
184
|
+
inline for (@typeInfo(@TypeOf(stats)).@"struct".fields) |field| {
|
|
187
185
|
const hp = std.mem.eql(u8, field.name, "hp");
|
|
188
186
|
const spc = std.mem.eql(u8, field.name, "spa") or std.mem.eql(u8, field.name, "spd");
|
|
189
187
|
@field(stats, field.name) = Stats(u16).calc(
|
|
@@ -195,7 +193,7 @@ pub const Pokemon = struct {
|
|
|
195
193
|
);
|
|
196
194
|
}
|
|
197
195
|
|
|
198
|
-
var ms
|
|
196
|
+
var ms: [4]MoveSlot = @splat(.{});
|
|
199
197
|
const n = if (rand.chance(u8, 1, 100)) rand.range(u4, 1, 3 + 1) else 4;
|
|
200
198
|
for (0..n) |i| {
|
|
201
199
|
var m: Move = .None;
|
|
@@ -130,11 +130,11 @@ fn start(battle: anytype, options: anytype) !Result {
|
|
|
130
130
|
|
|
131
131
|
const p1_slot = findFirstAlive(p1);
|
|
132
132
|
assert(!showdown or p1_slot == 1);
|
|
133
|
-
if (p1_slot == 0) return if (findFirstAlive(p2) == 0)
|
|
133
|
+
if (p1_slot == 0) return if (findFirstAlive(p2) == 0) .Tie else .Lose;
|
|
134
134
|
|
|
135
135
|
const p2_slot = findFirstAlive(p2);
|
|
136
136
|
assert(!showdown or p2_slot == 1);
|
|
137
|
-
if (p2_slot == 0) return
|
|
137
|
+
if (p2_slot == 0) return .Win;
|
|
138
138
|
|
|
139
139
|
try switchIn(battle, .P1, p1_slot, .Initial, options);
|
|
140
140
|
try switchIn(battle, .P2, p2_slot, .Initial, options);
|
|
@@ -819,13 +819,13 @@ fn checkFaint(
|
|
|
819
819
|
|
|
820
820
|
if (player_out and foe_out) {
|
|
821
821
|
try options.log.tie(.{});
|
|
822
|
-
return
|
|
822
|
+
return .Tie;
|
|
823
823
|
} else if (player_out) {
|
|
824
824
|
try options.log.win(.{player.foe()});
|
|
825
|
-
return if (player == .P1)
|
|
825
|
+
return if (player == .P1) .Lose else .Win;
|
|
826
826
|
} else if (foe_out) {
|
|
827
827
|
try options.log.win(.{player});
|
|
828
|
-
return if (player == .P1)
|
|
828
|
+
return if (player == .P1) .Win else .Lose;
|
|
829
829
|
}
|
|
830
830
|
|
|
831
831
|
const foe_choice: Choice.Type = if (foe_fainted) .Switch else .Pass;
|
|
@@ -1195,14 +1195,14 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1195
1195
|
|
|
1196
1196
|
if (pkmn.options.mod and battle.turn >= 1000) {
|
|
1197
1197
|
try options.log.tie(.{});
|
|
1198
|
-
return
|
|
1198
|
+
return .Tie;
|
|
1199
1199
|
} else if (battle.turn >= 65535) {
|
|
1200
|
-
return
|
|
1200
|
+
return .Error;
|
|
1201
1201
|
}
|
|
1202
1202
|
|
|
1203
1203
|
try options.log.turn(.{battle.turn});
|
|
1204
1204
|
|
|
1205
|
-
return
|
|
1205
|
+
return .Default;
|
|
1206
1206
|
}
|
|
1207
1207
|
|
|
1208
1208
|
pub fn buildRage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1558,7 +1558,7 @@ pub const Effects = struct {
|
|
|
1558
1558
|
const last = battle.foe(player).lastMove(true);
|
|
1559
1559
|
const failed = volatiles.Encore or
|
|
1560
1560
|
(last == .None or last == .Struggle or
|
|
1561
|
-
|
|
1561
|
+
last == .Encore or .last == .MirrorMove);
|
|
1562
1562
|
if (failed) return options.log.fail(.{ foe_ident, .None });
|
|
1563
1563
|
|
|
1564
1564
|
var slot: u3 = 0;
|
|
@@ -3081,7 +3081,7 @@ pub const Rolls = struct {
|
|
|
3081
3081
|
}
|
|
3082
3082
|
|
|
3083
3083
|
// Conversion 2 can at most choose between 7 types (vs. a Grass-type attack)
|
|
3084
|
-
var CONVERSION_2: [7]Type =
|
|
3084
|
+
var CONVERSION_2: [7]Type = @splat(.@"???");
|
|
3085
3085
|
|
|
3086
3086
|
fn conversion2(battle: anytype, player: Player, mtype: Type, options: anytype) !Type {
|
|
3087
3087
|
var i: u8 = 0;
|
|
@@ -3506,11 +3506,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3506
3506
|
},
|
|
3507
3507
|
.Switch => {
|
|
3508
3508
|
const side = battle.side(player);
|
|
3509
|
-
var slot:
|
|
3509
|
+
var slot: usize = 2;
|
|
3510
3510
|
while (slot <= 6) : (slot += 1) {
|
|
3511
3511
|
const id = side.order[slot - 1];
|
|
3512
3512
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
3513
|
-
out[n] = .{ .type = .Switch, .data = slot };
|
|
3513
|
+
out[n] = .{ .type = .Switch, .data = @intCast(slot) };
|
|
3514
3514
|
n += 1;
|
|
3515
3515
|
}
|
|
3516
3516
|
if (n == 0) {
|
|
@@ -3540,11 +3540,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3540
3540
|
return n;
|
|
3541
3541
|
}
|
|
3542
3542
|
|
|
3543
|
-
var slot:
|
|
3543
|
+
var slot: usize = 2;
|
|
3544
3544
|
while (!active.volatiles.Trapped and slot <= 6) : (slot += 1) {
|
|
3545
3545
|
const id = side.order[slot - 1];
|
|
3546
3546
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
3547
|
-
out[n] = .{ .type = .Switch, .data = slot };
|
|
3547
|
+
out[n] = .{ .type = .Switch, .data = @intCast(slot) };
|
|
3548
3548
|
n += 1;
|
|
3549
3549
|
}
|
|
3550
3550
|
|
|
@@ -3577,7 +3577,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3577
3577
|
const struggle =
|
|
3578
3578
|
m.id == .Bide and (m.pp == 0 or active.volatiles.disable.move == slot);
|
|
3579
3579
|
const s = if (struggle) 0 else slot;
|
|
3580
|
-
out[n] = .{ .type = .Move, .data = s };
|
|
3580
|
+
out[n] = .{ .type = .Move, .data = @intCast(s) };
|
|
3581
3581
|
n += 1;
|
|
3582
3582
|
return n;
|
|
3583
3583
|
}
|
|
@@ -3591,7 +3591,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3591
3591
|
if (m.id == .None) break;
|
|
3592
3592
|
if (m.pp == 0) continue;
|
|
3593
3593
|
if (active.volatiles.disable.move == slot) continue;
|
|
3594
|
-
out[n] = .{ .type = .Move, .data = slot };
|
|
3594
|
+
out[n] = .{ .type = .Move, .data = @intCast(slot) };
|
|
3595
3595
|
n += 1;
|
|
3596
3596
|
}
|
|
3597
3597
|
// Struggle (Pokémon Showdown would use 'move 1' here)
|
package/src/lib/napi.h
ADDED
package/src/lib/node.zig
CHANGED