Files
gogobee/internal/plugin/bootstrap_streak_restore.go
prosolis 2b86f3df16 Expedition: skip idle reaper + legacy morning DM while on expedition
The midnight idle reaper inspected only the overworld CombatActionsUsed/
HarvestActionsUsed counters, so a player on an active expedition was
flagged idle — streak halved and a nonsense overworld shame DM sent. The
08:00 morning DM had the same gap and stomped on the 06:00 expedition
briefing. Both paths now early-out when getActiveExpedition is non-nil
(advancing the streak in the midnight branch).

Includes a one-shot bootstrap that doubles CurrentStreak (capped at
BestStreak, +1 when room allows to recover the integer-divide odd half)
for characters still flagged StreakDecayed with an active expedition.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:54:40 -07:00

70 lines
2.1 KiB
Go

package plugin
import (
"log/slog"
"gogobee/internal/db"
)
// bootstrapRestoreExpeditionStreakDecay reverses the one-time streak halving
// applied by midnightReset to players who were on an active expedition. The
// idle reaper used to ignore expedition activity (it only inspected the
// legacy CombatActionsUsed/HarvestActionsUsed counters), so expeditioners
// were shamed and had their streak divided by 2.
//
// Restoration: CurrentStreak doubles (recovering the integer-divide), capped
// at BestStreak. The odd-half is recovered by adding 1 when BestStreak
// allows — pre-decay value was either CurrentStreak*2 or CurrentStreak*2+1,
// and BestStreak is a monotonic ceiling that bounds the original.
//
// Idempotent: gated on a fixed job key so it runs at most once per DB.
func bootstrapRestoreExpeditionStreakDecay() {
const jobName = "idle_reaper_expedition_streak_restore_v1"
if db.JobCompleted(jobName, "once") {
return
}
chars, err := loadAllAdvCharacters()
if err != nil {
slog.Error("bootstrap: streak restore — failed to load characters", "err", err)
return
}
restored := 0
for _, char := range chars {
if !char.StreakDecayed || char.CurrentStreak == 0 {
continue
}
exp, err := getActiveExpedition(char.UserID)
if err != nil {
slog.Warn("bootstrap: streak restore — expedition lookup failed", "user", char.UserID, "err", err)
continue
}
if exp == nil {
continue
}
old := char.CurrentStreak
candidate := old*2 + 1
if char.BestStreak > 0 && candidate > char.BestStreak {
candidate = char.BestStreak
}
if candidate < old*2 {
candidate = old * 2
}
char.CurrentStreak = candidate
char.StreakDecayed = false
if err := saveAdvCharacter(&char); err != nil {
slog.Error("bootstrap: streak restore — save failed", "user", char.UserID, "err", err)
continue
}
slog.Info("bootstrap: streak restored", "user", char.UserID, "old", old, "new", candidate, "best", char.BestStreak)
restored++
}
db.MarkJobCompleted(jobName, "once")
if restored > 0 {
slog.Warn("bootstrap: expedition streak decay restored", "count", restored)
}
}