mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 08:32:41 +00:00
Adv 2.0 D&D Phase 11 D9: !zone lore subcommand
Wires the existing GMLore narration pool (zone-specific LoreLines* + generic LoreLines fallback, all prewritten) into a !zone lore command. Deterministic per (run, room) — re-asking in the same room yields the same line; cross-room calls vary. No mood or state side-effects. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -60,6 +60,8 @@ func (p *AdventurePlugin) handleDnDZoneCmd(ctx MessageContext, args string) erro
|
|||||||
return p.zoneCmdTaunt(ctx)
|
return p.zoneCmdTaunt(ctx)
|
||||||
case "compliment":
|
case "compliment":
|
||||||
return p.zoneCmdCompliment(ctx)
|
return p.zoneCmdCompliment(ctx)
|
||||||
|
case "lore", "history":
|
||||||
|
return p.zoneCmdLore(ctx)
|
||||||
default:
|
default:
|
||||||
return p.SendDM(ctx.Sender, zoneHelpText())
|
return p.SendDM(ctx.Sender, zoneHelpText())
|
||||||
}
|
}
|
||||||
@@ -75,7 +77,8 @@ func zoneHelpText() string {
|
|||||||
b.WriteString("`!zone advance` — resolve the current room and move on\n")
|
b.WriteString("`!zone advance` — resolve the current room and move on\n")
|
||||||
b.WriteString("`!zone abandon` — end the active run (no rewards)\n")
|
b.WriteString("`!zone abandon` — end the active run (no rewards)\n")
|
||||||
b.WriteString("`!zone taunt` — poke TwinBee (mood −10)\n")
|
b.WriteString("`!zone taunt` — poke TwinBee (mood −10)\n")
|
||||||
b.WriteString("`!zone compliment` — flatter TwinBee (mood +5)")
|
b.WriteString("`!zone compliment` — flatter TwinBee (mood +5)\n")
|
||||||
|
b.WriteString("`!zone lore` — TwinBee shares zone history")
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,6 +544,30 @@ func (p *AdventurePlugin) zoneMoodInteraction(
|
|||||||
return p.SendDM(ctx.Sender, b.String())
|
return p.SendDM(ctx.Sender, b.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── lore ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// zoneCmdLore renders a deterministic TwinBee lore line for the active
|
||||||
|
// run's current room. Pulls from the zone-specific LoreLines* pool when
|
||||||
|
// one exists (Tier 1–5 ship dedicated lore), falls back to the generic
|
||||||
|
// LoreLines pool otherwise. roomIdx is folded into the selector so
|
||||||
|
// repeated `!zone lore` in the same room returns the same prose, but
|
||||||
|
// cross-room calls vary.
|
||||||
|
func (p *AdventurePlugin) zoneCmdLore(ctx MessageContext) error {
|
||||||
|
run, err := getActiveZoneRun(ctx.Sender)
|
||||||
|
if err != nil {
|
||||||
|
return p.SendDM(ctx.Sender, "Couldn't read run state: "+err.Error())
|
||||||
|
}
|
||||||
|
if run == nil {
|
||||||
|
return p.SendDM(ctx.Sender, "No active zone run. Use `!zone enter <id>`.")
|
||||||
|
}
|
||||||
|
zone, _ := getZone(run.ZoneID)
|
||||||
|
line := twinBeeLine(zone.ID, GMLore, run.RunID, run.CurrentRoom)
|
||||||
|
if line == "" {
|
||||||
|
return p.SendDM(ctx.Sender, "TwinBee has nothing to say about this place.")
|
||||||
|
}
|
||||||
|
return p.SendDM(ctx.Sender, fmt.Sprintf("📖 **%s — Lore**\n\n%s", zone.Display, line))
|
||||||
|
}
|
||||||
|
|
||||||
// ── abandon ─────────────────────────────────────────────────────────────────
|
// ── abandon ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
func (p *AdventurePlugin) zoneCmdAbandon(ctx MessageContext) error {
|
func (p *AdventurePlugin) zoneCmdAbandon(ctx MessageContext) error {
|
||||||
|
|||||||
@@ -318,6 +318,47 @@ func TestZoneCmd_TauntWithoutRunNoCrash(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Phase 11 D9 — !zone lore.
|
||||||
|
|
||||||
|
func TestZoneCmd_LoreWithoutRunNoCrash(t *testing.T) {
|
||||||
|
setupAuditTestDB(t)
|
||||||
|
uid := id.UserID("@zone-cmd-lore-norun:example")
|
||||||
|
zoneCmdTestCharacter(t, uid, 1)
|
||||||
|
defer cleanupZoneRuns(uid)
|
||||||
|
p := &AdventurePlugin{}
|
||||||
|
if err := p.handleDnDZoneCmd(MessageContext{Sender: uid}, "lore"); err != nil {
|
||||||
|
t.Fatalf("lore no-run: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestZoneCmd_LoreWithActiveRunNoSideEffects(t *testing.T) {
|
||||||
|
setupAuditTestDB(t)
|
||||||
|
uid := id.UserID("@zone-cmd-lore-run:example")
|
||||||
|
zoneCmdTestCharacter(t, uid, 1)
|
||||||
|
defer cleanupZoneRuns(uid)
|
||||||
|
p := &AdventurePlugin{}
|
||||||
|
if err := p.handleDnDZoneCmd(MessageContext{Sender: uid}, "enter goblin_warrens"); err != nil {
|
||||||
|
t.Fatalf("enter: %v", err)
|
||||||
|
}
|
||||||
|
before, err := getActiveZoneRun(uid)
|
||||||
|
if err != nil || before == nil {
|
||||||
|
t.Fatalf("active run after enter: %v", err)
|
||||||
|
}
|
||||||
|
if err := p.handleDnDZoneCmd(MessageContext{Sender: uid}, "lore"); err != nil {
|
||||||
|
t.Fatalf("lore: %v", err)
|
||||||
|
}
|
||||||
|
after, err := getActiveZoneRun(uid)
|
||||||
|
if err != nil || after == nil {
|
||||||
|
t.Fatalf("active run after lore: %v", err)
|
||||||
|
}
|
||||||
|
if after.GMMood != before.GMMood {
|
||||||
|
t.Errorf("lore should not move mood: before %d, after %d", before.GMMood, after.GMMood)
|
||||||
|
}
|
||||||
|
if after.CurrentRoom != before.CurrentRoom {
|
||||||
|
t.Errorf("lore should not advance room: before %d, after %d", before.CurrentRoom, after.CurrentRoom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTauntComplimentResponseLines_Deterministic(t *testing.T) {
|
func TestTauntComplimentResponseLines_Deterministic(t *testing.T) {
|
||||||
const runID = "run-d7-test"
|
const runID = "run-d7-test"
|
||||||
t1 := tauntResponseLine(runID, 0)
|
t1 := tauntResponseLine(runID, 0)
|
||||||
|
|||||||
Reference in New Issue
Block a user