@pkmn/engine 0.1.0-dev.89cc1c98 → 0.1.0-dev.900756a9
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/build/tools/debug.js +3 -1
- package/build/tools/debug.js.map +1 -1
- package/build.zig +19 -13
- package/package.json +1 -1
- package/src/lib/common/protocol.zig +35 -14
- package/src/lib/common/rational.zig +1 -1
- package/src/lib/common/rng.zig +9 -0
- package/src/lib/gen1/mechanics.zig +19 -23
- package/src/lib/gen2/README.md +0 -1
- package/src/lib/gen2/data.zig +4 -2
- package/src/lib/gen2/generated.zig +23 -72
- package/src/lib/gen2/mechanics.zig +788 -294
|
@@ -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 {
|
|
@@ -269,7 +274,13 @@ fn turnOrder(battle: anytype, c1: Choice, c2: Choice, options: anytype) !Player
|
|
|
269
274
|
// TODO: add to pending and only commit roll on Pokémon Showdown if roll is used
|
|
270
275
|
const qkc = showdown and try Rolls.quickClaw(battle, .P1, options);
|
|
271
276
|
|
|
272
|
-
if ((c1.type == .Switch) != (c2.type == .Switch))
|
|
277
|
+
if ((c1.type == .Switch) != (c2.type == .Switch)) {
|
|
278
|
+
const m1 = .Pound; // TODO battle.side(.P1).last_selected_move;
|
|
279
|
+
const m2 = .Pound; // TODO battle.side(.P2).last_selected_move;
|
|
280
|
+
return if (c1.type == .Switch)
|
|
281
|
+
if (m1 == .Pursuit) .P2 else .P1
|
|
282
|
+
else if (m2 == .Pursuit) .P1 else .P2;
|
|
283
|
+
}
|
|
273
284
|
|
|
274
285
|
// https://www.smogon.com/forums/threads/adv-switch-priority.3622189/
|
|
275
286
|
// > In Gen 1 it's irrelevant [which player switches first] because switches happen instantly on
|
|
@@ -398,8 +409,14 @@ fn executeMove(
|
|
|
398
409
|
|
|
399
410
|
// FIXME: need damage from doTurn
|
|
400
411
|
const m = Move.get(move);
|
|
401
|
-
var state: State =
|
|
402
|
-
.
|
|
412
|
+
var state: State = .{
|
|
413
|
+
.move = move,
|
|
414
|
+
.mslot = mslot,
|
|
415
|
+
.first = first,
|
|
416
|
+
.accuracy = m.accuracy,
|
|
417
|
+
.bp = m.bp,
|
|
418
|
+
.type = m.type,
|
|
419
|
+
};
|
|
403
420
|
try generated.runMove(battle, player, &state, options);
|
|
404
421
|
return null;
|
|
405
422
|
}
|
|
@@ -631,7 +648,7 @@ pub fn afterMove(
|
|
|
631
648
|
|
|
632
649
|
pub fn checkCriticalHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
633
650
|
const move = Move.get(state.move);
|
|
634
|
-
if (
|
|
651
|
+
if (state.bp == 0) return;
|
|
635
652
|
|
|
636
653
|
const side = battle.side(player);
|
|
637
654
|
const pokemon = side.stored();
|
|
@@ -669,31 +686,30 @@ pub fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype)
|
|
|
669
686
|
const side = battle.side(player);
|
|
670
687
|
const foe = battle.foe(player);
|
|
671
688
|
const types = foe.active.types;
|
|
672
|
-
const move = Move.get(state.move);
|
|
673
689
|
|
|
674
690
|
var d = state.damage;
|
|
675
|
-
if ((
|
|
676
|
-
(
|
|
691
|
+
if ((state.type == .Water and battle.field.weather == .Rain) or
|
|
692
|
+
(state.type == .Fire and battle.field.weather == .Sun))
|
|
677
693
|
{
|
|
678
694
|
d = d *% @intFromEnum(Effectiveness.Super) / 10;
|
|
679
|
-
} else if ((
|
|
680
|
-
((
|
|
695
|
+
} else if ((state.type == .Water and battle.field.weather == .Sun) or
|
|
696
|
+
((state.type == .Fire or state.move == .SolarBeam) and battle.field.weather == .Rain))
|
|
681
697
|
{
|
|
682
698
|
d = d *% @intFromEnum(Effectiveness.Resisted) / 10;
|
|
683
699
|
}
|
|
684
700
|
|
|
685
|
-
if (side.active.types.includes(
|
|
701
|
+
if (side.active.types.includes(state.type)) d +%= d / 2;
|
|
686
702
|
|
|
687
703
|
const neutral = @intFromEnum(Effectiveness.Neutral);
|
|
688
|
-
const foresight = foe.active.volatiles.Foresight and FORESIGHT.includes(
|
|
704
|
+
const foresight = foe.active.volatiles.Foresight and FORESIGHT.includes(state.type);
|
|
689
705
|
const eff1: u16 = if (foresight and types.type1 == .Ghost)
|
|
690
706
|
neutral
|
|
691
707
|
else
|
|
692
|
-
@intFromEnum(
|
|
708
|
+
@intFromEnum(state.type.effectiveness(types.type1));
|
|
693
709
|
const eff2: u16 = if (foresight and types.type2 == .Ghost)
|
|
694
710
|
neutral
|
|
695
711
|
else
|
|
696
|
-
@intFromEnum(
|
|
712
|
+
@intFromEnum(state.type.effectiveness(types.type2));
|
|
697
713
|
|
|
698
714
|
// Type effectiveness matchup precedence only matters with (NVE, SE)
|
|
699
715
|
if (!showdown and (eff1 + eff2) == Effectiveness.mismatch and
|
|
@@ -775,9 +791,15 @@ pub fn checkHit(
|
|
|
775
791
|
return;
|
|
776
792
|
}
|
|
777
793
|
}
|
|
778
|
-
if (move.effect == .Thunder
|
|
779
|
-
|
|
780
|
-
|
|
794
|
+
if (move.effect == .Thunder) {
|
|
795
|
+
if (battle.field.weather == .Rain) {
|
|
796
|
+
state.miss = false;
|
|
797
|
+
return;
|
|
798
|
+
} else if (battle.field.weather == .Sun) {
|
|
799
|
+
// This latter part gets applied in a separate Thunder accuracy handler
|
|
800
|
+
// on the cartridge but it makes more sense to just do it here
|
|
801
|
+
state.accuracy = Gen12.percent(50) + 1;
|
|
802
|
+
}
|
|
781
803
|
}
|
|
782
804
|
if (move.effect == .AlwaysHit) {
|
|
783
805
|
state.miss = false;
|
|
@@ -931,7 +953,7 @@ fn handleResidual(battle: anytype, player: Player, options: anytype) !bool {
|
|
|
931
953
|
const before = foe_stored.hp;
|
|
932
954
|
foe_stored.hp = @min(foe_stored.hp + damage, foe_stored.stats.hp);
|
|
933
955
|
// 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);
|
|
956
|
+
if (foe_stored.hp > before) try options.log.heal(.{ foe_ident, foe_stored, Heal.Silent });
|
|
935
957
|
if (stored.hp == 0) return true;
|
|
936
958
|
}
|
|
937
959
|
|
|
@@ -1047,7 +1069,9 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1047
1069
|
if (stored.item == .Leftovers) {
|
|
1048
1070
|
const before = stored.hp;
|
|
1049
1071
|
stored.hp = @min(stored.hp + @max(stored.stats.hp / 16, 1), stored.stats.hp);
|
|
1050
|
-
if (stored.hp > before)
|
|
1072
|
+
if (stored.hp > before) {
|
|
1073
|
+
try options.log.heal(.{ battle.active(player), stored, Heal.Leftovers });
|
|
1074
|
+
}
|
|
1051
1075
|
}
|
|
1052
1076
|
}
|
|
1053
1077
|
|
|
@@ -1116,7 +1140,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1116
1140
|
assert(offset < 3);
|
|
1117
1141
|
stored.hp = @min(stored.hp + ((offset + 1) * 10), stored.stats.hp);
|
|
1118
1142
|
const reason: Heal = @enumFromInt(@intFromEnum(Heal.Berry) + offset);
|
|
1119
|
-
try options.log.heal(ident, stored, reason);
|
|
1143
|
+
try options.log.heal(.{ ident, stored, reason });
|
|
1120
1144
|
}
|
|
1121
1145
|
} else if (num < @intFromEnum(Item.BitterBerry)) {
|
|
1122
1146
|
const proc = if (stored.item == .MiracleBerry)
|
|
@@ -1131,7 +1155,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1131
1155
|
|
|
1132
1156
|
// FIXME wtf?
|
|
1133
1157
|
// side.active.volatiles.Toxic = false;
|
|
1134
|
-
// side.active.volatiles.
|
|
1158
|
+
// side.active.volatiles.Nightmare = false;
|
|
1135
1159
|
if (stored.item == .MiracleBerry) {
|
|
1136
1160
|
side.active.volatiles.Confusion = false;
|
|
1137
1161
|
try options.log.end(ident, .Confusion);
|
|
@@ -1247,10 +1271,6 @@ pub fn destinyBond(battle: anytype, player: Player, _: *State, options: anytype)
|
|
|
1247
1271
|
}
|
|
1248
1272
|
|
|
1249
1273
|
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
1274
|
pub fn attract(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1255
1275
|
const side = battle.side(player);
|
|
1256
1276
|
var foe = battle.foe(player);
|
|
@@ -1258,12 +1278,10 @@ pub const Effects = struct {
|
|
|
1258
1278
|
const gender: u8 = @intFromEnum(side.stored().dvs.gender);
|
|
1259
1279
|
const foe_gender: u8 = @intFromEnum(foe.stored().dvs.gender);
|
|
1260
1280
|
if (gender + foe_gender != 1) {
|
|
1261
|
-
|
|
1262
|
-
return;
|
|
1281
|
+
return options.log.immune(battle.active(player.foe()), .None);
|
|
1263
1282
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1264
1283
|
try options.log.lastmiss();
|
|
1265
|
-
|
|
1266
|
-
return;
|
|
1284
|
+
return options.log.miss(battle.active(player));
|
|
1267
1285
|
}
|
|
1268
1286
|
|
|
1269
1287
|
foe.active.volatiles.Attract = true;
|
|
@@ -1280,14 +1298,59 @@ pub const Effects = struct {
|
|
|
1280
1298
|
|
|
1281
1299
|
pub fn bellyDrum(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1282
1300
|
_ = .{ battle, player, state, options }; // TODO
|
|
1301
|
+
// assert(!state.miss);
|
|
1302
|
+
// var side = battle.side(player);
|
|
1303
|
+
// try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
|
|
1304
|
+
// TODO
|
|
1305
|
+
// |-boost|p1a: Poliwag|atk|2|[silent]
|
|
1306
|
+
// |-fail|p1a: Poliwag
|
|
1307
|
+
//
|
|
1283
1308
|
}
|
|
1284
1309
|
|
|
1285
|
-
pub
|
|
1286
|
-
|
|
1287
|
-
|
|
1310
|
+
pub const bide = struct {
|
|
1311
|
+
pub fn store(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1312
|
+
var side = battle.side(player);
|
|
1313
|
+
var volatiles = &side.active.volatiles;
|
|
1314
|
+
|
|
1315
|
+
const ident = battle.active(player);
|
|
1316
|
+
|
|
1317
|
+
if (!volatiles.Bide) return;
|
|
1318
|
+
|
|
1319
|
+
assert(volatiles.attacks > 0);
|
|
1320
|
+
// if (options.calc.modify(player, .attacking)) |extend| {
|
|
1321
|
+
// if (!extend) volatiles.attacks = 0;
|
|
1322
|
+
// } else {
|
|
1323
|
+
volatiles.attacks -= 1;
|
|
1324
|
+
// }
|
|
1325
|
+
try options.chance.attacking(player, volatiles.attacks);
|
|
1326
|
+
|
|
1327
|
+
if (volatiles.attacks != 0) return options.log.activate(ident, .Bide);
|
|
1328
|
+
|
|
1329
|
+
volatiles.Bide = false;
|
|
1330
|
+
try options.log.end(ident, .Bide);
|
|
1331
|
+
|
|
1332
|
+
state.damage = volatiles.bide *% 2;
|
|
1333
|
+
volatiles.bide = 0;
|
|
1334
|
+
|
|
1335
|
+
if (state.damage == 0) state.miss = true;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
pub fn unleash(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1339
|
+
_ = .{ battle, player, state, options }; // TODO
|
|
1340
|
+
}
|
|
1341
|
+
};
|
|
1288
1342
|
|
|
1289
1343
|
pub fn binding(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1290
|
-
|
|
1344
|
+
var foe = battle.foe(player);
|
|
1345
|
+
|
|
1346
|
+
if (state.miss) return;
|
|
1347
|
+
if (foe.active.volatiles.bind.duration > 0) return;
|
|
1348
|
+
if (foe.active.volatiles.Substitute) return;
|
|
1349
|
+
|
|
1350
|
+
foe.active.volatiles.bind = .{
|
|
1351
|
+
.duration = Rolls.bindingDuration(battle, player, options),
|
|
1352
|
+
.reason = Move.get(state.move).extra.protocol,
|
|
1353
|
+
};
|
|
1291
1354
|
}
|
|
1292
1355
|
|
|
1293
1356
|
pub fn burnChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1297,7 +1360,7 @@ pub const Effects = struct {
|
|
|
1297
1360
|
if (foe.active.volatiles.Substitute) return;
|
|
1298
1361
|
if (Status.any(foe_stored.status)) {
|
|
1299
1362
|
if (Status.is(foe_stored.status, .FRZ)) {
|
|
1300
|
-
assert(Move.get(state.move).type == .Fire);
|
|
1363
|
+
assert(Move.get(state.move).type == .Fire or state.move == .TriAttack);
|
|
1301
1364
|
try options.log.curestatus(
|
|
1302
1365
|
battle.active(player.foe()),
|
|
1303
1366
|
foe_stored.status,
|
|
@@ -1311,7 +1374,9 @@ pub const Effects = struct {
|
|
|
1311
1374
|
if (foe.conditions.Safeguard) return;
|
|
1312
1375
|
// Nothing is immune to Fire-type, but Ghosts are immune to burnChance called by Tri Attack
|
|
1313
1376
|
if (state.immune()) return;
|
|
1314
|
-
if (foe.active.types.includes(Move.get(state.move).type))
|
|
1377
|
+
if (foe.active.types.includes(Move.get(state.move).type) and state.move != .TriAttack) {
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1315
1380
|
|
|
1316
1381
|
foe_stored.status = Status.init(.BRN);
|
|
1317
1382
|
foe.active.stats.atk = @max(foe.active.stats.atk / 2, 1);
|
|
@@ -1335,10 +1400,7 @@ pub const Effects = struct {
|
|
|
1335
1400
|
for (side.active.moves) |m| {
|
|
1336
1401
|
if (convertible(side.active, m.id)) n += 1;
|
|
1337
1402
|
}
|
|
1338
|
-
if (n == 0)
|
|
1339
|
-
try options.log.fail(battle.active(player), .None);
|
|
1340
|
-
return;
|
|
1341
|
-
}
|
|
1403
|
+
if (n == 0) return options.log.fail(battle.active(player), .None);
|
|
1342
1404
|
|
|
1343
1405
|
side.active.types.type1 = try Rolls.conversion(battle, player, n, options);
|
|
1344
1406
|
side.active.types.type2 = side.active.types.type1;
|
|
@@ -1348,26 +1410,87 @@ pub const Effects = struct {
|
|
|
1348
1410
|
var side = battle.side(player);
|
|
1349
1411
|
|
|
1350
1412
|
const last = battle.foe(player).lastMove(false);
|
|
1351
|
-
if (last == .None)
|
|
1352
|
-
try options.log.fail(battle.active(player), .None);
|
|
1353
|
-
return;
|
|
1354
|
-
}
|
|
1413
|
+
if (last == .None) return options.log.fail(battle.active(player), .None);
|
|
1355
1414
|
const move = Move.get(last);
|
|
1356
|
-
if (move.type == .@"???")
|
|
1357
|
-
try options.log.fail(battle.active(player), .None);
|
|
1358
|
-
return;
|
|
1359
|
-
}
|
|
1415
|
+
if (move.type == .@"???") return options.log.fail(battle.active(player), .None);
|
|
1360
1416
|
|
|
1361
1417
|
side.active.types.type1 = try Rolls.conversion2(battle, player, move.type, options);
|
|
1362
1418
|
side.active.types.type2 = side.active.types.type1;
|
|
1363
1419
|
}
|
|
1364
1420
|
|
|
1365
|
-
pub fn counter(battle: anytype, player: Player, state: *State,
|
|
1366
|
-
|
|
1421
|
+
pub fn counter(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1422
|
+
assert(state.move == .Counter or state.move == .MirrorCoat);
|
|
1423
|
+
|
|
1424
|
+
const foe = battle.foe(player);
|
|
1425
|
+
|
|
1426
|
+
state.miss = true;
|
|
1427
|
+
const last = foe.lastMove(false);
|
|
1428
|
+
|
|
1429
|
+
if (last == .None) return;
|
|
1430
|
+
if (last == state.move) return;
|
|
1431
|
+
|
|
1432
|
+
const immune = state.immune();
|
|
1433
|
+
state.effectiveness = Effectiveness.neutral;
|
|
1434
|
+
if (immune) {
|
|
1435
|
+
state.damage = 0;
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
const move = Move.get(last);
|
|
1440
|
+
if (state.bp == 0) return;
|
|
1441
|
+
if (move.type.special() != (last == .MirrorCoat)) return;
|
|
1442
|
+
if (state.damage == 0) return;
|
|
1443
|
+
|
|
1444
|
+
state.damage *|= 2;
|
|
1445
|
+
state.miss = false;
|
|
1367
1446
|
}
|
|
1368
1447
|
|
|
1369
1448
|
pub fn curse(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1370
|
-
|
|
1449
|
+
var side = battle.side(player);
|
|
1450
|
+
var foe = battle.foe(player);
|
|
1451
|
+
|
|
1452
|
+
const ident = battle.active(player);
|
|
1453
|
+
const foe_ident = battle.active(player.foe());
|
|
1454
|
+
|
|
1455
|
+
if (side.active.types.includes(.Ghost)) {
|
|
1456
|
+
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1457
|
+
try options.log.lastmiss();
|
|
1458
|
+
return options.log.miss(battle.active(player));
|
|
1459
|
+
} else if (foe.active.volatiles.Substitute) {
|
|
1460
|
+
return options.log.activateMove(foe_ident, .SubstituteBlock, state.move);
|
|
1461
|
+
} else if (foe.active.volatiles.Curse) {
|
|
1462
|
+
return options.log.fail(foe_ident, .None);
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
foe.active.volatiles.Curse = true;
|
|
1466
|
+
try options.log.start(foe_ident, .Curse);
|
|
1467
|
+
|
|
1468
|
+
side.stored().hp -= @max(side.stored().stats.hp / 2, 1);
|
|
1469
|
+
try options.log.damage(ident, side.stored(), .Curse);
|
|
1470
|
+
} else {
|
|
1471
|
+
var stats = &side.active.stats;
|
|
1472
|
+
var boosts = &side.active.boosts;
|
|
1473
|
+
|
|
1474
|
+
const max_atk = boosts.atk == 6 or stats.atk == MAX_STAT_VALUE;
|
|
1475
|
+
const max_def = boosts.def == 6 or stats.def == MAX_STAT_VALUE;
|
|
1476
|
+
if (max_atk and max_def) return options.log.fail(ident, .None);
|
|
1477
|
+
|
|
1478
|
+
if (!(boosts.spe == -6 or stats.spe == 1)) {
|
|
1479
|
+
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
1480
|
+
try unboostStat(battle, side, stats, boosts, "spe", 1);
|
|
1481
|
+
try options.log.boost(ident, .Speed, @as(i8, -1));
|
|
1482
|
+
}
|
|
1483
|
+
if (!max_atk) {
|
|
1484
|
+
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
1485
|
+
try boostStat(battle, side, stats, boosts, "atk", 1);
|
|
1486
|
+
try options.log.boost(ident, .Attack, @as(i8, 1));
|
|
1487
|
+
}
|
|
1488
|
+
if (!max_def) {
|
|
1489
|
+
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
1490
|
+
try boostStat(battle, side, stats, boosts, "def", 1);
|
|
1491
|
+
try options.log.boost(ident, .Defense, @as(i8, 1));
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1371
1494
|
}
|
|
1372
1495
|
|
|
1373
1496
|
pub fn defenseCurl(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
@@ -1393,14 +1516,12 @@ pub const Effects = struct {
|
|
|
1393
1516
|
const foe_ident = battle.active(player.foe());
|
|
1394
1517
|
|
|
1395
1518
|
if (volatiles.disable.move != 0) {
|
|
1396
|
-
|
|
1397
|
-
return;
|
|
1519
|
+
return options.log.fail(foe_ident, .None);
|
|
1398
1520
|
}
|
|
1399
1521
|
|
|
1400
1522
|
const last = battle.foe(player).lastMove(false);
|
|
1401
1523
|
if (last == .None or last == .Struggle) {
|
|
1402
|
-
|
|
1403
|
-
return;
|
|
1524
|
+
return options.log.fail(foe_ident, .None);
|
|
1404
1525
|
}
|
|
1405
1526
|
|
|
1406
1527
|
var slot: u3 = 0;
|
|
@@ -1410,10 +1531,7 @@ pub const Effects = struct {
|
|
|
1410
1531
|
if (m.id == last) break;
|
|
1411
1532
|
}
|
|
1412
1533
|
const move = foe.active.move(slot);
|
|
1413
|
-
if (move.pp == 0)
|
|
1414
|
-
try options.log.fail(foe_ident, .None);
|
|
1415
|
-
return;
|
|
1416
|
-
}
|
|
1534
|
+
if (move.pp == 0) return options.log.fail(foe_ident, .None);
|
|
1417
1535
|
|
|
1418
1536
|
volatiles.disable.move = slot;
|
|
1419
1537
|
volatiles.disable.duration = Rolls.disableDuration(battle, player, options);
|
|
@@ -1421,9 +1539,19 @@ pub const Effects = struct {
|
|
|
1421
1539
|
try options.log.startEffect(foe_ident, .Disable, move.id);
|
|
1422
1540
|
}
|
|
1423
1541
|
|
|
1424
|
-
pub
|
|
1425
|
-
|
|
1426
|
-
|
|
1542
|
+
pub const double = struct {
|
|
1543
|
+
pub fn flying(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1544
|
+
if (battle.foe(player).active.volatiles.Flying) state.damage *|= 2;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
pub fn underground(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1548
|
+
if (battle.foe(player).active.volatiles.Underground) state.damage *|= 2;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
pub fn minimize(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1552
|
+
if (battle.foe(player).active.volatiles.minimized) state.damage *|= 2;
|
|
1553
|
+
}
|
|
1554
|
+
};
|
|
1427
1555
|
|
|
1428
1556
|
pub fn drainHP(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1429
1557
|
var stored = battle.side(player).stored();
|
|
@@ -1445,10 +1573,7 @@ pub const Effects = struct {
|
|
|
1445
1573
|
const failed = volatiles.Encore or
|
|
1446
1574
|
(last == .None or last == .Struggle or
|
|
1447
1575
|
last == .Encore or .last == .MirrorMove);
|
|
1448
|
-
if (failed)
|
|
1449
|
-
try options.log.fail(foe_ident, .None);
|
|
1450
|
-
return;
|
|
1451
|
-
}
|
|
1576
|
+
if (failed) return options.log.fail(foe_ident, .None);
|
|
1452
1577
|
|
|
1453
1578
|
var slot: u3 = 0;
|
|
1454
1579
|
for (foe.active.moves) |m| {
|
|
@@ -1457,10 +1582,7 @@ pub const Effects = struct {
|
|
|
1457
1582
|
if (m.id == last) break;
|
|
1458
1583
|
}
|
|
1459
1584
|
const move = foe.active.move(slot);
|
|
1460
|
-
if (move.pp == 0)
|
|
1461
|
-
try options.log.fail(foe_ident, .None);
|
|
1462
|
-
return;
|
|
1463
|
-
}
|
|
1585
|
+
if (move.pp == 0) return options.log.fail(foe_ident, .None);
|
|
1464
1586
|
|
|
1465
1587
|
volatiles.Encore = true;
|
|
1466
1588
|
volatiles.encore = Rolls.encoreDuration(battle, player, options);
|
|
@@ -1500,10 +1622,6 @@ pub const Effects = struct {
|
|
|
1500
1622
|
};
|
|
1501
1623
|
}
|
|
1502
1624
|
|
|
1503
|
-
pub fn flameWheel(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1504
|
-
_ = .{ battle, player, state, options }; // TODO
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
1625
|
pub fn flinchChance(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1508
1626
|
const foe = battle.foe(player);
|
|
1509
1627
|
const status = foe.stored().status;
|
|
@@ -1541,7 +1659,7 @@ pub const Effects = struct {
|
|
|
1541
1659
|
|
|
1542
1660
|
const foe = battle.foe(player);
|
|
1543
1661
|
|
|
1544
|
-
if (state.first) return
|
|
1662
|
+
if (state.first) return options.log.fail(battle.active(player), .None);
|
|
1545
1663
|
|
|
1546
1664
|
var n: u4 = 0;
|
|
1547
1665
|
var i: u4 = 0;
|
|
@@ -1555,10 +1673,7 @@ pub const Effects = struct {
|
|
|
1555
1673
|
}
|
|
1556
1674
|
}
|
|
1557
1675
|
|
|
1558
|
-
if (n == 0)
|
|
1559
|
-
try options.log.fail(battle.active(player), .None);
|
|
1560
|
-
return;
|
|
1561
|
-
}
|
|
1676
|
+
if (n == 0) return options.log.fail(battle.active(player), .None);
|
|
1562
1677
|
|
|
1563
1678
|
const slot = try Rolls.forceSwitch(battle, player, SLOTS[0..i], n, options);
|
|
1564
1679
|
try switchIn(battle, player.foe(), slot, .Drag, options);
|
|
@@ -1569,11 +1684,9 @@ pub const Effects = struct {
|
|
|
1569
1684
|
|
|
1570
1685
|
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1571
1686
|
try options.log.lastmiss();
|
|
1572
|
-
|
|
1573
|
-
return;
|
|
1687
|
+
return options.log.miss(battle.active(player));
|
|
1574
1688
|
} else if (foe.active.volatiles.Foresight) {
|
|
1575
|
-
|
|
1576
|
-
return;
|
|
1689
|
+
return options.log.fail(battle.active(player), .None);
|
|
1577
1690
|
}
|
|
1578
1691
|
|
|
1579
1692
|
foe.active.volatiles.Foresight = true;
|
|
@@ -1589,8 +1702,11 @@ pub const Effects = struct {
|
|
|
1589
1702
|
if (Status.any(foe_stored.status)) return;
|
|
1590
1703
|
|
|
1591
1704
|
// Nothing is immune to Ice-type, but Ghosts are immune to freezeChance called by Tri Attack
|
|
1705
|
+
if (state.immune()) return;
|
|
1592
1706
|
if (battle.field.weather == .Sun) return;
|
|
1593
|
-
if (foe.active.types.includes(Move.get(state.move).type))
|
|
1707
|
+
if (foe.active.types.includes(Move.get(state.move).type) and state.move != .TriAttack) {
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1594
1710
|
if (!state.proc) return;
|
|
1595
1711
|
if (foe.conditions.Safeguard) return;
|
|
1596
1712
|
// Freeze Clause Mod
|
|
@@ -1619,73 +1735,200 @@ pub const Effects = struct {
|
|
|
1619
1735
|
state.bp = ((if (frustration) 255 - val else val) * 10) / 25;
|
|
1620
1736
|
}
|
|
1621
1737
|
|
|
1622
|
-
pub fn haze(battle: anytype, player: Player,
|
|
1623
|
-
|
|
1738
|
+
pub fn haze(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1739
|
+
var side = battle.side(player);
|
|
1740
|
+
var stats = unmodifiedStats(battle, side);
|
|
1741
|
+
side.active.boosts = .{};
|
|
1742
|
+
inline for (STATS) |s| @field(side.active.stats, s) = @field(stats, s);
|
|
1743
|
+
statusModify(side.stored().status, &side.active.stats);
|
|
1744
|
+
|
|
1745
|
+
var foe = battle.foe(player);
|
|
1746
|
+
stats = unmodifiedStats(battle, foe);
|
|
1747
|
+
foe.active.boosts = .{};
|
|
1748
|
+
inline for (STATS) |s| @field(foe.active.stats, s) = @field(stats, s);
|
|
1749
|
+
statusModify(foe.stored().status, &foe.active.stats);
|
|
1750
|
+
|
|
1751
|
+
try options.log.clearallboost();
|
|
1624
1752
|
}
|
|
1625
1753
|
|
|
1626
1754
|
pub fn heal(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1627
|
-
|
|
1628
|
-
|
|
1755
|
+
var side = battle.side(player);
|
|
1756
|
+
var stored = side.stored();
|
|
1757
|
+
const ident = battle.active(player);
|
|
1629
1758
|
|
|
1630
|
-
|
|
1631
|
-
|
|
1759
|
+
if (stored.stats.hp == stored.hp) return options.log.fail(ident, .None);
|
|
1760
|
+
|
|
1761
|
+
const rest = state.move == .Rest;
|
|
1762
|
+
if (rest) {
|
|
1763
|
+
// Adding the sleep status runs the sleep condition handler to roll duration
|
|
1764
|
+
if (showdown) battle.rng.advance(1);
|
|
1765
|
+
side.active.volatiles.Toxic = false;
|
|
1766
|
+
stored.status = Status.slf(2);
|
|
1767
|
+
recomputeStats(battle, side);
|
|
1768
|
+
try options.log.statusFrom(ident, stored.status, Move.Rest);
|
|
1769
|
+
stored.hp = stored.stats.hp;
|
|
1770
|
+
} else {
|
|
1771
|
+
stored.hp = @min(stored.stats.hp, stored.hp + (stored.stats.hp / 2));
|
|
1772
|
+
}
|
|
1773
|
+
try options.log.heal(.{ ident, stored, if (rest) Heal.Silent else Heal.None });
|
|
1632
1774
|
}
|
|
1633
1775
|
|
|
1634
|
-
pub fn
|
|
1635
|
-
|
|
1776
|
+
pub fn healBell(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1777
|
+
var side = battle.side(player);
|
|
1778
|
+
side.active.volatiles.Nightmare = false;
|
|
1779
|
+
|
|
1780
|
+
try options.log.cureteam(battle.active(player));
|
|
1781
|
+
for (&side.pokemon) |*p| p.status = 0;
|
|
1782
|
+
|
|
1783
|
+
// We technically only need to recompute stats if the active Pokémon was burned/paralyzed,
|
|
1784
|
+
// but adding a branch to check for that is probably more expensive than just recomputing
|
|
1785
|
+
recomputeStats(battle, side);
|
|
1636
1786
|
}
|
|
1637
1787
|
|
|
1638
|
-
pub fn
|
|
1639
|
-
|
|
1788
|
+
pub fn hiddenPower(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1789
|
+
const side = battle.side(player);
|
|
1790
|
+
state.bp = side.stored().dvs.power();
|
|
1791
|
+
state.type = side.stored().dvs.type;
|
|
1640
1792
|
}
|
|
1641
1793
|
|
|
1642
|
-
pub fn
|
|
1643
|
-
|
|
1794
|
+
pub fn hyperBeam(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1795
|
+
battle.side(player).active.volatiles.Recharging = true;
|
|
1796
|
+
try options.log.mustrecharge(battle.active(player));
|
|
1644
1797
|
}
|
|
1645
1798
|
|
|
1646
1799
|
pub fn leechSeed(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1647
|
-
|
|
1800
|
+
var foe = battle.foe(player);
|
|
1801
|
+
|
|
1802
|
+
if (showdown) {
|
|
1803
|
+
// Invulnerability trumps type immunity on Pokémon Showdown
|
|
1804
|
+
const invulnerable = foe.active.volatiles.Underground or foe.active.volatiles.Flying;
|
|
1805
|
+
if (!invulnerable and foe.active.types.includes(.Grass)) {
|
|
1806
|
+
return options.log.immune(battle.active(player.foe()), .None);
|
|
1807
|
+
}
|
|
1808
|
+
try checkHit(battle, player, state, options);
|
|
1809
|
+
if (state.miss) {
|
|
1810
|
+
try options.log.lastmiss();
|
|
1811
|
+
return options.log.miss(battle.active(player));
|
|
1812
|
+
}
|
|
1813
|
+
if (foe.active.volatiles.LeechSeed) return;
|
|
1814
|
+
} else {
|
|
1815
|
+
try checkHit(battle, player, state, options);
|
|
1816
|
+
const miss =
|
|
1817
|
+
state.miss or foe.active.types.includes(.Grass) or foe.active.volatiles.LeechSeed;
|
|
1818
|
+
if (miss) {
|
|
1819
|
+
try options.log.lastmiss();
|
|
1820
|
+
return options.log.miss(battle.active(player));
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
foe.active.volatiles.LeechSeed = true;
|
|
1825
|
+
|
|
1826
|
+
try options.log.start(battle.active(player.foe()), .LeechSeed);
|
|
1648
1827
|
}
|
|
1649
1828
|
|
|
1650
1829
|
pub fn lockOn(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1651
|
-
|
|
1830
|
+
var foe = battle.foe(player);
|
|
1831
|
+
const foe_ident = battle.active(player.foe());
|
|
1832
|
+
|
|
1833
|
+
if (foe.active.volatiles.Substitute) {
|
|
1834
|
+
return options.log.activateMove(foe_ident, .SubstituteBlock, state.move);
|
|
1835
|
+
} else if (state.miss) {
|
|
1836
|
+
return options.log.fail(foe_ident, .None);
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
foe.active.volatiles.LockOn = true;
|
|
1840
|
+
try options.log.activate(battle.active(player), .LockOn);
|
|
1652
1841
|
}
|
|
1653
1842
|
|
|
1654
1843
|
const MAGNITUDE_POWER = [_]u8{ 10, 30, 50, 70, 90, 110, 150 };
|
|
1655
1844
|
|
|
1656
1845
|
pub fn magnitude(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1657
|
-
|
|
1846
|
+
const num = try Rolls.magnitude(battle, player, options);
|
|
1847
|
+
try options.log.magnitude(battle.active(player), num);
|
|
1848
|
+
state.bp = MAGNITUDE_POWER[num - 4];
|
|
1658
1849
|
}
|
|
1659
1850
|
|
|
1660
|
-
pub fn meanLook(battle: anytype, player: Player,
|
|
1661
|
-
|
|
1851
|
+
pub fn meanLook(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1852
|
+
var foe = battle.foe(player);
|
|
1853
|
+
|
|
1854
|
+
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
1855
|
+
if (!showdown) return options.log.fail(battle.active(player.foe()), .None);
|
|
1856
|
+
try options.log.lastmiss();
|
|
1857
|
+
return options.log.miss(battle.active(player));
|
|
1858
|
+
} else if (foe.active.volatiles.Trapped) {
|
|
1859
|
+
return options.log.fail(battle.active(player.foe()), .None);
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
foe.active.volatiles.Trapped = true;
|
|
1662
1863
|
}
|
|
1663
1864
|
|
|
1664
|
-
pub fn metronome(
|
|
1665
|
-
|
|
1865
|
+
pub fn metronome(
|
|
1866
|
+
battle: anytype,
|
|
1867
|
+
player: Player,
|
|
1868
|
+
state: *State,
|
|
1869
|
+
options: anytype,
|
|
1870
|
+
) anyerror!void {
|
|
1871
|
+
var side = battle.side(player);
|
|
1872
|
+
|
|
1873
|
+
side.last_used_move = .None;
|
|
1874
|
+
side.active.volatiles.dirty = false;
|
|
1875
|
+
|
|
1876
|
+
// if (wPlayerCharging) ???
|
|
1877
|
+
// wPlayerCharging = 1
|
|
1878
|
+
|
|
1879
|
+
// FIXME
|
|
1880
|
+
state.move = try Rolls.metronome(battle, player, options);
|
|
1881
|
+
state.from = .Metronome;
|
|
1882
|
+
return generated.runMove(battle, player, state, options);
|
|
1666
1883
|
}
|
|
1667
1884
|
|
|
1668
1885
|
pub fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1669
1886
|
_ = .{ battle, player, state, options }; // TODO
|
|
1670
1887
|
}
|
|
1671
1888
|
|
|
1672
|
-
pub fn
|
|
1673
|
-
|
|
1674
|
-
|
|
1889
|
+
pub fn mirrorMove(
|
|
1890
|
+
battle: anytype,
|
|
1891
|
+
player: Player,
|
|
1892
|
+
state: *State,
|
|
1893
|
+
options: anytype,
|
|
1894
|
+
) anyerror!void {
|
|
1895
|
+
var side = battle.side(player);
|
|
1896
|
+
const foe = battle.foe(player);
|
|
1675
1897
|
|
|
1676
|
-
|
|
1677
|
-
|
|
1898
|
+
side.last_used_move = .None;
|
|
1899
|
+
side.active.volatiles.dirty = false;
|
|
1900
|
+
|
|
1901
|
+
const last = foe.lastMove(false);
|
|
1902
|
+
|
|
1903
|
+
if (last == .None) return options.log.fail(battle.active(player), .None);
|
|
1904
|
+
const knows = knows: for (side.active.moves) |m| {
|
|
1905
|
+
if (last == m.id) break :knows true;
|
|
1906
|
+
} else false;
|
|
1907
|
+
if (knows) return options.log.fail(battle.active(player), .None);
|
|
1908
|
+
|
|
1909
|
+
// if (wPlayerCharging) ???
|
|
1910
|
+
// wPlayerCharging = 1
|
|
1911
|
+
|
|
1912
|
+
// FIXME
|
|
1913
|
+
state.move = last;
|
|
1914
|
+
state.from = .MirrorMove;
|
|
1915
|
+
return generated.runMove(battle, player, state, options);
|
|
1678
1916
|
}
|
|
1679
1917
|
|
|
1680
|
-
pub fn mist(battle: anytype, player: Player,
|
|
1681
|
-
|
|
1918
|
+
pub fn mist(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1919
|
+
var side = battle.side(player);
|
|
1920
|
+
|
|
1921
|
+
if (side.active.volatiles.Mist) return options.log.fail(battle.active(player), .None);
|
|
1922
|
+
side.active.volatiles.Mist = true;
|
|
1923
|
+
|
|
1924
|
+
try options.log.start(battle.active(player), .Mist);
|
|
1682
1925
|
}
|
|
1683
1926
|
|
|
1684
1927
|
pub fn weatherHeal(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
1685
1928
|
var stored = battle.side(player).stored();
|
|
1686
1929
|
const ident = battle.active(player);
|
|
1687
1930
|
|
|
1688
|
-
if (stored.hp == stored.stats.hp) return
|
|
1931
|
+
if (stored.hp == stored.stats.hp) return options.log.fail(ident, .None);
|
|
1689
1932
|
|
|
1690
1933
|
stored.hp = switch (battle.field.weather) {
|
|
1691
1934
|
.Sun => stored.stats.hp,
|
|
@@ -1693,7 +1936,7 @@ pub const Effects = struct {
|
|
|
1693
1936
|
else => @min(stored.stats.hp, stored.hp + (stored.stats.hp / 4)),
|
|
1694
1937
|
};
|
|
1695
1938
|
|
|
1696
|
-
try options.log.heal(ident, stored, Heal.None);
|
|
1939
|
+
try options.log.heal(.{ ident, stored, Heal.None });
|
|
1697
1940
|
}
|
|
1698
1941
|
|
|
1699
1942
|
pub fn multiHit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1701,32 +1944,94 @@ pub const Effects = struct {
|
|
|
1701
1944
|
}
|
|
1702
1945
|
|
|
1703
1946
|
pub fn nightmare(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1704
|
-
|
|
1947
|
+
var foe = battle.foe(player);
|
|
1948
|
+
var foe_volatiles = &foe.active.volatiles;
|
|
1949
|
+
const foe_ident = battle.active(player.foe());
|
|
1950
|
+
|
|
1951
|
+
if (foe_volatiles.Flying or foe_volatiles.Underground) {
|
|
1952
|
+
if (!showdown) return options.log.fail(battle.active(player.foe()), .None);
|
|
1953
|
+
try options.log.lastmiss();
|
|
1954
|
+
return options.log.miss(battle.active(player));
|
|
1955
|
+
} else if (foe_volatiles.Substitute) {
|
|
1956
|
+
return options.log.activateMove(foe_ident, .SubstituteBlock, state.move);
|
|
1957
|
+
} else if (!Status.is(foe.stored().status, .SLP) or foe_volatiles.Nightmare) {
|
|
1958
|
+
return options.log.fail(foe_ident, .None);
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
foe_volatiles.Nightmare = true;
|
|
1962
|
+
try options.log.start(battle.active(player.foe()), .Nightmare);
|
|
1705
1963
|
}
|
|
1706
1964
|
|
|
1707
|
-
pub
|
|
1708
|
-
|
|
1709
|
-
|
|
1965
|
+
pub const ohko = struct {
|
|
1966
|
+
// OHKO calls adjustDamage but only actually cares about immunity
|
|
1967
|
+
pub fn immune(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1968
|
+
const move = Move.get(state.move);
|
|
1710
1969
|
|
|
1711
|
-
|
|
1970
|
+
assert(move.type == .Normal or move.type == .Ground);
|
|
1971
|
+
const foe = battle.foe(player);
|
|
1972
|
+
const zero = if (move.type == .Normal)
|
|
1973
|
+
foe.active.types.includes(.Ghost) and !foe.active.volatiles.Foresight
|
|
1974
|
+
else
|
|
1975
|
+
foe.active.types.includes(.Flying);
|
|
1976
|
+
state.effectiveness = if (zero) 0 else Effectiveness.neutral;
|
|
1977
|
+
}
|
|
1712
1978
|
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
if (delta == 0) return try options.log.immune(foe_ident, .OHKO);
|
|
1979
|
+
pub fn damage(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1980
|
+
const side = battle.side(player);
|
|
1981
|
+
const foe = battle.foe(player);
|
|
1717
1982
|
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1983
|
+
const foe_ident = battle.active(player.foe());
|
|
1984
|
+
|
|
1985
|
+
state.damage = 0;
|
|
1986
|
+
if (state.immune()) return options.log.immune(foe_ident, .None);
|
|
1987
|
+
const delta = side.stored().level -| foe.stored().level;
|
|
1988
|
+
if (delta == 0) return options.log.immune(foe_ident, .OHKO);
|
|
1989
|
+
|
|
1990
|
+
state.accuracy = 2 * delta +| Gen12.percent(30);
|
|
1991
|
+
try checkHit(battle, player, state, options);
|
|
1992
|
+
if (!state.miss) state.damage = 65535;
|
|
1993
|
+
state.ohko = true;
|
|
1994
|
+
}
|
|
1995
|
+
};
|
|
1723
1996
|
|
|
1724
1997
|
pub fn painSplit(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1725
|
-
|
|
1998
|
+
var side = battle.side(player);
|
|
1999
|
+
var foe = battle.foe(player);
|
|
2000
|
+
|
|
2001
|
+
const ident = battle.active(player);
|
|
2002
|
+
const foe_ident = battle.active(player.foe());
|
|
2003
|
+
|
|
2004
|
+
if (state.miss) return options.log.fail(foe_ident, .None);
|
|
2005
|
+
if (foe.active.volatiles.Substitute) {
|
|
2006
|
+
return options.log.activateMove(foe_ident, .SubstituteBlock, state.move);
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
const avg = @max((foe.stored().hp + side.stored().hp) / 2, 1);
|
|
2010
|
+
foe.stored().hp -= avg;
|
|
2011
|
+
side.stored().hp = avg;
|
|
2012
|
+
|
|
2013
|
+
try options.log.sethp(foe_ident, foe.stored(), .Silent);
|
|
2014
|
+
try options.log.sethp(ident, side.stored(), .None);
|
|
1726
2015
|
}
|
|
1727
2016
|
|
|
1728
2017
|
pub fn paralyze(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1729
|
-
|
|
2018
|
+
var foe = battle.foe(player);
|
|
2019
|
+
const foe_ident = battle.active(player.foe());
|
|
2020
|
+
|
|
2021
|
+
if (Status.is(foe.stored().status, .PAR)) return options.log.fail(foe_ident, .Paralysis);
|
|
2022
|
+
if (state.immune()) return options.log.immune(foe_ident, .None);
|
|
2023
|
+
if (Status.any(foe.stored().status)) return options.log.fail(foe_ident, .None);
|
|
2024
|
+
if (state.miss) {
|
|
2025
|
+
try options.log.lastmiss();
|
|
2026
|
+
return options.log.miss(battle.active(player));
|
|
2027
|
+
} else if (foe.active.volatiles.Substitute) {
|
|
2028
|
+
return options.log.activateMove(foe_ident, .SubstituteBlock, state.move);
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
foe.stored().status = Status.init(.PAR);
|
|
2032
|
+
foe.active.stats.spe = @max(foe.active.stats.spe / 4, 1);
|
|
2033
|
+
|
|
2034
|
+
try options.log.status(foe_ident, foe.stored().status, .None);
|
|
1730
2035
|
}
|
|
1731
2036
|
|
|
1732
2037
|
pub fn paralyzeChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1746,12 +2051,28 @@ pub const Effects = struct {
|
|
|
1746
2051
|
// TODO: check for status berry
|
|
1747
2052
|
}
|
|
1748
2053
|
|
|
1749
|
-
pub fn payDay(battle: anytype, player: Player,
|
|
1750
|
-
|
|
2054
|
+
pub fn payDay(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2055
|
+
if (!showdown or !battle.foe(player).active.volatiles.Substitute) {
|
|
2056
|
+
try options.log.fieldactivate(); // FIXME .PayDay
|
|
2057
|
+
}
|
|
1751
2058
|
}
|
|
1752
2059
|
|
|
1753
|
-
pub fn perishSong(battle: anytype, player: Player,
|
|
1754
|
-
|
|
2060
|
+
pub fn perishSong(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2061
|
+
var side = battle.side(player);
|
|
2062
|
+
var foe = battle.foe(player);
|
|
2063
|
+
|
|
2064
|
+
if (foe.active.volatiles.PerishSong) {
|
|
2065
|
+
return options.log.fail(battle.active(player.foe()), .None);
|
|
2066
|
+
} else if (!side.active.volatiles.PerishSong) {
|
|
2067
|
+
side.active.volatiles.PerishSong = true;
|
|
2068
|
+
side.active.volatiles.perish_song = 4;
|
|
2069
|
+
try options.log.start(battle.active(player), .PerishSong3Silent);
|
|
2070
|
+
}
|
|
2071
|
+
foe.active.volatiles.PerishSong = true;
|
|
2072
|
+
foe.active.volatiles.perish_song = 4;
|
|
2073
|
+
try options.log.start(battle.active(player.foe()), .PerishSong3Silent);
|
|
2074
|
+
|
|
2075
|
+
try options.log.fieldactivate(); // FIXME .PerishSong
|
|
1755
2076
|
}
|
|
1756
2077
|
|
|
1757
2078
|
pub fn poison(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1778,7 +2099,7 @@ pub const Effects = struct {
|
|
|
1778
2099
|
return options.log.activateMove(foe_ident, .SubstituteBlock, state.move);
|
|
1779
2100
|
} else if (state.miss) {
|
|
1780
2101
|
try options.log.lastmiss();
|
|
1781
|
-
|
|
2102
|
+
return options.log.miss(battle.active(player));
|
|
1782
2103
|
}
|
|
1783
2104
|
|
|
1784
2105
|
foe_stored.status = Status.init(.PSN);
|
|
@@ -1820,8 +2141,7 @@ pub const Effects = struct {
|
|
|
1820
2141
|
const failed = !state.first or volatiles.Substitute or
|
|
1821
2142
|
!try Rolls.protect(battle, player, volatiles.protect, options);
|
|
1822
2143
|
if (failed) {
|
|
1823
|
-
|
|
1824
|
-
return;
|
|
2144
|
+
return options.log.fail(battle.active(player), .None);
|
|
1825
2145
|
}
|
|
1826
2146
|
|
|
1827
2147
|
volatiles.protect = @max(8, volatiles.protect + 1);
|
|
@@ -1833,33 +2153,78 @@ pub const Effects = struct {
|
|
|
1833
2153
|
try options.log.singleturn(battle.active(player), state.move);
|
|
1834
2154
|
}
|
|
1835
2155
|
|
|
1836
|
-
pub fn psychUp(battle: anytype, player: Player,
|
|
1837
|
-
|
|
1838
|
-
|
|
2156
|
+
pub fn psychUp(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2157
|
+
var side = battle.side(player);
|
|
2158
|
+
const foe = battle.foe(player);
|
|
1839
2159
|
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
2160
|
+
// Pokémon Showdown should fail when the enemy has no boosts/unboosts but doesn't
|
|
2161
|
+
if (!showdown and @as(u32, @bitCast(foe.active.boosts)) == 0) {
|
|
2162
|
+
return options.log.fail(battle.active(player), .None);
|
|
2163
|
+
}
|
|
1843
2164
|
|
|
1844
|
-
|
|
1845
|
-
battle.
|
|
1846
|
-
try options.log.singlemove(battle.active(player), Move.Rage);
|
|
2165
|
+
side.active.boosts = foe.active.boosts;
|
|
2166
|
+
try options.log.copyboost(battle.active(player), battle.active(player.foe()));
|
|
1847
2167
|
}
|
|
1848
2168
|
|
|
1849
|
-
pub fn
|
|
1850
|
-
|
|
2169
|
+
pub fn pursuit(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2170
|
+
// TODO: remove switching variable and just look at Choice saved in Battle struct?
|
|
2171
|
+
if (battle.foe(player).active.volatiles.switching) state.damage *|= 2;
|
|
1851
2172
|
}
|
|
1852
2173
|
|
|
1853
|
-
pub
|
|
1854
|
-
|
|
2174
|
+
pub const rage = struct {
|
|
2175
|
+
pub fn start(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2176
|
+
battle.side(player).active.volatiles.Rage = true;
|
|
2177
|
+
try options.log.singlemove(battle.active(player), Move.Rage);
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
pub fn damage(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2181
|
+
var side = battle.side(player);
|
|
2182
|
+
var volatiles = &side.active.volatiles;
|
|
2183
|
+
|
|
2184
|
+
assert(volatiles.Rage);
|
|
2185
|
+
state.damage *|= (volatiles.rage +| 1);
|
|
2186
|
+
}
|
|
2187
|
+
};
|
|
2188
|
+
|
|
2189
|
+
pub fn rainDance(battle: anytype, _: Player, _: *State, options: anytype) !void {
|
|
2190
|
+
return weather(battle, .Rain, options);
|
|
1855
2191
|
}
|
|
1856
2192
|
|
|
1857
|
-
pub fn
|
|
1858
|
-
|
|
2193
|
+
pub fn rapidSpin(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2194
|
+
var side = battle.side(player);
|
|
2195
|
+
|
|
2196
|
+
if (side.active.volatiles.LeechSeed) {
|
|
2197
|
+
try options.log.end(battle.active(player), .LeechSeedFrom);
|
|
2198
|
+
side.active.volatiles.LeechSeed = false;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
if (side.conditions.Spikes) {
|
|
2202
|
+
try options.log.spikesend(battle.active(player));
|
|
2203
|
+
side.conditions.Spikes = false;
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
if (side.active.volatiles.bind.duration > 0) {
|
|
2207
|
+
const reason = @intFromEnum(End.Bind) + side.active.volatiles.bind.reason - 1;
|
|
2208
|
+
try options.log.end(battle.active(player), @enumFromInt(reason));
|
|
2209
|
+
side.active.volatiles.bind = .{};
|
|
2210
|
+
}
|
|
1859
2211
|
}
|
|
1860
2212
|
|
|
1861
2213
|
pub fn recoil(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1862
|
-
|
|
2214
|
+
var side = battle.side(player);
|
|
2215
|
+
var stored = side.stored();
|
|
2216
|
+
|
|
2217
|
+
assert(state.damage > 0);
|
|
2218
|
+
|
|
2219
|
+
const damage: i16 = @intCast(@max(state.damage / 4, 1));
|
|
2220
|
+
stored.hp = @intCast(@max(@as(i16, @intCast(stored.hp)) - damage, 0));
|
|
2221
|
+
|
|
2222
|
+
try options.log.damageOf(
|
|
2223
|
+
battle.active(player),
|
|
2224
|
+
stored,
|
|
2225
|
+
.RecoilOf,
|
|
2226
|
+
battle.active(player.foe()),
|
|
2227
|
+
);
|
|
1863
2228
|
}
|
|
1864
2229
|
|
|
1865
2230
|
pub fn reversal(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1887,36 +2252,84 @@ pub const Effects = struct {
|
|
|
1887
2252
|
try calcDamage(battle, player, state, options);
|
|
1888
2253
|
}
|
|
1889
2254
|
|
|
1890
|
-
pub
|
|
1891
|
-
|
|
1892
|
-
|
|
2255
|
+
pub const rollout = struct {
|
|
2256
|
+
pub fn check(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
2257
|
+
var side = battle.side(player);
|
|
1893
2258
|
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
2259
|
+
if (!side.active.volatiles.Rollout) {
|
|
2260
|
+
side.active.volatiles.attacks = 0;
|
|
2261
|
+
} else {
|
|
2262
|
+
// TODO SkipToBattleCommand(doturn_command)
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
1897
2265
|
|
|
1898
|
-
|
|
1899
|
-
|
|
2266
|
+
pub fn power(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2267
|
+
var side = battle.side(player);
|
|
2268
|
+
|
|
2269
|
+
if (Status.is(side.stored().status, .SLP)) return;
|
|
2270
|
+
|
|
2271
|
+
if (side.active.volatiles.attacks > 0) {
|
|
2272
|
+
// TODO set wSomeoneIsRampaging
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
if (state.miss) {
|
|
2276
|
+
side.active.volatiles.Rollout = false;
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
assert(side.active.volatiles.attacks < 5);
|
|
2281
|
+
side.active.volatiles.attacks += 1;
|
|
2282
|
+
side.active.volatiles.Rollout = side.active.volatiles.attacks != 5;
|
|
2283
|
+
|
|
2284
|
+
if (side.active.volatiles.DefenseCurl) state.damage *|= 2;
|
|
2285
|
+
}
|
|
2286
|
+
};
|
|
2287
|
+
|
|
2288
|
+
pub fn sandstorm(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2289
|
+
if (battle.field.weather != .Sandstorm) return weather(battle, .Sandstorm, options);
|
|
2290
|
+
return options.log.fail(battle.active(player), .None);
|
|
1900
2291
|
}
|
|
1901
2292
|
|
|
1902
|
-
pub fn
|
|
1903
|
-
|
|
2293
|
+
pub fn safeguard(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2294
|
+
return screens(battle, player, "Safeguard", "safeguard", options);
|
|
1904
2295
|
}
|
|
1905
2296
|
|
|
1906
|
-
pub fn
|
|
1907
|
-
|
|
2297
|
+
pub fn reflect(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2298
|
+
return screens(battle, player, "Reflect", "reflect", options);
|
|
1908
2299
|
}
|
|
1909
2300
|
|
|
1910
|
-
pub fn
|
|
1911
|
-
|
|
2301
|
+
pub fn lightScreen(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2302
|
+
return screens(battle, player, "LightScreen", "light_screen", options);
|
|
1912
2303
|
}
|
|
1913
2304
|
|
|
1914
|
-
|
|
1915
|
-
|
|
2305
|
+
fn screens(
|
|
2306
|
+
battle: anytype,
|
|
2307
|
+
player: Player,
|
|
2308
|
+
comptime key: []const u8,
|
|
2309
|
+
comptime value: []const u8,
|
|
2310
|
+
options: anytype,
|
|
2311
|
+
) !void {
|
|
2312
|
+
var side = battle.side(player);
|
|
2313
|
+
|
|
2314
|
+
if (@field(side.conditions, key)) return options.log.fail(battle.active(player), .None);
|
|
2315
|
+
|
|
2316
|
+
@field(side.conditions, key) = true;
|
|
2317
|
+
@field(side.conditions, value) = 5;
|
|
2318
|
+
try options.log.sidestart(player, @field(protocol.Side, key));
|
|
1916
2319
|
}
|
|
1917
2320
|
|
|
1918
|
-
pub fn
|
|
1919
|
-
|
|
2321
|
+
pub fn sketch(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2322
|
+
var side = battle.side(player);
|
|
2323
|
+
|
|
2324
|
+
side.last_used_move = .None;
|
|
2325
|
+
side.active.volatiles.dirty = false;
|
|
2326
|
+
|
|
2327
|
+
if (showdown) {
|
|
2328
|
+
// yes, actually "Splash". '|nothing' gets blindly mapped to '|-activate||move:Splash'
|
|
2329
|
+
try options.log.activate(battle.active(player), .Splash);
|
|
2330
|
+
} else {
|
|
2331
|
+
try options.log.fail(battle.active(player), .None);
|
|
2332
|
+
}
|
|
1920
2333
|
}
|
|
1921
2334
|
|
|
1922
2335
|
pub fn sleep(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1958,64 +2371,148 @@ pub const Effects = struct {
|
|
|
1958
2371
|
for (side.active.moves, 0..) |m, i| {
|
|
1959
2372
|
if (state.mslot - 1 != i and Move.get(m.id).extra.sleep_talk) n += 1;
|
|
1960
2373
|
}
|
|
1961
|
-
if (n == 0)
|
|
1962
|
-
try options.log.fail(battle.active(player), .None);
|
|
1963
|
-
return;
|
|
1964
|
-
}
|
|
2374
|
+
if (n == 0) return options.log.fail(battle.active(player), .None);
|
|
1965
2375
|
|
|
1966
2376
|
_ = try Rolls.sleepTalk(battle, player, n, state.mslot, options); // TODO
|
|
1967
2377
|
}
|
|
1968
2378
|
|
|
1969
|
-
pub fn snore(battle: anytype, player: Player, state: *State, options: anytype) !
|
|
1970
|
-
|
|
2379
|
+
pub fn snore(battle: anytype, player: Player, state: *State, options: anytype) !bool {
|
|
2380
|
+
if (Status.is(battle.side(player).stored().status, .SLP)) return true;
|
|
2381
|
+
state.damage = 0;
|
|
2382
|
+
state.miss = true; // TODO is this necessary?
|
|
2383
|
+
if (!showdown) try options.log.fail(battle.active(player), .None);
|
|
2384
|
+
return false;
|
|
1971
2385
|
}
|
|
1972
2386
|
|
|
1973
|
-
pub fn spikes(battle: anytype, player: Player,
|
|
1974
|
-
|
|
2387
|
+
pub fn spikes(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2388
|
+
var foe = battle.foe(player);
|
|
2389
|
+
if (foe.conditions.Spikes) return options.log.fail(battle.active(player), .None);
|
|
2390
|
+
|
|
2391
|
+
foe.conditions.Spikes = true;
|
|
2392
|
+
try options.log.sidestart(player.foe(), .Spikes);
|
|
1975
2393
|
}
|
|
1976
2394
|
|
|
1977
2395
|
pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1978
2396
|
_ = .{ battle, player, state, options }; // TODO
|
|
1979
|
-
|
|
2397
|
+
// var foe = battle.foe(player);
|
|
1980
2398
|
|
|
1981
|
-
|
|
1982
|
-
_ = .{ battle, player, state, options }; // TODO
|
|
1983
|
-
}
|
|
2399
|
+
// if (state.miss) return options.log.fail(battle.active(player), .None);
|
|
1984
2400
|
|
|
1985
|
-
|
|
1986
|
-
|
|
2401
|
+
// const last = foe.lastMove(false);
|
|
2402
|
+
// if (showdown) {
|
|
2403
|
+
// const roll = try Rolls.spite(battle, player, options);
|
|
2404
|
+
// } else {
|
|
2405
|
+
// if (last != .None) {}
|
|
2406
|
+
// }
|
|
1987
2407
|
}
|
|
1988
2408
|
|
|
1989
|
-
pub fn
|
|
1990
|
-
|
|
2409
|
+
pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2410
|
+
try options.log.activate(battle.active(player), .Splash);
|
|
1991
2411
|
}
|
|
1992
2412
|
|
|
1993
|
-
pub fn
|
|
1994
|
-
|
|
2413
|
+
pub fn substitute(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2414
|
+
var side = battle.side(player);
|
|
2415
|
+
if (side.active.volatiles.Substitute) {
|
|
2416
|
+
return options.log.fail(battle.active(player), .Substitute);
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
assert(side.stored().stats.hp <= 1023);
|
|
2420
|
+
const hp: u8 = @intCast(side.stored().stats.hp / 4);
|
|
2421
|
+
|
|
2422
|
+
if (hp == 0 or side.stored().hp <= hp) {
|
|
2423
|
+
return options.log.fail(battle.active(player), .Weak);
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
side.stored().hp -= hp;
|
|
2427
|
+
side.active.volatiles.substitute = hp;
|
|
2428
|
+
side.active.volatiles.Substitute = true;
|
|
2429
|
+
side.active.volatiles.bind = .{};
|
|
2430
|
+
try options.log.start(battle.active(player), .Substitute);
|
|
2431
|
+
try options.log.damage(battle.active(player), side.stored(), .None);
|
|
1995
2432
|
}
|
|
1996
2433
|
|
|
1997
|
-
pub fn
|
|
1998
|
-
|
|
2434
|
+
pub fn sunnyDay(battle: anytype, _: Player, _: *State, options: anytype) !void {
|
|
2435
|
+
return weather(battle, .Sun, options);
|
|
1999
2436
|
}
|
|
2000
2437
|
|
|
2001
|
-
|
|
2002
|
-
|
|
2438
|
+
fn weather(battle: anytype, w: Weather, options: anytype) !void {
|
|
2439
|
+
battle.field.weather = w;
|
|
2440
|
+
battle.field.weather_duration = 5;
|
|
2441
|
+
try options.log.weather(w, .None);
|
|
2003
2442
|
}
|
|
2004
2443
|
|
|
2005
|
-
pub fn teleport(battle: anytype, player: Player,
|
|
2006
|
-
|
|
2444
|
+
pub fn teleport(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2445
|
+
if (showdown) return;
|
|
2446
|
+
|
|
2447
|
+
try options.log.fail(battle.active(player), .None);
|
|
2448
|
+
try options.log.laststill();
|
|
2007
2449
|
}
|
|
2008
2450
|
|
|
2009
2451
|
pub fn thief(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2010
|
-
|
|
2452
|
+
const side = battle.side(player);
|
|
2453
|
+
const foe = battle.foe(player);
|
|
2454
|
+
|
|
2455
|
+
if (side.stored().item != .None or foe.stored().item == .None) return;
|
|
2456
|
+
// Pokémon Showdown incorrectly allows you steal Mail
|
|
2457
|
+
if (!showdown and foe.stored().item.isMail()) return;
|
|
2458
|
+
if (!state.proc) return;
|
|
2459
|
+
|
|
2460
|
+
side.stored().item = foe.stored().item;
|
|
2461
|
+
foe.stored().item = .None;
|
|
2462
|
+
|
|
2463
|
+
try options.log.item(
|
|
2464
|
+
battle.active(player),
|
|
2465
|
+
side.stored().item,
|
|
2466
|
+
battle.active(player.foe()),
|
|
2467
|
+
);
|
|
2011
2468
|
}
|
|
2012
2469
|
|
|
2013
|
-
pub fn thrashing(battle: anytype, player: Player,
|
|
2014
|
-
|
|
2470
|
+
pub fn thrashing(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2471
|
+
var side = battle.side(player);
|
|
2472
|
+
if (Status.is(side.stored().status, .SLP)) return;
|
|
2473
|
+
|
|
2474
|
+
assert(!side.active.volatiles.Thrashing);
|
|
2475
|
+
side.active.volatiles.Thrashing = true;
|
|
2476
|
+
side.active.volatiles.attacks = Rolls.attackingDuration(battle, player, options);
|
|
2477
|
+
// TODO wSomeoneIsRampaging = true;
|
|
2015
2478
|
}
|
|
2016
2479
|
|
|
2017
|
-
pub fn transform(battle: anytype, player: Player,
|
|
2018
|
-
|
|
2480
|
+
pub fn transform(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
2481
|
+
var side = battle.side(player);
|
|
2482
|
+
const foe = battle.foe(player);
|
|
2483
|
+
const foe_ident = battle.active(player.foe());
|
|
2484
|
+
|
|
2485
|
+
side.last_used_move = .None;
|
|
2486
|
+
side.active.volatiles.dirty = false;
|
|
2487
|
+
|
|
2488
|
+
if (foe.active.volatiles.Transform) {
|
|
2489
|
+
return options.log.fail(battle.active(player), .None);
|
|
2490
|
+
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2491
|
+
if (!showdown) return options.log.fail(battle.active(player), .None);
|
|
2492
|
+
try options.log.lastmiss();
|
|
2493
|
+
return options.log.miss(battle.active(player));
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
side.active.volatiles.Transform = true;
|
|
2497
|
+
side.active.volatiles.transform = foe_ident.int();
|
|
2498
|
+
side.active.volatiles.disable = .{};
|
|
2499
|
+
|
|
2500
|
+
// HP is not copied by Transform
|
|
2501
|
+
side.active.stats.atk = foe.active.stats.atk;
|
|
2502
|
+
side.active.stats.def = foe.active.stats.def;
|
|
2503
|
+
side.active.stats.spe = foe.active.stats.spe;
|
|
2504
|
+
side.active.stats.spa = foe.active.stats.spa;
|
|
2505
|
+
side.active.stats.spd = foe.active.stats.spd;
|
|
2506
|
+
|
|
2507
|
+
side.active.species = foe.active.species;
|
|
2508
|
+
side.active.types = foe.active.types;
|
|
2509
|
+
side.active.boosts = foe.active.boosts;
|
|
2510
|
+
for (foe.active.moves, 0..) |m, i| {
|
|
2511
|
+
side.active.moves[i].id = m.id;
|
|
2512
|
+
side.active.moves[i].pp = if (m.id != .None) if (m.id == .Sketch) 1 else 5 else 0;
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
try options.log.transform(battle.active(player), foe_ident);
|
|
2019
2516
|
}
|
|
2020
2517
|
|
|
2021
2518
|
pub fn triAttack(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2027,19 +2524,14 @@ pub const Effects = struct {
|
|
|
2027
2524
|
.FRZ => freezeChance(battle, player, state, options),
|
|
2028
2525
|
.BRN => burnChance(battle, player, state, options),
|
|
2029
2526
|
};
|
|
2030
|
-
} else if (!showdown
|
|
2527
|
+
} else if (!showdown) battle.rng.advance(1);
|
|
2031
2528
|
}
|
|
2032
2529
|
|
|
2033
2530
|
pub fn tripleKick(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2034
2531
|
_ = .{ battle, player, state, options }; // TODO
|
|
2035
2532
|
}
|
|
2036
2533
|
|
|
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 =
|
|
2534
|
+
const STAT_UP_REASONS =
|
|
2043
2535
|
[_]Boost{ .Attack, .Defense, .Speed, .SpecialAttack, .SpecialDefense };
|
|
2044
2536
|
|
|
2045
2537
|
pub fn allStatUpChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2049,20 +2541,15 @@ pub const Effects = struct {
|
|
|
2049
2541
|
var boosts = &side.active.boosts;
|
|
2050
2542
|
|
|
2051
2543
|
if (!state.proc) return;
|
|
2052
|
-
inline for (
|
|
2544
|
+
inline for (STATS, STAT_UP_REASONS) |s, r| {
|
|
2053
2545
|
assert(@field(boosts, s) >= -6 and @field(boosts, s) <= 6);
|
|
2054
2546
|
if (@field(boosts, s) == 6 or @field(stats, s) == MAX_STAT_VALUE) {
|
|
2055
2547
|
try options.log.boost(battle.active(player), r, 0);
|
|
2056
2548
|
} else {
|
|
2057
|
-
|
|
2058
|
-
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
|
|
2059
|
-
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2060
|
-
@field(stats, s) = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2549
|
+
try boostStat(battle, side, stats, boosts, s, 1);
|
|
2061
2550
|
try options.log.boost(battle.active(player), r, 1);
|
|
2062
2551
|
}
|
|
2063
2552
|
}
|
|
2064
|
-
|
|
2065
|
-
statusModify(side.stored().status, stats);
|
|
2066
2553
|
}
|
|
2067
2554
|
|
|
2068
2555
|
pub fn boost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2070,84 +2557,55 @@ pub const Effects = struct {
|
|
|
2070
2557
|
var side = battle.side(player);
|
|
2071
2558
|
const ident = battle.active(player);
|
|
2072
2559
|
|
|
2073
|
-
if (state.miss) return
|
|
2560
|
+
if (state.miss) return log.fail(ident, .None);
|
|
2074
2561
|
|
|
2075
2562
|
var stats = &side.active.stats;
|
|
2076
2563
|
var boosts = &side.active.boosts;
|
|
2077
2564
|
|
|
2078
2565
|
const move = Move.get(state.move);
|
|
2079
2566
|
switch (move.effect) {
|
|
2080
|
-
.AttackUp1, .AttackUp2, .Rage => {
|
|
2567
|
+
.AttackUp1, .AttackUp2, .Rage, .Swagger => {
|
|
2081
2568
|
assert(boosts.atk >= -6 and boosts.atk <= 6);
|
|
2082
|
-
if (boosts.atk == 6 or stats.atk == MAX_STAT_VALUE)
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
const n: u2 = if (move.effect == .AttackUp2) 2 else 1;
|
|
2086
|
-
boosts.atk = @as(i4, @intCast(@min(6, @as(i8, boosts.atk) + n)));
|
|
2569
|
+
if (boosts.atk == 6 or stats.atk == MAX_STAT_VALUE) return log.fail(ident, .None);
|
|
2570
|
+
const n: u2 = if (move.effect == .AttackUp2 or move.effect == .Swagger) 2 else 1;
|
|
2571
|
+
try boostStat(battle, side, stats, boosts, "atk", n);
|
|
2087
2572
|
const reason = if (showdown and move.effect == .Rage) Boost.Rage else Boost.Attack;
|
|
2088
|
-
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2089
|
-
const stat = unmodifiedStats(battle, side).atk;
|
|
2090
|
-
stats.atk = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2091
2573
|
try log.boost(ident, reason, n);
|
|
2092
2574
|
},
|
|
2093
|
-
.DefenseUp1, .DefenseUp2 => {
|
|
2575
|
+
.DefenseUp1, .DefenseUp2, .SkullBash => {
|
|
2094
2576
|
assert(boosts.def >= -6 and boosts.def <= 6);
|
|
2095
|
-
if (boosts.def == 6 or stats.def == MAX_STAT_VALUE)
|
|
2096
|
-
return try log.fail(ident, .None);
|
|
2097
|
-
}
|
|
2577
|
+
if (boosts.def == 6 or stats.def == MAX_STAT_VALUE) return log.fail(ident, .None);
|
|
2098
2578
|
const n: u2 = if (move.effect == .DefenseUp2) 2 else 1;
|
|
2099
|
-
|
|
2100
|
-
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2101
|
-
const stat = unmodifiedStats(battle, side).def;
|
|
2102
|
-
stats.def = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2579
|
+
try boostStat(battle, side, stats, boosts, "def", n);
|
|
2103
2580
|
try log.boost(ident, .Defense, n);
|
|
2104
2581
|
},
|
|
2105
2582
|
.SpeedUp2 => {
|
|
2106
2583
|
assert(boosts.spe >= -6 and boosts.spe <= 6);
|
|
2107
|
-
if (boosts.spe == 6 or stats.spe == MAX_STAT_VALUE)
|
|
2108
|
-
|
|
2109
|
-
}
|
|
2110
|
-
boosts.spe = @intCast(@min(6, @as(i8, boosts.spe) + 2));
|
|
2111
|
-
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2112
|
-
const stat = unmodifiedStats(battle, side).spe;
|
|
2113
|
-
stats.spe = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2584
|
+
if (boosts.spe == 6 or stats.spe == MAX_STAT_VALUE) return log.fail(ident, .None);
|
|
2585
|
+
try boostStat(battle, side, stats, boosts, "spe", 2);
|
|
2114
2586
|
try log.boost(ident, .Speed, 2);
|
|
2115
2587
|
},
|
|
2116
2588
|
.SpAtkUp1 => {
|
|
2117
2589
|
assert(boosts.spa >= -6 and boosts.spa <= 6);
|
|
2118
|
-
if (boosts.spa == 6 or stats.spa == MAX_STAT_VALUE)
|
|
2119
|
-
|
|
2120
|
-
}
|
|
2121
|
-
boosts.spa = @intCast(@min(6, @as(i8, boosts.spa) + 1));
|
|
2122
|
-
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.spa) + 6))];
|
|
2123
|
-
const stat = unmodifiedStats(battle, side).spa;
|
|
2124
|
-
stats.spa = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2590
|
+
if (boosts.spa == 6 or stats.spa == MAX_STAT_VALUE) return log.fail(ident, .None);
|
|
2591
|
+
try boostStat(battle, side, stats, boosts, "spa", 1);
|
|
2125
2592
|
try log.boost(ident, .SpecialAttack, 1);
|
|
2126
2593
|
},
|
|
2127
2594
|
.SpDefUp2 => {
|
|
2128
2595
|
assert(boosts.spd >= -6 and boosts.spd <= 6);
|
|
2129
|
-
if (boosts.spd == 6 or stats.spd == MAX_STAT_VALUE)
|
|
2130
|
-
|
|
2131
|
-
}
|
|
2132
|
-
boosts.spd = @intCast(@min(6, @as(i8, boosts.spd) + 2));
|
|
2133
|
-
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.spd) + 6))];
|
|
2134
|
-
const stat = unmodifiedStats(battle, side).spd;
|
|
2135
|
-
stats.spd = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2596
|
+
if (boosts.spd == 6 or stats.spd == MAX_STAT_VALUE) return log.fail(ident, .None);
|
|
2597
|
+
try boostStat(battle, side, stats, boosts, "spd", 2);
|
|
2136
2598
|
try log.boost(ident, .SpecialDefense, 2);
|
|
2137
2599
|
},
|
|
2138
2600
|
.EvasionUp1 => {
|
|
2139
2601
|
assert(boosts.evasion >= -6 and boosts.evasion <= 6);
|
|
2140
|
-
if (boosts.evasion == 6)
|
|
2141
|
-
return try log.fail(ident, .None);
|
|
2142
|
-
}
|
|
2602
|
+
if (boosts.evasion == 6) return log.fail(ident, .None);
|
|
2143
2603
|
boosts.evasion = @intCast(@min(6, @as(i8, boosts.evasion) + 1));
|
|
2144
2604
|
try log.boost(ident, .Evasion, 1);
|
|
2145
2605
|
if (state.move == .Minimize) side.active.volatiles.minimized = true;
|
|
2146
2606
|
},
|
|
2147
2607
|
else => unreachable,
|
|
2148
2608
|
}
|
|
2149
|
-
|
|
2150
|
-
statusModify(side.stored().status, stats);
|
|
2151
2609
|
}
|
|
2152
2610
|
|
|
2153
2611
|
pub fn unboost(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -2157,7 +2615,7 @@ pub const Effects = struct {
|
|
|
2157
2615
|
|
|
2158
2616
|
if (foe.active.volatiles.Mist) {
|
|
2159
2617
|
try log.activate(foe_ident, .Mist);
|
|
2160
|
-
return
|
|
2618
|
+
return log.fail(foe_ident, .None);
|
|
2161
2619
|
}
|
|
2162
2620
|
|
|
2163
2621
|
const fail = foe.active.volatiles.Substitute or state.miss or !state.proc;
|
|
@@ -2173,16 +2631,12 @@ pub const Effects = struct {
|
|
|
2173
2631
|
return if (move.effect != .AttackDownChance) try log.fail(foe_ident, .None);
|
|
2174
2632
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2175
2633
|
try options.log.lastmiss();
|
|
2176
|
-
|
|
2177
|
-
return;
|
|
2634
|
+
return options.log.miss(battle.active(player));
|
|
2178
2635
|
} else if (stats.atk == 1) {
|
|
2179
2636
|
return if (move.effect != .AttackDownChance) try log.fail(foe_ident, .None);
|
|
2180
2637
|
}
|
|
2181
2638
|
const n: u2 = if (move.effect == .AttackDown2) 2 else 1;
|
|
2182
|
-
|
|
2183
|
-
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.atk) + 6))];
|
|
2184
|
-
const stat = unmodifiedStats(battle, foe).atk;
|
|
2185
|
-
stats.atk = @max(1, stat * mod[0] / mod[1]);
|
|
2639
|
+
try unboostStat(battle, foe, stats, boosts, "atk", n);
|
|
2186
2640
|
try log.boost(foe_ident, .Attack, -@as(i8, n));
|
|
2187
2641
|
},
|
|
2188
2642
|
.DefenseDown1, .DefenseDown2, .DefenseDownChance => {
|
|
@@ -2191,16 +2645,12 @@ pub const Effects = struct {
|
|
|
2191
2645
|
return if (move.effect != .DefenseDownChance) try log.fail(foe_ident, .None);
|
|
2192
2646
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2193
2647
|
try options.log.lastmiss();
|
|
2194
|
-
|
|
2195
|
-
return;
|
|
2648
|
+
return options.log.miss(battle.active(player));
|
|
2196
2649
|
} else if (stats.def == 1) {
|
|
2197
2650
|
return if (move.effect != .DefenseDownChance) try log.fail(foe_ident, .None);
|
|
2198
2651
|
}
|
|
2199
2652
|
const n: u2 = if (move.effect == .DefenseDown2) 2 else 1;
|
|
2200
|
-
|
|
2201
|
-
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.def) + 6))];
|
|
2202
|
-
const stat = unmodifiedStats(battle, foe).def;
|
|
2203
|
-
stats.def = @max(1, stat * mod[0] / mod[1]);
|
|
2653
|
+
try unboostStat(battle, foe, stats, boosts, "def", n);
|
|
2204
2654
|
try log.boost(foe_ident, .Defense, -@as(i8, n));
|
|
2205
2655
|
},
|
|
2206
2656
|
.SpeedDown1, .SpeedDown2, .SpeedDownChance => {
|
|
@@ -2209,31 +2659,22 @@ pub const Effects = struct {
|
|
|
2209
2659
|
return if (move.effect != .SpeedDownChance) try log.fail(foe_ident, .None);
|
|
2210
2660
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2211
2661
|
try options.log.lastmiss();
|
|
2212
|
-
|
|
2213
|
-
return;
|
|
2662
|
+
return options.log.miss(battle.active(player));
|
|
2214
2663
|
} else if (stats.spe == 1) {
|
|
2215
2664
|
return if (move.effect != .SpeedDownChance) try log.fail(foe_ident, .None);
|
|
2216
2665
|
}
|
|
2217
2666
|
const n: u2 = if (move.effect == .SpeedDown2) 2 else 1;
|
|
2218
|
-
|
|
2219
|
-
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.spe) + 6))];
|
|
2220
|
-
const stat = unmodifiedStats(battle, foe).spe;
|
|
2221
|
-
stats.spe = @max(1, stat * mod[0] / mod[1]);
|
|
2667
|
+
try unboostStat(battle, foe, stats, boosts, "spe", n);
|
|
2222
2668
|
try log.boost(foe_ident, .Speed, -@as(i8, n));
|
|
2223
|
-
assert(boosts.spe >= -6);
|
|
2224
2669
|
},
|
|
2225
2670
|
.SpDefDownChance => {
|
|
2226
2671
|
assert(boosts.spd >= -6 and boosts.spd <= 6);
|
|
2227
2672
|
if (fail or boosts.spd == -6) return;
|
|
2228
2673
|
if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2229
2674
|
try options.log.lastmiss();
|
|
2230
|
-
|
|
2231
|
-
return;
|
|
2675
|
+
return options.log.miss(battle.active(player));
|
|
2232
2676
|
} else if (stats.spd == 1) return;
|
|
2233
|
-
|
|
2234
|
-
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, boosts.spd) + 6))];
|
|
2235
|
-
const stat = unmodifiedStats(battle, foe).spd;
|
|
2236
|
-
stats.spd = @max(1, stat * mod[0] / mod[1]);
|
|
2677
|
+
try unboostStat(battle, foe, stats, boosts, "spd", 1);
|
|
2237
2678
|
try log.boost(foe_ident, .SpecialDefense, -1);
|
|
2238
2679
|
},
|
|
2239
2680
|
.AccuracyDown1, .AccuracyDownChance => {
|
|
@@ -2242,16 +2683,13 @@ pub const Effects = struct {
|
|
|
2242
2683
|
return if (move.effect != .AccuracyDownChance) try log.fail(foe_ident, .None);
|
|
2243
2684
|
} else if (foe.active.volatiles.Flying or foe.active.volatiles.Underground) {
|
|
2244
2685
|
try options.log.lastmiss();
|
|
2245
|
-
|
|
2246
|
-
return;
|
|
2686
|
+
return options.log.miss(battle.active(player));
|
|
2247
2687
|
}
|
|
2248
2688
|
boosts.accuracy = @intCast(@max(-6, @as(i8, boosts.accuracy) - 1));
|
|
2249
2689
|
try log.boost(foe_ident, .Accuracy, -1);
|
|
2250
2690
|
},
|
|
2251
2691
|
else => unreachable,
|
|
2252
2692
|
}
|
|
2253
|
-
|
|
2254
|
-
statusModify(foe.stored().status, stats);
|
|
2255
2693
|
}
|
|
2256
2694
|
};
|
|
2257
2695
|
|
|
@@ -2261,6 +2699,15 @@ fn unmodifiedStats(battle: anytype, side: *Side) *Stats(u16) {
|
|
|
2261
2699
|
return &battle.side(id.player).pokemon[id.id - 1].stats;
|
|
2262
2700
|
}
|
|
2263
2701
|
|
|
2702
|
+
fn recomputeStats(battle: anytype, side: *Side) void {
|
|
2703
|
+
var stats = unmodifiedStats(battle, side);
|
|
2704
|
+
inline for (STATS) |s| {
|
|
2705
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(side.active.boosts, s)) + 6))];
|
|
2706
|
+
const val = @field(stats, s) * mod[0] / mod[1];
|
|
2707
|
+
@field(side.active.stats, s) = @max(1, @min(MAX_STAT_VALUE, val));
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2264
2711
|
fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
2265
2712
|
if (Status.is(status, .PAR)) {
|
|
2266
2713
|
stats.spe = @max(stats.spe / 4, 1);
|
|
@@ -2269,6 +2716,52 @@ fn statusModify(status: u8, stats: *Stats(u16)) void {
|
|
|
2269
2716
|
}
|
|
2270
2717
|
}
|
|
2271
2718
|
|
|
2719
|
+
fn boostStat(
|
|
2720
|
+
battle: anytype,
|
|
2721
|
+
side: *Side,
|
|
2722
|
+
stats: *Stats(u16),
|
|
2723
|
+
boosts: *Boosts,
|
|
2724
|
+
comptime s: []const u8,
|
|
2725
|
+
n: u2,
|
|
2726
|
+
) !void {
|
|
2727
|
+
@field(boosts, s) = @intCast(@min(6, @as(i8, @field(boosts, s)) + n));
|
|
2728
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
|
|
2729
|
+
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2730
|
+
@field(stats, s) = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2731
|
+
if (comptime std.mem.eql(u8, s, "atk")) {
|
|
2732
|
+
if (Status.is(side.stored().status, .BRN)) {
|
|
2733
|
+
@field(stats, s) = @max(@field(stats, s) / 2, 1);
|
|
2734
|
+
}
|
|
2735
|
+
} else if (comptime std.mem.eql(u8, s, "spe")) {
|
|
2736
|
+
if (Status.is(side.stored().status, .PAR)) {
|
|
2737
|
+
@field(stats, s) = @max(@field(stats, s) / 4, 1);
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
fn unboostStat(
|
|
2743
|
+
battle: anytype,
|
|
2744
|
+
side: *Side,
|
|
2745
|
+
stats: *Stats(u16),
|
|
2746
|
+
boosts: *Boosts,
|
|
2747
|
+
comptime s: []const u8,
|
|
2748
|
+
n: u2,
|
|
2749
|
+
) !void {
|
|
2750
|
+
@field(boosts, s) = @intCast(@max(-6, @as(i8, @field(boosts, s)) - n));
|
|
2751
|
+
var mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
|
|
2752
|
+
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2753
|
+
@field(stats, s) = @max(1, stat * mod[0] / mod[1]);
|
|
2754
|
+
if (comptime std.mem.eql(u8, s, "atk")) {
|
|
2755
|
+
if (Status.is(side.stored().status, .BRN)) {
|
|
2756
|
+
@field(stats, s) = @max(@field(stats, s) / 2, 1);
|
|
2757
|
+
}
|
|
2758
|
+
} else if (comptime std.mem.eql(u8, s, "spe")) {
|
|
2759
|
+
if (Status.is(side.stored().status, .PAR)) {
|
|
2760
|
+
@field(stats, s) = @max(@field(stats, s) / 4, 1);
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2272
2765
|
fn isForced(active: anytype) bool {
|
|
2273
2766
|
return active.volatiles.Recharging or active.volatiles.Rage or
|
|
2274
2767
|
active.volatiles.Thrashing or active.volatiles.Charging or
|
|
@@ -2458,7 +2951,7 @@ pub const Rolls = struct {
|
|
|
2458
2951
|
}
|
|
2459
2952
|
|
|
2460
2953
|
fn magnitude(battle: anytype, player: Player, options: anytype) !u8 {
|
|
2461
|
-
const num = if (options.calc.overridden(player, .magnitude)) |val|
|
|
2954
|
+
const num: u8 = if (options.calc.overridden(player, .magnitude)) |val|
|
|
2462
2955
|
val + 3
|
|
2463
2956
|
else if (showdown) num: {
|
|
2464
2957
|
const r = battle.rng.range(u8, 0, 100);
|
|
@@ -2717,12 +3210,12 @@ pub const Rolls = struct {
|
|
|
2717
3210
|
}
|
|
2718
3211
|
|
|
2719
3212
|
fn metronome(battle: anytype, player: Player, options: anytype) !Move {
|
|
2720
|
-
const moves = battle.
|
|
3213
|
+
const moves = battle.side(player).active.moves;
|
|
2721
3214
|
|
|
2722
3215
|
var n: u2 = 0;
|
|
2723
3216
|
for (moves) |m| {
|
|
2724
|
-
if (m == .None) break;
|
|
2725
|
-
if (Move.get(m).metronome) n += 1;
|
|
3217
|
+
if (m.id == .None) break;
|
|
3218
|
+
if (Move.get(m.id).extra.metronome) n += 1;
|
|
2726
3219
|
}
|
|
2727
3220
|
|
|
2728
3221
|
// TODO: consider throwing error instead of rerolling?
|
|
@@ -2733,10 +3226,11 @@ pub const Rolls = struct {
|
|
|
2733
3226
|
|
|
2734
3227
|
const move: Move = overridden orelse move: {
|
|
2735
3228
|
if (showdown) {
|
|
2736
|
-
var r = battle.rng.range(u8, 0, Move.METRONOME.len - n);
|
|
3229
|
+
var r = battle.rng.range(u8, 0, @intCast(Move.METRONOME.len - n));
|
|
2737
3230
|
for (moves) |m| {
|
|
2738
|
-
if (m == .None
|
|
2739
|
-
if (
|
|
3231
|
+
if (m.id == .None) break;
|
|
3232
|
+
if (@intFromEnum(m.id) > @intFromEnum(Move.METRONOME[r])) break;
|
|
3233
|
+
if (Move.get(m.id).extra.metronome) r -= 1;
|
|
2740
3234
|
}
|
|
2741
3235
|
break :move Move.METRONOME[r];
|
|
2742
3236
|
} else {
|
|
@@ -2751,7 +3245,7 @@ pub const Rolls = struct {
|
|
|
2751
3245
|
}
|
|
2752
3246
|
};
|
|
2753
3247
|
|
|
2754
|
-
assert(Move.get(move).metronome);
|
|
3248
|
+
assert(Move.get(move).extra.metronome);
|
|
2755
3249
|
try options.chance.metronome(player, move, n);
|
|
2756
3250
|
return move;
|
|
2757
3251
|
}
|
|
@@ -2975,7 +3469,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
2975
3469
|
}
|
|
2976
3470
|
|
|
2977
3471
|
var slot: u4 = 2;
|
|
2978
|
-
while (!active.volatiles.
|
|
3472
|
+
while (!active.volatiles.Trapped and slot <= 6) : (slot += 1) {
|
|
2979
3473
|
const id = side.order[slot - 1];
|
|
2980
3474
|
if (id == 0 or side.pokemon[id - 1].hp == 0) continue;
|
|
2981
3475
|
out[n] = .{ .type = .Switch, .data = slot };
|