@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 @@
|
|
|
1
|
+
{"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../../src/data/collection.ts"],"names":[],"mappings":"AAiBA,6EAA6E;AAC7E,MAAM,WAAW,gBAAgB,CAAC,CAAC,EAAE,CAAC;IACpC,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,sEAAsE;IACtE,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC;IAC1B;;;;OAIG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC;IAClC,4EAA4E;IAC5E,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,SAAS,CAAC;IACzC,8EAA8E;IAC9E,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACnD,4CAA4C;IAC5C,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,qBAAa,UAAU,CAAC,CAAC,EAAE,CAAC,CAAE,YAAW,QAAQ,CAAC,CAAC,CAAC;IAClD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAW;IACjC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAwB;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0B;IACjD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA0B;IACtD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAkC;IAC1D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;gBAE5B,GAAG,EAAE,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC;IAsBvC,6DAA6D;IAC7D,IAAI,GAAG,IAAI,CAAC,EAAE,CAEb;IAED,kCAAkC;IAClC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,2BAA2B;IAC3B,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAK9B,kDAAkD;IAClD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAIxB;;;;;;;;;OASG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAIlC;;;;;OAKG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,EAAE;IAc3B,gFAAgF;IAChF,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,CAAC,EAAE;IAIjC,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC;CAGjC"}
|
package/dist/data/collection.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collection.js","sourceRoot":"","sources":["../../src/data/collection.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A queryable, iterable view over one entity collection.\n *\n * Indexes (by id, by normalized name, by faction) are built once at construction.\n * Records are deduplicated by {@link CollectionConfig.dedupeKeyOf} (default: id,\n * first occurrence wins). Some records are intentionally shared: the same unit\n * id (e.g. `ministorum-priest`) appears under several factions, so units dedupe\n * on `(faction_id, id)` to keep each faction's copy; identical core abilities\n * (e.g. `leader`) copied into many faction files dedupe away on `ability_id`.\n *\n * `get(id)`/`find` return the first match when an id is shared across factions;\n * use {@link Collection.byFaction} or {@link Collection.findAll} to disambiguate.\n *\n * @packageDocumentation\n */\nimport { normalizeName } from \"./normalize.js\";\n\n/** How a {@link Collection} reads keys and builds views from raw records. */\nexport interface CollectionConfig<T, V> {\n items: T[];\n /** Primary id of a record (e.g. `u => u.id`, `a => a.ability_id`). */\n idOf: (item: T) => string;\n /**\n * Uniqueness key used for deduplication. Defaults to {@link idOf}. Set to a\n * composite (e.g. `(faction_id, id)`) for records that share an id across\n * factions, so distinct copies are preserved rather than collapsed.\n */\n dedupeKeyOf?: (item: T) => string;\n /** Display name, if the record has one — drives {@link Collection.find}. */\n nameOf?: (item: T) => string | undefined;\n /** Owning faction id, if applicable — drives {@link Collection.byFaction}. */\n factionOf?: (item: T) => string | null | undefined;\n /** Wrap a raw record in its linked view. */\n wrap: (item: T) => V;\n}\n\n/**\n * A collection of one entity type, exposing id/name/faction lookups.\n *\n * Iterable: `for (const unit of units) { … }`.\n *\n * @typeParam T - the raw (generated) record type\n * @typeParam V - the linked view type returned to callers\n */\nexport class Collection<T, V> implements Iterable<V> {\n private readonly items: T[] = [];\n private readonly byId = new Map<string, T>();\n private readonly byNorm = new Map<string, T[]>();\n private readonly byFactionId = new Map<string, T[]>();\n private readonly nameOf?: (item: T) => string | undefined;\n private readonly wrapFn: (item: T) => V;\n\n constructor(cfg: CollectionConfig<T, V>) {\n this.nameOf = cfg.nameOf;\n this.wrapFn = cfg.wrap;\n const dedupeKeyOf = cfg.dedupeKeyOf ?? cfg.idOf;\n const seen = new Set<string>();\n for (const item of cfg.items) {\n const dedupeKey = dedupeKeyOf(item);\n if (seen.has(dedupeKey)) continue; // first-wins dedup\n seen.add(dedupeKey);\n this.items.push(item);\n\n const id = cfg.idOf(item);\n if (!this.byId.has(id)) this.byId.set(id, item); // first-wins for shared ids\n\n const name = cfg.nameOf?.(item);\n if (name) push(this.byNorm, normalizeName(name), item);\n\n const faction = cfg.factionOf?.(item);\n if (faction) push(this.byFactionId, faction, item);\n }\n }\n\n /** Every record, deduplicated by id, in first-seen order. */\n get all(): V[] {\n return this.items.map((item) => this.wrapFn(item));\n }\n\n /** Number of distinct records. */\n get size(): number {\n return this.items.length;\n }\n\n /** Look up by exact id. */\n get(id: string): V | undefined {\n const item = this.byId.get(id);\n return item ? this.wrapFn(item) : undefined;\n }\n\n /** Whether a record with this exact id exists. */\n has(id: string): boolean {\n return this.byId.has(id);\n }\n\n /**\n * Find one record by id or name. Name matching is diacritic- and\n * punctuation-insensitive (see {@link normalizeName}), trying, in order:\n * exact id → exact normalized name → normalized-name substring. Returns the\n * first match; names can repeat across factions, so use {@link findAll} or\n * {@link byFaction} when a query may be ambiguous.\n *\n * @example\n * units.find(\"Kharn\"); // resolves \"Khârn the Betrayer\"\n */\n find(query: string): V | undefined {\n return this.findAll(query)[0];\n }\n\n /**\n * All records matching a query, by the same rules as {@link find}. An exact id\n * match returns just that record; otherwise every normalized-name-exact match\n * is returned, falling back to every normalized-name-substring match. Useful\n * to surface (rather than silently collapse) names shared across factions.\n */\n findAll(query: string): V[] {\n const byId = this.byId.get(query);\n if (byId) return [this.wrapFn(byId)];\n\n const key = normalizeName(query);\n const exact = this.byNorm.get(key);\n if (exact && exact.length > 0) return exact.map((i) => this.wrapFn(i));\n\n if (!this.nameOf || key === \"\") return [];\n return this.items\n .filter((item) => normalizeName(this.nameOf!(item) ?? \"\").includes(key))\n .map((item) => this.wrapFn(item));\n }\n\n /** All records belonging to a faction id (empty if the type has no faction). */\n byFaction(factionId: string): V[] {\n return (this.byFactionId.get(factionId) ?? []).map((i) => this.wrapFn(i));\n }\n\n [Symbol.iterator](): Iterator<V> {\n return this.items.map((item) => this.wrapFn(item))[Symbol.iterator]();\n }\n}\n\nfunction push<K, T>(map: Map<K, T[]>, key: K, value: T): void {\n const existing = map.get(key);\n if (existing) existing.push(value);\n else map.set(key, [value]);\n}\n"]}
|
package/dist/data/dataset.d.ts
CHANGED
|
@@ -5,14 +5,17 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @packageDocumentation
|
|
7
7
|
*/
|
|
8
|
-
import type { DeploymentPattern, Detachment, Enhancement, ForceDisposition, GameVersion, InteractionFlag, LeaderAttachment, Mission, MissionMatchup, Phase, ResourcePool, SecondaryCard, Stratagem, TimingFlag, Unit, UnitComposition, WargearOption } from "../generated.js";
|
|
8
|
+
import type { DeploymentPattern, Detachment, Enhancement, ForceDisposition, GameVersion, InteractionFlag, LeaderAttachment, Mission, MissionMatchup, Phase, ResourcePool, SecondaryCard, Stratagem, TimingFlag, Unit, UnitComposition, WargearOption, WeaponKeyword } from "../generated.js";
|
|
9
9
|
import { Collection } from "./collection.js";
|
|
10
|
-
import { AbilityView, FactionView, UnitView, WeaponView } from "./entities.js";
|
|
10
|
+
import { AbilityView, FactionView, UnitView, WeaponKeywordView, WeaponView } from "./entities.js";
|
|
11
11
|
import { type RawData } from "./types.js";
|
|
12
|
+
import type { Buff, EngineContext } from "../cruncher/buffs.js";
|
|
13
|
+
import { type EligibilityInput, type EligibleAbility } from "../abilities-resolver/index.js";
|
|
12
14
|
/** The whole dataset, with linked accessors over every entity collection. */
|
|
13
15
|
export declare class Dataset {
|
|
14
16
|
readonly units: Collection<Unit, UnitView>;
|
|
15
17
|
readonly weapons: Collection<RawData["weapons"][number], WeaponView>;
|
|
18
|
+
readonly weaponKeywords: Collection<WeaponKeyword, WeaponKeywordView>;
|
|
16
19
|
readonly factions: Collection<RawData["factions"][number], FactionView>;
|
|
17
20
|
readonly abilities: Collection<RawData["abilities"][number], AbilityView>;
|
|
18
21
|
readonly detachments: Collection<Detachment, Detachment>;
|
|
@@ -37,6 +40,8 @@ export declare class Dataset {
|
|
|
37
40
|
private readonly unitsByAbility;
|
|
38
41
|
/** weapon id → units that list it. */
|
|
39
42
|
private readonly unitsByWeapon;
|
|
43
|
+
/** weapon-keyword id → weapons whose profiles reference it. */
|
|
44
|
+
private readonly weaponsByKeyword;
|
|
40
45
|
constructor(raw?: RawData);
|
|
41
46
|
/** The dataset built from the package's embedded data. */
|
|
42
47
|
static embedded(): Dataset;
|
|
@@ -46,5 +51,52 @@ export declare class Dataset {
|
|
|
46
51
|
unitsWithAbility(abilityId: string): UnitView[];
|
|
47
52
|
/** Units that list the given weapon id. */
|
|
48
53
|
unitsWithWeapon(weaponId: string): UnitView[];
|
|
54
|
+
/** Weapons whose profiles reference the given weapon-keyword id. */
|
|
55
|
+
weaponsWithKeyword(keywordId: string): WeaponView[];
|
|
56
|
+
/**
|
|
57
|
+
* Enumerate every ability that could apply to the given unit in `phase`,
|
|
58
|
+
* grouped by source. The SPA uses this to render the abilities pane.
|
|
59
|
+
*/
|
|
60
|
+
eligibleAbilities(input: EligibilityInput, phase: Phase): EligibleAbility[];
|
|
61
|
+
/**
|
|
62
|
+
* Attacker-perspective {@link Buff} stack for a (unit, phase) combination:
|
|
63
|
+
* intrinsic weapon-profile keywords plus every eligible ability whose DSL
|
|
64
|
+
* effect translates to an attacker-side buff (army, detachment, unit,
|
|
65
|
+
* leader, support, plus any stratagems the caller has opted into).
|
|
66
|
+
*
|
|
67
|
+
* The result includes only buffs the buff layer can express today — the
|
|
68
|
+
* `unsupported` half of the DSL→Buff translation is dropped here so callers
|
|
69
|
+
* who just want the stack don't need to thread diagnostics through. Use
|
|
70
|
+
* {@link AbilityView.describeBuffs} when you need the diagnostics for an
|
|
71
|
+
* individual ability. Symmetric to {@link defensiveBuffsFor}, which walks
|
|
72
|
+
* the same eligibility set under target perspective.
|
|
73
|
+
*/
|
|
74
|
+
buffsFor(input: EligibilityInput & {
|
|
75
|
+
weaponProfiles?: {
|
|
76
|
+
weaponId: string;
|
|
77
|
+
profileIndex: number;
|
|
78
|
+
}[];
|
|
79
|
+
/** Stratagem ids the caller has opted into spending CP on. */
|
|
80
|
+
optedInStratagemIds?: string[];
|
|
81
|
+
}, context: EngineContext): Buff[];
|
|
82
|
+
/**
|
|
83
|
+
* Defender-perspective buff stack for the chosen unit: walks the same
|
|
84
|
+
* eligible-abilities set as {@link buffsFor} but translates each ability's
|
|
85
|
+
* DSL effect as defensive (FNP, save mods from `stat-modifier Sv`,
|
|
86
|
+
* toughness mods from `stat-modifier T`, save rerolls, incoming hit
|
|
87
|
+
* penalties from `bs-modifier`). Use this when the chosen unit is being
|
|
88
|
+
* crunched as the *target* — the engine reads `feelNoPain`/`saveMod`/
|
|
89
|
+
* `toughnessMod` out of `resolveBuffs` so wiring the result into `crunch`
|
|
90
|
+
* just means concatenating onto the existing `buffs` array.
|
|
91
|
+
*
|
|
92
|
+
* `weaponProfiles` are ignored under target perspective — weapon-keyword
|
|
93
|
+
* effects ride with the firing weapon, not the receiving unit.
|
|
94
|
+
*/
|
|
95
|
+
defensiveBuffsFor(input: EligibilityInput & {
|
|
96
|
+
optedInStratagemIds?: string[];
|
|
97
|
+
}, context: EngineContext): Buff[];
|
|
98
|
+
/** Shared implementation for buffsFor / defensiveBuffsFor. */
|
|
99
|
+
private collectBuffs;
|
|
49
100
|
private buildIndexes;
|
|
50
101
|
}
|
|
102
|
+
//# sourceMappingURL=dataset.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataset.d.ts","sourceRoot":"","sources":["../../src/data/dataset.ts"],"names":[],"mappings":"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"}
|
package/dist/data/dataset.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { Collection } from "./collection.js";
|
|
2
|
-
import { AbilityView, FactionView, UnitView, WeaponView } from "./entities.js";
|
|
2
|
+
import { AbilityView, FactionView, UnitView, WeaponKeywordView, WeaponView, } from "./entities.js";
|
|
3
3
|
import { emptyRawData } from "./types.js";
|
|
4
4
|
import { RAW_DATA } from "./bundle.generated.js";
|
|
5
|
+
import { resolveEligibleAbilities, } from "../abilities-resolver/index.js";
|
|
5
6
|
/** The whole dataset, with linked accessors over every entity collection. */
|
|
6
7
|
export class Dataset {
|
|
7
8
|
// Richly-linked collections.
|
|
8
9
|
units;
|
|
9
10
|
weapons;
|
|
11
|
+
weaponKeywords;
|
|
10
12
|
factions;
|
|
11
13
|
abilities;
|
|
12
14
|
// Id-bearing collections without bespoke views (records returned as-is).
|
|
@@ -33,6 +35,8 @@ export class Dataset {
|
|
|
33
35
|
unitsByAbility = new Map();
|
|
34
36
|
/** weapon id → units that list it. */
|
|
35
37
|
unitsByWeapon = new Map();
|
|
38
|
+
/** weapon-keyword id → weapons whose profiles reference it. */
|
|
39
|
+
weaponsByKeyword = new Map();
|
|
36
40
|
constructor(raw = emptyRawData()) {
|
|
37
41
|
this.units = new Collection({
|
|
38
42
|
items: raw.units,
|
|
@@ -50,6 +54,12 @@ export class Dataset {
|
|
|
50
54
|
nameOf: (w) => w.name,
|
|
51
55
|
wrap: (w) => new WeaponView(w, this),
|
|
52
56
|
});
|
|
57
|
+
this.weaponKeywords = new Collection({
|
|
58
|
+
items: raw.weaponKeywords,
|
|
59
|
+
idOf: (k) => k.id,
|
|
60
|
+
nameOf: (k) => k.name,
|
|
61
|
+
wrap: (k) => new WeaponKeywordView(k, this),
|
|
62
|
+
});
|
|
53
63
|
this.factions = new Collection({
|
|
54
64
|
items: raw.factions,
|
|
55
65
|
idOf: (f) => f.id,
|
|
@@ -97,6 +107,71 @@ export class Dataset {
|
|
|
97
107
|
unitsWithWeapon(weaponId) {
|
|
98
108
|
return (this.unitsByWeapon.get(weaponId) ?? []).map((u) => new UnitView(u, this));
|
|
99
109
|
}
|
|
110
|
+
/** Weapons whose profiles reference the given weapon-keyword id. */
|
|
111
|
+
weaponsWithKeyword(keywordId) {
|
|
112
|
+
return (this.weaponsByKeyword.get(keywordId) ?? []).map((w) => new WeaponView(w, this));
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Enumerate every ability that could apply to the given unit in `phase`,
|
|
116
|
+
* grouped by source. The SPA uses this to render the abilities pane.
|
|
117
|
+
*/
|
|
118
|
+
eligibleAbilities(input, phase) {
|
|
119
|
+
return resolveEligibleAbilities(this, input, phase);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Attacker-perspective {@link Buff} stack for a (unit, phase) combination:
|
|
123
|
+
* intrinsic weapon-profile keywords plus every eligible ability whose DSL
|
|
124
|
+
* effect translates to an attacker-side buff (army, detachment, unit,
|
|
125
|
+
* leader, support, plus any stratagems the caller has opted into).
|
|
126
|
+
*
|
|
127
|
+
* The result includes only buffs the buff layer can express today — the
|
|
128
|
+
* `unsupported` half of the DSL→Buff translation is dropped here so callers
|
|
129
|
+
* who just want the stack don't need to thread diagnostics through. Use
|
|
130
|
+
* {@link AbilityView.describeBuffs} when you need the diagnostics for an
|
|
131
|
+
* individual ability. Symmetric to {@link defensiveBuffsFor}, which walks
|
|
132
|
+
* the same eligibility set under target perspective.
|
|
133
|
+
*/
|
|
134
|
+
buffsFor(input, context) {
|
|
135
|
+
return this.collectBuffs(input, context, "attacker");
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Defender-perspective buff stack for the chosen unit: walks the same
|
|
139
|
+
* eligible-abilities set as {@link buffsFor} but translates each ability's
|
|
140
|
+
* DSL effect as defensive (FNP, save mods from `stat-modifier Sv`,
|
|
141
|
+
* toughness mods from `stat-modifier T`, save rerolls, incoming hit
|
|
142
|
+
* penalties from `bs-modifier`). Use this when the chosen unit is being
|
|
143
|
+
* crunched as the *target* — the engine reads `feelNoPain`/`saveMod`/
|
|
144
|
+
* `toughnessMod` out of `resolveBuffs` so wiring the result into `crunch`
|
|
145
|
+
* just means concatenating onto the existing `buffs` array.
|
|
146
|
+
*
|
|
147
|
+
* `weaponProfiles` are ignored under target perspective — weapon-keyword
|
|
148
|
+
* effects ride with the firing weapon, not the receiving unit.
|
|
149
|
+
*/
|
|
150
|
+
defensiveBuffsFor(input, context) {
|
|
151
|
+
return this.collectBuffs(input, context, "target");
|
|
152
|
+
}
|
|
153
|
+
/** Shared implementation for buffsFor / defensiveBuffsFor. */
|
|
154
|
+
collectBuffs(input, context, perspective) {
|
|
155
|
+
const out = [];
|
|
156
|
+
// Weapon-profile keywords are attacker-only.
|
|
157
|
+
if (perspective === "attacker") {
|
|
158
|
+
for (const ref of input.weaponProfiles ?? []) {
|
|
159
|
+
const weapon = this.weapons.get(ref.weaponId);
|
|
160
|
+
if (!weapon)
|
|
161
|
+
continue;
|
|
162
|
+
out.push(...weapon.profileBuffs(ref.profileIndex, context));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const optedIn = new Set(input.optedInStratagemIds ?? []);
|
|
166
|
+
for (const entry of this.eligibleAbilities(input, context.phase)) {
|
|
167
|
+
if (entry.source.kind === "detachment-stratagem" && !optedIn.has(entry.source.stratagemId)) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const source = bufferSourceFromEligible(entry);
|
|
171
|
+
out.push(...entry.ability.getBuffs(source, context, perspective));
|
|
172
|
+
}
|
|
173
|
+
return out;
|
|
174
|
+
}
|
|
100
175
|
buildIndexes(raw) {
|
|
101
176
|
for (const pm of raw.phaseMappings) {
|
|
102
177
|
const key = `${pm.source_type}:${pm.source_id}`;
|
|
@@ -113,6 +188,22 @@ export class Dataset {
|
|
|
113
188
|
for (const weaponId of unit.weapon_ids ?? [])
|
|
114
189
|
push(this.unitsByWeapon, weaponId, unit);
|
|
115
190
|
}
|
|
191
|
+
const seenByKeyword = new Map();
|
|
192
|
+
for (const weapon of raw.weapons) {
|
|
193
|
+
for (const profile of weapon.profiles) {
|
|
194
|
+
for (const ref of profile.keywords ?? []) {
|
|
195
|
+
let seen = seenByKeyword.get(ref.keyword_id);
|
|
196
|
+
if (!seen) {
|
|
197
|
+
seen = new Set();
|
|
198
|
+
seenByKeyword.set(ref.keyword_id, seen);
|
|
199
|
+
}
|
|
200
|
+
if (seen.has(weapon.id))
|
|
201
|
+
continue;
|
|
202
|
+
seen.add(weapon.id);
|
|
203
|
+
push(this.weaponsByKeyword, ref.keyword_id, weapon);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
116
207
|
}
|
|
117
208
|
}
|
|
118
209
|
/** Build a passthrough collection for an id-bearing record type. */
|
|
@@ -132,3 +223,22 @@ function push(map, key, value) {
|
|
|
132
223
|
else
|
|
133
224
|
map.set(key, [value]);
|
|
134
225
|
}
|
|
226
|
+
/** Map an EligibleAbility back to the BuffSource the translator expects. */
|
|
227
|
+
function bufferSourceFromEligible(entry) {
|
|
228
|
+
const abilityId = entry.ability.id;
|
|
229
|
+
switch (entry.source.kind) {
|
|
230
|
+
case "army":
|
|
231
|
+
return { kind: "ability", abilityId, abilityKind: "army" };
|
|
232
|
+
case "detachment":
|
|
233
|
+
return { kind: "ability", abilityId, abilityKind: "detachment" };
|
|
234
|
+
case "detachment-stratagem":
|
|
235
|
+
return { kind: "ability", abilityId, abilityKind: "detachment-stratagem" };
|
|
236
|
+
case "unit":
|
|
237
|
+
return { kind: "ability", abilityId, abilityKind: "unit" };
|
|
238
|
+
case "leader":
|
|
239
|
+
return { kind: "ability", abilityId, abilityKind: "leader" };
|
|
240
|
+
case "support":
|
|
241
|
+
return { kind: "ability", abilityId, abilityKind: "support" };
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=dataset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataset.js","sourceRoot":"","sources":["../../src/data/dataset.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * {@link Dataset} ties the embedded records together: it owns every\n * {@link Collection}, builds the cross-entity indexes once, and is the `this`\n * the linked views resolve against.\n *\n * @packageDocumentation\n */\nimport type {\n DeploymentPattern,\n Detachment,\n Enhancement,\n ForceDisposition,\n GameVersion,\n InteractionFlag,\n LeaderAttachment,\n Mission,\n MissionMatchup,\n Phase,\n ResourcePool,\n SecondaryCard,\n Stratagem,\n TimingFlag,\n Unit,\n UnitComposition,\n WargearOption,\n WeaponKeyword,\n} from \"../generated.js\";\nimport { Collection } from \"./collection.js\";\nimport {\n AbilityView,\n FactionView,\n UnitView,\n WeaponKeywordView,\n WeaponView,\n} from \"./entities.js\";\nimport { emptyRawData, type RawData } from \"./types.js\";\nimport { RAW_DATA } from \"./bundle.generated.js\";\nimport type { Buff, BuffSource, EngineContext } from \"../cruncher/buffs.js\";\nimport {\n resolveEligibleAbilities,\n type EligibilityInput,\n type EligibleAbility,\n} from \"../abilities-resolver/index.js\";\n\n/** The whole dataset, with linked accessors over every entity collection. */\nexport class Dataset {\n // Richly-linked collections.\n readonly units: Collection<Unit, UnitView>;\n readonly weapons: Collection<RawData[\"weapons\"][number], WeaponView>;\n readonly weaponKeywords: Collection<WeaponKeyword, WeaponKeywordView>;\n readonly factions: Collection<RawData[\"factions\"][number], FactionView>;\n readonly abilities: Collection<RawData[\"abilities\"][number], AbilityView>;\n\n // Id-bearing collections without bespoke views (records returned as-is).\n readonly detachments: Collection<Detachment, Detachment>;\n readonly enhancements: Collection<Enhancement, Enhancement>;\n readonly stratagems: Collection<Stratagem, Stratagem>;\n readonly wargearOptions: Collection<WargearOption, WargearOption>;\n readonly missions: Collection<Mission, Mission>;\n readonly missionMatchups: Collection<MissionMatchup, MissionMatchup>;\n readonly secondaryCards: Collection<SecondaryCard, SecondaryCard>;\n readonly deploymentPatterns: Collection<DeploymentPattern, DeploymentPattern>;\n readonly forceDispositions: Collection<ForceDisposition, ForceDisposition>;\n readonly resourcePools: Collection<ResourcePool, ResourcePool>;\n\n // Id-less collections, exposed as plain arrays.\n readonly leaderAttachments: readonly LeaderAttachment[];\n readonly unitCompositions: readonly UnitComposition[];\n readonly gameVersions: readonly GameVersion[];\n readonly timingFlags: readonly TimingFlag[];\n readonly interactionFlags: readonly InteractionFlag[];\n readonly phaseMappings: readonly RawData[\"phaseMappings\"][number][];\n\n /** `source_type:source_id` → unioned phases. */\n private readonly phaseIndex = new Map<string, Phase[]>();\n /** ability id → units that list it. */\n private readonly unitsByAbility = new Map<string, Unit[]>();\n /** weapon id → units that list it. */\n private readonly unitsByWeapon = new Map<string, Unit[]>();\n /** weapon-keyword id → weapons whose profiles reference it. */\n private readonly weaponsByKeyword = new Map<string, RawData[\"weapons\"][number][]>();\n\n constructor(raw: RawData = emptyRawData()) {\n this.units = new Collection({\n items: raw.units,\n idOf: (u) => u.id,\n // The same unit id is shared across factions (e.g. ministorum-priest);\n // keep each faction's copy, collapse only true within-faction duplicates.\n dedupeKeyOf: (u) => `${u.faction_id}::${u.id}`,\n nameOf: (u) => u.name,\n factionOf: (u) => u.faction_id,\n wrap: (u) => new UnitView(u, this),\n });\n this.weapons = new Collection({\n items: raw.weapons,\n idOf: (w) => w.id,\n nameOf: (w) => w.name,\n wrap: (w) => new WeaponView(w, this),\n });\n this.weaponKeywords = new Collection({\n items: raw.weaponKeywords,\n idOf: (k) => k.id,\n nameOf: (k) => k.name,\n wrap: (k) => new WeaponKeywordView(k, this),\n });\n this.factions = new Collection({\n items: raw.factions,\n idOf: (f) => f.id,\n nameOf: (f) => f.name,\n wrap: (f) => new FactionView(f, this),\n });\n this.abilities = new Collection({\n items: raw.abilities,\n idOf: (a) => a.ability_id,\n nameOf: (a) => a.name,\n factionOf: (a) => a.faction_id,\n wrap: (a) => new AbilityView(a, this),\n });\n\n this.detachments = idCollection(raw.detachments, (d) => d.faction_id);\n this.enhancements = idCollection(raw.enhancements);\n this.stratagems = idCollection(raw.stratagems);\n this.wargearOptions = idCollection(raw.wargearOptions);\n this.missions = idCollection(raw.missions);\n this.missionMatchups = idCollection(raw.missionMatchups);\n this.secondaryCards = idCollection(raw.secondaryCards);\n this.deploymentPatterns = idCollection(raw.deploymentPatterns);\n this.forceDispositions = idCollection(raw.forceDispositions);\n this.resourcePools = idCollection(raw.resourcePools);\n\n this.leaderAttachments = raw.leaderAttachments;\n this.unitCompositions = raw.unitCompositions;\n this.gameVersions = raw.gameVersions;\n this.timingFlags = raw.timingFlags;\n this.interactionFlags = raw.interactionFlags;\n this.phaseMappings = raw.phaseMappings;\n\n this.buildIndexes(raw);\n }\n\n /** The dataset built from the package's embedded data. */\n static embedded(): Dataset {\n return new Dataset(RAW_DATA);\n }\n\n /** Phases a source acts in, unioned across its phase-mappings. */\n phasesFor(sourceType: string, sourceId: string): Phase[] {\n return this.phaseIndex.get(`${sourceType}:${sourceId}`) ?? [];\n }\n\n /** Units that list the given ability id. */\n unitsWithAbility(abilityId: string): UnitView[] {\n return (this.unitsByAbility.get(abilityId) ?? []).map((u) => new UnitView(u, this));\n }\n\n /** Units that list the given weapon id. */\n unitsWithWeapon(weaponId: string): UnitView[] {\n return (this.unitsByWeapon.get(weaponId) ?? []).map((u) => new UnitView(u, this));\n }\n\n /** Weapons whose profiles reference the given weapon-keyword id. */\n weaponsWithKeyword(keywordId: string): WeaponView[] {\n return (this.weaponsByKeyword.get(keywordId) ?? []).map((w) => new WeaponView(w, this));\n }\n\n /**\n * Enumerate every ability that could apply to the given unit in `phase`,\n * grouped by source. The SPA uses this to render the abilities pane.\n */\n eligibleAbilities(input: EligibilityInput, phase: Phase): EligibleAbility[] {\n return resolveEligibleAbilities(this, input, phase);\n }\n\n /**\n * Attacker-perspective {@link Buff} stack for a (unit, phase) combination:\n * intrinsic weapon-profile keywords plus every eligible ability whose DSL\n * effect translates to an attacker-side buff (army, detachment, unit,\n * leader, support, plus any stratagems the caller has opted into).\n *\n * The result includes only buffs the buff layer can express today — the\n * `unsupported` half of the DSL→Buff translation is dropped here so callers\n * who just want the stack don't need to thread diagnostics through. Use\n * {@link AbilityView.describeBuffs} when you need the diagnostics for an\n * individual ability. Symmetric to {@link defensiveBuffsFor}, which walks\n * the same eligibility set under target perspective.\n */\n buffsFor(\n input: EligibilityInput & {\n weaponProfiles?: { weaponId: string; profileIndex: number }[];\n /** Stratagem ids the caller has opted into spending CP on. */\n optedInStratagemIds?: string[];\n },\n context: EngineContext,\n ): Buff[] {\n return this.collectBuffs(input, context, \"attacker\");\n }\n\n /**\n * Defender-perspective buff stack for the chosen unit: walks the same\n * eligible-abilities set as {@link buffsFor} but translates each ability's\n * DSL effect as defensive (FNP, save mods from `stat-modifier Sv`,\n * toughness mods from `stat-modifier T`, save rerolls, incoming hit\n * penalties from `bs-modifier`). Use this when the chosen unit is being\n * crunched as the *target* — the engine reads `feelNoPain`/`saveMod`/\n * `toughnessMod` out of `resolveBuffs` so wiring the result into `crunch`\n * just means concatenating onto the existing `buffs` array.\n *\n * `weaponProfiles` are ignored under target perspective — weapon-keyword\n * effects ride with the firing weapon, not the receiving unit.\n */\n defensiveBuffsFor(\n input: EligibilityInput & { optedInStratagemIds?: string[] },\n context: EngineContext,\n ): Buff[] {\n return this.collectBuffs(input, context, \"target\");\n }\n\n /** Shared implementation for buffsFor / defensiveBuffsFor. */\n private collectBuffs(\n input: EligibilityInput & {\n weaponProfiles?: { weaponId: string; profileIndex: number }[];\n optedInStratagemIds?: string[];\n },\n context: EngineContext,\n perspective: \"attacker\" | \"target\",\n ): Buff[] {\n const out: Buff[] = [];\n\n // Weapon-profile keywords are attacker-only.\n if (perspective === \"attacker\") {\n for (const ref of input.weaponProfiles ?? []) {\n const weapon = this.weapons.get(ref.weaponId);\n if (!weapon) continue;\n out.push(...weapon.profileBuffs(ref.profileIndex, context));\n }\n }\n\n const optedIn = new Set(input.optedInStratagemIds ?? []);\n for (const entry of this.eligibleAbilities(input, context.phase)) {\n if (entry.source.kind === \"detachment-stratagem\" && !optedIn.has(entry.source.stratagemId)) {\n continue;\n }\n const source = bufferSourceFromEligible(entry);\n out.push(...entry.ability.getBuffs(source, context, perspective));\n }\n\n return out;\n }\n\n private buildIndexes(raw: RawData): void {\n for (const pm of raw.phaseMappings) {\n const key = `${pm.source_type}:${pm.source_id}`;\n const existing = this.phaseIndex.get(key) ?? [];\n for (const phase of pm.phases) {\n if (!existing.includes(phase)) existing.push(phase);\n }\n this.phaseIndex.set(key, existing);\n }\n for (const unit of raw.units) {\n for (const abilityId of unit.ability_ids ?? []) push(this.unitsByAbility, abilityId, unit);\n for (const weaponId of unit.weapon_ids ?? []) push(this.unitsByWeapon, weaponId, unit);\n }\n const seenByKeyword = new Map<string, Set<string>>();\n for (const weapon of raw.weapons) {\n for (const profile of weapon.profiles) {\n for (const ref of profile.keywords ?? []) {\n let seen = seenByKeyword.get(ref.keyword_id);\n if (!seen) {\n seen = new Set();\n seenByKeyword.set(ref.keyword_id, seen);\n }\n if (seen.has(weapon.id)) continue;\n seen.add(weapon.id);\n push(this.weaponsByKeyword, ref.keyword_id, weapon);\n }\n }\n }\n }\n}\n\n/** Build a passthrough collection for an id-bearing record type. */\nfunction idCollection<T extends { id: string }>(\n items: T[],\n factionOf?: (item: T) => string | null | undefined,\n): Collection<T, T> {\n return new Collection<T, T>({\n items,\n idOf: (i) => i.id,\n nameOf: (i) => (i as { name?: string }).name,\n factionOf,\n wrap: (i) => i,\n });\n}\n\nfunction push<T>(map: Map<string, T[]>, key: string, value: T): void {\n const existing = map.get(key);\n if (existing) existing.push(value);\n else map.set(key, [value]);\n}\n\n/** Map an EligibleAbility back to the BuffSource the translator expects. */\nfunction bufferSourceFromEligible(entry: EligibleAbility): BuffSource {\n const abilityId = entry.ability.id;\n switch (entry.source.kind) {\n case \"army\":\n return { kind: \"ability\", abilityId, abilityKind: \"army\" };\n case \"detachment\":\n return { kind: \"ability\", abilityId, abilityKind: \"detachment\" };\n case \"detachment-stratagem\":\n return { kind: \"ability\", abilityId, abilityKind: \"detachment-stratagem\" };\n case \"unit\":\n return { kind: \"ability\", abilityId, abilityKind: \"unit\" };\n case \"leader\":\n return { kind: \"ability\", abilityId, abilityKind: \"leader\" };\n case \"support\":\n return { kind: \"ability\", abilityId, abilityKind: \"support\" };\n }\n}\n"]}
|
package/dist/data/entities.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Linked views over the
|
|
2
|
+
* Linked views over the richly-connected entity types. Each wraps a raw
|
|
3
3
|
* generated record and resolves its relationships lazily against the owning
|
|
4
4
|
* {@link Dataset}; the full underlying record is always available via `.raw`.
|
|
5
5
|
*
|
|
6
6
|
* @packageDocumentation
|
|
7
7
|
*/
|
|
8
|
-
import type { AbilityDSLEntry, Faction, Phase, Unit, Weapon } from "../generated.js";
|
|
8
|
+
import type { AbilityDSLEntry, Faction, Phase, Unit, Weapon, WeaponKeyword } from "../generated.js";
|
|
9
|
+
import type { Buff, BuffSource, EngineContext } from "../cruncher/buffs.js";
|
|
10
|
+
import { type TranslationPerspective, type UnsupportedFragment } from "../cruncher/from-dsl.js";
|
|
9
11
|
import type { Dataset } from "./dataset.js";
|
|
10
12
|
/** A unit, linked to its faction, weapons, and abilities. */
|
|
11
13
|
export declare class UnitView {
|
|
@@ -23,6 +25,12 @@ export declare class UnitView {
|
|
|
23
25
|
get weapons(): WeaponView[];
|
|
24
26
|
/** Abilities referenced by `ability_ids`; unresolved ids are skipped. */
|
|
25
27
|
get abilities(): AbilityView[];
|
|
28
|
+
/**
|
|
29
|
+
* The stat profile at index `i` (default 0). Returns the schema-generated
|
|
30
|
+
* profile object directly so callers can feed it straight to the engine
|
|
31
|
+
* without an intermediate wrapper.
|
|
32
|
+
*/
|
|
33
|
+
profileAt(i?: number): Unit["profiles"][number];
|
|
26
34
|
}
|
|
27
35
|
/**
|
|
28
36
|
* An ability, linked to the phases it acts in and the units that have it.
|
|
@@ -47,6 +55,25 @@ export declare class AbilityView {
|
|
|
47
55
|
get phases(): Phase[];
|
|
48
56
|
/** Units that list this ability in their `ability_ids`. */
|
|
49
57
|
get units(): UnitView[];
|
|
58
|
+
/**
|
|
59
|
+
* Buff stack this ability contributes against `context`, with provenance
|
|
60
|
+
* tagged via `source` (the caller knows whether this ability is being read
|
|
61
|
+
* as army, detachment, unit, leader, etc.). DSL branches the buff layer
|
|
62
|
+
* can't auto-apply are dropped here; call {@link describeBuffs} if you
|
|
63
|
+
* also want the diagnostics. `perspective` defaults to `"attacker"`; pass
|
|
64
|
+
* `"target"` to translate the ability as a defensive buff (FNP, T/Sv
|
|
65
|
+
* stat-mods, save rerolls, incoming hit penalties).
|
|
66
|
+
*/
|
|
67
|
+
getBuffs(source: BuffSource, context?: EngineContext, perspective?: TranslationPerspective): Buff[];
|
|
68
|
+
/**
|
|
69
|
+
* Full DSL→Buff translation, including the `unsupported` list of effect
|
|
70
|
+
* fragments the buff layer can't model. The SPA renders these as warnings
|
|
71
|
+
* so users see which abilities have effects that need a manual toggle.
|
|
72
|
+
*/
|
|
73
|
+
describeBuffs(source: BuffSource, context?: EngineContext, perspective?: TranslationPerspective): {
|
|
74
|
+
applied: Buff[];
|
|
75
|
+
unsupported: UnsupportedFragment[];
|
|
76
|
+
};
|
|
50
77
|
}
|
|
51
78
|
/** A weapon, linked to the units that carry it. */
|
|
52
79
|
export declare class WeaponView {
|
|
@@ -60,6 +87,46 @@ export declare class WeaponView {
|
|
|
60
87
|
get name(): string;
|
|
61
88
|
/** Units that list this weapon in their `weapon_ids`. */
|
|
62
89
|
get units(): UnitView[];
|
|
90
|
+
/** The stat profile at index `i` (default 0). */
|
|
91
|
+
profileAt(i?: number): Weapon["profiles"][number];
|
|
92
|
+
/**
|
|
93
|
+
* Catalog views for each keyword referenced by profile `i`, paired with the
|
|
94
|
+
* reference-site parameters. Unresolved keyword ids are skipped.
|
|
95
|
+
*/
|
|
96
|
+
keywordsAt(i?: number): {
|
|
97
|
+
keyword: WeaponKeywordView;
|
|
98
|
+
parameters: Record<string, unknown> | undefined;
|
|
99
|
+
}[];
|
|
100
|
+
/**
|
|
101
|
+
* Buffs contributed by profile `i`'s intrinsic keywords against `context` —
|
|
102
|
+
* the natural "what does this profile bring on its own?" call the engine
|
|
103
|
+
* makes automatically before adding ability/manual buffs.
|
|
104
|
+
*/
|
|
105
|
+
profileBuffs(i: number | undefined, context: EngineContext): Buff[];
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* A weapon-keyword catalog entry, linked to the weapons whose profiles
|
|
109
|
+
* reference it. Exposes the keyword's mechanical effect as a buff stack
|
|
110
|
+
* via {@link getBuffs}.
|
|
111
|
+
*/
|
|
112
|
+
export declare class WeaponKeywordView {
|
|
113
|
+
/** The full generated `WeaponKeyword` record. */
|
|
114
|
+
readonly raw: WeaponKeyword;
|
|
115
|
+
private readonly ds;
|
|
116
|
+
constructor(
|
|
117
|
+
/** The full generated `WeaponKeyword` record. */
|
|
118
|
+
raw: WeaponKeyword, ds: Dataset);
|
|
119
|
+
get id(): string;
|
|
120
|
+
get name(): string;
|
|
121
|
+
/** Weapons whose profiles reference this keyword id. */
|
|
122
|
+
get weapons(): WeaponView[];
|
|
123
|
+
/**
|
|
124
|
+
* Buff contributions from this catalog entry, for one reference site:
|
|
125
|
+
* pass the keyword's `parameters` (e.g. `{ value: 1 }` for Sustained Hits 1)
|
|
126
|
+
* along with the `weaponId` that's carrying it (used as the buff source)
|
|
127
|
+
* and the engine `context` (e.g. attacker stationary?).
|
|
128
|
+
*/
|
|
129
|
+
getBuffs(parameters: Record<string, unknown> | undefined, weaponId: string, context: EngineContext): Buff[];
|
|
63
130
|
}
|
|
64
131
|
/** A faction, linked to its units and the records scoped to it. */
|
|
65
132
|
export declare class FactionView {
|
|
@@ -78,3 +145,4 @@ export declare class FactionView {
|
|
|
78
145
|
/** Distinct weapons carried by this faction's units. */
|
|
79
146
|
get weapons(): WeaponView[];
|
|
80
147
|
}
|
|
148
|
+
//# sourceMappingURL=entities.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entities.d.ts","sourceRoot":"","sources":["../../src/data/entities.ts"],"names":[],"mappings":"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"}
|
package/dist/data/entities.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { buffsFromKeyword } from "../cruncher/from-keyword.js";
|
|
2
|
+
import { effectToBuffs, } from "../cruncher/from-dsl.js";
|
|
1
3
|
/** A unit, linked to its faction, weapons, and abilities. */
|
|
2
4
|
export class UnitView {
|
|
3
5
|
raw;
|
|
@@ -26,6 +28,18 @@ export class UnitView {
|
|
|
26
28
|
get abilities() {
|
|
27
29
|
return resolveAll(this.raw.ability_ids, (id) => this.ds.abilities.get(id));
|
|
28
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* The stat profile at index `i` (default 0). Returns the schema-generated
|
|
33
|
+
* profile object directly so callers can feed it straight to the engine
|
|
34
|
+
* without an intermediate wrapper.
|
|
35
|
+
*/
|
|
36
|
+
profileAt(i = 0) {
|
|
37
|
+
const profile = this.raw.profiles[i];
|
|
38
|
+
if (profile === undefined) {
|
|
39
|
+
throw new RangeError(`UnitView(${this.raw.id}).profileAt(${i}): only ${this.raw.profiles.length} profile(s) defined`);
|
|
40
|
+
}
|
|
41
|
+
return profile;
|
|
42
|
+
}
|
|
29
43
|
}
|
|
30
44
|
/**
|
|
31
45
|
* An ability, linked to the phases it acts in and the units that have it.
|
|
@@ -60,6 +74,27 @@ export class AbilityView {
|
|
|
60
74
|
get units() {
|
|
61
75
|
return this.ds.unitsWithAbility(this.raw.ability_id);
|
|
62
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Buff stack this ability contributes against `context`, with provenance
|
|
79
|
+
* tagged via `source` (the caller knows whether this ability is being read
|
|
80
|
+
* as army, detachment, unit, leader, etc.). DSL branches the buff layer
|
|
81
|
+
* can't auto-apply are dropped here; call {@link describeBuffs} if you
|
|
82
|
+
* also want the diagnostics. `perspective` defaults to `"attacker"`; pass
|
|
83
|
+
* `"target"` to translate the ability as a defensive buff (FNP, T/Sv
|
|
84
|
+
* stat-mods, save rerolls, incoming hit penalties).
|
|
85
|
+
*/
|
|
86
|
+
getBuffs(source, context, perspective = "attacker") {
|
|
87
|
+
return this.describeBuffs(source, context, perspective).applied;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Full DSL→Buff translation, including the `unsupported` list of effect
|
|
91
|
+
* fragments the buff layer can't model. The SPA renders these as warnings
|
|
92
|
+
* so users see which abilities have effects that need a manual toggle.
|
|
93
|
+
*/
|
|
94
|
+
describeBuffs(source, context, perspective = "attacker") {
|
|
95
|
+
const ctx = context ?? { phase: "shooting" };
|
|
96
|
+
return effectToBuffs(this.raw.effect, source, ctx, perspective);
|
|
97
|
+
}
|
|
63
98
|
}
|
|
64
99
|
/** A weapon, linked to the units that carry it. */
|
|
65
100
|
export class WeaponView {
|
|
@@ -81,6 +116,92 @@ export class WeaponView {
|
|
|
81
116
|
get units() {
|
|
82
117
|
return this.ds.unitsWithWeapon(this.raw.id);
|
|
83
118
|
}
|
|
119
|
+
/** The stat profile at index `i` (default 0). */
|
|
120
|
+
profileAt(i = 0) {
|
|
121
|
+
const profile = this.raw.profiles[i];
|
|
122
|
+
if (profile === undefined) {
|
|
123
|
+
throw new RangeError(`WeaponView(${this.raw.id}).profileAt(${i}): only ${this.raw.profiles.length} profile(s) defined`);
|
|
124
|
+
}
|
|
125
|
+
return profile;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Catalog views for each keyword referenced by profile `i`, paired with the
|
|
129
|
+
* reference-site parameters. Unresolved keyword ids are skipped.
|
|
130
|
+
*/
|
|
131
|
+
keywordsAt(i = 0) {
|
|
132
|
+
const profile = this.profileAt(i);
|
|
133
|
+
const refs = profile.keywords ?? [];
|
|
134
|
+
const out = [];
|
|
135
|
+
for (const ref of refs) {
|
|
136
|
+
const view = this.ds.weaponKeywords.get(ref.keyword_id);
|
|
137
|
+
if (!view)
|
|
138
|
+
continue;
|
|
139
|
+
out.push({
|
|
140
|
+
keyword: view,
|
|
141
|
+
parameters: ref.parameters,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Buffs contributed by profile `i`'s intrinsic keywords against `context` —
|
|
148
|
+
* the natural "what does this profile bring on its own?" call the engine
|
|
149
|
+
* makes automatically before adding ability/manual buffs.
|
|
150
|
+
*/
|
|
151
|
+
profileBuffs(i, context) {
|
|
152
|
+
const index = i ?? 0;
|
|
153
|
+
const out = [];
|
|
154
|
+
for (const { keyword, parameters } of this.keywordsAt(index)) {
|
|
155
|
+
out.push(...buffsFromKeyword({
|
|
156
|
+
keywordId: keyword.id,
|
|
157
|
+
weaponId: this.raw.id,
|
|
158
|
+
effect: keyword.raw.effect,
|
|
159
|
+
...(parameters !== undefined ? { parameters } : {}),
|
|
160
|
+
context,
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* A weapon-keyword catalog entry, linked to the weapons whose profiles
|
|
168
|
+
* reference it. Exposes the keyword's mechanical effect as a buff stack
|
|
169
|
+
* via {@link getBuffs}.
|
|
170
|
+
*/
|
|
171
|
+
export class WeaponKeywordView {
|
|
172
|
+
raw;
|
|
173
|
+
ds;
|
|
174
|
+
constructor(
|
|
175
|
+
/** The full generated `WeaponKeyword` record. */
|
|
176
|
+
raw, ds) {
|
|
177
|
+
this.raw = raw;
|
|
178
|
+
this.ds = ds;
|
|
179
|
+
}
|
|
180
|
+
get id() {
|
|
181
|
+
return this.raw.id;
|
|
182
|
+
}
|
|
183
|
+
get name() {
|
|
184
|
+
return this.raw.name;
|
|
185
|
+
}
|
|
186
|
+
/** Weapons whose profiles reference this keyword id. */
|
|
187
|
+
get weapons() {
|
|
188
|
+
return this.ds.weaponsWithKeyword(this.raw.id);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Buff contributions from this catalog entry, for one reference site:
|
|
192
|
+
* pass the keyword's `parameters` (e.g. `{ value: 1 }` for Sustained Hits 1)
|
|
193
|
+
* along with the `weaponId` that's carrying it (used as the buff source)
|
|
194
|
+
* and the engine `context` (e.g. attacker stationary?).
|
|
195
|
+
*/
|
|
196
|
+
getBuffs(parameters, weaponId, context) {
|
|
197
|
+
return buffsFromKeyword({
|
|
198
|
+
keywordId: this.raw.id,
|
|
199
|
+
weaponId,
|
|
200
|
+
effect: this.raw.effect,
|
|
201
|
+
...(parameters !== undefined ? { parameters } : {}),
|
|
202
|
+
context,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
84
205
|
}
|
|
85
206
|
/** A faction, linked to its units and the records scoped to it. */
|
|
86
207
|
export class FactionView {
|
|
@@ -131,3 +252,4 @@ function resolveAll(ids, get) {
|
|
|
131
252
|
}
|
|
132
253
|
return out;
|
|
133
254
|
}
|
|
255
|
+
//# sourceMappingURL=entities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entities.js","sourceRoot":"","sources":["../../src/data/entities.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Linked views over the richly-connected entity types. Each wraps a raw\n * generated record and resolves its relationships lazily against the owning\n * {@link Dataset}; the full underlying record is always available via `.raw`.\n *\n * @packageDocumentation\n */\nimport type {\n AbilityDSLEntry,\n Faction,\n Phase,\n Unit,\n Weapon,\n WeaponKeyword,\n} from \"../generated.js\";\nimport type { Buff, BuffSource, EngineContext } from \"../cruncher/buffs.js\";\nimport { buffsFromKeyword } from \"../cruncher/from-keyword.js\";\nimport {\n effectToBuffs,\n type TranslationPerspective,\n type UnsupportedFragment,\n} from \"../cruncher/from-dsl.js\";\nimport type { Dataset } from \"./dataset.js\";\n\n/** A unit, linked to its faction, weapons, and abilities. */\nexport class UnitView {\n constructor(\n /** The full generated `Unit` record. */\n readonly raw: Unit,\n private readonly ds: Dataset,\n ) {}\n\n get id(): string {\n return this.raw.id;\n }\n\n get name(): string {\n return this.raw.name;\n }\n\n /** The unit's faction, or `undefined` if its `faction_id` is unknown. */\n get faction(): FactionView | undefined {\n return this.ds.factions.get(this.raw.faction_id);\n }\n\n /** Weapons referenced by `weapon_ids`; unresolved ids are skipped. */\n get weapons(): WeaponView[] {\n return resolveAll(this.raw.weapon_ids, (id) => this.ds.weapons.get(id));\n }\n\n /** Abilities referenced by `ability_ids`; unresolved ids are skipped. */\n get abilities(): AbilityView[] {\n return resolveAll(this.raw.ability_ids, (id) => this.ds.abilities.get(id));\n }\n\n /**\n * The stat profile at index `i` (default 0). Returns the schema-generated\n * profile object directly so callers can feed it straight to the engine\n * without an intermediate wrapper.\n */\n profileAt(i = 0): Unit[\"profiles\"][number] {\n const profile = this.raw.profiles[i];\n if (profile === undefined) {\n throw new RangeError(\n `UnitView(${this.raw.id}).profileAt(${i}): only ${this.raw.profiles.length} profile(s) defined`,\n );\n }\n return profile;\n }\n}\n\n/**\n * An ability, linked to the phases it acts in and the units that have it.\n *\n * Phases are not stored on the ability — they live in `phase-mappings` records.\n *\n * @example\n * units.find(\"Kharn\")!.abilities\n * .filter(a => a.phases.includes(\"shooting\"));\n */\nexport class AbilityView {\n constructor(\n /** The full generated ability record. */\n readonly raw: AbilityDSLEntry,\n private readonly ds: Dataset,\n ) {}\n\n /** The ability's id (`ability_id` in the raw record). */\n get id(): string {\n return this.raw.ability_id;\n }\n\n get name(): string {\n return this.raw.name;\n }\n\n /** Game phases this ability acts in, unioned across its phase-mappings. */\n get phases(): Phase[] {\n return this.ds.phasesFor(\"ability\", this.raw.ability_id);\n }\n\n /** Units that list this ability in their `ability_ids`. */\n get units(): UnitView[] {\n return this.ds.unitsWithAbility(this.raw.ability_id);\n }\n\n /**\n * Buff stack this ability contributes against `context`, with provenance\n * tagged via `source` (the caller knows whether this ability is being read\n * as army, detachment, unit, leader, etc.). DSL branches the buff layer\n * can't auto-apply are dropped here; call {@link describeBuffs} if you\n * also want the diagnostics. `perspective` defaults to `\"attacker\"`; pass\n * `\"target\"` to translate the ability as a defensive buff (FNP, T/Sv\n * stat-mods, save rerolls, incoming hit penalties).\n */\n getBuffs(\n source: BuffSource,\n context?: EngineContext,\n perspective: TranslationPerspective = \"attacker\",\n ): Buff[] {\n return this.describeBuffs(source, context, perspective).applied;\n }\n\n /**\n * Full DSL→Buff translation, including the `unsupported` list of effect\n * fragments the buff layer can't model. The SPA renders these as warnings\n * so users see which abilities have effects that need a manual toggle.\n */\n describeBuffs(\n source: BuffSource,\n context?: EngineContext,\n perspective: TranslationPerspective = \"attacker\",\n ): { applied: Buff[]; unsupported: UnsupportedFragment[] } {\n const ctx: EngineContext = context ?? { phase: \"shooting\" };\n return effectToBuffs(this.raw.effect, source, ctx, perspective);\n }\n}\n\n/** A weapon, linked to the units that carry it. */\nexport class WeaponView {\n constructor(\n /** The full generated `Weapon` record. */\n readonly raw: Weapon,\n private readonly ds: Dataset,\n ) {}\n\n get id(): string {\n return this.raw.id;\n }\n\n get name(): string {\n return this.raw.name;\n }\n\n /** Units that list this weapon in their `weapon_ids`. */\n get units(): UnitView[] {\n return this.ds.unitsWithWeapon(this.raw.id);\n }\n\n /** The stat profile at index `i` (default 0). */\n profileAt(i = 0): Weapon[\"profiles\"][number] {\n const profile = this.raw.profiles[i];\n if (profile === undefined) {\n throw new RangeError(\n `WeaponView(${this.raw.id}).profileAt(${i}): only ${this.raw.profiles.length} profile(s) defined`,\n );\n }\n return profile;\n }\n\n /**\n * Catalog views for each keyword referenced by profile `i`, paired with the\n * reference-site parameters. Unresolved keyword ids are skipped.\n */\n keywordsAt(\n i = 0,\n ): { keyword: WeaponKeywordView; parameters: Record<string, unknown> | undefined }[] {\n const profile = this.profileAt(i);\n const refs = profile.keywords ?? [];\n const out: { keyword: WeaponKeywordView; parameters: Record<string, unknown> | undefined }[] = [];\n for (const ref of refs) {\n const view = this.ds.weaponKeywords.get(ref.keyword_id);\n if (!view) continue;\n out.push({\n keyword: view,\n parameters: ref.parameters as Record<string, unknown> | undefined,\n });\n }\n return out;\n }\n\n /**\n * Buffs contributed by profile `i`'s intrinsic keywords against `context` —\n * the natural \"what does this profile bring on its own?\" call the engine\n * makes automatically before adding ability/manual buffs.\n */\n profileBuffs(i: number | undefined, context: EngineContext): Buff[] {\n const index = i ?? 0;\n const out: Buff[] = [];\n for (const { keyword, parameters } of this.keywordsAt(index)) {\n out.push(\n ...buffsFromKeyword({\n keywordId: keyword.id,\n weaponId: this.raw.id,\n effect: keyword.raw.effect,\n ...(parameters !== undefined ? { parameters } : {}),\n context,\n }),\n );\n }\n return out;\n }\n}\n\n/**\n * A weapon-keyword catalog entry, linked to the weapons whose profiles\n * reference it. Exposes the keyword's mechanical effect as a buff stack\n * via {@link getBuffs}.\n */\nexport class WeaponKeywordView {\n constructor(\n /** The full generated `WeaponKeyword` record. */\n readonly raw: WeaponKeyword,\n private readonly ds: Dataset,\n ) {}\n\n get id(): string {\n return this.raw.id;\n }\n\n get name(): string {\n return this.raw.name;\n }\n\n /** Weapons whose profiles reference this keyword id. */\n get weapons(): WeaponView[] {\n return this.ds.weaponsWithKeyword(this.raw.id);\n }\n\n /**\n * Buff contributions from this catalog entry, for one reference site:\n * pass the keyword's `parameters` (e.g. `{ value: 1 }` for Sustained Hits 1)\n * along with the `weaponId` that's carrying it (used as the buff source)\n * and the engine `context` (e.g. attacker stationary?).\n */\n getBuffs(\n parameters: Record<string, unknown> | undefined,\n weaponId: string,\n context: EngineContext,\n ): Buff[] {\n return buffsFromKeyword({\n keywordId: this.raw.id,\n weaponId,\n effect: this.raw.effect,\n ...(parameters !== undefined ? { parameters } : {}),\n context,\n });\n }\n}\n\n/** A faction, linked to its units and the records scoped to it. */\nexport class FactionView {\n constructor(\n /** The full generated `Faction` record. */\n readonly raw: Faction,\n private readonly ds: Dataset,\n ) {}\n\n get id(): string {\n return this.raw.id;\n }\n\n get name(): string {\n return this.raw.name;\n }\n\n /** Units whose `faction_id` is this faction (may be empty for successors). */\n get units(): UnitView[] {\n return this.ds.units.byFaction(this.raw.id);\n }\n\n /** Faction-scoped abilities (abilities whose `faction_id` is this faction). */\n get abilities(): AbilityView[] {\n return this.ds.abilities.byFaction(this.raw.id);\n }\n\n /** Distinct weapons carried by this faction's units. */\n get weapons(): WeaponView[] {\n const seen = new Set<string>();\n const out: WeaponView[] = [];\n for (const unit of this.units) {\n for (const weapon of unit.weapons) {\n if (seen.has(weapon.id)) continue;\n seen.add(weapon.id);\n out.push(weapon);\n }\n }\n return out;\n }\n}\n\n/** Resolve a list of ids, dropping any that don't resolve. */\nfunction resolveAll<V>(ids: string[] | undefined, get: (id: string) => V | undefined): V[] {\n const out: V[] = [];\n for (const id of ids ?? []) {\n const v = get(id);\n if (v !== undefined) out.push(v);\n }\n return out;\n}\n"]}
|