@pkmn/engine 0.1.0-dev.3a62fd35 → 0.1.0-dev.3de821b9
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 -3
- package/README.md +13 -11
- package/build/pkg/addon.js +6 -7
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/data.d.ts +2 -3
- package/build/pkg/protocol.js +4 -4
- package/build/pkg/protocol.js.map +1 -1
- package/build/test/display.js +8 -3
- package/build/test/display.js.map +1 -1
- package/build/tools/debug.js +2 -3
- package/build/tools/debug.js.map +1 -1
- package/build.zig +32 -50
- package/package.json +24 -17
- package/src/bin/install-pkmn-engine +13 -5
- package/src/lib/common/debug.zig +2 -2
- package/src/lib/common/options.zig +2 -0
- package/src/lib/common/protocol.zig +11 -9
- package/src/lib/common/rational.zig +102 -12
- package/src/lib/common/rng.zig +6 -6
- package/src/lib/gen1/calc.zig +480 -92
- package/src/lib/gen1/chance.zig +384 -268
- package/src/lib/gen1/data/species.zig +1 -1
- package/src/lib/gen1/data.zig +3 -3
- package/src/lib/gen1/helpers.zig +90 -534
- package/src/lib/gen1/mechanics.zig +266 -158
- package/src/lib/gen2/chance.zig +8 -440
- package/src/lib/gen2/helpers.zig +37 -7
- package/src/lib/gen2/mechanics.zig +119 -61
- package/src/lib/pkmn.zig +4 -12
- package/src/test/display/pkmn.html.tmpl +5 -5
- package/src/lib/gen1/README.md +0 -894
- package/src/lib/gen2/README.md +0 -329
|
@@ -256,7 +256,6 @@ fn switchIn(battle: anytype, player: Player, slot: u8, kind: SwitchIn, options:
|
|
|
256
256
|
} else {
|
|
257
257
|
try options.log.switched(.{ battle.active(player), incoming });
|
|
258
258
|
}
|
|
259
|
-
options.chance.switched(player, side.pokemon[0].position, out);
|
|
260
259
|
|
|
261
260
|
if (side.conditions.Spikes and !active.types.includes(.Flying)) {
|
|
262
261
|
incoming.hp -|= @max(incoming.stats.hp / 8, 1);
|
|
@@ -442,18 +441,9 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
442
441
|
if (Status.is(stored.status, .SLP)) slp: {
|
|
443
442
|
const before = stored.status;
|
|
444
443
|
// Even if the EXT bit is set this will still correctly modify the sleep duration
|
|
445
|
-
// if (options.calc.modify(player, .sleep)) |extend| {
|
|
446
|
-
// if (!extend) stored.status = 0;
|
|
447
|
-
// } else {
|
|
448
444
|
stored.status -= 1;
|
|
449
|
-
// }
|
|
450
|
-
|
|
451
|
-
const duration = Status.duration(stored.status);
|
|
452
|
-
if (!Status.is(stored.status, .EXT)) {
|
|
453
|
-
try options.chance.sleep(player, duration);
|
|
454
|
-
}
|
|
455
445
|
|
|
456
|
-
if (duration == 0) {
|
|
446
|
+
if (Status.duration(stored.status) == 0) {
|
|
457
447
|
try log.curestatus(.{ ident, before, .Message });
|
|
458
448
|
stored.status = 0; // clears EXT if present
|
|
459
449
|
resetCant(volatiles);
|
|
@@ -483,12 +473,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
483
473
|
}
|
|
484
474
|
|
|
485
475
|
if (volatiles.disable.duration > 0) {
|
|
486
|
-
// if (options.calc.modify(player, .disable)) |extend| {
|
|
487
|
-
// if (!extend) volatiles.disable.duration = 0;
|
|
488
|
-
// } else {
|
|
489
476
|
volatiles.disable.duration -= 1;
|
|
490
|
-
// }
|
|
491
|
-
try options.chance.disable(player, volatiles.disable.duration);
|
|
492
477
|
|
|
493
478
|
if (volatiles.disable.duration == 0) {
|
|
494
479
|
volatiles.disable.move = 0;
|
|
@@ -498,13 +483,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
498
483
|
|
|
499
484
|
if (volatiles.Confusion) {
|
|
500
485
|
assert(volatiles.confusion > 0);
|
|
501
|
-
|
|
502
|
-
// if (options.calc.modify(player, .confusion)) |extend| {
|
|
503
|
-
// if (!extend) volatiles.confusion = 0;
|
|
504
|
-
// } else {
|
|
505
486
|
volatiles.confusion -= 1;
|
|
506
|
-
// }
|
|
507
|
-
try options.chance.confusion(player, volatiles.confusion);
|
|
508
487
|
|
|
509
488
|
if (volatiles.confusion == 0) {
|
|
510
489
|
volatiles.Confusion = false;
|
|
@@ -1304,14 +1283,32 @@ pub const Effects = struct {
|
|
|
1304
1283
|
}
|
|
1305
1284
|
|
|
1306
1285
|
pub fn bellyDrum(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1286
|
+
assert(!state.miss);
|
|
1287
|
+
var side = battle.side(player);
|
|
1288
|
+
|
|
1289
|
+
const ident = battle.active(player);
|
|
1290
|
+
|
|
1291
|
+
assert(side.active.boosts.atk >= -6 and side.active.boosts.atk <= 6);
|
|
1292
|
+
if (side.active.boosts.atk == 6 or side.active.boosts.atk == MAX_STAT_VALUE) {
|
|
1293
|
+
return options.log.fail(.{ ident, .None });
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
const hp = @max(side.stored().stats.hp / 2, 1);
|
|
1297
|
+
if (side.stored().hp <= hp) {
|
|
1298
|
+
try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
|
|
1299
|
+
try options.log.boost(.{ ident, .Attack, @as(i8, 2), .Silent });
|
|
1300
|
+
return options.log.fail(.{ ident, .None });
|
|
1301
|
+
} else {
|
|
1302
|
+
side.stored().hp -= hp;
|
|
1303
|
+
try options.log.damage(.{ ident, side.stored(), .None });
|
|
1304
|
+
|
|
1305
|
+
const before = side.active.boosts.atk;
|
|
1306
|
+
var i: usize = 0;
|
|
1307
|
+
while (i < 6 and side.active.stats.atk < MAX_STAT_VALUE) : (i += 1) {
|
|
1308
|
+
try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
|
|
1309
|
+
}
|
|
1310
|
+
try options.log.boost(.{ ident, .Attack, @as(i8, side.active.boosts.atk - before) });
|
|
1311
|
+
}
|
|
1315
1312
|
}
|
|
1316
1313
|
|
|
1317
1314
|
pub const bide = struct {
|
|
@@ -1323,13 +1320,7 @@ pub const Effects = struct {
|
|
|
1323
1320
|
|
|
1324
1321
|
if (!volatiles.Bide) return;
|
|
1325
1322
|
|
|
1326
|
-
// if (options.calc.modify(player, .attacking)) |extend| {
|
|
1327
|
-
// if (!extend) volatiles.attacks = 0;
|
|
1328
|
-
// } else {
|
|
1329
1323
|
volatiles.attacks -= 1;
|
|
1330
|
-
// }
|
|
1331
|
-
try options.chance.attacking(player, volatiles.attacks);
|
|
1332
|
-
|
|
1333
1324
|
if (volatiles.attacks != 0) return options.log.activate(.{ ident, .Bide });
|
|
1334
1325
|
|
|
1335
1326
|
volatiles.Bide = false;
|
|
@@ -1596,8 +1587,16 @@ pub const Effects = struct {
|
|
|
1596
1587
|
try options.log.start(.{ foe_ident, .Encore, move.id });
|
|
1597
1588
|
}
|
|
1598
1589
|
|
|
1599
|
-
pub fn explode(battle: anytype, player: Player,
|
|
1600
|
-
|
|
1590
|
+
pub fn explode(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
1591
|
+
var side = battle.side(player);
|
|
1592
|
+
var stored = side.stored();
|
|
1593
|
+
|
|
1594
|
+
stored.hp = 0;
|
|
1595
|
+
// Pokémon Showdown sets the status to 0 on faint(), and we need to do the same to be able
|
|
1596
|
+
// to correctly implement Pokémon Showdown's dumb speed-based switch mechanics
|
|
1597
|
+
if (!showdown) stored.status = 0;
|
|
1598
|
+
side.active.volatiles.LeechSeed = false;
|
|
1599
|
+
side.active.volatiles.DestinyBond = false;
|
|
1601
1600
|
}
|
|
1602
1601
|
|
|
1603
1602
|
pub fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
@@ -1723,8 +1722,26 @@ pub const Effects = struct {
|
|
|
1723
1722
|
foe.active.volatiles.frozen = true;
|
|
1724
1723
|
}
|
|
1725
1724
|
|
|
1726
|
-
|
|
1727
|
-
|
|
1725
|
+
// TODO FIXME showdown calls this before calcDamage (after checkHit) and modifies state.bp!
|
|
1726
|
+
pub fn furyCutter(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1727
|
+
var side = battle.side(player);
|
|
1728
|
+
var volatiles = &side.active.volatiles;
|
|
1729
|
+
|
|
1730
|
+
if (state.miss) {
|
|
1731
|
+
volatiles.fury_cutter = 0;
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
assert(volatiles.fury_cutter >= 0 and volatiles.fury_cutter <= 5);
|
|
1736
|
+
if (volatiles.fury_cutter == 5) return;
|
|
1737
|
+
volatiles.fury_cutter += 1;
|
|
1738
|
+
|
|
1739
|
+
if (showdown) {
|
|
1740
|
+
state.bp *= 2;
|
|
1741
|
+
assert(state.bp <= 160);
|
|
1742
|
+
} else {
|
|
1743
|
+
state.damage *|= 2;
|
|
1744
|
+
}
|
|
1728
1745
|
}
|
|
1729
1746
|
|
|
1730
1747
|
pub fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1885,7 +1902,29 @@ pub const Effects = struct {
|
|
|
1885
1902
|
}
|
|
1886
1903
|
|
|
1887
1904
|
pub fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1888
|
-
|
|
1905
|
+
var side = battle.side(player);
|
|
1906
|
+
|
|
1907
|
+
if (state.miss) {
|
|
1908
|
+
if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1909
|
+
try options.log.lastmiss(.{});
|
|
1910
|
+
return options.log.miss(.{battle.active(player)});
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
const move = battle.foe(player).lastMove(false);
|
|
1914
|
+
if (move == .None or move == .Struggle) {
|
|
1915
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
var slot: u4 = 0;
|
|
1919
|
+
for (side.active.moves, 0..) |m, i| {
|
|
1920
|
+
if (m.id == move) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1921
|
+
if (m.id == .Mimic) slot = @intCast(i + 1);
|
|
1922
|
+
}
|
|
1923
|
+
assert(slot != 0 and slot == state.mslot);
|
|
1924
|
+
|
|
1925
|
+
side.active.move(slot).id = move;
|
|
1926
|
+
side.active.move(slot).pp = 5;
|
|
1927
|
+
try options.log.start(.{ battle.active(player), .Mimic, move });
|
|
1889
1928
|
}
|
|
1890
1929
|
|
|
1891
1930
|
pub fn mirrorMove(
|
|
@@ -2393,17 +2432,37 @@ pub const Effects = struct {
|
|
|
2393
2432
|
}
|
|
2394
2433
|
|
|
2395
2434
|
pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2396
|
-
|
|
2397
|
-
// var foe = battle.foe(player);
|
|
2435
|
+
var foe = battle.foe(player);
|
|
2398
2436
|
|
|
2399
|
-
|
|
2437
|
+
const last = foe.lastMove(false);
|
|
2438
|
+
if (state.miss or last == .None or last == .Struggle) {
|
|
2439
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2440
|
+
}
|
|
2400
2441
|
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
//
|
|
2406
|
-
|
|
2442
|
+
var slot: u4 = 0;
|
|
2443
|
+
for (foe.active.moves, 0..) |m, i| {
|
|
2444
|
+
if (m.id == last) slot = @intCast(i + 1);
|
|
2445
|
+
}
|
|
2446
|
+
// If the move was called via Metronome it might not exist
|
|
2447
|
+
if (slot == 0 or foe.active.move(slot).pp == 0) {
|
|
2448
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
// Pokémon Showdown always deducts 4 instead of randomizing
|
|
2452
|
+
const pp = @min(
|
|
2453
|
+
foe.active.move(slot).pp,
|
|
2454
|
+
if (showdown) 4 else try Rolls.spite(battle, player, options),
|
|
2455
|
+
);
|
|
2456
|
+
foe.active.move(slot).pp -= pp;
|
|
2457
|
+
if (!foe.active.volatiles.Transform) {
|
|
2458
|
+
var move_slot = foe.stored().move(slot);
|
|
2459
|
+
assert(move_slot.pp > 0);
|
|
2460
|
+
|
|
2461
|
+
move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
|
|
2462
|
+
assert(foe.active.move(slot).pp == foe.stored().move(slot).pp);
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
try options.log.activate(.{ battle.active(player.foe()), .Spite, last, pp });
|
|
2407
2466
|
}
|
|
2408
2467
|
|
|
2409
2468
|
pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
@@ -3009,13 +3068,12 @@ pub const Rolls = struct {
|
|
|
3009
3068
|
return hits;
|
|
3010
3069
|
}
|
|
3011
3070
|
|
|
3012
|
-
fn spite(battle: anytype, player: Player, options: anytype) !
|
|
3013
|
-
|
|
3071
|
+
fn spite(battle: anytype, player: Player, options: anytype) !u3 {
|
|
3072
|
+
assert(!showdown);
|
|
3073
|
+
const pp: u3 = if (options.calc.overridden(player, .spite)) |val|
|
|
3014
3074
|
val + 1
|
|
3015
|
-
else if (showdown)
|
|
3016
|
-
battle.rng.range(u8, 2, 6)
|
|
3017
3075
|
else
|
|
3018
|
-
(battle.rng.next() & 3) + 2;
|
|
3076
|
+
@intCast((battle.rng.next() & 3) + 2);
|
|
3019
3077
|
|
|
3020
3078
|
assert(pp >= 2 and pp <= 5);
|
|
3021
3079
|
try options.chance.spite(player, pp);
|
|
@@ -3277,7 +3335,7 @@ pub const Rolls = struct {
|
|
|
3277
3335
|
};
|
|
3278
3336
|
|
|
3279
3337
|
assert(duration >= 1 and duration <= 6);
|
|
3280
|
-
options.chance.duration(
|
|
3338
|
+
options.chance.duration(player, duration);
|
|
3281
3339
|
return duration + 1;
|
|
3282
3340
|
}
|
|
3283
3341
|
|
|
@@ -3291,7 +3349,7 @@ pub const Rolls = struct {
|
|
|
3291
3349
|
@intCast((battle.rng.next() & if (self) 1 else 3) + 2);
|
|
3292
3350
|
|
|
3293
3351
|
assert(duration >= 2 and duration <= if (!showdown and self) 3 else 5);
|
|
3294
|
-
options.chance.duration(
|
|
3352
|
+
options.chance.duration(player, duration);
|
|
3295
3353
|
return duration;
|
|
3296
3354
|
}
|
|
3297
3355
|
|
|
@@ -3309,7 +3367,7 @@ pub const Rolls = struct {
|
|
|
3309
3367
|
};
|
|
3310
3368
|
|
|
3311
3369
|
assert(duration >= 2 and duration <= (if (showdown) 5 else 7));
|
|
3312
|
-
options.chance.duration(
|
|
3370
|
+
options.chance.duration(player, duration);
|
|
3313
3371
|
return duration;
|
|
3314
3372
|
}
|
|
3315
3373
|
|
|
@@ -3322,7 +3380,7 @@ pub const Rolls = struct {
|
|
|
3322
3380
|
@intCast((battle.rng.next() & 1) + 2); // BUG: thrash is + 1 not +2
|
|
3323
3381
|
|
|
3324
3382
|
assert(duration >= 2 and duration <= 3);
|
|
3325
|
-
options.chance.duration(
|
|
3383
|
+
options.chance.duration(player, duration);
|
|
3326
3384
|
return duration;
|
|
3327
3385
|
}
|
|
3328
3386
|
|
|
@@ -3336,7 +3394,7 @@ pub const Rolls = struct {
|
|
|
3336
3394
|
@intCast(battle.rng.next() & 3 + 2));
|
|
3337
3395
|
|
|
3338
3396
|
assert(duration >= 2 and duration <= 5);
|
|
3339
|
-
options.chance.duration(
|
|
3397
|
+
options.chance.duration(player, duration);
|
|
3340
3398
|
return duration + 1;
|
|
3341
3399
|
}
|
|
3342
3400
|
|
|
@@ -3350,7 +3408,7 @@ pub const Rolls = struct {
|
|
|
3350
3408
|
@intCast(battle.rng.next() & 3 + 2));
|
|
3351
3409
|
|
|
3352
3410
|
assert(duration >= 2 and duration <= 5);
|
|
3353
|
-
options.chance.duration(
|
|
3411
|
+
options.chance.duration(player, duration);
|
|
3354
3412
|
return duration + 1;
|
|
3355
3413
|
}
|
|
3356
3414
|
};
|
package/src/lib/pkmn.zig
CHANGED
|
@@ -26,9 +26,7 @@ pub const Result = @import("common/data.zig").Result;
|
|
|
26
26
|
pub const PSRNG = @import("common/rng.zig").PSRNG;
|
|
27
27
|
|
|
28
28
|
/// Namespace for helpers related to protocol message logging.
|
|
29
|
-
pub const protocol = if (options.internal) struct {
|
|
30
|
-
pub usingnamespace @import("common/protocol.zig");
|
|
31
|
-
} else struct {
|
|
29
|
+
pub const protocol = if (options.internal) @import("common/protocol.zig") else struct {
|
|
32
30
|
/// Logs protocol information to its `Writer` during a battle update when
|
|
33
31
|
/// `options.log` is enabled.
|
|
34
32
|
pub const Log = @import("common/protocol.zig").Log;
|
|
@@ -82,9 +80,7 @@ pub const gen1 = struct {
|
|
|
82
80
|
/// via `overrides` and tracks `summaries` of information relevant to damage calculation.
|
|
83
81
|
pub const Calc = @import("gen1/calc.zig").Calc;
|
|
84
82
|
/// Namespace for types associated with supported Generation I Pokémon damage calc features.
|
|
85
|
-
pub const calc = if (options.internal) struct {
|
|
86
|
-
pub usingnamespace @import("gen1/calc.zig");
|
|
87
|
-
} else struct {
|
|
83
|
+
pub const calc = if (options.internal) @import("gen1/calc.zig") else struct {
|
|
88
84
|
/// TODO
|
|
89
85
|
pub const Overrides = @import("gen1/calc.zig").Overrides;
|
|
90
86
|
/// Information relevant to damage calculation that occured during a Generation I
|
|
@@ -114,9 +110,7 @@ pub const gen2 = struct {
|
|
|
114
110
|
pub const NULL = @import("gen2/chance.zig").NULL;
|
|
115
111
|
};
|
|
116
112
|
pub const Calc = @import("gen2/calc.zig").Calc;
|
|
117
|
-
pub const calc = if (options.internal) struct {
|
|
118
|
-
pub usingnamespace @import("gen2/calc.zig");
|
|
119
|
-
} else struct {
|
|
113
|
+
pub const calc = if (options.internal) @import("gen2/calc.zig") else struct {
|
|
120
114
|
pub const Overrides = @import("gen2/calc.zig").Overrides;
|
|
121
115
|
pub const Summaries = @import("gen2/calc.zig").Summaries;
|
|
122
116
|
pub const Summary = @import("gen2/calc.zig").Summary;
|
|
@@ -127,9 +121,7 @@ pub const gen2 = struct {
|
|
|
127
121
|
|
|
128
122
|
// Internal APIs used by other pkmn libraries, not actually part of the public API.
|
|
129
123
|
// NOTE: `pub usingnamespace struct { ... }` here results in a (false?) dependency loop
|
|
130
|
-
pub const js = if (options.internal) struct {
|
|
131
|
-
pub usingnamespace @import("common/js.zig");
|
|
132
|
-
} else struct {};
|
|
124
|
+
pub const js = if (options.internal) @import("common/js.zig") else struct {};
|
|
133
125
|
pub const bindings = if (options.internal) struct {
|
|
134
126
|
pub const node = @import("node.zig");
|
|
135
127
|
pub const wasm = @import("wasm.zig");
|
|
@@ -218,8 +218,9 @@
|
|
|
218
218
|
border-radius: 0 4px 4px 0;
|
|
219
219
|
}
|
|
220
220
|
.moves ul {
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
display: grid;
|
|
222
|
+
grid-template-columns: repeat(2, auto);
|
|
223
|
+
gap: 0 2em;
|
|
223
224
|
}
|
|
224
225
|
.left {
|
|
225
226
|
padding-right: 32px;
|
|
@@ -268,7 +269,7 @@
|
|
|
268
269
|
}
|
|
269
270
|
@media(max-width: 1200px) {
|
|
270
271
|
.moves ul {
|
|
271
|
-
|
|
272
|
+
grid-template-columns: repeat(1, auto);
|
|
272
273
|
}
|
|
273
274
|
}
|
|
274
275
|
@media(max-width: 1100px) {
|
|
@@ -283,7 +284,7 @@
|
|
|
283
284
|
margin-left: 5px;
|
|
284
285
|
}
|
|
285
286
|
.moves ul {
|
|
286
|
-
|
|
287
|
+
grid-template-columns: repeat(2, auto);
|
|
287
288
|
}
|
|
288
289
|
.left {
|
|
289
290
|
padding-right: 0;
|
|
@@ -309,7 +310,6 @@
|
|
|
309
310
|
justify-content: center;
|
|
310
311
|
}
|
|
311
312
|
.moves ul {
|
|
312
|
-
column-count: 1;
|
|
313
313
|
display: inline-block;
|
|
314
314
|
padding: 0;
|
|
315
315
|
}
|