Class feat injections moved to yaml

This commit is contained in:
2026-05-24 20:23:08 +02:00
parent 5fa5246e9f
commit b2a58cb011
8 changed files with 429 additions and 86 deletions
+92 -11
View File
@@ -10,6 +10,7 @@ import (
"os"
"path/filepath"
"slices"
"strconv"
"strings"
"time"
@@ -206,17 +207,39 @@ type MusicDatasetConfig struct {
}
type TopDataConfig struct {
Source string `json:"source" yaml:"source"`
Build string `json:"build" yaml:"build"`
ReferenceBuilder string `json:"reference_builder" yaml:"reference_builder"`
Assets string `json:"assets" yaml:"assets"`
Compiled2DADir string `json:"compiled_2da_dir" yaml:"compiled_2da_dir"`
CompiledTLK string `json:"compiled_tlk" yaml:"compiled_tlk"`
PackageHAK string `json:"package_hak" yaml:"package_hak"`
PackageTLK string `json:"package_tlk" yaml:"package_tlk"`
ValueEncodings []TopDataValueEncodingConfig `json:"value_encodings" yaml:"value_encodings"`
ValueDefaults []TopDataValueDefaultConfig `json:"value_defaults" yaml:"value_defaults"`
Wiki TopDataWikiConfig `json:"wiki" yaml:"wiki"`
Source string `json:"source" yaml:"source"`
Build string `json:"build" yaml:"build"`
ReferenceBuilder string `json:"reference_builder" yaml:"reference_builder"`
Assets string `json:"assets" yaml:"assets"`
Compiled2DADir string `json:"compiled_2da_dir" yaml:"compiled_2da_dir"`
CompiledTLK string `json:"compiled_tlk" yaml:"compiled_tlk"`
PackageHAK string `json:"package_hak" yaml:"package_hak"`
PackageTLK string `json:"package_tlk" yaml:"package_tlk"`
ValueEncodings []TopDataValueEncodingConfig `json:"value_encodings" yaml:"value_encodings"`
ValueDefaults []TopDataValueDefaultConfig `json:"value_defaults" yaml:"value_defaults"`
ClassFeatInjections TopDataClassFeatInjectionConfig `json:"class_feat_injections" yaml:"class_feat_injections"`
Wiki TopDataWikiConfig `json:"wiki" yaml:"wiki"`
}
type TopDataClassFeatInjectionConfig struct {
GlobalFeats []TopDataClassFeatGlobalRule `json:"global_feats" yaml:"global_feats"`
ClassSkillMasterfeats []TopDataClassFeatMasterfeatRule `json:"class_skill_masterfeats" yaml:"class_skill_masterfeats"`
}
type TopDataClassFeatGlobalRule struct {
Feat string `json:"feat" yaml:"feat"`
List string `json:"list" yaml:"list"`
GrantedOnLevel string `json:"granted_on_level" yaml:"granted_on_level"`
OnMenu string `json:"on_menu" yaml:"on_menu"`
RequirePresent []string `json:"require_present,omitempty" yaml:"require_present,omitempty"`
UnlessPresent []string `json:"unless_present,omitempty" yaml:"unless_present,omitempty"`
}
type TopDataClassFeatMasterfeatRule struct {
Masterfeat string `json:"masterfeat" yaml:"masterfeat"`
List string `json:"list" yaml:"list"`
GrantedOnLevel string `json:"granted_on_level" yaml:"granted_on_level"`
OnMenu string `json:"on_menu" yaml:"on_menu"`
}
type TopDataValueEncodingConfig struct {
@@ -583,6 +606,7 @@ func (p *Project) ValidateLayout() error {
failures = append(failures, validateGeneratedConfig(effective.Generated)...)
failures = append(failures, validateTopDataValueEncodings(effective.TopData.ValueEncodings)...)
failures = append(failures, validateTopDataValueDefaults(effective.TopData.ValueDefaults)...)
failures = append(failures, validateTopDataClassFeatInjections(effective.TopData.ClassFeatInjections)...)
failures = append(failures, validateRelativePath("scripts.cache", effective.Scripts.Cache)...)
failures = append(failures, validateRelativePath("scripts.source_dir", effective.Scripts.SourceDir)...)
failures = append(failures, validateRelativePath("topdata.compiled_2da_dir", effective.TopData.Compiled2DADir)...)
@@ -781,6 +805,63 @@ func validateTopDataValueDefaults(defaults []TopDataValueDefaultConfig) []error
return failures
}
func validateTopDataClassFeatInjections(config TopDataClassFeatInjectionConfig) []error {
failures := []error{}
for index, rule := range config.GlobalFeats {
prefix := fmt.Sprintf("topdata.class_feat_injections.global_feats[%d]", index)
if feat := strings.TrimSpace(rule.Feat); feat == "" {
failures = append(failures, fmt.Errorf("%s.feat is required", prefix))
} else if !strings.HasPrefix(feat, "feat:") {
failures = append(failures, fmt.Errorf("%s.feat must use a feat: reference", prefix))
}
failures = append(failures, validateClassFeatInjectionFields(prefix, rule.List, rule.GrantedOnLevel, rule.OnMenu)...)
failures = append(failures, validateFeatReferenceList(prefix+".require_present", rule.RequirePresent)...)
failures = append(failures, validateFeatReferenceList(prefix+".unless_present", rule.UnlessPresent)...)
}
for index, rule := range config.ClassSkillMasterfeats {
prefix := fmt.Sprintf("topdata.class_feat_injections.class_skill_masterfeats[%d]", index)
if masterfeat := strings.TrimSpace(rule.Masterfeat); masterfeat == "" {
failures = append(failures, fmt.Errorf("%s.masterfeat is required", prefix))
} else if !strings.HasPrefix(masterfeat, "masterfeats:") {
failures = append(failures, fmt.Errorf("%s.masterfeat must use a masterfeats: reference", prefix))
}
failures = append(failures, validateClassFeatInjectionFields(prefix, rule.List, rule.GrantedOnLevel, rule.OnMenu)...)
}
return failures
}
func validateClassFeatInjectionFields(prefix, list, grantedOnLevel, onMenu string) []error {
failures := []error{}
for field, value := range map[string]string{
"list": list,
"granted_on_level": grantedOnLevel,
"on_menu": onMenu,
} {
trimmed := strings.TrimSpace(value)
if trimmed == "" {
failures = append(failures, fmt.Errorf("%s.%s is required", prefix, field))
continue
}
if _, err := strconv.Atoi(trimmed); err != nil {
failures = append(failures, fmt.Errorf("%s.%s must be an integer", prefix, field))
}
}
return failures
}
func validateFeatReferenceList(prefix string, refs []string) []error {
failures := []error{}
for index, ref := range refs {
trimmed := strings.TrimSpace(ref)
if trimmed == "" {
failures = append(failures, fmt.Errorf("%s[%d] is required", prefix, index))
} else if !strings.HasPrefix(trimmed, "feat:") {
failures = append(failures, fmt.Errorf("%s[%d] must use a feat: reference", prefix, index))
}
}
return failures
}
func intPow(base, exponent int) int {
result := 1
for i := 0; i < exponent; i++ {