[refactor] Split Database.cpp into db subdirectory

* Database.hpp is still the only external include file (moved to db/)
* The header is still uppercase to match its namespace
* db/internal.hpp is the shared header for the DB source files
* Added -Isrc/ compile flag for src-relative include paths
* Hoisted CHDR above CSRC in Makefile (it was bothering me)
* make clean now removes all objects in the subdirectories as well
This commit is contained in:
dongresource 2021-03-16 02:06:54 +01:00
parent dd41d5b610
commit c5776b9322
17 changed files with 2023 additions and 2007 deletions

View File

@ -5,7 +5,7 @@ CXX=clang++
# -w suppresses all warnings (the part that's commented out helps me find memory leaks, it ruins performance though!) # -w suppresses all warnings (the part that's commented out helps me find memory leaks, it ruins performance though!)
# If compiling with ASAN, invoke like this: $ LSAN_OPTIONS=suppressions=suppr.txt bin/fusion # If compiling with ASAN, invoke like this: $ LSAN_OPTIONS=suppressions=suppr.txt bin/fusion
CFLAGS=-O3 #-g3 -fsanitize=address CFLAGS=-O3 #-g3 -fsanitize=address
CXXFLAGS=-Wall -Wno-unknown-pragmas -std=c++17 -O2 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(GIT_VERSION)\" -I./vendor #-g3 -fsanitize=address CXXFLAGS=-Wall -Wno-unknown-pragmas -std=c++17 -O2 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(GIT_VERSION)\" -I./src -I./vendor #-g3 -fsanitize=address
LDFLAGS=-lpthread -lsqlite3 #-g3 -fsanitize=address LDFLAGS=-lpthread -lsqlite3 #-g3 -fsanitize=address
# specifies the name of our exectuable # specifies the name of our exectuable
SERVER=bin/fusion SERVER=bin/fusion
@ -18,24 +18,36 @@ PROTOCOL_VERSION?=104
WIN_CC=x86_64-w64-mingw32-gcc WIN_CC=x86_64-w64-mingw32-gcc
WIN_CXX=x86_64-w64-mingw32-g++ WIN_CXX=x86_64-w64-mingw32-g++
WIN_CFLAGS=-O3 #-g3 -fsanitize=address WIN_CFLAGS=-O3 #-g3 -fsanitize=address
WIN_CXXFLAGS=-D_WIN32_WINNT=0x0601 -Wall -Wno-unknown-pragmas -std=c++17 -O3 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(GIT_VERSION)\" -I./vendor #-g3 -fsanitize=address WIN_CXXFLAGS=-D_WIN32_WINNT=0x0601 -Wall -Wno-unknown-pragmas -std=c++17 -O3 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(GIT_VERSION)\" -I./src -I./vendor #-g3 -fsanitize=address
WIN_LDFLAGS=-static -lws2_32 -lwsock32 -lsqlite3 #-g3 -fsanitize=address WIN_LDFLAGS=-static -lws2_32 -lwsock32 -lsqlite3 #-g3 -fsanitize=address
WIN_SERVER=bin/winfusion.exe WIN_SERVER=bin/winfusion.exe
# C code; currently exclusively from vendored libraries
CSRC=\ CSRC=\
vendor/bcrypt/bcrypt.c\ vendor/bcrypt/bcrypt.c\
vendor/bcrypt/crypt_blowfish.c\ vendor/bcrypt/crypt_blowfish.c\
vendor/bcrypt/crypt_gensalt.c\ vendor/bcrypt/crypt_gensalt.c\
vendor/bcrypt/wrapper.c\ vendor/bcrypt/wrapper.c\
CHDR=\
vendor/bcrypt/bcrypt.h\
vendor/bcrypt/crypt_blowfish.h\
vendor/bcrypt/crypt_gensalt.h\
vendor/bcrypt/ow-crypt.h\
vendor/bcrypt/winbcrypt.h\
CXXSRC=\ CXXSRC=\
src/db/init.cpp\
src/db/login.cpp\
src/db/shard.cpp\
src/db/player.cpp\
src/db/email.cpp\
src/ChatManager.cpp\ src/ChatManager.cpp\
src/CustomCommands.cpp\ src/CustomCommands.cpp\
src/CNLoginServer.cpp\ src/CNLoginServer.cpp\
src/CNProtocol.cpp\ src/CNProtocol.cpp\
src/CNShardServer.cpp\ src/CNShardServer.cpp\
src/CNShared.cpp\ src/CNShared.cpp\
src/Database.cpp\
src/Defines.cpp\ src/Defines.cpp\
src/Email.cpp\ src/Email.cpp\
src/main.cpp\ src/main.cpp\
@ -61,17 +73,14 @@ CXXSRC=\
src/Trading.cpp\ src/Trading.cpp\
# headers (for timestamp purposes) # headers (for timestamp purposes)
CHDR=\
vendor/bcrypt/bcrypt.h\
vendor/bcrypt/crypt_blowfish.h\
vendor/bcrypt/crypt_gensalt.h\
vendor/bcrypt/ow-crypt.h\
vendor/bcrypt/winbcrypt.h\
CXXHDR=\ CXXHDR=\
vendor/bcrypt/BCrypt.hpp\ vendor/bcrypt/BCrypt.hpp\
vendor/INIReader.hpp\ vendor/INIReader.hpp\
vendor/JSON.hpp\ vendor/JSON.hpp\
vendor/INIReader.hpp\
vendor/JSON.hpp\
src/db/Database.hpp\
src/db/internal.hpp\
src/ChatManager.hpp\ src/ChatManager.hpp\
src/CustomCommands.hpp\ src/CustomCommands.hpp\
src/CNLoginServer.hpp\ src/CNLoginServer.hpp\
@ -79,11 +88,8 @@ CXXHDR=\
src/CNShardServer.hpp\ src/CNShardServer.hpp\
src/CNShared.hpp\ src/CNShared.hpp\
src/CNStructs.hpp\ src/CNStructs.hpp\
src/Database.hpp\
src/Defines.hpp\ src/Defines.hpp\
src/Email.hpp\ src/Email.hpp\
vendor/INIReader.hpp\
vendor/JSON.hpp\
src/MissionManager.hpp\ src/MissionManager.hpp\
src/MobAI.hpp\ src/MobAI.hpp\
src/Combat.hpp\ src/Combat.hpp\
@ -151,7 +157,7 @@ src/main.o: version.h
# only gets rid of OpenFusion objects, so we don't need to # only gets rid of OpenFusion objects, so we don't need to
# recompile the libs every time # recompile the libs every time
clean: clean:
rm -f src/*.o $(SERVER) $(WIN_SERVER) version.h rm -f src/*.o src/*/*.o $(SERVER) $(WIN_SERVER) version.h
# gets rid of all compiled objects, including the libraries # gets rid of all compiled objects, including the libraries
nuke: nuke:

