@pkmn/engine 0.1.0-dev.b9fb6eab → 0.1.0-dev.be951af1

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.
Files changed (60) hide show
  1. package/README.md +98 -59
  2. package/build/pkg/addon.d.ts +7 -0
  3. package/build/pkg/addon.js +112 -0
  4. package/build/pkg/addon.js.map +1 -0
  5. package/build/pkg/data/ids.json +12 -12
  6. package/build/pkg/data/layout.json +27 -5
  7. package/build/pkg/data/protocol.json +69 -12
  8. package/build/pkg/data.d.ts +3 -4
  9. package/build/pkg/data.js +3 -5
  10. package/build/pkg/data.js.map +1 -1
  11. package/build/pkg/gen1.d.ts +5 -4
  12. package/build/pkg/gen1.js +49 -33
  13. package/build/pkg/gen1.js.map +1 -1
  14. package/build/pkg/index.d.ts +15 -12
  15. package/build/pkg/index.js.map +1 -1
  16. package/build/pkg/protocol.d.ts +4 -2
  17. package/build/pkg/protocol.js +60 -39
  18. package/build/pkg/protocol.js.map +1 -1
  19. package/build/test/display.js +400 -0
  20. package/build/test/display.js.map +1 -0
  21. package/build/tools/debug.js +133 -0
  22. package/build/tools/debug.js.map +1 -0
  23. package/build.zig +98 -79
  24. package/package.json +72 -40
  25. package/src/bin/install-pkmn-engine +11 -6
  26. package/src/bin/pkmn-debug +62 -0
  27. package/src/lib/binding/node.zig +47 -142
  28. package/src/lib/binding/wasm.zig +6 -4
  29. package/src/lib/common/battle.zig +17 -0
  30. package/src/lib/common/data.zig +15 -27
  31. package/src/lib/common/debug.zig +21 -11
  32. package/src/lib/common/js.zig +118 -0
  33. package/src/lib/common/optional.zig +67 -0
  34. package/src/lib/common/options.zig +12 -3
  35. package/src/lib/common/protocol.zig +788 -246
  36. package/src/lib/common/rational.zig +353 -0
  37. package/src/lib/common/rng.zig +15 -15
  38. package/src/lib/common/util.zig +12 -0
  39. package/src/lib/gen1/README.md +295 -181
  40. package/src/lib/gen1/calc.zig +426 -0
  41. package/src/lib/gen1/chance.zig +1060 -0
  42. package/src/lib/gen1/data/moves.zig +192 -180
  43. package/src/lib/gen1/data/species.zig +2 -2
  44. package/src/lib/gen1/data/types.zig +5 -5
  45. package/src/lib/gen1/data.zig +84 -70
  46. package/src/lib/gen1/helpers.zig +577 -32
  47. package/src/lib/gen1/mechanics.zig +1043 -714
  48. package/src/lib/gen2/README.md +106 -85
  49. package/src/lib/gen2/calc.zig +143 -0
  50. package/src/lib/gen2/chance.zig +1314 -0
  51. package/src/lib/gen2/data/items.zig +12 -18
  52. package/src/lib/gen2/data/moves.zig +660 -566
  53. package/src/lib/gen2/data/species.zig +1 -1
  54. package/src/lib/gen2/data/types.zig +131 -28
  55. package/src/lib/gen2/data.zig +138 -88
  56. package/src/lib/gen2/generated.zig +1239 -0
  57. package/src/lib/gen2/helpers.zig +16 -22
  58. package/src/lib/gen2/mechanics.zig +2916 -12
  59. package/src/lib/pkmn.zig +68 -9
  60. package/src/test/display/pkmn.html.tmpl +368 -0
@@ -32,35 +32,36 @@ The following information is required to simulate a Generation II Pokémon battl
32
32
  | `pokemon.species` | `party_struct.Species` | `pokemon.baseSpecies` |
33
33
  | `active.species` | `battle_struct.Species` | `pokemon.species` |
