Adv 2.0 L4f-prep: flip DisplayName readers to loadDisplayName

Swap all char.DisplayName / c.DisplayName reader sites in
internal/plugin/ to loadDisplayName(userID). Touches 12 files
across combat (combat_bridge, dnd_zone_combat, dnd_zone_cmd,
dnd_expedition_combat), arena, hospital, events, render,
masterwork, robbie, scheduler, and adventure.go.

Only intentional char.DisplayName references remaining are in
adventure_character.go (scan + save + dual-write) and the
player_meta.go doc comment. go vet + go test ./internal/plugin/...
clean.

Unblocks deferred L2 step 9 (arena AdvCharacter import drop)
once L4 hospital/pets/render also lands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-05-09 09:36:50 -07:00
parent 5000c3d696
commit cc6fb7dd39
13 changed files with 67 additions and 37 deletions

View File

@@ -314,9 +314,11 @@ This is also the right shape because zone-boss plumbing (bestiary, mood events,
- `player_meta.display_name` column added via column migration in `internal/db/db.go`. - `player_meta.display_name` column added via column migration in `internal/db/db.go`.
- `backfillPlayerMetaDisplayName` runs on every Init: idempotent (`UPDATE ... WHERE display_name = ''` only touches empty rows, so dual-writes layered on top survive re-runs). - `backfillPlayerMetaDisplayName` runs on every Init: idempotent (`UPDATE ... WHERE display_name = ''` only touches empty rows, so dual-writes layered on top survive re-runs).
- `upsertPlayerMetaDisplayName(userID, name)` dual-writes from `createAdvCharacter` (inside the same tx, atomic with the AdvCharacter INSERT) and `saveAdvCharacter` (post-commit, error-logged but non-fatal). - `upsertPlayerMetaDisplayName(userID, name)` dual-writes from `createAdvCharacter` (inside the same tx, atomic with the AdvCharacter INSERT) and `saveAdvCharacter` (post-commit, error-logged but non-fatal).
- `loadDisplayName(userID)` reads `player_meta` → falls back to `adventure_characters.display_name` → empty string if neither exists. Helper exists but **readers are NOT flipped yet** — soak week begins now; per-call-site flip happens in a follow-up session once we've confirmed dual-write doesn't drift. - `loadDisplayName(userID)` reads `player_meta` → falls back to `adventure_characters.display_name` → empty string if neither exists.
- Tests: `TestPlayerMetaDisplayNameBackfill_Idempotent`, `TestLoadDisplayName_FallsBackToAdvCharacter`, `TestCreateAdvCharacter_DualWritesDisplayName`. - Tests: `TestPlayerMetaDisplayNameBackfill_Idempotent`, `TestLoadDisplayName_FallsBackToAdvCharacter`, `TestCreateAdvCharacter_DualWritesDisplayName`.
**Reader flip SHIPPED (2026-05-09).** All `char.DisplayName` / `c.DisplayName` reader sites in `internal/plugin/` were swapped to `loadDisplayName(userID)` (or pass-through equivalents) across: `combat_bridge.go`, `dnd_zone_cmd.go`, `dnd_expedition_combat.go`, `dnd_zone_combat.go`, `adventure_robbie.go`, `adventure_scheduler.go`, `adventure_hospital.go`, `adventure_events.go`, `adventure_render.go`, `adventure_masterwork.go`, `adventure.go`, `adventure_arena.go`. The only remaining `char.DisplayName` references are in `adventure_character.go` itself (scan into struct + `saveAdvCharacter` SQL + dual-write to `player_meta`) and the `player_meta.go` doc comment. `go vet` + `go test ./internal/plugin/...` clean.
**Pre-conditions:** **Pre-conditions:**
- L1L4 all shipped. - L1L4 all shipped.
- DisplayName migrated to `player_meta` (see above). - DisplayName migrated to `player_meta` (see above).

View File

@@ -699,8 +699,9 @@ func (p *AdventurePlugin) handleAdminRevive(ctx MessageContext, target string) e
return p.SendReply(ctx.RoomID, ctx.EventID, "That user has no adventurer.") return p.SendReply(ctx.RoomID, ctx.EventID, "That user has no adventurer.")
} }
displayName, _ := loadDisplayName(targetID)
if char.Alive { if char.Alive {
return p.SendReply(ctx.RoomID, ctx.EventID, fmt.Sprintf("%s is already alive.", char.DisplayName)) return p.SendReply(ctx.RoomID, ctx.EventID, fmt.Sprintf("%s is already alive.", displayName))
} }
char.Alive = true char.Alive = true
@@ -713,7 +714,7 @@ func (p *AdventurePlugin) handleAdminRevive(ctx MessageContext, target string) e
if p.achievements != nil { if p.achievements != nil {
p.achievements.GrantAchievement(targetID, "adv_revived") p.achievements.GrantAchievement(targetID, "adv_revived")
} }
return p.SendReply(ctx.RoomID, ctx.EventID, fmt.Sprintf("✅ %s has been revived.", char.DisplayName)) return p.SendReply(ctx.RoomID, ctx.EventID, fmt.Sprintf("✅ %s has been revived.", displayName))
} }
func (p *AdventurePlugin) handleAdminSummary(ctx MessageContext) error { func (p *AdventurePlugin) handleAdminSummary(ctx MessageContext) error {
@@ -982,6 +983,8 @@ func (p *AdventurePlugin) resolveRest(ctx MessageContext, char *AdventureCharact
logAdvActivity(char.UserID, string(AdvActivityRest), "", "rest", 0, 0, "") logAdvActivity(char.UserID, string(AdvActivityRest), "", "rest", 0, 0, "")
dispName, _ := loadDisplayName(char.UserID)
// Compute reset countdown // Compute reset countdown
now := time.Now().UTC() now := time.Now().UTC()
midnight := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, time.UTC) midnight := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, time.UTC)
@@ -998,7 +1001,7 @@ func (p *AdventurePlugin) resolveRest(ctx MessageContext, char *AdventureCharact
"Morning DM: %02d:00 UTC\n"+ "Morning DM: %02d:00 UTC\n"+
"Evening summary: %02d:00 UTC\n\n"+ "Evening summary: %02d:00 UTC\n\n"+
"The Arena is always open: `!arena`", "The Arena is always open: `!arena`",
char.DisplayName, hours, minutes, p.morningHour, p.summaryHour) dispName, hours, minutes, p.morningHour, p.summaryHour)
if err := p.SendDM(ctx.Sender, restMsg); err != nil { if err := p.SendDM(ctx.Sender, restMsg); err != nil {
slog.Error("adventure: failed to send rest DM", "user", ctx.Sender, "err", err) slog.Error("adventure: failed to send rest DM", "user", ctx.Sender, "err", err)
@@ -1210,8 +1213,9 @@ func (p *AdventurePlugin) announceTreasureToRoom(char *AdventureCharacter, def *
if gr == "" { if gr == "" {
return return
} }
displayName, _ := loadDisplayName(char.UserID)
announce := advSubstituteFlavor(def.RoomAnnounce, map[string]string{ announce := advSubstituteFlavor(def.RoomAnnounce, map[string]string{
"{name}": char.DisplayName, "{name}": displayName,
"{location}": loc.Name, "{location}": loc.Name,
}) })
p.SendMessage(id.RoomID(gr), announce) p.SendMessage(id.RoomID(gr), announce)
@@ -1221,8 +1225,9 @@ func (p *AdventurePlugin) announceTreasureToRoom(char *AdventureCharacter, def *
func (p *AdventurePlugin) selectFlavorText(char *AdventureCharacter, result *AdvActionResult) (string, string) { func (p *AdventurePlugin) selectFlavorText(char *AdventureCharacter, result *AdvActionResult) (string, string) {
loc := result.Location loc := result.Location
displayName, _ := loadDisplayName(char.UserID)
vars := map[string]string{ vars := map[string]string{
"{name}": char.DisplayName, "{name}": displayName,
"{location}": loc.Name, "{location}": loc.Name,
"{value}": fmt.Sprintf("%d", result.TotalLootValue), "{value}": fmt.Sprintf("%d", result.TotalLootValue),
"{xp}": fmt.Sprintf("%d", result.XPGained), "{xp}": fmt.Sprintf("%d", result.XPGained),

View File

@@ -205,10 +205,11 @@ type arenaBossNarration struct {
// surrounding economic glue. There is no legacy fallback — a boss-flow // surrounding economic glue. There is no legacy fallback — a boss-flow
// error surfaces to the player and aborts the round. // error surfaces to the player and aborts the round.
func (p *AdventurePlugin) resolveArenaRound(ctx MessageContext, run *ArenaRun, char *AdventureCharacter, equip map[EquipmentSlot]*AdvEquipment, tier *ArenaTier, monster *ArenaMonster) error { func (p *AdventurePlugin) resolveArenaRound(ctx MessageContext, run *ArenaRun, char *AdventureCharacter, equip map[EquipmentSlot]*AdvEquipment, tier *ArenaTier, monster *ArenaMonster) error {
displayName, _ := loadDisplayName(ctx.Sender)
intro, phases, outcome, result, err := p.resolveArenaBoss(ctx.Sender, ArenaBossEncounter{ intro, phases, outcome, result, err := p.resolveArenaBoss(ctx.Sender, ArenaBossEncounter{
Tier: run.Tier, Tier: run.Tier,
Round: run.Round, Round: run.Round,
DisplayName: char.DisplayName, DisplayName: displayName,
}) })
if err != nil { if err != nil {
slog.Error("arena: boss flow failed", "user", ctx.Sender, "err", err) slog.Error("arena: boss flow failed", "user", ctx.Sender, "err", err)
@@ -299,7 +300,8 @@ func (p *AdventurePlugin) handleArenaStats(ctx MessageContext) error {
} else { } else {
slog.Error("player_meta: arena stats read failed, falling back to AdvCharacter", "user", ctx.Sender, "err", err) slog.Error("player_meta: arena stats read failed, falling back to AdvCharacter", "user", ctx.Sender, "err", err)
} }
return p.SendDM(ctx.Sender, renderArenaPersonalStats(char.DisplayName, wins, losses, stats)) displayName, _ := loadDisplayName(ctx.Sender)
return p.SendDM(ctx.Sender, renderArenaPersonalStats(displayName, wins, losses, stats))
} }
func (p *AdventurePlugin) handleArenaLeaderboard(ctx MessageContext) error { func (p *AdventurePlugin) handleArenaLeaderboard(ctx MessageContext) error {
@@ -401,7 +403,8 @@ func (p *AdventurePlugin) resolveArenaSurvival(ctx MessageContext, run *ArenaRun
if dropped := p.arenaRollHelmetDrop(ctx.Sender, run.Tier); dropped != nil { if dropped := p.arenaRollHelmetDrop(ctx.Sender, run.Tier); dropped != nil {
finalMessage += "\n" + renderArenaHelmetDrop(dropped) finalMessage += "\n" + renderArenaHelmetDrop(dropped)
p.postArenaDropAnnouncement(char.DisplayName, dropped) displayName, _ := loadDisplayName(ctx.Sender)
p.postArenaDropAnnouncement(displayName, dropped)
} }
done := p.sendZoneCombatMessages(ctx.Sender, phaseMessages, finalMessage) done := p.sendZoneCombatMessages(ctx.Sender, phaseMessages, finalMessage)
@@ -498,7 +501,8 @@ func (p *AdventurePlugin) resolveArenaDeath(ctx MessageContext, run *ArenaRun, c
if dt.PetRecovered { if dt.PetRecovered {
gr := gamesRoom() gr := gamesRoom()
if gr != "" { if gr != "" {
_ = p.SendMessage(gr, petDitchRecoveryGameRoom(char.DisplayName, char.PetName, true)) displayName, _ := loadDisplayName(ctx.Sender)
_ = p.SendMessage(gr, petDitchRecoveryGameRoom(displayName, char.PetName, true))
} }
} }
@@ -581,8 +585,9 @@ func (p *AdventurePlugin) arenaCompleteSession(userID id.UserID, run *ArenaRun,
// Room announcement for T5 // Room announcement for T5
gr := gamesRoom() gr := gamesRoom()
if gr != "" { if gr != "" {
displayName, _ := loadDisplayName(userID)
announce := fmt.Sprintf("🏆 **%s has conquered the Arena.** Tier 5 streak. €%d earned. That Which Has Always Been has fallen.", announce := fmt.Sprintf("🏆 **%s has conquered the Arena.** Tier 5 streak. €%d earned. That Which Has Always Been has fallen.",
char.DisplayName, run.Earnings) displayName, run.Earnings)
p.SendMessage(id.RoomID(gr), announce) p.SendMessage(id.RoomID(gr), announce)
} }
} }
@@ -596,9 +601,10 @@ func (p *AdventurePlugin) arenaCompleteSession(userID id.UserID, run *ArenaRun,
// Game room announcement // Game room announcement
gr := gamesRoom() gr := gamesRoom()
if gr != "" { if gr != "" {
displayName, _ := loadDisplayName(userID)
p.SendMessage(id.RoomID(gr), fmt.Sprintf( p.SendMessage(id.RoomID(gr), fmt.Sprintf(
"🏆 %s walked out of the arena with the Gladiator's Helm. Tier %d streak. They had the option to stop. They did not stop.", "🏆 %s walked out of the arena with the Gladiator's Helm. Tier %d streak. They had the option to stop. They did not stop.",
char.DisplayName, tiersWon)) displayName, tiersWon))
} }
} }
} }

View File

@@ -142,9 +142,11 @@ func (p *AdventurePlugin) tryTriggerEvent(userID id.UserID) {
slog.Info("adventure: mid-day event triggered", "user", userID, "event", event.Key, "id", eventID) slog.Info("adventure: mid-day event triggered", "user", userID, "event", event.Key, "id", eventID)
displayName, _ := loadDisplayName(userID)
// DM the player // DM the player
triggerDM := advSubstituteFlavor(event.TriggerDM, map[string]string{ triggerDM := advSubstituteFlavor(event.TriggerDM, map[string]string{
"{name}": char.DisplayName, "{name}": displayName,
}) })
if err := p.SendDM(userID, triggerDM); err != nil { if err := p.SendDM(userID, triggerDM); err != nil {
slog.Error("adventure: events: failed to send trigger DM", "user", userID, "err", err) slog.Error("adventure: events: failed to send trigger DM", "user", userID, "err", err)
@@ -154,7 +156,7 @@ func (p *AdventurePlugin) tryTriggerEvent(userID id.UserID) {
gr := gamesRoom() gr := gamesRoom()
if gr != "" { if gr != "" {
roomLine := advSubstituteFlavor(advEventRoomTriggerWrapper, map[string]string{ roomLine := advSubstituteFlavor(advEventRoomTriggerWrapper, map[string]string{
"{trigger_room_line}": advSubstituteFlavor(event.TriggerRoomLine, map[string]string{"{name}": char.DisplayName}), "{trigger_room_line}": advSubstituteFlavor(event.TriggerRoomLine, map[string]string{"{name}": displayName}),
}) })
_ = p.SendMessage(id.RoomID(gr), roomLine) _ = p.SendMessage(id.RoomID(gr), roomLine)
} }

View File

@@ -55,7 +55,8 @@ func (p *AdventurePlugin) handleHospitalCmd(ctx MessageContext) error {
// Room announcement // Room announcement
gr := gamesRoom() gr := gamesRoom()
if gr != "" { if gr != "" {
p.SendMessage(gr, fmt.Sprintf(hospitalDitchAnnounce, char.DisplayName)) name, _ := loadDisplayName(char.UserID)
p.SendMessage(gr, fmt.Sprintf(hospitalDitchAnnounce, name))
} }
return nil return nil
} }
@@ -152,7 +153,8 @@ func (p *AdventurePlugin) resolveHospitalPay(ctx MessageContext, interaction *ad
gr := gamesRoom() gr := gamesRoom()
if gr != "" { if gr != "" {
p.SendMessage(gr, fmt.Sprintf(hospitalDitchAnnounce, char.DisplayName)) name, _ := loadDisplayName(char.UserID)
p.SendMessage(gr, fmt.Sprintf(hospitalDitchAnnounce, name))
} }
return nil return nil
} }
@@ -191,7 +193,8 @@ func (p *AdventurePlugin) resolveHospitalPay(ctx MessageContext, interaction *ad
// Room announcement // Room announcement
gr := gamesRoom() gr := gamesRoom()
if gr != "" { if gr != "" {
p.SendMessage(gr, fmt.Sprintf(hospitalDischargeAnnounce, char.DisplayName)) name, _ := loadDisplayName(char.UserID)
p.SendMessage(gr, fmt.Sprintf(hospitalDischargeAnnounce, name))
} }
return nil return nil
@@ -206,7 +209,8 @@ func (p *AdventurePlugin) resolveHospitalPay(ctx MessageContext, interaction *ad
if err == nil { if err == nil {
gr := gamesRoom() gr := gamesRoom()
if gr != "" { if gr != "" {
p.SendMessage(gr, fmt.Sprintf(hospitalDitchAnnounce, char.DisplayName)) name, _ := loadDisplayName(char.UserID)
p.SendMessage(gr, fmt.Sprintf(hospitalDitchAnnounce, name))
} }
} }

View File

@@ -262,7 +262,8 @@ func (p *AdventurePlugin) checkMasterworkDrop(userID id.UserID, char *AdventureC
p.SendDM(userID, sb.String()) p.SendDM(userID, sb.String())
// Room announcement (tiered) // Room announcement (tiered)
p.postMasterworkAnnouncement(char.DisplayName, def, loc.Name) displayName, _ := loadDisplayName(userID)
p.postMasterworkAnnouncement(displayName, def, loc.Name)
} }
// ── Room Announcements ───────────────────────────────────────────────────── // ── Room Announcements ─────────────────────────────────────────────────────

View File

@@ -92,7 +92,8 @@ func advSubstituteFlavor(template string, vars map[string]string) string {
func renderAdvCharacterSheet(char *AdventureCharacter, equip map[EquipmentSlot]*AdvEquipment, items []AdvItem, treasures []AdvTreasureBonus, balance float64) string { func renderAdvCharacterSheet(char *AdventureCharacter, equip map[EquipmentSlot]*AdvEquipment, items []AdvItem, treasures []AdvTreasureBonus, balance float64) string {
var sb strings.Builder var sb strings.Builder
sb.WriteString(fmt.Sprintf("⚔️ **%s's Adventurer**\n\n", char.DisplayName)) displayName, _ := loadDisplayName(char.UserID)
sb.WriteString(fmt.Sprintf("⚔️ **%s's Adventurer**\n\n", displayName))
// Stats // Stats
sb.WriteString("📊 Stats:\n") sb.WriteString("📊 Stats:\n")
@@ -275,8 +276,8 @@ func renderRivalNudge(char *AdventureCharacter) string {
hours = 1 // floor — "<1h" feels worse than "1h" hours = 1 // floor — "<1h" feels worse than "1h"
} }
rivalName := string(c.ChallengerID) rivalName := string(c.ChallengerID)
if rc, err := loadAdvCharacter(c.ChallengerID); err == nil && rc != nil && rc.DisplayName != "" { if name, err := loadDisplayName(c.ChallengerID); err == nil && name != "" {
rivalName = rc.DisplayName rivalName = name
} }
return fmt.Sprintf("⚔️ **Rival challenge open** — %s, round %d/3, €%d on the line · expires in %dh · reply **rock**, **paper**, or **scissors**", return fmt.Sprintf("⚔️ **Rival challenge open** — %s, round %d/3, €%d on the line · expires in %dh · reply **rock**, **paper**, or **scissors**",
rivalName, c.Round, c.Stake, hours) rivalName, c.Round, c.Stake, hours)
@@ -294,8 +295,9 @@ func renderAdvMorningDM(char *AdventureCharacter, equip map[EquipmentSlot]*AdvEq
// Pick a morning greeting // Pick a morning greeting
greeting, _ := advPickFlavor(MorningDM, char.UserID, "morning_dm") greeting, _ := advPickFlavor(MorningDM, char.UserID, "morning_dm")
displayName, _ := loadDisplayName(char.UserID)
vars := map[string]string{ vars := map[string]string{
"{name}": char.DisplayName, "{name}": displayName,
"{character_sheet}": fmt.Sprintf( "{character_sheet}": fmt.Sprintf(
" ⚔️ Combat Lv.%d ⛏️ Mining Lv.%d 🌿 Foraging Lv.%d 🎣 Fishing Lv.%d\n 💰 €%.0f", " ⚔️ Combat Lv.%d ⛏️ Mining Lv.%d 🌿 Foraging Lv.%d 🎣 Fishing Lv.%d\n 💰 €%.0f",
char.CombatLevel, char.MiningSkill, char.ForagingSkill, char.FishingSkill, balance), char.CombatLevel, char.MiningSkill, char.ForagingSkill, char.FishingSkill, balance),
@@ -503,8 +505,9 @@ func renderAdvDeathStatusDM(char *AdventureCharacter) string {
func renderAdvRespawnDM(char *AdventureCharacter) string { func renderAdvRespawnDM(char *AdventureCharacter) string {
text, _ := advPickFlavor(RespawnDM, char.UserID, "respawn_dm") text, _ := advPickFlavor(RespawnDM, char.UserID, "respawn_dm")
displayName, _ := loadDisplayName(char.UserID)
return advSubstituteFlavor(text, map[string]string{ return advSubstituteFlavor(text, map[string]string{
"{name}": char.DisplayName, "{name}": displayName,
}) })
} }
@@ -664,8 +667,9 @@ func masteryBar(value, total int) string {
func renderAdvIdleShameDM(char *AdventureCharacter) string { func renderAdvIdleShameDM(char *AdventureCharacter) string {
text, _ := advPickFlavor(IdleShameDM, char.UserID, "idle_shame") text, _ := advPickFlavor(IdleShameDM, char.UserID, "idle_shame")
displayName, _ := loadDisplayName(char.UserID)
return advSubstituteFlavor(text, map[string]string{ return advSubstituteFlavor(text, map[string]string{
"{name}": char.DisplayName, "{name}": displayName,
}) })
} }
@@ -673,8 +677,9 @@ func renderAdvIdleShameDM(char *AdventureCharacter) string {
func renderAdvOnboardingDM(char *AdventureCharacter) string { func renderAdvOnboardingDM(char *AdventureCharacter) string {
text, _ := advPickFlavor(OnboardingDM, char.UserID, "onboarding") text, _ := advPickFlavor(OnboardingDM, char.UserID, "onboarding")
displayName, _ := loadDisplayName(char.UserID)
return advSubstituteFlavor(text, map[string]string{ return advSubstituteFlavor(text, map[string]string{
"{name}": char.DisplayName, "{name}": displayName,
}) })
} }
@@ -890,8 +895,9 @@ func renderAdvLeaderboard(chars []AdventureCharacter) string {
var entries []entry var entries []entry
for _, c := range chars { for _, c := range chars {
score := (c.CombatLevel + c.MiningSkill + c.ForagingSkill + c.FishingSkill) * 10 score := (c.CombatLevel + c.MiningSkill + c.ForagingSkill + c.FishingSkill) * 10
name, _ := loadDisplayName(c.UserID)
entries = append(entries, entry{ entries = append(entries, entry{
Name: c.DisplayName, Name: name,
Score: score, Score: score,
Levels: fmt.Sprintf("⚔️%d ⛏️%d 🌿%d 🎣%d", c.CombatLevel, c.MiningSkill, c.ForagingSkill, c.FishingSkill), Levels: fmt.Sprintf("⚔️%d ⛏️%d 🌿%d 🎣%d", c.CombatLevel, c.MiningSkill, c.ForagingSkill, c.FishingSkill),
Streak: c.CurrentStreak, Streak: c.CurrentStreak,

View File

@@ -83,7 +83,8 @@ func (p *AdventurePlugin) robbieVisitAll() {
time.Sleep(time.Duration(1000+rand.IntN(2000)) * time.Millisecond) time.Sleep(time.Duration(1000+rand.IntN(2000)) * time.Millisecond)
} }
p.robbieVisitPlayer(char.UserID, char.DisplayName) name, _ := loadDisplayName(char.UserID)
p.robbieVisitPlayer(char.UserID, name)
} }
} }

View File

@@ -232,8 +232,9 @@ func (p *AdventurePlugin) postDailySummary() {
// Build player summaries // Build player summaries
var players []AdvPlayerDaySummary var players []AdvPlayerDaySummary
for _, c := range chars { for _, c := range chars {
dispName, _ := loadDisplayName(c.UserID)
ps := AdvPlayerDaySummary{ ps := AdvPlayerDaySummary{
DisplayName: c.DisplayName, DisplayName: dispName,
CombatLevel: c.CombatLevel, CombatLevel: c.CombatLevel,
MiningSkill: c.MiningSkill, MiningSkill: c.MiningSkill,
ForagingSkill: c.ForagingSkill, ForagingSkill: c.ForagingSkill,

View File

@@ -101,8 +101,9 @@ func (p *AdventurePlugin) runDungeonCombat(
ability = &MonsterAbility{Name: "Stone Skin", Phase: "opening", ProcChance: 0.20, Effect: "armor_break"} ability = &MonsterAbility{Name: "Stone Skin", Phase: "opening", ProcChance: 0.20, Effect: "armor_break"}
} }
displayName, _ := loadDisplayName(userID)
player := Combatant{ player := Combatant{
Name: char.DisplayName, Name: displayName,
Stats: playerStats, Stats: playerStats,
Mods: playerMods, Mods: playerMods,
IsPlayer: true, IsPlayer: true,

View File

@@ -378,8 +378,8 @@ func (p *AdventurePlugin) tryPatrolEncounter(
// Phases: forward-simulating engine play-by-play. // Phases: forward-simulating engine play-by-play.
playerName := "You" playerName := "You"
if char, _ := loadAdvCharacter(userID); char != nil && char.DisplayName != "" { if name, _ := loadDisplayName(userID); name != "" {
playerName = char.DisplayName playerName = name
} }
phases = RenderCombatLog(result, playerName, monster.Name) phases = RenderCombatLog(result, playerName, monster.Name)

View File

@@ -568,8 +568,8 @@ func (p *AdventurePlugin) resolveCombatRoom(userID id.UserID, run *DungeonRun, z
// Phases: forward-simulating engine play-by-play. Use the player's // Phases: forward-simulating engine play-by-play. Use the player's
// display name when available so narrative lines read naturally. // display name when available so narrative lines read naturally.
playerName := "You" playerName := "You"
if char, _ := loadAdvCharacter(userID); char != nil && char.DisplayName != "" { if name, _ := loadDisplayName(userID); name != "" {
playerName = char.DisplayName playerName = name
} }
phases = RenderCombatLog(result, playerName, monster.Name) phases = RenderCombatLog(result, playerName, monster.Name)
@@ -645,8 +645,8 @@ func (p *AdventurePlugin) resolveBossRoom(userID id.UserID, run *DungeonRun, zon
intro = fmt.Sprintf("👑 **Boss — %s** (HP %d, AC %d)", monster.Name, monster.HP, monster.AC) intro = fmt.Sprintf("👑 **Boss — %s** (HP %d, AC %d)", monster.Name, monster.HP, monster.AC)
playerName := "You" playerName := "You"
if char, _ := loadAdvCharacter(userID); char != nil && char.DisplayName != "" { if name, _ := loadDisplayName(userID); name != "" {
playerName = char.DisplayName playerName = name
} }
phases = RenderCombatLog(result, playerName, monster.Name) phases = RenderCombatLog(result, playerName, monster.Name)

View File

@@ -164,8 +164,9 @@ func (p *AdventurePlugin) runZoneCombat(
} }
applyPendingCast(userID, dndChar, &playerStats, &playerMods, &enemyStats) applyPendingCast(userID, dndChar, &playerStats, &playerMods, &enemyStats)
displayName, _ := loadDisplayName(userID)
player := Combatant{ player := Combatant{
Name: char.DisplayName, Name: displayName,
Stats: playerStats, Stats: playerStats,
Mods: playerMods, Mods: playerMods,
IsPlayer: true, IsPlayer: true,