View File

@ -3,9 +3,9 @@
#include "ChatManager.hpp" #include "ChatManager.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "BuddyManager.hpp" #include "BuddyManager.hpp"
#include "Database.hpp" #include "db/Database.hpp"
#include "ItemManager.hpp" #include "ItemManager.hpp"
#include "Database.hpp" #include "db/Database.hpp"
#include <iostream> #include <iostream>
#include <chrono> #include <chrono>

View File

@ -1,7 +1,7 @@
#include "CNLoginServer.hpp" #include "CNLoginServer.hpp"
#include "CNShared.hpp" #include "CNShared.hpp"
#include "CNStructs.hpp" #include "CNStructs.hpp"
#include "Database.hpp" #include "db/Database.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "ItemManager.hpp" #include "ItemManager.hpp"
#include <regex> #include <regex>

View File

@ -5,7 +5,7 @@
#include "MobAI.hpp" #include "MobAI.hpp"
#include "CNShared.hpp" #include "CNShared.hpp"
#include "settings.hpp" #include "settings.hpp"
#include "Database.hpp" #include "db/Database.hpp"
#include "Monitor.hpp" #include "Monitor.hpp"
#include "TableData.hpp" // for flush() #include "TableData.hpp" // for flush()

View File

@ -5,7 +5,7 @@
#include "NPCManager.hpp" #include "NPCManager.hpp"
#include "MobAI.hpp" #include "MobAI.hpp"
#include "ItemManager.hpp" #include "ItemManager.hpp"
#include "Database.hpp" #include "db/Database.hpp"
#include "TransportManager.hpp" #include "TransportManager.hpp"
#include "MissionManager.hpp" #include "MissionManager.hpp"

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@
#include "CNStructs.hpp" #include "CNStructs.hpp"
#include "CNShardServer.hpp" #include "CNShardServer.hpp"
#include "Database.hpp" #include "db/Database.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "ItemManager.hpp" #include "ItemManager.hpp"
#include "ChatManager.hpp" #include "ChatManager.hpp"

View File

@ -6,7 +6,7 @@
#include "NanoManager.hpp" #include "NanoManager.hpp"
#include "GroupManager.hpp" #include "GroupManager.hpp"
#include "ChatManager.hpp" #include "ChatManager.hpp"
#include "Database.hpp" #include "db/Database.hpp"
#include "BuddyManager.hpp" #include "BuddyManager.hpp"
#include "Combat.hpp" #include "Combat.hpp"
#include "RacingManager.hpp" #include "RacingManager.hpp"

