@alpaca-software/40kdc-data 0.5.5 → 0.5.12
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 +10 -0
- package/dist/codegen-data.js +2 -0
- package/dist/codegen-data.js.map +1 -1
- package/dist/commands/import.d.ts.map +1 -1
- package/dist/commands/import.js +3 -1
- package/dist/commands/import.js.map +1 -1
- package/dist/compare.d.ts +167 -0
- package/dist/compare.d.ts.map +1 -0
- package/dist/compare.js +302 -0
- package/dist/compare.js.map +1 -0
- package/dist/convert-faction.d.ts.map +1 -1
- package/dist/convert-faction.js +22 -2
- package/dist/convert-faction.js.map +1 -1
- package/dist/cruncher/buffs.d.ts +13 -0
- package/dist/cruncher/buffs.d.ts.map +1 -1
- package/dist/cruncher/buffs.js +7 -0
- package/dist/cruncher/buffs.js.map +1 -1
- package/dist/cruncher/engine.d.ts.map +1 -1
- package/dist/cruncher/engine.js +14 -10
- package/dist/cruncher/engine.js.map +1 -1
- package/dist/data/bundle.generated.js +1 -1
- package/dist/data/bundle.generated.js.map +1 -1
- package/dist/data/dataset.d.ts +3 -1
- package/dist/data/dataset.d.ts.map +1 -1
- package/dist/data/dataset.js +4 -0
- package/dist/data/dataset.js.map +1 -1
- package/dist/data/entities.d.ts +22 -0
- package/dist/data/entities.d.ts.map +1 -1
- package/dist/data/entities.js +45 -1
- package/dist/data/entities.js.map +1 -1
- package/dist/data/index.d.ts +4 -0
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/index.js +4 -0
- package/dist/data/index.js.map +1 -1
- package/dist/data/types.d.ts +5 -1
- package/dist/data/types.d.ts.map +1 -1
- package/dist/data/types.js +2 -0
- package/dist/data/types.js.map +1 -1
- package/dist/export/newrecruit-json.d.ts.map +1 -1
- package/dist/export/newrecruit-json.js +4 -3
- package/dist/export/newrecruit-json.js.map +1 -1
- package/dist/export/newrecruit-simple.js +2 -2
- package/dist/export/newrecruit-simple.js.map +1 -1
- package/dist/export/newrecruit-wtc.d.ts.map +1 -1
- package/dist/export/newrecruit-wtc.js +3 -1
- package/dist/export/newrecruit-wtc.js.map +1 -1
- package/dist/export/rosterizer.d.ts.map +1 -1
- package/dist/export/rosterizer.js +6 -7
- package/dist/export/rosterizer.js.map +1 -1
- package/dist/extract-faction-pack.d.ts +56 -0
- package/dist/extract-faction-pack.d.ts.map +1 -0
- package/dist/extract-faction-pack.js +374 -0
- package/dist/extract-faction-pack.js.map +1 -0
- package/dist/gen-conformance.js +37 -4
- package/dist/gen-conformance.js.map +1 -1
- package/dist/generated.d.ts +66 -2
- package/dist/generated.d.ts.map +1 -1
- package/dist/generated.js.map +1 -1
- package/dist/import/gw.js +1 -1
- package/dist/import/gw.js.map +1 -1
- package/dist/import/import-roster.d.ts.map +1 -1
- package/dist/import/import-roster.js +5 -0
- package/dist/import/import-roster.js.map +1 -1
- package/dist/import/index.d.ts +1 -0
- package/dist/import/index.d.ts.map +1 -1
- package/dist/import/index.js +1 -0
- package/dist/import/index.js.map +1 -1
- package/dist/import/listforge-text.js +1 -1
- package/dist/import/listforge-text.js.map +1 -1
- package/dist/import/listforge.d.ts.map +1 -1
- package/dist/import/listforge.js +18 -3
- package/dist/import/listforge.js.map +1 -1
- package/dist/import/newrecruit-json.d.ts.map +1 -1
- package/dist/import/newrecruit-json.js +21 -3
- package/dist/import/newrecruit-json.js.map +1 -1
- package/dist/import/newrecruit-simple.js +1 -1
- package/dist/import/newrecruit-simple.js.map +1 -1
- package/dist/import/newrecruit-wtc.js +1 -1
- package/dist/import/newrecruit-wtc.js.map +1 -1
- package/dist/import/resolve.d.ts.map +1 -1
- package/dist/import/resolve.js +46 -18
- package/dist/import/resolve.js.map +1 -1
- package/dist/import/roster-json.d.ts +32 -0
- package/dist/import/roster-json.d.ts.map +1 -0
- package/dist/import/roster-json.js +92 -0
- package/dist/import/roster-json.js.map +1 -0
- package/dist/import/rosterizer.js +3 -3
- package/dist/import/rosterizer.js.map +1 -1
- package/dist/import/types.d.ts +16 -5
- package/dist/import/types.d.ts.map +1 -1
- package/dist/import/types.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/migrate-keystone-twins.d.ts +23 -0
- package/dist/migrate-keystone-twins.d.ts.map +1 -0
- package/dist/migrate-keystone-twins.js +184 -0
- package/dist/migrate-keystone-twins.js.map +1 -0
- package/dist/rube-goldberg.js +1 -1
- package/dist/rube-goldberg.js.map +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +94 -1
- package/dist/runner.js.map +1 -1
- package/dist/scope.d.ts +31 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +34 -0
- package/dist/scope.js.map +1 -0
- package/dist/translate/effect.d.ts +17 -3
- package/dist/translate/effect.d.ts.map +1 -1
- package/dist/translate/effect.js +23 -4
- package/dist/translate/effect.js.map +1 -1
- package/dist/translate/index.d.ts +1 -1
- package/dist/translate/index.d.ts.map +1 -1
- package/dist/translate/index.js +1 -1
- package/dist/translate/index.js.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +2 -0
- package/dist/validate.js.map +1 -1
- package/package.json +2 -1
- package/schemas/core/hull-shape.schema.json +30 -0
- package/schemas/core/roster.schema.json +26 -9
- package/schemas/core/stratagem.schema.json +2 -2
- package/schemas/core/target-profile.schema.json +30 -0
- package/schemas/core/unit-composition.schema.json +7 -0
- package/schemas/enrichment/ability-dsl/ability.schema.json +14 -0
package/dist/data/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/data/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/data/types.ts"],"names":[],"mappings":"AAkFA,uEAAuE;AACvE,MAAM,UAAU,YAAY;IAC1B,OAAO;QACL,KAAK,EAAE,EAAE;QACT,cAAc,EAAE,EAAE;QAClB,OAAO,EAAE,EAAE;QACX,cAAc,EAAE,EAAE;QAClB,QAAQ,EAAE,EAAE;QACZ,SAAS,EAAE,EAAE;QACb,aAAa,EAAE,EAAE;QACjB,WAAW,EAAE,EAAE;QACf,UAAU,EAAE,EAAE;QACd,YAAY,EAAE,EAAE;QAChB,iBAAiB,EAAE,EAAE;QACrB,gBAAgB,EAAE,EAAE;QACpB,cAAc,EAAE,EAAE;QAClB,OAAO,EAAE,EAAE;QACX,YAAY,EAAE,EAAE;QAChB,QAAQ,EAAE,EAAE;QACZ,eAAe,EAAE,EAAE;QACnB,YAAY,EAAE,EAAE;QAChB,kBAAkB,EAAE,EAAE;QACtB,iBAAiB,EAAE,EAAE;QACrB,gBAAgB,EAAE,EAAE;QACpB,cAAc,EAAE,EAAE;QAClB,UAAU,EAAE,EAAE;QACd,aAAa,EAAE,EAAE;QACjB,WAAW,EAAE,EAAE;QACf,gBAAgB,EAAE,EAAE;KACrB,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The shape of the embedded data bundle: one named array per entity collection.\n *\n * `RawData` is the boundary between the generated JSON-Schema types and the\n * linked view layer. The codegen ({@link file://../codegen-data.ts}) emits a\n * `RAW_DATA: RawData` constant; {@link Dataset} wraps it with linked accessors.\n *\n * @packageDocumentation\n */\nimport type {\n AbilityDSLEntry,\n DeploymentPattern,\n Detachment,\n Enhancement,\n Faction,\n ForceDisposition,\n GameVersion,\n HullShape,\n InteractionFlag,\n LeaderAttachment,\n Mission,\n MissionMatchup,\n PhaseMapping,\n ResourcePool,\n SecondaryCard,\n Stratagem,\n TerrainLayout,\n TargetProfile,\n TerrainTemplate,\n TimingFlag,\n Unit,\n UnitComposition,\n Wargear,\n WargearOption,\n Weapon,\n WeaponKeyword,\n} from \"../generated.js\";\n\n/**\n * Every entity collection in the dataset, keyed by camelCase collection name.\n *\n * Collections with no authored data yet (e.g. `interactionFlags`) are present\n * as empty arrays so the API surface is stable and new data flows through\n * automatically once authored.\n */\nexport interface RawData {\n units: Unit[];\n /** Named target archetypes referencing real units (faction_id + unit_id). */\n targetProfiles: TargetProfile[];\n weapons: Weapon[];\n /** Catalog of weapon keywords (Lethal Hits, Sustained Hits N, Anti-X N+, ...). */\n weaponKeywords: WeaponKeyword[];\n factions: Faction[];\n /** Community-authored ability mechanics (key is `ability_id`, not `id`). */\n abilities: AbilityDSLEntry[];\n /** Phase assignments, joined to abilities/stratagems/etc. via `source_id`. */\n phaseMappings: PhaseMapping[];\n detachments: Detachment[];\n stratagems: Stratagem[];\n enhancements: Enhancement[];\n leaderAttachments: LeaderAttachment[];\n unitCompositions: UnitComposition[];\n wargearOptions: WargearOption[];\n /** Non-weapon wargear items (icons, attachments) referenced by wargear options. */\n wargear: Wargear[];\n gameVersions: GameVersion[];\n missions: Mission[];\n missionMatchups: MissionMatchup[];\n missionCards: SecondaryCard[];\n deploymentPatterns: DeploymentPattern[];\n forceDispositions: ForceDisposition[];\n /** Reusable terrain catalog: standard areas and scenery features. */\n terrainTemplates: TerrainTemplate[];\n /** Terrain layouts: arrangements of catalog/inline pieces on the board. */\n terrainLayouts: TerrainLayout[];\n /** Reusable model collision hulls (polygon footprints) referenced by id. */\n hullShapes: HullShape[];\n resourcePools: ResourcePool[];\n timingFlags: TimingFlag[];\n interactionFlags: InteractionFlag[];\n}\n\n/** A `RawData` with every collection initialised to an empty array. */\nexport function emptyRawData(): RawData {\n return {\n units: [],\n targetProfiles: [],\n weapons: [],\n weaponKeywords: [],\n factions: [],\n abilities: [],\n phaseMappings: [],\n detachments: [],\n stratagems: [],\n enhancements: [],\n leaderAttachments: [],\n unitCompositions: [],\n wargearOptions: [],\n wargear: [],\n gameVersions: [],\n missions: [],\n missionMatchups: [],\n missionCards: [],\n deploymentPatterns: [],\n forceDispositions: [],\n terrainTemplates: [],\n terrainLayouts: [],\n hullShapes: [],\n resourcePools: [],\n timingFlags: [],\n interactionFlags: [],\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"newrecruit-json.d.ts","sourceRoot":"","sources":["../../src/export/newrecruit-json.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAkJxD,eAAO,MAAM,wBAAwB,EAAE,
|
|
1
|
+
{"version":3,"file":"newrecruit-json.d.ts","sourceRoot":"","sources":["../../src/export/newrecruit-json.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAkJxD,eAAO,MAAM,wBAAwB,EAAE,gBAyCtC,CAAC"}
|
|
@@ -106,13 +106,14 @@ export const newRecruitJsonSerializer = {
|
|
|
106
106
|
serialize(roster) {
|
|
107
107
|
const faction = factionCategory(roster);
|
|
108
108
|
const factionDisplay = titleCaseId(roster.faction_id) ?? "Unknown";
|
|
109
|
-
const detachmentDisplay = titleCaseId(roster.detachment_id);
|
|
110
109
|
const battleSize = battleSizeLabel(roster);
|
|
111
110
|
const config = [];
|
|
112
111
|
if (battleSize)
|
|
113
112
|
config.push(configSelection("Battle Size", battleSize, "battle-size"));
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
for (const d of roster.detachments) {
|
|
114
|
+
const display = titleCaseId(d.ref.id) ?? d.ref.raw_name;
|
|
115
|
+
config.push(configSelection("Detachment", display, "detachment"));
|
|
116
|
+
}
|
|
116
117
|
const force = {
|
|
117
118
|
id: "force-1",
|
|
118
119
|
name: "Army Roster",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"newrecruit-json.js","sourceRoot":"","sources":["../../src/export/newrecruit-json.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * NewRecruit JSON exporter — emits a BattleScribe-shaped roster skeleton that\n * round-trips through {@link newRecruitJsonAdapter}.\n *\n * The shape carries only fields the importer reads: `name`, `type`, `number`,\n * `costs[]`, `categories[].name`, `group`, and `catalogueName`. No `rules` /\n * `profiles` / `description` ever appear — we don't store them, and emitting\n * them would be an IP violation.\n *\n * Faction and detachment display names come from\n * {@link titleCaseId}(faction_id) — the Roster doesn't carry the source's raw\n * faction name, so we reconstruct it from the kebab-case id. This is the only\n * lossy hop in the JSON round-trip (e.g. `tau-empire` → \"Tau Empire\" rather\n * than the canonical \"T'au Empire\").\n *\n * @packageDocumentation\n */\nimport type { Roster, RosterUnit, RosterWargear } from \"../import/types.js\";\nimport { prettyJson, titleCaseId, totalArmyPoints } from \"./helpers.js\";\nimport type { RosterSerializer } from \"./serializer.js\";\n\nconst PTS_TYPE_ID = \"pts-type\";\nconst NEWRECRUIT_XMLNS = \"http://www.battlescribe.net/schema/rosterSchema\";\nconst NEWRECRUIT_GENERATED_BY = \"https://newrecruit.eu\";\n\ninterface JsonSelection {\n id: string;\n name: string;\n type: \"model\" | \"unit\" | \"upgrade\";\n number: number;\n group?: string;\n categories?: { name: string; primary: boolean }[];\n costs?: { name: string; typeId: string; value: number }[];\n selections?: JsonSelection[];\n}\n\ninterface JsonForce {\n id: string;\n name: string;\n catalogueName: string;\n selections: JsonSelection[];\n}\n\ninterface JsonRoster {\n name: string;\n xmlns: string;\n generatedBy: string;\n costs: { name: string; typeId: string; value: number }[];\n forces: JsonForce[];\n}\n\ninterface JsonPayload {\n name: string;\n generatedBy: string;\n roster: JsonRoster;\n}\n\n/** Build a \"Faction: <name>\" category from the unit's roster context. */\nfunction factionCategory(roster: Roster): { name: string; primary: boolean } | null {\n const display = titleCaseId(roster.faction_id);\n if (display === null) return null;\n return { name: `Faction: ${display}`, primary: false };\n}\n\nfunction wargearSelection(idx: number, w: RosterWargear): JsonSelection {\n return {\n id: `w-${idx}`,\n name: w.ref.raw_name,\n type: \"upgrade\",\n number: w.count,\n // The NewRecruit importer recognises a wargear selection by a category\n // ending in \" Weapon\" — emit a generic \"Ranged Weapon\" so we don't have\n // to track ranged-vs-melee separation the Roster doesn't model.\n categories: [{ name: \"Ranged Weapon\", primary: false }],\n };\n}\n\nfunction unitSelection(idx: number, u: RosterUnit, faction: { name: string; primary: boolean } | null): JsonSelection {\n const inner: JsonSelection[] = [];\n if (u.is_warlord) {\n inner.push({ id: `u${idx}-warlord`, name: \"Warlord\", type: \"upgrade\", number: 1 });\n }\n if (u.enhancement) {\n const enhCost =\n u.enhancement_points === null\n ? undefined\n : [{ name: \"pts\", typeId: PTS_TYPE_ID, value: u.enhancement_points }];\n inner.push({\n id: `u${idx}-enh`,\n name: u.enhancement.raw_name,\n type: \"upgrade\",\n number: 1,\n group: \"Enhancements\",\n ...(enhCost ? { costs: enhCost } : {}),\n });\n }\n\n const wargearSelections = u.wargear.map((w, wi) => wargearSelection(wi, w));\n\n const ownCategories = faction ? [faction] : [];\n\n if (u.model_count <= 1) {\n return {\n id: `u-${idx}`,\n name: u.ref.raw_name,\n type: \"model\",\n number: 1,\n categories: ownCategories,\n ...(u.points === null ? {} : { costs: [{ name: \"pts\", typeId: PTS_TYPE_ID, value: u.points }] }),\n selections: [...inner, ...wargearSelections],\n };\n }\n\n // Multi-model: wrap in a `type: \"unit\"` with a nested `type: \"model\"` that\n // carries the model count and the (collapsed, per-unit) wargear.\n return {\n id: `u-${idx}`,\n name: u.ref.raw_name,\n type: \"unit\",\n number: 1,\n categories: ownCategories,\n ...(u.points === null ? {} : { costs: [{ name: \"pts\", typeId: PTS_TYPE_ID, value: u.points }] }),\n selections: [\n ...inner,\n {\n id: `u${idx}-model`,\n name: u.ref.raw_name,\n type: \"model\",\n number: u.model_count,\n selections: wargearSelections,\n },\n ],\n };\n}\n\nfunction configSelection(name: string, value: string, idx: string): JsonSelection {\n return {\n id: `cfg-${idx}`,\n name,\n type: \"upgrade\",\n number: 1,\n categories: [{ name: \"Configuration\", primary: true }],\n selections: [\n {\n id: `cfg-${idx}-val`,\n name: value,\n type: \"upgrade\",\n number: 1,\n },\n ],\n };\n}\n\nfunction battleSizeLabel(roster: Roster): string | null {\n if (roster.battle_size === \"strike-force\") {\n const limit = roster.points.declared_limit ?? 2000;\n return `Strike Force (${limit} Point limit)`;\n }\n if (roster.battle_size === \"incursion\") {\n const limit = roster.points.declared_limit ?? 1000;\n return `Incursion (${limit} Point limit)`;\n }\n return null;\n}\n\nexport const newRecruitJsonSerializer: RosterSerializer = {\n id: \"newrecruit-json\",\n\n serialize(roster: Roster): string {\n const faction = factionCategory(roster);\n const factionDisplay = titleCaseId(roster.faction_id) ?? \"Unknown\";\n const detachmentDisplay = titleCaseId(roster.detachment_id);\n const battleSize = battleSizeLabel(roster);\n\n const config: JsonSelection[] = [];\n if (battleSize) config.push(configSelection(\"Battle Size\", battleSize, \"battle-size\"));\n if (detachmentDisplay) config.push(configSelection(\"Detachment\", detachmentDisplay, \"detachment\"));\n\n const force: JsonForce = {\n id: \"force-1\",\n name: \"Army Roster\",\n catalogueName: factionDisplay,\n selections: [\n ...config,\n ...roster.units.map((u, i) => unitSelection(i, u, faction)),\n ],\n };\n\n const total = totalArmyPoints(roster);\n\n const payload: JsonPayload = {\n name: roster.name,\n generatedBy: NEWRECRUIT_GENERATED_BY,\n roster: {\n name: roster.name,\n xmlns: NEWRECRUIT_XMLNS,\n generatedBy: NEWRECRUIT_GENERATED_BY,\n costs: [{ name: \"pts\", typeId: PTS_TYPE_ID, value: total }],\n forces: [force],\n },\n };\n\n return prettyJson(payload);\n },\n};\n"]}
|
|
1
|
+
{"version":3,"file":"newrecruit-json.js","sourceRoot":"","sources":["../../src/export/newrecruit-json.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * NewRecruit JSON exporter — emits a BattleScribe-shaped roster skeleton that\n * round-trips through {@link newRecruitJsonAdapter}.\n *\n * The shape carries only fields the importer reads: `name`, `type`, `number`,\n * `costs[]`, `categories[].name`, `group`, and `catalogueName`. No `rules` /\n * `profiles` / `description` ever appear — we don't store them, and emitting\n * them would be an IP violation.\n *\n * Faction and detachment display names come from\n * {@link titleCaseId}(faction_id) — the Roster doesn't carry the source's raw\n * faction name, so we reconstruct it from the kebab-case id. This is the only\n * lossy hop in the JSON round-trip (e.g. `tau-empire` → \"Tau Empire\" rather\n * than the canonical \"T'au Empire\").\n *\n * @packageDocumentation\n */\nimport type { Roster, RosterUnit, RosterWargear } from \"../import/types.js\";\nimport { prettyJson, titleCaseId, totalArmyPoints } from \"./helpers.js\";\nimport type { RosterSerializer } from \"./serializer.js\";\n\nconst PTS_TYPE_ID = \"pts-type\";\nconst NEWRECRUIT_XMLNS = \"http://www.battlescribe.net/schema/rosterSchema\";\nconst NEWRECRUIT_GENERATED_BY = \"https://newrecruit.eu\";\n\ninterface JsonSelection {\n id: string;\n name: string;\n type: \"model\" | \"unit\" | \"upgrade\";\n number: number;\n group?: string;\n categories?: { name: string; primary: boolean }[];\n costs?: { name: string; typeId: string; value: number }[];\n selections?: JsonSelection[];\n}\n\ninterface JsonForce {\n id: string;\n name: string;\n catalogueName: string;\n selections: JsonSelection[];\n}\n\ninterface JsonRoster {\n name: string;\n xmlns: string;\n generatedBy: string;\n costs: { name: string; typeId: string; value: number }[];\n forces: JsonForce[];\n}\n\ninterface JsonPayload {\n name: string;\n generatedBy: string;\n roster: JsonRoster;\n}\n\n/** Build a \"Faction: <name>\" category from the unit's roster context. */\nfunction factionCategory(roster: Roster): { name: string; primary: boolean } | null {\n const display = titleCaseId(roster.faction_id);\n if (display === null) return null;\n return { name: `Faction: ${display}`, primary: false };\n}\n\nfunction wargearSelection(idx: number, w: RosterWargear): JsonSelection {\n return {\n id: `w-${idx}`,\n name: w.ref.raw_name,\n type: \"upgrade\",\n number: w.count,\n // The NewRecruit importer recognises a wargear selection by a category\n // ending in \" Weapon\" — emit a generic \"Ranged Weapon\" so we don't have\n // to track ranged-vs-melee separation the Roster doesn't model.\n categories: [{ name: \"Ranged Weapon\", primary: false }],\n };\n}\n\nfunction unitSelection(idx: number, u: RosterUnit, faction: { name: string; primary: boolean } | null): JsonSelection {\n const inner: JsonSelection[] = [];\n if (u.is_warlord) {\n inner.push({ id: `u${idx}-warlord`, name: \"Warlord\", type: \"upgrade\", number: 1 });\n }\n if (u.enhancement) {\n const enhCost =\n u.enhancement_points === null\n ? undefined\n : [{ name: \"pts\", typeId: PTS_TYPE_ID, value: u.enhancement_points }];\n inner.push({\n id: `u${idx}-enh`,\n name: u.enhancement.raw_name,\n type: \"upgrade\",\n number: 1,\n group: \"Enhancements\",\n ...(enhCost ? { costs: enhCost } : {}),\n });\n }\n\n const wargearSelections = u.wargear.map((w, wi) => wargearSelection(wi, w));\n\n const ownCategories = faction ? [faction] : [];\n\n if (u.model_count <= 1) {\n return {\n id: `u-${idx}`,\n name: u.ref.raw_name,\n type: \"model\",\n number: 1,\n categories: ownCategories,\n ...(u.points === null ? {} : { costs: [{ name: \"pts\", typeId: PTS_TYPE_ID, value: u.points }] }),\n selections: [...inner, ...wargearSelections],\n };\n }\n\n // Multi-model: wrap in a `type: \"unit\"` with a nested `type: \"model\"` that\n // carries the model count and the (collapsed, per-unit) wargear.\n return {\n id: `u-${idx}`,\n name: u.ref.raw_name,\n type: \"unit\",\n number: 1,\n categories: ownCategories,\n ...(u.points === null ? {} : { costs: [{ name: \"pts\", typeId: PTS_TYPE_ID, value: u.points }] }),\n selections: [\n ...inner,\n {\n id: `u${idx}-model`,\n name: u.ref.raw_name,\n type: \"model\",\n number: u.model_count,\n selections: wargearSelections,\n },\n ],\n };\n}\n\nfunction configSelection(name: string, value: string, idx: string): JsonSelection {\n return {\n id: `cfg-${idx}`,\n name,\n type: \"upgrade\",\n number: 1,\n categories: [{ name: \"Configuration\", primary: true }],\n selections: [\n {\n id: `cfg-${idx}-val`,\n name: value,\n type: \"upgrade\",\n number: 1,\n },\n ],\n };\n}\n\nfunction battleSizeLabel(roster: Roster): string | null {\n if (roster.battle_size === \"strike-force\") {\n const limit = roster.points.declared_limit ?? 2000;\n return `Strike Force (${limit} Point limit)`;\n }\n if (roster.battle_size === \"incursion\") {\n const limit = roster.points.declared_limit ?? 1000;\n return `Incursion (${limit} Point limit)`;\n }\n return null;\n}\n\nexport const newRecruitJsonSerializer: RosterSerializer = {\n id: \"newrecruit-json\",\n\n serialize(roster: Roster): string {\n const faction = factionCategory(roster);\n const factionDisplay = titleCaseId(roster.faction_id) ?? \"Unknown\";\n const battleSize = battleSizeLabel(roster);\n\n const config: JsonSelection[] = [];\n if (battleSize) config.push(configSelection(\"Battle Size\", battleSize, \"battle-size\"));\n for (const d of roster.detachments) {\n const display = titleCaseId(d.ref.id) ?? d.ref.raw_name;\n config.push(configSelection(\"Detachment\", display, \"detachment\"));\n }\n\n const force: JsonForce = {\n id: \"force-1\",\n name: \"Army Roster\",\n catalogueName: factionDisplay,\n selections: [\n ...config,\n ...roster.units.map((u, i) => unitSelection(i, u, faction)),\n ],\n };\n\n const total = totalArmyPoints(roster);\n\n const payload: JsonPayload = {\n name: roster.name,\n generatedBy: NEWRECRUIT_GENERATED_BY,\n roster: {\n name: roster.name,\n xmlns: NEWRECRUIT_XMLNS,\n generatedBy: NEWRECRUIT_GENERATED_BY,\n costs: [{ name: \"pts\", typeId: PTS_TYPE_ID, value: total }],\n forces: [force],\n },\n };\n\n return prettyJson(payload);\n },\n};\n"]}
|
|
@@ -48,7 +48,7 @@ export const newRecruitSimpleSerializer = {
|
|
|
48
48
|
id: "newrecruit-simple",
|
|
49
49
|
serialize(roster) {
|
|
50
50
|
const faction = titleCaseId(roster.faction_id) ?? "Unknown";
|
|
51
|
-
const
|
|
51
|
+
const detachments = roster.detachments.map((d) => titleCaseId(d.ref.id) ?? d.ref.raw_name);
|
|
52
52
|
const battle = battleSizeLabel(roster);
|
|
53
53
|
const total = totalArmyPoints(roster);
|
|
54
54
|
const lines = [];
|
|
@@ -62,7 +62,7 @@ export const newRecruitSimpleSerializer = {
|
|
|
62
62
|
lines.push("## Configuration");
|
|
63
63
|
if (battle)
|
|
64
64
|
lines.push(`Battle Size: ${battle}`);
|
|
65
|
-
|
|
65
|
+
for (const detachment of detachments)
|
|
66
66
|
lines.push(`Detachment: ${detachment}`);
|
|
67
67
|
lines.push("");
|
|
68
68
|
// The Roster doesn't tag allied vs. battleline per unit; emit one section.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"newrecruit-simple.js","sourceRoot":"","sources":["../../src/export/newrecruit-simple.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"newrecruit-simple.js","sourceRoot":"","sources":["../../src/export/newrecruit-simple.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * NewRecruit \"simple\" markdown-ish text exporter.\n *\n * Shape:\n * ```\n * <faction> - <list name> - [N pts]\n *\n * # ++ Army Roster ++ [N pts]\n * ## Configuration\n * Battle Size: <Label>\n * Detachment: <Name>\n *\n * ## Battleline [N pts]\n * <Unit> [pts]: <wargear, …, EnhName [N pts], …>\n * <Multi-Unit> [pts]:\n * • <Nx> <ModelType>: <wargear>\n * ```\n *\n * Enhancements are inlined as `Name [N pts]` (the only place we re-emit a\n * `[N pts]` bracket on a token).\n *\n * @packageDocumentation\n */\nimport type { Roster, RosterUnit } from \"../import/types.js\";\nimport { displayedUnitPoints, titleCaseId, totalArmyPoints } from \"./helpers.js\";\nimport type { RosterSerializer } from \"./serializer.js\";\n\nfunction battleSizeLabel(roster: Roster): string | null {\n if (roster.battle_size === \"strike-force\") {\n return `Strike Force (${roster.points.declared_limit ?? 2000} Point limit)`;\n }\n if (roster.battle_size === \"incursion\") {\n return `Incursion (${roster.points.declared_limit ?? 1000} Point limit)`;\n }\n return null;\n}\n\n/** Build the wargear list inline. For homogeneous multi-model units, divides\n * counts by model_count so the per-model render is clean. */\nfunction wargearText(u: RosterUnit, perModelDivisor: number): string {\n const parts: string[] = [];\n if (u.enhancement) {\n const ptsTag = u.enhancement_points === null ? \"\" : ` [${u.enhancement_points} pts]`;\n parts.push(`${u.enhancement.raw_name}${ptsTag}`);\n }\n if (u.is_warlord) parts.push(\"Warlord\");\n for (const w of u.wargear) {\n const c = perModelDivisor > 0 ? w.count / perModelDivisor : w.count;\n parts.push(c > 1 ? `${c}x ${w.ref.raw_name}` : w.ref.raw_name);\n }\n return parts.join(\", \");\n}\n\nfunction unitText(u: RosterUnit): string[] {\n const pts = displayedUnitPoints(u);\n const ptsText = pts === null ? \"\" : `${pts} pts`;\n\n if (u.model_count <= 1) {\n return [`${u.ref.raw_name} [${ptsText}]: ${wargearText(u, 1)}`];\n }\n // Multi-model: homogeneous when every weapon count divides cleanly.\n const divisible = u.wargear.every((w) => w.count % u.model_count === 0);\n if (divisible) {\n return [\n `${u.ref.raw_name} [${ptsText}]:`,\n `• ${u.model_count}x ${u.ref.raw_name}: ${wargearText(u, u.model_count)}`,\n ];\n }\n // Heterogeneous fallback: render as a single bullet with full counts.\n return [\n `${u.ref.raw_name} [${ptsText}]:`,\n `• ${u.model_count}x ${u.ref.raw_name}: ${wargearText(u, 1)}`,\n ];\n}\n\nexport const newRecruitSimpleSerializer: RosterSerializer = {\n id: \"newrecruit-simple\",\n\n serialize(roster: Roster): string {\n const faction = titleCaseId(roster.faction_id) ?? \"Unknown\";\n const detachments = roster.detachments.map((d) => titleCaseId(d.ref.id) ?? d.ref.raw_name);\n const battle = battleSizeLabel(roster);\n const total = totalArmyPoints(roster);\n\n const lines: string[] = [];\n // First line carries the *declared limit* (the army's points ceiling); the\n // `# ++ Army Roster ++` line carries the *reported total*. They differ\n // when the list isn't filled to the cap.\n const limit = roster.points.declared_limit ?? total;\n lines.push(`${faction} - ${roster.name} - [${limit} pts]`);\n lines.push(\"\");\n lines.push(`# ++ Army Roster ++ [${total} pts]`);\n lines.push(\"## Configuration\");\n if (battle) lines.push(`Battle Size: ${battle}`);\n for (const detachment of detachments) lines.push(`Detachment: ${detachment}`);\n lines.push(\"\");\n\n // The Roster doesn't tag allied vs. battleline per unit; emit one section.\n const sectionTotal = roster.units.reduce(\n (acc, u) => acc + (u.points ?? 0) + (u.enhancement_points ?? 0),\n 0,\n );\n lines.push(`## Battleline [${sectionTotal} pts]`);\n for (const u of roster.units) lines.push(...unitText(u));\n\n return lines.join(\"\\n\") + \"\\n\";\n },\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"newrecruit-wtc.d.ts","sourceRoot":"","sources":["../../src/export/newrecruit-wtc.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"newrecruit-wtc.d.ts","sourceRoot":"","sources":["../../src/export/newrecruit-wtc.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAgExD,eAAO,MAAM,8BAA8B,EAAE,gBAyB5C,CAAC;AAuBF,eAAO,MAAM,2BAA2B,EAAE,gBAgDzC,CAAC"}
|
|
@@ -11,7 +11,9 @@ function wargearListText(unit, includeWarlordTag) {
|
|
|
11
11
|
}
|
|
12
12
|
function header(roster, units, charSlots) {
|
|
13
13
|
const faction = titleCaseId(roster.faction_id) ?? "Unknown";
|
|
14
|
-
const detachment =
|
|
14
|
+
const detachment = roster.detachments.length
|
|
15
|
+
? roster.detachments.map((d) => titleCaseId(d.ref.id) ?? d.ref.raw_name).join(", ")
|
|
16
|
+
: null;
|
|
15
17
|
const limit = roster.points.declared_limit ?? totalArmyPoints(roster);
|
|
16
18
|
const total = roster.points.total_reported ?? totalArmyPoints(roster);
|
|
17
19
|
const warlordIdx = units.findIndex((u) => u.is_warlord);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"newrecruit-wtc.js","sourceRoot":"","sources":["../../src/export/newrecruit-wtc.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * NewRecruit wtc-compact and wtc-full text exporters.\n *\n * Both formats lead with a `++++++++` summary header and then list units. The\n * compact body packs each unit onto one line; the full body uses section\n * headers (`BATTLELINE` / `ALLIED UNITS`) and two-line unit blocks with\n * `N with <wargear>` and `• Nx <ModelType>` per-model breakdowns.\n *\n * Faction & detachment display names are reconstructed via\n * {@link titleCaseId}. `CharN:` numbering is re-derived heuristically from\n * `is_warlord || enhancement || leader_attachment` (see\n * {@link charSlotAssignment}). The `+ SECONDARY:` summary line is omitted —\n * tournament secondaries aren't modelled in the Roster.\n *\n * @packageDocumentation\n */\nimport type { Roster, RosterUnit } from \"../import/types.js\";\nimport {\n charSlotAssignment,\n displayedUnitPoints,\n titleCaseId,\n totalArmyPoints,\n} from \"./helpers.js\";\nimport type { RosterSerializer } from \"./serializer.js\";\n\nconst FENCE = \"+++++++++++++++++++++++++++++++++++++++++++++++\";\n\nfunction wargearListText(unit: RosterUnit, includeWarlordTag: boolean): string {\n const parts: string[] = [];\n for (const w of unit.wargear) {\n parts.push(w.count > 1 ? `${w.count}x ${w.ref.raw_name}` : w.ref.raw_name);\n }\n if (includeWarlordTag && unit.is_warlord) parts.push(\"Warlord\");\n return parts.join(\", \");\n}\n\nfunction header(roster: Roster, units: readonly RosterUnit[], charSlots: readonly (number | null)[]): string {\n const faction = titleCaseId(roster.faction_id) ?? \"Unknown\";\n const detachment = titleCaseId(roster.detachment_id);\n const limit = roster.points.declared_limit ?? totalArmyPoints(roster);\n const total = roster.points.total_reported ?? totalArmyPoints(roster);\n\n const warlordIdx = units.findIndex((u) => u.is_warlord);\n const warlord =\n warlordIdx >= 0\n ? `Char${charSlots[warlordIdx]}: ${units[warlordIdx].ref.raw_name}`\n : \"—\";\n\n const enhancementIdx = units.findIndex((u) => u.enhancement !== null);\n let enhancement = \"—\";\n if (enhancementIdx >= 0) {\n const u = units[enhancementIdx];\n enhancement = `${u.enhancement!.raw_name} (on Char${charSlots[enhancementIdx]}: ${u.ref.raw_name})`;\n }\n\n const lines: string[] = [\n FENCE,\n `+ LIST NAME: ${roster.name}`,\n `+ FACTION KEYWORD: ${faction}`,\n `+ DETACHMENT: ${detachment ?? \"—\"}`,\n `+ TOTAL ARMY POINTS: ${total}pts`,\n `+ POINTS LIMIT: ${limit}pts`,\n `+`,\n `+ WARLORD: ${warlord}`,\n `+ ENHANCEMENT: ${enhancement}`,\n `+ NUMBER OF UNITS: ${units.length}`,\n FENCE,\n ];\n return lines.join(\"\\n\");\n}\n\nfunction isAlliedUnit(u: RosterUnit, factionId: string | null): boolean {\n // Heuristic: the Roster doesn't tag allied units explicitly, but the\n // multi-force diagnostic + the fact that we only carry the primary faction\n // means non-primary-faction units aren't recognisable. The only fact we *do*\n // have is `leader_attachment` and warlord/enhancement (which mark primary\n // characters). For unit grouping in wtc-full we simply place everything in\n // BATTLELINE unless the Roster's multi-force flag suggests there's an allied\n // detachment. Since the flag is a diagnostic warning, not a per-unit tag,\n // wtc-full export collapses to a single BATTLELINE section.\n void u;\n void factionId;\n return false;\n}\n\nexport const newRecruitWtcCompactSerializer: RosterSerializer = {\n id: \"newrecruit-wtc-compact\",\n\n serialize(roster: Roster): string {\n const units = roster.units;\n const slots = charSlotAssignment(units);\n const lines: string[] = [header(roster, units, slots), \"\"];\n\n for (let i = 0; i < units.length; i += 1) {\n const u = units[i];\n const prefix = slots[i] !== null ? `Char${slots[i]}: ` : \"\";\n const pts = displayedUnitPoints(u);\n const ptsText = pts === null ? \"\" : `${pts} pts`;\n lines.push(`${prefix}${u.model_count}x ${u.ref.raw_name} (${ptsText}): ${wargearListText(u, true)}`);\n if (u.enhancement) {\n const enhText =\n u.enhancement_points === null\n ? `Enhancement: ${u.enhancement.raw_name}`\n : `Enhancement: ${u.enhancement.raw_name} (+${u.enhancement_points} pts)`;\n lines.push(enhText);\n }\n }\n\n return lines.join(\"\\n\") + \"\\n\";\n },\n};\n\n/**\n * For a multi-model unit, render its wargear as `N with <per-model list>` when\n * the wargear divides evenly across models (the natural NewRecruit form).\n * Otherwise emit `1 with <full Nx counts>` so the counts round-trip exactly.\n */\nfunction multiModelWithLine(u: RosterUnit): string {\n // Homogeneous when every weapon count divides cleanly by model_count.\n const divisible = u.wargear.every((w) => w.count % u.model_count === 0);\n if (divisible) {\n const perModel = u.wargear\n .map((w) => {\n const c = w.count / u.model_count;\n return c > 1 ? `${c}x ${w.ref.raw_name}` : w.ref.raw_name;\n })\n .filter((s) => s.length > 0);\n if (u.is_warlord) perModel.push(\"Warlord\");\n return `${u.model_count} with ${perModel.join(\", \")}`;\n }\n return `1 with ${wargearListText(u, true)}`;\n}\n\nexport const newRecruitWtcFullSerializer: RosterSerializer = {\n id: \"newrecruit-wtc-full\",\n\n serialize(roster: Roster): string {\n const units = roster.units;\n const slots = charSlotAssignment(units);\n\n const battlelineIdxs: number[] = [];\n const alliedIdxs: number[] = [];\n for (let i = 0; i < units.length; i += 1) {\n if (isAlliedUnit(units[i], roster.faction_id)) alliedIdxs.push(i);\n else battlelineIdxs.push(i);\n }\n\n const lines: string[] = [header(roster, units, slots), \"\", \"BATTLELINE\", \"\"];\n\n const emitUnit = (i: number): void => {\n const u = units[i];\n const prefix = slots[i] !== null ? `Char${slots[i]}: ` : \"\";\n const pts = displayedUnitPoints(u);\n const ptsText = pts === null ? \"\" : `${pts} pts`;\n lines.push(`${prefix}${u.model_count}x ${u.ref.raw_name} (${ptsText})`);\n\n if (u.model_count > 1) {\n lines.push(multiModelWithLine(u));\n } else {\n lines.push(`1 with ${wargearListText(u, true)}`);\n }\n\n if (u.enhancement) {\n const enhText =\n u.enhancement_points === null\n ? `Enhancement: ${u.enhancement.raw_name}`\n : `Enhancement: ${u.enhancement.raw_name} (+${u.enhancement_points} pts)`;\n lines.push(enhText);\n }\n lines.push(\"\");\n };\n\n for (const i of battlelineIdxs) emitUnit(i);\n\n if (alliedIdxs.length > 0) {\n lines.push(\"ALLIED UNITS\", \"\");\n for (const i of alliedIdxs) emitUnit(i);\n }\n\n return lines.join(\"\\n\");\n },\n};\n"]}
|
|
1
|
+
{"version":3,"file":"newrecruit-wtc.js","sourceRoot":"","sources":["../../src/export/newrecruit-wtc.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * NewRecruit wtc-compact and wtc-full text exporters.\n *\n * Both formats lead with a `++++++++` summary header and then list units. The\n * compact body packs each unit onto one line; the full body uses section\n * headers (`BATTLELINE` / `ALLIED UNITS`) and two-line unit blocks with\n * `N with <wargear>` and `• Nx <ModelType>` per-model breakdowns.\n *\n * Faction & detachment display names are reconstructed via\n * {@link titleCaseId}. `CharN:` numbering is re-derived heuristically from\n * `is_warlord || enhancement || leader_attachment` (see\n * {@link charSlotAssignment}). The `+ SECONDARY:` summary line is omitted —\n * tournament secondaries aren't modelled in the Roster.\n *\n * @packageDocumentation\n */\nimport type { Roster, RosterUnit } from \"../import/types.js\";\nimport {\n charSlotAssignment,\n displayedUnitPoints,\n titleCaseId,\n totalArmyPoints,\n} from \"./helpers.js\";\nimport type { RosterSerializer } from \"./serializer.js\";\n\nconst FENCE = \"+++++++++++++++++++++++++++++++++++++++++++++++\";\n\nfunction wargearListText(unit: RosterUnit, includeWarlordTag: boolean): string {\n const parts: string[] = [];\n for (const w of unit.wargear) {\n parts.push(w.count > 1 ? `${w.count}x ${w.ref.raw_name}` : w.ref.raw_name);\n }\n if (includeWarlordTag && unit.is_warlord) parts.push(\"Warlord\");\n return parts.join(\", \");\n}\n\nfunction header(roster: Roster, units: readonly RosterUnit[], charSlots: readonly (number | null)[]): string {\n const faction = titleCaseId(roster.faction_id) ?? \"Unknown\";\n const detachment = roster.detachments.length\n ? roster.detachments.map((d) => titleCaseId(d.ref.id) ?? d.ref.raw_name).join(\", \")\n : null;\n const limit = roster.points.declared_limit ?? totalArmyPoints(roster);\n const total = roster.points.total_reported ?? totalArmyPoints(roster);\n\n const warlordIdx = units.findIndex((u) => u.is_warlord);\n const warlord =\n warlordIdx >= 0\n ? `Char${charSlots[warlordIdx]}: ${units[warlordIdx].ref.raw_name}`\n : \"—\";\n\n const enhancementIdx = units.findIndex((u) => u.enhancement !== null);\n let enhancement = \"—\";\n if (enhancementIdx >= 0) {\n const u = units[enhancementIdx];\n enhancement = `${u.enhancement!.raw_name} (on Char${charSlots[enhancementIdx]}: ${u.ref.raw_name})`;\n }\n\n const lines: string[] = [\n FENCE,\n `+ LIST NAME: ${roster.name}`,\n `+ FACTION KEYWORD: ${faction}`,\n `+ DETACHMENT: ${detachment ?? \"—\"}`,\n `+ TOTAL ARMY POINTS: ${total}pts`,\n `+ POINTS LIMIT: ${limit}pts`,\n `+`,\n `+ WARLORD: ${warlord}`,\n `+ ENHANCEMENT: ${enhancement}`,\n `+ NUMBER OF UNITS: ${units.length}`,\n FENCE,\n ];\n return lines.join(\"\\n\");\n}\n\nfunction isAlliedUnit(u: RosterUnit, factionId: string | null): boolean {\n // Heuristic: the Roster doesn't tag allied units explicitly, but the\n // multi-force diagnostic + the fact that we only carry the primary faction\n // means non-primary-faction units aren't recognisable. The only fact we *do*\n // have is `leader_attachment` and warlord/enhancement (which mark primary\n // characters). For unit grouping in wtc-full we simply place everything in\n // BATTLELINE unless the Roster's multi-force flag suggests there's an allied\n // detachment. Since the flag is a diagnostic warning, not a per-unit tag,\n // wtc-full export collapses to a single BATTLELINE section.\n void u;\n void factionId;\n return false;\n}\n\nexport const newRecruitWtcCompactSerializer: RosterSerializer = {\n id: \"newrecruit-wtc-compact\",\n\n serialize(roster: Roster): string {\n const units = roster.units;\n const slots = charSlotAssignment(units);\n const lines: string[] = [header(roster, units, slots), \"\"];\n\n for (let i = 0; i < units.length; i += 1) {\n const u = units[i];\n const prefix = slots[i] !== null ? `Char${slots[i]}: ` : \"\";\n const pts = displayedUnitPoints(u);\n const ptsText = pts === null ? \"\" : `${pts} pts`;\n lines.push(`${prefix}${u.model_count}x ${u.ref.raw_name} (${ptsText}): ${wargearListText(u, true)}`);\n if (u.enhancement) {\n const enhText =\n u.enhancement_points === null\n ? `Enhancement: ${u.enhancement.raw_name}`\n : `Enhancement: ${u.enhancement.raw_name} (+${u.enhancement_points} pts)`;\n lines.push(enhText);\n }\n }\n\n return lines.join(\"\\n\") + \"\\n\";\n },\n};\n\n/**\n * For a multi-model unit, render its wargear as `N with <per-model list>` when\n * the wargear divides evenly across models (the natural NewRecruit form).\n * Otherwise emit `1 with <full Nx counts>` so the counts round-trip exactly.\n */\nfunction multiModelWithLine(u: RosterUnit): string {\n // Homogeneous when every weapon count divides cleanly by model_count.\n const divisible = u.wargear.every((w) => w.count % u.model_count === 0);\n if (divisible) {\n const perModel = u.wargear\n .map((w) => {\n const c = w.count / u.model_count;\n return c > 1 ? `${c}x ${w.ref.raw_name}` : w.ref.raw_name;\n })\n .filter((s) => s.length > 0);\n if (u.is_warlord) perModel.push(\"Warlord\");\n return `${u.model_count} with ${perModel.join(\", \")}`;\n }\n return `1 with ${wargearListText(u, true)}`;\n}\n\nexport const newRecruitWtcFullSerializer: RosterSerializer = {\n id: \"newrecruit-wtc-full\",\n\n serialize(roster: Roster): string {\n const units = roster.units;\n const slots = charSlotAssignment(units);\n\n const battlelineIdxs: number[] = [];\n const alliedIdxs: number[] = [];\n for (let i = 0; i < units.length; i += 1) {\n if (isAlliedUnit(units[i], roster.faction_id)) alliedIdxs.push(i);\n else battlelineIdxs.push(i);\n }\n\n const lines: string[] = [header(roster, units, slots), \"\", \"BATTLELINE\", \"\"];\n\n const emitUnit = (i: number): void => {\n const u = units[i];\n const prefix = slots[i] !== null ? `Char${slots[i]}: ` : \"\";\n const pts = displayedUnitPoints(u);\n const ptsText = pts === null ? \"\" : `${pts} pts`;\n lines.push(`${prefix}${u.model_count}x ${u.ref.raw_name} (${ptsText})`);\n\n if (u.model_count > 1) {\n lines.push(multiModelWithLine(u));\n } else {\n lines.push(`1 with ${wargearListText(u, true)}`);\n }\n\n if (u.enhancement) {\n const enhText =\n u.enhancement_points === null\n ? `Enhancement: ${u.enhancement.raw_name}`\n : `Enhancement: ${u.enhancement.raw_name} (+${u.enhancement_points} pts)`;\n lines.push(enhText);\n }\n lines.push(\"\");\n };\n\n for (const i of battlelineIdxs) emitUnit(i);\n\n if (alliedIdxs.length > 0) {\n lines.push(\"ALLIED UNITS\", \"\");\n for (const i of alliedIdxs) emitUnit(i);\n }\n\n return lines.join(\"\\n\");\n },\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rosterizer.d.ts","sourceRoot":"","sources":["../../src/export/rosterizer.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"rosterizer.d.ts","sourceRoot":"","sources":["../../src/export/rosterizer.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAsIxD,eAAO,MAAM,oBAAoB,EAAE,gBAsClC,CAAC"}
|
|
@@ -82,11 +82,11 @@ function factionAsset(roster) {
|
|
|
82
82
|
return null;
|
|
83
83
|
return { item: key(CLS_FACTION, display), name: display, quantity: 1 };
|
|
84
84
|
}
|
|
85
|
-
function
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return
|
|
89
|
-
|
|
85
|
+
function detachmentAssets(roster) {
|
|
86
|
+
return roster.detachments.map((d) => {
|
|
87
|
+
const display = titleCaseId(d.ref.id) ?? d.ref.raw_name;
|
|
88
|
+
return { item: key(CLS_DETACHMENT, display), name: display, quantity: 1 };
|
|
89
|
+
});
|
|
90
90
|
}
|
|
91
91
|
function battleSizeAsset(roster) {
|
|
92
92
|
if (roster.battle_size === "strike-force") {
|
|
@@ -108,8 +108,7 @@ export const rosterizerSerializer = {
|
|
|
108
108
|
const faction = factionAsset(roster);
|
|
109
109
|
if (faction)
|
|
110
110
|
included.push(faction);
|
|
111
|
-
const detachment
|
|
112
|
-
if (detachment)
|
|
111
|
+
for (const detachment of detachmentAssets(roster))
|
|
113
112
|
included.push(detachment);
|
|
114
113
|
const battleSize = battleSizeAsset(roster);
|
|
115
114
|
if (battleSize)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rosterizer.js","sourceRoot":"","sources":["../../src/export/rosterizer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Rosterizer serializer — emits a Rosterizer-shaped roster JSON skeleton that\n * round-trips through {@link rosterizerAdapter}.\n *\n * The shape carries only fields the importer reads: `rulebook` (envelope),\n * `snapshot` (an `Asset` tree rooted at `Roster§Roster`), and per-unit\n * `item`/`name`/`quantity`/`stats.Points.value`/`assets.included`/`assets.traits`.\n * No `text`, `description`, `rules`, `lineage`, `_layers`, `classIdentity`,\n * `processed`, or `bareResourceKey` ever appear — they aren't stored in the\n * Roster and emitting them could leak prose.\n *\n * Faction and detachment display names come from {@link titleCaseId} — the\n * Roster doesn't carry the source's raw faction name, so we reconstruct it\n * from the kebab-case id. Same lossy hop as the NewRecruit JSON serializer.\n *\n * @packageDocumentation\n */\nimport type { Roster, RosterUnit, RosterWargear } from \"../import/types.js\";\nimport { prettyJson, titleCaseId, totalArmyPoints } from \"./helpers.js\";\nimport type { RosterSerializer } from \"./serializer.js\";\n\n// Mirror the importer's constants (kept inline rather than imported so the\n// exporter stays decoupled — the seams are the `item` keys themselves).\nconst CLS_ROSTER = \"Roster\";\nconst CLS_FACTION = \"Faction\";\nconst CLS_DETACHMENT = \"Detachment\";\nconst CLS_UNIT = \"Unit\";\nconst CLS_WEAPON = \"Weapon\";\nconst CLS_ENHANCEMENT = \"Enhancement\";\nconst CLS_BATTLE_SIZE = \"Battle Size\";\nconst CLS_TRAIT = \"Trait\";\nconst DSG_WARLORD = \"Warlord\";\n\nconst RULEBOOK_NAME = \"40kdc\";\nconst RULEBOOK_GAME = \"Warhammer 40,000\";\nconst RULEBOOK_PUBLISHER = \"Alpaca Software\";\nconst RULEBOOK_URL = \"https://40kdc.dev\";\nconst RULEBOOK_GENRE = \"wargame\";\n\ninterface Asset {\n item: string;\n name?: string;\n quantity?: number;\n stats?: Record<string, { value: number }>;\n assets?: {\n included?: Asset[];\n traits?: Asset[];\n };\n}\n\ninterface Envelope {\n slug: string;\n key: string;\n visible: \"hidden\" | \"public\" | \"friends\";\n locked: boolean;\n rulebook: {\n name: string;\n game: string;\n publisher: string;\n url: string;\n genre: string;\n };\n snapshot: Asset;\n}\n\nfunction key(classification: string, designation: string): string {\n return `${classification}§${designation}`; // §\n}\n\nfunction pointsStat(value: number | null | undefined): Record<string, { value: number }> | undefined {\n if (value === null || value === undefined) return undefined;\n return { Points: { value } };\n}\n\nfunction wargearAsset(w: RosterWargear): Asset {\n return {\n item: key(CLS_WEAPON, w.ref.raw_name),\n name: w.ref.raw_name,\n quantity: w.count,\n };\n}\n\nfunction enhancementAsset(u: RosterUnit): Asset | null {\n if (!u.enhancement) return null;\n return {\n item: key(CLS_ENHANCEMENT, u.enhancement.raw_name),\n name: u.enhancement.raw_name,\n quantity: 1,\n ...(u.enhancement_points !== null\n ? { stats: pointsStat(u.enhancement_points) }\n : {}),\n };\n}\n\nfunction warlordTraitAsset(): Asset {\n return {\n item: key(CLS_TRAIT, DSG_WARLORD),\n name: DSG_WARLORD,\n quantity: 1,\n };\n}\n\nfunction unitAsset(u: RosterUnit): Asset {\n const included: Asset[] = [];\n const enh = enhancementAsset(u);\n if (enh !== null) included.push(enh);\n for (const w of u.wargear) included.push(wargearAsset(w));\n\n const traits: Asset[] = [];\n if (u.is_warlord) traits.push(warlordTraitAsset());\n\n const asset: Asset = {\n item: key(CLS_UNIT, u.ref.raw_name),\n name: u.ref.raw_name,\n quantity: u.model_count,\n };\n const stats = pointsStat(u.points);\n if (stats !== undefined) asset.stats = stats;\n if (included.length > 0 || traits.length > 0) {\n asset.assets = {};\n if (included.length > 0) asset.assets.included = included;\n if (traits.length > 0) asset.assets.traits = traits;\n }\n return asset;\n}\n\nfunction factionAsset(roster: Roster): Asset | null {\n const display = titleCaseId(roster.faction_id);\n if (display === null) return null;\n return { item: key(CLS_FACTION, display), name: display, quantity: 1 };\n}\n\nfunction detachmentAsset(roster: Roster): Asset | null {\n const display = titleCaseId(roster.detachment_id);\n if (display === null) return null;\n return { item: key(CLS_DETACHMENT, display), name: display, quantity: 1 };\n}\n\nfunction battleSizeAsset(roster: Roster): Asset | null {\n if (roster.battle_size === \"strike-force\") {\n const limit = roster.points.declared_limit ?? 2000;\n const label = `Strike Force (${limit} Point limit)`;\n return { item: key(CLS_BATTLE_SIZE, label), name: label, quantity: 1 };\n }\n if (roster.battle_size === \"incursion\") {\n const limit = roster.points.declared_limit ?? 1000;\n const label = `Incursion (${limit} Point limit)`;\n return { item: key(CLS_BATTLE_SIZE, label), name: label, quantity: 1 };\n }\n return null;\n}\n\nexport const rosterizerSerializer: RosterSerializer = {\n id: \"rosterizer\",\n\n serialize(roster: Roster): string {\n const included: Asset[] = [];\n const faction = factionAsset(roster);\n if (faction) included.push(faction);\n const detachment = detachmentAsset(roster);\n if (detachment) included.push(detachment);\n const battleSize = battleSizeAsset(roster);\n if (battleSize) included.push(battleSize);\n for (const u of roster.units) included.push(unitAsset(u));\n\n const total = totalArmyPoints(roster);\n const snapshot: Asset = {\n item: key(CLS_ROSTER, CLS_ROSTER),\n name: roster.name,\n quantity: 1,\n ...(total > 0 ? { stats: pointsStat(total) } : {}),\n assets: { included },\n };\n\n const envelope: Envelope = {\n slug: \"\",\n key: \"\",\n visible: \"hidden\",\n locked: false,\n rulebook: {\n name: RULEBOOK_NAME,\n game: RULEBOOK_GAME,\n publisher: RULEBOOK_PUBLISHER,\n url: RULEBOOK_URL,\n genre: RULEBOOK_GENRE,\n },\n snapshot,\n };\n\n return prettyJson(envelope);\n },\n};\n"]}
|
|
1
|
+
{"version":3,"file":"rosterizer.js","sourceRoot":"","sources":["../../src/export/rosterizer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Rosterizer serializer — emits a Rosterizer-shaped roster JSON skeleton that\n * round-trips through {@link rosterizerAdapter}.\n *\n * The shape carries only fields the importer reads: `rulebook` (envelope),\n * `snapshot` (an `Asset` tree rooted at `Roster§Roster`), and per-unit\n * `item`/`name`/`quantity`/`stats.Points.value`/`assets.included`/`assets.traits`.\n * No `text`, `description`, `rules`, `lineage`, `_layers`, `classIdentity`,\n * `processed`, or `bareResourceKey` ever appear — they aren't stored in the\n * Roster and emitting them could leak prose.\n *\n * Faction and detachment display names come from {@link titleCaseId} — the\n * Roster doesn't carry the source's raw faction name, so we reconstruct it\n * from the kebab-case id. Same lossy hop as the NewRecruit JSON serializer.\n *\n * @packageDocumentation\n */\nimport type { Roster, RosterUnit, RosterWargear } from \"../import/types.js\";\nimport { prettyJson, titleCaseId, totalArmyPoints } from \"./helpers.js\";\nimport type { RosterSerializer } from \"./serializer.js\";\n\n// Mirror the importer's constants (kept inline rather than imported so the\n// exporter stays decoupled — the seams are the `item` keys themselves).\nconst CLS_ROSTER = \"Roster\";\nconst CLS_FACTION = \"Faction\";\nconst CLS_DETACHMENT = \"Detachment\";\nconst CLS_UNIT = \"Unit\";\nconst CLS_WEAPON = \"Weapon\";\nconst CLS_ENHANCEMENT = \"Enhancement\";\nconst CLS_BATTLE_SIZE = \"Battle Size\";\nconst CLS_TRAIT = \"Trait\";\nconst DSG_WARLORD = \"Warlord\";\n\nconst RULEBOOK_NAME = \"40kdc\";\nconst RULEBOOK_GAME = \"Warhammer 40,000\";\nconst RULEBOOK_PUBLISHER = \"Alpaca Software\";\nconst RULEBOOK_URL = \"https://40kdc.dev\";\nconst RULEBOOK_GENRE = \"wargame\";\n\ninterface Asset {\n item: string;\n name?: string;\n quantity?: number;\n stats?: Record<string, { value: number }>;\n assets?: {\n included?: Asset[];\n traits?: Asset[];\n };\n}\n\ninterface Envelope {\n slug: string;\n key: string;\n visible: \"hidden\" | \"public\" | \"friends\";\n locked: boolean;\n rulebook: {\n name: string;\n game: string;\n publisher: string;\n url: string;\n genre: string;\n };\n snapshot: Asset;\n}\n\nfunction key(classification: string, designation: string): string {\n return `${classification}§${designation}`; // §\n}\n\nfunction pointsStat(value: number | null | undefined): Record<string, { value: number }> | undefined {\n if (value === null || value === undefined) return undefined;\n return { Points: { value } };\n}\n\nfunction wargearAsset(w: RosterWargear): Asset {\n return {\n item: key(CLS_WEAPON, w.ref.raw_name),\n name: w.ref.raw_name,\n quantity: w.count,\n };\n}\n\nfunction enhancementAsset(u: RosterUnit): Asset | null {\n if (!u.enhancement) return null;\n return {\n item: key(CLS_ENHANCEMENT, u.enhancement.raw_name),\n name: u.enhancement.raw_name,\n quantity: 1,\n ...(u.enhancement_points !== null\n ? { stats: pointsStat(u.enhancement_points) }\n : {}),\n };\n}\n\nfunction warlordTraitAsset(): Asset {\n return {\n item: key(CLS_TRAIT, DSG_WARLORD),\n name: DSG_WARLORD,\n quantity: 1,\n };\n}\n\nfunction unitAsset(u: RosterUnit): Asset {\n const included: Asset[] = [];\n const enh = enhancementAsset(u);\n if (enh !== null) included.push(enh);\n for (const w of u.wargear) included.push(wargearAsset(w));\n\n const traits: Asset[] = [];\n if (u.is_warlord) traits.push(warlordTraitAsset());\n\n const asset: Asset = {\n item: key(CLS_UNIT, u.ref.raw_name),\n name: u.ref.raw_name,\n quantity: u.model_count,\n };\n const stats = pointsStat(u.points);\n if (stats !== undefined) asset.stats = stats;\n if (included.length > 0 || traits.length > 0) {\n asset.assets = {};\n if (included.length > 0) asset.assets.included = included;\n if (traits.length > 0) asset.assets.traits = traits;\n }\n return asset;\n}\n\nfunction factionAsset(roster: Roster): Asset | null {\n const display = titleCaseId(roster.faction_id);\n if (display === null) return null;\n return { item: key(CLS_FACTION, display), name: display, quantity: 1 };\n}\n\nfunction detachmentAssets(roster: Roster): Asset[] {\n return roster.detachments.map((d) => {\n const display = titleCaseId(d.ref.id) ?? d.ref.raw_name;\n return { item: key(CLS_DETACHMENT, display), name: display, quantity: 1 };\n });\n}\n\nfunction battleSizeAsset(roster: Roster): Asset | null {\n if (roster.battle_size === \"strike-force\") {\n const limit = roster.points.declared_limit ?? 2000;\n const label = `Strike Force (${limit} Point limit)`;\n return { item: key(CLS_BATTLE_SIZE, label), name: label, quantity: 1 };\n }\n if (roster.battle_size === \"incursion\") {\n const limit = roster.points.declared_limit ?? 1000;\n const label = `Incursion (${limit} Point limit)`;\n return { item: key(CLS_BATTLE_SIZE, label), name: label, quantity: 1 };\n }\n return null;\n}\n\nexport const rosterizerSerializer: RosterSerializer = {\n id: \"rosterizer\",\n\n serialize(roster: Roster): string {\n const included: Asset[] = [];\n const faction = factionAsset(roster);\n if (faction) included.push(faction);\n for (const detachment of detachmentAssets(roster)) included.push(detachment);\n const battleSize = battleSizeAsset(roster);\n if (battleSize) included.push(battleSize);\n for (const u of roster.units) included.push(unitAsset(u));\n\n const total = totalArmyPoints(roster);\n const snapshot: Asset = {\n item: key(CLS_ROSTER, CLS_ROSTER),\n name: roster.name,\n quantity: 1,\n ...(total > 0 ? { stats: pointsStat(total) } : {}),\n assets: { included },\n };\n\n const envelope: Envelope = {\n slug: \"\",\n key: \"\",\n visible: \"hidden\",\n locked: false,\n rulebook: {\n name: RULEBOOK_NAME,\n game: RULEBOOK_GAME,\n publisher: RULEBOOK_PUBLISHER,\n url: RULEBOOK_URL,\n genre: RULEBOOK_GENRE,\n },\n snapshot,\n };\n\n return prettyJson(envelope);\n },\n};\n"]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface ExtractedStratagem {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
type: string | null;
|
|
5
|
+
cp_cost: number | null;
|
|
6
|
+
phases: string[];
|
|
7
|
+
player_turn: string | null;
|
|
8
|
+
timing: string | null;
|
|
9
|
+
flags: string[];
|
|
10
|
+
}
|
|
11
|
+
export interface ExtractedEnhancement {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
cost: null;
|
|
15
|
+
flags: string[];
|
|
16
|
+
}
|
|
17
|
+
export interface ExtractedDetachment {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
detachment_rule_name: string | null;
|
|
21
|
+
stratagems: ExtractedStratagem[];
|
|
22
|
+
enhancements: ExtractedEnhancement[];
|
|
23
|
+
flags: string[];
|
|
24
|
+
}
|
|
25
|
+
export interface PackExtract {
|
|
26
|
+
faction_id: string;
|
|
27
|
+
source_pack: string;
|
|
28
|
+
detachments: ExtractedDetachment[];
|
|
29
|
+
}
|
|
30
|
+
/** A positioned text block from `pdftotext -bbox-layout`. `gy` is page-globalised y. */
|
|
31
|
+
interface Block {
|
|
32
|
+
x: number;
|
|
33
|
+
gy: number;
|
|
34
|
+
text: string;
|
|
35
|
+
}
|
|
36
|
+
/** Title/caps name → kebab id, matching existing entity-id conventions. */
|
|
37
|
+
export declare function slug(name: string): string;
|
|
38
|
+
/** Title-case a pack ALL-CAPS header for storage as a display name. */
|
|
39
|
+
export declare function titleCase(raw: string): string;
|
|
40
|
+
/** Extract stratagems, enhancements, and rule name from a detachment's blocks. */
|
|
41
|
+
export declare function parseDetachmentBlocks(blocks: Block[], detachmentId: string): Omit<ExtractedDetachment, "id" | "name">;
|
|
42
|
+
/**
|
|
43
|
+
* Parse the pack's table of contents (first two pages) into the ordered list of
|
|
44
|
+
* detachments with their start pages. Uses non-layout extraction so the two
|
|
45
|
+
* columns don't interleave; dot leaders are private-font glyphs (non-word
|
|
46
|
+
* chars), so a TOC row is `<Name><2+ non-word><page#>`. Entries are kept only
|
|
47
|
+
* between the "Detachments" header and the first non-detachment section.
|
|
48
|
+
*/
|
|
49
|
+
export declare function parseToc(tocText: string): Array<{
|
|
50
|
+
name: string;
|
|
51
|
+
page: number;
|
|
52
|
+
section?: boolean;
|
|
53
|
+
}>;
|
|
54
|
+
export declare function extractPack(pdf: string, faction: string): PackExtract;
|
|
55
|
+
export {};
|
|
56
|
+
//# sourceMappingURL=extract-faction-pack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract-faction-pack.d.ts","sourceRoot":"","sources":["../src/extract-faction-pack.ts"],"names":[],"mappings":"AAkEA,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AACD,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,CAAC;IACX,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AACD,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,UAAU,EAAE,kBAAkB,EAAE,CAAC;IACjC,YAAY,EAAE,oBAAoB,EAAE,CAAC;IACrC,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AACD,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,mBAAmB,EAAE,CAAC;CACpC;AAED,wFAAwF;AACxF,UAAU,KAAK;IACb,CAAC,EAAE,MAAM,CAAC;IACV,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,2EAA2E;AAC3E,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOzC;AAED,uEAAuE;AACvE,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQ7C;AAgFD,kFAAkF;AAClF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC,mBAAmB,EAAE,IAAI,GAAG,MAAM,CAAC,CAsGrH;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAsBlG;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW,CAoCrE"}
|