@pkmn/engine 0.1.0-dev.04c4d944 → 0.1.0-dev.1336408
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/README.md +3 -3
- package/build/pkg/data/ids.json +12 -12
- package/build/pkg/data/protocol.json +69 -12
- package/build/pkg/data.d.ts +2 -2
- package/build/pkg/protocol.js +56 -37
- package/build/pkg/protocol.js.map +1 -1
- package/build.zig +7 -3
- package/package.json +20 -11
- package/src/bin/install-pkmn-engine +1 -1
- package/src/lib/binding/node.zig +1 -1
- package/src/lib/common/data.zig +4 -16
- package/src/lib/common/optional.zig +8 -1
- package/src/lib/common/protocol.zig +537 -79
- package/src/lib/common/rational.zig +1 -1
- package/src/lib/common/rng.zig +6 -6
- package/src/lib/gen1/README.md +3 -1
- package/src/lib/gen1/calc.zig +2 -3
- package/src/lib/gen1/chance.zig +51 -35
- package/src/lib/gen1/data/moves.zig +182 -170
- package/src/lib/gen1/data/types.zig +2 -2
- package/src/lib/gen1/data.zig +11 -23
- package/src/lib/gen1/helpers.zig +13 -28
- package/src/lib/gen1/mechanics.zig +61 -98
- package/src/lib/gen2/README.md +94 -72
- package/src/lib/gen2/data/items.zig +10 -16
- package/src/lib/gen2/data/moves.zig +659 -565
- package/src/lib/gen2/data/types.zig +127 -25
- package/src/lib/gen2/data.zig +100 -63
- package/src/lib/gen2/helpers.zig +8 -12
- package/src/lib/gen2/mechanics.zig +2608 -7
|
@@ -7,8 +7,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
9
|
|
|
10
|
-
const Action = @import("chance.zig").Action;
|
|
11
|
-
|
|
12
10
|
const data = @import("data.zig");
|
|
13
11
|
|
|
14
12
|
const assert = std.debug.assert;
|
|
@@ -59,23 +57,15 @@ const BOOSTS = &[_][2]u8{
|
|
|
59
57
|
|
|
60
58
|
const MAX_STAT_VALUE = 999;
|
|
61
59
|
|
|
62
|
-
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
63
|
-
|
|
64
60
|
pub fn update(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Result {
|
|
65
|
-
// FIXME
|
|
66
|
-
// std.debug.print("\n", .{});
|
|
67
|
-
// DEBUG(battle.turn);
|
|
68
|
-
// std.debug.print("// ", .{});
|
|
69
61
|
assert(c1.type != .Pass or c2.type != .Pass or battle.turn == 0);
|
|
70
62
|
if (battle.turn == 0) return start(battle, options);
|
|
71
63
|
|
|
72
64
|
var s1 = false;
|
|
73
65
|
var s2 = false;
|
|
74
|
-
var f1: ?Move = null;
|
|
75
|
-
var f2: ?Move = null;
|
|
76
66
|
|
|
77
|
-
if (selectMove(battle, .P1, c1, c2, &s1
|
|
78
|
-
if (selectMove(battle, .P2, c2, c1, &s2
|
|
67
|
+
if (selectMove(battle, .P1, c1, c2, &s1)) |r| return r;
|
|
68
|
+
if (selectMove(battle, .P2, c2, c1, &s2)) |r| return r;
|
|
79
69
|
|
|
80
70
|
var p1 = battle.side(.P1);
|
|
81
71
|
var p2 = battle.side(.P2);
|
|
@@ -84,9 +74,9 @@ pub fn update(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Result
|
|
|
84
74
|
var r2 = showdown and p2.active.volatiles.Binding and c1.type == .Switch;
|
|
85
75
|
|
|
86
76
|
if (try turnOrder(battle, c1, c2, options) == .P1) {
|
|
87
|
-
if (try doTurn(battle, .P1, c1, r1, s1,
|
|
77
|
+
if (try doTurn(battle, .P1, c1, r1, s1, .P2, c2, r2, s2, options)) |r| return r;
|
|
88
78
|
} else {
|
|
89
|
-
if (try doTurn(battle, .P2, c2, r2, s2,
|
|
79
|
+
if (try doTurn(battle, .P2, c2, r2, s2, .P1, c1, r1, s1, options)) |r| return r;
|
|
90
80
|
}
|
|
91
81
|
|
|
92
82
|
if (p1.active.volatiles.attacks == 0) p1.active.volatiles.Binding = false;
|
|
@@ -124,7 +114,6 @@ fn selectMove(
|
|
|
124
114
|
choice: Choice,
|
|
125
115
|
foe_choice: Choice,
|
|
126
116
|
skip_turn: *bool,
|
|
127
|
-
from: *?Move,
|
|
128
117
|
) ?Result {
|
|
129
118
|
if (choice.type == .Pass) return null;
|
|
130
119
|
|
|
@@ -141,7 +130,6 @@ fn selectMove(
|
|
|
141
130
|
return null;
|
|
142
131
|
}
|
|
143
132
|
if (volatiles.Rage) {
|
|
144
|
-
from.* = side.last_used_move;
|
|
145
133
|
if (showdown) {
|
|
146
134
|
if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
|
|
147
135
|
saveMove(battle, player, null);
|
|
@@ -151,7 +139,6 @@ fn selectMove(
|
|
|
151
139
|
// Pokémon Showdown removes Flinch at the end-of-turn in its residual handler
|
|
152
140
|
if (!showdown) volatiles.Flinch = false;
|
|
153
141
|
if (volatiles.Thrashing or volatiles.Charging) {
|
|
154
|
-
from.* = side.last_selected_move;
|
|
155
142
|
if (showdown) {
|
|
156
143
|
if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
|
|
157
144
|
saveMove(battle, player, null);
|
|
@@ -172,9 +159,7 @@ fn selectMove(
|
|
|
172
159
|
return null;
|
|
173
160
|
}
|
|
174
161
|
if (volatiles.Binding) {
|
|
175
|
-
from.* = side.last_used_move;
|
|
176
162
|
if (showdown) {
|
|
177
|
-
if (foe_choice.type == .Switch) from.* = null;
|
|
178
163
|
// Pokémon Showdown overwrites Mirror Move with whatever was selected - really this
|
|
179
164
|
// should set side.last_selected_move = last.id to reuse Mirror Move and fail in order
|
|
180
165
|
// to satisfy the conditions of the Desync Clause Mod. However, because Binding is still
|
|
@@ -214,12 +199,9 @@ fn selectMove(
|
|
|
214
199
|
}
|
|
215
200
|
break :ok true;
|
|
216
201
|
};
|
|
217
|
-
|
|
218
202
|
assert(struggle);
|
|
219
|
-
saveMove(battle, player, choice);
|
|
220
|
-
} else {
|
|
221
|
-
saveMove(battle, player, choice);
|
|
222
203
|
}
|
|
204
|
+
saveMove(battle, player, choice);
|
|
223
205
|
|
|
224
206
|
return null;
|
|
225
207
|
}
|
|
@@ -343,12 +325,10 @@ fn doTurn(
|
|
|
343
325
|
player_choice: Choice,
|
|
344
326
|
player_rewrap: bool,
|
|
345
327
|
player_skip: bool,
|
|
346
|
-
player_from: ?Move,
|
|
347
328
|
foe_player: Player,
|
|
348
329
|
foe_choice: Choice,
|
|
349
330
|
foe_rewrap: bool,
|
|
350
331
|
foe_skip: bool,
|
|
351
|
-
foe_from: ?Move,
|
|
352
332
|
options: anytype,
|
|
353
333
|
) !?Result {
|
|
354
334
|
assert(player_choice.type != .Pass);
|
|
@@ -361,7 +341,6 @@ fn doTurn(
|
|
|
361
341
|
player_choice,
|
|
362
342
|
player_rewrap,
|
|
363
343
|
player_skip,
|
|
364
|
-
player_from,
|
|
365
344
|
&residual,
|
|
366
345
|
options,
|
|
367
346
|
)) |r| return r;
|
|
@@ -384,7 +363,6 @@ fn doTurn(
|
|
|
384
363
|
foe_choice,
|
|
385
364
|
foe_rewrap,
|
|
386
365
|
foe_skip,
|
|
387
|
-
foe_from,
|
|
388
366
|
&residual,
|
|
389
367
|
options,
|
|
390
368
|
)) |r| return r;
|
|
@@ -412,7 +390,6 @@ fn executeMove(
|
|
|
412
390
|
choice: Choice,
|
|
413
391
|
rewrap: bool,
|
|
414
392
|
skip: bool,
|
|
415
|
-
from: ?Move,
|
|
416
393
|
residual: *bool,
|
|
417
394
|
options: anytype,
|
|
418
395
|
) !?Result {
|
|
@@ -447,8 +424,10 @@ fn executeMove(
|
|
|
447
424
|
mslot = @intCast(battle.lastMove(player).index);
|
|
448
425
|
const stored = side.stored();
|
|
449
426
|
// GLITCH: Struggle bypass PP underflow via Hyper Beam / Trapping-switch auto selection
|
|
450
|
-
auto = side.
|
|
451
|
-
|
|
427
|
+
auto = isForced(&side.active) or
|
|
428
|
+
side.active.volatiles.Binding or side.active.volatiles.Bide or
|
|
429
|
+
side.last_selected_move == .HyperBeam or
|
|
430
|
+
Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP);
|
|
452
431
|
// If it wasn't Hyper Beam or the continuation of a move effect then we must have just
|
|
453
432
|
// thawed, in which case we will desync unless the last_selected_move happened to be at
|
|
454
433
|
// index 1 and the current Pokémon has the same move in its first slot.
|
|
@@ -470,7 +449,7 @@ fn executeMove(
|
|
|
470
449
|
|
|
471
450
|
var skip_can = false;
|
|
472
451
|
var skip_pp = false;
|
|
473
|
-
switch (try beforeMove(battle, player, skip,
|
|
452
|
+
switch (try beforeMove(battle, player, skip, residual, options)) {
|
|
474
453
|
.done => return null,
|
|
475
454
|
.skip_can => skip_can = true,
|
|
476
455
|
.skip_pp => skip_pp = true,
|
|
@@ -478,9 +457,9 @@ fn executeMove(
|
|
|
478
457
|
.err => return @as(?Result, Result.Error),
|
|
479
458
|
}
|
|
480
459
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
460
|
+
const can = skip_can or
|
|
461
|
+
try canMove(battle, player, mslot, auto, skip_pp, .None, residual, options);
|
|
462
|
+
if (!can) return null;
|
|
484
463
|
|
|
485
464
|
return doMove(battle, player, mslot, rewrap, auto, residual, options);
|
|
486
465
|
}
|
|
@@ -491,7 +470,6 @@ fn beforeMove(
|
|
|
491
470
|
battle: anytype,
|
|
492
471
|
player: Player,
|
|
493
472
|
skip: bool,
|
|
494
|
-
from: ?Move,
|
|
495
473
|
residual: *bool,
|
|
496
474
|
options: anytype,
|
|
497
475
|
) !BeforeMove {
|
|
@@ -698,7 +676,6 @@ fn beforeMove(
|
|
|
698
676
|
|
|
699
677
|
if (volatiles.Thrashing) {
|
|
700
678
|
assert(volatiles.attacks > 0);
|
|
701
|
-
assert(from != null);
|
|
702
679
|
|
|
703
680
|
if (options.calc.modify(player, .attacking)) |extend| {
|
|
704
681
|
if (!extend) volatiles.attacks = 0;
|
|
@@ -710,7 +687,7 @@ fn beforeMove(
|
|
|
710
687
|
if (!showdown and handleThrashing(battle, active, player, options)) {
|
|
711
688
|
try log.start(battle.active(player), .ConfusionSilent);
|
|
712
689
|
}
|
|
713
|
-
try log.move(ident, side.last_selected_move, battle.active(player.foe())
|
|
690
|
+
try log.move(ident, side.last_selected_move, battle.active(player.foe()));
|
|
714
691
|
if (showdown) {
|
|
715
692
|
// This shouldn't actually set last_used_move, but Pokémon Showdown sets last
|
|
716
693
|
// used in useMove and doesn't have the notion of skipping canMove semantics
|
|
@@ -732,12 +709,7 @@ fn beforeMove(
|
|
|
732
709
|
}
|
|
733
710
|
try options.chance.binding(player, volatiles.attacks);
|
|
734
711
|
|
|
735
|
-
try log.move(
|
|
736
|
-
battle.active(player),
|
|
737
|
-
side.last_selected_move,
|
|
738
|
-
battle.active(player.foe()),
|
|
739
|
-
side.last_selected_move,
|
|
740
|
-
);
|
|
712
|
+
try log.move(battle.active(player), side.last_selected_move, battle.active(player.foe()));
|
|
741
713
|
if (showdown or battle.last_damage != 0) {
|
|
742
714
|
const sub = showdown and foe.active.volatiles.Substitute;
|
|
743
715
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, options);
|
|
@@ -757,7 +729,7 @@ fn canMove(
|
|
|
757
729
|
mslot: u4,
|
|
758
730
|
auto: bool,
|
|
759
731
|
skip_pp: bool,
|
|
760
|
-
from:
|
|
732
|
+
from: Move,
|
|
761
733
|
residual: *bool,
|
|
762
734
|
options: anytype,
|
|
763
735
|
) !bool {
|
|
@@ -765,33 +737,22 @@ fn canMove(
|
|
|
765
737
|
const player_ident = battle.active(player);
|
|
766
738
|
const move = Move.get(side.last_selected_move);
|
|
767
739
|
|
|
768
|
-
const foe_last = battle.foe(player).last_used_move;
|
|
769
|
-
const special = from != null and (from.? == .Metronome and
|
|
770
|
-
(from.? == .MirrorMove and !(foe_last == .None or foe_last == .MirrorMove)));
|
|
771
|
-
|
|
772
740
|
var skip = skip_pp;
|
|
773
741
|
if (side.active.volatiles.Charging) {
|
|
774
742
|
side.active.volatiles.Charging = false;
|
|
775
743
|
side.active.volatiles.Invulnerable = false;
|
|
776
744
|
} else if (move.effect == .Charge) {
|
|
777
|
-
try options.log.
|
|
745
|
+
try options.log.moveFrom(player_ident, side.last_selected_move, .{}, from);
|
|
778
746
|
setCounterable(battle, player, side, move);
|
|
779
747
|
try Effects.charge(battle, player, options);
|
|
780
748
|
return false;
|
|
781
749
|
}
|
|
782
750
|
|
|
783
|
-
|
|
751
|
+
side.last_used_move = side.last_selected_move;
|
|
784
752
|
if (!skip) decrementPP(side, mslot, auto);
|
|
785
753
|
|
|
786
754
|
const target = if (move.target == .Self) player else player.foe();
|
|
787
|
-
|
|
788
|
-
// don't have space to be able to track it in all circumstances and thus usually try to infer it
|
|
789
|
-
// from side.last_used_move. However, this gets reset by the opponent switching or fainting,
|
|
790
|
-
// meaning sometimes we lose this information. from is still set because it not being null is
|
|
791
|
-
// relevant for control flow, but we don't have anything useful to put in the log. This is
|
|
792
|
-
// regrettable, but this information is more "nice to have" than required
|
|
793
|
-
const f = if (from != null and from.? == .None) null else from;
|
|
794
|
-
try options.log.move(player_ident, side.last_selected_move, battle.active(target), f);
|
|
755
|
+
try options.log.moveFrom(player_ident, side.last_selected_move, battle.active(target), from);
|
|
795
756
|
setCounterable(battle, player, side, move);
|
|
796
757
|
|
|
797
758
|
if (move.effect.onBegin()) {
|
|
@@ -1438,7 +1399,7 @@ fn moveHit(
|
|
|
1438
1399
|
const overwritten = overwrite and state > 0;
|
|
1439
1400
|
assert(!overwritten or (0 < state and state <= 255 and !side.active.volatiles.Bide));
|
|
1440
1401
|
|
|
1441
|
-
var accuracy = if (overwritten) state else @as(u16,
|
|
1402
|
+
var accuracy = if (overwritten) state else @as(u16, move.accuracy);
|
|
1442
1403
|
var boost = BOOSTS[@as(u4, @intCast(@as(i8, side.active.boosts.accuracy) + 6))];
|
|
1443
1404
|
accuracy = accuracy * boost[0] / boost[1];
|
|
1444
1405
|
boost = BOOSTS[@as(u4, @intCast(@as(i8, -foe.active.boosts.evasion) + 6))];
|
|
@@ -1499,8 +1460,8 @@ fn checkFaint(
|
|
|
1499
1460
|
}
|
|
1500
1461
|
|
|
1501
1462
|
const foe_choice: Choice.Type = if (foe_fainted) .Switch else .Pass;
|
|
1502
|
-
if (player == .P1) return
|
|
1503
|
-
return
|
|
1463
|
+
if (player == .P1) return .{ .p1 = .Switch, .p2 = foe_choice };
|
|
1464
|
+
return .{ .p1 = foe_choice, .p2 = .Switch };
|
|
1504
1465
|
}
|
|
1505
1466
|
|
|
1506
1467
|
fn faint(battle: anytype, player: Player, done: bool, options: anytype) !?Result {
|
|
@@ -1548,21 +1509,19 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
1548
1509
|
var volatiles = &side.active.volatiles;
|
|
1549
1510
|
|
|
1550
1511
|
const brn = Status.is(stored.status, .BRN);
|
|
1551
|
-
if (brn or Status.is(stored.status, .PSN))
|
|
1512
|
+
if (brn or Status.is(stored.status, .PSN)) {
|
|
1552
1513
|
var damage = @max(stored.stats.hp / 16, 1);
|
|
1553
|
-
|
|
1554
1514
|
if (volatiles.Toxic) {
|
|
1555
1515
|
volatiles.toxic += 1;
|
|
1556
1516
|
damage *= volatiles.toxic;
|
|
1557
1517
|
}
|
|
1518
|
+
damage = @min(damage, stored.hp);
|
|
1558
1519
|
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
// Pokémon Showdown uses damageOf here but its not relevant in Generation I
|
|
1565
|
-
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
1520
|
+
if (!showdown or damage > 0) {
|
|
1521
|
+
stored.hp -= damage;
|
|
1522
|
+
// Pokémon Showdown uses damageOf here but its not relevant in Generation I
|
|
1523
|
+
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
1524
|
+
}
|
|
1566
1525
|
}
|
|
1567
1526
|
|
|
1568
1527
|
if (volatiles.LeechSeed) {
|
|
@@ -2395,12 +2354,17 @@ pub const Effects = struct {
|
|
|
2395
2354
|
}
|
|
2396
2355
|
|
|
2397
2356
|
fn switchAndTeleport(battle: anytype, player: Player, move: Move.Data, options: anytype) !void {
|
|
2398
|
-
if (!showdown or battle.side(player).last_selected_move == .Teleport) return;
|
|
2399
|
-
|
|
2400
2357
|
// Whirlwind/Roar should not roll to hit/reset damage but Pokémon Showdown does anyway
|
|
2401
|
-
if (
|
|
2402
|
-
|
|
2403
|
-
|
|
2358
|
+
if (showdown) {
|
|
2359
|
+
if (battle.side(player).last_selected_move == .Teleport) return;
|
|
2360
|
+
if (try checkHit(battle, player, move, options)) {
|
|
2361
|
+
try options.chance.commit(player, .hit);
|
|
2362
|
+
}
|
|
2363
|
+
battle.last_damage = 0;
|
|
2364
|
+
} else {
|
|
2365
|
+
try options.log.fail(battle.active(player), .None);
|
|
2366
|
+
try options.log.laststill();
|
|
2367
|
+
}
|
|
2404
2368
|
}
|
|
2405
2369
|
|
|
2406
2370
|
fn thrashing(battle: anytype, player: Player, options: anytype) void {
|
|
@@ -2705,15 +2669,15 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2705
2669
|
}
|
|
2706
2670
|
if (volatiles.Mist) {
|
|
2707
2671
|
volatiles.Mist = false;
|
|
2708
|
-
try log.end(ident, .
|
|
2672
|
+
try log.end(ident, .MistSilent);
|
|
2709
2673
|
}
|
|
2710
2674
|
if (volatiles.FocusEnergy) {
|
|
2711
2675
|
volatiles.FocusEnergy = false;
|
|
2712
|
-
try log.end(ident, .
|
|
2676
|
+
try log.end(ident, .FocusEnergySilent);
|
|
2713
2677
|
}
|
|
2714
2678
|
if (volatiles.LeechSeed) {
|
|
2715
2679
|
volatiles.LeechSeed = false;
|
|
2716
|
-
try log.end(ident, .
|
|
2680
|
+
try log.end(ident, .LeechSeedSilent);
|
|
2717
2681
|
}
|
|
2718
2682
|
if (!showdown and volatiles.Toxic) {
|
|
2719
2683
|
volatiles.Toxic = false;
|
|
@@ -2721,17 +2685,17 @@ fn clearVolatiles(battle: anytype, who: Player, clear: bool, options: anytype) !
|
|
|
2721
2685
|
}
|
|
2722
2686
|
if (volatiles.LightScreen) {
|
|
2723
2687
|
volatiles.LightScreen = false;
|
|
2724
|
-
try log.end(ident, .
|
|
2688
|
+
try log.end(ident, .LightScreenSilent);
|
|
2725
2689
|
}
|
|
2726
2690
|
if (volatiles.Reflect) {
|
|
2727
2691
|
volatiles.Reflect = false;
|
|
2728
|
-
try log.end(ident, .
|
|
2692
|
+
try log.end(ident, .ReflectSilent);
|
|
2729
2693
|
}
|
|
2730
2694
|
if (showdown and volatiles.Toxic) {
|
|
2731
2695
|
volatiles.Toxic = false;
|
|
2732
2696
|
// Pokémon Showdown erroneously clears the toxic counter
|
|
2733
2697
|
volatiles.toxic = 0;
|
|
2734
|
-
try log.end(ident, .
|
|
2698
|
+
try log.end(ident, .ToxicSilent);
|
|
2735
2699
|
}
|
|
2736
2700
|
}
|
|
2737
2701
|
|
|
@@ -2867,11 +2831,9 @@ pub const Rolls = struct {
|
|
|
2867
2831
|
inline fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
2868
2832
|
const move: Move = if (options.calc.overridden(player, .metronome)) |val|
|
|
2869
2833
|
val
|
|
2870
|
-
else if (showdown)
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
break :move @enumFromInt(r + mod);
|
|
2874
|
-
} else move: {
|
|
2834
|
+
else if (showdown)
|
|
2835
|
+
Move.METRONOME[battle.rng.range(u8, 0, Move.METRONOME.len)]
|
|
2836
|
+
else move: {
|
|
2875
2837
|
while (true) {
|
|
2876
2838
|
const r = battle.rng.next();
|
|
2877
2839
|
if (r == 0 or r == @intFromEnum(Move.Metronome)) continue;
|
|
@@ -2905,7 +2867,7 @@ pub const Rolls = struct {
|
|
|
2905
2867
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2906
2868
|
@intCast(val)
|
|
2907
2869
|
else if (showdown)
|
|
2908
|
-
|
|
2870
|
+
battle.rng.range(u3, 1, 8)
|
|
2909
2871
|
else duration: {
|
|
2910
2872
|
while (true) {
|
|
2911
2873
|
const r = battle.rng.next() & 7;
|
|
@@ -2922,7 +2884,7 @@ pub const Rolls = struct {
|
|
|
2922
2884
|
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2923
2885
|
val
|
|
2924
2886
|
else if (showdown)
|
|
2925
|
-
|
|
2887
|
+
battle.rng.range(u4, 1, 9)
|
|
2926
2888
|
else
|
|
2927
2889
|
@intCast((battle.rng.next() & 7) + 1);
|
|
2928
2890
|
|
|
@@ -2935,7 +2897,7 @@ pub const Rolls = struct {
|
|
|
2935
2897
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2936
2898
|
@intCast(val)
|
|
2937
2899
|
else if (showdown)
|
|
2938
|
-
|
|
2900
|
+
battle.rng.range(u3, 2, 6)
|
|
2939
2901
|
else
|
|
2940
2902
|
@intCast((battle.rng.next() & 3) + 2);
|
|
2941
2903
|
|
|
@@ -2948,7 +2910,7 @@ pub const Rolls = struct {
|
|
|
2948
2910
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2949
2911
|
@intCast(val)
|
|
2950
2912
|
else if (showdown)
|
|
2951
|
-
|
|
2913
|
+
battle.rng.range(u3, 2, 4)
|
|
2952
2914
|
else
|
|
2953
2915
|
@intCast((battle.rng.next() & 1) + 2);
|
|
2954
2916
|
|
|
@@ -2957,6 +2919,8 @@ pub const Rolls = struct {
|
|
|
2957
2919
|
return duration;
|
|
2958
2920
|
}
|
|
2959
2921
|
|
|
2922
|
+
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
2923
|
+
|
|
2960
2924
|
inline fn distribution(
|
|
2961
2925
|
battle: anytype,
|
|
2962
2926
|
comptime effect: Move.Effect,
|
|
@@ -2967,7 +2931,7 @@ pub const Rolls = struct {
|
|
|
2967
2931
|
const n: u3 = if (options.calc.overridden(player, roll)) |val|
|
|
2968
2932
|
@intCast(val)
|
|
2969
2933
|
else if (showdown)
|
|
2970
|
-
DISTRIBUTION[battle.rng.range(
|
|
2934
|
+
DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
|
|
2971
2935
|
else n: {
|
|
2972
2936
|
const r = (battle.rng.next() & 3);
|
|
2973
2937
|
break :n @intCast((if (r < 2) r else battle.rng.next() & 3) + 2);
|
|
@@ -3014,10 +2978,9 @@ pub const Rolls = struct {
|
|
|
3014
2978
|
while (i < moves.len and r > 0) : (i += 1) {
|
|
3015
2979
|
if (moves[i].pp > 0) {
|
|
3016
2980
|
r -= 1;
|
|
3017
|
-
if (r == 0) break;
|
|
2981
|
+
if (r == 0) break :slot @intCast(i + 1);
|
|
3018
2982
|
}
|
|
3019
2983
|
}
|
|
3020
|
-
break :slot @intCast(i + 1);
|
|
3021
2984
|
}
|
|
3022
2985
|
}
|
|
3023
2986
|
|
|
@@ -3039,11 +3002,11 @@ test "RNG agreement" {
|
|
|
3039
3002
|
expected[i] = @intCast(i * 0x1000000);
|
|
3040
3003
|
}
|
|
3041
3004
|
|
|
3042
|
-
var spe
|
|
3043
|
-
var cfz
|
|
3044
|
-
var par
|
|
3045
|
-
var brn
|
|
3046
|
-
var eff
|
|
3005
|
+
var spe: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3006
|
+
var cfz: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3007
|
+
var par: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3008
|
+
var brn: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3009
|
+
var eff: rng.FixedRNG(1, expected.len) = .{ .rolls = expected };
|
|
3047
3010
|
|
|
3048
3011
|
for (0..expected.len) |i| {
|
|
3049
3012
|
try expectEqual(spe.range(u8, 0, 2) == 0, i < Gen12.percent(50) + 1);
|
package/src/lib/gen2/README.md
CHANGED
|
@@ -32,25 +32,27 @@ The following information is required to simulate a Generation II Pokémon battl
|
|
|
32
32
|
| `pokemon.species` | `party_struct.Species` | `pokemon.baseSpecies` |
|
|
33
33
|
| `active.species` | `battle_struct.Species` | `pokemon.species` |
|
|
34
34
|
| `active.stats` | `TODO modified` | `pokemon.storedStats` |
|
|
35
|
-
| `active.trapped FIXME` | `PlayerTrappingMove` | `pokemon.trapped` |
|
|
36
|
-
| `active.switching` | `PlayerIsSwitching` | `pokemon.switchFlag` |
|
|
37
35
|
| `active.boosts` | `PlayerStatLevels` | `pokemon.boosts` |
|
|
38
36
|
| `active.volatiles` | `PlayerBattleStatus{1,2,3}` | `pokemon.volatiles` |
|
|
39
|
-
| `volatiles.
|
|
40
|
-
| `volatiles.
|
|
41
|
-
| `volatiles.
|
|
42
|
-
| `volatiles.
|
|
43
|
-
| `volatiles.
|
|
44
|
-
| `volatiles.
|
|
45
|
-
| `volatiles.
|
|
46
|
-
| `volatiles.
|
|
47
|
-
| `volatiles.
|
|
48
|
-
| `volatiles.
|
|
49
|
-
| `volatiles.
|
|
50
|
-
| `volatiles.
|
|
51
|
-
| `volatiles.
|
|
37
|
+
| `volatiles.bide` | `PlayerDamageTaken` | `volatiles.bide.totalDamage` |
|
|
38
|
+
| `volatiles.substitute` | `PlayerSubstituteHP` | `volatiles.substitute.hp` |
|
|
39
|
+
| `volatiles.rollout` | `PlayerRolloutCount` | `volatiles.rollout.hitCount` |
|
|
40
|
+
| `volatiles.confusion` | `PlayerConfuseCount` | `volatiles.confusion.duration` |
|
|
41
|
+
| `volatiles.toxic` | `PlayerToxicCount` | `volatiles.residualdmg.counter` |
|
|
42
|
+
| `volatiles.disabled` | `PlayerDisableCount` | `moveSlots.disabled` |
|
|
43
|
+
| `volatiles.encore` | `PlayerEncoreCount` | `volatiles.encore.duration` |
|
|
44
|
+
| `volatiles.perish_song` | `PlayerPerishCount` | `volatiles.perishsong.duration` |
|
|
45
|
+
| `volatiles.fury_cutter` | `PlayerFuryCutterCount` | `volatiles.furycutter.multiplier` |
|
|
46
|
+
| `volatiles.protect` | `PlayerProtectCount` | `volatiles.stall.counter` |
|
|
47
|
+
| `volatiles.future_sight` | `PlayerFutureSight{Damage,Count}` | `slotConditions[i].futuremove` |
|
|
48
|
+
| `volatiles.rage` | `PlayerRageCounter` | **BUG** |
|
|
49
|
+
| `volatiles.bind` | `PlayerWrapCount` | `volatiles.partiallytrapped.duration` |
|
|
50
|
+
| `volatiles.bind_reason` | `PlayerTrappingMove` | `volatiles.effectState.sourceEffect` |
|
|
52
51
|
| | `PlayerCharging` | |
|
|
53
|
-
|
|
|
52
|
+
| `volatiles.trapped FIXME` | `FIXME` | `pokemon.trapped` |
|
|
53
|
+
| `volatiles.switched` | `TODO` | `pokemon.switchFlag` |
|
|
54
|
+
| `volatiles.switching` | `PlayerIsSwitching` | `pokemon.switchFlag` |
|
|
55
|
+
| `volatiles.frozen` | `PlayerJustGotFrozen` | `volatiles.frozen` |
|
|
54
56
|
| | | |
|
|
55
57
|
| `active.last_used_move` | `PlayerUsedMove` | `side.lastMove` |
|
|
56
58
|
|
|
@@ -219,6 +221,81 @@ much overhead the preceding representations have after taking into consideration
|
|
|
219
221
|
padding](https://en.wikipedia.org/wiki/Data_structure_alignment) and
|
|
220
222
|
[denormalization](https://en.wikipedia.org/wiki/Denormalization):
|
|
221
223
|
|
|
224
|
+
## Details
|
|
225
|
+
|
|
226
|
+
| pkmn | Pokémon Crystal (pret) |
|
|
227
|
+
| ------------------------- | ------------------------------------------------- |
|
|
228
|
+
| `start` | `DoBattle` |
|
|
229
|
+
| `update` | `BattleTurn` |
|
|
230
|
+
| `selectMove` / `saveMove` | `CheckPlayerLockedIn` / `ParsePlayerAction` |
|
|
231
|
+
| `switchIn` | `SwitchPlayerMon` / `SendOutPlayerMon` |
|
|
232
|
+
| `turnOrder` | `DetermineMoveOrder` |
|
|
233
|
+
| `doTurn` | `DoTurn` |
|
|
234
|
+
| `executeMove` | `DoMove` |
|
|
235
|
+
| `beforeMove` | `CheckTurn` |
|
|
236
|
+
| `canMove` | |
|
|
237
|
+
| `decrementPP` | |
|
|
238
|
+
| `doMove` | |
|
|
239
|
+
| `checkCriticalHit` | `BattleCommand_Critical` |
|
|
240
|
+
| `calcDamage` | `BattleCommand_DamageCalc` / `PlayerAttackDamage` |
|
|
241
|
+
| `adjustDamage` | `BattleCommand_Stab` |
|
|
242
|
+
| `randomizeDamage` | `BattleCommand_DamageVariation` |
|
|
243
|
+
| `specialDamage` | `BattleCommand_ConstantDamage` |
|
|
244
|
+
| `applyDamage` | `BattleCommand_ApplyDamage` |
|
|
245
|
+
| `checkHit` | `BattleCommand_CheckHit` |
|
|
246
|
+
| `checkFaint` | `CheckFaint` |
|
|
247
|
+
| `handleResidual` | `ResidualDamage` |
|
|
248
|
+
| `endTurn` | `EndTurn` |
|
|
249
|
+
|
|
250
|
+
DoBattle
|
|
251
|
+
BattleTurn
|
|
252
|
+
- HandleBerserkGene
|
|
253
|
+
- CheckPlayerLockedIn
|
|
254
|
+
- BattleMenu
|
|
255
|
+
- ParsePlayerAction
|
|
256
|
+
- DetermineMoveOrder
|
|
257
|
+
- CompareMovePriority
|
|
258
|
+
- GetMovePriority
|
|
259
|
+
- Battle_*
|
|
260
|
+
- Handle*Switch
|
|
261
|
+
- SwitchPlayerMon
|
|
262
|
+
- BreakAttraction
|
|
263
|
+
- SendOutPlayerMon
|
|
264
|
+
- SpikesDamage
|
|
265
|
+
- *Turn_EndOpponentProtectEndureDestinyBond
|
|
266
|
+
- EndUserDestinyBond
|
|
267
|
+
- Do*Turn
|
|
268
|
+
- EndOpponentProtectEndureDestinyBond
|
|
269
|
+
- ResidualDamage
|
|
270
|
+
- CheckFaint_*
|
|
271
|
+
- HandleBetweenTurnEffects
|
|
272
|
+
- HandleFutureSight
|
|
273
|
+
- HandleWeather
|
|
274
|
+
- HandleWrap
|
|
275
|
+
- HandlePerishSong
|
|
276
|
+
- HandleLeftovers
|
|
277
|
+
- HandleMysteryBerry
|
|
278
|
+
- HandleDefrost
|
|
279
|
+
- HandleSafeguard
|
|
280
|
+
- HandleScreens
|
|
281
|
+
- HandleStatBoostingHeldItems
|
|
282
|
+
- HandleHealingItems
|
|
283
|
+
- HandleEncore
|
|
284
|
+
|
|
285
|
+
DoTurn
|
|
286
|
+
DoMove = executeMove
|
|
287
|
+
CheckTurn = beforeMove
|
|
288
|
+
BattleCommand_Critical = checkCriticalHit
|
|
289
|
+
BattleCommand_Stab = adjustDamage
|
|
290
|
+
BattleCommand_DamageVariation = randomizeDamage
|
|
291
|
+
BattleCommand_CheckHit = checkHit
|
|
292
|
+
BattleCommand_ApplyDamage = applyDamage
|
|
293
|
+
BattleCommand_CheckFaint
|
|
294
|
+
BattleCommand_BuildOpponentRage = buildRage
|
|
295
|
+
BattleCommand_DamageCalc (PlayerAttackDamage / EnemyAttackDamage) = calcDamage
|
|
296
|
+
BattleCommand_ConstantDamage = specialDamage
|
|
297
|
+
DoEnemyDamage / DoPlayerDamage / DoSubstituteDamage
|
|
298
|
+
|
|
222
299
|
==**TODO**==
|
|
223
300
|
|
|
224
301
|
## Protocol
|
|
@@ -226,62 +303,7 @@ padding](https://en.wikipedia.org/wiki/Data_structure_alignment) and
|
|
|
226
303
|
TODO
|
|
227
304
|
|
|
228
305
|
```txt
|
|
229
|
-
-activate: _,
|
|
230
|
-
-anim: _
|
|
231
|
-
-block: _
|
|
232
|
-
-boost: _, from, silent
|
|
233
|
-
-clearallboost: _
|
|
234
|
-
-copyboost: from
|
|
235
|
-
-crit: _
|
|
236
|
-
-curestatus: msg
|
|
237
|
-
-cureteam: from
|
|
238
|
-
-damage: _, from, of, partiallytrapped
|
|
239
|
-
-end: _, from, of, partiallytrapped, silent
|
|
240
|
-
-enditem: _, eat
|
|
241
|
-
-fail: _, block, weak
|
|
242
|
-
-fieldactivate: _
|
|
243
|
-
-heal: _, from, of, silent
|
|
244
|
-
-hint: _
|
|
245
|
-
-hitcount: _
|
|
246
|
-
-immune: _, ohko
|
|
247
|
-
-item: from, of
|
|
248
|
-
-miss: _
|
|
249
|
-
-mustrecharge: _
|
|
250
|
-
-ohko: _
|
|
251
|
-
-prepare: _
|
|
252
|
-
-resisted: _
|
|
253
|
-
-setboost: from
|
|
254
|
-
-sethp: from, silent
|
|
255
|
-
-sideend: _, from, of
|
|
256
|
-
-sidestart: _
|
|
257
|
-
-singlemove: _
|
|
258
|
-
-singleturn: _
|
|
259
|
-
-start: _, of, silent
|
|
260
|
-
-status: _, from, silent
|
|
261
|
-
-supereffective: _
|
|
262
|
-
-transform: _
|
|
263
|
-
-unboost: _
|
|
264
|
-
-weather: _, upkeep
|
|
265
|
-
cant: _
|
|
266
|
-
debug: _
|
|
267
|
-
done: _
|
|
268
|
-
drag: _
|
|
269
|
-
faint: _
|
|
270
|
-
gametype: _
|
|
271
|
-
gen: _
|
|
272
|
-
move: _, from, miss, still
|
|
273
|
-
player: _
|
|
274
|
-
request: _
|
|
275
|
-
rule: _
|
|
276
|
-
start: _
|
|
277
|
-
switch: _, from
|
|
278
|
-
t:: _
|
|
279
|
-
teamsize: _
|
|
280
|
-
tie: _
|
|
281
|
-
tier: _
|
|
282
|
-
turn: _
|
|
283
|
-
upkeep: _
|
|
284
|
-
win: _
|
|
306
|
+
-activate: _, move, name, number
|
|
285
307
|
```
|
|
286
308
|
|
|
287
309
|
## Bugs
|