@pkmn/engine 0.1.0-dev.dfd46bbb → 0.1.0-dev.e0a786f8
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 +51 -23
- package/build/pkg/addon/browser.d.ts +3 -0
- package/build/pkg/addon/browser.js +47 -0
- package/build/pkg/addon/browser.js.map +1 -0
- package/build/pkg/addon/node.d.ts +3 -0
- package/build/pkg/addon/node.js +110 -0
- package/build/pkg/addon/node.js.map +1 -0
- package/build/pkg/addon/wasm.d.ts +2 -0
- package/build/pkg/addon/wasm.js +53 -0
- package/build/pkg/addon/wasm.js.map +1 -0
- package/build/pkg/addon.d.ts +25 -3
- package/build/pkg/addon.js +39 -71
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/data/layout.json +15 -2
- package/build/pkg/data/protocol.json +23 -9
- package/build/pkg/data.d.ts +27 -5
- package/build/pkg/data.js.map +1 -1
- package/build/pkg/gen1.d.ts +15 -18
- package/build/pkg/gen1.js +28 -19
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/index.d.ts +42 -91
- package/build/pkg/index.js +23 -70
- package/build/pkg/index.js.map +1 -1
- package/build/pkg/protocol.d.ts +20 -1
- package/build/pkg/protocol.js +109 -107
- package/build/pkg/protocol.js.map +1 -1
- package/build/tools/debug.js +53 -76
- package/build/tools/debug.js.map +1 -1
- package/build/tools/display/debug.jsx +152 -0
- package/build/tools/display/debug.jsx.map +1 -0
- package/build/tools/display/index.js +152 -0
- package/build/tools/display/index.js.map +1 -0
- package/build/tools/display/ui.jsx +359 -0
- package/build/tools/display/ui.jsx.map +1 -0
- package/build/tools/display/util.js +33 -0
- package/build/tools/display/util.js.map +1 -0
- package/build.zig +330 -93
- package/package.json +59 -132
- package/src/bin/install-pkmn-engine +31 -20
- package/src/bin/pkmn-debug +1 -1
- package/src/lib/bindings/c.zig +255 -0
- package/src/lib/{binding → bindings}/node.zig +8 -21
- package/src/lib/bindings/wasm.zig +59 -0
- package/src/lib/common/array.zig +77 -0
- package/src/lib/common/data.zig +4 -4
- package/src/lib/common/debug.zig +39 -8
- package/src/lib/common/js.zig +4 -2
- package/src/lib/common/optional.zig +14 -10
- package/src/lib/common/options.zig +6 -0
- package/src/lib/common/protocol.zig +396 -399
- package/src/lib/common/rational.zig +327 -158
- package/src/lib/common/rng.zig +7 -9
- package/src/lib/common/util.zig +60 -1
- package/src/lib/gen1/calc.zig +696 -160
- package/src/lib/gen1/chance.zig +405 -280
- package/src/lib/gen1/data/moves.zig +2 -4
- package/src/lib/gen1/data/species.zig +2 -4
- package/src/lib/gen1/data/types.zig +1 -2
- package/src/lib/gen1/data.zig +24 -27
- package/src/lib/gen1/helpers.zig +92 -548
- package/src/lib/gen1/mechanics.zig +575 -461
- package/src/lib/gen2/calc.zig +37 -27
- package/src/lib/gen2/chance.zig +84 -496
- package/src/lib/gen2/data/items.zig +1 -3
- package/src/lib/gen2/data/moves.zig +2 -4
- package/src/lib/gen2/data/species.zig +1 -3
- package/src/lib/gen2/data/types.zig +1 -2
- package/src/lib/gen2/data.zig +20 -23
- package/src/lib/gen2/generated.zig +24 -37
- package/src/lib/gen2/helpers.zig +46 -25
- package/src/lib/gen2/mechanics.zig +721 -453
- package/src/lib/node.zig +18 -0
- package/src/lib/pkmn.zig +37 -15
- package/src/lib/wasm.zig +17 -0
- package/src/tools/display/dom.ts +93 -0
- package/src/tools/display/ui.css +354 -0
- package/build/test/display.js +0 -400
- package/build/test/display.js.map +0 -1
- package/src/lib/binding/wasm.zig +0 -12
- package/src/lib/gen1/README.md +0 -894
- package/src/lib/gen2/README.md +0 -330
- package/src/test/display/pkmn.html.tmpl +0 -368
|
@@ -1,49 +1,37 @@
|
|
|
1
|
-
const std = @import("std");
|
|
2
|
-
|
|
3
|
-
const pkmn = @import("../pkmn.zig");
|
|
4
|
-
|
|
5
|
-
const common = @import("../common/data.zig");
|
|
6
1
|
const DEBUG = @import("../common/debug.zig").print;
|
|
7
|
-
const protocol = @import("../common/protocol.zig");
|
|
8
|
-
const rng = @import("../common/rng.zig");
|
|
9
|
-
const util = @import("../common/util.zig");
|
|
10
2
|
|
|
3
|
+
const builtin = @import("builtin");
|
|
4
|
+
const common = @import("../common/data.zig");
|
|
11
5
|
const data = @import("data.zig");
|
|
12
6
|
const generated = @import("generated.zig");
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
const expectEqualSlices = std.testing.expectEqualSlices;
|
|
18
|
-
const expectEqualStrings = std.testing.expectEqualStrings;
|
|
19
|
-
|
|
20
|
-
const showdown = pkmn.options.showdown;
|
|
21
|
-
|
|
22
|
-
const Choice = common.Choice;
|
|
23
|
-
const ID = common.ID;
|
|
24
|
-
const Player = common.Player;
|
|
25
|
-
const Result = common.Result;
|
|
7
|
+
const pkmn = @import("../pkmn.zig");
|
|
8
|
+
const protocol = @import("../common/protocol.zig");
|
|
9
|
+
const rng = @import("../common/rng.zig");
|
|
10
|
+
const std = @import("std");
|
|
26
11
|
|
|
27
12
|
const Activate = protocol.Activate;
|
|
13
|
+
const ActivePokemon = data.ActivePokemon;
|
|
14
|
+
const assert = std.debug.assert;
|
|
28
15
|
const Boost = protocol.Boost;
|
|
29
|
-
const
|
|
16
|
+
const Boosts = data.Boosts;
|
|
17
|
+
const Choice = common.Choice;
|
|
30
18
|
const Damage = protocol.Damage;
|
|
19
|
+
const Effectiveness = data.Effectiveness;
|
|
31
20
|
const End = protocol.End;
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
|
|
21
|
+
const expectEqual = std.testing.expectEqual;
|
|
22
|
+
const expectEqualSlices = std.testing.expectEqualSlices;
|
|
23
|
+
const expectEqualStrings = std.testing.expectEqualStrings;
|
|
24
|
+
const Fail = protocol.Fail;
|
|
35
25
|
const Gen12 = rng.Gen12;
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
const ActivePokemon = data.ActivePokemon;
|
|
40
|
-
const Effectiveness = data.Effectiveness;
|
|
26
|
+
const Heal = protocol.Heal;
|
|
27
|
+
const ID = common.ID;
|
|
41
28
|
const Item = data.Item;
|
|
42
29
|
const Move = data.Move;
|
|
43
|
-
const
|
|
44
|
-
const
|
|
30
|
+
const Player = common.Player;
|
|
31
|
+
const Result = common.Result;
|
|
32
|
+
const showdown = pkmn.options.showdown;
|
|
45
33
|
const Side = data.Side;
|
|
46
|
-
const
|
|
34
|
+
const Start = protocol.Start;
|
|
47
35
|
const Stats = data.Stats;
|
|
48
36
|
const Status = data.Status;
|
|
49
37
|
const TriAttack = data.TriAttack;
|
|
@@ -96,12 +84,16 @@ const CRITICAL_HITS = &[_]u8{
|
|
|
96
84
|
|
|
97
85
|
const MAX_STAT_VALUE = 999;
|
|
98
86
|
|
|
87
|
+
const STATS = [_][]const u8{ "atk", "def", "spe", "spa", "spd" };
|
|
88
|
+
|
|
99
89
|
pub const State = struct {
|
|
100
90
|
damage: u16 = 0, // wCurDamage
|
|
101
91
|
effectiveness: u16 = Effectiveness.neutral, // wTypeModifier
|
|
102
92
|
bp: u8, // wPlayerMoveStructPower
|
|
93
|
+
type: Type, // BATTLE_VARS_MOVE_TYPE
|
|
103
94
|
accuracy: u8, // wEnemyMoveStruct + MOVE_ACC
|
|
104
95
|
move: Move, // wCurPlayerMove
|
|
96
|
+
hit: u3 = 0,
|
|
105
97
|
from: Move = .None,
|
|
106
98
|
mslot: u4, // wCurMoveNum
|
|
107
99
|
first: bool, // wEnemyGoesFirst
|
|
@@ -111,7 +103,7 @@ pub const State = struct {
|
|
|
111
103
|
proc: bool = true, // wEffectFailed
|
|
112
104
|
|
|
113
105
|
comptime {
|
|
114
|
-
assert(@sizeOf(State) ==
|
|
106
|
+
assert(@sizeOf(State) == 16);
|
|
115
107
|
}
|
|
116
108
|
|
|
117
109
|
pub fn immune(self: *State) bool {
|
|
@@ -136,11 +128,11 @@ fn start(battle: anytype, options: anytype) !Result {
|
|
|
136
128
|
const p1 = battle.side(.P1);
|
|
137
129
|
const p2 = battle.side(.P2);
|
|
138
130
|
|
|
139
|
-
|
|
131
|
+
const p1_slot = findFirstAlive(p1);
|
|
140
132
|
assert(!showdown or p1_slot == 1);
|
|
141
133
|
if (p1_slot == 0) return if (findFirstAlive(p2) == 0) Result.Tie else Result.Lose;
|
|
142
134
|
|
|
143
|
-
|
|
135
|
+
const p2_slot = findFirstAlive(p2);
|
|
144
136
|
assert(!showdown or p2_slot == 1);
|
|
145
137
|
if (p2_slot == 0) return Result.Win;
|
|
146
138
|
|
|
@@ -248,15 +240,14 @@ fn switchIn(battle: anytype, player: Player, slot: u8, kind: SwitchIn, options:
|
|
|
248
240
|
statusModify(incoming.status, &active.stats);
|
|
249
241
|
|
|
250
242
|
if (kind == .Drag) {
|
|
251
|
-
try options.log.drag(battle.active(player), incoming);
|
|
243
|
+
try options.log.drag(.{ battle.active(player), incoming });
|
|
252
244
|
} else {
|
|
253
|
-
try options.log.switched(battle.active(player), incoming);
|
|
245
|
+
try options.log.switched(.{ battle.active(player), incoming });
|
|
254
246
|
}
|
|
255
|
-
options.chance.switched(player, side.pokemon[0].position, out);
|
|
256
247
|
|
|
257
248
|
if (side.conditions.Spikes and !active.types.includes(.Flying)) {
|
|
258
249
|
incoming.hp -|= @max(incoming.stats.hp / 8, 1);
|
|
259
|
-
try options.log.damage(battle.active(player), incoming, .Spikes);
|
|
250
|
+
try options.log.damage(.{ battle.active(player), incoming, .Spikes });
|
|
260
251
|
}
|
|
261
252
|
}
|
|
262
253
|
|
|
@@ -399,14 +390,22 @@ fn executeMove(
|
|
|
399
390
|
|
|
400
391
|
assert(choice.type == .Move);
|
|
401
392
|
var mslot: u4 = @intCast(choice.data);
|
|
393
|
+
_ = &mslot;
|
|
402
394
|
var move = Move.Pound; // TODO
|
|
395
|
+
_ = &move;
|
|
403
396
|
|
|
404
397
|
if (!try beforeMove(battle, player, move, options)) return null;
|
|
405
398
|
|
|
406
399
|
// FIXME: need damage from doTurn
|
|
407
400
|
const m = Move.get(move);
|
|
408
|
-
var state: State =
|
|
409
|
-
.
|
|
401
|
+
var state: State = .{
|
|
402
|
+
.move = move,
|
|
403
|
+
.mslot = mslot,
|
|
404
|
+
.first = first,
|
|
405
|
+
.accuracy = m.accuracy,
|
|
406
|
+
.bp = m.bp,
|
|
407
|
+
.type = m.type,
|
|
408
|
+
};
|
|
410
409
|
try generated.runMove(battle, player, &state, options);
|
|
411
410
|
return null;
|
|
412
411
|
}
|
|
@@ -423,31 +422,22 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
423
422
|
if (volatiles.Recharging) {
|
|
424
423
|
volatiles.Recharging = false;
|
|
425
424
|
resetCant(volatiles);
|
|
426
|
-
try log.cant(ident, .Recharge);
|
|
425
|
+
try log.cant(.{ ident, .Recharge });
|
|
427
426
|
return false;
|
|
428
427
|
}
|
|
429
428
|
|
|
430
429
|
if (Status.is(stored.status, .SLP)) slp: {
|
|
431
430
|
const before = stored.status;
|
|
432
431
|
// Even if the EXT bit is set this will still correctly modify the sleep duration
|
|
433
|
-
// if (options.calc.modify(player, .sleep)) |extend| {
|
|
434
|
-
// if (!extend) stored.status = 0;
|
|
435
|
-
// } else {
|
|
436
432
|
stored.status -= 1;
|
|
437
|
-
// }
|
|
438
433
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
try options.chance.sleep(player, duration);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
if (duration == 0) {
|
|
445
|
-
try log.curestatus(ident, before, .Message);
|
|
434
|
+
if (Status.duration(stored.status) == 0) {
|
|
435
|
+
try log.curestatus(.{ ident, before, .Message });
|
|
446
436
|
stored.status = 0; // clears EXT if present
|
|
447
437
|
resetCant(volatiles);
|
|
448
438
|
volatiles.Nightmare = false;
|
|
449
439
|
} else {
|
|
450
|
-
try log.cant(ident, .Sleep);
|
|
440
|
+
try log.cant(.{ ident, .Sleep });
|
|
451
441
|
if (move == .Snore or move == .SleepTalk) break :slp;
|
|
452
442
|
resetCant(volatiles);
|
|
453
443
|
return false;
|
|
@@ -456,7 +446,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
456
446
|
|
|
457
447
|
if (Status.is(stored.status, .FRZ) and !(move == .FlameWheel or move == .SacredFire)) {
|
|
458
448
|
resetCant(volatiles);
|
|
459
|
-
try log.cant(ident, .Freeze);
|
|
449
|
+
try log.cant(.{ ident, .Freeze });
|
|
460
450
|
return false;
|
|
461
451
|
}
|
|
462
452
|
|
|
@@ -466,39 +456,28 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
466
456
|
// if (!showdown) volatiles.Flinch = false;
|
|
467
457
|
volatiles.Flinch = false;
|
|
468
458
|
resetCant(volatiles);
|
|
469
|
-
try log.cant(ident, .Flinch);
|
|
459
|
+
try log.cant(.{ ident, .Flinch });
|
|
470
460
|
return false;
|
|
471
461
|
}
|
|
472
462
|
|
|
473
463
|
if (volatiles.disable.duration > 0) {
|
|
474
|
-
// if (options.calc.modify(player, .disable)) |extend| {
|
|
475
|
-
// if (!extend) volatiles.disable.duration = 0;
|
|
476
|
-
// } else {
|
|
477
464
|
volatiles.disable.duration -= 1;
|
|
478
|
-
// }
|
|
479
|
-
try options.chance.disable(player, volatiles.disable.duration);
|
|
480
465
|
|
|
481
466
|
if (volatiles.disable.duration == 0) {
|
|
482
467
|
volatiles.disable.move = 0;
|
|
483
|
-
try log.end(ident, .Disable);
|
|
468
|
+
try log.end(.{ ident, .Disable });
|
|
484
469
|
}
|
|
485
470
|
}
|
|
486
471
|
|
|
487
472
|
if (volatiles.Confusion) {
|
|
488
473
|
assert(volatiles.confusion > 0);
|
|
489
|
-
|
|
490
|
-
// if (options.calc.modify(player, .confusion)) |extend| {
|
|
491
|
-
// if (!extend) volatiles.confusion = 0;
|
|
492
|
-
// } else {
|
|
493
474
|
volatiles.confusion -= 1;
|
|
494
|
-
// }
|
|
495
|
-
try options.chance.confusion(player, volatiles.confusion);
|
|
496
475
|
|
|
497
476
|
if (volatiles.confusion == 0) {
|
|
498
477
|
volatiles.Confusion = false;
|
|
499
|
-
try log.end(ident, .Confusion);
|
|
478
|
+
try log.end(.{ ident, .Confusion });
|
|
500
479
|
} else {
|
|
501
|
-
try log.activate(ident, .Confusion);
|
|
480
|
+
try log.activate(.{ ident, .Confusion });
|
|
502
481
|
|
|
503
482
|
if (try Rolls.confused(battle, player, options)) {
|
|
504
483
|
volatiles.BeatUp = false;
|
|
@@ -513,11 +492,11 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
513
492
|
}
|
|
514
493
|
|
|
515
494
|
if (volatiles.Attract) {
|
|
516
|
-
try log.activate(ident, .Attract);
|
|
495
|
+
try log.activate(.{ ident, .Attract });
|
|
517
496
|
|
|
518
497
|
if (try Rolls.attract(battle, player, options)) {
|
|
519
498
|
resetCant(volatiles);
|
|
520
|
-
try log.cant(ident, .Attract);
|
|
499
|
+
try log.cant(.{ ident, .Attract });
|
|
521
500
|
return false;
|
|
522
501
|
}
|
|
523
502
|
}
|
|
@@ -528,14 +507,14 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
528
507
|
if (m != .None and m == move) {
|
|
529
508
|
side.active.volatiles.Charging = false;
|
|
530
509
|
resetCant(volatiles);
|
|
531
|
-
try options.log.
|
|
510
|
+
try options.log.cant(.{ ident, .Disable, move });
|
|
532
511
|
return false;
|
|
533
512
|
}
|
|
534
513
|
}
|
|
535
514
|
|
|
536
515
|
if (Status.is(stored.status, .PAR) and try Rolls.paralyzed(battle, player, options)) {
|
|
537
516
|
resetCant(volatiles);
|
|
538
|
-
try log.cant(ident, .Paralysis);
|
|
517
|
+
try log.cant(.{ ident, .Paralysis });
|
|
539
518
|
return false;
|
|
540
519
|
}
|
|
541
520
|
|
|
@@ -555,11 +534,11 @@ fn resetCant(volatiles: *align(8) Volatiles) void {
|
|
|
555
534
|
|
|
556
535
|
pub fn canMove(battle: anytype, player: Player, state: *State, options: anytype) !bool {
|
|
557
536
|
var side = battle.side(player);
|
|
558
|
-
|
|
537
|
+
const volatiles = &side.active.volatiles;
|
|
559
538
|
|
|
560
539
|
// FIXME: pass in target to avoid lookup?
|
|
561
540
|
const target = if (Move.get(state.move).target == .Self) player else player.foe();
|
|
562
|
-
try options.log.
|
|
541
|
+
try options.log.move(.{ battle.active(player), state.move, battle.active(target), state.from });
|
|
563
542
|
|
|
564
543
|
const charging = false; // TODO charging uses doUse
|
|
565
544
|
const skip_pp = charging or state.move == .Struggle or
|
|
@@ -638,7 +617,7 @@ pub fn afterMove(
|
|
|
638
617
|
|
|
639
618
|
pub fn checkCriticalHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
640
619
|
const move = Move.get(state.move);
|
|
641
|
-
if (
|
|
620
|
+
if (state.bp == 0) return;
|
|
642
621
|
|
|
643
622
|
const side = battle.side(player);
|
|
644
623
|
const pokemon = side.stored();
|
|
@@ -653,7 +632,7 @@ pub fn checkCriticalHit(battle: anytype, player: Player, state: *State, options:
|
|
|
653
632
|
}
|
|
654
633
|
assert(stage <= 4);
|
|
655
634
|
|
|
656
|
-
state.crit = try Rolls.criticalHit(battle, player, CRITICAL_HITS[stage], options);
|
|
635
|
+
state.crit = try Rolls.criticalHit(battle, player, state.hit, CRITICAL_HITS[stage], options);
|
|
657
636
|
}
|
|
658
637
|
|
|
659
638
|
pub fn calcDamage(
|
|
@@ -676,31 +655,30 @@ pub fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype)
|
|
|
676
655
|
const side = battle.side(player);
|
|
677
656
|
const foe = battle.foe(player);
|
|
678
657
|
const types = foe.active.types;
|
|
679
|
-
const move = Move.get(state.move);
|
|
680
658
|
|
|
681
659
|
var d = state.damage;
|
|
682
|
-
if ((
|
|
683
|
-
(
|
|
660
|
+
if ((state.type == .Water and battle.field.weather == .Rain) or
|
|
661
|
+
(state.type == .Fire and battle.field.weather == .Sun))
|
|
684
662
|
{
|
|
685
663
|
d = d *% @intFromEnum(Effectiveness.Super) / 10;
|
|
686
|
-
} else if ((
|
|
687
|
-
((
|
|
664
|
+
} else if ((state.type == .Water and battle.field.weather == .Sun) or
|
|
665
|
+
((state.type == .Fire or state.move == .SolarBeam) and battle.field.weather == .Rain))
|
|
688
666
|
{
|
|
689
667
|
d = d *% @intFromEnum(Effectiveness.Resisted) / 10;
|
|
690
668
|
}
|
|
691
669
|
|
|
692
|
-
if (side.active.types.includes(
|
|
670
|
+
if (side.active.types.includes(state.type)) d +%= d / 2;
|
|
693
671
|
|
|
694
672
|
const neutral = @intFromEnum(Effectiveness.Neutral);
|
|
695
|
-
const foresight = foe.active.volatiles.Foresight and FORESIGHT.includes(
|
|
673
|
+
const foresight = foe.active.volatiles.Foresight and FORESIGHT.includes(state.type);
|
|
696
674
|
const eff1: u16 = if (foresight and types.type1 == .Ghost)
|
|
697
675
|
neutral
|
|
698
676
|
else
|
|
699
|
-
@intFromEnum(
|
|
677
|
+
@intFromEnum(state.type.effectiveness(types.type1));
|
|
700
678
|
const eff2: u16 = if (foresight and types.type2 == .Ghost)
|
|
701
679
|
neutral
|
|
702
680
|
else
|
|
703
|
-
@intFromEnum(
|
|
681
|
+
@intFromEnum(state.type.effectiveness(types.type2));
|
|
704
682
|
|
|
705
683
|
// Type effectiveness matchup precedence only matters with (NVE, SE)
|
|
706
684
|
if (!showdown and (eff1 + eff2) == Effectiveness.mismatch and
|
|
@@ -722,7 +700,7 @@ pub fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype)
|
|
|
722
700
|
|
|
723
701
|
pub fn randomizeDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
724
702
|
if (state.damage <= 1) return;
|
|
725
|
-
const random = try Rolls.damage(battle, player, options);
|
|
703
|
+
const random = try Rolls.damage(battle, player, state.hit, options);
|
|
726
704
|
options.calc.base(player.foe(), state.damage);
|
|
727
705
|
state.damage = @intCast(@as(u32, state.damage) *% random / 255);
|
|
728
706
|
options.calc.final(player.foe(), state.damage);
|
|
@@ -840,13 +818,13 @@ fn checkFaint(
|
|
|
840
818
|
if (foe_fainted) try faint(battle, player.foe(), !more, options);
|
|
841
819
|
|
|
842
820
|
if (player_out and foe_out) {
|
|
843
|
-
try options.log.tie();
|
|
821
|
+
try options.log.tie(.{});
|
|
844
822
|
return Result.Tie;
|
|
845
823
|
} else if (player_out) {
|
|
846
|
-
try options.log.win(player.foe());
|
|
824
|
+
try options.log.win(.{player.foe()});
|
|
847
825
|
return if (player == .P1) Result.Lose else Result.Win;
|
|
848
826
|
} else if (foe_out) {
|
|
849
|
-
try options.log.win(player);
|
|
827
|
+
try options.log.win(.{player});
|
|
850
828
|
return if (player == .P1) Result.Win else Result.Lose;
|
|
851
829
|
}
|
|
852
830
|
|
|
@@ -876,19 +854,19 @@ fn faint(battle: anytype, player: Player, done: bool, options: anytype) !void {
|
|
|
876
854
|
side.stored().stats.spe;
|
|
877
855
|
}
|
|
878
856
|
|
|
879
|
-
try options.log.faint(battle.active(player), done);
|
|
857
|
+
try options.log.faint(.{ battle.active(player), done });
|
|
880
858
|
options.calc.capped(player);
|
|
881
859
|
}
|
|
882
860
|
|
|
883
861
|
// TODO: see if we can move *before* applyDamage and include failuretext logic
|
|
884
862
|
pub fn reportOutcome(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
885
863
|
const foe_ident = battle.active(player.foe());
|
|
886
|
-
if (state.crit) try options.log.crit(foe_ident);
|
|
864
|
+
if (state.crit) try options.log.crit(.{foe_ident});
|
|
887
865
|
if (!state.immune()) {
|
|
888
866
|
if (state.effectiveness > Effectiveness.neutral) {
|
|
889
|
-
try options.log.supereffective(foe_ident);
|
|
867
|
+
try options.log.supereffective(.{foe_ident});
|
|
890
868
|
} else if (state.effectiveness < Effectiveness.neutral) {
|
|
891
|
-
try options.log.resisted(foe_ident);
|
|
869
|
+
try options.log.resisted(.{foe_ident});
|
|
892
870
|
}
|
|
893
871
|
}
|
|
894
872
|
}
|
|
@@ -921,8 +899,8 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !bool {
|
|
|
921
899
|
}
|
|
922
900
|
|
|
923
901
|
stored.hp -|= damage;
|
|
924
|
-
// Pokémon Showdown uses
|
|
925
|
-
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
902
|
+
// Pokémon Showdown uses damage [of] here but its not relevant in Generation II
|
|
903
|
+
try options.log.damage(.{ ident, stored, if (brn) Damage.Burn else Damage.Poison });
|
|
926
904
|
if (stored.hp == 0) return true;
|
|
927
905
|
}
|
|
928
906
|
|
|
@@ -938,27 +916,27 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !bool {
|
|
|
938
916
|
|
|
939
917
|
const damage = @max(stored.stats.hp / 8, 1);
|
|
940
918
|
stored.hp -|= damage;
|
|
941
|
-
// As above, Pokémon Showdown uses
|
|
942
|
-
try options.log.damage(ident, stored, .LeechSeed);
|
|
919
|
+
// As above, Pokémon Showdown uses damage [of] but its not relevant
|
|
920
|
+
try options.log.damage(.{ ident, stored, .LeechSeed });
|
|
943
921
|
|
|
944
922
|
const before = foe_stored.hp;
|
|
945
923
|
foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
|
|
946
924
|
// Pokémon Showdown uses the less specific heal here instead of drain... because reasons?
|
|
947
|
-
if (foe_stored.hp > before) try options.log.heal(foe_ident, foe_stored, .Silent);
|
|
925
|
+
if (foe_stored.hp > before) try options.log.heal(.{ foe_ident, foe_stored, .Silent });
|
|
948
926
|
if (stored.hp == 0) return true;
|
|
949
927
|
}
|
|
950
928
|
|
|
951
929
|
if (volatiles.Nightmare) {
|
|
952
930
|
stored.hp -= @max(stored.stats.hp / 4, 1);
|
|
953
931
|
// ibid
|
|
954
|
-
try options.log.damage(ident, stored, .Nightmare);
|
|
932
|
+
try options.log.damage(.{ ident, stored, .Nightmare });
|
|
955
933
|
if (stored.hp == 0) return true;
|
|
956
934
|
}
|
|
957
935
|
|
|
958
936
|
if (volatiles.Curse) {
|
|
959
937
|
stored.hp -= @max(stored.stats.hp / 4, 1);
|
|
960
938
|
// ibid
|
|
961
|
-
try options.log.damage(ident, stored, .Curse);
|
|
939
|
+
try options.log.damage(.{ ident, stored, .Curse });
|
|
962
940
|
if (stored.hp == 0) return true;
|
|
963
941
|
}
|
|
964
942
|
|
|
@@ -979,7 +957,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
979
957
|
future_sight.count -= 1;
|
|
980
958
|
assert(future_sight.count != 0);
|
|
981
959
|
if (future_sight.count == 1) {
|
|
982
|
-
try options.log.end(battle.active(player), .FutureSight);
|
|
960
|
+
try options.log.end(.{ battle.active(player), .FutureSight });
|
|
983
961
|
// TODO doMove
|
|
984
962
|
}
|
|
985
963
|
}
|
|
@@ -988,21 +966,24 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
988
966
|
|
|
989
967
|
// Weather
|
|
990
968
|
if (battle.field.weather != .None) {
|
|
991
|
-
assert(battle.field.weather_duration > 0);
|
|
992
969
|
battle.field.weather_duration -= 1;
|
|
993
970
|
|
|
994
971
|
if (battle.field.weather_duration == 0) {
|
|
995
972
|
battle.field.weather = .None;
|
|
996
|
-
try options.log.weather(battle.field.weather, .None);
|
|
973
|
+
try options.log.weather(.{ battle.field.weather, protocol.Weather.None });
|
|
997
974
|
} else {
|
|
998
|
-
try options.log.weather(battle.field.weather, .Upkeep);
|
|
975
|
+
try options.log.weather(.{ battle.field.weather, protocol.Weather.Upkeep });
|
|
999
976
|
if (battle.field.weather == .Sandstorm) {
|
|
1000
977
|
inline for (players) |player| {
|
|
1001
978
|
var side = battle.side(player);
|
|
1002
979
|
const active = side.active;
|
|
1003
980
|
if (!(active.volatiles.Underground or active.types.sandstormImmune())) {
|
|
1004
981
|
side.stored().hp -|= @max(side.stored().stats.hp / 8, 1);
|
|
1005
|
-
try options.log.damage(
|
|
982
|
+
try options.log.damage(.{
|
|
983
|
+
battle.active(player),
|
|
984
|
+
side.stored(),
|
|
985
|
+
.Sandstorm,
|
|
986
|
+
});
|
|
1006
987
|
}
|
|
1007
988
|
}
|
|
1008
989
|
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
@@ -1014,19 +995,24 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1014
995
|
inline for (players) |player| {
|
|
1015
996
|
var side = battle.side(player);
|
|
1016
997
|
var volatiles = &side.active.volatiles;
|
|
998
|
+
const ident = battle.active(player);
|
|
1017
999
|
|
|
1018
1000
|
if (volatiles.bind.duration > 0 and !volatiles.Substitute) {
|
|
1019
1001
|
volatiles.bind.duration -= 1;
|
|
1020
1002
|
if (volatiles.bind.duration == 0) {
|
|
1021
1003
|
const reason = @intFromEnum(End.Bind) + volatiles.bind.reason - 1;
|
|
1022
|
-
try options.log.end(
|
|
1004
|
+
try options.log.end(.{ ident, @as(End, @enumFromInt(reason)) });
|
|
1023
1005
|
} else {
|
|
1024
1006
|
var reason = @intFromEnum(Activate.Bind) + volatiles.bind.reason - 1;
|
|
1025
|
-
try options.log.activate(
|
|
1007
|
+
try options.log.activate(.{ ident, @as(Activate, @enumFromInt(reason)) });
|
|
1026
1008
|
|
|
1027
1009
|
reason = @intFromEnum(Damage.Bind) + volatiles.bind.reason - 1;
|
|
1028
1010
|
side.stored().hp -|= @max(side.stored().stats.hp / 16, 1);
|
|
1029
|
-
try options.log.damage(
|
|
1011
|
+
try options.log.damage(.{
|
|
1012
|
+
ident,
|
|
1013
|
+
side.stored(),
|
|
1014
|
+
@as(Damage, @enumFromInt(reason)),
|
|
1015
|
+
});
|
|
1030
1016
|
}
|
|
1031
1017
|
}
|
|
1032
1018
|
}
|
|
@@ -1038,12 +1024,11 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1038
1024
|
var volatiles = &side.active.volatiles;
|
|
1039
1025
|
|
|
1040
1026
|
if (volatiles.PerishSong) {
|
|
1041
|
-
assert(volatiles.perish_song > 0);
|
|
1042
1027
|
volatiles.perish_song -= 1;
|
|
1043
1028
|
|
|
1044
1029
|
assert(volatiles.perish_song < 3);
|
|
1045
1030
|
const reason = @intFromEnum(Start.PerishSong0) + volatiles.perish_song;
|
|
1046
|
-
try options.log.start(battle.active(player), @enumFromInt(reason));
|
|
1031
|
+
try options.log.start(.{ battle.active(player), @as(Start, @enumFromInt(reason)) });
|
|
1047
1032
|
|
|
1048
1033
|
if (volatiles.perish_song == 0) {
|
|
1049
1034
|
volatiles.PerishSong = false;
|
|
@@ -1060,13 +1045,15 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1060
1045
|
if (stored.item == .Leftovers) {
|
|
1061
1046
|
const before = stored.hp;
|
|
1062
1047
|
stored.hp = @min(stored.hp + @max(stored.stats.hp / 16, 1), stored.stats.hp);
|
|
1063
|
-
if (stored.hp > before)
|
|
1048
|
+
if (stored.hp > before) {
|
|
1049
|
+
try options.log.heal(.{ battle.active(player), stored, .Leftovers });
|
|
1050
|
+
}
|
|
1064
1051
|
}
|
|
1065
1052
|
}
|
|
1066
1053
|
|
|
1067
1054
|
// Mystery Berry
|
|
1068
1055
|
inline for (players) |player| {
|
|
1069
|
-
|
|
1056
|
+
const stored = battle.side(player).stored();
|
|
1070
1057
|
|
|
1071
1058
|
if (stored.item == .MysteryBerry) {
|
|
1072
1059
|
// TODO stored and active moves, transform, etc
|
|
@@ -1081,7 +1068,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1081
1068
|
!side.active.volatiles.frozen and
|
|
1082
1069
|
try Rolls.defrost(battle, player, options))
|
|
1083
1070
|
{
|
|
1084
|
-
try options.log.curestatus(battle.active(player), side.stored().status, .Message);
|
|
1071
|
+
try options.log.curestatus(.{ battle.active(player), side.stored().status, .Message });
|
|
1085
1072
|
side.stored().status = 0;
|
|
1086
1073
|
}
|
|
1087
1074
|
}
|
|
@@ -1122,14 +1109,14 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1122
1109
|
stored.hp < stored.stats.hp;
|
|
1123
1110
|
if (proc) {
|
|
1124
1111
|
assert(stored.hp != 0);
|
|
1125
|
-
try options.log.enditem(ident, stored.item, .Eat);
|
|
1112
|
+
try options.log.enditem(.{ ident, stored.item, .Eat });
|
|
1126
1113
|
stored.item = .None;
|
|
1127
1114
|
|
|
1128
1115
|
const offset = num - @intFromEnum(Item.Berry);
|
|
1129
1116
|
assert(offset < 3);
|
|
1130
1117
|
stored.hp = @min(stored.hp + ((offset + 1) * 10), stored.stats.hp);
|
|
1131
1118
|
const reason: Heal = @enumFromInt(@intFromEnum(Heal.Berry) + offset);
|
|
1132
|
-
try options.log.heal(ident, stored, reason);
|
|
1119
|
+
try options.log.heal(.{ ident, stored, reason });
|
|
1133
1120
|
}
|
|
1134
1121
|
} else if (num < @intFromEnum(Item.BitterBerry)) {
|
|
1135
1122
|
const proc = if (stored.item == .MiracleBerry)
|
|
@@ -1139,7 +1126,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1139
1126
|
|
|
1140
1127
|
if (proc) {
|
|
1141
1128
|
assert(Status.any(stored.status));
|
|
1142
|
-
try options.log.enditem(ident, stored.item, .Eat);
|
|
1129
|
+
try options.log.enditem(.{ ident, stored.item, .Eat });
|
|
1143
1130
|
stored.item = .None;
|
|
1144
1131
|
|
|
1145
1132
|
// FIXME wtf?
|
|
@@ -1147,21 +1134,21 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1147
1134
|
// side.active.volatiles.Nightmare = false;
|
|
1148
1135
|
if (stored.item == .MiracleBerry) {
|
|
1149
1136
|
side.active.volatiles.Confusion = false;
|
|
1150
|
-
try options.log.end(ident, .Confusion);
|
|
1137
|
+
try options.log.end(.{ ident, .Confusion });
|
|
1151
1138
|
}
|
|
1152
1139
|
stored.status = 0;
|
|
1153
1140
|
|
|
1154
1141
|
// FIXME recalc stats
|
|
1155
1142
|
|
|
1156
|
-
try options.log.curestatus(ident, stored.status, .Message);
|
|
1143
|
+
try options.log.curestatus(.{ ident, stored.status, .Message });
|
|
1157
1144
|
}
|
|
1158
1145
|
} else if (num == @intFromEnum(Item.BitterBerry)) {
|
|
1159
1146
|
if (side.active.volatiles.Confusion) {
|
|
1160
|
-
try options.log.enditem(ident, stored.item, .Eat);
|
|
1147
|
+
try options.log.enditem(.{ ident, stored.item, .Eat });
|
|
1161
1148
|
stored.item = .None;
|
|
1162
1149
|
|
|
1163
1150
|
side.active.volatiles.Confusion = false;
|
|
1164
|
-
try options.log.end(ident, .Confusion);
|
|
1151
|
+
try options.log.end(.{ ident, .Confusion });
|
|
1165
1152
|
}
|
|
1166
1153
|
}
|
|
1167
1154
|
}
|
|
@@ -1173,12 +1160,11 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1173
1160
|
var volatiles = &side.active.volatiles;
|
|
1174
1161
|
|
|
1175
1162
|
if (volatiles.Encore) {
|
|
1176
|
-
assert(volatiles.encore > 0);
|
|
1177
1163
|
volatiles.encore -= 1;
|
|
1178
1164
|
|
|
1179
1165
|
if (volatiles.encore == 0 or side.active.move(mslot).pp == 0) {
|
|
1180
1166
|
volatiles.Encore = false;
|
|
1181
|
-
try options.log.end(battle.active(player), .Encore);
|
|
1167
|
+
try options.log.end(.{ battle.active(player), .Encore });
|
|
1182
1168
|
}
|
|
1183
1169
|
}
|
|
1184
1170
|
}
|
|
@@ -1195,12 +1181,11 @@ fn updateSideCondition(
|
|
|
1195
1181
|
) !void {
|
|
1196
1182
|
var side = battle.side(player);
|
|
1197
1183
|
if (@field(side.conditions, key)) {
|
|
1198
|
-
assert(@field(side.conditions, value) > 0);
|
|
1199
1184
|
@field(side.conditions, value) -= 1;
|
|
1200
1185
|
|
|
1201
1186
|
if (@field(side.conditions, value) == 0) {
|
|
1202
1187
|
@field(side.conditions, key) = false;
|
|
1203
|
-
try options.log.sideend(player, @field(protocol.Side, key));
|
|
1188
|
+
try options.log.sideend(.{ player, @field(protocol.Side, key) });
|
|
1204
1189
|
}
|
|
1205
1190
|
}
|
|
1206
1191
|
}
|
|
@@ -1208,14 +1193,14 @@ fn updateSideCondition(
|
|
|
1208
1193
|
fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result {
|
|
1209
1194
|
battle.turn += 1;
|
|
1210
1195
|
|
|
1211
|
-
if (
|
|
1212
|
-
try options.log.tie();
|
|
1196
|
+
if (pkmn.options.mod and battle.turn >= 1000) {
|
|
1197
|
+
try options.log.tie(.{});
|
|
1213
1198
|
return Result.Tie;
|
|
1214
1199
|
} else if (battle.turn >= 65535) {
|
|
1215
1200
|
return Result.Error;
|
|
1216
1201
|
}
|
|
1217
1202
|
|
|
1218
|
-
try options.log.turn(battle.turn);
|
|
1203
|
+
try options.log.turn(.{battle.turn});
|
|
1219
1204
|
|
|
1220
1205
|
return Result.Default;
|
|
1221
1206
|
}
|
|
@@ -1231,7 +1216,7 @@ pub fn buildRage(battle: anytype, player: Player, state: *State, options: anytyp
|
|
|
1231
1216
|
}
|
|
1232
1217
|
} else {
|
|
1233
1218
|
foe.active.volatiles.rage +|= 1;
|
|
1234
|
-
try options.log.activate(battle.active(player), .Rage);
|
|
1219
|
+
try options.log.activate(.{ battle.active(player), .Rage });
|
|
1235
1220
|
}
|
|
1236
1221
|
}
|
|
1237
1222
|
}
|
|
@@ -1254,7 +1239,7 @@ pub fn destinyBond(battle: anytype, player: Player, _: *State, options: anytype)
|
|
|
1254
1239
|
|
|
1255
1240
|
if (foe.active.volatiles.DestinyBond) {
|
|
1256
1241
|
battle.side(player).stored().hp = 0;
|
|
1257
|
-
try options.log.activate(battle.active(player.foe()), .DestinyBond);
|
|
1242
|
+
try options.log.activate(.{ battle.active(player.foe()), .DestinyBond });
|
|
1258
1243
|
}
|
|
1259
1244
|
return true;
|
|
1260
1245
|
}
|
|
@@ -1267,14 +1252,14 @@ pub const Effects = struct {
|
|
|
1267
1252
|
const gender: u8 = @intFromEnum(side.stored().dvs.gender);
|
|
1268
1253
|
const foe_gender: u8 = @intFromEnum(foe.stored().dvs.gender);
|
|
1269
1254
|
if (gender + foe_gender != 1) {
|
|
1270
|
-
return options.log.immune(battle.active(player.foe()), .None);
|
|
1255
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
1271
1256
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1272
|
-
try options.log.lastmiss();
|
|
1273
|
-
return options.log.miss(battle.active(player));
|
|
1257
|
+
try options.log.lastmiss(.{});
|
|
1258
|
+
return options.log.miss(.{battle.active(player)});
|
|
1274
1259
|
}
|
|
1275
1260
|
|
|
1276
1261
|
foe.active.volatiles.Attract = true;
|
|
1277
|
-
try options.log.start(battle.active(player.foe()), .Attract);
|
|
1262
|
+
try options.log.start(.{ battle.active(player.foe()), .Attract });
|
|
1278
1263
|
}
|
|
1279
1264
|
|
|
1280
1265
|
pub fn batonPass(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1286,7 +1271,32 @@ pub const Effects = struct {
|
|
|
1286
1271
|
}
|
|
1287
1272
|
|
|
1288
1273
|
pub fn bellyDrum(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1289
|
-
|
|
1274
|
+
assert(!state.miss);
|
|
1275
|
+
var side = battle.side(player);
|
|
1276
|
+
|
|
1277
|
+
const ident = battle.active(player);
|
|
1278
|
+
|
|
1279
|
+
assert(side.active.boosts.atk >= -6 and side.active.boosts.atk <= 6);
|
|
1280
|
+
if (side.active.boosts.atk == 6 or side.active.boosts.atk == MAX_STAT_VALUE) {
|
|
1281
|
+
return options.log.fail(.{ ident, .None });
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
const hp = @max(side.stored().stats.hp / 2, 1);
|
|
1285
|
+
if (side.stored().hp <= hp) {
|
|
1286
|
+
try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
|
|
1287
|
+
try options.log.boost(.{ ident, .Attack, @as(i8, 2), .Silent });
|
|
1288
|
+
return options.log.fail(.{ ident, .None });
|
|
1289
|
+
} else {
|
|
1290
|
+
side.stored().hp -= hp;
|
|
1291
|
+
try options.log.damage(.{ ident, side.stored(), .None });
|
|
1292
|
+
|
|
1293
|
+
const before = side.active.boosts.atk;
|
|
1294
|
+
var i: usize = 0;
|
|
1295
|
+
while (i < 6 and side.active.stats.atk < MAX_STAT_VALUE) : (i += 1) {
|
|
1296
|
+
try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
|
|
1297
|
+
}
|
|
1298
|
+
try options.log.boost(.{ ident, .Attack, @as(i8, side.active.boosts.atk - before) });
|
|
1299
|
+
}
|
|
1290
1300
|
}
|
|
1291
1301
|
|
|
1292
1302
|
pub const bide = struct {
|
|
@@ -1298,18 +1308,11 @@ pub const Effects = struct {
|
|
|
1298
1308
|
|
|
1299
1309
|
if (!volatiles.Bide) return;
|
|
1300
1310
|
|
|
1301
|
-
assert(volatiles.attacks > 0);
|
|
1302
|
-
// if (options.calc.modify(player, .attacking)) |extend| {
|
|
1303
|
-
// if (!extend) volatiles.attacks = 0;
|
|
1304
|
-
// } else {
|
|
1305
1311
|
volatiles.attacks -= 1;
|
|
1306
|
-
|
|
1307
|
-
try options.chance.attacking(player, volatiles.attacks);
|
|
1308
|
-
|
|
1309
|
-
if (volatiles.attacks != 0) return options.log.activate(ident, .Bide);
|
|
1312
|
+
if (volatiles.attacks != 0) return options.log.activate(.{ ident, .Bide });
|
|
1310
1313
|
|
|
1311
1314
|
volatiles.Bide = false;
|
|
1312
|
-
try options.log.end(ident, .Bide);
|
|
1315
|
+
try options.log.end(.{ ident, .Bide });
|
|
1313
1316
|
|
|
1314
1317
|
state.damage = volatiles.bide *% 2;
|
|
1315
1318
|
volatiles.bide = 0;
|
|
@@ -1323,7 +1326,16 @@ pub const Effects = struct {
|
|
|
1323
1326
|
};
|
|
1324
1327
|
|
|
1325
1328
|
pub fn binding(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1326
|
-
|
|
1329
|
+
var foe = battle.foe(player);
|
|
1330
|
+
|
|
1331
|
+
if (state.miss) return;
|
|
1332
|
+
if (foe.active.volatiles.bind.duration > 0) return;
|
|
1333
|
+
if (foe.active.volatiles.Substitute) return;
|
|
1334
|
+
|
|
1335
|
+
foe.active.volatiles.bind = .{
|
|
1336
|
+
.duration = Rolls.bindingDuration(battle, player, options),
|
|
1337
|
+
.reason = Move.get(state.move).extra.protocol,
|
|
1338
|
+
};
|
|
1327
1339
|
}
|
|
1328
1340
|
|
|
1329
1341
|
pub fn burnChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1334,11 +1346,11 @@ pub const Effects = struct {
|
|
|
1334
1346
|
if (Status.any(foe_stored.status)) {
|
|
1335
1347
|
if (Status.is(foe_stored.status, .FRZ)) {
|
|
1336
1348
|
assert(Move.get(state.move).type == .Fire or state.move == .TriAttack);
|
|
1337
|
-
try options.log.curestatus(
|
|
1349
|
+
try options.log.curestatus(.{
|
|
1338
1350
|
battle.active(player.foe()),
|
|
1339
1351
|
foe_stored.status,
|
|
1340
1352
|
.Message,
|
|
1341
|
-
);
|
|
1353
|
+
});
|
|
1342
1354
|
foe_stored.status = 0;
|
|
1343
1355
|
}
|
|
1344
1356
|
return;
|
|
@@ -1353,7 +1365,8 @@ pub const Effects = struct {
|
|
|
1353
1365
|
|
|
1354
1366
|
foe_stored.status = Status.init(.BRN);
|
|
1355
1367
|
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
1356
|
-
|
|
1368
|
+
|
|
1369
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1357
1370
|
|
|
1358
1371
|
// TODO: check for status berry
|
|
1359
1372
|
}
|
|
@@ -1373,7 +1386,7 @@ pub const Effects = struct {
|
|
|
1373
1386
|
for (side.active.moves) |m| {
|
|
1374
1387
|
if (convertible(side.active, m.id)) n += 1;
|
|
1375
1388
|
}
|
|
1376
|
-
if (n == 0) return options.log.fail(battle.active(player), .None);
|
|
1389
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
1377
1390
|
|
|
1378
1391
|
side.active.types.type1 = try Rolls.conversion(battle, player, n, options);
|
|
1379
1392
|
side.active.types.type2 = side.active.types.type1;
|
|
@@ -1383,20 +1396,88 @@ pub const Effects = struct {
|
|
|
1383
1396
|
var side = battle.side(player);
|
|
1384
1397
|
|
|
1385
1398
|
const last = battle.foe(player).lastMove(false);
|
|
1386
|
-
if (last == .None) return options.log.fail(battle.active(player), .None);
|
|
1399
|
+
if (last == .None) return options.log.fail(.{ battle.active(player), .None });
|
|
1387
1400
|
const move = Move.get(last);
|
|
1388
|
-
if (move.type == .@"???") return options.log.fail(battle.active(player), .None);
|
|
1401
|
+
if (move.type == .@"???") return options.log.fail(.{ battle.active(player), .None });
|
|
1389
1402
|
|
|
1390
1403
|
side.active.types.type1 = try Rolls.conversion2(battle, player, move.type, options);
|
|
1391
1404
|
side.active.types.type2 = side.active.types.type1;
|
|
1392
1405
|
}
|
|
1393
1406
|
|
|
1394
|
-
pub fn counter(battle: anytype, player: Player, state: *State,
|
|
1395
|
-
|
|
1407
|
+
pub fn counter(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1408
|
+
assert(state.move == .Counter or state.move == .MirrorCoat);
|
|
1409
|
+
|
|
1410
|
+
const foe = battle.foe(player);
|
|
1411
|
+
|
|
1412
|
+
state.miss = true;
|
|
1413
|
+
const last = foe.lastMove(false);
|
|
1414
|
+
|
|
1415
|
+
if (last == .None) return;
|
|
1416
|
+
if (last == state.move) return;
|
|
1417
|
+
|
|
1418
|
+
const immune = state.immune();
|
|
1419
|
+
state.effectiveness = Effectiveness.neutral;
|
|
1420
|
+
if (immune) {
|
|
1421
|
+
state.damage = 0;
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
const move = Move.get(last);
|
|
1426
|
+
if (state.bp == 0) return;
|
|
1427
|
+
if (move.type.special() != (last == .MirrorCoat)) return;
|
|
1428
|
+
if (state.damage == 0) return;
|
|
1429
|
+
|
|
1430
|
+
// NOTE: llvm/llvm-project#58557
|
|
1431
|
+
state.damage = smul(state.damage, 2);
|
|
1432
|
+
state.miss = false;
|
|
1396
1433
|
}
|
|
1397
1434
|
|
|
1398
1435
|
pub fn curse(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1399
|
-
|
|
1436
|
+
var side = battle.side(player);
|
|
1437
|
+
var foe = battle.foe(player);
|
|
1438
|
+
|
|
1439
|
+
const ident = battle.active(player);
|
|
1440
|
+
const foe_ident = battle.active(player.foe());
|
|
1441
|
+
|
|
1442
|
+
if (side.active.types.includes(.Ghost)) {
|
|
1443
|
+
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1444
|
+
try options.log.lastmiss(.{});
|
|
1445
|
+
return options.log.miss(.{battle.active(player)});
|
|
1446
|
+
} else if (foe.active.volatiles.Substitute) {
|
|
1447
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1448
|
+
} else if (foe.active.volatiles.Curse) {
|
|
1449
|
+
return options.log.fail(.{ foe_ident, .None });
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
foe.active.volatiles.Curse = true;
|
|
1453
|
+
try options.log.start(.{ foe_ident, .Curse });
|
|
1454
|
+
|
|
1455
|
+
side.stored().hp -= @max(side.stored().stats.hp / 2, 1);
|
|
1456
|
+
try options.log.damage(.{ ident, side.stored(), .Curse });
|
|
1457
|
+
} else {
|
|
1458
|
+
const stats = &side.active.stats;
|
|
1459
|
+
const boosts = &side.active.boosts;
|
|
1460
|
+
|
|
1461
|
+
const max_atk = boosts.atk == 6 or stats.atk == MAX_STAT_VALUE;
|
|
1462
|
+
const max_def = boosts.def == 6 or stats.def == MAX_STAT_VALUE;
|
|
1463
|
+
if (max_atk and max_def) return options.log.fail(.{ ident, .None });
|
|
1464
|
+
|
|
1465
|
+
if (!(boosts.spe == -6 or stats.spe == 1)) {
|
|
1466
|
+
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
1467
|
+
try unboostStat(battle, side, stats, boosts, "spe", 1);
|
|
1468
|
+
try options.log.boost(.{ ident, .Speed, @as(i8, -1) });
|
|
1469
|
+
}
|
|
1470
|
+
if (!max_atk) {
|
|
1471
|
+
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
1472
|
+
try boostStat(battle, side, stats, boosts, "atk", 1);
|
|
1473
|
+
try options.log.boost(.{ ident, .Attack, @as(i8, 1) });
|
|
1474
|
+
}
|
|
1475
|
+
if (!max_def) {
|
|
1476
|
+
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
1477
|
+
try boostStat(battle, side, stats, boosts, "def", 1);
|
|
1478
|
+
try options.log.boost(.{ ident, .Defense, @as(i8, 1) });
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1400
1481
|
}
|
|
1401
1482
|
|
|
1402
1483
|
pub fn defenseCurl(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
@@ -1406,14 +1487,13 @@ pub const Effects = struct {
|
|
|
1406
1487
|
pub fn defrost(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1407
1488
|
var side = battle.side(player);
|
|
1408
1489
|
assert(Status.is(side.stored().status, .FRZ));
|
|
1409
|
-
|
|
1410
|
-
try options.log.curestatus(battle.active(player), side.stored().status, .Message);
|
|
1490
|
+
try options.log.curestatus(.{ battle.active(player), side.stored().status, .Message });
|
|
1411
1491
|
side.stored().status = 0;
|
|
1412
1492
|
}
|
|
1413
1493
|
|
|
1414
1494
|
pub fn destinyBond(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1415
1495
|
battle.side(player).active.volatiles.DestinyBond = true;
|
|
1416
|
-
try options.log.singlemove(battle.active(player), Move.DestinyBond);
|
|
1496
|
+
try options.log.singlemove(.{ battle.active(player), Move.DestinyBond });
|
|
1417
1497
|
}
|
|
1418
1498
|
|
|
1419
1499
|
pub fn disable(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
@@ -1421,14 +1501,10 @@ pub const Effects = struct {
|
|
|
1421
1501
|
var volatiles = &foe.active.volatiles;
|
|
1422
1502
|
const foe_ident = battle.active(player.foe());
|
|
1423
1503
|
|
|
1424
|
-
if (volatiles.disable.move != 0) {
|
|
1425
|
-
return options.log.fail(foe_ident, .None);
|
|
1426
|
-
}
|
|
1504
|
+
if (volatiles.disable.move != 0) return options.log.fail(.{ foe_ident, .None });
|
|
1427
1505
|
|
|
1428
1506
|
const last = battle.foe(player).lastMove(false);
|
|
1429
|
-
if (last == .None or last == .Struggle) {
|
|
1430
|
-
return options.log.fail(foe_ident, .None);
|
|
1431
|
-
}
|
|
1507
|
+
if (last == .None or last == .Struggle) return options.log.fail(.{ foe_ident, .None });
|
|
1432
1508
|
|
|
1433
1509
|
var slot: u3 = 0;
|
|
1434
1510
|
for (foe.active.moves) |m| {
|
|
@@ -1437,37 +1513,41 @@ pub const Effects = struct {
|
|
|
1437
1513
|
if (m.id == last) break;
|
|
1438
1514
|
}
|
|
1439
1515
|
const move = foe.active.move(slot);
|
|
1440
|
-
if (move.pp == 0) return options.log.fail(foe_ident, .None);
|
|
1516
|
+
if (move.pp == 0) return options.log.fail(.{ foe_ident, .None });
|
|
1441
1517
|
|
|
1442
1518
|
volatiles.disable.move = slot;
|
|
1443
1519
|
volatiles.disable.duration = Rolls.disableDuration(battle, player, options);
|
|
1444
1520
|
|
|
1445
|
-
try options.log.
|
|
1521
|
+
try options.log.start(.{ foe_ident, .Disable, move.id });
|
|
1446
1522
|
}
|
|
1447
1523
|
|
|
1448
1524
|
pub const double = struct {
|
|
1449
1525
|
pub fn flying(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1450
|
-
if (battle.foe(player).active.volatiles.Flying)
|
|
1526
|
+
if (!battle.foe(player).active.volatiles.Flying) return;
|
|
1527
|
+
state.damage = smul(state.damage, 2);
|
|
1451
1528
|
}
|
|
1452
1529
|
|
|
1453
1530
|
pub fn underground(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1454
|
-
if (battle.foe(player).active.volatiles.Underground)
|
|
1531
|
+
if (!battle.foe(player).active.volatiles.Underground) return;
|
|
1532
|
+
state.damage = smul(state.damage, 2);
|
|
1455
1533
|
}
|
|
1456
1534
|
|
|
1457
1535
|
pub fn minimize(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1458
|
-
if (battle.foe(player).active.volatiles.minimized)
|
|
1536
|
+
if (!battle.foe(player).active.volatiles.minimized) return;
|
|
1537
|
+
state.damage = smul(state.damage, 2);
|
|
1459
1538
|
}
|
|
1460
1539
|
};
|
|
1461
1540
|
|
|
1462
1541
|
pub fn drainHP(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1463
1542
|
var stored = battle.side(player).stored();
|
|
1543
|
+
const ident = battle.active(player);
|
|
1464
1544
|
|
|
1465
1545
|
assert(state.damage != 0);
|
|
1466
1546
|
|
|
1467
1547
|
if (stored.hp == stored.stats.hp) return;
|
|
1468
1548
|
stored.hp = @min(stored.stats.hp, stored.hp + @max(state.damage / 2, 1));
|
|
1469
1549
|
|
|
1470
|
-
try options.log.
|
|
1550
|
+
try options.log.heal(.{ ident, stored, .Drain, battle.active(player.foe()) });
|
|
1471
1551
|
}
|
|
1472
1552
|
|
|
1473
1553
|
pub fn encore(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1479,7 +1559,7 @@ pub const Effects = struct {
|
|
|
1479
1559
|
const failed = volatiles.Encore or
|
|
1480
1560
|
(last == .None or last == .Struggle or
|
|
1481
1561
|
last == .Encore or .last == .MirrorMove);
|
|
1482
|
-
if (failed) return options.log.fail(foe_ident, .None);
|
|
1562
|
+
if (failed) return options.log.fail(.{ foe_ident, .None });
|
|
1483
1563
|
|
|
1484
1564
|
var slot: u3 = 0;
|
|
1485
1565
|
for (foe.active.moves) |m| {
|
|
@@ -1488,7 +1568,7 @@ pub const Effects = struct {
|
|
|
1488
1568
|
if (m.id == last) break;
|
|
1489
1569
|
}
|
|
1490
1570
|
const move = foe.active.move(slot);
|
|
1491
|
-
if (move.pp == 0) return options.log.fail(foe_ident, .None);
|
|
1571
|
+
if (move.pp == 0) return options.log.fail(.{ foe_ident, .None });
|
|
1492
1572
|
|
|
1493
1573
|
volatiles.Encore = true;
|
|
1494
1574
|
volatiles.encore = Rolls.encoreDuration(battle, player, options);
|
|
@@ -1496,11 +1576,19 @@ pub const Effects = struct {
|
|
|
1496
1576
|
// TODO ???
|
|
1497
1577
|
_ = state;
|
|
1498
1578
|
|
|
1499
|
-
try options.log.
|
|
1579
|
+
try options.log.start(.{ foe_ident, .Encore, move.id });
|
|
1500
1580
|
}
|
|
1501
1581
|
|
|
1502
|
-
pub fn explode(battle: anytype, player: Player,
|
|
1503
|
-
|
|
1582
|
+
pub fn explode(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
1583
|
+
var side = battle.side(player);
|
|
1584
|
+
var stored = side.stored();
|
|
1585
|
+
|
|
1586
|
+
stored.hp = 0;
|
|
1587
|
+
// Pokémon Showdown sets the status to 0 on faint(), and we need to do the same to be able
|
|
1588
|
+
// to correctly implement Pokémon Showdown's dumb speed-based switch mechanics
|
|
1589
|
+
if (!showdown) stored.status = 0;
|
|
1590
|
+
side.active.volatiles.LeechSeed = false;
|
|
1591
|
+
side.active.volatiles.DestinyBond = false;
|
|
1504
1592
|
}
|
|
1505
1593
|
|
|
1506
1594
|
pub fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
@@ -1550,12 +1638,11 @@ pub const Effects = struct {
|
|
|
1550
1638
|
var side = battle.side(player);
|
|
1551
1639
|
|
|
1552
1640
|
if (side.active.volatiles.FocusEnergy) {
|
|
1553
|
-
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
1554
|
-
return;
|
|
1641
|
+
return if (!showdown) try options.log.fail(.{ battle.active(player), .None });
|
|
1555
1642
|
}
|
|
1556
1643
|
|
|
1557
1644
|
side.active.volatiles.FocusEnergy = true;
|
|
1558
|
-
try options.log.start(battle.active(player), .FocusEnergy);
|
|
1645
|
+
try options.log.start(.{ battle.active(player), .FocusEnergy });
|
|
1559
1646
|
}
|
|
1560
1647
|
|
|
1561
1648
|
var SLOTS: [5]u4 = .{ 0, 0, 0, 0, 0 };
|
|
@@ -1565,7 +1652,7 @@ pub const Effects = struct {
|
|
|
1565
1652
|
|
|
1566
1653
|
const foe = battle.foe(player);
|
|
1567
1654
|
|
|
1568
|
-
if (state.first) return options.log.fail(battle.active(player), .None);
|
|
1655
|
+
if (state.first) return options.log.fail(.{ battle.active(player), .None });
|
|
1569
1656
|
|
|
1570
1657
|
var n: u4 = 0;
|
|
1571
1658
|
var i: u4 = 0;
|
|
@@ -1579,7 +1666,7 @@ pub const Effects = struct {
|
|
|
1579
1666
|
}
|
|
1580
1667
|
}
|
|
1581
1668
|
|
|
1582
|
-
if (n == 0) return options.log.fail(battle.active(player), .None);
|
|
1669
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
1583
1670
|
|
|
1584
1671
|
const slot = try Rolls.forceSwitch(battle, player, SLOTS[0..i], n, options);
|
|
1585
1672
|
try switchIn(battle, player.foe(), slot, .Drag, options);
|
|
@@ -1589,14 +1676,14 @@ pub const Effects = struct {
|
|
|
1589
1676
|
var foe = battle.foe(player);
|
|
1590
1677
|
|
|
1591
1678
|
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1592
|
-
try options.log.lastmiss();
|
|
1593
|
-
return options.log.miss(battle.active(player));
|
|
1679
|
+
try options.log.lastmiss(.{});
|
|
1680
|
+
return options.log.miss(.{battle.active(player)});
|
|
1594
1681
|
} else if (foe.active.volatiles.Foresight) {
|
|
1595
|
-
return options.log.fail(battle.active(player), .None);
|
|
1682
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
1596
1683
|
}
|
|
1597
1684
|
|
|
1598
1685
|
foe.active.volatiles.Foresight = true;
|
|
1599
|
-
try options.log.start(battle.active(player.foe()), .FocusEnergy);
|
|
1686
|
+
try options.log.start(.{ battle.active(player.foe()), .FocusEnergy });
|
|
1600
1687
|
}
|
|
1601
1688
|
|
|
1602
1689
|
pub fn freezeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1616,10 +1703,10 @@ pub const Effects = struct {
|
|
|
1616
1703
|
if (!state.proc) return;
|
|
1617
1704
|
if (foe.conditions.Safeguard) return;
|
|
1618
1705
|
// Freeze Clause Mod
|
|
1619
|
-
if (
|
|
1706
|
+
if (pkmn.options.mod) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
|
|
1620
1707
|
|
|
1621
1708
|
foe_stored.status = Status.init(.FRZ);
|
|
1622
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
1709
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
1623
1710
|
|
|
1624
1711
|
// TODO: check for status berry
|
|
1625
1712
|
|
|
@@ -1627,8 +1714,26 @@ pub const Effects = struct {
|
|
|
1627
1714
|
foe.active.volatiles.frozen = true;
|
|
1628
1715
|
}
|
|
1629
1716
|
|
|
1630
|
-
|
|
1631
|
-
|
|
1717
|
+
// TODO FIXME showdown calls this before calcDamage (after checkHit) and modifies state.bp!
|
|
1718
|
+
pub fn furyCutter(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1719
|
+
var side = battle.side(player);
|
|
1720
|
+
var volatiles = &side.active.volatiles;
|
|
1721
|
+
|
|
1722
|
+
if (state.miss) {
|
|
1723
|
+
volatiles.fury_cutter = 0;
|
|
1724
|
+
return;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
assert(volatiles.fury_cutter >= 0 and volatiles.fury_cutter <= 5);
|
|
1728
|
+
if (volatiles.fury_cutter == 5) return;
|
|
1729
|
+
volatiles.fury_cutter += 1;
|
|
1730
|
+
|
|
1731
|
+
if (showdown) {
|
|
1732
|
+
state.bp *= 2;
|
|
1733
|
+
assert(state.bp <= 160);
|
|
1734
|
+
} else {
|
|
1735
|
+
state.damage = smul(state.damage, 2);
|
|
1736
|
+
}
|
|
1632
1737
|
}
|
|
1633
1738
|
|
|
1634
1739
|
pub fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1641,42 +1746,131 @@ pub const Effects = struct {
|
|
|
1641
1746
|
state.bp = ((if (frustration) 255 - val else val) * 10) / 25;
|
|
1642
1747
|
}
|
|
1643
1748
|
|
|
1644
|
-
pub fn haze(battle: anytype, player: Player,
|
|
1645
|
-
|
|
1749
|
+
pub fn haze(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1750
|
+
var side = battle.side(player);
|
|
1751
|
+
var stats = unmodifiedStats(battle, side);
|
|
1752
|
+
side.active.boosts = .{};
|
|
1753
|
+
inline for (STATS) |s| @field(side.active.stats, s) = @field(stats, s);
|
|
1754
|
+
statusModify(side.stored().status, &side.active.stats);
|
|
1755
|
+
|
|
1756
|
+
var foe = battle.foe(player);
|
|
1757
|
+
stats = unmodifiedStats(battle, foe);
|
|
1758
|
+
foe.active.boosts = .{};
|
|
1759
|
+
inline for (STATS) |s| @field(foe.active.stats, s) = @field(stats, s);
|
|
1760
|
+
statusModify(foe.stored().status, &foe.active.stats);
|
|
1761
|
+
|
|
1762
|
+
try options.log.clearallboost(.{});
|
|
1646
1763
|
}
|
|
1647
1764
|
|
|
1648
1765
|
pub fn heal(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1649
|
-
|
|
1766
|
+
var side = battle.side(player);
|
|
1767
|
+
var stored = side.stored();
|
|
1768
|
+
const ident = battle.active(player);
|
|
1769
|
+
|
|
1770
|
+
if (stored.stats.hp == stored.hp) return options.log.fail(.{ ident, .None });
|
|
1771
|
+
|
|
1772
|
+
const rest = state.move == .Rest;
|
|
1773
|
+
if (rest) {
|
|
1774
|
+
// Adding the sleep status runs the sleep condition handler to roll duration
|
|
1775
|
+
if (showdown) battle.rng.advance(1);
|
|
1776
|
+
side.active.volatiles.Toxic = false;
|
|
1777
|
+
stored.status = Status.slf(2);
|
|
1778
|
+
recomputeStats(battle, side);
|
|
1779
|
+
try options.log.status(.{ ident, stored.status, .From, Move.Rest });
|
|
1780
|
+
stored.hp = stored.stats.hp;
|
|
1781
|
+
} else {
|
|
1782
|
+
stored.hp = @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2));
|
|
1783
|
+
}
|
|
1784
|
+
try options.log.heal(.{ ident, stored, if (rest) Heal.Silent else Heal.None });
|
|
1650
1785
|
}
|
|
1651
1786
|
|
|
1652
|
-
pub fn healBell(battle: anytype, player: Player,
|
|
1653
|
-
|
|
1787
|
+
pub fn healBell(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1788
|
+
var side = battle.side(player);
|
|
1789
|
+
side.active.volatiles.Nightmare = false;
|
|
1790
|
+
|
|
1791
|
+
try options.log.cureteam(.{battle.active(player)});
|
|
1792
|
+
for (&side.pokemon) |*p| p.status = 0;
|
|
1793
|
+
|
|
1794
|
+
// We technically only need to recompute stats if the active Pokémon was burned/paralyzed,
|
|
1795
|
+
// but adding a branch to check for that is probably more expensive than just recomputing
|
|
1796
|
+
recomputeStats(battle, side);
|
|
1654
1797
|
}
|
|
1655
1798
|
|
|
1656
|
-
pub fn hiddenPower(battle: anytype, player: Player, state: *State,
|
|
1657
|
-
|
|
1799
|
+
pub fn hiddenPower(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1800
|
+
const side = battle.side(player);
|
|
1801
|
+
state.bp = side.stored().dvs.power();
|
|
1802
|
+
state.type = side.stored().dvs.type;
|
|
1658
1803
|
}
|
|
1659
1804
|
|
|
1660
|
-
pub fn hyperBeam(battle: anytype, player: Player,
|
|
1661
|
-
|
|
1805
|
+
pub fn hyperBeam(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1806
|
+
battle.side(player).active.volatiles.Recharging = true;
|
|
1807
|
+
try options.log.mustrecharge(.{battle.active(player)});
|
|
1662
1808
|
}
|
|
1663
1809
|
|
|
1664
1810
|
pub fn leechSeed(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1665
|
-
|
|
1811
|
+
var foe = battle.foe(player);
|
|
1812
|
+
|
|
1813
|
+
if (showdown) {
|
|
1814
|
+
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
1815
|
+
const invulnerable = foe.active.volatiles.Underground or foe.active.volatiles.Flying;
|
|
1816
|
+
if (!invulnerable and foe.active.types.includes(.Grass)) {
|
|
1817
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
1818
|
+
}
|
|
1819
|
+
try checkHit(battle, player, state, options);
|
|
1820
|
+
if (state.miss) {
|
|
1821
|
+
try options.log.lastmiss(.{});
|
|
1822
|
+
return options.log.miss(.{battle.active(player)});
|
|
1823
|
+
}
|
|
1824
|
+
if (foe.active.volatiles.LeechSeed) return;
|
|
1825
|
+
} else {
|
|
1826
|
+
try checkHit(battle, player, state, options);
|
|
1827
|
+
const miss =
|
|
1828
|
+
state.miss or foe.active.types.includes(.Grass) or foe.active.volatiles.LeechSeed;
|
|
1829
|
+
if (miss) {
|
|
1830
|
+
try options.log.lastmiss(.{});
|
|
1831
|
+
return options.log.miss(.{battle.active(player)});
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
foe.active.volatiles.LeechSeed = true;
|
|
1836
|
+
|
|
1837
|
+
try options.log.start(.{ battle.active(player.foe()), .LeechSeed });
|
|
1666
1838
|
}
|
|
1667
1839
|
|
|
1668
1840
|
pub fn lockOn(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1669
|
-
|
|
1841
|
+
var foe = battle.foe(player);
|
|
1842
|
+
const foe_ident = battle.active(player.foe());
|
|
1843
|
+
|
|
1844
|
+
if (foe.active.volatiles.Substitute) {
|
|
1845
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1846
|
+
} else if (state.miss) {
|
|
1847
|
+
return options.log.fail(.{ foe_ident, .None });
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
foe.active.volatiles.LockOn = true;
|
|
1851
|
+
try options.log.activate(.{ battle.active(player), .LockOn });
|
|
1670
1852
|
}
|
|
1671
1853
|
|
|
1672
1854
|
const MAGNITUDE_POWER = [_]u8{ 10, 30, 50, 70, 90, 110, 150 };
|
|
1673
1855
|
|
|
1674
1856
|
pub fn magnitude(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1675
|
-
|
|
1857
|
+
const num = try Rolls.magnitude(battle, player, options);
|
|
1858
|
+
try options.log.activate(.{ battle.active(player), .Magnitude, num });
|
|
1859
|
+
state.bp = MAGNITUDE_POWER[num - 4];
|
|
1676
1860
|
}
|
|
1677
1861
|
|
|
1678
|
-
pub fn meanLook(battle: anytype, player: Player,
|
|
1679
|
-
|
|
1862
|
+
pub fn meanLook(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1863
|
+
var foe = battle.foe(player);
|
|
1864
|
+
|
|
1865
|
+
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1866
|
+
if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1867
|
+
try options.log.lastmiss(.{});
|
|
1868
|
+
return options.log.miss(.{battle.active(player)});
|
|
1869
|
+
} else if (foe.active.volatiles.Trapped) {
|
|
1870
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
foe.active.volatiles.Trapped = true;
|
|
1680
1874
|
}
|
|
1681
1875
|
|
|
1682
1876
|
pub fn metronome(
|
|
@@ -1700,11 +1894,29 @@ pub const Effects = struct {
|
|
|
1700
1894
|
}
|
|
1701
1895
|
|
|
1702
1896
|
pub fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1703
|
-
|
|
1704
|
-
}
|
|
1897
|
+
var side = battle.side(player);
|
|
1705
1898
|
|
|
1706
|
-
|
|
1707
|
-
|
|
1899
|
+
if (state.miss) {
|
|
1900
|
+
if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1901
|
+
try options.log.lastmiss(.{});
|
|
1902
|
+
return options.log.miss(.{battle.active(player)});
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
const move = battle.foe(player).lastMove(false);
|
|
1906
|
+
if (move == .None or move == .Struggle) {
|
|
1907
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
var slot: u4 = 0;
|
|
1911
|
+
for (side.active.moves, 0..) |m, i| {
|
|
1912
|
+
if (m.id == move) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1913
|
+
if (m.id == .Mimic) slot = @intCast(i + 1);
|
|
1914
|
+
}
|
|
1915
|
+
assert(slot != 0 and slot == state.mslot);
|
|
1916
|
+
|
|
1917
|
+
side.active.move(slot).id = move;
|
|
1918
|
+
side.active.move(slot).pp = 5;
|
|
1919
|
+
try options.log.start(.{ battle.active(player), .Mimic, move });
|
|
1708
1920
|
}
|
|
1709
1921
|
|
|
1710
1922
|
pub fn mirrorMove(
|
|
@@ -1721,11 +1933,11 @@ pub const Effects = struct {
|
|
|
1721
1933
|
|
|
1722
1934
|
const last = foe.lastMove(false);
|
|
1723
1935
|
|
|
1724
|
-
if (last == .None) return options.log.fail(battle.active(player), .None);
|
|
1936
|
+
if (last == .None) return options.log.fail(.{ battle.active(player), .None });
|
|
1725
1937
|
const knows = knows: for (side.active.moves) |m| {
|
|
1726
1938
|
if (last == m.id) break :knows true;
|
|
1727
1939
|
} else false;
|
|
1728
|
-
if (knows) return options.log.fail(battle.active(player), .None);
|
|
1940
|
+
if (knows) return options.log.fail(.{ battle.active(player), .None });
|
|
1729
1941
|
|
|
1730
1942
|
// if (wPlayerCharging) ???
|
|
1731
1943
|
// wPlayerCharging = 1
|
|
@@ -1739,17 +1951,17 @@ pub const Effects = struct {
|
|
|
1739
1951
|
pub fn mist(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1740
1952
|
var side = battle.side(player);
|
|
1741
1953
|
|
|
1742
|
-
if (side.active.volatiles.Mist) return options.log.fail(battle.active(player), .None);
|
|
1954
|
+
if (side.active.volatiles.Mist) return options.log.fail(.{ battle.active(player), .None });
|
|
1743
1955
|
side.active.volatiles.Mist = true;
|
|
1744
1956
|
|
|
1745
|
-
try options.log.start(battle.active(player), .Mist);
|
|
1957
|
+
try options.log.start(.{ battle.active(player), .Mist });
|
|
1746
1958
|
}
|
|
1747
1959
|
|
|
1748
1960
|
pub fn weatherHeal(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1749
1961
|
var stored = battle.side(player).stored();
|
|
1750
1962
|
const ident = battle.active(player);
|
|
1751
1963
|
|
|
1752
|
-
if (stored.hp == stored.stats.hp) return options.log.fail(ident, .None);
|
|
1964
|
+
if (stored.hp == stored.stats.hp) return options.log.fail(.{ ident, .None });
|
|
1753
1965
|
|
|
1754
1966
|
stored.hp = switch (battle.field.weather) {
|
|
1755
1967
|
.Sun => stored.stats.hp,
|
|
@@ -1757,7 +1969,7 @@ pub const Effects = struct {
|
|
|
1757
1969
|
else => @min(stored.stats.hp, stored.hp + (stored.stats.hp / 4)),
|
|
1758
1970
|
};
|
|
1759
1971
|
|
|
1760
|
-
try options.log.heal(ident, stored,
|
|
1972
|
+
try options.log.heal(.{ ident, stored, .None });
|
|
1761
1973
|
}
|
|
1762
1974
|
|
|
1763
1975
|
pub fn multiHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1770,17 +1982,17 @@ pub const Effects = struct {
|
|
|
1770
1982
|
const foe_ident = battle.active(player.foe());
|
|
1771
1983
|
|
|
1772
1984
|
if (foe_volatiles.Flying or foe_volatiles.Underground) {
|
|
1773
|
-
if (!showdown) return options.log.fail(battle.active(player.foe()), .None);
|
|
1774
|
-
try options.log.lastmiss();
|
|
1775
|
-
return options.log.miss(battle.active(player));
|
|
1985
|
+
if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1986
|
+
try options.log.lastmiss(.{});
|
|
1987
|
+
return options.log.miss(.{battle.active(player)});
|
|
1776
1988
|
} else if (foe_volatiles.Substitute) {
|
|
1777
|
-
return options.log.
|
|
1989
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1778
1990
|
} else if (!Status.is(foe.stored().status, .SLP) or foe_volatiles.Nightmare) {
|
|
1779
|
-
return options.log.fail(foe_ident, .None);
|
|
1991
|
+
return options.log.fail(.{ foe_ident, .None });
|
|
1780
1992
|
}
|
|
1781
1993
|
|
|
1782
1994
|
foe_volatiles.Nightmare = true;
|
|
1783
|
-
try options.log.start(battle.active(player.foe()), .Nightmare);
|
|
1995
|
+
try options.log.start(.{ battle.active(player.foe()), .Nightmare });
|
|
1784
1996
|
}
|
|
1785
1997
|
|
|
1786
1998
|
pub const ohko = struct {
|
|
@@ -1804,9 +2016,9 @@ pub const Effects = struct {
|
|
|
1804
2016
|
const foe_ident = battle.active(player.foe());
|
|
1805
2017
|
|
|
1806
2018
|
state.damage = 0;
|
|
1807
|
-
if (state.immune()) return options.log.immune(foe_ident, .None);
|
|
2019
|
+
if (state.immune()) return options.log.immune(.{ foe_ident, .None });
|
|
1808
2020
|
const delta = side.stored().level -| foe.stored().level;
|
|
1809
|
-
if (delta == 0) return options.log.immune(foe_ident, .OHKO);
|
|
2021
|
+
if (delta == 0) return options.log.immune(.{ foe_ident, .OHKO });
|
|
1810
2022
|
|
|
1811
2023
|
state.accuracy = 2 * delta +| Gen12.percent(30);
|
|
1812
2024
|
try checkHit(battle, player, state, options);
|
|
@@ -1822,37 +2034,39 @@ pub const Effects = struct {
|
|
|
1822
2034
|
const ident = battle.active(player);
|
|
1823
2035
|
const foe_ident = battle.active(player.foe());
|
|
1824
2036
|
|
|
1825
|
-
if (state.miss) return options.log.fail(foe_ident, .None);
|
|
2037
|
+
if (state.miss) return options.log.fail(.{ foe_ident, .None });
|
|
1826
2038
|
if (foe.active.volatiles.Substitute) {
|
|
1827
|
-
return options.log.
|
|
2039
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1828
2040
|
}
|
|
1829
2041
|
|
|
1830
2042
|
const avg = @max((foe.stored().hp + side.stored().hp) / 2, 1);
|
|
1831
2043
|
foe.stored().hp -= avg;
|
|
1832
2044
|
side.stored().hp = avg;
|
|
1833
2045
|
|
|
1834
|
-
try options.log.sethp(foe_ident, foe.stored(), .Silent);
|
|
1835
|
-
try options.log.sethp(ident, side.stored(), .None);
|
|
2046
|
+
try options.log.sethp(.{ foe_ident, foe.stored(), .Silent });
|
|
2047
|
+
try options.log.sethp(.{ ident, side.stored(), .None });
|
|
1836
2048
|
}
|
|
1837
2049
|
|
|
1838
2050
|
pub fn paralyze(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1839
2051
|
var foe = battle.foe(player);
|
|
1840
2052
|
const foe_ident = battle.active(player.foe());
|
|
1841
2053
|
|
|
1842
|
-
if (Status.is(foe.stored().status, .PAR))
|
|
1843
|
-
|
|
1844
|
-
|
|
2054
|
+
if (Status.is(foe.stored().status, .PAR)) {
|
|
2055
|
+
return options.log.fail(.{ foe_ident, .Paralysis });
|
|
2056
|
+
}
|
|
2057
|
+
if (state.immune()) return options.log.immune(.{ foe_ident, .None });
|
|
2058
|
+
if (Status.any(foe.stored().status)) return options.log.fail(.{ foe_ident, .None });
|
|
1845
2059
|
if (state.miss) {
|
|
1846
|
-
try options.log.lastmiss();
|
|
1847
|
-
return options.log.miss(battle.active(player));
|
|
2060
|
+
try options.log.lastmiss(.{});
|
|
2061
|
+
return options.log.miss(.{battle.active(player)});
|
|
1848
2062
|
} else if (foe.active.volatiles.Substitute) {
|
|
1849
|
-
return options.log.
|
|
2063
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1850
2064
|
}
|
|
1851
2065
|
|
|
1852
2066
|
foe.stored().status = Status.init(.PAR);
|
|
1853
2067
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
1854
2068
|
|
|
1855
|
-
try options.log.status(foe_ident, foe.stored().status, .None);
|
|
2069
|
+
try options.log.status(.{ foe_ident, foe.stored().status, .None });
|
|
1856
2070
|
}
|
|
1857
2071
|
|
|
1858
2072
|
pub fn paralyzeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1867,14 +2081,15 @@ pub const Effects = struct {
|
|
|
1867
2081
|
|
|
1868
2082
|
foe_stored.status = Status.init(.PAR);
|
|
1869
2083
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
1870
|
-
|
|
2084
|
+
|
|
2085
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1871
2086
|
|
|
1872
2087
|
// TODO: check for status berry
|
|
1873
2088
|
}
|
|
1874
2089
|
|
|
1875
2090
|
pub fn payDay(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1876
2091
|
if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
|
|
1877
|
-
try options.log.fieldactivate(); // FIXME .PayDay
|
|
2092
|
+
try options.log.fieldactivate(.{}); // FIXME .PayDay
|
|
1878
2093
|
}
|
|
1879
2094
|
}
|
|
1880
2095
|
|
|
@@ -1883,17 +2098,17 @@ pub const Effects = struct {
|
|
|
1883
2098
|
var foe = battle.foe(player);
|
|
1884
2099
|
|
|
1885
2100
|
if (foe.active.volatiles.PerishSong) {
|
|
1886
|
-
return options.log.fail(battle.active(player.foe()), .None);
|
|
2101
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1887
2102
|
} else if (!side.active.volatiles.PerishSong) {
|
|
1888
2103
|
side.active.volatiles.PerishSong = true;
|
|
1889
2104
|
side.active.volatiles.perish_song = 4;
|
|
1890
|
-
try options.log.start(battle.active(player), .PerishSong3Silent);
|
|
2105
|
+
try options.log.start(.{ battle.active(player), .PerishSong3Silent });
|
|
1891
2106
|
}
|
|
1892
2107
|
foe.active.volatiles.PerishSong = true;
|
|
1893
2108
|
foe.active.volatiles.perish_song = 4;
|
|
1894
|
-
try options.log.start(battle.active(player.foe()), .PerishSong3Silent);
|
|
2109
|
+
try options.log.start(.{ battle.active(player.foe()), .PerishSong3Silent });
|
|
1895
2110
|
|
|
1896
|
-
try options.log.fieldactivate(); // FIXME .PerishSong
|
|
2111
|
+
try options.log.fieldactivate(.{}); // FIXME .PerishSong
|
|
1897
2112
|
}
|
|
1898
2113
|
|
|
1899
2114
|
pub fn poison(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1903,24 +2118,24 @@ pub const Effects = struct {
|
|
|
1903
2118
|
|
|
1904
2119
|
const toxic = state.move == .Toxic;
|
|
1905
2120
|
if (state.immune() or foe.active.types.includes(.Poison)) {
|
|
1906
|
-
return options.log.immune(foe_ident, .None);
|
|
2121
|
+
return options.log.immune(.{ foe_ident, .None });
|
|
1907
2122
|
} else if (Status.any(foe_stored.status)) {
|
|
1908
2123
|
// Pokémon Showdown considers Toxic to be a status even in Generation II and so
|
|
1909
2124
|
// will not include a fail reason for Toxic vs. Poison or vice-versa...
|
|
1910
|
-
return options.log.fail(foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2125
|
+
return options.log.fail(.{ foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
1911
2126
|
if (!showdown)
|
|
1912
|
-
.Poison
|
|
2127
|
+
Fail.Poison
|
|
1913
2128
|
else if (toxic == (foe_stored.status == Status.TOX))
|
|
1914
|
-
if (toxic) .Toxic else .Poison
|
|
2129
|
+
if (toxic) Fail.Toxic else Fail.Poison
|
|
1915
2130
|
else
|
|
1916
|
-
.None
|
|
2131
|
+
Fail.None
|
|
1917
2132
|
else
|
|
1918
|
-
.None);
|
|
2133
|
+
Fail.None });
|
|
1919
2134
|
} else if (foe.active.volatiles.Substitute) {
|
|
1920
|
-
return options.log.
|
|
2135
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1921
2136
|
} else if (state.miss) {
|
|
1922
|
-
try options.log.lastmiss();
|
|
1923
|
-
return options.log.miss(battle.active(player));
|
|
2137
|
+
try options.log.lastmiss(.{});
|
|
2138
|
+
return options.log.miss(.{battle.active(player)});
|
|
1924
2139
|
}
|
|
1925
2140
|
|
|
1926
2141
|
foe_stored.status = Status.init(.PSN);
|
|
@@ -1930,7 +2145,7 @@ pub const Effects = struct {
|
|
|
1930
2145
|
foe.active.volatiles.toxic = 0;
|
|
1931
2146
|
}
|
|
1932
2147
|
|
|
1933
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
2148
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
1934
2149
|
|
|
1935
2150
|
// TODO: check for status berry
|
|
1936
2151
|
}
|
|
@@ -1946,7 +2161,8 @@ pub const Effects = struct {
|
|
|
1946
2161
|
if (foe.conditions.Safeguard) return;
|
|
1947
2162
|
|
|
1948
2163
|
foe_stored.status = Status.init(.PSN);
|
|
1949
|
-
|
|
2164
|
+
|
|
2165
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1950
2166
|
|
|
1951
2167
|
// TODO: check for status berry
|
|
1952
2168
|
}
|
|
@@ -1961,9 +2177,7 @@ pub const Effects = struct {
|
|
|
1961
2177
|
|
|
1962
2178
|
const failed = !state.first or volatiles.Substitute or
|
|
1963
2179
|
!try Rolls.protect(battle, player, volatiles.protect, options);
|
|
1964
|
-
if (failed) {
|
|
1965
|
-
return options.log.fail(battle.active(player), .None);
|
|
1966
|
-
}
|
|
2180
|
+
if (failed) return options.log.fail(.{ battle.active(player), .None });
|
|
1967
2181
|
|
|
1968
2182
|
volatiles.protect = @max(8, volatiles.protect + 1);
|
|
1969
2183
|
if (state.move == .Endure) {
|
|
@@ -1971,7 +2185,7 @@ pub const Effects = struct {
|
|
|
1971
2185
|
} else {
|
|
1972
2186
|
volatiles.Protect = true;
|
|
1973
2187
|
}
|
|
1974
|
-
try options.log.singleturn(battle.active(player), state.move);
|
|
2188
|
+
try options.log.singleturn(.{ battle.active(player), state.move });
|
|
1975
2189
|
}
|
|
1976
2190
|
|
|
1977
2191
|
pub fn psychUp(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
@@ -1980,30 +2194,29 @@ pub const Effects = struct {
|
|
|
1980
2194
|
|
|
1981
2195
|
// Pokémon Showdown should fail when the enemy has no boosts/unboosts but doesn't
|
|
1982
2196
|
if (!showdown and @as(u32, @bitCast(foe.active.boosts)) == 0) {
|
|
1983
|
-
return options.log.fail(battle.active(player), .None);
|
|
2197
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
1984
2198
|
}
|
|
1985
2199
|
|
|
1986
2200
|
side.active.boosts = foe.active.boosts;
|
|
1987
|
-
try options.log.copyboost(battle.active(player), battle.active(player.foe()));
|
|
2201
|
+
try options.log.copyboost(.{ battle.active(player), battle.active(player.foe()) });
|
|
1988
2202
|
}
|
|
1989
2203
|
|
|
1990
2204
|
pub fn pursuit(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1991
2205
|
// TODO: remove switching variable and just look at Choice saved in Battle struct?
|
|
1992
|
-
if (battle.foe(player).active.volatiles.switching) state.damage
|
|
2206
|
+
if (battle.foe(player).active.volatiles.switching) state.damage = smul(state.damage, 2);
|
|
1993
2207
|
}
|
|
1994
2208
|
|
|
1995
2209
|
pub const rage = struct {
|
|
1996
2210
|
pub fn start(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1997
2211
|
battle.side(player).active.volatiles.Rage = true;
|
|
1998
|
-
try options.log.singlemove(battle.active(player), Move.Rage);
|
|
2212
|
+
try options.log.singlemove(.{ battle.active(player), Move.Rage });
|
|
1999
2213
|
}
|
|
2000
2214
|
|
|
2001
2215
|
pub fn damage(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2002
|
-
|
|
2003
|
-
var volatiles = &side.active.volatiles;
|
|
2216
|
+
const side = battle.side(player);
|
|
2004
2217
|
|
|
2005
|
-
assert(volatiles.Rage);
|
|
2006
|
-
state.damage
|
|
2218
|
+
assert(side.active.volatiles.Rage);
|
|
2219
|
+
state.damage = smul(state.damage, (side.active.volatiles.rage +| 1)); // TODO: |+
|
|
2007
2220
|
}
|
|
2008
2221
|
};
|
|
2009
2222
|
|
|
@@ -2015,18 +2228,18 @@ pub const Effects = struct {
|
|
|
2015
2228
|
var side = battle.side(player);
|
|
2016
2229
|
|
|
2017
2230
|
if (side.active.volatiles.LeechSeed) {
|
|
2018
|
-
try options.log.end(battle.active(player), .LeechSeedFrom);
|
|
2231
|
+
try options.log.end(.{ battle.active(player), .LeechSeedFrom });
|
|
2019
2232
|
side.active.volatiles.LeechSeed = false;
|
|
2020
2233
|
}
|
|
2021
2234
|
|
|
2022
2235
|
if (side.conditions.Spikes) {
|
|
2023
|
-
try options.log.
|
|
2236
|
+
try options.log.sideend(.{ player, protocol.Side.Spikes, battle.active(player) });
|
|
2024
2237
|
side.conditions.Spikes = false;
|
|
2025
2238
|
}
|
|
2026
2239
|
|
|
2027
2240
|
if (side.active.volatiles.bind.duration > 0) {
|
|
2028
2241
|
const reason = @intFromEnum(End.Bind) + side.active.volatiles.bind.reason - 1;
|
|
2029
|
-
try options.log.end(battle.active(player), @enumFromInt(reason));
|
|
2242
|
+
try options.log.end(.{ battle.active(player), @as(End, @enumFromInt(reason)) });
|
|
2030
2243
|
side.active.volatiles.bind = .{};
|
|
2031
2244
|
}
|
|
2032
2245
|
}
|
|
@@ -2034,18 +2247,14 @@ pub const Effects = struct {
|
|
|
2034
2247
|
pub fn recoil(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2035
2248
|
var side = battle.side(player);
|
|
2036
2249
|
var stored = side.stored();
|
|
2250
|
+
const ident = battle.active(player);
|
|
2037
2251
|
|
|
2038
2252
|
assert(state.damage > 0);
|
|
2039
2253
|
|
|
2040
2254
|
const damage: i16 = @intCast(@max(state.damage / 4, 1));
|
|
2041
2255
|
stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
|
|
2042
2256
|
|
|
2043
|
-
try options.log.
|
|
2044
|
-
battle.active(player),
|
|
2045
|
-
stored,
|
|
2046
|
-
.RecoilOf,
|
|
2047
|
-
battle.active(player.foe()),
|
|
2048
|
-
);
|
|
2257
|
+
try options.log.damage(.{ ident, stored, .RecoilOf, battle.active(player.foe()) });
|
|
2049
2258
|
}
|
|
2050
2259
|
|
|
2051
2260
|
pub fn reversal(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2102,13 +2311,13 @@ pub const Effects = struct {
|
|
|
2102
2311
|
side.active.volatiles.attacks += 1;
|
|
2103
2312
|
side.active.volatiles.Rollout = side.active.volatiles.attacks != 5;
|
|
2104
2313
|
|
|
2105
|
-
if (side.active.volatiles.DefenseCurl) state.damage
|
|
2314
|
+
if (side.active.volatiles.DefenseCurl) state.damage = smul(state.damage, 2);
|
|
2106
2315
|
}
|
|
2107
2316
|
};
|
|
2108
2317
|
|
|
2109
2318
|
pub fn sandstorm(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2110
2319
|
if (battle.field.weather != .Sandstorm) return weather(battle, .Sandstorm, options);
|
|
2111
|
-
return options.log.fail(battle.active(player), .None);
|
|
2320
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
2112
2321
|
}
|
|
2113
2322
|
|
|
2114
2323
|
pub fn safeguard(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
@@ -2132,11 +2341,13 @@ pub const Effects = struct {
|
|
|
2132
2341
|
) !void {
|
|
2133
2342
|
var side = battle.side(player);
|
|
2134
2343
|
|
|
2135
|
-
if (@field(side.conditions, key))
|
|
2344
|
+
if (@field(side.conditions, key)) {
|
|
2345
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
2346
|
+
}
|
|
2136
2347
|
|
|
2137
2348
|
@field(side.conditions, key) = true;
|
|
2138
2349
|
@field(side.conditions, value) = 5;
|
|
2139
|
-
try options.log.sidestart(player, @field(protocol.Side, key));
|
|
2350
|
+
try options.log.sidestart(.{ player, @field(protocol.Side, key) });
|
|
2140
2351
|
}
|
|
2141
2352
|
|
|
2142
2353
|
pub fn sketch(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
@@ -2147,9 +2358,9 @@ pub const Effects = struct {
|
|
|
2147
2358
|
|
|
2148
2359
|
if (showdown) {
|
|
2149
2360
|
// yes, actually "Splash". '|nothing' gets blindly mapped to '|-activate||move:Splash'
|
|
2150
|
-
try options.log.activate(battle.active(player), .Splash);
|
|
2361
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
2151
2362
|
} else {
|
|
2152
|
-
try options.log.fail(battle.active(player), .None);
|
|
2363
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2153
2364
|
}
|
|
2154
2365
|
}
|
|
2155
2366
|
|
|
@@ -2159,40 +2370,39 @@ pub const Effects = struct {
|
|
|
2159
2370
|
const foe_ident = battle.active(player.foe());
|
|
2160
2371
|
|
|
2161
2372
|
if (Status.any(foe_stored.status)) {
|
|
2162
|
-
return options.log.fail(
|
|
2373
|
+
return options.log.fail(.{
|
|
2163
2374
|
foe_ident,
|
|
2164
|
-
if (Status.is(foe_stored.status, .SLP)) .Sleep else .None,
|
|
2165
|
-
);
|
|
2375
|
+
if (Status.is(foe_stored.status, .SLP)) Fail.Sleep else Fail.None,
|
|
2376
|
+
});
|
|
2166
2377
|
}
|
|
2167
2378
|
|
|
2168
2379
|
if (foe.active.volatiles.Substitute) {
|
|
2169
|
-
return options.log.
|
|
2380
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
2170
2381
|
}
|
|
2171
2382
|
|
|
2172
2383
|
// Sleep Clause Mod
|
|
2173
|
-
if (
|
|
2384
|
+
if (pkmn.options.mod) {
|
|
2174
2385
|
for (foe.pokemon) |p| {
|
|
2175
2386
|
if (Status.is(p.status, .SLP) and !Status.is(p.status, .EXT)) return;
|
|
2176
2387
|
}
|
|
2177
2388
|
}
|
|
2178
2389
|
|
|
2179
2390
|
foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
|
|
2180
|
-
try options.log.
|
|
2391
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .From, state.move });
|
|
2181
2392
|
}
|
|
2182
2393
|
|
|
2183
2394
|
pub fn sleepTalk(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2184
2395
|
const side = battle.side(player);
|
|
2185
2396
|
|
|
2186
2397
|
if (!Status.is(side.stored().status, .SLP)) {
|
|
2187
|
-
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
2188
|
-
return;
|
|
2398
|
+
return if (!showdown) try options.log.fail(.{ battle.active(player), .None });
|
|
2189
2399
|
}
|
|
2190
2400
|
|
|
2191
2401
|
var n: u3 = 0;
|
|
2192
2402
|
for (side.active.moves, 0..) |m, i| {
|
|
2193
2403
|
if (state.mslot - 1 != i and Move.get(m.id).extra.sleep_talk) n += 1;
|
|
2194
2404
|
}
|
|
2195
|
-
if (n == 0) return options.log.fail(battle.active(player), .None);
|
|
2405
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
2196
2406
|
|
|
2197
2407
|
_ = try Rolls.sleepTalk(battle, player, n, state.mslot, options); // TODO
|
|
2198
2408
|
}
|
|
@@ -2201,55 +2411,75 @@ pub const Effects = struct {
|
|
|
2201
2411
|
if (Status.is(battle.side(player).stored().status, .SLP)) return true;
|
|
2202
2412
|
state.damage = 0;
|
|
2203
2413
|
state.miss = true; // TODO is this necessary?
|
|
2204
|
-
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
2414
|
+
if (!showdown) try options.log.fail(.{ battle.active(player), .None });
|
|
2205
2415
|
return false;
|
|
2206
2416
|
}
|
|
2207
2417
|
|
|
2208
2418
|
pub fn spikes(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2209
2419
|
var foe = battle.foe(player);
|
|
2210
|
-
if (foe.conditions.Spikes) return options.log.fail(battle.active(player), .None);
|
|
2420
|
+
if (foe.conditions.Spikes) return options.log.fail(.{ battle.active(player), .None });
|
|
2211
2421
|
|
|
2212
2422
|
foe.conditions.Spikes = true;
|
|
2213
|
-
try options.log.sidestart(player.foe(), .Spikes);
|
|
2423
|
+
try options.log.sidestart(.{ player.foe(), protocol.Side.Spikes });
|
|
2214
2424
|
}
|
|
2215
2425
|
|
|
2216
2426
|
pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2217
|
-
|
|
2218
|
-
// var foe = battle.foe(player);
|
|
2427
|
+
var foe = battle.foe(player);
|
|
2219
2428
|
|
|
2220
|
-
|
|
2429
|
+
const last = foe.lastMove(false);
|
|
2430
|
+
if (state.miss or last == .None or last == .Struggle) {
|
|
2431
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2432
|
+
}
|
|
2221
2433
|
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
//
|
|
2227
|
-
|
|
2434
|
+
var slot: u4 = 0;
|
|
2435
|
+
for (foe.active.moves, 0..) |m, i| {
|
|
2436
|
+
if (m.id == last) slot = @intCast(i + 1);
|
|
2437
|
+
}
|
|
2438
|
+
// If the move was called via Metronome it might not exist
|
|
2439
|
+
if (slot == 0 or foe.active.move(slot).pp == 0) {
|
|
2440
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
// Pokémon Showdown always deducts 4 instead of randomizing
|
|
2444
|
+
const pp = @min(
|
|
2445
|
+
foe.active.move(slot).pp,
|
|
2446
|
+
if (showdown) 4 else try Rolls.spite(battle, player, options),
|
|
2447
|
+
);
|
|
2448
|
+
foe.active.move(slot).pp -= pp;
|
|
2449
|
+
if (!foe.active.volatiles.Transform) {
|
|
2450
|
+
var move_slot = foe.stored().move(slot);
|
|
2451
|
+
assert(move_slot.pp > 0);
|
|
2452
|
+
|
|
2453
|
+
move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
|
|
2454
|
+
assert(foe.active.move(slot).pp == foe.stored().move(slot).pp);
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
try options.log.activate(.{ battle.active(player.foe()), .Spite, last, pp });
|
|
2228
2458
|
}
|
|
2229
2459
|
|
|
2230
2460
|
pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2231
|
-
try options.log.activate(battle.active(player), .Splash);
|
|
2461
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
2232
2462
|
}
|
|
2233
2463
|
|
|
2234
2464
|
pub fn substitute(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2235
2465
|
var side = battle.side(player);
|
|
2236
2466
|
if (side.active.volatiles.Substitute) {
|
|
2237
|
-
return options.log.fail(battle.active(player), .Substitute);
|
|
2467
|
+
return options.log.fail(.{ battle.active(player), .Substitute });
|
|
2238
2468
|
}
|
|
2239
2469
|
|
|
2240
2470
|
assert(side.stored().stats.hp <= 1023);
|
|
2241
2471
|
const hp: u8 = @intCast(side.stored().stats.hp / 4);
|
|
2242
2472
|
|
|
2243
2473
|
if (hp == 0 or side.stored().hp <= hp) {
|
|
2244
|
-
return options.log.fail(battle.active(player), .Weak);
|
|
2474
|
+
return options.log.fail(.{ battle.active(player), .Weak });
|
|
2245
2475
|
}
|
|
2246
2476
|
|
|
2247
2477
|
side.stored().hp -= hp;
|
|
2248
2478
|
side.active.volatiles.substitute = hp;
|
|
2249
2479
|
side.active.volatiles.Substitute = true;
|
|
2250
2480
|
side.active.volatiles.bind = .{};
|
|
2251
|
-
try options.log.start(battle.active(player), .Substitute);
|
|
2252
|
-
try options.log.damage(battle.active(player), side.stored(), .None);
|
|
2481
|
+
try options.log.start(.{ battle.active(player), .Substitute });
|
|
2482
|
+
try options.log.damage(.{ battle.active(player), side.stored(), .None });
|
|
2253
2483
|
}
|
|
2254
2484
|
|
|
2255
2485
|
pub fn sunnyDay(battle: anytype, _: Player, _: *State, options: anytype) !void {
|
|
@@ -2259,14 +2489,14 @@ pub const Effects = struct {
|
|
|
2259
2489
|
fn weather(battle: anytype, w: Weather, options: anytype) !void {
|
|
2260
2490
|
battle.field.weather = w;
|
|
2261
2491
|
battle.field.weather_duration = 5;
|
|
2262
|
-
try options.log.weather(w, .None);
|
|
2492
|
+
try options.log.weather(.{ w, protocol.Weather.None });
|
|
2263
2493
|
}
|
|
2264
2494
|
|
|
2265
2495
|
pub fn teleport(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2266
2496
|
if (showdown) return;
|
|
2267
2497
|
|
|
2268
|
-
try options.log.fail(battle.active(player), .None);
|
|
2269
|
-
try options.log.laststill();
|
|
2498
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2499
|
+
try options.log.laststill(.{});
|
|
2270
2500
|
}
|
|
2271
2501
|
|
|
2272
2502
|
pub fn thief(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2281,11 +2511,11 @@ pub const Effects = struct {
|
|
|
2281
2511
|
side.stored().item = foe.stored().item;
|
|
2282
2512
|
foe.stored().item = .None;
|
|
2283
2513
|
|
|
2284
|
-
try options.log.item(
|
|
2514
|
+
try options.log.item(.{
|
|
2285
2515
|
battle.active(player),
|
|
2286
2516
|
side.stored().item,
|
|
2287
2517
|
battle.active(player.foe()),
|
|
2288
|
-
);
|
|
2518
|
+
});
|
|
2289
2519
|
}
|
|
2290
2520
|
|
|
2291
2521
|
pub fn thrashing(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
@@ -2307,11 +2537,11 @@ pub const Effects = struct {
|
|
|
2307
2537
|
side.active.volatiles.dirty = false;
|
|
2308
2538
|
|
|
2309
2539
|
if (foe.active.volatiles.Transform) {
|
|
2310
|
-
return options.log.fail(battle.active(player), .None);
|
|
2540
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
2311
2541
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2312
|
-
if (!showdown) return options.log.fail(battle.active(player), .None);
|
|
2313
|
-
try options.log.lastmiss();
|
|
2314
|
-
return options.log.miss(battle.active(player));
|
|
2542
|
+
if (!showdown) return options.log.fail(.{ battle.active(player), .None });
|
|
2543
|
+
try options.log.lastmiss(.{});
|
|
2544
|
+
return options.log.miss(.{battle.active(player)});
|
|
2315
2545
|
}
|
|
2316
2546
|
|
|
2317
2547
|
side.active.volatiles.Transform = true;
|
|
@@ -2333,7 +2563,7 @@ pub const Effects = struct {
|
|
|
2333
2563
|
side.active.moves[i].pp = if (m.id != .None) if (m.id == .Sketch) 1 else 5 else 0;
|
|
2334
2564
|
}
|
|
2335
2565
|
|
|
2336
|
-
try options.log.transform(battle.active(player), foe_ident);
|
|
2566
|
+
try options.log.transform(.{ battle.active(player), foe_ident });
|
|
2337
2567
|
}
|
|
2338
2568
|
|
|
2339
2569
|
pub fn triAttack(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2352,31 +2582,25 @@ pub const Effects = struct {
|
|
|
2352
2582
|
_ = .{ battle, player, state, options }; // TODO
|
|
2353
2583
|
}
|
|
2354
2584
|
|
|
2355
|
-
const
|
|
2356
|
-
const ALL_STAT_UP_REASONS =
|
|
2585
|
+
const STAT_UP_REASONS =
|
|
2357
2586
|
[_]Boost{ .Attack, .Defense, .Speed, .SpecialAttack, .SpecialDefense };
|
|
2358
2587
|
|
|
2359
2588
|
pub fn allStatUpChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2360
2589
|
var side = battle.side(player);
|
|
2361
2590
|
|
|
2362
|
-
|
|
2363
|
-
|
|
2591
|
+
const stats = &side.active.stats;
|
|
2592
|
+
const boosts = &side.active.boosts;
|
|
2364
2593
|
|
|
2365
2594
|
if (!state.proc) return;
|
|
2366
|
-
inline for (
|
|
2595
|
+
inline for (STATS, STAT_UP_REASONS) |s, r| {
|
|
2367
2596
|
assert(@field(boosts, s) >= -6 and @field(boosts, s) <= 6);
|
|
2368
2597
|
if (@field(boosts, s) == 6 or @field(stats, s) == MAX_STAT_VALUE) {
|
|
2369
|
-
try options.log.boost(battle.active(player), r, 0);
|
|
2598
|
+
try options.log.boost(.{ battle.active(player), r, 0 });
|
|
2370
2599
|
} else {
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2374
|
-
@field(stats, s) = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2375
|
-
try options.log.boost(battle.active(player), r, 1);
|
|
2600
|
+
try boostStat(battle, side, stats, boosts, s, 1);
|
|
2601
|
+
try options.log.boost(.{ battle.active(player), r, 1 });
|
|
2376
2602
|
}
|
|
2377
2603
|
}
|
|
2378
|
-
|
|
2379
|
-
statusModify(side.stored().status, stats);
|
|
2380
2604
|
}
|
|
2381
2605
|
|
|
2382
2606
|
pub fn boost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2384,72 +2608,65 @@ pub const Effects = struct {
|
|
|
2384
2608
|
var side = battle.side(player);
|
|
2385
2609
|
const ident = battle.active(player);
|
|
2386
2610
|
|
|
2387
|
-
if (state.miss) return log.fail(ident, .None);
|
|
2611
|
+
if (state.miss) return log.fail(.{ ident, .None });
|
|
2388
2612
|
|
|
2389
|
-
|
|
2613
|
+
const stats = &side.active.stats;
|
|
2390
2614
|
var boosts = &side.active.boosts;
|
|
2391
2615
|
|
|
2392
2616
|
const move = Move.get(state.move);
|
|
2393
2617
|
switch (move.effect) {
|
|
2394
2618
|
.AttackUp1, .AttackUp2, .Rage, .Swagger => {
|
|
2395
2619
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2396
|
-
if (boosts.atk == 6 or stats.atk == MAX_STAT_VALUE)
|
|
2620
|
+
if (boosts.atk == 6 or stats.atk == MAX_STAT_VALUE) {
|
|
2621
|
+
return log.fail(.{ ident, .None });
|
|
2622
|
+
}
|
|
2397
2623
|
const n: u2 = if (move.effect == .AttackUp2 or move.effect == .Swagger) 2 else 1;
|
|
2398
|
-
|
|
2624
|
+
try boostStat(battle, side, stats, boosts, "atk", n);
|
|
2399
2625
|
const reason = if (showdown and move.effect == .Rage) Boost.Rage else Boost.Attack;
|
|
2400
|
-
|
|
2401
|
-
const stat = unmodifiedStats(battle, side).atk;
|
|
2402
|
-
stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2403
|
-
try log.boost(ident, reason, n);
|
|
2626
|
+
try log.boost(.{ ident, reason, n });
|
|
2404
2627
|
},
|
|
2405
2628
|
.DefenseUp1, .DefenseUp2, .SkullBash => {
|
|
2406
2629
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2407
|
-
if (boosts.def == 6 or stats.def == MAX_STAT_VALUE)
|
|
2630
|
+
if (boosts.def == 6 or stats.def == MAX_STAT_VALUE) {
|
|
2631
|
+
return log.fail(.{ ident, .None });
|
|
2632
|
+
}
|
|
2408
2633
|
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
const stat = unmodifiedStats(battle, side).def;
|
|
2412
|
-
stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2413
|
-
try log.boost(ident, .Defense, n);
|
|
2634
|
+
try boostStat(battle, side, stats, boosts, "def", n);
|
|
2635
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2414
2636
|
},
|
|
2415
2637
|
.SpeedUp2 => {
|
|
2416
2638
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2417
|
-
if (boosts.spe == 6 or stats.spe == MAX_STAT_VALUE)
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
try log.boost(ident, .Speed, 2);
|
|
2639
|
+
if (boosts.spe == 6 or stats.spe == MAX_STAT_VALUE) {
|
|
2640
|
+
return log.fail(.{ ident, .None });
|
|
2641
|
+
}
|
|
2642
|
+
try boostStat(battle, side, stats, boosts, "spe", 2);
|
|
2643
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2423
2644
|
},
|
|
2424
2645
|
.SpAtkUp1 => {
|
|
2425
2646
|
assert(boosts.spa >= -6 and boosts.spa <= 6);
|
|
2426
|
-
if (boosts.spa == 6 or stats.spa == MAX_STAT_VALUE)
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
try log.boost(ident, .SpecialAttack, 1);
|
|
2647
|
+
if (boosts.spa == 6 or stats.spa == MAX_STAT_VALUE) {
|
|
2648
|
+
return log.fail(.{ ident, .None });
|
|
2649
|
+
}
|
|
2650
|
+
try boostStat(battle, side, stats, boosts, "spa", 1);
|
|
2651
|
+
try log.boost(.{ ident, .SpecialAttack, 1 });
|
|
2432
2652
|
},
|
|
2433
2653
|
.SpDefUp2 => {
|
|
2434
2654
|
assert(boosts.spd >= -6 and boosts.spd <= 6);
|
|
2435
|
-
if (boosts.spd == 6 or stats.spd == MAX_STAT_VALUE)
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
try log.boost(ident, .SpecialDefense, 2);
|
|
2655
|
+
if (boosts.spd == 6 or stats.spd == MAX_STAT_VALUE) {
|
|
2656
|
+
return log.fail(.{ ident, .None });
|
|
2657
|
+
}
|
|
2658
|
+
try boostStat(battle, side, stats, boosts, "spd", 2);
|
|
2659
|
+
try log.boost(.{ ident, .SpecialDefense, 2 });
|
|
2441
2660
|
},
|
|
2442
2661
|
.EvasionUp1 => {
|
|
2443
2662
|
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2444
|
-
if (boosts.evasion == 6) return log.fail(ident, .None);
|
|
2663
|
+
if (boosts.evasion == 6) return log.fail(.{ ident, .None });
|
|
2445
2664
|
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2446
|
-
try log.boost(ident, .Evasion, 1);
|
|
2665
|
+
try log.boost(.{ ident, .Evasion, 1 });
|
|
2447
2666
|
if (state.move == .Minimize) side.active.volatiles.minimized = true;
|
|
2448
2667
|
},
|
|
2449
2668
|
else => unreachable,
|
|
2450
2669
|
}
|
|
2451
|
-
|
|
2452
|
-
statusModify(side.stored().status, stats);
|
|
2453
2670
|
}
|
|
2454
2671
|
|
|
2455
2672
|
pub fn unboost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2458,97 +2675,86 @@ pub const Effects = struct {
|
|
|
2458
2675
|
const foe_ident = battle.active(player.foe());
|
|
2459
2676
|
|
|
2460
2677
|
if (foe.active.volatiles.Mist) {
|
|
2461
|
-
try log.activate(foe_ident, .Mist);
|
|
2462
|
-
return log.fail(foe_ident, .None);
|
|
2678
|
+
try log.activate(.{ foe_ident, .Mist });
|
|
2679
|
+
return log.fail(.{ foe_ident, .None });
|
|
2463
2680
|
}
|
|
2464
2681
|
|
|
2465
2682
|
const fail = foe.active.volatiles.Substitute or state.miss or !state.proc;
|
|
2466
2683
|
|
|
2467
|
-
|
|
2684
|
+
const stats = &foe.active.stats;
|
|
2468
2685
|
var boosts = &foe.active.boosts;
|
|
2469
2686
|
|
|
2470
2687
|
const move = Move.get(state.move);
|
|
2688
|
+
const none: Fail = .None;
|
|
2471
2689
|
switch (move.effect) {
|
|
2472
2690
|
.AttackDown1, .AttackDown2, .AttackDownChance => {
|
|
2473
2691
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2474
2692
|
if (fail or boosts.atk == -6) {
|
|
2475
|
-
return if (move.effect != .AttackDownChance) try log.fail(foe_ident,
|
|
2693
|
+
return if (move.effect != .AttackDownChance) try log.fail(.{ foe_ident, none });
|
|
2476
2694
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2477
|
-
try options.log.lastmiss();
|
|
2478
|
-
return options.log.miss(battle.active(player));
|
|
2695
|
+
try options.log.lastmiss(.{});
|
|
2696
|
+
return options.log.miss(.{battle.active(player)});
|
|
2479
2697
|
} else if (stats.atk == 1) {
|
|
2480
|
-
return if (move.effect != .AttackDownChance) try log.fail(foe_ident,
|
|
2698
|
+
return if (move.effect != .AttackDownChance) try log.fail(.{ foe_ident, none });
|
|
2481
2699
|
}
|
|
2482
2700
|
const n: u2 = if (move.effect == .AttackDown2) 2 else 1;
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
const stat = unmodifiedStats(battle, foe).atk;
|
|
2486
|
-
stats.atk = @max(1, stat * mod[0] / mod[1]);
|
|
2487
|
-
try log.boost(foe_ident, .Attack, -@as(i8, n));
|
|
2701
|
+
try unboostStat(battle, foe, stats, boosts, "atk", n);
|
|
2702
|
+
try log.boost(.{ foe_ident, .Attack, -@as(i8, n) });
|
|
2488
2703
|
},
|
|
2489
2704
|
.DefenseDown1, .DefenseDown2, .DefenseDownChance => {
|
|
2490
2705
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2491
2706
|
if (fail or boosts.def == -6) {
|
|
2492
|
-
|
|
2707
|
+
if (move.effect != .DefenseDownChance) try log.fail(.{ foe_ident, none });
|
|
2708
|
+
return;
|
|
2493
2709
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2494
|
-
try options.log.lastmiss();
|
|
2495
|
-
return options.log.miss(battle.active(player));
|
|
2710
|
+
try options.log.lastmiss(.{});
|
|
2711
|
+
return options.log.miss(.{battle.active(player)});
|
|
2496
2712
|
} else if (stats.def == 1) {
|
|
2497
|
-
|
|
2713
|
+
if (move.effect != .DefenseDownChance) try log.fail(.{ foe_ident, none });
|
|
2714
|
+
return;
|
|
2498
2715
|
}
|
|
2499
2716
|
const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
const stat = unmodifiedStats(battle, foe).def;
|
|
2503
|
-
stats.def = @max(1, stat * mod[0] / mod[1]);
|
|
2504
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2717
|
+
try unboostStat(battle, foe, stats, boosts, "def", n);
|
|
2718
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2505
2719
|
},
|
|
2506
2720
|
.SpeedDown1, .SpeedDown2, .SpeedDownChance => {
|
|
2507
2721
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2508
2722
|
if (fail or boosts.spe == -6) {
|
|
2509
|
-
return if (move.effect != .SpeedDownChance) try log.fail(foe_ident,
|
|
2723
|
+
return if (move.effect != .SpeedDownChance) try log.fail(.{ foe_ident, none });
|
|
2510
2724
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2511
|
-
try options.log.lastmiss();
|
|
2512
|
-
return options.log.miss(battle.active(player));
|
|
2725
|
+
try options.log.lastmiss(.{});
|
|
2726
|
+
return options.log.miss(.{battle.active(player)});
|
|
2513
2727
|
} else if (stats.spe == 1) {
|
|
2514
|
-
return if (move.effect != .SpeedDownChance) try log.fail(foe_ident,
|
|
2728
|
+
return if (move.effect != .SpeedDownChance) try log.fail(.{ foe_ident, none });
|
|
2515
2729
|
}
|
|
2516
2730
|
const n: u2 = if (move.effect == .SpeedDown2) 2 else 1;
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
const stat = unmodifiedStats(battle, foe).spe;
|
|
2520
|
-
stats.spe = @max(1, stat * mod[0] / mod[1]);
|
|
2521
|
-
try log.boost(foe_ident, .Speed, -@as(i8, n));
|
|
2522
|
-
assert(boosts.spe >= -6);
|
|
2731
|
+
try unboostStat(battle, foe, stats, boosts, "spe", n);
|
|
2732
|
+
try log.boost(.{ foe_ident, .Speed, -@as(i8, n) });
|
|
2523
2733
|
},
|
|
2524
2734
|
.SpDefDownChance => {
|
|
2525
2735
|
assert(boosts.spd >= -6 and boosts.spd <= 6);
|
|
2526
2736
|
if (fail or boosts.spd == -6) return;
|
|
2527
2737
|
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2528
|
-
try options.log.lastmiss();
|
|
2529
|
-
return options.log.miss(battle.active(player));
|
|
2738
|
+
try options.log.lastmiss(.{});
|
|
2739
|
+
return options.log.miss(.{battle.active(player)});
|
|
2530
2740
|
} else if (stats.spd == 1) return;
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
const stat = unmodifiedStats(battle, foe).spd;
|
|
2534
|
-
stats.spd = @max(1, stat * mod[0] / mod[1]);
|
|
2535
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2741
|
+
try unboostStat(battle, foe, stats, boosts, "spd", 1);
|
|
2742
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2536
2743
|
},
|
|
2537
2744
|
.AccuracyDown1, .AccuracyDownChance => {
|
|
2538
2745
|
assert(boosts.accuracy >= -6 and boosts.accuracy <= 6);
|
|
2539
2746
|
if (fail or boosts.accuracy == -6) {
|
|
2540
|
-
|
|
2747
|
+
if (move.effect != .AccuracyDownChance) try log.fail(.{ foe_ident, none });
|
|
2748
|
+
return;
|
|
2541
2749
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2542
|
-
try options.log.lastmiss();
|
|
2543
|
-
return options.log.miss(battle.active(player));
|
|
2750
|
+
try options.log.lastmiss(.{});
|
|
2751
|
+
return options.log.miss(.{battle.active(player)});
|
|
2544
2752
|
}
|
|
2545
2753
|
boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
|
|
2546
|
-
try log.boost(foe_ident, .Accuracy, -1);
|
|
2754
|
+
try log.boost(.{ foe_ident, .Accuracy, -1 });
|
|
2547
2755
|
},
|
|
2548
2756
|
else => unreachable,
|
|
2549
2757
|
}
|
|
2550
|
-
|
|
2551
|
-
statusModify(foe.stored().status, stats);
|
|
2552
2758
|
}
|
|
2553
2759
|
};
|
|
2554
2760
|
|
|
@@ -2558,6 +2764,15 @@ fn unmodifiedStats(battle: anytype, side: *Side) *Stats(u16) {
|
|
|
2558
2764
|
return &battle.side(id.player).pokemon[id.id - 1].stats;
|
|
2559
2765
|
}
|
|
2560
2766
|
|
|
2767
|
+
fn recomputeStats(battle: anytype, side: *Side) void {
|
|
2768
|
+
const stats = unmodifiedStats(battle, side);
|
|
2769
|
+
inline for (STATS) |s| {
|
|
2770
|
+
const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(side.active.boosts, s)) + 6))];
|
|
2771
|
+
const val = @field(stats, s) * mod[0] / mod[1];
|
|
2772
|
+
@field(side.active.stats, s) = @max(1, @min(MAX_STAT_VALUE, val));
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2561
2776
|
fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
2562
2777
|
if (Status.is(status, .PAR)) {
|
|
2563
2778
|
stats.spe = @max(stats.spe / 4, 1);
|
|
@@ -2566,6 +2781,52 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
2566
2781
|
}
|
|
2567
2782
|
}
|
|
2568
2783
|
|
|
2784
|
+
fn boostStat(
|
|
2785
|
+
battle: anytype,
|
|
2786
|
+
side: *Side,
|
|
2787
|
+
stats: *Stats(u16),
|
|
2788
|
+
boosts: *Boosts,
|
|
2789
|
+
comptime s: []const u8,
|
|
2790
|
+
n: u2,
|
|
2791
|
+
) !void {
|
|
2792
|
+
@field(boosts, s) = @intCast(@min(6, @as(i8, @field(boosts, s)) + n));
|
|
2793
|
+
const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
|
|
2794
|
+
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2795
|
+
@field(stats, s) = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2796
|
+
if (comptime std.mem.eql(u8, s, "atk")) {
|
|
2797
|
+
if (Status.is(side.stored().status, .BRN)) {
|
|
2798
|
+
@field(stats, s) = @max(@field(stats, s) / 2, 1);
|
|
2799
|
+
}
|
|
2800
|
+
} else if (comptime std.mem.eql(u8, s, "spe")) {
|
|
2801
|
+
if (Status.is(side.stored().status, .PAR)) {
|
|
2802
|
+
@field(stats, s) = @max(@field(stats, s) / 4, 1);
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
fn unboostStat(
|
|
2808
|
+
battle: anytype,
|
|
2809
|
+
side: *Side,
|
|
2810
|
+
stats: *Stats(u16),
|
|
2811
|
+
boosts: *Boosts,
|
|
2812
|
+
comptime s: []const u8,
|
|
2813
|
+
n: u2,
|
|
2814
|
+
) !void {
|
|
2815
|
+
@field(boosts, s) = @intCast(@max(-6, @as(i8, @field(boosts, s)) - n));
|
|
2816
|
+
const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
|
|
2817
|
+
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2818
|
+
@field(stats, s) = @max(1, stat * mod[0] / mod[1]);
|
|
2819
|
+
if (comptime std.mem.eql(u8, s, "atk")) {
|
|
2820
|
+
if (Status.is(side.stored().status, .BRN)) {
|
|
2821
|
+
@field(stats, s) = @max(@field(stats, s) / 2, 1);
|
|
2822
|
+
}
|
|
2823
|
+
} else if (comptime std.mem.eql(u8, s, "spe")) {
|
|
2824
|
+
if (Status.is(side.stored().status, .PAR)) {
|
|
2825
|
+
@field(stats, s) = @max(@field(stats, s) / 4, 1);
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2569
2830
|
fn isForced(active: anytype) bool {
|
|
2570
2831
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
2571
2832
|
active.volatiles.Thrashing or active.volatiles.Charging or
|
|
@@ -2578,9 +2839,15 @@ fn convertible(active: ActivePokemon, m: Move) bool {
|
|
|
2578
2839
|
return t != .@"???" and !active.types.includes(t);
|
|
2579
2840
|
}
|
|
2580
2841
|
|
|
2842
|
+
// NOTE: llvm/llvm-project#58557
|
|
2843
|
+
fn smul(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
|
|
2844
|
+
if (!comptime builtin.target.isWasm()) return a *| b;
|
|
2845
|
+
return @max(a *% b, std.math.maxInt(@TypeOf(a)));
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2581
2848
|
pub const Rolls = struct {
|
|
2582
2849
|
fn speedTie(battle: anytype, options: anytype) !bool {
|
|
2583
|
-
const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
|
|
2850
|
+
const p1 = if (options.calc.overridden(.P1, 0, .speed_tie)) |player|
|
|
2584
2851
|
player == .P1
|
|
2585
2852
|
else if (showdown)
|
|
2586
2853
|
battle.rng.range(u8, 0, 2) == 0
|
|
@@ -2592,7 +2859,7 @@ pub const Rolls = struct {
|
|
|
2592
2859
|
}
|
|
2593
2860
|
|
|
2594
2861
|
fn quickClaw(battle: anytype, player: Player, options: anytype) !bool {
|
|
2595
|
-
const qkc = if (options.calc.overridden(player, .quick_claw)) |val|
|
|
2862
|
+
const qkc = if (options.calc.overridden(player, 0, .quick_claw)) |val|
|
|
2596
2863
|
val == .true
|
|
2597
2864
|
else if (showdown)
|
|
2598
2865
|
battle.rng.chance(u8, 60, 256)
|
|
@@ -2604,7 +2871,7 @@ pub const Rolls = struct {
|
|
|
2604
2871
|
}
|
|
2605
2872
|
|
|
2606
2873
|
fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) !bool {
|
|
2607
|
-
const ok = if (options.calc.overridden(player, .hit)) |val|
|
|
2874
|
+
const ok = if (options.calc.overridden(player, 0, .hit)) |val|
|
|
2608
2875
|
val == .true
|
|
2609
2876
|
else if (showdown)
|
|
2610
2877
|
battle.rng.chance(u8, accuracy, 256)
|
|
@@ -2615,20 +2882,22 @@ pub const Rolls = struct {
|
|
|
2615
2882
|
return ok;
|
|
2616
2883
|
}
|
|
2617
2884
|
|
|
2618
|
-
fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
|
|
2619
|
-
|
|
2885
|
+
fn criticalHit(battle: anytype, player: Player, i: u3, rate: u8, options: anytype) !bool {
|
|
2886
|
+
_ = .{ player, options };
|
|
2887
|
+
const crit = if (options.calc.overridden(player, i, .critical_hits)) |val|
|
|
2620
2888
|
val == .true
|
|
2621
2889
|
else if (showdown)
|
|
2622
2890
|
battle.rng.chance(u8, rate, 256)
|
|
2623
2891
|
else
|
|
2624
2892
|
battle.rng.next() < rate;
|
|
2625
2893
|
|
|
2626
|
-
try options.chance.criticalHit(player, crit, rate);
|
|
2894
|
+
try options.chance.criticalHit(player, i, crit, rate);
|
|
2627
2895
|
return crit;
|
|
2628
2896
|
}
|
|
2629
2897
|
|
|
2630
|
-
fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2631
|
-
|
|
2898
|
+
fn damage(battle: anytype, player: Player, i: u3, options: anytype) !u8 {
|
|
2899
|
+
_ = .{ player, options };
|
|
2900
|
+
const roll = if (options.calc.overridden(player, i, .damages)) |val|
|
|
2632
2901
|
val
|
|
2633
2902
|
else roll: {
|
|
2634
2903
|
if (showdown) break :roll battle.rng.range(u8, 217, 256);
|
|
@@ -2639,12 +2908,12 @@ pub const Rolls = struct {
|
|
|
2639
2908
|
};
|
|
2640
2909
|
|
|
2641
2910
|
assert(roll >= 217 and roll <= 255);
|
|
2642
|
-
try options.chance.damage(player, roll);
|
|
2911
|
+
try options.chance.damage(player, i, roll);
|
|
2643
2912
|
return roll;
|
|
2644
2913
|
}
|
|
2645
2914
|
|
|
2646
2915
|
fn confused(battle: anytype, player: Player, options: anytype) !bool {
|
|
2647
|
-
const cfz = if (options.calc.overridden(player, .confused)) |val|
|
|
2916
|
+
const cfz = if (options.calc.overridden(player, 0, .confused)) |val|
|
|
2648
2917
|
val == .true
|
|
2649
2918
|
else if (showdown)
|
|
2650
2919
|
!battle.rng.chance(u8, 128, 256)
|
|
@@ -2656,7 +2925,7 @@ pub const Rolls = struct {
|
|
|
2656
2925
|
}
|
|
2657
2926
|
|
|
2658
2927
|
fn attract(battle: anytype, player: Player, options: anytype) !bool {
|
|
2659
|
-
const cant = if (options.calc.overridden(player, .attract)) |val|
|
|
2928
|
+
const cant = if (options.calc.overridden(player, 0, .attract)) |val|
|
|
2660
2929
|
val == .true
|
|
2661
2930
|
else if (showdown)
|
|
2662
2931
|
battle.rng.chance(u8, 1, 2)
|
|
@@ -2668,7 +2937,7 @@ pub const Rolls = struct {
|
|
|
2668
2937
|
}
|
|
2669
2938
|
|
|
2670
2939
|
fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
|
|
2671
|
-
const par = if (options.calc.overridden(player, .paralyzed)) |val|
|
|
2940
|
+
const par = if (options.calc.overridden(player, 0, .paralyzed)) |val|
|
|
2672
2941
|
val == .true
|
|
2673
2942
|
else if (showdown)
|
|
2674
2943
|
battle.rng.chance(u8, 63, 256)
|
|
@@ -2680,7 +2949,7 @@ pub const Rolls = struct {
|
|
|
2680
2949
|
}
|
|
2681
2950
|
|
|
2682
2951
|
fn defrost(battle: anytype, player: Player, options: anytype) !bool {
|
|
2683
|
-
const thaw = if (options.calc.overridden(player, .defrost)) |val|
|
|
2952
|
+
const thaw = if (options.calc.overridden(player, 0, .defrost)) |val|
|
|
2684
2953
|
val == .true
|
|
2685
2954
|
else if (showdown)
|
|
2686
2955
|
battle.rng.chance(u8, 25, 256)
|
|
@@ -2693,7 +2962,7 @@ pub const Rolls = struct {
|
|
|
2693
2962
|
|
|
2694
2963
|
fn secondaryChance(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
|
|
2695
2964
|
// GLITCH: 100% secondary chance will still fail 1/256 of the time
|
|
2696
|
-
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2965
|
+
const proc = if (options.calc.overridden(player, 0, .secondary_chance)) |val|
|
|
2697
2966
|
val == .true
|
|
2698
2967
|
else if (showdown)
|
|
2699
2968
|
battle.rng.chance(u8, rate, 256)
|
|
@@ -2705,7 +2974,7 @@ pub const Rolls = struct {
|
|
|
2705
2974
|
}
|
|
2706
2975
|
|
|
2707
2976
|
fn item(battle: anytype, player: Player, options: anytype) !bool {
|
|
2708
|
-
const proc = if (options.calc.overridden(player, .item)) |val|
|
|
2977
|
+
const proc = if (options.calc.overridden(player, 0, .item)) |val|
|
|
2709
2978
|
val == .true
|
|
2710
2979
|
else if (showdown)
|
|
2711
2980
|
battle.rng.chance(u8, 30, 256)
|
|
@@ -2717,7 +2986,7 @@ pub const Rolls = struct {
|
|
|
2717
2986
|
}
|
|
2718
2987
|
|
|
2719
2988
|
fn triAttack(battle: anytype, player: Player, options: anytype) !TriAttack {
|
|
2720
|
-
const status: TriAttack = if (options.calc.overridden(player, .tri_attack)) |val|
|
|
2989
|
+
const status: TriAttack = if (options.calc.overridden(player, 0, .tri_attack)) |val|
|
|
2721
2990
|
@enumFromInt(@intFromEnum(val) - 1)
|
|
2722
2991
|
else if (showdown)
|
|
2723
2992
|
@enumFromInt(battle.rng.range(u2, 0, 2))
|
|
@@ -2733,7 +3002,7 @@ pub const Rolls = struct {
|
|
|
2733
3002
|
}
|
|
2734
3003
|
|
|
2735
3004
|
fn present(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2736
|
-
const power = if (options.calc.overridden(player, .present)) |val|
|
|
3005
|
+
const power = if (options.calc.overridden(player, 0, .present)) |val|
|
|
2737
3006
|
(val - 1) * 40
|
|
2738
3007
|
else if (showdown) power: {
|
|
2739
3008
|
const r = battle.rng.range(u8, 0, 10);
|
|
@@ -2755,7 +3024,7 @@ pub const Rolls = struct {
|
|
|
2755
3024
|
}
|
|
2756
3025
|
|
|
2757
3026
|
fn magnitude(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2758
|
-
const num = if (options.calc.overridden(player, .magnitude)) |val|
|
|
3027
|
+
const num: u8 = if (options.calc.overridden(player, 0, .magnitude)) |val|
|
|
2759
3028
|
val + 3
|
|
2760
3029
|
else if (showdown) num: {
|
|
2761
3030
|
const r = battle.rng.range(u8, 0, 100);
|
|
@@ -2783,7 +3052,7 @@ pub const Rolls = struct {
|
|
|
2783
3052
|
}
|
|
2784
3053
|
|
|
2785
3054
|
fn tripleKick(battle: anytype, player: Player, options: anytype) !u2 {
|
|
2786
|
-
const hits = if (options.calc.overridden(player, .triple_kick)) |val|
|
|
3055
|
+
const hits = if (options.calc.overridden(player, 0, .triple_kick)) |val|
|
|
2787
3056
|
val
|
|
2788
3057
|
else if (showdown)
|
|
2789
3058
|
battle.rng.range(u2, 1, 4)
|
|
@@ -2799,13 +3068,12 @@ pub const Rolls = struct {
|
|
|
2799
3068
|
return hits;
|
|
2800
3069
|
}
|
|
2801
3070
|
|
|
2802
|
-
fn spite(battle: anytype, player: Player, options: anytype) !
|
|
2803
|
-
|
|
3071
|
+
fn spite(battle: anytype, player: Player, options: anytype) !u3 {
|
|
3072
|
+
assert(!showdown);
|
|
3073
|
+
const pp: u3 = if (options.calc.overridden(player, 0, .spite)) |val|
|
|
2804
3074
|
val + 1
|
|
2805
|
-
else if (showdown)
|
|
2806
|
-
battle.rng.range(u8, 2, 6)
|
|
2807
3075
|
else
|
|
2808
|
-
(battle.rng.next() & 3) + 2;
|
|
3076
|
+
@intCast((battle.rng.next() & 3) + 2);
|
|
2809
3077
|
|
|
2810
3078
|
assert(pp >= 2 and pp <= 5);
|
|
2811
3079
|
try options.chance.spite(player, pp);
|
|
@@ -2819,7 +3087,7 @@ pub const Rolls = struct {
|
|
|
2819
3087
|
var i: u8 = 0;
|
|
2820
3088
|
const neutral = @intFromEnum(Effectiveness.Neutral);
|
|
2821
3089
|
assert(mtype != .@"???");
|
|
2822
|
-
const ty: Type = if (options.calc.overridden(player, .conversion_2)) |val|
|
|
3090
|
+
const ty: Type = if (options.calc.overridden(player, 0, .conversion_2)) |val|
|
|
2823
3091
|
@enumFromInt(@intFromEnum(val) - 1)
|
|
2824
3092
|
else if (showdown) ty: {
|
|
2825
3093
|
for (Type.SHOWDOWN) |t| {
|
|
@@ -2853,7 +3121,7 @@ pub const Rolls = struct {
|
|
|
2853
3121
|
assert(count <= 8);
|
|
2854
3122
|
const num = if (count == 8) 0 else @as(u8, 255) >> @intCast(count);
|
|
2855
3123
|
|
|
2856
|
-
const ok = if (options.calc.overridden(player, .protect)) |val|
|
|
3124
|
+
const ok = if (options.calc.overridden(player, 0, .protect)) |val|
|
|
2857
3125
|
val == .true
|
|
2858
3126
|
else if (showdown)
|
|
2859
3127
|
battle.rng.chance(u8, num, 255)
|
|
@@ -2875,7 +3143,7 @@ pub const Rolls = struct {
|
|
|
2875
3143
|
const moves = active.moves;
|
|
2876
3144
|
|
|
2877
3145
|
// TODO: consider throwing error instead of rerolling?
|
|
2878
|
-
const overridden = if (options.calc.overridden(player, .move_slot)) |val|
|
|
3146
|
+
const overridden = if (options.calc.overridden(player, 0, .move_slot)) |val|
|
|
2879
3147
|
if (convertible(active, moves[val - 1].id)) val else null
|
|
2880
3148
|
else
|
|
2881
3149
|
null;
|
|
@@ -2912,7 +3180,7 @@ pub const Rolls = struct {
|
|
|
2912
3180
|
const moves = active.moves;
|
|
2913
3181
|
|
|
2914
3182
|
// TODO: consider throwing error instead of rerolling?
|
|
2915
|
-
const overridden = if (options.calc.overridden(player, .move_slot)) |val|
|
|
3183
|
+
const overridden = if (options.calc.overridden(player, 0, .move_slot)) |val|
|
|
2916
3184
|
if (val != mslot and Move.get(moves[val - 1].id).extra.sleep_talk) val else null
|
|
2917
3185
|
else
|
|
2918
3186
|
null;
|
|
@@ -2956,7 +3224,7 @@ pub const Rolls = struct {
|
|
|
2956
3224
|
assert(n > 0 and n <= 6);
|
|
2957
3225
|
|
|
2958
3226
|
// TODO: consider throwing error instead of rerolling?
|
|
2959
|
-
const overridden = if (options.calc.overridden(player, .force_switch)) |val| val: {
|
|
3227
|
+
const overridden = if (options.calc.overridden(player, 0, .force_switch)) |val| val: {
|
|
2960
3228
|
for (slots) |slot| if (slot == val) break :val if (foe.get(slot).hp > 0) val else null;
|
|
2961
3229
|
break :val null;
|
|
2962
3230
|
} else null;
|
|
@@ -2982,7 +3250,7 @@ pub const Rolls = struct {
|
|
|
2982
3250
|
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
2983
3251
|
|
|
2984
3252
|
fn multiHit(battle: anytype, player: Player, options: anytype) !u3 {
|
|
2985
|
-
const n: u3 = if (options.calc.overridden(player, .multi_hit)) |val|
|
|
3253
|
+
const n: u3 = if (options.calc.overridden(player, 0, .multi_hit)) |val|
|
|
2986
3254
|
@intCast(val)
|
|
2987
3255
|
else if (showdown)
|
|
2988
3256
|
DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
|
|
@@ -2997,7 +3265,7 @@ pub const Rolls = struct {
|
|
|
2997
3265
|
}
|
|
2998
3266
|
|
|
2999
3267
|
fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
|
|
3000
|
-
const power = if (options.calc.overridden(player, .psywave)) |val|
|
|
3268
|
+
const power = if (options.calc.overridden(player, 0, .psywave)) |val|
|
|
3001
3269
|
val
|
|
3002
3270
|
else if (showdown)
|
|
3003
3271
|
battle.rng.range(u8, 1, max)
|
|
@@ -3023,14 +3291,14 @@ pub const Rolls = struct {
|
|
|
3023
3291
|
}
|
|
3024
3292
|
|
|
3025
3293
|
// TODO: consider throwing error instead of rerolling?
|
|
3026
|
-
const overridden = if (options.calc.overridden(player, .metronome)) |val| val: {
|
|
3294
|
+
const overridden = if (options.calc.overridden(player, 0, .metronome)) |val| val: {
|
|
3027
3295
|
for (moves) |m| if (m.id == val) break :val null;
|
|
3028
3296
|
break :val val;
|
|
3029
3297
|
} else null;
|
|
3030
3298
|
|
|
3031
3299
|
const move: Move = overridden orelse move: {
|
|
3032
3300
|
if (showdown) {
|
|
3033
|
-
var r = battle.rng.range(u8, 0, @intCast(Move.METRONOME.len - n));
|
|
3301
|
+
var r = battle.rng.range(u8, 0, @as(u8, @intCast(Move.METRONOME.len - n)));
|
|
3034
3302
|
for (moves) |m| {
|
|
3035
3303
|
if (m.id == .None) break;
|
|
3036
3304
|
if (@intFromEnum(m.id) > @intFromEnum(Move.METRONOME[r])) break;
|
|
@@ -3055,7 +3323,7 @@ pub const Rolls = struct {
|
|
|
3055
3323
|
}
|
|
3056
3324
|
|
|
3057
3325
|
fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
3058
|
-
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
3326
|
+
const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
|
|
3059
3327
|
@intCast(val)
|
|
3060
3328
|
else if (showdown)
|
|
3061
3329
|
battle.rng.range(u3, 1, 7)
|
|
@@ -3067,12 +3335,12 @@ pub const Rolls = struct {
|
|
|
3067
3335
|
};
|
|
3068
3336
|
|
|
3069
3337
|
assert(duration >= 1 and duration <= 6);
|
|
3070
|
-
options.chance.duration(
|
|
3338
|
+
options.chance.duration(player, duration);
|
|
3071
3339
|
return duration + 1;
|
|
3072
3340
|
}
|
|
3073
3341
|
|
|
3074
3342
|
fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
|
|
3075
|
-
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
3343
|
+
const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
|
|
3076
3344
|
@intCast(val)
|
|
3077
3345
|
else if (showdown)
|
|
3078
3346
|
// Pokémon Showdown incorrectly uses the same duration for self-confusion
|
|
@@ -3081,16 +3349,16 @@ pub const Rolls = struct {
|
|
|
3081
3349
|
@intCast((battle.rng.next() & if (self) 1 else 3) + 2);
|
|
3082
3350
|
|
|
3083
3351
|
assert(duration >= 2 and duration <= if (!showdown and self) 3 else 5);
|
|
3084
|
-
options.chance.duration(
|
|
3352
|
+
options.chance.duration(player, duration);
|
|
3085
3353
|
return duration;
|
|
3086
3354
|
}
|
|
3087
3355
|
|
|
3088
3356
|
fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
|
|
3089
|
-
const duration:
|
|
3357
|
+
const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
|
|
3090
3358
|
@intCast(val)
|
|
3091
3359
|
else if (showdown)
|
|
3092
3360
|
// Pokémon Showdown incorrectly inherits Generation III's Disable duration
|
|
3093
|
-
battle.rng.range(
|
|
3361
|
+
battle.rng.range(u3, 2, 6)
|
|
3094
3362
|
else duration: {
|
|
3095
3363
|
while (true) {
|
|
3096
3364
|
const r = battle.rng.next() & 7;
|
|
@@ -3099,12 +3367,12 @@ pub const Rolls = struct {
|
|
|
3099
3367
|
};
|
|
3100
3368
|
|
|
3101
3369
|
assert(duration >= 2 and duration <= (if (showdown) 5 else 7));
|
|
3102
|
-
options.chance.duration(
|
|
3370
|
+
options.chance.duration(player, duration);
|
|
3103
3371
|
return duration;
|
|
3104
3372
|
}
|
|
3105
3373
|
|
|
3106
3374
|
fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
3107
|
-
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
3375
|
+
const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
|
|
3108
3376
|
@intCast(val)
|
|
3109
3377
|
else if (showdown)
|
|
3110
3378
|
battle.rng.range(u3, 2, 4)
|
|
@@ -3112,12 +3380,12 @@ pub const Rolls = struct {
|
|
|
3112
3380
|
@intCast((battle.rng.next() & 1) + 2); // BUG: thrash is + 1 not +2
|
|
3113
3381
|
|
|
3114
3382
|
assert(duration >= 2 and duration <= 3);
|
|
3115
|
-
options.chance.duration(
|
|
3383
|
+
options.chance.duration(player, duration);
|
|
3116
3384
|
return duration;
|
|
3117
3385
|
}
|
|
3118
3386
|
|
|
3119
3387
|
fn bindingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
3120
|
-
const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
|
|
3388
|
+
const duration: u3 = (if (options.calc.overridden(player, 0, .duration)) |val|
|
|
3121
3389
|
@intCast(val)
|
|
3122
3390
|
else if (showdown)
|
|
3123
3391
|
// TODO: PS does range(3, 6) here which after subtracting 1 is actually 2-4 not 2-5!
|
|
@@ -3126,13 +3394,13 @@ pub const Rolls = struct {
|
|
|
3126
3394
|
@intCast(battle.rng.next() & 3 + 2));
|
|
3127
3395
|
|
|
3128
3396
|
assert(duration >= 2 and duration <= 5);
|
|
3129
|
-
options.chance.duration(
|
|
3397
|
+
options.chance.duration(player, duration);
|
|
3130
3398
|
return duration + 1;
|
|
3131
3399
|
}
|
|
3132
3400
|
|
|
3133
3401
|
// TODO: consider sharing implementation with bindingDuration
|
|
3134
3402
|
fn encoreDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
3135
|
-
const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
|
|
3403
|
+
const duration: u3 = (if (options.calc.overridden(player, 0, .duration)) |val|
|
|
3136
3404
|
@intCast(val)
|
|
3137
3405
|
else if (showdown)
|
|
3138
3406
|
battle.rng.range(u3, 2, 6)
|
|
@@ -3140,7 +3408,7 @@ pub const Rolls = struct {
|
|
|
3140
3408
|
@intCast(battle.rng.next() & 3 + 2));
|
|
3141
3409
|
|
|
3142
3410
|
assert(duration >= 2 and duration <= 5);
|
|
3143
|
-
options.chance.duration(
|
|
3411
|
+
options.chance.duration(player, duration);
|
|
3144
3412
|
return duration + 1;
|
|
3145
3413
|
}
|
|
3146
3414
|
};
|
|
@@ -3253,7 +3521,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3253
3521
|
.Move => {
|
|
3254
3522
|
const side = battle.side(player);
|
|
3255
3523
|
|
|
3256
|
-
|
|
3524
|
+
const active = &side.active;
|
|
3257
3525
|
const stored = side.stored();
|
|
3258
3526
|
|
|
3259
3527
|
// While players are not given any input options on the cartridge in these cases,
|
|
@@ -3273,7 +3541,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3273
3541
|
}
|
|
3274
3542
|
|
|
3275
3543
|
var slot: u4 = 2;
|
|
3276
|
-
while (!active.volatiles.
|
|
3544
|
+
while (!active.volatiles.Trapped and slot <= 6) : (slot += 1) {
|
|
3277
3545
|
const id = side.order[slot - 1];
|
|
3278
3546
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
3279
3547
|
out[n] = .{ .type = .Switch, .data = slot };
|