mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 16:42:41 +00:00
Adv 2.0 daily report: migrate to D&D-native render + unified activity source
Two longstanding warts surfaced when the user inspected today's TwinBee
daily report:
1. Active players were rendering "Acted today — no log recorded." The
adventure_activity_log path only captures !rest now; harvest/zone/
expedition action logging was lost during Phase R/L migration. The
stale HasActedToday() check fired on fossil harvest_actions_used
counters that nothing modern increments.
2. Per-player headline still rendered legacy four-skill line ("Combat
Lv.X | Mining Lv.Y | Forage Lv.Z | Fishing Lv.W"). No D&D level,
race, class, or HP — discoverability gap for the layer that's now
canonical.
Fix both:
- adventure_activity_unified.go: new loadAdvDailyActivity(date) unions
dnd_zone_run (zone runs touched today) + dnd_expedition_log
(expedition activity rolled up per expedition) + legacy
adventure_activity_log (mostly !rest). One source of truth.
- adventure_render.go: AdvPlayerDaySummary gains HasDnDChar/DnDLevel/
DnDRace/DnDClass/HPCurrent/HPMax. New advPlayerHeadline helper
renders "Lv.N Race Class — HP cur/max" when a dnd_character row
exists, falls back to the legacy four-skill line plus an inline
"(no D&D sheet — run !setup)" nudge for accounts that haven't
completed setup yet. titleizeDnDToken("half_elf") → "Half-Elf".
- adventure_scheduler.go: postDailySummary swaps loadAdvLogsForDate
for loadAdvDailyActivity. IsResting now derives from "len(acts)==0"
rather than the fossil HasActedToday() counter check, so dead
harvest_actions_used bytes can't trip the active-player branch.
- proddb_daily_report_test.go: renders the report against a copy of
data/gogobee.db and prints it for visual confirmation. Verified
output for 2026-05-09 shows real zone-run outcomes (Withdrew from
Sunken Temple at room 1/6, etc.) instead of the previous "no log
recorded" placeholder.
go vet + go test ./... clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -711,11 +711,24 @@ func renderAdvOnboardingDM(userID id.UserID) string {
|
||||
// ── Daily Summary ────────────────────────────────────────────────────────────
|
||||
|
||||
type AdvPlayerDaySummary struct {
|
||||
DisplayName string
|
||||
Level int
|
||||
MiningSkill int
|
||||
ForagingSkill int
|
||||
FishingSkill int
|
||||
DisplayName string
|
||||
|
||||
// D&D layer (preferred render). HasDnDChar=false → fall back to legacy
|
||||
// skill-line render and "needs !setup" hint.
|
||||
HasDnDChar bool
|
||||
DnDLevel int
|
||||
DnDRace string
|
||||
DnDClass string
|
||||
HPCurrent int
|
||||
HPMax int
|
||||
|
||||
// Legacy AdventureCharacter skill levels (rendered as fallback when
|
||||
// HasDnDChar is false).
|
||||
Level int // legacy combat level
|
||||
MiningSkill int
|
||||
ForagingSkill int
|
||||
FishingSkill int
|
||||
|
||||
Activity string
|
||||
Location string
|
||||
Outcome string
|
||||
@@ -729,6 +742,36 @@ type AdvPlayerDaySummary struct {
|
||||
DeathLocation string
|
||||
}
|
||||
|
||||
// advPlayerHeadline renders the per-player headline for the daily report.
|
||||
// D&D shape ("Lv.4 Half-Elf Cleric — HP 27/27") is preferred; falls back to
|
||||
// the legacy four-skill line for accounts that haven't run !setup yet.
|
||||
func advPlayerHeadline(p *AdvPlayerDaySummary) string {
|
||||
if p.HasDnDChar {
|
||||
race := titleizeDnDToken(p.DnDRace)
|
||||
class := titleizeDnDToken(p.DnDClass)
|
||||
return fmt.Sprintf("Lv.%d %s %s — HP %d/%d",
|
||||
p.DnDLevel, race, class, p.HPCurrent, p.HPMax)
|
||||
}
|
||||
return fmt.Sprintf("Combat Lv.%d | Mining Lv.%d | Forage Lv.%d | Fishing Lv.%d _(no D&D sheet — run `!setup`)_",
|
||||
p.Level, p.MiningSkill, p.ForagingSkill, p.FishingSkill)
|
||||
}
|
||||
|
||||
// titleizeDnDToken converts a snake_case D&D enum token like "half_elf" into
|
||||
// a human-readable label like "Half-Elf". Single-word tokens just title-case.
|
||||
func titleizeDnDToken(s string) string {
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
parts := strings.Split(s, "_")
|
||||
for i, p := range parts {
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
parts[i] = strings.ToUpper(p[:1]) + p[1:]
|
||||
}
|
||||
return strings.Join(parts, "-")
|
||||
}
|
||||
|
||||
func renderAdvDailySummary(date string, tb *TwinBeeResult, tbRewards TwinBeeRewardSummary, players []AdvPlayerDaySummary, holidayName string) string {
|
||||
var sb strings.Builder
|
||||
|
||||
@@ -809,8 +852,7 @@ func renderAdvDailySummary(date string, tb *TwinBeeResult, tbRewards TwinBeeRewa
|
||||
if !diedOnAdventure {
|
||||
icon = "⚔️"
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%s **%s** — Combat Lv.%d | Mining Lv.%d | Forage Lv.%d | Fishing Lv.%d\n",
|
||||
icon, p.DisplayName, p.Level, p.MiningSkill, p.ForagingSkill, p.FishingSkill))
|
||||
sb.WriteString(fmt.Sprintf("%s **%s** — %s\n", icon, p.DisplayName, advPlayerHeadline(p)))
|
||||
sb.WriteString(fmt.Sprintf(" Went to: %s\n", p.Location))
|
||||
sb.WriteString(fmt.Sprintf(" Outcome: %s\n", p.SummaryLine))
|
||||
if !diedOnAdventure {
|
||||
@@ -832,8 +874,7 @@ func renderAdvDailySummary(date string, tb *TwinBeeResult, tbRewards TwinBeeRewa
|
||||
continue
|
||||
}
|
||||
|
||||
sb.WriteString(fmt.Sprintf("⚔️ **%s** — Combat Lv.%d | Mining Lv.%d | Forage Lv.%d | Fishing Lv.%d\n",
|
||||
p.DisplayName, p.Level, p.MiningSkill, p.ForagingSkill, p.FishingSkill))
|
||||
sb.WriteString(fmt.Sprintf("⚔️ **%s** — %s\n", p.DisplayName, advPlayerHeadline(p)))
|
||||
if p.Location != "" {
|
||||
sb.WriteString(fmt.Sprintf(" Went to: %s\n", p.Location))
|
||||
sb.WriteString(fmt.Sprintf(" Outcome: %s\n\n", p.SummaryLine))
|
||||
|
||||
Reference in New Issue
Block a user