@pkmn/engine 0.1.0-dev.04a96f24 → 0.1.0-dev.059a0591
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/README.md +5 -4
- package/build/pkg/addon/browser.js +4 -1
- package/build/pkg/addon/browser.js.map +1 -1
- package/build/pkg/addon/node.js +3 -0
- package/build/pkg/addon/node.js.map +1 -1
- package/build/pkg/addon/wasm.js +41 -9
- package/build/pkg/addon/wasm.js.map +1 -1
- package/build/pkg/addon.d.ts +5 -5
- package/build/pkg/addon.js +8 -17
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/data/layout.json +15 -2
- package/build/pkg/data/protocol.json +23 -9
- package/build/pkg/gen1.d.ts +15 -18
- package/build/pkg/gen1.js +11 -12
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/index.d.ts +37 -100
- package/build/pkg/index.js +6 -68
- package/build/pkg/index.js.map +1 -1
- package/build/tools/debug.js +0 -1
- package/build/tools/debug.js.map +1 -1
- package/build/tools/display/index.js +1 -0
- package/build/tools/display/index.js.map +1 -1
- package/build/tools/display/ui.jsx.map +1 -1
- package/build/tools/display/util.js +16 -1
- package/build/tools/display/util.js.map +1 -1
- package/build.zig +118 -27
- package/package.json +23 -124
- package/src/bin/install-pkmn-engine +3 -2
- package/src/lib/bindings/c.zig +254 -0
- package/src/lib/bindings/node.zig +121 -0
- package/src/lib/bindings/wasm.zig +59 -0
- package/src/lib/common/array.zig +1 -1
- package/src/lib/common/optional.zig +1 -1
- package/src/lib/common/options.zig +2 -0
- package/src/lib/common/util.zig +1 -1
- package/src/lib/gen1/calc.zig +9 -9
- package/src/lib/gen1/mechanics.zig +52 -21
- package/src/lib/gen2/mechanics.zig +22 -11
- package/src/lib/node.zig +2 -113
- package/src/lib/pkmn.zig +3 -2
- package/src/lib/wasm.zig +14 -9
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const DEBUG = @import("../common/debug.zig").print;
|
|
2
2
|
|
|
3
|
+
const builtin = @import("builtin");
|
|
3
4
|
const chance = @import("chance.zig");
|
|
4
5
|
const common = @import("../common/data.zig");
|
|
5
6
|
const data = @import("data.zig");
|
|
@@ -165,7 +166,10 @@ fn selectMove(
|
|
|
165
166
|
if (foe_choice.type == .Switch) {
|
|
166
167
|
const last = side.active.move(battle.lastMove(player).index);
|
|
167
168
|
if (last.id == .Metronome) side.last_selected_move = last.id;
|
|
168
|
-
if (last.id == .MirrorMove)
|
|
169
|
+
if (last.id == .MirrorMove) {
|
|
170
|
+
if (!pkmn.options.mod) return Result.Error;
|
|
171
|
+
side.last_selected_move = last.id;
|
|
172
|
+
}
|
|
169
173
|
}
|
|
170
174
|
}
|
|
171
175
|
return null;
|
|
@@ -416,7 +420,7 @@ fn executeMove(
|
|
|
416
420
|
mslot = @intCast(battle.lastMove(player).index);
|
|
417
421
|
const stored = side.stored();
|
|
418
422
|
// GLITCH: Struggle bypass PP underflow via Hyper Beam / Trapping-switch auto selection
|
|
419
|
-
auto = isForced(&side.active) or
|
|
423
|
+
auto = pkmn.options.mod or isForced(&side.active) or
|
|
420
424
|
side.active.volatiles.Binding or side.active.volatiles.Bide or
|
|
421
425
|
side.last_selected_move == .HyperBeam or
|
|
422
426
|
Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP);
|
|
@@ -449,6 +453,25 @@ fn executeMove(
|
|
|
449
453
|
.err => return @as(?Result, Result.Error),
|
|
450
454
|
}
|
|
451
455
|
|
|
456
|
+
// Pokémon Showdown incorrectly implements PP deduction when handling the Hyper Beam automatic
|
|
457
|
+
// selection glitch - if the move was proced due to Metronome / Mirror Move it either subtracts
|
|
458
|
+
// from the Hyper Beam slot if present or skips PP deduction entirely...
|
|
459
|
+
if (showdown and !skip_pp and side.last_selected_move == .HyperBeam and
|
|
460
|
+
side.last_selected_move != side.active.move(mslot).id)
|
|
461
|
+
{
|
|
462
|
+
assert(mslot == 1);
|
|
463
|
+
const has_beam = has_beam: {
|
|
464
|
+
for (side.active.moves, 0..) |m, i| {
|
|
465
|
+
if (m.id == .HyperBeam) {
|
|
466
|
+
mslot = @intCast(i + 1);
|
|
467
|
+
break :has_beam true;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
break :has_beam false;
|
|
471
|
+
};
|
|
472
|
+
if (!has_beam) skip_pp = true;
|
|
473
|
+
}
|
|
474
|
+
|
|
452
475
|
const can = skip_can or
|
|
453
476
|
try canMove(battle, player, mslot, auto, skip_pp, .None, residual, options);
|
|
454
477
|
if (!can) return null;
|
|
@@ -546,7 +569,7 @@ fn beforeMove(
|
|
|
546
569
|
// This can only happen if a Pokémon started the battle frozen/sleeping and was thawed/woken
|
|
547
570
|
// before the side had a selected a move - we simply need to assume this leads to a desync
|
|
548
571
|
if (side.last_selected_move == .None) {
|
|
549
|
-
assert(!
|
|
572
|
+
assert(!pkmn.options.mod);
|
|
550
573
|
return .err;
|
|
551
574
|
}
|
|
552
575
|
|
|
@@ -765,9 +788,14 @@ fn canMove(
|
|
|
765
788
|
|
|
766
789
|
if (move.effect == .Thrashing) {
|
|
767
790
|
Effects.thrashing(battle, player, options);
|
|
768
|
-
} else if (
|
|
769
|
-
// Pokémon Showdown handles this after hit/miss checks and damage calculation
|
|
770
|
-
|
|
791
|
+
} else if (move.effect == .Binding) {
|
|
792
|
+
// Pokémon Showdown handles this after hit/miss checks and damage calculation, though clears
|
|
793
|
+
// Recharging in onTryMove (required for the Hyper Beam automatic selection glitch)
|
|
794
|
+
if (showdown) {
|
|
795
|
+
battle.foe(player).active.volatiles.Recharging = false;
|
|
796
|
+
} else {
|
|
797
|
+
try Effects.binding(battle, player, false, options);
|
|
798
|
+
}
|
|
771
799
|
}
|
|
772
800
|
|
|
773
801
|
return true;
|
|
@@ -1121,7 +1149,7 @@ fn calcDamage(
|
|
|
1121
1149
|
if ((!showdown or !cfz) and (atk > 255 or def > 255)) {
|
|
1122
1150
|
atk = @max((atk / 4) & 255, 1);
|
|
1123
1151
|
// GLITCH: not adjusted to be a min of 1 on cartridge (can lead to division-by-zero freeze)
|
|
1124
|
-
def = @max((def / 4) & 255, if (
|
|
1152
|
+
def = @max((def / 4) & 255, if (pkmn.options.mod) 1 else 0);
|
|
1125
1153
|
}
|
|
1126
1154
|
|
|
1127
1155
|
const lvl: u32 = side.stored().level * @as(u2, if (crit) 2 else 1);
|
|
@@ -1142,7 +1170,7 @@ fn calcDamage(
|
|
|
1142
1170
|
// encountered so we need to make sure that in the event of a miss we commit the status of the
|
|
1143
1171
|
// crit roll. The alternative to doing this extra work is to *always* commit the crit roll but
|
|
1144
1172
|
// that results in counter intuitive probabilities in the common case
|
|
1145
|
-
if (pkmn.options.chance and !
|
|
1173
|
+
if (pkmn.options.chance and !pkmn.options.mod and move.effect != .Explode) {
|
|
1146
1174
|
atk = attack(side, !crit, special);
|
|
1147
1175
|
def = defense(target, !crit, special, false);
|
|
1148
1176
|
if ((atk > 255 or def > 255) and (def / 4) & 255 == 0) options.chance.glitch();
|
|
@@ -1227,13 +1255,13 @@ fn specialDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1227
1255
|
.Psywave => power: {
|
|
1228
1256
|
const max: u8 = @intCast(@as(u16, side.stored().level) * 3 / 2);
|
|
1229
1257
|
// GLITCH: Psywave infinite glitch loop
|
|
1230
|
-
if (!
|
|
1258
|
+
if (!pkmn.options.mod and max <= 1) return Result.Error;
|
|
1231
1259
|
break :power try Rolls.psywave(battle, player, max, options);
|
|
1232
1260
|
},
|
|
1233
1261
|
else => unreachable,
|
|
1234
1262
|
};
|
|
1235
1263
|
|
|
1236
|
-
if (battle.last_damage == 0) return if (
|
|
1264
|
+
if (battle.last_damage == 0) return if (pkmn.options.mod) null else Result.Error;
|
|
1237
1265
|
|
|
1238
1266
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
1239
1267
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -1270,12 +1298,16 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1270
1298
|
|
|
1271
1299
|
if (!used or !selected) {
|
|
1272
1300
|
// GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
|
|
1273
|
-
if (!
|
|
1301
|
+
if (!pkmn.options.mod) return Result.Error;
|
|
1274
1302
|
try options.log.fail(.{ battle.active(player), .None });
|
|
1275
1303
|
return null;
|
|
1276
1304
|
}
|
|
1277
1305
|
|
|
1278
|
-
|
|
1306
|
+
// NOTE: llvm/llvm-project#58557
|
|
1307
|
+
battle.last_damage = if (comptime builtin.target.isWasm())
|
|
1308
|
+
@max(battle.last_damage *% 2, std.math.maxInt(u16))
|
|
1309
|
+
else
|
|
1310
|
+
battle.last_damage *| 2;
|
|
1279
1311
|
|
|
1280
1312
|
// Pokémon Showdown calls moveHit before Counter
|
|
1281
1313
|
if (!showdown and !try checkHit(battle, player, move, options)) return null;
|
|
@@ -1517,7 +1549,7 @@ fn faint(battle: anytype, player: Player, done: bool, cap: bool, options: anytyp
|
|
|
1517
1549
|
assert(!foe_volatiles.MultiHit);
|
|
1518
1550
|
if (foe_volatiles.Bide) {
|
|
1519
1551
|
assert(!foe_volatiles.Thrashing and !foe_volatiles.Rage);
|
|
1520
|
-
foe_volatiles.state = if (
|
|
1552
|
+
foe_volatiles.state = if (pkmn.options.mod) 0 else foe_volatiles.state & 255;
|
|
1521
1553
|
if (foe_volatiles.state != 0) return Result.Error;
|
|
1522
1554
|
}
|
|
1523
1555
|
|
|
@@ -1608,12 +1640,12 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1608
1640
|
|
|
1609
1641
|
battle.turn += 1;
|
|
1610
1642
|
|
|
1611
|
-
if (
|
|
1643
|
+
if (pkmn.options.mod and pkmn.options.ebc and checkEBC(battle)) {
|
|
1612
1644
|
try options.log.tie(.{});
|
|
1613
1645
|
return Result.Tie;
|
|
1614
1646
|
}
|
|
1615
1647
|
|
|
1616
|
-
if (
|
|
1648
|
+
if (pkmn.options.mod and battle.turn >= 1000) {
|
|
1617
1649
|
try options.log.tie(.{});
|
|
1618
1650
|
return Result.Tie;
|
|
1619
1651
|
} else if (battle.turn >= 65535) {
|
|
@@ -1908,12 +1940,11 @@ pub const Effects = struct {
|
|
|
1908
1940
|
|
|
1909
1941
|
// Technically this is still considered simply a "miss" on the cartridge,
|
|
1910
1942
|
// but diverging from Pokémon Showdown here would mostly just be pedantic
|
|
1911
|
-
if (n == 0) {
|
|
1943
|
+
if (n == 0 or (pkmn.options.mod and err)) {
|
|
1912
1944
|
try options.log.fail(.{ foe_ident, .None });
|
|
1913
1945
|
return null;
|
|
1914
1946
|
} else if (err) {
|
|
1915
1947
|
// GLITCH: Transform + Mirror Move / Metronome PP softlock
|
|
1916
|
-
assert(!showdown);
|
|
1917
1948
|
return Result.Error;
|
|
1918
1949
|
}
|
|
1919
1950
|
|
|
@@ -1991,7 +2022,7 @@ pub const Effects = struct {
|
|
|
1991
2022
|
if (foe.active.types.includes(move.type)) return;
|
|
1992
2023
|
if (!try Rolls.secondaryChance(battle, player, true, options)) return;
|
|
1993
2024
|
// Freeze Clause Mod
|
|
1994
|
-
if (
|
|
2025
|
+
if (pkmn.options.mod) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
|
|
1995
2026
|
|
|
1996
2027
|
foe_stored.status = Status.init(.FRZ);
|
|
1997
2028
|
// GLITCH: Hyper Beam recharging status is not cleared
|
|
@@ -2332,7 +2363,7 @@ pub const Effects = struct {
|
|
|
2332
2363
|
}
|
|
2333
2364
|
|
|
2334
2365
|
// Sleep Clause Mod
|
|
2335
|
-
if (
|
|
2366
|
+
if (pkmn.options.mod) {
|
|
2336
2367
|
for (foe.pokemon) |p| {
|
|
2337
2368
|
if (Status.is(p.status, .SLP) and !Status.is(p.status, .EXT)) return;
|
|
2338
2369
|
}
|
|
@@ -2438,7 +2469,7 @@ pub const Effects = struct {
|
|
|
2438
2469
|
if (side.active.volatiles.Binding) return;
|
|
2439
2470
|
side.active.volatiles.Binding = true;
|
|
2440
2471
|
// GLITCH: Hyper Beam automatic selection glitch if Recharging gets cleared on miss
|
|
2441
|
-
// (Pokémon Showdown
|
|
2472
|
+
// (Pokémon Showdown clears Recharging in onTryMove instead)
|
|
2442
2473
|
if (!showdown) {
|
|
2443
2474
|
foe.active.volatiles.Recharging = false;
|
|
2444
2475
|
} else if (foe.stored().hp == 0) return if (!rewrap) battle.rng.advance(1);
|
|
@@ -3204,7 +3235,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3204
3235
|
// Struggle (Pokémon Showdown would use 'move 1' here)
|
|
3205
3236
|
if (n == before) {
|
|
3206
3237
|
// GLITCH: Transform + Mirror Move / Metronome PP softlock
|
|
3207
|
-
if (!
|
|
3238
|
+
if (!pkmn.options.mod) {
|
|
3208
3239
|
while (slot <= 4) : (slot += 1) {
|
|
3209
3240
|
if (active.moves[slot - 1].pp != 0) return n;
|
|
3210
3241
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const DEBUG = @import("../common/debug.zig").print;
|
|
2
2
|
|
|
3
|
+
const builtin = @import("builtin");
|
|
3
4
|
const common = @import("../common/data.zig");
|
|
4
5
|
const data = @import("data.zig");
|
|
5
6
|
const generated = @import("generated.zig");
|
|
@@ -1192,7 +1193,7 @@ fn updateSideCondition(
|
|
|
1192
1193
|
fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result {
|
|
1193
1194
|
battle.turn += 1;
|
|
1194
1195
|
|
|
1195
|
-
if (
|
|
1196
|
+
if (pkmn.options.mod and battle.turn >= 1000) {
|
|
1196
1197
|
try options.log.tie(.{});
|
|
1197
1198
|
return Result.Tie;
|
|
1198
1199
|
} else if (battle.turn >= 65535) {
|
|
@@ -1426,7 +1427,8 @@ pub const Effects = struct {
|
|
|
1426
1427
|
if (move.type.special() != (last == .MirrorCoat)) return;
|
|
1427
1428
|
if (state.damage == 0) return;
|
|
1428
1429
|
|
|
1429
|
-
|
|
1430
|
+
// NOTE: llvm/llvm-project#58557
|
|
1431
|
+
state.damage = smul(state.damage, 2);
|
|
1430
1432
|
state.miss = false;
|
|
1431
1433
|
}
|
|
1432
1434
|
|
|
@@ -1521,15 +1523,18 @@ pub const Effects = struct {
|
|
|
1521
1523
|
|
|
1522
1524
|
pub const double = struct {
|
|
1523
1525
|
pub fn flying(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1524
|
-
if (battle.foe(player).active.volatiles.Flying)
|
|
1526
|
+
if (!battle.foe(player).active.volatiles.Flying) return;
|
|
1527
|
+
state.damage = smul(state.damage, 2);
|
|
1525
1528
|
}
|
|
1526
1529
|
|
|
1527
1530
|
pub fn underground(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1528
|
-
if (battle.foe(player).active.volatiles.Underground)
|
|
1531
|
+
if (!battle.foe(player).active.volatiles.Underground) return;
|
|
1532
|
+
state.damage = smul(state.damage, 2);
|
|
1529
1533
|
}
|
|
1530
1534
|
|
|
1531
1535
|
pub fn minimize(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1532
|
-
if (battle.foe(player).active.volatiles.minimized)
|
|
1536
|
+
if (!battle.foe(player).active.volatiles.minimized) return;
|
|
1537
|
+
state.damage = smul(state.damage, 2);
|
|
1533
1538
|
}
|
|
1534
1539
|
};
|
|
1535
1540
|
|
|
@@ -1698,7 +1703,7 @@ pub const Effects = struct {
|
|
|
1698
1703
|
if (!state.proc) return;
|
|
1699
1704
|
if (foe.conditions.Safeguard) return;
|
|
1700
1705
|
// Freeze Clause Mod
|
|
1701
|
-
if (
|
|
1706
|
+
if (pkmn.options.mod) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
|
|
1702
1707
|
|
|
1703
1708
|
foe_stored.status = Status.init(.FRZ);
|
|
1704
1709
|
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
@@ -1727,7 +1732,7 @@ pub const Effects = struct {
|
|
|
1727
1732
|
state.bp *= 2;
|
|
1728
1733
|
assert(state.bp <= 160);
|
|
1729
1734
|
} else {
|
|
1730
|
-
state.damage
|
|
1735
|
+
state.damage = smul(state.damage, 2);
|
|
1731
1736
|
}
|
|
1732
1737
|
}
|
|
1733
1738
|
|
|
@@ -2198,7 +2203,7 @@ pub const Effects = struct {
|
|
|
2198
2203
|
|
|
2199
2204
|
pub fn pursuit(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2200
2205
|
// TODO: remove switching variable and just look at Choice saved in Battle struct?
|
|
2201
|
-
if (battle.foe(player).active.volatiles.switching) state.damage
|
|
2206
|
+
if (battle.foe(player).active.volatiles.switching) state.damage = smul(state.damage, 2);
|
|
2202
2207
|
}
|
|
2203
2208
|
|
|
2204
2209
|
pub const rage = struct {
|
|
@@ -2211,7 +2216,7 @@ pub const Effects = struct {
|
|
|
2211
2216
|
const side = battle.side(player);
|
|
2212
2217
|
|
|
2213
2218
|
assert(side.active.volatiles.Rage);
|
|
2214
|
-
state.damage
|
|
2219
|
+
state.damage = smul(state.damage, (side.active.volatiles.rage +| 1)); // TODO: |+
|
|
2215
2220
|
}
|
|
2216
2221
|
};
|
|
2217
2222
|
|
|
@@ -2306,7 +2311,7 @@ pub const Effects = struct {
|
|
|
2306
2311
|
side.active.volatiles.attacks += 1;
|
|
2307
2312
|
side.active.volatiles.Rollout = side.active.volatiles.attacks != 5;
|
|
2308
2313
|
|
|
2309
|
-
if (side.active.volatiles.DefenseCurl) state.damage
|
|
2314
|
+
if (side.active.volatiles.DefenseCurl) state.damage = smul(state.damage, 2);
|
|
2310
2315
|
}
|
|
2311
2316
|
};
|
|
2312
2317
|
|
|
@@ -2376,7 +2381,7 @@ pub const Effects = struct {
|
|
|
2376
2381
|
}
|
|
2377
2382
|
|
|
2378
2383
|
// Sleep Clause Mod
|
|
2379
|
-
if (
|
|
2384
|
+
if (pkmn.options.mod) {
|
|
2380
2385
|
for (foe.pokemon) |p| {
|
|
2381
2386
|
if (Status.is(p.status, .SLP) and !Status.is(p.status, .EXT)) return;
|
|
2382
2387
|
}
|
|
@@ -2834,6 +2839,12 @@ fn convertible(active: ActivePokemon, m: Move) bool {
|
|
|
2834
2839
|
return t != .@"???" and !active.types.includes(t);
|
|
2835
2840
|
}
|
|
2836
2841
|
|
|
2842
|
+
// NOTE: llvm/llvm-project#58557
|
|
2843
|
+
fn smul(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
|
|
2844
|
+
if (!comptime builtin.target.isWasm()) return a *| b;
|
|
2845
|
+
return @max(a *% b, std.math.maxInt(@TypeOf(a)));
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2837
2848
|
pub const Rolls = struct {
|
|
2838
2849
|
fn speedTie(battle: anytype, options: anytype) !bool {
|
|
2839
2850
|
const p1 = if (options.calc.overridden(.P1, 0, .speed_tie)) |player|
|
package/src/lib/node.zig
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const js = @import("common/js.zig");
|
|
2
|
-
const
|
|
2
|
+
const node = @import("bindings/node.zig");
|
|
3
3
|
const std = @import("std");
|
|
4
4
|
|
|
5
5
|
const assert = std.debug.assert;
|
|
@@ -11,119 +11,8 @@ const c = @cImport({
|
|
|
11
11
|
|
|
12
12
|
export fn napi_register_module_v1(env: c.napi_env, exports: c.napi_value) c.napi_value {
|
|
13
13
|
const properties = [_]c.napi_property_descriptor{
|
|
14
|
-
js.Property.init("engine", .{ .value = register(env) }),
|
|
14
|
+
js.Property.init("engine", .{ .value = node.register(env) }),
|
|
15
15
|
};
|
|
16
16
|
assert(c.napi_define_properties(env, exports, properties.len, &properties) == c.napi_ok);
|
|
17
17
|
return exports;
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
pub fn register(env: c.napi_env) c.napi_value {
|
|
21
|
-
const object = js.Object.init(env);
|
|
22
|
-
const properties = [_]c.napi_property_descriptor{
|
|
23
|
-
js.Property.init("options", .{ .value = options(env) }),
|
|
24
|
-
js.Property.init("bindings", .{ .value = bindings(env) }),
|
|
25
|
-
};
|
|
26
|
-
assert(c.napi_define_properties(env, object, properties.len, &properties) == c.napi_ok);
|
|
27
|
-
return object;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
fn options(env: c.napi_env) c.napi_value {
|
|
31
|
-
const object = js.Object.init(env);
|
|
32
|
-
const properties = [_]c.napi_property_descriptor{
|
|
33
|
-
js.Property.init("showdown", .{ .value = js.Boolean.init(env, pkmn.options.showdown) }),
|
|
34
|
-
js.Property.init("log", .{ .value = js.Boolean.init(env, pkmn.options.log) }),
|
|
35
|
-
js.Property.init("chance", .{ .value = js.Boolean.init(env, pkmn.options.chance) }),
|
|
36
|
-
js.Property.init("calc", .{ .value = js.Boolean.init(env, pkmn.options.calc) }),
|
|
37
|
-
};
|
|
38
|
-
assert(c.napi_define_properties(env, object, properties.len, &properties) == c.napi_ok);
|
|
39
|
-
return object;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
fn bindings(env: c.napi_env) c.napi_value {
|
|
43
|
-
const array = js.Array.init(env, .{ .length = 1 });
|
|
44
|
-
js.Array.set(env, array, 0, bind(env, pkmn.gen1));
|
|
45
|
-
return array;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
fn bind(env: c.napi_env, gen: anytype) c.napi_value {
|
|
49
|
-
const choices_size: u32 = @intCast(gen.CHOICES_SIZE);
|
|
50
|
-
const logs_size: u32 = @intCast(gen.LOGS_SIZE);
|
|
51
|
-
const object = js.Object.init(env);
|
|
52
|
-
const properties = [_]c.napi_property_descriptor{
|
|
53
|
-
js.Property.init("CHOICES_SIZE", .{ .value = js.Number.init(env, choices_size) }),
|
|
54
|
-
js.Property.init("LOGS_SIZE", .{ .value = js.Number.init(env, logs_size) }),
|
|
55
|
-
js.Property.init("update", .{ .method = update(gen) }),
|
|
56
|
-
js.Property.init("choices", .{ .method = choices(gen) }),
|
|
57
|
-
};
|
|
58
|
-
assert(c.napi_define_properties(env, object, properties.len, &properties) == c.napi_ok);
|
|
59
|
-
return object;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
fn update(gen: anytype) c.napi_callback {
|
|
63
|
-
return struct {
|
|
64
|
-
fn call(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {
|
|
65
|
-
var argc: usize = 4;
|
|
66
|
-
var argv: [4]c.napi_value = undefined;
|
|
67
|
-
assert(c.napi_get_cb_info(env, info, &argc, &argv, null, null) == c.napi_ok);
|
|
68
|
-
assert(argc == 4);
|
|
69
|
-
|
|
70
|
-
var data: ?*anyopaque = undefined;
|
|
71
|
-
var len: usize = 0;
|
|
72
|
-
assert(c.napi_get_arraybuffer_info(env, argv[0], &data, &len) == c.napi_ok);
|
|
73
|
-
assert(len == @sizeOf(gen.Battle(gen.PRNG)));
|
|
74
|
-
|
|
75
|
-
var battle: *gen.Battle(gen.PRNG) = @alignCast(@ptrCast(data.?));
|
|
76
|
-
const c1: pkmn.Choice = @bitCast(js.Number.get(env, argv[1], u8));
|
|
77
|
-
const c2: pkmn.Choice = @bitCast(js.Number.get(env, argv[2], u8));
|
|
78
|
-
|
|
79
|
-
var vtype: c.napi_valuetype = undefined;
|
|
80
|
-
assert(c.napi_typeof(env, argv[3], &vtype) == c.napi_ok);
|
|
81
|
-
const result = switch (vtype) {
|
|
82
|
-
c.napi_undefined, c.napi_null => battle.update(c1, c2, &gen.NULL),
|
|
83
|
-
else => result: {
|
|
84
|
-
assert(c.napi_get_arraybuffer_info(env, argv[3], &data, &len) == c.napi_ok);
|
|
85
|
-
assert(len == gen.LOGS_SIZE);
|
|
86
|
-
|
|
87
|
-
const buf = @as([*]u8, @ptrCast(data.?))[0..gen.LOGS_SIZE];
|
|
88
|
-
var stream: pkmn.protocol.ByteStream = .{ .buffer = buf };
|
|
89
|
-
// TODO: extract out
|
|
90
|
-
var opts = pkmn.battle.options(
|
|
91
|
-
pkmn.protocol.FixedLog{ .writer = stream.writer() },
|
|
92
|
-
gen.chance.NULL,
|
|
93
|
-
gen.calc.NULL,
|
|
94
|
-
);
|
|
95
|
-
break :result battle.update(c1, c2, &opts);
|
|
96
|
-
},
|
|
97
|
-
} catch unreachable;
|
|
98
|
-
|
|
99
|
-
return js.Number.init(env, @as(u8, @bitCast(result)));
|
|
100
|
-
}
|
|
101
|
-
}.call;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
fn choices(gen: anytype) c.napi_callback {
|
|
105
|
-
return struct {
|
|
106
|
-
fn call(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {
|
|
107
|
-
var argc: usize = 4;
|
|
108
|
-
var argv: [4]c.napi_value = undefined;
|
|
109
|
-
assert(c.napi_get_cb_info(env, info, &argc, &argv, null, null) == c.napi_ok);
|
|
110
|
-
assert(argc == 4);
|
|
111
|
-
|
|
112
|
-
var data: ?*anyopaque = undefined;
|
|
113
|
-
var len: usize = 0;
|
|
114
|
-
assert(c.napi_get_arraybuffer_info(env, argv[0], &data, &len) == c.napi_ok);
|
|
115
|
-
assert(len == @sizeOf(gen.Battle(gen.PRNG)));
|
|
116
|
-
|
|
117
|
-
var battle: *gen.Battle(gen.PRNG) = @alignCast(@ptrCast(data.?));
|
|
118
|
-
const player: pkmn.Player = @enumFromInt(js.Number.get(env, argv[1], u8));
|
|
119
|
-
const request: pkmn.Choice.Type = @enumFromInt(js.Number.get(env, argv[2], u8));
|
|
120
|
-
|
|
121
|
-
assert(c.napi_get_arraybuffer_info(env, argv[3], &data, &len) == c.napi_ok);
|
|
122
|
-
assert(len == gen.CHOICES_SIZE);
|
|
123
|
-
|
|
124
|
-
const out = @as([*]pkmn.Choice, @ptrCast(data.?))[0..gen.CHOICES_SIZE];
|
|
125
|
-
const n = battle.choices(player, request, out);
|
|
126
|
-
return js.Number.init(env, @as(u8, @bitCast(n)));
|
|
127
|
-
}
|
|
128
|
-
}.call;
|
|
129
|
-
}
|
package/src/lib/pkmn.zig
CHANGED
|
@@ -129,6 +129,7 @@ pub const gen2 = struct {
|
|
|
129
129
|
// NOTE: `pub usingnamespace struct { ... }` here results in a (false?) dependency loop
|
|
130
130
|
pub const js = if (options.internal) @import("common/js.zig") else {};
|
|
131
131
|
pub const bindings = if (options.internal) struct {
|
|
132
|
-
pub const
|
|
133
|
-
pub const
|
|
132
|
+
pub const c = @import("bindings/c.zig");
|
|
133
|
+
pub const node = @import("bindings/node.zig");
|
|
134
|
+
pub const wasm = @import("bindings/wasm.zig");
|
|
134
135
|
} else {};
|
package/src/lib/wasm.zig
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
const pkmn = @import("pkmn.zig");
|
|
2
1
|
const std = @import("std");
|
|
2
|
+
const wasm = @import("bindings/wasm.zig");
|
|
3
3
|
|
|
4
|
-
export const SHOWDOWN =
|
|
5
|
-
export const LOG =
|
|
6
|
-
export const CHANCE =
|
|
7
|
-
export const CALC =
|
|
4
|
+
export const SHOWDOWN = wasm.options.showdown;
|
|
5
|
+
export const LOG = wasm.options.log;
|
|
6
|
+
export const CHANCE = wasm.options.chance;
|
|
7
|
+
export const CALC = wasm.options.calc;
|
|
8
8
|
|
|
9
|
-
export const GEN1_CHOICES_SIZE =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
export const GEN1_CHOICES_SIZE = wasm.gen(1).CHOICES_SIZE;
|
|
10
|
+
export const GEN1_LOGS_SIZE = wasm.gen(1).LOGS_SIZE;
|
|
11
|
+
|
|
12
|
+
const exportable = @hasDecl(std.zig, "Zir") and !@hasField(std.zig.Zir.Inst.Tag, "export_value");
|
|
13
|
+
|
|
14
|
+
usingnamespace if (exportable) struct {
|
|
15
|
+
export const GEN1_update = wasm.gen(1).update;
|
|
16
|
+
export const GEN1_choices = wasm.gen(1).choices;
|
|
17
|
+
} else wasm.exports();
|