VFX Category Label Generation
This commit is contained in:
@@ -423,7 +423,7 @@ Minimum messages:
|
||||
- `Preparing model compiler...`
|
||||
- `Preparing model compilation workspace...`
|
||||
- `Compiling model 12/324: part/belt/pfa0_belt018.mdl`
|
||||
- `Reusing compiled model: vfxs/head_accessories/hfx_bandana.mdl`
|
||||
- `Reusing compiled model: vfxs/headaccessory/hfx_bandana.mdl`
|
||||
- `Skipping binary model source: creatures/foo.mdl`
|
||||
- `FAILED model compile: placeables/broken_model.mdl`
|
||||
|
||||
|
||||
@@ -414,12 +414,19 @@ For `head_visualeffects` autogen, producers can publish the group-to-row policy
|
||||
in `autogen.producers[].head_visualeffects`, and consumers can override or
|
||||
extend it in `autogen.consumers[].head_visualeffects`. The toolkit uses
|
||||
compatibility defaults for older configs and manifests, but YAML can own the
|
||||
group prefixes, model stem prefixes to strip, key/label formats, model column,
|
||||
and default row values. Individual groups can override the model column with
|
||||
`model_column` or write the discovered model stem to several columns with
|
||||
`model_columns`, which is useful for root-node visualeffects. Supported format
|
||||
tokens are `{dataset}`, `{group}`, `{prefix}`, `{stem}`, `{stem_upper}`, and
|
||||
`{model_stem}`.
|
||||
group token source, category source, delimiter, case policy, legacy group
|
||||
mapping, model stem prefixes to strip, key/label formats, model column, and
|
||||
default row values. `group_token_source: folder_name` uses the manifest folder
|
||||
group directly instead of a prefix mapping, and `category_from:
|
||||
immediate_parent` can expose the model's category folder in keys and labels.
|
||||
`legacy_groups` maps released manifest group names to current folder groups,
|
||||
and `legacy_key_format` preserves row IDs when migrating older generated lock
|
||||
keys. Individual groups can override the model column with `model_column` or
|
||||
write the discovered model stem to several columns with `model_columns`, which
|
||||
is useful for root-node visualeffects. Supported format tokens are `{dataset}`,
|
||||
`{group}`, `{group_raw}`, `{prefix}`, `{category}`, `{category_upper}`,
|
||||
`{category_segment}`, `{category_segment_upper}`, `{subgroup}`, `{stem}`,
|
||||
`{stem_upper}`, `{model_stem}`, and `{delimiter}`.
|
||||
|
||||
For `parts_rows` generated assets, `parts_rows.acbonus` can normalize final
|
||||
ACBONUS values before module overrides are applied. The
|
||||
|
||||
@@ -2178,10 +2178,10 @@ func TestPlanHAKsWritesManifestWithoutArchives(t *testing.T) {
|
||||
func TestBuildHAKsWritesConfiguredHeadVisualeffectsAutogenManifest(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
mustMkdir(t, filepath.Join(root, "src"))
|
||||
mustMkdir(t, filepath.Join(root, "assets", "vfxs", "chest_accessories"))
|
||||
mustMkdir(t, filepath.Join(root, "assets", "vfxs", "head_accessories"))
|
||||
mustMkdir(t, filepath.Join(root, "assets", "vfxs", "head_decorations"))
|
||||
mustMkdir(t, filepath.Join(root, "assets", "vfxs", "head_features", "hair"))
|
||||
mustMkdir(t, filepath.Join(root, "assets", "vfxs", "chestaccessory"))
|
||||
mustMkdir(t, filepath.Join(root, "assets", "vfxs", "headaccessory"))
|
||||
mustMkdir(t, filepath.Join(root, "assets", "vfxs", "headdecoration"))
|
||||
mustMkdir(t, filepath.Join(root, "assets", "vfxs", "headfeature", "hair"))
|
||||
mustMkdir(t, filepath.Join(root, "build"))
|
||||
|
||||
mustWriteFile(t, filepath.Join(root, "nwn-tool.json"), `{
|
||||
@@ -2200,7 +2200,7 @@ func TestBuildHAKsWritesConfiguredHeadVisualeffectsAutogenManifest(t *testing.T)
|
||||
{
|
||||
"id": "head_visualeffects",
|
||||
"root": "vfxs",
|
||||
"include": ["chest_accessories/**/*.mdl", "head_accessories/**/*.mdl", "head_decorations/**/*.mdl", "head_features/**/*.mdl"],
|
||||
"include": ["chestaccessory/**/*.mdl", "headaccessory/**/*.mdl", "headdecoration/**/*.mdl", "headfeature/**/*.mdl"],
|
||||
"derive": {
|
||||
"kind": "model_stem",
|
||||
"group_from": "first_path_segment"
|
||||
@@ -2212,7 +2212,7 @@ func TestBuildHAKsWritesConfiguredHeadVisualeffectsAutogenManifest(t *testing.T)
|
||||
},
|
||||
"head_visualeffects": {
|
||||
"groups": {
|
||||
"head_accessories": {
|
||||
"headaccessory": {
|
||||
"prefix": "headaccessory",
|
||||
"model_columns": ["Imp_HeadCon_Node", "Imp_Root_H_Node"]
|
||||
}
|
||||
@@ -2233,10 +2233,10 @@ func TestBuildHAKsWritesConfiguredHeadVisualeffectsAutogenManifest(t *testing.T)
|
||||
}
|
||||
`)
|
||||
|
||||
mustWriteFile(t, filepath.Join(root, "assets", "vfxs", "chest_accessories", "tfx_sash.mdl"), "sash")
|
||||
mustWriteFile(t, filepath.Join(root, "assets", "vfxs", "head_accessories", "hfx_bandana.mdl"), "bandana")
|
||||
mustWriteFile(t, filepath.Join(root, "assets", "vfxs", "head_decorations", "hfx_laurel.mdl"), "laurel")
|
||||
mustWriteFile(t, filepath.Join(root, "assets", "vfxs", "head_features", "hair", "hfx_hair_bangs.mdl"), "hair")
|
||||
mustWriteFile(t, filepath.Join(root, "assets", "vfxs", "chestaccessory", "tfx_sash.mdl"), "sash")
|
||||
mustWriteFile(t, filepath.Join(root, "assets", "vfxs", "headaccessory", "hfx_bandana.mdl"), "bandana")
|
||||
mustWriteFile(t, filepath.Join(root, "assets", "vfxs", "headdecoration", "hfx_laurel.mdl"), "laurel")
|
||||
mustWriteFile(t, filepath.Join(root, "assets", "vfxs", "headfeature", "hair", "hfx_hair_bangs.mdl"), "hair")
|
||||
|
||||
p, err := project.Load(root)
|
||||
if err != nil {
|
||||
@@ -2267,16 +2267,16 @@ func TestBuildHAKsWritesConfiguredHeadVisualeffectsAutogenManifest(t *testing.T)
|
||||
text := string(headRaw)
|
||||
for _, want := range []string{
|
||||
`"id": "head_visualeffects"`,
|
||||
`"group": "chest_accessories"`,
|
||||
`"group": "chestaccessory"`,
|
||||
`"model_stem": "tfx_sash"`,
|
||||
`"group": "head_accessories"`,
|
||||
`"group": "headaccessory"`,
|
||||
`"model_stem": "hfx_bandana"`,
|
||||
`"head_visualeffects": {`,
|
||||
`"model_columns": [`,
|
||||
`"Imp_Root_H_Node"`,
|
||||
`"group": "head_decorations"`,
|
||||
`"group": "headdecoration"`,
|
||||
`"model_stem": "hfx_laurel"`,
|
||||
`"group": "head_features"`,
|
||||
`"group": "headfeature"`,
|
||||
`"model_stem": "hfx_hair_bangs"`,
|
||||
} {
|
||||
if !strings.Contains(text, want) {
|
||||
@@ -2285,6 +2285,103 @@ func TestBuildHAKsWritesConfiguredHeadVisualeffectsAutogenManifest(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildHAKsWritesFolderCategoryInHeadVisualeffectsAutogenManifest(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
mustMkdir(t, filepath.Join(root, "src"))
|
||||
mustMkdir(t, filepath.Join(root, "assets", "vfxs", "headfeature", "sinfar", "ears_plt"))
|
||||
mustMkdir(t, filepath.Join(root, "build"))
|
||||
|
||||
mustWriteFile(t, filepath.Join(root, "nwn-tool.json"), `{
|
||||
"module": {
|
||||
"name": "Test Module",
|
||||
"resref": "testmod",
|
||||
"hak_order": ["group:core"]
|
||||
},
|
||||
"paths": {
|
||||
"source": "src",
|
||||
"assets": "assets",
|
||||
"build": "build"
|
||||
},
|
||||
"autogen": {
|
||||
"producers": [
|
||||
{
|
||||
"id": "head_visualeffects",
|
||||
"root": "vfxs",
|
||||
"include": ["headfeature/**/*.mdl"],
|
||||
"derive": {
|
||||
"kind": "model_stem",
|
||||
"group_from": "first_path_segment"
|
||||
},
|
||||
"manifest": {
|
||||
"release_tag": "head-vfx-manifest-current",
|
||||
"asset_name": "sow-head-vfx-manifest.json",
|
||||
"cache_name": "sow-head-vfx-manifest.json"
|
||||
},
|
||||
"head_visualeffects": {
|
||||
"groups": {
|
||||
"headfeature": {
|
||||
"model_columns": ["Imp_HeadCon_Node", "Imp_Root_H_Node"]
|
||||
}
|
||||
},
|
||||
"group_token_source": "folder_name",
|
||||
"category_from": "immediate_parent",
|
||||
"delimiter": "/",
|
||||
"case": "preserve",
|
||||
"strip_model_prefixes": ["hfx_"],
|
||||
"key_format": "{dataset}:{group}{delimiter}{category_segment}{stem}",
|
||||
"label_format": "{group}{delimiter}{category_segment}{stem}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"haks": [
|
||||
{
|
||||
"name": "core",
|
||||
"priority": 1,
|
||||
"max_bytes": 0,
|
||||
"split": false,
|
||||
"include": ["vfxs/**"]
|
||||
}
|
||||
]
|
||||
}
|
||||
`)
|
||||
|
||||
mustWriteFile(t, filepath.Join(root, "assets", "vfxs", "headfeature", "sinfar", "ears_plt", "hfx_Ear_EL_L.mdl"), "ear")
|
||||
|
||||
p, err := project.Load(root)
|
||||
if err != nil {
|
||||
t.Fatalf("load project: %v", err)
|
||||
}
|
||||
if err := p.ValidateLayout(); err != nil {
|
||||
t.Fatalf("validate layout: %v", err)
|
||||
}
|
||||
if err := p.Scan(); err != nil {
|
||||
t.Fatalf("scan: %v", err)
|
||||
}
|
||||
|
||||
if _, err := BuildHAKs(p); err != nil {
|
||||
t.Fatalf("build haks: %v", err)
|
||||
}
|
||||
headRaw, err := os.ReadFile(filepath.Join(root, "build", "sow-head-vfx-manifest.json"))
|
||||
if err != nil {
|
||||
t.Fatalf("read head visualeffects manifest: %v", err)
|
||||
}
|
||||
text := string(headRaw)
|
||||
for _, want := range []string{
|
||||
`"group": "headfeature"`,
|
||||
`"subgroup": "sinfar/ears_plt"`,
|
||||
`"category": "ears_plt"`,
|
||||
`"model_stem": "hfx_Ear_EL_L"`,
|
||||
`"source": "headfeature/sinfar/ears_plt/hfx_Ear_EL_L.mdl"`,
|
||||
`"group_token_source": "folder_name"`,
|
||||
`"category_from": "immediate_parent"`,
|
||||
} {
|
||||
if !strings.Contains(text, want) {
|
||||
t.Fatalf("expected head visualeffects manifest to contain %q, got:\n%s", want, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildHAKsGeneratesParts2DAAssetsFromLocalModels(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
mustMkdir(t, filepath.Join(root, "src"))
|
||||
|
||||
@@ -388,9 +388,15 @@ type AutogenConsumerConfig struct {
|
||||
|
||||
type HeadVisualeffectsConfig struct {
|
||||
Groups map[string]HeadVisualeffectGroupConfig `json:"groups,omitempty" yaml:"groups"`
|
||||
GroupTokenSource string `json:"group_token_source,omitempty" yaml:"group_token_source"`
|
||||
CategoryFrom string `json:"category_from,omitempty" yaml:"category_from"`
|
||||
Delimiter string `json:"delimiter,omitempty" yaml:"delimiter"`
|
||||
Case string `json:"case,omitempty" yaml:"case"`
|
||||
StripModelPrefixes []string `json:"strip_model_prefixes,omitempty" yaml:"strip_model_prefixes"`
|
||||
KeyFormat string `json:"key_format,omitempty" yaml:"key_format"`
|
||||
LabelFormat string `json:"label_format,omitempty" yaml:"label_format"`
|
||||
LegacyGroups map[string]string `json:"legacy_groups,omitempty" yaml:"legacy_groups"`
|
||||
LegacyKeyFormat string `json:"legacy_key_format,omitempty" yaml:"legacy_key_format"`
|
||||
ModelColumn string `json:"model_column,omitempty" yaml:"model_column"`
|
||||
RowDefaults map[string]string `json:"row_defaults,omitempty" yaml:"row_defaults"`
|
||||
}
|
||||
@@ -1807,15 +1813,45 @@ func validateGeneratedConfig(cfg GeneratedConfig) []error {
|
||||
|
||||
func headVisualeffectsConfigConfigured(cfg HeadVisualeffectsConfig) bool {
|
||||
return len(cfg.Groups) > 0 ||
|
||||
strings.TrimSpace(cfg.GroupTokenSource) != "" ||
|
||||
strings.TrimSpace(cfg.CategoryFrom) != "" ||
|
||||
strings.TrimSpace(cfg.Delimiter) != "" ||
|
||||
strings.TrimSpace(cfg.Case) != "" ||
|
||||
len(cfg.StripModelPrefixes) > 0 ||
|
||||
strings.TrimSpace(cfg.KeyFormat) != "" ||
|
||||
strings.TrimSpace(cfg.LabelFormat) != "" ||
|
||||
len(cfg.LegacyGroups) > 0 ||
|
||||
strings.TrimSpace(cfg.LegacyKeyFormat) != "" ||
|
||||
strings.TrimSpace(cfg.ModelColumn) != "" ||
|
||||
len(cfg.RowDefaults) > 0
|
||||
}
|
||||
|
||||
func validateHeadVisualeffectsConfig(fieldPrefix string, cfg HeadVisualeffectsConfig) []error {
|
||||
var failures []error
|
||||
groupTokenSource := strings.TrimSpace(cfg.GroupTokenSource)
|
||||
switch groupTokenSource {
|
||||
case "", "prefix", "folder_name":
|
||||
default:
|
||||
failures = append(failures, fmt.Errorf("%s.group_token_source %q is not supported", fieldPrefix, cfg.GroupTokenSource))
|
||||
}
|
||||
categoryFrom := strings.TrimSpace(cfg.CategoryFrom)
|
||||
switch categoryFrom {
|
||||
case "", "none", "immediate_parent", "full_subgroup":
|
||||
default:
|
||||
failures = append(failures, fmt.Errorf("%s.category_from %q is not supported", fieldPrefix, cfg.CategoryFrom))
|
||||
}
|
||||
if strings.TrimSpace(cfg.Delimiter) == "" && cfg.Delimiter != "" {
|
||||
failures = append(failures, fmt.Errorf("%s.delimiter must not be only whitespace", fieldPrefix))
|
||||
}
|
||||
if strings.ContainsAny(cfg.Delimiter, " \t\r\n\x00") {
|
||||
failures = append(failures, fmt.Errorf("%s.delimiter must not contain whitespace or NUL bytes", fieldPrefix))
|
||||
}
|
||||
caseMode := strings.TrimSpace(cfg.Case)
|
||||
switch caseMode {
|
||||
case "", "preserve", "lower", "upper":
|
||||
default:
|
||||
failures = append(failures, fmt.Errorf("%s.case %q is not supported", fieldPrefix, cfg.Case))
|
||||
}
|
||||
for group, groupCfg := range cfg.Groups {
|
||||
group = strings.TrimSpace(group)
|
||||
if group == "" {
|
||||
@@ -1824,7 +1860,7 @@ func validateHeadVisualeffectsConfig(fieldPrefix string, cfg HeadVisualeffectsCo
|
||||
}
|
||||
failures = append(failures, validateTreeRootPath(fieldPrefix+".groups["+group+"]", group)...)
|
||||
prefix := strings.TrimSpace(groupCfg.Prefix)
|
||||
if prefix == "" {
|
||||
if prefix == "" && groupTokenSource != "folder_name" {
|
||||
failures = append(failures, fmt.Errorf("%s.groups[%s].prefix is required", fieldPrefix, group))
|
||||
}
|
||||
if strings.ContainsAny(prefix, " \t\r\n\x00") {
|
||||
@@ -1852,6 +1888,21 @@ func validateHeadVisualeffectsConfig(fieldPrefix string, cfg HeadVisualeffectsCo
|
||||
}
|
||||
}
|
||||
}
|
||||
for legacyGroup, group := range cfg.LegacyGroups {
|
||||
legacyGroup = strings.TrimSpace(legacyGroup)
|
||||
group = strings.TrimSpace(group)
|
||||
if legacyGroup == "" {
|
||||
failures = append(failures, fmt.Errorf("%s.legacy_groups contains an empty legacy group key", fieldPrefix))
|
||||
continue
|
||||
}
|
||||
if group == "" {
|
||||
failures = append(failures, fmt.Errorf("%s.legacy_groups[%s] must not be empty", fieldPrefix, legacyGroup))
|
||||
}
|
||||
failures = append(failures, validateTreeRootPath(fieldPrefix+".legacy_groups["+legacyGroup+"]", legacyGroup)...)
|
||||
if group != "" {
|
||||
failures = append(failures, validateTreeRootPath(fieldPrefix+".legacy_groups["+legacyGroup+"]", group)...)
|
||||
}
|
||||
}
|
||||
for index, prefix := range cfg.StripModelPrefixes {
|
||||
if strings.TrimSpace(prefix) == "" {
|
||||
failures = append(failures, fmt.Errorf("%s.strip_model_prefixes[%d] must not be empty", fieldPrefix, index))
|
||||
|
||||
@@ -1332,7 +1332,7 @@ func TestValidateLayoutRejectsInvalidAutogenConsumerConfig(t *testing.T) {
|
||||
Dataset: "visualeffects",
|
||||
Mode: "unsupported",
|
||||
Root: "vfxs",
|
||||
Include: []string{"head_accessories/**/*.mdl"},
|
||||
Include: []string{"headaccessory/**/*.mdl"},
|
||||
Derive: AutogenDeriveConfig{Kind: "model_stem", GroupFrom: "first_path_segment"},
|
||||
Manifest: AutogenManifestConfig{
|
||||
ReleaseTag: "head-vfx-manifest-current",
|
||||
@@ -1416,6 +1416,66 @@ func TestValidateLayoutRejectsInvalidHeadVisualeffectsConfig(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateLayoutRejectsInvalidHeadVisualeffectsNamingConfig(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
mkdirAll(t, filepath.Join(root, "src"))
|
||||
mkdirAll(t, filepath.Join(root, "build"))
|
||||
|
||||
proj := &Project{
|
||||
Root: root,
|
||||
Config: Config{
|
||||
Module: ModuleConfig{Name: "Test", ResRef: "test"},
|
||||
Paths: PathConfig{Source: "src", Build: "build"},
|
||||
Autogen: AutogenConfig{
|
||||
Consumers: []AutogenConsumerConfig{
|
||||
{
|
||||
ID: "head_visualeffects",
|
||||
Producer: "head_visualeffects",
|
||||
Dataset: "visualeffects",
|
||||
Mode: "head_visualeffects",
|
||||
Root: "vfxs",
|
||||
Include: []string{"headfeature/**/*.mdl"},
|
||||
Derive: AutogenDeriveConfig{Kind: "model_stem", GroupFrom: "first_path_segment"},
|
||||
Manifest: AutogenManifestConfig{
|
||||
ReleaseTag: "head-vfx-manifest-current",
|
||||
AssetName: "sow-head-vfx-manifest.json",
|
||||
CacheName: "sow-head-vfx-manifest.json",
|
||||
},
|
||||
HeadVisualeffects: HeadVisualeffectsConfig{
|
||||
Groups: map[string]HeadVisualeffectGroupConfig{
|
||||
"headfeature": {},
|
||||
},
|
||||
GroupTokenSource: "unsupported",
|
||||
CategoryFrom: "unsupported",
|
||||
Delimiter: " ",
|
||||
Case: "unsupported",
|
||||
LegacyGroups: map[string]string{
|
||||
"head_features": "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := proj.ValidateLayout()
|
||||
if err == nil {
|
||||
t.Fatal("expected head visualeffects naming validation error")
|
||||
}
|
||||
for _, needle := range []string{
|
||||
"autogen.consumers[0].head_visualeffects.group_token_source",
|
||||
"autogen.consumers[0].head_visualeffects.category_from",
|
||||
"autogen.consumers[0].head_visualeffects.delimiter",
|
||||
"autogen.consumers[0].head_visualeffects.case",
|
||||
"autogen.consumers[0].head_visualeffects.legacy_groups[head_features]",
|
||||
} {
|
||||
if !strings.Contains(err.Error(), needle) {
|
||||
t.Fatalf("expected validation error to mention %s, got %v", needle, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateLayoutAcceptsGeneratedTopData2DAConfig(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
mkdirAll(t, filepath.Join(root, "assets"))
|
||||
@@ -1460,7 +1520,7 @@ func TestValidateLayoutAcceptsGeneratedTopData2DAConfig(t *testing.T) {
|
||||
ID: "cachedmodels",
|
||||
Mode: "cachedmodels_rows",
|
||||
Root: "vfxs",
|
||||
Include: []string{"head_accessories/**/*.mdl", "head_features/**/*.mdl"},
|
||||
Include: []string{"headaccessory/**/*.mdl", "headfeature/**/*.mdl"},
|
||||
Derive: AutogenDeriveConfig{Kind: "model_stem", GroupFrom: "first_path_segment"},
|
||||
},
|
||||
},
|
||||
|
||||
+194
-16
@@ -29,6 +29,7 @@ type autogenManifestEntry struct {
|
||||
Source string `json:"source"`
|
||||
Group string `json:"group,omitempty"`
|
||||
Subgroup string `json:"subgroup,omitempty"`
|
||||
Category string `json:"category,omitempty"`
|
||||
ModelStem string `json:"model_stem,omitempty"`
|
||||
RowID int `json:"row_id,omitempty"`
|
||||
}
|
||||
@@ -319,6 +320,7 @@ func deriveAutogenManifestEntry(rel string, derive project.AutogenDeriveConfig)
|
||||
entry.Group = parts[0]
|
||||
if len(parts) > 2 {
|
||||
entry.Subgroup = strings.Join(parts[1:len(parts)-1], "/")
|
||||
entry.Category = parts[len(parts)-2]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -740,11 +742,26 @@ func augmentWithAutogeneratedHeadVisualeffects(collected []nativeCollectedDatase
|
||||
|
||||
rowID, ok := lockData[key]
|
||||
if !ok {
|
||||
var migratedLegacyKey string
|
||||
if preservedRowID, preserved := historicalLockData[key]; preserved {
|
||||
rowID = preservedRowID
|
||||
} else {
|
||||
preservedLegacyID := false
|
||||
for _, legacyKey := range headVisualeffectLegacyKeys(dataset.Dataset.Name, entry, policy) {
|
||||
if preservedRowID, preserved := historicalLockData[legacyKey]; preserved {
|
||||
rowID = preservedRowID
|
||||
preservedLegacyID = true
|
||||
migratedLegacyKey = legacyKey
|
||||
break
|
||||
}
|
||||
}
|
||||
if !preservedLegacyID {
|
||||
rowID = allocateNextID()
|
||||
}
|
||||
}
|
||||
if migratedLegacyKey != "" && migratedLegacyKey != key {
|
||||
delete(lockData, migratedLegacyKey)
|
||||
}
|
||||
lockData[key] = rowID
|
||||
}
|
||||
newRow := createDefaultHeadVisualeffectRow(dataset.Columns, rowID, key, label, modelStem, policy, groupPolicy)
|
||||
@@ -772,9 +789,15 @@ func nextAvailableAutogenID(used map[int]struct{}) int {
|
||||
|
||||
type headVisualeffectsPolicy struct {
|
||||
Groups map[string]headVisualeffectsGroupPolicy
|
||||
GroupTokenSource string
|
||||
CategoryFrom string
|
||||
Delimiter string
|
||||
Case string
|
||||
StripModelPrefixes []string
|
||||
KeyFormat string
|
||||
LabelFormat string
|
||||
LegacyGroups map[string]string
|
||||
LegacyKeyFormat string
|
||||
ModelColumn string
|
||||
RowDefaults map[string]string
|
||||
}
|
||||
@@ -798,11 +821,13 @@ func applyHeadVisualeffectsConfig(policy *headVisualeffectsPolicy, cfg project.H
|
||||
for group, groupCfg := range cfg.Groups {
|
||||
group = strings.TrimSpace(group)
|
||||
prefix := strings.TrimSpace(groupCfg.Prefix)
|
||||
if group == "" || prefix == "" {
|
||||
if group == "" {
|
||||
continue
|
||||
}
|
||||
groupPolicy := policy.Groups[group]
|
||||
if prefix != "" {
|
||||
groupPolicy.Prefix = prefix
|
||||
}
|
||||
if columns := normalizeHeadVisualeffectsModelColumns(groupCfg.ModelColumn, groupCfg.ModelColumns); len(columns) > 0 {
|
||||
groupPolicy.ModelColumns = columns
|
||||
}
|
||||
@@ -812,6 +837,18 @@ func applyHeadVisualeffectsConfig(policy *headVisualeffectsPolicy, cfg project.H
|
||||
mergeStringMap(groupPolicy.RowDefaults, normalizeHeadVisualeffectsRowDefaults(groupCfg.RowDefaults))
|
||||
policy.Groups[group] = groupPolicy
|
||||
}
|
||||
if strings.TrimSpace(cfg.GroupTokenSource) != "" {
|
||||
policy.GroupTokenSource = strings.TrimSpace(cfg.GroupTokenSource)
|
||||
}
|
||||
if strings.TrimSpace(cfg.CategoryFrom) != "" {
|
||||
policy.CategoryFrom = strings.TrimSpace(cfg.CategoryFrom)
|
||||
}
|
||||
if cfg.Delimiter != "" {
|
||||
policy.Delimiter = cfg.Delimiter
|
||||
}
|
||||
if strings.TrimSpace(cfg.Case) != "" {
|
||||
policy.Case = strings.TrimSpace(cfg.Case)
|
||||
}
|
||||
if len(cfg.StripModelPrefixes) > 0 {
|
||||
policy.StripModelPrefixes = append([]string(nil), cfg.StripModelPrefixes...)
|
||||
}
|
||||
@@ -821,6 +858,22 @@ func applyHeadVisualeffectsConfig(policy *headVisualeffectsPolicy, cfg project.H
|
||||
if strings.TrimSpace(cfg.LabelFormat) != "" {
|
||||
policy.LabelFormat = strings.TrimSpace(cfg.LabelFormat)
|
||||
}
|
||||
if len(cfg.LegacyGroups) > 0 {
|
||||
if policy.LegacyGroups == nil {
|
||||
policy.LegacyGroups = map[string]string{}
|
||||
}
|
||||
for legacyGroup, group := range cfg.LegacyGroups {
|
||||
legacyGroup = strings.TrimSpace(legacyGroup)
|
||||
group = strings.TrimSpace(group)
|
||||
if legacyGroup == "" || group == "" {
|
||||
continue
|
||||
}
|
||||
policy.LegacyGroups[legacyGroup] = group
|
||||
}
|
||||
}
|
||||
if strings.TrimSpace(cfg.LegacyKeyFormat) != "" {
|
||||
policy.LegacyKeyFormat = strings.TrimSpace(cfg.LegacyKeyFormat)
|
||||
}
|
||||
if strings.TrimSpace(cfg.ModelColumn) != "" {
|
||||
policy.ModelColumn = strings.TrimSpace(cfg.ModelColumn)
|
||||
}
|
||||
@@ -834,6 +887,10 @@ func defaultHeadVisualeffectsPolicy() headVisualeffectsPolicy {
|
||||
"head_decorations": {Prefix: "headdecoration"},
|
||||
"head_features": {Prefix: "headfeature"},
|
||||
},
|
||||
GroupTokenSource: "prefix",
|
||||
CategoryFrom: "none",
|
||||
Delimiter: "_",
|
||||
Case: "preserve",
|
||||
StripModelPrefixes: []string{"hfx_", "vfx_"},
|
||||
KeyFormat: "{dataset}:{prefix}_{stem}",
|
||||
LabelFormat: "{prefix}_{stem_upper}",
|
||||
@@ -882,11 +939,104 @@ func mergeStringMap(target map[string]string, source map[string]string) {
|
||||
}
|
||||
|
||||
func headVisualeffectIdentity(dataset string, entry autogenManifestEntry, policy headVisualeffectsPolicy) (string, string, string, headVisualeffectsGroupPolicy, bool) {
|
||||
entry = normalizeHeadVisualeffectEntry(entry, policy)
|
||||
group, ok := policy.Groups[entry.Group]
|
||||
if !ok || strings.TrimSpace(entry.ModelStem) == "" {
|
||||
return "", "", "", headVisualeffectsGroupPolicy{}, false
|
||||
}
|
||||
modelStem := strings.TrimSpace(entry.ModelStem)
|
||||
stem := stripHeadVisualeffectModelPrefix(modelStem, policy)
|
||||
stem = strings.TrimSpace(stem)
|
||||
if stem == "" {
|
||||
return "", "", "", headVisualeffectsGroupPolicy{}, false
|
||||
}
|
||||
category := headVisualeffectCategory(entry, policy)
|
||||
groupToken := headVisualeffectGroupToken(entry.Group, group, policy)
|
||||
values := map[string]string{
|
||||
"dataset": dataset,
|
||||
"group": applyHeadVisualeffectCase(groupToken, policy),
|
||||
"group_raw": entry.Group,
|
||||
"prefix": applyHeadVisualeffectCase(group.Prefix, policy),
|
||||
"category": applyHeadVisualeffectCase(category, policy),
|
||||
"category_upper": strings.ToUpper(category),
|
||||
"category_segment": headVisualeffectDelimitedSegment(applyHeadVisualeffectCase(category, policy), policy.Delimiter),
|
||||
"category_segment_upper": headVisualeffectDelimitedSegment(strings.ToUpper(category), policy.Delimiter),
|
||||
"subgroup": applyHeadVisualeffectCase(entry.Subgroup, policy),
|
||||
"stem": applyHeadVisualeffectCase(stem, policy),
|
||||
"stem_upper": strings.ToUpper(stem),
|
||||
"model_stem": modelStem,
|
||||
"delimiter": policy.Delimiter,
|
||||
}
|
||||
key := expandHeadVisualeffectsFormat(policy.KeyFormat, values)
|
||||
label := expandHeadVisualeffectsFormat(policy.LabelFormat, values)
|
||||
if strings.TrimSpace(key) == "" || strings.TrimSpace(label) == "" {
|
||||
return "", "", "", headVisualeffectsGroupPolicy{}, false
|
||||
}
|
||||
return key, label, modelStem, group, true
|
||||
}
|
||||
|
||||
func headVisualeffectLegacyKeys(dataset string, entry autogenManifestEntry, policy headVisualeffectsPolicy) []string {
|
||||
if strings.TrimSpace(policy.LegacyKeyFormat) == "" {
|
||||
return nil
|
||||
}
|
||||
entry = normalizeHeadVisualeffectEntry(entry, policy)
|
||||
group, ok := policy.Groups[entry.Group]
|
||||
if !ok || strings.TrimSpace(entry.ModelStem) == "" {
|
||||
return nil
|
||||
}
|
||||
modelStem := strings.TrimSpace(entry.ModelStem)
|
||||
stem := strings.TrimSpace(stripHeadVisualeffectModelPrefix(modelStem, policy))
|
||||
if stem == "" {
|
||||
return nil
|
||||
}
|
||||
groupToken := headVisualeffectGroupToken(entry.Group, group, policy)
|
||||
values := map[string]string{
|
||||
"dataset": dataset,
|
||||
"group": applyHeadVisualeffectCase(groupToken, policy),
|
||||
"group_raw": entry.Group,
|
||||
"prefix": applyHeadVisualeffectCase(group.Prefix, policy),
|
||||
"stem": applyHeadVisualeffectCase(stem, policy),
|
||||
"stem_upper": strings.ToUpper(stem),
|
||||
"model_stem": modelStem,
|
||||
"delimiter": policy.Delimiter,
|
||||
}
|
||||
legacyKey := strings.TrimSpace(expandHeadVisualeffectsFormat(policy.LegacyKeyFormat, values))
|
||||
if legacyKey == "" {
|
||||
return nil
|
||||
}
|
||||
return []string{legacyKey}
|
||||
}
|
||||
|
||||
func normalizeHeadVisualeffectEntry(entry autogenManifestEntry, policy headVisualeffectsPolicy) autogenManifestEntry {
|
||||
entry.Group = strings.TrimSpace(entry.Group)
|
||||
if mapped, ok := policy.LegacyGroups[entry.Group]; ok && strings.TrimSpace(mapped) != "" {
|
||||
entry.Group = strings.TrimSpace(mapped)
|
||||
}
|
||||
if strings.TrimSpace(entry.Group) == "" && strings.TrimSpace(entry.Source) != "" {
|
||||
parts := strings.Split(filepath.ToSlash(entry.Source), "/")
|
||||
if len(parts) > 1 {
|
||||
entry.Group = parts[0]
|
||||
if mapped, ok := policy.LegacyGroups[entry.Group]; ok && strings.TrimSpace(mapped) != "" {
|
||||
entry.Group = strings.TrimSpace(mapped)
|
||||
}
|
||||
}
|
||||
}
|
||||
if strings.TrimSpace(entry.Subgroup) == "" && strings.TrimSpace(entry.Source) != "" {
|
||||
parts := strings.Split(filepath.ToSlash(entry.Source), "/")
|
||||
if len(parts) > 2 {
|
||||
entry.Subgroup = strings.Join(parts[1:len(parts)-1], "/")
|
||||
}
|
||||
}
|
||||
if strings.TrimSpace(entry.Category) == "" && strings.TrimSpace(entry.Source) != "" {
|
||||
parts := strings.Split(filepath.ToSlash(entry.Source), "/")
|
||||
if len(parts) > 2 {
|
||||
entry.Category = parts[len(parts)-2]
|
||||
}
|
||||
}
|
||||
return entry
|
||||
}
|
||||
|
||||
func stripHeadVisualeffectModelPrefix(modelStem string, policy headVisualeffectsPolicy) string {
|
||||
stem := modelStem
|
||||
for _, prefix := range policy.StripModelPrefixes {
|
||||
prefix = strings.TrimSpace(prefix)
|
||||
@@ -895,24 +1045,46 @@ func headVisualeffectIdentity(dataset string, entry autogenManifestEntry, policy
|
||||
}
|
||||
stem = strings.TrimPrefix(stem, prefix)
|
||||
}
|
||||
stem = strings.TrimSpace(stem)
|
||||
if stem == "" {
|
||||
return "", "", "", headVisualeffectsGroupPolicy{}, false
|
||||
return stem
|
||||
}
|
||||
values := map[string]string{
|
||||
"dataset": dataset,
|
||||
"group": entry.Group,
|
||||
"prefix": group.Prefix,
|
||||
"stem": stem,
|
||||
"stem_upper": strings.ToUpper(stem),
|
||||
"model_stem": modelStem,
|
||||
|
||||
func headVisualeffectGroupToken(groupName string, group headVisualeffectsGroupPolicy, policy headVisualeffectsPolicy) string {
|
||||
if policy.GroupTokenSource == "folder_name" {
|
||||
return groupName
|
||||
}
|
||||
key := expandHeadVisualeffectsFormat(policy.KeyFormat, values)
|
||||
label := expandHeadVisualeffectsFormat(policy.LabelFormat, values)
|
||||
if strings.TrimSpace(key) == "" || strings.TrimSpace(label) == "" {
|
||||
return "", "", "", headVisualeffectsGroupPolicy{}, false
|
||||
if strings.TrimSpace(group.Prefix) != "" {
|
||||
return group.Prefix
|
||||
}
|
||||
return groupName
|
||||
}
|
||||
|
||||
func headVisualeffectCategory(entry autogenManifestEntry, policy headVisualeffectsPolicy) string {
|
||||
switch policy.CategoryFrom {
|
||||
case "immediate_parent":
|
||||
return strings.TrimSpace(entry.Category)
|
||||
case "full_subgroup":
|
||||
return strings.TrimSpace(entry.Subgroup)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func headVisualeffectDelimitedSegment(value, delimiter string) string {
|
||||
if strings.TrimSpace(value) == "" {
|
||||
return ""
|
||||
}
|
||||
return value + delimiter
|
||||
}
|
||||
|
||||
func applyHeadVisualeffectCase(value string, policy headVisualeffectsPolicy) string {
|
||||
switch policy.Case {
|
||||
case "lower":
|
||||
return strings.ToLower(value)
|
||||
case "upper":
|
||||
return strings.ToUpper(value)
|
||||
default:
|
||||
return value
|
||||
}
|
||||
return key, label, modelStem, group, true
|
||||
}
|
||||
|
||||
func expandHeadVisualeffectsFormat(format string, values map[string]string) string {
|
||||
@@ -997,9 +1169,15 @@ func formatAutogenManifest(root string, producer project.AutogenProducerConfig,
|
||||
|
||||
func autogenHeadVisualeffectsConfigConfigured(cfg project.HeadVisualeffectsConfig) bool {
|
||||
return len(cfg.Groups) > 0 ||
|
||||
strings.TrimSpace(cfg.GroupTokenSource) != "" ||
|
||||
strings.TrimSpace(cfg.CategoryFrom) != "" ||
|
||||
cfg.Delimiter != "" ||
|
||||
strings.TrimSpace(cfg.Case) != "" ||
|
||||
len(cfg.StripModelPrefixes) > 0 ||
|
||||
strings.TrimSpace(cfg.KeyFormat) != "" ||
|
||||
strings.TrimSpace(cfg.LabelFormat) != "" ||
|
||||
len(cfg.LegacyGroups) > 0 ||
|
||||
strings.TrimSpace(cfg.LegacyKeyFormat) != "" ||
|
||||
strings.TrimSpace(cfg.ModelColumn) != "" ||
|
||||
len(cfg.RowDefaults) > 0
|
||||
}
|
||||
|
||||
@@ -7409,12 +7409,150 @@ func TestApplyAutogenConsumersAugmentsHeadVisualeffectsFromLocalOverride(t *test
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyAutogenConsumersUsesFolderDrivenSlashHeadVisualeffectsNames(t *testing.T) {
|
||||
root := testProjectRoot(t)
|
||||
overrideRoot := filepath.Join(root, "autogen-assets")
|
||||
mkdirAll(t, filepath.Join(overrideRoot, "vfxs", "headfeature", "sinfar", "ears_plt"))
|
||||
writeFile(t, filepath.Join(overrideRoot, "vfxs", "headfeature", "sinfar", "ears_plt", "hfx_Ear_EL_L.mdl"), "mdl\n")
|
||||
|
||||
p := testProject(root)
|
||||
p.Config.Autogen.Consumers = []project.AutogenConsumerConfig{
|
||||
{
|
||||
ID: "head_visualeffects",
|
||||
Producer: "head_visualeffects",
|
||||
Dataset: "visualeffects",
|
||||
Mode: "head_visualeffects",
|
||||
Root: "vfxs",
|
||||
Include: []string{"headfeature/**/*.mdl"},
|
||||
Derive: project.AutogenDeriveConfig{Kind: "model_stem", GroupFrom: "first_path_segment"},
|
||||
Manifest: project.AutogenManifestConfig{ReleaseTag: "head-vfx-manifest-current", AssetName: "sow-head-vfx-manifest.json", CacheName: "sow-head-vfx-manifest.json"},
|
||||
LocalOverrideRoot: "autogen-assets",
|
||||
HeadVisualeffects: project.HeadVisualeffectsConfig{
|
||||
Groups: map[string]project.HeadVisualeffectGroupConfig{
|
||||
"headfeature": {
|
||||
ModelColumns: []string{"Imp_HeadCon_Node", "Imp_Root_H_Node"},
|
||||
},
|
||||
},
|
||||
GroupTokenSource: "folder_name",
|
||||
CategoryFrom: "immediate_parent",
|
||||
Delimiter: "/",
|
||||
Case: "preserve",
|
||||
StripModelPrefixes: []string{"hfx_"},
|
||||
KeyFormat: "{dataset}:{group}{delimiter}{category_segment}{stem}",
|
||||
LabelFormat: "{group}{delimiter}{category_segment}{stem}",
|
||||
LegacyKeyFormat: "{dataset}:{group}_{stem}",
|
||||
RowDefaults: map[string]string{
|
||||
"Type_FD": "D",
|
||||
"OrientWithGround": "0",
|
||||
"OrientWithObject": "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
collected := []nativeCollectedDataset{
|
||||
{
|
||||
Dataset: nativeDataset{Name: "visualeffects", Kind: nativeDatasetBase},
|
||||
Columns: []string{"Label", "Type_FD", "OrientWithGround", "Imp_HeadCon_Node", "Imp_Root_H_Node", "OrientWithObject"},
|
||||
Rows: nil,
|
||||
LockData: map[string]int{
|
||||
"visualeffects:headfeature_Ear_EL_L": 11030,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
got, err := applyAutogenConsumers(p, collected, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("applyAutogenConsumers failed: %v", err)
|
||||
}
|
||||
if len(got) != 1 || len(got[0].Rows) != 1 {
|
||||
t.Fatalf("expected one autogenerated visualeffects row, got %#v", got)
|
||||
}
|
||||
row := got[0].Rows[0]
|
||||
if row["key"] != "visualeffects:headfeature/ears_plt/Ear_EL_L" {
|
||||
t.Fatalf("expected folder-driven key, got %#v", row)
|
||||
}
|
||||
if row["id"] != 11030 {
|
||||
t.Fatalf("expected legacy lock id to be preserved, got %#v", row)
|
||||
}
|
||||
if row["Label"] != "headfeature/ears_plt/Ear_EL_L" {
|
||||
t.Fatalf("expected preserve-case slash label, got %#v", row)
|
||||
}
|
||||
if row["Imp_HeadCon_Node"] != "hfx_Ear_EL_L" || row["Imp_Root_H_Node"] != "hfx_Ear_EL_L" {
|
||||
t.Fatalf("expected configured model columns, got %#v", row)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeadVisualeffectsConsumerMapsLegacyManifestGroupsToFolderDrivenNames(t *testing.T) {
|
||||
collected := []nativeCollectedDataset{
|
||||
{
|
||||
Dataset: nativeDataset{Name: "visualeffects", Kind: nativeDatasetBase},
|
||||
Columns: []string{"Label", "Type_FD", "OrientWithGround", "Imp_HeadCon_Node", "Imp_Root_H_Node", "OrientWithObject"},
|
||||
Rows: nil,
|
||||
LockData: map[string]int{
|
||||
"visualeffects:headfeature_ear_el_l": 11030,
|
||||
},
|
||||
},
|
||||
}
|
||||
entries := []autogenManifestEntry{
|
||||
{
|
||||
Group: "head_features",
|
||||
Source: "head_features/sinfar/ears_plt/hfx_ear_el_l.mdl",
|
||||
ModelStem: "hfx_ear_el_l",
|
||||
},
|
||||
}
|
||||
consumer := project.AutogenConsumerConfig{
|
||||
Dataset: "visualeffects",
|
||||
HeadVisualeffects: project.HeadVisualeffectsConfig{
|
||||
Groups: map[string]project.HeadVisualeffectGroupConfig{
|
||||
"headfeature": {
|
||||
ModelColumns: []string{"Imp_HeadCon_Node", "Imp_Root_H_Node"},
|
||||
},
|
||||
},
|
||||
GroupTokenSource: "folder_name",
|
||||
CategoryFrom: "immediate_parent",
|
||||
Delimiter: "/",
|
||||
Case: "preserve",
|
||||
StripModelPrefixes: []string{
|
||||
"hfx_",
|
||||
},
|
||||
KeyFormat: "{dataset}:{group}{delimiter}{category_segment}{stem}",
|
||||
LabelFormat: "{group}{delimiter}{category_segment}{stem}",
|
||||
LegacyGroups: map[string]string{"head_features": "headfeature"},
|
||||
LegacyKeyFormat: "{dataset}:{group}_{stem}",
|
||||
RowDefaults: map[string]string{
|
||||
"Type_FD": "D",
|
||||
"OrientWithGround": "0",
|
||||
"OrientWithObject": "1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
got, err := augmentWithAutogeneratedHeadVisualeffects(collected, entries, consumer, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("augmentWithAutogeneratedHeadVisualeffects failed: %v", err)
|
||||
}
|
||||
if len(got) != 1 || len(got[0].Rows) != 1 {
|
||||
t.Fatalf("expected one autogenerated row, got %#v", got)
|
||||
}
|
||||
row := got[0].Rows[0]
|
||||
if row["key"] != "visualeffects:headfeature/ears_plt/ear_el_l" || row["id"] != 11030 {
|
||||
t.Fatalf("expected migrated slash key with preserved ID, got %#v", row)
|
||||
}
|
||||
if _, exists := got[0].LockData["visualeffects:headfeature/ears_plt/ear_el_l"]; !exists {
|
||||
t.Fatalf("expected new slash key in lock data, got %#v", got[0].LockData)
|
||||
}
|
||||
if _, exists := got[0].LockData["visualeffects:headfeature_ear_el_l"]; exists {
|
||||
t.Fatalf("expected legacy key to be removed from active lock data, got %#v", got[0].LockData)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildNativeUsesConfiguredHeadVisualeffectsGroups(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
mkdirAll(t, filepath.Join(root, "src"))
|
||||
mkdirAll(t, filepath.Join(root, "build"))
|
||||
mkdirAll(t, filepath.Join(root, "topdata", "data", "visualeffects"))
|
||||
mkdirAll(t, filepath.Join(root, "autogen-assets", "vfxs", "chest_accessories"))
|
||||
mkdirAll(t, filepath.Join(root, "autogen-assets", "vfxs", "chestaccessory"))
|
||||
mkdirAll(t, filepath.Join(root, "autogen-assets", "vfxs", "head_jewels"))
|
||||
writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n")
|
||||
writeFile(t, filepath.Join(root, "topdata", "data", "visualeffects", "base.json"), `{
|
||||
@@ -7422,7 +7560,7 @@ func TestBuildNativeUsesConfiguredHeadVisualeffectsGroups(t *testing.T) {
|
||||
"columns": ["Label", "Type_FD", "OrientWithGround", "Imp_HeadCon_Node", "Imp_Root_S_Node", "Imp_Root_M_Node", "Imp_Root_L_Node", "Imp_Root_H_Node", "OrientWithObject"],
|
||||
"rows": []
|
||||
}`+"\n")
|
||||
writeFile(t, filepath.Join(root, "autogen-assets", "vfxs", "chest_accessories", "tfx_sash.mdl"), "mdl\n")
|
||||
writeFile(t, filepath.Join(root, "autogen-assets", "vfxs", "chestaccessory", "tfx_sash.mdl"), "mdl\n")
|
||||
writeFile(t, filepath.Join(root, "autogen-assets", "vfxs", "head_jewels", "hfx_circlet.mdl"), "mdl\n")
|
||||
writeFile(t, filepath.Join(root, "nwn-tool.yaml"), `
|
||||
module:
|
||||
@@ -7442,7 +7580,7 @@ autogen:
|
||||
mode: head_visualeffects
|
||||
root: vfxs
|
||||
include:
|
||||
- chest_accessories/**/*.mdl
|
||||
- chestaccessory/**/*.mdl
|
||||
- head_jewels/**/*.mdl
|
||||
derive:
|
||||
kind: model_stem
|
||||
@@ -7454,7 +7592,7 @@ autogen:
|
||||
local_override_root: autogen-assets
|
||||
head_visualeffects:
|
||||
groups:
|
||||
chest_accessories:
|
||||
chestaccessory:
|
||||
prefix: chestaccessory
|
||||
model_columns:
|
||||
- Imp_Root_S_Node
|
||||
@@ -7499,16 +7637,16 @@ autogen:
|
||||
|
||||
func TestBuildGenerated2DAAssetsAppendsCachedModelsFromHeadVisualeffects(t *testing.T) {
|
||||
root := testProjectRoot(t)
|
||||
mkdirAll(t, filepath.Join(root, "assets", "vfxs", "chest_accessories"))
|
||||
mkdirAll(t, filepath.Join(root, "assets", "vfxs", "head_accessories"))
|
||||
mkdirAll(t, filepath.Join(root, "assets", "vfxs", "head_decorations"))
|
||||
mkdirAll(t, filepath.Join(root, "assets", "vfxs", "head_features", "hair"))
|
||||
mkdirAll(t, filepath.Join(root, "assets", "vfxs", "chestaccessory"))
|
||||
mkdirAll(t, filepath.Join(root, "assets", "vfxs", "headaccessory"))
|
||||
mkdirAll(t, filepath.Join(root, "assets", "vfxs", "headdecoration"))
|
||||
mkdirAll(t, filepath.Join(root, "assets", "vfxs", "headfeature", "hair"))
|
||||
mkdirAll(t, filepath.Join(root, "topdata", "data", "cachedmodels"))
|
||||
writeFile(t, filepath.Join(root, "assets", "vfxs", "chest_accessories", "tfx_sash.mdl"), "mdl\n")
|
||||
writeFile(t, filepath.Join(root, "assets", "vfxs", "head_accessories", "hfx_bandana.mdl"), "mdl\n")
|
||||
writeFile(t, filepath.Join(root, "assets", "vfxs", "head_accessories", "hfx_existing.mdl"), "mdl\n")
|
||||
writeFile(t, filepath.Join(root, "assets", "vfxs", "head_decorations", "hfx_laurel.mdl"), "mdl\n")
|
||||
writeFile(t, filepath.Join(root, "assets", "vfxs", "head_features", "hair", "hfx_hair_bangs.mdl"), "mdl\n")
|
||||
writeFile(t, filepath.Join(root, "assets", "vfxs", "chestaccessory", "tfx_sash.mdl"), "mdl\n")
|
||||
writeFile(t, filepath.Join(root, "assets", "vfxs", "headaccessory", "hfx_bandana.mdl"), "mdl\n")
|
||||
writeFile(t, filepath.Join(root, "assets", "vfxs", "headaccessory", "hfx_existing.mdl"), "mdl\n")
|
||||
writeFile(t, filepath.Join(root, "assets", "vfxs", "headdecoration", "hfx_laurel.mdl"), "mdl\n")
|
||||
writeFile(t, filepath.Join(root, "assets", "vfxs", "headfeature", "hair", "hfx_hair_bangs.mdl"), "mdl\n")
|
||||
writeFile(t, filepath.Join(root, "topdata", "data", "cachedmodels", "base.json"), `{
|
||||
"columns": ["Model"],
|
||||
"rows": [
|
||||
@@ -7530,7 +7668,7 @@ func TestBuildGenerated2DAAssetsAppendsCachedModelsFromHeadVisualeffects(t *test
|
||||
ID: "cachedmodels",
|
||||
Mode: "cachedmodels_rows",
|
||||
Root: "vfxs",
|
||||
Include: []string{"chest_accessories/**/*.mdl", "head_accessories/**/*.mdl", "head_decorations/**/*.mdl", "head_features/**/*.mdl"},
|
||||
Include: []string{"chestaccessory/**/*.mdl", "headaccessory/**/*.mdl", "headdecoration/**/*.mdl", "headfeature/**/*.mdl"},
|
||||
Derive: project.AutogenDeriveConfig{Kind: "model_stem", GroupFrom: "first_path_segment"},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user