@pkmn/engine 0.1.0-dev.a93847b2 → 0.1.0-dev.abd68573
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 -3
- package/README.md +39 -16
- package/build/pkg/addon.js +6 -7
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/data.d.ts +4 -4
- package/build/pkg/data.js.map +1 -1
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/index.d.ts +2 -1
- package/build/pkg/index.js.map +1 -1
- package/build/pkg/protocol.js +5 -5
- package/build/pkg/protocol.js.map +1 -1
- package/build/test/display.js +2 -2
- package/build/test/display.js.map +1 -1
- package/build/tools/debug.js +5 -4
- package/build/tools/debug.js.map +1 -1
- package/build.zig +90 -76
- package/package.json +31 -23
- package/src/bin/install-pkmn-engine +26 -18
- package/src/lib/common/data.zig +8 -8
- package/src/lib/common/optional.zig +1 -1
- package/src/lib/common/protocol.zig +393 -396
- package/src/lib/common/rational.zig +11 -7
- package/src/lib/common/rng.zig +20 -11
- package/src/lib/gen1/calc.zig +7 -7
- package/src/lib/gen1/chance.zig +31 -30
- package/src/lib/gen1/data/moves.zig +9 -9
- package/src/lib/gen1/data/species.zig +2 -2
- package/src/lib/gen1/data/types.zig +4 -4
- package/src/lib/gen1/data.zig +32 -32
- package/src/lib/gen1/helpers.zig +48 -28
- package/src/lib/gen1/mechanics.zig +277 -255
- package/src/lib/gen2/calc.zig +1 -1
- package/src/lib/gen2/chance.zig +42 -41
- package/src/lib/gen2/data/items.zig +2 -2
- package/src/lib/gen2/data/moves.zig +2 -2
- package/src/lib/gen2/data/types.zig +9 -9
- package/src/lib/gen2/data.zig +23 -21
- package/src/lib/gen2/generated.zig +27 -78
- package/src/lib/gen2/mechanics.zig +1052 -459
- package/src/lib/{binding/node.zig → node.zig} +9 -12
- package/src/lib/pkmn.zig +27 -12
- package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
- package/src/lib/gen1/README.md +0 -894
- package/src/lib/gen2/README.md +0 -330
package/src/lib/gen1/data.zig
CHANGED
|
@@ -70,24 +70,24 @@ pub fn Battle(comptime RNG: anytype) type {
|
|
|
70
70
|
rng: RNG,
|
|
71
71
|
|
|
72
72
|
/// Returns the `Side` for the given `player`.
|
|
73
|
-
pub
|
|
73
|
+
pub fn side(self: anytype, player: Player) PointerType(@TypeOf(self), Side) {
|
|
74
74
|
assert(@typeInfo(@TypeOf(self)).Pointer.child == Self);
|
|
75
75
|
return &self.sides[@intFromEnum(player)];
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/// Returns the `Side` of the opponent for the given `player`
|
|
79
|
-
pub
|
|
79
|
+
pub fn foe(self: anytype, player: Player) PointerType(@TypeOf(self), Side) {
|
|
80
80
|
assert(@typeInfo(@TypeOf(self)).Pointer.child == Self);
|
|
81
81
|
return &self.sides[@intFromEnum(player.foe())];
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/// Returns an identifier for the active Pokémon of `player`.
|
|
85
|
-
pub
|
|
85
|
+
pub fn active(self: *const Self, player: Player) ID {
|
|
86
86
|
return player.ident(@intCast(self.side(player).order[0]));
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/// Returns the `MoveDetails` for the given `player`.
|
|
90
|
-
pub
|
|
90
|
+
pub fn lastMove(
|
|
91
91
|
self: anytype,
|
|
92
92
|
player: Player,
|
|
93
93
|
) PointerType(@TypeOf(self), MoveDetails) {
|
|
@@ -112,7 +112,7 @@ pub fn Battle(comptime RNG: anytype) type {
|
|
|
112
112
|
/// make (i.e. on the cartridge a soft-lock occurs).
|
|
113
113
|
///
|
|
114
114
|
/// This function will always return choices > 0 if `pkmn.options.showdown` is true.
|
|
115
|
-
pub fn choices(self: *Self, player: Player, request: Choice.Type, out: []Choice) u8 {
|
|
115
|
+
pub fn choices(self: *const Self, player: Player, request: Choice.Type, out: []Choice) u8 {
|
|
116
116
|
return mechanics.choices(self, player, request, out);
|
|
117
117
|
}
|
|
118
118
|
};
|
|
@@ -141,7 +141,7 @@ pub const Side = extern struct {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
/// Returns the stored `Pokemon` corresponding to the one-indexed party `slot`.
|
|
144
|
-
pub
|
|
144
|
+
pub fn get(self: anytype, slot: u8) PointerType(@TypeOf(self), Pokemon) {
|
|
145
145
|
assert(@typeInfo(@TypeOf(self)).Pointer.child == Side);
|
|
146
146
|
assert(slot > 0 and slot <= 6);
|
|
147
147
|
const id = self.order[slot - 1];
|
|
@@ -150,7 +150,7 @@ pub const Side = extern struct {
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
/// Returns the stored `Pokemon` corresponding to the `active` Pokémon (slot 1).
|
|
153
|
-
pub
|
|
153
|
+
pub fn stored(self: anytype) PointerType(@TypeOf(self), Pokemon) {
|
|
154
154
|
assert(@typeInfo(@TypeOf(self)).Pointer.child == Side);
|
|
155
155
|
return self.get(1);
|
|
156
156
|
}
|
|
@@ -176,7 +176,7 @@ pub const ActivePokemon = extern struct {
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
/// Returns the active Pokémon's current move slot located at the one-indexed `mslot`.
|
|
179
|
-
pub
|
|
179
|
+
pub fn move(self: anytype, mslot: u8) PointerType(@TypeOf(self), MoveSlot) {
|
|
180
180
|
assert(@typeInfo(@TypeOf(self)).Pointer.child == ActivePokemon);
|
|
181
181
|
assert(mslot > 0 and mslot <= 4);
|
|
182
182
|
assert(self.moves[mslot - 1].id != .None);
|
|
@@ -206,7 +206,7 @@ pub const Pokemon = extern struct {
|
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
/// Returns the Pokémon's original move slot located at the one-indexed `mslot`.
|
|
209
|
-
pub
|
|
209
|
+
pub fn move(self: anytype, mslot: u8) PointerType(@TypeOf(self), MoveSlot) {
|
|
210
210
|
assert(@typeInfo(@TypeOf(self)).Pointer.child == Pokemon);
|
|
211
211
|
assert(mslot > 0 and mslot <= 4);
|
|
212
212
|
assert(self.moves[mslot - 1].id != .None);
|
|
@@ -258,36 +258,36 @@ pub const Status = enum(u8) {
|
|
|
258
258
|
const SLP = 0b111;
|
|
259
259
|
|
|
260
260
|
/// Whether or not the status `num` is the same as `status`.
|
|
261
|
-
pub
|
|
261
|
+
pub fn is(num: u8, status: Status) bool {
|
|
262
262
|
if (status == .SLP) return Status.duration(num) > 0;
|
|
263
263
|
return ((num >> @intCast(@intFromEnum(status))) & 1) != 0;
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
/// Initializes a non-sleep `status`. Use `slp` or `slf` to initialize a sleep status.
|
|
267
|
-
pub
|
|
267
|
+
pub fn init(status: Status) u8 {
|
|
268
268
|
assert(status != .SLP and status != .EXT);
|
|
269
269
|
return @as(u8, 1) << @intCast(@intFromEnum(status));
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
/// Initializes a non self-inflicted sleep status with duration `dur`.
|
|
273
|
-
pub
|
|
273
|
+
pub fn slp(dur: u3) u8 {
|
|
274
274
|
assert(dur > 0);
|
|
275
275
|
return @as(u8, dur);
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
/// Initializes a self-inflicted sleep status with duration `dur`.
|
|
279
|
-
pub
|
|
279
|
+
pub fn slf(dur: u3) u8 {
|
|
280
280
|
assert(dur > 0);
|
|
281
281
|
return 0x80 | slp(dur);
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
/// Returns the duration of a sleep status.
|
|
285
|
-
pub
|
|
285
|
+
pub fn duration(num: u8) u3 {
|
|
286
286
|
return @intCast(num & SLP);
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
/// Returns whether `num` reflects any status.
|
|
290
|
-
pub
|
|
290
|
+
pub fn any(num: u8) bool {
|
|
291
291
|
return num > 0;
|
|
292
292
|
}
|
|
293
293
|
|
|
@@ -316,12 +316,12 @@ test Status {
|
|
|
316
316
|
try expect(Status.is(Status.slp(5), .SLP));
|
|
317
317
|
try expect(!Status.is(Status.slp(5), .EXT));
|
|
318
318
|
try expect(!Status.is(Status.slp(7), .PSN));
|
|
319
|
-
try expectEqual(
|
|
319
|
+
try expectEqual(5, Status.duration(Status.slp(5)));
|
|
320
320
|
try expect(Status.is(Status.slf(2), .SLP));
|
|
321
321
|
try expect(Status.is(Status.slf(2), .EXT));
|
|
322
322
|
try expect(Status.is(Status.TOX, .PSN));
|
|
323
323
|
try expect(Status.is(Status.TOX, .EXT));
|
|
324
|
-
try expectEqual(
|
|
324
|
+
try expectEqual(1, Status.duration(Status.slp(1)));
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
/// Bitfield representation of volatile statuses and associated data in Generation I.
|
|
@@ -405,13 +405,13 @@ test Volatiles {
|
|
|
405
405
|
try expect(!volatiles.Transform);
|
|
406
406
|
try expect(!volatiles.MultiHit);
|
|
407
407
|
|
|
408
|
-
try expectEqual(
|
|
409
|
-
try expectEqual(
|
|
410
|
-
try expectEqual(
|
|
411
|
-
try expectEqual(
|
|
412
|
-
try expectEqual(
|
|
413
|
-
try expectEqual(
|
|
414
|
-
try expectEqual(
|
|
408
|
+
try expectEqual(235, volatiles.state);
|
|
409
|
+
try expectEqual(42, volatiles.substitute);
|
|
410
|
+
try expectEqual(2, volatiles.disable_move);
|
|
411
|
+
try expectEqual(4, volatiles.disable_duration);
|
|
412
|
+
try expectEqual(2, volatiles.confusion);
|
|
413
|
+
try expectEqual(4, volatiles.toxic);
|
|
414
|
+
try expectEqual(3, volatiles.attacks);
|
|
415
415
|
}
|
|
416
416
|
|
|
417
417
|
/// Representaiton of a Pokémon's stats in Generation I.
|
|
@@ -442,9 +442,9 @@ test Stats {
|
|
|
442
442
|
try expectEqual(0, stats.def);
|
|
443
443
|
|
|
444
444
|
var base = Species.get(.Gyarados).stats;
|
|
445
|
-
try expectEqual(
|
|
445
|
+
try expectEqual(127, Stats(u16).calc("hp", base.hp, 0, 5120, 38));
|
|
446
446
|
base = Species.get(.Pidgeot).stats;
|
|
447
|
-
try expectEqual(
|
|
447
|
+
try expectEqual(279, Stats(u16).calc("spe", base.spe, 15, 63001, 100));
|
|
448
448
|
}
|
|
449
449
|
|
|
450
450
|
/// Representation of a Pokémon's boosts in Generation I.
|
|
@@ -537,7 +537,7 @@ pub const Species = species.Species;
|
|
|
537
537
|
|
|
538
538
|
test Species {
|
|
539
539
|
try expectEqual(2, @intFromEnum(Species.Ivysaur));
|
|
540
|
-
try expectEqual(
|
|
540
|
+
try expectEqual(100, Species.get(.Mew).stats.def);
|
|
541
541
|
}
|
|
542
542
|
|
|
543
543
|
/// Representation of a Generation I type in Pokémon.
|
|
@@ -576,8 +576,8 @@ test Types {
|
|
|
576
576
|
try expectEqual(Effectiveness.Resisted, Type.effectiveness(.Fire, .Water));
|
|
577
577
|
try expectEqual(Effectiveness.Neutral, Type.effectiveness(.Normal, .Grass));
|
|
578
578
|
|
|
579
|
-
try expectEqual(
|
|
580
|
-
try expectEqual(
|
|
579
|
+
try expectEqual(3, Type.precedence(.Water, .Water));
|
|
580
|
+
try expectEqual(26, Type.precedence(.Bug, .Ghost));
|
|
581
581
|
try expect(Type.precedence(.Poison, .Bug) > Type.precedence(.Poison, .Poison));
|
|
582
582
|
|
|
583
583
|
const t: Types = .{ .type1 = .Rock, .type2 = .Ground };
|
|
@@ -617,18 +617,18 @@ pub const DVs = struct {
|
|
|
617
617
|
|
|
618
618
|
test DVs {
|
|
619
619
|
var dvs: DVs = .{ .spc = 15, .spe = 15 };
|
|
620
|
-
try expectEqual(
|
|
620
|
+
try expectEqual(15, dvs.hp());
|
|
621
621
|
dvs = .{
|
|
622
622
|
.atk = 5,
|
|
623
623
|
.def = 15,
|
|
624
624
|
.spe = 13,
|
|
625
625
|
.spc = 13,
|
|
626
626
|
};
|
|
627
|
-
try expectEqual(
|
|
627
|
+
try expectEqual(15, dvs.hp());
|
|
628
628
|
dvs = .{
|
|
629
629
|
.def = 3,
|
|
630
630
|
.spe = 10,
|
|
631
631
|
.spc = 11,
|
|
632
632
|
};
|
|
633
|
-
try expectEqual(
|
|
633
|
+
try expectEqual(13, dvs.hp());
|
|
634
634
|
}
|
package/src/lib/gen1/helpers.zig
CHANGED
|
@@ -86,18 +86,38 @@ pub const Battle = struct {
|
|
|
86
86
|
};
|
|
87
87
|
|
|
88
88
|
fn prng(rand: *PSRNG) data.PRNG {
|
|
89
|
-
// GLITCH: initial bytes in seed can only range from 0-252, not 0-255
|
|
90
|
-
const max: u8 = 253;
|
|
91
89
|
return .{
|
|
92
90
|
.src = .{
|
|
93
91
|
.seed = if (showdown)
|
|
94
92
|
rand.newSeed()
|
|
95
|
-
else
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
93
|
+
else seed: {
|
|
94
|
+
// GLITCH: The initial RNG seed in Gen I link battles is heavily constrained
|
|
95
|
+
// https://www.smogon.com/forums/threads/rng-in-rby-link-battles.3746779
|
|
96
|
+
var seed = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
97
|
+
|
|
98
|
+
// Bytes in seed can only range from 0-252, not 0-255
|
|
99
|
+
const max: u8 = 253;
|
|
100
|
+
// Instructions take multiples of 4 cycles to execute
|
|
101
|
+
var timer: u16 = 4 * rand.range(u16, 0, 16384);
|
|
102
|
+
var add: u8 = rand.range(u8, 0, 256);
|
|
103
|
+
var carry: u8 = 0;
|
|
104
|
+
|
|
105
|
+
var i: u8 = 0;
|
|
106
|
+
while (i < seed.len) {
|
|
107
|
+
const div: u8 = @intCast(timer >> 8);
|
|
108
|
+
add +%= div +% carry;
|
|
109
|
+
if (add < max) {
|
|
110
|
+
seed[i] = add;
|
|
111
|
+
i += 1;
|
|
112
|
+
timer +%= 472;
|
|
113
|
+
carry = 1;
|
|
114
|
+
} else {
|
|
115
|
+
timer +%= 452;
|
|
116
|
+
carry = 0;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
break :seed seed;
|
|
120
|
+
},
|
|
101
121
|
},
|
|
102
122
|
};
|
|
103
123
|
}
|
|
@@ -266,7 +286,7 @@ pub const Rolls = struct {
|
|
|
266
286
|
const PLAYERS = [_]Optional(Player){ .P1, .P2 };
|
|
267
287
|
|
|
268
288
|
/// Returns a slice with the correct range of values for speed ties given the `action` state.
|
|
269
|
-
pub
|
|
289
|
+
pub fn speedTie(action: Action) []const Optional(Player) {
|
|
270
290
|
return if (@field(action, "speed_tie") == .None) &PLAYER_NONE else &PLAYERS;
|
|
271
291
|
}
|
|
272
292
|
|
|
@@ -275,14 +295,14 @@ pub const Rolls = struct {
|
|
|
275
295
|
|
|
276
296
|
/// Returns a slice with the correct range of values for hits given the `action` state
|
|
277
297
|
/// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
|
|
278
|
-
pub
|
|
298
|
+
pub fn hit(action: Action, parent: Optional(bool)) []const Optional(bool) {
|
|
279
299
|
if (parent == .true) return &BOOL_NONE;
|
|
280
300
|
return if (@field(action, "hit") == .None) &BOOL_NONE else &BOOLS;
|
|
281
301
|
}
|
|
282
302
|
|
|
283
303
|
/// Returns a slice with the correct range of values for critical hits given the `action` state
|
|
284
304
|
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
285
|
-
pub
|
|
305
|
+
pub fn criticalHit(
|
|
286
306
|
action: Action,
|
|
287
307
|
parent: Optional(bool),
|
|
288
308
|
) []const Optional(bool) {
|
|
@@ -292,7 +312,7 @@ pub const Rolls = struct {
|
|
|
292
312
|
|
|
293
313
|
/// Returns a slice with the correct range of values for secondary chances hits given the
|
|
294
314
|
/// `action` state and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
295
|
-
pub
|
|
315
|
+
pub fn secondaryChance(
|
|
296
316
|
action: Action,
|
|
297
317
|
parent: Optional(bool),
|
|
298
318
|
) []const Optional(bool) {
|
|
@@ -305,7 +325,7 @@ pub const Rolls = struct {
|
|
|
305
325
|
|
|
306
326
|
/// Returns the range bounding damage rolls given the `action` state and the state of
|
|
307
327
|
/// the `parent` (whether the player's Pokémon's move hit).
|
|
308
|
-
pub
|
|
328
|
+
pub fn damage(action: Action, parent: Optional(bool)) Range {
|
|
309
329
|
return if (parent == .false or @field(action, "damage") == 0)
|
|
310
330
|
.{ .min = 0, .max = 1 }
|
|
311
331
|
else
|
|
@@ -314,7 +334,7 @@ pub const Rolls = struct {
|
|
|
314
334
|
|
|
315
335
|
/// Returns the max damage roll which will produce the same damage as `roll`
|
|
316
336
|
/// given the base damage in `summaries`.
|
|
317
|
-
pub
|
|
337
|
+
pub fn coalesce(player: Player, roll: u8, summaries: *calc.Summaries, cap: bool) !u8 {
|
|
318
338
|
if (roll == 0) return roll;
|
|
319
339
|
|
|
320
340
|
const dmg = summaries.get(player.foe()).damage;
|
|
@@ -326,13 +346,13 @@ pub const Rolls = struct {
|
|
|
326
346
|
|
|
327
347
|
/// Returns a slice with the correct range of values for confused given the `action` state
|
|
328
348
|
/// and the state of the `parent` (the player's Pokémon's remaining confusion turns).
|
|
329
|
-
pub
|
|
349
|
+
pub fn confused(action: Action, parent: u4) []const Optional(bool) {
|
|
330
350
|
return if (parent == 0 or @field(action, "confused") == .None) &BOOL_NONE else &BOOLS;
|
|
331
351
|
}
|
|
332
352
|
|
|
333
353
|
/// Returns a slice with the correct range of values for paralysis given the `action` state
|
|
334
354
|
/// and the state of the `parent` (whether the player's Pokémon was confused).
|
|
335
|
-
pub
|
|
355
|
+
pub fn paralyzed(action: Action, parent: Optional(bool)) []const Optional(bool) {
|
|
336
356
|
if (parent == .true) return &BOOL_NONE;
|
|
337
357
|
return if (@field(action, "paralyzed") == .None) &BOOL_NONE else &BOOLS;
|
|
338
358
|
}
|
|
@@ -342,7 +362,7 @@ pub const Rolls = struct {
|
|
|
342
362
|
|
|
343
363
|
/// Returns a slice with a range of values for duration given the `action` state
|
|
344
364
|
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
345
|
-
pub
|
|
365
|
+
pub fn duration(action: Action, parent: Optional(bool)) []const u1 {
|
|
346
366
|
if (parent == .false) return &DURATION_NONE;
|
|
347
367
|
return if (@field(action, "duration") == 0) &DURATION_NONE else &DURATION;
|
|
348
368
|
}
|
|
@@ -418,7 +438,7 @@ pub const Rolls = struct {
|
|
|
418
438
|
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
419
439
|
///
|
|
420
440
|
/// These slots may or **may not be valid** as slots may be unset / have 0 PP.
|
|
421
|
-
pub
|
|
441
|
+
pub fn moveSlot(action: Action, parent: Optional(bool)) []const u4 {
|
|
422
442
|
if (parent == .false) return &SLOT_NONE;
|
|
423
443
|
return if (@field(action, "move_slot") == 0) &SLOT_NONE else &SLOT;
|
|
424
444
|
}
|
|
@@ -428,7 +448,7 @@ pub const Rolls = struct {
|
|
|
428
448
|
|
|
429
449
|
/// Returns a slice with the correct range of values for multi hit given the `action` state
|
|
430
450
|
/// and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
431
|
-
pub
|
|
451
|
+
pub fn multiHit(action: Action, parent: Optional(bool)) []const u4 {
|
|
432
452
|
if (parent == .false) return &MULTI_NONE;
|
|
433
453
|
return if (@field(action, "multi_hit") == 0) &MULTI_NONE else &MULTI;
|
|
434
454
|
}
|
|
@@ -442,7 +462,7 @@ pub const Rolls = struct {
|
|
|
442
462
|
|
|
443
463
|
/// Returns a slice with the correct range of values for psywave given the `action` state,
|
|
444
464
|
/// the `side`, and the state of the `parent` (whether the player's Pokémon's move hit).
|
|
445
|
-
pub
|
|
465
|
+
pub fn psywave(
|
|
446
466
|
action: Action,
|
|
447
467
|
side: *data.Side,
|
|
448
468
|
parent: Optional(bool),
|
|
@@ -457,7 +477,7 @@ pub const Rolls = struct {
|
|
|
457
477
|
const MOVE_NONE = [_]Move{.None};
|
|
458
478
|
|
|
459
479
|
/// Returns a slice with the correct range of values for metronome given the `action` state.
|
|
460
|
-
pub
|
|
480
|
+
pub fn metronome(action: Action) []const Move {
|
|
461
481
|
return if (@field(action, "metronome") == .None) &MOVE_NONE else &Move.METRONOME;
|
|
462
482
|
}
|
|
463
483
|
};
|
|
@@ -478,12 +498,12 @@ test "Rolls.damage" {
|
|
|
478
498
|
test "Rolls.coalesce" {
|
|
479
499
|
var summaries =
|
|
480
500
|
calc.Summaries{ .p1 = .{ .damage = .{ .base = 74, .final = 69, .capped = true } } };
|
|
481
|
-
try expectEqual(
|
|
482
|
-
try expectEqual(
|
|
483
|
-
try expectEqual(
|
|
501
|
+
try expectEqual(0, try Rolls.coalesce(.P2, 0, &summaries, false));
|
|
502
|
+
try expectEqual(241, try Rolls.coalesce(.P2, 238, &summaries, false));
|
|
503
|
+
try expectEqual(255, try Rolls.coalesce(.P2, 238, &summaries, true));
|
|
484
504
|
summaries.p1.damage.final = 74;
|
|
485
|
-
try expectEqual(
|
|
486
|
-
try expectEqual(
|
|
505
|
+
try expectEqual(217, try Rolls.coalesce(.P2, 217, &summaries, false));
|
|
506
|
+
try expectEqual(255, try Rolls.coalesce(.P2, 217, &summaries, true));
|
|
487
507
|
}
|
|
488
508
|
|
|
489
509
|
test "Rolls.hit" {
|
|
@@ -771,9 +791,9 @@ test "Rolls.psywave" {
|
|
|
771
791
|
|
|
772
792
|
try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p1, &side, .None));
|
|
773
793
|
var rolls = Rolls.psywave(actions.p2, &side, .None);
|
|
774
|
-
try expectEqual(
|
|
794
|
+
try expectEqual(150, rolls[rolls.len - 1]);
|
|
775
795
|
side.stored().level = 81;
|
|
776
796
|
rolls = Rolls.psywave(actions.p2, &side, .None);
|
|
777
|
-
try expectEqual(
|
|
797
|
+
try expectEqual(121, rolls[rolls.len - 1]);
|
|
778
798
|
try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p2, &side, .false));
|
|
779
799
|
}
|