diff --git a/src/CNLoginServer.cpp b/src/CNLoginServer.cpp index 448f754..533dcd0 100644 --- a/src/CNLoginServer.cpp +++ b/src/CNLoginServer.cpp @@ -121,24 +121,27 @@ void CNLoginServer::login(CNSocket* sock, CNPacketData* data) { if (!CNLoginServer::isLoginDataGood(userLogin, userPassword)) return loginFail(LoginError::LOGIN_ERROR, userLogin, sock); - std::unique_ptr findUser = Database::findAccount(userLogin); - if (findUser == nullptr) + Database::Account findUser = {}; + Database::findAccount(&findUser, userLogin); + + // if 0 was returned, account was not found + if (findUser.AccountID == 0) 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); /* * calling this here to timestamp login attempt, * 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); loginSessions[sock] = CNLoginData(); - loginSessions[sock].userID = findUser->AccountID; + loginSessions[sock].userID = findUser.AccountID; loginSessions[sock].lastHeartbeat = getTime(); std::vector 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)); resp.iCharCount = characters.size(); - resp.iSlotNum = findUser->Selected; + resp.iSlotNum = findUser.Selected; resp.iPaymentFlag = 1; resp.iOpenBetaFlag = 0; 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) { 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(); INITSTRUCT(sP_LS2CL_REP_LOGIN_SUCC, resp); diff --git a/src/Database.cpp b/src/Database.cpp index 618513c..73caad8 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -33,8 +33,8 @@ void Database::open() { createTables(); std::cout << "[INFO] Database in operation "; - int accounts = getAccountsCount(); - int players = getPlayersCount(); + int accounts = getTableSize("Accounts"); + int players = getTableSize("Players"); std::string message = ""; if (accounts > 0) { message += ": Found " + std::to_string(accounts) + " Account"; @@ -59,58 +59,58 @@ void Database::createTables() { "AccountID" INTEGER NOT NULL, "Login" TEXT NOT NULL UNIQUE, "Password" TEXT NOT NULL, - "Selected" INTEGER NOT NULL, - "Created" INTEGER NOT NULL, - "LastLogin" INTEGER NOT NULL, + "Selected" INTEGER DEFAULT 1 NOT NULL, + "Created" INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL, + "LastLogin" INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL, PRIMARY KEY("AccountID" AUTOINCREMENT) ); - CREATE TABLE IF NOT EXISTS "Players" ( + CREATE TABLE IF NOT EXISTS "Players" ( "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 NOT NULL, - "LastLogin" INTEGER NOT NULL, - "Level" INTEGER NOT NULL, - "Nano1" INTEGER NOT NULL, - "Nano2" INTEGER NOT NULL, - "Nano3" INTEGER NOT NULL, - "AppearanceFlag" INTEGER NOT NULL, - "TutorialFlag" INTEGER NOT NULL, - "PayZoneFlag" INTEGER NOT NULL, + "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, "XCoordinates" INTEGER NOT NULL, "YCoordinates" INTEGER NOT NULL, "ZCoordinates" INTEGER NOT NULL, + "Angle" INTEGER NOT NULL, "HP" INTEGER NOT NULL, "NameCheck" INTEGER NOT NULL, "AccountLevel" INTEGER NOT NULL, - "FusionMatter" INTEGER NOT NULL, - "Taros" INTEGER NOT NULL, + "FusionMatter" INTEGER DEFAULT 0 NOT NULL, + "Taros" INTEGER DEFAULT 0 NOT NULL, "Quests" BLOB NOT NULL, - "BatteryW" INTEGER NOT NULL, - "BatteryN" INTEGER NOT NULL, - "Mentor" INTEGER NOT NULL, - "WarpLocationFlag" INTEGER NOT NULL, - "SkywayLocationFlag1" INTEGER NOT NULL, - "SkywayLocationFlag2" INTEGER NOT NULL, - "CurrentMissionID" INTEGER 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, PRIMARY KEY("PlayerID" AUTOINCREMENT), FOREIGN KEY("AccountID") REFERENCES "Accounts"("AccountID") ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS "Appearances" ( "PlayerID" INTEGER NOT NULL, - "Angle" INTEGER NOT NULL, - "Body" INTEGER NOT NULL, - "EyeColor" INTEGER NOT NULL, - "FaceStyle" INTEGER NOT NULL, - "Gender" INTEGER NOT NULL, - "HairColor" INTEGER NOT NULL, - "HairStyle" INTEGER NOT NULL, - "Height" INTEGER NOT NULL, - "SkinColor" INTEGER 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, FOREIGN KEY("PlayerID") REFERENCES "Players"("PlayerID") ON DELETE CASCADE ); @@ -120,7 +120,7 @@ void Database::createTables() { "Id" INTEGER NOT NULL, "Type" 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 ); @@ -128,7 +128,7 @@ void Database::createTables() { "PlayerId" INTEGER NOT NULL, "Id" 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 ); @@ -191,206 +191,397 @@ void Database::createTables() { } } +int Database::getTableSize(std::string tableName) { + std::lock_guard lock(dbCrit); - -int Database::getAccountsCount() { - int result; - char* sql = "SELECT COUNT(*) FROM Accounts"; - rc = sqlite3_exec(db, sql, CBAccountsCount, result, &err_msg); -} - -static int Database::CBAccountsCount(void* AccountsCount, int count, char** data, char** columns) { - *AccountsCount = (int)data[0]; -} - - -int Database::getPlayersCount() { - return db.count(); + std::string query = "SELECT COUNT(*) FROM " + tableName + ";"; + const char* sql = query.c_str(); + sqlite3_stmt* stmt; + sqlite3_prepare_v2(db, sql, -1, &stmt, 0); + sqlite3_step(stmt); + int result = sqlite3_column_int(stmt, 0); + sqlite3_finalize(stmt); + return result; } int Database::addAccount(std::string login, std::string password) { std::lock_guard lock(dbCrit); - password = BCrypt::generateHash(password); - Account account = {}; - account.Login = login; - account.Password = password; - account.Selected = 1; - account.Created = getTimestamp(); - account.LastLogin = account.Created; - return db.insert(account); + const char* sql = R"( + INSERT INTO "Accounts" + ("Login", "Password") + VALUES (?, ?); + )"; + sqlite3_stmt* stmt; + + 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) { std::lock_guard lock(dbCrit); - Account acc = db.get(accountId); - acc.Selected = slot; - // timestamp - acc.LastLogin = getTimestamp(); - db.update(acc); + if (slot < 0 || 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); } -std::unique_ptr Database::findAccount(std::string login) { +void Database::findAccount(Account* account, std::string login) { std::lock_guard lock(dbCrit); - // this is awful, I've tried everything to improve it - auto find = db.get_all( - where(c(&Account::Login) == login), limit(1)); - if (find.empty()) - return nullptr; - return - std::unique_ptr(new Account(find.front())); + 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(sqlite3_column_text(stmt, 1))); + account->Selected = sqlite3_column_int(stmt, 2); + } + sqlite3_finalize(stmt); } bool Database::validateCharacter(int characterID, int userID) { - return db.select(&DbPlayer::PlayerID, - where((c(&DbPlayer::PlayerID) == characterID) && (c(&DbPlayer::AccountID) == userID))) - .size() > 0; + std::lock_guard 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 lock(dbCrit); - return - (db.get_all - (where((c(&DbPlayer::FirstName) == firstName) - and (c(&DbPlayer::LastName) == lastName))) - .empty()); + 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 lock(dbCrit); - return - (db.get_all - (where((c(&DbPlayer::AccountID) == accountId) - and (c(&DbPlayer::slot) == slotNum))) - .empty()); + if (slotNum < 0 || slotNum > 4) { + std::cout << "[WARN] Invalid slot number passed to isSlotFree()! " << 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 lock(dbCrit); + + sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, NULL); - // fail if the player already has 4 or more characters - if (db.count(where(c(&DbPlayer::AccountID) == AccountID)) >= 4) - return -1; + 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)); - DbPlayer create = {}; + // 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); - // set timestamp - create.Created = getTimestamp(); - // save packet data - create.FirstName = U16toU8(save->szFirstName); - create.LastName = U16toU8(save->szLastName); - create.slot = save->iSlotNum; - create.AccountID = AccountID; + // 128 byte blob for completed quests + unsigned char blobBuffer[128]; + sqlite3_bind_blob(stmt, 12, blobBuffer, sizeof(blobBuffer), 0); - // set flags - create.AppearanceFlag = 0; - create.TutorialFlag = 0; - create.PayZoneFlag = 0; + sqlite3_step(stmt); - // set namecheck based on setting - if (settings::APPROVEALLNAMES || save->iFNCode) - create.NameCheck = 1; - else - create.NameCheck = 0; + int playerId = sqlite3_last_insert_rowid(db); + sqlite3_finalize(stmt); - // create default body character - 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; + // if something failed + if (playerId == 0) + return 0; - // initialize the quest blob to 128 0-bytes - create.QuestFlag = std::vector(128, 0); + sql = R"( + INSERT INTO "Appearances" + ("PlayerID") + VALUES (?); + )"; + sqlite3_prepare_v2(db, sql, -1, &stmt, 0); + sqlite3_bind_int(stmt, 1, playerId); - return db.insert(create); + 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 lock(dbCrit); + + const char* sql = R"( + SELECT "PlayerID" + FROM "Players" + WHERE "PlayerID" = ? 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); - DbPlayer finish = getDbPlayerById(character->PCStyle.iPC_UID); - finish.AppearanceFlag = 1; - finish.Body = character->PCStyle.iBody; - finish.Class = character->PCStyle.iClass; - finish.EyeColor = character->PCStyle.iEyeColor; - finish.FaceStyle = character->PCStyle.iFaceStyle; - finish.Gender = character->PCStyle.iGender; - finish.HairColor = character->PCStyle.iHairColor; - finish.HairStyle = character->PCStyle.iHairStyle; - finish.Height = character->PCStyle.iHeight; - finish.Level = 1; - finish.SkinColor = character->PCStyle.iSkinColor; - db.update(finish); - // clothes - Inventory Foot, LB, UB; - Foot.playerId = character->PCStyle.iPC_UID; - Foot.id = character->sOn_Item.iEquipFootID; - Foot.Type = 3; - Foot.slot = 3; - Foot.Opt = 1; - Foot.TimeLimit = 0; - db.insert(Foot); - LB.playerId = character->PCStyle.iPC_UID; - LB.id = character->sOn_Item.iEquipLBID; - LB.Type = 2; - LB.slot = 2; - LB.Opt = 1; - LB.TimeLimit = 0; - db.insert(LB); - UB.playerId = character->PCStyle.iPC_UID; - UB.id = character->sOn_Item.iEquipUBID; - UB.Type = 1; - UB.slot = 1; - UB.Opt = 1; - UB.TimeLimit = 0; - db.insert(UB); + if (sqlite3_step(stmt) != SQLITE_ROW) + { + std::cout << "[WARN] Player tried Character Creation on already existing character?!" << std::endl; + sqlite3_finalize(stmt); + 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); + if (sqlite3_step(stmt) != SQLITE_DONE) + { + sqlite3_finalize(stmt); + 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); + + 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 lock(dbCrit); - Player finish = getPlayer(PlayerID); - // set flag - finish.PCStyle2.iTutorialFlag= 1; - // add Gun - Inventory LightningGun = {}; - LightningGun.playerId = PlayerID; - LightningGun.id = 328; - LightningGun.slot = 0; - LightningGun.Type = 0; - 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); + const char* sql = R"( + SELECT "PlayerID" + FROM "Players" + WHERE "PlayerID" = ? AND "TutorialFlag" = 0 + LIMIT 1; + )"; + sqlite3_stmt* stmt; + sqlite3_prepare_v2(db, sql, -1, &stmt, 0); + sqlite3_bind_int(stmt, 1, playerID); - 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) { diff --git a/src/Database.hpp b/src/Database.hpp index fd2418c..11bc202 100644 --- a/src/Database.hpp +++ b/src/Database.hpp @@ -9,11 +9,8 @@ namespace Database { struct Account { int AccountID; - std::string Login; std::string Password; int Selected; - uint64_t Created; - uint64_t LastLogin; }; struct Inventory { int playerId; @@ -72,21 +69,20 @@ namespace Database { void open(); void createTables(); - int getAccountsCount(); - int getPlayersCount(); - // returns ID + int getTableSize(std::string tableName); + // returns ID, 0 if something failed int addAccount(std::string login, std::string password); void updateSelected(int accountId, int playerId); - std::unique_ptr findAccount(std::string login); + void findAccount(Account* account, std::string login); bool validateCharacter(int characterID, int userID); bool isNameFree(std::string firstName, std::string lastName); bool isSlotFree(int accountId, int slotNum); // called after chosing name, returns ID int createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID); // called after finishing creation - void finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character); + bool finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character); // called after tutorial - void finishTutorial(int PlayerID); + bool finishTutorial(int playerID); // returns slot number int deleteCharacter(int characterID, int userID); std::vector getCharacters(int userID);