Compare commits

...

5 Commits

Author SHA1 Message Date
100b4605ec Fix early CNShared timeout
Revisiting this again; the issue was that the comparison operator was
facing the wrong way, so connections were being pruned every 30 seconds
or less. This was effectively a race condition kicking an unlucky player
every so often when pruning happened exactly during an attempt to enter
the game.

Now that the proper timeout is being enforced, I've reduced it to 5
minutes, down from 15, since it really doesn't need to be that long.
2022-12-11 19:46:29 +01:00
741b898230 Remove redundant copy of Player object when added to the shard
Since the Player object is loaded up in loadPlayer() now, it's pretty
apparent that there's no more reason to copy it around at any point.
2022-12-06 02:11:31 +01:00
3f44f53f97 On login, load Player from DB in shard thread, not in login thread
This avoids some needless data shuffling and fixes a rare desync.
2022-12-06 01:07:21 +01:00
d92b407349 Fix sanity check in emailReceiveItemSingle() 2022-12-05 22:30:02 +01:00
9b3e856a05 Sync player data to DB when trading and sending emails 2022-12-05 22:29:23 +01:00
10 changed files with 152 additions and 97 deletions

View File

@ -93,7 +93,7 @@ static void emailReceiveItemSingle(CNSocket* sock, CNPacketData* data) {
auto pkt = (sP_CL2FE_REQ_PC_RECV_EMAIL_ITEM*)data->buf; auto pkt = (sP_CL2FE_REQ_PC_RECV_EMAIL_ITEM*)data->buf;
Player* plr = PlayerManager::getPlayer(sock); Player* plr = PlayerManager::getPlayer(sock);
if (pkt->iSlotNum < 0 || pkt->iSlotNum >= AINVEN_COUNT || pkt->iSlotNum < 1 || pkt->iSlotNum > 4) if (pkt->iSlotNum < 0 || pkt->iSlotNum >= AINVEN_COUNT || pkt->iEmailItemSlot < 1 || pkt->iEmailItemSlot > 4)
return; // sanity check return; // sanity check
// get email item from db and delete it // get email item from db and delete it
@ -276,7 +276,7 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
0 // DeleteTime (unimplemented) 0 // DeleteTime (unimplemented)
}; };
if (!Database::sendEmail(&email, attachments)) { if (!Database::sendEmail(&email, attachments, plr)) {
plr->money += cost; // give money back plr->money += cost; // give money back
// give items back // give items back
while (!attachments.empty()) { while (!attachments.empty()) {

View File

@ -28,17 +28,12 @@ using namespace PlayerManager;
std::map<CNSocket*, Player*> PlayerManager::players; std::map<CNSocket*, Player*> PlayerManager::players;
static void addPlayer(CNSocket* key, Player& plr) { static void addPlayer(CNSocket* key, Player *plr) {
Player *p = new Player(); players[key] = plr;
plr->chunkPos = Chunking::INVALID_CHUNK;
plr->lastHeartbeat = 0;
// copy object into heap memory std::cout << getPlayerName(plr) << " has joined!" << std::endl;
*p = plr;
players[key] = p;
p->chunkPos = Chunking::INVALID_CHUNK;
p->lastHeartbeat = 0;
std::cout << getPlayerName(p) << " has joined!" << std::endl;
std::cout << players.size() << " players" << std::endl; std::cout << players.size() << " players" << std::endl;
} }
@ -224,69 +219,73 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
return; return;
} }
// for convenience Player *plr = new Player();
Player& plr = lm->plr; Database::getPlayer(plr, lm->playerId);
plr.groupCnt = 1;
plr.iIDGroup = plr.groupIDs[0] = plr.iID;
// check if account is already in use // check if account is already in use
if (isAccountInUse(plr.accountId)) { if (isAccountInUse(plr->accountId)) {
// kick the other player // kick the other player
exitDuplicate(plr.accountId); exitDuplicate(plr->accountId);
// re-read the player from disk, in case it was just flushed
*plr = {};
Database::getPlayer(plr, lm->playerId);
} }
response.iID = plr.iID; plr->groupCnt = 1;
plr->iIDGroup = plr->groupIDs[0] = plr->iID;
response.iID = plr->iID;
response.uiSvrTime = getTime(); response.uiSvrTime = getTime();
response.PCLoadData2CL.iUserLevel = plr.accountLevel; response.PCLoadData2CL.iUserLevel = plr->accountLevel;
response.PCLoadData2CL.iHP = plr.HP; response.PCLoadData2CL.iHP = plr->HP;
response.PCLoadData2CL.iLevel = plr.level; response.PCLoadData2CL.iLevel = plr->level;
response.PCLoadData2CL.iCandy = plr.money; response.PCLoadData2CL.iCandy = plr->money;
response.PCLoadData2CL.iFusionMatter = plr.fusionmatter; response.PCLoadData2CL.iFusionMatter = plr->fusionmatter;
response.PCLoadData2CL.iMentor = plr.mentor; response.PCLoadData2CL.iMentor = plr->mentor;
response.PCLoadData2CL.iMentorCount = 1; // how many guides the player has had response.PCLoadData2CL.iMentorCount = 1; // how many guides the player has had
response.PCLoadData2CL.iX = plr.x; response.PCLoadData2CL.iX = plr->x;
response.PCLoadData2CL.iY = plr.y; response.PCLoadData2CL.iY = plr->y;
response.PCLoadData2CL.iZ = plr.z; response.PCLoadData2CL.iZ = plr->z;
response.PCLoadData2CL.iAngle = plr.angle; response.PCLoadData2CL.iAngle = plr->angle;
response.PCLoadData2CL.iBatteryN = plr.batteryN; response.PCLoadData2CL.iBatteryN = plr->batteryN;
response.PCLoadData2CL.iBatteryW = plr.batteryW; response.PCLoadData2CL.iBatteryW = plr->batteryW;
response.PCLoadData2CL.iBuddyWarpTime = 60; // sets 60s warp cooldown on login response.PCLoadData2CL.iBuddyWarpTime = 60; // sets 60s warp cooldown on login
response.PCLoadData2CL.iWarpLocationFlag = plr.iWarpLocationFlag; response.PCLoadData2CL.iWarpLocationFlag = plr->iWarpLocationFlag;
response.PCLoadData2CL.aWyvernLocationFlag[0] = plr.aSkywayLocationFlag[0]; response.PCLoadData2CL.aWyvernLocationFlag[0] = plr->aSkywayLocationFlag[0];
response.PCLoadData2CL.aWyvernLocationFlag[1] = plr.aSkywayLocationFlag[1]; response.PCLoadData2CL.aWyvernLocationFlag[1] = plr->aSkywayLocationFlag[1];
response.PCLoadData2CL.iActiveNanoSlotNum = -1; response.PCLoadData2CL.iActiveNanoSlotNum = -1;
response.PCLoadData2CL.iFatigue = 50; response.PCLoadData2CL.iFatigue = 50;
response.PCLoadData2CL.PCStyle = plr.PCStyle; response.PCLoadData2CL.PCStyle = plr->PCStyle;
// client doesnt read this, it gets it from charinfo // client doesnt read this, it gets it from charinfo
// response.PCLoadData2CL.PCStyle2 = plr.PCStyle2; // response.PCLoadData2CL.PCStyle2 = plr->PCStyle2;
// inventory // inventory
for (int i = 0; i < AEQUIP_COUNT; i++) for (int i = 0; i < AEQUIP_COUNT; i++)
response.PCLoadData2CL.aEquip[i] = plr.Equip[i]; response.PCLoadData2CL.aEquip[i] = plr->Equip[i];
for (int i = 0; i < AINVEN_COUNT; i++) for (int i = 0; i < AINVEN_COUNT; i++)
response.PCLoadData2CL.aInven[i] = plr.Inven[i]; response.PCLoadData2CL.aInven[i] = plr->Inven[i];
// quest inventory // quest inventory
for (int i = 0; i < AQINVEN_COUNT; i++) for (int i = 0; i < AQINVEN_COUNT; i++)
response.PCLoadData2CL.aQInven[i] = plr.QInven[i]; response.PCLoadData2CL.aQInven[i] = plr->QInven[i];
// nanos // nanos
for (int i = 1; i < SIZEOF_NANO_BANK_SLOT; i++) { for (int i = 1; i < SIZEOF_NANO_BANK_SLOT; i++) {
response.PCLoadData2CL.aNanoBank[i] = plr.Nanos[i]; response.PCLoadData2CL.aNanoBank[i] = plr->Nanos[i];
} }
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
response.PCLoadData2CL.aNanoSlots[i] = plr.equippedNanos[i]; response.PCLoadData2CL.aNanoSlots[i] = plr->equippedNanos[i];
} }
// missions in progress // missions in progress
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) { for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
if (plr.tasks[i] == 0) if (plr->tasks[i] == 0)
break; break;
response.PCLoadData2CL.aRunningQuest[i].m_aCurrTaskID = plr.tasks[i]; response.PCLoadData2CL.aRunningQuest[i].m_aCurrTaskID = plr->tasks[i];
TaskData &task = *Missions::Tasks[plr.tasks[i]]; TaskData &task = *Missions::Tasks[plr->tasks[i]];
for (int j = 0; j < 3; j++) { for (int j = 0; j < 3; j++) {
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCID[j] = (int)task["m_iCSUEnemyID"][j]; response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCID[j] = (int)task["m_iCSUEnemyID"][j];
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCCount[j] = plr.RemainingNPCCount[i][j]; response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCCount[j] = plr->RemainingNPCCount[i][j];
/* /*
* client doesn't care about NeededItem ID and Count, * client doesn't care about NeededItem ID and Count,
* it gets Count from Quest Inventory * it gets Count from Quest Inventory
@ -296,12 +295,12 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
*/ */
} }
} }
response.PCLoadData2CL.iCurrentMissionID = plr.CurrentMissionID; response.PCLoadData2CL.iCurrentMissionID = plr->CurrentMissionID;
// completed missions // completed missions
// the packet requires 32 items, but the client only checks the first 16 (shrug) // the packet requires 32 items, but the client only checks the first 16 (shrug)
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
response.PCLoadData2CL.aQuestFlag[i] = plr.aQuestFlag[i]; response.PCLoadData2CL.aQuestFlag[i] = plr->aQuestFlag[i];
} }
// Computress tips // Computress tips
@ -310,11 +309,11 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
response.PCLoadData2CL.iFirstUseFlag2 = UINT64_MAX; response.PCLoadData2CL.iFirstUseFlag2 = UINT64_MAX;
} }
else { else {
response.PCLoadData2CL.iFirstUseFlag1 = plr.iFirstUseFlag[0]; response.PCLoadData2CL.iFirstUseFlag1 = plr->iFirstUseFlag[0];
response.PCLoadData2CL.iFirstUseFlag2 = plr.iFirstUseFlag[1]; response.PCLoadData2CL.iFirstUseFlag2 = plr->iFirstUseFlag[1];
} }
plr.instanceID = INSTANCE_OVERWORLD; // the player should never be in an instance on enter plr->instanceID = INSTANCE_OVERWORLD; // the player should never be in an instance on enter
sock->setEKey(CNSocketEncryption::createNewKey(response.uiSvrTime, response.iID + 1, response.PCLoadData2CL.iFusionMatter + 1)); sock->setEKey(CNSocketEncryption::createNewKey(response.uiSvrTime, response.iID + 1, response.PCLoadData2CL.iFusionMatter + 1));
sock->setFEKey(lm->FEKey); sock->setFEKey(lm->FEKey);
@ -325,14 +324,14 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
// transmit MOTD after entering the game, so the client hopefully changes modes on time // transmit MOTD after entering the game, so the client hopefully changes modes on time
Chat::sendServerMessage(sock, settings::MOTDSTRING); Chat::sendServerMessage(sock, settings::MOTDSTRING);
// copy Player object into the shard // transfer ownership of Player object into the shard (still valid in this function though)
addPlayer(sock, plr); addPlayer(sock, plr);
// check if there is an expiring vehicle // check if there is an expiring vehicle
Items::checkItemExpire(sock, getPlayer(sock)); Items::checkItemExpire(sock, plr);
// set player equip stats // set player equip stats
Items::setItemStats(getPlayer(sock)); Items::setItemStats(plr);
Missions::failInstancedMissions(sock); Missions::failInstancedMissions(sock);
@ -343,9 +342,9 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
for (auto& pair : players) for (auto& pair : players)
if (pair.second->notify) if (pair.second->notify)
Chat::sendServerMessage(pair.first, "[ADMIN]" + getPlayerName(&plr) + " has joined."); Chat::sendServerMessage(pair.first, "[ADMIN]" + getPlayerName(plr) + " has joined.");
// deallocate lm (and therefore the plr object) // deallocate lm
delete lm; delete lm;
} }

