2021-12-05 17:54:36 +00:00
|
|
|
#include <iostream>
|
|
|
|
#include <string>
|
|
|
|
#include <ctime>
|
|
|
|
#include <chrono>
|
|
|
|
#include <thread>
|
|
|
|
|
|
|
|
#include "MD5.h"
|
|
|
|
|
|
|
|
//DLU Includes:
|
|
|
|
#include "dCommonVars.h"
|
|
|
|
#include "dServer.h"
|
|
|
|
#include "dLogger.h"
|
|
|
|
#include "Database.h"
|
|
|
|
#include "dConfig.h"
|
|
|
|
#include "dpWorld.h"
|
|
|
|
#include "dZoneManager.h"
|
|
|
|
#include "Metrics.hpp"
|
|
|
|
#include "PerformanceManager.h"
|
|
|
|
#include "Diagnostics.h"
|
|
|
|
|
|
|
|
//RakNet includes:
|
|
|
|
#include "RakNetDefines.h"
|
|
|
|
#include "RakNetworkFactory.h"
|
|
|
|
#include "RakString.h"
|
|
|
|
|
|
|
|
//World includes:
|
|
|
|
#include <csignal>
|
|
|
|
|
|
|
|
#include "AuthPackets.h"
|
|
|
|
#include "PacketUtils.h"
|
|
|
|
#include "WorldPackets.h"
|
|
|
|
#include "UserManager.h"
|
|
|
|
#include "dMessageIdentifiers.h"
|
|
|
|
#include "CDClientManager.h"
|
|
|
|
#include "CDClientDatabase.h"
|
|
|
|
#include "GeneralUtils.h"
|
|
|
|
#include "ObjectIDManager.h"
|
|
|
|
#include "ZoneInstanceManager.h"
|
|
|
|
#include "dChatFilter.h"
|
|
|
|
#include "ClientPackets.h"
|
|
|
|
#include "CharacterComponent.h"
|
|
|
|
|
|
|
|
#include "EntityManager.h"
|
|
|
|
#include "Entity.h"
|
|
|
|
#include "Character.h"
|
|
|
|
#include "ChatPackets.h"
|
|
|
|
#include "GameMessageHandler.h"
|
|
|
|
#include "GameMessages.h"
|
|
|
|
#include "Mail.h"
|
|
|
|
#include "dLocale.h"
|
|
|
|
#include "TeamManager.h"
|
|
|
|
#include "SkillComponent.h"
|
|
|
|
#include "DestroyableComponent.h"
|
|
|
|
#include "Game.h"
|
|
|
|
#include "MasterPackets.h"
|
|
|
|
#include "Player.h"
|
|
|
|
#include "PropertyManagementComponent.h"
|
|
|
|
|
|
|
|
#include "ZCompression.h"
|
|
|
|
|
|
|
|
namespace Game {
|
|
|
|
dLogger* logger;
|
|
|
|
dServer* server;
|
|
|
|
dZoneManager* zoneManager;
|
|
|
|
dpWorld* physicsWorld;
|
|
|
|
dChatFilter* chatFilter;
|
|
|
|
dConfig* config;
|
|
|
|
dLocale* locale;
|
|
|
|
std::mt19937 randomEngine;
|
|
|
|
|
|
|
|
RakPeerInterface* chatServer;
|
|
|
|
SystemAddress chatSysAddr;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool chatDisabled = false;
|
|
|
|
bool worldShutdownSequenceStarted = false;
|
|
|
|
bool worldShutdownSequenceComplete = false;
|
|
|
|
void WorldShutdownSequence();
|
|
|
|
|
|
|
|
dLogger* SetupLogger(int zoneID, int instanceID);
|
|
|
|
void HandlePacketChat(Packet* packet);
|
|
|
|
void HandlePacket(Packet* packet);
|
|
|
|
|
|
|
|
struct tempSessionInfo {
|
|
|
|
SystemAddress sysAddr;
|
|
|
|
std::string hash;
|
|
|
|
};
|
|
|
|
|
|
|
|
std::map<std::string, tempSessionInfo> m_PendingUsers;
|
|
|
|
int instanceID = 0;
|
|
|
|
int g_CloneID = 0;
|
2021-12-12 15:14:04 +00:00
|
|
|
std::string fdbChecksum = "";
|
2021-12-05 17:54:36 +00:00
|
|
|
|
|
|
|
int main(int argc, char** argv) {
|
|
|
|
Diagnostics::SetProcessName("World");
|
|
|
|
Diagnostics::SetProcessFileName(argv[0]);
|
|
|
|
Diagnostics::Initialize();
|
|
|
|
|
|
|
|
// Triggers the shutdown sequence at application exit
|
|
|
|
std::atexit(WorldShutdownSequence);
|
|
|
|
|
|
|
|
signal(SIGINT, [](int)
|
|
|
|
{
|
|
|
|
WorldShutdownSequence();
|
|
|
|
});
|
|
|
|
|
|
|
|
int zoneID = 1000;
|
|
|
|
int cloneID = 0;
|
|
|
|
int maxClients = 8;
|
|
|
|
int ourPort = 2007;
|
|
|
|
|
|
|
|
//Check our arguments:
|
|
|
|
for (int i = 0; i < argc; ++i) {
|
|
|
|
std::string argument(argv[i]);
|
|
|
|
|
|
|
|
if (argument == "-zone") zoneID = atoi(argv[i + 1]);
|
|
|
|
if (argument == "-instance") instanceID = atoi(argv[i + 1]);
|
|
|
|
if (argument == "-clone") cloneID = atoi(argv[i + 1]);
|
|
|
|
if (argument == "-maxclients") maxClients = atoi(argv[i + 1]);
|
|
|
|
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 0;
|
|
|
|
|
|
|
|
Game::logger->SetLogToConsole(true); //We want this info to always be logged.
|
|
|
|
Game::logger->Log("WorldServer", "Starting World server...\n");
|
|
|
|
Game::logger->Log("WorldServer", "Version: %i.%i\n", PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR);
|
|
|
|
Game::logger->Log("WorldServer", "Compiled on: %s\n", __TIMESTAMP__);
|
|
|
|
|
|
|
|
#ifndef _DEBUG
|
|
|
|
Game::logger->SetLogToConsole(false); //By default, turn it back off if not in debug.
|
|
|
|
#endif
|
|
|
|
|
|
|
|
//Read our config:
|
|
|
|
dConfig config("worldconfig.ini");
|
|
|
|
Game::config = &config;
|
|
|
|
Game::logger->SetLogToConsole(bool(std::stoi(config.GetValue("log_to_console"))));
|
|
|
|
if (config.GetValue("disable_chat") == "1") chatDisabled = true;
|
|
|
|
|
|
|
|
// Connect to CDClient
|
|
|
|
try {
|
|
|
|
CDClientDatabase::Connect("./res/CDServer.sqlite");
|
|
|
|
} catch (CppSQLite3Exception& e) {
|
|
|
|
Game::logger->Log("WorldServer", "Unable to connect to CDServer SQLite Database\n");
|
|
|
|
Game::logger->Log("WorldServer", "Error: %s\n", e.errorMessage());
|
|
|
|
Game::logger->Log("WorldServer", "Error Code: %i\n", e.errorCode());
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
CDClientManager::Instance()->Initialize();
|
|
|
|
|
|
|
|
//Connect to the MySQL Database
|
|
|
|
std::string mysql_host = config.GetValue("mysql_host");
|
|
|
|
std::string mysql_database = config.GetValue("mysql_database");
|
|
|
|
std::string mysql_username = config.GetValue("mysql_username");
|
|
|
|
std::string mysql_password = config.GetValue("mysql_password");
|
|
|
|
|
|
|
|
Diagnostics::SetProduceMemoryDump(config.GetValue("generate_dump") == "1");
|
|
|
|
|
|
|
|
if (!config.GetValue("dump_folder").empty())
|
|
|
|
{
|
|
|
|
Diagnostics::SetOutDirectory(config.GetValue("dump_folder"));
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
Database::Connect(mysql_host, mysql_database, mysql_username, mysql_password);
|
|
|
|
} catch (sql::SQLException& ex) {
|
|
|
|
Game::logger->Log("WorldServer", "Got an error while connecting to the database: %s\n", ex.what());
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Find out the master's IP:
|
|
|
|
std::string masterIP = "localhost";
|
|
|
|
int masterPort = 1000;
|
|
|
|
sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT ip, port FROM servers WHERE name='master';");
|
|
|
|
auto res = stmt->executeQuery();
|
|
|
|
while (res->next()) {
|
|
|
|
masterIP = res->getString(1).c_str();
|
|
|
|
masterPort = res->getInt(2);
|
|
|
|
}
|
|
|
|
|
|
|
|
delete res;
|
|
|
|
delete stmt;
|
|
|
|
|
|
|
|
ObjectIDManager::Instance()->Initialize();
|
|
|
|
UserManager::Instance()->Initialize();
|
|
|
|
Game::chatFilter = new dChatFilter("./res/chatplus_en_us", bool(std::stoi(config.GetValue("dont_generate_dcf"))));
|
|
|
|
|
|
|
|
Game::server = new dServer(masterIP, ourPort, instanceID, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::World, zoneID);
|
|
|
|
|
|
|
|
//Connect to the chat server:
|
|
|
|
int chatPort = 1501;
|
|
|
|
if (config.GetValue("chat_server_port") != "") chatPort = std::atoi(config.GetValue("chat_server_port").c_str());
|
|
|
|
|
|
|
|
auto chatSock = SocketDescriptor(uint16_t(ourPort + 2), 0);
|
|
|
|
Game::chatServer = RakNetworkFactory::GetRakPeerInterface();
|
|
|
|
Game::chatServer->Startup(1, 30, &chatSock, 1);
|
|
|
|
Game::chatServer->Connect(masterIP.c_str(), chatPort, "3.25 ND1", 8);
|
|
|
|
|
|
|
|
//Set up other things:
|
|
|
|
Game::randomEngine = std::mt19937(time(0));
|
|
|
|
Game::locale = new dLocale();
|
|
|
|
|
|
|
|
//Run it until server gets a kill message from Master:
|
|
|
|
auto lastTime = std::chrono::high_resolution_clock::now();
|
|
|
|
auto t = std::chrono::high_resolution_clock::now();
|
|
|
|
|
|
|
|
Packet* packet = nullptr;
|
|
|
|
int framesSinceLastFlush = 0;
|
|
|
|
int framesSinceMasterDisconnect = 0;
|
|
|
|
int framesSinceLastUsersSave = 0;
|
|
|
|
int framesSinceLastSQLPing = 0;
|
|
|
|
int framesSinceLastUser = 0;
|
|
|
|
|
|
|
|
const float maxPacketProcessingTime = 1.5f; //0.015f;
|
|
|
|
const int maxPacketsToProcess = 1024;
|
|
|
|
|
|
|
|
bool ready = false;
|
|
|
|
int framesSinceMasterStatus = 0;
|
|
|
|
int framesSinceShutdownSequence = 0;
|
|
|
|
int currentFramerate = highFrameRate;
|
|
|
|
int physicsFramerate = highFrameRate;
|
|
|
|
int physicsStepRate = 0;
|
|
|
|
int physicsStepCount = 0;
|
|
|
|
int ghostingStepCount = 0;
|
|
|
|
auto ghostingLastTime = std::chrono::high_resolution_clock::now();
|
|
|
|
|
|
|
|
PerformanceManager::SelectProfile(zoneID);
|
|
|
|
|
|
|
|
//Load our level:
|
|
|
|
if (zoneID != 0) {
|
|
|
|
dpWorld::Instance().Initialize(zoneID);
|
|
|
|
Game::physicsWorld = &dpWorld::Instance(); //just in case some old code references it
|
|
|
|
dZoneManager::Instance()->Initialize(LWOZONEID(zoneID, instanceID, cloneID));
|
|
|
|
g_CloneID = cloneID;
|
2021-12-12 15:14:04 +00:00
|
|
|
|
|
|
|
// pre calculate the FDB checksum
|
2021-12-14 18:12:44 +00:00
|
|
|
if (Game::config->GetValue("check_fdb") == "1") {
|
2021-12-12 15:14:04 +00:00
|
|
|
std::ifstream fileStream;
|
2021-12-12 15:53:19 +00:00
|
|
|
fileStream.open("res/CDServer.fdb", std::ios::binary | std::ios::in);
|
2021-12-12 15:14:04 +00:00
|
|
|
const int bufferSize = 1024;
|
|
|
|
MD5* md5 = new MD5();
|
|
|
|
|
2021-12-12 15:53:19 +00:00
|
|
|
std::vector<char> fileStreamBuffer = std::vector<char>(bufferSize, 0);
|
|
|
|
|
2021-12-12 15:14:04 +00:00
|
|
|
while (!fileStream.eof()) {
|
2021-12-12 15:53:19 +00:00
|
|
|
fileStreamBuffer.clear();
|
|
|
|
fileStream.read(fileStreamBuffer.data(), bufferSize);
|
|
|
|
md5->update(fileStreamBuffer.data(), fileStream.gcount());
|
2021-12-12 15:14:04 +00:00
|
|
|
}
|
|
|
|
|
2021-12-12 15:53:19 +00:00
|
|
|
fileStreamBuffer.clear();
|
|
|
|
|
2021-12-12 15:14:04 +00:00
|
|
|
const char* nullTerminateBuffer = "\0";
|
|
|
|
md5->update(nullTerminateBuffer, 1); // null terminate the data
|
|
|
|
md5->finalize();
|
|
|
|
fdbChecksum = md5->hexdigest();
|
|
|
|
|
|
|
|
delete md5;
|
|
|
|
|
|
|
|
Game::logger->Log("WorldServer", "FDB Checksum calculated as: %s\n", fdbChecksum.c_str());
|
|
|
|
}
|
2021-12-05 17:54:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
Metrics::StartMeasurement(MetricVariable::Frame);
|
|
|
|
Metrics::StartMeasurement(MetricVariable::GameLoop);
|
|
|
|
|
|
|
|
std::clock_t metricCPUTimeStart = std::clock();
|
|
|
|
|
|
|
|
const auto currentTime = std::chrono::high_resolution_clock::now();
|
|
|
|
float deltaTime = std::chrono::duration<float>(currentTime - lastTime).count();
|
|
|
|
lastTime = currentTime;
|
|
|
|
|
|
|
|
const auto occupied = UserManager::Instance()->GetUserCount() != 0;
|
|
|
|
|
|
|
|
if (!ready)
|
|
|
|
{
|
|
|
|
currentFramerate = highFrameRate;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
currentFramerate = PerformanceManager::GetServerFramerate();
|
|
|
|
}
|
|
|
|
|
|
|
|
physicsFramerate = PerformanceManager::GetPhysicsFramerate();
|
|
|
|
physicsStepRate = PerformanceManager::GetPhysicsStepRate();
|
|
|
|
|
|
|
|
//Warning if we ran slow
|
|
|
|
if (deltaTime > currentFramerate) {
|
|
|
|
Game::logger->Log("WorldServer", "We're running behind, dT: %f > %f (framerate)\n", deltaTime, currentFramerate);
|
|
|
|
}
|
|
|
|
|
|
|
|
//Check if we're still connected to master:
|
|
|
|
if (!Game::server->GetIsConnectedToMaster()) {
|
|
|
|
framesSinceMasterDisconnect++;
|
|
|
|
|
|
|
|
if (framesSinceMasterDisconnect >= 30) {
|
|
|
|
worldShutdownSequenceStarted = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else framesSinceMasterDisconnect = 0;
|
|
|
|
|
|
|
|
//In world we'd update our other systems here.
|
|
|
|
|
|
|
|
if (zoneID != 0 && deltaTime > 0.0f) {
|
|
|
|
Metrics::StartMeasurement(MetricVariable::Physics);
|
|
|
|
if (physicsStepCount++ >= physicsStepRate) {
|
|
|
|
dpWorld::Instance().StepWorld(deltaTime);
|
|
|
|
physicsStepCount = 0;
|
|
|
|
}
|
|
|
|
Metrics::EndMeasurement(MetricVariable::Physics);
|
|
|
|
|
|
|
|
Metrics::StartMeasurement(MetricVariable::UpdateEntities);
|
|
|
|
EntityManager::Instance()->UpdateEntities(deltaTime);
|
|
|
|
Metrics::EndMeasurement(MetricVariable::UpdateEntities);
|
|
|
|
|
|
|
|
Metrics::StartMeasurement(MetricVariable::Ghosting);
|
|
|
|
if (std::chrono::duration<float>(currentTime - ghostingLastTime).count() >= 1.0f) {
|
|
|
|
EntityManager::Instance()->UpdateGhosting();
|
|
|
|
ghostingLastTime = currentTime;
|
|
|
|
}
|
|
|
|
Metrics::EndMeasurement(MetricVariable::Ghosting);
|
|
|
|
|
|
|
|
Metrics::StartMeasurement(MetricVariable::UpdateSpawners);
|
|
|
|
dZoneManager::Instance()->Update(deltaTime);
|
|
|
|
Metrics::EndMeasurement(MetricVariable::UpdateSpawners);
|
|
|
|
}
|
|
|
|
|
|
|
|
Metrics::StartMeasurement(MetricVariable::PacketHandling);
|
|
|
|
|
|
|
|
//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);
|
|
|
|
Game::server->DeallocateMasterPacket(packet);
|
|
|
|
}
|
|
|
|
|
|
|
|
//Handle our chat packets:
|
|
|
|
packet = Game::chatServer->Receive();
|
|
|
|
if (packet) {
|
|
|
|
HandlePacketChat(packet);
|
|
|
|
Game::chatServer->DeallocatePacket(packet);
|
|
|
|
}
|
|
|
|
|
|
|
|
//Handle world-specific packets:
|
|
|
|
float timeSpent = 0.0f;
|
|
|
|
|
|
|
|
UserManager::Instance()->DeletePendingRemovals();
|
|
|
|
|
|
|
|
auto t1 = std::chrono::high_resolution_clock::now();
|
|
|
|
for (int curPacket = 0; curPacket < maxPacketsToProcess && timeSpent < maxPacketProcessingTime; curPacket++) {
|
|
|
|
packet = Game::server->Receive();
|
|
|
|
if (packet) {
|
|
|
|
auto t1 = std::chrono::high_resolution_clock::now();
|
|
|
|
HandlePacket(packet);
|
|
|
|
auto t2 = std::chrono::high_resolution_clock::now();
|
|
|
|
|
|
|
|
timeSpent += std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
|
|
|
|
Game::server->DeallocatePacket(packet);
|
|
|
|
packet = nullptr;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Metrics::EndMeasurement(MetricVariable::PacketHandling);
|
|
|
|
|
|
|
|
Metrics::StartMeasurement(MetricVariable::UpdateReplica);
|
|
|
|
|
|
|
|
//Update our replica objects:
|
|
|
|
Game::server->UpdateReplica();
|
|
|
|
|
|
|
|
Metrics::EndMeasurement(MetricVariable::UpdateReplica);
|
|
|
|
|
|
|
|
//Push our log every 15s:
|
|
|
|
if (framesSinceLastFlush >= 1000) {
|
|
|
|
Game::logger->Flush();
|
|
|
|
framesSinceLastFlush = 0;
|
|
|
|
} else framesSinceLastFlush++;
|
|
|
|
|
|
|
|
if (zoneID != 0 && !occupied)
|
|
|
|
{
|
|
|
|
framesSinceLastUser++;
|
|
|
|
|
|
|
|
//If we haven't had any players for a while, time out and shut down:
|
|
|
|
if (framesSinceLastUser == (cloneID != 0 ? 4000 : 40000))
|
|
|
|
{
|
|
|
|
worldShutdownSequenceStarted = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
framesSinceLastUser = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Save all connected users every 10 minutes:
|
|
|
|
if (framesSinceLastUsersSave >= 40000 && zoneID != 0) {
|
|
|
|
UserManager::Instance()->SaveAllActiveCharacters();
|
|
|
|
framesSinceLastUsersSave = 0;
|
|
|
|
|
|
|
|
if (PropertyManagementComponent::Instance() != nullptr) {
|
|
|
|
PropertyManagementComponent::Instance()->Save();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else framesSinceLastUsersSave++;
|
|
|
|
|
|
|
|
//Every 10 min we ping our sql server to keep it alive hopefully:
|
|
|
|
if (framesSinceLastSQLPing >= 40000) {
|
|
|
|
//Find out the master's IP for absolutely no reason:
|
|
|
|
std::string masterIP;
|
|
|
|
int masterPort;
|
|
|
|
sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT ip, port FROM servers WHERE name='master';");
|
|
|
|
auto res = stmt->executeQuery();
|
|
|
|
while (res->next()) {
|
|
|
|
masterIP = res->getString(1).c_str();
|
|
|
|
masterPort = res->getInt(2);
|
|
|
|
}
|
|
|
|
|
|
|
|
delete res;
|
|
|
|
delete stmt;
|
|
|
|
|
|
|
|
framesSinceLastSQLPing = 0;
|
|
|
|
}
|
|
|
|
else framesSinceLastSQLPing++;
|
|
|
|
|
|
|
|
Metrics::EndMeasurement(MetricVariable::GameLoop);
|
|
|
|
|
|
|
|
Metrics::StartMeasurement(MetricVariable::Sleep);
|
|
|
|
|
|
|
|
t += std::chrono::milliseconds(currentFramerate);
|
|
|
|
std::this_thread::sleep_until(t);
|
|
|
|
|
|
|
|
Metrics::EndMeasurement(MetricVariable::Sleep);
|
|
|
|
|
|
|
|
if (!ready && Game::server->GetIsConnectedToMaster())
|
|
|
|
{
|
|
|
|
// Some delay is required here or else we crash the client?
|
|
|
|
|
|
|
|
framesSinceMasterStatus++;
|
|
|
|
|
|
|
|
if (framesSinceMasterStatus >= 200)
|
|
|
|
{
|
|
|
|
Game::logger->Log("WorldServer", "Finished loading world, ready up!\n");
|
|
|
|
|
|
|
|
MasterPackets::SendWorldReady(Game::server, Game::server->GetZoneID(), Game::server->GetInstanceID());
|
|
|
|
|
|
|
|
ready = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (worldShutdownSequenceStarted && !worldShutdownSequenceComplete)
|
|
|
|
{
|
|
|
|
if (framesSinceShutdownSequence == 0) {
|
|
|
|
|
|
|
|
ChatPackets::SendSystemMessage(UNASSIGNED_SYSTEM_ADDRESS, u"Server shutting down...", true);
|
|
|
|
|
|
|
|
for (auto i = 0; i < Game::server->GetReplicaManager()->GetParticipantCount(); ++i)
|
|
|
|
{
|
|
|
|
const auto& player = Game::server->GetReplicaManager()->GetParticipantAtIndex(i);
|
|
|
|
|
|
|
|
auto* entity = Player::GetPlayer(player);
|
|
|
|
|
|
|
|
if (entity != nullptr && entity->GetCharacter() != nullptr)
|
|
|
|
{
|
|
|
|
auto* skillComponent = entity->GetComponent<SkillComponent>();
|
|
|
|
|
|
|
|
if (skillComponent != nullptr)
|
|
|
|
{
|
|
|
|
skillComponent->Reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
entity->GetCharacter()->SaveXMLToDatabase();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (PropertyManagementComponent::Instance() != nullptr) {
|
|
|
|
ChatPackets::SendSystemMessage(UNASSIGNED_SYSTEM_ADDRESS, u"Property data saved...", true);
|
|
|
|
PropertyManagementComponent::Instance()->Save();
|
|
|
|
}
|
|
|
|
|
|
|
|
ChatPackets::SendSystemMessage(UNASSIGNED_SYSTEM_ADDRESS, u"Character data saved...", true);
|
|
|
|
}
|
|
|
|
|
|
|
|
framesSinceShutdownSequence++;
|
|
|
|
|
|
|
|
if (framesSinceShutdownSequence == 100)
|
|
|
|
{
|
|
|
|
while (Game::server->GetReplicaManager()->GetParticipantCount() > 0)
|
|
|
|
{
|
|
|
|
const auto& player = Game::server->GetReplicaManager()->GetParticipantAtIndex(0);
|
|
|
|
|
|
|
|
Game::server->Disconnect(player, SERVER_DISCON_KICK);
|
|
|
|
}
|
|
|
|
|
|
|
|
CBITSTREAM;
|
|
|
|
PacketUtils::WriteHeader(bitStream, MASTER, MSG_MASTER_SHUTDOWN_RESPONSE);
|
|
|
|
Game::server->SendToMaster(&bitStream);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (framesSinceShutdownSequence == 300)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Metrics::AddMeasurement(MetricVariable::CPUTime, (1e6 * (1000.0 * (std::clock() - metricCPUTimeStart))) / CLOCKS_PER_SEC);
|
|
|
|
Metrics::EndMeasurement(MetricVariable::Frame);
|
|
|
|
}
|
|
|
|
|
|
|
|
//Delete our objects here:
|
|
|
|
if (Game::physicsWorld) Game::physicsWorld = nullptr;
|
|
|
|
if (Game::zoneManager) delete Game::zoneManager;
|
|
|
|
|
|
|
|
Game::logger->Log("Test", "Quitting\n");
|
|
|
|
|
|
|
|
Metrics::Clear();
|
|
|
|
Database::Destroy();
|
|
|
|
delete Game::chatFilter;
|
|
|
|
delete Game::server;
|
|
|
|
delete Game::logger;
|
|
|
|
|
|
|
|
worldShutdownSequenceComplete = true;
|
|
|
|
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
dLogger * SetupLogger(int zoneID, int instanceID) {
|
|
|
|
std::string logPath = "./logs/WorldServer_" + std::to_string(zoneID) + "_" + std::to_string(instanceID) + "_" + std::to_string(time(nullptr)) + ".log";
|
|
|
|
bool logToConsole = false;
|
|
|
|
#ifdef _DEBUG
|
|
|
|
logToConsole = true;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return new dLogger(logPath, logToConsole);
|
|
|
|
}
|
|
|
|
|
|
|
|
void HandlePacketChat(Packet* packet) {
|
|
|
|
if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) {
|
|
|
|
Game::logger->Log("WorldServer", "Lost our connection to chat.\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (packet->data[0] == ID_CONNECTION_REQUEST_ACCEPTED) {
|
|
|
|
Game::logger->Log("WorldServer", "Established connection to chat\n");
|
|
|
|
Game::chatSysAddr = packet->systemAddress;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (packet->data[0] == ID_USER_PACKET_ENUM) {
|
|
|
|
if (packet->data[1] == CHAT_INTERNAL) {
|
|
|
|
switch (packet->data[3]) {
|
|
|
|
case MSG_CHAT_INTERNAL_ROUTE_TO_PLAYER: {
|
|
|
|
CINSTREAM;
|
|
|
|
LWOOBJID playerID;
|
|
|
|
inStream.Read(playerID);
|
|
|
|
inStream.Read(playerID);
|
|
|
|
|
|
|
|
auto player = EntityManager::Instance()->GetEntity(playerID);
|
|
|
|
if (!player) return;
|
|
|
|
|
|
|
|
auto sysAddr = player->GetSystemAddress();
|
|
|
|
|
|
|
|
//Write our stream outwards:
|
|
|
|
CBITSTREAM;
|
|
|
|
for (int i = 0; i < inStream.GetNumberOfBytesUsed(); i++) {
|
|
|
|
bitStream.Write(packet->data[i + 16]); //16 bytes == header + playerID to skip
|
|
|
|
}
|
|
|
|
|
|
|
|
SEND_PACKET; //send routed packet to player
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_CHAT_INTERNAL_ANNOUNCEMENT: {
|
|
|
|
CINSTREAM;
|
|
|
|
LWOOBJID header;
|
|
|
|
inStream.Read(header);
|
|
|
|
|
|
|
|
RakNet::RakString title;
|
|
|
|
RakNet::RakString msg;
|
|
|
|
|
|
|
|
inStream.Read(title);
|
|
|
|
inStream.Read(msg);
|
|
|
|
|
|
|
|
//Send to our clients:
|
|
|
|
AMFArrayValue args;
|
|
|
|
auto* titleValue = new AMFStringValue();
|
|
|
|
titleValue->SetStringValue(title.C_String());
|
|
|
|
auto* messageValue = new AMFStringValue();
|
|
|
|
messageValue->SetStringValue(msg.C_String());
|
|
|
|
|
|
|
|
args.InsertValue("title", titleValue);
|
|
|
|
args.InsertValue("message", messageValue);
|
|
|
|
|
|
|
|
GameMessages::SendUIMessageServerToAllClients("ToggleAnnounce", &args);
|
|
|
|
|
|
|
|
delete titleValue;
|
|
|
|
delete messageValue;
|
|
|
|
titleValue = nullptr;
|
|
|
|
messageValue = nullptr;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_CHAT_INTERNAL_MUTE_UPDATE: {
|
|
|
|
CINSTREAM;
|
|
|
|
LWOOBJID playerId;
|
|
|
|
time_t expire = 0;
|
|
|
|
inStream.Read(playerId);
|
|
|
|
inStream.Read(playerId);
|
|
|
|
inStream.Read(expire);
|
|
|
|
|
|
|
|
auto* entity = EntityManager::Instance()->GetEntity(playerId);
|
|
|
|
|
|
|
|
if (entity != nullptr)
|
|
|
|
{
|
|
|
|
entity->GetParentUser()->SetMuteExpire(expire);
|
|
|
|
|
|
|
|
entity->GetCharacter()->SendMuteNotice();
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_CHAT_INTERNAL_TEAM_UPDATE: {
|
|
|
|
CINSTREAM;
|
|
|
|
LWOOBJID header;
|
|
|
|
inStream.Read(header);
|
|
|
|
|
|
|
|
LWOOBJID teamID = 0;
|
|
|
|
char lootOption = 0;
|
|
|
|
char memberCount = 0;
|
|
|
|
std::vector<LWOOBJID> members;
|
|
|
|
|
|
|
|
inStream.Read(teamID);
|
|
|
|
bool deleteTeam = inStream.ReadBit();
|
|
|
|
|
|
|
|
if (deleteTeam)
|
|
|
|
{
|
|
|
|
TeamManager::Instance()->DeleteTeam(teamID);
|
|
|
|
|
|
|
|
Game::logger->Log("WorldServer", "Deleting team (%llu)\n", teamID);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
inStream.Read(lootOption);
|
|
|
|
inStream.Read(memberCount);
|
|
|
|
Game::logger->Log("WorldServer", "Updating team (%llu), (%i), (%i)\n", teamID, lootOption, memberCount);
|
|
|
|
for (char i = 0; i < memberCount; i++)
|
|
|
|
{
|
|
|
|
LWOOBJID member = LWOOBJID_EMPTY;
|
|
|
|
inStream.Read(member);
|
|
|
|
members.push_back(member);
|
|
|
|
|
|
|
|
Game::logger->Log("WorldServer", "Updating team member (%llu)\n", member);
|
|
|
|
}
|
|
|
|
|
|
|
|
TeamManager::Instance()->UpdateTeam(teamID, lootOption, members);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
Game::logger->Log("WorldServer", "Received an unknown chat internal: %i\n", 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);
|
|
|
|
if (!user) return;
|
|
|
|
|
|
|
|
auto c = user->GetLastUsedChar();
|
|
|
|
if (!c) {
|
|
|
|
UserManager::Instance()->DeleteUser(packet->systemAddress);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto* entity = EntityManager::Instance()->GetEntity(c->GetObjectID());
|
|
|
|
|
|
|
|
if (!entity)
|
|
|
|
{
|
|
|
|
entity = Player::GetPlayer(packet->systemAddress);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (entity) {
|
|
|
|
auto* skillComponent = entity->GetComponent<SkillComponent>();
|
|
|
|
|
|
|
|
if (skillComponent != nullptr)
|
|
|
|
{
|
|
|
|
skillComponent->Reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
entity->GetCharacter()->SaveXMLToDatabase();
|
|
|
|
|
|
|
|
Game::logger->Log("WorldServer", "Deleting player %llu\n", entity->GetObjectID());
|
|
|
|
|
|
|
|
EntityManager::Instance()->DestroyEntity(entity);
|
|
|
|
|
|
|
|
CBITSTREAM;
|
|
|
|
PacketUtils::WriteHeader(bitStream, CHAT_INTERNAL, MSG_CHAT_INTERNAL_PLAYER_REMOVED_NOTIFICATION);
|
|
|
|
bitStream.Write(c->GetObjectID());
|
|
|
|
Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
UserManager::Instance()->DeleteUser(packet->systemAddress);
|
|
|
|
|
|
|
|
if (PropertyManagementComponent::Instance() != nullptr) {
|
|
|
|
PropertyManagementComponent::Instance()->Save();
|
|
|
|
}
|
|
|
|
|
|
|
|
CBITSTREAM;
|
|
|
|
PacketUtils::WriteHeader(bitStream, MASTER, MSG_MASTER_PLAYER_REMOVED);
|
|
|
|
bitStream.Write((LWOMAPID)Game::server->GetZoneID());
|
|
|
|
bitStream.Write((LWOINSTANCEID)instanceID);
|
|
|
|
Game::server->SendToMaster(&bitStream);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (packet->data[0] != ID_USER_PACKET_ENUM) return;
|
|
|
|
if (packet->data[1] == SERVER) {
|
|
|
|
if (packet->data[3] == MSG_SERVER_VERSION_CONFIRM) {
|
|
|
|
AuthPackets::HandleHandshake(Game::server, packet);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (packet->data[1] == MASTER) {
|
|
|
|
switch (packet->data[3]) {
|
|
|
|
case MSG_MASTER_REQUEST_PERSISTENT_ID_RESPONSE: {
|
|
|
|
uint64_t requestID = PacketUtils::ReadPacketU64(8, packet);
|
|
|
|
uint32_t objectID = PacketUtils::ReadPacketU32(16, packet);
|
|
|
|
ObjectIDManager::Instance()->HandleRequestPersistentIDResponse(requestID, objectID);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_MASTER_REQUEST_ZONE_TRANSFER_RESPONSE: {
|
|
|
|
uint64_t requestID = PacketUtils::ReadPacketU64(8, packet);
|
|
|
|
ZoneInstanceManager::Instance()->HandleRequestZoneTransferResponse(requestID, packet);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_MASTER_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) {
|
|
|
|
Game::logger->Log("WorldServer", "SOMEONE IS TRYING TO HACK? SESSION KEY MISMATCH: ours: %s != master: %s\n", userHash.c_str(), it->second.hash.c_str());
|
|
|
|
Game::server->Disconnect(it->second.sysAddr, SERVER_DISCON_INVALID_SESSION_KEY);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
Game::logger->Log("WorldServer", "User %s authenticated with correct key.\n", 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 = dZoneManager::Instance()->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;
|
|
|
|
PacketUtils::WriteHeader(bitStream, MASTER, MSG_MASTER_PLAYER_ADDED);
|
|
|
|
bitStream.Write((LWOMAPID)Game::server->GetZoneID());
|
|
|
|
bitStream.Write((LWOINSTANCEID)instanceID);
|
|
|
|
Game::server->SendToMaster(&bitStream);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MSG_MASTER_AFFIRM_TRANSFER_REQUEST: {
|
|
|
|
const uint64_t requestID = PacketUtils::ReadPacketU64(8, packet);
|
|
|
|
|
|
|
|
Game::logger->Log("MasterServer", "Got affirmation request of transfer %llu\n", requestID);
|
|
|
|
|
|
|
|
CBITSTREAM
|
|
|
|
|
|
|
|
PacketUtils::WriteHeader(bitStream, MASTER, MSG_MASTER_AFFIRM_TRANSFER_RESPONSE);
|
|
|
|
bitStream.Write(requestID);
|
|
|
|
Game::server->SendToMaster(&bitStream);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_MASTER_SHUTDOWN: {
|
|
|
|
worldShutdownSequenceStarted = true;
|
|
|
|
Game::logger->Log("WorldServer", "Got shutdown request\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_MASTER_NEW_SESSION_ALERT: {
|
|
|
|
RakNet::BitStream inStream(packet->data, packet->length, false);
|
|
|
|
uint64_t header = inStream.Read(header);
|
|
|
|
uint32_t sessionKey = inStream.Read(sessionKey);
|
|
|
|
RakNet::RakString username;
|
|
|
|
inStream.Read(username);
|
|
|
|
|
|
|
|
//Find them:
|
|
|
|
User* user = UserManager::Instance()->GetUser(username.C_String());
|
|
|
|
if (!user) {
|
|
|
|
Game::logger->Log("WorldServer", "Got new session alert for user %s, but they're not logged in.\n", username.C_String());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Check the key:
|
|
|
|
if (sessionKey != std::atoi(user->GetSessionKey().c_str())) {
|
|
|
|
Game::logger->Log("WorldServer", "Got new session alert for user %s, but the session key is invalid.\n", username.C_String());
|
|
|
|
Game::server->Disconnect(user->GetSystemAddress(), SERVER_DISCON_INVALID_SESSION_KEY);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
Game::logger->Log("WorldServer", "Unknown packet ID from master %i\n", int(packet->data[3]));
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (packet->data[1] != WORLD) return;
|
2021-12-07 18:58:24 +00:00
|
|
|
|
2021-12-05 17:54:36 +00:00
|
|
|
switch (packet->data[3]) {
|
|
|
|
case MSG_WORLD_CLIENT_VALIDATION: {
|
|
|
|
std::string username = PacketUtils::ReadString(0x08, packet, true);
|
|
|
|
std::string sessionKey = PacketUtils::ReadString(74, packet, true);
|
2021-12-12 15:14:04 +00:00
|
|
|
std::string theirFdbChecksum = PacketUtils::ReadString(packet->length - 33, packet, false);
|
2021-12-15 18:45:42 +00:00
|
|
|
theirFdbChecksum = theirFdbChecksum.substr(0, 32); // sometimes client puts a null terminator at the end of the checksum and sometimes doesn't; weird
|
2021-12-12 03:41:11 +00:00
|
|
|
|
2021-12-14 18:12:44 +00:00
|
|
|
if (Game::config->GetValue("check_fdb") == "1" && fdbChecksum != "") { // if fdbChecksum is empty, likely means we are a character server.
|
2021-12-14 19:25:30 +00:00
|
|
|
uint32_t gmLevel = 0;
|
|
|
|
sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT gm_level FROM accounts WHERE name=? LIMIT 1;");
|
|
|
|
stmt->setString(1, username.c_str());
|
|
|
|
|
|
|
|
sql::ResultSet* res = stmt->executeQuery();
|
|
|
|
while (res->next()) {
|
|
|
|
gmLevel = res->getInt(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
delete stmt;
|
|
|
|
delete res;
|
|
|
|
|
|
|
|
if (gmLevel != 9) {
|
|
|
|
Game::logger->Log("WorldServer", "Got client checksum %s and we have server checksum %s. \n", theirFdbChecksum.c_str(), fdbChecksum.c_str());
|
|
|
|
if (theirFdbChecksum != fdbChecksum) {
|
|
|
|
Game::logger->Log("WorldServer", "Client checksum does not match server checksum.\n");
|
|
|
|
Game::server->Disconnect(packet->systemAddress, SERVER_DISCON_KICK);
|
|
|
|
return;
|
|
|
|
}
|
2021-12-12 03:41:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-05 17:54:36 +00:00
|
|
|
//Request the session info from Master:
|
|
|
|
CBITSTREAM;
|
|
|
|
PacketUtils::WriteHeader(bitStream, MASTER, MSG_MASTER_REQUEST_SESSION_KEY);
|
|
|
|
PacketUtils::WriteString(bitStream, username, 64);
|
|
|
|
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));
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_WORLD_CLIENT_CHARACTER_LIST_REQUEST: {
|
|
|
|
//We need to delete the entity first, otherwise the char list could delete it while it exists in the world!
|
|
|
|
if (Game::server->GetZoneID() != 0) {
|
|
|
|
auto user = UserManager::Instance()->GetUser(packet->systemAddress);
|
|
|
|
if (!user) return;
|
|
|
|
EntityManager::Instance()->DestroyEntity(user->GetLastUsedChar()->GetEntity());
|
|
|
|
}
|
|
|
|
|
|
|
|
//This loops prevents users who aren't authenticated to double-request the char list, which
|
|
|
|
//would make the login screen freeze sometimes.
|
|
|
|
if (m_PendingUsers.size() > 0) {
|
|
|
|
for (auto it : m_PendingUsers) {
|
|
|
|
if (it.second.sysAddr == packet->systemAddress) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
UserManager::Instance()->RequestCharacterList(packet->systemAddress);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_WORLD_CLIENT_GAME_MSG: {
|
|
|
|
RakNet::BitStream bitStream(packet->data, packet->length, false);
|
|
|
|
|
|
|
|
uint64_t header;
|
|
|
|
LWOOBJID objectID;
|
|
|
|
uint16_t messageID;
|
|
|
|
|
|
|
|
bitStream.Read(header);
|
|
|
|
bitStream.Read(objectID);
|
|
|
|
bitStream.Read(messageID);
|
|
|
|
|
|
|
|
RakNet::BitStream dataStream;
|
|
|
|
bitStream.Read(dataStream, bitStream.GetNumberOfUnreadBits());
|
|
|
|
|
|
|
|
GameMessageHandler::HandleMessage(&dataStream, packet->systemAddress, objectID, GAME_MSG(messageID));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_WORLD_CLIENT_CHARACTER_CREATE_REQUEST: {
|
|
|
|
UserManager::Instance()->CreateCharacter(packet->systemAddress, packet);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_WORLD_CLIENT_LOGIN_REQUEST: {
|
|
|
|
RakNet::BitStream inStream(packet->data, packet->length, false);
|
|
|
|
uint64_t header = inStream.Read(header);
|
|
|
|
|
|
|
|
LWOOBJID playerID = 0;
|
|
|
|
inStream.Read(playerID);
|
|
|
|
playerID = GeneralUtils::ClearBit(playerID, OBJECT_BIT_CHARACTER);
|
|
|
|
playerID = GeneralUtils::ClearBit(playerID, OBJECT_BIT_PERSISTENT);
|
|
|
|
|
|
|
|
UserManager::Instance()->LoginCharacter(packet->systemAddress, static_cast<uint32_t>(playerID));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_WORLD_CLIENT_CHARACTER_DELETE_REQUEST: {
|
|
|
|
UserManager::Instance()->DeleteCharacter(packet->systemAddress, packet);
|
|
|
|
UserManager::Instance()->RequestCharacterList(packet->systemAddress);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_WORLD_CLIENT_CHARACTER_RENAME_REQUEST: {
|
|
|
|
UserManager::Instance()->RenameCharacter(packet->systemAddress, packet);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_WORLD_CLIENT_LEVEL_LOAD_COMPLETE: {
|
|
|
|
Game::logger->Log("WorldServer", "Received level load complete from user.\n");
|
|
|
|
User* user = UserManager::Instance()->GetUser(packet->systemAddress);
|
|
|
|
if (user) {
|
|
|
|
Character* c = user->GetLastUsedChar();
|
|
|
|
if (c != nullptr) {
|
|
|
|
std::u16string username = GeneralUtils::ASCIIToUTF16(c->GetName());
|
|
|
|
WorldPackets::SendCreateCharacter(packet->systemAddress, c->GetObjectID(), c->GetXMLData(), username, c->GetGMLevel());
|
|
|
|
WorldPackets::SendServerState(packet->systemAddress);
|
|
|
|
|
|
|
|
Game::server->GetReplicaManager()->AddParticipant(packet->systemAddress);
|
|
|
|
|
|
|
|
EntityInfo info {};
|
|
|
|
info.lot = 1;
|
|
|
|
Entity* player = EntityManager::Instance()->CreateEntity(info, UserManager::Instance()->GetUser(packet->systemAddress));
|
|
|
|
|
|
|
|
const auto respawnPoint = player->GetCharacter()->GetRespawnPoint(dZoneManager::Instance()->GetZone()->GetWorldID());
|
|
|
|
|
|
|
|
EntityManager::Instance()->ConstructEntity(player, UNASSIGNED_SYSTEM_ADDRESS, true);
|
|
|
|
|
|
|
|
if (respawnPoint != NiPoint3::ZERO)
|
|
|
|
{
|
|
|
|
GameMessages::SendPlayerReachedRespawnCheckpoint(player, respawnPoint, NiQuaternion::IDENTITY);
|
|
|
|
}
|
|
|
|
|
|
|
|
EntityManager::Instance()->ConstructAllEntities(packet->systemAddress);
|
|
|
|
|
|
|
|
player->GetComponent<CharacterComponent>()->SetLastRocketConfig(u"");
|
|
|
|
player->GetCharacter()->SetTargetScene("");
|
|
|
|
|
|
|
|
// Fix the destroyable component
|
|
|
|
auto* destroyableComponent = player->GetComponent<DestroyableComponent>();
|
|
|
|
|
|
|
|
if (destroyableComponent != nullptr)
|
|
|
|
{
|
|
|
|
destroyableComponent->FixStats();
|
|
|
|
}
|
|
|
|
|
|
|
|
//Tell the player to generate BBB models, if any:
|
|
|
|
if (g_CloneID != 0) {
|
|
|
|
const auto& worldId = dZoneManager::Instance()->GetZone()->GetZoneID();
|
|
|
|
|
|
|
|
const auto zoneId = Game::server->GetZoneID();
|
|
|
|
const auto cloneId = g_CloneID;
|
|
|
|
|
|
|
|
std::stringstream query;
|
|
|
|
|
|
|
|
query << "SELECT id FROM PropertyTemplate WHERE mapID = " << std::to_string(zoneId) << ";";
|
|
|
|
|
|
|
|
auto result = CDClientDatabase::ExecuteQuery(query.str());
|
|
|
|
|
|
|
|
if (result.eof() || result.fieldIsNull(0)) {
|
|
|
|
Game::logger->Log("WorldServer", "No property templates found for zone %d, not sending BBB\n", zoneId);
|
|
|
|
goto noBBB;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Check for BBB models:
|
|
|
|
auto stmt = Database::CreatePreppedStmt("SELECT ugc_id FROM properties_contents WHERE lot=14 AND property_id=?");
|
|
|
|
|
|
|
|
int templateId = result.getIntField(0);
|
|
|
|
|
|
|
|
result.finalize();
|
|
|
|
|
|
|
|
auto* propertyLookup = Database::CreatePreppedStmt("SELECT * FROM properties WHERE template_id = ? AND clone_id = ?;");
|
|
|
|
|
|
|
|
propertyLookup->setInt(1, templateId);
|
|
|
|
propertyLookup->setInt64(2, g_CloneID);
|
|
|
|
|
|
|
|
auto* propertyEntry = propertyLookup->executeQuery();
|
|
|
|
uint64_t propertyId = 0;
|
|
|
|
|
|
|
|
if (propertyEntry->next()) {
|
|
|
|
propertyId = propertyEntry->getUInt64(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
delete propertyLookup;
|
|
|
|
|
|
|
|
stmt->setUInt64(1, propertyId);
|
|
|
|
auto res = stmt->executeQuery();
|
|
|
|
while (res->next()) {
|
|
|
|
Game::logger->Log("UGC", "Getting lxfml ugcID: " + std::to_string(res->getUInt(1)) + "\n");
|
|
|
|
|
|
|
|
//Get lxfml:
|
|
|
|
auto stmtL = Database::CreatePreppedStmt("SELECT lxfml from ugc where id=?");
|
|
|
|
stmtL->setUInt(1, res->getUInt(1));
|
|
|
|
|
|
|
|
auto lxres = stmtL->executeQuery();
|
|
|
|
|
|
|
|
while (lxres->next()) {
|
|
|
|
auto lxfml = lxres->getBlob(1);
|
|
|
|
|
|
|
|
lxfml->seekg(0, std::ios::end);
|
|
|
|
size_t lxfmlSize = lxfml->tellg();
|
|
|
|
lxfml->seekg(0);
|
|
|
|
|
|
|
|
//Send message:
|
|
|
|
{
|
|
|
|
LWOOBJID blueprintID = res->getUInt(1);
|
|
|
|
blueprintID = GeneralUtils::SetBit(blueprintID, OBJECT_BIT_CHARACTER);
|
|
|
|
blueprintID = GeneralUtils::SetBit(blueprintID, OBJECT_BIT_PERSISTENT);
|
|
|
|
|
|
|
|
CBITSTREAM;
|
|
|
|
PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_BLUEPRINT_SAVE_RESPONSE);
|
|
|
|
bitStream.Write<LWOOBJID>(0); //always zero so that a check on the client passes
|
|
|
|
bitStream.Write<unsigned int>(0);
|
|
|
|
bitStream.Write<unsigned int>(1);
|
|
|
|
bitStream.Write(blueprintID);
|
|
|
|
|
|
|
|
bitStream.Write<uint32_t>(lxfmlSize + 9);
|
|
|
|
|
|
|
|
//Write a fake sd0 header:
|
|
|
|
bitStream.Write<unsigned char>(0x73); //s
|
|
|
|
bitStream.Write<unsigned char>(0x64); //d
|
|
|
|
bitStream.Write<unsigned char>(0x30); //0
|
|
|
|
bitStream.Write<unsigned char>(0x01); //1
|
|
|
|
bitStream.Write<unsigned char>(0xFF); //end magic
|
|
|
|
|
|
|
|
bitStream.Write<uint32_t>(lxfmlSize);
|
|
|
|
|
|
|
|
for (size_t i = 0; i < lxfmlSize; ++i)
|
|
|
|
bitStream.Write<uint8_t>(lxfml->get());
|
|
|
|
|
|
|
|
SystemAddress sysAddr = packet->systemAddress;
|
|
|
|
SEND_PACKET;
|
|
|
|
PacketUtils::SavePacket("lxfml packet " + std::to_string(res->getUInt(1)) + ".bin", (char*)bitStream.GetData(), bitStream.GetNumberOfBytesUsed());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
delete stmtL;
|
|
|
|
delete lxres;
|
|
|
|
}
|
|
|
|
|
|
|
|
delete stmt;
|
|
|
|
delete res;
|
|
|
|
}
|
|
|
|
|
|
|
|
noBBB:
|
|
|
|
|
|
|
|
// Tell the client it's done loading:
|
|
|
|
GameMessages::SendInvalidZoneTransferList(player, packet->systemAddress, u"https://forms.zohopublic.eu/virtualoffice204/form/DLUInGameSurvey/formperma/kpU-IL5v2-Wt41QcB5UFnYjzlLp-j2LEisF8e11PisU", u"", false, false);
|
|
|
|
GameMessages::SendServerDoneLoadingAllObjects(player, packet->systemAddress);
|
|
|
|
|
|
|
|
//Send the player it's mail count:
|
|
|
|
//update: this might not be needed so im going to try disabling this here.
|
|
|
|
//Mail::HandleNotificationRequest(packet->systemAddress, player->GetObjectID());
|
|
|
|
|
|
|
|
//Notify chat that a player has loaded:
|
|
|
|
{
|
|
|
|
const auto& playerName = player->GetCharacter()->GetName();
|
|
|
|
//RakNet::RakString playerName(player->GetCharacter()->GetName().c_str());
|
|
|
|
|
|
|
|
CBITSTREAM;
|
|
|
|
PacketUtils::WriteHeader(bitStream, CHAT_INTERNAL, MSG_CHAT_INTERNAL_PLAYER_ADDED_NOTIFICATION);
|
|
|
|
bitStream.Write(player->GetObjectID());
|
|
|
|
bitStream.Write<uint16_t>(playerName.size());
|
|
|
|
for (size_t i = 0; i < playerName.size(); i++)
|
|
|
|
{
|
|
|
|
bitStream.Write(playerName[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
//bitStream.Write(playerName);
|
|
|
|
|
|
|
|
auto zone = dZoneManager::Instance()->GetZone()->GetZoneID();
|
|
|
|
bitStream.Write(zone.GetMapID());
|
|
|
|
bitStream.Write(zone.GetInstanceID());
|
|
|
|
bitStream.Write(zone.GetCloneID());
|
|
|
|
bitStream.Write(player->GetParentUser()->GetMuteExpire());
|
|
|
|
|
|
|
|
Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
Game::logger->Log("WorldMain", "Couldn't find character to log in with for user %s (%i)!\n", user->GetUsername().c_str(), user->GetAccountID());
|
|
|
|
Game::server->Disconnect(packet->systemAddress, SERVER_DISCON_CHARACTER_NOT_FOUND);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Game::logger->Log("WorldMain", "Couldn't get user for level load complete!\n");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_WORLD_CLIENT_POSITION_UPDATE: {
|
|
|
|
ClientPackets::HandleClientPositionUpdate(packet->systemAddress, packet);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_WORLD_CLIENT_MAIL: {
|
|
|
|
RakNet::BitStream bitStream(packet->data, packet->length, false);
|
|
|
|
LWOOBJID space;
|
|
|
|
bitStream.Read(space);
|
|
|
|
Mail::HandleMailStuff(&bitStream, packet->systemAddress, UserManager::Instance()->GetUser(packet->systemAddress)->GetLastUsedChar()->GetEntity());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_WORLD_CLIENT_ROUTE_PACKET: {
|
|
|
|
//Yeet to chat
|
|
|
|
CINSTREAM;
|
|
|
|
uint64_t header = 0;
|
|
|
|
uint32_t size = 0;
|
|
|
|
inStream.Read(header);
|
|
|
|
inStream.Read(size);
|
|
|
|
|
|
|
|
if (size > 20000) {
|
|
|
|
Game::logger->Log("WorldServer", "Tried to route a packet with a read size > 20000, so likely a false packet.\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
CBITSTREAM;
|
|
|
|
|
|
|
|
PacketUtils::WriteHeader(bitStream, CHAT, packet->data[14]);
|
|
|
|
|
|
|
|
//We need to insert the player's objectID so the chat server can find who originated this request:
|
|
|
|
LWOOBJID objectID = 0;
|
|
|
|
auto user = UserManager::Instance()->GetUser(packet->systemAddress);
|
|
|
|
if (user) {
|
|
|
|
objectID = user->GetLastUsedChar()->GetObjectID();
|
|
|
|
}
|
|
|
|
|
|
|
|
bitStream.Write(objectID);
|
|
|
|
|
|
|
|
//Now write the rest of the data:
|
|
|
|
auto data = inStream.GetData();
|
|
|
|
for (uint32_t i = 0; i < size; ++i) {
|
|
|
|
bitStream.Write(data[i+23]);
|
|
|
|
}
|
|
|
|
|
|
|
|
Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE_ORDERED, 0, Game::chatSysAddr, false);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_WORLD_CLIENT_STRING_CHECK: {
|
|
|
|
ClientPackets::HandleChatModerationRequest(packet->systemAddress, packet);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_WORLD_CLIENT_GENERAL_CHAT_MESSAGE: {
|
|
|
|
if (chatDisabled) {
|
|
|
|
ChatPackets::SendMessageFail(packet->systemAddress);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
ClientPackets::HandleChatMessage(packet->systemAddress, packet);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case MSG_WORLD_CLIENT_HANDLE_FUNNESS: {
|
|
|
|
//This means the client is running slower or faster than it should.
|
|
|
|
//Could be insane lag, but I'mma just YEET them as it's usually speedhacking.
|
|
|
|
//This is updated to now count the amount of times we've been caught "speedhacking" to kick with a delay
|
|
|
|
//This is hopefully going to fix the random disconnects people face sometimes.
|
|
|
|
if (Game::config->GetValue("disable_anti_speedhack") == "1") {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
User* user = UserManager::Instance()->GetUser(packet->systemAddress);
|
|
|
|
if (user) {
|
|
|
|
user->UserOutOfSync();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
Game::server->Disconnect(packet->systemAddress, SERVER_DISCON_KICK);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
Game::server->GetLogger()->Log("HandlePacket", "Unknown world packet received: %i\n", int(packet->data[3]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void WorldShutdownSequence()
|
|
|
|
{
|
|
|
|
if (worldShutdownSequenceStarted || worldShutdownSequenceComplete)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
worldShutdownSequenceStarted = true;
|
|
|
|
|
|
|
|
auto t = std::chrono::high_resolution_clock::now();
|
|
|
|
auto ticks = 0;
|
|
|
|
|
|
|
|
printf("Attempting to shutdown world, max 10 seconds...");;
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
if (worldShutdownSequenceStarted)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
t += std::chrono::milliseconds(highFrameRate);
|
|
|
|
std::this_thread::sleep_until(t);
|
|
|
|
|
|
|
|
ticks++;
|
|
|
|
|
|
|
|
if (ticks == 600)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|