@alpaca-software/40kdc-data 0.1.1 → 0.1.3
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 +2 -0
- package/dist/abilities-resolver/index.d.ts +9 -0
- package/dist/abilities-resolver/index.d.ts.map +1 -0
- package/dist/abilities-resolver/index.js +9 -0
- package/dist/abilities-resolver/index.js.map +1 -0
- package/dist/abilities-resolver/resolver.d.ts +73 -0
- package/dist/abilities-resolver/resolver.d.ts.map +1 -0
- package/dist/abilities-resolver/resolver.js +142 -0
- package/dist/abilities-resolver/resolver.js.map +1 -0
- package/dist/audit-coverage.d.ts +78 -0
- package/dist/audit-coverage.d.ts.map +1 -0
- package/dist/audit-coverage.js +341 -0
- package/dist/audit-coverage.js.map +1 -0
- package/dist/author-batch.d.ts +147 -0
- package/dist/author-batch.d.ts.map +1 -0
- package/dist/author-batch.js +675 -0
- package/dist/author-batch.js.map +1 -0
- package/dist/author-input.d.ts +37 -0
- package/dist/author-input.d.ts.map +1 -0
- package/dist/author-input.js +162 -0
- package/dist/author-input.js.map +1 -0
- package/dist/bundle-schemas.d.ts +1 -0
- package/dist/bundle-schemas.d.ts.map +1 -0
- package/dist/bundle-schemas.js +1 -0
- package/dist/bundle-schemas.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +9 -0
- package/dist/cli.js.map +1 -0
- package/dist/codegen-data.d.ts +1 -0
- package/dist/codegen-data.d.ts.map +1 -0
- package/dist/codegen-data.js +2 -0
- package/dist/codegen-data.js.map +1 -0
- package/dist/commands/import.d.ts +1 -0
- package/dist/commands/import.d.ts.map +1 -0
- package/dist/commands/import.js +1 -0
- package/dist/commands/import.js.map +1 -0
- package/dist/commands/translate.d.ts +1 -0
- package/dist/commands/translate.d.ts.map +1 -0
- package/dist/commands/translate.js +10 -4
- package/dist/commands/translate.js.map +1 -0
- package/dist/commands/validate-all.d.ts +1 -0
- package/dist/commands/validate-all.d.ts.map +1 -0
- package/dist/commands/validate-all.js +1 -0
- package/dist/commands/validate-all.js.map +1 -0
- package/dist/commands/validate-core.d.ts +1 -0
- package/dist/commands/validate-core.d.ts.map +1 -0
- package/dist/commands/validate-core.js +1 -0
- package/dist/commands/validate-core.js.map +1 -0
- package/dist/commands/validate-enrichment.d.ts +1 -0
- package/dist/commands/validate-enrichment.d.ts.map +1 -0
- package/dist/commands/validate-enrichment.js +1 -0
- package/dist/commands/validate-enrichment.js.map +1 -0
- package/dist/convert-faction.d.ts +1 -0
- package/dist/convert-faction.d.ts.map +1 -0
- package/dist/convert-faction.js +1 -0
- package/dist/convert-faction.js.map +1 -0
- package/dist/converters/configs/adepta-sororitas.d.ts +1 -0
- package/dist/converters/configs/adepta-sororitas.d.ts.map +1 -0
- package/dist/converters/configs/adepta-sororitas.js +1 -0
- package/dist/converters/configs/adepta-sororitas.js.map +1 -0
- package/dist/converters/configs/adeptus-astartes.d.ts +1 -0
- package/dist/converters/configs/adeptus-astartes.d.ts.map +1 -0
- package/dist/converters/configs/adeptus-astartes.js +1 -0
- package/dist/converters/configs/adeptus-astartes.js.map +1 -0
- package/dist/converters/configs/adeptus-custodes.d.ts +1 -0
- package/dist/converters/configs/adeptus-custodes.d.ts.map +1 -0
- package/dist/converters/configs/adeptus-custodes.js +1 -0
- package/dist/converters/configs/adeptus-custodes.js.map +1 -0
- package/dist/converters/configs/adeptus-mechanicus.d.ts +1 -0
- package/dist/converters/configs/adeptus-mechanicus.d.ts.map +1 -0
- package/dist/converters/configs/adeptus-mechanicus.js +1 -0
- package/dist/converters/configs/adeptus-mechanicus.js.map +1 -0
- package/dist/converters/configs/aeldari.d.ts +1 -0
- package/dist/converters/configs/aeldari.d.ts.map +1 -0
- package/dist/converters/configs/aeldari.js +1 -0
- package/dist/converters/configs/aeldari.js.map +1 -0
- package/dist/converters/configs/agents-of-the-imperium.d.ts +1 -0
- package/dist/converters/configs/agents-of-the-imperium.d.ts.map +1 -0
- package/dist/converters/configs/agents-of-the-imperium.js +1 -0
- package/dist/converters/configs/agents-of-the-imperium.js.map +1 -0
- package/dist/converters/configs/astra-militarum.d.ts +1 -0
- package/dist/converters/configs/astra-militarum.d.ts.map +1 -0
- package/dist/converters/configs/astra-militarum.js +1 -0
- package/dist/converters/configs/astra-militarum.js.map +1 -0
- package/dist/converters/configs/black-templars.d.ts +1 -0
- package/dist/converters/configs/black-templars.d.ts.map +1 -0
- package/dist/converters/configs/black-templars.js +1 -0
- package/dist/converters/configs/black-templars.js.map +1 -0
- package/dist/converters/configs/blood-angels.d.ts +1 -0
- package/dist/converters/configs/blood-angels.d.ts.map +1 -0
- package/dist/converters/configs/blood-angels.js +1 -0
- package/dist/converters/configs/blood-angels.js.map +1 -0
- package/dist/converters/configs/chaos-daemons.d.ts +1 -0
- package/dist/converters/configs/chaos-daemons.d.ts.map +1 -0
- package/dist/converters/configs/chaos-daemons.js +1 -0
- package/dist/converters/configs/chaos-daemons.js.map +1 -0
- package/dist/converters/configs/chaos-knights.d.ts +1 -0
- package/dist/converters/configs/chaos-knights.d.ts.map +1 -0
- package/dist/converters/configs/chaos-knights.js +1 -0
- package/dist/converters/configs/chaos-knights.js.map +1 -0
- package/dist/converters/configs/chaos-space-marines.d.ts +1 -0
- package/dist/converters/configs/chaos-space-marines.d.ts.map +1 -0
- package/dist/converters/configs/chaos-space-marines.js +1 -0
- package/dist/converters/configs/chaos-space-marines.js.map +1 -0
- package/dist/converters/configs/crimson-fists.d.ts +1 -0
- package/dist/converters/configs/crimson-fists.d.ts.map +1 -0
- package/dist/converters/configs/crimson-fists.js +1 -0
- package/dist/converters/configs/crimson-fists.js.map +1 -0
- package/dist/converters/configs/dark-angels.d.ts +1 -0
- package/dist/converters/configs/dark-angels.d.ts.map +1 -0
- package/dist/converters/configs/dark-angels.js +1 -0
- package/dist/converters/configs/dark-angels.js.map +1 -0
- package/dist/converters/configs/death-guard.d.ts +1 -0
- package/dist/converters/configs/death-guard.d.ts.map +1 -0
- package/dist/converters/configs/death-guard.js +1 -0
- package/dist/converters/configs/death-guard.js.map +1 -0
- package/dist/converters/configs/deathwatch.d.ts +1 -0
- package/dist/converters/configs/deathwatch.d.ts.map +1 -0
- package/dist/converters/configs/deathwatch.js +1 -0
- package/dist/converters/configs/deathwatch.js.map +1 -0
- package/dist/converters/configs/drukhari.d.ts +1 -0
- package/dist/converters/configs/drukhari.d.ts.map +1 -0
- package/dist/converters/configs/drukhari.js +1 -0
- package/dist/converters/configs/drukhari.js.map +1 -0
- package/dist/converters/configs/emperors-children.d.ts +1 -0
- package/dist/converters/configs/emperors-children.d.ts.map +1 -0
- package/dist/converters/configs/emperors-children.js +1 -0
- package/dist/converters/configs/emperors-children.js.map +1 -0
- package/dist/converters/configs/genestealer-cults.d.ts +1 -0
- package/dist/converters/configs/genestealer-cults.d.ts.map +1 -0
- package/dist/converters/configs/genestealer-cults.js +1 -0
- package/dist/converters/configs/genestealer-cults.js.map +1 -0
- package/dist/converters/configs/grey-knights.d.ts +1 -0
- package/dist/converters/configs/grey-knights.d.ts.map +1 -0
- package/dist/converters/configs/grey-knights.js +1 -0
- package/dist/converters/configs/grey-knights.js.map +1 -0
- package/dist/converters/configs/imperial-fists.d.ts +1 -0
- package/dist/converters/configs/imperial-fists.d.ts.map +1 -0
- package/dist/converters/configs/imperial-fists.js +1 -0
- package/dist/converters/configs/imperial-fists.js.map +1 -0
- package/dist/converters/configs/imperial-knights.d.ts +1 -0
- package/dist/converters/configs/imperial-knights.d.ts.map +1 -0
- package/dist/converters/configs/imperial-knights.js +1 -0
- package/dist/converters/configs/imperial-knights.js.map +1 -0
- package/dist/converters/configs/iron-hands.d.ts +1 -0
- package/dist/converters/configs/iron-hands.d.ts.map +1 -0
- package/dist/converters/configs/iron-hands.js +1 -0
- package/dist/converters/configs/iron-hands.js.map +1 -0
- package/dist/converters/configs/leagues-of-votann.d.ts +1 -0
- package/dist/converters/configs/leagues-of-votann.d.ts.map +1 -0
- package/dist/converters/configs/leagues-of-votann.js +1 -0
- package/dist/converters/configs/leagues-of-votann.js.map +1 -0
- package/dist/converters/configs/necrons.d.ts +1 -0
- package/dist/converters/configs/necrons.d.ts.map +1 -0
- package/dist/converters/configs/necrons.js +1 -0
- package/dist/converters/configs/necrons.js.map +1 -0
- package/dist/converters/configs/orks.d.ts +1 -0
- package/dist/converters/configs/orks.d.ts.map +1 -0
- package/dist/converters/configs/orks.js +1 -0
- package/dist/converters/configs/orks.js.map +1 -0
- package/dist/converters/configs/raven-guard.d.ts +1 -0
- package/dist/converters/configs/raven-guard.d.ts.map +1 -0
- package/dist/converters/configs/raven-guard.js +1 -0
- package/dist/converters/configs/raven-guard.js.map +1 -0
- package/dist/converters/configs/salamanders.d.ts +1 -0
- package/dist/converters/configs/salamanders.d.ts.map +1 -0
- package/dist/converters/configs/salamanders.js +1 -0
- package/dist/converters/configs/salamanders.js.map +1 -0
- package/dist/converters/configs/space-wolves.d.ts +1 -0
- package/dist/converters/configs/space-wolves.d.ts.map +1 -0
- package/dist/converters/configs/space-wolves.js +1 -0
- package/dist/converters/configs/space-wolves.js.map +1 -0
- package/dist/converters/configs/tau-empire.d.ts +1 -0
- package/dist/converters/configs/tau-empire.d.ts.map +1 -0
- package/dist/converters/configs/tau-empire.js +1 -0
- package/dist/converters/configs/tau-empire.js.map +1 -0
- package/dist/converters/configs/thousand-sons.d.ts +1 -0
- package/dist/converters/configs/thousand-sons.d.ts.map +1 -0
- package/dist/converters/configs/thousand-sons.js +1 -0
- package/dist/converters/configs/thousand-sons.js.map +1 -0
- package/dist/converters/configs/tyranids.d.ts +1 -0
- package/dist/converters/configs/tyranids.d.ts.map +1 -0
- package/dist/converters/configs/tyranids.js +1 -0
- package/dist/converters/configs/tyranids.js.map +1 -0
- package/dist/converters/configs/ultramarines.d.ts +1 -0
- package/dist/converters/configs/ultramarines.d.ts.map +1 -0
- package/dist/converters/configs/ultramarines.js +1 -0
- package/dist/converters/configs/ultramarines.js.map +1 -0
- package/dist/converters/configs/white-scars.d.ts +1 -0
- package/dist/converters/configs/white-scars.d.ts.map +1 -0
- package/dist/converters/configs/white-scars.js +1 -0
- package/dist/converters/configs/white-scars.js.map +1 -0
- package/dist/converters/configs/world-eaters.d.ts +1 -0
- package/dist/converters/configs/world-eaters.d.ts.map +1 -0
- package/dist/converters/configs/world-eaters.js +1 -0
- package/dist/converters/configs/world-eaters.js.map +1 -0
- package/dist/converters/faction-config.d.ts +1 -0
- package/dist/converters/faction-config.d.ts.map +1 -0
- package/dist/converters/faction-config.js +1 -0
- package/dist/converters/faction-config.js.map +1 -0
- package/dist/converters/id-generator.d.ts +1 -0
- package/dist/converters/id-generator.d.ts.map +1 -0
- package/dist/converters/id-generator.js +1 -0
- package/dist/converters/id-generator.js.map +1 -0
- package/dist/converters/keyword-filter.d.ts +1 -0
- package/dist/converters/keyword-filter.d.ts.map +1 -0
- package/dist/converters/keyword-filter.js +1 -0
- package/dist/converters/keyword-filter.js.map +1 -0
- package/dist/converters/stat-parser.d.ts +1 -0
- package/dist/converters/stat-parser.d.ts.map +1 -0
- package/dist/converters/stat-parser.js +1 -0
- package/dist/converters/stat-parser.js.map +1 -0
- package/dist/converters/view-selector.d.ts +1 -0
- package/dist/converters/view-selector.d.ts.map +1 -0
- package/dist/converters/view-selector.js +1 -0
- package/dist/converters/view-selector.js.map +1 -0
- package/dist/converters/weapon-dedup.d.ts +1 -0
- package/dist/converters/weapon-dedup.d.ts.map +1 -0
- package/dist/converters/weapon-dedup.js +1 -0
- package/dist/converters/weapon-dedup.js.map +1 -0
- package/dist/cruncher/attribution.d.ts +66 -0
- package/dist/cruncher/attribution.d.ts.map +1 -0
- package/dist/cruncher/attribution.js +88 -0
- package/dist/cruncher/attribution.js.map +1 -0
- package/dist/cruncher/buffs.d.ts +206 -0
- package/dist/cruncher/buffs.d.ts.map +1 -0
- package/dist/cruncher/buffs.js +150 -0
- package/dist/cruncher/buffs.js.map +1 -0
- package/dist/cruncher/engine.d.ts +50 -0
- package/dist/cruncher/engine.d.ts.map +1 -0
- package/dist/cruncher/engine.js +312 -0
- package/dist/cruncher/engine.js.map +1 -0
- package/dist/cruncher/from-dsl.d.ts +101 -0
- package/dist/cruncher/from-dsl.d.ts.map +1 -0
- package/dist/cruncher/from-dsl.js +968 -0
- package/dist/cruncher/from-dsl.js.map +1 -0
- package/dist/cruncher/from-keyword.d.ts +35 -0
- package/dist/cruncher/from-keyword.d.ts.map +1 -0
- package/dist/cruncher/from-keyword.js +159 -0
- package/dist/cruncher/from-keyword.js.map +1 -0
- package/dist/cruncher/get-buffs.d.ts +12 -0
- package/dist/cruncher/get-buffs.d.ts.map +1 -0
- package/dist/cruncher/get-buffs.js +7 -0
- package/dist/cruncher/get-buffs.js.map +1 -0
- package/dist/cruncher/index.d.ts +12 -0
- package/dist/cruncher/index.d.ts.map +1 -0
- package/dist/cruncher/index.js +12 -0
- package/dist/cruncher/index.js.map +1 -0
- package/dist/data/bundle.generated.d.ts +1 -0
- package/dist/data/bundle.generated.d.ts.map +1 -0
- package/dist/data/bundle.generated.js +2 -1
- package/dist/data/bundle.generated.js.map +1 -0
- package/dist/data/collection.d.ts +10 -0
- package/dist/data/collection.d.ts.map +1 -0
- package/dist/data/collection.js +15 -0
- package/dist/data/collection.js.map +1 -0
- package/dist/data/dataset.d.ts +132 -2
- package/dist/data/dataset.d.ts.map +1 -0
- package/dist/data/dataset.js +248 -1
- package/dist/data/dataset.js.map +1 -0
- package/dist/data/entities.d.ts +67 -2
- package/dist/data/entities.d.ts.map +1 -0
- package/dist/data/entities.js +122 -0
- package/dist/data/entities.js.map +1 -0
- package/dist/data/index.d.ts +10 -1
- package/dist/data/index.d.ts.map +1 -0
- package/dist/data/index.js +14 -1
- package/dist/data/index.js.map +1 -0
- package/dist/data/normalize.d.ts +1 -0
- package/dist/data/normalize.d.ts.map +1 -0
- package/dist/data/normalize.js +1 -0
- package/dist/data/normalize.js.map +1 -0
- package/dist/data/roster-resolve.d.ts +58 -0
- package/dist/data/roster-resolve.d.ts.map +1 -0
- package/dist/data/roster-resolve.js +82 -0
- package/dist/data/roster-resolve.js.map +1 -0
- package/dist/data/types.d.ts +4 -1
- package/dist/data/types.d.ts.map +1 -0
- package/dist/data/types.js +2 -0
- package/dist/data/types.js.map +1 -0
- package/dist/export/helpers.d.ts +33 -0
- package/dist/export/helpers.d.ts.map +1 -0
- package/dist/export/helpers.js +57 -0
- package/dist/export/helpers.js.map +1 -0
- package/dist/export/index.d.ts +22 -0
- package/dist/export/index.d.ts.map +1 -0
- package/dist/export/index.js +28 -0
- package/dist/export/index.js.map +1 -0
- package/dist/export/newrecruit-json.d.ts +3 -0
- package/dist/export/newrecruit-json.d.ts.map +1 -0
- package/dist/export/newrecruit-json.js +140 -0
- package/dist/export/newrecruit-json.js.map +1 -0
- package/dist/export/newrecruit-simple.d.ts +3 -0
- package/dist/export/newrecruit-simple.d.ts.map +1 -0
- package/dist/export/newrecruit-simple.js +76 -0
- package/dist/export/newrecruit-simple.js.map +1 -0
- package/dist/export/newrecruit-wtc.d.ts +4 -0
- package/dist/export/newrecruit-wtc.d.ts.map +1 -0
- package/dist/export/newrecruit-wtc.js +142 -0
- package/dist/export/newrecruit-wtc.js.map +1 -0
- package/dist/export/roster-json.d.ts +3 -0
- package/dist/export/roster-json.d.ts.map +1 -0
- package/dist/export/roster-json.js +8 -0
- package/dist/export/roster-json.js.map +1 -0
- package/dist/export/rosterizer.d.ts +3 -0
- package/dist/export/rosterizer.d.ts.map +1 -0
- package/dist/export/rosterizer.js +144 -0
- package/dist/export/rosterizer.js.map +1 -0
- package/dist/export/serializer.d.ts +27 -0
- package/dist/export/serializer.d.ts.map +1 -0
- package/dist/export/serializer.js +2 -0
- package/dist/export/serializer.js.map +1 -0
- package/dist/gen-conformance.d.ts +1 -0
- package/dist/gen-conformance.d.ts.map +1 -0
- package/dist/gen-conformance.js +274 -12
- package/dist/gen-conformance.js.map +1 -0
- package/dist/generated.d.ts +194 -118
- package/dist/generated.d.ts.map +1 -0
- package/dist/generated.js +1 -0
- package/dist/generated.js.map +1 -0
- package/dist/import/adapter.d.ts +4 -3
- package/dist/import/adapter.d.ts.map +1 -0
- package/dist/import/adapter.js +1 -0
- package/dist/import/adapter.js.map +1 -0
- package/dist/import/decode.d.ts +1 -0
- package/dist/import/decode.d.ts.map +1 -0
- package/dist/import/decode.js +1 -0
- package/dist/import/decode.js.map +1 -0
- package/dist/import/gw.d.ts +69 -0
- package/dist/import/gw.d.ts.map +1 -0
- package/dist/import/gw.js +245 -0
- package/dist/import/gw.js.map +1 -0
- package/dist/import/import-roster.d.ts +84 -0
- package/dist/import/import-roster.d.ts.map +1 -0
- package/dist/import/import-roster.js +207 -0
- package/dist/import/import-roster.js.map +1 -0
- package/dist/import/index.d.ts +7 -3
- package/dist/import/index.d.ts.map +1 -0
- package/dist/import/index.js +5 -1
- package/dist/import/index.js.map +1 -0
- package/dist/import/listforge.d.ts +1 -0
- package/dist/import/listforge.d.ts.map +1 -0
- package/dist/import/listforge.js +22 -2
- package/dist/import/listforge.js.map +1 -0
- package/dist/import/newrecruit-json.d.ts +31 -0
- package/dist/import/newrecruit-json.d.ts.map +1 -0
- package/dist/import/newrecruit-json.js +224 -0
- package/dist/import/newrecruit-json.js.map +1 -0
- package/dist/import/newrecruit-simple.d.ts +29 -0
- package/dist/import/newrecruit-simple.d.ts.map +1 -0
- package/dist/import/newrecruit-simple.js +200 -0
- package/dist/import/newrecruit-simple.js.map +1 -0
- package/dist/import/newrecruit-text.d.ts +51 -0
- package/dist/import/newrecruit-text.d.ts.map +1 -0
- package/dist/import/newrecruit-text.js +102 -0
- package/dist/import/newrecruit-text.js.map +1 -0
- package/dist/import/newrecruit-wtc.d.ts +36 -0
- package/dist/import/newrecruit-wtc.d.ts.map +1 -0
- package/dist/import/newrecruit-wtc.js +337 -0
- package/dist/import/newrecruit-wtc.js.map +1 -0
- package/dist/import/resolve.d.ts +3 -2
- package/dist/import/resolve.d.ts.map +1 -0
- package/dist/import/resolve.js +5 -2
- package/dist/import/resolve.js.map +1 -0
- package/dist/import/rosterizer.d.ts +70 -0
- package/dist/import/rosterizer.d.ts.map +1 -0
- package/dist/import/rosterizer.js +348 -0
- package/dist/import/rosterizer.js.map +1 -0
- package/dist/import/types.d.ts +11 -1
- package/dist/import/types.d.ts.map +1 -0
- package/dist/import/types.js +1 -0
- package/dist/import/types.js.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -0
- package/dist/known-support-10e.d.ts +1 -0
- package/dist/known-support-10e.d.ts.map +1 -0
- package/dist/known-support-10e.js +1 -0
- package/dist/known-support-10e.js.map +1 -0
- package/dist/link-abilities.d.ts +41 -0
- package/dist/link-abilities.d.ts.map +1 -0
- package/dist/link-abilities.js +159 -0
- package/dist/link-abilities.js.map +1 -0
- package/dist/migrations/2026-weapon-keywords.d.ts +2 -0
- package/dist/migrations/2026-weapon-keywords.d.ts.map +1 -0
- package/dist/migrations/2026-weapon-keywords.js +247 -0
- package/dist/migrations/2026-weapon-keywords.js.map +1 -0
- package/dist/port-10e-faction.d.ts +1 -0
- package/dist/port-10e-faction.d.ts.map +1 -0
- package/dist/port-10e-faction.js +1 -0
- package/dist/port-10e-faction.js.map +1 -0
- package/dist/report.d.ts +1 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +1 -0
- package/dist/report.js.map +1 -0
- package/dist/rube-goldberg.d.ts +3 -0
- package/dist/rube-goldberg.d.ts.map +1 -0
- package/dist/rube-goldberg.js +109 -0
- package/dist/rube-goldberg.js.map +1 -0
- package/dist/runner.d.ts +38 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +492 -0
- package/dist/runner.js.map +1 -0
- package/dist/schema-loader.d.ts +1 -0
- package/dist/schema-loader.d.ts.map +1 -0
- package/dist/schema-loader.js +1 -0
- package/dist/schema-loader.js.map +1 -0
- package/dist/scrub-ip.d.ts +14 -0
- package/dist/scrub-ip.d.ts.map +1 -0
- package/dist/scrub-ip.js +88 -0
- package/dist/scrub-ip.js.map +1 -0
- package/dist/validate.d.ts +1 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +2 -0
- package/dist/validate.js.map +1 -0
- package/package.json +15 -3
- package/schemas/core/roster.schema.json +19 -4
- package/schemas/core/weapon-keyword.schema.json +31 -0
- package/schemas/core/weapon.schema.json +22 -1
- package/schemas/enrichment/ability-dsl/effect.schema.json +23 -1
- package/dist/import/import-listforge.d.ts +0 -23
- package/dist/import/import-listforge.js +0 -32
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { classifyWargearList, factionFromKeyword, inferBattleSizeRaw, splitWargearList, stripParenthetical, } from "./newrecruit-text.js";
|
|
2
|
+
const WTC_HEADER_PREFIX = "+ FACTION KEYWORD:";
|
|
3
|
+
const HEADER_FIELDS = {
|
|
4
|
+
faction: /^\+\s*FACTION KEYWORD:\s*(.+?)\s*$/i,
|
|
5
|
+
detachment: /^\+\s*DETACHMENT:\s*(.+?)\s*$/i,
|
|
6
|
+
totalPoints: /^\+\s*TOTAL ARMY POINTS:\s*(\d+)\s*pts?\s*$/i,
|
|
7
|
+
pointsLimit: /^\+\s*POINTS LIMIT:\s*(\d+)\s*pts?\s*$/i,
|
|
8
|
+
listName: /^\+\s*LIST NAME:\s*(.+?)\s*$/i,
|
|
9
|
+
};
|
|
10
|
+
/** Parse the leading `++++ ... ++++` block. Returns `null` if no header is found. */
|
|
11
|
+
function parseWtcHeader(text) {
|
|
12
|
+
const lines = text.split(/\r?\n/);
|
|
13
|
+
let faction_raw_name = null;
|
|
14
|
+
let detachment_raw_name = null;
|
|
15
|
+
let totalReported = null;
|
|
16
|
+
let pointsLimit = null;
|
|
17
|
+
let listName = null;
|
|
18
|
+
// Two `+++++…` fence lines wrap the header. Find them.
|
|
19
|
+
const fenceIndices = [];
|
|
20
|
+
for (let i = 0; i < lines.length && fenceIndices.length < 2; i += 1) {
|
|
21
|
+
if (/^\++\s*$/.test(lines[i]))
|
|
22
|
+
fenceIndices.push(i);
|
|
23
|
+
}
|
|
24
|
+
let sawFactionKeyword = false;
|
|
25
|
+
for (const line of lines) {
|
|
26
|
+
if (!line.startsWith("+"))
|
|
27
|
+
continue;
|
|
28
|
+
const factionMatch = HEADER_FIELDS.faction.exec(line);
|
|
29
|
+
if (factionMatch) {
|
|
30
|
+
faction_raw_name = factionFromKeyword(factionMatch[1]);
|
|
31
|
+
sawFactionKeyword = true;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const detMatch = HEADER_FIELDS.detachment.exec(line);
|
|
35
|
+
if (detMatch) {
|
|
36
|
+
detachment_raw_name = stripParenthetical(detMatch[1]);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const ptsMatch = HEADER_FIELDS.totalPoints.exec(line);
|
|
40
|
+
if (ptsMatch) {
|
|
41
|
+
totalReported = Number.parseInt(ptsMatch[1], 10);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const limitMatch = HEADER_FIELDS.pointsLimit.exec(line);
|
|
45
|
+
if (limitMatch) {
|
|
46
|
+
pointsLimit = Number.parseInt(limitMatch[1], 10);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const nameMatch = HEADER_FIELDS.listName.exec(line);
|
|
50
|
+
if (nameMatch) {
|
|
51
|
+
listName = nameMatch[1];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (!sawFactionKeyword)
|
|
55
|
+
return null;
|
|
56
|
+
const bodyStart = fenceIndices.length >= 2 ? fenceIndices[1] + 1 : 0;
|
|
57
|
+
// POINTS LIMIT — the round-trip-friendly companion to TOTAL ARMY POINTS —
|
|
58
|
+
// is the army's points ceiling. When the source carries only a single
|
|
59
|
+
// figure (the tournament default), fall back to it.
|
|
60
|
+
const declared_limit = pointsLimit ?? totalReported;
|
|
61
|
+
const battle_size_raw = inferBattleSizeRaw(declared_limit);
|
|
62
|
+
return {
|
|
63
|
+
header: {
|
|
64
|
+
name: listName ?? "Imported roster",
|
|
65
|
+
faction_raw_name,
|
|
66
|
+
detachment_raw_name,
|
|
67
|
+
declared_limit,
|
|
68
|
+
total_reported: totalReported,
|
|
69
|
+
battle_size_raw,
|
|
70
|
+
},
|
|
71
|
+
bodyStart,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
// --- shared body helpers ----------------------------------------------------
|
|
75
|
+
const UNIT_HEADER_COMPACT = /^(?:Char\d+:\s*)?(\d+)x\s+(.+?)\s*\(\s*(\d+)\s*pts?\s*\)\s*:\s*(.*)$/i;
|
|
76
|
+
const UNIT_HEADER_FULL = /^(?:Char\d+:\s*)?(\d+)x\s+(.+?)\s*\(\s*(\d+)\s*pts?\s*\)\s*$/i;
|
|
77
|
+
const ENHANCEMENT_LINE = /^Enhancement:\s*(.+?)\s*\(\+\s*(\d+)\s*pts?\s*\)\s*$/i;
|
|
78
|
+
const WITH_PREFIX = /^(\d+)\s+with\s+(.*)$/i;
|
|
79
|
+
const MODEL_BREAKDOWN = /^\s*•\s*(\d+)x\s+(.+?)(?:\s*\[[^\]]*\])?\s*$/u;
|
|
80
|
+
const SECTION_HEADER = /^[A-Z][A-Z0-9 \-/&]+$/; // BATTLELINE, ALLIED UNITS, etc.
|
|
81
|
+
const HEADER_LINE = /^\+/;
|
|
82
|
+
/**
|
|
83
|
+
* `N with X, Y, Z` means each of `N` models carries the same list — the weapon
|
|
84
|
+
* counts in the list multiply by `N`. Returns `{multiplier:1, list:text}` when
|
|
85
|
+
* the line has no `with` prefix.
|
|
86
|
+
*/
|
|
87
|
+
function parseWithGroup(text) {
|
|
88
|
+
const m = WITH_PREFIX.exec(text);
|
|
89
|
+
if (m) {
|
|
90
|
+
const n = Number.parseInt(m[1], 10);
|
|
91
|
+
return { multiplier: n > 0 ? n : 1, list: m[2] };
|
|
92
|
+
}
|
|
93
|
+
return { multiplier: 1, list: text };
|
|
94
|
+
}
|
|
95
|
+
function newUnit(name, displayed_pts, leading_count, is_character_prefix) {
|
|
96
|
+
return {
|
|
97
|
+
raw_name: name,
|
|
98
|
+
is_character: is_character_prefix,
|
|
99
|
+
is_warlord: false,
|
|
100
|
+
enhancement_raw_name: null,
|
|
101
|
+
displayed_pts,
|
|
102
|
+
enhancement_pts: 0,
|
|
103
|
+
model_count: leading_count > 0 ? leading_count : 1,
|
|
104
|
+
wargear: new Map(),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function addWargear(unit, items) {
|
|
108
|
+
for (const { raw_name, count } of items) {
|
|
109
|
+
unit.wargear.set(raw_name, (unit.wargear.get(raw_name) ?? 0) + count);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function applyWithGroup(unit, listText) {
|
|
113
|
+
const { multiplier, list } = parseWithGroup(listText);
|
|
114
|
+
const tokens = splitWargearList(list);
|
|
115
|
+
const cls = classifyWargearList(tokens);
|
|
116
|
+
if (cls.is_warlord)
|
|
117
|
+
unit.is_warlord = true;
|
|
118
|
+
if (cls.is_character)
|
|
119
|
+
unit.is_character = true;
|
|
120
|
+
// wtc never inlines the enhancement points in the wargear list (that's the
|
|
121
|
+
// simple format) but classifyWargearList silently absorbs it if it shows up;
|
|
122
|
+
// wtc's enhancement is always parsed off the explicit "Enhancement:" line.
|
|
123
|
+
const scaled = cls.wargear.map((w) => ({ raw_name: w.raw_name, count: w.count * multiplier }));
|
|
124
|
+
addWargear(unit, scaled);
|
|
125
|
+
}
|
|
126
|
+
function finishUnit(unit) {
|
|
127
|
+
const displayed = unit.displayed_pts;
|
|
128
|
+
const points = displayed === null ? null : displayed - unit.enhancement_pts;
|
|
129
|
+
const wargear = [];
|
|
130
|
+
for (const [raw_name, count] of unit.wargear) {
|
|
131
|
+
wargear.push({ raw_name, count });
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
raw_name: unit.raw_name,
|
|
135
|
+
is_character: unit.is_character,
|
|
136
|
+
model_count: unit.model_count,
|
|
137
|
+
points,
|
|
138
|
+
is_warlord: unit.is_warlord,
|
|
139
|
+
enhancement_raw_name: unit.enhancement_raw_name,
|
|
140
|
+
enhancement_points: unit.enhancement_raw_name === null ? null : unit.enhancement_pts,
|
|
141
|
+
wargear,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/** Compute total_computed by walking every parsed unit cost line. */
|
|
145
|
+
function computeTotal(units, enhancementPtsByIndex) {
|
|
146
|
+
let total = 0;
|
|
147
|
+
for (let i = 0; i < units.length; i += 1) {
|
|
148
|
+
total += units[i].points ?? 0;
|
|
149
|
+
total += enhancementPtsByIndex[i] ?? 0;
|
|
150
|
+
}
|
|
151
|
+
return total;
|
|
152
|
+
}
|
|
153
|
+
function attachEnhancement(unit, raw_name, pts) {
|
|
154
|
+
unit.enhancement_raw_name = raw_name.trim();
|
|
155
|
+
unit.enhancement_pts = pts;
|
|
156
|
+
}
|
|
157
|
+
// --- compact body parser ----------------------------------------------------
|
|
158
|
+
function parseCompactBody(body) {
|
|
159
|
+
const lines = body.split(/\r?\n/);
|
|
160
|
+
const units = [];
|
|
161
|
+
const enhancementPts = [];
|
|
162
|
+
let current = null;
|
|
163
|
+
const finalize = () => {
|
|
164
|
+
if (current) {
|
|
165
|
+
units.push(finishUnit(current));
|
|
166
|
+
enhancementPts.push(current.enhancement_pts);
|
|
167
|
+
current = null;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
for (const raw of lines) {
|
|
171
|
+
const line = raw.trim();
|
|
172
|
+
if (!line || HEADER_LINE.test(line) || /^\++$/.test(line))
|
|
173
|
+
continue;
|
|
174
|
+
const enhMatch = ENHANCEMENT_LINE.exec(line);
|
|
175
|
+
if (enhMatch && current) {
|
|
176
|
+
attachEnhancement(current, enhMatch[1], Number.parseInt(enhMatch[2], 10));
|
|
177
|
+
// Emit immediately so subsequent unit lines start fresh.
|
|
178
|
+
finalize();
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const unitMatch = UNIT_HEADER_COMPACT.exec(line);
|
|
182
|
+
if (unitMatch) {
|
|
183
|
+
finalize();
|
|
184
|
+
const leading_count = Number.parseInt(unitMatch[1], 10);
|
|
185
|
+
const name = unitMatch[2].trim();
|
|
186
|
+
const pts = Number.parseInt(unitMatch[3], 10);
|
|
187
|
+
const is_character_prefix = /^Char\d+:/i.test(line);
|
|
188
|
+
current = newUnit(name, pts, leading_count, is_character_prefix);
|
|
189
|
+
applyWithGroup(current, unitMatch[4]);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
finalize();
|
|
194
|
+
return { units, enhancementPts };
|
|
195
|
+
}
|
|
196
|
+
// --- full body parser -------------------------------------------------------
|
|
197
|
+
function parseFullBody(body) {
|
|
198
|
+
const lines = body.split(/\r?\n/);
|
|
199
|
+
const units = [];
|
|
200
|
+
const enhancementPts = [];
|
|
201
|
+
let current = null;
|
|
202
|
+
let breakdownModels = 0;
|
|
203
|
+
const finalize = () => {
|
|
204
|
+
if (current) {
|
|
205
|
+
if (breakdownModels > 0)
|
|
206
|
+
current.model_count = breakdownModels;
|
|
207
|
+
units.push(finishUnit(current));
|
|
208
|
+
enhancementPts.push(current.enhancement_pts);
|
|
209
|
+
current = null;
|
|
210
|
+
breakdownModels = 0;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
for (const raw of lines) {
|
|
214
|
+
const line = raw.trim();
|
|
215
|
+
if (!line || HEADER_LINE.test(line) || /^\++$/.test(line))
|
|
216
|
+
continue;
|
|
217
|
+
if (SECTION_HEADER.test(line) && !UNIT_HEADER_FULL.test(line)) {
|
|
218
|
+
finalize();
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const enhMatch = ENHANCEMENT_LINE.exec(line);
|
|
222
|
+
if (enhMatch && current) {
|
|
223
|
+
attachEnhancement(current, enhMatch[1], Number.parseInt(enhMatch[2], 10));
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const unitMatch = UNIT_HEADER_FULL.exec(line);
|
|
227
|
+
if (unitMatch) {
|
|
228
|
+
finalize();
|
|
229
|
+
const leading_count = Number.parseInt(unitMatch[1], 10);
|
|
230
|
+
const name = unitMatch[2].trim();
|
|
231
|
+
const pts = Number.parseInt(unitMatch[3], 10);
|
|
232
|
+
const is_character_prefix = /^Char\d+:/i.test(line);
|
|
233
|
+
current = newUnit(name, pts, leading_count, is_character_prefix);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const breakdown = MODEL_BREAKDOWN.exec(raw);
|
|
237
|
+
if (breakdown && current) {
|
|
238
|
+
breakdownModels += Number.parseInt(breakdown[1], 10);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (WITH_PREFIX.test(line) && current) {
|
|
242
|
+
applyWithGroup(current, line);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
finalize();
|
|
247
|
+
return { units, enhancementPts };
|
|
248
|
+
}
|
|
249
|
+
// --- multi-force detection --------------------------------------------------
|
|
250
|
+
/** Heuristic for `multi_force`: are there units with "ALLIED" decorating
|
|
251
|
+
* the body? wtc-full has an explicit `ALLIED UNITS` section header; compact
|
|
252
|
+
* has no section markers but the user-facing summary header counts every unit
|
|
253
|
+
* together, so detect from explicit section presence. */
|
|
254
|
+
function detectMultiForce(text, format) {
|
|
255
|
+
if (format === "wtc-full") {
|
|
256
|
+
return /^ALLIED UNITS\s*$/im.test(text);
|
|
257
|
+
}
|
|
258
|
+
// wtc-compact has no section header. Multi-force surfaces only via the
|
|
259
|
+
// primary-faction summary; assume single-force unless we add a richer marker.
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
// --- adapters ---------------------------------------------------------------
|
|
263
|
+
function isWtcText(decoded) {
|
|
264
|
+
if (typeof decoded !== "string")
|
|
265
|
+
return null;
|
|
266
|
+
// Both wtc formats begin with the FACTION KEYWORD header line (possibly
|
|
267
|
+
// after some leading whitespace/fence characters).
|
|
268
|
+
if (!decoded.includes(WTC_HEADER_PREFIX))
|
|
269
|
+
return null;
|
|
270
|
+
return decoded;
|
|
271
|
+
}
|
|
272
|
+
/** Distinguishes wtc-full from wtc-compact: full has a line starting with
|
|
273
|
+
* `\d+ with ` at the start of a body line (compact only puts `N with` after
|
|
274
|
+
* `:` on the same line as the unit header). */
|
|
275
|
+
function isFullFormat(text) {
|
|
276
|
+
return /^[\t ]*\d+\s+with\b/m.test(text);
|
|
277
|
+
}
|
|
278
|
+
/** `•`-prefixed body lines. wtc-full uses them for per-model breakdowns; the GW
|
|
279
|
+
* app format uses them for every wargear entry. wtc-compact never emits them,
|
|
280
|
+
* so it's the one matcher that must exclude them to stay disjoint from GW. */
|
|
281
|
+
function hasBullets(text) {
|
|
282
|
+
return /^[\t ]*•/mu.test(text);
|
|
283
|
+
}
|
|
284
|
+
function parseWith(text, format) {
|
|
285
|
+
const parsed = parseWtcHeader(text);
|
|
286
|
+
if (!parsed) {
|
|
287
|
+
throw new Error(`${format}: missing "+ FACTION KEYWORD:" header`);
|
|
288
|
+
}
|
|
289
|
+
const { header, bodyStart } = parsed;
|
|
290
|
+
const body = text.split(/\r?\n/).slice(bodyStart).join("\n");
|
|
291
|
+
const { units, enhancementPts } = format === "wtc-full" ? parseFullBody(body) : parseCompactBody(body);
|
|
292
|
+
return {
|
|
293
|
+
name: header.name,
|
|
294
|
+
generated_by: null,
|
|
295
|
+
faction_raw_name: header.faction_raw_name,
|
|
296
|
+
detachment_raw_name: header.detachment_raw_name,
|
|
297
|
+
battle_size_raw: header.battle_size_raw,
|
|
298
|
+
declared_limit: header.declared_limit,
|
|
299
|
+
total_reported: header.total_reported,
|
|
300
|
+
total_computed: computeTotal(units, enhancementPts),
|
|
301
|
+
units,
|
|
302
|
+
multi_force: detectMultiForce(text, format),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
export const newRecruitWtcCompactAdapter = {
|
|
306
|
+
id: "newrecruit-wtc-compact",
|
|
307
|
+
matches(decoded) {
|
|
308
|
+
const text = isWtcText(decoded);
|
|
309
|
+
if (text === null)
|
|
310
|
+
return false;
|
|
311
|
+
// wtc-compact has no `N with` lines (that's wtc-full) and no `•` bullets
|
|
312
|
+
// (that's the GW app format) — excluding both keeps the matcher disjoint.
|
|
313
|
+
return !isFullFormat(text) && !hasBullets(text);
|
|
314
|
+
},
|
|
315
|
+
parse(decoded) {
|
|
316
|
+
const text = isWtcText(decoded);
|
|
317
|
+
if (text === null)
|
|
318
|
+
throw new Error("newrecruit-wtc-compact: input is not a string");
|
|
319
|
+
return parseWith(text, "wtc-compact");
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
export const newRecruitWtcFullAdapter = {
|
|
323
|
+
id: "newrecruit-wtc-full",
|
|
324
|
+
matches(decoded) {
|
|
325
|
+
const text = isWtcText(decoded);
|
|
326
|
+
if (text === null)
|
|
327
|
+
return false;
|
|
328
|
+
return isFullFormat(text);
|
|
329
|
+
},
|
|
330
|
+
parse(decoded) {
|
|
331
|
+
const text = isWtcText(decoded);
|
|
332
|
+
if (text === null)
|
|
333
|
+
throw new Error("newrecruit-wtc-full: input is not a string");
|
|
334
|
+
return parseWith(text, "wtc-full");
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
//# sourceMappingURL=newrecruit-wtc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"newrecruit-wtc.js","sourceRoot":"","sources":["../../src/import/newrecruit-wtc.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * NewRecruit \"wtc-compact\" and \"wtc-full\" text adapters.\n *\n * Both formats open with a `++++++++` summary header carrying FACTION KEYWORD,\n * DETACHMENT, TOTAL ARMY POINTS, WARLORD, ENHANCEMENT(s), NUMBER OF UNITS, and\n * SECONDARY tournament-objective shorthand. The body diverges:\n *\n * - **wtc-compact** — one unit per line:\n * `[CharN: ]Nx <Unit> (P pts): <comma-separated wargear>`\n * followed optionally by `Enhancement: <Name> (+P pts)` on the next line.\n *\n * - **wtc-full** — uppercase section headers (`BATTLELINE`, `ALLIED UNITS`),\n * two-line unit blocks (`[CharN: ]Nx <Unit> (P pts)` then `N with <wargear>`),\n * `Enhancement: <Name> (+P pts)` on its own line, and per-model-type\n * breakdowns with `• Nx <ModelType>` + indented `N with <wargear>` lines.\n *\n * The {@link Roster} pivot stores units at unit granularity — per-model-type\n * wargear breakdowns and `CharN:` slot numbers aren't modelled, so this adapter\n * collapses them: the parsed unit's `model_count` is summed from the breakdown\n * and its `wargear` is the union of every loadout under it. The `WARLORD` /\n * `Houndpack Lance Character` tokens are stripped from the wargear list (and\n * set `is_warlord`/`is_character` instead) so resolution doesn't try to look\n * them up as weapons. Round-trips are at Roster level, not byte-for-byte.\n *\n * Enhancement points (`+15 pts`) are subtracted from the displayed unit total\n * so `ParsedUnit.points` is the *base* unit cost — matching the ListForge\n * convention where the unit's own cost line is base and the enhancement is a\n * sibling cost line. `total_computed` walks every cost line just like ListForge\n * (base unit pts + each enhancement pts).\n *\n * @packageDocumentation\n */\nimport type { FormatAdapter } from \"./adapter.js\";\nimport type { ParsedRoster, ParsedUnit, ParsedWargear } from \"./types.js\";\nimport {\n classifyWargearList,\n factionFromKeyword,\n inferBattleSizeRaw,\n splitWargearList,\n stripParenthetical,\n} from \"./newrecruit-text.js\";\n\nconst WTC_HEADER_PREFIX = \"+ FACTION KEYWORD:\";\n\n// --- header parsing ---------------------------------------------------------\n\ninterface WtcHeader {\n name: string;\n faction_raw_name: string | null;\n detachment_raw_name: string | null;\n declared_limit: number | null;\n total_reported: number | null;\n battle_size_raw: string | null;\n}\n\nconst HEADER_FIELDS = {\n faction: /^\\+\\s*FACTION KEYWORD:\\s*(.+?)\\s*$/i,\n detachment: /^\\+\\s*DETACHMENT:\\s*(.+?)\\s*$/i,\n totalPoints: /^\\+\\s*TOTAL ARMY POINTS:\\s*(\\d+)\\s*pts?\\s*$/i,\n pointsLimit: /^\\+\\s*POINTS LIMIT:\\s*(\\d+)\\s*pts?\\s*$/i,\n listName: /^\\+\\s*LIST NAME:\\s*(.+?)\\s*$/i,\n} as const;\n\n/** Parse the leading `++++ ... ++++` block. Returns `null` if no header is found. */\nfunction parseWtcHeader(text: string): { header: WtcHeader; bodyStart: number } | null {\n const lines = text.split(/\\r?\\n/);\n let faction_raw_name: string | null = null;\n let detachment_raw_name: string | null = null;\n let totalReported: number | null = null;\n let pointsLimit: number | null = null;\n let listName: string | null = null;\n\n // Two `+++++…` fence lines wrap the header. Find them.\n const fenceIndices: number[] = [];\n for (let i = 0; i < lines.length && fenceIndices.length < 2; i += 1) {\n if (/^\\++\\s*$/.test(lines[i])) fenceIndices.push(i);\n }\n let sawFactionKeyword = false;\n for (const line of lines) {\n if (!line.startsWith(\"+\")) continue;\n const factionMatch = HEADER_FIELDS.faction.exec(line);\n if (factionMatch) {\n faction_raw_name = factionFromKeyword(factionMatch[1]);\n sawFactionKeyword = true;\n continue;\n }\n const detMatch = HEADER_FIELDS.detachment.exec(line);\n if (detMatch) {\n detachment_raw_name = stripParenthetical(detMatch[1]);\n continue;\n }\n const ptsMatch = HEADER_FIELDS.totalPoints.exec(line);\n if (ptsMatch) {\n totalReported = Number.parseInt(ptsMatch[1], 10);\n continue;\n }\n const limitMatch = HEADER_FIELDS.pointsLimit.exec(line);\n if (limitMatch) {\n pointsLimit = Number.parseInt(limitMatch[1], 10);\n continue;\n }\n const nameMatch = HEADER_FIELDS.listName.exec(line);\n if (nameMatch) {\n listName = nameMatch[1];\n }\n }\n\n if (!sawFactionKeyword) return null;\n\n const bodyStart = fenceIndices.length >= 2 ? fenceIndices[1] + 1 : 0;\n // POINTS LIMIT — the round-trip-friendly companion to TOTAL ARMY POINTS —\n // is the army's points ceiling. When the source carries only a single\n // figure (the tournament default), fall back to it.\n const declared_limit = pointsLimit ?? totalReported;\n const battle_size_raw = inferBattleSizeRaw(declared_limit);\n\n return {\n header: {\n name: listName ?? \"Imported roster\",\n faction_raw_name,\n detachment_raw_name,\n declared_limit,\n total_reported: totalReported,\n battle_size_raw,\n },\n bodyStart,\n };\n}\n\n// --- shared body helpers ----------------------------------------------------\n\nconst UNIT_HEADER_COMPACT =\n /^(?:Char\\d+:\\s*)?(\\d+)x\\s+(.+?)\\s*\\(\\s*(\\d+)\\s*pts?\\s*\\)\\s*:\\s*(.*)$/i;\nconst UNIT_HEADER_FULL = /^(?:Char\\d+:\\s*)?(\\d+)x\\s+(.+?)\\s*\\(\\s*(\\d+)\\s*pts?\\s*\\)\\s*$/i;\nconst ENHANCEMENT_LINE =\n /^Enhancement:\\s*(.+?)\\s*\\(\\+\\s*(\\d+)\\s*pts?\\s*\\)\\s*$/i;\nconst WITH_PREFIX = /^(\\d+)\\s+with\\s+(.*)$/i;\nconst MODEL_BREAKDOWN = /^\\s*•\\s*(\\d+)x\\s+(.+?)(?:\\s*\\[[^\\]]*\\])?\\s*$/u;\nconst SECTION_HEADER = /^[A-Z][A-Z0-9 \\-/&]+$/; // BATTLELINE, ALLIED UNITS, etc.\nconst HEADER_LINE = /^\\+/;\n\n/**\n * `N with X, Y, Z` means each of `N` models carries the same list — the weapon\n * counts in the list multiply by `N`. Returns `{multiplier:1, list:text}` when\n * the line has no `with` prefix.\n */\nfunction parseWithGroup(text: string): { multiplier: number; list: string } {\n const m = WITH_PREFIX.exec(text);\n if (m) {\n const n = Number.parseInt(m[1], 10);\n return { multiplier: n > 0 ? n : 1, list: m[2] };\n }\n return { multiplier: 1, list: text };\n}\n\ninterface UnitBuilder {\n raw_name: string;\n is_character: boolean;\n is_warlord: boolean;\n enhancement_raw_name: string | null;\n /** Total displayed pts from the header line; base computed once an enhancement is known. */\n displayed_pts: number | null;\n enhancement_pts: number;\n model_count: number;\n wargear: Map<string, number>;\n}\n\nfunction newUnit(name: string, displayed_pts: number, leading_count: number, is_character_prefix: boolean): UnitBuilder {\n return {\n raw_name: name,\n is_character: is_character_prefix,\n is_warlord: false,\n enhancement_raw_name: null,\n displayed_pts,\n enhancement_pts: 0,\n model_count: leading_count > 0 ? leading_count : 1,\n wargear: new Map(),\n };\n}\n\nfunction addWargear(unit: UnitBuilder, items: ParsedWargear[]): void {\n for (const { raw_name, count } of items) {\n unit.wargear.set(raw_name, (unit.wargear.get(raw_name) ?? 0) + count);\n }\n}\n\nfunction applyWithGroup(unit: UnitBuilder, listText: string): void {\n const { multiplier, list } = parseWithGroup(listText);\n const tokens = splitWargearList(list);\n const cls = classifyWargearList(tokens);\n if (cls.is_warlord) unit.is_warlord = true;\n if (cls.is_character) unit.is_character = true;\n // wtc never inlines the enhancement points in the wargear list (that's the\n // simple format) but classifyWargearList silently absorbs it if it shows up;\n // wtc's enhancement is always parsed off the explicit \"Enhancement:\" line.\n const scaled = cls.wargear.map((w) => ({ raw_name: w.raw_name, count: w.count * multiplier }));\n addWargear(unit, scaled);\n}\n\nfunction finishUnit(unit: UnitBuilder): ParsedUnit {\n const displayed = unit.displayed_pts;\n const points = displayed === null ? null : displayed - unit.enhancement_pts;\n const wargear: ParsedWargear[] = [];\n for (const [raw_name, count] of unit.wargear) {\n wargear.push({ raw_name, count });\n }\n return {\n raw_name: unit.raw_name,\n is_character: unit.is_character,\n model_count: unit.model_count,\n points,\n is_warlord: unit.is_warlord,\n enhancement_raw_name: unit.enhancement_raw_name,\n enhancement_points: unit.enhancement_raw_name === null ? null : unit.enhancement_pts,\n wargear,\n };\n}\n\n/** Compute total_computed by walking every parsed unit cost line. */\nfunction computeTotal(units: ParsedUnit[], enhancementPtsByIndex: number[]): number {\n let total = 0;\n for (let i = 0; i < units.length; i += 1) {\n total += units[i].points ?? 0;\n total += enhancementPtsByIndex[i] ?? 0;\n }\n return total;\n}\n\nfunction attachEnhancement(unit: UnitBuilder, raw_name: string, pts: number): void {\n unit.enhancement_raw_name = raw_name.trim();\n unit.enhancement_pts = pts;\n}\n\n// --- compact body parser ----------------------------------------------------\n\nfunction parseCompactBody(body: string): { units: ParsedUnit[]; enhancementPts: number[] } {\n const lines = body.split(/\\r?\\n/);\n const units: ParsedUnit[] = [];\n const enhancementPts: number[] = [];\n let current: UnitBuilder | null = null;\n\n const finalize = (): void => {\n if (current) {\n units.push(finishUnit(current));\n enhancementPts.push(current.enhancement_pts);\n current = null;\n }\n };\n\n for (const raw of lines) {\n const line = raw.trim();\n if (!line || HEADER_LINE.test(line) || /^\\++$/.test(line)) continue;\n\n const enhMatch = ENHANCEMENT_LINE.exec(line);\n if (enhMatch && current) {\n attachEnhancement(current, enhMatch[1], Number.parseInt(enhMatch[2], 10));\n // Emit immediately so subsequent unit lines start fresh.\n finalize();\n continue;\n }\n\n const unitMatch = UNIT_HEADER_COMPACT.exec(line);\n if (unitMatch) {\n finalize();\n const leading_count = Number.parseInt(unitMatch[1], 10);\n const name = unitMatch[2].trim();\n const pts = Number.parseInt(unitMatch[3], 10);\n const is_character_prefix = /^Char\\d+:/i.test(line);\n current = newUnit(name, pts, leading_count, is_character_prefix);\n applyWithGroup(current, unitMatch[4]);\n continue;\n }\n }\n\n finalize();\n return { units, enhancementPts };\n}\n\n// --- full body parser -------------------------------------------------------\n\nfunction parseFullBody(body: string): { units: ParsedUnit[]; enhancementPts: number[] } {\n const lines = body.split(/\\r?\\n/);\n const units: ParsedUnit[] = [];\n const enhancementPts: number[] = [];\n let current: UnitBuilder | null = null;\n let breakdownModels = 0;\n\n const finalize = (): void => {\n if (current) {\n if (breakdownModels > 0) current.model_count = breakdownModels;\n units.push(finishUnit(current));\n enhancementPts.push(current.enhancement_pts);\n current = null;\n breakdownModels = 0;\n }\n };\n\n for (const raw of lines) {\n const line = raw.trim();\n if (!line || HEADER_LINE.test(line) || /^\\++$/.test(line)) continue;\n if (SECTION_HEADER.test(line) && !UNIT_HEADER_FULL.test(line)) {\n finalize();\n continue;\n }\n\n const enhMatch = ENHANCEMENT_LINE.exec(line);\n if (enhMatch && current) {\n attachEnhancement(current, enhMatch[1], Number.parseInt(enhMatch[2], 10));\n continue;\n }\n\n const unitMatch = UNIT_HEADER_FULL.exec(line);\n if (unitMatch) {\n finalize();\n const leading_count = Number.parseInt(unitMatch[1], 10);\n const name = unitMatch[2].trim();\n const pts = Number.parseInt(unitMatch[3], 10);\n const is_character_prefix = /^Char\\d+:/i.test(line);\n current = newUnit(name, pts, leading_count, is_character_prefix);\n continue;\n }\n\n const breakdown = MODEL_BREAKDOWN.exec(raw);\n if (breakdown && current) {\n breakdownModels += Number.parseInt(breakdown[1], 10);\n continue;\n }\n\n if (WITH_PREFIX.test(line) && current) {\n applyWithGroup(current, line);\n continue;\n }\n }\n\n finalize();\n return { units, enhancementPts };\n}\n\n// --- multi-force detection --------------------------------------------------\n\n/** Heuristic for `multi_force`: are there units with \"ALLIED\" decorating\n * the body? wtc-full has an explicit `ALLIED UNITS` section header; compact\n * has no section markers but the user-facing summary header counts every unit\n * together, so detect from explicit section presence. */\nfunction detectMultiForce(text: string, format: \"wtc-compact\" | \"wtc-full\"): boolean {\n if (format === \"wtc-full\") {\n return /^ALLIED UNITS\\s*$/im.test(text);\n }\n // wtc-compact has no section header. Multi-force surfaces only via the\n // primary-faction summary; assume single-force unless we add a richer marker.\n return false;\n}\n\n// --- adapters ---------------------------------------------------------------\n\nfunction isWtcText(decoded: unknown): string | null {\n if (typeof decoded !== \"string\") return null;\n // Both wtc formats begin with the FACTION KEYWORD header line (possibly\n // after some leading whitespace/fence characters).\n if (!decoded.includes(WTC_HEADER_PREFIX)) return null;\n return decoded;\n}\n\n/** Distinguishes wtc-full from wtc-compact: full has a line starting with\n * `\\d+ with ` at the start of a body line (compact only puts `N with` after\n * `:` on the same line as the unit header). */\nfunction isFullFormat(text: string): boolean {\n return /^[\\t ]*\\d+\\s+with\\b/m.test(text);\n}\n\n/** `•`-prefixed body lines. wtc-full uses them for per-model breakdowns; the GW\n * app format uses them for every wargear entry. wtc-compact never emits them,\n * so it's the one matcher that must exclude them to stay disjoint from GW. */\nfunction hasBullets(text: string): boolean {\n return /^[\\t ]*•/mu.test(text);\n}\n\nfunction parseWith(text: string, format: \"wtc-compact\" | \"wtc-full\"): ParsedRoster {\n const parsed = parseWtcHeader(text);\n if (!parsed) {\n throw new Error(`${format}: missing \"+ FACTION KEYWORD:\" header`);\n }\n const { header, bodyStart } = parsed;\n const body = text.split(/\\r?\\n/).slice(bodyStart).join(\"\\n\");\n const { units, enhancementPts } =\n format === \"wtc-full\" ? parseFullBody(body) : parseCompactBody(body);\n\n return {\n name: header.name,\n generated_by: null,\n faction_raw_name: header.faction_raw_name,\n detachment_raw_name: header.detachment_raw_name,\n battle_size_raw: header.battle_size_raw,\n declared_limit: header.declared_limit,\n total_reported: header.total_reported,\n total_computed: computeTotal(units, enhancementPts),\n units,\n multi_force: detectMultiForce(text, format),\n };\n}\n\nexport const newRecruitWtcCompactAdapter: FormatAdapter = {\n id: \"newrecruit-wtc-compact\",\n\n matches(decoded: unknown): boolean {\n const text = isWtcText(decoded);\n if (text === null) return false;\n // wtc-compact has no `N with` lines (that's wtc-full) and no `•` bullets\n // (that's the GW app format) — excluding both keeps the matcher disjoint.\n return !isFullFormat(text) && !hasBullets(text);\n },\n\n parse(decoded: unknown): ParsedRoster {\n const text = isWtcText(decoded);\n if (text === null) throw new Error(\"newrecruit-wtc-compact: input is not a string\");\n return parseWith(text, \"wtc-compact\");\n },\n};\n\nexport const newRecruitWtcFullAdapter: FormatAdapter = {\n id: \"newrecruit-wtc-full\",\n\n matches(decoded: unknown): boolean {\n const text = isWtcText(decoded);\n if (text === null) return false;\n return isFullFormat(text);\n },\n\n parse(decoded: unknown): ParsedRoster {\n const text = isWtcText(decoded);\n if (text === null) throw new Error(\"newrecruit-wtc-full: input is not a string\");\n return parseWith(text, \"wtc-full\");\n },\n};\n"]}
|
package/dist/import/resolve.d.ts
CHANGED
|
@@ -15,5 +15,6 @@
|
|
|
15
15
|
* @packageDocumentation
|
|
16
16
|
*/
|
|
17
17
|
import type { Dataset } from "../data/dataset.js";
|
|
18
|
-
import type { ParsedRoster, Roster } from "./types.js";
|
|
19
|
-
export declare function resolve(parsed: ParsedRoster, ds: Dataset): Roster;
|
|
18
|
+
import type { ParsedRoster, Roster, RosterFormat } from "./types.js";
|
|
19
|
+
export declare function resolve(parsed: ParsedRoster, ds: Dataset, format?: RosterFormat): Roster;
|
|
20
|
+
//# sourceMappingURL=resolve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/import/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,KAAK,EAIV,YAAY,EAGZ,MAAM,EACN,YAAY,EAIb,MAAM,YAAY,CAAC;AAwDpB,wBAAgB,OAAO,CACrB,MAAM,EAAE,YAAY,EACpB,EAAE,EAAE,OAAO,EACX,MAAM,GAAE,YAA0B,GACjC,MAAM,CAuER"}
|
package/dist/import/resolve.js
CHANGED
|
@@ -42,7 +42,7 @@ function mapBattleSize(raw) {
|
|
|
42
42
|
return "incursion";
|
|
43
43
|
return null;
|
|
44
44
|
}
|
|
45
|
-
export function resolve(parsed, ds) {
|
|
45
|
+
export function resolve(parsed, ds, format = "listforge") {
|
|
46
46
|
const diag = new DiagnosticsBuilder();
|
|
47
47
|
if (parsed.multi_force) {
|
|
48
48
|
diag.warn("multi-force", "Source list contains more than one faction; the primary faction was used for scoping.");
|
|
@@ -88,7 +88,7 @@ export function resolve(parsed, ds) {
|
|
|
88
88
|
}
|
|
89
89
|
return {
|
|
90
90
|
name: parsed.name,
|
|
91
|
-
source: { format
|
|
91
|
+
source: { format, generated_by: parsed.generated_by },
|
|
92
92
|
faction_id,
|
|
93
93
|
detachment_id,
|
|
94
94
|
battle_size,
|
|
@@ -124,6 +124,7 @@ function resolveUnit(parsed, faction_id, detachment_id, ds, diag) {
|
|
|
124
124
|
const enhancement = parsed.enhancement_raw_name
|
|
125
125
|
? resolveEnhancement(parsed.enhancement_raw_name, detachment_id, ds, diag)
|
|
126
126
|
: null;
|
|
127
|
+
const enhancement_points = enhancement === null ? null : parsed.enhancement_points;
|
|
127
128
|
const wargear = parsed.wargear.map((w) => {
|
|
128
129
|
const hits = ds.weapons.findAll(w.raw_name);
|
|
129
130
|
if (hits[0]) {
|
|
@@ -140,6 +141,7 @@ function resolveUnit(parsed, faction_id, detachment_id, ds, diag) {
|
|
|
140
141
|
points: parsed.points,
|
|
141
142
|
is_warlord: parsed.is_warlord,
|
|
142
143
|
enhancement,
|
|
144
|
+
enhancement_points,
|
|
143
145
|
wargear,
|
|
144
146
|
leader_attachment: null,
|
|
145
147
|
};
|
|
@@ -185,3 +187,4 @@ function inferLeaderAttachments(parsedUnits, units, ds, diag) {
|
|
|
185
187
|
diag.warn("leader-attachment-inferred", "Leader attachment was inferred from leader-attachment data and is provisional.", unit.ref.raw_name);
|
|
186
188
|
});
|
|
187
189
|
}
|
|
190
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/import/resolve.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Resolve a {@link ParsedRoster} onto 40kdc entity ids, producing a {@link Roster}.\n *\n * Resolution is lenient: a name that doesn't match a 40kdc entity yields a\n * {@link ResolvedRef} with `id: null`, `resolved: false`, and up to five\n * candidate suggestions — the roster is never dropped or rejected. Everything\n * that didn't resolve cleanly is summarised in the {@link Diagnostics} block.\n *\n * Matching reuses the dataset's own lookups ({@link Collection.find},\n * {@link Collection.findAll}, {@link Collection.byFaction}) and\n * {@link normalizeName}; there is no bespoke fuzzy matcher. Faction is resolved\n * first so unit/detachment/enhancement lookups can be scoped to it — the same\n * unit id can appear under several factions, so scoping disambiguates.\n *\n * @packageDocumentation\n */\nimport type { Dataset } from \"../data/dataset.js\";\nimport { normalizeName } from \"../data/normalize.js\";\nimport type {\n BattleSize,\n Candidate,\n Diagnostics,\n ParsedRoster,\n ParsedUnit,\n ResolvedRef,\n Roster,\n RosterFormat,\n RosterUnit,\n Warning,\n WarningCode,\n} from \"./types.js\";\n\n/** The dataset edition/dataslate stamped onto an imported roster. */\nconst ROSTER_GAME_VERSION = { edition: \"11th\", dataslate: \"pre-launch-provisional\" };\n\nconst MAX_CANDIDATES = 5;\n\ninterface NamedRecord {\n id: string;\n name: string;\n}\n\n/** Accumulates warnings and resolved/unresolved tallies during an import. */\nclass DiagnosticsBuilder {\n resolved_units = 0;\n unresolved_units = 0;\n resolved_weapons = 0;\n unresolved_weapons = 0;\n readonly warnings: Warning[] = [];\n\n warn(code: WarningCode, message: string, raw_name: string | null = null): void {\n this.warnings.push({ code, message, raw_name });\n }\n\n build(): Diagnostics {\n return {\n resolved_units: this.resolved_units,\n unresolved_units: this.unresolved_units,\n resolved_weapons: this.resolved_weapons,\n unresolved_weapons: this.unresolved_weapons,\n warnings: this.warnings,\n };\n }\n}\n\nfunction unresolved(raw_name: string, candidates: Candidate[] = []): ResolvedRef {\n return { id: null, raw_name, resolved: false, candidates };\n}\n\nfunction resolved(id: string, raw_name: string): ResolvedRef {\n return { id, raw_name, resolved: true, candidates: [] };\n}\n\nfunction toCandidates(records: readonly NamedRecord[]): Candidate[] {\n return records.slice(0, MAX_CANDIDATES).map((r) => ({ id: r.id, name: r.name }));\n}\n\n/** Map a source battle-size label to the 40kdc enum, if recognisable. */\nfunction mapBattleSize(raw: string | null): BattleSize | null {\n if (!raw) return null;\n const key = normalizeName(raw);\n if (key.includes(\"strike force\")) return \"strike-force\";\n if (key.includes(\"incursion\")) return \"incursion\";\n return null;\n}\n\nexport function resolve(\n parsed: ParsedRoster,\n ds: Dataset,\n format: RosterFormat = \"listforge\",\n): Roster {\n const diag = new DiagnosticsBuilder();\n\n if (parsed.multi_force) {\n diag.warn(\n \"multi-force\",\n \"Source list contains more than one faction; the primary faction was used for scoping.\",\n );\n }\n\n // --- Faction (resolved first so other lookups can scope to it). -----------\n let faction_id: string | null = null;\n if (parsed.faction_raw_name) {\n const hit = ds.factions.find(parsed.faction_raw_name);\n if (hit) {\n faction_id = hit.id;\n } else {\n diag.warn(\"faction-unresolved\", \"Faction name did not match any 40kdc faction.\", parsed.faction_raw_name);\n }\n }\n\n // --- Detachment (scoped to faction, then global fallback). ----------------\n let detachment_id: string | null = null;\n if (parsed.detachment_raw_name) {\n const key = normalizeName(parsed.detachment_raw_name);\n const scoped = faction_id\n ? ds.detachments.byFaction(faction_id).find((d) => normalizeName(d.name ?? \"\") === key)\n : undefined;\n const hit = scoped ?? ds.detachments.find(parsed.detachment_raw_name);\n if (hit) {\n detachment_id = hit.id;\n } else {\n diag.warn(\"detachment-unresolved\", \"Detachment name did not match any 40kdc detachment.\", parsed.detachment_raw_name);\n }\n }\n\n // --- Battle size. ---------------------------------------------------------\n const battle_size = mapBattleSize(parsed.battle_size_raw);\n if (parsed.battle_size_raw && battle_size === null) {\n diag.warn(\"battle-size-unmapped\", \"Battle size label could not be mapped.\", parsed.battle_size_raw);\n }\n\n // --- Units (and their enhancements / wargear). ----------------------------\n const units = parsed.units.map((u) => resolveUnit(u, faction_id, detachment_id, ds, diag));\n\n // --- Leader attachments (second pass: needs all resolved unit ids). -------\n inferLeaderAttachments(parsed.units, units, ds, diag);\n\n // --- Points reconciliation (reported vs computed kept distinct). ----------\n if (parsed.total_reported !== null && parsed.total_reported !== parsed.total_computed) {\n diag.warn(\n \"points-mismatch\",\n `Source-reported total (${parsed.total_reported}) differs from the sum of cost lines (${parsed.total_computed}).`,\n );\n }\n\n return {\n name: parsed.name,\n source: { format, generated_by: parsed.generated_by },\n faction_id,\n detachment_id,\n battle_size,\n points: {\n declared_limit: parsed.declared_limit,\n total_reported: parsed.total_reported,\n total_computed: parsed.total_computed,\n },\n units,\n game_version: { ...ROSTER_GAME_VERSION },\n diagnostics: diag.build(),\n };\n}\n\nfunction resolveUnit(\n parsed: ParsedUnit,\n faction_id: string | null,\n detachment_id: string | null,\n ds: Dataset,\n diag: DiagnosticsBuilder,\n): RosterUnit {\n // Prefer a faction-scoped match (the same unit id recurs across factions),\n // then fall back to a global name lookup.\n const key = normalizeName(parsed.raw_name);\n const scoped = faction_id\n ? ds.units.byFaction(faction_id).find((u) => normalizeName(u.name) === key)\n : undefined;\n const all = ds.units.findAll(parsed.raw_name);\n const hit = scoped ?? all[0];\n\n let ref: ResolvedRef;\n if (hit) {\n ref = resolved(hit.id, parsed.raw_name);\n diag.resolved_units += 1;\n } else {\n ref = unresolved(parsed.raw_name, toCandidates(all));\n diag.unresolved_units += 1;\n diag.warn(\"unit-unresolved\", \"Unit name did not match any 40kdc unit.\", parsed.raw_name);\n }\n\n const enhancement = parsed.enhancement_raw_name\n ? resolveEnhancement(parsed.enhancement_raw_name, detachment_id, ds, diag)\n : null;\n const enhancement_points = enhancement === null ? null : parsed.enhancement_points;\n\n const wargear = parsed.wargear.map((w) => {\n const hits = ds.weapons.findAll(w.raw_name);\n if (hits[0]) {\n diag.resolved_weapons += 1;\n return { ref: resolved(hits[0].id, w.raw_name), count: w.count };\n }\n diag.unresolved_weapons += 1;\n diag.warn(\"weapon-unresolved\", \"Weapon name did not match any 40kdc weapon.\", w.raw_name);\n return { ref: unresolved(w.raw_name, toCandidates(hits)), count: w.count };\n });\n\n return {\n ref,\n model_count: parsed.model_count,\n points: parsed.points,\n is_warlord: parsed.is_warlord,\n enhancement,\n enhancement_points,\n wargear,\n leader_attachment: null,\n };\n}\n\nfunction resolveEnhancement(\n raw_name: string,\n detachment_id: string | null,\n ds: Dataset,\n diag: DiagnosticsBuilder,\n): ResolvedRef {\n const key = normalizeName(raw_name);\n // Enhancements belong to a detachment, not a faction — scope by detachment_id.\n const scoped = detachment_id\n ? ds.enhancements.all.find((e) => e.detachment_id === detachment_id && normalizeName(e.name ?? \"\") === key)\n : undefined;\n const hit = scoped ?? ds.enhancements.find(raw_name);\n if (hit) {\n return resolved(hit.id, raw_name);\n }\n diag.warn(\"enhancement-unresolved\", \"Enhancement name did not match any 40kdc enhancement.\", raw_name);\n return unresolved(raw_name, toCandidates(ds.enhancements.findAll(raw_name) as NamedRecord[]));\n}\n\n/**\n * Infer leader→bodyguard attachments. The source format does not encode an\n * unambiguous attachment, so each inferred link is marked provisional: we match\n * a resolved character unit against a resolved non-character unit in the same\n * roster using the dataset's leader-attachment data.\n */\nfunction inferLeaderAttachments(\n parsedUnits: ParsedUnit[],\n units: RosterUnit[],\n ds: Dataset,\n diag: DiagnosticsBuilder,\n): void {\n const bodyguardIds = new Set(\n units.filter((u, i) => u.ref.id && !parsedUnits[i].is_character).map((u) => u.ref.id as string),\n );\n\n units.forEach((unit, i) => {\n if (!unit.ref.id || !parsedUnits[i].is_character) return;\n const leaderId = unit.ref.id;\n const attachment = ds.leaderAttachments.find((la) => la.leader_id === leaderId);\n if (!attachment) return;\n const bodyguardId = attachment.eligible_bodyguard_ids.find((id) => bodyguardIds.has(id));\n if (!bodyguardId) return;\n\n const bodyguard = units.find((u) => u.ref.id === bodyguardId);\n if (!bodyguard) return;\n\n unit.leader_attachment = {\n bodyguard_ref: resolved(bodyguardId, bodyguard.ref.raw_name),\n provisional: true,\n };\n diag.warn(\n \"leader-attachment-inferred\",\n \"Leader attachment was inferred from leader-attachment data and is provisional.\",\n unit.ref.raw_name,\n );\n });\n}\n"]}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rosterizer adapter: lower a Rosterizer roster JSON payload to a
|
|
3
|
+
* {@link ParsedRoster}.
|
|
4
|
+
*
|
|
5
|
+
* Rosterizer (https://rosterizer.com) stores a roster as a `Roster` envelope
|
|
6
|
+
* with a recursive `Asset` tree under `snapshot` (or `history.present.roster`
|
|
7
|
+
* as a fallback). Every entity — faction, detachment, unit, weapon, ability,
|
|
8
|
+
* enhancement — is an `Asset` keyed by `Classification§Designation` (e.g.
|
|
9
|
+
* `"Unit§Tactical Squad"`). Children sit under `assets.included` (game pieces)
|
|
10
|
+
* and `assets.traits` (modifiers, abilities, markers).
|
|
11
|
+
*
|
|
12
|
+
* The schema is rulebook-agnostic, so the actual `Classification` strings come
|
|
13
|
+
* from whichever Rosterizer rulebook authored the roster. The constants below
|
|
14
|
+
* encode the 40K convention used by the consortium's reference rulebook; tune
|
|
15
|
+
* them here without touching parser logic if a real export disagrees.
|
|
16
|
+
*
|
|
17
|
+
* **IP safety**: the walk reads an ALLOWLIST — `item`, `designation`, `name`,
|
|
18
|
+
* `classification`, `quantity`, `meta.points`, `stats.Points.value`,
|
|
19
|
+
* `aspects.Visibility`, and the recursive `assets.included`/`assets.traits`
|
|
20
|
+
* children. Prose-bearing fields — `text`, `description`, `rules`, ability
|
|
21
|
+
* `stats`, `_layers`, `lineage`, `processed`, `classIdentity`, `bareResourceKey`
|
|
22
|
+
* — are never touched, so the importer's output is free of copyrighted prose
|
|
23
|
+
* by construction.
|
|
24
|
+
*
|
|
25
|
+
* @packageDocumentation
|
|
26
|
+
*/
|
|
27
|
+
import type { FormatAdapter } from "./adapter.js";
|
|
28
|
+
interface RawAsset {
|
|
29
|
+
item?: unknown;
|
|
30
|
+
name?: unknown;
|
|
31
|
+
designation?: unknown;
|
|
32
|
+
classification?: unknown;
|
|
33
|
+
quantity?: unknown;
|
|
34
|
+
aspects?: unknown;
|
|
35
|
+
assets?: unknown;
|
|
36
|
+
meta?: unknown;
|
|
37
|
+
stats?: unknown;
|
|
38
|
+
keywords?: unknown;
|
|
39
|
+
}
|
|
40
|
+
/** Split `Classification§Designation` into its two halves. Falls back to the
|
|
41
|
+
* raw `classification`/`designation` fields when `item` is absent. */
|
|
42
|
+
declare function splitItem(asset: RawAsset): {
|
|
43
|
+
classification: string;
|
|
44
|
+
designation: string;
|
|
45
|
+
};
|
|
46
|
+
/** A user-facing display name for an asset: `name` override beats the
|
|
47
|
+
* designation parsed out of the `item` key. */
|
|
48
|
+
declare function displayName(asset: RawAsset): string;
|
|
49
|
+
declare function classOf(asset: RawAsset): string;
|
|
50
|
+
/** Find the first child Asset with the given classification, if any. */
|
|
51
|
+
declare function findChildByClass(asset: RawAsset, cls: string): RawAsset | null;
|
|
52
|
+
export declare const rosterizerAdapter: FormatAdapter;
|
|
53
|
+
export declare const _internals: {
|
|
54
|
+
CLS_ROSTER: string;
|
|
55
|
+
CLS_FACTION: string;
|
|
56
|
+
CLS_DETACHMENT: string;
|
|
57
|
+
CLS_UNIT: string;
|
|
58
|
+
CLS_WEAPON: string;
|
|
59
|
+
CLS_ENHANCEMENT: string;
|
|
60
|
+
CLS_BATTLE_SIZE: string;
|
|
61
|
+
CLS_TRAIT: string;
|
|
62
|
+
DSG_WARLORD: string;
|
|
63
|
+
POINTS_STAT_KEYS: string[];
|
|
64
|
+
splitItem: typeof splitItem;
|
|
65
|
+
displayName: typeof displayName;
|
|
66
|
+
classOf: typeof classOf;
|
|
67
|
+
findChildByClass: typeof findChildByClass;
|
|
68
|
+
};
|
|
69
|
+
export {};
|
|
70
|
+
//# sourceMappingURL=rosterizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rosterizer.d.ts","sourceRoot":"","sources":["../../src/import/rosterizer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAkClD,UAAU,QAAQ;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA6CD;sEACsE;AACtE,iBAAS,SAAS,CAAC,KAAK,EAAE,QAAQ,GAAG;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAenF;AAED;+CAC+C;AAC/C,iBAAS,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAE5C;AA4CD,iBAAS,OAAO,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAExC;AAmHD,wEAAwE;AACxE,iBAAS,gBAAgB,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAKvE;AASD,eAAO,MAAM,iBAAiB,EAAE,aAoG/B,CAAC;AAGF,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;CAetB,CAAC"}
|