View File

@ -1,5 +1,6 @@
#include "Trading.hpp" #include "Trading.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "db/Database.hpp"
using namespace Trading; using namespace Trading;
@ -269,6 +270,8 @@ static void tradeConfirm(CNSocket* sock, CNPacketData* data) {
otherSock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE); otherSock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
return; return;
} }
Database::commitTrade(plr, plr2);
} }
static void tradeConfirmCancel(CNSocket* sock, CNPacketData* data) { static void tradeConfirmCancel(CNSocket* sock, CNPacketData* data) {

View File

@ -32,7 +32,7 @@ void CNShared::pruneLoginMetadata(CNServer *serv, time_t currTime) {
auto& sk = it->first; auto& sk = it->first;
auto& lm = it->second; auto& lm = it->second;
if (lm->timestamp + CNSHARED_TIMEOUT > currTime) { if (currTime > lm->timestamp + CNSHARED_TIMEOUT) {
std::cout << "[WARN] Pruning hung connection attempt" << std::endl; std::cout << "[WARN] Pruning hung connection attempt" << std::endl;
// deallocate object and remove map entry // deallocate object and remove map entry

View File

@ -11,14 +11,14 @@
#include "Player.hpp" #include "Player.hpp"
/* /*
* Connecions time out after 15 minutes, checked every 30 seconds. * Connecions time out after 5 minutes, checked every 30 seconds.
*/ */
#define CNSHARED_TIMEOUT 900000 #define CNSHARED_TIMEOUT 300000
#define CNSHARED_PERIOD 30000 #define CNSHARED_PERIOD 30000
struct LoginMetadata { struct LoginMetadata {
uint64_t FEKey; uint64_t FEKey;
Player plr; int32_t playerId;
time_t timestamp; time_t timestamp;
}; };

View File

@ -52,7 +52,8 @@ namespace Database {
bool banPlayer(int playerId, std::string& reason); bool banPlayer(int playerId, std::string& reason);
bool unbanPlayer(int playerId); bool unbanPlayer(int playerId);
void updateSelected(int accountId, int playerId); void updateSelected(int accountId, int slot);
void updateSelectedByPlayerId(int accountId, int playerId);
bool validateCharacter(int characterID, int userID); bool validateCharacter(int characterID, int userID);
bool isNameFree(std::string firstName, std::string lastName); bool isNameFree(std::string firstName, std::string lastName);
@ -78,7 +79,9 @@ namespace Database {
// getting players // getting players
void getPlayer(Player* plr, int id); void getPlayer(Player* plr, int id);
bool _updatePlayer(Player *player);
void updatePlayer(Player *player); void updatePlayer(Player *player);
void commitTrade(Player *plr1, Player *plr2);
// buddies // buddies
int getNumBuddies(Player* player); int getNumBuddies(Player* player);
@ -98,7 +101,7 @@ namespace Database {
void deleteEmailAttachments(int playerID, int index, int slot); void deleteEmailAttachments(int playerID, int index, int slot);
void deleteEmails(int playerID, int64_t* indices); void deleteEmails(int playerID, int64_t* indices);
int getNextEmailIndex(int playerID); int getNextEmailIndex(int playerID);
bool sendEmail(EmailData* data, std::vector<sItemBase> attachments); bool sendEmail(EmailData* data, std::vector<sItemBase> attachments, Player *sender);
// racing // racing
RaceRanking getTopRaceRanking(int epID, int playerID); RaceRanking getTopRaceRanking(int epID, int playerID);

View File

@ -152,7 +152,8 @@ void Database::updateEmailContent(EmailData* data) {
sqlite3_step(stmt); sqlite3_step(stmt);
int attachmentsCount = sqlite3_column_int(stmt, 0); int attachmentsCount = sqlite3_column_int(stmt, 0);
data->ItemFlag = (data->Taros > 0 || attachmentsCount > 0) ? 1 : 0; // set attachment flag dynamically // set attachment flag dynamically
data->ItemFlag = (data->Taros > 0 || attachmentsCount > 0) ? 1 : 0;
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
@ -265,7 +266,7 @@ int Database::getNextEmailIndex(int playerID) {
return (index > 0 ? index + 1 : 1); return (index > 0 ? index + 1 : 1);
} }
bool Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments) { bool Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments, Player *sender) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL); sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
@ -330,6 +331,13 @@ bool Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments) {
sqlite3_reset(stmt); sqlite3_reset(stmt);
} }
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
if (!_updatePlayer(sender)) {
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
return false;
}
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL); sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
return true; return true;
} }

View File

@ -79,6 +79,29 @@ void Database::updateSelected(int accountId, int slot) {
std::cout << "[WARN] Database fail on updateSelected(): " << sqlite3_errmsg(db) << std::endl; std::cout << "[WARN] Database fail on updateSelected(): " << sqlite3_errmsg(db) << std::endl;
} }
void Database::updateSelectedByPlayerId(int accountId, int32_t playerId) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
UPDATE Accounts SET
Selected = p.Slot,
LastLogin = (strftime('%s', 'now'))
FROM (SELECT Slot From Players WHERE PlayerId = ?) AS p
WHERE AccountID = ?;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, playerId);
sqlite3_bind_int(stmt, 2, accountId);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE)
std::cout << "[WARN] Database fail on updateSelectedByPlayerId(): " << sqlite3_errmsg(db) << std::endl;
}
bool Database::validateCharacter(int characterID, int userID) { bool Database::validateCharacter(int characterID, int userID) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);

