@pkmn/engine 0.1.0-dev.cbdbb719 → 0.1.0-dev.ce4c0b2a
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/build/pkg/addon/browser.d.ts +3 -0
- package/build/pkg/addon/browser.js +43 -0
- package/build/pkg/addon/browser.js.map +1 -0
- package/build/pkg/addon/node.d.ts +3 -0
- package/build/pkg/addon/node.js +96 -0
- package/build/pkg/addon/node.js.map +1 -0
- package/build/pkg/addon.d.ts +2 -0
- package/build/pkg/addon.js +29 -51
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/data.d.ts +23 -1
- package/build/pkg/data.js.map +1 -1
- package/build/pkg/gen1.js +1 -1
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/index.d.ts +21 -8
- package/build/pkg/index.js +15 -10
- package/build/pkg/index.js.map +1 -1
- package/build/pkg/protocol.d.ts +20 -1
- package/build/pkg/protocol.js +105 -103
- package/build/pkg/protocol.js.map +1 -1
- package/build/tools/debug.js +34 -67
- package/build/tools/debug.js.map +1 -1
- package/build/tools/display/debug.jsx +128 -0
- package/build/tools/display/debug.jsx.map +1 -0
- package/build/tools/display/index.js +132 -0
- package/build/tools/display/index.js.map +1 -0
- package/build/tools/display/ui.jsx +349 -0
- package/build/tools/display/ui.jsx.map +1 -0
- package/build/tools/display/util.js +18 -0
- package/build/tools/display/util.js.map +1 -0
- package/build.zig +0 -2
- package/package.json +33 -23
- package/src/bin/install-pkmn-engine +4 -2
- package/src/bin/pkmn-debug +1 -1
- package/src/lib/common/debug.zig +9 -4
- package/src/lib/common/options.zig +2 -0
- package/src/lib/gen1/calc.zig +158 -14
- package/src/lib/gen1/chance.zig +3 -1
- package/src/lib/gen1/data.zig +2 -2
- package/src/lib/gen1/helpers.zig +0 -3
- package/src/lib/gen1/mechanics.zig +13 -6
- package/src/lib/gen2/calc.zig +1 -1
- package/src/lib/gen2/chance.zig +8 -13
- package/src/lib/gen2/helpers.zig +0 -4
- package/src/lib/gen2/mechanics.zig +3 -9
- package/src/tools/display/dom.ts +96 -0
- package/src/tools/display/ui.css +351 -0
- package/build/test/display.js +0 -405
- package/build/test/display.js.map +0 -1
- package/src/test/display/pkmn.html.tmpl +0 -368
package/src/lib/gen1/calc.zig
CHANGED
|
@@ -35,7 +35,11 @@ const isPointerTo = util.isPointerTo;
|
|
|
35
35
|
const Actions = chance.Actions;
|
|
36
36
|
const Action = chance.Action;
|
|
37
37
|
const Chance = chance.Chance;
|
|
38
|
+
const Confusion = chance.Confusion;
|
|
39
|
+
const Duration = chance.Duration;
|
|
38
40
|
const Durations = chance.Durations;
|
|
41
|
+
const Observation = chance.Observation;
|
|
42
|
+
const Sleeps = chance.Sleeps;
|
|
39
43
|
|
|
40
44
|
const tty = true; // DEBUG
|
|
41
45
|
const summary = false; // DEBUG
|
|
@@ -194,9 +198,11 @@ pub fn transitions(
|
|
|
194
198
|
var frontier = std.ArrayList(Actions).init(allocator);
|
|
195
199
|
defer frontier.deinit();
|
|
196
200
|
|
|
201
|
+
const durations = options.durations;
|
|
202
|
+
|
|
197
203
|
var opts = pkmn.battle.options(
|
|
198
204
|
protocol.NULL,
|
|
199
|
-
Chance(Rational(u128)){ .probability = .{}, .durations =
|
|
205
|
+
Chance(Rational(u128)){ .probability = .{}, .durations = durations },
|
|
200
206
|
Calc{},
|
|
201
207
|
);
|
|
202
208
|
|
|
@@ -220,6 +226,7 @@ pub fn transitions(
|
|
|
220
226
|
assert(frontier.items.len == 1);
|
|
221
227
|
while (i < frontier.items.len) : (i += 1) {
|
|
222
228
|
const f = frontier.items[i];
|
|
229
|
+
const saved = stats.saved;
|
|
223
230
|
|
|
224
231
|
try debug(writer, f, .{
|
|
225
232
|
.shape = true,
|
|
@@ -235,10 +242,20 @@ pub fn transitions(
|
|
|
235
242
|
};
|
|
236
243
|
|
|
237
244
|
for (Rolls.speedTie(f.p1)) |tie| { a.p1.speed_tie = tie; a.p2.speed_tie = tie;
|
|
238
|
-
for (Rolls.
|
|
239
|
-
for (Rolls.
|
|
245
|
+
for (Rolls.sleep(f.p1, durations.p1)) |p1_slp| { a.p1.sleep = p1_slp;
|
|
246
|
+
for (Rolls.sleep(f.p2, durations.p2)) |p2_slp| { a.p2.sleep = p2_slp;
|
|
247
|
+
for (Rolls.disable(f.p1, durations.p1, p1_slp)) |p1_dis| { a.p1.disable = p1_dis;
|
|
248
|
+
for (Rolls.disable(f.p2, durations.p2, p2_slp)) |p2_dis| { a.p2.disable = p2_dis;
|
|
249
|
+
for (Rolls.confusion(f.p1, durations.p1, p1_slp)) |p1_cfz| { a.p1.confusion = p1_cfz;
|
|
250
|
+
for (Rolls.confusion(f.p2, durations.p2, p1_slp)) |p2_cfz| { a.p2.confusion = p2_cfz;
|
|
251
|
+
for (Rolls.confused(f.p1, p1_cfz)) |p1_cfzd| { a.p1.confused = p1_cfzd;
|
|
252
|
+
for (Rolls.confused(f.p2, p2_cfz)) |p2_cfzd| { a.p2.confused = p2_cfzd;
|
|
240
253
|
for (Rolls.paralyzed(f.p1, p1_cfzd)) |p1_par| { a.p1.paralyzed = p1_par;
|
|
241
254
|
for (Rolls.paralyzed(f.p2, p2_cfzd)) |p2_par| { a.p2.paralyzed = p2_par;
|
|
255
|
+
for (Rolls.attacking(f.p1, durations.p1, p1_par)) |p1_atk| { a.p1.attacking = p1_atk;
|
|
256
|
+
for (Rolls.attacking(f.p2, durations.p2, p2_par)) |p2_atk| { a.p2.attacking = p2_atk;
|
|
257
|
+
for (Rolls.binding(f.p1, durations.p1, p1_par)) |p1_bind| { a.p1.binding = p1_bind;
|
|
258
|
+
for (Rolls.binding(f.p2, durations.p2, p2_par)) |p2_bind| { a.p2.binding = p2_bind;
|
|
242
259
|
for (Rolls.hit(f.p1, p1_par)) |p1_hit| { a.p1.hit = p1_hit;
|
|
243
260
|
for (Rolls.hit(f.p2, p2_par)) |p2_hit| { a.p2.hit = p2_hit;
|
|
244
261
|
for (Rolls.psywave(f.p1, p1, p1_hit)) |p1_psywave| { a.p1.psywave = p1_psywave;
|
|
@@ -266,7 +283,7 @@ pub fn transitions(
|
|
|
266
283
|
|
|
267
284
|
opts.calc.overrides = a;
|
|
268
285
|
opts.calc.summaries = .{};
|
|
269
|
-
opts.chance = .{ .probability = .{}, .durations =
|
|
286
|
+
opts.chance = .{ .probability = .{}, .durations = durations };
|
|
270
287
|
const q = &opts.chance.probability;
|
|
271
288
|
|
|
272
289
|
b = battle;
|
|
@@ -283,7 +300,7 @@ pub fn transitions(
|
|
|
283
300
|
try Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), summaries, cap);
|
|
284
301
|
|
|
285
302
|
if (opts.chance.actions.matches(f)) {
|
|
286
|
-
if (!opts.chance.actions.eql(a)) {
|
|
303
|
+
if (!map(opts.chance.actions).eql(a)) {
|
|
287
304
|
if (!summary) {
|
|
288
305
|
try debug(writer, opts.chance.actions, .{
|
|
289
306
|
.p1_max = p1_max,
|
|
@@ -358,9 +375,11 @@ pub fn transitions(
|
|
|
358
375
|
p2_dmg.min = p2_max;
|
|
359
376
|
}
|
|
360
377
|
|
|
361
|
-
}}}}}}}}}}}}}}}}}}
|
|
378
|
+
}}}}}}}}}}}}}}}}}}}}}}}}}}}}
|
|
362
379
|
|
|
363
|
-
|
|
380
|
+
assert(stats.saved > saved);
|
|
381
|
+
const old = @hasDecl(std.fmt, "formatFloatDecimal");
|
|
382
|
+
if (!old and @TypeOf(writer) != @TypeOf(std.io.null_writer)) {
|
|
364
383
|
p.reduce();
|
|
365
384
|
try writer.print(
|
|
366
385
|
" = {s} ({d:.2}%)\n\n",
|
|
@@ -496,6 +515,14 @@ fn unfix(actual: anytype) data.Battle(data.PRNG) {
|
|
|
496
515
|
};
|
|
497
516
|
}
|
|
498
517
|
|
|
518
|
+
fn map(actions: Actions) Actions {
|
|
519
|
+
if (!pkmn.options.overwrite) return actions;
|
|
520
|
+
var a = actions;
|
|
521
|
+
if (a.p1.confusion == .overwritten) a.p1.confusion = .continuing;
|
|
522
|
+
if (a.p2.confusion == .overwritten) a.p2.confusion = .continuing;
|
|
523
|
+
return a;
|
|
524
|
+
}
|
|
525
|
+
|
|
499
526
|
fn matches(actions: Actions, i: usize, frontier: []Actions) bool {
|
|
500
527
|
for (frontier, 0..) |f, j| {
|
|
501
528
|
// TODO: is skipping this redundant check worth it?
|
|
@@ -577,8 +604,6 @@ fn format(writer: anytype, actions: Actions, p1: u9, p2: u9) !void {
|
|
|
577
604
|
try writer.writeAll(output[0..len]);
|
|
578
605
|
}
|
|
579
606
|
|
|
580
|
-
const NONE = @intFromEnum(Optional(bool).None);
|
|
581
|
-
|
|
582
607
|
/// Helper functions that efficiently return valid ranges for various RNG events based on the
|
|
583
608
|
/// state of an `Action` and other events to be used to construct a "transitions" function.
|
|
584
609
|
pub const Rolls = struct {
|
|
@@ -590,6 +615,101 @@ pub const Rolls = struct {
|
|
|
590
615
|
return if (action.speed_tie == .None) &PLAYER_NONE else &PLAYERS;
|
|
591
616
|
}
|
|
592
617
|
|
|
618
|
+
const OBS_NONE = [_]Optional(Observation){.None};
|
|
619
|
+
const OBS_STARTED = [_]Optional(Observation){.started};
|
|
620
|
+
const OBS_CONTINUING = [_]Optional(Observation){.continuing};
|
|
621
|
+
const OBS_ENDED = [_]Optional(Observation){.ended};
|
|
622
|
+
const OBS = [_]Optional(Observation){ .continuing, .ended };
|
|
623
|
+
|
|
624
|
+
/// Returns a slice with a range of values for sleep given the `action` state and observed
|
|
625
|
+
/// `durations`.
|
|
626
|
+
pub fn sleep(action: Action, duration: Duration) []const Optional(Observation) {
|
|
627
|
+
return switch (action.sleep) {
|
|
628
|
+
.None => &OBS_NONE,
|
|
629
|
+
.started => &OBS_STARTED,
|
|
630
|
+
else => if (Sleeps.get(duration.sleeps, 0) >= 7) &OBS_ENDED else &OBS,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/// Returns a slice with a range of values for disable given the `action`, observed `durations`,
|
|
635
|
+
/// and the state of the `parent` (observation of the player's Pokémon sleep status).
|
|
636
|
+
pub fn disable(
|
|
637
|
+
action: Action,
|
|
638
|
+
duration: Duration,
|
|
639
|
+
parent: Optional(Observation),
|
|
640
|
+
) []const Optional(Observation) {
|
|
641
|
+
// if (parent == .started or parent == .continuing) return &OBS_NONE;
|
|
642
|
+
_ = parent; // FIXME
|
|
643
|
+
return switch (action.disable) {
|
|
644
|
+
.None => &OBS_NONE,
|
|
645
|
+
.started => &OBS_STARTED,
|
|
646
|
+
else => if (duration.disable >= 8) &OBS_ENDED else &OBS,
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const CFZ_NONE = [_]Optional(Confusion){.None};
|
|
651
|
+
const CFZ_STARTED = [_]Optional(Confusion){.started};
|
|
652
|
+
const CFZ_CONTINUING = [_]Optional(Confusion){.continuing};
|
|
653
|
+
const CFZ_ENDED = [_]Optional(Confusion){.ended};
|
|
654
|
+
const CFZ = [_]Optional(Confusion){ .continuing, .ended };
|
|
655
|
+
|
|
656
|
+
/// Returns a slice with a range of values for confusion given the `action`, observed `durations`,
|
|
657
|
+
/// and the state of the `parent` (observation of the player's Pokémon sleep status).
|
|
658
|
+
pub fn confusion(
|
|
659
|
+
action: Action,
|
|
660
|
+
duration: Duration,
|
|
661
|
+
parent: Optional(Observation),
|
|
662
|
+
) []const Optional(Confusion) {
|
|
663
|
+
// if (parent == .started or parent == .continuing) return &CFZ_NONE;
|
|
664
|
+
_ = parent; // FIXME
|
|
665
|
+
return switch (action.confusion) {
|
|
666
|
+
.None => &CFZ_NONE,
|
|
667
|
+
.started => &CFZ_STARTED,
|
|
668
|
+
else => if (duration.confusion >= 5)
|
|
669
|
+
&CFZ_ENDED
|
|
670
|
+
else if (duration.confusion < 2)
|
|
671
|
+
&CFZ_CONTINUING
|
|
672
|
+
else
|
|
673
|
+
&CFZ,
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/// Returns a slice with a range of values for attacking given the `action`, observed
|
|
678
|
+
/// `durations`, and the state of the `parent` (whether the player's Pokémon was fully
|
|
679
|
+
/// paralyzed).
|
|
680
|
+
pub fn attacking(
|
|
681
|
+
action: Action,
|
|
682
|
+
duration: Duration,
|
|
683
|
+
parent: Optional(bool),
|
|
684
|
+
) []const Optional(Observation) {
|
|
685
|
+
if (parent == .true) return &OBS_NONE;
|
|
686
|
+
return switch (action.attacking) {
|
|
687
|
+
.None => &OBS_NONE,
|
|
688
|
+
.started => &OBS_STARTED,
|
|
689
|
+
else => if (duration.attacking >= 3)
|
|
690
|
+
&OBS_ENDED
|
|
691
|
+
else if (duration.attacking < 2)
|
|
692
|
+
&OBS_CONTINUING
|
|
693
|
+
else
|
|
694
|
+
&OBS,
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/// Returns a slice with a range of values for binding given the `action`, observed `durations`,
|
|
699
|
+
/// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
|
|
700
|
+
pub fn binding(
|
|
701
|
+
action: Action,
|
|
702
|
+
duration: Duration,
|
|
703
|
+
parent: Optional(bool),
|
|
704
|
+
) []const Optional(Observation) {
|
|
705
|
+
if (parent == .true) return &OBS_NONE;
|
|
706
|
+
return switch (action.binding) {
|
|
707
|
+
.None => &OBS_NONE,
|
|
708
|
+
.started => &OBS_STARTED,
|
|
709
|
+
else => if (duration.binding >= 4) &OBS_ENDED else &OBS,
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
|
|
593
713
|
const BOOL_NONE = [_]Optional(bool){.None};
|
|
594
714
|
const BOOLS = [_]Optional(bool){ .false, .true };
|
|
595
715
|
|
|
@@ -638,9 +758,12 @@ pub const Rolls = struct {
|
|
|
638
758
|
return @min(255, roll + ((254 - ((@as(u32, dmg.base) * roll) % 255)) / dmg.base));
|
|
639
759
|
}
|
|
640
760
|
|
|
641
|
-
/// Returns a slice with the correct range of values for confused
|
|
642
|
-
|
|
643
|
-
|
|
761
|
+
/// Returns a slice with the correct range of values for confused given the `action` state and
|
|
762
|
+
/// the state of the `parent` (observation of the player's Pokémon confusion status).
|
|
763
|
+
pub fn confused(action: Action, parent: Optional(Confusion)) []const Optional(bool) {
|
|
764
|
+
const done = false; // parent == .None or parent == .ended;
|
|
765
|
+
_ = parent; // FIXME
|
|
766
|
+
return if (done or action.confused == .None) &BOOL_NONE else &BOOLS;
|
|
644
767
|
}
|
|
645
768
|
|
|
646
769
|
/// Returns a slice with the correct range of values for paralysis given the `action` state
|
|
@@ -707,6 +830,26 @@ test "Rolls.speedTie" {
|
|
|
707
830
|
try expectEqualSlices(Optional(Player), &.{.None}, Rolls.speedTie(actions.p2));
|
|
708
831
|
}
|
|
709
832
|
|
|
833
|
+
test "Rolls.sleep" {
|
|
834
|
+
return error.SkipZigTest; // TODO
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
test "Rolls.disable" {
|
|
838
|
+
return error.SkipZigTest; // TODO
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
test "Rolls.confusion" {
|
|
842
|
+
return error.SkipZigTest; // TODO
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
test "Rolls.atacking" {
|
|
846
|
+
return error.SkipZigTest; // TODO
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
test "Rolls.binding" {
|
|
850
|
+
return error.SkipZigTest; // TODO
|
|
851
|
+
}
|
|
852
|
+
|
|
710
853
|
test "Rolls.damage" {
|
|
711
854
|
const actions: Actions = .{ .p2 = .{ .damage = 221 } };
|
|
712
855
|
try expectEqual(Rolls.Range{ .min = 0, .max = 1 }, Rolls.damage(actions.p1, .None));
|
|
@@ -756,8 +899,9 @@ test "Rolls.criticalHit" {
|
|
|
756
899
|
|
|
757
900
|
test "Rolls.confused" {
|
|
758
901
|
const actions: Actions = .{ .p2 = .{ .confused = .true } };
|
|
759
|
-
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p1));
|
|
760
|
-
try expectEqualSlices(Optional(bool), &.{ .false, .true }, Rolls.confused(actions.p2));
|
|
902
|
+
try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p1, .None));
|
|
903
|
+
try expectEqualSlices(Optional(bool), &.{ .false, .true }, Rolls.confused(actions.p2, .continuing));
|
|
904
|
+
// FIXME try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p2, .ended));
|
|
761
905
|
}
|
|
762
906
|
|
|
763
907
|
test "Rolls.paralyzed" {
|
package/src/lib/gen1/chance.zig
CHANGED
|
@@ -251,7 +251,7 @@ pub const Durations = struct {
|
|
|
251
251
|
}
|
|
252
252
|
};
|
|
253
253
|
|
|
254
|
-
const Sleeps = Array(6, u3);
|
|
254
|
+
pub const Sleeps = Array(6, u3);
|
|
255
255
|
|
|
256
256
|
/// TODO
|
|
257
257
|
pub const Duration = packed struct(u32) {
|
|
@@ -656,6 +656,7 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
656
656
|
var d = self.durations.get(player);
|
|
657
657
|
const n = d.attacking;
|
|
658
658
|
if (obs == .ended) {
|
|
659
|
+
// "3-4 turns" includes the turn for the initial attack *plus* the forced attacks
|
|
659
660
|
assert(n >= 2 and n <= 3);
|
|
660
661
|
if (n != 3) try self.probability.update(1, 4 - @as(u4, n));
|
|
661
662
|
d.attacking = 0;
|
|
@@ -682,6 +683,7 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
682
683
|
const q: u4 = if (n < 3) 8 - ((n - 1) * p) else 2;
|
|
683
684
|
|
|
684
685
|
if (obs == .ended) {
|
|
686
|
+
// "2-5 turns" includes the turn for the initial attack *plus* the forced attacks
|
|
685
687
|
assert(n >= 1 and n <= 4);
|
|
686
688
|
if (n != 4) try self.probability.update(p, q);
|
|
687
689
|
d.binding = 0;
|
package/src/lib/gen1/data.zig
CHANGED
|
@@ -256,7 +256,7 @@ pub const Status = enum(u8) {
|
|
|
256
256
|
|
|
257
257
|
pub const TOX = 0b10001000;
|
|
258
258
|
|
|
259
|
-
const
|
|
259
|
+
const SLEEP = 0b111;
|
|
260
260
|
|
|
261
261
|
/// Whether or not the status `num` is the same as `status`.
|
|
262
262
|
pub fn is(num: u8, status: Status) bool {
|
|
@@ -284,7 +284,7 @@ pub const Status = enum(u8) {
|
|
|
284
284
|
|
|
285
285
|
/// Returns the duration of a sleep status.
|
|
286
286
|
pub fn duration(num: u8) u3 {
|
|
287
|
-
return @intCast(num &
|
|
287
|
+
return @intCast(num & SLEEP);
|
|
288
288
|
}
|
|
289
289
|
|
|
290
290
|
/// Returns whether `num` reflects any status.
|
package/src/lib/gen1/helpers.zig
CHANGED
|
@@ -12,10 +12,8 @@ const data = @import("data.zig");
|
|
|
12
12
|
const assert = std.debug.assert;
|
|
13
13
|
|
|
14
14
|
const Choice = common.Choice;
|
|
15
|
-
const Player = common.Player;
|
|
16
15
|
|
|
17
16
|
const showdown = options.showdown;
|
|
18
|
-
const chance = options.chance;
|
|
19
17
|
|
|
20
18
|
const PSRNG = rng.PSRNG;
|
|
21
19
|
|
|
@@ -24,7 +22,6 @@ const Move = data.Move;
|
|
|
24
22
|
const MoveSlot = data.MoveSlot;
|
|
25
23
|
const Species = data.Species;
|
|
26
24
|
const Stats = data.Stats;
|
|
27
|
-
const Status = data.Status;
|
|
28
25
|
|
|
29
26
|
const endian = builtin.cpu.arch.endian();
|
|
30
27
|
|
|
@@ -4,7 +4,6 @@ const pkmn = @import("../pkmn.zig");
|
|
|
4
4
|
|
|
5
5
|
const common = @import("../common/data.zig");
|
|
6
6
|
const DEBUG = @import("../common/debug.zig").print;
|
|
7
|
-
const optional = @import("../common/optional.zig");
|
|
8
7
|
const protocol = @import("../common/protocol.zig");
|
|
9
8
|
const rng = @import("../common/rng.zig");
|
|
10
9
|
|
|
@@ -29,11 +28,9 @@ const Heal = protocol.Heal;
|
|
|
29
28
|
|
|
30
29
|
const Gen12 = rng.Gen12;
|
|
31
30
|
|
|
32
|
-
const ActivePokemon = data.ActivePokemon;
|
|
33
31
|
const Effectiveness = data.Effectiveness;
|
|
34
32
|
const Move = data.Move;
|
|
35
33
|
const MoveSlot = data.MoveSlot;
|
|
36
|
-
const Pokemon = data.Pokemon;
|
|
37
34
|
const Side = data.Side;
|
|
38
35
|
const Species = data.Species;
|
|
39
36
|
const Stats = data.Stats;
|
|
@@ -344,6 +341,7 @@ fn doTurn(
|
|
|
344
341
|
player_choice,
|
|
345
342
|
player_rewrap,
|
|
346
343
|
player_skip,
|
|
344
|
+
true,
|
|
347
345
|
&residual,
|
|
348
346
|
options,
|
|
349
347
|
)) |r| return r;
|
|
@@ -366,6 +364,7 @@ fn doTurn(
|
|
|
366
364
|
foe_choice,
|
|
367
365
|
foe_rewrap,
|
|
368
366
|
foe_skip,
|
|
367
|
+
false,
|
|
369
368
|
&residual,
|
|
370
369
|
options,
|
|
371
370
|
)) |r| return r;
|
|
@@ -393,6 +392,7 @@ fn executeMove(
|
|
|
393
392
|
choice: Choice,
|
|
394
393
|
rewrap: bool,
|
|
395
394
|
skip: bool,
|
|
395
|
+
first: bool,
|
|
396
396
|
residual: *bool,
|
|
397
397
|
options: anytype,
|
|
398
398
|
) !?Result {
|
|
@@ -452,7 +452,7 @@ fn executeMove(
|
|
|
452
452
|
|
|
453
453
|
var skip_can = false;
|
|
454
454
|
var skip_pp = false;
|
|
455
|
-
switch (try beforeMove(battle, player, skip, residual, options)) {
|
|
455
|
+
switch (try beforeMove(battle, player, skip, first, residual, options)) {
|
|
456
456
|
.done => return null,
|
|
457
457
|
.skip_can => skip_can = true,
|
|
458
458
|
.skip_pp => skip_pp = true,
|
|
@@ -473,6 +473,7 @@ fn beforeMove(
|
|
|
473
473
|
battle: anytype,
|
|
474
474
|
player: Player,
|
|
475
475
|
skip: bool,
|
|
476
|
+
first: bool,
|
|
476
477
|
residual: *bool,
|
|
477
478
|
options: anytype,
|
|
478
479
|
) !BeforeMove {
|
|
@@ -564,7 +565,13 @@ fn beforeMove(
|
|
|
564
565
|
if (volatiles.Confusion) {
|
|
565
566
|
assert(volatiles.confusion > 0);
|
|
566
567
|
if (options.calc.overridden(player, .confusion)) |obs| switch (obs) {
|
|
567
|
-
.started =>
|
|
568
|
+
.started => {
|
|
569
|
+
if (!first or (options.calc.overridden(player, .confused) orelse .None) != .None) {
|
|
570
|
+
volatiles.confusion -= 1;
|
|
571
|
+
} else {
|
|
572
|
+
volatiles.confusion = 0;
|
|
573
|
+
}
|
|
574
|
+
},
|
|
568
575
|
.continuing => if (volatiles.confusion > 1) {
|
|
569
576
|
volatiles.confusion -= 1;
|
|
570
577
|
},
|
|
@@ -696,7 +703,7 @@ fn beforeMove(
|
|
|
696
703
|
try options.chance.attacking(player, if (volatiles.attacks == 0) .ended else .continuing);
|
|
697
704
|
|
|
698
705
|
if (volatiles.attacks == 0) {
|
|
699
|
-
const overwritten = volatiles.Confusion;
|
|
706
|
+
const overwritten = pkmn.options.overwrite and volatiles.Confusion;
|
|
700
707
|
|
|
701
708
|
volatiles.Thrashing = false;
|
|
702
709
|
volatiles.Confusion = true;
|
package/src/lib/gen2/calc.zig
CHANGED
package/src/lib/gen2/chance.zig
CHANGED
|
@@ -8,7 +8,6 @@ const expectEqual = std.testing.expectEqual;
|
|
|
8
8
|
|
|
9
9
|
const options = @import("../common/options.zig");
|
|
10
10
|
const rational = @import("../common/rational.zig");
|
|
11
|
-
const rng = @import("../common/rng.zig");
|
|
12
11
|
const util = @import("../common/util.zig");
|
|
13
12
|
|
|
14
13
|
const Array = @import("../common/array.zig").Array;
|
|
@@ -20,8 +19,6 @@ const data = @import("data.zig");
|
|
|
20
19
|
const enabled = options.chance;
|
|
21
20
|
const showdown = options.showdown;
|
|
22
21
|
|
|
23
|
-
const Gen12 = rng.Gen12;
|
|
24
|
-
|
|
25
22
|
const PointerType = util.PointerType;
|
|
26
23
|
const isPointerTo = util.isPointerTo;
|
|
27
24
|
|
|
@@ -111,7 +108,7 @@ test Actions {
|
|
|
111
108
|
/// Observation made about a duration - whether the duration has started, been continued, or ended.
|
|
112
109
|
pub const Observation = enum { started, continuing, ended };
|
|
113
110
|
|
|
114
|
-
pub const Damages = Array(5,
|
|
111
|
+
pub const Damages = Array(5, u8);
|
|
115
112
|
pub const Criticals = Array(5, Optional(bool));
|
|
116
113
|
|
|
117
114
|
/// Information about the RNG that was observed during a Generation II battle `update` for a
|
|
@@ -164,10 +161,6 @@ pub const Action = packed struct(u128) {
|
|
|
164
161
|
/// If not 0, the value (2-5) to return for Rolls.distribution for multi hit.
|
|
165
162
|
multi_hit: u3 = 0,
|
|
166
163
|
|
|
167
|
-
_: u9 = 0,
|
|
168
|
-
|
|
169
|
-
/// If not 0, the value to by one of the Rolls.*Duration rolls.
|
|
170
|
-
duration: u4 = 0,
|
|
171
164
|
sleep: Optional(Observation) = .None,
|
|
172
165
|
/// TODO
|
|
173
166
|
confusion: Optional(Observation) = .None,
|
|
@@ -180,6 +173,8 @@ pub const Action = packed struct(u128) {
|
|
|
180
173
|
/// TODO
|
|
181
174
|
encore: Optional(Observation) = .None,
|
|
182
175
|
|
|
176
|
+
/// If not 0, the value to by one of the Rolls.*Duration rolls.
|
|
177
|
+
duration: u3 = 0,
|
|
183
178
|
/// If not None, the value to return for Rolls.conversion2.
|
|
184
179
|
conversion_2: Optional(Type) = .None,
|
|
185
180
|
|
|
@@ -292,7 +287,7 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
292
287
|
|
|
293
288
|
try self.probability.update(1, 39);
|
|
294
289
|
var a = self.actions.get(player);
|
|
295
|
-
a.damages = Damages.set(a.damages, i,
|
|
290
|
+
a.damages = Damages.set(a.damages, i, roll);
|
|
296
291
|
}
|
|
297
292
|
|
|
298
293
|
pub fn confused(self: *Self, player: Player, cfz: bool) Error!void {
|
|
@@ -444,7 +439,7 @@ pub fn Chance(comptime Rational: type) type {
|
|
|
444
439
|
self.actions.get(player).multi_hit = n;
|
|
445
440
|
}
|
|
446
441
|
|
|
447
|
-
pub fn duration(self: *Self, player: Player, turns:
|
|
442
|
+
pub fn duration(self: *Self, player: Player, turns: u3) void {
|
|
448
443
|
if (!enabled) return;
|
|
449
444
|
|
|
450
445
|
self.actions.get(player).duration = if (options.key) 2 else turns;
|
|
@@ -514,7 +509,7 @@ test "Chance.damage" {
|
|
|
514
509
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
515
510
|
|
|
516
511
|
try chance.damage(.P1, 0, 219);
|
|
517
|
-
try expectValue(@as(
|
|
512
|
+
try expectValue(@as(u8, 219), Damages.get(chance.actions.p1.damages, 0));
|
|
518
513
|
try expectProbability(&chance.probability, 1, 39);
|
|
519
514
|
}
|
|
520
515
|
|
|
@@ -783,7 +778,7 @@ test "Chance.duration" {
|
|
|
783
778
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
784
779
|
|
|
785
780
|
chance.duration(.P1, 2);
|
|
786
|
-
try expectValue(@as(
|
|
781
|
+
try expectValue(@as(u3, 2), chance.actions.p1.duration);
|
|
787
782
|
}
|
|
788
783
|
|
|
789
784
|
pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
|
|
@@ -901,7 +896,7 @@ const Null = struct {
|
|
|
901
896
|
_ = .{ self, player, move, n };
|
|
902
897
|
}
|
|
903
898
|
|
|
904
|
-
pub fn duration(self: Null, player: Player, turns:
|
|
899
|
+
pub fn duration(self: Null, player: Player, turns: u3) void {
|
|
905
900
|
_ = .{ self, player, turns };
|
|
906
901
|
}
|
|
907
902
|
};
|
package/src/lib/gen2/helpers.zig
CHANGED
|
@@ -2,7 +2,6 @@ const std = @import("std");
|
|
|
2
2
|
|
|
3
3
|
const common = @import("../common/data.zig");
|
|
4
4
|
const options = @import("../common/options.zig");
|
|
5
|
-
const protocol = @import("../common/protocol.zig");
|
|
6
5
|
const rng = @import("../common/rng.zig");
|
|
7
6
|
|
|
8
7
|
const gen1 = @import("../gen1/data.zig");
|
|
@@ -11,8 +10,6 @@ const data = @import("data.zig");
|
|
|
11
10
|
|
|
12
11
|
const assert = std.debug.assert;
|
|
13
12
|
|
|
14
|
-
const expectEqual = std.testing.expectEqual;
|
|
15
|
-
|
|
16
13
|
const Choice = common.Choice;
|
|
17
14
|
|
|
18
15
|
const showdown = options.showdown;
|
|
@@ -25,7 +22,6 @@ const Move = data.Move;
|
|
|
25
22
|
const MoveSlot = data.MoveSlot;
|
|
26
23
|
const Species = data.Species;
|
|
27
24
|
const Stats = data.Stats;
|
|
28
|
-
const Status = data.Status;
|
|
29
25
|
|
|
30
26
|
const Struct = if (@hasField(std.builtin.Type, "struct")) .@"struct" else .Struct;
|
|
31
27
|
|
|
@@ -6,7 +6,6 @@ const common = @import("../common/data.zig");
|
|
|
6
6
|
const DEBUG = @import("../common/debug.zig").print;
|
|
7
7
|
const protocol = @import("../common/protocol.zig");
|
|
8
8
|
const rng = @import("../common/rng.zig");
|
|
9
|
-
const util = @import("../common/util.zig");
|
|
10
9
|
|
|
11
10
|
const data = @import("data.zig");
|
|
12
11
|
const generated = @import("generated.zig");
|
|
@@ -34,17 +33,12 @@ const Start = protocol.Start;
|
|
|
34
33
|
|
|
35
34
|
const Gen12 = rng.Gen12;
|
|
36
35
|
|
|
37
|
-
const PointerType = util.PointerType;
|
|
38
|
-
|
|
39
36
|
const ActivePokemon = data.ActivePokemon;
|
|
40
37
|
const Boosts = data.Boosts;
|
|
41
38
|
const Effectiveness = data.Effectiveness;
|
|
42
39
|
const Item = data.Item;
|
|
43
40
|
const Move = data.Move;
|
|
44
|
-
const MoveSlot = data.MoveSlot;
|
|
45
|
-
const Pokemon = data.Pokemon;
|
|
46
41
|
const Side = data.Side;
|
|
47
|
-
const Species = data.Species;
|
|
48
42
|
const Stats = data.Stats;
|
|
49
43
|
const Status = data.Status;
|
|
50
44
|
const TriAttack = data.TriAttack;
|
|
@@ -2901,7 +2895,7 @@ pub const Rolls = struct {
|
|
|
2901
2895
|
fn damage(battle: anytype, player: Player, i: u3, options: anytype) !u8 {
|
|
2902
2896
|
_ = .{ player, options };
|
|
2903
2897
|
const roll = if (options.calc.overridden(player, i, .damages)) |val|
|
|
2904
|
-
|
|
2898
|
+
val
|
|
2905
2899
|
else roll: {
|
|
2906
2900
|
if (showdown) break :roll battle.rng.range(u8, 217, 256);
|
|
2907
2901
|
while (true) {
|
|
@@ -3357,11 +3351,11 @@ pub const Rolls = struct {
|
|
|
3357
3351
|
}
|
|
3358
3352
|
|
|
3359
3353
|
fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
|
|
3360
|
-
const duration:
|
|
3354
|
+
const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
|
|
3361
3355
|
@intCast(val)
|
|
3362
3356
|
else if (showdown)
|
|
3363
3357
|
// Pokémon Showdown incorrectly inherits Generation III's Disable duration
|
|
3364
|
-
battle.rng.range(
|
|
3358
|
+
battle.rng.range(u3, 2, 6)
|
|
3365
3359
|
else duration: {
|
|
3366
3360
|
while (true) {
|
|
3367
3361
|
const r = battle.rng.next() & 7;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** @license MIT modified from Vadim Demedes's https://github.com/vadimdemedes/dom-chef */
|
|
2
|
+
type Attributes = JSX.IntrinsicElements['div'];
|
|
3
|
+
type DocumentFragmentConstructor = typeof DocumentFragment;
|
|
4
|
+
type ElementFunction = (props?: any) => HTMLElement;
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
namespace JSX {
|
|
8
|
+
interface Element extends HTMLElement, DocumentFragment {
|
|
9
|
+
addEventListener: HTMLElement['addEventListener'];
|
|
10
|
+
removeEventListener: HTMLElement['removeEventListener'];
|
|
11
|
+
className: HTMLElement['className'];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface JSXElementClassDocumentFragment extends DocumentFragment, JSX.ElementClass { }
|
|
17
|
+
interface Fragment {
|
|
18
|
+
prototype: JSXElementClassDocumentFragment;
|
|
19
|
+
new(): JSXElementClassDocumentFragment;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// https://github.com/preactjs/preact/blob/1bbd687c/src/constants.js#L3
|
|
23
|
+
const IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;
|
|
24
|
+
|
|
25
|
+
const isFragment = (type: DocumentFragmentConstructor | ElementFunction):
|
|
26
|
+
type is DocumentFragmentConstructor => type === DocumentFragment;
|
|
27
|
+
|
|
28
|
+
const setCSSProps = (element: HTMLElement, style: CSSStyleDeclaration) => {
|
|
29
|
+
for (const [name, value] of Object.entries(style)) {
|
|
30
|
+
if (name.startsWith('-')) {
|
|
31
|
+
element.style.setProperty(name, value);
|
|
32
|
+
} else if (typeof value === 'number' && !IS_NON_DIMENSIONAL.test(name)) {
|
|
33
|
+
element.style[name as any] = `${value as string}px`;
|
|
34
|
+
} else {
|
|
35
|
+
element.style[name as any] = value;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const setAttribute = (element: HTMLElement, name: string, value: string) => {
|
|
41
|
+
if (value !== undefined && value !== null) element.setAttribute(name, value);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const addChildren = (parent: Element | DocumentFragment, children: Node[]) => {
|
|
45
|
+
for (const child of children) {
|
|
46
|
+
if (child instanceof Node) {
|
|
47
|
+
parent.appendChild(child);
|
|
48
|
+
} else if (Array.isArray(child)) {
|
|
49
|
+
addChildren(parent, child);
|
|
50
|
+
} else if (typeof child !== 'boolean' && typeof child !== 'undefined' && child !== null) {
|
|
51
|
+
parent.appendChild(document.createTextNode(child));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// https://github.com/facebook/react/blob/3f899089/packages/react-dom/src/shared/DOMProperty.js#L288-L322
|
|
57
|
+
const FALSIFIABLE_ATTRIBUTES = ['contentEditable', 'draggable', 'spellCheck', 'value'];
|
|
58
|
+
|
|
59
|
+
export const h = (
|
|
60
|
+
type: DocumentFragmentConstructor | ElementFunction | string,
|
|
61
|
+
attributes?: Attributes,
|
|
62
|
+
...children: Node[]
|
|
63
|
+
) => {
|
|
64
|
+
if (typeof type !== 'string') {
|
|
65
|
+
const element = isFragment(type) ? document.createDocumentFragment() : type(attributes);
|
|
66
|
+
addChildren(element, children);
|
|
67
|
+
return element;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const element = document.createElement(type);
|
|
71
|
+
addChildren(element, children);
|
|
72
|
+
if (!attributes) return element;
|
|
73
|
+
|
|
74
|
+
for (let [name, value] of Object.entries(attributes)) {
|
|
75
|
+
if (name === 'htmlFor') name = 'for';
|
|
76
|
+
|
|
77
|
+
if (name === 'class' || name === 'className') {
|
|
78
|
+
const existingClassname = element.getAttribute('class') ?? '';
|
|
79
|
+
setAttribute(element, 'class', (existingClassname + ' ' + String(value)).trim());
|
|
80
|
+
} else if (name === 'style') {
|
|
81
|
+
setCSSProps(element, value);
|
|
82
|
+
} else if (name.startsWith('on')) {
|
|
83
|
+
const eventName = name.slice(2).toLowerCase().replace(/^-/, '');
|
|
84
|
+
element.addEventListener(eventName, value);
|
|
85
|
+
} else if (name === 'dangerouslySetInnerHTML' && '__html' in value) {
|
|
86
|
+
element.innerHTML = value.__html;
|
|
87
|
+
} else if (name !== 'key' && (FALSIFIABLE_ATTRIBUTES.includes(name) || value !== false)) {
|
|
88
|
+
setAttribute(element, name, value === true ? '' : value);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return element;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const Fragment =
|
|
96
|
+
(typeof DocumentFragment === 'function' ? DocumentFragment : () => {}) as Fragment;
|