@alpaca-software/40kdc-data 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +64 -0
- package/dist/abilities-resolver/resolver.d.ts.map +1 -0
- package/dist/abilities-resolver/resolver.js +135 -0
- package/dist/abilities-resolver/resolver.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 +2 -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 +1 -0
- 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/buffs.d.ts +184 -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 +69 -0
- package/dist/cruncher/from-dsl.d.ts.map +1 -0
- package/dist/cruncher/from-dsl.js +523 -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 +11 -0
- package/dist/cruncher/index.d.ts.map +1 -0
- package/dist/cruncher/index.js +11 -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 +1 -0
- package/dist/data/collection.d.ts.map +1 -0
- package/dist/data/collection.js +1 -0
- package/dist/data/collection.js.map +1 -0
- package/dist/data/dataset.d.ts +54 -2
- package/dist/data/dataset.d.ts.map +1 -0
- package/dist/data/dataset.js +111 -1
- package/dist/data/dataset.js.map +1 -0
- package/dist/data/entities.d.ts +70 -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 +9 -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 +33 -0
- package/dist/data/roster-resolve.d.ts.map +1 -0
- package/dist/data/roster-resolve.js +36 -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 +21 -0
- package/dist/export/index.d.ts.map +1 -0
- package/dist/export/index.js +25 -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/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 +73 -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/import-roster.d.ts +35 -0
- package/dist/import/import-roster.d.ts.map +1 -0
- package/dist/import/import-roster.js +97 -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 +7 -1
- 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 +48 -0
- package/dist/import/newrecruit-text.d.ts.map +1 -0
- package/dist/import/newrecruit-text.js +96 -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 +334 -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/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 +243 -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/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/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 +7 -2
- package/schemas/core/roster.schema.json +17 -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,200 @@
|
|
|
1
|
+
import { classifyWargearList, splitWargearList } from "./newrecruit-text.js";
|
|
2
|
+
const FIRST_LINE = /^(.+)\s-\s\[\s*(\d+)\s*pts?\s*\]\s*$/i;
|
|
3
|
+
const ROSTER_HEADER = /^#\s*\+\+\s*Army Roster\s*\+\+\s*\[\s*(\d+)\s*pts?\s*\]\s*$/i;
|
|
4
|
+
const SECTION_HEADER = /^##\s*(.+?)(?:\s*\[\s*(\d+)\s*pts?\s*\])?\s*$/;
|
|
5
|
+
const UNIT_LINE = /^(.+?)\s*\[\s*(\d+)\s*pts?\s*\](?:\s*:\s*(.*))?$/i;
|
|
6
|
+
const BULLET = /^\s*•\s*(\d+)x\s+(.+?)(?:\s*\[\s*(\d+)\s*pts?\s*\])?(?:\s*:\s*(.*))?\s*$/u;
|
|
7
|
+
function newUnit(name, displayed_pts) {
|
|
8
|
+
return {
|
|
9
|
+
raw_name: name,
|
|
10
|
+
is_character: false,
|
|
11
|
+
is_warlord: false,
|
|
12
|
+
enhancement_raw_name: null,
|
|
13
|
+
enhancement_pts: 0,
|
|
14
|
+
displayed_pts,
|
|
15
|
+
model_count: 1,
|
|
16
|
+
wargear: new Map(),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function addWargear(unit, items) {
|
|
20
|
+
for (const { raw_name, count } of items) {
|
|
21
|
+
unit.wargear.set(raw_name, (unit.wargear.get(raw_name) ?? 0) + count);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function applyTokens(unit, tokensCsv, multiplier = 1) {
|
|
25
|
+
const tokens = splitWargearList(tokensCsv);
|
|
26
|
+
const cls = classifyWargearList(tokens);
|
|
27
|
+
if (cls.is_warlord)
|
|
28
|
+
unit.is_warlord = true;
|
|
29
|
+
if (cls.is_character)
|
|
30
|
+
unit.is_character = true;
|
|
31
|
+
if (cls.enhancement_raw_name && unit.enhancement_raw_name === null) {
|
|
32
|
+
unit.enhancement_raw_name = cls.enhancement_raw_name;
|
|
33
|
+
unit.enhancement_pts = cls.enhancement_points ?? 0;
|
|
34
|
+
}
|
|
35
|
+
const scaled = cls.wargear.map((w) => ({
|
|
36
|
+
raw_name: w.raw_name,
|
|
37
|
+
count: w.count * multiplier,
|
|
38
|
+
}));
|
|
39
|
+
addWargear(unit, scaled);
|
|
40
|
+
}
|
|
41
|
+
function finishUnit(unit) {
|
|
42
|
+
const points = unit.displayed_pts === null ? null : unit.displayed_pts - unit.enhancement_pts;
|
|
43
|
+
return {
|
|
44
|
+
raw_name: unit.raw_name,
|
|
45
|
+
is_character: unit.is_character,
|
|
46
|
+
model_count: unit.model_count,
|
|
47
|
+
points,
|
|
48
|
+
is_warlord: unit.is_warlord,
|
|
49
|
+
enhancement_raw_name: unit.enhancement_raw_name,
|
|
50
|
+
enhancement_points: unit.enhancement_raw_name === null ? null : unit.enhancement_pts,
|
|
51
|
+
wargear: [...unit.wargear].map(([raw_name, count]) => ({ raw_name, count })),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function parseFirstLine(line) {
|
|
55
|
+
const m = FIRST_LINE.exec(line);
|
|
56
|
+
if (!m)
|
|
57
|
+
return null;
|
|
58
|
+
const declared_limit = Number.parseInt(m[2], 10);
|
|
59
|
+
const parts = m[1].split(" - ").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
60
|
+
if (parts.length === 0)
|
|
61
|
+
return null;
|
|
62
|
+
const list_name = parts[parts.length - 1];
|
|
63
|
+
const faction = parts.length >= 2 ? parts[parts.length - 2] : null;
|
|
64
|
+
return { name: list_name, faction, declared_limit };
|
|
65
|
+
}
|
|
66
|
+
export const newRecruitSimpleAdapter = {
|
|
67
|
+
id: "newrecruit-simple",
|
|
68
|
+
matches(decoded) {
|
|
69
|
+
if (typeof decoded !== "string")
|
|
70
|
+
return false;
|
|
71
|
+
const lines = decoded.split(/\r?\n/);
|
|
72
|
+
const firstNonBlank = lines.find((l) => l.trim().length > 0);
|
|
73
|
+
if (!firstNonBlank)
|
|
74
|
+
return false;
|
|
75
|
+
if (!FIRST_LINE.test(firstNonBlank))
|
|
76
|
+
return false;
|
|
77
|
+
return /^#\s*\+\+\s*Army Roster\s*\+\+/m.test(decoded);
|
|
78
|
+
},
|
|
79
|
+
parse(decoded) {
|
|
80
|
+
if (typeof decoded !== "string") {
|
|
81
|
+
throw new Error("newrecruit-simple: input is not a string");
|
|
82
|
+
}
|
|
83
|
+
const lines = decoded.split(/\r?\n/);
|
|
84
|
+
let name = "Imported roster";
|
|
85
|
+
let faction_raw_name = null;
|
|
86
|
+
let declared_limit = null;
|
|
87
|
+
let total_reported = null;
|
|
88
|
+
let detachment_raw_name = null;
|
|
89
|
+
let battle_size_raw = null;
|
|
90
|
+
const units = [];
|
|
91
|
+
let current = null;
|
|
92
|
+
let multi_force = false;
|
|
93
|
+
let section = "preamble";
|
|
94
|
+
const enhancementPts = [];
|
|
95
|
+
const finalize = () => {
|
|
96
|
+
if (current) {
|
|
97
|
+
enhancementPts.push(current.enhancement_pts);
|
|
98
|
+
units.push(finishUnit(current));
|
|
99
|
+
current = null;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
103
|
+
const raw = lines[i];
|
|
104
|
+
const line = raw.trim();
|
|
105
|
+
if (!line)
|
|
106
|
+
continue;
|
|
107
|
+
// First non-blank line carries `<breadcrumb> - <faction> - <list> - [N pts]`.
|
|
108
|
+
if (section === "preamble" && name === "Imported roster") {
|
|
109
|
+
const first = parseFirstLine(line);
|
|
110
|
+
if (first) {
|
|
111
|
+
name = first.name;
|
|
112
|
+
faction_raw_name = first.faction;
|
|
113
|
+
declared_limit = first.declared_limit;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const rosterMatch = ROSTER_HEADER.exec(line);
|
|
118
|
+
if (rosterMatch) {
|
|
119
|
+
total_reported = Number.parseInt(rosterMatch[1], 10);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const sectionMatch = SECTION_HEADER.exec(line);
|
|
123
|
+
if (sectionMatch) {
|
|
124
|
+
finalize();
|
|
125
|
+
const heading = sectionMatch[1].trim().toLowerCase();
|
|
126
|
+
if (heading === "configuration") {
|
|
127
|
+
section = "configuration";
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
section = "units";
|
|
131
|
+
if (heading.includes("allied"))
|
|
132
|
+
multi_force = true;
|
|
133
|
+
}
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (section === "configuration") {
|
|
137
|
+
const idx = line.indexOf(":");
|
|
138
|
+
if (idx > 0) {
|
|
139
|
+
const key = line.slice(0, idx).trim().toLowerCase();
|
|
140
|
+
const value = line.slice(idx + 1).trim();
|
|
141
|
+
if (key === "battle size")
|
|
142
|
+
battle_size_raw = value;
|
|
143
|
+
else if (key === "detachment")
|
|
144
|
+
detachment_raw_name = value;
|
|
145
|
+
}
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
// Unit section. A bullet line extends the *current* unit.
|
|
149
|
+
const bulletMatch = BULLET.exec(raw);
|
|
150
|
+
if (bulletMatch && current) {
|
|
151
|
+
const count = Number.parseInt(bulletMatch[1], 10);
|
|
152
|
+
// Bullets may add to the unit's model count beyond the implicit 1 we
|
|
153
|
+
// set when we created it from the unit header.
|
|
154
|
+
if (current.wargear.size === 0 && current.model_count === 1) {
|
|
155
|
+
// First bullet: replace the implicit single-model assumption.
|
|
156
|
+
current.model_count = count;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
current.model_count += count;
|
|
160
|
+
}
|
|
161
|
+
if (bulletMatch[4])
|
|
162
|
+
applyTokens(current, bulletMatch[4], count);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const unitMatch = UNIT_LINE.exec(line);
|
|
166
|
+
if (unitMatch) {
|
|
167
|
+
finalize();
|
|
168
|
+
const unitName = unitMatch[1].trim();
|
|
169
|
+
const pts = Number.parseInt(unitMatch[2], 10);
|
|
170
|
+
current = newUnit(unitName, pts);
|
|
171
|
+
const inlineWargear = unitMatch[3]?.trim() ?? "";
|
|
172
|
+
if (inlineWargear.length > 0) {
|
|
173
|
+
applyTokens(current, inlineWargear, 1);
|
|
174
|
+
}
|
|
175
|
+
// Leave model_count at the default 1. If `•` bullet lines follow, the
|
|
176
|
+
// bullet handler resets model_count to the (summed) bullet counts.
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
finalize();
|
|
181
|
+
let total_computed = 0;
|
|
182
|
+
for (let i = 0; i < units.length; i += 1) {
|
|
183
|
+
total_computed += units[i].points ?? 0;
|
|
184
|
+
total_computed += enhancementPts[i] ?? 0;
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
name,
|
|
188
|
+
generated_by: null,
|
|
189
|
+
faction_raw_name,
|
|
190
|
+
detachment_raw_name,
|
|
191
|
+
battle_size_raw,
|
|
192
|
+
declared_limit,
|
|
193
|
+
total_reported,
|
|
194
|
+
total_computed,
|
|
195
|
+
units,
|
|
196
|
+
multi_force,
|
|
197
|
+
};
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
//# sourceMappingURL=newrecruit-simple.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"newrecruit-simple.js","sourceRoot":"","sources":["../../src/import/newrecruit-simple.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * NewRecruit \"simple\" markdown-ish text adapter.\n *\n * Shape:\n * ```\n * <breadcrumb> - <faction> - <list name> - [N pts]\n *\n * # ++ Army Roster ++ [N pts]\n * ## Configuration\n * Battle Size: <Label>\n * Detachment: <Name>\n * Show/Hide Options: ...\n *\n * ## <Section> [N pts]\n * <Unit> [N pts]: <wargear>\n * <Unit> [N pts]:\n * • <count>x <ModelType>[ [N pts]]: <wargear>\n * ```\n *\n * Enhancements are inlined in the wargear list as `<Name> [N pts]` — the only\n * wargear token wearing a `[…]` pts suffix. `Warlord` and the detachment\n * \"<X> Character\" keyword are also stripped from the list and set as flags.\n * Per-model-type breakdowns under `•` lines are collapsed onto the parent unit.\n *\n * @packageDocumentation\n */\nimport type { FormatAdapter } from \"./adapter.js\";\nimport type { ParsedRoster, ParsedUnit, ParsedWargear } from \"./types.js\";\nimport { classifyWargearList, splitWargearList } from \"./newrecruit-text.js\";\n\nconst FIRST_LINE = /^(.+)\\s-\\s\\[\\s*(\\d+)\\s*pts?\\s*\\]\\s*$/i;\nconst ROSTER_HEADER = /^#\\s*\\+\\+\\s*Army Roster\\s*\\+\\+\\s*\\[\\s*(\\d+)\\s*pts?\\s*\\]\\s*$/i;\nconst SECTION_HEADER = /^##\\s*(.+?)(?:\\s*\\[\\s*(\\d+)\\s*pts?\\s*\\])?\\s*$/;\nconst UNIT_LINE = /^(.+?)\\s*\\[\\s*(\\d+)\\s*pts?\\s*\\](?:\\s*:\\s*(.*))?$/i;\nconst BULLET =\n /^\\s*•\\s*(\\d+)x\\s+(.+?)(?:\\s*\\[\\s*(\\d+)\\s*pts?\\s*\\])?(?:\\s*:\\s*(.*))?\\s*$/u;\n\ninterface UnitBuilder {\n raw_name: string;\n is_character: boolean;\n is_warlord: boolean;\n enhancement_raw_name: string | null;\n enhancement_pts: number;\n displayed_pts: number | null;\n model_count: number;\n /** Aggregated wargear, keyed by name. Counts sum across `• Nx ModelType` breakdowns. */\n wargear: Map<string, number>;\n}\n\nfunction newUnit(name: string, displayed_pts: number | null): UnitBuilder {\n return {\n raw_name: name,\n is_character: false,\n is_warlord: false,\n enhancement_raw_name: null,\n enhancement_pts: 0,\n displayed_pts,\n model_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 applyTokens(unit: UnitBuilder, tokensCsv: string, multiplier = 1): void {\n const tokens = splitWargearList(tokensCsv);\n const cls = classifyWargearList(tokens);\n if (cls.is_warlord) unit.is_warlord = true;\n if (cls.is_character) unit.is_character = true;\n if (cls.enhancement_raw_name && unit.enhancement_raw_name === null) {\n unit.enhancement_raw_name = cls.enhancement_raw_name;\n unit.enhancement_pts = cls.enhancement_points ?? 0;\n }\n const scaled = cls.wargear.map((w) => ({\n raw_name: w.raw_name,\n count: w.count * multiplier,\n }));\n addWargear(unit, scaled);\n}\n\nfunction finishUnit(unit: UnitBuilder): ParsedUnit {\n const points =\n unit.displayed_pts === null ? null : unit.displayed_pts - unit.enhancement_pts;\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: [...unit.wargear].map(([raw_name, count]) => ({ raw_name, count })),\n };\n}\n\nfunction parseFirstLine(line: string): { name: string; faction: string | null; declared_limit: number | null } | null {\n const m = FIRST_LINE.exec(line);\n if (!m) return null;\n const declared_limit = Number.parseInt(m[2], 10);\n const parts = m[1].split(\" - \").map((s) => s.trim()).filter((s) => s.length > 0);\n if (parts.length === 0) return null;\n const list_name = parts[parts.length - 1];\n const faction = parts.length >= 2 ? parts[parts.length - 2] : null;\n return { name: list_name, faction, declared_limit };\n}\n\ntype Section = \"preamble\" | \"configuration\" | \"units\";\n\nexport const newRecruitSimpleAdapter: FormatAdapter = {\n id: \"newrecruit-simple\",\n\n matches(decoded: unknown): boolean {\n if (typeof decoded !== \"string\") return false;\n const lines = decoded.split(/\\r?\\n/);\n const firstNonBlank = lines.find((l) => l.trim().length > 0);\n if (!firstNonBlank) return false;\n if (!FIRST_LINE.test(firstNonBlank)) return false;\n return /^#\\s*\\+\\+\\s*Army Roster\\s*\\+\\+/m.test(decoded);\n },\n\n parse(decoded: unknown): ParsedRoster {\n if (typeof decoded !== \"string\") {\n throw new Error(\"newrecruit-simple: input is not a string\");\n }\n const lines = decoded.split(/\\r?\\n/);\n\n let name = \"Imported roster\";\n let faction_raw_name: string | null = null;\n let declared_limit: number | null = null;\n let total_reported: number | null = null;\n let detachment_raw_name: string | null = null;\n let battle_size_raw: string | null = null;\n const units: ParsedUnit[] = [];\n let current: UnitBuilder | null = null;\n let multi_force = false;\n let section: Section = \"preamble\";\n const enhancementPts: number[] = [];\n\n const finalize = (): void => {\n if (current) {\n enhancementPts.push(current.enhancement_pts);\n units.push(finishUnit(current));\n current = null;\n }\n };\n\n for (let i = 0; i < lines.length; i += 1) {\n const raw = lines[i];\n const line = raw.trim();\n if (!line) continue;\n\n // First non-blank line carries `<breadcrumb> - <faction> - <list> - [N pts]`.\n if (section === \"preamble\" && name === \"Imported roster\") {\n const first = parseFirstLine(line);\n if (first) {\n name = first.name;\n faction_raw_name = first.faction;\n declared_limit = first.declared_limit;\n continue;\n }\n }\n\n const rosterMatch = ROSTER_HEADER.exec(line);\n if (rosterMatch) {\n total_reported = Number.parseInt(rosterMatch[1], 10);\n continue;\n }\n\n const sectionMatch = SECTION_HEADER.exec(line);\n if (sectionMatch) {\n finalize();\n const heading = sectionMatch[1].trim().toLowerCase();\n if (heading === \"configuration\") {\n section = \"configuration\";\n } else {\n section = \"units\";\n if (heading.includes(\"allied\")) multi_force = true;\n }\n continue;\n }\n\n if (section === \"configuration\") {\n const idx = line.indexOf(\":\");\n if (idx > 0) {\n const key = line.slice(0, idx).trim().toLowerCase();\n const value = line.slice(idx + 1).trim();\n if (key === \"battle size\") battle_size_raw = value;\n else if (key === \"detachment\") detachment_raw_name = value;\n }\n continue;\n }\n\n // Unit section. A bullet line extends the *current* unit.\n const bulletMatch = BULLET.exec(raw);\n if (bulletMatch && current) {\n const count = Number.parseInt(bulletMatch[1], 10);\n // Bullets may add to the unit's model count beyond the implicit 1 we\n // set when we created it from the unit header.\n if (current.wargear.size === 0 && current.model_count === 1) {\n // First bullet: replace the implicit single-model assumption.\n current.model_count = count;\n } else {\n current.model_count += count;\n }\n if (bulletMatch[4]) applyTokens(current, bulletMatch[4], count);\n continue;\n }\n\n const unitMatch = UNIT_LINE.exec(line);\n if (unitMatch) {\n finalize();\n const unitName = unitMatch[1].trim();\n const pts = Number.parseInt(unitMatch[2], 10);\n current = newUnit(unitName, pts);\n const inlineWargear = unitMatch[3]?.trim() ?? \"\";\n if (inlineWargear.length > 0) {\n applyTokens(current, inlineWargear, 1);\n }\n // Leave model_count at the default 1. If `•` bullet lines follow, the\n // bullet handler resets model_count to the (summed) bullet counts.\n continue;\n }\n }\n finalize();\n\n let total_computed = 0;\n for (let i = 0; i < units.length; i += 1) {\n total_computed += units[i].points ?? 0;\n total_computed += enhancementPts[i] ?? 0;\n }\n\n return {\n name,\n generated_by: null,\n faction_raw_name,\n detachment_raw_name,\n battle_size_raw,\n declared_limit,\n total_reported,\n total_computed,\n units,\n multi_force,\n };\n },\n};\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers shared by the three NewRecruit text adapters (wtc-compact, wtc-full,
|
|
3
|
+
* simple). These are pure string-massage utilities: they take format-specific
|
|
4
|
+
* tokens and turn them into the format-agnostic {@link ParsedRoster} pieces.
|
|
5
|
+
*
|
|
6
|
+
* No business knowledge of dataset entities lives here — name resolution is
|
|
7
|
+
* still {@link resolve}'s job downstream.
|
|
8
|
+
*
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
*/
|
|
11
|
+
import type { ParsedWargear } from "./types.js";
|
|
12
|
+
/**
|
|
13
|
+
* Synthesize a {@link ParsedRoster.battle_size_raw} from a points limit. The
|
|
14
|
+
* wtc/simple formats don't carry the battle-size label explicitly — they only
|
|
15
|
+
* report the total army points — so we map the limit to its standard label
|
|
16
|
+
* (the same one {@link mapBattleSize} expects).
|
|
17
|
+
*/
|
|
18
|
+
export declare function inferBattleSizeRaw(limit: number | null): string | null;
|
|
19
|
+
/** Outcome of classifying a comma-separated wargear list. */
|
|
20
|
+
export interface ClassifiedTokens {
|
|
21
|
+
wargear: ParsedWargear[];
|
|
22
|
+
is_warlord: boolean;
|
|
23
|
+
is_character: boolean;
|
|
24
|
+
/** Enhancement raw name, when one was inlined in the wargear list (simple format). */
|
|
25
|
+
enhancement_raw_name: string | null;
|
|
26
|
+
/** Enhancement points cost when given inline (simple format), else null. */
|
|
27
|
+
enhancement_points: number | null;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Classify each token in a comma-separated wargear list. Strips the markers
|
|
31
|
+
* that aren't real wargear — `Warlord`, the detachment "<Name> Character"
|
|
32
|
+
* keyword, and the inline `Name [N pts]` enhancement (simple format) — and
|
|
33
|
+
* collects everything else as {@link ParsedWargear} with optional `Nx` count.
|
|
34
|
+
*
|
|
35
|
+
* Tokens are pre-split: pass `["Armoured feet", "2x War Dog autocannon", ...]`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function classifyWargearList(tokens: readonly string[]): ClassifiedTokens;
|
|
38
|
+
/**
|
|
39
|
+
* Split a wargear list on top-level commas. (No nested parentheses with commas
|
|
40
|
+
* are produced by NewRecruit, so a plain split is enough; the helper keeps
|
|
41
|
+
* intent explicit for future format quirks.)
|
|
42
|
+
*/
|
|
43
|
+
export declare function splitWargearList(text: string): string[];
|
|
44
|
+
/** Strip a trailing parenthetical (e.g. "Houndpack Lance (Marked Prey)" → "Houndpack Lance"). */
|
|
45
|
+
export declare function stripParenthetical(name: string): string;
|
|
46
|
+
/** Parse a `(\d+) pts` or `[\d+ pts]` suffix from a unit header line. */
|
|
47
|
+
export declare function pointsFrom(token: string): number | null;
|
|
48
|
+
//# sourceMappingURL=newrecruit-text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"newrecruit-text.d.ts","sourceRoot":"","sources":["../../src/import/newrecruit-text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAUhD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAMtE;AAED,6DAA6D;AAC7D,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,sFAAsF;IACtF,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,4EAA4E;IAC5E,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAOD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,gBAAgB,CAwC/E;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAKvD;AAED,iGAAiG;AACjG,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGvD;AAED,yEAAyE;AACzE,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIvD"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** Tournament-standard battle sizes by points ceiling (10th ed). */
|
|
2
|
+
const BATTLE_SIZES = [
|
|
3
|
+
{ upper: 500, label: "Combat Patrol (500 Point limit)" },
|
|
4
|
+
{ upper: 1000, label: "Incursion (1000 Point limit)" },
|
|
5
|
+
{ upper: 2000, label: "Strike Force (2000 Point limit)" },
|
|
6
|
+
{ upper: 3000, label: "Onslaught (3000 Point limit)" },
|
|
7
|
+
];
|
|
8
|
+
/**
|
|
9
|
+
* Synthesize a {@link ParsedRoster.battle_size_raw} from a points limit. The
|
|
10
|
+
* wtc/simple formats don't carry the battle-size label explicitly — they only
|
|
11
|
+
* report the total army points — so we map the limit to its standard label
|
|
12
|
+
* (the same one {@link mapBattleSize} expects).
|
|
13
|
+
*/
|
|
14
|
+
export function inferBattleSizeRaw(limit) {
|
|
15
|
+
if (limit === null)
|
|
16
|
+
return null;
|
|
17
|
+
for (const { upper, label } of BATTLE_SIZES) {
|
|
18
|
+
if (limit <= upper)
|
|
19
|
+
return label;
|
|
20
|
+
}
|
|
21
|
+
return BATTLE_SIZES[BATTLE_SIZES.length - 1].label; // beyond Onslaught: cap at Onslaught
|
|
22
|
+
}
|
|
23
|
+
const NX_PREFIX = /^(\d+)x\s+(.+)$/;
|
|
24
|
+
const INLINE_PTS = /^(.+?)\s*\[\s*(\d+)\s*pts?\s*\]\s*$/i;
|
|
25
|
+
const CHARACTER_SUFFIX = " Character";
|
|
26
|
+
const WARLORD_MARKER = "Warlord";
|
|
27
|
+
/**
|
|
28
|
+
* Classify each token in a comma-separated wargear list. Strips the markers
|
|
29
|
+
* that aren't real wargear — `Warlord`, the detachment "<Name> Character"
|
|
30
|
+
* keyword, and the inline `Name [N pts]` enhancement (simple format) — and
|
|
31
|
+
* collects everything else as {@link ParsedWargear} with optional `Nx` count.
|
|
32
|
+
*
|
|
33
|
+
* Tokens are pre-split: pass `["Armoured feet", "2x War Dog autocannon", ...]`.
|
|
34
|
+
*/
|
|
35
|
+
export function classifyWargearList(tokens) {
|
|
36
|
+
const wargear = [];
|
|
37
|
+
let is_warlord = false;
|
|
38
|
+
let is_character = false;
|
|
39
|
+
let enhancement_raw_name = null;
|
|
40
|
+
let enhancement_points = null;
|
|
41
|
+
for (const raw of tokens) {
|
|
42
|
+
const token = raw.trim();
|
|
43
|
+
if (!token)
|
|
44
|
+
continue;
|
|
45
|
+
if (token === WARLORD_MARKER) {
|
|
46
|
+
is_warlord = true;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (token.endsWith(CHARACTER_SUFFIX)) {
|
|
50
|
+
is_character = true;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
// Simple format inlines the enhancement as `Name [15 pts]`.
|
|
54
|
+
const pts = INLINE_PTS.exec(token);
|
|
55
|
+
if (pts) {
|
|
56
|
+
if (enhancement_raw_name === null) {
|
|
57
|
+
enhancement_raw_name = pts[1].trim();
|
|
58
|
+
enhancement_points = Number.parseInt(pts[2], 10);
|
|
59
|
+
}
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const nx = NX_PREFIX.exec(token);
|
|
63
|
+
if (nx) {
|
|
64
|
+
const count = Number.parseInt(nx[1], 10);
|
|
65
|
+
wargear.push({ raw_name: nx[2].trim(), count: count > 0 ? count : 1 });
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
wargear.push({ raw_name: token, count: 1 });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { wargear, is_warlord, is_character, enhancement_raw_name, enhancement_points };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Split a wargear list on top-level commas. (No nested parentheses with commas
|
|
75
|
+
* are produced by NewRecruit, so a plain split is enough; the helper keeps
|
|
76
|
+
* intent explicit for future format quirks.)
|
|
77
|
+
*/
|
|
78
|
+
export function splitWargearList(text) {
|
|
79
|
+
return text
|
|
80
|
+
.split(",")
|
|
81
|
+
.map((s) => s.trim())
|
|
82
|
+
.filter((s) => s.length > 0);
|
|
83
|
+
}
|
|
84
|
+
/** Strip a trailing parenthetical (e.g. "Houndpack Lance (Marked Prey)" → "Houndpack Lance"). */
|
|
85
|
+
export function stripParenthetical(name) {
|
|
86
|
+
const idx = name.indexOf("(");
|
|
87
|
+
return idx >= 0 ? name.slice(0, idx).trim() : name.trim();
|
|
88
|
+
}
|
|
89
|
+
/** Parse a `(\d+) pts` or `[\d+ pts]` suffix from a unit header line. */
|
|
90
|
+
export function pointsFrom(token) {
|
|
91
|
+
const m = /\(\s*(\d+)\s*pts?\s*\)|\[\s*(\d+)\s*pts?\s*\]/i.exec(token);
|
|
92
|
+
if (!m)
|
|
93
|
+
return null;
|
|
94
|
+
return Number.parseInt(m[1] ?? m[2], 10);
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=newrecruit-text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"newrecruit-text.js","sourceRoot":"","sources":["../../src/import/newrecruit-text.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Helpers shared by the three NewRecruit text adapters (wtc-compact, wtc-full,\n * simple). These are pure string-massage utilities: they take format-specific\n * tokens and turn them into the format-agnostic {@link ParsedRoster} pieces.\n *\n * No business knowledge of dataset entities lives here — name resolution is\n * still {@link resolve}'s job downstream.\n *\n * @packageDocumentation\n */\nimport type { ParsedWargear } from \"./types.js\";\n\n/** Tournament-standard battle sizes by points ceiling (10th ed). */\nconst BATTLE_SIZES: readonly { upper: number; label: string }[] = [\n { upper: 500, label: \"Combat Patrol (500 Point limit)\" },\n { upper: 1000, label: \"Incursion (1000 Point limit)\" },\n { upper: 2000, label: \"Strike Force (2000 Point limit)\" },\n { upper: 3000, label: \"Onslaught (3000 Point limit)\" },\n];\n\n/**\n * Synthesize a {@link ParsedRoster.battle_size_raw} from a points limit. The\n * wtc/simple formats don't carry the battle-size label explicitly — they only\n * report the total army points — so we map the limit to its standard label\n * (the same one {@link mapBattleSize} expects).\n */\nexport function inferBattleSizeRaw(limit: number | null): string | null {\n if (limit === null) return null;\n for (const { upper, label } of BATTLE_SIZES) {\n if (limit <= upper) return label;\n }\n return BATTLE_SIZES[BATTLE_SIZES.length - 1].label; // beyond Onslaught: cap at Onslaught\n}\n\n/** Outcome of classifying a comma-separated wargear list. */\nexport interface ClassifiedTokens {\n wargear: ParsedWargear[];\n is_warlord: boolean;\n is_character: boolean;\n /** Enhancement raw name, when one was inlined in the wargear list (simple format). */\n enhancement_raw_name: string | null;\n /** Enhancement points cost when given inline (simple format), else null. */\n enhancement_points: number | null;\n}\n\nconst NX_PREFIX = /^(\\d+)x\\s+(.+)$/;\nconst INLINE_PTS = /^(.+?)\\s*\\[\\s*(\\d+)\\s*pts?\\s*\\]\\s*$/i;\nconst CHARACTER_SUFFIX = \" Character\";\nconst WARLORD_MARKER = \"Warlord\";\n\n/**\n * Classify each token in a comma-separated wargear list. Strips the markers\n * that aren't real wargear — `Warlord`, the detachment \"<Name> Character\"\n * keyword, and the inline `Name [N pts]` enhancement (simple format) — and\n * collects everything else as {@link ParsedWargear} with optional `Nx` count.\n *\n * Tokens are pre-split: pass `[\"Armoured feet\", \"2x War Dog autocannon\", ...]`.\n */\nexport function classifyWargearList(tokens: readonly string[]): ClassifiedTokens {\n const wargear: ParsedWargear[] = [];\n let is_warlord = false;\n let is_character = false;\n let enhancement_raw_name: string | null = null;\n let enhancement_points: number | null = null;\n\n for (const raw of tokens) {\n const token = raw.trim();\n if (!token) continue;\n\n if (token === WARLORD_MARKER) {\n is_warlord = true;\n continue;\n }\n if (token.endsWith(CHARACTER_SUFFIX)) {\n is_character = true;\n continue;\n }\n\n // Simple format inlines the enhancement as `Name [15 pts]`.\n const pts = INLINE_PTS.exec(token);\n if (pts) {\n if (enhancement_raw_name === null) {\n enhancement_raw_name = pts[1].trim();\n enhancement_points = Number.parseInt(pts[2], 10);\n }\n continue;\n }\n\n const nx = NX_PREFIX.exec(token);\n if (nx) {\n const count = Number.parseInt(nx[1], 10);\n wargear.push({ raw_name: nx[2].trim(), count: count > 0 ? count : 1 });\n } else {\n wargear.push({ raw_name: token, count: 1 });\n }\n }\n\n return { wargear, is_warlord, is_character, enhancement_raw_name, enhancement_points };\n}\n\n/**\n * Split a wargear list on top-level commas. (No nested parentheses with commas\n * are produced by NewRecruit, so a plain split is enough; the helper keeps\n * intent explicit for future format quirks.)\n */\nexport function splitWargearList(text: string): string[] {\n return text\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n}\n\n/** Strip a trailing parenthetical (e.g. \"Houndpack Lance (Marked Prey)\" → \"Houndpack Lance\"). */\nexport function stripParenthetical(name: string): string {\n const idx = name.indexOf(\"(\");\n return idx >= 0 ? name.slice(0, idx).trim() : name.trim();\n}\n\n/** Parse a `(\\d+) pts` or `[\\d+ pts]` suffix from a unit header line. */\nexport function pointsFrom(token: string): number | null {\n const m = /\\(\\s*(\\d+)\\s*pts?\\s*\\)|\\[\\s*(\\d+)\\s*pts?\\s*\\]/i.exec(token);\n if (!m) return null;\n return Number.parseInt(m[1] ?? m[2], 10);\n}\n"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NewRecruit "wtc-compact" and "wtc-full" text adapters.
|
|
3
|
+
*
|
|
4
|
+
* Both formats open with a `++++++++` summary header carrying FACTION KEYWORD,
|
|
5
|
+
* DETACHMENT, TOTAL ARMY POINTS, WARLORD, ENHANCEMENT(s), NUMBER OF UNITS, and
|
|
6
|
+
* SECONDARY tournament-objective shorthand. The body diverges:
|
|
7
|
+
*
|
|
8
|
+
* - **wtc-compact** — one unit per line:
|
|
9
|
+
* `[CharN: ]Nx <Unit> (P pts): <comma-separated wargear>`
|
|
10
|
+
* followed optionally by `Enhancement: <Name> (+P pts)` on the next line.
|
|
11
|
+
*
|
|
12
|
+
* - **wtc-full** — uppercase section headers (`BATTLELINE`, `ALLIED UNITS`),
|
|
13
|
+
* two-line unit blocks (`[CharN: ]Nx <Unit> (P pts)` then `N with <wargear>`),
|
|
14
|
+
* `Enhancement: <Name> (+P pts)` on its own line, and per-model-type
|
|
15
|
+
* breakdowns with `• Nx <ModelType>` + indented `N with <wargear>` lines.
|
|
16
|
+
*
|
|
17
|
+
* The {@link Roster} pivot stores units at unit granularity — per-model-type
|
|
18
|
+
* wargear breakdowns and `CharN:` slot numbers aren't modelled, so this adapter
|
|
19
|
+
* collapses them: the parsed unit's `model_count` is summed from the breakdown
|
|
20
|
+
* and its `wargear` is the union of every loadout under it. The `WARLORD` /
|
|
21
|
+
* `Houndpack Lance Character` tokens are stripped from the wargear list (and
|
|
22
|
+
* set `is_warlord`/`is_character` instead) so resolution doesn't try to look
|
|
23
|
+
* them up as weapons. Round-trips are at Roster level, not byte-for-byte.
|
|
24
|
+
*
|
|
25
|
+
* Enhancement points (`+15 pts`) are subtracted from the displayed unit total
|
|
26
|
+
* so `ParsedUnit.points` is the *base* unit cost — matching the ListForge
|
|
27
|
+
* convention where the unit's own cost line is base and the enhancement is a
|
|
28
|
+
* sibling cost line. `total_computed` walks every cost line just like ListForge
|
|
29
|
+
* (base unit pts + each enhancement pts).
|
|
30
|
+
*
|
|
31
|
+
* @packageDocumentation
|
|
32
|
+
*/
|
|
33
|
+
import type { FormatAdapter } from "./adapter.js";
|
|
34
|
+
export declare const newRecruitWtcCompactAdapter: FormatAdapter;
|
|
35
|
+
export declare const newRecruitWtcFullAdapter: FormatAdapter;
|
|
36
|
+
//# sourceMappingURL=newrecruit-wtc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"newrecruit-wtc.d.ts","sourceRoot":"","sources":["../../src/import/newrecruit-wtc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AA+WlD,eAAO,MAAM,2BAA2B,EAAE,aAczC,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,aActC,CAAC"}
|