@pkmn/engine 0.1.0-dev.89cc1c98 → 0.1.0-dev.8b192003
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -3
- package/README.md +44 -19
- package/build/pkg/addon.js +6 -7
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/data.d.ts +4 -4
- package/build/pkg/data.js.map +1 -1
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/index.d.ts +2 -1
- package/build/pkg/index.js.map +1 -1
- package/build/pkg/protocol.js +5 -5
- package/build/pkg/protocol.js.map +1 -1
- package/build/test/display.js +2 -2
- package/build/test/display.js.map +1 -1
- package/build/tools/debug.js +5 -4
- package/build/tools/debug.js.map +1 -1
- package/build.zig +100 -102
- package/package.json +31 -23
- package/src/bin/install-pkmn-engine +26 -18
- package/src/lib/common/data.zig +4 -4
- package/src/lib/common/debug.zig +2 -2
- package/src/lib/common/optional.zig +1 -1
- package/src/lib/common/options.zig +2 -0
- package/src/lib/common/protocol.zig +393 -396
- package/src/lib/common/rational.zig +109 -15
- package/src/lib/common/rng.zig +20 -11
- package/src/lib/gen1/calc.zig +478 -92
- package/src/lib/gen1/chance.zig +195 -428
- package/src/lib/gen1/data/species.zig +1 -1
- package/src/lib/gen1/data.zig +21 -21
- package/src/lib/gen1/helpers.zig +88 -534
- package/src/lib/gen1/mechanics.zig +377 -360
- package/src/lib/gen2/chance.zig +25 -457
- package/src/lib/gen2/data.zig +13 -11
- package/src/lib/gen2/generated.zig +27 -78
- package/src/lib/gen2/helpers.zig +37 -7
- package/src/lib/gen2/mechanics.zig +1022 -456
- package/src/lib/{binding/node.zig → node.zig} +9 -12
- package/src/lib/pkmn.zig +27 -12
- package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
- package/src/test/display/pkmn.html.tmpl +5 -5
- package/src/lib/gen1/README.md +0 -894
- package/src/lib/gen2/README.md +0 -330
|
@@ -26,9 +26,9 @@ const Result = common.Result;
|
|
|
26
26
|
|
|
27
27
|
const Activate = protocol.Activate;
|
|
28
28
|
const Boost = protocol.Boost;
|
|
29
|
-
const Cant = protocol.Cant;
|
|
30
29
|
const Damage = protocol.Damage;
|
|
31
30
|
const End = protocol.End;
|
|
31
|
+
const Fail = protocol.Fail;
|
|
32
32
|
const Heal = protocol.Heal;
|
|
33
33
|
const Start = protocol.Start;
|
|
34
34
|
|
|
@@ -37,6 +37,7 @@ const Gen12 = rng.Gen12;
|
|
|
37
37
|
const PointerType = util.PointerType;
|
|
38
38
|
|
|
39
39
|
const ActivePokemon = data.ActivePokemon;
|
|
40
|
+
const Boosts = data.Boosts;
|
|
40
41
|
const Effectiveness = data.Effectiveness;
|
|
41
42
|
const Item = data.Item;
|
|
42
43
|
const Move = data.Move;
|
|
@@ -50,6 +51,7 @@ const TriAttack = data.TriAttack;
|
|
|
50
51
|
const Type = data.Type;
|
|
51
52
|
const Types = data.Types;
|
|
52
53
|
const Volatiles = data.Volatiles;
|
|
54
|
+
const Weather = data.Weather;
|
|
53
55
|
|
|
54
56
|
// zig fmt: off
|
|
55
57
|
const STAT_BOOSTS = &[_][2]u8{
|
|
@@ -95,10 +97,13 @@ const CRITICAL_HITS = &[_]u8{
|
|
|
95
97
|
|
|
96
98
|
const MAX_STAT_VALUE = 999;
|
|
97
99
|
|
|
100
|
+
const STATS = [_][]const u8{ "atk", "def", "spe", "spa", "spd" };
|
|
101
|
+
|
|
98
102
|
pub const State = struct {
|
|
99
103
|
damage: u16 = 0, // wCurDamage
|
|
100
104
|
effectiveness: u16 = Effectiveness.neutral, // wTypeModifier
|
|
101
105
|
bp: u8, // wPlayerMoveStructPower
|
|
106
|
+
type: Type, // BATTLE_VARS_MOVE_TYPE
|
|
102
107
|
accuracy: u8, // wEnemyMoveStruct + MOVE_ACC
|
|
103
108
|
move: Move, // wCurPlayerMove
|
|
104
109
|
from: Move = .None,
|
|
@@ -110,7 +115,7 @@ pub const State = struct {
|
|
|
110
115
|
proc: bool = true, // wEffectFailed
|
|
111
116
|
|
|
112
117
|
comptime {
|
|
113
|
-
assert(@sizeOf(State) ==
|
|
118
|
+
assert(@sizeOf(State) == 16);
|
|
114
119
|
}
|
|
115
120
|
|
|
116
121
|
pub fn immune(self: *State) bool {
|
|
@@ -135,11 +140,11 @@ fn start(battle: anytype, options: anytype) !Result {
|
|
|
135
140
|
const p1 = battle.side(.P1);
|
|
136
141
|
const p2 = battle.side(.P2);
|
|
137
142
|
|
|
138
|
-
|
|
143
|
+
const p1_slot = findFirstAlive(p1);
|
|
139
144
|
assert(!showdown or p1_slot == 1);
|
|
140
145
|
if (p1_slot == 0) return if (findFirstAlive(p2) == 0) Result.Tie else Result.Lose;
|
|
141
146
|
|
|
142
|
-
|
|
147
|
+
const p2_slot = findFirstAlive(p2);
|
|
143
148
|
assert(!showdown or p2_slot == 1);
|
|
144
149
|
if (p2_slot == 0) return Result.Win;
|
|
145
150
|
|
|
@@ -247,15 +252,14 @@ fn switchIn(battle: anytype, player: Player, slot: u8, kind: SwitchIn, options:
|
|
|
247
252
|
statusModify(incoming.status, &active.stats);
|
|
248
253
|
|
|
249
254
|
if (kind == .Drag) {
|
|
250
|
-
try options.log.drag(battle.active(player), incoming);
|
|
255
|
+
try options.log.drag(.{ battle.active(player), incoming });
|
|
251
256
|
} else {
|
|
252
|
-
try options.log.switched(battle.active(player), incoming);
|
|
257
|
+
try options.log.switched(.{ battle.active(player), incoming });
|
|
253
258
|
}
|
|
254
|
-
options.chance.switched(player, side.pokemon[0].position, out);
|
|
255
259
|
|
|
256
260
|
if (side.conditions.Spikes and !active.types.includes(.Flying)) {
|
|
257
261
|
incoming.hp -|= @max(incoming.stats.hp / 8, 1);
|
|
258
|
-
try options.log.damage(battle.active(player), incoming, .Spikes);
|
|
262
|
+
try options.log.damage(.{ battle.active(player), incoming, .Spikes });
|
|
259
263
|
}
|
|
260
264
|
}
|
|
261
265
|
|
|
@@ -269,7 +273,13 @@ fn turnOrder(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Player
|
|
|
269
273
|
// TODO: add to pending and only commit roll on Pokémon Showdown if roll is used
|
|
270
274
|
const qkc = showdown and try Rolls.quickClaw(battle, .P1, options);
|
|
271
275
|
|
|
272
|
-
if ((c1.type == .Switch) != (c2.type == .Switch))
|
|
276
|
+
if ((c1.type == .Switch) != (c2.type == .Switch)) {
|
|
277
|
+
const m1 = .Pound; // TODO battle.side(.P1).last_selected_move;
|
|
278
|
+
const m2 = .Pound; // TODO battle.side(.P2).last_selected_move;
|
|
279
|
+
return if (c1.type == .Switch)
|
|
280
|
+
if (m1 == .Pursuit) .P2 else .P1
|
|
281
|
+
else if (m2 == .Pursuit) .P1 else .P2;
|
|
282
|
+
}
|
|
273
283
|
|
|
274
284
|
// https://www.smogon.com/forums/threads/adv-switch-priority.3622189/
|
|
275
285
|
// > In Gen 1 it's irrelevant [which player switches first] because switches happen instantly on
|
|
@@ -392,14 +402,22 @@ fn executeMove(
|
|
|
392
402
|
|
|
393
403
|
assert(choice.type == .Move);
|
|
394
404
|
var mslot: u4 = @intCast(choice.data);
|
|
405
|
+
_ = &mslot;
|
|
395
406
|
var move = Move.Pound; // TODO
|
|
407
|
+
_ = &move;
|
|
396
408
|
|
|
397
409
|
if (!try beforeMove(battle, player, move, options)) return null;
|
|
398
410
|
|
|
399
411
|
// FIXME: need damage from doTurn
|
|
400
412
|
const m = Move.get(move);
|
|
401
|
-
var state: State =
|
|
402
|
-
.
|
|
413
|
+
var state: State = .{
|
|
414
|
+
.move = move,
|
|
415
|
+
.mslot = mslot,
|
|
416
|
+
.first = first,
|
|
417
|
+
.accuracy = m.accuracy,
|
|
418
|
+
.bp = m.bp,
|
|
419
|
+
.type = m.type,
|
|
420
|
+
};
|
|
403
421
|
try generated.runMove(battle, player, &state, options);
|
|
404
422
|
return null;
|
|
405
423
|
}
|
|
@@ -416,31 +434,22 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
416
434
|
if (volatiles.Recharging) {
|
|
417
435
|
volatiles.Recharging = false;
|
|
418
436
|
resetCant(volatiles);
|
|
419
|
-
try log.cant(ident, .Recharge);
|
|
437
|
+
try log.cant(.{ ident, .Recharge });
|
|
420
438
|
return false;
|
|
421
439
|
}
|
|
422
440
|
|
|
423
441
|
if (Status.is(stored.status, .SLP)) slp: {
|
|
424
442
|
const before = stored.status;
|
|
425
443
|
// Even if the EXT bit is set this will still correctly modify the sleep duration
|
|
426
|
-
// if (options.calc.modify(player, .sleep)) |extend| {
|
|
427
|
-
// if (!extend) stored.status = 0;
|
|
428
|
-
// } else {
|
|
429
444
|
stored.status -= 1;
|
|
430
|
-
// }
|
|
431
445
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
try options.chance.sleep(player, duration);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
if (duration == 0) {
|
|
438
|
-
try log.curestatus(ident, before, .Message);
|
|
446
|
+
if (Status.duration(stored.status) == 0) {
|
|
447
|
+
try log.curestatus(.{ ident, before, .Message });
|
|
439
448
|
stored.status = 0; // clears EXT if present
|
|
440
449
|
resetCant(volatiles);
|
|
441
450
|
volatiles.Nightmare = false;
|
|
442
451
|
} else {
|
|
443
|
-
try log.cant(ident, .Sleep);
|
|
452
|
+
try log.cant(.{ ident, .Sleep });
|
|
444
453
|
if (move == .Snore or move == .SleepTalk) break :slp;
|
|
445
454
|
resetCant(volatiles);
|
|
446
455
|
return false;
|
|
@@ -449,7 +458,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
449
458
|
|
|
450
459
|
if (Status.is(stored.status, .FRZ) and !(move == .FlameWheel or move == .SacredFire)) {
|
|
451
460
|
resetCant(volatiles);
|
|
452
|
-
try log.cant(ident, .Freeze);
|
|
461
|
+
try log.cant(.{ ident, .Freeze });
|
|
453
462
|
return false;
|
|
454
463
|
}
|
|
455
464
|
|
|
@@ -459,39 +468,28 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
459
468
|
// if (!showdown) volatiles.Flinch = false;
|
|
460
469
|
volatiles.Flinch = false;
|
|
461
470
|
resetCant(volatiles);
|
|
462
|
-
try log.cant(ident, .Flinch);
|
|
471
|
+
try log.cant(.{ ident, .Flinch });
|
|
463
472
|
return false;
|
|
464
473
|
}
|
|
465
474
|
|
|
466
475
|
if (volatiles.disable.duration > 0) {
|
|
467
|
-
// if (options.calc.modify(player, .disable)) |extend| {
|
|
468
|
-
// if (!extend) volatiles.disable.duration = 0;
|
|
469
|
-
// } else {
|
|
470
476
|
volatiles.disable.duration -= 1;
|
|
471
|
-
// }
|
|
472
|
-
try options.chance.disable(player, volatiles.disable.duration);
|
|
473
477
|
|
|
474
478
|
if (volatiles.disable.duration == 0) {
|
|
475
479
|
volatiles.disable.move = 0;
|
|
476
|
-
try log.end(ident, .Disable);
|
|
480
|
+
try log.end(.{ ident, .Disable });
|
|
477
481
|
}
|
|
478
482
|
}
|
|
479
483
|
|
|
480
484
|
if (volatiles.Confusion) {
|
|
481
485
|
assert(volatiles.confusion > 0);
|
|
482
|
-
|
|
483
|
-
// if (options.calc.modify(player, .confusion)) |extend| {
|
|
484
|
-
// if (!extend) volatiles.confusion = 0;
|
|
485
|
-
// } else {
|
|
486
486
|
volatiles.confusion -= 1;
|
|
487
|
-
// }
|
|
488
|
-
try options.chance.confusion(player, volatiles.confusion);
|
|
489
487
|
|
|
490
488
|
if (volatiles.confusion == 0) {
|
|
491
489
|
volatiles.Confusion = false;
|
|
492
|
-
try log.end(ident, .Confusion);
|
|
490
|
+
try log.end(.{ ident, .Confusion });
|
|
493
491
|
} else {
|
|
494
|
-
try log.activate(ident, .Confusion);
|
|
492
|
+
try log.activate(.{ ident, .Confusion });
|
|
495
493
|
|
|
496
494
|
if (try Rolls.confused(battle, player, options)) {
|
|
497
495
|
volatiles.BeatUp = false;
|
|
@@ -506,11 +504,11 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
506
504
|
}
|
|
507
505
|
|
|
508
506
|
if (volatiles.Attract) {
|
|
509
|
-
try log.activate(ident, .Attract);
|
|
507
|
+
try log.activate(.{ ident, .Attract });
|
|
510
508
|
|
|
511
509
|
if (try Rolls.attract(battle, player, options)) {
|
|
512
510
|
resetCant(volatiles);
|
|
513
|
-
try log.cant(ident, .Attract);
|
|
511
|
+
try log.cant(.{ ident, .Attract });
|
|
514
512
|
return false;
|
|
515
513
|
}
|
|
516
514
|
}
|
|
@@ -521,14 +519,14 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
|
|
|
521
519
|
if (m != .None and m == move) {
|
|
522
520
|
side.active.volatiles.Charging = false;
|
|
523
521
|
resetCant(volatiles);
|
|
524
|
-
try options.log.
|
|
522
|
+
try options.log.cant(.{ ident, .Disable, move });
|
|
525
523
|
return false;
|
|
526
524
|
}
|
|
527
525
|
}
|
|
528
526
|
|
|
529
527
|
if (Status.is(stored.status, .PAR) and try Rolls.paralyzed(battle, player, options)) {
|
|
530
528
|
resetCant(volatiles);
|
|
531
|
-
try log.cant(ident, .Paralysis);
|
|
529
|
+
try log.cant(.{ ident, .Paralysis });
|
|
532
530
|
return false;
|
|
533
531
|
}
|
|
534
532
|
|
|
@@ -548,11 +546,11 @@ fn resetCant(volatiles: *align(8) Volatiles) void {
|
|
|
548
546
|
|
|
549
547
|
pub fn canMove(battle: anytype, player: Player, state: *State, options: anytype) !bool {
|
|
550
548
|
var side = battle.side(player);
|
|
551
|
-
|
|
549
|
+
const volatiles = &side.active.volatiles;
|
|
552
550
|
|
|
553
551
|
// FIXME: pass in target to avoid lookup?
|
|
554
552
|
const target = if (Move.get(state.move).target == .Self) player else player.foe();
|
|
555
|
-
try options.log.
|
|
553
|
+
try options.log.move(.{ battle.active(player), state.move, battle.active(target), state.from });
|
|
556
554
|
|
|
557
555
|
const charging = false; // TODO charging uses doUse
|
|
558
556
|
const skip_pp = charging or state.move == .Struggle or
|
|
@@ -631,7 +629,7 @@ pub fn afterMove(
|
|
|
631
629
|
|
|
632
630
|
pub fn checkCriticalHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
633
631
|
const move = Move.get(state.move);
|
|
634
|
-
if (
|
|
632
|
+
if (state.bp == 0) return;
|
|
635
633
|
|
|
636
634
|
const side = battle.side(player);
|
|
637
635
|
const pokemon = side.stored();
|
|
@@ -669,31 +667,30 @@ pub fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype)
|
|
|
669
667
|
const side = battle.side(player);
|
|
670
668
|
const foe = battle.foe(player);
|
|
671
669
|
const types = foe.active.types;
|
|
672
|
-
const move = Move.get(state.move);
|
|
673
670
|
|
|
674
671
|
var d = state.damage;
|
|
675
|
-
if ((
|
|
676
|
-
(
|
|
672
|
+
if ((state.type == .Water and battle.field.weather == .Rain) or
|
|
673
|
+
(state.type == .Fire and battle.field.weather == .Sun))
|
|
677
674
|
{
|
|
678
675
|
d = d *% @intFromEnum(Effectiveness.Super) / 10;
|
|
679
|
-
} else if ((
|
|
680
|
-
((
|
|
676
|
+
} else if ((state.type == .Water and battle.field.weather == .Sun) or
|
|
677
|
+
((state.type == .Fire or state.move == .SolarBeam) and battle.field.weather == .Rain))
|
|
681
678
|
{
|
|
682
679
|
d = d *% @intFromEnum(Effectiveness.Resisted) / 10;
|
|
683
680
|
}
|
|
684
681
|
|
|
685
|
-
if (side.active.types.includes(
|
|
682
|
+
if (side.active.types.includes(state.type)) d +%= d / 2;
|
|
686
683
|
|
|
687
684
|
const neutral = @intFromEnum(Effectiveness.Neutral);
|
|
688
|
-
const foresight = foe.active.volatiles.Foresight and FORESIGHT.includes(
|
|
685
|
+
const foresight = foe.active.volatiles.Foresight and FORESIGHT.includes(state.type);
|
|
689
686
|
const eff1: u16 = if (foresight and types.type1 == .Ghost)
|
|
690
687
|
neutral
|
|
691
688
|
else
|
|
692
|
-
@intFromEnum(
|
|
689
|
+
@intFromEnum(state.type.effectiveness(types.type1));
|
|
693
690
|
const eff2: u16 = if (foresight and types.type2 == .Ghost)
|
|
694
691
|
neutral
|
|
695
692
|
else
|
|
696
|
-
@intFromEnum(
|
|
693
|
+
@intFromEnum(state.type.effectiveness(types.type2));
|
|
697
694
|
|
|
698
695
|
// Type effectiveness matchup precedence only matters with (NVE, SE)
|
|
699
696
|
if (!showdown and (eff1 + eff2) == Effectiveness.mismatch and
|
|
@@ -775,9 +772,15 @@ pub fn checkHit(
|
|
|
775
772
|
return;
|
|
776
773
|
}
|
|
777
774
|
}
|
|
778
|
-
if (move.effect == .Thunder
|
|
779
|
-
|
|
780
|
-
|
|
775
|
+
if (move.effect == .Thunder) {
|
|
776
|
+
if (battle.field.weather == .Rain) {
|
|
777
|
+
state.miss = false;
|
|
778
|
+
return;
|
|
779
|
+
} else if (battle.field.weather == .Sun) {
|
|
780
|
+
// This latter part gets applied in a separate Thunder accuracy handler
|
|
781
|
+
// on the cartridge but it makes more sense to just do it here
|
|
782
|
+
state.accuracy = Gen12.percent(50) + 1;
|
|
783
|
+
}
|
|
781
784
|
}
|
|
782
785
|
if (move.effect == .AlwaysHit) {
|
|
783
786
|
state.miss = false;
|
|
@@ -827,13 +830,13 @@ fn checkFaint(
|
|
|
827
830
|
if (foe_fainted) try faint(battle, player.foe(), !more, options);
|
|
828
831
|
|
|
829
832
|
if (player_out and foe_out) {
|
|
830
|
-
try options.log.tie();
|
|
833
|
+
try options.log.tie(.{});
|
|
831
834
|
return Result.Tie;
|
|
832
835
|
} else if (player_out) {
|
|
833
|
-
try options.log.win(player.foe());
|
|
836
|
+
try options.log.win(.{player.foe()});
|
|
834
837
|
return if (player == .P1) Result.Lose else Result.Win;
|
|
835
838
|
} else if (foe_out) {
|
|
836
|
-
try options.log.win(player);
|
|
839
|
+
try options.log.win(.{player});
|
|
837
840
|
return if (player == .P1) Result.Win else Result.Lose;
|
|
838
841
|
}
|
|
839
842
|
|
|
@@ -863,19 +866,19 @@ fn faint(battle: anytype, player: Player, done: bool, options: anytype) !void {
|
|
|
863
866
|
side.stored().stats.spe;
|
|
864
867
|
}
|
|
865
868
|
|
|
866
|
-
try options.log.faint(battle.active(player), done);
|
|
869
|
+
try options.log.faint(.{ battle.active(player), done });
|
|
867
870
|
options.calc.capped(player);
|
|
868
871
|
}
|
|
869
872
|
|
|
870
873
|
// TODO: see if we can move *before* applyDamage and include failuretext logic
|
|
871
874
|
pub fn reportOutcome(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
872
875
|
const foe_ident = battle.active(player.foe());
|
|
873
|
-
if (state.crit) try options.log.crit(foe_ident);
|
|
876
|
+
if (state.crit) try options.log.crit(.{foe_ident});
|
|
874
877
|
if (!state.immune()) {
|
|
875
878
|
if (state.effectiveness > Effectiveness.neutral) {
|
|
876
|
-
try options.log.supereffective(foe_ident);
|
|
879
|
+
try options.log.supereffective(.{foe_ident});
|
|
877
880
|
} else if (state.effectiveness < Effectiveness.neutral) {
|
|
878
|
-
try options.log.resisted(foe_ident);
|
|
881
|
+
try options.log.resisted(.{foe_ident});
|
|
879
882
|
}
|
|
880
883
|
}
|
|
881
884
|
}
|
|
@@ -908,8 +911,8 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !bool {
|
|
|
908
911
|
}
|
|
909
912
|
|
|
910
913
|
stored.hp -|= damage;
|
|
911
|
-
// Pokémon Showdown uses
|
|
912
|
-
try options.log.damage(ident, stored, if (brn) Damage.Burn else Damage.Poison);
|
|
914
|
+
// Pokémon Showdown uses damage [of] here but its not relevant in Generation II
|
|
915
|
+
try options.log.damage(.{ ident, stored, if (brn) Damage.Burn else Damage.Poison });
|
|
913
916
|
if (stored.hp == 0) return true;
|
|
914
917
|
}
|
|
915
918
|
|
|
@@ -925,27 +928,27 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !bool {
|
|
|
925
928
|
|
|
926
929
|
const damage = @max(stored.stats.hp / 8, 1);
|
|
927
930
|
stored.hp -|= damage;
|
|
928
|
-
// As above, Pokémon Showdown uses
|
|
929
|
-
try options.log.damage(ident, stored, .LeechSeed);
|
|
931
|
+
// As above, Pokémon Showdown uses damage [of] but its not relevant
|
|
932
|
+
try options.log.damage(.{ ident, stored, .LeechSeed });
|
|
930
933
|
|
|
931
934
|
const before = foe_stored.hp;
|
|
932
935
|
foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
|
|
933
936
|
// Pokémon Showdown uses the less specific heal here instead of drain... because reasons?
|
|
934
|
-
if (foe_stored.hp > before) try options.log.heal(foe_ident, foe_stored, .Silent);
|
|
937
|
+
if (foe_stored.hp > before) try options.log.heal(.{ foe_ident, foe_stored, .Silent });
|
|
935
938
|
if (stored.hp == 0) return true;
|
|
936
939
|
}
|
|
937
940
|
|
|
938
941
|
if (volatiles.Nightmare) {
|
|
939
942
|
stored.hp -= @max(stored.stats.hp / 4, 1);
|
|
940
943
|
// ibid
|
|
941
|
-
try options.log.damage(ident, stored, .Nightmare);
|
|
944
|
+
try options.log.damage(.{ ident, stored, .Nightmare });
|
|
942
945
|
if (stored.hp == 0) return true;
|
|
943
946
|
}
|
|
944
947
|
|
|
945
948
|
if (volatiles.Curse) {
|
|
946
949
|
stored.hp -= @max(stored.stats.hp / 4, 1);
|
|
947
950
|
// ibid
|
|
948
|
-
try options.log.damage(ident, stored, .Curse);
|
|
951
|
+
try options.log.damage(.{ ident, stored, .Curse });
|
|
949
952
|
if (stored.hp == 0) return true;
|
|
950
953
|
}
|
|
951
954
|
|
|
@@ -966,7 +969,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
966
969
|
future_sight.count -= 1;
|
|
967
970
|
assert(future_sight.count != 0);
|
|
968
971
|
if (future_sight.count == 1) {
|
|
969
|
-
try options.log.end(battle.active(player), .FutureSight);
|
|
972
|
+
try options.log.end(.{ battle.active(player), .FutureSight });
|
|
970
973
|
// TODO doMove
|
|
971
974
|
}
|
|
972
975
|
}
|
|
@@ -975,21 +978,24 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
975
978
|
|
|
976
979
|
// Weather
|
|
977
980
|
if (battle.field.weather != .None) {
|
|
978
|
-
assert(battle.field.weather_duration > 0);
|
|
979
981
|
battle.field.weather_duration -= 1;
|
|
980
982
|
|
|
981
983
|
if (battle.field.weather_duration == 0) {
|
|
982
984
|
battle.field.weather = .None;
|
|
983
|
-
try options.log.weather(battle.field.weather, .None);
|
|
985
|
+
try options.log.weather(.{ battle.field.weather, protocol.Weather.None });
|
|
984
986
|
} else {
|
|
985
|
-
try options.log.weather(battle.field.weather, .Upkeep);
|
|
987
|
+
try options.log.weather(.{ battle.field.weather, protocol.Weather.Upkeep });
|
|
986
988
|
if (battle.field.weather == .Sandstorm) {
|
|
987
989
|
inline for (players) |player| {
|
|
988
990
|
var side = battle.side(player);
|
|
989
991
|
const active = side.active;
|
|
990
992
|
if (!(active.volatiles.Underground or active.types.sandstormImmune())) {
|
|
991
993
|
side.stored().hp -|= @max(side.stored().stats.hp / 8, 1);
|
|
992
|
-
try options.log.damage(
|
|
994
|
+
try options.log.damage(.{
|
|
995
|
+
battle.active(player),
|
|
996
|
+
side.stored(),
|
|
997
|
+
.Sandstorm,
|
|
998
|
+
});
|
|
993
999
|
}
|
|
994
1000
|
}
|
|
995
1001
|
if (try checkFaint(battle, .P1, options)) |r| return r;
|
|
@@ -1001,19 +1007,24 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1001
1007
|
inline for (players) |player| {
|
|
1002
1008
|
var side = battle.side(player);
|
|
1003
1009
|
var volatiles = &side.active.volatiles;
|
|
1010
|
+
const ident = battle.active(player);
|
|
1004
1011
|
|
|
1005
1012
|
if (volatiles.bind.duration > 0 and !volatiles.Substitute) {
|
|
1006
1013
|
volatiles.bind.duration -= 1;
|
|
1007
1014
|
if (volatiles.bind.duration == 0) {
|
|
1008
1015
|
const reason = @intFromEnum(End.Bind) + volatiles.bind.reason - 1;
|
|
1009
|
-
try options.log.end(
|
|
1016
|
+
try options.log.end(.{ ident, @as(End, @enumFromInt(reason)) });
|
|
1010
1017
|
} else {
|
|
1011
1018
|
var reason = @intFromEnum(Activate.Bind) + volatiles.bind.reason - 1;
|
|
1012
|
-
try options.log.activate(
|
|
1019
|
+
try options.log.activate(.{ ident, @as(Activate, @enumFromInt(reason)) });
|
|
1013
1020
|
|
|
1014
1021
|
reason = @intFromEnum(Damage.Bind) + volatiles.bind.reason - 1;
|
|
1015
1022
|
side.stored().hp -|= @max(side.stored().stats.hp / 16, 1);
|
|
1016
|
-
try options.log.damage(
|
|
1023
|
+
try options.log.damage(.{
|
|
1024
|
+
ident,
|
|
1025
|
+
side.stored(),
|
|
1026
|
+
@as(Damage, @enumFromInt(reason)),
|
|
1027
|
+
});
|
|
1017
1028
|
}
|
|
1018
1029
|
}
|
|
1019
1030
|
}
|
|
@@ -1025,12 +1036,11 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1025
1036
|
var volatiles = &side.active.volatiles;
|
|
1026
1037
|
|
|
1027
1038
|
if (volatiles.PerishSong) {
|
|
1028
|
-
assert(volatiles.perish_song > 0);
|
|
1029
1039
|
volatiles.perish_song -= 1;
|
|
1030
1040
|
|
|
1031
1041
|
assert(volatiles.perish_song < 3);
|
|
1032
1042
|
const reason = @intFromEnum(Start.PerishSong0) + volatiles.perish_song;
|
|
1033
|
-
try options.log.start(battle.active(player), @enumFromInt(reason));
|
|
1043
|
+
try options.log.start(.{ battle.active(player), @as(Start, @enumFromInt(reason)) });
|
|
1034
1044
|
|
|
1035
1045
|
if (volatiles.perish_song == 0) {
|
|
1036
1046
|
volatiles.PerishSong = false;
|
|
@@ -1047,13 +1057,15 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1047
1057
|
if (stored.item == .Leftovers) {
|
|
1048
1058
|
const before = stored.hp;
|
|
1049
1059
|
stored.hp = @min(stored.hp + @max(stored.stats.hp / 16, 1), stored.stats.hp);
|
|
1050
|
-
if (stored.hp > before)
|
|
1060
|
+
if (stored.hp > before) {
|
|
1061
|
+
try options.log.heal(.{ battle.active(player), stored, .Leftovers });
|
|
1062
|
+
}
|
|
1051
1063
|
}
|
|
1052
1064
|
}
|
|
1053
1065
|
|
|
1054
1066
|
// Mystery Berry
|
|
1055
1067
|
inline for (players) |player| {
|
|
1056
|
-
|
|
1068
|
+
const stored = battle.side(player).stored();
|
|
1057
1069
|
|
|
1058
1070
|
if (stored.item == .MysteryBerry) {
|
|
1059
1071
|
// TODO stored and active moves, transform, etc
|
|
@@ -1068,7 +1080,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1068
1080
|
!side.active.volatiles.frozen and
|
|
1069
1081
|
try Rolls.defrost(battle, player, options))
|
|
1070
1082
|
{
|
|
1071
|
-
try options.log.curestatus(battle.active(player), side.stored().status, .Message);
|
|
1083
|
+
try options.log.curestatus(.{ battle.active(player), side.stored().status, .Message });
|
|
1072
1084
|
side.stored().status = 0;
|
|
1073
1085
|
}
|
|
1074
1086
|
}
|
|
@@ -1109,14 +1121,14 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1109
1121
|
stored.hp < stored.stats.hp;
|
|
1110
1122
|
if (proc) {
|
|
1111
1123
|
assert(stored.hp != 0);
|
|
1112
|
-
try options.log.enditem(ident, stored.item, .Eat);
|
|
1124
|
+
try options.log.enditem(.{ ident, stored.item, .Eat });
|
|
1113
1125
|
stored.item = .None;
|
|
1114
1126
|
|
|
1115
1127
|
const offset = num - @intFromEnum(Item.Berry);
|
|
1116
1128
|
assert(offset < 3);
|
|
1117
1129
|
stored.hp = @min(stored.hp + ((offset + 1) * 10), stored.stats.hp);
|
|
1118
1130
|
const reason: Heal = @enumFromInt(@intFromEnum(Heal.Berry) + offset);
|
|
1119
|
-
try options.log.heal(ident, stored, reason);
|
|
1131
|
+
try options.log.heal(.{ ident, stored, reason });
|
|
1120
1132
|
}
|
|
1121
1133
|
} else if (num < @intFromEnum(Item.BitterBerry)) {
|
|
1122
1134
|
const proc = if (stored.item == .MiracleBerry)
|
|
@@ -1126,29 +1138,29 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1126
1138
|
|
|
1127
1139
|
if (proc) {
|
|
1128
1140
|
assert(Status.any(stored.status));
|
|
1129
|
-
try options.log.enditem(ident, stored.item, .Eat);
|
|
1141
|
+
try options.log.enditem(.{ ident, stored.item, .Eat });
|
|
1130
1142
|
stored.item = .None;
|
|
1131
1143
|
|
|
1132
1144
|
// FIXME wtf?
|
|
1133
1145
|
// side.active.volatiles.Toxic = false;
|
|
1134
|
-
// side.active.volatiles.
|
|
1146
|
+
// side.active.volatiles.Nightmare = false;
|
|
1135
1147
|
if (stored.item == .MiracleBerry) {
|
|
1136
1148
|
side.active.volatiles.Confusion = false;
|
|
1137
|
-
try options.log.end(ident, .Confusion);
|
|
1149
|
+
try options.log.end(.{ ident, .Confusion });
|
|
1138
1150
|
}
|
|
1139
1151
|
stored.status = 0;
|
|
1140
1152
|
|
|
1141
1153
|
// FIXME recalc stats
|
|
1142
1154
|
|
|
1143
|
-
try options.log.curestatus(ident, stored.status, .Message);
|
|
1155
|
+
try options.log.curestatus(.{ ident, stored.status, .Message });
|
|
1144
1156
|
}
|
|
1145
1157
|
} else if (num == @intFromEnum(Item.BitterBerry)) {
|
|
1146
1158
|
if (side.active.volatiles.Confusion) {
|
|
1147
|
-
try options.log.enditem(ident, stored.item, .Eat);
|
|
1159
|
+
try options.log.enditem(.{ ident, stored.item, .Eat });
|
|
1148
1160
|
stored.item = .None;
|
|
1149
1161
|
|
|
1150
1162
|
side.active.volatiles.Confusion = false;
|
|
1151
|
-
try options.log.end(ident, .Confusion);
|
|
1163
|
+
try options.log.end(.{ ident, .Confusion });
|
|
1152
1164
|
}
|
|
1153
1165
|
}
|
|
1154
1166
|
}
|
|
@@ -1160,12 +1172,11 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1160
1172
|
var volatiles = &side.active.volatiles;
|
|
1161
1173
|
|
|
1162
1174
|
if (volatiles.Encore) {
|
|
1163
|
-
assert(volatiles.encore > 0);
|
|
1164
1175
|
volatiles.encore -= 1;
|
|
1165
1176
|
|
|
1166
1177
|
if (volatiles.encore == 0 or side.active.move(mslot).pp == 0) {
|
|
1167
1178
|
volatiles.Encore = false;
|
|
1168
|
-
try options.log.end(battle.active(player), .Encore);
|
|
1179
|
+
try options.log.end(.{ battle.active(player), .Encore });
|
|
1169
1180
|
}
|
|
1170
1181
|
}
|
|
1171
1182
|
}
|
|
@@ -1182,12 +1193,11 @@ fn updateSideCondition(
|
|
|
1182
1193
|
) !void {
|
|
1183
1194
|
var side = battle.side(player);
|
|
1184
1195
|
if (@field(side.conditions, key)) {
|
|
1185
|
-
assert(@field(side.conditions, value) > 0);
|
|
1186
1196
|
@field(side.conditions, value) -= 1;
|
|
1187
1197
|
|
|
1188
1198
|
if (@field(side.conditions, value) == 0) {
|
|
1189
1199
|
@field(side.conditions, key) = false;
|
|
1190
|
-
try options.log.sideend(player, @field(protocol.Side, key));
|
|
1200
|
+
try options.log.sideend(.{ player, @field(protocol.Side, key) });
|
|
1191
1201
|
}
|
|
1192
1202
|
}
|
|
1193
1203
|
}
|
|
@@ -1196,13 +1206,13 @@ fn endTurn(battle: anytype, options: anytype) @TypeOf(options.log).Error!Result
|
|
|
1196
1206
|
battle.turn += 1;
|
|
1197
1207
|
|
|
1198
1208
|
if (showdown and battle.turn >= 1000) {
|
|
1199
|
-
try options.log.tie();
|
|
1209
|
+
try options.log.tie(.{});
|
|
1200
1210
|
return Result.Tie;
|
|
1201
1211
|
} else if (battle.turn >= 65535) {
|
|
1202
1212
|
return Result.Error;
|
|
1203
1213
|
}
|
|
1204
1214
|
|
|
1205
|
-
try options.log.turn(battle.turn);
|
|
1215
|
+
try options.log.turn(.{battle.turn});
|
|
1206
1216
|
|
|
1207
1217
|
return Result.Default;
|
|
1208
1218
|
}
|
|
@@ -1218,7 +1228,7 @@ pub fn buildRage(battle: anytype, player: Player, state: *State, options: anytyp
|
|
|
1218
1228
|
}
|
|
1219
1229
|
} else {
|
|
1220
1230
|
foe.active.volatiles.rage +|= 1;
|
|
1221
|
-
try options.log.activate(battle.active(player), .Rage);
|
|
1231
|
+
try options.log.activate(.{ battle.active(player), .Rage });
|
|
1222
1232
|
}
|
|
1223
1233
|
}
|
|
1224
1234
|
}
|
|
@@ -1241,16 +1251,12 @@ pub fn destinyBond(battle: anytype, player: Player, _: *State, options: anytype)
|
|
|
1241
1251
|
|
|
1242
1252
|
if (foe.active.volatiles.DestinyBond) {
|
|
1243
1253
|
battle.side(player).stored().hp = 0;
|
|
1244
|
-
try options.log.activate(battle.active(player.foe()), .DestinyBond);
|
|
1254
|
+
try options.log.activate(.{ battle.active(player.foe()), .DestinyBond });
|
|
1245
1255
|
}
|
|
1246
1256
|
return true;
|
|
1247
1257
|
}
|
|
1248
1258
|
|
|
1249
1259
|
pub const Effects = struct {
|
|
1250
|
-
pub fn alwaysHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1251
|
-
_ = .{ battle, player, state, options }; // TODO
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
1260
|
pub fn attract(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1255
1261
|
const side = battle.side(player);
|
|
1256
1262
|
var foe = battle.foe(player);
|
|
@@ -1258,16 +1264,14 @@ pub const Effects = struct {
|
|
|
1258
1264
|
const gender: u8 = @intFromEnum(side.stored().dvs.gender);
|
|
1259
1265
|
const foe_gender: u8 = @intFromEnum(foe.stored().dvs.gender);
|
|
1260
1266
|
if (gender + foe_gender != 1) {
|
|
1261
|
-
|
|
1262
|
-
return;
|
|
1267
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
1263
1268
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1264
|
-
try options.log.lastmiss();
|
|
1265
|
-
|
|
1266
|
-
return;
|
|
1269
|
+
try options.log.lastmiss(.{});
|
|
1270
|
+
return options.log.miss(.{battle.active(player)});
|
|
1267
1271
|
}
|
|
1268
1272
|
|
|
1269
1273
|
foe.active.volatiles.Attract = true;
|
|
1270
|
-
try options.log.start(battle.active(player.foe()), .Attract);
|
|
1274
|
+
try options.log.start(.{ battle.active(player.foe()), .Attract });
|
|
1271
1275
|
}
|
|
1272
1276
|
|
|
1273
1277
|
pub fn batonPass(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1279,15 +1283,71 @@ pub const Effects = struct {
|
|
|
1279
1283
|
}
|
|
1280
1284
|
|
|
1281
1285
|
pub fn bellyDrum(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1282
|
-
|
|
1283
|
-
|
|
1286
|
+
assert(!state.miss);
|
|
1287
|
+
var side = battle.side(player);
|
|
1284
1288
|
|
|
1285
|
-
|
|
1286
|
-
|
|
1289
|
+
const ident = battle.active(player);
|
|
1290
|
+
|
|
1291
|
+
assert(side.active.boosts.atk >= -6 and side.active.boosts.atk <= 6);
|
|
1292
|
+
if (side.active.boosts.atk == 6 or side.active.boosts.atk == MAX_STAT_VALUE) {
|
|
1293
|
+
return options.log.fail(.{ ident, .None });
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
const hp = @max(side.stored().stats.hp / 2, 1);
|
|
1297
|
+
if (side.stored().hp <= hp) {
|
|
1298
|
+
try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
|
|
1299
|
+
try options.log.boost(.{ ident, .Attack, @as(i8, 2), .Silent });
|
|
1300
|
+
return options.log.fail(.{ ident, .None });
|
|
1301
|
+
} else {
|
|
1302
|
+
side.stored().hp -= hp;
|
|
1303
|
+
try options.log.damage(.{ ident, side.stored(), .None });
|
|
1304
|
+
|
|
1305
|
+
const before = side.active.boosts.atk;
|
|
1306
|
+
var i: usize = 0;
|
|
1307
|
+
while (i < 6 and side.active.stats.atk < MAX_STAT_VALUE) : (i += 1) {
|
|
1308
|
+
try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
|
|
1309
|
+
}
|
|
1310
|
+
try options.log.boost(.{ ident, .Attack, @as(i8, side.active.boosts.atk - before) });
|
|
1311
|
+
}
|
|
1287
1312
|
}
|
|
1288
1313
|
|
|
1314
|
+
pub const bide = struct {
|
|
1315
|
+
pub fn store(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1316
|
+
var side = battle.side(player);
|
|
1317
|
+
var volatiles = &side.active.volatiles;
|
|
1318
|
+
|
|
1319
|
+
const ident = battle.active(player);
|
|
1320
|
+
|
|
1321
|
+
if (!volatiles.Bide) return;
|
|
1322
|
+
|
|
1323
|
+
volatiles.attacks -= 1;
|
|
1324
|
+
if (volatiles.attacks != 0) return options.log.activate(.{ ident, .Bide });
|
|
1325
|
+
|
|
1326
|
+
volatiles.Bide = false;
|
|
1327
|
+
try options.log.end(.{ ident, .Bide });
|
|
1328
|
+
|
|
1329
|
+
state.damage = volatiles.bide *% 2;
|
|
1330
|
+
volatiles.bide = 0;
|
|
1331
|
+
|
|
1332
|
+
if (state.damage == 0) state.miss = true;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
pub fn unleash(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1336
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1337
|
+
}
|
|
1338
|
+
};
|
|
1339
|
+
|
|
1289
1340
|
pub fn binding(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1290
|
-
|
|
1341
|
+
var foe = battle.foe(player);
|
|
1342
|
+
|
|
1343
|
+
if (state.miss) return;
|
|
1344
|
+
if (foe.active.volatiles.bind.duration > 0) return;
|
|
1345
|
+
if (foe.active.volatiles.Substitute) return;
|
|
1346
|
+
|
|
1347
|
+
foe.active.volatiles.bind = .{
|
|
1348
|
+
.duration = Rolls.bindingDuration(battle, player, options),
|
|
1349
|
+
.reason = Move.get(state.move).extra.protocol,
|
|
1350
|
+
};
|
|
1291
1351
|
}
|
|
1292
1352
|
|
|
1293
1353
|
pub fn burnChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1297,12 +1357,12 @@ pub const Effects = struct {
|
|
|
1297
1357
|
if (foe.active.volatiles.Substitute) return;
|
|
1298
1358
|
if (Status.any(foe_stored.status)) {
|
|
1299
1359
|
if (Status.is(foe_stored.status, .FRZ)) {
|
|
1300
|
-
assert(Move.get(state.move).type == .Fire);
|
|
1301
|
-
try options.log.curestatus(
|
|
1360
|
+
assert(Move.get(state.move).type == .Fire or state.move == .TriAttack);
|
|
1361
|
+
try options.log.curestatus(.{
|
|
1302
1362
|
battle.active(player.foe()),
|
|
1303
1363
|
foe_stored.status,
|
|
1304
1364
|
.Message,
|
|
1305
|
-
);
|
|
1365
|
+
});
|
|
1306
1366
|
foe_stored.status = 0;
|
|
1307
1367
|
}
|
|
1308
1368
|
return;
|
|
@@ -1311,11 +1371,14 @@ pub const Effects = struct {
|
|
|
1311
1371
|
if (foe.conditions.Safeguard) return;
|
|
1312
1372
|
// Nothing is immune to Fire-type, but Ghosts are immune to burnChance called by Tri Attack
|
|
1313
1373
|
if (state.immune()) return;
|
|
1314
|
-
if (foe.active.types.includes(Move.get(state.move).type))
|
|
1374
|
+
if (foe.active.types.includes(Move.get(state.move).type) and state.move != .TriAttack) {
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1315
1377
|
|
|
1316
1378
|
foe_stored.status = Status.init(.BRN);
|
|
1317
1379
|
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
1318
|
-
|
|
1380
|
+
|
|
1381
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1319
1382
|
|
|
1320
1383
|
// TODO: check for status berry
|
|
1321
1384
|
}
|
|
@@ -1335,10 +1398,7 @@ pub const Effects = struct {
|
|
|
1335
1398
|
for (side.active.moves) |m| {
|
|
1336
1399
|
if (convertible(side.active, m.id)) n += 1;
|
|
1337
1400
|
}
|
|
1338
|
-
if (n == 0) {
|
|
1339
|
-
try options.log.fail(battle.active(player), .None);
|
|
1340
|
-
return;
|
|
1341
|
-
}
|
|
1401
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
1342
1402
|
|
|
1343
1403
|
side.active.types.type1 = try Rolls.conversion(battle, player, n, options);
|
|
1344
1404
|
side.active.types.type2 = side.active.types.type1;
|
|
@@ -1348,26 +1408,87 @@ pub const Effects = struct {
|
|
|
1348
1408
|
var side = battle.side(player);
|
|
1349
1409
|
|
|
1350
1410
|
const last = battle.foe(player).lastMove(false);
|
|
1351
|
-
if (last == .None) {
|
|
1352
|
-
try options.log.fail(battle.active(player), .None);
|
|
1353
|
-
return;
|
|
1354
|
-
}
|
|
1411
|
+
if (last == .None) return options.log.fail(.{ battle.active(player), .None });
|
|
1355
1412
|
const move = Move.get(last);
|
|
1356
|
-
if (move.type == .@"???") {
|
|
1357
|
-
try options.log.fail(battle.active(player), .None);
|
|
1358
|
-
return;
|
|
1359
|
-
}
|
|
1413
|
+
if (move.type == .@"???") return options.log.fail(.{ battle.active(player), .None });
|
|
1360
1414
|
|
|
1361
1415
|
side.active.types.type1 = try Rolls.conversion2(battle, player, move.type, options);
|
|
1362
1416
|
side.active.types.type2 = side.active.types.type1;
|
|
1363
1417
|
}
|
|
1364
1418
|
|
|
1365
|
-
pub fn counter(battle: anytype, player: Player, state: *State,
|
|
1366
|
-
|
|
1419
|
+
pub fn counter(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1420
|
+
assert(state.move == .Counter or state.move == .MirrorCoat);
|
|
1421
|
+
|
|
1422
|
+
const foe = battle.foe(player);
|
|
1423
|
+
|
|
1424
|
+
state.miss = true;
|
|
1425
|
+
const last = foe.lastMove(false);
|
|
1426
|
+
|
|
1427
|
+
if (last == .None) return;
|
|
1428
|
+
if (last == state.move) return;
|
|
1429
|
+
|
|
1430
|
+
const immune = state.immune();
|
|
1431
|
+
state.effectiveness = Effectiveness.neutral;
|
|
1432
|
+
if (immune) {
|
|
1433
|
+
state.damage = 0;
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
const move = Move.get(last);
|
|
1438
|
+
if (state.bp == 0) return;
|
|
1439
|
+
if (move.type.special() != (last == .MirrorCoat)) return;
|
|
1440
|
+
if (state.damage == 0) return;
|
|
1441
|
+
|
|
1442
|
+
state.damage *|= 2;
|
|
1443
|
+
state.miss = false;
|
|
1367
1444
|
}
|
|
1368
1445
|
|
|
1369
1446
|
pub fn curse(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1370
|
-
|
|
1447
|
+
var side = battle.side(player);
|
|
1448
|
+
var foe = battle.foe(player);
|
|
1449
|
+
|
|
1450
|
+
const ident = battle.active(player);
|
|
1451
|
+
const foe_ident = battle.active(player.foe());
|
|
1452
|
+
|
|
1453
|
+
if (side.active.types.includes(.Ghost)) {
|
|
1454
|
+
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1455
|
+
try options.log.lastmiss(.{});
|
|
1456
|
+
return options.log.miss(.{battle.active(player)});
|
|
1457
|
+
} else if (foe.active.volatiles.Substitute) {
|
|
1458
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1459
|
+
} else if (foe.active.volatiles.Curse) {
|
|
1460
|
+
return options.log.fail(.{ foe_ident, .None });
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
foe.active.volatiles.Curse = true;
|
|
1464
|
+
try options.log.start(.{ foe_ident, .Curse });
|
|
1465
|
+
|
|
1466
|
+
side.stored().hp -= @max(side.stored().stats.hp / 2, 1);
|
|
1467
|
+
try options.log.damage(.{ ident, side.stored(), .Curse });
|
|
1468
|
+
} else {
|
|
1469
|
+
const stats = &side.active.stats;
|
|
1470
|
+
const boosts = &side.active.boosts;
|
|
1471
|
+
|
|
1472
|
+
const max_atk = boosts.atk == 6 or stats.atk == MAX_STAT_VALUE;
|
|
1473
|
+
const max_def = boosts.def == 6 or stats.def == MAX_STAT_VALUE;
|
|
1474
|
+
if (max_atk and max_def) return options.log.fail(.{ ident, .None });
|
|
1475
|
+
|
|
1476
|
+
if (!(boosts.spe == -6 or stats.spe == 1)) {
|
|
1477
|
+
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
1478
|
+
try unboostStat(battle, side, stats, boosts, "spe", 1);
|
|
1479
|
+
try options.log.boost(.{ ident, .Speed, @as(i8, -1) });
|
|
1480
|
+
}
|
|
1481
|
+
if (!max_atk) {
|
|
1482
|
+
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
1483
|
+
try boostStat(battle, side, stats, boosts, "atk", 1);
|
|
1484
|
+
try options.log.boost(.{ ident, .Attack, @as(i8, 1) });
|
|
1485
|
+
}
|
|
1486
|
+
if (!max_def) {
|
|
1487
|
+
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
1488
|
+
try boostStat(battle, side, stats, boosts, "def", 1);
|
|
1489
|
+
try options.log.boost(.{ ident, .Defense, @as(i8, 1) });
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1371
1492
|
}
|
|
1372
1493
|
|
|
1373
1494
|
pub fn defenseCurl(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
@@ -1377,14 +1498,13 @@ pub const Effects = struct {
|
|
|
1377
1498
|
pub fn defrost(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1378
1499
|
var side = battle.side(player);
|
|
1379
1500
|
assert(Status.is(side.stored().status, .FRZ));
|
|
1380
|
-
|
|
1381
|
-
try options.log.curestatus(battle.active(player), side.stored().status, .Message);
|
|
1501
|
+
try options.log.curestatus(.{ battle.active(player), side.stored().status, .Message });
|
|
1382
1502
|
side.stored().status = 0;
|
|
1383
1503
|
}
|
|
1384
1504
|
|
|
1385
1505
|
pub fn destinyBond(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1386
1506
|
battle.side(player).active.volatiles.DestinyBond = true;
|
|
1387
|
-
try options.log.singlemove(battle.active(player), Move.DestinyBond);
|
|
1507
|
+
try options.log.singlemove(.{ battle.active(player), Move.DestinyBond });
|
|
1388
1508
|
}
|
|
1389
1509
|
|
|
1390
1510
|
pub fn disable(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
@@ -1392,16 +1512,10 @@ pub const Effects = struct {
|
|
|
1392
1512
|
var volatiles = &foe.active.volatiles;
|
|
1393
1513
|
const foe_ident = battle.active(player.foe());
|
|
1394
1514
|
|
|
1395
|
-
if (volatiles.disable.move != 0) {
|
|
1396
|
-
try options.log.fail(foe_ident, .None);
|
|
1397
|
-
return;
|
|
1398
|
-
}
|
|
1515
|
+
if (volatiles.disable.move != 0) return options.log.fail(.{ foe_ident, .None });
|
|
1399
1516
|
|
|
1400
1517
|
const last = battle.foe(player).lastMove(false);
|
|
1401
|
-
if (last == .None or last == .Struggle) {
|
|
1402
|
-
try options.log.fail(foe_ident, .None);
|
|
1403
|
-
return;
|
|
1404
|
-
}
|
|
1518
|
+
if (last == .None or last == .Struggle) return options.log.fail(.{ foe_ident, .None });
|
|
1405
1519
|
|
|
1406
1520
|
var slot: u3 = 0;
|
|
1407
1521
|
for (foe.active.moves) |m| {
|
|
@@ -1410,30 +1524,38 @@ pub const Effects = struct {
|
|
|
1410
1524
|
if (m.id == last) break;
|
|
1411
1525
|
}
|
|
1412
1526
|
const move = foe.active.move(slot);
|
|
1413
|
-
if (move.pp == 0) {
|
|
1414
|
-
try options.log.fail(foe_ident, .None);
|
|
1415
|
-
return;
|
|
1416
|
-
}
|
|
1527
|
+
if (move.pp == 0) return options.log.fail(.{ foe_ident, .None });
|
|
1417
1528
|
|
|
1418
1529
|
volatiles.disable.move = slot;
|
|
1419
1530
|
volatiles.disable.duration = Rolls.disableDuration(battle, player, options);
|
|
1420
1531
|
|
|
1421
|
-
try options.log.
|
|
1532
|
+
try options.log.start(.{ foe_ident, .Disable, move.id });
|
|
1422
1533
|
}
|
|
1423
1534
|
|
|
1424
|
-
pub
|
|
1425
|
-
|
|
1426
|
-
|
|
1535
|
+
pub const double = struct {
|
|
1536
|
+
pub fn flying(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1537
|
+
if (battle.foe(player).active.volatiles.Flying) state.damage *|= 2;
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
pub fn underground(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1541
|
+
if (battle.foe(player).active.volatiles.Underground) state.damage *|= 2;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
pub fn minimize(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1545
|
+
if (battle.foe(player).active.volatiles.minimized) state.damage *|= 2;
|
|
1546
|
+
}
|
|
1547
|
+
};
|
|
1427
1548
|
|
|
1428
1549
|
pub fn drainHP(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1429
1550
|
var stored = battle.side(player).stored();
|
|
1551
|
+
const ident = battle.active(player);
|
|
1430
1552
|
|
|
1431
1553
|
assert(state.damage != 0);
|
|
1432
1554
|
|
|
1433
1555
|
if (stored.hp == stored.stats.hp) return;
|
|
1434
1556
|
stored.hp = @min(stored.stats.hp, stored.hp + @max(state.damage / 2, 1));
|
|
1435
1557
|
|
|
1436
|
-
try options.log.
|
|
1558
|
+
try options.log.heal(.{ ident, stored, .Drain, battle.active(player.foe()) });
|
|
1437
1559
|
}
|
|
1438
1560
|
|
|
1439
1561
|
pub fn encore(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1445,10 +1567,7 @@ pub const Effects = struct {
|
|
|
1445
1567
|
const failed = volatiles.Encore or
|
|
1446
1568
|
(last == .None or last == .Struggle or
|
|
1447
1569
|
last == .Encore or .last == .MirrorMove);
|
|
1448
|
-
if (failed) {
|
|
1449
|
-
try options.log.fail(foe_ident, .None);
|
|
1450
|
-
return;
|
|
1451
|
-
}
|
|
1570
|
+
if (failed) return options.log.fail(.{ foe_ident, .None });
|
|
1452
1571
|
|
|
1453
1572
|
var slot: u3 = 0;
|
|
1454
1573
|
for (foe.active.moves) |m| {
|
|
@@ -1457,10 +1576,7 @@ pub const Effects = struct {
|
|
|
1457
1576
|
if (m.id == last) break;
|
|
1458
1577
|
}
|
|
1459
1578
|
const move = foe.active.move(slot);
|
|
1460
|
-
if (move.pp == 0) {
|
|
1461
|
-
try options.log.fail(foe_ident, .None);
|
|
1462
|
-
return;
|
|
1463
|
-
}
|
|
1579
|
+
if (move.pp == 0) return options.log.fail(.{ foe_ident, .None });
|
|
1464
1580
|
|
|
1465
1581
|
volatiles.Encore = true;
|
|
1466
1582
|
volatiles.encore = Rolls.encoreDuration(battle, player, options);
|
|
@@ -1468,11 +1584,19 @@ pub const Effects = struct {
|
|
|
1468
1584
|
// TODO ???
|
|
1469
1585
|
_ = state;
|
|
1470
1586
|
|
|
1471
|
-
try options.log.
|
|
1587
|
+
try options.log.start(.{ foe_ident, .Encore, move.id });
|
|
1472
1588
|
}
|
|
1473
1589
|
|
|
1474
|
-
pub fn explode(battle: anytype, player: Player,
|
|
1475
|
-
|
|
1590
|
+
pub fn explode(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
1591
|
+
var side = battle.side(player);
|
|
1592
|
+
var stored = side.stored();
|
|
1593
|
+
|
|
1594
|
+
stored.hp = 0;
|
|
1595
|
+
// Pokémon Showdown sets the status to 0 on faint(), and we need to do the same to be able
|
|
1596
|
+
// to correctly implement Pokémon Showdown's dumb speed-based switch mechanics
|
|
1597
|
+
if (!showdown) stored.status = 0;
|
|
1598
|
+
side.active.volatiles.LeechSeed = false;
|
|
1599
|
+
side.active.volatiles.DestinyBond = false;
|
|
1476
1600
|
}
|
|
1477
1601
|
|
|
1478
1602
|
pub fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
@@ -1500,10 +1624,6 @@ pub const Effects = struct {
|
|
|
1500
1624
|
};
|
|
1501
1625
|
}
|
|
1502
1626
|
|
|
1503
|
-
pub fn flameWheel(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1504
|
-
_ = .{ battle, player, state, options }; // TODO
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
1627
|
pub fn flinchChance(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1508
1628
|
const foe = battle.foe(player);
|
|
1509
1629
|
const status = foe.stored().status;
|
|
@@ -1526,12 +1646,11 @@ pub const Effects = struct {
|
|
|
1526
1646
|
var side = battle.side(player);
|
|
1527
1647
|
|
|
1528
1648
|
if (side.active.volatiles.FocusEnergy) {
|
|
1529
|
-
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
1530
|
-
return;
|
|
1649
|
+
return if (!showdown) try options.log.fail(.{ battle.active(player), .None });
|
|
1531
1650
|
}
|
|
1532
1651
|
|
|
1533
1652
|
side.active.volatiles.FocusEnergy = true;
|
|
1534
|
-
try options.log.start(battle.active(player), .FocusEnergy);
|
|
1653
|
+
try options.log.start(.{ battle.active(player), .FocusEnergy });
|
|
1535
1654
|
}
|
|
1536
1655
|
|
|
1537
1656
|
var SLOTS: [5]u4 = .{ 0, 0, 0, 0, 0 };
|
|
@@ -1541,7 +1660,7 @@ pub const Effects = struct {
|
|
|
1541
1660
|
|
|
1542
1661
|
const foe = battle.foe(player);
|
|
1543
1662
|
|
|
1544
|
-
if (state.first) return
|
|
1663
|
+
if (state.first) return options.log.fail(.{ battle.active(player), .None });
|
|
1545
1664
|
|
|
1546
1665
|
var n: u4 = 0;
|
|
1547
1666
|
var i: u4 = 0;
|
|
@@ -1555,10 +1674,7 @@ pub const Effects = struct {
|
|
|
1555
1674
|
}
|
|
1556
1675
|
}
|
|
1557
1676
|
|
|
1558
|
-
if (n == 0) {
|
|
1559
|
-
try options.log.fail(battle.active(player), .None);
|
|
1560
|
-
return;
|
|
1561
|
-
}
|
|
1677
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
1562
1678
|
|
|
1563
1679
|
const slot = try Rolls.forceSwitch(battle, player, SLOTS[0..i], n, options);
|
|
1564
1680
|
try switchIn(battle, player.foe(), slot, .Drag, options);
|
|
@@ -1568,16 +1684,14 @@ pub const Effects = struct {
|
|
|
1568
1684
|
var foe = battle.foe(player);
|
|
1569
1685
|
|
|
1570
1686
|
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1571
|
-
try options.log.lastmiss();
|
|
1572
|
-
|
|
1573
|
-
return;
|
|
1687
|
+
try options.log.lastmiss(.{});
|
|
1688
|
+
return options.log.miss(.{battle.active(player)});
|
|
1574
1689
|
} else if (foe.active.volatiles.Foresight) {
|
|
1575
|
-
|
|
1576
|
-
return;
|
|
1690
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
1577
1691
|
}
|
|
1578
1692
|
|
|
1579
1693
|
foe.active.volatiles.Foresight = true;
|
|
1580
|
-
try options.log.start(battle.active(player.foe()), .FocusEnergy);
|
|
1694
|
+
try options.log.start(.{ battle.active(player.foe()), .FocusEnergy });
|
|
1581
1695
|
}
|
|
1582
1696
|
|
|
1583
1697
|
pub fn freezeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1589,15 +1703,18 @@ pub const Effects = struct {
|
|
|
1589
1703
|
if (Status.any(foe_stored.status)) return;
|
|
1590
1704
|
|
|
1591
1705
|
// Nothing is immune to Ice-type, but Ghosts are immune to freezeChance called by Tri Attack
|
|
1706
|
+
if (state.immune()) return;
|
|
1592
1707
|
if (battle.field.weather == .Sun) return;
|
|
1593
|
-
if (foe.active.types.includes(Move.get(state.move).type))
|
|
1708
|
+
if (foe.active.types.includes(Move.get(state.move).type) and state.move != .TriAttack) {
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1594
1711
|
if (!state.proc) return;
|
|
1595
1712
|
if (foe.conditions.Safeguard) return;
|
|
1596
1713
|
// Freeze Clause Mod
|
|
1597
1714
|
if (showdown) for (foe.pokemon) |p| if (Status.is(p.status, .FRZ)) return;
|
|
1598
1715
|
|
|
1599
1716
|
foe_stored.status = Status.init(.FRZ);
|
|
1600
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
1717
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
1601
1718
|
|
|
1602
1719
|
// TODO: check for status berry
|
|
1603
1720
|
|
|
@@ -1605,8 +1722,26 @@ pub const Effects = struct {
|
|
|
1605
1722
|
foe.active.volatiles.frozen = true;
|
|
1606
1723
|
}
|
|
1607
1724
|
|
|
1608
|
-
|
|
1609
|
-
|
|
1725
|
+
// TODO FIXME showdown calls this before calcDamage (after checkHit) and modifies state.bp!
|
|
1726
|
+
pub fn furyCutter(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1727
|
+
var side = battle.side(player);
|
|
1728
|
+
var volatiles = &side.active.volatiles;
|
|
1729
|
+
|
|
1730
|
+
if (state.miss) {
|
|
1731
|
+
volatiles.fury_cutter = 0;
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
assert(volatiles.fury_cutter >= 0 and volatiles.fury_cutter <= 5);
|
|
1736
|
+
if (volatiles.fury_cutter == 5) return;
|
|
1737
|
+
volatiles.fury_cutter += 1;
|
|
1738
|
+
|
|
1739
|
+
if (showdown) {
|
|
1740
|
+
state.bp *= 2;
|
|
1741
|
+
assert(state.bp <= 160);
|
|
1742
|
+
} else {
|
|
1743
|
+
state.damage *|= 2;
|
|
1744
|
+
}
|
|
1610
1745
|
}
|
|
1611
1746
|
|
|
1612
1747
|
pub fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1619,73 +1754,222 @@ pub const Effects = struct {
|
|
|
1619
1754
|
state.bp = ((if (frustration) 255 - val else val) * 10) / 25;
|
|
1620
1755
|
}
|
|
1621
1756
|
|
|
1622
|
-
pub fn haze(battle: anytype, player: Player,
|
|
1623
|
-
|
|
1757
|
+
pub fn haze(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1758
|
+
var side = battle.side(player);
|
|
1759
|
+
var stats = unmodifiedStats(battle, side);
|
|
1760
|
+
side.active.boosts = .{};
|
|
1761
|
+
inline for (STATS) |s| @field(side.active.stats, s) = @field(stats, s);
|
|
1762
|
+
statusModify(side.stored().status, &side.active.stats);
|
|
1763
|
+
|
|
1764
|
+
var foe = battle.foe(player);
|
|
1765
|
+
stats = unmodifiedStats(battle, foe);
|
|
1766
|
+
foe.active.boosts = .{};
|
|
1767
|
+
inline for (STATS) |s| @field(foe.active.stats, s) = @field(stats, s);
|
|
1768
|
+
statusModify(foe.stored().status, &foe.active.stats);
|
|
1769
|
+
|
|
1770
|
+
try options.log.clearallboost(.{});
|
|
1624
1771
|
}
|
|
1625
1772
|
|
|
1626
1773
|
pub fn heal(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1627
|
-
|
|
1628
|
-
|
|
1774
|
+
var side = battle.side(player);
|
|
1775
|
+
var stored = side.stored();
|
|
1776
|
+
const ident = battle.active(player);
|
|
1629
1777
|
|
|
1630
|
-
|
|
1631
|
-
|
|
1778
|
+
if (stored.stats.hp == stored.hp) return options.log.fail(.{ ident, .None });
|
|
1779
|
+
|
|
1780
|
+
const rest = state.move == .Rest;
|
|
1781
|
+
if (rest) {
|
|
1782
|
+
// Adding the sleep status runs the sleep condition handler to roll duration
|
|
1783
|
+
if (showdown) battle.rng.advance(1);
|
|
1784
|
+
side.active.volatiles.Toxic = false;
|
|
1785
|
+
stored.status = Status.slf(2);
|
|
1786
|
+
recomputeStats(battle, side);
|
|
1787
|
+
try options.log.status(.{ ident, stored.status, .From, Move.Rest });
|
|
1788
|
+
stored.hp = stored.stats.hp;
|
|
1789
|
+
} else {
|
|
1790
|
+
stored.hp = @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2));
|
|
1791
|
+
}
|
|
1792
|
+
try options.log.heal(.{ ident, stored, if (rest) Heal.Silent else Heal.None });
|
|
1632
1793
|
}
|
|
1633
1794
|
|
|
1634
|
-
pub fn
|
|
1635
|
-
|
|
1795
|
+
pub fn healBell(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1796
|
+
var side = battle.side(player);
|
|
1797
|
+
side.active.volatiles.Nightmare = false;
|
|
1798
|
+
|
|
1799
|
+
try options.log.cureteam(.{battle.active(player)});
|
|
1800
|
+
for (&side.pokemon) |*p| p.status = 0;
|
|
1801
|
+
|
|
1802
|
+
// We technically only need to recompute stats if the active Pokémon was burned/paralyzed,
|
|
1803
|
+
// but adding a branch to check for that is probably more expensive than just recomputing
|
|
1804
|
+
recomputeStats(battle, side);
|
|
1636
1805
|
}
|
|
1637
1806
|
|
|
1638
|
-
pub fn
|
|
1639
|
-
|
|
1807
|
+
pub fn hiddenPower(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1808
|
+
const side = battle.side(player);
|
|
1809
|
+
state.bp = side.stored().dvs.power();
|
|
1810
|
+
state.type = side.stored().dvs.type;
|
|
1640
1811
|
}
|
|
1641
1812
|
|
|
1642
|
-
pub fn
|
|
1643
|
-
|
|
1813
|
+
pub fn hyperBeam(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1814
|
+
battle.side(player).active.volatiles.Recharging = true;
|
|
1815
|
+
try options.log.mustrecharge(.{battle.active(player)});
|
|
1644
1816
|
}
|
|
1645
1817
|
|
|
1646
1818
|
pub fn leechSeed(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1647
|
-
|
|
1819
|
+
var foe = battle.foe(player);
|
|
1820
|
+
|
|
1821
|
+
if (showdown) {
|
|
1822
|
+
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
1823
|
+
const invulnerable = foe.active.volatiles.Underground or foe.active.volatiles.Flying;
|
|
1824
|
+
if (!invulnerable and foe.active.types.includes(.Grass)) {
|
|
1825
|
+
return options.log.immune(.{ battle.active(player.foe()), .None });
|
|
1826
|
+
}
|
|
1827
|
+
try checkHit(battle, player, state, options);
|
|
1828
|
+
if (state.miss) {
|
|
1829
|
+
try options.log.lastmiss(.{});
|
|
1830
|
+
return options.log.miss(.{battle.active(player)});
|
|
1831
|
+
}
|
|
1832
|
+
if (foe.active.volatiles.LeechSeed) return;
|
|
1833
|
+
} else {
|
|
1834
|
+
try checkHit(battle, player, state, options);
|
|
1835
|
+
const miss =
|
|
1836
|
+
state.miss or foe.active.types.includes(.Grass) or foe.active.volatiles.LeechSeed;
|
|
1837
|
+
if (miss) {
|
|
1838
|
+
try options.log.lastmiss(.{});
|
|
1839
|
+
return options.log.miss(.{battle.active(player)});
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
foe.active.volatiles.LeechSeed = true;
|
|
1844
|
+
|
|
1845
|
+
try options.log.start(.{ battle.active(player.foe()), .LeechSeed });
|
|
1648
1846
|
}
|
|
1649
1847
|
|
|
1650
1848
|
pub fn lockOn(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1651
|
-
|
|
1849
|
+
var foe = battle.foe(player);
|
|
1850
|
+
const foe_ident = battle.active(player.foe());
|
|
1851
|
+
|
|
1852
|
+
if (foe.active.volatiles.Substitute) {
|
|
1853
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1854
|
+
} else if (state.miss) {
|
|
1855
|
+
return options.log.fail(.{ foe_ident, .None });
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
foe.active.volatiles.LockOn = true;
|
|
1859
|
+
try options.log.activate(.{ battle.active(player), .LockOn });
|
|
1652
1860
|
}
|
|
1653
1861
|
|
|
1654
1862
|
const MAGNITUDE_POWER = [_]u8{ 10, 30, 50, 70, 90, 110, 150 };
|
|
1655
1863
|
|
|
1656
1864
|
pub fn magnitude(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1657
|
-
|
|
1865
|
+
const num = try Rolls.magnitude(battle, player, options);
|
|
1866
|
+
try options.log.activate(.{ battle.active(player), .Magnitude, num });
|
|
1867
|
+
state.bp = MAGNITUDE_POWER[num - 4];
|
|
1658
1868
|
}
|
|
1659
1869
|
|
|
1660
|
-
pub fn meanLook(battle: anytype, player: Player,
|
|
1661
|
-
|
|
1870
|
+
pub fn meanLook(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1871
|
+
var foe = battle.foe(player);
|
|
1872
|
+
|
|
1873
|
+
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1874
|
+
if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1875
|
+
try options.log.lastmiss(.{});
|
|
1876
|
+
return options.log.miss(.{battle.active(player)});
|
|
1877
|
+
} else if (foe.active.volatiles.Trapped) {
|
|
1878
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
foe.active.volatiles.Trapped = true;
|
|
1662
1882
|
}
|
|
1663
1883
|
|
|
1664
|
-
pub fn metronome(
|
|
1665
|
-
|
|
1884
|
+
pub fn metronome(
|
|
1885
|
+
battle: anytype,
|
|
1886
|
+
player: Player,
|
|
1887
|
+
state: *State,
|
|
1888
|
+
options: anytype,
|
|
1889
|
+
) anyerror!void {
|
|
1890
|
+
var side = battle.side(player);
|
|
1891
|
+
|
|
1892
|
+
side.last_used_move = .None;
|
|
1893
|
+
side.active.volatiles.dirty = false;
|
|
1894
|
+
|
|
1895
|
+
// if (wPlayerCharging) ???
|
|
1896
|
+
// wPlayerCharging = 1
|
|
1897
|
+
|
|
1898
|
+
// FIXME
|
|
1899
|
+
state.move = try Rolls.metronome(battle, player, options);
|
|
1900
|
+
state.from = .Metronome;
|
|
1901
|
+
return generated.runMove(battle, player, state, options);
|
|
1666
1902
|
}
|
|
1667
1903
|
|
|
1668
1904
|
pub fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1669
|
-
|
|
1670
|
-
}
|
|
1905
|
+
var side = battle.side(player);
|
|
1671
1906
|
|
|
1672
|
-
|
|
1673
|
-
|
|
1907
|
+
if (state.miss) {
|
|
1908
|
+
if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1909
|
+
try options.log.lastmiss(.{});
|
|
1910
|
+
return options.log.miss(.{battle.active(player)});
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
const move = battle.foe(player).lastMove(false);
|
|
1914
|
+
if (move == .None or move == .Struggle) {
|
|
1915
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
var slot: u4 = 0;
|
|
1919
|
+
for (side.active.moves, 0..) |m, i| {
|
|
1920
|
+
if (m.id == move) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1921
|
+
if (m.id == .Mimic) slot = @intCast(i + 1);
|
|
1922
|
+
}
|
|
1923
|
+
assert(slot != 0 and slot == state.mslot);
|
|
1924
|
+
|
|
1925
|
+
side.active.move(slot).id = move;
|
|
1926
|
+
side.active.move(slot).pp = 5;
|
|
1927
|
+
try options.log.start(.{ battle.active(player), .Mimic, move });
|
|
1674
1928
|
}
|
|
1675
1929
|
|
|
1676
|
-
pub fn mirrorMove(
|
|
1677
|
-
|
|
1930
|
+
pub fn mirrorMove(
|
|
1931
|
+
battle: anytype,
|
|
1932
|
+
player: Player,
|
|
1933
|
+
state: *State,
|
|
1934
|
+
options: anytype,
|
|
1935
|
+
) anyerror!void {
|
|
1936
|
+
var side = battle.side(player);
|
|
1937
|
+
const foe = battle.foe(player);
|
|
1938
|
+
|
|
1939
|
+
side.last_used_move = .None;
|
|
1940
|
+
side.active.volatiles.dirty = false;
|
|
1941
|
+
|
|
1942
|
+
const last = foe.lastMove(false);
|
|
1943
|
+
|
|
1944
|
+
if (last == .None) return options.log.fail(.{ battle.active(player), .None });
|
|
1945
|
+
const knows = knows: for (side.active.moves) |m| {
|
|
1946
|
+
if (last == m.id) break :knows true;
|
|
1947
|
+
} else false;
|
|
1948
|
+
if (knows) return options.log.fail(.{ battle.active(player), .None });
|
|
1949
|
+
|
|
1950
|
+
// if (wPlayerCharging) ???
|
|
1951
|
+
// wPlayerCharging = 1
|
|
1952
|
+
|
|
1953
|
+
// FIXME
|
|
1954
|
+
state.move = last;
|
|
1955
|
+
state.from = .MirrorMove;
|
|
1956
|
+
return generated.runMove(battle, player, state, options);
|
|
1678
1957
|
}
|
|
1679
1958
|
|
|
1680
|
-
pub fn mist(battle: anytype, player: Player,
|
|
1681
|
-
|
|
1959
|
+
pub fn mist(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1960
|
+
var side = battle.side(player);
|
|
1961
|
+
|
|
1962
|
+
if (side.active.volatiles.Mist) return options.log.fail(.{ battle.active(player), .None });
|
|
1963
|
+
side.active.volatiles.Mist = true;
|
|
1964
|
+
|
|
1965
|
+
try options.log.start(.{ battle.active(player), .Mist });
|
|
1682
1966
|
}
|
|
1683
1967
|
|
|
1684
1968
|
pub fn weatherHeal(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1685
1969
|
var stored = battle.side(player).stored();
|
|
1686
1970
|
const ident = battle.active(player);
|
|
1687
1971
|
|
|
1688
|
-
if (stored.hp == stored.stats.hp) return
|
|
1972
|
+
if (stored.hp == stored.stats.hp) return options.log.fail(.{ ident, .None });
|
|
1689
1973
|
|
|
1690
1974
|
stored.hp = switch (battle.field.weather) {
|
|
1691
1975
|
.Sun => stored.stats.hp,
|
|
@@ -1693,7 +1977,7 @@ pub const Effects = struct {
|
|
|
1693
1977
|
else => @min(stored.stats.hp, stored.hp + (stored.stats.hp / 4)),
|
|
1694
1978
|
};
|
|
1695
1979
|
|
|
1696
|
-
try options.log.heal(ident, stored,
|
|
1980
|
+
try options.log.heal(.{ ident, stored, .None });
|
|
1697
1981
|
}
|
|
1698
1982
|
|
|
1699
1983
|
pub fn multiHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1701,32 +1985,96 @@ pub const Effects = struct {
|
|
|
1701
1985
|
}
|
|
1702
1986
|
|
|
1703
1987
|
pub fn nightmare(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1704
|
-
|
|
1988
|
+
var foe = battle.foe(player);
|
|
1989
|
+
var foe_volatiles = &foe.active.volatiles;
|
|
1990
|
+
const foe_ident = battle.active(player.foe());
|
|
1991
|
+
|
|
1992
|
+
if (foe_volatiles.Flying or foe_volatiles.Underground) {
|
|
1993
|
+
if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1994
|
+
try options.log.lastmiss(.{});
|
|
1995
|
+
return options.log.miss(.{battle.active(player)});
|
|
1996
|
+
} else if (foe_volatiles.Substitute) {
|
|
1997
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1998
|
+
} else if (!Status.is(foe.stored().status, .SLP) or foe_volatiles.Nightmare) {
|
|
1999
|
+
return options.log.fail(.{ foe_ident, .None });
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
foe_volatiles.Nightmare = true;
|
|
2003
|
+
try options.log.start(.{ battle.active(player.foe()), .Nightmare });
|
|
1705
2004
|
}
|
|
1706
2005
|
|
|
1707
|
-
pub
|
|
1708
|
-
|
|
1709
|
-
|
|
2006
|
+
pub const ohko = struct {
|
|
2007
|
+
// OHKO calls adjustDamage but only actually cares about immunity
|
|
2008
|
+
pub fn immune(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2009
|
+
const move = Move.get(state.move);
|
|
1710
2010
|
|
|
1711
|
-
|
|
2011
|
+
assert(move.type == .Normal or move.type == .Ground);
|
|
2012
|
+
const foe = battle.foe(player);
|
|
2013
|
+
const zero = if (move.type == .Normal)
|
|
2014
|
+
foe.active.types.includes(.Ghost) and !foe.active.volatiles.Foresight
|
|
2015
|
+
else
|
|
2016
|
+
foe.active.types.includes(.Flying);
|
|
2017
|
+
state.effectiveness = if (zero) 0 else Effectiveness.neutral;
|
|
2018
|
+
}
|
|
1712
2019
|
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
if (delta == 0) return try options.log.immune(foe_ident, .OHKO);
|
|
2020
|
+
pub fn damage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2021
|
+
const side = battle.side(player);
|
|
2022
|
+
const foe = battle.foe(player);
|
|
1717
2023
|
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
2024
|
+
const foe_ident = battle.active(player.foe());
|
|
2025
|
+
|
|
2026
|
+
state.damage = 0;
|
|
2027
|
+
if (state.immune()) return options.log.immune(.{ foe_ident, .None });
|
|
2028
|
+
const delta = side.stored().level -| foe.stored().level;
|
|
2029
|
+
if (delta == 0) return options.log.immune(.{ foe_ident, .OHKO });
|
|
2030
|
+
|
|
2031
|
+
state.accuracy = 2 * delta +| Gen12.percent(30);
|
|
2032
|
+
try checkHit(battle, player, state, options);
|
|
2033
|
+
if (!state.miss) state.damage = 65535;
|
|
2034
|
+
state.ohko = true;
|
|
2035
|
+
}
|
|
2036
|
+
};
|
|
1723
2037
|
|
|
1724
2038
|
pub fn painSplit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1725
|
-
|
|
2039
|
+
var side = battle.side(player);
|
|
2040
|
+
var foe = battle.foe(player);
|
|
2041
|
+
|
|
2042
|
+
const ident = battle.active(player);
|
|
2043
|
+
const foe_ident = battle.active(player.foe());
|
|
2044
|
+
|
|
2045
|
+
if (state.miss) return options.log.fail(.{ foe_ident, .None });
|
|
2046
|
+
if (foe.active.volatiles.Substitute) {
|
|
2047
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
const avg = @max((foe.stored().hp + side.stored().hp) / 2, 1);
|
|
2051
|
+
foe.stored().hp -= avg;
|
|
2052
|
+
side.stored().hp = avg;
|
|
2053
|
+
|
|
2054
|
+
try options.log.sethp(.{ foe_ident, foe.stored(), .Silent });
|
|
2055
|
+
try options.log.sethp(.{ ident, side.stored(), .None });
|
|
1726
2056
|
}
|
|
1727
2057
|
|
|
1728
2058
|
pub fn paralyze(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1729
|
-
|
|
2059
|
+
var foe = battle.foe(player);
|
|
2060
|
+
const foe_ident = battle.active(player.foe());
|
|
2061
|
+
|
|
2062
|
+
if (Status.is(foe.stored().status, .PAR)) {
|
|
2063
|
+
return options.log.fail(.{ foe_ident, .Paralysis });
|
|
2064
|
+
}
|
|
2065
|
+
if (state.immune()) return options.log.immune(.{ foe_ident, .None });
|
|
2066
|
+
if (Status.any(foe.stored().status)) return options.log.fail(.{ foe_ident, .None });
|
|
2067
|
+
if (state.miss) {
|
|
2068
|
+
try options.log.lastmiss(.{});
|
|
2069
|
+
return options.log.miss(.{battle.active(player)});
|
|
2070
|
+
} else if (foe.active.volatiles.Substitute) {
|
|
2071
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
foe.stored().status = Status.init(.PAR);
|
|
2075
|
+
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2076
|
+
|
|
2077
|
+
try options.log.status(.{ foe_ident, foe.stored().status, .None });
|
|
1730
2078
|
}
|
|
1731
2079
|
|
|
1732
2080
|
pub fn paralyzeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1741,17 +2089,34 @@ pub const Effects = struct {
|
|
|
1741
2089
|
|
|
1742
2090
|
foe_stored.status = Status.init(.PAR);
|
|
1743
2091
|
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
1744
|
-
|
|
2092
|
+
|
|
2093
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1745
2094
|
|
|
1746
2095
|
// TODO: check for status berry
|
|
1747
2096
|
}
|
|
1748
2097
|
|
|
1749
|
-
pub fn payDay(battle: anytype, player: Player,
|
|
1750
|
-
|
|
2098
|
+
pub fn payDay(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2099
|
+
if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
|
|
2100
|
+
try options.log.fieldactivate(.{}); // FIXME .PayDay
|
|
2101
|
+
}
|
|
1751
2102
|
}
|
|
1752
2103
|
|
|
1753
|
-
pub fn perishSong(battle: anytype, player: Player,
|
|
1754
|
-
|
|
2104
|
+
pub fn perishSong(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2105
|
+
var side = battle.side(player);
|
|
2106
|
+
var foe = battle.foe(player);
|
|
2107
|
+
|
|
2108
|
+
if (foe.active.volatiles.PerishSong) {
|
|
2109
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2110
|
+
} else if (!side.active.volatiles.PerishSong) {
|
|
2111
|
+
side.active.volatiles.PerishSong = true;
|
|
2112
|
+
side.active.volatiles.perish_song = 4;
|
|
2113
|
+
try options.log.start(.{ battle.active(player), .PerishSong3Silent });
|
|
2114
|
+
}
|
|
2115
|
+
foe.active.volatiles.PerishSong = true;
|
|
2116
|
+
foe.active.volatiles.perish_song = 4;
|
|
2117
|
+
try options.log.start(.{ battle.active(player.foe()), .PerishSong3Silent });
|
|
2118
|
+
|
|
2119
|
+
try options.log.fieldactivate(.{}); // FIXME .PerishSong
|
|
1755
2120
|
}
|
|
1756
2121
|
|
|
1757
2122
|
pub fn poison(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1761,24 +2126,24 @@ pub const Effects = struct {
|
|
|
1761
2126
|
|
|
1762
2127
|
const toxic = state.move == .Toxic;
|
|
1763
2128
|
if (state.immune() or foe.active.types.includes(.Poison)) {
|
|
1764
|
-
return options.log.immune(foe_ident, .None);
|
|
2129
|
+
return options.log.immune(.{ foe_ident, .None });
|
|
1765
2130
|
} else if (Status.any(foe_stored.status)) {
|
|
1766
2131
|
// Pokémon Showdown considers Toxic to be a status even in Generation II and so
|
|
1767
2132
|
// will not include a fail reason for Toxic vs. Poison or vice-versa...
|
|
1768
|
-
return options.log.fail(foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2133
|
+
return options.log.fail(.{ foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
1769
2134
|
if (!showdown)
|
|
1770
|
-
.Poison
|
|
2135
|
+
Fail.Poison
|
|
1771
2136
|
else if (toxic == (foe_stored.status == Status.TOX))
|
|
1772
|
-
if (toxic) .Toxic else .Poison
|
|
2137
|
+
if (toxic) Fail.Toxic else Fail.Poison
|
|
1773
2138
|
else
|
|
1774
|
-
.None
|
|
2139
|
+
Fail.None
|
|
1775
2140
|
else
|
|
1776
|
-
.None);
|
|
2141
|
+
Fail.None });
|
|
1777
2142
|
} else if (foe.active.volatiles.Substitute) {
|
|
1778
|
-
return options.log.
|
|
2143
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1779
2144
|
} else if (state.miss) {
|
|
1780
|
-
try options.log.lastmiss();
|
|
1781
|
-
|
|
2145
|
+
try options.log.lastmiss(.{});
|
|
2146
|
+
return options.log.miss(.{battle.active(player)});
|
|
1782
2147
|
}
|
|
1783
2148
|
|
|
1784
2149
|
foe_stored.status = Status.init(.PSN);
|
|
@@ -1788,7 +2153,7 @@ pub const Effects = struct {
|
|
|
1788
2153
|
foe.active.volatiles.toxic = 0;
|
|
1789
2154
|
}
|
|
1790
2155
|
|
|
1791
|
-
try options.log.status(foe_ident, foe_stored.status, .None);
|
|
2156
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .None });
|
|
1792
2157
|
|
|
1793
2158
|
// TODO: check for status berry
|
|
1794
2159
|
}
|
|
@@ -1804,7 +2169,8 @@ pub const Effects = struct {
|
|
|
1804
2169
|
if (foe.conditions.Safeguard) return;
|
|
1805
2170
|
|
|
1806
2171
|
foe_stored.status = Status.init(.PSN);
|
|
1807
|
-
|
|
2172
|
+
|
|
2173
|
+
try options.log.status(.{ battle.active(player.foe()), foe_stored.status, .None });
|
|
1808
2174
|
|
|
1809
2175
|
// TODO: check for status berry
|
|
1810
2176
|
}
|
|
@@ -1819,10 +2185,7 @@ pub const Effects = struct {
|
|
|
1819
2185
|
|
|
1820
2186
|
const failed = !state.first or volatiles.Substitute or
|
|
1821
2187
|
!try Rolls.protect(battle, player, volatiles.protect, options);
|
|
1822
|
-
if (failed) {
|
|
1823
|
-
try options.log.fail(battle.active(player), .None);
|
|
1824
|
-
return;
|
|
1825
|
-
}
|
|
2188
|
+
if (failed) return options.log.fail(.{ battle.active(player), .None });
|
|
1826
2189
|
|
|
1827
2190
|
volatiles.protect = @max(8, volatiles.protect + 1);
|
|
1828
2191
|
if (state.move == .Endure) {
|
|
@@ -1830,36 +2193,76 @@ pub const Effects = struct {
|
|
|
1830
2193
|
} else {
|
|
1831
2194
|
volatiles.Protect = true;
|
|
1832
2195
|
}
|
|
1833
|
-
try options.log.singleturn(battle.active(player), state.move);
|
|
2196
|
+
try options.log.singleturn(.{ battle.active(player), state.move });
|
|
1834
2197
|
}
|
|
1835
2198
|
|
|
1836
|
-
pub fn psychUp(battle: anytype, player: Player,
|
|
1837
|
-
|
|
1838
|
-
|
|
2199
|
+
pub fn psychUp(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2200
|
+
var side = battle.side(player);
|
|
2201
|
+
const foe = battle.foe(player);
|
|
1839
2202
|
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
2203
|
+
// Pokémon Showdown should fail when the enemy has no boosts/unboosts but doesn't
|
|
2204
|
+
if (!showdown and @as(u32, @bitCast(foe.active.boosts)) == 0) {
|
|
2205
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
2206
|
+
}
|
|
1843
2207
|
|
|
1844
|
-
|
|
1845
|
-
battle.
|
|
1846
|
-
try options.log.singlemove(battle.active(player), Move.Rage);
|
|
2208
|
+
side.active.boosts = foe.active.boosts;
|
|
2209
|
+
try options.log.copyboost(.{ battle.active(player), battle.active(player.foe()) });
|
|
1847
2210
|
}
|
|
1848
2211
|
|
|
1849
|
-
pub fn
|
|
1850
|
-
|
|
2212
|
+
pub fn pursuit(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2213
|
+
// TODO: remove switching variable and just look at Choice saved in Battle struct?
|
|
2214
|
+
if (battle.foe(player).active.volatiles.switching) state.damage *|= 2;
|
|
1851
2215
|
}
|
|
1852
2216
|
|
|
1853
|
-
pub
|
|
1854
|
-
|
|
2217
|
+
pub const rage = struct {
|
|
2218
|
+
pub fn start(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2219
|
+
battle.side(player).active.volatiles.Rage = true;
|
|
2220
|
+
try options.log.singlemove(.{ battle.active(player), Move.Rage });
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
pub fn damage(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2224
|
+
const side = battle.side(player);
|
|
2225
|
+
|
|
2226
|
+
assert(side.active.volatiles.Rage);
|
|
2227
|
+
state.damage *|= (side.active.volatiles.rage +| 1);
|
|
2228
|
+
}
|
|
2229
|
+
};
|
|
2230
|
+
|
|
2231
|
+
pub fn rainDance(battle: anytype, _: Player, _: *State, options: anytype) !void {
|
|
2232
|
+
return weather(battle, .Rain, options);
|
|
1855
2233
|
}
|
|
1856
2234
|
|
|
1857
|
-
pub fn
|
|
1858
|
-
|
|
2235
|
+
pub fn rapidSpin(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2236
|
+
var side = battle.side(player);
|
|
2237
|
+
|
|
2238
|
+
if (side.active.volatiles.LeechSeed) {
|
|
2239
|
+
try options.log.end(.{ battle.active(player), .LeechSeedFrom });
|
|
2240
|
+
side.active.volatiles.LeechSeed = false;
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
if (side.conditions.Spikes) {
|
|
2244
|
+
try options.log.sideend(.{ player, protocol.Side.Spikes, battle.active(player) });
|
|
2245
|
+
side.conditions.Spikes = false;
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
if (side.active.volatiles.bind.duration > 0) {
|
|
2249
|
+
const reason = @intFromEnum(End.Bind) + side.active.volatiles.bind.reason - 1;
|
|
2250
|
+
try options.log.end(.{ battle.active(player), @as(End, @enumFromInt(reason)) });
|
|
2251
|
+
side.active.volatiles.bind = .{};
|
|
2252
|
+
}
|
|
1859
2253
|
}
|
|
1860
2254
|
|
|
1861
2255
|
pub fn recoil(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1862
|
-
|
|
2256
|
+
var side = battle.side(player);
|
|
2257
|
+
var stored = side.stored();
|
|
2258
|
+
const ident = battle.active(player);
|
|
2259
|
+
|
|
2260
|
+
assert(state.damage > 0);
|
|
2261
|
+
|
|
2262
|
+
const damage: i16 = @intCast(@max(state.damage / 4, 1));
|
|
2263
|
+
stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
|
|
2264
|
+
|
|
2265
|
+
try options.log.damage(.{ ident, stored, .RecoilOf, battle.active(player.foe()) });
|
|
1863
2266
|
}
|
|
1864
2267
|
|
|
1865
2268
|
pub fn reversal(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1887,36 +2290,86 @@ pub const Effects = struct {
|
|
|
1887
2290
|
try calcDamage(battle, player, state, options);
|
|
1888
2291
|
}
|
|
1889
2292
|
|
|
1890
|
-
pub
|
|
1891
|
-
|
|
1892
|
-
|
|
2293
|
+
pub const rollout = struct {
|
|
2294
|
+
pub fn check(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
2295
|
+
var side = battle.side(player);
|
|
1893
2296
|
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
2297
|
+
if (!side.active.volatiles.Rollout) {
|
|
2298
|
+
side.active.volatiles.attacks = 0;
|
|
2299
|
+
} else {
|
|
2300
|
+
// TODO SkipToBattleCommand(doturn_command)
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
1897
2303
|
|
|
1898
|
-
|
|
1899
|
-
|
|
2304
|
+
pub fn power(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2305
|
+
var side = battle.side(player);
|
|
2306
|
+
|
|
2307
|
+
if (Status.is(side.stored().status, .SLP)) return;
|
|
2308
|
+
|
|
2309
|
+
if (side.active.volatiles.attacks > 0) {
|
|
2310
|
+
// TODO set wSomeoneIsRampaging
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
if (state.miss) {
|
|
2314
|
+
side.active.volatiles.Rollout = false;
|
|
2315
|
+
return;
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
assert(side.active.volatiles.attacks < 5);
|
|
2319
|
+
side.active.volatiles.attacks += 1;
|
|
2320
|
+
side.active.volatiles.Rollout = side.active.volatiles.attacks != 5;
|
|
2321
|
+
|
|
2322
|
+
if (side.active.volatiles.DefenseCurl) state.damage *|= 2;
|
|
2323
|
+
}
|
|
2324
|
+
};
|
|
2325
|
+
|
|
2326
|
+
pub fn sandstorm(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2327
|
+
if (battle.field.weather != .Sandstorm) return weather(battle, .Sandstorm, options);
|
|
2328
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
1900
2329
|
}
|
|
1901
2330
|
|
|
1902
|
-
pub fn
|
|
1903
|
-
|
|
2331
|
+
pub fn safeguard(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2332
|
+
return screens(battle, player, "Safeguard", "safeguard", options);
|
|
1904
2333
|
}
|
|
1905
2334
|
|
|
1906
|
-
pub fn
|
|
1907
|
-
|
|
2335
|
+
pub fn reflect(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2336
|
+
return screens(battle, player, "Reflect", "reflect", options);
|
|
1908
2337
|
}
|
|
1909
2338
|
|
|
1910
|
-
pub fn
|
|
1911
|
-
|
|
2339
|
+
pub fn lightScreen(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2340
|
+
return screens(battle, player, "LightScreen", "light_screen", options);
|
|
1912
2341
|
}
|
|
1913
2342
|
|
|
1914
|
-
|
|
1915
|
-
|
|
2343
|
+
fn screens(
|
|
2344
|
+
battle: anytype,
|
|
2345
|
+
player: Player,
|
|
2346
|
+
comptime key: []const u8,
|
|
2347
|
+
comptime value: []const u8,
|
|
2348
|
+
options: anytype,
|
|
2349
|
+
) !void {
|
|
2350
|
+
var side = battle.side(player);
|
|
2351
|
+
|
|
2352
|
+
if (@field(side.conditions, key)) {
|
|
2353
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
@field(side.conditions, key) = true;
|
|
2357
|
+
@field(side.conditions, value) = 5;
|
|
2358
|
+
try options.log.sidestart(.{ player, @field(protocol.Side, key) });
|
|
1916
2359
|
}
|
|
1917
2360
|
|
|
1918
|
-
pub fn
|
|
1919
|
-
|
|
2361
|
+
pub fn sketch(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2362
|
+
var side = battle.side(player);
|
|
2363
|
+
|
|
2364
|
+
side.last_used_move = .None;
|
|
2365
|
+
side.active.volatiles.dirty = false;
|
|
2366
|
+
|
|
2367
|
+
if (showdown) {
|
|
2368
|
+
// yes, actually "Splash". '|nothing' gets blindly mapped to '|-activate||move:Splash'
|
|
2369
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
2370
|
+
} else {
|
|
2371
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2372
|
+
}
|
|
1920
2373
|
}
|
|
1921
2374
|
|
|
1922
2375
|
pub fn sleep(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1925,14 +2378,14 @@ pub const Effects = struct {
|
|
|
1925
2378
|
const foe_ident = battle.active(player.foe());
|
|
1926
2379
|
|
|
1927
2380
|
if (Status.any(foe_stored.status)) {
|
|
1928
|
-
return options.log.fail(
|
|
2381
|
+
return options.log.fail(.{
|
|
1929
2382
|
foe_ident,
|
|
1930
|
-
if (Status.is(foe_stored.status, .SLP)) .Sleep else .None,
|
|
1931
|
-
);
|
|
2383
|
+
if (Status.is(foe_stored.status, .SLP)) Fail.Sleep else Fail.None,
|
|
2384
|
+
});
|
|
1932
2385
|
}
|
|
1933
2386
|
|
|
1934
2387
|
if (foe.active.volatiles.Substitute) {
|
|
1935
|
-
return options.log.
|
|
2388
|
+
return options.log.activate(.{ foe_ident, .SubstituteBlock, state.move });
|
|
1936
2389
|
}
|
|
1937
2390
|
|
|
1938
2391
|
// Sleep Clause Mod
|
|
@@ -1943,79 +2396,182 @@ pub const Effects = struct {
|
|
|
1943
2396
|
}
|
|
1944
2397
|
|
|
1945
2398
|
foe_stored.status = Status.slp(Rolls.sleepDuration(battle, player, options));
|
|
1946
|
-
try options.log.
|
|
2399
|
+
try options.log.status(.{ foe_ident, foe_stored.status, .From, state.move });
|
|
1947
2400
|
}
|
|
1948
2401
|
|
|
1949
2402
|
pub fn sleepTalk(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1950
2403
|
const side = battle.side(player);
|
|
1951
2404
|
|
|
1952
2405
|
if (!Status.is(side.stored().status, .SLP)) {
|
|
1953
|
-
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
1954
|
-
return;
|
|
2406
|
+
return if (!showdown) try options.log.fail(.{ battle.active(player), .None });
|
|
1955
2407
|
}
|
|
1956
2408
|
|
|
1957
2409
|
var n: u3 = 0;
|
|
1958
2410
|
for (side.active.moves, 0..) |m, i| {
|
|
1959
2411
|
if (state.mslot - 1 != i and Move.get(m.id).extra.sleep_talk) n += 1;
|
|
1960
2412
|
}
|
|
1961
|
-
if (n == 0) {
|
|
1962
|
-
try options.log.fail(battle.active(player), .None);
|
|
1963
|
-
return;
|
|
1964
|
-
}
|
|
2413
|
+
if (n == 0) return options.log.fail(.{ battle.active(player), .None });
|
|
1965
2414
|
|
|
1966
2415
|
_ = try Rolls.sleepTalk(battle, player, n, state.mslot, options); // TODO
|
|
1967
2416
|
}
|
|
1968
2417
|
|
|
1969
|
-
pub fn snore(battle: anytype, player: Player, state: *State, options: anytype) !
|
|
1970
|
-
|
|
2418
|
+
pub fn snore(battle: anytype, player: Player, state: *State, options: anytype) !bool {
|
|
2419
|
+
if (Status.is(battle.side(player).stored().status, .SLP)) return true;
|
|
2420
|
+
state.damage = 0;
|
|
2421
|
+
state.miss = true; // TODO is this necessary?
|
|
2422
|
+
if (!showdown) try options.log.fail(.{ battle.active(player), .None });
|
|
2423
|
+
return false;
|
|
1971
2424
|
}
|
|
1972
2425
|
|
|
1973
|
-
pub fn spikes(battle: anytype, player: Player,
|
|
1974
|
-
|
|
2426
|
+
pub fn spikes(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2427
|
+
var foe = battle.foe(player);
|
|
2428
|
+
if (foe.conditions.Spikes) return options.log.fail(.{ battle.active(player), .None });
|
|
2429
|
+
|
|
2430
|
+
foe.conditions.Spikes = true;
|
|
2431
|
+
try options.log.sidestart(.{ player.foe(), protocol.Side.Spikes });
|
|
1975
2432
|
}
|
|
1976
2433
|
|
|
1977
2434
|
pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1978
|
-
|
|
1979
|
-
}
|
|
2435
|
+
var foe = battle.foe(player);
|
|
1980
2436
|
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
2437
|
+
const last = foe.lastMove(false);
|
|
2438
|
+
if (state.miss or last == .None or last == .Struggle) {
|
|
2439
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2440
|
+
}
|
|
1984
2441
|
|
|
1985
|
-
|
|
1986
|
-
|
|
2442
|
+
var slot: u4 = 0;
|
|
2443
|
+
for (foe.active.moves, 0..) |m, i| {
|
|
2444
|
+
if (m.id == last) slot = @intCast(i + 1);
|
|
2445
|
+
}
|
|
2446
|
+
// If the move was called via Metronome it might not exist
|
|
2447
|
+
if (slot == 0 or foe.active.move(slot).pp == 0) {
|
|
2448
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
// Pokémon Showdown always deducts 4 instead of randomizing
|
|
2452
|
+
const pp = @min(
|
|
2453
|
+
foe.active.move(slot).pp,
|
|
2454
|
+
if (showdown) 4 else try Rolls.spite(battle, player, options),
|
|
2455
|
+
);
|
|
2456
|
+
foe.active.move(slot).pp -= pp;
|
|
2457
|
+
if (!foe.active.volatiles.Transform) {
|
|
2458
|
+
var move_slot = foe.stored().move(slot);
|
|
2459
|
+
assert(move_slot.pp > 0);
|
|
2460
|
+
|
|
2461
|
+
move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
|
|
2462
|
+
assert(foe.active.move(slot).pp == foe.stored().move(slot).pp);
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
try options.log.activate(.{ battle.active(player.foe()), .Spite, last, pp });
|
|
1987
2466
|
}
|
|
1988
2467
|
|
|
1989
|
-
pub fn
|
|
1990
|
-
|
|
2468
|
+
pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2469
|
+
try options.log.activate(.{ battle.active(player), .Splash });
|
|
1991
2470
|
}
|
|
1992
2471
|
|
|
1993
|
-
pub fn
|
|
1994
|
-
|
|
2472
|
+
pub fn substitute(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2473
|
+
var side = battle.side(player);
|
|
2474
|
+
if (side.active.volatiles.Substitute) {
|
|
2475
|
+
return options.log.fail(.{ battle.active(player), .Substitute });
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
assert(side.stored().stats.hp <= 1023);
|
|
2479
|
+
const hp: u8 = @intCast(side.stored().stats.hp / 4);
|
|
2480
|
+
|
|
2481
|
+
if (hp == 0 or side.stored().hp <= hp) {
|
|
2482
|
+
return options.log.fail(.{ battle.active(player), .Weak });
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2485
|
+
side.stored().hp -= hp;
|
|
2486
|
+
side.active.volatiles.substitute = hp;
|
|
2487
|
+
side.active.volatiles.Substitute = true;
|
|
2488
|
+
side.active.volatiles.bind = .{};
|
|
2489
|
+
try options.log.start(.{ battle.active(player), .Substitute });
|
|
2490
|
+
try options.log.damage(.{ battle.active(player), side.stored(), .None });
|
|
1995
2491
|
}
|
|
1996
2492
|
|
|
1997
|
-
pub fn
|
|
1998
|
-
|
|
2493
|
+
pub fn sunnyDay(battle: anytype, _: Player, _: *State, options: anytype) !void {
|
|
2494
|
+
return weather(battle, .Sun, options);
|
|
1999
2495
|
}
|
|
2000
2496
|
|
|
2001
|
-
|
|
2002
|
-
|
|
2497
|
+
fn weather(battle: anytype, w: Weather, options: anytype) !void {
|
|
2498
|
+
battle.field.weather = w;
|
|
2499
|
+
battle.field.weather_duration = 5;
|
|
2500
|
+
try options.log.weather(.{ w, protocol.Weather.None });
|
|
2003
2501
|
}
|
|
2004
2502
|
|
|
2005
|
-
pub fn teleport(battle: anytype, player: Player,
|
|
2006
|
-
|
|
2503
|
+
pub fn teleport(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2504
|
+
if (showdown) return;
|
|
2505
|
+
|
|
2506
|
+
try options.log.fail(.{ battle.active(player), .None });
|
|
2507
|
+
try options.log.laststill(.{});
|
|
2007
2508
|
}
|
|
2008
2509
|
|
|
2009
2510
|
pub fn thief(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2010
|
-
|
|
2511
|
+
const side = battle.side(player);
|
|
2512
|
+
const foe = battle.foe(player);
|
|
2513
|
+
|
|
2514
|
+
if (side.stored().item != .None or foe.stored().item == .None) return;
|
|
2515
|
+
// Pokémon Showdown incorrectly allows you steal Mail
|
|
2516
|
+
if (!showdown and foe.stored().item.isMail()) return;
|
|
2517
|
+
if (!state.proc) return;
|
|
2518
|
+
|
|
2519
|
+
side.stored().item = foe.stored().item;
|
|
2520
|
+
foe.stored().item = .None;
|
|
2521
|
+
|
|
2522
|
+
try options.log.item(.{
|
|
2523
|
+
battle.active(player),
|
|
2524
|
+
side.stored().item,
|
|
2525
|
+
battle.active(player.foe()),
|
|
2526
|
+
});
|
|
2011
2527
|
}
|
|
2012
2528
|
|
|
2013
|
-
pub fn thrashing(battle: anytype, player: Player,
|
|
2014
|
-
|
|
2529
|
+
pub fn thrashing(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2530
|
+
var side = battle.side(player);
|
|
2531
|
+
if (Status.is(side.stored().status, .SLP)) return;
|
|
2532
|
+
|
|
2533
|
+
assert(!side.active.volatiles.Thrashing);
|
|
2534
|
+
side.active.volatiles.Thrashing = true;
|
|
2535
|
+
side.active.volatiles.attacks = Rolls.attackingDuration(battle, player, options);
|
|
2536
|
+
// TODO wSomeoneIsRampaging = true;
|
|
2015
2537
|
}
|
|
2016
2538
|
|
|
2017
|
-
pub fn transform(battle: anytype, player: Player,
|
|
2018
|
-
|
|
2539
|
+
pub fn transform(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2540
|
+
var side = battle.side(player);
|
|
2541
|
+
const foe = battle.foe(player);
|
|
2542
|
+
const foe_ident = battle.active(player.foe());
|
|
2543
|
+
|
|
2544
|
+
side.last_used_move = .None;
|
|
2545
|
+
side.active.volatiles.dirty = false;
|
|
2546
|
+
|
|
2547
|
+
if (foe.active.volatiles.Transform) {
|
|
2548
|
+
return options.log.fail(.{ battle.active(player), .None });
|
|
2549
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2550
|
+
if (!showdown) return options.log.fail(.{ battle.active(player), .None });
|
|
2551
|
+
try options.log.lastmiss(.{});
|
|
2552
|
+
return options.log.miss(.{battle.active(player)});
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
side.active.volatiles.Transform = true;
|
|
2556
|
+
side.active.volatiles.transform = foe_ident.int();
|
|
2557
|
+
side.active.volatiles.disable = .{};
|
|
2558
|
+
|
|
2559
|
+
// HP is not copied by Transform
|
|
2560
|
+
side.active.stats.atk = foe.active.stats.atk;
|
|
2561
|
+
side.active.stats.def = foe.active.stats.def;
|
|
2562
|
+
side.active.stats.spe = foe.active.stats.spe;
|
|
2563
|
+
side.active.stats.spa = foe.active.stats.spa;
|
|
2564
|
+
side.active.stats.spd = foe.active.stats.spd;
|
|
2565
|
+
|
|
2566
|
+
side.active.species = foe.active.species;
|
|
2567
|
+
side.active.types = foe.active.types;
|
|
2568
|
+
side.active.boosts = foe.active.boosts;
|
|
2569
|
+
for (foe.active.moves, 0..) |m, i| {
|
|
2570
|
+
side.active.moves[i].id = m.id;
|
|
2571
|
+
side.active.moves[i].pp = if (m.id != .None) if (m.id == .Sketch) 1 else 5 else 0;
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
try options.log.transform(.{ battle.active(player), foe_ident });
|
|
2019
2575
|
}
|
|
2020
2576
|
|
|
2021
2577
|
pub fn triAttack(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2027,42 +2583,32 @@ pub const Effects = struct {
|
|
|
2027
2583
|
.FRZ => freezeChance(battle, player, state, options),
|
|
2028
2584
|
.BRN => burnChance(battle, player, state, options),
|
|
2029
2585
|
};
|
|
2030
|
-
} else if (!showdown
|
|
2586
|
+
} else if (!showdown) battle.rng.advance(1);
|
|
2031
2587
|
}
|
|
2032
2588
|
|
|
2033
2589
|
pub fn tripleKick(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2034
2590
|
_ = .{ battle, player, state, options }; // TODO
|
|
2035
2591
|
}
|
|
2036
2592
|
|
|
2037
|
-
|
|
2038
|
-
_ = .{ battle, player, state, options }; // TODO
|
|
2039
|
-
}
|
|
2040
|
-
|
|
2041
|
-
const ALL_STAT_UP_STATS = [_][]const u8{ "atk", "def", "spe", "spa", "spd" };
|
|
2042
|
-
const ALL_STAT_UP_REASONS =
|
|
2593
|
+
const STAT_UP_REASONS =
|
|
2043
2594
|
[_]Boost{ .Attack, .Defense, .Speed, .SpecialAttack, .SpecialDefense };
|
|
2044
2595
|
|
|
2045
2596
|
pub fn allStatUpChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2046
2597
|
var side = battle.side(player);
|
|
2047
2598
|
|
|
2048
|
-
|
|
2049
|
-
|
|
2599
|
+
const stats = &side.active.stats;
|
|
2600
|
+
const boosts = &side.active.boosts;
|
|
2050
2601
|
|
|
2051
2602
|
if (!state.proc) return;
|
|
2052
|
-
inline for (
|
|
2603
|
+
inline for (STATS, STAT_UP_REASONS) |s, r| {
|
|
2053
2604
|
assert(@field(boosts, s) >= -6 and @field(boosts, s) <= 6);
|
|
2054
2605
|
if (@field(boosts, s) == 6 or @field(stats, s) == MAX_STAT_VALUE) {
|
|
2055
|
-
try options.log.boost(battle.active(player), r, 0);
|
|
2606
|
+
try options.log.boost(.{ battle.active(player), r, 0 });
|
|
2056
2607
|
} else {
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2060
|
-
@field(stats, s) = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2061
|
-
try options.log.boost(battle.active(player), r, 1);
|
|
2608
|
+
try boostStat(battle, side, stats, boosts, s, 1);
|
|
2609
|
+
try options.log.boost(.{ battle.active(player), r, 1 });
|
|
2062
2610
|
}
|
|
2063
2611
|
}
|
|
2064
|
-
|
|
2065
|
-
statusModify(side.stored().status, stats);
|
|
2066
2612
|
}
|
|
2067
2613
|
|
|
2068
2614
|
pub fn boost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2070,84 +2616,65 @@ pub const Effects = struct {
|
|
|
2070
2616
|
var side = battle.side(player);
|
|
2071
2617
|
const ident = battle.active(player);
|
|
2072
2618
|
|
|
2073
|
-
if (state.miss) return
|
|
2619
|
+
if (state.miss) return log.fail(.{ ident, .None });
|
|
2074
2620
|
|
|
2075
|
-
|
|
2621
|
+
const stats = &side.active.stats;
|
|
2076
2622
|
var boosts = &side.active.boosts;
|
|
2077
2623
|
|
|
2078
2624
|
const move = Move.get(state.move);
|
|
2079
2625
|
switch (move.effect) {
|
|
2080
|
-
.AttackUp1, .AttackUp2, .Rage => {
|
|
2626
|
+
.AttackUp1, .AttackUp2, .Rage, .Swagger => {
|
|
2081
2627
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2082
2628
|
if (boosts.atk == 6 or stats.atk == MAX_STAT_VALUE) {
|
|
2083
|
-
return
|
|
2629
|
+
return log.fail(.{ ident, .None });
|
|
2084
2630
|
}
|
|
2085
|
-
const n: u2 = if (move.effect == .AttackUp2) 2 else 1;
|
|
2086
|
-
|
|
2631
|
+
const n: u2 = if (move.effect == .AttackUp2 or move.effect == .Swagger) 2 else 1;
|
|
2632
|
+
try boostStat(battle, side, stats, boosts, "atk", n);
|
|
2087
2633
|
const reason = if (showdown and move.effect == .Rage) Boost.Rage else Boost.Attack;
|
|
2088
|
-
|
|
2089
|
-
const stat = unmodifiedStats(battle, side).atk;
|
|
2090
|
-
stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2091
|
-
try log.boost(ident, reason, n);
|
|
2634
|
+
try log.boost(.{ ident, reason, n });
|
|
2092
2635
|
},
|
|
2093
|
-
.DefenseUp1, .DefenseUp2 => {
|
|
2636
|
+
.DefenseUp1, .DefenseUp2, .SkullBash => {
|
|
2094
2637
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2095
2638
|
if (boosts.def == 6 or stats.def == MAX_STAT_VALUE) {
|
|
2096
|
-
return
|
|
2639
|
+
return log.fail(.{ ident, .None });
|
|
2097
2640
|
}
|
|
2098
2641
|
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
const stat = unmodifiedStats(battle, side).def;
|
|
2102
|
-
stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2103
|
-
try log.boost(ident, .Defense, n);
|
|
2642
|
+
try boostStat(battle, side, stats, boosts, "def", n);
|
|
2643
|
+
try log.boost(.{ ident, .Defense, n });
|
|
2104
2644
|
},
|
|
2105
2645
|
.SpeedUp2 => {
|
|
2106
2646
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2107
2647
|
if (boosts.spe == 6 or stats.spe == MAX_STAT_VALUE) {
|
|
2108
|
-
return
|
|
2648
|
+
return log.fail(.{ ident, .None });
|
|
2109
2649
|
}
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
const stat = unmodifiedStats(battle, side).spe;
|
|
2113
|
-
stats.spe = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2114
|
-
try log.boost(ident, .Speed, 2);
|
|
2650
|
+
try boostStat(battle, side, stats, boosts, "spe", 2);
|
|
2651
|
+
try log.boost(.{ ident, .Speed, 2 });
|
|
2115
2652
|
},
|
|
2116
2653
|
.SpAtkUp1 => {
|
|
2117
2654
|
assert(boosts.spa >= -6 and boosts.spa <= 6);
|
|
2118
2655
|
if (boosts.spa == 6 or stats.spa == MAX_STAT_VALUE) {
|
|
2119
|
-
return
|
|
2656
|
+
return log.fail(.{ ident, .None });
|
|
2120
2657
|
}
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
const stat = unmodifiedStats(battle, side).spa;
|
|
2124
|
-
stats.spa = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2125
|
-
try log.boost(ident, .SpecialAttack, 1);
|
|
2658
|
+
try boostStat(battle, side, stats, boosts, "spa", 1);
|
|
2659
|
+
try log.boost(.{ ident, .SpecialAttack, 1 });
|
|
2126
2660
|
},
|
|
2127
2661
|
.SpDefUp2 => {
|
|
2128
2662
|
assert(boosts.spd >= -6 and boosts.spd <= 6);
|
|
2129
2663
|
if (boosts.spd == 6 or stats.spd == MAX_STAT_VALUE) {
|
|
2130
|
-
return
|
|
2664
|
+
return log.fail(.{ ident, .None });
|
|
2131
2665
|
}
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
const stat = unmodifiedStats(battle, side).spd;
|
|
2135
|
-
stats.spd = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2136
|
-
try log.boost(ident, .SpecialDefense, 2);
|
|
2666
|
+
try boostStat(battle, side, stats, boosts, "spd", 2);
|
|
2667
|
+
try log.boost(.{ ident, .SpecialDefense, 2 });
|
|
2137
2668
|
},
|
|
2138
2669
|
.EvasionUp1 => {
|
|
2139
2670
|
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2140
|
-
if (boosts.evasion == 6) {
|
|
2141
|
-
return try log.fail(ident, .None);
|
|
2142
|
-
}
|
|
2671
|
+
if (boosts.evasion == 6) return log.fail(.{ ident, .None });
|
|
2143
2672
|
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2144
|
-
try log.boost(ident, .Evasion, 1);
|
|
2673
|
+
try log.boost(.{ ident, .Evasion, 1 });
|
|
2145
2674
|
if (state.move == .Minimize) side.active.volatiles.minimized = true;
|
|
2146
2675
|
},
|
|
2147
2676
|
else => unreachable,
|
|
2148
2677
|
}
|
|
2149
|
-
|
|
2150
|
-
statusModify(side.stored().status, stats);
|
|
2151
2678
|
}
|
|
2152
2679
|
|
|
2153
2680
|
pub fn unboost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2156,102 +2683,86 @@ pub const Effects = struct {
|
|
|
2156
2683
|
const foe_ident = battle.active(player.foe());
|
|
2157
2684
|
|
|
2158
2685
|
if (foe.active.volatiles.Mist) {
|
|
2159
|
-
try log.activate(foe_ident, .Mist);
|
|
2160
|
-
return
|
|
2686
|
+
try log.activate(.{ foe_ident, .Mist });
|
|
2687
|
+
return log.fail(.{ foe_ident, .None });
|
|
2161
2688
|
}
|
|
2162
2689
|
|
|
2163
2690
|
const fail = foe.active.volatiles.Substitute or state.miss or !state.proc;
|
|
2164
2691
|
|
|
2165
|
-
|
|
2692
|
+
const stats = &foe.active.stats;
|
|
2166
2693
|
var boosts = &foe.active.boosts;
|
|
2167
2694
|
|
|
2168
2695
|
const move = Move.get(state.move);
|
|
2696
|
+
const none: Fail = .None;
|
|
2169
2697
|
switch (move.effect) {
|
|
2170
2698
|
.AttackDown1, .AttackDown2, .AttackDownChance => {
|
|
2171
2699
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2172
2700
|
if (fail or boosts.atk == -6) {
|
|
2173
|
-
return if (move.effect != .AttackDownChance) try log.fail(foe_ident,
|
|
2701
|
+
return if (move.effect != .AttackDownChance) try log.fail(.{ foe_ident, none });
|
|
2174
2702
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2175
|
-
try options.log.lastmiss();
|
|
2176
|
-
|
|
2177
|
-
return;
|
|
2703
|
+
try options.log.lastmiss(.{});
|
|
2704
|
+
return options.log.miss(.{battle.active(player)});
|
|
2178
2705
|
} else if (stats.atk == 1) {
|
|
2179
|
-
return if (move.effect != .AttackDownChance) try log.fail(foe_ident,
|
|
2706
|
+
return if (move.effect != .AttackDownChance) try log.fail(.{ foe_ident, none });
|
|
2180
2707
|
}
|
|
2181
2708
|
const n: u2 = if (move.effect == .AttackDown2) 2 else 1;
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
const stat = unmodifiedStats(battle, foe).atk;
|
|
2185
|
-
stats.atk = @max(1, stat * mod[0] / mod[1]);
|
|
2186
|
-
try log.boost(foe_ident, .Attack, -@as(i8, n));
|
|
2709
|
+
try unboostStat(battle, foe, stats, boosts, "atk", n);
|
|
2710
|
+
try log.boost(.{ foe_ident, .Attack, -@as(i8, n) });
|
|
2187
2711
|
},
|
|
2188
2712
|
.DefenseDown1, .DefenseDown2, .DefenseDownChance => {
|
|
2189
2713
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2190
2714
|
if (fail or boosts.def == -6) {
|
|
2191
|
-
|
|
2192
|
-
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2193
|
-
try options.log.lastmiss();
|
|
2194
|
-
try options.log.miss(battle.active(player));
|
|
2715
|
+
if (move.effect != .DefenseDownChance) try log.fail(.{ foe_ident, none });
|
|
2195
2716
|
return;
|
|
2717
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2718
|
+
try options.log.lastmiss(.{});
|
|
2719
|
+
return options.log.miss(.{battle.active(player)});
|
|
2196
2720
|
} else if (stats.def == 1) {
|
|
2197
|
-
|
|
2721
|
+
if (move.effect != .DefenseDownChance) try log.fail(.{ foe_ident, none });
|
|
2722
|
+
return;
|
|
2198
2723
|
}
|
|
2199
2724
|
const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
const stat = unmodifiedStats(battle, foe).def;
|
|
2203
|
-
stats.def = @max(1, stat * mod[0] / mod[1]);
|
|
2204
|
-
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2725
|
+
try unboostStat(battle, foe, stats, boosts, "def", n);
|
|
2726
|
+
try log.boost(.{ foe_ident, .Defense, -@as(i8, n) });
|
|
2205
2727
|
},
|
|
2206
2728
|
.SpeedDown1, .SpeedDown2, .SpeedDownChance => {
|
|
2207
2729
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2208
2730
|
if (fail or boosts.spe == -6) {
|
|
2209
|
-
return if (move.effect != .SpeedDownChance) try log.fail(foe_ident,
|
|
2731
|
+
return if (move.effect != .SpeedDownChance) try log.fail(.{ foe_ident, none });
|
|
2210
2732
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2211
|
-
try options.log.lastmiss();
|
|
2212
|
-
|
|
2213
|
-
return;
|
|
2733
|
+
try options.log.lastmiss(.{});
|
|
2734
|
+
return options.log.miss(.{battle.active(player)});
|
|
2214
2735
|
} else if (stats.spe == 1) {
|
|
2215
|
-
return if (move.effect != .SpeedDownChance) try log.fail(foe_ident,
|
|
2736
|
+
return if (move.effect != .SpeedDownChance) try log.fail(.{ foe_ident, none });
|
|
2216
2737
|
}
|
|
2217
2738
|
const n: u2 = if (move.effect == .SpeedDown2) 2 else 1;
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
const stat = unmodifiedStats(battle, foe).spe;
|
|
2221
|
-
stats.spe = @max(1, stat * mod[0] / mod[1]);
|
|
2222
|
-
try log.boost(foe_ident, .Speed, -@as(i8, n));
|
|
2223
|
-
assert(boosts.spe >= -6);
|
|
2739
|
+
try unboostStat(battle, foe, stats, boosts, "spe", n);
|
|
2740
|
+
try log.boost(.{ foe_ident, .Speed, -@as(i8, n) });
|
|
2224
2741
|
},
|
|
2225
2742
|
.SpDefDownChance => {
|
|
2226
2743
|
assert(boosts.spd >= -6 and boosts.spd <= 6);
|
|
2227
2744
|
if (fail or boosts.spd == -6) return;
|
|
2228
2745
|
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2229
|
-
try options.log.lastmiss();
|
|
2230
|
-
|
|
2231
|
-
return;
|
|
2746
|
+
try options.log.lastmiss(.{});
|
|
2747
|
+
return options.log.miss(.{battle.active(player)});
|
|
2232
2748
|
} else if (stats.spd == 1) return;
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
const stat = unmodifiedStats(battle, foe).spd;
|
|
2236
|
-
stats.spd = @max(1, stat * mod[0] / mod[1]);
|
|
2237
|
-
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2749
|
+
try unboostStat(battle, foe, stats, boosts, "spd", 1);
|
|
2750
|
+
try log.boost(.{ foe_ident, .SpecialDefense, -1 });
|
|
2238
2751
|
},
|
|
2239
2752
|
.AccuracyDown1, .AccuracyDownChance => {
|
|
2240
2753
|
assert(boosts.accuracy >= -6 and boosts.accuracy <= 6);
|
|
2241
2754
|
if (fail or boosts.accuracy == -6) {
|
|
2242
|
-
|
|
2243
|
-
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2244
|
-
try options.log.lastmiss();
|
|
2245
|
-
try options.log.miss(battle.active(player));
|
|
2755
|
+
if (move.effect != .AccuracyDownChance) try log.fail(.{ foe_ident, none });
|
|
2246
2756
|
return;
|
|
2757
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2758
|
+
try options.log.lastmiss(.{});
|
|
2759
|
+
return options.log.miss(.{battle.active(player)});
|
|
2247
2760
|
}
|
|
2248
2761
|
boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
|
|
2249
|
-
try log.boost(foe_ident, .Accuracy, -1);
|
|
2762
|
+
try log.boost(.{ foe_ident, .Accuracy, -1 });
|
|
2250
2763
|
},
|
|
2251
2764
|
else => unreachable,
|
|
2252
2765
|
}
|
|
2253
|
-
|
|
2254
|
-
statusModify(foe.stored().status, stats);
|
|
2255
2766
|
}
|
|
2256
2767
|
};
|
|
2257
2768
|
|
|
@@ -2261,6 +2772,15 @@ fn unmodifiedStats(battle: anytype, side: *Side) *Stats(u16) {
|
|
|
2261
2772
|
return &battle.side(id.player).pokemon[id.id - 1].stats;
|
|
2262
2773
|
}
|
|
2263
2774
|
|
|
2775
|
+
fn recomputeStats(battle: anytype, side: *Side) void {
|
|
2776
|
+
const stats = unmodifiedStats(battle, side);
|
|
2777
|
+
inline for (STATS) |s| {
|
|
2778
|
+
const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(side.active.boosts, s)) + 6))];
|
|
2779
|
+
const val = @field(stats, s) * mod[0] / mod[1];
|
|
2780
|
+
@field(side.active.stats, s) = @max(1, @min(MAX_STAT_VALUE, val));
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2264
2784
|
fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
2265
2785
|
if (Status.is(status, .PAR)) {
|
|
2266
2786
|
stats.spe = @max(stats.spe / 4, 1);
|
|
@@ -2269,6 +2789,52 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
2269
2789
|
}
|
|
2270
2790
|
}
|
|
2271
2791
|
|
|
2792
|
+
fn boostStat(
|
|
2793
|
+
battle: anytype,
|
|
2794
|
+
side: *Side,
|
|
2795
|
+
stats: *Stats(u16),
|
|
2796
|
+
boosts: *Boosts,
|
|
2797
|
+
comptime s: []const u8,
|
|
2798
|
+
n: u2,
|
|
2799
|
+
) !void {
|
|
2800
|
+
@field(boosts, s) = @intCast(@min(6, @as(i8, @field(boosts, s)) + n));
|
|
2801
|
+
const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
|
|
2802
|
+
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2803
|
+
@field(stats, s) = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2804
|
+
if (comptime std.mem.eql(u8, s, "atk")) {
|
|
2805
|
+
if (Status.is(side.stored().status, .BRN)) {
|
|
2806
|
+
@field(stats, s) = @max(@field(stats, s) / 2, 1);
|
|
2807
|
+
}
|
|
2808
|
+
} else if (comptime std.mem.eql(u8, s, "spe")) {
|
|
2809
|
+
if (Status.is(side.stored().status, .PAR)) {
|
|
2810
|
+
@field(stats, s) = @max(@field(stats, s) / 4, 1);
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
fn unboostStat(
|
|
2816
|
+
battle: anytype,
|
|
2817
|
+
side: *Side,
|
|
2818
|
+
stats: *Stats(u16),
|
|
2819
|
+
boosts: *Boosts,
|
|
2820
|
+
comptime s: []const u8,
|
|
2821
|
+
n: u2,
|
|
2822
|
+
) !void {
|
|
2823
|
+
@field(boosts, s) = @intCast(@max(-6, @as(i8, @field(boosts, s)) - n));
|
|
2824
|
+
const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
|
|
2825
|
+
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2826
|
+
@field(stats, s) = @max(1, stat * mod[0] / mod[1]);
|
|
2827
|
+
if (comptime std.mem.eql(u8, s, "atk")) {
|
|
2828
|
+
if (Status.is(side.stored().status, .BRN)) {
|
|
2829
|
+
@field(stats, s) = @max(@field(stats, s) / 2, 1);
|
|
2830
|
+
}
|
|
2831
|
+
} else if (comptime std.mem.eql(u8, s, "spe")) {
|
|
2832
|
+
if (Status.is(side.stored().status, .PAR)) {
|
|
2833
|
+
@field(stats, s) = @max(@field(stats, s) / 4, 1);
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2272
2838
|
fn isForced(active: anytype) bool {
|
|
2273
2839
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
2274
2840
|
active.volatiles.Thrashing or active.volatiles.Charging or
|
|
@@ -2458,7 +3024,7 @@ pub const Rolls = struct {
|
|
|
2458
3024
|
}
|
|
2459
3025
|
|
|
2460
3026
|
fn magnitude(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2461
|
-
const num = if (options.calc.overridden(player, .magnitude)) |val|
|
|
3027
|
+
const num: u8 = if (options.calc.overridden(player, .magnitude)) |val|
|
|
2462
3028
|
val + 3
|
|
2463
3029
|
else if (showdown) num: {
|
|
2464
3030
|
const r = battle.rng.range(u8, 0, 100);
|
|
@@ -2502,13 +3068,12 @@ pub const Rolls = struct {
|
|
|
2502
3068
|
return hits;
|
|
2503
3069
|
}
|
|
2504
3070
|
|
|
2505
|
-
fn spite(battle: anytype, player: Player, options: anytype) !
|
|
2506
|
-
|
|
3071
|
+
fn spite(battle: anytype, player: Player, options: anytype) !u3 {
|
|
3072
|
+
assert(!showdown);
|
|
3073
|
+
const pp: u3 = if (options.calc.overridden(player, .spite)) |val|
|
|
2507
3074
|
val + 1
|
|
2508
|
-
else if (showdown)
|
|
2509
|
-
battle.rng.range(u8, 2, 6)
|
|
2510
3075
|
else
|
|
2511
|
-
(battle.rng.next() & 3) + 2;
|
|
3076
|
+
@intCast((battle.rng.next() & 3) + 2);
|
|
2512
3077
|
|
|
2513
3078
|
assert(pp >= 2 and pp <= 5);
|
|
2514
3079
|
try options.chance.spite(player, pp);
|
|
@@ -2717,12 +3282,12 @@ pub const Rolls = struct {
|
|
|
2717
3282
|
}
|
|
2718
3283
|
|
|
2719
3284
|
fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
2720
|
-
const moves = battle.
|
|
3285
|
+
const moves = battle.side(player).active.moves;
|
|
2721
3286
|
|
|
2722
3287
|
var n: u2 = 0;
|
|
2723
3288
|
for (moves) |m| {
|
|
2724
|
-
if (m == .None) break;
|
|
2725
|
-
if (Move.get(m).metronome) n += 1;
|
|
3289
|
+
if (m.id == .None) break;
|
|
3290
|
+
if (Move.get(m.id).extra.metronome) n += 1;
|
|
2726
3291
|
}
|
|
2727
3292
|
|
|
2728
3293
|
// TODO: consider throwing error instead of rerolling?
|
|
@@ -2733,10 +3298,11 @@ pub const Rolls = struct {
|
|
|
2733
3298
|
|
|
2734
3299
|
const move: Move = overridden orelse move: {
|
|
2735
3300
|
if (showdown) {
|
|
2736
|
-
var r = battle.rng.range(u8, 0, Move.METRONOME.len - n);
|
|
3301
|
+
var r = battle.rng.range(u8, 0, @as(u8, @intCast(Move.METRONOME.len - n)));
|
|
2737
3302
|
for (moves) |m| {
|
|
2738
|
-
if (m == .None
|
|
2739
|
-
if (
|
|
3303
|
+
if (m.id == .None) break;
|
|
3304
|
+
if (@intFromEnum(m.id) > @intFromEnum(Move.METRONOME[r])) break;
|
|
3305
|
+
if (Move.get(m.id).extra.metronome) r -= 1;
|
|
2740
3306
|
}
|
|
2741
3307
|
break :move Move.METRONOME[r];
|
|
2742
3308
|
} else {
|
|
@@ -2751,7 +3317,7 @@ pub const Rolls = struct {
|
|
|
2751
3317
|
}
|
|
2752
3318
|
};
|
|
2753
3319
|
|
|
2754
|
-
assert(Move.get(move).metronome);
|
|
3320
|
+
assert(Move.get(move).extra.metronome);
|
|
2755
3321
|
try options.chance.metronome(player, move, n);
|
|
2756
3322
|
return move;
|
|
2757
3323
|
}
|
|
@@ -2769,7 +3335,7 @@ pub const Rolls = struct {
|
|
|
2769
3335
|
};
|
|
2770
3336
|
|
|
2771
3337
|
assert(duration >= 1 and duration <= 6);
|
|
2772
|
-
options.chance.duration(
|
|
3338
|
+
options.chance.duration(player, duration);
|
|
2773
3339
|
return duration + 1;
|
|
2774
3340
|
}
|
|
2775
3341
|
|
|
@@ -2783,7 +3349,7 @@ pub const Rolls = struct {
|
|
|
2783
3349
|
@intCast((battle.rng.next() & if (self) 1 else 3) + 2);
|
|
2784
3350
|
|
|
2785
3351
|
assert(duration >= 2 and duration <= if (!showdown and self) 3 else 5);
|
|
2786
|
-
options.chance.duration(
|
|
3352
|
+
options.chance.duration(player, duration);
|
|
2787
3353
|
return duration;
|
|
2788
3354
|
}
|
|
2789
3355
|
|
|
@@ -2801,7 +3367,7 @@ pub const Rolls = struct {
|
|
|
2801
3367
|
};
|
|
2802
3368
|
|
|
2803
3369
|
assert(duration >= 2 and duration <= (if (showdown) 5 else 7));
|
|
2804
|
-
options.chance.duration(
|
|
3370
|
+
options.chance.duration(player, duration);
|
|
2805
3371
|
return duration;
|
|
2806
3372
|
}
|
|
2807
3373
|
|
|
@@ -2814,7 +3380,7 @@ pub const Rolls = struct {
|
|
|
2814
3380
|
@intCast((battle.rng.next() & 1) + 2); // BUG: thrash is + 1 not +2
|
|
2815
3381
|
|
|
2816
3382
|
assert(duration >= 2 and duration <= 3);
|
|
2817
|
-
options.chance.duration(
|
|
3383
|
+
options.chance.duration(player, duration);
|
|
2818
3384
|
return duration;
|
|
2819
3385
|
}
|
|
2820
3386
|
|
|
@@ -2828,7 +3394,7 @@ pub const Rolls = struct {
|
|
|
2828
3394
|
@intCast(battle.rng.next() & 3 + 2));
|
|
2829
3395
|
|
|
2830
3396
|
assert(duration >= 2 and duration <= 5);
|
|
2831
|
-
options.chance.duration(
|
|
3397
|
+
options.chance.duration(player, duration);
|
|
2832
3398
|
return duration + 1;
|
|
2833
3399
|
}
|
|
2834
3400
|
|
|
@@ -2842,7 +3408,7 @@ pub const Rolls = struct {
|
|
|
2842
3408
|
@intCast(battle.rng.next() & 3 + 2));
|
|
2843
3409
|
|
|
2844
3410
|
assert(duration >= 2 and duration <= 5);
|
|
2845
|
-
options.chance.duration(
|
|
3411
|
+
options.chance.duration(player, duration);
|
|
2846
3412
|
return duration + 1;
|
|
2847
3413
|
}
|
|
2848
3414
|
};
|
|
@@ -2955,7 +3521,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
2955
3521
|
.Move => {
|
|
2956
3522
|
const side = battle.side(player);
|
|
2957
3523
|
|
|
2958
|
-
|
|
3524
|
+
const active = &side.active;
|
|
2959
3525
|
const stored = side.stored();
|
|
2960
3526
|
|
|
2961
3527
|
// While players are not given any input options on the cartridge in these cases,
|
|
@@ -2975,7 +3541,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
2975
3541
|
}
|
|
2976
3542
|
|
|
2977
3543
|
var slot: u4 = 2;
|
|
2978
|
-
while (!active.volatiles.
|
|
3544
|
+
while (!active.volatiles.Trapped and slot <= 6) : (slot += 1) {
|
|
2979
3545
|
const id = side.order[slot - 1];
|
|
2980
3546
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
2981
3547
|
out[n] = .{ .type = .Switch, .data = slot };
|