#include "db/internal.hpp" // Loading and saving players to/from the DB static void removeExpiredVehicles(Player* player) { int32_t currentTime = getTimestamp(); // if there are expired vehicles in bank just remove them silently for (int i = 0; i < ABANK_COUNT; i++) { if (player->Bank[i].iType == 10 && player->Bank[i].iTimeLimit < currentTime && player->Bank[i].iTimeLimit != 0) { memset(&player->Bank[i], 0, sizeof(sItemBase)); } } // we want to leave only 1 expired vehicle on player to delete it with the client packet std::vector toRemove; // equipped vehicle if (player->Equip[8].iOpt > 0 && player->Equip[8].iTimeLimit < currentTime && player->Equip[8].iTimeLimit != 0) { toRemove.push_back(&player->Equip[8]); player->toRemoveVehicle.eIL = 0; player->toRemoveVehicle.iSlotNum = 8; } // inventory for (int i = 0; i < AINVEN_COUNT; i++) { if (player->Inven[i].iType == 10 && player->Inven[i].iTimeLimit < currentTime && player->Inven[i].iTimeLimit != 0) { toRemove.push_back(&player->Inven[i]); player->toRemoveVehicle.eIL = 1; player->toRemoveVehicle.iSlotNum = i; } } // delete all but one vehicles, leave last one for ceremonial deletion for (int i = 0; i < (int)toRemove.size()-1; i++) { memset(toRemove[i], 0, sizeof(sItemBase)); } } void Database::getPlayer(Player* plr, int id) { std::lock_guard lock(dbCrit); const char* sql = R"( SELECT p.AccountID, p.Slot, p.FirstName, p.LastName, p.Level, p.Nano1, p.Nano2, p.Nano3, p.AppearanceFlag, p.TutorialFlag, p.PayZoneFlag, p.XCoordinate, p.YCoordinate, p.ZCoordinate, p.NameCheck, p.Angle, p.HP, acc.AccountLevel, p.FusionMatter, p.Taros, p.Quests, p.BatteryW, p.BatteryN, p.Mentor, p.WarpLocationFlag, p.SkywayLocationFlag, p.CurrentMissionID, p.FirstUseFlag, a.Body, a.EyeColor, a.FaceStyle, a.Gender, a.HairColor, a.HairStyle, a.Height, a.SkinColor FROM Players as p INNER JOIN Appearances as a ON p.PlayerID = a.PlayerID INNER JOIN Accounts as acc ON p.AccountID = acc.AccountID WHERE p.PlayerID = ?; )"; sqlite3_stmt* stmt; sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); sqlite3_bind_int(stmt, 1, id); if (sqlite3_step(stmt) != SQLITE_ROW) { sqlite3_finalize(stmt); std::cout << "[WARN] Database: Failed to load character [" << id << "]: " << sqlite3_errmsg(db) << std::endl; return; } plr->iID = id; plr->PCStyle.iPC_UID = id; plr->accountId = sqlite3_column_int(stmt, 0); plr->slot = sqlite3_column_int(stmt, 1); // parsing const unsigned char* to char16_t std::string placeHolder = std::string(reinterpret_cast(sqlite3_column_text(stmt, 2))); U8toU16(placeHolder, plr->PCStyle.szFirstName, sizeof(plr->PCStyle.szFirstName)); placeHolder = std::string(reinterpret_cast(sqlite3_column_text(stmt, 3))); U8toU16(placeHolder, plr->PCStyle.szLastName, sizeof(plr->PCStyle.szLastName)); plr->level = sqlite3_column_int(stmt, 4); plr->equippedNanos[0] = sqlite3_column_int(stmt, 5); plr->equippedNanos[1] = sqlite3_column_int(stmt, 6); plr->equippedNanos[2] = sqlite3_column_int(stmt, 7); plr->PCStyle2.iAppearanceFlag = sqlite3_column_int(stmt, 8); plr->PCStyle2.iTutorialFlag = sqlite3_column_int(stmt, 9); plr->PCStyle2.iPayzoneFlag = sqlite3_column_int(stmt, 10); plr->x = sqlite3_column_int(stmt, 11); plr->y = sqlite3_column_int(stmt, 12); plr->z = sqlite3_column_int(stmt, 13); plr->PCStyle.iNameCheck = sqlite3_column_int(stmt, 14); plr->angle = sqlite3_column_int(stmt, 15); plr->HP = sqlite3_column_int(stmt, 16); plr->accountLevel = sqlite3_column_int(stmt, 17); plr->fusionmatter = sqlite3_column_int(stmt, 18); plr->money = sqlite3_column_int(stmt, 19); memcpy(plr->aQuestFlag, sqlite3_column_blob(stmt, 20), sizeof(plr->aQuestFlag)); plr->batteryW = sqlite3_column_int(stmt, 21); plr->batteryN = sqlite3_column_int(stmt, 22); plr->mentor = sqlite3_column_int(stmt, 23); plr->iWarpLocationFlag = sqlite3_column_int(stmt, 24); memcpy(plr->aSkywayLocationFlag, sqlite3_column_blob(stmt, 25), sizeof(plr->aSkywayLocationFlag)); plr->CurrentMissionID = sqlite3_column_int(stmt, 26); memcpy(plr->iFirstUseFlag, sqlite3_column_blob(stmt, 27), sizeof(plr->iFirstUseFlag)); plr->PCStyle.iBody = sqlite3_column_int(stmt, 28); plr->PCStyle.iEyeColor = sqlite3_column_int(stmt, 29); plr->PCStyle.iFaceStyle = sqlite3_column_int(stmt, 30); plr->PCStyle.iGender = sqlite3_column_int(stmt, 31); plr->PCStyle.iHairColor = sqlite3_column_int(stmt, 32); plr->PCStyle.iHairStyle = sqlite3_column_int(stmt, 33); plr->PCStyle.iHeight = sqlite3_column_int(stmt, 34); plr->PCStyle.iSkinColor = sqlite3_column_int(stmt, 35); sqlite3_finalize(stmt); // get inventory sql = R"( SELECT Slot, Type, ID, Opt, TimeLimit FROM Inventory WHERE PlayerID = ?; )"; sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); sqlite3_bind_int(stmt, 1, id); while (sqlite3_step(stmt) == SQLITE_ROW) { int slot = sqlite3_column_int(stmt, 0); // for extra safety if (slot < 0 || slot > AEQUIP_COUNT + AINVEN_COUNT + ABANK_COUNT) { std::cout << "[WARN] Database: Invalid item slot in db?! " << std::endl; continue; } sItemBase* item; if (slot < AEQUIP_COUNT) { // equipment item = &plr->Equip[slot]; } else if (slot < (AEQUIP_COUNT + AINVEN_COUNT)) { // inventory item = &plr->Inven[slot - AEQUIP_COUNT]; } else { // bank item = &plr->Bank[slot - AEQUIP_COUNT - AINVEN_COUNT]; } item->iType = sqlite3_column_int(stmt, 1); item->iID = sqlite3_column_int(stmt, 2); item->iOpt = sqlite3_column_int(stmt, 3); item->iTimeLimit = sqlite3_column_int(stmt, 4); } sqlite3_finalize(stmt); removeExpiredVehicles(plr); // get quest inventory sql = R"( SELECT Slot, ID, Opt FROM QuestItems WHERE PlayerID = ?; )"; sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); sqlite3_bind_int(stmt, 1, id); while (sqlite3_step(stmt) == SQLITE_ROW) { int slot = sqlite3_column_int(stmt, 0); // for extra safety if (slot < 0) continue; sItemBase* item = &plr->QInven[slot]; item->iType = 8; item->iID = sqlite3_column_int(stmt, 1); item->iOpt = sqlite3_column_int(stmt, 2); } sqlite3_finalize(stmt); // get nanos sql = R"( SELECT ID, Skill, Stamina FROM Nanos WHERE PlayerID = ?; )"; sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); sqlite3_bind_int(stmt, 1, id); while (sqlite3_step(stmt) == SQLITE_ROW) { int id = sqlite3_column_int(stmt, 0); // for extra safety if (id < 0 || id > NANO_COUNT) continue; sNano* nano = &plr->Nanos[id]; nano->iID = id; nano->iSkillID = sqlite3_column_int(stmt, 1); nano->iStamina = sqlite3_column_int(stmt, 2); } sqlite3_finalize(stmt); // get active quests sql = R"( SELECT TaskID, RemainingNPCCount1, RemainingNPCCount2, RemainingNPCCount3 FROM RunningQuests WHERE PlayerID = ?; )"; sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); sqlite3_bind_int(stmt, 1, id); std::set tasksSet; // used to prevent duplicate tasks from loading in for (int i = 0; sqlite3_step(stmt) == SQLITE_ROW && i < ACTIVE_MISSION_COUNT; i++) { int taskID = sqlite3_column_int(stmt, 0); if (tasksSet.find(taskID) != tasksSet.end()) continue; plr->tasks[i] = taskID; tasksSet.insert(taskID); plr->RemainingNPCCount[i][0] = sqlite3_column_int(stmt, 1); plr->RemainingNPCCount[i][1] = sqlite3_column_int(stmt, 2); plr->RemainingNPCCount[i][2] = sqlite3_column_int(stmt, 3); } sqlite3_finalize(stmt); // get buddies sql = R"( SELECT PlayerAID, PlayerBID FROM Buddyships WHERE PlayerAID = ? OR PlayerBID = ?; )"; sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); sqlite3_bind_int(stmt, 1, id); sqlite3_bind_int(stmt, 2, id); int i = 0; while (sqlite3_step(stmt) == SQLITE_ROW && i < 50) { int PlayerAId = sqlite3_column_int(stmt, 0); int PlayerBId = sqlite3_column_int(stmt, 1); plr->buddyIDs[i] = id == PlayerAId ? PlayerBId : PlayerAId; plr->isBuddyBlocked[i] = false; i++; } sqlite3_finalize(stmt); // get blocked players sql = R"( SELECT BlockedPlayerID FROM Blocks WHERE PlayerID = ?; )"; sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); sqlite3_bind_int(stmt, 1, id); // i retains its value from after the loop over Buddyships while (sqlite3_step(stmt) == SQLITE_ROW && i < 50) { plr->buddyIDs[i] = sqlite3_column_int(stmt, 0); plr->isBuddyBlocked[i] = true; i++; } sqlite3_finalize(stmt); } /* * Low-level function to save a player to DB. * Must be run in a SQL transaction and with dbCrit locked. * 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"( UPDATE Players SET Level = ? , Nano1 = ?, Nano2 = ?, Nano3 = ?, XCoordinate = ?, YCoordinate = ?, ZCoordinate = ?, Angle = ?, HP = ?, FusionMatter = ?, Taros = ?, Quests = ?, BatteryW = ?, BatteryN = ?, WarplocationFlag = ?, SkywayLocationFlag = ?, CurrentMissionID = ?, PayZoneFlag = ?, FirstUseFlag = ?, Mentor = ? WHERE PlayerID = ?; )"; sqlite3_stmt* stmt; sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); sqlite3_bind_int(stmt, 1, player->level); sqlite3_bind_int(stmt, 2, player->equippedNanos[0]); sqlite3_bind_int(stmt, 3, player->equippedNanos[1]); sqlite3_bind_int(stmt, 4, player->equippedNanos[2]); if (player->instanceID == 0 && !player->onMonkey) { sqlite3_bind_int(stmt, 5, player->x); sqlite3_bind_int(stmt, 6, player->y); sqlite3_bind_int(stmt, 7, player->z); sqlite3_bind_int(stmt, 8, player->angle); } else { sqlite3_bind_int(stmt, 5, player->lastX); sqlite3_bind_int(stmt, 6, player->lastY); sqlite3_bind_int(stmt, 7, player->lastZ); sqlite3_bind_int(stmt, 8, player->lastAngle); } sqlite3_bind_int(stmt, 9, player->HP); sqlite3_bind_int(stmt, 10, player->fusionmatter); sqlite3_bind_int(stmt, 11, player->money); sqlite3_bind_blob(stmt, 12, player->aQuestFlag, sizeof(player->aQuestFlag), NULL); sqlite3_bind_int(stmt, 13, player->batteryW); sqlite3_bind_int(stmt, 14, player->batteryN); sqlite3_bind_int(stmt, 15, player->iWarpLocationFlag); sqlite3_bind_blob(stmt, 16, player->aSkywayLocationFlag, sizeof(player->aSkywayLocationFlag), NULL); sqlite3_bind_int(stmt, 17, player->CurrentMissionID); sqlite3_bind_int(stmt, 18, player->PCStyle2.iPayzoneFlag); sqlite3_bind_blob(stmt, 19, player->iFirstUseFlag, sizeof(player->iFirstUseFlag), NULL); sqlite3_bind_int(stmt, 20, player->mentor); sqlite3_bind_int(stmt, 21, player->iID); if (sqlite3_step(stmt) != SQLITE_DONE) { sqlite3_finalize(stmt); return false; } sqlite3_finalize(stmt); // update inventory sql = R"( DELETE FROM Inventory WHERE PlayerID = ?; )"; sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); sqlite3_bind_int(stmt, 1, player->iID); int rc = sqlite3_step(stmt); sqlite3_finalize(stmt); sql = R"( INSERT INTO Inventory (PlayerID, Slot, Type, Opt, ID, Timelimit) VALUES (?, ?, ?, ?, ?, ?); )"; sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); for (int i = 0; i < AEQUIP_COUNT; i++) { if (player->Equip[i].iID == 0) continue; sqlite3_bind_int(stmt, 1, player->iID); sqlite3_bind_int(stmt, 2, i); sqlite3_bind_int(stmt, 3, player->Equip[i].iType); sqlite3_bind_int(stmt, 4, player->Equip[i].iOpt); sqlite3_bind_int(stmt, 5, player->Equip[i].iID); sqlite3_bind_int(stmt, 6, player->Equip[i].iTimeLimit); rc = sqlite3_step(stmt); if (rc != SQLITE_DONE) { sqlite3_finalize(stmt); return false; } sqlite3_reset(stmt); } for (int i = 0; i < AINVEN_COUNT; i++) { if (player->Inven[i].iID == 0) continue; sqlite3_bind_int(stmt, 1, player->iID); sqlite3_bind_int(stmt, 2, i + AEQUIP_COUNT); sqlite3_bind_int(stmt, 3, player->Inven[i].iType); sqlite3_bind_int(stmt, 4, player->Inven[i].iOpt); sqlite3_bind_int(stmt, 5, player->Inven[i].iID); sqlite3_bind_int(stmt, 6, player->Inven[i].iTimeLimit); if (sqlite3_step(stmt) != SQLITE_DONE) { sqlite3_finalize(stmt); return false; } sqlite3_reset(stmt); } for (int i = 0; i < ABANK_COUNT; i++) { if (player->Bank[i].iID == 0) continue; sqlite3_bind_int(stmt, 1, player->iID); sqlite3_bind_int(stmt, 2, i + AEQUIP_COUNT + AINVEN_COUNT); sqlite3_bind_int(stmt, 3, player->Bank[i].iType); sqlite3_bind_int(stmt, 4, player->Bank[i].iOpt); sqlite3_bind_int(stmt, 5, player->Bank[i].iID); sqlite3_bind_int(stmt, 6, player->Bank[i].iTimeLimit); if (sqlite3_step(stmt) != SQLITE_DONE) { sqlite3_finalize(stmt); return false; } sqlite3_reset(stmt); } sqlite3_finalize(stmt); // Update Quest Inventory sql = R"( DELETE FROM QuestItems WHERE PlayerID = ?; )"; sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); sqlite3_bind_int(stmt, 1, player->iID); sqlite3_step(stmt); sqlite3_finalize(stmt); sql = R"( INSERT INTO QuestItems (PlayerID, Slot, Opt, ID) VALUES (?, ?, ?, ?); )"; sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); for (int i = 0; i < AQINVEN_COUNT; i++) { if (player->QInven[i].iID == 0) continue; sqlite3_bind_int(stmt, 1, player->iID); sqlite3_bind_int(stmt, 2, i); sqlite3_bind_int(stmt, 3, player->QInven[i].iOpt); sqlite3_bind_int(stmt, 4, player->QInven[i].iID); if (sqlite3_step(stmt) != SQLITE_DONE) { sqlite3_finalize(stmt); return false; } sqlite3_reset(stmt); } sqlite3_finalize(stmt); // Update Nanos sql = R"( DELETE FROM Nanos WHERE PlayerID = ?; )"; sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); sqlite3_bind_int(stmt, 1, player->iID); sqlite3_step(stmt); sqlite3_finalize(stmt); sql = R"( INSERT INTO Nanos (PlayerID, ID, SKill, Stamina) VALUES (?, ?, ?, ?); )"; sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); for (int i = 0; i < NANO_COUNT; i++) { if (player->Nanos[i].iID == 0) continue; sqlite3_bind_int(stmt, 1, player->iID); sqlite3_bind_int(stmt, 2, player->Nanos[i].iID); sqlite3_bind_int(stmt, 3, player->Nanos[i].iSkillID); sqlite3_bind_int(stmt, 4, player->Nanos[i].iStamina); if (sqlite3_step(stmt) != SQLITE_DONE) { sqlite3_finalize(stmt); return false; } sqlite3_reset(stmt); } sqlite3_finalize(stmt); // Update Running Quests sql = R"( DELETE FROM RunningQuests WHERE PlayerID = ?; )"; sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); sqlite3_bind_int(stmt, 1, player->iID); sqlite3_step(stmt); sqlite3_finalize(stmt); sql = R"( INSERT INTO RunningQuests (PlayerID, TaskID, RemainingNPCCount1, RemainingNPCCount2, RemainingNPCCount3) VALUES (?, ?, ?, ?, ?); )"; sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) { if (player->tasks[i] == 0) continue; sqlite3_bind_int(stmt, 1, player->iID); sqlite3_bind_int(stmt, 2, player->tasks[i]); sqlite3_bind_int(stmt, 3, player->RemainingNPCCount[i][0]); sqlite3_bind_int(stmt, 4, player->RemainingNPCCount[i][1]); sqlite3_bind_int(stmt, 5, player->RemainingNPCCount[i][2]); if (sqlite3_step(stmt) != SQLITE_DONE) { sqlite3_finalize(stmt); return false; } sqlite3_reset(stmt); } sqlite3_finalize(stmt); return true; } void Database::updatePlayer(Player *player) { std::lock_guard 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 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); }