@archivist-gg/dnd5e 0.2.0 → 0.3.1

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 (58) hide show
  1. package/dist/background/background.codec.js +3 -3
  2. package/dist/background/background.parser.js +2 -2
  3. package/dist/background/background.schema.d.ts +8 -0
  4. package/dist/background/background.schema.js +5 -3
  5. package/dist/chunk-353WXS6S.js +25 -0
  6. package/dist/{chunk-DC74TZNA.js → chunk-4LZJXJJS.js} +7 -2
  7. package/dist/{chunk-DZXSWSBP.js → chunk-5ZBE4CWV.js} +1 -1
  8. package/dist/{chunk-IXZXFFFW.js → chunk-6IRYTM5V.js} +6 -1
  9. package/dist/chunk-7TEK5KK7.js +49 -0
  10. package/dist/{chunk-V6SOFUJW.js → chunk-AIRFC5NQ.js} +3 -1
  11. package/dist/{chunk-VTYUBINW.js → chunk-BIK4DFWK.js} +1 -1
  12. package/dist/{chunk-TQZB4OCJ.js → chunk-D3CLOBE3.js} +49 -21
  13. package/dist/{chunk-3TRGRSCH.js → chunk-LV4SNWZQ.js} +7 -1
  14. package/dist/{chunk-UC4FE5X3.js → chunk-N6N74K6X.js} +1 -1
  15. package/dist/chunk-O5EYMKFH.js +6 -0
  16. package/dist/{chunk-TUIFSVLI.js → chunk-SE7AZDR3.js} +1 -0
  17. package/dist/{chunk-5WQD3WNZ.js → chunk-TN6ZLTNU.js} +6 -5
  18. package/dist/{chunk-R5SQHP2X.js → chunk-TNUFPIXI.js} +48 -30
  19. package/dist/{chunk-J2RYSZMQ.js → chunk-VTTA2ALZ.js} +2 -2
  20. package/dist/class/class.slug.d.ts +7 -5
  21. package/dist/class/class.slug.js +1 -1
  22. package/dist/entities/base-item-resolver.d.ts +9 -0
  23. package/dist/entities/base-item-resolver.js +6 -4
  24. package/dist/entities/slug.d.ts +7 -0
  25. package/dist/entities/slug.js +3 -1
  26. package/dist/index.js +3902 -3623
  27. package/dist/item/item.actions-map.d.ts +8 -7
  28. package/dist/item/item.actions-map.js +3 -4
  29. package/dist/item/item.ai-schema.d.ts +1 -1
  30. package/dist/pc/pc.decision-engine.d.ts +13 -3
  31. package/dist/pc/pc.decision-engine.js +9 -9
  32. package/dist/pc/pc.equipment.js +6 -12
  33. package/dist/pc/pc.feature-effects.d.ts +5 -0
  34. package/dist/pc/pc.feature-effects.js +2 -11
  35. package/dist/pc/pc.parser.js +1 -1
  36. package/dist/pc/pc.pools.js +7 -7
  37. package/dist/pc/pc.proficiencies.d.ts +31 -0
  38. package/dist/pc/pc.proficiencies.js +96 -23
  39. package/dist/pc/pc.proficiency-normalize.d.ts +5 -0
  40. package/dist/pc/pc.proficiency-query.js +5 -1
  41. package/dist/pc/pc.recalc.d.ts +7 -0
  42. package/dist/pc/pc.recalc.js +112 -58
  43. package/dist/pc/pc.resolver.js +7 -7
  44. package/dist/pc/pc.rest.d.ts +1 -1
  45. package/dist/pc/pc.rest.js +20 -11
  46. package/dist/pc/pc.schema.d.ts +10 -4
  47. package/dist/pc/pc.schema.js +1 -1
  48. package/dist/pc/pc.slotting.js +3 -3
  49. package/dist/pc/pc.spellcasting.d.ts +5 -1
  50. package/dist/pc/pc.spellcasting.js +4 -2
  51. package/dist/pc/pc.types.d.ts +30 -4
  52. package/dist/spell/spell.access.js +1 -1
  53. package/dist/types/choice.d.ts +5 -0
  54. package/dist/types/choice.js +9 -3
  55. package/dist/weapon/weapon.classify.d.ts +21 -0
  56. package/dist/weapon/weapon.classify.js +7 -0
  57. package/package.json +1 -1
  58. package/dist/chunk-BH6AMZVY.js +0 -25
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  backgroundCodec
3
- } from "../chunk-VTYUBINW.js";
4
- import "../chunk-DZXSWSBP.js";
5
- import "../chunk-TUIFSVLI.js";
3
+ } from "../chunk-BIK4DFWK.js";
4
+ import "../chunk-5ZBE4CWV.js";
5
+ import "../chunk-SE7AZDR3.js";
6
6
  import "../chunk-3DZ74AKK.js";
