feat(chat): the chat server handles guilds

Connects GuildManager to the chat server (ChatGuilds): the client's GUILD_INVITE, GUILD_INVITE_RESPONSE, GUILD_LEAVE
and GUILD_GET_ALL (routed through its world), the worlds' GUILD_CREATE, GUILD_KICK, GUILD_SET_RANK and GUILD_DISBAND,
guild chat (GENERAL_CHAT_MESSAGE in channel 10, sent to the online members as channel 10 private chat, which the
client's guild tab shows, and logged as "guild"), guildmates told when a member logs in, out or changes worlds, and a
member's pending invite dropped when they log off. Client packets go through the member's world as WorldRoutePacket;
GUILD_GET_STATUS goes to the world itself.

Guild names: the chat filter's deny list refuses a name (dChatFilter::HasDenyList, since without a deny list the deny
check refuses everything); a name the allow list doesn't cover waits for moderation, as pet names do.

The dashboard's new player action GUILD_CHANGED goes from master to the chat server only (answered 1 when chat is
connected), which catches online members up. chatconfig.ini: guild_max_members (100) and guild_invite_timeout (600
seconds).

Check in game: nothing yet on its own (the world side makes guilds reachable). Server log: the chat server starts and
logs no unhandled guild packets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-29 05:56:57 -05:00
parent e49c22374b
commit d1eec7dacb
12 changed files with 155 additions and 0 deletions

View File

@@ -941,6 +941,16 @@ namespace {
pending.action = request.action;
pending.deadline = std::chrono::steady_clock::now() + PLAYER_ACTION_TIMEOUT;
// Guilds belong to the chat server: it tells the players (docs/Guilds.md)
if (request.action == ePlayerAction::GUILD_CHANGED) {
const bool chatConnected = chatServerMasterPeerSysAddr != UNASSIGNED_SYSTEM_ADDRESS;
if (chatConnected) MasterPackets::SendTo(chatServerMasterPeerSysAddr, request);
pending.affected = chatConnected ? 1 : 0;
g_PendingPlayerActions[request.requestId] = std::move(pending);
FinishPlayerAction(request.requestId, false);
return;
}
for (const auto& instance : Game::im->GetInstances()) {
if (!instance || !instance->GetIsReady() || instance->GetIsShuttingDown()) continue;
MasterPackets::SendTo(instance->GetSysAddr(), request);