From 3ab770639cb4ff2e2aa8fd84aedb56fefa1d0aab Mon Sep 17 00:00:00 2001 From: vickydotbat Date: Sun, 19 Jul 2026 10:03:31 +0200 Subject: [PATCH] topdata: remove completed legacy-migration cluster The one-time legacy normalize migration finished; NormalizeProject had no production callers. Delete migrate.go, the 26 *_migrate.go files, the importLegacyItempropsRegistry cluster, loadLegacyTLK, and the 48 test blocks that only exercised them. mergeLegacyTLK moves to tlk_native.go because the live TLK compiler still uses it for base_dialog.json. Co-Authored-By: Claude Fable 5 --- internal/topdata/appearance_migrate.go | 96 - internal/topdata/armor_migrate.go | 160 - internal/topdata/base_dialog_migrate.go | 41 - internal/topdata/baseitems_migrate.go | 105 - internal/topdata/classes_migrate.go | 226 -- internal/topdata/cloakmodel_migrate.go | 6 - internal/topdata/creaturespeed_migrate.go | 145 - internal/topdata/damagetypes_migrate.go | 288 -- internal/topdata/doortypes_migrate.go | 99 - internal/topdata/feat_migrate.go | 64 - internal/topdata/genericdoors_migrate.go | 6 - internal/topdata/itemprops_registry.go | 391 -- internal/topdata/legacy_dataset_migrate.go | 176 - internal/topdata/loadscreens_migrate.go | 6 - internal/topdata/masterfeats_migrate.go | 237 -- internal/topdata/migrate.go | 415 -- internal/topdata/native.go | 75 - internal/topdata/placeables_migrate.go | 6 - internal/topdata/portraits_migrate.go | 8 - internal/topdata/progfx_migrate.go | 6 - internal/topdata/racialtypes_migrate.go | 282 -- internal/topdata/repadjust_migrate.go | 63 - internal/topdata/ruleset_migrate.go | 171 - internal/topdata/skills_migrate.go | 140 - internal/topdata/skyboxes_migrate.go | 6 - internal/topdata/spells_migrate.go | 103 - internal/topdata/tailmodel_migrate.go | 86 - internal/topdata/tlk_native.go | 33 + internal/topdata/topdata_test.go | 4259 -------------------- internal/topdata/vfx_persistent_migrate.go | 198 - internal/topdata/visualeffects_migrate.go | 6 - internal/topdata/wingmodel_migrate.go | 86 - 32 files changed, 33 insertions(+), 7956 deletions(-) delete mode 100644 internal/topdata/appearance_migrate.go delete mode 100644 internal/topdata/armor_migrate.go delete mode 100644 internal/topdata/base_dialog_migrate.go delete mode 100644 internal/topdata/baseitems_migrate.go delete mode 100644 internal/topdata/classes_migrate.go delete mode 100644 internal/topdata/cloakmodel_migrate.go delete mode 100644 internal/topdata/creaturespeed_migrate.go delete mode 100644 internal/topdata/damagetypes_migrate.go delete mode 100644 internal/topdata/doortypes_migrate.go delete mode 100644 internal/topdata/feat_migrate.go delete mode 100644 internal/topdata/genericdoors_migrate.go delete mode 100644 internal/topdata/legacy_dataset_migrate.go delete mode 100644 internal/topdata/loadscreens_migrate.go delete mode 100644 internal/topdata/masterfeats_migrate.go delete mode 100644 internal/topdata/migrate.go delete mode 100644 internal/topdata/placeables_migrate.go delete mode 100644 internal/topdata/portraits_migrate.go delete mode 100644 internal/topdata/progfx_migrate.go delete mode 100644 internal/topdata/racialtypes_migrate.go delete mode 100644 internal/topdata/repadjust_migrate.go delete mode 100644 internal/topdata/ruleset_migrate.go delete mode 100644 internal/topdata/skills_migrate.go delete mode 100644 internal/topdata/skyboxes_migrate.go delete mode 100644 internal/topdata/spells_migrate.go delete mode 100644 internal/topdata/tailmodel_migrate.go delete mode 100644 internal/topdata/vfx_persistent_migrate.go delete mode 100644 internal/topdata/visualeffects_migrate.go delete mode 100644 internal/topdata/wingmodel_migrate.go diff --git a/internal/topdata/appearance_migrate.go b/internal/topdata/appearance_migrate.go deleted file mode 100644 index 1983194..0000000 --- a/internal/topdata/appearance_migrate.go +++ /dev/null @@ -1,96 +0,0 @@ -package topdata - -import ( - "encoding/json" - "os" - "path/filepath" -) - -func importLegacyAppearance(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - _ = legacyTLK - - legacyDir := filepath.Join(referenceBuilderDir, "data", "appearance") - if _, err := os.Stat(legacyDir); err != nil { - if os.IsNotExist(err) { - return 0, nil - } - return 0, err - } - - targetDir := filepath.Join(dataDir, "appearance") - targetBasePath := filepath.Join(targetDir, "base.json") - targetLockPath := filepath.Join(targetDir, "lock.json") - targetModulesDir := filepath.Join(targetDir, "modules") - moduleNames := []string{ - "cotblreaver.json", - "crawlingclaw.json", - "halfogre.json", - "zombieknight.json", - } - targetModulePaths := make([]string, 0, len(moduleNames)) - for _, name := range moduleNames { - targetModulePaths = append(targetModulePaths, filepath.Join(targetModulesDir, name)) - } - if fileExists(targetBasePath) && fileExists(targetLockPath) { - allModulesPresent := true - for _, path := range targetModulePaths { - if !fileExists(path) { - allModulesPresent = false - break - } - } - if allModulesPresent { - return 0, nil - } - } - - baseObj, err := loadJSONObject(filepath.Join(legacyDir, "base.json")) - if err != nil { - return 0, err - } - baseObj["output"] = "appearance.2da" - - lockObj, err := loadJSONObject(filepath.Join(legacyDir, "lock.json")) - if err != nil { - return 0, err - } - - moduleObjs := make([]map[string]any, 0, len(moduleNames)) - for _, name := range moduleNames { - obj, err := loadJSONObject(filepath.Join(legacyDir, "modules", name)) - if err != nil { - return 0, err - } - moduleObjs = append(moduleObjs, obj) - } - - if err := os.MkdirAll(targetModulesDir, 0o755); err != nil { - return 0, err - } - - writes := []struct { - path string - obj map[string]any - }{ - {path: targetBasePath, obj: baseObj}, - {path: targetLockPath, obj: lockObj}, - } - for i, path := range targetModulePaths { - writes = append(writes, struct { - path string - obj map[string]any - }{path: path, obj: moduleObjs[i]}) - } - - for _, write := range writes { - raw, err := json.MarshalIndent(write.obj, "", " ") - if err != nil { - return 0, err - } - raw = append(raw, '\n') - if err := os.WriteFile(write.path, raw, 0o644); err != nil { - return 0, err - } - } - return len(writes), nil -} diff --git a/internal/topdata/armor_migrate.go b/internal/topdata/armor_migrate.go deleted file mode 100644 index 0bdfbdd..0000000 --- a/internal/topdata/armor_migrate.go +++ /dev/null @@ -1,160 +0,0 @@ -package topdata - -import ( - "encoding/json" - "os" - "path/filepath" - "sort" -) - -func importLegacyArmor(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - _ = legacyTLK - - legacyDir := filepath.Join(referenceBuilderDir, "data", "armor") - if _, err := os.Stat(legacyDir); err != nil { - if os.IsNotExist(err) { - return 0, nil - } - return 0, err - } - - targetDir := filepath.Join(dataDir, "armor") - targetPath := filepath.Join(targetDir, "armor.json") - if _, err := os.Stat(targetPath); err == nil { - obj, err := loadJSONObject(targetPath) - if err == nil && countLegacyTLKRefsInValue(obj) == 0 { - return 0, nil - } - } - - baseObj, err := loadJSONObject(filepath.Join(legacyDir, "base.json")) - if err != nil { - return 0, err - } - rows, err := mergeArmorRows(baseObj, filepath.Join(legacyDir, "modules")) - if err != nil { - return 0, err - } - - if err := os.MkdirAll(targetDir, 0o755); err != nil { - return 0, err - } - if err := removePathIfExists(filepath.Join(targetDir, "base.json")); err != nil { - return 0, err - } - if err := removePathIfExists(filepath.Join(targetDir, "lock.json")); err != nil { - return 0, err - } - if err := removePathIfExists(filepath.Join(targetDir, "modules")); err != nil { - return 0, err - } - - out := map[string]any{ - "output": "armor.2da", - "columns": baseObj["columns"], - "rows": rows, - } - raw, err := json.MarshalIndent(out, "", " ") - if err != nil { - return 0, err - } - raw = append(raw, '\n') - if err := os.WriteFile(targetPath, raw, 0o644); err != nil { - return 0, err - } - return 1, nil -} - -func mergeArmorRows(baseObj map[string]any, modulesDir string) ([]any, error) { - rawRows, ok := baseObj["rows"].([]any) - if !ok { - return nil, nil - } - - rows := make([]map[string]any, 0, len(rawRows)) - byID := map[int]map[string]any{} - for _, raw := range rawRows { - row, ok := deepCopyValue(raw).(map[string]any) - if !ok { - continue - } - delete(row, "key") - rawID, ok := row["id"] - if !ok { - continue - } - id, err := asInt(rawID) - if err != nil { - return nil, err - } - row["id"] = id - rows = append(rows, row) - byID[id] = row - } - - modulePaths, err := collectModulePaths(modulesDir) - if err != nil { - return nil, err - } - for _, path := range modulePaths { - obj, err := loadJSONObject(path) - if err != nil { - return nil, err - } - if entries, ok := obj["entries"].(map[string]any); ok { - for _, raw := range entries { - row, ok := deepCopyValue(raw).(map[string]any) - if !ok { - continue - } - delete(row, "key") - rawID, ok := row["id"] - if !ok { - continue - } - id, err := asInt(rawID) - if err != nil { - return nil, err - } - row["id"] = id - rows = append(rows, row) - byID[id] = row - } - } - if overrides, ok := obj["overrides"].([]any); ok { - for _, raw := range overrides { - override, ok := raw.(map[string]any) - if !ok { - continue - } - rawID, ok := override["id"] - if !ok { - continue - } - id, err := asInt(rawID) - if err != nil { - return nil, err - } - row := byID[id] - if row == nil { - continue - } - for key, value := range override { - if key == "id" || key == "key" || key == "_tlk" || isMetadataField(key) { - continue - } - row[key] = deepCopyValue(value) - } - } - } - } - - sort.Slice(rows, func(i, j int) bool { - return rows[i]["id"].(int) < rows[j]["id"].(int) - }) - out := make([]any, 0, len(rows)) - for _, row := range rows { - out = append(out, row) - } - return out, nil -} diff --git a/internal/topdata/base_dialog_migrate.go b/internal/topdata/base_dialog_migrate.go deleted file mode 100644 index 40ca883..0000000 --- a/internal/topdata/base_dialog_migrate.go +++ /dev/null @@ -1,41 +0,0 @@ -package topdata - -import ( - "encoding/json" - "os" - "path/filepath" -) - -func importLegacyBaseDialog(referenceBuilderDir, sourceDir string) (int, error) { - legacyPath := filepath.Join(referenceBuilderDir, "tlk", "base.json") - if _, err := os.Stat(legacyPath); err != nil { - if os.IsNotExist(err) { - return 0, nil - } - return 0, err - } - - targetPath := filepath.Join(sourceDir, "base_dialog.json") - if fileExists(targetPath) { - current, err := loadJSONObject(targetPath) - if err == nil { - if entries, ok := current["entries"].(map[string]any); ok && len(entries) > 0 { - return 0, nil - } - } - } - - obj, err := loadJSONObject(legacyPath) - if err != nil { - return 0, err - } - raw, err := json.MarshalIndent(obj, "", " ") - if err != nil { - return 0, err - } - raw = append(raw, '\n') - if err := os.WriteFile(targetPath, raw, 0o644); err != nil { - return 0, err - } - return 1, nil -} diff --git a/internal/topdata/baseitems_migrate.go b/internal/topdata/baseitems_migrate.go deleted file mode 100644 index e048b71..0000000 --- a/internal/topdata/baseitems_migrate.go +++ /dev/null @@ -1,105 +0,0 @@ -package topdata - -import ( - "encoding/json" - "os" - "path/filepath" -) - -func importLegacyBaseitems(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - _ = legacyTLK - - legacyDir := filepath.Join(referenceBuilderDir, "data", "baseitems") - if _, err := os.Stat(legacyDir); err != nil { - if os.IsNotExist(err) { - return 0, nil - } - return 0, err - } - - targetDir := filepath.Join(dataDir, "baseitems") - targetBasePath := filepath.Join(targetDir, "base.json") - targetLockPath := filepath.Join(targetDir, "lock.json") - targetModulesDir := filepath.Join(targetDir, "modules") - moduleNames := []string{ - "blunderbuss.json", - "coin.json", - "estoc.json", - "heavymace.json", - "maulandfalchion.json", - "ovr_baseitems.json", - "ovr_cloak255.json", - "ovr_helmet255.json", - "shortspear.json", - } - targetModulePaths := make([]string, 0, len(moduleNames)) - for _, name := range moduleNames { - targetModulePaths = append(targetModulePaths, filepath.Join(targetModulesDir, name)) - } - if fileExists(targetBasePath) && fileExists(targetLockPath) { - allModulesPresent := true - for _, path := range targetModulePaths { - if !fileExists(path) { - allModulesPresent = false - break - } - } - if allModulesPresent { - return 0, nil - } - } - - baseObj, err := loadJSONObject(filepath.Join(legacyDir, "base.json")) - if err != nil { - return 0, err - } - baseObj["output"] = "baseitems.2da" - canonicalizeWikiMetadataDocument(baseObj) - - lockObj, err := loadJSONObject(filepath.Join(legacyDir, "lock.json")) - if err != nil { - return 0, err - } - - moduleObjs := make([]map[string]any, 0, len(moduleNames)) - for _, name := range moduleNames { - obj, err := loadJSONObject(filepath.Join(legacyDir, "modules", name)) - if err != nil { - return 0, err - } - canonicalizeWikiMetadataDocument(obj) - moduleObjs = append(moduleObjs, obj) - } - - if err := os.MkdirAll(targetModulesDir, 0o755); err != nil { - return 0, err - } - - writes := []struct { - path string - obj map[string]any - }{ - {path: targetBasePath, obj: baseObj}, - {path: targetLockPath, obj: lockObj}, - } - for index, path := range targetModulePaths { - writes = append(writes, struct { - path string - obj map[string]any - }{ - path: path, - obj: moduleObjs[index], - }) - } - for _, write := range writes { - raw, err := json.MarshalIndent(write.obj, "", " ") - if err != nil { - return 0, err - } - raw = append(raw, '\n') - if err := os.WriteFile(write.path, raw, 0o644); err != nil { - return 0, err - } - } - return len(writes), nil -} diff --git a/internal/topdata/classes_migrate.go b/internal/topdata/classes_migrate.go deleted file mode 100644 index 4443ab5..0000000 --- a/internal/topdata/classes_migrate.go +++ /dev/null @@ -1,226 +0,0 @@ -package topdata - -import ( - "encoding/json" - "os" - "path/filepath" - "slices" - "strings" -) - -var legacyClassesPlainFamilies = []string{"feats", "skills", "savthr", "bfeat", "pres"} - -func importLegacyClasses(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - legacyRoot := filepath.Join(referenceBuilderDir, "data", "classes") - if _, err := os.Stat(legacyRoot); err != nil { - if os.IsNotExist(err) { - return 0, nil - } - return 0, err - } - - targetRoot := filepath.Join(dataDir, "classes") - if canonicalClassesPresent(targetRoot) { - return 0, nil - } - - coreCollected, plainCollected, err := collectLegacyClassesDatasets(legacyRoot) - if err != nil { - return 0, err - } - - tableKeyByOutput := map[string]string{} - for _, dataset := range plainCollected { - if dataset.TableKey == "" { - continue - } - registerLegacyClassesTableKey(tableKeyByOutput, dataset.TableKey, dataset.Dataset.OutputName) - } - - coreRows := make([]map[string]any, 0, len(coreCollected.Rows)) - for _, rawRow := range coreCollected.Rows { - row, ok := deepCopyValue(rawRow).(map[string]any) - if !ok { - continue - } - if legacyTLK != nil { - if _, _, err := inlineLegacyTLKValue(row, legacyTLK); err != nil { - return 0, err - } - } - rewriteLegacyClassesCoreRow(row, tableKeyByOutput) - coreRows = append(coreRows, row) - } - - if err := removePathIfExists(targetRoot); err != nil { - return 0, err - } - if err := os.MkdirAll(filepath.Join(targetRoot, "core"), 0o755); err != nil { - return 0, err - } - - writes := []struct { - path string - obj map[string]any - }{ - { - path: filepath.Join(targetRoot, "core", "base.json"), - obj: map[string]any{ - "output": coreCollected.Dataset.OutputName, - "columns": stringSliceToAny(coreCollected.Columns), - "rows": rowsToAny(coreRows), - }, - }, - { - path: filepath.Join(targetRoot, "core", "lock.json"), - obj: anyMapInt(coreCollected.LockData), - }, - } - - for _, collected := range plainCollected { - obj := map[string]any{ - "output": collected.Dataset.OutputName, - "columns": stringSliceToAny(collected.Columns), - "rows": rowsToAny(collected.Rows), - } - if strings.TrimSpace(collected.TableKey) != "" { - obj["key"] = collected.TableKey - } - writes = append(writes, struct { - path string - obj map[string]any - }{ - path: filepath.Join(dataDir, collected.Dataset.Name+".json"), - obj: obj, - }) - } - - for _, write := range writes { - if err := os.MkdirAll(filepath.Dir(write.path), 0o755); err != nil { - return 0, err - } - raw, err := json.MarshalIndent(write.obj, "", " ") - if err != nil { - return 0, err - } - raw = append(raw, '\n') - if err := os.WriteFile(write.path, raw, 0o644); err != nil { - return 0, err - } - } - - return len(writes), nil -} - -func canonicalClassesPresent(targetRoot string) bool { - if !fileExists(filepath.Join(targetRoot, "core", "base.json")) || !fileExists(filepath.Join(targetRoot, "core", "lock.json")) { - return false - } - for _, family := range legacyClassesPlainFamilies { - ok, err := hasJSONFiles(filepath.Join(targetRoot, family)) - if err != nil || !ok { - return false - } - } - return true -} - -func collectLegacyClassesDatasets(legacyRoot string) (nativeCollectedDataset, []nativeCollectedDataset, error) { - coreCollected, err := collectBaseDataset(nativeDataset{ - Name: "classes/core", - BasePath: filepath.Join(legacyRoot, "core", "base.json"), - LockPath: filepath.Join(legacyRoot, "core", "lock.json"), - ModulesDir: filepath.Join(legacyRoot, "core", "modules"), - OutputName: "classes.2da", - Spec: specForDataset("classes"), - }) - if err != nil { - return nativeCollectedDataset{}, nil, err - } - coreCollected.Dataset.OutputName = "classes.2da" - - plainCollected := make([]nativeCollectedDataset, 0) - for _, family := range legacyClassesPlainFamilies { - familyDir := filepath.Join(legacyRoot, family) - entries, err := os.ReadDir(familyDir) - if err != nil { - if os.IsNotExist(err) { - continue - } - return nativeCollectedDataset{}, nil, err - } - for _, entry := range entries { - if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" || strings.HasPrefix(entry.Name(), ".") || entry.Name() == "lock.json" { - continue - } - filePath := filepath.Join(familyDir, entry.Name()) - tableData, err := loadJSONObject(filePath) - if err != nil { - return nativeCollectedDataset{}, nil, err - } - outputName, _ := tableData["output"].(string) - if strings.TrimSpace(outputName) == "" { - outputName = strings.TrimSuffix(entry.Name(), filepath.Ext(entry.Name())) + ".2da" - } - collected, err := collectPlainDataset(nativeDataset{ - Name: filepath.ToSlash(filepath.Join("classes", family, strings.TrimSuffix(entry.Name(), filepath.Ext(entry.Name())))), - BasePath: filePath, - LockPath: filepath.Join(familyDir, "lock.json"), - OutputName: outputName, - Spec: specForDataset(filepath.ToSlash(filepath.Join("classes", family, strings.TrimSuffix(entry.Name(), filepath.Ext(entry.Name()))))), - }) - if err != nil { - return nativeCollectedDataset{}, nil, err - } - plainCollected = append(plainCollected, collected) - } - } - - slices.SortFunc(plainCollected, func(a, b nativeCollectedDataset) int { - return strings.Compare(a.Dataset.Name, b.Dataset.Name) - }) - return coreCollected, plainCollected, nil -} - -func registerLegacyClassesTableKey(tableKeyByOutput map[string]string, tableKey, outputName string) { - trimmedOutput := strings.TrimSpace(outputName) - if trimmedOutput != "" { - tableKeyByOutput[trimmedOutput] = tableKey - } - trimmedStem := strings.TrimSpace(outputStem(outputName)) - if trimmedStem != "" { - tableKeyByOutput[trimmedStem] = tableKey - } -} - -func rewriteLegacyClassesCoreRow(row map[string]any, tableKeyByOutput map[string]string) { - for _, field := range []string{"FeatsTable", "SavingThrowTable", "SkillsTable", "BonusFeatsTable", "PreReqTable"} { - tableKey := legacyClassesTableKeyForValue(row[field], tableKeyByOutput) - if tableKey == "" { - continue - } - row[field] = map[string]any{"table": tableKey} - } -} - -func legacyClassesTableKeyForValue(value any, tableKeyByOutput map[string]string) string { - switch typed := value.(type) { - case map[string]any: - tableKey, _ := typed["table"].(string) - return strings.TrimSpace(tableKey) - case string: - trimmed := strings.TrimSpace(typed) - if trimmed == "" || trimmed == nullValue { - return "" - } - if trimmed != strings.ToLower(trimmed) { - return "" - } - if tableKey, ok := tableKeyByOutput[trimmed]; ok { - return tableKey - } - return "" - default: - return "" - } -} diff --git a/internal/topdata/cloakmodel_migrate.go b/internal/topdata/cloakmodel_migrate.go deleted file mode 100644 index d26bdbf..0000000 --- a/internal/topdata/cloakmodel_migrate.go +++ /dev/null @@ -1,6 +0,0 @@ -package topdata - -func importLegacyCloakmodel(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - _ = legacyTLK - return importLegacyDatasetMirror(referenceBuilderDir, dataDir, "cloakmodel", "cloakmodel.2da", nil) -} diff --git a/internal/topdata/creaturespeed_migrate.go b/internal/topdata/creaturespeed_migrate.go deleted file mode 100644 index 7771a4c..0000000 --- a/internal/topdata/creaturespeed_migrate.go +++ /dev/null @@ -1,145 +0,0 @@ -package topdata - -import ( - "encoding/json" - "os" - "path/filepath" -) - -func importLegacyCreaturespeed(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - _ = legacyTLK - - legacyDir := filepath.Join(referenceBuilderDir, "data", "creaturespeed") - if _, err := os.Stat(legacyDir); err != nil { - if os.IsNotExist(err) { - return 0, nil - } - return 0, err - } - - targetDir := filepath.Join(dataDir, "creaturespeed") - targetPath := filepath.Join(targetDir, "creaturespeed.json") - if _, err := os.Stat(targetPath); err == nil { - obj, err := loadJSONObject(targetPath) - if err == nil && countLegacyTLKRefsInValue(obj) == 0 { - return 0, nil - } - } - - baseObj, err := loadJSONObject(filepath.Join(legacyDir, "base.json")) - if err != nil { - return 0, err - } - rows, err := mergeCreaturespeedRows(baseObj, filepath.Join(legacyDir, "modules")) - if err != nil { - return 0, err - } - if err := os.MkdirAll(targetDir, 0o755); err != nil { - return 0, err - } - - if err := removePathIfExists(filepath.Join(targetDir, "base.json")); err != nil { - return 0, err - } - if err := removePathIfExists(filepath.Join(targetDir, "lock.json")); err != nil { - return 0, err - } - if err := removePathIfExists(filepath.Join(targetDir, "modules")); err != nil { - return 0, err - } - - out := map[string]any{ - "output": "creaturespeed.2da", - "columns": baseObj["columns"], - "rows": rows, - } - - raw, err := json.MarshalIndent(out, "", " ") - if err != nil { - return 0, err - } - raw = append(raw, '\n') - if err := os.WriteFile(targetPath, raw, 0o644); err != nil { - return 0, err - } - return 1, nil -} - -func mergeCreaturespeedRows(baseObj map[string]any, modulesDir string) ([]any, error) { - rawRows, ok := baseObj["rows"].([]any) - if !ok { - return nil, nil - } - rows := make([]map[string]any, 0, len(rawRows)) - byID := map[int]map[string]any{} - for _, raw := range rawRows { - row, ok := deepCopyValue(raw).(map[string]any) - if !ok { - continue - } - delete(row, "key") - rows = append(rows, row) - if rawID, ok := row["id"]; ok { - id, err := asInt(rawID) - if err != nil { - return nil, err - } - byID[id] = row - } - } - - modulePaths, err := collectModulePaths(modulesDir) - if err != nil { - return nil, err - } - for _, path := range modulePaths { - obj, err := loadJSONObject(path) - if err != nil { - return nil, err - } - overrideList, ok := obj["overrides"].([]any) - if !ok { - continue - } - for _, raw := range overrideList { - override, ok := raw.(map[string]any) - if !ok { - continue - } - rawID, ok := override["id"] - if !ok { - continue - } - id, err := asInt(rawID) - if err != nil { - return nil, err - } - row := byID[id] - if row == nil { - continue - } - for key, value := range override { - if key == "id" || key == "key" || key == "_tlk" { - continue - } - row[key] = deepCopyValue(value) - } - } - } - - out := make([]any, 0, len(rows)) - for _, row := range rows { - out = append(out, row) - } - return out, nil -} - -func removePathIfExists(path string) error { - if _, err := os.Stat(path); err != nil { - if os.IsNotExist(err) { - return nil - } - return err - } - return os.RemoveAll(path) -} diff --git a/internal/topdata/damagetypes_migrate.go b/internal/topdata/damagetypes_migrate.go deleted file mode 100644 index d3c70cb..0000000 --- a/internal/topdata/damagetypes_migrate.go +++ /dev/null @@ -1,288 +0,0 @@ -package topdata - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "sort" - "strings" -) - -func importLegacyDamagetypes(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - _ = legacyTLK - - legacyRoot := filepath.Join(referenceBuilderDir, "data", "damagetypes") - if _, err := os.Stat(legacyRoot); err != nil { - if os.IsNotExist(err) { - return 0, nil - } - return 0, err - } - - targetDir := filepath.Join(dataDir, "damagetypes", "registry") - targetPath := filepath.Join(targetDir, "types.json") - if _, err := os.Stat(targetPath); err == nil { - obj, err := loadJSONObject(targetPath) - if err == nil && countLegacyTLKRefsInValue(obj) == 0 { - return 0, nil - } - } - - coreBase, err := loadJSONObject(filepath.Join(legacyRoot, "core", "base.json")) - if err != nil { - return 0, err - } - groupBase, err := loadJSONObject(filepath.Join(legacyRoot, "groups", "base.json")) - if err != nil { - return 0, err - } - hitvisualBase, err := loadJSONObject(filepath.Join(legacyRoot, "hitvisual", "base.json")) - if err != nil { - return 0, err - } - coreModule, err := loadJSONObject(filepath.Join(legacyRoot, "core", "modules", "add_eos.json")) - if err != nil { - return 0, err - } - coreLock, err := loadLockfile(filepath.Join(legacyRoot, "core", "lock.json")) - if err != nil { - return 0, err - } - groupModule, err := loadJSONObject(filepath.Join(legacyRoot, "groups", "modules", "add_eos.json")) - if err != nil { - return 0, err - } - groupLock, err := loadLockfile(filepath.Join(legacyRoot, "groups", "lock.json")) - if err != nil { - return 0, err - } - hitvisualModule, err := loadJSONObject(filepath.Join(legacyRoot, "hitvisual", "modules", "add_eos.json")) - if err != nil { - return 0, err - } - hitvisualLock, err := loadLockfile(filepath.Join(legacyRoot, "hitvisual", "lock.json")) - if err != nil { - return 0, err - } - - rows, lockData, err := mergeLegacyDamagetypes(coreBase, groupBase, hitvisualBase, coreModule, coreLock, groupModule, groupLock, hitvisualModule, hitvisualLock) - if err != nil { - return 0, err - } - - if err := os.MkdirAll(targetDir, 0o755); err != nil { - return 0, err - } - if err := removePathIfExists(filepath.Join(dataDir, "damagetypes", "core")); err != nil { - return 0, err - } - if err := removePathIfExists(filepath.Join(dataDir, "damagetypes", "groups")); err != nil { - return 0, err - } - if err := removePathIfExists(filepath.Join(dataDir, "damagetypes", "hitvisual")); err != nil { - return 0, err - } - - typesOut := map[string]any{"rows": rows} - raw, err := json.MarshalIndent(typesOut, "", " ") - if err != nil { - return 0, err - } - raw = append(raw, '\n') - if err := os.WriteFile(targetPath, raw, 0o644); err != nil { - return 0, err - } - if err := saveLockfile(filepath.Join(targetDir, damagetypesRegistryLock), lockData); err != nil { - return 0, err - } - return 2, nil -} - -func mergeLegacyDamagetypes(coreBase, groupBase, hitvisualBase, coreModule map[string]any, coreLock map[string]int, groupModule map[string]any, groupLock map[string]int, hitvisualModule map[string]any, hitvisualLock map[string]int) ([]map[string]any, map[string]int, error) { - coreRows, err := coerceRowMapSlice(coreBase["rows"]) - if err != nil { - return nil, nil, err - } - groupRows, err := coerceRowMapSlice(groupBase["rows"]) - if err != nil { - return nil, nil, err - } - hitRows, err := coerceRowMapSlice(hitvisualBase["rows"]) - if err != nil { - return nil, nil, err - } - groupByID := map[int]map[string]any{} - for _, row := range groupRows { - id, err := asInt(row["id"]) - if err != nil { - return nil, nil, err - } - groupByID[id] = row - } - hitByID := map[int]map[string]any{} - for _, row := range hitRows { - id, err := asInt(row["id"]) - if err != nil { - return nil, nil, err - } - hitByID[id] = row - } - - outRows := make([]map[string]any, 0, len(coreRows)) - lockData := map[string]int{} - for _, core := range coreRows { - id, err := asInt(core["id"]) - if err != nil { - return nil, nil, err - } - groupID, err := asInt(core["DamageTypeGroup"]) - if err != nil { - return nil, nil, err - } - group := groupByID[groupID] - if group == nil { - return nil, nil, fmt.Errorf("core id %d references missing group %d", id, groupID) - } - hit := hitByID[id] - if hit == nil { - return nil, nil, fmt.Errorf("core id %d is missing hitvisual row", id) - } - key := "damagetype:" + normalizeDamagetypeKey(core["Label"]) - lockData[key] = id - outRows = append(outRows, map[string]any{ - "key": key, - "id": id, - "label": deepCopyValue(core["Label"]), - "charsheet_strref": deepCopyValue(core["CharsheetStrref"]), - "damage_type_group": strconvString(groupID), - "damage_ranged_projectile": deepCopyValue(core["DamageRangedProjectile"]), - "group_label": deepCopyValue(group["Label"]), - "feedback_strref": deepCopyValue(group["FeedbackStrref"]), - "color_r": deepCopyValue(group["ColorR"]), - "color_g": deepCopyValue(group["ColorG"]), - "color_b": deepCopyValue(group["ColorB"]), - "visual_effect_id": deepCopyValue(hit["VisualEffectID"]), - "ranged_effect_id": deepCopyValue(hit["RangedEffectID"]), - }) - } - - coreEntries, err := coerceEntriesMap(coreModule["entries"]) - if err != nil { - return nil, nil, err - } - groupEntries, err := coerceEntriesMap(groupModule["entries"]) - if err != nil { - return nil, nil, err - } - hitEntries, err := coerceEntriesMap(hitvisualModule["entries"]) - if err != nil { - return nil, nil, err - } - - keys := make([]string, 0, len(coreEntries)) - for key := range coreEntries { - keys = append(keys, key) - } - sort.Slice(keys, func(i, j int) bool { - a := strings.TrimPrefix(keys[i], "damagetypes:") - b := strings.TrimPrefix(keys[j], "damagetypes:") - return a < b - }) - for _, coreKey := range keys { - core := coreEntries[coreKey] - suffix := strings.TrimPrefix(coreKey, "damagetypes:") - group := groupEntries["damagetypegroups:"+suffix] - if group == nil { - return nil, nil, fmt.Errorf("%s: missing matching group entry", coreKey) - } - hit := hitEntries["damagehitvisual:"+suffix] - if hit == nil { - return nil, nil, fmt.Errorf("%s: missing matching hitvisual entry", coreKey) - } - id, ok := coreLock[coreKey] - if !ok { - return nil, nil, fmt.Errorf("%s: missing core lock id", coreKey) - } - groupKey := "damagetypegroups:" + suffix - groupID, ok := groupLock[groupKey] - if !ok { - return nil, nil, fmt.Errorf("%s: missing group lock id", groupKey) - } - hitKey := "damagehitvisual:" + suffix - hitID, ok := hitvisualLock[hitKey] - if !ok { - return nil, nil, fmt.Errorf("%s: missing hitvisual lock id", hitKey) - } - if hitID != id { - return nil, nil, fmt.Errorf("%s: core id %d does not match hitvisual id %d", suffix, id, hitID) - } - key := "damagetype:" + suffix - lockData[key] = id - outRows = append(outRows, map[string]any{ - "key": key, - "id": id, - "label": deepCopyValue(core["Label"]), - "charsheet_strref": deepCopyValue(core["CharsheetStrref"]), - "damage_type_group": strconvString(groupID), - "damage_ranged_projectile": deepCopyValue(core["DamageRangedProjectile"]), - "group_label": deepCopyValue(group["Label"]), - "feedback_strref": deepCopyValue(group["FeedbackStrref"]), - "color_r": deepCopyValue(group["ColorR"]), - "color_g": deepCopyValue(group["ColorG"]), - "color_b": deepCopyValue(group["ColorB"]), - "visual_effect_id": deepCopyValue(hit["VisualEffectID"]), - "ranged_effect_id": deepCopyValue(hit["RangedEffectID"]), - }) - } - - sort.Slice(outRows, func(i, j int) bool { - return outRows[i]["id"].(int) < outRows[j]["id"].(int) - }) - return outRows, lockData, nil -} - -func coerceRowMapSlice(value any) ([]map[string]any, error) { - rawRows, ok := value.([]any) - if !ok { - return nil, fmt.Errorf("rows must be an array") - } - rows := make([]map[string]any, 0, len(rawRows)) - for _, raw := range rawRows { - row, ok := raw.(map[string]any) - if !ok { - return nil, fmt.Errorf("row must be an object") - } - rows = append(rows, row) - } - return rows, nil -} - -func coerceEntriesMap(value any) (map[string]map[string]any, error) { - rawEntries, ok := value.(map[string]any) - if !ok { - return nil, fmt.Errorf("entries must be an object") - } - entries := make(map[string]map[string]any, len(rawEntries)) - for key, raw := range rawEntries { - row, ok := raw.(map[string]any) - if !ok { - return nil, fmt.Errorf("entry %s must be an object", key) - } - entries[key] = row - } - return entries, nil -} - -func normalizeDamagetypeKey(value any) string { - text := strings.TrimSpace(strings.ToLower(format2DAValue(value))) - text = strings.TrimSuffix(text, "damage") - text = strings.ReplaceAll(text, " ", "") - text = strings.ReplaceAll(text, "_", "") - text = strings.ReplaceAll(text, "-", "") - return text -} - -func strconvString(v int) string { - return fmt.Sprintf("%d", v) -} diff --git a/internal/topdata/doortypes_migrate.go b/internal/topdata/doortypes_migrate.go deleted file mode 100644 index 234d868..0000000 --- a/internal/topdata/doortypes_migrate.go +++ /dev/null @@ -1,99 +0,0 @@ -package topdata - -import ( - "encoding/json" - "os" - "path/filepath" -) - -func importLegacyDoortypes(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - _ = legacyTLK - - legacyDir := filepath.Join(referenceBuilderDir, "data", "doortypes") - if _, err := os.Stat(legacyDir); err != nil { - if os.IsNotExist(err) { - return 0, nil - } - return 0, err - } - - targetDir := filepath.Join(dataDir, "doortypes") - targetBasePath := filepath.Join(targetDir, "base.json") - targetLockPath := filepath.Join(targetDir, "lock.json") - targetModulesDir := filepath.Join(targetDir, "modules") - moduleNames := []string{ - "add_pld01.json", - "add_sic11.json", - "add_tapr.json", - "add_tdm01.json", - "add_tdx01.json", - "add_tei01.json", - "add_tfm01.json", - } - targetModulePaths := make([]string, 0, len(moduleNames)) - for _, name := range moduleNames { - targetModulePaths = append(targetModulePaths, filepath.Join(targetModulesDir, name)) - } - if fileExists(targetBasePath) && fileExists(targetLockPath) { - allModulesPresent := true - for _, path := range targetModulePaths { - if !fileExists(path) { - allModulesPresent = false - break - } - } - if allModulesPresent { - return 0, nil - } - } - - baseObj, err := loadJSONObject(filepath.Join(legacyDir, "base.json")) - if err != nil { - return 0, err - } - baseObj["output"] = "doortypes.2da" - - lockObj, err := loadJSONObject(filepath.Join(legacyDir, "lock.json")) - if err != nil { - return 0, err - } - - moduleObjs := make([]map[string]any, 0, len(moduleNames)) - for _, name := range moduleNames { - obj, err := loadJSONObject(filepath.Join(legacyDir, "modules", name)) - if err != nil { - return 0, err - } - moduleObjs = append(moduleObjs, obj) - } - - if err := os.MkdirAll(targetModulesDir, 0o755); err != nil { - return 0, err - } - - writes := []struct { - path string - obj map[string]any - }{ - {path: targetBasePath, obj: baseObj}, - {path: targetLockPath, obj: lockObj}, - } - for i, path := range targetModulePaths { - writes = append(writes, struct { - path string - obj map[string]any - }{path: path, obj: moduleObjs[i]}) - } - - for _, write := range writes { - raw, err := json.MarshalIndent(write.obj, "", " ") - if err != nil { - return 0, err - } - raw = append(raw, '\n') - if err := os.WriteFile(write.path, raw, 0o644); err != nil { - return 0, err - } - } - return len(writes), nil -} diff --git a/internal/topdata/feat_migrate.go b/internal/topdata/feat_migrate.go deleted file mode 100644 index b7b48f1..0000000 --- a/internal/topdata/feat_migrate.go +++ /dev/null @@ -1,64 +0,0 @@ -package topdata - -import ( - "encoding/json" - "os" - "path/filepath" -) - -func importLegacyFeat(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - _ = legacyTLK - - legacyDir := filepath.Join(referenceBuilderDir, "data", "feat") - if _, err := os.Stat(legacyDir); err != nil { - if os.IsNotExist(err) { - return 0, nil - } - return 0, err - } - legacyBasePath := filepath.Join(legacyDir, "base.json") - if _, err := os.Stat(legacyBasePath); err != nil { - if os.IsNotExist(err) { - return 0, nil - } - return 0, err - } - - targetDir := filepath.Join(dataDir, "feat") - targetBasePath := filepath.Join(targetDir, "base.json") - var targetObj map[string]any - if _, err := os.Stat(targetBasePath); err == nil { - targetObj, err = loadJSONObject(targetBasePath) - if err != nil { - return 0, err - } - } else if !os.IsNotExist(err) { - return 0, err - } - if targetObj != nil { - if rows, ok := targetObj["rows"].([]any); ok && len(rows) > 0 { - return 0, nil - } - } - - baseObj, err := loadJSONObject(legacyBasePath) - if err != nil { - return 0, err - } - baseObj["output"] = "feat.2da" - baseObj["compare_reference"] = false - canonicalizeWikiMetadataDocument(baseObj) - - if err := os.MkdirAll(targetDir, 0o755); err != nil { - return 0, err - } - raw, err := json.MarshalIndent(baseObj, "", " ") - if err != nil { - return 0, err - } - raw = append(raw, '\n') - if err := os.WriteFile(targetBasePath, raw, 0o644); err != nil { - return 0, err - } - return 1, nil -} diff --git a/internal/topdata/genericdoors_migrate.go b/internal/topdata/genericdoors_migrate.go deleted file mode 100644 index 07f3c15..0000000 --- a/internal/topdata/genericdoors_migrate.go +++ /dev/null @@ -1,6 +0,0 @@ -package topdata - -func importLegacyGenericdoors(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - _ = legacyTLK - return importLegacyDatasetMirror(referenceBuilderDir, dataDir, "genericdoors", "genericdoors.2da", nil) -} diff --git a/internal/topdata/itemprops_registry.go b/internal/topdata/itemprops_registry.go index 1c948ec..b873edf 100644 --- a/internal/topdata/itemprops_registry.go +++ b/internal/topdata/itemprops_registry.go @@ -601,397 +601,6 @@ func deepCopyValue(value any) any { return out } -func importLegacyItempropsRegistry(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - legacyDataDir := filepath.Join(referenceBuilderDir, "data") - registryDir := filepath.Join(dataDir, filepath.FromSlash(itempropsRegistryDirName)) - if _, err := os.Stat(filepath.Join(registryDir, "properties.json")); err == nil { - if refs, err := countLegacyTLKRefsInRegistry(registryDir); err == nil && refs == 0 { - return 0, nil - } - } - if _, err := os.Stat(filepath.Join(legacyDataDir, "itemprops")); err != nil { - if os.IsNotExist(err) { - return 0, nil - } - return 0, err - } - - defs, err := collectBaseDataset(nativeDataset{ - Name: "itemprops/defs", - BasePath: filepath.Join(legacyDataDir, "itemprops", "defs", "base.json"), - LockPath: filepath.Join(legacyDataDir, "itemprops", "defs", "lock.json"), - ModulesDir: filepath.Join(legacyDataDir, "itemprops", "defs", "modules"), - Spec: specForDataset("itemprops"), - }) - if err != nil { - return 0, err - } - sets, err := collectBaseDataset(nativeDataset{ - Name: "itemprops/sets", - BasePath: filepath.Join(legacyDataDir, "itemprops", "sets", "base.json"), - LockPath: filepath.Join(legacyDataDir, "itemprops", "sets", "lock.json"), - ModulesDir: filepath.Join(legacyDataDir, "itemprops", "sets", "modules"), - Spec: specForDataset("itemprops"), - }) - if err != nil { - return 0, err - } - costIndex, err := collectBaseDataset(nativeDataset{ - Name: "itemprops/costtables/index", - BasePath: filepath.Join(legacyDataDir, "itemprops", "costtables", "index", "base.json"), - LockPath: filepath.Join(legacyDataDir, "itemprops", "costtables", "index", "lock.json"), - ModulesDir: filepath.Join(legacyDataDir, "itemprops", "costtables", "index", "modules"), - Spec: specForDataset("itemprops"), - }) - if err != nil { - return 0, err - } - paramIndex, err := collectBaseDataset(nativeDataset{ - Name: "itemprops/paramtables/index", - BasePath: filepath.Join(legacyDataDir, "itemprops", "paramtables", "index", "base.json"), - LockPath: filepath.Join(legacyDataDir, "itemprops", "paramtables", "index", "lock.json"), - ModulesDir: filepath.Join(legacyDataDir, "itemprops", "paramtables", "index", "modules"), - Spec: specForDataset("itemprops"), - }) - if err != nil { - return 0, err - } - - ownedCostTables, err := collectLegacyOwnedItempropTables(filepath.Join(legacyDataDir, "itemprops", "costtables"), "index") - if err != nil { - return 0, err - } - ownedParamTables, err := collectLegacyOwnedItempropTables(filepath.Join(legacyDataDir, "itemprops", "paramtables"), "index") - if err != nil { - return 0, err - } - ownedSubtypeTables, err := collectLegacyOwnedItempropTables(filepath.Join(legacyDataDir, "itemprops", "subtypes"), "") - if err != nil && !os.IsNotExist(err) { - return 0, err - } - - setsByID := map[int]map[string]any{} - for _, row := range sets.Rows { - setsByID[row["id"].(int)] = row - } - costByID := map[int]map[string]any{} - for _, row := range costIndex.Rows { - costByID[row["id"].(int)] = row - } - paramByID := map[int]map[string]any{} - for _, row := range paramIndex.Rows { - paramByID[row["id"].(int)] = row - } - - registryLock := map[string]int{} - legacyKeyByID := map[string]string{} - if legacyTLK != nil { - for key, id := range legacyTLK.Lock { - legacyKeyByID[strconv.Itoa(id)] = key - } - } - properties := make([]map[string]any, 0, len(defs.Rows)) - subtypeRegistryByKey := map[string]map[string]any{} - for _, row := range defs.Rows { - rowID := row["id"].(int) - key := canonicalItempropPropertyKey(row) - registryLock[key] = rowID - - property := map[string]any{ - "key": key, - "label": row["Label"], - "name": deepCopyValue(row["Name"]), - "property_text": deepCopyValue(row["GameStrRef"]), - } - if description := nullableRegistryField(row, "Description"); description != nullValue { - property["description"] = description - } - if setRow, ok := setsByID[rowID]; ok { - property["availability"] = importLegacyAvailability(setRow) - if setName := nullableRegistryField(setRow, "StringRef"); setName != nullValue && setName != stringField(row, "Name") { - property["set_name"] = setName - } - if setLabel := stringField(setRow, "Label"); setLabel != "" && setLabel != stringField(row, "Label") { - property["set_label"] = setLabel - } - } else { - property["availability"] = []any{} - } - - if subtypeResRef := stringField(row, "SubTypeResRef"); subtypeResRef != "" && subtypeResRef != nullValue { - subtypeKey := canonicalModelKey("subtype_models", subtypeResRef) - property["subtype"] = map[string]any{"ref": subtypeKey} - if _, ok := subtypeRegistryByKey[subtypeKey]; !ok { - subtypeRegistryByKey[subtypeKey] = buildLegacySubtypeModel(subtypeKey, subtypeResRef, ownedSubtypeTables) - } - } - costValue := stringField(row, "Cost") - costID := stringField(row, "CostTableResRef") - if costValue != "" && costValue != nullValue || costID != "" && costID != nullValue { - cost := map[string]any{} - if costValue != "" && costValue != nullValue { - cost["value"] = costValue - } - if costID != "" && costID != nullValue { - id, err := strconv.Atoi(costID) - if err != nil { - return 0, err - } - costRow, ok := costByID[id] - if !ok { - return 0, fmt.Errorf("%s references missing cost table index %s", key, costID) - } - cost["ref"] = canonicalModelKey("cost_models", stringField(costRow, "Name")) - } - property["cost"] = cost - } - if paramID := stringField(row, "Param1ResRef"); paramID != "" && paramID != nullValue { - id, err := strconv.Atoi(paramID) - if err != nil { - return 0, err - } - paramRow, ok := paramByID[id] - if !ok { - return 0, fmt.Errorf("%s references missing param table index %s", key, paramID) - } - property["param"] = map[string]any{"ref": canonicalModelKey("param_models", stringField(paramRow, "TableResRef"))} - } - if legacyTLK != nil { - property["name"] = importLegacyStrrefValue(property["name"], key+".name", legacyTLK, legacyKeyByID) - property["property_text"] = importLegacyStrrefValue(property["property_text"], key+".property_text", legacyTLK, legacyKeyByID) - if description, ok := property["description"]; ok { - property["description"] = importLegacyStrrefValue(description, key+".description", legacyTLK, legacyKeyByID) - } - if updated, _, err := inlineLegacyTLKValue(property, legacyTLK); err != nil { - return 0, err - } else if updated { - // property updated in place - } - } - properties = append(properties, property) - } - - costModels := make([]map[string]any, 0, len(costIndex.Rows)) - for _, row := range costIndex.Rows { - key := canonicalModelKey("cost_models", stringField(row, "Name")) - registryLock[key] = row["id"].(int) - model := map[string]any{ - "key": key, - "index_name": row["Name"], - "label": row["Label"], - "client_load": row["ClientLoad"], - "table": buildLegacyOwnedModelTable(stringField(row, "Name"), ownedCostTables), - } - costModels = append(costModels, model) - } - - paramModels := make([]map[string]any, 0, len(paramIndex.Rows)) - for _, row := range paramIndex.Rows { - key := canonicalModelKey("param_models", stringField(row, "TableResRef")) - registryLock[key] = row["id"].(int) - model := map[string]any{ - "key": key, - "name": deepCopyValue(row["Name"]), - "label": row["Lable"], - "table": buildLegacyOwnedModelTable(stringField(row, "TableResRef"), ownedParamTables), - } - paramModels = append(paramModels, model) - } - - subtypeModels := make([]map[string]any, 0, len(subtypeRegistryByKey)) - for _, key := range sortedKeysAny(subtypeRegistryByKey) { - subtypeModels = append(subtypeModels, subtypeRegistryByKey[key]) - } - - writes := []struct { - path string - obj map[string]any - }{ - {filepath.Join(registryDir, "properties.json"), map[string]any{"rows": properties}}, - {filepath.Join(registryDir, "cost_models.json"), map[string]any{"rows": costModels}}, - {filepath.Join(registryDir, "param_models.json"), map[string]any{"rows": paramModels}}, - {filepath.Join(registryDir, "subtype_models.json"), map[string]any{"rows": subtypeModels}}, - {filepath.Join(registryDir, itempropsRegistryLock), anyMapInt(registryLock)}, - } - if err := os.MkdirAll(registryDir, 0o755); err != nil { - return 0, err - } - for _, write := range writes { - raw, err := json.MarshalIndent(write.obj, "", " ") - if err != nil { - return 0, err - } - raw = append(raw, '\n') - if err := os.WriteFile(write.path, raw, 0o644); err != nil { - return 0, err - } - } - return len(writes), nil -} - -func countLegacyTLKRefsInRegistry(registryDir string) (int, error) { - paths, err := collectDataJSONPaths(registryDir) - if err != nil { - return 0, err - } - refs := 0 - for _, path := range paths { - obj, err := loadJSONObject(path) - if err != nil { - return 0, err - } - refs += countLegacyTLKRefsInValue(obj) - } - return refs, nil -} - -func collectLegacyOwnedItempropTables(root, skipDir string) (map[string]map[string]any, error) { - result := map[string]map[string]any{} - entries, err := os.ReadDir(root) - if err != nil { - return nil, err - } - for _, entry := range entries { - if !entry.IsDir() { - continue - } - if skipDir != "" && entry.Name() == skipDir { - continue - } - dir := filepath.Join(root, entry.Name()) - basePath := filepath.Join(dir, "base.json") - if _, err := os.Stat(basePath); err != nil { - continue - } - collected, err := collectBaseDataset(nativeDataset{ - Name: filepath.ToSlash(filepath.Join("itemprops", filepath.Base(root), entry.Name())), - BasePath: basePath, - LockPath: filepath.Join(dir, "lock.json"), - ModulesDir: filepath.Join(dir, "modules"), - Spec: specForDataset("itemprops"), - }) - if err != nil { - return nil, err - } - result[strings.ToLower(outputStem(collected.Dataset.OutputName))] = map[string]any{ - "ownership": "owned", - "resref": outputStem(collected.Dataset.OutputName), - "output": collected.Dataset.OutputName, - "columns": stringSliceToAny(collected.Columns), - "rows": rowsToAny(collected.Rows), - } - } - return result, nil -} - -func buildLegacySubtypeModel(key, resref string, owned map[string]map[string]any) map[string]any { - return map[string]any{ - "key": key, - "table_resref": resref, - "table": buildLegacyOwnedModelTable(resref, owned), - } -} - -func buildLegacyOwnedModelTable(resref string, owned map[string]map[string]any) map[string]any { - if table, ok := owned[strings.ToLower(resref)]; ok { - return table - } - return map[string]any{ - "ownership": "external", - "resref": resref, - } -} - -func canonicalItempropPropertyKey(row map[string]any) string { - if key := stringField(row, "key"); key != "" { - return "itemprop:" + strings.TrimPrefix(key, "itempropdef:") - } - return canonicalModelKey("itemprop", stringField(row, "Label")) -} - -func canonicalModelKey(prefix, source string) string { - normalized := strings.ToLower(strings.TrimSpace(source)) - normalized = strings.ReplaceAll(normalized, " ", "") - normalized = strings.ReplaceAll(normalized, "-", "") - normalized = strings.ReplaceAll(normalized, "_", "") - return prefix + ":" + normalized -} - -func importLegacyStrrefValue(value any, fallbackKey string, legacy *legacyTLKData, keyByID map[string]string) any { - if legacy == nil { - return value - } - if _, ok, _ := parseTLKPayload(value, true); ok { - return value - } - _ = keyByID - key := fallbackKey - entry, ok := legacy.Entries[key] - if !ok { - return value - } - payload := map[string]any{ - "key": key, - "text": entry.Text, - } - if entry.SoundResRef != "" { - payload["sound_resref"] = entry.SoundResRef - } - if entry.SoundLength != 0 { - payload["sound_length"] = entry.SoundLength - } - return map[string]any{"tlk": payload} -} - -func importLegacyAvailability(row map[string]any) []any { - out := make([]any, 0) - for _, column := range itempropsAvailabilityColumns { - value, ok := row[column] - if !ok { - continue - } - text := formatRegistryScalar(value) - if text != "1" { - continue - } - out = append(out, normalizeAvailabilityName(column)) - } - return out -} - -func anyMapInt(values map[string]int) map[string]any { - out := make(map[string]any, len(values)) - for key, value := range values { - out[key] = value - } - return out -} - -func stringSliceToAny(values []string) []any { - out := make([]any, 0, len(values)) - for _, value := range values { - out = append(out, value) - } - return out -} - -func rowsToAny(rows []map[string]any) []any { - out := make([]any, 0, len(rows)) - for _, row := range rows { - out = append(out, deepCopyValue(row)) - } - return out -} - -func sortedKeysAny(values map[string]map[string]any) []string { - out := make([]string, 0, len(values)) - for key := range values { - out = append(out, key) - } - slices.Sort(out) - return out -} - func validateItempropsRegistryGraph(dataDir string, report *ValidationReport) { registry, err := loadItempropsRegistry(dataDir) if err != nil { diff --git a/internal/topdata/legacy_dataset_migrate.go b/internal/topdata/legacy_dataset_migrate.go deleted file mode 100644 index 0037cbf..0000000 --- a/internal/topdata/legacy_dataset_migrate.go +++ /dev/null @@ -1,176 +0,0 @@ -package topdata - -import ( - "encoding/json" - "io/fs" - "os" - "path/filepath" - "sort" - "strings" -) - -type legacyDatasetTransform func(relativePath string, obj map[string]any) - -func importLegacyDatasetMirror(referenceBuilderDir, dataDir, datasetName, outputName string, transform legacyDatasetTransform) (int, error) { - legacyDir := filepath.Join(referenceBuilderDir, "data", datasetName) - if _, err := os.Stat(legacyDir); err != nil { - if os.IsNotExist(err) { - return 0, nil - } - return 0, err - } - - targetDir := filepath.Join(dataDir, datasetName) - baseObj, err := loadJSONObject(filepath.Join(legacyDir, "base.json")) - if err != nil { - return 0, err - } - if outputName != "" { - baseObj["output"] = outputName - } - if transform != nil { - transform("base.json", baseObj) - } - - lockObj, err := loadJSONObject(filepath.Join(legacyDir, "lock.json")) - if err != nil { - return 0, err - } - if transform != nil { - transform("lock.json", lockObj) - } - - moduleRelPaths, err := collectJSONRelativePaths(filepath.Join(legacyDir, "modules")) - if err != nil { - return 0, err - } - - moduleObjs := make(map[string]map[string]any, len(moduleRelPaths)) - for _, relPath := range moduleRelPaths { - obj, err := loadJSONObject(filepath.Join(legacyDir, "modules", relPath)) - if err != nil { - return 0, err - } - normalizedRel := filepath.ToSlash(relPath) - if transform != nil { - transform(filepath.ToSlash(filepath.Join("modules", normalizedRel)), obj) - } - moduleObjs[normalizedRel] = obj - } - - if err := os.MkdirAll(targetDir, 0o755); err != nil { - return 0, err - } - - updated := 0 - for _, write := range []struct { - path string - obj map[string]any - }{ - {path: filepath.Join(targetDir, "base.json"), obj: baseObj}, - {path: filepath.Join(targetDir, "lock.json"), obj: lockObj}, - } { - changed, err := writeJSONObjectIfChanged(write.path, write.obj) - if err != nil { - return 0, err - } - if changed { - updated++ - } - } - - targetModulesDir := filepath.Join(targetDir, "modules") - if len(moduleObjs) > 0 { - if err := os.MkdirAll(targetModulesDir, 0o755); err != nil { - return 0, err - } - } - for relPath, obj := range moduleObjs { - targetPath := filepath.Join(targetModulesDir, filepath.FromSlash(relPath)) - if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil { - return 0, err - } - changed, err := writeJSONObjectIfChanged(targetPath, obj) - if err != nil { - return 0, err - } - if changed { - updated++ - } - } - - staleModules, err := collectJSONRelativePaths(targetModulesDir) - if err != nil { - return 0, err - } - for _, relPath := range staleModules { - normalizedRel := filepath.ToSlash(relPath) - if _, ok := moduleObjs[normalizedRel]; ok { - continue - } - if err := os.Remove(filepath.Join(targetModulesDir, filepath.FromSlash(normalizedRel))); err != nil && !os.IsNotExist(err) { - return 0, err - } - updated++ - } - - return updated, nil -} - -func collectJSONRelativePaths(root string) ([]string, error) { - if _, err := os.Stat(root); err != nil { - if os.IsNotExist(err) { - return nil, nil - } - return nil, err - } - - var relPaths []string - err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - return nil - } - if !strings.EqualFold(filepath.Ext(d.Name()), ".json") { - return nil - } - relPath, err := filepath.Rel(root, path) - if err != nil { - return err - } - relPaths = append(relPaths, filepath.ToSlash(relPath)) - return nil - }) - if err != nil { - return nil, err - } - sort.Strings(relPaths) - return relPaths, nil -} - -func writeJSONObjectIfChanged(path string, obj map[string]any) (bool, error) { - raw, err := json.MarshalIndent(obj, "", " ") - if err != nil { - return false, err - } - raw = append(raw, '\n') - - current, err := os.ReadFile(path) - if err == nil && string(current) == string(raw) { - return false, nil - } - if err != nil && !os.IsNotExist(err) { - return false, err - } - if err := os.WriteFile(path, raw, 0o644); err != nil { - return false, err - } - return true, nil -} - -func fileExists(path string) bool { - _, err := os.Stat(path) - return err == nil -} diff --git a/internal/topdata/loadscreens_migrate.go b/internal/topdata/loadscreens_migrate.go deleted file mode 100644 index b0d6203..0000000 --- a/internal/topdata/loadscreens_migrate.go +++ /dev/null @@ -1,6 +0,0 @@ -package topdata - -func importLegacyLoadscreens(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - _ = legacyTLK - return importLegacyDatasetMirror(referenceBuilderDir, dataDir, "loadscreens", "loadscreens.2da", nil) -} diff --git a/internal/topdata/masterfeats_migrate.go b/internal/topdata/masterfeats_migrate.go deleted file mode 100644 index 76bbe82..0000000 --- a/internal/topdata/masterfeats_migrate.go +++ /dev/null @@ -1,237 +0,0 @@ -package topdata - -import ( - "encoding/json" - "os" - "path/filepath" - "strconv" -) - -func importLegacyMasterfeats(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - legacyDir := filepath.Join(referenceBuilderDir, "data", "feat", "masterfeats") - if _, err := os.Stat(legacyDir); err != nil { - if os.IsNotExist(err) { - return 0, nil - } - return 0, err - } - - targetDir := filepath.Join(dataDir, "masterfeats") - targetPath := filepath.Join(targetDir, "base.json") - legacyPlainPath := filepath.Join(targetDir, "masterfeats.json") - if _, err := os.Stat(targetPath); err == nil { - obj, err := loadJSONObject(targetPath) - if err == nil && countLegacyTLKRefsInValue(obj) == 0 { - return 0, nil - } - } - - collected, err := importLegacyMasterfeatsDataset(legacyDir) - if err != nil { - return 0, err - } - referenceIDs, err := collectReferenceLockIDs(filepath.Join(referenceBuilderDir, "data")) - if err != nil { - return 0, err - } - - rows := make([]any, 0, len(collected.Rows)) - for _, row := range collected.Rows { - copyRow, ok := deepCopyValue(row).(map[string]any) - if !ok { - continue - } - if legacyTLK != nil { - if _, _, err := inlineLegacyTLKValue(copyRow, legacyTLK); err != nil { - return 0, err - } - } - copyRow = rewriteImportedIDRefs(copyRow, referenceIDs) - rows = append(rows, copyRow) - } - - if err := os.MkdirAll(targetDir, 0o755); err != nil { - return 0, err - } - writes := []struct { - path string - obj map[string]any - }{ - { - path: targetPath, - obj: map[string]any{ - "output": collected.Dataset.OutputName, - "columns": stringSliceToAny(collected.Columns), - "rows": rows, - }, - }, - { - path: filepath.Join(targetDir, "lock.json"), - obj: anyMapInt(collected.LockData), - }, - } - for _, write := range writes { - raw, err := json.MarshalIndent(write.obj, "", " ") - if err != nil { - return 0, err - } - raw = append(raw, '\n') - if err := os.WriteFile(write.path, raw, 0o644); err != nil { - return 0, err - } - } - updatedFiles := len(writes) - if err := os.Remove(legacyPlainPath); err == nil { - updatedFiles++ - } else if err != nil && !os.IsNotExist(err) { - return 0, err - } - return updatedFiles, nil -} - -func importLegacyMasterfeatsDataset(legacyDir string) (nativeCollectedDataset, error) { - dataset := nativeDataset{ - Name: "masterfeats", - BasePath: filepath.Join(legacyDir, "masterfeats.json"), - LockPath: filepath.Join(legacyDir, "lock.json"), - Spec: specForDataset("masterfeats"), - } - tableData, err := loadJSONObject(dataset.BasePath) - if err != nil { - return nativeCollectedDataset{}, err - } - columns, err := parseColumns(tableData, dataset.Name) - if err != nil { - return nativeCollectedDataset{}, err - } - rawRows, ok := tableData["rows"].([]any) - if !ok { - return nativeCollectedDataset{}, nil - } - lockData, err := loadLockfile(dataset.LockPath) - if err != nil { - return nativeCollectedDataset{}, err - } - usedIDs := map[int]struct{}{} - for _, rowID := range lockData { - usedIDs[rowID] = struct{}{} - } - - rows := make([]map[string]any, 0, len(rawRows)) - explicitID := make([]bool, 0, len(rawRows)) - for index, raw := range rawRows { - rowObj, ok := raw.(map[string]any) - if !ok { - continue - } - rowID := index - if value, ok := rowObj["id"]; ok { - parsed, err := asInt(value) - if err != nil { - return nativeCollectedDataset{}, err - } - rowID = parsed - } - row := map[string]any{"id": rowID} - for _, column := range columns { - row[column] = nullValue - } - for key, value := range rowObj { - switch key { - case "id": - case "key": - if keyText, ok := value.(string); ok && keyText != "" { - row["key"] = keyText - } - default: - columnName, ok := canonicalColumn(columns, key) - if !ok { - continue - } - row[columnName] = value - } - } - rows = append(rows, row) - _, hasExplicitID := rowObj["id"] - explicitID = append(explicitID, hasExplicitID) - if hasExplicitID { - usedIDs[rowID] = struct{}{} - } - } - - nextID := nextAvailableID(usedIDs) - lockModified := false - for index, row := range rows { - key, hasKey := row["key"].(string) - if hasKey && key != "" { - if lockedID, ok := lockData[key]; ok { - row["id"] = lockedID - continue - } - if explicitID[index] { - lockData[key] = row["id"].(int) - } else { - row["id"] = nextID - lockData[key] = nextID - usedIDs[nextID] = struct{}{} - nextID = nextAvailableID(usedIDs) - } - lockModified = true - continue - } - if !explicitID[index] { - row["id"] = index - } - } - if lockModified { - _ = lockModified - } - - return nativeCollectedDataset{ - Dataset: nativeDataset{ - Name: dataset.Name, - OutputName: "masterfeats.2da", - Columns: columns, - Spec: dataset.Spec, - }, - Columns: columns, - Rows: rows, - LockData: lockData, - }, nil -} - -func rewriteImportedIDRefs(value any, ids map[string]int) map[string]any { - row, ok := rewriteImportedIDRefsValue(value, ids).(map[string]any) - if !ok { - return map[string]any{} - } - return row -} - -func rewriteImportedIDRefsValue(value any, ids map[string]int) any { - switch typed := value.(type) { - case map[string]any: - if len(typed) == 1 { - if rawID, ok := typed["id"]; ok { - if key, ok := rawID.(string); ok { - if resolved, ok := ids[key]; ok { - return strconv.Itoa(resolved) - } - } - } - } - out := make(map[string]any, len(typed)) - for key, child := range typed { - out[key] = rewriteImportedIDRefsValue(child, ids) - } - return out - case []any: - out := make([]any, 0, len(typed)) - for _, child := range typed { - out = append(out, rewriteImportedIDRefsValue(child, ids)) - } - return out - default: - return value - } -} diff --git a/internal/topdata/migrate.go b/internal/topdata/migrate.go deleted file mode 100644 index 2f61a08..0000000 --- a/internal/topdata/migrate.go +++ /dev/null @@ -1,415 +0,0 @@ -package topdata - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - - "git.westgate.pw/ShadowsOverWestgate/sow-tools/internal/project" -) - -type NormalizeResult struct { - StatePath string - ScannedFiles int - UpdatedFiles int - InlineValues int - RemainingFiles int - RemainingLegacyRefs int -} - -func NormalizeProject(p *project.Project) (NormalizeResult, error) { - if !p.HasTopData() { - return NormalizeResult{}, fmt.Errorf("topdata is not configured for this project") - } - - sourceDir := p.TopDataSourceDir() - dataDir := filepath.Join(sourceDir, "data") - migrationRoot := resolveMigrationInputRoot(sourceDir, p.TopDataReferenceBuilderDir()) - legacyTLK, err := loadLegacyTLK(filepath.Join(sourceDir, "tlk")) - if err != nil { - return NormalizeResult{}, err - } - if migrationRoot != "" { - migrationTLK, err := loadLegacyTLK(filepath.Join(migrationRoot, "tlk")) - if err != nil { - return NormalizeResult{}, err - } - legacyTLK = mergeLegacyTLK(migrationTLK, legacyTLK) - } - baseDialogImported, err := importLegacyBaseDialog(migrationRoot, sourceDir) - if err != nil { - return NormalizeResult{}, err - } - - itempropsImported, err := importLegacyItempropsRegistry(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - masterfeatsImported, err := importLegacyMasterfeats(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - featImported, err := importLegacyFeat(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - creaturespeedImported, err := importLegacyCreaturespeed(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - armorImported, err := importLegacyArmor(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - baseitemsImported, err := importLegacyBaseitems(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - damagetypesImported, err := importLegacyDamagetypes(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - skyboxesImported, err := importLegacySkyboxes(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - vfxPersistentImported, err := importLegacyVFXPersistent(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - loadscreensImported, err := importLegacyLoadscreens(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - progfxImported, err := importLegacyProgfx(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - genericdoorsImported, err := importLegacyGenericdoors(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - cloakmodelImported, err := importLegacyCloakmodel(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - repadjustImported, err := importLegacyRepadjust(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - wingmodelImported, err := importLegacyWingmodel(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - tailmodelImported, err := importLegacyTailmodel(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - doortypesImported, err := importLegacyDoortypes(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - visualeffectsImported, err := importLegacyVisualeffects(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - appearanceImported, err := importLegacyAppearance(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - placeablesImported, err := importLegacyPlaceables(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - rulesetImported, err := importLegacyRuleset(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - skillsImported, err := importLegacySkills(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - spellsImported, err := importLegacySpells(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - racialtypesImported, err := importLegacyRacialtypes(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - classesImported, err := importLegacyClasses(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - portraitsImported, err := importLegacyPortraits(migrationRoot, dataDir, legacyTLK) - if err != nil { - return NormalizeResult{}, err - } - - paths, err := collectDataJSONPaths(dataDir) - if err != nil { - return NormalizeResult{}, err - } - - result := NormalizeResult{ - StatePath: filepath.Join(sourceDir, tlkStateFile), - ScannedFiles: len(paths), - UpdatedFiles: baseDialogImported + itempropsImported + masterfeatsImported + featImported + creaturespeedImported + armorImported + baseitemsImported + damagetypesImported + skyboxesImported + vfxPersistentImported + loadscreensImported + progfxImported + genericdoorsImported + cloakmodelImported + repadjustImported + wingmodelImported + tailmodelImported + doortypesImported + visualeffectsImported + appearanceImported + placeablesImported + rulesetImported + skillsImported + spellsImported + racialtypesImported + classesImported + portraitsImported, - } - state, err := loadTLKState(result.StatePath) - if err != nil { - return result, err - } - if state.Entries == nil { - state.Entries = map[string]tlkStateMapping{} - } - if legacyTLK != nil && state.Language == "" { - state.Language = legacyTLK.Language - } - if legacyTLK != nil { - for key, id := range legacyTLK.Lock { - if _, ok := state.Entries[key]; !ok { - state.Entries[key] = tlkStateMapping{ID: id} - } - } - } - - if legacyTLK != nil { - for _, path := range paths { - updated, count, err := inlineLegacyTLKFile(path, legacyTLK) - if err != nil { - return result, err - } - if updated { - result.UpdatedFiles++ - result.InlineValues += count - } - } - } - - if err := saveTLKState(result.StatePath, state); err != nil { - return result, err - } - if err := removeLegacyTLKAuthoredInput(filepath.Join(sourceDir, "tlk")); err != nil { - return result, err - } - remainingFiles, remainingRefs, err := countLegacyTLKRefs(paths) - if err != nil { - return result, err - } - result.RemainingFiles = remainingFiles - result.RemainingLegacyRefs = remainingRefs - return result, nil -} - -func mergeLegacyTLK(base, override *legacyTLKData) *legacyTLKData { - if base == nil && override == nil { - return nil - } - merged := &legacyTLKData{ - Language: "en", - Entries: map[string]tlkEntryData{}, - Lock: map[string]int{}, - } - if base != nil { - if base.Language != "" { - merged.Language = base.Language - } - for key, entry := range base.Entries { - merged.Entries[key] = entry - } - for key, id := range base.Lock { - merged.Lock[key] = id - } - } - if override != nil { - if override.Language != "" { - merged.Language = override.Language - } - for key, entry := range override.Entries { - merged.Entries[key] = entry - } - for key, id := range override.Lock { - merged.Lock[key] = id - } - } - return merged -} - -func removeLegacyTLKAuthoredInput(tlkDir string) error { - modulesDir := filepath.Join(tlkDir, "modules") - if err := os.RemoveAll(modulesDir); err != nil { - return fmt.Errorf("remove %s: %w", modulesDir, err) - } - lockPath := filepath.Join(tlkDir, "lock.json") - if err := os.Remove(lockPath); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("remove %s: %w", lockPath, err) - } - gitkeepPath := filepath.Join(tlkDir, ".gitkeep") - if err := os.Remove(gitkeepPath); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("remove %s: %w", gitkeepPath, err) - } - if err := os.Remove(tlkDir); err != nil && !os.IsNotExist(err) { - if !strings.Contains(err.Error(), "directory not empty") { - return fmt.Errorf("remove %s: %w", tlkDir, err) - } - } - return nil -} - -func resolveMigrationInputRoot(sourceDir, referenceBuilderDir string) string { - if strings.TrimSpace(referenceBuilderDir) != "" { - return referenceBuilderDir - } - snapshotRoot := filepath.Join(sourceDir, "migration_snapshot") - if info, err := os.Stat(snapshotRoot); err == nil && info.IsDir() { - return snapshotRoot - } - return "" -} - -func inlineLegacyTLKFile(path string, legacy *legacyTLKData) (bool, int, error) { - obj, err := loadJSONObject(path) - if err != nil { - return false, 0, err - } - updated, count, err := inlineLegacyTLKValue(obj, legacy) - if err != nil { - return false, 0, err - } - if !updated { - return false, 0, nil - } - raw, err := json.MarshalIndent(obj, "", " ") - if err != nil { - return false, 0, err - } - raw = append(raw, '\n') - if err := os.WriteFile(path, raw, 0o644); err != nil { - return false, 0, err - } - return true, count, nil -} - -func inlineLegacyTLKValue(value any, legacy *legacyTLKData) (bool, int, error) { - switch typed := value.(type) { - case map[string]any: - if rawTLK, ok := typed["tlk"]; ok { - key, ok := rawTLK.(string) - if ok { - entry, ok := legacy.Entries[key] - if !ok { - return false, 0, nil - } - payload := map[string]any{ - "key": key, - "text": entry.Text, - } - if entry.SoundResRef != "" { - payload["sound_resref"] = entry.SoundResRef - } - if entry.SoundLength != 0 { - payload["sound_length"] = entry.SoundLength - } - typed["tlk"] = payload - return true, 1, nil - } - } - - updated := false - total := 0 - for key, child := range typed { - childUpdated, childCount, err := inlineLegacyTLKValue(child, legacy) - if err != nil { - return false, 0, err - } - if childUpdated { - typed[key] = child - updated = true - total += childCount - } - } - return updated, total, nil - case []any: - updated := false - total := 0 - for index, child := range typed { - childUpdated, childCount, err := inlineLegacyTLKValue(child, legacy) - if err != nil { - return false, 0, err - } - if childUpdated { - typed[index] = child - updated = true - total += childCount - } - } - return updated, total, nil - default: - return false, 0, nil - } -} - -func collectDataJSONPaths(dataDir string) ([]string, error) { - paths := make([]string, 0) - err := filepath.WalkDir(dataDir, func(path string, d os.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - return nil - } - if strings.HasSuffix(strings.ToLower(d.Name()), ".json") && d.Name() != "lock.json" { - paths = append(paths, path) - } - return nil - }) - if err != nil { - return nil, err - } - return paths, nil -} - -func countLegacyTLKRefs(paths []string) (int, int, error) { - files := 0 - refs := 0 - for _, path := range paths { - obj, err := loadJSONObject(path) - if err != nil { - return 0, 0, err - } - count := countLegacyTLKRefsInValue(obj) - if count == 0 { - continue - } - files++ - refs += count - } - return files, refs, nil -} - -func countLegacyTLKRefsInValue(value any) int { - switch typed := value.(type) { - case map[string]any: - total := 0 - if rawTLK, ok := typed["tlk"]; ok { - if _, ok := rawTLK.(string); ok { - total++ - } - } - for _, child := range typed { - total += countLegacyTLKRefsInValue(child) - } - return total - case []any: - total := 0 - for _, child := range typed { - total += countLegacyTLKRefsInValue(child) - } - return total - default: - return 0 - } -} diff --git a/internal/topdata/native.go b/internal/topdata/native.go index c29a7bb..8ec519d 100644 --- a/internal/topdata/native.go +++ b/internal/topdata/native.go @@ -6642,81 +6642,6 @@ func marshalOrderedLockfile(keys []string, lockData map[string]int, formatting j return buffer.Bytes(), nil } -func loadLegacyTLK(root string) (*legacyTLKData, error) { - info, err := os.Stat(root) - if err != nil { - if os.IsNotExist(err) { - return nil, nil - } - return nil, err - } - if !info.IsDir() { - return nil, fmt.Errorf("legacy tlk root must be a directory: %s", root) - } - - result := &legacyTLKData{ - Language: "en", - Entries: map[string]tlkEntryData{}, - Lock: map[string]int{}, - } - - lockPath := filepath.Join(root, "lock.json") - lockData, err := loadLockfile(lockPath) - if err != nil && !os.IsNotExist(err) { - return nil, err - } - for key, id := range lockData { - result.Lock[key] = id - } - - basePath := filepath.Join(root, "base.json") - if _, err := os.Stat(basePath); err == nil { - base, err := loadJSONObject(basePath) - if err != nil { - return nil, err - } - if language, ok := base["language"].(string); ok && language != "" { - result.Language = language - } - if entries, ok := base["entries"].(map[string]any); ok { - normalized, err := normalizeLegacyTLKEntries(entries) - if err != nil { - return nil, err - } - for key, entry := range normalized { - result.Entries[key] = entry - } - } - } - - modulesDir := filepath.Join(root, "modules") - modulePaths, err := collectModulePaths(modulesDir) - if err != nil { - if os.IsNotExist(err) { - return result, nil - } - return nil, err - } - for _, path := range modulePaths { - obj, err := loadJSONObject(path) - if err != nil { - return nil, err - } - entries, ok := obj["entries"].(map[string]any) - if !ok { - continue - } - normalized, err := normalizeLegacyTLKEntries(entries) - if err != nil { - return nil, fmt.Errorf("%s: %w", path, err) - } - for key, entry := range normalized { - result.Entries[key] = entry - } - } - return result, nil -} - func normalizeLegacyTLKEntries(entries map[string]any) (map[string]tlkEntryData, error) { normalized := map[string]tlkEntryData{} for _, key := range sortedKeys(entries) { diff --git a/internal/topdata/placeables_migrate.go b/internal/topdata/placeables_migrate.go deleted file mode 100644 index 2535307..0000000 --- a/internal/topdata/placeables_migrate.go +++ /dev/null @@ -1,6 +0,0 @@ -package topdata - -func importLegacyPlaceables(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - _ = legacyTLK - return importLegacyDatasetMirror(referenceBuilderDir, dataDir, "placeables", "placeables.2da", nil) -} diff --git a/internal/topdata/portraits_migrate.go b/internal/topdata/portraits_migrate.go deleted file mode 100644 index d3f48dc..0000000 --- a/internal/topdata/portraits_migrate.go +++ /dev/null @@ -1,8 +0,0 @@ -package topdata - -func importLegacyPortraits(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - if legacyTLK == nil { - return 0, nil - } - return importLegacyDatasetMirror(referenceBuilderDir, dataDir, "portraits", "portraits.2da", nil) -} diff --git a/internal/topdata/progfx_migrate.go b/internal/topdata/progfx_migrate.go deleted file mode 100644 index 17ba8ff..0000000 --- a/internal/topdata/progfx_migrate.go +++ /dev/null @@ -1,6 +0,0 @@ -package topdata - -func importLegacyProgfx(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - _ = legacyTLK - return importLegacyDatasetMirror(referenceBuilderDir, dataDir, "progfx", "progfx.2da", nil) -} diff --git a/internal/topdata/racialtypes_migrate.go b/internal/topdata/racialtypes_migrate.go deleted file mode 100644 index a012906..0000000 --- a/internal/topdata/racialtypes_migrate.go +++ /dev/null @@ -1,282 +0,0 @@ -package topdata - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "slices" - "strings" -) - -type legacyRacialtypesFeatTable struct { - Output string - Rows []map[string]any -} - -func importLegacyRacialtypes(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - if strings.TrimSpace(referenceBuilderDir) == "" { - return 0, nil - } - - legacyCoreDir := filepath.Join(referenceBuilderDir, "data", "racialtypes", "core") - legacyFeatsDir := filepath.Join(referenceBuilderDir, "data", "racialtypes", "feats") - if _, err := os.Stat(legacyCoreDir); err != nil { - if os.IsNotExist(err) { - return 0, nil - } - return 0, err - } - if _, err := os.Stat(legacyFeatsDir); err != nil { - if os.IsNotExist(err) { - return 0, nil - } - return 0, err - } - - targetRoot := filepath.Join(dataDir, "racialtypes", "registry") - targetBasePath := filepath.Join(targetRoot, "base.json") - targetLockPath := filepath.Join(targetRoot, "lock.json") - targetRacesDir := filepath.Join(targetRoot, "races") - if fileExists(targetBasePath) && fileExists(targetLockPath) { - entries, err := os.ReadDir(targetRacesDir) - if err == nil { - raceFiles := 0 - for _, entry := range entries { - if !entry.IsDir() && strings.HasSuffix(strings.ToLower(entry.Name()), ".json") { - raceFiles++ - } - } - if raceFiles == 16 { - return 0, nil - } - } - } - - baseColumns, baseRows, lockData, raceRows, err := collectLegacyRacialtypesRegistry(legacyCoreDir, legacyFeatsDir, legacyTLK) - if err != nil { - return 0, err - } - - if err := removePathIfExists(filepath.Join(dataDir, "racialtypes", "core")); err != nil { - return 0, err - } - if err := removePathIfExists(filepath.Join(dataDir, "racialtypes", "feats")); err != nil { - return 0, err - } - if err := removePathIfExists(filepath.Join(targetRoot, "base_rows.json")); err != nil { - return 0, err - } - if err := removePathIfExists(targetRacesDir); err != nil { - return 0, err - } - if err := os.MkdirAll(targetRacesDir, 0o755); err != nil { - return 0, err - } - - writes := []struct { - path string - obj any - }{ - { - path: targetBasePath, - obj: map[string]any{ - "columns": stringSliceToAny(baseColumns), - "rows": rowsToAny(baseRows), - }, - }, - { - path: targetLockPath, - obj: anyMapInt(lockData), - }, - } - - raceKeys := make([]string, 0, len(raceRows)) - for key := range raceRows { - raceKeys = append(raceKeys, key) - } - slices.Sort(raceKeys) - for _, key := range raceKeys { - fileName := strings.TrimPrefix(key, "racialtypes:") + ".json" - writes = append(writes, struct { - path string - obj any - }{ - path: filepath.Join(targetRacesDir, fileName), - obj: raceRows[key], - }) - } - - for _, write := range writes { - raw, err := json.MarshalIndent(write.obj, "", " ") - if err != nil { - return 0, err - } - raw = append(raw, '\n') - if err := os.WriteFile(write.path, raw, 0o644); err != nil { - return 0, err - } - } - return len(writes), nil -} - -func collectLegacyRacialtypesRegistry(coreDir, featsDir string, legacyTLK *legacyTLKData) ([]string, []map[string]any, map[string]int, map[string]map[string]any, error) { - coreCollected, err := collectBaseDataset(nativeDataset{ - Name: "racialtypes", - BasePath: filepath.Join(coreDir, "base.json"), - LockPath: filepath.Join(coreDir, "lock.json"), - ModulesDir: filepath.Join(coreDir, "modules"), - Spec: specForDataset("racialtypes"), - }) - if err != nil { - return nil, nil, nil, nil, err - } - - featTables, err := loadLegacyRacialtypesFeatTables(featsDir) - if err != nil { - return nil, nil, nil, nil, err - } - - baseRows := make([]map[string]any, 0) - raceRows := map[string]map[string]any{} - lockData := map[string]int{} - for key, id := range coreCollected.LockData { - if strings.HasPrefix(key, "racialtypes:") { - lockData[key] = id - } - } - - for _, collectedRow := range coreCollected.Rows { - row, ok := deepCopyValue(collectedRow).(map[string]any) - if !ok { - continue - } - if legacyTLK != nil { - if _, _, err := inlineLegacyTLKValue(row, legacyTLK); err != nil { - return nil, nil, nil, nil, err - } - } - key, _ := row["key"].(string) - if !strings.HasPrefix(key, "racialtypes:") { - assignRacialtypesBaseRowKey(row) - if key, _ := row["key"].(string); key != "" { - rowID, err := asInt(row["id"]) - if err != nil { - return nil, nil, nil, nil, err - } - lockData[key] = rowID - } - baseRows = append(baseRows, row) - continue - } - - delete(row, "id") - delete(row, "key") - - raceFile := map[string]any{ - "key": key, - "core": row, - } - if tableKey := extractRacialtypesFeatTableKey(row["FeatsTable"]); tableKey != "" { - table, ok := featTables[tableKey] - if !ok { - return nil, nil, nil, nil, fmt.Errorf("%s: unknown feat table %s", key, tableKey) - } - delete(row, "FeatsTable") - raceFile["feat_output"] = table.Output - raceFile["feats"] = rowsToAny(table.Rows) - } - raceRows[key] = raceFile - } - - slices.SortFunc(baseRows, func(a, b map[string]any) int { - left, _ := asInt(a["id"]) - right, _ := asInt(b["id"]) - return left - right - }) - return coreCollected.Columns, baseRows, lockData, raceRows, nil -} - -func loadLegacyRacialtypesFeatTables(featsDir string) (map[string]legacyRacialtypesFeatTable, error) { - entries, err := os.ReadDir(featsDir) - if err != nil { - return nil, err - } - - tables := map[string]legacyRacialtypesFeatTable{} - for _, entry := range entries { - if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" { - continue - } - obj, err := loadJSONObject(filepath.Join(featsDir, entry.Name())) - if err != nil { - return nil, err - } - tableKey, _ := obj["key"].(string) - outputName, _ := obj["output"].(string) - rawRows, ok := obj["rows"].([]any) - if tableKey == "" || outputName == "" || !ok { - return nil, fmt.Errorf("%s: racialtypes feat table must define key, output, and rows", entry.Name()) - } - rows := make([]map[string]any, 0, len(rawRows)) - for index, raw := range rawRows { - row, ok := raw.(map[string]any) - if !ok { - return nil, fmt.Errorf("%s: row %d must be an object", entry.Name(), index) - } - rows = append(rows, row) - } - tables[tableKey] = legacyRacialtypesFeatTable{ - Output: outputName, - Rows: rows, - } - } - return tables, nil -} - -func extractRacialtypesFeatTableKey(value any) string { - obj, ok := value.(map[string]any) - if !ok { - return "" - } - tableKey, _ := obj["table"].(string) - return tableKey -} - -func assignRacialtypesBaseRowKey(row map[string]any) { - name, _ := row["Name"].(string) - if strings.TrimSpace(name) == "" || name == nullValue { - delete(row, "key") - return - } - label, _ := row["Label"].(string) - keySuffix := normalizeRacialtypesKeySuffix(label) - if keySuffix == "" { - delete(row, "key") - return - } - row["key"] = "racialtypes:" + keySuffix -} - -func normalizeRacialtypesKeySuffix(label string) string { - normalized := strings.ToLower(strings.TrimSpace(label)) - if normalized == "" { - return "" - } - var b strings.Builder - lastUnderscore := false - for _, ch := range normalized { - isAlphaNum := (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') - if isAlphaNum { - b.WriteRune(ch) - lastUnderscore = false - continue - } - if lastUnderscore { - continue - } - b.WriteByte('_') - lastUnderscore = true - } - return strings.Trim(b.String(), "_") -} diff --git a/internal/topdata/repadjust_migrate.go b/internal/topdata/repadjust_migrate.go deleted file mode 100644 index 0a3bcc1..0000000 --- a/internal/topdata/repadjust_migrate.go +++ /dev/null @@ -1,63 +0,0 @@ -package topdata - -import ( - "encoding/json" - "os" - "path/filepath" -) - -func importLegacyRepadjust(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - _ = legacyTLK - - legacyDir := filepath.Join(referenceBuilderDir, "data", "repadjust") - if _, err := os.Stat(legacyDir); err != nil { - if os.IsNotExist(err) { - return 0, nil - } - return 0, err - } - - targetDir := filepath.Join(dataDir, "repadjust") - targetBasePath := filepath.Join(targetDir, "base.json") - targetLockPath := filepath.Join(targetDir, "lock.json") - if fileExists(targetBasePath) && fileExists(targetLockPath) { - obj, err := loadJSONObject(targetBasePath) - if err == nil && countLegacyTLKRefsInValue(obj) == 0 { - return 0, nil - } - } - - baseObj, err := loadJSONObject(filepath.Join(legacyDir, "base.json")) - if err != nil { - return 0, err - } - baseObj["output"] = "repadjust.2da" - - lockObj, err := loadJSONObject(filepath.Join(legacyDir, "lock.json")) - if err != nil { - return 0, err - } - - if err := os.MkdirAll(targetDir, 0o755); err != nil { - return 0, err - } - - writes := []struct { - path string - obj map[string]any - }{ - {path: targetBasePath, obj: baseObj}, - {path: targetLockPath, obj: lockObj}, - } - for _, write := range writes { - raw, err := json.MarshalIndent(write.obj, "", " ") - if err != nil { - return 0, err - } - raw = append(raw, '\n') - if err := os.WriteFile(write.path, raw, 0o644); err != nil { - return 0, err - } - } - return len(writes), nil -} diff --git a/internal/topdata/ruleset_migrate.go b/internal/topdata/ruleset_migrate.go deleted file mode 100644 index ffb5d0c..0000000 --- a/internal/topdata/ruleset_migrate.go +++ /dev/null @@ -1,171 +0,0 @@ -package topdata - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "sort" -) - -func importLegacyRuleset(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - _ = legacyTLK - - legacyDir := filepath.Join(referenceBuilderDir, "data", "ruleset") - if _, err := os.Stat(legacyDir); err != nil { - if os.IsNotExist(err) { - return 0, nil - } - return 0, err - } - - targetDir := filepath.Join(dataDir, "ruleset") - targetPath := filepath.Join(targetDir, "ruleset.json") - if _, err := os.Stat(targetPath); err == nil { - obj, err := loadJSONObject(targetPath) - if err == nil && countLegacyTLKRefsInValue(obj) == 0 { - return 0, nil - } - } - - baseObj, err := loadJSONObject(filepath.Join(legacyDir, "base.json")) - if err != nil { - return 0, err - } - rows, err := mergeRulesetRows(baseObj, filepath.Join(legacyDir, "modules")) - if err != nil { - return 0, err - } - - if err := os.MkdirAll(targetDir, 0o755); err != nil { - return 0, err - } - if err := removePathIfExists(filepath.Join(targetDir, "base.json")); err != nil { - return 0, err - } - if err := removePathIfExists(filepath.Join(targetDir, "lock.json")); err != nil { - return 0, err - } - if err := removePathIfExists(filepath.Join(targetDir, "modules")); err != nil { - return 0, err - } - - out := map[string]any{ - "output": "ruleset.2da", - "columns": baseObj["columns"], - "rows": rows, - } - raw, err := json.MarshalIndent(out, "", " ") - if err != nil { - return 0, err - } - raw = append(raw, '\n') - if err := os.WriteFile(targetPath, raw, 0o644); err != nil { - return 0, err - } - return 1, nil -} - -func mergeRulesetRows(baseObj map[string]any, modulesDir string) ([]any, error) { - rawRows, ok := baseObj["rows"].([]any) - if !ok { - return nil, nil - } - - rows := make([]map[string]any, 0, len(rawRows)) - byLabel := map[string]map[string]any{} - for _, raw := range rawRows { - row, ok := deepCopyValue(raw).(map[string]any) - if !ok { - continue - } - delete(row, "key") - if rawID, ok := row["id"]; ok { - id, err := asInt(rawID) - if err != nil { - return nil, err - } - row["id"] = id - } - if label, ok := row["Label"].(string); ok && label != "" && label != "****" { - if _, exists := byLabel[label]; exists { - return nil, fmt.Errorf("duplicate ruleset label %q", label) - } - byLabel[label] = row - } - rows = append(rows, row) - } - - modulePaths, err := collectModulePaths(modulesDir) - if err != nil { - return nil, err - } - for _, path := range modulePaths { - obj, err := loadJSONObject(path) - if err != nil { - return nil, err - } - overrideList, ok := obj["overrides"].([]any) - if !ok { - continue - } - for _, raw := range overrideList { - override, ok := raw.(map[string]any) - if !ok { - continue - } - matchObj, ok := override["match"].(map[string]any) - if !ok { - return nil, fmt.Errorf("%s: ruleset override is missing match object", path) - } - rawLabel, ok := matchObj["Label"] - if !ok { - return nil, fmt.Errorf("%s: ruleset override match is missing Label", path) - } - labels, err := normalizeRulesetMatchLabels(rawLabel) - if err != nil { - return nil, fmt.Errorf("%s: %w", path, err) - } - for _, label := range labels { - row, ok := byLabel[label] - if !ok { - return nil, fmt.Errorf("%s: ruleset override target label %q was not found", path, label) - } - for key, value := range override { - if key == "id" || key == "key" || key == "_tlk" || isMetadataField(key) || key == "match" { - continue - } - row[key] = deepCopyValue(value) - } - } - } - } - - sort.Slice(rows, func(i, j int) bool { - return rows[i]["id"].(int) < rows[j]["id"].(int) - }) - out := make([]any, 0, len(rows)) - for _, row := range rows { - out = append(out, row) - } - return out, nil -} - -func normalizeRulesetMatchLabels(raw any) ([]string, error) { - switch typed := raw.(type) { - case string: - return []string{typed}, nil - case []any: - out := make([]string, 0, len(typed)) - for _, item := range typed { - label, ok := item.(string) - if !ok { - return nil, fmt.Errorf("ruleset match.Label entries must be strings") - } - out = append(out, label) - } - return out, nil - default: - return nil, fmt.Errorf("ruleset match.Label must be a string or string array") - } -} diff --git a/internal/topdata/skills_migrate.go b/internal/topdata/skills_migrate.go deleted file mode 100644 index 01f168a..0000000 --- a/internal/topdata/skills_migrate.go +++ /dev/null @@ -1,140 +0,0 @@ -package topdata - -import ( - "encoding/json" - "os" - "path/filepath" -) - -func importLegacySkills(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - _ = legacyTLK - - legacyDir := filepath.Join(referenceBuilderDir, "data", "skills") - if _, err := os.Stat(legacyDir); err != nil { - if os.IsNotExist(err) { - return 0, nil - } - return 0, err - } - - targetDir := filepath.Join(dataDir, "skills") - targetBasePath := filepath.Join(targetDir, "base.json") - targetLockPath := filepath.Join(targetDir, "lock.json") - targetModulesDir := filepath.Join(targetDir, "modules") - moduleNames := []string{ - "add_athletics.json", - "add_disguise.json", - "add_linguistics.json", - "add_perception.json", - "add_scriptcraft.json", - "add_sensemotive.json", - "add_stealth.json", - "add_survival.json", - "add_userope.json", - filepath.Join("craft", "add_craftalchemy.json"), - filepath.Join("craft", "add_craftcooking.json"), - filepath.Join("craft", "add_craftjewelry.json"), - filepath.Join("craft", "add_craftleatherworking.json"), - filepath.Join("craft", "add_craftstonework.json"), - filepath.Join("craft", "add_crafttextiles.json"), - filepath.Join("craft", "ovr_craftarmorsmithing.json"), - filepath.Join("craft", "ovr_crafttinkering.json"), - filepath.Join("craft", "ovr_craftweaponsmithing.json"), - filepath.Join("craft", "ovr_craftwoodworking.json"), - filepath.Join("knowledge", "add_knowledgearcana.json"), - filepath.Join("knowledge", "add_knowledgearchitecture.json"), - filepath.Join("knowledge", "add_knowledgedungeoneering.json"), - filepath.Join("knowledge", "add_knowledgegeography.json"), - filepath.Join("knowledge", "add_knowledgehistory.json"), - filepath.Join("knowledge", "add_knowledgelocal.json"), - filepath.Join("knowledge", "add_knowledgenature.json"), - filepath.Join("knowledge", "add_knowledgenobility.json"), - filepath.Join("knowledge", "add_knowledgeplanar.json"), - filepath.Join("knowledge", "add_knowledgereligion.json"), - "ovr_acrobatics.json", - "ovr_animalhandling.json", - "ovr_appraise.json", - "ovr_concentration.json", - "ovr_disabledevice.json", - "ovr_heal.json", - "ovr_hiddenskills.json", - "ovr_influence.json", - "ovr_openlock.json", - "ovr_parry.json", - "ovr_searchtowis.json", - "ovr_sleightofhand.json", - "ovr_taunttointimidate.json", - "rmv_removedskills.json", - } - targetModulePaths := make([]string, 0, len(moduleNames)) - for _, name := range moduleNames { - targetModulePaths = append(targetModulePaths, filepath.Join(targetModulesDir, name)) - } - if fileExists(targetBasePath) && fileExists(targetLockPath) { - allModulesPresent := true - for _, path := range targetModulePaths { - if !fileExists(path) { - allModulesPresent = false - break - } - } - if allModulesPresent { - return 0, nil - } - } - - baseObj, err := loadJSONObject(filepath.Join(legacyDir, "base.json")) - if err != nil { - return 0, err - } - baseObj["output"] = "skills.2da" - canonicalizeWikiMetadataDocument(baseObj) - - lockObj, err := loadJSONObject(filepath.Join(legacyDir, "lock.json")) - if err != nil { - return 0, err - } - - moduleObjs := make([]map[string]any, 0, len(moduleNames)) - for _, name := range moduleNames { - obj, err := loadJSONObject(filepath.Join(legacyDir, "modules", name)) - if err != nil { - return 0, err - } - canonicalizeWikiMetadataDocument(obj) - moduleObjs = append(moduleObjs, obj) - } - - if err := os.MkdirAll(targetModulesDir, 0o755); err != nil { - return 0, err - } - - writes := []struct { - path string - obj map[string]any - }{ - {path: targetBasePath, obj: baseObj}, - {path: targetLockPath, obj: lockObj}, - } - for i, path := range targetModulePaths { - if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { - return 0, err - } - writes = append(writes, struct { - path string - obj map[string]any - }{path: path, obj: moduleObjs[i]}) - } - - for _, write := range writes { - raw, err := json.MarshalIndent(write.obj, "", " ") - if err != nil { - return 0, err - } - raw = append(raw, '\n') - if err := os.WriteFile(write.path, raw, 0o644); err != nil { - return 0, err - } - } - return len(writes), nil -} diff --git a/internal/topdata/skyboxes_migrate.go b/internal/topdata/skyboxes_migrate.go deleted file mode 100644 index 6b09eb3..0000000 --- a/internal/topdata/skyboxes_migrate.go +++ /dev/null @@ -1,6 +0,0 @@ -package topdata - -func importLegacySkyboxes(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - _ = legacyTLK - return importLegacyDatasetMirror(referenceBuilderDir, dataDir, "skyboxes", "skyboxes.2da", nil) -} diff --git a/internal/topdata/spells_migrate.go b/internal/topdata/spells_migrate.go deleted file mode 100644 index 1dca1f2..0000000 --- a/internal/topdata/spells_migrate.go +++ /dev/null @@ -1,103 +0,0 @@ -package topdata - -import ( - "encoding/json" - "os" - "path/filepath" -) - -func importLegacySpells(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - _ = legacyTLK - - legacyDir := filepath.Join(referenceBuilderDir, "data", "spells") - if _, err := os.Stat(legacyDir); err != nil { - if os.IsNotExist(err) { - return 0, nil - } - return 0, err - } - - targetDir := filepath.Join(dataDir, "spells") - targetBasePath := filepath.Join(targetDir, "base.json") - targetLockPath := filepath.Join(targetDir, "lock.json") - targetModulesDir := filepath.Join(targetDir, "modules") - moduleNames := []string{ - "specialattacks.json", - "ovr_fixduplicates.json", - filepath.Join("bardicmusic", "fascinate.json"), - filepath.Join("bardicmusic", "inspirecompetence.json"), - filepath.Join("bardicmusic", "inspirecourage.json"), - filepath.Join("bardicmusic", "inspiregreatness.json"), - filepath.Join("bardicmusic", "inspireheroics.json"), - filepath.Join("bardicmusic", "songoffreedom.json"), - } - targetModulePaths := make([]string, 0, len(moduleNames)) - for _, name := range moduleNames { - targetModulePaths = append(targetModulePaths, filepath.Join(targetModulesDir, name)) - } - if fileExists(targetBasePath) && fileExists(targetLockPath) { - allModulesPresent := true - for _, path := range targetModulePaths { - if !fileExists(path) { - allModulesPresent = false - break - } - } - if allModulesPresent { - return 0, nil - } - } - - baseObj, err := loadJSONObject(filepath.Join(legacyDir, "base.json")) - if err != nil { - return 0, err - } - baseObj["output"] = "spells.2da" - - lockObj, err := loadJSONObject(filepath.Join(legacyDir, "lock.json")) - if err != nil { - return 0, err - } - - moduleObjs := make([]map[string]any, 0, len(moduleNames)) - for _, name := range moduleNames { - obj, err := loadJSONObject(filepath.Join(legacyDir, "modules", name)) - if err != nil { - return 0, err - } - moduleObjs = append(moduleObjs, obj) - } - - if err := os.MkdirAll(targetModulesDir, 0o755); err != nil { - return 0, err - } - - writes := []struct { - path string - obj map[string]any - }{ - {path: targetBasePath, obj: baseObj}, - {path: targetLockPath, obj: lockObj}, - } - for i, path := range targetModulePaths { - if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { - return 0, err - } - writes = append(writes, struct { - path string - obj map[string]any - }{path: path, obj: moduleObjs[i]}) - } - - for _, write := range writes { - raw, err := json.MarshalIndent(write.obj, "", " ") - if err != nil { - return 0, err - } - raw = append(raw, '\n') - if err := os.WriteFile(write.path, raw, 0o644); err != nil { - return 0, err - } - } - return len(writes), nil -} diff --git a/internal/topdata/tailmodel_migrate.go b/internal/topdata/tailmodel_migrate.go deleted file mode 100644 index 4c44ee9..0000000 --- a/internal/topdata/tailmodel_migrate.go +++ /dev/null @@ -1,86 +0,0 @@ -package topdata - -import ( - "encoding/json" - "os" - "path/filepath" -) - -func importLegacyTailmodel(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - _ = legacyTLK - - legacyDir := filepath.Join(referenceBuilderDir, "data", "tailmodel") - if _, err := os.Stat(legacyDir); err != nil { - if os.IsNotExist(err) { - return 0, nil - } - return 0, err - } - - targetDir := filepath.Join(dataDir, "tailmodel") - targetBasePath := filepath.Join(targetDir, "base.json") - targetLockPath := filepath.Join(targetDir, "lock.json") - targetModulesDir := filepath.Join(targetDir, "modules") - targetModulePaths := []string{ - filepath.Join(targetModulesDir, "add.json"), - filepath.Join(targetModulesDir, "ovr_tailprefixes.json"), - } - if fileExists(targetBasePath) && fileExists(targetLockPath) { - allModulesPresent := true - for _, path := range targetModulePaths { - if !fileExists(path) { - allModulesPresent = false - break - } - } - if allModulesPresent { - return 0, nil - } - } - - baseObj, err := loadJSONObject(filepath.Join(legacyDir, "base.json")) - if err != nil { - return 0, err - } - baseObj["output"] = "tailmodel.2da" - - lockObj, err := loadJSONObject(filepath.Join(legacyDir, "lock.json")) - if err != nil { - return 0, err - } - - moduleNames := []string{"add.json", "ovr_tailprefixes.json"} - moduleObjs := make([]map[string]any, 0, len(moduleNames)) - for _, name := range moduleNames { - obj, err := loadJSONObject(filepath.Join(legacyDir, "modules", name)) - if err != nil { - return 0, err - } - moduleObjs = append(moduleObjs, obj) - } - - if err := os.MkdirAll(targetModulesDir, 0o755); err != nil { - return 0, err - } - - writes := []struct { - path string - obj map[string]any - }{ - {path: targetBasePath, obj: baseObj}, - {path: targetLockPath, obj: lockObj}, - {path: targetModulePaths[0], obj: moduleObjs[0]}, - {path: targetModulePaths[1], obj: moduleObjs[1]}, - } - for _, write := range writes { - raw, err := json.MarshalIndent(write.obj, "", " ") - if err != nil { - return 0, err - } - raw = append(raw, '\n') - if err := os.WriteFile(write.path, raw, 0o644); err != nil { - return 0, err - } - } - return len(writes), nil -} diff --git a/internal/topdata/tlk_native.go b/internal/topdata/tlk_native.go index 01da36f..a6daa67 100644 --- a/internal/topdata/tlk_native.go +++ b/internal/topdata/tlk_native.go @@ -727,3 +727,36 @@ func sortedKeys[M ~map[string]V, V any](input M) []string { func almostEqualFloat32(a, b float32) bool { return math.Abs(float64(a-b)) < 0.0001 } +func mergeLegacyTLK(base, override *legacyTLKData) *legacyTLKData { + if base == nil && override == nil { + return nil + } + merged := &legacyTLKData{ + Language: "en", + Entries: map[string]tlkEntryData{}, + Lock: map[string]int{}, + } + if base != nil { + if base.Language != "" { + merged.Language = base.Language + } + for key, entry := range base.Entries { + merged.Entries[key] = entry + } + for key, id := range base.Lock { + merged.Lock[key] = id + } + } + if override != nil { + if override.Language != "" { + merged.Language = override.Language + } + for key, entry := range override.Entries { + merged.Entries[key] = entry + } + for key, id := range override.Lock { + merged.Lock[key] = id + } + } + return merged +} diff --git a/internal/topdata/topdata_test.go b/internal/topdata/topdata_test.go index d77d2fa..fdb00a4 100644 --- a/internal/topdata/topdata_test.go +++ b/internal/topdata/topdata_test.go @@ -1532,157 +1532,6 @@ func TestResolveNativeDatasetPreservesScalarTableReferenceBehavior(t *testing.T) } } -func TestNormalizeProjectImportsLegacyClasses(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - - referenceRoot := filepath.Join(root, "reference") - mkdirAll(t, filepath.Join(referenceRoot, "data", "classes", "core", "modules")) - mkdirAll(t, filepath.Join(referenceRoot, "data", "classes", "feats")) - mkdirAll(t, filepath.Join(referenceRoot, "data", "classes", "skills")) - mkdirAll(t, filepath.Join(referenceRoot, "data", "classes", "savthr")) - mkdirAll(t, filepath.Join(referenceRoot, "data", "classes", "bfeat")) - mkdirAll(t, filepath.Join(referenceRoot, "tlk", "modules", "classes")) - - writeFile(t, filepath.Join(referenceRoot, "data", "classes", "core", "base.json"), `{ - "output": "classes.2da", - "columns": ["Label", "Description", "FeatsTable", "SavingThrowTable", "SkillsTable", "BonusFeatsTable", "PreReqTable"], - "rows": [ - { - "id": 4, - "key": "classes:fighter", - "Label": "Fighter", - "Description": "240", - "FeatsTable": "cls_feat_fight", - "SavingThrowTable": "cls_savthr_fort", - "SkillsTable": "cls_skill_fight", - "BonusFeatsTable": "cls_bfeat_fight", - "PreReqTable": "****" - }, - { - "id": 11, - "key": "classes:aberration", - "Label": "Aberration", - "Description": "241", - "FeatsTable": "CLS_FEAT_ABER", - "SavingThrowTable": "CLS_SAVTHR_WIZ", - "SkillsTable": "CLS_SKILL_FIGHT", - "BonusFeatsTable": "CLS_BFEAT_BARB", - "PreReqTable": "****" - } - ] -}`+"\n") - writeFile(t, filepath.Join(referenceRoot, "data", "classes", "core", "lock.json"), `{ - "classes:fighter": 4, - "classes:aberration": 11 -}`+"\n") - writeFile(t, filepath.Join(referenceRoot, "data", "classes", "core", "modules", "fighter.json"), `{ - "overrides": [ - { - "key": "classes:fighter", - "Description": { "tlk": "classes:fighter.description" } - } - ] -}`+"\n") - writeFile(t, filepath.Join(referenceRoot, "data", "classes", "feats", "fighter.json"), `{ - "key": "classes/feats:fighter", - "output": "cls_feat_fight.2da", - "columns": ["FeatIndex"], - "rows": [ - {"FeatIndex": {"id": "feat:literate"}} - ] -}`+"\n") - writeFile(t, filepath.Join(referenceRoot, "data", "classes", "skills", "fighter.json"), `{ - "key": "classes/skills:fighter", - "output": "cls_skill_fight.2da", - "columns": ["SkillIndex"], - "rows": [ - {"SkillIndex": {"id": "skills:spot"}} - ] -}`+"\n") - writeFile(t, filepath.Join(referenceRoot, "data", "classes", "savthr", "fortitude_high.json"), `{ - "key": "classes/saves:fortitude", - "output": "cls_savthr_fort.2da", - "columns": ["Level", "FortSave"], - "rows": [ - {"Level": "1", "FortSave": "2"} - ] -}`+"\n") - writeFile(t, filepath.Join(referenceRoot, "data", "classes", "bfeat", "fighter.json"), `{ - "key": "classes/bonusfeats:fighter", - "output": "cls_bfeat_fight.2da", - "columns": ["Bonus"], - "rows": [ - {"id": 1, "Bonus": "1"} - ] -}`+"\n") - writeFile(t, filepath.Join(referenceRoot, "tlk", "lock.json"), `{ - "classes:fighter.description": 1234 -}`+"\n") - writeFile(t, filepath.Join(referenceRoot, "tlk", "modules", "classes", "fighter.json"), `{ - "entries": { - "classes:fighter.description": { - "text": "Martial expert." - } - } -}`+"\n") - - p := testProject(root) - p.Config.TopData.ReferenceBuilder = "reference" - result, err := NormalizeProject(p) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles == 0 { - t.Fatal("expected classes import to write canonical files") - } - - coreObj := map[string]any{} - coreRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "classes", "core", "base.json")) - if err != nil { - t.Fatalf("read classes core: %v", err) - } - if err := json.Unmarshal(coreRaw, &coreObj); err != nil { - t.Fatalf("parse classes core: %v", err) - } - rows := coreObj["rows"].([]any) - row := rows[0].(map[string]any) - - description := row["Description"].(map[string]any)["tlk"].(map[string]any) - if description["key"] != "classes:fighter.description" || description["text"] != "Martial expert." { - t.Fatalf("unexpected inline TLK payload: %#v", description) - } - if table := row["FeatsTable"].(map[string]any)["table"]; table != "classes/feats:fighter" { - t.Fatalf("expected feats table ref rewrite, got %#v", row["FeatsTable"]) - } - if table := row["SavingThrowTable"].(map[string]any)["table"]; table != "classes/saves:fortitude" { - t.Fatalf("expected save table ref rewrite, got %#v", row["SavingThrowTable"]) - } - if table := row["SkillsTable"].(map[string]any)["table"]; table != "classes/skills:fighter" { - t.Fatalf("expected skills table ref rewrite, got %#v", row["SkillsTable"]) - } - if table := row["BonusFeatsTable"].(map[string]any)["table"]; table != "classes/bonusfeats:fighter" { - t.Fatalf("expected bonus feats table ref rewrite, got %#v", row["BonusFeatsTable"]) - } - monsterRow := rows[1].(map[string]any) - if got := monsterRow["SkillsTable"]; got != "CLS_SKILL_FIGHT" { - t.Fatalf("expected legacy uppercase monster table stem to remain literal, got %#v", got) - } - - state := tlkStateDocument{} - stateRaw, err := os.ReadFile(filepath.Join(root, "topdata", ".tlk_state.json")) - if err != nil { - t.Fatalf("read tlk state: %v", err) - } - if err := json.Unmarshal(stateRaw, &state); err != nil { - t.Fatalf("parse tlk state: %v", err) - } - if got := state.Entries["classes:fighter.description"].ID; got != 1234 { - t.Fatalf("expected preserved TLK state ID 1234, got %d", got) - } -} - func TestBuildNativeSupportsClassesInlineTLKFields(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "classes", "core")) @@ -2314,54 +2163,6 @@ func TestValidateProjectRejectsLegacyAuthoredTLKModules(t *testing.T) { } } -func TestNormalizeProjectRemovesLegacyAuthoredTLKModules(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data", "feat")) - mkdirAll(t, filepath.Join(root, "topdata", "tlk", "modules", "feat")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "topdata", "data", "feat", "base.json"), `{ - "output": "feat.2da", - "columns": ["LABEL", "FEAT", "DESCRIPTION"], - "rows": [ - { - "id": 0, - "key": "feat:test", - "LABEL": "FEAT_TEST", - "FEAT": {"tlk": "feat:test.name"}, - "DESCRIPTION": {"tlk": "feat:test.description"} - } - ] -}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "data", "feat", "lock.json"), `{"feat:test":0}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "tlk", "lock.json"), `{"feat:test.name":9,"feat:test.description":10}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "tlk", "modules", "feat", "test.json"), `{ - "entries": { - "feat:test.name": {"text": "Legacy Name"}, - "feat:test.description": {"text": "Legacy Description"} - } -}`+"\n") - mkdirAll(t, filepath.Join(root, "reference")) - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.RemainingLegacyRefs != 0 { - t.Fatalf("expected no remaining legacy refs, got %#v", result) - } - if _, err := os.Stat(filepath.Join(root, "topdata", "tlk", "modules")); !os.IsNotExist(err) { - t.Fatalf("expected legacy tlk/modules to be removed, got err=%v", err) - } - if _, err := os.Stat(filepath.Join(root, "topdata", "tlk", "lock.json")); !os.IsNotExist(err) { - t.Fatalf("expected legacy tlk/lock.json to be removed, got err=%v", err) - } - report := ValidateProject(testProject(root)) - if report.HasErrors() { - t.Fatalf("expected normalized project to validate cleanly, got:\n%s", diagnosticsText(report.Diagnostics)) - } -} - func TestValidateProjectAcceptsCanonicalMasterfeatsContract(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "masterfeats")) @@ -2884,106 +2685,6 @@ with open(os.path.join(tlk, "sow_tlk.tlk"), "wb") as f: } } -func TestNormalizeProjectInlinesLegacyTLKReferences(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data", "feat", "modules")) - mkdirAll(t, filepath.Join(root, "topdata", "tlk", "modules", "feat")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "topdata", "data", "feat", "base.json"), `{ - "output": "feat.2da", - "columns": ["LABEL", "FEAT", "DESCRIPTION"], - "rows": [ - {"id": 0, "key": "feat:test", "LABEL": "FeatTest", "FEAT": {"tlk": "feat:test.name"}, "DESCRIPTION": {"tlk": "feat:test.description"}} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "data", "feat", "lock.json"), `{"feat:test":0}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "tlk", "lock.json"), `{"feat:test.name":9,"feat:test.description":10}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "tlk", "modules", "feat", "test.json"), `{ - "entries": { - "feat:test": { - "name": {"text": "Legacy Name"}, - "description": {"text": "Legacy Description"} - } - } -}`+"\n") - mkdirAll(t, filepath.Join(root, "reference")) - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles == 0 || result.InlineValues != 2 { - t.Fatalf("unexpected normalize result: %#v", result) - } - - normalized, err := os.ReadFile(filepath.Join(root, "topdata", "data", "feat", "base.json")) - if err != nil { - t.Fatalf("read normalized file: %v", err) - } - text := string(normalized) - if !strings.Contains(text, `"key": "feat:test.name"`) || !strings.Contains(text, `"text": "Legacy Name"`) { - t.Fatalf("expected inlined TLK payloads, got:\n%s", text) - } - - stateRaw, err := os.ReadFile(filepath.Join(root, "topdata", tlkStateFile)) - if err != nil { - t.Fatalf("read state: %v", err) - } - var state tlkStateDocument - if err := json.Unmarshal(stateRaw, &state); err != nil { - t.Fatalf("parse state: %v", err) - } - if state.Entries["feat:test.name"].ID != 9 || state.Entries["feat:test.description"].ID != 10 { - t.Fatalf("expected state seeded from legacy TLK lock, got %#v", state.Entries) - } -} - -func TestNormalizeProjectHandlesPlainTablesAndReportsRemainingRefs(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data", "masterfeats")) - mkdirAll(t, filepath.Join(root, "topdata", "tlk", "modules", "feat")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "topdata", "data", "masterfeats", "base.json"), `{ - "output": "masterfeats.2da", - "columns": ["LABEL", "STRREF", "DESCRIPTION"], - "rows": [ - {"key": "masterfeats:test", "LABEL": "Test", "STRREF": {"tlk": "masterfeats:test.name"}, "DESCRIPTION": {"tlk": "masterfeats:test.description"}} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "tlk", "lock.json"), `{"masterfeats:test.name":9,"masterfeats:test.description":10}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "tlk", "modules", "feat", "masterfeats.json"), `{ - "entries": { - "masterfeats:test": { - "name": {"text": "Masterfeat Name"}, - "description": {"text": "Masterfeat Description"} - } - } -}`+"\n") - mkdirAll(t, filepath.Join(root, "reference")) - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.ScannedFiles != 1 || result.UpdatedFiles != 1 || result.InlineValues != 2 { - t.Fatalf("unexpected normalize result: %#v", result) - } - if result.RemainingFiles != 0 || result.RemainingLegacyRefs != 0 { - t.Fatalf("expected no remaining legacy refs, got %#v", result) - } - - normalized, err := os.ReadFile(filepath.Join(root, "topdata", "data", "masterfeats", "base.json")) - if err != nil { - t.Fatalf("read normalized file: %v", err) - } - text := string(normalized) - if !strings.Contains(text, `"text": "Masterfeat Name"`) || !strings.Contains(text, `"text": "Masterfeat Description"`) { - t.Fatalf("expected normalized inline TLK payloads, got:\n%s", text) - } -} - func TestBuildSupportsCanonicalItempropsRegistry(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "itemprops", "registry")) @@ -3066,387 +2767,6 @@ func TestBuildSupportsCanonicalItempropsRegistry(t *testing.T) { } } -func TestNormalizeProjectImportsLegacyItempropsRegistry(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - mkdirAll(t, filepath.Join(root, "reference", "tlk", "modules", "itemprops")) - writeFile(t, filepath.Join(root, "reference", "tlk", "lock.json"), `{ - "itemprops:basearmorclass.name": 384, - "itemprops:basearmorclass.property_text": 385 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "tlk", "modules", "itemprops", "add_itemprops.json"), `{ - "entries": { - "itemprops:basearmorclass.name": {"text": "Base Armor Class"}, - "itemprops:basearmorclass.property_text": {"text": "Base armor class"} - } -}`+"\n") - - refData := filepath.Join(root, "reference", "data", "itemprops") - mkdirAll(t, filepath.Join(refData, "defs", "modules")) - mkdirAll(t, filepath.Join(refData, "sets", "modules")) - mkdirAll(t, filepath.Join(refData, "costtables", "index", "modules")) - mkdirAll(t, filepath.Join(refData, "costtables", "baseaccost")) - mkdirAll(t, filepath.Join(refData, "paramtables", "index")) - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - writeFile(t, filepath.Join(refData, "defs", "base.json"), `{ - "output": "itempropdef.2da", - "columns": ["Name","Label","SubTypeResRef","Cost","CostTableResRef","Param1ResRef","GameStrRef","Description"], - "rows": [] -}`+"\n") - writeFile(t, filepath.Join(refData, "defs", "lock.json"), `{"itempropdef:basearmorclass":31}`+"\n") - writeFile(t, filepath.Join(refData, "defs", "modules", "add_eos.json"), `{ - "entries": { - "itempropdef:basearmorclass": { - "Name": {"tlk": "itemprops:basearmorclass.name"}, - "Label": "BaseArmorClass", - "SubTypeResRef": "****", - "Cost": "0", - "CostTableResRef": "31", - "Param1ResRef": "****", - "GameStrRef": {"tlk": "itemprops:basearmorclass.property_text"}, - "Description": "****" - } - } -}`+"\n") - writeFile(t, filepath.Join(refData, "sets", "base.json"), `{ - "output": "itemprops.2da", - "columns": ["0_Melee","1_Ranged","2_Thrown","3_Staves","4_Rods","5_Ammo","6_Arm_Shld","7_Helm","8_Potions","9_Scrolls","10_Wands","11_Thieves","12_TrapKits","13_Hide","14_Claw","15_Misc_Uneq","16_Misc","17_No_Props","18_Containers","19_HealerKit","20_Torch","21_Glove","StringRef","Label"], - "rows": [] -}`+"\n") - writeFile(t, filepath.Join(refData, "sets", "lock.json"), `{"itemprops:basearmorclass":31}`+"\n") - writeFile(t, filepath.Join(refData, "sets", "modules", "add_eos.json"), `{ - "entries": { - "itemprops:basearmorclass": { - "6_Arm_Shld": "1", - "StringRef": {"tlk": "itemprops:basearmorclass.name"}, - "Label": "BaseArmorClass" - } - } -}`+"\n") - writeFile(t, filepath.Join(refData, "costtables", "index", "base.json"), `{ - "output": "iprp_costtable.2da", - "columns": ["Name","Label","ClientLoad"], - "rows": [] -}`+"\n") - writeFile(t, filepath.Join(refData, "costtables", "index", "lock.json"), `{"itemprops:costtable_index:iprp_baseaccost":31}`+"\n") - writeFile(t, filepath.Join(refData, "costtables", "index", "modules", "add_eos.json"), `{ - "entries": { - "itemprops:costtable_index:iprp_baseaccost": { - "Name": "iprp_baseaccost", - "Label": "IPRP_BASEACCOST", - "ClientLoad": "0" - } - } -}`+"\n") - writeFile(t, filepath.Join(refData, "costtables", "baseaccost", "base.json"), `{ - "output": "iprp_baseaccost.2da", - "columns": ["Name","Cost"], - "rows": [{"id": 0, "Name": "0", "Cost": "0"}] -}`+"\n") - writeFile(t, filepath.Join(refData, "paramtables", "index", "base.json"), `{ - "output": "iprp_paramtable.2da", - "columns": ["Name","Lable","TableResRef"], - "rows": [] -}`+"\n") - writeFile(t, filepath.Join(refData, "paramtables", "index", "lock.json"), `{} `+"\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles < 5 { - t.Fatalf("expected registry files to be imported, got %#v", result) - } - - propertiesRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "itemprops", "registry", "properties.json")) - if err != nil { - t.Fatalf("read properties.json: %v", err) - } - text := string(propertiesRaw) - if !strings.Contains(text, `"itemprop:basearmorclass"`) || !strings.Contains(text, `"text": "Base Armor Class"`) || !strings.Contains(text, `"availability": [`) { - t.Fatalf("unexpected imported properties.json:\n%s", text) - } - if strings.Contains(text, `"tlk": "itemprops:basearmorclass.name"`) { - t.Fatalf("expected imported itemprops TLK text to be fully inlined, got:\n%s", text) - } - - lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "itemprops", "registry", "lock.json")) - if err != nil { - t.Fatalf("read registry lock: %v", err) - } - if !strings.Contains(string(lockRaw), `"itemprop:basearmorclass": 31`) || !strings.Contains(string(lockRaw), `"cost_models:iprpbaseaccost": 31`) { - t.Fatalf("unexpected registry lock:\n%s", string(lockRaw)) - } - - stateRaw, err := os.ReadFile(filepath.Join(root, "topdata", tlkStateFile)) - if err != nil { - t.Fatalf("read tlk state: %v", err) - } - if !strings.Contains(string(stateRaw), `"itemprops:basearmorclass.name"`) || !strings.Contains(string(stateRaw), `"id": 384`) { - t.Fatalf("expected imported itemprops ids to seed tlk state, got:\n%s", string(stateRaw)) - } -} - -func TestNormalizeProjectRefreshesLegacyItempropsRegistry(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data", "itemprops", "registry")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "topdata", "data", "itemprops", "registry", "properties.json"), `{ - "rows": [ - { - "key": "itemprop:basearmorclass", - "label": "BaseArmorClass", - "name": {"tlk": "itemprops:basearmorclass.name"}, - "property_text": {"tlk": "itemprops:basearmorclass.property_text"}, - "availability": ["arm_shld"], - "cost": {"value": "0", "ref": "cost_models:iprpbaseaccost"} - } - ] -}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "data", "itemprops", "registry", "cost_models.json"), `{"rows":[]}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "data", "itemprops", "registry", "param_models.json"), `{"rows":[]}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "data", "itemprops", "registry", "subtype_models.json"), `{"rows":[]}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "data", "itemprops", "registry", "lock.json"), `{} `+"\n") - mkdirAll(t, filepath.Join(root, "reference", "tlk", "modules", "itemprops")) - writeFile(t, filepath.Join(root, "reference", "tlk", "lock.json"), `{ - "itemprops:basearmorclass.name": 384, - "itemprops:basearmorclass.property_text": 385 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "tlk", "modules", "itemprops", "add_itemprops.json"), `{ - "entries": { - "itemprops:basearmorclass.name": {"text": "Base Armor Class"}, - "itemprops:basearmorclass.property_text": {"text": "Base armor class"} - } -}`+"\n") - refData := filepath.Join(root, "reference", "data", "itemprops") - mkdirAll(t, filepath.Join(refData, "defs", "modules")) - mkdirAll(t, filepath.Join(refData, "sets", "modules")) - mkdirAll(t, filepath.Join(refData, "costtables", "index", "modules")) - mkdirAll(t, filepath.Join(refData, "costtables", "baseaccost")) - mkdirAll(t, filepath.Join(refData, "paramtables", "index")) - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - writeFile(t, filepath.Join(refData, "defs", "base.json"), `{"output":"itempropdef.2da","columns":["Name","Label","SubTypeResRef","Cost","CostTableResRef","Param1ResRef","GameStrRef","Description"],"rows":[]}`+"\n") - writeFile(t, filepath.Join(refData, "defs", "lock.json"), `{"itempropdef:basearmorclass":31}`+"\n") - writeFile(t, filepath.Join(refData, "defs", "modules", "add_eos.json"), `{"entries":{"itempropdef:basearmorclass":{"Name":{"tlk":"itemprops:basearmorclass.name"},"Label":"BaseArmorClass","SubTypeResRef":"****","Cost":"0","CostTableResRef":"31","Param1ResRef":"****","GameStrRef":{"tlk":"itemprops:basearmorclass.property_text"},"Description":"****"}}}`+"\n") - writeFile(t, filepath.Join(refData, "sets", "base.json"), `{"output":"itemprops.2da","columns":["0_Melee","1_Ranged","2_Thrown","3_Staves","4_Rods","5_Ammo","6_Arm_Shld","7_Helm","8_Potions","9_Scrolls","10_Wands","11_Thieves","12_TrapKits","13_Hide","14_Claw","15_Misc_Uneq","16_Misc","17_No_Props","18_Containers","19_HealerKit","20_Torch","21_Glove","StringRef","Label"],"rows":[]}`+"\n") - writeFile(t, filepath.Join(refData, "sets", "lock.json"), `{"itemprops:basearmorclass":31}`+"\n") - writeFile(t, filepath.Join(refData, "sets", "modules", "add_eos.json"), `{"entries":{"itemprops:basearmorclass":{"6_Arm_Shld":"1","StringRef":{"tlk":"itemprops:basearmorclass.name"},"Label":"BaseArmorClass"}}}`+"\n") - writeFile(t, filepath.Join(refData, "costtables", "index", "base.json"), `{"output":"iprp_costtable.2da","columns":["Name","Label","ClientLoad"],"rows":[]}`+"\n") - writeFile(t, filepath.Join(refData, "costtables", "index", "lock.json"), `{"itemprops:costtable_index:iprp_baseaccost":31}`+"\n") - writeFile(t, filepath.Join(refData, "costtables", "index", "modules", "add_eos.json"), `{"entries":{"itemprops:costtable_index:iprp_baseaccost":{"Name":"iprp_baseaccost","Label":"IPRP_BASEACCOST","ClientLoad":"0"}}}`+"\n") - writeFile(t, filepath.Join(refData, "costtables", "baseaccost", "base.json"), `{"output":"iprp_baseaccost.2da","columns":["Name","Cost"],"rows":[{"id":0,"Name":"0","Cost":"0"}]}`+"\n") - writeFile(t, filepath.Join(refData, "paramtables", "index", "base.json"), `{"output":"iprp_paramtable.2da","columns":["Name","Lable","TableResRef"],"rows":[]}`+"\n") - writeFile(t, filepath.Join(refData, "paramtables", "index", "lock.json"), `{} `+"\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles < 5 { - t.Fatalf("expected registry refresh, got %#v", result) - } - propertiesRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "itemprops", "registry", "properties.json")) - if err != nil { - t.Fatalf("read properties.json: %v", err) - } - if strings.Contains(string(propertiesRaw), `"tlk": "itemprops:basearmorclass.name"`) { - t.Fatalf("expected refreshed registry to inline TLK text, got:\n%s", string(propertiesRaw)) - } -} - -func TestNormalizeProjectPreservesItempropsSetProjectionDifferences(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - mkdirAll(t, filepath.Join(root, "reference", "data", "itemprops", "defs", "modules")) - mkdirAll(t, filepath.Join(root, "reference", "data", "itemprops", "sets", "modules")) - mkdirAll(t, filepath.Join(root, "reference", "data", "itemprops", "costtables", "index")) - mkdirAll(t, filepath.Join(root, "reference", "data", "itemprops", "paramtables", "index")) - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - writeFile(t, filepath.Join(root, "reference", "data", "itemprops", "defs", "base.json"), `{ - "output": "itempropdef.2da", - "columns": ["Name","Label","SubTypeResRef","Cost","CostTableResRef","Param1ResRef","GameStrRef","Description"], - "rows": [ - {"id":82,"key":"itempropdef:onhitcastspell","Name":"83400","Label":"OnHitCastSpell","SubTypeResRef":"IPRP_ONHITSPELL","Cost":"****","CostTableResRef":"26","Param1ResRef":"****","GameStrRef":"5512","Description":"1450"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "itemprops", "defs", "lock.json"), `{"itempropdef:onhitcastspell":82}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "itemprops", "sets", "base.json"), `{ - "output": "itemprops.2da", - "columns": ["0_Melee","1_Ranged","2_Thrown","3_Staves","4_Rods","5_Ammo","6_Arm_Shld","7_Helm","8_Potions","9_Scrolls","10_Wands","11_Thieves","12_TrapKits","13_Hide","14_Claw","15_Misc_Uneq","16_Misc","17_No_Props","18_Containers","19_HealerKit","20_Torch","21_Glove","StringRef","Label"], - "rows": [ - {"id":82,"0_Melee":"1","1_Ranged":"****","2_Thrown":"1","3_Staves":"1","4_Rods":"****","5_Ammo":"1","6_Arm_Shld":"1","7_Helm":"****","8_Potions":"****","9_Scrolls":"****","10_Wands":"****","11_Thieves":"****","12_TrapKits":"****","13_Hide":"1","14_Claw":"1","15_Misc_Uneq":"****","16_Misc":"****","17_No_Props":"****","18_Containers":"****","19_HealerKit":"****","20_Torch":"****","21_Glove":"1","StringRef":"723","Label":"On_Hit_Cast_Spell"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "itemprops", "sets", "lock.json"), `{"itemprops:onhitcastspell":82}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "itemprops", "costtables", "index", "base.json"), `{ - "output": "iprp_costtable.2da", - "columns": ["Name","Label","ClientLoad"], - "rows": [{"id":26,"Name":"iprp_spellcst","Label":"IPRP_SPELLCST","ClientLoad":"0"}] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "itemprops", "costtables", "index", "lock.json"), `{"cost_models:iprpspellcstr":26}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "itemprops", "paramtables", "index", "base.json"), `{"output":"iprp_paramtable.2da","columns":["Name","Lable","TableResRef"],"rows":[]}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "itemprops", "paramtables", "index", "lock.json"), `{} `+"\n") - - if _, err := NormalizeProject(testProject(root)); err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - - propertiesRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "itemprops", "registry", "properties.json")) - if err != nil { - t.Fatalf("read properties.json: %v", err) - } - text := string(propertiesRaw) - for _, want := range []string{`"label": "OnHitCastSpell"`, `"set_label": "On_Hit_Cast_Spell"`, `"name": "83400"`, `"set_name": "723"`} { - if !strings.Contains(text, want) { - t.Fatalf("expected %s in imported registry, got:\n%s", want, text) - } - } -} - -func TestItempropsRegistryMatchesLegacyReferenceOutputs(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - mkdirAll(t, filepath.Join(root, "reference", "tlk", "modules", "itemprops")) - writeFile(t, filepath.Join(root, "reference", "tlk", "lock.json"), `{ - "itemprops:basearmorclass.name": 384, - "itemprops:basearmorclass.property_text": 385, - "itemprops:maximumdexteritybonus.name": 386, - "itemprops:maximumdexteritybonus.property_text": 387, - "itemprops:armorcheckpenalty.name": 388, - "itemprops:armorcheckpenalty.property_text": 389 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "tlk", "modules", "itemprops", "add_itemprops.json"), `{ - "entries": { - "itemprops:basearmorclass.name": {"text": "Base Armor Class"}, - "itemprops:basearmorclass.property_text": {"text": "Base Armor Class:"}, - "itemprops:maximumdexteritybonus.name": {"text": "Maximum Dexterity Bonus"}, - "itemprops:maximumdexteritybonus.property_text": {"text": "Maximum Dexterity Bonus:"}, - "itemprops:armorcheckpenalty.name": {"text": "Armor Check Penalty"}, - "itemprops:armorcheckpenalty.property_text": {"text": "Armor Check Penalty:"} - } -}`+"\n") - refData := filepath.Join(root, "reference", "data", "itemprops") - mkdirAll(t, filepath.Join(refData, "defs", "modules")) - mkdirAll(t, filepath.Join(refData, "sets", "modules")) - mkdirAll(t, filepath.Join(refData, "costtables", "index", "modules")) - mkdirAll(t, filepath.Join(refData, "costtables", "baseaccost")) - mkdirAll(t, filepath.Join(refData, "costtables", "maxdexcost")) - mkdirAll(t, filepath.Join(refData, "costtables", "accheckcost")) - mkdirAll(t, filepath.Join(refData, "paramtables", "index")) - writeFile(t, filepath.Join(root, "reference", "build.py"), `#!/usr/bin/env python3 -import json, os, sys -root = os.getcwd() -out = None -tlk = None -args = sys.argv[1:] -for i, arg in enumerate(args): - if arg == "--out": - out = args[i + 1] - if arg == "--tlk-dir": - tlk = args[i + 1] -os.makedirs(out, exist_ok=True) -os.makedirs(tlk, exist_ok=True) -def write_table(src_rel): - with open(os.path.join(root, "data", src_rel), "r", encoding="utf-8") as f: - obj = json.load(f) - cols = obj["columns"] - rows = obj["rows"] - out_name = obj["output"] - with open(os.path.join(out, out_name), "w", encoding="utf-8") as f: - f.write("2DA V2.0\\n\\n") - f.write("\\t".join(cols) + "\\n") - for row in rows: - vals = [str(row.get("id", ""))] - for c in cols: - vals.append(str(row[c])) - f.write("\\t".join(vals) + "\\n") -for rel in [ - "itemprops/sets/base.json", - "itemprops/defs/base.json", - "itemprops/costtables/index/base.json", - "itemprops/paramtables/index/base.json", - "itemprops/costtables/baseaccost/base.json", - "itemprops/costtables/maxdexcost/base.json", - "itemprops/costtables/accheckcost/base.json", -]: - write_table(rel) -with open(os.path.join(tlk, "sow_tlk.tlk"), "wb") as f: - f.write(b"TLK") -`+"\n") - writeFile(t, filepath.Join(refData, "defs", "base.json"), `{ - "output": "itempropdef.2da", - "columns": ["Name","Label","SubTypeResRef","Cost","CostTableResRef","Param1ResRef","GameStrRef","Description"], - "rows": [ - {"id":31,"key":"itempropdef:basearmorclass","Name":{"tlk":"itemprops:basearmorclass.name"},"Label":"BaseArmorClass","SubTypeResRef":"****","Cost":"0","CostTableResRef":"31","Param1ResRef":"****","GameStrRef":{"tlk":"itemprops:basearmorclass.property_text"},"Description":"****"}, - {"id":32,"key":"itempropdef:maximumdexteritybonus","Name":{"tlk":"itemprops:maximumdexteritybonus.name"},"Label":"MaximumDexterityBonus","SubTypeResRef":"****","Cost":"0","CostTableResRef":"32","Param1ResRef":"****","GameStrRef":{"tlk":"itemprops:maximumdexteritybonus.property_text"},"Description":"****"}, - {"id":33,"key":"itempropdef:armorcheckpenalty","Name":{"tlk":"itemprops:armorcheckpenalty.name"},"Label":"ArmorCheckPenalty","SubTypeResRef":"****","Cost":"0","CostTableResRef":"33","Param1ResRef":"****","GameStrRef":{"tlk":"itemprops:armorcheckpenalty.property_text"},"Description":"****"} - ] -}`+"\n") - writeFile(t, filepath.Join(refData, "defs", "lock.json"), `{"itempropdef:basearmorclass":31,"itempropdef:maximumdexteritybonus":32,"itempropdef:armorcheckpenalty":33}`+"\n") - writeFile(t, filepath.Join(refData, "sets", "base.json"), `{ - "output": "itemprops.2da", - "columns": ["0_Melee","1_Ranged","2_Thrown","3_Staves","4_Rods","5_Ammo","6_Arm_Shld","7_Helm","8_Potions","9_Scrolls","10_Wands","11_Thieves","12_TrapKits","13_Hide","14_Claw","15_Misc_Uneq","16_Misc","17_No_Props","18_Containers","19_HealerKit","20_Torch","21_Glove","StringRef","Label"], - "rows": [ - {"id":31,"key":"itemprops:basearmorclass","0_Melee":"****","1_Ranged":"****","2_Thrown":"****","3_Staves":"****","4_Rods":"****","5_Ammo":"****","6_Arm_Shld":"1","7_Helm":"****","8_Potions":"****","9_Scrolls":"****","10_Wands":"****","11_Thieves":"****","12_TrapKits":"****","13_Hide":"****","14_Claw":"****","15_Misc_Uneq":"****","16_Misc":"****","17_No_Props":"****","18_Containers":"****","19_HealerKit":"****","20_Torch":"****","21_Glove":"****","StringRef":{"tlk":"itemprops:basearmorclass.name"},"Label":"BaseArmorClass"}, - {"id":32,"key":"itemprops:maximumdexteritybonus","0_Melee":"****","1_Ranged":"****","2_Thrown":"****","3_Staves":"****","4_Rods":"****","5_Ammo":"****","6_Arm_Shld":"1","7_Helm":"****","8_Potions":"****","9_Scrolls":"****","10_Wands":"****","11_Thieves":"****","12_TrapKits":"****","13_Hide":"****","14_Claw":"****","15_Misc_Uneq":"****","16_Misc":"****","17_No_Props":"****","18_Containers":"****","19_HealerKit":"****","20_Torch":"****","21_Glove":"****","StringRef":{"tlk":"itemprops:maximumdexteritybonus.name"},"Label":"MaximumDexterityBonus"}, - {"id":33,"key":"itemprops:armorcheckpenalty","0_Melee":"****","1_Ranged":"****","2_Thrown":"****","3_Staves":"****","4_Rods":"****","5_Ammo":"****","6_Arm_Shld":"1","7_Helm":"****","8_Potions":"****","9_Scrolls":"****","10_Wands":"****","11_Thieves":"****","12_TrapKits":"****","13_Hide":"****","14_Claw":"****","15_Misc_Uneq":"****","16_Misc":"****","17_No_Props":"****","18_Containers":"****","19_HealerKit":"****","20_Torch":"****","21_Glove":"****","StringRef":{"tlk":"itemprops:armorcheckpenalty.name"},"Label":"ArmorCheckPenalty"} - ] -}`+"\n") - writeFile(t, filepath.Join(refData, "sets", "lock.json"), `{"itemprops:basearmorclass":31,"itemprops:maximumdexteritybonus":32,"itemprops:armorcheckpenalty":33}`+"\n") - writeFile(t, filepath.Join(refData, "costtables", "index", "base.json"), `{ - "output": "iprp_costtable.2da", - "columns": ["Name","Label","ClientLoad"], - "rows": [ - {"id":31,"Name":"iprp_baseaccost","Label":"IPRP_BASEACCOST","ClientLoad":"0"}, - {"id":32,"Name":"iprp_maxdexcost","Label":"IPRP_MAXDEXCOST","ClientLoad":"0"}, - {"id":33,"Name":"iprp_accheckcost","Label":"IPRP_ACCHECKCOST","ClientLoad":"0"} - ] -}`+"\n") - writeFile(t, filepath.Join(refData, "costtables", "index", "lock.json"), `{"itemprops:costtable_index:iprp_baseaccost":31,"itemprops:costtable_index:iprp_maxdexcost":32,"itemprops:costtable_index:iprp_accheckcost":33}`+"\n") - writeFile(t, filepath.Join(refData, "paramtables", "index", "base.json"), `{"output":"iprp_paramtable.2da","columns":["Name","Lable","TableResRef"],"rows":[]}`+"\n") - writeFile(t, filepath.Join(refData, "paramtables", "index", "lock.json"), `{} `+"\n") - writeFile(t, filepath.Join(refData, "costtables", "baseaccost", "base.json"), `{"output":"iprp_baseaccost.2da","columns":["Name","Cost"],"rows":[{"id":0,"Name":"0","Cost":"0"}]}`+"\n") - writeFile(t, filepath.Join(refData, "costtables", "maxdexcost", "base.json"), `{"output":"iprp_maxdexcost.2da","columns":["Name","Cost"],"rows":[{"id":0,"Name":"0","Cost":"0"}]}`+"\n") - writeFile(t, filepath.Join(refData, "costtables", "accheckcost", "base.json"), `{"output":"iprp_accheckcost.2da","columns":["Name","Cost"],"rows":[{"id":0,"Name":"0","Cost":"0"}]}`+"\n") - - if _, err := NormalizeProject(testProject(root)); err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - nativeResult, err := BuildNative(testProject(root), nil) - if err != nil { - t.Fatalf("BuildNative failed: %v", err) - } - referenceResult, err := BuildReference(testProject(root), nil) - if err != nil { - t.Fatalf("BuildReference failed: %v", err) - } - for _, outputName := range []string{"itemprops.2da", "itempropdef.2da", "iprp_costtable.2da", "iprp_paramtable.2da"} { - nativeBytes, err := os.ReadFile(filepath.Join(nativeResult.Output2DADir, outputName)) - if err != nil { - t.Fatalf("read native %s: %v", outputName, err) - } - referenceBytes, err := os.ReadFile(filepath.Join(referenceResult.Output2DADir, outputName)) - if err != nil { - t.Fatalf("read reference %s: %v", outputName, err) - } - if string(nativeBytes) != string(referenceBytes) { - t.Fatalf("output mismatch for %s\nnative:\n%s\nreference:\n%s", outputName, string(nativeBytes), string(referenceBytes)) - } - } - stateRaw, err := os.ReadFile(filepath.Join(root, "topdata", tlkStateFile)) - if err != nil { - t.Fatalf("read tlk state: %v", err) - } - for _, want := range []string{`"itemprops:basearmorclass.name"`, `"itemprops:maximumdexteritybonus.name"`, `"itemprops:armorcheckpenalty.name"`} { - if !strings.Contains(string(stateRaw), want) { - t.Fatalf("expected %s in tlk state, got:\n%s", want, string(stateRaw)) - } - } -} - func TestValidateProjectRejectsBrokenItempropsRegistryRefs(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "itemprops", "registry")) @@ -4913,84 +4233,6 @@ func TestDiscoverNativeOutputCatalogSkipsCompareDisabledDataset(t *testing.T) { } } -func TestNormalizeProjectImportsLegacyMasterfeats(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - - mkdirAll(t, filepath.Join(root, "reference", "data", "feat", "masterfeats")) - writeFile(t, filepath.Join(root, "reference", "data", "feat", "masterfeats", "masterfeats.json"), `{ - "output": "masterfeats.2da", - "columns": ["LABEL", "STRREF", "DESCRIPTION", "ICON", "SUCCESSOR"], - "rows": [ - {"id": 0, "key": "masterfeats:weaponfocus", "LABEL": "WeaponFocus", "STRREF": "6490", "DESCRIPTION": "436", "ICON": "ife_wepfoc"}, - {"key": "masterfeats:skillfocus", "LABEL": "SkillFocus", "STRREF": {"tlk": "masterfeats:skillfocus.name"}, "DESCRIPTION": {"tlk": "masterfeats:skillfocus.description"}, "ICON": "ife_skfoc"}, - {"id": 2, "LABEL": "DevastatingCrit", "STRREF": "83482", "DESCRIPTION": "3909", "ICON": "ife_X2DevCrit"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "feat", "masterfeats", "lock.json"), `{ - "masterfeats:weaponfocus": 0, - "masterfeats:skillfocus": 1 -}`+"\n") - mkdirAll(t, filepath.Join(root, "reference", "tlk", "modules", "feat", "masterfeats")) - writeFile(t, filepath.Join(root, "reference", "tlk", "lock.json"), `{ - "masterfeats:skillfocus.name": 100, - "masterfeats:skillfocus.description": 101 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "tlk", "modules", "feat", "masterfeats", "skillfocus.json"), `{ - "entries": { - "masterfeats:skillfocus": { - "name": {"text": "Skill Focus"}, - "description": {"text": "Choose a skill."} - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles < 2 { - t.Fatalf("expected imported masterfeats files, got %#v", result) - } - - raw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "masterfeats", "base.json")) - if err != nil { - t.Fatalf("read imported masterfeats: %v", err) - } - text := string(raw) - for _, want := range []string{ - `"STRREF": "6490"`, - `"DESCRIPTION": "436"`, - `"key": "masterfeats:skillfocus.name"`, - `"text": "Skill Focus"`, - `"key": "masterfeats:skillfocus.description"`, - `"text": "Choose a skill."`, - `"id": 1`, - } { - if !strings.Contains(text, want) { - t.Fatalf("expected imported masterfeats content %q, got:\n%s", want, text) - } - } - - lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "masterfeats", "lock.json")) - if err != nil { - t.Fatalf("read imported lock: %v", err) - } - if !strings.Contains(string(lockRaw), `"masterfeats:skillfocus": 1`) { - t.Fatalf("expected imported lock data, got:\n%s", string(lockRaw)) - } - - stateRaw, err := os.ReadFile(filepath.Join(root, "topdata", tlkStateFile)) - if err != nil { - t.Fatalf("read tlk state: %v", err) - } - if !strings.Contains(string(stateRaw), `"masterfeats:skillfocus.name"`) || !strings.Contains(string(stateRaw), `"id": 100`) { - t.Fatalf("expected seeded masterfeats tlk state, got:\n%s", string(stateRaw)) - } -} - func TestBuildSupportsCanonicalMasterfeats(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "masterfeats")) @@ -5036,56 +4278,6 @@ func TestBuildSupportsCanonicalMasterfeats(t *testing.T) { } } -func TestNormalizeProjectImportsLegacyCreaturespeed(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - mkdirAll(t, filepath.Join(root, "reference", "data", "creaturespeed", "modules")) - writeFile(t, filepath.Join(root, "reference", "data", "creaturespeed", "base.json"), `{ - "output": "creaturespeed.2da", - "columns": ["Label", "Name", "2DAName", "WALKRATE", "RUNRATE"], - "rows": [ - {"key": "creaturespeed:pcmovement", "id": 0, "Label": "PC_Movement", "Name": "****", "2DAName": "PLAYER", "WALKRATE": "2.00", "RUNRATE": "4.00"}, - {"key": "creaturespeed:normal", "id": 4, "Label": "Normal", "Name": "2322", "2DAName": "NORM", "WALKRATE": "1.75", "RUNRATE": "3.50"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "creaturespeed", "lock.json"), `{"creaturespeed:normal":4}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "creaturespeed", "modules", "ovr_mobspeed.json"), `{ - "overrides": [ - {"key": "creaturespeed:normal", "id": 4, "WALKRATE": "2.00", "RUNRATE": "4.00"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles < 1 { - t.Fatalf("expected creaturespeed file to be imported, got %#v", result) - } - - baseRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "creaturespeed", "creaturespeed.json")) - if err != nil { - t.Fatalf("read creaturespeed table: %v", err) - } - text := string(baseRaw) - for _, want := range []string{`"output": "creaturespeed.2da"`, `"Label": "Normal"`, `"WALKRATE": "2.00"`, `"RUNRATE": "4.00"`} { - if !strings.Contains(text, want) { - t.Fatalf("expected imported creaturespeed content %q, got:\n%s", want, text) - } - } - if strings.Contains(text, `"key": "creaturespeed:normal"`) { - t.Fatalf("did not expect creaturespeed keys in plain-table import, got:\n%s", text) - } - if _, err := os.Stat(filepath.Join(root, "topdata", "data", "creaturespeed", "lock.json")); !os.IsNotExist(err) { - t.Fatalf("expected no creaturespeed lock file, got err=%v", err) - } - if _, err := os.Stat(filepath.Join(root, "topdata", "data", "creaturespeed", "modules", "ovr_mobspeed.json")); !os.IsNotExist(err) { - t.Fatalf("expected no creaturespeed module file, got err=%v", err) - } -} - func TestBuildSupportsCanonicalCreaturespeed(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "creaturespeed")) @@ -5117,58 +4309,6 @@ func TestBuildSupportsCanonicalCreaturespeed(t *testing.T) { } } -func TestNormalizeProjectImportsLegacyArmor(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - mkdirAll(t, filepath.Join(root, "reference", "data", "armor", "modules")) - writeFile(t, filepath.Join(root, "reference", "data", "armor", "base.json"), `{ - "columns": ["ACBONUS", "DEXBONUS", "ACCHECK", "ARCANEFAILURE%", "WEIGHT", "COST", "DESCRIPTIONS", "BASEITEMSTATREF"], - "rows": [ - {"id": 0, "ACBONUS": "0", "DEXBONUS": "100", "ACCHECK": "0", "ARCANEFAILURE%": "0", "WEIGHT": "10", "COST": "1", "DESCRIPTIONS": "1727", "BASEITEMSTATREF": "5411"}, - {"id": 8, "ACBONUS": "8", "DEXBONUS": "1", "ACCHECK": "-8", "ARCANEFAILURE%": "45", "WEIGHT": "500", "COST": "1500", "DESCRIPTIONS": "1736", "BASEITEMSTATREF": "5441"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "armor", "lock.json"), `{"armor:ac9":9}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "armor", "modules", "add_12ac.json"), `{ - "entries": { - "armor:ac9": {"id": 9, "ACBONUS": "9", "DEXBONUS": "0", "ACCHECK": "-8", "ARCANEFAILURE%": "45", "WEIGHT": "650", "COST": "2400", "DESCRIPTIONS": "1736", "BASEITEMSTATREF": "****"} - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "armor", "modules", "ovr_statref.json"), `{ - "overrides": [ - {"id": 0, "BASEITEMSTATREF": "****"}, - {"id": 8, "BASEITEMSTATREF": "****"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles < 1 { - t.Fatalf("expected armor file to be imported, got %#v", result) - } - - tableRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "armor", "armor.json")) - if err != nil { - t.Fatalf("read armor table: %v", err) - } - text := string(tableRaw) - for _, want := range []string{`"output": "armor.2da"`, `"id": 0`, `"BASEITEMSTATREF": "****"`, `"id": 9`, `"ACBONUS": "9"`} { - if !strings.Contains(text, want) { - t.Fatalf("expected imported armor content %q, got:\n%s", want, text) - } - } - if _, err := os.Stat(filepath.Join(root, "topdata", "data", "armor", "lock.json")); !os.IsNotExist(err) { - t.Fatalf("expected no armor lock file, got err=%v", err) - } - if _, err := os.Stat(filepath.Join(root, "topdata", "data", "armor", "modules")); !os.IsNotExist(err) { - t.Fatalf("expected no armor modules dir, got err=%v", err) - } -} - func TestBuildSupportsCanonicalArmor(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "armor")) @@ -5200,189 +4340,6 @@ func TestBuildSupportsCanonicalArmor(t *testing.T) { } } -func TestBuildCanonicalArmorMatchesReference(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - mkdirAll(t, filepath.Join(root, "reference", "data", "armor", "modules")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), `#!/usr/bin/env python3 -import os, sys -out = None -tlk = None -args = sys.argv[1:] -for i, arg in enumerate(args): - if arg == "--out": - out = args[i + 1] - if arg == "--tlk-dir": - tlk = args[i + 1] -os.makedirs(out, exist_ok=True) -os.makedirs(tlk, exist_ok=True) -with open(os.path.join(out, "armor.2da"), "w", encoding="utf-8") as f: - f.write("2DA V2.0\n\n") - f.write("ACBONUS\tDEXBONUS\tACCHECK\tARCANEFAILURE%\tWEIGHT\tCOST\tDESCRIPTIONS\tBASEITEMSTATREF\n") - f.write("0\t0\t100\t0\t0\t10\t1\t1727\t****\n") - f.write("1\t1\t8\t0\t5\t50\t5\t1728\t****\n") - f.write("2\t2\t6\t0\t10\t100\t10\t1729\t****\n") - f.write("3\t3\t4\t-1\t20\t150\t15\t1730\t****\n") - f.write("4\t4\t4\t-2\t20\t300\t100\t1731\t****\n") - f.write("5\t5\t2\t-5\t30\t400\t150\t1732\t****\n") - f.write("6\t6\t1\t-7\t40\t450\t200\t1733\t****\n") - f.write("7\t7\t1\t-7\t40\t500\t600\t1734\t****\n") - f.write("8\t8\t1\t-8\t45\t500\t1500\t1736\t****\n") - f.write("9\t9\t0\t-8\t45\t650\t2400\t1736\t****\n") - f.write("10\t10\t0\t-9\t50\t800\t5000\t1736\t****\n") - f.write("11\t11\t0\t-10\t55\t800\t7500\t1736\t****\n") - f.write("12\t12\t0\t-11\t60\t1250\t10000\t1736\t****\n") -with open(os.path.join(tlk, "sow_tlk.tlk"), "wb") as f: - f.write(b"TLK") -`) - writeFile(t, filepath.Join(root, "reference", "data", "armor", "base.json"), `{ - "columns": ["ACBONUS", "DEXBONUS", "ACCHECK", "ARCANEFAILURE%", "WEIGHT", "COST", "DESCRIPTIONS", "BASEITEMSTATREF"], - "rows": [ - {"id": 0, "ACBONUS": "0", "DEXBONUS": "100", "ACCHECK": "0", "ARCANEFAILURE%": "0", "WEIGHT": "10", "COST": "1", "DESCRIPTIONS": "1727", "BASEITEMSTATREF": "5411"}, - {"id": 1, "ACBONUS": "1", "DEXBONUS": "8", "ACCHECK": "0", "ARCANEFAILURE%": "5", "WEIGHT": "50", "COST": "5", "DESCRIPTIONS": "1728", "BASEITEMSTATREF": "5432"}, - {"id": 2, "ACBONUS": "2", "DEXBONUS": "6", "ACCHECK": "0", "ARCANEFAILURE%": "10", "WEIGHT": "100", "COST": "10", "DESCRIPTIONS": "1729", "BASEITEMSTATREF": "5435"}, - {"id": 3, "ACBONUS": "3", "DEXBONUS": "4", "ACCHECK": "-1", "ARCANEFAILURE%": "20", "WEIGHT": "150", "COST": "15", "DESCRIPTIONS": "1730", "BASEITEMSTATREF": "5436"}, - {"id": 4, "ACBONUS": "4", "DEXBONUS": "4", "ACCHECK": "-2", "ARCANEFAILURE%": "20", "WEIGHT": "300", "COST": "100", "DESCRIPTIONS": "1731", "BASEITEMSTATREF": "5437"}, - {"id": 5, "ACBONUS": "5", "DEXBONUS": "2", "ACCHECK": "-5", "ARCANEFAILURE%": "30", "WEIGHT": "400", "COST": "150", "DESCRIPTIONS": "1732", "BASEITEMSTATREF": "5438"}, - {"id": 6, "ACBONUS": "6", "DEXBONUS": "1", "ACCHECK": "-7", "ARCANEFAILURE%": "40", "WEIGHT": "450", "COST": "200", "DESCRIPTIONS": "1733", "BASEITEMSTATREF": "5439"}, - {"id": 7, "ACBONUS": "7", "DEXBONUS": "1", "ACCHECK": "-7", "ARCANEFAILURE%": "40", "WEIGHT": "500", "COST": "600", "DESCRIPTIONS": "1734", "BASEITEMSTATREF": "5440"}, - {"id": 8, "ACBONUS": "8", "DEXBONUS": "1", "ACCHECK": "-8", "ARCANEFAILURE%": "45", "WEIGHT": "500", "COST": "1500", "DESCRIPTIONS": "1736", "BASEITEMSTATREF": "5441"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "armor", "lock.json"), `{"armor:ac9":9,"armor:ac10":10,"armor:ac11":11,"armor:ac12":12}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "armor", "modules", "add_12ac.json"), `{ - "entries": { - "armor:ac9": {"id": 9, "ACBONUS": "9", "DEXBONUS": "0", "ACCHECK": "-8", "ARCANEFAILURE%": "45", "WEIGHT": "650", "COST": "2400", "DESCRIPTIONS": "1736", "BASEITEMSTATREF": "****"}, - "armor:ac10": {"id": 10, "ACBONUS": "10", "DEXBONUS": "0", "ACCHECK": "-9", "ARCANEFAILURE%": "50", "WEIGHT": "800", "COST": "5000", "DESCRIPTIONS": "1736", "BASEITEMSTATREF": "****"}, - "armor:ac11": {"id": 11, "ACBONUS": "11", "DEXBONUS": "0", "ACCHECK": "-10", "ARCANEFAILURE%": "55", "WEIGHT": "800", "COST": "7500", "DESCRIPTIONS": "1736", "BASEITEMSTATREF": "****"}, - "armor:ac12": {"id": 12, "ACBONUS": "12", "DEXBONUS": "0", "ACCHECK": "-11", "ARCANEFAILURE%": "60", "WEIGHT": "1250", "COST": "10000", "DESCRIPTIONS": "1736", "BASEITEMSTATREF": "****"} - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "armor", "modules", "ovr_statref.json"), `{ - "overrides": [ - {"id": 0, "BASEITEMSTATREF": "****"}, - {"id": 1, "BASEITEMSTATREF": "****"}, - {"id": 2, "BASEITEMSTATREF": "****"}, - {"id": 3, "BASEITEMSTATREF": "****"}, - {"id": 4, "BASEITEMSTATREF": "****"}, - {"id": 5, "BASEITEMSTATREF": "****"}, - {"id": 6, "BASEITEMSTATREF": "****"}, - {"id": 7, "BASEITEMSTATREF": "****"}, - {"id": 8, "BASEITEMSTATREF": "****"} - ] -}`+"\n") - - if _, err := NormalizeProject(testProject(root)); err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - nativeResult, err := BuildNative(testProject(root), nil) - if err != nil { - t.Fatalf("BuildNative failed: %v", err) - } - referenceResult, err := BuildReference(testProject(root), nil) - if err != nil { - t.Fatalf("BuildReference failed: %v", err) - } - nativeBytes, err := os.ReadFile(filepath.Join(nativeResult.Output2DADir, "armor.2da")) - if err != nil { - t.Fatalf("read native armor.2da: %v", err) - } - referenceBytes, err := os.ReadFile(filepath.Join(referenceResult.Output2DADir, "armor.2da")) - if err != nil { - t.Fatalf("read reference armor.2da: %v", err) - } - if string(nativeBytes) != string(referenceBytes) { - t.Fatalf("armor output mismatch\nnative:\n%s\nreference:\n%s", string(nativeBytes), string(referenceBytes)) - } -} - -func TestNormalizeProjectImportsLegacyDamagetypesRegistry(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - for _, part := range []string{"core", "groups", "hitvisual"} { - mkdirAll(t, filepath.Join(root, "reference", "data", "damagetypes", part, "modules")) - } - writeFile(t, filepath.Join(root, "reference", "data", "damagetypes", "core", "base.json"), `{ - "output": "damagetypes.2da", - "columns": ["Label", "CharsheetStrref", "DamageTypeGroup", "DamageRangedProjectile"], - "rows": [ - {"id": 0, "Label": "Bludgeoning", "CharsheetStrref": "58345", "DamageTypeGroup": "0", "DamageRangedProjectile": "0"}, - {"id": 12, "Label": "Base", "CharsheetStrref": "58301", "DamageTypeGroup": "0", "DamageRangedProjectile": "0"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "damagetypes", "groups", "base.json"), `{ - "output": "damagetypegroups.2da", - "columns": ["Label", "FeedbackStrref", "ColorR", "ColorG", "ColorB"], - "rows": [ - {"id": 0, "Label": "Physical", "FeedbackStrref": "5594", "ColorR": "255", "ColorG": "102", "ColorB": "0"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "damagetypes", "hitvisual", "base.json"), `{ - "output": "damagehitvisual.2da", - "columns": ["Label", "VisualEffectID", "RangedEffectID"], - "rows": [ - {"id": 0, "Label": "Bludgeoning", "VisualEffectID": "****", "RangedEffectID": "****"}, - {"id": 12, "Label": "Base", "VisualEffectID": "****", "RangedEffectID": "****"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "damagetypes", "core", "lock.json"), `{"damagetypes:psychic":13}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "damagetypes", "groups", "lock.json"), `{"damagetypegroups:psychic":10}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "damagetypes", "hitvisual", "lock.json"), `{"damagehitvisual:psychic":13}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "damagetypes", "core", "modules", "add_eos.json"), `{ - "entries": { - "damagetypes:psychic": {"Label": "PsychicDamage", "CharsheetStrref": "70001", "DamageTypeGroup": "10", "DamageRangedProjectile": "0"} - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "damagetypes", "groups", "modules", "add_eos.json"), `{ - "entries": { - "damagetypegroups:psychic": {"Label": "Psychic", "FeedbackStrref": "70011", "ColorR": "225", "ColorG": "117", "ColorB": "255"} - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "damagetypes", "hitvisual", "modules", "add_eos.json"), `{ - "entries": { - "damagehitvisual:psychic": {"Label": "PsychicDamage", "VisualEffectID": "202", "RangedEffectID": "****"} - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles < 2 { - t.Fatalf("expected damagetypes registry files to be imported, got %#v", result) - } - - typesRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "damagetypes", "registry", "types.json")) - if err != nil { - t.Fatalf("read damagetypes registry: %v", err) - } - text := string(typesRaw) - for _, want := range []string{`"key": "damagetype:bludgeoning"`, `"group_label": "Physical"`, `"key": "damagetype:psychic"`, `"feedback_strref": "70011"`} { - if !strings.Contains(text, want) { - t.Fatalf("expected imported damagetypes content %q, got:\n%s", want, text) - } - } - lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "damagetypes", "registry", "lock.json")) - if err != nil { - t.Fatalf("read damagetypes lock: %v", err) - } - if !strings.Contains(string(lockRaw), `"damagetype:psychic": 13`) { - t.Fatalf("expected imported damagetypes lock ids, got:\n%s", string(lockRaw)) - } - for _, oldPath := range []string{ - filepath.Join(root, "topdata", "data", "damagetypes", "core"), - filepath.Join(root, "topdata", "data", "damagetypes", "groups"), - filepath.Join(root, "topdata", "data", "damagetypes", "hitvisual"), - } { - if _, err := os.Stat(oldPath); !os.IsNotExist(err) { - t.Fatalf("expected no legacy canonical damagetypes path %s, got err=%v", oldPath, err) - } - } -} - func TestBuildSupportsCanonicalDamagetypesRegistry(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "damagetypes", "registry")) @@ -5424,227 +4381,6 @@ func TestBuildSupportsCanonicalDamagetypesRegistry(t *testing.T) { } } -func TestBuildCanonicalDamagetypesRegistryMatchesReference(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - for _, part := range []string{"core", "groups", "hitvisual"} { - mkdirAll(t, filepath.Join(root, "reference", "data", "damagetypes", part, "modules")) - } - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), `#!/usr/bin/env python3 -import os, sys -out = None -tlk = None -args = sys.argv[1:] -for i, arg in enumerate(args): - if arg == "--out": - out = args[i + 1] - if arg == "--tlk-dir": - tlk = args[i + 1] -os.makedirs(out, exist_ok=True) -os.makedirs(tlk, exist_ok=True) -with open(os.path.join(out, "damagetypes.2da"), "w", encoding="utf-8") as f: - f.write("2DA V2.0\n\n") - f.write("Label\tCharsheetStrref\tDamageTypeGroup\tDamageRangedProjectile\n") - f.write("0\tBludgeoning\t58345\t0\t0\n") - f.write("1\tPiercing\t58341\t0\t0\n") - f.write("13\tPsychicDamage\t70001\t10\t0\n") -with open(os.path.join(out, "damagetypegroups.2da"), "w", encoding="utf-8") as f: - f.write("2DA V2.0\n\n") - f.write("Label\tFeedbackStrref\tColorR\tColorG\tColorB\n") - f.write("0\tPhysical\t5594\t255\t102\t0\n") - f.write("1\tMagical\t5593\t204\t119\t255\n") - f.write("10\tPsychic\t70011\t225\t117\t255\n") -with open(os.path.join(out, "damagehitvisual.2da"), "w", encoding="utf-8") as f: - f.write("2DA V2.0\n\n") - f.write("Label\tVisualEffectID\tRangedEffectID\n") - f.write("0\tBludgeoning\t****\t****\n") - f.write("1\tPiercing\t****\t****\n") - f.write("13\tPsychicDamage\t202\t****\n") -with open(os.path.join(tlk, "sow_tlk.tlk"), "wb") as f: - f.write(b"TLK") -`) - writeFile(t, filepath.Join(root, "reference", "data", "damagetypes", "core", "base.json"), `{ - "output": "damagetypes.2da", - "columns": ["Label", "CharsheetStrref", "DamageTypeGroup", "DamageRangedProjectile"], - "rows": [ - {"id": 0, "Label": "Bludgeoning", "CharsheetStrref": "58345", "DamageTypeGroup": "0", "DamageRangedProjectile": "0"}, - {"id": 1, "Label": "Piercing", "CharsheetStrref": "58341", "DamageTypeGroup": "0", "DamageRangedProjectile": "0"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "damagetypes", "groups", "base.json"), `{ - "output": "damagetypegroups.2da", - "columns": ["Label", "FeedbackStrref", "ColorR", "ColorG", "ColorB"], - "rows": [ - {"id": 0, "Label": "Physical", "FeedbackStrref": "5594", "ColorR": "255", "ColorG": "102", "ColorB": "0"}, - {"id": 1, "Label": "Magical", "FeedbackStrref": "5593", "ColorR": "204", "ColorG": "119", "ColorB": "255"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "damagetypes", "hitvisual", "base.json"), `{ - "output": "damagehitvisual.2da", - "columns": ["Label", "VisualEffectID", "RangedEffectID"], - "rows": [ - {"id": 0, "Label": "Bludgeoning", "VisualEffectID": "****", "RangedEffectID": "****"}, - {"id": 1, "Label": "Piercing", "VisualEffectID": "****", "RangedEffectID": "****"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "damagetypes", "core", "lock.json"), `{"damagetypes:psychic":13}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "damagetypes", "groups", "lock.json"), `{"damagetypegroups:psychic":10}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "damagetypes", "hitvisual", "lock.json"), `{"damagehitvisual:psychic":13}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "damagetypes", "core", "modules", "add_eos.json"), `{"entries":{"damagetypes:psychic":{"Label":"PsychicDamage","CharsheetStrref":"70001","DamageTypeGroup":"10","DamageRangedProjectile":"0"}}}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "damagetypes", "groups", "modules", "add_eos.json"), `{"entries":{"damagetypegroups:psychic":{"Label":"Psychic","FeedbackStrref":"70011","ColorR":"225","ColorG":"117","ColorB":"255"}}}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "damagetypes", "hitvisual", "modules", "add_eos.json"), `{"entries":{"damagehitvisual:psychic":{"Label":"PsychicDamage","VisualEffectID":"202","RangedEffectID":"****"}}}`+"\n") - - if _, err := NormalizeProject(testProject(root)); err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - nativeResult, err := BuildNative(testProject(root), nil) - if err != nil { - t.Fatalf("BuildNative failed: %v", err) - } - referenceResult, err := BuildReference(testProject(root), nil) - if err != nil { - t.Fatalf("BuildReference failed: %v", err) - } - for _, name := range []string{"damagetypes.2da", "damagetypegroups.2da", "damagehitvisual.2da"} { - nativeBytes, err := os.ReadFile(filepath.Join(nativeResult.Output2DADir, name)) - if err != nil { - t.Fatalf("read native %s: %v", name, err) - } - referenceBytes, err := os.ReadFile(filepath.Join(referenceResult.Output2DADir, name)) - if err != nil { - t.Fatalf("read reference %s: %v", name, err) - } - if string(nativeBytes) != string(referenceBytes) { - t.Fatalf("%s output mismatch\nnative:\n%s\nreference:\n%s", name, string(nativeBytes), string(referenceBytes)) - } - } -} - -func TestBuildCanonicalCreaturespeedMatchesReference(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - mkdirAll(t, filepath.Join(root, "reference", "data", "creaturespeed", "modules")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), `#!/usr/bin/env python3 -import os, sys -out = None -tlk = None -args = sys.argv[1:] -for i, arg in enumerate(args): - if arg == "--out": - out = args[i + 1] - if arg == "--tlk-dir": - tlk = args[i + 1] -os.makedirs(out, exist_ok=True) -os.makedirs(tlk, exist_ok=True) -with open(os.path.join(out, "creaturespeed.2da"), "w", encoding="utf-8") as f: - f.write("2DA V2.0\n\n") - f.write("Label\tName\t2DAName\tWALKRATE\tRUNRATE\n") - f.write("0\tPC_Movement\t****\tPLAYER\t2.00\t4.00\n") - f.write("1\t****\t****\t****\t****\t****\n") - f.write("2\t****\t****\t****\t****\t****\n") - f.write("3\t****\t****\t****\t****\t****\n") - f.write("4\tNormal\t2322\tNORM\t2.00\t4.00\n") -with open(os.path.join(tlk, "sow_tlk.tlk"), "wb") as f: - f.write(b"TLK") -`) - writeFile(t, filepath.Join(root, "reference", "data", "creaturespeed", "base.json"), `{ - "output": "creaturespeed.2da", - "columns": ["Label", "Name", "2DAName", "WALKRATE", "RUNRATE"], - "rows": [ - {"key": "creaturespeed:pcmovement", "id": 0, "Label": "PC_Movement", "Name": "****", "2DAName": "PLAYER", "WALKRATE": "2.00", "RUNRATE": "4.00"}, - {"key": "creaturespeed:normal", "id": 4, "Label": "Normal", "Name": "2322", "2DAName": "NORM", "WALKRATE": "1.75", "RUNRATE": "3.50"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "creaturespeed", "lock.json"), `{"creaturespeed:normal":4}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "creaturespeed", "modules", "ovr_mobspeed.json"), `{ - "overrides": [ - {"key": "creaturespeed:normal", "id": 4, "WALKRATE": "2.00", "RUNRATE": "4.00"} - ] -}`+"\n") - - if _, err := NormalizeProject(testProject(root)); err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - nativeResult, err := BuildNative(testProject(root), nil) - if err != nil { - t.Fatalf("BuildNative failed: %v", err) - } - referenceResult, err := BuildReference(testProject(root), nil) - if err != nil { - t.Fatalf("BuildReference failed: %v", err) - } - nativeBytes, err := os.ReadFile(filepath.Join(nativeResult.Output2DADir, "creaturespeed.2da")) - if err != nil { - t.Fatalf("read native creaturespeed.2da: %v", err) - } - referenceBytes, err := os.ReadFile(filepath.Join(referenceResult.Output2DADir, "creaturespeed.2da")) - if err != nil { - t.Fatalf("read reference creaturespeed.2da: %v", err) - } - if string(nativeBytes) != string(referenceBytes) { - t.Fatalf("creaturespeed output mismatch\nnative:\n%s\nreference:\n%s", string(nativeBytes), string(referenceBytes)) - } -} - -func TestNormalizeProjectImportsLegacySkyboxes(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - mkdirAll(t, filepath.Join(root, "reference", "data", "skyboxes", "modules")) - writeFile(t, filepath.Join(root, "reference", "data", "skyboxes", "base.json"), `{ - "output": "skyboxes.2da", - "columns": ["LABEL", "STRING_REF", "CYCLICAL", "DAWN", "DAY", "DUSK", "NIGHT"], - "rows": [ - {"id": 0, "LABEL": "(None)", "STRING_REF": "****", "CYCLICAL": "****", "DAWN": "****", "DAY": "****", "DUSK": "****", "NIGHT": "****"}, - {"id": 1, "LABEL": "Grass_Clear", "STRING_REF": "****", "CYCLICAL": "1", "DAWN": "Skyda_001", "DAY": "Sky_001", "DUSK": "Skyd_001", "NIGHT": "Skyn_001"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "skyboxes", "lock.json"), `{ - "festerpot:id_7": 7 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "skyboxes", "modules", "add_fpskies.json"), `{ - "entries": { - "festerpot:id_7": { - "LABEL": "Mountains", - "CYCLICAL": 1, - "DAWN": "CCS_01a", - "DAY": "CCS_01b", - "DUSK": "CCS_01c", - "NIGHT": "CCS_01d" - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles < 3 { - t.Fatalf("expected skyboxes files to be imported, got %#v", result) - } - - for _, path := range []string{ - filepath.Join(root, "topdata", "data", "skyboxes", "base.json"), - filepath.Join(root, "topdata", "data", "skyboxes", "lock.json"), - filepath.Join(root, "topdata", "data", "skyboxes", "modules", "add_fpskies.json"), - } { - if _, err := os.Stat(path); err != nil { - t.Fatalf("expected imported skyboxes path %s: %v", path, err) - } - } - - lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "skyboxes", "lock.json")) - if err != nil { - t.Fatalf("read skyboxes lock: %v", err) - } - if !strings.Contains(string(lockRaw), `"festerpot:id_7": 7`) { - t.Fatalf("expected imported skyboxes lock IDs, got:\n%s", string(lockRaw)) - } -} - func TestBuildSupportsCanonicalSkyboxes(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "skyboxes", "modules")) @@ -5692,82 +4428,6 @@ func TestBuildSupportsCanonicalSkyboxes(t *testing.T) { } } -func TestBuildCanonicalSkyboxesMatchesReference(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - mkdirAll(t, filepath.Join(root, "reference", "data", "skyboxes", "modules")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), `#!/usr/bin/env python3 -import os, sys -out = None -tlk = None -args = sys.argv[1:] -for i, arg in enumerate(args): - if arg == "--out": - out = args[i + 1] - if arg == "--tlk-dir": - tlk = args[i + 1] -os.makedirs(out, exist_ok=True) -os.makedirs(tlk, exist_ok=True) -with open(os.path.join(out, "skyboxes.2da"), "w", encoding="utf-8") as f: - f.write("2DA V2.0\n\n") - f.write("LABEL\tSTRING_REF\tCYCLICAL\tDAWN\tDAY\tDUSK\tNIGHT\n") - f.write("0\t(None)\t****\t****\t****\t****\t****\t****\n") - f.write("1\tGrass_Clear\t****\t1\tSkyda_001\tSky_001\tSkyd_001\tSkyn_001\n") - for i in range(2, 7): - f.write(f"{i}\t****\t****\t****\t****\t****\t****\t****\n") - f.write("7\tMountains\t****\t1\tCCS_01a\tCCS_01b\tCCS_01c\tCCS_01d\n") -with open(os.path.join(tlk, "sow_tlk.tlk"), "wb") as f: - f.write(b"TLK") -`) - writeFile(t, filepath.Join(root, "reference", "data", "skyboxes", "base.json"), `{ - "output": "skyboxes.2da", - "columns": ["LABEL", "STRING_REF", "CYCLICAL", "DAWN", "DAY", "DUSK", "NIGHT"], - "rows": [ - {"id": 0, "LABEL": "(None)", "STRING_REF": "****", "CYCLICAL": "****", "DAWN": "****", "DAY": "****", "DUSK": "****", "NIGHT": "****"}, - {"id": 1, "LABEL": "Grass_Clear", "STRING_REF": "****", "CYCLICAL": "1", "DAWN": "Skyda_001", "DAY": "Sky_001", "DUSK": "Skyd_001", "NIGHT": "Skyn_001"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "skyboxes", "lock.json"), `{ - "festerpot:id_7": 7 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "skyboxes", "modules", "add_fpskies.json"), `{ - "entries": { - "festerpot:id_7": { - "LABEL": "Mountains", - "CYCLICAL": 1, - "DAWN": "CCS_01a", - "DAY": "CCS_01b", - "DUSK": "CCS_01c", - "NIGHT": "CCS_01d" - } - } -}`+"\n") - - if _, err := NormalizeProject(testProject(root)); err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - nativeResult, err := BuildNative(testProject(root), nil) - if err != nil { - t.Fatalf("BuildNative failed: %v", err) - } - referenceResult, err := BuildReference(testProject(root), nil) - if err != nil { - t.Fatalf("BuildReference failed: %v", err) - } - nativeBytes, err := os.ReadFile(filepath.Join(nativeResult.Output2DADir, "skyboxes.2da")) - if err != nil { - t.Fatalf("read native skyboxes.2da: %v", err) - } - referenceBytes, err := os.ReadFile(filepath.Join(referenceResult.Output2DADir, "skyboxes.2da")) - if err != nil { - t.Fatalf("read reference skyboxes.2da: %v", err) - } - if string(nativeBytes) != string(referenceBytes) { - t.Fatalf("skyboxes output mismatch\nnative:\n%s\nreference:\n%s", string(nativeBytes), string(referenceBytes)) - } -} - func TestCompareNativeCoverageCountsPlainAndBaseDatasets(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data")) @@ -5859,147 +4519,6 @@ func TestCompareNativeCoverageCountsPlainAndBaseDatasets(t *testing.T) { } } -func TestNormalizeProjectImportsLegacyVFXPersistent(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - mkdirAll(t, filepath.Join(root, "reference", "data", "vfx_persistent", "modules")) - writeFile(t, filepath.Join(root, "reference", "data", "vfx_persistent", "base.json"), `{ - "output": "vfx_persistent.2da", - "columns": ["LABEL", "SHAPE", "RADIUS"], - "rows": [ - {"id": 0, "LABEL": "VFX_PER_FOGACID", "SHAPE": "C", "RADIUS": "5"}, - {"id": 46, "LABEL": "VFX_MOB_NIGHTMARE_SMOKE", "SHAPE": "C", "RADIUS": "1.5"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "vfx_persistent", "lock.json"), `{ - "vfx_persistent:blankradius5": 47, - "vfx_persistent:blankradius10": 48, - "vfx_persistent:blankradius15": 49, - "vfx_persistent:blankradius20": 50, - "vfx_persistent:blankradius25": 51, - "vfx_persistent:blankradius30": 52 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "vfx_persistent", "modules", "freeformaoes.json"), `{ - "entries": { - "vfx_persistent:blankradius5": {"LABEL": "VFX_PER_BLANK_RADIUS_5", "SHAPE": "C", "RADIUS": "5"}, - "vfx_persistent:blankradius10": {"LABEL": "VFX_PER_BLANK_RADIUS_10", "SHAPE": "C", "RADIUS": "5"}, - "vfx_persistent:blankradius15": {"LABEL": "VFX_PER_BLANK_RADIUS_15", "SHAPE": "C", "RADIUS": "15"}, - "vfx_persistent:blankradius20": {"LABEL": "VFX_PER_BLANK_RADIUS_20", "SHAPE": "C", "RADIUS": "20"}, - "vfx_persistent:blankradius25": {"LABEL": "VFX_PER_BLANK_RADIUS_25", "SHAPE": "C", "RADIUS": "25"}, - "vfx_persistent:blankradius30": {"LABEL": "VFX_PER_BLANK_RADIUS_30", "SHAPE": "C", "RADIUS": "30"} - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles < 2 { - t.Fatalf("expected vfx_persistent files to be imported, got %#v", result) - } - - for _, path := range []string{ - filepath.Join(root, "topdata", "data", "vfx_persistent", "base.json"), - filepath.Join(root, "topdata", "data", "vfx_persistent", "lock.json"), - filepath.Join(root, "topdata", "data", "vfx_persistent", "modules", "freeformaoes.json"), - } { - if _, err := os.Stat(path); err != nil { - t.Fatalf("expected imported vfx_persistent path %s: %v", path, err) - } - } - - lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "vfx_persistent", "lock.json")) - if err != nil { - t.Fatalf("read vfx_persistent lock: %v", err) - } - lockText := string(lockRaw) - if !strings.Contains(lockText, `"vfx_persistent:vfx_per_fogacid": 0`) || - !strings.Contains(lockText, `"vfx_persistent:vfx_mob_nightmare_smoke": 46`) || - !strings.Contains(lockText, `"vfx_persistent:blankradius5": 47`) || - !strings.Contains(lockText, `"vfx_persistent:blankradius10": 48`) || - !strings.Contains(lockText, `"vfx_persistent:blankradius30": 52`) { - t.Fatalf("expected imported vfx_persistent lock IDs, got:\n%s", lockText) - } - - baseRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "vfx_persistent", "base.json")) - if err != nil { - t.Fatalf("read vfx_persistent base: %v", err) - } - baseText := string(baseRaw) - if !strings.Contains(baseText, `"key": "vfx_persistent:vfx_per_fogacid"`) || !strings.Contains(baseText, `"key": "vfx_persistent:vfx_mob_nightmare_smoke"`) { - t.Fatalf("expected imported vfx_persistent keys, got:\n%s", baseText) - } -} - -func TestNormalizeProjectRefreshesLegacyVFXPersistentLockFromSnapshot(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data", "vfx_persistent", "modules")) - mkdirAll(t, filepath.Join(root, "topdata", "migration_snapshot", "data", "vfx_persistent", "modules")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "topdata", "data", "vfx_persistent", "base.json"), `{ - "output": "vfx_persistent.2da", - "columns": ["LABEL", "SHAPE", "RADIUS"], - "rows": [ - {"id": 0, "key": "vfx_persistent:vfx_per_fogacid", "LABEL": "VFX_PER_FOGACID", "SHAPE": "C", "RADIUS": "5"}, - {"id": 46, "key": "vfx_persistent:vfx_mob_nightmare_smoke", "LABEL": "VFX_MOB_NIGHTMARE_SMOKE", "SHAPE": "C", "RADIUS": "1.5"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "data", "vfx_persistent", "lock.json"), `{ - "vfx_persistent:blankradius10": 47, - "vfx_persistent:blankradius15": 48, - "vfx_persistent:blankradius20": 49, - "vfx_persistent:blankradius25": 50, - "vfx_persistent:blankradius30": 51, - "vfx_persistent:blankradius5": 52 -}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "data", "vfx_persistent", "modules", "freeformaoes.json"), `{"entries": {}}`+"\n") - - writeFile(t, filepath.Join(root, "topdata", "migration_snapshot", "data", "vfx_persistent", "base.json"), `{ - "output": "vfx_persistent.2da", - "columns": ["LABEL", "SHAPE", "RADIUS"], - "rows": [ - {"id": 0, "LABEL": "VFX_PER_FOGACID", "SHAPE": "C", "RADIUS": "5"}, - {"id": 46, "LABEL": "VFX_MOB_NIGHTMARE_SMOKE", "SHAPE": "C", "RADIUS": "1.5"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "migration_snapshot", "data", "vfx_persistent", "lock.json"), `{ - "vfx_persistent:blankradius5": 47, - "vfx_persistent:blankradius10": 48, - "vfx_persistent:blankradius15": 49, - "vfx_persistent:blankradius20": 50, - "vfx_persistent:blankradius25": 51, - "vfx_persistent:blankradius30": 52 -}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "migration_snapshot", "data", "vfx_persistent", "modules", "freeformaoes.json"), `{ - "entries": { - "vfx_persistent:blankradius5": {"LABEL": "VFX_PER_BLANK_RADIUS_5", "SHAPE": "C", "RADIUS": "5"} - } -}`+"\n") - - p := testProject(root) - p.Config.TopData.ReferenceBuilder = "" - result, err := NormalizeProject(p) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles < 3 { - t.Fatalf("expected snapshot-backed vfx_persistent refresh, got %#v", result) - } - - lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "vfx_persistent", "lock.json")) - if err != nil { - t.Fatalf("read vfx_persistent lock: %v", err) - } - lockText := string(lockRaw) - if !strings.Contains(lockText, `"vfx_persistent:blankradius5": 47`) || - !strings.Contains(lockText, `"vfx_persistent:blankradius10": 48`) || - !strings.Contains(lockText, `"vfx_persistent:blankradius30": 52`) { - t.Fatalf("expected refreshed snapshot lock IDs, got:\n%s", lockText) - } -} - func TestBuildSupportsCanonicalVFXPersistent(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "vfx_persistent")) @@ -6035,90 +4554,6 @@ func TestBuildSupportsCanonicalVFXPersistent(t *testing.T) { } } -func TestBuildCanonicalVFXPersistentMatchesReference(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - mkdirAll(t, filepath.Join(root, "reference", "data", "vfx_persistent", "modules")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), `#!/usr/bin/env python3 -import os, sys -out = None -tlk = None -args = sys.argv[1:] -for i, arg in enumerate(args): - if arg == "--out": - out = args[i + 1] - if arg == "--tlk-dir": - tlk = args[i + 1] -os.makedirs(out, exist_ok=True) -os.makedirs(tlk, exist_ok=True) -with open(os.path.join(out, "vfx_persistent.2da"), "w", encoding="utf-8") as f: - f.write("2DA V2.0\n\n") - f.write("LABEL\tSHAPE\tRADIUS\n") - f.write("0\tVFX_PER_FOGACID\tC\t5\n") - for i in range(1, 46): - f.write(f"{i}\t****\t****\t****\n") - f.write("46\tVFX_MOB_NIGHTMARE_SMOKE\tC\t1.5\n") - f.write("47\tVFX_PER_BLANK_RADIUS_5\tC\t5\n") - f.write("48\tVFX_PER_BLANK_RADIUS_10\tC\t5\n") - f.write("49\tVFX_PER_BLANK_RADIUS_15\tC\t15\n") - f.write("50\tVFX_PER_BLANK_RADIUS_20\tC\t20\n") - f.write("51\tVFX_PER_BLANK_RADIUS_25\tC\t25\n") - f.write("52\tVFX_PER_BLANK_RADIUS_30\tC\t30\n") -with open(os.path.join(tlk, "sow_tlk.tlk"), "wb") as f: - f.write(b"TLK") -`) - writeFile(t, filepath.Join(root, "reference", "data", "vfx_persistent", "base.json"), `{ - "output": "vfx_persistent.2da", - "columns": ["LABEL", "SHAPE", "RADIUS"], - "rows": [ - {"id": 0, "LABEL": "VFX_PER_FOGACID", "SHAPE": "C", "RADIUS": "5"}, - {"id": 46, "LABEL": "VFX_MOB_NIGHTMARE_SMOKE", "SHAPE": "C", "RADIUS": "1.5"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "vfx_persistent", "lock.json"), `{ - "vfx_persistent:blankradius5": 47, - "vfx_persistent:blankradius10": 48, - "vfx_persistent:blankradius15": 49, - "vfx_persistent:blankradius20": 50, - "vfx_persistent:blankradius25": 51, - "vfx_persistent:blankradius30": 52 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "vfx_persistent", "modules", "freeformaoes.json"), `{ - "entries": { - "vfx_persistent:blankradius5": {"LABEL": "VFX_PER_BLANK_RADIUS_5", "SHAPE": "C", "RADIUS": "5"}, - "vfx_persistent:blankradius10": {"LABEL": "VFX_PER_BLANK_RADIUS_10", "SHAPE": "C", "RADIUS": "5"}, - "vfx_persistent:blankradius15": {"LABEL": "VFX_PER_BLANK_RADIUS_15", "SHAPE": "C", "RADIUS": "15"}, - "vfx_persistent:blankradius20": {"LABEL": "VFX_PER_BLANK_RADIUS_20", "SHAPE": "C", "RADIUS": "20"}, - "vfx_persistent:blankradius25": {"LABEL": "VFX_PER_BLANK_RADIUS_25", "SHAPE": "C", "RADIUS": "25"}, - "vfx_persistent:blankradius30": {"LABEL": "VFX_PER_BLANK_RADIUS_30", "SHAPE": "C", "RADIUS": "30"} - } -}`+"\n") - - if _, err := NormalizeProject(testProject(root)); err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - nativeResult, err := BuildNative(testProject(root), nil) - if err != nil { - t.Fatalf("BuildNative failed: %v", err) - } - referenceResult, err := BuildReference(testProject(root), nil) - if err != nil { - t.Fatalf("BuildReference failed: %v", err) - } - nativeBytes, err := os.ReadFile(filepath.Join(nativeResult.Output2DADir, "vfx_persistent.2da")) - if err != nil { - t.Fatalf("read native vfx_persistent.2da: %v", err) - } - referenceBytes, err := os.ReadFile(filepath.Join(referenceResult.Output2DADir, "vfx_persistent.2da")) - if err != nil { - t.Fatalf("read reference vfx_persistent.2da: %v", err) - } - if string(nativeBytes) != string(referenceBytes) { - t.Fatalf("vfx_persistent output mismatch\nnative:\n%s\nreference:\n%s", string(nativeBytes), string(referenceBytes)) - } -} - func TestBuildNativeRemovesStaleTopdataOutputs(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "repadjust")) @@ -6235,46 +4670,6 @@ func TestCompareReferenceFailsClearlyWithoutReferenceBuilder(t *testing.T) { } } -func TestNormalizeProjectImportsLegacyProgfx(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - mkdirAll(t, filepath.Join(root, "reference", "data", "progfx")) - writeFile(t, filepath.Join(root, "reference", "data", "progfx", "base.json"), `{ - "output": "progfx.2da", - "columns": ["Label", "Type", "Param1", "Param2", "Param3", "Param4", "Param5", "Param6", "Param7", "Param8"], - "rows": [ - {"id": 0, "Label": "****", "Type": "****", "Param1": "****", "Param2": "****", "Param3": "****", "Param4": "****", "Param5": "****", "Param6": "****", "Param7": "****", "Param8": "****"}, - {"id": 1300, "Label": "FreezeAnimations", "Type": "13", "Param1": "****", "Param2": "****", "Param3": "****", "Param4": "****", "Param5": "****", "Param6": "****", "Param7": "****", "Param8": "****"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "progfx", "lock.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles < 2 { - t.Fatalf("expected progfx files to be imported, got %#v", result) - } - for _, path := range []string{ - filepath.Join(root, "topdata", "data", "progfx", "base.json"), - filepath.Join(root, "topdata", "data", "progfx", "lock.json"), - } { - if _, err := os.Stat(path); err != nil { - t.Fatalf("expected imported progfx path %s: %v", path, err) - } - } - lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "progfx", "lock.json")) - if err != nil { - t.Fatalf("read progfx lock: %v", err) - } - if strings.TrimSpace(string(lockRaw)) != "{}" { - t.Fatalf("expected empty imported progfx lock, got:\n%s", string(lockRaw)) - } -} - func TestBuildSupportsCanonicalProgfx(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "progfx")) @@ -6307,113 +4702,6 @@ func TestBuildSupportsCanonicalProgfx(t *testing.T) { } } -func TestBuildCanonicalProgfxMatchesReference(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - mkdirAll(t, filepath.Join(root, "reference", "data", "progfx")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), `#!/usr/bin/env python3 -import os, sys -out = None -tlk = None -args = sys.argv[1:] -for i, arg in enumerate(args): - if arg == "--out": - out = args[i + 1] - if arg == "--tlk-dir": - tlk = args[i + 1] -os.makedirs(out, exist_ok=True) -os.makedirs(tlk, exist_ok=True) -with open(os.path.join(out, "progfx.2da"), "w", encoding="utf-8") as f: - f.write("2DA V2.0\n\n") - f.write("Label\tType\tParam1\tParam2\tParam3\tParam4\tParam5\tParam6\tParam7\tParam8\n") - f.write("0\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\n") - for i in range(1, 1300): - f.write(f"{i}\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\n") - f.write("1300\tFreezeAnimations\t13\t****\t****\t****\t****\t****\t****\t****\t****\n") -with open(os.path.join(tlk, "sow_tlk.tlk"), "wb") as f: - f.write(b"TLK") -`) - writeFile(t, filepath.Join(root, "reference", "data", "progfx", "base.json"), `{ - "output": "progfx.2da", - "columns": ["Label", "Type", "Param1", "Param2", "Param3", "Param4", "Param5", "Param6", "Param7", "Param8"], - "rows": [ - {"id": 0, "Label": "****", "Type": "****", "Param1": "****", "Param2": "****", "Param3": "****", "Param4": "****", "Param5": "****", "Param6": "****", "Param7": "****", "Param8": "****"}, - {"id": 1300, "Label": "FreezeAnimations", "Type": "13", "Param1": "****", "Param2": "****", "Param3": "****", "Param4": "****", "Param5": "****", "Param6": "****", "Param7": "****", "Param8": "****"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "progfx", "lock.json"), "{}\n") - - if _, err := NormalizeProject(testProject(root)); err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - nativeResult, err := BuildNative(testProject(root), nil) - if err != nil { - t.Fatalf("BuildNative failed: %v", err) - } - referenceResult, err := BuildReference(testProject(root), nil) - if err != nil { - t.Fatalf("BuildReference failed: %v", err) - } - nativeBytes, err := os.ReadFile(filepath.Join(nativeResult.Output2DADir, "progfx.2da")) - if err != nil { - t.Fatalf("read native progfx.2da: %v", err) - } - referenceBytes, err := os.ReadFile(filepath.Join(referenceResult.Output2DADir, "progfx.2da")) - if err != nil { - t.Fatalf("read reference progfx.2da: %v", err) - } - if string(nativeBytes) != string(referenceBytes) { - t.Fatalf("progfx output mismatch\nnative:\n%s\nreference:\n%s", string(nativeBytes), string(referenceBytes)) - } -} - -func TestNormalizeProjectImportsLegacyRepadjust(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - mkdirAll(t, filepath.Join(root, "reference", "data", "repadjust")) - writeFile(t, filepath.Join(root, "reference", "data", "repadjust", "base.json"), `{ - "columns": ["LABEL", "PERSONALREP", "FACTIONREP", "WITFRIA", "WITFRIB", "WITFRIC", "WITNEUA", "WITNEUB", "WITNEUC", "WITENEA", "WITENEB", "WITENEC"], - "rows": [ - {"id": 0, "LABEL": "Attack", "PERSONALREP": "-100", "FACTIONREP": "-5", "WITFRIA": "-100", "WITFRIB": "-100", "WITFRIC": "0", "WITNEUA": "-5", "WITNEUB": "0", "WITNEUC": "0", "WITENEA": "0", "WITENEB": "0", "WITENEC": "0"}, - {"id": 3, "LABEL": "Help", "PERSONALREP": "0", "FACTIONREP": "0", "WITFRIA": "0", "WITFRIB": "0", "WITFRIC": "0", "WITNEUA": "0", "WITNEUB": "0", "WITNEUC": "0", "WITENEA": "0", "WITENEB": "0", "WITENEC": "0"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "repadjust", "lock.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles < 2 { - t.Fatalf("expected repadjust files to be imported, got %#v", result) - } - for _, path := range []string{ - filepath.Join(root, "topdata", "data", "repadjust", "base.json"), - filepath.Join(root, "topdata", "data", "repadjust", "lock.json"), - } { - if _, err := os.Stat(path); err != nil { - t.Fatalf("expected imported repadjust path %s: %v", path, err) - } - } - baseRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "repadjust", "base.json")) - if err != nil { - t.Fatalf("read repadjust base: %v", err) - } - if !strings.Contains(string(baseRaw), `"output": "repadjust.2da"`) { - t.Fatalf("expected imported repadjust output name, got:\n%s", string(baseRaw)) - } - lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "repadjust", "lock.json")) - if err != nil { - t.Fatalf("read repadjust lock: %v", err) - } - if strings.TrimSpace(string(lockRaw)) != "{}" { - t.Fatalf("expected empty imported repadjust lock, got:\n%s", string(lockRaw)) - } -} - func TestBuildSupportsCanonicalRepadjust(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "repadjust")) @@ -6446,137 +4734,6 @@ func TestBuildSupportsCanonicalRepadjust(t *testing.T) { } } -func TestBuildCanonicalRepadjustMatchesReference(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - mkdirAll(t, filepath.Join(root, "reference", "data", "repadjust")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), `#!/usr/bin/env python3 -import os, sys -out = None -tlk = None -args = sys.argv[1:] -for i, arg in enumerate(args): - if arg == "--out": - out = args[i + 1] - if arg == "--tlk-dir": - tlk = args[i + 1] -os.makedirs(out, exist_ok=True) -os.makedirs(tlk, exist_ok=True) -with open(os.path.join(out, "repadjust.2da"), "w", encoding="utf-8") as f: - f.write("2DA V2.0\n\n") - f.write("LABEL\tPERSONALREP\tFACTIONREP\tWITFRIA\tWITFRIB\tWITFRIC\tWITNEUA\tWITNEUB\tWITNEUC\tWITENEA\tWITENEB\tWITENEC\n") - f.write("0\tAttack\t-100\t-5\t-100\t-100\t0\t-5\t0\t0\t0\t0\t0\n") - f.write("1\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\n") - f.write("2\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\n") - f.write("3\tHelp\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\n") -with open(os.path.join(tlk, "sow_tlk.tlk"), "wb") as f: - f.write(b"TLK") -`) - writeFile(t, filepath.Join(root, "reference", "data", "repadjust", "base.json"), `{ - "output": "repadjust.2da", - "columns": ["LABEL", "PERSONALREP", "FACTIONREP", "WITFRIA", "WITFRIB", "WITFRIC", "WITNEUA", "WITNEUB", "WITNEUC", "WITENEA", "WITENEB", "WITENEC"], - "rows": [ - {"id": 0, "LABEL": "Attack", "PERSONALREP": "-100", "FACTIONREP": "-5", "WITFRIA": "-100", "WITFRIB": "-100", "WITFRIC": "0", "WITNEUA": "-5", "WITNEUB": "0", "WITNEUC": "0", "WITENEA": "0", "WITENEB": "0", "WITENEC": "0"}, - {"id": 3, "LABEL": "Help", "PERSONALREP": "0", "FACTIONREP": "0", "WITFRIA": "0", "WITFRIB": "0", "WITFRIC": "0", "WITNEUA": "0", "WITNEUB": "0", "WITNEUC": "0", "WITENEA": "0", "WITENEB": "0", "WITENEC": "0"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "repadjust", "lock.json"), "{}\n") - - if _, err := NormalizeProject(testProject(root)); err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - nativeResult, err := BuildNative(testProject(root), nil) - if err != nil { - t.Fatalf("BuildNative failed: %v", err) - } - referenceResult, err := BuildReference(testProject(root), nil) - if err != nil { - t.Fatalf("BuildReference failed: %v", err) - } - nativeBytes, err := os.ReadFile(filepath.Join(nativeResult.Output2DADir, "repadjust.2da")) - if err != nil { - t.Fatalf("read native repadjust.2da: %v", err) - } - referenceBytes, err := os.ReadFile(filepath.Join(referenceResult.Output2DADir, "repadjust.2da")) - if err != nil { - t.Fatalf("read reference repadjust.2da: %v", err) - } - if string(nativeBytes) != string(referenceBytes) { - t.Fatalf("repadjust output mismatch\nnative:\n%s\nreference:\n%s", string(nativeBytes), string(referenceBytes)) - } -} - -func TestNormalizeProjectImportsLegacyWingmodel(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - mkdirAll(t, filepath.Join(root, "reference", "data", "wingmodel", "modules")) - writeFile(t, filepath.Join(root, "reference", "data", "wingmodel", "base.json"), `{ - "columns": ["LABEL", "MODEL", "ENVMAP"], - "rows": [ - {"key": "wingmodel:none", "id": 0, "LABEL": "(None)", "MODEL": "****", "ENVMAP": "****"}, - {"key": "wingmodel:angel", "id": 2, "LABEL": "Angel", "MODEL": "c_angel", "ENVMAP": "default"}, - {"key": "wingmodel:greatsword", "id": 89, "LABEL": "Greatsword", "MODEL": "c_greatsword", "ENVMAP": "default"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "wingmodel", "lock.json"), `{ - "wings:armoredangel": 90, - "wings:fallenangel": 91, - "wings:mephisto": 92, - "wingmodel:demon": 1, - "wingmodel:angel": 2 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "wingmodel", "modules", "add.json"), `{ - "entries": { - "wings:armoredangel": { - "LABEL": "Armored Angel", - "MODEL": "c_armoredangel", - "ENVMAP": "default" - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "wingmodel", "modules", "ovr_wingprefixes.json"), `{ - "overrides": [ - {"key": "wingmodel:angel", "id": 2, "LABEL": "Wings: Angel"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles < 4 { - t.Fatalf("expected wingmodel files to be imported, got %#v", result) - } - for _, path := range []string{ - filepath.Join(root, "topdata", "data", "wingmodel", "base.json"), - filepath.Join(root, "topdata", "data", "wingmodel", "lock.json"), - filepath.Join(root, "topdata", "data", "wingmodel", "modules", "add.json"), - filepath.Join(root, "topdata", "data", "wingmodel", "modules", "ovr_wingprefixes.json"), - } { - if _, err := os.Stat(path); err != nil { - t.Fatalf("expected imported wingmodel path %s: %v", path, err) - } - } - baseRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "wingmodel", "base.json")) - if err != nil { - t.Fatalf("read wingmodel base: %v", err) - } - if !strings.Contains(string(baseRaw), `"output": "wingmodel.2da"`) { - t.Fatalf("expected imported wingmodel output name, got:\n%s", string(baseRaw)) - } - lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "wingmodel", "lock.json")) - if err != nil { - t.Fatalf("read wingmodel lock: %v", err) - } - lockText := string(lockRaw) - if !strings.Contains(lockText, `"wings:mephisto": 92`) || !strings.Contains(lockText, `"wingmodel:angel": 2`) { - t.Fatalf("expected imported wingmodel lock IDs, got:\n%s", lockText) - } -} - func TestBuildSupportsCanonicalWingmodel(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "wingmodel", "modules")) @@ -6627,158 +4784,6 @@ func TestBuildSupportsCanonicalWingmodel(t *testing.T) { } } -func TestBuildCanonicalWingmodelMatchesReference(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - mkdirAll(t, filepath.Join(root, "reference", "data", "wingmodel", "modules")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), `#!/usr/bin/env python3 -import os, sys -out = None -tlk = None -args = sys.argv[1:] -for i, arg in enumerate(args): - if arg == "--out": - out = args[i + 1] - if arg == "--tlk-dir": - tlk = args[i + 1] -os.makedirs(out, exist_ok=True) -os.makedirs(tlk, exist_ok=True) -with open(os.path.join(out, "wingmodel.2da"), "w", encoding="utf-8") as f: - f.write("2DA V2.0\n\n") - f.write("LABEL\tMODEL\tENVMAP\n") - f.write("0\t(None)\t****\t****\n") - f.write("1\t****\t****\t****\n") - f.write("2\tWings: Angel\tc_angel\tdefault\n") - for i in range(3, 90): - f.write(f"{i}\t****\t****\t****\n") - f.write("90\tArmored Angel\tc_armoredangel\tdefault\n") -with open(os.path.join(tlk, "sow_tlk.tlk"), "wb") as f: - f.write(b"TLK") -`) - writeFile(t, filepath.Join(root, "reference", "data", "wingmodel", "base.json"), `{ - "output": "wingmodel.2da", - "columns": ["LABEL", "MODEL", "ENVMAP"], - "rows": [ - {"key": "wingmodel:none", "id": 0, "LABEL": "(None)", "MODEL": "****", "ENVMAP": "****"}, - {"key": "wingmodel:angel", "id": 2, "LABEL": "Angel", "MODEL": "c_angel", "ENVMAP": "default"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "wingmodel", "lock.json"), `{ - "wings:armoredangel": 90, - "wingmodel:angel": 2 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "wingmodel", "modules", "add.json"), `{ - "entries": { - "wings:armoredangel": { - "LABEL": "Armored Angel", - "MODEL": "c_armoredangel", - "ENVMAP": "default" - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "wingmodel", "modules", "ovr_wingprefixes.json"), `{ - "overrides": [ - {"key": "wingmodel:angel", "id": 2, "LABEL": "Wings: Angel"} - ] -}`+"\n") - - if _, err := NormalizeProject(testProject(root)); err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - nativeResult, err := BuildNative(testProject(root), nil) - if err != nil { - t.Fatalf("BuildNative failed: %v", err) - } - referenceResult, err := BuildReference(testProject(root), nil) - if err != nil { - t.Fatalf("BuildReference failed: %v", err) - } - nativeBytes, err := os.ReadFile(filepath.Join(nativeResult.Output2DADir, "wingmodel.2da")) - if err != nil { - t.Fatalf("read native wingmodel.2da: %v", err) - } - referenceBytes, err := os.ReadFile(filepath.Join(referenceResult.Output2DADir, "wingmodel.2da")) - if err != nil { - t.Fatalf("read reference wingmodel.2da: %v", err) - } - if string(nativeBytes) != string(referenceBytes) { - t.Fatalf("wingmodel output mismatch\nnative:\n%s\nreference:\n%s", string(nativeBytes), string(referenceBytes)) - } -} - -func TestNormalizeProjectImportsLegacyTailmodel(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - - mkdirAll(t, filepath.Join(root, "reference", "data", "tailmodel", "modules")) - writeFile(t, filepath.Join(root, "reference", "data", "tailmodel", "base.json"), `{ - "columns": ["LABEL", "MODEL", "ENVMAP"], - "rows": [ - {"key": "tailmodel:none", "id": 0, "LABEL": "(None)", "MODEL": "****", "ENVMAP": "****"}, - {"key": "tailmodel:lizard", "id": 1, "LABEL": "Lizard", "MODEL": "c_tailliz", "ENVMAP": "default"}, - {"key": "tailmodel:dragonwhite", "id": 13, "LABEL": "Dragon, White", "MODEL": "c_tail_whi", "ENVMAP": "****"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "tailmodel", "lock.json"), `{ - "tails:taildemon": 5050, - "tails:taildemon2": 5051, - "tails:taildemon4": 5052, - "tails:tailfluff": 5053, - "tailmodel:lizard": 1, - "tailmodel:dragonwhite": 13 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "tailmodel", "modules", "add.json"), `{ - "entries": { - "tails:taildemon": {"LABEL": "Tail: Demon", "MODEL": "c_taildemon", "ENVMAP": "default"}, - "tails:tailfluff": {"LABEL": "Tail: Demon, Fluffy", "MODEL": "c_tailfluff", "ENVMAP": "default"} - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "tailmodel", "modules", "ovr_tailprefixes.json"), `{ - "overrides": [ - {"key": "tailmodel:lizard", "id": 1, "LABEL": "Tail: Lizard"}, - {"key": "tailmodel:dragonwhite", "id": 13, "LABEL": "Tail: Dragon, White"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles < 4 { - t.Fatalf("expected tailmodel files to be imported, got %#v", result) - } - - for _, path := range []string{ - filepath.Join(root, "topdata", "data", "tailmodel", "base.json"), - filepath.Join(root, "topdata", "data", "tailmodel", "lock.json"), - filepath.Join(root, "topdata", "data", "tailmodel", "modules", "add.json"), - filepath.Join(root, "topdata", "data", "tailmodel", "modules", "ovr_tailprefixes.json"), - } { - if _, err := os.Stat(path); err != nil { - t.Fatalf("expected imported tailmodel path %s: %v", path, err) - } - } - - baseRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "tailmodel", "base.json")) - if err != nil { - t.Fatalf("read tailmodel base: %v", err) - } - if !strings.Contains(string(baseRaw), `"output": "tailmodel.2da"`) { - t.Fatalf("expected imported tailmodel output name, got:\n%s", string(baseRaw)) - } - lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "tailmodel", "lock.json")) - if err != nil { - t.Fatalf("read tailmodel lock: %v", err) - } - lockText := string(lockRaw) - if !strings.Contains(lockText, `"tails:taildemon": 5050`) || !strings.Contains(lockText, `"tailmodel:dragonwhite": 13`) { - t.Fatalf("expected imported tailmodel lock IDs, got:\n%s", lockText) - } -} - func TestBuildSupportsCanonicalTailmodel(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "tailmodel", "modules")) @@ -6825,199 +4830,6 @@ func TestBuildSupportsCanonicalTailmodel(t *testing.T) { } } -func TestBuildCanonicalTailmodelMatchesReference(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data", "tailmodel", "modules")) - mkdirAll(t, filepath.Join(root, "reference", "data", "tailmodel", "modules")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), `#!/usr/bin/env python3 -import os, sys -out = None -tlk = None -args = sys.argv[1:] -for i, arg in enumerate(args): - if arg == "--out": - out = args[i + 1] - if arg == "--tlk-dir": - tlk = args[i + 1] -os.makedirs(out, exist_ok=True) -os.makedirs(tlk, exist_ok=True) -with open(os.path.join(out, "tailmodel.2da"), "w", encoding="utf-8") as f: - f.write("2DA V2.0\n\n") - f.write("LABEL\tMODEL\tENVMAP\n") - f.write("0\t(None)\t****\t****\n") - f.write("1\t\"Tail: Lizard\"\tc_tailliz\tdefault\n") - for i in range(2, 5050): - f.write(f"{i}\t****\t****\t****\n") - f.write("5050\t\"Tail: Demon\"\tc_taildemon\tdefault\n") -with open(os.path.join(tlk, "sow_tlk.tlk"), "wb") as f: - f.write(b"TLK") -`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "tailmodel", "base.json"), `{ - "output": "tailmodel.2da", - "columns": ["LABEL", "MODEL", "ENVMAP"], - "rows": [ - {"key": "tailmodel:none", "id": 0, "LABEL": "(None)", "MODEL": "****", "ENVMAP": "****"}, - {"key": "tailmodel:lizard", "id": 1, "LABEL": "Lizard", "MODEL": "c_tailliz", "ENVMAP": "default"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "tailmodel", "lock.json"), `{ - "tails:taildemon": 5050, - "tailmodel:lizard": 1 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "tailmodel", "modules", "add.json"), `{ - "entries": { - "tails:taildemon": {"LABEL": "Tail: Demon", "MODEL": "c_taildemon", "ENVMAP": "default"} - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "tailmodel", "modules", "ovr_tailprefixes.json"), `{ - "overrides": [ - {"key": "tailmodel:lizard", "id": 1, "LABEL": "Tail: Lizard"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "data", "tailmodel", "base.json"), `{ - "output": "tailmodel.2da", - "columns": ["LABEL", "MODEL", "ENVMAP"], - "rows": [ - {"key": "tailmodel:none", "id": 0, "LABEL": "(None)", "MODEL": "****", "ENVMAP": "****"}, - {"key": "tailmodel:lizard", "id": 1, "LABEL": "Lizard", "MODEL": "c_tailliz", "ENVMAP": "default"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "data", "tailmodel", "lock.json"), `{ - "tails:taildemon": 5050, - "tailmodel:lizard": 1 -}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "data", "tailmodel", "modules", "add.json"), `{ - "entries": { - "tails:taildemon": {"LABEL": "Tail: Demon", "MODEL": "c_taildemon", "ENVMAP": "default"} - } -}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "data", "tailmodel", "modules", "ovr_tailprefixes.json"), `{ - "overrides": [ - {"key": "tailmodel:lizard", "id": 1, "LABEL": "Tail: Lizard"} - ] -}`+"\n") - - if _, err := NormalizeProject(testProject(root)); err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - nativeResult, err := BuildNative(testProject(root), nil) - if err != nil { - t.Fatalf("BuildNative failed: %v", err) - } - referenceResult, err := BuildReference(testProject(root), nil) - if err != nil { - t.Fatalf("BuildReference failed: %v", err) - } - nativeBytes, err := os.ReadFile(filepath.Join(nativeResult.Output2DADir, "tailmodel.2da")) - if err != nil { - t.Fatalf("read native tailmodel.2da: %v", err) - } - referenceBytes, err := os.ReadFile(filepath.Join(referenceResult.Output2DADir, "tailmodel.2da")) - if err != nil { - t.Fatalf("read reference tailmodel.2da: %v", err) - } - if string(nativeBytes) != string(referenceBytes) { - t.Fatalf("tailmodel output mismatch\nnative:\n%s\nreference:\n%s", string(nativeBytes), string(referenceBytes)) - } -} - -func TestNormalizeProjectImportsLegacyDoortypes(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - - mkdirAll(t, filepath.Join(root, "reference", "data", "doortypes", "modules")) - writeFile(t, filepath.Join(root, "reference", "data", "doortypes", "base.json"), `{ - "columns": ["Label", "Model", "TileSet", "TemplateResRef", "StringRefGame", "BlockSight", "VisibleModel", "SoundAppType"], - "rows": [ - {"id": 0, "Label": "Generic", "Model": "****", "TileSet": "****", "TemplateResRef": "****", "StringRefGame": "66717", "BlockSight": "****", "VisibleModel": "****", "SoundAppType": "****"}, - {"id": 1, "Label": "Wall1Door", "Model": "TTR_UDoor_01", "TileSet": "TTR01", "TemplateResRef": "nw_door_ttr_01", "StringRefGame": "63491", "BlockSight": "1", "VisibleModel": "1", "SoundAppType": "1"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "doortypes", "lock.json"), `{ - "txpple:id_6000": 5200, - "ancarion:id_125": 5226, - "babayaga:id_1046": 5244 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "doortypes", "modules", "add_pld01.json"), `{ - "entries": { - "ancarion:id_125": {"Label": "CastleTrans", "Model": "pld_door_01", "TileSet": "PLD01", "TemplateResRef": "nw_door_ttr_15", "StringRefGame": 63520, "BlockSight": 0, "VisibleModel": 0} - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "doortypes", "modules", "add_sic11.json"), `{ - "entries": { - "sens:id_1351": {"Label": "MulsantirGreat01", "Model": "door_mul01", "TileSet": "TNO01", "TemplateResRef": "nw_door_ttr_01", "StringRefGame": 5349, "BlockSight": 1, "VisibleModel": 1, "SoundAppType": 1} - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "doortypes", "modules", "add_tapr.json"), `{ - "entries": { - "txpple:id_6000": {"Label": "Necropolis: Main Door", "Model": "mst01_door_01", "TileSet": "mst01", "TemplateResRef": "nw_door_metal", "StringRefGame": 66717, "BlockSight": 1, "VisibleModel": 1, "SoundAppType": 9} - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "doortypes", "modules", "add_tdm01.json"), `{ - "entries": { - "projectq:id_5225": {"Label": "DungeonGate", "Model": "tdm02_sdoor_01", "TileSet": "TDM01", "TemplateResRef": "nw_door_ttr_01", "StringRefGame": 63491, "BlockSight": 1, "VisibleModel": 1, "SoundAppType": 1} - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "doortypes", "modules", "add_tdx01.json"), `{ - "entries": { - "sixandzwerkules:id_1167": {"Label": "DungeonMechGate1", "Model": "tdx_door04", "TileSet": "TDX01", "TemplateResRef": "nw_door_ttr_03", "StringRefGame": 63537, "BlockSight": 1, "VisibleModel": 1, "SoundAppType": 3} - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "doortypes", "modules", "add_tei01.json"), `{ - "entries": { - "zwerkules:id_1451": {"Label": "TEI01_StairTrans1", "Model": "tei_udoor01", "TileSet": "TEI01", "TemplateResRef": "nw_door_ttr_34", "StringRefGame": 5349, "BlockSight": 0, "VisibleModel": 0} - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "doortypes", "modules", "add_tfm01.json"), `{ - "entries": { - "babayaga:id_1046": {"Label": "TFM01_BarnDoor", "Model": "tfm_udoor_06", "TileSet": "TFM01", "TemplateResRef": "nw_door_ttr_05", "StringRefGame": 5350, "BlockSight": 1, "VisibleModel": 1, "SoundAppType": 1} - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles < 9 { - t.Fatalf("expected doortypes files to be imported, got %#v", result) - } - - for _, path := range []string{ - filepath.Join(root, "topdata", "data", "doortypes", "base.json"), - filepath.Join(root, "topdata", "data", "doortypes", "lock.json"), - filepath.Join(root, "topdata", "data", "doortypes", "modules", "add_pld01.json"), - filepath.Join(root, "topdata", "data", "doortypes", "modules", "add_sic11.json"), - filepath.Join(root, "topdata", "data", "doortypes", "modules", "add_tapr.json"), - filepath.Join(root, "topdata", "data", "doortypes", "modules", "add_tdm01.json"), - filepath.Join(root, "topdata", "data", "doortypes", "modules", "add_tdx01.json"), - filepath.Join(root, "topdata", "data", "doortypes", "modules", "add_tei01.json"), - filepath.Join(root, "topdata", "data", "doortypes", "modules", "add_tfm01.json"), - } { - if _, err := os.Stat(path); err != nil { - t.Fatalf("expected imported doortypes path %s: %v", path, err) - } - } - - baseRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "doortypes", "base.json")) - if err != nil { - t.Fatalf("read doortypes base: %v", err) - } - if !strings.Contains(string(baseRaw), `"output": "doortypes.2da"`) { - t.Fatalf("expected imported doortypes output name, got:\n%s", string(baseRaw)) - } - lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "doortypes", "lock.json")) - if err != nil { - t.Fatalf("read doortypes lock: %v", err) - } - lockText := string(lockRaw) - if !strings.Contains(lockText, `"txpple:id_6000": 5200`) || !strings.Contains(lockText, `"babayaga:id_1046": 5244`) { - t.Fatalf("expected imported doortypes lock IDs, got:\n%s", lockText) - } -} - func TestBuildSupportsCanonicalDoortypes(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "doortypes", "modules")) @@ -7060,245 +4872,6 @@ func TestBuildSupportsCanonicalDoortypes(t *testing.T) { } } -func TestBuildCanonicalDoortypesMatchesReference(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data", "doortypes", "modules")) - mkdirAll(t, filepath.Join(root, "reference", "data", "doortypes", "modules")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), `#!/usr/bin/env python3 -import os, sys -out = None -tlk = None -args = sys.argv[1:] -for i, arg in enumerate(args): - if arg == "--out": - out = args[i + 1] - if arg == "--tlk-dir": - tlk = args[i + 1] -os.makedirs(out, exist_ok=True) -os.makedirs(tlk, exist_ok=True) -with open(os.path.join(out, "doortypes.2da"), "w", encoding="utf-8") as f: - f.write("2DA V2.0\n\n") - f.write("Label\tModel\tTileSet\tTemplateResRef\tStringRefGame\tBlockSight\tVisibleModel\tSoundAppType\n") - f.write("0\tGeneric\t****\t****\t****\t66717\t****\t****\t****\n") - f.write("1\tWall1Door\tTTR_UDoor_01\tTTR01\tnw_door_ttr_01\t63491\t1\t1\t1\n") - for row_id in range(2, 5200): - f.write(f"{row_id}\t****\t****\t****\t****\t****\t****\t****\t****\n") - f.write("5200\t\"Necropolis: Main Door\"\tmst01_door_01\tmst01\tnw_door_metal\t66717\t1\t1\t9\n") -with open(os.path.join(tlk, "sow_tlk.tlk"), "wb") as f: - f.write(b"TLK") -`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "doortypes", "base.json"), `{ - "output": "doortypes.2da", - "columns": ["Label", "Model", "TileSet", "TemplateResRef", "StringRefGame", "BlockSight", "VisibleModel", "SoundAppType"], - "rows": [ - {"id": 0, "Label": "Generic", "Model": "****", "TileSet": "****", "TemplateResRef": "****", "StringRefGame": "66717", "BlockSight": "****", "VisibleModel": "****", "SoundAppType": "****"}, - {"id": 1, "Label": "Wall1Door", "Model": "TTR_UDoor_01", "TileSet": "TTR01", "TemplateResRef": "nw_door_ttr_01", "StringRefGame": "63491", "BlockSight": "1", "VisibleModel": "1", "SoundAppType": "1"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "doortypes", "lock.json"), `{ - "txpple:id_6000": 5200 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "doortypes", "modules", "add_tapr.json"), `{ - "entries": { - "txpple:id_6000": {"Label": "Necropolis: Main Door", "Model": "mst01_door_01", "TileSet": "mst01", "TemplateResRef": "nw_door_metal", "StringRefGame": 66717, "BlockSight": 1, "VisibleModel": 1, "SoundAppType": 9} - } -}`+"\n") - for _, name := range []string{"add_pld01.json", "add_sic11.json", "add_tdm01.json", "add_tdx01.json", "add_tei01.json", "add_tfm01.json"} { - writeFile(t, filepath.Join(root, "reference", "data", "doortypes", "modules", name), `{"entries": {}}`+"\n") - } - writeFile(t, filepath.Join(root, "topdata", "data", "doortypes", "base.json"), `{ - "output": "doortypes.2da", - "columns": ["Label", "Model", "TileSet", "TemplateResRef", "StringRefGame", "BlockSight", "VisibleModel", "SoundAppType"], - "rows": [ - {"id": 0, "Label": "Generic", "Model": "****", "TileSet": "****", "TemplateResRef": "****", "StringRefGame": "66717", "BlockSight": "****", "VisibleModel": "****", "SoundAppType": "****"}, - {"id": 1, "Label": "Wall1Door", "Model": "TTR_UDoor_01", "TileSet": "TTR01", "TemplateResRef": "nw_door_ttr_01", "StringRefGame": "63491", "BlockSight": "1", "VisibleModel": "1", "SoundAppType": "1"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "data", "doortypes", "lock.json"), `{ - "txpple:id_6000": 5200 -}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "data", "doortypes", "modules", "add_tapr.json"), `{ - "entries": { - "txpple:id_6000": {"Label": "Necropolis: Main Door", "Model": "mst01_door_01", "TileSet": "mst01", "TemplateResRef": "nw_door_metal", "StringRefGame": 66717, "BlockSight": 1, "VisibleModel": 1, "SoundAppType": 9} - } -}`+"\n") - for _, name := range []string{"add_pld01.json", "add_sic11.json", "add_tdm01.json", "add_tdx01.json", "add_tei01.json", "add_tfm01.json"} { - writeFile(t, filepath.Join(root, "topdata", "data", "doortypes", "modules", name), `{"entries": {}}`+"\n") - } - - if _, err := NormalizeProject(testProject(root)); err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - nativeResult, err := BuildNative(testProject(root), nil) - if err != nil { - t.Fatalf("BuildNative failed: %v", err) - } - referenceResult, err := BuildReference(testProject(root), nil) - if err != nil { - t.Fatalf("BuildReference failed: %v", err) - } - nativeBytes, err := os.ReadFile(filepath.Join(nativeResult.Output2DADir, "doortypes.2da")) - if err != nil { - t.Fatalf("read native doortypes.2da: %v", err) - } - referenceBytes, err := os.ReadFile(filepath.Join(referenceResult.Output2DADir, "doortypes.2da")) - if err != nil { - t.Fatalf("read reference doortypes.2da: %v", err) - } - if string(nativeBytes) != string(referenceBytes) { - t.Fatalf("doortypes output mismatch\nnative:\n%s\nreference:\n%s", string(nativeBytes), string(referenceBytes)) - } -} - -func TestNormalizeProjectImportsLegacyVisualeffects(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - - mkdirAll(t, filepath.Join(root, "reference", "data", "visualeffects", "modules", "accessories")) - writeFile(t, filepath.Join(root, "reference", "data", "visualeffects", "base.json"), `{ - "columns": ["Label", "Type_FD", "OrientWithGround", "Imp_HeadCon_Node", "Imp_Impact_Node", "Imp_Root_S_Node", "Imp_Root_M_Node", "Imp_Root_L_Node", "Imp_Root_H_Node", "ProgFX_Impact", "SoundImpact", "ProgFX_Duration", "SoundDuration", "ProgFX_Cessation", "SoundCessastion", "Ces_HeadCon_Node", "Ces_Impact_Node", "Ces_Root_S_Node", "Ces_Root_M_Node", "Ces_Root_L_Node", "Ces_Root_H_Node", "ShakeType", "ShakeDelay", "ShakeDuration", "LowViolence", "LowQuality", "OrientWithObject"], - "rows": [ - {"key": "visualeffects:fnf_blinddeaf", "id": 18, "Label": "VFX_FNF_BLINDDEAF", "Type_FD": "F", "OrientWithGround": "0", "Imp_HeadCon_Node": "****", "Imp_Impact_Node": "****", "Imp_Root_S_Node": "****", "Imp_Root_M_Node": "****", "Imp_Root_L_Node": "****", "Imp_Root_H_Node": "****", "ProgFX_Impact": "****", "SoundImpact": "****", "ProgFX_Duration": "****", "SoundDuration": "****", "ProgFX_Cessation": "****", "SoundCessastion": "****", "Ces_HeadCon_Node": "****", "Ces_Impact_Node": "****", "Ces_Root_S_Node": "****", "Ces_Root_M_Node": "****", "Ces_Root_L_Node": "****", "Ces_Root_H_Node": "****", "ShakeType": "****", "ShakeDelay": "****", "ShakeDuration": "****", "LowViolence": "****", "LowQuality": "****", "OrientWithObject": "0"}, - {"key": "visualeffects:com_hitnegative", "id": 288, "Label": "VFX_COM_HIT_NEGATIVE", "Type_FD": "F", "OrientWithGround": "0", "Imp_HeadCon_Node": "****", "Imp_Impact_Node": "****", "Imp_Root_S_Node": "****", "Imp_Root_M_Node": "****", "Imp_Root_L_Node": "****", "Imp_Root_H_Node": "****", "ProgFX_Impact": "****", "SoundImpact": "old_sound", "ProgFX_Duration": "****", "SoundDuration": "****", "ProgFX_Cessation": "****", "SoundCessastion": "****", "Ces_HeadCon_Node": "****", "Ces_Impact_Node": "****", "Ces_Root_S_Node": "****", "Ces_Root_M_Node": "****", "Ces_Root_L_Node": "****", "Ces_Root_H_Node": "****", "ShakeType": "****", "ShakeDelay": "****", "ShakeDuration": "****", "LowViolence": "****", "LowQuality": "****", "OrientWithObject": "0"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "visualeffects", "lock.json"), `{ - "visualeffects:chatbubble": 10100, - "visualeffects:head_accessories_antlers_l": 10101, - "visualeffects:vcm_hitmasscrit": 10125, - "visualeffects:com_hitnegative": 288, - "visualeffects:fnf_blinddeaf": 18 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "visualeffects", "modules", "chatbubble.json"), `{ - "entries": { - "visualeffects:chatbubble": { - "Label": "VFX_DUR_CHAT_BUBBLE", - "Type_FD": "D", - "OrientWithGround": "0", - "Imp_HeadCon_Node": "vdr_chatbubble", - "Imp_Impact_Node": "****", - "Imp_Root_S_Node": "****", - "Imp_Root_M_Node": "****", - "Imp_Root_L_Node": "****", - "Imp_Root_H_Node": "****", - "ProgFX_Impact": "****", - "SoundImpact": "****", - "ProgFX_Duration": "****", - "SoundDuration": "****", - "ProgFX_Cessation": "****", - "SoundCessastion": "****", - "Ces_HeadCon_Node": "****", - "Ces_Impact_Node": "****", - "Ces_Root_S_Node": "****", - "Ces_Root_M_Node": "****", - "Ces_Root_L_Node": "****", - "Ces_Root_H_Node": "****", - "ShakeType": "****", - "ShakeDelay": "****", - "ShakeDuration": "****", - "LowViolence": "****", - "LowQuality": "****", - "OrientWithObject": "0" - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "visualeffects", "modules", "add_onhitvfx.json"), `{ - "entries": { - "visualeffects:vcm_hitmasscrit": { - "Label": "VFX_COM_HIT_MASSIVE_CRITICALS", - "Type_FD": "F", - "OrientWithGround": 0, - "Imp_Impact_Node": "vcm_hitmasscrit", - "OrientWithObject": 0 - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "visualeffects", "modules", "fix_comhitnegative.json"), `{ - "overrides": [ - {"key": "visualeffects:com_hitnegative", "id": 288, "SoundImpact": "scm_hitsmite"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "visualeffects", "modules", "ovr_lowqualitymodels.json"), `{ - "overrides": [ - {"key": "visualeffects:fnf_blinddeaf", "id": 18, "LowQuality": "vff_explblind_l"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "visualeffects", "modules", "accessories", "add_eos.json"), `{ - "entries": { - "visualeffects:head_accessories_antlers_l": { - "Label": "head_accessories_ANTLERS_L", - "Type_FD": "D", - "OrientWithGround": "0", - "Imp_HeadCon_Node": "hfx_antlers_l", - "Imp_Impact_Node": "****", - "Imp_Root_S_Node": "****", - "Imp_Root_M_Node": "****", - "Imp_Root_L_Node": "****", - "Imp_Root_H_Node": "****", - "ProgFX_Impact": "****", - "SoundImpact": "****", - "ProgFX_Duration": "****", - "SoundDuration": "****", - "ProgFX_Cessation": "****", - "SoundCessastion": "****", - "Ces_HeadCon_Node": "****", - "Ces_Impact_Node": "****", - "Ces_Root_S_Node": "****", - "Ces_Root_M_Node": "****", - "Ces_Root_L_Node": "****", - "Ces_Root_H_Node": "****", - "ShakeType": "****", - "ShakeDelay": "****", - "ShakeDuration": "****", - "LowViolence": "****", - "LowQuality": "****", - "OrientWithObject": "0" - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles < 6 { - t.Fatalf("expected visualeffects files to be imported, got %#v", result) - } - - for _, path := range []string{ - filepath.Join(root, "topdata", "data", "visualeffects", "base.json"), - filepath.Join(root, "topdata", "data", "visualeffects", "lock.json"), - filepath.Join(root, "topdata", "data", "visualeffects", "modules", "add_onhitvfx.json"), - filepath.Join(root, "topdata", "data", "visualeffects", "modules", "chatbubble.json"), - filepath.Join(root, "topdata", "data", "visualeffects", "modules", "fix_comhitnegative.json"), - filepath.Join(root, "topdata", "data", "visualeffects", "modules", "ovr_lowqualitymodels.json"), - filepath.Join(root, "topdata", "data", "visualeffects", "modules", "accessories", "add_eos.json"), - } { - if _, err := os.Stat(path); err != nil { - t.Fatalf("expected imported visualeffects path %s: %v", path, err) - } - } - - baseRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "visualeffects", "base.json")) - if err != nil { - t.Fatalf("read visualeffects base: %v", err) - } - if !strings.Contains(string(baseRaw), `"output": "visualeffects.2da"`) { - t.Fatalf("expected imported visualeffects output name, got:\n%s", string(baseRaw)) - } - lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "visualeffects", "lock.json")) - if err != nil { - t.Fatalf("read visualeffects lock: %v", err) - } - lockText := string(lockRaw) - if !strings.Contains(lockText, `"visualeffects:chatbubble": 10100`) || !strings.Contains(lockText, `"visualeffects:head_accessories_antlers_l": 10101`) { - t.Fatalf("expected imported visualeffects lock IDs, got:\n%s", lockText) - } -} - func TestBuildSupportsCanonicalVisualeffects(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "visualeffects", "modules", "accessories")) @@ -7432,163 +5005,6 @@ func TestBuildSupportsCanonicalVisualeffects(t *testing.T) { } } -func TestBuildCanonicalVisualeffectsMatchesReference(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data", "visualeffects", "modules", "accessories")) - mkdirAll(t, filepath.Join(root, "reference", "data", "visualeffects", "modules", "accessories")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), `#!/usr/bin/env python3 -import os, sys -out = None -tlk = None -args = sys.argv[1:] -for i, arg in enumerate(args): - if arg == "--out": - out = args[i + 1] - if arg == "--tlk-dir": - tlk = args[i + 1] -os.makedirs(out, exist_ok=True) -os.makedirs(tlk, exist_ok=True) -with open(os.path.join(out, "visualeffects.2da"), "w", encoding="utf-8") as f: - f.write("2DA V2.0\n\n") - f.write("Label\tType_FD\tOrientWithGround\tImp_HeadCon_Node\tImp_Impact_Node\tImp_Root_S_Node\tImp_Root_M_Node\tImp_Root_L_Node\tImp_Root_H_Node\tProgFX_Impact\tSoundImpact\tProgFX_Duration\tSoundDuration\tProgFX_Cessation\tSoundCessastion\tCes_HeadCon_Node\tCes_Impact_Node\tCes_Root_S_Node\tCes_Root_M_Node\tCes_Root_L_Node\tCes_Root_H_Node\tShakeType\tShakeDelay\tShakeDuration\tLowViolence\tLowQuality\tOrientWithObject\n") - f.write("18\tVFX_FNF_BLINDDEAF\tF\t0\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\tvff_explblind_l\t0\n") - f.write("288\tVFX_COM_HIT_NEGATIVE\tF\t0\t****\t****\t****\t****\t****\t****\tscm_hitsmite\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t0\n") - f.write("10100\tVFX_DUR_CHAT_BUBBLE\tD\t0\tvdr_chatbubble\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t0\n") - f.write("10101\thead_accessories_ANTLERS_L\tD\t0\thfx_antlers_l\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t0\n") - f.write("10125\tVFX_COM_HIT_MASSIVE_CRITICALS\tF\t0\t****\tvcm_hitmasscrit\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t0\n") -with open(os.path.join(tlk, "sow_tlk.tlk"), "wb") as f: - f.write(b"TLK") -`+"\n") - for _, target := range []string{"topdata", "reference"} { - writeFile(t, filepath.Join(root, target, "data", "visualeffects", "base.json"), `{ - "output": "visualeffects.2da", - "columns": ["Label", "Type_FD", "OrientWithGround", "Imp_HeadCon_Node", "Imp_Impact_Node", "Imp_Root_S_Node", "Imp_Root_M_Node", "Imp_Root_L_Node", "Imp_Root_H_Node", "ProgFX_Impact", "SoundImpact", "ProgFX_Duration", "SoundDuration", "ProgFX_Cessation", "SoundCessastion", "Ces_HeadCon_Node", "Ces_Impact_Node", "Ces_Root_S_Node", "Ces_Root_M_Node", "Ces_Root_L_Node", "Ces_Root_H_Node", "ShakeType", "ShakeDelay", "ShakeDuration", "LowViolence", "LowQuality", "OrientWithObject"], - "rows": [ - {"key": "visualeffects:fnf_blinddeaf", "id": 18, "Label": "VFX_FNF_BLINDDEAF", "Type_FD": "F", "OrientWithGround": "0", "Imp_HeadCon_Node": "****", "Imp_Impact_Node": "****", "Imp_Root_S_Node": "****", "Imp_Root_M_Node": "****", "Imp_Root_L_Node": "****", "Imp_Root_H_Node": "****", "ProgFX_Impact": "****", "SoundImpact": "****", "ProgFX_Duration": "****", "SoundDuration": "****", "ProgFX_Cessation": "****", "SoundCessastion": "****", "Ces_HeadCon_Node": "****", "Ces_Impact_Node": "****", "Ces_Root_S_Node": "****", "Ces_Root_M_Node": "****", "Ces_Root_L_Node": "****", "Ces_Root_H_Node": "****", "ShakeType": "****", "ShakeDelay": "****", "ShakeDuration": "****", "LowViolence": "****", "LowQuality": "****", "OrientWithObject": "0"}, - {"key": "visualeffects:com_hitnegative", "id": 288, "Label": "VFX_COM_HIT_NEGATIVE", "Type_FD": "F", "OrientWithGround": "0", "Imp_HeadCon_Node": "****", "Imp_Impact_Node": "****", "Imp_Root_S_Node": "****", "Imp_Root_M_Node": "****", "Imp_Root_L_Node": "****", "Imp_Root_H_Node": "****", "ProgFX_Impact": "****", "SoundImpact": "old_sound", "ProgFX_Duration": "****", "SoundDuration": "****", "ProgFX_Cessation": "****", "SoundCessastion": "****", "Ces_HeadCon_Node": "****", "Ces_Impact_Node": "****", "Ces_Root_S_Node": "****", "Ces_Root_M_Node": "****", "Ces_Root_L_Node": "****", "Ces_Root_H_Node": "****", "ShakeType": "****", "ShakeDelay": "****", "ShakeDuration": "****", "LowViolence": "****", "LowQuality": "****", "OrientWithObject": "0"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, target, "data", "visualeffects", "lock.json"), `{ - "visualeffects:chatbubble": 10100, - "visualeffects:head_accessories_antlers_l": 10101, - "visualeffects:vcm_hitmasscrit": 10125, - "visualeffects:com_hitnegative": 288, - "visualeffects:fnf_blinddeaf": 18 -}`+"\n") - writeFile(t, filepath.Join(root, target, "data", "visualeffects", "modules", "chatbubble.json"), `{ - "entries": { - "visualeffects:chatbubble": { - "Label": "VFX_DUR_CHAT_BUBBLE", - "Type_FD": "D", - "OrientWithGround": "0", - "Imp_HeadCon_Node": "vdr_chatbubble", - "Imp_Impact_Node": "****", - "Imp_Root_S_Node": "****", - "Imp_Root_M_Node": "****", - "Imp_Root_L_Node": "****", - "Imp_Root_H_Node": "****", - "ProgFX_Impact": "****", - "SoundImpact": "****", - "ProgFX_Duration": "****", - "SoundDuration": "****", - "ProgFX_Cessation": "****", - "SoundCessastion": "****", - "Ces_HeadCon_Node": "****", - "Ces_Impact_Node": "****", - "Ces_Root_S_Node": "****", - "Ces_Root_M_Node": "****", - "Ces_Root_L_Node": "****", - "Ces_Root_H_Node": "****", - "ShakeType": "****", - "ShakeDelay": "****", - "ShakeDuration": "****", - "LowViolence": "****", - "LowQuality": "****", - "OrientWithObject": "0" - } - } -}`+"\n") - writeFile(t, filepath.Join(root, target, "data", "visualeffects", "modules", "add_onhitvfx.json"), `{ - "entries": { - "visualeffects:vcm_hitmasscrit": { - "Label": "VFX_COM_HIT_MASSIVE_CRITICALS", - "Type_FD": "F", - "OrientWithGround": 0, - "Imp_Impact_Node": "vcm_hitmasscrit", - "OrientWithObject": 0 - } - } -}`+"\n") - writeFile(t, filepath.Join(root, target, "data", "visualeffects", "modules", "fix_comhitnegative.json"), `{ - "overrides": [ - {"key": "visualeffects:com_hitnegative", "id": 288, "SoundImpact": "scm_hitsmite"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, target, "data", "visualeffects", "modules", "ovr_lowqualitymodels.json"), `{ - "overrides": [ - {"key": "visualeffects:fnf_blinddeaf", "id": 18, "LowQuality": "vff_explblind_l"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, target, "data", "visualeffects", "modules", "accessories", "add_eos.json"), `{ - "entries": { - "visualeffects:head_accessories_antlers_l": { - "Label": "head_accessories_ANTLERS_L", - "Type_FD": "D", - "OrientWithGround": "0", - "Imp_HeadCon_Node": "hfx_antlers_l", - "Imp_Impact_Node": "****", - "Imp_Root_S_Node": "****", - "Imp_Root_M_Node": "****", - "Imp_Root_L_Node": "****", - "Imp_Root_H_Node": "****", - "ProgFX_Impact": "****", - "SoundImpact": "****", - "ProgFX_Duration": "****", - "SoundDuration": "****", - "ProgFX_Cessation": "****", - "SoundCessastion": "****", - "Ces_HeadCon_Node": "****", - "Ces_Impact_Node": "****", - "Ces_Root_S_Node": "****", - "Ces_Root_M_Node": "****", - "Ces_Root_L_Node": "****", - "Ces_Root_H_Node": "****", - "ShakeType": "****", - "ShakeDelay": "****", - "ShakeDuration": "****", - "LowViolence": "****", - "LowQuality": "****", - "OrientWithObject": "0" - } - } -}`+"\n") - } - - if _, err := NormalizeProject(testProject(root)); err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - nativeResult, err := BuildNative(testProject(root), nil) - if err != nil { - t.Fatalf("BuildNative failed: %v", err) - } - referenceResult, err := BuildReference(testProject(root), nil) - if err != nil { - t.Fatalf("BuildReference failed: %v", err) - } - nativeBytes, err := os.ReadFile(filepath.Join(nativeResult.Output2DADir, "visualeffects.2da")) - if err != nil { - t.Fatalf("read native visualeffects.2da: %v", err) - } - referenceBytes, err := os.ReadFile(filepath.Join(referenceResult.Output2DADir, "visualeffects.2da")) - if err != nil { - t.Fatalf("read reference visualeffects.2da: %v", err) - } - if string(nativeBytes) != string(referenceBytes) { - t.Fatalf("visualeffects output mismatch\nnative:\n%s\nreference:\n%s", string(nativeBytes), string(referenceBytes)) - } -} - func TestApplyAutogenConsumersAugmentsPartsFromLocalOverride(t *testing.T) { root := testProjectRoot(t) overrideRoot := filepath.Join(root, "autogen-assets") @@ -8001,87 +5417,6 @@ func TestBuildGenerated2DAAssetsWritesLockfilesForSelectedDatasets(t *testing.T) } } -func TestNormalizeProjectImportsLegacyLoadscreens(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - mkdirAll(t, filepath.Join(root, "reference", "data", "loadscreens", "modules")) - writeFile(t, filepath.Join(root, "reference", "data", "loadscreens", "base.json"), `{ - "columns": ["Label", "ScriptingName", "BMPResRef", "TileSet", "StrRef"], - "rows": [ - {"id": 0, "Label": "Random", "ScriptingName": "****", "BMPResRef": "****", "TileSet": "****", "StrRef": "63402"}, - {"id": 333, "Label": "Daggerford12", "ScriptingName": "AREA_TRANSITION_DAG_12", "BMPResRef": "LS_DAG_12", "TileSet": "TMS01", "StrRef": "****"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "loadscreens", "lock.json"), `{ - "loadscreens:vdungeon01": 334, - "loadscreens:tcd_01": 335, - "loadscreens:ori_01": 336, - "loadscreens:ori_02": 337, - "loadscreens:ori_03": 338, - "loadscreens:ori_04": 339, - "loadscreens:ori_05": 340 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "loadscreens", "modules", "add_lsvd.json"), `{ - "entries": { - "loadscreens:vdungeon01": { - "Label": "VDungeon01", - "ScriptingName": "AREA_TRANSITION_VDUNGEON_01", - "BMPResRef": "LS_VD_01", - "TileSet": "TVD" - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "loadscreens", "modules", "add_ori.json"), `{ - "entries": { - "loadscreens:ori_01": { - "Label": "Bamboo", - "ScriptingName": "AREA_TRANSITION_RURAL_06", - "BMPResRef": "LS_Ori_01", - "TileSet": "TTR01" - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "loadscreens", "modules", "add_tcd01.json"), `{ - "entries": { - "loadscreens:tcd_01": { - "Label": "cd01", - "ScriptingName": "AREA_TRANSITION_CDUNGEON_01", - "BMPResRef": "ls_tcd_01", - "TileSet": "TCD01", - "StrRef": 68555 - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles < 5 { - t.Fatalf("expected loadscreens files to be imported, got %#v", result) - } - for _, path := range []string{ - filepath.Join(root, "topdata", "data", "loadscreens", "base.json"), - filepath.Join(root, "topdata", "data", "loadscreens", "lock.json"), - filepath.Join(root, "topdata", "data", "loadscreens", "modules", "add_lsvd.json"), - filepath.Join(root, "topdata", "data", "loadscreens", "modules", "add_ori.json"), - filepath.Join(root, "topdata", "data", "loadscreens", "modules", "add_tcd01.json"), - } { - if _, err := os.Stat(path); err != nil { - t.Fatalf("expected imported loadscreens path %s: %v", path, err) - } - } - lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "loadscreens", "lock.json")) - if err != nil { - t.Fatalf("read loadscreens lock: %v", err) - } - if !strings.Contains(string(lockRaw), `"loadscreens:ori_05": 340`) { - t.Fatalf("expected imported loadscreens lock IDs, got:\n%s", string(lockRaw)) - } -} - func TestBuildSupportsCanonicalLoadscreens(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "loadscreens", "modules")) @@ -8125,167 +5460,6 @@ func TestBuildSupportsCanonicalLoadscreens(t *testing.T) { } } -func TestBuildCanonicalLoadscreensMatchesReference(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - mkdirAll(t, filepath.Join(root, "reference", "data", "loadscreens", "modules")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), `#!/usr/bin/env python3 -import os, sys -out = None -tlk = None -args = sys.argv[1:] -for i, arg in enumerate(args): - if arg == "--out": - out = args[i + 1] - if arg == "--tlk-dir": - tlk = args[i + 1] -os.makedirs(out, exist_ok=True) -os.makedirs(tlk, exist_ok=True) -with open(os.path.join(out, "loadscreens.2da"), "w", encoding="utf-8") as f: - f.write("2DA V2.0\n\n") - f.write("Label\tScriptingName\tBMPResRef\tTileSet\tStrRef\n") - f.write("0\tRandom\t****\t****\t****\t63402\n") - for row_id in range(1, 334): - f.write(f"{row_id}\t****\t****\t****\t****\t****\n") - f.write("334\tVDungeon01\tAREA_TRANSITION_VDUNGEON_01\tLS_VD_01\tTVD\t****\n") - f.write("335\tcd01\tAREA_TRANSITION_CDUNGEON_01\tls_tcd_01\tTCD01\t68555\n") -with open(os.path.join(tlk, "sow_tlk.tlk"), "wb") as f: - f.write(b"TLK") -`) - writeFile(t, filepath.Join(root, "reference", "data", "loadscreens", "base.json"), `{ - "columns": ["Label", "ScriptingName", "BMPResRef", "TileSet", "StrRef"], - "rows": [ - {"id": 0, "Label": "Random", "ScriptingName": "****", "BMPResRef": "****", "TileSet": "****", "StrRef": "63402"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "loadscreens", "lock.json"), `{ - "loadscreens:vdungeon01": 334, - "loadscreens:tcd_01": 335 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "loadscreens", "modules", "add_lsvd.json"), `{ - "entries": { - "loadscreens:vdungeon01": { - "Label": "VDungeon01", - "ScriptingName": "AREA_TRANSITION_VDUNGEON_01", - "BMPResRef": "LS_VD_01", - "TileSet": "TVD" - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "loadscreens", "modules", "add_ori.json"), `{"entries": {}}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "loadscreens", "modules", "add_tcd01.json"), `{ - "entries": { - "loadscreens:tcd_01": { - "Label": "cd01", - "ScriptingName": "AREA_TRANSITION_CDUNGEON_01", - "BMPResRef": "ls_tcd_01", - "TileSet": "TCD01", - "StrRef": 68555 - } - } -}`+"\n") - - if _, err := NormalizeProject(testProject(root)); err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - nativeResult, err := BuildNative(testProject(root), nil) - if err != nil { - t.Fatalf("BuildNative failed: %v", err) - } - referenceResult, err := BuildReference(testProject(root), nil) - if err != nil { - t.Fatalf("BuildReference failed: %v", err) - } - nativeBytes, err := os.ReadFile(filepath.Join(nativeResult.Output2DADir, "loadscreens.2da")) - if err != nil { - t.Fatalf("read native loadscreens.2da: %v", err) - } - referenceBytes, err := os.ReadFile(filepath.Join(referenceResult.Output2DADir, "loadscreens.2da")) - if err != nil { - t.Fatalf("read reference loadscreens.2da: %v", err) - } - if string(nativeBytes) != string(referenceBytes) { - t.Fatalf("loadscreens output mismatch\nnative:\n%s\nreference:\n%s", string(nativeBytes), string(referenceBytes)) - } -} - -func TestNormalizeImportsLegacyGenericdoors(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata")) - mkdirAll(t, filepath.Join(root, "reference", "data", "genericdoors", "modules")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "data", "genericdoors", "base.json"), `{ - "columns": ["Label", "StrRef", "ModelName", "BlockSight", "VisibleModel", "SoundAppType", "Name"], - "rows": [ - {"id": 0, "Label": "Wood_strong", "StrRef": "5349", "ModelName": "T_DOOR01", "BlockSight": "1", "VisibleModel": "1", "SoundAppType": "0", "Name": "63750"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "genericdoors", "lock.json"), `{ - "babayaga:hs_door60": 3200, - "orientaltileset:t_Door12": 3202 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "genericdoors", "modules", "add_mb017.json"), `{ - "entries": { - "babayaga:hs_door60": { - "Label": "Door60", - "StrRef": "****", - "ModelName": "hs_door60", - "BlockSight": "1", - "VisibleModel": "1", - "SoundAppType": "0", - "Name": "****" - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "genericdoors", "modules", "add_ori.json"), `{ - "entries": { - "orientaltileset:t_Door12": { - "Label": "Door12", - "StrRef": "5349", - "ModelName": "t_Door12", - "BlockSight": "1", - "VisibleModel": "1", - "SoundAppType": "0", - "Name": "14445" - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles < 4 { - t.Fatalf("expected genericdoors files to be imported, got %#v", result) - } - for _, path := range []string{ - filepath.Join(root, "topdata", "data", "genericdoors", "base.json"), - filepath.Join(root, "topdata", "data", "genericdoors", "lock.json"), - filepath.Join(root, "topdata", "data", "genericdoors", "modules", "add_mb017.json"), - filepath.Join(root, "topdata", "data", "genericdoors", "modules", "add_ori.json"), - } { - if _, err := os.Stat(path); err != nil { - t.Fatalf("expected imported genericdoors path %s: %v", path, err) - } - } - baseRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "genericdoors", "base.json")) - if err != nil { - t.Fatalf("read genericdoors base: %v", err) - } - if !strings.Contains(string(baseRaw), `"output": "genericdoors.2da"`) { - t.Fatalf("expected imported genericdoors output name, got:\n%s", string(baseRaw)) - } - lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "genericdoors", "lock.json")) - if err != nil { - t.Fatalf("read genericdoors lock: %v", err) - } - if !strings.Contains(string(lockRaw), `"orientaltileset:t_Door12": 3202`) { - t.Fatalf("expected imported genericdoors lock IDs, got:\n%s", string(lockRaw)) - } -} - func TestBuildSupportsCanonicalGenericdoors(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "genericdoors", "modules")) @@ -8332,197 +5506,6 @@ func TestBuildSupportsCanonicalGenericdoors(t *testing.T) { } } -func TestBuildCanonicalGenericdoorsMatchesReference(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - mkdirAll(t, filepath.Join(root, "reference", "data", "genericdoors", "modules")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), `#!/usr/bin/env python3 -import os, sys -out = None -tlk = None -args = sys.argv[1:] -for i, arg in enumerate(args): - if arg == "--out": - out = args[i + 1] - if arg == "--tlk-dir": - tlk = args[i + 1] -os.makedirs(out, exist_ok=True) -os.makedirs(tlk, exist_ok=True) -with open(os.path.join(out, "genericdoors.2da"), "w", encoding="utf-8") as f: - f.write("2DA V2.0\n\n") - f.write("Label\tStrRef\tModelName\tBlockSight\tVisibleModel\tSoundAppType\tName\n") - f.write("0\tWood_strong\t5349\tT_DOOR01\t1\t1\t0\t63750\n") - for row_id in range(1, 3200): - f.write(f"{row_id}\t****\t****\t****\t****\t****\t****\t****\n") - f.write("3200\tDoor60\t****\ths_door60\t1\t1\t0\t****\n") - f.write("3201\t****\t****\t****\t****\t****\t****\t****\n") - f.write("3202\tDoor12\t5349\tt_Door12\t1\t1\t0\t14445\n") -with open(os.path.join(tlk, "sow_tlk.tlk"), "wb") as f: - f.write(b"TLK") -`) - writeFile(t, filepath.Join(root, "reference", "data", "genericdoors", "base.json"), `{ - "columns": ["Label", "StrRef", "ModelName", "BlockSight", "VisibleModel", "SoundAppType", "Name"], - "rows": [ - {"id": 0, "Label": "Wood_strong", "StrRef": "5349", "ModelName": "T_DOOR01", "BlockSight": "1", "VisibleModel": "1", "SoundAppType": "0", "Name": "63750"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "genericdoors", "lock.json"), `{ - "babayaga:hs_door60": 3200, - "orientaltileset:t_Door12": 3202 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "genericdoors", "modules", "add_mb017.json"), `{ - "entries": { - "babayaga:hs_door60": { - "Label": "Door60", - "StrRef": "****", - "ModelName": "hs_door60", - "BlockSight": "1", - "VisibleModel": "1", - "SoundAppType": "0", - "Name": "****" - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "genericdoors", "modules", "add_ori.json"), `{ - "entries": { - "orientaltileset:t_Door12": { - "Label": "Door12", - "StrRef": "5349", - "ModelName": "t_Door12", - "BlockSight": "1", - "VisibleModel": "1", - "SoundAppType": "0", - "Name": "14445" - } - } -}`+"\n") - - if _, err := NormalizeProject(testProject(root)); err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - nativeResult, err := BuildNative(testProject(root), nil) - if err != nil { - t.Fatalf("BuildNative failed: %v", err) - } - referenceResult, err := BuildReference(testProject(root), nil) - if err != nil { - t.Fatalf("BuildReference failed: %v", err) - } - nativeBytes, err := os.ReadFile(filepath.Join(nativeResult.Output2DADir, "genericdoors.2da")) - if err != nil { - t.Fatalf("read native genericdoors.2da: %v", err) - } - referenceBytes, err := os.ReadFile(filepath.Join(referenceResult.Output2DADir, "genericdoors.2da")) - if err != nil { - t.Fatalf("read reference genericdoors.2da: %v", err) - } - if string(nativeBytes) != string(referenceBytes) { - t.Fatalf("genericdoors output mismatch\nnative:\n%s\nreference:\n%s", string(nativeBytes), string(referenceBytes)) - } -} - -func TestNormalizeProjectImportsLegacyBaseitems(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata")) - mkdirAll(t, filepath.Join(root, "reference", "data", "baseitems", "modules")) - mkdirAll(t, filepath.Join(root, "reference", "tlk")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "data", "baseitems", "base.json"), `{ - "columns": ["Name", "label", "MaxRange"], - "rows": [ - {"key": "baseitems:shortsword", "id": 0, "Name": "106", "label": "shortsword", "MaxRange": "100"}, - {"key": "baseitems:helmet", "id": 17, "Name": "500", "label": "helmet", "MaxRange": "100"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "baseitems", "lock.json"), `{ - "baseitems:shortsword": 0, - "baseitems:helmet": 17, - "baseitems:coin": 113 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "baseitems", "modules", "blunderbuss.json"), `{"entries": {}}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "baseitems", "modules", "coin.json"), `{ - "entries": { - "baseitems:coin": { - "Name": { "tlk": "baseitems:coin.name" }, - "label": "Coin", - "MaxRange": "100" - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "baseitems", "modules", "estoc.json"), `{"entries": {}}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "baseitems", "modules", "heavymace.json"), `{"entries": {}}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "baseitems", "modules", "ovr_baseitems.json"), `{ - "overrides": [ - {"key": "baseitems:shortsword", "id": 0, "label": "shortsword"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "baseitems", "modules", "ovr_cloak255.json"), `{"overrides": []}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "baseitems", "modules", "ovr_helmet255.json"), `{ - "overrides": [ - {"key": "baseitems:helmet", "id": 17, "MaxRange": "255"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "baseitems", "modules", "maulandfalchion.json"), `{"entries": {}}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "baseitems", "modules", "shortspear.json"), `{"entries": {}}`+"\n") - writeFile(t, filepath.Join(root, "reference", "tlk", "base.json"), `{ - "language": "en", - "entries": { - "baseitems:coin.name": { "text": "Coin" } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "tlk", "lock.json"), `{ - "baseitems:coin.name": 70000 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles < 10 { - t.Fatalf("expected baseitems files to be imported, got %#v", result) - } - for _, path := range []string{ - filepath.Join(root, "topdata", "data", "baseitems", "base.json"), - filepath.Join(root, "topdata", "data", "baseitems", "lock.json"), - filepath.Join(root, "topdata", "data", "baseitems", "modules", "blunderbuss.json"), - filepath.Join(root, "topdata", "data", "baseitems", "modules", "coin.json"), - filepath.Join(root, "topdata", "data", "baseitems", "modules", "estoc.json"), - filepath.Join(root, "topdata", "data", "baseitems", "modules", "heavymace.json"), - filepath.Join(root, "topdata", "data", "baseitems", "modules", "maulandfalchion.json"), - filepath.Join(root, "topdata", "data", "baseitems", "modules", "ovr_baseitems.json"), - filepath.Join(root, "topdata", "data", "baseitems", "modules", "ovr_cloak255.json"), - filepath.Join(root, "topdata", "data", "baseitems", "modules", "ovr_helmet255.json"), - filepath.Join(root, "topdata", "data", "baseitems", "modules", "shortspear.json"), - } { - if _, err := os.Stat(path); err != nil { - t.Fatalf("expected imported baseitems path %s: %v", path, err) - } - } - baseRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "baseitems", "base.json")) - if err != nil { - t.Fatalf("read baseitems base: %v", err) - } - if !strings.Contains(string(baseRaw), `"output": "baseitems.2da"`) { - t.Fatalf("expected imported baseitems output name, got:\n%s", string(baseRaw)) - } - lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "baseitems", "lock.json")) - if err != nil { - t.Fatalf("read baseitems lock: %v", err) - } - if !strings.Contains(string(lockRaw), `"baseitems:coin": 113`) { - t.Fatalf("expected imported baseitems lock IDs, got:\n%s", string(lockRaw)) - } - moduleRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "baseitems", "modules", "coin.json")) - if err != nil { - t.Fatalf("read baseitems coin module: %v", err) - } - if !strings.Contains(string(moduleRaw), `"text": "Coin"`) { - t.Fatalf("expected imported baseitems module tlk to be inlined, got:\n%s", string(moduleRaw)) - } -} - func TestBuildSupportsCanonicalBaseitems(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "baseitems", "modules")) @@ -9262,108 +6245,6 @@ func TestBuildOverrideAsteriskKeyRemovesIdentityWithoutNullingRow(t *testing.T) } } -func TestBuildCanonicalBaseitemsMatchesReference(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - mkdirAll(t, filepath.Join(root, "reference", "data", "baseitems", "modules")) - mkdirAll(t, filepath.Join(root, "reference", "tlk")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), `#!/usr/bin/env python3 -import os, sys -out = None -tlk = None -args = sys.argv[1:] -for i, arg in enumerate(args): - if arg == "--out": - out = args[i + 1] - if arg == "--tlk-dir": - tlk = args[i + 1] -os.makedirs(out, exist_ok=True) -os.makedirs(tlk, exist_ok=True) -with open(os.path.join(out, "baseitems.2da"), "w", encoding="utf-8") as f: - f.write("2DA V2.0\\n\\n") - f.write("Name\\tlabel\\tMaxRange\\n") - f.write("0\\t106\\tshortsword\\t100\\n") - for row_id in range(1, 17): - f.write(f"{row_id}\\t****\\t****\\t****\\n") - f.write("17\\t500\\thelmet\\t255\\n") - for row_id in range(18, 113): - f.write(f"{row_id}\\t****\\t****\\t****\\n") - f.write("113\\tCoin\\tCoin\\t100\\n") -with open(os.path.join(tlk, "sow_tlk.tlk"), "wb") as f: - f.write(b"TLK") -`) - writeFile(t, filepath.Join(root, "reference", "data", "baseitems", "base.json"), `{ - "columns": ["Name", "label", "MaxRange"], - "rows": [ - {"key": "baseitems:shortsword", "id": 0, "Name": "106", "label": "shortsword", "MaxRange": "100"}, - {"key": "baseitems:helmet", "id": 17, "Name": "500", "label": "helmet", "MaxRange": "100"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "baseitems", "lock.json"), `{ - "baseitems:shortsword": 0, - "baseitems:helmet": 17, - "baseitems:coin": 113 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "baseitems", "modules", "blunderbuss.json"), `{"entries": {}}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "baseitems", "modules", "coin.json"), `{ - "entries": { - "baseitems:coin": { - "Name": { "tlk": "baseitems:coin.name" }, - "label": "Coin", - "MaxRange": "100" - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "baseitems", "modules", "estoc.json"), `{"entries": {}}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "baseitems", "modules", "heavymace.json"), `{"entries": {}}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "baseitems", "modules", "ovr_baseitems.json"), `{ - "overrides": [ - {"key": "baseitems:shortsword", "id": 0, "label": "shortsword"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "baseitems", "modules", "ovr_cloak255.json"), `{"overrides": []}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "baseitems", "modules", "ovr_helmet255.json"), `{ - "overrides": [ - {"key": "baseitems:helmet", "id": 17, "MaxRange": "255"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "baseitems", "modules", "maulandfalchion.json"), `{"entries": {}}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "baseitems", "modules", "shortspear.json"), `{"entries": {}}`+"\n") - writeFile(t, filepath.Join(root, "reference", "tlk", "base.json"), `{ - "language": "en", - "entries": { - "baseitems:coin.name": { "text": "Coin" } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "tlk", "lock.json"), `{ - "baseitems:coin.name": 70000 -}`+"\n") - - if _, err := NormalizeProject(testProject(root)); err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - nativeResult, err := BuildNative(testProject(root), nil) - if err != nil { - t.Fatalf("BuildNative failed: %v", err) - } - referenceResult, err := BuildReference(testProject(root), nil) - if err != nil { - t.Fatalf("BuildReference failed: %v", err) - } - nativeBytes, err := os.ReadFile(filepath.Join(nativeResult.Output2DADir, "baseitems.2da")) - if err != nil { - t.Fatalf("read native baseitems.2da: %v", err) - } - referenceBytes, err := os.ReadFile(filepath.Join(referenceResult.Output2DADir, "baseitems.2da")) - if err != nil { - t.Fatalf("read reference baseitems.2da: %v", err) - } - if string(nativeBytes) != string(referenceBytes) { - t.Fatalf("baseitems output mismatch\nnative:\n%s\nreference:\n%s", string(nativeBytes), string(referenceBytes)) - } -} - func TestBuildUsesReferenceLockIDsForUnmigratedKeyRefs(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "masterfeats")) @@ -10004,211 +6885,6 @@ func TestBuildNativeTreatsMetaCaseInsensitive(t *testing.T) { } } -func TestBuildCanonicalAppearanceMatchesReference(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - mkdirAll(t, filepath.Join(root, "reference", "data", "appearance", "modules")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), `#!/usr/bin/env python3 -import os, sys -out = None -tlk = None -args = sys.argv[1:] -for i, arg in enumerate(args): - if arg == "--out": - out = args[i + 1] - if arg == "--tlk-dir": - tlk = args[i + 1] -os.makedirs(out, exist_ok=True) -os.makedirs(tlk, exist_ok=True) -with open(os.path.join(out, "appearance.2da"), "w", encoding="utf-8") as f: - f.write("2DA V2.0\n\n") - f.write("LABEL\tPORTRAIT\n") - f.write("0\tDwarf\t****\n") - f.write("15105\tBlack Lagoon Reaver\tpo_cotbl\n") - f.write("15111\tBlack Lagoon Reaver, Swimming\tpo_cotbl\n") -with open(os.path.join(tlk, "sow_tlk.tlk"), "wb") as f: - f.write(b"TLK") -`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "appearance", "base.json"), `{ - "columns": ["LABEL", "PORTRAIT"], - "rows": [ - {"key": "appearance:dwarf", "id": 0, "LABEL": "Dwarf", "PORTRAIT": "****"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "appearance", "lock.json"), `{ - "appearance:blacklagoonreaver": 15105, - "appearance:blacklagoonreaverswimming": 15111 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "appearance", "modules", "cotblreaver.json"), `{ - "entries": { - "appearance:blacklagoonreaver": {"LABEL": "Black Lagoon Reaver", "PORTRAIT": "po_cotbl"}, - "appearance:blacklagoonreaverswimming": {"LABEL": "Black Lagoon Reaver, Swimming", "PORTRAIT": "po_cotbl"} - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "appearance", "modules", "crawlingclaw.json"), `{"entries": {}}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "appearance", "modules", "halfogre.json"), `{"entries": {}}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "appearance", "modules", "zombieknight.json"), `{"entries": {}}`+"\n") - - if _, err := NormalizeProject(testProject(root)); err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - nativeResult, err := BuildNative(testProject(root), nil) - if err != nil { - t.Fatalf("BuildNative failed: %v", err) - } - referenceResult, err := BuildReference(testProject(root), nil) - if err != nil { - t.Fatalf("BuildReference failed: %v", err) - } - nativeBytes, err := os.ReadFile(filepath.Join(nativeResult.Output2DADir, "appearance.2da")) - if err != nil { - t.Fatalf("read native appearance.2da: %v", err) - } - referenceBytes, err := os.ReadFile(filepath.Join(referenceResult.Output2DADir, "appearance.2da")) - if err != nil { - t.Fatalf("read reference appearance.2da: %v", err) - } - if string(nativeBytes) != string(referenceBytes) { - t.Fatalf("appearance output mismatch\nnative:\n%s\nreference:\n%s", string(nativeBytes), string(referenceBytes)) - } -} - -func TestNormalizeProjectImportsLegacyPlaceables(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - mkdirAll(t, filepath.Join(root, "reference", "data", "placeables", "modules")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "data", "placeables", "base.json"), `{ - "columns": ["Label", "StrRef", "ModelName", "LightColor", "LightOffsetX", "LightOffsetY", "LightOffsetZ", "SoundAppType", "ShadowSize", "BodyBag", "LowGore", "Reflection", "Static"], - "rows": [ - {"key": "placeables:armoire", "id": 0, "Label": "Armoire", "StrRef": "5645", "ModelName": "PLC_A01", "SoundAppType": "13", "ShadowSize": "1", "BodyBag": "0", "Static": "1"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "placeables", "lock.json"), `{ - "placeables:armoire": 0, - "placeables:tmp_crappile01": 16500 -}`+"\n") - for _, name := range []string{ - "add_ccfeb2019plcs.json", - "add_cepcarpets.json", - "add_cepfloors.json", - "add_cepnwn2library.json", - "add_nwic.json", - "add_ori.json", - "add_sic11.json", - "add_tapr.json", - "add_tdfloors.json", - "add_undecals.json", - "add_witcher1.json", - "add_witcher2.json", - } { - writeFile(t, filepath.Join(root, "reference", "data", "placeables", "modules", name), `{"entries": {}}`+"\n") - } - writeFile(t, filepath.Join(root, "reference", "data", "placeables", "modules", "add_avernostragarbage.json"), `{ - "entries": { - "placeables:tmp_crappile01": { - "Label": "Decoration: Pile 1 (Avernostra)", - "ModelName": "tmp_crappile01", - "Static": 1 - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "placeables", "modules", "ovr_vanillaplcsorts.json"), `{ - "overrides": [ - {"key": "placeables:armoire", "id": 0, "Label": "Furniture: Armoire (Bioware)", "StrRef": "****"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles < 16 { - t.Fatalf("expected placeables files to be imported, got %#v", result) - } - for _, path := range []string{ - filepath.Join(root, "topdata", "data", "placeables", "base.json"), - filepath.Join(root, "topdata", "data", "placeables", "lock.json"), - filepath.Join(root, "topdata", "data", "placeables", "modules", "add_avernostragarbage.json"), - filepath.Join(root, "topdata", "data", "placeables", "modules", "ovr_vanillaplcsorts.json"), - } { - if _, err := os.Stat(path); err != nil { - t.Fatalf("expected imported placeables path %s: %v", path, err) - } - } - baseRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "placeables", "base.json")) - if err != nil { - t.Fatalf("read placeables base: %v", err) - } - if !strings.Contains(string(baseRaw), `"output": "placeables.2da"`) { - t.Fatalf("expected imported placeables output name, got:\n%s", string(baseRaw)) - } - lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "placeables", "lock.json")) - if err != nil { - t.Fatalf("read placeables lock: %v", err) - } - if !strings.Contains(string(lockRaw), `"placeables:tmp_crappile01": 16500`) { - t.Fatalf("expected imported placeables lock IDs, got:\n%s", string(lockRaw)) - } -} - -func TestNormalizeProjectImportsLegacyRuleset(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - mkdirAll(t, filepath.Join(root, "reference", "data", "ruleset", "modules")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "data", "ruleset", "base.json"), `{ - "columns": ["Label", "Value"], - "rows": [ - {"id": 0, "Label": "****", "Value": "****"}, - {"id": 7, "Label": "CALLED_SHOT_TO_HIT_MODIFIER", "Value": "-4"}, - {"id": 21, "Label": "GOOD_AIM_MODIFIER", "Value": "1"}, - {"id": 100, "Label": "HASTE_DODGE_AC_INCREASE_AMOUNT", "Value": "0"}, - {"id": 101, "Label": "HASTED_SPELL_CONJURE_TIME_MODIFIER", "Value": "0.5f"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "ruleset", "lock.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "data", "ruleset", "modules", "ovr_single.json"), `{ - "overrides": [{"match": {"Label": "GOOD_AIM_MODIFIER"}, "Value": "2"}] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "ruleset", "modules", "ovr_array.json"), `{ - "overrides": [{"match": {"Label": ["HASTE_DODGE_AC_INCREASE_AMOUNT", "HASTED_SPELL_CONJURE_TIME_MODIFIER"]}, "Value": "1.0f"}] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles < 1 { - t.Fatalf("expected ruleset import, got %#v", result) - } - rulesetPath := filepath.Join(root, "topdata", "data", "ruleset", "ruleset.json") - if _, err := os.Stat(rulesetPath); err != nil { - t.Fatalf("expected imported ruleset file: %v", err) - } - if _, err := os.Stat(filepath.Join(root, "topdata", "data", "ruleset", "lock.json")); !os.IsNotExist(err) { - t.Fatalf("expected no ruleset lock file, got %v", err) - } - if _, err := os.Stat(filepath.Join(root, "topdata", "data", "ruleset", "modules")); !os.IsNotExist(err) { - t.Fatalf("expected no ruleset modules dir, got %v", err) - } - raw, err := os.ReadFile(rulesetPath) - if err != nil { - t.Fatalf("read ruleset: %v", err) - } - text := string(raw) - if !strings.Contains(text, `"output": "ruleset.2da"`) || - !strings.Contains(text, `"Label": "GOOD_AIM_MODIFIER"`) || - !strings.Contains(text, `"Value": "2"`) || - !strings.Contains(text, `"Label": "HASTED_SPELL_CONJURE_TIME_MODIFIER"`) || - !strings.Contains(text, `"Value": "1.0f"`) { - t.Fatalf("unexpected imported ruleset:\n%s", text) - } -} - func TestBuildSupportsCanonicalRuleset(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "ruleset")) @@ -10241,273 +6917,6 @@ func TestBuildSupportsCanonicalRuleset(t *testing.T) { } } -func TestBuildCanonicalRulesetMatchesReference(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - mkdirAll(t, filepath.Join(root, "reference", "data", "ruleset", "modules")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), `#!/usr/bin/env python3 -import os, sys -out = None -tlk = None -args = sys.argv[1:] -for i, arg in enumerate(args): - if arg == "--out": - out = args[i + 1] - if arg == "--tlk-dir": - tlk = args[i + 1] -os.makedirs(out, exist_ok=True) -os.makedirs(tlk, exist_ok=True) -with open(os.path.join(out, "ruleset.2da"), "w", encoding="utf-8") as f: - f.write("2DA V2.0\n\n") - f.write("Label\tValue\n") - f.write("0\t****\t****\n") - f.write("21\tGOOD_AIM_MODIFIER\t2\n") - f.write("100\tHASTE_DODGE_AC_INCREASE_AMOUNT\t1.0f\n") - f.write("101\tHASTED_SPELL_CONJURE_TIME_MODIFIER\t1.0f\n") -with open(os.path.join(tlk, "sow_tlk.tlk"), "wb") as f: - f.write(b"TLK") -`) - writeFile(t, filepath.Join(root, "reference", "data", "ruleset", "base.json"), `{ - "columns": ["Label", "Value"], - "rows": [ - {"id": 0, "Label": "****", "Value": "****"}, - {"id": 21, "Label": "GOOD_AIM_MODIFIER", "Value": "1"}, - {"id": 100, "Label": "HASTE_DODGE_AC_INCREASE_AMOUNT", "Value": "0"}, - {"id": 101, "Label": "HASTED_SPELL_CONJURE_TIME_MODIFIER", "Value": "0.5f"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "ruleset", "lock.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "data", "ruleset", "modules", "ovr_single.json"), `{ - "overrides": [{"match": {"Label": "GOOD_AIM_MODIFIER"}, "Value": "2"}] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "ruleset", "modules", "ovr_array.json"), `{ - "overrides": [{"match": {"Label": ["HASTE_DODGE_AC_INCREASE_AMOUNT", "HASTED_SPELL_CONJURE_TIME_MODIFIER"]}, "Value": "1.0f"}] -}`+"\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles < 1 { - t.Fatalf("expected ruleset import, got %#v", result) - } - if _, err := Build(testProject(root), nil); err != nil { - t.Fatalf("Build failed: %v", err) - } - compare, err := CompareReference(testProject(root), nil) - if err != nil { - t.Fatalf("CompareReference failed: %v", err) - } - if compare.NativeMismatch != 0 || compare.NativePass != 1 { - t.Fatalf("unexpected compare result: %#v", compare) - } -} - -func TestNormalizeProjectImportsLegacySkills(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - mkdirAll(t, filepath.Join(root, "topdata", "tlk", "modules", "skills")) - mkdirAll(t, filepath.Join(root, "reference", "data", "skills", "modules", "craft")) - mkdirAll(t, filepath.Join(root, "reference", "data", "skills", "modules", "knowledge")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "topdata", "tlk", "lock.json"), `{ - "skills:athletics.name": 1000, - "skills:athletics.description": 1001, - "skills:craft_armorsmithing.name": 1002, - "skills:craft_armorsmithing.description": 1003, - "skills:knowledge_arcana.name": 1004 -}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "tlk", "modules", "skills", "skills.json"), `{ - "entries": { - "skills:athletics.name": {"text": "Athletics"}, - "skills:athletics.description": {"text": "Athletics description"}, - "skills:craft_armorsmithing.name": {"text": "Craft Armor Smithing"}, - "skills:craft_armorsmithing.description": {"text": "Craft armor description"}, - "skills:knowledge_arcana.name": {"text": "Knowledge Arcana"} - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "skills", "base.json"), `{ - "columns": ["Label", "Name", "Description", "Icon", "Untrained", "KeyAbility", "ArmorCheckPenalty", "AllClassesCanUse", "Category", "MaxCR", "Constant", "HostileSkill", "HideFromLevelUp"], - "rows": [ - {"key": "skills:animalempathy", "id": 0, "Label": "AnimalEmpathy", "Name": "269", "Description": "344", "Icon": "isk_aniemp", "Untrained": "0", "KeyAbility": "CHA", "ArmorCheckPenalty": "0", "AllClassesCanUse": "0", "Category": "****", "MaxCR": "1", "Constant": "SKILL_ANIMAL_EMPATHY", "HostileSkill": "1", "HideFromLevelUp": "0"}, - {"key": "skills:concentration", "id": 1, "Label": "Concentration", "Name": "270", "Description": "345", "Icon": "isk_concen", "Untrained": "1", "KeyAbility": "CON", "ArmorCheckPenalty": "0", "AllClassesCanUse": "1", "Category": "****", "MaxCR": "****", "Constant": "SKILL_CONCENTRATION", "HostileSkill": "0", "HideFromLevelUp": "0"}, - {"key": "skills:discipline", "id": 3, "Label": "Discipline", "Name": "343", "Description": "347", "Icon": "isk_discipline", "Untrained": "1", "KeyAbility": "STR", "ArmorCheckPenalty": "0", "AllClassesCanUse": "1", "Category": "****", "MaxCR": "****", "Constant": "SKILL_DISCIPLINE", "HostileSkill": "0", "HideFromLevelUp": "0"}, - {"key": "skills:tumble", "id": 25, "Label": "Tumble", "Name": "280", "Description": "356", "Icon": "isk_tumble", "Untrained": "1", "KeyAbility": "DEX", "ArmorCheckPenalty": "1", "AllClassesCanUse": "1", "Category": "****", "MaxCR": "****", "Constant": "SKILL_TUMBLE", "HostileSkill": "0", "HideFromLevelUp": "0"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "skills", "lock.json"), `{ - "skills:craft_armorsmithing": 25, - "skills:athletics": 28, - "skills:knowledge_arcana": 40 -}`+"\n") - for _, name := range []string{ - "add_disguise.json", - "add_linguistics.json", - "add_perception.json", - "add_scriptcraft.json", - "add_sensemotive.json", - "add_stealth.json", - "add_survival.json", - "add_userope.json", - "ovr_acrobatics.json", - "ovr_animalhandling.json", - "ovr_appraise.json", - "ovr_concentration.json", - "ovr_disabledevice.json", - "ovr_heal.json", - "ovr_hiddenskills.json", - "ovr_influence.json", - "ovr_openlock.json", - "ovr_parry.json", - "ovr_searchtowis.json", - "ovr_sleightofhand.json", - "ovr_taunttointimidate.json", - } { - writeFile(t, filepath.Join(root, "reference", "data", "skills", "modules", name), `{"overrides": []}`+"\n") - } - for _, name := range []string{ - "add_craftalchemy.json", - "add_craftcooking.json", - "add_craftjewelry.json", - "add_craftleatherworking.json", - "add_craftstonework.json", - "add_crafttextiles.json", - "ovr_crafttinkering.json", - "ovr_craftweaponsmithing.json", - "ovr_craftwoodworking.json", - } { - payload := `{"entries": {}}` - if strings.HasPrefix(name, "ovr_") { - payload = `{"overrides": []}` - } - writeFile(t, filepath.Join(root, "reference", "data", "skills", "modules", "craft", name), payload+"\n") - } - for _, name := range []string{ - "add_knowledgearchitecture.json", - "add_knowledgedungeoneering.json", - "add_knowledgegeography.json", - "add_knowledgehistory.json", - "add_knowledgelocal.json", - "add_knowledgenature.json", - "add_knowledgenobility.json", - "add_knowledgeplanar.json", - "add_knowledgereligion.json", - } { - writeFile(t, filepath.Join(root, "reference", "data", "skills", "modules", "knowledge", name), `{"entries": {}}`+"\n") - } - writeFile(t, filepath.Join(root, "reference", "data", "skills", "modules", "add_athletics.json"), `{ - "entries": { - "skills:athletics": { - "Label": "Athletics", - "Name": {"tlk": "skills:athletics.name"}, - "Description": {"tlk": "skills:athletics.description"}, - "Icon": "isk_athletics", - "Untrained": "1", - "KeyAbility": "STR", - "ArmorCheckPenalty": "1", - "AllClassesCanUse": "1", - "Constant": "SKILL_ATHLETICS", - "HostileSkill": "0", - "HideFromLevelUp": "0" - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "skills", "modules", "craft", "ovr_craftarmorsmithing.json"), `{ - "overrides": [ - { - "key": "skills:craft_armorsmithing", - "id": 25, - "Label": "Craftarmorsmithing", - "Name": {"tlk": "skills:craft_armorsmithing.name"}, - "Description": {"tlk": "skills:craft_armorsmithing.description"}, - "Icon": "isk_x2carm", - "Untrained": "0", - "Constant": "SKILL_CRAFT_ARMORSMITHING", - "meta": {"wiki": {"status": "new"}}, - "HideFromLevelUp": "0" - } - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "skills", "modules", "knowledge", "add_knowledgearcana.json"), `{ - "entries": { - "skills:knowledge_arcana": { - "Label": "Knowledgearcana", - "Name": {"tlk": "skills:knowledge_arcana.name"}, - "Icon": "isk_knarcana", - "Untrained": "1", - "KeyAbility": "INT", - "ArmorCheckPenalty": "0", - "AllClassesCanUse": "1", - "Constant": "SKILL_KNOWLEDGE_ARCANA", - "HostileSkill": "0", - "HideFromLevelUp": "0" - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "skills", "modules", "rmv_removedskills.json"), `{ - "overrides": [ - { - "key": null, - "id": 3, - "Label": "Discipline_REMOVED", - "Constant": "****", - "AllClassesCanUse": "0", - "HideFromLevelUp": "1" - } - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles < 6 { - t.Fatalf("expected skills files to be imported, got %#v", result) - } - for _, path := range []string{ - filepath.Join(root, "topdata", "data", "skills", "base.json"), - filepath.Join(root, "topdata", "data", "skills", "lock.json"), - filepath.Join(root, "topdata", "data", "skills", "modules", "add_athletics.json"), - filepath.Join(root, "topdata", "data", "skills", "modules", "craft", "ovr_craftarmorsmithing.json"), - filepath.Join(root, "topdata", "data", "skills", "modules", "knowledge", "add_knowledgearcana.json"), - filepath.Join(root, "topdata", "data", "skills", "modules", "rmv_removedskills.json"), - } { - if _, err := os.Stat(path); err != nil { - t.Fatalf("expected imported skills path %s: %v", path, err) - } - } - baseRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "skills", "base.json")) - if err != nil { - t.Fatalf("read skills base: %v", err) - } - if !strings.Contains(string(baseRaw), `"output": "skills.2da"`) { - t.Fatalf("expected imported skills output name, got:\n%s", string(baseRaw)) - } - lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "skills", "lock.json")) - if err != nil { - t.Fatalf("read skills lock: %v", err) - } - if !strings.Contains(string(lockRaw), `"skills:athletics": 28`) || !strings.Contains(string(lockRaw), `"skills:knowledge_arcana": 40`) { - t.Fatalf("expected imported skills lock ids, got:\n%s", string(lockRaw)) - } - moduleRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "skills", "modules", "add_athletics.json")) - if err != nil { - t.Fatalf("read skills athletics module: %v", err) - } - if !strings.Contains(string(moduleRaw), `"text": "Athletics"`) || !strings.Contains(string(moduleRaw), `"text": "Athletics description"`) { - t.Fatalf("expected normalized inline TLK payloads in athletics module, got:\n%s", string(moduleRaw)) - } - craftRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "skills", "modules", "craft", "ovr_craftarmorsmithing.json")) - if err != nil { - t.Fatalf("read skills craft override: %v", err) - } - if !strings.Contains(string(craftRaw), `"text": "Craft Armor Smithing"`) || !strings.Contains(string(craftRaw), `"text": "Craft armor description"`) { - t.Fatalf("expected normalized inline TLK payloads in craft override, got:\n%s", string(craftRaw)) - } -} - func TestBuildSupportsCanonicalSkills(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "skills", "modules", "craft")) @@ -11511,343 +7920,6 @@ func TestBuildSkillsIgnoresStaleBaseSpanLockDuringInitialLoad(t *testing.T) { } } -func TestBuildCanonicalSkillsMatchesReference(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - mkdirAll(t, filepath.Join(root, "topdata", "tlk", "modules", "skills")) - mkdirAll(t, filepath.Join(root, "reference", "data", "skills", "modules", "craft")) - mkdirAll(t, filepath.Join(root, "reference", "data", "skills", "modules", "knowledge")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "topdata", "tlk", "lock.json"), `{ - "skills:athletics.name": 1000, - "skills:athletics.description": 1001, - "skills:craft_armorsmithing.name": 1002, - "skills:craft_armorsmithing.description": 1003, - "skills:knowledge_arcana.name": 1004 -}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "tlk", "modules", "skills", "skills.json"), `{ - "entries": { - "skills:athletics.name": {"text": "Athletics"}, - "skills:athletics.description": {"text": "Athletics description"}, - "skills:craft_armorsmithing.name": {"text": "Craft Armor Smithing"}, - "skills:craft_armorsmithing.description": {"text": "Craft armor description"}, - "skills:knowledge_arcana.name": {"text": "Knowledge Arcana"} - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), `#!/usr/bin/env python3 -import os, sys -out = None -tlk = None -args = sys.argv[1:] -for i, arg in enumerate(args): - if arg == "--out": - out = args[i + 1] - if arg == "--tlk-dir": - tlk = args[i + 1] -os.makedirs(out, exist_ok=True) -os.makedirs(tlk, exist_ok=True) -cols = ["Label", "Name", "Description", "Icon", "Untrained", "KeyAbility", "ArmorCheckPenalty", "AllClassesCanUse", "Category", "MaxCR", "Constant", "HostileSkill", "HideFromLevelUp"] -with open(os.path.join(out, "skills.2da"), "w", encoding="utf-8") as f: - def emit(row_id, values): - row = {c: "****" for c in cols} - row.update(values) - f.write(str(row_id) + "\t" + "\t".join(row[c] for c in cols) + "\n") - f.write("2DA V2.0\n\n") - f.write("\t".join(cols) + "\n") - emit(0, {"Label": "AnimalEmpathy", "Name": "269", "Description": "344", "Icon": "isk_aniemp", "Untrained": "0", "KeyAbility": "CHA", "ArmorCheckPenalty": "0", "AllClassesCanUse": "0", "Category": "****", "MaxCR": "1", "Constant": "SKILL_ANIMAL_EMPATHY", "HostileSkill": "1", "HideFromLevelUp": "0"}) - emit(1, {"Label": "Concentration", "Name": "270", "Description": "345", "Icon": "isk_concen", "Untrained": "1", "KeyAbility": "CON", "ArmorCheckPenalty": "0", "AllClassesCanUse": "1", "Category": "****", "MaxCR": "****", "Constant": "SKILL_CONCENTRATION", "HostileSkill": "0", "HideFromLevelUp": "0"}) - emit(2, {}) - emit(3, {}) - for row_id in range(4, 25): - emit(row_id, {}) - emit(25, {"Label": "Craftarmorsmithing", "Name": "1002", "Description": "1003", "Icon": "isk_x2carm", "Untrained": "0", "KeyAbility": "DEX", "ArmorCheckPenalty": "1", "AllClassesCanUse": "1", "Category": "****", "MaxCR": "****", "Constant": "SKILL_CRAFT_ARMORSMITHING", "HostileSkill": "0", "HideFromLevelUp": "0"}) - emit(26, {}) - emit(27, {}) - emit(28, {"Label": "Athletics", "Name": "1000", "Description": "1001", "Icon": "isk_athletics", "Untrained": "1", "KeyAbility": "STR", "ArmorCheckPenalty": "1", "AllClassesCanUse": "1", "Category": "****", "MaxCR": "****", "Constant": "SKILL_ATHLETICS", "HostileSkill": "0", "HideFromLevelUp": "0"}) - for row_id in range(29, 40): - emit(row_id, {}) - emit(40, {"Label": "Knowledgearcana", "Name": "1004", "Description": "****", "Icon": "isk_knarcana", "Untrained": "1", "KeyAbility": "INT", "ArmorCheckPenalty": "0", "AllClassesCanUse": "1", "Category": "****", "MaxCR": "****", "Constant": "SKILL_KNOWLEDGE_ARCANA", "HostileSkill": "0", "HideFromLevelUp": "0"}) -with open(os.path.join(tlk, "sow_tlk.tlk"), "wb") as f: - f.write(b"TLK") -`) - writeFile(t, filepath.Join(root, "reference", "data", "skills", "base.json"), `{ - "columns": ["Label", "Name", "Description", "Icon", "Untrained", "KeyAbility", "ArmorCheckPenalty", "AllClassesCanUse", "Category", "MaxCR", "Constant", "HostileSkill", "HideFromLevelUp"], - "rows": [ - {"key": "skills:animalempathy", "id": 0, "Label": "AnimalEmpathy", "Name": "269", "Description": "344", "Icon": "isk_aniemp", "Untrained": "0", "KeyAbility": "CHA", "ArmorCheckPenalty": "0", "AllClassesCanUse": "0", "Category": "****", "MaxCR": "1", "Constant": "SKILL_ANIMAL_EMPATHY", "HostileSkill": "1", "HideFromLevelUp": "0"}, - {"key": "skills:concentration", "id": 1, "Label": "Concentration", "Name": "270", "Description": "345", "Icon": "isk_concen", "Untrained": "1", "KeyAbility": "CON", "ArmorCheckPenalty": "0", "AllClassesCanUse": "1", "Category": "****", "MaxCR": "****", "Constant": "SKILL_CONCENTRATION", "HostileSkill": "0", "HideFromLevelUp": "0"}, - {"key": "skills:discipline", "id": 3, "Label": "Discipline", "Name": "343", "Description": "347", "Icon": "isk_discipline", "Untrained": "1", "KeyAbility": "STR", "ArmorCheckPenalty": "0", "AllClassesCanUse": "1", "Category": "****", "MaxCR": "****", "Constant": "SKILL_DISCIPLINE", "HostileSkill": "0", "HideFromLevelUp": "0"}, - {"key": "skills:craft_armorsmithing", "id": 25, "Label": "Tumble", "Name": "280", "Description": "356", "Icon": "isk_tumble", "Untrained": "1", "KeyAbility": "DEX", "ArmorCheckPenalty": "1", "AllClassesCanUse": "1", "Category": "****", "MaxCR": "****", "Constant": "SKILL_TUMBLE", "HostileSkill": "0", "HideFromLevelUp": "0"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "skills", "lock.json"), `{ - "skills:craft_armorsmithing": 25, - "skills:athletics": 28, - "skills:knowledge_arcana": 40 -}`+"\n") - for _, name := range []string{ - "add_disguise.json", - "add_linguistics.json", - "add_perception.json", - "add_scriptcraft.json", - "add_sensemotive.json", - "add_stealth.json", - "add_survival.json", - "add_userope.json", - "ovr_acrobatics.json", - "ovr_animalhandling.json", - "ovr_appraise.json", - "ovr_concentration.json", - "ovr_disabledevice.json", - "ovr_heal.json", - "ovr_hiddenskills.json", - "ovr_influence.json", - "ovr_openlock.json", - "ovr_parry.json", - "ovr_searchtowis.json", - "ovr_sleightofhand.json", - "ovr_taunttointimidate.json", - } { - writeFile(t, filepath.Join(root, "reference", "data", "skills", "modules", name), `{"overrides": []}`+"\n") - } - for _, name := range []string{ - "add_craftalchemy.json", - "add_craftcooking.json", - "add_craftjewelry.json", - "add_craftleatherworking.json", - "add_craftstonework.json", - "add_crafttextiles.json", - "ovr_crafttinkering.json", - "ovr_craftweaponsmithing.json", - "ovr_craftwoodworking.json", - } { - payload := `{"entries": {}}` - if strings.HasPrefix(name, "ovr_") { - payload = `{"overrides": []}` - } - writeFile(t, filepath.Join(root, "reference", "data", "skills", "modules", "craft", name), payload+"\n") - } - for _, name := range []string{ - "add_knowledgearchitecture.json", - "add_knowledgedungeoneering.json", - "add_knowledgegeography.json", - "add_knowledgehistory.json", - "add_knowledgelocal.json", - "add_knowledgenature.json", - "add_knowledgenobility.json", - "add_knowledgeplanar.json", - "add_knowledgereligion.json", - } { - writeFile(t, filepath.Join(root, "reference", "data", "skills", "modules", "knowledge", name), `{"entries": {}}`+"\n") - } - writeFile(t, filepath.Join(root, "reference", "data", "skills", "modules", "add_athletics.json"), `{ - "entries": { - "skills:athletics": { - "Label": "Athletics", - "Name": {"tlk": "skills:athletics.name"}, - "Description": {"tlk": "skills:athletics.description"}, - "Icon": "isk_athletics", - "Untrained": "1", - "KeyAbility": "STR", - "ArmorCheckPenalty": "1", - "AllClassesCanUse": "1", - "Constant": "SKILL_ATHLETICS", - "HostileSkill": "0", - "HideFromLevelUp": "0" - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "skills", "modules", "craft", "ovr_craftarmorsmithing.json"), `{ - "overrides": [ - { - "key": "skills:craft_armorsmithing", - "id": 25, - "Label": "Craftarmorsmithing", - "Name": {"tlk": "skills:craft_armorsmithing.name"}, - "Description": {"tlk": "skills:craft_armorsmithing.description"}, - "Icon": "isk_x2carm", - "Untrained": "0", - "Constant": "SKILL_CRAFT_ARMORSMITHING", - "meta": {"wiki": {"status": "new"}}, - "HideFromLevelUp": "0" - } - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "skills", "modules", "knowledge", "add_knowledgearcana.json"), `{ - "entries": { - "skills:knowledge_arcana": { - "Label": "Knowledgearcana", - "Name": {"tlk": "skills:knowledge_arcana.name"}, - "Icon": "isk_knarcana", - "Untrained": "1", - "KeyAbility": "INT", - "ArmorCheckPenalty": "0", - "AllClassesCanUse": "1", - "Constant": "SKILL_KNOWLEDGE_ARCANA", - "HostileSkill": "0", - "HideFromLevelUp": "0" - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "skills", "modules", "rmv_removedskills.json"), `{ - "overrides": [ - { - "key": null, - "id": 3, - "Label": "Discipline_REMOVED", - "Constant": "****", - "AllClassesCanUse": "0", - "HideFromLevelUp": "1" - } - ] -}`+"\n") - - if _, err := NormalizeProject(testProject(root)); err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - nativeResult, err := BuildNative(testProject(root), nil) - if err != nil { - t.Fatalf("BuildNative failed: %v", err) - } - referenceResult, err := BuildReference(testProject(root), nil) - if err != nil { - t.Fatalf("BuildReference failed: %v", err) - } - nativeBytes, err := os.ReadFile(filepath.Join(nativeResult.Output2DADir, "skills.2da")) - if err != nil { - t.Fatalf("read native skills.2da: %v", err) - } - referenceBytes, err := os.ReadFile(filepath.Join(referenceResult.Output2DADir, "skills.2da")) - if err != nil { - t.Fatalf("read reference skills.2da: %v", err) - } - if string(nativeBytes) != string(referenceBytes) { - t.Fatalf("skills output mismatch\nnative:\n%s\nreference:\n%s", string(nativeBytes), string(referenceBytes)) - } -} - -func TestNormalizeProjectImportsLegacySpells(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - mkdirAll(t, filepath.Join(root, "topdata", "tlk", "modules", "spells")) - mkdirAll(t, filepath.Join(root, "topdata", "tlk", "modules", "feat")) - mkdirAll(t, filepath.Join(root, "reference", "data", "spells", "modules", "bardicmusic")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "topdata", "tlk", "lock.json"), `{ - "spells:specialattacks.name": 525, - "feat:bardfascinate.name": 700, - "feat:bardfascinate.description": 701, - "feat:bardfascinate.alt": 702 -}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "tlk", "modules", "spells", "specialattacks.json"), `{ - "entries": { - "spells:specialattacks.name": {"text": "Special Attacks"} - } -}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "tlk", "modules", "feat", "bardfascinate.json"), `{ - "entries": { - "feat:bardfascinate.name": {"text": "Fascinate"}, - "feat:bardfascinate.description": {"text": "Fascinate description"}, - "feat:bardfascinate.alt": {"text": "Alt message"} - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "spells", "base.json"), `{ - "columns": ["Label", "Name", "IconResRef", "School", "Range", "VS", "MetaMagic", "TargetType", "ImpactScript", "Bard", "Cleric", "Druid", "Paladin", "Ranger", "Wiz_Sorc", "Innate", "ConjTime", "ConjAnim", "ConjHeadVisual", "ConjHandVisual", "ConjGrndVisual", "ConjSoundVFX", "ConjSoundMale", "ConjSoundFemale", "CastAnim", "CastTime", "CastHeadVisual", "CastHandVisual", "CastGrndVisual", "CastSound", "Proj", "ProjModel", "ProjType", "ProjSpwnPoint", "ProjSound", "ProjOrientation", "ImmunityType", "ItemImmunity", "SubRadSpell1", "SubRadSpell2", "SubRadSpell3", "SubRadSpell4", "SubRadSpell5", "Category", "Master", "UserType", "SpellDesc", "UseConcentration", "SpontaneouslyCast", "AltMessage", "HostileSetting", "FeatID", "Counter1", "Counter2", "HasProjectile", "TargetShape", "TargetSizeX", "TargetSizeY", "TargetFlags", "SubRadSpell6", "SubRadSpell7", "SubRadSpell8"], - "rows": [ - {"key": "spells:blessweapon", "id": 7, "Label": "Bless_Weapon", "Name": "123", "Category": "8"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "spells", "lock.json"), `{ - "spells:specialattacks": 840, - "spells:disarm": 841, - "spells:bardfascinate": 849 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "spells", "modules", "specialattacks.json"), `{ - "entries": { - "spells:specialattacks": { - "Label": "SpecialAttacks", - "Name": {"tlk": "spells:specialattacks.name"}, - "Category": "22", - "FeatID": {"id": "feat:specialattacks"}, - "SubRadSpell1": {"id": "spells:disarm"} - }, - "spells:disarm": { - "Label": "Disarm", - "Name": {"tlk": "spells:disarm.name"}, - "FeatID": {"id": "feat:disarm"}, - "Master": {"id": "spells:specialattacks"}, - "Category": {"ref": "spells:specialattacks", "field": "Category"} - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "spells", "modules", "ovr_fixduplicates.json"), `{ - "overrides": [ - {"key": null, "id": 7, "Label": "Bless_Weapon_OLD"} - ] -}`+"\n") - for _, name := range []string{ - "inspirecompetence.json", - "inspirecourage.json", - "inspiregreatness.json", - "inspireheroics.json", - "songoffreedom.json", - } { - writeFile(t, filepath.Join(root, "reference", "data", "spells", "modules", "bardicmusic", name), `{"entries": {}}`+"\n") - } - writeFile(t, filepath.Join(root, "reference", "data", "spells", "modules", "bardicmusic", "fascinate.json"), `{ - "entries": { - "spells:bardfascinate": { - "Label": "Bard_Fascinate", - "Name": {"tlk": "feat:bardfascinate.name"}, - "SpellDesc": {"tlk": "feat:bardfascinate.description"}, - "FeatID": {"id": "feat:bardfascinate"}, - "AltMessage": {"tlk": "feat:bardfascinate.alt"}, - "Category": "2" - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - - result, err := NormalizeProject(testProject(root)) - if err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - if result.UpdatedFiles < 10 { - t.Fatalf("expected spells files to be imported, got %#v", result) - } - for _, path := range []string{ - filepath.Join(root, "topdata", "data", "spells", "base.json"), - filepath.Join(root, "topdata", "data", "spells", "lock.json"), - filepath.Join(root, "topdata", "data", "spells", "modules", "specialattacks.json"), - filepath.Join(root, "topdata", "data", "spells", "modules", "ovr_fixduplicates.json"), - filepath.Join(root, "topdata", "data", "spells", "modules", "bardicmusic", "fascinate.json"), - } { - if _, err := os.Stat(path); err != nil { - t.Fatalf("expected imported spells path %s: %v", path, err) - } - } - baseRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "spells", "base.json")) - if err != nil { - t.Fatalf("read spells base: %v", err) - } - if !strings.Contains(string(baseRaw), `"output": "spells.2da"`) { - t.Fatalf("expected imported spells output name, got:\n%s", string(baseRaw)) - } - lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "spells", "lock.json")) - if err != nil { - t.Fatalf("read spells lock: %v", err) - } - if !strings.Contains(string(lockRaw), `"spells:bardfascinate": 849`) || !strings.Contains(string(lockRaw), `"spells:specialattacks": 840`) { - t.Fatalf("expected imported spells lock ids, got:\n%s", string(lockRaw)) - } - moduleRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "spells", "modules", "bardicmusic", "fascinate.json")) - if err != nil { - t.Fatalf("read spells bardicmusic module: %v", err) - } - if !strings.Contains(string(moduleRaw), `"text": "Fascinate"`) || !strings.Contains(string(moduleRaw), `"text": "Alt message"`) { - t.Fatalf("expected normalized inline TLK payloads in bardicmusic module, got:\n%s", string(moduleRaw)) - } -} - func TestBuildSupportsCanonicalSpells(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "feat")) @@ -12000,159 +8072,6 @@ func TestBuildCanonicalSpellsImpactScriptsComeFromGlobalDefaults(t *testing.T) { } } -func TestBuildCanonicalSpellsMatchesReference(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - mkdirAll(t, filepath.Join(root, "topdata", "tlk", "modules", "spells")) - mkdirAll(t, filepath.Join(root, "topdata", "tlk", "modules", "feat")) - mkdirAll(t, filepath.Join(root, "reference", "data", "spells", "modules", "bardicmusic")) - mkdirAll(t, filepath.Join(root, "reference", "data", "feat")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "topdata", "tlk", "lock.json"), `{ - "spells:specialattacks.name": 525, - "spells:disarm.name": 526, - "feat:bardfascinate.name": 700, - "feat:bardfascinate.description": 701, - "feat:bardfascinate.alt": 702 -}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "tlk", "modules", "spells", "specialattacks.json"), `{ - "entries": { - "spells:specialattacks.name": {"text": "Special Attacks"}, - "spells:disarm.name": {"text": "Disarm"} - } -}`+"\n") - writeFile(t, filepath.Join(root, "topdata", "tlk", "modules", "feat", "bardfascinate.json"), `{ - "entries": { - "feat:bardfascinate.name": {"text": "Fascinate"}, - "feat:bardfascinate.description": {"text": "Fascinate description"}, - "feat:bardfascinate.alt": {"text": "Alt message"} - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), `#!/usr/bin/env python3 -import os, sys -out = None -tlk = None -args = sys.argv[1:] -for i, arg in enumerate(args): - if arg == "--out": - out = args[i + 1] - if arg == "--tlk-dir": - tlk = args[i + 1] -os.makedirs(out, exist_ok=True) -os.makedirs(tlk, exist_ok=True) -with open(os.path.join(out, "spells.2da"), "w", encoding="utf-8") as f: - cols = ["Label", "Name", "IconResRef", "School", "Range", "VS", "MetaMagic", "TargetType", "ImpactScript", "Bard", "Cleric", "Druid", "Paladin", "Ranger", "Wiz_Sorc", "Innate", "ConjTime", "ConjAnim", "ConjHeadVisual", "ConjHandVisual", "ConjGrndVisual", "ConjSoundVFX", "ConjSoundMale", "ConjSoundFemale", "CastAnim", "CastTime", "CastHeadVisual", "CastHandVisual", "CastGrndVisual", "CastSound", "Proj", "ProjModel", "ProjType", "ProjSpwnPoint", "ProjSound", "ProjOrientation", "ImmunityType", "ItemImmunity", "SubRadSpell1", "SubRadSpell2", "SubRadSpell3", "SubRadSpell4", "SubRadSpell5", "Category", "Master", "UserType", "SpellDesc", "UseConcentration", "SpontaneouslyCast", "AltMessage", "HostileSetting", "FeatID", "Counter1", "Counter2", "HasProjectile", "TargetShape", "TargetSizeX", "TargetSizeY", "TargetFlags", "SubRadSpell6", "SubRadSpell7", "SubRadSpell8"] - def emit(row_id, values): - row = {c: "****" for c in cols} - row.update(values) - f.write(str(row_id) + "\t" + "\t".join(row[c] for c in cols) + "\n") - f.write("2DA V2.0\n\n") - f.write("\t".join(cols) + "\n") - for row_id in range(0, 7): - emit(row_id, {}) - emit(7, {}) - for row_id in range(8, 840): - emit(row_id, {}) - emit(840, {"Label": "SpecialAttacks", "Name": "16777741", "SubRadSpell1": "841", "Category": "22", "FeatID": "1000"}) - emit(841, {"Label": "Disarm", "Name": "16777742", "Category": "22", "Master": "840", "FeatID": "1001"}) - for row_id in range(842, 849): - emit(row_id, {}) - emit(849, {"Label": "Bard_Fascinate", "Name": "16777916", "Category": "2", "SpellDesc": "16777917", "AltMessage": "16777918", "FeatID": "1002"}) -with open(os.path.join(tlk, "sow_tlk.tlk"), "wb") as f: - f.write(b"TLK") -`) - writeFile(t, filepath.Join(root, "reference", "data", "feat", "base.json"), `{ - "output": "feat.2da", - "columns": ["LABEL"], - "rows": [ - {"id": 1000, "key": "feat:specialattacks", "LABEL": "Special Attacks"}, - {"id": 1001, "key": "feat:disarm", "LABEL": "Disarm"}, - {"id": 1002, "key": "feat:bardfascinate", "LABEL": "Fascinate"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "feat", "lock.json"), `{"feat:specialattacks":1000,"feat:disarm":1001,"feat:bardfascinate":1002}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "spells", "base.json"), `{ - "columns": ["Label", "Name", "IconResRef", "School", "Range", "VS", "MetaMagic", "TargetType", "ImpactScript", "Bard", "Cleric", "Druid", "Paladin", "Ranger", "Wiz_Sorc", "Innate", "ConjTime", "ConjAnim", "ConjHeadVisual", "ConjHandVisual", "ConjGrndVisual", "ConjSoundVFX", "ConjSoundMale", "ConjSoundFemale", "CastAnim", "CastTime", "CastHeadVisual", "CastHandVisual", "CastGrndVisual", "CastSound", "Proj", "ProjModel", "ProjType", "ProjSpwnPoint", "ProjSound", "ProjOrientation", "ImmunityType", "ItemImmunity", "SubRadSpell1", "SubRadSpell2", "SubRadSpell3", "SubRadSpell4", "SubRadSpell5", "Category", "Master", "UserType", "SpellDesc", "UseConcentration", "SpontaneouslyCast", "AltMessage", "HostileSetting", "FeatID", "Counter1", "Counter2", "HasProjectile", "TargetShape", "TargetSizeX", "TargetSizeY", "TargetFlags", "SubRadSpell6", "SubRadSpell7", "SubRadSpell8"], - "rows": [ - {"key": "spells:blessweapon", "id": 7, "Label": "Bless_Weapon", "Name": "123", "Category": "8"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "spells", "lock.json"), `{ - "spells:specialattacks": 840, - "spells:disarm": 841, - "spells:bardfascinate": 849 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "spells", "modules", "specialattacks.json"), `{ - "entries": { - "spells:specialattacks": { - "Label": "SpecialAttacks", - "Name": {"tlk": "spells:specialattacks.name"}, - "Category": "22", - "UserType": "3", - "FeatID": {"id": "feat:specialattacks"}, - "SubRadSpell1": {"id": "spells:disarm"} - }, - "spells:disarm": { - "Label": "Disarm", - "Name": {"tlk": "spells:disarm.name"}, - "FeatID": {"id": "feat:disarm"}, - "Master": {"id": "spells:specialattacks"}, - "Category": {"ref": "spells:specialattacks", "field": "Category"}, - "UserType": {"ref": "spells:specialattacks", "field": "UserType"} - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "spells", "modules", "ovr_fixduplicates.json"), `{ - "overrides": [ - {"key": null, "id": 7, "Label": "Bless_Weapon_OLD"} - ] -}`+"\n") - for _, name := range []string{ - "inspirecompetence.json", - "inspirecourage.json", - "inspiregreatness.json", - "inspireheroics.json", - "songoffreedom.json", - } { - writeFile(t, filepath.Join(root, "reference", "data", "spells", "modules", "bardicmusic", name), `{"entries": {}}`+"\n") - } - writeFile(t, filepath.Join(root, "reference", "data", "spells", "modules", "bardicmusic", "fascinate.json"), `{ - "entries": { - "spells:bardfascinate": { - "Label": "Bard_Fascinate", - "Name": {"tlk": "feat:bardfascinate.name"}, - "SpellDesc": {"tlk": "feat:bardfascinate.description"}, - "FeatID": {"id": "feat:bardfascinate"}, - "AltMessage": {"tlk": "feat:bardfascinate.alt"}, - "Category": "2" - } - } -}`+"\n") - - if _, err := NormalizeProject(testProject(root)); err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - nativeResult, err := BuildNative(testProject(root), nil) - if err != nil { - t.Fatalf("BuildNative failed: %v", err) - } - referenceResult, err := BuildReference(testProject(root), nil) - if err != nil { - t.Fatalf("BuildReference failed: %v", err) - } - nativeBytes, err := os.ReadFile(filepath.Join(nativeResult.Output2DADir, "spells.2da")) - if err != nil { - t.Fatalf("read native spells.2da: %v", err) - } - referenceBytes, err := os.ReadFile(filepath.Join(referenceResult.Output2DADir, "spells.2da")) - if err != nil { - t.Fatalf("read reference spells.2da: %v", err) - } - if string(nativeBytes) != string(referenceBytes) { - t.Fatalf("spells output mismatch\nnative:\n%s\nreference:\n%s", string(nativeBytes), string(referenceBytes)) - } -} - func TestBuildSupportsCanonicalPlaceables(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "placeables", "modules")) @@ -12217,184 +8136,6 @@ func TestBuildSupportsCanonicalPlaceables(t *testing.T) { } } -func TestBuildCanonicalPlaceablesMatchesReference(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - mkdirAll(t, filepath.Join(root, "reference", "data", "placeables", "modules")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), `#!/usr/bin/env python3 -import os, sys -out = None -tlk = None -args = sys.argv[1:] -for i, arg in enumerate(args): - if arg == "--out": - out = args[i + 1] - if arg == "--tlk-dir": - tlk = args[i + 1] -os.makedirs(out, exist_ok=True) -os.makedirs(tlk, exist_ok=True) -with open(os.path.join(out, "placeables.2da"), "w", encoding="utf-8") as f: - f.write("2DA V2.0\n\n") - f.write("Label\tStrRef\tModelName\tLightColor\tLightOffsetX\tLightOffsetY\tLightOffsetZ\tSoundAppType\tShadowSize\tBodyBag\tLowGore\tReflection\tStatic\n") - f.write("0\t\"Furniture: Armoire (Bioware)\"\t****\tPLC_A01\t****\t****\t****\t****\t13\t1\t0\t****\t****\t1\n") - for row_id in range(1, 16500): - f.write(f"{row_id}\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\n") - f.write("16500\t\"Decoration: Pile 1 (Avernostra)\"\t****\ttmp_crappile01\t****\t****\t****\t****\t****\t****\t****\t****\t1\n") -with open(os.path.join(tlk, "sow_tlk.tlk"), "wb") as f: - f.write(b"TLK") -`) - writeFile(t, filepath.Join(root, "reference", "data", "placeables", "base.json"), `{ - "columns": ["Label", "StrRef", "ModelName", "LightColor", "LightOffsetX", "LightOffsetY", "LightOffsetZ", "SoundAppType", "ShadowSize", "BodyBag", "LowGore", "Reflection", "Static"], - "rows": [ - {"key": "placeables:armoire", "id": 0, "Label": "Armoire", "StrRef": "5645", "ModelName": "PLC_A01", "SoundAppType": "13", "ShadowSize": "1", "BodyBag": "0", "Static": "1"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "placeables", "lock.json"), `{ - "placeables:armoire": 0, - "placeables:tmp_crappile01": 16500 -}`+"\n") - for _, name := range []string{ - "add_ccfeb2019plcs.json", - "add_cepcarpets.json", - "add_cepfloors.json", - "add_cepnwn2library.json", - "add_nwic.json", - "add_ori.json", - "add_sic11.json", - "add_tapr.json", - "add_tdfloors.json", - "add_undecals.json", - "add_witcher1.json", - "add_witcher2.json", - } { - writeFile(t, filepath.Join(root, "reference", "data", "placeables", "modules", name), `{"entries": {}}`+"\n") - } - writeFile(t, filepath.Join(root, "reference", "data", "placeables", "modules", "add_avernostragarbage.json"), `{ - "entries": { - "placeables:tmp_crappile01": { - "Label": "Decoration: Pile 1 (Avernostra)", - "ModelName": "tmp_crappile01", - "Static": 1 - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "placeables", "modules", "ovr_vanillaplcsorts.json"), `{ - "overrides": [ - {"key": "placeables:armoire", "id": 0, "Label": "Furniture: Armoire (Bioware)", "StrRef": "****"} - ] -}`+"\n") - - if _, err := NormalizeProject(testProject(root)); err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - nativeResult, err := BuildNative(testProject(root), nil) - if err != nil { - t.Fatalf("BuildNative failed: %v", err) - } - referenceResult, err := BuildReference(testProject(root), nil) - if err != nil { - t.Fatalf("BuildReference failed: %v", err) - } - nativeBytes, err := os.ReadFile(filepath.Join(nativeResult.Output2DADir, "placeables.2da")) - if err != nil { - t.Fatalf("read native placeables.2da: %v", err) - } - referenceBytes, err := os.ReadFile(filepath.Join(referenceResult.Output2DADir, "placeables.2da")) - if err != nil { - t.Fatalf("read reference placeables.2da: %v", err) - } - if string(nativeBytes) != string(referenceBytes) { - t.Fatalf("placeables output mismatch\nnative:\n%s\nreference:\n%s", string(nativeBytes), string(referenceBytes)) - } -} - -func TestNormalizeProjectImportsRacialtypesRegistry(t *testing.T) { - root := testProjectRoot(t) - mkdirAll(t, filepath.Join(root, "topdata", "data")) - mkdirAll(t, filepath.Join(root, "reference", "data", "racialtypes", "core", "modules")) - mkdirAll(t, filepath.Join(root, "reference", "data", "racialtypes", "feats")) - mkdirAll(t, filepath.Join(root, "reference", "tlk", "modules", "racialtypes")) - writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") - writeFile(t, filepath.Join(root, "reference", "build.py"), "print('ok')\n") - writeFile(t, filepath.Join(root, "reference", "data", "racialtypes", "core", "base.json"), `{ - "output": "racialtypes.2da", - "columns": ["Label", "Name", "Appearance", "ToolsetDefaultClass", "FeatsTable", "Description"], - "rows": [ - {"id": 6, "Label": "Human", "Name": "34", "Appearance": "0", "ToolsetDefaultClass": "4", "FeatsTable": "RACE_FEAT_HUMAN", "Description": "****"}, - {"id": 7, "Label": "Beast", "Name": "527", "Appearance": "****", "ToolsetDefaultClass": "****", "FeatsTable": "****", "Description": "****"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "racialtypes", "core", "lock.json"), `{ - "racialtypes:human": 6, - "racialtypes:tiefling": 31 -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "racialtypes", "core", "modules", "ovr_human.json"), `{ - "overrides": [ - {"key": "racialtypes:human", "id": 6, "Label": "Human", "Description": "****"} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "racialtypes", "core", "modules", "tiefling.json"), `{ - "entries": { - "racialtypes:tiefling": { - "Label": "Tiefling", - "Appearance": {"id": "appearance:human"}, - "ToolsetDefaultClass": {"id": "classes:rogue"}, - "FeatsTable": {"table": "racialtypes/feats:tiefling"}, - "Description": {"tlk": "racialtypes:tiefling.description"} - } - } -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "data", "racialtypes", "feats", "tiefling.json"), `{ - "key": "racialtypes/feats:tiefling", - "output": "race_feat_tief.2da", - "columns": ["FeatLabel", "FeatIndex"], - "rows": [ - {"FeatLabel": "Darkvision", "FeatIndex": {"id": "feat:darkvision"}} - ] -}`+"\n") - writeFile(t, filepath.Join(root, "reference", "tlk", "lock.json"), `{"racialtypes:tiefling.description": 100}`+"\n") - writeFile(t, filepath.Join(root, "reference", "tlk", "modules", "racialtypes", "tiefling.json"), `{ - "entries": { - "racialtypes:tiefling": { - "description": {"text": "Tiefling description"} - } - } -}`+"\n") - - if _, err := NormalizeProject(testProject(root)); err != nil { - t.Fatalf("NormalizeProject failed: %v", err) - } - - baseRowsRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "racialtypes", "registry", "base.json")) - if err != nil { - t.Fatalf("read base.json: %v", err) - } - baseText := string(baseRowsRaw) - if !strings.Contains(baseText, `"Label": "Beast"`) || !strings.Contains(baseText, `"key": "racialtypes:beast"`) { - t.Fatalf("expected keyed static core row scaffold, got:\n%s", baseText) - } - - lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "racialtypes", "registry", "lock.json")) - if err != nil { - t.Fatalf("read lock.json: %v", err) - } - if !strings.Contains(string(lockRaw), `"racialtypes:human": 6`) || !strings.Contains(string(lockRaw), `"racialtypes:tiefling": 31`) || !strings.Contains(string(lockRaw), `"racialtypes:beast": 7`) { - t.Fatalf("expected preserved lock ids, got:\n%s", string(lockRaw)) - } - - tieflingRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "racialtypes", "registry", "races", "tiefling.json")) - if err != nil { - t.Fatalf("read tiefling.json: %v", err) - } - tieflingText := string(tieflingRaw) - if !strings.Contains(tieflingText, `"feat_output": "race_feat_tief.2da"`) || - !strings.Contains(tieflingText, `"text": "Tiefling description"`) || - !strings.Contains(tieflingText, `"FeatLabel": "Darkvision"`) { - t.Fatalf("unexpected tiefling registry row:\n%s", tieflingText) - } -} - func TestBuildSupportsRacialtypesRegistryWithSnapshotClassIDs(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "appearance")) diff --git a/internal/topdata/vfx_persistent_migrate.go b/internal/topdata/vfx_persistent_migrate.go deleted file mode 100644 index 3057fe7..0000000 --- a/internal/topdata/vfx_persistent_migrate.go +++ /dev/null @@ -1,198 +0,0 @@ -package topdata - -import ( - "os" - "path/filepath" - "strings" -) - -func importLegacyVFXPersistent(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - _ = legacyTLK - if strings.TrimSpace(referenceBuilderDir) == "" { - return 0, nil - } - - legacyDir := filepath.Join(referenceBuilderDir, "data", "vfx_persistent") - if _, err := os.Stat(legacyDir); err != nil { - if os.IsNotExist(err) { - return 0, nil - } - return 0, err - } - - targetDir := filepath.Join(dataDir, "vfx_persistent") - targetBasePath := filepath.Join(targetDir, "base.json") - targetLockPath := filepath.Join(targetDir, "lock.json") - targetModulesDir := filepath.Join(targetDir, "modules") - targetModulePath := filepath.Join(targetModulesDir, "freeformaoes.json") - if fileExists(targetBasePath) && fileExists(targetLockPath) && fileExists(targetModulePath) { - baseObj, baseErr := loadJSONObject(targetBasePath) - lockObj, lockErr := loadJSONObject(targetLockPath) - if baseErr == nil && lockErr == nil && vfxPersistentImportComplete(baseObj, lockObj) { - legacyModulePaths, err := collectJSONRelativePaths(filepath.Join(legacyDir, "modules")) - if err == nil { - targetModulePaths, err := collectJSONRelativePaths(targetModulesDir) - if err == nil && equalStringSlices(legacyModulePaths, targetModulePaths) { - return 0, nil - } - } - } - } - - baseObj, err := loadJSONObject(filepath.Join(legacyDir, "base.json")) - if err != nil { - return 0, err - } - lockObj, err := synthesizeVFXPersistentKeys(baseObj) - if err != nil { - return 0, err - } - legacyLockObj, err := loadJSONObject(filepath.Join(legacyDir, "lock.json")) - if err != nil { - return 0, err - } - for key, value := range legacyLockObj { - lockObj[key] = value - } - if err := os.MkdirAll(targetModulesDir, 0o755); err != nil { - return 0, err - } - - updated := 0 - for _, write := range []struct { - path string - obj map[string]any - }{ - {path: targetBasePath, obj: baseObj}, - {path: targetLockPath, obj: lockObj}, - } { - changed, err := writeJSONObjectIfChanged(write.path, write.obj) - if err != nil { - return 0, err - } - if changed { - updated++ - } - } - - moduleRelPaths, err := collectJSONRelativePaths(filepath.Join(legacyDir, "modules")) - if err != nil { - return 0, err - } - for _, relPath := range moduleRelPaths { - moduleObj, err := loadJSONObject(filepath.Join(legacyDir, "modules", filepath.FromSlash(relPath))) - if err != nil { - return 0, err - } - targetPath := filepath.Join(targetModulesDir, filepath.FromSlash(relPath)) - if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil { - return 0, err - } - changed, err := writeJSONObjectIfChanged(targetPath, moduleObj) - if err != nil { - return 0, err - } - if changed { - updated++ - } - } - - targetModulePaths, err := collectJSONRelativePaths(targetModulesDir) - if err != nil { - return 0, err - } - legacyModules := make(map[string]struct{}, len(moduleRelPaths)) - for _, relPath := range moduleRelPaths { - legacyModules[relPath] = struct{}{} - } - for _, relPath := range targetModulePaths { - if _, ok := legacyModules[relPath]; ok { - continue - } - if err := os.Remove(filepath.Join(targetModulesDir, filepath.FromSlash(relPath))); err != nil && !os.IsNotExist(err) { - return 0, err - } - updated++ - } - - return updated, nil -} - -func vfxPersistentImportComplete(baseObj, lockObj map[string]any) bool { - if !vfxPersistentRowsHaveKeys(baseObj) { - return false - } - expected := map[string]int{ - "vfx_persistent:blankradius5": 47, - "vfx_persistent:blankradius10": 48, - "vfx_persistent:blankradius15": 49, - "vfx_persistent:blankradius20": 50, - "vfx_persistent:blankradius25": 51, - "vfx_persistent:blankradius30": 52, - } - for key, want := range expected { - raw, ok := lockObj[key] - if !ok { - return false - } - got, err := asInt(raw) - if err != nil || got != want { - return false - } - } - return true -} - -func synthesizeVFXPersistentKeys(baseObj map[string]any) (map[string]any, error) { - rawRows, ok := baseObj["rows"].([]any) - if !ok { - return map[string]any{}, nil - } - lockObj := map[string]any{} - for _, raw := range rawRows { - row, ok := raw.(map[string]any) - if !ok { - continue - } - label, _ := row["LABEL"].(string) - if label == "" { - continue - } - key := "vfx_persistent:" + strings.ToLower(label) - row["key"] = key - if rawID, ok := row["id"]; ok { - lockObj[key] = rawID - } - } - return lockObj, nil -} - -func vfxPersistentRowsHaveKeys(baseObj map[string]any) bool { - rawRows, ok := baseObj["rows"].([]any) - if !ok { - return false - } - for _, raw := range rawRows { - row, ok := raw.(map[string]any) - if !ok { - return false - } - key, _ := row["key"].(string) - if key == "" { - return false - } - } - return true -} - -func equalStringSlices(left, right []string) bool { - if len(left) != len(right) { - return false - } - for i := range left { - if left[i] != right[i] { - return false - } - } - return true -} diff --git a/internal/topdata/visualeffects_migrate.go b/internal/topdata/visualeffects_migrate.go deleted file mode 100644 index 36a29aa..0000000 --- a/internal/topdata/visualeffects_migrate.go +++ /dev/null @@ -1,6 +0,0 @@ -package topdata - -func importLegacyVisualeffects(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - _ = legacyTLK - return importLegacyDatasetMirror(referenceBuilderDir, dataDir, "visualeffects", "visualeffects.2da", nil) -} diff --git a/internal/topdata/wingmodel_migrate.go b/internal/topdata/wingmodel_migrate.go deleted file mode 100644 index 395d1bc..0000000 --- a/internal/topdata/wingmodel_migrate.go +++ /dev/null @@ -1,86 +0,0 @@ -package topdata - -import ( - "encoding/json" - "os" - "path/filepath" -) - -func importLegacyWingmodel(referenceBuilderDir, dataDir string, legacyTLK *legacyTLKData) (int, error) { - _ = legacyTLK - - legacyDir := filepath.Join(referenceBuilderDir, "data", "wingmodel") - if _, err := os.Stat(legacyDir); err != nil { - if os.IsNotExist(err) { - return 0, nil - } - return 0, err - } - - targetDir := filepath.Join(dataDir, "wingmodel") - targetBasePath := filepath.Join(targetDir, "base.json") - targetLockPath := filepath.Join(targetDir, "lock.json") - targetModulesDir := filepath.Join(targetDir, "modules") - targetModulePaths := []string{ - filepath.Join(targetModulesDir, "add.json"), - filepath.Join(targetModulesDir, "ovr_wingprefixes.json"), - } - if fileExists(targetBasePath) && fileExists(targetLockPath) { - allModulesPresent := true - for _, path := range targetModulePaths { - if !fileExists(path) { - allModulesPresent = false - break - } - } - if allModulesPresent { - return 0, nil - } - } - - baseObj, err := loadJSONObject(filepath.Join(legacyDir, "base.json")) - if err != nil { - return 0, err - } - baseObj["output"] = "wingmodel.2da" - - lockObj, err := loadJSONObject(filepath.Join(legacyDir, "lock.json")) - if err != nil { - return 0, err - } - - moduleNames := []string{"add.json", "ovr_wingprefixes.json"} - moduleObjs := make([]map[string]any, 0, len(moduleNames)) - for _, name := range moduleNames { - obj, err := loadJSONObject(filepath.Join(legacyDir, "modules", name)) - if err != nil { - return 0, err - } - moduleObjs = append(moduleObjs, obj) - } - - if err := os.MkdirAll(targetModulesDir, 0o755); err != nil { - return 0, err - } - - writes := []struct { - path string - obj map[string]any - }{ - {path: targetBasePath, obj: baseObj}, - {path: targetLockPath, obj: lockObj}, - {path: targetModulePaths[0], obj: moduleObjs[0]}, - {path: targetModulePaths[1], obj: moduleObjs[1]}, - } - for _, write := range writes { - raw, err := json.MarshalIndent(write.obj, "", " ") - if err != nil { - return 0, err - } - raw = append(raw, '\n') - if err := os.WriteFile(write.path, raw, 0o644); err != nil { - return 0, err - } - } - return len(writes), nil -} -- 2.54.0