View File

@ -285,11 +285,13 @@ void Database::getPlayer(Player* plr, int id) {
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
} }
void Database::updatePlayer(Player *player) { /*
std::lock_guard<std::mutex> lock(dbCrit); * Low-level function to save a player to DB.
* Must be run in a SQL transaction and with dbCrit locked.
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL); * The caller manages the transacstion, so if this function returns false,
* the caller must roll it back.
*/
bool Database::_updatePlayer(Player *player) {
const char* sql = R"( const char* sql = R"(
UPDATE Players UPDATE Players
SET SET
@ -336,10 +338,8 @@ void Database::updatePlayer(Player *player) {
sqlite3_bind_int(stmt, 21, player->iID); sqlite3_bind_int(stmt, 21, player->iID);
if (sqlite3_step(stmt) != SQLITE_DONE) { if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL); return false;
return;
} }
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
@ -375,10 +375,8 @@ void Database::updatePlayer(Player *player) {
rc = sqlite3_step(stmt); rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) { if (rc != SQLITE_DONE) {
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
return; return false;
} }
sqlite3_reset(stmt); sqlite3_reset(stmt);
} }
@ -395,10 +393,8 @@ void Database::updatePlayer(Player *player) {
sqlite3_bind_int(stmt, 6, player->Inven[i].iTimeLimit); sqlite3_bind_int(stmt, 6, player->Inven[i].iTimeLimit);
if (sqlite3_step(stmt) != SQLITE_DONE) { if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
return; return false;
} }
sqlite3_reset(stmt); sqlite3_reset(stmt);
} }
@ -415,10 +411,8 @@ void Database::updatePlayer(Player *player) {
sqlite3_bind_int(stmt, 6, player->Bank[i].iTimeLimit); sqlite3_bind_int(stmt, 6, player->Bank[i].iTimeLimit);
if (sqlite3_step(stmt) != SQLITE_DONE) { if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
return; return false;
} }
sqlite3_reset(stmt); sqlite3_reset(stmt);
} }
@ -451,10 +445,8 @@ void Database::updatePlayer(Player *player) {
sqlite3_bind_int(stmt, 4, player->QInven[i].iID); sqlite3_bind_int(stmt, 4, player->QInven[i].iID);
if (sqlite3_step(stmt) != SQLITE_DONE) { if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
return; return false;
} }
sqlite3_reset(stmt); sqlite3_reset(stmt);
} }
@ -487,10 +479,8 @@ void Database::updatePlayer(Player *player) {
sqlite3_bind_int(stmt, 4, player->Nanos[i].iStamina); sqlite3_bind_int(stmt, 4, player->Nanos[i].iStamina);
if (sqlite3_step(stmt) != SQLITE_DONE) { if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
return; return false;
} }
sqlite3_reset(stmt); sqlite3_reset(stmt);
} }
@ -524,14 +514,47 @@ void Database::updatePlayer(Player *player) {
sqlite3_bind_int(stmt, 5, player->RemainingNPCCount[i][2]); sqlite3_bind_int(stmt, 5, player->RemainingNPCCount[i][2]);
if (sqlite3_step(stmt) != SQLITE_DONE) { if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
return; return false;
} }
sqlite3_reset(stmt); sqlite3_reset(stmt);
} }
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
return true;
}
void Database::updatePlayer(Player *player) {
std::lock_guard<std::mutex> lock(dbCrit);
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
if (!_updatePlayer(player)) {
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
return;
}
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
}
void Database::commitTrade(Player *plr1, Player *plr2) {
std::lock_guard<std::mutex> lock(dbCrit);
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
if (!_updatePlayer(plr1)) {
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
return;
}
if (!_updatePlayer(plr2)) {
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
return;
}
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
} }