7
7
  import "../chunk-NXSWH3AE.js";
8
8
  import "../chunk-NXR3BD2V.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  parseBackground
3
- } from "../chunk-DZXSWSBP.js";
4
- import "../chunk-TUIFSVLI.js";
3
+ } from "../chunk-5ZBE4CWV.js";
4
+ import "../chunk-SE7AZDR3.js";
5
5
  import "../chunk-3DZ74AKK.js";
6
6
  import "../chunk-NXSWH3AE.js";
7
7
  import "../chunk-NXR3BD2V.js";
@@ -1,4 +1,12 @@
1
1
  import { z } from "zod";
2
+ export declare const langProfSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
3
+ kind: z.ZodLiteral<"fixed">;
4
+ languages: z.ZodArray<z.ZodString>;
5
+ }, z.core.$strip>, z.ZodObject<{
6
+ kind: z.ZodLiteral<"choice">;
7
+ count: z.ZodNumber;
8
+ from: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
9
+ }, z.core.$strip>], "kind">;
2
10
  export declare const backgroundEntitySchema: z.ZodObject<{
3
11
  slug: z.ZodString;
4
12
  name: z.ZodString;
@@ -1,9 +1,11 @@
1
1
  import {
2
- backgroundEntitySchema
3
- } from "../chunk-TUIFSVLI.js";
2
+ backgroundEntitySchema,
3
+ langProfSchema
4
+ } from "../chunk-SE7AZDR3.js";
4
5
  import "../chunk-3DZ74AKK.js";
5
6
  import "../chunk-NXSWH3AE.js";
6
7
  import "../chunk-NXR3BD2V.js";
7
8
  export {
8
- backgroundEntitySchema
9
+ backgroundEntitySchema,
10
+ langProfSchema
9
11
  };
@@ -0,0 +1,25 @@
1
+ import {
2
+ bareEntitySlug
3
+ } from "./chunk-LV4SNWZQ.js";
4
+
5
+ // src/weapon/weapon.classify.ts
6
+ var PURE_RANGED_THROWN_SLUGS = /* @__PURE__ */ new Set(["dart", "net"]);
7
+ function classifyWeaponRange(weapon) {
8
+ const has = (p) => weapon.properties.some((x) => x === p);
9
+ const isCatRanged = /ranged/.test(weapon.category);
10
+ const pureRanged = isCatRanged && (has("ammunition") || has("loading") || PURE_RANGED_THROWN_SLUGS.has(bareEntitySlug(weapon.slug)));
11
+ if (pureRanged) {
12
+ return {
13
+ melee: null,
14
+ ranged: weapon.range ? { normal: weapon.range.normal, long: weapon.range.long, thrown: false } : null
15
+ };
16
+ }
17
+ return {
18
+ melee: { reach: 5 + (has("reach") ? 5 : 0) },
19
+ ranged: weapon.range ? { normal: weapon.range.normal, long: weapon.range.long, thrown: true } : null
20
+ };
21
+ }
22
+
23
+ export {
24
+ classifyWeaponRange
25
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  slugify
3
- } from "./chunk-3TRGRSCH.js";
3
+ } from "./chunk-LV4SNWZQ.js";
4
4
 
5
5
  // src/entities/base-item-resolver.ts
