@pkmn/engine 0.1.0-dev.10889360 → 0.1.0-dev.115ecc94
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 +0 -2
- 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 +2 -2
- 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 +10 -8
- package/src/lib/common/rational.zig +102 -12
- package/src/lib/common/rng.zig +6 -6
- package/src/lib/gen1/calc.zig +266 -97
- package/src/lib/gen1/chance.zig +114 -148
- package/src/lib/gen1/data/species.zig +1 -1
- package/src/lib/gen1/data.zig +3 -3
- package/src/lib/gen1/helpers.zig +184 -199
- package/src/lib/gen1/mechanics.zig +91 -54
- package/src/lib/gen2/chance.zig +193 -103
- package/src/lib/gen2/helpers.zig +37 -7
- package/src/lib/gen2/mechanics.zig +112 -27
- 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
|
@@ -166,7 +166,7 @@ fn selectMove(
|
|
|
166
166
|
// to satisfy the conditions of the Desync Clause Mod. However, because Binding is still
|
|
167
167
|
// set the selected move will not actually be used, it will just be reported as having
|
|
168
168
|
// been used (this differs from how Pokémon Showdown works, but its impossible to
|
|
169
|
-
// replicate the incorrect behavior with the correct mechanisms)
|
|
169
|
+
// replicate the incorrect behavior with the correct mechanisms)
|
|
170
170
|
saveMove(battle, player, choice);
|
|
171
171
|
} else {
|
|
172
172
|
assert(choice.data == 0);
|
|
@@ -347,10 +347,10 @@ fn doTurn(
|
|
|
347
347
|
)) |r| return r;
|
|
348
348
|
if (!replace) {
|
|
349
349
|
if (player_choice.type != .Switch) {
|
|
350
|
-
if (try checkFaint(battle, foe_player, options)) |r| return r;
|
|
350
|
+
if (try checkFaint(battle, foe_player, true, options)) |r| return r;
|
|
351
351
|
}
|
|
352
352
|
if (residual) try handleResidual(battle, player, options);
|
|
353
|
-
if (try checkFaint(battle, player, options)) |r| return r;
|
|
353
|
+
if (try checkFaint(battle, player, false, options)) |r| return r;
|
|
354
354
|
} else if (foe_choice.type == .Pass) return null;
|
|
355
355
|
|
|
356
356
|
options.chance.clearPending();
|
|
@@ -369,10 +369,10 @@ fn doTurn(
|
|
|
369
369
|
)) |r| return r;
|
|
370
370
|
if (!replace) {
|
|
371
371
|
if (!calc and foe_choice.type != .Switch) {
|
|
372
|
-
if (try checkFaint(battle, player, options)) |r| return r;
|
|
372
|
+
if (try checkFaint(battle, player, true, options)) |r| return r;
|
|
373
373
|
}
|
|
374
374
|
if (residual) try handleResidual(battle, foe_player, options);
|
|
375
|
-
if (try checkFaint(battle, foe_player, options)) |r| return r;
|
|
375
|
+
if (try checkFaint(battle, foe_player, false, options)) |r| return r;
|
|
376
376
|
}
|
|
377
377
|
|
|
378
378
|
// Flinch is bugged on Pokémon Showdown because it gets implemented with a duration which causes
|
|
@@ -431,7 +431,7 @@ fn executeMove(
|
|
|
431
431
|
Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP);
|
|
432
432
|
// If it wasn't Hyper Beam or the continuation of a move effect then we must have just
|
|
433
433
|
// thawed, in which case we will desync unless the last_selected_move happened to be at
|
|
434
|
-
// index 1 and the current Pokémon has the same move in its first slot
|
|
434
|
+
// index 1 and the current Pokémon has the same move in its first slot
|
|
435
435
|
if (!auto) {
|
|
436
436
|
// side.active.moves(slot) is safe to check even though the slot in question might not
|
|
437
437
|
// technically be from this Pokémon because it must be exactly 1 to not desync and
|
|
@@ -852,7 +852,7 @@ fn doMove(
|
|
|
852
852
|
break :miss true;
|
|
853
853
|
}
|
|
854
854
|
break :miss (move.target != .Self and
|
|
855
|
-
!moveHit(battle, player, move, &immune, &mist, options));
|
|
855
|
+
!try moveHit(battle, player, move, &immune, &mist, options));
|
|
856
856
|
};
|
|
857
857
|
assert(!immune or miss or (showdown and move.effect == .Binding));
|
|
858
858
|
|
|
@@ -899,7 +899,7 @@ fn doMove(
|
|
|
899
899
|
miss = if (showdown or skip)
|
|
900
900
|
miss
|
|
901
901
|
else
|
|
902
|
-
(!moveHit(battle, player, move, &immune, &mist, options) or zero);
|
|
902
|
+
(!try moveHit(battle, player, move, &immune, &mist, options) or zero);
|
|
903
903
|
|
|
904
904
|
assert(showdown or miss or battle.last_damage > 0 or skip);
|
|
905
905
|
assert((!showdown and miss) or !(ohko and immune));
|
|
@@ -930,7 +930,7 @@ fn doMove(
|
|
|
930
930
|
if (!foe.active.volatiles.Substitute) try log.activate(.{ foe_ident, .Mist });
|
|
931
931
|
try log.fail(.{ foe_ident, .None });
|
|
932
932
|
} else {
|
|
933
|
-
if (showdown
|
|
933
|
+
if (!showdown and !zero) try options.chance.commit(player, .miss);
|
|
934
934
|
try log.lastmiss(.{});
|
|
935
935
|
try log.miss(.{battle.active(player)});
|
|
936
936
|
}
|
|
@@ -949,13 +949,13 @@ fn doMove(
|
|
|
949
949
|
try options.chance.commit(player, .binding);
|
|
950
950
|
}
|
|
951
951
|
return null;
|
|
952
|
-
} else {
|
|
952
|
+
} else if (!showdown) {
|
|
953
953
|
try options.chance.commit(player, .hit);
|
|
954
954
|
}
|
|
955
955
|
|
|
956
956
|
// On the cartridge MultiHit doesn't get set up until after damage has been applied for the
|
|
957
957
|
// first time but its more convenient and efficient to set it up here (Pokémon Showdown sets
|
|
958
|
-
// it up above before damage calculation)
|
|
958
|
+
// it up above before damage calculation)
|
|
959
959
|
if (!showdown and move.effect.isMulti()) {
|
|
960
960
|
try Effects.multiHit(battle, player, move, options);
|
|
961
961
|
hits = side.active.volatiles.attacks;
|
|
@@ -1014,12 +1014,12 @@ fn doMove(
|
|
|
1014
1014
|
}
|
|
1015
1015
|
|
|
1016
1016
|
// Substitute being broken nullifies the move's effect completely so even
|
|
1017
|
-
// if an effect was intended to "always happen" it will still get skipped
|
|
1017
|
+
// if an effect was intended to "always happen" it will still get skipped
|
|
1018
1018
|
if (nullified) return null;
|
|
1019
1019
|
|
|
1020
1020
|
// On the cartridge, "always happen" effect handlers are called in the applyDamage loop above,
|
|
1021
1021
|
// but this is only done to setup the MultiHit looping in the first place. Moving the MultiHit
|
|
1022
|
-
// setup before the loop means we can avoid having to waste time doing no-op handler searches
|
|
1022
|
+
// setup before the loop means we can avoid having to waste time doing no-op handler searches
|
|
1023
1023
|
if (move.effect.alwaysHappens()) try alwaysHappens(battle, player, move, residual, options);
|
|
1024
1024
|
|
|
1025
1025
|
if (foe.stored().hp == 0) return null;
|
|
@@ -1034,7 +1034,7 @@ fn doMove(
|
|
|
1034
1034
|
// below). For Twineedle we change the data to that of one with PoisonChance1 given its
|
|
1035
1035
|
// MultiHit behavior is complete after the loop above, though Pokémon Showdown handles the
|
|
1036
1036
|
// Twineedle secondary effect in the MultiHit cleanup block above because it incorrectly
|
|
1037
|
-
// puts the |-status| message before |-hitcount| instead of after
|
|
1037
|
+
// puts the |-status| message before |-hitcount| instead of after
|
|
1038
1038
|
if (move.effect == .Twineedle) {
|
|
1039
1039
|
if (!showdown) try Effects.poison(battle, player, Move.get(.PoisonSting), options);
|
|
1040
1040
|
} else if (move.effect == .Disable) {
|
|
@@ -1058,7 +1058,9 @@ fn checkCriticalHit(battle: anytype, player: Player, move: Move.Data, options: a
|
|
|
1058
1058
|
rate = if (side.active.volatiles.FocusEnergy) rate / 2 else @min(rate * 2, 255);
|
|
1059
1059
|
rate = if (move.effect == .HighCritical) @min(rate * 4, 255) else rate / 2;
|
|
1060
1060
|
|
|
1061
|
-
|
|
1061
|
+
const spurious =
|
|
1062
|
+
!showdown and ((move.bp == 0 and move.effect != .OHKO) or move.target == .Depends);
|
|
1063
|
+
return Rolls.criticalHit(battle, player, @intCast(rate), spurious, options);
|
|
1062
1064
|
}
|
|
1063
1065
|
|
|
1064
1066
|
fn calcDamage(
|
|
@@ -1173,7 +1175,6 @@ fn specialDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1173
1175
|
const foe = battle.foe(player);
|
|
1174
1176
|
|
|
1175
1177
|
if (!try checkHit(battle, player, move, options)) return null;
|
|
1176
|
-
try options.chance.commit(player, .hit);
|
|
1177
1178
|
|
|
1178
1179
|
battle.last_damage = switch (side.last_selected_move) {
|
|
1179
1180
|
.SuperFang => @max(foe.stored().hp / 2, 1),
|
|
@@ -1234,9 +1235,8 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1234
1235
|
|
|
1235
1236
|
battle.last_damage *|= 2;
|
|
1236
1237
|
|
|
1237
|
-
// Pokémon Showdown calls
|
|
1238
|
+
// Pokémon Showdown calls moveHit before Counter
|
|
1238
1239
|
if (!showdown and !try checkHit(battle, player, move, options)) return null;
|
|
1239
|
-
try options.chance.commit(player, .hit);
|
|
1240
1240
|
|
|
1241
1241
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
1242
1242
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -1265,7 +1265,7 @@ fn applyDamage(
|
|
|
1265
1265
|
subbed.active.volatiles.Substitute = false;
|
|
1266
1266
|
// battle.last_damage is not updated with the amount of HP the Substitute had
|
|
1267
1267
|
try options.log.end(.{ battle.active(sub_player), .Substitute });
|
|
1268
|
-
options.calc.capped(
|
|
1268
|
+
options.calc.capped(sub_player);
|
|
1269
1269
|
return true;
|
|
1270
1270
|
} else {
|
|
1271
1271
|
// Safe to truncate since less than subbed.volatiles.substitute which is a u8
|
|
@@ -1332,7 +1332,10 @@ fn checkHit(battle: anytype, player: Player, move: Move.Data, options: anytype)
|
|
|
1332
1332
|
var immune = false;
|
|
1333
1333
|
var mist = false;
|
|
1334
1334
|
|
|
1335
|
-
if (moveHit(battle, player, move, &immune, &mist, options))
|
|
1335
|
+
if (try moveHit(battle, player, move, &immune, &mist, options)) {
|
|
1336
|
+
if (!showdown) try options.chance.commit(player, .hit);
|
|
1337
|
+
return true;
|
|
1338
|
+
}
|
|
1336
1339
|
|
|
1337
1340
|
assert(!immune);
|
|
1338
1341
|
if (mist) {
|
|
@@ -1341,11 +1344,11 @@ fn checkHit(battle: anytype, player: Player, move: Move.Data, options: anytype)
|
|
|
1341
1344
|
try options.log.activate(.{ foe_ident, .Mist });
|
|
1342
1345
|
try options.log.fail(.{ foe_ident, .None });
|
|
1343
1346
|
} else {
|
|
1344
|
-
try options.chance.commit(player, .miss);
|
|
1345
1347
|
try options.log.lastmiss(.{});
|
|
1346
1348
|
try options.log.miss(.{battle.active(player)});
|
|
1347
1349
|
}
|
|
1348
1350
|
|
|
1351
|
+
if (!showdown) try options.chance.commit(player, .miss);
|
|
1349
1352
|
return false;
|
|
1350
1353
|
}
|
|
1351
1354
|
|
|
@@ -1356,7 +1359,7 @@ fn moveHit(
|
|
|
1356
1359
|
immune: *bool,
|
|
1357
1360
|
mist: *bool,
|
|
1358
1361
|
options: anytype,
|
|
1359
|
-
) bool {
|
|
1362
|
+
) !bool {
|
|
1360
1363
|
var side = battle.side(player);
|
|
1361
1364
|
const foe = battle.foe(player);
|
|
1362
1365
|
|
|
@@ -1407,7 +1410,7 @@ fn moveHit(
|
|
|
1407
1410
|
if (overwrite) side.active.volatiles.state = accuracy;
|
|
1408
1411
|
|
|
1409
1412
|
// GLITCH: max accuracy is 255 so 1/256 chance of miss
|
|
1410
|
-
break :miss !Rolls.hit(battle, player, @intCast(accuracy), options);
|
|
1413
|
+
break :miss !try Rolls.hit(battle, player, @intCast(accuracy), options);
|
|
1411
1414
|
};
|
|
1412
1415
|
|
|
1413
1416
|
// Pokémon Showdown reports miss instead of fail for moves blocked by Mist that 1/256 miss
|
|
@@ -1425,6 +1428,7 @@ fn moveHit(
|
|
|
1425
1428
|
fn checkFaint(
|
|
1426
1429
|
battle: anytype,
|
|
1427
1430
|
player: Player,
|
|
1431
|
+
cap: bool,
|
|
1428
1432
|
options: anytype,
|
|
1429
1433
|
) @TypeOf(options.log).Error!?Result {
|
|
1430
1434
|
const side = battle.side(player);
|
|
@@ -1437,8 +1441,8 @@ fn checkFaint(
|
|
|
1437
1441
|
const foe_out = foe_fainted and findFirstAlive(foe) == 0;
|
|
1438
1442
|
const more = player_out or foe_out;
|
|
1439
1443
|
|
|
1440
|
-
if (try faint(battle, player, !(more or foe_fainted), options)) |r| return r;
|
|
1441
|
-
if (foe_fainted) if (try faint(battle, player.foe(), !more, options)) |r| return r;
|
|
1444
|
+
if (try faint(battle, player, !(more or foe_fainted), cap, options)) |r| return r;
|
|
1445
|
+
if (foe_fainted) if (try faint(battle, player.foe(), !more, false, options)) |r| return r;
|
|
1442
1446
|
|
|
1443
1447
|
assert(!side.active.volatiles.MultiHit);
|
|
1444
1448
|
assert(!foe.active.volatiles.MultiHit);
|
|
@@ -1459,7 +1463,7 @@ fn checkFaint(
|
|
|
1459
1463
|
return .{ .p1 = foe_choice, .p2 = .Switch };
|
|
1460
1464
|
}
|
|
1461
1465
|
|
|
1462
|
-
fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result {
|
|
1466
|
+
fn faint(battle: anytype, player: Player, done: bool, cap: bool, options: anytype) !?Result {
|
|
1463
1467
|
var side = battle.side(player);
|
|
1464
1468
|
var foe = battle.foe(player);
|
|
1465
1469
|
assert(side.stored().hp == 0);
|
|
@@ -1493,7 +1497,9 @@ fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result
|
|
|
1493
1497
|
}
|
|
1494
1498
|
|
|
1495
1499
|
try options.log.faint(.{ battle.active(player), done });
|
|
1496
|
-
|
|
1500
|
+
// We can only safely set capped if the faint occured as the direct result of an opponent's
|
|
1501
|
+
// move (as opposed to due to residual damage, recoil, Explosion, etc)
|
|
1502
|
+
if (cap) options.calc.capped(player);
|
|
1497
1503
|
return null;
|
|
1498
1504
|
}
|
|
1499
1505
|
|
|
@@ -1813,7 +1819,6 @@ pub const Effects = struct {
|
|
|
1813
1819
|
if (sub) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1814
1820
|
if (!try checkHit(battle, player, move, options)) return;
|
|
1815
1821
|
}
|
|
1816
|
-
try options.chance.commit(player, .hit);
|
|
1817
1822
|
}
|
|
1818
1823
|
|
|
1819
1824
|
if (foe.active.volatiles.Confusion) return;
|
|
@@ -1846,10 +1851,7 @@ pub const Effects = struct {
|
|
|
1846
1851
|
const foe_ident = battle.active(player.foe());
|
|
1847
1852
|
|
|
1848
1853
|
// Pokémon Showdown handles hit/miss earlier in doMove
|
|
1849
|
-
if (!showdown)
|
|
1850
|
-
if (!try checkHit(battle, player, move, options)) return null;
|
|
1851
|
-
try options.chance.commit(player, .hit);
|
|
1852
|
-
}
|
|
1854
|
+
if (!showdown and !try checkHit(battle, player, move, options)) return null;
|
|
1853
1855
|
|
|
1854
1856
|
if (volatiles.disable_move != 0) {
|
|
1855
1857
|
try options.log.fail(.{ foe_ident, .None });
|
|
@@ -2051,7 +2053,6 @@ pub const Effects = struct {
|
|
|
2051
2053
|
}
|
|
2052
2054
|
}
|
|
2053
2055
|
|
|
2054
|
-
try options.chance.commit(player, .hit);
|
|
2055
2056
|
foe.active.volatiles.LeechSeed = true;
|
|
2056
2057
|
|
|
2057
2058
|
try options.log.start(.{ battle.active(player.foe()), .LeechSeed });
|
|
@@ -2092,12 +2093,10 @@ pub const Effects = struct {
|
|
|
2092
2093
|
if (!has_mimic) {
|
|
2093
2094
|
// Invulnerable foes or 1/256 miss can trigger |-miss| instead of |-fail|
|
|
2094
2095
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2095
|
-
try options.chance.commit(player, .hit);
|
|
2096
2096
|
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2097
2097
|
}
|
|
2098
2098
|
}
|
|
2099
2099
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2100
|
-
try options.chance.commit(player, .hit);
|
|
2101
2100
|
|
|
2102
2101
|
const rslot = try Rolls.moveSlot(battle, player, &foe.active.moves, 0, options);
|
|
2103
2102
|
side.active.move(oslot).id = foe.active.move(rslot).id;
|
|
@@ -2151,7 +2150,6 @@ pub const Effects = struct {
|
|
|
2151
2150
|
if (immune) return log.immune(.{ foe_ident, .None });
|
|
2152
2151
|
if (!try checkHit(battle, player, move, options)) return;
|
|
2153
2152
|
}
|
|
2154
|
-
try options.chance.commit(player, .hit);
|
|
2155
2153
|
|
|
2156
2154
|
foe_stored.status = Status.init(.PAR);
|
|
2157
2155
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
@@ -2219,7 +2217,6 @@ pub const Effects = struct {
|
|
|
2219
2217
|
|
|
2220
2218
|
if (move.effect == .Poison) {
|
|
2221
2219
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
2222
|
-
try options.chance.commit(player, .hit);
|
|
2223
2220
|
} else {
|
|
2224
2221
|
if (!try Rolls.poisonChance(battle, player, move.effect == .PoisonChance1, options)) {
|
|
2225
2222
|
return;
|
|
@@ -2288,9 +2285,8 @@ pub const Effects = struct {
|
|
|
2288
2285
|
if (Status.is(foe_stored.status, .SLP)) Fail.Sleep else Fail.None,
|
|
2289
2286
|
});
|
|
2290
2287
|
}
|
|
2291
|
-
// If
|
|
2288
|
+
// If moveHit in doMove didn't return true Pokémon Showdown wouldn't be in here
|
|
2292
2289
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
2293
|
-
try options.chance.commit(player, .hit);
|
|
2294
2290
|
}
|
|
2295
2291
|
|
|
2296
2292
|
// Sleep Clause Mod
|
|
@@ -2343,9 +2339,7 @@ pub const Effects = struct {
|
|
|
2343
2339
|
// Whirlwind/Roar should not roll to hit/reset damage but Pokémon Showdown does anyway
|
|
2344
2340
|
if (showdown) {
|
|
2345
2341
|
if (battle.side(player).last_selected_move == .Teleport) return;
|
|
2346
|
-
|
|
2347
|
-
try options.chance.commit(player, .hit);
|
|
2348
|
-
}
|
|
2342
|
+
_ = try checkHit(battle, player, move, options);
|
|
2349
2343
|
battle.last_damage = 0;
|
|
2350
2344
|
} else {
|
|
2351
2345
|
try options.log.fail(.{ battle.active(player), .None });
|
|
@@ -2521,7 +2515,6 @@ pub const Effects = struct {
|
|
|
2521
2515
|
} else {
|
|
2522
2516
|
// checkHit already checks for Invulnerable
|
|
2523
2517
|
if (!showdown and !try checkHit(battle, player, move, options)) return;
|
|
2524
|
-
try options.chance.commit(player, .hit);
|
|
2525
2518
|
}
|
|
2526
2519
|
|
|
2527
2520
|
var stats = &foe.active.stats;
|
|
@@ -2689,6 +2682,8 @@ pub const Rolls = struct {
|
|
|
2689
2682
|
fn speedTie(battle: anytype, options: anytype) !bool {
|
|
2690
2683
|
const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
|
|
2691
2684
|
player == .P1
|
|
2685
|
+
else if (@hasDecl(@TypeOf(battle.rng), "speedTie"))
|
|
2686
|
+
battle.rng.speedTie()
|
|
2692
2687
|
else if (showdown)
|
|
2693
2688
|
battle.rng.range(u8, 0, 2) == 0
|
|
2694
2689
|
else
|
|
@@ -2698,21 +2693,31 @@ pub const Rolls = struct {
|
|
|
2698
2693
|
return p1;
|
|
2699
2694
|
}
|
|
2700
2695
|
|
|
2701
|
-
fn criticalHit(
|
|
2696
|
+
fn criticalHit(
|
|
2697
|
+
battle: anytype,
|
|
2698
|
+
player: Player,
|
|
2699
|
+
rate: u8,
|
|
2700
|
+
spurious: bool,
|
|
2701
|
+
options: anytype,
|
|
2702
|
+
) !bool {
|
|
2702
2703
|
const crit = if (options.calc.overridden(player, .critical_hit)) |val|
|
|
2703
2704
|
val == .true
|
|
2705
|
+
else if (@hasDecl(@TypeOf(battle.rng), "criticalHit"))
|
|
2706
|
+
battle.rng.criticalHit(player, rate)
|
|
2704
2707
|
else if (showdown)
|
|
2705
2708
|
battle.rng.chance(u8, rate, 256)
|
|
2706
2709
|
else
|
|
2707
2710
|
std.math.rotl(u8, battle.rng.next(), 3) < rate;
|
|
2708
2711
|
|
|
2709
|
-
try options.chance.criticalHit(player, crit, rate);
|
|
2712
|
+
if (!spurious) try options.chance.criticalHit(player, crit, rate);
|
|
2710
2713
|
return crit;
|
|
2711
2714
|
}
|
|
2712
2715
|
|
|
2713
2716
|
fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2714
2717
|
const roll = if (options.calc.overridden(player, .damage)) |val|
|
|
2715
2718
|
val
|
|
2719
|
+
else if (@hasDecl(@TypeOf(battle.rng), "damage"))
|
|
2720
|
+
battle.rng.damage(player)
|
|
2716
2721
|
else roll: {
|
|
2717
2722
|
if (showdown) break :roll battle.rng.range(u8, 217, 256);
|
|
2718
2723
|
while (true) {
|
|
@@ -2726,21 +2731,27 @@ pub const Rolls = struct {
|
|
|
2726
2731
|
return roll;
|
|
2727
2732
|
}
|
|
2728
2733
|
|
|
2729
|
-
|
|
2734
|
+
const uN = if (pkmn.options.miss) u8 else u9;
|
|
2735
|
+
fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) !bool {
|
|
2736
|
+
const acc: uN = if (pkmn.options.miss) accuracy else @as(uN, accuracy) + 1;
|
|
2730
2737
|
const ok = if (options.calc.overridden(player, .hit)) |val|
|
|
2731
2738
|
val == .true
|
|
2739
|
+
else if (@hasDecl(@TypeOf(battle.rng), "hit"))
|
|
2740
|
+
battle.rng.hit(player, acc)
|
|
2732
2741
|
else if (showdown)
|
|
2733
|
-
battle.rng.chance(
|
|
2742
|
+
battle.rng.chance(uN, acc, 256)
|
|
2734
2743
|
else
|
|
2735
|
-
battle.rng.next() <
|
|
2744
|
+
battle.rng.next() < acc;
|
|
2736
2745
|
|
|
2737
|
-
options.chance.hit(ok,
|
|
2746
|
+
try options.chance.hit(player, ok, acc);
|
|
2738
2747
|
return ok;
|
|
2739
2748
|
}
|
|
2740
2749
|
|
|
2741
2750
|
fn confused(battle: anytype, player: Player, options: anytype) !bool {
|
|
2742
2751
|
const cfz = if (options.calc.overridden(player, .confused)) |val|
|
|
2743
2752
|
val == .true
|
|
2753
|
+
else if (@hasDecl(@TypeOf(battle.rng), "confused"))
|
|
2754
|
+
battle.rng.confused(player)
|
|
2744
2755
|
else if (showdown)
|
|
2745
2756
|
!battle.rng.chance(u8, 128, 256)
|
|
2746
2757
|
else
|
|
@@ -2753,6 +2764,8 @@ pub const Rolls = struct {
|
|
|
2753
2764
|
fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
|
|
2754
2765
|
const par = if (options.calc.overridden(player, .paralyzed)) |val|
|
|
2755
2766
|
val == .true
|
|
2767
|
+
else if (@hasDecl(@TypeOf(battle.rng), "paralyzed"))
|
|
2768
|
+
battle.rng.paralyzed(player)
|
|
2756
2769
|
else if (showdown)
|
|
2757
2770
|
battle.rng.chance(u8, 63, 256)
|
|
2758
2771
|
else
|
|
@@ -2765,6 +2778,8 @@ pub const Rolls = struct {
|
|
|
2765
2778
|
fn confusionChance(battle: anytype, player: Player, options: anytype) !bool {
|
|
2766
2779
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2767
2780
|
val == .true
|
|
2781
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2782
|
+
battle.rng.secondaryChance(player, 25)
|
|
2768
2783
|
else if (showdown)
|
|
2769
2784
|
battle.rng.chance(u8, 25, 256)
|
|
2770
2785
|
else
|
|
@@ -2779,6 +2794,8 @@ pub const Rolls = struct {
|
|
|
2779
2794
|
|
|
2780
2795
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2781
2796
|
val == .true
|
|
2797
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2798
|
+
battle.rng.secondaryChance(player, rate)
|
|
2782
2799
|
else if (showdown)
|
|
2783
2800
|
battle.rng.chance(u8, rate, 256)
|
|
2784
2801
|
else
|
|
@@ -2793,6 +2810,8 @@ pub const Rolls = struct {
|
|
|
2793
2810
|
|
|
2794
2811
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2795
2812
|
val == .true
|
|
2813
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2814
|
+
battle.rng.secondaryChance(player, rate)
|
|
2796
2815
|
else if (showdown)
|
|
2797
2816
|
battle.rng.chance(u8, rate, 256)
|
|
2798
2817
|
else
|
|
@@ -2805,6 +2824,8 @@ pub const Rolls = struct {
|
|
|
2805
2824
|
fn unboost(battle: anytype, player: Player, options: anytype) !bool {
|
|
2806
2825
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2807
2826
|
val == .true
|
|
2827
|
+
else if (@hasDecl(@TypeOf(battle.rng), "secondaryChance"))
|
|
2828
|
+
battle.rng.secondaryChance(player, 85)
|
|
2808
2829
|
else if (showdown)
|
|
2809
2830
|
battle.rng.chance(u8, 85, 256)
|
|
2810
2831
|
else
|
|
@@ -2817,6 +2838,8 @@ pub const Rolls = struct {
|
|
|
2817
2838
|
fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
2818
2839
|
const move: Move = if (options.calc.overridden(player, .metronome)) |val|
|
|
2819
2840
|
val
|
|
2841
|
+
else if (@hasDecl(@TypeOf(battle.rng), "metronome"))
|
|
2842
|
+
battle.rng.metronome(player)
|
|
2820
2843
|
else if (showdown)
|
|
2821
2844
|
Move.METRONOME[battle.rng.range(u8, 0, Move.METRONOME.len)]
|
|
2822
2845
|
else move: {
|
|
@@ -2835,6 +2858,8 @@ pub const Rolls = struct {
|
|
|
2835
2858
|
fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
|
|
2836
2859
|
const power = if (options.calc.overridden(player, .psywave)) |val|
|
|
2837
2860
|
val - 1
|
|
2861
|
+
else if (@hasDecl(@TypeOf(battle.rng), "psywave"))
|
|
2862
|
+
battle.rng.psywave(player)
|
|
2838
2863
|
else if (showdown)
|
|
2839
2864
|
battle.rng.range(u8, 0, max)
|
|
2840
2865
|
else power: {
|
|
@@ -2852,6 +2877,8 @@ pub const Rolls = struct {
|
|
|
2852
2877
|
fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2853
2878
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2854
2879
|
@intCast(val)
|
|
2880
|
+
else if (@hasDecl(@TypeOf(battle.rng), "sleepDuration"))
|
|
2881
|
+
battle.rng.sleepDuration(player)
|
|
2855
2882
|
else if (showdown)
|
|
2856
2883
|
battle.rng.range(u3, 1, 8)
|
|
2857
2884
|
else duration: {
|
|
@@ -2869,6 +2896,8 @@ pub const Rolls = struct {
|
|
|
2869
2896
|
fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
|
|
2870
2897
|
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2871
2898
|
val
|
|
2899
|
+
else if (@hasDecl(@TypeOf(battle.rng), "disableDuration"))
|
|
2900
|
+
battle.rng.disableDuration(player)
|
|
2872
2901
|
else if (showdown)
|
|
2873
2902
|
battle.rng.range(u4, 1, 9)
|
|
2874
2903
|
else
|
|
@@ -2882,6 +2911,8 @@ pub const Rolls = struct {
|
|
|
2882
2911
|
fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
|
|
2883
2912
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2884
2913
|
@intCast(val)
|
|
2914
|
+
else if (@hasDecl(@TypeOf(battle.rng), "confusionDuration"))
|
|
2915
|
+
battle.rng.confusionDuration(player, self)
|
|
2885
2916
|
else if (showdown)
|
|
2886
2917
|
battle.rng.range(u3, 2, 6)
|
|
2887
2918
|
else
|
|
@@ -2895,6 +2926,8 @@ pub const Rolls = struct {
|
|
|
2895
2926
|
fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2896
2927
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2897
2928
|
@intCast(val)
|
|
2929
|
+
else if (@hasDecl(@TypeOf(battle.rng), "attackingDuration"))
|
|
2930
|
+
battle.rng.attackingDuration(player)
|
|
2898
2931
|
else if (showdown)
|
|
2899
2932
|
battle.rng.range(u3, 2, 4)
|
|
2900
2933
|
else
|
|
@@ -2916,6 +2949,8 @@ pub const Rolls = struct {
|
|
|
2916
2949
|
const roll = if (effect == .Binding) .duration else .multi_hit;
|
|
2917
2950
|
const n: u3 = if (options.calc.overridden(player, roll)) |val|
|
|
2918
2951
|
@intCast(val)
|
|
2952
|
+
else if (@hasDecl(@TypeOf(battle.rng), "distribution"))
|
|
2953
|
+
battle.rng.distribution(player)
|
|
2919
2954
|
else if (showdown)
|
|
2920
2955
|
DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
|
|
2921
2956
|
else n: {
|
|
@@ -2948,7 +2983,9 @@ pub const Rolls = struct {
|
|
|
2948
2983
|
else
|
|
2949
2984
|
null;
|
|
2950
2985
|
|
|
2951
|
-
const slot: u4 = overridden orelse
|
|
2986
|
+
const slot: u4 = overridden orelse if (@hasDecl(@TypeOf(battle.rng), "moveSlot"))
|
|
2987
|
+
battle.rng.moveSlot(player, moves, check_pp)
|
|
2988
|
+
else slot: {
|
|
2952
2989
|
if (showdown) {
|
|
2953
2990
|
if (check_pp == 0) {
|
|
2954
2991
|
var i: usize = moves.len;
|
|
@@ -3032,7 +3069,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3032
3069
|
const stored = side.stored();
|
|
3033
3070
|
|
|
3034
3071
|
// While players are not given any input options on the cartridge in these cases,
|
|
3035
|
-
// Pokémon Showdown instead produces a list with a single move that must be chosen
|
|
3072
|
+
// Pokémon Showdown instead produces a list with a single move that must be chosen
|
|
3036
3073
|
//
|
|
3037
3074
|
// Given that no input is allowed on the cartridge 'Pass' seems like it would be logical
|
|
3038
3075
|
// here when not in compatibility mode, but the engine needs to be able to differentiate
|
|
@@ -3040,7 +3077,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3040
3077
|
// passing due to being forced into using a move. Instead of introducing another option
|
|
3041
3078
|
// we simply repurpose Move with no move slot, even though pedantically this is not
|
|
3042
3079
|
// strictly correct as the player would not have been presented the option to move or
|
|
3043
|
-
// switch at all
|
|
3080
|
+
// switch at all
|
|
3044
3081
|
if (isForced(active)) {
|
|
3045
3082
|
out[n] = .{ .type = .Move, .data = @intFromBool(showdown) };
|
|
3046
3083
|
n += 1;
|
|
@@ -3059,7 +3096,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3059
3096
|
// On the cartridge, all of these happen after "FIGHT" (indicating you are not
|
|
3060
3097
|
// switching) but before you are allowed to select a move. Pokémon Showdown instead
|
|
3061
3098
|
// either disables all other moves in the case of limited or requires you to select a
|
|
3062
|
-
// move normally if sleeping/frozen/bound
|
|
3099
|
+
// move normally if sleeping/frozen/bound
|
|
3063
3100
|
if (!showdown and (limited or foe.active.volatiles.Binding or
|
|
3064
3101
|
Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP)))
|
|
3065
3102
|
{
|
|
@@ -3072,7 +3109,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3072
3109
|
// Pokémon Showdown handles Bide and Binding moves by checking if the move in question
|
|
3073
3110
|
// is present in the Pokémon's moveset (which means moves called via Metronome / Mirror
|
|
3074
3111
|
// Move will not result in forcing the subsequent use unless the user also had the
|
|
3075
|
-
// proc-ed move in their moveset) and disabling all other moves
|
|
3112
|
+
// proc-ed move in their moveset) and disabling all other moves
|
|
3076
3113
|
if (limited) {
|
|
3077
3114
|
assert(showdown);
|
|
3078
3115
|
assert(side.last_selected_move != .None);
|