From a2d9820f8d826c134fc18de6bd74d3285c9d7ae6 Mon Sep 17 00:00:00 2001 From: vickydotbat Date: Wed, 27 May 2026 10:28:44 +0200 Subject: [PATCH] Spellcasting expansion --- internal/topdata/wiki_native_test.go | 51 ++++++++++++++++++++++++++++ internal/topdata/wiki_tables.go | 30 ++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/internal/topdata/wiki_native_test.go b/internal/topdata/wiki_native_test.go index fcb7e7a..c478979 100644 --- a/internal/topdata/wiki_native_test.go +++ b/internal/topdata/wiki_native_test.go @@ -1740,6 +1740,57 @@ tables: } } +func TestWikiTemplateRendersClassProgressionSpellcastingAdvancementNotes(t *testing.T) { + ctx := testWikiTableContext(t, ` +tables: + ClassProgression: + source: class_progression + rows: + levels: 1-4 + columns: + - key: level + label: Lvl + value: "{{Level}}" + - key: notes + label: Notes + value: "{{SpellcastingAdvancementNote + if(present(SpellcastingAdvancementNote) && present(Notes), \" \", \"\") + Notes}}" + empty: "" +`) + page := wikiTemplatePage{ + PageID: "classes:pale_master", + Category: "classes", + Key: "classes:pale_master", + Title: "Pale Master", + Row: map[string]any{ + "SpellCaster": 0, + "ArcSpellLvlMod": 2, + "DivSpellLvlMod": 0, + "meta": map[string]any{ + "wiki": map[string]any{ + "progression_notes": map[string]any{ + "3": "Manual level note.", + }, + }, + }, + }, + } + + got, err := ctx.renderWikiTemplateString("classes.html", `{{table:ClassProgression}}`, page) + if err != nil { + t.Fatalf("render table template: %v", err) + } + for _, expected := range []string{ + `1+1 level of existing arcane spellcasting class`, + `2`, + `3+1 level of existing arcane spellcasting class Manual level note.`, + `4`, + } { + if !strings.Contains(got, expected) { + t.Fatalf("expected %q in rendered table:\n%s", expected, got) + } + } +} + func TestWikiTemplateHidesUnusedConfiguredClassSpellColumns(t *testing.T) { ctx := testWikiTableContext(t, ` tables: diff --git a/internal/topdata/wiki_tables.go b/internal/topdata/wiki_tables.go index 3c78c3b..4a6a184 100644 --- a/internal/topdata/wiki_tables.go +++ b/internal/topdata/wiki_tables.go @@ -452,6 +452,7 @@ func (ctx *wikiContext) classProgressionRows(levels string, projections map[stri row["HasSpellProgression"] = hasSpellProgression(classRow) row["HasSpellsPerDay"] = hasPerDay row["HasSpellsKnown"] = hasKnown + row["SpellcastingAdvancementNote"] = classSpellcastingAdvancementNote(classRow, level) populateClassSpellProgressionFields(row, "SpellsPerDay", spellsPerDayByLevel[level]) populateClassSpellProgressionFields(row, "SpellsKnown", spellsKnownByLevel[level]) out = append(out, row) @@ -459,6 +460,35 @@ func (ctx *wikiContext) classProgressionRows(levels string, projections map[stri return out, nil } +func classSpellcastingAdvancementNote(classRow map[string]any, level int) string { + if level < 1 || isClassSpellcaster(classRow) { + return "" + } + notes := []string{} + if advancesSpellcastingAtLevel(classRow, "ArcSpellLvlMod", level) { + notes = append(notes, "+1 level of existing arcane spellcasting class") + } + if advancesSpellcastingAtLevel(classRow, "DivSpellLvlMod", level) { + notes = append(notes, "+1 level of existing divine spellcasting class") + } + return strings.Join(notes, "; ") +} + +func advancesSpellcastingAtLevel(classRow map[string]any, field string, level int) bool { + mod, err := asInt(fieldValue(classRow, field)) + if err != nil || mod <= 0 { + return false + } + return roundedSpellcastingLevelAdvancement(level, mod) > roundedSpellcastingLevelAdvancement(level-1, mod) +} + +func roundedSpellcastingLevelAdvancement(level, mod int) int { + if level <= 0 || mod <= 0 { + return 0 + } + return (level*2 + mod) / (mod * 2) +} + func (ctx *wikiContext) classProgressionNote(classRow map[string]any, level int) string { meta, err := parseExistingMetadata(classRow) if err != nil || meta == nil {