@pkmn/engine 0.1.0-dev.c4f94762 → 0.1.0-dev.c4ffaf39

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.
@@ -1304,14 +1304,32 @@ pub const Effects = struct {
1304
1304
  }
1305
1305
 
1306
1306
  pub fn bellyDrum(battle: anytype, player: Player, state: *State, options: anytype) !void {
1307
- _ = .{ battle, player, state, options }; // TODO
1308
- // assert(!state.miss);
1309
- // var side = battle.side(player);
1310
- // try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
1311
- // TODO
1312
- // |-boost|p1a: Poliwag|atk|2|[silent]
1313
- // |-fail|p1a: Poliwag
1314
- //
1307
+ assert(!state.miss);
1308
+ var side = battle.side(player);
1309
+
1310
+ const ident = battle.active(player);
1311
+
1312
+ assert(side.active.boosts.atk >= -6 and side.active.boosts.atk <= 6);
1313
+ if (side.active.boosts.atk == 6 or side.active.boosts.atk == MAX_STAT_VALUE) {
1314
+ return options.log.fail(.{ ident, .None });
1315
+ }
1316
+
1317
+ const hp = @max(side.stored().stats.hp / 2, 1);
1318
+ if (side.stored().hp <= hp) {
1319
+ try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
1320
+ try options.log.boost(.{ ident, .Attack, @as(i8, 2), .Silent });
1321
+ return options.log.fail(.{ ident, .None });
1322
+ } else {
1323
+ side.stored().hp -= hp;
1324
+ try options.log.damage(.{ ident, side.stored(), .None });
1325
+
1326
+ const before = side.active.boosts.atk;
1327
+ var i: usize = 0;
1328
+ while (i < 6 and side.active.stats.atk < MAX_STAT_VALUE) : (i += 1) {
1329
+ try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
1330
+ }
1331
+ try options.log.boost(.{ ident, .Attack, @as(i8, side.active.boosts.atk - before) });
1332
+ }
1315
1333
  }
1316
1334
 
