mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 00:32:40 +00:00
Close expedition/run seam on combat loss & flee
Run-loss paths (turn-based elite/boss death + flee, exploration combat death/retreat, interrupt/patrol death) abandoned the zone run but left the wrapping expedition row at status='active', so the ambient ticker kept DMing about a dungeon the player had walked away from. Adds forceExtractExpeditionForRunLoss helper and wires it into every run-loss site; ambient ticker also skips when the expedition's run is no longer active as a safety net. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -267,6 +267,7 @@ func (p *AdventurePlugin) finishCombatSession(userID id.UserID, sess *CombatSess
|
||||
_, _ = applyMoodEvent(sess.RunID, MoodEventPlayerDeath)
|
||||
}
|
||||
_ = abandonZoneRun(userID)
|
||||
forceExtractExpeditionForRunLoss(userID, "combat death")
|
||||
markAdventureDead(userID, "zone", zone.Display)
|
||||
if line := twinBeeLine(zone.ID, DMPlayerDeath, sess.RunID, cadence); line != "" {
|
||||
b.WriteString(line + "\n")
|
||||
@@ -278,6 +279,7 @@ func (p *AdventurePlugin) finishCombatSession(userID id.UserID, sess *CombatSess
|
||||
// but no death timer. Chosen candidate from the migration plan's
|
||||
// open question on flee outcome.
|
||||
_ = abandonZoneRun(userID)
|
||||
forceExtractExpeditionForRunLoss(userID, "combat flee")
|
||||
b.WriteString(fmt.Sprintf("🏃 You broke off from **%s** and slipped away. Run ended — you keep your wounds, but you live.", enemy.Name))
|
||||
|
||||
default:
|
||||
|
||||
@@ -188,6 +188,7 @@ func (p *AdventurePlugin) runHarvestInterrupt(
|
||||
_, _ = applyMoodEvent(run.RunID, MoodEventPlayerDeath)
|
||||
_ = abandonZoneRun(userID)
|
||||
_ = retireAllRegionRuns(exp)
|
||||
_, _, _ = forcedExtractExpedition(exp.ID, "interrupt death")
|
||||
markAdventureDead(userID, "expedition", zone.Display)
|
||||
if line := flavor.Pick(flavor.PlayerDeath); line != "" {
|
||||
b.WriteString(line)
|
||||
@@ -513,6 +514,7 @@ func (p *AdventurePlugin) tryPatrolEncounter(
|
||||
_, _ = applyMoodEvent(run.RunID, MoodEventPlayerDeath)
|
||||
_ = abandonZoneRun(userID)
|
||||
_ = retireAllRegionRuns(exp)
|
||||
_, _, _ = forcedExtractExpedition(exp.ID, "patrol death")
|
||||
markAdventureDead(userID, "patrol", zone.Display)
|
||||
if line := flavor.Pick(flavor.PlayerDeath); line != "" {
|
||||
ob.WriteString(line)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -92,6 +93,23 @@ func forcedExtractExpedition(expID, reason string) (*Expedition, int, error) {
|
||||
return e, tax, nil
|
||||
}
|
||||
|
||||
// forceExtractExpeditionForRunLoss bridges run-loss call sites (turn-based
|
||||
// elite/boss death or flee, exploration combat death, patrol-interrupt
|
||||
// death) into the forced-extract flow. Those sites already abandon the
|
||||
// zone run, but without flipping the wrapping expedition the ambient
|
||||
// ticker keeps DMing about a dungeon the player walked away from. No-op
|
||||
// when there is no active expedition for this user.
|
||||
func forceExtractExpeditionForRunLoss(userID id.UserID, reason string) {
|
||||
exp, err := getActiveExpedition(userID)
|
||||
if err != nil || exp == nil {
|
||||
return
|
||||
}
|
||||
if _, _, err := forcedExtractExpedition(exp.ID, reason); err != nil {
|
||||
slog.Warn("expedition: force-extract on run loss",
|
||||
"user", userID, "expedition", exp.ID, "reason", reason, "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// getResumableExpedition returns the most recent 'extracting' row for the
|
||||
// user, regardless of age (caller checks the 7-day window).
|
||||
func getResumableExpedition(userID id.UserID) (*Expedition, error) {
|
||||
|
||||
@@ -861,6 +861,9 @@ func (p *AdventurePlugin) resolveCombatRoom(userID id.UserID, run *DungeonRun, z
|
||||
// respawn timer for what is mechanically "ran out the clock".
|
||||
if !result.TimedOut {
|
||||
markAdventureDead(userID, "zone", zone.Display)
|
||||
forceExtractExpeditionForRunLoss(userID, "combat death")
|
||||
} else {
|
||||
forceExtractExpeditionForRunLoss(userID, "combat retreat")
|
||||
}
|
||||
if line := twinBeeLine(zone.ID, DMPlayerDeath, run.RunID, narrationCadence(run)); line != "" {
|
||||
ob.WriteString(line)
|
||||
|
||||
@@ -84,6 +84,15 @@ func (p *AdventurePlugin) fireExpeditionAmbients(now time.Time) {
|
||||
if e.Status != ExpeditionStatusActive {
|
||||
continue
|
||||
}
|
||||
// Safety net: if the wrapping zone run is abandoned or completed,
|
||||
// the player has functionally left the dungeon even though the
|
||||
// expedition row hasn't been flipped. Skip silently rather than
|
||||
// DM about a place they're no longer in.
|
||||
if e.RunID != "" {
|
||||
if run, _ := getZoneRun(e.RunID); run != nil && !run.IsActive() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
uid := id.UserID(e.UserID)
|
||||
if hasActiveCombatSession(uid) {
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user