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