Skip to content

feat(discord): GM commands, direct messages and scheduled events (M6) - #1162

Open
sven-n wants to merge 7 commits into
claude/sweet-cray-w8crms-m5from
claude/sweet-cray-w8crms-m6
Open

sven-n wants to merge 7 commits into
claude/sweet-cray-w8crms-m5from
claude/sweet-cray-w8crms-m6

Conversation

@sven-n

@sven-n sven-n commented Oct 9, 2026

Copy link
Copy Markdown
Member

Milestone M6 of the Discord integration (#1104). Stacked on #1161 (M5).

What

  • GM slash commands (M6.1):
    • Commands:
      • /announce <text> calls IGameServer.SendGlobalMessageAsync (golden notice) on all game servers.
      • /kick <character> calls DisconnectPlayerAsync and /ban <character> calls BanPlayerAsync. Each goes to the game servers until one succeeds; the Dapr clients broadcast.
    • Access: allowed only with the layout role GM on the hosted Discord and a linked account whose selected character has CharacterStatus.GameMaster. This uses the new light query IPlayerContext.GetCharacterStatusAsync.
    • Audit: every use is logged and posted into the new notification category StaffAlerts, routed to #staff-alerts by the layout.
  • Direct messages (M6.2): opt-in per type, stored as AccountNotificationTypes flags on AccountExternalLink, with a migration and compiled models. Toggled with /notify type enabled in Discord or /discord notify login|letter|friend in game. New game events are published directly by the game:
    • AccountLoginBlockedEvent: login with the correct password while the account is already logged in. The account is resolved with the new GetAccountIdByLoginNameAsync.
    • LetterReceivedEvent: published by LetterSendAction.
    • PlayerEnteredGameEvent: published by GameServerContext. Friends with the opt-in are notified unless the player is invisible, checked with IFriendServer.GetOnlineServerIdAsync.
    • Sending: DMs are sent in the background, so the game never waits for the database or Discord.
  • Discord Scheduled Events (M6.3):
    • CastleSiegeStateChangedEvent gets the optional NextBattleStartUtc / NextBattleEndUtc, from the new CastleSiegeSchedule.GetNextPeriod.
    • With Bot__CreateScheduledEvents=true the bot creates or updates an external scheduled event "Castle Siege" on the hosted Discord. It needs the Create Events permission.
  • Docs: sections for GM commands, direct messages and scheduled events, plus a new setting.
  • Fix in M5 (feat(discord): chat bridge for guild, alliance and world chat (M5) #1161): the M5 docs had lost the ## Settings heading. It's restored on M5 and merged forward.

Notes

  • Only castle siege becomes a scheduled event. Blood Castle and the like run every few hours and would flood the event list.
  • The new game events are published directly by the game, not through the optional Game Event Publisher plugin, because they are personal notifications that need the user's opt-in anyway.

Tests

  • DiscordGameMasterCommandsTest: role and GM checks, announcing to all servers, kick/ban routing, staff alert text.
  • DiscordDirectMessagesTest: opt-in, letter, blocked login, friend online and invisible friend.
  • CastleSiegeNextBattleTest: next battle this week and next week, unscheduled state.
  • /discord notify toggling, and serialization of the new events.
  • Full solution test run is green.

Not tested against a real Discord bot or a real PostgreSQL database.

🤖 Generated with Claude Code

https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ


Generated by Claude Code

claude added 4 commits October 9, 2026 08:34
…tle siege

The game publishes blocked logins, received letters and characters which
entered the game, and the castle siege events tell when the next battle
takes place. /discord notify turns direct messages in Discord on or off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
…d events

/announce, /kick and /ban are allowed for users with the GM role and a linked
game master character, and every use is posted to the staff alerts channel.
Linked users opt in to direct messages with /notify. The bot can create a
scheduled event for the next castle siege.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
…s and scheduled events

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Deploying openmudocs with  Cloudflare Pages  Cloudflare Pages

Latest commit: e3e0875
Status: ✅  Deploy successful!
Preview URL: https://878ec86d.openmudocs.pages.dev
Branch Preview URL: https://claude-sweet-cray-w8crms-m6.openmudocs.pages.dev

View logs

@sven-n sven-n left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Part of a review pass over the whole Discord stack. This PR has the most privileged surface in the set, so the authorisation path got the closest look.

The GM gate is correctly layered and I couldn't find a way through it: /announce, /kick and /ban each require both the Discord GM role and that the linked account's selected character has CharacterStatus.GameMaster, where the selected character is verified to belong to that account by M4's SelectCharacterAsync. Every use is logged and mirrored to the staff channel, and the answers are ephemeral. The hasGameMasterRole parameter being passed in rather than determined inside the command class also keeps it testable, which DiscordGameMasterCommandsTest takes advantage of.

Three comments inline. The friend-notification one is the only one I'd call important, and it's throughput rather than a bug.

One smaller observation not worth its own thread: AccountLoginBlockedEvent is published only from the already-logged-in paths in LoginAction, not on a failed password. That makes the "login attempt on your account" DM narrower than its name suggests — useful as-is (it tells a player someone knows their account name and is trying it while they're online), but someone extending this later may well read the event name as covering all blocked logins.


Generated by Claude Code

Comment thread src/Discord/DiscordDirectMessages.cs Outdated
Comment thread src/Discord/DiscordGameMasterCommands.cs Outdated
Comment thread src/Discord/DiscordBot.Extras.cs
…rms-m6

The migration AddAccountNotifications is generated again, so that it follows
the other migrations. The next castle siege is calculated in the time zone
of the server, which master added to the schedule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
- The friends who get a direct message when a character enters the game
  are determined with two queries instead of several per friend, and with
  none when the character has no friends.
- /announce is limited to 200 characters, a failing game server doesn't
  stop the announcement on the others, and the answer tells on how many
  game servers it was shown. A failing game server doesn't stop /kick and
  /ban either.
- The remarks explain that the GM role is only a filter, and that the game
  master status of the character is the actual check.
- A failed command gets an answer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants