Family Expansions
This commit is contained in:
+220
-245
@@ -553,6 +553,27 @@ func collectBaseDataset(dataset nativeDataset) (nativeCollectedDataset, error) {
|
||||
if err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
}
|
||||
modulePaths, err := collectModulePaths(dataset.ModulesDir)
|
||||
if err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
}
|
||||
moduleData := make([]nativeGeneratedModule, 0, len(modulePaths))
|
||||
for _, path := range modulePaths {
|
||||
obj, err := loadJSONObject(path)
|
||||
if err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
}
|
||||
moduleData = append(moduleData, nativeGeneratedModule{
|
||||
Name: path,
|
||||
Data: obj,
|
||||
})
|
||||
}
|
||||
for _, module := range moduleData {
|
||||
columns, err = extendColumns(columns, module.Data, dataset.Name, module.Name)
|
||||
if err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
}
|
||||
}
|
||||
rawBaseRows, ok := baseData["rows"].([]any)
|
||||
if !ok {
|
||||
return nativeCollectedDataset{}, fmt.Errorf("dataset %s: rows must be an array", dataset.Name)
|
||||
@@ -804,26 +825,35 @@ func collectBaseDataset(dataset nativeDataset) (nativeCollectedDataset, error) {
|
||||
if err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
}
|
||||
for _, module := range generatedModules {
|
||||
updatedColumns, err := extendColumns(columns, module.Data, dataset.Name, module.Name)
|
||||
if err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
}
|
||||
if len(updatedColumns) != len(columns) {
|
||||
columns = updatedColumns
|
||||
ensureRowsExposeColumns(rows, columns)
|
||||
}
|
||||
}
|
||||
for _, module := range generatedModules {
|
||||
if err := applyModuleData(module.Name, module.Data, false); err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
}
|
||||
}
|
||||
|
||||
modulePaths, err := collectModulePaths(dataset.ModulesDir)
|
||||
if err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
for _, module := range moduleData {
|
||||
if err := applyModuleData(module.Name, module.Data, true); err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
}
|
||||
}
|
||||
for _, path := range modulePaths {
|
||||
moduleData, err := loadJSONObject(path)
|
||||
if err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
for _, module := range generatedModules {
|
||||
if !module.ApplyAfterModules {
|
||||
continue
|
||||
}
|
||||
if err := applyModuleData(path, moduleData, true); err != nil {
|
||||
if err := applyModuleData(module.Name, module.Data, false); err != nil {
|
||||
return nativeCollectedDataset{}, err
|
||||
}
|
||||
}
|
||||
|
||||
if dataset.Name == "spells" {
|
||||
normalizeCollectedSpellImpactScripts(rows, baseRowKeys)
|
||||
}
|
||||
@@ -871,8 +901,9 @@ func dedupeCollectedRows(rows []map[string]any) []map[string]any {
|
||||
}
|
||||
|
||||
type nativeGeneratedModule struct {
|
||||
Name string
|
||||
Data map[string]any
|
||||
Name string
|
||||
Data map[string]any
|
||||
ApplyAfterModules bool
|
||||
}
|
||||
|
||||
func collectGeneratedModules(dataset nativeDataset, lockData map[string]int) ([]nativeGeneratedModule, error) {
|
||||
@@ -887,37 +918,44 @@ func collectGeneratedModules(dataset nativeDataset, lockData map[string]int) ([]
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
modules := make([]nativeGeneratedModule, 0, len(paths))
|
||||
type generatedSource struct {
|
||||
path string
|
||||
obj map[string]any
|
||||
}
|
||||
sources := make([]generatedSource, 0, len(paths))
|
||||
for _, path := range paths {
|
||||
obj, err := loadJSONObject(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
family, _ := obj["family"].(string)
|
||||
switch family {
|
||||
case "skill_focus",
|
||||
"greater_skill_focus",
|
||||
"weapon_focus",
|
||||
"weapon_specialization",
|
||||
"greater_weapon_focus",
|
||||
"greater_weapon_specialization",
|
||||
"improved_critical",
|
||||
"overwhelming_critical",
|
||||
"proficiencies",
|
||||
"special_attacks":
|
||||
moduleData, err := buildFeatGeneratedModule(path, obj, ctx)
|
||||
if isFamilyExpansionObject(obj) {
|
||||
spec, err := parseFamilyExpansionSpec(path, obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
modules = append(modules, nativeGeneratedModule{
|
||||
Name: path,
|
||||
Data: moduleData,
|
||||
})
|
||||
case "":
|
||||
return nil, fmt.Errorf("dataset %s: generated file %s is missing family", dataset.Name, path)
|
||||
default:
|
||||
return nil, fmt.Errorf("dataset %s: generated file %s uses unsupported family %q", dataset.Name, path, family)
|
||||
ctx.familySpecs[spec.FamilyKey] = spec
|
||||
}
|
||||
sources = append(sources, generatedSource{path: path, obj: obj})
|
||||
}
|
||||
modules := make([]nativeGeneratedModule, 0, len(paths))
|
||||
for _, source := range sources {
|
||||
applyAfterModules := false
|
||||
if isFamilyExpansionObject(source.obj) {
|
||||
spec, err := parseFamilyExpansionSpec(source.path, source.obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
applyAfterModules = spec.ApplyAfterModules
|
||||
}
|
||||
moduleData, err := buildFeatGeneratedModule(source.path, source.obj, ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
modules = append(modules, nativeGeneratedModule{
|
||||
Name: source.path,
|
||||
Data: moduleData,
|
||||
ApplyAfterModules: applyAfterModules,
|
||||
})
|
||||
}
|
||||
racialtypesModule, err := buildRacialtypesSkillAffinityModule(ctx)
|
||||
if err != nil {
|
||||
@@ -941,76 +979,16 @@ type featGeneratedContext struct {
|
||||
existingFeat map[string]struct{}
|
||||
datasets map[string]nativeDataset
|
||||
rowsByDataset map[string]map[string]map[string]any
|
||||
familySpecs map[string]familyExpansionSpec
|
||||
}
|
||||
|
||||
type compactSourceOverride = map[string]map[string]any
|
||||
|
||||
type featWeaponFamilySpec struct {
|
||||
FamilyKey string
|
||||
SourceColumn string
|
||||
LabelPrefix string
|
||||
ConstantPrefix string
|
||||
NamePrefix string
|
||||
AutoPrereqFamily1 string
|
||||
AutoPrereqFamily2 string
|
||||
}
|
||||
|
||||
type affinityGrant struct {
|
||||
featKey string
|
||||
races []string
|
||||
}
|
||||
|
||||
var featWeaponFamilies = map[string]featWeaponFamilySpec{
|
||||
"weapon_focus": {
|
||||
SourceColumn: "WeaponFocusFeat",
|
||||
FamilyKey: "weaponfocus",
|
||||
LabelPrefix: "FEAT_WEAPON_FOCUS",
|
||||
ConstantPrefix: "FEAT_WEAPON_FOCUS",
|
||||
NamePrefix: "Weapon Focus",
|
||||
},
|
||||
"weapon_specialization": {
|
||||
SourceColumn: "WeaponSpecializationFeat",
|
||||
FamilyKey: "weaponspecialization",
|
||||
LabelPrefix: "FEAT_WEAPON_SPECIALIZATION",
|
||||
ConstantPrefix: "FEAT_WEAPON_SPECIALIZATION",
|
||||
NamePrefix: "Weapon Specialization",
|
||||
AutoPrereqFamily1: "weaponfocus",
|
||||
},
|
||||
"greater_weapon_focus": {
|
||||
SourceColumn: "EpicWeaponFocusFeat",
|
||||
FamilyKey: "greaterweaponfocus",
|
||||
LabelPrefix: "FEAT_GREATER_WEAPON_FOCUS",
|
||||
ConstantPrefix: "FEAT_GREATER_WEAPON_FOCUS",
|
||||
NamePrefix: "Greater Weapon Focus",
|
||||
AutoPrereqFamily1: "weaponfocus",
|
||||
},
|
||||
"greater_weapon_specialization": {
|
||||
SourceColumn: "EpicWeaponSpecializationFeat",
|
||||
FamilyKey: "greaterweaponspecialization",
|
||||
LabelPrefix: "FEAT_GREATER_WEAPON_SPECIALIZATION",
|
||||
ConstantPrefix: "FEAT_GREATER_WEAPON_SPECIALIZATION",
|
||||
NamePrefix: "Greater Weapon Specialization",
|
||||
AutoPrereqFamily1: "weaponspecialization",
|
||||
AutoPrereqFamily2: "greaterweaponfocus",
|
||||
},
|
||||
"improved_critical": {
|
||||
SourceColumn: "WeaponImprovedCriticalFeat",
|
||||
FamilyKey: "improvedcritical",
|
||||
LabelPrefix: "FEAT_IMPROVED_CRITICAL",
|
||||
ConstantPrefix: "FEAT_IMPROVED_CRITICAL",
|
||||
NamePrefix: "Improved Critical",
|
||||
AutoPrereqFamily1: "weaponfocus",
|
||||
},
|
||||
"overwhelming_critical": {
|
||||
SourceColumn: "EpicWeaponOverwhelmingCriticalFeat",
|
||||
FamilyKey: "overwhelmingcritical",
|
||||
LabelPrefix: "FEAT_EPIC_OVERWHELMING_CRITICAL",
|
||||
ConstantPrefix: "FEAT_EPIC_OVERWHELMING_CRITICAL",
|
||||
NamePrefix: "Overwhelming Critical",
|
||||
AutoPrereqFamily2: "improvedcritical",
|
||||
},
|
||||
}
|
||||
|
||||
func newFeatGeneratedContext(dataset nativeDataset, lockData map[string]int) (*featGeneratedContext, error) {
|
||||
sourceDir := filepath.Dir(filepath.Dir(dataset.RootPath))
|
||||
dataDir := filepath.Join(sourceDir, "data")
|
||||
@@ -1051,6 +1029,7 @@ func newFeatGeneratedContext(dataset nativeDataset, lockData map[string]int) (*f
|
||||
existingFeat: existingFeat,
|
||||
datasets: datasetMap,
|
||||
rowsByDataset: map[string]map[string]map[string]any{},
|
||||
familySpecs: map[string]familyExpansionSpec{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1235,20 +1214,17 @@ func betterExpandedKey(a, b string) string {
|
||||
|
||||
func buildFeatGeneratedModule(path string, obj map[string]any, ctx *featGeneratedContext) (map[string]any, error) {
|
||||
family, _ := obj["family"].(string)
|
||||
if strings.TrimSpace(family) == "" {
|
||||
return nil, fmt.Errorf("generated feat file %s is missing family", path)
|
||||
}
|
||||
if isFamilyExpansionObject(obj) {
|
||||
return buildFamilyExpansionGeneratedModule(path, obj, ctx)
|
||||
}
|
||||
switch family {
|
||||
case "skill_focus", "greater_skill_focus":
|
||||
return buildSkillFocusGeneratedModule(path, obj, ctx)
|
||||
case "weapon_focus",
|
||||
"weapon_specialization",
|
||||
"greater_weapon_focus",
|
||||
"greater_weapon_specialization",
|
||||
"improved_critical",
|
||||
"overwhelming_critical":
|
||||
return buildWeaponFamilyGeneratedModule(path, obj, ctx)
|
||||
case "proficiencies", "special_attacks":
|
||||
case "special_attacks":
|
||||
return buildLegacyFeatGeneratedModule(path, obj)
|
||||
default:
|
||||
return nil, fmt.Errorf("generated feat file %s uses unsupported family %q", path, family)
|
||||
return buildLegacyFeatGeneratedModule(path, obj)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1274,7 +1250,7 @@ func buildLegacyFeatGeneratedModule(path string, obj map[string]any) (map[string
|
||||
return module, nil
|
||||
}
|
||||
|
||||
func buildSkillFocusGeneratedModule(path string, obj map[string]any, ctx *featGeneratedContext) (map[string]any, error) {
|
||||
func buildFamilyExpansionGeneratedModule(path string, obj map[string]any, ctx *featGeneratedContext) (map[string]any, error) {
|
||||
spec, err := parseFamilyExpansionSpec(path, obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1283,42 +1259,33 @@ func buildSkillFocusGeneratedModule(path string, obj map[string]any, ctx *featGe
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generated feat file %s: %w", path, err)
|
||||
}
|
||||
if spec.ChildSource.Dataset != "skills" {
|
||||
return nil, fmt.Errorf("generated feat file %s: child_source.dataset must be skills", path)
|
||||
}
|
||||
skillRows, err := ctx.datasetRows("skills")
|
||||
sourceRows, err := ctx.datasetRows(spec.ChildSource.Dataset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
labelPrefix := "FEAT_SKILL_FOCUS"
|
||||
namePrefix := "Skill Focus"
|
||||
if spec.Family == "greater_skill_focus" {
|
||||
labelPrefix = "FEAT_GREATER_SKILL_FOCUS"
|
||||
namePrefix = "Greater Skill Focus"
|
||||
}
|
||||
|
||||
overrideList := make([]any, 0)
|
||||
familyAllowlist := ctx.familyHasExistingRows(spec.FamilyKey)
|
||||
for _, skillKey := range sortedStringMapKeys(skillRows) {
|
||||
row := skillRows[skillKey]
|
||||
if spec.ChildSource.Predicate == "accessible" && isHiddenSkill(row) {
|
||||
continue
|
||||
}
|
||||
slug := strings.TrimPrefix(skillKey, "skills:")
|
||||
featKey := ctx.preferredGeneratedFeatKey(spec.FamilyKey, slug)
|
||||
childToken := childTokenFromExpandedKey(featKey, spec.FamilyKey)
|
||||
labelSuffix := upperSnake(displayNameForSkill(row))
|
||||
rowID, hasID, err := ctx.featID(featKey, nil)
|
||||
familyAllowlist := spec.AllowExistingOnly && ctx.familyHasExistingRows(spec.FamilyKey)
|
||||
for _, sourceKey := range sortedStringMapKeys(sourceRows) {
|
||||
row := sourceRows[sourceKey]
|
||||
include, err := familyExpansionSourceEligible(spec, row)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generated feat file %s: %w", path, err)
|
||||
}
|
||||
if !include {
|
||||
continue
|
||||
}
|
||||
slug := strings.TrimPrefix(sourceKey, spec.ChildSource.Dataset+":")
|
||||
featKey, rowID, hasID, err := ctx.resolveGeneratedFeatIdentity(spec, slug, row)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generated feat file %s: %w", path, err)
|
||||
}
|
||||
childToken := childTokenFromExpandedKey(featKey, spec.FamilyKey)
|
||||
displayName := displayNameForGeneratedSource(spec.ChildSource.Dataset, row)
|
||||
labelSuffix := upperSnake(displayName)
|
||||
override := map[string]any{
|
||||
"key": featKey,
|
||||
"MASTERFEAT": map[string]any{"id": spec.Template},
|
||||
"REQSKILL": map[string]any{"id": skillKey},
|
||||
"TOOLSCATEGORIES": "2",
|
||||
"meta": familyMetadata(spec.FamilyKey, childToken, skillKey, spec.Template),
|
||||
"key": featKey,
|
||||
"MASTERFEAT": map[string]any{"id": spec.Template},
|
||||
"meta": familyMetadata(spec.FamilyKey, childToken, sourceKey, spec.Template),
|
||||
}
|
||||
if hasID {
|
||||
override["id"] = rowID
|
||||
@@ -1326,105 +1293,37 @@ func buildSkillFocusGeneratedModule(path string, obj map[string]any, ctx *featGe
|
||||
if familyAllowlist && !ctx.featKeyExists(featKey) {
|
||||
continue
|
||||
}
|
||||
if !ctx.featKeyExists(featKey) {
|
||||
override["LABEL"] = labelPrefix + "_" + labelSuffix
|
||||
override["FEAT"] = map[string]any{
|
||||
"tlk": map[string]any{
|
||||
"key": featKey + ".name",
|
||||
"text": fmt.Sprintf("%s (%s)", namePrefix, displayNameForSkill(row)),
|
||||
},
|
||||
if spec.ChildRefField != "" {
|
||||
override[spec.ChildRefField] = map[string]any{"id": sourceKey}
|
||||
}
|
||||
for field, prereqFamily := range spec.AutoPrereqFields {
|
||||
if prereqKey, ok := generatedFamilyPrereqKey(ctx, row, slug, prereqFamily); ok {
|
||||
override[field] = map[string]any{"id": prereqKey}
|
||||
}
|
||||
override["DESCRIPTION"] = map[string]any{"field": "DESCRIPTION", "ref": spec.Template}
|
||||
override["ICON"] = map[string]any{"field": "ICON", "ref": spec.Template}
|
||||
override["CRValue"] = map[string]any{"field": "CRValue", "ref": spec.Template}
|
||||
override["ReqSkillMinRanks"] = map[string]any{"field": "ReqSkillMinRanks", "ref": spec.Template}
|
||||
override["Constant"] = labelPrefix + "_" + labelSuffix
|
||||
override["TOOLSCATEGORIES"] = "2"
|
||||
override["ALLCLASSESCANUSE"] = map[string]any{"field": "ALLCLASSESCANUSE", "ref": spec.Template}
|
||||
override["PreReqEpic"] = map[string]any{"field": "PreReqEpic", "ref": spec.Template}
|
||||
override["ReqAction"] = map[string]any{"field": "ReqAction", "ref": spec.Template}
|
||||
}
|
||||
mergeGeneratedOverride(override, overrides[skillKey])
|
||||
overrideList = append(overrideList, override)
|
||||
}
|
||||
return map[string]any{"overrides": overrideList}, nil
|
||||
}
|
||||
|
||||
func buildWeaponFamilyGeneratedModule(path string, obj map[string]any, ctx *featGeneratedContext) (map[string]any, error) {
|
||||
fileSpec, err := parseFamilyExpansionSpec(path, obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spec, ok := featWeaponFamilies[fileSpec.Family]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("generated feat file %s uses unsupported weapon family %q", path, fileSpec.Family)
|
||||
}
|
||||
if fileSpec.ChildSource.Dataset != "baseitems" {
|
||||
return nil, fmt.Errorf("generated feat file %s: child_source.dataset must be baseitems", path)
|
||||
}
|
||||
if fileSpec.ChildSource.Column == "" {
|
||||
return nil, fmt.Errorf("generated feat file %s: child_source.column must be set", path)
|
||||
}
|
||||
overrides, err := parseCompactSourceOverrides(obj["overrides"])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generated feat file %s: %w", path, err)
|
||||
}
|
||||
baseitemRows, err := ctx.datasetRows("baseitems")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
overrideList := make([]any, 0)
|
||||
for _, baseitemKey := range sortedStringMapKeys(baseitemRows) {
|
||||
row := baseitemRows[baseitemKey]
|
||||
rawID, ok := lookupField(row, fileSpec.ChildSource.Column)
|
||||
if !ok || isNullishValue(rawID) {
|
||||
continue
|
||||
}
|
||||
slug := strings.TrimPrefix(baseitemKey, "baseitems:")
|
||||
featKey, rowID, hasID, err := ctx.resolveWeaponFeatIdentity(fileSpec.FamilyKey, slug, rawID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("generated feat file %s: %w", path, err)
|
||||
}
|
||||
childToken := childTokenFromExpandedKey(featKey, fileSpec.FamilyKey)
|
||||
labelSuffix := upperSnake(displayNameForBaseitem(row))
|
||||
override := map[string]any{
|
||||
"key": featKey,
|
||||
"MASTERFEAT": map[string]any{"id": fileSpec.Template},
|
||||
"TOOLSCATEGORIES": "1",
|
||||
"meta": familyMetadata(fileSpec.FamilyKey, childToken, baseitemKey, fileSpec.Template),
|
||||
}
|
||||
if hasID {
|
||||
override["id"] = rowID
|
||||
}
|
||||
if !ctx.featKeyExists(featKey) {
|
||||
override["LABEL"] = spec.LabelPrefix + "_" + labelSuffix
|
||||
override["FEAT"] = map[string]any{
|
||||
"tlk": map[string]any{
|
||||
"key": featKey + ".name",
|
||||
"text": fmt.Sprintf("%s (%s)", spec.NamePrefix, displayNameForBaseitem(row)),
|
||||
},
|
||||
if spec.LabelPrefix != "" {
|
||||
override["LABEL"] = spec.LabelPrefix + "_" + labelSuffix
|
||||
}
|
||||
if spec.NamePrefix != "" {
|
||||
override["FEAT"] = map[string]any{
|
||||
"tlk": map[string]any{
|
||||
"key": featKey + ".name",
|
||||
"text": fmt.Sprintf("%s (%s)", spec.NamePrefix, displayName),
|
||||
},
|
||||
}
|
||||
}
|
||||
for _, field := range spec.TemplateFields {
|
||||
override[field] = map[string]any{"field": field, "ref": spec.Template}
|
||||
}
|
||||
if spec.ConstantPrefix != "" {
|
||||
override["Constant"] = spec.ConstantPrefix + "_" + labelSuffix
|
||||
}
|
||||
override["DESCRIPTION"] = map[string]any{"field": "DESCRIPTION", "ref": fileSpec.Template}
|
||||
override["ICON"] = map[string]any{"field": "ICON", "ref": fileSpec.Template}
|
||||
override["MINATTACKBONUS"] = map[string]any{"field": "MINATTACKBONUS", "ref": fileSpec.Template}
|
||||
override["ALLCLASSESCANUSE"] = map[string]any{"field": "ALLCLASSESCANUSE", "ref": fileSpec.Template}
|
||||
override["PreReqEpic"] = map[string]any{"field": "PreReqEpic", "ref": fileSpec.Template}
|
||||
override["ReqAction"] = map[string]any{"field": "ReqAction", "ref": fileSpec.Template}
|
||||
override["TOOLSCATEGORIES"] = "1"
|
||||
override["CRValue"] = map[string]any{"field": "CRValue", "ref": fileSpec.Template}
|
||||
override["MinLevel"] = map[string]any{"field": "MinLevel", "ref": fileSpec.Template}
|
||||
override["MinLevelClass"] = map[string]any{"field": "MinLevelClass", "ref": fileSpec.Template}
|
||||
override["MINSTR"] = map[string]any{"field": "MINSTR", "ref": fileSpec.Template}
|
||||
override["Constant"] = spec.ConstantPrefix + "_" + labelSuffix
|
||||
}
|
||||
if prereqKey, ok := weaponFamilyPrereqKey(ctx, row, slug, spec.AutoPrereqFamily1); ok {
|
||||
override["PREREQFEAT1"] = map[string]any{"id": prereqKey}
|
||||
for field, value := range spec.DefaultFields {
|
||||
override[field] = value
|
||||
}
|
||||
if prereqKey, ok := weaponFamilyPrereqKey(ctx, row, slug, spec.AutoPrereqFamily2); ok {
|
||||
override["PREREQFEAT2"] = map[string]any{"id": prereqKey}
|
||||
}
|
||||
mergeGeneratedOverride(override, overrides[baseitemKey])
|
||||
mergeGeneratedOverride(override, overrides[sourceKey])
|
||||
overrideList = append(overrideList, override)
|
||||
}
|
||||
return map[string]any{"overrides": overrideList}, nil
|
||||
@@ -1668,6 +1567,29 @@ func displayNameForBaseitem(row map[string]any) string {
|
||||
return "Unknown Weapon"
|
||||
}
|
||||
|
||||
func displayNameForGeneratedSource(dataset string, row map[string]any) string {
|
||||
switch dataset {
|
||||
case "skills":
|
||||
return displayNameForSkill(row)
|
||||
case "baseitems":
|
||||
return displayNameForBaseitem(row)
|
||||
default:
|
||||
if text, ok := displayTextFromValue(row["Name"]); ok {
|
||||
return text
|
||||
}
|
||||
if label, ok := row["Label"].(string); ok && strings.TrimSpace(label) != "" {
|
||||
return normalizeDisplayName(strings.TrimSpace(label))
|
||||
}
|
||||
if label, ok := row["label"].(string); ok && strings.TrimSpace(label) != "" {
|
||||
return normalizeDisplayName(strings.TrimSpace(label))
|
||||
}
|
||||
if key, ok := row["key"].(string); ok {
|
||||
return displayNameFromSlug(strings.TrimPrefix(key, dataset+":"))
|
||||
}
|
||||
return "Unknown Entry"
|
||||
}
|
||||
}
|
||||
|
||||
func displayNameForRaceFile(obj map[string]any) string {
|
||||
core, ok := obj["core"].(map[string]any)
|
||||
if ok {
|
||||
@@ -1804,7 +1726,7 @@ func featSourceID(value any) (int, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *featGeneratedContext) resolveWeaponFeatIdentity(familyKey, slug string, rawSource any) (string, int, bool, error) {
|
||||
func (c *featGeneratedContext) resolveGeneratedFeatIdentityBySource(familyKey, slug string, rawSource any) (string, int, bool, error) {
|
||||
if rawSource != nil && rawSource != nullValue {
|
||||
if parsedID, err := featSourceID(rawSource); err == nil && parsedID > 0 {
|
||||
if existingKey, ok := c.preferredFeatKeyForID(familyKey, parsedID); ok {
|
||||
@@ -1823,34 +1745,51 @@ func (c *featGeneratedContext) resolveWeaponFeatIdentity(familyKey, slug string,
|
||||
return featKey, rowID, hasID, err
|
||||
}
|
||||
|
||||
func weaponFamilyPrereqKey(ctx *featGeneratedContext, baseitemRow map[string]any, slug, familyKey string) (string, bool) {
|
||||
func (c *featGeneratedContext) resolveGeneratedFeatIdentity(spec familyExpansionSpec, slug string, sourceRow map[string]any) (string, int, bool, error) {
|
||||
if spec.IdentitySource == "child_source_value" {
|
||||
rawSource, ok := lookupField(sourceRow, spec.ChildSource.Column)
|
||||
if !ok {
|
||||
rawSource = nil
|
||||
}
|
||||
return c.resolveGeneratedFeatIdentityBySource(spec.FamilyKey, slug, rawSource)
|
||||
}
|
||||
featKey := c.preferredGeneratedFeatKey(spec.FamilyKey, slug)
|
||||
rowID, hasID, err := c.featID(featKey, nil)
|
||||
return featKey, rowID, hasID, err
|
||||
}
|
||||
|
||||
func familyExpansionSourceEligible(spec familyExpansionSpec, row map[string]any) (bool, error) {
|
||||
if spec.ChildSource.Column != "" {
|
||||
rawValue, ok := lookupField(row, spec.ChildSource.Column)
|
||||
if !ok || isNullishValue(rawValue) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
switch spec.ChildSource.Predicate {
|
||||
case "":
|
||||
return true, nil
|
||||
case "accessible":
|
||||
return !isHiddenSkill(row), nil
|
||||
default:
|
||||
return false, fmt.Errorf("unsupported child_source.predicate %q", spec.ChildSource.Predicate)
|
||||
}
|
||||
}
|
||||
|
||||
func generatedFamilyPrereqKey(ctx *featGeneratedContext, sourceRow map[string]any, slug, familyKey string) (string, bool) {
|
||||
if familyKey == "" {
|
||||
return "", false
|
||||
}
|
||||
column := weaponFamilySourceColumn(familyKey)
|
||||
if column == "" {
|
||||
spec, ok := ctx.familySpecs[familyKey]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
rawSource, ok := lookupField(baseitemRow, column)
|
||||
if !ok || isNullishValue(rawSource) {
|
||||
return "", false
|
||||
}
|
||||
featKey, _, _, err := ctx.resolveWeaponFeatIdentity(familyKey, slug, rawSource)
|
||||
featKey, _, _, err := ctx.resolveGeneratedFeatIdentity(spec, slug, sourceRow)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
return featKey, true
|
||||
}
|
||||
|
||||
func weaponFamilySourceColumn(familyKey string) string {
|
||||
for _, spec := range featWeaponFamilies {
|
||||
if spec.FamilyKey == familyKey {
|
||||
return spec.SourceColumn
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *featGeneratedContext) preferredFeatKeyForID(familyKey string, rowID int) (string, bool) {
|
||||
prefix := "feat:" + familyKey
|
||||
type candidateBuckets struct {
|
||||
@@ -2947,6 +2886,42 @@ func parseColumns(baseData map[string]any, datasetName string) ([]string, error)
|
||||
return columns, nil
|
||||
}
|
||||
|
||||
func extendColumns(columns []string, obj map[string]any, datasetName, sourceLabel string) ([]string, error) {
|
||||
rawColumns, ok := obj["columns"]
|
||||
if !ok {
|
||||
return columns, nil
|
||||
}
|
||||
columnList, ok := rawColumns.([]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("dataset %s: columns in %s must be an array", datasetName, sourceLabel)
|
||||
}
|
||||
extended := append([]string(nil), columns...)
|
||||
for _, raw := range columnList {
|
||||
column, ok := raw.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("dataset %s: columns in %s must contain strings", datasetName, sourceLabel)
|
||||
}
|
||||
if isDeprecatedAuthoringOnlyField(datasetName, column) || isMetadataField(column) {
|
||||
continue
|
||||
}
|
||||
if _, exists := canonicalColumn(extended, column); exists {
|
||||
continue
|
||||
}
|
||||
extended = append(extended, column)
|
||||
}
|
||||
return extended, nil
|
||||
}
|
||||
|
||||
func ensureRowsExposeColumns(rows []map[string]any, columns []string) {
|
||||
for _, row := range rows {
|
||||
for _, column := range columns {
|
||||
if _, ok := row[column]; !ok {
|
||||
row[column] = nullValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func canonicalizeBaseRow(dataset nativeDataset, columns []string, raw map[string]any, index int) (map[string]any, error) {
|
||||
rowID := index
|
||||
if value, ok := raw["id"]; ok {
|
||||
|
||||
Reference in New Issue
Block a user