@optolith/entity-descriptions 0.11.0 → 0.11.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
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.11.1](https://github.com/Optolith/entity-descriptions/compare/v0.11.0...v0.11.1) (2026-09-13)
6
+
7
+ ### Features
8
+
9
+ * attributed target category ([8411c00](https://github.com/Optolith/entity-descriptions/commit/8411c00eb0ac8df5f4335a41db951578cbaffe26))
10
+
11
+ ### Bug Fixes
12
+
13
+ * advanced special ability sorting ([fd8ec9c](https://github.com/Optolith/entity-descriptions/commit/fd8ec9cf41625bc67f243850045f2bdf358bae5b)), closes [#50](https://github.com/Optolith/entity-descriptions/issues/50)
14
+ * advanced special ability sorting (other parts) ([75ccc67](https://github.com/Optolith/entity-descriptions/commit/75ccc671ad4a60116067a0b5df143d0b1c1cc3f9))
15
+ * display additional name ([460385e](https://github.com/Optolith/entity-descriptions/commit/460385ee19ea7be58766fb46859256514c75208f))
5
16
  ## [0.11.0](https://github.com/Optolith/entity-descriptions/compare/v0.10.0...v0.11.0) (2026-09-13)
6
17
 
7
18
  ### ⚠ BREAKING CHANGES
@@ -34,6 +34,7 @@ export type BaseActivatable = {
34
34
  */
35
35
  export type BaseActivatableTranslation = {
36
36
  name: string;
37
+ additionalName?: string;
37
38
  name_in_library?: string;
38
39
  rules?: string;
39
40
  range?: string;
@@ -48,7 +49,7 @@ export type BaseActivatableTranslation = {
48
49
  /**
49
50
  * Get a JSON representation of the rules text for a special ability.
50
51
  */
51
- export declare const getActivatableEntityDescription: import("../creator.js").EntityDescriptionCreator<"AdvancedCombatSpecialAbility" | "AdvancedKarmaSpecialAbility" | "AdvancedMagicalSpecialAbility" | "AdvancedSkillSpecialAbility" | "Advantage" | "AncestorGlyph" | "ArcaneOrbEnchantment" | "AttireEnchantment" | "Beutelzauber" | "BlessedTradition" | "BowlEnchantment" | "BrawlingSpecialAbility" | "CauldronEnchantment" | "CeremonialItemSpecialAbility" | "ChronicleEnchantment" | "CombatSpecialAbility" | "CombatStyleSpecialAbility" | "CommandSpecialAbility" | "DaggerRitual" | "Disadvantage" | "FamiliarSpecialAbility" | "FatePointSexSpecialAbility" | "FatePointSpecialAbility" | "FoolsHatEnchantment" | "GeneralSpecialAbility" | "Haubenzauber" | "Hauerkettenzauber" | "InstrumentEnchantment" | "KarmaSpecialAbility" | "Keulenzauber" | "Krallenkettenzauber" | "Kristallkugelzauber" | "LiturgicalStyleSpecialAbility" | "LycantropicGift" | "MagicStyleSpecialAbility" | "MagicalSign" | "MagicalSpecialAbility" | "MagicalTradition" | "OrbEnchantment" | "PactGift" | "ProtectiveWardingCircleSpecialAbility" | "RingEnchantment" | "Schweinetrommelzauber" | "Sermon" | "SexSpecialAbility" | "SickleRitual" | "SikaryanDrainSpecialAbility" | "SkillStyleSpecialAbility" | "SpellSwordEnchantment" | "StaffEnchantment" | "ToyEnchantment" | "Trinkhornzauber" | "VampiricGift" | "Vision" | "WandEnchantment" | "WeaponEnchantment", {
52
+ 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", {
52
53
  getInstanceById: GetInstanceById<"Publication" | "Subject" | ActivatableIdentifier["kind"] | RatedIdentifier["kind"] | "TradeSecret" | "Aspect" | "Property" | "Race" | "Culture" | "State" | "Enhancement" | "PactCategory" | "PactDomain" | "SocialStatus" | "Weapon" | "Patron" | "PersonalityTrait" | "Brew" | "Blessing" | "Cantrip">;
53
54
  getAllInstances: GetAllInstances<"Script">;
54
55
  getResolvedSelectOptionById: GetResolvedSelectOptionById;
@@ -4,6 +4,7 @@ import { count } from "@elyukai/utils/array/reductions";
4
4
  import { deepEqual } from "@elyukai/utils/equality";
5
5
  import { constant, on } from "@elyukai/utils/function";
6
6
  import { isNotNullish } from "@elyukai/utils/nullable";
7
+ import { reduceCompare } from "@elyukai/utils/ordering";
7
8
  import { Reader } from "@elyukai/utils/reader";
8
9
  import { numAsc } from "@optolith/helpers/compare";
9
10
  import { sign } from "@optolith/helpers/math";
@@ -546,7 +547,7 @@ const renderAdvancedSpecialAbilityName = (translate, translateMap, getInstanceBy
546
547
  const normalizedId = normalizeId(entityName, id);
547
548
  return `^[${mapNullable(printActivatableName(getInstanceById, translate, normalizedId, option, undefined, getResolvedSelectOptionById, false), name => renderActivatableNameComponents(translateMap, name, false)) ?? MISSING_VALUE}](entity: "${normalizedId.kind}", instance: "${fromUniformCase(normalizedId)}", style: "normal")`;
548
549
  };
549
- const renderAdvancedValue = (translate, translateMap, localeJoin, getInstanceById, getResolvedSelectOptionById, entityName, advanced) => {
550
+ const renderAdvancedValue = (translate, translateMap, localeJoin, localeCompare, getInstanceById, getResolvedSelectOptionById, entityName, advanced) => {
550
551
  if (advanced.every(entry => entry.kind === "OneOf") &&
551
552
  isNotEmpty(advanced) &&
552
553
  advanced.every(entry => entry.OneOf.options.length === 3)) {
@@ -558,7 +559,9 @@ const renderAdvancedValue = (translate, translateMap, localeJoin, getInstanceByI
558
559
  if (uniqueOptions.length === advanced.length) {
559
560
  const [first, second] = optionsInAllEntries.map(option => renderAdvancedSpecialAbilityName(translate, translateMap, getInstanceById, getResolvedSelectOptionById, entityName, option));
560
561
  return [
561
- ...uniqueOptions.map(option => renderAdvancedSpecialAbilityName(translate, translateMap, getInstanceById, getResolvedSelectOptionById, entityName, option)),
562
+ ...uniqueOptions
563
+ .map(option => renderAdvancedSpecialAbilityName(translate, translateMap, getInstanceById, getResolvedSelectOptionById, entityName, option))
564
+ .toSorted(localeCompare),
562
565
  translate("one or two of these special abilities can alternatively be replaced by advanced special abilities {$first} and/or {$second}", { first, second }),
563
566
  ].join(", ");
564
567
  }
@@ -568,25 +571,38 @@ const renderAdvancedValue = (translate, translateMap, localeJoin, getInstanceByI
568
571
  entry.DeriveFromExternalOption.display_option === undefined);
569
572
  const anyEntriesToGenerate = count(advanced, entry => entry.kind === "Any");
570
573
  const arr = [
571
- ...advanced.map((entry) => {
574
+ ...advanced
575
+ .map((entry) => {
572
576
  switch (entry.kind) {
573
577
  case "General":
574
- return renderAdvancedSpecialAbilityName(translate, translateMap, getInstanceById, getResolvedSelectOptionById, entityName, entry.General);
578
+ return [
579
+ "general",
580
+ renderAdvancedSpecialAbilityName(translate, translateMap, getInstanceById, getResolvedSelectOptionById, entityName, entry.General),
581
+ ];
575
582
  case "RestrictOptions":
576
- return renderAdvancedSpecialAbilityName(translate, translateMap, getInstanceById, getResolvedSelectOptionById, entityName, entry.RestrictOptions.id, entry.RestrictOptions.option);
583
+ return [
584
+ "general",
585
+ renderAdvancedSpecialAbilityName(translate, translateMap, getInstanceById, getResolvedSelectOptionById, entityName, entry.RestrictOptions.id, entry.RestrictOptions.option),
586
+ ];
577
587
  case "OneOf": {
578
588
  if (entry.OneOf.display_option !== undefined) {
579
589
  switch (entry.OneOf.display_option.kind) {
580
590
  case "Hide":
581
591
  return undefined;
582
592
  case "ReplaceWith":
583
- return (translateMap(entry.OneOf.display_option.ReplaceWith.translations)?.replacement ??
584
- MISSING_VALUE);
593
+ return [
594
+ "special",
595
+ translateMap(entry.OneOf.display_option.ReplaceWith.translations)
596
+ ?.replacement ?? MISSING_VALUE,
597
+ ];
585
598
  default:
586
599
  return assertExhaustive(entry.OneOf.display_option);
587
600
  }
588
601
  }
589
- return localeJoin(entry.OneOf.options.map(option => renderAdvancedSpecialAbilityName(translate, translateMap, getInstanceById, getResolvedSelectOptionById, entityName, option)), "disjunction");
602
+ return [
603
+ "general",
604
+ localeJoin(entry.OneOf.options.map(option => renderAdvancedSpecialAbilityName(translate, translateMap, getInstanceById, getResolvedSelectOptionById, entityName, option)), "disjunction"),
605
+ ];
590
606
  }
591
607
  case "DeriveFromExternalOption":
592
608
  if (entry.DeriveFromExternalOption.display_option !== undefined) {
@@ -594,7 +610,10 @@ const renderAdvancedValue = (translate, translateMap, localeJoin, getInstanceByI
594
610
  case "Hide":
595
611
  return undefined;
596
612
  case "ReplaceWith":
597
- return (translateMap(entry.DeriveFromExternalOption.display_option.ReplaceWith.translations)?.replacement ?? MISSING_VALUE);
613
+ return [
614
+ "special",
615
+ translateMap(entry.DeriveFromExternalOption.display_option.ReplaceWith.translations)?.replacement ?? MISSING_VALUE,
616
+ ];
598
617
  default:
599
618
  return assertExhaustive(entry.DeriveFromExternalOption.display_option);
600
619
  }
@@ -605,7 +624,10 @@ const renderAdvancedValue = (translate, translateMap, localeJoin, getInstanceByI
605
624
  default:
606
625
  return assertExhaustive(entry);
607
626
  }
608
- }),
627
+ })
628
+ .filter((item) => isNotNullish(item) && item[1] !== undefined)
629
+ .toSorted(reduceCompare(on(([type]) => type, localeCompare), on(([, value]) => value, localeCompare)))
630
+ .map(value => value[1]),
609
631
  derivedFromExternalOptionEntriesToGenerate === 0
610
632
  ? undefined
611
633
  : translate(".input {$count :number} {{{$count} more by primary patron}}", {
@@ -782,8 +804,7 @@ export const getActivatableEntityDescription = createEntityDescriptionCreator(({
782
804
  ? name => translate("Tradition ({$tradition})", { tradition: name })
783
805
  : name => name;
784
806
  return {
785
- title: makeTraditionName(translation.name_in_library ?? translation.name) +
786
- (baseEntry.levels !== undefined ? ` I–${romanize(baseEntry.levels)}` : ""),
807
+ title: makeTraditionName(translation.name_in_library ?? translation.name + parensIf(translation.additionalName)) + (baseEntry.levels !== undefined ? ` I–${romanize(baseEntry.levels)}` : ""),
787
808
  subtitle: mapNullable(baseEntry.subtype, subtype => renderAdvantageDisadvantageSubtype(subtype).run(env)) ??
788
809
  mapNullable(baseEntry.usage_type, usageType => renderCombatSpecialAbilityType(usageType).run(env)),
789
810
  badge: mapNullable(entityName === "CombatStyleSpecialAbility" || entityName === "AdvancedCombatSpecialAbility"
@@ -839,7 +860,7 @@ export const getActivatableEntityDescription = createEntityDescriptionCreator(({
839
860
  mapNullable(baseEntry.advanced, advanced => isStyleSpecialAbilityKind(entityName)
840
861
  ? {
841
862
  label: renderAdvancedLabel(translate, entityName),
842
- value: renderAdvancedValue(translate, translateMap, locale.join, getInstanceById, getResolvedSelectOptionById, entityName, advanced),
863
+ value: renderAdvancedValue(translate, translateMap, locale.join, locale.compare, getInstanceById, getResolvedSelectOptionById, entityName, advanced),
843
864
  }
844
865
  : undefined),
845
866
  mapNullable(baseEntry.penalty, penalty => ({
@@ -3,7 +3,7 @@ import type { IdMap } from "../index.js";
3
3
  /**
4
4
  * Get a JSON representation of the rules text for an optional rule.
5
5
  */
6
- export declare const getDiseaseEntityDescription: import("../creator.js").EntityDescriptionCreator<"AnimalDisease" | "Disease", {
6
+ export declare const getDiseaseEntityDescription: import("../creator.js").EntityDescriptionCreator<"Disease" | "AnimalDisease", {
7
7
  getInstanceById: GetInstanceById<"Publication" | "AnimalType" | "DerivedCharacteristic">;
8
8
  idMap: IdMap;
9
9
  }, import("../index.js").EntityDescription>;
@@ -19,7 +19,7 @@ export declare const renderRangedWeapon: <Damage>(translate: Translate, translat
19
19
  /**
20
20
  * Get a JSON representation of the rules text for equipment.
21
21
  */
22
- export declare const getEquipmentEntityDescription: import("../creator.js").EntityDescriptionCreator<"Ammunition" | "Animal" | "AnimalCare" | "Armor" | "BandageOrRemedy" | "Book" | "CeremonialItem" | "Clothes" | "ClothingPackage" | "Container" | "EquipmentOfBlessedOnes" | "GemOrPreciousStone" | "IlluminationLightSource" | "IlluminationRefillOrSupply" | "Jewelry" | "Laboratory" | "Liebesspielzeug" | "LuxuryGood" | "MagicalArtifact" | "MusicalInstrument" | "Newspaper" | "OrienteeringAid" | "RopeOrChain" | "Stationery" | "ThievesTool" | "ToolOfTheTrade" | "TravelGearOrTool" | "Vehicle" | "Weapon" | "WeaponAccessory" | "WorkingSupernaturalCreature", {
22
+ export declare const getEquipmentEntityDescription: import("../creator.js").EntityDescriptionCreator<"Weapon" | "Ammunition" | "Armor" | "Animal" | "AnimalCare" | "BandageOrRemedy" | "Book" | "CeremonialItem" | "Clothes" | "ClothingPackage" | "Container" | "EquipmentOfBlessedOnes" | "GemOrPreciousStone" | "IlluminationLightSource" | "IlluminationRefillOrSupply" | "Jewelry" | "Laboratory" | "Liebesspielzeug" | "LuxuryGood" | "MagicalArtifact" | "MusicalInstrument" | "Newspaper" | "OrienteeringAid" | "RopeOrChain" | "Stationery" | "ThievesTool" | "ToolOfTheTrade" | "TravelGearOrTool" | "Vehicle" | "WeaponAccessory" | "WorkingSupernaturalCreature", {
23
23
  getInstanceById: GetInstanceById<"Publication" | "Attribute" | "Reach" | "SocialStatus" | "CloseCombatTechnique" | "RangedCombatTechnique" | "MagicalTradition" | "BlessedTradition" | "DerivedCharacteristic" | AmmunitionishIdentifier["kind"] | "Race" | "Culture" | "Profession" | "Skill">;
24
24
  idMap: IdMap;
25
25
  }, import("../index.js").EntityDescription>;
@@ -42,7 +42,7 @@ export declare const renderIndefiniteCost: (value: IndefiniteCost, shouldAppendN
42
42
  /**
43
43
  * Returns the text for a standalone one-time cost map of an activatable skill.
44
44
  */
45
- export declare const renderStandaloneCostMap: (value: StandaloneCostMap) => Reader<StdEnv<"rts" | "t" | "tm"> & {
45
+ export declare const renderStandaloneCostMap: (value: StandaloneCostMap) => Reader<StdEnv<"tm" | "t" | "rts"> & {
46
46
  translate: import("../../../../helpers/translate.js").Translate;
47
47
  energyUnit: import("../../units/energy.js").EnergyUnit;
48
48
  }, string>;
@@ -63,7 +63,7 @@ export declare const renderNonModifiableSustainedCost: (value: NonModifiableSust
63
63
  responsiveTextSize: import("../../responsiveText.js").ResponsiveTextSize;
64
64
  } & {
65
65
  translate: import("../../../../helpers/translate.js").Translate;
66
- } & StdEnv<"f" | "rts" | "t" | "tm"> & StdEnv<"eu" | "rts" | "t" | "tm", never, never, never>, string>;
66
+ } & StdEnv<"f" | "tm" | "t" | "rts"> & StdEnv<"tm" | "t" | "rts" | "eu", never, never, never>, string>;
67
67
  /**
68
68
  * Returns the text for the cost of a sustained activatable skill.
69
69
  */
@@ -1,12 +1,9 @@
1
1
  import { Reader } from "@elyukai/utils/reader";
2
- import { mapNullable } from "@optolith/helpers/nullable";
3
2
  import { assertExhaustive } from "@optolith/helpers/typeSafety";
4
- import { getInstanceByIdFnR, translateMapR, translateR } from "../../reader.js";
3
+ import { attributedNameR, translateMapR, translateR } from "../../reader.js";
5
4
  import { MISSING_VALUE } from "../../unknown.js";
6
5
  import { appendInParensIfNotEmpty } from "./parensIf.js";
7
- const renderPredefined = (targetCategoryId) => getInstanceByIdFnR()
8
- .thenW(getInstanceById => mapNullable(getInstanceById("TargetCategory", targetCategoryId), targetCategory => translateMapR(targetCategory.translations).map(translation => translation?.name)) ?? Reader.of(undefined))
9
- .map(translation => translation ?? MISSING_VALUE);
6
+ const renderPredefined = (targetCategoryId) => attributedNameR("target-category", "TargetCategory", targetCategoryId).map(name => name ?? MISSING_VALUE);
10
7
  const getTargetCategoryTranslationByType = (id) => {
11
8
  switch (id.kind) {
12
9
  case "Self":
@@ -316,5 +316,5 @@ export declare const modifyBySpeedR: Reader<{
316
316
  /**
317
317
  * Returns the default values wrapped in a reader if the value is nullish, otherwise applies the given function to the value and returns the result.
318
318
  */
319
- export declare const mapNullableR: <T, U, R>(defaultValue: U, fn: (value: NonNullable<T>) => Reader<U, R>) => (value: T) => Reader<unknown, U> | Reader<U, R>;
319
+ export declare const mapNullableR: <T, U, R>(defaultValue: U, fn: (value: NonNullable<T>) => Reader<U, R>) => (value: T) => Reader<U, R> | Reader<unknown, U>;
320
320
  export {};
@@ -40,4 +40,4 @@ export declare const replaceTextIfNeeded: (translations: LocaleMap<{
40
40
  */
41
41
  export declare const appendNoteIfNeeded: (translations: LocaleMap<{
42
42
  note?: ResponsiveTextOptional;
43
- }> | undefined, valueToAppendTo: string) => Reader<StdEnv<"rts" | "tm">, string>;
43
+ }> | undefined, valueToAppendTo: string) => Reader<StdEnv<"tm" | "rts">, string>;