Repository navigation
Conversation
IFriendServer.GetOnlineServerIdAsync returns the game server on which a character is online. Invisible characters appear offline, like they do for other players. The friend server already tracks the online state of all characters, so this works for the all-in-one and the distributed deployment. It's needed for the /who command of the Discord bot. Refs #1100 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
The periodic task plugins (mini games, invasions, happy hour, ...) now provide their next start time according to their timetable, in the time zone of the server. UpcomingEvents collects the ones of the active plugins which start within a time span, e.g. for the /events command of the Discord bot. Refs #1100 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
IPlayerContext.GetCharacterRankingAsync returns the best characters, ordered by resets, master level, level and experience. Characters of bots, templates and game masters aren't ranked. GetCharacterSummaryAsync returns the same summary (class, level, master level, resets) for one character. The attribute definitions of the levels are passed in, because they are defined by the game logic. The Entity Framework queries are built by separate methods, so that tests can check that they translate to SQL. Refs #1100 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
…ands The new DiscordBot (based on Discord.Net) runs as manageable server: * Its presence shows the number of online players, and it keeps a message with the status of the game servers up to date in a configured channel. It finds its message again after a restart, so nothing has to be stored. * It answers the slash commands /online, /who, /guild, /events and /rank, in the configured language. They only show information. * It can post the game events into channels, instead of webhooks. It only needs the Guilds intent, because it reacts on slash commands instead of reading messages. The commands and the status are formatted by separate classes, which get their data from IDiscordGameDataProvider, so that they can be tested without Discord. DiscordGameDataProvider implements it with the interfaces of the servers and the database, so it works in-process as well as with the servers in separate processes. The queue of the webhooks became the base class DiscordMessageQueue, which is also used to post through the bot. The settings are now DiscordSettings with a Bot section, and DiscordWebhookNotifier is now DiscordNotifier. Refs #1100 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
* The Discord project got AddDiscord, which adds the bot when a token is configured, and the notifier when webhooks or bot channels are configured. Both hosts use it. * The all-in-one server lists the bot with the other servers, e.g. in the admin panel. * The new Dapr host Discord.Host subscribes to the game events of the game servers and runs the bot and the notifier for the distributed deployment. It's part of the docker-compose file with the profile "discord" and of the published Docker images. * The bot stops when Discord rejects its token, instead of trying again and again, and it can be disposed more than once. Refs #1100 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
The Discord page now describes how to create and invite the bot, its slash commands, the status message, the channels into which it posts the notifications, and all settings. The page of the distributed deployment describes how to start its Discord container. Refs #1100 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
Deploying openmudocs with
|
| Latest commit: |
a404a0f
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://34ebfc45.openmudocs.pages.dev |
| Branch Preview URL: | https://claude-sweet-cray-w8crms-m2.openmudocs.pages.dev |
sven-n
left a comment
There was a problem hiding this comment.
Part of a review pass over the whole Discord stack.
The lifecycle handling in DiscordBot is careful — the _lifecycleLock around start/shutdown, the idempotent DisposeAsync for the singleton-plus-hosted-service case, stopping the client on an Unauthorized from the log callback so an invalid token doesn't reconnect in a loop, and deferring every interaction before touching the database. I checked the Task.Run in OnLogAsync for a deadlock against StartAsync holding the lock and it's fine, since client.StartAsync returns before the gateway connects.
Three comments inline. The status-message one is the only behavioural issue; the other two are cost and conventions.
Two smaller things not worth their own threads:
RegisterCommandsAsyncfalls back to global registration when_settings.GuildIdis set butclient.GetGuild(guildId)returns null at Ready time. The operator gets commands that take up to an hour to appear and no indication that their configured guild id was the problem — a warning in that branch would save some confusion./whomatches the character name exactly (character.Name == characterName). That's consistent with the rest of the codebase (AccountRepository, the letter receiver lookup), so I'm not asking for a change — but this is a name typed by hand in a Discord client rather than sent by the game client, so case-insensitive matching would be kinder.
Generated by Claude Code
# Conflicts: # src/Discord/DiscordWebhookSender.cs # src/Startup/DiscordServiceCollectionExtensions.cs
…cache the ranking The bot only edits its own status message, identified by the status title, so game events in the same channel aren't overwritten; it warns when the status channel is also configured for events. The ranking for /rank is cached for two minutes, because every member of the Discord server can request it. The bot warns when it isn't on the configured Discord server. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
Implements milestone M2 of the Discord integration epic (#1097): the Discord bot, in both deployments.
Closes #1100
Changes
Queries the bot needs (
9f8af3e3,9f642ef2,b3f5339f)IFriendServer.GetOnlineServerIdAsync: the friend server already tracks the online state of all characters. Invisible characters appear offline, like they do in the game. Dapr glue:FriendServerclient +FriendServerControllerendpoint.game-server, so a request to one specific game server isn't possible in the distributed deployment.IScheduledPlugIn/PeriodicTaskConfiguration.GetNextStartUtc: the periodic task plugins (mini games, invasions, happy hour, …) provide their next start, in the server's time zone.UpcomingEvents.Getcollects the active ones within a time span.IPlayerContext.GetCharacterRankingAsync/GetCharacterSummaryAsync(EF + in-memory): ordered by resets, master level, level, experience; bots, templates and game masters aren't ranked. The level attribute definitions are passed in, because they're defined by the game logic.The bot (
4815e5bc)DiscordBot(Discord.Net 3.20.2) is anIManageableServer:/online,/who,/guild,/events,/rank. They're read-only and answered in the configured language. The answer is deferred, because the data can come from the database.Guildsintent; no privileged intent, because it doesn't read messages.DiscordCommands) and status (DiscordStatusFormatter) are independent of Discord.Net. They get their data fromIDiscordGameDataProvider, so they're tested without Discord.DiscordGameDataProviderimplements it with the interfaces of the servers and the database, so it works in-process and distributed.DiscordMessageQueue;DiscordBotChannelSenderuses it to post through the bot.DiscordWebhookSettings→DiscordSettings(with aBotsection),DiscordWebhookNotifier→DiscordNotifier.ServerType.Discordappended to the enum.Hosting (
5bb083d8)AddDiscord(in the Discord project) adds the bot when a token is configured, and the notifier when webhooks or bot channels are configured. Both hosts use it.Discord.Host:GameEventAsynctopic and runs the bot and the notifier, so M1's notifications now work in the distributed deployment, too;docker-compose.ymlbehind the profilediscord;openmu-discord.Docs (
d2921169)Notes for review
munique/openmu-discordmust exist before the next release, otherwise that matrix job fails. The other images aren't affected (fail-fast: false)./guildfor a guild without online members loads its container in the guild server, which stays loaded until a member logs in and out again. The castle siege lookups behave the same way. It's bounded by the number of guilds.Testing
dotnet test tests/MUnique.OpenMU.Tests -p:ci=true: all 1678 tests pass (31 new):Discord.Host,Startup,CentralServer.Hostbuild without errors or new warnings.🤖 Generated with Claude Code
https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
Generated by Claude Code