mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
feat(world): activity lobbies join, ready and leave through the chat server
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,6 @@
|
|||||||
#include "MissionComponent.h"
|
#include "MissionComponent.h"
|
||||||
#include "Character.h"
|
#include "Character.h"
|
||||||
#include "dZoneManager.h"
|
#include "dZoneManager.h"
|
||||||
#include "ZoneInstanceManager.h"
|
|
||||||
#include "Game.h"
|
#include "Game.h"
|
||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
#include "ClientPackets.h"
|
#include "ClientPackets.h"
|
||||||
@@ -25,7 +24,6 @@
|
|||||||
#include "eMatchUpdate.h"
|
#include "eMatchUpdate.h"
|
||||||
#include "ServiceType.h"
|
#include "ServiceType.h"
|
||||||
#include "MessageType/Chat.h"
|
#include "MessageType/Chat.h"
|
||||||
#include "ObjectIDManager.h"
|
|
||||||
|
|
||||||
#include "CDActivityRewardsTable.h"
|
#include "CDActivityRewardsTable.h"
|
||||||
#include "CDActivitiesTable.h"
|
#include "CDActivitiesTable.h"
|
||||||
@@ -113,196 +111,72 @@ void ActivityComponent::HandleMessageBoxResponse(Entity* player, const std::stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActivityComponent::PlayerJoin(Entity* player) {
|
void ActivityComponent::PlayerJoin(Entity* player, const std::string& playerChoices) {
|
||||||
if (PlayerIsInQueue(player)) return;
|
|
||||||
// If we have a lobby, queue the player and allow others to join, otherwise spin up an instance on the spot
|
// If we have a lobby, queue the player and allow others to join, otherwise spin up an instance on the spot
|
||||||
if (HasLobby()) {
|
if (HasLobby()) {
|
||||||
PlayerJoinLobby(player);
|
PlayerJoinLobby(player, playerChoices);
|
||||||
} else if (!IsPlayedBy(player)) {
|
} else if (!IsPlayedBy(player)) {
|
||||||
NewInstance().AddParticipant(player);
|
NewInstance().AddParticipant(player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActivityComponent::PlayerJoinLobby(Entity* player) {
|
namespace {
|
||||||
|
void SendMatchRequest(const LWOOBJID playerID, const ChatPackets::eMatchRequestType type, const int32_t value) {
|
||||||
|
ChatPackets::MatchRequest request;
|
||||||
|
request.playerID = playerID;
|
||||||
|
request.type = type;
|
||||||
|
request.value = value;
|
||||||
|
ChatServerLink::Send(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityComponent::PlayerJoinLobby(Entity* player, const std::string& playerChoices) {
|
||||||
if (!m_Parent->HasComponent(eReplicaComponentType::QUICK_BUILD)) {
|
if (!m_Parent->HasComponent(eReplicaComponentType::QUICK_BUILD)) {
|
||||||
GameMessages::MatchResponse matchResponse;
|
GameMessages::MatchResponse matchResponse;
|
||||||
matchResponse.target = player->GetObjectID();
|
matchResponse.target = player->GetObjectID();
|
||||||
matchResponse.response = 0;
|
matchResponse.response = 0;
|
||||||
matchResponse.SendToClient(player->GetSystemAddress()); // tell the client they joined a lobby
|
matchResponse.SendToClient(player->GetSystemAddress()); // tell the client they joined a lobby
|
||||||
}
|
}
|
||||||
LobbyPlayer newLobbyPlayer{};
|
|
||||||
newLobbyPlayer.entityID = player->GetObjectID();
|
|
||||||
LWOOBJID playerLobbyID = LWOOBJID_EMPTY;
|
|
||||||
|
|
||||||
auto* character = player->GetCharacter();
|
auto* character = player->GetCharacter();
|
||||||
if (character != nullptr)
|
if (character != nullptr)
|
||||||
character->SetLastNonInstanceZoneID(Game::zoneManager->GetZone()->GetWorldID());
|
character->SetLastNonInstanceZoneID(Game::zoneManager->GetZone()->GetWorldID());
|
||||||
|
|
||||||
for (auto& [lobbyID, lobby] : m_Queue) {
|
// The lobbies are the chat server's, so players of every instance of this zone wait together (docs/Matchmaking.md).
|
||||||
if (lobby.players.size() < m_ActivityInfo.maxTeamSize || m_ActivityInfo.maxTeamSize == 1 && lobby.players.size() < m_ActivityInfo.maxTeams) {
|
// It gets what this world read from the activity, overrides (solo racing, transfer zone) included.
|
||||||
// If an empty slot in an existing lobby is found
|
ChatPackets::MatchRequest request;
|
||||||
lobby.players.push_back(newLobbyPlayer);
|
request.playerID = player->GetObjectID();
|
||||||
playerLobbyID = lobbyID;
|
request.type = ChatPackets::eMatchRequestType::JOIN;
|
||||||
|
request.activityID = m_ActivityInfo.ActivityID;
|
||||||
// Update the joining player on players already in the lobby, and update players already in the lobby on the joining player
|
request.playerName = character != nullptr ? character->GetName() : "";
|
||||||
LDFData<LWOOBJID> playerLDF("player", player->GetObjectID());
|
request.playerChoices = playerChoices;
|
||||||
LDFData<std::string> playerName("playerName", player->GetCharacter()->GetName());
|
request.instanceMapID = m_ActivityInfo.instanceMapID;
|
||||||
std::string matchUpdateJoined = playerLDF.GetString() + "\n" + playerName.GetString();
|
request.minTeams = m_ActivityInfo.minTeams;
|
||||||
for (const auto& joinedPlayer : lobby.players) {
|
request.maxTeams = m_ActivityInfo.maxTeams;
|
||||||
auto* const entity = joinedPlayer.GetEntity();
|
request.minTeamSize = m_ActivityInfo.minTeamSize;
|
||||||
|
request.maxTeamSize = m_ActivityInfo.maxTeamSize;
|
||||||
if (entity == nullptr) {
|
request.waitTime = m_ActivityInfo.waitTime;
|
||||||
continue;
|
request.startDelay = m_ActivityInfo.startDelay;
|
||||||
}
|
ChatServerLink::Send(request);
|
||||||
|
|
||||||
LDFData<LWOOBJID> entityLDF("player", entity->GetObjectID());
|
|
||||||
LDFData<std::string> entityName("playerName", entity->GetCharacter()->GetName());
|
|
||||||
std::string matchUpdate = entityLDF.GetString() + "\n" + entityName.GetString();
|
|
||||||
GameMessages::MatchUpdate existingPlayerUpdate;
|
|
||||||
existingPlayerUpdate.target = player->GetObjectID();
|
|
||||||
existingPlayerUpdate.data = matchUpdate;
|
|
||||||
existingPlayerUpdate.type = eMatchUpdate::PLAYER_ADDED;
|
|
||||||
existingPlayerUpdate.SendToClient(player->GetSystemAddress());
|
|
||||||
PlayerReady(entity, joinedPlayer.ready);
|
|
||||||
GameMessages::MatchUpdate joinedPlayerUpdate;
|
|
||||||
joinedPlayerUpdate.target = entity->GetObjectID();
|
|
||||||
joinedPlayerUpdate.data = matchUpdateJoined;
|
|
||||||
joinedPlayerUpdate.type = eMatchUpdate::PLAYER_ADDED;
|
|
||||||
joinedPlayerUpdate.SendToClient(entity->GetSystemAddress());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (playerLobbyID == LWOOBJID_EMPTY) {
|
|
||||||
// If all lobbies are full
|
|
||||||
playerLobbyID = ObjectIDManager::GenerateObjectID();
|
|
||||||
auto& newLobby = m_Queue[playerLobbyID];
|
|
||||||
newLobby.players.push_back(newLobbyPlayer);
|
|
||||||
newLobby.timer = m_ActivityInfo.waitTime / 1000;
|
|
||||||
}
|
|
||||||
const auto& lobby = m_Queue[playerLobbyID];
|
|
||||||
|
|
||||||
if (m_ActivityInfo.maxTeamSize != 1 && lobby.players.size() >= m_ActivityInfo.minTeamSize || m_ActivityInfo.maxTeamSize == 1 && lobby.players.size() >= m_ActivityInfo.minTeams) {
|
|
||||||
// Update the joining player on the match timer
|
|
||||||
LDFData<float> matchTimer("time", lobby.timer);
|
|
||||||
GameMessages::MatchUpdate matchUpdate;
|
|
||||||
matchUpdate.target = player->GetObjectID();
|
|
||||||
matchUpdate.data = matchTimer.GetString();
|
|
||||||
matchUpdate.type = eMatchUpdate::PHASE_WAIT_READY;
|
|
||||||
matchUpdate.SendToClient(player->GetSystemAddress());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActivityComponent::PlayerLeave(LWOOBJID playerID) {
|
void ActivityComponent::PlayerLeave(LWOOBJID playerID) {
|
||||||
// Removes the player from a lobby and notifies the others, not applicable for non-lobby instances
|
// Not applicable for non-lobby instances
|
||||||
for (auto& lobby : m_Queue | std::views::values) {
|
if (!HasLobby()) return;
|
||||||
for (int i = 0; i < lobby.players.size(); i++) {
|
SendMatchRequest(playerID, ChatPackets::eMatchRequestType::LEAVE, 0);
|
||||||
const auto& player = lobby.players[i];
|
|
||||||
if (player.entityID == playerID) {
|
|
||||||
LDFData<LWOOBJID> matchUpdateLeft("player", playerID);
|
|
||||||
for (const auto& lobbyPlayer : lobby.players) {
|
|
||||||
auto* const entity = lobbyPlayer.GetEntity();
|
|
||||||
if (entity == nullptr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
GameMessages::MatchUpdate matchUpdate;
|
|
||||||
matchUpdate.target = entity->GetObjectID();
|
|
||||||
matchUpdate.data = matchUpdateLeft.GetString();
|
|
||||||
matchUpdate.type = eMatchUpdate::PLAYER_REMOVED;
|
|
||||||
matchUpdate.SendToClient(entity->GetSystemAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
lobby.players.erase(lobby.players.begin() + i);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActivityComponent::Update(float deltaTime) {
|
void ActivityComponent::StartMatch(const ChatPackets::MatchTransfer& transfer) {
|
||||||
std::vector<LWOOBJID> lobbiesToRemove{};
|
auto& instance = NewInstance();
|
||||||
// Ticks all the lobbies, not applicable for non-instance activities
|
for (const auto playerID : transfer.players) {
|
||||||
for (auto& [lobbyID, lobby] : m_Queue) {
|
auto* const entity = Game::entityManager->GetEntity(playerID);
|
||||||
for (const auto& player : lobby.players) {
|
if (entity == nullptr || !CheckCost(entity)) {
|
||||||
const auto* const entity = player.GetEntity();
|
|
||||||
if (entity == nullptr) {
|
|
||||||
PlayerLeave(player.entityID);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lobby.players.empty()) {
|
|
||||||
lobbiesToRemove.push_back(lobbyID);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the match time for all players
|
instance.AddParticipant(entity);
|
||||||
if (m_ActivityInfo.maxTeamSize != 1 && lobby.players.size() >= m_ActivityInfo.minTeamSize
|
|
||||||
|| m_ActivityInfo.maxTeamSize == 1 && lobby.players.size() >= m_ActivityInfo.minTeams) {
|
|
||||||
if (lobby.timer == m_ActivityInfo.waitTime / 1000) {
|
|
||||||
for (const auto& joinedPlayer : lobby.players) {
|
|
||||||
auto* const entity = joinedPlayer.GetEntity();
|
|
||||||
|
|
||||||
if (entity == nullptr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
LDFData<float> matchTimerUpdate("time", lobby.timer);
|
|
||||||
GameMessages::MatchUpdate matchUpdate;
|
|
||||||
matchUpdate.target = entity->GetObjectID();
|
|
||||||
matchUpdate.data = matchTimerUpdate.GetString();
|
|
||||||
matchUpdate.type = eMatchUpdate::PHASE_WAIT_READY;
|
|
||||||
matchUpdate.SendToClient(entity->GetSystemAddress());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lobby.timer -= deltaTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool lobbyReady = true;
|
|
||||||
for (const auto& player : lobby.players) {
|
|
||||||
if (player.ready) continue;
|
|
||||||
lobbyReady = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If everyone's ready, jump the timer
|
|
||||||
if (lobbyReady && lobby.timer > m_ActivityInfo.startDelay / 1000) {
|
|
||||||
lobby.timer = m_ActivityInfo.startDelay / 1000;
|
|
||||||
|
|
||||||
// Update players in lobby on switch to start delay
|
|
||||||
LDFData<float> matchTimerUpdate("time", lobby.timer);
|
|
||||||
for (const auto& player : lobby.players) {
|
|
||||||
auto* const entity = player.GetEntity();
|
|
||||||
|
|
||||||
if (entity == nullptr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
GameMessages::MatchUpdate matchUpdate;
|
|
||||||
matchUpdate.target = entity->GetObjectID();
|
|
||||||
matchUpdate.data = matchTimerUpdate.GetString();
|
|
||||||
matchUpdate.type = eMatchUpdate::PHASE_WAIT_START;
|
|
||||||
matchUpdate.SendToClient(entity->GetSystemAddress());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The timer has elapsed, start the instance
|
|
||||||
if (lobby.timer <= 0.0f) {
|
|
||||||
LOG("Setting up instance.");
|
|
||||||
auto& instance = NewInstance();
|
|
||||||
LoadPlayersIntoInstance(instance, lobby.players);
|
|
||||||
instance.StartZone();
|
|
||||||
lobbiesToRemove.push_back(lobbyID);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
instance.TransferParticipants(transfer);
|
||||||
for (const auto id : lobbiesToRemove) {
|
|
||||||
RemoveLobby(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActivityComponent::RemoveLobby(const LWOOBJID lobbyID) {
|
|
||||||
if (m_Queue.contains(lobbyID)) m_Queue.erase(lobbyID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ActivityComponent::HasLobby() const {
|
bool ActivityComponent::HasLobby() const {
|
||||||
@@ -310,16 +184,6 @@ bool ActivityComponent::HasLobby() const {
|
|||||||
return m_ActivityInfo.instanceMapID != UINT_MAX && m_ActivityInfo.instanceMapID != Game::server->GetZoneID();
|
return m_ActivityInfo.instanceMapID != UINT_MAX && m_ActivityInfo.instanceMapID != Game::server->GetZoneID();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ActivityComponent::PlayerIsInQueue(Entity* player) {
|
|
||||||
for (const auto& lobby : m_Queue | std::views::values) {
|
|
||||||
for (const auto& lobbyPlayer : lobby.players) {
|
|
||||||
if (player->GetObjectID() == lobbyPlayer.entityID) return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ActivityComponent::IsPlayedBy(Entity* player) const {
|
bool ActivityComponent::IsPlayedBy(Entity* player) const {
|
||||||
for (const auto& instance : m_Instances) {
|
for (const auto& instance : m_Instances) {
|
||||||
for (const auto* instancePlayer : instance.GetParticipants()) {
|
for (const auto* instancePlayer : instance.GetParticipants()) {
|
||||||
@@ -362,30 +226,7 @@ bool ActivityComponent::TakeCost(Entity* player) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ActivityComponent::PlayerReady(Entity* player, bool bReady) {
|
void ActivityComponent::PlayerReady(Entity* player, bool bReady) {
|
||||||
for (auto& lobby : m_Queue | std::views::values) {
|
SendMatchRequest(player->GetObjectID(), ChatPackets::eMatchRequestType::READY, bReady ? 1 : 0);
|
||||||
for (auto& lobbyPlayer : lobby.players) {
|
|
||||||
if (lobbyPlayer.entityID == player->GetObjectID()) {
|
|
||||||
|
|
||||||
lobbyPlayer.ready = bReady;
|
|
||||||
|
|
||||||
// Update players in lobby on player being ready
|
|
||||||
LDFData<LWOOBJID> matchReadyUpdate("player", player->GetObjectID());
|
|
||||||
eMatchUpdate readyStatus = eMatchUpdate::PLAYER_READY;
|
|
||||||
if (!bReady) readyStatus = eMatchUpdate::PLAYER_NOT_READY;
|
|
||||||
for (const auto& otherPlayer : lobby.players) {
|
|
||||||
auto* const entity = otherPlayer.GetEntity();
|
|
||||||
if (entity == nullptr)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
GameMessages::MatchUpdate matchUpdate;
|
|
||||||
matchUpdate.target = entity->GetObjectID();
|
|
||||||
matchUpdate.data = matchReadyUpdate.GetString();
|
|
||||||
matchUpdate.type = readyStatus;
|
|
||||||
matchUpdate.SendToClient(entity->GetSystemAddress());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ActivityInstance& ActivityComponent::NewInstance() {
|
ActivityInstance& ActivityComponent::NewInstance() {
|
||||||
@@ -393,17 +234,6 @@ ActivityInstance& ActivityComponent::NewInstance() {
|
|||||||
return m_Instances.back();
|
return m_Instances.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActivityComponent::LoadPlayersIntoInstance(ActivityInstance& instance, const std::vector<LobbyPlayer>& lobby) const {
|
|
||||||
for (const auto& player : lobby) {
|
|
||||||
auto* const entity = player.GetEntity();
|
|
||||||
if (entity == nullptr || !CheckCost(entity)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
instance.AddParticipant(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ActivityInstance& ActivityComponent::GetInstance(const LWOOBJID playerID) const {
|
const ActivityInstance& ActivityComponent::GetInstance(const LWOOBJID playerID) const {
|
||||||
for (const auto& instance : m_Instances) {
|
for (const auto& instance : m_Instances) {
|
||||||
for (const auto* participant : instance.GetParticipants()) {
|
for (const auto* participant : instance.GetParticipants()) {
|
||||||
@@ -462,59 +292,28 @@ void ActivityComponent::PlayerRemove(LWOOBJID playerID) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActivityInstance::StartZone() {
|
void ActivityInstance::TransferParticipants(const ChatPackets::MatchTransfer& transfer) {
|
||||||
if (m_Participants.empty())
|
const auto zoneID = transfer.zoneID.GetMapID();
|
||||||
return;
|
const auto zoneInstance = transfer.zoneID.GetInstanceID();
|
||||||
|
const auto zoneClone = transfer.zoneID.GetCloneID();
|
||||||
const auto& participants = GetParticipants();
|
for (Entity* player : GetParticipants()) {
|
||||||
if (participants.empty())
|
LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", player->GetCharacter()->GetName().c_str(), zoneID, zoneInstance, zoneClone, transfer.mythranShift == true ? "true" : "false", transfer.serverIP.c_str(), transfer.serverPort);
|
||||||
return;
|
if (player->GetCharacter()) {
|
||||||
|
auto* characterComponent = player->GetComponent<CharacterComponent>();
|
||||||
auto* leader = participants[0];
|
if (characterComponent) {
|
||||||
LWOZONEID zoneId = LWOZONEID(m_ActivityInfo.instanceMapID, 0, leader->GetCharacter()->GetPropertyCloneID());
|
characterComponent->AddVisitedLevel(LWOZONEID(zoneID, LWOINSTANCEID_INVALID, zoneClone));
|
||||||
|
|
||||||
// only make a team if we have more than one participant
|
|
||||||
if (participants.size() > 1) {
|
|
||||||
ChatPackets::CreateTeam createTeam;
|
|
||||||
createTeam.leaderID = leader->GetObjectID();
|
|
||||||
|
|
||||||
for (const auto& participant : m_Participants) {
|
|
||||||
createTeam.members.push_back(participant);
|
|
||||||
}
|
|
||||||
|
|
||||||
createTeam.zoneID = zoneId;
|
|
||||||
|
|
||||||
ChatServerLink::Send(createTeam);
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto cloneId = GeneralUtils::GenerateRandomNumber<uint32_t>(1, UINT32_MAX);
|
|
||||||
for (Entity* player : participants) {
|
|
||||||
const auto objid = player->GetObjectID();
|
|
||||||
ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, m_ActivityInfo.instanceMapID, cloneId, false, [objid](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) {
|
|
||||||
|
|
||||||
auto* player = Game::entityManager->GetEntity(objid);
|
|
||||||
if (player == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", player->GetCharacter()->GetName().c_str(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort);
|
|
||||||
if (player->GetCharacter()) {
|
|
||||||
auto* characterComponent = player->GetComponent<CharacterComponent>();
|
|
||||||
if (characterComponent) {
|
|
||||||
characterComponent->AddVisitedLevel(LWOZONEID(zoneID, LWOINSTANCEID_INVALID, zoneClone));
|
|
||||||
}
|
|
||||||
|
|
||||||
player->GetCharacter()->SetZoneID(zoneID);
|
|
||||||
player->GetCharacter()->SetZoneInstance(zoneInstance);
|
|
||||||
player->GetCharacter()->SetZoneClone(zoneClone);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientPackets::TransferToWorld transfer;
|
player->GetCharacter()->SetZoneID(zoneID);
|
||||||
transfer.serverIP = LUString(serverIP);
|
player->GetCharacter()->SetZoneInstance(zoneInstance);
|
||||||
transfer.serverPort = serverPort;
|
player->GetCharacter()->SetZoneClone(zoneClone);
|
||||||
transfer.mythranShift = mythranShift;
|
}
|
||||||
transfer.Send(player->GetSystemAddress());
|
|
||||||
return;
|
ClientPackets::TransferToWorld transferToWorld;
|
||||||
});
|
transferToWorld.serverIP = LUString(transfer.serverIP);
|
||||||
|
transferToWorld.serverPort = transfer.serverPort;
|
||||||
|
transferToWorld.mythranShift = transfer.mythranShift;
|
||||||
|
transferToWorld.Send(player->GetSystemAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
m_NextZoneCloneID++;
|
m_NextZoneCloneID++;
|
||||||
@@ -573,10 +372,6 @@ void ActivityInstance::SetScore(uint32_t score) {
|
|||||||
this->score = score;
|
this->score = score;
|
||||||
}
|
}
|
||||||
|
|
||||||
Entity* LobbyPlayer::GetEntity() const {
|
|
||||||
return Game::entityManager->GetEntity(entityID);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ActivityComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
|
bool ActivityComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
|
||||||
auto& activityInfo = reportInfo.info->PushDebug("Activity");
|
auto& activityInfo = reportInfo.info->PushDebug("Activity");
|
||||||
|
|
||||||
@@ -620,23 +415,6 @@ bool ActivityComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo&
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& queue = activityInfo.PushDebug("Queue");
|
|
||||||
i = 0;
|
|
||||||
for (const auto& lobbyQueue : m_Queue | std::views::values) {
|
|
||||||
auto& lobby = queue.PushDebug("Lobby " + std::to_string(i++));
|
|
||||||
lobby.PushDebug<AMFDoubleValue>("Timer") = lobbyQueue.timer;
|
|
||||||
|
|
||||||
auto& players = lobby.PushDebug("Players");
|
|
||||||
for (const auto& player : lobbyQueue.players) {
|
|
||||||
const auto* const playerEntity = player.GetEntity();
|
|
||||||
if (!playerEntity) continue;
|
|
||||||
auto* character = playerEntity->GetCharacter();
|
|
||||||
if (!character) continue;
|
|
||||||
|
|
||||||
players.PushDebug<AMFStringValue>(std::to_string(playerEntity->GetObjectID()) + ": " + character->GetName()) = player.ready ? "Ready" : "Not Ready";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& activityPlayers = activityInfo.PushDebug("Activity Players");
|
auto& activityPlayers = activityInfo.PushDebug("Activity Players");
|
||||||
for (const auto& [playerID, playerScores] : m_ActivityPlayers) {
|
for (const auto& [playerID, playerScores] : m_ActivityPlayers) {
|
||||||
auto* const activityPlayerEntity = Game::entityManager->GetEntity(playerID);
|
auto* const activityPlayerEntity = Game::entityManager->GetEntity(playerID);
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ namespace GameMessages {
|
|||||||
class GameMsg;
|
class GameMsg;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
namespace ChatPackets {
|
||||||
|
struct MatchTransfer;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an instance of an activity, having participants and score
|
* Represents an instance of an activity, having participants and score
|
||||||
*/
|
*/
|
||||||
@@ -34,9 +38,10 @@ public:
|
|||||||
void ClearParticipants() { m_Participants.clear(); };
|
void ClearParticipants() { m_Participants.clear(); };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the instance world for this activity and sends all participants there
|
* Sends the participants to the activity's instance that master started (the chat server's matchmaking found it)
|
||||||
|
* @param transfer the instance's zone, address and the players chat sent here
|
||||||
*/
|
*/
|
||||||
void StartZone();
|
void TransferParticipants(const ChatPackets::MatchTransfer& transfer);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gives the rewards for completing this activity to some participant
|
* Gives the rewards for completing this activity to some participant
|
||||||
@@ -97,44 +102,6 @@ private:
|
|||||||
std::vector<LWOOBJID> m_Participants;
|
std::vector<LWOOBJID> m_Participants;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an entity in a lobby
|
|
||||||
*/
|
|
||||||
struct LobbyPlayer {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The ID of the entity that is in the lobby
|
|
||||||
*/
|
|
||||||
LWOOBJID entityID = LWOOBJID_EMPTY;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not the entity is ready
|
|
||||||
*/
|
|
||||||
bool ready = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the entity that is in the lobby
|
|
||||||
* @return the entity that is in the lobby
|
|
||||||
*/
|
|
||||||
Entity* GetEntity() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a lobby of players with a timer until it should start the activity
|
|
||||||
*/
|
|
||||||
struct Lobby {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The lobby of players
|
|
||||||
*/
|
|
||||||
std::vector<LobbyPlayer> players;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The timer that determines when the activity should start
|
|
||||||
*/
|
|
||||||
float timer{};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the score for the player in an activity, one index might represent score, another one time, etc.
|
* Represents the score for the player in an activity, one index might represent score, another one time, etc.
|
||||||
*/
|
*/
|
||||||
@@ -163,23 +130,25 @@ public:
|
|||||||
|
|
||||||
void LoadActivityData(const int32_t activityId);
|
void LoadActivityData(const int32_t activityId);
|
||||||
|
|
||||||
void Update(float deltaTime) override;
|
|
||||||
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
|
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes some entity join the minigame, if it's a lobbied one, the entity will be placed in the lobby
|
* Makes some entity join the minigame, if it's a lobbied one, the entity will be placed in the lobby
|
||||||
* @param player the entity to join the game
|
* @param player the entity to join the game
|
||||||
|
* @param playerChoices the name-value text of the client's MatchRequest (a racing car), shown to the lobby
|
||||||
*/
|
*/
|
||||||
void PlayerJoin(Entity* player);
|
void PlayerJoin(Entity* player, const std::string& playerChoices = "");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes an entity join the lobby for this minigame, if it exists
|
* Makes an entity join the lobby for this minigame. The lobbies are the chat server's, across every world
|
||||||
|
* (docs/Matchmaking.md).
|
||||||
* @param player the entity to join
|
* @param player the entity to join
|
||||||
|
* @param playerChoices the name-value text of the client's MatchRequest
|
||||||
*/
|
*/
|
||||||
void PlayerJoinLobby(Entity* player);
|
void PlayerJoinLobby(Entity* player, const std::string& playerChoices = "");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes the player leave the lobby
|
* Makes the player leave the lobby (the chat server's)
|
||||||
* @param playerID the entity to leave the lobby
|
* @param playerID the entity to leave the lobby
|
||||||
*/
|
*/
|
||||||
void PlayerLeave(LWOOBJID playerID);
|
void PlayerLeave(LWOOBJID playerID);
|
||||||
@@ -191,24 +160,17 @@ public:
|
|||||||
void PlayerRemove(LWOOBJID playerID);
|
void PlayerRemove(LWOOBJID playerID);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds all the players to an instance of some activity
|
* A lobby's match for this activity has its instance: the players of it in this world who can pay go there
|
||||||
* @param instance the instance to load the players into
|
* @param transfer the instance and the players, from the chat server
|
||||||
* @param lobby the players to load into the instance
|
|
||||||
*/
|
*/
|
||||||
void LoadPlayersIntoInstance(ActivityInstance& instance, const std::vector<LobbyPlayer>& lobby) const;
|
void StartMatch(const ChatPackets::MatchTransfer& transfer);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a lobby from the activity manager
|
* Marks a player as (un)ready in the lobby they wait in (the chat server knows which)
|
||||||
* @param lobby the lobby to remove
|
|
||||||
*/
|
|
||||||
void RemoveLobby(const LWOOBJID lobbyID);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marks a player as (un)ready in a lobby
|
|
||||||
* @param player the entity to mark
|
* @param player the entity to mark
|
||||||
* @param bReady true if the entity is ready, false otherwise
|
* @param bReady true if the entity is ready, false otherwise
|
||||||
*/
|
*/
|
||||||
void PlayerReady(Entity* player, bool bReady);
|
static void PlayerReady(Entity* player, bool bReady);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the ID of this activity
|
* Returns the ID of this activity
|
||||||
@@ -226,13 +188,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
bool HasLobby() const;
|
bool HasLobby() const;
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a player is currently waiting in a lobby
|
|
||||||
* @param player the entity to check for
|
|
||||||
* @return true if the entity is waiting in a lobby, false otherwise
|
|
||||||
*/
|
|
||||||
bool PlayerIsInQueue(Entity* player);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if an entity is currently playing this activity
|
* Checks if an entity is currently playing this activity
|
||||||
* @param player the entity to check
|
* @param player the entity to check
|
||||||
@@ -334,11 +289,6 @@ private:
|
|||||||
*/
|
*/
|
||||||
std::vector<ActivityInstance> m_Instances;
|
std::vector<ActivityInstance> m_Instances;
|
||||||
|
|
||||||
/**
|
|
||||||
* The current lobbies for this activity
|
|
||||||
*/
|
|
||||||
std::map<LWOOBJID, Lobby> m_Queue;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All the activity score for the players in this activity
|
* All the activity score for the players in this activity
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -143,18 +143,17 @@ namespace GameMessages {
|
|||||||
auto* comp = scriptedAct->GetComponent<ScriptedActivityComponent>();
|
auto* comp = scriptedAct->GetComponent<ScriptedActivityComponent>();
|
||||||
if (!comp) continue;
|
if (!comp) continue;
|
||||||
if (comp->GetActivityID() == value) {
|
if (comp->GetActivityID() == value) {
|
||||||
comp->PlayerJoin(&entity);
|
comp->PlayerJoin(&entity, playerChoices);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (type == 1) { // ready/unready
|
} else if (type == 1) { // ready/unready
|
||||||
for (Entity* scriptedAct : scriptedActs) {
|
// Answered like a join (live), then the chat server tells the lobby (docs/Matchmaking.md)
|
||||||
auto* comp = scriptedAct->GetComponent<ScriptedActivityComponent>();
|
MatchResponse response;
|
||||||
if (!comp) continue;
|
response.target = entity.GetObjectID();
|
||||||
if (comp->PlayerIsInQueue(&entity)) {
|
response.response = 0;
|
||||||
comp->PlayerReady(&entity, value);
|
response.SendToClient(sysAddr);
|
||||||
}
|
ActivityComponent::PlayerReady(&entity, value != 0);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,8 @@
|
|||||||
#include "Entity.h"
|
#include "Entity.h"
|
||||||
#include "Character.h"
|
#include "Character.h"
|
||||||
#include "ChatPackets.h"
|
#include "ChatPackets.h"
|
||||||
|
#include "ScriptedActivityComponent.h"
|
||||||
|
#include "eReplicaComponentType.h"
|
||||||
#include "WorldRoutePacket.h"
|
#include "WorldRoutePacket.h"
|
||||||
#include "ChatServerLink.h"
|
#include "ChatServerLink.h"
|
||||||
#include "PacketDispatcher.h"
|
#include "PacketDispatcher.h"
|
||||||
@@ -768,6 +770,17 @@ namespace {
|
|||||||
Game::entityManager->SerializeEntity(player);
|
Game::entityManager->SerializeEntity(player);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// An activity lobby's match has its instance (docs/Matchmaking.md): send our players in it there
|
||||||
|
handlers.On<ChatPackets::MatchTransfer>(MessageType::Chat::MATCH_TRANSFER, [](const ChatPackets::MatchTransfer& transfer, const SystemAddress&) {
|
||||||
|
for (auto* entity : Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SCRIPTED_ACTIVITY)) {
|
||||||
|
auto* activity = entity->GetComponent<ScriptedActivityComponent>();
|
||||||
|
if (!activity || activity->GetActivityID() != transfer.activityID || !activity->HasLobby()) continue;
|
||||||
|
activity->StartMatch(transfer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG("Matchmaking: no activity %i in this world for a match of %zu player(s)", transfer.activityID, transfer.players.size());
|
||||||
|
});
|
||||||
|
|
||||||
// New mail for a player the chat server says is in this world
|
// New mail for a player the chat server says is in this world
|
||||||
handlers.On<ChatPackets::MailNotify>(MessageType::Chat::MAIL, [](const ChatPackets::MailNotify& notify, const SystemAddress&) {
|
handlers.On<ChatPackets::MailNotify>(MessageType::Chat::MAIL, [](const ChatPackets::MailNotify& notify, const SystemAddress&) {
|
||||||
Mail::NotifyNewMailHere(notify.receiverID);
|
Mail::NotifyNewMailHere(notify.receiverID);
|
||||||
|
|||||||
Reference in New Issue
Block a user