@pkmn/engine 0.1.0-dev.6c5fdce2 → 0.1.0-dev.6cc9e067

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.
@@ -999,7 +999,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
999
999
 
1000
1000
  // Weather
1001
1001
  if (battle.field.weather != .None) {
1002
- assert(battle.field.weather_duration > 0);
1003
1002
  battle.field.weather_duration -= 1;
1004
1003
 
1005
1004
  if (battle.field.weather_duration == 0) {
@@ -1058,7 +1057,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1058
1057
  var volatiles = &side.active.volatiles;
1059
1058
 
1060
1059
  if (volatiles.PerishSong) {
1061
- assert(volatiles.perish_song > 0);
1062
1060
  volatiles.perish_song -= 1;
1063
1061
 
1064
1062
  assert(volatiles.perish_song < 3);
@@ -1195,7 +1193,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
1195
1193
  var volatiles = &side.active.volatiles;
1196
1194
 
1197
1195
  if (volatiles.Encore) {
1198
- assert(volatiles.encore > 0);
1199
1196
  volatiles.encore -= 1;
1200
1197
 
1201
1198
  if (volatiles.encore == 0 or side.active.move(mslot).pp == 0) {
@@ -1217,7 +1214,6 @@ fn updateSideCondition(
1217
1214
  ) !void {
1218
1215
  var side = battle.side(player);
1219
1216
  if (@field(side.conditions, key)) {
1220
- assert(@field(side.conditions, value) > 0);
1221
1217
  @field(side.conditions, value) -= 1;
1222
1218
 
1223
1219
  if (@field(side.conditions, value) == 0) {
@@ -1308,14 +1304,32 @@ pub const Effects = struct {
1308
1304
  }
1309
1305
 
1310
1306
  pub fn bellyDrum(battle: anytype, player: Player, state: *State, options: anytype) !void {
1311
- _ = .{ battle, player, state, options }; // TODO
1312
- // assert(!state.miss);
1313
- // var side = battle.side(player);
1314
- // try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
1315
- // TODO
1316
- // |-boost|p1a: Poliwag|atk|2|[silent]
1317
- // |-fail|p1a: Poliwag
1318
- //
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
+ }
1319
1333
  }
1320
1334
 
1321
1335
  pub const bide = struct {
@@ -1327,7 +1341,6 @@ pub const Effects = struct {
1327
1341
 
1328
1342
  if (!volatiles.Bide) return;
1329
1343
 
1330
- assert(volatiles.attacks > 0);
1331
1344
  // if (options.calc.modify(player, .attacking)) |extend| {
1332
1345
  // if (!extend) volatiles.attacks = 0;
1333
1346
  // } else {
@@ -1601,8 +1614,16 @@ pub const Effects = struct {
1601
1614
  try options.log.start(.{ foe_ident, .Encore, move.id });
1602
1615
  }
1603
1616
 
1604
- pub fn explode(battle: anytype, player: Player, state: *State, options: anytype) !void {
1605
- _ = .{ 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;
1606
1627
  }
1607
1628
 
1608
1629
  pub fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
@@ -1728,8 +1749,26 @@ pub const Effects = struct {
1728
1749
  foe.active.volatiles.frozen = true;
1729
1750
  }
1730
1751
 
1731
- pub fn furyCutter(battle: anytype, player: Player, state: *State, options: anytype) !void {
1732
- _ = .{ 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
+ }
1733
1772
  }
1734
1773
 
1735
1774
  pub fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -1890,7 +1929,29 @@ pub const Effects = struct {
1890
1929
  }
1891
1930
 
1892
1931
  pub fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
1893
- _ = .{ 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 });
1894
1955
  }
1895
1956
 
1896
1957
  pub fn mirrorMove(
@@ -2398,17 +2459,37 @@ pub const Effects = struct {
2398
2459
  }
2399
2460
 
2400
2461
  pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
2401
- _ = .{ battle, player, state, options }; // TODO
2402
- // var foe = battle.foe(player);
2462
+ var foe = battle.foe(player);
2403
2463
 
2404
- // 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
+ }
2405
2468
 
2406
- // const last = foe.lastMove(false);
2407
- // if (showdown) {
2408
- // const roll = try Rolls.spite(battle, player, options);
2409
- // } else {
2410
- // if (last != .None) {}
2411
- // }
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 });
2412
2493
  }
2413
2494
 
2414
2495
  pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
@@ -3014,13 +3095,12 @@ pub const Rolls = struct {
3014
3095
  return hits;
3015
3096
  }
3016
3097
 
3017
- fn spite(battle: anytype, player: Player, options: anytype) !u8 {
3018
- 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|
3019
3101
  val + 1
3020
- else if (showdown)
3021
- battle.rng.range(u8, 2, 6)
3022
3102
  else
3023
- (battle.rng.next() & 3) + 2;
3103
+ @intCast((battle.rng.next() & 3) + 2);
3024
3104
 
3025
3105
  assert(pp >= 2 and pp <= 5);
3026
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
 
@@ -72,7 +73,6 @@ fn update(gen: anytype) c.napi_callback {
72
73
  var len: usize = 0;
73
74
  assert(c.napi_get_arraybuffer_info(env, argv[0], &data, &len) == c.napi_ok);
74
75
  assert(len == @sizeOf(gen.Battle(gen.PRNG)));
75
- assert(data != null);
76
76
 
77
77
  var battle: *gen.Battle(gen.PRNG) = @alignCast(@ptrCast(data.?));
78
78
  const c1: pkmn.Choice = @bitCast(js.Number.get(env, argv[1], u8));
@@ -85,7 +85,6 @@ fn update(gen: anytype) c.napi_callback {
85
85
  else => result: {
86
86
  assert(c.napi_get_arraybuffer_info(env, argv[3], &data, &len) == c.napi_ok);
87
87
  assert(len == gen.LOGS_SIZE);
88
- assert(data != null);
89
88
 
90
89
  const buf = @as([*]u8, @ptrCast(data.?))[0..gen.LOGS_SIZE];
91
90
  var stream: pkmn.protocol.ByteStream = .{ .buffer = buf };
@@ -116,7 +115,6 @@ fn choices(gen: anytype) c.napi_callback {
116
115
  var len: usize = 0;
117
116
  assert(c.napi_get_arraybuffer_info(env, argv[0], &data, &len) == c.napi_ok);
118
117
  assert(len == @sizeOf(gen.Battle(gen.PRNG)));
119
- assert(data != null);
120
118
 
121
119
  var battle: *gen.Battle(gen.PRNG) = @alignCast(@ptrCast(data.?));
122
120
  const player: pkmn.Player = @enumFromInt(js.Number.get(env, argv[1], u8));
@@ -124,7 +122,6 @@ fn choices(gen: anytype) c.napi_callback {
124
122
 
125
123
  assert(c.napi_get_arraybuffer_info(env, argv[3], &data, &len) == c.napi_ok);
126
124
  assert(len == gen.CHOICES_SIZE);
127
- assert(data != null);
128
125
 
129
126
  const out = @as([*]pkmn.Choice, @ptrCast(data.?))[0..gen.CHOICES_SIZE];
130
127
  const n = battle.choices(player, request, out);
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;