mirror of
https://gitlab.com/Scribble/gdlenhanced.git
synced 2026-08-17 14:23:04 -04:00
985 lines
No EOL
27 KiB
C++
985 lines
No EOL
27 KiB
C++
|
|
#include "StdAfx.h"
|
|
#include "AllegianceManager.h"
|
|
#include "World.h"
|
|
#include "Player.h"
|
|
#include "DatabaseIO.h"
|
|
#include "ChatMsgs.h"
|
|
|
|
#define RELEASE_ASSERT(x) if (!(x)) DebugBreak();
|
|
|
|
DEFINE_PACK(AllegianceInfo)
|
|
{
|
|
_info.Pack(pWriter);
|
|
}
|
|
|
|
DEFINE_UNPACK(AllegianceInfo)
|
|
{
|
|
_info.UnPack(pReader);
|
|
return true;
|
|
}
|
|
|
|
AllegianceTreeNode::AllegianceTreeNode()
|
|
{
|
|
}
|
|
|
|
AllegianceTreeNode::~AllegianceTreeNode()
|
|
{
|
|
for (auto &entry : _vassals)
|
|
delete entry.second;
|
|
_vassals.clear();
|
|
}
|
|
|
|
AllegianceTreeNode *AllegianceTreeNode::FindCharByNameRecursivelySlow(const std::string &charName)
|
|
{
|
|
const char *targetName = charName.c_str();
|
|
if (targetName[0] == '+')
|
|
targetName++;
|
|
|
|
const char *compareName = _charName.c_str();
|
|
if (compareName[0] == '+')
|
|
compareName++;
|
|
|
|
if (!_stricmp(targetName, compareName))
|
|
{
|
|
return this;
|
|
}
|
|
|
|
AllegianceTreeNode *node = nullptr;
|
|
for (auto &entry : _vassals)
|
|
{
|
|
if (node = entry.second->FindCharByNameRecursivelySlow(charName))
|
|
break;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
void AllegianceTreeNode::FillAllegianceNode(AllegianceNode *node)
|
|
{
|
|
node->_data._rank = _rank;
|
|
node->_data._level = _level;
|
|
node->_data._cp_cached = min(4294967295, _cp_cached);
|
|
node->_data._cp_tithed = min(4294967295, _cp_tithed);
|
|
node->_data._gender = _gender;
|
|
node->_data._hg = _hg;
|
|
node->_data._leadership = _leadership;
|
|
node->_data._loyalty = _loyalty;
|
|
node->_data._name = _charName;
|
|
node->_data._id = _charID;
|
|
// node->_data._allegiance_age = _unixTimeSwornAt; what unit is this and what does the client do with this information?
|
|
// node->_data._time_online = _ingameSecondsSworn; what unit is this and what does the client do with this information?
|
|
}
|
|
|
|
void AllegianceTreeNode::UpdateWithWeenie(std::shared_ptr<CWeenieObject> weenie)
|
|
{
|
|
_charID = weenie->GetID();
|
|
_charName = weenie->GetName();
|
|
_hg = static_cast<HeritageGroup>(weenie->InqIntQuality(HERITAGE_GROUP_INT, Invalid_HeritageGroup));
|
|
_gender = static_cast<Gender>(weenie->InqIntQuality(GENDER_INT, Invalid_Gender));
|
|
_level = weenie->InqIntQuality(LEVEL_INT, 1);
|
|
_leadership = 0;
|
|
weenie->m_Qualities.InqSkill(LEADERSHIP_SKILL, *reinterpret_cast<DWORD *>(&_leadership), FALSE);
|
|
_loyalty = 0;
|
|
weenie->m_Qualities.InqSkill(LOYALTY_SKILL, *reinterpret_cast<DWORD *>(&_loyalty), FALSE);
|
|
}
|
|
|
|
DEFINE_PACK(AllegianceTreeNode)
|
|
{
|
|
pWriter->Write<DWORD>(2); // version
|
|
pWriter->Write<DWORD>(_charID);
|
|
pWriter->WriteString(_charName);
|
|
|
|
pWriter->Write<DWORD>(_monarchID);
|
|
pWriter->Write<DWORD>(_patronID);
|
|
pWriter->Write<int>(_hg);
|
|
pWriter->Write<int>(_gender);
|
|
pWriter->Write<DWORD>(_rank);
|
|
pWriter->Write<DWORD>(_level);
|
|
pWriter->Write<DWORD>(_leadership);
|
|
pWriter->Write<DWORD>(_loyalty);
|
|
pWriter->Write<DWORD>(_numFollowers);
|
|
pWriter->Write<DWORD64>(_cp_cached);
|
|
pWriter->Write<DWORD64>(_cp_tithed);
|
|
pWriter->Write<DWORD64>(_cp_pool_to_unload);
|
|
pWriter->Write<DWORD64>(_unixTimeSwornAt);
|
|
pWriter->Write<DWORD64>(_ingameSecondsSworn);
|
|
|
|
pWriter->Write<DWORD>(_vassals.size());
|
|
for (auto &entry : _vassals)
|
|
entry.second->Pack(pWriter);
|
|
}
|
|
|
|
DEFINE_UNPACK(AllegianceTreeNode)
|
|
{
|
|
DWORD version = pReader->Read<DWORD>(); // version
|
|
_charID = pReader->Read<DWORD>();
|
|
_charName = pReader->ReadString();
|
|
|
|
_monarchID = pReader->Read<DWORD>();
|
|
_patronID = pReader->Read<DWORD>();
|
|
_hg = (HeritageGroup)pReader->Read<int>();
|
|
_gender = (Gender)pReader->Read<int>();
|
|
_rank = pReader->Read<DWORD>();
|
|
_level = pReader->Read<DWORD>();
|
|
_leadership = pReader->Read<DWORD>();
|
|
_loyalty = pReader->Read<DWORD>();
|
|
_numFollowers = pReader->Read<DWORD>();
|
|
_cp_cached = pReader->Read<DWORD64>();
|
|
_cp_tithed = pReader->Read<DWORD64>();
|
|
_cp_pool_to_unload = pReader->Read<DWORD64>();
|
|
if (version < 2) {
|
|
_unixTimeSwornAt = time(0); // set to now
|
|
_ingameSecondsSworn = 1;
|
|
} else {
|
|
_unixTimeSwornAt = pReader->Read<DWORD64>();
|
|
_ingameSecondsSworn = pReader->Read<DWORD64>();
|
|
}
|
|
|
|
DWORD numVassals = pReader->Read<DWORD>();
|
|
for (DWORD i = 0; i < numVassals; i++)
|
|
{
|
|
AllegianceTreeNode *node = new AllegianceTreeNode();
|
|
node->UnPack(pReader);
|
|
_vassals[node->_charID] = node;
|
|
|
|
assert(node->_charID >= 0x50000000 && node->_charID < 0x70000000);
|
|
}
|
|
|
|
assert(_charID >= 0x50000000 && _charID < 0x70000000);
|
|
return true;
|
|
}
|
|
|
|
AllegianceManager::AllegianceManager()
|
|
{
|
|
Load();
|
|
}
|
|
|
|
AllegianceManager::~AllegianceManager()
|
|
{
|
|
Save();
|
|
|
|
for (auto &entry : _monarchs)
|
|
delete entry.second;
|
|
_monarchs.clear();
|
|
_directNodes.clear();
|
|
|
|
for (auto &entry : _allegInfos)
|
|
delete entry.second;
|
|
_allegInfos.clear();
|
|
}
|
|
|
|
void AllegianceManager::Load()
|
|
{
|
|
void *data = NULL;
|
|
DWORD length = 0;
|
|
if (g_pDBIO->GetGlobalData(DBIO_GLOBAL_ALLEGIANCE_DATA, &data, &length))
|
|
{
|
|
BinaryReader reader(data, length);
|
|
UnPack(&reader);
|
|
}
|
|
|
|
m_LastSave = Timer::cur_time;
|
|
}
|
|
|
|
void AllegianceManager::Save()
|
|
{
|
|
BinaryWriter data;
|
|
Pack(&data);
|
|
g_pDBIO->CreateOrUpdateGlobalData(DBIO_GLOBAL_ALLEGIANCE_DATA, data.GetData(), data.GetSize());
|
|
}
|
|
|
|
void AllegianceManager::WalkTreeAndBumpOnlineTime(AllegianceTreeNode *node, int onlineSecondsDelta) {
|
|
// Walk allegiance tree and bump _ingameSecondsSworn for all online characters.
|
|
// There's probably a better way of maintaining number of seconds of online-time.
|
|
for (auto &entry : node->_vassals) {
|
|
AllegianceTreeNode *vassal = entry.second;
|
|
std::shared_ptr<CPlayerWeenie> target = g_pWorld->FindPlayer(vassal->_charID);
|
|
if (target)
|
|
vassal->_ingameSecondsSworn += onlineSecondsDelta;
|
|
WalkTreeAndBumpOnlineTime(vassal, onlineSecondsDelta);
|
|
}
|
|
}
|
|
|
|
void AllegianceManager::Tick()
|
|
{
|
|
if (m_LastSave <= (Timer::cur_time - 300.0)) // every 5 minutes save
|
|
{
|
|
for (auto &entry : _monarchs)
|
|
WalkTreeAndBumpOnlineTime(entry.second, ((int)round(Timer::cur_time - m_LastSave)));
|
|
Save();
|
|
m_LastSave = Timer::cur_time;
|
|
}
|
|
}
|
|
|
|
DEFINE_PACK(AllegianceManager)
|
|
{
|
|
pWriter->Write<DWORD>(1); // version
|
|
pWriter->Write<DWORD>(_monarchs.size());
|
|
|
|
for (auto &entry : _monarchs)
|
|
entry.second->Pack(pWriter);
|
|
|
|
pWriter->Write<DWORD>(_allegInfos.size());
|
|
|
|
for (auto &entry : _allegInfos)
|
|
{
|
|
pWriter->Write<DWORD>(entry.first);
|
|
entry.second->Pack(pWriter);
|
|
}
|
|
}
|
|
|
|
DEFINE_UNPACK(AllegianceManager)
|
|
{
|
|
DWORD version = pReader->Read<DWORD>();
|
|
DWORD numMonarchs = pReader->Read<DWORD>();
|
|
|
|
for (DWORD i = 0; i < numMonarchs; i++)
|
|
{
|
|
AllegianceTreeNode *node = new AllegianceTreeNode();
|
|
node->UnPack(pReader);
|
|
_monarchs[node->_charID] = node;
|
|
CacheInitialDataRecursively(node, NULL);
|
|
}
|
|
|
|
DWORD numInfos = pReader->Read<DWORD>();
|
|
|
|
for (DWORD i = 0; i < numInfos; i++)
|
|
{
|
|
DWORD monarchID = pReader->Read<DWORD>();
|
|
|
|
AllegianceInfo *info = new AllegianceInfo();
|
|
info->UnPack(pReader);
|
|
_allegInfos[monarchID] = info;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void AllegianceManager::CacheInitialDataRecursively(AllegianceTreeNode *node, AllegianceTreeNode *parent)
|
|
{
|
|
if (!node)
|
|
return;
|
|
|
|
_directNodes[node->_charID] = node;
|
|
|
|
node->_monarchID = parent ? parent->_monarchID : node->_charID;
|
|
node->_patronID = parent ? parent->_charID : 0;
|
|
node->_rank = 1;
|
|
node->_numFollowers = 0;
|
|
|
|
unsigned int highestVassalRank = 0;
|
|
bool bRankUp = false;
|
|
|
|
for (auto &entry : node->_vassals)
|
|
{
|
|
AllegianceTreeNode *vassal = entry.second;
|
|
CacheInitialDataRecursively(vassal, node);
|
|
|
|
if (vassal->_rank > highestVassalRank)
|
|
{
|
|
highestVassalRank = vassal->_rank;
|
|
bRankUp = false;
|
|
}
|
|
else if (vassal->_rank == highestVassalRank)
|
|
{
|
|
bRankUp = true;
|
|
}
|
|
|
|
node->_numFollowers += vassal->_numFollowers + 1;
|
|
}
|
|
|
|
if (!highestVassalRank)
|
|
{
|
|
node->_rank = 1;
|
|
}
|
|
else
|
|
{
|
|
node->_rank = highestVassalRank;
|
|
if (bRankUp)
|
|
node->_rank++;
|
|
}
|
|
}
|
|
|
|
void AllegianceManager::CacheDataRecursively(AllegianceTreeNode *node, AllegianceTreeNode *parent)
|
|
{
|
|
if (!node)
|
|
return;
|
|
|
|
node->_monarchID = parent ? parent->_monarchID : node->_charID;
|
|
node->_patronID = parent ? parent->_charID : 0;
|
|
node->_rank = 1;
|
|
node->_numFollowers = 0;
|
|
|
|
unsigned int highestVassalRank = 0;
|
|
bool bRankUp = false;
|
|
|
|
for (auto &entry : node->_vassals)
|
|
{
|
|
AllegianceTreeNode *vassal = entry.second;
|
|
CacheDataRecursively(vassal, node);
|
|
|
|
if (vassal->_rank > highestVassalRank)
|
|
{
|
|
highestVassalRank = vassal->_rank;
|
|
bRankUp = false;
|
|
}
|
|
else if (vassal->_rank == highestVassalRank)
|
|
{
|
|
bRankUp = true;
|
|
}
|
|
|
|
node->_numFollowers += vassal->_numFollowers + 1;
|
|
}
|
|
|
|
if (!highestVassalRank)
|
|
{
|
|
node->_rank = 1;
|
|
}
|
|
else
|
|
{
|
|
node->_rank = highestVassalRank;
|
|
if (bRankUp)
|
|
node->_rank++;
|
|
}
|
|
}
|
|
|
|
|
|
void AllegianceManager::NotifyTreeRefreshRecursively(AllegianceTreeNode *node)
|
|
{
|
|
if (!node)
|
|
return;
|
|
|
|
std::shared_ptr<CWeenieObject> weenie = g_pWorld->FindPlayer(node->_charID);
|
|
if (weenie)
|
|
{
|
|
/*
|
|
unsigned int rank = 0;
|
|
AllegianceProfile *prof = CreateAllegianceProfile(weenie, &rank);
|
|
|
|
BinaryWriter allegianceUpdate;
|
|
allegianceUpdate.Write<DWORD>(0x20);
|
|
allegianceUpdate.Write<DWORD>(rank);
|
|
prof->Pack(&allegianceUpdate);
|
|
weenie->SendNetMessage(&allegianceUpdate, PRIVATE_MSG, TRUE, FALSE);
|
|
delete prof;
|
|
*/
|
|
SetWeenieAllegianceQualities(weenie);
|
|
}
|
|
|
|
for (auto &entry : node->_vassals)
|
|
NotifyTreeRefreshRecursively(entry.second);
|
|
}
|
|
|
|
AllegianceTreeNode *AllegianceManager::GetTreeNode(DWORD charID)
|
|
{
|
|
auto i = _directNodes.find(charID);
|
|
if (i != _directNodes.end())
|
|
return i->second;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
AllegianceInfo *AllegianceManager::GetInfo(DWORD monarchID)
|
|
{
|
|
auto i = _allegInfos.find(monarchID);
|
|
if (i != _allegInfos.end())
|
|
return i->second;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void AllegianceManager::SetWeenieAllegianceQualities(std::shared_ptr<CWeenieObject> weenie)
|
|
{
|
|
if (!weenie)
|
|
return;
|
|
|
|
AllegianceTreeNode *monarch;
|
|
AllegianceTreeNode *patron;
|
|
|
|
AllegianceTreeNode *node = GetTreeNode(weenie->GetID());
|
|
if (node)
|
|
{
|
|
monarch = GetTreeNode(node->_monarchID);
|
|
patron = GetTreeNode(node->_patronID);
|
|
|
|
weenie->m_Qualities.SetInt(ALLEGIANCE_FOLLOWERS_INT, node->_numFollowers);
|
|
weenie->m_Qualities.SetInt(ALLEGIANCE_RANK_INT, node->_rank);
|
|
// wrong weenie->m_Qualities.SetInt(ALLEGIANCE_CP_POOL_INT, node->_cp_cached);
|
|
}
|
|
else
|
|
{
|
|
monarch = NULL;
|
|
patron = NULL;
|
|
|
|
weenie->m_Qualities.RemoveInt(ALLEGIANCE_FOLLOWERS_INT);
|
|
weenie->m_Qualities.RemoveInt(ALLEGIANCE_RANK_INT);
|
|
// wrong weenie->m_Qualities.RemoveInt(ALLEGIANCE_CP_POOL_INT);
|
|
}
|
|
|
|
if (monarch)
|
|
{
|
|
weenie->m_Qualities.SetInstanceID(MONARCH_IID, monarch->_charID);
|
|
weenie->m_Qualities.SetInt(MONARCHS_RANK_INT, monarch->_rank);
|
|
|
|
if (monarch->_charID != weenie->GetID())
|
|
{
|
|
weenie->m_Qualities.SetString(MONARCHS_NAME_STRING, monarch->_charName);
|
|
weenie->m_Qualities.SetString(MONARCHS_TITLE_STRING, monarch->_charName); // TODO prefix
|
|
}
|
|
else
|
|
{
|
|
weenie->m_Qualities.RemoveString(MONARCHS_NAME_STRING);
|
|
weenie->m_Qualities.RemoveString(MONARCHS_TITLE_STRING);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
weenie->m_Qualities.RemoveInstanceID(MONARCH_IID);
|
|
weenie->m_Qualities.RemoveInt(MONARCHS_RANK_INT);
|
|
weenie->m_Qualities.RemoveString(MONARCHS_NAME_STRING);
|
|
weenie->m_Qualities.RemoveString(MONARCHS_TITLE_STRING);
|
|
}
|
|
|
|
if (patron)
|
|
{
|
|
weenie->m_Qualities.SetInstanceID(PATRON_IID, patron->_charID);
|
|
weenie->m_Qualities.SetString(PATRONS_TITLE_STRING, patron->_charName); // TODO prefix
|
|
}
|
|
else
|
|
{
|
|
weenie->m_Qualities.RemoveInstanceID(PATRON_IID);
|
|
weenie->m_Qualities.RemoveString(PATRONS_TITLE_STRING);
|
|
}
|
|
}
|
|
|
|
AllegianceProfile *AllegianceManager::CreateAllegianceProfile(DWORD char_id, unsigned int *pRank)
|
|
{
|
|
AllegianceProfile *prof = new AllegianceProfile;
|
|
*pRank = 0;
|
|
|
|
AllegianceTreeNode *node = GetTreeNode(char_id);
|
|
if (node)
|
|
{
|
|
*pRank = node->_rank;
|
|
|
|
AllegianceTreeNode *monarch = GetTreeNode(node->_monarchID);
|
|
AllegianceTreeNode *patron = GetTreeNode(node->_patronID);
|
|
AllegianceInfo *info = GetInfo(node->_monarchID);
|
|
|
|
RELEASE_ASSERT(monarch);
|
|
if (monarch)
|
|
prof->_total_members = monarch->_numFollowers;
|
|
|
|
prof->_total_vassals = node->_numFollowers;
|
|
|
|
if (!info)
|
|
{
|
|
info = new AllegianceInfo();
|
|
_allegInfos[node->_monarchID] = info;
|
|
}
|
|
|
|
if (info)
|
|
{
|
|
RELEASE_ASSERT(!info->_info._nodes.size());
|
|
prof->_allegiance = info->_info;
|
|
}
|
|
|
|
if (monarch)
|
|
{
|
|
AllegianceNode *patronNode = NULL;
|
|
|
|
if (monarch != node)
|
|
{
|
|
AllegianceNode *monarchNode = new AllegianceNode;
|
|
monarch->FillAllegianceNode(monarchNode);
|
|
|
|
if (g_pWorld->FindPlayer(monarch->_charID))
|
|
monarchNode->_data._bitfield |= LoggedIn_AllegianceIndex;
|
|
|
|
prof->_allegiance._nodes.push_back(monarchNode);
|
|
|
|
patronNode = monarchNode;
|
|
if (patron && patron != monarch)
|
|
{
|
|
patronNode = new AllegianceNode;
|
|
patron->FillAllegianceNode(patronNode);
|
|
patronNode->_patron = monarchNode;
|
|
|
|
if (g_pWorld->FindPlayer(patron->_charID))
|
|
patronNode->_data._bitfield |= LoggedIn_AllegianceIndex;
|
|
|
|
prof->_allegiance._nodes.push_back(patronNode);
|
|
}
|
|
}
|
|
|
|
AllegianceNode *selfNode = new AllegianceNode;
|
|
node->FillAllegianceNode(selfNode);
|
|
selfNode->_patron = patronNode;
|
|
selfNode->_data._bitfield |= LoggedIn_AllegianceIndex;
|
|
prof->_allegiance._nodes.push_back(selfNode);
|
|
|
|
for (auto &entry : node->_vassals)
|
|
{
|
|
AllegianceTreeNode *vassal = entry.second;
|
|
|
|
AllegianceNode *vassalNode = new AllegianceNode;
|
|
vassal->FillAllegianceNode(vassalNode);
|
|
vassalNode->_patron = selfNode;
|
|
|
|
if (g_pWorld->FindPlayer(vassal->_charID))
|
|
vassalNode->_data._bitfield |= LoggedIn_AllegianceIndex;
|
|
|
|
prof->_allegiance._nodes.push_back(vassalNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
return prof;
|
|
}
|
|
|
|
const unsigned int MAX_DIRECT_VASSALS = 11;
|
|
|
|
int AllegianceManager::TrySwearAllegiance(std::shared_ptr<CWeenieObject> source, std::shared_ptr<CWeenieObject> target)
|
|
{
|
|
if (source->IsBusyOrInAction())
|
|
return WERROR_ACTIONS_LOCKED;
|
|
if (target->IsBusyOrInAction())
|
|
return WERROR_ACTIONS_LOCKED;
|
|
|
|
if (source->DistanceTo(target) >= 4.0)
|
|
return WERROR_TOO_FAR;
|
|
|
|
if (std::shared_ptr<CPlayerWeenie> player = target->AsPlayer())
|
|
{
|
|
if (player->GetCharacterOptions() & IgnoreAllegianceRequests_CharacterOption)
|
|
return WERROR_ALLEGIANCE_IGNORING_REQUESTS;
|
|
}
|
|
|
|
if (source->InqIntQuality(LEVEL_INT, 0) > target->InqIntQuality(LEVEL_INT, 0))
|
|
return WERROR_ALLEGIANCE_ILLEGAL_LEVEL;
|
|
|
|
AllegianceTreeNode *selfTreeNode = g_pAllegianceManager->GetTreeNode(source->GetID());
|
|
if (selfTreeNode && selfTreeNode->_patronID)
|
|
{
|
|
// already sworn to someone
|
|
return WERROR_NONE;
|
|
}
|
|
|
|
AllegianceTreeNode *targetTreeNode = g_pAllegianceManager->GetTreeNode(target->GetID());
|
|
if (targetTreeNode)
|
|
{
|
|
if (selfTreeNode && selfTreeNode->_charID == targetTreeNode->_monarchID)
|
|
{
|
|
// Clearly he doesn't have updated data.
|
|
SendAllegianceProfile(source);
|
|
|
|
return WERROR_ALLEGIANCE_ADD_HIERARCHY_FAILURE;
|
|
}
|
|
|
|
if (targetTreeNode->_vassals.size() >= MAX_DIRECT_VASSALS)
|
|
{
|
|
// too many vassals
|
|
return WERROR_NONE;
|
|
}
|
|
}
|
|
|
|
if (!targetTreeNode)
|
|
{
|
|
// target wasn't in an allegiance already
|
|
|
|
// make one...
|
|
targetTreeNode = new AllegianceTreeNode();
|
|
targetTreeNode->_monarchID = target->GetID();
|
|
_directNodes[target->GetID()] = targetTreeNode;
|
|
_monarchs[target->GetID()] = targetTreeNode;
|
|
|
|
AllegianceInfo *info = new AllegianceInfo();
|
|
_allegInfos[target->GetID()] = info;
|
|
}
|
|
targetTreeNode->UpdateWithWeenie(target);
|
|
|
|
if (selfTreeNode)
|
|
{
|
|
// must be a current monarch
|
|
_monarchs.erase(source->GetID());
|
|
|
|
auto i = _allegInfos.find(source->GetID());
|
|
if (i != _allegInfos.end())
|
|
{
|
|
delete i->second;
|
|
_allegInfos.erase(i);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
selfTreeNode = new AllegianceTreeNode();
|
|
_directNodes[source->GetID()] = selfTreeNode;
|
|
}
|
|
|
|
selfTreeNode->UpdateWithWeenie(source);
|
|
selfTreeNode->_rank = 1;
|
|
selfTreeNode->_numFollowers = 0;
|
|
selfTreeNode->_patronID = targetTreeNode->_charID;
|
|
selfTreeNode->_monarchID = targetTreeNode->_monarchID;
|
|
selfTreeNode->_unixTimeSwornAt = time(0);
|
|
selfTreeNode->_ingameSecondsSworn = 1;
|
|
|
|
targetTreeNode->_vassals[selfTreeNode->_charID] = selfTreeNode;
|
|
|
|
// not efficient, can revise later
|
|
RELEASE_ASSERT(_monarchs.find(targetTreeNode->_monarchID) != _monarchs.end());
|
|
|
|
AllegianceTreeNode *monarchTreeNode = _monarchs[targetTreeNode->_monarchID];
|
|
CacheDataRecursively(monarchTreeNode, NULL);
|
|
NotifyTreeRefreshRecursively(monarchTreeNode);
|
|
|
|
source->SendText(csprintf("%s has accepted your oath of Allegiance!", target->GetName().c_str()), LTT_DEFAULT);
|
|
target->SendText(csprintf("%s has sworn Allegiance to you.", source->GetName().c_str()), LTT_DEFAULT);
|
|
|
|
source->DoForcedMotion(Motion_Kneel);
|
|
|
|
Save();
|
|
|
|
return WERROR_NONE;
|
|
}
|
|
|
|
bool AllegianceManager::ShouldRemoveAllegianceNode(AllegianceTreeNode *node)
|
|
{
|
|
if (!node->_patronID && !node->_vassals.size())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void AllegianceManager::RemoveAllegianceNode(AllegianceTreeNode *node)
|
|
{
|
|
_monarchs.erase(node->_charID);
|
|
_directNodes.erase(node->_charID);
|
|
|
|
auto allegInfoEntry = _allegInfos.find(node->_charID);
|
|
if (allegInfoEntry != _allegInfos.end())
|
|
{
|
|
delete allegInfoEntry->second;
|
|
_allegInfos.erase(allegInfoEntry);
|
|
}
|
|
|
|
delete node;
|
|
}
|
|
|
|
void AllegianceManager::BreakAllegiance(AllegianceTreeNode *patronNode, AllegianceTreeNode *vassalNode)
|
|
{
|
|
// the target is a vassal
|
|
patronNode->_vassals.erase(vassalNode->_charID);
|
|
|
|
vassalNode->_monarchID = vassalNode->_charID;
|
|
vassalNode->_patronID = 0;
|
|
|
|
_monarchs[vassalNode->_charID] = vassalNode;
|
|
|
|
AllegianceInfo *info = new AllegianceInfo();
|
|
_allegInfos[vassalNode->_charID] = info;
|
|
|
|
{
|
|
RELEASE_ASSERT(_monarchs.find(patronNode->_monarchID) != _monarchs.end());
|
|
|
|
AllegianceTreeNode *patronMonarchNode = _monarchs[patronNode->_monarchID];
|
|
CacheDataRecursively(patronMonarchNode, NULL);
|
|
NotifyTreeRefreshRecursively(patronMonarchNode);
|
|
|
|
// check if i should even have allegiance data anymore (no vassals/patron)
|
|
if (ShouldRemoveAllegianceNode(patronNode))
|
|
{
|
|
// don't need allegiance data anymore
|
|
RemoveAllegianceNode(patronNode);
|
|
}
|
|
}
|
|
|
|
{
|
|
RELEASE_ASSERT(_monarchs.find(vassalNode->_monarchID) != _monarchs.end());
|
|
|
|
// check if i should even have allegiance data anymore (no vassals/patron)
|
|
AllegianceTreeNode *vassalMonarchNode = _monarchs[vassalNode->_monarchID];
|
|
CacheDataRecursively(vassalMonarchNode, NULL);
|
|
NotifyTreeRefreshRecursively(vassalMonarchNode);
|
|
|
|
// check if i should even have allegiance data anymore (no vassals/patron)
|
|
if (ShouldRemoveAllegianceNode(vassalNode))
|
|
{
|
|
// don't need allegiance data anymore
|
|
RemoveAllegianceNode(vassalNode);
|
|
}
|
|
}
|
|
|
|
Save();
|
|
}
|
|
|
|
int AllegianceManager::TryBreakAllegiance(std::shared_ptr<CWeenieObject> source, DWORD target_id)
|
|
{
|
|
AllegianceTreeNode *selfTreeNode = g_pAllegianceManager->GetTreeNode(source->GetID());
|
|
if (!selfTreeNode)
|
|
{
|
|
// not in an allegiance
|
|
return WERROR_NONE;
|
|
}
|
|
|
|
AllegianceTreeNode *targetTreeNode = g_pAllegianceManager->GetTreeNode(target_id);
|
|
if (!targetTreeNode)
|
|
{
|
|
// target not in an allegiance
|
|
return WERROR_NONE;
|
|
}
|
|
|
|
std::string targetCharName = targetTreeNode->_charName;
|
|
|
|
if (selfTreeNode->_charID == targetTreeNode->_patronID)
|
|
{
|
|
// the target is a vassal
|
|
BreakAllegiance(selfTreeNode, targetTreeNode);
|
|
}
|
|
else if (selfTreeNode->_patronID == target_id)
|
|
{
|
|
// the target is the patron
|
|
BreakAllegiance(targetTreeNode, selfTreeNode);
|
|
}
|
|
else
|
|
{
|
|
return WERROR_NO_OBJECT;
|
|
}
|
|
|
|
source->SendText(csprintf(" You have broken your Allegiance to %s!", targetCharName.c_str()), LTT_DEFAULT);
|
|
|
|
std::shared_ptr<CWeenieObject> target = g_pWorld->FindPlayer(target_id);
|
|
if (target)
|
|
target->SendText(csprintf("%s has broken their Allegiance to you!", source->GetName().c_str()), LTT_DEFAULT);
|
|
|
|
return WERROR_NONE;
|
|
}
|
|
|
|
void AllegianceManager::BreakAllAllegiance(DWORD char_id)
|
|
{
|
|
AllegianceTreeNode *selfTreeNode = g_pAllegianceManager->GetTreeNode(char_id);
|
|
if (!selfTreeNode)
|
|
{
|
|
// not in an allegiance
|
|
return;
|
|
}
|
|
|
|
if (selfTreeNode->_patronID)
|
|
{
|
|
AllegianceTreeNode *patronTreeNode = g_pAllegianceManager->GetTreeNode(selfTreeNode->_patronID);
|
|
if (patronTreeNode)
|
|
{
|
|
BreakAllegiance(patronTreeNode, selfTreeNode);
|
|
}
|
|
}
|
|
|
|
while ((selfTreeNode = g_pAllegianceManager->GetTreeNode(char_id)) && !selfTreeNode->_vassals.empty())
|
|
{
|
|
AllegianceTreeNodeMap::iterator vassalEntry = selfTreeNode->_vassals.begin();
|
|
|
|
if (vassalEntry->second)
|
|
{
|
|
BreakAllegiance(selfTreeNode, vassalEntry->second);
|
|
}
|
|
else
|
|
{
|
|
selfTreeNode->_vassals.erase(vassalEntry);
|
|
}
|
|
}
|
|
|
|
// should not be necessary
|
|
assert(selfTreeNode != g_pAllegianceManager->GetTreeNode(char_id));
|
|
|
|
if (selfTreeNode = g_pAllegianceManager->GetTreeNode(char_id))
|
|
{
|
|
if (ShouldRemoveAllegianceNode(selfTreeNode))
|
|
{
|
|
RemoveAllegianceNode(selfTreeNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AllegianceManager::HandleAllegiancePassup(DWORD source_id, long long amount, bool direct)
|
|
{
|
|
AllegianceTreeNode *node = GetTreeNode(source_id);
|
|
if (!node) // no allegiance
|
|
return;
|
|
|
|
if (!node->_patronID) // no patron (only vassals)
|
|
return;
|
|
|
|
AllegianceTreeNode *patron = GetTreeNode(node->_patronID);
|
|
RELEASE_ASSERT(patron);
|
|
|
|
if (!patron) // shouldn't happen
|
|
return;
|
|
|
|
time_t currentTime = time(0);
|
|
|
|
double realDaysSworn = 0;
|
|
double ingameHoursSworn = 0;
|
|
|
|
double avgRealDaysVassalsSworn = 0;
|
|
double avgIngameHoursVassalsSworn = 0;
|
|
for (auto &entry : patron->_vassals) {
|
|
AllegianceTreeNode *vassal = entry.second;
|
|
double vassalDaysSworn = ((currentTime - vassal->_unixTimeSwornAt) / 86400.0);
|
|
double vassalIngameHours = (vassal->_ingameSecondsSworn / 3600.0);
|
|
if (vassal->_charID == node->_charID) {
|
|
realDaysSworn = vassalDaysSworn;
|
|
ingameHoursSworn = vassalIngameHours;
|
|
}
|
|
avgRealDaysVassalsSworn += vassalDaysSworn;
|
|
avgIngameHoursVassalsSworn += vassalIngameHours;
|
|
}
|
|
|
|
avgRealDaysVassalsSworn /= max(1, patron->_vassals.size());
|
|
avgIngameHoursVassalsSworn /= max(1, patron->_vassals.size());
|
|
|
|
double vassalFactor = min(1.0, max(0.0, 0.25 * patron->_vassals.size()));
|
|
double realDaysSwornFactor = min(realDaysSworn, 730.0) / 730.0;
|
|
double ingameHoursSwornFactor = min(ingameHoursSworn, 720.0) / 720.0;
|
|
double avgRealDaysVassalsSwornFactor = min(avgRealDaysVassalsSworn, 730.0) / 730.0;
|
|
double avgIngameHoursVassalsSwornFactor = min(avgIngameHoursVassalsSworn, 720.0) / 720.0;
|
|
double loyaltyFactor = min(node->_loyalty, 291.0) / 291.0;
|
|
double leadershipFactor = min(patron->_leadership, 291.0) / 291.0;
|
|
|
|
double factor1 = direct ? 50.0 : 16.0;
|
|
double factor2 = direct ? 22.5 : 8.0;
|
|
|
|
double generatedPercent = 0.01 * (factor1 + factor2 * loyaltyFactor * (1.0 + realDaysSwornFactor * ingameHoursSwornFactor));
|
|
double receivedPercent = 0.01 * (factor1 + factor2 * leadershipFactor * (1.0 + vassalFactor * avgRealDaysVassalsSwornFactor * avgIngameHoursVassalsSwornFactor));
|
|
|
|
double passup = generatedPercent * receivedPercent;
|
|
|
|
unsigned long long passupAmount = amount * passup;
|
|
|
|
if (passup > 0)
|
|
{
|
|
node->_cp_tithed += passupAmount;
|
|
patron->_cp_cached += passupAmount;
|
|
patron->_cp_pool_to_unload = min(4294967295ull, patron->_cp_pool_to_unload + passupAmount);
|
|
|
|
std::shared_ptr<CWeenieObject> patron_weenie = g_pWorld->FindPlayer(patron->_charID);
|
|
if (patron_weenie)
|
|
patron_weenie->TryToUnloadAllegianceXP(false);
|
|
|
|
HandleAllegiancePassup(patron->_charID, passupAmount, false);
|
|
}
|
|
}
|
|
|
|
void AllegianceManager::ChatMonarch(DWORD sender_id, const char *text)
|
|
{
|
|
AllegianceTreeNode *node = GetTreeNode(sender_id);
|
|
if (!node || !node->_monarchID || node->_monarchID == sender_id) // no allegiance
|
|
return;
|
|
|
|
std::shared_ptr<CWeenieObject> sender_weenie = g_pWorld->FindPlayer(sender_id);
|
|
if (!sender_weenie)
|
|
return;
|
|
|
|
std::shared_ptr<CWeenieObject> target = g_pWorld->FindPlayer(node->_monarchID);
|
|
if (!target)
|
|
return;
|
|
|
|
sender_weenie->SendNetMessage(ServerText(csprintf("You say to your Monarch, \"%s\"", text), LTT_SPEECH_DIRECT_SEND), PRIVATE_MSG, FALSE, TRUE);
|
|
target->SendNetMessage(ServerText(csprintf("Your follower %s says to you, \"%s\"", sender_weenie->GetName().c_str(), text), LTT_SPEECH_DIRECT), PRIVATE_MSG, FALSE, TRUE);
|
|
}
|
|
|
|
void AllegianceManager::ChatPatron(DWORD sender_id, const char *text)
|
|
{
|
|
AllegianceTreeNode *node = GetTreeNode(sender_id);
|
|
if (!node || !node->_patronID) // no allegiance
|
|
return;
|
|
|
|
std::shared_ptr<CWeenieObject> sender_weenie = g_pWorld->FindPlayer(sender_id);
|
|
if (!sender_weenie)
|
|
return;
|
|
|
|
std::shared_ptr<CWeenieObject> target = g_pWorld->FindPlayer(node->_patronID);
|
|
if (!target)
|
|
return;
|
|
|
|
sender_weenie->SendNetMessage(ServerText(csprintf("You say to your Patron, \"%s\"", text), LTT_SPEECH_DIRECT_SEND), PRIVATE_MSG, FALSE, TRUE);
|
|
target->SendNetMessage(ServerText(csprintf("Your vassal %s says to you, \"%s\"", sender_weenie->GetName().c_str(), text), LTT_SPEECH_DIRECT), PRIVATE_MSG, FALSE, TRUE);
|
|
}
|
|
|
|
void AllegianceManager::ChatVassals(DWORD sender_id, const char *text)
|
|
{
|
|
AllegianceTreeNode *node = GetTreeNode(sender_id);
|
|
if (!node) // no allegiance
|
|
return;
|
|
|
|
std::shared_ptr<CWeenieObject> sender_weenie = g_pWorld->FindPlayer(sender_id);
|
|
if (!sender_weenie)
|
|
return;
|
|
|
|
sender_weenie->SendNetMessage(ServerText(csprintf("You say to your Vassals, \"%s\"", text), LTT_SPEECH_DIRECT_SEND), PRIVATE_MSG, FALSE, TRUE);
|
|
|
|
for (auto &entry : node->_vassals)
|
|
{
|
|
std::shared_ptr<CWeenieObject> target = g_pWorld->FindPlayer(entry.second->_charID);
|
|
if (!target)
|
|
continue;
|
|
|
|
target->SendNetMessage(ServerText(csprintf("Your patron %s says to you, \"%s\"", sender_weenie->GetName().c_str(), text), LTT_SPEECH_DIRECT), PRIVATE_MSG, FALSE, TRUE);
|
|
}
|
|
}
|
|
|
|
void AllegianceManager::ChatCovassals(DWORD sender_id, const char *text)
|
|
{
|
|
AllegianceTreeNode *node = GetTreeNode(sender_id);
|
|
if (!node || !node->_patronID) // no allegiance
|
|
return;
|
|
|
|
AllegianceTreeNode *patron_node = GetTreeNode(node->_patronID);
|
|
if (!patron_node) // no patron
|
|
return;
|
|
|
|
std::shared_ptr<CWeenieObject> sender_weenie = g_pWorld->FindPlayer(sender_id);
|
|
if (!sender_weenie)
|
|
return;
|
|
|
|
sender_weenie->SendNetMessage(ServerText(csprintf("[Co-Vassals] You Say, \"%s\"", text), LTT_SPEECH_DIRECT_SEND), PRIVATE_MSG, FALSE, TRUE);
|
|
|
|
for (auto &entry : patron_node->_vassals)
|
|
{
|
|
if (entry.second->_charID == sender_id)
|
|
continue;
|
|
|
|
std::shared_ptr<CWeenieObject> target = g_pWorld->FindPlayer(entry.second->_charID);
|
|
if (target)
|
|
target->SendNetMessage(ServerText(csprintf("[Co-Vassals] %s says, \"%s\"", sender_weenie->GetName().c_str(), text), LTT_SPEECH_DIRECT), PRIVATE_MSG, FALSE, TRUE);
|
|
}
|
|
|
|
std::shared_ptr<CWeenieObject> patron_weenie = g_pWorld->FindPlayer(node->_patronID);
|
|
if (patron_weenie)
|
|
patron_weenie->SendNetMessage(ServerText(csprintf("[Co-Vassals] %s says, \"%s\"", sender_weenie->GetName().c_str(), text), LTT_SPEECH_DIRECT), PRIVATE_MSG, FALSE, TRUE);
|
|
|
|
}
|
|
|
|
void AllegianceManager::SendAllegianceProfile(std::shared_ptr<CWeenieObject> pPlayer)
|
|
{
|
|
unsigned int rank = 0;
|
|
AllegianceProfile *prof = g_pAllegianceManager->CreateAllegianceProfile(pPlayer->GetID(), &rank);
|
|
|
|
BinaryWriter allegianceUpdate;
|
|
allegianceUpdate.Write<DWORD>(0x20);
|
|
allegianceUpdate.Write<DWORD>(rank);
|
|
prof->Pack(&allegianceUpdate);
|
|
pPlayer->SendNetMessage(&allegianceUpdate, PRIVATE_MSG, TRUE, FALSE);
|
|
|
|
BinaryWriter allegianceUpdateDone;
|
|
allegianceUpdateDone.Write<DWORD>(0x1C8);
|
|
allegianceUpdateDone.Write<DWORD>(0);
|
|
pPlayer->SendNetMessage(&allegianceUpdateDone, PRIVATE_MSG, TRUE, FALSE);
|
|
|
|
delete prof;
|
|
}
|
|
|
|
DWORD AllegianceManager::GetCachedMonarchIDForPlayer(std::shared_ptr<CPlayerWeenie> player)
|
|
{
|
|
// this data may not be trustworthy, should use tree node for anything important
|
|
return player->InqIIDQuality(MONARCH_IID, 0);
|
|
} |