View File

@ -471,11 +471,7 @@ void CNLoginServer::characterSelect(CNSocket* sock, CNPacketData* data) {
LoginMetadata *lm = new LoginMetadata(); LoginMetadata *lm = new LoginMetadata();
lm->FEKey = sock->getFEKey(); lm->FEKey = sock->getFEKey();
lm->timestamp = getTime(); lm->timestamp = getTime();
lm->playerId = selection->iPC_UID;
Database::getPlayer(&lm->plr, selection->iPC_UID);
// this should never happen but for extra safety
if (lm->plr.iID == 0)
return invalidCharacter(sock);
resp.iEnterSerialKey = Rand::cryptoRand(); resp.iEnterSerialKey = Rand::cryptoRand();
@ -485,7 +481,7 @@ void CNLoginServer::characterSelect(CNSocket* sock, CNPacketData* data) {
sock->sendPacket(resp, P_LS2CL_REP_SHARD_SELECT_SUCC); sock->sendPacket(resp, P_LS2CL_REP_SHARD_SELECT_SUCC);
// update current slot in DB // update current slot in DB
Database::updateSelected(loginSessions[sock].userID, lm->plr.slot); Database::updateSelectedByPlayerId(loginSessions[sock].userID, selection->iPC_UID);
} }
void CNLoginServer::finishTutorial(CNSocket* sock, CNPacketData* data) { void CNLoginServer::finishTutorial(CNSocket* sock, CNPacketData* data) {