@pkmn/engine 0.1.0-dev.bcb16a6c → 0.1.0-dev.be951af1
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/build/pkg/data/layout.json +2 -2
- package/build/pkg/gen1.js +2 -2
- package/build/pkg/gen1.js.map +1 -1
- package/package.json +17 -14
- package/src/lib/common/optional.zig +4 -6
- package/src/lib/common/protocol.zig +46 -19
- package/src/lib/gen1/README.md +28 -28
- package/src/lib/gen1/chance.zig +43 -41
- package/src/lib/gen1/data/moves.zig +14 -0
- package/src/lib/gen1/data.zig +6 -6
- package/src/lib/gen1/helpers.zig +1 -16
- package/src/lib/gen1/mechanics.zig +61 -94
- package/src/lib/gen2/README.md +1 -1
- package/src/lib/gen2/calc.zig +143 -0
- package/src/lib/gen2/chance.zig +1314 -0
- package/src/lib/gen2/data/moves.zig +27 -10
- package/src/lib/gen2/data/types.zig +3 -2
- package/src/lib/gen2/data.zig +11 -5
- package/src/lib/gen2/generated.zig +1239 -0
- package/src/lib/gen2/mechanics.zig +1712 -329
|
@@ -6,22 +6,26 @@ const common = @import("../common/data.zig");
|
|
|
6
6
|
const DEBUG = @import("../common/debug.zig").print;
|
|
7
7
|
const protocol = @import("../common/protocol.zig");
|
|
8
8
|
const rng = @import("../common/rng.zig");
|
|
9
|
+
const util = @import("../common/util.zig");
|
|
9
10
|
|
|
10
11
|
const data = @import("data.zig");
|
|
12
|
+
const generated = @import("generated.zig");
|
|
11
13
|
|
|
12
14
|
const assert = std.debug.assert;
|
|
13
15
|
|
|
14
16
|
const expectEqual = std.testing.expectEqual;
|
|
15
17
|
const expectEqualSlices = std.testing.expectEqualSlices;
|
|
18
|
+
const expectEqualStrings = std.testing.expectEqualStrings;
|
|
19
|
+
|
|
20
|
+
const showdown = pkmn.options.showdown;
|
|
16
21
|
|
|
17
22
|
const Choice = common.Choice;
|
|
18
23
|
const ID = common.ID;
|
|
19
24
|
const Player = common.Player;
|
|
20
25
|
const Result = common.Result;
|
|
21
26
|
|
|
22
|
-
const showdown = pkmn.options.showdown;
|
|
23
|
-
|
|
24
27
|
const Activate = protocol.Activate;
|
|
28
|
+
const Boost = protocol.Boost;
|
|
25
29
|
const Cant = protocol.Cant;
|
|
26
30
|
const Damage = protocol.Damage;
|
|
27
31
|
const End = protocol.End;
|
|
@@ -30,6 +34,8 @@ const Start = protocol.Start;
|
|
|
30
34
|
|
|
31
35
|
const Gen12 = rng.Gen12;
|
|
32
36
|
|
|
37
|
+
const PointerType = util.PointerType;
|
|
38
|
+
|
|
33
39
|
const ActivePokemon = data.ActivePokemon;
|
|
34
40
|
const Effectiveness = data.Effectiveness;
|
|
35
41
|
const Item = data.Item;
|
|
@@ -89,7 +95,25 @@ const CRITICAL_HITS = &[_]u8{
|
|
|
89
95
|
|
|
90
96
|
const MAX_STAT_VALUE = 999;
|
|
91
97
|
|
|
92
|
-
const
|
|
98
|
+
pub const State = struct {
|
|
99
|
+
damage: u16 = 0, // wCurDamage
|
|
100
|
+
effectiveness: u16 = Effectiveness.neutral, // wTypeModifier
|
|
101
|
+
bp: u8 = 0, // wPlayerMoveStructPower
|
|
102
|
+
move: Move = .None, // wCurPlayerMove
|
|
103
|
+
mslot: u4 = 0, // wCurMoveNum
|
|
104
|
+
first: bool = false, // wEnemyGoesFirst
|
|
105
|
+
miss: bool = false, // wAttackMissed
|
|
106
|
+
crit: bool = false, // wCriticalHit (NB: used on device for OHKO as well)
|
|
107
|
+
proc: bool = true, // wEffectFailed
|
|
108
|
+
|
|
109
|
+
comptime {
|
|
110
|
+
assert(@sizeOf(State) == 12);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
inline fn immune(self: *State) bool {
|
|
114
|
+
return self.effectiveness == 0;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
93
117
|
|
|
94
118
|
pub fn update(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Result {
|
|
95
119
|
assert(c1.type != .Pass or c2.type != .Pass or battle.turn == 0);
|
|
@@ -169,14 +193,14 @@ fn selectMove(battle: anytype, player: Player, choice: Choice) void {
|
|
|
169
193
|
if (choice.data == 0) {
|
|
170
194
|
const struggle = ok: {
|
|
171
195
|
for (side.active.moves, 0..) |move, i| {
|
|
172
|
-
if (move.pp > 0 and volatiles.
|
|
196
|
+
if (move.pp > 0 and volatiles.disable.move != i + 1) break :ok false;
|
|
173
197
|
}
|
|
174
198
|
break :ok true;
|
|
175
199
|
};
|
|
176
200
|
assert(struggle);
|
|
177
201
|
// TODO wCurPlayerMove = .Struggle;
|
|
178
202
|
} else {
|
|
179
|
-
assert(showdown or side.active.volatiles.
|
|
203
|
+
assert(showdown or side.active.volatiles.disable.move != choice.data);
|
|
180
204
|
const move = side.active.move(choice.data);
|
|
181
205
|
// You cannot *select* a move with 0 PP
|
|
182
206
|
assert(move.pp != 0);
|
|
@@ -316,22 +340,42 @@ fn doTurn(
|
|
|
316
340
|
const volatiles = &battle.side(player).active.volatiles;
|
|
317
341
|
const foe_volatiles = &battle.foe(player).active.volatiles;
|
|
318
342
|
|
|
343
|
+
// Technically this happens at the very beginning of update but its unused until now
|
|
344
|
+
// TODO: move into state and pass into betweenTurns
|
|
345
|
+
volatiles.frozen = false;
|
|
346
|
+
foe_volatiles.frozen = false;
|
|
347
|
+
|
|
319
348
|
volatiles.DestinyBond = false;
|
|
320
|
-
if (try executeMove(battle, player, player_choice, options)) |r| return r;
|
|
349
|
+
if (try executeMove(battle, player, player_choice, true, options)) |r| return r;
|
|
321
350
|
foe_volatiles.Protect = false;
|
|
322
351
|
foe_volatiles.Endure = false;
|
|
323
352
|
foe_volatiles.DestinyBond = false;
|
|
353
|
+
if (try checkFaint(battle, foe_player, options)) |r| return r;
|
|
354
|
+
if (try handleResidual(battle, player, options)) {
|
|
355
|
+
if (try checkFaint(battle, player, options)) |r| return r;
|
|
356
|
+
}
|
|
324
357
|
|
|
325
358
|
// foe_volatiles.DestinyBond = false;
|
|
326
|
-
if (try executeMove(battle, foe_player, foe_choice, options)) |r| return r;
|
|
359
|
+
if (try executeMove(battle, foe_player, foe_choice, false, options)) |r| return r;
|
|
327
360
|
volatiles.Protect = false;
|
|
328
361
|
volatiles.Endure = false;
|
|
329
362
|
volatiles.DestinyBond = false;
|
|
363
|
+
if (try checkFaint(battle, player, options)) |r| return r;
|
|
364
|
+
if (try handleResidual(battle, foe_player, options)) {
|
|
365
|
+
if (try checkFaint(battle, foe_player, options)) |r| return r;
|
|
366
|
+
}
|
|
330
367
|
|
|
331
|
-
|
|
368
|
+
// FIXME
|
|
369
|
+
return betweenTurns(battle, 0, options);
|
|
332
370
|
}
|
|
333
371
|
|
|
334
|
-
fn executeMove(
|
|
372
|
+
fn executeMove(
|
|
373
|
+
battle: anytype,
|
|
374
|
+
player: Player,
|
|
375
|
+
choice: Choice,
|
|
376
|
+
first: bool,
|
|
377
|
+
options: anytype,
|
|
378
|
+
) !?Result {
|
|
335
379
|
if (choice.type == .Switch) {
|
|
336
380
|
try switchIn(battle, player, choice.data, false, options);
|
|
337
381
|
return null;
|
|
@@ -339,15 +383,17 @@ fn executeMove(battle: anytype, player: Player, choice: Choice, options: anytype
|
|
|
339
383
|
|
|
340
384
|
assert(choice.type == .Move);
|
|
341
385
|
var mslot: u4 = @intCast(choice.data);
|
|
386
|
+
var move = Move.Pound; // TODO
|
|
342
387
|
|
|
343
|
-
|
|
388
|
+
if (!try beforeMove(battle, player, move, options)) return null;
|
|
344
389
|
|
|
345
|
-
//
|
|
346
|
-
|
|
347
|
-
|
|
390
|
+
// FIXME: need damage from doTurn
|
|
391
|
+
var state: State = .{ .move = move, .mslot = mslot, .first = first };
|
|
392
|
+
_ = try TODO(battle, player, &state, options);
|
|
393
|
+
return generated.doMove(battle, player, &state, options);
|
|
348
394
|
}
|
|
349
395
|
|
|
350
|
-
fn beforeMove(battle: anytype, player: Player, options: anytype) !bool {
|
|
396
|
+
fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bool {
|
|
351
397
|
var log = options.log;
|
|
352
398
|
var side = battle.side(player);
|
|
353
399
|
// const foe = battle.foe(player);
|
|
@@ -356,8 +402,6 @@ fn beforeMove(battle: anytype, player: Player, options: anytype) !bool {
|
|
|
356
402
|
const ident = battle.active(player);
|
|
357
403
|
var volatiles = &active.volatiles;
|
|
358
404
|
|
|
359
|
-
const move = Move.Pound; // TODO
|
|
360
|
-
|
|
361
405
|
if (volatiles.Recharging) {
|
|
362
406
|
volatiles.Recharging = false;
|
|
363
407
|
cantMove(volatiles);
|
|
@@ -408,16 +452,16 @@ fn beforeMove(battle: anytype, player: Player, options: anytype) !bool {
|
|
|
408
452
|
return false;
|
|
409
453
|
}
|
|
410
454
|
|
|
411
|
-
if (volatiles.
|
|
455
|
+
if (volatiles.disable.duration > 0) {
|
|
412
456
|
// if (options.calc.modify(player, .disable)) |extend| {
|
|
413
|
-
// if (!extend) volatiles.
|
|
457
|
+
// if (!extend) volatiles.disable.duration = 0;
|
|
414
458
|
// } else {
|
|
415
|
-
volatiles.
|
|
459
|
+
volatiles.disable.duration -= 1;
|
|
416
460
|
// }
|
|
417
|
-
try options.chance.disable(player, volatiles.
|
|
461
|
+
try options.chance.disable(player, volatiles.disable.duration);
|
|
418
462
|
|
|
419
|
-
if (volatiles.
|
|
420
|
-
volatiles.
|
|
463
|
+
if (volatiles.disable.duration == 0) {
|
|
464
|
+
volatiles.disable.move = 0;
|
|
421
465
|
try log.end(ident, .Disable);
|
|
422
466
|
}
|
|
423
467
|
}
|
|
@@ -451,7 +495,7 @@ fn beforeMove(battle: anytype, player: Player, options: anytype) !bool {
|
|
|
451
495
|
}
|
|
452
496
|
|
|
453
497
|
if (volatiles.Attract) {
|
|
454
|
-
try log.
|
|
498
|
+
try log.activate(ident, .Attract);
|
|
455
499
|
|
|
456
500
|
if (try Rolls.attract(battle, player, options)) {
|
|
457
501
|
cantMove(volatiles);
|
|
@@ -460,9 +504,9 @@ fn beforeMove(battle: anytype, player: Player, options: anytype) !bool {
|
|
|
460
504
|
}
|
|
461
505
|
}
|
|
462
506
|
|
|
463
|
-
if (side.active.volatiles.
|
|
464
|
-
// A Pokémon that transforms after being
|
|
465
|
-
const m = side.active.moves[side.active.volatiles.
|
|
507
|
+
if (side.active.volatiles.disable.move != 0) {
|
|
508
|
+
// A Pokémon that transforms after being disable may end up with less move slots
|
|
509
|
+
const m = side.active.moves[side.active.volatiles.disable.move - 1].id;
|
|
466
510
|
if (m != .None and m == move) {
|
|
467
511
|
side.active.volatiles.Charging = false;
|
|
468
512
|
cantMove(volatiles);
|
|
@@ -480,7 +524,7 @@ fn beforeMove(battle: anytype, player: Player, options: anytype) !bool {
|
|
|
480
524
|
return true;
|
|
481
525
|
}
|
|
482
526
|
|
|
483
|
-
fn cantMove(volatiles: *Volatiles) void {
|
|
527
|
+
fn cantMove(volatiles: *align(8) Volatiles) void {
|
|
484
528
|
volatiles.Bide = false;
|
|
485
529
|
volatiles.Thrashing = false;
|
|
486
530
|
volatiles.Charging = false;
|
|
@@ -491,47 +535,82 @@ fn cantMove(volatiles: *Volatiles) void {
|
|
|
491
535
|
volatiles.fury_cutter = 0;
|
|
492
536
|
}
|
|
493
537
|
|
|
494
|
-
fn canMove(battle: anytype, player: Player, options: anytype) !
|
|
538
|
+
fn canMove(battle: anytype, player: Player, options: anytype) !void {
|
|
495
539
|
_ = battle;
|
|
496
540
|
_ = player;
|
|
497
541
|
_ = options;
|
|
498
542
|
return false;
|
|
499
543
|
}
|
|
500
544
|
|
|
501
|
-
fn decrementPP(side: *Side, mslot: u4)
|
|
545
|
+
pub fn decrementPP(side: *Side, move: Move, mslot: u4) bool {
|
|
502
546
|
var active = &side.active;
|
|
503
547
|
const volatiles = &active.volatiles;
|
|
504
548
|
|
|
505
|
-
|
|
549
|
+
assert(move != .Struggle);
|
|
506
550
|
assert(!volatiles.Charging and !volatiles.BeatUp and !volatiles.Thrashing and !volatiles.Bide);
|
|
507
551
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
if (volatiles.Transform) return;
|
|
552
|
+
var move_slot = active.move(mslot);
|
|
553
|
+
if (move_slot.pp == 0) return false;
|
|
511
554
|
|
|
512
|
-
|
|
555
|
+
move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
|
|
556
|
+
if (volatiles.Transform) return true;
|
|
513
557
|
|
|
514
|
-
|
|
515
|
-
|
|
558
|
+
move_slot = side.stored().move(mslot);
|
|
559
|
+
assert(move_slot.pp > 0);
|
|
560
|
+
|
|
561
|
+
if (move_slot.id == .Mimic and move != .Mimic) return true;
|
|
562
|
+
|
|
563
|
+
move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
|
|
516
564
|
assert(active.move(mslot).pp == side.stored().move(mslot).pp);
|
|
565
|
+
|
|
566
|
+
return true;
|
|
517
567
|
}
|
|
518
568
|
|
|
519
|
-
fn
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
569
|
+
fn TODO(battle: anytype, player: Player, state: *State, options: anytype) !?Result {
|
|
570
|
+
try checkHit(battle, player, state, options);
|
|
571
|
+
try checkCriticalHit(battle, player, state, options);
|
|
572
|
+
try calcDamage(battle, player, state, options);
|
|
573
|
+
try adjustDamage(battle, player, state, options);
|
|
574
|
+
try randomizeDamage(battle, player, state, options);
|
|
575
|
+
try applyDamage(battle, player, state, options);
|
|
576
|
+
try buildRage(battle, player, state, options);
|
|
577
|
+
try rageDamage(battle, player, state, options);
|
|
578
|
+
try kingsRock(battle, player, state, options);
|
|
579
|
+
_ = try destinyBond(battle, player, state, options);
|
|
580
|
+
|
|
581
|
+
try Effects.attract(battle, player, state, options);
|
|
582
|
+
try Effects.boost(battle, player, state, options);
|
|
583
|
+
try Effects.burnChance(battle, player, state, options);
|
|
584
|
+
try Effects.conversion(battle, player, state, options);
|
|
585
|
+
try Effects.conversion2(battle, player, state, options);
|
|
586
|
+
try Effects.defrost(battle, player, state, options);
|
|
587
|
+
try Effects.destinyBond(battle, player, state, options);
|
|
588
|
+
try Effects.disable(battle, player, state, options);
|
|
589
|
+
try Effects.drainHP(battle, player, state, options);
|
|
590
|
+
try Effects.encore(battle, player, state, options);
|
|
591
|
+
try Effects.falseSwipe(battle, player, state, options);
|
|
592
|
+
try Effects.fixedDamage(battle, player, state, options);
|
|
593
|
+
try Effects.focusEnergy(battle, player, state, options);
|
|
594
|
+
try Effects.forceSwitch(battle, player, state, options);
|
|
595
|
+
try Effects.foresight(battle, player, state, options);
|
|
596
|
+
try Effects.freezeChance(battle, player, state, options);
|
|
597
|
+
try Effects.happiness(battle, player, state, options);
|
|
598
|
+
try Effects.paralyzeChance(battle, player, state, options);
|
|
599
|
+
try Effects.poison(battle, player, state, options);
|
|
600
|
+
try Effects.poisonChance(battle, player, state, options);
|
|
601
|
+
try Effects.protect(battle, player, state, options);
|
|
602
|
+
try Effects.rage(battle, player, state, options);
|
|
603
|
+
try Effects.reversal(battle, player, state, options);
|
|
604
|
+
try Effects.sleep(battle, player, state, options);
|
|
605
|
+
try Effects.unboost(battle, player, state, options);
|
|
606
|
+
try Effects.weatherHeal(battle, player, state, options);
|
|
607
|
+
|
|
530
608
|
return null;
|
|
531
609
|
}
|
|
532
610
|
|
|
533
|
-
fn checkCriticalHit(battle: anytype, player: Player,
|
|
534
|
-
|
|
611
|
+
pub fn checkCriticalHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
612
|
+
const move = Move.get(state.move);
|
|
613
|
+
if (move.bp == 0) return;
|
|
535
614
|
|
|
536
615
|
const side = battle.side(player);
|
|
537
616
|
const pokemon = side.stored();
|
|
@@ -546,41 +625,38 @@ fn checkCriticalHit(battle: anytype, player: Player, move: Move.Data, options: a
|
|
|
546
625
|
}
|
|
547
626
|
assert(stage <= 4);
|
|
548
627
|
|
|
549
|
-
|
|
628
|
+
state.crit = try Rolls.criticalHit(battle, player, CRITICAL_HITS[stage], options);
|
|
550
629
|
}
|
|
551
630
|
|
|
552
|
-
fn calcDamage(
|
|
631
|
+
pub fn calcDamage(
|
|
553
632
|
battle: anytype,
|
|
554
633
|
player: Player,
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
) u16 {
|
|
634
|
+
state: *State,
|
|
635
|
+
options: anytype,
|
|
636
|
+
) !void {
|
|
559
637
|
_ = battle;
|
|
560
638
|
_ = player;
|
|
561
|
-
_ =
|
|
562
|
-
_ =
|
|
563
|
-
_ = crit;
|
|
564
|
-
return 0;
|
|
639
|
+
_ = state;
|
|
640
|
+
_ = options;
|
|
565
641
|
}
|
|
566
642
|
|
|
567
643
|
const FORESIGHT: Types = .{ .type1 = .Normal, .type2 = .Fighting };
|
|
568
644
|
|
|
569
|
-
fn adjustDamage(battle: anytype, player: Player,
|
|
570
|
-
if (
|
|
645
|
+
pub fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
646
|
+
if (state.move == .Struggle) return;
|
|
571
647
|
|
|
572
648
|
const side = battle.side(player);
|
|
573
649
|
const foe = battle.foe(player);
|
|
574
650
|
const types = foe.active.types;
|
|
575
|
-
const move = Move.get(
|
|
651
|
+
const move = Move.get(state.move);
|
|
576
652
|
|
|
577
|
-
var d = damage;
|
|
653
|
+
var d = state.damage;
|
|
578
654
|
if ((move.type == .Water and battle.field.weather == .Rain) or
|
|
579
655
|
(move.type == .Fire and battle.field.weather == .Sun))
|
|
580
656
|
{
|
|
581
657
|
d = d *% @intFromEnum(Effectiveness.Super) / 10;
|
|
582
658
|
} else if ((move.type == .Water and battle.field.weather == .Sun) or
|
|
583
|
-
((move.type == .Fire or
|
|
659
|
+
((move.type == .Fire or state.move == .SolarBeam) and battle.field.weather == .Rain))
|
|
584
660
|
{
|
|
585
661
|
d = d *% @intFromEnum(Effectiveness.Resisted) / 10;
|
|
586
662
|
}
|
|
@@ -590,17 +666,17 @@ fn adjustDamage(battle: anytype, player: Player, m: Move, damage: u16, effective
|
|
|
590
666
|
const neutral = @intFromEnum(Effectiveness.Neutral);
|
|
591
667
|
const foresight = foe.active.volatiles.Foresight and FORESIGHT.includes(move.type);
|
|
592
668
|
const eff1: u16 = if (foresight and types.type1 == .Ghost)
|
|
593
|
-
|
|
669
|
+
neutral
|
|
594
670
|
else
|
|
595
671
|
@intFromEnum(move.type.effectiveness(types.type1));
|
|
596
672
|
const eff2: u16 = if (foresight and types.type2 == .Ghost)
|
|
597
|
-
|
|
673
|
+
neutral
|
|
598
674
|
else
|
|
599
675
|
@intFromEnum(move.type.effectiveness(types.type2));
|
|
600
676
|
|
|
601
677
|
// Type effectiveness matchup precedence only matters with (NVE, SE)
|
|
602
678
|
if (!showdown and (eff1 + eff2) == Effectiveness.mismatch and
|
|
603
|
-
|
|
679
|
+
types.type1.precedence() > types.type2.precedence())
|
|
604
680
|
{
|
|
605
681
|
assert(eff2 != neutral);
|
|
606
682
|
d = d *% eff2 / 10;
|
|
@@ -612,65 +688,79 @@ fn adjustDamage(battle: anytype, player: Player, m: Move, damage: u16, effective
|
|
|
612
688
|
if (types.type1 != types.type2 and eff2 != neutral) d = d *% eff2 / 10;
|
|
613
689
|
}
|
|
614
690
|
|
|
615
|
-
effectiveness
|
|
616
|
-
|
|
691
|
+
state.effectiveness = if (types.type1 == types.type2) eff1 * neutral else eff1 * eff2;
|
|
692
|
+
state.damage = d;
|
|
617
693
|
}
|
|
618
694
|
|
|
619
|
-
fn randomizeDamage(battle: anytype, player: Player,
|
|
620
|
-
if (damage <= 1) return;
|
|
695
|
+
pub fn randomizeDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
696
|
+
if (state.damage <= 1) return;
|
|
621
697
|
const random = try Rolls.damage(battle, player, options);
|
|
622
|
-
options.calc.base(player.foe(), damage);
|
|
623
|
-
damage = @intCast(@as(u32, damage) *% random / 255);
|
|
624
|
-
options.calc.final(player.foe(), damage);
|
|
625
|
-
return damage;
|
|
698
|
+
options.calc.base(player.foe(), state.damage);
|
|
699
|
+
state.damage = @intCast(@as(u32, state.damage) *% random / 255);
|
|
700
|
+
options.calc.final(player.foe(), state.damage);
|
|
626
701
|
}
|
|
627
702
|
|
|
628
|
-
fn applyDamage(
|
|
703
|
+
pub fn applyDamage(
|
|
629
704
|
battle: anytype,
|
|
630
705
|
player: Player,
|
|
706
|
+
state: *State,
|
|
631
707
|
options: anytype,
|
|
632
|
-
) !
|
|
708
|
+
) !void {
|
|
633
709
|
_ = battle;
|
|
634
710
|
_ = player;
|
|
711
|
+
_ = state;
|
|
635
712
|
_ = options;
|
|
636
|
-
return false;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
fn checkHit(battle: anytype, player: Player, move: Move.Data, options: anytype) !bool {
|
|
640
|
-
if (moveHit(battle, player, move, options)) return true;
|
|
641
|
-
|
|
642
|
-
// try options.chance.commit(player, .miss);
|
|
643
|
-
try options.log.lastmiss();
|
|
644
|
-
try options.log.miss(battle.active(player));
|
|
645
|
-
|
|
646
|
-
return false;
|
|
647
713
|
}
|
|
648
714
|
|
|
649
715
|
// TODO: set damage = 0 if missed and not (High) Jump Kick
|
|
650
|
-
fn
|
|
716
|
+
pub fn checkHit(
|
|
651
717
|
battle: anytype,
|
|
652
718
|
player: Player,
|
|
653
|
-
|
|
719
|
+
state: *State,
|
|
654
720
|
options: anytype,
|
|
655
|
-
) !
|
|
721
|
+
) !void {
|
|
656
722
|
const side = battle.side(player);
|
|
657
723
|
const foe = battle.foe(player);
|
|
658
724
|
|
|
659
|
-
|
|
660
|
-
|
|
725
|
+
const move = Move.get(state.move);
|
|
726
|
+
|
|
727
|
+
if (move.effect == .DreamEater and !Status.is(foe.stored().status, .SLP)) {
|
|
728
|
+
state.miss = true;
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
if (foe.active.volatiles.Protect) {
|
|
732
|
+
state.miss = true;
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
661
735
|
const drain = move.effect == .DrainHP or move.effect == .DreamEater;
|
|
662
|
-
if (drain and foe.active.volatiles.Substitute)
|
|
736
|
+
if (drain and foe.active.volatiles.Substitute) {
|
|
737
|
+
state.miss = true;
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
663
740
|
if (side.active.volatiles.LockOn) {
|
|
664
|
-
|
|
741
|
+
state.miss = !(foe.active.volatiles.Flying and move.extra.underground);
|
|
742
|
+
return;
|
|
665
743
|
}
|
|
666
744
|
assert(!(foe.active.volatiles.Flying and foe.active.volatiles.Underground));
|
|
667
745
|
if (foe.active.volatiles.Flying) {
|
|
668
|
-
if (!move.extra.flying)
|
|
746
|
+
if (!move.extra.flying) {
|
|
747
|
+
state.miss = true;
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
669
750
|
} else if (foe.active.volatiles.Underground) {
|
|
670
|
-
if (!move.extra.underground)
|
|
751
|
+
if (!move.extra.underground) {
|
|
752
|
+
state.miss = true;
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
if (move.effect == .Thunder and battle.field.weather == .Rain) {
|
|
757
|
+
state.miss = false;
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
if (move.effect == .AlwaysHit) {
|
|
761
|
+
state.miss = false;
|
|
762
|
+
return;
|
|
671
763
|
}
|
|
672
|
-
if (move.effect == .Thunder and battle.field.weather == .Rain) return true;
|
|
673
|
-
if (move.effect == .AlwaysHit) return true;
|
|
674
764
|
|
|
675
765
|
var accuracy: u16 = move.accuracy;
|
|
676
766
|
if (foe.active.boosts.evasion <= side.active.boosts.accuracy or
|
|
@@ -688,8 +778,10 @@ fn moveHit(
|
|
|
688
778
|
}
|
|
689
779
|
|
|
690
780
|
// The accuracy roll is skipped entirely if maxed
|
|
691
|
-
if (accuracy == 255)
|
|
692
|
-
|
|
781
|
+
state.miss = if (accuracy == 255)
|
|
782
|
+
false
|
|
783
|
+
else
|
|
784
|
+
!try Rolls.hit(battle, player, @intCast(accuracy), options);
|
|
693
785
|
}
|
|
694
786
|
|
|
695
787
|
fn checkFaint(
|
|
@@ -697,20 +789,62 @@ fn checkFaint(
|
|
|
697
789
|
player: Player,
|
|
698
790
|
options: anytype,
|
|
699
791
|
) @TypeOf(options.log).Error!?Result {
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
792
|
+
const side = battle.side(player);
|
|
793
|
+
const foe = battle.foe(player);
|
|
794
|
+
|
|
795
|
+
const player_fainted = side.stored().hp == 0;
|
|
796
|
+
const foe_fainted = foe.stored().hp == 0;
|
|
797
|
+
if (!(player_fainted or foe_fainted)) return null;
|
|
798
|
+
|
|
799
|
+
const player_out = player_fainted and findFirstAlive(side) == 0;
|
|
800
|
+
const foe_out = foe_fainted and findFirstAlive(foe) == 0;
|
|
801
|
+
const more = player_out or foe_out;
|
|
802
|
+
|
|
803
|
+
if (player_fainted) try faint(battle, player, !(more or foe_fainted), options);
|
|
804
|
+
if (foe_fainted) try faint(battle, player.foe(), !more, options);
|
|
805
|
+
|
|
806
|
+
if (player_out and foe_out) {
|
|
807
|
+
try options.log.tie();
|
|
808
|
+
return Result.Tie;
|
|
809
|
+
} else if (player_out) {
|
|
810
|
+
try options.log.win(player.foe());
|
|
811
|
+
return if (player == .P1) Result.Lose else Result.Win;
|
|
812
|
+
} else if (foe_out) {
|
|
813
|
+
try options.log.win(player);
|
|
814
|
+
return if (player == .P1) Result.Win else Result.Lose;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
const foe_choice: Choice.Type = if (foe_fainted) .Switch else .Pass;
|
|
818
|
+
if (player == .P1) return .{ .p1 = .Switch, .p2 = foe_choice };
|
|
819
|
+
return .{ .p1 = foe_choice, .p2 = .Switch };
|
|
703
820
|
}
|
|
704
821
|
|
|
705
|
-
fn faint(battle: anytype, player: Player, done: bool, options: anytype)
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
822
|
+
fn faint(battle: anytype, player: Player, done: bool, options: anytype) !void {
|
|
823
|
+
var side = battle.side(player);
|
|
824
|
+
var foe = battle.foe(player);
|
|
825
|
+
assert(side.stored().hp == 0);
|
|
826
|
+
|
|
827
|
+
var foe_volatiles = &foe.active.volatiles;
|
|
828
|
+
foe_volatiles.BeatUp = false;
|
|
829
|
+
|
|
830
|
+
// We always need to clear status for Pokémon Showdown's Sleep/Freeze Clause Mod
|
|
831
|
+
const status = side.stored().status;
|
|
832
|
+
|
|
833
|
+
side.stored().status = 0;
|
|
834
|
+
// This shouldn't matter, but Pokémon Showdown decides double switching priority based on speed,
|
|
835
|
+
// and resets a Pokémon's stats when it faints... only it still factors in paralysis -_-
|
|
836
|
+
if (showdown) {
|
|
837
|
+
side.active.stats.spe = if (Status.is(status, .PAR))
|
|
838
|
+
@max(side.stored().stats.spe / 4, 1)
|
|
839
|
+
else
|
|
840
|
+
side.stored().stats.spe;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
try options.log.faint(battle.active(player), done);
|
|
844
|
+
options.calc.capped(player);
|
|
711
845
|
}
|
|
712
846
|
|
|
713
|
-
fn handleResidual(battle: anytype, player: Player, options: anytype) !
|
|
847
|
+
fn handleResidual(battle: anytype, player: Player, options: anytype) !bool {
|
|
714
848
|
var side = battle.side(player);
|
|
715
849
|
var stored = side.stored();
|
|
716
850
|
const ident = battle.active(player);
|
|
@@ -732,7 +866,7 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
732
866
|
stored.hp -|= damage;
|
|
733
867
|
// Pokémon Showdown uses damageOf here but its not relevant in Generation II
|
|
734
868
|
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
735
|
-
if (stored.hp == 0) return;
|
|
869
|
+
if (stored.hp == 0) return true;
|
|
736
870
|
}
|
|
737
871
|
|
|
738
872
|
if (volatiles.LeechSeed) {
|
|
@@ -754,28 +888,30 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
754
888
|
foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
|
|
755
889
|
// Pokémon Showdown uses the less specific heal here instead of drain... because reasons?
|
|
756
890
|
if (foe_stored.hp > before) try options.log.heal(foe_ident, foe_stored, .Silent);
|
|
757
|
-
if (stored.hp == 0) return;
|
|
891
|
+
if (stored.hp == 0) return true;
|
|
758
892
|
}
|
|
759
893
|
|
|
760
894
|
if (volatiles.Nightmare) {
|
|
761
895
|
stored.hp -= @max(stored.stats.hp / 4, 1);
|
|
762
896
|
// ibid
|
|
763
|
-
try options.log.damage(ident, stored,
|
|
764
|
-
if (stored.hp == 0) return;
|
|
897
|
+
try options.log.damage(ident, stored, .Nightmare);
|
|
898
|
+
if (stored.hp == 0) return true;
|
|
765
899
|
}
|
|
766
900
|
|
|
767
901
|
if (volatiles.Curse) {
|
|
768
902
|
stored.hp -= @max(stored.stats.hp / 4, 1);
|
|
769
903
|
// ibid
|
|
770
|
-
try options.log.damage(ident, stored,
|
|
771
|
-
if (stored.hp == 0) return;
|
|
904
|
+
try options.log.damage(ident, stored, .Curse);
|
|
905
|
+
if (stored.hp == 0) return true;
|
|
772
906
|
}
|
|
907
|
+
|
|
908
|
+
return false;
|
|
773
909
|
}
|
|
774
910
|
|
|
775
|
-
fn betweenTurns(battle: anytype, mslot: u4, options: anytype)
|
|
776
|
-
const players = std.enums.values(Player);
|
|
911
|
+
fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
912
|
+
const players = comptime std.enums.values(Player);
|
|
777
913
|
|
|
778
|
-
|
|
914
|
+
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
779
915
|
|
|
780
916
|
// Future Sight
|
|
781
917
|
inline for (players) |player| {
|
|
@@ -791,7 +927,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
791
927
|
}
|
|
792
928
|
}
|
|
793
929
|
}
|
|
794
|
-
|
|
930
|
+
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
795
931
|
|
|
796
932
|
// Weather
|
|
797
933
|
if (battle.field.weather != .None) {
|
|
@@ -807,12 +943,12 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
807
943
|
inline for (players) |player| {
|
|
808
944
|
var side = battle.side(player);
|
|
809
945
|
const active = side.active;
|
|
810
|
-
if (!(active.volatiles.
|
|
946
|
+
if (!(active.volatiles.Underground or active.types.sandstormImmune())) {
|
|
811
947
|
side.stored().hp -|= @max(side.stored().stats.hp / 8, 1);
|
|
812
948
|
try options.log.damage(battle.active(player), side.stored(), .Sandstorm);
|
|
813
949
|
}
|
|
814
950
|
}
|
|
815
|
-
|
|
951
|
+
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
816
952
|
}
|
|
817
953
|
}
|
|
818
954
|
}
|
|
@@ -822,22 +958,22 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
822
958
|
var side = battle.side(player);
|
|
823
959
|
var volatiles = &side.active.volatiles;
|
|
824
960
|
|
|
825
|
-
if (volatiles.bind > 0 and !volatiles.Substitute) {
|
|
826
|
-
volatiles.bind -= 1;
|
|
827
|
-
if (volatiles.bind == 0) {
|
|
828
|
-
const reason = @intFromEnum(End.Bind) + volatiles.
|
|
961
|
+
if (volatiles.bind.duration > 0 and !volatiles.Substitute) {
|
|
962
|
+
volatiles.bind.duration -= 1;
|
|
963
|
+
if (volatiles.bind.duration == 0) {
|
|
964
|
+
const reason = @intFromEnum(End.Bind) + volatiles.bind.reason - 1;
|
|
829
965
|
try options.log.end(battle.active(player), @enumFromInt(reason));
|
|
830
966
|
} else {
|
|
831
|
-
var reason = @intFromEnum(Activate.Bind) + volatiles.
|
|
967
|
+
var reason = @intFromEnum(Activate.Bind) + volatiles.bind.reason - 1;
|
|
832
968
|
try options.log.activate(battle.active(player), @enumFromInt(reason));
|
|
833
969
|
|
|
834
|
-
reason = @intFromEnum(
|
|
970
|
+
reason = @intFromEnum(Damage.Bind) + volatiles.bind.reason - 1;
|
|
835
971
|
side.stored().hp -|= @max(side.stored().stats.hp / 16, 1);
|
|
836
|
-
try options.log.damage(battle.active(player), side.stored(), reason);
|
|
972
|
+
try options.log.damage(battle.active(player), side.stored(), @enumFromInt(reason));
|
|
837
973
|
}
|
|
838
974
|
}
|
|
839
975
|
}
|
|
840
|
-
|
|
976
|
+
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
841
977
|
|
|
842
978
|
// Perish Song
|
|
843
979
|
inline for (players) |player| {
|
|
@@ -850,7 +986,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
850
986
|
|
|
851
987
|
assert(volatiles.perish_song < 3);
|
|
852
988
|
const reason = @intFromEnum(Start.PerishSong0) + volatiles.perish_song;
|
|
853
|
-
try options.log.start(battle.active(player), reason);
|
|
989
|
+
try options.log.start(battle.active(player), @enumFromInt(reason));
|
|
854
990
|
|
|
855
991
|
if (volatiles.perish_song == 0) {
|
|
856
992
|
volatiles.PerishSong = false;
|
|
@@ -858,7 +994,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
858
994
|
}
|
|
859
995
|
}
|
|
860
996
|
}
|
|
861
|
-
|
|
997
|
+
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
862
998
|
|
|
863
999
|
// Leftovers
|
|
864
1000
|
inline for (players) |player| {
|
|
@@ -896,22 +1032,21 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
896
1032
|
// Side Conditions
|
|
897
1033
|
if (showdown) {
|
|
898
1034
|
inline for (players) |player| {
|
|
899
|
-
updateSideCondition(battle, player, "Reflect", "reflect");
|
|
1035
|
+
try updateSideCondition(battle, player, "Reflect", "reflect", options);
|
|
900
1036
|
}
|
|
901
1037
|
inline for (players) |player| {
|
|
902
|
-
updateSideCondition(battle, player, "LightScreen", "light_screen");
|
|
1038
|
+
try updateSideCondition(battle, player, "LightScreen", "light_screen", options);
|
|
903
1039
|
}
|
|
904
1040
|
inline for (players) |player| {
|
|
905
|
-
updateSideCondition(battle, player, "Safeguard", "safeguard");
|
|
1041
|
+
try updateSideCondition(battle, player, "Safeguard", "safeguard", options);
|
|
906
1042
|
}
|
|
907
1043
|
} else {
|
|
908
1044
|
inline for (players) |player| {
|
|
909
|
-
updateSideCondition(battle, player, "Safeguard", "safeguard");
|
|
1045
|
+
try updateSideCondition(battle, player, "Safeguard", "safeguard", options);
|
|
910
1046
|
}
|
|
911
|
-
|
|
912
1047
|
inline for (players) |player| {
|
|
913
|
-
updateSideCondition(battle, player, "LightScreen", "light_screen");
|
|
914
|
-
updateSideCondition(battle, player, "Reflect", "reflect");
|
|
1048
|
+
try updateSideCondition(battle, player, "LightScreen", "light_screen", options);
|
|
1049
|
+
try updateSideCondition(battle, player, "Reflect", "reflect", options);
|
|
915
1050
|
}
|
|
916
1051
|
}
|
|
917
1052
|
|
|
@@ -984,12 +1119,14 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
984
1119
|
assert(volatiles.encore > 0);
|
|
985
1120
|
volatiles.encore -= 1;
|
|
986
1121
|
|
|
987
|
-
if (volatiles.encore == 0 or side.active.
|
|
1122
|
+
if (volatiles.encore == 0 or side.active.move(mslot).pp == 0) {
|
|
988
1123
|
volatiles.Encore = false;
|
|
989
1124
|
try options.log.end(battle.active(player), .Encore);
|
|
990
1125
|
}
|
|
991
1126
|
}
|
|
992
1127
|
}
|
|
1128
|
+
|
|
1129
|
+
return null;
|
|
993
1130
|
}
|
|
994
1131
|
|
|
995
1132
|
inline fn updateSideCondition(
|
|
@@ -1006,7 +1143,7 @@ inline fn updateSideCondition(
|
|
|
1006
1143
|
|
|
1007
1144
|
if (@field(side.conditions, value) == 0) {
|
|
1008
1145
|
@field(side.conditions, key) = false;
|
|
1009
|
-
try options.log.sideend(
|
|
1146
|
+
try options.log.sideend(player, @field(protocol.Side, key));
|
|
1010
1147
|
}
|
|
1011
1148
|
}
|
|
1012
1149
|
}
|
|
@@ -1026,206 +1163,1178 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1026
1163
|
return Result.Default;
|
|
1027
1164
|
}
|
|
1028
1165
|
|
|
1029
|
-
pub
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
// TODO
|
|
1038
|
-
|
|
1039
|
-
if (move.effect == .Endure) {
|
|
1040
|
-
volatiles.Endure = true;
|
|
1166
|
+
pub fn buildRage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1167
|
+
var foe = battle.foe(player);
|
|
1168
|
+
if (foe.active.volatiles.Rage) {
|
|
1169
|
+
if (showdown) {
|
|
1170
|
+
// TODO if showdown boost
|
|
1171
|
+
if (foe.active.boosts.atk < 6) {
|
|
1172
|
+
try Effects.boost(battle, player, state, options);
|
|
1173
|
+
}
|
|
1041
1174
|
} else {
|
|
1042
|
-
volatiles.
|
|
1175
|
+
foe.active.volatiles.rage +|= 1;
|
|
1176
|
+
try options.log.activate(battle.active(player), .Rage);
|
|
1043
1177
|
}
|
|
1044
|
-
try options.log.singleturn(battle.active(player), move.id);
|
|
1045
1178
|
}
|
|
1179
|
+
}
|
|
1046
1180
|
|
|
1047
|
-
|
|
1048
|
-
|
|
1181
|
+
pub fn rageDamage(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1182
|
+
const volatiles = battle.side(player).active.volatiles;
|
|
1183
|
+
assert(volatiles.Rage);
|
|
1184
|
+
state.damage *|= (volatiles.rage +| 1);
|
|
1185
|
+
}
|
|
1049
1186
|
|
|
1050
|
-
fn
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1187
|
+
pub fn kingsRock(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1188
|
+
var foe = battle.foe(player);
|
|
1189
|
+
const flinch = battle.side(player).stored().item == .KingsRock and
|
|
1190
|
+
!foe.active.volatiles.Substitute and
|
|
1191
|
+
try Rolls.item(battle, player, options);
|
|
1192
|
+
if (flinch) {
|
|
1193
|
+
foe.active.volatiles.Recharging = false;
|
|
1194
|
+
foe.active.volatiles.Flinch = true;
|
|
1055
1195
|
}
|
|
1056
1196
|
}
|
|
1057
1197
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
active.volatiles.Rollout;
|
|
1062
|
-
}
|
|
1198
|
+
pub fn destinyBond(battle: anytype, player: Player, _: *State, options: anytype) !bool {
|
|
1199
|
+
const foe = battle.foe(player);
|
|
1200
|
+
if (foe.stored().hp > 0) return false;
|
|
1063
1201
|
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
else
|
|
1071
|
-
battle.rng.next() < Gen12.percent(50) + 1;
|
|
1202
|
+
if (foe.active.volatiles.DestinyBond) {
|
|
1203
|
+
battle.side(player).stored().hp = 0;
|
|
1204
|
+
try options.log.activate(battle.active(player.foe()), .DestinyBond);
|
|
1205
|
+
}
|
|
1206
|
+
return true;
|
|
1207
|
+
}
|
|
1072
1208
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1209
|
+
pub const Effects = struct {
|
|
1210
|
+
pub fn alwaysHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1211
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1075
1212
|
}
|
|
1076
1213
|
|
|
1077
|
-
|
|
1078
|
-
const
|
|
1079
|
-
|
|
1080
|
-
else if (showdown)
|
|
1081
|
-
battle.rng.chance(u8, 60, 256)
|
|
1082
|
-
else
|
|
1083
|
-
battle.rng.next() < 60;
|
|
1214
|
+
pub fn attract(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1215
|
+
const side = battle.side(player);
|
|
1216
|
+
var foe = battle.foe(player);
|
|
1084
1217
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1218
|
+
const gender: u8 = @intFromEnum(side.stored().dvs.gender);
|
|
1219
|
+
const foe_gender: u8 = @intFromEnum(foe.stored().dvs.gender);
|
|
1220
|
+
if (gender + foe_gender != 1) {
|
|
1221
|
+
try options.log.immune(battle.active(player.foe()), .None);
|
|
1222
|
+
return;
|
|
1223
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1224
|
+
try options.log.lastmiss();
|
|
1225
|
+
try options.log.miss(battle.active(player));
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
foe.active.volatiles.Attract = true;
|
|
1230
|
+
try options.log.start(battle.active(player.foe()), .Attract);
|
|
1087
1231
|
}
|
|
1088
1232
|
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
else if (showdown)
|
|
1093
|
-
battle.rng.chance(u8, accuracy, 256)
|
|
1094
|
-
else
|
|
1095
|
-
battle.rng.next() < accuracy;
|
|
1233
|
+
pub fn batonPass(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1234
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1235
|
+
}
|
|
1096
1236
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1237
|
+
pub fn beatUp(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1238
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1099
1239
|
}
|
|
1100
1240
|
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
else if (showdown)
|
|
1105
|
-
battle.rng.chance(u8, rate, 256)
|
|
1106
|
-
else
|
|
1107
|
-
battle.rng.next() < rate;
|
|
1241
|
+
pub fn bellyDrum(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1242
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1243
|
+
}
|
|
1108
1244
|
|
|
1109
|
-
|
|
1110
|
-
|
|
1245
|
+
pub fn bide(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1246
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1111
1247
|
}
|
|
1112
1248
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1249
|
+
pub fn binding(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1250
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
pub fn burnChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1254
|
+
var foe = battle.foe(player);
|
|
1255
|
+
var foe_stored = foe.stored();
|
|
1256
|
+
|
|
1257
|
+
if (foe.active.volatiles.Substitute) return;
|
|
1258
|
+
if (Status.any(foe_stored.status)) {
|
|
1259
|
+
if (Status.is(foe_stored.status, .FRZ)) {
|
|
1260
|
+
assert(Move.get(state.move).type == .Fire);
|
|
1261
|
+
try options.log.curestatus(
|
|
1262
|
+
battle.active(player.foe()),
|
|
1263
|
+
foe_stored.status,
|
|
1264
|
+
.Message,
|
|
1265
|
+
);
|
|
1266
|
+
foe_stored.status = 0;
|
|
1121
1267
|
}
|
|
1122
|
-
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
if (!state.proc) return;
|
|
1271
|
+
if (foe.conditions.Safeguard) return;
|
|
1272
|
+
// No need to check for immunity because nothing is immune to Fire-type
|
|
1273
|
+
assert(!state.immune());
|
|
1274
|
+
if (foe.active.types.includes(Move.get(state.move).type)) return;
|
|
1123
1275
|
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
}
|
|
1276
|
+
foe_stored.status = Status.init(.BRN);
|
|
1277
|
+
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
1278
|
+
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
1128
1279
|
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
val == .true
|
|
1132
|
-
else if (showdown)
|
|
1133
|
-
!battle.rng.chance(u8, 128, 256)
|
|
1134
|
-
else
|
|
1135
|
-
battle.rng.next() >= Gen12.percent(50) + 1;
|
|
1280
|
+
// TODO: check for status berry
|
|
1281
|
+
}
|
|
1136
1282
|
|
|
1137
|
-
|
|
1138
|
-
|
|
1283
|
+
pub fn confusion(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1284
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1139
1285
|
}
|
|
1140
1286
|
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
val == .true
|
|
1144
|
-
else if (showdown)
|
|
1145
|
-
battle.rng.chance(u8, 1, 2)
|
|
1146
|
-
else
|
|
1147
|
-
battle.rng.next() > Gen12.percent(50) + 1;
|
|
1287
|
+
pub fn conversion(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1288
|
+
var side = battle.side(player);
|
|
1148
1289
|
|
|
1149
|
-
|
|
1150
|
-
|
|
1290
|
+
var n: u3 = 0;
|
|
1291
|
+
for (side.active.moves) |m| {
|
|
1292
|
+
if (convertible(side.active, m.id)) n += 1;
|
|
1293
|
+
}
|
|
1294
|
+
if (n == 0) {
|
|
1295
|
+
try options.log.fail(battle.active(player), .None);
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
side.active.types.type1 = try Rolls.conversion(battle, player, n, options);
|
|
1300
|
+
side.active.types.type2 = side.active.types.type1;
|
|
1151
1301
|
}
|
|
1152
1302
|
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
val == .true
|
|
1156
|
-
else if (showdown)
|
|
1157
|
-
battle.rng.chance(u8, 63, 256)
|
|
1158
|
-
else
|
|
1159
|
-
battle.rng.next() < Gen12.percent(25);
|
|
1303
|
+
pub fn conversion2(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1304
|
+
var side = battle.side(player);
|
|
1160
1305
|
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1306
|
+
const last = battle.foe(player).lastMove(false);
|
|
1307
|
+
if (last == .None) {
|
|
1308
|
+
try options.log.fail(battle.active(player), .None);
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
const move = Move.get(last);
|
|
1312
|
+
if (move.type == .@"???") {
|
|
1313
|
+
try options.log.fail(battle.active(player), .None);
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1164
1316
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
else if (showdown)
|
|
1169
|
-
battle.rng.chance(u8, 25, 256)
|
|
1170
|
-
else
|
|
1171
|
-
battle.rng.next() < Gen12.percent(10);
|
|
1317
|
+
side.active.types.type1 = try Rolls.conversion2(battle, player, move.type, options);
|
|
1318
|
+
side.active.types.type2 = side.active.types.type1;
|
|
1319
|
+
}
|
|
1172
1320
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1321
|
+
pub fn counter(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1322
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1175
1323
|
}
|
|
1176
1324
|
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
val == .true
|
|
1181
|
-
else if (showdown)
|
|
1182
|
-
battle.rng.chance(u8, rate, 256)
|
|
1183
|
-
else
|
|
1184
|
-
battle.rng.next() < rate;
|
|
1325
|
+
pub fn curse(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1326
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1327
|
+
}
|
|
1185
1328
|
|
|
1186
|
-
|
|
1187
|
-
|
|
1329
|
+
pub fn defenseCurl(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1330
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1188
1331
|
}
|
|
1189
1332
|
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
else if (showdown)
|
|
1194
|
-
battle.rng.chance(u8, 30, 256)
|
|
1195
|
-
else
|
|
1196
|
-
battle.rng.next() < 30;
|
|
1333
|
+
pub fn defrost(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1334
|
+
var side = battle.side(player);
|
|
1335
|
+
assert(Status.is(side.stored().status, .FRZ));
|
|
1197
1336
|
|
|
1198
|
-
try options.
|
|
1199
|
-
|
|
1337
|
+
try options.log.curestatus(battle.active(player), side.stored().status, .Message);
|
|
1338
|
+
side.stored().status = 0;
|
|
1200
1339
|
}
|
|
1201
1340
|
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
else if (showdown)
|
|
1206
|
-
battle.rng.chance(u8, 30, 256)
|
|
1207
|
-
else
|
|
1208
|
-
battle.rng.next() < 30;
|
|
1209
|
-
|
|
1210
|
-
try options.chance.kingsRock(player, proc);
|
|
1211
|
-
return proc;
|
|
1341
|
+
pub fn destinyBond(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1342
|
+
battle.side(player).active.volatiles.DestinyBond = true;
|
|
1343
|
+
try options.log.singlemove(battle.active(player), Move.DestinyBond);
|
|
1212
1344
|
}
|
|
1213
1345
|
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1346
|
+
pub fn disable(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1347
|
+
var foe = battle.foe(player);
|
|
1348
|
+
var volatiles = &foe.active.volatiles;
|
|
1349
|
+
const foe_ident = battle.active(player.foe());
|
|
1350
|
+
|
|
1351
|
+
if (volatiles.disable.move != 0) {
|
|
1352
|
+
try options.log.fail(foe_ident, .None);
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
const last = battle.foe(player).lastMove(false);
|
|
1357
|
+
if (last == .None or last == .Struggle) {
|
|
1358
|
+
try options.log.fail(foe_ident, .None);
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
var slot: u3 = 0;
|
|
1363
|
+
for (foe.active.moves) |m| {
|
|
1364
|
+
assert(m.id != .None);
|
|
1365
|
+
slot += 1;
|
|
1366
|
+
if (m.id == last) break;
|
|
1367
|
+
}
|
|
1368
|
+
const move = foe.active.move(slot);
|
|
1369
|
+
if (move.pp == 0) {
|
|
1370
|
+
try options.log.fail(foe_ident, .None);
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
volatiles.disable.move = slot;
|
|
1375
|
+
volatiles.disable.duration = Rolls.disableDuration(battle, player, options);
|
|
1376
|
+
|
|
1377
|
+
try options.log.startEffect(foe_ident, .Disable, move.id);
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
pub fn doubleHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1381
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
pub fn drainHP(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1385
|
+
var stored = battle.side(player).stored();
|
|
1386
|
+
|
|
1387
|
+
assert(state.damage != 0);
|
|
1388
|
+
|
|
1389
|
+
if (stored.hp == stored.stats.hp) return;
|
|
1390
|
+
stored.hp = @min(stored.stats.hp, stored.hp + @max(state.damage / 2, 1));
|
|
1391
|
+
|
|
1392
|
+
try options.log.drain(battle.active(player), stored, battle.active(player.foe()));
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
pub fn encore(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1396
|
+
var foe = battle.foe(player);
|
|
1397
|
+
var volatiles = &foe.active.volatiles;
|
|
1398
|
+
const foe_ident = battle.active(player.foe());
|
|
1399
|
+
|
|
1400
|
+
const last = battle.foe(player).lastMove(true);
|
|
1401
|
+
const failed = volatiles.Encore or
|
|
1402
|
+
(last == .None or last == .Struggle or
|
|
1403
|
+
last == .Encore or .last == .MirrorMove);
|
|
1404
|
+
if (failed) {
|
|
1405
|
+
try options.log.fail(foe_ident, .None);
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
var slot: u3 = 0;
|
|
1410
|
+
for (foe.active.moves) |m| {
|
|
1411
|
+
assert(m.id != .None);
|
|
1412
|
+
slot += 1;
|
|
1413
|
+
if (m.id == last) break;
|
|
1414
|
+
}
|
|
1415
|
+
const move = foe.active.move(slot);
|
|
1416
|
+
if (move.pp == 0) {
|
|
1417
|
+
try options.log.fail(foe_ident, .None);
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
volatiles.Encore = true;
|
|
1422
|
+
volatiles.encore = Rolls.encoreDuration(battle, player, options);
|
|
1423
|
+
|
|
1424
|
+
// TODO ???
|
|
1425
|
+
_ = state;
|
|
1426
|
+
|
|
1427
|
+
try options.log.startEffect(foe_ident, .Encore, move.id);
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
pub fn explode(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1431
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
pub fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1435
|
+
const foe = battle.foe(player);
|
|
1436
|
+
assert(foe.stored().hp > 0);
|
|
1437
|
+
// FIXME crit?
|
|
1438
|
+
if (state.damage > foe.stored().hp) state.damage = foe.stored().hp - 1;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
pub fn fixedDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1442
|
+
const side = battle.side(player);
|
|
1443
|
+
const foe = battle.foe(player);
|
|
1444
|
+
|
|
1445
|
+
state.damage = switch (Move.get(state.move).effect) {
|
|
1446
|
+
.SuperFang => @max(foe.stored().hp / 2, 1),
|
|
1447
|
+
.LevelDamage => side.stored().level,
|
|
1448
|
+
.FixedDamage => if (state.move == .SonicBoom) 20 else 40,
|
|
1449
|
+
.Psywave => try Rolls.psywave(
|
|
1450
|
+
battle,
|
|
1451
|
+
player,
|
|
1452
|
+
@intCast(@as(u16, side.stored().level) * 3 / 2),
|
|
1453
|
+
options,
|
|
1454
|
+
),
|
|
1455
|
+
else => unreachable,
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
pub fn flameWheel(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1460
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
pub fn flinchChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1464
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
pub fn flyDig(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1468
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
pub fn focusEnergy(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1472
|
+
var side = battle.side(player);
|
|
1473
|
+
|
|
1474
|
+
if (side.active.volatiles.FocusEnergy) {
|
|
1475
|
+
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
side.active.volatiles.FocusEnergy = true;
|
|
1480
|
+
try options.log.start(battle.active(player), .FocusEnergy);
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
var SLOTS: [5]u4 = .{ 0, 0, 0, 0, 0 };
|
|
1484
|
+
|
|
1485
|
+
pub fn forceSwitch(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1486
|
+
const foe = battle.foe(player);
|
|
1487
|
+
|
|
1488
|
+
var n: u4 = 0;
|
|
1489
|
+
var i: u4 = 0;
|
|
1490
|
+
for (foe.pokemon, 0..) |pokemon, j| {
|
|
1491
|
+
if (pokemon.species == .None) break;
|
|
1492
|
+
n += 1;
|
|
1493
|
+
if (pokemon.position == 1) continue;
|
|
1494
|
+
if (pokemon.hp > 0) {
|
|
1495
|
+
SLOTS[i] = @intCast(foe.pokemon[j].position);
|
|
1496
|
+
i += 1;
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
if (n == 0) {
|
|
1501
|
+
try options.log.fail(battle.active(player), .None);
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
_ = try Rolls.forceSwitch(battle, player, SLOTS[0..i], n, options); // TODO
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
pub fn foresight(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1509
|
+
var foe = battle.foe(player);
|
|
1510
|
+
|
|
1511
|
+
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1512
|
+
try options.log.lastmiss();
|
|
1513
|
+
try options.log.miss(battle.active(player));
|
|
1514
|
+
return;
|
|
1515
|
+
} else if (foe.active.volatiles.Foresight) {
|
|
1516
|
+
try options.log.fail(battle.active(player), .None);
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
foe.active.volatiles.Foresight = true;
|
|
1521
|
+
try options.log.start(battle.active(player.foe()), .FocusEnergy);
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
pub fn freezeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1525
|
+
var foe = battle.foe(player);
|
|
1526
|
+
var foe_stored = foe.stored();
|
|
1527
|
+
const foe_ident = battle.active(player.foe());
|
|
1528
|
+
|
|
1529
|
+
if (foe.active.volatiles.Substitute) return;
|
|
1530
|
+
if (Status.any(foe_stored.status)) return;
|
|
1531
|
+
|
|
1532
|
+
// No need to check for immunity because nothing is immune to Ice-type
|
|
1533
|
+
assert(!state.immune());
|
|
1534
|
+
if (battle.field.weather == .Sun) return;
|
|
1535
|
+
if (foe.active.types.includes(Move.get(state.move).type)) return;
|
|
1536
|
+
if (!state.proc) return;
|
|
1537
|
+
if (foe.conditions.Safeguard) return;
|
|
1538
|
+
// Freeze Clause Mod
|
|
1539
|
+
if (showdown) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
|
|
1540
|
+
|
|
1541
|
+
foe_stored.status = Status.init(.FRZ);
|
|
1542
|
+
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
1543
|
+
|
|
1544
|
+
// TODO: check for status berry
|
|
1545
|
+
|
|
1546
|
+
foe.active.volatiles.Recharging = false;
|
|
1547
|
+
foe.active.volatiles.frozen = true;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
pub fn furyCutter(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1551
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
pub fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1555
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
pub fn happiness(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1559
|
+
const val = battle.side(player).stored().happiness;
|
|
1560
|
+
const frustration = Move.get(state.move).effect == .Frustration;
|
|
1561
|
+
state.bp = ((if (frustration) 255 - val else val) * 10) / 25;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
pub fn haze(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1565
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
pub fn heal(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1569
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
pub fn healBell(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1573
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
pub fn hiddenPower(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1577
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
pub fn hyperBeam(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1581
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
pub fn jumpKick(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1585
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
pub fn leechSeed(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1589
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
pub fn lockOn(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1593
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
const MAGNITUDE_POWER = [_]u8{ 10, 30, 50, 70, 90, 110, 150 };
|
|
1597
|
+
|
|
1598
|
+
pub fn magnitude(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1599
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
pub fn meanLook(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1603
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
pub fn metronome(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1607
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
pub fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1611
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
pub fn mirrorCoat(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1615
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
pub fn mirrorMove(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1619
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
pub fn mist(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1623
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
pub fn weatherHeal(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1627
|
+
var stored = battle.side(player).stored();
|
|
1628
|
+
const ident = battle.active(player);
|
|
1629
|
+
|
|
1630
|
+
if (stored.hp == stored.stats.hp) return try options.log.fail(ident, .None);
|
|
1631
|
+
|
|
1632
|
+
stored.hp = switch (battle.field.weather) {
|
|
1633
|
+
.Sun => stored.stats.hp,
|
|
1634
|
+
.None => @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2)),
|
|
1635
|
+
else => @min(stored.stats.hp, stored.hp + (stored.stats.hp / 4)),
|
|
1636
|
+
};
|
|
1637
|
+
|
|
1638
|
+
try options.log.heal(ident, stored, Heal.None);
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
pub fn multiHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1642
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
pub fn nightmare(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1646
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
pub fn ohko(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1650
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
pub fn painSplit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1654
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
pub fn paralyze(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1658
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
pub fn paralyzeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1662
|
+
var foe = battle.foe(player);
|
|
1663
|
+
var foe_stored = foe.stored();
|
|
1664
|
+
|
|
1665
|
+
if (foe.active.volatiles.Substitute) return;
|
|
1666
|
+
if (Status.any(foe_stored.status)) return;
|
|
1667
|
+
if (state.immune()) return;
|
|
1668
|
+
if (!state.proc) return;
|
|
1669
|
+
if (foe.conditions.Safeguard) return;
|
|
1670
|
+
|
|
1671
|
+
foe_stored.status = Status.init(.PAR);
|
|
1672
|
+
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
1673
|
+
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
1674
|
+
|
|
1675
|
+
// TODO: check for status berry
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
pub fn payDay(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1679
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
pub fn perishSong(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1683
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
pub fn poison(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1687
|
+
var foe = battle.foe(player);
|
|
1688
|
+
var foe_stored = foe.stored();
|
|
1689
|
+
const foe_ident = battle.active(player.foe());
|
|
1690
|
+
|
|
1691
|
+
const toxic = state.move == .Toxic;
|
|
1692
|
+
if (state.immune() or foe.active.types.includes(.Poison)) {
|
|
1693
|
+
return options.log.immune(foe_ident, .None);
|
|
1694
|
+
} else if (Status.any(foe_stored.status)) {
|
|
1695
|
+
// Pokémon Showdown considers Toxic to be a status even in Generation II and so
|
|
1696
|
+
// will not include a fail reason for Toxic vs. Poison or vice-versa...
|
|
1697
|
+
return options.log.fail(foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
1698
|
+
if (!showdown)
|
|
1699
|
+
.Poison
|
|
1700
|
+
else if (toxic == (foe_stored.status == Status.TOX))
|
|
1701
|
+
if (toxic) .Toxic else .Poison
|
|
1702
|
+
else
|
|
1703
|
+
.None
|
|
1704
|
+
else
|
|
1705
|
+
.None);
|
|
1706
|
+
} else if (foe.active.volatiles.Substitute) {
|
|
1707
|
+
return options.log.activateMove(foe_ident, .SubstituteBlock, state.move);
|
|
1708
|
+
} else if (state.miss) {
|
|
1709
|
+
try options.log.lastmiss();
|
|
1710
|
+
try options.log.miss(battle.active(player));
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
foe_stored.status = Status.init(.PSN);
|
|
1714
|
+
if (toxic) {
|
|
1715
|
+
if (showdown) foe_stored.status = Status.TOX;
|
|
1716
|
+
foe.active.volatiles.Toxic = true;
|
|
1717
|
+
foe.active.volatiles.toxic = 0;
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
1721
|
+
|
|
1722
|
+
// TODO: check for status berry
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
pub fn poisonChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1726
|
+
var foe = battle.foe(player);
|
|
1727
|
+
var foe_stored = foe.stored();
|
|
1728
|
+
|
|
1729
|
+
if (foe.active.volatiles.Substitute) return;
|
|
1730
|
+
if (Status.any(foe_stored.status)) return if (showdown) battle.rng.advance(1);
|
|
1731
|
+
if (state.immune() or foe.active.types.includes(.Poison)) return;
|
|
1732
|
+
if (!state.proc) return;
|
|
1733
|
+
if (foe.conditions.Safeguard) return;
|
|
1734
|
+
|
|
1735
|
+
foe_stored.status = Status.init(.PSN);
|
|
1736
|
+
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
1737
|
+
|
|
1738
|
+
// TODO: check for status berry
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
pub fn present(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1742
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
pub fn protect(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1746
|
+
var side = battle.side(player);
|
|
1747
|
+
var volatiles = &side.active.volatiles;
|
|
1748
|
+
|
|
1749
|
+
const failed = !state.first or volatiles.Substitute or
|
|
1750
|
+
!try Rolls.protect(battle, player, volatiles.protect, options);
|
|
1751
|
+
if (failed) {
|
|
1752
|
+
try options.log.fail(battle.active(player), .None);
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
volatiles.protect = @max(8, volatiles.protect + 1);
|
|
1757
|
+
if (state.move == .Endure) {
|
|
1758
|
+
volatiles.Endure = true;
|
|
1759
|
+
} else {
|
|
1760
|
+
volatiles.Protect = true;
|
|
1761
|
+
}
|
|
1762
|
+
try options.log.singleturn(battle.active(player), state.move);
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
pub fn psychUp(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1766
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
pub fn pursuit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1770
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
pub fn rage(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1774
|
+
battle.side(player).active.volatiles.Rage = true;
|
|
1775
|
+
try options.log.singlemove(battle.active(player), Move.Rage);
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
pub fn rainDance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1779
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
pub fn rapidSpin(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1783
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
pub fn razorWind(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1787
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
pub fn recoil(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1791
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
pub fn reversal(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1795
|
+
const stored = battle.side(player).stored();
|
|
1796
|
+
|
|
1797
|
+
// Pokémon Showdown Gen II implementation incorrectly inherits Gen IV's table instead
|
|
1798
|
+
state.bp = if (showdown) power: {
|
|
1799
|
+
const x = @max(1, stored.hp * 64 / stored.stats.hp);
|
|
1800
|
+
if (x < 2) break :power 200;
|
|
1801
|
+
if (x < 6) break :power 150;
|
|
1802
|
+
if (x < 13) break :power 100;
|
|
1803
|
+
if (x < 22) break :power 80;
|
|
1804
|
+
if (x < 43) break :power 40;
|
|
1805
|
+
break :power 20;
|
|
1806
|
+
} else power: {
|
|
1807
|
+
const x = @max(1, stored.hp * 48 / stored.stats.hp);
|
|
1808
|
+
if (x < 1) break :power 200;
|
|
1809
|
+
if (x < 4) break :power 150;
|
|
1810
|
+
if (x < 9) break :power 100;
|
|
1811
|
+
if (x < 16) break :power 80;
|
|
1812
|
+
if (x < 32) break :power 40;
|
|
1813
|
+
break :power 20;
|
|
1814
|
+
};
|
|
1815
|
+
|
|
1816
|
+
try calcDamage(battle, player, state, options);
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
pub fn rollout(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1820
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
pub fn sacredFire(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1824
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
pub fn safeguard(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1828
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
pub fn sandstorm(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1832
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
pub fn screens(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1836
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
pub fn sketch(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1840
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
pub fn skullBash(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1844
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
pub fn skyAttack(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1848
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
pub fn sleep(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1852
|
+
var foe = battle.foe(player);
|
|
1853
|
+
var foe_stored = foe.stored();
|
|
1854
|
+
const foe_ident = battle.active(player.foe());
|
|
1855
|
+
|
|
1856
|
+
if (Status.any(foe_stored.status)) {
|
|
1857
|
+
return options.log.fail(
|
|
1858
|
+
foe_ident,
|
|
1859
|
+
if (Status.is(foe_stored.status, .SLP)) .Sleep else .None,
|
|
1860
|
+
);
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
if (foe.active.volatiles.Substitute) {
|
|
1864
|
+
return options.log.activateMove(foe_ident, .SubstituteBlock, state.move);
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
// Sleep Clause Mod
|
|
1868
|
+
if (showdown) {
|
|
1869
|
+
for (foe.pokemon) |p| {
|
|
1870
|
+
if (Status.is(p.status, .SLP) and !Status.is(p.status, .EXT)) return;
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
|
|
1875
|
+
try options.log.statusFrom(foe_ident, foe_stored.status, state.move);
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
pub fn sleepTalk(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1879
|
+
const side = battle.side(player);
|
|
1880
|
+
|
|
1881
|
+
if (!Status.is(side.stored().status, .SLP)) {
|
|
1882
|
+
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
var n: u3 = 0;
|
|
1887
|
+
for (side.active.moves, 0..) |m, i| {
|
|
1888
|
+
if (state.mslot - 1 != i and Move.get(m.id).extra.sleep_talk) n += 1;
|
|
1889
|
+
}
|
|
1890
|
+
if (n == 0) {
|
|
1891
|
+
try options.log.fail(battle.active(player), .None);
|
|
1892
|
+
return;
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
_ = try Rolls.sleepTalk(battle, player, n, state.mslot, options); // TODO
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
pub fn snore(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1899
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
pub fn solarBeam(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1903
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
pub fn spikes(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1907
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1911
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
pub fn splash(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1915
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
pub fn stomp(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1919
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
pub fn substitute(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1923
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
pub fn sunnyDay(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1927
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
pub fn swagger(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1931
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
pub fn synthesis(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1935
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
pub fn teleport(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1939
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
pub fn thief(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1943
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
pub fn thrashing(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1947
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
pub fn transform(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1951
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
pub fn tripleKick(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1955
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
pub fn twister(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1959
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
const ALL_STAT_UP_STATS = [_][]const u8{ "atk", "def", "spe", "spa", "spd" };
|
|
1963
|
+
const ALL_STAT_UP_REASONS =
|
|
1964
|
+
[_]Boost{ .Attack, .Defense, .Speed, .SpecialAttack, .SpecialDefense };
|
|
1965
|
+
|
|
1966
|
+
pub fn boost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1967
|
+
var log = options.log;
|
|
1968
|
+
var side = battle.side(player);
|
|
1969
|
+
const ident = battle.active(player);
|
|
1970
|
+
|
|
1971
|
+
if (state.miss) return try log.fail(ident, .None);
|
|
1972
|
+
|
|
1973
|
+
var stats = &side.active.stats;
|
|
1974
|
+
var boosts = &side.active.boosts;
|
|
1975
|
+
|
|
1976
|
+
const move = Move.get(state.move);
|
|
1977
|
+
switch (move.effect) {
|
|
1978
|
+
.AttackUp1, .AttackUp2, .Rage => {
|
|
1979
|
+
assert(state.proc);
|
|
1980
|
+
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
1981
|
+
if (boosts.atk == 6 or stats.atk == MAX_STAT_VALUE) {
|
|
1982
|
+
return try log.fail(ident, .None);
|
|
1983
|
+
}
|
|
1984
|
+
const n: u2 = if (move.effect == .AttackUp2) 2 else 1;
|
|
1985
|
+
boosts.atk = @as(i4, @intCast(@min(6, @as(i8, boosts.atk) + n)));
|
|
1986
|
+
const reason = if (showdown and move.effect == .Rage) Boost.Rage else Boost.Attack;
|
|
1987
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
1988
|
+
const stat = unmodifiedStats(battle, side).atk;
|
|
1989
|
+
stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
1990
|
+
try log.boost(ident, reason, n);
|
|
1991
|
+
},
|
|
1992
|
+
.DefenseUp1, .DefenseUp2 => {
|
|
1993
|
+
assert(state.proc);
|
|
1994
|
+
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
1995
|
+
if (boosts.def == 6 or stats.def == MAX_STAT_VALUE) {
|
|
1996
|
+
return try log.fail(ident, .None);
|
|
1997
|
+
}
|
|
1998
|
+
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
1999
|
+
boosts.def = @intCast(@min(6, @as(i8, boosts.def) + n));
|
|
2000
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2001
|
+
const stat = unmodifiedStats(battle, side).def;
|
|
2002
|
+
stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2003
|
+
try log.boost(ident, .Defense, n);
|
|
2004
|
+
},
|
|
2005
|
+
.SpeedUp2 => {
|
|
2006
|
+
assert(state.proc);
|
|
2007
|
+
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2008
|
+
if (boosts.spe == 6 or stats.spe == MAX_STAT_VALUE) {
|
|
2009
|
+
return try log.fail(ident, .None);
|
|
2010
|
+
}
|
|
2011
|
+
boosts.spe = @intCast(@min(6, @as(i8, boosts.spe) + 2));
|
|
2012
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2013
|
+
const stat = unmodifiedStats(battle, side).spe;
|
|
2014
|
+
stats.spe = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2015
|
+
try log.boost(ident, .Speed, 2);
|
|
2016
|
+
},
|
|
2017
|
+
.SpAtkUp1 => {
|
|
2018
|
+
assert(state.proc);
|
|
2019
|
+
assert(boosts.spa >= -6 and boosts.spa <= 6);
|
|
2020
|
+
if (boosts.spa == 6 or stats.spa == MAX_STAT_VALUE) {
|
|
2021
|
+
return try log.fail(ident, .None);
|
|
2022
|
+
}
|
|
2023
|
+
boosts.spa = @intCast(@min(6, @as(i8, boosts.spa) + 1));
|
|
2024
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.spa) + 6))];
|
|
2025
|
+
const stat = unmodifiedStats(battle, side).spa;
|
|
2026
|
+
stats.spa = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2027
|
+
try log.boost(ident, .SpecialAttack, 1);
|
|
2028
|
+
},
|
|
2029
|
+
.SpDefUp2 => {
|
|
2030
|
+
assert(state.proc);
|
|
2031
|
+
assert(boosts.spd >= -6 and boosts.spd <= 6);
|
|
2032
|
+
if (boosts.spd == 6 or stats.spd == MAX_STAT_VALUE) {
|
|
2033
|
+
return try log.fail(ident, .None);
|
|
2034
|
+
}
|
|
2035
|
+
boosts.spd = @intCast(@min(6, @as(i8, boosts.spd) + 2));
|
|
2036
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.spd) + 6))];
|
|
2037
|
+
const stat = unmodifiedStats(battle, side).spd;
|
|
2038
|
+
stats.spd = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2039
|
+
try log.boost(ident, .SpecialDefense, 2);
|
|
2040
|
+
},
|
|
2041
|
+
.EvasionUp1 => {
|
|
2042
|
+
assert(state.proc);
|
|
2043
|
+
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2044
|
+
if (boosts.evasion == 6) {
|
|
2045
|
+
return try log.fail(ident, .None);
|
|
2046
|
+
}
|
|
2047
|
+
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2048
|
+
try log.boost(ident, .Evasion, 1);
|
|
2049
|
+
},
|
|
2050
|
+
.AllStatUpChance => {
|
|
2051
|
+
if (!state.proc) return;
|
|
2052
|
+
inline for (ALL_STAT_UP_STATS, ALL_STAT_UP_REASONS) |s, r| {
|
|
2053
|
+
assert(@field(boosts, s) >= -6 and @field(boosts, s) <= 6);
|
|
2054
|
+
if (@field(boosts, s) == 6 or @field(stats, s) == MAX_STAT_VALUE) {
|
|
2055
|
+
try log.boost(ident, r, 0);
|
|
2056
|
+
} else {
|
|
2057
|
+
@field(boosts, s) = @intCast(@min(6, @as(i8, @field(boosts, s)) + 1));
|
|
2058
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
|
|
2059
|
+
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2060
|
+
@field(stats, s) = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2061
|
+
try log.boost(ident, r, 1);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
},
|
|
2065
|
+
else => unreachable,
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
statusModify(side.stored().status, stats);
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
pub fn unboost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2072
|
+
var log = options.log;
|
|
2073
|
+
var foe = battle.foe(player);
|
|
2074
|
+
const foe_ident = battle.active(player.foe());
|
|
2075
|
+
|
|
2076
|
+
if (foe.active.volatiles.Mist) {
|
|
2077
|
+
try log.activate(foe_ident, .Mist);
|
|
2078
|
+
return try log.fail(foe_ident, .None);
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
const fail = foe.active.volatiles.Substitute or state.miss or !state.proc;
|
|
2082
|
+
|
|
2083
|
+
var stats = &foe.active.stats;
|
|
2084
|
+
var boosts = &foe.active.boosts;
|
|
2085
|
+
|
|
2086
|
+
const move = Move.get(state.move);
|
|
2087
|
+
switch (move.effect) {
|
|
2088
|
+
.AttackDown1, .AttackDown2, .AttackDownChance => {
|
|
2089
|
+
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2090
|
+
if (fail or boosts.atk == -6) {
|
|
2091
|
+
return if (move.effect != .AttackDownChance) try log.fail(foe_ident, .None);
|
|
2092
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2093
|
+
try options.log.lastmiss();
|
|
2094
|
+
try options.log.miss(battle.active(player));
|
|
2095
|
+
return;
|
|
2096
|
+
} else if (stats.atk == 1) {
|
|
2097
|
+
return if (move.effect != .AttackDownChance) try log.fail(foe_ident, .None);
|
|
2098
|
+
}
|
|
2099
|
+
const n: u2 = if (move.effect == .AttackDown2) 2 else 1;
|
|
2100
|
+
boosts.atk = @intCast(@max(-6, @as(i8, boosts.atk) - n));
|
|
2101
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2102
|
+
const stat = unmodifiedStats(battle, foe).atk;
|
|
2103
|
+
stats.atk = @max(1, stat * mod[0] / mod[1]);
|
|
2104
|
+
try log.boost(foe_ident, .Attack, -@as(i8, n));
|
|
2105
|
+
},
|
|
2106
|
+
.DefenseDown1, .DefenseDown2, .DefenseDownChance => {
|
|
2107
|
+
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2108
|
+
if (fail or boosts.def == -6) {
|
|
2109
|
+
return if (move.effect != .DefenseDownChance) try log.fail(foe_ident, .None);
|
|
2110
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2111
|
+
try options.log.lastmiss();
|
|
2112
|
+
try options.log.miss(battle.active(player));
|
|
2113
|
+
return;
|
|
2114
|
+
} else if (stats.def == 1) {
|
|
2115
|
+
return if (move.effect != .DefenseDownChance) try log.fail(foe_ident, .None);
|
|
2116
|
+
}
|
|
2117
|
+
const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
|
|
2118
|
+
boosts.def = @intCast(@max(-6, @as(i8, boosts.def) - n));
|
|
2119
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2120
|
+
const stat = unmodifiedStats(battle, foe).def;
|
|
2121
|
+
stats.def = @max(1, stat * mod[0] / mod[1]);
|
|
2122
|
+
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2123
|
+
},
|
|
2124
|
+
.SpeedDown1, .SpeedDown2, .SpeedDownChance => {
|
|
2125
|
+
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2126
|
+
if (fail or boosts.spe == -6) {
|
|
2127
|
+
return if (move.effect != .SpeedDownChance) try log.fail(foe_ident, .None);
|
|
2128
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2129
|
+
try options.log.lastmiss();
|
|
2130
|
+
try options.log.miss(battle.active(player));
|
|
2131
|
+
return;
|
|
2132
|
+
} else if (stats.spe == 1) {
|
|
2133
|
+
return if (move.effect != .SpeedDownChance) try log.fail(foe_ident, .None);
|
|
2134
|
+
}
|
|
2135
|
+
const n: u2 = if (move.effect == .SpeedDown2) 2 else 1;
|
|
2136
|
+
boosts.spe = @intCast(@max(-6, @as(i8, boosts.spe) - 1));
|
|
2137
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2138
|
+
const stat = unmodifiedStats(battle, foe).spe;
|
|
2139
|
+
stats.spe = @max(1, stat * mod[0] / mod[1]);
|
|
2140
|
+
try log.boost(foe_ident, .Speed, -@as(i8, n));
|
|
2141
|
+
assert(boosts.spe >= -6);
|
|
2142
|
+
},
|
|
2143
|
+
.SpDefDownChance => {
|
|
2144
|
+
assert(boosts.spd >= -6 and boosts.spd <= 6);
|
|
2145
|
+
if (fail or boosts.spd == -6) return;
|
|
2146
|
+
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2147
|
+
try options.log.lastmiss();
|
|
2148
|
+
try options.log.miss(battle.active(player));
|
|
2149
|
+
return;
|
|
2150
|
+
} else if (stats.spd == 1) return;
|
|
2151
|
+
boosts.spd = @intCast(@max(-6, @as(i8, boosts.spd) - 1));
|
|
2152
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.spd) + 6))];
|
|
2153
|
+
const stat = unmodifiedStats(battle, foe).spd;
|
|
2154
|
+
stats.spd = @max(1, stat * mod[0] / mod[1]);
|
|
2155
|
+
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2156
|
+
},
|
|
2157
|
+
.AccuracyDown1, .AccuracyDownChance => {
|
|
2158
|
+
assert(boosts.accuracy >= -6 and boosts.accuracy <= 6);
|
|
2159
|
+
if (fail or boosts.accuracy == -6) {
|
|
2160
|
+
return if (move.effect != .AccuracyDownChance) try log.fail(foe_ident, .None);
|
|
2161
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2162
|
+
try options.log.lastmiss();
|
|
2163
|
+
try options.log.miss(battle.active(player));
|
|
2164
|
+
return;
|
|
2165
|
+
}
|
|
2166
|
+
boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
|
|
2167
|
+
try log.boost(foe_ident, .Accuracy, -1);
|
|
2168
|
+
},
|
|
2169
|
+
else => unreachable,
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
statusModify(foe.stored().status, stats);
|
|
2173
|
+
}
|
|
2174
|
+
};
|
|
2175
|
+
|
|
2176
|
+
fn unmodifiedStats(battle: anytype, side: *Side) *Stats(u16) {
|
|
2177
|
+
if (!side.active.volatiles.Transform) return &side.stored().stats;
|
|
2178
|
+
const id = ID.from(side.active.volatiles.transform);
|
|
2179
|
+
return &battle.side(id.player).pokemon[id.id - 1].stats;
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
2183
|
+
if (Status.is(status, .PAR)) {
|
|
2184
|
+
stats.spe = @max(stats.spe / 4, 1);
|
|
2185
|
+
} else if (Status.is(status, .BRN)) {
|
|
2186
|
+
stats.atk = @max(stats.atk / 2, 1);
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
inline fn isForced(active: anytype) bool {
|
|
2191
|
+
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
2192
|
+
active.volatiles.Thrashing or active.volatiles.Charging or
|
|
2193
|
+
active.volatiles.Rollout;
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
inline fn convertible(active: ActivePokemon, m: Move) bool {
|
|
2197
|
+
if (m == .None) return false;
|
|
2198
|
+
const t = Move.get(m).type;
|
|
2199
|
+
return t != .@"???" and !active.types.includes(t);
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
pub const Rolls = struct {
|
|
2203
|
+
inline fn speedTie(battle: anytype, options: anytype) !bool {
|
|
2204
|
+
const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
|
|
2205
|
+
player == .P1
|
|
2206
|
+
else if (showdown)
|
|
2207
|
+
battle.rng.range(u8, 0, 2) == 0
|
|
2208
|
+
else
|
|
2209
|
+
battle.rng.next() < Gen12.percent(50) + 1;
|
|
2210
|
+
|
|
2211
|
+
try options.chance.speedTie(p1);
|
|
2212
|
+
return p1;
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
inline fn quickClaw(battle: anytype, player: Player, options: anytype) !bool {
|
|
2216
|
+
const qkc = if (options.calc.overridden(player, .quick_claw)) |val|
|
|
2217
|
+
val == .true
|
|
2218
|
+
else if (showdown)
|
|
2219
|
+
battle.rng.chance(u8, 60, 256)
|
|
2220
|
+
else
|
|
2221
|
+
battle.rng.next() < 60;
|
|
2222
|
+
|
|
2223
|
+
try options.chance.quickClaw(player, qkc);
|
|
2224
|
+
return qkc;
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
inline fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) !bool {
|
|
2228
|
+
const ok = if (options.calc.overridden(player, .hit)) |val|
|
|
2229
|
+
val == .true
|
|
2230
|
+
else if (showdown)
|
|
2231
|
+
battle.rng.chance(u8, accuracy, 256)
|
|
2232
|
+
else
|
|
2233
|
+
battle.rng.next() < accuracy;
|
|
2234
|
+
|
|
2235
|
+
try options.chance.hit(player, ok, accuracy);
|
|
2236
|
+
return ok;
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
inline fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
|
|
2240
|
+
const crit = if (options.calc.overridden(player, .critical_hit)) |val|
|
|
2241
|
+
val == .true
|
|
2242
|
+
else if (showdown)
|
|
2243
|
+
battle.rng.chance(u8, rate, 256)
|
|
2244
|
+
else
|
|
2245
|
+
battle.rng.next() < rate;
|
|
2246
|
+
|
|
2247
|
+
try options.chance.criticalHit(player, crit, rate);
|
|
2248
|
+
return crit;
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
inline fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2252
|
+
const roll = if (options.calc.overridden(player, .damage)) |val|
|
|
2253
|
+
val
|
|
2254
|
+
else roll: {
|
|
2255
|
+
if (showdown) break :roll battle.rng.range(u8, 217, 256);
|
|
2256
|
+
while (true) {
|
|
2257
|
+
const r = std.math.rotr(u8, battle.rng.next(), 1);
|
|
2258
|
+
if (r >= 217) break :roll r;
|
|
1223
2259
|
}
|
|
1224
2260
|
};
|
|
1225
2261
|
|
|
1226
|
-
assert(
|
|
1227
|
-
options.chance.
|
|
1228
|
-
return
|
|
2262
|
+
assert(roll >= 217 and roll <= 255);
|
|
2263
|
+
try options.chance.damage(player, roll);
|
|
2264
|
+
return roll;
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
inline fn confused(battle: anytype, player: Player, options: anytype) !bool {
|
|
2268
|
+
const cfz = if (options.calc.overridden(player, .confused)) |val|
|
|
2269
|
+
val == .true
|
|
2270
|
+
else if (showdown)
|
|
2271
|
+
!battle.rng.chance(u8, 128, 256)
|
|
2272
|
+
else
|
|
2273
|
+
battle.rng.next() >= Gen12.percent(50) + 1;
|
|
2274
|
+
|
|
2275
|
+
try options.chance.confused(player, cfz);
|
|
2276
|
+
return cfz;
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2279
|
+
inline fn attract(battle: anytype, player: Player, options: anytype) !bool {
|
|
2280
|
+
const cant = if (options.calc.overridden(player, .attract)) |val|
|
|
2281
|
+
val == .true
|
|
2282
|
+
else if (showdown)
|
|
2283
|
+
battle.rng.chance(u8, 1, 2)
|
|
2284
|
+
else
|
|
2285
|
+
battle.rng.next() > Gen12.percent(50) + 1;
|
|
2286
|
+
|
|
2287
|
+
try options.chance.attract(player, cant);
|
|
2288
|
+
return cant;
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
inline fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
|
|
2292
|
+
const par = if (options.calc.overridden(player, .paralyzed)) |val|
|
|
2293
|
+
val == .true
|
|
2294
|
+
else if (showdown)
|
|
2295
|
+
battle.rng.chance(u8, 63, 256)
|
|
2296
|
+
else
|
|
2297
|
+
battle.rng.next() < Gen12.percent(25);
|
|
2298
|
+
|
|
2299
|
+
try options.chance.paralyzed(player, par);
|
|
2300
|
+
return par;
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
inline fn defrost(battle: anytype, player: Player, options: anytype) !bool {
|
|
2304
|
+
const thaw = if (options.calc.overridden(player, .defrost)) |val|
|
|
2305
|
+
val == .true
|
|
2306
|
+
else if (showdown)
|
|
2307
|
+
battle.rng.chance(u8, 25, 256)
|
|
2308
|
+
else
|
|
2309
|
+
battle.rng.next() < Gen12.percent(10);
|
|
2310
|
+
|
|
2311
|
+
try options.chance.defrost(player, thaw);
|
|
2312
|
+
return thaw;
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
inline fn secondaryChance(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
|
|
2316
|
+
// GLITCH: 100% secondary chance will still fail 1/256 of the time
|
|
2317
|
+
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
2318
|
+
val == .true
|
|
2319
|
+
else if (showdown)
|
|
2320
|
+
battle.rng.chance(u8, rate, 256)
|
|
2321
|
+
else
|
|
2322
|
+
battle.rng.next() < rate;
|
|
2323
|
+
|
|
2324
|
+
try options.chance.secondaryChance(player, proc, rate);
|
|
2325
|
+
return proc;
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
inline fn item(battle: anytype, player: Player, options: anytype) !bool {
|
|
2329
|
+
const proc = if (options.calc.overridden(player, .item)) |val|
|
|
2330
|
+
val == .true
|
|
2331
|
+
else if (showdown)
|
|
2332
|
+
battle.rng.chance(u8, 30, 256)
|
|
2333
|
+
else
|
|
2334
|
+
battle.rng.next() < 30;
|
|
2335
|
+
|
|
2336
|
+
try options.chance.item(player, proc);
|
|
2337
|
+
return proc;
|
|
1229
2338
|
}
|
|
1230
2339
|
|
|
1231
2340
|
inline fn triAttack(battle: anytype, player: Player, options: anytype) !u8 {
|
|
@@ -1236,7 +2345,7 @@ pub const Rolls = struct {
|
|
|
1236
2345
|
else loop: {
|
|
1237
2346
|
while (true) {
|
|
1238
2347
|
const r = std.math.rotr(u8, std.battle.rng.next(), 4) & 3;
|
|
1239
|
-
if (r != 0) break :loop @
|
|
2348
|
+
if (r != 0) break :loop @enumFromInt(r - 1);
|
|
1240
2349
|
}
|
|
1241
2350
|
};
|
|
1242
2351
|
|
|
@@ -1294,11 +2403,11 @@ pub const Rolls = struct {
|
|
|
1294
2403
|
return num;
|
|
1295
2404
|
}
|
|
1296
2405
|
|
|
1297
|
-
inline fn tripleKick(battle: anytype, player: Player, options: anytype) !
|
|
2406
|
+
inline fn tripleKick(battle: anytype, player: Player, options: anytype) !u2 {
|
|
1298
2407
|
const hits = if (options.calc.overridden(player, .triple_kick)) |val|
|
|
1299
2408
|
val
|
|
1300
2409
|
else if (showdown)
|
|
1301
|
-
battle.rng.range(
|
|
2410
|
+
battle.rng.range(u2, 1, 4)
|
|
1302
2411
|
else power: {
|
|
1303
2412
|
while (true) {
|
|
1304
2413
|
const r = battle.rng.next() & 3;
|
|
@@ -1325,7 +2434,7 @@ pub const Rolls = struct {
|
|
|
1325
2434
|
}
|
|
1326
2435
|
|
|
1327
2436
|
// Conversion 2 can at most choose between 7 types (vs. a Grass-type attack)
|
|
1328
|
-
|
|
2437
|
+
var CONVERSION_2: [7]Type = [_]Type{.@"???"} ** 7;
|
|
1329
2438
|
|
|
1330
2439
|
inline fn conversion2(battle: anytype, player: Player, mtype: Type, options: anytype) !Type {
|
|
1331
2440
|
var i: u8 = 0;
|
|
@@ -1349,7 +2458,7 @@ pub const Rolls = struct {
|
|
|
1349
2458
|
if (r > 9 and r < 20) continue; // UNUSED_TYPES
|
|
1350
2459
|
if (r > 27) continue; // TYPES_END
|
|
1351
2460
|
|
|
1352
|
-
const t =
|
|
2461
|
+
const t = Type.conversion2(r);
|
|
1353
2462
|
if (@intFromEnum(mtype.effectiveness(t)) < neutral) {
|
|
1354
2463
|
break :ty t;
|
|
1355
2464
|
}
|
|
@@ -1361,47 +2470,299 @@ pub const Rolls = struct {
|
|
|
1361
2470
|
return ty;
|
|
1362
2471
|
}
|
|
1363
2472
|
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
2473
|
+
inline fn protect(battle: anytype, player: Player, count: u8, options: anytype) !bool {
|
|
2474
|
+
assert(count <= 8);
|
|
2475
|
+
const num = if (count == 8) 0 else @as(u8, 255) >> @intCast(count);
|
|
2476
|
+
|
|
2477
|
+
const ok = if (options.calc.overridden(player, .protect)) |val|
|
|
2478
|
+
val == .true
|
|
2479
|
+
else if (showdown)
|
|
2480
|
+
battle.rng.chance(u8, num, 255)
|
|
2481
|
+
else protected: {
|
|
2482
|
+
while (true) {
|
|
2483
|
+
const r = battle.rng.next();
|
|
2484
|
+
if (r != 0) break :protected !(num <= r - 1);
|
|
2485
|
+
}
|
|
2486
|
+
};
|
|
2487
|
+
|
|
2488
|
+
try options.chance.protect(player, num, ok);
|
|
2489
|
+
return ok;
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
fn conversion(battle: anytype, player: Player, n: u4, options: anytype) !Type {
|
|
2493
|
+
assert(n > 0);
|
|
2494
|
+
|
|
2495
|
+
const active = battle.side(player).active;
|
|
2496
|
+
const moves = active.moves;
|
|
2497
|
+
|
|
2498
|
+
// TODO: consider throwing error instead of rerolling?
|
|
2499
|
+
const overridden = if (options.calc.overridden(player, .move_slot)) |val|
|
|
2500
|
+
if (convertible(active, moves[val - 1].id)) val else null
|
|
2501
|
+
else
|
|
2502
|
+
null;
|
|
2503
|
+
|
|
2504
|
+
const slot: u4 = overridden orelse slot: {
|
|
2505
|
+
if (showdown) {
|
|
2506
|
+
var r = battle.rng.range(u4, 0, n) + 1;
|
|
2507
|
+
var i: usize = 0;
|
|
2508
|
+
while (i < moves.len and r > 0) : (i += 1) {
|
|
2509
|
+
if (convertible(active, moves[i].id)) {
|
|
2510
|
+
r -= 1;
|
|
2511
|
+
if (r == 0) break :slot @intCast(i + 1);
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
break :slot @intCast(i + 1);
|
|
2515
|
+
} else {
|
|
2516
|
+
while (true) {
|
|
2517
|
+
const r: u4 = @intCast(battle.rng.next() & 3);
|
|
2518
|
+
if (convertible(active, moves[r].id)) break :slot @intCast(r + 1);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
};
|
|
2522
|
+
|
|
2523
|
+
assert(n >= 1 and n <= 4);
|
|
2524
|
+
assert(slot >= 1 and slot <= 4);
|
|
2525
|
+
try options.chance.moveSlot(player, slot, n);
|
|
2526
|
+
return Move.get(active.move(slot).id).type;
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
fn sleepTalk(battle: anytype, player: Player, n: u4, mslot: u4, options: anytype) !u4 {
|
|
2530
|
+
assert(n > 0);
|
|
2531
|
+
|
|
2532
|
+
const active = battle.side(player).active;
|
|
2533
|
+
const moves = active.moves;
|
|
2534
|
+
|
|
2535
|
+
// TODO: consider throwing error instead of rerolling?
|
|
2536
|
+
const overridden = if (options.calc.overridden(player, .move_slot)) |val|
|
|
2537
|
+
if (val != mslot and Move.get(moves[val - 1].id).extra.sleep_talk) val else null
|
|
2538
|
+
else
|
|
2539
|
+
null;
|
|
2540
|
+
|
|
2541
|
+
const slot: u4 = overridden orelse slot: {
|
|
2542
|
+
if (showdown) {
|
|
2543
|
+
var r = battle.rng.range(u4, 0, n) + 1;
|
|
2544
|
+
var i: usize = 0;
|
|
2545
|
+
while (i < moves.len and r > 0) : (i += 1) {
|
|
2546
|
+
if (i + 1 != mslot and Move.get(moves[i].id).extra.sleep_talk) {
|
|
2547
|
+
r -= 1;
|
|
2548
|
+
if (r == 0) break :slot @intCast(i + 1);
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
break :slot @intCast(i + 1);
|
|
2552
|
+
} else {
|
|
2553
|
+
while (true) {
|
|
2554
|
+
const r: u4 = @intCast(battle.rng.next() & 3);
|
|
2555
|
+
if (r + 1 == mslot or !Move.get(moves[r].id).extra.sleep_talk) continue;
|
|
2556
|
+
break :slot @intCast(r + 1);
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
};
|
|
2560
|
+
|
|
2561
|
+
assert(n >= 1 and n <= 4);
|
|
2562
|
+
assert(slot >= 1 and slot <= 4);
|
|
2563
|
+
try options.chance.moveSlot(player, slot, n);
|
|
2564
|
+
return slot;
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
inline fn forceSwitch(
|
|
2568
|
+
battle: anytype,
|
|
2569
|
+
player: Player,
|
|
2570
|
+
slots: []u4,
|
|
2571
|
+
n: u4,
|
|
2572
|
+
options: anytype,
|
|
2573
|
+
) !u4 {
|
|
2574
|
+
const foe = battle.foe(player);
|
|
2575
|
+
|
|
2576
|
+
assert(slots.len < 5);
|
|
2577
|
+
assert(n > 0 and n <= 6);
|
|
2578
|
+
|
|
2579
|
+
// TODO: consider throwing error instead of rerolling?
|
|
2580
|
+
const overridden = if (options.calc.overridden(player, .force_switch)) |val| val: {
|
|
2581
|
+
for (slots) |slot| if (slot == val) break :val if (foe.get(slot).hp > 0) val else null;
|
|
2582
|
+
break :val null;
|
|
2583
|
+
} else null;
|
|
2584
|
+
|
|
2585
|
+
const slot: u4 = overridden orelse slot: {
|
|
2586
|
+
if (showdown) {
|
|
2587
|
+
break :slot slots[battle.rng.range(u4, 0, @as(u4, @intCast(slots.len)))];
|
|
2588
|
+
} else {
|
|
2589
|
+
while (true) {
|
|
2590
|
+
const r = battle.rng.next() & 7;
|
|
2591
|
+
if (r > n or r == 1) continue;
|
|
2592
|
+
if (foe.get(r).hp > 0) break :slot @intCast(r);
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
};
|
|
2596
|
+
|
|
2597
|
+
assert(n >= 1 and n <= 6);
|
|
2598
|
+
assert(slot >= 1 and slot <= 6);
|
|
2599
|
+
try options.chance.forceSwitch(player, slot, n);
|
|
2600
|
+
return slot;
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
2604
|
+
|
|
2605
|
+
inline fn multiHit(battle: anytype, player: Player, options: anytype) !u3 {
|
|
2606
|
+
const n: u3 = if (options.calc.overridden(player, .multi_hit)) |val|
|
|
2607
|
+
@intCast(val)
|
|
2608
|
+
else if (showdown)
|
|
2609
|
+
DISTRIBUTION[battle.rng.range(u3, 0, DISTRIBUTION.len)]
|
|
2610
|
+
else n: {
|
|
2611
|
+
const r = (battle.rng.next() & 3);
|
|
2612
|
+
break :n @intCast((if (r < 2) r else battle.rng.next() & 3) + 2);
|
|
2613
|
+
};
|
|
2614
|
+
|
|
2615
|
+
assert(n >= 2 and n <= 5);
|
|
2616
|
+
try options.chance.multiHit(player, n);
|
|
2617
|
+
return n;
|
|
2618
|
+
}
|
|
1375
2619
|
|
|
1376
2620
|
inline fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
|
|
1377
2621
|
const power = if (options.calc.overridden(player, .psywave)) |val|
|
|
1378
|
-
val
|
|
2622
|
+
val
|
|
1379
2623
|
else if (showdown)
|
|
1380
|
-
battle.rng.range(u8,
|
|
2624
|
+
battle.rng.range(u8, 1, max)
|
|
1381
2625
|
else power: {
|
|
1382
2626
|
while (true) {
|
|
1383
2627
|
const r = battle.rng.next();
|
|
1384
|
-
if (r < max) break :power r;
|
|
2628
|
+
if (r != 0 and r < max) break :power r;
|
|
1385
2629
|
}
|
|
1386
2630
|
};
|
|
1387
2631
|
|
|
1388
|
-
assert(power < max);
|
|
2632
|
+
assert(power != 0 and power < max);
|
|
1389
2633
|
try options.chance.psywave(player, power, max);
|
|
1390
2634
|
return power;
|
|
1391
2635
|
}
|
|
1392
2636
|
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
}
|
|
2637
|
+
inline fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
2638
|
+
const moves = battle.get(player).active.moves;
|
|
2639
|
+
|
|
2640
|
+
var n: u2 = 0;
|
|
2641
|
+
for (moves) |m| {
|
|
2642
|
+
if (m == .None) break;
|
|
2643
|
+
if (Move.get(m).metronome) n += 1;
|
|
1401
2644
|
}
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
2645
|
+
|
|
2646
|
+
// TODO: consider throwing error instead of rerolling?
|
|
2647
|
+
const overridden = if (options.calc.overridden(player, .metronome)) |val| val: {
|
|
2648
|
+
for (moves) |m| if (m.id == val) break :val null;
|
|
2649
|
+
break :val val;
|
|
2650
|
+
} else null;
|
|
2651
|
+
|
|
2652
|
+
const move: Move = overridden orelse move: {
|
|
2653
|
+
if (showdown) {
|
|
2654
|
+
var r = battle.rng.range(u8, 0, Move.METRONOME.len - n);
|
|
2655
|
+
for (moves) |m| {
|
|
2656
|
+
if (m == .None or @intFromEnum(m) > @intFromEnum(Move.METRONOME[r])) break;
|
|
2657
|
+
if (Move.get(m).metronome) r -= 1;
|
|
2658
|
+
}
|
|
2659
|
+
break :move Move.METRONOME[r];
|
|
2660
|
+
} else {
|
|
2661
|
+
outer: while (true) {
|
|
2662
|
+
const r = battle.rng.next();
|
|
2663
|
+
if (r == 0 or r >= Move.size) continue;
|
|
2664
|
+
const selected: Move = @enumFromInt(r);
|
|
2665
|
+
if (!Move.get(selected).extra.metronome) continue;
|
|
2666
|
+
for (moves) |m| if (m.id == selected) continue :outer;
|
|
2667
|
+
break :move selected;
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
};
|
|
2671
|
+
|
|
2672
|
+
assert(Move.get(move).metronome);
|
|
2673
|
+
try options.chance.metronome(player, move, n);
|
|
2674
|
+
return move;
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
inline fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2678
|
+
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2679
|
+
@intCast(val)
|
|
2680
|
+
else if (showdown)
|
|
2681
|
+
battle.rng.range(u3, 1, 7)
|
|
2682
|
+
else duration: {
|
|
2683
|
+
while (true) {
|
|
2684
|
+
const r = battle.rng.next() & 7;
|
|
2685
|
+
if (r != 0 and r != 7) break :duration @intCast(r);
|
|
2686
|
+
}
|
|
2687
|
+
};
|
|
2688
|
+
|
|
2689
|
+
assert(duration >= 1 and duration <= 6);
|
|
2690
|
+
options.chance.duration(.sleep, player, player.foe(), duration);
|
|
2691
|
+
return duration + 1;
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
inline fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
|
|
2695
|
+
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2696
|
+
@intCast(val)
|
|
2697
|
+
else if (showdown)
|
|
2698
|
+
// Pokémon Showdown incorrectly uses the same duration for self-confusion
|
|
2699
|
+
battle.rng.range(u3, 2, 6)
|
|
2700
|
+
else
|
|
2701
|
+
@intCast((battle.rng.next() & if (self) 1 else 3) + 2);
|
|
2702
|
+
|
|
2703
|
+
assert(duration >= 2 and duration <= if (!showdown and self) 3 else 5);
|
|
2704
|
+
options.chance.duration(.confusion, player, if (self) player else player.foe(), duration);
|
|
2705
|
+
return duration;
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
inline fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
|
|
2709
|
+
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2710
|
+
@intCast(val)
|
|
2711
|
+
else if (showdown)
|
|
2712
|
+
// Pokémon Showdown incorrectly inherits Generation III's Disable duration
|
|
2713
|
+
battle.rng.range(u4, 2, 6)
|
|
2714
|
+
else duration: {
|
|
2715
|
+
while (true) {
|
|
2716
|
+
const r = battle.rng.next() & 7;
|
|
2717
|
+
if (r != 0) break :duration @intCast(r + 1);
|
|
2718
|
+
}
|
|
2719
|
+
};
|
|
2720
|
+
|
|
2721
|
+
assert(duration >= 2 and duration <= (if (showdown) 5 else 7));
|
|
2722
|
+
options.chance.duration(.disable, player, player.foe(), duration);
|
|
2723
|
+
return duration;
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
inline fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2727
|
+
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2728
|
+
@intCast(val)
|
|
2729
|
+
else if (showdown)
|
|
2730
|
+
battle.rng.range(u3, 2, 4)
|
|
2731
|
+
else
|
|
2732
|
+
@intCast((battle.rng.next() & 1) + 2); // BUG: thrash is + 1 not +2
|
|
2733
|
+
|
|
2734
|
+
assert(duration >= 2 and duration <= 3);
|
|
2735
|
+
options.chance.duration(.attacking, player, player, duration);
|
|
2736
|
+
return duration;
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
inline fn bindingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2740
|
+
const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
|
|
2741
|
+
@intCast(val)
|
|
2742
|
+
else if (showdown)
|
|
2743
|
+
// TODO: PS does range(3, 6) here which after subtracting 1 is actually 2-4 not 2-5!
|
|
2744
|
+
battle.rng.range(u3, 2, 5)
|
|
2745
|
+
else
|
|
2746
|
+
@intCast(battle.rng.next() & 3 + 2));
|
|
2747
|
+
|
|
2748
|
+
assert(duration >= 2 and duration <= 5);
|
|
2749
|
+
options.chance.duration(.binding, player, player.foe(), duration);
|
|
2750
|
+
return duration + 1;
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
// TODO: consider sharing implementation with bindingDuration
|
|
2754
|
+
inline fn encoreDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2755
|
+
const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
|
|
2756
|
+
@intCast(val)
|
|
2757
|
+
else if (showdown)
|
|
2758
|
+
battle.rng.range(u3, 2, 6)
|
|
2759
|
+
else
|
|
2760
|
+
@intCast(battle.rng.next() & 3 + 2));
|
|
2761
|
+
|
|
2762
|
+
assert(duration >= 2 and duration <= 5);
|
|
2763
|
+
options.chance.duration(.encore, player, player.foe(), duration);
|
|
2764
|
+
return duration + 1;
|
|
2765
|
+
}
|
|
1405
2766
|
};
|
|
1406
2767
|
|
|
1407
2768
|
test "RNG agreement" {
|
|
@@ -1466,6 +2827,28 @@ test "Roll probabilities" {
|
|
|
1466
2827
|
try expectEqualSlices(u8, &.{ 14, 26, 50, 77, 51, 25, 13 }, &magnitude);
|
|
1467
2828
|
}
|
|
1468
2829
|
|
|
2830
|
+
test "Reasons" {
|
|
2831
|
+
const moves = [_]Move{ .Bind, .Wrap, .FireSpin, .Clamp, .Whirlpool };
|
|
2832
|
+
for (moves) |move| {
|
|
2833
|
+
const reason = Move.get(move).extra.protocol - 1;
|
|
2834
|
+
|
|
2835
|
+
const activate: Activate = @enumFromInt(@intFromEnum(Activate.Bind) + reason);
|
|
2836
|
+
try expectEqualStrings(@tagName(move), @tagName(activate));
|
|
2837
|
+
|
|
2838
|
+
const damage: Damage = @enumFromInt(@intFromEnum(Damage.Bind) + reason);
|
|
2839
|
+
try expectEqualStrings(@tagName(move), @tagName(damage));
|
|
2840
|
+
|
|
2841
|
+
const end: End = @enumFromInt(@intFromEnum(End.Bind) + reason);
|
|
2842
|
+
try expectEqualStrings(@tagName(move), @tagName(end));
|
|
2843
|
+
}
|
|
2844
|
+
|
|
2845
|
+
const perish_song = [_]Start{ .PerishSong0, .PerishSong1, .PerishSong2, .PerishSong3 };
|
|
2846
|
+
for (perish_song, 0..) |ps, i| {
|
|
2847
|
+
const reason: Start = @enumFromInt(@intFromEnum(Start.PerishSong0) + i);
|
|
2848
|
+
try expectEqualStrings(@tagName(ps), @tagName(reason));
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
|
|
1469
2852
|
pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Choice) u8 {
|
|
1470
2853
|
var n: u8 = 0;
|
|
1471
2854
|
switch (request) {
|
|
@@ -1544,7 +2927,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
1544
2927
|
if (m.id == if (active.volatiles.Bide) .Bide else side.last_selected_move) {
|
|
1545
2928
|
// Pokémon Showdown displays Struggle if limited to Bide but unable to pick
|
|
1546
2929
|
const struggle =
|
|
1547
|
-
m.id == .Bide and (m.pp == 0 or active.volatiles.
|
|
2930
|
+
m.id == .Bide and (m.pp == 0 or active.volatiles.disable.move == slot);
|
|
1548
2931
|
const s = if (struggle) 0 else slot;
|
|
1549
2932
|
out[n] = .{ .type = .Move, .data = s };
|
|
1550
2933
|
n += 1;
|
|
@@ -1559,7 +2942,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
1559
2942
|
const m = active.moves[slot - 1];
|
|
1560
2943
|
if (m.id == .None) break;
|
|
1561
2944
|
if (m.pp == 0) continue;
|
|
1562
|
-
if (active.volatiles.
|
|
2945
|
+
if (active.volatiles.disable.move == slot) continue;
|
|
1563
2946
|
out[n] = .{ .type = .Move, .data = slot };
|
|
1564
2947
|
n += 1;
|
|
1565
2948
|
}
|