diff --git a/config.ini b/config.ini index de43a05..4349b2d 100644 --- a/config.ini +++ b/config.ini @@ -14,6 +14,8 @@ acceptallcustomnames=true # how often should everything be flushed to the database? # the default is 4 minutes dbsaveinterval=240 +# use the web API +usewebapi=false # Shard Server configuration [shard] diff --git a/src/CNLoginServer.cpp b/src/CNLoginServer.cpp index add5e84..248115a 100644 --- a/src/CNLoginServer.cpp +++ b/src/CNLoginServer.cpp @@ -53,17 +53,27 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) { else { std::unique_ptr findUser = Database::findAccount(userLogin); - // if account not found, create it + // if account not found if (findUser == nullptr) { - loginSessions[sock] = CNLoginData(); - loginSessions[sock].userID = Database::addAccount(userLogin, userPassword); - loginSessions[sock].slot = 1; - success = true; + //web api takes care of registration + if (settings::USEWEBAPI) + errorCode = (int)LoginError::ID_DOESNT_EXIST; + //without web api, create new account + else + { + loginSessions[sock] = CNLoginData(); + loginSessions[sock].userID = Database::addAccount(userLogin, userPassword); + loginSessions[sock].slot = 1; + success = true; + } } // if user exists, check if password is correct else if (CNLoginServer::isPasswordCorrect(findUser->Password, userPassword)) { + /*calling this here to timestamp login attempt, + * in order to make duplicate exit sanity check work*/ + Database::updateSelected(findUser->AccountID, findUser->Selected); // check if account isn't currently in use if (CNLoginServer::isAccountInUse(findUser->AccountID) || PlayerManager::isAccountInUse(findUser->AccountID)) @@ -100,7 +110,7 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) { resp.iPaymentFlag = 1; resp.iOpenBetaFlag = 0; resp.uiSvrTime = getTime(); - + // send the resp in with original key sock->sendPacket((void*)&resp, P_LS2CL_REP_LOGIN_SUCC, sizeof(sP_LS2CL_REP_LOGIN_SUCC)); @@ -144,10 +154,8 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) { // Failure else { INITSTRUCT(sP_LS2CL_REP_LOGIN_FAIL, resp); - - memcpy(resp.szID, login->szID, sizeof(char16_t) * 33); + U8toU16(userLogin, resp.szID); resp.iErrorCode = errorCode; - sock->sendPacket((void*)&resp, P_LS2CL_REP_LOGIN_FAIL, sizeof(sP_LS2CL_REP_LOGIN_FAIL)); } @@ -348,8 +356,22 @@ void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) { sP_CL2LS_REQ_PC_EXIT_DUPLICATE* exit = (sP_CL2LS_REQ_PC_EXIT_DUPLICATE*)data->buf; auto account = Database::findAccount(U16toU8(exit->szID)); + //sanity check if (account == nullptr) + { + std::cout << "[WARN] P_CL2LS_REQ_PC_EXIT_DUPLICATE submitted unknown username: " << exit->szID << std::endl; break; + } + /* sanity check + * client is supposed to send us user password for verification, + * however it never sends it (>_<) + * therefore, we check if the account made a login attempt within last 30s + */ + if (account->LastLogin + 30000 < getTime()) + { + std::cout << "[WARN] P_CL2LS_REQ_PC_EXIT_DUPLICATE submitted without a login attempt on: " << exit->szID << std::endl; + break; + } int accountId = account->AccountID; if (!exitDuplicate(accountId)) @@ -407,11 +429,19 @@ bool CNLoginServer::exitDuplicate(int accountId) { bool CNLoginServer::isLoginDataGood(std::string login, std::string password) { std::regex loginRegex("[a-zA-Z0-9_-]{4,32}"); + //web api sends password hashed, so we don't check it + if (settings::USEWEBAPI) + return (std::regex_match(login, loginRegex)); + //without web api std::regex passwordRegex("[a-zA-Z0-9!@#$%^&*()_+]{8,32}"); return (std::regex_match(login, loginRegex) && std::regex_match(password, passwordRegex)); } bool CNLoginServer::isPasswordCorrect(std::string actualPassword, std::string tryPassword) { + //web api sends password already hashed + if (settings::USEWEBAPI) + return actualPassword == tryPassword; + //without web api return BCrypt::validatePassword(tryPassword, actualPassword); } diff --git a/src/Database.cpp b/src/Database.cpp index d4213a1..b37be18 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -18,7 +18,9 @@ auto db = make_storage("database.db", make_column("AccountID", &Database::Account::AccountID, autoincrement(), primary_key()), make_column("Login", &Database::Account::Login), make_column("Password", &Database::Account::Password), - make_column("Selected", &Database::Account::Selected) + make_column("Selected", &Database::Account::Selected), + make_column("Created", &Database::Account::Created), + make_column("LastLogin", &Database::Account::LastLogin) ), make_table("Players", make_column("PlayerID", &Database::DbPlayer::PlayerID, autoincrement(), primary_key()), @@ -26,6 +28,8 @@ auto db = make_storage("database.db", make_column("Slot", &Database::DbPlayer::slot), make_column("Firstname", &Database::DbPlayer::FirstName, collate_nocase()), make_column("LastName", &Database::DbPlayer::LastName, collate_nocase()), + make_column("Created", &Database::DbPlayer::Created), + make_column("LastLogin", &Database::DbPlayer::LastLogin), make_column("Level", &Database::DbPlayer::Level), make_column("Nano1", &Database::DbPlayer::Nano1), make_column("Nano2", &Database::DbPlayer::Nano2), @@ -51,7 +55,14 @@ auto db = make_storage("database.db", make_column("isGM", &Database::DbPlayer::isGM), make_column("FusionMatter", &Database::DbPlayer::FusionMatter), make_column("Taros", &Database::DbPlayer::Taros), - make_column("Quests", &Database::DbPlayer::QuestFlag) + make_column("Quests", &Database::DbPlayer::QuestFlag), + make_column("BatteryW", &Database::DbPlayer::BatteryW), + make_column("BatteryN", &Database::DbPlayer::BatteryN), + make_column("Mentor", &Database::DbPlayer::Mentor), + make_column("WarpLocationFlag", &Database::DbPlayer::WarpLocationFlag), + make_column("SkywayLocationFlag1", &Database::DbPlayer::SkywayLocationFlag1), + make_column("SkywayLocationFlag2", &Database::DbPlayer::SkywayLocationFlag2), + make_column("CurrentMissionID", &Database::DbPlayer::CurrentMissionID) ), make_table("Inventory", make_column("PlayerId", &Database::Inventory::playerId), @@ -66,6 +77,13 @@ auto db = make_storage("database.db", make_column("Id", &Database::Nano::iID), make_column("Skill", &Database::Nano::iSkillID), make_column("Stamina", &Database::Nano::iStamina) + ), + make_table("RunningQuests", + make_column("PlayerId", &Database::DbQuest::PlayerId), + make_column("TaskId", &Database::DbQuest::TaskId), + make_column("RemainingNPCCount1", &Database::DbQuest::RemainingNPCCount1), + make_column("RemainingNPCCount2", &Database::DbQuest::RemainingNPCCount2), + make_column("RemainingNPCCount3", &Database::DbQuest::RemainingNPCCount3) ) ); @@ -78,25 +96,48 @@ void Database::open() // this parameter means it will try to preserve data during migration bool preserve = true; db.sync_schema(preserve); - DEBUGLOG( - std::cout << "[DB] Database in operation" << std::endl; - ) + std::cout << "[INFO] Database in operation "; + int accounts = getAccountsCount(); + int players = getPlayersCount(); + 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; +} + +int Database::getAccountsCount() { + return db.count(); +} + +int Database::getPlayersCount() { + return db.count(); } int Database::addAccount(std::string login, std::string password) { password = BCrypt::generateHash(password); - Account x = {}; - x.Login = login; - x.Password = password; - x.Selected = 1; - return db.insert(x); + Account account = {}; + account.Login = login; + account.Password = password; + account.Selected = 1; + account.Created = getTime(); + return db.insert(account); } void Database::updateSelected(int accountId, int slot) { Account acc = db.get(accountId); acc.Selected = slot; + //timestamp + acc.LastLogin = getTime(); db.update(acc); } @@ -129,7 +170,9 @@ int Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID) return -1; DbPlayer create = {}; - + + //set timestamp + create.Created = getTime(); // save packet data create.FirstName = U16toU8(save->szFirstName); create.LastName = U16toU8(save->szLastName); @@ -165,6 +208,8 @@ int Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID) create.z_coordinates = settings::SPAWN_Z; create.angle = settings::SPAWN_ANGLE; create.QuestFlag = std::vector(); + //set mentor to computress + create.Mentor = 5; return db.insert(create); } @@ -285,7 +330,8 @@ void Database::changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save) { Database::DbPlayer Database::playerToDb(Player *player) { - DbPlayer result = {}; // fixes some weird memory errors, this zeros out the members (not the padding inbetween though) + //TODO: move stuff that is never updated to separate table so it doesn't try to update it every time + DbPlayer result = {}; result.PlayerID = player->iID; result.AccountID = player->accountId; @@ -318,22 +364,24 @@ Database::DbPlayer Database::playerToDb(Player *player) result.Nano1 = player->equippedNanos[0]; result.Nano2 = player->equippedNanos[1]; result.Nano3 = player->equippedNanos[2]; + result.BatteryN = player->batteryN; + result.BatteryW = player->batteryW; + result.Mentor = player->mentor; + result.WarpLocationFlag = player->iWarpLocationFlag; + result.SkywayLocationFlag1 = player->aSkywayLocationFlag[0]; + result.SkywayLocationFlag2 = player->aSkywayLocationFlag[1]; + result.CurrentMissionID = player->CurrentMissionID; - // quests + // finished quests: parsing to blob result.QuestFlag = std::vector(); - // parsing long array to char vector for (int i=0; i<16; i++) { - int64_t temp = player->aQuestFlag[i]; - for (int j = 0; j < 8; j++) { - int64_t check2 = (temp >> (8 * (7 - j))); - char toadd = check2; - result.QuestFlag.push_back( - toadd - ); - } + int64_t flag = player->aQuestFlag[i]; + appendBlob(&result.QuestFlag, flag); } - + //timestamp + result.LastLogin = getTime(); + result.Created = getDbPlayerById(player->iID).Created; return result; } @@ -370,28 +418,31 @@ Player Database::DbToPlayer(DbPlayer player) { result.angle = player.angle; result.money = player.Taros; result.fusionmatter = player.FusionMatter; + result.batteryN = player.BatteryN; + result.batteryW = player.BatteryW; + result.mentor = player.Mentor; + result.CurrentMissionID = player.CurrentMissionID; result.equippedNanos[0] = player.Nano1; result.equippedNanos[1] = player.Nano2; result.equippedNanos[2] = player.Nano3; + result.iWarpLocationFlag = player.WarpLocationFlag; + result.aSkywayLocationFlag[0] = player.SkywayLocationFlag1; + result.aSkywayLocationFlag[1] = player.SkywayLocationFlag2; + Database::getInventory(&result); Database::getNanos(&result); + Database::getQuests(&result); std::vector::iterator it = player.QuestFlag.begin(); for (int i = 0; i < 16; i++) { if (it == player.QuestFlag.end()) break; - - int64_t toAdd = 0; - for (int j = 0; j < 8; j++) { - int64_t temp = *it; - int64_t check2 = (temp << (8 * (7 - j))); - toAdd += check2; - it++; - } - result.aQuestFlag[i] = toAdd; + result.aQuestFlag[i] = blobToInt64(it); + //move iterator to the next flag + it += 8; } return result; @@ -417,6 +468,7 @@ void Database::updatePlayer(Player *player) { db.update(toUpdate); updateInventory(player); updateNanos(player); + updateQuests(player); } void Database::updateInventory(Player *player){ @@ -468,8 +520,23 @@ void Database::updateInventory(Player *player){ db.insert(toAdd); } } + // insert quest items + for (int i = 0; i < AQINVEN_COUNT; i++) { + if (player->QInven[i].iID != 0) { + sItemBase* next = &player->QInven[i]; + Inventory toAdd = {}; + toAdd.playerId = player->iID; + toAdd.slot = i + AEQUIP_COUNT + AINVEN_COUNT + ABANK_COUNT; + toAdd.id = next->iID; + toAdd.Opt = next->iOpt; + toAdd.Type = next->iType; + toAdd.TimeLimit = next->iTimeLimit; + db.insert(toAdd); + } + } db.commit(); } + void Database::updateNanos(Player *player) { // start transaction db.begin_transaction(); @@ -492,6 +559,30 @@ void Database::updateNanos(Player *player) { } db.commit(); } + +void Database::updateQuests(Player* player) { + // start transaction + db.begin_transaction(); + // remove all + db.remove_all( + where(c(&DbQuest::PlayerId) == player->iID) + ); + // insert + for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) + { + if (player->tasks[i] == 0) + continue; + DbQuest toAdd = {}; + toAdd.PlayerId = player->iID; + toAdd.TaskId = player->tasks[i]; + toAdd.RemainingNPCCount1 = player->RemainingNPCCount[i][0]; + toAdd.RemainingNPCCount2 = player->RemainingNPCCount[i][1]; + toAdd.RemainingNPCCount3 = player->RemainingNPCCount[i][2]; + db.insert(toAdd); + } + db.commit(); +} + void Database::getInventory(Player* player) { // get items from DB auto items = db.get_all( @@ -505,15 +596,18 @@ void Database::getInventory(Player* player) { toSet.iOpt = current.Opt; toSet.iTimeLimit = current.TimeLimit; // assign to proper arrays - if (current.slot <= AEQUIP_COUNT) + if (current.slot < AEQUIP_COUNT) player->Equip[current.slot] = toSet; - else if (current.slot <= (AEQUIP_COUNT + AINVEN_COUNT)) + else if (current.slot < (AEQUIP_COUNT + AINVEN_COUNT)) player->Inven[current.slot - AEQUIP_COUNT] = toSet; - else + else if (current.slot < (AEQUIP_COUNT + AINVEN_COUNT + ABANK_COUNT)) player->Bank[current.slot - AEQUIP_COUNT - AINVEN_COUNT] = toSet; + else + player->QInven[current.slot - AEQUIP_COUNT - AINVEN_COUNT - ABANK_COUNT] = toSet; } } + void Database::getNanos(Player* player) { // get from DB auto nanos = db.get_all( @@ -527,4 +621,42 @@ void Database::getNanos(Player* player) { toSet->iStamina = current.iStamina; } } + +void Database::getQuests(Player* player) { + // get from DB + auto quests = db.get_all( + where(c(&DbQuest::PlayerId) == player->iID) + ); + // set + int i = 0; + for (const DbQuest& current : quests) { + player->tasks[i] = current.TaskId; + player->RemainingNPCCount[i][0] = current.RemainingNPCCount1; + player->RemainingNPCCount[i][1] = current.RemainingNPCCount2; + player->RemainingNPCCount[i][2] = current.RemainingNPCCount3; + i++; + } +} + #pragma endregion ShardServer + +#pragma region parsingBlobs + +void Database::appendBlob(std::vector *blob, int64_t input) { + for (int i = 0; i < 8; i++) { + char toadd = (input >> (8 * (7 - i))); + blob->push_back(toadd); + } +} + +int64_t Database::blobToInt64(std::vector::iterator it) { + int64_t result = 0; + for (int i = 0; i < 8; i++) { + int64_t toAdd = ((int64_t)*it << (8 * (7 - i))); + result += toAdd; + it++; + } + return result; +} + +#pragma endregion parsingBlobs diff --git a/src/Database.hpp b/src/Database.hpp index e319e36..65d80b1 100644 --- a/src/Database.hpp +++ b/src/Database.hpp @@ -13,6 +13,8 @@ namespace Database { std::string Login; std::string Password; int Selected; + uint64_t Created; + uint64_t LastLogin; }; struct Inventory { @@ -36,6 +38,8 @@ namespace Database { short int slot; std::string FirstName; std::string LastName; + uint64_t Created; + uint64_t LastLogin; short int Level; int Nano1; int Nano2; @@ -62,15 +66,30 @@ namespace Database { int z_coordinates; int angle; short int PCState; + int BatteryW; + int BatteryN; + int16_t Mentor; std::vector QuestFlag; + int32_t CurrentMissionID; + int32_t WarpLocationFlag; + int64_t SkywayLocationFlag1; + int64_t SkywayLocationFlag2; + }; + struct DbQuest { + int PlayerId; + int32_t TaskId; + int RemainingNPCCount1; + int RemainingNPCCount2; + int RemainingNPCCount3; }; - #pragma endregion DatabaseStructs // handles migrations void open(); + int getAccountsCount(); + int getPlayersCount(); // returns ID int addAccount(std::string login, std::string password); void updateSelected(int accountId, int playerId); @@ -104,7 +123,13 @@ namespace Database { void updatePlayer(Player *player); void updateInventory(Player *player); void updateNanos(Player *player); + void updateQuests(Player* player); void getInventory(Player* player); void getNanos(Player* player); + void getQuests(Player* player); + + //parsing blobs + void appendBlob(std::vector*blob, int64_t input); + int64_t blobToInt64(std::vector::iterator it); } diff --git a/src/ItemManager.cpp b/src/ItemManager.cpp index b808e45..c968085 100644 --- a/src/ItemManager.cpp +++ b/src/ItemManager.cpp @@ -69,6 +69,9 @@ void ItemManager::itemMoveHandler(CNSocket* sock, CNPacketData* data) { case SlotType::BANK: fromItem = &plr.plr->Bank[itemmove->iFromSlotNum]; break; + default: + std::cout << "[WARN] MoveItem submitted unknown Item Type?! " << itemmove->eFrom << std::endl; + return; } // get the toItem @@ -83,6 +86,9 @@ void ItemManager::itemMoveHandler(CNSocket* sock, CNPacketData* data) { case SlotType::BANK: toItem = &plr.plr->Bank[itemmove->iToSlotNum]; break; + default: + std::cout << "[WARN] MoveItem submitted unknown Item Type?! " << itemmove->eTo << std::endl; + return; } // save items to response diff --git a/src/MissionManager.cpp b/src/MissionManager.cpp index 2ad8f9f..769283b 100644 --- a/src/MissionManager.cpp +++ b/src/MissionManager.cpp @@ -31,14 +31,19 @@ void MissionManager::taskStart(CNSocket* sock, CNPacketData* data) { sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC)); return; } - + + TaskData& task = *Tasks[missionData->iTaskNum]; int i; for (i = 0; i < ACTIVE_MISSION_COUNT; i++) { if (plr->tasks[i] == 0) { plr->tasks[i] = missionData->iTaskNum; + for (int j = 0; j < 3; j++) { + plr->RemainingNPCCount[i][j] = (int)task["m_iCSUNumToKill"][j]; + } break; } } + if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != missionData->iTaskNum) { std::cout << "[WARN] Player has more than 6 active missions!?" << std::endl; } @@ -80,6 +85,7 @@ void MissionManager::taskEnd(CNSocket* sock, CNPacketData* data) { * iSUInstancename is the number of items to give. It is usually negative at the end of * a mission, to clean up its quest items. */ + for (int i = 0; i < 3; i++) if (task["m_iSUItem"][i] != 0) dropQuestItem(sock, missionData->iTaskNum, task["m_iSUInstancename"][i], task["m_iSUItem"][i], 0); @@ -94,7 +100,12 @@ void MissionManager::taskEnd(CNSocket* sock, CNPacketData* data) { int i; for (i = 0; i < ACTIVE_MISSION_COUNT; i++) { if (plr->tasks[i] == missionData->iTaskNum) + { plr->tasks[i] = 0; + for (int j = 0; j < 3; j++) { + plr->RemainingNPCCount[i][j] = 0; + } + } } if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != 0) { std::cout << "[WARN] Player completed non-active mission!?" << std::endl; @@ -105,6 +116,8 @@ void MissionManager::taskEnd(CNSocket* sock, CNPacketData* data) { { // save completed mission on player saveMission(plr, (int)(task["m_iHMissionID"])-1); + // remove current mission + plr->CurrentMissionID = 0; } sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_END_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_END_SUCC)); @@ -116,9 +129,11 @@ void MissionManager::setMission(CNSocket* sock, CNPacketData* data) { sP_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID* missionData = (sP_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID*)data->buf; INITSTRUCT(sP_FE2CL_REP_PC_SET_CURRENT_MISSION_ID, response); - + response.iCurrentMissionID = missionData->iCurrentMissionID; sock->sendPacket((void*)&response, P_FE2CL_REP_PC_SET_CURRENT_MISSION_ID, sizeof(sP_FE2CL_REP_PC_SET_CURRENT_MISSION_ID)); + Player* plr = PlayerManager::getPlayer(sock); + plr->CurrentMissionID = missionData->iCurrentMissionID; } void MissionManager::quitMission(CNSocket* sock, CNPacketData* data) { @@ -133,17 +148,24 @@ void MissionManager::quitMission(CNSocket* sock, CNPacketData* data) { int i; for (i = 0; i < ACTIVE_MISSION_COUNT; i++) { if (plr->tasks[i] == missionData->iTaskNum) + { plr->tasks[i] = 0; + for (int j = 0; j < 3; j++) { + plr->RemainingNPCCount[i][j] = 0; + } + } } if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != 0) { std::cout << "[WARN] Player quit non-active mission!?" << std::endl; } + // remove current mission + plr->CurrentMissionID = 0; TaskData& task = *Tasks[missionData->iTaskNum]; // clean up quest items for (i = 0; i < 3; i++) { - if (task["m_iSUItem"][i] == 0 && task["m_iCSUItem"][i] == 0) + if (task["m_iSUItem"][i] == 0 && task["m_iCSUItemID"][i] == 0) continue; /* @@ -151,7 +173,7 @@ void MissionManager::quitMission(CNSocket* sock, CNPacketData* data) { * slot later items will be placed in. */ for (int j = 0; j < AQINVEN_COUNT; j++) - if (plr->QInven[j].iID == task["m_iSUItem"][i] || plr->QInven[j].iID == task["m_iCSUItem"][i]) + if (plr->QInven[j].iID == task["m_iSUItem"][i] || plr->QInven[j].iID == task["m_iCSUItemID"][i]) memset(&plr->QInven[j], 0, sizeof(sItemBase)); } @@ -304,10 +326,12 @@ void MissionManager::mobKilled(CNSocket *sock, int mobid) { // acknowledge killing of mission mob... if (task["m_iCSUNumToKill"][j] != 0) + { missionmob = true; - + plr->RemainingNPCCount[i][j]--; + } // drop quest item - if (task["m_iCSUItemNumNeeded"][j] != 0) { + if (task["m_iCSUItemNumNeeded"][j] != 0 && !isQuestItemFull(sock, task["m_iCSUItemID"][j], task["m_iCSUItemNumNeeded"][j]) ) { bool drop = rand() % 100 < task["m_iSTItemDropRate"][j]; if (drop) { // XXX: are CSUItemID and CSTItemID the same? @@ -332,8 +356,26 @@ void MissionManager::mobKilled(CNSocket *sock, int mobid) { } void MissionManager::saveMission(Player* player, int missionId) { + //sanity check missionID so we don't get exceptions + if (missionId < 0 || missionId>1023) { + std::cout << "[WARN] Client submitted invalid missionId: " <aQuestFlag[row] |= (1ULL << column); } + +bool MissionManager::isQuestItemFull(CNSocket* sock, int itemId, int itemCount) { + Player* plr = PlayerManager::getPlayer(sock); + int slot = findQSlot(plr, itemId); + if (slot == -1) { + // this should never happen + std::cout << "[WARN] Player has no room for quest item!?" << std::endl; + return true; + } + + return (itemCount == plr->QInven[slot].iOpt); +} diff --git a/src/MissionManager.hpp b/src/MissionManager.hpp index 4105514..1d6bed5 100644 --- a/src/MissionManager.hpp +++ b/src/MissionManager.hpp @@ -47,6 +47,8 @@ namespace MissionManager { int findQSlot(Player *plr, int id); void dropQuestItem(CNSocket *sock, int task, int count, int id, int mobid); + //checks if player doesn't have n/n quest items + bool isQuestItemFull(CNSocket* sock, int itemId, int itemCount); int giveMissionReward(CNSocket *sock, int task); void mobKilled(CNSocket *sock, int mobid); diff --git a/src/Player.hpp b/src/Player.hpp index 4417aed..a0a2bb8 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -19,6 +19,7 @@ struct Player { int level; int HP; int slot; // player slot, not nano slot + int16_t mentor; int32_t money; int32_t fusionmatter; int32_t batteryW; @@ -45,5 +46,7 @@ struct Player { int64_t aQuestFlag[16]; int tasks[ACTIVE_MISSION_COUNT]; + int RemainingNPCCount[ACTIVE_MISSION_COUNT][3]; sItemBase QInven[AQINVEN_COUNT]; + int32_t CurrentMissionID; }; diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index 7c69f1f..3f8a2de 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -3,6 +3,7 @@ #include "NPCManager.hpp" #include "CNShardServer.hpp" #include "CNShared.hpp" +#include "MissionManager.hpp" #include "settings.hpp" @@ -207,24 +208,30 @@ void PlayerManager::enterPlayer(CNSocket* sock, CNPacketData* data) { response.PCLoadData2CL.iLevel = plr.level; response.PCLoadData2CL.iCandy = plr.money; response.PCLoadData2CL.iFusionMatter = plr.fusionmatter; - response.PCLoadData2CL.iMentor = 5; // Computress + response.PCLoadData2CL.iMentor = plr.mentor; response.PCLoadData2CL.iMentorCount = 1; // how many guides the player has had - response.PCLoadData2CL.iMapNum = 0; response.PCLoadData2CL.iX = plr.x; response.PCLoadData2CL.iY = plr.y; response.PCLoadData2CL.iZ = plr.z; response.PCLoadData2CL.iAngle = plr.angle; + response.PCLoadData2CL.iBatteryN = plr.batteryN; + response.PCLoadData2CL.iBatteryW = plr.batteryW; + response.PCLoadData2CL.iBuddyWarpTime = 60; //sets 60s warp cooldown on login response.PCLoadData2CL.iActiveNanoSlotNum = -1; response.PCLoadData2CL.iFatigue = 50; response.PCLoadData2CL.PCStyle = plr.PCStyle; - response.PCLoadData2CL.PCStyle2 = plr.PCStyle2; + + //client doesnt read this, it gets it from charinfo + //response.PCLoadData2CL.PCStyle2 = plr.PCStyle2; // inventory for (int i = 0; i < AEQUIP_COUNT; i++) response.PCLoadData2CL.aEquip[i] = plr.Equip[i]; - for (int i = 0; i < AINVEN_COUNT; i++) response.PCLoadData2CL.aInven[i] = plr.Inven[i]; + // quest inventory + for (int i = 0; i < AQINVEN_COUNT; i++) + response.PCLoadData2CL.aQInven[i] = plr.QInven[i]; // nanos for (int i = 1; i < SIZEOF_NANO_BANK_SLOT; i++) { response.PCLoadData2CL.aNanoBank[i] = plr.Nanos[i]; @@ -232,8 +239,28 @@ void PlayerManager::enterPlayer(CNSocket* sock, CNPacketData* data) { for (int i = 0; i < 3; i++) { response.PCLoadData2CL.aNanoSlots[i] = plr.equippedNanos[i]; } + //missions in progress + for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) { + if (plr.tasks[i] == 0) + break; + response.PCLoadData2CL.aRunningQuest[i].m_aCurrTaskID = plr.tasks[i]; + TaskData &task = *MissionManager::Tasks[plr.tasks[i]]; + for (int j = 0; j < 3; j++) { + response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCID[j] = (int)task["m_iCSUEnemyID"][j]; + response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCCount[j] = plr.RemainingNPCCount[i][j]; + /* + * client doesn't care about NeededItem ID and Count, + * it gets Count from Quest Inventory + * + * KillNPCCount sets RemainEnemyNum in the client + * Yes, this is extraordinary stupid, even for Grigon + */ + } + } + response.PCLoadData2CL.iCurrentMissionID = plr.CurrentMissionID; - // missions + // completed missions + // the packet requires 32 items, but the client only checks the first 16 (shrug) for (int i = 0; i < 16; i++) { response.PCLoadData2CL.aQuestFlag[i] = plr.aQuestFlag[i]; } @@ -719,8 +746,10 @@ void PlayerManager::changePlayerGuide(CNSocket *sock, CNPacketData *data) { resp.iMentor = pkt->iMentor; resp.iMentorCnt = 1; resp.iFusionMatter = plr->fusionmatter; // no cost - + sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_CHANGE_MENTOR_SUCC, sizeof(sP_FE2CL_REP_PC_CHANGE_MENTOR_SUCC)); + //save it on player + plr->mentor = pkt->iMentor; } #pragma region Helper methods diff --git a/src/settings.cpp b/src/settings.cpp index e7a83ce..e289ff9 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -7,6 +7,7 @@ int settings::VERBOSITY = 1; int settings::LOGINPORT = 8001; bool settings::APPROVEALLNAMES = true; +bool settings::USEWEBAPI = false; int settings::DBSAVEINTERVAL = 240; int settings::SHARDPORT = 8002; @@ -41,9 +42,10 @@ void settings::init() { APPROVEALLNAMES = reader.GetBoolean("", "acceptallcustomnames", APPROVEALLNAMES); VERBOSITY = reader.GetInteger("", "verbosity", VERBOSITY); LOGINPORT = reader.GetInteger("login", "port", LOGINPORT); + USEWEBAPI = reader.GetBoolean("login", "usewebapi", USEWEBAPI); SHARDPORT = reader.GetInteger("shard", "port", SHARDPORT); SHARDSERVERIP = reader.Get("shard", "ip", "127.0.0.1"); - DBSAVEINTERVAL = reader.GetInteger("login", "dbsaveinterval", DBSAVEINTERVAL); + DBSAVEINTERVAL = reader.GetInteger("shard", "dbsaveinterval", DBSAVEINTERVAL); TIMEOUT = reader.GetInteger("shard", "timeout", TIMEOUT); PLAYERDISTANCE = reader.GetInteger("shard", "playerdistance", PLAYERDISTANCE); NPCDISTANCE = reader.GetInteger("shard", "npcdistance", NPCDISTANCE); diff --git a/src/settings.hpp b/src/settings.hpp index 6700ab6..0a1f7b7 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -4,6 +4,7 @@ namespace settings { extern int VERBOSITY; extern int LOGINPORT; extern bool APPROVEALLNAMES; + extern bool USEWEBAPI; extern int DBSAVEINTERVAL; extern int SHARDPORT; extern std::string SHARDSERVERIP;