core: add clientless player manager and controller API
This commit is contained in:
1 parent
0c2e333bb4
commit
5ac9ed673c
4 files changed
+276
-17
No files matched your search
@@ -0,0 +1,175 @@
|
||||
#include "ClientlessPlayerMgr.h"
|
||||
#include "Log.h"
|
||||
#include "Player.h"
|
||||
#include "World.h"
|
||||
#include "WorldSession.h"
|
||||
|
||||
ClientlessPlayerMgr* ClientlessPlayerMgr::instance()
|
||||
{
|
||||
static ClientlessPlayerMgr instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
bool ClientlessPlayerMgr::StartPlayer(uint32 accountId, uint32 characterGuid,
|
||||
std::unique_ptr<ClientlessPlayerController> controller)
|
||||
{
|
||||
if (!accountId || !characterGuid)
|
||||
return false;
|
||||
|
||||
if (IsManaged(characterGuid) ||
|
||||
sWorld->GetClientlessPlayerState(characterGuid) != ClientlessPlayerState::Offline)
|
||||
{
|
||||
TC_LOG_ERROR("server.worldserver",
|
||||
"ClientlessPlayerMgr::StartPlayer rejected character GUID %u because it is already managed or not Offline.",
|
||||
characterGuid);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!controller)
|
||||
controller = std::make_unique<ClientlessPlayerController>();
|
||||
|
||||
ManagedPlayer record;
|
||||
record.AccountId = accountId;
|
||||
record.Controller = std::move(controller);
|
||||
|
||||
auto inserted = _records.emplace(characterGuid, std::move(record));
|
||||
if (!inserted.second)
|
||||
return false;
|
||||
|
||||
if (!sWorld->CreateClientlessPlayerSession(accountId, characterGuid))
|
||||
{
|
||||
_records.erase(characterGuid);
|
||||
return false;
|
||||
}
|
||||
|
||||
TC_LOG_INFO("server.worldserver",
|
||||
"ClientlessPlayerMgr started character GUID %u (account %u).",
|
||||
characterGuid, accountId);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ClientlessPlayerMgr::StopPlayer(uint32 characterGuid)
|
||||
{
|
||||
if (_records.find(characterGuid) == _records.end())
|
||||
return false;
|
||||
return sWorld->RemoveClientlessSession(characterGuid);
|
||||
}
|
||||
|
||||
bool ClientlessPlayerMgr::IsManaged(uint32 characterGuid) const
|
||||
{
|
||||
return _records.find(characterGuid) != _records.end();
|
||||
}
|
||||
|
||||
uint32 ClientlessPlayerMgr::GetAccountId(uint32 characterGuid) const
|
||||
{
|
||||
auto itr = _records.find(characterGuid);
|
||||
return itr != _records.end() ? itr->second.AccountId : 0u;
|
||||
}
|
||||
|
||||
bool ClientlessPlayerMgr::SetIntent(uint32 characterGuid, ClientlessIntent intent)
|
||||
{
|
||||
auto itr = _records.find(characterGuid);
|
||||
if (itr == _records.end() || intent.Empty())
|
||||
return false;
|
||||
|
||||
itr->second.Intent = std::move(intent);
|
||||
|
||||
if (WorldSession* session = sWorld->FindClientlessSession(characterGuid))
|
||||
if (Player* player = session->GetPlayer())
|
||||
if (itr->second.Controller)
|
||||
itr->second.Controller->OnIntentChanged(player, &itr->second.Intent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ClientlessPlayerMgr::ClearIntent(uint32 characterGuid)
|
||||
{
|
||||
auto itr = _records.find(characterGuid);
|
||||
if (itr == _records.end())
|
||||
return false;
|
||||
|
||||
itr->second.Intent = ClientlessIntent();
|
||||
|
||||
if (WorldSession* session = sWorld->FindClientlessSession(characterGuid))
|
||||
if (Player* player = session->GetPlayer())
|
||||
if (itr->second.Controller)
|
||||
itr->second.Controller->OnIntentChanged(player, nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ClientlessIntent const* ClientlessPlayerMgr::GetIntent(uint32 characterGuid) const
|
||||
{
|
||||
auto itr = _records.find(characterGuid);
|
||||
if (itr == _records.end() || itr->second.Intent.Empty())
|
||||
return nullptr;
|
||||
return &itr->second.Intent;
|
||||
}
|
||||
|
||||
bool ClientlessPlayerMgr::QueueAction(uint32 characterGuid, ClientlessAction action)
|
||||
{
|
||||
auto itr = _records.find(characterGuid);
|
||||
if (itr == _records.end() || action.Name.empty())
|
||||
return false;
|
||||
itr->second.Actions.push_back(std::move(action));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ClientlessPlayerMgr::PopNextAction(uint32 characterGuid, ClientlessAction& action)
|
||||
{
|
||||
auto itr = _records.find(characterGuid);
|
||||
if (itr == _records.end() || itr->second.Actions.empty())
|
||||
return false;
|
||||
action = std::move(itr->second.Actions.front());
|
||||
itr->second.Actions.pop_front();
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32 ClientlessPlayerMgr::GetQueuedActionCount(uint32 characterGuid) const
|
||||
{
|
||||
auto itr = _records.find(characterGuid);
|
||||
return itr != _records.end() ? uint32(itr->second.Actions.size()) : 0u;
|
||||
}
|
||||
|
||||
void ClientlessPlayerMgr::Update(uint32 diff)
|
||||
{
|
||||
for (auto itr = _records.begin(); itr != _records.end(); )
|
||||
{
|
||||
uint32 characterGuid = itr->first;
|
||||
ManagedPlayer& record = itr->second;
|
||||
ClientlessPlayerState state = sWorld->GetClientlessPlayerState(characterGuid);
|
||||
|
||||
if (state == ClientlessPlayerState::Offline)
|
||||
{
|
||||
itr = _records.erase(itr);
|
||||
continue;
|
||||
}
|
||||
|
||||
WorldSession* session = sWorld->FindClientlessSession(characterGuid);
|
||||
Player* player = session ? session->GetPlayer() : nullptr;
|
||||
|
||||
if (state == ClientlessPlayerState::Online && player)
|
||||
{
|
||||
if (!record.OnlineNotified)
|
||||
{
|
||||
record.OnlineNotified = true;
|
||||
if (record.Controller)
|
||||
{
|
||||
record.Controller->OnPlayerOnline(player);
|
||||
if (!record.Intent.Empty())
|
||||
record.Controller->OnIntentChanged(player, &record.Intent);
|
||||
}
|
||||
}
|
||||
if (record.Controller)
|
||||
record.Controller->Update(player, diff);
|
||||
}
|
||||
else if (state == ClientlessPlayerState::Removing && !record.LogoutNotified)
|
||||
{
|
||||
record.LogoutNotified = true;
|
||||
if (record.Controller && player)
|
||||
record.Controller->OnPlayerLogout(player);
|
||||
}
|
||||
|
||||
++itr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Vcore generic clientless Player manager.
|
||||
* C6 contains no AdventureBot policy and no Lua runtime.
|
||||
*/
|
||||
#ifndef VCORE_CLIENTLESS_PLAYER_MGR_H
|
||||
#define VCORE_CLIENTLESS_PLAYER_MGR_H
|
||||
|
||||
#include "Common.h"
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
class Player;
|
||||
|
||||
struct TC_GAME_API ClientlessIntent
|
||||
{
|
||||
std::string Name;
|
||||
std::unordered_map<std::string, std::string> Parameters;
|
||||
bool Empty() const { return Name.empty(); }
|
||||
};
|
||||
|
||||
struct TC_GAME_API ClientlessAction
|
||||
{
|
||||
std::string Name;
|
||||
std::unordered_map<std::string, std::string> Parameters;
|
||||
};
|
||||
|
||||
class TC_GAME_API ClientlessPlayerController
|
||||
{
|
||||
public:
|
||||
virtual ~ClientlessPlayerController() = default;
|
||||
virtual void OnPlayerOnline(Player*) { }
|
||||
virtual void OnIntentChanged(Player*, ClientlessIntent const*) { }
|
||||
virtual void Update(Player*, uint32) { }
|
||||
virtual void OnPlayerLogout(Player*) { }
|
||||
};
|
||||
|
||||
class TC_GAME_API ClientlessPlayerMgr
|
||||
{
|
||||
public:
|
||||
static ClientlessPlayerMgr* instance();
|
||||
|
||||
bool StartPlayer(uint32 accountId, uint32 characterGuid,
|
||||
std::unique_ptr<ClientlessPlayerController> controller = nullptr);
|
||||
bool StopPlayer(uint32 characterGuid);
|
||||
|
||||
bool IsManaged(uint32 characterGuid) const;
|
||||
uint32 GetAccountId(uint32 characterGuid) const;
|
||||
uint32 GetManagedCount() const { return uint32(_records.size()); }
|
||||
|
||||
bool SetIntent(uint32 characterGuid, ClientlessIntent intent);
|
||||
bool ClearIntent(uint32 characterGuid);
|
||||
ClientlessIntent const* GetIntent(uint32 characterGuid) const;
|
||||
|
||||
bool QueueAction(uint32 characterGuid, ClientlessAction action);
|
||||
bool PopNextAction(uint32 characterGuid, ClientlessAction& action);
|
||||
uint32 GetQueuedActionCount(uint32 characterGuid) const;
|
||||
|
||||
void Update(uint32 diff);
|
||||
|
||||
private:
|
||||
struct ManagedPlayer
|
||||
{
|
||||
uint32 AccountId = 0;
|
||||
std::unique_ptr<ClientlessPlayerController> Controller;
|
||||
ClientlessIntent Intent;
|
||||
std::deque<ClientlessAction> Actions;
|
||||
bool OnlineNotified = false;
|
||||
bool LogoutNotified = false;
|
||||
};
|
||||
|
||||
std::unordered_map<uint32, ManagedPlayer> _records;
|
||||
};
|
||||
|
||||
#define sClientlessPlayerMgr ClientlessPlayerMgr::instance()
|
||||
#endif
|
||||
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
|
||||
#include "World.h"
|
||||
#include "ClientlessPlayerMgr.h"
|
||||
#include "AccountMgr.h"
|
||||
#include "AchievementMgr.h"
|
||||
#include "AddonMgr.h"
|
||||
@@ -420,17 +421,6 @@ bool World::CreateClientlessPlayerSession(uint32 accountId, uint32 characterGuid
|
||||
return false;
|
||||
}
|
||||
|
||||
for (SessionMap::const_iterator itr = m_clientlessSessions.begin(); itr != m_clientlessSessions.end(); ++itr)
|
||||
{
|
||||
if (itr->second && itr->second->GetAccountId() == accountId)
|
||||
{
|
||||
TC_LOG_ERROR("server.worldserver",
|
||||
"World::CreateClientlessPlayerSession rejected account %u because character GUID %u already owns a clientless session.",
|
||||
accountId, itr->first);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QueryResult characterResult = CharacterDatabase.PQuery(
|
||||
"SELECT account FROM characters WHERE guid = %u", characterGuid);
|
||||
if (!characterResult)
|
||||
@@ -540,6 +530,19 @@ void World::AddSession_(WorldSession* s)
|
||||
{
|
||||
ASSERT(s);
|
||||
|
||||
for (SessionMap::const_iterator itr = m_clientlessSessions.begin(); itr != m_clientlessSessions.end(); ++itr)
|
||||
{
|
||||
if (itr->second && itr->second->GetAccountId() == s->GetAccountId())
|
||||
{
|
||||
TC_LOG_ERROR("server.worldserver",
|
||||
"World::AddSession_ rejected network session for account %u because clientless character GUID %u is active.",
|
||||
s->GetAccountId(), itr->first);
|
||||
s->KickPlayer();
|
||||
delete s;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//NOTE - Still there is race condition in WorldSession* being used in the Sockets
|
||||
|
||||
///- kick already loaded player with same account (if any) and remove session
|
||||
@@ -3387,6 +3390,10 @@ void World::UpdateSessions(uint32 diff)
|
||||
}
|
||||
|
||||
|
||||
// High-level clientless controllers observe state before the low-level
|
||||
// lifecycle loop may destroy a Removing session.
|
||||
sClientlessPlayerMgr->Update(diff);
|
||||
|
||||
// Clientless sessions share normal WorldSession callbacks/update machinery,
|
||||
// but are intentionally outside the network session registry and queue.
|
||||
// Explicit states keep async initialization/player loading from being
|
||||
|
||||
Reference in new issue
Block a user