diff --git a/internal/plugin/adventure_scheduler.go b/internal/plugin/adventure_scheduler.go index 0661818..d66de9d 100644 --- a/internal/plugin/adventure_scheduler.go +++ b/internal/plugin/adventure_scheduler.go @@ -495,6 +495,12 @@ func (p *AdventurePlugin) midnightReset() error { return fmt.Errorf("reset daily actions after 3 attempts: %w", resetErr) } + // Clear the one-day pet morning-defense buff so it re-rolls fresh each + // morning (briefing or overworld DM) instead of leaking permanently. + if err := resetAllPetMorningDefense(); err != nil { + slog.Error("adventure: failed to reset pet morning defense", "err", err) + } + // Prune expired buffs if err := pruneAdvExpiredBuffs(); err != nil { slog.Error("adventure: failed to prune expired buffs", "err", err) diff --git a/internal/plugin/dnd_expedition_cycle.go b/internal/plugin/dnd_expedition_cycle.go index fa2df6d..ddc7c2f 100644 --- a/internal/plugin/dnd_expedition_cycle.go +++ b/internal/plugin/dnd_expedition_cycle.go @@ -300,6 +300,22 @@ func (p *AdventurePlugin) deliverBriefing(e *Expedition, now time.Time) error { } if uid := id.UserID(e.UserID); uid != "" { + // The legacy overworld morning DM is skipped while underground, so + // its 25% morning pet event fires here instead, granting the one-day + // defense buff (reset at midnight via resetAllPetMorningDefense). + // Pet *arrival* is handled separately on the emergence seam below โ€” + // not queued here โ€” so we only roll the morning event. + if pet, perr := loadPetState(uid); perr == nil { + if petEvent := petMorningEvent(pet); petEvent != "" { + if char, cerr := loadAdvCharacter(uid); cerr == nil { + char.PetMorningDefense = true + if serr := saveAdvCharacter(char); serr != nil { + slog.Warn("expedition: save pet morning defense", "user", uid, "err", serr) + } + } + body = fmt.Sprintf("๐Ÿพ *%s*\n\n%s", petEvent, body) + } + } if err := p.SendDM(uid, body); err != nil { slog.Warn("expedition: send briefing DM", "user", uid, "err", err) } diff --git a/internal/plugin/player_meta.go b/internal/plugin/player_meta.go index b92cfb7..2304eef 100644 --- a/internal/plugin/player_meta.go +++ b/internal/plugin/player_meta.go @@ -909,6 +909,19 @@ func resetAllPlayerMetaDailyActions() error { return err } +// resetAllPetMorningDefense clears the one-day pet morning-defense buff for +// every player. The buff is a fresh morning grant (25% roll in the briefing / +// overworld DM), so it must not survive past the midnight rollover. The flag +// lives inside pet_flags_json (see petFlagsJSON), so patch that key in place; +// only rows where it's currently set are touched. +func resetAllPetMorningDefense() error { + _, err := db.Get().Exec(` + UPDATE player_meta + SET pet_flags_json = json_set(pet_flags_json, '$.morning_defense', json('false')) + WHERE json_extract(pet_flags_json, '$.morning_defense') = 1`) + return err +} + // DeathState mirrors player_meta's death-state columns. Phase L5e ports // these fields off AdvCharacter (gogobee_legacy_migration.md ยง7.3 L5e). // Mutation surface is large (~50 saveAdvCharacter sites touch death