@pkmn/engine 0.1.0-dev.14ca6108 → 0.1.0-dev.15f64810
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -3
- package/README.md +44 -19
- package/build/pkg/addon.js +6 -7
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/data.d.ts +4 -4
- package/build/pkg/data.js.map +1 -1
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/index.d.ts +2 -1
- package/build/pkg/index.js.map +1 -1
- package/build/pkg/protocol.js +5 -5
- package/build/pkg/protocol.js.map +1 -1
- package/build/test/display.js +2 -2
- package/build/test/display.js.map +1 -1
- package/build/tools/debug.js +5 -4
- package/build/tools/debug.js.map +1 -1
- package/build.zig +100 -102
- package/package.json +34 -23
- package/src/bin/install-pkmn-engine +26 -18
- package/src/lib/common/data.zig +8 -8
- package/src/lib/common/debug.zig +2 -2
- package/src/lib/common/optional.zig +1 -1
- package/src/lib/common/options.zig +2 -0
- package/src/lib/common/protocol.zig +397 -384
- package/src/lib/common/rational.zig +11 -8
- package/src/lib/common/rng.zig +20 -11
- package/src/lib/gen1/calc.zig +132 -29
- package/src/lib/gen1/chance.zig +91 -92
- package/src/lib/gen1/data/moves.zig +10 -10
- package/src/lib/gen1/data/species.zig +3 -3
- package/src/lib/gen1/data/types.zig +5 -5
- package/src/lib/gen1/data.zig +35 -35
- package/src/lib/gen1/helpers.zig +136 -49
- package/src/lib/gen1/mechanics.zig +327 -307
- package/src/lib/gen2/calc.zig +143 -0
- package/src/lib/gen2/chance.zig +1315 -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 +27 -37
- package/src/lib/gen2/generated.zig +806 -0
- package/src/lib/gen2/helpers.zig +37 -7
- package/src/lib/gen2/mechanics.zig +1633 -484
- package/src/lib/{binding/node.zig → node.zig} +9 -12
- package/src/lib/pkmn.zig +27 -12
- package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
- package/src/lib/gen1/README.md +0 -894
- package/src/lib/gen2/README.md +0 -330
|
@@ -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,9 +25,10 @@ const Player = common.Player;
|
|
|
24
25
|
const Result = common.Result;
|
|
25
26
|
|
|
26
27
|
const Activate = protocol.Activate;
|
|
27
|
-
const
|
|
28
|
+
const Boost = protocol.Boost;
|
|
28
29
|
const Damage = protocol.Damage;
|
|
29
30
|
const End = protocol.End;
|
|
31
|
+
const Fail = protocol.Fail;
|
|
30
32
|
const Heal = protocol.Heal;
|
|
31
33
|
const Start = protocol.Start;
|
|
32
34
|
|
|
@@ -35,6 +37,7 @@ const Gen12 = rng.Gen12;
|
|
|
35
37
|
const PointerType = util.PointerType;
|
|
36
38
|
|
|
37
39
|
const ActivePokemon = data.ActivePokemon;
|
|
40
|
+
const Boosts = data.Boosts;
|
|
38
41
|
const Effectiveness = data.Effectiveness;
|
|
39
42
|
const Item = data.Item;
|
|
40
43
|
const Move = data.Move;
|
|
@@ -48,6 +51,7 @@ const TriAttack = data.TriAttack;
|
|
|
48
51
|
const Type = data.Type;
|
|
49
52
|
const Types = data.Types;
|
|
50
53
|
const Volatiles = data.Volatiles;
|
|
54
|
+
const Weather = data.Weather;
|
|
51
55
|
|
|
52
56
|
// zig fmt: off
|
|
53
57
|
const STAT_BOOSTS = &[_][2]u8{
|
|
@@ -93,21 +97,28 @@ const CRITICAL_HITS = &[_]u8{
|
|
|
93
97
|
|
|
94
98
|
const MAX_STAT_VALUE = 999;
|
|
95
99
|
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
const STATS = [_][]const u8{ "atk", "def", "spe", "spa", "spd" };
|
|
101
|
+
|
|
102
|
+
pub const State = struct {
|
|
103
|
+
damage: u16 = 0, // wCurDamage
|
|
104
|
+
effectiveness: u16 = Effectiveness.neutral, // wTypeModifier
|
|
105
|
+
bp: u8, // wPlayerMoveStructPower
|
|
106
|
+
type: Type, // BATTLE_VARS_MOVE_TYPE
|
|
107
|
+
accuracy: u8, // wEnemyMoveStruct + MOVE_ACC
|
|
108
|
+
move: Move, // wCurPlayerMove
|
|
109
|
+
from: Move = .None,
|
|
110
|
+
mslot: u4, // wCurMoveNum
|
|
111
|
+
first: bool, // wEnemyGoesFirst
|
|
112
|
+
miss: bool = false, // wAttackMissed
|
|
113
|
+
crit: bool = false, // wCriticalHit = 1
|
|
114
|
+
ohko: bool = false, // wCriticalHit = 2
|
|
115
|
+
proc: bool = true, // wEffectFailed
|
|
105
116
|
|
|
106
117
|
comptime {
|
|
107
|
-
assert(@sizeOf(State) ==
|
|
118
|
+
assert(@sizeOf(State) == 16);
|
|
108
119
|
}
|
|
109
120
|
|
|
110
|
-
|
|
121
|
+
pub fn immune(self: *State) bool {
|
|
111
122
|
return self.effectiveness == 0;
|
|
112
123
|
}
|
|
113
124
|
};
|
|
@@ -129,16 +140,16 @@ fn start(battle: anytype, options: anytype) !Result {
|
|
|
129
140
|
const p1 = battle.side(.P1);
|
|
130
141
|
const p2 = battle.side(.P2);
|
|
131
142
|
|
|
132
|
-
|
|
143
|
+
const p1_slot = findFirstAlive(p1);
|
|
133
144
|
assert(!showdown or p1_slot == 1);
|
|
134
145
|
if (p1_slot == 0) return if (findFirstAlive(p2) == 0) Result.Tie else Result.Lose;
|
|
135
146
|
|
|
136
|
-
|
|
147
|
+
const p2_slot = findFirstAlive(p2);
|
|
137
148
|
assert(!showdown or p2_slot == 1);
|
|
138
149
|
if (p2_slot == 0) return Result.Win;
|
|
139
150
|
|
|
140
|
-
try switchIn(battle, .P1, p1_slot,
|
|
141
|
-
try switchIn(battle, .P2, p2_slot,
|
|
151
|
+
try switchIn(battle, .P1, p1_slot, .Initial, options);
|
|
152
|
+
try switchIn(battle, .P2, p2_slot, .Initial, options);
|
|
142
153
|
|
|
143
154
|
return endTurn(battle, options);
|
|
144
155
|
}
|
|
@@ -212,7 +223,9 @@ fn selectMove(battle: anytype, player: Player, choice: Choice) void {
|
|
|
212
223
|
volatiles.protect = 0;
|
|
213
224
|
}
|
|
214
225
|
|
|
215
|
-
|
|
226
|
+
const SwitchIn = enum { Initial, Switch, Drag };
|
|
227
|
+
|
|
228
|
+
fn switchIn(battle: anytype, player: Player, slot: u8, kind: SwitchIn, options: anytype) !void {
|
|
216
229
|
var side = battle.side(player);
|
|
217
230
|
var foe = battle.foe(player);
|
|
218
231
|
|
|
@@ -220,7 +233,7 @@ fn switchIn(battle: anytype, player: Player, slot: u8, initial: bool, options: a
|
|
|
220
233
|
const incoming = side.get(slot);
|
|
221
234
|
|
|
222
235
|
assert(incoming.hp != 0);
|
|
223
|
-
assert(slot != 1 or
|
|
236
|
+
assert(slot != 1 or kind == .Initial);
|
|
224
237
|
|
|
225
238
|
const out = side.pokemon[0].position;
|
|
226
239
|
side.pokemon[0].position = side.pokemon[slot - 1].position;
|
|
@@ -238,12 +251,16 @@ fn switchIn(battle: anytype, player: Player, slot: u8, initial: bool, options: a
|
|
|
238
251
|
|
|
239
252
|
statusModify(incoming.status, &active.stats);
|
|
240
253
|
|
|
241
|
-
|
|
254
|
+
if (kind == .Drag) {
|
|
255
|
+
try options.log.drag(.{ battle.active(player), incoming });
|
|
256
|
+
} else {
|
|
257
|
+
try options.log.switched(.{ battle.active(player), incoming });
|
|
258
|
+
}
|
|
242
259
|
options.chance.switched(player, side.pokemon[0].position, out);
|
|
243
260
|
|
|
244
261
|
if (side.conditions.Spikes and !active.types.includes(.Flying)) {
|
|
245
262
|
incoming.hp -|= @max(incoming.stats.hp / 8, 1);
|
|
246
|
-
try options.log.damage(battle.active(player), incoming, .Spikes);
|
|
263
|
+
try options.log.damage(.{ battle.active(player), incoming, .Spikes });
|
|
247
264
|
}
|
|
248
265
|
}
|
|
249
266
|
|
|
@@ -257,7 +274,13 @@ fn turnOrder(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Player
|
|
|
257
274
|
// TODO: add to pending and only commit roll on Pokémon Showdown if roll is used
|
|
258
275
|
const qkc = showdown and try Rolls.quickClaw(battle, .P1, options);
|
|
259
276
|
|
|
260
|
-
if ((c1.type == .Switch) != (c2.type == .Switch))
|
|
277
|
+
if ((c1.type == .Switch) != (c2.type == .Switch)) {
|
|
278
|
+
const m1 = .Pound; // TODO battle.side(.P1).last_selected_move;
|
|
279
|
+
const m2 = .Pound; // TODO battle.side(.P2).last_selected_move;
|
|
280
|
+
return if (c1.type == .Switch)
|
|
281
|
+
if (m1 == .Pursuit) .P2 else .P1
|
|
282
|
+
else if (m2 == .Pursuit) .P1 else .P2;
|
|
283
|
+
}
|
|
261
284
|
|
|
262
285
|
// https://www.smogon.com/forums/threads/adv-switch-priority.3622189/
|
|
263
286
|
// > In Gen 1 it's irrelevant [which player switches first] because switches happen instantly on
|
|
@@ -337,25 +360,33 @@ fn doTurn(
|
|
|
337
360
|
const volatiles = &battle.side(player).active.volatiles;
|
|
338
361
|
const foe_volatiles = &battle.foe(player).active.volatiles;
|
|
339
362
|
|
|
363
|
+
// Technically this happens at the very beginning of update but its unused until now
|
|
364
|
+
// TODO: move into state and pass into betweenTurns
|
|
365
|
+
volatiles.frozen = false;
|
|
366
|
+
foe_volatiles.frozen = false;
|
|
367
|
+
|
|
340
368
|
volatiles.DestinyBond = false;
|
|
341
369
|
if (try executeMove(battle, player, player_choice, true, options)) |r| return r;
|
|
342
370
|
foe_volatiles.Protect = false;
|
|
343
371
|
foe_volatiles.Endure = false;
|
|
344
372
|
foe_volatiles.DestinyBond = false;
|
|
373
|
+
if (try checkFaint(battle, foe_player, options)) |r| return r;
|
|
374
|
+
if (try handleResidual(battle, player, options)) {
|
|
375
|
+
if (try checkFaint(battle, player, options)) |r| return r;
|
|
376
|
+
}
|
|
345
377
|
|
|
346
378
|
// foe_volatiles.DestinyBond = false;
|
|
347
379
|
if (try executeMove(battle, foe_player, foe_choice, false, options)) |r| return r;
|
|
348
380
|
volatiles.Protect = false;
|
|
349
381
|
volatiles.Endure = false;
|
|
350
382
|
volatiles.DestinyBond = false;
|
|
383
|
+
if (try checkFaint(battle, player, options)) |r| return r;
|
|
384
|
+
if (try handleResidual(battle, foe_player, options)) {
|
|
385
|
+
if (try checkFaint(battle, foe_player, options)) |r| return r;
|
|
386
|
+
}
|
|
351
387
|
|
|
352
388
|
// FIXME
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
// FIXME
|
|
356
|
-
try betweenTurns(battle, 0, options);
|
|
357
|
-
|
|
358
|
-
return null;
|
|
389
|
+
return betweenTurns(battle, 0, options);
|
|
359
390
|
}
|
|
360
391
|
|
|
361
392
|
fn executeMove(
|
|
@@ -366,18 +397,30 @@ fn executeMove(
|
|
|
366
397
|
options: anytype,
|
|
367
398
|
) !?Result {
|
|
368
399
|
if (choice.type == .Switch) {
|
|
369
|
-
try switchIn(battle, player, choice.data,
|
|
400
|
+
try switchIn(battle, player, choice.data, .Switch, options);
|
|
370
401
|
return null;
|
|
371
402
|
}
|
|
372
403
|
|
|
373
404
|
assert(choice.type == .Move);
|
|
374
405
|
var mslot: u4 = @intCast(choice.data);
|
|
406
|
+
_ = &mslot;
|
|
375
407
|
var move = Move.Pound; // TODO
|
|
408
|
+
_ = &move;
|
|
376
409
|
|
|
377
410
|
if (!try beforeMove(battle, player, move, options)) return null;
|
|
378
411
|
|
|
379
|
-
|
|
380
|
-
|
|
412
|
+
// FIXME: need damage from doTurn
|
|
413
|
+
const m = Move.get(move);
|
|
414
|
+
var state: State = .{
|
|
415
|
+
.move = move,
|
|
416
|
+
.mslot = mslot,
|
|
417
|
+
.first = first,
|
|
418
|
+
.accuracy = m.accuracy,
|
|
419
|
+
.bp = m.bp,
|
|
420
|
+
.type = m.type,
|
|
421
|
+
};
|
|
422
|
+
try generated.runMove(battle, player, &state, options);
|
|
423
|
+
return null;
|
|
381
424
|
}
|
|
382
425
|
|
|
383
426
|
fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bool {
|
|
@@ -391,8 +434,8 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
391
434
|
|
|
392
435
|
if (volatiles.Recharging) {
|
|
393
436
|
volatiles.Recharging = false;
|
|
394
|
-
|
|
395
|
-
try log.cant(ident, .Recharge);
|
|
437
|
+
resetCant(volatiles);
|
|
438
|
+
try log.cant(.{ ident, .Recharge });
|
|
396
439
|
return false;
|
|
397
440
|
}
|
|
398
441
|
|
|
@@ -411,21 +454,21 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
411
454
|
}
|
|
412
455
|
|
|
413
456
|
if (duration == 0) {
|
|
414
|
-
try log.curestatus(ident, before, .Message);
|
|
457
|
+
try log.curestatus(.{ ident, before, .Message });
|
|
415
458
|
stored.status = 0; // clears EXT if present
|
|
416
|
-
|
|
459
|
+
resetCant(volatiles);
|
|
417
460
|
volatiles.Nightmare = false;
|
|
418
461
|
} else {
|
|
419
|
-
try log.cant(ident, .Sleep);
|
|
462
|
+
try log.cant(.{ ident, .Sleep });
|
|
420
463
|
if (move == .Snore or move == .SleepTalk) break :slp;
|
|
421
|
-
|
|
464
|
+
resetCant(volatiles);
|
|
422
465
|
return false;
|
|
423
466
|
}
|
|
424
467
|
}
|
|
425
468
|
|
|
426
469
|
if (Status.is(stored.status, .FRZ) and !(move == .FlameWheel or move == .SacredFire)) {
|
|
427
|
-
|
|
428
|
-
try log.cant(ident, .Freeze);
|
|
470
|
+
resetCant(volatiles);
|
|
471
|
+
try log.cant(.{ ident, .Freeze });
|
|
429
472
|
return false;
|
|
430
473
|
}
|
|
431
474
|
|
|
@@ -434,8 +477,8 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
434
477
|
// Pokémon can sometimes flinch multiple times from the same original hit
|
|
435
478
|
// if (!showdown) volatiles.Flinch = false;
|
|
436
479
|
volatiles.Flinch = false;
|
|
437
|
-
|
|
438
|
-
try log.cant(ident, .Flinch);
|
|
480
|
+
resetCant(volatiles);
|
|
481
|
+
try log.cant(.{ ident, .Flinch });
|
|
439
482
|
return false;
|
|
440
483
|
}
|
|
441
484
|
|
|
@@ -449,7 +492,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
449
492
|
|
|
450
493
|
if (volatiles.disable.duration == 0) {
|
|
451
494
|
volatiles.disable.move = 0;
|
|
452
|
-
try log.end(ident, .Disable);
|
|
495
|
+
try log.end(.{ ident, .Disable });
|
|
453
496
|
}
|
|
454
497
|
}
|
|
455
498
|
|
|
@@ -465,14 +508,14 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
465
508
|
|
|
466
509
|
if (volatiles.confusion == 0) {
|
|
467
510
|
volatiles.Confusion = false;
|
|
468
|
-
try log.end(ident, .Confusion);
|
|
511
|
+
try log.end(.{ ident, .Confusion });
|
|
469
512
|
} else {
|
|
470
|
-
try log.activate(ident, .Confusion);
|
|
513
|
+
try log.activate(.{ ident, .Confusion });
|
|
471
514
|
|
|
472
515
|
if (try Rolls.confused(battle, player, options)) {
|
|
473
516
|
volatiles.BeatUp = false;
|
|
474
517
|
volatiles.Flinch = false;
|
|
475
|
-
|
|
518
|
+
resetCant(volatiles);
|
|
476
519
|
|
|
477
520
|
// TODO HitConfusion
|
|
478
521
|
|
|
@@ -482,11 +525,11 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
482
525
|
}
|
|
483
526
|
|
|
484
527
|
if (volatiles.Attract) {
|
|
485
|
-
try log.activate(ident, .Attract);
|
|
528
|
+
try log.activate(.{ ident, .Attract });
|
|
486
529
|
|
|
487
530
|
if (try Rolls.attract(battle, player, options)) {
|
|
488
|
-
|
|
489
|
-
try log.cant(ident, .Attract);
|
|
531
|
+
resetCant(volatiles);
|
|
532
|
+
try log.cant(.{ ident, .Attract });
|
|
490
533
|
return false;
|
|
491
534
|
}
|
|
492
535
|
}
|
|
@@ -496,22 +539,22 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
496
539
|
const m = side.active.moves[side.active.volatiles.disable.move - 1].id;
|
|
497
540
|
if (m != .None and m == move) {
|
|
498
541
|
side.active.volatiles.Charging = false;
|
|
499
|
-
|
|
500
|
-
try options.log.
|
|
542
|
+
resetCant(volatiles);
|
|
543
|
+
try options.log.cant(.{ ident, .Disable, move });
|
|
501
544
|
return false;
|
|
502
545
|
}
|
|
503
546
|
}
|
|
504
547
|
|
|
505
548
|
if (Status.is(stored.status, .PAR) and try Rolls.paralyzed(battle, player, options)) {
|
|
506
|
-
|
|
507
|
-
try log.cant(ident, .Paralysis);
|
|
549
|
+
resetCant(volatiles);
|
|
550
|
+
try log.cant(.{ ident, .Paralysis });
|
|
508
551
|
return false;
|
|
509
552
|
}
|
|
510
553
|
|
|
511
554
|
return true;
|
|
512
555
|
}
|
|
513
556
|
|
|
514
|
-
fn
|
|
557
|
+
fn resetCant(volatiles: *align(8) Volatiles) void {
|
|
515
558
|
volatiles.Bide = false;
|
|
516
559
|
volatiles.Thrashing = false;
|
|
517
560
|
volatiles.Charging = false;
|
|
@@ -522,81 +565,92 @@ fn cantMove(volatiles: *align(8) Volatiles) void {
|
|
|
522
565
|
volatiles.fury_cutter = 0;
|
|
523
566
|
}
|
|
524
567
|
|
|
525
|
-
fn canMove(battle: anytype, player: Player, options: anytype) !
|
|
568
|
+
pub fn canMove(battle: anytype, player: Player, state: *State, options: anytype) !bool {
|
|
569
|
+
var side = battle.side(player);
|
|
570
|
+
const volatiles = &side.active.volatiles;
|
|
571
|
+
|
|
572
|
+
// FIXME: pass in target to avoid lookup?
|
|
573
|
+
const target = if (Move.get(state.move).target == .Self) player else player.foe();
|
|
574
|
+
try options.log.move(.{ battle.active(player), state.move, battle.active(target), state.from });
|
|
575
|
+
|
|
576
|
+
const charging = false; // TODO charging uses doUse
|
|
577
|
+
const skip_pp = charging or state.move == .Struggle or
|
|
578
|
+
(volatiles.BeatUp or volatiles.Thrashing or volatiles.Bide);
|
|
579
|
+
if (!skip_pp) _ = decrementPP(side, state.move, state.mslot); // TODO if no pp return
|
|
580
|
+
|
|
581
|
+
return true;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
pub fn canCharge(battle: anytype, player: Player, state: *State, options: anytype) !bool {
|
|
526
585
|
_ = battle;
|
|
527
586
|
_ = player;
|
|
587
|
+
_ = state;
|
|
528
588
|
_ = options;
|
|
529
|
-
|
|
589
|
+
// TODO skipsuncharge account for SolarBeam
|
|
590
|
+
return true;
|
|
530
591
|
}
|
|
531
592
|
|
|
532
|
-
fn decrementPP(side: *Side, mslot:
|
|
593
|
+
pub fn decrementPP(side: *Side, move: Move, mslot: u4) bool {
|
|
533
594
|
var active = &side.active;
|
|
534
595
|
const volatiles = &active.volatiles;
|
|
535
596
|
|
|
536
|
-
|
|
597
|
+
assert(move != .Struggle);
|
|
537
598
|
assert(!volatiles.Charging and !volatiles.BeatUp and !volatiles.Thrashing and !volatiles.Bide);
|
|
538
599
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
if (volatiles.Transform) return;
|
|
600
|
+
var move_slot = active.move(mslot);
|
|
601
|
+
if (move_slot.pp == 0) return false;
|
|
542
602
|
|
|
543
|
-
|
|
603
|
+
move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
|
|
604
|
+
if (volatiles.Transform) return true;
|
|
544
605
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
assert(active.move(mslot).pp == side.stored().move(mslot).pp);
|
|
548
|
-
}
|
|
606
|
+
move_slot = side.stored().move(mslot);
|
|
607
|
+
assert(move_slot.pp > 0);
|
|
549
608
|
|
|
550
|
-
|
|
551
|
-
fn (battle: anytype, player: Player, state: *State, options: anytype) anyerror!void;
|
|
609
|
+
if (move_slot.id == .Mimic and move != .Mimic) return true;
|
|
552
610
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
// // None
|
|
556
|
-
// (if (showdown) &[_]Command{
|
|
557
|
-
// // checkHit, checkCriticalHit, calcDamage, adjustDamage, randomizeDamage,
|
|
558
|
-
// // applyDamage, buildRage, kingsRock,
|
|
559
|
-
// } else &[_]Command{
|
|
560
|
-
// // checkCriticalHit, calcDamage, adjustDamage, randomizeDamage,
|
|
561
|
-
// // checkHit, applyDamage, buildRage, kingsRock,
|
|
562
|
-
// }),
|
|
563
|
-
// };
|
|
564
|
-
// zig fmt: on
|
|
611
|
+
move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
|
|
612
|
+
assert(active.move(mslot).pp == side.stored().move(mslot).pp);
|
|
565
613
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
614
|
+
return true;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
pub fn doMove(
|
|
618
|
+
comptime secondary: bool,
|
|
619
|
+
battle: anytype,
|
|
620
|
+
player: Player,
|
|
621
|
+
state: *State,
|
|
622
|
+
options: anytype,
|
|
623
|
+
) !bool {
|
|
573
624
|
try checkCriticalHit(battle, player, state, options);
|
|
574
625
|
try calcDamage(battle, player, state, options);
|
|
575
626
|
try adjustDamage(battle, player, state, options);
|
|
576
627
|
try randomizeDamage(battle, player, state, options);
|
|
628
|
+
try checkHit(battle, player, state, options);
|
|
629
|
+
if (secondary) try effectChance(battle, player, state, options);
|
|
577
630
|
try applyDamage(battle, player, state, options);
|
|
631
|
+
try reportOutcome(battle, player, state, options);
|
|
632
|
+
if (try destinyBond(battle, player, state, options)) return false;
|
|
578
633
|
try buildRage(battle, player, state, options);
|
|
579
634
|
try kingsRock(battle, player, state, options);
|
|
635
|
+
return true;
|
|
636
|
+
}
|
|
580
637
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
try
|
|
589
|
-
try
|
|
590
|
-
try
|
|
591
|
-
|
|
592
|
-
try Effects.protect(battle, player, state, options);
|
|
593
|
-
|
|
594
|
-
return null;
|
|
638
|
+
pub fn afterMove(
|
|
639
|
+
comptime kings: bool,
|
|
640
|
+
battle: anytype,
|
|
641
|
+
player: Player,
|
|
642
|
+
state: *State,
|
|
643
|
+
options: anytype,
|
|
644
|
+
) !bool {
|
|
645
|
+
if (try destinyBond(battle, player, state, options)) return false;
|
|
646
|
+
try buildRage(battle, player, state, options);
|
|
647
|
+
if (kings) try kingsRock(battle, player, state, options);
|
|
648
|
+
return true;
|
|
595
649
|
}
|
|
596
650
|
|
|
597
|
-
fn checkCriticalHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
651
|
+
pub fn checkCriticalHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
598
652
|
const move = Move.get(state.move);
|
|
599
|
-
if (
|
|
653
|
+
if (state.bp == 0) return;
|
|
600
654
|
|
|
601
655
|
const side = battle.side(player);
|
|
602
656
|
const pokemon = side.stored();
|
|
@@ -614,7 +668,7 @@ fn checkCriticalHit(battle: anytype, player: Player, state: *State, options: any
|
|
|
614
668
|
state.crit = try Rolls.criticalHit(battle, player, CRITICAL_HITS[stage], options);
|
|
615
669
|
}
|
|
616
670
|
|
|
617
|
-
fn calcDamage(
|
|
671
|
+
pub fn calcDamage(
|
|
618
672
|
battle: anytype,
|
|
619
673
|
player: Player,
|
|
620
674
|
state: *State,
|
|
@@ -628,37 +682,36 @@ fn calcDamage(
|
|
|
628
682
|
|
|
629
683
|
const FORESIGHT: Types = .{ .type1 = .Normal, .type2 = .Fighting };
|
|
630
684
|
|
|
631
|
-
fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
685
|
+
pub fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
632
686
|
if (state.move == .Struggle) return;
|
|
633
687
|
|
|
634
688
|
const side = battle.side(player);
|
|
635
689
|
const foe = battle.foe(player);
|
|
636
690
|
const types = foe.active.types;
|
|
637
|
-
const move = Move.get(state.move);
|
|
638
691
|
|
|
639
692
|
var d = state.damage;
|
|
640
|
-
if ((
|
|
641
|
-
(
|
|
693
|
+
if ((state.type == .Water and battle.field.weather == .Rain) or
|
|
694
|
+
(state.type == .Fire and battle.field.weather == .Sun))
|
|
642
695
|
{
|
|
643
696
|
d = d *% @intFromEnum(Effectiveness.Super) / 10;
|
|
644
|
-
} else if ((
|
|
645
|
-
((
|
|
697
|
+
} else if ((state.type == .Water and battle.field.weather == .Sun) or
|
|
698
|
+
((state.type == .Fire or state.move == .SolarBeam) and battle.field.weather == .Rain))
|
|
646
699
|
{
|
|
647
700
|
d = d *% @intFromEnum(Effectiveness.Resisted) / 10;
|
|
648
701
|
}
|
|
649
702
|
|
|
650
|
-
if (side.active.types.includes(
|
|
703
|
+
if (side.active.types.includes(state.type)) d +%= d / 2;
|
|
651
704
|
|
|
652
705
|
const neutral = @intFromEnum(Effectiveness.Neutral);
|
|
653
|
-
const foresight = foe.active.volatiles.Foresight and FORESIGHT.includes(
|
|
706
|
+
const foresight = foe.active.volatiles.Foresight and FORESIGHT.includes(state.type);
|
|
654
707
|
const eff1: u16 = if (foresight and types.type1 == .Ghost)
|
|
655
708
|
neutral
|
|
656
709
|
else
|
|
657
|
-
@intFromEnum(
|
|
710
|
+
@intFromEnum(state.type.effectiveness(types.type1));
|
|
658
711
|
const eff2: u16 = if (foresight and types.type2 == .Ghost)
|
|
659
712
|
neutral
|
|
660
713
|
else
|
|
661
|
-
@intFromEnum(
|
|
714
|
+
@intFromEnum(state.type.effectiveness(types.type2));
|
|
662
715
|
|
|
663
716
|
// Type effectiveness matchup precedence only matters with (NVE, SE)
|
|
664
717
|
if (!showdown and (eff1 + eff2) == Effectiveness.mismatch and
|
|
@@ -678,7 +731,7 @@ fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype) !voi
|
|
|
678
731
|
state.damage = d;
|
|
679
732
|
}
|
|
680
733
|
|
|
681
|
-
fn randomizeDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
734
|
+
pub fn randomizeDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
682
735
|
if (state.damage <= 1) return;
|
|
683
736
|
const random = try Rolls.damage(battle, player, options);
|
|
684
737
|
options.calc.base(player.foe(), state.damage);
|
|
@@ -686,12 +739,13 @@ fn randomizeDamage(battle: anytype, player: Player, state: *State, options: anyt
|
|
|
686
739
|
options.calc.final(player.foe(), state.damage);
|
|
687
740
|
}
|
|
688
741
|
|
|
689
|
-
fn applyDamage(
|
|
742
|
+
pub fn applyDamage(
|
|
690
743
|
battle: anytype,
|
|
691
744
|
player: Player,
|
|
692
745
|
state: *State,
|
|
693
746
|
options: anytype,
|
|
694
747
|
) !void {
|
|
748
|
+
// TODO failuretext (if not Bide) + applydamage
|
|
695
749
|
_ = battle;
|
|
696
750
|
_ = player;
|
|
697
751
|
_ = state;
|
|
@@ -699,7 +753,7 @@ fn applyDamage(
|
|
|
699
753
|
}
|
|
700
754
|
|
|
701
755
|
// TODO: set damage = 0 if missed and not (High) Jump Kick
|
|
702
|
-
fn checkHit(
|
|
756
|
+
pub fn checkHit(
|
|
703
757
|
battle: anytype,
|
|
704
758
|
player: Player,
|
|
705
759
|
state: *State,
|
|
@@ -724,7 +778,7 @@ fn checkHit(
|
|
|
724
778
|
return;
|
|
725
779
|
}
|
|
726
780
|
if (side.active.volatiles.LockOn) {
|
|
727
|
-
state.miss = !(foe.active.volatiles.Flying and move.extra.
|
|
781
|
+
state.miss = !(foe.active.volatiles.Flying and move.extra.underground);
|
|
728
782
|
return;
|
|
729
783
|
}
|
|
730
784
|
assert(!(foe.active.volatiles.Flying and foe.active.volatiles.Underground));
|
|
@@ -739,16 +793,23 @@ fn checkHit(
|
|
|
739
793
|
return;
|
|
740
794
|
}
|
|
741
795
|
}
|
|
742
|
-
if (move.effect == .Thunder
|
|
743
|
-
|
|
744
|
-
|
|
796
|
+
if (move.effect == .Thunder) {
|
|
797
|
+
if (battle.field.weather == .Rain) {
|
|
798
|
+
state.miss = false;
|
|
799
|
+
return;
|
|
800
|
+
} else if (battle.field.weather == .Sun) {
|
|
801
|
+
// This latter part gets applied in a separate Thunder accuracy handler
|
|
802
|
+
// on the cartridge but it makes more sense to just do it here
|
|
803
|
+
state.accuracy = Gen12.percent(50) + 1;
|
|
804
|
+
}
|
|
745
805
|
}
|
|
746
806
|
if (move.effect == .AlwaysHit) {
|
|
747
807
|
state.miss = false;
|
|
748
808
|
return;
|
|
749
809
|
}
|
|
750
810
|
|
|
751
|
-
var accuracy: u16 =
|
|
811
|
+
var accuracy: u16 = state.accuracy;
|
|
812
|
+
assert(accuracy > 0);
|
|
752
813
|
if (foe.active.boosts.evasion <= side.active.boosts.accuracy or
|
|
753
814
|
!foe.active.volatiles.Foresight)
|
|
754
815
|
{
|
|
@@ -773,24 +834,85 @@ fn checkHit(
|
|
|
773
834
|
fn checkFaint(
|
|
774
835
|
battle: anytype,
|
|
775
836
|
player: Player,
|
|
776
|
-
state: *State,
|
|
777
837
|
options: anytype,
|
|
778
838
|
) @TypeOf(options.log).Error!?Result {
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
839
|
+
const side = battle.side(player);
|
|
840
|
+
const foe = battle.foe(player);
|
|
841
|
+
|
|
842
|
+
const player_fainted = side.stored().hp == 0;
|
|
843
|
+
const foe_fainted = foe.stored().hp == 0;
|
|
844
|
+
if (!(player_fainted or foe_fainted)) return null;
|
|
845
|
+
|
|
846
|
+
const player_out = player_fainted and findFirstAlive(side) == 0;
|
|
847
|
+
const foe_out = foe_fainted and findFirstAlive(foe) == 0;
|
|
848
|
+
const more = player_out or foe_out;
|
|
849
|
+
|
|
850
|
+
if (player_fainted) try faint(battle, player, !(more or foe_fainted), options);
|
|
851
|
+
if (foe_fainted) try faint(battle, player.foe(), !more, options);
|
|
852
|
+
|
|
853
|
+
if (player_out and foe_out) {
|
|
854
|
+
try options.log.tie(.{});
|
|
855
|
+
return Result.Tie;
|
|
856
|
+
} else if (player_out) {
|
|
857
|
+
try options.log.win(.{player.foe()});
|
|
858
|
+
return if (player == .P1) Result.Lose else Result.Win;
|
|
859
|
+
} else if (foe_out) {
|
|
860
|
+
try options.log.win(.{player});
|
|
861
|
+
return if (player == .P1) Result.Win else Result.Lose;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
const foe_choice: Choice.Type = if (foe_fainted) .Switch else .Pass;
|
|
865
|
+
if (player == .P1) return .{ .p1 = .Switch, .p2 = foe_choice };
|
|
866
|
+
return .{ .p1 = foe_choice, .p2 = .Switch };
|
|
783
867
|
}
|
|
784
868
|
|
|
785
|
-
fn faint(battle: anytype, player: Player, done: bool, options: anytype)
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
869
|
+
fn faint(battle: anytype, player: Player, done: bool, options: anytype) !void {
|
|
870
|
+
var side = battle.side(player);
|
|
871
|
+
var foe = battle.foe(player);
|
|
872
|
+
assert(side.stored().hp == 0);
|
|
873
|
+
|
|
874
|
+
var foe_volatiles = &foe.active.volatiles;
|
|
875
|
+
foe_volatiles.BeatUp = false;
|
|
876
|
+
|
|
877
|
+
// We always need to clear status for Pokémon Showdown's Sleep/Freeze Clause Mod
|
|
878
|
+
const status = side.stored().status;
|
|
879
|
+
|
|
880
|
+
side.stored().status = 0;
|
|
881
|
+
// This shouldn't matter, but Pokémon Showdown decides double switching priority based on speed,
|
|
882
|
+
// and resets a Pokémon's stats when it faints... only it still factors in paralysis -_-
|
|
883
|
+
if (showdown) {
|
|
884
|
+
side.active.stats.spe = if (Status.is(status, .PAR))
|
|
885
|
+
@max(side.stored().stats.spe / 4, 1)
|
|
886
|
+
else
|
|
887
|
+
side.stored().stats.spe;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
try options.log.faint(.{ battle.active(player), done });
|
|
891
|
+
options.calc.capped(player);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// TODO: see if we can move *before* applyDamage and include failuretext logic
|
|
895
|
+
pub fn reportOutcome(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
896
|
+
const foe_ident = battle.active(player.foe());
|
|
897
|
+
if (state.crit) try options.log.crit(.{foe_ident});
|
|
898
|
+
if (!state.immune()) {
|
|
899
|
+
if (state.effectiveness > Effectiveness.neutral) {
|
|
900
|
+
try options.log.supereffective(.{foe_ident});
|
|
901
|
+
} else if (state.effectiveness < Effectiveness.neutral) {
|
|
902
|
+
try options.log.resisted(.{foe_ident});
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
pub fn effectChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
908
|
+
const rate = Move.get(state.move).chance;
|
|
909
|
+
assert(rate > 0);
|
|
910
|
+
// FIXME: only commit secondary chance roll if effect actually is successful (doesn't fail)
|
|
911
|
+
state.proc = !battle.foe(player).active.volatiles.Substitute and
|
|
912
|
+
try Rolls.secondaryChance(battle, player, rate, options);
|
|
791
913
|
}
|
|
792
914
|
|
|
793
|
-
fn handleResidual(battle: anytype, player: Player, options: anytype) !
|
|
915
|
+
fn handleResidual(battle: anytype, player: Player, options: anytype) !bool {
|
|
794
916
|
var side = battle.side(player);
|
|
795
917
|
var stored = side.stored();
|
|
796
918
|
const ident = battle.active(player);
|
|
@@ -810,9 +932,9 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
810
932
|
}
|
|
811
933
|
|
|
812
934
|
stored.hp -|= damage;
|
|
813
|
-
// Pokémon Showdown uses
|
|
814
|
-
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
815
|
-
if (stored.hp == 0) return;
|
|
935
|
+
// Pokémon Showdown uses damage [of] here but its not relevant in Generation II
|
|
936
|
+
try options.log.damage(.{ ident, stored, if (brn) Damage.Burn else Damage.Poison });
|
|
937
|
+
if (stored.hp == 0) return true;
|
|
816
938
|
}
|
|
817
939
|
|
|
818
940
|
if (volatiles.LeechSeed) {
|
|
@@ -827,35 +949,37 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
827
949
|
|
|
828
950
|
const damage = @max(stored.stats.hp / 8, 1);
|
|
829
951
|
stored.hp -|= damage;
|
|
830
|
-
// As above, Pokémon Showdown uses
|
|
831
|
-
try options.log.damage(ident, stored, .LeechSeed);
|
|
952
|
+
// As above, Pokémon Showdown uses damage [of] but its not relevant
|
|
953
|
+
try options.log.damage(.{ ident, stored, .LeechSeed });
|
|
832
954
|
|
|
833
955
|
const before = foe_stored.hp;
|
|
834
956
|
foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
|
|
835
957
|
// Pokémon Showdown uses the less specific heal here instead of drain... because reasons?
|
|
836
|
-
if (foe_stored.hp > before) try options.log.heal(foe_ident, foe_stored, .Silent);
|
|
837
|
-
if (stored.hp == 0) return;
|
|
958
|
+
if (foe_stored.hp > before) try options.log.heal(.{ foe_ident, foe_stored, .Silent });
|
|
959
|
+
if (stored.hp == 0) return true;
|
|
838
960
|
}
|
|
839
961
|
|
|
840
962
|
if (volatiles.Nightmare) {
|
|
841
963
|
stored.hp -= @max(stored.stats.hp / 4, 1);
|
|
842
964
|
// ibid
|
|
843
|
-
try options.log.damage(ident, stored, .Nightmare);
|
|
844
|
-
if (stored.hp == 0) return;
|
|
965
|
+
try options.log.damage(.{ ident, stored, .Nightmare });
|
|
966
|
+
if (stored.hp == 0) return true;
|
|
845
967
|
}
|
|
846
968
|
|
|
847
969
|
if (volatiles.Curse) {
|
|
848
970
|
stored.hp -= @max(stored.stats.hp / 4, 1);
|
|
849
971
|
// ibid
|
|
850
|
-
try options.log.damage(ident, stored, .Curse);
|
|
851
|
-
if (stored.hp == 0) return;
|
|
972
|
+
try options.log.damage(.{ ident, stored, .Curse });
|
|
973
|
+
if (stored.hp == 0) return true;
|
|
852
974
|
}
|
|
975
|
+
|
|
976
|
+
return false;
|
|
853
977
|
}
|
|
854
978
|
|
|
855
|
-
fn betweenTurns(battle: anytype, mslot: u4, options: anytype)
|
|
979
|
+
fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
856
980
|
const players = comptime std.enums.values(Player);
|
|
857
981
|
|
|
858
|
-
|
|
982
|
+
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
859
983
|
|
|
860
984
|
// Future Sight
|
|
861
985
|
inline for (players) |player| {
|
|
@@ -866,33 +990,36 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
866
990
|
future_sight.count -= 1;
|
|
867
991
|
assert(future_sight.count != 0);
|
|
868
992
|
if (future_sight.count == 1) {
|
|
869
|
-
try options.log.end(battle.active(player), .FutureSight);
|
|
993
|
+
try options.log.end(.{ battle.active(player), .FutureSight });
|
|
870
994
|
// TODO doMove
|
|
871
995
|
}
|
|
872
996
|
}
|
|
873
997
|
}
|
|
874
|
-
|
|
998
|
+
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
875
999
|
|
|
876
1000
|
// Weather
|
|
877
1001
|
if (battle.field.weather != .None) {
|
|
878
|
-
assert(battle.field.weather_duration > 0);
|
|
879
1002
|
battle.field.weather_duration -= 1;
|
|
880
1003
|
|
|
881
1004
|
if (battle.field.weather_duration == 0) {
|
|
882
1005
|
battle.field.weather = .None;
|
|
883
|
-
try options.log.weather(battle.field.weather, .None);
|
|
1006
|
+
try options.log.weather(.{ battle.field.weather, protocol.Weather.None });
|
|
884
1007
|
} else {
|
|
885
|
-
try options.log.weather(battle.field.weather, .Upkeep);
|
|
1008
|
+
try options.log.weather(.{ battle.field.weather, protocol.Weather.Upkeep });
|
|
886
1009
|
if (battle.field.weather == .Sandstorm) {
|
|
887
1010
|
inline for (players) |player| {
|
|
888
1011
|
var side = battle.side(player);
|
|
889
1012
|
const active = side.active;
|
|
890
1013
|
if (!(active.volatiles.Underground or active.types.sandstormImmune())) {
|
|
891
1014
|
side.stored().hp -|= @max(side.stored().stats.hp / 8, 1);
|
|
892
|
-
try options.log.damage(
|
|
1015
|
+
try options.log.damage(.{
|
|
1016
|
+
battle.active(player),
|
|
1017
|
+
side.stored(),
|
|
1018
|
+
.Sandstorm,
|
|
1019
|
+
});
|
|
893
1020
|
}
|
|
894
1021
|
}
|
|
895
|
-
|
|
1022
|
+
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
896
1023
|
}
|
|
897
1024
|
}
|
|
898
1025
|
}
|
|
@@ -901,23 +1028,28 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
901
1028
|
inline for (players) |player| {
|
|
902
1029
|
var side = battle.side(player);
|
|
903
1030
|
var volatiles = &side.active.volatiles;
|
|
1031
|
+
const ident = battle.active(player);
|
|
904
1032
|
|
|
905
1033
|
if (volatiles.bind.duration > 0 and !volatiles.Substitute) {
|
|
906
1034
|
volatiles.bind.duration -= 1;
|
|
907
1035
|
if (volatiles.bind.duration == 0) {
|
|
908
1036
|
const reason = @intFromEnum(End.Bind) + volatiles.bind.reason - 1;
|
|
909
|
-
try options.log.end(
|
|
1037
|
+
try options.log.end(.{ ident, @as(End, @enumFromInt(reason)) });
|
|
910
1038
|
} else {
|
|
911
1039
|
var reason = @intFromEnum(Activate.Bind) + volatiles.bind.reason - 1;
|
|
912
|
-
try options.log.activate(
|
|
1040
|
+
try options.log.activate(.{ ident, @as(Activate, @enumFromInt(reason)) });
|
|
913
1041
|
|
|
914
1042
|
reason = @intFromEnum(Damage.Bind) + volatiles.bind.reason - 1;
|
|
915
1043
|
side.stored().hp -|= @max(side.stored().stats.hp / 16, 1);
|
|
916
|
-
try options.log.damage(
|
|
1044
|
+
try options.log.damage(.{
|
|
1045
|
+
ident,
|
|
1046
|
+
side.stored(),
|
|
1047
|
+
@as(Damage, @enumFromInt(reason)),
|
|
1048
|
+
});
|
|
917
1049
|
}
|
|
918
1050
|
}
|
|
919
1051
|
}
|
|
920
|
-
|
|
1052
|
+
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
921
1053
|
|
|
922
1054
|
// Perish Song
|
|
923
1055
|
inline for (players) |player| {
|
|
@@ -925,12 +1057,11 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
925
1057
|
var volatiles = &side.active.volatiles;
|
|
926
1058
|
|
|
927
1059
|
if (volatiles.PerishSong) {
|
|
928
|
-
assert(volatiles.perish_song > 0);
|
|
929
1060
|
volatiles.perish_song -= 1;
|
|
930
1061
|
|
|
931
1062
|
assert(volatiles.perish_song < 3);
|
|
932
1063
|
const reason = @intFromEnum(Start.PerishSong0) + volatiles.perish_song;
|
|
933
|
-
try options.log.start(battle.active(player), @enumFromInt(reason));
|
|
1064
|
+
try options.log.start(.{ battle.active(player), @as(Start, @enumFromInt(reason)) });
|
|
934
1065
|
|
|
935
1066
|
if (volatiles.perish_song == 0) {
|
|
936
1067
|
volatiles.PerishSong = false;
|
|
@@ -938,7 +1069,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
938
1069
|
}
|
|
939
1070
|
}
|
|
940
1071
|
}
|
|
941
|
-
|
|
1072
|
+
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
942
1073
|
|
|
943
1074
|
// Leftovers
|
|
944
1075
|
inline for (players) |player| {
|
|
@@ -947,13 +1078,15 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
947
1078
|
if (stored.item == .Leftovers) {
|
|
948
1079
|
const before = stored.hp;
|
|
949
1080
|
stored.hp = @min(stored.hp + @max(stored.stats.hp / 16, 1), stored.stats.hp);
|
|
950
|
-
if (stored.hp > before)
|
|
1081
|
+
if (stored.hp > before) {
|
|
1082
|
+
try options.log.heal(.{ battle.active(player), stored, .Leftovers });
|
|
1083
|
+
}
|
|
951
1084
|
}
|
|
952
1085
|
}
|
|
953
1086
|
|
|
954
1087
|
// Mystery Berry
|
|
955
1088
|
inline for (players) |player| {
|
|
956
|
-
|
|
1089
|
+
const stored = battle.side(player).stored();
|
|
957
1090
|
|
|
958
1091
|
if (stored.item == .MysteryBerry) {
|
|
959
1092
|
// TODO stored and active moves, transform, etc
|
|
@@ -968,7 +1101,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
968
1101
|
!side.active.volatiles.frozen and
|
|
969
1102
|
try Rolls.defrost(battle, player, options))
|
|
970
1103
|
{
|
|
971
|
-
try options.log.curestatus(battle.active(player), side.stored().status, .Message);
|
|
1104
|
+
try options.log.curestatus(.{ battle.active(player), side.stored().status, .Message });
|
|
972
1105
|
side.stored().status = 0;
|
|
973
1106
|
}
|
|
974
1107
|
}
|
|
@@ -1009,14 +1142,14 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
1009
1142
|
stored.hp < stored.stats.hp;
|
|
1010
1143
|
if (proc) {
|
|
1011
1144
|
assert(stored.hp != 0);
|
|
1012
|
-
try options.log.enditem(ident, stored.item, .Eat);
|
|
1145
|
+
try options.log.enditem(.{ ident, stored.item, .Eat });
|
|
1013
1146
|
stored.item = .None;
|
|
1014
1147
|
|
|
1015
1148
|
const offset = num - @intFromEnum(Item.Berry);
|
|
1016
1149
|
assert(offset < 3);
|
|
1017
1150
|
stored.hp = @min(stored.hp + ((offset + 1) * 10), stored.stats.hp);
|
|
1018
1151
|
const reason: Heal = @enumFromInt(@intFromEnum(Heal.Berry) + offset);
|
|
1019
|
-
try options.log.heal(ident, stored, reason);
|
|
1152
|
+
try options.log.heal(.{ ident, stored, reason });
|
|
1020
1153
|
}
|
|
1021
1154
|
} else if (num < @intFromEnum(Item.BitterBerry)) {
|
|
1022
1155
|
const proc = if (stored.item == .MiracleBerry)
|
|
@@ -1026,29 +1159,29 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
1026
1159
|
|
|
1027
1160
|
if (proc) {
|
|
1028
1161
|
assert(Status.any(stored.status));
|
|
1029
|
-
try options.log.enditem(ident, stored.item, .Eat);
|
|
1162
|
+
try options.log.enditem(.{ ident, stored.item, .Eat });
|
|
1030
1163
|
stored.item = .None;
|
|
1031
1164
|
|
|
1032
1165
|
// FIXME wtf?
|
|
1033
1166
|
// side.active.volatiles.Toxic = false;
|
|
1034
|
-
// side.active.volatiles.
|
|
1167
|
+
// side.active.volatiles.Nightmare = false;
|
|
1035
1168
|
if (stored.item == .MiracleBerry) {
|
|
1036
1169
|
side.active.volatiles.Confusion = false;
|
|
1037
|
-
try options.log.end(ident, .Confusion);
|
|
1170
|
+
try options.log.end(.{ ident, .Confusion });
|
|
1038
1171
|
}
|
|
1039
1172
|
stored.status = 0;
|
|
1040
1173
|
|
|
1041
1174
|
// FIXME recalc stats
|
|
1042
1175
|
|
|
1043
|
-
try options.log.curestatus(ident, stored.status, .Message);
|
|
1176
|
+
try options.log.curestatus(.{ ident, stored.status, .Message });
|
|
1044
1177
|
}
|
|
1045
1178
|
} else if (num == @intFromEnum(Item.BitterBerry)) {
|
|
1046
1179
|
if (side.active.volatiles.Confusion) {
|
|
1047
|
-
try options.log.enditem(ident, stored.item, .Eat);
|
|
1180
|
+
try options.log.enditem(.{ ident, stored.item, .Eat });
|
|
1048
1181
|
stored.item = .None;
|
|
1049
1182
|
|
|
1050
1183
|
side.active.volatiles.Confusion = false;
|
|
1051
|
-
try options.log.end(ident, .Confusion);
|
|
1184
|
+
try options.log.end(.{ ident, .Confusion });
|
|
1052
1185
|
}
|
|
1053
1186
|
}
|
|
1054
1187
|
}
|
|
@@ -1060,18 +1193,19 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
1060
1193
|
var volatiles = &side.active.volatiles;
|
|
1061
1194
|
|
|
1062
1195
|
if (volatiles.Encore) {
|
|
1063
|
-
assert(volatiles.encore > 0);
|
|
1064
1196
|
volatiles.encore -= 1;
|
|
1065
1197
|
|
|
1066
1198
|
if (volatiles.encore == 0 or side.active.move(mslot).pp == 0) {
|
|
1067
1199
|
volatiles.Encore = false;
|
|
1068
|
-
try options.log.end(battle.active(player), .Encore);
|
|
1200
|
+
try options.log.end(.{ battle.active(player), .Encore });
|
|
1069
1201
|
}
|
|
1070
1202
|
}
|
|
1071
1203
|
}
|
|
1204
|
+
|
|
1205
|
+
return null;
|
|
1072
1206
|
}
|
|
1073
1207
|
|
|
1074
|
-
|
|
1208
|
+
fn updateSideCondition(
|
|
1075
1209
|
battle: anytype,
|
|
1076
1210
|
player: Player,
|
|
1077
1211
|
comptime key: []const u8,
|
|
@@ -1080,12 +1214,11 @@ inline fn updateSideCondition(
|
|
|
1080
1214
|
) !void {
|
|
1081
1215
|
var side = battle.side(player);
|
|
1082
1216
|
if (@field(side.conditions, key)) {
|
|
1083
|
-
assert(@field(side.conditions, value) > 0);
|
|
1084
1217
|
@field(side.conditions, value) -= 1;
|
|
1085
1218
|
|
|
1086
1219
|
if (@field(side.conditions, value) == 0) {
|
|
1087
1220
|
@field(side.conditions, key) = false;
|
|
1088
|
-
try options.log.sideend(player, @field(protocol.Side, key));
|
|
1221
|
+
try options.log.sideend(.{ player, @field(protocol.Side, key) });
|
|
1089
1222
|
}
|
|
1090
1223
|
}
|
|
1091
1224
|
}
|
|
@@ -1094,18 +1227,19 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1094
1227
|
battle.turn += 1;
|
|
1095
1228
|
|
|
1096
1229
|
if (showdown and battle.turn >= 1000) {
|
|
1097
|
-
try options.log.tie();
|
|
1230
|
+
try options.log.tie(.{});
|
|
1098
1231
|
return Result.Tie;
|
|
1099
1232
|
} else if (battle.turn >= 65535) {
|
|
1100
1233
|
return Result.Error;
|
|
1101
1234
|
}
|
|
1102
1235
|
|
|
1103
|
-
try options.log.turn(battle.turn);
|
|
1236
|
+
try options.log.turn(.{battle.turn});
|
|
1104
1237
|
|
|
1105
1238
|
return Result.Default;
|
|
1106
1239
|
}
|
|
1107
1240
|
|
|
1108
|
-
fn buildRage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1241
|
+
pub fn buildRage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1242
|
+
assert(!state.miss); // TODO
|
|
1109
1243
|
var foe = battle.foe(player);
|
|
1110
1244
|
if (foe.active.volatiles.Rage) {
|
|
1111
1245
|
if (showdown) {
|
|
@@ -1115,12 +1249,13 @@ fn buildRage(battle: anytype, player: Player, state: *State, options: anytype) !
|
|
|
1115
1249
|
}
|
|
1116
1250
|
} else {
|
|
1117
1251
|
foe.active.volatiles.rage +|= 1;
|
|
1118
|
-
try options.log.activate(battle.active(player), .Rage);
|
|
1252
|
+
try options.log.activate(.{ battle.active(player), .Rage });
|
|
1119
1253
|
}
|
|
1120
1254
|
}
|
|
1121
1255
|
}
|
|
1122
1256
|
|
|
1123
|
-
fn kingsRock(battle: anytype, player: Player,
|
|
1257
|
+
pub fn kingsRock(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1258
|
+
assert(!state.miss); // TODO
|
|
1124
1259
|
var foe = battle.foe(player);
|
|
1125
1260
|
const flinch = battle.side(player).stored().item == .KingsRock and
|
|
1126
1261
|
!foe.active.volatiles.Substitute and
|
|
@@ -1131,124 +1266,283 @@ fn kingsRock(battle: anytype, player: Player, _: *State, options: anytype) !void
|
|
|
1131
1266
|
}
|
|
1132
1267
|
}
|
|
1133
1268
|
|
|
1134
|
-
pub
|
|
1135
|
-
|
|
1136
|
-
|
|
1269
|
+
pub fn destinyBond(battle: anytype, player: Player, _: *State, options: anytype) !bool {
|
|
1270
|
+
const foe = battle.foe(player);
|
|
1271
|
+
if (foe.stored().hp > 0) return false;
|
|
1272
|
+
|
|
1273
|
+
if (foe.active.volatiles.DestinyBond) {
|
|
1274
|
+
battle.side(player).stored().hp = 0;
|
|
1275
|
+
try options.log.activate(.{ battle.active(player.foe()), .DestinyBond });
|
|
1137
1276
|
}
|
|
1277
|
+
return true;
|
|
1278
|
+
}
|
|
1138
1279
|
|
|
1139
|
-
|
|
1280
|
+
pub const Effects = struct {
|
|
1281
|
+
pub fn attract(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1140
1282
|
const side = battle.side(player);
|
|
1141
1283
|
var foe = battle.foe(player);
|
|
1142
1284
|
|
|
1143
1285
|
const gender: u8 = @intFromEnum(side.stored().dvs.gender);
|
|
1144
1286
|
const foe_gender: u8 = @intFromEnum(foe.stored().dvs.gender);
|
|
1145
1287
|
if (gender + foe_gender != 1) {
|
|
1146
|
-
|
|
1147
|
-
return;
|
|
1288
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
1148
1289
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1149
|
-
try options.log.lastmiss();
|
|
1150
|
-
|
|
1151
|
-
return;
|
|
1290
|
+
try options.log.lastmiss(.{});
|
|
1291
|
+
return options.log.miss(.{battle.active(player)});
|
|
1152
1292
|
}
|
|
1153
1293
|
|
|
1154
1294
|
foe.active.volatiles.Attract = true;
|
|
1155
|
-
try options.log.start(battle.active(player.foe()), .Attract);
|
|
1295
|
+
try options.log.start(.{ battle.active(player.foe()), .Attract });
|
|
1156
1296
|
}
|
|
1157
1297
|
|
|
1158
|
-
fn batonPass(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1298
|
+
pub fn batonPass(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1159
1299
|
_ = .{ battle, player, state, options }; // TODO
|
|
1160
1300
|
}
|
|
1161
1301
|
|
|
1162
|
-
fn beatUp(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1302
|
+
pub fn beatUp(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1163
1303
|
_ = .{ battle, player, state, options }; // TODO
|
|
1164
1304
|
}
|
|
1165
1305
|
|
|
1166
|
-
fn bellyDrum(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1167
|
-
|
|
1306
|
+
pub fn bellyDrum(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1307
|
+
assert(!state.miss);
|
|
1308
|
+
var side = battle.side(player);
|
|
1309
|
+
|
|
1310
|
+
const ident = battle.active(player);
|
|
1311
|
+
|
|
1312
|
+
assert(side.active.boosts.atk >= -6 and side.active.boosts.atk <= 6);
|
|
1313
|
+
if (side.active.boosts.atk == 6 or side.active.boosts.atk == MAX_STAT_VALUE) {
|
|
1314
|
+
return options.log.fail(.{ ident, .None });
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
const hp = @max(side.stored().stats.hp / 2, 1);
|
|
1318
|
+
if (side.stored().hp <= hp) {
|
|
1319
|
+
try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
|
|
1320
|
+
try options.log.boost(.{ ident, .Attack, @as(i8, 2), .Silent });
|
|
1321
|
+
return options.log.fail(.{ ident, .None });
|
|
1322
|
+
} else {
|
|
1323
|
+
side.stored().hp -= hp;
|
|
1324
|
+
try options.log.damage(.{ ident, side.stored(), .None });
|
|
1325
|
+
|
|
1326
|
+
const before = side.active.boosts.atk;
|
|
1327
|
+
var i: usize = 0;
|
|
1328
|
+
while (i < 6 and side.active.stats.atk < MAX_STAT_VALUE) : (i += 1) {
|
|
1329
|
+
try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
|
|
1330
|
+
}
|
|
1331
|
+
try options.log.boost(.{ ident, .Attack, @as(i8, side.active.boosts.atk - before) });
|
|
1332
|
+
}
|
|
1168
1333
|
}
|
|
1169
1334
|
|
|
1170
|
-
|
|
1171
|
-
|
|
1335
|
+
pub const bide = struct {
|
|
1336
|
+
pub fn store(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1337
|
+
var side = battle.side(player);
|
|
1338
|
+
var volatiles = &side.active.volatiles;
|
|
1339
|
+
|
|
1340
|
+
const ident = battle.active(player);
|
|
1341
|
+
|
|
1342
|
+
if (!volatiles.Bide) return;
|
|
1343
|
+
|
|
1344
|
+
// if (options.calc.modify(player, .attacking)) |extend| {
|
|
1345
|
+
// if (!extend) volatiles.attacks = 0;
|
|
1346
|
+
// } else {
|
|
1347
|
+
volatiles.attacks -= 1;
|
|
1348
|
+
// }
|
|
1349
|
+
try options.chance.attacking(player, volatiles.attacks);
|
|
1350
|
+
|
|
1351
|
+
if (volatiles.attacks != 0) return options.log.activate(.{ ident, .Bide });
|
|
1352
|
+
|
|
1353
|
+
volatiles.Bide = false;
|
|
1354
|
+
try options.log.end(.{ ident, .Bide });
|
|
1355
|
+
|
|
1356
|
+
state.damage = volatiles.bide *% 2;
|
|
1357
|
+
volatiles.bide = 0;
|
|
1358
|
+
|
|
1359
|
+
if (state.damage == 0) state.miss = true;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
pub fn unleash(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1363
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1364
|
+
}
|
|
1365
|
+
};
|
|
1366
|
+
|
|
1367
|
+
pub fn binding(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1368
|
+
var foe = battle.foe(player);
|
|
1369
|
+
|
|
1370
|
+
if (state.miss) return;
|
|
1371
|
+
if (foe.active.volatiles.bind.duration > 0) return;
|
|
1372
|
+
if (foe.active.volatiles.Substitute) return;
|
|
1373
|
+
|
|
1374
|
+
foe.active.volatiles.bind = .{
|
|
1375
|
+
.duration = Rolls.bindingDuration(battle, player, options),
|
|
1376
|
+
.reason = Move.get(state.move).extra.protocol,
|
|
1377
|
+
};
|
|
1172
1378
|
}
|
|
1173
1379
|
|
|
1174
|
-
fn
|
|
1175
|
-
|
|
1380
|
+
pub fn burnChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1381
|
+
var foe = battle.foe(player);
|
|
1382
|
+
var foe_stored = foe.stored();
|
|
1383
|
+
|
|
1384
|
+
if (foe.active.volatiles.Substitute) return;
|
|
1385
|
+
if (Status.any(foe_stored.status)) {
|
|
1386
|
+
if (Status.is(foe_stored.status, .FRZ)) {
|
|
1387
|
+
assert(Move.get(state.move).type == .Fire or state.move == .TriAttack);
|
|
1388
|
+
try options.log.curestatus(.{
|
|
1389
|
+
battle.active(player.foe()),
|
|
1390
|
+
foe_stored.status,
|
|
1391
|
+
.Message,
|
|
1392
|
+
});
|
|
1393
|
+
foe_stored.status = 0;
|
|
1394
|
+
}
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
if (!state.proc) return;
|
|
1398
|
+
if (foe.conditions.Safeguard) return;
|
|
1399
|
+
// Nothing is immune to Fire-type, but Ghosts are immune to burnChance called by Tri Attack
|
|
1400
|
+
if (state.immune()) return;
|
|
1401
|
+
if (foe.active.types.includes(Move.get(state.move).type) and state.move != .TriAttack) {
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
foe_stored.status = Status.init(.BRN);
|
|
1406
|
+
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
1407
|
+
|
|
1408
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1409
|
+
|
|
1410
|
+
// TODO: check for status berry
|
|
1176
1411
|
}
|
|
1177
1412
|
|
|
1178
|
-
fn
|
|
1413
|
+
pub fn confusion(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1179
1414
|
_ = .{ battle, player, state, options }; // TODO
|
|
1180
1415
|
}
|
|
1181
1416
|
|
|
1182
|
-
fn
|
|
1417
|
+
pub fn confusionChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1183
1418
|
_ = .{ battle, player, state, options }; // TODO
|
|
1184
1419
|
}
|
|
1185
1420
|
|
|
1186
|
-
fn conversion(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1421
|
+
pub fn conversion(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1187
1422
|
var side = battle.side(player);
|
|
1188
1423
|
|
|
1189
1424
|
var n: u3 = 0;
|
|
1190
1425
|
for (side.active.moves) |m| {
|
|
1191
1426
|
if (convertible(side.active, m.id)) n += 1;
|
|
1192
1427
|
}
|
|
1193
|
-
if (n == 0) {
|
|
1194
|
-
try options.log.fail(battle.active(player), .None);
|
|
1195
|
-
return;
|
|
1196
|
-
}
|
|
1428
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
1197
1429
|
|
|
1198
1430
|
side.active.types.type1 = try Rolls.conversion(battle, player, n, options);
|
|
1199
1431
|
side.active.types.type2 = side.active.types.type1;
|
|
1200
1432
|
}
|
|
1201
1433
|
|
|
1202
|
-
fn conversion2(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1434
|
+
pub fn conversion2(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1203
1435
|
var side = battle.side(player);
|
|
1204
1436
|
|
|
1205
1437
|
const last = battle.foe(player).lastMove(false);
|
|
1206
|
-
if (last == .None) {
|
|
1207
|
-
try options.log.fail(battle.active(player), .None);
|
|
1208
|
-
return;
|
|
1209
|
-
}
|
|
1438
|
+
if (last == .None) return options.log.fail(.{ battle.active(player), .None });
|
|
1210
1439
|
const move = Move.get(last);
|
|
1211
|
-
if (move.type == .@"???") {
|
|
1212
|
-
try options.log.fail(battle.active(player), .None);
|
|
1213
|
-
return;
|
|
1214
|
-
}
|
|
1440
|
+
if (move.type == .@"???") return options.log.fail(.{ battle.active(player), .None });
|
|
1215
1441
|
|
|
1216
1442
|
side.active.types.type1 = try Rolls.conversion2(battle, player, move.type, options);
|
|
1217
1443
|
side.active.types.type2 = side.active.types.type1;
|
|
1218
1444
|
}
|
|
1219
1445
|
|
|
1220
|
-
fn counter(battle: anytype, player: Player, state: *State,
|
|
1221
|
-
|
|
1446
|
+
pub fn counter(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1447
|
+
assert(state.move == .Counter or state.move == .MirrorCoat);
|
|
1448
|
+
|
|
1449
|
+
const foe = battle.foe(player);
|
|
1450
|
+
|
|
1451
|
+
state.miss = true;
|
|
1452
|
+
const last = foe.lastMove(false);
|
|
1453
|
+
|
|
1454
|
+
if (last == .None) return;
|
|
1455
|
+
if (last == state.move) return;
|
|
1456
|
+
|
|
1457
|
+
const immune = state.immune();
|
|
1458
|
+
state.effectiveness = Effectiveness.neutral;
|
|
1459
|
+
if (immune) {
|
|
1460
|
+
state.damage = 0;
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
const move = Move.get(last);
|
|
1465
|
+
if (state.bp == 0) return;
|
|
1466
|
+
if (move.type.special() != (last == .MirrorCoat)) return;
|
|
1467
|
+
if (state.damage == 0) return;
|
|
1468
|
+
|
|
1469
|
+
state.damage *|= 2;
|
|
1470
|
+
state.miss = false;
|
|
1222
1471
|
}
|
|
1223
1472
|
|
|
1224
|
-
fn curse(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1225
|
-
|
|
1473
|
+
pub fn curse(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1474
|
+
var side = battle.side(player);
|
|
1475
|
+
var foe = battle.foe(player);
|
|
1476
|
+
|
|
1477
|
+
const ident = battle.active(player);
|
|
1478
|
+
const foe_ident = battle.active(player.foe());
|
|
1479
|
+
|
|
1480
|
+
if (side.active.types.includes(.Ghost)) {
|
|
1481
|
+
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1482
|
+
try options.log.lastmiss(.{});
|
|
1483
|
+
return options.log.miss(.{battle.active(player)});
|
|
1484
|
+
} else if (foe.active.volatiles.Substitute) {
|
|
1485
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1486
|
+
} else if (foe.active.volatiles.Curse) {
|
|
1487
|
+
return options.log.fail(.{ foe_ident, .None });
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
foe.active.volatiles.Curse = true;
|
|
1491
|
+
try options.log.start(.{ foe_ident, .Curse });
|
|
1492
|
+
|
|
1493
|
+
side.stored().hp -= @max(side.stored().stats.hp / 2, 1);
|
|
1494
|
+
try options.log.damage(.{ ident, side.stored(), .Curse });
|
|
1495
|
+
} else {
|
|
1496
|
+
const stats = &side.active.stats;
|
|
1497
|
+
const boosts = &side.active.boosts;
|
|
1498
|
+
|
|
1499
|
+
const max_atk = boosts.atk == 6 or stats.atk == MAX_STAT_VALUE;
|
|
1500
|
+
const max_def = boosts.def == 6 or stats.def == MAX_STAT_VALUE;
|
|
1501
|
+
if (max_atk and max_def) return options.log.fail(.{ ident, .None });
|
|
1502
|
+
|
|
1503
|
+
if (!(boosts.spe == -6 or stats.spe == 1)) {
|
|
1504
|
+
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
1505
|
+
try unboostStat(battle, side, stats, boosts, "spe", 1);
|
|
1506
|
+
try options.log.boost(.{ ident, .Speed, @as(i8, -1) });
|
|
1507
|
+
}
|
|
1508
|
+
if (!max_atk) {
|
|
1509
|
+
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
1510
|
+
try boostStat(battle, side, stats, boosts, "atk", 1);
|
|
1511
|
+
try options.log.boost(.{ ident, .Attack, @as(i8, 1) });
|
|
1512
|
+
}
|
|
1513
|
+
if (!max_def) {
|
|
1514
|
+
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
1515
|
+
try boostStat(battle, side, stats, boosts, "def", 1);
|
|
1516
|
+
try options.log.boost(.{ ident, .Defense, @as(i8, 1) });
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1226
1519
|
}
|
|
1227
1520
|
|
|
1228
|
-
fn defenseCurl(battle: anytype, player: Player,
|
|
1229
|
-
|
|
1521
|
+
pub fn defenseCurl(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
1522
|
+
battle.side(player).active.volatiles.DefenseCurl = true;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
pub fn defrost(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1526
|
+
var side = battle.side(player);
|
|
1527
|
+
assert(Status.is(side.stored().status, .FRZ));
|
|
1528
|
+
try options.log.curestatus(.{ battle.active(player), side.stored().status, .Message });
|
|
1529
|
+
side.stored().status = 0;
|
|
1230
1530
|
}
|
|
1231
1531
|
|
|
1232
|
-
fn destinyBond(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1532
|
+
pub fn destinyBond(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1233
1533
|
battle.side(player).active.volatiles.DestinyBond = true;
|
|
1234
|
-
try options.log.singlemove(battle.active(player), Move.DestinyBond);
|
|
1534
|
+
try options.log.singlemove(.{ battle.active(player), Move.DestinyBond });
|
|
1235
1535
|
}
|
|
1236
1536
|
|
|
1237
|
-
fn disable(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1537
|
+
pub fn disable(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1238
1538
|
var foe = battle.foe(player);
|
|
1239
1539
|
var volatiles = &foe.active.volatiles;
|
|
1240
1540
|
const foe_ident = battle.active(player.foe());
|
|
1241
1541
|
|
|
1242
|
-
if (volatiles.disable.move != 0) {
|
|
1243
|
-
try options.log.fail(foe_ident, .None);
|
|
1244
|
-
return;
|
|
1245
|
-
}
|
|
1542
|
+
if (volatiles.disable.move != 0) return options.log.fail(.{ foe_ident, .None });
|
|
1246
1543
|
|
|
1247
1544
|
const last = battle.foe(player).lastMove(false);
|
|
1248
|
-
if (last == .None or last == .Struggle) {
|
|
1249
|
-
try options.log.fail(foe_ident, .None);
|
|
1250
|
-
return;
|
|
1251
|
-
}
|
|
1545
|
+
if (last == .None or last == .Struggle) return options.log.fail(.{ foe_ident, .None });
|
|
1252
1546
|
|
|
1253
1547
|
var slot: u3 = 0;
|
|
1254
1548
|
for (foe.active.moves) |m| {
|
|
@@ -1257,26 +1551,41 @@ pub const Effects = struct {
|
|
|
1257
1551
|
if (m.id == last) break;
|
|
1258
1552
|
}
|
|
1259
1553
|
const move = foe.active.move(slot);
|
|
1260
|
-
if (move.pp == 0) {
|
|
1261
|
-
try options.log.fail(foe_ident, .None);
|
|
1262
|
-
return;
|
|
1263
|
-
}
|
|
1554
|
+
if (move.pp == 0) return options.log.fail(.{ foe_ident, .None });
|
|
1264
1555
|
|
|
1265
1556
|
volatiles.disable.move = slot;
|
|
1266
1557
|
volatiles.disable.duration = Rolls.disableDuration(battle, player, options);
|
|
1267
1558
|
|
|
1268
|
-
try options.log.
|
|
1559
|
+
try options.log.start(.{ foe_ident, .Disable, move.id });
|
|
1269
1560
|
}
|
|
1270
1561
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1562
|
+
pub const double = struct {
|
|
1563
|
+
pub fn flying(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1564
|
+
if (battle.foe(player).active.volatiles.Flying) state.damage *|= 2;
|
|
1565
|
+
}
|
|
1274
1566
|
|
|
1275
|
-
|
|
1276
|
-
|
|
1567
|
+
pub fn underground(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1568
|
+
if (battle.foe(player).active.volatiles.Underground) state.damage *|= 2;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
pub fn minimize(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1572
|
+
if (battle.foe(player).active.volatiles.minimized) state.damage *|= 2;
|
|
1573
|
+
}
|
|
1574
|
+
};
|
|
1575
|
+
|
|
1576
|
+
pub fn drainHP(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1577
|
+
var stored = battle.side(player).stored();
|
|
1578
|
+
const ident = battle.active(player);
|
|
1579
|
+
|
|
1580
|
+
assert(state.damage != 0);
|
|
1581
|
+
|
|
1582
|
+
if (stored.hp == stored.stats.hp) return;
|
|
1583
|
+
stored.hp = @min(stored.stats.hp, stored.hp + @max(state.damage / 2, 1));
|
|
1584
|
+
|
|
1585
|
+
try options.log.heal(.{ ident, stored, .Drain, battle.active(player.foe()) });
|
|
1277
1586
|
}
|
|
1278
1587
|
|
|
1279
|
-
fn encore(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1588
|
+
pub fn encore(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1280
1589
|
var foe = battle.foe(player);
|
|
1281
1590
|
var volatiles = &foe.active.volatiles;
|
|
1282
1591
|
const foe_ident = battle.active(player.foe());
|
|
@@ -1285,10 +1594,7 @@ pub const Effects = struct {
|
|
|
1285
1594
|
const failed = volatiles.Encore or
|
|
1286
1595
|
(last == .None or last == .Struggle or
|
|
1287
1596
|
last == .Encore or .last == .MirrorMove);
|
|
1288
|
-
if (failed) {
|
|
1289
|
-
try options.log.fail(foe_ident, .None);
|
|
1290
|
-
return;
|
|
1291
|
-
}
|
|
1597
|
+
if (failed) return options.log.fail(.{ foe_ident, .None });
|
|
1292
1598
|
|
|
1293
1599
|
var slot: u3 = 0;
|
|
1294
1600
|
for (foe.active.moves) |m| {
|
|
@@ -1297,10 +1603,7 @@ pub const Effects = struct {
|
|
|
1297
1603
|
if (m.id == last) break;
|
|
1298
1604
|
}
|
|
1299
1605
|
const move = foe.active.move(slot);
|
|
1300
|
-
if (move.pp == 0) {
|
|
1301
|
-
try options.log.fail(foe_ident, .None);
|
|
1302
|
-
return;
|
|
1303
|
-
}
|
|
1606
|
+
if (move.pp == 0) return options.log.fail(.{ foe_ident, .None });
|
|
1304
1607
|
|
|
1305
1608
|
volatiles.Encore = true;
|
|
1306
1609
|
volatiles.encore = Rolls.encoreDuration(battle, player, options);
|
|
@@ -1308,53 +1611,84 @@ pub const Effects = struct {
|
|
|
1308
1611
|
// TODO ???
|
|
1309
1612
|
_ = state;
|
|
1310
1613
|
|
|
1311
|
-
try options.log.
|
|
1614
|
+
try options.log.start(.{ foe_ident, .Encore, move.id });
|
|
1312
1615
|
}
|
|
1313
1616
|
|
|
1314
|
-
fn explode(battle: anytype, player: Player,
|
|
1315
|
-
|
|
1617
|
+
pub fn explode(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
1618
|
+
var side = battle.side(player);
|
|
1619
|
+
var stored = side.stored();
|
|
1620
|
+
|
|
1621
|
+
stored.hp = 0;
|
|
1622
|
+
// Pokémon Showdown sets the status to 0 on faint(), and we need to do the same to be able
|
|
1623
|
+
// to correctly implement Pokémon Showdown's dumb speed-based switch mechanics
|
|
1624
|
+
if (!showdown) stored.status = 0;
|
|
1625
|
+
side.active.volatiles.LeechSeed = false;
|
|
1626
|
+
side.active.volatiles.DestinyBond = false;
|
|
1316
1627
|
}
|
|
1317
1628
|
|
|
1318
|
-
fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1629
|
+
pub fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1319
1630
|
const foe = battle.foe(player);
|
|
1320
1631
|
assert(foe.stored().hp > 0);
|
|
1321
1632
|
// FIXME crit?
|
|
1322
1633
|
if (state.damage > foe.stored().hp) state.damage = foe.stored().hp - 1;
|
|
1323
1634
|
}
|
|
1324
1635
|
|
|
1325
|
-
fn fixedDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1326
|
-
|
|
1327
|
-
|
|
1636
|
+
pub fn fixedDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1637
|
+
const side = battle.side(player);
|
|
1638
|
+
const foe = battle.foe(player);
|
|
1328
1639
|
|
|
1329
|
-
|
|
1330
|
-
|
|
1640
|
+
state.damage = switch (Move.get(state.move).effect) {
|
|
1641
|
+
.SuperFang => @max(foe.stored().hp / 2, 1),
|
|
1642
|
+
.LevelDamage => side.stored().level,
|
|
1643
|
+
.FixedDamage => if (state.move == .SonicBoom) 20 else 40,
|
|
1644
|
+
.Psywave => try Rolls.psywave(
|
|
1645
|
+
battle,
|
|
1646
|
+
player,
|
|
1647
|
+
@intCast(@as(u16, side.stored().level) * 3 / 2),
|
|
1648
|
+
options,
|
|
1649
|
+
),
|
|
1650
|
+
else => unreachable,
|
|
1651
|
+
};
|
|
1331
1652
|
}
|
|
1332
1653
|
|
|
1333
|
-
fn flinchChance(battle: anytype, player: Player, state: *State,
|
|
1334
|
-
|
|
1654
|
+
pub fn flinchChance(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1655
|
+
const foe = battle.foe(player);
|
|
1656
|
+
const status = foe.stored().status;
|
|
1657
|
+
var volatiles = &foe.active.volatiles;
|
|
1658
|
+
|
|
1659
|
+
if (volatiles.Substitute) return;
|
|
1660
|
+
if (Status.is(status, .FRZ) or Status.is(status, .SLP)) return;
|
|
1661
|
+
if (!state.first) return;
|
|
1662
|
+
if (!state.proc) return;
|
|
1663
|
+
|
|
1664
|
+
volatiles.Flinch = true;
|
|
1665
|
+
volatiles.Recharging = false;
|
|
1335
1666
|
}
|
|
1336
1667
|
|
|
1337
|
-
fn flyDig(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1668
|
+
pub fn flyDig(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1338
1669
|
_ = .{ battle, player, state, options }; // TODO
|
|
1339
1670
|
}
|
|
1340
1671
|
|
|
1341
|
-
fn focusEnergy(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1672
|
+
pub fn focusEnergy(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1342
1673
|
var side = battle.side(player);
|
|
1343
1674
|
|
|
1344
1675
|
if (side.active.volatiles.FocusEnergy) {
|
|
1345
|
-
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
1346
|
-
return;
|
|
1676
|
+
return if (!showdown) try options.log.fail(.{ battle.active(player), .None });
|
|
1347
1677
|
}
|
|
1348
1678
|
|
|
1349
1679
|
side.active.volatiles.FocusEnergy = true;
|
|
1350
|
-
try options.log.start(battle.active(player), .FocusEnergy);
|
|
1680
|
+
try options.log.start(.{ battle.active(player), .FocusEnergy });
|
|
1351
1681
|
}
|
|
1352
1682
|
|
|
1353
1683
|
var SLOTS: [5]u4 = .{ 0, 0, 0, 0, 0 };
|
|
1354
1684
|
|
|
1355
|
-
fn forceSwitch(battle: anytype, player: Player,
|
|
1685
|
+
pub fn forceSwitch(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1686
|
+
assert(!state.miss); // FIXME ensure checkHit handles fail message
|
|
1687
|
+
|
|
1356
1688
|
const foe = battle.foe(player);
|
|
1357
1689
|
|
|
1690
|
+
if (state.first) return options.log.fail(.{ battle.active(player), .None });
|
|
1691
|
+
|
|
1358
1692
|
var n: u4 = 0;
|
|
1359
1693
|
var i: u4 = 0;
|
|
1360
1694
|
for (foe.pokemon, 0..) |pokemon, j| {
|
|
@@ -1367,169 +1701,518 @@ pub const Effects = struct {
|
|
|
1367
1701
|
}
|
|
1368
1702
|
}
|
|
1369
1703
|
|
|
1370
|
-
if (n == 0) {
|
|
1371
|
-
try options.log.fail(battle.active(player), .None);
|
|
1372
|
-
return;
|
|
1373
|
-
}
|
|
1704
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
1374
1705
|
|
|
1375
|
-
|
|
1706
|
+
const slot = try Rolls.forceSwitch(battle, player, SLOTS[0..i], n, options);
|
|
1707
|
+
try switchIn(battle, player.foe(), slot, .Drag, options);
|
|
1376
1708
|
}
|
|
1377
1709
|
|
|
1378
|
-
fn foresight(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1710
|
+
pub fn foresight(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1379
1711
|
var foe = battle.foe(player);
|
|
1380
1712
|
|
|
1381
1713
|
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1382
|
-
try options.log.lastmiss();
|
|
1383
|
-
|
|
1384
|
-
return;
|
|
1714
|
+
try options.log.lastmiss(.{});
|
|
1715
|
+
return options.log.miss(.{battle.active(player)});
|
|
1385
1716
|
} else if (foe.active.volatiles.Foresight) {
|
|
1386
|
-
|
|
1387
|
-
return;
|
|
1717
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
1388
1718
|
}
|
|
1389
1719
|
|
|
1390
1720
|
foe.active.volatiles.Foresight = true;
|
|
1391
|
-
try options.log.start(battle.active(player.foe()), .FocusEnergy);
|
|
1721
|
+
try options.log.start(.{ battle.active(player.foe()), .FocusEnergy });
|
|
1392
1722
|
}
|
|
1393
1723
|
|
|
1394
|
-
fn freezeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1395
|
-
|
|
1724
|
+
pub fn freezeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1725
|
+
var foe = battle.foe(player);
|
|
1726
|
+
var foe_stored = foe.stored();
|
|
1727
|
+
const foe_ident = battle.active(player.foe());
|
|
1728
|
+
|
|
1729
|
+
if (foe.active.volatiles.Substitute) return;
|
|
1730
|
+
if (Status.any(foe_stored.status)) return;
|
|
1731
|
+
|
|
1732
|
+
// Nothing is immune to Ice-type, but Ghosts are immune to freezeChance called by Tri Attack
|
|
1733
|
+
if (state.immune()) return;
|
|
1734
|
+
if (battle.field.weather == .Sun) return;
|
|
1735
|
+
if (foe.active.types.includes(Move.get(state.move).type) and state.move != .TriAttack) {
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1738
|
+
if (!state.proc) return;
|
|
1739
|
+
if (foe.conditions.Safeguard) return;
|
|
1740
|
+
// Freeze Clause Mod
|
|
1741
|
+
if (showdown) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
|
|
1742
|
+
|
|
1743
|
+
foe_stored.status = Status.init(.FRZ);
|
|
1744
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
1745
|
+
|
|
1746
|
+
// TODO: check for status berry
|
|
1747
|
+
|
|
1748
|
+
foe.active.volatiles.Recharging = false;
|
|
1749
|
+
foe.active.volatiles.frozen = true;
|
|
1396
1750
|
}
|
|
1397
1751
|
|
|
1398
|
-
|
|
1399
|
-
|
|
1752
|
+
// TODO FIXME showdown calls this before calcDamage (after checkHit) and modifies state.bp!
|
|
1753
|
+
pub fn furyCutter(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1754
|
+
var side = battle.side(player);
|
|
1755
|
+
var volatiles = &side.active.volatiles;
|
|
1756
|
+
|
|
1757
|
+
if (state.miss) {
|
|
1758
|
+
volatiles.fury_cutter = 0;
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
assert(volatiles.fury_cutter >= 0 and volatiles.fury_cutter <= 5);
|
|
1763
|
+
if (volatiles.fury_cutter == 5) return;
|
|
1764
|
+
volatiles.fury_cutter += 1;
|
|
1765
|
+
|
|
1766
|
+
if (showdown) {
|
|
1767
|
+
state.bp *= 2;
|
|
1768
|
+
assert(state.bp <= 160);
|
|
1769
|
+
} else {
|
|
1770
|
+
state.damage *|= 2;
|
|
1771
|
+
}
|
|
1400
1772
|
}
|
|
1401
1773
|
|
|
1402
|
-
fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1774
|
+
pub fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1403
1775
|
_ = .{ battle, player, state, options }; // TODO
|
|
1404
1776
|
}
|
|
1405
1777
|
|
|
1406
|
-
fn happiness(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1778
|
+
pub fn happiness(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1407
1779
|
const val = battle.side(player).stored().happiness;
|
|
1408
1780
|
const frustration = Move.get(state.move).effect == .Frustration;
|
|
1409
1781
|
state.bp = ((if (frustration) 255 - val else val) * 10) / 25;
|
|
1410
1782
|
}
|
|
1411
1783
|
|
|
1412
|
-
fn haze(battle: anytype, player: Player,
|
|
1413
|
-
|
|
1414
|
-
|
|
1784
|
+
pub fn haze(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1785
|
+
var side = battle.side(player);
|
|
1786
|
+
var stats = unmodifiedStats(battle, side);
|
|
1787
|
+
side.active.boosts = .{};
|
|
1788
|
+
inline for (STATS) |s| @field(side.active.stats, s) = @field(stats, s);
|
|
1789
|
+
statusModify(side.stored().status, &side.active.stats);
|
|
1415
1790
|
|
|
1416
|
-
|
|
1417
|
-
|
|
1791
|
+
var foe = battle.foe(player);
|
|
1792
|
+
stats = unmodifiedStats(battle, foe);
|
|
1793
|
+
foe.active.boosts = .{};
|
|
1794
|
+
inline for (STATS) |s| @field(foe.active.stats, s) = @field(stats, s);
|
|
1795
|
+
statusModify(foe.stored().status, &foe.active.stats);
|
|
1796
|
+
|
|
1797
|
+
try options.log.clearallboost(.{});
|
|
1418
1798
|
}
|
|
1419
1799
|
|
|
1420
|
-
fn
|
|
1421
|
-
|
|
1422
|
-
|
|
1800
|
+
pub fn heal(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1801
|
+
var side = battle.side(player);
|
|
1802
|
+
var stored = side.stored();
|
|
1803
|
+
const ident = battle.active(player);
|
|
1423
1804
|
|
|
1424
|
-
|
|
1425
|
-
|
|
1805
|
+
if (stored.stats.hp == stored.hp) return options.log.fail(.{ ident, .None });
|
|
1806
|
+
|
|
1807
|
+
const rest = state.move == .Rest;
|
|
1808
|
+
if (rest) {
|
|
1809
|
+
// Adding the sleep status runs the sleep condition handler to roll duration
|
|
1810
|
+
if (showdown) battle.rng.advance(1);
|
|
1811
|
+
side.active.volatiles.Toxic = false;
|
|
1812
|
+
stored.status = Status.slf(2);
|
|
1813
|
+
recomputeStats(battle, side);
|
|
1814
|
+
try options.log.status(.{ ident, stored.status, .From, Move.Rest });
|
|
1815
|
+
stored.hp = stored.stats.hp;
|
|
1816
|
+
} else {
|
|
1817
|
+
stored.hp = @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2));
|
|
1818
|
+
}
|
|
1819
|
+
try options.log.heal(.{ ident, stored, if (rest) Heal.Silent else Heal.None });
|
|
1426
1820
|
}
|
|
1427
1821
|
|
|
1428
|
-
fn
|
|
1429
|
-
|
|
1822
|
+
pub fn healBell(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1823
|
+
var side = battle.side(player);
|
|
1824
|
+
side.active.volatiles.Nightmare = false;
|
|
1825
|
+
|
|
1826
|
+
try options.log.cureteam(.{battle.active(player)});
|
|
1827
|
+
for (&side.pokemon) |*p| p.status = 0;
|
|
1828
|
+
|
|
1829
|
+
// We technically only need to recompute stats if the active Pokémon was burned/paralyzed,
|
|
1830
|
+
// but adding a branch to check for that is probably more expensive than just recomputing
|
|
1831
|
+
recomputeStats(battle, side);
|
|
1430
1832
|
}
|
|
1431
1833
|
|
|
1432
|
-
fn
|
|
1433
|
-
|
|
1834
|
+
pub fn hiddenPower(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1835
|
+
const side = battle.side(player);
|
|
1836
|
+
state.bp = side.stored().dvs.power();
|
|
1837
|
+
state.type = side.stored().dvs.type;
|
|
1434
1838
|
}
|
|
1435
1839
|
|
|
1436
|
-
fn
|
|
1437
|
-
|
|
1840
|
+
pub fn hyperBeam(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1841
|
+
battle.side(player).active.volatiles.Recharging = true;
|
|
1842
|
+
try options.log.mustrecharge(.{battle.active(player)});
|
|
1438
1843
|
}
|
|
1439
1844
|
|
|
1440
|
-
fn
|
|
1441
|
-
|
|
1845
|
+
pub fn leechSeed(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1846
|
+
var foe = battle.foe(player);
|
|
1847
|
+
|
|
1848
|
+
if (showdown) {
|
|
1849
|
+
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
1850
|
+
const invulnerable = foe.active.volatiles.Underground or foe.active.volatiles.Flying;
|
|
1851
|
+
if (!invulnerable and foe.active.types.includes(.Grass)) {
|
|
1852
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
1853
|
+
}
|
|
1854
|
+
try checkHit(battle, player, state, options);
|
|
1855
|
+
if (state.miss) {
|
|
1856
|
+
try options.log.lastmiss(.{});
|
|
1857
|
+
return options.log.miss(.{battle.active(player)});
|
|
1858
|
+
}
|
|
1859
|
+
if (foe.active.volatiles.LeechSeed) return;
|
|
1860
|
+
} else {
|
|
1861
|
+
try checkHit(battle, player, state, options);
|
|
1862
|
+
const miss =
|
|
1863
|
+
state.miss or foe.active.types.includes(.Grass) or foe.active.volatiles.LeechSeed;
|
|
1864
|
+
if (miss) {
|
|
1865
|
+
try options.log.lastmiss(.{});
|
|
1866
|
+
return options.log.miss(.{battle.active(player)});
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
foe.active.volatiles.LeechSeed = true;
|
|
1871
|
+
|
|
1872
|
+
try options.log.start(.{ battle.active(player.foe()), .LeechSeed });
|
|
1442
1873
|
}
|
|
1443
1874
|
|
|
1444
|
-
fn lockOn(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1445
|
-
|
|
1875
|
+
pub fn lockOn(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1876
|
+
var foe = battle.foe(player);
|
|
1877
|
+
const foe_ident = battle.active(player.foe());
|
|
1878
|
+
|
|
1879
|
+
if (foe.active.volatiles.Substitute) {
|
|
1880
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1881
|
+
} else if (state.miss) {
|
|
1882
|
+
return options.log.fail(.{ foe_ident, .None });
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
foe.active.volatiles.LockOn = true;
|
|
1886
|
+
try options.log.activate(.{ battle.active(player), .LockOn });
|
|
1446
1887
|
}
|
|
1447
1888
|
|
|
1448
1889
|
const MAGNITUDE_POWER = [_]u8{ 10, 30, 50, 70, 90, 110, 150 };
|
|
1449
1890
|
|
|
1450
|
-
fn magnitude(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1451
|
-
|
|
1891
|
+
pub fn magnitude(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1892
|
+
const num = try Rolls.magnitude(battle, player, options);
|
|
1893
|
+
try options.log.activate(.{ battle.active(player), .Magnitude, num });
|
|
1894
|
+
state.bp = MAGNITUDE_POWER[num - 4];
|
|
1452
1895
|
}
|
|
1453
1896
|
|
|
1454
|
-
fn meanLook(battle: anytype, player: Player,
|
|
1455
|
-
|
|
1456
|
-
}
|
|
1897
|
+
pub fn meanLook(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1898
|
+
var foe = battle.foe(player);
|
|
1457
1899
|
|
|
1458
|
-
|
|
1459
|
-
|
|
1900
|
+
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1901
|
+
if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1902
|
+
try options.log.lastmiss(.{});
|
|
1903
|
+
return options.log.miss(.{battle.active(player)});
|
|
1904
|
+
} else if (foe.active.volatiles.Trapped) {
|
|
1905
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
foe.active.volatiles.Trapped = true;
|
|
1460
1909
|
}
|
|
1461
1910
|
|
|
1462
|
-
fn
|
|
1463
|
-
|
|
1911
|
+
pub fn metronome(
|
|
1912
|
+
battle: anytype,
|
|
1913
|
+
player: Player,
|
|
1914
|
+
state: *State,
|
|
1915
|
+
options: anytype,
|
|
1916
|
+
) anyerror!void {
|
|
1917
|
+
var side = battle.side(player);
|
|
1918
|
+
|
|
1919
|
+
side.last_used_move = .None;
|
|
1920
|
+
side.active.volatiles.dirty = false;
|
|
1921
|
+
|
|
1922
|
+
// if (wPlayerCharging) ???
|
|
1923
|
+
// wPlayerCharging = 1
|
|
1924
|
+
|
|
1925
|
+
// FIXME
|
|
1926
|
+
state.move = try Rolls.metronome(battle, player, options);
|
|
1927
|
+
state.from = .Metronome;
|
|
1928
|
+
return generated.runMove(battle, player, state, options);
|
|
1464
1929
|
}
|
|
1465
1930
|
|
|
1466
|
-
fn
|
|
1467
|
-
|
|
1931
|
+
pub fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1932
|
+
var side = battle.side(player);
|
|
1933
|
+
|
|
1934
|
+
if (state.miss) {
|
|
1935
|
+
if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1936
|
+
try options.log.lastmiss(.{});
|
|
1937
|
+
return options.log.miss(.{battle.active(player)});
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
const move = battle.foe(player).lastMove(false);
|
|
1941
|
+
if (move == .None or move == .Struggle) {
|
|
1942
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
var slot: u4 = 0;
|
|
1946
|
+
for (side.active.moves, 0..) |m, i| {
|
|
1947
|
+
if (m.id == move) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1948
|
+
if (m.id == .Mimic) slot = @intCast(i + 1);
|
|
1949
|
+
}
|
|
1950
|
+
assert(slot != 0 and slot == state.mslot);
|
|
1951
|
+
|
|
1952
|
+
side.active.move(slot).id = move;
|
|
1953
|
+
side.active.move(slot).pp = 5;
|
|
1954
|
+
try options.log.start(.{ battle.active(player), .Mimic, move });
|
|
1468
1955
|
}
|
|
1469
1956
|
|
|
1470
|
-
fn mirrorMove(
|
|
1471
|
-
|
|
1957
|
+
pub fn mirrorMove(
|
|
1958
|
+
battle: anytype,
|
|
1959
|
+
player: Player,
|
|
1960
|
+
state: *State,
|
|
1961
|
+
options: anytype,
|
|
1962
|
+
) anyerror!void {
|
|
1963
|
+
var side = battle.side(player);
|
|
1964
|
+
const foe = battle.foe(player);
|
|
1965
|
+
|
|
1966
|
+
side.last_used_move = .None;
|
|
1967
|
+
side.active.volatiles.dirty = false;
|
|
1968
|
+
|
|
1969
|
+
const last = foe.lastMove(false);
|
|
1970
|
+
|
|
1971
|
+
if (last == .None) return options.log.fail(.{ battle.active(player), .None });
|
|
1972
|
+
const knows = knows: for (side.active.moves) |m| {
|
|
1973
|
+
if (last == m.id) break :knows true;
|
|
1974
|
+
} else false;
|
|
1975
|
+
if (knows) return options.log.fail(.{ battle.active(player), .None });
|
|
1976
|
+
|
|
1977
|
+
// if (wPlayerCharging) ???
|
|
1978
|
+
// wPlayerCharging = 1
|
|
1979
|
+
|
|
1980
|
+
// FIXME
|
|
1981
|
+
state.move = last;
|
|
1982
|
+
state.from = .MirrorMove;
|
|
1983
|
+
return generated.runMove(battle, player, state, options);
|
|
1472
1984
|
}
|
|
1473
1985
|
|
|
1474
|
-
fn mist(battle: anytype, player: Player,
|
|
1475
|
-
|
|
1986
|
+
pub fn mist(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1987
|
+
var side = battle.side(player);
|
|
1988
|
+
|
|
1989
|
+
if (side.active.volatiles.Mist) return options.log.fail(.{ battle.active(player), .None });
|
|
1990
|
+
side.active.volatiles.Mist = true;
|
|
1991
|
+
|
|
1992
|
+
try options.log.start(.{ battle.active(player), .Mist });
|
|
1476
1993
|
}
|
|
1477
1994
|
|
|
1478
|
-
fn weatherHeal(battle: anytype, player: Player,
|
|
1479
|
-
|
|
1995
|
+
pub fn weatherHeal(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1996
|
+
var stored = battle.side(player).stored();
|
|
1997
|
+
const ident = battle.active(player);
|
|
1998
|
+
|
|
1999
|
+
if (stored.hp == stored.stats.hp) return options.log.fail(.{ ident, .None });
|
|
2000
|
+
|
|
2001
|
+
stored.hp = switch (battle.field.weather) {
|
|
2002
|
+
.Sun => stored.stats.hp,
|
|
2003
|
+
.None => @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2)),
|
|
2004
|
+
else => @min(stored.stats.hp, stored.hp + (stored.stats.hp / 4)),
|
|
2005
|
+
};
|
|
2006
|
+
|
|
2007
|
+
try options.log.heal(.{ ident, stored, .None });
|
|
1480
2008
|
}
|
|
1481
2009
|
|
|
1482
|
-
fn multiHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2010
|
+
pub fn multiHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1483
2011
|
_ = .{ battle, player, state, options }; // TODO
|
|
1484
2012
|
}
|
|
1485
2013
|
|
|
1486
|
-
fn nightmare(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1487
|
-
|
|
2014
|
+
pub fn nightmare(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2015
|
+
var foe = battle.foe(player);
|
|
2016
|
+
var foe_volatiles = &foe.active.volatiles;
|
|
2017
|
+
const foe_ident = battle.active(player.foe());
|
|
2018
|
+
|
|
2019
|
+
if (foe_volatiles.Flying or foe_volatiles.Underground) {
|
|
2020
|
+
if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2021
|
+
try options.log.lastmiss(.{});
|
|
2022
|
+
return options.log.miss(.{battle.active(player)});
|
|
2023
|
+
} else if (foe_volatiles.Substitute) {
|
|
2024
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
2025
|
+
} else if (!Status.is(foe.stored().status, .SLP) or foe_volatiles.Nightmare) {
|
|
2026
|
+
return options.log.fail(.{ foe_ident, .None });
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
foe_volatiles.Nightmare = true;
|
|
2030
|
+
try options.log.start(.{ battle.active(player.foe()), .Nightmare });
|
|
1488
2031
|
}
|
|
1489
2032
|
|
|
1490
|
-
|
|
1491
|
-
|
|
2033
|
+
pub const ohko = struct {
|
|
2034
|
+
// OHKO calls adjustDamage but only actually cares about immunity
|
|
2035
|
+
pub fn immune(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2036
|
+
const move = Move.get(state.move);
|
|
2037
|
+
|
|
2038
|
+
assert(move.type == .Normal or move.type == .Ground);
|
|
2039
|
+
const foe = battle.foe(player);
|
|
2040
|
+
const zero = if (move.type == .Normal)
|
|
2041
|
+
foe.active.types.includes(.Ghost) and !foe.active.volatiles.Foresight
|
|
2042
|
+
else
|
|
2043
|
+
foe.active.types.includes(.Flying);
|
|
2044
|
+
state.effectiveness = if (zero) 0 else Effectiveness.neutral;
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
pub fn damage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2048
|
+
const side = battle.side(player);
|
|
2049
|
+
const foe = battle.foe(player);
|
|
2050
|
+
|
|
2051
|
+
const foe_ident = battle.active(player.foe());
|
|
2052
|
+
|
|
2053
|
+
state.damage = 0;
|
|
2054
|
+
if (state.immune()) return options.log.immune(.{ foe_ident, .None });
|
|
2055
|
+
const delta = side.stored().level -| foe.stored().level;
|
|
2056
|
+
if (delta == 0) return options.log.immune(.{ foe_ident, .OHKO });
|
|
2057
|
+
|
|
2058
|
+
state.accuracy = 2 * delta +| Gen12.percent(30);
|
|
2059
|
+
try checkHit(battle, player, state, options);
|
|
2060
|
+
if (!state.miss) state.damage = 65535;
|
|
2061
|
+
state.ohko = true;
|
|
2062
|
+
}
|
|
2063
|
+
};
|
|
2064
|
+
|
|
2065
|
+
pub fn painSplit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2066
|
+
var side = battle.side(player);
|
|
2067
|
+
var foe = battle.foe(player);
|
|
2068
|
+
|
|
2069
|
+
const ident = battle.active(player);
|
|
2070
|
+
const foe_ident = battle.active(player.foe());
|
|
2071
|
+
|
|
2072
|
+
if (state.miss) return options.log.fail(.{ foe_ident, .None });
|
|
2073
|
+
if (foe.active.volatiles.Substitute) {
|
|
2074
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
const avg = @max((foe.stored().hp + side.stored().hp) / 2, 1);
|
|
2078
|
+
foe.stored().hp -= avg;
|
|
2079
|
+
side.stored().hp = avg;
|
|
2080
|
+
|
|
2081
|
+
try options.log.sethp(.{ foe_ident, foe.stored(), .Silent });
|
|
2082
|
+
try options.log.sethp(.{ ident, side.stored(), .None });
|
|
1492
2083
|
}
|
|
1493
2084
|
|
|
1494
|
-
fn
|
|
1495
|
-
|
|
2085
|
+
pub fn paralyze(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2086
|
+
var foe = battle.foe(player);
|
|
2087
|
+
const foe_ident = battle.active(player.foe());
|
|
2088
|
+
|
|
2089
|
+
if (Status.is(foe.stored().status, .PAR)) {
|
|
2090
|
+
return options.log.fail(.{ foe_ident, .Paralysis });
|
|
2091
|
+
}
|
|
2092
|
+
if (state.immune()) return options.log.immune(.{ foe_ident, .None });
|
|
2093
|
+
if (Status.any(foe.stored().status)) return options.log.fail(.{ foe_ident, .None });
|
|
2094
|
+
if (state.miss) {
|
|
2095
|
+
try options.log.lastmiss(.{});
|
|
2096
|
+
return options.log.miss(.{battle.active(player)});
|
|
2097
|
+
} else if (foe.active.volatiles.Substitute) {
|
|
2098
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
foe.stored().status = Status.init(.PAR);
|
|
2102
|
+
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2103
|
+
|
|
2104
|
+
try options.log.status(.{ foe_ident, foe.stored().status, .None });
|
|
1496
2105
|
}
|
|
1497
2106
|
|
|
1498
|
-
fn
|
|
1499
|
-
|
|
2107
|
+
pub fn paralyzeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2108
|
+
var foe = battle.foe(player);
|
|
2109
|
+
var foe_stored = foe.stored();
|
|
2110
|
+
|
|
2111
|
+
if (foe.active.volatiles.Substitute) return;
|
|
2112
|
+
if (Status.any(foe_stored.status)) return;
|
|
2113
|
+
if (state.immune()) return;
|
|
2114
|
+
if (!state.proc) return;
|
|
2115
|
+
if (foe.conditions.Safeguard) return;
|
|
2116
|
+
|
|
2117
|
+
foe_stored.status = Status.init(.PAR);
|
|
2118
|
+
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2119
|
+
|
|
2120
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
2121
|
+
|
|
2122
|
+
// TODO: check for status berry
|
|
1500
2123
|
}
|
|
1501
2124
|
|
|
1502
|
-
fn
|
|
1503
|
-
|
|
1504
|
-
|
|
2125
|
+
pub fn payDay(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2126
|
+
if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
|
|
2127
|
+
try options.log.fieldactivate(.{}); // FIXME .PayDay
|
|
2128
|
+
}
|
|
1505
2129
|
}
|
|
1506
2130
|
|
|
1507
|
-
fn
|
|
1508
|
-
|
|
2131
|
+
pub fn perishSong(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2132
|
+
var side = battle.side(player);
|
|
2133
|
+
var foe = battle.foe(player);
|
|
2134
|
+
|
|
2135
|
+
if (foe.active.volatiles.PerishSong) {
|
|
2136
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2137
|
+
} else if (!side.active.volatiles.PerishSong) {
|
|
2138
|
+
side.active.volatiles.PerishSong = true;
|
|
2139
|
+
side.active.volatiles.perish_song = 4;
|
|
2140
|
+
try options.log.start(.{ battle.active(player), .PerishSong3Silent });
|
|
2141
|
+
}
|
|
2142
|
+
foe.active.volatiles.PerishSong = true;
|
|
2143
|
+
foe.active.volatiles.perish_song = 4;
|
|
2144
|
+
try options.log.start(.{ battle.active(player.foe()), .PerishSong3Silent });
|
|
2145
|
+
|
|
2146
|
+
try options.log.fieldactivate(.{}); // FIXME .PerishSong
|
|
1509
2147
|
}
|
|
1510
2148
|
|
|
1511
|
-
fn
|
|
1512
|
-
|
|
2149
|
+
pub fn poison(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2150
|
+
var foe = battle.foe(player);
|
|
2151
|
+
var foe_stored = foe.stored();
|
|
2152
|
+
const foe_ident = battle.active(player.foe());
|
|
2153
|
+
|
|
2154
|
+
const toxic = state.move == .Toxic;
|
|
2155
|
+
if (state.immune() or foe.active.types.includes(.Poison)) {
|
|
2156
|
+
return options.log.immune(.{ foe_ident, .None });
|
|
2157
|
+
} else if (Status.any(foe_stored.status)) {
|
|
2158
|
+
// Pokémon Showdown considers Toxic to be a status even in Generation II and so
|
|
2159
|
+
// will not include a fail reason for Toxic vs. Poison or vice-versa...
|
|
2160
|
+
return options.log.fail(.{ foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2161
|
+
if (!showdown)
|
|
2162
|
+
Fail.Poison
|
|
2163
|
+
else if (toxic == (foe_stored.status == Status.TOX))
|
|
2164
|
+
if (toxic) Fail.Toxic else Fail.Poison
|
|
2165
|
+
else
|
|
2166
|
+
Fail.None
|
|
2167
|
+
else
|
|
2168
|
+
Fail.None });
|
|
2169
|
+
} else if (foe.active.volatiles.Substitute) {
|
|
2170
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
2171
|
+
} else if (state.miss) {
|
|
2172
|
+
try options.log.lastmiss(.{});
|
|
2173
|
+
return options.log.miss(.{battle.active(player)});
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
foe_stored.status = Status.init(.PSN);
|
|
2177
|
+
if (toxic) {
|
|
2178
|
+
if (showdown) foe_stored.status = Status.TOX;
|
|
2179
|
+
foe.active.volatiles.Toxic = true;
|
|
2180
|
+
foe.active.volatiles.toxic = 0;
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
2184
|
+
|
|
2185
|
+
// TODO: check for status berry
|
|
1513
2186
|
}
|
|
1514
2187
|
|
|
1515
|
-
fn
|
|
1516
|
-
|
|
2188
|
+
pub fn poisonChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2189
|
+
var foe = battle.foe(player);
|
|
2190
|
+
var foe_stored = foe.stored();
|
|
2191
|
+
|
|
2192
|
+
if (foe.active.volatiles.Substitute) return;
|
|
2193
|
+
if (Status.any(foe_stored.status)) return if (showdown) battle.rng.advance(1);
|
|
2194
|
+
if (state.immune() or foe.active.types.includes(.Poison)) return;
|
|
2195
|
+
if (!state.proc) return;
|
|
2196
|
+
if (foe.conditions.Safeguard) return;
|
|
2197
|
+
|
|
2198
|
+
foe_stored.status = Status.init(.PSN);
|
|
2199
|
+
|
|
2200
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
2201
|
+
|
|
2202
|
+
// TODO: check for status berry
|
|
1517
2203
|
}
|
|
1518
2204
|
|
|
1519
|
-
fn present(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2205
|
+
pub fn present(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1520
2206
|
_ = .{ battle, player, state, options }; // TODO
|
|
1521
2207
|
}
|
|
1522
2208
|
|
|
1523
|
-
fn protect(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2209
|
+
pub fn protect(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1524
2210
|
var side = battle.side(player);
|
|
1525
2211
|
var volatiles = &side.active.volatiles;
|
|
1526
2212
|
|
|
1527
2213
|
const failed = !state.first or volatiles.Substitute or
|
|
1528
2214
|
!try Rolls.protect(battle, player, volatiles.protect, options);
|
|
1529
|
-
if (failed) {
|
|
1530
|
-
try options.log.fail(battle.active(player), .None);
|
|
1531
|
-
return;
|
|
1532
|
-
}
|
|
2215
|
+
if (failed) return options.log.fail(.{ battle.active(player), .None });
|
|
1533
2216
|
|
|
1534
2217
|
volatiles.protect = @max(8, volatiles.protect + 1);
|
|
1535
2218
|
if (state.move == .Endure) {
|
|
@@ -1537,174 +2220,594 @@ pub const Effects = struct {
|
|
|
1537
2220
|
} else {
|
|
1538
2221
|
volatiles.Protect = true;
|
|
1539
2222
|
}
|
|
1540
|
-
try options.log.singleturn(battle.active(player), state.move);
|
|
2223
|
+
try options.log.singleturn(.{ battle.active(player), state.move });
|
|
1541
2224
|
}
|
|
1542
2225
|
|
|
1543
|
-
fn psychUp(battle: anytype, player: Player,
|
|
1544
|
-
|
|
1545
|
-
|
|
2226
|
+
pub fn psychUp(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2227
|
+
var side = battle.side(player);
|
|
2228
|
+
const foe = battle.foe(player);
|
|
1546
2229
|
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
2230
|
+
// Pokémon Showdown should fail when the enemy has no boosts/unboosts but doesn't
|
|
2231
|
+
if (!showdown and @as(u32, @bitCast(foe.active.boosts)) == 0) {
|
|
2232
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
2233
|
+
}
|
|
1550
2234
|
|
|
1551
|
-
|
|
1552
|
-
|
|
2235
|
+
side.active.boosts = foe.active.boosts;
|
|
2236
|
+
try options.log.copyboost(.{ battle.active(player), battle.active(player.foe()) });
|
|
1553
2237
|
}
|
|
1554
2238
|
|
|
1555
|
-
fn
|
|
1556
|
-
|
|
2239
|
+
pub fn pursuit(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2240
|
+
// TODO: remove switching variable and just look at Choice saved in Battle struct?
|
|
2241
|
+
if (battle.foe(player).active.volatiles.switching) state.damage *|= 2;
|
|
1557
2242
|
}
|
|
1558
2243
|
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
2244
|
+
pub const rage = struct {
|
|
2245
|
+
pub fn start(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2246
|
+
battle.side(player).active.volatiles.Rage = true;
|
|
2247
|
+
try options.log.singlemove(.{ battle.active(player), Move.Rage });
|
|
2248
|
+
}
|
|
1562
2249
|
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
}
|
|
2250
|
+
pub fn damage(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2251
|
+
const side = battle.side(player);
|
|
1566
2252
|
|
|
1567
|
-
|
|
1568
|
-
|
|
2253
|
+
assert(side.active.volatiles.Rage);
|
|
2254
|
+
state.damage *|= (side.active.volatiles.rage +| 1);
|
|
2255
|
+
}
|
|
2256
|
+
};
|
|
2257
|
+
|
|
2258
|
+
pub fn rainDance(battle: anytype, _: Player, _: *State, options: anytype) !void {
|
|
2259
|
+
return weather(battle, .Rain, options);
|
|
1569
2260
|
}
|
|
1570
2261
|
|
|
1571
|
-
fn
|
|
1572
|
-
|
|
2262
|
+
pub fn rapidSpin(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2263
|
+
var side = battle.side(player);
|
|
2264
|
+
|
|
2265
|
+
if (side.active.volatiles.LeechSeed) {
|
|
2266
|
+
try options.log.end(.{ battle.active(player), .LeechSeedFrom });
|
|
2267
|
+
side.active.volatiles.LeechSeed = false;
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
if (side.conditions.Spikes) {
|
|
2271
|
+
try options.log.sideend(.{ player, protocol.Side.Spikes, battle.active(player) });
|
|
2272
|
+
side.conditions.Spikes = false;
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
if (side.active.volatiles.bind.duration > 0) {
|
|
2276
|
+
const reason = @intFromEnum(End.Bind) + side.active.volatiles.bind.reason - 1;
|
|
2277
|
+
try options.log.end(.{ battle.active(player), @as(End, @enumFromInt(reason)) });
|
|
2278
|
+
side.active.volatiles.bind = .{};
|
|
2279
|
+
}
|
|
1573
2280
|
}
|
|
1574
2281
|
|
|
1575
|
-
fn
|
|
1576
|
-
|
|
2282
|
+
pub fn recoil(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2283
|
+
var side = battle.side(player);
|
|
2284
|
+
var stored = side.stored();
|
|
2285
|
+
const ident = battle.active(player);
|
|
2286
|
+
|
|
2287
|
+
assert(state.damage > 0);
|
|
2288
|
+
|
|
2289
|
+
const damage: i16 = @intCast(@max(state.damage / 4, 1));
|
|
2290
|
+
stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
|
|
2291
|
+
|
|
2292
|
+
try options.log.damage(.{ ident, stored, .RecoilOf, battle.active(player.foe()) });
|
|
1577
2293
|
}
|
|
1578
2294
|
|
|
1579
|
-
fn
|
|
1580
|
-
|
|
2295
|
+
pub fn reversal(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2296
|
+
const stored = battle.side(player).stored();
|
|
2297
|
+
|
|
2298
|
+
// Pokémon Showdown Gen II implementation incorrectly inherits Gen IV's table instead
|
|
2299
|
+
state.bp = if (showdown) power: {
|
|
2300
|
+
const x = @max(1, stored.hp * 64 / stored.stats.hp);
|
|
2301
|
+
if (x < 2) break :power 200;
|
|
2302
|
+
if (x < 6) break :power 150;
|
|
2303
|
+
if (x < 13) break :power 100;
|
|
2304
|
+
if (x < 22) break :power 80;
|
|
2305
|
+
if (x < 43) break :power 40;
|
|
2306
|
+
break :power 20;
|
|
2307
|
+
} else power: {
|
|
2308
|
+
const x = @max(1, stored.hp * 48 / stored.stats.hp);
|
|
2309
|
+
if (x < 1) break :power 200;
|
|
2310
|
+
if (x < 4) break :power 150;
|
|
2311
|
+
if (x < 9) break :power 100;
|
|
2312
|
+
if (x < 16) break :power 80;
|
|
2313
|
+
if (x < 32) break :power 40;
|
|
2314
|
+
break :power 20;
|
|
2315
|
+
};
|
|
2316
|
+
|
|
2317
|
+
try calcDamage(battle, player, state, options);
|
|
1581
2318
|
}
|
|
1582
2319
|
|
|
1583
|
-
|
|
1584
|
-
|
|
2320
|
+
pub const rollout = struct {
|
|
2321
|
+
pub fn check(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
2322
|
+
var side = battle.side(player);
|
|
2323
|
+
|
|
2324
|
+
if (!side.active.volatiles.Rollout) {
|
|
2325
|
+
side.active.volatiles.attacks = 0;
|
|
2326
|
+
} else {
|
|
2327
|
+
// TODO SkipToBattleCommand(doturn_command)
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2331
|
+
pub fn power(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2332
|
+
var side = battle.side(player);
|
|
2333
|
+
|
|
2334
|
+
if (Status.is(side.stored().status, .SLP)) return;
|
|
2335
|
+
|
|
2336
|
+
if (side.active.volatiles.attacks > 0) {
|
|
2337
|
+
// TODO set wSomeoneIsRampaging
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
if (state.miss) {
|
|
2341
|
+
side.active.volatiles.Rollout = false;
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
assert(side.active.volatiles.attacks < 5);
|
|
2346
|
+
side.active.volatiles.attacks += 1;
|
|
2347
|
+
side.active.volatiles.Rollout = side.active.volatiles.attacks != 5;
|
|
2348
|
+
|
|
2349
|
+
if (side.active.volatiles.DefenseCurl) state.damage *|= 2;
|
|
2350
|
+
}
|
|
2351
|
+
};
|
|
2352
|
+
|
|
2353
|
+
pub fn sandstorm(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2354
|
+
if (battle.field.weather != .Sandstorm) return weather(battle, .Sandstorm, options);
|
|
2355
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
1585
2356
|
}
|
|
1586
2357
|
|
|
1587
|
-
fn
|
|
1588
|
-
|
|
2358
|
+
pub fn safeguard(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2359
|
+
return screens(battle, player, "Safeguard", "safeguard", options);
|
|
1589
2360
|
}
|
|
1590
2361
|
|
|
1591
|
-
fn
|
|
1592
|
-
|
|
2362
|
+
pub fn reflect(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2363
|
+
return screens(battle, player, "Reflect", "reflect", options);
|
|
1593
2364
|
}
|
|
1594
2365
|
|
|
1595
|
-
fn
|
|
1596
|
-
|
|
2366
|
+
pub fn lightScreen(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2367
|
+
return screens(battle, player, "LightScreen", "light_screen", options);
|
|
1597
2368
|
}
|
|
1598
2369
|
|
|
1599
|
-
fn
|
|
1600
|
-
|
|
2370
|
+
fn screens(
|
|
2371
|
+
battle: anytype,
|
|
2372
|
+
player: Player,
|
|
2373
|
+
comptime key: []const u8,
|
|
2374
|
+
comptime value: []const u8,
|
|
2375
|
+
options: anytype,
|
|
2376
|
+
) !void {
|
|
2377
|
+
var side = battle.side(player);
|
|
2378
|
+
|
|
2379
|
+
if (@field(side.conditions, key)) {
|
|
2380
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
@field(side.conditions, key) = true;
|
|
2384
|
+
@field(side.conditions, value) = 5;
|
|
2385
|
+
try options.log.sidestart(.{ player, @field(protocol.Side, key) });
|
|
1601
2386
|
}
|
|
1602
2387
|
|
|
1603
|
-
fn
|
|
1604
|
-
|
|
2388
|
+
pub fn sketch(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2389
|
+
var side = battle.side(player);
|
|
2390
|
+
|
|
2391
|
+
side.last_used_move = .None;
|
|
2392
|
+
side.active.volatiles.dirty = false;
|
|
2393
|
+
|
|
2394
|
+
if (showdown) {
|
|
2395
|
+
// yes, actually "Splash". '|nothing' gets blindly mapped to '|-activate||move:Splash'
|
|
2396
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
2397
|
+
} else {
|
|
2398
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2399
|
+
}
|
|
1605
2400
|
}
|
|
1606
2401
|
|
|
1607
|
-
fn sleep(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1608
|
-
|
|
2402
|
+
pub fn sleep(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2403
|
+
var foe = battle.foe(player);
|
|
2404
|
+
var foe_stored = foe.stored();
|
|
2405
|
+
const foe_ident = battle.active(player.foe());
|
|
2406
|
+
|
|
2407
|
+
if (Status.any(foe_stored.status)) {
|
|
2408
|
+
return options.log.fail(.{
|
|
2409
|
+
foe_ident,
|
|
2410
|
+
if (Status.is(foe_stored.status, .SLP)) Fail.Sleep else Fail.None,
|
|
2411
|
+
});
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
if (foe.active.volatiles.Substitute) {
|
|
2415
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2418
|
+
// Sleep Clause Mod
|
|
2419
|
+
if (showdown) {
|
|
2420
|
+
for (foe.pokemon) |p| {
|
|
2421
|
+
if (Status.is(p.status, .SLP) and !Status.is(p.status, .EXT)) return;
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
|
|
2426
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .From, state.move });
|
|
1609
2427
|
}
|
|
1610
2428
|
|
|
1611
|
-
fn sleepTalk(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2429
|
+
pub fn sleepTalk(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1612
2430
|
const side = battle.side(player);
|
|
1613
2431
|
|
|
1614
2432
|
if (!Status.is(side.stored().status, .SLP)) {
|
|
1615
|
-
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
1616
|
-
return;
|
|
2433
|
+
return if (!showdown) try options.log.fail(.{ battle.active(player), .None });
|
|
1617
2434
|
}
|
|
1618
2435
|
|
|
1619
2436
|
var n: u3 = 0;
|
|
1620
2437
|
for (side.active.moves, 0..) |m, i| {
|
|
1621
2438
|
if (state.mslot - 1 != i and Move.get(m.id).extra.sleep_talk) n += 1;
|
|
1622
2439
|
}
|
|
1623
|
-
if (n == 0) {
|
|
1624
|
-
try options.log.fail(battle.active(player), .None);
|
|
1625
|
-
return;
|
|
1626
|
-
}
|
|
2440
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
1627
2441
|
|
|
1628
2442
|
_ = try Rolls.sleepTalk(battle, player, n, state.mslot, options); // TODO
|
|
1629
2443
|
}
|
|
1630
2444
|
|
|
1631
|
-
fn snore(battle: anytype, player: Player, state: *State, options: anytype) !
|
|
1632
|
-
|
|
2445
|
+
pub fn snore(battle: anytype, player: Player, state: *State, options: anytype) !bool {
|
|
2446
|
+
if (Status.is(battle.side(player).stored().status, .SLP)) return true;
|
|
2447
|
+
state.damage = 0;
|
|
2448
|
+
state.miss = true; // TODO is this necessary?
|
|
2449
|
+
if (!showdown) try options.log.fail(.{ battle.active(player), .None });
|
|
2450
|
+
return false;
|
|
1633
2451
|
}
|
|
1634
2452
|
|
|
1635
|
-
fn
|
|
1636
|
-
|
|
1637
|
-
|
|
2453
|
+
pub fn spikes(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2454
|
+
var foe = battle.foe(player);
|
|
2455
|
+
if (foe.conditions.Spikes) return options.log.fail(.{ battle.active(player), .None });
|
|
1638
2456
|
|
|
1639
|
-
|
|
1640
|
-
|
|
2457
|
+
foe.conditions.Spikes = true;
|
|
2458
|
+
try options.log.sidestart(.{ player.foe(), protocol.Side.Spikes });
|
|
1641
2459
|
}
|
|
1642
2460
|
|
|
1643
|
-
fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1644
|
-
|
|
1645
|
-
}
|
|
2461
|
+
pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2462
|
+
var foe = battle.foe(player);
|
|
1646
2463
|
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
2464
|
+
const last = foe.lastMove(false);
|
|
2465
|
+
if (state.miss or last == .None or last == .Struggle) {
|
|
2466
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2467
|
+
}
|
|
1650
2468
|
|
|
1651
|
-
|
|
1652
|
-
|
|
2469
|
+
var slot: u4 = 0;
|
|
2470
|
+
for (foe.active.moves, 0..) |m, i| {
|
|
2471
|
+
if (m.id == last) slot = @intCast(i + 1);
|
|
2472
|
+
}
|
|
2473
|
+
// If the move was called via Metronome it might not exist
|
|
2474
|
+
if (slot == 0 or foe.active.move(slot).pp == 0) {
|
|
2475
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
// Pokémon Showdown always deducts 4 instead of randomizing
|
|
2479
|
+
const pp = @min(
|
|
2480
|
+
foe.active.move(slot).pp,
|
|
2481
|
+
if (showdown) 4 else try Rolls.spite(battle, player, options),
|
|
2482
|
+
);
|
|
2483
|
+
foe.active.move(slot).pp -= pp;
|
|
2484
|
+
if (!foe.active.volatiles.Transform) {
|
|
2485
|
+
var move_slot = foe.stored().move(slot);
|
|
2486
|
+
assert(move_slot.pp > 0);
|
|
2487
|
+
|
|
2488
|
+
move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
|
|
2489
|
+
assert(foe.active.move(slot).pp == foe.stored().move(slot).pp);
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
try options.log.activate(.{ battle.active(player.foe()), .Spite, last, pp });
|
|
1653
2493
|
}
|
|
1654
2494
|
|
|
1655
|
-
fn
|
|
1656
|
-
|
|
2495
|
+
pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2496
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
1657
2497
|
}
|
|
1658
2498
|
|
|
1659
|
-
fn
|
|
1660
|
-
|
|
2499
|
+
pub fn substitute(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2500
|
+
var side = battle.side(player);
|
|
2501
|
+
if (side.active.volatiles.Substitute) {
|
|
2502
|
+
return options.log.fail(.{ battle.active(player), .Substitute });
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
assert(side.stored().stats.hp <= 1023);
|
|
2506
|
+
const hp: u8 = @intCast(side.stored().stats.hp / 4);
|
|
2507
|
+
|
|
2508
|
+
if (hp == 0 or side.stored().hp <= hp) {
|
|
2509
|
+
return options.log.fail(.{ battle.active(player), .Weak });
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
side.stored().hp -= hp;
|
|
2513
|
+
side.active.volatiles.substitute = hp;
|
|
2514
|
+
side.active.volatiles.Substitute = true;
|
|
2515
|
+
side.active.volatiles.bind = .{};
|
|
2516
|
+
try options.log.start(.{ battle.active(player), .Substitute });
|
|
2517
|
+
try options.log.damage(.{ battle.active(player), side.stored(), .None });
|
|
1661
2518
|
}
|
|
1662
2519
|
|
|
1663
|
-
fn
|
|
1664
|
-
|
|
2520
|
+
pub fn sunnyDay(battle: anytype, _: Player, _: *State, options: anytype) !void {
|
|
2521
|
+
return weather(battle, .Sun, options);
|
|
1665
2522
|
}
|
|
1666
2523
|
|
|
1667
|
-
fn
|
|
1668
|
-
|
|
2524
|
+
fn weather(battle: anytype, w: Weather, options: anytype) !void {
|
|
2525
|
+
battle.field.weather = w;
|
|
2526
|
+
battle.field.weather_duration = 5;
|
|
2527
|
+
try options.log.weather(.{ w, protocol.Weather.None });
|
|
1669
2528
|
}
|
|
1670
2529
|
|
|
1671
|
-
fn teleport(battle: anytype, player: Player,
|
|
1672
|
-
|
|
2530
|
+
pub fn teleport(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2531
|
+
if (showdown) return;
|
|
2532
|
+
|
|
2533
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2534
|
+
try options.log.laststill(.{});
|
|
1673
2535
|
}
|
|
1674
2536
|
|
|
1675
|
-
fn thief(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1676
|
-
|
|
2537
|
+
pub fn thief(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2538
|
+
const side = battle.side(player);
|
|
2539
|
+
const foe = battle.foe(player);
|
|
2540
|
+
|
|
2541
|
+
if (side.stored().item != .None or foe.stored().item == .None) return;
|
|
2542
|
+
// Pokémon Showdown incorrectly allows you steal Mail
|
|
2543
|
+
if (!showdown and foe.stored().item.isMail()) return;
|
|
2544
|
+
if (!state.proc) return;
|
|
2545
|
+
|
|
2546
|
+
side.stored().item = foe.stored().item;
|
|
2547
|
+
foe.stored().item = .None;
|
|
2548
|
+
|
|
2549
|
+
try options.log.item(.{
|
|
2550
|
+
battle.active(player),
|
|
2551
|
+
side.stored().item,
|
|
2552
|
+
battle.active(player.foe()),
|
|
2553
|
+
});
|
|
1677
2554
|
}
|
|
1678
2555
|
|
|
1679
|
-
fn thrashing(battle: anytype, player: Player,
|
|
1680
|
-
|
|
2556
|
+
pub fn thrashing(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2557
|
+
var side = battle.side(player);
|
|
2558
|
+
if (Status.is(side.stored().status, .SLP)) return;
|
|
2559
|
+
|
|
2560
|
+
assert(!side.active.volatiles.Thrashing);
|
|
2561
|
+
side.active.volatiles.Thrashing = true;
|
|
2562
|
+
side.active.volatiles.attacks = Rolls.attackingDuration(battle, player, options);
|
|
2563
|
+
// TODO wSomeoneIsRampaging = true;
|
|
1681
2564
|
}
|
|
1682
2565
|
|
|
1683
|
-
fn
|
|
1684
|
-
|
|
2566
|
+
pub fn transform(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2567
|
+
var side = battle.side(player);
|
|
2568
|
+
const foe = battle.foe(player);
|
|
2569
|
+
const foe_ident = battle.active(player.foe());
|
|
2570
|
+
|
|
2571
|
+
side.last_used_move = .None;
|
|
2572
|
+
side.active.volatiles.dirty = false;
|
|
2573
|
+
|
|
2574
|
+
if (foe.active.volatiles.Transform) {
|
|
2575
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
2576
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2577
|
+
if (!showdown) return options.log.fail(.{ battle.active(player), .None });
|
|
2578
|
+
try options.log.lastmiss(.{});
|
|
2579
|
+
return options.log.miss(.{battle.active(player)});
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
side.active.volatiles.Transform = true;
|
|
2583
|
+
side.active.volatiles.transform = foe_ident.int();
|
|
2584
|
+
side.active.volatiles.disable = .{};
|
|
2585
|
+
|
|
2586
|
+
// HP is not copied by Transform
|
|
2587
|
+
side.active.stats.atk = foe.active.stats.atk;
|
|
2588
|
+
side.active.stats.def = foe.active.stats.def;
|
|
2589
|
+
side.active.stats.spe = foe.active.stats.spe;
|
|
2590
|
+
side.active.stats.spa = foe.active.stats.spa;
|
|
2591
|
+
side.active.stats.spd = foe.active.stats.spd;
|
|
2592
|
+
|
|
2593
|
+
side.active.species = foe.active.species;
|
|
2594
|
+
side.active.types = foe.active.types;
|
|
2595
|
+
side.active.boosts = foe.active.boosts;
|
|
2596
|
+
for (foe.active.moves, 0..) |m, i| {
|
|
2597
|
+
side.active.moves[i].id = m.id;
|
|
2598
|
+
side.active.moves[i].pp = if (m.id != .None) if (m.id == .Sketch) 1 else 5 else 0;
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
try options.log.transform(.{ battle.active(player), foe_ident });
|
|
1685
2602
|
}
|
|
1686
2603
|
|
|
1687
|
-
fn
|
|
1688
|
-
|
|
2604
|
+
pub fn triAttack(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2605
|
+
// FIXME only commit effectchance and triattack rolls if actually does something...
|
|
2606
|
+
try effectChance(battle, player, state, options);
|
|
2607
|
+
if (state.proc) {
|
|
2608
|
+
try switch (try Rolls.triAttack(battle, player, options)) {
|
|
2609
|
+
.PAR => paralyzeChance(battle, player, state, options),
|
|
2610
|
+
.FRZ => freezeChance(battle, player, state, options),
|
|
2611
|
+
.BRN => burnChance(battle, player, state, options),
|
|
2612
|
+
};
|
|
2613
|
+
} else if (!showdown) battle.rng.advance(1);
|
|
1689
2614
|
}
|
|
1690
2615
|
|
|
1691
|
-
fn tripleKick(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2616
|
+
pub fn tripleKick(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1692
2617
|
_ = .{ battle, player, state, options }; // TODO
|
|
1693
2618
|
}
|
|
1694
2619
|
|
|
1695
|
-
|
|
1696
|
-
_
|
|
2620
|
+
const STAT_UP_REASONS =
|
|
2621
|
+
[_]Boost{ .Attack, .Defense, .Speed, .SpecialAttack, .SpecialDefense };
|
|
2622
|
+
|
|
2623
|
+
pub fn allStatUpChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2624
|
+
var side = battle.side(player);
|
|
2625
|
+
|
|
2626
|
+
const stats = &side.active.stats;
|
|
2627
|
+
const boosts = &side.active.boosts;
|
|
2628
|
+
|
|
2629
|
+
if (!state.proc) return;
|
|
2630
|
+
inline for (STATS, STAT_UP_REASONS) |s, r| {
|
|
2631
|
+
assert(@field(boosts, s) >= -6 and @field(boosts, s) <= 6);
|
|
2632
|
+
if (@field(boosts, s) == 6 or @field(stats, s) == MAX_STAT_VALUE) {
|
|
2633
|
+
try options.log.boost(.{ battle.active(player), r, 0 });
|
|
2634
|
+
} else {
|
|
2635
|
+
try boostStat(battle, side, stats, boosts, s, 1);
|
|
2636
|
+
try options.log.boost(.{ battle.active(player), r, 1 });
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
1697
2639
|
}
|
|
1698
2640
|
|
|
1699
|
-
fn boost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1700
|
-
|
|
2641
|
+
pub fn boost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2642
|
+
var log = options.log;
|
|
2643
|
+
var side = battle.side(player);
|
|
2644
|
+
const ident = battle.active(player);
|
|
2645
|
+
|
|
2646
|
+
if (state.miss) return log.fail(.{ ident, .None });
|
|
2647
|
+
|
|
2648
|
+
const stats = &side.active.stats;
|
|
2649
|
+
var boosts = &side.active.boosts;
|
|
2650
|
+
|
|
2651
|
+
const move = Move.get(state.move);
|
|
2652
|
+
switch (move.effect) {
|
|
2653
|
+
.AttackUp1, .AttackUp2, .Rage, .Swagger => {
|
|
2654
|
+
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2655
|
+
if (boosts.atk == 6 or stats.atk == MAX_STAT_VALUE) {
|
|
2656
|
+
return log.fail(.{ ident, .None });
|
|
2657
|
+
}
|
|
2658
|
+
const n: u2 = if (move.effect == .AttackUp2 or move.effect == .Swagger) 2 else 1;
|
|
2659
|
+
try boostStat(battle, side, stats, boosts, "atk", n);
|
|
2660
|
+
const reason = if (showdown and move.effect == .Rage) Boost.Rage else Boost.Attack;
|
|
2661
|
+
try log.boost(.{ ident, reason, n });
|
|
2662
|
+
},
|
|
2663
|
+
.DefenseUp1, .DefenseUp2, .SkullBash => {
|
|
2664
|
+
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2665
|
+
if (boosts.def == 6 or stats.def == MAX_STAT_VALUE) {
|
|
2666
|
+
return log.fail(.{ ident, .None });
|
|
2667
|
+
}
|
|
2668
|
+
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
2669
|
+
try boostStat(battle, side, stats, boosts, "def", n);
|
|
2670
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2671
|
+
},
|
|
2672
|
+
.SpeedUp2 => {
|
|
2673
|
+
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2674
|
+
if (boosts.spe == 6 or stats.spe == MAX_STAT_VALUE) {
|
|
2675
|
+
return log.fail(.{ ident, .None });
|
|
2676
|
+
}
|
|
2677
|
+
try boostStat(battle, side, stats, boosts, "spe", 2);
|
|
2678
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2679
|
+
},
|
|
2680
|
+
.SpAtkUp1 => {
|
|
2681
|
+
assert(boosts.spa >= -6 and boosts.spa <= 6);
|
|
2682
|
+
if (boosts.spa == 6 or stats.spa == MAX_STAT_VALUE) {
|
|
2683
|
+
return log.fail(.{ ident, .None });
|
|
2684
|
+
}
|
|
2685
|
+
try boostStat(battle, side, stats, boosts, "spa", 1);
|
|
2686
|
+
try log.boost(.{ ident, .SpecialAttack, 1 });
|
|
2687
|
+
},
|
|
2688
|
+
.SpDefUp2 => {
|
|
2689
|
+
assert(boosts.spd >= -6 and boosts.spd <= 6);
|
|
2690
|
+
if (boosts.spd == 6 or stats.spd == MAX_STAT_VALUE) {
|
|
2691
|
+
return log.fail(.{ ident, .None });
|
|
2692
|
+
}
|
|
2693
|
+
try boostStat(battle, side, stats, boosts, "spd", 2);
|
|
2694
|
+
try log.boost(.{ ident, .SpecialDefense, 2 });
|
|
2695
|
+
},
|
|
2696
|
+
.EvasionUp1 => {
|
|
2697
|
+
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2698
|
+
if (boosts.evasion == 6) return log.fail(.{ ident, .None });
|
|
2699
|
+
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2700
|
+
try log.boost(.{ ident, .Evasion, 1 });
|
|
2701
|
+
if (state.move == .Minimize) side.active.volatiles.minimized = true;
|
|
2702
|
+
},
|
|
2703
|
+
else => unreachable,
|
|
2704
|
+
}
|
|
1701
2705
|
}
|
|
1702
2706
|
|
|
1703
|
-
fn unboost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1704
|
-
|
|
2707
|
+
pub fn unboost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2708
|
+
var log = options.log;
|
|
2709
|
+
var foe = battle.foe(player);
|
|
2710
|
+
const foe_ident = battle.active(player.foe());
|
|
2711
|
+
|
|
2712
|
+
if (foe.active.volatiles.Mist) {
|
|
2713
|
+
try log.activate(.{ foe_ident, .Mist });
|
|
2714
|
+
return log.fail(.{ foe_ident, .None });
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
const fail = foe.active.volatiles.Substitute or state.miss or !state.proc;
|
|
2718
|
+
|
|
2719
|
+
const stats = &foe.active.stats;
|
|
2720
|
+
var boosts = &foe.active.boosts;
|
|
2721
|
+
|
|
2722
|
+
const move = Move.get(state.move);
|
|
2723
|
+
const none: Fail = .None;
|
|
2724
|
+
switch (move.effect) {
|
|
2725
|
+
.AttackDown1, .AttackDown2, .AttackDownChance => {
|
|
2726
|
+
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2727
|
+
if (fail or boosts.atk == -6) {
|
|
2728
|
+
return if (move.effect != .AttackDownChance) try log.fail(.{ foe_ident, none });
|
|
2729
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2730
|
+
try options.log.lastmiss(.{});
|
|
2731
|
+
return options.log.miss(.{battle.active(player)});
|
|
2732
|
+
} else if (stats.atk == 1) {
|
|
2733
|
+
return if (move.effect != .AttackDownChance) try log.fail(.{ foe_ident, none });
|
|
2734
|
+
}
|
|
2735
|
+
const n: u2 = if (move.effect == .AttackDown2) 2 else 1;
|
|
2736
|
+
try unboostStat(battle, foe, stats, boosts, "atk", n);
|
|
2737
|
+
try log.boost(.{ foe_ident, .Attack, -@as(i8, n) });
|
|
2738
|
+
},
|
|
2739
|
+
.DefenseDown1, .DefenseDown2, .DefenseDownChance => {
|
|
2740
|
+
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2741
|
+
if (fail or boosts.def == -6) {
|
|
2742
|
+
if (move.effect != .DefenseDownChance) try log.fail(.{ foe_ident, none });
|
|
2743
|
+
return;
|
|
2744
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2745
|
+
try options.log.lastmiss(.{});
|
|
2746
|
+
return options.log.miss(.{battle.active(player)});
|
|
2747
|
+
} else if (stats.def == 1) {
|
|
2748
|
+
if (move.effect != .DefenseDownChance) try log.fail(.{ foe_ident, none });
|
|
2749
|
+
return;
|
|
2750
|
+
}
|
|
2751
|
+
const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
|
|
2752
|
+
try unboostStat(battle, foe, stats, boosts, "def", n);
|
|
2753
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2754
|
+
},
|
|
2755
|
+
.SpeedDown1, .SpeedDown2, .SpeedDownChance => {
|
|
2756
|
+
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2757
|
+
if (fail or boosts.spe == -6) {
|
|
2758
|
+
return if (move.effect != .SpeedDownChance) try log.fail(.{ foe_ident, none });
|
|
2759
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2760
|
+
try options.log.lastmiss(.{});
|
|
2761
|
+
return options.log.miss(.{battle.active(player)});
|
|
2762
|
+
} else if (stats.spe == 1) {
|
|
2763
|
+
return if (move.effect != .SpeedDownChance) try log.fail(.{ foe_ident, none });
|
|
2764
|
+
}
|
|
2765
|
+
const n: u2 = if (move.effect == .SpeedDown2) 2 else 1;
|
|
2766
|
+
try unboostStat(battle, foe, stats, boosts, "spe", n);
|
|
2767
|
+
try log.boost(.{ foe_ident, .Speed, -@as(i8, n) });
|
|
2768
|
+
},
|
|
2769
|
+
.SpDefDownChance => {
|
|
2770
|
+
assert(boosts.spd >= -6 and boosts.spd <= 6);
|
|
2771
|
+
if (fail or boosts.spd == -6) return;
|
|
2772
|
+
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2773
|
+
try options.log.lastmiss(.{});
|
|
2774
|
+
return options.log.miss(.{battle.active(player)});
|
|
2775
|
+
} else if (stats.spd == 1) return;
|
|
2776
|
+
try unboostStat(battle, foe, stats, boosts, "spd", 1);
|
|
2777
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2778
|
+
},
|
|
2779
|
+
.AccuracyDown1, .AccuracyDownChance => {
|
|
2780
|
+
assert(boosts.accuracy >= -6 and boosts.accuracy <= 6);
|
|
2781
|
+
if (fail or boosts.accuracy == -6) {
|
|
2782
|
+
if (move.effect != .AccuracyDownChance) try log.fail(.{ foe_ident, none });
|
|
2783
|
+
return;
|
|
2784
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2785
|
+
try options.log.lastmiss(.{});
|
|
2786
|
+
return options.log.miss(.{battle.active(player)});
|
|
2787
|
+
}
|
|
2788
|
+
boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
|
|
2789
|
+
try log.boost(.{ foe_ident, .Accuracy, -1 });
|
|
2790
|
+
},
|
|
2791
|
+
else => unreachable,
|
|
2792
|
+
}
|
|
1705
2793
|
}
|
|
1706
2794
|
};
|
|
1707
2795
|
|
|
2796
|
+
fn unmodifiedStats(battle: anytype, side: *Side) *Stats(u16) {
|
|
2797
|
+
if (!side.active.volatiles.Transform) return &side.stored().stats;
|
|
2798
|
+
const id = ID.from(side.active.volatiles.transform);
|
|
2799
|
+
return &battle.side(id.player).pokemon[id.id - 1].stats;
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
fn recomputeStats(battle: anytype, side: *Side) void {
|
|
2803
|
+
const stats = unmodifiedStats(battle, side);
|
|
2804
|
+
inline for (STATS) |s| {
|
|
2805
|
+
const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(side.active.boosts, s)) + 6))];
|
|
2806
|
+
const val = @field(stats, s) * mod[0] / mod[1];
|
|
2807
|
+
@field(side.active.stats, s) = @max(1, @min(MAX_STAT_VALUE, val));
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
|
|
1708
2811
|
fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
1709
2812
|
if (Status.is(status, .PAR)) {
|
|
1710
2813
|
stats.spe = @max(stats.spe / 4, 1);
|
|
@@ -1713,20 +2816,66 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
1713
2816
|
}
|
|
1714
2817
|
}
|
|
1715
2818
|
|
|
1716
|
-
|
|
2819
|
+
fn boostStat(
|
|
2820
|
+
battle: anytype,
|
|
2821
|
+
side: *Side,
|
|
2822
|
+
stats: *Stats(u16),
|
|
2823
|
+
boosts: *Boosts,
|
|
2824
|
+
comptime s: []const u8,
|
|
2825
|
+
n: u2,
|
|
2826
|
+
) !void {
|
|
2827
|
+
@field(boosts, s) = @intCast(@min(6, @as(i8, @field(boosts, s)) + n));
|
|
2828
|
+
const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
|
|
2829
|
+
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2830
|
+
@field(stats, s) = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2831
|
+
if (comptime std.mem.eql(u8, s, "atk")) {
|
|
2832
|
+
if (Status.is(side.stored().status, .BRN)) {
|
|
2833
|
+
@field(stats, s) = @max(@field(stats, s) / 2, 1);
|
|
2834
|
+
}
|
|
2835
|
+
} else if (comptime std.mem.eql(u8, s, "spe")) {
|
|
2836
|
+
if (Status.is(side.stored().status, .PAR)) {
|
|
2837
|
+
@field(stats, s) = @max(@field(stats, s) / 4, 1);
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
fn unboostStat(
|
|
2843
|
+
battle: anytype,
|
|
2844
|
+
side: *Side,
|
|
2845
|
+
stats: *Stats(u16),
|
|
2846
|
+
boosts: *Boosts,
|
|
2847
|
+
comptime s: []const u8,
|
|
2848
|
+
n: u2,
|
|
2849
|
+
) !void {
|
|
2850
|
+
@field(boosts, s) = @intCast(@max(-6, @as(i8, @field(boosts, s)) - n));
|
|
2851
|
+
const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
|
|
2852
|
+
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2853
|
+
@field(stats, s) = @max(1, stat * mod[0] / mod[1]);
|
|
2854
|
+
if (comptime std.mem.eql(u8, s, "atk")) {
|
|
2855
|
+
if (Status.is(side.stored().status, .BRN)) {
|
|
2856
|
+
@field(stats, s) = @max(@field(stats, s) / 2, 1);
|
|
2857
|
+
}
|
|
2858
|
+
} else if (comptime std.mem.eql(u8, s, "spe")) {
|
|
2859
|
+
if (Status.is(side.stored().status, .PAR)) {
|
|
2860
|
+
@field(stats, s) = @max(@field(stats, s) / 4, 1);
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
fn isForced(active: anytype) bool {
|
|
1717
2866
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
1718
2867
|
active.volatiles.Thrashing or active.volatiles.Charging or
|
|
1719
2868
|
active.volatiles.Rollout;
|
|
1720
2869
|
}
|
|
1721
2870
|
|
|
1722
|
-
|
|
2871
|
+
fn convertible(active: ActivePokemon, m: Move) bool {
|
|
1723
2872
|
if (m == .None) return false;
|
|
1724
2873
|
const t = Move.get(m).type;
|
|
1725
2874
|
return t != .@"???" and !active.types.includes(t);
|
|
1726
2875
|
}
|
|
1727
2876
|
|
|
1728
2877
|
pub const Rolls = struct {
|
|
1729
|
-
|
|
2878
|
+
fn speedTie(battle: anytype, options: anytype) !bool {
|
|
1730
2879
|
const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
|
|
1731
2880
|
player == .P1
|
|
1732
2881
|
else if (showdown)
|
|
@@ -1738,7 +2887,7 @@ pub const Rolls = struct {
|
|
|
1738
2887
|
return p1;
|
|
1739
2888
|
}
|
|
1740
2889
|
|
|
1741
|
-
|
|
2890
|
+
fn quickClaw(battle: anytype, player: Player, options: anytype) !bool {
|
|
1742
2891
|
const qkc = if (options.calc.overridden(player, .quick_claw)) |val|
|
|
1743
2892
|
val == .true
|
|
1744
2893
|
else if (showdown)
|
|
@@ -1750,7 +2899,7 @@ pub const Rolls = struct {
|
|
|
1750
2899
|
return qkc;
|
|
1751
2900
|
}
|
|
1752
2901
|
|
|
1753
|
-
|
|
2902
|
+
fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) !bool {
|
|
1754
2903
|
const ok = if (options.calc.overridden(player, .hit)) |val|
|
|
1755
2904
|
val == .true
|
|
1756
2905
|
else if (showdown)
|
|
@@ -1762,7 +2911,7 @@ pub const Rolls = struct {
|
|
|
1762
2911
|
return ok;
|
|
1763
2912
|
}
|
|
1764
2913
|
|
|
1765
|
-
|
|
2914
|
+
fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
|
|
1766
2915
|
const crit = if (options.calc.overridden(player, .critical_hit)) |val|
|
|
1767
2916
|
val == .true
|
|
1768
2917
|
else if (showdown)
|
|
@@ -1774,7 +2923,7 @@ pub const Rolls = struct {
|
|
|
1774
2923
|
return crit;
|
|
1775
2924
|
}
|
|
1776
2925
|
|
|
1777
|
-
|
|
2926
|
+
fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
1778
2927
|
const roll = if (options.calc.overridden(player, .damage)) |val|
|
|
1779
2928
|
val
|
|
1780
2929
|
else roll: {
|
|
@@ -1790,7 +2939,7 @@ pub const Rolls = struct {
|
|
|
1790
2939
|
return roll;
|
|
1791
2940
|
}
|
|
1792
2941
|
|
|
1793
|
-
|
|
2942
|
+
fn confused(battle: anytype, player: Player, options: anytype) !bool {
|
|
1794
2943
|
const cfz = if (options.calc.overridden(player, .confused)) |val|
|
|
1795
2944
|
val == .true
|
|
1796
2945
|
else if (showdown)
|
|
@@ -1802,7 +2951,7 @@ pub const Rolls = struct {
|
|
|
1802
2951
|
return cfz;
|
|
1803
2952
|
}
|
|
1804
2953
|
|
|
1805
|
-
|
|
2954
|
+
fn attract(battle: anytype, player: Player, options: anytype) !bool {
|
|
1806
2955
|
const cant = if (options.calc.overridden(player, .attract)) |val|
|
|
1807
2956
|
val == .true
|
|
1808
2957
|
else if (showdown)
|
|
@@ -1814,7 +2963,7 @@ pub const Rolls = struct {
|
|
|
1814
2963
|
return cant;
|
|
1815
2964
|
}
|
|
1816
2965
|
|
|
1817
|
-
|
|
2966
|
+
fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
|
|
1818
2967
|
const par = if (options.calc.overridden(player, .paralyzed)) |val|
|
|
1819
2968
|
val == .true
|
|
1820
2969
|
else if (showdown)
|
|
@@ -1826,7 +2975,7 @@ pub const Rolls = struct {
|
|
|
1826
2975
|
return par;
|
|
1827
2976
|
}
|
|
1828
2977
|
|
|
1829
|
-
|
|
2978
|
+
fn defrost(battle: anytype, player: Player, options: anytype) !bool {
|
|
1830
2979
|
const thaw = if (options.calc.overridden(player, .defrost)) |val|
|
|
1831
2980
|
val == .true
|
|
1832
2981
|
else if (showdown)
|
|
@@ -1838,7 +2987,7 @@ pub const Rolls = struct {
|
|
|
1838
2987
|
return thaw;
|
|
1839
2988
|
}
|
|
1840
2989
|
|
|
1841
|
-
|
|
2990
|
+
fn secondaryChance(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
|
|
1842
2991
|
// GLITCH: 100% secondary chance will still fail 1/256 of the time
|
|
1843
2992
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
1844
2993
|
val == .true
|
|
@@ -1851,7 +3000,7 @@ pub const Rolls = struct {
|
|
|
1851
3000
|
return proc;
|
|
1852
3001
|
}
|
|
1853
3002
|
|
|
1854
|
-
|
|
3003
|
+
fn item(battle: anytype, player: Player, options: anytype) !bool {
|
|
1855
3004
|
const proc = if (options.calc.overridden(player, .item)) |val|
|
|
1856
3005
|
val == .true
|
|
1857
3006
|
else if (showdown)
|
|
@@ -1863,23 +3012,23 @@ pub const Rolls = struct {
|
|
|
1863
3012
|
return proc;
|
|
1864
3013
|
}
|
|
1865
3014
|
|
|
1866
|
-
|
|
3015
|
+
fn triAttack(battle: anytype, player: Player, options: anytype) !TriAttack {
|
|
1867
3016
|
const status: TriAttack = if (options.calc.overridden(player, .tri_attack)) |val|
|
|
1868
|
-
val
|
|
3017
|
+
@enumFromInt(@intFromEnum(val) - 1)
|
|
1869
3018
|
else if (showdown)
|
|
1870
|
-
@enumFromInt(battle.rng.range(
|
|
3019
|
+
@enumFromInt(battle.rng.range(u2, 0, 2))
|
|
1871
3020
|
else loop: {
|
|
1872
3021
|
while (true) {
|
|
1873
|
-
const r = std.math.rotr(u8,
|
|
3022
|
+
const r = std.math.rotr(u8, battle.rng.next(), 4) & 3;
|
|
1874
3023
|
if (r != 0) break :loop @enumFromInt(r - 1);
|
|
1875
3024
|
}
|
|
1876
3025
|
};
|
|
1877
3026
|
|
|
1878
3027
|
try options.chance.triAttack(player, status);
|
|
1879
|
-
return status
|
|
3028
|
+
return status;
|
|
1880
3029
|
}
|
|
1881
3030
|
|
|
1882
|
-
|
|
3031
|
+
fn present(battle: anytype, player: Player, options: anytype) !u8 {
|
|
1883
3032
|
const power = if (options.calc.overridden(player, .present)) |val|
|
|
1884
3033
|
(val - 1) * 40
|
|
1885
3034
|
else if (showdown) power: {
|
|
@@ -1901,8 +3050,8 @@ pub const Rolls = struct {
|
|
|
1901
3050
|
return power;
|
|
1902
3051
|
}
|
|
1903
3052
|
|
|
1904
|
-
|
|
1905
|
-
const num = if (options.calc.overridden(player, .magnitude)) |val|
|
|
3053
|
+
fn magnitude(battle: anytype, player: Player, options: anytype) !u8 {
|
|
3054
|
+
const num: u8 = if (options.calc.overridden(player, .magnitude)) |val|
|
|
1906
3055
|
val + 3
|
|
1907
3056
|
else if (showdown) num: {
|
|
1908
3057
|
const r = battle.rng.range(u8, 0, 100);
|
|
@@ -1929,7 +3078,7 @@ pub const Rolls = struct {
|
|
|
1929
3078
|
return num;
|
|
1930
3079
|
}
|
|
1931
3080
|
|
|
1932
|
-
|
|
3081
|
+
fn tripleKick(battle: anytype, player: Player, options: anytype) !u2 {
|
|
1933
3082
|
const hits = if (options.calc.overridden(player, .triple_kick)) |val|
|
|
1934
3083
|
val
|
|
1935
3084
|
else if (showdown)
|
|
@@ -1946,13 +3095,12 @@ pub const Rolls = struct {
|
|
|
1946
3095
|
return hits;
|
|
1947
3096
|
}
|
|
1948
3097
|
|
|
1949
|
-
|
|
1950
|
-
|
|
3098
|
+
fn spite(battle: anytype, player: Player, options: anytype) !u3 {
|
|
3099
|
+
assert(!showdown);
|
|
3100
|
+
const pp: u3 = if (options.calc.overridden(player, .spite)) |val|
|
|
1951
3101
|
val + 1
|
|
1952
|
-
else if (showdown)
|
|
1953
|
-
battle.rng.range(u8, 2, 6)
|
|
1954
3102
|
else
|
|
1955
|
-
(battle.rng.next() & 3) + 2;
|
|
3103
|
+
@intCast((battle.rng.next() & 3) + 2);
|
|
1956
3104
|
|
|
1957
3105
|
assert(pp >= 2 and pp <= 5);
|
|
1958
3106
|
try options.chance.spite(player, pp);
|
|
@@ -1962,7 +3110,7 @@ pub const Rolls = struct {
|
|
|
1962
3110
|
// Conversion 2 can at most choose between 7 types (vs. a Grass-type attack)
|
|
1963
3111
|
var CONVERSION_2: [7]Type = [_]Type{.@"???"} ** 7;
|
|
1964
3112
|
|
|
1965
|
-
|
|
3113
|
+
fn conversion2(battle: anytype, player: Player, mtype: Type, options: anytype) !Type {
|
|
1966
3114
|
var i: u8 = 0;
|
|
1967
3115
|
const neutral = @intFromEnum(Effectiveness.Neutral);
|
|
1968
3116
|
assert(mtype != .@"???");
|
|
@@ -1996,7 +3144,7 @@ pub const Rolls = struct {
|
|
|
1996
3144
|
return ty;
|
|
1997
3145
|
}
|
|
1998
3146
|
|
|
1999
|
-
|
|
3147
|
+
fn protect(battle: anytype, player: Player, count: u8, options: anytype) !bool {
|
|
2000
3148
|
assert(count <= 8);
|
|
2001
3149
|
const num = if (count == 8) 0 else @as(u8, 255) >> @intCast(count);
|
|
2002
3150
|
|
|
@@ -2090,7 +3238,7 @@ pub const Rolls = struct {
|
|
|
2090
3238
|
return slot;
|
|
2091
3239
|
}
|
|
2092
3240
|
|
|
2093
|
-
|
|
3241
|
+
fn forceSwitch(
|
|
2094
3242
|
battle: anytype,
|
|
2095
3243
|
player: Player,
|
|
2096
3244
|
slots: []u4,
|
|
@@ -2128,7 +3276,7 @@ pub const Rolls = struct {
|
|
|
2128
3276
|
|
|
2129
3277
|
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
2130
3278
|
|
|
2131
|
-
|
|
3279
|
+
fn multiHit(battle: anytype, player: Player, options: anytype) !u3 {
|
|
2132
3280
|
const n: u3 = if (options.calc.overridden(player, .multi_hit)) |val|
|
|
2133
3281
|
@intCast(val)
|
|
2134
3282
|
else if (showdown)
|
|
@@ -2143,30 +3291,30 @@ pub const Rolls = struct {
|
|
|
2143
3291
|
return n;
|
|
2144
3292
|
}
|
|
2145
3293
|
|
|
2146
|
-
|
|
3294
|
+
fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
|
|
2147
3295
|
const power = if (options.calc.overridden(player, .psywave)) |val|
|
|
2148
|
-
val
|
|
3296
|
+
val
|
|
2149
3297
|
else if (showdown)
|
|
2150
|
-
battle.rng.range(u8,
|
|
3298
|
+
battle.rng.range(u8, 1, max)
|
|
2151
3299
|
else power: {
|
|
2152
3300
|
while (true) {
|
|
2153
3301
|
const r = battle.rng.next();
|
|
2154
|
-
if (r < max) break :power r;
|
|
3302
|
+
if (r != 0 and r < max) break :power r;
|
|
2155
3303
|
}
|
|
2156
3304
|
};
|
|
2157
3305
|
|
|
2158
|
-
assert(power < max);
|
|
3306
|
+
assert(power != 0 and power < max);
|
|
2159
3307
|
try options.chance.psywave(player, power, max);
|
|
2160
3308
|
return power;
|
|
2161
3309
|
}
|
|
2162
3310
|
|
|
2163
|
-
|
|
2164
|
-
const moves = battle.
|
|
3311
|
+
fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
3312
|
+
const moves = battle.side(player).active.moves;
|
|
2165
3313
|
|
|
2166
3314
|
var n: u2 = 0;
|
|
2167
3315
|
for (moves) |m| {
|
|
2168
|
-
if (m == .None) break;
|
|
2169
|
-
if (Move.get(m).metronome) n += 1;
|
|
3316
|
+
if (m.id == .None) break;
|
|
3317
|
+
if (Move.get(m.id).extra.metronome) n += 1;
|
|
2170
3318
|
}
|
|
2171
3319
|
|
|
2172
3320
|
// TODO: consider throwing error instead of rerolling?
|
|
@@ -2177,10 +3325,11 @@ pub const Rolls = struct {
|
|
|
2177
3325
|
|
|
2178
3326
|
const move: Move = overridden orelse move: {
|
|
2179
3327
|
if (showdown) {
|
|
2180
|
-
var r = battle.rng.range(u8, 0, Move.METRONOME.len - n);
|
|
3328
|
+
var r = battle.rng.range(u8, 0, @as(u8, @intCast(Move.METRONOME.len - n)));
|
|
2181
3329
|
for (moves) |m| {
|
|
2182
|
-
if (m == .None
|
|
2183
|
-
if (
|
|
3330
|
+
if (m.id == .None) break;
|
|
3331
|
+
if (@intFromEnum(m.id) > @intFromEnum(Move.METRONOME[r])) break;
|
|
3332
|
+
if (Move.get(m.id).extra.metronome) r -= 1;
|
|
2184
3333
|
}
|
|
2185
3334
|
break :move Move.METRONOME[r];
|
|
2186
3335
|
} else {
|
|
@@ -2195,12 +3344,12 @@ pub const Rolls = struct {
|
|
|
2195
3344
|
}
|
|
2196
3345
|
};
|
|
2197
3346
|
|
|
2198
|
-
assert(Move.get(move).metronome);
|
|
3347
|
+
assert(Move.get(move).extra.metronome);
|
|
2199
3348
|
try options.chance.metronome(player, move, n);
|
|
2200
3349
|
return move;
|
|
2201
3350
|
}
|
|
2202
3351
|
|
|
2203
|
-
|
|
3352
|
+
fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2204
3353
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2205
3354
|
@intCast(val)
|
|
2206
3355
|
else if (showdown)
|
|
@@ -2217,7 +3366,7 @@ pub const Rolls = struct {
|
|
|
2217
3366
|
return duration + 1;
|
|
2218
3367
|
}
|
|
2219
3368
|
|
|
2220
|
-
|
|
3369
|
+
fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
|
|
2221
3370
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2222
3371
|
@intCast(val)
|
|
2223
3372
|
else if (showdown)
|
|
@@ -2231,7 +3380,7 @@ pub const Rolls = struct {
|
|
|
2231
3380
|
return duration;
|
|
2232
3381
|
}
|
|
2233
3382
|
|
|
2234
|
-
|
|
3383
|
+
fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
|
|
2235
3384
|
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2236
3385
|
@intCast(val)
|
|
2237
3386
|
else if (showdown)
|
|
@@ -2249,7 +3398,7 @@ pub const Rolls = struct {
|
|
|
2249
3398
|
return duration;
|
|
2250
3399
|
}
|
|
2251
3400
|
|
|
2252
|
-
|
|
3401
|
+
fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2253
3402
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2254
3403
|
@intCast(val)
|
|
2255
3404
|
else if (showdown)
|
|
@@ -2262,7 +3411,7 @@ pub const Rolls = struct {
|
|
|
2262
3411
|
return duration;
|
|
2263
3412
|
}
|
|
2264
3413
|
|
|
2265
|
-
|
|
3414
|
+
fn bindingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2266
3415
|
const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
|
|
2267
3416
|
@intCast(val)
|
|
2268
3417
|
else if (showdown)
|
|
@@ -2277,7 +3426,7 @@ pub const Rolls = struct {
|
|
|
2277
3426
|
}
|
|
2278
3427
|
|
|
2279
3428
|
// TODO: consider sharing implementation with bindingDuration
|
|
2280
|
-
|
|
3429
|
+
fn encoreDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2281
3430
|
const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
|
|
2282
3431
|
@intCast(val)
|
|
2283
3432
|
else if (showdown)
|
|
@@ -2399,7 +3548,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
2399
3548
|
.Move => {
|
|
2400
3549
|
const side = battle.side(player);
|
|
2401
3550
|
|
|
2402
|
-
|
|
3551
|
+
const active = &side.active;
|
|
2403
3552
|
const stored = side.stored();
|
|
2404
3553
|
|
|
2405
3554
|
// While players are not given any input options on the cartridge in these cases,
|
|
@@ -2419,7 +3568,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
2419
3568
|
}
|
|
2420
3569
|
|
|
2421
3570
|
var slot: u4 = 2;
|
|
2422
|
-
while (!active.volatiles.
|
|
3571
|
+
while (!active.volatiles.Trapped and slot <= 6) : (slot += 1) {
|
|
2423
3572
|
const id = side.order[slot - 1];
|
|
2424
3573
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
2425
3574
|
out[n] = .{ .type = .Switch, .data = slot };
|