@optolith/entity-descriptions 0.5.1 → 0.6.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/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
4
4
 
5
+ ## [0.6.0](https://github.com/Optolith/entity-descriptions/compare/v0.5.1...v0.6.0) (2026-09-08)
6
+
7
+ ### ⚠ BREAKING CHANGES
8
+
9
+ * **deps:** upgrade to schema 0.55.0
10
+
11
+ ### build
12
+
13
+ * **deps:** upgrade to schema 0.55.0 ([0dff095](https://github.com/Optolith/entity-descriptions/commit/0dff0950d939beec0e688e792c58d6d0a2ce44d7))
14
+
15
+ ### Bug Fixes
16
+
17
+ * restricted blessings for traditions ([ca26c4c](https://github.com/Optolith/entity-descriptions/commit/ca26c4cadc77198b164967074735707ac6ec8593)), closes [#42](https://github.com/Optolith/entity-descriptions/issues/42)
5
18
  ## [0.5.1](https://github.com/Optolith/entity-descriptions/compare/v0.5.0...v0.5.1) (2026-09-08)
6
19
  ## [0.5.0](https://github.com/Optolith/entity-descriptions/compare/v0.4.26...v0.5.0) (2026-09-08)
7
20
 
@@ -47,7 +47,7 @@ export type BaseActivatableTranslation = {
47
47
  * Get a JSON representation of the rules text for a special ability.
48
48
  */
49
49
  export declare const getActivatableEntityDescription: import("../creator.js").EntityDescriptionCreator<"Advantage" | "Disadvantage" | "AdvancedCombatSpecialAbility" | "AdvancedKarmaSpecialAbility" | "AdvancedMagicalSpecialAbility" | "AdvancedSkillSpecialAbility" | "AncestorGlyph" | "ArcaneOrbEnchantment" | "AttireEnchantment" | "Beutelzauber" | "BlessedTradition" | "BowlEnchantment" | "BrawlingSpecialAbility" | "CauldronEnchantment" | "CeremonialItemSpecialAbility" | "ChronicleEnchantment" | "CombatSpecialAbility" | "CombatStyleSpecialAbility" | "CommandSpecialAbility" | "DaggerRitual" | "FamiliarSpecialAbility" | "FatePointSexSpecialAbility" | "FatePointSpecialAbility" | "FoolsHatEnchantment" | "GeneralSpecialAbility" | "Haubenzauber" | "Hauerkettenzauber" | "InstrumentEnchantment" | "KarmaSpecialAbility" | "Keulenzauber" | "Krallenkettenzauber" | "Kristallkugelzauber" | "LiturgicalStyleSpecialAbility" | "LycantropicGift" | "MagicalSign" | "MagicalSpecialAbility" | "MagicalTradition" | "MagicStyleSpecialAbility" | "OrbEnchantment" | "PactGift" | "ProtectiveWardingCircleSpecialAbility" | "RingEnchantment" | "Schweinetrommelzauber" | "Sermon" | "SexSpecialAbility" | "SickleRitual" | "SikaryanDrainSpecialAbility" | "SkillStyleSpecialAbility" | "SpellSwordEnchantment" | "StaffEnchantment" | "ToyEnchantment" | "Trinkhornzauber" | "VampiricGift" | "Vision" | "WandEnchantment" | "WeaponEnchantment", {
50
- getInstanceById: GetInstanceById<"Publication" | "Subject" | ActivatableIdentifier["kind"] | RatedIdentifier["kind"] | "TradeSecret" | "Aspect" | "Property" | "Race" | "Culture" | "State" | "Enhancement" | "PactCategory" | "PactDomain" | "SocialStatus" | "Weapon" | "Patron" | "PersonalityTrait">;
50
+ getInstanceById: GetInstanceById<"Publication" | "Subject" | ActivatableIdentifier["kind"] | RatedIdentifier["kind"] | "TradeSecret" | "Aspect" | "Property" | "Race" | "Culture" | "State" | "Enhancement" | "PactCategory" | "PactDomain" | "SocialStatus" | "Weapon" | "Patron" | "PersonalityTrait" | "Blessing">;
51
51
  getAllInstances: GetAllInstances<"Script">;
52
52
  getResolvedSelectOptionById: GetResolvedSelectOptionById;
53
53
  getAllResolvedSelectOptions: GetAllResolvedSelectOptions;
@@ -1,7 +1,7 @@
1
1
  import { isNotEmpty } from "@elyukai/utils/array/nonEmpty";
2
2
  import { count } from "@elyukai/utils/array/reductions";
3
3
  import { deepEqual } from "@elyukai/utils/equality";
4
- import { on } from "@elyukai/utils/function";
4
+ import { constant, on } from "@elyukai/utils/function";
5
5
  import { isNotNullish } from "@elyukai/utils/nullable";
6
6
  import { Reader } from "@elyukai/utils/reader";
7
7
  import { numAsc } from "@optolith/helpers/compare";
@@ -651,6 +651,37 @@ const renderFavoredSkillsSelection = (baseList, favoredSkillsSelection) => favor
651
651
  baseList,
652
652
  selection: names.join(", "),
653
653
  }));
654
+ const getRestrictedBlessingsSpecialRuleLabelKey = (restrictedBlessings) => {
655
+ if (restrictedBlessings === "All") {
656
+ return "No Blessings";
657
+ }
658
+ switch (restrictedBlessings.kind) {
659
+ case "Three":
660
+ return "Restricted Blessings";
661
+ case "Six":
662
+ return "Strongly Restricted Blessings";
663
+ default:
664
+ return assertExhaustive(restrictedBlessings);
665
+ }
666
+ };
667
+ const renderRestrictedBlessingsSpecialRule = (restrictedBlessings, nameOfPeopleWithThisTradition) => translateR(getRestrictedBlessingsSpecialRuleLabelKey(restrictedBlessings)).thenW(label => restrictedBlessings === "All"
668
+ ? translateR("{$nameOfBlessedOnes} cannot use blessings.", {
669
+ nameOfBlessedOnes: nameOfPeopleWithThisTradition,
670
+ }).map(text => ({
671
+ label,
672
+ text,
673
+ }))
674
+ : Reader.traverse(restrictedBlessings.kind === "Three"
675
+ ? restrictedBlessings.Three
676
+ : restrictedBlessings.Six, id => attributedNameR("restricted-blessings", "Blessing", id).map(name => name ?? MISSING_VALUE))
677
+ .thenW(localeSortR)
678
+ .thenW(list => translateR("{$nameOfBlessedOnes} cannot use {$blessings :list type=disjunction}.", {
679
+ nameOfBlessedOnes: nameOfPeopleWithThisTradition,
680
+ blessings: list,
681
+ }).map(text => ({
682
+ label,
683
+ text,
684
+ }))));
654
685
  const renderFavoredSkillsSpecialRule = (favoredCombatTechniques, favoredSkills, favoredSkillsSelection) => translateR("Favored Skills").thenW(label => renderFavoredCombatTechniques(favoredCombatTechniques)
655
686
  .thenW(favoredCombatTechniquesText => renderFavoredSkillsBaseList(favoredCombatTechniquesText, favoredSkills))
656
687
  .thenW(list => renderFavoredSkillsSelection(list, favoredSkillsSelection))
@@ -670,7 +701,11 @@ const renderPrimaryAttributeSpecialRule = (traditionName, nameOfPeopleWithThisTr
670
701
  }))).map(description => ({
671
702
  text: description,
672
703
  }));
673
- const renderAdditionalSpecialRules = (traditionName, nameOfPeopleWithThisTradition, favoredCombatTechniques, favoredSkills, favoredSkillsSelection, primaryAttribute) => Reader.sequence([
704
+ const renderAdditionalSpecialRules = (fixedSpecialRules, traditionName, nameOfPeopleWithThisTradition, restrictedBlessings, favoredCombatTechniques, favoredSkills, favoredSkillsSelection, primaryAttribute) => Reader.sequence([
705
+ restrictedBlessings === undefined
706
+ ? Reader.of(undefined)
707
+ : renderRestrictedBlessingsSpecialRule(restrictedBlessings, nameOfPeopleWithThisTradition),
708
+ ...fixedSpecialRules.map(rule => Reader.of(rule)),
674
709
  favoredSkills === undefined
675
710
  ? Reader.of(undefined)
676
711
  : renderFavoredSkillsSpecialRule(favoredCombatTechniques, favoredSkills, favoredSkillsSelection),
@@ -691,14 +726,15 @@ export const getActivatableEntityDescription = createEntityDescriptionCreator(({
691
726
  translateMap,
692
727
  getInstanceById,
693
728
  localeCompare: locale.compare,
729
+ localeJoin: locale.join,
694
730
  };
695
731
  const baseEntry = entry;
696
732
  const wrappedId = Case(entityName, id);
697
- const additionalSpecialRules = entityName === "BlessedTradition"
698
- ? renderAdditionalSpecialRules(translation.name, translation.nameOfBlessedOnes, entry.favored_combat_techniques, entry.favored_skills, entry.favored_skills_selection, entry.primary).run(env)
733
+ const addAdditionalSpecialRules = entityName === "BlessedTradition"
734
+ ? (baseList) => renderAdditionalSpecialRules(baseList, translation.name, translation.nameOfBlessedOnes, entry.type.kind === "Church" ? entry.type.Church.restrictedBlessings : "All", entry.favored_combat_techniques, entry.favored_skills, entry.favored_skills_selection, entry.primary).run(env)
699
735
  : entityName === "MagicalTradition"
700
- ? renderAdditionalSpecialRules(translation.name, translation.nameOfSpellcasters, undefined, undefined, undefined, entry.primary).run(env)
701
- : [];
736
+ ? (baseList) => renderAdditionalSpecialRules(baseList, translation.name, translation.nameOfSpellcasters, undefined, undefined, undefined, undefined, entry.primary).run(env)
737
+ : constant([]);
702
738
  const makeTraditionName = entityName === "BlessedTradition" || entityName === "MagicalTradition"
703
739
  ? name => translate("Tradition ({$tradition})", { tradition: name })
704
740
  : name => name;
@@ -723,8 +759,7 @@ export const getActivatableEntityDescription = createEntityDescriptionCreator(({
723
759
  body: [
724
760
  mapNullable(translation.special_rules, specialRules => ({
725
761
  type: "plain",
726
- text: specialRules
727
- .concat(additionalSpecialRules)
762
+ text: addAdditionalSpecialRules(specialRules)
728
763
  .map(specialRule => specialRule.label === undefined
729
764
  ? `- ${specialRule.text}`
730
765
  : `- *${specialRule.label}*: ${specialRule.text}`)
@@ -17,16 +17,19 @@ const getTextForBlessingTraditions = (deps, id) => {
17
17
  const blessedTraditions = deps.getAllInstances("BlessedTradition");
18
18
  const primaryTradition = blessedTraditions.find(trad => trad.content.primaryBlessing === id);
19
19
  const secondaryTraditions = blessedTraditions.filter(trad => {
20
- switch (trad.content.restricted_blessings?.kind) {
21
- case "Six":
22
- return !trad.content.restricted_blessings.Six.includes(id);
23
- case "Three":
24
- return !trad.content.restricted_blessings.Three.includes(id);
25
- case undefined:
26
- return false;
27
- default:
28
- return assertExhaustive(trad.content.restricted_blessings);
20
+ if (trad.content.type.kind === "Church") {
21
+ switch (trad.content.type.Church.restrictedBlessings?.kind) {
22
+ case "Six":
23
+ return !trad.content.type.Church.restrictedBlessings.Six.includes(id);
24
+ case "Three":
25
+ return !trad.content.type.Church.restrictedBlessings.Three.includes(id);
26
+ case undefined:
27
+ return false;
28
+ default:
29
+ return assertExhaustive(trad.content.type.Church.restrictedBlessings);
30
+ }
29
31
  }
32
+ return false;
30
33
  });
31
34
  const translateTradition = (trad) => {
32
35
  const traditionTranslation = deps.translateMap(trad.content.translations);
@@ -471,25 +471,27 @@ const renderRestrictedBlessings = (restrictedBlessings) => {
471
471
  return assertExhaustive(restrictedBlessings);
472
472
  }
473
473
  };
474
- const renderSingleBlessings = (traditions) => Reader.traverse(traditions, traditionId => getInstanceByIdR("BlessedTradition", traditionId).map(tradition => tradition
475
- ? { restrictedBlessings: tradition.restricted_blessings, type: tradition.type }
476
- : undefined)).thenW(restrictedBlessingsList => !isNotEmpty(restrictedBlessingsList)
474
+ const renderSingleBlessings = (traditions) => Reader.traverse(traditions, traditionId => getInstanceByIdR("BlessedTradition", traditionId).map(tradition => tradition?.type))
475
+ .map(traditionTypes => traditionTypes.filter(isNotNullish))
476
+ .thenW(traditionTypes => !isNotEmpty(traditionTypes)
477
477
  ? Reader.of(undefined)
478
- : restrictedBlessingsList.length === 1
478
+ : traditionTypes.length === 1
479
479
  ? (() => {
480
- const [first] = restrictedBlessingsList;
481
- const { restrictedBlessings, type } = first ?? {};
482
- return type?.kind === "Shamanistic"
483
- ? Reader.of(undefined)
484
- : restrictedBlessings === undefined
485
- ? translateR("The Twelve Blessings")
486
- : translateR("The Twelve Blessings").thenW(base => renderRestrictedBlessings(restrictedBlessings).thenW(list => translateR("except for {$list :list type=conjunction}", {
487
- list,
488
- }).map(note => base + parensIf(note))));
480
+ const [first] = traditionTypes;
481
+ if (first.kind === "Shamanistic") {
482
+ return Reader.of(undefined);
483
+ }
484
+ const { restrictedBlessings } = first.Church;
485
+ return restrictedBlessings === undefined
486
+ ? translateR("The Twelve Blessings")
487
+ : translateR("The Twelve Blessings").thenW(base => renderRestrictedBlessings(restrictedBlessings).thenW(list => translateR("except for {$list :list type=conjunction}", {
488
+ list,
489
+ }).map(note => base + parensIf(note))));
489
490
  })()
490
- : restrictedBlessingsList.every(trad => trad?.type.kind === "Shamanistic")
491
+ : traditionTypes.every(trad => trad.kind === "Shamanistic")
491
492
  ? Reader.of(undefined)
492
- : restrictedBlessingsList.some(trad => trad?.restrictedBlessings !== undefined || trad?.type.kind === "Shamanistic")
493
+ : traditionTypes.some(trad => (trad.kind === "Church" && trad.Church.restrictedBlessings !== undefined) ||
494
+ trad.kind === "Shamanistic")
493
495
  ? translateR("The Twelve Blessings").map2(translateR("depends on selected tradition"), (base, note) => base + parensIf(note))
494
496
  : translateR("The Twelve Blessings"));
495
497
  const renderBlessings = (traditions) => {