Adv 2.0 D&D Phase 9 SP4: mage spellbook cap + level-up nudge

- Enforce per-level Mage known-spell cap in handleSpellsLearn; surface
  spellbook budget in renderSpellsList.
- Mage level-up DM now nudges with "Spells available to learn: N" via
  extracted buildLevelUpMessage.
- Extract halveSavedDamage and enemySpellSaveMod for clarity; document
  single-target AoE limitation in applySpellDamageSave.
- Add tests: mage learn cap, prepare flow, AoE behavior, spell save
  rounding, spells migration.
This commit is contained in:
prosolis
2026-05-08 09:10:05 -07:00
parent 01c70f5297
commit b53e516bf0
9 changed files with 870 additions and 13 deletions

View File

@@ -430,6 +430,16 @@ func renderSpellsList(c *DnDCharacter) string {
b.WriteString(fmt.Sprintf("**Spell DC:** %d **Spell Atk:** +%d\n",
spellSaveDC(c), spellAttackBonus(c)))
if c.Class == ClassMage {
count, _ := mageLeveledKnownCount(c.UserID)
cap := mageKnownSpellsCap(c.Level)
b.WriteString(fmt.Sprintf("**Spellbook:** %d/%d leveled spells", count, cap))
if avail := cap - count; avail > 0 {
b.WriteString(fmt.Sprintf(" _(can learn %d more)_", avail))
}
b.WriteString("\n")
}
if pc, ok := decodePendingCast(c.PendingCast); ok {
b.WriteString(fmt.Sprintf("\n_Queued: **%s**", displaySpellName(pc.SpellID)))
if pc.SlotLevel > 0 {
@@ -475,6 +485,19 @@ func (p *AdventurePlugin) handleSpellsLearn(ctx MessageContext, c *DnDCharacter,
if err == nil && known {
return p.SendDM(ctx.Sender, "You already know "+spell.Name+".")
}
// Cap applies to leveled spells only — cantrips are unbounded.
if spell.Level > 0 {
count, err := mageLeveledKnownCount(ctx.Sender)
if err != nil {
return p.SendDM(ctx.Sender, "Couldn't check your spellbook.")
}
cap := mageKnownSpellsCap(c.Level)
if count >= cap {
return p.SendDM(ctx.Sender, fmt.Sprintf(
"Spellbook is full (%d/%d). Level up to learn more leveled spells.",
count, cap))
}
}
if err := addKnownSpell(ctx.Sender, spell.ID, "class", true); err != nil {
return p.SendDM(ctx.Sender, "Couldn't learn: "+err.Error())
}