BAB tables

This commit is contained in:
2026-05-23 20:53:58 +02:00
parent 3f36ed529a
commit ed2ad4f9a0
4 changed files with 429 additions and 4 deletions
+132 -3
View File
@@ -279,7 +279,7 @@ func (ctx *wikiContext) wikiTableSourceRows(def wikiTableDefinition, page wikiTe
if page.Category != "classes" {
return nil, nil
}
return ctx.classProgressionRows(def.Rows.Levels, nil, page.Row)
return ctx.classProgressionRows(def.Rows.Levels, nil, wikiDataProviderAttacksConfig{}, page.Key, page.Row)
case "class_feats":
if page.Category != "classes" {
return nil, nil
@@ -303,7 +303,7 @@ func (ctx *wikiContext) wikiTableSourceRows(def wikiTableDefinition, page wikiTe
}
}
func (ctx *wikiContext) classProgressionRows(levels string, projections map[string]wikiDataProviderProjectionField, classRow map[string]any) ([]map[string]any, error) {
func (ctx *wikiContext) classProgressionRows(levels string, projections map[string]wikiDataProviderProjectionField, attacks wikiDataProviderAttacksConfig, classKey string, classRow map[string]any) ([]map[string]any, error) {
start, end, err := parseLevelRange(levels)
if err != nil {
return nil, err
@@ -363,7 +363,17 @@ func (ctx *wikiContext) classProgressionRows(levels string, projections map[stri
for level := start; level <= end; level++ {
row := cloneRowMap(classRow)
row["Level"] = level
row["BAB"] = formatSignedInt(classBABAtLevel(stringValue(classRow, "AttackBonusTable"), level))
bab := classBABAtLevel(stringValue(classRow, "AttackBonusTable"), level)
row["BABValue"] = bab
row["BAB"] = formatSignedInt(bab)
attackCount := classAttackCount(attacks, classKey, bab, level)
if attackCount < 1 {
attackCount = 1
}
countField := defaultStringValue(attacks.CountField, "AttackCount")
sequenceField := defaultStringValue(attacks.Field, "AttackSequence")
row[countField] = attackCount
row[sequenceField] = formatAttackSequence(bab, attackCount, attackStep(attacks))
if saveRow := saveRows[strconv.Itoa(level)]; saveRow != nil {
for key, value := range saveRow {
row[key] = value
@@ -418,6 +428,96 @@ func classBABAtLevel(table string, level int) int {
}
}
func classAttackCount(cfg wikiDataProviderAttacksConfig, classKey string, bab, level int) int {
count := baseClassAttackCount(cfg, bab)
for _, override := range cfg.Overrides {
if !wikiAttackOverrideMatches(override, classKey, level) {
continue
}
if override.Attacks > 0 {
count = override.Attacks
continue
}
count += override.Add
}
if count < 1 {
return 1
}
return count
}
func baseClassAttackCount(cfg wikiDataProviderAttacksConfig, bab int) int {
switch strings.TrimSpace(cfg.Mode) {
case "fixed":
if cfg.Attacks > 0 {
return cfg.Attacks
}
case "bab_thresholds":
return attackCountFromThresholds(cfg.Thresholds, bab)
case "":
return attackCountFromThresholds(defaultWikiAttackThresholds(), bab)
}
return 1
}
func attackCountFromThresholds(thresholds []wikiDataProviderAttackThreshold, bab int) int {
count := 1
for _, threshold := range thresholds {
if threshold.Attacks > 0 && bab >= threshold.MinBAB {
count = threshold.Attacks
}
}
return count
}
func defaultWikiAttackThresholds() []wikiDataProviderAttackThreshold {
return []wikiDataProviderAttackThreshold{
{MinBAB: 1, Attacks: 1},
{MinBAB: 6, Attacks: 2},
{MinBAB: 11, Attacks: 3},
{MinBAB: 16, Attacks: 4},
}
}
func wikiAttackOverrideMatches(override wikiDataProviderAttackOverrideRule, classKey string, level int) bool {
foundClass := false
for _, candidate := range override.Classes {
if strings.TrimSpace(candidate) == classKey {
foundClass = true
break
}
}
if !foundClass {
return false
}
levels, err := parseWikiAttackLevels(override.Levels)
if err != nil {
return false
}
_, ok := levels[level]
return ok
}
func attackStep(cfg wikiDataProviderAttacksConfig) int {
if cfg.Step == 0 {
return -5
}
return cfg.Step
}
func formatAttackSequence(bab, attacks, step int) string {
if attacks < 1 {
attacks = 1
}
values := make([]string, 0, attacks)
current := bab
for i := 0; i < attacks; i++ {
values = append(values, formatSignedInt(current))
current += step
}
return strings.Join(values, "/")
}
func formatSignedInt(value int) string {
if value > 0 {
return "+" + strconv.Itoa(value)
@@ -884,6 +984,11 @@ func (p *wikiExprParser) callHelper(name string, args []any) (any, error) {
default:
return stringifyWikiTableValue(args[0]), nil
}
case "count":
if len(args) != 1 {
return nil, fmt.Errorf("count expects 1 argument")
}
return countWikiTemplateValue(args[0]), nil
case "nonzero":
if len(args) != 1 {
return nil, fmt.Errorf("nonzero expects 1 argument")
@@ -974,6 +1079,30 @@ func (p *wikiExprParser) callHelper(name string, args []any) (any, error) {
}
}
func countWikiTemplateValue(value any) int {
switch typed := value.(type) {
case nil:
return 0
case []string:
return len(typed)
case []any:
count := 0
for _, item := range typed {
if presentWikiValue(item) {
count++
}
}
return count
case map[string]any:
return len(typed)
default:
if presentWikiValue(value) {
return 1
}
return 0
}
}
func (p *wikiExprParser) hiddenWikiReference(value any, targetDataset string) (bool, error) {
targetDataset = strings.TrimSpace(targetDataset)
if !isWikiVisibilityDataset(targetDataset) {