From 89f1aafe0599add8a1612df0e3f754814e091e27 Mon Sep 17 00:00:00 2001 From: AK Date: Thu, 20 Aug 2026 10:59:18 -0700 Subject: [PATCH] fix: consistently color golden duck label --- docs/games.md | 9 +++++---- plugins/duckhunt.go | 8 +++++--- plugins/duckhunt_test.go | 22 +++++++++++++++++++--- plugins/formatting.go | 3 ++- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/docs/games.md b/docs/games.md index 2d2bf65..0cda884 100644 --- a/docs/games.md +++ b/docs/games.md @@ -274,9 +274,10 @@ username hit a duck in the flock for 1 damage! It has 0 HP left. +10 XP The active-duck announcement stays compact for terminal clients: `\_o< QUACK!`. When IRC colors are supported, the duck uses a soft tan approximation, green -head, and yellow bill. The `GOLDEN DUCK` label is reserved for hit, befriending, and -escape messages, where it is shown in yellow; it is not inserted into the duck -ASCII itself. +head, and yellow bill. The exact `GOLDEN DUCK` label is reserved for hit, +befriending, escape, and achievement messages, where it is shown with IRC's +gold/yellow color code; it is not inserted into the duck ASCII itself. Clients +without color support still receive the same readable label. Before the duck appears, GoBot may send one randomly timed, colorized teaser such as a flight trail, quack, or flap. There is at most one teaser per hunt @@ -402,7 +403,7 @@ achievements include Golden Slayer, Golden Companion, Flock Buster, Deadeye Duck, Pond Patroller, Quackstorm, Willow Whisperer, Marsh Marksman, and Golden Arsenal. Long-term achievements include Wing Commander, Legendary Slayer, Pond Peacemaker, Friend of All Feathers, XP Voyager, and Point Hoarder. Golden Duck -phrases in achievement messages are highlighted in yellow. Use +phrases in achievement messages are highlighted in gold. Use `!achievements [nickname]` to view unlocked achievements; `!duckstats` also shows the unlocked/total count. Existing players begin with zero achievements and can earn them without losing any existing progress. diff --git a/plugins/duckhunt.go b/plugins/duckhunt.go index 983b395..9f118c2 100644 --- a/plugins/duckhunt.go +++ b/plugins/duckhunt.go @@ -115,6 +115,8 @@ type duckAchievementEvent struct { BoughtGoldenWeapon bool } +const goldenDuckLabel = "GOLDEN DUCK" + var duckAchievementCatalog = []duckAchievementDefinition{ {Key: "first_shot", Name: "First Shot", Description: "Landed your first hit"}, {Key: "first_quackdown", Name: "First Quackdown", Description: "Killed your first duck"}, @@ -1127,8 +1129,8 @@ func duckAchievementCondition(key string, player duckPlayer, kills, friends uint func formatDuckAchievement(nick string, achievement duckAchievementDefinition) string { description := achievement.Description - if strings.Contains(description, "GOLDEN DUCK") { - description = strings.Replace(description, "GOLDEN DUCK", ircColor(ircYellow, "GOLDEN DUCK"), 1) + if strings.Contains(description, goldenDuckLabel) { + description = strings.Replace(description, goldenDuckLabel, ircColor(ircGold, goldenDuckLabel), 1) } return fmt.Sprintf("%s %s unlocked: %s - %s", ircColor(ircGreen, "[Achievement]"), ircColor(ircCyan, nick), ircColor(ircYellow, achievement.Name), description) } @@ -1898,7 +1900,7 @@ func xpToNextLevel(xp int64) int64 { func duckName(state *duckHuntState) string { if state != nil && state.golden { - return "the " + ircColor(ircYellow, "GOLDEN DUCK") + return "the " + ircColor(ircGold, goldenDuckLabel) } if state != nil && state.flockRemaining > 1 { return ircColor(ircCyan, "a duck in the flock") diff --git a/plugins/duckhunt_test.go b/plugins/duckhunt_test.go index bb8d075..99a4f95 100644 --- a/plugins/duckhunt_test.go +++ b/plugins/duckhunt_test.go @@ -78,7 +78,7 @@ func TestDuckHuntGoldenAchievementColorsOnlyGoldenDuck(t *testing.T) { } } message := formatDuckAchievement("GoBot", golden) - if !strings.Contains(message, "Killed a "+ircColor(ircYellow, "GOLDEN DUCK")) { + if !strings.Contains(message, "Killed a "+ircColor(ircGold, goldenDuckLabel)) { t.Fatalf("achievement message = %q, want only GOLDEN DUCK highlighted", message) } if strings.Contains(message, ircYellow+"Killed a") || strings.Contains(message, ircYellow+"a ") { @@ -390,7 +390,7 @@ func TestDuckHuntAnnouncementKeepsDuckASCIICompact(t *testing.T) { func TestDuckHuntGoldenDuckColorStartsAtLabel(t *testing.T) { got := duckName(&duckHuntState{golden: true}) - want := "the " + ircColor(ircYellow, "GOLDEN DUCK") + want := "the " + ircColor(ircGold, goldenDuckLabel) if got != want { t.Fatalf("golden duck name = %q, want %q", got, want) } @@ -399,6 +399,22 @@ func TestDuckHuntGoldenDuckColorStartsAtLabel(t *testing.T) { } } +func TestDuckHuntGoldenDuckUsesGoldInEveryOutputPath(t *testing.T) { + gold := ircColor(ircGold, goldenDuckLabel) + outputs := []string{ + duckName(&duckHuntState{golden: true}), + formatDuckAchievement("GoBot", duckAchievementDefinition{Description: "Found a GOLDEN DUCK"}), + } + for i := 0; i < 100; i++ { + outputs = append(outputs, randomDuckEscapeForState(&duckHuntState{golden: true})) + } + for _, output := range outputs { + if strings.Contains(output, goldenDuckLabel) && !strings.Contains(output, gold) { + t.Fatalf("golden duck output is not gold-highlighted: %q", output) + } + } +} + func TestDuckHuntSchedulesAfterActivityThreshold(t *testing.T) { plugin := &DuckHunt{} if err := plugin.Init(bot.PluginConfig{ @@ -835,7 +851,7 @@ func TestDuckHuntEscapeIncludesColorAndMotion(t *testing.T) { } goldenEscape := randomDuckEscapeForState(&duckHuntState{golden: true}) - if !strings.Contains(goldenEscape, "the "+ircColor(ircYellow, "GOLDEN DUCK")) { + if !strings.Contains(goldenEscape, "the "+ircColor(ircGold, goldenDuckLabel)) { t.Fatalf("golden escape = %q, want colored GOLDEN DUCK label", goldenEscape) } } diff --git a/plugins/formatting.go b/plugins/formatting.go index 8fa548e..26d48e6 100644 --- a/plugins/formatting.go +++ b/plugins/formatting.go @@ -15,7 +15,8 @@ const ( ircRed = "\x0304" ircTan = "\x0307" // mIRC orange, a practical tan approximation ircCyan = "\x0311" - ircYellow = "\x0308" + ircGold = "\x0308" // mIRC yellow, rendered as gold by common IRC clients + ircYellow = ircGold // keep the existing name for non-golden UI accents ) func ircColor(color, text string) string {