mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 00:32:40 +00:00
§5.1 fortified camp unlocked in !camp, gated on exp.BossDefeated for
now (cache-site waypoints land in E3+). +2 SU cost was already in the
E1e cost table; this commit just stops rejecting the type.
§3 / §5.1 / §8.1 overnight rest applied at briefing rollover via
processOvernightCamp:
- rough: HP floor of HPMax/2 (no spell refresh)
- standard: full HP, exhaustion -1, refreshAllResources +
refreshSpellSlots
- fortified / base: standard rest + 1d6 HP bonus on wake +
threat-clock -5 (§8.1 modifier)
The camp auto-breaks once the rest applies — staying camped multi-day
isn't a thing in the spec; the player can re-pitch on the next night
if they want to. Briefing body now includes a 💤 line summarizing the
HP/threat delta from the night's rest.
Combat-driven side of fortified ('+1d6 HP on wake' as a rare flavor
event) currently surfaces as the rest summary; a peaceful-night TwinBee
narration variant could come later if we add a dedicated pool — for
now reusing the existing CampEstablished ambience suffices.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
146 lines
4.4 KiB
Go
146 lines
4.4 KiB
Go
package plugin
|
||
|
||
import (
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
|
||
"gogobee/internal/db"
|
||
"maunium.net/go/mautrix/id"
|
||
)
|
||
|
||
// Phase 12 E2d — Fortified camp branch.
|
||
|
||
func TestCampCmd_FortifiedRequiresBossDefeated(t *testing.T) {
|
||
setupZoneRunTestDB(t)
|
||
uid := id.UserID("@camp-fortified-pre:example")
|
||
campTestCharacter(t, uid, 1)
|
||
defer cleanupExpeditions(uid)
|
||
|
||
if _, err := startExpedition(uid, ZoneGoblinWarrens, "",
|
||
ExpeditionSupplies{Current: 5, Max: 5, DailyBurn: 1, HarshMod: 1}); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
p := &AdventurePlugin{}
|
||
if err := p.handleCampCmd(MessageContext{Sender: uid}, "fortified"); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
got, _ := getActiveExpedition(uid)
|
||
if got.Camp != nil && got.Camp.Active {
|
||
t.Error("fortified camp should be rejected before boss defeated")
|
||
}
|
||
}
|
||
|
||
func TestCampCmd_FortifiedAfterBossSucceeds(t *testing.T) {
|
||
setupZoneRunTestDB(t)
|
||
uid := id.UserID("@camp-fortified-post:example")
|
||
campTestCharacter(t, uid, 1)
|
||
defer cleanupExpeditions(uid)
|
||
|
||
exp, err := startExpedition(uid, ZoneGoblinWarrens, "",
|
||
ExpeditionSupplies{Current: 5, Max: 5, DailyBurn: 1, HarshMod: 1})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if _, err := db.Get().Exec(`UPDATE dnd_expedition SET boss_defeated = 1 WHERE expedition_id = ?`, exp.ID); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
p := &AdventurePlugin{}
|
||
if err := p.handleCampCmd(MessageContext{Sender: uid}, "fortified"); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
got, _ := getActiveExpedition(uid)
|
||
if got.Camp == nil || !got.Camp.Active || got.Camp.Type != CampTypeFortified {
|
||
t.Errorf("expected active fortified camp, got %+v", got.Camp)
|
||
}
|
||
if got.Supplies.Current != 3 { // 5 − 2 SU
|
||
t.Errorf("supplies after fortified pitch = %v, want 3", got.Supplies.Current)
|
||
}
|
||
}
|
||
|
||
func TestProcessOvernightCamp_StandardRefillsHP(t *testing.T) {
|
||
setupZoneRunTestDB(t)
|
||
uid := id.UserID("@camp-rest-std:example")
|
||
campTestCharacter(t, uid, 1)
|
||
defer cleanupExpeditions(uid)
|
||
|
||
c, _ := LoadDnDCharacter(uid)
|
||
c.HPCurrent = 5
|
||
_ = SaveDnDCharacter(c)
|
||
|
||
exp, _ := startExpedition(uid, ZoneGoblinWarrens, "",
|
||
ExpeditionSupplies{Current: 5, Max: 5, DailyBurn: 1, HarshMod: 1})
|
||
exp.Camp = &CampState{Active: true, Type: CampTypeStandard, EstablishedAt: time.Now().UTC()}
|
||
_ = updateCamp(exp.ID, exp.Camp)
|
||
|
||
summary := processOvernightCamp(exp)
|
||
if !strings.Contains(strings.ToLower(summary), "long rest") {
|
||
t.Errorf("standard rest summary = %q", summary)
|
||
}
|
||
got, _ := LoadDnDCharacter(uid)
|
||
if got.HPCurrent != got.HPMax {
|
||
t.Errorf("HP after standard rest = %d, want max %d", got.HPCurrent, got.HPMax)
|
||
}
|
||
postExp, _ := getExpedition(exp.ID)
|
||
if postExp.Camp != nil {
|
||
t.Error("camp should auto-break after rest")
|
||
}
|
||
}
|
||
|
||
func TestProcessOvernightCamp_FortifiedReducesThreatAndAddsBonus(t *testing.T) {
|
||
setupZoneRunTestDB(t)
|
||
uid := id.UserID("@camp-rest-fort:example")
|
||
campTestCharacter(t, uid, 1)
|
||
defer cleanupExpeditions(uid)
|
||
|
||
c, _ := LoadDnDCharacter(uid)
|
||
c.HPCurrent = c.HPMax // already full; bonus would clip at max
|
||
c.HPMax = 30
|
||
c.HPCurrent = 30
|
||
_ = SaveDnDCharacter(c)
|
||
|
||
exp, _ := startExpedition(uid, ZoneGoblinWarrens, "",
|
||
ExpeditionSupplies{Current: 5, Max: 5, DailyBurn: 1, HarshMod: 1})
|
||
if err := applyThreatDelta(exp.ID, 40, "seed"); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
exp, _ = getExpedition(exp.ID)
|
||
exp.Camp = &CampState{Active: true, Type: CampTypeFortified, EstablishedAt: time.Now().UTC()}
|
||
_ = updateCamp(exp.ID, exp.Camp)
|
||
|
||
summary := processOvernightCamp(exp)
|
||
if !strings.Contains(strings.ToLower(summary), "fortified rest") {
|
||
t.Errorf("fortified rest summary = %q", summary)
|
||
}
|
||
postExp, _ := getExpedition(exp.ID)
|
||
if postExp.ThreatLevel != 35 {
|
||
t.Errorf("threat after fortified rest = %d, want 35", postExp.ThreatLevel)
|
||
}
|
||
if postExp.Camp != nil {
|
||
t.Error("camp should auto-break after rest")
|
||
}
|
||
}
|
||
|
||
func TestProcessOvernightCamp_RoughHalfHPFloor(t *testing.T) {
|
||
setupZoneRunTestDB(t)
|
||
uid := id.UserID("@camp-rest-rough:example")
|
||
campTestCharacter(t, uid, 1)
|
||
defer cleanupExpeditions(uid)
|
||
|
||
c, _ := LoadDnDCharacter(uid)
|
||
c.HPMax = 20
|
||
c.HPCurrent = 4 // way under half
|
||
_ = SaveDnDCharacter(c)
|
||
|
||
exp, _ := startExpedition(uid, ZoneGoblinWarrens, "",
|
||
ExpeditionSupplies{Current: 5, Max: 5, DailyBurn: 1, HarshMod: 1})
|
||
exp.Camp = &CampState{Active: true, Type: CampTypeRough, EstablishedAt: time.Now().UTC()}
|
||
_ = updateCamp(exp.ID, exp.Camp)
|
||
|
||
_ = processOvernightCamp(exp)
|
||
got, _ := LoadDnDCharacter(uid)
|
||
if got.HPCurrent != 10 {
|
||
t.Errorf("rough rest HP = %d, want 10 (half of 20)", got.HPCurrent)
|
||
}
|
||
}
|