uesp-eso-build-wrapper 0.2.0 → 0.3.0

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 CHANGED
@@ -1,10 +1,13 @@
1
1
  # uesp-eso-build-wrapper
2
2
 
3
3
  [![CI](https://github.com/srtomy/uesp-eso-build-wrapper/actions/workflows/ci.yml/badge.svg)](https://github.com/srtomy/uesp-eso-build-wrapper/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/uesp-eso-build-wrapper.svg)](https://www.npmjs.com/package/uesp-eso-build-wrapper)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+ [![Node >=20.12](https://img.shields.io/badge/node-%3E%3D20.12-brightgreen)](https://nodejs.org/en/download/)
4
7
 
5
8
  A Node.js/TypeScript wrapper around the [UESP ESO Build Editor](https://github.com/uesp/uesp-esochardata) math engine.
6
9
 
7
- Calculate Elder Scrolls Online **Computed Character Statistics** — Health, Magicka, Stamina, mitigation, crit chance, regeneration, and 200+ more — using UESP's own formulas. No formula reimplementation. No database.
10
+ Calculate Elder Scrolls Online **Computed Character Statistics** — Health, Magicka, Stamina, mitigation, crit chance, regeneration, and 200+ more — using UESP's own formulas. No formula reimplementation.
8
11
 
9
12
  ## Features
10
13
 
@@ -22,11 +25,21 @@ npm install uesp-eso-build-wrapper
22
25
 
23
26
  ## Quick Start
24
27
 
28
+ Game data is not bundled in the npm package — you supply it via `initEsoEngineFromData`.
29
+ The bundled `vendor/uesp-data/uesp-game-data.json` (committed to this repo) is the canonical source.
30
+
25
31
  ```ts
26
- import { initEsoEngine, calculateBuild } from 'uesp-eso-build-wrapper';
32
+ import fs from 'fs';
33
+ import { initEsoEngineFromData, calculateBuild } from 'uesp-eso-build-wrapper';
34
+ import type { UespInitData } from 'uesp-eso-build-wrapper';
35
+
36
+ // Load game data generated by: npm run generate-data -- --db /path/to/local.db
37
+ const initData = JSON.parse(
38
+ fs.readFileSync('vendor/uesp-data/uesp-game-data.json', 'utf-8')
39
+ ) as UespInitData;
27
40
 
28
- // Initialize once resolves bundled vendor files automatically
29
- initEsoEngine();
41
+ // Initialize once per process (no-op on subsequent calls)
42
+ initEsoEngineFromData({ initData });
30
43
 
31
44
  const stats = calculateBuild({
32
45
  character: {
@@ -37,10 +50,10 @@ const stats = calculateBuild({
37
50
  },
38
51
  });
39
52
 
40
- console.log(stats.Health); // 16000
41
- console.log(stats.Magicka); // 19104
53
+ console.log(stats.Health); // 16000
54
+ console.log(stats.Magicka); // 19104
42
55
  console.log(stats.MagickaRegen); // 514
43
- console.log(stats.SpellDamage); // 1000
56
+ console.log(stats.SpellDamage); // 1000
44
57
  ```
45
58
 
46
59
  ## With Equipped Items
@@ -48,14 +61,12 @@ console.log(stats.SpellDamage); // 1000
48
61
  Items are passed directly as returned by the [UESP public item API](https://esolog.uesp.net/exportJson.php?table=minedItem&id=70&level=50&quality=5).
49
62
 
50
63
  ```ts
51
- // 1. Fetch item data from UESP API
52
64
  const res = await fetch(
53
65
  'https://esolog.uesp.net/exportJson.php?table=minedItem&id=70&level=50&quality=5',
54
66
  );
55
67
  const data = await res.json();
56
- const item = data.minedItem[0]; // pick the variant you want
68
+ const item = data.minedItem[0];
57
69
 
58
- // 2. Pass it directly — no mapping needed
59
70
  const stats = calculateBuild({
60
71
  character: {
61
72
  race: 'Nord',
@@ -71,18 +82,45 @@ const stats = calculateBuild({
71
82
  console.log(stats.Health); // includes item enchant + set bonus
72
83
  ```
73
84
 
85
+ ## With Passives, Buffs, and Skill Bars
86
+
87
+ ```ts
88
+ const stats = calculateBuild({
89
+ character: {
90
+ race: 'High Elf',
91
+ class: 'Sorcerer',
92
+ level: 50,
93
+ attributes: { health: 0, magicka: 64, stamina: 0 },
94
+ mundusStone: 'The Apprentice',
95
+ championPoints: 160,
96
+ },
97
+ // Auto-inject all racial passives at highest rank (mirrors UESP "Auto Purchase Racial Passives")
98
+ autoPassives: true,
99
+ // Named buffs from the UESP buff catalog
100
+ activeBuffs: ['Major Prophecy', 'Minor Slayer', 'Major Sorcery'],
101
+ // Active skills slotted on bars (affects conditional set bonuses and skill-line passives)
102
+ skillBars: {
103
+ bar1: [
104
+ { skillId: 28807, morphIndex: 2 }, // Crystal Fragments
105
+ { skillId: 24322 }, // Mages' Fury
106
+ ],
107
+ },
108
+ });
109
+ ```
110
+
74
111
  ## API
75
112
 
76
- ### `initEsoEngine(resourcesPath?, initDataPath?)`
113
+ ### `initEsoEngineFromData(options)`
77
114
 
78
- Initializes the UESP math engine. **Must be called once** before `calculateBuild()`.
115
+ Initializes the UESP math engine from a `UespInitData` object. **Must be called once** before `calculateBuild()`.
79
116
 
80
117
  Safe to call multiple times — only executes on the first call.
81
118
 
82
- | Param | Type | Default | Description |
83
- | --------------- | -------- | -------------- | ------------------------------------------------ |
84
- | `resourcesPath` | `string` | bundled vendor | Path to `esoEditBuild.js` and `esobuilddata.js` |
85
- | `initDataPath` | `string` | bundled vendor | Path to `uesp-init-data.json` with game formulas |
119
+ | Option | Type | Description |
120
+ | ---------- | -------------- | ---------------------------------------------- |
121
+ | `initData` | `UespInitData` | Parsed game data (see `vendor/uesp-data/`) |
122
+
123
+ ---
86
124
 
87
125
  ### `calculateBuild(input: BuildInput): ComputedStats`
88
126
 
@@ -93,22 +131,35 @@ Runs the UESP engine and returns the computed stats.
93
131
  ```ts
94
132
  interface BuildInput {
95
133
  character: {
96
- race: string; // "High Elf" | "Nord" | "Breton" | "Khajiit" | ...
97
- class: string; // "Sorcerer" | "Dragonknight" | "Nightblade" | ...
98
- level: number; // 1–50
134
+ race: string; // "High Elf" | "Nord" | "Breton" | "Khajiit" | ...
135
+ class: string; // "Sorcerer" | "Dragonknight" | "Nightblade" | ...
136
+ level: number; // 1–50
99
137
  attributes: {
100
- health: number; // attribute points (max 64 total)
138
+ health: number; // attribute points (max 64 total)
101
139
  magicka: number;
102
140
  stamina: number;
103
141
  };
104
- mundusStone?: string; // "The Thief" | "The Apprentice" | ...
105
- cyrodiil?: boolean; // Battle Spirit (PvP)
106
- vampireStage?: number; // 0–4
142
+ mundusStone?: string; // "The Thief" | "The Apprentice" | ...
143
+ mundusStone2?: string; // second Mundus Stone (requires Twice-Born Star set)
144
+ cyrodiil?: boolean; // Battle Spirit (PvP)
145
+ vampireStage?: number; // 0–4
107
146
  werewolfStage?: number; // 0 or 1
108
147
  championPoints?: number; // 0–3600
109
- rulesVersion?: string; // "Live" (default) | "PTS"
148
+ rulesVersion?: string; // "Live" (default) | "PTS"
110
149
  };
111
150
  items?: Partial<Record<EquipSlot, UespItemApiData>>;
151
+ championPointNodes?: Record<string | number, ChampionPointNode>;
152
+ activeBuffs?: string[]; // named buffs — see listAvailableBuffs()
153
+ toggleSkills?: string[]; // toggled skills — see listAvailableToggleSkills()
154
+ skillBars?: {
155
+ bar1?: SkillSlot[];
156
+ bar2?: SkillSlot[];
157
+ };
158
+ activeWeaponBar?: 1 | 2; // which weapon bar is active (default: 1)
159
+ passiveSkills?: number[]; // ability IDs of unlocked passive skills
160
+ autoPassives?: boolean; // auto-inject racial passives (default: false)
161
+ enchantOverrides?: Partial<Record<string, { enchantDesc: string; enchantName?: string }>>;
162
+ toggledSetBonuses?: string[];
112
163
  }
113
164
  ```
114
165
 
@@ -123,7 +174,7 @@ Poison1 | Poison2 | Food | Potion
123
174
 
124
175
  #### `ComputedStats`
125
176
 
126
- Key stats returned (see [`types.ts`](src/lib/eso-engine/types.ts) for full list):
177
+ Key stats returned (see [`types.ts`](src/lib/eso-engine/types.ts) for the full list):
127
178
 
128
179
  | Property | Description |
129
180
  | ------------------------------------------------------ | ----------------------------------------- |
@@ -140,30 +191,127 @@ Key stats returned (see [`types.ts`](src/lib/eso-engine/types.ts) for full list)
140
191
 
141
192
  > All stat IDs match `g_EsoComputedStats` from the UESP engine (version 49+).
142
193
 
143
- ## Updating Formulas After a New ESO Patch
194
+ ---
195
+
196
+ ### Catalog Functions
197
+
198
+ | Function | Returns | Description |
199
+ |---|---|---|
200
+ | `listAvailableBuffs(group?)` | `BuffInfo[]` | 164 named buffs; filter by group `"Major"` \| `"Minor"` \| `"Set"` \| `"Target"` \| … |
201
+ | `listRacialPassives(race)` | `PassiveSkillInfo[]` | All passive ranks for a given race |
202
+ | `listClassPassives(cls)` | `PassiveSkillInfo[]` | All passive ranks for a given class (3 skill lines) |
203
+ | `listPassivesBySkillLine(line)` | `PassiveSkillInfo[]` | Passives for any skill line (Heavy Armor, Undaunted, etc.) |
204
+ | `listAvailableSkillLines()` | `string[]` | All available skill line names |
205
+ | `listAvailableToggleSkills()` | `ToggleSkillInfo[]` | 105 toggle skills; `requiresCyrodiil` marks PvP-only |
206
+
207
+ ```ts
208
+ // Example: see all Major buffs
209
+ const buffs = listAvailableBuffs('Major');
210
+ // Example: find racial passive IDs for a High Elf build
211
+ const passives = listRacialPassives('High Elf');
212
+ const ids = passives.map(p => p.abilityId);
213
+ ```
214
+
215
+ ---
144
216
 
145
- When ZeniMax releases a new patch or DLC, the formulas may change. To update:
217
+ ## Game Data
218
+
219
+ Game data (`UespInitData`) is extracted from the UESP SQL dumps and stored in `vendor/uesp-data/uesp-game-data.json`.
220
+ This file is committed to the repo but **not bundled in the npm package** — consumers are expected to supply their own copy or generate it.
221
+
222
+ ### Generating game data
223
+
224
+ Requires Node.js ≥ 22 (uses `node:sqlite`).
146
225
 
147
226
  ```bash
148
- # 1. Update the UESP submodule
227
+ npm run generate-data -- --db /path/to/local.db [--version 50]
228
+ ```
229
+
230
+ The script reads from `local.db` and writes to `vendor/uesp-data/uesp-game-data.json`.
231
+ To populate `local.db`, use the seed script from [eso-build-editor](https://github.com/srtomy/eso-build-editor).
232
+
233
+ ### Scripts
234
+
235
+ | Command | Description |
236
+ |---|---|
237
+ | `npm run generate-data -- --db <path>` | Generate `vendor/uesp-data/uesp-game-data.json` from a SQLite DB |
238
+ | `npm run test:db-init [json] [db] [version]` | Validate committed JSON matches a live DB |
239
+ | `npm run test:build <export.json>` | Print all computed stats for a UESP build export |
240
+ | `npm run test:explore` | Interactive engine exploration script |
241
+
242
+ ---
243
+
244
+ ## Validating Against the UESP Browser
245
+
246
+ The `scripts/browser-export-build.js` script runs inside the UESP Build Editor DevTools console and exports the full build — inputs and expected stats — as a JSON file.
247
+
248
+ **How to use:**
249
+
250
+ 1. Open [esobuilds.uesp.net](https://esobuilds.uesp.net), configure your build
251
+ 2. Open DevTools (F12) → Console, paste and run `browser-export-build.js`
252
+ 3. A `uesp-build-export.json` file is downloaded
253
+
254
+ **Explore stats interactively:**
255
+
256
+ ```bash
257
+ npm run test:build path/to/uesp-build-export.json
258
+ ```
259
+
260
+ **Add as a golden regression fixture:**
261
+
262
+ ```bash
263
+ cp path/to/uesp-build-export.json tests/fixtures/my-build.json
264
+ npm test
265
+ ```
266
+
267
+ `tests/build-fixtures.test.ts` auto-discovers all `.json` files in `tests/fixtures/` and asserts every stat in `expectedStats` against the wrapper output.
268
+
269
+ ---
270
+
271
+ ## Updating Formulas After a New ESO Patch
272
+
273
+ ```bash
274
+ # 1. Update the UESP engine submodule
149
275
  cd vendor/uesp-esochardata
150
276
  git fetch upstream && git merge upstream/master
151
277
  cd ../..
152
278
 
153
- # 2. Re-extract the formulas from the UESP website
154
- # Open https://esobuilds.uesp.net in a browser
155
- # Run vendor/uesp-data/browser-extract.js in DevTools Console
156
- # Save the result to vendor/uesp-data/uesp-init-data.json
279
+ # 2. Regenerate game data from an up-to-date local.db
280
+ # (populate local.db first via eso-build-editor's npm run db:seed)
281
+ npm run generate-data -- --db /path/to/local.db --version <patch>
282
+
283
+ # 3. Commit the updated game data
284
+ git add vendor/uesp-data/uesp-game-data.json
157
285
 
158
- # 3. Run tests to verify
286
+ # 4. Run tests update any golden values that changed intentionally
159
287
  npm test
160
288
  ```
161
289
 
290
+ ## Framework Integration
291
+
292
+ ### Next.js (Vercel / serverless)
293
+
294
+ The vendor JS files (`esobuilddata.js`, `esoEditBuild.js`) are loaded at runtime via `fs.readFileSync` with a dynamic path. Next.js's output file tracer (`@vercel/nft`) cannot detect them automatically, so they must be declared explicitly in `next.config.ts`:
295
+
296
+ ```ts
297
+ // next.config.ts
298
+ const nextConfig: NextConfig = {
299
+ serverExternalPackages: ['uesp-eso-build-wrapper'],
300
+ outputFileTracingIncludes: {
301
+ '/**': ['./node_modules/uesp-eso-build-wrapper/vendor/**'],
302
+ },
303
+ };
304
+ ```
305
+
306
+ Without this, the engine will throw at runtime in any serverless environment (Vercel, AWS Lambda, etc.) because the vendor files are not included in the deployment bundle.
307
+
308
+ ---
309
+
162
310
  ## License
163
311
 
164
312
  MIT © srtomy
165
313
 
166
- This package bundles files from [uesp/uesp-esochardata](https://github.com/uesp/uesp-esochardata) (MIT).
314
+ This package bundles files from [uesp/uesp-esochardata](https://github.com/uesp/uesp-esochardata) (MIT).
167
315
  See [THIRD_PARTY_NOTICES](THIRD_PARTY_NOTICES) for details.
168
316
 
169
317
  Elder Scrolls Online is a trademark of ZeniMax Media Inc. This project is not affiliated with or endorsed by ZeniMax Media Inc.
@@ -18,6 +18,7 @@
18
18
  * 4. Ler os resultados de g_EsoComputedStats[statId].value
19
19
  */
20
20
  import type { BuffInfo, BuildInput, ComputedStats, PassiveSkillInfo, ToggleSkillInfo } from './types';
21
+ export declare function cacheStatObjects(): void;
21
22
  /**
22
23
  * Calcula os Computed Character Statistics para a build fornecida.
23
24
  *
@@ -42,7 +43,7 @@ export declare function calculateBuild(input: BuildInput): ComputedStats;
42
43
  * Each entry maps to one row in the UESP buff tab. Pass `entry.name` to
43
44
  * `BuildInput.activeBuffs` to enable that buff in a calculation.
44
45
  *
45
- * Must be called after `initEsoEngine()`.
46
+ * Must be called after `initEsoEngineFromData()`.
46
47
  *
47
48
  * @param group - Optional filter. Ex: "Major", "Minor", "Set", "Target",
48
49
  * "Skill", "Potion", "Poison", "Cyrodiil", "Other".
@@ -54,7 +55,7 @@ export declare function listAvailableBuffs(group?: string): BuffInfo[];
54
55
  * Each passive may appear in multiple ranks (rank 1, 2, 3).
55
56
  * Pass the abilityId of the desired rank to BuildInput.passiveSkills.
56
57
  *
57
- * Must be called after initEsoEngine().
58
+ * Must be called after initEsoEngineFromData().
58
59
  *
59
60
  * @param race - Race name as passed to BuildInput.character.race.
60
61
  * Ex: "High Elf", "Nord", "Khajiit"
@@ -65,7 +66,7 @@ export declare function listRacialPassives(race: string): PassiveSkillInfo[];
65
66
  * Each passive may appear in multiple ranks (rank 1, 2, 3).
66
67
  * Pass the abilityId of the desired rank to BuildInput.passiveSkills.
67
68
  *
68
- * Must be called after initEsoEngine().
69
+ * Must be called after initEsoEngineFromData().
69
70
  *
70
71
  * @param className - Class name as passed to BuildInput.character.class.
71
72
  * Ex: "Sorcerer", "Nightblade", "Dragonknight"
@@ -76,7 +77,7 @@ export declare function listClassPassives(className: string): PassiveSkillInfo[]
76
77
  * Each passive may appear in multiple ranks (rank 1, 2, 3).
77
78
  * Pass the abilityId of the desired rank to BuildInput.passiveSkills.
78
79
  *
79
- * Must be called after initEsoEngine().
80
+ * Must be called after initEsoEngineFromData().
80
81
  *
81
82
  * @param skillLine - Skill line name as it appears in g_SkillsData.
82
83
  * Use listAvailableSkillLines() to discover valid names.
@@ -89,7 +90,7 @@ export declare function listPassivesBySkillLine(skillLine: string): PassiveSkill
89
90
  * Returns all skill line names that have passive skills available.
90
91
  * Use the returned names with listPassivesBySkillLine().
91
92
  *
92
- * Must be called after initEsoEngine().
93
+ * Must be called after initEsoEngineFromData().
93
94
  */
94
95
  export declare function listAvailableSkillLines(): string[];
95
96
  /**
@@ -100,7 +101,7 @@ export declare function listAvailableSkillLines(): string[];
100
101
  * Toggle skills backed by a passive (isPassive=true) need the associated skill
101
102
  * in passiveSkills/skillBars for the engine to process the description match.
102
103
  *
103
- * Must be called after initEsoEngine().
104
+ * Must be called after initEsoEngineFromData().
104
105
  */
105
106
  export declare function listAvailableToggleSkills(): ToggleSkillInfo[];
106
107
  //# sourceMappingURL=calculator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"calculator.d.ts","sourceRoot":"","sources":["../../../src/lib/eso-engine/calculator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EACV,QAAQ,EACR,UAAU,EAEV,aAAa,EAEb,gBAAgB,EAEhB,eAAe,EAEhB,MAAM,SAAS,CAAC;AAqCjB;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,aAAa,CAgc/D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,EAAE,CA6B7D;AA2BD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAKnE;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAKvE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAK7E;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,EAAE,CASlD;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,IAAI,eAAe,EAAE,CA6B7D"}
1
+ {"version":3,"file":"calculator.d.ts","sourceRoot":"","sources":["../../../src/lib/eso-engine/calculator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EACV,QAAQ,EACR,UAAU,EAEV,aAAa,EAEb,gBAAgB,EAEhB,eAAe,EAEhB,MAAM,SAAS,CAAC;AAMjB,wBAAgB,gBAAgB,IAAI,IAAI,CAKvC;AAqCD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,aAAa,CAwf/D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,EAAE,CA6B7D;AA2BD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAKnE;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAKvE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAK7E;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,EAAE,CASlD;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,IAAI,eAAe,EAAE,CA6B7D"}