1317
1335
  pub const bide = struct {
@@ -1596,8 +1614,16 @@ pub const Effects = struct {
1596
1614
  try options.log.start(.{ foe_ident, .Encore, move.id });
1597
1615
  }
1598
1616
 
1599
- pub fn explode(battle: anytype, player: Player, state: *State, options: anytype) !void {
1600
- _ = .{ battle, player, state, options }; // TODO
1617
+ pub fn explode(battle: anytype, player: Player, _: *State, _: anytype) !void {
1618
+ var side = battle.side(player);
1619
+ var stored = side.stored();
1620
+
1621
+ stored.hp = 0;
1622
+ // Pokémon Showdown sets the status to 0 on faint(), and we need to do the same to be able
1623
+ // to correctly implement Pokémon Showdown's dumb speed-based switch mechanics
1624
+ if (!showdown) stored.status = 0;
1625
+ side.active.volatiles.LeechSeed = false;
1626
+ side.active.volatiles.DestinyBond = false;
1601
1627
  }
1602
1628
 
1603
1629
  pub fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
@@ -1723,8 +1749,26 @@ pub const Effects = struct {
1723
1749
  foe.active.volatiles.frozen = true;
1724
1750
  }
1725
1751
 
1726
- pub fn furyCutter(battle: anytype, player: Player, state: *State, options: anytype) !void {
1727
- _ = .{ battle, player, state, options }; // TODO
1752
+ // TODO FIXME showdown calls this before calcDamage (after checkHit) and modifies state.bp!
1753
+ pub fn furyCutter(battle: anytype, player: Player, state: *State, _: anytype) !void {
1754
+ var side = battle.side(player);
1755
+ var volatiles = &side.active.volatiles;
1756
+
1757
+ if (state.miss) {
1758
+ volatiles.fury_cutter = 0;
1759
+ return;
1760
+ }
1761
+
1762
+ assert(volatiles.fury_cutter >= 0 and volatiles.fury_cutter <= 5);
1763
+ if (volatiles.fury_cutter == 5) return;
1764
+ volatiles.fury_cutter += 1;
1765
+
1766
+ if (showdown) {
1767
+ state.bp *= 2;
1768
+ assert(state.bp <= 160);
1769
+ } else {
1770
+ state.damage *|= 2;
1771
+ }
1728
1772
  }
1729
1773
 
1730
1774
  pub fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -1885,7 +1929,29 @@ pub const Effects = struct {
1885
1929
  }
1886
1930
 
1887
1931
  pub fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
1888
- _ = .{ battle, player, state, options }; // TODO
1932
+ var side = battle.side(player);
1933
+
1934
+ if (state.miss) {
1935
+ if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
1936
+ try options.log.lastmiss(.{});
1937
+ return options.log.miss(.{battle.active(player)});
1938
+ }
1939
+
1940
+ const move = battle.foe(player).lastMove(false);
1941
+ if (move == .None or move == .Struggle) {
1942
+ return options.log.fail(.{ battle.active(player.foe()), .None });
1943
+ }
1944
+
1945
+ var slot: u4 = 0;
1946
+ for (side.active.moves, 0..) |m, i| {
1947
+ if (m.id == move) return options.log.fail(.{ battle.active(player.foe()), .None });
1948
+ if (m.id == .Mimic) slot = @intCast(i + 1);
1949
+ }
1950
+ assert(slot != 0 and slot == state.mslot);
1951
+
1952
+ side.active.move(slot).id = move;
1953
+ side.active.move(slot).pp = 5;
1954
+ try options.log.start(.{ battle.active(player), .Mimic, move });
1889
1955
  }
1890
1956
 
1891
1957
  pub fn mirrorMove(
@@ -2393,17 +2459,37 @@ pub const Effects = struct {
2393
2459
  }
2394
2460
 
2395
2461
  pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
2396
- _ = .{ battle, player, state, options }; // TODO
2397
- // var foe = battle.foe(player);
2462
+ var foe = battle.foe(player);
2398
2463
 
2399
- // if (state.miss) return options.log.fail(.{ battle.active(player), .None });
2464
+ const last = foe.lastMove(false);
2465
+ if (state.miss or last == .None or last == .Struggle) {
2466
+ return options.log.fail(.{ battle.active(player.foe()), .None });
2467
+ }
2400
2468
 
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
- // }
2469
+ var slot: u4 = 0;
2470
+ for (foe.active.moves, 0..) |m, i| {
2471
+ if (m.id == last) slot = @intCast(i + 1);
2472
+ }
2473
+ // If the move was called via Metronome it might not exist
2474
+ if (slot == 0 or foe.active.move(slot).pp == 0) {
2475
+ return options.log.fail(.{ battle.active(player.foe()), .None });
2476
+ }
2477
+
2478
+ // Pokémon Showdown always deducts 4 instead of randomizing
2479
+ const pp = @min(
2480
+ foe.active.move(slot).pp,
2481
+ if (showdown) 4 else try Rolls.spite(battle, player, options),
2482
+ );
2483
+ foe.active.move(slot).pp -= pp;
2484
+ if (!foe.active.volatiles.Transform) {
2485
+ var move_slot = foe.stored().move(slot);
2486
+ assert(move_slot.pp > 0);
2487
+
2488
+ move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
2489
+ assert(foe.active.move(slot).pp == foe.stored().move(slot).pp);
2490
+ }
2491
+
2492
+ try options.log.activate(.{ battle.active(player.foe()), .Spite, last, pp });
2407
2493
  }
2408
2494
 
2409
2495
  pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
@@ -3009,13 +3095,12 @@ pub const Rolls = struct {
3009
3095
  return hits;
3010
3096
  }
3011
3097
 
3012
- fn spite(battle: anytype, player: Player, options: anytype) !u8 {
3013
- const pp = if (options.calc.overridden(player, .spite)) |val|
3098
+ fn spite(battle: anytype, player: Player, options: anytype) !u3 {
3099
+ assert(!showdown);
3100
+ const pp: u3 = if (options.calc.overridden(player, .spite)) |val|
3014
3101
  val + 1
3015
- else if (showdown)
3016
- battle.rng.range(u8, 2, 6)
3017
3102
  else
3018
- (battle.rng.next() & 3) + 2;
3103
+ @intCast((battle.rng.next() & 3) + 2);
3019
3104
 
3020
3105
  assert(pp >= 2 and pp <= 5);
3021
3106
  try options.chance.spite(player, pp);
@@ -1,7 +1,8 @@
1
1
  const std = @import("std");
2
- const pkmn = @import("../pkmn.zig");
3
2
 
4
- const js = @import("../common/js.zig");
3
+ const pkmn = @import("pkmn.zig");
4
+
5
+ const js = @import("common/js.zig");
5
6
 
6
7
  const assert = std.debug.assert;
7
8
 
package/src/lib/pkmn.zig CHANGED
@@ -26,9 +26,7 @@ pub const Result = @import("common/data.zig").Result;
26
26
  pub const PSRNG = @import("common/rng.zig").PSRNG;
27
27
 
28
28
  /// Namespace for helpers related to protocol message logging.
29
- pub const protocol = if (options.internal) struct {
30
- pub usingnamespace @import("common/protocol.zig");
31
- } else struct {
29
+ pub const protocol = if (options.internal) @import("common/protocol.zig") else struct {
32
30
  /// Logs protocol information to its `Writer` during a battle update when
33
31
  /// `options.log` is enabled.
34
32
  pub const Log = @import("common/protocol.zig").Log;
@@ -82,16 +80,14 @@ pub const gen1 = struct {
82
80
  /// via `overrides` and tracks `summaries` of information relevant to damage calculation.
83
81
  pub const Calc = @import("gen1/calc.zig").Calc;
84
82
  /// Namespace for types associated with supported Generation I Pokémon damage calc features.
85
- pub const calc = if (options.internal) struct {
86
- pub usingnamespace @import("gen1/calc.zig");
87
- } else struct {
83
+ pub const calc = if (options.internal) @import("gen1/calc.zig") else struct {
88
84
  /// TODO
89
85
  pub const Overrides = @import("gen1/calc.zig").Overrides;
90
86
  /// Information relevant to damage calculation that occured during a Generation I
91
87
  /// battle `update`.
92
88
  pub const Summaries = @import("gen1/calc.zig").Summaries;
93
89
  /// Information relevant to damage calculation that occured during a Generation I
94
- /// battle `update` for a single player.
90
+ /// battle `update` for a single player.
95
91
  pub const Summary = @import("gen1/calc.zig").Summary;
96
92
  /// Null object pattern implementation of Generation I `Calc` which does nothing,
97
93
  /// though damage calculator support should additionally be turned off
@@ -102,12 +98,31 @@ pub const gen1 = struct {
102
98
  pub const helpers = @import("gen1/helpers.zig");
103
99
  };
104
100
 
101
+ /// TODO
102
+ pub const gen2 = struct {
103
+ pub usingnamespace @import("gen2/data.zig");
104
+ pub const Chance = @import("gen2/chance.zig").Chance;
105
+ pub const chance = struct {
106
+ pub const Actions = @import("gen2/chance.zig").Actions;
107
+ pub const Action = @import("gen2/chance.zig").Action;
108
+ pub const Durations = @import("gen2/chance.zig").Durations;
109
+ pub const Duration = @import("gen2/chance.zig").Duration;
110
+ pub const NULL = @import("gen2/chance.zig").NULL;
111
+ };
112
+ pub const Calc = @import("gen2/calc.zig").Calc;
113
+ pub const calc = if (options.internal) @import("gen2/calc.zig") else struct {
114
+ pub const Overrides = @import("gen2/calc.zig").Overrides;
115
+ pub const Summaries = @import("gen2/calc.zig").Summaries;
116
+ pub const Summary = @import("gen2/calc.zig").Summary;
117
+ pub const NULL = @import("gen2/calc.zig").NULL;
118
+ };
119
+ pub const helpers = @import("gen2/helpers.zig");
120
+ };
121
+
105
122
  // Internal APIs used by other pkmn libraries, not actually part of the public API.
106
123
  // NOTE: `pub usingnamespace struct { ... }` here results in a (false?) dependency loop
107
- pub const js = if (options.internal) struct {
108
- pub usingnamespace @import("common/js.zig");
109
- } else struct {};
124
+ pub const js = if (options.internal) @import("common/js.zig") else struct {};
110
125
  pub const bindings = if (options.internal) struct {
111
- pub const node = @import("binding/node.zig");
112
- pub const wasm = @import("binding/wasm.zig");
126
+ pub const node = @import("node.zig");
127
+ pub const wasm = @import("wasm.zig");
113
128
  } else struct {};
@@ -1,5 +1,6 @@
1
1
  const std = @import("std");
2
- const pkmn = @import("../pkmn.zig");
2
+
3
+ const pkmn = @import("pkmn.zig");
3
4
 
4
5
  export const SHOWDOWN = pkmn.options.showdown;
5
6
  export const LOG = pkmn.options.log;