@pkmn/engine 0.1.0-dev.207fa86a → 0.1.0-dev.2ec67dba
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/build/pkg/index.d.ts +1 -1
- package/build/pkg/index.js +1 -1
- package/build/pkg/index.js.map +1 -1
- package/package.json +5 -4
- package/src/lib/common/debug.zig +64 -0
- package/src/lib/gen1/README.md +2 -1
- package/src/lib/gen1/data.zig +1 -1
- package/src/lib/gen1/mechanics.zig +54 -24
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ implementation of both Pokémon battles as defined by the original game code, an
|
|
|
22
22
|
Showdown](https://pokemonshowdown.com/)[^1] simulator which represents Pokémon battling as practically
|
|
23
23
|
interpreted online.
|
|
24
24
|
|
|
25
|
-
The pkmn engine is
|
|
25
|
+
The pkmn engine is [**more than 1000× faster**](docs/TESTING.md#results) than the
|
|
26
26
|
[patched](docs/TESTING.md#patches) [Pokémon Showdown simulator
|
|
27
27
|
code](https://github.com/smogon/pokemon-showdown) when playing out supported formats in
|
|
28
28
|
compatibility mode and is extensively [tested](docs/TESTING.md) and [documented](docs). Note,
|
|
@@ -74,7 +74,7 @@ compiler (see above regarding minimum version) and download one to
|
|
|
74
74
|
`node_module/@pkmn/engine/build/bin` if it can't find one, as well as looking for (and downloading,
|
|
75
75
|
if necessary) the required Node headers needed to successfully build the addons natively.
|
|
76
76
|
|
|
77
|
-
**If you have configured
|
|
77
|
+
**If you have configured `npm` to `--ignore-scripts` you must either run `npx install-pkmn-engine`
|
|
78
78
|
directly or build the addons manually and place the artifacts in the expected paths.**
|
|
79
79
|
|
|
80
80
|
### `pkmn`
|
package/build/pkg/index.d.ts
CHANGED
|
@@ -362,7 +362,7 @@ export declare class Choice {
|
|
|
362
362
|
* Only numeric choice data is supported.
|
|
363
363
|
*/
|
|
364
364
|
static parse(choice: string): Choice;
|
|
365
|
-
/** Formats a `Choice`
|
|
365
|
+
/** Formats a `Choice` into a Pokémon Showdown compatible choice string. */
|
|
366
366
|
static format(choice: Choice): string;
|
|
367
367
|
/** Returns the "pass" `Choice`. */
|
|
368
368
|
static pass(): Choice;
|
package/build/pkg/index.js
CHANGED
|
@@ -80,7 +80,7 @@ class Choice {
|
|
|
80
80
|
const data = +(m[4] ?? m[7] ?? 0);
|
|
81
81
|
return { type, data };
|
|
82
82
|
}
|
|
83
|
-
/** Formats a `Choice`
|
|
83
|
+
/** Formats a `Choice` into a Pokémon Showdown compatible choice string. */
|
|
84
84
|
static format(choice) {
|
|
85
85
|
return choice.type === 'pass' ? choice.type : `${choice.type} ${choice.data}`;
|
|
86
86
|
}
|
package/build/pkg/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/pkg/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/pkg/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAIA,+CAAiC;AACjC,iCAA8B;AAC9B,6CAA+B;AA8V/B,2CAA2C;AAC9B,QAAA,MAAM,GAAG,IAAI;IACxB,2EAA2E;IAC3E,MAAM,CAAC,GAAe,EAAE,OAAsB;QAC5C,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAChC,MAAM,MAAM,GAAG,aAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,QAAQ,GAAG,CAAC,GAAG,EAAE;YACjB,KAAK,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;YACxD,OAAO,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,mBAAmB,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;SACtD;IACH,CAAC;IAED;;;OAGG;IACH,OAAO,CAAC,GAAe,EAAE,MAAc,EAAE,OAAuB;QAC9D,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAChC,MAAM,MAAM,GAAG,aAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,QAAQ,GAAG,CAAC,GAAG,EAAE;YACjB,KAAK,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;YACjE,OAAO,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,mBAAmB,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;SACtD;IACH,CAAC;CACF,CAAC;AAEF,mDAAmD;AACnD,MAAa,MAAM;IACjB,+BAA+B;IAC/B,MAAM,CAAC,KAAK,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAU,CAAC;IAEzC,MAAM,CAAC,IAAI,GAAG,EAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAU,CAAC;IAC/C,MAAM,CAAC,KAAK,GAAG,kDAAkD,CAAC;IAE5E,gBAAuB,CAAC;IAExB,sDAAsD;IACtD,MAAM,CAAC,MAAM,CAAC,IAAY;QACxB,OAAO,EAAC,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,EAAC,CAAC;IAC5D,CAAC;IAED,oDAAoD;IACpD,MAAM,CAAC,MAAM,CAAC,MAAe;QAC3B,OAAO,CAAC,MAAM;YACZ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACpF,CAAC,CAAC,CAAC,CAAC,CAAC;IACT,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,KAAK,CAAC,MAAc;QACzB,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACpC,IAAI,CAAC,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,MAAM,GAAG,CAAC,CAAC;QACvD,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAmB,CAAC;QACtD,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAClC,OAAO,EAAC,IAAI,EAAE,IAAI,EAAC,CAAC;IACtB,CAAC;IAED,2EAA2E;IAC3E,MAAM,CAAC,MAAM,CAAC,MAAc;QAC1B,OAAO,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;IAChF,CAAC;IAED,mCAAmC;IACnC,MAAM,CAAC,IAAI;QACT,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;IAED,wDAAwD;IACxD,MAAM,CAAC,IAAI,CAAC,IAAuB;QACjC,OAAO,EAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAC,CAAC;IAC9B,CAAC;IAED,0DAA0D;IAC1D,MAAM,CAAC,MAAM,CAAC,IAAuB;QACnC,OAAO,EAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAC,CAAC;IAChC,CAAC;;AAnDU,wBAAM;AAsDnB,kDAAkD;AAClD,MAAa,MAAM;IACjB,8BAA8B;IAC9B,MAAM,CAAC,KAAK,GAAG,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAU,CAAC;IAEnE,gBAAuB,CAAC;IAExB,wDAAwD;IACxD,MAAM,CAAC,MAAM,CAAC,IAAY;QACxB,OAAO;YACL,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,MAAM,CAAC;YACjC,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;YACpC,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC;SAC5B,CAAC;IACJ,CAAC;;AAbU,wBAAM;AAgBnB,uCAAwE;AAApD,gGAAA,IAAI,OAAA;AAAE,oGAAA,QAAQ,OAAA;AAAe,+FAAA,GAAG,OAAA;AACpD,+BAA8B;AAAtB,8FAAA,MAAM,OAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pkmn/engine",
|
|
3
|
-
"version": "0.1.0-dev.
|
|
3
|
+
"version": "0.1.0-dev.2ec67dba",
|
|
4
4
|
"description": "A minimal, complete, Pokémon battle simulation engine optimized for performance",
|
|
5
5
|
"repository": "github:pkmn/engine",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"src/lib/binding/node.zig",
|
|
14
14
|
"src/lib/binding/wasm.zig",
|
|
15
15
|
"src/lib/common/data.zig",
|
|
16
|
+
"src/lib/common/debug.zig",
|
|
16
17
|
"src/lib/common/options.zig",
|
|
17
18
|
"src/lib/common/protocol.zig",
|
|
18
19
|
"src/lib/common/rng.zig",
|
|
@@ -56,14 +57,14 @@
|
|
|
56
57
|
"access": "public"
|
|
57
58
|
},
|
|
58
59
|
"dependencies": {
|
|
59
|
-
"@pkmn/data": "^0.7.
|
|
60
|
+
"@pkmn/data": "^0.7.34",
|
|
60
61
|
"@pkmn/protocol": "^0.6.11"
|
|
61
62
|
},
|
|
62
63
|
"devDependencies": {
|
|
63
64
|
"@pkmn/eslint-config": "^3.0.0-alpha.0",
|
|
64
65
|
"@pkmn/img": "^0.2.8",
|
|
65
66
|
"@pkmn/sets": "^5.0.4",
|
|
66
|
-
"@pkmn/sim": "0.7.
|
|
67
|
+
"@pkmn/sim": "0.7.34",
|
|
67
68
|
"@types/html-minifier": "^4.0.2",
|
|
68
69
|
"@types/jest": "^29.5.0",
|
|
69
70
|
"@types/minimist": "^1.2.2",
|
|
@@ -188,4 +189,4 @@
|
|
|
188
189
|
}
|
|
189
190
|
]
|
|
190
191
|
}
|
|
191
|
-
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const std = @import("std");
|
|
2
|
+
|
|
3
|
+
const debug = std.debug;
|
|
4
|
+
const io = std.io;
|
|
5
|
+
|
|
6
|
+
pub fn print(value: anytype) void {
|
|
7
|
+
debug.getStderrMutex().lock();
|
|
8
|
+
defer debug.getStderrMutex().unlock();
|
|
9
|
+
const stderr = io.getStdErr().writer();
|
|
10
|
+
|
|
11
|
+
nosuspend {
|
|
12
|
+
stderr.writeAll("\x1b[41m") catch return;
|
|
13
|
+
switch (@typeInfo(@TypeOf(value))) {
|
|
14
|
+
.Struct => |info| {
|
|
15
|
+
if (info.is_tuple) {
|
|
16
|
+
inline for (info.fields, 0..) |f, i| {
|
|
17
|
+
inspect(@field(value, f.name));
|
|
18
|
+
if (i < info.fields.len - 1) stderr.writeAll(" ") catch return;
|
|
19
|
+
}
|
|
20
|
+
} else {
|
|
21
|
+
inspect(value);
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
else => inspect(value),
|
|
25
|
+
}
|
|
26
|
+
stderr.writeAll("\x1b[K\x1b[0m\n") catch return;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fn inspect(value: anytype) void {
|
|
31
|
+
const stderr = io.getStdErr().writer();
|
|
32
|
+
|
|
33
|
+
nosuspend {
|
|
34
|
+
const err = "Unable to format type '" ++ @typeName(@TypeOf(value)) ++ "'";
|
|
35
|
+
switch (@typeInfo(@TypeOf(value))) {
|
|
36
|
+
.Array => |info| {
|
|
37
|
+
if (info.child == u8) return stderr.print("{s}", .{value}) catch return;
|
|
38
|
+
@compileError(err);
|
|
39
|
+
},
|
|
40
|
+
.Pointer => |ptr_info| switch (ptr_info.size) {
|
|
41
|
+
.One => switch (@typeInfo(ptr_info.child)) {
|
|
42
|
+
.Array => |info| {
|
|
43
|
+
if (info.child == u8) return stderr.print("{s}", .{value}) catch return;
|
|
44
|
+
@compileError(err);
|
|
45
|
+
},
|
|
46
|
+
.Enum, .Union, .Struct => return inspect(value.*),
|
|
47
|
+
else => @compileError(err),
|
|
48
|
+
},
|
|
49
|
+
.Many, .C => {
|
|
50
|
+
if (ptr_info.sentinel) |_| return inspect(std.mem.span(value));
|
|
51
|
+
if (ptr_info.child == u8) {
|
|
52
|
+
return stderr.print("{s}", .{std.mem.span(value)}) catch return;
|
|
53
|
+
}
|
|
54
|
+
@compileError(err);
|
|
55
|
+
},
|
|
56
|
+
.Slice => {
|
|
57
|
+
if (ptr_info.child == u8) return stderr.print("{s}", .{value}) catch return;
|
|
58
|
+
@compileError(err);
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
else => stderr.print("{}", .{value}) catch return,
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/lib/gen1/README.md
CHANGED
|
@@ -515,7 +515,8 @@ the pkmn engine, but the following moves have their broken behavior preserved in
|
|
|
515
515
|
glitch](https://glitchcity.wiki/Substitute_%C2%BC_HP_glitch) also fails in many cases due to
|
|
516
516
|
Pokémon Showdown implementing the health check based on floating point division instead of integer
|
|
517
517
|
divison like on the cartridge (meaning the Substitute 1/4 glitch will only occur if the Pokémon's
|
|
518
|
-
maximum HP is evenly divisible by 4).
|
|
518
|
+
maximum HP is evenly divisible by 4). Finally, Substitute also incorrectly blocks Dream Eater on
|
|
519
|
+
Pokémon Showdown.
|
|
519
520
|
|
|
520
521
|
In addition to numerous cases where Pokémon Showdown uses the wrong type of message (e.g. `|-fail`
|
|
521
522
|
vs. `|-miss|` vs. `|-immune|`, e.g. in the case of Leech Seed) which are not documented here, Pokémon
|
package/src/lib/gen1/data.zig
CHANGED
|
@@ -272,7 +272,7 @@ pub const Status = enum(u8) {
|
|
|
272
272
|
return num > 0;
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
-
///
|
|
275
|
+
/// Returns a human-readable representation of the status `num`.
|
|
276
276
|
pub fn name(num: u8) []const u8 {
|
|
277
277
|
if (Status.is(num, .EXT)) return if (num == Status.TOX) "TOX" else "SLF";
|
|
278
278
|
if (Status.is(num, .SLP)) return "SLP";
|
|
@@ -442,7 +442,9 @@ fn executeMove(
|
|
|
442
442
|
.err => return @as(?Result, Result.Error),
|
|
443
443
|
}
|
|
444
444
|
|
|
445
|
-
if (!skip_can and !try canMove(battle, player, mslot, auto, skip_pp, from, log))
|
|
445
|
+
if (!skip_can and !try canMove(battle, player, mslot, auto, skip_pp, from, residual, log)) {
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
446
448
|
|
|
447
449
|
return doMove(battle, player, mslot, auto, residual, log);
|
|
448
450
|
}
|
|
@@ -654,6 +656,7 @@ fn canMove(
|
|
|
654
656
|
auto: bool,
|
|
655
657
|
skip_pp: bool,
|
|
656
658
|
from: ?Move,
|
|
659
|
+
residual: *bool,
|
|
657
660
|
log: anytype,
|
|
658
661
|
) !bool {
|
|
659
662
|
var side = battle.side(player);
|
|
@@ -688,7 +691,7 @@ fn canMove(
|
|
|
688
691
|
try log.move(player_ident, side.last_selected_move, battle.active(target), f);
|
|
689
692
|
|
|
690
693
|
if (move.effect.onBegin()) {
|
|
691
|
-
try onBegin(battle, player, move, mslot, log);
|
|
694
|
+
try onBegin(battle, player, move, mslot, residual, log);
|
|
692
695
|
return false;
|
|
693
696
|
}
|
|
694
697
|
|
|
@@ -1216,7 +1219,7 @@ fn mirrorMove(
|
|
|
1216
1219
|
|
|
1217
1220
|
incrementPP(side, mslot);
|
|
1218
1221
|
|
|
1219
|
-
if (!try canMove(battle, player, mslot, auto, false, .MirrorMove, log)) return null;
|
|
1222
|
+
if (!try canMove(battle, player, mslot, auto, false, .MirrorMove, residual, log)) return null;
|
|
1220
1223
|
return doMove(battle, player, mslot, auto, residual, log);
|
|
1221
1224
|
}
|
|
1222
1225
|
|
|
@@ -1245,7 +1248,7 @@ fn metronome(
|
|
|
1245
1248
|
|
|
1246
1249
|
incrementPP(side, mslot);
|
|
1247
1250
|
|
|
1248
|
-
if (!try canMove(battle, player, mslot, auto, false, .Metronome, log)) return null;
|
|
1251
|
+
if (!try canMove(battle, player, mslot, auto, false, .Metronome, residual, log)) return null;
|
|
1249
1252
|
return doMove(battle, player, mslot, auto, residual, log);
|
|
1250
1253
|
}
|
|
1251
1254
|
|
|
@@ -1278,7 +1281,9 @@ fn moveHit(battle: anytype, player: Player, move: Move.Data, immune: *bool, mist
|
|
|
1278
1281
|
if (move.effect == .Swift) return true;
|
|
1279
1282
|
if (foe.active.volatiles.Invulnerable) break :miss true;
|
|
1280
1283
|
}
|
|
1281
|
-
if (move.effect == .DreamEater and !Status.is(foe.stored().status, .SLP)
|
|
1284
|
+
if (move.effect == .DreamEater and (!Status.is(foe.stored().status, .SLP) or
|
|
1285
|
+
(showdown and foe.active.volatiles.Substitute)))
|
|
1286
|
+
{
|
|
1282
1287
|
immune.* = true;
|
|
1283
1288
|
if (showdown) return false;
|
|
1284
1289
|
break :miss true;
|
|
@@ -1480,16 +1485,20 @@ fn endTurn(battle: anytype, log: anytype) @TypeOf(log).Error!Result {
|
|
|
1480
1485
|
|
|
1481
1486
|
fn checkEBC(battle: anytype) bool {
|
|
1482
1487
|
for (battle.sides, 0..) |side, i| {
|
|
1488
|
+
const foe = battle.sides[~@intCast(u1, i)];
|
|
1489
|
+
|
|
1483
1490
|
var foe_all_ghosts = true;
|
|
1484
1491
|
var foe_all_transform = true;
|
|
1492
|
+
for (foe.order, 0..) |id, j| {
|
|
1493
|
+
if (id == 0) break;
|
|
1494
|
+
const active = j == 0;
|
|
1495
|
+
const pokemon = foe.pokemon[id - 1];
|
|
1485
1496
|
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
const ghost = pokemon.hp == 0 or pokemon.types.includes(.Ghost);
|
|
1497
|
+
const ghost = pokemon.hp == 0 or
|
|
1498
|
+
(if (active) foe.active.types else pokemon.types).includes(.Ghost);
|
|
1490
1499
|
foe_all_ghosts = foe_all_ghosts and ghost;
|
|
1491
1500
|
foe_all_transform = foe_all_transform and pokemon.hp == 0 or transform: {
|
|
1492
|
-
for (pokemon.moves) |m| {
|
|
1501
|
+
for (if (active) foe.active.moves else pokemon.moves) |m| {
|
|
1493
1502
|
if (m.id == .None) break :transform true;
|
|
1494
1503
|
if (m.id != .Transform) break :transform false;
|
|
1495
1504
|
}
|
|
@@ -1497,10 +1506,14 @@ fn checkEBC(battle: anytype) bool {
|
|
|
1497
1506
|
};
|
|
1498
1507
|
}
|
|
1499
1508
|
|
|
1500
|
-
for (side.
|
|
1509
|
+
for (side.order, 0..) |id, j| {
|
|
1510
|
+
if (id == 0) break;
|
|
1511
|
+
const active = j == 0;
|
|
1512
|
+
const pokemon = side.pokemon[id - 1];
|
|
1513
|
+
|
|
1501
1514
|
if (pokemon.hp == 0 or Status.is(pokemon.status, .FRZ)) continue;
|
|
1502
1515
|
const transform = foe_all_transform and transform: {
|
|
1503
|
-
for (pokemon.moves) |m| {
|
|
1516
|
+
for (if (active) side.active.moves else pokemon.moves) |m| {
|
|
1504
1517
|
if (m.id == .None) break :transform true;
|
|
1505
1518
|
if (m.id != .Transform) break :transform false;
|
|
1506
1519
|
}
|
|
@@ -1508,7 +1521,7 @@ fn checkEBC(battle: anytype) bool {
|
|
|
1508
1521
|
};
|
|
1509
1522
|
if (transform) continue;
|
|
1510
1523
|
const no_pp = foe_all_ghosts and no_pp: {
|
|
1511
|
-
for (pokemon.moves) |m| {
|
|
1524
|
+
for (if (active) side.active.moves else pokemon.moves) |m| {
|
|
1512
1525
|
if (m.pp != 0) break :no_pp false;
|
|
1513
1526
|
}
|
|
1514
1527
|
break :no_pp true;
|
|
@@ -1524,7 +1537,14 @@ fn checkEBC(battle: anytype) bool {
|
|
|
1524
1537
|
return false;
|
|
1525
1538
|
}
|
|
1526
1539
|
|
|
1527
|
-
inline fn onBegin(
|
|
1540
|
+
inline fn onBegin(
|
|
1541
|
+
battle: anytype,
|
|
1542
|
+
player: Player,
|
|
1543
|
+
move: Move.Data,
|
|
1544
|
+
mslot: u8,
|
|
1545
|
+
residual: *bool,
|
|
1546
|
+
log: anytype,
|
|
1547
|
+
) !void {
|
|
1528
1548
|
assert(move.effect.onBegin());
|
|
1529
1549
|
return switch (move.effect) {
|
|
1530
1550
|
.Confusion => Effects.confusion(battle, player, move, log),
|
|
@@ -1540,7 +1560,7 @@ inline fn onBegin(battle: anytype, player: Player, move: Move.Data, mslot: u8, l
|
|
|
1540
1560
|
.Poison => Effects.poison(battle, player, move, log),
|
|
1541
1561
|
.Reflect => Effects.reflect(battle, player, log),
|
|
1542
1562
|
.Splash => Effects.splash(battle, player, log),
|
|
1543
|
-
.Substitute => Effects.substitute(battle, player, log),
|
|
1563
|
+
.Substitute => Effects.substitute(battle, player, residual, log),
|
|
1544
1564
|
.SwitchAndTeleport => Effects.switchAndTeleport(battle, player, move, log),
|
|
1545
1565
|
.Transform => Effects.transform(battle, player, log),
|
|
1546
1566
|
else => unreachable,
|
|
@@ -1753,6 +1773,11 @@ pub const Effects = struct {
|
|
|
1753
1773
|
var side = battle.side(player);
|
|
1754
1774
|
var stored = side.stored();
|
|
1755
1775
|
|
|
1776
|
+
if (battle.last_damage == 0) {
|
|
1777
|
+
assert(showdown);
|
|
1778
|
+
return;
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1756
1781
|
const drain = @max(battle.last_damage / 2, 1);
|
|
1757
1782
|
battle.last_damage = drain;
|
|
1758
1783
|
|
|
@@ -2063,7 +2088,7 @@ pub const Effects = struct {
|
|
|
2063
2088
|
return log.fail(foe_ident, if (Status.is(foe_stored.status, .PSN))
|
|
2064
2089
|
if (!showdown)
|
|
2065
2090
|
.Poison
|
|
2066
|
-
else if (toxic ==
|
|
2091
|
+
else if (toxic == (foe_stored.status == Status.TOX))
|
|
2067
2092
|
if (toxic) .Toxic else .Poison
|
|
2068
2093
|
else
|
|
2069
2094
|
.None
|
|
@@ -2171,7 +2196,7 @@ pub const Effects = struct {
|
|
|
2171
2196
|
try log.activate(battle.active(player), .Splash);
|
|
2172
2197
|
}
|
|
2173
2198
|
|
|
2174
|
-
fn substitute(battle: anytype, player: Player, log: anytype) !void {
|
|
2199
|
+
fn substitute(battle: anytype, player: Player, residual: *bool, log: anytype) !void {
|
|
2175
2200
|
var side = battle.side(player);
|
|
2176
2201
|
if (side.active.volatiles.Substitute) {
|
|
2177
2202
|
try log.fail(battle.active(player), .Substitute);
|
|
@@ -2196,7 +2221,10 @@ pub const Effects = struct {
|
|
|
2196
2221
|
side.active.volatiles.substitute = hp + 1;
|
|
2197
2222
|
side.active.volatiles.Substitute = true;
|
|
2198
2223
|
try log.start(battle.active(player), .Substitute);
|
|
2199
|
-
if (hp > 0)
|
|
2224
|
+
if (hp > 0) {
|
|
2225
|
+
try log.damage(battle.active(player), side.stored(), .None);
|
|
2226
|
+
if (showdown and side.stored().hp == 0) residual.* = false;
|
|
2227
|
+
}
|
|
2200
2228
|
}
|
|
2201
2229
|
|
|
2202
2230
|
fn switchAndTeleport(battle: anytype, player: Player, move: Move.Data, log: anytype) !void {
|
|
@@ -2514,13 +2542,9 @@ fn clearVolatiles(battle: anytype, who: Player, log: anytype) !void {
|
|
|
2514
2542
|
volatiles.LeechSeed = false;
|
|
2515
2543
|
try log.end(ident, .LeechSeed);
|
|
2516
2544
|
}
|
|
2517
|
-
if (volatiles.Toxic) {
|
|
2545
|
+
if (!showdown and volatiles.Toxic) {
|
|
2518
2546
|
volatiles.Toxic = false;
|
|
2519
|
-
// volatiles.toxic is left unchanged, except on Pokémon Showdown which clears it
|
|
2520
|
-
if (showdown) {
|
|
2521
|
-
volatiles.toxic = 0;
|
|
2522
|
-
try log.end(ident, .Toxic);
|
|
2523
|
-
}
|
|
2547
|
+
// volatiles.toxic is left unchanged, except on Pokémon Showdown which clears it (below)
|
|
2524
2548
|
}
|
|
2525
2549
|
if (volatiles.LightScreen) {
|
|
2526
2550
|
volatiles.LightScreen = false;
|
|
@@ -2530,6 +2554,12 @@ fn clearVolatiles(battle: anytype, who: Player, log: anytype) !void {
|
|
|
2530
2554
|
volatiles.Reflect = false;
|
|
2531
2555
|
try log.end(ident, .Reflect);
|
|
2532
2556
|
}
|
|
2557
|
+
if (showdown and volatiles.Toxic) {
|
|
2558
|
+
volatiles.Toxic = false;
|
|
2559
|
+
// Pokémon Showdown erroneously clears the toxic counter
|
|
2560
|
+
volatiles.toxic = 0;
|
|
2561
|
+
try log.end(ident, .Toxic);
|
|
2562
|
+
}
|
|
2533
2563
|
}
|
|
2534
2564
|
|
|
2535
2565
|
fn disabled(side: *Side, ident: ID, log: anytype) !bool {
|