View File

@ -4,7 +4,7 @@
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "MissionManager.hpp" #include "MissionManager.hpp"
#include "ItemManager.hpp" #include "ItemManager.hpp"
#include "Database.hpp" #include "db/Database.hpp"
#include "NPCManager.hpp" #include "NPCManager.hpp"
std::map<int32_t, EPInfo> RacingManager::EPData; std::map<int32_t, EPInfo> RacingManager::EPData;

335
src/db/email.cpp Normal file
View File

@ -0,0 +1,335 @@
#include "db/internal.hpp"
// Email-related DB interactions
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, NULL);
sqlite3_bind_int(stmt, 1, playerID);
sqlite3_step(stmt);
int ret = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
return ret;
}
std::vector<EmailData> Database::getEmails(int playerID, int page) {
std::lock_guard<std::mutex> lock(dbCrit);
std::vector<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, NULL);
sqlite3_bind_int(stmt, 1, playerID);
int offset = 5 * page - 5;
sqlite3_bind_int(stmt, 2, offset);
while (sqlite3_step(stmt) == SQLITE_ROW) {
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;
}
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, NULL);
sqlite3_bind_int(stmt, 1, playerID);
sqlite3_bind_int(stmt, 2, index);
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);
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, NULL);
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);
}
sqlite3_finalize(stmt);
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, NULL);
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
sqlite3_finalize(stmt);
sql = R"(
UPDATE EmailData
SET
PlayerID = ?,
MsgIndex = ?,
ReadFlag = ?,
ItemFlag = ?,
SenderID = ?,
SenderFirstName = ?,
SenderLastName = ?,
SubjectLine = ?,
MsgBody = ?,
Taros = ?,
SendTime = ?,
DeleteTime = ?
WHERE PlayerID = ? AND MsgIndex = ?;
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
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, NULL);
sqlite3_bind_text(stmt, 7, data->SenderLastName.c_str(), -1, NULL);
sqlite3_bind_text(stmt, 8, data->SubjectLine.c_str(), -1, NULL);
sqlite3_bind_text(stmt, 9, data->MsgBody.c_str(), -1, NULL);
sqlite3_bind_int(stmt, 10, data->Taros);
sqlite3_bind_int64(stmt, 11, data->SendTime);
sqlite3_bind_int64(stmt, 12, data->DeleteTime);
sqlite3_bind_int(stmt, 13, data->PlayerId);
sqlite3_bind_int(stmt, 14, data->MsgIndex);
if (sqlite3_step(stmt) != SQLITE_DONE)
std::cout << "[WARN] Database: failed to update email: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt);
}
void Database::deleteEmailAttachments(int playerID, int index, int slot) {
std::lock_guard<std::mutex> lock(dbCrit);
sqlite3_stmt* stmt;
std::string sql(R"(
DELETE FROM EmailItems
WHERE PlayerID = ? AND MsgIndex = ?;
)");
if (slot != -1)
sql += " AND \"Slot\" = ? ";
sql += ";";
sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, playerID);
sqlite3_bind_int(stmt, 2, index);
if (slot != -1)
sqlite3_bind_int(stmt, 3, slot);
if (sqlite3_step(stmt) != SQLITE_DONE)
std::cout << "[WARN] Database: Failed to delete email attachments: " << sqlite3_errmsg(db) << 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, NULL);
for (int i = 0; i < 5; i++) {
sqlite3_bind_int(stmt, 1, playerID);
sqlite3_bind_int64(stmt, 2, indices[i]);
if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[WARN] Database: Failed to delete an email: " << sqlite3_errmsg(db) << std::endl;
}
sqlite3_reset(stmt);
}
sqlite3_finalize(stmt);
sqlite3_exec(db, "COMMIT;", 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, NULL);
sqlite3_bind_int(stmt, 1, playerID);
sqlite3_step(stmt);
int index = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
return (index > 0 ? index + 1 : 1);
}
bool 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, NULL);
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, NULL);
sqlite3_bind_text(stmt, 7, data->SenderLastName.c_str(), -1, NULL);
sqlite3_bind_text(stmt, 8, data->SubjectLine.c_str(), -1, NULL);
sqlite3_bind_text(stmt, 9, data->MsgBody.c_str(), -1, NULL);
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: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
sqlite3_finalize(stmt);
return false;
}
sqlite3_finalize(stmt);
sql = R"(
INSERT INTO EmailItems
(PlayerID, MsgIndex, Slot, ID, Type, Opt, TimeLimit)
VALUES (?, ?, ?, ?, ?, ?, ?);
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
// send attachments
int slot = 1;
for (sItemBase item : attachments) {
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: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
sqlite3_finalize(stmt);
return false;
}
sqlite3_reset(stmt);
}
sqlite3_finalize(stmt);
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
return true;
}

