Merge branch 'main' into fix/cmake-libs-2

This commit is contained in:
David Markowitz
2024-01-25 02:43:29 -08:00
225 changed files with 2937 additions and 2556 deletions

View File

@@ -15,4 +15,4 @@ target_link_libraries(WorldServer ${COMMON_LIBRARIES}
dUtilities
dGameMessages
dInventory
dGame dChatFilter dZoneManager dPhysics Detour Recast tinyxml2 dWorldServer dNavigation)
dGame dChatFilter dZoneManager dPhysics Detour Recast tinyxml2 dWorldServer dNavigation)

View File

@@ -29,7 +29,6 @@
#include <csignal>
#include "AuthPackets.h"
#include "PacketUtils.h"
#include "BitStreamUtils.h"
#include "WorldPackets.h"
#include "UserManager.h"
@@ -77,11 +76,14 @@
#include "CheatDetection.h"
#include "eGameMasterLevel.h"
#include "StringifiedEnum.h"
#include "Server.h"
#include "PositionUpdate.h"
#include "PlayerManager.h"
#include "eLoginResponse.h"
namespace Game {
Logger* logger = nullptr;
dServer* server = nullptr;
dpWorld* physicsWorld = nullptr;
dChatFilter* chatFilter = nullptr;
dConfig* config = nullptr;
AssetManager* assetManager = nullptr;
@@ -102,8 +104,8 @@ void WorldShutdownProcess(uint32_t zoneId);
void FinalizeShutdown();
void SendShutdownMessageToMaster();
Logger* SetupLogger(uint32_t zoneID, uint32_t instanceID);
void HandlePacketChat(Packet* packet);
void HandleMasterPacket(Packet* packet);
void HandlePacket(Packet* packet);
struct tempSessionInfo {
@@ -143,14 +145,11 @@ int main(int argc, char** argv) {
if (argument == "-port") ourPort = atoi(argv[i + 1]);
}
//Create all the objects we need to run our service:
Game::logger = SetupLogger(zoneID, instanceID);
if (!Game::logger) return EXIT_FAILURE;
//Read our config:
Game::config = new dConfig("worldconfig.ini");
Game::logger->SetLogToConsole(Game::config->GetValue("log_to_console") != "0");
Game::logger->SetLogDebugStatements(Game::config->GetValue("log_debug_statements") == "1");
//Create all the objects we need to run our service:
Server::SetupLogger("WorldServer_" + std::to_string(zoneID) + "_" + std::to_string(instanceID));
if (!Game::logger) return EXIT_FAILURE;
LOG("Starting World server...");
LOG("Version: %s", Game::projectVersion.c_str());
@@ -182,7 +181,7 @@ int main(int argc, char** argv) {
return EXIT_FAILURE;
}
CDClientManager::Instance();
CDClientManager::Instance().LoadValuesFromDatabase();
Diagnostics::SetProduceMemoryDump(Game::config->GetValue("generate_dump") == "1");
@@ -257,7 +256,7 @@ int main(int argc, char** argv) {
Game::zoneManager = new dZoneManager();
//Load our level:
if (zoneID != 0) {
dpWorld::Instance().Initialize(zoneID);
dpWorld::Initialize(zoneID);
Game::zoneManager->Initialize(LWOZONEID(zoneID, instanceID, cloneID));
g_CloneID = cloneID;
@@ -388,7 +387,7 @@ int main(int argc, char** argv) {
if (zoneID != 0 && deltaTime > 0.0f) {
Metrics::StartMeasurement(MetricVariable::Physics);
dpWorld::Instance().StepWorld(deltaTime);
dpWorld::StepWorld(deltaTime);
Metrics::EndMeasurement(MetricVariable::Physics);
Metrics::StartMeasurement(MetricVariable::UpdateEntities);
@@ -412,7 +411,7 @@ int main(int argc, char** argv) {
//Check for packets here:
packet = Game::server->ReceiveFromMaster();
if (packet) { //We can get messages not handle-able by the dServer class, so handle them if we returned anything.
HandlePacket(packet);
HandleMasterPacket(packet);
Game::server->DeallocateMasterPacket(packet);
}
@@ -529,18 +528,6 @@ int main(int argc, char** argv) {
return EXIT_SUCCESS;
}
Logger* SetupLogger(uint32_t zoneID, uint32_t instanceID) {
std::string logPath = (BinaryPathFinder::GetBinaryDir() / ("logs/WorldServer_" + std::to_string(zoneID) + "_" + std::to_string(instanceID) + "_" + std::to_string(time(nullptr)) + ".log")).string();
bool logToConsole = false;
bool logDebugStatements = false;
#ifdef _DEBUG
logToConsole = true;
logDebugStatements = true;
#endif
return new Logger(logPath, logToConsole, logDebugStatements);
}
void HandlePacketChat(Packet* packet) {
if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) {
LOG("Lost our connection to chat, zone(%i), instance(%i)", Game::server->GetZoneID(), Game::server->GetInstanceID());
@@ -672,6 +659,132 @@ void HandlePacketChat(Packet* packet) {
}
}
void HandleMasterPacket(Packet* packet) {
if (static_cast<eConnectionType>(packet->data[1]) != eConnectionType::MASTER || packet->length < 4) return;
switch (static_cast<eMasterMessageType>(packet->data[3])) {
case eMasterMessageType::REQUEST_PERSISTENT_ID_RESPONSE: {
CINSTREAM_SKIP_HEADER;
uint64_t requestID;
inStream.Read(requestID);
uint32_t objectID;
inStream.Read(objectID);
ObjectIDManager::HandleRequestPersistentIDResponse(requestID, objectID);
break;
}
case eMasterMessageType::SESSION_KEY_RESPONSE: {
//Read our session key and to which user it belongs:
CINSTREAM_SKIP_HEADER;
uint32_t sessionKey = 0;
inStream.Read(sessionKey);
LUWString username;
inStream.Read(username);
//Find them:
auto it = m_PendingUsers.find(username.GetAsString());
if (it == m_PendingUsers.end()) return;
//Convert our key:
std::string userHash = std::to_string(sessionKey);
userHash = md5(userHash);
//Verify it:
if (userHash != it->second.hash) {
LOG("SOMEONE IS TRYING TO HACK? SESSION KEY MISMATCH: ours: %s != master: %s", userHash.c_str(), it->second.hash.c_str());
Game::server->Disconnect(it->second.sysAddr, eServerDisconnectIdentifiers::INVALID_SESSION_KEY);
return;
} else {
LOG("User %s authenticated with correct key.", username.GetAsString().c_str());
UserManager::Instance()->DeleteUser(packet->systemAddress);
//Create our user and send them in:
UserManager::Instance()->CreateUser(it->second.sysAddr, username.GetAsString(), userHash);
auto zone = Game::zoneManager->GetZone();
if (zone) {
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
if (zone->GetZoneID().GetMapID() == 1100) {
auto pos = zone->GetSpawnPos();
x = pos.x;
y = pos.y;
z = pos.z;
}
WorldPackets::SendLoadStaticZone(it->second.sysAddr, x, y, z, zone->GetChecksum(), Game::zoneManager->GetZoneID());
}
if (Game::server->GetZoneID() == 0) {
//Since doing this reroute breaks the client's request, we have to call this manually.
UserManager::Instance()->RequestCharacterList(it->second.sysAddr);
}
m_PendingUsers.erase(username.GetAsString());
//Notify master:
{
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::MASTER, eMasterMessageType::PLAYER_ADDED);
bitStream.Write((LWOMAPID)Game::server->GetZoneID());
bitStream.Write((LWOINSTANCEID)instanceID);
Game::server->SendToMaster(&bitStream);
}
}
break;
}
case eMasterMessageType::AFFIRM_TRANSFER_REQUEST: {
CINSTREAM_SKIP_HEADER;
uint64_t requestID;
inStream.Read(requestID);
LOG("Got affirmation request of transfer %llu", requestID);
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::MASTER, eMasterMessageType::AFFIRM_TRANSFER_RESPONSE);
bitStream.Write(requestID);
Game::server->SendToMaster(&bitStream);
break;
}
case eMasterMessageType::SHUTDOWN: {
Game::lastSignal = -1;
LOG("Got shutdown request from master, zone (%i), instance (%i)", Game::server->GetZoneID(), Game::server->GetInstanceID());
break;
}
case eMasterMessageType::NEW_SESSION_ALERT: {
CINSTREAM_SKIP_HEADER;
uint32_t sessionKey = inStream.Read(sessionKey);
LUString username;
inStream.Read(username);
LOG("Got new session alert for user %s", username.string.c_str());
//Find them:
User* user = UserManager::Instance()->GetUser(username.string.c_str());
if (!user) {
LOG("But they're not logged in?");
return;
}
//Check the key:
if (sessionKey != std::atoi(user->GetSessionKey().c_str())) {
LOG("But the session key is invalid!", username.string.c_str());
Game::server->Disconnect(user->GetSystemAddress(), eServerDisconnectIdentifiers::INVALID_SESSION_KEY);
return;
}
break;
}
default:
LOG("Unknown packet ID from master %i", int(packet->data[3]));
}
}
void HandlePacket(Packet* packet) {
if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) {
auto user = UserManager::Instance()->GetUser(packet->systemAddress);
@@ -686,7 +799,7 @@ void HandlePacket(Packet* packet) {
auto* entity = Game::entityManager->GetEntity(c->GetObjectID());
if (!entity) {
entity = Player::GetPlayer(packet->systemAddress);
entity = PlayerManager::GetPlayer(packet->systemAddress);
}
if (entity) {
@@ -730,159 +843,23 @@ void HandlePacket(Packet* packet) {
}
}
if (static_cast<eConnectionType>(packet->data[1]) == eConnectionType::MASTER) {
switch (static_cast<eMasterMessageType>(packet->data[3])) {
case eMasterMessageType::REQUEST_PERSISTENT_ID_RESPONSE: {
uint64_t requestID = PacketUtils::ReadU64(8, packet);
uint32_t objectID = PacketUtils::ReadU32(16, packet);
ObjectIDManager::HandleRequestPersistentIDResponse(requestID, objectID);
break;
}
case eMasterMessageType::REQUEST_ZONE_TRANSFER_RESPONSE: {
uint64_t requestID = PacketUtils::ReadU64(8, packet);
ZoneInstanceManager::Instance()->HandleRequestZoneTransferResponse(requestID, packet);
break;
}
case eMasterMessageType::SESSION_KEY_RESPONSE: {
//Read our session key and to which user it belongs:
RakNet::BitStream inStream(packet->data, packet->length, false);
uint64_t header = inStream.Read(header);
uint32_t sessionKey = 0;
std::string username;
inStream.Read(sessionKey);
username = PacketUtils::ReadString(12, packet, false);
//Find them:
auto it = m_PendingUsers.find(username);
if (it == m_PendingUsers.end()) return;
//Convert our key:
std::string userHash = std::to_string(sessionKey);
userHash = md5(userHash);
//Verify it:
if (userHash != it->second.hash) {
LOG("SOMEONE IS TRYING TO HACK? SESSION KEY MISMATCH: ours: %s != master: %s", userHash.c_str(), it->second.hash.c_str());
Game::server->Disconnect(it->second.sysAddr, eServerDisconnectIdentifiers::INVALID_SESSION_KEY);
return;
} else {
LOG("User %s authenticated with correct key.", username.c_str());
UserManager::Instance()->DeleteUser(packet->systemAddress);
//Create our user and send them in:
UserManager::Instance()->CreateUser(it->second.sysAddr, username, userHash);
auto zone = Game::zoneManager->GetZone();
if (zone) {
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
if (zone->GetZoneID().GetMapID() == 1100) {
auto pos = zone->GetSpawnPos();
x = pos.x;
y = pos.y;
z = pos.z;
}
WorldPackets::SendLoadStaticZone(it->second.sysAddr, x, y, z, zone->GetChecksum());
}
if (Game::server->GetZoneID() == 0) {
//Since doing this reroute breaks the client's request, we have to call this manually.
UserManager::Instance()->RequestCharacterList(it->second.sysAddr);
}
m_PendingUsers.erase(username);
//Notify master:
{
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::MASTER, eMasterMessageType::PLAYER_ADDED);
bitStream.Write((LWOMAPID)Game::server->GetZoneID());
bitStream.Write((LWOINSTANCEID)instanceID);
Game::server->SendToMaster(&bitStream);
}
}
break;
}
case eMasterMessageType::AFFIRM_TRANSFER_REQUEST: {
const uint64_t requestID = PacketUtils::ReadU64(8, packet);
LOG("Got affirmation request of transfer %llu", requestID);
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::MASTER, eMasterMessageType::AFFIRM_TRANSFER_RESPONSE);
bitStream.Write(requestID);
Game::server->SendToMaster(&bitStream);
break;
}
case eMasterMessageType::SHUTDOWN: {
Game::lastSignal = -1;
LOG("Got shutdown request from master, zone (%i), instance (%i)", Game::server->GetZoneID(), Game::server->GetInstanceID());
break;
}
case eMasterMessageType::NEW_SESSION_ALERT: {
RakNet::BitStream inStream(packet->data, packet->length, false);
uint64_t header = inStream.Read(header);
uint32_t sessionKey = inStream.Read(sessionKey);
std::string username;
uint32_t len;
inStream.Read(len);
for (uint32_t i = 0; i < len; i++) {
char character; inStream.Read<char>(character);
username += character;
}
//Find them:
User* user = UserManager::Instance()->GetUser(username.c_str());
if (!user) {
LOG("Got new session alert for user %s, but they're not logged in.", username.c_str());
return;
}
//Check the key:
if (sessionKey != std::atoi(user->GetSessionKey().c_str())) {
LOG("Got new session alert for user %s, but the session key is invalid.", username.c_str());
Game::server->Disconnect(user->GetSystemAddress(), eServerDisconnectIdentifiers::INVALID_SESSION_KEY);
return;
}
break;
}
default:
LOG("Unknown packet ID from master %i", int(packet->data[3]));
}
return;
}
if (static_cast<eConnectionType>(packet->data[1]) != eConnectionType::WORLD) return;
switch (static_cast<eWorldMessageType>(packet->data[3])) {
case eWorldMessageType::VALIDATION: {
std::string username = PacketUtils::ReadString(0x08, packet, true);
std::string sessionKey = PacketUtils::ReadString(74, packet, true);
std::string clientDatabaseChecksum = PacketUtils::ReadString(packet->length - 33, packet, false);
CINSTREAM_SKIP_HEADER;
LUWString username;
inStream.Read(username);
LUWString sessionKey;
// sometimes client puts a null terminator at the end of the checksum and sometimes doesn't, weird
clientDatabaseChecksum = clientDatabaseChecksum.substr(0, 32);
inStream.Read(sessionKey);
LUString clientDatabaseChecksum(32);
inStream.Read(clientDatabaseChecksum);
// If the check is turned on, validate the client's database checksum.
if (Game::config->GetValue("check_fdb") == "1" && !databaseChecksum.empty()) {
auto accountInfo = Database::Get()->GetAccountInfo(username);
auto accountInfo = Database::Get()->GetAccountInfo(username.GetAsString());
if (!accountInfo) {
LOG("Client's account does not exist in the database, aborting connection.");
Game::server->Disconnect(packet->systemAddress, eServerDisconnectIdentifiers::CHARACTER_NOT_FOUND);
@@ -890,24 +867,42 @@ void HandlePacket(Packet* packet) {
}
// Developers may skip this check
if (accountInfo->maxGmLevel < eGameMasterLevel::DEVELOPER && clientDatabaseChecksum != databaseChecksum) {
LOG("Client's database checksum does not match the server's, aborting connection.");
Game::server->Disconnect(packet->systemAddress, eServerDisconnectIdentifiers::WRONG_GAME_VERSION);
return;
if (clientDatabaseChecksum.string != databaseChecksum) {
if (accountInfo->maxGmLevel < eGameMasterLevel::DEVELOPER) {
LOG("Client's database checksum does not match the server's, aborting connection.");
std::vector<Stamp> stamps;
// Using the LoginResponse here since the UI is still in the login screen state
// and we have a way to send a message about the client mismatch.
AuthPackets::SendLoginResponse(
Game::server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH,
Game::config->GetValue("cdclient_mismatch_message"), "", 0, "", stamps);
return;
} else {
AMFArrayValue args;
args.Insert("title", Game::config->GetValue("cdclient_mismatch_title"));
args.Insert("message", Game::config->GetValue("cdclient_mismatch_message"));
GameMessages::SendUIMessageServerToSingleClient("ToggleAnnounce", args, packet->systemAddress);
LOG("Account (%s) with GmLevel (%s) does not have a matching FDB, but is a developer and will skip this check."
, username.GetAsString().c_str(), StringifiedEnum::ToString(accountInfo->maxGmLevel).data());
}
}
}
//Request the session info from Master:
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::MASTER, eMasterMessageType::REQUEST_SESSION_KEY);
bitStream.Write(LUString(username, 64));
bitStream.Write(username);
Game::server->SendToMaster(&bitStream);
//Insert info into our pending list
tempSessionInfo info;
info.sysAddr = SystemAddress(packet->systemAddress);
info.hash = sessionKey;
m_PendingUsers.insert(std::make_pair(username, info));
info.hash = sessionKey.GetAsString();
m_PendingUsers.insert(std::make_pair(username.GetAsString(), info));
break;
}
@@ -1026,7 +1021,10 @@ void HandlePacket(Packet* packet) {
info.lot = 1;
Entity* player = Game::entityManager->CreateEntity(info, UserManager::Instance()->GetUser(packet->systemAddress));
WorldPackets::SendCreateCharacter(packet->systemAddress, player, c->GetXMLData(), username, c->GetGMLevel());
auto* characterComponent = player->GetComponent<CharacterComponent>();
if (!characterComponent) return;
WorldPackets::SendCreateCharacter(packet->systemAddress, player->GetComponent<CharacterComponent>()->GetReputation(), player->GetObjectID(), c->GetXMLData(), username, c->GetGMLevel());
WorldPackets::SendServerState(packet->systemAddress);
const auto respawnPoint = player->GetCharacter()->GetRespawnPoint(Game::zoneManager->GetZone()->GetWorldID());
@@ -1039,8 +1037,6 @@ void HandlePacket(Packet* packet) {
Game::entityManager->ConstructAllEntities(packet->systemAddress);
auto* characterComponent = player->GetComponent<CharacterComponent>();
if (!characterComponent) return;
characterComponent->RocketUnEquip(player);
// Do charxml fixes here
@@ -1117,7 +1113,6 @@ void HandlePacket(Packet* packet) {
SystemAddress sysAddr = packet->systemAddress;
SEND_PACKET;
// PacketUtils::SavePacket("lxfml packet " + std::to_string(bbbModel.id) + ".bin", (char*)bitStream.GetData(), bitStream.GetNumberOfBytesUsed());
}
}
@@ -1149,6 +1144,7 @@ void HandlePacket(Packet* packet) {
bitStream.Write(zone.GetInstanceID());
bitStream.Write(zone.GetCloneID());
bitStream.Write(player->GetParentUser()->GetMuteExpire());
bitStream.Write(player->GetGMLevel());
Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
}
@@ -1163,7 +1159,16 @@ void HandlePacket(Packet* packet) {
}
case eWorldMessageType::POSITION_UPDATE: {
ClientPackets::HandleClientPositionUpdate(packet->systemAddress, packet);
auto positionUpdate = ClientPackets::HandleClientPositionUpdate(packet);
User* user = UserManager::Instance()->GetUser(packet->systemAddress);
if (!user) {
LOG("Unable to get user to parse position update");
return;
}
Entity* entity = Game::entityManager->GetEntity(user->GetLastUsedChar()->GetObjectID());
if (entity) entity->ProcessPositionUpdate(positionUpdate);
break;
}
@@ -1211,7 +1216,74 @@ void HandlePacket(Packet* packet) {
}
case eWorldMessageType::STRING_CHECK: {
ClientPackets::HandleChatModerationRequest(packet->systemAddress, packet);
auto request = ClientPackets::HandleChatModerationRequest(packet);
// TODO: Find a good home for the logic in this case.
User* user = UserManager::Instance()->GetUser(packet->systemAddress);
if (!user) {
LOG("Unable to get user to parse chat moderation request");
return;
}
auto* entity = PlayerManager::GetPlayer(packet->systemAddress);
if (entity == nullptr) {
LOG("Unable to get player to parse chat moderation request");
return;
}
// Check if the player has restricted chat access
auto* character = entity->GetCharacter();
if (character->HasPermission(ePermissionMap::RestrictedChatAccess)) {
// Send a message to the player
ChatPackets::SendSystemMessage(
packet->systemAddress,
u"This character has restricted chat access."
);
return;
}
bool isBestFriend = false;
if (request.chatLevel == 1) {
// Private chat
LWOOBJID idOfReceiver = LWOOBJID_EMPTY;
{
auto characterIdFetch = Database::Get()->GetCharacterInfo(request.receiver);
if (characterIdFetch) {
idOfReceiver = characterIdFetch->id;
}
}
const auto& bffMap = user->GetIsBestFriendMap();
if (bffMap.find(request.receiver) == bffMap.end() && idOfReceiver != LWOOBJID_EMPTY) {
auto bffInfo = Database::Get()->GetBestFriendStatus(entity->GetObjectID(), idOfReceiver);
if (bffInfo) {
isBestFriend = bffInfo->bestFriendStatus == 3;
}
if (isBestFriend) {
user->UpdateBestFriendValue(request.receiver, true);
}
} else if (bffMap.find(request.receiver) != bffMap.end()) {
isBestFriend = true;
}
}
std::vector<std::pair<uint8_t, uint8_t>> segments = Game::chatFilter->IsSentenceOkay(request.message, entity->GetGMLevel(), !(isBestFriend && request.chatLevel == 1));
bool bAllClean = segments.empty();
if (user->GetIsMuted()) {
bAllClean = false;
}
user->SetLastChatMessageApproved(bAllClean);
WorldPackets::SendChatModerationResponse(packet->systemAddress, bAllClean, request.requestID, request.receiver, segments);
break;
}
@@ -1219,7 +1291,29 @@ void HandlePacket(Packet* packet) {
if (chatDisabled) {
ChatPackets::SendMessageFail(packet->systemAddress);
} else {
ClientPackets::HandleChatMessage(packet->systemAddress, packet);
auto chatMessage = ClientPackets::HandleChatMessage(packet);
// TODO: Find a good home for the logic in this case.
User* user = UserManager::Instance()->GetUser(packet->systemAddress);
if (!user) {
LOG("Unable to get user to parse chat message");
return;
}
if (user->GetIsMuted()) {
user->GetLastUsedChar()->SendMuteNotice();
return;
}
std::string playerName = user->GetLastUsedChar()->GetName();
bool isMythran = user->GetLastUsedChar()->GetGMLevel() > eGameMasterLevel::CIVILIAN;
bool isOk = Game::chatFilter->IsSentenceOkay(GeneralUtils::UTF16ToWTF8(chatMessage.message), user->GetLastUsedChar()->GetGMLevel()).empty();
LOG_DEBUG("Msg: %s was approved previously? %i", GeneralUtils::UTF16ToWTF8(chatMessage.message).c_str(), user->GetLastChatMessageApproved());
if (!isOk) return;
if (!isOk && !isMythran) return;
std::string sMessage = GeneralUtils::UTF16ToWTF8(chatMessage.message);
LOG("%s: %s", playerName.c_str(), sMessage.c_str());
ChatPackets::SendChatMessage(packet->systemAddress, chatMessage.chatChannel, playerName, user->GetLoggedInChar(), isMythran, chatMessage.message);
}
break;
@@ -1245,7 +1339,37 @@ void HandlePacket(Packet* packet) {
case eWorldMessageType::UI_HELP_TOP_5: {
ClientPackets::SendTop5HelpIssues(packet);
auto language = ClientPackets::SendTop5HelpIssues(packet);
// TODO: Handle different languages in a nice way
// 0: en_US
// 1: pl_US
// 2: de_DE
// 3: en_GB
// TODO: Find a good home for the logic in this case.
auto* user = UserManager::Instance()->GetUser(packet->systemAddress);
if (!user) return;
auto* character = user->GetLastUsedChar();
if (!character) return;
auto* entity = character->GetEntity();
if (!entity) return;
AMFArrayValue data;
// Summaries
data.Insert("Summary0", Game::config->GetValue("help_0_summary"));
data.Insert("Summary1", Game::config->GetValue("help_1_summary"));
data.Insert("Summary2", Game::config->GetValue("help_2_summary"));
data.Insert("Summary3", Game::config->GetValue("help_3_summary"));
data.Insert("Summary4", Game::config->GetValue("help_4_summary"));
// Descriptions
data.Insert("Description0", Game::config->GetValue("help_0_description"));
data.Insert("Description1", Game::config->GetValue("help_1_description"));
data.Insert("Description2", Game::config->GetValue("help_2_description"));
data.Insert("Description3", Game::config->GetValue("help_3_description"));
data.Insert("Description4", Game::config->GetValue("help_4_description"));
GameMessages::SendUIMessageServerToSingleClient(entity, packet->systemAddress, "UIHelpTop5", data);
break;
}
@@ -1261,7 +1385,7 @@ void WorldShutdownProcess(uint32_t zoneId) {
for (auto i = 0; i < Game::server->GetReplicaManager()->GetParticipantCount(); ++i) {
const auto& player = Game::server->GetReplicaManager()->GetParticipantAtIndex(i);
auto* entity = Player::GetPlayer(player);
auto* entity = PlayerManager::GetPlayer(player);
LOG("Saving data!");
if (entity != nullptr && entity->GetCharacter() != nullptr) {
auto* skillComponent = entity->GetComponent<SkillComponent>();
@@ -1314,6 +1438,7 @@ void FinalizeShutdown() {
//Delete our objects here:
Metrics::Clear();
dpWorld::Shutdown();
Database::Destroy("WorldServer");
if (Game::chatFilter) delete Game::chatFilter;
Game::chatFilter = nullptr;