@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
|
@@ -26,9 +26,9 @@ const Result = common.Result;
|
|
|
26
26
|
|
|
27
27
|
const Activate = protocol.Activate;
|
|
28
28
|
const Boost = protocol.Boost;
|
|
29
|
-
const Cant = protocol.Cant;
|
|
30
29
|
const Damage = protocol.Damage;
|
|
31
30
|
const End = protocol.End;
|
|
31
|
+
const Fail = protocol.Fail;
|
|
32
32
|
const Heal = protocol.Heal;
|
|
33
33
|
const Start = protocol.Start;
|
|
34
34
|
|
|
@@ -37,6 +37,7 @@ const Gen12 = rng.Gen12;
|
|
|
37
37
|
const PointerType = util.PointerType;
|
|
38
38
|
|
|
39
39
|
const ActivePokemon = data.ActivePokemon;
|
|
40
|
+
const Boosts = data.Boosts;
|
|
40
41
|
const Effectiveness = data.Effectiveness;
|
|
41
42
|
const Item = data.Item;
|
|
42
43
|
const Move = data.Move;
|
|
@@ -50,6 +51,7 @@ const TriAttack = data.TriAttack;
|
|
|
50
51
|
const Type = data.Type;
|
|
51
52
|
const Types = data.Types;
|
|
52
53
|
const Volatiles = data.Volatiles;
|
|
54
|
+
const Weather = data.Weather;
|
|
53
55
|
|
|
54
56
|
// zig fmt: off
|
|
55
57
|
const STAT_BOOSTS = &[_][2]u8{
|
|
@@ -95,10 +97,13 @@ const CRITICAL_HITS = &[_]u8{
|
|
|
95
97
|
|
|
96
98
|
const MAX_STAT_VALUE = 999;
|
|
97
99
|
|
|
100
|
+
const STATS = [_][]const u8{ "atk", "def", "spe", "spa", "spd" };
|
|
101
|
+
|
|
98
102
|
pub const State = struct {
|
|
99
103
|
damage: u16 = 0, // wCurDamage
|
|
100
104
|
effectiveness: u16 = Effectiveness.neutral, // wTypeModifier
|
|
101
105
|
bp: u8, // wPlayerMoveStructPower
|
|
106
|
+
type: Type, // BATTLE_VARS_MOVE_TYPE
|
|
102
107
|
accuracy: u8, // wEnemyMoveStruct + MOVE_ACC
|
|
103
108
|
move: Move, // wCurPlayerMove
|
|
104
109
|
from: Move = .None,
|
|
@@ -110,10 +115,10 @@ pub const State = struct {
|
|
|
110
115
|
proc: bool = true, // wEffectFailed
|
|
111
116
|
|
|
112
117
|
comptime {
|
|
113
|
-
assert(@sizeOf(State) ==
|
|
118
|
+
assert(@sizeOf(State) == 16);
|
|
114
119
|
}
|
|
115
120
|
|
|
116
|
-
pub
|
|
121
|
+
pub fn immune(self: *State) bool {
|
|
117
122
|
return self.effectiveness == 0;
|
|
118
123
|
}
|
|
119
124
|
};
|
|
@@ -135,11 +140,11 @@ fn start(battle: anytype, options: anytype) !Result {
|
|
|
135
140
|
const p1 = battle.side(.P1);
|
|
136
141
|
const p2 = battle.side(.P2);
|
|
137
142
|
|
|
138
|
-
|
|
143
|
+
const p1_slot = findFirstAlive(p1);
|
|
139
144
|
assert(!showdown or p1_slot == 1);
|
|
140
145
|
if (p1_slot == 0) return if (findFirstAlive(p2) == 0) Result.Tie else Result.Lose;
|
|
141
146
|
|
|
142
|
-
|
|
147
|
+
const p2_slot = findFirstAlive(p2);
|
|
143
148
|
assert(!showdown or p2_slot == 1);
|
|
144
149
|
if (p2_slot == 0) return Result.Win;
|
|
145
150
|
|
|
@@ -247,15 +252,15 @@ fn switchIn(battle: anytype, player: Player, slot: u8, kind: SwitchIn, options:
|
|
|
247
252
|
statusModify(incoming.status, &active.stats);
|
|
248
253
|
|
|
249
254
|
if (kind == .Drag) {
|
|
250
|
-
try options.log.drag(battle.active(player), incoming);
|
|
255
|
+
try options.log.drag(.{ battle.active(player), incoming });
|
|
251
256
|
} else {
|
|
252
|
-
try options.log.switched(battle.active(player), incoming);
|
|
257
|
+
try options.log.switched(.{ battle.active(player), incoming });
|
|
253
258
|
}
|
|
254
259
|
options.chance.switched(player, side.pokemon[0].position, out);
|
|
255
260
|
|
|
256
261
|
if (side.conditions.Spikes and !active.types.includes(.Flying)) {
|
|
257
262
|
incoming.hp -|= @max(incoming.stats.hp / 8, 1);
|
|
258
|
-
try options.log.damage(battle.active(player), incoming, .Spikes);
|
|
263
|
+
try options.log.damage(.{ battle.active(player), incoming, .Spikes });
|
|
259
264
|
}
|
|
260
265
|
}
|
|
261
266
|
|
|
@@ -269,7 +274,13 @@ fn turnOrder(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Player
|
|
|
269
274
|
// TODO: add to pending and only commit roll on Pokémon Showdown if roll is used
|
|
270
275
|
const qkc = showdown and try Rolls.quickClaw(battle, .P1, options);
|
|
271
276
|
|
|
272
|
-
if ((c1.type == .Switch) != (c2.type == .Switch))
|
|
277
|
+
if ((c1.type == .Switch) != (c2.type == .Switch)) {
|
|
278
|
+
const m1 = .Pound; // TODO battle.side(.P1).last_selected_move;
|
|
279
|
+
const m2 = .Pound; // TODO battle.side(.P2).last_selected_move;
|
|
280
|
+
return if (c1.type == .Switch)
|
|
281
|
+
if (m1 == .Pursuit) .P2 else .P1
|
|
282
|
+
else if (m2 == .Pursuit) .P1 else .P2;
|
|
283
|
+
}
|
|
273
284
|
|
|
274
285
|
// https://www.smogon.com/forums/threads/adv-switch-priority.3622189/
|
|
275
286
|
// > In Gen 1 it's irrelevant [which player switches first] because switches happen instantly on
|
|
@@ -392,14 +403,22 @@ fn executeMove(
|
|
|
392
403
|
|
|
393
404
|
assert(choice.type == .Move);
|
|
394
405
|
var mslot: u4 = @intCast(choice.data);
|
|
406
|
+
_ = &mslot;
|
|
395
407
|
var move = Move.Pound; // TODO
|
|
408
|
+
_ = &move;
|
|
396
409
|
|
|
397
410
|
if (!try beforeMove(battle, player, move, options)) return null;
|
|
398
411
|
|
|
399
412
|
// FIXME: need damage from doTurn
|
|
400
413
|
const m = Move.get(move);
|
|
401
|
-
var state: State =
|
|
402
|
-
.
|
|
414
|
+
var state: State = .{
|
|
415
|
+
.move = move,
|
|
416
|
+
.mslot = mslot,
|
|
417
|
+
.first = first,
|
|
418
|
+
.accuracy = m.accuracy,
|
|
419
|
+
.bp = m.bp,
|
|
420
|
+
.type = m.type,
|
|
421
|
+
};
|
|
403
422
|
try generated.runMove(battle, player, &state, options);
|
|
404
423
|
return null;
|
|
405
424
|
}
|
|
@@ -416,7 +435,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
416
435
|
if (volatiles.Recharging) {
|
|
417
436
|
volatiles.Recharging = false;
|
|
418
437
|
resetCant(volatiles);
|
|
419
|
-
try log.cant(ident, .Recharge);
|
|
438
|
+
try log.cant(.{ ident, .Recharge });
|
|
420
439
|
return false;
|
|
421
440
|
}
|
|
422
441
|
|
|
@@ -435,12 +454,12 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
435
454
|
}
|
|
436
455
|
|
|
437
456
|
if (duration == 0) {
|
|
438
|
-
try log.curestatus(ident, before, .Message);
|
|
457
|
+
try log.curestatus(.{ ident, before, .Message });
|
|
439
458
|
stored.status = 0; // clears EXT if present
|
|
440
459
|
resetCant(volatiles);
|
|
441
460
|
volatiles.Nightmare = false;
|
|
442
461
|
} else {
|
|
443
|
-
try log.cant(ident, .Sleep);
|
|
462
|
+
try log.cant(.{ ident, .Sleep });
|
|
444
463
|
if (move == .Snore or move == .SleepTalk) break :slp;
|
|
445
464
|
resetCant(volatiles);
|
|
446
465
|
return false;
|
|
@@ -449,7 +468,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
449
468
|
|
|
450
469
|
if (Status.is(stored.status, .FRZ) and !(move == .FlameWheel or move == .SacredFire)) {
|
|
451
470
|
resetCant(volatiles);
|
|
452
|
-
try log.cant(ident, .Freeze);
|
|
471
|
+
try log.cant(.{ ident, .Freeze });
|
|
453
472
|
return false;
|
|
454
473
|
}
|
|
455
474
|
|
|
@@ -459,7 +478,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
459
478
|
// if (!showdown) volatiles.Flinch = false;
|
|
460
479
|
volatiles.Flinch = false;
|
|
461
480
|
resetCant(volatiles);
|
|
462
|
-
try log.cant(ident, .Flinch);
|
|
481
|
+
try log.cant(.{ ident, .Flinch });
|
|
463
482
|
return false;
|
|
464
483
|
}
|
|
465
484
|
|
|
@@ -473,7 +492,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
473
492
|
|
|
474
493
|
if (volatiles.disable.duration == 0) {
|
|
475
494
|
volatiles.disable.move = 0;
|
|
476
|
-
try log.end(ident, .Disable);
|
|
495
|
+
try log.end(.{ ident, .Disable });
|
|
477
496
|
}
|
|
478
497
|
}
|
|
479
498
|
|
|
@@ -489,9 +508,9 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
489
508
|
|
|
490
509
|
if (volatiles.confusion == 0) {
|
|
491
510
|
volatiles.Confusion = false;
|
|
492
|
-
try log.end(ident, .Confusion);
|
|
511
|
+
try log.end(.{ ident, .Confusion });
|
|
493
512
|
} else {
|
|
494
|
-
try log.activate(ident, .Confusion);
|
|
513
|
+
try log.activate(.{ ident, .Confusion });
|
|
495
514
|
|
|
496
515
|
if (try Rolls.confused(battle, player, options)) {
|
|
497
516
|
volatiles.BeatUp = false;
|
|
@@ -506,11 +525,11 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
506
525
|
}
|
|
507
526
|
|
|
508
527
|
if (volatiles.Attract) {
|
|
509
|
-
try log.activate(ident, .Attract);
|
|
528
|
+
try log.activate(.{ ident, .Attract });
|
|
510
529
|
|
|
511
530
|
if (try Rolls.attract(battle, player, options)) {
|
|
512
531
|
resetCant(volatiles);
|
|
513
|
-
try log.cant(ident, .Attract);
|
|
532
|
+
try log.cant(.{ ident, .Attract });
|
|
514
533
|
return false;
|
|
515
534
|
}
|
|
516
535
|
}
|
|
@@ -521,14 +540,14 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
521
540
|
if (m != .None and m == move) {
|
|
522
541
|
side.active.volatiles.Charging = false;
|
|
523
542
|
resetCant(volatiles);
|
|
524
|
-
try options.log.
|
|
543
|
+
try options.log.cant(.{ ident, .Disable, move });
|
|
525
544
|
return false;
|
|
526
545
|
}
|
|
527
546
|
}
|
|
528
547
|
|
|
529
548
|
if (Status.is(stored.status, .PAR) and try Rolls.paralyzed(battle, player, options)) {
|
|
530
549
|
resetCant(volatiles);
|
|
531
|
-
try log.cant(ident, .Paralysis);
|
|
550
|
+
try log.cant(.{ ident, .Paralysis });
|
|
532
551
|
return false;
|
|
533
552
|
}
|
|
534
553
|
|
|
@@ -548,11 +567,11 @@ fn resetCant(volatiles: *align(8) Volatiles) void {
|
|
|
548
567
|
|
|
549
568
|
pub fn canMove(battle: anytype, player: Player, state: *State, options: anytype) !bool {
|
|
550
569
|
var side = battle.side(player);
|
|
551
|
-
|
|
570
|
+
const volatiles = &side.active.volatiles;
|
|
552
571
|
|
|
553
572
|
// FIXME: pass in target to avoid lookup?
|
|
554
573
|
const target = if (Move.get(state.move).target == .Self) player else player.foe();
|
|
555
|
-
try options.log.
|
|
574
|
+
try options.log.move(.{ battle.active(player), state.move, battle.active(target), state.from });
|
|
556
575
|
|
|
557
576
|
const charging = false; // TODO charging uses doUse
|
|
558
577
|
const skip_pp = charging or state.move == .Struggle or
|
|
@@ -631,7 +650,7 @@ pub fn afterMove(
|
|
|
631
650
|
|
|
632
651
|
pub fn checkCriticalHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
633
652
|
const move = Move.get(state.move);
|
|
634
|
-
if (
|
|
653
|
+
if (state.bp == 0) return;
|
|
635
654
|
|
|
636
655
|
const side = battle.side(player);
|
|
637
656
|
const pokemon = side.stored();
|
|
@@ -669,31 +688,30 @@ pub fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype)
|
|
|
669
688
|
const side = battle.side(player);
|
|
670
689
|
const foe = battle.foe(player);
|
|
671
690
|
const types = foe.active.types;
|
|
672
|
-
const move = Move.get(state.move);
|
|
673
691
|
|
|
674
692
|
var d = state.damage;
|
|
675
|
-
if ((
|
|
676
|
-
(
|
|
693
|
+
if ((state.type == .Water and battle.field.weather == .Rain) or
|
|
694
|
+
(state.type == .Fire and battle.field.weather == .Sun))
|
|
677
695
|
{
|
|
678
696
|
d = d *% @intFromEnum(Effectiveness.Super) / 10;
|
|
679
|
-
} else if ((
|
|
680
|
-
((
|
|
697
|
+
} else if ((state.type == .Water and battle.field.weather == .Sun) or
|
|
698
|
+
((state.type == .Fire or state.move == .SolarBeam) and battle.field.weather == .Rain))
|
|
681
699
|
{
|
|
682
700
|
d = d *% @intFromEnum(Effectiveness.Resisted) / 10;
|
|
683
701
|
}
|
|
684
702
|
|
|
685
|
-
if (side.active.types.includes(
|
|
703
|
+
if (side.active.types.includes(state.type)) d +%= d / 2;
|
|
686
704
|
|
|
687
705
|
const neutral = @intFromEnum(Effectiveness.Neutral);
|
|
688
|
-
const foresight = foe.active.volatiles.Foresight and FORESIGHT.includes(
|
|
706
|
+
const foresight = foe.active.volatiles.Foresight and FORESIGHT.includes(state.type);
|
|
689
707
|
const eff1: u16 = if (foresight and types.type1 == .Ghost)
|
|
690
708
|
neutral
|
|
691
709
|
else
|
|
692
|
-
@intFromEnum(
|
|
710
|
+
@intFromEnum(state.type.effectiveness(types.type1));
|
|
693
711
|
const eff2: u16 = if (foresight and types.type2 == .Ghost)
|
|
694
712
|
neutral
|
|
695
713
|
else
|
|
696
|
-
@intFromEnum(
|
|
714
|
+
@intFromEnum(state.type.effectiveness(types.type2));
|
|
697
715
|
|
|
698
716
|
// Type effectiveness matchup precedence only matters with (NVE, SE)
|
|
699
717
|
if (!showdown and (eff1 + eff2) == Effectiveness.mismatch and
|
|
@@ -775,9 +793,15 @@ pub fn checkHit(
|
|
|
775
793
|
return;
|
|
776
794
|
}
|
|
777
795
|
}
|
|
778
|
-
if (move.effect == .Thunder
|
|
779
|
-
|
|
780
|
-
|
|
796
|
+
if (move.effect == .Thunder) {
|
|
797
|
+
if (battle.field.weather == .Rain) {
|
|
798
|
+
state.miss = false;
|
|
799
|
+
return;
|
|
800
|
+
} else if (battle.field.weather == .Sun) {
|
|
801
|
+
// This latter part gets applied in a separate Thunder accuracy handler
|
|
802
|
+
// on the cartridge but it makes more sense to just do it here
|
|
803
|
+
state.accuracy = Gen12.percent(50) + 1;
|
|
804
|
+
}
|
|
781
805
|
}
|
|
782
806
|
if (move.effect == .AlwaysHit) {
|
|
783
807
|
state.miss = false;
|
|
@@ -827,13 +851,13 @@ fn checkFaint(
|
|
|
827
851
|
if (foe_fainted) try faint(battle, player.foe(), !more, options);
|
|
828
852
|
|
|
829
853
|
if (player_out and foe_out) {
|
|
830
|
-
try options.log.tie();
|
|
854
|
+
try options.log.tie(.{});
|
|
831
855
|
return Result.Tie;
|
|
832
856
|
} else if (player_out) {
|
|
833
|
-
try options.log.win(player.foe());
|
|
857
|
+
try options.log.win(.{player.foe()});
|
|
834
858
|
return if (player == .P1) Result.Lose else Result.Win;
|
|
835
859
|
} else if (foe_out) {
|
|
836
|
-
try options.log.win(player);
|
|
860
|
+
try options.log.win(.{player});
|
|
837
861
|
return if (player == .P1) Result.Win else Result.Lose;
|
|
838
862
|
}
|
|
839
863
|
|
|
@@ -863,19 +887,19 @@ fn faint(battle: anytype, player: Player, done: bool, options: anytype) !void {
|
|
|
863
887
|
side.stored().stats.spe;
|
|
864
888
|
}
|
|
865
889
|
|
|
866
|
-
try options.log.faint(battle.active(player), done);
|
|
890
|
+
try options.log.faint(.{ battle.active(player), done });
|
|
867
891
|
options.calc.capped(player);
|
|
868
892
|
}
|
|
869
893
|
|
|
870
894
|
// TODO: see if we can move *before* applyDamage and include failuretext logic
|
|
871
895
|
pub fn reportOutcome(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
872
896
|
const foe_ident = battle.active(player.foe());
|
|
873
|
-
if (state.crit) try options.log.crit(foe_ident);
|
|
897
|
+
if (state.crit) try options.log.crit(.{foe_ident});
|
|
874
898
|
if (!state.immune()) {
|
|
875
899
|
if (state.effectiveness > Effectiveness.neutral) {
|
|
876
|
-
try options.log.supereffective(foe_ident);
|
|
900
|
+
try options.log.supereffective(.{foe_ident});
|
|
877
901
|
} else if (state.effectiveness < Effectiveness.neutral) {
|
|
878
|
-
try options.log.resisted(foe_ident);
|
|
902
|
+
try options.log.resisted(.{foe_ident});
|
|
879
903
|
}
|
|
880
904
|
}
|
|
881
905
|
}
|
|
@@ -908,8 +932,8 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !bool {
|
|
|
908
932
|
}
|
|
909
933
|
|
|
910
934
|
stored.hp -|= damage;
|
|
911
|
-
// Pokémon Showdown uses
|
|
912
|
-
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
935
|
+
// Pokémon Showdown uses damage [of] here but its not relevant in Generation II
|
|
936
|
+
try options.log.damage(.{ ident, stored, if (brn) Damage.Burn else Damage.Poison });
|
|
913
937
|
if (stored.hp == 0) return true;
|
|
914
938
|
}
|
|
915
939
|
|
|
@@ -925,27 +949,27 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !bool {
|
|
|
925
949
|
|
|
926
950
|
const damage = @max(stored.stats.hp / 8, 1);
|
|
927
951
|
stored.hp -|= damage;
|
|
928
|
-
// As above, Pokémon Showdown uses
|
|
929
|
-
try options.log.damage(ident, stored, .LeechSeed);
|
|
952
|
+
// As above, Pokémon Showdown uses damage [of] but its not relevant
|
|
953
|
+
try options.log.damage(.{ ident, stored, .LeechSeed });
|
|
930
954
|
|
|
931
955
|
const before = foe_stored.hp;
|
|
932
956
|
foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
|
|
933
957
|
// Pokémon Showdown uses the less specific heal here instead of drain... because reasons?
|
|
934
|
-
if (foe_stored.hp > before) try options.log.heal(foe_ident, foe_stored, .Silent);
|
|
958
|
+
if (foe_stored.hp > before) try options.log.heal(.{ foe_ident, foe_stored, .Silent });
|
|
935
959
|
if (stored.hp == 0) return true;
|
|
936
960
|
}
|
|
937
961
|
|
|
938
962
|
if (volatiles.Nightmare) {
|
|
939
963
|
stored.hp -= @max(stored.stats.hp / 4, 1);
|
|
940
964
|
// ibid
|
|
941
|
-
try options.log.damage(ident, stored, .Nightmare);
|
|
965
|
+
try options.log.damage(.{ ident, stored, .Nightmare });
|
|
942
966
|
if (stored.hp == 0) return true;
|
|
943
967
|
}
|
|
944
968
|
|
|
945
969
|
if (volatiles.Curse) {
|
|
946
970
|
stored.hp -= @max(stored.stats.hp / 4, 1);
|
|
947
971
|
// ibid
|
|
948
|
-
try options.log.damage(ident, stored, .Curse);
|
|
972
|
+
try options.log.damage(.{ ident, stored, .Curse });
|
|
949
973
|
if (stored.hp == 0) return true;
|
|
950
974
|
}
|
|
951
975
|
|
|
@@ -966,7 +990,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
966
990
|
future_sight.count -= 1;
|
|
967
991
|
assert(future_sight.count != 0);
|
|
968
992
|
if (future_sight.count == 1) {
|
|
969
|
-
try options.log.end(battle.active(player), .FutureSight);
|
|
993
|
+
try options.log.end(.{ battle.active(player), .FutureSight });
|
|
970
994
|
// TODO doMove
|
|
971
995
|
}
|
|
972
996
|
}
|
|
@@ -975,21 +999,24 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
975
999
|
|
|
976
1000
|
// Weather
|
|
977
1001
|
if (battle.field.weather != .None) {
|
|
978
|
-
assert(battle.field.weather_duration > 0);
|
|
979
1002
|
battle.field.weather_duration -= 1;
|
|
980
1003
|
|
|
981
1004
|
if (battle.field.weather_duration == 0) {
|
|
982
1005
|
battle.field.weather = .None;
|
|
983
|
-
try options.log.weather(battle.field.weather, .None);
|
|
1006
|
+
try options.log.weather(.{ battle.field.weather, protocol.Weather.None });
|
|
984
1007
|
} else {
|
|
985
|
-
try options.log.weather(battle.field.weather, .Upkeep);
|
|
1008
|
+
try options.log.weather(.{ battle.field.weather, protocol.Weather.Upkeep });
|
|
986
1009
|
if (battle.field.weather == .Sandstorm) {
|
|
987
1010
|
inline for (players) |player| {
|
|
988
1011
|
var side = battle.side(player);
|
|
989
1012
|
const active = side.active;
|
|
990
1013
|
if (!(active.volatiles.Underground or active.types.sandstormImmune())) {
|
|
991
1014
|
side.stored().hp -|= @max(side.stored().stats.hp / 8, 1);
|
|
992
|
-
try options.log.damage(
|
|
1015
|
+
try options.log.damage(.{
|
|
1016
|
+
battle.active(player),
|
|
1017
|
+
side.stored(),
|
|
1018
|
+
.Sandstorm,
|
|
1019
|
+
});
|
|
993
1020
|
}
|
|
994
1021
|
}
|
|
995
1022
|
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
@@ -1001,19 +1028,24 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1001
1028
|
inline for (players) |player| {
|
|
1002
1029
|
var side = battle.side(player);
|
|
1003
1030
|
var volatiles = &side.active.volatiles;
|
|
1031
|
+
const ident = battle.active(player);
|
|
1004
1032
|
|
|
1005
1033
|
if (volatiles.bind.duration > 0 and !volatiles.Substitute) {
|
|
1006
1034
|
volatiles.bind.duration -= 1;
|
|
1007
1035
|
if (volatiles.bind.duration == 0) {
|
|
1008
1036
|
const reason = @intFromEnum(End.Bind) + volatiles.bind.reason - 1;
|
|
1009
|
-
try options.log.end(
|
|
1037
|
+
try options.log.end(.{ ident, @as(End, @enumFromInt(reason)) });
|
|
1010
1038
|
} else {
|
|
1011
1039
|
var reason = @intFromEnum(Activate.Bind) + volatiles.bind.reason - 1;
|
|
1012
|
-
try options.log.activate(
|
|
1040
|
+
try options.log.activate(.{ ident, @as(Activate, @enumFromInt(reason)) });
|
|
1013
1041
|
|
|
1014
1042
|
reason = @intFromEnum(Damage.Bind) + volatiles.bind.reason - 1;
|
|
1015
1043
|
side.stored().hp -|= @max(side.stored().stats.hp / 16, 1);
|
|
1016
|
-
try options.log.damage(
|
|
1044
|
+
try options.log.damage(.{
|
|
1045
|
+
ident,
|
|
1046
|
+
side.stored(),
|
|
1047
|
+
@as(Damage, @enumFromInt(reason)),
|
|
1048
|
+
});
|
|
1017
1049
|
}
|
|
1018
1050
|
}
|
|
1019
1051
|
}
|
|
@@ -1025,12 +1057,11 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1025
1057
|
var volatiles = &side.active.volatiles;
|
|
1026
1058
|
|
|
1027
1059
|
if (volatiles.PerishSong) {
|
|
1028
|
-
assert(volatiles.perish_song > 0);
|
|
1029
1060
|
volatiles.perish_song -= 1;
|
|
1030
1061
|
|
|
1031
1062
|
assert(volatiles.perish_song < 3);
|
|
1032
1063
|
const reason = @intFromEnum(Start.PerishSong0) + volatiles.perish_song;
|
|
1033
|
-
try options.log.start(battle.active(player), @enumFromInt(reason));
|
|
1064
|
+
try options.log.start(.{ battle.active(player), @as(Start, @enumFromInt(reason)) });
|
|
1034
1065
|
|
|
1035
1066
|
if (volatiles.perish_song == 0) {
|
|
1036
1067
|
volatiles.PerishSong = false;
|
|
@@ -1047,13 +1078,15 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1047
1078
|
if (stored.item == .Leftovers) {
|
|
1048
1079
|
const before = stored.hp;
|
|
1049
1080
|
stored.hp = @min(stored.hp + @max(stored.stats.hp / 16, 1), stored.stats.hp);
|
|
1050
|
-
if (stored.hp > before)
|
|
1081
|
+
if (stored.hp > before) {
|
|
1082
|
+
try options.log.heal(.{ battle.active(player), stored, .Leftovers });
|
|
1083
|
+
}
|
|
1051
1084
|
}
|
|
1052
1085
|
}
|
|
1053
1086
|
|
|
1054
1087
|
// Mystery Berry
|
|
1055
1088
|
inline for (players) |player| {
|
|
1056
|
-
|
|
1089
|
+
const stored = battle.side(player).stored();
|
|
1057
1090
|
|
|
1058
1091
|
if (stored.item == .MysteryBerry) {
|
|
1059
1092
|
// TODO stored and active moves, transform, etc
|
|
@@ -1068,7 +1101,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1068
1101
|
!side.active.volatiles.frozen and
|
|
1069
1102
|
try Rolls.defrost(battle, player, options))
|
|
1070
1103
|
{
|
|
1071
|
-
try options.log.curestatus(battle.active(player), side.stored().status, .Message);
|
|
1104
|
+
try options.log.curestatus(.{ battle.active(player), side.stored().status, .Message });
|
|
1072
1105
|
side.stored().status = 0;
|
|
1073
1106
|
}
|
|
1074
1107
|
}
|
|
@@ -1109,14 +1142,14 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1109
1142
|
stored.hp < stored.stats.hp;
|
|
1110
1143
|
if (proc) {
|
|
1111
1144
|
assert(stored.hp != 0);
|
|
1112
|
-
try options.log.enditem(ident, stored.item, .Eat);
|
|
1145
|
+
try options.log.enditem(.{ ident, stored.item, .Eat });
|
|
1113
1146
|
stored.item = .None;
|
|
1114
1147
|
|
|
1115
1148
|
const offset = num - @intFromEnum(Item.Berry);
|
|
1116
1149
|
assert(offset < 3);
|
|
1117
1150
|
stored.hp = @min(stored.hp + ((offset + 1) * 10), stored.stats.hp);
|
|
1118
1151
|
const reason: Heal = @enumFromInt(@intFromEnum(Heal.Berry) + offset);
|
|
1119
|
-
try options.log.heal(ident, stored, reason);
|
|
1152
|
+
try options.log.heal(.{ ident, stored, reason });
|
|
1120
1153
|
}
|
|
1121
1154
|
} else if (num < @intFromEnum(Item.BitterBerry)) {
|
|
1122
1155
|
const proc = if (stored.item == .MiracleBerry)
|
|
@@ -1126,29 +1159,29 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1126
1159
|
|
|
1127
1160
|
if (proc) {
|
|
1128
1161
|
assert(Status.any(stored.status));
|
|
1129
|
-
try options.log.enditem(ident, stored.item, .Eat);
|
|
1162
|
+
try options.log.enditem(.{ ident, stored.item, .Eat });
|
|
1130
1163
|
stored.item = .None;
|
|
1131
1164
|
|
|
1132
1165
|
// FIXME wtf?
|
|
1133
1166
|
// side.active.volatiles.Toxic = false;
|
|
1134
|
-
// side.active.volatiles.
|
|
1167
|
+
// side.active.volatiles.Nightmare = false;
|
|
1135
1168
|
if (stored.item == .MiracleBerry) {
|
|
1136
1169
|
side.active.volatiles.Confusion = false;
|
|
1137
|
-
try options.log.end(ident, .Confusion);
|
|
1170
|
+
try options.log.end(.{ ident, .Confusion });
|
|
1138
1171
|
}
|
|
1139
1172
|
stored.status = 0;
|
|
1140
1173
|
|
|
1141
1174
|
// FIXME recalc stats
|
|
1142
1175
|
|
|
1143
|
-
try options.log.curestatus(ident, stored.status, .Message);
|
|
1176
|
+
try options.log.curestatus(.{ ident, stored.status, .Message });
|
|
1144
1177
|
}
|
|
1145
1178
|
} else if (num == @intFromEnum(Item.BitterBerry)) {
|
|
1146
1179
|
if (side.active.volatiles.Confusion) {
|
|
1147
|
-
try options.log.enditem(ident, stored.item, .Eat);
|
|
1180
|
+
try options.log.enditem(.{ ident, stored.item, .Eat });
|
|
1148
1181
|
stored.item = .None;
|
|
1149
1182
|
|
|
1150
1183
|
side.active.volatiles.Confusion = false;
|
|
1151
|
-
try options.log.end(ident, .Confusion);
|
|
1184
|
+
try options.log.end(.{ ident, .Confusion });
|
|
1152
1185
|
}
|
|
1153
1186
|
}
|
|
1154
1187
|
}
|
|
@@ -1160,12 +1193,11 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1160
1193
|
var volatiles = &side.active.volatiles;
|
|
1161
1194
|
|
|
1162
1195
|
if (volatiles.Encore) {
|
|
1163
|
-
assert(volatiles.encore > 0);
|
|
1164
1196
|
volatiles.encore -= 1;
|
|
1165
1197
|
|
|
1166
1198
|
if (volatiles.encore == 0 or side.active.move(mslot).pp == 0) {
|
|
1167
1199
|
volatiles.Encore = false;
|
|
1168
|
-
try options.log.end(battle.active(player), .Encore);
|
|
1200
|
+
try options.log.end(.{ battle.active(player), .Encore });
|
|
1169
1201
|
}
|
|
1170
1202
|
}
|
|
1171
1203
|
}
|
|
@@ -1173,7 +1205,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1173
1205
|
return null;
|
|
1174
1206
|
}
|
|
1175
1207
|
|
|
1176
|
-
|
|
1208
|
+
fn updateSideCondition(
|
|
1177
1209
|
battle: anytype,
|
|
1178
1210
|
player: Player,
|
|
1179
1211
|
comptime key: []const u8,
|
|
@@ -1182,12 +1214,11 @@ inline fn updateSideCondition(
|
|
|
1182
1214
|
) !void {
|
|
1183
1215
|
var side = battle.side(player);
|
|
1184
1216
|
if (@field(side.conditions, key)) {
|
|
1185
|
-
assert(@field(side.conditions, value) > 0);
|
|
1186
1217
|
@field(side.conditions, value) -= 1;
|
|
1187
1218
|
|
|
1188
1219
|
if (@field(side.conditions, value) == 0) {
|
|
1189
1220
|
@field(side.conditions, key) = false;
|
|
1190
|
-
try options.log.sideend(player, @field(protocol.Side, key));
|
|
1221
|
+
try options.log.sideend(.{ player, @field(protocol.Side, key) });
|
|
1191
1222
|
}
|
|
1192
1223
|
}
|
|
1193
1224
|
}
|
|
@@ -1196,13 +1227,13 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1196
1227
|
battle.turn += 1;
|
|
1197
1228
|
|
|
1198
1229
|
if (showdown and battle.turn >= 1000) {
|
|
1199
|
-
try options.log.tie();
|
|
1230
|
+
try options.log.tie(.{});
|
|
1200
1231
|
return Result.Tie;
|
|
1201
1232
|
} else if (battle.turn >= 65535) {
|
|
1202
1233
|
return Result.Error;
|
|
1203
1234
|
}
|
|
1204
1235
|
|
|
1205
|
-
try options.log.turn(battle.turn);
|
|
1236
|
+
try options.log.turn(.{battle.turn});
|
|
1206
1237
|
|
|
1207
1238
|
return Result.Default;
|
|
1208
1239
|
}
|
|
@@ -1218,7 +1249,7 @@ pub fn buildRage(battle: anytype, player: Player, state: *State, options: anytyp
|
|
|
1218
1249
|
}
|
|
1219
1250
|
} else {
|
|
1220
1251
|
foe.active.volatiles.rage +|= 1;
|
|
1221
|
-
try options.log.activate(battle.active(player), .Rage);
|
|
1252
|
+
try options.log.activate(.{ battle.active(player), .Rage });
|
|
1222
1253
|
}
|
|
1223
1254
|
}
|
|
1224
1255
|
}
|
|
@@ -1241,16 +1272,12 @@ pub fn destinyBond(battle: anytype, player: Player, _: *State, options: anytype)
|
|
|
1241
1272
|
|
|
1242
1273
|
if (foe.active.volatiles.DestinyBond) {
|
|
1243
1274
|
battle.side(player).stored().hp = 0;
|
|
1244
|
-
try options.log.activate(battle.active(player.foe()), .DestinyBond);
|
|
1275
|
+
try options.log.activate(.{ battle.active(player.foe()), .DestinyBond });
|
|
1245
1276
|
}
|
|
1246
1277
|
return true;
|
|
1247
1278
|
}
|
|
1248
1279
|
|
|
1249
1280
|
pub const Effects = struct {
|
|
1250
|
-
pub fn alwaysHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1251
|
-
_ = .{ battle, player, state, options }; // TODO
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
1281
|
pub fn attract(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1255
1282
|
const side = battle.side(player);
|
|
1256
1283
|
var foe = battle.foe(player);
|
|
@@ -1258,16 +1285,14 @@ pub const Effects = struct {
|
|
|
1258
1285
|
const gender: u8 = @intFromEnum(side.stored().dvs.gender);
|
|
1259
1286
|
const foe_gender: u8 = @intFromEnum(foe.stored().dvs.gender);
|
|
1260
1287
|
if (gender + foe_gender != 1) {
|
|
1261
|
-
|
|
1262
|
-
return;
|
|
1288
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
1263
1289
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1264
|
-
try options.log.lastmiss();
|
|
1265
|
-
|
|
1266
|
-
return;
|
|
1290
|
+
try options.log.lastmiss(.{});
|
|
1291
|
+
return options.log.miss(.{battle.active(player)});
|
|
1267
1292
|
}
|
|
1268
1293
|
|
|
1269
1294
|
foe.active.volatiles.Attract = true;
|
|
1270
|
-
try options.log.start(battle.active(player.foe()), .Attract);
|
|
1295
|
+
try options.log.start(.{ battle.active(player.foe()), .Attract });
|
|
1271
1296
|
}
|
|
1272
1297
|
|
|
1273
1298
|
pub fn batonPass(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1279,15 +1304,77 @@ pub const Effects = struct {
|
|
|
1279
1304
|
}
|
|
1280
1305
|
|
|
1281
1306
|
pub fn bellyDrum(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1282
|
-
|
|
1283
|
-
|
|
1307
|
+
assert(!state.miss);
|
|
1308
|
+
var side = battle.side(player);
|
|
1284
1309
|
|
|
1285
|
-
|
|
1286
|
-
|
|
1310
|
+
const ident = battle.active(player);
|
|
1311
|
+
|
|
1312
|
+
assert(side.active.boosts.atk >= -6 and side.active.boosts.atk <= 6);
|
|
1313
|
+
if (side.active.boosts.atk == 6 or side.active.boosts.atk == MAX_STAT_VALUE) {
|
|
1314
|
+
return options.log.fail(.{ ident, .None });
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
const hp = @max(side.stored().stats.hp / 2, 1);
|
|
1318
|
+
if (side.stored().hp <= hp) {
|
|
1319
|
+
try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
|
|
1320
|
+
try options.log.boost(.{ ident, .Attack, @as(i8, 2), .Silent });
|
|
1321
|
+
return options.log.fail(.{ ident, .None });
|
|
1322
|
+
} else {
|
|
1323
|
+
side.stored().hp -= hp;
|
|
1324
|
+
try options.log.damage(.{ ident, side.stored(), .None });
|
|
1325
|
+
|
|
1326
|
+
const before = side.active.boosts.atk;
|
|
1327
|
+
var i: usize = 0;
|
|
1328
|
+
while (i < 6 and side.active.stats.atk < MAX_STAT_VALUE) : (i += 1) {
|
|
1329
|
+
try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
|
|
1330
|
+
}
|
|
1331
|
+
try options.log.boost(.{ ident, .Attack, @as(i8, side.active.boosts.atk - before) });
|
|
1332
|
+
}
|
|
1287
1333
|
}
|
|
1288
1334
|
|
|
1335
|
+
pub const bide = struct {
|
|
1336
|
+
pub fn store(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1337
|
+
var side = battle.side(player);
|
|
1338
|
+
var volatiles = &side.active.volatiles;
|
|
1339
|
+
|
|
1340
|
+
const ident = battle.active(player);
|
|
1341
|
+
|
|
1342
|
+
if (!volatiles.Bide) return;
|
|
1343
|
+
|
|
1344
|
+
// if (options.calc.modify(player, .attacking)) |extend| {
|
|
1345
|
+
// if (!extend) volatiles.attacks = 0;
|
|
1346
|
+
// } else {
|
|
1347
|
+
volatiles.attacks -= 1;
|
|
1348
|
+
// }
|
|
1349
|
+
try options.chance.attacking(player, volatiles.attacks);
|
|
1350
|
+
|
|
1351
|
+
if (volatiles.attacks != 0) return options.log.activate(.{ ident, .Bide });
|
|
1352
|
+
|
|
1353
|
+
volatiles.Bide = false;
|
|
1354
|
+
try options.log.end(.{ ident, .Bide });
|
|
1355
|
+
|
|
1356
|
+
state.damage = volatiles.bide *% 2;
|
|
1357
|
+
volatiles.bide = 0;
|
|
1358
|
+
|
|
1359
|
+
if (state.damage == 0) state.miss = true;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
pub fn unleash(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1363
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1364
|
+
}
|
|
1365
|
+
};
|
|
1366
|
+
|
|
1289
1367
|
pub fn binding(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1290
|
-
|
|
1368
|
+
var foe = battle.foe(player);
|
|
1369
|
+
|
|
1370
|
+
if (state.miss) return;
|
|
1371
|
+
if (foe.active.volatiles.bind.duration > 0) return;
|
|
1372
|
+
if (foe.active.volatiles.Substitute) return;
|
|
1373
|
+
|
|
1374
|
+
foe.active.volatiles.bind = .{
|
|
1375
|
+
.duration = Rolls.bindingDuration(battle, player, options),
|
|
1376
|
+
.reason = Move.get(state.move).extra.protocol,
|
|
1377
|
+
};
|
|
1291
1378
|
}
|
|
1292
1379
|
|
|
1293
1380
|
pub fn burnChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1297,12 +1384,12 @@ pub const Effects = struct {
|
|
|
1297
1384
|
if (foe.active.volatiles.Substitute) return;
|
|
1298
1385
|
if (Status.any(foe_stored.status)) {
|
|
1299
1386
|
if (Status.is(foe_stored.status, .FRZ)) {
|
|
1300
|
-
assert(Move.get(state.move).type == .Fire);
|
|
1301
|
-
try options.log.curestatus(
|
|
1387
|
+
assert(Move.get(state.move).type == .Fire or state.move == .TriAttack);
|
|
1388
|
+
try options.log.curestatus(.{
|
|
1302
1389
|
battle.active(player.foe()),
|
|
1303
1390
|
foe_stored.status,
|
|
1304
1391
|
.Message,
|
|
1305
|
-
);
|
|
1392
|
+
});
|
|
1306
1393
|
foe_stored.status = 0;
|
|
1307
1394
|
}
|
|
1308
1395
|
return;
|
|
@@ -1311,11 +1398,14 @@ pub const Effects = struct {
|
|
|
1311
1398
|
if (foe.conditions.Safeguard) return;
|
|
1312
1399
|
// Nothing is immune to Fire-type, but Ghosts are immune to burnChance called by Tri Attack
|
|
1313
1400
|
if (state.immune()) return;
|
|
1314
|
-
if (foe.active.types.includes(Move.get(state.move).type))
|
|
1401
|
+
if (foe.active.types.includes(Move.get(state.move).type) and state.move != .TriAttack) {
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1315
1404
|
|
|
1316
1405
|
foe_stored.status = Status.init(.BRN);
|
|
1317
1406
|
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
1318
|
-
|
|
1407
|
+
|
|
1408
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1319
1409
|
|
|
1320
1410
|
// TODO: check for status berry
|
|
1321
1411
|
}
|
|
@@ -1335,10 +1425,7 @@ pub const Effects = struct {
|
|
|
1335
1425
|
for (side.active.moves) |m| {
|
|
1336
1426
|
if (convertible(side.active, m.id)) n += 1;
|
|
1337
1427
|
}
|
|
1338
|
-
if (n == 0) {
|
|
1339
|
-
try options.log.fail(battle.active(player), .None);
|
|
1340
|
-
return;
|
|
1341
|
-
}
|
|
1428
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
1342
1429
|
|
|
1343
1430
|
side.active.types.type1 = try Rolls.conversion(battle, player, n, options);
|
|
1344
1431
|
side.active.types.type2 = side.active.types.type1;
|
|
@@ -1348,26 +1435,87 @@ pub const Effects = struct {
|
|
|
1348
1435
|
var side = battle.side(player);
|
|
1349
1436
|
|
|
1350
1437
|
const last = battle.foe(player).lastMove(false);
|
|
1351
|
-
if (last == .None) {
|
|
1352
|
-
try options.log.fail(battle.active(player), .None);
|
|
1353
|
-
return;
|
|
1354
|
-
}
|
|
1438
|
+
if (last == .None) return options.log.fail(.{ battle.active(player), .None });
|
|
1355
1439
|
const move = Move.get(last);
|
|
1356
|
-
if (move.type == .@"???") {
|
|
1357
|
-
try options.log.fail(battle.active(player), .None);
|
|
1358
|
-
return;
|
|
1359
|
-
}
|
|
1440
|
+
if (move.type == .@"???") return options.log.fail(.{ battle.active(player), .None });
|
|
1360
1441
|
|
|
1361
1442
|
side.active.types.type1 = try Rolls.conversion2(battle, player, move.type, options);
|
|
1362
1443
|
side.active.types.type2 = side.active.types.type1;
|
|
1363
1444
|
}
|
|
1364
1445
|
|
|
1365
|
-
pub fn counter(battle: anytype, player: Player, state: *State,
|
|
1366
|
-
|
|
1446
|
+
pub fn counter(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1447
|
+
assert(state.move == .Counter or state.move == .MirrorCoat);
|
|
1448
|
+
|
|
1449
|
+
const foe = battle.foe(player);
|
|
1450
|
+
|
|
1451
|
+
state.miss = true;
|
|
1452
|
+
const last = foe.lastMove(false);
|
|
1453
|
+
|
|
1454
|
+
if (last == .None) return;
|
|
1455
|
+
if (last == state.move) return;
|
|
1456
|
+
|
|
1457
|
+
const immune = state.immune();
|
|
1458
|
+
state.effectiveness = Effectiveness.neutral;
|
|
1459
|
+
if (immune) {
|
|
1460
|
+
state.damage = 0;
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
const move = Move.get(last);
|
|
1465
|
+
if (state.bp == 0) return;
|
|
1466
|
+
if (move.type.special() != (last == .MirrorCoat)) return;
|
|
1467
|
+
if (state.damage == 0) return;
|
|
1468
|
+
|
|
1469
|
+
state.damage *|= 2;
|
|
1470
|
+
state.miss = false;
|
|
1367
1471
|
}
|
|
1368
1472
|
|
|
1369
1473
|
pub fn curse(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1370
|
-
|
|
1474
|
+
var side = battle.side(player);
|
|
1475
|
+
var foe = battle.foe(player);
|
|
1476
|
+
|
|
1477
|
+
const ident = battle.active(player);
|
|
1478
|
+
const foe_ident = battle.active(player.foe());
|
|
1479
|
+
|
|
1480
|
+
if (side.active.types.includes(.Ghost)) {
|
|
1481
|
+
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1482
|
+
try options.log.lastmiss(.{});
|
|
1483
|
+
return options.log.miss(.{battle.active(player)});
|
|
1484
|
+
} else if (foe.active.volatiles.Substitute) {
|
|
1485
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1486
|
+
} else if (foe.active.volatiles.Curse) {
|
|
1487
|
+
return options.log.fail(.{ foe_ident, .None });
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
foe.active.volatiles.Curse = true;
|
|
1491
|
+
try options.log.start(.{ foe_ident, .Curse });
|
|
1492
|
+
|
|
1493
|
+
side.stored().hp -= @max(side.stored().stats.hp / 2, 1);
|
|
1494
|
+
try options.log.damage(.{ ident, side.stored(), .Curse });
|
|
1495
|
+
} else {
|
|
1496
|
+
const stats = &side.active.stats;
|
|
1497
|
+
const boosts = &side.active.boosts;
|
|
1498
|
+
|
|
1499
|
+
const max_atk = boosts.atk == 6 or stats.atk == MAX_STAT_VALUE;
|
|
1500
|
+
const max_def = boosts.def == 6 or stats.def == MAX_STAT_VALUE;
|
|
1501
|
+
if (max_atk and max_def) return options.log.fail(.{ ident, .None });
|
|
1502
|
+
|
|
1503
|
+
if (!(boosts.spe == -6 or stats.spe == 1)) {
|
|
1504
|
+
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
1505
|
+
try unboostStat(battle, side, stats, boosts, "spe", 1);
|
|
1506
|
+
try options.log.boost(.{ ident, .Speed, @as(i8, -1) });
|
|
1507
|
+
}
|
|
1508
|
+
if (!max_atk) {
|
|
1509
|
+
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
1510
|
+
try boostStat(battle, side, stats, boosts, "atk", 1);
|
|
1511
|
+
try options.log.boost(.{ ident, .Attack, @as(i8, 1) });
|
|
1512
|
+
}
|
|
1513
|
+
if (!max_def) {
|
|
1514
|
+
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
1515
|
+
try boostStat(battle, side, stats, boosts, "def", 1);
|
|
1516
|
+
try options.log.boost(.{ ident, .Defense, @as(i8, 1) });
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1371
1519
|
}
|
|
1372
1520
|
|
|
1373
1521
|
pub fn defenseCurl(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
@@ -1377,14 +1525,13 @@ pub const Effects = struct {
|
|
|
1377
1525
|
pub fn defrost(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1378
1526
|
var side = battle.side(player);
|
|
1379
1527
|
assert(Status.is(side.stored().status, .FRZ));
|
|
1380
|
-
|
|
1381
|
-
try options.log.curestatus(battle.active(player), side.stored().status, .Message);
|
|
1528
|
+
try options.log.curestatus(.{ battle.active(player), side.stored().status, .Message });
|
|
1382
1529
|
side.stored().status = 0;
|
|
1383
1530
|
}
|
|
1384
1531
|
|
|
1385
1532
|
pub fn destinyBond(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1386
1533
|
battle.side(player).active.volatiles.DestinyBond = true;
|
|
1387
|
-
try options.log.singlemove(battle.active(player), Move.DestinyBond);
|
|
1534
|
+
try options.log.singlemove(.{ battle.active(player), Move.DestinyBond });
|
|
1388
1535
|
}
|
|
1389
1536
|
|
|
1390
1537
|
pub fn disable(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
@@ -1392,16 +1539,10 @@ pub const Effects = struct {
|
|
|
1392
1539
|
var volatiles = &foe.active.volatiles;
|
|
1393
1540
|
const foe_ident = battle.active(player.foe());
|
|
1394
1541
|
|
|
1395
|
-
if (volatiles.disable.move != 0) {
|
|
1396
|
-
try options.log.fail(foe_ident, .None);
|
|
1397
|
-
return;
|
|
1398
|
-
}
|
|
1542
|
+
if (volatiles.disable.move != 0) return options.log.fail(.{ foe_ident, .None });
|
|
1399
1543
|
|
|
1400
1544
|
const last = battle.foe(player).lastMove(false);
|
|
1401
|
-
if (last == .None or last == .Struggle) {
|
|
1402
|
-
try options.log.fail(foe_ident, .None);
|
|
1403
|
-
return;
|
|
1404
|
-
}
|
|
1545
|
+
if (last == .None or last == .Struggle) return options.log.fail(.{ foe_ident, .None });
|
|
1405
1546
|
|
|
1406
1547
|
var slot: u3 = 0;
|
|
1407
1548
|
for (foe.active.moves) |m| {
|
|
@@ -1410,30 +1551,38 @@ pub const Effects = struct {
|
|
|
1410
1551
|
if (m.id == last) break;
|
|
1411
1552
|
}
|
|
1412
1553
|
const move = foe.active.move(slot);
|
|
1413
|
-
if (move.pp == 0) {
|
|
1414
|
-
try options.log.fail(foe_ident, .None);
|
|
1415
|
-
return;
|
|
1416
|
-
}
|
|
1554
|
+
if (move.pp == 0) return options.log.fail(.{ foe_ident, .None });
|
|
1417
1555
|
|
|
1418
1556
|
volatiles.disable.move = slot;
|
|
1419
1557
|
volatiles.disable.duration = Rolls.disableDuration(battle, player, options);
|
|
1420
1558
|
|
|
1421
|
-
try options.log.
|
|
1559
|
+
try options.log.start(.{ foe_ident, .Disable, move.id });
|
|
1422
1560
|
}
|
|
1423
1561
|
|
|
1424
|
-
pub
|
|
1425
|
-
|
|
1426
|
-
|
|
1562
|
+
pub const double = struct {
|
|
1563
|
+
pub fn flying(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1564
|
+
if (battle.foe(player).active.volatiles.Flying) state.damage *|= 2;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
pub fn underground(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1568
|
+
if (battle.foe(player).active.volatiles.Underground) state.damage *|= 2;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
pub fn minimize(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1572
|
+
if (battle.foe(player).active.volatiles.minimized) state.damage *|= 2;
|
|
1573
|
+
}
|
|
1574
|
+
};
|
|
1427
1575
|
|
|
1428
1576
|
pub fn drainHP(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1429
1577
|
var stored = battle.side(player).stored();
|
|
1578
|
+
const ident = battle.active(player);
|
|
1430
1579
|
|
|
1431
1580
|
assert(state.damage != 0);
|
|
1432
1581
|
|
|
1433
1582
|
if (stored.hp == stored.stats.hp) return;
|
|
1434
1583
|
stored.hp = @min(stored.stats.hp, stored.hp + @max(state.damage / 2, 1));
|
|
1435
1584
|
|
|
1436
|
-
try options.log.
|
|
1585
|
+
try options.log.heal(.{ ident, stored, .Drain, battle.active(player.foe()) });
|
|
1437
1586
|
}
|
|
1438
1587
|
|
|
1439
1588
|
pub fn encore(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1445,10 +1594,7 @@ pub const Effects = struct {
|
|
|
1445
1594
|
const failed = volatiles.Encore or
|
|
1446
1595
|
(last == .None or last == .Struggle or
|
|
1447
1596
|
last == .Encore or .last == .MirrorMove);
|
|
1448
|
-
if (failed) {
|
|
1449
|
-
try options.log.fail(foe_ident, .None);
|
|
1450
|
-
return;
|
|
1451
|
-
}
|
|
1597
|
+
if (failed) return options.log.fail(.{ foe_ident, .None });
|
|
1452
1598
|
|
|
1453
1599
|
var slot: u3 = 0;
|
|
1454
1600
|
for (foe.active.moves) |m| {
|
|
@@ -1457,10 +1603,7 @@ pub const Effects = struct {
|
|
|
1457
1603
|
if (m.id == last) break;
|
|
1458
1604
|
}
|
|
1459
1605
|
const move = foe.active.move(slot);
|
|
1460
|
-
if (move.pp == 0) {
|
|
1461
|
-
try options.log.fail(foe_ident, .None);
|
|
1462
|
-
return;
|
|
1463
|
-
}
|
|
1606
|
+
if (move.pp == 0) return options.log.fail(.{ foe_ident, .None });
|
|
1464
1607
|
|
|
1465
1608
|
volatiles.Encore = true;
|
|
1466
1609
|
volatiles.encore = Rolls.encoreDuration(battle, player, options);
|
|
@@ -1468,11 +1611,19 @@ pub const Effects = struct {
|
|
|
1468
1611
|
// TODO ???
|
|
1469
1612
|
_ = state;
|
|
1470
1613
|
|
|
1471
|
-
try options.log.
|
|
1614
|
+
try options.log.start(.{ foe_ident, .Encore, move.id });
|
|
1472
1615
|
}
|
|
1473
1616
|
|
|
1474
|
-
pub fn explode(battle: anytype, player: Player,
|
|
1475
|
-
|
|
1617
|
+
pub fn explode(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
1618
|
+
var side = battle.side(player);
|
|
1619
|
+
var stored = side.stored();
|
|
1620
|
+
|
|
1621
|
+
stored.hp = 0;
|
|
1622
|
+
// Pokémon Showdown sets the status to 0 on faint(), and we need to do the same to be able
|
|
1623
|
+
// to correctly implement Pokémon Showdown's dumb speed-based switch mechanics
|
|
1624
|
+
if (!showdown) stored.status = 0;
|
|
1625
|
+
side.active.volatiles.LeechSeed = false;
|
|
1626
|
+
side.active.volatiles.DestinyBond = false;
|
|
1476
1627
|
}
|
|
1477
1628
|
|
|
1478
1629
|
pub fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
@@ -1500,10 +1651,6 @@ pub const Effects = struct {
|
|
|
1500
1651
|
};
|
|
1501
1652
|
}
|
|
1502
1653
|
|
|
1503
|
-
pub fn flameWheel(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1504
|
-
_ = .{ battle, player, state, options }; // TODO
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
1654
|
pub fn flinchChance(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1508
1655
|
const foe = battle.foe(player);
|
|
1509
1656
|
const status = foe.stored().status;
|
|
@@ -1526,12 +1673,11 @@ pub const Effects = struct {
|
|
|
1526
1673
|
var side = battle.side(player);
|
|
1527
1674
|
|
|
1528
1675
|
if (side.active.volatiles.FocusEnergy) {
|
|
1529
|
-
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
1530
|
-
return;
|
|
1676
|
+
return if (!showdown) try options.log.fail(.{ battle.active(player), .None });
|
|
1531
1677
|
}
|
|
1532
1678
|
|
|
1533
1679
|
side.active.volatiles.FocusEnergy = true;
|
|
1534
|
-
try options.log.start(battle.active(player), .FocusEnergy);
|
|
1680
|
+
try options.log.start(.{ battle.active(player), .FocusEnergy });
|
|
1535
1681
|
}
|
|
1536
1682
|
|
|
1537
1683
|
var SLOTS: [5]u4 = .{ 0, 0, 0, 0, 0 };
|
|
@@ -1541,7 +1687,7 @@ pub const Effects = struct {
|
|
|
1541
1687
|
|
|
1542
1688
|
const foe = battle.foe(player);
|
|
1543
1689
|
|
|
1544
|
-
if (state.first) return
|
|
1690
|
+
if (state.first) return options.log.fail(.{ battle.active(player), .None });
|
|
1545
1691
|
|
|
1546
1692
|
var n: u4 = 0;
|
|
1547
1693
|
var i: u4 = 0;
|
|
@@ -1555,10 +1701,7 @@ pub const Effects = struct {
|
|
|
1555
1701
|
}
|
|
1556
1702
|
}
|
|
1557
1703
|
|
|
1558
|
-
if (n == 0) {
|
|
1559
|
-
try options.log.fail(battle.active(player), .None);
|
|
1560
|
-
return;
|
|
1561
|
-
}
|
|
1704
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
1562
1705
|
|
|
1563
1706
|
const slot = try Rolls.forceSwitch(battle, player, SLOTS[0..i], n, options);
|
|
1564
1707
|
try switchIn(battle, player.foe(), slot, .Drag, options);
|
|
@@ -1568,16 +1711,14 @@ pub const Effects = struct {
|
|
|
1568
1711
|
var foe = battle.foe(player);
|
|
1569
1712
|
|
|
1570
1713
|
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1571
|
-
try options.log.lastmiss();
|
|
1572
|
-
|
|
1573
|
-
return;
|
|
1714
|
+
try options.log.lastmiss(.{});
|
|
1715
|
+
return options.log.miss(.{battle.active(player)});
|
|
1574
1716
|
} else if (foe.active.volatiles.Foresight) {
|
|
1575
|
-
|
|
1576
|
-
return;
|
|
1717
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
1577
1718
|
}
|
|
1578
1719
|
|
|
1579
1720
|
foe.active.volatiles.Foresight = true;
|
|
1580
|
-
try options.log.start(battle.active(player.foe()), .FocusEnergy);
|
|
1721
|
+
try options.log.start(.{ battle.active(player.foe()), .FocusEnergy });
|
|
1581
1722
|
}
|
|
1582
1723
|
|
|
1583
1724
|
pub fn freezeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1589,15 +1730,18 @@ pub const Effects = struct {
|
|
|
1589
1730
|
if (Status.any(foe_stored.status)) return;
|
|
1590
1731
|
|
|
1591
1732
|
// Nothing is immune to Ice-type, but Ghosts are immune to freezeChance called by Tri Attack
|
|
1733
|
+
if (state.immune()) return;
|
|
1592
1734
|
if (battle.field.weather == .Sun) return;
|
|
1593
|
-
if (foe.active.types.includes(Move.get(state.move).type))
|
|
1735
|
+
if (foe.active.types.includes(Move.get(state.move).type) and state.move != .TriAttack) {
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1594
1738
|
if (!state.proc) return;
|
|
1595
1739
|
if (foe.conditions.Safeguard) return;
|
|
1596
1740
|
// Freeze Clause Mod
|
|
1597
1741
|
if (showdown) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
|
|
1598
1742
|
|
|
1599
1743
|
foe_stored.status = Status.init(.FRZ);
|
|
1600
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
1744
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
1601
1745
|
|
|
1602
1746
|
// TODO: check for status berry
|
|
1603
1747
|
|
|
@@ -1605,8 +1749,26 @@ pub const Effects = struct {
|
|
|
1605
1749
|
foe.active.volatiles.frozen = true;
|
|
1606
1750
|
}
|
|
1607
1751
|
|
|
1608
|
-
|
|
1609
|
-
|
|
1752
|
+
// TODO FIXME showdown calls this before calcDamage (after checkHit) and modifies state.bp!
|
|
1753
|
+
pub fn furyCutter(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1754
|
+
var side = battle.side(player);
|
|
1755
|
+
var volatiles = &side.active.volatiles;
|
|
1756
|
+
|
|
1757
|
+
if (state.miss) {
|
|
1758
|
+
volatiles.fury_cutter = 0;
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
assert(volatiles.fury_cutter >= 0 and volatiles.fury_cutter <= 5);
|
|
1763
|
+
if (volatiles.fury_cutter == 5) return;
|
|
1764
|
+
volatiles.fury_cutter += 1;
|
|
1765
|
+
|
|
1766
|
+
if (showdown) {
|
|
1767
|
+
state.bp *= 2;
|
|
1768
|
+
assert(state.bp <= 160);
|
|
1769
|
+
} else {
|
|
1770
|
+
state.damage *|= 2;
|
|
1771
|
+
}
|
|
1610
1772
|
}
|
|
1611
1773
|
|
|
1612
1774
|
pub fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1619,73 +1781,222 @@ pub const Effects = struct {
|
|
|
1619
1781
|
state.bp = ((if (frustration) 255 - val else val) * 10) / 25;
|
|
1620
1782
|
}
|
|
1621
1783
|
|
|
1622
|
-
pub fn haze(battle: anytype, player: Player,
|
|
1623
|
-
|
|
1784
|
+
pub fn haze(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1785
|
+
var side = battle.side(player);
|
|
1786
|
+
var stats = unmodifiedStats(battle, side);
|
|
1787
|
+
side.active.boosts = .{};
|
|
1788
|
+
inline for (STATS) |s| @field(side.active.stats, s) = @field(stats, s);
|
|
1789
|
+
statusModify(side.stored().status, &side.active.stats);
|
|
1790
|
+
|
|
1791
|
+
var foe = battle.foe(player);
|
|
1792
|
+
stats = unmodifiedStats(battle, foe);
|
|
1793
|
+
foe.active.boosts = .{};
|
|
1794
|
+
inline for (STATS) |s| @field(foe.active.stats, s) = @field(stats, s);
|
|
1795
|
+
statusModify(foe.stored().status, &foe.active.stats);
|
|
1796
|
+
|
|
1797
|
+
try options.log.clearallboost(.{});
|
|
1624
1798
|
}
|
|
1625
1799
|
|
|
1626
1800
|
pub fn heal(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1627
|
-
|
|
1628
|
-
|
|
1801
|
+
var side = battle.side(player);
|
|
1802
|
+
var stored = side.stored();
|
|
1803
|
+
const ident = battle.active(player);
|
|
1629
1804
|
|
|
1630
|
-
|
|
1631
|
-
|
|
1805
|
+
if (stored.stats.hp == stored.hp) return options.log.fail(.{ ident, .None });
|
|
1806
|
+
|
|
1807
|
+
const rest = state.move == .Rest;
|
|
1808
|
+
if (rest) {
|
|
1809
|
+
// Adding the sleep status runs the sleep condition handler to roll duration
|
|
1810
|
+
if (showdown) battle.rng.advance(1);
|
|
1811
|
+
side.active.volatiles.Toxic = false;
|
|
1812
|
+
stored.status = Status.slf(2);
|
|
1813
|
+
recomputeStats(battle, side);
|
|
1814
|
+
try options.log.status(.{ ident, stored.status, .From, Move.Rest });
|
|
1815
|
+
stored.hp = stored.stats.hp;
|
|
1816
|
+
} else {
|
|
1817
|
+
stored.hp = @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2));
|
|
1818
|
+
}
|
|
1819
|
+
try options.log.heal(.{ ident, stored, if (rest) Heal.Silent else Heal.None });
|
|
1632
1820
|
}
|
|
1633
1821
|
|
|
1634
|
-
pub fn
|
|
1635
|
-
|
|
1822
|
+
pub fn healBell(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1823
|
+
var side = battle.side(player);
|
|
1824
|
+
side.active.volatiles.Nightmare = false;
|
|
1825
|
+
|
|
1826
|
+
try options.log.cureteam(.{battle.active(player)});
|
|
1827
|
+
for (&side.pokemon) |*p| p.status = 0;
|
|
1828
|
+
|
|
1829
|
+
// We technically only need to recompute stats if the active Pokémon was burned/paralyzed,
|
|
1830
|
+
// but adding a branch to check for that is probably more expensive than just recomputing
|
|
1831
|
+
recomputeStats(battle, side);
|
|
1636
1832
|
}
|
|
1637
1833
|
|
|
1638
|
-
pub fn
|
|
1639
|
-
|
|
1834
|
+
pub fn hiddenPower(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1835
|
+
const side = battle.side(player);
|
|
1836
|
+
state.bp = side.stored().dvs.power();
|
|
1837
|
+
state.type = side.stored().dvs.type;
|
|
1640
1838
|
}
|
|
1641
1839
|
|
|
1642
|
-
pub fn
|
|
1643
|
-
|
|
1840
|
+
pub fn hyperBeam(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1841
|
+
battle.side(player).active.volatiles.Recharging = true;
|
|
1842
|
+
try options.log.mustrecharge(.{battle.active(player)});
|
|
1644
1843
|
}
|
|
1645
1844
|
|
|
1646
1845
|
pub fn leechSeed(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1647
|
-
|
|
1846
|
+
var foe = battle.foe(player);
|
|
1847
|
+
|
|
1848
|
+
if (showdown) {
|
|
1849
|
+
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
1850
|
+
const invulnerable = foe.active.volatiles.Underground or foe.active.volatiles.Flying;
|
|
1851
|
+
if (!invulnerable and foe.active.types.includes(.Grass)) {
|
|
1852
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
1853
|
+
}
|
|
1854
|
+
try checkHit(battle, player, state, options);
|
|
1855
|
+
if (state.miss) {
|
|
1856
|
+
try options.log.lastmiss(.{});
|
|
1857
|
+
return options.log.miss(.{battle.active(player)});
|
|
1858
|
+
}
|
|
1859
|
+
if (foe.active.volatiles.LeechSeed) return;
|
|
1860
|
+
} else {
|
|
1861
|
+
try checkHit(battle, player, state, options);
|
|
1862
|
+
const miss =
|
|
1863
|
+
state.miss or foe.active.types.includes(.Grass) or foe.active.volatiles.LeechSeed;
|
|
1864
|
+
if (miss) {
|
|
1865
|
+
try options.log.lastmiss(.{});
|
|
1866
|
+
return options.log.miss(.{battle.active(player)});
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
foe.active.volatiles.LeechSeed = true;
|
|
1871
|
+
|
|
1872
|
+
try options.log.start(.{ battle.active(player.foe()), .LeechSeed });
|
|
1648
1873
|
}
|
|
1649
1874
|
|
|
1650
1875
|
pub fn lockOn(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1651
|
-
|
|
1876
|
+
var foe = battle.foe(player);
|
|
1877
|
+
const foe_ident = battle.active(player.foe());
|
|
1878
|
+
|
|
1879
|
+
if (foe.active.volatiles.Substitute) {
|
|
1880
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1881
|
+
} else if (state.miss) {
|
|
1882
|
+
return options.log.fail(.{ foe_ident, .None });
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
foe.active.volatiles.LockOn = true;
|
|
1886
|
+
try options.log.activate(.{ battle.active(player), .LockOn });
|
|
1652
1887
|
}
|
|
1653
1888
|
|
|
1654
1889
|
const MAGNITUDE_POWER = [_]u8{ 10, 30, 50, 70, 90, 110, 150 };
|
|
1655
1890
|
|
|
1656
1891
|
pub fn magnitude(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1657
|
-
|
|
1892
|
+
const num = try Rolls.magnitude(battle, player, options);
|
|
1893
|
+
try options.log.activate(.{ battle.active(player), .Magnitude, num });
|
|
1894
|
+
state.bp = MAGNITUDE_POWER[num - 4];
|
|
1658
1895
|
}
|
|
1659
1896
|
|
|
1660
|
-
pub fn meanLook(battle: anytype, player: Player,
|
|
1661
|
-
|
|
1897
|
+
pub fn meanLook(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1898
|
+
var foe = battle.foe(player);
|
|
1899
|
+
|
|
1900
|
+
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1901
|
+
if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1902
|
+
try options.log.lastmiss(.{});
|
|
1903
|
+
return options.log.miss(.{battle.active(player)});
|
|
1904
|
+
} else if (foe.active.volatiles.Trapped) {
|
|
1905
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
foe.active.volatiles.Trapped = true;
|
|
1662
1909
|
}
|
|
1663
1910
|
|
|
1664
|
-
pub fn metronome(
|
|
1665
|
-
|
|
1911
|
+
pub fn metronome(
|
|
1912
|
+
battle: anytype,
|
|
1913
|
+
player: Player,
|
|
1914
|
+
state: *State,
|
|
1915
|
+
options: anytype,
|
|
1916
|
+
) anyerror!void {
|
|
1917
|
+
var side = battle.side(player);
|
|
1918
|
+
|
|
1919
|
+
side.last_used_move = .None;
|
|
1920
|
+
side.active.volatiles.dirty = false;
|
|
1921
|
+
|
|
1922
|
+
// if (wPlayerCharging) ???
|
|
1923
|
+
// wPlayerCharging = 1
|
|
1924
|
+
|
|
1925
|
+
// FIXME
|
|
1926
|
+
state.move = try Rolls.metronome(battle, player, options);
|
|
1927
|
+
state.from = .Metronome;
|
|
1928
|
+
return generated.runMove(battle, player, state, options);
|
|
1666
1929
|
}
|
|
1667
1930
|
|
|
1668
1931
|
pub fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1669
|
-
|
|
1670
|
-
}
|
|
1932
|
+
var side = battle.side(player);
|
|
1671
1933
|
|
|
1672
|
-
|
|
1673
|
-
|
|
1934
|
+
if (state.miss) {
|
|
1935
|
+
if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1936
|
+
try options.log.lastmiss(.{});
|
|
1937
|
+
return options.log.miss(.{battle.active(player)});
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
const move = battle.foe(player).lastMove(false);
|
|
1941
|
+
if (move == .None or move == .Struggle) {
|
|
1942
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
var slot: u4 = 0;
|
|
1946
|
+
for (side.active.moves, 0..) |m, i| {
|
|
1947
|
+
if (m.id == move) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1948
|
+
if (m.id == .Mimic) slot = @intCast(i + 1);
|
|
1949
|
+
}
|
|
1950
|
+
assert(slot != 0 and slot == state.mslot);
|
|
1951
|
+
|
|
1952
|
+
side.active.move(slot).id = move;
|
|
1953
|
+
side.active.move(slot).pp = 5;
|
|
1954
|
+
try options.log.start(.{ battle.active(player), .Mimic, move });
|
|
1674
1955
|
}
|
|
1675
1956
|
|
|
1676
|
-
pub fn mirrorMove(
|
|
1677
|
-
|
|
1957
|
+
pub fn mirrorMove(
|
|
1958
|
+
battle: anytype,
|
|
1959
|
+
player: Player,
|
|
1960
|
+
state: *State,
|
|
1961
|
+
options: anytype,
|
|
1962
|
+
) anyerror!void {
|
|
1963
|
+
var side = battle.side(player);
|
|
1964
|
+
const foe = battle.foe(player);
|
|
1965
|
+
|
|
1966
|
+
side.last_used_move = .None;
|
|
1967
|
+
side.active.volatiles.dirty = false;
|
|
1968
|
+
|
|
1969
|
+
const last = foe.lastMove(false);
|
|
1970
|
+
|
|
1971
|
+
if (last == .None) return options.log.fail(.{ battle.active(player), .None });
|
|
1972
|
+
const knows = knows: for (side.active.moves) |m| {
|
|
1973
|
+
if (last == m.id) break :knows true;
|
|
1974
|
+
} else false;
|
|
1975
|
+
if (knows) return options.log.fail(.{ battle.active(player), .None });
|
|
1976
|
+
|
|
1977
|
+
// if (wPlayerCharging) ???
|
|
1978
|
+
// wPlayerCharging = 1
|
|
1979
|
+
|
|
1980
|
+
// FIXME
|
|
1981
|
+
state.move = last;
|
|
1982
|
+
state.from = .MirrorMove;
|
|
1983
|
+
return generated.runMove(battle, player, state, options);
|
|
1678
1984
|
}
|
|
1679
1985
|
|
|
1680
|
-
pub fn mist(battle: anytype, player: Player,
|
|
1681
|
-
|
|
1986
|
+
pub fn mist(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1987
|
+
var side = battle.side(player);
|
|
1988
|
+
|
|
1989
|
+
if (side.active.volatiles.Mist) return options.log.fail(.{ battle.active(player), .None });
|
|
1990
|
+
side.active.volatiles.Mist = true;
|
|
1991
|
+
|
|
1992
|
+
try options.log.start(.{ battle.active(player), .Mist });
|
|
1682
1993
|
}
|
|
1683
1994
|
|
|
1684
1995
|
pub fn weatherHeal(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1685
1996
|
var stored = battle.side(player).stored();
|
|
1686
1997
|
const ident = battle.active(player);
|
|
1687
1998
|
|
|
1688
|
-
if (stored.hp == stored.stats.hp) return
|
|
1999
|
+
if (stored.hp == stored.stats.hp) return options.log.fail(.{ ident, .None });
|
|
1689
2000
|
|
|
1690
2001
|
stored.hp = switch (battle.field.weather) {
|
|
1691
2002
|
.Sun => stored.stats.hp,
|
|
@@ -1693,7 +2004,7 @@ pub const Effects = struct {
|
|
|
1693
2004
|
else => @min(stored.stats.hp, stored.hp + (stored.stats.hp / 4)),
|
|
1694
2005
|
};
|
|
1695
2006
|
|
|
1696
|
-
try options.log.heal(ident, stored,
|
|
2007
|
+
try options.log.heal(.{ ident, stored, .None });
|
|
1697
2008
|
}
|
|
1698
2009
|
|
|
1699
2010
|
pub fn multiHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1701,32 +2012,96 @@ pub const Effects = struct {
|
|
|
1701
2012
|
}
|
|
1702
2013
|
|
|
1703
2014
|
pub fn nightmare(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1704
|
-
|
|
2015
|
+
var foe = battle.foe(player);
|
|
2016
|
+
var foe_volatiles = &foe.active.volatiles;
|
|
2017
|
+
const foe_ident = battle.active(player.foe());
|
|
2018
|
+
|
|
2019
|
+
if (foe_volatiles.Flying or foe_volatiles.Underground) {
|
|
2020
|
+
if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2021
|
+
try options.log.lastmiss(.{});
|
|
2022
|
+
return options.log.miss(.{battle.active(player)});
|
|
2023
|
+
} else if (foe_volatiles.Substitute) {
|
|
2024
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
2025
|
+
} else if (!Status.is(foe.stored().status, .SLP) or foe_volatiles.Nightmare) {
|
|
2026
|
+
return options.log.fail(.{ foe_ident, .None });
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
foe_volatiles.Nightmare = true;
|
|
2030
|
+
try options.log.start(.{ battle.active(player.foe()), .Nightmare });
|
|
1705
2031
|
}
|
|
1706
2032
|
|
|
1707
|
-
pub
|
|
1708
|
-
|
|
1709
|
-
|
|
2033
|
+
pub const ohko = struct {
|
|
2034
|
+
// OHKO calls adjustDamage but only actually cares about immunity
|
|
2035
|
+
pub fn immune(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2036
|
+
const move = Move.get(state.move);
|
|
1710
2037
|
|
|
1711
|
-
|
|
2038
|
+
assert(move.type == .Normal or move.type == .Ground);
|
|
2039
|
+
const foe = battle.foe(player);
|
|
2040
|
+
const zero = if (move.type == .Normal)
|
|
2041
|
+
foe.active.types.includes(.Ghost) and !foe.active.volatiles.Foresight
|
|
2042
|
+
else
|
|
2043
|
+
foe.active.types.includes(.Flying);
|
|
2044
|
+
state.effectiveness = if (zero) 0 else Effectiveness.neutral;
|
|
2045
|
+
}
|
|
1712
2046
|
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
if (delta == 0) return try options.log.immune(foe_ident, .OHKO);
|
|
2047
|
+
pub fn damage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2048
|
+
const side = battle.side(player);
|
|
2049
|
+
const foe = battle.foe(player);
|
|
1717
2050
|
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
2051
|
+
const foe_ident = battle.active(player.foe());
|
|
2052
|
+
|
|
2053
|
+
state.damage = 0;
|
|
2054
|
+
if (state.immune()) return options.log.immune(.{ foe_ident, .None });
|
|
2055
|
+
const delta = side.stored().level -| foe.stored().level;
|
|
2056
|
+
if (delta == 0) return options.log.immune(.{ foe_ident, .OHKO });
|
|
2057
|
+
|
|
2058
|
+
state.accuracy = 2 * delta +| Gen12.percent(30);
|
|
2059
|
+
try checkHit(battle, player, state, options);
|
|
2060
|
+
if (!state.miss) state.damage = 65535;
|
|
2061
|
+
state.ohko = true;
|
|
2062
|
+
}
|
|
2063
|
+
};
|
|
1723
2064
|
|
|
1724
2065
|
pub fn painSplit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1725
|
-
|
|
2066
|
+
var side = battle.side(player);
|
|
2067
|
+
var foe = battle.foe(player);
|
|
2068
|
+
|
|
2069
|
+
const ident = battle.active(player);
|
|
2070
|
+
const foe_ident = battle.active(player.foe());
|
|
2071
|
+
|
|
2072
|
+
if (state.miss) return options.log.fail(.{ foe_ident, .None });
|
|
2073
|
+
if (foe.active.volatiles.Substitute) {
|
|
2074
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
const avg = @max((foe.stored().hp + side.stored().hp) / 2, 1);
|
|
2078
|
+
foe.stored().hp -= avg;
|
|
2079
|
+
side.stored().hp = avg;
|
|
2080
|
+
|
|
2081
|
+
try options.log.sethp(.{ foe_ident, foe.stored(), .Silent });
|
|
2082
|
+
try options.log.sethp(.{ ident, side.stored(), .None });
|
|
1726
2083
|
}
|
|
1727
2084
|
|
|
1728
2085
|
pub fn paralyze(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1729
|
-
|
|
2086
|
+
var foe = battle.foe(player);
|
|
2087
|
+
const foe_ident = battle.active(player.foe());
|
|
2088
|
+
|
|
2089
|
+
if (Status.is(foe.stored().status, .PAR)) {
|
|
2090
|
+
return options.log.fail(.{ foe_ident, .Paralysis });
|
|
2091
|
+
}
|
|
2092
|
+
if (state.immune()) return options.log.immune(.{ foe_ident, .None });
|
|
2093
|
+
if (Status.any(foe.stored().status)) return options.log.fail(.{ foe_ident, .None });
|
|
2094
|
+
if (state.miss) {
|
|
2095
|
+
try options.log.lastmiss(.{});
|
|
2096
|
+
return options.log.miss(.{battle.active(player)});
|
|
2097
|
+
} else if (foe.active.volatiles.Substitute) {
|
|
2098
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
foe.stored().status = Status.init(.PAR);
|
|
2102
|
+
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2103
|
+
|
|
2104
|
+
try options.log.status(.{ foe_ident, foe.stored().status, .None });
|
|
1730
2105
|
}
|
|
1731
2106
|
|
|
1732
2107
|
pub fn paralyzeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1741,17 +2116,34 @@ pub const Effects = struct {
|
|
|
1741
2116
|
|
|
1742
2117
|
foe_stored.status = Status.init(.PAR);
|
|
1743
2118
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
1744
|
-
|
|
2119
|
+
|
|
2120
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1745
2121
|
|
|
1746
2122
|
// TODO: check for status berry
|
|
1747
2123
|
}
|
|
1748
2124
|
|
|
1749
|
-
pub fn payDay(battle: anytype, player: Player,
|
|
1750
|
-
|
|
2125
|
+
pub fn payDay(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2126
|
+
if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
|
|
2127
|
+
try options.log.fieldactivate(.{}); // FIXME .PayDay
|
|
2128
|
+
}
|
|
1751
2129
|
}
|
|
1752
2130
|
|
|
1753
|
-
pub fn perishSong(battle: anytype, player: Player,
|
|
1754
|
-
|
|
2131
|
+
pub fn perishSong(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2132
|
+
var side = battle.side(player);
|
|
2133
|
+
var foe = battle.foe(player);
|
|
2134
|
+
|
|
2135
|
+
if (foe.active.volatiles.PerishSong) {
|
|
2136
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2137
|
+
} else if (!side.active.volatiles.PerishSong) {
|
|
2138
|
+
side.active.volatiles.PerishSong = true;
|
|
2139
|
+
side.active.volatiles.perish_song = 4;
|
|
2140
|
+
try options.log.start(.{ battle.active(player), .PerishSong3Silent });
|
|
2141
|
+
}
|
|
2142
|
+
foe.active.volatiles.PerishSong = true;
|
|
2143
|
+
foe.active.volatiles.perish_song = 4;
|
|
2144
|
+
try options.log.start(.{ battle.active(player.foe()), .PerishSong3Silent });
|
|
2145
|
+
|
|
2146
|
+
try options.log.fieldactivate(.{}); // FIXME .PerishSong
|
|
1755
2147
|
}
|
|
1756
2148
|
|
|
1757
2149
|
pub fn poison(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1761,24 +2153,24 @@ pub const Effects = struct {
|
|
|
1761
2153
|
|
|
1762
2154
|
const toxic = state.move == .Toxic;
|
|
1763
2155
|
if (state.immune() or foe.active.types.includes(.Poison)) {
|
|
1764
|
-
return options.log.immune(foe_ident, .None);
|
|
2156
|
+
return options.log.immune(.{ foe_ident, .None });
|
|
1765
2157
|
} else if (Status.any(foe_stored.status)) {
|
|
1766
2158
|
// Pokémon Showdown considers Toxic to be a status even in Generation II and so
|
|
1767
2159
|
// will not include a fail reason for Toxic vs. Poison or vice-versa...
|
|
1768
|
-
return options.log.fail(foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2160
|
+
return options.log.fail(.{ foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
1769
2161
|
if (!showdown)
|
|
1770
|
-
.Poison
|
|
2162
|
+
Fail.Poison
|
|
1771
2163
|
else if (toxic == (foe_stored.status == Status.TOX))
|
|
1772
|
-
if (toxic) .Toxic else .Poison
|
|
2164
|
+
if (toxic) Fail.Toxic else Fail.Poison
|
|
1773
2165
|
else
|
|
1774
|
-
.None
|
|
2166
|
+
Fail.None
|
|
1775
2167
|
else
|
|
1776
|
-
.None);
|
|
2168
|
+
Fail.None });
|
|
1777
2169
|
} else if (foe.active.volatiles.Substitute) {
|
|
1778
|
-
return options.log.
|
|
2170
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1779
2171
|
} else if (state.miss) {
|
|
1780
|
-
try options.log.lastmiss();
|
|
1781
|
-
|
|
2172
|
+
try options.log.lastmiss(.{});
|
|
2173
|
+
return options.log.miss(.{battle.active(player)});
|
|
1782
2174
|
}
|
|
1783
2175
|
|
|
1784
2176
|
foe_stored.status = Status.init(.PSN);
|
|
@@ -1788,7 +2180,7 @@ pub const Effects = struct {
|
|
|
1788
2180
|
foe.active.volatiles.toxic = 0;
|
|
1789
2181
|
}
|
|
1790
2182
|
|
|
1791
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
2183
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
1792
2184
|
|
|
1793
2185
|
// TODO: check for status berry
|
|
1794
2186
|
}
|
|
@@ -1804,7 +2196,8 @@ pub const Effects = struct {
|
|
|
1804
2196
|
if (foe.conditions.Safeguard) return;
|
|
1805
2197
|
|
|
1806
2198
|
foe_stored.status = Status.init(.PSN);
|
|
1807
|
-
|
|
2199
|
+
|
|
2200
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1808
2201
|
|
|
1809
2202
|
// TODO: check for status berry
|
|
1810
2203
|
}
|
|
@@ -1819,10 +2212,7 @@ pub const Effects = struct {
|
|
|
1819
2212
|
|
|
1820
2213
|
const failed = !state.first or volatiles.Substitute or
|
|
1821
2214
|
!try Rolls.protect(battle, player, volatiles.protect, options);
|
|
1822
|
-
if (failed) {
|
|
1823
|
-
try options.log.fail(battle.active(player), .None);
|
|
1824
|
-
return;
|
|
1825
|
-
}
|
|
2215
|
+
if (failed) return options.log.fail(.{ battle.active(player), .None });
|
|
1826
2216
|
|
|
1827
2217
|
volatiles.protect = @max(8, volatiles.protect + 1);
|
|
1828
2218
|
if (state.move == .Endure) {
|
|
@@ -1830,36 +2220,76 @@ pub const Effects = struct {
|
|
|
1830
2220
|
} else {
|
|
1831
2221
|
volatiles.Protect = true;
|
|
1832
2222
|
}
|
|
1833
|
-
try options.log.singleturn(battle.active(player), state.move);
|
|
2223
|
+
try options.log.singleturn(.{ battle.active(player), state.move });
|
|
1834
2224
|
}
|
|
1835
2225
|
|
|
1836
|
-
pub fn psychUp(battle: anytype, player: Player,
|
|
1837
|
-
|
|
1838
|
-
|
|
2226
|
+
pub fn psychUp(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2227
|
+
var side = battle.side(player);
|
|
2228
|
+
const foe = battle.foe(player);
|
|
1839
2229
|
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
2230
|
+
// Pokémon Showdown should fail when the enemy has no boosts/unboosts but doesn't
|
|
2231
|
+
if (!showdown and @as(u32, @bitCast(foe.active.boosts)) == 0) {
|
|
2232
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
2233
|
+
}
|
|
1843
2234
|
|
|
1844
|
-
|
|
1845
|
-
battle.
|
|
1846
|
-
try options.log.singlemove(battle.active(player), Move.Rage);
|
|
2235
|
+
side.active.boosts = foe.active.boosts;
|
|
2236
|
+
try options.log.copyboost(.{ battle.active(player), battle.active(player.foe()) });
|
|
1847
2237
|
}
|
|
1848
2238
|
|
|
1849
|
-
pub fn
|
|
1850
|
-
|
|
2239
|
+
pub fn pursuit(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2240
|
+
// TODO: remove switching variable and just look at Choice saved in Battle struct?
|
|
2241
|
+
if (battle.foe(player).active.volatiles.switching) state.damage *|= 2;
|
|
1851
2242
|
}
|
|
1852
2243
|
|
|
1853
|
-
pub
|
|
1854
|
-
|
|
2244
|
+
pub const rage = struct {
|
|
2245
|
+
pub fn start(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2246
|
+
battle.side(player).active.volatiles.Rage = true;
|
|
2247
|
+
try options.log.singlemove(.{ battle.active(player), Move.Rage });
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
pub fn damage(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2251
|
+
const side = battle.side(player);
|
|
2252
|
+
|
|
2253
|
+
assert(side.active.volatiles.Rage);
|
|
2254
|
+
state.damage *|= (side.active.volatiles.rage +| 1);
|
|
2255
|
+
}
|
|
2256
|
+
};
|
|
2257
|
+
|
|
2258
|
+
pub fn rainDance(battle: anytype, _: Player, _: *State, options: anytype) !void {
|
|
2259
|
+
return weather(battle, .Rain, options);
|
|
1855
2260
|
}
|
|
1856
2261
|
|
|
1857
|
-
pub fn
|
|
1858
|
-
|
|
2262
|
+
pub fn rapidSpin(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2263
|
+
var side = battle.side(player);
|
|
2264
|
+
|
|
2265
|
+
if (side.active.volatiles.LeechSeed) {
|
|
2266
|
+
try options.log.end(.{ battle.active(player), .LeechSeedFrom });
|
|
2267
|
+
side.active.volatiles.LeechSeed = false;
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
if (side.conditions.Spikes) {
|
|
2271
|
+
try options.log.sideend(.{ player, protocol.Side.Spikes, battle.active(player) });
|
|
2272
|
+
side.conditions.Spikes = false;
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
if (side.active.volatiles.bind.duration > 0) {
|
|
2276
|
+
const reason = @intFromEnum(End.Bind) + side.active.volatiles.bind.reason - 1;
|
|
2277
|
+
try options.log.end(.{ battle.active(player), @as(End, @enumFromInt(reason)) });
|
|
2278
|
+
side.active.volatiles.bind = .{};
|
|
2279
|
+
}
|
|
1859
2280
|
}
|
|
1860
2281
|
|
|
1861
2282
|
pub fn recoil(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1862
|
-
|
|
2283
|
+
var side = battle.side(player);
|
|
2284
|
+
var stored = side.stored();
|
|
2285
|
+
const ident = battle.active(player);
|
|
2286
|
+
|
|
2287
|
+
assert(state.damage > 0);
|
|
2288
|
+
|
|
2289
|
+
const damage: i16 = @intCast(@max(state.damage / 4, 1));
|
|
2290
|
+
stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
|
|
2291
|
+
|
|
2292
|
+
try options.log.damage(.{ ident, stored, .RecoilOf, battle.active(player.foe()) });
|
|
1863
2293
|
}
|
|
1864
2294
|
|
|
1865
2295
|
pub fn reversal(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1887,36 +2317,86 @@ pub const Effects = struct {
|
|
|
1887
2317
|
try calcDamage(battle, player, state, options);
|
|
1888
2318
|
}
|
|
1889
2319
|
|
|
1890
|
-
pub
|
|
1891
|
-
|
|
1892
|
-
|
|
2320
|
+
pub const rollout = struct {
|
|
2321
|
+
pub fn check(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
2322
|
+
var side = battle.side(player);
|
|
1893
2323
|
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
2324
|
+
if (!side.active.volatiles.Rollout) {
|
|
2325
|
+
side.active.volatiles.attacks = 0;
|
|
2326
|
+
} else {
|
|
2327
|
+
// TODO SkipToBattleCommand(doturn_command)
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
1897
2330
|
|
|
1898
|
-
|
|
1899
|
-
|
|
2331
|
+
pub fn power(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2332
|
+
var side = battle.side(player);
|
|
2333
|
+
|
|
2334
|
+
if (Status.is(side.stored().status, .SLP)) return;
|
|
2335
|
+
|
|
2336
|
+
if (side.active.volatiles.attacks > 0) {
|
|
2337
|
+
// TODO set wSomeoneIsRampaging
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
if (state.miss) {
|
|
2341
|
+
side.active.volatiles.Rollout = false;
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
assert(side.active.volatiles.attacks < 5);
|
|
2346
|
+
side.active.volatiles.attacks += 1;
|
|
2347
|
+
side.active.volatiles.Rollout = side.active.volatiles.attacks != 5;
|
|
2348
|
+
|
|
2349
|
+
if (side.active.volatiles.DefenseCurl) state.damage *|= 2;
|
|
2350
|
+
}
|
|
2351
|
+
};
|
|
2352
|
+
|
|
2353
|
+
pub fn sandstorm(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2354
|
+
if (battle.field.weather != .Sandstorm) return weather(battle, .Sandstorm, options);
|
|
2355
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
1900
2356
|
}
|
|
1901
2357
|
|
|
1902
|
-
pub fn
|
|
1903
|
-
|
|
2358
|
+
pub fn safeguard(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2359
|
+
return screens(battle, player, "Safeguard", "safeguard", options);
|
|
1904
2360
|
}
|
|
1905
2361
|
|
|
1906
|
-
pub fn
|
|
1907
|
-
|
|
2362
|
+
pub fn reflect(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2363
|
+
return screens(battle, player, "Reflect", "reflect", options);
|
|
1908
2364
|
}
|
|
1909
2365
|
|
|
1910
|
-
pub fn
|
|
1911
|
-
|
|
2366
|
+
pub fn lightScreen(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2367
|
+
return screens(battle, player, "LightScreen", "light_screen", options);
|
|
1912
2368
|
}
|
|
1913
2369
|
|
|
1914
|
-
|
|
1915
|
-
|
|
2370
|
+
fn screens(
|
|
2371
|
+
battle: anytype,
|
|
2372
|
+
player: Player,
|
|
2373
|
+
comptime key: []const u8,
|
|
2374
|
+
comptime value: []const u8,
|
|
2375
|
+
options: anytype,
|
|
2376
|
+
) !void {
|
|
2377
|
+
var side = battle.side(player);
|
|
2378
|
+
|
|
2379
|
+
if (@field(side.conditions, key)) {
|
|
2380
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
@field(side.conditions, key) = true;
|
|
2384
|
+
@field(side.conditions, value) = 5;
|
|
2385
|
+
try options.log.sidestart(.{ player, @field(protocol.Side, key) });
|
|
1916
2386
|
}
|
|
1917
2387
|
|
|
1918
|
-
pub fn
|
|
1919
|
-
|
|
2388
|
+
pub fn sketch(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2389
|
+
var side = battle.side(player);
|
|
2390
|
+
|
|
2391
|
+
side.last_used_move = .None;
|
|
2392
|
+
side.active.volatiles.dirty = false;
|
|
2393
|
+
|
|
2394
|
+
if (showdown) {
|
|
2395
|
+
// yes, actually "Splash". '|nothing' gets blindly mapped to '|-activate||move:Splash'
|
|
2396
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
2397
|
+
} else {
|
|
2398
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2399
|
+
}
|
|
1920
2400
|
}
|
|
1921
2401
|
|
|
1922
2402
|
pub fn sleep(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1925,14 +2405,14 @@ pub const Effects = struct {
|
|
|
1925
2405
|
const foe_ident = battle.active(player.foe());
|
|
1926
2406
|
|
|
1927
2407
|
if (Status.any(foe_stored.status)) {
|
|
1928
|
-
return options.log.fail(
|
|
2408
|
+
return options.log.fail(.{
|
|
1929
2409
|
foe_ident,
|
|
1930
|
-
if (Status.is(foe_stored.status, .SLP)) .Sleep else .None,
|
|
1931
|
-
);
|
|
2410
|
+
if (Status.is(foe_stored.status, .SLP)) Fail.Sleep else Fail.None,
|
|
2411
|
+
});
|
|
1932
2412
|
}
|
|
1933
2413
|
|
|
1934
2414
|
if (foe.active.volatiles.Substitute) {
|
|
1935
|
-
return options.log.
|
|
2415
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1936
2416
|
}
|
|
1937
2417
|
|
|
1938
2418
|
// Sleep Clause Mod
|
|
@@ -1943,79 +2423,182 @@ pub const Effects = struct {
|
|
|
1943
2423
|
}
|
|
1944
2424
|
|
|
1945
2425
|
foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
|
|
1946
|
-
try options.log.
|
|
2426
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .From, state.move });
|
|
1947
2427
|
}
|
|
1948
2428
|
|
|
1949
2429
|
pub fn sleepTalk(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1950
2430
|
const side = battle.side(player);
|
|
1951
2431
|
|
|
1952
2432
|
if (!Status.is(side.stored().status, .SLP)) {
|
|
1953
|
-
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
1954
|
-
return;
|
|
2433
|
+
return if (!showdown) try options.log.fail(.{ battle.active(player), .None });
|
|
1955
2434
|
}
|
|
1956
2435
|
|
|
1957
2436
|
var n: u3 = 0;
|
|
1958
2437
|
for (side.active.moves, 0..) |m, i| {
|
|
1959
2438
|
if (state.mslot - 1 != i and Move.get(m.id).extra.sleep_talk) n += 1;
|
|
1960
2439
|
}
|
|
1961
|
-
if (n == 0) {
|
|
1962
|
-
try options.log.fail(battle.active(player), .None);
|
|
1963
|
-
return;
|
|
1964
|
-
}
|
|
2440
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
1965
2441
|
|
|
1966
2442
|
_ = try Rolls.sleepTalk(battle, player, n, state.mslot, options); // TODO
|
|
1967
2443
|
}
|
|
1968
2444
|
|
|
1969
|
-
pub fn snore(battle: anytype, player: Player, state: *State, options: anytype) !
|
|
1970
|
-
|
|
2445
|
+
pub fn snore(battle: anytype, player: Player, state: *State, options: anytype) !bool {
|
|
2446
|
+
if (Status.is(battle.side(player).stored().status, .SLP)) return true;
|
|
2447
|
+
state.damage = 0;
|
|
2448
|
+
state.miss = true; // TODO is this necessary?
|
|
2449
|
+
if (!showdown) try options.log.fail(.{ battle.active(player), .None });
|
|
2450
|
+
return false;
|
|
1971
2451
|
}
|
|
1972
2452
|
|
|
1973
|
-
pub fn spikes(battle: anytype, player: Player,
|
|
1974
|
-
|
|
2453
|
+
pub fn spikes(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2454
|
+
var foe = battle.foe(player);
|
|
2455
|
+
if (foe.conditions.Spikes) return options.log.fail(.{ battle.active(player), .None });
|
|
2456
|
+
|
|
2457
|
+
foe.conditions.Spikes = true;
|
|
2458
|
+
try options.log.sidestart(.{ player.foe(), protocol.Side.Spikes });
|
|
1975
2459
|
}
|
|
1976
2460
|
|
|
1977
2461
|
pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1978
|
-
|
|
1979
|
-
}
|
|
2462
|
+
var foe = battle.foe(player);
|
|
1980
2463
|
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
2464
|
+
const last = foe.lastMove(false);
|
|
2465
|
+
if (state.miss or last == .None or last == .Struggle) {
|
|
2466
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2467
|
+
}
|
|
1984
2468
|
|
|
1985
|
-
|
|
1986
|
-
|
|
2469
|
+
var slot: u4 = 0;
|
|
2470
|
+
for (foe.active.moves, 0..) |m, i| {
|
|
2471
|
+
if (m.id == last) slot = @intCast(i + 1);
|
|
2472
|
+
}
|
|
2473
|
+
// If the move was called via Metronome it might not exist
|
|
2474
|
+
if (slot == 0 or foe.active.move(slot).pp == 0) {
|
|
2475
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
// Pokémon Showdown always deducts 4 instead of randomizing
|
|
2479
|
+
const pp = @min(
|
|
2480
|
+
foe.active.move(slot).pp,
|
|
2481
|
+
if (showdown) 4 else try Rolls.spite(battle, player, options),
|
|
2482
|
+
);
|
|
2483
|
+
foe.active.move(slot).pp -= pp;
|
|
2484
|
+
if (!foe.active.volatiles.Transform) {
|
|
2485
|
+
var move_slot = foe.stored().move(slot);
|
|
2486
|
+
assert(move_slot.pp > 0);
|
|
2487
|
+
|
|
2488
|
+
move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
|
|
2489
|
+
assert(foe.active.move(slot).pp == foe.stored().move(slot).pp);
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
try options.log.activate(.{ battle.active(player.foe()), .Spite, last, pp });
|
|
1987
2493
|
}
|
|
1988
2494
|
|
|
1989
|
-
pub fn
|
|
1990
|
-
|
|
2495
|
+
pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2496
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
1991
2497
|
}
|
|
1992
2498
|
|
|
1993
|
-
pub fn
|
|
1994
|
-
|
|
2499
|
+
pub fn substitute(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2500
|
+
var side = battle.side(player);
|
|
2501
|
+
if (side.active.volatiles.Substitute) {
|
|
2502
|
+
return options.log.fail(.{ battle.active(player), .Substitute });
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
assert(side.stored().stats.hp <= 1023);
|
|
2506
|
+
const hp: u8 = @intCast(side.stored().stats.hp / 4);
|
|
2507
|
+
|
|
2508
|
+
if (hp == 0 or side.stored().hp <= hp) {
|
|
2509
|
+
return options.log.fail(.{ battle.active(player), .Weak });
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
side.stored().hp -= hp;
|
|
2513
|
+
side.active.volatiles.substitute = hp;
|
|
2514
|
+
side.active.volatiles.Substitute = true;
|
|
2515
|
+
side.active.volatiles.bind = .{};
|
|
2516
|
+
try options.log.start(.{ battle.active(player), .Substitute });
|
|
2517
|
+
try options.log.damage(.{ battle.active(player), side.stored(), .None });
|
|
1995
2518
|
}
|
|
1996
2519
|
|
|
1997
|
-
pub fn
|
|
1998
|
-
|
|
2520
|
+
pub fn sunnyDay(battle: anytype, _: Player, _: *State, options: anytype) !void {
|
|
2521
|
+
return weather(battle, .Sun, options);
|
|
1999
2522
|
}
|
|
2000
2523
|
|
|
2001
|
-
|
|
2002
|
-
|
|
2524
|
+
fn weather(battle: anytype, w: Weather, options: anytype) !void {
|
|
2525
|
+
battle.field.weather = w;
|
|
2526
|
+
battle.field.weather_duration = 5;
|
|
2527
|
+
try options.log.weather(.{ w, protocol.Weather.None });
|
|
2003
2528
|
}
|
|
2004
2529
|
|
|
2005
|
-
pub fn teleport(battle: anytype, player: Player,
|
|
2006
|
-
|
|
2530
|
+
pub fn teleport(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2531
|
+
if (showdown) return;
|
|
2532
|
+
|
|
2533
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2534
|
+
try options.log.laststill(.{});
|
|
2007
2535
|
}
|
|
2008
2536
|
|
|
2009
2537
|
pub fn thief(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2010
|
-
|
|
2538
|
+
const side = battle.side(player);
|
|
2539
|
+
const foe = battle.foe(player);
|
|
2540
|
+
|
|
2541
|
+
if (side.stored().item != .None or foe.stored().item == .None) return;
|
|
2542
|
+
// Pokémon Showdown incorrectly allows you steal Mail
|
|
2543
|
+
if (!showdown and foe.stored().item.isMail()) return;
|
|
2544
|
+
if (!state.proc) return;
|
|
2545
|
+
|
|
2546
|
+
side.stored().item = foe.stored().item;
|
|
2547
|
+
foe.stored().item = .None;
|
|
2548
|
+
|
|
2549
|
+
try options.log.item(.{
|
|
2550
|
+
battle.active(player),
|
|
2551
|
+
side.stored().item,
|
|
2552
|
+
battle.active(player.foe()),
|
|
2553
|
+
});
|
|
2011
2554
|
}
|
|
2012
2555
|
|
|
2013
|
-
pub fn thrashing(battle: anytype, player: Player,
|
|
2014
|
-
|
|
2556
|
+
pub fn thrashing(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2557
|
+
var side = battle.side(player);
|
|
2558
|
+
if (Status.is(side.stored().status, .SLP)) return;
|
|
2559
|
+
|
|
2560
|
+
assert(!side.active.volatiles.Thrashing);
|
|
2561
|
+
side.active.volatiles.Thrashing = true;
|
|
2562
|
+
side.active.volatiles.attacks = Rolls.attackingDuration(battle, player, options);
|
|
2563
|
+
// TODO wSomeoneIsRampaging = true;
|
|
2015
2564
|
}
|
|
2016
2565
|
|
|
2017
|
-
pub fn transform(battle: anytype, player: Player,
|
|
2018
|
-
|
|
2566
|
+
pub fn transform(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2567
|
+
var side = battle.side(player);
|
|
2568
|
+
const foe = battle.foe(player);
|
|
2569
|
+
const foe_ident = battle.active(player.foe());
|
|
2570
|
+
|
|
2571
|
+
side.last_used_move = .None;
|
|
2572
|
+
side.active.volatiles.dirty = false;
|
|
2573
|
+
|
|
2574
|
+
if (foe.active.volatiles.Transform) {
|
|
2575
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
2576
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2577
|
+
if (!showdown) return options.log.fail(.{ battle.active(player), .None });
|
|
2578
|
+
try options.log.lastmiss(.{});
|
|
2579
|
+
return options.log.miss(.{battle.active(player)});
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
side.active.volatiles.Transform = true;
|
|
2583
|
+
side.active.volatiles.transform = foe_ident.int();
|
|
2584
|
+
side.active.volatiles.disable = .{};
|
|
2585
|
+
|
|
2586
|
+
// HP is not copied by Transform
|
|
2587
|
+
side.active.stats.atk = foe.active.stats.atk;
|
|
2588
|
+
side.active.stats.def = foe.active.stats.def;
|
|
2589
|
+
side.active.stats.spe = foe.active.stats.spe;
|
|
2590
|
+
side.active.stats.spa = foe.active.stats.spa;
|
|
2591
|
+
side.active.stats.spd = foe.active.stats.spd;
|
|
2592
|
+
|
|
2593
|
+
side.active.species = foe.active.species;
|
|
2594
|
+
side.active.types = foe.active.types;
|
|
2595
|
+
side.active.boosts = foe.active.boosts;
|
|
2596
|
+
for (foe.active.moves, 0..) |m, i| {
|
|
2597
|
+
side.active.moves[i].id = m.id;
|
|
2598
|
+
side.active.moves[i].pp = if (m.id != .None) if (m.id == .Sketch) 1 else 5 else 0;
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
try options.log.transform(.{ battle.active(player), foe_ident });
|
|
2019
2602
|
}
|
|
2020
2603
|
|
|
2021
2604
|
pub fn triAttack(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2027,42 +2610,32 @@ pub const Effects = struct {
|
|
|
2027
2610
|
.FRZ => freezeChance(battle, player, state, options),
|
|
2028
2611
|
.BRN => burnChance(battle, player, state, options),
|
|
2029
2612
|
};
|
|
2030
|
-
} else if (!showdown
|
|
2613
|
+
} else if (!showdown) battle.rng.advance(1);
|
|
2031
2614
|
}
|
|
2032
2615
|
|
|
2033
2616
|
pub fn tripleKick(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2034
2617
|
_ = .{ battle, player, state, options }; // TODO
|
|
2035
2618
|
}
|
|
2036
2619
|
|
|
2037
|
-
|
|
2038
|
-
_ = .{ battle, player, state, options }; // TODO
|
|
2039
|
-
}
|
|
2040
|
-
|
|
2041
|
-
const ALL_STAT_UP_STATS = [_][]const u8{ "atk", "def", "spe", "spa", "spd" };
|
|
2042
|
-
const ALL_STAT_UP_REASONS =
|
|
2620
|
+
const STAT_UP_REASONS =
|
|
2043
2621
|
[_]Boost{ .Attack, .Defense, .Speed, .SpecialAttack, .SpecialDefense };
|
|
2044
2622
|
|
|
2045
2623
|
pub fn allStatUpChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2046
2624
|
var side = battle.side(player);
|
|
2047
2625
|
|
|
2048
|
-
|
|
2049
|
-
|
|
2626
|
+
const stats = &side.active.stats;
|
|
2627
|
+
const boosts = &side.active.boosts;
|
|
2050
2628
|
|
|
2051
2629
|
if (!state.proc) return;
|
|
2052
|
-
inline for (
|
|
2630
|
+
inline for (STATS, STAT_UP_REASONS) |s, r| {
|
|
2053
2631
|
assert(@field(boosts, s) >= -6 and @field(boosts, s) <= 6);
|
|
2054
2632
|
if (@field(boosts, s) == 6 or @field(stats, s) == MAX_STAT_VALUE) {
|
|
2055
|
-
try options.log.boost(battle.active(player), r, 0);
|
|
2633
|
+
try options.log.boost(.{ battle.active(player), r, 0 });
|
|
2056
2634
|
} else {
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2060
|
-
@field(stats, s) = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2061
|
-
try options.log.boost(battle.active(player), r, 1);
|
|
2635
|
+
try boostStat(battle, side, stats, boosts, s, 1);
|
|
2636
|
+
try options.log.boost(.{ battle.active(player), r, 1 });
|
|
2062
2637
|
}
|
|
2063
2638
|
}
|
|
2064
|
-
|
|
2065
|
-
statusModify(side.stored().status, stats);
|
|
2066
2639
|
}
|
|
2067
2640
|
|
|
2068
2641
|
pub fn boost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2070,84 +2643,65 @@ pub const Effects = struct {
|
|
|
2070
2643
|
var side = battle.side(player);
|
|
2071
2644
|
const ident = battle.active(player);
|
|
2072
2645
|
|
|
2073
|
-
if (state.miss) return
|
|
2646
|
+
if (state.miss) return log.fail(.{ ident, .None });
|
|
2074
2647
|
|
|
2075
|
-
|
|
2648
|
+
const stats = &side.active.stats;
|
|
2076
2649
|
var boosts = &side.active.boosts;
|
|
2077
2650
|
|
|
2078
2651
|
const move = Move.get(state.move);
|
|
2079
2652
|
switch (move.effect) {
|
|
2080
|
-
.AttackUp1, .AttackUp2, .Rage => {
|
|
2653
|
+
.AttackUp1, .AttackUp2, .Rage, .Swagger => {
|
|
2081
2654
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2082
2655
|
if (boosts.atk == 6 or stats.atk == MAX_STAT_VALUE) {
|
|
2083
|
-
return
|
|
2656
|
+
return log.fail(.{ ident, .None });
|
|
2084
2657
|
}
|
|
2085
|
-
const n: u2 = if (move.effect == .AttackUp2) 2 else 1;
|
|
2086
|
-
|
|
2658
|
+
const n: u2 = if (move.effect == .AttackUp2 or move.effect == .Swagger) 2 else 1;
|
|
2659
|
+
try boostStat(battle, side, stats, boosts, "atk", n);
|
|
2087
2660
|
const reason = if (showdown and move.effect == .Rage) Boost.Rage else Boost.Attack;
|
|
2088
|
-
|
|
2089
|
-
const stat = unmodifiedStats(battle, side).atk;
|
|
2090
|
-
stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2091
|
-
try log.boost(ident, reason, n);
|
|
2661
|
+
try log.boost(.{ ident, reason, n });
|
|
2092
2662
|
},
|
|
2093
|
-
.DefenseUp1, .DefenseUp2 => {
|
|
2663
|
+
.DefenseUp1, .DefenseUp2, .SkullBash => {
|
|
2094
2664
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2095
2665
|
if (boosts.def == 6 or stats.def == MAX_STAT_VALUE) {
|
|
2096
|
-
return
|
|
2666
|
+
return log.fail(.{ ident, .None });
|
|
2097
2667
|
}
|
|
2098
2668
|
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
const stat = unmodifiedStats(battle, side).def;
|
|
2102
|
-
stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2103
|
-
try log.boost(ident, .Defense, n);
|
|
2669
|
+
try boostStat(battle, side, stats, boosts, "def", n);
|
|
2670
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2104
2671
|
},
|
|
2105
2672
|
.SpeedUp2 => {
|
|
2106
2673
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2107
2674
|
if (boosts.spe == 6 or stats.spe == MAX_STAT_VALUE) {
|
|
2108
|
-
return
|
|
2675
|
+
return log.fail(.{ ident, .None });
|
|
2109
2676
|
}
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
const stat = unmodifiedStats(battle, side).spe;
|
|
2113
|
-
stats.spe = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2114
|
-
try log.boost(ident, .Speed, 2);
|
|
2677
|
+
try boostStat(battle, side, stats, boosts, "spe", 2);
|
|
2678
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2115
2679
|
},
|
|
2116
2680
|
.SpAtkUp1 => {
|
|
2117
2681
|
assert(boosts.spa >= -6 and boosts.spa <= 6);
|
|
2118
2682
|
if (boosts.spa == 6 or stats.spa == MAX_STAT_VALUE) {
|
|
2119
|
-
return
|
|
2683
|
+
return log.fail(.{ ident, .None });
|
|
2120
2684
|
}
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
const stat = unmodifiedStats(battle, side).spa;
|
|
2124
|
-
stats.spa = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2125
|
-
try log.boost(ident, .SpecialAttack, 1);
|
|
2685
|
+
try boostStat(battle, side, stats, boosts, "spa", 1);
|
|
2686
|
+
try log.boost(.{ ident, .SpecialAttack, 1 });
|
|
2126
2687
|
},
|
|
2127
2688
|
.SpDefUp2 => {
|
|
2128
2689
|
assert(boosts.spd >= -6 and boosts.spd <= 6);
|
|
2129
2690
|
if (boosts.spd == 6 or stats.spd == MAX_STAT_VALUE) {
|
|
2130
|
-
return
|
|
2691
|
+
return log.fail(.{ ident, .None });
|
|
2131
2692
|
}
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
const stat = unmodifiedStats(battle, side).spd;
|
|
2135
|
-
stats.spd = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2136
|
-
try log.boost(ident, .SpecialDefense, 2);
|
|
2693
|
+
try boostStat(battle, side, stats, boosts, "spd", 2);
|
|
2694
|
+
try log.boost(.{ ident, .SpecialDefense, 2 });
|
|
2137
2695
|
},
|
|
2138
2696
|
.EvasionUp1 => {
|
|
2139
2697
|
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2140
|
-
if (boosts.evasion == 6) {
|
|
2141
|
-
return try log.fail(ident, .None);
|
|
2142
|
-
}
|
|
2698
|
+
if (boosts.evasion == 6) return log.fail(.{ ident, .None });
|
|
2143
2699
|
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2144
|
-
try log.boost(ident, .Evasion, 1);
|
|
2700
|
+
try log.boost(.{ ident, .Evasion, 1 });
|
|
2145
2701
|
if (state.move == .Minimize) side.active.volatiles.minimized = true;
|
|
2146
2702
|
},
|
|
2147
2703
|
else => unreachable,
|
|
2148
2704
|
}
|
|
2149
|
-
|
|
2150
|
-
statusModify(side.stored().status, stats);
|
|
2151
2705
|
}
|
|
2152
2706
|
|
|
2153
2707
|
pub fn unboost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2156,102 +2710,86 @@ pub const Effects = struct {
|
|
|
2156
2710
|
const foe_ident = battle.active(player.foe());
|
|
2157
2711
|
|
|
2158
2712
|
if (foe.active.volatiles.Mist) {
|
|
2159
|
-
try log.activate(foe_ident, .Mist);
|
|
2160
|
-
return
|
|
2713
|
+
try log.activate(.{ foe_ident, .Mist });
|
|
2714
|
+
return log.fail(.{ foe_ident, .None });
|
|
2161
2715
|
}
|
|
2162
2716
|
|
|
2163
2717
|
const fail = foe.active.volatiles.Substitute or state.miss or !state.proc;
|
|
2164
2718
|
|
|
2165
|
-
|
|
2719
|
+
const stats = &foe.active.stats;
|
|
2166
2720
|
var boosts = &foe.active.boosts;
|
|
2167
2721
|
|
|
2168
2722
|
const move = Move.get(state.move);
|
|
2723
|
+
const none: Fail = .None;
|
|
2169
2724
|
switch (move.effect) {
|
|
2170
2725
|
.AttackDown1, .AttackDown2, .AttackDownChance => {
|
|
2171
2726
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2172
2727
|
if (fail or boosts.atk == -6) {
|
|
2173
|
-
return if (move.effect != .AttackDownChance) try log.fail(foe_ident,
|
|
2728
|
+
return if (move.effect != .AttackDownChance) try log.fail(.{ foe_ident, none });
|
|
2174
2729
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2175
|
-
try options.log.lastmiss();
|
|
2176
|
-
|
|
2177
|
-
return;
|
|
2730
|
+
try options.log.lastmiss(.{});
|
|
2731
|
+
return options.log.miss(.{battle.active(player)});
|
|
2178
2732
|
} else if (stats.atk == 1) {
|
|
2179
|
-
return if (move.effect != .AttackDownChance) try log.fail(foe_ident,
|
|
2733
|
+
return if (move.effect != .AttackDownChance) try log.fail(.{ foe_ident, none });
|
|
2180
2734
|
}
|
|
2181
2735
|
const n: u2 = if (move.effect == .AttackDown2) 2 else 1;
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
const stat = unmodifiedStats(battle, foe).atk;
|
|
2185
|
-
stats.atk = @max(1, stat * mod[0] / mod[1]);
|
|
2186
|
-
try log.boost(foe_ident, .Attack, -@as(i8, n));
|
|
2736
|
+
try unboostStat(battle, foe, stats, boosts, "atk", n);
|
|
2737
|
+
try log.boost(.{ foe_ident, .Attack, -@as(i8, n) });
|
|
2187
2738
|
},
|
|
2188
2739
|
.DefenseDown1, .DefenseDown2, .DefenseDownChance => {
|
|
2189
2740
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2190
2741
|
if (fail or boosts.def == -6) {
|
|
2191
|
-
|
|
2192
|
-
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2193
|
-
try options.log.lastmiss();
|
|
2194
|
-
try options.log.miss(battle.active(player));
|
|
2742
|
+
if (move.effect != .DefenseDownChance) try log.fail(.{ foe_ident, none });
|
|
2195
2743
|
return;
|
|
2744
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2745
|
+
try options.log.lastmiss(.{});
|
|
2746
|
+
return options.log.miss(.{battle.active(player)});
|
|
2196
2747
|
} else if (stats.def == 1) {
|
|
2197
|
-
|
|
2748
|
+
if (move.effect != .DefenseDownChance) try log.fail(.{ foe_ident, none });
|
|
2749
|
+
return;
|
|
2198
2750
|
}
|
|
2199
2751
|
const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
const stat = unmodifiedStats(battle, foe).def;
|
|
2203
|
-
stats.def = @max(1, stat * mod[0] / mod[1]);
|
|
2204
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2752
|
+
try unboostStat(battle, foe, stats, boosts, "def", n);
|
|
2753
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2205
2754
|
},
|
|
2206
2755
|
.SpeedDown1, .SpeedDown2, .SpeedDownChance => {
|
|
2207
2756
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2208
2757
|
if (fail or boosts.spe == -6) {
|
|
2209
|
-
return if (move.effect != .SpeedDownChance) try log.fail(foe_ident,
|
|
2758
|
+
return if (move.effect != .SpeedDownChance) try log.fail(.{ foe_ident, none });
|
|
2210
2759
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2211
|
-
try options.log.lastmiss();
|
|
2212
|
-
|
|
2213
|
-
return;
|
|
2760
|
+
try options.log.lastmiss(.{});
|
|
2761
|
+
return options.log.miss(.{battle.active(player)});
|
|
2214
2762
|
} else if (stats.spe == 1) {
|
|
2215
|
-
return if (move.effect != .SpeedDownChance) try log.fail(foe_ident,
|
|
2763
|
+
return if (move.effect != .SpeedDownChance) try log.fail(.{ foe_ident, none });
|
|
2216
2764
|
}
|
|
2217
2765
|
const n: u2 = if (move.effect == .SpeedDown2) 2 else 1;
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
const stat = unmodifiedStats(battle, foe).spe;
|
|
2221
|
-
stats.spe = @max(1, stat * mod[0] / mod[1]);
|
|
2222
|
-
try log.boost(foe_ident, .Speed, -@as(i8, n));
|
|
2223
|
-
assert(boosts.spe >= -6);
|
|
2766
|
+
try unboostStat(battle, foe, stats, boosts, "spe", n);
|
|
2767
|
+
try log.boost(.{ foe_ident, .Speed, -@as(i8, n) });
|
|
2224
2768
|
},
|
|
2225
2769
|
.SpDefDownChance => {
|
|
2226
2770
|
assert(boosts.spd >= -6 and boosts.spd <= 6);
|
|
2227
2771
|
if (fail or boosts.spd == -6) return;
|
|
2228
2772
|
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2229
|
-
try options.log.lastmiss();
|
|
2230
|
-
|
|
2231
|
-
return;
|
|
2773
|
+
try options.log.lastmiss(.{});
|
|
2774
|
+
return options.log.miss(.{battle.active(player)});
|
|
2232
2775
|
} else if (stats.spd == 1) return;
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
const stat = unmodifiedStats(battle, foe).spd;
|
|
2236
|
-
stats.spd = @max(1, stat * mod[0] / mod[1]);
|
|
2237
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2776
|
+
try unboostStat(battle, foe, stats, boosts, "spd", 1);
|
|
2777
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2238
2778
|
},
|
|
2239
2779
|
.AccuracyDown1, .AccuracyDownChance => {
|
|
2240
2780
|
assert(boosts.accuracy >= -6 and boosts.accuracy <= 6);
|
|
2241
2781
|
if (fail or boosts.accuracy == -6) {
|
|
2242
|
-
|
|
2243
|
-
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2244
|
-
try options.log.lastmiss();
|
|
2245
|
-
try options.log.miss(battle.active(player));
|
|
2782
|
+
if (move.effect != .AccuracyDownChance) try log.fail(.{ foe_ident, none });
|
|
2246
2783
|
return;
|
|
2784
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2785
|
+
try options.log.lastmiss(.{});
|
|
2786
|
+
return options.log.miss(.{battle.active(player)});
|
|
2247
2787
|
}
|
|
2248
2788
|
boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
|
|
2249
|
-
try log.boost(foe_ident, .Accuracy, -1);
|
|
2789
|
+
try log.boost(.{ foe_ident, .Accuracy, -1 });
|
|
2250
2790
|
},
|
|
2251
2791
|
else => unreachable,
|
|
2252
2792
|
}
|
|
2253
|
-
|
|
2254
|
-
statusModify(foe.stored().status, stats);
|
|
2255
2793
|
}
|
|
2256
2794
|
};
|
|
2257
2795
|
|
|
@@ -2261,6 +2799,15 @@ fn unmodifiedStats(battle: anytype, side: *Side) *Stats(u16) {
|
|
|
2261
2799
|
return &battle.side(id.player).pokemon[id.id - 1].stats;
|
|
2262
2800
|
}
|
|
2263
2801
|
|
|
2802
|
+
fn recomputeStats(battle: anytype, side: *Side) void {
|
|
2803
|
+
const stats = unmodifiedStats(battle, side);
|
|
2804
|
+
inline for (STATS) |s| {
|
|
2805
|
+
const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(side.active.boosts, s)) + 6))];
|
|
2806
|
+
const val = @field(stats, s) * mod[0] / mod[1];
|
|
2807
|
+
@field(side.active.stats, s) = @max(1, @min(MAX_STAT_VALUE, val));
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2264
2811
|
fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
2265
2812
|
if (Status.is(status, .PAR)) {
|
|
2266
2813
|
stats.spe = @max(stats.spe / 4, 1);
|
|
@@ -2269,20 +2816,66 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
2269
2816
|
}
|
|
2270
2817
|
}
|
|
2271
2818
|
|
|
2272
|
-
|
|
2819
|
+
fn boostStat(
|
|
2820
|
+
battle: anytype,
|
|
2821
|
+
side: *Side,
|
|
2822
|
+
stats: *Stats(u16),
|
|
2823
|
+
boosts: *Boosts,
|
|
2824
|
+
comptime s: []const u8,
|
|
2825
|
+
n: u2,
|
|
2826
|
+
) !void {
|
|
2827
|
+
@field(boosts, s) = @intCast(@min(6, @as(i8, @field(boosts, s)) + n));
|
|
2828
|
+
const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
|
|
2829
|
+
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2830
|
+
@field(stats, s) = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2831
|
+
if (comptime std.mem.eql(u8, s, "atk")) {
|
|
2832
|
+
if (Status.is(side.stored().status, .BRN)) {
|
|
2833
|
+
@field(stats, s) = @max(@field(stats, s) / 2, 1);
|
|
2834
|
+
}
|
|
2835
|
+
} else if (comptime std.mem.eql(u8, s, "spe")) {
|
|
2836
|
+
if (Status.is(side.stored().status, .PAR)) {
|
|
2837
|
+
@field(stats, s) = @max(@field(stats, s) / 4, 1);
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
fn unboostStat(
|
|
2843
|
+
battle: anytype,
|
|
2844
|
+
side: *Side,
|
|
2845
|
+
stats: *Stats(u16),
|
|
2846
|
+
boosts: *Boosts,
|
|
2847
|
+
comptime s: []const u8,
|
|
2848
|
+
n: u2,
|
|
2849
|
+
) !void {
|
|
2850
|
+
@field(boosts, s) = @intCast(@max(-6, @as(i8, @field(boosts, s)) - n));
|
|
2851
|
+
const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
|
|
2852
|
+
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2853
|
+
@field(stats, s) = @max(1, stat * mod[0] / mod[1]);
|
|
2854
|
+
if (comptime std.mem.eql(u8, s, "atk")) {
|
|
2855
|
+
if (Status.is(side.stored().status, .BRN)) {
|
|
2856
|
+
@field(stats, s) = @max(@field(stats, s) / 2, 1);
|
|
2857
|
+
}
|
|
2858
|
+
} else if (comptime std.mem.eql(u8, s, "spe")) {
|
|
2859
|
+
if (Status.is(side.stored().status, .PAR)) {
|
|
2860
|
+
@field(stats, s) = @max(@field(stats, s) / 4, 1);
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
fn isForced(active: anytype) bool {
|
|
2273
2866
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
2274
2867
|
active.volatiles.Thrashing or active.volatiles.Charging or
|
|
2275
2868
|
active.volatiles.Rollout;
|
|
2276
2869
|
}
|
|
2277
2870
|
|
|
2278
|
-
|
|
2871
|
+
fn convertible(active: ActivePokemon, m: Move) bool {
|
|
2279
2872
|
if (m == .None) return false;
|
|
2280
2873
|
const t = Move.get(m).type;
|
|
2281
2874
|
return t != .@"???" and !active.types.includes(t);
|
|
2282
2875
|
}
|
|
2283
2876
|
|
|
2284
2877
|
pub const Rolls = struct {
|
|
2285
|
-
|
|
2878
|
+
fn speedTie(battle: anytype, options: anytype) !bool {
|
|
2286
2879
|
const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
|
|
2287
2880
|
player == .P1
|
|
2288
2881
|
else if (showdown)
|
|
@@ -2294,7 +2887,7 @@ pub const Rolls = struct {
|
|
|
2294
2887
|
return p1;
|
|
2295
2888
|
}
|
|
2296
2889
|
|
|
2297
|
-
|
|
2890
|
+
fn quickClaw(battle: anytype, player: Player, options: anytype) !bool {
|
|
2298
2891
|
const qkc = if (options.calc.overridden(player, .quick_claw)) |val|
|
|
2299
2892
|
val == .true
|
|
2300
2893
|
else if (showdown)
|
|
@@ -2306,7 +2899,7 @@ pub const Rolls = struct {
|
|
|
2306
2899
|
return qkc;
|
|
2307
2900
|
}
|
|
2308
2901
|
|
|
2309
|
-
|
|
2902
|
+
fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) !bool {
|
|
2310
2903
|
const ok = if (options.calc.overridden(player, .hit)) |val|
|
|
2311
2904
|
val == .true
|
|
2312
2905
|
else if (showdown)
|
|
@@ -2318,7 +2911,7 @@ pub const Rolls = struct {
|
|
|
2318
2911
|
return ok;
|
|
2319
2912
|
}
|
|
2320
2913
|
|
|
2321
|
-
|
|
2914
|
+
fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
|
|
2322
2915
|
const crit = if (options.calc.overridden(player, .critical_hit)) |val|
|
|
2323
2916
|
val == .true
|
|
2324
2917
|
else if (showdown)
|
|
@@ -2330,7 +2923,7 @@ pub const Rolls = struct {
|
|
|
2330
2923
|
return crit;
|
|
2331
2924
|
}
|
|
2332
2925
|
|
|
2333
|
-
|
|
2926
|
+
fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2334
2927
|
const roll = if (options.calc.overridden(player, .damage)) |val|
|
|
2335
2928
|
val
|
|
2336
2929
|
else roll: {
|
|
@@ -2346,7 +2939,7 @@ pub const Rolls = struct {
|
|
|
2346
2939
|
return roll;
|
|
2347
2940
|
}
|
|
2348
2941
|
|
|
2349
|
-
|
|
2942
|
+
fn confused(battle: anytype, player: Player, options: anytype) !bool {
|
|
2350
2943
|
const cfz = if (options.calc.overridden(player, .confused)) |val|
|
|
2351
2944
|
val == .true
|
|
2352
2945
|
else if (showdown)
|
|
@@ -2358,7 +2951,7 @@ pub const Rolls = struct {
|
|
|
2358
2951
|
return cfz;
|
|
2359
2952
|
}
|
|
2360
2953
|
|
|
2361
|
-
|
|
2954
|
+
fn attract(battle: anytype, player: Player, options: anytype) !bool {
|
|
2362
2955
|
const cant = if (options.calc.overridden(player, .attract)) |val|
|
|
2363
2956
|
val == .true
|
|
2364
2957
|
else if (showdown)
|
|
@@ -2370,7 +2963,7 @@ pub const Rolls = struct {
|
|
|
2370
2963
|
return cant;
|
|
2371
2964
|
}
|
|
2372
2965
|
|
|
2373
|
-
|
|
2966
|
+
fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
|
|
2374
2967
|
const par = if (options.calc.overridden(player, .paralyzed)) |val|
|
|
2375
2968
|
val == .true
|
|
2376
2969
|
else if (showdown)
|
|
@@ -2382,7 +2975,7 @@ pub const Rolls = struct {
|
|
|
2382
2975
|
return par;
|
|
2383
2976
|
}
|
|
2384
2977
|
|
|
2385
|
-
|
|
2978
|
+
fn defrost(battle: anytype, player: Player, options: anytype) !bool {
|
|
2386
2979
|
const thaw = if (options.calc.overridden(player, .defrost)) |val|
|
|
2387
2980
|
val == .true
|
|
2388
2981
|
else if (showdown)
|
|
@@ -2394,7 +2987,7 @@ pub const Rolls = struct {
|
|
|
2394
2987
|
return thaw;
|
|
2395
2988
|
}
|
|
2396
2989
|
|
|
2397
|
-
|
|
2990
|
+
fn secondaryChance(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
|
|
2398
2991
|
// GLITCH: 100% secondary chance will still fail 1/256 of the time
|
|
2399
2992
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2400
2993
|
val == .true
|
|
@@ -2407,7 +3000,7 @@ pub const Rolls = struct {
|
|
|
2407
3000
|
return proc;
|
|
2408
3001
|
}
|
|
2409
3002
|
|
|
2410
|
-
|
|
3003
|
+
fn item(battle: anytype, player: Player, options: anytype) !bool {
|
|
2411
3004
|
const proc = if (options.calc.overridden(player, .item)) |val|
|
|
2412
3005
|
val == .true
|
|
2413
3006
|
else if (showdown)
|
|
@@ -2419,7 +3012,7 @@ pub const Rolls = struct {
|
|
|
2419
3012
|
return proc;
|
|
2420
3013
|
}
|
|
2421
3014
|
|
|
2422
|
-
|
|
3015
|
+
fn triAttack(battle: anytype, player: Player, options: anytype) !TriAttack {
|
|
2423
3016
|
const status: TriAttack = if (options.calc.overridden(player, .tri_attack)) |val|
|
|
2424
3017
|
@enumFromInt(@intFromEnum(val) - 1)
|
|
2425
3018
|
else if (showdown)
|
|
@@ -2435,7 +3028,7 @@ pub const Rolls = struct {
|
|
|
2435
3028
|
return status;
|
|
2436
3029
|
}
|
|
2437
3030
|
|
|
2438
|
-
|
|
3031
|
+
fn present(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2439
3032
|
const power = if (options.calc.overridden(player, .present)) |val|
|
|
2440
3033
|
(val - 1) * 40
|
|
2441
3034
|
else if (showdown) power: {
|
|
@@ -2457,8 +3050,8 @@ pub const Rolls = struct {
|
|
|
2457
3050
|
return power;
|
|
2458
3051
|
}
|
|
2459
3052
|
|
|
2460
|
-
|
|
2461
|
-
const num = if (options.calc.overridden(player, .magnitude)) |val|
|
|
3053
|
+
fn magnitude(battle: anytype, player: Player, options: anytype) !u8 {
|
|
3054
|
+
const num: u8 = if (options.calc.overridden(player, .magnitude)) |val|
|
|
2462
3055
|
val + 3
|
|
2463
3056
|
else if (showdown) num: {
|
|
2464
3057
|
const r = battle.rng.range(u8, 0, 100);
|
|
@@ -2485,7 +3078,7 @@ pub const Rolls = struct {
|
|
|
2485
3078
|
return num;
|
|
2486
3079
|
}
|
|
2487
3080
|
|
|
2488
|
-
|
|
3081
|
+
fn tripleKick(battle: anytype, player: Player, options: anytype) !u2 {
|
|
2489
3082
|
const hits = if (options.calc.overridden(player, .triple_kick)) |val|
|
|
2490
3083
|
val
|
|
2491
3084
|
else if (showdown)
|
|
@@ -2502,13 +3095,12 @@ pub const Rolls = struct {
|
|
|
2502
3095
|
return hits;
|
|
2503
3096
|
}
|
|
2504
3097
|
|
|
2505
|
-
|
|
2506
|
-
|
|
3098
|
+
fn spite(battle: anytype, player: Player, options: anytype) !u3 {
|
|
3099
|
+
assert(!showdown);
|
|
3100
|
+
const pp: u3 = if (options.calc.overridden(player, .spite)) |val|
|
|
2507
3101
|
val + 1
|
|
2508
|
-
else if (showdown)
|
|
2509
|
-
battle.rng.range(u8, 2, 6)
|
|
2510
3102
|
else
|
|
2511
|
-
(battle.rng.next() & 3) + 2;
|
|
3103
|
+
@intCast((battle.rng.next() & 3) + 2);
|
|
2512
3104
|
|
|
2513
3105
|
assert(pp >= 2 and pp <= 5);
|
|
2514
3106
|
try options.chance.spite(player, pp);
|
|
@@ -2518,7 +3110,7 @@ pub const Rolls = struct {
|
|
|
2518
3110
|
// Conversion 2 can at most choose between 7 types (vs. a Grass-type attack)
|
|
2519
3111
|
var CONVERSION_2: [7]Type = [_]Type{.@"???"} ** 7;
|
|
2520
3112
|
|
|
2521
|
-
|
|
3113
|
+
fn conversion2(battle: anytype, player: Player, mtype: Type, options: anytype) !Type {
|
|
2522
3114
|
var i: u8 = 0;
|
|
2523
3115
|
const neutral = @intFromEnum(Effectiveness.Neutral);
|
|
2524
3116
|
assert(mtype != .@"???");
|
|
@@ -2552,7 +3144,7 @@ pub const Rolls = struct {
|
|
|
2552
3144
|
return ty;
|
|
2553
3145
|
}
|
|
2554
3146
|
|
|
2555
|
-
|
|
3147
|
+
fn protect(battle: anytype, player: Player, count: u8, options: anytype) !bool {
|
|
2556
3148
|
assert(count <= 8);
|
|
2557
3149
|
const num = if (count == 8) 0 else @as(u8, 255) >> @intCast(count);
|
|
2558
3150
|
|
|
@@ -2646,7 +3238,7 @@ pub const Rolls = struct {
|
|
|
2646
3238
|
return slot;
|
|
2647
3239
|
}
|
|
2648
3240
|
|
|
2649
|
-
|
|
3241
|
+
fn forceSwitch(
|
|
2650
3242
|
battle: anytype,
|
|
2651
3243
|
player: Player,
|
|
2652
3244
|
slots: []u4,
|
|
@@ -2684,7 +3276,7 @@ pub const Rolls = struct {
|
|
|
2684
3276
|
|
|
2685
3277
|
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
2686
3278
|
|
|
2687
|
-
|
|
3279
|
+
fn multiHit(battle: anytype, player: Player, options: anytype) !u3 {
|
|
2688
3280
|
const n: u3 = if (options.calc.overridden(player, .multi_hit)) |val|
|
|
2689
3281
|
@intCast(val)
|
|
2690
3282
|
else if (showdown)
|
|
@@ -2699,7 +3291,7 @@ pub const Rolls = struct {
|
|
|
2699
3291
|
return n;
|
|
2700
3292
|
}
|
|
2701
3293
|
|
|
2702
|
-
|
|
3294
|
+
fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
|
|
2703
3295
|
const power = if (options.calc.overridden(player, .psywave)) |val|
|
|
2704
3296
|
val
|
|
2705
3297
|
else if (showdown)
|
|
@@ -2716,13 +3308,13 @@ pub const Rolls = struct {
|
|
|
2716
3308
|
return power;
|
|
2717
3309
|
}
|
|
2718
3310
|
|
|
2719
|
-
|
|
2720
|
-
const moves = battle.
|
|
3311
|
+
fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
3312
|
+
const moves = battle.side(player).active.moves;
|
|
2721
3313
|
|
|
2722
3314
|
var n: u2 = 0;
|
|
2723
3315
|
for (moves) |m| {
|
|
2724
|
-
if (m == .None) break;
|
|
2725
|
-
if (Move.get(m).metronome) n += 1;
|
|
3316
|
+
if (m.id == .None) break;
|
|
3317
|
+
if (Move.get(m.id).extra.metronome) n += 1;
|
|
2726
3318
|
}
|
|
2727
3319
|
|
|
2728
3320
|
// TODO: consider throwing error instead of rerolling?
|
|
@@ -2733,10 +3325,11 @@ pub const Rolls = struct {
|
|
|
2733
3325
|
|
|
2734
3326
|
const move: Move = overridden orelse move: {
|
|
2735
3327
|
if (showdown) {
|
|
2736
|
-
var r = battle.rng.range(u8, 0, Move.METRONOME.len - n);
|
|
3328
|
+
var r = battle.rng.range(u8, 0, @as(u8, @intCast(Move.METRONOME.len - n)));
|
|
2737
3329
|
for (moves) |m| {
|
|
2738
|
-
if (m == .None
|
|
2739
|
-
if (
|
|
3330
|
+
if (m.id == .None) break;
|
|
3331
|
+
if (@intFromEnum(m.id) > @intFromEnum(Move.METRONOME[r])) break;
|
|
3332
|
+
if (Move.get(m.id).extra.metronome) r -= 1;
|
|
2740
3333
|
}
|
|
2741
3334
|
break :move Move.METRONOME[r];
|
|
2742
3335
|
} else {
|
|
@@ -2751,12 +3344,12 @@ pub const Rolls = struct {
|
|
|
2751
3344
|
}
|
|
2752
3345
|
};
|
|
2753
3346
|
|
|
2754
|
-
assert(Move.get(move).metronome);
|
|
3347
|
+
assert(Move.get(move).extra.metronome);
|
|
2755
3348
|
try options.chance.metronome(player, move, n);
|
|
2756
3349
|
return move;
|
|
2757
3350
|
}
|
|
2758
3351
|
|
|
2759
|
-
|
|
3352
|
+
fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2760
3353
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2761
3354
|
@intCast(val)
|
|
2762
3355
|
else if (showdown)
|
|
@@ -2773,7 +3366,7 @@ pub const Rolls = struct {
|
|
|
2773
3366
|
return duration + 1;
|
|
2774
3367
|
}
|
|
2775
3368
|
|
|
2776
|
-
|
|
3369
|
+
fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
|
|
2777
3370
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2778
3371
|
@intCast(val)
|
|
2779
3372
|
else if (showdown)
|
|
@@ -2787,7 +3380,7 @@ pub const Rolls = struct {
|
|
|
2787
3380
|
return duration;
|
|
2788
3381
|
}
|
|
2789
3382
|
|
|
2790
|
-
|
|
3383
|
+
fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
|
|
2791
3384
|
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2792
3385
|
@intCast(val)
|
|
2793
3386
|
else if (showdown)
|
|
@@ -2805,7 +3398,7 @@ pub const Rolls = struct {
|
|
|
2805
3398
|
return duration;
|
|
2806
3399
|
}
|
|
2807
3400
|
|
|
2808
|
-
|
|
3401
|
+
fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2809
3402
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2810
3403
|
@intCast(val)
|
|
2811
3404
|
else if (showdown)
|
|
@@ -2818,7 +3411,7 @@ pub const Rolls = struct {
|
|
|
2818
3411
|
return duration;
|
|
2819
3412
|
}
|
|
2820
3413
|
|
|
2821
|
-
|
|
3414
|
+
fn bindingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2822
3415
|
const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
|
|
2823
3416
|
@intCast(val)
|
|
2824
3417
|
else if (showdown)
|
|
@@ -2833,7 +3426,7 @@ pub const Rolls = struct {
|
|
|
2833
3426
|
}
|
|
2834
3427
|
|
|
2835
3428
|
// TODO: consider sharing implementation with bindingDuration
|
|
2836
|
-
|
|
3429
|
+
fn encoreDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2837
3430
|
const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
|
|
2838
3431
|
@intCast(val)
|
|
2839
3432
|
else if (showdown)
|
|
@@ -2955,7 +3548,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
2955
3548
|
.Move => {
|
|
2956
3549
|
const side = battle.side(player);
|
|
2957
3550
|
|
|
2958
|
-
|
|
3551
|
+
const active = &side.active;
|
|
2959
3552
|
const stored = side.stored();
|
|
2960
3553
|
|
|
2961
3554
|
// While players are not given any input options on the cartridge in these cases,
|
|
@@ -2975,7 +3568,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
2975
3568
|
}
|
|
2976
3569
|
|
|
2977
3570
|
var slot: u4 = 2;
|
|
2978
|
-
while (!active.volatiles.
|
|
3571
|
+
while (!active.volatiles.Trapped and slot <= 6) : (slot += 1) {
|
|
2979
3572
|
const id = side.order[slot - 1];
|
|
2980
3573
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
2981
3574
|
out[n] = .{ .type = .Switch, .data = slot };
|