6
6
  function unwrapWikilink(input) {
@@ -10,6 +10,7 @@ function unwrapWikilink(input) {
10
10
  const pipe = inner.indexOf("|");
11
11
  return pipe === -1 ? inner : inner.slice(0, pipe);
12
12
  }
13
+ var FOLDER_TO_TYPE = { Weapons: "weapon", Armor: "armor" };
13
14
  function vaultPathToSlug(target) {
14
15
  const segments = target.split("/").map((s) => s.trim()).filter((s) => s.length > 0);
15
16
  if (segments.length === 0) return "";
@@ -17,10 +18,13 @@ function vaultPathToSlug(target) {
17
18
  return slugify(segments[0]);
18
19
  }
19
20
  const compendiumName = segments[0];
21
+ const typeFolder = segments[segments.length - 2];
20
22
  const basename = segments[segments.length - 1];
21
23
  const prefix = slugify(compendiumName);
22
24
  const nameSlug = slugify(basename);
23
- return prefix && nameSlug ? `${prefix}_${nameSlug}` : nameSlug;
25
+ if (!prefix || !nameSlug) return nameSlug;
26
+ const type = FOLDER_TO_TYPE[typeFolder];
27
+ return type ? `${prefix}_${type}_${nameSlug}` : `${prefix}_${nameSlug}`;
24
28
  }
25
29
  function resolveBaseItem(baseItem, registry) {
26
30
  var _a, _b;
@@ -41,6 +45,7 @@ function resolveBaseItemOfType(baseItem, entityType, registry) {
41
45
  }
42
46
 
43
47
  export {
48
+ vaultPathToSlug,
44
49
  resolveBaseItem,
45
50
  resolveBaseItemOfType
46
51
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  backgroundEntitySchema
3
- } from "./chunk-TUIFSVLI.js";
3
+ } from "./chunk-SE7AZDR3.js";
4
4
 
5
5
  // src/background/background.parser.ts
6
6
  import { parseYaml } from "@archivist-gg/core";
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-5XUXXAQJ.js";
4
4
  import {
5
5
  bareSlug
6
- } from "./chunk-J2RYSZMQ.js";
6
+ } from "./chunk-VTTA2ALZ.js";
7
7
  import {
8
8
  abilityModifier
9
9
  } from "./chunk-NAEPSZAM.js";
@@ -24,6 +24,10 @@ function resolveSpellcasting(rc) {
24
24
  table
25
25
  };
26
26
  }
