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