240
src/db/init.cpp Normal file
View File

@ -0,0 +1,240 @@
#include "db/internal.hpp"
#include "settings.hpp"
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
std::mutex dbCrit;
sqlite3 *db;
static void createMetaTable() {
std::lock_guard<std::mutex> lock(dbCrit); // XXX
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
const char* sql = R"(
CREATE TABLE Meta(
Key TEXT NOT NULL UNIQUE,
Value INTEGER NOT NULL
);
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[FATAL] Failed to create meta table: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt);
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
exit(1);
}
sqlite3_finalize(stmt);
sql = R"(
INSERT INTO Meta (Key, Value)
VALUES (?, ?);
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_text(stmt, 1, "ProtocolVersion", -1, NULL);
sqlite3_bind_int(stmt, 2, PROTOCOL_VERSION);
if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[FATAL] Failed to create meta table: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt);
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
exit(1);
}
sqlite3_reset(stmt);
sqlite3_bind_text(stmt, 1, "DatabaseVersion", -1, NULL);
sqlite3_bind_int(stmt, 2, DATABASE_VERSION);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) {
std::cout << "[FATAL] Failed to create meta table: " << sqlite3_errmsg(db) << std::endl;
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
exit(1);
}
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
std::cout << "[INFO] Created new meta table" << std::endl;
}
static void checkMetaTable() {
// first check if meta table exists
const char* sql = R"(
SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='Meta';
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (sqlite3_step(stmt) != SQLITE_ROW) {
std::cout << "[FATAL] Failed to check meta table" << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt);
exit(1);
}
int count = sqlite3_column_int(stmt, 0);
if (count == 0) {
sqlite3_finalize(stmt);
// check if there's other non-internal tables first
sql = R"(
SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%';
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (sqlite3_step(stmt) != SQLITE_ROW || sqlite3_column_int(stmt, 0) != 0) {
sqlite3_finalize(stmt);
std::cout << "[FATAL] Existing DB is outdated" << std::endl;
exit(1);
}
// create meta table
sqlite3_finalize(stmt);
return createMetaTable();
}
sqlite3_finalize(stmt);
// check protocol version
sql = R"(
SELECT Value FROM Meta WHERE Key = 'ProtocolVersion';
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (sqlite3_step(stmt) != SQLITE_ROW) {
std::cout << "[FATAL] Failed to check DB Protocol Version: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt);
exit(1);
}
if (sqlite3_column_int(stmt, 0) != PROTOCOL_VERSION) {
sqlite3_finalize(stmt);
std::cout << "[FATAL] DB Protocol Version doesn't match Server Build" << std::endl;
exit(1);
}
sqlite3_finalize(stmt);
sql = R"(
SELECT Value FROM Meta WHERE Key = 'DatabaseVersion';
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (sqlite3_step(stmt) != SQLITE_ROW) {
std::cout << "[FATAL] Failed to check DB Version: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt);
exit(1);
}
int dbVersion = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
if (dbVersion > DATABASE_VERSION) {
std::cout << "[FATAL] Server Build is incompatible with DB Version" << std::endl;
exit(1);
} else if (dbVersion < DATABASE_VERSION) {
// we're gonna migrate; back up the DB
std::cout << "[INFO] Backing up database" << std::endl;
// copy db file over using binary streams
std::ifstream src(settings::DBPATH, std::ios::binary);
std::ofstream dst(settings::DBPATH + ".old." + std::to_string(dbVersion), std::ios::binary);
dst << src.rdbuf();
src.close();
dst.close();
}
while (dbVersion != DATABASE_VERSION) {
// db migrations
std::cout << "[INFO] Migrating Database to Version " << dbVersion + 1 << std::endl;
std::string path = "sql/migration" + std::to_string(dbVersion) + ".sql";
std::ifstream file(path);
if (!file.is_open()) {
std::cout << "[FATAL] Failed to migrate database: Couldn't open migration file" << std::endl;
exit(1);
}
std::ostringstream stream;
stream << file.rdbuf();
std::string sql = stream.str();
int rc = sqlite3_exec(db, sql.c_str(), NULL, NULL, NULL);
if (rc != SQLITE_OK) {
std::cout << "[FATAL] Failed to migrate database: " << sqlite3_errmsg(db) << std::endl;
exit(1);
}
dbVersion++;
std::cout << "[INFO] Successful Database Migration to Version " << dbVersion << std::endl;
}
}
static void createTables() {
std::ifstream file("sql/tables.sql");
if (!file.is_open()) {
std::cout << "[FATAL] Failed to open database scheme" << std::endl;
exit(1);
}
std::ostringstream stream;
stream << file.rdbuf();
std::string read = stream.str();
const char* sql = read.c_str();
char* errMsg = 0;
int rc = sqlite3_exec(db, sql, NULL, NULL, &errMsg);
if (rc != SQLITE_OK) {
std::cout << "[FATAL] Database failed to create tables: " << errMsg << std::endl;
exit(1);
}
}
static int getTableSize(std::string tableName) {
std::lock_guard<std::mutex> lock(dbCrit); // XXX
const char* sql = "SELECT COUNT(*) FROM ?";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_text(stmt, 1, tableName.c_str(), -1, NULL);
sqlite3_step(stmt);
int result = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
return result;
}
void Database::open() {
// XXX: move locks here
int rc = sqlite3_open(settings::DBPATH.c_str(), &db);
if (rc != SQLITE_OK) {
std::cout << "[FATAL] Cannot open database: " << sqlite3_errmsg(db) << std::endl;
exit(1);
}
// foreign keys in sqlite are off by default; enable them
sqlite3_exec(db, "PRAGMA foreign_keys=ON;", NULL, NULL, NULL);
// just in case a DB operation collides with an external manual modification
sqlite3_busy_timeout(db, 2000);
checkMetaTable();
createTables();
std::cout << "[INFO] Database in operation ";
int accounts = getTableSize("Accounts");
int players = getTableSize("Players");
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;
}
void Database::close() {
sqlite3_close(db);
}

