OpenFusion/src/Database.cpp

1601 lines
54 KiB
C++
Raw Normal View History

#include "Database.hpp"
#include "Database.hpp"
#include "contrib/bcrypt/BCrypt.hpp"
#include "CNProtocol.hpp"
#include <string>
#include "contrib/JSON.hpp"
2020-11-28 23:19:07 +00:00
#include "contrib/sqlite/sqlite3.h"
#include "CNStructs.hpp"
#include "settings.hpp"
#include "Player.hpp"
#include "CNStructs.hpp"
2020-09-13 18:45:51 +00:00
#include "MissionManager.hpp"
#if defined(__MINGW32__) && !defined(_GLIBCXX_HAS_GTHREADS)
#include "mingw/mingw.mutex.h"
#else
#include <mutex>
#endif
std::mutex dbCrit;
2020-11-28 23:31:00 +00:00
sqlite3* db;
void Database::open() {
2020-11-28 23:31:00 +00:00
int rc = sqlite3_open(settings::DBPATH.c_str(), &db);
if (rc != SQLITE_OK) {
std::cout << "[FATAL] Cannot open database: " << sqlite3_errmsg(db) << std::endl;
terminate(0);
}
2020-12-03 00:40:36 +00:00
// foreign keys are off by default
sqlite3_exec(db, "PRAGMA foreign_keys=ON", NULL, NULL, NULL);
2020-11-30 12:44:43 +00:00
createTables();
Database saving update (#104) * implemented saving BatteryN and BatteryW * implemented saving mentor * moved int64->blob parsing to a separate function * moved parsing blob->int64 to a separate function * added functions for parsing int32->blob and vice versa * added functions for parsing int16->blob and vice versa * WIP saving quest items and active tasks * Quest items are stored in inventory table instead of blob * added sanity check for missionId * saving active missions works * removed unneccesary include * implemented saving warplocationflag, skywaylocationflag and currentmissionid in database * INFO DB message now shows how many accounts and player characters are in the database * fixed dbsaveinterval being in [login] instead of [shard] * fixed mission quit: - fixed wrong json name, causing qitems not deleting properly - quitting mission now resets npc kill count * adjusted saving active missions * removed blob parsing functions that ended up being unused * removed accidentaly added include * removed sending PCStyle2 on Player Enter * added a sanity check in itemMoveHandler * removed MapNum from PCLoad, as client doesn't even read it * set BuddyWarpCooldown to 60s on PCLoad * fixed a bug causing EXIT DUPLICATE not working * added creation and last login timestamps to accounts and players * added a sanity check for P_CL2LS_REQ_PC_EXIT_DUPLICATE * implemented web api support, toggled by new setting (off by default) * add usewebapi to config Co-authored-by: Gent <gentsemaj@live.com>
2020-09-21 19:43:53 +00:00
std::cout << "[INFO] Database in operation ";
int accounts = getTableSize("Accounts");
int players = getTableSize("Players");
Database saving update (#104) * implemented saving BatteryN and BatteryW * implemented saving mentor * moved int64->blob parsing to a separate function * moved parsing blob->int64 to a separate function * added functions for parsing int32->blob and vice versa * added functions for parsing int16->blob and vice versa * WIP saving quest items and active tasks * Quest items are stored in inventory table instead of blob * added sanity check for missionId * saving active missions works * removed unneccesary include * implemented saving warplocationflag, skywaylocationflag and currentmissionid in database * INFO DB message now shows how many accounts and player characters are in the database * fixed dbsaveinterval being in [login] instead of [shard] * fixed mission quit: - fixed wrong json name, causing qitems not deleting properly - quitting mission now resets npc kill count * adjusted saving active missions * removed blob parsing functions that ended up being unused * removed accidentaly added include * removed sending PCStyle2 on Player Enter * added a sanity check in itemMoveHandler * removed MapNum from PCLoad, as client doesn't even read it * set BuddyWarpCooldown to 60s on PCLoad * fixed a bug causing EXIT DUPLICATE not working * added creation and last login timestamps to accounts and players * added a sanity check for P_CL2LS_REQ_PC_EXIT_DUPLICATE * implemented web api support, toggled by new setting (off by default) * add usewebapi to config Co-authored-by: Gent <gentsemaj@live.com>
2020-09-21 19:43:53 +00:00
std::string message = "";
if (accounts > 0) {
message += ": Found " + std::to_string(accounts) + " Account";
if (accounts > 1)
message += "s";
}
if (players > 0) {
message += " and " + std::to_string(players) + " Player Character";
if (players > 1)
message += "s";
}
std::cout << message << std::endl;
}
2020-11-30 12:44:43 +00:00
void Database::createTables() {
char* errMsg = 0;
char* sql;
sql = R"(
CREATE TABLE IF NOT EXISTS "Accounts" (
"AccountID" INTEGER NOT NULL,
"Login" TEXT NOT NULL UNIQUE,
"Password" TEXT NOT NULL,
"Selected" INTEGER DEFAULT 1 NOT NULL,
"Created" INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
"LastLogin" INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
2020-11-30 12:44:43 +00:00
PRIMARY KEY("AccountID" AUTOINCREMENT)
);
CREATE TABLE IF NOT EXISTS "Players" (
2020-11-30 12:44:43 +00:00
"PlayerID" INTEGER NOT NULL,
"AccountID" INTEGER NOT NULL,
"Slot" INTEGER NOT NULL,
"Firstname" TEXT NOT NULL COLLATE NOCASE,
"LastName" TEXT NOT NULL COLLATE NOCASE,
"Created" INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
"LastLogin" INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
"Level" INTEGER DEFAULT 1 NOT NULL,
"Nano1" INTEGER DEFAULT 0 NOT NULL,
"Nano2" INTEGER DEFAULT 0 NOT NULL,
"Nano3" INTEGER DEFAULT 0 NOT NULL,
"AppearanceFlag" INTEGER DEFAULT 0 NOT NULL,
"TutorialFlag" INTEGER DEFAULT 0 NOT NULL,
"PayZoneFlag" INTEGER DEFAULT 0 NOT NULL,
2020-11-30 12:44:43 +00:00
"XCoordinates" INTEGER NOT NULL,
"YCoordinates" INTEGER NOT NULL,
"ZCoordinates" INTEGER NOT NULL,
"Angle" INTEGER NOT NULL,
2020-11-30 12:44:43 +00:00
"HP" INTEGER NOT NULL,
"NameCheck" INTEGER NOT NULL,
"AccountLevel" INTEGER NOT NULL,
"FusionMatter" INTEGER DEFAULT 0 NOT NULL,
"Taros" INTEGER DEFAULT 0 NOT NULL,
2020-11-30 12:44:43 +00:00
"Quests" BLOB NOT NULL,
"BatteryW" INTEGER DEFAULT 0 NOT NULL,
"BatteryN" INTEGER DEFAULT 0 NOT NULL,
"Mentor" INTEGER DEFAULT 5 NOT NULL,
"WarpLocationFlag" INTEGER DEFAULT 0 NOT NULL,
"SkywayLocationFlag1" INTEGER DEFAULT 0 NOT NULL,
"SkywayLocationFlag2" INTEGER DEFAULT 0 NOT NULL,
"CurrentMissionID" INTEGER DEFAULT 0 NOT NULL,
2020-11-30 12:44:43 +00:00
PRIMARY KEY("PlayerID" AUTOINCREMENT),
2020-12-02 13:29:05 +00:00
FOREIGN KEY("AccountID") REFERENCES "Accounts"("AccountID") ON DELETE CASCADE,
2020-12-02 13:55:17 +00:00
UNIQUE ("AccountID", "Slot"),
UNIQUE ("Firstname", "LastName")
2020-11-30 12:44:43 +00:00
);
CREATE TABLE IF NOT EXISTS "Appearances" (
2020-12-02 13:29:05 +00:00
"PlayerID" INTEGER UNIQUE NOT NULL,
"Body" INTEGER DEFAULT 0 NOT NULL,
"EyeColor" INTEGER DEFAULT 1 NOT NULL,
"FaceStyle" INTEGER DEFAULT 1 NOT NULL,
"Gender" INTEGER DEFAULT 1 NOT NULL,
"HairColor" INTEGER DEFAULT 1 NOT NULL,
"HairStyle" INTEGER DEFAULT 1 NOT NULL,
"Height" INTEGER DEFAULT 0 NOT NULL,
"SkinColor" INTEGER DEFAULT 1 NOT NULL,
2020-11-30 12:44:43 +00:00
FOREIGN KEY("PlayerID") REFERENCES "Players"("PlayerID") ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS "Inventory" (
"PlayerId" INTEGER NOT NULL,
"Slot" INTEGER NOT NULL,
"Id" INTEGER NOT NULL,
"Type" INTEGER NOT NULL,
"Opt" INTEGER NOT NULL,
2020-12-02 13:29:05 +00:00
"TimeLimit" INTEGER DEFAULT 0 NOT NULL,
FOREIGN KEY("PlayerID") REFERENCES "Players"("PlayerID") ON DELETE CASCADE,
UNIQUE ("PlayerId", "Slot")
2020-11-30 12:44:43 +00:00
);
CREATE TABLE IF NOT EXISTS "QuestItems" (
"PlayerId" INTEGER NOT NULL,
"Slot" INTEGER NOT NULL,
"Id" INTEGER NOT NULL,
"Opt" INTEGER NOT NULL,
FOREIGN KEY("PlayerID") REFERENCES "Players"("PlayerID") ON DELETE CASCADE,
UNIQUE ("PlayerId", "Slot")
);
2020-11-30 12:44:43 +00:00
CREATE TABLE IF NOT EXISTS "Nanos" (
"PlayerId" INTEGER NOT NULL,
"Id" INTEGER NOT NULL,
"Skill" INTEGER NOT NULL,
2020-12-02 13:29:05 +00:00
"Stamina" INTEGER DEFAULT 150 NOT NULL,
FOREIGN KEY("PlayerID") REFERENCES "Players"("PlayerID") ON DELETE CASCADE,
UNIQUE ("PlayerId", "Id")
2020-11-30 12:44:43 +00:00
);
CREATE TABLE IF NOT EXISTS "RunningQuests" (
"PlayerId" INTEGER NOT NULL,
"TaskId" INTEGER NOT NULL,
"RemainingNPCCount1" INTEGER NOT NULL,
"RemainingNPCCount2" INTEGER NOT NULL,
"RemainingNPCCount3" INTEGER NOT NULL,
FOREIGN KEY("PlayerID") REFERENCES "Players"("PlayerID") ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS "Buddyships" (
"PlayerAId" INTEGER NOT NULL,
"PlayerBId" INTEGER NOT NULL,
"Status" INTEGER NOT NULL,
FOREIGN KEY("PlayerAId") REFERENCES "Players"("PlayerID") ON DELETE CASCADE,
FOREIGN KEY("PlayerBId") REFERENCES "Players"("PlayerID") ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS "EmailData" (
"PlayerId" INTEGER NOT NULL,
"MsgIndex" INTEGER NOT NULL,
"ReadFlag" INTEGER NOT NULL,
"ItemFlag" INTEGER NOT NULL,
"SenderId" INTEGER NOT NULL,
"SenderFirstName" TEXT NOT NULL COLLATE NOCASE,
"SenderLastName" TEXT NOT NULL COLLATE NOCASE,
"SubjectLine" TEXT NOT NULL,
"MsgBody" TEXT NOT NULL,
"Taros" INTEGER NOT NULL,
"SendTime" INTEGER NOT NULL,
"DeleteTime" INTEGER NOT NULL,
FOREIGN KEY("PlayerID") REFERENCES "Players"("PlayerID") ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS "EmailItems" (
"PlayerId" INTEGER NOT NULL,
"MsgIndex" INTEGER NOT NULL,
"Slot" INTEGER NOT NULL,
"Id" INTEGER NOT NULL,
"Type" INTEGER NOT NULL,
"Opt" INTEGER NOT NULL,
"TimeLimit" INTEGER NOT NULL,
UNIQUE ("MsgIndex", "Slot")
2020-11-30 12:44:43 +00:00
);
CREATE TABLE IF NOT EXISTS "RaceResults"(
"EPID" INTEGER NOT NULL,
"PlayerID" INTEGER NOT NULL,
"Score" INTEGER NOT NULL,
"Timestamp" INTEGER NOT NULL,
FOREIGN KEY("PlayerID") REFERENCES "Players"("PlayerID") ON DELETE CASCADE
)
)";
int rc = sqlite3_exec(db, sql, 0, 0, &errMsg);
if (rc != SQLITE_OK) {
std::cout << "[FATAL] Database failed to create tables: " << errMsg << std::endl;
terminate(0);
}
}
int Database::getTableSize(std::string tableName) {
std::lock_guard<std::mutex> lock(dbCrit);
2020-11-30 12:44:43 +00:00
2020-12-02 13:55:17 +00:00
const char* sql = "SELECT COUNT(*) FROM ?;";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
2020-12-02 13:55:17 +00:00
sqlite3_bind_text(stmt, 1, tableName.c_str(), -1, 0);
sqlite3_step(stmt);
int result = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
return result;
}
2020-12-03 13:11:02 +00:00
void Database::findAccount(Account* account, std::string login) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
SELECT "AccountID", "Password", "Selected"
FROM "Accounts"
WHERE "Login" = ?
LIMIT 1;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_text(stmt, 1, login.c_str(), -1, 0);
int rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW)
{
account->AccountID = sqlite3_column_int(stmt, 0);
account->Password = std::string(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1)));
account->Selected = sqlite3_column_int(stmt, 2);
}
sqlite3_finalize(stmt);
}
int Database::addAccount(std::string login, std::string password) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
INSERT INTO "Accounts"
("Login", "Password")
VALUES (?, ?);
)";
sqlite3_stmt* stmt;
2020-12-02 13:55:17 +00:00
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_text(stmt, 1, login.c_str(), -1, 0);
2020-12-03 00:40:36 +00:00
std::string hashedPassword = BCrypt::generateHash(password);
sqlite3_bind_text(stmt, 2, hashedPassword.c_str(), -1, 0);
2020-12-02 13:55:17 +00:00
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
2020-12-03 13:11:02 +00:00
if (rc != SQLITE_DONE) {
std::cout << "[WARN] Database: failed to add new character" << std::endl;
return 0;
}
return sqlite3_last_insert_rowid(db);
}
void Database::updateSelected(int accountId, int slot) {
std::lock_guard<std::mutex> lock(dbCrit);
2020-12-02 13:55:17 +00:00
if (slot < 1 || slot > 4) {
std::cout << "[WARN] Invalid slot number passed to updateSelected()! " << std::endl;
return;
}
const char* sql = R"(
UPDATE "Accounts"
SET "Selected" = ?, "LastLogin" = (strftime('%s', 'now'))
WHERE "AccountID" = ?;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, slot);
sqlite3_bind_int(stmt, 2, accountId);
int rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
std::cout << "[WARN] Database fail on updateSelected(). Error Code " << rc << std::endl;
sqlite3_finalize(stmt);
}
bool Database::validateCharacter(int characterID, int userID) {
std::lock_guard<std::mutex> lock(dbCrit);
// query whatever
const char* sql = R"(
SELECT "PlayerID"
FROM "Players"
WHERE "PlayerID" = ? AND "AccountID" = ?
LIMIT 1;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, characterID);
sqlite3_bind_int(stmt, 2, userID);
int rc = sqlite3_step(stmt);
// if we got a row back, the character is valid
bool result = (rc == SQLITE_ROW);
sqlite3_finalize(stmt);
return result;
}
bool Database::isNameFree(std::string firstName, std::string lastName) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
SELECT "PlayerID"
FROM "Players"
WHERE "Firstname" = ? AND "LastName" = ?
LIMIT 1;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_text(stmt, 1, firstName.c_str(), -1, 0);
sqlite3_bind_text(stmt, 2, lastName.c_str(), -1, 0);
int rc = sqlite3_step(stmt);
bool result = (rc != SQLITE_ROW);
sqlite3_finalize(stmt);
return result;
}
bool Database::isSlotFree(int accountId, int slotNum) {
std::lock_guard<std::mutex> lock(dbCrit);
2020-12-02 13:55:17 +00:00
if (slotNum < 1 || slotNum > 4) {
2020-12-03 13:11:02 +00:00
std::cout << "[WARN] Invalid slot number passed to isSlotFree()! "<<slotNum << std::endl;
return false;
}
const char* sql = R"(
SELECT "PlayerID"
FROM "Players"
WHERE "AccountID" = ? AND "Slot" = ?
LIMIT 1;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, accountId);
sqlite3_bind_int(stmt, 2, slotNum);
int rc = sqlite3_step(stmt);
bool result = (rc != SQLITE_ROW);
sqlite3_finalize(stmt);
return result;
}
int Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID) {
std::lock_guard<std::mutex> lock(dbCrit);
sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, NULL);
const char* sql = R"(
INSERT INTO "Players"
("AccountID", "Slot", "Firstname", "LastName", "XCoordinates" , "YCoordinates", "ZCoordinates", "Angle",
"HP", "NameCheck", "AccountLevel", "Quests")
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
)";
sqlite3_stmt* stmt;
std::string firstName = U16toU8(save->szFirstName);
std::string lastName = U16toU8(save->szLastName);
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, AccountID);
sqlite3_bind_int(stmt, 2, save->iSlotNum);
sqlite3_bind_text(stmt, 3, firstName.c_str(), -1, 0);
sqlite3_bind_text(stmt, 4, lastName.c_str(), -1, 0);
sqlite3_bind_int(stmt, 5, settings::SPAWN_X);
sqlite3_bind_int(stmt, 6, settings::SPAWN_Y);
sqlite3_bind_int(stmt, 7, settings::SPAWN_Z);
sqlite3_bind_int(stmt, 8, settings::SPAWN_ANGLE);
sqlite3_bind_int(stmt, 9, PC_MAXHEALTH(1));
// if FNCode isn't 0, it's a wheel name
int nameCheck = (settings::APPROVEALLNAMES || save->iFNCode) ? 1 : 0;
sqlite3_bind_int(stmt, 10, nameCheck);
sqlite3_bind_int(stmt, 11, settings::ACCLEVEL);
// 128 byte blob for completed quests
2020-12-02 13:55:17 +00:00
unsigned char blobBuffer[128] = { 0 };
sqlite3_bind_blob(stmt, 12, blobBuffer, sizeof(blobBuffer), 0);
2020-12-02 13:55:17 +00:00
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
2020-12-02 13:55:17 +00:00
if (rc != SQLITE_DONE) {
sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
return 0;
2020-12-02 13:55:17 +00:00
}
2020-12-02 13:55:17 +00:00
int playerId = sqlite3_last_insert_rowid(db);
sql = R"(
INSERT INTO "Appearances"
("PlayerID")
VALUES (?);
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, playerId);
2020-12-02 13:55:17 +00:00
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) {
sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
return 0;
}
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, NULL);
return playerId;
}
2020-12-03 13:11:02 +00:00
bool Database::finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character, int accountId) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
SELECT "PlayerID"
FROM "Players"
2020-12-03 13:11:02 +00:00
WHERE "PlayerID" = ? AND "AccountID" = ? AND "AppearanceFlag" = 0
LIMIT 1;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, character->PCStyle.iPC_UID);
2020-12-03 13:11:02 +00:00
sqlite3_bind_int(stmt, 2, accountId);
2020-12-02 13:55:17 +00:00
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
2020-12-02 13:55:17 +00:00
if ( rc != SQLITE_ROW)
2020-12-03 13:11:02 +00:00
{
std::cout << "[WARN] Database: Invalid data submitted to finish character creation" << std::endl;
return false;
}
sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, NULL);
sql = R"(
UPDATE "Players"
SET "AppearanceFlag" = 1
WHERE "PlayerID" = ?;
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, character->PCStyle.iPC_UID);
2020-12-03 00:40:36 +00:00
rc = sqlite3_step(stmt);
2020-12-02 13:55:17 +00:00
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE)
{
2020-12-02 13:55:17 +00:00
sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
return false;
}
sql = R"(
UPDATE "Appearances"
SET
"Body" = ? ,
"EyeColor" = ? ,
"FaceStyle" = ? ,
"Gender" = ? ,
"HairColor" = ? ,
"HairStyle" = ? ,
"Height" = ? ,
"SkinColor" = ?
WHERE "PlayerID" = ? ;
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, character->PCStyle.iBody);
sqlite3_bind_int(stmt, 2, character->PCStyle.iEyeColor);
sqlite3_bind_int(stmt, 3, character->PCStyle.iFaceStyle);
sqlite3_bind_int(stmt, 4, character->PCStyle.iGender);
sqlite3_bind_int(stmt, 5, character->PCStyle.iHairColor);
sqlite3_bind_int(stmt, 6, character->PCStyle.iHairStyle);
sqlite3_bind_int(stmt, 7, character->PCStyle.iHeight);
sqlite3_bind_int(stmt, 8, character->PCStyle.iSkinColor);
sqlite3_bind_int(stmt, 9, character->PCStyle.iPC_UID);
2020-12-02 13:55:17 +00:00
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE)
{
2020-12-02 13:55:17 +00:00
sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
return false;
}
sql = R"(
INSERT INTO "Inventory"
("PlayerID", "Slot", "Id", "Type", "Opt")
VALUES (?, ?, ?, ?, 1);
)";
2020-12-02 13:55:17 +00:00
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
int items[3] = { character->sOn_Item.iEquipUBID, character->sOn_Item.iEquipLBID, character->sOn_Item.iEquipFootID };
for (int i = 0; i < 3; i++) {
2020-12-02 13:55:17 +00:00
sqlite3_bind_int(stmt, 1, character->PCStyle.iPC_UID);
sqlite3_bind_int(stmt, 2, i+1);
sqlite3_bind_int(stmt, 3, items[i]);
sqlite3_bind_int(stmt, 4, i+1);
2020-12-02 13:55:17 +00:00
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE)
{
sqlite3_finalize(stmt);
2020-12-02 13:55:17 +00:00
sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
return false;
}
sqlite3_reset(stmt);
}
sqlite3_finalize(stmt);
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, NULL);
return true;
}
2020-12-03 13:11:02 +00:00
bool Database::finishTutorial(int playerID, int accountID) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
SELECT "PlayerID"
FROM "Players"
2020-12-03 13:11:02 +00:00
WHERE "PlayerID" = ? AND "AccountID" = ? AND "TutorialFlag" = 0
LIMIT 1;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, playerID);
2020-12-03 13:11:02 +00:00
sqlite3_bind_int(stmt, 2, accountID);
if (sqlite3_step(stmt) != SQLITE_ROW)
{
2020-12-03 13:11:02 +00:00
std::cout << "[WARN] Database: Invalid data submitted to finish tutorial" << std::endl;
sqlite3_finalize(stmt);
return false;
}
sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, NULL);
// Lightning Gun
sql = R"(
INSERT INTO "Inventory"
("PlayerID", "Slot", "Id", "Type", "Opt")
VALUES (?, ?, ?, ?, 1);
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, playerID);
sqlite3_bind_int(stmt, 2, 0);
sqlite3_bind_int(stmt, 3, 328);
sqlite3_bind_int(stmt, 4, 0);
2020-12-02 13:55:17 +00:00
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE)
{
2020-12-02 13:55:17 +00:00
sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
return false;
}
// Nano Buttercup
sql = R"(
INSERT INTO "Nanos"
("PlayerID", "Id", "Skill")
VALUES (?, ?, ?);
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, playerID);
sqlite3_bind_int(stmt, 2, 1);
sqlite3_bind_int(stmt, 3, 1);
2020-12-02 13:55:17 +00:00
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE)
{
2020-12-02 13:55:17 +00:00
sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
return false;
}
sql = R"(
UPDATE "Players"
SET "TutorialFlag" = 1,
"Nano1" = 1,
"Quests" = ?
WHERE "PlayerID" = ?;
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
// save missions nr 1 & 2
unsigned char questBuffer[128] = { 0 };
questBuffer[0] = 3;
sqlite3_bind_blob(stmt, 1, questBuffer, sizeof(questBuffer), 0);
sqlite3_bind_int(stmt, 2, playerID);
2020-12-02 13:55:17 +00:00
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE)
{
2020-12-02 13:55:17 +00:00
sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
return false;
}
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, NULL);
return true;
}
int Database::deleteCharacter(int characterID, int userID) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
SELECT "Slot"
FROM "Players"
WHERE "AccountID" = ? AND "PlayerID" = ?
LIMIT 1;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, userID);
sqlite3_bind_int(stmt, 2, characterID);
int rc = sqlite3_step(stmt);
if (rc != SQLITE_ROW)
{
sqlite3_finalize(stmt);
return 0;
2020-09-02 15:53:39 +00:00
}
int slot = sqlite3_column_int(stmt, 0);
sql = R"(
DELETE FROM "Players"
WHERE "AccountID" = ? AND "PlayerID" = ?;
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, userID);
sqlite3_bind_int(stmt, 2, characterID);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE)
return 0;
return slot;
}
2020-12-03 13:11:02 +00:00
void Database::getCharInfo(std::vector <sP_LS2CL_REP_CHAR_INFO>* result, int userID) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
SELECT p.PlayerID, p.Slot, p.Firstname, p.LastName, p.Level, p.AppearanceFlag, p.TutorialFlag, p.PayZoneFlag,
p.XCoordinates, p.YCoordinates, p.ZCoordinates, p.NameCheck,
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
WHERE p.AccountID = ?
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, userID);
while (sqlite3_step(stmt) == SQLITE_ROW) {
sP_LS2CL_REP_CHAR_INFO toAdd = {};
toAdd.sPC_Style.iPC_UID = sqlite3_column_int(stmt, 0);
toAdd.iSlot = sqlite3_column_int(stmt, 1);
// parsing const unsigned char* to char16_t
std::string placeHolder = std::string(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2)));
U8toU16(placeHolder, toAdd.sPC_Style.szFirstName, sizeof(toAdd.sPC_Style.szFirstName));
placeHolder = std::string(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3)));
U8toU16(placeHolder, toAdd.sPC_Style.szLastName, sizeof(toAdd.sPC_Style.szLastName));
toAdd.iLevel = sqlite3_column_int(stmt, 4);
toAdd.sPC_Style2.iAppearanceFlag = sqlite3_column_int(stmt, 5);
toAdd.sPC_Style2.iTutorialFlag = sqlite3_column_int(stmt, 6);
toAdd.sPC_Style2.iPayzoneFlag = sqlite3_column_int(stmt, 7);
toAdd.iX = sqlite3_column_int(stmt, 8);
toAdd.iY = sqlite3_column_int(stmt, 9);
toAdd.iZ = sqlite3_column_int(stmt, 10);
toAdd.sPC_Style.iNameCheck = sqlite3_column_int(stmt, 11);
toAdd.sPC_Style.iBody = sqlite3_column_int(stmt, 12);
toAdd.sPC_Style.iEyeColor = sqlite3_column_int(stmt, 13);
toAdd.sPC_Style.iFaceStyle = sqlite3_column_int(stmt, 14);
toAdd.sPC_Style.iGender = sqlite3_column_int(stmt, 15);
toAdd.sPC_Style.iHairColor = sqlite3_column_int(stmt, 16);
toAdd.sPC_Style.iHairStyle = sqlite3_column_int(stmt, 17);
toAdd.sPC_Style.iHeight = sqlite3_column_int(stmt, 18);
toAdd.sPC_Style.iSkinColor = sqlite3_column_int(stmt, 19);
// request aEquip
const char* sql2 = R"(
SELECT "Slot", "Type", "Id", "Opt", "TimeLimit" from Inventory
WHERE "PlayerID" = ? AND "Slot"< ?
)";
sqlite3_stmt* stmt2;
sqlite3_prepare_v2(db, sql2, -1, &stmt2, 0);
sqlite3_bind_int(stmt2, 1, toAdd.sPC_Style.iPC_UID);
sqlite3_bind_int(stmt2, 2, AEQUIP_COUNT);
while (sqlite3_step(stmt2) == SQLITE_ROW) {
sItemBase* item = &toAdd.aEquip[sqlite3_column_int(stmt2, 0)];
item->iType = sqlite3_column_int(stmt2, 1);
item->iID = sqlite3_column_int(stmt2, 2);
item->iOpt = sqlite3_column_int(stmt2, 3);
item->iTimeLimit = sqlite3_column_int(stmt2, 4);
}
sqlite3_finalize(stmt2);
2020-12-03 13:11:02 +00:00
result->push_back(toAdd);
}
sqlite3_finalize(stmt);
}
// XXX: This is never called?
void Database::evaluateCustomName(int characterID, CustomName decision) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
UPDATE "Players"
SET "NameCheck" = ?
WHERE "PlayerID" = ?;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, int(decision));
sqlite3_bind_int(stmt, 2, characterID);
if (sqlite3_step(stmt) != SQLITE_DONE)
std::cout << "[WARN] Database: Failed to update nameCheck" << std::endl;
sqlite3_finalize(stmt);
}
bool Database::changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save, int accountId) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
UPDATE "Players"
SET "Firstname" = ?,
2020-12-03 13:11:02 +00:00
"LastName" = ?, "NameCheck" = ?
WHERE "PlayerID" = ? AND "AccountID" = ?;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
2020-12-03 13:11:02 +00:00
std::string firstName = U16toU8(save->szFirstName);
std::string lastName = U16toU8(save->szLastName);
sqlite3_bind_text(stmt, 1, firstName.c_str(), -1, 0);
sqlite3_bind_text(stmt, 2, lastName.c_str(), -1, 0);
// if FNCode isn't 0, it's a wheel name
int nameCheck = (settings::APPROVEALLNAMES || save->iFNCode) ? 1 : 0;
sqlite3_bind_int(stmt, 3, nameCheck);
sqlite3_bind_int(stmt, 4, save->iPCUID);
sqlite3_bind_int(stmt, 5, accountId);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc == SQLITE_DONE;
}
void Database::getPlayer(Player* plr, int id) {
std::lock_guard<std::mutex> 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.XCoordinates, p.YCoordinates, p.ZCoordinates, p.NameCheck,
p.Angle, p.HP, p.AccountLevel, p.FusionMatter, p.Taros, p.Quests,
p.BatteryW, p.BatteryN, p.Mentor, p.WarpLocationFlag,
2020-12-03 00:40:36 +00:00
p.SkywayLocationFlag1, p.SkywayLocationFlag2, p.CurrentMissionID,
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
WHERE p.PlayerID = ?
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, id);
if (sqlite3_step(stmt) != SQLITE_ROW) {
sqlite3_finalize(stmt);
std::cout << "[WARN] Database: Failed to load character [" << id << "]" << 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);
2020-12-03 00:40:36 +00:00
// parsing const unsigned char* to char16_t
std::string placeHolder = std::string(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2)));
2020-12-03 00:40:36 +00:00
U8toU16(placeHolder, plr->PCStyle.szFirstName, sizeof(plr->PCStyle.szFirstName));
placeHolder = std::string(reinterpret_cast<const char*>(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);
2020-12-03 00:40:36 +00:00
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);
2020-12-03 00:40:36 +00:00
plr->aSkywayLocationFlag[0] = sqlite3_column_int64(stmt, 25);
plr->aSkywayLocationFlag[1] = sqlite3_column_int64(stmt, 26);
plr->CurrentMissionID = sqlite3_column_int(stmt, 27);
2020-09-13 18:45:51 +00:00
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);
// get inventory
sql = R"(
SELECT "Slot", "Type", "Id", "Opt", "TimeLimit" from Inventory
WHERE "PlayerID" = ? AND "Slot" < ?
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, id);
// we don't want bank items here
sqlite3_bind_int(stmt, 2, AEQUIP_COUNT + AINVEN_COUNT);
while (sqlite3_step(stmt) == SQLITE_ROW) {
int slot = sqlite3_column_int(stmt, 0);
sItemBase* item = slot < AEQUIP_COUNT ? &plr->Equip[slot] : &plr->Inven[slot = AEQUIP_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);
}
Database::removeExpiredVehicles(plr);
// get quest inventory
2020-09-07 00:16:44 +00:00
sql = R"(
SELECT "Slot", "Id", "Opt" from QuestItems
WHERE "PlayerID" = ?
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, id);
while (sqlite3_step(stmt) == SQLITE_ROW) {
int slot = sqlite3_column_int(stmt, 0);
sItemBase* item = &plr->QInven[slot];
item->iType = 8;
item->iID = sqlite3_column_int(stmt, 1);
item->iOpt = sqlite3_column_int(stmt, 2);
}
// get nanos
sql = R"(
SELECT "Id", "Skill", "Stamina" from "Nanos"
WHERE "PlayerID" = ?
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, id);
while (sqlite3_step(stmt) == SQLITE_ROW) {
int id = sqlite3_column_int(stmt, 0);
sNano* nano = &plr->Nanos[id];
nano->iID = id;
nano->iSkillID = sqlite3_column_int(stmt, 1);
nano->iStamina = sqlite3_column_int(stmt, 2);
}
// get active quests
sql = R"(
SELECT "TaskId", "RemainingNPCCount1", "RemainingNPCCount2", "RemainingNPCCount3" from "RunningQuests"
WHERE "PlayerID" = ?
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, id);
int i = 0;
while (sqlite3_step(stmt) == SQLITE_ROW && i < ACTIVE_MISSION_COUNT) {
plr->tasks[i] = sqlite3_column_int(stmt, 0);
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);
}
// get buddies
sql = R"(
SELECT "PlayerAId", "PlayerBId" from "Buddyships"
WHERE "PlayerAId" = ? OR "PlayerBId" = ?
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, id);
sqlite3_bind_int(stmt, 2, id);
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;
}
sqlite3_finalize(stmt);
}
void Database::updatePlayer(Player *player) {
std::lock_guard<std::mutex> lock(dbCrit);
sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, NULL);
2020-09-07 00:16:44 +00:00
const char* sql = R"(
UPDATE "Players"
SET
"Level" = ? , "Nano1" = ?, "Nano2" = ?, "Nano3" = ?,
"XCoordinates" = ?, "YCoordinates" = ?, "ZCoordinates" = ?,
"Angle" = ?, "HP" = ?, "FusionMatter" = ?, "Taros" = ?, "Quests" = ?,
"BatteryW" = ?, "BatteryN" = ?, "WarplocationFlag" = ?,
"SkywayLocationFlag1" = ?, "SkywayLocationFlag2" = ?, "CurrentMissionID" = ?,
"PayZoneFlag" = ?
WHERE "PlayerID" = ?
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
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), 0);
sqlite3_bind_int(stmt, 13, player->batteryW);
sqlite3_bind_int(stmt, 14, player->batteryN);
sqlite3_bind_int(stmt, 15, player->iWarpLocationFlag);
2020-12-03 00:40:36 +00:00
sqlite3_bind_int64(stmt, 16, player->aSkywayLocationFlag[0]);
sqlite3_bind_int64(stmt, 17, player->aSkywayLocationFlag[1]);
sqlite3_bind_int(stmt, 18, player->CurrentMissionID);
sqlite3_bind_int(stmt, 19, player->PCStyle2.iPayzoneFlag);
sqlite3_bind_int(stmt, 20, player->iID);
if (sqlite3_step(stmt) != SQLITE_DONE) {
sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
sqlite3_finalize(stmt);
std::cout << "[WARN] Database: Failed to save player to database" << std::endl;
return;
2020-09-07 00:16:44 +00:00
}
// update inventory
sql = R"(
DELETE FROM "Inventory" WHERE "PlayerID" = ?;
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, player->iID);
sqlite3_step(stmt);
sql = R"(
INSERT INTO "Inventory"
2020-12-03 00:40:36 +00:00
("PlayerID", "Slot", "Type", "Opt" , "Id", "Timelimit")
VALUES (?, ?, ?, ?, ?, ?);
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
2020-12-03 00:40:36 +00:00
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);
2020-12-03 00:40:36 +00:00
if (sqlite3_step(stmt) != SQLITE_DONE) {
sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
sqlite3_finalize(stmt);
std::cout << "[WARN] Database: Failed to save player to database" << std::endl;
return;
2020-09-07 00:16:44 +00:00
}
sqlite3_reset(stmt);
2020-09-07 00:16:44 +00:00
}
2020-12-03 00:40:36 +00:00
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_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
sqlite3_finalize(stmt);
std::cout << "[WARN] Database: Failed to save player to database" << std::endl;
return;
}
sqlite3_reset(stmt);
}
// Update Quest Inventory
sql = R"(
DELETE FROM "QuestItems" WHERE "PlayerID" = ?;
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, player->iID);
sqlite3_step(stmt);
sql = R"(
INSERT INTO "QuestItems"
2020-12-03 00:40:36 +00:00
("PlayerID", "Slot", "Opt", "Id")
VALUES (?, ?, ?, ?);
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
2020-12-03 00:40:36 +00:00
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_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
sqlite3_finalize(stmt);
std::cout << "[WARN] Database: Failed to save player to database" << std::endl;
return;
Database saving update (#104) * implemented saving BatteryN and BatteryW * implemented saving mentor * moved int64->blob parsing to a separate function * moved parsing blob->int64 to a separate function * added functions for parsing int32->blob and vice versa * added functions for parsing int16->blob and vice versa * WIP saving quest items and active tasks * Quest items are stored in inventory table instead of blob * added sanity check for missionId * saving active missions works * removed unneccesary include * implemented saving warplocationflag, skywaylocationflag and currentmissionid in database * INFO DB message now shows how many accounts and player characters are in the database * fixed dbsaveinterval being in [login] instead of [shard] * fixed mission quit: - fixed wrong json name, causing qitems not deleting properly - quitting mission now resets npc kill count * adjusted saving active missions * removed blob parsing functions that ended up being unused * removed accidentaly added include * removed sending PCStyle2 on Player Enter * added a sanity check in itemMoveHandler * removed MapNum from PCLoad, as client doesn't even read it * set BuddyWarpCooldown to 60s on PCLoad * fixed a bug causing EXIT DUPLICATE not working * added creation and last login timestamps to accounts and players * added a sanity check for P_CL2LS_REQ_PC_EXIT_DUPLICATE * implemented web api support, toggled by new setting (off by default) * add usewebapi to config Co-authored-by: Gent <gentsemaj@live.com>
2020-09-21 19:43:53 +00:00
}
sqlite3_reset(stmt);
Database saving update (#104) * implemented saving BatteryN and BatteryW * implemented saving mentor * moved int64->blob parsing to a separate function * moved parsing blob->int64 to a separate function * added functions for parsing int32->blob and vice versa * added functions for parsing int16->blob and vice versa * WIP saving quest items and active tasks * Quest items are stored in inventory table instead of blob * added sanity check for missionId * saving active missions works * removed unneccesary include * implemented saving warplocationflag, skywaylocationflag and currentmissionid in database * INFO DB message now shows how many accounts and player characters are in the database * fixed dbsaveinterval being in [login] instead of [shard] * fixed mission quit: - fixed wrong json name, causing qitems not deleting properly - quitting mission now resets npc kill count * adjusted saving active missions * removed blob parsing functions that ended up being unused * removed accidentaly added include * removed sending PCStyle2 on Player Enter * added a sanity check in itemMoveHandler * removed MapNum from PCLoad, as client doesn't even read it * set BuddyWarpCooldown to 60s on PCLoad * fixed a bug causing EXIT DUPLICATE not working * added creation and last login timestamps to accounts and players * added a sanity check for P_CL2LS_REQ_PC_EXIT_DUPLICATE * implemented web api support, toggled by new setting (off by default) * add usewebapi to config Co-authored-by: Gent <gentsemaj@live.com>
2020-09-21 19:43:53 +00:00
}
// Update Nanos
sql = R"(
DELETE FROM "Nanos" WHERE "PlayerID" = ?;
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, player->iID);
sqlite3_step(stmt);
Database saving update (#104) * implemented saving BatteryN and BatteryW * implemented saving mentor * moved int64->blob parsing to a separate function * moved parsing blob->int64 to a separate function * added functions for parsing int32->blob and vice versa * added functions for parsing int16->blob and vice versa * WIP saving quest items and active tasks * Quest items are stored in inventory table instead of blob * added sanity check for missionId * saving active missions works * removed unneccesary include * implemented saving warplocationflag, skywaylocationflag and currentmissionid in database * INFO DB message now shows how many accounts and player characters are in the database * fixed dbsaveinterval being in [login] instead of [shard] * fixed mission quit: - fixed wrong json name, causing qitems not deleting properly - quitting mission now resets npc kill count * adjusted saving active missions * removed blob parsing functions that ended up being unused * removed accidentaly added include * removed sending PCStyle2 on Player Enter * added a sanity check in itemMoveHandler * removed MapNum from PCLoad, as client doesn't even read it * set BuddyWarpCooldown to 60s on PCLoad * fixed a bug causing EXIT DUPLICATE not working * added creation and last login timestamps to accounts and players * added a sanity check for P_CL2LS_REQ_PC_EXIT_DUPLICATE * implemented web api support, toggled by new setting (off by default) * add usewebapi to config Co-authored-by: Gent <gentsemaj@live.com>
2020-09-21 19:43:53 +00:00
sql = R"(
INSERT INTO "Nanos"
2020-12-03 00:40:36 +00:00
("PlayerID", "Id", "SKill", "Stamina")
VALUES (?, ?, ?, ?);
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
2020-12-03 00:40:36 +00:00
for (int i = 0; i < SIZEOF_NANO_BANK_SLOT; 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_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
sqlite3_finalize(stmt);
std::cout << "[WARN] Database: Failed to save player to database" << std::endl;
return;
}
sqlite3_reset(stmt);
Database saving update (#104) * implemented saving BatteryN and BatteryW * implemented saving mentor * moved int64->blob parsing to a separate function * moved parsing blob->int64 to a separate function * added functions for parsing int32->blob and vice versa * added functions for parsing int16->blob and vice versa * WIP saving quest items and active tasks * Quest items are stored in inventory table instead of blob * added sanity check for missionId * saving active missions works * removed unneccesary include * implemented saving warplocationflag, skywaylocationflag and currentmissionid in database * INFO DB message now shows how many accounts and player characters are in the database * fixed dbsaveinterval being in [login] instead of [shard] * fixed mission quit: - fixed wrong json name, causing qitems not deleting properly - quitting mission now resets npc kill count * adjusted saving active missions * removed blob parsing functions that ended up being unused * removed accidentaly added include * removed sending PCStyle2 on Player Enter * added a sanity check in itemMoveHandler * removed MapNum from PCLoad, as client doesn't even read it * set BuddyWarpCooldown to 60s on PCLoad * fixed a bug causing EXIT DUPLICATE not working * added creation and last login timestamps to accounts and players * added a sanity check for P_CL2LS_REQ_PC_EXIT_DUPLICATE * implemented web api support, toggled by new setting (off by default) * add usewebapi to config Co-authored-by: Gent <gentsemaj@live.com>
2020-09-21 19:43:53 +00:00
}
// Update Running Quests
sql = R"(
DELETE FROM "RunningQuests" WHERE "PlayerID" = ?;
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, player->iID);
sqlite3_step(stmt);
2020-11-08 17:42:27 +00:00
sql = R"(
INSERT INTO "RunningQuests"
("PlayerID", "TaskId", "RemainingNPCCount1", "RemainingNPCCount2", "RemainingNPCCount3")
VALUES (?, ?, ?, ?, ?);
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
2020-12-03 00:40:36 +00:00
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]);
2020-11-08 17:42:27 +00:00
if (sqlite3_step(stmt) != SQLITE_DONE) {
sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
sqlite3_finalize(stmt);
std::cout << "[WARN] Database: Failed to save player to database" << std::endl;
return;
2020-11-08 17:42:27 +00:00
}
sqlite3_reset(stmt);
2020-11-08 17:42:27 +00:00
}
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, NULL);
sqlite3_finalize(stmt);
2020-11-08 17:42:27 +00:00
}
// note: do not use. explicitly add/remove records instead.
void Database::updateBuddies(Player* player) {
//db.begin_transaction();
//db.remove_all<Buddyship>( // remove all buddyships with this player involved
// where(c(&Buddyship::PlayerAId) == player->iID || c(&Buddyship::PlayerBId) == player->iID)
// );
//// iterate through player's buddies and add records for each non-zero entry
//for (int i = 0; i < 50; i++) {
// if (player->buddyIDs[i] != 0) {
// Buddyship record;
// record.PlayerAId = player->iID;
// record.PlayerBId = player->buddyIDs[i];
// record.Status = 0; // still not sure how we'll handle blocking
// db.insert(record);
// }
//}
//db.commit();
}
void Database::removeExpiredVehicles(Player* player) {
int32_t currentTime = getTimestamp();
// we want to leave only 1 expired vehicle on player to delete it with the client packet
std::vector<sItemBase*> toRemove;
// equiped vehicle
if (player->Equip[8].iOpt > 0 && player->Equip[8].iTimeLimit < currentTime) {
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) {
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
2020-09-22 11:41:28 +00:00
for (int i = 0; i < (int)toRemove.size()-1; i++) {
memset(toRemove[i], 0, sizeof(sItemBase));
}
2020-09-07 00:16:44 +00:00
}
Database saving update (#104) * implemented saving BatteryN and BatteryW * implemented saving mentor * moved int64->blob parsing to a separate function * moved parsing blob->int64 to a separate function * added functions for parsing int32->blob and vice versa * added functions for parsing int16->blob and vice versa * WIP saving quest items and active tasks * Quest items are stored in inventory table instead of blob * added sanity check for missionId * saving active missions works * removed unneccesary include * implemented saving warplocationflag, skywaylocationflag and currentmissionid in database * INFO DB message now shows how many accounts and player characters are in the database * fixed dbsaveinterval being in [login] instead of [shard] * fixed mission quit: - fixed wrong json name, causing qitems not deleting properly - quitting mission now resets npc kill count * adjusted saving active missions * removed blob parsing functions that ended up being unused * removed accidentaly added include * removed sending PCStyle2 on Player Enter * added a sanity check in itemMoveHandler * removed MapNum from PCLoad, as client doesn't even read it * set BuddyWarpCooldown to 60s on PCLoad * fixed a bug causing EXIT DUPLICATE not working * added creation and last login timestamps to accounts and players * added a sanity check for P_CL2LS_REQ_PC_EXIT_DUPLICATE * implemented web api support, toggled by new setting (off by default) * add usewebapi to config Co-authored-by: Gent <gentsemaj@live.com>
2020-09-21 19:43:53 +00:00
2020-12-03 13:11:02 +00:00
// buddies
2020-11-08 17:42:27 +00:00
int Database::getNumBuddies(Player* player) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
SELECT COUNT(*) FROM "Buddyships"
WHERE "PlayerAId" = ? OR "PlayerBId" = ?;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, player->iID);
sqlite3_bind_int(stmt, 2, player->iID);
sqlite3_step(stmt);
int result = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
2020-11-08 17:42:27 +00:00
// again, for peace of mind
return result > 50 ? 50 : result;
2020-11-08 17:42:27 +00:00
}
void Database::addBuddyship(int playerA, int playerB) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
INSERT INTO "Buddyships"
("PlayerAId", "PlayerBId", Status")
VALUES (?, ?, ?);
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, playerA);
sqlite3_bind_int(stmt, 2, playerB);
// blocking???
sqlite3_bind_int(stmt, 3, 0);
if (sqlite3_step(stmt) != SQLITE_DONE)
std::cout << "[WARN] Database: failed to add buddies" << std::endl;
sqlite3_finalize(stmt);
}
void Database::removeBuddyship(int playerA, int playerB) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
DELETE FROM "Buddyships"
WHERE ("PlayerAId" = ? AND "PlayerBId" = ?) OR ("PlayerAId" = ? AND "PlayerBId" = ?);
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, playerA);
sqlite3_bind_int(stmt, 2, playerB);
sqlite3_bind_int(stmt, 3, playerB);
sqlite3_bind_int(stmt, 4, playerA);
if (sqlite3_step(stmt) != SQLITE_DONE)
std::cout << "[WARN] Database: failed to remove buddies" << std::endl;
sqlite3_finalize(stmt);
}
// email
int Database::getUnreadEmailCount(int playerID) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
SELECT COUNT (*) FROM "EmailData"
WHERE "PlayerId" = ? AND "ReadFlag" = 0;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, playerID);
sqlite3_step(stmt);
return sqlite3_column_int(stmt, 0);
}
std::vector<Database::EmailData> Database::getEmails(int playerID, int page) {
std::lock_guard<std::mutex> lock(dbCrit);
std::vector<Database::EmailData> emails;
const char* sql = R"(
SELECT "MsgIndex", "ItemFlag", "ReadFlag", "SenderId", "SenderFirstName", "SenderLastName", "SubjectLine", "MsgBody",
"Taros", "SendTime", "DeleteTime"
FROM "EmailData"
WHERE "PlayerId" = ?
ORDER BY "MsgIndex" DESC
LIMIT 5
OFFSET ?;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, playerID);
int offset = 5 * page - 5;
sqlite3_bind_int(stmt, 2, offset);
while (sqlite3_step(stmt) == SQLITE_ROW) {
Database::EmailData toAdd;
toAdd.PlayerId = playerID;
toAdd.MsgIndex = sqlite3_column_int(stmt, 0);
toAdd.ItemFlag = sqlite3_column_int(stmt, 1);
toAdd.ReadFlag = sqlite3_column_int(stmt, 2);
toAdd.SenderId = sqlite3_column_int(stmt, 3);
toAdd.SenderFirstName = std::string(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 4)));
toAdd.SenderLastName = std::string(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 5)));
toAdd.SubjectLine = std::string(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 6)));
toAdd.MsgBody = std::string(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 7)));
toAdd.Taros = sqlite3_column_int(stmt, 8);
toAdd.SendTime = sqlite3_column_int64(stmt, 9);
toAdd.DeleteTime = sqlite3_column_int64(stmt, 10);
emails.push_back(toAdd);
}
sqlite3_finalize(stmt);
return emails;
}
Database::EmailData Database::getEmail(int playerID, int index) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
SELECT "ItemFlag", "ReadFlag", "SenderId", "SenderFirstName", "SenderLastName", "SubjectLine", "MsgBody",
"Taros", "SendTime", "DeleteTime"
FROM "EmailData"
WHERE "PlayerId" = ? AND "MsgIndex" = ?;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, playerID);
sqlite3_bind_int(stmt, 2, index);
Database::EmailData result;
if (sqlite3_step(stmt) != SQLITE_ROW) {
std::cout << "[WARN] Database: Email not found!" << std::endl;
sqlite3_finalize(stmt);
return result;
}
result.PlayerId = playerID;
result.MsgIndex = index;
result.ItemFlag = sqlite3_column_int(stmt, 0);
result.ReadFlag = sqlite3_column_int(stmt, 1);
result.SenderId = sqlite3_column_int(stmt, 2);
result.SenderFirstName = std::string(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 3)));
result.SenderLastName = std::string(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 4)));
result.SubjectLine = std::string(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 5)));
result.MsgBody = std::string(reinterpret_cast<const char*>(sqlite3_column_text(stmt, 6)));
result.Taros = sqlite3_column_int(stmt, 7);
result.SendTime = sqlite3_column_int64(stmt, 8);
result.DeleteTime = sqlite3_column_int64(stmt, 9);
if (sqlite3_step(stmt) == SQLITE_ROW)
std::cout << "[WARN] Database: Player has multiple emails with the same index ?!" << std::endl;
sqlite3_finalize(stmt);
return result;
}
sItemBase* Database::getEmailAttachments(int playerID, int index) {
std::lock_guard<std::mutex> lock(dbCrit);
sItemBase* items = new sItemBase[4];
for (int i = 0; i < 4; i++)
items[i] = { 0, 0, 0, 0 };
const char* sql = R"(
SELECT "Slot", "Id", "Type", "Opt", "TimeLimit"
FROM "EmailItems"
WHERE "PlayerId" = ? AND "MsgIndex" = ?;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, playerID);
sqlite3_bind_int(stmt, 2, index);
while (sqlite3_step(stmt) == SQLITE_ROW) {
int slot = sqlite3_column_int(stmt, 0) - 1;
if (slot < 0 || slot > 3) {
std::cout << "[WARN] Email item has invalid slot number ?!" << std::endl;
continue;
}
items[slot].iID = sqlite3_column_int(stmt, 1);
items[slot].iType = sqlite3_column_int(stmt, 2);
items[slot].iOpt = sqlite3_column_int(stmt, 3);
items[slot].iTimeLimit = sqlite3_column_int(stmt, 4);
}
return items;
}
void Database::updateEmailContent(EmailData* data) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
SELECT COUNT(*) FROM "EmailItems"
WHERE "PlayerId" = ? AND "MsgIndex" = ?;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, data->PlayerId);
sqlite3_bind_int(stmt, 2, data->MsgIndex);
sqlite3_step(stmt);
int attachmentsCount = sqlite3_column_int(stmt, 0);
data->ItemFlag = (data->Taros > 0 || attachmentsCount > 0) ? 1 : 0; // set attachment flag dynamically
// TODO: CHANGE THIS TO UPDATE
sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, NULL);
2020-12-03 00:40:36 +00:00
sql = R"(
DELETE FROM "EmailData"
WHERE "PlayerId" = ? AND "MsgIndex" = ?;
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, data->PlayerId);
sqlite3_bind_int(stmt, 2, data->MsgIndex);
sqlite3_step(stmt);
2020-12-03 00:40:36 +00:00
sql = R"(
INSERT INTO "EmailData"
("PlayerId", "MsgIndex", "ReadFlag", "ItemFlag", "SenderId", "SenderFirstName", "SenderLastName",
2020-12-03 00:40:36 +00:00
"SubjectLine", "MsgBody", "Taros", "SendTime", "DeleteTime" )
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, data->PlayerId);
sqlite3_bind_int(stmt, 2, data->MsgIndex);
sqlite3_bind_int(stmt, 3, data->ReadFlag);
sqlite3_bind_int(stmt, 4, data->ItemFlag);
sqlite3_bind_int(stmt, 5, data->SenderId);
sqlite3_bind_text(stmt, 6, data->SenderFirstName.c_str(), -1, 0);
sqlite3_bind_text(stmt, 7, data->SenderLastName.c_str(), -1, 0);
sqlite3_bind_text(stmt, 8, data->SubjectLine.c_str(), -1, 0);
sqlite3_bind_text(stmt, 9, data->MsgBody.c_str(), -1, 0);
sqlite3_bind_int(stmt, 10, data->Taros);
sqlite3_bind_int64(stmt, 11, data->SendTime);
sqlite3_bind_int64(stmt, 12, data->DeleteTime);
if (sqlite3_step(stmt) != SQLITE_DONE)
std::cout << "[WARN] Database: failed to update email" << std::endl;
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, NULL);
sqlite3_finalize(stmt);
}
void Database::deleteEmailAttachments(int playerID, int index, int slot) {
std::lock_guard<std::mutex> lock(dbCrit);
sqlite3_stmt* stmt;
if (slot == -1) { // delete all
const char* sql = R"(
DELETE FROM "EmailItems"
WHERE "PlayerId" = ? AND "MsgIndex" = ?;
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, playerID);
sqlite3_bind_int(stmt, 2, index);
if (sqlite3_step(stmt) != SQLITE_DONE)
std::cout << "[wARN] Database: Failed to delete email attachemtns" << std::endl;
} else { // delete single by comparing slot num
const char* sql = R"(
DELETE FROM "EmailItems"
WHERE "PlayerId" = ? AND "MsgIndex" = ? AND "Slot" = ?;
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, playerID);
sqlite3_bind_int(stmt, 2, index);
sqlite3_bind_int(stmt, 3, slot);
if (sqlite3_step(stmt) != SQLITE_DONE)
std::cout << "[wARN] Database: Failed to delete email attachemtns" << std::endl;
}
sqlite3_finalize(stmt);
}
void Database::deleteEmails(int playerID, int64_t* indices) {
std::lock_guard<std::mutex> lock(dbCrit);
sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, NULL);
sqlite3_stmt* stmt;
const char* sql = R"(
DELETE FROM "EmailData"
WHERE "PlayerId" = ? AND "MsgIndex" = ?;
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
for (int i = 0; i < 5; i++) {
sqlite3_bind_int(stmt, 1, playerID);
sqlite3_bind_int64(stmt, 2, indices[i]);
sqlite3_step(stmt);
sqlite3_reset(stmt);
}
sqlite3_finalize(stmt);
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, NULL);
}
int Database::getNextEmailIndex(int playerID) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
SELECT "MsgIndex" FROM "EmailData"
WHERE "PlayerId" = ?
ORDER BY "MsgIndex" DESC
LIMIT 1
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, playerID);
sqlite3_step(stmt);
int index = sqlite3_column_int(stmt, 0);
return (index > 0 ? index + 1 : 1);
}
void Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments) {
std::lock_guard<std::mutex> lock(dbCrit);
sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, NULL);
const char* sql = R"(
INSERT INTO "EmailData"
("PlayerId", "MsgIndex", "ReadFlag", "ItemFlag", "SenderId", "SenderFirstName", "SenderLastName",
"SubjectLine", "MsgBody", "Taros", "SendTime", "DeleteTime"
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, data->PlayerId);
sqlite3_bind_int(stmt, 2, data->MsgIndex);
sqlite3_bind_int(stmt, 3, data->ReadFlag);
sqlite3_bind_int(stmt, 4, data->ItemFlag);
sqlite3_bind_int(stmt, 5, data->SenderId);
sqlite3_bind_text(stmt, 6, data->SenderFirstName.c_str(), -1, 0);
sqlite3_bind_text(stmt, 7, data->SenderLastName.c_str(), -1, 0);
sqlite3_bind_text(stmt, 8, data->SubjectLine.c_str(), -1, 0);
sqlite3_bind_text(stmt, 9, data->MsgBody.c_str(), -1, 0);
sqlite3_bind_int(stmt, 10, data->Taros);
sqlite3_bind_int64(stmt, 11, data->SendTime);
sqlite3_bind_int64(stmt, 12, data->DeleteTime);
if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[WARN] Database: Failed to send email" << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
sqlite3_finalize(stmt);
return;
}
int slot = 1;
for (sItemBase item : attachments) {
sql = R"(
INSERT INTO EmailItems
("PlayerId", "MsgIndex", "Slot", "Id", "Type", "Opt", "TimeLimit")
VALUES (?, ?, ?, ?, ?, ?, ?);
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, data->PlayerId);
sqlite3_bind_int(stmt, 2, data->MsgIndex);
sqlite3_bind_int(stmt, 3, slot++);
sqlite3_bind_int(stmt, 4, item.iID);
sqlite3_bind_int(stmt, 5, item.iType);
sqlite3_bind_int(stmt, 6, item.iOpt);
sqlite3_bind_int(stmt, 7, item.iTimeLimit);
if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[WARN] Database: Failed to send email" << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
sqlite3_finalize(stmt);
return;
}
sqlite3_reset(stmt);
}
sqlite3_finalize(stmt);
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, NULL);
}