@pkmn/engine 0.1.0-dev.f3aab252 → 0.1.0-dev.f3b63b86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +35 -8
- package/build/pkg/addon/browser.d.ts +3 -0
- package/build/pkg/addon/browser.js +47 -0
- package/build/pkg/addon/browser.js.map +1 -0
- package/build/pkg/addon/node.d.ts +3 -0
- package/build/pkg/addon/node.js +110 -0
- package/build/pkg/addon/node.js.map +1 -0
- package/build/pkg/addon/wasm.d.ts +2 -0
- package/build/pkg/addon/wasm.js +53 -0
- package/build/pkg/addon/wasm.js.map +1 -0
- package/build/pkg/addon.d.ts +26 -4
- package/build/pkg/addon.js +34 -65
- 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/data.d.ts +23 -1
- package/build/pkg/data.js.map +1 -1
- package/build/pkg/gen1.d.ts +15 -18
- package/build/pkg/gen1.js +28 -19
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/index.d.ts +39 -89
- package/build/pkg/index.js +23 -70
- package/build/pkg/index.js.map +1 -1
- package/build/pkg/protocol.d.ts +20 -1
- package/build/pkg/protocol.js +105 -103
- package/build/pkg/protocol.js.map +1 -1
- package/build/tools/debug.js +68 -74
- package/build/tools/debug.js.map +1 -1
- package/build/tools/display/debug.jsx +152 -0
- package/build/tools/display/debug.jsx.map +1 -0
- package/build/tools/display/index.js +152 -0
- package/build/tools/display/index.js.map +1 -0
- package/build/tools/display/ui.jsx +359 -0
- package/build/tools/display/ui.jsx.map +1 -0
- package/build/tools/display/util.js +33 -0
- package/build/tools/display/util.js.map +1 -0
- package/build.zig +235 -213
- package/package.json +59 -141
- package/src/bin/install-pkmn-engine +25 -9
- package/src/bin/pkmn-debug +1 -1
- package/src/lib/bindings/c.zig +200 -0
- package/src/lib/bindings/node.zig +117 -0
- package/src/lib/bindings/wasm.zig +50 -0
- package/src/lib/common/array.zig +5 -12
- package/src/lib/common/debug.zig +30 -9
- package/src/lib/common/js.zig +3 -9
- package/src/lib/common/optional.zig +11 -21
- package/src/lib/common/options.zig +4 -0
- package/src/lib/common/protocol.zig +180 -171
- package/src/lib/common/rational.zig +278 -285
- package/src/lib/common/rng.zig +1 -3
- package/src/lib/common/util.zig +14 -31
- package/src/lib/gen1/calc.zig +258 -106
- package/src/lib/gen1/chance.zig +57 -54
- package/src/lib/gen1/data/moves.zig +2 -4
- package/src/lib/gen1/data/species.zig +1 -3
- package/src/lib/gen1/data/types.zig +3 -4
- package/src/lib/gen1/data.zig +12 -16
- package/src/lib/gen1/helpers.zig +13 -28
- package/src/lib/gen1/mechanics.zig +131 -101
- package/src/lib/gen2/calc.zig +12 -23
- package/src/lib/gen2/chance.zig +12 -18
- package/src/lib/gen2/data/items.zig +1 -3
- package/src/lib/gen2/data/moves.zig +2 -4
- package/src/lib/gen2/data/species.zig +1 -3
- package/src/lib/gen2/data/types.zig +7 -5
- package/src/lib/gen2/data.zig +19 -22
- package/src/lib/gen2/generated.zig +18 -23
- package/src/lib/gen2/helpers.zig +9 -18
- package/src/lib/gen2/mechanics.zig +50 -47
- package/src/lib/napi.h +2 -0
- package/src/lib/node.zig +4 -121
- package/src/lib/pkmn.zig +98 -19
- package/src/lib/wasm.zig +9 -9
- package/src/tools/display/dom.ts +93 -0
- package/src/tools/display/ui.css +354 -0
- package/build/test/display.js +0 -405
- package/build/test/display.js.map +0 -1
- package/src/test/display/pkmn.html.tmpl +0 -368
|
@@ -1,36 +1,29 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
const pkmn = @import("../pkmn.zig");
|
|
1
|
+
const DEBUG = @import("../common/debug.zig").print;
|
|
4
2
|
|
|
3
|
+
const builtin = @import("builtin");
|
|
4
|
+
const chance = @import("chance.zig");
|
|
5
5
|
const common = @import("../common/data.zig");
|
|
6
|
-
const
|
|
6
|
+
const data = @import("data.zig");
|
|
7
|
+
const pkmn = @import("../pkmn.zig");
|
|
7
8
|
const protocol = @import("../common/protocol.zig");
|
|
8
9
|
const rng = @import("../common/rng.zig");
|
|
9
|
-
|
|
10
|
-
const data = @import("data.zig");
|
|
11
|
-
const chance = @import("chance.zig");
|
|
10
|
+
const std = @import("std");
|
|
12
11
|
|
|
13
12
|
const assert = std.debug.assert;
|
|
14
|
-
|
|
15
|
-
const expectEqual = std.testing.expectEqual;
|
|
16
|
-
|
|
17
|
-
const showdown = pkmn.options.showdown;
|
|
18
|
-
|
|
19
|
-
const Choice = common.Choice;
|
|
20
|
-
const ID = common.ID;
|
|
21
|
-
const Player = common.Player;
|
|
22
|
-
const Result = common.Result;
|
|
23
|
-
|
|
24
13
|
const Boost = protocol.Boost;
|
|
14
|
+
const Choice = common.Choice;
|
|
25
15
|
const Damage = protocol.Damage;
|
|
16
|
+
const Effectiveness = data.Effectiveness;
|
|
17
|
+
const expectEqual = std.testing.expectEqual;
|
|
26
18
|
const Fail = protocol.Fail;
|
|
27
|
-
const Heal = protocol.Heal;
|
|
28
|
-
|
|
29
19
|
const Gen12 = rng.Gen12;
|
|
30
|
-
|
|
31
|
-
const
|
|
20
|
+
const Heal = protocol.Heal;
|
|
21
|
+
const ID = common.ID;
|
|
32
22
|
const Move = data.Move;
|
|
33
23
|
const MoveSlot = data.MoveSlot;
|
|
24
|
+
const Player = common.Player;
|
|
25
|
+
const Result = common.Result;
|
|
26
|
+
const showdown = pkmn.options.showdown;
|
|
34
27
|
const Side = data.Side;
|
|
35
28
|
const Species = data.Species;
|
|
36
29
|
const Stats = data.Stats;
|
|
@@ -173,7 +166,10 @@ fn selectMove(
|
|
|
173
166
|
if (foe_choice.type == .Switch) {
|
|
174
167
|
const last = side.active.move(battle.lastMove(player).index);
|
|
175
168
|
if (last.id == .Metronome) side.last_selected_move = last.id;
|
|
176
|
-
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
|
+
}
|
|
177
173
|
}
|
|
178
174
|
}
|
|
179
175
|
return null;
|
|
@@ -424,7 +420,7 @@ fn executeMove(
|
|
|
424
420
|
mslot = @intCast(battle.lastMove(player).index);
|
|
425
421
|
const stored = side.stored();
|
|
426
422
|
// GLITCH: Struggle bypass PP underflow via Hyper Beam / Trapping-switch auto selection
|
|
427
|
-
auto = isForced(&side.active) or
|
|
423
|
+
auto = pkmn.options.mod or isForced(&side.active) or
|
|
428
424
|
side.active.volatiles.Binding or side.active.volatiles.Bide or
|
|
429
425
|
side.last_selected_move == .HyperBeam or
|
|
430
426
|
Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP);
|
|
@@ -457,6 +453,25 @@ fn executeMove(
|
|
|
457
453
|
.err => return @as(?Result, Result.Error),
|
|
458
454
|
}
|
|
459
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
|
+
|
|
460
475
|
const can = skip_can or
|
|
461
476
|
try canMove(battle, player, mslot, auto, skip_pp, .None, residual, options);
|
|
462
477
|
if (!can) return null;
|
|
@@ -554,18 +569,17 @@ fn beforeMove(
|
|
|
554
569
|
// This can only happen if a Pokémon started the battle frozen/sleeping and was thawed/woken
|
|
555
570
|
// before the side had a selected a move - we simply need to assume this leads to a desync
|
|
556
571
|
if (side.last_selected_move == .None) {
|
|
557
|
-
assert(!
|
|
572
|
+
assert(!pkmn.options.mod);
|
|
558
573
|
return .err;
|
|
559
574
|
}
|
|
560
575
|
|
|
561
576
|
if (volatiles.Confusion) {
|
|
562
577
|
assert(volatiles.confusion > 0);
|
|
563
578
|
if (options.calc.overridden(player, .confusion)) |obs| switch (obs) {
|
|
564
|
-
.started => volatiles.confusion
|
|
579
|
+
.started, .ended => volatiles.confusion = 0,
|
|
565
580
|
.continuing => if (volatiles.confusion > 1) {
|
|
566
581
|
volatiles.confusion -= 1;
|
|
567
582
|
},
|
|
568
|
-
.ended => volatiles.confusion = 0,
|
|
569
583
|
.overwritten => {},
|
|
570
584
|
else => unreachable,
|
|
571
585
|
} else {
|
|
@@ -604,17 +618,10 @@ fn beforeMove(
|
|
|
604
618
|
return .err;
|
|
605
619
|
}
|
|
606
620
|
}
|
|
607
|
-
// Pokémon Showdown incorrectly changes the "target" of the confusion self-hit based
|
|
608
|
-
// on the targeting behavior of the confused Pokémon's selected move which results
|
|
609
|
-
// in the wrong behavior with respect to the Substitute + Confusion glitch
|
|
610
|
-
const target = if (showdown and Move.get(side.last_selected_move).target == .Self)
|
|
611
|
-
player
|
|
612
|
-
else
|
|
613
|
-
player.foe();
|
|
614
621
|
|
|
615
622
|
const uncapped = battle.last_damage;
|
|
616
623
|
// Skipping adjustDamage / randomizeDamage / checkHit
|
|
617
|
-
_ = try applyDamage(battle, player,
|
|
624
|
+
_ = try applyDamage(battle, player, player.foe(), .Confusion, options);
|
|
618
625
|
// Pokémon Showdown thinks that confusion damage is uncapped ¯\_(ツ)_/¯
|
|
619
626
|
if (showdown) battle.last_damage = uncapped;
|
|
620
627
|
|
|
@@ -704,7 +711,11 @@ fn beforeMove(
|
|
|
704
711
|
// can be expected to recognize this pattern and pass the information only to the
|
|
705
712
|
// correct player)
|
|
706
713
|
try log.start(.{ battle.active(player), .ConfusionSilent });
|
|
707
|
-
options.chance.observe(.confusion, player, if (overwritten)
|
|
714
|
+
options.chance.observe(.confusion, player, if (overwritten)
|
|
715
|
+
if (pkmn.options.overwrite) .overwritten else .continuing
|
|
716
|
+
else
|
|
717
|
+
.started);
|
|
718
|
+
if (!overwritten) options.calc.confusion(player);
|
|
708
719
|
}
|
|
709
720
|
|
|
710
721
|
// This shouldn't actually set last_used_move, but Pokémon Showdown sets last
|
|
@@ -770,9 +781,14 @@ fn canMove(
|
|
|
770
781
|
|
|
771
782
|
if (move.effect == .Thrashing) {
|
|
772
783
|
Effects.thrashing(battle, player, options);
|
|
773
|
-
} else if (
|
|
774
|
-
// Pokémon Showdown handles this after hit/miss checks and damage calculation
|
|
775
|
-
|
|
784
|
+
} else if (move.effect == .Binding) {
|
|
785
|
+
// Pokémon Showdown handles this after hit/miss checks and damage calculation, though clears
|
|
786
|
+
// Recharging in onTryMove (required for the Hyper Beam automatic selection glitch)
|
|
787
|
+
if (showdown) {
|
|
788
|
+
battle.foe(player).active.volatiles.Recharging = false;
|
|
789
|
+
} else {
|
|
790
|
+
try Effects.binding(battle, player, false, options);
|
|
791
|
+
}
|
|
776
792
|
}
|
|
777
793
|
|
|
778
794
|
return true;
|
|
@@ -862,7 +878,7 @@ fn doMove(
|
|
|
862
878
|
var miss = showdown and miss: {
|
|
863
879
|
immune = move.target != .Self and !status and !counter and
|
|
864
880
|
(@intFromEnum(move.type.effectiveness(foe.active.types.type1)) == 0 or
|
|
865
|
-
|
|
881
|
+
@intFromEnum(move.type.effectiveness(foe.active.types.type2)) == 0);
|
|
866
882
|
if (immune and move.effect != .Binding) break :miss true;
|
|
867
883
|
if (move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe) {
|
|
868
884
|
battle.last_damage = 0;
|
|
@@ -1003,7 +1019,7 @@ fn doMove(
|
|
|
1003
1019
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
1004
1020
|
|
|
1005
1021
|
var nullified = false;
|
|
1006
|
-
var hit:
|
|
1022
|
+
var hit: usize = 0;
|
|
1007
1023
|
while (hit < hits) {
|
|
1008
1024
|
if (hit == 0) {
|
|
1009
1025
|
if (crit) try log.crit(.{battle.active(player.foe())});
|
|
@@ -1031,23 +1047,21 @@ fn doMove(
|
|
|
1031
1047
|
if (showdown and move.effect == .Twineedle and !nullified and foe.stored().hp > 0) {
|
|
1032
1048
|
try Effects.poison(battle, player, Move.get(.PoisonSting), options);
|
|
1033
1049
|
}
|
|
1034
|
-
try log.hitcount(.{ battle.active(player.foe()), hit });
|
|
1035
|
-
} else if (showdown) {
|
|
1050
|
+
try log.hitcount(.{ battle.active(player.foe()), @as(u4, @intCast(hit)) });
|
|
1051
|
+
} else if (showdown and move.effect == .Binding) {
|
|
1036
1052
|
// This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
try buildRage(battle, player.foe(), options);
|
|
1048
|
-
}
|
|
1049
|
-
return null;
|
|
1053
|
+
try Effects.binding(battle, player, rewrap, options);
|
|
1054
|
+
if (immune) {
|
|
1055
|
+
battle.last_damage = 0;
|
|
1056
|
+
assert(foe.stored().hp > 0);
|
|
1057
|
+
// Pokémon Showdown logs |-damage| here instead of |-immune| because logic...
|
|
1058
|
+
if (sub) {
|
|
1059
|
+
try log.activate(.{ battle.active(player.foe()), .Substitute });
|
|
1060
|
+
} else {
|
|
1061
|
+
try log.damage(.{ battle.active(player.foe()), foe.stored(), .None });
|
|
1062
|
+
try buildRage(battle, player.foe(), options);
|
|
1050
1063
|
}
|
|
1064
|
+
return null;
|
|
1051
1065
|
}
|
|
1052
1066
|
}
|
|
1053
1067
|
|
|
@@ -1128,7 +1142,7 @@ fn calcDamage(
|
|
|
1128
1142
|
if ((!showdown or !cfz) and (atk > 255 or def > 255)) {
|
|
1129
1143
|
atk = @max((atk / 4) & 255, 1);
|
|
1130
1144
|
// GLITCH: not adjusted to be a min of 1 on cartridge (can lead to division-by-zero freeze)
|
|
1131
|
-
def = @max((def / 4) & 255, if (
|
|
1145
|
+
def = @max((def / 4) & 255, if (pkmn.options.mod) 1 else 0);
|
|
1132
1146
|
}
|
|
1133
1147
|
|
|
1134
1148
|
const lvl: u32 = side.stored().level * @as(u2, if (crit) 2 else 1);
|
|
@@ -1149,7 +1163,7 @@ fn calcDamage(
|
|
|
1149
1163
|
// encountered so we need to make sure that in the event of a miss we commit the status of the
|
|
1150
1164
|
// crit roll. The alternative to doing this extra work is to *always* commit the crit roll but
|
|
1151
1165
|
// that results in counter intuitive probabilities in the common case
|
|
1152
|
-
if (pkmn.options.chance and !
|
|
1166
|
+
if (pkmn.options.chance and !pkmn.options.mod and move.effect != .Explode) {
|
|
1153
1167
|
atk = attack(side, !crit, special);
|
|
1154
1168
|
def = defense(target, !crit, special, false);
|
|
1155
1169
|
if ((atk > 255 or def > 255) and (def / 4) & 255 == 0) options.chance.glitch();
|
|
@@ -1234,13 +1248,13 @@ fn specialDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1234
1248
|
.Psywave => power: {
|
|
1235
1249
|
const max: u8 = @intCast(@as(u16, side.stored().level) * 3 / 2);
|
|
1236
1250
|
// GLITCH: Psywave infinite glitch loop
|
|
1237
|
-
if (!
|
|
1251
|
+
if (!pkmn.options.mod and max <= 1) return Result.Error;
|
|
1238
1252
|
break :power try Rolls.psywave(battle, player, max, options);
|
|
1239
1253
|
},
|
|
1240
1254
|
else => unreachable,
|
|
1241
1255
|
};
|
|
1242
1256
|
|
|
1243
|
-
if (battle.last_damage == 0) return if (
|
|
1257
|
+
if (battle.last_damage == 0) return if (pkmn.options.mod) null else Result.Error;
|
|
1244
1258
|
|
|
1245
1259
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
1246
1260
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -1260,15 +1274,15 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1260
1274
|
// Pretend Splash was used as a stand-in when no move has been used to fail below with 0 BP
|
|
1261
1275
|
const foe_last_selected_move =
|
|
1262
1276
|
Move.get(if (foe.last_selected_move == .None or foe.last_selected_move == .SKIP_TURN)
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1277
|
+
.Splash
|
|
1278
|
+
else
|
|
1279
|
+
foe.last_selected_move);
|
|
1266
1280
|
|
|
1267
1281
|
const used = battle.lastMove(player.foe()).counterable != 0;
|
|
1268
1282
|
const selected = foe_last_selected_move.bp > 0 and
|
|
1269
1283
|
foe.last_selected_move != .Counter and
|
|
1270
1284
|
(foe_last_selected_move.type == .Normal or
|
|
1271
|
-
|
|
1285
|
+
foe_last_selected_move.type == .Fighting);
|
|
1272
1286
|
|
|
1273
1287
|
if (!used and !selected) {
|
|
1274
1288
|
try options.log.fail(.{ battle.active(player), .None });
|
|
@@ -1277,12 +1291,16 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1277
1291
|
|
|
1278
1292
|
if (!used or !selected) {
|
|
1279
1293
|
// GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
|
|
1280
|
-
if (!
|
|
1294
|
+
if (!pkmn.options.mod) return Result.Error;
|
|
1281
1295
|
try options.log.fail(.{ battle.active(player), .None });
|
|
1282
1296
|
return null;
|
|
1283
1297
|
}
|
|
1284
1298
|
|
|
1285
|
-
|
|
1299
|
+
// NOTE: llvm/llvm-project#58557
|
|
1300
|
+
battle.last_damage = if (comptime builtin.target.cpu.arch.isWasm())
|
|
1301
|
+
@max(battle.last_damage *% 2, std.math.maxInt(u16))
|
|
1302
|
+
else
|
|
1303
|
+
battle.last_damage *| 2;
|
|
1286
1304
|
|
|
1287
1305
|
// Pokémon Showdown calls moveHit before Counter
|
|
1288
1306
|
if (!showdown and !try checkHit(battle, player, move, options)) return null;
|
|
@@ -1314,7 +1332,7 @@ fn applyDamage(
|
|
|
1314
1332
|
subbed.active.volatiles.Substitute = false;
|
|
1315
1333
|
// battle.last_damage is not updated with the amount of HP the Substitute had
|
|
1316
1334
|
try options.log.end(.{ battle.active(sub_player), .Substitute });
|
|
1317
|
-
options.calc.capped(sub_player);
|
|
1335
|
+
if (reason != .Confusion) options.calc.capped(sub_player);
|
|
1318
1336
|
return true;
|
|
1319
1337
|
} else {
|
|
1320
1338
|
// Safe to truncate since less than subbed.volatiles.substitute which is a u8
|
|
@@ -1428,7 +1446,16 @@ fn moveHit(
|
|
|
1428
1446
|
break :miss true;
|
|
1429
1447
|
}
|
|
1430
1448
|
if (!showdown) {
|
|
1431
|
-
if (move.effect == .Swift)
|
|
1449
|
+
if (move.effect == .Swift) {
|
|
1450
|
+
// If Swift could cause a division-by-zero freeze (which doesn't exist on Pokémon
|
|
1451
|
+
// Showdown, hence why the block above doesn't include this code), we need the
|
|
1452
|
+
// damage roll to be committed. However, the commit code will not save this roll if
|
|
1453
|
+
// the glitch occurs and the move was not marked as having hit. To work around this
|
|
1454
|
+
// we flag that the move hit (as Swift trivially does), but pass the probability of
|
|
1455
|
+
// the hit as zero to ensure it doesn't actually get saved.
|
|
1456
|
+
try options.chance.hit(player, true, 0);
|
|
1457
|
+
return true;
|
|
1458
|
+
}
|
|
1432
1459
|
if (foe.active.volatiles.Invulnerable) break :miss true;
|
|
1433
1460
|
}
|
|
1434
1461
|
|
|
@@ -1524,7 +1551,7 @@ fn faint(battle: anytype, player: Player, done: bool, cap: bool, options: anytyp
|
|
|
1524
1551
|
assert(!foe_volatiles.MultiHit);
|
|
1525
1552
|
if (foe_volatiles.Bide) {
|
|
1526
1553
|
assert(!foe_volatiles.Thrashing and !foe_volatiles.Rage);
|
|
1527
|
-
foe_volatiles.state = if (
|
|
1554
|
+
foe_volatiles.state = if (pkmn.options.mod) 0 else foe_volatiles.state & 255;
|
|
1528
1555
|
if (foe_volatiles.state != 0) return Result.Error;
|
|
1529
1556
|
}
|
|
1530
1557
|
|
|
@@ -1615,12 +1642,12 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1615
1642
|
|
|
1616
1643
|
battle.turn += 1;
|
|
1617
1644
|
|
|
1618
|
-
if (
|
|
1645
|
+
if (pkmn.options.mod and pkmn.options.ebc and checkEBC(battle)) {
|
|
1619
1646
|
try options.log.tie(.{});
|
|
1620
1647
|
return Result.Tie;
|
|
1621
1648
|
}
|
|
1622
1649
|
|
|
1623
|
-
if (
|
|
1650
|
+
if (pkmn.options.mod and battle.turn >= 1000) {
|
|
1624
1651
|
try options.log.tie(.{});
|
|
1625
1652
|
return Result.Tie;
|
|
1626
1653
|
} else if (battle.turn >= 65535) {
|
|
@@ -1869,6 +1896,7 @@ pub const Effects = struct {
|
|
|
1869
1896
|
|
|
1870
1897
|
foe.active.volatiles.confusion = Rolls.confusionDuration(battle, player, options);
|
|
1871
1898
|
options.chance.observe(.confusion, player.foe(), .started);
|
|
1899
|
+
options.calc.confusion(player.foe());
|
|
1872
1900
|
|
|
1873
1901
|
try options.log.start(.{ battle.active(player.foe()), .Confusion });
|
|
1874
1902
|
}
|
|
@@ -1914,12 +1942,11 @@ pub const Effects = struct {
|
|
|
1914
1942
|
|
|
1915
1943
|
// Technically this is still considered simply a "miss" on the cartridge,
|
|
1916
1944
|
// but diverging from Pokémon Showdown here would mostly just be pedantic
|
|
1917
|
-
if (n == 0) {
|
|
1945
|
+
if (n == 0 or (pkmn.options.mod and err)) {
|
|
1918
1946
|
try options.log.fail(.{ foe_ident, .None });
|
|
1919
1947
|
return null;
|
|
1920
1948
|
} else if (err) {
|
|
1921
1949
|
// GLITCH: Transform + Mirror Move / Metronome PP softlock
|
|
1922
|
-
assert(!showdown);
|
|
1923
1950
|
return Result.Error;
|
|
1924
1951
|
}
|
|
1925
1952
|
|
|
@@ -1997,7 +2024,7 @@ pub const Effects = struct {
|
|
|
1997
2024
|
if (foe.active.types.includes(move.type)) return;
|
|
1998
2025
|
if (!try Rolls.secondaryChance(battle, player, true, options)) return;
|
|
1999
2026
|
// Freeze Clause Mod
|
|
2000
|
-
if (
|
|
2027
|
+
if (pkmn.options.mod) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
|
|
2001
2028
|
|
|
2002
2029
|
foe_stored.status = Status.init(.FRZ);
|
|
2003
2030
|
// GLITCH: Hyper Beam recharging status is not cleared
|
|
@@ -2338,7 +2365,7 @@ pub const Effects = struct {
|
|
|
2338
2365
|
}
|
|
2339
2366
|
|
|
2340
2367
|
// Sleep Clause Mod
|
|
2341
|
-
if (
|
|
2368
|
+
if (pkmn.options.mod) {
|
|
2342
2369
|
for (foe.pokemon) |p| {
|
|
2343
2370
|
if (Status.is(p.status, .SLP) and !Status.is(p.status, .EXT)) return;
|
|
2344
2371
|
}
|
|
@@ -2444,7 +2471,7 @@ pub const Effects = struct {
|
|
|
2444
2471
|
if (side.active.volatiles.Binding) return;
|
|
2445
2472
|
side.active.volatiles.Binding = true;
|
|
2446
2473
|
// GLITCH: Hyper Beam automatic selection glitch if Recharging gets cleared on miss
|
|
2447
|
-
// (Pokémon Showdown
|
|
2474
|
+
// (Pokémon Showdown clears Recharging in onTryMove instead)
|
|
2448
2475
|
if (!showdown) {
|
|
2449
2476
|
foe.active.volatiles.Recharging = false;
|
|
2450
2477
|
} else if (foe.stored().hp == 0) return if (!rewrap) battle.rng.advance(1);
|
|
@@ -2941,9 +2968,10 @@ pub const Rolls = struct {
|
|
|
2941
2968
|
}
|
|
2942
2969
|
|
|
2943
2970
|
fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2944
|
-
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2945
|
-
@intCast(val)
|
|
2946
|
-
else
|
|
2971
|
+
const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
|
|
2972
|
+
if (val >= 1 and val <= 7) @as(u3, @intCast(val)) else null
|
|
2973
|
+
else
|
|
2974
|
+
null) orelse if (@hasDecl(@TypeOf(battle.rng), "sleepDuration"))
|
|
2947
2975
|
battle.rng.sleepDuration(player)
|
|
2948
2976
|
else if (showdown)
|
|
2949
2977
|
battle.rng.range(u3, 1, 8)
|
|
@@ -2960,9 +2988,10 @@ pub const Rolls = struct {
|
|
|
2960
2988
|
}
|
|
2961
2989
|
|
|
2962
2990
|
fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
|
|
2963
|
-
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2964
|
-
val
|
|
2965
|
-
else
|
|
2991
|
+
const duration: u4 = (if (options.calc.overridden(player, .duration)) |val|
|
|
2992
|
+
if (val >= 1 and val <= 8) val else null
|
|
2993
|
+
else
|
|
2994
|
+
null) orelse if (@hasDecl(@TypeOf(battle.rng), "disableDuration"))
|
|
2966
2995
|
battle.rng.disableDuration(player)
|
|
2967
2996
|
else if (showdown)
|
|
2968
2997
|
battle.rng.range(u4, 1, 9)
|
|
@@ -2975,9 +3004,10 @@ pub const Rolls = struct {
|
|
|
2975
3004
|
}
|
|
2976
3005
|
|
|
2977
3006
|
fn confusionDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2978
|
-
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2979
|
-
@intCast(val)
|
|
2980
|
-
else
|
|
3007
|
+
const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
|
|
3008
|
+
if (val >= 2 and val <= 5) @as(u3, @intCast(val)) else null
|
|
3009
|
+
else
|
|
3010
|
+
null) orelse if (@hasDecl(@TypeOf(battle.rng), "confusionDuration"))
|
|
2981
3011
|
battle.rng.confusionDuration(player)
|
|
2982
3012
|
else if (showdown)
|
|
2983
3013
|
battle.rng.range(u3, 2, 6)
|
|
@@ -2990,9 +3020,10 @@ pub const Rolls = struct {
|
|
|
2990
3020
|
}
|
|
2991
3021
|
|
|
2992
3022
|
fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2993
|
-
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2994
|
-
@intCast(val)
|
|
2995
|
-
else
|
|
3023
|
+
const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
|
|
3024
|
+
if (val >= 2 and val <= 3) @as(u3, @intCast(val)) else null
|
|
3025
|
+
else
|
|
3026
|
+
null) orelse if (@hasDecl(@TypeOf(battle.rng), "attackingDuration"))
|
|
2996
3027
|
battle.rng.attackingDuration(player)
|
|
2997
3028
|
else if (showdown)
|
|
2998
3029
|
battle.rng.range(u3, 2, 4)
|
|
@@ -3013,9 +3044,10 @@ pub const Rolls = struct {
|
|
|
3013
3044
|
options: anytype,
|
|
3014
3045
|
) !u3 {
|
|
3015
3046
|
const roll = if (effect == .Binding) .duration else .multi_hit;
|
|
3016
|
-
const n: u3 = if (options.calc.overridden(player, roll)) |val|
|
|
3017
|
-
@intCast(val)
|
|
3018
|
-
else
|
|
3047
|
+
const n: u3 = (if (options.calc.overridden(player, roll)) |val|
|
|
3048
|
+
if (val >= 2 and val <= 5) @as(u3, @intCast(val)) else null
|
|
3049
|
+
else
|
|
3050
|
+
null) orelse if (@hasDecl(@TypeOf(battle.rng), "distribution"))
|
|
3019
3051
|
battle.rng.distribution(player)
|
|
3020
3052
|
else if (showdown)
|
|
3021
3053
|
DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
|
|
@@ -3041,15 +3073,13 @@ pub const Rolls = struct {
|
|
|
3041
3073
|
options: anytype,
|
|
3042
3074
|
) !u4 {
|
|
3043
3075
|
// TODO: consider throwing error instead of rerolling?
|
|
3044
|
-
const
|
|
3076
|
+
const slot: u4 = (if (options.calc.overridden(player, .move_slot)) |val|
|
|
3045
3077
|
if (moves[val - 1].id != .None and (check_pp == 0 or moves[val - 1].pp > 0))
|
|
3046
3078
|
val
|
|
3047
3079
|
else
|
|
3048
3080
|
null
|
|
3049
3081
|
else
|
|
3050
|
-
null
|
|
3051
|
-
|
|
3052
|
-
const slot: u4 = overridden orelse if (@hasDecl(@TypeOf(battle.rng), "moveSlot"))
|
|
3082
|
+
null) orelse if (@hasDecl(@TypeOf(battle.rng), "moveSlot"))
|
|
3053
3083
|
battle.rng.moveSlot(player, moves, check_pp)
|
|
3054
3084
|
else slot: {
|
|
3055
3085
|
if (showdown) {
|
|
@@ -3115,11 +3145,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3115
3145
|
},
|
|
3116
3146
|
.Switch => {
|
|
3117
3147
|
const side = battle.side(player);
|
|
3118
|
-
var slot:
|
|
3148
|
+
var slot: usize = 2;
|
|
3119
3149
|
while (slot <= 6) : (slot += 1) {
|
|
3120
3150
|
const id = side.order[slot - 1];
|
|
3121
3151
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
3122
|
-
out[n] = .{ .type = .Switch, .data = slot };
|
|
3152
|
+
out[n] = .{ .type = .Switch, .data = @intCast(slot) };
|
|
3123
3153
|
n += 1;
|
|
3124
3154
|
}
|
|
3125
3155
|
if (n == 0) {
|
|
@@ -3150,11 +3180,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3150
3180
|
return n;
|
|
3151
3181
|
}
|
|
3152
3182
|
|
|
3153
|
-
var slot:
|
|
3183
|
+
var slot: usize = 2;
|
|
3154
3184
|
while (slot <= 6) : (slot += 1) {
|
|
3155
3185
|
const id = side.order[slot - 1];
|
|
3156
3186
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
3157
|
-
out[n] = .{ .type = .Switch, .data = slot };
|
|
3187
|
+
out[n] = .{ .type = .Switch, .data = @intCast(slot) };
|
|
3158
3188
|
n += 1;
|
|
3159
3189
|
}
|
|
3160
3190
|
|
|
@@ -3187,7 +3217,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3187
3217
|
const struggle =
|
|
3188
3218
|
m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
|
|
3189
3219
|
const s = if (struggle) 0 else slot;
|
|
3190
|
-
out[n] = .{ .type = .Move, .data = s };
|
|
3220
|
+
out[n] = .{ .type = .Move, .data = @intCast(s) };
|
|
3191
3221
|
n += 1;
|
|
3192
3222
|
return n;
|
|
3193
3223
|
}
|
|
@@ -3201,13 +3231,13 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3201
3231
|
if (m.id == .None) break;
|
|
3202
3232
|
if (m.pp == 0) continue;
|
|
3203
3233
|
if (active.volatiles.disable_move == slot) continue;
|
|
3204
|
-
out[n] = .{ .type = .Move, .data = slot };
|
|
3234
|
+
out[n] = .{ .type = .Move, .data = @intCast(slot) };
|
|
3205
3235
|
n += 1;
|
|
3206
3236
|
}
|
|
3207
3237
|
// Struggle (Pokémon Showdown would use 'move 1' here)
|
|
3208
3238
|
if (n == before) {
|
|
3209
3239
|
// GLITCH: Transform + Mirror Move / Metronome PP softlock
|
|
3210
|
-
if (!
|
|
3240
|
+
if (!pkmn.options.mod) {
|
|
3211
3241
|
while (slot <= 4) : (slot += 1) {
|
|
3212
3242
|
if (active.moves[slot - 1].pp != 0) return n;
|
|
3213
3243
|
}
|
package/src/lib/gen2/calc.zig
CHANGED
|
@@ -1,30 +1,19 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
const pkmn = @import("../pkmn.zig");
|
|
4
|
-
|
|
1
|
+
const chance = @import("chance.zig");
|
|
5
2
|
const common = @import("../common/data.zig");
|
|
6
|
-
const util = @import("../common/util.zig");
|
|
7
|
-
|
|
8
3
|
const Optional = @import("../common/optional.zig").Optional;
|
|
4
|
+
const pkmn = @import("../pkmn.zig");
|
|
5
|
+
const std = @import("std");
|
|
6
|
+
const util = @import("../common/util.zig");
|
|
9
7
|
|
|
10
|
-
const
|
|
11
|
-
|
|
8
|
+
const Action = chance.Action;
|
|
9
|
+
const Actions = chance.Actions;
|
|
12
10
|
const assert = std.debug.assert;
|
|
13
|
-
|
|
11
|
+
const Criticals = chance.Criticals;
|
|
12
|
+
const Damages = chance.Damages;
|
|
14
13
|
const enabled = pkmn.options.calc;
|
|
15
|
-
|
|
14
|
+
const isPointerTo = util.isPointerTo;
|
|
16
15
|
const Player = common.Player;
|
|
17
|
-
|
|
18
16
|
const PointerType = util.PointerType;
|
|
19
|
-
const isPointerTo = util.isPointerTo;
|
|
20
|
-
|
|
21
|
-
const Actions = chance.Actions;
|
|
22
|
-
const Action = chance.Action;
|
|
23
|
-
const Criticals = chance.Criticals;
|
|
24
|
-
const Damages = chance.Damages;
|
|
25
|
-
|
|
26
|
-
const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
|
|
27
|
-
const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
|
|
28
17
|
|
|
29
18
|
/// Information relevant to damage calculation that occured during a Generation II battle `update`.
|
|
30
19
|
pub const Summaries = extern struct {
|
|
@@ -99,8 +88,8 @@ pub const Calc = struct {
|
|
|
99
88
|
};
|
|
100
89
|
|
|
101
90
|
return if (switch (@typeInfo(@TypeOf(val))) {
|
|
102
|
-
|
|
103
|
-
|
|
91
|
+
.@"enum" => val != .None,
|
|
92
|
+
.int => val != 0,
|
|
104
93
|
else => unreachable,
|
|
105
94
|
}) val else null;
|
|
106
95
|
}
|
|
@@ -156,6 +145,6 @@ fn ReturnType(comptime field: Action.Field) type {
|
|
|
156
145
|
return ?(switch (field) {
|
|
157
146
|
.damages => u8,
|
|
158
147
|
.critical_hits => Optional(bool),
|
|
159
|
-
else =>
|
|
148
|
+
else => @FieldType(Action, @tagName(field)),
|
|
160
149
|
});
|
|
161
150
|
}
|