@pkmn/engine 0.1.0-dev.565de616 → 0.1.0-dev.587596a6
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 +21 -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/gen1.d.ts +5 -5
- package/build/pkg/gen1.js +28 -19
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/index.d.ts +6 -97
- package/build/pkg/index.js +23 -75
- package/build/pkg/index.js.map +1 -1
- package/build/tools/debug.js +36 -9
- package/build/tools/debug.js.map +1 -1
- package/build/tools/display/debug.jsx +18 -7
- 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 +18 -8
- package/build/tools/display/ui.jsx.map +1 -1
- package/build/tools/display/util.js +16 -1
- package/build/tools/display/util.js.map +1 -1
- package/build.zig +181 -198
- package/package.json +31 -130
- package/src/bin/install-pkmn-engine +24 -8
- 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 +11 -21
- package/src/lib/common/js.zig +3 -9
- package/src/lib/common/optional.zig +11 -21
- package/src/lib/common/protocol.zig +176 -163
- package/src/lib/common/rational.zig +275 -284
- package/src/lib/common/util.zig +14 -31
- package/src/lib/gen1/calc.zig +65 -62
- package/src/lib/gen1/chance.zig +17 -32
- 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 +32 -25
- 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 +28 -17
- 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 +5 -8
package/src/lib/gen1/helpers.zig
CHANGED
|
@@ -19,8 +19,6 @@ const Stats = data.Stats;
|
|
|
19
19
|
|
|
20
20
|
const endian = builtin.cpu.arch.endian();
|
|
21
21
|
|
|
22
|
-
const Struct = if (@hasField(std.builtin.Type, "struct")) .@"struct" else .Struct;
|
|
23
|
-
|
|
24
22
|
/// Configuration options controlling the domain of valid Pokémon that can be generated by the
|
|
25
23
|
/// random generator helpers.
|
|
26
24
|
pub const Options = struct {
|
|
@@ -163,7 +161,7 @@ pub const Pokemon = struct {
|
|
|
163
161
|
var pokemon = data.Pokemon{};
|
|
164
162
|
pokemon.species = p.species;
|
|
165
163
|
const species = Species.get(p.species);
|
|
166
|
-
inline for (@
|
|
164
|
+
inline for (@typeInfo(@TypeOf(pokemon.stats)).@"struct".fields) |field| {
|
|
167
165
|
const hp = comptime std.mem.eql(u8, field.name, "hp");
|
|
168
166
|
const spc =
|
|
169
167
|
comptime std.mem.eql(u8, field.name, "spa") or std.mem.eql(u8, field.name, "spd");
|
|
@@ -199,7 +197,7 @@ pub const Pokemon = struct {
|
|
|
199
197
|
const lvl = if (rand.chance(u8, 1, 20)) rand.range(u8, 1, 99 + 1) else 100;
|
|
200
198
|
var stats: Stats(u16) = .{};
|
|
201
199
|
const dvs = DVs.random(rand);
|
|
202
|
-
inline for (@
|
|
200
|
+
inline for (@typeInfo(@TypeOf(stats)).@"struct".fields) |field| {
|
|
203
201
|
@field(stats, field.name) = Stats(u16).calc(
|
|
204
202
|
field.name,
|
|
205
203
|
@field(species.stats, field.name),
|
|
@@ -212,7 +210,7 @@ pub const Pokemon = struct {
|
|
|
212
210
|
);
|
|
213
211
|
}
|
|
214
212
|
|
|
215
|
-
var ms
|
|
213
|
+
var ms: [4]MoveSlot = @splat(.{});
|
|
216
214
|
const n = if (rand.chance(u8, 1, 100)) rand.range(u4, 1, 3 + 1) else 4;
|
|
217
215
|
for (0..n) |i| {
|
|
218
216
|
var m: Move = .None;
|
|
@@ -292,23 +290,23 @@ else
|
|
|
292
290
|
pub fn diff(
|
|
293
291
|
a: *const data.Battle(data.PRNG),
|
|
294
292
|
b: *const data.Battle(data.PRNG),
|
|
295
|
-
w: protocol.
|
|
293
|
+
w: *protocol.Writer,
|
|
296
294
|
) !usize {
|
|
297
|
-
const pos = w.
|
|
295
|
+
const pos = w.pos;
|
|
298
296
|
const size = @sizeOf(data.Battle(data.PRNG)) - @sizeOf(data.PRNG);
|
|
299
297
|
assert(size % 2 == 0);
|
|
300
298
|
assert(size / 2 < 255);
|
|
301
299
|
|
|
302
300
|
var i: usize = 0;
|
|
303
301
|
while (i < size) : (i += 2) {
|
|
304
|
-
const val =
|
|
305
|
-
if (val !=
|
|
302
|
+
const val = std.mem.bytesAsValue(u16, std.mem.asBytes(a)[i .. i + 2]).*;
|
|
303
|
+
if (val != std.mem.bytesAsValue(u16, std.mem.asBytes(b)[i .. i + 2]).*) {
|
|
306
304
|
try w.writeByte(@intCast(i / 2));
|
|
307
305
|
try w.writeInt(u16, val, endian);
|
|
308
306
|
}
|
|
309
307
|
}
|
|
310
308
|
|
|
311
|
-
return w.
|
|
309
|
+
return w.pos - pos;
|
|
312
310
|
}
|
|
313
311
|
|
|
314
312
|
/// TODO
|
|
@@ -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");
|
|
@@ -456,7 +457,7 @@ fn executeMove(
|
|
|
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;
|
|
@@ -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();
|
|
@@ -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 });
|
|
@@ -1302,7 +1296,11 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
|
|
|
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
|
|
|
@@ -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;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const DEBUG = @import("../common/debug.zig").print;
|
|
2
2
|
|
|
3
|
+
const builtin = @import("builtin");
|
|
3
4
|
const common = @import("../common/data.zig");
|
|
4
5
|
const data = @import("data.zig");
|
|
5
6
|
const generated = @import("generated.zig");
|
|
@@ -681,7 +682,7 @@ pub fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype)
|
|
|
681
682
|
|
|
682
683
|
// Type effectiveness matchup precedence only matters with (NVE, SE)
|
|
683
684
|
if (!showdown and (eff1 + eff2) == Effectiveness.mismatch and
|
|
684
|
-
types.type1.precedence(
|
|
685
|
+
Type.precedence(state.type, types.type1) > Type.precedence(state.type, types.type2))
|
|
685
686
|
{
|
|
686
687
|
assert(eff2 != neutral);
|
|
687
688
|
d = d *% eff2 / 10;
|
|
@@ -1426,7 +1427,8 @@ pub const Effects = struct {
|
|
|
1426
1427
|
if (move.type.special() != (last == .MirrorCoat)) return;
|
|
1427
1428
|
if (state.damage == 0) return;
|
|
1428
1429
|
|
|
1429
|
-
|
|
1430
|
+
// NOTE: llvm/llvm-project#58557
|
|
1431
|
+
state.damage = smul(state.damage, 2);
|
|
1430
1432
|
state.miss = false;
|
|
1431
1433
|
}
|
|
1432
1434
|
|
|
@@ -1521,15 +1523,18 @@ pub const Effects = struct {
|
|
|
1521
1523
|
|
|
1522
1524
|
pub const double = struct {
|
|
1523
1525
|
pub fn flying(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1524
|
-
if (battle.foe(player).active.volatiles.Flying)
|
|
1526
|
+
if (!battle.foe(player).active.volatiles.Flying) return;
|
|
1527
|
+
state.damage = smul(state.damage, 2);
|
|
1525
1528
|
}
|
|
1526
1529
|
|
|
1527
1530
|
pub fn underground(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1528
|
-
if (battle.foe(player).active.volatiles.Underground)
|
|
1531
|
+
if (!battle.foe(player).active.volatiles.Underground) return;
|
|
1532
|
+
state.damage = smul(state.damage, 2);
|
|
1529
1533
|
}
|
|
1530
1534
|
|
|
1531
1535
|
pub fn minimize(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1532
|
-
if (battle.foe(player).active.volatiles.minimized)
|
|
1536
|
+
if (!battle.foe(player).active.volatiles.minimized) return;
|
|
1537
|
+
state.damage = smul(state.damage, 2);
|
|
1533
1538
|
}
|
|
1534
1539
|
};
|
|
1535
1540
|
|
|
@@ -1553,7 +1558,7 @@ pub const Effects = struct {
|
|
|
1553
1558
|
const last = battle.foe(player).lastMove(true);
|
|
1554
1559
|
const failed = volatiles.Encore or
|
|
1555
1560
|
(last == .None or last == .Struggle or
|
|
1556
|
-
|
|
1561
|
+
last == .Encore or .last == .MirrorMove);
|
|
1557
1562
|
if (failed) return options.log.fail(.{ foe_ident, .None });
|
|
1558
1563
|
|
|
1559
1564
|
var slot: u3 = 0;
|
|
@@ -1727,7 +1732,7 @@ pub const Effects = struct {
|
|
|
1727
1732
|
state.bp *= 2;
|
|
1728
1733
|
assert(state.bp <= 160);
|
|
1729
1734
|
} else {
|
|
1730
|
-
state.damage
|
|
1735
|
+
state.damage = smul(state.damage, 2);
|
|
1731
1736
|
}
|
|
1732
1737
|
}
|
|
1733
1738
|
|
|
@@ -2198,7 +2203,7 @@ pub const Effects = struct {
|
|
|
2198
2203
|
|
|
2199
2204
|
pub fn pursuit(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2200
2205
|
// TODO: remove switching variable and just look at Choice saved in Battle struct?
|
|
2201
|
-
if (battle.foe(player).active.volatiles.switching) state.damage
|
|
2206
|
+
if (battle.foe(player).active.volatiles.switching) state.damage = smul(state.damage, 2);
|
|
2202
2207
|
}
|
|
2203
2208
|
|
|
2204
2209
|
pub const rage = struct {
|
|
@@ -2211,7 +2216,7 @@ pub const Effects = struct {
|
|
|
2211
2216
|
const side = battle.side(player);
|
|
2212
2217
|
|
|
2213
2218
|
assert(side.active.volatiles.Rage);
|
|
2214
|
-
state.damage
|
|
2219
|
+
state.damage = smul(state.damage, (side.active.volatiles.rage +| 1)); // TODO: |+
|
|
2215
2220
|
}
|
|
2216
2221
|
};
|
|
2217
2222
|
|
|
@@ -2306,7 +2311,7 @@ pub const Effects = struct {
|
|
|
2306
2311
|
side.active.volatiles.attacks += 1;
|
|
2307
2312
|
side.active.volatiles.Rollout = side.active.volatiles.attacks != 5;
|
|
2308
2313
|
|
|
2309
|
-
if (side.active.volatiles.DefenseCurl) state.damage
|
|
2314
|
+
if (side.active.volatiles.DefenseCurl) state.damage = smul(state.damage, 2);
|
|
2310
2315
|
}
|
|
2311
2316
|
};
|
|
2312
2317
|
|
|
@@ -2834,6 +2839,12 @@ fn convertible(active: ActivePokemon, m: Move) bool {
|
|
|
2834
2839
|
return t != .@"???" and !active.types.includes(t);
|
|
2835
2840
|
}
|
|
2836
2841
|
|
|
2842
|
+
// NOTE: llvm/llvm-project#58557
|
|
2843
|
+
fn smul(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
|
|
2844
|
+
if (!comptime builtin.target.cpu.arch.isWasm()) return a *| b;
|
|
2845
|
+
return @max(a *% b, std.math.maxInt(@TypeOf(a)));
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2837
2848
|
pub const Rolls = struct {
|
|
2838
2849
|
fn speedTie(battle: anytype, options: anytype) !bool {
|
|
2839
2850
|
const p1 = if (options.calc.overridden(.P1, 0, .speed_tie)) |player|
|
|
@@ -3070,7 +3081,7 @@ pub const Rolls = struct {
|
|
|
3070
3081
|
}
|
|
3071
3082
|
|
|
3072
3083
|
// Conversion 2 can at most choose between 7 types (vs. a Grass-type attack)
|
|
3073
|
-
var CONVERSION_2: [7]Type =
|
|
3084
|
+
var CONVERSION_2: [7]Type = @splat(.@"???");
|
|
3074
3085
|
|
|
3075
3086
|
fn conversion2(battle: anytype, player: Player, mtype: Type, options: anytype) !Type {
|
|
3076
3087
|
var i: u8 = 0;
|
|
@@ -3495,11 +3506,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3495
3506
|
},
|
|
3496
3507
|
.Switch => {
|
|
3497
3508
|
const side = battle.side(player);
|
|
3498
|
-
var slot:
|
|
3509
|
+
var slot: usize = 2;
|
|
3499
3510
|
while (slot <= 6) : (slot += 1) {
|
|
3500
3511
|
const id = side.order[slot - 1];
|
|
3501
3512
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
3502
|
-
out[n] = .{ .type = .Switch, .data = slot };
|
|
3513
|
+
out[n] = .{ .type = .Switch, .data = @intCast(slot) };
|
|
3503
3514
|
n += 1;
|
|
3504
3515
|
}
|
|
3505
3516
|
if (n == 0) {
|
|
@@ -3529,11 +3540,11 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3529
3540
|
return n;
|
|
3530
3541
|
}
|
|
3531
3542
|
|
|
3532
|
-
var slot:
|
|
3543
|
+
var slot: usize = 2;
|
|
3533
3544
|
while (!active.volatiles.Trapped and slot <= 6) : (slot += 1) {
|
|
3534
3545
|
const id = side.order[slot - 1];
|
|
3535
3546
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
3536
|
-
out[n] = .{ .type = .Switch, .data = slot };
|
|
3547
|
+
out[n] = .{ .type = .Switch, .data = @intCast(slot) };
|
|
3537
3548
|
n += 1;
|
|
3538
3549
|
}
|
|
3539
3550
|
|
|
@@ -3566,7 +3577,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3566
3577
|
const struggle =
|
|
3567
3578
|
m.id == .Bide and (m.pp == 0 or active.volatiles.disable.move == slot);
|
|
3568
3579
|
const s = if (struggle) 0 else slot;
|
|
3569
|
-
out[n] = .{ .type = .Move, .data = s };
|
|
3580
|
+
out[n] = .{ .type = .Move, .data = @intCast(s) };
|
|
3570
3581
|
n += 1;
|
|
3571
3582
|
return n;
|
|
3572
3583
|
}
|
|
@@ -3580,7 +3591,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3580
3591
|
if (m.id == .None) break;
|
|
3581
3592
|
if (m.pp == 0) continue;
|
|
3582
3593
|
if (active.volatiles.disable.move == slot) continue;
|
|
3583
|
-
out[n] = .{ .type = .Move, .data = slot };
|
|
3594
|
+
out[n] = .{ .type = .Move, .data = @intCast(slot) };
|
|
3584
3595
|
n += 1;
|
|
3585
3596
|
}
|
|
3586
3597
|
// Struggle (Pokémon Showdown would use 'move 1' here)
|
package/src/lib/napi.h
ADDED