mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 16:42:41 +00:00
- 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.
186 lines
5.4 KiB
Go
186 lines
5.4 KiB
Go
package plugin
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"maunium.net/go/mautrix/id"
|
|
)
|
|
|
|
// Phase 9 SP4 — Mage `!spells learn` cap + level-up nudge.
|
|
|
|
// TestMageKnownSpellsCapPerLevel — formula: 6 + 2*(level-1).
|
|
func TestMageKnownSpellsCapPerLevel(t *testing.T) {
|
|
cases := []struct {
|
|
level, cap int
|
|
}{
|
|
{1, 6},
|
|
{2, 8},
|
|
{3, 10},
|
|
{5, 14},
|
|
{9, 22},
|
|
{20, 44},
|
|
}
|
|
for _, tc := range cases {
|
|
if got := mageKnownSpellsCap(tc.level); got != tc.cap {
|
|
t.Errorf("mageKnownSpellsCap(L%d)=%d, want %d", tc.level, got, tc.cap)
|
|
}
|
|
}
|
|
}
|
|
|
|
func setupMageForLearn(t *testing.T, uid id.UserID, level int) *DnDCharacter {
|
|
t.Helper()
|
|
if err := createAdvCharacter(uid, "mage_learn"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
c := &DnDCharacter{
|
|
UserID: uid, Race: RaceElf, Class: ClassMage, Level: level,
|
|
STR: 8, DEX: 14, CON: 12, INT: 17, WIS: 12, CHA: 10,
|
|
HPMax: 20, HPCurrent: 20, ArmorClass: 12,
|
|
}
|
|
if err := SaveDnDCharacter(c); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := setSpellSlotsForLevel(uid, ClassMage, level); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return c
|
|
}
|
|
|
|
// TestMageLearnCapEnforced — at L1 the cap is 6 leveled spells. Pre-fill 6;
|
|
// the 7th attempt is refused without writing to the spellbook.
|
|
func TestMageLearnCapEnforced(t *testing.T) {
|
|
setupAbilitiesTestDB(t)
|
|
uid := id.UserID("@mage_cap:example")
|
|
c := setupMageForLearn(t, uid, 1)
|
|
|
|
// Six leveled mage spells (all L1-eligible).
|
|
pre := []string{
|
|
"magic_missile", "thunderwave", "mage_armor", "burning_hands",
|
|
"grease", "sleep",
|
|
}
|
|
for _, sid := range pre {
|
|
if err := addKnownSpell(uid, sid, "class", true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
// Cantrips: don't count.
|
|
if err := addKnownSpell(uid, "fire_bolt", "class", true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
count, _ := mageLeveledKnownCount(uid)
|
|
if count != 6 {
|
|
t.Fatalf("setup: leveled count=%d, want 6", count)
|
|
}
|
|
|
|
p := &AdventurePlugin{}
|
|
// 7th leveled spell — must be refused.
|
|
if err := p.handleSpellsLearn(MessageContext{Sender: uid}, c, "chromatic_orb"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if known, _, _ := playerKnowsSpell(uid, "chromatic_orb"); known {
|
|
t.Errorf("over-cap learn slipped through")
|
|
}
|
|
|
|
// A cantrip should still be learnable beyond the cap.
|
|
if err := p.handleSpellsLearn(MessageContext{Sender: uid}, c, "minor_illusion"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if known, _, _ := playerKnowsSpell(uid, "minor_illusion"); !known {
|
|
t.Errorf("cantrip blocked by leveled-spell cap")
|
|
}
|
|
}
|
|
|
|
// TestMageLearnAcceptsUpToCap — fresh Mage at L1 can learn 6 leveled spells
|
|
// before the cap kicks in.
|
|
func TestMageLearnAcceptsUpToCap(t *testing.T) {
|
|
setupAbilitiesTestDB(t)
|
|
uid := id.UserID("@mage_fill:example")
|
|
c := setupMageForLearn(t, uid, 1)
|
|
|
|
wantList := []string{
|
|
"magic_missile", "thunderwave", "mage_armor", "burning_hands",
|
|
"grease", "sleep",
|
|
}
|
|
p := &AdventurePlugin{}
|
|
for _, sid := range wantList {
|
|
if err := p.handleSpellsLearn(MessageContext{Sender: uid}, c, sid); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if known, _, _ := playerKnowsSpell(uid, sid); !known {
|
|
t.Errorf("under-cap learn rejected: %s", sid)
|
|
}
|
|
}
|
|
if got, _ := mageLeveledKnownCount(uid); got != 6 {
|
|
t.Errorf("after 6 learns, count=%d, want 6", got)
|
|
}
|
|
}
|
|
|
|
// TestMageLevelUpDMNudge — buildLevelUpMessage surfaces the spellbook headroom
|
|
// after a Mage levels up.
|
|
func TestMageLevelUpDMNudge(t *testing.T) {
|
|
setupAbilitiesTestDB(t)
|
|
uid := id.UserID("@mage_nudge:example")
|
|
c := setupMageForLearn(t, uid, 2) // post-level-up state
|
|
|
|
// 5 leveled known + cantrips. At L2 cap = 8 → headroom = 3.
|
|
for _, sid := range []string{
|
|
"magic_missile", "mage_armor", "shield", "burning_hands", "grease",
|
|
} {
|
|
_ = addKnownSpell(uid, sid, "class", true)
|
|
}
|
|
_ = addKnownSpell(uid, "fire_bolt", "class", true)
|
|
|
|
events := []LevelUpEvent{{NewLevel: 2, HPGain: 5}}
|
|
msg := buildLevelUpMessage(c, events, "")
|
|
|
|
if !strings.Contains(msg, "Spells available to learn: **3**") {
|
|
t.Errorf("nudge missing or wrong count; msg=\n%s", msg)
|
|
}
|
|
if !strings.Contains(msg, "!spells learn") {
|
|
t.Errorf("nudge missing command hint; msg=\n%s", msg)
|
|
}
|
|
}
|
|
|
|
// TestNonMageLevelUpNoSpellNudge — Fighter levelling never sees the nudge.
|
|
func TestNonMageLevelUpNoSpellNudge(t *testing.T) {
|
|
c := &DnDCharacter{
|
|
Race: RaceHuman, Class: ClassFighter, Level: 5,
|
|
HPMax: 50, HPCurrent: 50,
|
|
}
|
|
msg := buildLevelUpMessage(c, []LevelUpEvent{{NewLevel: 5, HPGain: 8}}, "")
|
|
if strings.Contains(msg, "Spells available to learn") {
|
|
t.Errorf("Fighter level-up shouldn't show spell nudge; msg=\n%s", msg)
|
|
}
|
|
}
|
|
|
|
// TestMageLevelUpDeltaIsTwo — leveling from L1 → L2 increases the cap by 2.
|
|
func TestMageLevelUpDeltaIsTwo(t *testing.T) {
|
|
for lvl := 1; lvl < 20; lvl++ {
|
|
got := mageKnownSpellsCap(lvl+1) - mageKnownSpellsCap(lvl)
|
|
if got != 2 {
|
|
t.Errorf("delta L%d→L%d = %d, want 2", lvl, lvl+1, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestMageSpellbookLineInRender — renderSpellsList shows the budget for Mage.
|
|
func TestMageSpellbookLineInRender(t *testing.T) {
|
|
setupAbilitiesTestDB(t)
|
|
uid := id.UserID("@mage_render:example")
|
|
c := setupMageForLearn(t, uid, 3)
|
|
for _, sid := range []string{"magic_missile", "mage_armor"} {
|
|
_ = addKnownSpell(uid, sid, "class", true)
|
|
}
|
|
|
|
out := renderSpellsList(c)
|
|
// L3 cap = 10, count = 2.
|
|
if !strings.Contains(out, "Spellbook:") || !strings.Contains(out, "2/10") {
|
|
t.Errorf("renderSpellsList missing/wrong Spellbook line; got\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "can learn 8 more") {
|
|
t.Errorf("renderSpellsList missing headroom hint; got\n%s", out)
|
|
}
|
|
}
|