From e9860c12c63d2c9387f14d880b2ffbea95eae3d8 Mon Sep 17 00:00:00 2001 From: gitea-bot Date: Sat, 11 Jul 2026 15:10:10 +0000 Subject: [PATCH] fix(topdata): encode spell radial feat IDs (#37) ## Summary - add explicit `{"spellradial": {"id": "feat:key"}}` authoring syntax for `spells.2da` `FeatID` - resolve spell and feat rows dynamically and emit NWN:EE packed subradial values - reject malformed or misplaced spell-radial references ## Verification - `go test ./internal/topdata` Co-authored-by: vickydotbat Reviewed-on: https://git.westgate.pw/ShadowsOverWestgate/sow-tools/pulls/37 Reviewed-by: archvillainette Reviewed-by: xtul Co-authored-by: gitea-bot Co-committed-by: gitea-bot --- internal/topdata/native.go | 18 ++++++++++++++++++ internal/topdata/topdata_test.go | 4 ++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/internal/topdata/native.go b/internal/topdata/native.go index e689f3d..2692664 100644 --- a/internal/topdata/native.go +++ b/internal/topdata/native.go @@ -4712,6 +4712,24 @@ func (r *valueResolver) resolveValue(row map[string]any, field string, value any switch typed := value.(type) { case map[string]any: + if radial, ok := typed["spellradial"].(map[string]any); ok && len(typed) == 1 { + if r.dataset.Name != "spells" || field != "FeatID" { + return tlkCompiledValue{}, fmt.Errorf("spellradial reference is only valid for spells.FeatID") + } + radialKey, ok := radial["id"].(string) + if !ok || len(radial) != 1 { + return tlkCompiledValue{}, fmt.Errorf("spellradial reference requires exactly one id") + } + featID, ok := r.keyToID[radialKey] + if !ok { + return tlkCompiledValue{}, fmt.Errorf("unknown spellradial feat reference: %s", radialKey) + } + spellID, ok := row["id"].(int) + if !ok { + return tlkCompiledValue{}, fmt.Errorf("spellradial reference requires a numeric spell row id") + } + return tlkCompiledValue{Value: spellID<<16 + featID}, nil + } if encoding, ok := r.valueEncodingForField(field); ok { mode := strings.TrimSpace(encoding.Mode) _, hasList := typed["list"] diff --git a/internal/topdata/topdata_test.go b/internal/topdata/topdata_test.go index bc6a9a0..d77d2fa 100644 --- a/internal/topdata/topdata_test.go +++ b/internal/topdata/topdata_test.go @@ -11887,7 +11887,7 @@ func TestBuildSupportsCanonicalSpells(t *testing.T) { "spells:disarm": { "Label": "Disarm", "Name": "501", - "FeatID": {"id": "feat:disarm"}, + "FeatID": {"spellradial": {"id": "feat:specialattacks"}}, "Master": {"id": "spells:specialattacks"}, "Category": {"ref": "spells:specialattacks", "field": "Category"} } @@ -11939,7 +11939,7 @@ func TestBuildSupportsCanonicalSpells(t *testing.T) { !strings.Contains(text, "\t1000\t") || !strings.Contains(text, "841\tDisarm\t501") || !strings.Contains(text, "\t840\t") || - !strings.Contains(text, "\t1001\t") || + !strings.Contains(text, "\t55116776\t") || !strings.Contains(text, "849\tBard_Fascinate\t600") || !strings.Contains(text, "\t601\t") || !strings.Contains(text, "\t602\t") ||