@pkmn/engine 0.1.0-dev.979a393a → 0.1.0-dev.a5a9f4b2
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 +9 -9
- package/build/pkg/addon.d.ts +7 -0
- package/build/pkg/addon.js +111 -0
- package/build/pkg/addon.js.map +1 -0
- package/build/pkg/gen1.js +1 -1
- package/build/test/display.js +2 -1
- package/build/test/display.js.map +1 -1
- package/build.zig +10 -15
- package/package.json +11 -8
- package/src/bin/install-pkmn-engine +1 -1
- package/src/bin/pkmn-debug +29 -6
- package/src/lib/binding/node.zig +4 -4
- package/src/lib/binding/wasm.zig +2 -2
- package/src/lib/gen1/README.md +12 -5
- package/src/lib/gen1/data.zig +5 -5
- package/src/lib/gen1/mechanics.zig +231 -235
- package/src/lib/gen2/data.zig +4 -4
- package/src/lib/pkmn.zig +3 -3
|
@@ -56,6 +56,8 @@ const BOOSTS = &[_][2]u8{
|
|
|
56
56
|
|
|
57
57
|
const MAX_STAT_VALUE = 999;
|
|
58
58
|
|
|
59
|
+
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
60
|
+
|
|
59
61
|
pub fn update(battle: anytype, c1: Choice, c2: Choice, log: anytype) !Result {
|
|
60
62
|
assert(c1.type != .Pass or c2.type != .Pass or battle.turn == 0);
|
|
61
63
|
if (battle.turn == 0) return start(battle, log);
|
|
@@ -185,6 +187,7 @@ fn selectMove(
|
|
|
185
187
|
}
|
|
186
188
|
|
|
187
189
|
// move select
|
|
190
|
+
volatiles.state = 0;
|
|
188
191
|
if (choice.data == 0) {
|
|
189
192
|
const struggle = ok: {
|
|
190
193
|
for (side.active.moves, 0..) |move, i| {
|
|
@@ -301,11 +304,7 @@ fn turnOrder(battle: anytype, c1: Choice, c2: Choice) Player {
|
|
|
301
304
|
// Pokémon Showdown's beforeTurnCallback shenanigans
|
|
302
305
|
if (showdown and m1 == .Counter and m2 == .Counter) battle.rng.advance(1);
|
|
303
306
|
|
|
304
|
-
const p1 =
|
|
305
|
-
battle.rng.range(u8, 0, 2) == 0
|
|
306
|
-
else
|
|
307
|
-
battle.rng.next() < Gen12.percent(50) + 1;
|
|
308
|
-
|
|
307
|
+
const p1 = Rolls.speedTie(battle);
|
|
309
308
|
if (!showdown) return if (p1) .P1 else .P2;
|
|
310
309
|
|
|
311
310
|
// Pokémon Showdown's "lockedmove" volatile's onBeforeTurn uses BattleQueue#changeAction,
|
|
@@ -315,7 +314,7 @@ fn turnOrder(battle: anytype, c1: Choice, c2: Choice) Player {
|
|
|
315
314
|
const t1 = battle.side(.P1).active.volatiles.Thrashing;
|
|
316
315
|
const t2 = battle.side(.P2).active.volatiles.Thrashing;
|
|
317
316
|
// If *both* sides are thrashing it really should be another speed tie, but we've patched
|
|
318
|
-
// that out and enforce host ordering of events, so P1 just goes first
|
|
317
|
+
// that out and enforce host ordering of events, so P1 just goes first regardless of who
|
|
319
318
|
// won the original coin flip
|
|
320
319
|
if (t1 and t2) return .P1;
|
|
321
320
|
return if (p1) if (t1 and !t2) .P2 else .P1 else if (t2 and !t1) .P1 else .P2;
|
|
@@ -450,7 +449,7 @@ fn executeMove(
|
|
|
450
449
|
return doMove(battle, player, mslot, auto, residual, log);
|
|
451
450
|
}
|
|
452
451
|
|
|
453
|
-
const BeforeMove =
|
|
452
|
+
const BeforeMove = enum { done, skip_can, skip_pp, ok, err };
|
|
454
453
|
|
|
455
454
|
fn beforeMove(
|
|
456
455
|
battle: anytype,
|
|
@@ -513,9 +512,10 @@ fn beforeMove(
|
|
|
513
512
|
// Pokémon Showdown's disable condition has a single onBeforeMove handler
|
|
514
513
|
if (showdown and try disabled(side, ident, log)) return .done;
|
|
515
514
|
|
|
516
|
-
// Pokémon Showdown checks for Flinch *after* instead of before
|
|
515
|
+
// Pokémon Showdown checks for Flinch *after* instead of before... and doesn't clear Flinch
|
|
516
|
+
// until its imaginary "residual" phase, meaning Pokémon can sometimes flinch multiple times
|
|
517
|
+
// from the same original hit
|
|
517
518
|
if (showdown and volatiles.Flinch) {
|
|
518
|
-
volatiles.Flinch = false;
|
|
519
519
|
try log.cant(ident, .Flinch);
|
|
520
520
|
return .done;
|
|
521
521
|
}
|
|
@@ -537,12 +537,7 @@ fn beforeMove(
|
|
|
537
537
|
} else {
|
|
538
538
|
try log.activate(ident, .Confusion);
|
|
539
539
|
|
|
540
|
-
|
|
541
|
-
!battle.rng.chance(u8, 128, 256)
|
|
542
|
-
else
|
|
543
|
-
battle.rng.next() >= Gen12.percent(50) + 1;
|
|
544
|
-
|
|
545
|
-
if (confused) {
|
|
540
|
+
if (Rolls.confused(battle)) {
|
|
546
541
|
assert(!volatiles.MultiHit);
|
|
547
542
|
if (!volatiles.Rage) volatiles.state = 0;
|
|
548
543
|
volatiles.Bide = false;
|
|
@@ -568,8 +563,12 @@ fn beforeMove(
|
|
|
568
563
|
player
|
|
569
564
|
else
|
|
570
565
|
player.foe();
|
|
566
|
+
|
|
567
|
+
const uncapped = battle.last_damage;
|
|
571
568
|
// Skipping adjustDamage / randomizeDamage / checkHit
|
|
572
569
|
_ = try applyDamage(battle, player, target, .Confusion, log);
|
|
570
|
+
// Pokémon Showdown thinks that confusion damage is uncapped ¯\_(ツ)_/¯
|
|
571
|
+
if (showdown) battle.last_damage = uncapped;
|
|
573
572
|
|
|
574
573
|
return .done;
|
|
575
574
|
}
|
|
@@ -578,22 +577,15 @@ fn beforeMove(
|
|
|
578
577
|
|
|
579
578
|
if (!showdown and try disabled(side, ident, log)) return .done;
|
|
580
579
|
|
|
581
|
-
if (Status.is(stored.status, .PAR)) {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
volatiles.Thrashing = false;
|
|
591
|
-
volatiles.Charging = false;
|
|
592
|
-
volatiles.Binding = false;
|
|
593
|
-
// GLITCH: Invulnerable is not cleared, resulting in permanent Fly/Dig invulnerability
|
|
594
|
-
try log.cant(ident, .Paralysis);
|
|
595
|
-
return .done;
|
|
596
|
-
}
|
|
580
|
+
if (Status.is(stored.status, .PAR) and Rolls.paralyzed(battle)) {
|
|
581
|
+
if (!volatiles.Rage) volatiles.state = 0;
|
|
582
|
+
volatiles.Bide = false;
|
|
583
|
+
volatiles.Thrashing = false;
|
|
584
|
+
volatiles.Charging = false;
|
|
585
|
+
volatiles.Binding = false;
|
|
586
|
+
// GLITCH: Invulnerable is not cleared, resulting in permanent Fly/Dig invulnerability
|
|
587
|
+
try log.cant(ident, .Paralysis);
|
|
588
|
+
return .done;
|
|
597
589
|
}
|
|
598
590
|
|
|
599
591
|
if (volatiles.Bide) {
|
|
@@ -796,7 +788,7 @@ fn doMove(
|
|
|
796
788
|
};
|
|
797
789
|
assert(!immune or miss or (showdown and move.effect == .Binding));
|
|
798
790
|
|
|
799
|
-
var late = showdown;
|
|
791
|
+
var late = showdown and move.effect != .Explode;
|
|
800
792
|
const skip = status or immune;
|
|
801
793
|
if ((!showdown or (!skip or counter)) and !miss) blk: {
|
|
802
794
|
if (showdown and move.effect.isMulti()) {
|
|
@@ -916,7 +908,7 @@ fn doMove(
|
|
|
916
908
|
if (hit == 0 and ohko) try log.ohko();
|
|
917
909
|
hit += 1;
|
|
918
910
|
if (foe.stored().hp == 0) break;
|
|
919
|
-
if (!late and !sub) try buildRage(battle, player.foe(), log);
|
|
911
|
+
if (!late and (!sub or move.effect == .Explode)) try buildRage(battle, player.foe(), log);
|
|
920
912
|
// If the substitute breaks during a multi-hit attack, the attack ends
|
|
921
913
|
if (nullified) break;
|
|
922
914
|
}
|
|
@@ -987,8 +979,7 @@ fn checkCriticalHit(battle: anytype, player: Player, move: Move.Data) bool {
|
|
|
987
979
|
chance = if (side.active.volatiles.FocusEnergy) chance / 2 else @min(chance * 2, 255);
|
|
988
980
|
chance = if (move.effect == .HighCritical) @min(chance * 4, 255) else chance / 2;
|
|
989
981
|
|
|
990
|
-
|
|
991
|
-
return std.math.rotl(u8, battle.rng.next(), 3) < chance;
|
|
982
|
+
return Rolls.criticalHit(battle, chance);
|
|
992
983
|
}
|
|
993
984
|
|
|
994
985
|
fn calcDamage(
|
|
@@ -1092,16 +1083,7 @@ fn adjustDamage(battle: anytype, player: Player) u16 {
|
|
|
1092
1083
|
|
|
1093
1084
|
fn randomizeDamage(battle: anytype) void {
|
|
1094
1085
|
if (battle.last_damage <= 1) return;
|
|
1095
|
-
|
|
1096
|
-
const random = if (showdown)
|
|
1097
|
-
battle.rng.range(u8, 217, 256)
|
|
1098
|
-
else loop: {
|
|
1099
|
-
while (true) {
|
|
1100
|
-
const r = std.math.rotr(u8, battle.rng.next(), 1);
|
|
1101
|
-
if (r >= 217) break :loop r;
|
|
1102
|
-
}
|
|
1103
|
-
};
|
|
1104
|
-
|
|
1086
|
+
const random = Rolls.damage(battle);
|
|
1105
1087
|
battle.last_damage = @intCast(u16, @as(u32, battle.last_damage) *% random / 255);
|
|
1106
1088
|
}
|
|
1107
1089
|
|
|
@@ -1119,16 +1101,9 @@ fn specialDamage(battle: anytype, player: Player, move: Move.Data, log: anytype)
|
|
|
1119
1101
|
// GLITCH: if power = 0 then a desync occurs (or a miss on Pokémon Showdown)
|
|
1120
1102
|
.Psywave => power: {
|
|
1121
1103
|
const max = @intCast(u8, @as(u16, side.stored().level) * 3 / 2);
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
// GLITCH: Psywave infinite glitch loop
|
|
1126
|
-
if (max <= 1) return Result.Error;
|
|
1127
|
-
while (true) {
|
|
1128
|
-
const r = battle.rng.next();
|
|
1129
|
-
if (r < max) break :power r;
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1104
|
+
// GLITCH: Psywave infinite glitch loop
|
|
1105
|
+
if (!showdown and max <= 1) return Result.Error;
|
|
1106
|
+
break :power Rolls.psywave(battle, max);
|
|
1132
1107
|
},
|
|
1133
1108
|
else => unreachable,
|
|
1134
1109
|
};
|
|
@@ -1260,19 +1235,7 @@ fn metronome(
|
|
|
1260
1235
|
) !?Result {
|
|
1261
1236
|
var side = battle.side(player);
|
|
1262
1237
|
|
|
1263
|
-
side.last_selected_move =
|
|
1264
|
-
const r = battle.rng.range(u8, 0, @enumToInt(Move.Struggle) - 2);
|
|
1265
|
-
const mod = @as(u2, (if (r < @enumToInt(Move.Metronome) - 1) 1 else 2));
|
|
1266
|
-
break :blk @intToEnum(Move, r + mod);
|
|
1267
|
-
} else loop: {
|
|
1268
|
-
while (true) {
|
|
1269
|
-
const r = battle.rng.next();
|
|
1270
|
-
if (r == 0 or r == @enumToInt(Move.Metronome)) continue;
|
|
1271
|
-
if (r >= @enumToInt(Move.Struggle)) continue;
|
|
1272
|
-
break :loop @intToEnum(Move, r);
|
|
1273
|
-
}
|
|
1274
|
-
};
|
|
1275
|
-
|
|
1238
|
+
side.last_selected_move = Rolls.metronome(battle);
|
|
1276
1239
|
incrementPP(side, mslot);
|
|
1277
1240
|
|
|
1278
1241
|
if (!try canMove(battle, player, mslot, auto, false, .Metronome, residual, log)) return null;
|
|
@@ -1330,23 +1293,24 @@ fn moveHit(battle: anytype, player: Player, move: Move.Data, immune: *bool, mist
|
|
|
1330
1293
|
}
|
|
1331
1294
|
|
|
1332
1295
|
// GLITCH: Thrash / Petal Dance / Rage get their accuracy overwritten on subsequent hits
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1296
|
+
const state = side.active.volatiles.state;
|
|
1297
|
+
var overwrite = move.effect == .Rage or move.effect == .Thrashing;
|
|
1298
|
+
const overwritten = overwrite and state > 0;
|
|
1299
|
+
assert(!overwritten or (0 < state and state <= 255 and !side.active.volatiles.Bide));
|
|
1300
|
+
|
|
1301
|
+
var accuracy = if (overwritten) state else @as(u16, Gen12.percent(move.accuracy));
|
|
1337
1302
|
var boost = BOOSTS[@intCast(u4, @as(i8, side.active.boosts.accuracy) + 6)];
|
|
1338
1303
|
accuracy = accuracy * boost[0] / boost[1];
|
|
1339
1304
|
boost = BOOSTS[@intCast(u4, @as(i8, -foe.active.boosts.evasion) + 6)];
|
|
1340
1305
|
accuracy = accuracy * boost[0] / boost[1];
|
|
1341
1306
|
accuracy = @min(255, @max(1, accuracy));
|
|
1342
1307
|
|
|
1343
|
-
|
|
1308
|
+
// Pokémon Showdown only overwrites if the volatile is present
|
|
1309
|
+
if (showdown) overwrite = side.active.volatiles.Rage or side.active.volatiles.Thrashing;
|
|
1310
|
+
if (overwrite) side.active.volatiles.state = accuracy;
|
|
1344
1311
|
|
|
1345
1312
|
// GLITCH: max accuracy is 255 so 1/256 chance of miss
|
|
1346
|
-
break :miss
|
|
1347
|
-
!battle.rng.chance(u8, @intCast(u8, accuracy), 256)
|
|
1348
|
-
else
|
|
1349
|
-
battle.rng.next() >= accuracy;
|
|
1313
|
+
break :miss !Rolls.hit(battle, accuracy);
|
|
1350
1314
|
};
|
|
1351
1315
|
|
|
1352
1316
|
// Pokémon Showdown reports miss instead of fail for moves blocked by Mist that 1/256 miss
|
|
@@ -1478,14 +1442,15 @@ fn handleResidual(battle: anytype, player: Player, log: anytype) !void {
|
|
|
1478
1442
|
damage *= volatiles.toxic;
|
|
1479
1443
|
}
|
|
1480
1444
|
|
|
1481
|
-
// Pokémon Showdown erroneously updates last damage with uncapped Leech Seed damage
|
|
1482
|
-
if (showdown) battle.last_damage = damage;
|
|
1483
|
-
|
|
1484
1445
|
const amount = @min(damage, stored.hp);
|
|
1485
1446
|
stored.hp -= amount;
|
|
1486
1447
|
|
|
1487
1448
|
// As above, Pokémon Showdown uses damageOf but its not relevant
|
|
1488
|
-
if (amount > 0)
|
|
1449
|
+
if (amount > 0) {
|
|
1450
|
+
try log.damage(ident, stored, .LeechSeed);
|
|
1451
|
+
// Pokémon Showdown erroneously updates last damage with uncapped Leech Seed damage
|
|
1452
|
+
if (showdown) battle.last_damage = damage;
|
|
1453
|
+
}
|
|
1489
1454
|
|
|
1490
1455
|
const before = foe_stored.hp;
|
|
1491
1456
|
// Uncapped damage is added back to the foe
|
|
@@ -1572,6 +1537,42 @@ fn checkEBC(battle: anytype) bool {
|
|
|
1572
1537
|
return false;
|
|
1573
1538
|
}
|
|
1574
1539
|
|
|
1540
|
+
fn disabled(side: *Side, ident: ID, log: anytype) !bool {
|
|
1541
|
+
if (side.active.volatiles.disabled_move != 0) {
|
|
1542
|
+
// A Pokémon that transforms after being disabled may end up with less move slots
|
|
1543
|
+
const m = side.active.moves[side.active.volatiles.disabled_move - 1].id;
|
|
1544
|
+
// side.last_selected_move can be Struggle here on Pokemon Showdown we need an extra check
|
|
1545
|
+
const last = if (showdown and m == .Bide and side.active.volatiles.Bide)
|
|
1546
|
+
m
|
|
1547
|
+
else
|
|
1548
|
+
side.last_selected_move;
|
|
1549
|
+
if (m != .None and m == last) {
|
|
1550
|
+
side.active.volatiles.Charging = false;
|
|
1551
|
+
try log.disabled(ident, last);
|
|
1552
|
+
return true;
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
return false;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
inline fn buildRage(battle: anytype, who: Player, log: anytype) !void {
|
|
1559
|
+
const side = battle.side(who);
|
|
1560
|
+
if (side.active.volatiles.Rage and side.active.boosts.atk < 6) {
|
|
1561
|
+
try Effects.boost(battle, who, Move.get(.Rage), log);
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
fn handleThrashing(battle: anytype, active: *ActivePokemon) bool {
|
|
1566
|
+
var volatiles = &active.volatiles;
|
|
1567
|
+
assert(volatiles.Thrashing);
|
|
1568
|
+
if (volatiles.attacks > 0) return false;
|
|
1569
|
+
|
|
1570
|
+
volatiles.Thrashing = false;
|
|
1571
|
+
volatiles.Confusion = true;
|
|
1572
|
+
volatiles.confusion = Rolls.confusionDuration(battle);
|
|
1573
|
+
return true;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1575
1576
|
inline fn onBegin(
|
|
1576
1577
|
battle: anytype,
|
|
1577
1578
|
player: Player,
|
|
@@ -1662,10 +1663,7 @@ pub const Effects = struct {
|
|
|
1662
1663
|
side.active.volatiles.Bide = true;
|
|
1663
1664
|
assert(!side.active.volatiles.Thrashing and !side.active.volatiles.Rage);
|
|
1664
1665
|
side.active.volatiles.state = 0;
|
|
1665
|
-
side.active.volatiles.attacks =
|
|
1666
|
-
battle.rng.range(u4, 2, 4)
|
|
1667
|
-
else
|
|
1668
|
-
(battle.rng.next() & 1) + 2);
|
|
1666
|
+
side.active.volatiles.attacks = Rolls.bideThrashDuration(battle);
|
|
1669
1667
|
|
|
1670
1668
|
try log.start(battle.active(player), .Bide);
|
|
1671
1669
|
}
|
|
@@ -1687,14 +1685,8 @@ pub const Effects = struct {
|
|
|
1687
1685
|
return;
|
|
1688
1686
|
}
|
|
1689
1687
|
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
else
|
|
1693
|
-
battle.rng.next() < 1 + (if (move.effect == .BurnChance1)
|
|
1694
|
-
Gen12.percent(10)
|
|
1695
|
-
else
|
|
1696
|
-
Gen12.percent(30));
|
|
1697
|
-
if (!chance) return;
|
|
1688
|
+
if (foe.active.types.includes(move.type)) return;
|
|
1689
|
+
if (!Rolls.secondaryChance(battle, move.effect == .BurnChance1)) return;
|
|
1698
1690
|
|
|
1699
1691
|
foe_stored.status = Status.init(.BRN);
|
|
1700
1692
|
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
@@ -1718,11 +1710,7 @@ pub const Effects = struct {
|
|
|
1718
1710
|
const sub = foe.active.volatiles.Substitute;
|
|
1719
1711
|
|
|
1720
1712
|
if (move.effect == .ConfusionChance) {
|
|
1721
|
-
|
|
1722
|
-
battle.rng.chance(u8, 25, 256)
|
|
1723
|
-
else
|
|
1724
|
-
battle.rng.next() < Gen12.percent(10);
|
|
1725
|
-
if (!chance) return;
|
|
1713
|
+
if (!Rolls.confusionChance(battle)) return;
|
|
1726
1714
|
} else {
|
|
1727
1715
|
if (showdown) {
|
|
1728
1716
|
if (!try checkHit(battle, player, move, log)) {
|
|
@@ -1741,10 +1729,7 @@ pub const Effects = struct {
|
|
|
1741
1729
|
|
|
1742
1730
|
if (foe.active.volatiles.Confusion) return;
|
|
1743
1731
|
foe.active.volatiles.Confusion = true;
|
|
1744
|
-
foe.active.volatiles.confusion =
|
|
1745
|
-
battle.rng.range(u8, 2, 6)
|
|
1746
|
-
else
|
|
1747
|
-
(battle.rng.next() & 3) + 2);
|
|
1732
|
+
foe.active.volatiles.confusion = Rolls.confusionDuration(battle);
|
|
1748
1733
|
|
|
1749
1734
|
try log.start(battle.active(player.foe()), .Confusion);
|
|
1750
1735
|
}
|
|
@@ -1794,11 +1779,8 @@ pub const Effects = struct {
|
|
|
1794
1779
|
return Result.Error;
|
|
1795
1780
|
}
|
|
1796
1781
|
|
|
1797
|
-
volatiles.disabled_move = @intCast(u3,
|
|
1798
|
-
volatiles.disabled_duration =
|
|
1799
|
-
battle.rng.range(u8, 1, 9)
|
|
1800
|
-
else
|
|
1801
|
-
(battle.rng.next() & 7) + 1);
|
|
1782
|
+
volatiles.disabled_move = @intCast(u3, Rolls.moveSlot(battle, &foe.active.moves, n));
|
|
1783
|
+
volatiles.disabled_duration = Rolls.disableDuration(battle);
|
|
1802
1784
|
|
|
1803
1785
|
try log.startEffect(foe_ident, .Disable, foe.active.move(volatiles.disabled_move).id);
|
|
1804
1786
|
return null;
|
|
@@ -1810,7 +1792,7 @@ pub const Effects = struct {
|
|
|
1810
1792
|
|
|
1811
1793
|
if (battle.last_damage == 0) {
|
|
1812
1794
|
assert(showdown);
|
|
1813
|
-
return;
|
|
1795
|
+
if (!battle.foe(player).active.volatiles.Substitute) return;
|
|
1814
1796
|
}
|
|
1815
1797
|
|
|
1816
1798
|
const drain = @max(battle.last_damage / 2, 1);
|
|
@@ -1837,15 +1819,7 @@ pub const Effects = struct {
|
|
|
1837
1819
|
var volatiles = &battle.foe(player).active.volatiles;
|
|
1838
1820
|
|
|
1839
1821
|
if (volatiles.Substitute) return;
|
|
1840
|
-
|
|
1841
|
-
const chance = if (showdown)
|
|
1842
|
-
battle.rng.chance(u8, @as(u8, if (move.effect == .FlinchChance1) 26 else 77), 256)
|
|
1843
|
-
else
|
|
1844
|
-
battle.rng.next() < 1 + (if (move.effect == .FlinchChance1)
|
|
1845
|
-
Gen12.percent(10)
|
|
1846
|
-
else
|
|
1847
|
-
Gen12.percent(30));
|
|
1848
|
-
if (!chance) return;
|
|
1822
|
+
if (!Rolls.secondaryChance(battle, move.effect == .FlinchChance1)) return;
|
|
1849
1823
|
|
|
1850
1824
|
volatiles.Flinch = true;
|
|
1851
1825
|
volatiles.Recharging = false;
|
|
@@ -1871,12 +1845,8 @@ pub const Effects = struct {
|
|
|
1871
1845
|
return if (showdown and !foe.active.types.includes(move.type)) battle.rng.advance(1);
|
|
1872
1846
|
}
|
|
1873
1847
|
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
else
|
|
1877
|
-
battle.rng.next() < 1 + Gen12.percent(10);
|
|
1878
|
-
if (!chance) return;
|
|
1879
|
-
|
|
1848
|
+
if (foe.active.types.includes(move.type)) return;
|
|
1849
|
+
if (!Rolls.secondaryChance(battle, true)) return;
|
|
1880
1850
|
// Freeze Clause Mod
|
|
1881
1851
|
if (showdown) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
|
|
1882
1852
|
|
|
@@ -2021,7 +1991,7 @@ pub const Effects = struct {
|
|
|
2021
1991
|
}
|
|
2022
1992
|
if (!try checkHit(battle, player, move, log)) return;
|
|
2023
1993
|
|
|
2024
|
-
const rslot =
|
|
1994
|
+
const rslot = Rolls.moveSlot(battle, &foe.active.moves, 0);
|
|
2025
1995
|
side.active.move(oslot).id = foe.active.move(rslot).id;
|
|
2026
1996
|
|
|
2027
1997
|
try log.startEffect(battle.active(player), .Mimic, side.active.move(oslot).id);
|
|
@@ -2042,7 +2012,8 @@ pub const Effects = struct {
|
|
|
2042
2012
|
assert(!side.active.volatiles.MultiHit);
|
|
2043
2013
|
side.active.volatiles.MultiHit = true;
|
|
2044
2014
|
|
|
2045
|
-
side.active.volatiles.attacks =
|
|
2015
|
+
side.active.volatiles.attacks =
|
|
2016
|
+
if (move.effect == .MultiHit) Rolls.distribution(battle) else 2;
|
|
2046
2017
|
}
|
|
2047
2018
|
|
|
2048
2019
|
fn paralyze(battle: anytype, player: Player, move: Move.Data, log: anytype) !void {
|
|
@@ -2086,14 +2057,8 @@ pub const Effects = struct {
|
|
|
2086
2057
|
}
|
|
2087
2058
|
|
|
2088
2059
|
// Body Slam can't paralyze a Normal type Pokémon
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
else
|
|
2092
|
-
battle.rng.next() < 1 + (if (move.effect == .ParalyzeChance1)
|
|
2093
|
-
Gen12.percent(10)
|
|
2094
|
-
else
|
|
2095
|
-
Gen12.percent(30));
|
|
2096
|
-
if (!chance) return;
|
|
2060
|
+
if (foe.active.types.includes(move.type)) return;
|
|
2061
|
+
if (!Rolls.secondaryChance(battle, move.effect == .ParalyzeChance1)) return;
|
|
2097
2062
|
|
|
2098
2063
|
foe_stored.status = Status.init(.PAR);
|
|
2099
2064
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
@@ -2136,16 +2101,7 @@ pub const Effects = struct {
|
|
|
2136
2101
|
|
|
2137
2102
|
if (move.effect == .Poison) {
|
|
2138
2103
|
if (!showdown and !try checkHit(battle, player, move, log)) return;
|
|
2139
|
-
} else
|
|
2140
|
-
const chance = if (showdown)
|
|
2141
|
-
battle.rng.chance(u8, @as(u8, if (move.effect == .PoisonChance1) 52 else 103), 256)
|
|
2142
|
-
else
|
|
2143
|
-
battle.rng.next() < 1 + (if (move.effect == .PoisonChance1)
|
|
2144
|
-
Gen12.percent(20)
|
|
2145
|
-
else
|
|
2146
|
-
Gen12.percent(40));
|
|
2147
|
-
if (!chance) return;
|
|
2148
|
-
}
|
|
2104
|
+
} else if (!Rolls.poisonChance(battle, move.effect == .PoisonChance1)) return;
|
|
2149
2105
|
|
|
2150
2106
|
foe_stored.status = Status.init(.PSN);
|
|
2151
2107
|
if (toxic) {
|
|
@@ -2159,10 +2115,8 @@ pub const Effects = struct {
|
|
|
2159
2115
|
|
|
2160
2116
|
fn rage(battle: anytype, player: Player) !void {
|
|
2161
2117
|
var volatiles = &battle.side(player).active.volatiles;
|
|
2162
|
-
if (volatiles.Rage) return;
|
|
2163
|
-
volatiles.Rage = true;
|
|
2164
2118
|
assert(!volatiles.Bide);
|
|
2165
|
-
volatiles.
|
|
2119
|
+
volatiles.Rage = true;
|
|
2166
2120
|
}
|
|
2167
2121
|
|
|
2168
2122
|
fn recoil(battle: anytype, player: Player, residual: *bool, log: anytype) !void {
|
|
@@ -2214,16 +2168,7 @@ pub const Effects = struct {
|
|
|
2214
2168
|
}
|
|
2215
2169
|
foe.active.volatiles.Recharging = false;
|
|
2216
2170
|
|
|
2217
|
-
|
|
2218
|
-
battle.rng.range(u8, 1, 8)
|
|
2219
|
-
else loop: {
|
|
2220
|
-
while (true) {
|
|
2221
|
-
const r = battle.rng.next() & 7;
|
|
2222
|
-
if (r != 0) break :loop r;
|
|
2223
|
-
}
|
|
2224
|
-
});
|
|
2225
|
-
|
|
2226
|
-
foe_stored.status = Status.slp(duration);
|
|
2171
|
+
foe_stored.status = Status.slp(Rolls.sleepDuration(battle));
|
|
2227
2172
|
try log.statusFrom(foe_ident, foe_stored.status, battle.side(player).last_selected_move);
|
|
2228
2173
|
}
|
|
2229
2174
|
|
|
@@ -2276,11 +2221,7 @@ pub const Effects = struct {
|
|
|
2276
2221
|
assert(!volatiles.Bide);
|
|
2277
2222
|
|
|
2278
2223
|
volatiles.Thrashing = true;
|
|
2279
|
-
volatiles.
|
|
2280
|
-
volatiles.attacks = @intCast(u3, if (showdown)
|
|
2281
|
-
battle.rng.range(u8, 2, 4)
|
|
2282
|
-
else
|
|
2283
|
-
(battle.rng.next() & 1) + 2);
|
|
2224
|
+
volatiles.attacks = Rolls.bideThrashDuration(battle);
|
|
2284
2225
|
}
|
|
2285
2226
|
|
|
2286
2227
|
fn transform(battle: anytype, player: Player, log: anytype) !void {
|
|
@@ -2322,7 +2263,7 @@ pub const Effects = struct {
|
|
|
2322
2263
|
// (Pokémon Showdown unitentionally patches this glitch, preventing automatic selection)
|
|
2323
2264
|
if (!showdown) foe.active.volatiles.Recharging = false;
|
|
2324
2265
|
|
|
2325
|
-
side.active.volatiles.attacks = distribution(battle) - 1;
|
|
2266
|
+
side.active.volatiles.attacks = Rolls.distribution(battle) - 1;
|
|
2326
2267
|
}
|
|
2327
2268
|
|
|
2328
2269
|
fn boost(battle: anytype, player: Player, move: Move.Data, log: anytype) !void {
|
|
@@ -2349,7 +2290,7 @@ pub const Effects = struct {
|
|
|
2349
2290
|
return try log.fail(ident, .None);
|
|
2350
2291
|
}
|
|
2351
2292
|
var mod = BOOSTS[@intCast(u4, @as(i8, boosts.atk) + 6)];
|
|
2352
|
-
const stat = unmodifiedStats(battle,
|
|
2293
|
+
const stat = unmodifiedStats(battle, side).atk;
|
|
2353
2294
|
stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2354
2295
|
try log.boost(ident, reason, n);
|
|
2355
2296
|
// Pokémon Showdown doesn't re-apply status modifiers after Rage boosts
|
|
@@ -2369,7 +2310,7 @@ pub const Effects = struct {
|
|
|
2369
2310
|
return try log.fail(ident, .None);
|
|
2370
2311
|
}
|
|
2371
2312
|
var mod = BOOSTS[@intCast(u4, @as(i8, boosts.def) + 6)];
|
|
2372
|
-
const stat = unmodifiedStats(battle,
|
|
2313
|
+
const stat = unmodifiedStats(battle, side).def;
|
|
2373
2314
|
stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2374
2315
|
try log.boost(ident, .Defense, n);
|
|
2375
2316
|
},
|
|
@@ -2386,7 +2327,7 @@ pub const Effects = struct {
|
|
|
2386
2327
|
return try log.fail(ident, .None);
|
|
2387
2328
|
}
|
|
2388
2329
|
var mod = BOOSTS[@intCast(u4, @as(i8, boosts.spe) + 6)];
|
|
2389
|
-
const stat = unmodifiedStats(battle,
|
|
2330
|
+
const stat = unmodifiedStats(battle, side).spe;
|
|
2390
2331
|
stats.spe = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2391
2332
|
try log.boost(ident, .Speed, 2);
|
|
2392
2333
|
},
|
|
@@ -2406,7 +2347,7 @@ pub const Effects = struct {
|
|
|
2406
2347
|
return try log.fail(ident, .None);
|
|
2407
2348
|
}
|
|
2408
2349
|
var mod = BOOSTS[@intCast(u4, @as(i8, boosts.spc) + 6)];
|
|
2409
|
-
const stat = unmodifiedStats(battle,
|
|
2350
|
+
const stat = unmodifiedStats(battle, side).spc;
|
|
2410
2351
|
stats.spc = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2411
2352
|
try log.boost(ident, .SpecialAttack, n);
|
|
2412
2353
|
try log.boost(ident, .SpecialDefense, n);
|
|
@@ -2433,11 +2374,7 @@ pub const Effects = struct {
|
|
|
2433
2374
|
}
|
|
2434
2375
|
|
|
2435
2376
|
if (move.effect.isStatDownChance()) {
|
|
2436
|
-
|
|
2437
|
-
battle.rng.chance(u8, 85, 256)
|
|
2438
|
-
else
|
|
2439
|
-
battle.rng.next() < Gen12.percent(33) + 1;
|
|
2440
|
-
if (!chance or foe.active.volatiles.Invulnerable) return;
|
|
2377
|
+
if (!Rolls.unboost(battle) or foe.active.volatiles.Invulnerable) return;
|
|
2441
2378
|
} else if (!showdown and !try checkHit(battle, player, move, log)) {
|
|
2442
2379
|
return; // checkHit already checks for Invulnerable
|
|
2443
2380
|
}
|
|
@@ -2459,7 +2396,7 @@ pub const Effects = struct {
|
|
|
2459
2396
|
return try log.fail(foe_ident, .None);
|
|
2460
2397
|
}
|
|
2461
2398
|
var mod = BOOSTS[@intCast(u4, @as(i8, boosts.atk) + 6)];
|
|
2462
|
-
const stat = unmodifiedStats(battle,
|
|
2399
|
+
const stat = unmodifiedStats(battle, foe).atk;
|
|
2463
2400
|
stats.atk = @max(1, stat * mod[0] / mod[1]);
|
|
2464
2401
|
try log.boost(foe_ident, .Attack, -1);
|
|
2465
2402
|
},
|
|
@@ -2477,7 +2414,7 @@ pub const Effects = struct {
|
|
|
2477
2414
|
return try log.fail(foe_ident, .None);
|
|
2478
2415
|
}
|
|
2479
2416
|
var mod = BOOSTS[@intCast(u4, @as(i8, boosts.def) + 6)];
|
|
2480
|
-
const stat = unmodifiedStats(battle,
|
|
2417
|
+
const stat = unmodifiedStats(battle, foe).def;
|
|
2481
2418
|
stats.def = @max(1, stat * mod[0] / mod[1]);
|
|
2482
2419
|
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2483
2420
|
},
|
|
@@ -2494,7 +2431,7 @@ pub const Effects = struct {
|
|
|
2494
2431
|
return try log.fail(foe_ident, .None);
|
|
2495
2432
|
}
|
|
2496
2433
|
var mod = BOOSTS[@intCast(u4, @as(i8, boosts.spe) + 6)];
|
|
2497
|
-
const stat = unmodifiedStats(battle,
|
|
2434
|
+
const stat = unmodifiedStats(battle, foe).spe;
|
|
2498
2435
|
stats.spe = @max(1, stat * mod[0] / mod[1]);
|
|
2499
2436
|
try log.boost(foe_ident, .Speed, -1);
|
|
2500
2437
|
assert(boosts.spe >= -6);
|
|
@@ -2514,7 +2451,7 @@ pub const Effects = struct {
|
|
|
2514
2451
|
return try log.fail(foe_ident, .None);
|
|
2515
2452
|
}
|
|
2516
2453
|
var mod = BOOSTS[@intCast(u4, @as(i8, boosts.spc) + 6)];
|
|
2517
|
-
const stat = unmodifiedStats(battle,
|
|
2454
|
+
const stat = unmodifiedStats(battle, foe).spc;
|
|
2518
2455
|
stats.spc = @max(1, stat * mod[0] / mod[1]);
|
|
2519
2456
|
try log.boost(foe_ident, .SpecialAttack, -1);
|
|
2520
2457
|
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
@@ -2533,8 +2470,7 @@ pub const Effects = struct {
|
|
|
2533
2470
|
}
|
|
2534
2471
|
};
|
|
2535
2472
|
|
|
2536
|
-
fn unmodifiedStats(battle: anytype,
|
|
2537
|
-
const side = battle.side(who);
|
|
2473
|
+
fn unmodifiedStats(battle: anytype, side: *Side) *Stats(u16) {
|
|
2538
2474
|
if (!side.active.volatiles.Transform) return &side.stored().stats;
|
|
2539
2475
|
const id = ID.from(side.active.volatiles.transform);
|
|
2540
2476
|
return &battle.side(id.player).pokemon[id.id - 1].stats;
|
|
@@ -2600,80 +2536,140 @@ fn clearVolatiles(battle: anytype, who: Player, log: anytype) !void {
|
|
|
2600
2536
|
}
|
|
2601
2537
|
}
|
|
2602
2538
|
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2539
|
+
pub const Rolls = struct {
|
|
2540
|
+
inline fn speedTie(battle: anytype) bool {
|
|
2541
|
+
if (showdown) return battle.rng.range(u8, 0, 2) == 0;
|
|
2542
|
+
return battle.rng.next() < Gen12.percent(50) + 1;
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
inline fn criticalHit(battle: anytype, chance: u16) bool {
|
|
2546
|
+
if (showdown) return battle.rng.chance(u8, @intCast(u8, chance), 256);
|
|
2547
|
+
return std.math.rotl(u8, battle.rng.next(), 3) < chance;
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2550
|
+
inline fn damage(battle: anytype) u8 {
|
|
2551
|
+
if (showdown) return battle.rng.range(u8, 217, 256);
|
|
2552
|
+
while (true) {
|
|
2553
|
+
const r = std.math.rotr(u8, battle.rng.next(), 1);
|
|
2554
|
+
if (r >= 217) return r;
|
|
2616
2555
|
}
|
|
2617
2556
|
}
|
|
2618
|
-
return false;
|
|
2619
|
-
}
|
|
2620
2557
|
|
|
2621
|
-
inline fn
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
try Effects.boost(battle, who, Move.get(.Rage), log);
|
|
2558
|
+
inline fn hit(battle: anytype, accuracy: u16) bool {
|
|
2559
|
+
if (showdown) return battle.rng.chance(u8, @intCast(u8, accuracy), 256);
|
|
2560
|
+
return battle.rng.next() < accuracy;
|
|
2625
2561
|
}
|
|
2626
|
-
}
|
|
2627
2562
|
|
|
2628
|
-
fn
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2563
|
+
inline fn confusionDuration(battle: anytype) u3 {
|
|
2564
|
+
if (showdown) return @intCast(u3, battle.rng.range(u8, 2, 6));
|
|
2565
|
+
return @intCast(u3, (battle.rng.next() & 3) + 2);
|
|
2566
|
+
}
|
|
2632
2567
|
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
else
|
|
2638
|
-
(battle.rng.next() & 3) + 2);
|
|
2568
|
+
inline fn confused(battle: anytype) bool {
|
|
2569
|
+
if (showdown) return !battle.rng.chance(u8, 128, 256);
|
|
2570
|
+
return battle.rng.next() >= Gen12.percent(50) + 1;
|
|
2571
|
+
}
|
|
2639
2572
|
|
|
2640
|
-
|
|
2641
|
-
|
|
2573
|
+
inline fn paralyzed(battle: anytype) bool {
|
|
2574
|
+
if (showdown) return battle.rng.chance(u8, 63, 256);
|
|
2575
|
+
return battle.rng.next() < Gen12.percent(25);
|
|
2576
|
+
}
|
|
2642
2577
|
|
|
2643
|
-
|
|
2578
|
+
inline fn confusionChance(battle: anytype) bool {
|
|
2579
|
+
if (showdown) return battle.rng.chance(u8, 25, 256);
|
|
2580
|
+
return battle.rng.next() < Gen12.percent(10);
|
|
2581
|
+
}
|
|
2644
2582
|
|
|
2645
|
-
fn
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
}
|
|
2583
|
+
inline fn secondaryChance(battle: anytype, low: bool) bool {
|
|
2584
|
+
if (showdown) return battle.rng.chance(u8, @as(u8, if (low) 26 else 77), 256);
|
|
2585
|
+
return battle.rng.next() < 1 + (if (low) Gen12.percent(10) else Gen12.percent(30));
|
|
2586
|
+
}
|
|
2650
2587
|
|
|
2651
|
-
fn
|
|
2652
|
-
|
|
2653
|
-
if (
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2588
|
+
inline fn poisonChance(battle: anytype, low: bool) bool {
|
|
2589
|
+
if (showdown) return battle.rng.chance(u8, @as(u8, if (low) 52 else 103), 256);
|
|
2590
|
+
return battle.rng.next() < 1 + (if (low) Gen12.percent(20) else Gen12.percent(40));
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
inline fn sleepDuration(battle: anytype) u3 {
|
|
2594
|
+
if (showdown) return @intCast(u3, battle.rng.range(u8, 1, 8));
|
|
2595
|
+
while (true) {
|
|
2596
|
+
const r = battle.rng.next() & 7;
|
|
2597
|
+
if (r != 0) return @intCast(u3, r);
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
inline fn bideThrashDuration(battle: anytype) u3 {
|
|
2602
|
+
if (showdown) return @intCast(u3, battle.rng.range(u4, 2, 4));
|
|
2603
|
+
return @intCast(u3, (battle.rng.next() & 1) + 2);
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
fn moveSlot(battle: anytype, moves: []MoveSlot, check_pp: u4) u4 {
|
|
2607
|
+
if (showdown) {
|
|
2608
|
+
if (check_pp == 0) {
|
|
2609
|
+
var i: usize = moves.len;
|
|
2610
|
+
while (i > 0) {
|
|
2611
|
+
i -= 1;
|
|
2612
|
+
if (moves[i].id != .None) {
|
|
2613
|
+
return battle.rng.range(u4, 0, @intCast(u4, i + 1)) + 1;
|
|
2614
|
+
}
|
|
2666
2615
|
}
|
|
2616
|
+
} else {
|
|
2617
|
+
var r = battle.rng.range(u4, 0, @intCast(u4, check_pp)) + 1;
|
|
2618
|
+
var i: usize = 0;
|
|
2619
|
+
while (i < moves.len and r > 0) : (i += 1) {
|
|
2620
|
+
if (moves[i].pp > 0) {
|
|
2621
|
+
r -= 1;
|
|
2622
|
+
if (r == 0) break;
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
return @intCast(u4, i + 1);
|
|
2667
2626
|
}
|
|
2668
|
-
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
while (true) {
|
|
2630
|
+
const r = @intCast(u4, battle.rng.next() & 3);
|
|
2631
|
+
if (moves[r].id != .None and (check_pp == 0 or moves[r].pp > 0)) return r + 1;
|
|
2669
2632
|
}
|
|
2670
2633
|
}
|
|
2671
2634
|
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2635
|
+
inline fn disableDuration(battle: anytype) u4 {
|
|
2636
|
+
if (showdown) return @intCast(u4, battle.rng.range(u8, 1, 9));
|
|
2637
|
+
return @intCast(u4, (battle.rng.next() & 7) + 1);
|
|
2675
2638
|
}
|
|
2676
|
-
|
|
2639
|
+
|
|
2640
|
+
inline fn metronome(battle: anytype) Move {
|
|
2641
|
+
if (showdown) {
|
|
2642
|
+
const r = battle.rng.range(u8, 0, @enumToInt(Move.Struggle) - 2);
|
|
2643
|
+
const mod = @as(u2, (if (r < @enumToInt(Move.Metronome) - 1) 1 else 2));
|
|
2644
|
+
return @intToEnum(Move, r + mod);
|
|
2645
|
+
}
|
|
2646
|
+
while (true) {
|
|
2647
|
+
const r = battle.rng.next();
|
|
2648
|
+
if (r == 0 or r == @enumToInt(Move.Metronome)) continue;
|
|
2649
|
+
if (r >= @enumToInt(Move.Struggle)) continue;
|
|
2650
|
+
return @intToEnum(Move, r);
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
inline fn psywave(battle: anytype, max: u8) u8 {
|
|
2655
|
+
if (showdown) return battle.rng.range(u8, 0, max);
|
|
2656
|
+
while (true) {
|
|
2657
|
+
const r = battle.rng.next();
|
|
2658
|
+
if (r < max) return r;
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
inline fn distribution(battle: anytype) u3 {
|
|
2663
|
+
if (showdown) return DISTRIBUTION[battle.rng.range(u8, 0, DISTRIBUTION.len)];
|
|
2664
|
+
const r = (battle.rng.next() & 3);
|
|
2665
|
+
return @intCast(u3, (if (r < 2) r else battle.rng.next() & 3) + 2);
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
inline fn unboost(battle: anytype) bool {
|
|
2669
|
+
if (showdown) return battle.rng.chance(u8, 85, 256);
|
|
2670
|
+
return battle.rng.next() < Gen12.percent(33) + 1;
|
|
2671
|
+
}
|
|
2672
|
+
};
|
|
2677
2673
|
|
|
2678
2674
|
test "RNG agreement" {
|
|
2679
2675
|
if (!showdown) return;
|