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