mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2024-11-13 19:58:21 +00:00
455f9470a5
* Move EntityManager to Game namespace * move initialization to later Need to wait for dZoneManager to be initialized. * Fix bugs - Cannot delete from a RandomAccessIterator while in a range based for loop. Touchup zone manager initialize replace magic numbers with better named constants replace magic zonecontrol id with a more readable hex alternative condense stack variables move initializers closer to their use initialize entity manager with zone control change initialize timings If zone is not zero we expect to initialize the entity manager during zone manager initialization Add constexpr for zone control LOT * Add proper error handling * revert vanity changes * Update WorldServer.cpp * Update dZoneManager.cpp
647 lines
19 KiB
C++
647 lines
19 KiB
C++
#include "Character.h"
|
|
#include "User.h"
|
|
#include "Database.h"
|
|
#include "GeneralUtils.h"
|
|
#include "dLogger.h"
|
|
#include "BitStream.h"
|
|
#include "Game.h"
|
|
#include <chrono>
|
|
#include "Entity.h"
|
|
#include "EntityManager.h"
|
|
#include "GameMessages.h"
|
|
#include "MissionComponent.h"
|
|
#include "dZoneManager.h"
|
|
#include "dServer.h"
|
|
#include "Zone.h"
|
|
#include "ChatPackets.h"
|
|
#include "Inventory.h"
|
|
#include "InventoryComponent.h"
|
|
#include "eMissionTaskType.h"
|
|
#include "eMissionState.h"
|
|
#include "eObjectBits.h"
|
|
#include "eGameMasterLevel.h"
|
|
#include "ePlayerFlag.h"
|
|
|
|
Character::Character(uint32_t id, User* parentUser) {
|
|
//First load the name, etc:
|
|
m_ID = id;
|
|
|
|
sql::PreparedStatement* stmt = Database::CreatePreppedStmt(
|
|
"SELECT name, pending_name, needs_rename, prop_clone_id, permission_map FROM charinfo WHERE id=? LIMIT 1;"
|
|
);
|
|
|
|
stmt->setInt64(1, id);
|
|
|
|
sql::ResultSet* res = stmt->executeQuery();
|
|
|
|
while (res->next()) {
|
|
m_Name = res->getString(1).c_str();
|
|
m_UnapprovedName = res->getString(2).c_str();
|
|
m_NameRejected = res->getBoolean(3);
|
|
m_PropertyCloneID = res->getUInt(4);
|
|
m_PermissionMap = static_cast<ePermissionMap>(res->getUInt64(5));
|
|
}
|
|
|
|
delete res;
|
|
delete stmt;
|
|
|
|
//Load the xmlData now:
|
|
sql::PreparedStatement* xmlStmt = Database::CreatePreppedStmt(
|
|
"SELECT xml_data FROM charxml WHERE id=? LIMIT 1;"
|
|
);
|
|
|
|
xmlStmt->setInt64(1, id);
|
|
|
|
sql::ResultSet* xmlRes = xmlStmt->executeQuery();
|
|
while (xmlRes->next()) {
|
|
m_XMLData = xmlRes->getString(1).c_str();
|
|
}
|
|
|
|
delete xmlRes;
|
|
delete xmlStmt;
|
|
|
|
m_ZoneID = 0; //TEMP! Set back to 0 when done. This is so we can see loading screen progress for testing.
|
|
m_ZoneInstanceID = 0; //These values don't really matter, these are only used on the char select screen and seem unused.
|
|
m_ZoneCloneID = 0;
|
|
|
|
m_Doc = nullptr;
|
|
|
|
//Quickly and dirtly parse the xmlData to get the info we need:
|
|
DoQuickXMLDataParse();
|
|
|
|
//Set our objectID:
|
|
m_ObjectID = m_ID;
|
|
GeneralUtils::SetBit(m_ObjectID, eObjectBits::CHARACTER);
|
|
GeneralUtils::SetBit(m_ObjectID, eObjectBits::PERSISTENT);
|
|
|
|
m_ParentUser = parentUser;
|
|
m_OurEntity = nullptr;
|
|
m_BuildMode = false;
|
|
}
|
|
|
|
Character::~Character() {
|
|
delete m_Doc;
|
|
m_Doc = nullptr;
|
|
}
|
|
|
|
void Character::UpdateFromDatabase() {
|
|
sql::PreparedStatement* stmt = Database::CreatePreppedStmt(
|
|
"SELECT name, pending_name, needs_rename, prop_clone_id, permission_map FROM charinfo WHERE id=? LIMIT 1;"
|
|
);
|
|
|
|
stmt->setInt64(1, m_ID);
|
|
|
|
sql::ResultSet* res = stmt->executeQuery();
|
|
|
|
while (res->next()) {
|
|
m_Name = res->getString(1).c_str();
|
|
m_UnapprovedName = res->getString(2).c_str();
|
|
m_NameRejected = res->getBoolean(3);
|
|
m_PropertyCloneID = res->getUInt(4);
|
|
m_PermissionMap = static_cast<ePermissionMap>(res->getUInt64(5));
|
|
}
|
|
|
|
delete res;
|
|
delete stmt;
|
|
|
|
//Load the xmlData now:
|
|
sql::PreparedStatement* xmlStmt = Database::CreatePreppedStmt(
|
|
"SELECT xml_data FROM charxml WHERE id=? LIMIT 1;"
|
|
);
|
|
xmlStmt->setInt64(1, m_ID);
|
|
|
|
sql::ResultSet* xmlRes = xmlStmt->executeQuery();
|
|
while (xmlRes->next()) {
|
|
m_XMLData = xmlRes->getString(1).c_str();
|
|
}
|
|
|
|
delete xmlRes;
|
|
delete xmlStmt;
|
|
|
|
m_ZoneID = 0; //TEMP! Set back to 0 when done. This is so we can see loading screen progress for testing.
|
|
m_ZoneInstanceID = 0; //These values don't really matter, these are only used on the char select screen and seem unused.
|
|
m_ZoneCloneID = 0;
|
|
|
|
delete m_Doc;
|
|
m_Doc = nullptr;
|
|
|
|
//Quickly and dirtly parse the xmlData to get the info we need:
|
|
DoQuickXMLDataParse();
|
|
|
|
//Set our objectID:
|
|
m_ObjectID = m_ID;
|
|
GeneralUtils::SetBit(m_ObjectID, eObjectBits::CHARACTER);
|
|
GeneralUtils::SetBit(m_ObjectID, eObjectBits::PERSISTENT);
|
|
|
|
m_OurEntity = nullptr;
|
|
m_BuildMode = false;
|
|
}
|
|
|
|
void Character::DoQuickXMLDataParse() {
|
|
if (m_XMLData.size() == 0) return;
|
|
|
|
delete m_Doc;
|
|
m_Doc = new tinyxml2::XMLDocument();
|
|
if (!m_Doc) return;
|
|
|
|
if (m_Doc->Parse(m_XMLData.c_str(), m_XMLData.size()) == 0) {
|
|
Game::logger->Log("Character", "Loaded xmlData for character %s (%i)!", m_Name.c_str(), m_ID);
|
|
} else {
|
|
Game::logger->Log("Character", "Failed to load xmlData!");
|
|
//Server::rakServer->CloseConnection(m_ParentUser->GetSystemAddress(), true);
|
|
return;
|
|
}
|
|
|
|
tinyxml2::XMLElement* mf = m_Doc->FirstChildElement("obj")->FirstChildElement("mf");
|
|
if (!mf) {
|
|
Game::logger->Log("Character", "Failed to find mf tag!");
|
|
return;
|
|
}
|
|
|
|
mf->QueryAttribute("hc", &m_HairColor);
|
|
mf->QueryAttribute("hs", &m_HairStyle);
|
|
|
|
mf->QueryAttribute("t", &m_ShirtColor);
|
|
mf->QueryAttribute("l", &m_PantsColor);
|
|
|
|
mf->QueryAttribute("lh", &m_LeftHand);
|
|
mf->QueryAttribute("rh", &m_RightHand);
|
|
|
|
mf->QueryAttribute("es", &m_Eyebrows);
|
|
mf->QueryAttribute("ess", &m_Eyes);
|
|
mf->QueryAttribute("ms", &m_Mouth);
|
|
|
|
tinyxml2::XMLElement* inv = m_Doc->FirstChildElement("obj")->FirstChildElement("inv");
|
|
if (!inv) {
|
|
Game::logger->Log("Character", "Char has no inv!");
|
|
return;
|
|
}
|
|
|
|
tinyxml2::XMLElement* bag = inv->FirstChildElement("items")->FirstChildElement("in");
|
|
|
|
if (!bag) {
|
|
Game::logger->Log("Character", "Couldn't find bag0!");
|
|
return;
|
|
}
|
|
|
|
while (bag != nullptr) {
|
|
auto* sib = bag->FirstChildElement();
|
|
|
|
while (sib != nullptr) {
|
|
bool eq = false;
|
|
sib->QueryAttribute("eq", &eq);
|
|
LOT lot = 0;
|
|
sib->QueryAttribute("l", &lot);
|
|
|
|
if (eq) {
|
|
if (lot != 0) m_EquippedItems.push_back(lot);
|
|
}
|
|
|
|
sib = sib->NextSiblingElement();
|
|
}
|
|
|
|
bag = bag->NextSiblingElement();
|
|
}
|
|
|
|
|
|
tinyxml2::XMLElement* character = m_Doc->FirstChildElement("obj")->FirstChildElement("char");
|
|
if (character) {
|
|
character->QueryAttribute("cc", &m_Coins);
|
|
int32_t gm_level = 0;
|
|
character->QueryAttribute("gm", &gm_level);
|
|
m_GMLevel = static_cast<eGameMasterLevel>(gm_level);
|
|
|
|
uint64_t lzidConcat = 0;
|
|
if (character->FindAttribute("lzid")) {
|
|
character->QueryAttribute("lzid", &lzidConcat);
|
|
m_ZoneID = lzidConcat & ((1 << 16) - 1);
|
|
m_ZoneInstanceID = (lzidConcat >> 16) & ((1 << 16) - 1);
|
|
m_ZoneCloneID = (lzidConcat >> 32) & ((1 << 30) - 1);
|
|
}
|
|
|
|
//Darwin's backup
|
|
if (character->FindAttribute("lwid")) {
|
|
uint32_t worldID = 0;
|
|
character->QueryAttribute("lwid", &worldID);
|
|
m_ZoneID = worldID;
|
|
}
|
|
|
|
if (character->FindAttribute("lnzid")) {
|
|
uint32_t lastNonInstanceZoneID = 0;
|
|
character->QueryAttribute("lnzid", &lastNonInstanceZoneID);
|
|
m_LastNonInstanceZoneID = lastNonInstanceZoneID;
|
|
}
|
|
|
|
if (character->FindAttribute("tscene")) {
|
|
const char* tscene = nullptr;
|
|
character->QueryStringAttribute("tscene", &tscene);
|
|
m_TargetScene = std::string(tscene);
|
|
}
|
|
|
|
//To try and fix the AG landing into:
|
|
if (m_ZoneID == 1000 && Game::server->GetZoneID() == 1100) {
|
|
//sneakily insert our position:
|
|
auto pos = dZoneManager::Instance()->GetZone()->GetSpawnPos();
|
|
character->SetAttribute("lzx", pos.x);
|
|
character->SetAttribute("lzy", pos.y);
|
|
character->SetAttribute("lzz", pos.z);
|
|
}
|
|
|
|
auto emotes = character->FirstChildElement("ue");
|
|
if (emotes) {
|
|
auto currentChild = emotes->FirstChildElement();
|
|
|
|
while (currentChild) {
|
|
int emoteID;
|
|
currentChild->QueryAttribute("id", &emoteID);
|
|
m_UnlockedEmotes.push_back(emoteID);
|
|
currentChild = currentChild->NextSiblingElement();
|
|
}
|
|
}
|
|
|
|
character->QueryAttribute("lzx", &m_OriginalPosition.x);
|
|
character->QueryAttribute("lzy", &m_OriginalPosition.y);
|
|
character->QueryAttribute("lzz", &m_OriginalPosition.z);
|
|
character->QueryAttribute("lzrx", &m_OriginalRotation.x);
|
|
character->QueryAttribute("lzry", &m_OriginalRotation.y);
|
|
character->QueryAttribute("lzrz", &m_OriginalRotation.z);
|
|
character->QueryAttribute("lzrw", &m_OriginalRotation.w);
|
|
}
|
|
|
|
auto* flags = m_Doc->FirstChildElement("obj")->FirstChildElement("flag");
|
|
if (flags) {
|
|
auto* currentChild = flags->FirstChildElement();
|
|
while (currentChild) {
|
|
const auto* temp = currentChild->Attribute("v");
|
|
const auto* id = currentChild->Attribute("id");
|
|
if (temp && id) {
|
|
uint32_t index = 0;
|
|
uint64_t value = 0;
|
|
|
|
index = std::stoul(id);
|
|
value = std::stoull(temp);
|
|
|
|
m_PlayerFlags.insert(std::make_pair(index, value));
|
|
}
|
|
currentChild = currentChild->NextSiblingElement();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Character::UnlockEmote(int emoteID) {
|
|
m_UnlockedEmotes.push_back(emoteID);
|
|
GameMessages::SendSetEmoteLockState(Game::entityManager->GetEntity(m_ObjectID), false, emoteID);
|
|
}
|
|
|
|
void Character::SetBuildMode(bool buildMode) {
|
|
m_BuildMode = buildMode;
|
|
|
|
auto* controller = dZoneManager::Instance()->GetZoneControlObject();
|
|
|
|
controller->OnFireEventServerSide(m_OurEntity, buildMode ? "OnBuildModeEnter" : "OnBuildModeLeave");
|
|
}
|
|
|
|
void Character::SaveXMLToDatabase() {
|
|
if (!m_Doc) return;
|
|
|
|
//For metrics, we'll record the time it took to save:
|
|
auto start = std::chrono::system_clock::now();
|
|
|
|
tinyxml2::XMLElement* character = m_Doc->FirstChildElement("obj")->FirstChildElement("char");
|
|
if (character) {
|
|
character->SetAttribute("gm", static_cast<uint32_t>(m_GMLevel));
|
|
character->SetAttribute("cc", m_Coins);
|
|
|
|
auto zoneInfo = dZoneManager::Instance()->GetZone()->GetZoneID();
|
|
// lzid garbage, binary concat of zoneID, zoneInstance and zoneClone
|
|
if (zoneInfo.GetMapID() != 0 && zoneInfo.GetCloneID() == 0) {
|
|
uint64_t lzidConcat = zoneInfo.GetCloneID();
|
|
lzidConcat = (lzidConcat << 16) | uint16_t(zoneInfo.GetInstanceID());
|
|
lzidConcat = (lzidConcat << 16) | uint16_t(zoneInfo.GetMapID());
|
|
character->SetAttribute("lzid", lzidConcat);
|
|
character->SetAttribute("lnzid", GetLastNonInstanceZoneID());
|
|
|
|
//Darwin's backup:
|
|
character->SetAttribute("lwid", Game::server->GetZoneID());
|
|
|
|
// Set the target scene, custom attribute
|
|
character->SetAttribute("tscene", m_TargetScene.c_str());
|
|
}
|
|
|
|
auto emotes = character->FirstChildElement("ue");
|
|
if (!emotes) emotes = m_Doc->NewElement("ue");
|
|
|
|
emotes->DeleteChildren();
|
|
for (int emoteID : m_UnlockedEmotes) {
|
|
auto emote = m_Doc->NewElement("e");
|
|
emote->SetAttribute("id", emoteID);
|
|
|
|
emotes->LinkEndChild(emote);
|
|
}
|
|
|
|
character->LinkEndChild(emotes);
|
|
}
|
|
|
|
//Export our flags:
|
|
auto* flags = m_Doc->FirstChildElement("obj")->FirstChildElement("flag");
|
|
if (!flags) {
|
|
flags = m_Doc->NewElement("flag"); //Create a flags tag if we don't have one
|
|
m_Doc->FirstChildElement("obj")->LinkEndChild(flags); //Link it to the obj tag so we can find next time
|
|
}
|
|
|
|
flags->DeleteChildren(); //Clear it if we have anything, so that we can fill it up again without dupes
|
|
for (std::pair<uint32_t, uint64_t> flag : m_PlayerFlags) {
|
|
auto* f = m_Doc->NewElement("f");
|
|
f->SetAttribute("id", flag.first);
|
|
|
|
//Because of the joy that is tinyxml2, it doesn't offer a function to set a uint64 as an attribute.
|
|
//Only signed 64-bits ints would work.
|
|
std::string v = std::to_string(flag.second);
|
|
f->SetAttribute("v", v.c_str());
|
|
|
|
flags->LinkEndChild(f);
|
|
}
|
|
|
|
// Prevents the news feed from showing up on world transfers
|
|
if (GetPlayerFlag(ePlayerFlag::IS_NEWS_SCREEN_VISIBLE)) {
|
|
auto* s = m_Doc->NewElement("s");
|
|
s->SetAttribute("si", ePlayerFlag::IS_NEWS_SCREEN_VISIBLE);
|
|
flags->LinkEndChild(s);
|
|
}
|
|
|
|
SaveXmlRespawnCheckpoints();
|
|
|
|
//Call upon the entity to update our xmlDoc:
|
|
if (!m_OurEntity) {
|
|
Game::logger->Log("Character", "%i:%s didn't have an entity set while saving! CHARACTER WILL NOT BE SAVED!", this->GetID(), this->GetName().c_str());
|
|
return;
|
|
}
|
|
|
|
m_OurEntity->UpdateXMLDoc(m_Doc);
|
|
|
|
WriteToDatabase();
|
|
|
|
//For metrics, log the time it took to save:
|
|
auto end = std::chrono::system_clock::now();
|
|
std::chrono::duration<double> elapsed = end - start;
|
|
Game::logger->Log("Character", "%i:%s Saved character to Database in: %fs", this->GetID(), this->GetName().c_str(), elapsed.count());
|
|
}
|
|
|
|
void Character::SetIsNewLogin() {
|
|
// If we dont have a flag element, then we cannot have a s element as a child of flag.
|
|
auto* flags = m_Doc->FirstChildElement("obj")->FirstChildElement("flag");
|
|
if (!flags) return;
|
|
|
|
auto* currentChild = flags->FirstChildElement();
|
|
while (currentChild) {
|
|
if (currentChild->Attribute("si")) {
|
|
flags->DeleteChild(currentChild);
|
|
Game::logger->Log("Character", "Removed isLoggedIn flag from character %i:%s, saving character to database", GetID(), GetName().c_str());
|
|
WriteToDatabase();
|
|
}
|
|
currentChild = currentChild->NextSiblingElement();
|
|
}
|
|
}
|
|
|
|
void Character::WriteToDatabase() {
|
|
//Dump our xml into m_XMLData:
|
|
auto* printer = new tinyxml2::XMLPrinter(0, true, 0);
|
|
m_Doc->Print(printer);
|
|
m_XMLData = printer->CStr();
|
|
|
|
//Finally, save to db:
|
|
sql::PreparedStatement* stmt = Database::CreatePreppedStmt("UPDATE charxml SET xml_data=? WHERE id=?");
|
|
stmt->setString(1, m_XMLData.c_str());
|
|
stmt->setUInt(2, m_ID);
|
|
stmt->execute();
|
|
delete stmt;
|
|
delete printer;
|
|
}
|
|
|
|
void Character::SetPlayerFlag(const uint32_t flagId, const bool value) {
|
|
// If the flag is already set, we don't have to recalculate it
|
|
if (GetPlayerFlag(flagId) == value) return;
|
|
|
|
if (value) {
|
|
// Update the mission component:
|
|
auto* player = Game::entityManager->GetEntity(m_ObjectID);
|
|
|
|
if (player != nullptr) {
|
|
auto* missionComponent = player->GetComponent<MissionComponent>();
|
|
|
|
if (missionComponent != nullptr) {
|
|
missionComponent->Progress(eMissionTaskType::PLAYER_FLAG, flagId);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calculate the index first
|
|
auto flagIndex = uint32_t(std::floor(flagId / 64));
|
|
|
|
const auto shiftedValue = 1ULL << flagId % 64;
|
|
|
|
auto it = m_PlayerFlags.find(flagIndex);
|
|
|
|
// Check if flag index exists
|
|
if (it != m_PlayerFlags.end()) {
|
|
// Update the value
|
|
if (value) {
|
|
it->second |= shiftedValue;
|
|
} else {
|
|
it->second &= ~shiftedValue;
|
|
}
|
|
} else {
|
|
if (value) {
|
|
// Otherwise, insert the value
|
|
uint64_t flagValue = 0;
|
|
|
|
flagValue |= shiftedValue;
|
|
|
|
m_PlayerFlags.insert(std::make_pair(flagIndex, flagValue));
|
|
}
|
|
}
|
|
|
|
// Notify the client that a flag has changed server-side
|
|
GameMessages::SendNotifyClientFlagChange(m_ObjectID, flagId, value, m_ParentUser->GetSystemAddress());
|
|
}
|
|
|
|
bool Character::GetPlayerFlag(const uint32_t flagId) const {
|
|
// Calculate the index first
|
|
const auto flagIndex = uint32_t(std::floor(flagId / 64));
|
|
|
|
const auto shiftedValue = 1ULL << flagId % 64;
|
|
|
|
auto it = m_PlayerFlags.find(flagIndex);
|
|
if (it != m_PlayerFlags.end()) {
|
|
// Don't set the data if we don't have to
|
|
return (it->second & shiftedValue) != 0;
|
|
}
|
|
|
|
return false; //by def, return false.
|
|
}
|
|
|
|
void Character::SetRetroactiveFlags() {
|
|
// Retroactive check for if player has joined a faction to set their 'joined a faction' flag to true.
|
|
if (GetPlayerFlag(ePlayerFlag::VENTURE_FACTION) || GetPlayerFlag(ePlayerFlag::ASSEMBLY_FACTION) || GetPlayerFlag(ePlayerFlag::PARADOX_FACTION) || GetPlayerFlag(ePlayerFlag::SENTINEL_FACTION)) {
|
|
SetPlayerFlag(ePlayerFlag::JOINED_A_FACTION, true);
|
|
}
|
|
}
|
|
|
|
void Character::SaveXmlRespawnCheckpoints() {
|
|
//Export our respawn points:
|
|
auto* points = m_Doc->FirstChildElement("obj")->FirstChildElement("res");
|
|
if (!points) {
|
|
points = m_Doc->NewElement("res");
|
|
m_Doc->FirstChildElement("obj")->LinkEndChild(points);
|
|
}
|
|
|
|
points->DeleteChildren();
|
|
for (const auto& point : m_WorldRespawnCheckpoints) {
|
|
auto* r = m_Doc->NewElement("r");
|
|
r->SetAttribute("w", point.first);
|
|
|
|
r->SetAttribute("x", point.second.x);
|
|
r->SetAttribute("y", point.second.y);
|
|
r->SetAttribute("z", point.second.z);
|
|
|
|
points->LinkEndChild(r);
|
|
}
|
|
}
|
|
|
|
void Character::LoadXmlRespawnCheckpoints() {
|
|
m_WorldRespawnCheckpoints.clear();
|
|
|
|
auto* points = m_Doc->FirstChildElement("obj")->FirstChildElement("res");
|
|
if (!points) {
|
|
return;
|
|
}
|
|
|
|
auto* r = points->FirstChildElement("r");
|
|
while (r != nullptr) {
|
|
int32_t map = 0;
|
|
NiPoint3 point = NiPoint3::ZERO;
|
|
|
|
r->QueryAttribute("w", &map);
|
|
r->QueryAttribute("x", &point.x);
|
|
r->QueryAttribute("y", &point.y);
|
|
r->QueryAttribute("z", &point.z);
|
|
|
|
r = r->NextSiblingElement("r");
|
|
|
|
m_WorldRespawnCheckpoints[map] = point;
|
|
}
|
|
|
|
}
|
|
|
|
void Character::OnZoneLoad() {
|
|
if (m_OurEntity == nullptr) {
|
|
return;
|
|
}
|
|
|
|
auto* missionComponent = m_OurEntity->GetComponent<MissionComponent>();
|
|
|
|
if (missionComponent != nullptr) {
|
|
// Fix the monument race flag
|
|
if (missionComponent->GetMissionState(319) >= eMissionState::READY_TO_COMPLETE) {
|
|
SetPlayerFlag(ePlayerFlag::AG_FINISH_LINE_BUILT, true);
|
|
}
|
|
}
|
|
|
|
const auto maxGMLevel = m_ParentUser->GetMaxGMLevel();
|
|
|
|
// This does not apply to the GMs
|
|
if (maxGMLevel > eGameMasterLevel::CIVILIAN) {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Restrict old character to 1 million coins
|
|
*/
|
|
if (HasPermission(ePermissionMap::Old)) {
|
|
if (GetCoins() > 1000000) {
|
|
SetCoins(1000000, eLootSourceType::NONE);
|
|
}
|
|
}
|
|
|
|
auto* inventoryComponent = m_OurEntity->GetComponent<InventoryComponent>();
|
|
|
|
if (inventoryComponent == nullptr) {
|
|
return;
|
|
}
|
|
|
|
// Remove all GM items
|
|
for (const auto lot : Inventory::GetAllGMItems()) {
|
|
inventoryComponent->RemoveItem(lot, inventoryComponent->GetLotCount(lot));
|
|
}
|
|
}
|
|
|
|
ePermissionMap Character::GetPermissionMap() const {
|
|
return m_PermissionMap;
|
|
}
|
|
|
|
bool Character::HasPermission(ePermissionMap permission) const {
|
|
return (static_cast<uint64_t>(m_PermissionMap) & static_cast<uint64_t>(permission)) != 0;
|
|
}
|
|
|
|
void Character::SetRespawnPoint(LWOMAPID map, const NiPoint3& point) {
|
|
m_WorldRespawnCheckpoints[map] = point;
|
|
}
|
|
|
|
const NiPoint3& Character::GetRespawnPoint(LWOMAPID map) const {
|
|
const auto& pair = m_WorldRespawnCheckpoints.find(map);
|
|
|
|
if (pair == m_WorldRespawnCheckpoints.end()) return NiPoint3::ZERO;
|
|
|
|
return pair->second;
|
|
}
|
|
|
|
void Character::SetCoins(int64_t newCoins, eLootSourceType lootSource) {
|
|
if (newCoins < 0) {
|
|
newCoins = 0;
|
|
}
|
|
|
|
m_Coins = newCoins;
|
|
|
|
GameMessages::SendSetCurrency(Game::entityManager->GetEntity(m_ObjectID), m_Coins, 0, 0, 0, 0, true, lootSource);
|
|
}
|
|
|
|
bool Character::HasBeenToWorld(LWOMAPID mapID) const {
|
|
return m_WorldRespawnCheckpoints.find(mapID) != m_WorldRespawnCheckpoints.end();
|
|
}
|
|
|
|
void Character::SendMuteNotice() const {
|
|
if (!m_ParentUser->GetIsMuted()) return;
|
|
|
|
time_t expire = m_ParentUser->GetMuteExpire();
|
|
|
|
char buffer[32] = "brought up for review.\0";
|
|
|
|
if (expire != 1) {
|
|
std::tm* ptm = std::localtime(&expire);
|
|
// Format: Mo, 15.06.2009 20:20:00
|
|
std::strftime(buffer, 32, "%a, %d.%m.%Y %H:%M:%S", ptm);
|
|
}
|
|
|
|
const auto timeStr = GeneralUtils::ASCIIToUTF16(std::string(buffer));
|
|
|
|
ChatPackets::SendSystemMessage(GetEntity()->GetSystemAddress(), u"You are muted until " + timeStr);
|
|
}
|
|
|
|
void Character::SetBillboardVisible(bool visible) {
|
|
if (m_BillboardVisible == visible) return;
|
|
m_BillboardVisible = visible;
|
|
|
|
GameMessages::SendSetNamebillboardState(UNASSIGNED_SYSTEM_ADDRESS, m_OurEntity->GetObjectID());
|
|
|
|
if (!visible) return;
|
|
|
|
// The GameMessage we send for turning the nameplate off just deletes the BillboardSubcomponent from the parent component.
|
|
// Because that same message does not allow for custom parameters, we need to create the BillboardSubcomponent a different way
|
|
// This workaround involves sending an unrelated GameMessage that does not apply to player entites,
|
|
// but forces the client to create the necessary SubComponent that controls the billboard.
|
|
GameMessages::SendShowBillboardInteractIcon(UNASSIGNED_SYSTEM_ADDRESS, m_OurEntity->GetObjectID());
|
|
|
|
// Now turn off the billboard for the owner.
|
|
GameMessages::SendSetNamebillboardState(m_OurEntity->GetSystemAddress(), m_OurEntity->GetObjectID());
|
|
}
|