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