mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
feat(chat): keep activity lobbies of every world and start their instances through master
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
set(DCHATSERVER_SOURCES
|
set(DCHATSERVER_SOURCES
|
||||||
"ChatIgnoreList.cpp"
|
"ChatIgnoreList.cpp"
|
||||||
|
"ChatMatchmaking.cpp"
|
||||||
"ChatPacketHandler.cpp"
|
"ChatPacketHandler.cpp"
|
||||||
"ChatGuilds.cpp"
|
"ChatGuilds.cpp"
|
||||||
"GuildManager.cpp"
|
"GuildManager.cpp"
|
||||||
|
"Matchmaking.cpp"
|
||||||
"PlayerContainer.cpp"
|
"PlayerContainer.cpp"
|
||||||
"TeamContainer.cpp"
|
"TeamContainer.cpp"
|
||||||
)
|
)
|
||||||
|
|||||||
126
dChatServer/ChatMatchmaking.cpp
Normal file
126
dChatServer/ChatMatchmaking.cpp
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
#include "ChatMatchmaking.h"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include "ChatPacketHandler.h"
|
||||||
|
#include "ChatPackets.h"
|
||||||
|
#include "ClientPackets.h"
|
||||||
|
#include "Game.h"
|
||||||
|
#include "GeneralUtils.h"
|
||||||
|
#include "Logger.h"
|
||||||
|
#include "PlayerContainer.h"
|
||||||
|
#include "TeamContainer.h"
|
||||||
|
#include "ZoneInstanceManager.h"
|
||||||
|
#include "dServer.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Matchmaking::Lobbies g_Lobbies;
|
||||||
|
|
||||||
|
void SendUpdates(const std::vector<Matchmaking::Update>& updates) {
|
||||||
|
for (const auto& update : updates) {
|
||||||
|
const auto& player = Game::playerContainer.GetPlayerData(update.to);
|
||||||
|
if (!player) continue;
|
||||||
|
ClientPackets::MatchUpdate msg;
|
||||||
|
msg.target = update.to;
|
||||||
|
msg.data = update.data;
|
||||||
|
msg.type = update.type;
|
||||||
|
ChatPacketHandler::SendRouted(update.to, player.worldServerSysAddr, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The players' worlds send them to the instance master started
|
||||||
|
void SendToInstance(const Matchmaking::Match& match, const bool mythranShift, const uint32_t zoneID, const uint32_t zoneInstance, const uint32_t zoneClone, const std::string& serverIP, const uint16_t serverPort) {
|
||||||
|
std::map<SystemAddress, ChatPackets::MatchTransfer> perWorld;
|
||||||
|
std::vector<LWOOBJID> online;
|
||||||
|
for (const auto playerID : match.players) {
|
||||||
|
const auto& player = Game::playerContainer.GetPlayerData(playerID);
|
||||||
|
if (!player || Game::playerContainer.PlayerBeingRemoved(playerID)) continue;
|
||||||
|
online.push_back(playerID);
|
||||||
|
|
||||||
|
auto& transfer = perWorld[player.worldServerSysAddr];
|
||||||
|
transfer.activityID = match.settings.activityID;
|
||||||
|
transfer.zoneID = LWOZONEID(zoneID, zoneInstance, zoneClone);
|
||||||
|
transfer.serverIP = serverIP;
|
||||||
|
transfer.serverPort = serverPort;
|
||||||
|
transfer.mythranShift = mythranShift;
|
||||||
|
transfer.players.push_back(playerID);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& [world, transfer] : perWorld) {
|
||||||
|
RakNet::BitStream bitStream;
|
||||||
|
transfer.WritePacket(bitStream);
|
||||||
|
Game::server->Send(bitStream, world, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Players who start an activity together are a team, as when a world made the lobby (teams of up to
|
||||||
|
// ChatPackets::CreateTeam::MAX_MEMBERS players; bigger groups never were one)
|
||||||
|
if (online.size() > 1 && online.size() <= ChatPackets::CreateTeam::MAX_MEMBERS) {
|
||||||
|
ChatPackets::CreateTeam team;
|
||||||
|
team.leaderID = online.front();
|
||||||
|
team.members = online;
|
||||||
|
team.zoneID = LWOZONEID(zoneID, zoneInstance, zoneClone);
|
||||||
|
TeamContainer::CreateTeamServer(team, UNASSIGNED_SYSTEM_ADDRESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG("Matchmaking: activity %i starts in zone %u instance %u clone %u (%zu players, %zu worlds)", match.settings.activityID, zoneID, zoneInstance, zoneClone, online.size(), perWorld.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace ChatMatchmaking {
|
||||||
|
Matchmaking::Lobbies& GetLobbies() {
|
||||||
|
return g_Lobbies;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleMatchRequest(const ChatPackets::MatchRequest& request, const SystemAddress& sysAddr) {
|
||||||
|
const auto& player = Game::playerContainer.GetPlayerData(request.playerID);
|
||||||
|
// Only players this chat server knows are in the world that sent it
|
||||||
|
if (!player || player.worldServerSysAddr != sysAddr) return;
|
||||||
|
|
||||||
|
std::vector<Matchmaking::Update> updates;
|
||||||
|
switch (request.type) {
|
||||||
|
case ChatPackets::eMatchRequestType::JOIN: {
|
||||||
|
Matchmaking::ActivitySettings settings;
|
||||||
|
settings.activityID = request.activityID;
|
||||||
|
settings.instanceMapID = request.instanceMapID;
|
||||||
|
settings.minTeams = request.minTeams;
|
||||||
|
settings.maxTeams = request.maxTeams;
|
||||||
|
settings.minTeamSize = request.minTeamSize;
|
||||||
|
settings.maxTeamSize = request.maxTeamSize;
|
||||||
|
settings.waitTime = static_cast<float>(request.waitTime) / 1000.0f;
|
||||||
|
settings.startDelay = static_cast<float>(request.startDelay) / 1000.0f;
|
||||||
|
const auto& name = request.playerName.empty() ? player.playerName : request.playerName;
|
||||||
|
g_Lobbies.Join(request.playerID, name, request.playerChoices, settings, updates);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ChatPackets::eMatchRequestType::READY:
|
||||||
|
g_Lobbies.SetReady(request.playerID, request.value != 0, updates);
|
||||||
|
break;
|
||||||
|
case ChatPackets::eMatchRequestType::LEAVE:
|
||||||
|
g_Lobbies.Leave(request.playerID, updates);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SendUpdates(updates);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerLeftWorld(const LWOOBJID playerID) {
|
||||||
|
std::vector<Matchmaking::Update> updates;
|
||||||
|
if (g_Lobbies.Leave(playerID, updates)) SendUpdates(updates);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Update(const float deltaTime) {
|
||||||
|
if (g_Lobbies.GetLobbies().empty()) return;
|
||||||
|
std::vector<Matchmaking::Update> updates;
|
||||||
|
const auto matches = g_Lobbies.Tick(deltaTime, updates);
|
||||||
|
SendUpdates(updates);
|
||||||
|
for (const auto& match : matches) StartMatch(match);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartMatch(const Matchmaking::Match& match) {
|
||||||
|
// A clone no other instance has: master starts a new instance of the zone for this match alone
|
||||||
|
const auto cloneID = GeneralUtils::GenerateRandomNumber<uint32_t>(1, UINT32_MAX);
|
||||||
|
ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, match.settings.instanceMapID, cloneID, false,
|
||||||
|
[match](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) {
|
||||||
|
SendToInstance(match, mythranShift, zoneID, zoneInstance, zoneClone, serverIP, serverPort);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
34
dChatServer/ChatMatchmaking.h
Normal file
34
dChatServer/ChatMatchmaking.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#ifndef CHATMATCHMAKING_H
|
||||||
|
#define CHATMATCHMAKING_H
|
||||||
|
|
||||||
|
#include "dCommonVars.h"
|
||||||
|
#include "Matchmaking.h"
|
||||||
|
|
||||||
|
struct SystemAddress;
|
||||||
|
|
||||||
|
namespace ChatPackets {
|
||||||
|
struct MatchRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The chat server's activity matchmaking (docs/Matchmaking.md): lobbies across every world. Worlds send the players'
|
||||||
|
* lobby joins, readies and leaves; lobby updates go to the clients through their worlds; when a countdown runs out
|
||||||
|
* master starts one instance of the activity's zone and each player's world is told to send them there.
|
||||||
|
* Runs on the chat server's main thread only.
|
||||||
|
*/
|
||||||
|
namespace ChatMatchmaking {
|
||||||
|
void HandleMatchRequest(const ChatPackets::MatchRequest& request, const SystemAddress& sysAddr);
|
||||||
|
|
||||||
|
// The player left their world (logged out, or loaded into another one): they leave their lobby
|
||||||
|
void PlayerLeftWorld(LWOOBJID playerID);
|
||||||
|
|
||||||
|
// Once a frame: the lobby countdowns
|
||||||
|
void Update(float deltaTime);
|
||||||
|
|
||||||
|
// Starts the instance of a formed match and tells the players' worlds (also used by tests)
|
||||||
|
void StartMatch(const Matchmaking::Match& match);
|
||||||
|
|
||||||
|
Matchmaking::Lobbies& GetLobbies();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CHATMATCHMAKING_H
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
#include "ChatPackets.h"
|
#include "ChatPackets.h"
|
||||||
#include "master/LiveUpdate.h"
|
#include "master/LiveUpdate.h"
|
||||||
#include "MasterPackets.h"
|
#include "MasterPackets.h"
|
||||||
|
#include "ChatMatchmaking.h"
|
||||||
|
|
||||||
#include "Game.h"
|
#include "Game.h"
|
||||||
#include "Server.h"
|
#include "Server.h"
|
||||||
@@ -169,6 +170,10 @@ int main(int argc, char** argv) {
|
|||||||
Profiler::Scope scope("Player container update", Profiler::Phase::ENTITIES);
|
Profiler::Scope scope("Player container update", Profiler::Phase::ENTITIES);
|
||||||
Game::playerContainer.Update(deltaTime);
|
Game::playerContainer.Update(deltaTime);
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
Profiler::Scope scope("Matchmaking", Profiler::Phase::ENTITIES);
|
||||||
|
ChatMatchmaking::Update(deltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
//Check for packets here:
|
//Check for packets here:
|
||||||
//ReceiveFromMaster also handles the master packets if needed; it hands back the ones for us.
|
//ReceiveFromMaster also handles the master packets if needed; it hands back the ones for us.
|
||||||
@@ -283,7 +288,15 @@ namespace {
|
|||||||
handlers.On<GuildKick>(Chat::GUILD_KICK, [](const GuildKick& kick, const SystemAddress&) { ChatGuilds::Get().Kick(kick.playerID, kick.kickedPlayer.GetAsString()); });
|
handlers.On<GuildKick>(Chat::GUILD_KICK, [](const GuildKick& kick, const SystemAddress&) { ChatGuilds::Get().Kick(kick.playerID, kick.kickedPlayer.GetAsString()); });
|
||||||
handlers.On<GuildSetRank>(Chat::GUILD_SET_RANK, [](const GuildSetRank& rank, const SystemAddress&) { ChatGuilds::Get().SetRank(rank.playerID, rank.targetPlayer.GetAsString(), static_cast<eGuildRank>(rank.rank)); });
|
handlers.On<GuildSetRank>(Chat::GUILD_SET_RANK, [](const GuildSetRank& rank, const SystemAddress&) { ChatGuilds::Get().SetRank(rank.playerID, rank.targetPlayer.GetAsString(), static_cast<eGuildRank>(rank.rank)); });
|
||||||
handlers.On<GuildDisband>(Chat::GUILD_DISBAND, [](const GuildDisband& disband, const SystemAddress&) { ChatGuilds::Get().Disband(disband.playerID); });
|
handlers.On<GuildDisband>(Chat::GUILD_DISBAND, [](const GuildDisband& disband, const SystemAddress&) { ChatGuilds::Get().Disband(disband.playerID); });
|
||||||
handlers.On<UnexpectedDisconnect>(Chat::UNEXPECTED_DISCONNECT, [](const UnexpectedDisconnect& notify, const SystemAddress& sysAddr) { Game::playerContainer.ScheduleRemovePlayer(notify, sysAddr); });
|
handlers.On<UnexpectedDisconnect>(Chat::UNEXPECTED_DISCONNECT, [](const UnexpectedDisconnect& notify, const SystemAddress& sysAddr) {
|
||||||
|
// The world they are in lost them: they can't be sent from it to an activity. (A world they already left
|
||||||
|
// can say so after they joined a lobby in their new one.)
|
||||||
|
const auto& player = Game::playerContainer.GetPlayerData(notify.playerID);
|
||||||
|
if (player && player.worldServerSysAddr == sysAddr) ChatMatchmaking::PlayerLeftWorld(notify.playerID);
|
||||||
|
Game::playerContainer.ScheduleRemovePlayer(notify, sysAddr);
|
||||||
|
});
|
||||||
|
// Activity lobbies across every world (docs/Matchmaking.md)
|
||||||
|
handlers.On<MatchRequest>(Chat::MATCH_REQUEST, ChatMatchmaking::HandleMatchRequest);
|
||||||
handlers.On<FindPlayerRequest>(Chat::WHO, ChatPacketHandler::HandleWho);
|
handlers.On<FindPlayerRequest>(Chat::WHO, ChatPacketHandler::HandleWho);
|
||||||
handlers.On<ShowAllRequest>(Chat::SHOW_ALL, ChatPacketHandler::HandleShowAll);
|
handlers.On<ShowAllRequest>(Chat::SHOW_ALL, ChatPacketHandler::HandleShowAll);
|
||||||
handlers.On<AchievementNotify>(Chat::ACHIEVEMENT_NOTIFY, ChatPacketHandler::OnAchievementNotify);
|
handlers.On<AchievementNotify>(Chat::ACHIEVEMENT_NOTIFY, ChatPacketHandler::OnAchievementNotify);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "dConfig.h"
|
#include "dConfig.h"
|
||||||
#include "MessageType/Chat.h"
|
#include "MessageType/Chat.h"
|
||||||
#include "TeamContainer.h"
|
#include "TeamContainer.h"
|
||||||
|
#include "ChatMatchmaking.h"
|
||||||
#include "ChatGuilds.h"
|
#include "ChatGuilds.h"
|
||||||
#include "GuildManager.h"
|
#include "GuildManager.h"
|
||||||
|
|
||||||
@@ -58,6 +59,9 @@ void PlayerContainer::InsertPlayer(const ChatPackets::LoginSessionNotify& notify
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Loaded into a world (a new one when changing zones): a lobby they waited in on their last world is left
|
||||||
|
ChatMatchmaking::PlayerLeftWorld(playerId);
|
||||||
|
|
||||||
auto isLogin = !m_Players.contains(playerId);
|
auto isLogin = !m_Players.contains(playerId);
|
||||||
auto& data = m_Players[playerId];
|
auto& data = m_Players[playerId];
|
||||||
data = PlayerData();
|
data = PlayerData();
|
||||||
|
|||||||
Reference in New Issue
Block a user