Files
Vantz Stockwell 651a35d4be
All checks were successful
CI / backend-types (push) Successful in 10s
CI / frontend-build (push) Successful in 15s
CI / agent-tests (push) Successful in 39s
CI / integration (push) Successful in 22s
docs(reference): import Dune: Awakening server-manager references
Phase 2 references for the host-agent Dune adapter, moved out of volatile /tmp
into docs/reference-repos/ (per Commander). Three upstream projects, .git +
node_modules + compiled binaries stripped (16MB source). Nested AI-instruction
files (.claude/, CLAUDE.md) removed so they don't pollute Corrosion sessions.

- icehunter/    dune-admin (Go+React) — 4 control planes; SETUP_DOCKER.md is the
                closest analog to our agent's Dune docker control plane (compose
                lifecycle, docker logs, RabbitMQ-via-exec, dune Postgres schema)
- adainrivers/  Rust/Tauri desktop — SSH+k8s BattleGroup control, maintenance
                daemon, in-game admin console (Rust idiom reference)
- the4rchangel/ Node web UI replacing battlegroup.bat — matches the Commander's
                Hyper-V self-host path + game-config schema

See docs/reference-repos/README.md for the full index + how we use each.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 21:08:05 -04:00

71 lines
2.8 KiB
Go

package main
import (
"context"
"log"
"strings"
)
// ── Message of the Day (#163/#167/#135) ─────────────────────────────────────
// A configurable in-game message whispered to a player every time they join the
// server (a per-session trigger, distinct from the welcome package's once-per-
// version grant message). Join detection is the presenceTracker diffing the
// online set across scanner ticks; the send reuses the proven GM-whisper path.
// motdDefaultPlayerName is substituted for {player} when a joining account has
// no resolvable character name, so the message is never malformed.
const motdDefaultPlayerName = "traveler"
// renderMOTD substitutes message placeholders with the joining player's details.
// v1 supports {player} (the character name). Kept pure so it is trivially
// testable and free of side effects.
func renderMOTD(template string, acc welcomeAccount) string {
name := strings.TrimSpace(acc.CharacterName)
if name == "" {
name = motdDefaultPlayerName
}
return strings.ReplaceAll(template, "{player}", name)
}
// motdWhisper is one resolved message to send: recipient account, sender
// identity (blank → seeded GM persona), and the rendered text.
type motdWhisper struct {
accountID int64
sourcePlayer string
message string
}
// motdWhispersForJoins builds the whispers to send for a set of join events
// under the given MOTD config. Returns nil when MOTD is disabled or the message
// is blank, so the caller never sends an empty message. Pure (no side effects).
func motdWhispersForJoins(joins []welcomeAccount, enabled bool, message, sourcePlayer string) []motdWhisper {
if !enabled || strings.TrimSpace(message) == "" {
return nil
}
out := make([]motdWhisper, 0, len(joins))
for _, acc := range joins {
out = append(out, motdWhisper{
accountID: acc.AccountID,
sourcePlayer: sourcePlayer,
message: renderMOTD(message, acc),
})
}
return out
}
// welcomePresence is the join-detection state for the MOTD feature. Touched only
// by the single welcome-scanner goroutine, so it needs no synchronisation.
var welcomePresence = newPresenceTracker()
// runMOTDOnJoin observes the current online set, then whispers the configured
// MOTD to each newly-joined player. Called from the scanner tick with the
// online snapshot already fetched (shared with the package scan).
func runMOTDOnJoin(ctx context.Context, rt welcomePackageRuntime, online []welcomeAccount) {
joins := welcomePresence.observe(online)
for _, m := range motdWhispersForJoins(joins, rt.motdEnabled, rt.motdMessage, rt.motdSourcePlayer) {
if err := sendWelcomeWhisper(ctx, m.accountID, m.sourcePlayer, m.message); err != nil {
log.Printf("motd: whisper to account %d failed: %v", m.accountID, err)
}
}
}