@pkmn/engine 0.1.0-dev.36b5d56b → 0.1.0-dev.3846c8e6
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 +28 -13
- package/build/pkg/addon/browser.js +4 -1
- package/build/pkg/addon/browser.js.map +1 -1
- package/build/pkg/addon/node.js +20 -7
- package/build/pkg/addon/node.js.map +1 -1
- package/build/pkg/addon/wasm.js +41 -9
- package/build/pkg/addon/wasm.js.map +1 -1
- package/build/pkg/addon.d.ts +7 -7
- 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/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 +54 -11
- 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 +18 -7
- 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 +200 -201
- package/package.json +31 -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/protocol.zig +224 -189
- package/src/lib/common/rational.zig +275 -284
- package/src/lib/common/util.zig +14 -31
- package/src/lib/gen1/calc.zig +139 -117
- package/src/lib/gen1/chance.zig +112 -75
- package/src/lib/gen1/data/types.zig +2 -2
- package/src/lib/gen1/data.zig +5 -5
- package/src/lib/gen1/helpers.zig +8 -10
- package/src/lib/gen1/mechanics.zig +50 -43
- package/src/lib/gen2/calc.zig +3 -6
- package/src/lib/gen2/chance.zig +12 -18
- package/src/lib/gen2/data/types.zig +6 -3
- package/src/lib/gen2/data.zig +8 -7
- package/src/lib/gen2/helpers.zig +4 -6
- package/src/lib/gen2/mechanics.zig +36 -25
- package/src/lib/napi.h +2 -0
- package/src/lib/node.zig +3 -118
- package/src/lib/pkmn.zig +94 -19
- package/src/lib/wasm.zig +10 -9
- package/src/tools/display/dom.ts +46 -34
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const DEBUG = @import("../common/debug.zig").print;
|
|
2
2
|
|
|
3
|
+
const builtin = @import("builtin");
|
|
3
4
|
const chance = @import("chance.zig");
|
|
4
5
|
const common = @import("../common/data.zig");
|
|
5
6
|
const data = @import("data.zig");
|
|
@@ -83,11 +84,11 @@ fn start(battle: anytype, options: anytype) !Result {
|
|
|
83
84
|
|
|
84
85
|
const p1_slot = findFirstAlive(p1);
|
|
85
86
|
assert(!showdown or p1_slot == 1);
|
|
86
|
-
if (p1_slot == 0) return if (findFirstAlive(p2) == 0)
|
|
87
|
+
if (p1_slot == 0) return if (findFirstAlive(p2) == 0) .Tie else .Lose;
|
|
87
88
|
|
|
88
89
|
const p2_slot = findFirstAlive(p2);
|
|
89
90
|
assert(!showdown or p2_slot == 1);
|
|
90
|
-
if (p2_slot == 0) return
|
|
91
|
+
if (p2_slot == 0) return .Win;
|
|
91
92
|
|
|
92
93
|
try switchIn(battle, .P1, p1_slot, true, options);
|
|
93
94
|
try switchIn(battle, .P2, p2_slot, true, options);
|
|
@@ -166,7 +167,7 @@ fn selectMove(
|
|
|
166
167
|
const last = side.active.move(battle.lastMove(player).index);
|
|
167
168
|
if (last.id == .Metronome) side.last_selected_move = last.id;
|
|
168
169
|
if (last.id == .MirrorMove) {
|
|
169
|
-
if (!pkmn.options.mod) return
|
|
170
|
+
if (!pkmn.options.mod) return .Error;
|
|
170
171
|
side.last_selected_move = last.id;
|
|
171
172
|
}
|
|
172
173
|
}
|
|
@@ -431,7 +432,7 @@ fn executeMove(
|
|
|
431
432
|
// technically be from this Pokémon because it must be exactly 1 to not desync and
|
|
432
433
|
// every Pokémon must have at least one move
|
|
433
434
|
if (mslot != 1 or side.active.move(mslot).id != side.last_selected_move) {
|
|
434
|
-
return
|
|
435
|
+
return .Error;
|
|
435
436
|
} else {
|
|
436
437
|
auto = true;
|
|
437
438
|
}
|
|
@@ -449,14 +450,14 @@ fn executeMove(
|
|
|
449
450
|
.skip_can => skip_can = true,
|
|
450
451
|
.skip_pp => skip_pp = true,
|
|
451
452
|
.ok => {},
|
|
452
|
-
.err => return
|
|
453
|
+
.err => return .Error,
|
|
453
454
|
}
|
|
454
455
|
|
|
455
456
|
// Pokémon Showdown incorrectly implements PP deduction when handling the Hyper Beam automatic
|
|
456
457
|
// selection glitch - if the move was proced due to Metronome / Mirror Move it either subtracts
|
|
457
458
|
// from the Hyper Beam slot if present or skips PP deduction entirely...
|
|
458
459
|
if (showdown and !skip_pp and side.last_selected_move == .HyperBeam and
|
|
459
|
-
side.last_selected_move != side.active.move(
|
|
460
|
+
side.last_selected_move != side.active.move(mslot).id)
|
|
460
461
|
{
|
|
461
462
|
assert(mslot == 1);
|
|
462
463
|
const has_beam = has_beam: {
|
|
@@ -617,17 +618,10 @@ fn beforeMove(
|
|
|
617
618
|
return .err;
|
|
618
619
|
}
|
|
619
620
|
}
|
|
620
|
-
// Pokémon Showdown incorrectly changes the "target" of the confusion self-hit based
|
|
621
|
-
// on the targeting behavior of the confused Pokémon's selected move which results
|
|
622
|
-
// in the wrong behavior with respect to the Substitute + Confusion glitch
|
|
623
|
-
const target = if (showdown and Move.get(side.last_selected_move).target == .Self)
|
|
624
|
-
player
|
|
625
|
-
else
|
|
626
|
-
player.foe();
|
|
627
621
|
|
|
628
622
|
const uncapped = battle.last_damage;
|
|
629
623
|
// Skipping adjustDamage / randomizeDamage / checkHit
|
|
630
|
-
_ = try applyDamage(battle, player,
|
|
624
|
+
_ = try applyDamage(battle, player, player.foe(), .Confusion, options);
|
|
631
625
|
// Pokémon Showdown thinks that confusion damage is uncapped ¯\_(ツ)_/¯
|
|
632
626
|
if (showdown) battle.last_damage = uncapped;
|
|
633
627
|
|
|
@@ -884,7 +878,7 @@ fn doMove(
|
|
|
884
878
|
var miss = showdown and miss: {
|
|
885
879
|
immune = move.target != .Self and !status and !counter and
|
|
886
880
|
(@intFromEnum(move.type.effectiveness(foe.active.types.type1)) == 0 or
|
|
887
|
-
|
|
881
|
+
@intFromEnum(move.type.effectiveness(foe.active.types.type2)) == 0);
|
|
888
882
|
if (immune and move.effect != .Binding) break :miss true;
|
|
889
883
|
if (move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe) {
|
|
890
884
|
battle.last_damage = 0;
|
|
@@ -922,7 +916,7 @@ fn doMove(
|
|
|
922
916
|
if (showdown) break :damage; // skip adjustDamage / randomizeDamage
|
|
923
917
|
} else if (!calcDamage(battle, player, player.foe(), move, crit, options)) {
|
|
924
918
|
if (!showdown) try options.chance.commit(player, .err);
|
|
925
|
-
return
|
|
919
|
+
return .Error;
|
|
926
920
|
}
|
|
927
921
|
if (battle.last_damage == 0) {
|
|
928
922
|
immune = true;
|
|
@@ -1025,7 +1019,7 @@ fn doMove(
|
|
|
1025
1019
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
1026
1020
|
|
|
1027
1021
|
var nullified = false;
|
|
1028
|
-
var hit:
|
|
1022
|
+
var hit: usize = 0;
|
|
1029
1023
|
while (hit < hits) {
|
|
1030
1024
|
if (hit == 0) {
|
|
1031
1025
|
if (crit) try log.crit(.{battle.active(player.foe())});
|
|
@@ -1053,7 +1047,7 @@ fn doMove(
|
|
|
1053
1047
|
if (showdown and move.effect == .Twineedle and !nullified and foe.stored().hp > 0) {
|
|
1054
1048
|
try Effects.poison(battle, player, Move.get(.PoisonSting), options);
|
|
1055
1049
|
}
|
|
1056
|
-
try log.hitcount(.{ battle.active(player.foe()), hit });
|
|
1050
|
+
try log.hitcount(.{ battle.active(player.foe()), @as(u4, @intCast(hit)) });
|
|
1057
1051
|
} else if (showdown and move.effect == .Binding) {
|
|
1058
1052
|
// This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
|
|
1059
1053
|
try Effects.binding(battle, player, rewrap, options);
|
|
@@ -1169,7 +1163,7 @@ fn calcDamage(
|
|
|
1169
1163
|
// encountered so we need to make sure that in the event of a miss we commit the status of the
|
|
1170
1164
|
// crit roll. The alternative to doing this extra work is to *always* commit the crit roll but
|
|
1171
1165
|
// that results in counter intuitive probabilities in the common case
|
|
1172
|
-
if (pkmn.options.chance and !pkmn.options.mod) {
|
|
1166
|
+
if (pkmn.options.chance and !pkmn.options.mod and move.effect != .Explode) {
|
|
1173
1167
|
atk = attack(side, !crit, special);
|
|
1174
1168
|
def = defense(target, !crit, special, false);
|
|
1175
1169
|
if ((atk > 255 or def > 255) and (def / 4) & 255 == 0) options.chance.glitch();
|
|
@@ -1254,13 +1248,13 @@ fn specialDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1254
1248
|
.Psywave => power: {
|
|
1255
1249
|
const max: u8 = @intCast(@as(u16, side.stored().level) * 3 / 2);
|
|
1256
1250
|
// GLITCH: Psywave infinite glitch loop
|
|
1257
|
-
if (!pkmn.options.mod and max <= 1) return
|
|
1251
|
+
if (!pkmn.options.mod and max <= 1) return .Error;
|
|
1258
1252
|
break :power try Rolls.psywave(battle, player, max, options);
|
|
1259
1253
|
},
|
|
1260
1254
|
else => unreachable,
|
|
1261
1255
|
};
|
|
1262
1256
|
|
|
1263
|
-
if (battle.last_damage == 0) return if (pkmn.options.mod) null else
|
|
1257
|
+
if (battle.last_damage == 0) return if (pkmn.options.mod) null else .Error;
|
|
1264
1258
|
|
|
1265
1259
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
1266
1260
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -1280,15 +1274,15 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1280
1274
|
// Pretend Splash was used as a stand-in when no move has been used to fail below with 0 BP
|
|
1281
1275
|
const foe_last_selected_move =
|
|
1282
1276
|
Move.get(if (foe.last_selected_move == .None or foe.last_selected_move == .SKIP_TURN)
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1277
|
+
.Splash
|
|
1278
|
+
else
|
|
1279
|
+
foe.last_selected_move);
|
|
1286
1280
|
|
|
1287
1281
|
const used = battle.lastMove(player.foe()).counterable != 0;
|
|
1288
1282
|
const selected = foe_last_selected_move.bp > 0 and
|
|
1289
1283
|
foe.last_selected_move != .Counter and
|
|
1290
1284
|
(foe_last_selected_move.type == .Normal or
|
|
1291
|
-
|
|
1285
|
+
foe_last_selected_move.type == .Fighting);
|
|
1292
1286
|
|
|
1293
1287
|
if (!used and !selected) {
|
|
1294
1288
|
try options.log.fail(.{ battle.active(player), .None });
|
|
@@ -1297,12 +1291,16 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
1297
1291
|
|
|
1298
1292
|
if (!used or !selected) {
|
|
1299
1293
|
// GLITCH: Counter desync (covered by Desync Clause Mod on Pokémon Showdown)
|
|
1300
|
-
if (!pkmn.options.mod) return
|
|
1294
|
+
if (!pkmn.options.mod) return .Error;
|
|
1301
1295
|
try options.log.fail(.{ battle.active(player), .None });
|
|
1302
1296
|
return null;
|
|
1303
1297
|
}
|
|
1304
1298
|
|
|
1305
|
-
|
|
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;
|
|
1306
1304
|
|
|
1307
1305
|
// Pokémon Showdown calls moveHit before Counter
|
|
1308
1306
|
if (!showdown and !try checkHit(battle, player, move, options)) return null;
|
|
@@ -1448,7 +1446,16 @@ fn moveHit(
|
|
|
1448
1446
|
break :miss true;
|
|
1449
1447
|
}
|
|
1450
1448
|
if (!showdown) {
|
|
1451
|
-
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
|
+
}
|
|
1452
1459
|
if (foe.active.volatiles.Invulnerable) break :miss true;
|
|
1453
1460
|
}
|
|
1454
1461
|
|
|
@@ -1521,13 +1528,13 @@ fn checkFaint(
|
|
|
1521
1528
|
|
|
1522
1529
|
if (player_out and foe_out) {
|
|
1523
1530
|
try options.log.tie(.{});
|
|
1524
|
-
return
|
|
1531
|
+
return .Tie;
|
|
1525
1532
|
} else if (player_out) {
|
|
1526
1533
|
try options.log.win(.{player.foe()});
|
|
1527
|
-
return if (player == .P1)
|
|
1534
|
+
return if (player == .P1) .Lose else .Win;
|
|
1528
1535
|
} else if (foe_out) {
|
|
1529
1536
|
try options.log.win(.{player});
|
|
1530
|
-
return if (player == .P1)
|
|
1537
|
+
return if (player == .P1) .Win else .Lose;
|
|
1531
1538
|
}
|
|
1532
1539
|
|
|
1533
1540
|
const foe_choice: Choice.Type = if (foe_fainted) .Switch else .Pass;
|
|
@@ -1545,7 +1552,7 @@ fn faint(battle: anytype, player: Player, done: bool, cap: bool, options: anytyp
|
|
|
1545
1552
|
if (foe_volatiles.Bide) {
|
|
1546
1553
|
assert(!foe_volatiles.Thrashing and !foe_volatiles.Rage);
|
|
1547
1554
|
foe_volatiles.state = if (pkmn.options.mod) 0 else foe_volatiles.state & 255;
|
|
1548
|
-
if (foe_volatiles.state != 0) return
|
|
1555
|
+
if (foe_volatiles.state != 0) return .Error;
|
|
1549
1556
|
}
|
|
1550
1557
|
|
|
1551
1558
|
// Clearing these is not strictly necessary as provided the battle hasn't ended the side that
|
|
@@ -1637,19 +1644,19 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1637
1644
|
|
|
1638
1645
|
if (pkmn.options.mod and pkmn.options.ebc and checkEBC(battle)) {
|
|
1639
1646
|
try options.log.tie(.{});
|
|
1640
|
-
return
|
|
1647
|
+
return .Tie;
|
|
1641
1648
|
}
|
|
1642
1649
|
|
|
1643
1650
|
if (pkmn.options.mod and battle.turn >= 1000) {
|
|
1644
1651
|
try options.log.tie(.{});
|
|
1645
|
-
return
|
|
1652
|
+
return .Tie;
|
|
1646
1653
|
} else if (battle.turn >= 65535) {
|
|
1647
|
-
return
|
|
1654
|
+
return .Error;
|
|
1648
1655
|
}
|
|
1649
1656
|
|
|
1650
1657
|
try options.log.turn(.{battle.turn});
|
|
1651
1658
|
|
|
1652
|
-
return
|
|
1659
|
+
return .Default;
|
|
1653
1660
|
}
|
|
1654
1661
|
|
|
1655
1662
|
fn checkEBC(battle: anytype) bool {
|
|
@@ -1940,7 +1947,7 @@ pub const Effects = struct {
|
|
|
1940
1947
|
return null;
|
|
1941
1948
|
} else if (err) {
|
|
1942
1949
|
// GLITCH: Transform + Mirror Move / Metronome PP softlock
|
|
1943
|
-
return
|
|
1950
|
+
return .Error;
|
|
1944
1951
|
}
|
|
1945
1952
|
|
|
1946
1953
|
volatiles.disable_move =
|
|
@@ -3138,11 +3145,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3138
3145
|
},
|
|
3139
3146
|
.Switch => {
|
|
3140
3147
|
const side = battle.side(player);
|
|
3141
|
-
var slot:
|
|
3148
|
+
var slot: usize = 2;
|
|
3142
3149
|
while (slot <= 6) : (slot += 1) {
|
|
3143
3150
|
const id = side.order[slot - 1];
|
|
3144
3151
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
3145
|
-
out[n] = .{ .type = .Switch, .data = slot };
|
|
3152
|
+
out[n] = .{ .type = .Switch, .data = @intCast(slot) };
|
|
3146
3153
|
n += 1;
|
|
3147
3154
|
}
|
|
3148
3155
|
if (n == 0) {
|
|
@@ -3173,11 +3180,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3173
3180
|
return n;
|
|
3174
3181
|
}
|
|
3175
3182
|
|
|
3176
|
-
var slot:
|
|
3183
|
+
var slot: usize = 2;
|
|
3177
3184
|
while (slot <= 6) : (slot += 1) {
|
|
3178
3185
|
const id = side.order[slot - 1];
|
|
3179
3186
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
3180
|
-
out[n] = .{ .type = .Switch, .data = slot };
|
|
3187
|
+
out[n] = .{ .type = .Switch, .data = @intCast(slot) };
|
|
3181
3188
|
n += 1;
|
|
3182
3189
|
}
|
|
3183
3190
|
|
|
@@ -3210,7 +3217,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3210
3217
|
const struggle =
|
|
3211
3218
|
m.id == .Bide and (m.pp == 0 or active.volatiles.disable_move == slot);
|
|
3212
3219
|
const s = if (struggle) 0 else slot;
|
|
3213
|
-
out[n] = .{ .type = .Move, .data = s };
|
|
3220
|
+
out[n] = .{ .type = .Move, .data = @intCast(s) };
|
|
3214
3221
|
n += 1;
|
|
3215
3222
|
return n;
|
|
3216
3223
|
}
|
|
@@ -3224,7 +3231,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3224
3231
|
if (m.id == .None) break;
|
|
3225
3232
|
if (m.pp == 0) continue;
|
|
3226
3233
|
if (active.volatiles.disable_move == slot) continue;
|
|
3227
|
-
out[n] = .{ .type = .Move, .data = slot };
|
|
3234
|
+
out[n] = .{ .type = .Move, .data = @intCast(slot) };
|
|
3228
3235
|
n += 1;
|
|
3229
3236
|
}
|
|
3230
3237
|
// Struggle (Pokémon Showdown would use 'move 1' here)
|
package/src/lib/gen2/calc.zig
CHANGED
|
@@ -15,9 +15,6 @@ const isPointerTo = util.isPointerTo;
|
|
|
15
15
|
const Player = common.Player;
|
|
16
16
|
const PointerType = util.PointerType;
|
|
17
17
|
|
|
18
|
-
const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
|
|
19
|
-
const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
|
|
20
|
-
|
|
21
18
|
/// Information relevant to damage calculation that occured during a Generation II battle `update`.
|
|
22
19
|
pub const Summaries = extern struct {
|
|
23
20
|
/// Relevant information for Player 1.
|
|
@@ -91,8 +88,8 @@ pub const Calc = struct {
|
|
|
91
88
|
};
|
|
92
89
|
|
|
93
90
|
return if (switch (@typeInfo(@TypeOf(val))) {
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
.@"enum" => val != .None,
|
|
92
|
+
.int => val != 0,
|
|
96
93
|
else => unreachable,
|
|
97
94
|
}) val else null;
|
|
98
95
|
}
|
|
@@ -148,6 +145,6 @@ fn ReturnType(comptime field: Action.Field) type {
|
|
|
148
145
|
return ?(switch (field) {
|
|
149
146
|
.damages => u8,
|
|
150
147
|
.critical_hits => Optional(bool),
|
|
151
|
-
else =>
|
|
148
|
+
else => @FieldType(Action, @tagName(field)),
|
|
152
149
|
});
|
|
153
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
|
}
|
|
@@ -172,9 +172,12 @@ pub const Type = enum(u8) {
|
|
|
172
172
|
return CHART[@intFromEnum(t1)][@intFromEnum(t2)];
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
/// The precedence order of Type `
|
|
176
|
-
pub fn precedence(
|
|
177
|
-
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)];
|
|
178
181
|
}
|
|
179
182
|
|
|
180
183
|
/// The internal index of this Type used by Present.
|
package/src/lib/gen2/data.zig
CHANGED
|
@@ -57,7 +57,7 @@ pub fn Battle(comptime RNG: anytype) type {
|
|
|
57
57
|
turn: u16 = 0,
|
|
58
58
|
field: Field = .{},
|
|
59
59
|
|
|
60
|
-
_: [padding]u8 =
|
|
60
|
+
_: [padding]u8 = @splat(0),
|
|
61
61
|
|
|
62
62
|
pub fn side(self: anytype, player: Player) PointerType(@TypeOf(self), Side) {
|
|
63
63
|
assert(isPointerTo(self, Self));
|
|
@@ -100,13 +100,13 @@ pub const Weather = enum(u4) {
|
|
|
100
100
|
};
|
|
101
101
|
|
|
102
102
|
pub const Side = extern struct {
|
|
103
|
-
pokemon: [6]Pokemon =
|
|
103
|
+
pokemon: [6]Pokemon = @splat(.{}),
|
|
104
104
|
active: ActivePokemon align(8) = .{},
|
|
105
105
|
conditions: Conditions = .{},
|
|
106
106
|
// NB: wLastPlayerCounterMove / wLastEnemyCounterMove (wLastPlayerMove / wLastEnemyMove)
|
|
107
107
|
last_used_move: Move = .None,
|
|
108
108
|
|
|
109
|
-
_: [7]u8 =
|
|
109
|
+
_: [7]u8 = @splat(0),
|
|
110
110
|
|
|
111
111
|
const Conditions = packed struct(u32) {
|
|
112
112
|
Spikes: bool = false,
|
|
@@ -161,7 +161,7 @@ pub const Side = extern struct {
|
|
|
161
161
|
pub const ActivePokemon = extern struct {
|
|
162
162
|
volatiles: Volatiles align(4) = .{},
|
|
163
163
|
stats: Stats(u16) = .{},
|
|
164
|
-
moves: [4]MoveSlot =
|
|
164
|
+
moves: [4]MoveSlot = @splat(.{}),
|
|
165
165
|
boosts: Boosts = .{},
|
|
166
166
|
types: Types = .{},
|
|
167
167
|
species: Species = .None,
|
|
@@ -182,7 +182,7 @@ pub const ActivePokemon = extern struct {
|
|
|
182
182
|
|
|
183
183
|
pub const Pokemon = extern struct {
|
|
184
184
|
stats: Stats(u16) = .{},
|
|
185
|
-
moves: [4]MoveSlot =
|
|
185
|
+
moves: [4]MoveSlot = @splat(.{}),
|
|
186
186
|
types: Types = .{},
|
|
187
187
|
dvs: DVs = .{},
|
|
188
188
|
hp: u16 = 0,
|
|
@@ -449,8 +449,9 @@ test Types {
|
|
|
449
449
|
try expect(!Type.Steel.special());
|
|
450
450
|
try expect(Type.Dark.special());
|
|
451
451
|
|
|
452
|
-
try expectEqual(@as(u8, 3), Type.Water.
|
|
453
|
-
try expect(Type.
|
|
452
|
+
try expectEqual(@as(u8, 3), Type.precedence(.Water, .Water));
|
|
453
|
+
try expect(Type.precedence(.Poison, .Bug) > Type.precedence(.Poison, .Poison));
|
|
454
|
+
try expect(Type.precedence(.Ice, .Fire) > Type.precedence(.Ice, .Water));
|
|
454
455
|
|
|
455
456
|
try expectEqual(Effectiveness.Super, Type.effectiveness(.Ghost, .Psychic));
|
|
456
457
|
try expectEqual(Effectiveness.Super, Type.effectiveness(.Water, .Fire));
|
package/src/lib/gen2/helpers.zig
CHANGED
|
@@ -16,8 +16,6 @@ const showdown = options.showdown;
|
|
|
16
16
|
const Species = data.Species;
|
|
17
17
|
const Stats = data.Stats;
|
|
18
18
|
|
|
19
|
-
const Struct = if (@hasField(std.builtin.Type, "struct")) .@"struct" else .Struct;
|
|
20
|
-
|
|
21
19
|
pub const Options = struct {
|
|
22
20
|
cleric: bool = showdown,
|
|
23
21
|
};
|
|
@@ -74,7 +72,7 @@ pub fn prng(rand: *PSRNG) data.PRNG {
|
|
|
74
72
|
var carry: u8 = 0;
|
|
75
73
|
var div: u8 = 0;
|
|
76
74
|
|
|
77
|
-
var i:
|
|
75
|
+
var i: usize = 0;
|
|
78
76
|
while (i < seed.len) {
|
|
79
77
|
div = @intCast(timer >> 8);
|
|
80
78
|
// This is effectively @addWithOverflow but with three operands
|
|
@@ -142,7 +140,7 @@ pub const Pokemon = struct {
|
|
|
142
140
|
var pokemon = data.Pokemon{};
|
|
143
141
|
pokemon.species = p.species;
|
|
144
142
|
const species = Species.get(p.species);
|
|
145
|
-
inline for (@
|
|
143
|
+
inline for (@typeInfo(@TypeOf(pokemon.stats)).@"struct".fields) |field| {
|
|
146
144
|
const hp = comptime std.mem.eql(u8, field.name, "hp");
|
|
147
145
|
const spc =
|
|
148
146
|
comptime std.mem.eql(u8, field.name, "spa") or std.mem.eql(u8, field.name, "spd");
|
|
@@ -183,7 +181,7 @@ pub const Pokemon = struct {
|
|
|
183
181
|
|
|
184
182
|
var stats: Stats(u16) = .{};
|
|
185
183
|
const dvs = gen1.DVs.random(rand);
|
|
186
|
-
inline for (@
|
|
184
|
+
inline for (@typeInfo(@TypeOf(stats)).@"struct".fields) |field| {
|
|
187
185
|
const hp = std.mem.eql(u8, field.name, "hp");
|
|
188
186
|
const spc = std.mem.eql(u8, field.name, "spa") or std.mem.eql(u8, field.name, "spd");
|
|
189
187
|
@field(stats, field.name) = Stats(u16).calc(
|
|
@@ -195,7 +193,7 @@ pub const Pokemon = struct {
|
|
|
195
193
|
);
|
|
196
194
|
}
|
|
197
195
|
|
|
198
|
-
var ms
|
|
196
|
+
var ms: [4]MoveSlot = @splat(.{});
|
|
199
197
|
const n = if (rand.chance(u8, 1, 100)) rand.range(u4, 1, 3 + 1) else 4;
|
|
200
198
|
for (0..n) |i| {
|
|
201
199
|
var m: Move = .None;
|