diff --git a/docs/configuration.md b/docs/configuration.md index e74dc50..a1f0bc3 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -151,6 +151,8 @@ claim the same day. The default reward is 25 XP, added to the Duck Hunt progression profile for the channel where it is claimed. Daily bonuses require Duck Hunt to be enabled globally and for that channel. Claims are persisted in the configured database, so restarting GoBot does not reset the daily limit. +Successful consecutive UTC-day claims also report a per-user streak; missing a +day resets the streak to one. The optional `welcome` plugin listens for users joining a channel. It applies the configured probability to each join and then enforces a per-channel diff --git a/docs/plugins.md b/docs/plugins.md index 7dc45a0..b941cdf 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -793,8 +793,11 @@ project-- - tell queues a message and delivers it when the addressed nickname next speaks. - karma tracks case-insensitive thing++ and thing-- changes. - thing++ and thing-- also work as standalone ordinary chat messages. GoBot - confirms each update in one compact line, such as `karma updated: thing=+2`; - command messages are not treated as karma changes. + confirms each update in one compact line, such as + `Karma boost! thing +1 (total +2)`; command messages are not treated as + karma changes. +- Positive karma milestones at +10, +25, +50, and +100 add a highlighted + trophy notice when the total crosses the threshold. - dice accepts NdN notation or a single number such as !roll 20, meaning 1d20. It allows up to 100 dice and 10,000 sides per die and uses secure random values. diff --git a/plugins/karma.go b/plugins/karma.go index 3418004..8708d70 100644 --- a/plugins/karma.go +++ b/plugins/karma.go @@ -62,6 +62,8 @@ type karmaUpdate struct { value int } +var karmaMilestones = []int{10, 25, 50, 100} + func (p *Karma) applyTextChanges(text string) []karmaUpdate { if p.db == nil || p.rx == nil { return nil @@ -105,13 +107,39 @@ func formatKarmaUpdates(updates []karmaUpdate) string { } details = append(details, fmt.Sprintf("%s %+d (total %+d)", update.key, update.delta, update.value)) } + message := "" if positive { - return ircColor(ircGreen, "Karma boost! "+strings.Join(details, ", ")+" ✨🎯🌟💫") + message = ircColor(ircGreen, "Karma boost! "+strings.Join(details, ", ")+" ✨🎯🌟💫") + } else if negative { + message = ircColor(ircRed, "Karma dip! "+strings.Join(details, ", ")+" 📉🌀💥😬") + } else { + message = ircColor(ircCyan, "Karma update! "+strings.Join(details, ", ")+" ✨📊🔄🌟") + } + + milestones := make([]string, 0) + for _, update := range updates { + if milestone := crossedKarmaMilestone(update); milestone > 0 { + milestones = append(milestones, fmt.Sprintf("%s has reached %+d karma! 🏆", update.key, milestone)) + } } - if negative { - return ircColor(ircRed, "Karma dip! "+strings.Join(details, ", ")+" 📉🌀💥😬") + if len(milestones) > 0 { + message += " " + ircColor(ircTan, strings.Join(milestones, " ")) + } + return message +} + +func crossedKarmaMilestone(update karmaUpdate) int { + if update.delta <= 0 || update.value <= 0 { + return 0 + } + previous := update.value - update.delta + reached := 0 + for _, milestone := range karmaMilestones { + if previous < milestone && update.value >= milestone { + reached = milestone + } } - return ircColor(ircCyan, "Karma update! "+strings.Join(details, ", ")+" ✨📊🔄🌟") + return reached } func isKarmaWordByte(value byte) bool { diff --git a/plugins/karma_test.go b/plugins/karma_test.go index 2942982..931bcc4 100644 --- a/plugins/karma_test.go +++ b/plugins/karma_test.go @@ -40,6 +40,23 @@ func TestKarmaUpdateMessageIsColorfulAndCompact(t *testing.T) { } } +func TestKarmaMilestoneDecoration(t *testing.T) { + message := formatKarmaUpdates([]karmaUpdate{{key: "project", delta: 1, value: 25}}) + if !strings.Contains(message, "project has reached +25 karma! 🏆") { + t.Fatalf("missing milestone notice: %q", message) + } + if !strings.Contains(message, ircTan) { + t.Fatalf("expected milestone color formatting: %q", message) + } + + if message := formatKarmaUpdates([]karmaUpdate{{key: "project", delta: 1, value: 26}}); strings.Contains(message, "has reached") { + t.Fatalf("milestone repeated without crossing a threshold: %q", message) + } + if message := formatKarmaUpdates([]karmaUpdate{{key: "project", delta: -1, value: -25}}); strings.Contains(message, "has reached") { + t.Fatalf("negative karma unexpectedly received a positive milestone: %q", message) + } +} + func TestKarmaChangesPersist(t *testing.T) { db, err := storage.Open(filepath.Join(t.TempDir(), "karma.db")) if err != nil {