Reimplement DB functions 1/2

implemented addAccount
implemented updateSelected
implemented findAccount validateCharacter isNameFree and isSlotFree
implemented getTableSize function
implemented functions for name save, character creation and finishing tutorial
This commit is contained in:
Kamil 2020-12-02 01:21:45 +01:00 committed by Gent S
parent 34ca36062c
commit 2f5c2a8764
3 changed files with 393 additions and 203 deletions

View File

@ -121,24 +121,27 @@ void CNLoginServer::login(CNSocket* sock, CNPacketData* data) {
if (!CNLoginServer::isLoginDataGood(userLogin, userPassword)) if (!CNLoginServer::isLoginDataGood(userLogin, userPassword))
return loginFail(LoginError::LOGIN_ERROR, userLogin, sock); return loginFail(LoginError::LOGIN_ERROR, userLogin, sock);
std::unique_ptr<Database::Account> findUser = Database::findAccount(userLogin); Database::Account findUser = {};
if (findUser == nullptr) Database::findAccount(&findUser, userLogin);
// if 0 was returned, account was not found
if (findUser.AccountID == 0)
return newAccount(sock, userLogin, userPassword, login->iClientVerC); return newAccount(sock, userLogin, userPassword, login->iClientVerC);
if (!CNLoginServer::isPasswordCorrect(findUser->Password, userPassword)) if (!CNLoginServer::isPasswordCorrect(findUser.Password, userPassword))
return loginFail(LoginError::ID_AND_PASSWORD_DO_NOT_MATCH, userLogin, sock); return loginFail(LoginError::ID_AND_PASSWORD_DO_NOT_MATCH, userLogin, sock);
/* /*
* calling this here to timestamp login attempt, * calling this here to timestamp login attempt,
* in order to make duplicate exit sanity check work * in order to make duplicate exit sanity check work
*/ */
Database::updateSelected(findUser->AccountID, findUser->Selected); Database::updateSelected(findUser.AccountID, findUser.Selected);
if (CNLoginServer::isAccountInUse(findUser->AccountID)) if (CNLoginServer::isAccountInUse(findUser.AccountID))
return loginFail(LoginError::ID_ALREADY_IN_USE, userLogin, sock); return loginFail(LoginError::ID_ALREADY_IN_USE, userLogin, sock);
loginSessions[sock] = CNLoginData(); loginSessions[sock] = CNLoginData();
loginSessions[sock].userID = findUser->AccountID; loginSessions[sock].userID = findUser.AccountID;
loginSessions[sock].lastHeartbeat = getTime(); loginSessions[sock].lastHeartbeat = getTime();
std::vector<sP_LS2CL_REP_CHAR_INFO> characters = Database::getCharInfo(loginSessions[sock].userID); std::vector<sP_LS2CL_REP_CHAR_INFO> characters = Database::getCharInfo(loginSessions[sock].userID);
@ -147,7 +150,7 @@ void CNLoginServer::login(CNSocket* sock, CNPacketData* data) {
memcpy(resp.szID, login->szID, sizeof(login->szID)); memcpy(resp.szID, login->szID, sizeof(login->szID));
resp.iCharCount = characters.size(); resp.iCharCount = characters.size();
resp.iSlotNum = findUser->Selected; resp.iSlotNum = findUser.Selected;
resp.iPaymentFlag = 1; resp.iPaymentFlag = 1;
resp.iOpenBetaFlag = 0; resp.iOpenBetaFlag = 0;
resp.uiSvrTime = getTime(); resp.uiSvrTime = getTime();
@ -178,7 +181,7 @@ void CNLoginServer::login(CNSocket* sock, CNPacketData* data) {
void CNLoginServer::newAccount(CNSocket* sock, std::string userLogin, std::string userPassword, int32_t clientVerC) { void CNLoginServer::newAccount(CNSocket* sock, std::string userLogin, std::string userPassword, int32_t clientVerC) {
loginSessions[sock] = CNLoginData(); loginSessions[sock] = CNLoginData();
loginSessions[sock].userID = Database::addAccount(userLogin, userPassword); loginSessions[sock].userID = Database::addAccount(userLogin, userPassword); //TODO: Add logic if query fails
loginSessions[sock].lastHeartbeat = getTime(); loginSessions[sock].lastHeartbeat = getTime();
INITSTRUCT(sP_LS2CL_REP_LOGIN_SUCC, resp); INITSTRUCT(sP_LS2CL_REP_LOGIN_SUCC, resp);

View File

@ -33,8 +33,8 @@ void Database::open() {
createTables(); createTables();
std::cout << "[INFO] Database in operation "; std::cout << "[INFO] Database in operation ";
int accounts = getAccountsCount(); int accounts = getTableSize("Accounts");
int players = getPlayersCount(); int players = getTableSize("Players");
std::string message = ""; std::string message = "";
if (accounts > 0) { if (accounts > 0) {
message += ": Found " + std::to_string(accounts) + " Account"; message += ": Found " + std::to_string(accounts) + " Account";
@ -59,9 +59,9 @@ void Database::createTables() {
"AccountID" INTEGER NOT NULL, "AccountID" INTEGER NOT NULL,
"Login" TEXT NOT NULL UNIQUE, "Login" TEXT NOT NULL UNIQUE,
"Password" TEXT NOT NULL, "Password" TEXT NOT NULL,
"Selected" INTEGER NOT NULL, "Selected" INTEGER DEFAULT 1 NOT NULL,
"Created" INTEGER NOT NULL, "Created" INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
"LastLogin" INTEGER NOT NULL, "LastLogin" INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
PRIMARY KEY("AccountID" AUTOINCREMENT) PRIMARY KEY("AccountID" AUTOINCREMENT)
); );
@ -71,46 +71,46 @@ void Database::createTables() {
"Slot" INTEGER NOT NULL, "Slot" INTEGER NOT NULL,
"Firstname" TEXT NOT NULL COLLATE NOCASE, "Firstname" TEXT NOT NULL COLLATE NOCASE,
"LastName" TEXT NOT NULL COLLATE NOCASE, "LastName" TEXT NOT NULL COLLATE NOCASE,
"Created" INTEGER NOT NULL, "Created" INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
"LastLogin" INTEGER NOT NULL, "LastLogin" INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
"Level" INTEGER NOT NULL, "Level" INTEGER DEFAULT 1 NOT NULL,
"Nano1" INTEGER NOT NULL, "Nano1" INTEGER DEFAULT 0 NOT NULL,
"Nano2" INTEGER NOT NULL, "Nano2" INTEGER DEFAULT 0 NOT NULL,
"Nano3" INTEGER NOT NULL, "Nano3" INTEGER DEFAULT 0 NOT NULL,
"AppearanceFlag" INTEGER NOT NULL, "AppearanceFlag" INTEGER DEFAULT 0 NOT NULL,
"TutorialFlag" INTEGER NOT NULL, "TutorialFlag" INTEGER DEFAULT 0 NOT NULL,
"PayZoneFlag" INTEGER NOT NULL, "PayZoneFlag" INTEGER DEFAULT 0 NOT NULL,
"XCoordinates" INTEGER NOT NULL, "XCoordinates" INTEGER NOT NULL,
"YCoordinates" INTEGER NOT NULL, "YCoordinates" INTEGER NOT NULL,
"ZCoordinates" INTEGER NOT NULL, "ZCoordinates" INTEGER NOT NULL,
"Angle" INTEGER NOT NULL,
"HP" INTEGER NOT NULL, "HP" INTEGER NOT NULL,
"NameCheck" INTEGER NOT NULL, "NameCheck" INTEGER NOT NULL,
"AccountLevel" INTEGER NOT NULL, "AccountLevel" INTEGER NOT NULL,
"FusionMatter" INTEGER NOT NULL, "FusionMatter" INTEGER DEFAULT 0 NOT NULL,
"Taros" INTEGER NOT NULL, "Taros" INTEGER DEFAULT 0 NOT NULL,
"Quests" BLOB NOT NULL, "Quests" BLOB NOT NULL,
"BatteryW" INTEGER NOT NULL, "BatteryW" INTEGER DEFAULT 0 NOT NULL,
"BatteryN" INTEGER NOT NULL, "BatteryN" INTEGER DEFAULT 0 NOT NULL,
"Mentor" INTEGER NOT NULL, "Mentor" INTEGER DEFAULT 5 NOT NULL,
"WarpLocationFlag" INTEGER NOT NULL, "WarpLocationFlag" INTEGER DEFAULT 0 NOT NULL,
"SkywayLocationFlag1" INTEGER NOT NULL, "SkywayLocationFlag1" INTEGER DEFAULT 0 NOT NULL,
"SkywayLocationFlag2" INTEGER NOT NULL, "SkywayLocationFlag2" INTEGER DEFAULT 0 NOT NULL,
"CurrentMissionID" INTEGER NOT NULL, "CurrentMissionID" INTEGER DEFAULT 0 NOT NULL,
PRIMARY KEY("PlayerID" AUTOINCREMENT), PRIMARY KEY("PlayerID" AUTOINCREMENT),
FOREIGN KEY("AccountID") REFERENCES "Accounts"("AccountID") ON DELETE CASCADE FOREIGN KEY("AccountID") REFERENCES "Accounts"("AccountID") ON DELETE CASCADE
); );
CREATE TABLE IF NOT EXISTS "Appearances" ( CREATE TABLE IF NOT EXISTS "Appearances" (
"PlayerID" INTEGER NOT NULL, "PlayerID" INTEGER NOT NULL,
"Angle" INTEGER NOT NULL, "Body" INTEGER DEFAULT 0 NOT NULL,
"Body" INTEGER NOT NULL, "EyeColor" INTEGER DEFAULT 1 NOT NULL,
"EyeColor" INTEGER NOT NULL, "FaceStyle" INTEGER DEFAULT 1 NOT NULL,
"FaceStyle" INTEGER NOT NULL, "Gender" INTEGER DEFAULT 1 NOT NULL,
"Gender" INTEGER NOT NULL, "HairColor" INTEGER DEFAULT 1 NOT NULL,
"HairColor" INTEGER NOT NULL, "HairStyle" INTEGER DEFAULT 1 NOT NULL,
"HairStyle" INTEGER NOT NULL, "Height" INTEGER DEFAULT 0 NOT NULL,
"Height" INTEGER NOT NULL, "SkinColor" INTEGER DEFAULT 1 NOT NULL,
"SkinColor" INTEGER NOT NULL,
FOREIGN KEY("PlayerID") REFERENCES "Players"("PlayerID") ON DELETE CASCADE FOREIGN KEY("PlayerID") REFERENCES "Players"("PlayerID") ON DELETE CASCADE
); );
@ -120,7 +120,7 @@ void Database::createTables() {
"Id" INTEGER NOT NULL, "Id" INTEGER NOT NULL,
"Type" INTEGER NOT NULL, "Type" INTEGER NOT NULL,
"Opt" INTEGER NOT NULL, "Opt" INTEGER NOT NULL,
"TimeLimit" INTEGER NOT NULL, "TimeLimit" INTEGER NOT DEFALUT 0 NULL,
FOREIGN KEY("PlayerID") REFERENCES "Players"("PlayerID") ON DELETE CASCADE FOREIGN KEY("PlayerID") REFERENCES "Players"("PlayerID") ON DELETE CASCADE
); );
@ -128,7 +128,7 @@ void Database::createTables() {
"PlayerId" INTEGER NOT NULL, "PlayerId" INTEGER NOT NULL,
"Id" INTEGER NOT NULL, "Id" INTEGER NOT NULL,
"Skill" INTEGER NOT NULL, "Skill" INTEGER NOT NULL,
"Stamina" INTEGER NOT NULL, "Stamina" INTEGER DEFAULT = 150 NOT NULL,
FOREIGN KEY("PlayerID") REFERENCES "Players"("PlayerID") ON DELETE CASCADE FOREIGN KEY("PlayerID") REFERENCES "Players"("PlayerID") ON DELETE CASCADE
); );
@ -191,206 +191,397 @@ void Database::createTables() {
} }
} }
int Database::getTableSize(std::string tableName) {
std::lock_guard<std::mutex> lock(dbCrit);
std::string query = "SELECT COUNT(*) FROM " + tableName + ";";
int Database::getAccountsCount() { const char* sql = query.c_str();
int result; sqlite3_stmt* stmt;
char* sql = "SELECT COUNT(*) FROM Accounts"; sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
rc = sqlite3_exec(db, sql, CBAccountsCount, result, &err_msg); sqlite3_step(stmt);
} int result = sqlite3_column_int(stmt, 0);
sqlite3_finalize(stmt);
static int Database::CBAccountsCount(void* AccountsCount, int count, char** data, char** columns) { return result;
*AccountsCount = (int)data[0];
}
int Database::getPlayersCount() {
return db.count<DbPlayer>();
} }
int Database::addAccount(std::string login, std::string password) { int Database::addAccount(std::string login, std::string password) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
password = BCrypt::generateHash(password); const char* sql = R"(
Account account = {}; INSERT INTO "Accounts"
account.Login = login; ("Login", "Password")
account.Password = password; VALUES (?, ?);
account.Selected = 1; )";
account.Created = getTimestamp(); sqlite3_stmt* stmt;
account.LastLogin = account.Created;
return db.insert(account); int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_text(stmt, 1, login.c_str(), -1, 0);
sqlite3_bind_text(stmt, 2, BCrypt::generateHash(password).c_str(), -1, 0);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
return sqlite3_last_insert_rowid(db);
} }
void Database::updateSelected(int accountId, int slot) { void Database::updateSelected(int accountId, int slot) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
Account acc = db.get<Account>(accountId); if (slot < 0 || slot > 4) {
acc.Selected = slot; std::cout << "[WARN] Invalid slot number passed to updateSelected()! " << std::endl;
// timestamp return;
acc.LastLogin = getTimestamp(); }
db.update(acc);
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);
} }
std::unique_ptr<Database::Account> Database::findAccount(std::string login) { void Database::findAccount(Account* account, std::string login) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
// this is awful, I've tried everything to improve it const char* sql = R"(
auto find = db.get_all<Account>( SELECT "AccountID", "Password", "Selected"
where(c(&Account::Login) == login), limit(1)); FROM "Accounts"
if (find.empty()) WHERE "Login" = ?
return nullptr; LIMIT 1;
return )";
std::unique_ptr<Account>(new Account(find.front())); 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);
} }
bool Database::validateCharacter(int characterID, int userID) { bool Database::validateCharacter(int characterID, int userID) {
return db.select(&DbPlayer::PlayerID, std::lock_guard<std::mutex> lock(dbCrit);
where((c(&DbPlayer::PlayerID) == characterID) && (c(&DbPlayer::AccountID) == userID)))
.size() > 0; // 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) { bool Database::isNameFree(std::string firstName, std::string lastName) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
return const char* sql = R"(
(db.get_all<DbPlayer> SELECT "PlayerID"
(where((c(&DbPlayer::FirstName) == firstName) FROM "Players"
and (c(&DbPlayer::LastName) == lastName))) WHERE "Firstname" = ? AND "LastName" = ?
.empty()); 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) { bool Database::isSlotFree(int accountId, int slotNum) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
return if (slotNum < 0 || slotNum > 4) {
(db.get_all<DbPlayer> std::cout << "[WARN] Invalid slot number passed to isSlotFree()! " << std::endl;
(where((c(&DbPlayer::AccountID) == accountId) return false;
and (c(&DbPlayer::slot) == slotNum))) }
.empty());
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) { int Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
// fail if the player already has 4 or more characters sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, NULL);
if (db.count<DbPlayer>(where(c(&DbPlayer::AccountID) == AccountID)) >= 4)
return -1;
DbPlayer create = {}; 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);
// set timestamp sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
create.Created = getTimestamp(); sqlite3_bind_int(stmt, 1, AccountID);
// save packet data sqlite3_bind_int(stmt, 2, save->iSlotNum);
create.FirstName = U16toU8(save->szFirstName); sqlite3_bind_text(stmt, 3, firstName.c_str(), -1, 0);
create.LastName = U16toU8(save->szLastName); sqlite3_bind_text(stmt, 4, lastName.c_str(), -1, 0);
create.slot = save->iSlotNum; sqlite3_bind_int(stmt, 5, settings::SPAWN_X);
create.AccountID = AccountID; 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));
// set flags // if FNCode isn't 0, it's a wheel name
create.AppearanceFlag = 0; int nameCheck = (settings::APPROVEALLNAMES || save->iFNCode) ? 1 : 0;
create.TutorialFlag = 0; sqlite3_bind_int(stmt, 10, nameCheck);
create.PayZoneFlag = 0; sqlite3_bind_int(stmt, 11, settings::ACCLEVEL);
// set namecheck based on setting // 128 byte blob for completed quests
if (settings::APPROVEALLNAMES || save->iFNCode) unsigned char blobBuffer[128];
create.NameCheck = 1; sqlite3_bind_blob(stmt, 12, blobBuffer, sizeof(blobBuffer), 0);
else
create.NameCheck = 0;
// create default body character sqlite3_step(stmt);
create.Body = 0;
create.Class = 0;
create.EyeColor = 1;
create.FaceStyle = 1;
create.Gender = 1;
create.Level = 1;
create.HP = PC_MAXHEALTH(create.Level);
create.HairColor = 1;
create.HairStyle = 1;
create.Height = 0;
create.SkinColor = 1;
create.AccountLevel = settings::ACCLEVEL;
create.x_coordinates = settings::SPAWN_X;
create.y_coordinates = settings::SPAWN_Y;
create.z_coordinates = settings::SPAWN_Z;
create.angle = settings::SPAWN_ANGLE;
// set mentor to computress
create.Mentor = 5;
// initialize the quest blob to 128 0-bytes int playerId = sqlite3_last_insert_rowid(db);
create.QuestFlag = std::vector<char>(128, 0); sqlite3_finalize(stmt);
return db.insert(create); // if something failed
if (playerId == 0)
return 0;
sql = R"(
INSERT INTO "Appearances"
("PlayerID")
VALUES (?);
)";
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, playerId);
if (sqlite3_step(stmt) != SQLITE_DONE) {
sqlite3_finalize(stmt);
return 0;
}
sqlite3_finalize(stmt);
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, NULL);
return playerId;
} }
void Database::finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character) { bool Database::finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
DbPlayer finish = getDbPlayerById(character->PCStyle.iPC_UID); const char* sql = R"(
finish.AppearanceFlag = 1; SELECT "PlayerID"
finish.Body = character->PCStyle.iBody; FROM "Players"
finish.Class = character->PCStyle.iClass; WHERE "PlayerID" = ? AND "AppearanceFlag" = 0
finish.EyeColor = character->PCStyle.iEyeColor; LIMIT 1;
finish.FaceStyle = character->PCStyle.iFaceStyle; )";
finish.Gender = character->PCStyle.iGender; sqlite3_stmt* stmt;
finish.HairColor = character->PCStyle.iHairColor; sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
finish.HairStyle = character->PCStyle.iHairStyle; sqlite3_bind_int(stmt, 1, character->PCStyle.iPC_UID);
finish.Height = character->PCStyle.iHeight;
finish.Level = 1; if (sqlite3_step(stmt) != SQLITE_ROW)
finish.SkinColor = character->PCStyle.iSkinColor; {
db.update(finish); std::cout << "[WARN] Player tried Character Creation on already existing character?!" << std::endl;
// clothes sqlite3_finalize(stmt);
Inventory Foot, LB, UB; return false;
Foot.playerId = character->PCStyle.iPC_UID; }
Foot.id = character->sOn_Item.iEquipFootID;
Foot.Type = 3; sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, NULL);
Foot.slot = 3;
Foot.Opt = 1; sql = R"(
Foot.TimeLimit = 0; UPDATE "Players"
db.insert(Foot); SET "AppearanceFlag" = 1
LB.playerId = character->PCStyle.iPC_UID; WHERE "PlayerID" = ?;
LB.id = character->sOn_Item.iEquipLBID; )";
LB.Type = 2;
LB.slot = 2; sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
LB.Opt = 1; sqlite3_bind_int(stmt, 1, character->PCStyle.iPC_UID);
LB.TimeLimit = 0; if (sqlite3_step(stmt) != SQLITE_DONE)
db.insert(LB); {
UB.playerId = character->PCStyle.iPC_UID; sqlite3_finalize(stmt);
UB.id = character->sOn_Item.iEquipUBID; return false;
UB.Type = 1; }
UB.slot = 1;
UB.Opt = 1; sql = R"(
UB.TimeLimit = 0; UPDATE "Appearances"
db.insert(UB); 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);
if (sqlite3_step(stmt) != SQLITE_DONE)
{
sqlite3_finalize(stmt);
return false;
}
sqlite3_finalize(stmt);
sql = R"(
INSERT INTO "Inventory"
("PlayerID", "Slot", "Id", "Type", "Opt")
VALUES (?, ?, ?, ?, 1);
)";
int rc = 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++) {
rc = sqlite3_bind_int(stmt, 1, character->PCStyle.iPC_UID);
rc = sqlite3_bind_int(stmt, 2, i+1);
rc = sqlite3_bind_int(stmt, 3, items[i]);
rc = sqlite3_bind_int(stmt, 4, i+1);
if (sqlite3_step(stmt) != SQLITE_DONE)
{
sqlite3_finalize(stmt);
return false;
}
sqlite3_reset(stmt);
}
sqlite3_finalize(stmt);
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, NULL);
return true;
} }
void Database::finishTutorial(int PlayerID) { bool Database::finishTutorial(int playerID) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
Player finish = getPlayer(PlayerID); const char* sql = R"(
// set flag SELECT "PlayerID"
finish.PCStyle2.iTutorialFlag= 1; FROM "Players"
// add Gun WHERE "PlayerID" = ? AND "TutorialFlag" = 0
Inventory LightningGun = {}; LIMIT 1;
LightningGun.playerId = PlayerID; )";
LightningGun.id = 328; sqlite3_stmt* stmt;
LightningGun.slot = 0; sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
LightningGun.Type = 0; sqlite3_bind_int(stmt, 1, playerID);
LightningGun.Opt = 1;
db.insert(LightningGun);
// add Nano
Nano Buttercup = {};
Buttercup.playerId = PlayerID;
Buttercup.iID = 1;
Buttercup.iSkillID = 1;
Buttercup.iStamina = 150;
finish.equippedNanos[0] = 1;
db.insert(Buttercup);
// save missions
MissionManager::saveMission(&finish, 0);
MissionManager::saveMission(&finish, 1);
db.update(playerToDb(&finish)); if (sqlite3_step(stmt) != SQLITE_ROW)
{
std::cout << "[WARN] Player tried to finish tutorial on a character that already did?!" << 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);
if (sqlite3_step(stmt) != SQLITE_DONE)
{
sqlite3_finalize(stmt);
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);
if (sqlite3_step(stmt) != SQLITE_DONE)
{
sqlite3_finalize(stmt);
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);
if (sqlite3_step(stmt) != SQLITE_DONE)
{
sqlite3_finalize(stmt);
return false;
}
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, NULL);
sqlite3_finalize(stmt);
return true;
} }
int Database::deleteCharacter(int characterID, int userID) { int Database::deleteCharacter(int characterID, int userID) {

View File

@ -9,11 +9,8 @@ namespace Database {
struct Account { struct Account {
int AccountID; int AccountID;
std::string Login;
std::string Password; std::string Password;
int Selected; int Selected;
uint64_t Created;
uint64_t LastLogin;
}; };
struct Inventory { struct Inventory {
int playerId; int playerId;
@ -72,21 +69,20 @@ namespace Database {
void open(); void open();
void createTables(); void createTables();
int getAccountsCount(); int getTableSize(std::string tableName);
int getPlayersCount(); // returns ID, 0 if something failed
// returns ID
int addAccount(std::string login, std::string password); int addAccount(std::string login, std::string password);
void updateSelected(int accountId, int playerId); void updateSelected(int accountId, int playerId);
std::unique_ptr<Account> findAccount(std::string login); void findAccount(Account* account, std::string login);
bool validateCharacter(int characterID, int userID); bool validateCharacter(int characterID, int userID);
bool isNameFree(std::string firstName, std::string lastName); bool isNameFree(std::string firstName, std::string lastName);
bool isSlotFree(int accountId, int slotNum); bool isSlotFree(int accountId, int slotNum);
// called after chosing name, returns ID // called after chosing name, returns ID
int createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID); int createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID);
// called after finishing creation // called after finishing creation
void finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character); bool finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character);
// called after tutorial // called after tutorial
void finishTutorial(int PlayerID); bool finishTutorial(int playerID);
// returns slot number // returns slot number
int deleteCharacter(int characterID, int userID); int deleteCharacter(int characterID, int userID);
std::vector <Player> getCharacters(int userID); std::vector <Player> getCharacters(int userID);