27
+ function effectiveSpellcastingAbility(classSlug, dataAbility, overrides) {
28
+ var _a, _b;
29
+ return (_b = (_a = overrides.spellcasting_ability_by_class) == null ? void 0 : _a[classSlug]) != null ? _b : dataAbility;
30
+ }
27
31
  function normalizeKnownSpell(entry) {
28
32
  var _a, _b;
29
33
  if (typeof entry === "string") {
@@ -263,6 +267,7 @@ function computeSpellLimits(inputs) {
263
267
 
264
268
  export {
265
269
  resolveSpellcasting,
270
+ effectiveSpellcastingAbility,
266
271
  normalizeKnownSpell,
267
272
  deriveSpellSlots,
268
273
  computeSpellLimits
@@ -0,0 +1,49 @@
1
+ // src/types/choice.ts
2
+ var ALL_SKILL_SLUGS = [
3
+ "acrobatics",
4
+ "animal-handling",
5
+ "arcana",
6
+ "athletics",
7
+ "deception",
8
+ "history",
9
+ "insight",
10
+ "intimidation",
11
+ "investigation",
12
+ "medicine",
13
+ "nature",
14
+ "perception",
15
+ "performance",
16
+ "persuasion",
17
+ "religion",
18
+ "sleight-of-hand",
19
+ "stealth",
20
+ "survival"
21
+ ];
22
+ var STANDARD_LANGUAGES = [
23
+ "common",
24
+ "dwarvish",
25
+ "elvish",
26
+ "giant",
27
+ "gnomish",
28
+ "goblin",
29
+ "halfling",
30
+ "orc"
31
+ ];
32
+ var EXOTIC_LANGUAGES = [
33
+ "abyssal",
34
+ "celestial",
35
+ "deep-speech",
36
+ "draconic",
37
+ "infernal",
38
+ "primordial",
39
+ "sylvan",
40
+ "undercommon"
41
+ ];
42
+ var ALL_LANGUAGES = [...STANDARD_LANGUAGES, ...EXOTIC_LANGUAGES];
43
+
44
+ export {
45
+ ALL_SKILL_SLUGS,
46
+ STANDARD_LANGUAGES,
47
+ EXOTIC_LANGUAGES,
48
+ ALL_LANGUAGES
49
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  bareEntitySlug
3
- } from "./chunk-R5SQHP2X.js";
3
+ } from "./chunk-LV4SNWZQ.js";
4
4
  import {
5
5
  ABILITY_KEYS
6
6
  } from "./chunk-WP2CX2JO.js";
@@ -10,6 +10,7 @@ function emptyFeatureEffectTotals() {
10
10
  return {
11
11
  initiative_bonus: 0,
12
12
  hp_per_level_bonus: 0,
13
+ hp_per_level_terms: [],
13
14
  speed_walk_bonus: 0,
14
15
  speed_walk_set: 0,
15
16
  senses: { darkvision: 0, blindsight: 0, tremorsense: 0, truesight: 0 },
@@ -66,6 +67,7 @@ function applyEffect(out, eff, label) {
66
67
  break;
67
68
  case "hp-per-level-bonus":
68
69
  out.hp_per_level_bonus += eff.value;
70
+ out.hp_per_level_terms.push({ label, value: eff.value });
69
71
  break;
70
72
  case "speed-bonus":
71
73
  if (eff.mode === "walk") {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseBackground
3
- } from "./chunk-DZXSWSBP.js";
3
+ } from "./chunk-5ZBE4CWV.js";
4
4
 
5
5
  // src/background/background.codec.ts
6
6
  import yaml from "js-yaml";
@@ -1,19 +1,25 @@
1
1
  import {
2
- bareEntitySlug
3
- } from "./chunk-R5SQHP2X.js";
2
+ classifyWeaponRange
3
+ } from "./chunk-353WXS6S.js";
4
+ import {
5
+ normKey
6
+ } from "./chunk-O5EYMKFH.js";
4
7
  import {
5
8
  defaultSlotForType,
6
9
  effectiveArmor,
7
10
  isItemEntity,
8
11
  isWeaponEntity,
9
12
  resolveEntityForEntry
10
- } from "./chunk-UC4FE5X3.js";
13
+ } from "./chunk-N6N74K6X.js";
11
14
  import {
12
15
  readNumericBonus
13
16
  } from "./chunk-LWFTHJIE.js";
14
17
  import {
15
18
  resolveBaseItem
16
- } from "./chunk-DC74TZNA.js";
19
+ } from "./chunk-4LZJXJJS.js";
20
+ import {
21
+ bareEntitySlug
22
+ } from "./chunk-LV4SNWZQ.js";
17
23
 
18
24
  // src/weapon/weapon-mastery.ts
19
25
  var MASTERY = {
@@ -245,7 +251,14 @@ function assignSlots(resolved, registry, warnings) {
245
251
  const { entity, entityType } = resolveEntityForEntry(entry.item, registry);
246
252
  if (!entity || !entityType) continue;
247
253
  const defaultSlot = defaultSlotForType(entityType, entity, registry);
248
- if (!defaultSlot) continue;
254
+ if (!defaultSlot) {
255
+ if (isItemEntity(entity) && typeof entity.base_item === "string" && entity.base_item.length > 0) {
256
+ warnings.push(
257
+ `Magic item "${entity.name}" base_item ${entity.base_item} did not resolve to a base weapon or armor; not slotted (AC/attack not applied).`
258
+ );
259
+ }
260
+ continue;
261
+ }
249
262
  const placed = { index: i, entity, entityType, entry };
250
263
  if (defaultSlot === "mainhand") {
251
264
  if (!slots.mainhand) slots.mainhand = placed;
@@ -265,12 +278,17 @@ function assignSlots(resolved, registry, warnings) {
265
278
  }
266
279
  return slots;
267
280
  }
268
- function computeAC(equippedSlots, resolved, mods, registry) {
281
+ function computeAC(equippedSlots, resolved, mods, registry, warnings) {
269
282
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
270
283
  const breakdown = [];
271
284
  const armorSlot = equippedSlots.armor;
272
285
  const armorEntity = (_a = armorSlot == null ? void 0 : armorSlot.entity) != null ? _a : null;
273
286
  const armor = effectiveArmor(armorEntity, registry);
287
+ if (isItemEntity(armorEntity) && typeof armorEntity.base_item === "string" && !armor) {
288
+ warnings.push(
289
+ `Magic armor "${armorEntity.name}" base_item ${armorEntity.base_item} did not resolve to a base armor; AC not applied.`
290
+ );
291
+ }
274
292
  let ac = 10;
275
293
  if (armor) {
276
294
  const armorBase = armor.ac.base + ((_b = armor.ac.flat) != null ? _b : 0);
@@ -329,8 +347,9 @@ function hasProperty(weapon, prop) {
329
347
  return weapon.properties.some((p) => p === prop);
330
348
  }
331
349
  function attackAbility(weapon, mods, weaponAbilities) {
332
- const isRanged = /ranged/.test(weapon.category);
333
- if (!isRanged) {
350
+ const modes = classifyWeaponRange(weapon);
351
+ const finesse = hasProperty(weapon, "finesse");
352
+ if (modes.melee) {
334
353
  const scoped = weaponAbilities.find((o2) => {
335
354
  var _a;
336
355
  return (_a = o2.weaponSlugs) == null ? void 0 : _a.includes(bareEntitySlug(weapon.slug));
@@ -338,14 +357,14 @@ function attackAbility(weapon, mods, weaponAbilities) {
338
357
  const glob = weaponAbilities.find((o2) => !o2.weaponSlugs);
339
358
  const o = scoped != null ? scoped : glob;
340
359
  if (o) return o.ability;
360
+ if (finesse) return mods.dex >= mods.str ? "dex" : "str";
361
+ return "str";
341
362
  }
342
- if (isRanged) return "dex";
343
- if (hasProperty(weapon, "finesse")) return mods.dex >= mods.str ? "dex" : "str";
344
- return "str";
363
+ return finesse && mods.str > mods.dex ? "str" : "dex";
345
364
  }
346
365
  function isWeaponSlugProficient(weapon, weaponSlug, profs) {
347
366
  const wp = profs.weapons;
348
- if (wp.specific.includes(weaponSlug)) return true;
367
+ if (wp.specific.some((e) => e === weaponSlug || normKey(e) === normKey(weapon.name))) return true;
349
368
  const cat = weapon.category;
350
369
  if (typeof cat === "string") {
351
370
  if (/^simple\b/.test(cat) && wp.categories.includes("simple")) return true;
@@ -420,8 +439,10 @@ function buildAttackRow(args) {
420
439
  { source: `${ability.toUpperCase()} modifier`, amount: abilityMod, kind: "ability" },
421
440
  { source: magicSource, amount: magic.dmg, kind: "item" }
422
441
  ];
423
- const isRanged = /ranged/.test(weapon.category);
424
- const range = isRanged && weapon.range ? `${weapon.range.normal}/${weapon.range.long}` : "melee";
442
+ const modes = classifyWeaponRange(weapon);
443
+ const fmtRanged = (r) => `${r.normal}/${r.long} ft`;
444
+ const range = modes.melee ? `${modes.melee.reach} ft` : modes.ranged ? fmtRanged(modes.ranged) : void 0;
445
+ const thrownRange = modes.melee && modes.ranged ? fmtRanged(modes.ranged) : void 0;
425
446
  const stringProps = weapon.properties.filter(
426
447
  (p) => typeof p === "string"
427
448
  );
@@ -429,7 +450,8 @@ function buildAttackRow(args) {
429
450
  const row = {
430
451
  id,
431
452
  name,
432
- range,
453
+ ...range !== void 0 ? { range } : {},
454
+ ...thrownRange !== void 0 ? { thrownRange } : {},
433
455
  toHit,
434
456
  damageDice,
435
457
  damageType: (_b = magic.damageTypeOverride) != null ? _b : weapon.damage.type,
@@ -446,7 +468,7 @@ function buildAttackRow(args) {
446
468
  breakdown: { toHit: toHitBreakdown, damage: damageBreakdown },
447
469
  informational: args.magic.informational.length > 0 ? args.magic.informational : void 0,
448
470
  slotKey: args.slotKey,
449
- subLabel: formatWeaponSubLabel(weapon, finalProps),
471
+ subLabel: formatWeaponSubLabel(weapon, finalProps, modes),
450
472
  actionCost: args.actionCost,
451
473
  versatile: args.versatileDice ? { damageDice: formatDice(args.versatileDice) } : void 0
452
474
  };
@@ -460,10 +482,16 @@ function buildAttackRow(args) {
460
482
  }
461
483
  return row;
462
484
  }
463
- function formatWeaponSubLabel(weapon, properties) {
485
+ function humanizeWeaponToken(s) {
486
+ return s.replace(/[-_]/g, " ").replace(/(^|[\s(])\w/g, (c) => c.toUpperCase());
487
+ }
488
+ function formatWeaponSubLabel(weapon, properties, modes) {
464
489
  const parts = [];
465
- if (weapon.category) parts.push(weapon.category);
466
- if (properties.length > 0) parts.push(properties.join(", "));
490
+ if (weapon.category) {
491
+ const cat = modes.melee && /ranged/.test(weapon.category) ? weapon.category.replace("ranged", "melee") : weapon.category;
492
+ parts.push(humanizeWeaponToken(cat));
493
+ }
494
+ if (properties.length > 0) parts.push(properties.map(humanizeWeaponToken).join(", "));
467
495
  return parts.join(" \xB7 ");
468
496
  }
469
497
  function computeAttacks(equippedSlots, mods, profs, registry, warnings, proficiencyBonus, ctx, weaponMasteries, weaponAbilities = []) {
@@ -486,7 +514,7 @@ function computeAttacks(equippedSlots, mods, profs, registry, warnings, proficie
486
514
  if (found && found.entityType === "weapon" && isWeaponEntity(found.data)) {
487
515
  weapon = found.data;
488
516
  } else {
489
- warnings.push(`Magic weapon ${entity.name} references missing base_item [[${baseSlug}]].`);
517
+ warnings.push(`Magic weapon ${entity.name} references missing base_item ${baseSlug}.`);
490
518
  continue;
491
519
  }
492
520
  } else {
@@ -547,7 +575,7 @@ function computeSlotsAndAttacks(resolved, mods, profs, registry, warnings, profi
547
575
  var _a, _b, _c;
548
576
  const equippedSlots = assignSlots(resolved, registry, warnings);
549
577
  const overrides = (_a = resolved.definition.overrides) != null ? _a : {};
550
- const acOut = computeAC(equippedSlots, resolved, mods, registry);
578
+ const acOut = computeAC(equippedSlots, resolved, mods, registry, warnings);
551
579
  const ctx = buildConditionContext(resolved, equippedSlots);
552
580
  const attacks = computeAttacks(equippedSlots, mods, profs, registry, warnings, proficiencyBonus, ctx, weaponMasteries, weaponAbilities);
553
581
  return {
@@ -2,7 +2,13 @@
2
2
  function slugify(name) {
3
3
  return name.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/[\s]+/g, "-").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "");
4
4
  }
5
+ function bareEntitySlug(slug) {
6
+ if (!slug) return "";
7
+ const p = slug.split("_");
8
+ return p.length >= 3 ? p.slice(2).join("_") : p[p.length - 1];
9
+ }
5
10
 
6
11
  export {
7
- slugify
12
+ slugify,
13
+ bareEntitySlug
8
14
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  resolveBaseItem
3
- } from "./chunk-DC74TZNA.js";
3
+ } from "./chunk-4LZJXJJS.js";
4
4
 
5
5
  // src/pc/pc.slotting.ts
6
6
  function isItemEntity(e) {
@@ -0,0 +1,6 @@
1
+ // src/pc/pc.proficiency-normalize.ts
2
+ var normKey = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim().split(/\s+/).filter(Boolean).map((w) => w.endsWith("s") ? w.slice(0, -1) : w).sort().join(" ");
3
+
4
+ export {
5
+ normKey
6
+ };
@@ -79,5 +79,6 @@ var backgroundEntitySchema = z.object({
79
79
  });
80
80
 
81
81
  export {
82
+ langProfSchema,
82
83
  backgroundEntitySchema
83
84
  };
@@ -94,14 +94,15 @@ var characterOverridesSchema = z.object({
94
94
  investigation: z.number().int().optional(),
95
95
  insight: z.number().int().optional()
96
96
  }).partial().optional(),
97
- hp: z.object({ max: z.number().int().positive().optional() }).optional(),
97
+ hp: z.object({
98
+ max: z.number().int().positive().optional(),
99
+ rolled: z.number().int().positive().optional(),
100
+ modifier: z.number().int().optional()
101
+ }).optional(),
98
102
  ac: z.number().int().optional(),
99
103
  speed: z.number().int().optional(),
100
104
  initiative: z.number().int().optional(),
101
- spellcasting: z.object({
102
- saveDC: z.number().int().optional(),
103
- attackBonus: z.number().int().optional()
104
- }).optional(),
105
+ spellcasting_ability_by_class: z.record(z.string(), abilityEnum).optional(),
105
106
  // Character-level DC-ability fallback for own-ability-less spells (e.g. a Spell
106
107
  // Scroll cast by a non-caster). Same ability enum as the per-instance spell_ability.
107
108
  spellcasting_ability: abilityEnum.optional(),
@@ -1,20 +1,25 @@
1
1
  import {
2
+ ALL_LANGUAGES,
2
3
  ALL_SKILL_SLUGS
3
- } from "./chunk-BH6AMZVY.js";
4
- import {
5
- isItemEntity,
6
- resolveEntityForEntry
7
- } from "./chunk-UC4FE5X3.js";
4
+ } from "./chunk-7TEK5KK7.js";
8
5
  import {
6
+ effectiveSpellcastingAbility,
9
7
  normalizeKnownSpell,
10
8
  resolveSpellcasting
11
- } from "./chunk-IXZXFFFW.js";
9
+ } from "./chunk-6IRYTM5V.js";
12
10
  import {
13
11
  readTableColumn
14
12
  } from "./chunk-5XUXXAQJ.js";
15
13
  import {
16
14
  recognizeDecision
17
15
  } from "./chunk-JEYQU3JT.js";
16
+ import {
17
+ isItemEntity,
18
+ resolveEntityForEntry
19
+ } from "./chunk-N6N74K6X.js";
20
+ import {
21
+ bareEntitySlug
22
+ } from "./chunk-LV4SNWZQ.js";
18
23
  import {
19
24
  ABILITY_KEYS
20
25
  } from "./chunk-WP2CX2JO.js";
@@ -131,8 +136,9 @@ var PCResolver = class {
131
136
  }
132
137
  }
133
138
  const ownSpellcastingAbility = (_h = classes.map((c) => {
134
- var _a2, _b2;
135
- return (_b2 = (_a2 = resolveSpellcasting(c)) == null ? void 0 : _a2.ability) != null ? _b2 : null;
139
+ if (!c.entity) return null;
140
+ const p = resolveSpellcasting(c);
141
+ return p ? effectiveSpellcastingAbility(c.entity.slug, p.ability, character.overrides) : null;
136
142
  }).find((a) => a != null)) != null ? _h : null;
137
143
  spells.push(...collectItemGrantedSpells(character, ownSpellcastingAbility, this.entities, warnings));
138
144
  const chosenMasteries = collectChosenWeaponMasteries(character);
@@ -483,11 +489,6 @@ function wikilinkTailSlug(link) {
483
489
  const tail = (_a = inner.split("/").pop()) != null ? _a : inner;
484
490
  return tail.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
485
491
  }
486
- function bareEntitySlug(slug) {
487
- if (!slug) return "";
488
- const i = slug.indexOf("_");
489
- return i === -1 ? slug : slug.slice(i + 1);
490
- }
491
492
  function matchesFilter(e, where, ownerBare) {
492
493
  var _a, _b, _c, _d, _e;
493
494
  const d = e.data;
@@ -547,7 +548,7 @@ function enumerateOptions(choice, ctx, ownerBare) {
547
548
  return filtered.map((e) => ({ value: e.slug, label: e.name, entity: e }));
548
549
  }
549
550
  case "select-proficiency": {
550
- const pool = (_a = choice.from) != null ? _a : choice.domain === "skill" ? [...ALL_SKILL_SLUGS] : [];
551
+ const pool = (_a = choice.from) != null ? _a : choice.domain === "skill" ? [...ALL_SKILL_SLUGS] : choice.domain === "language" ? [...ALL_LANGUAGES] : [];
551
552
  return pool.map((v) => ({ value: v, label: v.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()) }));
552
553
  }
553
554
  case "ability-points": {
@@ -650,22 +651,11 @@ function buildSubclassItem(choice, source, level, featureName, c, ctx, ownerBare
650
651
  status: selected ? "resolved" : "unresolved"
651
652
  };
652
653
  }
653
- function collectChosenProficiencies(resolved) {
654
+ function visitProficiencyChoices(resolved, visit) {
654
655
  var _a, _b;
655
- const out = { skills: [], expertise: [], languages: [], tools: [] };
656
- const apply = (choice, selected) => {
657
- if (choice.kind !== "select-proficiency") return;
658
- const vals = Array.isArray(selected) ? selected : typeof selected === "string" ? [selected] : [];
659
- const pool = choice.from;
660
- const valid = pool ? vals.filter((v) => pool.includes(v)) : vals;
661
- const bucket = choice.domain === "skill" ? choice.expertise ? out.expertise : out.skills : choice.domain === "language" ? out.languages : choice.domain === "tool" ? out.tools : null;
662
- if (bucket) {
663
- for (const v of valid) if (!bucket.includes(v)) bucket.push(v);
664
- }
665
- };
666
656
  const walk = (choices, read) => {
667
657
  for (const ch of choices != null ? choices : []) {
668
- apply(ch, read(ch.id));
658
+ visit(ch, read(ch.id));
669
659
  if (ch.kind === "select-inline") {
670
660
  const sel = read(ch.id);
671
661
  const branch = typeof sel === "string" ? ch.options.find((o) => o.value === sel) : void 0;
@@ -682,7 +672,7 @@ function collectChosenProficiencies(resolved) {
682
672
  return (_a3 = c.choices[lvl]) == null ? void 0 : _a3[id];
683
673
  };
684
674
  if (i === 0 && ((_b2 = (_a2 = entity.skill_choices) == null ? void 0 : _a2.from) == null ? void 0 : _b2.length)) {
685
- apply({
675
+ visit({
686
676
  kind: "select-proficiency",
687
677
  id: "skills",
688
678
  count: entity.skill_choices.count,
@@ -706,11 +696,39 @@ function collectChosenProficiencies(resolved) {
706
696
  if (resolved.background) {
707
697
  walk(resolved.background.choices, originRead("background"));
708
698
  if (resolved.background.feature) {
709
- walk(resolved.background.feature.choices, originRead("background"));
699
+ const feature = resolved.background.feature;
700
+ walk(feature.choices, originRead("background"));
710
701
  }
711
702
  }
703
+ }
704
+ function collectChosenProficiencies(resolved) {
705
+ const out = { skills: [], expertise: [], languages: [], tools: [] };
706
+ visitProficiencyChoices(resolved, (choice, selected) => {
707
+ if (choice.kind !== "select-proficiency") return;
708
+ const vals = Array.isArray(selected) ? selected : typeof selected === "string" ? [selected] : [];
709
+ const pool = choice.from;
710
+ const valid = pool ? vals.filter((v) => pool.includes(v)) : vals;
711
+ const bucket = choice.domain === "skill" ? choice.expertise ? out.expertise : out.skills : choice.domain === "language" ? out.languages : choice.domain === "tool" ? out.tools : null;
712
+ if (bucket) {
713
+ for (const v of valid) if (!bucket.includes(v)) bucket.push(v);
714
+ }
715
+ });
712
716
  return out;
713
717
  }
718
+ function collectLanguageToolChoiceStatus(resolved) {
719
+ const languages = [];
720
+ const tools = [];
721
+ visitProficiencyChoices(resolved, (choice, selected) => {
722
+ if (choice.kind !== "select-proficiency") return;
723
+ if (choice.domain !== "language" && choice.domain !== "tool") return;
724
+ const vals = Array.isArray(selected) ? selected : typeof selected === "string" ? [selected] : [];
725
+ const pool = choice.from;
726
+ const valid = pool ? vals.filter((v) => pool.includes(v)) : vals;
727
+ const status = { id: choice.id, count: choice.count, selected: valid.length };
728
+ (choice.domain === "language" ? languages : tools).push(status);
729
+ });
730
+ return { languages, tools };
731
+ }
714
732
  function collectChosenAbilityPoints(resolved) {
715
733
  var _a, _b, _c;
716
734
  const oc = (_a = resolved.definition.origin_choices) != null ? _a : {};
@@ -1084,9 +1102,9 @@ export {
1084
1102
  collectChosenGrantedFeatures,
1085
1103
  collectResolvedFeatures,
1086
1104
  wikilinkTailSlug,
1087
- bareEntitySlug,
1088
1105
  __matchesFilterForTest,
1089
1106
  collectChosenProficiencies,
1107
+ collectLanguageToolChoiceStatus,
1090
1108
  collectChosenAbilityPoints,
1091
1109
  buildDecisionLedger
1092
1110
  };
@@ -6,8 +6,8 @@ function bareSlug(ref) {
6
6
  }
7
7
  function baseClassName(ref) {
8
8
  const bare = bareSlug(ref);
9
- const sep = bare.indexOf("_");
10
- return sep >= 0 ? bare.slice(sep + 1) : bare;
9
+ const p = bare.split("_");
10
+ return p.length >= 3 ? p.slice(2).join("_") : p[p.length - 1];
11
11
  }
12
12
 
13
13
  export {
@@ -2,10 +2,12 @@ export declare function bareSlug(ref: string): string;
2
2
  /**
3
3
  * Bare class name for profile / spell-`classes` lookups.
4
4
  *
5
- * Compendium-qualified slugs are `<compendium>_<name>` — e.g. `srd-5e_wizard`,
6
- * `srd-2024_bard` (compendium slugs are hyphen-cased, so `_` only ever separates
7
- * the compendium from the entity name). Strip that prefix to get the canonical
8
- * class key (`wizard`). Hand-written/test slugs without a `_` pass through
9
- * unchanged (`wizard` → `wizard`).
5
+ * Compendium-qualified slugs are `<compendium>_<type>_<name>` (3-part, e.g.
6
+ * `srd-2024_class_wizard`) or the legacy `<compendium>_<name>` (2-part, e.g.
7
+ * `srd-5e_wizard`); compendium/type segments are hyphen-cased, so `_` only ever
8
+ * separates the structural parts from the entity name. Strip that prefix to get
9
+ * the canonical class key (`wizard`). Name-slugs never contain `_`, so a 3-part
10
+ * slug's name is `parts.slice(2).join("_")`. Hand-written/test slugs without a
11
+ * `_` pass through unchanged (`wizard` → `wizard`).
10
12
  */
11
13
  export declare function baseClassName(ref: string): string;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  bareSlug,
3
3
  baseClassName
4
- } from "../chunk-J2RYSZMQ.js";
4
+ } from "../chunk-VTTA2ALZ.js";
5
5
  export {
6
6
  bareSlug,
7
7
  baseClassName