@pkmn/engine 0.1.0-dev.7feed58c → 0.1.0-dev.8058a1d0
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 +18 -11
- package/build/pkg/addon/node.js +17 -7
- package/build/pkg/addon/node.js.map +1 -1
- 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.d.ts +1 -1
- package/build/pkg/gen1.js +17 -7
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/index.js +17 -7
- package/build/pkg/index.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 +54 -10
- package/build/tools/debug.js.map +1 -1
- package/build/tools/display/debug.jsx +19 -94
- package/build/tools/display/debug.jsx.map +1 -1
- package/build/tools/display/index.js +17 -7
- package/build/tools/display/index.js.map +1 -1
- package/build/tools/display/ui.jsx +20 -9
- 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 -231
- package/package.json +26 -24
- package/src/bin/install-pkmn-engine +22 -7
- package/src/lib/bindings/c.zig +27 -82
- package/src/lib/bindings/node.zig +7 -11
- 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 -9
- package/src/lib/common/optional.zig +11 -21
- package/src/lib/common/protocol.zig +224 -189
- package/src/lib/common/rational.zig +275 -284
- package/src/lib/common/util.zig +14 -31
- package/src/lib/gen1/calc.zig +131 -109
- package/src/lib/gen1/chance.zig +112 -75
- package/src/lib/gen1/data/types.zig +2 -2
- package/src/lib/gen1/data.zig +5 -5
- package/src/lib/gen1/helpers.zig +8 -10
- package/src/lib/gen1/mechanics.zig +43 -41
- package/src/lib/gen2/calc.zig +3 -6
- package/src/lib/gen2/chance.zig +12 -18
- package/src/lib/gen2/data/types.zig +6 -3
- package/src/lib/gen2/data.zig +8 -7
- package/src/lib/gen2/helpers.zig +4 -6
- package/src/lib/gen2/mechanics.zig +18 -18
- package/src/lib/napi.h +2 -0
- package/src/lib/node.zig +1 -5
- package/src/lib/pkmn.zig +91 -17
- package/src/lib/wasm.zig +2 -6
- package/src/tools/display/dom.ts +43 -31
|
@@ -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
|
|
@@ -618,17 +618,10 @@ fn beforeMove(
|
|
|
618
618
|
return .err;
|
|
619
619
|
}
|
|
620
620
|
}
|
|
621
|
-
// Pokémon Showdown incorrectly changes the "target" of the confusion self-hit based
|
|
622
|
-
// on the targeting behavior of the confused Pokémon's selected move which results
|
|
623
|
-
// in the wrong behavior with respect to the Substitute + Confusion glitch
|
|
624
|
-
const target = if (showdown and Move.get(side.last_selected_move).target == .Self)
|
|
625
|
-
player
|
|
626
|
-
else
|
|
627
|
-
player.foe();
|
|
628
621
|
|
|
629
622
|
const uncapped = battle.last_damage;
|
|
630
623
|
// Skipping adjustDamage / randomizeDamage / checkHit
|
|
631
|
-
_ = try applyDamage(battle, player,
|
|
624
|
+
_ = try applyDamage(battle, player, player.foe(), .Confusion, options);
|
|
632
625
|
// Pokémon Showdown thinks that confusion damage is uncapped ¯\_(ツ)_/¯
|
|
633
626
|
if (showdown) battle.last_damage = uncapped;
|
|
634
627
|
|
|
@@ -885,7 +878,7 @@ fn doMove(
|
|
|
885
878
|
var miss = showdown and miss: {
|
|
886
879
|
immune = move.target != .Self and !status and !counter and
|
|
887
880
|
(@intFromEnum(move.type.effectiveness(foe.active.types.type1)) == 0 or
|
|
888
|
-
|
|
881
|
+
@intFromEnum(move.type.effectiveness(foe.active.types.type2)) == 0);
|
|
889
882
|
if (immune and move.effect != .Binding) break :miss true;
|
|
890
883
|
if (move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe) {
|
|
891
884
|
battle.last_damage = 0;
|
|
@@ -923,7 +916,7 @@ fn doMove(
|
|
|
923
916
|
if (showdown) break :damage; // skip adjustDamage / randomizeDamage
|
|
924
917
|
} else if (!calcDamage(battle, player, player.foe(), move, crit, options)) {
|
|
925
918
|
if (!showdown) try options.chance.commit(player, .err);
|
|
926
|
-
return
|
|
919
|
+
return .Error;
|
|
927
920
|
}
|
|
928
921
|
if (battle.last_damage == 0) {
|
|
929
922
|
immune = true;
|
|
@@ -1026,7 +1019,7 @@ fn doMove(
|
|
|
1026
1019
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
1027
1020
|
|
|
1028
1021
|
var nullified = false;
|
|
1029
|
-
var hit:
|
|
1022
|
+
var hit: usize = 0;
|
|
1030
1023
|
while (hit < hits) {
|
|
1031
1024
|
if (hit == 0) {
|
|
1032
1025
|
if (crit) try log.crit(.{battle.active(player.foe())});
|
|
@@ -1054,7 +1047,7 @@ fn doMove(
|
|
|
1054
1047
|
if (showdown and move.effect == .Twineedle and !nullified and foe.stored().hp > 0) {
|
|
1055
1048
|
try Effects.poison(battle, player, Move.get(.PoisonSting), options);
|
|
1056
1049
|
}
|
|
1057
|
-
try log.hitcount(.{ battle.active(player.foe()), hit });
|
|
1050
|
+
try log.hitcount(.{ battle.active(player.foe()), @as(u4, @intCast(hit)) });
|
|
1058
1051
|
} else if (showdown and move.effect == .Binding) {
|
|
1059
1052
|
// This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
|
|
1060
1053
|
try Effects.binding(battle, player, rewrap, options);
|
|
@@ -1255,13 +1248,13 @@ fn specialDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1255
1248
|
.Psywave => power: {
|
|
1256
1249
|
const max: u8 = @intCast(@as(u16, side.stored().level) * 3 / 2);
|
|
1257
1250
|
// GLITCH: Psywave infinite glitch loop
|
|
1258
|
-
if (!pkmn.options.mod and max <= 1) return
|
|
1251
|
+
if (!pkmn.options.mod and max <= 1) return .Error;
|
|
1259
1252
|
break :power try Rolls.psywave(battle, player, max, options);
|
|
1260
1253
|
},
|
|
1261
1254
|
else => unreachable,
|
|
1262
1255
|
};
|
|
1263
1256
|
|
|
1264
|
-
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;
|
|
1265
1258
|
|
|
1266
1259
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
1267
1260
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -1281,15 +1274,15 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1281
1274
|
// Pretend Splash was used as a stand-in when no move has been used to fail below with 0 BP
|
|
1282
1275
|
const foe_last_selected_move =
|
|
1283
1276
|
Move.get(if (foe.last_selected_move == .None or foe.last_selected_move == .SKIP_TURN)
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1277
|
+
.Splash
|
|
1278
|
+
else
|
|
1279
|
+
foe.last_selected_move);
|
|
1287
1280
|
|
|
1288
1281
|
const used = battle.lastMove(player.foe()).counterable != 0;
|
|
1289
1282
|
const selected = foe_last_selected_move.bp > 0 and
|
|
1290
1283
|
foe.last_selected_move != .Counter and
|
|
1291
1284
|
(foe_last_selected_move.type == .Normal or
|
|
1292
|
-
|
|
1285
|
+
foe_last_selected_move.type == .Fighting);
|
|
1293
1286
|
|
|
1294
1287
|
if (!used and !selected) {
|
|
1295
1288
|
try options.log.fail(.{ battle.active(player), .None });
|
|
@@ -1298,13 +1291,13 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1298
1291
|
|
|
1299
1292
|
if (!used or !selected) {
|
|
1300
1293
|
// GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
|
|
1301
|
-
if (!pkmn.options.mod) return
|
|
1294
|
+
if (!pkmn.options.mod) return .Error;
|
|
1302
1295
|
try options.log.fail(.{ battle.active(player), .None });
|
|
1303
1296
|
return null;
|
|
1304
1297
|
}
|
|
1305
1298
|
|
|
1306
1299
|
// NOTE: llvm/llvm-project#58557
|
|
1307
|
-
battle.last_damage = if (comptime builtin.target.isWasm())
|
|
1300
|
+
battle.last_damage = if (comptime builtin.target.cpu.arch.isWasm())
|
|
1308
1301
|
@max(battle.last_damage *% 2, std.math.maxInt(u16))
|
|
1309
1302
|
else
|
|
1310
1303
|
battle.last_damage *| 2;
|
|
@@ -1453,7 +1446,16 @@ fn moveHit(
|
|
|
1453
1446
|
break :miss true;
|
|
1454
1447
|
}
|
|
1455
1448
|
if (!showdown) {
|
|
1456
|
-
if (move.effect == .Swift)
|
|
1449
|
+
if (move.effect == .Swift) {
|
|
1450
|
+
// If Swift could cause a division-by-zero freeze (which doesn't exist on Pokémon
|
|
1451
|
+
// Showdown, hence why the block above doesn't include this code), we need the
|
|
1452
|
+
// damage roll to be committed. However, the commit code will not save this roll if
|
|
1453
|
+
// the glitch occurs and the move was not marked as having hit. To work around this
|
|
1454
|
+
// we flag that the move hit (as Swift trivially does), but pass the probability of
|
|
1455
|
+
// the hit as zero to ensure it doesn't actually get saved.
|
|
1456
|
+
try options.chance.hit(player, true, 0);
|
|
1457
|
+
return true;
|
|
1458
|
+
}
|
|
1457
1459
|
if (foe.active.volatiles.Invulnerable) break :miss true;
|
|
1458
1460
|
}
|
|
1459
1461
|
|
|
@@ -1526,13 +1528,13 @@ fn checkFaint(
|
|
|
1526
1528
|
|
|
1527
1529
|
if (player_out and foe_out) {
|
|
1528
1530
|
try options.log.tie(.{});
|
|
1529
|
-
return
|
|
1531
|
+
return .Tie;
|
|
1530
1532
|
} else if (player_out) {
|
|
1531
1533
|
try options.log.win(.{player.foe()});
|
|
1532
|
-
return if (player == .P1)
|
|
1534
|
+
return if (player == .P1) .Lose else .Win;
|
|
1533
1535
|
} else if (foe_out) {
|
|
1534
1536
|
try options.log.win(.{player});
|
|
1535
|
-
return if (player == .P1)
|
|
1537
|
+
return if (player == .P1) .Win else .Lose;
|
|
1536
1538
|
}
|
|
1537
1539
|
|
|
1538
1540
|
const foe_choice: Choice.Type = if (foe_fainted) .Switch else .Pass;
|
|
@@ -1550,7 +1552,7 @@ fn faint(battle: anytype, player: Player, done: bool, cap: bool, options: anytyp
|
|
|
1550
1552
|
if (foe_volatiles.Bide) {
|
|
1551
1553
|
assert(!foe_volatiles.Thrashing and !foe_volatiles.Rage);
|
|
1552
1554
|
foe_volatiles.state = if (pkmn.options.mod) 0 else foe_volatiles.state & 255;
|
|
1553
|
-
if (foe_volatiles.state != 0) return
|
|
1555
|
+
if (foe_volatiles.state != 0) return .Error;
|
|
1554
1556
|
}
|
|
1555
1557
|
|
|
1556
1558
|
// Clearing these is not strictly necessary as provided the battle hasn't ended the side that
|
|
@@ -1642,19 +1644,19 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1642
1644
|
|
|
1643
1645
|
if (pkmn.options.mod and pkmn.options.ebc and checkEBC(battle)) {
|
|
1644
1646
|
try options.log.tie(.{});
|
|
1645
|
-
return
|
|
1647
|
+
return .Tie;
|
|
1646
1648
|
}
|
|
1647
1649
|
|
|
1648
1650
|
if (pkmn.options.mod and battle.turn >= 1000) {
|
|
1649
1651
|
try options.log.tie(.{});
|
|
1650
|
-
return
|
|
1652
|
+
return .Tie;
|
|
1651
1653
|
} else if (battle.turn >= 65535) {
|
|
1652
|
-
return
|
|
1654
|
+
return .Error;
|
|
1653
1655
|
}
|
|
1654
1656
|
|
|
1655
1657
|
try options.log.turn(.{battle.turn});
|
|
1656
1658
|
|
|
1657
|
-
return
|
|
1659
|
+
return .Default;
|
|
1658
1660
|
}
|
|
1659
1661
|
|
|
1660
1662
|
fn checkEBC(battle: anytype) bool {
|
|
@@ -1945,7 +1947,7 @@ pub const Effects = struct {
|
|
|
1945
1947
|
return null;
|
|
1946
1948
|
} else if (err) {
|
|
1947
1949
|
// GLITCH: Transform + Mirror Move / Metronome PP softlock
|
|
1948
|
-
return
|
|
1950
|
+
return .Error;
|
|
1949
1951
|
}
|
|
1950
1952
|
|
|
1951
1953
|
volatiles.disable_move =
|
|
@@ -3143,11 +3145,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3143
3145
|
},
|
|
3144
3146
|
.Switch => {
|
|
3145
3147
|
const side = battle.side(player);
|
|
3146
|
-
var slot:
|
|
3148
|
+
var slot: usize = 2;
|
|
3147
3149
|
while (slot <= 6) : (slot += 1) {
|
|
3148
3150
|
const id = side.order[slot - 1];
|
|
3149
3151
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
3150
|
-
out[n] = .{ .type = .Switch, .data = slot };
|
|
3152
|
+
out[n] = .{ .type = .Switch, .data = @intCast(slot) };
|
|
3151
3153
|
n += 1;
|
|
3152
3154
|
}
|
|
3153
3155
|
if (n == 0) {
|
|
@@ -3178,11 +3180,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3178
3180
|
return n;
|
|
3179
3181
|
}
|
|
3180
3182
|
|
|
3181
|
-
var slot:
|
|
3183
|
+
var slot: usize = 2;
|
|
3182
3184
|
while (slot <= 6) : (slot += 1) {
|
|
3183
3185
|
const id = side.order[slot - 1];
|
|
3184
3186
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
3185
|
-
out[n] = .{ .type = .Switch, .data = slot };
|
|
3187
|
+
out[n] = .{ .type = .Switch, .data = @intCast(slot) };
|
|
3186
3188
|
n += 1;
|
|
3187
3189
|
}
|
|
3188
3190
|
|
|
@@ -3215,7 +3217,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3215
3217
|
const struggle =
|
|
3216
3218
|
m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
|
|
3217
3219
|
const s = if (struggle) 0 else slot;
|
|
3218
|
-
out[n] = .{ .type = .Move, .data = s };
|
|
3220
|
+
out[n] = .{ .type = .Move, .data = @intCast(s) };
|
|
3219
3221
|
n += 1;
|
|
3220
3222
|
return n;
|
|
3221
3223
|
}
|
|
@@ -3229,7 +3231,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3229
3231
|
if (m.id == .None) break;
|
|
3230
3232
|
if (m.pp == 0) continue;
|
|
3231
3233
|
if (active.volatiles.disable_move == slot) continue;
|
|
3232
|
-
out[n] = .{ .type = .Move, .data = slot };
|
|
3234
|
+
out[n] = .{ .type = .Move, .data = @intCast(slot) };
|
|
3233
3235
|
n += 1;
|
|
3234
3236
|
}
|
|
3235
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
|
}
|
|
@@ -172,9 +172,12 @@ pub const Type = enum(u8) {
|
|
|
172
172
|
return CHART[@intFromEnum(t1)][@intFromEnum(t2)];
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
/// The precedence order of Type `
|
|
176
|
-
pub fn precedence(
|
|
177
|
-
return
|
|
175
|
+
/// The precedence order of Type `t1` vs. Type `t2`.
|
|
176
|
+
pub fn precedence(t1: Type, t2: Type) u8 {
|
|
177
|
+
// The Ice vs. Fire matchup is out of order - return a higher
|
|
178
|
+
// number than anything else so that it sorts last correctly
|
|
179
|
+
if (t1 == .Ice and t2 == .Fire) return 18;
|
|
180
|
+
return PRECEDENCE[@intFromEnum(t2)];
|
|
178
181
|
}
|
|
179
182
|
|
|
180
183
|
/// The internal index of this Type used by Present.
|
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,
|
|
@@ -449,8 +449,9 @@ test Types {
|
|
|
449
449
|
try expect(!Type.Steel.special());
|
|
450
450
|
try expect(Type.Dark.special());
|
|
451
451
|
|
|
452
|
-
try expectEqual(@as(u8, 3), Type.Water.
|
|
453
|
-
try expect(Type.
|
|
452
|
+
try expectEqual(@as(u8, 3), Type.precedence(.Water, .Water));
|
|
453
|
+
try expect(Type.precedence(.Poison, .Bug) > Type.precedence(.Poison, .Poison));
|
|
454
|
+
try expect(Type.precedence(.Ice, .Fire) > Type.precedence(.Ice, .Water));
|
|
454
455
|
|
|
455
456
|
try expectEqual(Effectiveness.Super, Type.effectiveness(.Ghost, .Psychic));
|
|
456
457
|
try expectEqual(Effectiveness.Super, Type.effectiveness(.Water, .Fire));
|
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);
|
|
@@ -682,7 +682,7 @@ pub fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype)
|
|
|
682
682
|
|
|
683
683
|
// Type effectiveness matchup precedence only matters with (NVE, SE)
|
|
684
684
|
if (!showdown and (eff1 + eff2) == Effectiveness.mismatch and
|
|
685
|
-
types.type1.precedence(
|
|
685
|
+
Type.precedence(state.type, types.type1) > Type.precedence(state.type, types.type2))
|
|
686
686
|
{
|
|
687
687
|
assert(eff2 != neutral);
|
|
688
688
|
d = d *% eff2 / 10;
|
|
@@ -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;
|
|
@@ -2841,7 +2841,7 @@ fn convertible(active: ActivePokemon, m: Move) bool {
|
|
|
2841
2841
|
|
|
2842
2842
|
// NOTE: llvm/llvm-project#58557
|
|
2843
2843
|
fn smul(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
|
|
2844
|
-
if (!comptime builtin.target.isWasm()) return a *| b;
|
|
2844
|
+
if (!comptime builtin.target.cpu.arch.isWasm()) return a *| b;
|
|
2845
2845
|
return @max(a *% b, std.math.maxInt(@TypeOf(a)));
|
|
2846
2846
|
}
|
|
2847
2847
|
|
|
@@ -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
|
@@ -1,14 +1,10 @@
|
|
|
1
|
+
const c = @import("napi");
|
|
1
2
|
const js = @import("common/js.zig");
|
|
2
3
|
const node = @import("bindings/node.zig");
|
|
3
4
|
const std = @import("std");
|
|
4
5
|
|
|
5
6
|
const assert = std.debug.assert;
|
|
6
7
|
|
|
7
|
-
const c = @cImport({
|
|
8
|
-
@cDefine("NAPI_VERSION", "8");
|
|
9
|
-
@cInclude("node_api.h");
|
|
10
|
-
});
|
|
11
|
-
|
|
12
8
|
export fn napi_register_module_v1(env: c.napi_env, exports: c.napi_value) c.napi_value {
|
|
13
9
|
const properties = [_]c.napi_property_descriptor{
|
|
14
10
|
js.Property.init("engine", .{ .value = node.register(env) }),
|