diff --git a/src/MissionManager.cpp b/src/MissionManager.cpp index 078c63a..95dad3b 100644 --- a/src/MissionManager.cpp +++ b/src/MissionManager.cpp @@ -4,9 +4,12 @@ #include "PlayerManager.hpp" #include "ItemManager.hpp" +#include "string.h" + std::map MissionManager::Rewards; std::map MissionManager::SUItems; std::map MissionManager::QuestDropSets; +std::map MissionManager::ItemCleanups; void MissionManager::init() { REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_START, acceptMission); @@ -37,6 +40,22 @@ void MissionManager::completeTask(CNSocket* sock, CNPacketData* data) { response.iTaskNum = missionData->iTaskNum; std::cout << missionData->iTaskNum << std::endl; + // clean up quest items which have served their purpose + if (ItemCleanups.find(missionData->iTaskNum) != ItemCleanups.end()) { + ItemCleanup *clean = ItemCleanups[missionData->iTaskNum]; + + std::cout << "clearing qitems\n"; + for (int i = 0; i < AQINVEN_COUNT; i++) + for (int j = 0; j < 4; j++) + if (plr->QInven[i].iID == clean->itemIds[j]) + memset(&plr->QInven[i], 0, sizeof(sItemBase)); + /* + * NOTE: As quest items are not graphically enumerated client-side, + * the client does not need to be notified of item cleanup, since + * the items will just be clobbered upon assignment anyway. + */ + } + /* * SUItems * @@ -49,42 +68,104 @@ void MissionManager::completeTask(CNSocket* sock, CNPacketData* data) { if (SUItems.find(missionData->iTaskNum) != SUItems.end()) { SUItem *suitem = SUItems[missionData->iTaskNum]; - const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); - assert(resplen < CN_PACKET_BUFFER_SIZE); - // we know it's only one trailing struct, so we can skip full validation - - uint8_t respbuf[resplen]; // not a variable length array, don't worry - sP_FE2CL_REP_REWARD_ITEM *reward = (sP_FE2CL_REP_REWARD_ITEM *)respbuf; - sItemReward *item = (sItemReward *)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM)); - - // don't forget to zero the buffer! - memset(respbuf, 0, resplen); - - reward->m_iCandy = plr->money; - reward->m_iFusionMatter = plr->fusionmatter; - reward->iFatigue = 100; // prevents warning message - reward->iFatigue_Level = 1; - reward->iItemCnt = 1; // remember to update resplen if you change this - reward->iTaskID = missionData->iTaskNum; - reward->iNPC_TypeID = 0; // XXX - - item->sItem.iType = 8; - item->sItem.iID = suitem->itemIds[0]; // XXX XXX XXX - item->sItem.iOpt = 1; // XXX count needs to be correct - item->iSlotNum = 0; // XXX - item->eIL = 2; - - std::cout << "sending item packet\n"; - sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen); + for (int i = 0; i < 3; i++) + if (suitem->itemIds[i] != 0) + dropQuestItem(sock, missionData->iTaskNum, 1, suitem->itemIds[i], 0); } // mission rewards - if (Rewards.find(missionData->iTaskNum) == Rewards.end()) { - // not a mission's final task - sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_END_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_END_SUCC)); + if (Rewards.find(missionData->iTaskNum) != Rewards.end()) { + giveMissionReward(sock, missionData->iTaskNum); + } + + sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_END_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_END_SUCC)); +} + +void MissionManager::setMission(CNSocket* sock, CNPacketData* data) { + if (data->size != sizeof(sP_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID)) + return; // malformed packet + + 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)); +} + +void MissionManager::quitMission(CNSocket* sock, CNPacketData* data) { + if (data->size != sizeof(sP_CL2FE_REQ_PC_TASK_STOP)) + return; // malformed packet + + sP_CL2FE_REQ_PC_TASK_STOP* missionData = (sP_CL2FE_REQ_PC_TASK_STOP*)data->buf; + INITSTRUCT(sP_FE2CL_REP_PC_TASK_STOP_SUCC, response); + + response.iTaskNum = missionData->iTaskNum; + sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_STOP_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_STOP_SUCC)); +} + +// TODO: coalesce into ItemManager::findFreeSlot()? +int MissionManager::findFreeQSlot(Player *plr) { + int i; + sItemBase free; + + memset(&free, 0, sizeof(sItemBase)); + + for (i = 0; i < AQINVEN_COUNT; i++) + if (memcmp(&plr->QInven[i], &free, sizeof(sItemBase)) == 0) + return i; + + // not found + return -1; +} + +void MissionManager::dropQuestItem(CNSocket *sock, int task, int count, int id, int mobid) { + const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); + assert(resplen < CN_PACKET_BUFFER_SIZE); + // we know it's only one trailing struct, so we can skip full validation + + uint8_t respbuf[resplen]; // not a variable length array, don't worry + sP_FE2CL_REP_REWARD_ITEM *reward = (sP_FE2CL_REP_REWARD_ITEM *)respbuf; + sItemReward *item = (sItemReward *)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM)); + Player *plr = PlayerManager::getPlayer(sock); + + // don't forget to zero the buffer! + memset(respbuf, 0, resplen); + + // find free quest item slot + int slot = findFreeQSlot(plr); + if (slot == -1) { + // this should never happen + std::cout << "[WARN] Player has no room for quest item!?" << std::endl; return; } - Reward *reward = Rewards[missionData->iTaskNum]; + std::cout << "new qitem in slot " << slot << std::endl; + + // update player + plr->QInven[slot].iType = 8; + plr->QInven[slot].iID = id; + plr->QInven[slot].iOpt = count; + + // preserve stats + reward->m_iCandy = plr->money; + reward->m_iFusionMatter = plr->fusionmatter; + reward->iFatigue = 100; // prevents warning message + reward->iFatigue_Level = 1; + + reward->iItemCnt = 1; // remember to update resplen if you change this + reward->iTaskID = task; + reward->iNPC_TypeID = mobid; + + item->sItem = plr->QInven[slot]; + item->iSlotNum = slot; + item->eIL = 2; + + std::cout << "sending quest item\n"; + sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen); +} + +void MissionManager::giveMissionReward(CNSocket *sock, int task) { + Reward *reward = Rewards[task]; + Player *plr = PlayerManager::getPlayer(sock); int nrewards = 0; for (int i = 0; i < 4; i++) { @@ -99,7 +180,7 @@ void MissionManager::completeTask(CNSocket* sock, CNPacketData* data) { std::cout << "Not enough room to complete task" << std::endl; INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_FAIL, fail); - fail.iTaskNum = missionData->iTaskNum; + fail.iTaskNum = task; fail.iErrorCode = 13; // inventory full sock->sendPacket((void*)&fail, P_FE2CL_REP_PC_TASK_END_FAIL, sizeof(sP_FE2CL_REP_PC_TASK_END_FAIL)); @@ -137,30 +218,5 @@ void MissionManager::completeTask(CNSocket* sock, CNPacketData* data) { plr->Inven[slots[i]] = item->sItem; } - sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_END_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_END_SUCC)); sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen); } - -void MissionManager::setMission(CNSocket* sock, CNPacketData* data) { - if (data->size != sizeof(sP_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID)) - return; // malformed packet - - 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)); -} - -void MissionManager::quitMission(CNSocket* sock, CNPacketData* data) { - if (data->size != sizeof(sP_CL2FE_REQ_PC_TASK_STOP)) - return; // malformed packet - - sP_CL2FE_REQ_PC_TASK_STOP* missionData = (sP_CL2FE_REQ_PC_TASK_STOP*)data->buf; - INITSTRUCT(sP_FE2CL_REP_PC_TASK_STOP_SUCC, response); - - response.iTaskNum = missionData->iTaskNum; - sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_STOP_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_STOP_SUCC)); -} - -//void MissionManager::dropQuestItem(CNSocket *sock, int mobid, int count) {} diff --git a/src/MissionManager.hpp b/src/MissionManager.hpp index 81e8449..66406a1 100644 --- a/src/MissionManager.hpp +++ b/src/MissionManager.hpp @@ -1,6 +1,7 @@ #pragma once #include "CNShardServer.hpp" +#include "Player.hpp" #include "contrib/JSON.hpp" @@ -42,14 +43,29 @@ struct QuestDropSet { } }; +struct ItemCleanup { + int32_t itemIds[4]; + + ItemCleanup(nlohmann::json ids) { + for (int i = 0; i < 4; i++) { + itemIds[i] = ids[i]; + } + } +}; + namespace MissionManager { extern std::map Rewards; extern std::map SUItems; extern std::map QuestDropSets; + extern std::map ItemCleanups; void init(); void acceptMission(CNSocket* sock, CNPacketData* data); void completeTask(CNSocket* sock, CNPacketData* data); void setMission(CNSocket* sock, CNPacketData* data); void quitMission(CNSocket* sock, CNPacketData* data); + + int findFreeQSlot(Player *plr); + void dropQuestItem(CNSocket *sock, int task, int count, int id, int mobid); + void giveMissionReward(CNSocket *sock, int task); } diff --git a/src/Player.hpp b/src/Player.hpp index a82ba99..2604b9f 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -32,4 +32,7 @@ struct Player { bool isTrading; bool isTradeConfirm; bool IsGM; + + int tasks[6]; + sItemBase QInven[AQINVEN_COUNT]; }; diff --git a/src/TableData.cpp b/src/TableData.cpp index 82ee372..48cc26f 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -12,7 +12,6 @@ void TableData::init() { // load NPCs from NPC.json try { - std::ifstream inFile(settings::NPCJSON); nlohmann::json npcData; @@ -39,7 +38,7 @@ void TableData::init() { // load temporary mob dump try { - std::ifstream inFile(settings::MOBJSON); // not in settings, since it's temp + std::ifstream inFile(settings::MOBJSON); nlohmann::json npcData; // read file into json @@ -82,7 +81,7 @@ void TableData::init() { std::cout << "[INFO] populated " << NPCManager::Warps.size() << " Warps" << std::endl; - // missions + // load mission-related data nlohmann::json tasks = xdtData["m_pMissionTable"]["m_pMissionData"]; for (auto _task = tasks.begin(); _task != tasks.end(); _task++) { @@ -99,9 +98,8 @@ void TableData::init() { // quest items obtained after completing a certain task // (distinct from quest items dropped from mobs) - if (task["m_iSUItem"][0] != 0) { + if (task["m_iSUItem"][0] != 0) MissionManager::SUItems[task["m_iHTaskID"]] = new SUItem(task["m_iSUItem"]); - } // quest item mob drops if (task["m_iCSUItemID"][0] != 0) { @@ -109,6 +107,12 @@ void TableData::init() { // TODO: timeouts, drop rates, etc. // not sure if we need to keep track of NumNeeded/NumToKill server-side. } + + // quest item cleanup + if (task["m_iDelItemID"][0] != 0) { + std::cout << "adding DelItem for " << task["m_iHTaskID"] << std::endl; + MissionManager::ItemCleanups[task["m_iHTaskID"]] = new ItemCleanup(task["m_iDelItemID"]); + } } std::cout << "[INFO] Loaded mission-related data" << std::endl; @@ -129,4 +133,6 @@ void TableData::cleanup() { delete pair.second; for (auto& pair : MissionManager::QuestDropSets) delete pair.second; + for (auto& pair : MissionManager::ItemCleanups) + delete pair.second; } diff --git a/src/main.cpp b/src/main.cpp index e5cd478..f4af7e2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -34,7 +34,9 @@ void terminate(int arg) { std::cout << "OpenFusion: terminating." << std::endl; shardServer->kill(); shardThread->join(); +#if defined(__SANITIZE_ADDRESS__) || (defined(__has_feature) && __has_feature(address_sanitizer)) TableData::cleanup(); +#endif exit(0); } diff --git a/src/structs/0104.hpp b/src/structs/0104.hpp index bd70161..1482212 100644 --- a/src/structs/0104.hpp +++ b/src/structs/0104.hpp @@ -2,6 +2,7 @@ #define AEQUIP_COUNT 9 #define AINVEN_COUNT 50 +#define AQINVEN_COUNT 50 #pragma pack(push) diff --git a/src/structs/0728.hpp b/src/structs/0728.hpp index 27bbd47..5f42b90 100644 --- a/src/structs/0728.hpp +++ b/src/structs/0728.hpp @@ -2,6 +2,7 @@ #define AEQUIP_COUNT 12 #define AINVEN_COUNT 50 +#define AQINVEN_COUNT 50 #pragma pack(push)