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))
return loginFail(LoginError::LOGIN_ERROR, userLogin, sock);
std::unique_ptr<Database::Account> 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<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));
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);

View File

@ -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<std::mutex> 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<DbPlayer>();
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<std::mutex> 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<std::mutex> lock(dbCrit);
Account acc = db.get<Account>(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::Account> Database::findAccount(std::string login) {
void Database::findAccount(Account* account, std::string login) {
std::lock_guard<std::mutex> lock(dbCrit);
// this is awful, I've tried everything to improve it
auto find = db.get_all<Account>(
where(c(&Account::Login) == login), limit(1));
if (find.empty())
return nullptr;
return
std::unique_ptr<Account>(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<const char*>(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<std::mutex> lock(dbCrit);
// query whatever
const char* sql = R"(
SELECT "PlayerID"
FROM "Players"
WHERE "PlayerID" = ? AND "AccountID" = ?
LIMIT 1;
)";
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
sqlite3_bind_int(stmt, 1, characterID);
sqlite3_bind_int(stmt, 2, userID);
int rc = sqlite3_step(stmt);
// if we got a row back, the character is valid
bool result = (rc == SQLITE_ROW);
sqlite3_finalize(stmt);
return result;
}
bool Database::isNameFree(std::string firstName, std::string lastName) {
std::lock_guard<std::mutex> lock(dbCrit);
return
(db.get_all<DbPlayer>
(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<std::mutex> lock(dbCrit);
return
(db.get_all<DbPlayer>
(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<std::mutex> lock(dbCrit);
sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, NULL);
// fail if the player already has 4 or more characters
if (db.count<DbPlayer>(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<char>(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<std::mutex> 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<std::mutex> 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) {

View File

@ -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<Account> 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 <Player> getCharacters(int userID);