@pkmn/engine 0.1.0-dev.a0447192 → 0.1.0-dev.a15b6c17
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 +22 -12
- package/build/pkg/addon/browser.d.ts +1 -1
- package/build/pkg/addon/browser.js +6 -2
- package/build/pkg/addon/browser.js.map +1 -1
- package/build/pkg/addon/node.js +22 -8
- package/build/pkg/addon/node.js.map +1 -1
- 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 +25 -5
- package/build/pkg/addon.js +8 -17
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/data/layout.json +15 -2
- package/build/pkg/data/protocol.json +23 -9
- package/build/pkg/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 +37 -100
- package/build/pkg/index.js +23 -75
- package/build/pkg/index.js.map +1 -1
- package/build/pkg/protocol.d.ts +11 -2
- package/build/pkg/protocol.js.map +1 -1
- package/build/tools/debug.js +55 -12
- package/build/tools/debug.js.map +1 -1
- package/build/tools/display/debug.jsx +19 -94
- package/build/tools/display/debug.jsx.map +1 -1
- package/build/tools/display/index.js +35 -15
- package/build/tools/display/index.js.map +1 -1
- package/build/tools/display/ui.jsx +20 -9
- package/build/tools/display/ui.jsx.map +1 -1
- package/build/tools/display/util.js +140 -1
- package/build/tools/display/util.js.map +1 -1
- package/build.zig +239 -217
- package/package.json +34 -130
- package/src/bin/install-pkmn-engine +25 -9
- 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 +33 -59
- package/src/lib/common/js.zig +3 -9
- package/src/lib/common/optional.zig +11 -21
- package/src/lib/common/options.zig +2 -0
- package/src/lib/common/protocol.zig +228 -197
- package/src/lib/common/rational.zig +275 -284
- package/src/lib/common/rng.zig +1 -3
- package/src/lib/common/util.zig +14 -31
- package/src/lib/gen1/calc.zig +159 -163
- package/src/lib/gen1/chance.zig +146 -93
- 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 +12 -20
- package/src/lib/gen1/mechanics.zig +103 -78
- 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 +58 -55
- 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 +43 -31
- package/src/tools/display/ui.css +0 -5
|
@@ -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;
|
|
@@ -91,11 +84,11 @@ fn start(battle: anytype, options: anytype) !Result {
|
|
|
91
84
|
|
|
92
85
|
const p1_slot = findFirstAlive(p1);
|
|
93
86
|
assert(!showdown or p1_slot == 1);
|
|
94
|
-
if (p1_slot == 0) return if (findFirstAlive(p2) == 0)
|
|
87
|
+
if (p1_slot == 0) return if (findFirstAlive(p2) == 0) .Tie else .Lose;
|
|
95
88
|
|
|
96
89
|
const p2_slot = findFirstAlive(p2);
|
|
97
90
|
assert(!showdown or p2_slot == 1);
|
|
98
|
-
if (p2_slot == 0) return
|
|
91
|
+
if (p2_slot == 0) return .Win;
|
|
99
92
|
|
|
100
93
|
try switchIn(battle, .P1, p1_slot, true, options);
|
|
101
94
|
try switchIn(battle, .P2, p2_slot, true, options);
|
|
@@ -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 .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);
|
|
@@ -436,7 +432,7 @@ fn executeMove(
|
|
|
436
432
|
// technically be from this Pokémon because it must be exactly 1 to not desync and
|
|
437
433
|
// every Pokémon must have at least one move
|
|
438
434
|
if (mslot != 1 or side.active.move(mslot).id != side.last_selected_move) {
|
|
439
|
-
return
|
|
435
|
+
return .Error;
|
|
440
436
|
} else {
|
|
441
437
|
auto = true;
|
|
442
438
|
}
|
|
@@ -454,7 +450,26 @@ fn executeMove(
|
|
|
454
450
|
.skip_can => skip_can = true,
|
|
455
451
|
.skip_pp => skip_pp = true,
|
|
456
452
|
.ok => {},
|
|
457
|
-
.err => return
|
|
453
|
+
.err => return .Error,
|
|
454
|
+
}
|
|
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;
|
|
458
473
|
}
|
|
459
474
|
|
|
460
475
|
const can = skip_can or
|
|
@@ -554,7 +569,7 @@ 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
|
|
|
@@ -603,17 +618,10 @@ fn beforeMove(
|
|
|
603
618
|
return .err;
|
|
604
619
|
}
|
|
605
620
|
}
|
|
606
|
-
// Pokémon Showdown incorrectly changes the "target" of the confusion self-hit based
|
|
607
|
-
// on the targeting behavior of the confused Pokémon's selected move which results
|
|
608
|
-
// in the wrong behavior with respect to the Substitute + Confusion glitch
|
|
609
|
-
const target = if (showdown and Move.get(side.last_selected_move).target == .Self)
|
|
610
|
-
player
|
|
611
|
-
else
|
|
612
|
-
player.foe();
|
|
613
621
|
|
|
614
622
|
const uncapped = battle.last_damage;
|
|
615
623
|
// Skipping adjustDamage / randomizeDamage / checkHit
|
|
616
|
-
_ = try applyDamage(battle, player,
|
|
624
|
+
_ = try applyDamage(battle, player, player.foe(), .Confusion, options);
|
|
617
625
|
// Pokémon Showdown thinks that confusion damage is uncapped ¯\_(ツ)_/¯
|
|
618
626
|
if (showdown) battle.last_damage = uncapped;
|
|
619
627
|
|
|
@@ -773,9 +781,14 @@ fn canMove(
|
|
|
773
781
|
|
|
774
782
|
if (move.effect == .Thrashing) {
|
|
775
783
|
Effects.thrashing(battle, player, options);
|
|
776
|
-
} else if (
|
|
777
|
-
// Pokémon Showdown handles this after hit/miss checks and damage calculation
|
|
778
|
-
|
|
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
|
+
}
|
|
779
792
|
}
|
|
780
793
|
|
|
781
794
|
return true;
|
|
@@ -865,7 +878,7 @@ fn doMove(
|
|
|
865
878
|
var miss = showdown and miss: {
|
|
866
879
|
immune = move.target != .Self and !status and !counter and
|
|
867
880
|
(@intFromEnum(move.type.effectiveness(foe.active.types.type1)) == 0 or
|
|
868
|
-
|
|
881
|
+
@intFromEnum(move.type.effectiveness(foe.active.types.type2)) == 0);
|
|
869
882
|
if (immune and move.effect != .Binding) break :miss true;
|
|
870
883
|
if (move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe) {
|
|
871
884
|
battle.last_damage = 0;
|
|
@@ -903,7 +916,7 @@ fn doMove(
|
|
|
903
916
|
if (showdown) break :damage; // skip adjustDamage / randomizeDamage
|
|
904
917
|
} else if (!calcDamage(battle, player, player.foe(), move, crit, options)) {
|
|
905
918
|
if (!showdown) try options.chance.commit(player, .err);
|
|
906
|
-
return
|
|
919
|
+
return .Error;
|
|
907
920
|
}
|
|
908
921
|
if (battle.last_damage == 0) {
|
|
909
922
|
immune = true;
|
|
@@ -1006,7 +1019,7 @@ fn doMove(
|
|
|
1006
1019
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
1007
1020
|
|
|
1008
1021
|
var nullified = false;
|
|
1009
|
-
var hit:
|
|
1022
|
+
var hit: usize = 0;
|
|
1010
1023
|
while (hit < hits) {
|
|
1011
1024
|
if (hit == 0) {
|
|
1012
1025
|
if (crit) try log.crit(.{battle.active(player.foe())});
|
|
@@ -1034,7 +1047,7 @@ fn doMove(
|
|
|
1034
1047
|
if (showdown and move.effect == .Twineedle and !nullified and foe.stored().hp > 0) {
|
|
1035
1048
|
try Effects.poison(battle, player, Move.get(.PoisonSting), options);
|
|
1036
1049
|
}
|
|
1037
|
-
try log.hitcount(.{ battle.active(player.foe()), hit });
|
|
1050
|
+
try log.hitcount(.{ battle.active(player.foe()), @as(u4, @intCast(hit)) });
|
|
1038
1051
|
} else if (showdown and move.effect == .Binding) {
|
|
1039
1052
|
// This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
|
|
1040
1053
|
try Effects.binding(battle, player, rewrap, options);
|
|
@@ -1129,7 +1142,7 @@ fn calcDamage(
|
|
|
1129
1142
|
if ((!showdown or !cfz) and (atk > 255 or def > 255)) {
|
|
1130
1143
|
atk = @max((atk / 4) & 255, 1);
|
|
1131
1144
|
// GLITCH: not adjusted to be a min of 1 on cartridge (can lead to division-by-zero freeze)
|
|
1132
|
-
def = @max((def / 4) & 255, if (
|
|
1145
|
+
def = @max((def / 4) & 255, if (pkmn.options.mod) 1 else 0);
|
|
1133
1146
|
}
|
|
1134
1147
|
|
|
1135
1148
|
const lvl: u32 = side.stored().level * @as(u2, if (crit) 2 else 1);
|
|
@@ -1150,7 +1163,7 @@ fn calcDamage(
|
|
|
1150
1163
|
// encountered so we need to make sure that in the event of a miss we commit the status of the
|
|
1151
1164
|
// crit roll. The alternative to doing this extra work is to *always* commit the crit roll but
|
|
1152
1165
|
// that results in counter intuitive probabilities in the common case
|
|
1153
|
-
if (pkmn.options.chance and !
|
|
1166
|
+
if (pkmn.options.chance and !pkmn.options.mod and move.effect != .Explode) {
|
|
1154
1167
|
atk = attack(side, !crit, special);
|
|
1155
1168
|
def = defense(target, !crit, special, false);
|
|
1156
1169
|
if ((atk > 255 or def > 255) and (def / 4) & 255 == 0) options.chance.glitch();
|
|
@@ -1235,13 +1248,13 @@ fn specialDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1235
1248
|
.Psywave => power: {
|
|
1236
1249
|
const max: u8 = @intCast(@as(u16, side.stored().level) * 3 / 2);
|
|
1237
1250
|
// GLITCH: Psywave infinite glitch loop
|
|
1238
|
-
if (!
|
|
1251
|
+
if (!pkmn.options.mod and max <= 1) return .Error;
|
|
1239
1252
|
break :power try Rolls.psywave(battle, player, max, options);
|
|
1240
1253
|
},
|
|
1241
1254
|
else => unreachable,
|
|
1242
1255
|
};
|
|
1243
1256
|
|
|
1244
|
-
if (battle.last_damage == 0) return if (
|
|
1257
|
+
if (battle.last_damage == 0) return if (pkmn.options.mod) null else .Error;
|
|
1245
1258
|
|
|
1246
1259
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
1247
1260
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -1261,15 +1274,15 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1261
1274
|
// Pretend Splash was used as a stand-in when no move has been used to fail below with 0 BP
|
|
1262
1275
|
const foe_last_selected_move =
|
|
1263
1276
|
Move.get(if (foe.last_selected_move == .None or foe.last_selected_move == .SKIP_TURN)
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1277
|
+
.Splash
|
|
1278
|
+
else
|
|
1279
|
+
foe.last_selected_move);
|
|
1267
1280
|
|
|
1268
1281
|
const used = battle.lastMove(player.foe()).counterable != 0;
|
|
1269
1282
|
const selected = foe_last_selected_move.bp > 0 and
|
|
1270
1283
|
foe.last_selected_move != .Counter and
|
|
1271
1284
|
(foe_last_selected_move.type == .Normal or
|
|
1272
|
-
|
|
1285
|
+
foe_last_selected_move.type == .Fighting);
|
|
1273
1286
|
|
|
1274
1287
|
if (!used and !selected) {
|
|
1275
1288
|
try options.log.fail(.{ battle.active(player), .None });
|
|
@@ -1278,12 +1291,16 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1278
1291
|
|
|
1279
1292
|
if (!used or !selected) {
|
|
1280
1293
|
// GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
|
|
1281
|
-
if (!
|
|
1294
|
+
if (!pkmn.options.mod) return .Error;
|
|
1282
1295
|
try options.log.fail(.{ battle.active(player), .None });
|
|
1283
1296
|
return null;
|
|
1284
1297
|
}
|
|
1285
1298
|
|
|
1286
|
-
|
|
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;
|
|
1287
1304
|
|
|
1288
1305
|
// Pokémon Showdown calls moveHit before Counter
|
|
1289
1306
|
if (!showdown and !try checkHit(battle, player, move, options)) return null;
|
|
@@ -1429,7 +1446,16 @@ fn moveHit(
|
|
|
1429
1446
|
break :miss true;
|
|
1430
1447
|
}
|
|
1431
1448
|
if (!showdown) {
|
|
1432
|
-
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
|
+
}
|
|
1433
1459
|
if (foe.active.volatiles.Invulnerable) break :miss true;
|
|
1434
1460
|
}
|
|
1435
1461
|
|
|
@@ -1502,13 +1528,13 @@ fn checkFaint(
|
|
|
1502
1528
|
|
|
1503
1529
|
if (player_out and foe_out) {
|
|
1504
1530
|
try options.log.tie(.{});
|
|
1505
|
-
return
|
|
1531
|
+
return .Tie;
|
|
1506
1532
|
} else if (player_out) {
|
|
1507
1533
|
try options.log.win(.{player.foe()});
|
|
1508
|
-
return if (player == .P1)
|
|
1534
|
+
return if (player == .P1) .Lose else .Win;
|
|
1509
1535
|
} else if (foe_out) {
|
|
1510
1536
|
try options.log.win(.{player});
|
|
1511
|
-
return if (player == .P1)
|
|
1537
|
+
return if (player == .P1) .Win else .Lose;
|
|
1512
1538
|
}
|
|
1513
1539
|
|
|
1514
1540
|
const foe_choice: Choice.Type = if (foe_fainted) .Switch else .Pass;
|
|
@@ -1525,8 +1551,8 @@ fn faint(battle: anytype, player: Player, done: bool, cap: bool, options: anytyp
|
|
|
1525
1551
|
assert(!foe_volatiles.MultiHit);
|
|
1526
1552
|
if (foe_volatiles.Bide) {
|
|
1527
1553
|
assert(!foe_volatiles.Thrashing and !foe_volatiles.Rage);
|
|
1528
|
-
foe_volatiles.state = if (
|
|
1529
|
-
if (foe_volatiles.state != 0) return
|
|
1554
|
+
foe_volatiles.state = if (pkmn.options.mod) 0 else foe_volatiles.state & 255;
|
|
1555
|
+
if (foe_volatiles.state != 0) return .Error;
|
|
1530
1556
|
}
|
|
1531
1557
|
|
|
1532
1558
|
// Clearing these is not strictly necessary as provided the battle hasn't ended the side that
|
|
@@ -1616,21 +1642,21 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1616
1642
|
|
|
1617
1643
|
battle.turn += 1;
|
|
1618
1644
|
|
|
1619
|
-
if (
|
|
1645
|
+
if (pkmn.options.mod and pkmn.options.ebc and checkEBC(battle)) {
|
|
1620
1646
|
try options.log.tie(.{});
|
|
1621
|
-
return
|
|
1647
|
+
return .Tie;
|
|
1622
1648
|
}
|
|
1623
1649
|
|
|
1624
|
-
if (
|
|
1650
|
+
if (pkmn.options.mod and battle.turn >= 1000) {
|
|
1625
1651
|
try options.log.tie(.{});
|
|
1626
|
-
return
|
|
1652
|
+
return .Tie;
|
|
1627
1653
|
} else if (battle.turn >= 65535) {
|
|
1628
|
-
return
|
|
1654
|
+
return .Error;
|
|
1629
1655
|
}
|
|
1630
1656
|
|
|
1631
1657
|
try options.log.turn(.{battle.turn});
|
|
1632
1658
|
|
|
1633
|
-
return
|
|
1659
|
+
return .Default;
|
|
1634
1660
|
}
|
|
1635
1661
|
|
|
1636
1662
|
fn checkEBC(battle: anytype) bool {
|
|
@@ -1916,13 +1942,12 @@ pub const Effects = struct {
|
|
|
1916
1942
|
|
|
1917
1943
|
// Technically this is still considered simply a "miss" on the cartridge,
|
|
1918
1944
|
// but diverging from Pokémon Showdown here would mostly just be pedantic
|
|
1919
|
-
if (n == 0) {
|
|
1945
|
+
if (n == 0 or (pkmn.options.mod and err)) {
|
|
1920
1946
|
try options.log.fail(.{ foe_ident, .None });
|
|
1921
1947
|
return null;
|
|
1922
1948
|
} else if (err) {
|
|
1923
1949
|
// GLITCH: Transform + Mirror Move / Metronome PP softlock
|
|
1924
|
-
|
|
1925
|
-
return Result.Error;
|
|
1950
|
+
return .Error;
|
|
1926
1951
|
}
|
|
1927
1952
|
|
|
1928
1953
|
volatiles.disable_move =
|
|
@@ -1999,7 +2024,7 @@ pub const Effects = struct {
|
|
|
1999
2024
|
if (foe.active.types.includes(move.type)) return;
|
|
2000
2025
|
if (!try Rolls.secondaryChance(battle, player, true, options)) return;
|
|
2001
2026
|
// Freeze Clause Mod
|
|
2002
|
-
if (
|
|
2027
|
+
if (pkmn.options.mod) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
|
|
2003
2028
|
|
|
2004
2029
|
foe_stored.status = Status.init(.FRZ);
|
|
2005
2030
|
// GLITCH: Hyper Beam recharging status is not cleared
|
|
@@ -2340,7 +2365,7 @@ pub const Effects = struct {
|
|
|
2340
2365
|
}
|
|
2341
2366
|
|
|
2342
2367
|
// Sleep Clause Mod
|
|
2343
|
-
if (
|
|
2368
|
+
if (pkmn.options.mod) {
|
|
2344
2369
|
for (foe.pokemon) |p| {
|
|
2345
2370
|
if (Status.is(p.status, .SLP) and !Status.is(p.status, .EXT)) return;
|
|
2346
2371
|
}
|
|
@@ -2446,7 +2471,7 @@ pub const Effects = struct {
|
|
|
2446
2471
|
if (side.active.volatiles.Binding) return;
|
|
2447
2472
|
side.active.volatiles.Binding = true;
|
|
2448
2473
|
// GLITCH: Hyper Beam automatic selection glitch if Recharging gets cleared on miss
|
|
2449
|
-
// (Pokémon Showdown
|
|
2474
|
+
// (Pokémon Showdown clears Recharging in onTryMove instead)
|
|
2450
2475
|
if (!showdown) {
|
|
2451
2476
|
foe.active.volatiles.Recharging = false;
|
|
2452
2477
|
} else if (foe.stored().hp == 0) return if (!rewrap) battle.rng.advance(1);
|
|
@@ -3120,11 +3145,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3120
3145
|
},
|
|
3121
3146
|
.Switch => {
|
|
3122
3147
|
const side = battle.side(player);
|
|
3123
|
-
var slot:
|
|
3148
|
+
var slot: usize = 2;
|
|
3124
3149
|
while (slot <= 6) : (slot += 1) {
|
|
3125
3150
|
const id = side.order[slot - 1];
|
|
3126
3151
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
3127
|
-
out[n] = .{ .type = .Switch, .data = slot };
|
|
3152
|
+
out[n] = .{ .type = .Switch, .data = @intCast(slot) };
|
|
3128
3153
|
n += 1;
|
|
3129
3154
|
}
|
|
3130
3155
|
if (n == 0) {
|
|
@@ -3155,11 +3180,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3155
3180
|
return n;
|
|
3156
3181
|
}
|
|
3157
3182
|
|
|
3158
|
-
var slot:
|
|
3183
|
+
var slot: usize = 2;
|
|
3159
3184
|
while (slot <= 6) : (slot += 1) {
|
|
3160
3185
|
const id = side.order[slot - 1];
|
|
3161
3186
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
3162
|
-
out[n] = .{ .type = .Switch, .data = slot };
|
|
3187
|
+
out[n] = .{ .type = .Switch, .data = @intCast(slot) };
|
|
3163
3188
|
n += 1;
|
|
3164
3189
|
}
|
|
3165
3190
|
|
|
@@ -3192,7 +3217,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3192
3217
|
const struggle =
|
|
3193
3218
|
m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
|
|
3194
3219
|
const s = if (struggle) 0 else slot;
|
|
3195
|
-
out[n] = .{ .type = .Move, .data = s };
|
|
3220
|
+
out[n] = .{ .type = .Move, .data = @intCast(s) };
|
|
3196
3221
|
n += 1;
|
|
3197
3222
|
return n;
|
|
3198
3223
|
}
|
|
@@ -3206,13 +3231,13 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3206
3231
|
if (m.id == .None) break;
|
|
3207
3232
|
if (m.pp == 0) continue;
|
|
3208
3233
|
if (active.volatiles.disable_move == slot) continue;
|
|
3209
|
-
out[n] = .{ .type = .Move, .data = slot };
|
|
3234
|
+
out[n] = .{ .type = .Move, .data = @intCast(slot) };
|
|
3210
3235
|
n += 1;
|
|
3211
3236
|
}
|
|
3212
3237
|
// Struggle (Pokémon Showdown would use 'move 1' here)
|
|
3213
3238
|
if (n == before) {
|
|
3214
3239
|
// GLITCH: Transform + Mirror Move / Metronome PP softlock
|
|
3215
|
-
if (!
|
|
3240
|
+
if (!pkmn.options.mod) {
|
|
3216
3241
|
while (slot <= 4) : (slot += 1) {
|
|
3217
3242
|
if (active.moves[slot - 1].pp != 0) return n;
|
|
3218
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
|
}
|
package/src/lib/gen2/chance.zig
CHANGED
|
@@ -27,10 +27,6 @@ const Type = data.Type;
|
|
|
27
27
|
const Effectiveness = data.Effectiveness;
|
|
28
28
|
const TriAttack = data.TriAttack;
|
|
29
29
|
|
|
30
|
-
const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
|
|
31
|
-
const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
|
|
32
|
-
const Struct = if (@hasField(std.builtin.Type, "struct")) .@"struct" else .Struct;
|
|
33
|
-
|
|
34
30
|
/// Actions taken by a hypothetical "chance player" that convey information about which RNG events
|
|
35
31
|
/// were observed during a Generation II battle `update`. This can additionally be provided as input
|
|
36
32
|
/// to the `update` call to override the normal behavior of the RNG in order to force specific
|
|
@@ -57,15 +53,15 @@ pub const Actions = extern struct {
|
|
|
57
53
|
/// same shape if they have the same fields set (though those fields need not necessarily be
|
|
58
54
|
/// set to the same value).
|
|
59
55
|
pub fn matches(a: Actions, b: Actions) bool {
|
|
60
|
-
inline for (@
|
|
61
|
-
inline for (@
|
|
56
|
+
inline for (@typeInfo(Actions).@"struct".fields) |player| {
|
|
57
|
+
inline for (@typeInfo(Action).@"struct".fields) |field| {
|
|
62
58
|
const a_val = @field(@field(a, player.name), field.name);
|
|
63
59
|
const b_val = @field(@field(b, player.name), field.name);
|
|
64
60
|
|
|
65
61
|
switch (@typeInfo(@TypeOf(a_val))) {
|
|
66
|
-
|
|
62
|
+
.@"enum" => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
|
|
67
63
|
return false,
|
|
68
|
-
|
|
64
|
+
.int => if ((a_val > 0) != (b_val > 0)) return false,
|
|
69
65
|
else => unreachable,
|
|
70
66
|
}
|
|
71
67
|
}
|
|
@@ -73,7 +69,7 @@ pub const Actions = extern struct {
|
|
|
73
69
|
return true;
|
|
74
70
|
}
|
|
75
71
|
|
|
76
|
-
pub fn fmt(self: Actions, writer:
|
|
72
|
+
pub fn fmt(self: Actions, writer: *std.Io.Writer, shape: bool) !void {
|
|
77
73
|
try writer.writeAll("<P1 = ");
|
|
78
74
|
try self.p1.fmt(writer, shape);
|
|
79
75
|
try writer.writeAll(", P2 = ");
|
|
@@ -81,8 +77,7 @@ pub const Actions = extern struct {
|
|
|
81
77
|
try writer.writeAll(">");
|
|
82
78
|
}
|
|
83
79
|
|
|
84
|
-
pub fn format(a: Actions,
|
|
85
|
-
_ = .{ f, o };
|
|
80
|
+
pub fn format(a: Actions, w: *std.Io.Writer) !void {
|
|
86
81
|
try fmt(a, w, false);
|
|
87
82
|
}
|
|
88
83
|
};
|
|
@@ -186,18 +181,17 @@ pub const Action = packed struct(u128) {
|
|
|
186
181
|
|
|
187
182
|
pub const Field = std.meta.FieldEnum(Action);
|
|
188
183
|
|
|
189
|
-
pub fn format(a: Action,
|
|
190
|
-
_ = .{ f, o };
|
|
184
|
+
pub fn format(a: Action, w: *std.Io.Writer) !void {
|
|
191
185
|
try fmt(a, w, false);
|
|
192
186
|
}
|
|
193
187
|
|
|
194
|
-
pub fn fmt(self: Action, writer:
|
|
188
|
+
pub fn fmt(self: Action, writer: *std.Io.Writer, shape: bool) !void {
|
|
195
189
|
try writer.writeByte('(');
|
|
196
190
|
var printed = false;
|
|
197
|
-
inline for (@
|
|
191
|
+
inline for (@typeInfo(Action).@"struct".fields) |field| {
|
|
198
192
|
const val = @field(self, field.name);
|
|
199
193
|
switch (@typeInfo(@TypeOf(val))) {
|
|
200
|
-
|
|
194
|
+
.@"enum" => if (val != .None) {
|
|
201
195
|
if (printed) try writer.writeAll(", ");
|
|
202
196
|
if (shape) {
|
|
203
197
|
try writer.print("{s}:?", .{field.name});
|
|
@@ -211,7 +205,7 @@ pub const Action = packed struct(u128) {
|
|
|
211
205
|
}
|
|
212
206
|
printed = true;
|
|
213
207
|
},
|
|
214
|
-
|
|
208
|
+
.int => if (val != 0) {
|
|
215
209
|
if (printed) try writer.writeAll(", ");
|
|
216
210
|
if (shape) {
|
|
217
211
|
try writer.print("{s}:?", .{field.name});
|
|
@@ -786,7 +780,7 @@ pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
|
|
|
786
780
|
|
|
787
781
|
r.reduce();
|
|
788
782
|
if (r.p != p or r.q != q) {
|
|
789
|
-
print("expected {d}/{d}, found {}\n", .{ p, q, r });
|
|
783
|
+
print("expected {d}/{d}, found {f}\n", .{ p, q, r });
|
|
790
784
|
return error.TestExpectedEqual;
|
|
791
785
|
}
|
|
792
786
|
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
//! Code generated by `tools/generate` - manual edits will be overwritten.
|
|
2
2
|
|
|
3
|
-
const std = @import("std");
|
|
4
|
-
|
|
5
3
|
const data = @import("../../gen2/data.zig");
|
|
4
|
+
const std = @import("std");
|
|
6
5
|
|
|
7
6
|
const assert = std.debug.assert;
|
|
8
|
-
|
|
9
7
|
const Type = data.Type;
|
|
10
8
|
|
|
11
9
|
/// Representation of a held item in Generation II Pokémon.
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
//! Code generated by `tools/generate` - manual edits will be overwritten.
|
|
2
2
|
|
|
3
|
-
const std = @import("std");
|
|
4
|
-
|
|
5
|
-
const rng = @import("../../common/rng.zig");
|
|
6
3
|
const gen2 = @import("../../gen2/data.zig");
|
|
4
|
+
const rng = @import("../../common/rng.zig");
|
|
5
|
+
const std = @import("std");
|
|
7
6
|
|
|
8
7
|
const assert = std.debug.assert;
|
|
9
|
-
|
|
10
8
|
const percent = rng.Gen12.percent;
|
|
11
9
|
const Type = gen2.Type;
|
|
12
10
|
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
//! Code generated by `tools/generate` - manual edits will be overwritten.
|
|
2
2
|
|
|
3
|
-
const std = @import("std");
|
|
4
|
-
|
|
5
3
|
const gen2 = @import("../../gen2/data.zig");
|
|
4
|
+
const std = @import("std");
|
|
6
5
|
|
|
7
6
|
const assert = std.debug.assert;
|
|
8
|
-
|
|
9
7
|
const Stats = gen2.Stats;
|
|
10
8
|
const Types = gen2.Types;
|
|
11
9
|
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
//! Code generated by `tools/generate` - manual edits will be overwritten.
|
|
2
2
|
|
|
3
|
-
const std = @import("std");
|
|
4
|
-
|
|
5
3
|
const gen1 = @import("../../gen1/data.zig");
|
|
4
|
+
const std = @import("std");
|
|
6
5
|
|
|
7
6
|
const assert = std.debug.assert;
|
|
8
7
|
const Effectiveness = gen1.Effectiveness;
|
|
@@ -173,9 +172,12 @@ pub const Type = enum(u8) {
|
|
|
173
172
|
return CHART[@intFromEnum(t1)][@intFromEnum(t2)];
|
|
174
173
|
}
|
|
175
174
|
|
|
176
|
-
/// The precedence order of Type `
|
|
177
|
-
pub fn precedence(
|
|
178
|
-
return
|
|
175
|
+
/// The precedence order of Type `t1` vs. Type `t2`.
|
|
176
|
+
pub fn precedence(t1: Type, t2: Type) u8 {
|
|
177
|
+
// The Ice vs. Fire matchup is out of order - return a higher
|
|
178
|
+
// number than anything else so that it sorts last correctly
|
|
179
|
+
if (t1 == .Ice and t2 == .Fire) return 18;
|
|
180
|
+
return PRECEDENCE[@intFromEnum(t2)];
|
|
179
181
|
}
|
|
180
182
|
|
|
181
183
|
/// The internal index of this Type used by Present.
|