@pkmn/engine 0.1.0-dev.dc33f678 → 0.1.0-dev.deda8593
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 +33 -22
- 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 +217 -47
- 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 -55
- package/src/lib/gen1/mechanics.zig +333 -312
- 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 +1621 -456
- 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/test/display/pkmn.html.tmpl +5 -5
- 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,14 +565,32 @@ 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, move: Move, mslot: u4) bool {
|
|
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
|
|
|
@@ -553,34 +614,43 @@ fn decrementPP(side: *Side, move: Move, mslot: u4) bool {
|
|
|
553
614
|
return true;
|
|
554
615
|
}
|
|
555
616
|
|
|
556
|
-
fn doMove(
|
|
617
|
+
pub fn doMove(
|
|
618
|
+
comptime secondary: bool,
|
|
619
|
+
battle: anytype,
|
|
620
|
+
player: Player,
|
|
621
|
+
state: *State,
|
|
622
|
+
options: anytype,
|
|
623
|
+
) !bool {
|
|
557
624
|
try checkCriticalHit(battle, player, state, options);
|
|
558
625
|
try calcDamage(battle, player, state, options);
|
|
559
626
|
try adjustDamage(battle, player, state, options);
|
|
560
627
|
try randomizeDamage(battle, player, state, options);
|
|
628
|
+
try checkHit(battle, player, state, options);
|
|
629
|
+
if (secondary) try effectChance(battle, player, state, options);
|
|
561
630
|
try applyDamage(battle, player, state, options);
|
|
631
|
+
try reportOutcome(battle, player, state, options);
|
|
632
|
+
if (try destinyBond(battle, player, state, options)) return false;
|
|
562
633
|
try buildRage(battle, player, state, options);
|
|
563
634
|
try kingsRock(battle, player, state, options);
|
|
635
|
+
return true;
|
|
636
|
+
}
|
|
564
637
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
try
|
|
573
|
-
try
|
|
574
|
-
try
|
|
575
|
-
|
|
576
|
-
try Effects.protect(battle, player, state, options);
|
|
577
|
-
|
|
578
|
-
return null;
|
|
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;
|
|
579
649
|
}
|
|
580
650
|
|
|
581
|
-
fn checkCriticalHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
651
|
+
pub fn checkCriticalHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
582
652
|
const move = Move.get(state.move);
|
|
583
|
-
if (
|
|
653
|
+
if (state.bp == 0) return;
|
|
584
654
|
|
|
585
655
|
const side = battle.side(player);
|
|
586
656
|
const pokemon = side.stored();
|
|
@@ -598,7 +668,7 @@ fn checkCriticalHit(battle: anytype, player: Player, state: *State, options: any
|
|
|
598
668
|
state.crit = try Rolls.criticalHit(battle, player, CRITICAL_HITS[stage], options);
|
|
599
669
|
}
|
|
600
670
|
|
|
601
|
-
fn calcDamage(
|
|
671
|
+
pub fn calcDamage(
|
|
602
672
|
battle: anytype,
|
|
603
673
|
player: Player,
|
|
604
674
|
state: *State,
|
|
@@ -612,37 +682,36 @@ fn calcDamage(
|
|
|
612
682
|
|
|
613
683
|
const FORESIGHT: Types = .{ .type1 = .Normal, .type2 = .Fighting };
|
|
614
684
|
|
|
615
|
-
fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
685
|
+
pub fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
616
686
|
if (state.move == .Struggle) return;
|
|
617
687
|
|
|
618
688
|
const side = battle.side(player);
|
|
619
689
|
const foe = battle.foe(player);
|
|
620
690
|
const types = foe.active.types;
|
|
621
|
-
const move = Move.get(state.move);
|
|
622
691
|
|
|
623
692
|
var d = state.damage;
|
|
624
|
-
if ((
|
|
625
|
-
(
|
|
693
|
+
if ((state.type == .Water and battle.field.weather == .Rain) or
|
|
694
|
+
(state.type == .Fire and battle.field.weather == .Sun))
|
|
626
695
|
{
|
|
627
696
|
d = d *% @intFromEnum(Effectiveness.Super) / 10;
|
|
628
|
-
} else if ((
|
|
629
|
-
((
|
|
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))
|
|
630
699
|
{
|
|
631
700
|
d = d *% @intFromEnum(Effectiveness.Resisted) / 10;
|
|
632
701
|
}
|
|
633
702
|
|
|
634
|
-
if (side.active.types.includes(
|
|
703
|
+
if (side.active.types.includes(state.type)) d +%= d / 2;
|
|
635
704
|
|
|
636
705
|
const neutral = @intFromEnum(Effectiveness.Neutral);
|
|
637
|
-
const foresight = foe.active.volatiles.Foresight and FORESIGHT.includes(
|
|
706
|
+
const foresight = foe.active.volatiles.Foresight and FORESIGHT.includes(state.type);
|
|
638
707
|
const eff1: u16 = if (foresight and types.type1 == .Ghost)
|
|
639
708
|
neutral
|
|
640
709
|
else
|
|
641
|
-
@intFromEnum(
|
|
710
|
+
@intFromEnum(state.type.effectiveness(types.type1));
|
|
642
711
|
const eff2: u16 = if (foresight and types.type2 == .Ghost)
|
|
643
712
|
neutral
|
|
644
713
|
else
|
|
645
|
-
@intFromEnum(
|
|
714
|
+
@intFromEnum(state.type.effectiveness(types.type2));
|
|
646
715
|
|
|
647
716
|
// Type effectiveness matchup precedence only matters with (NVE, SE)
|
|
648
717
|
if (!showdown and (eff1 + eff2) == Effectiveness.mismatch and
|
|
@@ -662,7 +731,7 @@ fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype) !voi
|
|
|
662
731
|
state.damage = d;
|
|
663
732
|
}
|
|
664
733
|
|
|
665
|
-
fn randomizeDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
734
|
+
pub fn randomizeDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
666
735
|
if (state.damage <= 1) return;
|
|
667
736
|
const random = try Rolls.damage(battle, player, options);
|
|
668
737
|
options.calc.base(player.foe(), state.damage);
|
|
@@ -670,12 +739,13 @@ fn randomizeDamage(battle: anytype, player: Player, state: *State, options: anyt
|
|
|
670
739
|
options.calc.final(player.foe(), state.damage);
|
|
671
740
|
}
|
|
672
741
|
|
|
673
|
-
fn applyDamage(
|
|
742
|
+
pub fn applyDamage(
|
|
674
743
|
battle: anytype,
|
|
675
744
|
player: Player,
|
|
676
745
|
state: *State,
|
|
677
746
|
options: anytype,
|
|
678
747
|
) !void {
|
|
748
|
+
// TODO failuretext (if not Bide) + applydamage
|
|
679
749
|
_ = battle;
|
|
680
750
|
_ = player;
|
|
681
751
|
_ = state;
|
|
@@ -683,7 +753,7 @@ fn applyDamage(
|
|
|
683
753
|
}
|
|
684
754
|
|
|
685
755
|
// TODO: set damage = 0 if missed and not (High) Jump Kick
|
|
686
|
-
fn checkHit(
|
|
756
|
+
pub fn checkHit(
|
|
687
757
|
battle: anytype,
|
|
688
758
|
player: Player,
|
|
689
759
|
state: *State,
|
|
@@ -708,7 +778,7 @@ fn checkHit(
|
|
|
708
778
|
return;
|
|
709
779
|
}
|
|
710
780
|
if (side.active.volatiles.LockOn) {
|
|
711
|
-
state.miss = !(foe.active.volatiles.Flying and move.extra.
|
|
781
|
+
state.miss = !(foe.active.volatiles.Flying and move.extra.underground);
|
|
712
782
|
return;
|
|
713
783
|
}
|
|
714
784
|
assert(!(foe.active.volatiles.Flying and foe.active.volatiles.Underground));
|
|
@@ -723,16 +793,23 @@ fn checkHit(
|
|
|
723
793
|
return;
|
|
724
794
|
}
|
|
725
795
|
}
|
|
726
|
-
if (move.effect == .Thunder
|
|
727
|
-
|
|
728
|
-
|
|
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
|
+
}
|
|
729
805
|
}
|
|
730
806
|
if (move.effect == .AlwaysHit) {
|
|
731
807
|
state.miss = false;
|
|
732
808
|
return;
|
|
733
809
|
}
|
|
734
810
|
|
|
735
|
-
var accuracy: u16 =
|
|
811
|
+
var accuracy: u16 = state.accuracy;
|
|
812
|
+
assert(accuracy > 0);
|
|
736
813
|
if (foe.active.boosts.evasion <= side.active.boosts.accuracy or
|
|
737
814
|
!foe.active.volatiles.Foresight)
|
|
738
815
|
{
|
|
@@ -757,24 +834,85 @@ fn checkHit(
|
|
|
757
834
|
fn checkFaint(
|
|
758
835
|
battle: anytype,
|
|
759
836
|
player: Player,
|
|
760
|
-
state: *State,
|
|
761
837
|
options: anytype,
|
|
762
838
|
) @TypeOf(options.log).Error!?Result {
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
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 };
|
|
767
867
|
}
|
|
768
868
|
|
|
769
|
-
fn faint(battle: anytype, player: Player, done: bool, options: anytype)
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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);
|
|
775
913
|
}
|
|
776
914
|
|
|
777
|
-
fn handleResidual(battle: anytype, player: Player, options: anytype) !
|
|
915
|
+
fn handleResidual(battle: anytype, player: Player, options: anytype) !bool {
|
|
778
916
|
var side = battle.side(player);
|
|
779
917
|
var stored = side.stored();
|
|
780
918
|
const ident = battle.active(player);
|
|
@@ -794,9 +932,9 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
794
932
|
}
|
|
795
933
|
|
|
796
934
|
stored.hp -|= damage;
|
|
797
|
-
// Pokémon Showdown uses
|
|
798
|
-
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
799
|
-
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;
|
|
800
938
|
}
|
|
801
939
|
|
|
802
940
|
if (volatiles.LeechSeed) {
|
|
@@ -811,35 +949,37 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !void {
|
|
|
811
949
|
|
|
812
950
|
const damage = @max(stored.stats.hp / 8, 1);
|
|
813
951
|
stored.hp -|= damage;
|
|
814
|
-
// As above, Pokémon Showdown uses
|
|
815
|
-
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 });
|
|
816
954
|
|
|
817
955
|
const before = foe_stored.hp;
|
|
818
956
|
foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
|
|
819
957
|
// Pokémon Showdown uses the less specific heal here instead of drain... because reasons?
|
|
820
|
-
if (foe_stored.hp > before) try options.log.heal(foe_ident, foe_stored, .Silent);
|
|
821
|
-
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;
|
|
822
960
|
}
|
|
823
961
|
|
|
824
962
|
if (volatiles.Nightmare) {
|
|
825
963
|
stored.hp -= @max(stored.stats.hp / 4, 1);
|
|
826
964
|
// ibid
|
|
827
|
-
try options.log.damage(ident, stored, .Nightmare);
|
|
828
|
-
if (stored.hp == 0) return;
|
|
965
|
+
try options.log.damage(.{ ident, stored, .Nightmare });
|
|
966
|
+
if (stored.hp == 0) return true;
|
|
829
967
|
}
|
|
830
968
|
|
|
831
969
|
if (volatiles.Curse) {
|
|
832
970
|
stored.hp -= @max(stored.stats.hp / 4, 1);
|
|
833
971
|
// ibid
|
|
834
|
-
try options.log.damage(ident, stored, .Curse);
|
|
835
|
-
if (stored.hp == 0) return;
|
|
972
|
+
try options.log.damage(.{ ident, stored, .Curse });
|
|
973
|
+
if (stored.hp == 0) return true;
|
|
836
974
|
}
|
|
975
|
+
|
|
976
|
+
return false;
|
|
837
977
|
}
|
|
838
978
|
|
|
839
|
-
fn betweenTurns(battle: anytype, mslot: u4, options: anytype)
|
|
979
|
+
fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
840
980
|
const players = comptime std.enums.values(Player);
|
|
841
981
|
|
|
842
|
-
|
|
982
|
+
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
843
983
|
|
|
844
984
|
// Future Sight
|
|
845
985
|
inline for (players) |player| {
|
|
@@ -850,33 +990,36 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
850
990
|
future_sight.count -= 1;
|
|
851
991
|
assert(future_sight.count != 0);
|
|
852
992
|
if (future_sight.count == 1) {
|
|
853
|
-
try options.log.end(battle.active(player), .FutureSight);
|
|
993
|
+
try options.log.end(.{ battle.active(player), .FutureSight });
|
|
854
994
|
// TODO doMove
|
|
855
995
|
}
|
|
856
996
|
}
|
|
857
997
|
}
|
|
858
|
-
|
|
998
|
+
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
859
999
|
|
|
860
1000
|
// Weather
|
|
861
1001
|
if (battle.field.weather != .None) {
|
|
862
|
-
assert(battle.field.weather_duration > 0);
|
|
863
1002
|
battle.field.weather_duration -= 1;
|
|
864
1003
|
|
|
865
1004
|
if (battle.field.weather_duration == 0) {
|
|
866
1005
|
battle.field.weather = .None;
|
|
867
|
-
try options.log.weather(battle.field.weather, .None);
|
|
1006
|
+
try options.log.weather(.{ battle.field.weather, protocol.Weather.None });
|
|
868
1007
|
} else {
|
|
869
|
-
try options.log.weather(battle.field.weather, .Upkeep);
|
|
1008
|
+
try options.log.weather(.{ battle.field.weather, protocol.Weather.Upkeep });
|
|
870
1009
|
if (battle.field.weather == .Sandstorm) {
|
|
871
1010
|
inline for (players) |player| {
|
|
872
1011
|
var side = battle.side(player);
|
|
873
1012
|
const active = side.active;
|
|
874
1013
|
if (!(active.volatiles.Underground or active.types.sandstormImmune())) {
|
|
875
1014
|
side.stored().hp -|= @max(side.stored().stats.hp / 8, 1);
|
|
876
|
-
try options.log.damage(
|
|
1015
|
+
try options.log.damage(.{
|
|
1016
|
+
battle.active(player),
|
|
1017
|
+
side.stored(),
|
|
1018
|
+
.Sandstorm,
|
|
1019
|
+
});
|
|
877
1020
|
}
|
|
878
1021
|
}
|
|
879
|
-
|
|
1022
|
+
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
880
1023
|
}
|
|
881
1024
|
}
|
|
882
1025
|
}
|
|
@@ -885,23 +1028,28 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
885
1028
|
inline for (players) |player| {
|
|
886
1029
|
var side = battle.side(player);
|
|
887
1030
|
var volatiles = &side.active.volatiles;
|
|
1031
|
+
const ident = battle.active(player);
|
|
888
1032
|
|
|
889
1033
|
if (volatiles.bind.duration > 0 and !volatiles.Substitute) {
|
|
890
1034
|
volatiles.bind.duration -= 1;
|
|
891
1035
|
if (volatiles.bind.duration == 0) {
|
|
892
1036
|
const reason = @intFromEnum(End.Bind) + volatiles.bind.reason - 1;
|
|
893
|
-
try options.log.end(
|
|
1037
|
+
try options.log.end(.{ ident, @as(End, @enumFromInt(reason)) });
|
|
894
1038
|
} else {
|
|
895
1039
|
var reason = @intFromEnum(Activate.Bind) + volatiles.bind.reason - 1;
|
|
896
|
-
try options.log.activate(
|
|
1040
|
+
try options.log.activate(.{ ident, @as(Activate, @enumFromInt(reason)) });
|
|
897
1041
|
|
|
898
1042
|
reason = @intFromEnum(Damage.Bind) + volatiles.bind.reason - 1;
|
|
899
1043
|
side.stored().hp -|= @max(side.stored().stats.hp / 16, 1);
|
|
900
|
-
try options.log.damage(
|
|
1044
|
+
try options.log.damage(.{
|
|
1045
|
+
ident,
|
|
1046
|
+
side.stored(),
|
|
1047
|
+
@as(Damage, @enumFromInt(reason)),
|
|
1048
|
+
});
|
|
901
1049
|
}
|
|
902
1050
|
}
|
|
903
1051
|
}
|
|
904
|
-
|
|
1052
|
+
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
905
1053
|
|
|
906
1054
|
// Perish Song
|
|
907
1055
|
inline for (players) |player| {
|
|
@@ -909,12 +1057,11 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
909
1057
|
var volatiles = &side.active.volatiles;
|
|
910
1058
|
|
|
911
1059
|
if (volatiles.PerishSong) {
|
|
912
|
-
assert(volatiles.perish_song > 0);
|
|
913
1060
|
volatiles.perish_song -= 1;
|
|
914
1061
|
|
|
915
1062
|
assert(volatiles.perish_song < 3);
|
|
916
1063
|
const reason = @intFromEnum(Start.PerishSong0) + volatiles.perish_song;
|
|
917
|
-
try options.log.start(battle.active(player), @enumFromInt(reason));
|
|
1064
|
+
try options.log.start(.{ battle.active(player), @as(Start, @enumFromInt(reason)) });
|
|
918
1065
|
|
|
919
1066
|
if (volatiles.perish_song == 0) {
|
|
920
1067
|
volatiles.PerishSong = false;
|
|
@@ -922,7 +1069,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
922
1069
|
}
|
|
923
1070
|
}
|
|
924
1071
|
}
|
|
925
|
-
|
|
1072
|
+
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
926
1073
|
|
|
927
1074
|
// Leftovers
|
|
928
1075
|
inline for (players) |player| {
|
|
@@ -931,13 +1078,15 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
931
1078
|
if (stored.item == .Leftovers) {
|
|
932
1079
|
const before = stored.hp;
|
|
933
1080
|
stored.hp = @min(stored.hp + @max(stored.stats.hp / 16, 1), stored.stats.hp);
|
|
934
|
-
if (stored.hp > before)
|
|
1081
|
+
if (stored.hp > before) {
|
|
1082
|
+
try options.log.heal(.{ battle.active(player), stored, .Leftovers });
|
|
1083
|
+
}
|
|
935
1084
|
}
|
|
936
1085
|
}
|
|
937
1086
|
|
|
938
1087
|
// Mystery Berry
|
|
939
1088
|
inline for (players) |player| {
|
|
940
|
-
|
|
1089
|
+
const stored = battle.side(player).stored();
|
|
941
1090
|
|
|
942
1091
|
if (stored.item == .MysteryBerry) {
|
|
943
1092
|
// TODO stored and active moves, transform, etc
|
|
@@ -952,7 +1101,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
952
1101
|
!side.active.volatiles.frozen and
|
|
953
1102
|
try Rolls.defrost(battle, player, options))
|
|
954
1103
|
{
|
|
955
|
-
try options.log.curestatus(battle.active(player), side.stored().status, .Message);
|
|
1104
|
+
try options.log.curestatus(.{ battle.active(player), side.stored().status, .Message });
|
|
956
1105
|
side.stored().status = 0;
|
|
957
1106
|
}
|
|
958
1107
|
}
|
|
@@ -993,14 +1142,14 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
993
1142
|
stored.hp < stored.stats.hp;
|
|
994
1143
|
if (proc) {
|
|
995
1144
|
assert(stored.hp != 0);
|
|
996
|
-
try options.log.enditem(ident, stored.item, .Eat);
|
|
1145
|
+
try options.log.enditem(.{ ident, stored.item, .Eat });
|
|
997
1146
|
stored.item = .None;
|
|
998
1147
|
|
|
999
1148
|
const offset = num - @intFromEnum(Item.Berry);
|
|
1000
1149
|
assert(offset < 3);
|
|
1001
1150
|
stored.hp = @min(stored.hp + ((offset + 1) * 10), stored.stats.hp);
|
|
1002
1151
|
const reason: Heal = @enumFromInt(@intFromEnum(Heal.Berry) + offset);
|
|
1003
|
-
try options.log.heal(ident, stored, reason);
|
|
1152
|
+
try options.log.heal(.{ ident, stored, reason });
|
|
1004
1153
|
}
|
|
1005
1154
|
} else if (num < @intFromEnum(Item.BitterBerry)) {
|
|
1006
1155
|
const proc = if (stored.item == .MiracleBerry)
|
|
@@ -1010,29 +1159,29 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
1010
1159
|
|
|
1011
1160
|
if (proc) {
|
|
1012
1161
|
assert(Status.any(stored.status));
|
|
1013
|
-
try options.log.enditem(ident, stored.item, .Eat);
|
|
1162
|
+
try options.log.enditem(.{ ident, stored.item, .Eat });
|
|
1014
1163
|
stored.item = .None;
|
|
1015
1164
|
|
|
1016
1165
|
// FIXME wtf?
|
|
1017
1166
|
// side.active.volatiles.Toxic = false;
|
|
1018
|
-
// side.active.volatiles.
|
|
1167
|
+
// side.active.volatiles.Nightmare = false;
|
|
1019
1168
|
if (stored.item == .MiracleBerry) {
|
|
1020
1169
|
side.active.volatiles.Confusion = false;
|
|
1021
|
-
try options.log.end(ident, .Confusion);
|
|
1170
|
+
try options.log.end(.{ ident, .Confusion });
|
|
1022
1171
|
}
|
|
1023
1172
|
stored.status = 0;
|
|
1024
1173
|
|
|
1025
1174
|
// FIXME recalc stats
|
|
1026
1175
|
|
|
1027
|
-
try options.log.curestatus(ident, stored.status, .Message);
|
|
1176
|
+
try options.log.curestatus(.{ ident, stored.status, .Message });
|
|
1028
1177
|
}
|
|
1029
1178
|
} else if (num == @intFromEnum(Item.BitterBerry)) {
|
|
1030
1179
|
if (side.active.volatiles.Confusion) {
|
|
1031
|
-
try options.log.enditem(ident, stored.item, .Eat);
|
|
1180
|
+
try options.log.enditem(.{ ident, stored.item, .Eat });
|
|
1032
1181
|
stored.item = .None;
|
|
1033
1182
|
|
|
1034
1183
|
side.active.volatiles.Confusion = false;
|
|
1035
|
-
try options.log.end(ident, .Confusion);
|
|
1184
|
+
try options.log.end(.{ ident, .Confusion });
|
|
1036
1185
|
}
|
|
1037
1186
|
}
|
|
1038
1187
|
}
|
|
@@ -1044,18 +1193,19 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !void {
|
|
|
1044
1193
|
var volatiles = &side.active.volatiles;
|
|
1045
1194
|
|
|
1046
1195
|
if (volatiles.Encore) {
|
|
1047
|
-
assert(volatiles.encore > 0);
|
|
1048
1196
|
volatiles.encore -= 1;
|
|
1049
1197
|
|
|
1050
1198
|
if (volatiles.encore == 0 or side.active.move(mslot).pp == 0) {
|
|
1051
1199
|
volatiles.Encore = false;
|
|
1052
|
-
try options.log.end(battle.active(player), .Encore);
|
|
1200
|
+
try options.log.end(.{ battle.active(player), .Encore });
|
|
1053
1201
|
}
|
|
1054
1202
|
}
|
|
1055
1203
|
}
|
|
1204
|
+
|
|
1205
|
+
return null;
|
|
1056
1206
|
}
|
|
1057
1207
|
|
|
1058
|
-
|
|
1208
|
+
fn updateSideCondition(
|
|
1059
1209
|
battle: anytype,
|
|
1060
1210
|
player: Player,
|
|
1061
1211
|
comptime key: []const u8,
|
|
@@ -1064,12 +1214,11 @@ inline fn updateSideCondition(
|
|
|
1064
1214
|
) !void {
|
|
1065
1215
|
var side = battle.side(player);
|
|
1066
1216
|
if (@field(side.conditions, key)) {
|
|
1067
|
-
assert(@field(side.conditions, value) > 0);
|
|
1068
1217
|
@field(side.conditions, value) -= 1;
|
|
1069
1218
|
|
|
1070
1219
|
if (@field(side.conditions, value) == 0) {
|
|
1071
1220
|
@field(side.conditions, key) = false;
|
|
1072
|
-
try options.log.sideend(player, @field(protocol.Side, key));
|
|
1221
|
+
try options.log.sideend(.{ player, @field(protocol.Side, key) });
|
|
1073
1222
|
}
|
|
1074
1223
|
}
|
|
1075
1224
|
}
|
|
@@ -1078,18 +1227,19 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1078
1227
|
battle.turn += 1;
|
|
1079
1228
|
|
|
1080
1229
|
if (showdown and battle.turn >= 1000) {
|
|
1081
|
-
try options.log.tie();
|
|
1230
|
+
try options.log.tie(.{});
|
|
1082
1231
|
return Result.Tie;
|
|
1083
1232
|
} else if (battle.turn >= 65535) {
|
|
1084
1233
|
return Result.Error;
|
|
1085
1234
|
}
|
|
1086
1235
|
|
|
1087
|
-
try options.log.turn(battle.turn);
|
|
1236
|
+
try options.log.turn(.{battle.turn});
|
|
1088
1237
|
|
|
1089
1238
|
return Result.Default;
|
|
1090
1239
|
}
|
|
1091
1240
|
|
|
1092
|
-
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
|
|
1093
1243
|
var foe = battle.foe(player);
|
|
1094
1244
|
if (foe.active.volatiles.Rage) {
|
|
1095
1245
|
if (showdown) {
|
|
@@ -1099,12 +1249,13 @@ fn buildRage(battle: anytype, player: Player, state: *State, options: anytype) !
|
|
|
1099
1249
|
}
|
|
1100
1250
|
} else {
|
|
1101
1251
|
foe.active.volatiles.rage +|= 1;
|
|
1102
|
-
try options.log.activate(battle.active(player), .Rage);
|
|
1252
|
+
try options.log.activate(.{ battle.active(player), .Rage });
|
|
1103
1253
|
}
|
|
1104
1254
|
}
|
|
1105
1255
|
}
|
|
1106
1256
|
|
|
1107
|
-
fn kingsRock(battle: anytype, player: Player,
|
|
1257
|
+
pub fn kingsRock(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1258
|
+
assert(!state.miss); // TODO
|
|
1108
1259
|
var foe = battle.foe(player);
|
|
1109
1260
|
const flinch = battle.side(player).stored().item == .KingsRock and
|
|
1110
1261
|
!foe.active.volatiles.Substitute and
|
|
@@ -1115,124 +1266,283 @@ fn kingsRock(battle: anytype, player: Player, _: *State, options: anytype) !void
|
|
|
1115
1266
|
}
|
|
1116
1267
|
}
|
|
1117
1268
|
|
|
1118
|
-
pub
|
|
1119
|
-
|
|
1120
|
-
|
|
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 });
|
|
1121
1276
|
}
|
|
1277
|
+
return true;
|
|
1278
|
+
}
|
|
1122
1279
|
|
|
1123
|
-
|
|
1280
|
+
pub const Effects = struct {
|
|
1281
|
+
pub fn attract(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1124
1282
|
const side = battle.side(player);
|
|
1125
1283
|
var foe = battle.foe(player);
|
|
1126
1284
|
|
|
1127
1285
|
const gender: u8 = @intFromEnum(side.stored().dvs.gender);
|
|
1128
1286
|
const foe_gender: u8 = @intFromEnum(foe.stored().dvs.gender);
|
|
1129
1287
|
if (gender + foe_gender != 1) {
|
|
1130
|
-
|
|
1131
|
-
return;
|
|
1288
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
1132
1289
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1133
|
-
try options.log.lastmiss();
|
|
1134
|
-
|
|
1135
|
-
return;
|
|
1290
|
+
try options.log.lastmiss(.{});
|
|
1291
|
+
return options.log.miss(.{battle.active(player)});
|
|
1136
1292
|
}
|
|
1137
1293
|
|
|
1138
1294
|
foe.active.volatiles.Attract = true;
|
|
1139
|
-
try options.log.start(battle.active(player.foe()), .Attract);
|
|
1295
|
+
try options.log.start(.{ battle.active(player.foe()), .Attract });
|
|
1140
1296
|
}
|
|
1141
1297
|
|
|
1142
|
-
fn batonPass(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1298
|
+
pub fn batonPass(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1143
1299
|
_ = .{ battle, player, state, options }; // TODO
|
|
1144
1300
|
}
|
|
1145
1301
|
|
|
1146
|
-
fn beatUp(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1302
|
+
pub fn beatUp(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1147
1303
|
_ = .{ battle, player, state, options }; // TODO
|
|
1148
1304
|
}
|
|
1149
1305
|
|
|
1150
|
-
fn bellyDrum(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1151
|
-
|
|
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
|
+
}
|
|
1152
1333
|
}
|
|
1153
1334
|
|
|
1154
|
-
|
|
1155
|
-
|
|
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
|
+
};
|
|
1156
1378
|
}
|
|
1157
1379
|
|
|
1158
|
-
fn
|
|
1159
|
-
|
|
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
|
|
1160
1411
|
}
|
|
1161
1412
|
|
|
1162
|
-
fn
|
|
1413
|
+
pub fn confusion(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1163
1414
|
_ = .{ battle, player, state, options }; // TODO
|
|
1164
1415
|
}
|
|
1165
1416
|
|
|
1166
|
-
fn
|
|
1417
|
+
pub fn confusionChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1167
1418
|
_ = .{ battle, player, state, options }; // TODO
|
|
1168
1419
|
}
|
|
1169
1420
|
|
|
1170
|
-
fn conversion(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1421
|
+
pub fn conversion(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1171
1422
|
var side = battle.side(player);
|
|
1172
1423
|
|
|
1173
1424
|
var n: u3 = 0;
|
|
1174
1425
|
for (side.active.moves) |m| {
|
|
1175
1426
|
if (convertible(side.active, m.id)) n += 1;
|
|
1176
1427
|
}
|
|
1177
|
-
if (n == 0) {
|
|
1178
|
-
try options.log.fail(battle.active(player), .None);
|
|
1179
|
-
return;
|
|
1180
|
-
}
|
|
1428
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
1181
1429
|
|
|
1182
1430
|
side.active.types.type1 = try Rolls.conversion(battle, player, n, options);
|
|
1183
1431
|
side.active.types.type2 = side.active.types.type1;
|
|
1184
1432
|
}
|
|
1185
1433
|
|
|
1186
|
-
fn conversion2(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1434
|
+
pub fn conversion2(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1187
1435
|
var side = battle.side(player);
|
|
1188
1436
|
|
|
1189
1437
|
const last = battle.foe(player).lastMove(false);
|
|
1190
|
-
if (last == .None) {
|
|
1191
|
-
try options.log.fail(battle.active(player), .None);
|
|
1192
|
-
return;
|
|
1193
|
-
}
|
|
1438
|
+
if (last == .None) return options.log.fail(.{ battle.active(player), .None });
|
|
1194
1439
|
const move = Move.get(last);
|
|
1195
|
-
if (move.type == .@"???") {
|
|
1196
|
-
try options.log.fail(battle.active(player), .None);
|
|
1197
|
-
return;
|
|
1198
|
-
}
|
|
1440
|
+
if (move.type == .@"???") return options.log.fail(.{ battle.active(player), .None });
|
|
1199
1441
|
|
|
1200
1442
|
side.active.types.type1 = try Rolls.conversion2(battle, player, move.type, options);
|
|
1201
1443
|
side.active.types.type2 = side.active.types.type1;
|
|
1202
1444
|
}
|
|
1203
1445
|
|
|
1204
|
-
fn counter(battle: anytype, player: Player, state: *State,
|
|
1205
|
-
|
|
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;
|
|
1206
1471
|
}
|
|
1207
1472
|
|
|
1208
|
-
fn curse(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1209
|
-
|
|
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
|
+
}
|
|
1210
1519
|
}
|
|
1211
1520
|
|
|
1212
|
-
fn defenseCurl(battle: anytype, player: Player,
|
|
1213
|
-
|
|
1521
|
+
pub fn defenseCurl(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
1522
|
+
battle.side(player).active.volatiles.DefenseCurl = true;
|
|
1214
1523
|
}
|
|
1215
1524
|
|
|
1216
|
-
fn
|
|
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;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
pub fn destinyBond(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1217
1533
|
battle.side(player).active.volatiles.DestinyBond = true;
|
|
1218
|
-
try options.log.singlemove(battle.active(player), Move.DestinyBond);
|
|
1534
|
+
try options.log.singlemove(.{ battle.active(player), Move.DestinyBond });
|
|
1219
1535
|
}
|
|
1220
1536
|
|
|
1221
|
-
fn disable(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1537
|
+
pub fn disable(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1222
1538
|
var foe = battle.foe(player);
|
|
1223
1539
|
var volatiles = &foe.active.volatiles;
|
|
1224
1540
|
const foe_ident = battle.active(player.foe());
|
|
1225
1541
|
|
|
1226
|
-
if (volatiles.disable.move != 0) {
|
|
1227
|
-
try options.log.fail(foe_ident, .None);
|
|
1228
|
-
return;
|
|
1229
|
-
}
|
|
1542
|
+
if (volatiles.disable.move != 0) return options.log.fail(.{ foe_ident, .None });
|
|
1230
1543
|
|
|
1231
1544
|
const last = battle.foe(player).lastMove(false);
|
|
1232
|
-
if (last == .None or last == .Struggle) {
|
|
1233
|
-
try options.log.fail(foe_ident, .None);
|
|
1234
|
-
return;
|
|
1235
|
-
}
|
|
1545
|
+
if (last == .None or last == .Struggle) return options.log.fail(.{ foe_ident, .None });
|
|
1236
1546
|
|
|
1237
1547
|
var slot: u3 = 0;
|
|
1238
1548
|
for (foe.active.moves) |m| {
|
|
@@ -1241,26 +1551,41 @@ pub const Effects = struct {
|
|
|
1241
1551
|
if (m.id == last) break;
|
|
1242
1552
|
}
|
|
1243
1553
|
const move = foe.active.move(slot);
|
|
1244
|
-
if (move.pp == 0) {
|
|
1245
|
-
try options.log.fail(foe_ident, .None);
|
|
1246
|
-
return;
|
|
1247
|
-
}
|
|
1554
|
+
if (move.pp == 0) return options.log.fail(.{ foe_ident, .None });
|
|
1248
1555
|
|
|
1249
1556
|
volatiles.disable.move = slot;
|
|
1250
1557
|
volatiles.disable.duration = Rolls.disableDuration(battle, player, options);
|
|
1251
1558
|
|
|
1252
|
-
try options.log.
|
|
1559
|
+
try options.log.start(.{ foe_ident, .Disable, move.id });
|
|
1253
1560
|
}
|
|
1254
1561
|
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
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
|
+
}
|
|
1258
1566
|
|
|
1259
|
-
|
|
1260
|
-
|
|
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()) });
|
|
1261
1586
|
}
|
|
1262
1587
|
|
|
1263
|
-
fn encore(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1588
|
+
pub fn encore(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1264
1589
|
var foe = battle.foe(player);
|
|
1265
1590
|
var volatiles = &foe.active.volatiles;
|
|
1266
1591
|
const foe_ident = battle.active(player.foe());
|
|
@@ -1269,10 +1594,7 @@ pub const Effects = struct {
|
|
|
1269
1594
|
const failed = volatiles.Encore or
|
|
1270
1595
|
(last == .None or last == .Struggle or
|
|
1271
1596
|
last == .Encore or .last == .MirrorMove);
|
|
1272
|
-
if (failed) {
|
|
1273
|
-
try options.log.fail(foe_ident, .None);
|
|
1274
|
-
return;
|
|
1275
|
-
}
|
|
1597
|
+
if (failed) return options.log.fail(.{ foe_ident, .None });
|
|
1276
1598
|
|
|
1277
1599
|
var slot: u3 = 0;
|
|
1278
1600
|
for (foe.active.moves) |m| {
|
|
@@ -1281,10 +1603,7 @@ pub const Effects = struct {
|
|
|
1281
1603
|
if (m.id == last) break;
|
|
1282
1604
|
}
|
|
1283
1605
|
const move = foe.active.move(slot);
|
|
1284
|
-
if (move.pp == 0) {
|
|
1285
|
-
try options.log.fail(foe_ident, .None);
|
|
1286
|
-
return;
|
|
1287
|
-
}
|
|
1606
|
+
if (move.pp == 0) return options.log.fail(.{ foe_ident, .None });
|
|
1288
1607
|
|
|
1289
1608
|
volatiles.Encore = true;
|
|
1290
1609
|
volatiles.encore = Rolls.encoreDuration(battle, player, options);
|
|
@@ -1292,53 +1611,84 @@ pub const Effects = struct {
|
|
|
1292
1611
|
// TODO ???
|
|
1293
1612
|
_ = state;
|
|
1294
1613
|
|
|
1295
|
-
try options.log.
|
|
1614
|
+
try options.log.start(.{ foe_ident, .Encore, move.id });
|
|
1296
1615
|
}
|
|
1297
1616
|
|
|
1298
|
-
fn explode(battle: anytype, player: Player,
|
|
1299
|
-
|
|
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;
|
|
1300
1627
|
}
|
|
1301
1628
|
|
|
1302
|
-
fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1629
|
+
pub fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1303
1630
|
const foe = battle.foe(player);
|
|
1304
1631
|
assert(foe.stored().hp > 0);
|
|
1305
1632
|
// FIXME crit?
|
|
1306
1633
|
if (state.damage > foe.stored().hp) state.damage = foe.stored().hp - 1;
|
|
1307
1634
|
}
|
|
1308
1635
|
|
|
1309
|
-
fn fixedDamage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1310
|
-
|
|
1311
|
-
|
|
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);
|
|
1312
1639
|
|
|
1313
|
-
|
|
1314
|
-
|
|
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
|
+
};
|
|
1315
1652
|
}
|
|
1316
1653
|
|
|
1317
|
-
fn flinchChance(battle: anytype, player: Player, state: *State,
|
|
1318
|
-
|
|
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;
|
|
1319
1666
|
}
|
|
1320
1667
|
|
|
1321
|
-
fn flyDig(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1668
|
+
pub fn flyDig(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1322
1669
|
_ = .{ battle, player, state, options }; // TODO
|
|
1323
1670
|
}
|
|
1324
1671
|
|
|
1325
|
-
fn focusEnergy(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1672
|
+
pub fn focusEnergy(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1326
1673
|
var side = battle.side(player);
|
|
1327
1674
|
|
|
1328
1675
|
if (side.active.volatiles.FocusEnergy) {
|
|
1329
|
-
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
1330
|
-
return;
|
|
1676
|
+
return if (!showdown) try options.log.fail(.{ battle.active(player), .None });
|
|
1331
1677
|
}
|
|
1332
1678
|
|
|
1333
1679
|
side.active.volatiles.FocusEnergy = true;
|
|
1334
|
-
try options.log.start(battle.active(player), .FocusEnergy);
|
|
1680
|
+
try options.log.start(.{ battle.active(player), .FocusEnergy });
|
|
1335
1681
|
}
|
|
1336
1682
|
|
|
1337
1683
|
var SLOTS: [5]u4 = .{ 0, 0, 0, 0, 0 };
|
|
1338
1684
|
|
|
1339
|
-
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
|
+
|
|
1340
1688
|
const foe = battle.foe(player);
|
|
1341
1689
|
|
|
1690
|
+
if (state.first) return options.log.fail(.{ battle.active(player), .None });
|
|
1691
|
+
|
|
1342
1692
|
var n: u4 = 0;
|
|
1343
1693
|
var i: u4 = 0;
|
|
1344
1694
|
for (foe.pokemon, 0..) |pokemon, j| {
|
|
@@ -1351,169 +1701,518 @@ pub const Effects = struct {
|
|
|
1351
1701
|
}
|
|
1352
1702
|
}
|
|
1353
1703
|
|
|
1354
|
-
if (n == 0) {
|
|
1355
|
-
try options.log.fail(battle.active(player), .None);
|
|
1356
|
-
return;
|
|
1357
|
-
}
|
|
1704
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
1358
1705
|
|
|
1359
|
-
|
|
1706
|
+
const slot = try Rolls.forceSwitch(battle, player, SLOTS[0..i], n, options);
|
|
1707
|
+
try switchIn(battle, player.foe(), slot, .Drag, options);
|
|
1360
1708
|
}
|
|
1361
1709
|
|
|
1362
|
-
fn foresight(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1710
|
+
pub fn foresight(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1363
1711
|
var foe = battle.foe(player);
|
|
1364
1712
|
|
|
1365
1713
|
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1366
|
-
try options.log.lastmiss();
|
|
1367
|
-
|
|
1368
|
-
return;
|
|
1714
|
+
try options.log.lastmiss(.{});
|
|
1715
|
+
return options.log.miss(.{battle.active(player)});
|
|
1369
1716
|
} else if (foe.active.volatiles.Foresight) {
|
|
1370
|
-
|
|
1371
|
-
return;
|
|
1717
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
1372
1718
|
}
|
|
1373
1719
|
|
|
1374
1720
|
foe.active.volatiles.Foresight = true;
|
|
1375
|
-
try options.log.start(battle.active(player.foe()), .FocusEnergy);
|
|
1721
|
+
try options.log.start(.{ battle.active(player.foe()), .FocusEnergy });
|
|
1376
1722
|
}
|
|
1377
1723
|
|
|
1378
|
-
fn freezeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1379
|
-
|
|
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;
|
|
1380
1750
|
}
|
|
1381
1751
|
|
|
1382
|
-
|
|
1383
|
-
|
|
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
|
+
}
|
|
1384
1772
|
}
|
|
1385
1773
|
|
|
1386
|
-
fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1774
|
+
pub fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1387
1775
|
_ = .{ battle, player, state, options }; // TODO
|
|
1388
1776
|
}
|
|
1389
1777
|
|
|
1390
|
-
fn happiness(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1778
|
+
pub fn happiness(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1391
1779
|
const val = battle.side(player).stored().happiness;
|
|
1392
1780
|
const frustration = Move.get(state.move).effect == .Frustration;
|
|
1393
1781
|
state.bp = ((if (frustration) 255 - val else val) * 10) / 25;
|
|
1394
1782
|
}
|
|
1395
1783
|
|
|
1396
|
-
fn haze(battle: anytype, player: Player,
|
|
1397
|
-
|
|
1398
|
-
|
|
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);
|
|
1399
1790
|
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
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);
|
|
1403
1796
|
|
|
1404
|
-
|
|
1405
|
-
_ = .{ battle, player, state, options }; // TODO
|
|
1797
|
+
try options.log.clearallboost(.{});
|
|
1406
1798
|
}
|
|
1407
1799
|
|
|
1408
|
-
fn
|
|
1409
|
-
|
|
1410
|
-
|
|
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);
|
|
1411
1804
|
|
|
1412
|
-
|
|
1413
|
-
|
|
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 });
|
|
1414
1820
|
}
|
|
1415
1821
|
|
|
1416
|
-
fn
|
|
1417
|
-
|
|
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);
|
|
1418
1832
|
}
|
|
1419
1833
|
|
|
1420
|
-
fn
|
|
1421
|
-
|
|
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;
|
|
1422
1838
|
}
|
|
1423
1839
|
|
|
1424
|
-
fn
|
|
1425
|
-
|
|
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)});
|
|
1426
1843
|
}
|
|
1427
1844
|
|
|
1428
|
-
fn
|
|
1429
|
-
|
|
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 });
|
|
1430
1873
|
}
|
|
1431
1874
|
|
|
1432
|
-
|
|
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());
|
|
1433
1878
|
|
|
1434
|
-
|
|
1435
|
-
|
|
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 });
|
|
1436
1887
|
}
|
|
1437
1888
|
|
|
1438
|
-
|
|
1439
|
-
|
|
1889
|
+
const MAGNITUDE_POWER = [_]u8{ 10, 30, 50, 70, 90, 110, 150 };
|
|
1890
|
+
|
|
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];
|
|
1440
1895
|
}
|
|
1441
1896
|
|
|
1442
|
-
fn
|
|
1443
|
-
|
|
1897
|
+
pub fn meanLook(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1898
|
+
var foe = battle.foe(player);
|
|
1899
|
+
|
|
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;
|
|
1444
1909
|
}
|
|
1445
1910
|
|
|
1446
|
-
fn
|
|
1447
|
-
|
|
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);
|
|
1448
1929
|
}
|
|
1449
1930
|
|
|
1450
|
-
fn
|
|
1451
|
-
|
|
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 });
|
|
1452
1955
|
}
|
|
1453
1956
|
|
|
1454
|
-
fn mirrorMove(
|
|
1455
|
-
|
|
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);
|
|
1456
1984
|
}
|
|
1457
1985
|
|
|
1458
|
-
fn mist(battle: anytype, player: Player,
|
|
1459
|
-
|
|
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 });
|
|
1460
1993
|
}
|
|
1461
1994
|
|
|
1462
|
-
fn weatherHeal(battle: anytype, player: Player,
|
|
1463
|
-
|
|
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 });
|
|
1464
2008
|
}
|
|
1465
2009
|
|
|
1466
|
-
fn multiHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2010
|
+
pub fn multiHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1467
2011
|
_ = .{ battle, player, state, options }; // TODO
|
|
1468
2012
|
}
|
|
1469
2013
|
|
|
1470
|
-
fn nightmare(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1471
|
-
|
|
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 });
|
|
1472
2031
|
}
|
|
1473
2032
|
|
|
1474
|
-
|
|
1475
|
-
|
|
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 });
|
|
1476
2083
|
}
|
|
1477
2084
|
|
|
1478
|
-
fn
|
|
1479
|
-
|
|
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 });
|
|
1480
2105
|
}
|
|
1481
2106
|
|
|
1482
|
-
fn
|
|
1483
|
-
|
|
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
|
|
1484
2123
|
}
|
|
1485
2124
|
|
|
1486
|
-
fn
|
|
1487
|
-
|
|
1488
|
-
|
|
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
|
+
}
|
|
1489
2129
|
}
|
|
1490
2130
|
|
|
1491
|
-
fn
|
|
1492
|
-
|
|
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
|
|
1493
2147
|
}
|
|
1494
2148
|
|
|
1495
|
-
fn
|
|
1496
|
-
|
|
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
|
|
1497
2186
|
}
|
|
1498
2187
|
|
|
1499
|
-
fn
|
|
1500
|
-
|
|
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
|
|
1501
2203
|
}
|
|
1502
2204
|
|
|
1503
|
-
fn present(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2205
|
+
pub fn present(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1504
2206
|
_ = .{ battle, player, state, options }; // TODO
|
|
1505
2207
|
}
|
|
1506
2208
|
|
|
1507
|
-
fn protect(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2209
|
+
pub fn protect(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1508
2210
|
var side = battle.side(player);
|
|
1509
2211
|
var volatiles = &side.active.volatiles;
|
|
1510
2212
|
|
|
1511
2213
|
const failed = !state.first or volatiles.Substitute or
|
|
1512
2214
|
!try Rolls.protect(battle, player, volatiles.protect, options);
|
|
1513
|
-
if (failed) {
|
|
1514
|
-
try options.log.fail(battle.active(player), .None);
|
|
1515
|
-
return;
|
|
1516
|
-
}
|
|
2215
|
+
if (failed) return options.log.fail(.{ battle.active(player), .None });
|
|
1517
2216
|
|
|
1518
2217
|
volatiles.protect = @max(8, volatiles.protect + 1);
|
|
1519
2218
|
if (state.move == .Endure) {
|
|
@@ -1521,174 +2220,594 @@ pub const Effects = struct {
|
|
|
1521
2220
|
} else {
|
|
1522
2221
|
volatiles.Protect = true;
|
|
1523
2222
|
}
|
|
1524
|
-
try options.log.singleturn(battle.active(player), state.move);
|
|
2223
|
+
try options.log.singleturn(.{ battle.active(player), state.move });
|
|
1525
2224
|
}
|
|
1526
2225
|
|
|
1527
|
-
fn psychUp(battle: anytype, player: Player,
|
|
1528
|
-
|
|
1529
|
-
|
|
2226
|
+
pub fn psychUp(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2227
|
+
var side = battle.side(player);
|
|
2228
|
+
const foe = battle.foe(player);
|
|
1530
2229
|
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
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
|
+
}
|
|
1534
2234
|
|
|
1535
|
-
|
|
1536
|
-
|
|
2235
|
+
side.active.boosts = foe.active.boosts;
|
|
2236
|
+
try options.log.copyboost(.{ battle.active(player), battle.active(player.foe()) });
|
|
1537
2237
|
}
|
|
1538
2238
|
|
|
1539
|
-
fn
|
|
1540
|
-
|
|
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;
|
|
1541
2242
|
}
|
|
1542
2243
|
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
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
|
+
}
|
|
1546
2249
|
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
}
|
|
2250
|
+
pub fn damage(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2251
|
+
const side = battle.side(player);
|
|
1550
2252
|
|
|
1551
|
-
|
|
1552
|
-
|
|
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);
|
|
1553
2260
|
}
|
|
1554
2261
|
|
|
1555
|
-
fn
|
|
1556
|
-
|
|
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
|
+
}
|
|
1557
2280
|
}
|
|
1558
2281
|
|
|
1559
|
-
fn
|
|
1560
|
-
|
|
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()) });
|
|
1561
2293
|
}
|
|
1562
2294
|
|
|
1563
|
-
fn
|
|
1564
|
-
|
|
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);
|
|
1565
2318
|
}
|
|
1566
2319
|
|
|
1567
|
-
|
|
1568
|
-
|
|
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 });
|
|
1569
2356
|
}
|
|
1570
2357
|
|
|
1571
|
-
fn
|
|
1572
|
-
|
|
2358
|
+
pub fn safeguard(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2359
|
+
return screens(battle, player, "Safeguard", "safeguard", options);
|
|
1573
2360
|
}
|
|
1574
2361
|
|
|
1575
|
-
fn
|
|
1576
|
-
|
|
2362
|
+
pub fn reflect(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2363
|
+
return screens(battle, player, "Reflect", "reflect", options);
|
|
1577
2364
|
}
|
|
1578
2365
|
|
|
1579
|
-
fn
|
|
1580
|
-
|
|
2366
|
+
pub fn lightScreen(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2367
|
+
return screens(battle, player, "LightScreen", "light_screen", options);
|
|
1581
2368
|
}
|
|
1582
2369
|
|
|
1583
|
-
fn
|
|
1584
|
-
|
|
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) });
|
|
1585
2386
|
}
|
|
1586
2387
|
|
|
1587
|
-
fn
|
|
1588
|
-
|
|
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
|
+
}
|
|
1589
2400
|
}
|
|
1590
2401
|
|
|
1591
|
-
fn sleep(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1592
|
-
|
|
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 });
|
|
1593
2427
|
}
|
|
1594
2428
|
|
|
1595
|
-
fn sleepTalk(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2429
|
+
pub fn sleepTalk(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1596
2430
|
const side = battle.side(player);
|
|
1597
2431
|
|
|
1598
2432
|
if (!Status.is(side.stored().status, .SLP)) {
|
|
1599
|
-
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
1600
|
-
return;
|
|
2433
|
+
return if (!showdown) try options.log.fail(.{ battle.active(player), .None });
|
|
1601
2434
|
}
|
|
1602
2435
|
|
|
1603
2436
|
var n: u3 = 0;
|
|
1604
2437
|
for (side.active.moves, 0..) |m, i| {
|
|
1605
2438
|
if (state.mslot - 1 != i and Move.get(m.id).extra.sleep_talk) n += 1;
|
|
1606
2439
|
}
|
|
1607
|
-
if (n == 0) {
|
|
1608
|
-
try options.log.fail(battle.active(player), .None);
|
|
1609
|
-
return;
|
|
1610
|
-
}
|
|
2440
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
1611
2441
|
|
|
1612
2442
|
_ = try Rolls.sleepTalk(battle, player, n, state.mslot, options); // TODO
|
|
1613
2443
|
}
|
|
1614
2444
|
|
|
1615
|
-
fn snore(battle: anytype, player: Player, state: *State, options: anytype) !
|
|
1616
|
-
|
|
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;
|
|
1617
2451
|
}
|
|
1618
2452
|
|
|
1619
|
-
fn
|
|
1620
|
-
|
|
1621
|
-
|
|
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 });
|
|
1622
2456
|
|
|
1623
|
-
|
|
1624
|
-
|
|
2457
|
+
foe.conditions.Spikes = true;
|
|
2458
|
+
try options.log.sidestart(.{ player.foe(), protocol.Side.Spikes });
|
|
1625
2459
|
}
|
|
1626
2460
|
|
|
1627
|
-
fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1628
|
-
|
|
1629
|
-
}
|
|
2461
|
+
pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2462
|
+
var foe = battle.foe(player);
|
|
1630
2463
|
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
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
|
+
}
|
|
1634
2468
|
|
|
1635
|
-
|
|
1636
|
-
|
|
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 });
|
|
1637
2493
|
}
|
|
1638
2494
|
|
|
1639
|
-
fn
|
|
1640
|
-
|
|
2495
|
+
pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2496
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
1641
2497
|
}
|
|
1642
2498
|
|
|
1643
|
-
fn
|
|
1644
|
-
|
|
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 });
|
|
1645
2518
|
}
|
|
1646
2519
|
|
|
1647
|
-
fn
|
|
1648
|
-
|
|
2520
|
+
pub fn sunnyDay(battle: anytype, _: Player, _: *State, options: anytype) !void {
|
|
2521
|
+
return weather(battle, .Sun, options);
|
|
1649
2522
|
}
|
|
1650
2523
|
|
|
1651
|
-
fn
|
|
1652
|
-
|
|
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 });
|
|
1653
2528
|
}
|
|
1654
2529
|
|
|
1655
|
-
fn teleport(battle: anytype, player: Player,
|
|
1656
|
-
|
|
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(.{});
|
|
1657
2535
|
}
|
|
1658
2536
|
|
|
1659
|
-
fn thief(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1660
|
-
|
|
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
|
+
});
|
|
1661
2554
|
}
|
|
1662
2555
|
|
|
1663
|
-
fn thrashing(battle: anytype, player: Player,
|
|
1664
|
-
|
|
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;
|
|
1665
2564
|
}
|
|
1666
2565
|
|
|
1667
|
-
fn
|
|
1668
|
-
|
|
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 });
|
|
1669
2602
|
}
|
|
1670
2603
|
|
|
1671
|
-
fn
|
|
1672
|
-
|
|
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);
|
|
1673
2614
|
}
|
|
1674
2615
|
|
|
1675
|
-
fn tripleKick(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2616
|
+
pub fn tripleKick(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1676
2617
|
_ = .{ battle, player, state, options }; // TODO
|
|
1677
2618
|
}
|
|
1678
2619
|
|
|
1679
|
-
|
|
1680
|
-
_
|
|
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
|
+
}
|
|
1681
2639
|
}
|
|
1682
2640
|
|
|
1683
|
-
fn boost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1684
|
-
|
|
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
|
+
}
|
|
1685
2705
|
}
|
|
1686
2706
|
|
|
1687
|
-
fn unboost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1688
|
-
|
|
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
|
+
}
|
|
1689
2793
|
}
|
|
1690
2794
|
};
|
|
1691
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
|
+
|
|
1692
2811
|
fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
1693
2812
|
if (Status.is(status, .PAR)) {
|
|
1694
2813
|
stats.spe = @max(stats.spe / 4, 1);
|
|
@@ -1697,20 +2816,66 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
1697
2816
|
}
|
|
1698
2817
|
}
|
|
1699
2818
|
|
|
1700
|
-
|
|
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 {
|
|
1701
2866
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
1702
2867
|
active.volatiles.Thrashing or active.volatiles.Charging or
|
|
1703
2868
|
active.volatiles.Rollout;
|
|
1704
2869
|
}
|
|
1705
2870
|
|
|
1706
|
-
|
|
2871
|
+
fn convertible(active: ActivePokemon, m: Move) bool {
|
|
1707
2872
|
if (m == .None) return false;
|
|
1708
2873
|
const t = Move.get(m).type;
|
|
1709
2874
|
return t != .@"???" and !active.types.includes(t);
|
|
1710
2875
|
}
|
|
1711
2876
|
|
|
1712
2877
|
pub const Rolls = struct {
|
|
1713
|
-
|
|
2878
|
+
fn speedTie(battle: anytype, options: anytype) !bool {
|
|
1714
2879
|
const p1 = if (options.calc.overridden(.P1, .speed_tie)) |player|
|
|
1715
2880
|
player == .P1
|
|
1716
2881
|
else if (showdown)
|
|
@@ -1722,7 +2887,7 @@ pub const Rolls = struct {
|
|
|
1722
2887
|
return p1;
|
|
1723
2888
|
}
|
|
1724
2889
|
|
|
1725
|
-
|
|
2890
|
+
fn quickClaw(battle: anytype, player: Player, options: anytype) !bool {
|
|
1726
2891
|
const qkc = if (options.calc.overridden(player, .quick_claw)) |val|
|
|
1727
2892
|
val == .true
|
|
1728
2893
|
else if (showdown)
|
|
@@ -1734,7 +2899,7 @@ pub const Rolls = struct {
|
|
|
1734
2899
|
return qkc;
|
|
1735
2900
|
}
|
|
1736
2901
|
|
|
1737
|
-
|
|
2902
|
+
fn hit(battle: anytype, player: Player, accuracy: u8, options: anytype) !bool {
|
|
1738
2903
|
const ok = if (options.calc.overridden(player, .hit)) |val|
|
|
1739
2904
|
val == .true
|
|
1740
2905
|
else if (showdown)
|
|
@@ -1746,7 +2911,7 @@ pub const Rolls = struct {
|
|
|
1746
2911
|
return ok;
|
|
1747
2912
|
}
|
|
1748
2913
|
|
|
1749
|
-
|
|
2914
|
+
fn criticalHit(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
|
|
1750
2915
|
const crit = if (options.calc.overridden(player, .critical_hit)) |val|
|
|
1751
2916
|
val == .true
|
|
1752
2917
|
else if (showdown)
|
|
@@ -1758,7 +2923,7 @@ pub const Rolls = struct {
|
|
|
1758
2923
|
return crit;
|
|
1759
2924
|
}
|
|
1760
2925
|
|
|
1761
|
-
|
|
2926
|
+
fn damage(battle: anytype, player: Player, options: anytype) !u8 {
|
|
1762
2927
|
const roll = if (options.calc.overridden(player, .damage)) |val|
|
|
1763
2928
|
val
|
|
1764
2929
|
else roll: {
|
|
@@ -1774,7 +2939,7 @@ pub const Rolls = struct {
|
|
|
1774
2939
|
return roll;
|
|
1775
2940
|
}
|
|
1776
2941
|
|
|
1777
|
-
|
|
2942
|
+
fn confused(battle: anytype, player: Player, options: anytype) !bool {
|
|
1778
2943
|
const cfz = if (options.calc.overridden(player, .confused)) |val|
|
|
1779
2944
|
val == .true
|
|
1780
2945
|
else if (showdown)
|
|
@@ -1786,7 +2951,7 @@ pub const Rolls = struct {
|
|
|
1786
2951
|
return cfz;
|
|
1787
2952
|
}
|
|
1788
2953
|
|
|
1789
|
-
|
|
2954
|
+
fn attract(battle: anytype, player: Player, options: anytype) !bool {
|
|
1790
2955
|
const cant = if (options.calc.overridden(player, .attract)) |val|
|
|
1791
2956
|
val == .true
|
|
1792
2957
|
else if (showdown)
|
|
@@ -1798,7 +2963,7 @@ pub const Rolls = struct {
|
|
|
1798
2963
|
return cant;
|
|
1799
2964
|
}
|
|
1800
2965
|
|
|
1801
|
-
|
|
2966
|
+
fn paralyzed(battle: anytype, player: Player, options: anytype) !bool {
|
|
1802
2967
|
const par = if (options.calc.overridden(player, .paralyzed)) |val|
|
|
1803
2968
|
val == .true
|
|
1804
2969
|
else if (showdown)
|
|
@@ -1810,7 +2975,7 @@ pub const Rolls = struct {
|
|
|
1810
2975
|
return par;
|
|
1811
2976
|
}
|
|
1812
2977
|
|
|
1813
|
-
|
|
2978
|
+
fn defrost(battle: anytype, player: Player, options: anytype) !bool {
|
|
1814
2979
|
const thaw = if (options.calc.overridden(player, .defrost)) |val|
|
|
1815
2980
|
val == .true
|
|
1816
2981
|
else if (showdown)
|
|
@@ -1822,7 +2987,7 @@ pub const Rolls = struct {
|
|
|
1822
2987
|
return thaw;
|
|
1823
2988
|
}
|
|
1824
2989
|
|
|
1825
|
-
|
|
2990
|
+
fn secondaryChance(battle: anytype, player: Player, rate: u8, options: anytype) !bool {
|
|
1826
2991
|
// GLITCH: 100% secondary chance will still fail 1/256 of the time
|
|
1827
2992
|
const proc = if (options.calc.overridden(player, .secondary_chance)) |val|
|
|
1828
2993
|
val == .true
|
|
@@ -1835,7 +3000,7 @@ pub const Rolls = struct {
|
|
|
1835
3000
|
return proc;
|
|
1836
3001
|
}
|
|
1837
3002
|
|
|
1838
|
-
|
|
3003
|
+
fn item(battle: anytype, player: Player, options: anytype) !bool {
|
|
1839
3004
|
const proc = if (options.calc.overridden(player, .item)) |val|
|
|
1840
3005
|
val == .true
|
|
1841
3006
|
else if (showdown)
|
|
@@ -1847,23 +3012,23 @@ pub const Rolls = struct {
|
|
|
1847
3012
|
return proc;
|
|
1848
3013
|
}
|
|
1849
3014
|
|
|
1850
|
-
|
|
3015
|
+
fn triAttack(battle: anytype, player: Player, options: anytype) !TriAttack {
|
|
1851
3016
|
const status: TriAttack = if (options.calc.overridden(player, .tri_attack)) |val|
|
|
1852
|
-
val
|
|
3017
|
+
@enumFromInt(@intFromEnum(val) - 1)
|
|
1853
3018
|
else if (showdown)
|
|
1854
|
-
@enumFromInt(battle.rng.range(
|
|
3019
|
+
@enumFromInt(battle.rng.range(u2, 0, 2))
|
|
1855
3020
|
else loop: {
|
|
1856
3021
|
while (true) {
|
|
1857
|
-
const r = std.math.rotr(u8,
|
|
3022
|
+
const r = std.math.rotr(u8, battle.rng.next(), 4) & 3;
|
|
1858
3023
|
if (r != 0) break :loop @enumFromInt(r - 1);
|
|
1859
3024
|
}
|
|
1860
3025
|
};
|
|
1861
3026
|
|
|
1862
3027
|
try options.chance.triAttack(player, status);
|
|
1863
|
-
return status
|
|
3028
|
+
return status;
|
|
1864
3029
|
}
|
|
1865
3030
|
|
|
1866
|
-
|
|
3031
|
+
fn present(battle: anytype, player: Player, options: anytype) !u8 {
|
|
1867
3032
|
const power = if (options.calc.overridden(player, .present)) |val|
|
|
1868
3033
|
(val - 1) * 40
|
|
1869
3034
|
else if (showdown) power: {
|
|
@@ -1885,8 +3050,8 @@ pub const Rolls = struct {
|
|
|
1885
3050
|
return power;
|
|
1886
3051
|
}
|
|
1887
3052
|
|
|
1888
|
-
|
|
1889
|
-
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|
|
|
1890
3055
|
val + 3
|
|
1891
3056
|
else if (showdown) num: {
|
|
1892
3057
|
const r = battle.rng.range(u8, 0, 100);
|
|
@@ -1913,7 +3078,7 @@ pub const Rolls = struct {
|
|
|
1913
3078
|
return num;
|
|
1914
3079
|
}
|
|
1915
3080
|
|
|
1916
|
-
|
|
3081
|
+
fn tripleKick(battle: anytype, player: Player, options: anytype) !u2 {
|
|
1917
3082
|
const hits = if (options.calc.overridden(player, .triple_kick)) |val|
|
|
1918
3083
|
val
|
|
1919
3084
|
else if (showdown)
|
|
@@ -1930,13 +3095,12 @@ pub const Rolls = struct {
|
|
|
1930
3095
|
return hits;
|
|
1931
3096
|
}
|
|
1932
3097
|
|
|
1933
|
-
|
|
1934
|
-
|
|
3098
|
+
fn spite(battle: anytype, player: Player, options: anytype) !u3 {
|
|
3099
|
+
assert(!showdown);
|
|
3100
|
+
const pp: u3 = if (options.calc.overridden(player, .spite)) |val|
|
|
1935
3101
|
val + 1
|
|
1936
|
-
else if (showdown)
|
|
1937
|
-
battle.rng.range(u8, 2, 6)
|
|
1938
3102
|
else
|
|
1939
|
-
(battle.rng.next() & 3) + 2;
|
|
3103
|
+
@intCast((battle.rng.next() & 3) + 2);
|
|
1940
3104
|
|
|
1941
3105
|
assert(pp >= 2 and pp <= 5);
|
|
1942
3106
|
try options.chance.spite(player, pp);
|
|
@@ -1946,7 +3110,7 @@ pub const Rolls = struct {
|
|
|
1946
3110
|
// Conversion 2 can at most choose between 7 types (vs. a Grass-type attack)
|
|
1947
3111
|
var CONVERSION_2: [7]Type = [_]Type{.@"???"} ** 7;
|
|
1948
3112
|
|
|
1949
|
-
|
|
3113
|
+
fn conversion2(battle: anytype, player: Player, mtype: Type, options: anytype) !Type {
|
|
1950
3114
|
var i: u8 = 0;
|
|
1951
3115
|
const neutral = @intFromEnum(Effectiveness.Neutral);
|
|
1952
3116
|
assert(mtype != .@"???");
|
|
@@ -1980,7 +3144,7 @@ pub const Rolls = struct {
|
|
|
1980
3144
|
return ty;
|
|
1981
3145
|
}
|
|
1982
3146
|
|
|
1983
|
-
|
|
3147
|
+
fn protect(battle: anytype, player: Player, count: u8, options: anytype) !bool {
|
|
1984
3148
|
assert(count <= 8);
|
|
1985
3149
|
const num = if (count == 8) 0 else @as(u8, 255) >> @intCast(count);
|
|
1986
3150
|
|
|
@@ -2074,7 +3238,7 @@ pub const Rolls = struct {
|
|
|
2074
3238
|
return slot;
|
|
2075
3239
|
}
|
|
2076
3240
|
|
|
2077
|
-
|
|
3241
|
+
fn forceSwitch(
|
|
2078
3242
|
battle: anytype,
|
|
2079
3243
|
player: Player,
|
|
2080
3244
|
slots: []u4,
|
|
@@ -2112,7 +3276,7 @@ pub const Rolls = struct {
|
|
|
2112
3276
|
|
|
2113
3277
|
const DISTRIBUTION = [_]u3{ 2, 2, 2, 3, 3, 3, 4, 5 };
|
|
2114
3278
|
|
|
2115
|
-
|
|
3279
|
+
fn multiHit(battle: anytype, player: Player, options: anytype) !u3 {
|
|
2116
3280
|
const n: u3 = if (options.calc.overridden(player, .multi_hit)) |val|
|
|
2117
3281
|
@intCast(val)
|
|
2118
3282
|
else if (showdown)
|
|
@@ -2127,30 +3291,30 @@ pub const Rolls = struct {
|
|
|
2127
3291
|
return n;
|
|
2128
3292
|
}
|
|
2129
3293
|
|
|
2130
|
-
|
|
3294
|
+
fn psywave(battle: anytype, player: Player, max: u8, options: anytype) !u8 {
|
|
2131
3295
|
const power = if (options.calc.overridden(player, .psywave)) |val|
|
|
2132
|
-
val
|
|
3296
|
+
val
|
|
2133
3297
|
else if (showdown)
|
|
2134
|
-
battle.rng.range(u8,
|
|
3298
|
+
battle.rng.range(u8, 1, max)
|
|
2135
3299
|
else power: {
|
|
2136
3300
|
while (true) {
|
|
2137
3301
|
const r = battle.rng.next();
|
|
2138
|
-
if (r < max) break :power r;
|
|
3302
|
+
if (r != 0 and r < max) break :power r;
|
|
2139
3303
|
}
|
|
2140
3304
|
};
|
|
2141
3305
|
|
|
2142
|
-
assert(power < max);
|
|
3306
|
+
assert(power != 0 and power < max);
|
|
2143
3307
|
try options.chance.psywave(player, power, max);
|
|
2144
3308
|
return power;
|
|
2145
3309
|
}
|
|
2146
3310
|
|
|
2147
|
-
|
|
2148
|
-
const moves = battle.
|
|
3311
|
+
fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
3312
|
+
const moves = battle.side(player).active.moves;
|
|
2149
3313
|
|
|
2150
3314
|
var n: u2 = 0;
|
|
2151
3315
|
for (moves) |m| {
|
|
2152
|
-
if (m == .None) break;
|
|
2153
|
-
if (Move.get(m).metronome) n += 1;
|
|
3316
|
+
if (m.id == .None) break;
|
|
3317
|
+
if (Move.get(m.id).extra.metronome) n += 1;
|
|
2154
3318
|
}
|
|
2155
3319
|
|
|
2156
3320
|
// TODO: consider throwing error instead of rerolling?
|
|
@@ -2161,10 +3325,11 @@ pub const Rolls = struct {
|
|
|
2161
3325
|
|
|
2162
3326
|
const move: Move = overridden orelse move: {
|
|
2163
3327
|
if (showdown) {
|
|
2164
|
-
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)));
|
|
2165
3329
|
for (moves) |m| {
|
|
2166
|
-
if (m == .None
|
|
2167
|
-
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;
|
|
2168
3333
|
}
|
|
2169
3334
|
break :move Move.METRONOME[r];
|
|
2170
3335
|
} else {
|
|
@@ -2179,12 +3344,12 @@ pub const Rolls = struct {
|
|
|
2179
3344
|
}
|
|
2180
3345
|
};
|
|
2181
3346
|
|
|
2182
|
-
assert(Move.get(move).metronome);
|
|
3347
|
+
assert(Move.get(move).extra.metronome);
|
|
2183
3348
|
try options.chance.metronome(player, move, n);
|
|
2184
3349
|
return move;
|
|
2185
3350
|
}
|
|
2186
3351
|
|
|
2187
|
-
|
|
3352
|
+
fn sleepDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2188
3353
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2189
3354
|
@intCast(val)
|
|
2190
3355
|
else if (showdown)
|
|
@@ -2201,7 +3366,7 @@ pub const Rolls = struct {
|
|
|
2201
3366
|
return duration + 1;
|
|
2202
3367
|
}
|
|
2203
3368
|
|
|
2204
|
-
|
|
3369
|
+
fn confusionDuration(battle: anytype, player: Player, self: bool, options: anytype) u3 {
|
|
2205
3370
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2206
3371
|
@intCast(val)
|
|
2207
3372
|
else if (showdown)
|
|
@@ -2215,7 +3380,7 @@ pub const Rolls = struct {
|
|
|
2215
3380
|
return duration;
|
|
2216
3381
|
}
|
|
2217
3382
|
|
|
2218
|
-
|
|
3383
|
+
fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
|
|
2219
3384
|
const duration: u4 = if (options.calc.overridden(player, .duration)) |val|
|
|
2220
3385
|
@intCast(val)
|
|
2221
3386
|
else if (showdown)
|
|
@@ -2233,7 +3398,7 @@ pub const Rolls = struct {
|
|
|
2233
3398
|
return duration;
|
|
2234
3399
|
}
|
|
2235
3400
|
|
|
2236
|
-
|
|
3401
|
+
fn attackingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2237
3402
|
const duration: u3 = if (options.calc.overridden(player, .duration)) |val|
|
|
2238
3403
|
@intCast(val)
|
|
2239
3404
|
else if (showdown)
|
|
@@ -2246,7 +3411,7 @@ pub const Rolls = struct {
|
|
|
2246
3411
|
return duration;
|
|
2247
3412
|
}
|
|
2248
3413
|
|
|
2249
|
-
|
|
3414
|
+
fn bindingDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2250
3415
|
const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
|
|
2251
3416
|
@intCast(val)
|
|
2252
3417
|
else if (showdown)
|
|
@@ -2261,7 +3426,7 @@ pub const Rolls = struct {
|
|
|
2261
3426
|
}
|
|
2262
3427
|
|
|
2263
3428
|
// TODO: consider sharing implementation with bindingDuration
|
|
2264
|
-
|
|
3429
|
+
fn encoreDuration(battle: anytype, player: Player, options: anytype) u3 {
|
|
2265
3430
|
const duration: u3 = (if (options.calc.overridden(player, .duration)) |val|
|
|
2266
3431
|
@intCast(val)
|
|
2267
3432
|
else if (showdown)
|
|
@@ -2383,7 +3548,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
2383
3548
|
.Move => {
|
|
2384
3549
|
const side = battle.side(player);
|
|
2385
3550
|
|
|
2386
|
-
|
|
3551
|
+
const active = &side.active;
|
|
2387
3552
|
const stored = side.stored();
|
|
2388
3553
|
|
|
2389
3554
|
// While players are not given any input options on the cartridge in these cases,
|
|
@@ -2403,7 +3568,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
2403
3568
|
}
|
|
2404
3569
|
|
|
2405
3570
|
var slot: u4 = 2;
|
|
2406
|
-
while (!active.volatiles.
|
|
3571
|
+
while (!active.volatiles.Trapped and slot <= 6) : (slot += 1) {
|
|
2407
3572
|
const id = side.order[slot - 1];
|
|
2408
3573
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
2409
3574
|
out[n] = .{ .type = .Switch, .data = slot };
|