@optolith/entity-descriptions 0.13.1 → 0.13.2

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,11 @@
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.13.2](https://github.com/Optolith/entity-descriptions/compare/v0.13.1...v0.13.2) (2026-09-16)
6
+
7
+ ### Features
8
+
9
+ * add focus rule badges to activatable entries, spellworks and liturgical chants and ceremonies ([dd142b4](https://github.com/Optolith/entity-descriptions/commit/dd142b47f3cd29620d29a2561e4a8ae8ad1d2a2d)), closes [#41](https://github.com/Optolith/entity-descriptions/issues/41)
5
10
  ## [0.13.1](https://github.com/Optolith/entity-descriptions/compare/v0.13.0...v0.13.1) (2026-09-16)
6
11
 
7
12
  ### Bug Fixes
@@ -51,7 +51,7 @@ export type BaseActivatableTranslation = {
51
51
  * Get a JSON representation of the rules text for a special ability.
52
52
  */
53
53
  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", {
54
- getInstanceById: GetInstanceById<"Publication" | "Subject" | ActivatableIdentifier["kind"] | RatedIdentifier["kind"] | "TradeSecret" | "Aspect" | "Property" | "Race" | "Culture" | "State" | "Enhancement" | "PactCategory" | "PactDomain" | "SocialStatus" | "Weapon" | "Patron" | "PersonalityTrait" | "Brew" | "Blessing" | "Cantrip">;
54
+ getInstanceById: GetInstanceById<"Publication" | "Subject" | ActivatableIdentifier["kind"] | RatedIdentifier["kind"] | "TradeSecret" | "Aspect" | "Property" | "Race" | "Culture" | "State" | "Enhancement" | "PactCategory" | "PactDomain" | "SocialStatus" | "Weapon" | "Patron" | "PersonalityTrait" | "Brew" | "Blessing" | "Cantrip" | "FocusRule">;
55
55
  getAllInstances: GetAllInstances<"Script">;
56
56
  getResolvedSelectOptionById: GetResolvedSelectOptionById;
57
57
  getAllResolvedSelectOptions: GetAllResolvedSelectOptions;
@@ -18,7 +18,7 @@ import { renderAdventurePointsValue } from "./partial/adventurePointsValue.js";
18
18
  import { renderParameterMap } from "./partial/map.js";
19
19
  import { attributedName } from "./partial/markdown.js";
20
20
  import { additionFormatter } from "./partial/mathOperation.js";
21
- import { printAdvantageDisadvantagePrerequisites, printGeneralPrerequisites, } from "./partial/prerequisites/index.js";
21
+ import { getDerivedFocusRuleBadgeFromPrerequisites, printAdvantageDisadvantagePrerequisites, printGeneralPrerequisites, } from "./partial/prerequisites/index.js";
22
22
  import { printActivatableName, } from "./partial/prerequisites/single/activatable.js";
23
23
  import { renderStandaloneCostMap } from "./partial/rated/activatable/cost.js";
24
24
  import { parensIf } from "./partial/rated/activatable/parensIf.js";
@@ -826,9 +826,10 @@ export const getActivatableEntityDescription = createEntityDescriptionCreator(({
826
826
  title: addLevels(makeTraditionName(translation.name_in_library ?? translation.name + parensIf(translation.additionalName))),
827
827
  subtitle: mapNullable(baseEntry.subtype, subtype => renderAdvantageDisadvantageSubtype(subtype).run(env)) ??
828
828
  mapNullable(baseEntry.usage_type, usageType => renderCombatSpecialAbilityType(usageType).run(env)),
829
- badge: mapNullable(entityName === "CombatStyleSpecialAbility" || entityName === "AdvancedCombatSpecialAbility"
829
+ badge: mapNullable(entityName === "CombatStyleSpecialAbility" ||
830
+ entityName === "AdvancedCombatSpecialAbility"
830
831
  ? entry.type
831
- : undefined, combatType => {
832
+ : undefined, (combatType) => {
832
833
  switch (combatType.kind) {
833
834
  case "Armed":
834
835
  return { type: "armedCombat", value: translate("AC") };
@@ -837,7 +838,8 @@ export const getActivatableEntityDescription = createEntityDescriptionCreator(({
837
838
  default:
838
839
  return assertExhaustive(combatType);
839
840
  }
840
- }),
841
+ }) ??
842
+ mapNullable(baseEntry.prerequisites, getDerivedFocusRuleBadgeFromPrerequisites)?.run(env),
841
843
  className: "special-ability",
842
844
  body: [
843
845
  mapNullable(translation.special_rules, specialRules => ({
@@ -12,7 +12,7 @@ export declare const getBlessingEntityDescription: import("../creator.js").Entit
12
12
  * Get a JSON representation of the rules text for a liturgical chant.
13
13
  */
14
14
  export declare const getLiturgicalChantEntityDescription: import("../creator.js").EntityDescriptionCreator<"LiturgicalChant", {
15
- getInstanceById: GetInstanceById<"Publication" | "Attribute" | "SkillModificationLevel" | "TargetCategory" | "Aspect" | "BlessedTradition" | "DerivedCharacteristic" | RatedIdentifier["kind"] | "Enhancement">;
15
+ getInstanceById: GetInstanceById<"Publication" | "Attribute" | "SkillModificationLevel" | "TargetCategory" | "Aspect" | "BlessedTradition" | "DerivedCharacteristic" | RatedIdentifier["kind"] | "Enhancement" | "FocusRule">;
16
16
  getChildInstancesForInstanceId: GetAllChildInstancesForParent<"Enhancement">;
17
17
  idMap: IdMap;
18
18
  }, import("../index.js").EntityDescription>;
@@ -20,7 +20,7 @@ export declare const getLiturgicalChantEntityDescription: import("../creator.js"
20
20
  * Get a JSON representation of the rules text for a ceremony.
21
21
  */
22
22
  export declare const getCeremonyEntityDescription: import("../creator.js").EntityDescriptionCreator<"Ceremony", {
23
- getInstanceById: GetInstanceById<"Publication" | "Attribute" | "SkillModificationLevel" | "TargetCategory" | "Aspect" | "BlessedTradition" | "DerivedCharacteristic" | RatedIdentifier["kind"] | "Enhancement">;
23
+ getInstanceById: GetInstanceById<"Publication" | "Attribute" | "SkillModificationLevel" | "TargetCategory" | "Aspect" | "BlessedTradition" | "DerivedCharacteristic" | RatedIdentifier["kind"] | "Enhancement" | "FocusRule">;
24
24
  getChildInstancesForInstanceId: GetAllChildInstancesForParent<"Enhancement">;
25
25
  idMap: IdMap;
26
26
  }, import("../index.js").EntityDescription>;
@@ -1,9 +1,11 @@
1
+ import { mapNullable } from "@elyukai/utils/nullable";
1
2
  import { isNotNullish } from "@optolith/helpers/nullable";
2
3
  import { assertExhaustive } from "@optolith/helpers/typeSafety";
3
4
  import { Case } from "tsondb/schema/gen";
4
5
  import { createEntityDescriptionCreator } from "../creator.js";
5
6
  import { renderEnhancements } from "./partial/enhancements.js";
6
7
  import { attributedInstance, attributedName } from "./partial/markdown.js";
8
+ import { getDerivedFocusRuleBadgeFromPrerequisites } from "./partial/prerequisites/index.js";
7
9
  import { renderOneTimeDuration } from "./partial/rated/activatable/duration.js";
8
10
  import { renderEffect } from "./partial/rated/activatable/effect.js";
9
11
  import { combineGeneratedTextWithStaticTranslation, renderFastPerformanceParameters, renderSlowPerformanceParameters, } from "./partial/rated/activatable/index.js";
@@ -173,6 +175,7 @@ export const getLiturgicalChantEntityDescription = createEntityDescriptionCreato
173
175
  return {
174
176
  title: translation.name,
175
177
  className: "liturgical-chant",
178
+ badge: mapNullable(entry.prerequisites, getDerivedFocusRuleBadgeFromPrerequisites)?.run(env),
176
179
  body: [
177
180
  {
178
181
  type: "definitionList",
@@ -237,6 +240,7 @@ export const getCeremonyEntityDescription = createEntityDescriptionCreator(({ ge
237
240
  return {
238
241
  title: translation.name,
239
242
  className: "ceremony",
243
+ badge: mapNullable(entry.prerequisites, getDerivedFocusRuleBadgeFromPrerequisites)?.run(env),
240
244
  body: [
241
245
  {
242
246
  type: "definitionList",
@@ -1,6 +1,7 @@
1
1
  import { Reader } from "@elyukai/utils/reader";
2
2
  import type { ActivatableIdentifier, AdvantageDisadvantagePrerequisites, AnimistPowerPrerequisites, ArcaneTraditionPrerequisites, DerivedCharacteristicPrerequisites, EnhancementPrerequisites, GeneralPrerequisites, GeodeRitualPrerequisites, InfluencePrerequisites, LanguagePrerequisites, LiturgyPrerequisites, PersonalityTraitPrerequisites, PlainGeneralPrerequisites, ProfessionPrerequisites, ProfessionVariantPrerequisites, PublicationPrerequisites, SpellworkPrerequisites } from "@optolith/database-schema/gen";
3
- import type { StdEnv } from "../../../env.js";
3
+ import type { StdEnv, StdReader } from "../../../env.js";
4
+ import type { RawEntityDescriptionBadge } from "../../../index.js";
4
5
  import { printAdvantageDisadvantagePrerequisiteGroup, printAnimistPowerPrerequisiteGroup, printArcaneTraditionPrerequisiteGroup, printDerivedCharacteristicPrerequisiteGroup, printEnhancementPrerequisiteGroup, printGeneralPrerequisiteGroup, printGeodeRitualPrerequisiteGroup, printInfluencePrerequisiteGroup, printLanguagePrerequisiteGroup, printLiturgyPrerequisiteGroup, printPersonalityTraitPrerequisiteGroup, printProfessionPrerequisiteGroup, printPublicationPrerequisiteGroup, printSpellworkPrerequisiteGroup } from "./prerequisiteGroups.js";
5
6
  type DeriveEnvF<T extends (...args: any[]) => any> = ReturnType<T> extends Reader<infer E, unknown> ? E : never;
6
7
  /**
@@ -70,4 +71,8 @@ export declare const printGeodeRitualPrerequisites: (value: GeodeRitualPrerequis
70
71
  * Print enhancement prerequisites as a string.
71
72
  */
72
73
  export declare const printEnhancementPrerequisites: (value: EnhancementPrerequisites) => Reader<DeriveEnvF<typeof printEnhancementPrerequisiteGroup> & StdEnv<"lj" | "lc">, string>;
74
+ /**
75
+ * Get the focus rule badge from prerequisites.
76
+ */
77
+ export declare const getDerivedFocusRuleBadgeFromPrerequisites: (prerequisites: AdvantageDisadvantagePrerequisites | GeneralPrerequisites | PlainGeneralPrerequisites) => StdReader<RawEntityDescriptionBadge | undefined, "ibi", "FocusRule">;
73
78
  export {};
@@ -7,7 +7,7 @@ import { isNotNullish } from "@optolith/helpers/nullable";
7
7
  import { romanize } from "@optolith/helpers/roman";
8
8
  import { assertExhaustive } from "@optolith/helpers/typeSafety";
9
9
  import { renderActivatableNameComponents, renderActivatableNameComponentsCombinedIfPossible, } from "../activatableNameChunks.js";
10
- import { localeJoinR, translateR } from "../reader.js";
10
+ import { getInstanceByIdR, localeJoinR, translateR } from "../reader.js";
11
11
  import { MISSING_VALUE } from "../unknown.js";
12
12
  import { printDisplayOption } from "./displayOption.js";
13
13
  import { hasPartValueObject, joinPrerequisiteParts } from "./part.js";
@@ -230,3 +230,26 @@ export const printGeodeRitualPrerequisites = (value) => printPlainPrerequisites(
230
230
  * Print enhancement prerequisites as a string.
231
231
  */
232
232
  export const printEnhancementPrerequisites = (value) => printPlainPrerequisites(printEnhancementPrerequisiteGroup, value);
233
+ const getDerivedFocusRuleBadgeFromPrerequisite = (prerequisite) => prerequisite.kind === "Single" &&
234
+ prerequisite.Single.kind === "Rule" &&
235
+ prerequisite.Single.Rule.id.kind === "FocusRule"
236
+ ? getInstanceByIdR(prerequisite.Single.Rule.id).map(focusRule => focusRule?.level)
237
+ : Reader.of(undefined);
238
+ /**
239
+ * Get the focus rule badge from prerequisites.
240
+ */
241
+ export const getDerivedFocusRuleBadgeFromPrerequisites = (prerequisites) => Reader.asks(env => {
242
+ for (const element of prerequisites) {
243
+ const actualPrerequisite = "level" in element ? (element.level === 1 ? element.prerequisite : undefined) : element;
244
+ if (actualPrerequisite !== undefined) {
245
+ const badge = getDerivedFocusRuleBadgeFromPrerequisite(actualPrerequisite).run(env);
246
+ if (badge !== undefined) {
247
+ return {
248
+ type: "level",
249
+ value: romanize(badge),
250
+ };
251
+ }
252
+ }
253
+ }
254
+ return undefined;
255
+ });
@@ -12,7 +12,7 @@ export declare const getCantripEntityDescription: import("../creator.js").Entity
12
12
  * Get a JSON representation of the rules text for a skill.
13
13
  */
14
14
  export declare const getSpellEntityDescription: import("../creator.js").EntityDescriptionCreator<"Spell", {
15
- getInstanceById: GetInstanceById<"Publication" | "Attribute" | "SkillModificationLevel" | "TargetCategory" | "Property" | "MagicalTradition" | "DerivedCharacteristic" | RatedIdentifier["kind"] | "Enhancement">;
15
+ getInstanceById: GetInstanceById<"Publication" | "Attribute" | "SkillModificationLevel" | "TargetCategory" | "Property" | "MagicalTradition" | "DerivedCharacteristic" | RatedIdentifier["kind"] | "Enhancement" | "FocusRule">;
16
16
  getChildInstancesForInstanceId: GetAllChildInstancesForParent<"Enhancement">;
17
17
  idMap: IdMap;
18
18
  }, import("../index.js").EntityDescription>;
@@ -20,7 +20,7 @@ export declare const getSpellEntityDescription: import("../creator.js").EntityDe
20
20
  * Get a JSON representation of the rules text for a ritual.
21
21
  */
22
22
  export declare const getRitualEntityDescription: import("../creator.js").EntityDescriptionCreator<"Ritual", {
23
- getInstanceById: GetInstanceById<"Publication" | "Attribute" | "SkillModificationLevel" | "TargetCategory" | "Property" | "MagicalTradition" | "DerivedCharacteristic" | RatedIdentifier["kind"] | "Enhancement">;
23
+ getInstanceById: GetInstanceById<"Publication" | "Attribute" | "SkillModificationLevel" | "TargetCategory" | "Property" | "MagicalTradition" | "DerivedCharacteristic" | RatedIdentifier["kind"] | "Enhancement" | "FocusRule">;
24
24
  getChildInstancesForInstanceId: GetAllChildInstancesForParent<"Enhancement">;
25
25
  idMap: IdMap;
26
26
  }, import("../index.js").EntityDescription>;
@@ -12,7 +12,7 @@ import { createEntityDescriptionCreator } from "../creator.js";
12
12
  import { renderAnimalTypesSection } from "./partial/animalTypes.js";
13
13
  import { renderEnhancements } from "./partial/enhancements.js";
14
14
  import { attributedCustomName, attributedName } from "./partial/markdown.js";
15
- import { printAnimistPowerPrerequisites, printGeodeRitualPrerequisites, } from "./partial/prerequisites/index.js";
15
+ import { getDerivedFocusRuleBadgeFromPrerequisites, printAnimistPowerPrerequisites, printGeodeRitualPrerequisites, } from "./partial/prerequisites/index.js";
16
16
  import { renderCastingTime, renderFastSkillNonModifiableCastingTime, renderSlowSkillNonModifiableCastingTime, } from "./partial/rated/activatable/castingTime.js";
17
17
  import { renderMagicalActionCost, renderModifiableOneTimeCost, renderNonModifiableOneTimeCost, renderNonModifiableSustainedCost, renderStandaloneCostMap, } from "./partial/rated/activatable/cost.js";
18
18
  import { renderExpressionBasedDuration, renderMusicDuration, renderOneTimeDuration, renderSustainedDuration, } from "./partial/rated/activatable/duration.js";
@@ -162,6 +162,7 @@ export const getSpellEntityDescription = createEntityDescriptionCreator(({ getIn
162
162
  return {
163
163
  title: translation.name,
164
164
  className: "spell",
165
+ badge: mapNullable(entry.prerequisites, getDerivedFocusRuleBadgeFromPrerequisites)?.run(env),
165
166
  body: [
166
167
  {
167
168
  type: "definitionList",
@@ -221,6 +222,7 @@ export const getRitualEntityDescription = createEntityDescriptionCreator(({ getI
221
222
  return {
222
223
  title: translation.name,
223
224
  className: "ritual",
225
+ badge: mapNullable(entry.prerequisites, getDerivedFocusRuleBadgeFromPrerequisites)?.run(env),
224
226
  body: [
225
227
  {
226
228
  type: "definitionList",