BAB tables
This commit is contained in:
@@ -3,6 +3,7 @@ package topdata
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
@@ -16,6 +17,7 @@ type wikiDataProviderDefinition struct {
|
||||
Source string `json:"source" yaml:"source"`
|
||||
Levels string `json:"levels" yaml:"levels"`
|
||||
ClassFeats wikiDataProviderClassFeatConfig `json:"class_feats" yaml:"class_feats"`
|
||||
Attacks wikiDataProviderAttacksConfig `json:"attacks" yaml:"attacks"`
|
||||
Provider string `json:"provider" yaml:"provider"`
|
||||
Columns int `json:"columns" yaml:"columns"`
|
||||
ItemsPerColumn int `json:"items_per_column" yaml:"items_per_column"`
|
||||
@@ -30,6 +32,28 @@ type wikiDataProviderProjectionField struct {
|
||||
When string `json:"when" yaml:"when"`
|
||||
}
|
||||
|
||||
type wikiDataProviderAttacksConfig struct {
|
||||
Field string `json:"field" yaml:"field"`
|
||||
CountField string `json:"count_field" yaml:"count_field"`
|
||||
Mode string `json:"mode" yaml:"mode"`
|
||||
Step int `json:"step" yaml:"step"`
|
||||
Attacks int `json:"attacks" yaml:"attacks"`
|
||||
Thresholds []wikiDataProviderAttackThreshold `json:"thresholds" yaml:"thresholds"`
|
||||
Overrides []wikiDataProviderAttackOverrideRule `json:"overrides" yaml:"overrides"`
|
||||
}
|
||||
|
||||
type wikiDataProviderAttackThreshold struct {
|
||||
MinBAB int `json:"min_bab" yaml:"min_bab"`
|
||||
Attacks int `json:"attacks" yaml:"attacks"`
|
||||
}
|
||||
|
||||
type wikiDataProviderAttackOverrideRule struct {
|
||||
Classes []string `json:"classes" yaml:"classes"`
|
||||
Levels any `json:"levels" yaml:"levels"`
|
||||
Attacks int `json:"attacks" yaml:"attacks"`
|
||||
Add int `json:"add" yaml:"add"`
|
||||
}
|
||||
|
||||
func loadWikiDataProviders(path string) (map[string]wikiDataProviderDefinition, error) {
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
@@ -60,10 +84,16 @@ func loadWikiDataProviders(path string) (map[string]wikiDataProviderDefinition,
|
||||
return nil, fmt.Errorf("wiki data providers %s: provider %s class feat projection %s missing when", path, name, field)
|
||||
}
|
||||
}
|
||||
if err := validateWikiDataProviderAttacks(path, name, def.Attacks); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "columns":
|
||||
if len(def.ClassFeats.Fields) > 0 {
|
||||
return nil, fmt.Errorf("wiki data providers %s: provider %s class feat projections require source class_progression", path, name)
|
||||
}
|
||||
if strings.TrimSpace(def.Attacks.Mode) != "" {
|
||||
return nil, fmt.Errorf("wiki data providers %s: provider %s attacks config requires source class_progression", path, name)
|
||||
}
|
||||
if strings.TrimSpace(def.Provider) == "" {
|
||||
return nil, fmt.Errorf("wiki data providers %s: provider %s columns provider missing provider", path, name)
|
||||
}
|
||||
@@ -80,6 +110,9 @@ func loadWikiDataProviders(path string) (map[string]wikiDataProviderDefinition,
|
||||
if len(def.ClassFeats.Fields) > 0 {
|
||||
return nil, fmt.Errorf("wiki data providers %s: provider %s class feat projections require source class_progression", path, name)
|
||||
}
|
||||
if strings.TrimSpace(def.Attacks.Mode) != "" {
|
||||
return nil, fmt.Errorf("wiki data providers %s: provider %s attacks config requires source class_progression", path, name)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("wiki data providers %s: provider %s has unknown source %q", path, name, def.Source)
|
||||
}
|
||||
@@ -114,7 +147,7 @@ func (ctx *wikiContext) wikiDataProviderRows(name string, page wikiTemplatePage)
|
||||
if page.Category != "classes" {
|
||||
return nil, nil
|
||||
}
|
||||
return ctx.classProgressionRows(def.Levels, def.ClassFeats.Fields, page.Row)
|
||||
return ctx.classProgressionRows(def.Levels, def.ClassFeats.Fields, def.Attacks, page.Key, page.Row)
|
||||
case "class_feats":
|
||||
if page.Category != "classes" {
|
||||
return nil, nil
|
||||
@@ -145,6 +178,123 @@ func (ctx *wikiContext) wikiDataProviderRows(name string, page wikiTemplatePage)
|
||||
}
|
||||
}
|
||||
|
||||
func validateWikiDataProviderAttacks(path, provider string, cfg wikiDataProviderAttacksConfig) error {
|
||||
if strings.TrimSpace(cfg.Mode) == "" {
|
||||
return nil
|
||||
}
|
||||
switch strings.TrimSpace(cfg.Mode) {
|
||||
case "fixed":
|
||||
if cfg.Attacks < 1 {
|
||||
return fmt.Errorf("wiki data providers %s: provider %s attacks fixed mode requires positive attacks", path, provider)
|
||||
}
|
||||
case "bab_thresholds":
|
||||
if len(cfg.Thresholds) == 0 {
|
||||
return fmt.Errorf("wiki data providers %s: provider %s attacks bab_thresholds mode requires thresholds", path, provider)
|
||||
}
|
||||
for i, threshold := range cfg.Thresholds {
|
||||
if threshold.MinBAB < 0 {
|
||||
return fmt.Errorf("wiki data providers %s: provider %s attacks threshold %d min_bab must be non-negative", path, provider, i+1)
|
||||
}
|
||||
if threshold.Attacks < 1 {
|
||||
return fmt.Errorf("wiki data providers %s: provider %s attacks threshold %d attacks must be positive", path, provider, i+1)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("wiki data providers %s: provider %s attacks has unknown mode %q", path, provider, cfg.Mode)
|
||||
}
|
||||
for i, override := range cfg.Overrides {
|
||||
if len(override.Classes) == 0 {
|
||||
return fmt.Errorf("wiki data providers %s: provider %s attacks override %d missing classes", path, provider, i+1)
|
||||
}
|
||||
for _, class := range override.Classes {
|
||||
if strings.TrimSpace(class) == "" {
|
||||
return fmt.Errorf("wiki data providers %s: provider %s attacks override %d has empty class", path, provider, i+1)
|
||||
}
|
||||
}
|
||||
if _, err := parseWikiAttackLevels(override.Levels); err != nil {
|
||||
return fmt.Errorf("wiki data providers %s: provider %s attacks override %d bad levels: %w", path, provider, i+1, err)
|
||||
}
|
||||
if override.Attacks != 0 && override.Add != 0 {
|
||||
return fmt.Errorf("wiki data providers %s: provider %s attacks override %d must not set both attacks and add", path, provider, i+1)
|
||||
}
|
||||
if override.Attacks == 0 && override.Add == 0 {
|
||||
return fmt.Errorf("wiki data providers %s: provider %s attacks override %d must set attacks or add", path, provider, i+1)
|
||||
}
|
||||
if override.Attacks < 0 {
|
||||
return fmt.Errorf("wiki data providers %s: provider %s attacks override %d attacks must be positive", path, provider, i+1)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseWikiAttackLevels(raw any) (map[int]struct{}, error) {
|
||||
out := map[int]struct{}{}
|
||||
addLevel := func(value any) error {
|
||||
switch typed := value.(type) {
|
||||
case int:
|
||||
if typed < 1 {
|
||||
return fmt.Errorf("level must be positive")
|
||||
}
|
||||
out[typed] = struct{}{}
|
||||
return nil
|
||||
case int64:
|
||||
if typed < 1 {
|
||||
return fmt.Errorf("level must be positive")
|
||||
}
|
||||
out[int(typed)] = struct{}{}
|
||||
return nil
|
||||
case float64:
|
||||
level := int(typed)
|
||||
if typed != float64(level) || level < 1 {
|
||||
return fmt.Errorf("level must be a positive integer")
|
||||
}
|
||||
out[level] = struct{}{}
|
||||
return nil
|
||||
case string:
|
||||
text := strings.TrimSpace(typed)
|
||||
if text == "" {
|
||||
return fmt.Errorf("level must not be empty")
|
||||
}
|
||||
if strings.Contains(text, "-") {
|
||||
start, end, err := parseLevelRange(text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for level := start; level <= end; level++ {
|
||||
out[level] = struct{}{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
level, err := strconv.Atoi(text)
|
||||
if err != nil || level < 1 {
|
||||
return fmt.Errorf("level must be a positive integer")
|
||||
}
|
||||
out[level] = struct{}{}
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unsupported level value")
|
||||
}
|
||||
}
|
||||
switch typed := raw.(type) {
|
||||
case nil:
|
||||
return nil, fmt.Errorf("levels is required")
|
||||
case []any:
|
||||
if len(typed) == 0 {
|
||||
return nil, fmt.Errorf("levels must not be empty")
|
||||
}
|
||||
for _, value := range typed {
|
||||
if err := addLevel(value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
default:
|
||||
if err := addLevel(raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func wikiColumnProviderRows(sourceRows []map[string]any, configuredColumns, itemsPerColumn int, forceColumns bool) []map[string]any {
|
||||
if configuredColumns <= 0 || itemsPerColumn <= 0 {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user