Repository navigation
Conversation
Groundwork for #700 that needs no server support yet: the display path the ExternalChatMessage packet handler will call (UI::Chat::External::Show). - The sender is shown as "[Discord] <name>", with the full Discord name (up to 32 characters) instead of the 10-byte character name. - The message takes its scope's chat type and colours (guild, alliance, world) and is also listed in a new Discord view of the chat log. - Discord users can't be whispered: no whisper allow-list entry, no speech balloon, and right-clicking their line doesn't start a whisper. - F2 cycles all -> whispers -> Discord -> all; the Discord view is only offered once a message from Discord arrived, so nothing changes on servers without a bridge. The repeated create-and-append blocks of CNewUIChatLogWindow::ProcessAddText are extracted into AppendMessage, and the copy into a second view (an error's origin, now also the Discord view) into GetSecondaryMsgs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012feEciycvL8PGjsezyRFqj
OpenMU's chat bridge (MUnique/OpenMU#1161) sends messages written in Discord through the ordinary guild and alliance chat, as the linked character with an '@' prefix that no character name can contain. Such lines are now shown through UI::Chat::External as messages from Discord: "[Discord] <name>" in the guild or alliance colours, in the Discord view, and not whisperable. This makes the C4 display path work against OpenMU today, without waiting for a dedicated packet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012feEciycvL8PGjsezyRFqj
… claude/discord-presence-mumain-597-l2q09d-server-info # Conflicts: # docs/discord.md
Regenerates the bindings from a build of MUnique.OpenMU.Network.Packets which contains the Discord integration packets (MUnique/OpenMU#1174): DiscordIntegrationInfoRequest, DiscordLinkCodeRequest and DiscordUnlinkRequest. The definitions on OpenMU's master also typed three Castle Siege requests since 0.9.10 (tax type, guild command, gate state); their callers pass the new enums, the bytes on the wire stay the same. OpenMuPacketsVersion points at a local pre-release build until the packets are released; it has to be bumped to the released version before merging. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012feEciycvL8PGjsezyRFqj
Uses the Discord integration messages of OpenMU (MUnique/OpenMU#1174): - C2: after entering the world the client asks for the integration info (GameLogic::Discord::ServerIntegration). The server's Rich Presence application, image keys and invite link win over config.ini, which stays the fallback for servers that don't send them. - C3: on such a server the options window's Discord button opens a Discord dialog: whether the account is linked and which chats are mirrored, with Link / Unlink and Join. Link shows the one-time code, copies it to the clipboard and says to enter /link <code> in Discord. - C4: ExternalChatMessage is shown through UI::Chat::External with the Discord name and its scope, now including the world chat. The messages are read in Network::Discord (DiscordPackets) and dispatched from the F5 group by Network::Discord::HandleMessage. Also brings the formatting of the Castle Siege call sites of the bindings update in line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012feEciycvL8PGjsezyRFqj
…cord Under the guild name: "Guild chat mirrored to Discord" (alliance tab: the alliance chat), from the server's Discord integration info - the notice that what is written there leaves the game. A guild master whose chat isn't mirrored is told to bind it with /guildchat in Discord; the binding itself happens in Discord (MUnique/OpenMU#1161). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012feEciycvL8PGjsezyRFqj
sven-n
left a comment
There was a problem hiding this comment.
Part of a review pass over the whole Discord integration — the four client PRs here and the seven server PRs in MUnique/OpenMU. Reviewing as a draft, so this is early feedback rather than a gate.
I spent most of the time on DiscordPackets.cpp, since untrusted packet parsing is where this PR can actually hurt, and it holds up:
- Every parser length-checks before indexing (
packet.size() < InfoLength,< LinkCodeLength,<= ExternalMessageOffset), and unknown enum values are rejected rather than cast blindly. ReadStringis safe for the awkward case I went looking for — a fixed field filled to its last byte with no NUL. It passes an explicitmaxSourceLengthtoMultiByteToWideChar, so there's no scan past the field; the buffer islength + 1and zero-initialised, andbuffer[length]is set explicitly rather than relying onConvertFromUtf8, which only terminates whenwritten < maxSourceLength.LinkedUserNameis exactly the case that matters: 32 bytes, flush with the end of the 224-byte packet, and a 32-character Discord username is legal. It's handled.- I diffed the offsets against the server definitions in MUnique/OpenMU#1174 field by field —
ExternalChatMessage5/6/7+48/55,DiscordIntegrationInfo4/5/6/7/8+20/28+32/60+32/92+100/192+32,DiscordLinkCode4/5+10/15. No drift. - The server-supplied invite URL is correctly routed through #707's allowlist:
NewUIOptionWindow.cpp:813checksIsInviteUrlbefore showing the button andOpenre-validates. That's the right place to be strict.
Two comments inline on ServerIntegration.cpp, both minor.
One process note: this PR's base is claude/discord-presence-mumain-597-l2q09d-c2-invite (#707) rather than #708's branch, so its diff re-includes all of #708's C4 work — ExternalChat.*, the NewUIChatLogWindow changes and test_external_chat.cpp are byte-identical in both. #708 is an ancestor here, so nothing will conflict; it just means this PR currently shows ~400 lines that belong to #708's review. Retargeting the base onto ...-c4-chat, or landing #708 first, would narrow this diff to the C2–C5 work and make it much easier to read when it comes out of draft.
I have not reviewed DiscordMsgBox.cpp or the ClientLibrary/PacketFunctions wiring in depth — happy to do that when you take it out of draft.
Generated by Claude Code
…discord-presence-mumain-597-l2q09d-c4-chat
Says next to IsBridgedSender that the prefix is a convention of the server (enforced in character names by MUnique/OpenMU#1174), not something the client can verify, and that ExternalChatMessage is the authoritative signal where the server sends it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012feEciycvL8PGjsezyRFqj
…to claude/discord-presence-mumain-597-l2q09d-server-info # Conflicts: # src/source/UI/NewUI/Options/NewUIOptionWindow.cpp
… claude/discord-presence-mumain-597-l2q09d-server-info
- The Rich Presence application id is only taken from the server when it is a Discord snowflake (17 to 20 digits); anything else falls back to config.ini, like the invite link is checked before it's offered. - Asking the server again keeps its previous answer until the new one arrives, so the account dialog doesn't show "not linked" for a round trip. The answer is forgotten on entering the world instead, so a previous server's info doesn't stay when the new server doesn't answer. - Opening the account dialog asks again, so the next opening reflects a link made in Discord meanwhile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012feEciycvL8PGjsezyRFqj
OpenMU hands out a new link code at most every 30 seconds and answers in between with the new DiscordLinkCode result TooSoon (MUnique/OpenMU#1174). The client tells the player to wait a moment instead of claiming that linking isn't available; an unknown result still reads as not available. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012feEciycvL8PGjsezyRFqj
…discord-presence-mumain-597-l2q09d-c4-chat main moved the chat log to UI/HUD/ChatLogWindow (mu::ui::window, drawn with RmlUi), the message types to UI/Chat/MessageType.h and the chat posting to UI::Chat::PostChat. The Discord view and the external messages move along: TYPE_DISCORD_MESSAGE joins MessageType.h, and the RmlUi right-click no longer offers a whisper to a Discord sender. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
… claude/discord-presence-mumain-597-l2q09d-server-info Brings in main through C4. main moved the remaining native windows to RmlUi, so the server integration's UI moves along: - The Discord dialog is shown on the generic menu dialog instead of its own native message box (UI/Dialogs/DiscordMsgBox). - The guild window's Discord notice is a line of its RmlUi model. - The siege commander and tax call sites already use the packet enums on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
…to claude/discord-presence-mumain-597-l2q09d-server-info The Discord button of the RmlUi options window opens the account dialog when the server has the Discord integration, and the invite otherwise, as the native window's button did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01USFZ9xErMB1EwkpfdUHzoZ
Summary
The client side of the Discord milestones that need the server, using the opt-in
F5packets drafted in MUnique/OpenMU#1174.C2: integration info from the server
DiscordIntegrationInfoRequest.GameLogic::Discord::ServerIntegrationkeeps the answer.config.ini.config.inistays the fallback for servers that send none.C3: account link dialog
CDiscordMsgBox, built like the other custom message boxes). It shows whether the account is linked and as whom, and which chats are mirrored.SDL_SetClipboardText), Copy copies it again, and the dialog says to enter/link <code>in Discord. If linking isn't available, a system message says so.C4: messages from Discord
ExternalChatMessagegoes throughUI::Chat::External::Showfrom feat(chat): show chat messages from Discord as such (C4) #708. It now covers the world scope and the sender name (up to 48 bytes).@sender recognition from feat(chat): show chat messages from Discord as such (C4) #708 stays the fallback for servers that don't send the packet.C5: guild window
/guildchatin Discord. OpenMU's M5 binds in Discord itself (/guildchat bind/create), so there is no bind code to show in the game.Code layout: the messages are read in
Network::Discord(DiscordPackets) and dispatched from theF5group byNetwork::Discord::HandleMessage, instead of growingWSclient.cpp.Bindings update (its own commit): regenerated from the new packets. OpenMU master has also typed three Castle Siege requests since 0.9.10:
CastleSiegeTaxTypeCastleSiegeGuildCommandTypecloseState→isOpenTheir three call sites now pass the enums. The bytes on the wire are the same.
Docs:
docs/discord.mdcovers the account dialog, the guild notice, and the server values taking over fromconfig.ini.Verification
linux-x64Debug build against the local packets package: fullctestrun, 457/457 passed. That includes 7 new tests intests/network/test_discord_packets.cpp:config.iniand are forgotten on the next requestMU_ENABLE_DISCORDundefined, the touched files compile.Related issues
C2 #698, C3 #699, C4 #700, C5 #701. These close once this and MUnique/OpenMU#1174 are in.
Checklist
docs/CODING_RULES.md.docs/build/README.md), against a local build of the packets package.🤖 Generated with Claude Code
https://claude.ai/code/session_012feEciycvL8PGjsezyRFqj
Generated by Claude Code