mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-16 08:52:42 +00:00
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>
This commit is contained in:
@@ -175,6 +175,7 @@ func (p *AdventurePlugin) Init() error {
|
|||||||
// have a player_meta row. Idempotent. Required for DBs that didn't go
|
// have a player_meta row. Idempotent. Required for DBs that didn't go
|
||||||
// through the L4-L5h dual-write soak (fresh deploys, restored backups).
|
// through the L4-L5h dual-write soak (fresh deploys, restored backups).
|
||||||
bootstrapPlayerMetaFromLegacy()
|
bootstrapPlayerMetaFromLegacy()
|
||||||
|
bootstrapRestoreExpeditionStreakDecay()
|
||||||
|
|
||||||
// Rehydrate DM room mappings for existing characters
|
// Rehydrate DM room mappings for existing characters
|
||||||
chars, err := loadAllAdvCharacters()
|
chars, err := loadAllAdvCharacters()
|
||||||
|
|||||||
@@ -98,6 +98,17 @@ func (p *AdventurePlugin) sendMorningDMs() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Active expedition: the expedition cycle delivers its own morning
|
||||||
|
// briefing at 06:00 UTC (deliverBriefing). The legacy overworld
|
||||||
|
// morning DM is irrelevant — and confusing — while underground.
|
||||||
|
if char.Alive {
|
||||||
|
if exp, err := getActiveExpedition(char.UserID); err != nil {
|
||||||
|
slog.Warn("adventure: failed to check active expedition for morning DM", "user", char.UserID, "err", err)
|
||||||
|
} else if exp != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If still dead, send death status
|
// If still dead, send death status
|
||||||
if !char.Alive {
|
if !char.Alive {
|
||||||
text := renderAdvDeathStatusDM(char.UserID)
|
text := renderAdvDeathStatusDM(char.UserID)
|
||||||
@@ -375,6 +386,27 @@ func (p *AdventurePlugin) midnightReset() error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Active expedition counts as activity. The expedition system tracks
|
||||||
|
// its own action flow (zone/harvest/combat/transit/extract) and never
|
||||||
|
// touches the legacy CombatActionsUsed/HarvestActionsUsed counters, so
|
||||||
|
// HasActedToday() reports false for expeditioners. Treat them like the
|
||||||
|
// acted-today branch below: advance the streak and bail out.
|
||||||
|
if exp, err := getActiveExpedition(char.UserID); err != nil {
|
||||||
|
slog.Warn("adventure: failed to check active expedition for idle reaper", "user", char.UserID, "err", err)
|
||||||
|
} else if exp != nil {
|
||||||
|
if char.LastActionDate == yesterday || char.LastActionDate == today {
|
||||||
|
char.CurrentStreak++
|
||||||
|
} else {
|
||||||
|
char.CurrentStreak = 1
|
||||||
|
}
|
||||||
|
if char.CurrentStreak > char.BestStreak {
|
||||||
|
char.BestStreak = char.CurrentStreak
|
||||||
|
}
|
||||||
|
char.LastActionDate = today
|
||||||
|
_ = saveAdvCharacter(&char)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Jitter between DMs to avoid Matrix rate limits
|
// Jitter between DMs to avoid Matrix rate limits
|
||||||
if dmsSent > 0 {
|
if dmsSent > 0 {
|
||||||
time.Sleep(time.Duration(1000+rand.IntN(2000)) * time.Millisecond)
|
time.Sleep(time.Duration(1000+rand.IntN(2000)) * time.Millisecond)
|
||||||
|
|||||||
69
internal/plugin/bootstrap_streak_restore.go
Normal file
69
internal/plugin/bootstrap_streak_restore.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user