34
34
  | `active.stats` | `TODO modified` | `pokemon.storedStats` |
35
- | `active.trapped FIXME` | `PlayerTrappingMove` | `pokemon.trapped` |
36
- | `active.switching` | `PlayerIsSwitching` | `pokemon.switchFlag` |
37
35
  | `active.boosts` | `PlayerStatLevels` | `pokemon.boosts` |
38
36
  | `active.volatiles` | `PlayerBattleStatus{1,2,3}` | `pokemon.volatiles` |
39
- | `volatiles.data.bide` | `PlayerDamageTaken` | `volatiles.bide.totalDamage` |
40
- | `volatiles.data.substitute` | `PlayerSubstituteHP` | `volatiles.substitute.hp` |
41
- | `volatiles.data.rollout` | `PlayerRolloutCount` | `volatiles.rollout.hitCount` |
42
- | `volatiles.data.confusion` | `PlayerConfuseCount` | `volatiles.confusion.duration` |
43
- | `volatiles.data.toxic` | `PlayerToxicCount` | `volatiles.residualdmg.counter` |
44
- | `volatiles.data.disabled` | `PlayerDisableCount` | `moveSlots.disabled` |
45
- | `volatiles.data.encore` | `PlayerEncoreCount` | `volatiles.encore.duration` |
46
- | `volatiles.data.perish_song` | `PlayerPerishCount` | `volatiles.perishsong.duration` |
47
- | `volatiles.data.fury_cutter` | `PlayerFuryCutterCount` | `volatiles.furycutter.multiplier` |
48
- | `volatiles.data.protect` | `PlayerProtectCount` | `volatiles.stall.counter` |
49
- | `volatiles.data.future_sight` | `PlayerFutureSight{Damage,Count}` | `slotConditions[i].futuremove` |
50
- | `volatiles.data.rage` | `PlayerRageCounter` | **BUG** |
51
- | `volatiles.data.bind` | `PlayerWrapCount` | `volatiles.partiallytrapped.duration` |
37
+ | `volatiles.bide` | `PlayerDamageTaken` | `volatiles.bide.totalDamage` |
38
+ | `volatiles.substitute` | `PlayerSubstituteHP` | `volatiles.substitute.hp` |
39
+ | `volatiles.rollout` | `PlayerRolloutCount` | `volatiles.rollout.hitCount` |
40
+ | `volatiles.confusion` | `PlayerConfuseCount` | `volatiles.confusion.duration` |
41
+ | `volatiles.toxic` | `PlayerToxicCount` | `volatiles.residualdmg.counter` |
42
+ | `volatiles.disable` | `PlayerDisableCount` | `moveSlots.disabled` |
43
+ | `volatiles.encore` | `PlayerEncoreCount` | `volatiles.encore.duration` |
44
+ | `volatiles.perish_song` | `PlayerPerishCount` | `volatiles.perishsong.duration` |
45
+ | `volatiles.fury_cutter` | `PlayerFuryCutterCount` | `volatiles.furycutter.multiplier` |
46
+ | `volatiles.protect` | `PlayerProtectCount` | `volatiles.stall.counter` |
47
+ | `volatiles.future_sight` | `PlayerFutureSight{Damage,Count}` | `slotConditions[i].futuremove` |
48
+ | `volatiles.rage` | `PlayerRageCounter` | **BUG** |
49
+ | `volatiles.bind` | `PlayerWrapCount` | `volatiles.partiallytrapped.duration` |
50
+ | `volatiles.bind_reason` | `PlayerTrappingMove` | `volatiles.effectState.sourceEffect` |
52
51
  | | `PlayerCharging` | |
53
- | | `PlayerJustGotFrozen` | |
52
+ | `volatiles.trapped FIXME` | `FIXME` | `pokemon.trapped` |
53
+ | `volatiles.switched` | `TODO` | `pokemon.switchFlag` |
54
+ | `volatiles.switching` | `PlayerIsSwitching` | `pokemon.switchFlag` |
55
+ | `volatiles.frozen` | `PlayerJustGotFrozen` | `volatiles.frozen` |
54
56
  | | | |