15
src/db/internal.hpp Normal file
View File

@ -0,0 +1,15 @@
#pragma once
#include "db/Database.hpp"
#include <sqlite3.h>
#if defined(__MINGW32__) && !defined(_GLIBCXX_HAS_GTHREADS)
#include "mingw/mingw.mutex.h"
#else
#include <mutex>
#endif
extern std::mutex dbCrit;
extern sqlite3 *db;
using namespace Database;

536
src/db/login.cpp Normal file
View File

@ -0,0 +1,536 @@
#include "db/internal.hpp"
#include "bcrypt/BCrypt.hpp"
void Database::findAccount(Account* account, std::string login) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
SELECT AccountID, Password, Selected, BannedUntil, BanReason
FROM Accounts
WHERE Login = ?
LIMIT 1;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_text(stmt, 1, login.c_str(), -1, NULL);
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);
account->BannedUntil = sqlite3_column_int64(stmt, 3);
account->BanReason = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 4));
}
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, AccountLevel)
VALUES (?, ?, ?);
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_text(stmt, 1, login.c_str(), -1, NULL);
std::string hashedPassword = BCrypt::generateHash(password);
sqlite3_bind_text(stmt, 2, hashedPassword.c_str(), -1, NULL);
sqlite3_bind_int(stmt, 3, settings::ACCLEVEL);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) {
std::cout << "[WARN] Database: failed to add new account" << std::endl;
return 0;
}
return sqlite3_last_insert_rowid(db);
}
void Database::updateSelected(int accountId, int slot) {
std::lock_guard<std::mutex> lock(dbCrit);
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, NULL);
sqlite3_bind_int(stmt, 1, slot);
sqlite3_bind_int(stmt, 2, accountId);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE)
std::cout << "[WARN] Database fail on updateSelected(): " << sqlite3_errmsg(db) << std::endl;
}
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, NULL);
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 COUNT(*)
FROM Players
WHERE FirstName = ? AND LastName = ?
LIMIT 1;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_text(stmt, 1, firstName.c_str(), -1, NULL);
sqlite3_bind_text(stmt, 2, lastName.c_str(), -1, NULL);
int rc = sqlite3_step(stmt);
bool result = (rc == SQLITE_ROW && sqlite3_column_int(stmt, 0) == 0);
sqlite3_finalize(stmt);
return result;
}
bool Database::isSlotFree(int accountId, int slotNum) {
std::lock_guard<std::mutex> lock(dbCrit);
if (slotNum < 1 || slotNum > 4) {
std::cout << "[WARN] Invalid slot number passed to isSlotFree()! " << slotNum << std::endl;
return false;
}
const char* sql = R"(
SELECT COUNT(*)
FROM Players
WHERE AccountID = ? AND Slot = ?
LIMIT 1;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, accountId);
sqlite3_bind_int(stmt, 2, slotNum);
int rc = sqlite3_step(stmt);
bool result = (rc == SQLITE_ROW && sqlite3_column_int(stmt, 0) == 0);
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,
XCoordinate, YCoordinate, ZCoordinate, Angle,
HP, NameCheck, Quests, SkywayLocationFlag, FirstUseFlag)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
)";
sqlite3_stmt* stmt;
std::string firstName = U16toU8(save->szFirstName);
std::string lastName = U16toU8(save->szLastName);
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, AccountID);
sqlite3_bind_int(stmt, 2, save->iSlotNum);
sqlite3_bind_text(stmt, 3, firstName.c_str(), -1, NULL);
sqlite3_bind_text(stmt, 4, lastName.c_str(), -1, NULL);
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);
// blobs
unsigned char blobBuffer[sizeof(Player::aQuestFlag)] = { 0 };
sqlite3_bind_blob(stmt, 11, blobBuffer, sizeof(Player::aQuestFlag), NULL);
sqlite3_bind_blob(stmt, 12, blobBuffer, sizeof(Player::aSkywayLocationFlag), NULL);
sqlite3_bind_blob(stmt, 13, blobBuffer, sizeof(Player::iFirstUseFlag), NULL);
if (sqlite3_step(stmt) != SQLITE_DONE) {
sqlite3_finalize(stmt);
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
return 0;
}
int playerId = sqlite3_last_insert_rowid(db);
sqlite3_finalize(stmt);
sql = R"(
INSERT INTO Appearances (PlayerID)
VALUES (?);
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, playerId);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) {
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
return 0;
}
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
return playerId;
}
bool Database::finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character, int accountId) {
std::lock_guard<std::mutex> lock(dbCrit);
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
const char* sql = R"(
UPDATE Players
SET AppearanceFlag = 1
WHERE PlayerID = ? AND AccountID = ? AND AppearanceFlag = 0;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, character->PCStyle.iPC_UID);
sqlite3_bind_int(stmt, 2, accountId);
if (sqlite3_step(stmt) != SQLITE_DONE) {
sqlite3_finalize(stmt);
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
return false;
}
sqlite3_finalize(stmt);
sql = R"(
UPDATE Appearances
SET
Body = ?,
EyeColor = ?,
FaceStyle = ?,
Gender = ?,
HairColor = ?,
HairStyle = ?,
Height = ?,
SkinColor = ?
WHERE PlayerID = ?;
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
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);
if (sqlite3_step(stmt) != SQLITE_DONE) {
sqlite3_finalize(stmt);
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
return false;
}
sqlite3_finalize(stmt);
sql = R"(
INSERT INTO Inventory (PlayerID, Slot, ID, Type, Opt)
VALUES (?, ?, ?, ?, 1);
)";
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++) {
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);
if (sqlite3_step(stmt) != SQLITE_DONE) {
sqlite3_finalize(stmt);
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
return false;
}
sqlite3_reset(stmt);
}
sqlite3_finalize(stmt);
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
return true;
}
bool Database::finishTutorial(int playerID, int accountID) {
std::lock_guard<std::mutex> lock(dbCrit);
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
const char* sql = R"(
UPDATE Players SET
TutorialFlag = 1,
Nano1 = ?,
Quests = ?
WHERE PlayerID = ? AND AccountID = ? AND TutorialFlag = 0;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
unsigned char questBuffer[128] = { 0 };
#ifndef ACADEMY
// save missions nr 1 & 2; equip Buttercup
questBuffer[0] = 3;
sqlite3_bind_int(stmt, 1, 1);
#else
// no, none of that
sqlite3_bind_int(stmt, 1, 0);
#endif
sqlite3_bind_blob(stmt, 2, questBuffer, sizeof(questBuffer), NULL);
sqlite3_bind_int(stmt, 3, playerID);
sqlite3_bind_int(stmt, 4, accountID);
if (sqlite3_step(stmt) != SQLITE_DONE) {
sqlite3_finalize(stmt);
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
return false;
}
sqlite3_finalize(stmt);
#ifndef ACADEMY
// Lightning Gun
sql = R"(
INSERT INTO Inventory
(PlayerID, Slot, ID, Type, Opt)
VALUES (?, 0, 328, 0, 1);
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, playerID);
if (sqlite3_step(stmt) != SQLITE_DONE) {
sqlite3_finalize(stmt);
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
return false;
}
sqlite3_finalize(stmt);
// Nano Buttercup
sql = R"(
INSERT INTO Nanos
(PlayerID, ID, Skill)
VALUES (?, 1, 1);
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, playerID);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) {
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
return false;
}
#endif
sqlite3_exec(db, "COMMIT;", 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, NULL);
sqlite3_bind_int(stmt, 1, userID);
sqlite3_bind_int(stmt, 2, characterID);
if (sqlite3_step(stmt) != SQLITE_ROW) {
sqlite3_finalize(stmt);
return 0;
}
int slot = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
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);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE)
return 0;
return slot;
}
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.XCoordinate, p.YCoordinate, p.ZCoordinate, 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, NULL);
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, NULL);
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);
result->push_back(toAdd);
}
sqlite3_finalize(stmt);
}
// NOTE: This is currently 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, NULL);
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: " << sqlite3_errmsg(db) << 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 = ?,
LastName = ?,
NameCheck = ?
WHERE PlayerID = ? AND AccountID = ?;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
std::string firstName = U16toU8(save->szFirstName);
std::string lastName = U16toU8(save->szLastName);
sqlite3_bind_text(stmt, 1, firstName.c_str(), -1, NULL);
sqlite3_bind_text(stmt, 2, lastName.c_str(), -1, NULL);
// 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;
}

