@pkmn/engine 0.1.0-dev.dc33f678 → 0.1.0-dev.dfd46bbb
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.zig +19 -13
- package/package.json +5 -2
- package/src/lib/common/data.zig +4 -4
- package/src/lib/common/protocol.zig +24 -7
- package/src/lib/common/rational.zig +4 -4
- package/src/lib/common/rng.zig +9 -0
- package/src/lib/gen1/calc.zig +2 -2
- package/src/lib/gen1/chance.zig +4 -4
- package/src/lib/gen1/data/moves.zig +10 -10
- package/src/lib/gen1/data/species.zig +2 -2
- package/src/lib/gen1/data/types.zig +5 -5
- package/src/lib/gen1/data.zig +14 -14
- package/src/lib/gen1/helpers.zig +13 -13
- package/src/lib/gen1/mechanics.zig +44 -50
- package/src/lib/gen2/calc.zig +143 -0
- package/src/lib/gen2/chance.zig +1314 -0
- package/src/lib/gen2/data/items.zig +3 -3
- package/src/lib/gen2/data/moves.zig +5 -5
- package/src/lib/gen2/data/species.zig +1 -1
- package/src/lib/gen2/data/types.zig +10 -10
- package/src/lib/gen2/data.zig +17 -27
- package/src/lib/gen2/generated.zig +814 -0
- package/src/lib/gen2/mechanics.zig +1221 -351
|
@@ -9,6 +9,7 @@ const rng = @import("../common/rng.zig");
|
|
|
9
9
|
const util = @import("../common/util.zig");
|
|
10
10
|
|
|
11
11
|
const data = @import("data.zig");
|
|
12
|
+
const generated = @import("generated.zig");
|
|
12
13
|
|
|
13
14
|
const assert = std.debug.assert;
|
|
14
15
|
|
|
@@ -24,6 +25,7 @@ const Player = common.Player;
|
|
|
24
25
|
const Result = common.Result;
|
|
25
26
|
|
|
26
27
|
const Activate = protocol.Activate;
|
|
28
|
+
const Boost = protocol.Boost;
|
|
27
29
|
const Cant = protocol.Cant;
|
|
28
30
|
const Damage = protocol.Damage;
|
|
29
31
|
const End = protocol.End;
|
|
@@ -48,6 +50,7 @@ const TriAttack = data.TriAttack;
|
|
|
48
50
|
const Type = data.Type;
|
|
49
51
|
const Types = data.Types;
|
|
50
52
|
const Volatiles = data.Volatiles;
|
|
53
|
+
const Weather = data.Weather;
|
|
51
54
|
|
|
52
55
|
// zig fmt: off
|
|
53
56
|
const STAT_BOOSTS = &[_][2]u8{
|
|
@@ -93,21 +96,25 @@ const CRITICAL_HITS = &[_]u8{
|
|
|
93
96
|
|
|
94
97
|
const MAX_STAT_VALUE = 999;
|
|
95
98
|
|
|
96
|
-
const State = struct {
|
|
97
|
-
damage: u16 = 0,
|
|
98
|
-
effectiveness: u16 = Effectiveness.neutral,
|
|
99
|
-
bp: u8
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
99
|
+
pub const State = struct {
|
|
100
|
+
damage: u16 = 0, // wCurDamage
|
|
101
|
+
effectiveness: u16 = Effectiveness.neutral, // wTypeModifier
|
|
102
|
+
bp: u8, // wPlayerMoveStructPower
|
|
103
|
+
accuracy: u8, // wEnemyMoveStruct + MOVE_ACC
|
|
104
|
+
move: Move, // wCurPlayerMove
|
|
105
|
+
from: Move = .None,
|
|
106
|
+
mslot: u4, // wCurMoveNum
|
|
107
|
+
first: bool, // wEnemyGoesFirst
|
|
108
|
+
miss: bool = false, // wAttackMissed
|
|
109
|
+
crit: bool = false, // wCriticalHit = 1
|
|
110
|
+
ohko: bool = false, // wCriticalHit = 2
|
|
111
|
+
proc: bool = true, // wEffectFailed
|
|
105
112
|
|
|
106
113
|
comptime {
|
|
107
|
-
assert(@sizeOf(State) ==
|
|
114
|
+
assert(@sizeOf(State) == 14);
|
|
108
115
|
}
|
|
109
116
|
|
|
110
|
-
|
|
117
|
+
pub fn immune(self: *State) bool {
|
|
111
118
|
return self.effectiveness == 0;
|
|
112
119
|
}
|
|
113
120
|
};
|
|
@@ -137,8 +144,8 @@ fn start(battle: anytype, options: anytype) !Result {
|
|
|
137
144
|
assert(!showdown or p2_slot == 1);
|
|
138
145
|
if (p2_slot == 0) return Result.Win;
|
|
139
146
|
|
|
140
|
-
try switchIn(battle, .P1, p1_slot,
|
|
141
|
-
try switchIn(battle, .P2, p2_slot,
|
|
147
|
+
try switchIn(battle, .P1, p1_slot, .Initial, options);
|
|
148
|
+
try switchIn(battle, .P2, p2_slot, .Initial, options);
|
|
142
149
|
|
|
143
150
|
return endTurn(battle, options);
|
|
144
151
|
}
|
|
@@ -212,7 +219,9 @@ fn selectMove(battle: anytype, player: Player, choice: Choice) void {
|
|
|
212
219
|
volatiles.protect = 0;
|
|
213
220
|
}
|
|
214
221
|
|
|
215
|
-
|
|
222
|
+
const SwitchIn = enum { Initial, Switch, Drag };
|
|
223
|
+
|
|
224
|
+
fn switchIn(battle: anytype, player: Player, slot: u8, kind: SwitchIn, options: anytype) !void {
|
|
216
225
|
var side = battle.side(player);
|
|
217
226
|
var foe = battle.foe(player);
|
|
218
227
|
|
|
@@ -220,7 +229,7 @@ fn switchIn(battle: anytype, player: Player, slot: u8, initial: bool, options: a
|
|
|
220
229
|
const incoming = side.get(slot);
|
|
221
230
|
|
|
222
231
|
assert(incoming.hp != 0);
|
|
223
|
-
assert(slot != 1 or
|
|
232
|
+
assert(slot != 1 or kind == .Initial);
|
|
224
233
|
|
|
225
234
|
const out = side.pokemon[0].position;
|
|
226
235
|
side.pokemon[0].position = side.pokemon[slot - 1].position;
|
|
@@ -238,7 +247,11 @@ fn switchIn(battle: anytype, player: Player, slot: u8, initial: bool, options: a
|
|
|
238
247
|
|
|
239
248
|
statusModify(incoming.status, &active.stats);
|
|
240
249
|
|
|
241
|
-
|
|
250
|
+
if (kind == .Drag) {
|
|
251
|
+
try options.log.drag(battle.active(player), incoming);
|
|
252
|
+
} else {
|
|
253
|
+
try options.log.switched(battle.active(player), incoming);
|
|
254
|
+
}
|
|
242
255
|
options.chance.switched(player, side.pokemon[0].position, out);
|
|
243
256
|
|
|
244
257
|
if (side.conditions.Spikes and !active.types.includes(.Flying)) {
|
|
@@ -257,7 +270,13 @@ fn turnOrder(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Player
|
|
|
257
270
|
// TODO: add to pending and only commit roll on Pokémon Showdown if roll is used
|
|
258
271
|
const qkc = showdown and try Rolls.quickClaw(battle, .P1, options);
|
|
259
272
|
|
|
260
|
-
if ((c1.type == .Switch) != (c2.type == .Switch))
|
|
273
|
+
if ((c1.type == .Switch) != (c2.type == .Switch)) {
|
|
274
|
+
const m1 = .Pound; // TODO battle.side(.P1).last_selected_move;
|
|
275
|
+
const m2 = .Pound; // TODO battle.side(.P2).last_selected_move;
|
|
276
|
+
return if (c1.type == .Switch)
|
|
277
|
+
if (m1 == .Pursuit) .P2 else .P1
|
|
278
|
+
else if (m2 == .Pursuit) .P1 else .P2;
|
|
279
|
+
}
|
|
261
280
|
|
|
262
281
|
// https://www.smogon.com/forums/threads/adv-switch-priority.3622189/
|
|
263
282
|
// > In Gen 1 it's irrelevant [which player switches first] because switches happen instantly on
|
|
@@ -337,25 +356,33 @@ fn doTurn(
|
|
|
337
356
|
const volatiles = &battle.side(player).active.volatiles;
|
|
338
357
|
const foe_volatiles = &battle.foe(player).active.volatiles;
|
|
339
358
|
|
|
359
|
+
// Technically this happens at the very beginning of update but its unused until now
|
|
360
|
+
// TODO: move into state and pass into betweenTurns
|
|
361
|
+
volatiles.frozen = false;
|
|
362
|
+
foe_volatiles.frozen = false;
|
|
363
|
+
|
|
340
364
|
volatiles.DestinyBond = false;
|
|
341
365
|
if (try executeMove(battle, player, player_choice, true, options)) |r| return r;
|
|
342
366
|
foe_volatiles.Protect = false;
|
|
343
367
|
foe_volatiles.Endure = false;
|
|
344
368
|
foe_volatiles.DestinyBond = false;
|
|
369
|
+
if (try checkFaint(battle, foe_player, options)) |r| return r;
|
|
370
|
+
if (try handleResidual(battle, player, options)) {
|
|
371
|
+
if (try checkFaint(battle, player, options)) |r| return r;
|
|
372
|
+
}
|
|
345
373
|
|
|
346
374
|
// foe_volatiles.DestinyBond = false;
|
|
347
375
|
if (try executeMove(battle, foe_player, foe_choice, false, options)) |r| return r;
|
|
348
376
|
volatiles.Protect = false;
|
|
349
377
|
volatiles.Endure = false;
|
|
350
378
|
volatiles.DestinyBond = false;
|
|
379
|
+
if (try checkFaint(battle, player, options)) |r| return r;
|
|
380
|
+
if (try handleResidual(battle, foe_player, options)) {
|
|
381
|
+
if (try checkFaint(battle, foe_player, options)) |r| return r;
|
|
382
|
+
}
|
|
351
383
|
|
|
352
384
|
// FIXME
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
// FIXME
|
|
356
|
-
try betweenTurns(battle, 0, options);
|
|
357
|
-
|
|
358
|
-
return null;
|
|
385
|
+
return betweenTurns(battle, 0, options);
|
|
359
386
|
}
|
|
360
387
|
|
|
361
388
|
fn executeMove(
|
|
@@ -366,7 +393,7 @@ fn executeMove(
|
|
|
366
393
|
options: anytype,
|
|
367
394
|
) !?Result {
|
|
368
395
|
if (choice.type == .Switch) {
|
|
369
|
-
try switchIn(battle, player, choice.data,
|
|
396
|
+
try switchIn(battle, player, choice.data, .Switch, options);
|
|
370
397
|
return null;
|
|
371
398
|
}
|
|
372
399
|
|
|
@@ -376,8 +403,12 @@ fn executeMove(
|
|
|
376
403
|
|
|
377
404
|
if (!try beforeMove(battle, player, move, options)) return null;
|
|
378
405
|
|
|
379
|
-
|
|
380
|
-
|
|
406
|
+
// FIXME: need damage from doTurn
|
|
407
|
+
const m = Move.get(move);
|
|
408
|
+
var state: State =
|
|
409
|
+
.{ .move = move, .mslot = mslot, .first = first, .accuracy = m.accuracy, .bp = m.bp };
|
|
410
|
+
try generated.runMove(battle, player, &state, options);
|
|
411
|
+
return null;
|
|
381
412
|
}
|
|
382
413
|
|
|
383
414
|
fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bool {
|
|
@@ -391,7 +422,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
391
422
|
|
|
392
423
|
if (volatiles.Recharging) {
|
|
393
424
|
volatiles.Recharging = false;
|
|
394
|
-
|
|
425
|
+
resetCant(volatiles);
|
|
395
426
|
try log.cant(ident, .Recharge);
|
|
396
427
|
return false;
|
|
397
428
|
}
|
|
@@ -413,18 +444,18 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
413
444
|
if (duration == 0) {
|
|
414
445
|
try log.curestatus(ident, before, .Message);
|
|
415
446
|
stored.status = 0; // clears EXT if present
|
|
416
|
-
|
|
447
|
+
resetCant(volatiles);
|
|
417
448
|
volatiles.Nightmare = false;
|
|
418
449
|
} else {
|
|
419
450
|
try log.cant(ident, .Sleep);
|
|
420
451
|
if (move == .Snore or move == .SleepTalk) break :slp;
|
|
421
|
-
|
|
452
|
+
resetCant(volatiles);
|
|
422
453
|
return false;
|
|
423
454
|
}
|
|
424
455
|
}
|
|
425
456
|
|
|
426
457
|
if (Status.is(stored.status, .FRZ) and !(move == .FlameWheel or move == .SacredFire)) {
|
|
427
|
-
|
|
458
|
+
resetCant(volatiles);
|
|
428
459
|
try log.cant(ident, .Freeze);
|
|
429
460
|
return false;
|
|
430
461
|
}
|
|
@@ -434,7 +465,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
434
465
|
// Pokémon can sometimes flinch multiple times from the same original hit
|
|
435
466
|
// if (!showdown) volatiles.Flinch = false;
|
|
436
467
|
volatiles.Flinch = false;
|
|
437
|
-
|
|
468
|
+
resetCant(volatiles);
|
|
438
469
|
try log.cant(ident, .Flinch);
|
|
439
470
|
return false;
|
|
440
471
|
}
|
|
@@ -472,7 +503,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
472
503
|
if (try Rolls.confused(battle, player, options)) {
|
|
473
504
|
volatiles.BeatUp = false;
|
|
474
505
|
volatiles.Flinch = false;
|
|
475
|
-
|
|
506
|
+
resetCant(volatiles);
|
|
476
507
|
|
|
477
508
|
// TODO HitConfusion
|
|
478
509
|
|
|
@@ -485,7 +516,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
485
516
|
try log.activate(ident, .Attract);
|
|
486
517
|
|
|
487
518
|
if (try Rolls.attract(battle, player, options)) {
|
|
488
|
-
|
|
519
|
+
resetCant(volatiles);
|
|
489
520
|
try log.cant(ident, .Attract);
|
|
490
521
|
return false;
|
|
491
522
|
}
|
|
@@ -496,14 +527,14 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
496
527
|
const m = side.active.moves[side.active.volatiles.disable.move - 1].id;
|
|
497
528
|
if (m != .None and m == move) {
|
|
498
529
|
side.active.volatiles.Charging = false;
|
|
499
|
-
|
|
530
|
+
resetCant(volatiles);
|
|
500
531
|
try options.log.disabled(ident, move);
|
|
501
532
|
return false;
|
|
502
533
|
}
|
|
503
534
|
}
|
|
504
535
|
|
|
505
536
|
if (Status.is(stored.status, .PAR) and try Rolls.paralyzed(battle, player, options)) {
|
|
506
|
-
|
|
537
|
+
resetCant(volatiles);
|
|
507
538
|
try log.cant(ident, .Paralysis);
|
|
508
539
|
return false;
|
|
509
540
|
}
|
|
@@ -511,7 +542,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
511
542
|
return true;
|
|
512
543
|
}
|
|
513
544
|
|
|
514
|
-
fn
|
|
545
|
+
fn resetCant(volatiles: *align(8) Volatiles) void {
|
|
515
546
|
volatiles.Bide = false;
|
|
516
547
|
volatiles.Thrashing = false;
|
|
517
548
|
volatiles.Charging = false;
|
|
@@ -522,14 +553,32 @@ fn cantMove(volatiles: *align(8) Volatiles) void {
|
|
|
522
553
|
volatiles.fury_cutter = 0;
|
|
523
554
|
}
|
|
524
555
|
|
|
525
|
-
fn canMove(battle: anytype, player: Player, options: anytype) !
|
|
556
|
+
pub fn canMove(battle: anytype, player: Player, state: *State, options: anytype) !bool {
|
|
557
|
+
var side = battle.side(player);
|
|
558
|
+
var volatiles = &side.active.volatiles;
|
|
559
|
+
|
|
560
|
+
// FIXME: pass in target to avoid lookup?
|
|
561
|
+
const target = if (Move.get(state.move).target == .Self) player else player.foe();
|
|
562
|
+
try options.log.moveFrom(battle.active(player), state.move, battle.active(target), state.from);
|
|
563
|
+
|
|
564
|
+
const charging = false; // TODO charging uses doUse
|
|
565
|
+
const skip_pp = charging or state.move == .Struggle or
|
|
566
|
+
(volatiles.BeatUp or volatiles.Thrashing or volatiles.Bide);
|
|
567
|
+
if (!skip_pp) _ = decrementPP(side, state.move, state.mslot); // TODO if no pp return
|
|
568
|
+
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
pub fn canCharge(battle: anytype, player: Player, state: *State, options: anytype) !bool {
|
|
526
573
|
_ = battle;
|
|
527
574
|
_ = player;
|
|
575
|
+
_ = state;
|
|
528
576
|
_ = options;
|
|
529
|
-
|
|
577
|
+
// TODO skipsuncharge account for SolarBeam
|
|
578
|
+
return true;
|
|
530
579
|
}
|
|
531
580
|
|
|
532
|
-
fn decrementPP(side: *Side, move: Move, mslot: u4) bool {
|
|
581
|
+
pub fn decrementPP(side: *Side, move: Move, mslot: u4) bool {
|
|
533
582
|
var active = &side.active;
|
|
534
583
|
const volatiles = &active.volatiles;
|
|
535
584
|
|
|
@@ -553,32 +602,41 @@ fn decrementPP(side: *Side, move: Move, mslot: u4) bool {
|
|
|
553
602
|
return true;
|
|
554
603
|
}
|
|
555
604
|
|
|
556
|
-
fn doMove(
|
|
605
|
+
pub fn doMove(
|
|
606
|
+
comptime secondary: bool,
|
|
607
|
+
battle: anytype,
|
|
608
|
+
player: Player,
|
|
609
|
+
state: *State,
|
|
610
|
+
options: anytype,
|
|
611
|
+
) !bool {
|
|
557
612
|
try checkCriticalHit(battle, player, state, options);
|
|
558
613
|
try calcDamage(battle, player, state, options);
|
|
559
614
|
try adjustDamage(battle, player, state, options);
|
|
560
615
|
try randomizeDamage(battle, player, state, options);
|
|
616
|
+
try checkHit(battle, player, state, options);
|
|
617
|
+
if (secondary) try effectChance(battle, player, state, options);
|
|
561
618
|
try applyDamage(battle, player, state, options);
|
|
619
|
+
try reportOutcome(battle, player, state, options);
|
|
620
|
+
if (try destinyBond(battle, player, state, options)) return false;
|
|
562
621
|
try buildRage(battle, player, state, options);
|
|
563
622
|
try kingsRock(battle, player, state, options);
|
|
623
|
+
return true;
|
|
624
|
+
}
|
|
564
625
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
try
|
|
573
|
-
try
|
|
574
|
-
try
|
|
575
|
-
|
|
576
|
-
try Effects.protect(battle, player, state, options);
|
|
577
|
-
|
|
578
|
-
return null;
|
|
626
|
+
pub fn afterMove(
|
|
627
|
+
comptime kings: bool,
|
|
628
|
+
battle: anytype,
|
|
629
|
+
player: Player,
|
|
630
|
+
state: *State,
|
|
631
|
+
options: anytype,
|
|
632
|
+
) !bool {
|
|
633
|
+
if (try destinyBond(battle, player, state, options)) return false;
|
|
634
|
+
try buildRage(battle, player, state, options);
|
|
635
|
+
if (kings) try kingsRock(battle, player, state, options);
|
|
636
|
+
return true;
|
|
579
637
|
}
|
|
580
638
|
|
|
581
|
-
fn checkCriticalHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
639
|
+
pub fn checkCriticalHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
582
640
|
const move = Move.get(state.move);
|
|
583
641
|
if (move.bp == 0) return;
|
|
584
642
|
|
|
@@ -598,7 +656,7 @@ fn checkCriticalHit(battle: anytype, player: Player, state: *State, options: any
|
|
|
598
656
|
state.crit = try Rolls.criticalHit(battle, player, CRITICAL_HITS[stage], options);
|
|
599
657
|
}
|
|
600
658
|
|
|
601
|
-
fn calcDamage(
|
|
659
|
+
pub fn calcDamage(
|
|
602
660
|
battle: anytype,
|
|
603
661
|
player: Player,
|
|
604
662
|
state: *State,
|
|
@@ -612,7 +670,7 @@ fn calcDamage(
|
|
|
612
670
|
|
|
613
671
|
const FORESIGHT: Types = .{ .type1 = .Normal, .type2 = .Fighting };
|
|
614
672
|
|
|
615
|
-
fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
673
|
+
pub fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
616
674
|
if (state.move == .Struggle) return;
|
|
617
675
|
|
|
618
676
|
const side = battle.side(player);
|
|
@@ -662,7 +720,7 @@ fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype) !voi
|
|
|
662
720
|
state.damage = d;
|
|
663
721
|
}
|
|
664
722
|
|
|
665
|
-
fn randomizeDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
723
|
+
pub fn randomizeDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
666
724
|
if (state.damage <= 1) return;
|
|
667
725
|
const random = try Rolls.damage(battle, player, options);
|
|
668
726
|
options.calc.base(player.foe(), state.damage);
|
|
@@ -670,12 +728,13 @@ fn randomizeDamage(battle: anytype, player: Player, state: *State, options: anyt
|
|
|
670
728
|
options.calc.final(player.foe(), state.damage);
|
|
671
729
|
}
|
|
672
730
|
|
|
673
|
-
fn applyDamage(
|
|
731
|
+
pub fn applyDamage(
|
|
674
732
|
battle: anytype,
|
|
675
733
|
player: Player,
|
|
676
734
|
state: *State,
|
|
677
735
|
options: anytype,
|
|
678
736
|
) !void {
|
|
737
|
+
// TODO failuretext (if not Bide) + applydamage
|
|
679
738
|
_ = battle;
|
|
680
739
|
_ = player;
|
|
681
740
|
_ = state;
|
|
@@ -683,7 +742,7 @@ fn applyDamage(
|
|
|
683
742
|
}
|
|
684
743
|
|
|
685
744
|
// TODO: set damage = 0 if missed and not (High) Jump Kick
|
|
686
|
-
fn checkHit(
|
|
745
|
+
pub fn checkHit(
|
|
687
746
|
battle: anytype,
|
|
688
747
|
player: Player,
|
|
689
748
|
state: *State,
|
|
@@ -708,7 +767,7 @@ fn checkHit(
|
|
|
708
767
|
return;
|
|
709
768
|
}
|
|
710
769
|
if (side.active.volatiles.LockOn) {
|
|
711
|
-
state.miss = !(foe.active.volatiles.Flying and move.extra.
|
|
770
|
+
state.miss = !(foe.active.volatiles.Flying and move.extra.underground);
|
|
712
771
|
return;
|
|
713
772
|
}
|
|
714
773
|
assert(!(foe.active.volatiles.Flying and foe.active.volatiles.Underground));
|
|
@@ -723,16 +782,23 @@ fn checkHit(
|
|
|
723
782
|
return;
|
|
724
783
|
}
|
|
725
784
|
}
|
|
726
|
-
if (move.effect == .Thunder
|
|
727
|
-
|
|
728
|
-
|
|
785
|
+
if (move.effect == .Thunder) {
|
|
786
|
+
if (battle.field.weather == .Rain) {
|
|
787
|
+
state.miss = false;
|
|
788
|
+
return;
|
|
789
|
+
} else if (battle.field.weather == .Sun) {
|
|
790
|
+
// This latter part gets applied in a separate Thunder accuracy handler
|
|
791
|
+
// on the cartridge but it makes more sense to just do it here
|
|
792
|
+
state.accuracy = Gen12.percent(50) + 1;
|
|
793
|
+
}
|
|
729
794
|
}
|
|
730
795
|
if (move.effect == .AlwaysHit) {
|
|
731
796
|
state.miss = false;
|
|
732
797
|
return;
|
|
733
798
|
}
|
|
734
799
|
|
|
735
|
-
var accuracy: u16 =
|
|
800
|
+
var accuracy: u16 = state.accuracy;
|
|
801
|
+
assert(accuracy > 0);
|
|
736
802
|
if (foe.active.boosts.evasion <= side.active.boosts.accuracy or
|
|
737
803
|
!foe.active.volatiles.Foresight)
|
|
738
804
|
{
|
|
@@ -757,24 +823,85 @@ fn checkHit(
|
|
|
757
823
|
fn checkFaint(
|
|
758
824
|
battle: anytype,
|
|
759
825
|
player: Player,
|
|
760
|
-
state: *State,
|
|
761
826
|
options: anytype,
|
|
762
827
|
) @TypeOf(options.log).Error!?Result {
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
828
|
+
const side = battle.side(player);
|
|
829
|
+
const foe = battle.foe(player);
|
|
830
|
+
|
|
831
|
+
const player_fainted = side.stored().hp == 0;
|
|
832
|
+
const foe_fainted = foe.stored().hp == 0;
|
|
833
|
+
if (!(player_fainted or foe_fainted)) return null;
|
|
834
|
+
|
|
835
|
+
const player_out = player_fainted and findFirstAlive(side) == 0;
|
|
836
|
+
const foe_out = foe_fainted and findFirstAlive(foe) == 0;
|
|
837
|
+
const more = player_out or foe_out;
|
|
838
|
+
|
|
839
|
+
if (player_fainted) try faint(battle, player, !(more or foe_fainted), options);
|
|
840
|
+
if (foe_fainted) try faint(battle, player.foe(), !more, options);
|
|
841
|
+
|
|
842
|
+
if (player_out and foe_out) {
|
|
843
|
+
try options.log.tie();
|
|
844
|
+
return Result.Tie;
|
|
845
|
+
} else if (player_out) {
|
|
846
|
+
try options.log.win(player.foe());
|
|
847
|
+
return if (player == .P1) Result.Lose else Result.Win;
|
|
848
|
+
} else if (foe_out) {
|
|
849
|
+
try options.log.win(player);
|
|
850
|
+
return if (player == .P1) Result.Win else Result.Lose;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const foe_choice: Choice.Type = if (foe_fainted) .Switch else .Pass;
|
|
854
|
+
if (player == .P1) return .{ .p1 = .Switch, .p2 = foe_choice };
|
|
855
|
+
return .{ .p1 = foe_choice, .p2 = .Switch };
|
|
767
856
|
}
|
|
768
857
|
|
|
769
|
-
fn faint(battle: anytype, player: Player, done: bool, options: anytype)
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
858
|
+
fn faint(battle: anytype, player: Player, done: bool, options: anytype) !void {
|
|
859
|
+
var side = battle.side(player);
|
|
860
|
+
var foe = battle.foe(player);
|
|
861
|
+
assert(side.stored().hp == 0);
|
|
862
|
+
|
|
863
|
+
var foe_volatiles = &foe.active.volatiles;
|
|
864
|
+
foe_volatiles.BeatUp = false;
|
|
865
|
+
|
|
866
|
+
// We always need to clear status for Pokémon Showdown's Sleep/Freeze Clause Mod
|
|
867
|
+
const status = side.stored().status;
|
|
868
|
+
|
|
869
|
+
side.stored().status = 0;
|
|
870
|
+
// This shouldn't matter, but Pokémon Showdown decides double switching priority based on speed,
|
|
871
|
+
// and resets a Pokémon's stats when it faints... only it still factors in paralysis -_-
|
|
872
|
+
if (showdown) {
|
|
873
|
+
side.active.stats.spe = if (Status.is(status, .PAR))
|
|
874
|
+
@max(side.stored().stats.spe / 4, 1)
|
|
875
|
+
else
|
|
876
|
+
side.stored().stats.spe;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
try options.log.faint(battle.active(player), done);
|
|
880
|
+
options.calc.capped(player);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// TODO: see if we can move *before* applyDamage and include failuretext logic
|
|
884
|
+
pub fn reportOutcome(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
885
|
+
const foe_ident = battle.active(player.foe());
|
|
886
|
+
if (state.crit) try options.log.crit(foe_ident);
|
|
887
|
+
if (!state.immune()) {
|
|
888
|
+
if (state.effectiveness > Effectiveness.neutral) {
|
|
889
|
+
try options.log.supereffective(foe_ident);
|
|
890
|
+
} else if (state.effectiveness < Effectiveness.neutral) {
|
|
891
|
+
try options.log.resisted(foe_ident);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
pub fn effectChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
897
|
+
const rate = Move.get(state.move).chance;
|
|
898
|
+
assert(rate > 0);
|
|
899
|
+
// FIXME: only commit secondary chance roll if effect actually is successful (doesn't fail)
|
|
900
|
+
state.proc = !battle.foe(player).active.volatiles.Substitute and
|
|
901
|
+
try Rolls.secondaryChance(battle, player, rate, options);
|
|
775
902
|
}
|
|
776
903
|
|
|
777
|
-
fn handleResidual(battle: anytype, player: Player, options: anytype) !
|
|
904
|
+
fn handleResidual(battle: anytype, player: Player, options: anytype) !bool {
|
|
778
905
|
var side = battle.side(player);
|
|
779
906
|
var stored = side.stored();
|
|
780
907
|
const ident = battle.active(player);
|
|
@@ -796,7 +923,7 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
796
923
|
stored.hp -|= damage;
|
|
797
924
|
// Pokémon Showdown uses damageOf here but its not relevant in Generation II
|
|
798
925
|
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
799
|
-
if (stored.hp == 0) return;
|
|
926
|
+
if (stored.hp == 0) return true;
|
|
800
927
|
}
|
|
801
928
|
|
|
802
929
|
if (volatiles.LeechSeed) {
|
|
@@ -818,28 +945,30 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
818
945
|
foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
|
|
819
946
|
// Pokémon Showdown uses the less specific heal here instead of drain... because reasons?
|
|
820
947
|
if (foe_stored.hp > before) try options.log.heal(foe_ident, foe_stored, .Silent);
|
|
821
|
-
if (stored.hp == 0) return;
|
|
948
|
+
if (stored.hp == 0) return true;
|
|
822
949
|
}
|
|
823
950
|
|
|
824
951
|
if (volatiles.Nightmare) {
|
|
825
952
|
stored.hp -= @max(stored.stats.hp / 4, 1);
|
|
826
953
|
// ibid
|
|
827
954
|
try options.log.damage(ident, stored, .Nightmare);
|
|
828
|
-
if (stored.hp == 0) return;
|
|
955
|
+
if (stored.hp == 0) return true;
|
|
829
956
|
}
|
|
830
957
|
|
|
831
958
|
if (volatiles.Curse) {
|
|
832
959
|
stored.hp -= @max(stored.stats.hp / 4, 1);
|
|
833
960
|
// ibid
|
|
834
961
|
try options.log.damage(ident, stored, .Curse);
|
|
835
|
-
if (stored.hp == 0) return;
|
|
962
|
+
if (stored.hp == 0) return true;
|
|
836
963
|
}
|
|
964
|
+
|
|
965
|
+
return false;
|
|
837
966
|
}
|
|
838
967
|
|
|
839
|
-
fn betweenTurns(battle: anytype, mslot: u4, options: anytype)
|
|
968
|
+
fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
840
969
|
const players = comptime std.enums.values(Player);
|
|
841
970
|
|
|
842
|
-
|
|
971
|
+
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
843
972
|
|
|
844
973
|
// Future Sight
|
|
845
974
|
inline for (players) |player| {
|
|
@@ -855,7 +984,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
855
984
|
}
|
|
856
985
|
}
|
|
857
986
|
}
|
|
858
|
-
|
|
987
|
+
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
859
988
|
|
|
860
989
|
// Weather
|
|
861
990
|
if (battle.field.weather != .None) {
|
|
@@ -876,7 +1005,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
876
1005
|
try options.log.damage(battle.active(player), side.stored(), .Sandstorm);
|
|
877
1006
|
}
|
|
878
1007
|
}
|
|
879
|
-
|
|
1008
|
+
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
880
1009
|
}
|
|
881
1010
|
}
|
|
882
1011
|
}
|
|
@@ -901,7 +1030,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
901
1030
|
}
|
|
902
1031
|
}
|
|
903
1032
|
}
|
|
904
|
-
|
|
1033
|
+
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
905
1034
|
|
|
906
1035
|
// Perish Song
|
|
907
1036
|
inline for (players) |player| {
|
|
@@ -922,7 +1051,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
922
1051
|
}
|
|
923
1052
|
}
|
|
924
1053
|
}
|
|
925
|
-
|
|
1054
|
+
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
926
1055
|
|
|
927
1056
|
// Leftovers
|
|
928
1057
|
inline for (players) |player| {
|
|
@@ -1015,7 +1144,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
1015
1144
|
|
|
1016
1145
|
// FIXME wtf?
|
|
1017
1146
|
// side.active.volatiles.Toxic = false;
|
|
1018
|
-
// side.active.volatiles.
|
|
1147
|
+
// side.active.volatiles.Nightmare = false;
|
|
1019
1148
|
if (stored.item == .MiracleBerry) {
|
|
1020
1149
|
side.active.volatiles.Confusion = false;
|
|
1021
1150
|
try options.log.end(ident, .Confusion);
|
|
@@ -1053,9 +1182,11 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
1053
1182
|
}
|
|
1054
1183
|
}
|
|
1055
1184
|
}
|
|
1185
|
+
|
|
1186
|
+
return null;
|
|
1056
1187
|
}
|
|
1057
1188
|
|
|
1058
|
-
|
|
1189
|
+
fn updateSideCondition(
|
|
1059
1190
|
battle: anytype,
|
|
1060
1191
|
player: Player,
|
|
1061
1192
|
comptime key: []const u8,
|
|
@@ -1089,7 +1220,8 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1089
1220
|
return Result.Default;
|
|
1090
1221
|
}
|
|
1091
1222
|
|
|
1092
|
-
fn buildRage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1223
|
+
pub fn buildRage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1224
|
+
assert(!state.miss); // TODO
|
|
1093
1225
|
var foe = battle.foe(player);
|
|
1094
1226
|
if (foe.active.volatiles.Rage) {
|
|
1095
1227
|
if (showdown) {
|
|
@@ -1104,7 +1236,8 @@ fn buildRage(battle: anytype, player: Player, state: *State, options: anytype) !
|
|
|
1104
1236
|
}
|
|
1105
1237
|
}
|
|
1106
1238
|
|
|
1107
|
-
fn kingsRock(battle: anytype, player: Player,
|
|
1239
|
+
pub fn kingsRock(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1240
|
+
assert(!state.miss); // TODO
|
|
1108
1241
|
var foe = battle.foe(player);
|
|
1109
1242
|
const flinch = battle.side(player).stored().item == .KingsRock and
|
|
1110
1243
|
!foe.active.volatiles.Substitute and
|
|
@@ -1115,123 +1248,186 @@ fn kingsRock(battle: anytype, player: Player, _: *State, options: anytype) !void
|
|
|
1115
1248
|
}
|
|
1116
1249
|
}
|
|
1117
1250
|
|
|
1118
|
-
pub
|
|
1119
|
-
|
|
1120
|
-
|
|
1251
|
+
pub fn destinyBond(battle: anytype, player: Player, _: *State, options: anytype) !bool {
|
|
1252
|
+
const foe = battle.foe(player);
|
|
1253
|
+
if (foe.stored().hp > 0) return false;
|
|
1254
|
+
|
|
1255
|
+
if (foe.active.volatiles.DestinyBond) {
|
|
1256
|
+
battle.side(player).stored().hp = 0;
|
|
1257
|
+
try options.log.activate(battle.active(player.foe()), .DestinyBond);
|
|
1121
1258
|
}
|
|
1259
|
+
return true;
|
|
1260
|
+
}
|
|
1122
1261
|
|
|
1123
|
-
|
|
1262
|
+
pub const Effects = struct {
|
|
1263
|
+
pub fn attract(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1124
1264
|
const side = battle.side(player);
|
|
1125
1265
|
var foe = battle.foe(player);
|
|
1126
1266
|
|
|
1127
1267
|
const gender: u8 = @intFromEnum(side.stored().dvs.gender);
|
|
1128
1268
|
const foe_gender: u8 = @intFromEnum(foe.stored().dvs.gender);
|
|
1129
1269
|
if (gender + foe_gender != 1) {
|
|
1130
|
-
|
|
1131
|
-
return;
|
|
1270
|
+
return options.log.immune(battle.active(player.foe()), .None);
|
|
1132
1271
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1133
1272
|
try options.log.lastmiss();
|
|
1134
|
-
|
|
1135
|
-
return;
|
|
1273
|
+
return options.log.miss(battle.active(player));
|
|
1136
1274
|
}
|
|
1137
1275
|
|
|
1138
1276
|
foe.active.volatiles.Attract = true;
|
|
1139
1277
|
try options.log.start(battle.active(player.foe()), .Attract);
|
|
1140
1278
|
}
|
|
1141
1279
|
|
|
1142
|
-
fn batonPass(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1280
|
+
pub fn batonPass(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1143
1281
|
_ = .{ battle, player, state, options }; // TODO
|
|
1144
1282
|
}
|
|
1145
1283
|
|
|
1146
|
-
fn beatUp(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1284
|
+
pub fn beatUp(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1147
1285
|
_ = .{ battle, player, state, options }; // TODO
|
|
1148
1286
|
}
|
|
1149
1287
|
|
|
1150
|
-
fn bellyDrum(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1288
|
+
pub fn bellyDrum(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1151
1289
|
_ = .{ battle, player, state, options }; // TODO
|
|
1152
1290
|
}
|
|
1153
1291
|
|
|
1154
|
-
|
|
1292
|
+
pub const bide = struct {
|
|
1293
|
+
pub fn store(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1294
|
+
var side = battle.side(player);
|
|
1295
|
+
var volatiles = &side.active.volatiles;
|
|
1296
|
+
|
|
1297
|
+
const ident = battle.active(player);
|
|
1298
|
+
|
|
1299
|
+
if (!volatiles.Bide) return;
|
|
1300
|
+
|
|
1301
|
+
assert(volatiles.attacks > 0);
|
|
1302
|
+
// if (options.calc.modify(player, .attacking)) |extend| {
|
|
1303
|
+
// if (!extend) volatiles.attacks = 0;
|
|
1304
|
+
// } else {
|
|
1305
|
+
volatiles.attacks -= 1;
|
|
1306
|
+
// }
|
|
1307
|
+
try options.chance.attacking(player, volatiles.attacks);
|
|
1308
|
+
|
|
1309
|
+
if (volatiles.attacks != 0) return options.log.activate(ident, .Bide);
|
|
1310
|
+
|
|
1311
|
+
volatiles.Bide = false;
|
|
1312
|
+
try options.log.end(ident, .Bide);
|
|
1313
|
+
|
|
1314
|
+
state.damage = volatiles.bide *% 2;
|
|
1315
|
+
volatiles.bide = 0;
|
|
1316
|
+
|
|
1317
|
+
if (state.damage == 0) state.miss = true;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
pub fn unleash(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1321
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1322
|
+
}
|
|
1323
|
+
};
|
|
1324
|
+
|
|
1325
|
+
pub fn binding(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1155
1326
|
_ = .{ battle, player, state, options }; // TODO
|
|
1156
1327
|
}
|
|
1157
1328
|
|
|
1158
|
-
fn
|
|
1159
|
-
|
|
1329
|
+
pub fn burnChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1330
|
+
var foe = battle.foe(player);
|
|
1331
|
+
var foe_stored = foe.stored();
|
|
1332
|
+
|
|
1333
|
+
if (foe.active.volatiles.Substitute) return;
|
|
1334
|
+
if (Status.any(foe_stored.status)) {
|
|
1335
|
+
if (Status.is(foe_stored.status, .FRZ)) {
|
|
1336
|
+
assert(Move.get(state.move).type == .Fire or state.move == .TriAttack);
|
|
1337
|
+
try options.log.curestatus(
|
|
1338
|
+
battle.active(player.foe()),
|
|
1339
|
+
foe_stored.status,
|
|
1340
|
+
.Message,
|
|
1341
|
+
);
|
|
1342
|
+
foe_stored.status = 0;
|
|
1343
|
+
}
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
if (!state.proc) return;
|
|
1347
|
+
if (foe.conditions.Safeguard) return;
|
|
1348
|
+
// Nothing is immune to Fire-type, but Ghosts are immune to burnChance called by Tri Attack
|
|
1349
|
+
if (state.immune()) return;
|
|
1350
|
+
if (foe.active.types.includes(Move.get(state.move).type) and state.move != .TriAttack) {
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
foe_stored.status = Status.init(.BRN);
|
|
1355
|
+
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
1356
|
+
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
1357
|
+
|
|
1358
|
+
// TODO: check for status berry
|
|
1160
1359
|
}
|
|
1161
1360
|
|
|
1162
|
-
fn
|
|
1361
|
+
pub fn confusion(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1163
1362
|
_ = .{ battle, player, state, options }; // TODO
|
|
1164
1363
|
}
|
|
1165
1364
|
|
|
1166
|
-
fn
|
|
1365
|
+
pub fn confusionChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1167
1366
|
_ = .{ battle, player, state, options }; // TODO
|
|
1168
1367
|
}
|
|
1169
1368
|
|
|
1170
|
-
fn conversion(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1369
|
+
pub fn conversion(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1171
1370
|
var side = battle.side(player);
|
|
1172
1371
|
|
|
1173
1372
|
var n: u3 = 0;
|
|
1174
1373
|
for (side.active.moves) |m| {
|
|
1175
1374
|
if (convertible(side.active, m.id)) n += 1;
|
|
1176
1375
|
}
|
|
1177
|
-
if (n == 0)
|
|
1178
|
-
try options.log.fail(battle.active(player), .None);
|
|
1179
|
-
return;
|
|
1180
|
-
}
|
|
1376
|
+
if (n == 0) return options.log.fail(battle.active(player), .None);
|
|
1181
1377
|
|
|
1182
1378
|
side.active.types.type1 = try Rolls.conversion(battle, player, n, options);
|
|
1183
1379
|
side.active.types.type2 = side.active.types.type1;
|
|
1184
1380
|
}
|
|
1185
1381
|
|
|
1186
|
-
fn conversion2(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1382
|
+
pub fn conversion2(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1187
1383
|
var side = battle.side(player);
|
|
1188
1384
|
|
|
1189
1385
|
const last = battle.foe(player).lastMove(false);
|
|
1190
|
-
if (last == .None)
|
|
1191
|
-
try options.log.fail(battle.active(player), .None);
|
|
1192
|
-
return;
|
|
1193
|
-
}
|
|
1386
|
+
if (last == .None) return options.log.fail(battle.active(player), .None);
|
|
1194
1387
|
const move = Move.get(last);
|
|
1195
|
-
if (move.type == .@"???")
|
|
1196
|
-
try options.log.fail(battle.active(player), .None);
|
|
1197
|
-
return;
|
|
1198
|
-
}
|
|
1388
|
+
if (move.type == .@"???") return options.log.fail(battle.active(player), .None);
|
|
1199
1389
|
|
|
1200
1390
|
side.active.types.type1 = try Rolls.conversion2(battle, player, move.type, options);
|
|
1201
1391
|
side.active.types.type2 = side.active.types.type1;
|
|
1202
1392
|
}
|
|
1203
1393
|
|
|
1204
|
-
fn counter(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1394
|
+
pub fn counter(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1205
1395
|
_ = .{ battle, player, state, options }; // TODO
|
|
1206
1396
|
}
|
|
1207
1397
|
|
|
1208
|
-
fn curse(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1398
|
+
pub fn curse(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1209
1399
|
_ = .{ battle, player, state, options }; // TODO
|
|
1210
1400
|
}
|
|
1211
1401
|
|
|
1212
|
-
fn defenseCurl(battle: anytype, player: Player,
|
|
1213
|
-
|
|
1402
|
+
pub fn defenseCurl(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
1403
|
+
battle.side(player).active.volatiles.DefenseCurl = true;
|
|
1214
1404
|
}
|
|
1215
1405
|
|
|
1216
|
-
fn
|
|
1406
|
+
pub fn defrost(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1407
|
+
var side = battle.side(player);
|
|
1408
|
+
assert(Status.is(side.stored().status, .FRZ));
|
|
1409
|
+
|
|
1410
|
+
try options.log.curestatus(battle.active(player), side.stored().status, .Message);
|
|
1411
|
+
side.stored().status = 0;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
pub fn destinyBond(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1217
1415
|
battle.side(player).active.volatiles.DestinyBond = true;
|
|
1218
1416
|
try options.log.singlemove(battle.active(player), Move.DestinyBond);
|
|
1219
1417
|
}
|
|
1220
1418
|
|
|
1221
|
-
fn disable(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1419
|
+
pub fn disable(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1222
1420
|
var foe = battle.foe(player);
|
|
1223
1421
|
var volatiles = &foe.active.volatiles;
|
|
1224
1422
|
const foe_ident = battle.active(player.foe());
|
|
1225
1423
|
|
|
1226
1424
|
if (volatiles.disable.move != 0) {
|
|
1227
|
-
|
|
1228
|
-
return;
|
|
1425
|
+
return options.log.fail(foe_ident, .None);
|
|
1229
1426
|
}
|
|
1230
1427
|
|
|
1231
1428
|
const last = battle.foe(player).lastMove(false);
|
|
1232
1429
|
if (last == .None or last == .Struggle) {
|
|
1233
|
-
|
|
1234
|
-
return;
|
|
1430
|
+
return options.log.fail(foe_ident, .None);
|
|
1235
1431
|
}
|
|
1236
1432
|
|
|
1237
1433
|
var slot: u3 = 0;
|
|
@@ -1241,10 +1437,7 @@ pub const Effects = struct {
|
|
|
1241
1437
|
if (m.id == last) break;
|
|
1242
1438
|
}
|
|
1243
1439
|
const move = foe.active.move(slot);
|
|
1244
|
-
if (move.pp == 0)
|
|
1245
|
-
try options.log.fail(foe_ident, .None);
|
|
1246
|
-
return;
|
|
1247
|
-
}
|
|
1440
|
+
if (move.pp == 0) return options.log.fail(foe_ident, .None);
|
|
1248
1441
|
|
|
1249
1442
|
volatiles.disable.move = slot;
|
|
1250
1443
|
volatiles.disable.duration = Rolls.disableDuration(battle, player, options);
|
|
@@ -1252,15 +1445,32 @@ pub const Effects = struct {
|
|
|
1252
1445
|
try options.log.startEffect(foe_ident, .Disable, move.id);
|
|
1253
1446
|
}
|
|
1254
1447
|
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1448
|
+
pub const double = struct {
|
|
1449
|
+
pub fn flying(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1450
|
+
if (battle.foe(player).active.volatiles.Flying) state.damage *|= 2;
|
|
1451
|
+
}
|
|
1258
1452
|
|
|
1259
|
-
|
|
1260
|
-
|
|
1453
|
+
pub fn underground(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1454
|
+
if (battle.foe(player).active.volatiles.Underground) state.damage *|= 2;
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
pub fn minimize(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1458
|
+
if (battle.foe(player).active.volatiles.minimized) state.damage *|= 2;
|
|
1459
|
+
}
|
|
1460
|
+
};
|
|
1461
|
+
|
|
1462
|
+
pub fn drainHP(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1463
|
+
var stored = battle.side(player).stored();
|
|
1464
|
+
|
|
1465
|
+
assert(state.damage != 0);
|
|
1466
|
+
|
|
1467
|
+
if (stored.hp == stored.stats.hp) return;
|
|
1468
|
+
stored.hp = @min(stored.stats.hp, stored.hp + @max(state.damage / 2, 1));
|
|
1469
|
+
|
|
1470
|
+
try options.log.drain(battle.active(player), stored, battle.active(player.foe()));
|
|
1261
1471
|
}
|
|
1262
1472
|
|
|
1263
|
-
fn encore(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1473
|
+
pub fn encore(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1264
1474
|
var foe = battle.foe(player);
|
|
1265
1475
|
var volatiles = &foe.active.volatiles;
|
|
1266
1476
|
const foe_ident = battle.active(player.foe());
|
|
@@ -1269,10 +1479,7 @@ pub const Effects = struct {
|
|
|
1269
1479
|
const failed = volatiles.Encore or
|
|
1270
1480
|
(last == .None or last == .Struggle or
|
|
1271
1481
|
last == .Encore or .last == .MirrorMove);
|
|
1272
|
-
if (failed)
|
|
1273
|
-
try options.log.fail(foe_ident, .None);
|
|
1274
|
-
return;
|
|
1275
|
-
}
|
|
1482
|
+
if (failed) return options.log.fail(foe_ident, .None);
|
|
1276
1483
|
|
|
1277
1484
|
var slot: u3 = 0;
|
|
1278
1485
|
for (foe.active.moves) |m| {
|
|
@@ -1281,10 +1488,7 @@ pub const Effects = struct {
|
|
|
1281
1488
|
if (m.id == last) break;
|
|
1282
1489
|
}
|
|
1283
1490
|
const move = foe.active.move(slot);
|
|
1284
|
-
if (move.pp == 0)
|
|
1285
|
-
try options.log.fail(foe_ident, .None);
|
|
1286
|
-
return;
|
|
1287
|
-
}
|
|
1491
|
+
if (move.pp == 0) return options.log.fail(foe_ident, .None);
|
|
1288
1492
|
|
|
1289
1493
|
volatiles.Encore = true;
|
|
1290
1494
|
volatiles.encore = Rolls.encoreDuration(battle, player, options);
|
|
@@ -1295,34 +1499,54 @@ pub const Effects = struct {
|
|
|
1295
1499
|
try options.log.startEffect(foe_ident, .Encore, move.id);
|
|
1296
1500
|
}
|
|
1297
1501
|
|
|
1298
|
-
fn explode(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1502
|
+
pub fn explode(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1299
1503
|
_ = .{ battle, player, state, options }; // TODO
|
|
1300
1504
|
}
|
|
1301
1505
|
|
|
1302
|
-
fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1506
|
+
pub fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1303
1507
|
const foe = battle.foe(player);
|
|
1304
1508
|
assert(foe.stored().hp > 0);
|
|
1305
1509
|
// FIXME crit?
|
|
1306
1510
|
if (state.damage > foe.stored().hp) state.damage = foe.stored().hp - 1;
|
|
1307
1511
|
}
|
|
1308
1512
|
|
|
1309
|
-
fn fixedDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1310
|
-
|
|
1311
|
-
|
|
1513
|
+
pub fn fixedDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1514
|
+
const side = battle.side(player);
|
|
1515
|
+
const foe = battle.foe(player);
|
|
1312
1516
|
|
|
1313
|
-
|
|
1314
|
-
|
|
1517
|
+
state.damage = switch (Move.get(state.move).effect) {
|
|
1518
|
+
.SuperFang => @max(foe.stored().hp / 2, 1),
|
|
1519
|
+
.LevelDamage => side.stored().level,
|
|
1520
|
+
.FixedDamage => if (state.move == .SonicBoom) 20 else 40,
|
|
1521
|
+
.Psywave => try Rolls.psywave(
|
|
1522
|
+
battle,
|
|
1523
|
+
player,
|
|
1524
|
+
@intCast(@as(u16, side.stored().level) * 3 / 2),
|
|
1525
|
+
options,
|
|
1526
|
+
),
|
|
1527
|
+
else => unreachable,
|
|
1528
|
+
};
|
|
1315
1529
|
}
|
|
1316
1530
|
|
|
1317
|
-
fn flinchChance(battle: anytype, player: Player, state: *State,
|
|
1318
|
-
|
|
1531
|
+
pub fn flinchChance(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1532
|
+
const foe = battle.foe(player);
|
|
1533
|
+
const status = foe.stored().status;
|
|
1534
|
+
var volatiles = &foe.active.volatiles;
|
|
1535
|
+
|
|
1536
|
+
if (volatiles.Substitute) return;
|
|
1537
|
+
if (Status.is(status, .FRZ) or Status.is(status, .SLP)) return;
|
|
1538
|
+
if (!state.first) return;
|
|
1539
|
+
if (!state.proc) return;
|
|
1540
|
+
|
|
1541
|
+
volatiles.Flinch = true;
|
|
1542
|
+
volatiles.Recharging = false;
|
|
1319
1543
|
}
|
|
1320
1544
|
|
|
1321
|
-
fn flyDig(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1545
|
+
pub fn flyDig(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1322
1546
|
_ = .{ battle, player, state, options }; // TODO
|
|
1323
1547
|
}
|
|
1324
1548
|
|
|
1325
|
-
fn focusEnergy(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1549
|
+
pub fn focusEnergy(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1326
1550
|
var side = battle.side(player);
|
|
1327
1551
|
|
|
1328
1552
|
if (side.active.volatiles.FocusEnergy) {
|
|
@@ -1336,9 +1560,13 @@ pub const Effects = struct {
|
|
|
1336
1560
|
|
|
1337
1561
|
var SLOTS: [5]u4 = .{ 0, 0, 0, 0, 0 };
|
|
1338
1562
|
|
|
1339
|
-
fn forceSwitch(battle: anytype, player: Player,
|
|
1563
|
+
pub fn forceSwitch(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1564
|
+
assert(!state.miss); // FIXME ensure checkHit handles fail message
|
|
1565
|
+
|
|
1340
1566
|
const foe = battle.foe(player);
|
|
1341
1567
|
|
|
1568
|
+
if (state.first) return options.log.fail(battle.active(player), .None);
|
|
1569
|
+
|
|
1342
1570
|
var n: u4 = 0;
|
|
1343
1571
|
var i: u4 = 0;
|
|
1344
1572
|
for (foe.pokemon, 0..) |pokemon, j| {
|
|
@@ -1351,168 +1579,390 @@ pub const Effects = struct {
|
|
|
1351
1579
|
}
|
|
1352
1580
|
}
|
|
1353
1581
|
|
|
1354
|
-
if (n == 0)
|
|
1355
|
-
try options.log.fail(battle.active(player), .None);
|
|
1356
|
-
return;
|
|
1357
|
-
}
|
|
1582
|
+
if (n == 0) return options.log.fail(battle.active(player), .None);
|
|
1358
1583
|
|
|
1359
|
-
|
|
1584
|
+
const slot = try Rolls.forceSwitch(battle, player, SLOTS[0..i], n, options);
|
|
1585
|
+
try switchIn(battle, player.foe(), slot, .Drag, options);
|
|
1360
1586
|
}
|
|
1361
1587
|
|
|
1362
|
-
fn foresight(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1588
|
+
pub fn foresight(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1363
1589
|
var foe = battle.foe(player);
|
|
1364
1590
|
|
|
1365
1591
|
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1366
1592
|
try options.log.lastmiss();
|
|
1367
|
-
|
|
1368
|
-
return;
|
|
1593
|
+
return options.log.miss(battle.active(player));
|
|
1369
1594
|
} else if (foe.active.volatiles.Foresight) {
|
|
1370
|
-
|
|
1371
|
-
return;
|
|
1595
|
+
return options.log.fail(battle.active(player), .None);
|
|
1372
1596
|
}
|
|
1373
1597
|
|
|
1374
1598
|
foe.active.volatiles.Foresight = true;
|
|
1375
1599
|
try options.log.start(battle.active(player.foe()), .FocusEnergy);
|
|
1376
1600
|
}
|
|
1377
1601
|
|
|
1378
|
-
fn freezeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1379
|
-
|
|
1602
|
+
pub fn freezeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1603
|
+
var foe = battle.foe(player);
|
|
1604
|
+
var foe_stored = foe.stored();
|
|
1605
|
+
const foe_ident = battle.active(player.foe());
|
|
1606
|
+
|
|
1607
|
+
if (foe.active.volatiles.Substitute) return;
|
|
1608
|
+
if (Status.any(foe_stored.status)) return;
|
|
1609
|
+
|
|
1610
|
+
// Nothing is immune to Ice-type, but Ghosts are immune to freezeChance called by Tri Attack
|
|
1611
|
+
if (state.immune()) return;
|
|
1612
|
+
if (battle.field.weather == .Sun) return;
|
|
1613
|
+
if (foe.active.types.includes(Move.get(state.move).type) and state.move != .TriAttack) {
|
|
1614
|
+
return;
|
|
1615
|
+
}
|
|
1616
|
+
if (!state.proc) return;
|
|
1617
|
+
if (foe.conditions.Safeguard) return;
|
|
1618
|
+
// Freeze Clause Mod
|
|
1619
|
+
if (showdown) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
|
|
1620
|
+
|
|
1621
|
+
foe_stored.status = Status.init(.FRZ);
|
|
1622
|
+
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
1623
|
+
|
|
1624
|
+
// TODO: check for status berry
|
|
1625
|
+
|
|
1626
|
+
foe.active.volatiles.Recharging = false;
|
|
1627
|
+
foe.active.volatiles.frozen = true;
|
|
1380
1628
|
}
|
|
1381
1629
|
|
|
1382
|
-
fn furyCutter(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1630
|
+
pub fn furyCutter(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1383
1631
|
_ = .{ battle, player, state, options }; // TODO
|
|
1384
1632
|
}
|
|
1385
1633
|
|
|
1386
|
-
fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1634
|
+
pub fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1387
1635
|
_ = .{ battle, player, state, options }; // TODO
|
|
1388
1636
|
}
|
|
1389
1637
|
|
|
1390
|
-
fn happiness(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1638
|
+
pub fn happiness(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1391
1639
|
const val = battle.side(player).stored().happiness;
|
|
1392
1640
|
const frustration = Move.get(state.move).effect == .Frustration;
|
|
1393
1641
|
state.bp = ((if (frustration) 255 - val else val) * 10) / 25;
|
|
1394
1642
|
}
|
|
1395
1643
|
|
|
1396
|
-
fn haze(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1644
|
+
pub fn haze(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1397
1645
|
_ = .{ battle, player, state, options }; // TODO
|
|
1398
1646
|
}
|
|
1399
1647
|
|
|
1400
|
-
fn heal(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1648
|
+
pub fn heal(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1401
1649
|
_ = .{ battle, player, state, options }; // TODO
|
|
1402
1650
|
}
|
|
1403
1651
|
|
|
1404
|
-
fn healBell(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1652
|
+
pub fn healBell(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1405
1653
|
_ = .{ battle, player, state, options }; // TODO
|
|
1406
1654
|
}
|
|
1407
1655
|
|
|
1408
|
-
fn hiddenPower(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1656
|
+
pub fn hiddenPower(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1409
1657
|
_ = .{ battle, player, state, options }; // TODO
|
|
1410
1658
|
}
|
|
1411
1659
|
|
|
1412
|
-
fn hyperBeam(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1660
|
+
pub fn hyperBeam(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1413
1661
|
_ = .{ battle, player, state, options }; // TODO
|
|
1414
1662
|
}
|
|
1415
1663
|
|
|
1416
|
-
fn
|
|
1664
|
+
pub fn leechSeed(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1417
1665
|
_ = .{ battle, player, state, options }; // TODO
|
|
1418
1666
|
}
|
|
1419
1667
|
|
|
1420
|
-
fn
|
|
1668
|
+
pub fn lockOn(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1421
1669
|
_ = .{ battle, player, state, options }; // TODO
|
|
1422
1670
|
}
|
|
1423
1671
|
|
|
1424
|
-
|
|
1672
|
+
const MAGNITUDE_POWER = [_]u8{ 10, 30, 50, 70, 90, 110, 150 };
|
|
1673
|
+
|
|
1674
|
+
pub fn magnitude(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1425
1675
|
_ = .{ battle, player, state, options }; // TODO
|
|
1426
1676
|
}
|
|
1427
1677
|
|
|
1428
|
-
fn
|
|
1678
|
+
pub fn meanLook(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1429
1679
|
_ = .{ battle, player, state, options }; // TODO
|
|
1430
1680
|
}
|
|
1431
1681
|
|
|
1432
|
-
|
|
1682
|
+
pub fn metronome(
|
|
1683
|
+
battle: anytype,
|
|
1684
|
+
player: Player,
|
|
1685
|
+
state: *State,
|
|
1686
|
+
options: anytype,
|
|
1687
|
+
) anyerror!void {
|
|
1688
|
+
var side = battle.side(player);
|
|
1433
1689
|
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
}
|
|
1690
|
+
side.last_used_move = .None;
|
|
1691
|
+
side.active.volatiles.dirty = false;
|
|
1437
1692
|
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
}
|
|
1693
|
+
// if (wPlayerCharging) ???
|
|
1694
|
+
// wPlayerCharging = 1
|
|
1441
1695
|
|
|
1442
|
-
|
|
1443
|
-
|
|
1696
|
+
// FIXME
|
|
1697
|
+
state.move = try Rolls.metronome(battle, player, options);
|
|
1698
|
+
state.from = .Metronome;
|
|
1699
|
+
return generated.runMove(battle, player, state, options);
|
|
1444
1700
|
}
|
|
1445
1701
|
|
|
1446
|
-
fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1702
|
+
pub fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1447
1703
|
_ = .{ battle, player, state, options }; // TODO
|
|
1448
1704
|
}
|
|
1449
1705
|
|
|
1450
|
-
fn mirrorCoat(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1706
|
+
pub fn mirrorCoat(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1451
1707
|
_ = .{ battle, player, state, options }; // TODO
|
|
1452
1708
|
}
|
|
1453
1709
|
|
|
1454
|
-
fn mirrorMove(
|
|
1455
|
-
|
|
1710
|
+
pub fn mirrorMove(
|
|
1711
|
+
battle: anytype,
|
|
1712
|
+
player: Player,
|
|
1713
|
+
state: *State,
|
|
1714
|
+
options: anytype,
|
|
1715
|
+
) anyerror!void {
|
|
1716
|
+
var side = battle.side(player);
|
|
1717
|
+
const foe = battle.foe(player);
|
|
1718
|
+
|
|
1719
|
+
side.last_used_move = .None;
|
|
1720
|
+
side.active.volatiles.dirty = false;
|
|
1721
|
+
|
|
1722
|
+
const last = foe.lastMove(false);
|
|
1723
|
+
|
|
1724
|
+
if (last == .None) return options.log.fail(battle.active(player), .None);
|
|
1725
|
+
const knows = knows: for (side.active.moves) |m| {
|
|
1726
|
+
if (last == m.id) break :knows true;
|
|
1727
|
+
} else false;
|
|
1728
|
+
if (knows) return options.log.fail(battle.active(player), .None);
|
|
1729
|
+
|
|
1730
|
+
// if (wPlayerCharging) ???
|
|
1731
|
+
// wPlayerCharging = 1
|
|
1732
|
+
|
|
1733
|
+
// FIXME
|
|
1734
|
+
state.move = last;
|
|
1735
|
+
state.from = .MirrorMove;
|
|
1736
|
+
return generated.runMove(battle, player, state, options);
|
|
1456
1737
|
}
|
|
1457
1738
|
|
|
1458
|
-
fn mist(battle: anytype, player: Player,
|
|
1459
|
-
|
|
1739
|
+
pub fn mist(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1740
|
+
var side = battle.side(player);
|
|
1741
|
+
|
|
1742
|
+
if (side.active.volatiles.Mist) return options.log.fail(battle.active(player), .None);
|
|
1743
|
+
side.active.volatiles.Mist = true;
|
|
1744
|
+
|
|
1745
|
+
try options.log.start(battle.active(player), .Mist);
|
|
1460
1746
|
}
|
|
1461
1747
|
|
|
1462
|
-
fn weatherHeal(battle: anytype, player: Player,
|
|
1463
|
-
|
|
1748
|
+
pub fn weatherHeal(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1749
|
+
var stored = battle.side(player).stored();
|
|
1750
|
+
const ident = battle.active(player);
|
|
1751
|
+
|
|
1752
|
+
if (stored.hp == stored.stats.hp) return options.log.fail(ident, .None);
|
|
1753
|
+
|
|
1754
|
+
stored.hp = switch (battle.field.weather) {
|
|
1755
|
+
.Sun => stored.stats.hp,
|
|
1756
|
+
.None => @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2)),
|
|
1757
|
+
else => @min(stored.stats.hp, stored.hp + (stored.stats.hp / 4)),
|
|
1758
|
+
};
|
|
1759
|
+
|
|
1760
|
+
try options.log.heal(ident, stored, Heal.None);
|
|
1464
1761
|
}
|
|
1465
1762
|
|
|
1466
|
-
fn multiHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1763
|
+
pub fn multiHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1467
1764
|
_ = .{ battle, player, state, options }; // TODO
|
|
1468
1765
|
}
|
|
1469
1766
|
|
|
1470
|
-
fn nightmare(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1471
|
-
|
|
1767
|
+
pub fn nightmare(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1768
|
+
var foe = battle.foe(player);
|
|
1769
|
+
var foe_volatiles = &foe.active.volatiles;
|
|
1770
|
+
const foe_ident = battle.active(player.foe());
|
|
1771
|
+
|
|
1772
|
+
if (foe_volatiles.Flying or foe_volatiles.Underground) {
|
|
1773
|
+
if (!showdown) return options.log.fail(battle.active(player.foe()), .None);
|
|
1774
|
+
try options.log.lastmiss();
|
|
1775
|
+
return options.log.miss(battle.active(player));
|
|
1776
|
+
} else if (foe_volatiles.Substitute) {
|
|
1777
|
+
return options.log.activateMove(foe_ident, .SubstituteBlock, state.move);
|
|
1778
|
+
} else if (!Status.is(foe.stored().status, .SLP) or foe_volatiles.Nightmare) {
|
|
1779
|
+
return options.log.fail(foe_ident, .None);
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
foe_volatiles.Nightmare = true;
|
|
1783
|
+
try options.log.start(battle.active(player.foe()), .Nightmare);
|
|
1472
1784
|
}
|
|
1473
1785
|
|
|
1474
|
-
|
|
1475
|
-
|
|
1786
|
+
pub const ohko = struct {
|
|
1787
|
+
// OHKO calls adjustDamage but only actually cares about immunity
|
|
1788
|
+
pub fn immune(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1789
|
+
const move = Move.get(state.move);
|
|
1790
|
+
|
|
1791
|
+
assert(move.type == .Normal or move.type == .Ground);
|
|
1792
|
+
const foe = battle.foe(player);
|
|
1793
|
+
const zero = if (move.type == .Normal)
|
|
1794
|
+
foe.active.types.includes(.Ghost) and !foe.active.volatiles.Foresight
|
|
1795
|
+
else
|
|
1796
|
+
foe.active.types.includes(.Flying);
|
|
1797
|
+
state.effectiveness = if (zero) 0 else Effectiveness.neutral;
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
pub fn damage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1801
|
+
const side = battle.side(player);
|
|
1802
|
+
const foe = battle.foe(player);
|
|
1803
|
+
|
|
1804
|
+
const foe_ident = battle.active(player.foe());
|
|
1805
|
+
|
|
1806
|
+
state.damage = 0;
|
|
1807
|
+
if (state.immune()) return options.log.immune(foe_ident, .None);
|
|
1808
|
+
const delta = side.stored().level -| foe.stored().level;
|
|
1809
|
+
if (delta == 0) return options.log.immune(foe_ident, .OHKO);
|
|
1810
|
+
|
|
1811
|
+
state.accuracy = 2 * delta +| Gen12.percent(30);
|
|
1812
|
+
try checkHit(battle, player, state, options);
|
|
1813
|
+
if (!state.miss) state.damage = 65535;
|
|
1814
|
+
state.ohko = true;
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
1817
|
+
|
|
1818
|
+
pub fn painSplit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1819
|
+
var side = battle.side(player);
|
|
1820
|
+
var foe = battle.foe(player);
|
|
1821
|
+
|
|
1822
|
+
const ident = battle.active(player);
|
|
1823
|
+
const foe_ident = battle.active(player.foe());
|
|
1824
|
+
|
|
1825
|
+
if (state.miss) return options.log.fail(foe_ident, .None);
|
|
1826
|
+
if (foe.active.volatiles.Substitute) {
|
|
1827
|
+
return options.log.activateMove(foe_ident, .SubstituteBlock, state.move);
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
const avg = @max((foe.stored().hp + side.stored().hp) / 2, 1);
|
|
1831
|
+
foe.stored().hp -= avg;
|
|
1832
|
+
side.stored().hp = avg;
|
|
1833
|
+
|
|
1834
|
+
try options.log.sethp(foe_ident, foe.stored(), .Silent);
|
|
1835
|
+
try options.log.sethp(ident, side.stored(), .None);
|
|
1476
1836
|
}
|
|
1477
1837
|
|
|
1478
|
-
fn
|
|
1479
|
-
|
|
1838
|
+
pub fn paralyze(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1839
|
+
var foe = battle.foe(player);
|
|
1840
|
+
const foe_ident = battle.active(player.foe());
|
|
1841
|
+
|
|
1842
|
+
if (Status.is(foe.stored().status, .PAR)) return options.log.fail(foe_ident, .Paralysis);
|
|
1843
|
+
if (state.immune()) return options.log.immune(foe_ident, .None);
|
|
1844
|
+
if (Status.any(foe.stored().status)) return options.log.fail(foe_ident, .None);
|
|
1845
|
+
if (state.miss) {
|
|
1846
|
+
try options.log.lastmiss();
|
|
1847
|
+
return options.log.miss(battle.active(player));
|
|
1848
|
+
} else if (foe.active.volatiles.Substitute) {
|
|
1849
|
+
return options.log.activateMove(foe_ident, .SubstituteBlock, state.move);
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
foe.stored().status = Status.init(.PAR);
|
|
1853
|
+
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
1854
|
+
|
|
1855
|
+
try options.log.status(foe_ident, foe.stored().status, .None);
|
|
1480
1856
|
}
|
|
1481
1857
|
|
|
1482
|
-
fn
|
|
1483
|
-
|
|
1858
|
+
pub fn paralyzeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1859
|
+
var foe = battle.foe(player);
|
|
1860
|
+
var foe_stored = foe.stored();
|
|
1861
|
+
|
|
1862
|
+
if (foe.active.volatiles.Substitute) return;
|
|
1863
|
+
if (Status.any(foe_stored.status)) return;
|
|
1864
|
+
if (state.immune()) return;
|
|
1865
|
+
if (!state.proc) return;
|
|
1866
|
+
if (foe.conditions.Safeguard) return;
|
|
1867
|
+
|
|
1868
|
+
foe_stored.status = Status.init(.PAR);
|
|
1869
|
+
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
1870
|
+
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
1871
|
+
|
|
1872
|
+
// TODO: check for status berry
|
|
1484
1873
|
}
|
|
1485
1874
|
|
|
1486
|
-
fn
|
|
1487
|
-
|
|
1488
|
-
|
|
1875
|
+
pub fn payDay(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1876
|
+
if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
|
|
1877
|
+
try options.log.fieldactivate(); // FIXME .PayDay
|
|
1878
|
+
}
|
|
1489
1879
|
}
|
|
1490
1880
|
|
|
1491
|
-
fn
|
|
1492
|
-
|
|
1881
|
+
pub fn perishSong(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1882
|
+
var side = battle.side(player);
|
|
1883
|
+
var foe = battle.foe(player);
|
|
1884
|
+
|
|
1885
|
+
if (foe.active.volatiles.PerishSong) {
|
|
1886
|
+
return options.log.fail(battle.active(player.foe()), .None);
|
|
1887
|
+
} else if (!side.active.volatiles.PerishSong) {
|
|
1888
|
+
side.active.volatiles.PerishSong = true;
|
|
1889
|
+
side.active.volatiles.perish_song = 4;
|
|
1890
|
+
try options.log.start(battle.active(player), .PerishSong3Silent);
|
|
1891
|
+
}
|
|
1892
|
+
foe.active.volatiles.PerishSong = true;
|
|
1893
|
+
foe.active.volatiles.perish_song = 4;
|
|
1894
|
+
try options.log.start(battle.active(player.foe()), .PerishSong3Silent);
|
|
1895
|
+
|
|
1896
|
+
try options.log.fieldactivate(); // FIXME .PerishSong
|
|
1493
1897
|
}
|
|
1494
1898
|
|
|
1495
|
-
fn
|
|
1496
|
-
|
|
1899
|
+
pub fn poison(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1900
|
+
var foe = battle.foe(player);
|
|
1901
|
+
var foe_stored = foe.stored();
|
|
1902
|
+
const foe_ident = battle.active(player.foe());
|
|
1903
|
+
|
|
1904
|
+
const toxic = state.move == .Toxic;
|
|
1905
|
+
if (state.immune() or foe.active.types.includes(.Poison)) {
|
|
1906
|
+
return options.log.immune(foe_ident, .None);
|
|
1907
|
+
} else if (Status.any(foe_stored.status)) {
|
|
1908
|
+
// Pokémon Showdown considers Toxic to be a status even in Generation II and so
|
|
1909
|
+
// will not include a fail reason for Toxic vs. Poison or vice-versa...
|
|
1910
|
+
return options.log.fail(foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
1911
|
+
if (!showdown)
|
|
1912
|
+
.Poison
|
|
1913
|
+
else if (toxic == (foe_stored.status == Status.TOX))
|
|
1914
|
+
if (toxic) .Toxic else .Poison
|
|
1915
|
+
else
|
|
1916
|
+
.None
|
|
1917
|
+
else
|
|
1918
|
+
.None);
|
|
1919
|
+
} else if (foe.active.volatiles.Substitute) {
|
|
1920
|
+
return options.log.activateMove(foe_ident, .SubstituteBlock, state.move);
|
|
1921
|
+
} else if (state.miss) {
|
|
1922
|
+
try options.log.lastmiss();
|
|
1923
|
+
return options.log.miss(battle.active(player));
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
foe_stored.status = Status.init(.PSN);
|
|
1927
|
+
if (toxic) {
|
|
1928
|
+
if (showdown) foe_stored.status = Status.TOX;
|
|
1929
|
+
foe.active.volatiles.Toxic = true;
|
|
1930
|
+
foe.active.volatiles.toxic = 0;
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
1934
|
+
|
|
1935
|
+
// TODO: check for status berry
|
|
1497
1936
|
}
|
|
1498
1937
|
|
|
1499
|
-
fn
|
|
1500
|
-
|
|
1938
|
+
pub fn poisonChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1939
|
+
var foe = battle.foe(player);
|
|
1940
|
+
var foe_stored = foe.stored();
|
|
1941
|
+
|
|
1942
|
+
if (foe.active.volatiles.Substitute) return;
|
|
1943
|
+
if (Status.any(foe_stored.status)) return if (showdown) battle.rng.advance(1);
|
|
1944
|
+
if (state.immune() or foe.active.types.includes(.Poison)) return;
|
|
1945
|
+
if (!state.proc) return;
|
|
1946
|
+
if (foe.conditions.Safeguard) return;
|
|
1947
|
+
|
|
1948
|
+
foe_stored.status = Status.init(.PSN);
|
|
1949
|
+
try options.log.status(battle.active(player.foe()), foe_stored.status, .None);
|
|
1950
|
+
|
|
1951
|
+
// TODO: check for status berry
|
|
1501
1952
|
}
|
|
1502
1953
|
|
|
1503
|
-
fn present(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1954
|
+
pub fn present(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1504
1955
|
_ = .{ battle, player, state, options }; // TODO
|
|
1505
1956
|
}
|
|
1506
1957
|
|
|
1507
|
-
fn protect(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1958
|
+
pub fn protect(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1508
1959
|
var side = battle.side(player);
|
|
1509
1960
|
var volatiles = &side.active.volatiles;
|
|
1510
1961
|
|
|
1511
1962
|
const failed = !state.first or volatiles.Substitute or
|
|
1512
1963
|
!try Rolls.protect(battle, player, volatiles.protect, options);
|
|
1513
1964
|
if (failed) {
|
|
1514
|
-
|
|
1515
|
-
return;
|
|
1965
|
+
return options.log.fail(battle.active(player), .None);
|
|
1516
1966
|
}
|
|
1517
1967
|
|
|
1518
1968
|
volatiles.protect = @max(8, volatiles.protect + 1);
|
|
@@ -1524,75 +1974,213 @@ pub const Effects = struct {
|
|
|
1524
1974
|
try options.log.singleturn(battle.active(player), state.move);
|
|
1525
1975
|
}
|
|
1526
1976
|
|
|
1527
|
-
fn psychUp(battle: anytype, player: Player,
|
|
1528
|
-
|
|
1529
|
-
|
|
1977
|
+
pub fn psychUp(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1978
|
+
var side = battle.side(player);
|
|
1979
|
+
const foe = battle.foe(player);
|
|
1530
1980
|
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1981
|
+
// Pokémon Showdown should fail when the enemy has no boosts/unboosts but doesn't
|
|
1982
|
+
if (!showdown and @as(u32, @bitCast(foe.active.boosts)) == 0) {
|
|
1983
|
+
return options.log.fail(battle.active(player), .None);
|
|
1984
|
+
}
|
|
1534
1985
|
|
|
1535
|
-
|
|
1536
|
-
|
|
1986
|
+
side.active.boosts = foe.active.boosts;
|
|
1987
|
+
try options.log.copyboost(battle.active(player), battle.active(player.foe()));
|
|
1537
1988
|
}
|
|
1538
1989
|
|
|
1539
|
-
fn
|
|
1540
|
-
|
|
1990
|
+
pub fn pursuit(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1991
|
+
// TODO: remove switching variable and just look at Choice saved in Battle struct?
|
|
1992
|
+
if (battle.foe(player).active.volatiles.switching) state.damage *|= 2;
|
|
1541
1993
|
}
|
|
1542
1994
|
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1995
|
+
pub const rage = struct {
|
|
1996
|
+
pub fn start(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1997
|
+
battle.side(player).active.volatiles.Rage = true;
|
|
1998
|
+
try options.log.singlemove(battle.active(player), Move.Rage);
|
|
1999
|
+
}
|
|
1546
2000
|
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
2001
|
+
pub fn damage(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2002
|
+
var side = battle.side(player);
|
|
2003
|
+
var volatiles = &side.active.volatiles;
|
|
1550
2004
|
|
|
1551
|
-
|
|
1552
|
-
|
|
2005
|
+
assert(volatiles.Rage);
|
|
2006
|
+
state.damage *|= (volatiles.rage +| 1);
|
|
2007
|
+
}
|
|
2008
|
+
};
|
|
2009
|
+
|
|
2010
|
+
pub fn rainDance(battle: anytype, _: Player, _: *State, options: anytype) !void {
|
|
2011
|
+
return weather(battle, .Rain, options);
|
|
1553
2012
|
}
|
|
1554
2013
|
|
|
1555
|
-
fn
|
|
1556
|
-
|
|
2014
|
+
pub fn rapidSpin(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2015
|
+
var side = battle.side(player);
|
|
2016
|
+
|
|
2017
|
+
if (side.active.volatiles.LeechSeed) {
|
|
2018
|
+
try options.log.end(battle.active(player), .LeechSeedFrom);
|
|
2019
|
+
side.active.volatiles.LeechSeed = false;
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
if (side.conditions.Spikes) {
|
|
2023
|
+
try options.log.spikesend(battle.active(player));
|
|
2024
|
+
side.conditions.Spikes = false;
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
if (side.active.volatiles.bind.duration > 0) {
|
|
2028
|
+
const reason = @intFromEnum(End.Bind) + side.active.volatiles.bind.reason - 1;
|
|
2029
|
+
try options.log.end(battle.active(player), @enumFromInt(reason));
|
|
2030
|
+
side.active.volatiles.bind = .{};
|
|
2031
|
+
}
|
|
1557
2032
|
}
|
|
1558
2033
|
|
|
1559
|
-
fn
|
|
1560
|
-
|
|
2034
|
+
pub fn recoil(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2035
|
+
var side = battle.side(player);
|
|
2036
|
+
var stored = side.stored();
|
|
2037
|
+
|
|
2038
|
+
assert(state.damage > 0);
|
|
2039
|
+
|
|
2040
|
+
const damage: i16 = @intCast(@max(state.damage / 4, 1));
|
|
2041
|
+
stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
|
|
2042
|
+
|
|
2043
|
+
try options.log.damageOf(
|
|
2044
|
+
battle.active(player),
|
|
2045
|
+
stored,
|
|
2046
|
+
.RecoilOf,
|
|
2047
|
+
battle.active(player.foe()),
|
|
2048
|
+
);
|
|
1561
2049
|
}
|
|
1562
2050
|
|
|
1563
|
-
fn
|
|
1564
|
-
|
|
2051
|
+
pub fn reversal(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2052
|
+
const stored = battle.side(player).stored();
|
|
2053
|
+
|
|
2054
|
+
// Pokémon Showdown Gen II implementation incorrectly inherits Gen IV's table instead
|
|
2055
|
+
state.bp = if (showdown) power: {
|
|
2056
|
+
const x = @max(1, stored.hp * 64 / stored.stats.hp);
|
|
2057
|
+
if (x < 2) break :power 200;
|
|
2058
|
+
if (x < 6) break :power 150;
|
|
2059
|
+
if (x < 13) break :power 100;
|
|
2060
|
+
if (x < 22) break :power 80;
|
|
2061
|
+
if (x < 43) break :power 40;
|
|
2062
|
+
break :power 20;
|
|
2063
|
+
} else power: {
|
|
2064
|
+
const x = @max(1, stored.hp * 48 / stored.stats.hp);
|
|
2065
|
+
if (x < 1) break :power 200;
|
|
2066
|
+
if (x < 4) break :power 150;
|
|
2067
|
+
if (x < 9) break :power 100;
|
|
2068
|
+
if (x < 16) break :power 80;
|
|
2069
|
+
if (x < 32) break :power 40;
|
|
2070
|
+
break :power 20;
|
|
2071
|
+
};
|
|
2072
|
+
|
|
2073
|
+
try calcDamage(battle, player, state, options);
|
|
1565
2074
|
}
|
|
1566
2075
|
|
|
1567
|
-
|
|
1568
|
-
|
|
2076
|
+
pub const rollout = struct {
|
|
2077
|
+
pub fn check(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
2078
|
+
var side = battle.side(player);
|
|
2079
|
+
|
|
2080
|
+
if (!side.active.volatiles.Rollout) {
|
|
2081
|
+
side.active.volatiles.attacks = 0;
|
|
2082
|
+
} else {
|
|
2083
|
+
// TODO SkipToBattleCommand(doturn_command)
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
pub fn power(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2088
|
+
var side = battle.side(player);
|
|
2089
|
+
|
|
2090
|
+
if (Status.is(side.stored().status, .SLP)) return;
|
|
2091
|
+
|
|
2092
|
+
if (side.active.volatiles.attacks > 0) {
|
|
2093
|
+
// TODO set wSomeoneIsRampaging
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
if (state.miss) {
|
|
2097
|
+
side.active.volatiles.Rollout = false;
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
assert(side.active.volatiles.attacks < 5);
|
|
2102
|
+
side.active.volatiles.attacks += 1;
|
|
2103
|
+
side.active.volatiles.Rollout = side.active.volatiles.attacks != 5;
|
|
2104
|
+
|
|
2105
|
+
if (side.active.volatiles.DefenseCurl) state.damage *|= 2;
|
|
2106
|
+
}
|
|
2107
|
+
};
|
|
2108
|
+
|
|
2109
|
+
pub fn sandstorm(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2110
|
+
if (battle.field.weather != .Sandstorm) return weather(battle, .Sandstorm, options);
|
|
2111
|
+
return options.log.fail(battle.active(player), .None);
|
|
1569
2112
|
}
|
|
1570
2113
|
|
|
1571
|
-
fn
|
|
1572
|
-
|
|
2114
|
+
pub fn safeguard(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2115
|
+
return screens(battle, player, "Safeguard", "safeguard", options);
|
|
1573
2116
|
}
|
|
1574
2117
|
|
|
1575
|
-
fn
|
|
1576
|
-
|
|
2118
|
+
pub fn reflect(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2119
|
+
return screens(battle, player, "Reflect", "reflect", options);
|
|
1577
2120
|
}
|
|
1578
2121
|
|
|
1579
|
-
fn
|
|
1580
|
-
|
|
2122
|
+
pub fn lightScreen(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2123
|
+
return screens(battle, player, "LightScreen", "light_screen", options);
|
|
1581
2124
|
}
|
|
1582
2125
|
|
|
1583
|
-
fn
|
|
1584
|
-
|
|
2126
|
+
fn screens(
|
|
2127
|
+
battle: anytype,
|
|
2128
|
+
player: Player,
|
|
2129
|
+
comptime key: []const u8,
|
|
2130
|
+
comptime value: []const u8,
|
|
2131
|
+
options: anytype,
|
|
2132
|
+
) !void {
|
|
2133
|
+
var side = battle.side(player);
|
|
2134
|
+
|
|
2135
|
+
if (@field(side.conditions, key)) return options.log.fail(battle.active(player), .None);
|
|
2136
|
+
|
|
2137
|
+
@field(side.conditions, key) = true;
|
|
2138
|
+
@field(side.conditions, value) = 5;
|
|
2139
|
+
try options.log.sidestart(player, @field(protocol.Side, key));
|
|
1585
2140
|
}
|
|
1586
2141
|
|
|
1587
|
-
fn
|
|
1588
|
-
|
|
2142
|
+
pub fn sketch(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2143
|
+
var side = battle.side(player);
|
|
2144
|
+
|
|
2145
|
+
side.last_used_move = .None;
|
|
2146
|
+
side.active.volatiles.dirty = false;
|
|
2147
|
+
|
|
2148
|
+
if (showdown) {
|
|
2149
|
+
// yes, actually "Splash". '|nothing' gets blindly mapped to '|-activate||move:Splash'
|
|
2150
|
+
try options.log.activate(battle.active(player), .Splash);
|
|
2151
|
+
} else {
|
|
2152
|
+
try options.log.fail(battle.active(player), .None);
|
|
2153
|
+
}
|
|
1589
2154
|
}
|
|
1590
2155
|
|
|
1591
|
-
fn sleep(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1592
|
-
|
|
2156
|
+
pub fn sleep(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2157
|
+
var foe = battle.foe(player);
|
|
2158
|
+
var foe_stored = foe.stored();
|
|
2159
|
+
const foe_ident = battle.active(player.foe());
|
|
2160
|
+
|
|
2161
|
+
if (Status.any(foe_stored.status)) {
|
|
2162
|
+
return options.log.fail(
|
|
2163
|
+
foe_ident,
|
|
2164
|
+
if (Status.is(foe_stored.status, .SLP)) .Sleep else .None,
|
|
2165
|
+
);
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
if (foe.active.volatiles.Substitute) {
|
|
2169
|
+
return options.log.activateMove(foe_ident, .SubstituteBlock, state.move);
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
// Sleep Clause Mod
|
|
2173
|
+
if (showdown) {
|
|
2174
|
+
for (foe.pokemon) |p| {
|
|
2175
|
+
if (Status.is(p.status, .SLP) and !Status.is(p.status, .EXT)) return;
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
|
|
2180
|
+
try options.log.statusFrom(foe_ident, foe_stored.status, state.move);
|
|
1593
2181
|
}
|
|
1594
2182
|
|
|
1595
|
-
fn sleepTalk(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2183
|
+
pub fn sleepTalk(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1596
2184
|
const side = battle.side(player);
|
|
1597
2185
|
|
|
1598
2186
|
if (!Status.is(side.stored().status, .SLP)) {
|
|
@@ -1604,91 +2192,372 @@ pub const Effects = struct {
|
|
|
1604
2192
|
for (side.active.moves, 0..) |m, i| {
|
|
1605
2193
|
if (state.mslot - 1 != i and Move.get(m.id).extra.sleep_talk) n += 1;
|
|
1606
2194
|
}
|
|
1607
|
-
if (n == 0)
|
|
1608
|
-
try options.log.fail(battle.active(player), .None);
|
|
1609
|
-
return;
|
|
1610
|
-
}
|
|
2195
|
+
if (n == 0) return options.log.fail(battle.active(player), .None);
|
|
1611
2196
|
|
|
1612
2197
|
_ = try Rolls.sleepTalk(battle, player, n, state.mslot, options); // TODO
|
|
1613
2198
|
}
|
|
1614
2199
|
|
|
1615
|
-
fn snore(battle: anytype, player: Player, state: *State, options: anytype) !
|
|
1616
|
-
|
|
2200
|
+
pub fn snore(battle: anytype, player: Player, state: *State, options: anytype) !bool {
|
|
2201
|
+
if (Status.is(battle.side(player).stored().status, .SLP)) return true;
|
|
2202
|
+
state.damage = 0;
|
|
2203
|
+
state.miss = true; // TODO is this necessary?
|
|
2204
|
+
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
2205
|
+
return false;
|
|
1617
2206
|
}
|
|
1618
2207
|
|
|
1619
|
-
fn
|
|
1620
|
-
|
|
1621
|
-
|
|
2208
|
+
pub fn spikes(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2209
|
+
var foe = battle.foe(player);
|
|
2210
|
+
if (foe.conditions.Spikes) return options.log.fail(battle.active(player), .None);
|
|
1622
2211
|
|
|
1623
|
-
|
|
1624
|
-
|
|
2212
|
+
foe.conditions.Spikes = true;
|
|
2213
|
+
try options.log.sidestart(player.foe(), .Spikes);
|
|
1625
2214
|
}
|
|
1626
2215
|
|
|
1627
|
-
fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2216
|
+
pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1628
2217
|
_ = .{ battle, player, state, options }; // TODO
|
|
1629
|
-
|
|
2218
|
+
// var foe = battle.foe(player);
|
|
1630
2219
|
|
|
1631
|
-
|
|
1632
|
-
_ = .{ battle, player, state, options }; // TODO
|
|
1633
|
-
}
|
|
2220
|
+
// if (state.miss) return options.log.fail(battle.active(player), .None);
|
|
1634
2221
|
|
|
1635
|
-
|
|
1636
|
-
|
|
2222
|
+
// const last = foe.lastMove(false);
|
|
2223
|
+
// if (showdown) {
|
|
2224
|
+
// const roll = try Rolls.spite(battle, player, options);
|
|
2225
|
+
// } else {
|
|
2226
|
+
// if (last != .None) {}
|
|
2227
|
+
// }
|
|
1637
2228
|
}
|
|
1638
2229
|
|
|
1639
|
-
fn
|
|
1640
|
-
|
|
2230
|
+
pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2231
|
+
try options.log.activate(battle.active(player), .Splash);
|
|
1641
2232
|
}
|
|
1642
2233
|
|
|
1643
|
-
fn
|
|
1644
|
-
|
|
2234
|
+
pub fn substitute(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2235
|
+
var side = battle.side(player);
|
|
2236
|
+
if (side.active.volatiles.Substitute) {
|
|
2237
|
+
return options.log.fail(battle.active(player), .Substitute);
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
assert(side.stored().stats.hp <= 1023);
|
|
2241
|
+
const hp: u8 = @intCast(side.stored().stats.hp / 4);
|
|
2242
|
+
|
|
2243
|
+
if (hp == 0 or side.stored().hp <= hp) {
|
|
2244
|
+
return options.log.fail(battle.active(player), .Weak);
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
side.stored().hp -= hp;
|
|
2248
|
+
side.active.volatiles.substitute = hp;
|
|
2249
|
+
side.active.volatiles.Substitute = true;
|
|
2250
|
+
side.active.volatiles.bind = .{};
|
|
2251
|
+
try options.log.start(battle.active(player), .Substitute);
|
|
2252
|
+
try options.log.damage(battle.active(player), side.stored(), .None);
|
|
1645
2253
|
}
|
|
1646
2254
|
|
|
1647
|
-
fn
|
|
1648
|
-
|
|
2255
|
+
pub fn sunnyDay(battle: anytype, _: Player, _: *State, options: anytype) !void {
|
|
2256
|
+
return weather(battle, .Sun, options);
|
|
1649
2257
|
}
|
|
1650
2258
|
|
|
1651
|
-
fn
|
|
1652
|
-
|
|
2259
|
+
fn weather(battle: anytype, w: Weather, options: anytype) !void {
|
|
2260
|
+
battle.field.weather = w;
|
|
2261
|
+
battle.field.weather_duration = 5;
|
|
2262
|
+
try options.log.weather(w, .None);
|
|
1653
2263
|
}
|
|
1654
2264
|
|
|
1655
|
-
fn teleport(battle: anytype, player: Player,
|
|
1656
|
-
|
|
2265
|
+
pub fn teleport(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2266
|
+
if (showdown) return;
|
|
2267
|
+
|
|
2268
|
+
try options.log.fail(battle.active(player), .None);
|
|
2269
|
+
try options.log.laststill();
|
|
1657
2270
|
}
|
|
1658
2271
|
|
|
1659
|
-
fn thief(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1660
|
-
|
|
2272
|
+
pub fn thief(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2273
|
+
const side = battle.side(player);
|
|
2274
|
+
const foe = battle.foe(player);
|
|
2275
|
+
|
|
2276
|
+
if (side.stored().item != .None or foe.stored().item == .None) return;
|
|
2277
|
+
// Pokémon Showdown incorrectly allows you steal Mail
|
|
2278
|
+
if (!showdown and foe.stored().item.isMail()) return;
|
|
2279
|
+
if (!state.proc) return;
|
|
2280
|
+
|
|
2281
|
+
side.stored().item = foe.stored().item;
|
|
2282
|
+
foe.stored().item = .None;
|
|
2283
|
+
|
|
2284
|
+
try options.log.item(
|
|
2285
|
+
battle.active(player),
|
|
2286
|
+
side.stored().item,
|
|
2287
|
+
battle.active(player.foe()),
|
|
2288
|
+
);
|
|
1661
2289
|
}
|
|
1662
2290
|
|
|
1663
|
-
fn thrashing(battle: anytype, player: Player,
|
|
1664
|
-
|
|
2291
|
+
pub fn thrashing(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2292
|
+
var side = battle.side(player);
|
|
2293
|
+
if (Status.is(side.stored().status, .SLP)) return;
|
|
2294
|
+
|
|
2295
|
+
assert(!side.active.volatiles.Thrashing);
|
|
2296
|
+
side.active.volatiles.Thrashing = true;
|
|
2297
|
+
side.active.volatiles.attacks = Rolls.attackingDuration(battle, player, options);
|
|
2298
|
+
// TODO wSomeoneIsRampaging = true;
|
|
1665
2299
|
}
|
|
1666
2300
|
|
|
1667
|
-
fn
|
|
1668
|
-
|
|
2301
|
+
pub fn transform(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2302
|
+
var side = battle.side(player);
|
|
2303
|
+
const foe = battle.foe(player);
|
|
2304
|
+
const foe_ident = battle.active(player.foe());
|
|
2305
|
+
|
|
2306
|
+
side.last_used_move = .None;
|
|
2307
|
+
side.active.volatiles.dirty = false;
|
|
2308
|
+
|
|
2309
|
+
if (foe.active.volatiles.Transform) {
|
|
2310
|
+
return options.log.fail(battle.active(player), .None);
|
|
2311
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2312
|
+
if (!showdown) return options.log.fail(battle.active(player), .None);
|
|
2313
|
+
try options.log.lastmiss();
|
|
2314
|
+
return options.log.miss(battle.active(player));
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
side.active.volatiles.Transform = true;
|
|
2318
|
+
side.active.volatiles.transform = foe_ident.int();
|
|
2319
|
+
side.active.volatiles.disable = .{};
|
|
2320
|
+
|
|
2321
|
+
// HP is not copied by Transform
|
|
2322
|
+
side.active.stats.atk = foe.active.stats.atk;
|
|
2323
|
+
side.active.stats.def = foe.active.stats.def;
|
|
2324
|
+
side.active.stats.spe = foe.active.stats.spe;
|
|
2325
|
+
side.active.stats.spa = foe.active.stats.spa;
|
|
2326
|
+
side.active.stats.spd = foe.active.stats.spd;
|
|
2327
|
+
|
|
2328
|
+
side.active.species = foe.active.species;
|
|
2329
|
+
side.active.types = foe.active.types;
|
|
2330
|
+
side.active.boosts = foe.active.boosts;
|
|
2331
|
+
for (foe.active.moves, 0..) |m, i| {
|
|
2332
|
+
side.active.moves[i].id = m.id;
|
|
2333
|
+
side.active.moves[i].pp = if (m.id != .None) if (m.id == .Sketch) 1 else 5 else 0;
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
try options.log.transform(battle.active(player), foe_ident);
|
|
1669
2337
|
}
|
|
1670
2338
|
|
|
1671
|
-
fn
|
|
1672
|
-
|
|
2339
|
+
pub fn triAttack(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2340
|
+
// FIXME only commit effectchance and triattack rolls if actually does something...
|
|
2341
|
+
try effectChance(battle, player, state, options);
|
|
2342
|
+
if (state.proc) {
|
|
2343
|
+
try switch (try Rolls.triAttack(battle, player, options)) {
|
|
2344
|
+
.PAR => paralyzeChance(battle, player, state, options),
|
|
2345
|
+
.FRZ => freezeChance(battle, player, state, options),
|
|
2346
|
+
.BRN => burnChance(battle, player, state, options),
|
|
2347
|
+
};
|
|
2348
|
+
} else if (!showdown) battle.rng.advance(1);
|
|
1673
2349
|
}
|
|
1674
2350
|
|
|
1675
|
-
fn tripleKick(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2351
|
+
pub fn tripleKick(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1676
2352
|
_ = .{ battle, player, state, options }; // TODO
|
|
1677
2353
|
}
|
|
1678
2354
|
|
|
1679
|
-
|
|
1680
|
-
|
|
2355
|
+
const ALL_STAT_UP_STATS = [_][]const u8{ "atk", "def", "spe", "spa", "spd" };
|
|
2356
|
+
const ALL_STAT_UP_REASONS =
|
|
2357
|
+
[_]Boost{ .Attack, .Defense, .Speed, .SpecialAttack, .SpecialDefense };
|
|
2358
|
+
|
|
2359
|
+
pub fn allStatUpChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2360
|
+
var side = battle.side(player);
|
|
2361
|
+
|
|
2362
|
+
var stats = &side.active.stats;
|
|
2363
|
+
var boosts = &side.active.boosts;
|
|
2364
|
+
|
|
2365
|
+
if (!state.proc) return;
|
|
2366
|
+
inline for (ALL_STAT_UP_STATS, ALL_STAT_UP_REASONS) |s, r| {
|
|
2367
|
+
assert(@field(boosts, s) >= -6 and @field(boosts, s) <= 6);
|
|
2368
|
+
if (@field(boosts, s) == 6 or @field(stats, s) == MAX_STAT_VALUE) {
|
|
2369
|
+
try options.log.boost(battle.active(player), r, 0);
|
|
2370
|
+
} else {
|
|
2371
|
+
@field(boosts, s) = @intCast(@min(6, @as(i8, @field(boosts, s)) + 1));
|
|
2372
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
|
|
2373
|
+
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2374
|
+
@field(stats, s) = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2375
|
+
try options.log.boost(battle.active(player), r, 1);
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
statusModify(side.stored().status, stats);
|
|
1681
2380
|
}
|
|
1682
2381
|
|
|
1683
|
-
fn boost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1684
|
-
|
|
2382
|
+
pub fn boost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2383
|
+
var log = options.log;
|
|
2384
|
+
var side = battle.side(player);
|
|
2385
|
+
const ident = battle.active(player);
|
|
2386
|
+
|
|
2387
|
+
if (state.miss) return log.fail(ident, .None);
|
|
2388
|
+
|
|
2389
|
+
var stats = &side.active.stats;
|
|
2390
|
+
var boosts = &side.active.boosts;
|
|
2391
|
+
|
|
2392
|
+
const move = Move.get(state.move);
|
|
2393
|
+
switch (move.effect) {
|
|
2394
|
+
.AttackUp1, .AttackUp2, .Rage, .Swagger => {
|
|
2395
|
+
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2396
|
+
if (boosts.atk == 6 or stats.atk == MAX_STAT_VALUE) return log.fail(ident, .None);
|
|
2397
|
+
const n: u2 = if (move.effect == .AttackUp2 or move.effect == .Swagger) 2 else 1;
|
|
2398
|
+
boosts.atk = @as(i4, @intCast(@min(6, @as(i8, boosts.atk) + n)));
|
|
2399
|
+
const reason = if (showdown and move.effect == .Rage) Boost.Rage else Boost.Attack;
|
|
2400
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2401
|
+
const stat = unmodifiedStats(battle, side).atk;
|
|
2402
|
+
stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2403
|
+
try log.boost(ident, reason, n);
|
|
2404
|
+
},
|
|
2405
|
+
.DefenseUp1, .DefenseUp2, .SkullBash => {
|
|
2406
|
+
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2407
|
+
if (boosts.def == 6 or stats.def == MAX_STAT_VALUE) return log.fail(ident, .None);
|
|
2408
|
+
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
2409
|
+
boosts.def = @intCast(@min(6, @as(i8, boosts.def) + n));
|
|
2410
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2411
|
+
const stat = unmodifiedStats(battle, side).def;
|
|
2412
|
+
stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2413
|
+
try log.boost(ident, .Defense, n);
|
|
2414
|
+
},
|
|
2415
|
+
.SpeedUp2 => {
|
|
2416
|
+
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2417
|
+
if (boosts.spe == 6 or stats.spe == MAX_STAT_VALUE) return log.fail(ident, .None);
|
|
2418
|
+
boosts.spe = @intCast(@min(6, @as(i8, boosts.spe) + 2));
|
|
2419
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2420
|
+
const stat = unmodifiedStats(battle, side).spe;
|
|
2421
|
+
stats.spe = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2422
|
+
try log.boost(ident, .Speed, 2);
|
|
2423
|
+
},
|
|
2424
|
+
.SpAtkUp1 => {
|
|
2425
|
+
assert(boosts.spa >= -6 and boosts.spa <= 6);
|
|
2426
|
+
if (boosts.spa == 6 or stats.spa == MAX_STAT_VALUE) return log.fail(ident, .None);
|
|
2427
|
+
boosts.spa = @intCast(@min(6, @as(i8, boosts.spa) + 1));
|
|
2428
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.spa) + 6))];
|
|
2429
|
+
const stat = unmodifiedStats(battle, side).spa;
|
|
2430
|
+
stats.spa = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2431
|
+
try log.boost(ident, .SpecialAttack, 1);
|
|
2432
|
+
},
|
|
2433
|
+
.SpDefUp2 => {
|
|
2434
|
+
assert(boosts.spd >= -6 and boosts.spd <= 6);
|
|
2435
|
+
if (boosts.spd == 6 or stats.spd == MAX_STAT_VALUE) return log.fail(ident, .None);
|
|
2436
|
+
boosts.spd = @intCast(@min(6, @as(i8, boosts.spd) + 2));
|
|
2437
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.spd) + 6))];
|
|
2438
|
+
const stat = unmodifiedStats(battle, side).spd;
|
|
2439
|
+
stats.spd = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2440
|
+
try log.boost(ident, .SpecialDefense, 2);
|
|
2441
|
+
},
|
|
2442
|
+
.EvasionUp1 => {
|
|
2443
|
+
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2444
|
+
if (boosts.evasion == 6) return log.fail(ident, .None);
|
|
2445
|
+
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2446
|
+
try log.boost(ident, .Evasion, 1);
|
|
2447
|
+
if (state.move == .Minimize) side.active.volatiles.minimized = true;
|
|
2448
|
+
},
|
|
2449
|
+
else => unreachable,
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
statusModify(side.stored().status, stats);
|
|
1685
2453
|
}
|
|
1686
2454
|
|
|
1687
|
-
fn unboost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1688
|
-
|
|
2455
|
+
pub fn unboost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2456
|
+
var log = options.log;
|
|
2457
|
+
var foe = battle.foe(player);
|
|
2458
|
+
const foe_ident = battle.active(player.foe());
|
|
2459
|
+
|
|
2460
|
+
if (foe.active.volatiles.Mist) {
|
|
2461
|
+
try log.activate(foe_ident, .Mist);
|
|
2462
|
+
return log.fail(foe_ident, .None);
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
const fail = foe.active.volatiles.Substitute or state.miss or !state.proc;
|
|
2466
|
+
|
|
2467
|
+
var stats = &foe.active.stats;
|
|
2468
|
+
var boosts = &foe.active.boosts;
|
|
2469
|
+
|
|
2470
|
+
const move = Move.get(state.move);
|
|
2471
|
+
switch (move.effect) {
|
|
2472
|
+
.AttackDown1, .AttackDown2, .AttackDownChance => {
|
|
2473
|
+
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2474
|
+
if (fail or boosts.atk == -6) {
|
|
2475
|
+
return if (move.effect != .AttackDownChance) try log.fail(foe_ident, .None);
|
|
2476
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2477
|
+
try options.log.lastmiss();
|
|
2478
|
+
return options.log.miss(battle.active(player));
|
|
2479
|
+
} else if (stats.atk == 1) {
|
|
2480
|
+
return if (move.effect != .AttackDownChance) try log.fail(foe_ident, .None);
|
|
2481
|
+
}
|
|
2482
|
+
const n: u2 = if (move.effect == .AttackDown2) 2 else 1;
|
|
2483
|
+
boosts.atk = @intCast(@max(-6, @as(i8, boosts.atk) - n));
|
|
2484
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2485
|
+
const stat = unmodifiedStats(battle, foe).atk;
|
|
2486
|
+
stats.atk = @max(1, stat * mod[0] / mod[1]);
|
|
2487
|
+
try log.boost(foe_ident, .Attack, -@as(i8, n));
|
|
2488
|
+
},
|
|
2489
|
+
.DefenseDown1, .DefenseDown2, .DefenseDownChance => {
|
|
2490
|
+
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2491
|
+
if (fail or boosts.def == -6) {
|
|
2492
|
+
return if (move.effect != .DefenseDownChance) try log.fail(foe_ident, .None);
|
|
2493
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2494
|
+
try options.log.lastmiss();
|
|
2495
|
+
return options.log.miss(battle.active(player));
|
|
2496
|
+
} else if (stats.def == 1) {
|
|
2497
|
+
return if (move.effect != .DefenseDownChance) try log.fail(foe_ident, .None);
|
|
2498
|
+
}
|
|
2499
|
+
const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
|
|
2500
|
+
boosts.def = @intCast(@max(-6, @as(i8, boosts.def) - n));
|
|
2501
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2502
|
+
const stat = unmodifiedStats(battle, foe).def;
|
|
2503
|
+
stats.def = @max(1, stat * mod[0] / mod[1]);
|
|
2504
|
+
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2505
|
+
},
|
|
2506
|
+
.SpeedDown1, .SpeedDown2, .SpeedDownChance => {
|
|
2507
|
+
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2508
|
+
if (fail or boosts.spe == -6) {
|
|
2509
|
+
return if (move.effect != .SpeedDownChance) try log.fail(foe_ident, .None);
|
|
2510
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2511
|
+
try options.log.lastmiss();
|
|
2512
|
+
return options.log.miss(battle.active(player));
|
|
2513
|
+
} else if (stats.spe == 1) {
|
|
2514
|
+
return if (move.effect != .SpeedDownChance) try log.fail(foe_ident, .None);
|
|
2515
|
+
}
|
|
2516
|
+
const n: u2 = if (move.effect == .SpeedDown2) 2 else 1;
|
|
2517
|
+
boosts.spe = @intCast(@max(-6, @as(i8, boosts.spe) - 1));
|
|
2518
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2519
|
+
const stat = unmodifiedStats(battle, foe).spe;
|
|
2520
|
+
stats.spe = @max(1, stat * mod[0] / mod[1]);
|
|
2521
|
+
try log.boost(foe_ident, .Speed, -@as(i8, n));
|
|
2522
|
+
assert(boosts.spe >= -6);
|
|
2523
|
+
},
|
|
2524
|
+
.SpDefDownChance => {
|
|
2525
|
+
assert(boosts.spd >= -6 and boosts.spd <= 6);
|
|
2526
|
+
if (fail or boosts.spd == -6) return;
|
|
2527
|
+
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2528
|
+
try options.log.lastmiss();
|
|
2529
|
+
return options.log.miss(battle.active(player));
|
|
2530
|
+
} else if (stats.spd == 1) return;
|
|
2531
|
+
boosts.spd = @intCast(@max(-6, @as(i8, boosts.spd) - 1));
|
|
2532
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.spd) + 6))];
|
|
2533
|
+
const stat = unmodifiedStats(battle, foe).spd;
|
|
2534
|
+
stats.spd = @max(1, stat * mod[0] / mod[1]);
|
|
2535
|
+
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2536
|
+
},
|
|
2537
|
+
.AccuracyDown1, .AccuracyDownChance => {
|
|
2538
|
+
assert(boosts.accuracy >= -6 and boosts.accuracy <= 6);
|
|
2539
|
+
if (fail or boosts.accuracy == -6) {
|
|
2540
|
+
return if (move.effect != .AccuracyDownChance) try log.fail(foe_ident, .None);
|
|
2541
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2542
|
+
try options.log.lastmiss();
|
|
2543
|
+
return options.log.miss(battle.active(player));
|
|
2544
|
+
}
|
|
2545
|
+
boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
|
|
2546
|
+
try log.boost(foe_ident, .Accuracy, -1);
|
|
2547
|
+
},
|
|
2548
|
+
else => unreachable,
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
statusModify(foe.stored().status, stats);
|
|
1689
2552
|
}
|
|
1690
2553
|
};
|
|
1691
2554
|
|
|
2555
|
+
fn unmodifiedStats(battle: anytype, side: *Side) *Stats(u16) {
|
|
2556
|
+
if (!side.active.volatiles.Transform) return &side.stored().stats;
|
|
2557
|
+
const id = ID.from(side.active.volatiles.transform);
|
|
2558
|
+
return &battle.side(id.player).pokemon[id.id - 1].stats;
|
|
2559
|
+
}
|
|
2560
|
+
|
|
1692
2561
|
fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
1693
2562
|
if (Status.is(status, .PAR)) {
|
|
1694
2563
|
stats.spe = @max(stats.spe / 4, 1);
|
|
@@ -1697,20 +2566,20 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
1697
2566
|
}
|
|
1698
2567
|
}
|
|
1699
2568
|
|
|
1700
|
-
|
|
2569
|
+
fn isForced(active: anytype) bool {
|
|
1701
2570
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
1702
2571
|
active.volatiles.Thrashing or active.volatiles.Charging or
|
|
1703
2572
|
active.volatiles.Rollout;
|
|
1704
2573
|
}
|
|
1705
2574
|
|
|
1706
|
-
|
|
2575
|
+
fn convertible(active: ActivePokemon, m: Move) bool {
|
|
1707
2576
|
if (m == .None) return false;
|
|
1708
2577
|
const t = Move.get(m).type;
|
|
1709
2578
|
return t != .@"???" and !active.types.includes(t);
|
|
1710
2579
|
}
|
|
1711
2580
|
|
|
1712
2581
|
pub const Rolls = struct {
|
|
1713
|
-
|
|
2582
|
+
fn speedTie(battle: anytype, options: anytype) !bool {
|
|
1714
2583
|
const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
|
|
1715
2584
|
player == .P1
|
|
1716
2585
|
else if (showdown)
|
|
@@ -1722,7 +2591,7 @@ pub const Rolls = struct {
|
|
|
1722
2591
|
return p1;
|
|
1723
2592
|
}
|
|
1724
2593
|
|
|
1725
|
-
|
|
2594
|
+
fn quickClaw(battle: anytype, player: Player, options: anytype) !bool {
|
|
1726
2595
|
const qkc = if (options.calc.overridden(player, .quick_claw)) |val|
|
|
1727
2596
|
val == .true
|
|
1728
2597
|
else if (showdown)
|
|
@@ -1734,7 +2603,7 @@ pub const Rolls = struct {
|
|
|
1734
2603
|
return qkc;
|
|
1735
2604
|
}
|
|
1736
2605
|
|
|
1737
|
-
|
|
2606
|
+
fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) !bool {
|
|
1738
2607
|
const ok = if (options.calc.overridden(player, .hit)) |val|
|
|
1739
2608
|
val == .true
|
|
1740
2609
|
else if (showdown)
|
|
@@ -1746,7 +2615,7 @@ pub const Rolls = struct {
|
|
|
1746
2615
|
return ok;
|
|
1747
2616
|
}
|
|
1748
2617
|
|
|
1749
|
-
|
|
2618
|
+
fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
|
|
1750
2619
|
const crit = if (options.calc.overridden(player, .critical_hit)) |val|
|
|
1751
2620
|
val == .true
|
|
1752
2621
|
else if (showdown)
|
|
@@ -1758,7 +2627,7 @@ pub const Rolls = struct {
|
|
|
1758
2627
|
return crit;
|
|
1759
2628
|
}
|
|
1760
2629
|
|
|
1761
|
-
|
|
2630
|
+
fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
1762
2631
|
const roll = if (options.calc.overridden(player, .damage)) |val|
|
|
1763
2632
|
val
|
|
1764
2633
|
else roll: {
|
|
@@ -1774,7 +2643,7 @@ pub const Rolls = struct {
|
|
|
1774
2643
|
return roll;
|
|
1775
2644
|
}
|
|
1776
2645
|
|
|
1777
|
-
|
|
2646
|
+
fn confused(battle: anytype, player: Player, options: anytype) !bool {
|
|
1778
2647
|
const cfz = if (options.calc.overridden(player, .confused)) |val|
|
|
1779
2648
|
val == .true
|
|
1780
2649
|
else if (showdown)
|
|
@@ -1786,7 +2655,7 @@ pub const Rolls = struct {
|
|
|
1786
2655
|
return cfz;
|
|
1787
2656
|
}
|
|
1788
2657
|
|
|
1789
|
-
|
|
2658
|
+
fn attract(battle: anytype, player: Player, options: anytype) !bool {
|
|
1790
2659
|
const cant = if (options.calc.overridden(player, .attract)) |val|
|
|
1791
2660
|
val == .true
|
|
1792
2661
|
else if (showdown)
|
|
@@ -1798,7 +2667,7 @@ pub const Rolls = struct {
|
|
|
1798
2667
|
return cant;
|
|
1799
2668
|
}
|
|
1800
2669
|
|
|
1801
|
-
|
|
2670
|
+
fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
|
|
1802
2671
|
const par = if (options.calc.overridden(player, .paralyzed)) |val|
|
|
1803
2672
|
val == .true
|
|
1804
2673
|
else if (showdown)
|
|
@@ -1810,7 +2679,7 @@ pub const Rolls = struct {
|
|
|
1810
2679
|
return par;
|
|
1811
2680
|
}
|
|
1812
2681
|
|
|
1813
|
-
|
|
2682
|
+
fn defrost(battle: anytype, player: Player, options: anytype) !bool {
|
|
1814
2683
|
const thaw = if (options.calc.overridden(player, .defrost)) |val|
|
|
1815
2684
|
val == .true
|
|
1816
2685
|
else if (showdown)
|
|
@@ -1822,7 +2691,7 @@ pub const Rolls = struct {
|
|
|
1822
2691
|
return thaw;
|
|
1823
2692
|
}
|
|
1824
2693
|
|
|
1825
|
-
|
|
2694
|
+
fn secondaryChance(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
|
|
1826
2695
|
// GLITCH: 100% secondary chance will still fail 1/256 of the time
|
|
1827
2696
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
1828
2697
|
val == .true
|
|
@@ -1835,7 +2704,7 @@ pub const Rolls = struct {
|
|
|
1835
2704
|
return proc;
|
|
1836
2705
|
}
|
|
1837
2706
|
|
|
1838
|
-
|
|
2707
|
+
fn item(battle: anytype, player: Player, options: anytype) !bool {
|
|
1839
2708
|
const proc = if (options.calc.overridden(player, .item)) |val|
|
|
1840
2709
|
val == .true
|
|
1841
2710
|
else if (showdown)
|
|
@@ -1847,23 +2716,23 @@ pub const Rolls = struct {
|
|
|
1847
2716
|
return proc;
|
|
1848
2717
|
}
|
|
1849
2718
|
|
|
1850
|
-
|
|
2719
|
+
fn triAttack(battle: anytype, player: Player, options: anytype) !TriAttack {
|
|
1851
2720
|
const status: TriAttack = if (options.calc.overridden(player, .tri_attack)) |val|
|
|
1852
|
-
val
|
|
2721
|
+
@enumFromInt(@intFromEnum(val) - 1)
|
|
1853
2722
|
else if (showdown)
|
|
1854
|
-
@enumFromInt(battle.rng.range(
|
|
2723
|
+
@enumFromInt(battle.rng.range(u2, 0, 2))
|
|
1855
2724
|
else loop: {
|
|
1856
2725
|
while (true) {
|
|
1857
|
-
const r = std.math.rotr(u8,
|
|
2726
|
+
const r = std.math.rotr(u8, battle.rng.next(), 4) & 3;
|
|
1858
2727
|
if (r != 0) break :loop @enumFromInt(r - 1);
|
|
1859
2728
|
}
|
|
1860
2729
|
};
|
|
1861
2730
|
|
|
1862
2731
|
try options.chance.triAttack(player, status);
|
|
1863
|
-
return status
|
|
2732
|
+
return status;
|
|
1864
2733
|
}
|
|
1865
2734
|
|
|
1866
|
-
|
|
2735
|
+
fn present(battle: anytype, player: Player, options: anytype) !u8 {
|
|
1867
2736
|
const power = if (options.calc.overridden(player, .present)) |val|
|
|
1868
2737
|
(val - 1) * 40
|
|
1869
2738
|
else if (showdown) power: {
|
|
@@ -1885,7 +2754,7 @@ pub const Rolls = struct {
|
|
|
1885
2754
|
return power;
|
|
1886
2755
|
}
|
|
1887
2756
|
|
|
1888
|
-
|
|
2757
|
+
fn magnitude(battle: anytype, player: Player, options: anytype) !u8 {
|
|
1889
2758
|
const num = if (options.calc.overridden(player, .magnitude)) |val|
|
|
1890
2759
|
val + 3
|
|
1891
2760
|
else if (showdown) num: {
|
|
@@ -1913,7 +2782,7 @@ pub const Rolls = struct {
|
|
|
1913
2782
|
return num;
|
|
1914
2783
|
}
|
|
1915
2784
|
|
|
1916
|
-
|
|
2785
|
+
fn tripleKick(battle: anytype, player: Player, options: anytype) !u2 {
|
|
1917
2786
|
const hits = if (options.calc.overridden(player, .triple_kick)) |val|
|
|
1918
2787
|
val
|
|
1919
2788
|
else if (showdown)
|
|
@@ -1930,7 +2799,7 @@ pub const Rolls = struct {
|
|
|
1930
2799
|
return hits;
|
|
1931
2800
|
}
|
|
1932
2801
|
|
|
1933
|
-
|
|
2802
|
+
fn spite(battle: anytype, player: Player, options: anytype) !u8 {
|
|
1934
2803
|
const pp = if (options.calc.overridden(player, .spite)) |val|
|
|
1935
2804
|
val + 1
|
|
1936
2805
|
else if (showdown)
|
|
@@ -1946,7 +2815,7 @@ pub const Rolls = struct {
|
|
|
1946
2815
|
// Conversion 2 can at most choose between 7 types (vs. a Grass-type attack)
|
|
1947
2816
|
var CONVERSION_2: [7]Type = [_]Type{.@"???"} ** 7;
|
|
1948
2817
|
|
|
1949
|
-
|
|
2818
|
+
fn conversion2(battle: anytype, player: Player, mtype: Type, options: anytype) !Type {
|
|
1950
2819
|
var i: u8 = 0;
|
|
1951
2820
|
const neutral = @intFromEnum(Effectiveness.Neutral);
|
|
1952
2821
|
assert(mtype != .@"???");
|
|
@@ -1980,7 +2849,7 @@ pub const Rolls = struct {
|
|
|
1980
2849
|
return ty;
|
|
1981
2850
|
}
|
|
1982
2851
|
|
|
1983
|
-
|
|
2852
|
+
fn protect(battle: anytype, player: Player, count: u8, options: anytype) !bool {
|
|
1984
2853
|
assert(count <= 8);
|
|
1985
2854
|
const num = if (count == 8) 0 else @as(u8, 255) >> @intCast(count);
|
|
1986
2855
|
|
|
@@ -2074,7 +2943,7 @@ pub const Rolls = struct {
|
|
|
2074
2943
|
return slot;
|
|
2075
2944
|
}
|
|
2076
2945
|
|
|
2077
|
-
|
|
2946
|
+
fn forceSwitch(
|
|
2078
2947
|
battle: anytype,
|
|
2079
2948
|
player: Player,
|
|
2080
2949
|
slots: []u4,
|
|
@@ -2112,7 +2981,7 @@ pub const Rolls = struct {
|
|
|
2112
2981
|
|
|
2113
2982
|
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
2114
2983
|
|
|
2115
|
-
|
|
2984
|
+
fn multiHit(battle: anytype, player: Player, options: anytype) !u3 {
|
|
2116
2985
|
const n: u3 = if (options.calc.overridden(player, .multi_hit)) |val|
|
|
2117
2986
|
@intCast(val)
|
|
2118
2987
|
else if (showdown)
|
|
@@ -2127,30 +2996,30 @@ pub const Rolls = struct {
|
|
|
2127
2996
|
return n;
|
|
2128
2997
|
}
|
|
2129
2998
|
|
|
2130
|
-
|
|
2999
|
+
fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
|
|
2131
3000
|
const power = if (options.calc.overridden(player, .psywave)) |val|
|
|
2132
|
-
val
|
|
3001
|
+
val
|
|
2133
3002
|
else if (showdown)
|
|
2134
|
-
battle.rng.range(u8,
|
|
3003
|
+
battle.rng.range(u8, 1, max)
|
|
2135
3004
|
else power: {
|
|
2136
3005
|
while (true) {
|
|
2137
3006
|
const r = battle.rng.next();
|
|
2138
|
-
if (r < max) break :power r;
|
|
3007
|
+
if (r != 0 and r < max) break :power r;
|
|
2139
3008
|
}
|
|
2140
3009
|
};
|
|
2141
3010
|
|
|
2142
|
-
assert(power < max);
|
|
3011
|
+
assert(power != 0 and power < max);
|
|
2143
3012
|
try options.chance.psywave(player, power, max);
|
|
2144
3013
|
return power;
|
|
2145
3014
|
}
|
|
2146
3015
|
|
|
2147
|
-
|
|
2148
|
-
const moves = battle.
|
|
3016
|
+
fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
3017
|
+
const moves = battle.side(player).active.moves;
|
|
2149
3018
|
|
|
2150
3019
|
var n: u2 = 0;
|
|
2151
3020
|
for (moves) |m| {
|
|
2152
|
-
if (m == .None) break;
|
|
2153
|
-
if (Move.get(m).metronome) n += 1;
|
|
3021
|
+
if (m.id == .None) break;
|
|
3022
|
+
if (Move.get(m.id).extra.metronome) n += 1;
|
|
2154
3023
|
}
|
|
2155
3024
|
|
|
2156
3025
|
// TODO: consider throwing error instead of rerolling?
|
|
@@ -2161,10 +3030,11 @@ pub const Rolls = struct {
|
|
|
2161
3030
|
|
|
2162
3031
|
const move: Move = overridden orelse move: {
|
|
2163
3032
|
if (showdown) {
|
|
2164
|
-
var r = battle.rng.range(u8, 0, Move.METRONOME.len - n);
|
|
3033
|
+
var r = battle.rng.range(u8, 0, @intCast(Move.METRONOME.len - n));
|
|
2165
3034
|
for (moves) |m| {
|
|
2166
|
-
if (m == .None
|
|
2167
|
-
if (
|
|
3035
|
+
if (m.id == .None) break;
|
|
3036
|
+
if (@intFromEnum(m.id) > @intFromEnum(Move.METRONOME[r])) break;
|
|
3037
|
+
if (Move.get(m.id).extra.metronome) r -= 1;
|
|
2168
3038
|
}
|
|
2169
3039
|
break :move Move.METRONOME[r];
|
|
2170
3040
|
} else {
|
|
@@ -2179,12 +3049,12 @@ pub const Rolls = struct {
|
|
|
2179
3049
|
}
|
|
2180
3050
|
};
|
|
2181
3051
|
|
|
2182
|
-
assert(Move.get(move).metronome);
|
|
3052
|
+
assert(Move.get(move).extra.metronome);
|
|
2183
3053
|
try options.chance.metronome(player, move, n);
|
|
2184
3054
|
return move;
|
|
2185
3055
|
}
|
|
2186
3056
|
|
|
2187
|
-
|
|
3057
|
+
fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2188
3058
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2189
3059
|
@intCast(val)
|
|
2190
3060
|
else if (showdown)
|
|
@@ -2201,7 +3071,7 @@ pub const Rolls = struct {
|
|
|
2201
3071
|
return duration + 1;
|
|
2202
3072
|
}
|
|
2203
3073
|
|
|
2204
|
-
|
|
3074
|
+
fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
|
|
2205
3075
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2206
3076
|
@intCast(val)
|
|
2207
3077
|
else if (showdown)
|
|
@@ -2215,7 +3085,7 @@ pub const Rolls = struct {
|
|
|
2215
3085
|
return duration;
|
|
2216
3086
|
}
|
|
2217
3087
|
|
|
2218
|
-
|
|
3088
|
+
fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
|
|
2219
3089
|
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2220
3090
|
@intCast(val)
|
|
2221
3091
|
else if (showdown)
|
|
@@ -2233,7 +3103,7 @@ pub const Rolls = struct {
|
|
|
2233
3103
|
return duration;
|
|
2234
3104
|
}
|
|
2235
3105
|
|
|
2236
|
-
|
|
3106
|
+
fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2237
3107
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2238
3108
|
@intCast(val)
|
|
2239
3109
|
else if (showdown)
|
|
@@ -2246,7 +3116,7 @@ pub const Rolls = struct {
|
|
|
2246
3116
|
return duration;
|
|
2247
3117
|
}
|
|
2248
3118
|
|
|
2249
|
-
|
|
3119
|
+
fn bindingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2250
3120
|
const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
|
|
2251
3121
|
@intCast(val)
|
|
2252
3122
|
else if (showdown)
|
|
@@ -2261,7 +3131,7 @@ pub const Rolls = struct {
|
|
|
2261
3131
|
}
|
|
2262
3132
|
|
|
2263
3133
|
// TODO: consider sharing implementation with bindingDuration
|
|
2264
|
-
|
|
3134
|
+
fn encoreDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2265
3135
|
const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
|
|
2266
3136
|
@intCast(val)
|
|
2267
3137
|
else if (showdown)
|