55
57
  | `active.last_used_move` | `PlayerUsedMove` | `side.lastMove` |
56
58
 
57
- - Pokémon Showdown does not implement the correct Generation II RNG and as such its `seed` is
59
+ - Pokémon Showdown doesn't implement the correct Generation II RNG and as such its `seed` is
58
60
  different
59
- - `battle.turn` only needs to be tracked in order to be compatible with the Pokémon Showdown
60
- protocol
61
+ - `battle.turn` only needs to be tracked to be compatible with the Pokémon Showdown protocol
61
62
  - Battle results (win, lose, draw) or request states are communicated via the return code of
62
63
  `Battle.update`
63
- - Nicknames (`BattleMonNick`/`pokemon.name`) are not handled by the pkmn engine as they are expected
64
+ - Nicknames (`BattleMonNick`/`pokemon.name`) aren't handled by the pkmn engine as they're expected
64
65
  to be handled by driver code if required
65
66
 
66
67
  ==**TODO**==
@@ -77,9 +78,9 @@ between the data structure for the "active" Pokémon and its party members (see
77
78
 
78
79
  ### `Pokemon` / `ActivePokemon`
79
80
 
80
- Similar to the cartridge, in order to save space different information is stored depending on
81
- whether a [Pokémon](https://pkmn.cc/bulba/Pok%c3%a9mon_data_structure_%28Generation_II%29) is
82
- actively participating in battle vs. is switched out (pret's [`battle_struct` vs.
81
+ Similar to the cartridge, to save space different information is stored depending on whether a
82
+ [Pokémon](https://pkmn.cc/bulba/Pok%c3%a9mon_data_structure_%28Generation_II%29) is actively
83
+ participating in battle vs. is switched out (pret's [`battle_struct` vs.
83
84
  `party_struct`](https://pkmn.cc/pokecrystal/macros/ram.asm)). In Pokémon Showdown, all the Pokemon
84
85
  are represented by the same
85
86
  [`Pokemon`](https://github.com/smogon/pokemon-showdown/blob/master/sim/pokemon.ts) class, and static
@@ -93,14 +94,14 @@ A `MoveSlot` is a data-type for a `(move, current pp)` pair. A pared down versio
93
94
  Showdown's `Pokemon#moveSlot`, it also stores data from the cartridge's `battle_struct::Move` macro
94
95
  and can be used to replace the `PlayerMove*` data. Move PP is stored as a full byte instead of how
95
96
  the cartridge (`battle_struct::PP`) stores it (6 bits for current PP and the remaining 2 bits used
96
- to store the number of applied PP Ups). PP Up bits do not actually need to be stored on move slot
97
+ to store the number of applied PP Ups). PP Up bits don't actually need to be stored on move slot
97
98
  as max PP is never relevant in Generation II (the Mystery Berry always increases PP from 0 -> 5).
98
99
 
99
100
  #### `Status`
100
101
 
101
102
  Bitfield representation of a Pokémon's major [status
102
- condition](https://pkmn.cc/bulba/Status_condition), mirroring how it is stored on the cartridge. A
103
- value of `0x00` means that the Pokémon is not affected by any major status, otherwise the lower 3
103
+ condition](https://pkmn.cc/bulba/Status_condition), mirroring how it's stored on the cartridge. A
104
+ value of `0x00` means that the Pokémon isn't affected by any major status, otherwise the lower 3
104
105
  bits represent the remaining duration for Sleep. Other status are denoted by the presence of
105
106
  individual bits - at most one status should be set at any given time.
106
107
 
@@ -161,7 +162,7 @@ boosts should always range from `-6`...`6` instead of `1`...`13` as on the cartr
161
162
 
162
163
  `Moves` serves as an identifier for a unique [Pokémon move](https://pkmn.cc/bulba/Move) that can be
163
164
  used to retrieve a `Move` with information regarding base power, accuracy and type. As covered
164
- above, PP information isn't strictly necessary in Generation I, but fits neatly into the 4 bits of
165
+ earlier, PP information isn't strictly necessary in Generation I, but fits neatly into the 4 bits of
165
166
  padding after noticing that all PP values are multiples of 5. `Moves.None` exists as a special
166
167
  sentinel value to indicate `null`.
167
168
 
@@ -215,11 +216,86 @@ entropy](https://en.wikipedia.org/wiki/Entropy_(information_theory))) is as foll
215
216
  | **wrap** | 0...5 | 3 | | **perish song** | 0...4 | 3 |
216
217
  | **item** | TODO | 5? | | **gender** | 0..1 | 1 |
217
218
 
218
- From this we can determine the minimum bits required to store each data structure to determine how
219
- much overhead the representations above have after taking into consideration [alignment &
219
+ From this one can determine the minimum bits required to store each data structure to determine how
220
+ much overhead the preceding representations have after taking into consideration [alignment &
220
221
  padding](https://en.wikipedia.org/wiki/Data_structure_alignment) and
221
222
  [denormalization](https://en.wikipedia.org/wiki/Denormalization):
222
223
 
224
+ ## Details
225
+
226
+ | pkmn | Pokémon Crystal (pret) |
227
+ | ------------------------- | ------------------------------------------------- |
228
+ | `start` | `DoBattle` |
229
+ | `update` | `BattleTurn` |
230
+ | `selectMove` / `saveMove` | `CheckPlayerLockedIn` / `ParsePlayerAction` |
231
+ | `switchIn` | `SwitchPlayerMon` / `SendOutPlayerMon` |
232
+ | `turnOrder` | `DetermineMoveOrder` |
233
+ | `doTurn` | `DoTurn` |
234
+ | `executeMove` | `DoMove` |
235
+ | `beforeMove` | `CheckTurn` |
236
+ | `canMove` | |
237
+ | `decrementPP` | |
238
+ | `doMove` | |
239
+ | `checkCriticalHit` | `BattleCommand_Critical` |
240
+ | `calcDamage` | `BattleCommand_DamageCalc` / `PlayerAttackDamage` |
241
+ | `adjustDamage` | `BattleCommand_Stab` |
242
+ | `randomizeDamage` | `BattleCommand_DamageVariation` |
243
+ | `specialDamage` | `BattleCommand_ConstantDamage` |
244
+ | `applyDamage` | `BattleCommand_ApplyDamage` |
245
+ | `checkHit` | `BattleCommand_CheckHit` |
246
+ | `checkFaint` | `CheckFaint` |
247
+ | `handleResidual` | `ResidualDamage` |
248
+ | `endTurn` | `EndTurn` |
249
+
250
+ DoBattle
251
+ BattleTurn
252
+ - HandleBerserkGene
253
+ - CheckPlayerLockedIn
254
+ - BattleMenu
255
+ - ParsePlayerAction
256
+ - DetermineMoveOrder
257
+ - CompareMovePriority
258
+ - GetMovePriority
259
+ - Battle_*
260
+ - Handle*Switch
261
+ - SwitchPlayerMon
262
+ - BreakAttraction
263
+ - SendOutPlayerMon
264
+ - SpikesDamage
265
+ - *Turn_EndOpponentProtectEndureDestinyBond
266
+ - EndUserDestinyBond
267
+ - Do*Turn
268
+ - EndOpponentProtectEndureDestinyBond
269
+ - ResidualDamage
270
+ - CheckFaint_*
271
+ - HandleBetweenTurnEffects
272
+ - HandleFutureSight
273
+ - HandleWeather
274
+ - HandleWrap
275
+ - HandlePerishSong
276
+ - HandleLeftovers
277
+ - HandleMysteryBerry
278
+ - HandleDefrost
279
+ - HandleSafeguard
280
+ - HandleScreens
281
+ - HandleStatBoostingHeldItems
282
+ - HandleHealingItems
283
+ - HandleEncore
284
+
285
+ DoTurn
286
+ DoMove = executeMove
287
+ CheckTurn = beforeMove
288
+ BattleCommand_Critical = checkCriticalHit
289
+ BattleCommand_Stab = adjustDamage
290
+ BattleCommand_DamageVariation = randomizeDamage
291
+ BattleCommand_CheckHit = checkHit
292
+ BattleCommand_ApplyDamage = applyDamage
293
+ BattleCommand_CheckFaint
294
+ BattleCommand_BuildOpponentRage = buildRage
295
+ BattleCommand_DamageCalc (PlayerAttackDamage / EnemyAttackDamage) = calcDamage
296
+ BattleCommand_ConstantDamage = specialDamage
297
+ DoEnemyDamage / DoPlayerDamage / DoSubstituteDamage
298
+
223
299
  ==**TODO**==
224
300
 
225
301
  ## Protocol
@@ -227,62 +303,7 @@ padding](https://en.wikipedia.org/wiki/Data_structure_alignment) and
227
303
  TODO
228
304
 
229
305
  ```txt
230
- -activate: _, damage, move, name, number, of
231
- -anim: _
232
- -block: _
233
- -boost: _, from, silent
234
- -clearallboost: _
235
- -copyboost: from
236
- -crit: _
237
- -curestatus: msg
238
- -cureteam: from
239
- -damage: _, from, of, partiallytrapped
240
- -end: _, from, of, partiallytrapped, silent
241
- -enditem: _, eat
242
- -fail: _, block, weak
243
- -fieldactivate: _
244
- -heal: _, from, of, silent
245
- -hint: _
246
- -hitcount: _
247
- -immune: _, ohko
248
- -item: from, of
249
- -miss: _
250
- -mustrecharge: _
251
- -ohko: _
252
- -prepare: _
253
- -resisted: _
254
- -setboost: from
255
- -sethp: from, silent
256
- -sideend: _, from, of
257
- -sidestart: _
258
- -singlemove: _
259
- -singleturn: _
260
- -start: _, of, silent
261
- -status: _, from, silent
262
- -supereffective: _
263
- -transform: _
264
- -unboost: _
265
- -weather: _, upkeep
266
- cant: _
267
- debug: _
268
- done: _
269
- drag: _
270
- faint: _
271
- gametype: _
272
- gen: _
273
- move: _, from, miss, still
274
- player: _
275
- request: _
276
- rule: _
277
- start: _
278
- switch: _, from
279
- t:: _
280
- teamsize: _
281
- tie: _
282
- tier: _
283
- turn: _
284
- upkeep: _
285
- win: _
306
+ -activate: _, move, name, number
286
307
  ```
287
308
 
288
309
  ## Bugs
@@ -0,0 +1,143 @@
1
+ const std = @import("std");
2
+
3
+ const pkmn = @import("../pkmn.zig");
4
+
5
+ const common = @import("../common/data.zig");
6
+ const util = @import("../common/util.zig");
7
+
8
+ const chance = @import("chance.zig");
9
+
10
+ const assert = std.debug.assert;
11
+
12
+ const enabled = pkmn.options.calc;
13
+
14
+ const Player = common.Player;
15
+
16
+ const PointerType = util.PointerType;
17
+
18
+ const Actions = chance.Actions;
19
+ const Action = chance.Action;
20
+
21
+ /// Information relevant to damage calculation that occured during a Generation II battle `update`.
22
+ pub const Summaries = extern struct {
23
+ /// Relevant information for Player 1.
24
+ p1: Summary = .{},
25
+ /// Relevant information for Player 2.
26
+ p2: Summary = .{},
27
+
28
+ comptime {
29
+ assert(@sizeOf(Summaries) == 12);
30
+ }
31
+
32
+ /// Returns the `Summary` for the given `player`.
33
+ pub inline fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Summary) {
34
+ assert(@typeInfo(@TypeOf(self)).Pointer.child == Summaries);
35
+ return if (player == .P1) &self.p1 else &self.p2;
36
+ }
37
+ };
38
+
39
+ /// Information relevant to damage calculation that occured during a Generation II battle `update`
40
+ /// for a single player.
41
+ pub const Summary = extern struct {
42
+ /// The computed raw damage values.
43
+ damage: Damage = .{},
44
+
45
+ /// Intermediate raw damage values computed during a calculation.
46
+ pub const Damage = extern struct {
47
+ /// The base computed damage before the damage roll is applied.
48
+ base: u16 = 0,
49
+ /// The final computed damage that gets applied to the Pokémon. May exceed the target's HP
50
+ // (to determine the *actual* damage done compare the target's stored HP before and after).
51
+ final: u16 = 0,
52
+ /// Whether higher damage will saturate / result in the same outcome (e.g. additional damage
53
+ /// gets ignored due to it already breaking a Substitute or causing the target to faint).
54
+ capped: bool = false,
55
+
56
+ // NOTE: 15 bits padding
57
+
58
+ comptime {
59
+ assert(@sizeOf(Damage) == 6);
60
+ }
61
+ };
62
+
63
+ comptime {
64
+ assert(@sizeOf(Summary) == 6);
65
+ }
66
+ };
67
+
68
+ /// TODO
69
+ pub const Overrides = extern struct {
70
+ /// TODO
71
+ actions: Actions = .{},
72
+ };
73
+
74
+ /// Allows for forcing the value of specific RNG events during a Generation II battle `update` via
75
+ /// `overrides` and tracks `summaries` of information relevant to damage calculation.
76
+ pub const Calc = struct {
77
+ /// Overrides the normal behavior of the RNG during an `update` to force specific outcomes.
78
+ overrides: Overrides = .{},
79
+ /// Information relevant to damage calculation.
80
+ summaries: Summaries = .{},
81
+
82
+ pub fn overridden(
83
+ self: Calc,
84
+ player: Player,
85
+ comptime field: Action.Field,
86
+ ) ?std.meta.FieldType(Action, field) {
87
+ if (!enabled) return null;
88
+
89
+ const overrides =
90
+ if (player == .P1) self.overrides.actions.p1 else self.overrides.actions.p2;
91
+ const val = @field(overrides, @tagName(field));
92
+ return if (switch (@typeInfo(@TypeOf(val))) {
93
+ .Enum => val != .None,
94
+ .Int => val != 0,
95
+ else => unreachable,
96
+ }) val else null;
97
+ }
98
+
99
+ pub fn base(self: *Calc, player: Player, val: u16) void {
100
+ if (!enabled) return;
101
+
102
+ self.summaries.get(player).damage.base = val;
103
+ }
104
+
105
+ pub fn final(self: *Calc, player: Player, val: u16) void {
106
+ if (!enabled) return;
107
+
108
+ self.summaries.get(player).damage.final = val;
109
+ }
110
+
111
+ pub fn capped(self: *Calc, player: Player) void {
112
+ if (!enabled) return;
113
+
114
+ self.summaries.get(player).damage.capped = true;
115
+ }
116
+ };
117
+
118
+ /// Null object pattern implementation of Generation II `Calc` which does nothing, though damage
119
+ /// calculator support should additionally be turned off entirely via `options.calc`.
120
+ pub const NULL: Null = .{};
121
+
122
+ const Null = struct {
123
+ pub fn overridden(
124
+ self: Null,
125
+ player: Player,
126
+ comptime field: Action.Field,
127
+ ) ?std.meta.FieldType(Action, field) {
128
+ _ = .{ self, player };
129
+ return null;
130
+ }
131
+
132
+ pub fn base(self: Null, player: Player, val: u16) void {
133
+ _ = .{ self, player, val };
134
+ }
135
+
136
+ pub fn final(self: Null, player: Player, val: u16) void {
137
+ _ = .{ self, player, val };
138
+ }
139
+
140
+ pub fn capped(self: Null, player: Player) void {
141
+ _ = .{ self, player };
142
+ }
143
+ };