537
src/db/player.cpp Normal file
View File

@ -0,0 +1,537 @@
#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<sItemBase*> 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<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.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<const char*>(sqlite3_column_text(stmt, 2)));
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);
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<int> 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);
}
void Database::updatePlayer(Player *player) {
std::lock_guard<std::mutex> lock(dbCrit);
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
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) {
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt);
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
return;
}
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) {
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);
return;
}
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) {
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);
return;
}
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) {
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);
return;
}
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) {
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);
return;
}
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) {
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);
return;
}
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) {
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);
return;
}
sqlite3_reset(stmt);
}
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
sqlite3_finalize(stmt);
}

329
src/db/shard.cpp Normal file
View File

@ -0,0 +1,329 @@
#include "db/internal.hpp"
#include <assert.h>
// Miscellanious in-game database interactions
static int getAccountIDFromPlayerID(int playerId, int *accountLevel=nullptr) {
const char *sql = R"(
SELECT Players.AccountID, AccountLevel
FROM Players
JOIN Accounts ON Players.AccountID = Accounts.AccountID
WHERE PlayerID = ?;
)";
sqlite3_stmt *stmt;
// get AccountID from PlayerID
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, playerId);
if (sqlite3_step(stmt) != SQLITE_ROW) {
std::cout << "[WARN] Database: failed to get AccountID from PlayerID: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt);
return -1;
}
int accountId = sqlite3_column_int(stmt, 0);
// optional secondary return value, for checking GM status
if (accountLevel != nullptr)
*accountLevel = sqlite3_column_int(stmt, 1);
sqlite3_finalize(stmt);
return accountId;
}
static bool banAccount(int accountId, int days, std::string& reason) {
const char* sql = R"(
UPDATE Accounts SET
BannedSince = (strftime('%s', 'now')),
BannedUntil = (strftime('%s', 'now')) + ?,
BanReason = ?
WHERE AccountID = ?;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, days * 86400); // convert days to seconds
sqlite3_bind_text(stmt, 2, reason.c_str(), -1, NULL);
sqlite3_bind_int(stmt, 3, accountId);
if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[WARN] Database: failed to ban account: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt);
return false;
}
sqlite3_finalize(stmt);
return true;
}
static bool unbanAccount(int accountId) {
const char* sql = R"(
UPDATE Accounts SET
BannedSince = 0,
BannedUntil = 0,
BanReason = ''
WHERE AccountID = ?;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, accountId);
if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[WARN] Database: failed to unban account: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt);
return false;
}
sqlite3_finalize(stmt);
return true;
}
// expressed in days
#define THIRTY_YEARS 10957
bool Database::banPlayer(int playerId, std::string& reason) {
std::lock_guard<std::mutex> lock(dbCrit);
int accountLevel;
int accountId = getAccountIDFromPlayerID(playerId, &accountLevel);
if (accountId < 0) {
return false;
}
if (accountLevel <= 30) {
std::cout << "[WARN] Cannot ban a GM." << std::endl;
return false;
}
// do the ban
if (!banAccount(accountId, THIRTY_YEARS, reason)) {
return false;
}
return true;
}
bool Database::unbanPlayer(int playerId) {
std::lock_guard<std::mutex> lock(dbCrit);
int accountId = getAccountIDFromPlayerID(playerId);
if (accountId < 0)
return false;
return unbanAccount(accountId);
}
// buddies
// returns num of buddies + blocked players
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, NULL);
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);
sql = R"(
SELECT COUNT(*)
FROM Blocks
WHERE PlayerID = ?;
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, player->iID);
sqlite3_step(stmt);
result += sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
// again, for peace of mind
return result > 50 ? 50 : result;
}
void Database::addBuddyship(int playerA, int playerB) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
INSERT INTO Buddyships (PlayerAID, PlayerBID)
VALUES (?, ?);
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, playerA);
sqlite3_bind_int(stmt, 2, playerB);
if (sqlite3_step(stmt) != SQLITE_DONE)
std::cout << "[WARN] Database: failed to add buddyship: " << sqlite3_errmsg(db) << 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, NULL);
sqlite3_bind_int(stmt, 1, playerA);
sqlite3_bind_int(stmt, 2, playerB);
sqlite3_bind_int(stmt, 3, playerB);
sqlite3_bind_int(stmt, 4, playerA);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
}
// blocking
void Database::addBlock(int playerId, int blockedPlayerId) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
INSERT INTO Blocks (PlayerID, BlockedPlayerID)
VALUES (?, ?);
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, playerId);
sqlite3_bind_int(stmt, 2, blockedPlayerId);
if (sqlite3_step(stmt) != SQLITE_DONE)
std::cout << "[WARN] Database: failed to block player: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt);
}
void Database::removeBlock(int playerId, int blockedPlayerId) {
const char* sql = R"(
DELETE FROM Blocks
WHERE PlayerID = ? AND BlockedPlayerID = ?;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, playerId);
sqlite3_bind_int(stmt, 2, blockedPlayerId);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
}
RaceRanking Database::getTopRaceRanking(int epID, int playerID) {
std::lock_guard<std::mutex> lock(dbCrit);
std::string sql(R"(
SELECT
EPID, PlayerID, Score, RingCount, Time, Timestamp
FROM RaceResults
WHERE EPID = ?
)");
if (playerID > -1)
sql += " AND PlayerID = ? ";
sql += R"(
ORDER BY Score DESC
LIMIT 1;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, epID);
if(playerID > -1)
sqlite3_bind_int(stmt, 2, playerID);
RaceRanking ranking = {};
if (sqlite3_step(stmt) != SQLITE_ROW) {
// this race hasn't been run before, so return a blank ranking
sqlite3_finalize(stmt);
return ranking;
}
assert(epID == sqlite3_column_int(stmt, 0)); // EPIDs should always match
ranking.EPID = epID;
ranking.PlayerID = sqlite3_column_int(stmt, 1);
ranking.Score = sqlite3_column_int(stmt, 2);
ranking.RingCount = sqlite3_column_int(stmt, 3);
ranking.Time = sqlite3_column_int64(stmt, 4);
ranking.Timestamp = sqlite3_column_int64(stmt, 5);
sqlite3_finalize(stmt);
return ranking;
}
void Database::postRaceRanking(RaceRanking ranking) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
INSERT INTO RaceResults
(EPID, PlayerID, Score, RingCount, Time, Timestamp)
VALUES(?, ?, ?, ?, ?, ?);
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, ranking.EPID);
sqlite3_bind_int(stmt, 2, ranking.PlayerID);
sqlite3_bind_int(stmt, 3, ranking.Score);
sqlite3_bind_int(stmt, 4, ranking.RingCount);
sqlite3_bind_int64(stmt, 5, ranking.Time);
sqlite3_bind_int64(stmt, 6, ranking.Timestamp);
if (sqlite3_step(stmt) != SQLITE_DONE) {
std::cout << "[WARN] Database: Failed to post race result" << std::endl;
}
sqlite3_finalize(stmt);
}
bool Database::isCodeRedeemed(int playerId, std::string code) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
SELECT COUNT(*)
FROM RedeemedCodes
WHERE PlayerID = ? AND Code = ?
LIMIT 1;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, playerId);
sqlite3_bind_text(stmt, 2, code.c_str(), -1, NULL);
sqlite3_step(stmt);
int result = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
return result;
}
void Database::recordCodeRedemption(int playerId, std::string code) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
INSERT INTO RedeemedCodes (PlayerID, Code)
VALUES (?, ?);
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, playerId);
sqlite3_bind_text(stmt, 2, code.c_str(), -1, NULL);
if (sqlite3_step(stmt) != SQLITE_DONE)
std::cout << "[WARN] Database: recording of code redemption failed: " << sqlite3_errmsg(db) << std::endl;
sqlite3_finalize(stmt);
}

View File

@ -13,7 +13,7 @@
#include "NPCManager.hpp" #include "NPCManager.hpp"
#include "TransportManager.hpp" #include "TransportManager.hpp"
#include "BuddyManager.hpp" #include "BuddyManager.hpp"
#include "Database.hpp" #include "db/Database.hpp"
#include "TableData.hpp" #include "TableData.hpp"
#include "ChunkManager.hpp" #include "ChunkManager.hpp"
#include "GroupManager.hpp" #include "GroupManager.hpp"