@pkmn/engine 0.1.0-dev.3a273268 → 0.1.0-dev.47feedd9
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 +18 -18
- package/build/pkg/addon.d.ts +1 -1
- package/build/pkg/addon.js +4 -4
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/gen1.d.ts +3 -3
- package/build/pkg/gen1.js +10 -12
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/index.d.ts +11 -10
- package/build/pkg/index.js.map +1 -1
- package/build/test/display.js +24 -10
- package/build/test/display.js.map +1 -1
- package/build.zig +15 -22
- package/package.json +10 -9
- package/src/bin/install-pkmn-engine +3 -3
- package/src/lib/binding/node.zig +3 -3
- package/src/lib/binding/wasm.zig +1 -1
- package/src/lib/common/debug.zig +1 -0
- package/src/lib/common/options.zig +3 -3
- package/src/lib/common/protocol.zig +38 -38
- package/src/lib/gen1/README.md +19 -13
- package/src/lib/gen1/data.zig +1 -1
- package/src/lib/gen1/helpers.zig +10 -7
- package/src/lib/gen1/mechanics.zig +104 -58
- package/src/lib/pkmn.zig +4 -4
|
@@ -62,26 +62,28 @@ pub fn update(battle: anytype, c1: Choice, c2: Choice, log: anytype) !Result {
|
|
|
62
62
|
assert(c1.type != .Pass or c2.type != .Pass or battle.turn == 0);
|
|
63
63
|
if (battle.turn == 0) return start(battle, log);
|
|
64
64
|
|
|
65
|
+
var s1 = false;
|
|
66
|
+
var s2 = false;
|
|
65
67
|
var f1: ?Move = null;
|
|
66
68
|
var f2: ?Move = null;
|
|
67
69
|
|
|
68
|
-
if (selectMove(battle, .P1, c1, c2, &f1)) |r| return r;
|
|
69
|
-
if (selectMove(battle, .P2, c2, c1, &f2)) |r| return r;
|
|
70
|
+
if (selectMove(battle, .P1, c1, c2, &s1, &f1)) |r| return r;
|
|
71
|
+
if (selectMove(battle, .P2, c2, c1, &s2, &f2)) |r| return r;
|
|
72
|
+
|
|
73
|
+
var p1 = battle.side(.P1);
|
|
74
|
+
var p2 = battle.side(.P2);
|
|
75
|
+
|
|
76
|
+
var r1 = showdown and p1.active.volatiles.Binding and c2.type == .Switch;
|
|
77
|
+
var r2 = showdown and p2.active.volatiles.Binding and c1.type == .Switch;
|
|
70
78
|
|
|
71
79
|
if (turnOrder(battle, c1, c2) == .P1) {
|
|
72
|
-
if (try doTurn(battle, .P1, c1, f1, .P2, c2, f2, log)) |r| return r;
|
|
80
|
+
if (try doTurn(battle, .P1, c1, r1, s1, f1, .P2, c2, r2, s2, f2, log)) |r| return r;
|
|
73
81
|
} else {
|
|
74
|
-
if (try doTurn(battle, .P2, c2, f2, .P1, c1, f1, log)) |r| return r;
|
|
82
|
+
if (try doTurn(battle, .P2, c2, r2, s2, f2, .P1, c1, r1, s1, f1, log)) |r| return r;
|
|
75
83
|
}
|
|
76
84
|
|
|
77
|
-
|
|
78
|
-
if (
|
|
79
|
-
p1.active.volatiles.Binding = false;
|
|
80
|
-
}
|
|
81
|
-
var p2 = battle.side(.P2);
|
|
82
|
-
if (p2.active.volatiles.attacks == 0) {
|
|
83
|
-
p2.active.volatiles.Binding = false;
|
|
84
|
-
}
|
|
85
|
+
if (p1.active.volatiles.attacks == 0) p1.active.volatiles.Binding = false;
|
|
86
|
+
if (p2.active.volatiles.attacks == 0) p2.active.volatiles.Binding = false;
|
|
85
87
|
|
|
86
88
|
return endTurn(battle, log);
|
|
87
89
|
}
|
|
@@ -114,6 +116,7 @@ fn selectMove(
|
|
|
114
116
|
player: Player,
|
|
115
117
|
choice: Choice,
|
|
116
118
|
foe_choice: Choice,
|
|
119
|
+
skip_turn: *bool,
|
|
117
120
|
from: *?Move,
|
|
118
121
|
) ?Result {
|
|
119
122
|
if (choice.type == .Pass) return null;
|
|
@@ -129,14 +132,20 @@ fn selectMove(
|
|
|
129
132
|
if (volatiles.Recharging) return null;
|
|
130
133
|
if (volatiles.Rage) {
|
|
131
134
|
from.* = side.last_used_move;
|
|
132
|
-
if (showdown)
|
|
135
|
+
if (showdown) {
|
|
136
|
+
if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
|
|
137
|
+
saveMove(battle, player, null);
|
|
138
|
+
}
|
|
133
139
|
return null;
|
|
134
140
|
}
|
|
135
141
|
// Pokémon Showdown removes Flinch at the end-of-turn in its residual handler
|
|
136
142
|
if (!showdown) volatiles.Flinch = false;
|
|
137
143
|
if (volatiles.Thrashing or volatiles.Charging) {
|
|
138
144
|
from.* = side.last_selected_move;
|
|
139
|
-
if (showdown)
|
|
145
|
+
if (showdown) {
|
|
146
|
+
if (battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
|
|
147
|
+
saveMove(battle, player, null);
|
|
148
|
+
}
|
|
140
149
|
return null;
|
|
141
150
|
}
|
|
142
151
|
|
|
@@ -146,7 +155,10 @@ fn selectMove(
|
|
|
146
155
|
// pre-move select
|
|
147
156
|
if (Status.is(stored.status, .FRZ) or Status.is(stored.status, .SLP) or volatiles.Bide) {
|
|
148
157
|
assert(showdown or choice.data == 0);
|
|
149
|
-
if (showdown)
|
|
158
|
+
if (showdown) {
|
|
159
|
+
if (volatiles.Bide and battle.foe(player).active.volatiles.Binding) skip_turn.* = true;
|
|
160
|
+
saveMove(battle, player, choice);
|
|
161
|
+
}
|
|
150
162
|
return null;
|
|
151
163
|
}
|
|
152
164
|
if (volatiles.Binding) {
|
|
@@ -177,6 +189,7 @@ fn selectMove(
|
|
|
177
189
|
}
|
|
178
190
|
|
|
179
191
|
if (battle.foe(player).active.volatiles.Binding) {
|
|
192
|
+
skip_turn.* = true;
|
|
180
193
|
if (showdown) {
|
|
181
194
|
saveMove(battle, player, choice);
|
|
182
195
|
} else {
|
|
@@ -327,9 +340,13 @@ fn doTurn(
|
|
|
327
340
|
battle: anytype,
|
|
328
341
|
player: Player,
|
|
329
342
|
player_choice: Choice,
|
|
343
|
+
player_rewrap: bool,
|
|
344
|
+
player_skip: bool,
|
|
330
345
|
player_from: ?Move,
|
|
331
346
|
foe_player: Player,
|
|
332
347
|
foe_choice: Choice,
|
|
348
|
+
foe_rewrap: bool,
|
|
349
|
+
foe_skip: bool,
|
|
333
350
|
foe_from: ?Move,
|
|
334
351
|
log: anytype,
|
|
335
352
|
) !?Result {
|
|
@@ -337,7 +354,16 @@ fn doTurn(
|
|
|
337
354
|
|
|
338
355
|
var residual = true;
|
|
339
356
|
var replace = battle.side(player).stored().hp == 0;
|
|
340
|
-
if (try executeMove(
|
|
357
|
+
if (try executeMove(
|
|
358
|
+
battle,
|
|
359
|
+
player,
|
|
360
|
+
player_choice,
|
|
361
|
+
player_rewrap,
|
|
362
|
+
player_skip,
|
|
363
|
+
player_from,
|
|
364
|
+
&residual,
|
|
365
|
+
log,
|
|
366
|
+
)) |r| return r;
|
|
341
367
|
if (!replace) {
|
|
342
368
|
if (player_choice.type != .Switch) {
|
|
343
369
|
if (try checkFaint(battle, foe_player, log)) |r| return r;
|
|
@@ -348,7 +374,16 @@ fn doTurn(
|
|
|
348
374
|
|
|
349
375
|
residual = true;
|
|
350
376
|
replace = battle.side(foe_player).stored().hp == 0;
|
|
351
|
-
if (try executeMove(
|
|
377
|
+
if (try executeMove(
|
|
378
|
+
battle,
|
|
379
|
+
foe_player,
|
|
380
|
+
foe_choice,
|
|
381
|
+
foe_rewrap,
|
|
382
|
+
foe_skip,
|
|
383
|
+
foe_from,
|
|
384
|
+
&residual,
|
|
385
|
+
log,
|
|
386
|
+
)) |r| return r;
|
|
352
387
|
if (!replace) {
|
|
353
388
|
if (foe_choice.type != .Switch) {
|
|
354
389
|
if (try checkFaint(battle, player, log)) |r| return r;
|
|
@@ -371,6 +406,8 @@ fn executeMove(
|
|
|
371
406
|
battle: anytype,
|
|
372
407
|
player: Player,
|
|
373
408
|
choice: Choice,
|
|
409
|
+
rewrap: bool,
|
|
410
|
+
skip: bool,
|
|
374
411
|
from: ?Move,
|
|
375
412
|
residual: *bool,
|
|
376
413
|
log: anytype,
|
|
@@ -382,9 +419,10 @@ fn executeMove(
|
|
|
382
419
|
return null;
|
|
383
420
|
}
|
|
384
421
|
|
|
422
|
+
// This is the correct place to check for SKIP_TURN and abort early, however since Pokémon
|
|
423
|
+
// Showdown overwrites the SKIP_TURN sentinel with its botched move select we need to add an
|
|
424
|
+
// additional skip boolean to accomplish the same thing in the Binding check of BeforeMove
|
|
385
425
|
if (side.last_selected_move == .SKIP_TURN) {
|
|
386
|
-
// Pokémon Showdown overwrites the SKIP_TURN sentinel with its botched move select,
|
|
387
|
-
// Binding instead gets handled in beforeMove after sleep and freeze
|
|
388
426
|
assert(!showdown);
|
|
389
427
|
if (battle.foe(player).active.volatiles.Binding) {
|
|
390
428
|
try log.cant(battle.active(player), .Bound);
|
|
@@ -434,7 +472,7 @@ fn executeMove(
|
|
|
434
472
|
|
|
435
473
|
var skip_can = false;
|
|
436
474
|
var skip_pp = false;
|
|
437
|
-
switch (try beforeMove(battle, player, from, residual, log)) {
|
|
475
|
+
switch (try beforeMove(battle, player, skip, from, residual, log)) {
|
|
438
476
|
.done => return null,
|
|
439
477
|
.skip_can => skip_can = true,
|
|
440
478
|
.skip_pp => skip_pp = true,
|
|
@@ -446,14 +484,15 @@ fn executeMove(
|
|
|
446
484
|
return null;
|
|
447
485
|
}
|
|
448
486
|
|
|
449
|
-
return doMove(battle, player, mslot, auto, residual, log);
|
|
487
|
+
return doMove(battle, player, mslot, rewrap, auto, residual, log);
|
|
450
488
|
}
|
|
451
489
|
|
|
452
|
-
const BeforeMove =
|
|
490
|
+
const BeforeMove = enum { done, skip_can, skip_pp, ok, err };
|
|
453
491
|
|
|
454
492
|
fn beforeMove(
|
|
455
493
|
battle: anytype,
|
|
456
494
|
player: Player,
|
|
495
|
+
skip: bool,
|
|
457
496
|
from: ?Move,
|
|
458
497
|
residual: *bool,
|
|
459
498
|
log: anytype,
|
|
@@ -485,13 +524,15 @@ fn beforeMove(
|
|
|
485
524
|
return .done;
|
|
486
525
|
}
|
|
487
526
|
|
|
488
|
-
if (foe.active.volatiles.Binding) {
|
|
527
|
+
if (skip or foe.active.volatiles.Binding) {
|
|
489
528
|
try log.cant(ident, .Bound);
|
|
490
529
|
return .done;
|
|
491
530
|
}
|
|
492
531
|
|
|
493
|
-
if (
|
|
494
|
-
|
|
532
|
+
if (volatiles.Flinch) {
|
|
533
|
+
// Pokémon Showdown doesn't clear Flinch until its imaginary "residual" phase, meaning
|
|
534
|
+
// Pokémon can sometimes flinch multiple times from the same original hit
|
|
535
|
+
if (!showdown) volatiles.Flinch = false;
|
|
495
536
|
try log.cant(ident, .Flinch);
|
|
496
537
|
return .done;
|
|
497
538
|
}
|
|
@@ -512,13 +553,6 @@ fn beforeMove(
|
|
|
512
553
|
// Pokémon Showdown's disable condition has a single onBeforeMove handler
|
|
513
554
|
if (showdown and try disabled(side, ident, log)) return .done;
|
|
514
555
|
|
|
515
|
-
// Pokémon Showdown checks for Flinch *after* instead of before
|
|
516
|
-
if (showdown and volatiles.Flinch) {
|
|
517
|
-
volatiles.Flinch = false;
|
|
518
|
-
try log.cant(ident, .Flinch);
|
|
519
|
-
return .done;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
556
|
// This can only happen if a Pokémon started the battle frozen/sleeping and was thawed/woken
|
|
523
557
|
// before the side had a selected a move - we simply need to assume this leads to a desync
|
|
524
558
|
if (side.last_selected_move == .None) {
|
|
@@ -634,8 +668,13 @@ fn beforeMove(
|
|
|
634
668
|
try log.start(battle.active(player), .ConfusionSilent);
|
|
635
669
|
}
|
|
636
670
|
try log.move(ident, side.last_selected_move, battle.active(player.foe()), from);
|
|
637
|
-
if (showdown
|
|
638
|
-
|
|
671
|
+
if (showdown) {
|
|
672
|
+
// This shouldn't actually set last_used_move, but Pokémon Showdown sets last
|
|
673
|
+
// used in useMove and doesn't have the notion of skipping canMove semantics
|
|
674
|
+
side.last_used_move = side.last_selected_move;
|
|
675
|
+
if (handleThrashing(battle, active)) {
|
|
676
|
+
try log.start(battle.active(player), .ConfusionSilent);
|
|
677
|
+
}
|
|
639
678
|
}
|
|
640
679
|
return .skip_can;
|
|
641
680
|
}
|
|
@@ -650,7 +689,11 @@ fn beforeMove(
|
|
|
650
689
|
side.last_selected_move,
|
|
651
690
|
);
|
|
652
691
|
if (showdown or battle.last_damage != 0) {
|
|
692
|
+
const sub = showdown and foe.active.volatiles.Substitute;
|
|
653
693
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, log);
|
|
694
|
+
if (battle.foe(player).stored().hp > 0 and !sub) {
|
|
695
|
+
try buildRage(battle, player.foe(), log);
|
|
696
|
+
}
|
|
654
697
|
}
|
|
655
698
|
return .done;
|
|
656
699
|
}
|
|
@@ -708,7 +751,7 @@ fn canMove(
|
|
|
708
751
|
Effects.thrashing(battle, player);
|
|
709
752
|
} else if (!showdown and move.effect == .Binding) {
|
|
710
753
|
// Pokémon Showdown handles this after hit/miss checks and damage calculation
|
|
711
|
-
Effects.binding(battle, player);
|
|
754
|
+
Effects.binding(battle, player, false);
|
|
712
755
|
}
|
|
713
756
|
|
|
714
757
|
return true;
|
|
@@ -749,6 +792,7 @@ fn doMove(
|
|
|
749
792
|
battle: anytype,
|
|
750
793
|
player: Player,
|
|
751
794
|
mslot: u4,
|
|
795
|
+
rewrap: bool,
|
|
752
796
|
auto: bool,
|
|
753
797
|
residual: *bool,
|
|
754
798
|
log: anytype,
|
|
@@ -840,9 +884,9 @@ fn doMove(
|
|
|
840
884
|
|
|
841
885
|
if (!showdown or !miss) {
|
|
842
886
|
if (move.effect == .MirrorMove) {
|
|
843
|
-
return mirrorMove(battle, player, mslot, auto, residual, log);
|
|
887
|
+
return mirrorMove(battle, player, mslot, rewrap, auto, residual, log);
|
|
844
888
|
} else if (move.effect == .Metronome) {
|
|
845
|
-
return metronome(battle, player, mslot, auto, residual, log);
|
|
889
|
+
return metronome(battle, player, mslot, rewrap, auto, residual, log);
|
|
846
890
|
} else if (move.effect.onEnd()) {
|
|
847
891
|
try onEnd(battle, player, move, log);
|
|
848
892
|
return null;
|
|
@@ -857,7 +901,7 @@ fn doMove(
|
|
|
857
901
|
move.effect == .OHKO and side.active.stats.spe < foe.active.stats.spe;
|
|
858
902
|
if (ohko) {
|
|
859
903
|
try log.immune(foe_ident, .OHKO);
|
|
860
|
-
} else if (immune and !invulnerable) {
|
|
904
|
+
} else if (immune and !invulnerable and (!showdown or move.effect != .Binding)) {
|
|
861
905
|
try log.immune(foe_ident, .None);
|
|
862
906
|
} else if (mist) {
|
|
863
907
|
if (!foe.active.volatiles.Substitute) try log.activate(foe_ident, .Mist);
|
|
@@ -923,7 +967,7 @@ fn doMove(
|
|
|
923
967
|
} else if (showdown) {
|
|
924
968
|
// This should be handled much earlier but Pokémon Showdown does it here... ¯\_(ツ)_/¯
|
|
925
969
|
if (move.effect == .Binding) {
|
|
926
|
-
Effects.binding(battle, player);
|
|
970
|
+
Effects.binding(battle, player, rewrap);
|
|
927
971
|
if (immune) {
|
|
928
972
|
battle.last_damage = 0;
|
|
929
973
|
// Pokémon Showdown logs |-damage| here instead of |-immune| because logic...
|
|
@@ -1160,7 +1204,9 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, log: anytype)
|
|
|
1160
1204
|
// Pokémon Showdown calls checkHit before Counter
|
|
1161
1205
|
if (!showdown and !try checkHit(battle, player, move, log)) return null;
|
|
1162
1206
|
|
|
1207
|
+
const sub = showdown and foe.active.volatiles.Substitute;
|
|
1163
1208
|
_ = try applyDamage(battle, player.foe(), player.foe(), .None, log);
|
|
1209
|
+
if (battle.foe(player).stored().hp > 0 and !sub) try buildRage(battle, player.foe(), log);
|
|
1164
1210
|
return null;
|
|
1165
1211
|
}
|
|
1166
1212
|
|
|
@@ -1204,6 +1250,7 @@ fn mirrorMove(
|
|
|
1204
1250
|
battle: anytype,
|
|
1205
1251
|
player: Player,
|
|
1206
1252
|
mslot: u4,
|
|
1253
|
+
rewrap: bool,
|
|
1207
1254
|
auto: bool,
|
|
1208
1255
|
residual: *bool,
|
|
1209
1256
|
log: anytype,
|
|
@@ -1216,18 +1263,19 @@ fn mirrorMove(
|
|
|
1216
1263
|
if (foe.last_used_move == .None or foe.last_used_move == .MirrorMove) {
|
|
1217
1264
|
try log.fail(battle.active(player), .None);
|
|
1218
1265
|
return null;
|
|
1266
|
+
} else if (!showdown or foe.last_used_move != .Struggle) {
|
|
1267
|
+
incrementPP(side, mslot);
|
|
1219
1268
|
}
|
|
1220
1269
|
|
|
1221
|
-
incrementPP(side, mslot);
|
|
1222
|
-
|
|
1223
1270
|
if (!try canMove(battle, player, mslot, auto, false, .MirrorMove, residual, log)) return null;
|
|
1224
|
-
return doMove(battle, player, mslot, auto, residual, log);
|
|
1271
|
+
return doMove(battle, player, mslot, rewrap, auto, residual, log);
|
|
1225
1272
|
}
|
|
1226
1273
|
|
|
1227
1274
|
fn metronome(
|
|
1228
1275
|
battle: anytype,
|
|
1229
1276
|
player: Player,
|
|
1230
1277
|
mslot: u4,
|
|
1278
|
+
rewrap: bool,
|
|
1231
1279
|
auto: bool,
|
|
1232
1280
|
residual: *bool,
|
|
1233
1281
|
log: anytype,
|
|
@@ -1238,7 +1286,7 @@ fn metronome(
|
|
|
1238
1286
|
incrementPP(side, mslot);
|
|
1239
1287
|
|
|
1240
1288
|
if (!try canMove(battle, player, mslot, auto, false, .Metronome, residual, log)) return null;
|
|
1241
|
-
return doMove(battle, player, mslot, auto, residual, log);
|
|
1289
|
+
return doMove(battle, player, mslot, rewrap, auto, residual, log);
|
|
1242
1290
|
}
|
|
1243
1291
|
|
|
1244
1292
|
fn checkHit(battle: anytype, player: Player, move: Move.Data, log: anytype) !bool {
|
|
@@ -1445,11 +1493,7 @@ fn handleResidual(battle: anytype, player: Player, log: anytype) !void {
|
|
|
1445
1493
|
stored.hp -= amount;
|
|
1446
1494
|
|
|
1447
1495
|
// As above, Pokémon Showdown uses damageOf but its not relevant
|
|
1448
|
-
if (amount > 0)
|
|
1449
|
-
try log.damage(ident, stored, .LeechSeed);
|
|
1450
|
-
// Pokémon Showdown erroneously updates last damage with uncapped Leech Seed damage
|
|
1451
|
-
if (showdown) battle.last_damage = damage;
|
|
1452
|
-
}
|
|
1496
|
+
if (amount > 0) try log.damage(ident, stored, .LeechSeed);
|
|
1453
1497
|
|
|
1454
1498
|
const before = foe_stored.hp;
|
|
1455
1499
|
// Uncapped damage is added back to the foe
|
|
@@ -1662,7 +1706,7 @@ pub const Effects = struct {
|
|
|
1662
1706
|
side.active.volatiles.Bide = true;
|
|
1663
1707
|
assert(!side.active.volatiles.Thrashing and !side.active.volatiles.Rage);
|
|
1664
1708
|
side.active.volatiles.state = 0;
|
|
1665
|
-
side.active.volatiles.attacks = Rolls.
|
|
1709
|
+
side.active.volatiles.attacks = Rolls.bideThrashDuration(battle);
|
|
1666
1710
|
|
|
1667
1711
|
try log.start(battle.active(player), .Bide);
|
|
1668
1712
|
}
|
|
@@ -1982,9 +2026,9 @@ pub const Effects = struct {
|
|
|
1982
2026
|
}
|
|
1983
2027
|
break :has_mimic false;
|
|
1984
2028
|
};
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
2029
|
+
if (!has_mimic) {
|
|
2030
|
+
// Invulnerable foes or 1/256 miss can trigger |-miss| instead of |-fail|
|
|
2031
|
+
if (!try checkHit(battle, player, move, log)) return;
|
|
1988
2032
|
return try log.fail(battle.active(player.foe()), .None);
|
|
1989
2033
|
}
|
|
1990
2034
|
}
|
|
@@ -2167,7 +2211,7 @@ pub const Effects = struct {
|
|
|
2167
2211
|
}
|
|
2168
2212
|
foe.active.volatiles.Recharging = false;
|
|
2169
2213
|
|
|
2170
|
-
foe_stored.status = Status.slp(Rolls.
|
|
2214
|
+
foe_stored.status = Status.slp(Rolls.sleepDuration(battle));
|
|
2171
2215
|
try log.statusFrom(foe_ident, foe_stored.status, battle.side(player).last_selected_move);
|
|
2172
2216
|
}
|
|
2173
2217
|
|
|
@@ -2220,7 +2264,7 @@ pub const Effects = struct {
|
|
|
2220
2264
|
assert(!volatiles.Bide);
|
|
2221
2265
|
|
|
2222
2266
|
volatiles.Thrashing = true;
|
|
2223
|
-
volatiles.attacks = Rolls.
|
|
2267
|
+
volatiles.attacks = Rolls.bideThrashDuration(battle);
|
|
2224
2268
|
}
|
|
2225
2269
|
|
|
2226
2270
|
fn transform(battle: anytype, player: Player, log: anytype) !void {
|
|
@@ -2252,7 +2296,7 @@ pub const Effects = struct {
|
|
|
2252
2296
|
try log.transform(battle.active(player), foe_ident);
|
|
2253
2297
|
}
|
|
2254
2298
|
|
|
2255
|
-
fn binding(battle: anytype, player: Player) void {
|
|
2299
|
+
fn binding(battle: anytype, player: Player, rewrap: bool) void {
|
|
2256
2300
|
var side = battle.side(player);
|
|
2257
2301
|
var foe = battle.foe(player);
|
|
2258
2302
|
|
|
@@ -2260,7 +2304,9 @@ pub const Effects = struct {
|
|
|
2260
2304
|
side.active.volatiles.Binding = true;
|
|
2261
2305
|
// GLITCH: Hyper Beam automatic selection glitch if Recharging gets cleared on miss
|
|
2262
2306
|
// (Pokémon Showdown unitentionally patches this glitch, preventing automatic selection)
|
|
2263
|
-
if (!showdown)
|
|
2307
|
+
if (!showdown) {
|
|
2308
|
+
foe.active.volatiles.Recharging = false;
|
|
2309
|
+
} else if (foe.stored().hp == 0) return if (!rewrap) battle.rng.advance(1);
|
|
2264
2310
|
|
|
2265
2311
|
side.active.volatiles.attacks = Rolls.distribution(battle) - 1;
|
|
2266
2312
|
}
|
|
@@ -2589,7 +2635,7 @@ pub const Rolls = struct {
|
|
|
2589
2635
|
return battle.rng.next() < 1 + (if (low) Gen12.percent(20) else Gen12.percent(40));
|
|
2590
2636
|
}
|
|
2591
2637
|
|
|
2592
|
-
inline fn
|
|
2638
|
+
inline fn sleepDuration(battle: anytype) u3 {
|
|
2593
2639
|
if (showdown) return @intCast(u3, battle.rng.range(u8, 1, 8));
|
|
2594
2640
|
while (true) {
|
|
2595
2641
|
const r = battle.rng.next() & 7;
|
|
@@ -2597,7 +2643,7 @@ pub const Rolls = struct {
|
|
|
2597
2643
|
}
|
|
2598
2644
|
}
|
|
2599
2645
|
|
|
2600
|
-
inline fn
|
|
2646
|
+
inline fn bideThrashDuration(battle: anytype) u3 {
|
|
2601
2647
|
if (showdown) return @intCast(u3, battle.rng.range(u4, 2, 4));
|
|
2602
2648
|
return @intCast(u3, (battle.rng.next() & 1) + 2);
|
|
2603
2649
|
}
|
package/src/lib/pkmn.zig
CHANGED
|
@@ -25,12 +25,12 @@ pub const Result = @import("common/data.zig").Result;
|
|
|
25
25
|
/// Pokémon Showdown's RNG (backed by a Generation V & VI RNG).
|
|
26
26
|
pub const PSRNG = @import("common/rng.zig").PSRNG;
|
|
27
27
|
|
|
28
|
-
/// Namespace for helpers related to protocol
|
|
28
|
+
/// Namespace for helpers related to protocol message logging.
|
|
29
29
|
pub const protocol = if (options.internal) struct {
|
|
30
30
|
pub usingnamespace @import("common/protocol.zig");
|
|
31
31
|
} else struct {
|
|
32
32
|
/// Logs protocol information to its `Writer` during a battle update when
|
|
33
|
-
/// `options.
|
|
33
|
+
/// `options.log` is enabled.
|
|
34
34
|
pub const Log = @import("common/protocol.zig").Log;
|
|
35
35
|
/// Stripped down version of `std.io.FixedBufferStream` optimized for
|
|
36
36
|
/// efficiently writing the individual protocol bytes. Note that the
|
|
@@ -41,8 +41,8 @@ pub const protocol = if (options.internal) struct {
|
|
|
41
41
|
/// intialized with a `LOGS_SIZE`-sized buffer.
|
|
42
42
|
pub const FixedLog = @import("common/protocol.zig").FixedLog;
|
|
43
43
|
/// Null object pattern implementation of `Log` backed by a
|
|
44
|
-
/// `std.io.null_writer`. Ignores anything sent to it, though
|
|
45
|
-
/// should
|
|
44
|
+
/// `std.io.null_writer`. Ignores anything sent to it, though protocol
|
|
45
|
+
/// logging should additionally be turned off entirely with `options.log`.
|
|
46
46
|
pub const NULL = @import("common/protocol.zig").NULL;
|
|
47
47
|
};
|
|
48
48
|
|