diff --git a/src/CombatManager.cpp b/src/CombatManager.cpp index 02c2312..e932b23 100644 --- a/src/CombatManager.cpp +++ b/src/CombatManager.cpp @@ -2,6 +2,7 @@ #include "PlayerManager.hpp" #include "NPCManager.hpp" #include "ItemManager.hpp" +#include "MissionManager.hpp" #include @@ -58,11 +59,7 @@ void CombatManager::pcAttackNpcs(CNSocket *sock, CNPacketData *data) { if (mob.appearanceData.iHP <= 0) { giveReward(sock); - INITSTRUCT(sP_FE2CL_REP_PC_KILL_QUEST_NPCs_SUCC, kill); - - kill.iNPCID = mob.appearanceData.iNPCType; - - sock->sendPacket((void*)&kill, P_FE2CL_REP_PC_KILL_QUEST_NPCs_SUCC, sizeof(sP_FE2CL_REP_PC_KILL_QUEST_NPCs_SUCC)); + MissionManager::mobKilled(sock, mob.appearanceData.iNPCType); // TODO: despawn mobs when they die } diff --git a/src/MissionManager.cpp b/src/MissionManager.cpp index 95dad3b..83cfd45 100644 --- a/src/MissionManager.cpp +++ b/src/MissionManager.cpp @@ -7,29 +7,47 @@ #include "string.h" std::map MissionManager::Rewards; -std::map MissionManager::SUItems; -std::map MissionManager::QuestDropSets; -std::map MissionManager::ItemCleanups; +std::map MissionManager::Tasks; void MissionManager::init() { - REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_START, acceptMission); - REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_END, completeTask); + REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_START, taskStart); + REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_END, taskEnd); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID, setMission); REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_STOP, quitMission); } -void MissionManager::acceptMission(CNSocket* sock, CNPacketData* data) { +void MissionManager::taskStart(CNSocket* sock, CNPacketData* data) { if (data->size != sizeof(sP_CL2FE_REQ_PC_TASK_START)) return; // malformed packet sP_CL2FE_REQ_PC_TASK_START* missionData = (sP_CL2FE_REQ_PC_TASK_START*)data->buf; INITSTRUCT(sP_FE2CL_REP_PC_TASK_START_SUCC, response); + Player *plr = PlayerManager::getPlayer(sock); + + if (Tasks.find(missionData->iTaskNum) == Tasks.end()) { + std::cout << "[WARN] Player submitted unknown task!?" << std::endl; + // TODO: TASK_FAIL? + response.iTaskNum = missionData->iTaskNum; + sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC)); + return; + } + + int i; + for (i = 0; i < ACTIVE_MISSION_COUNT; i++) { + if (plr->tasks[i] == 0) { + plr->tasks[i] = missionData->iTaskNum; + break; + } + } + if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != missionData->iTaskNum) { + std::cout << "[WARN] Player has more than 6 active missions!?" << std::endl; + } response.iTaskNum = missionData->iTaskNum; sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC)); } -void MissionManager::completeTask(CNSocket* sock, CNPacketData* data) { +void MissionManager::taskEnd(CNSocket* sock, CNPacketData* data) { if (data->size != sizeof(sP_CL2FE_REQ_PC_TASK_END)) return; // malformed packet @@ -40,22 +58,16 @@ 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. - */ + if (Tasks.find(missionData->iTaskNum) == Tasks.end()) { + std::cout << "[WARN] Player submitted unknown task!?" << std::endl; + // TODO: TASK_FAIL? + sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_END_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_END_SUCC)); + return; } + // ugly pointer/reference juggling for the sake of operator overloading... + TaskData& task = *Tasks[missionData->iTaskNum]; + /* * SUItems * @@ -65,12 +77,23 @@ void MissionManager::completeTask(CNSocket* sock, CNPacketData* data) { * The server is responsible for dropping the correct item. * Yes, this is idiotic. */ - if (SUItems.find(missionData->iTaskNum) != SUItems.end()) { - SUItem *suitem = SUItems[missionData->iTaskNum]; + for (int i = 0; i < 3; i++) + if (task["m_iSUItem"][i] != 0) + dropQuestItem(sock, missionData->iTaskNum, 1, task["m_iSUItem"][i], 0); - for (int i = 0; i < 3; i++) - if (suitem->itemIds[i] != 0) - dropQuestItem(sock, missionData->iTaskNum, 1, suitem->itemIds[i], 0); + // clean up quest items which have served their purpose + if (task["m_iCSUItemID"][0] != 0) { + for (int i = 0; i < AQINVEN_COUNT; i++) + for (int j = 0; j < 4; j++) + if (plr->QInven[i].iID == task["m_CSUItemID"][j]) { + std::cout << "deleting qitem " << i << std::endl; + 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. + */ } // mission rewards @@ -78,6 +101,16 @@ void MissionManager::completeTask(CNSocket* sock, CNPacketData* data) { giveMissionReward(sock, missionData->iTaskNum); } + // update player + int i; + for (i = 0; i < ACTIVE_MISSION_COUNT; i++) { + if (plr->tasks[i] == missionData->iTaskNum) + plr->tasks[i] = 0; + } + if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != 0) { + std::cout << "[WARN] Player completed non-active mission!?" << std::endl; + } + sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_END_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_END_SUCC)); } @@ -104,14 +137,14 @@ void MissionManager::quitMission(CNSocket* sock, CNPacketData* data) { } // TODO: coalesce into ItemManager::findFreeSlot()? -int MissionManager::findFreeQSlot(Player *plr) { +int MissionManager::findQSlot(Player *plr, int id) { 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) + if (plr->QInven[i].iID == id || memcmp(&plr->QInven[i], &free, sizeof(sItemBase)) == 0) return i; // not found @@ -132,7 +165,7 @@ void MissionManager::dropQuestItem(CNSocket *sock, int task, int count, int id, memset(respbuf, 0, resplen); // find free quest item slot - int slot = findFreeQSlot(plr); + int slot = findQSlot(plr, id); if (slot == -1) { // this should never happen std::cout << "[WARN] Player has no room for quest item!?" << std::endl; @@ -143,7 +176,7 @@ void MissionManager::dropQuestItem(CNSocket *sock, int task, int count, int id, // update player plr->QInven[slot].iType = 8; plr->QInven[slot].iID = id; - plr->QInven[slot].iOpt = count; + plr->QInven[slot].iOpt += count; // stacking // preserve stats reward->m_iCandy = plr->money; @@ -159,7 +192,6 @@ void MissionManager::dropQuestItem(CNSocket *sock, int task, int count, int id, item->iSlotNum = slot; item->eIL = 2; - std::cout << "sending quest item\n"; sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen); } @@ -220,3 +252,41 @@ void MissionManager::giveMissionReward(CNSocket *sock, int task) { sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen); } + +void MissionManager::mobKilled(CNSocket *sock, int mobid) { + Player *plr = PlayerManager::getPlayer(sock); + + for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) { + if (plr->tasks[i] == 0) + continue; + + // tasks[] should always have valid IDs + TaskData& task = *Tasks[plr->tasks[i]]; + + for (int j = 0; j < 3; j++) { + if (task["m_iCSUEnemyID"][j] != mobid) + continue; + + // acknowledge killing of mission mob + if (task["m_iCSUNumToKill"][j] != 0) { + INITSTRUCT(sP_FE2CL_REP_PC_KILL_QUEST_NPCs_SUCC, kill); + + kill.iNPCID = mobid; + + sock->sendPacket((void*)&kill, P_FE2CL_REP_PC_KILL_QUEST_NPCs_SUCC, sizeof(sP_FE2CL_REP_PC_KILL_QUEST_NPCs_SUCC)); + } + + // drop quest item + if (task["m_iCSUItemNumNeeded"][j] != 0) { + bool drop = rand() % 100 < task["m_iSTItemDropRate"][j]; + if (drop) { + // XXX: are CSUItemID and CSTItemID the same? + dropQuestItem(sock, plr->tasks[i], 1, task["m_iCSUItemID"][j], mobid); + } else { + // fail to drop (itemID == 0) + dropQuestItem(sock, plr->tasks[i], 1, 0, mobid); + } + } + } + } +} diff --git a/src/MissionManager.hpp b/src/MissionManager.hpp index 66406a1..732edec 100644 --- a/src/MissionManager.hpp +++ b/src/MissionManager.hpp @@ -21,51 +21,33 @@ struct Reward { }; }; -struct SUItem { - int32_t itemIds[3]; +struct TaskData { + /* + * TODO: We'll probably want to keep only the data the server actually needs, + * but for now RE/development is much easier if we have everything at + * our fingertips. + */ + nlohmann::json task; - SUItem(nlohmann::json ids) { - for (int i = 0; i < 3; i++) { - itemIds[i] = ids[i]; - } - } -}; + TaskData(nlohmann::json t) : task(t) {} -struct QuestDropSet { - int32_t mobIds[3]; - int32_t itemIds[3]; - - QuestDropSet(nlohmann::json mobs, nlohmann::json items) { - for (int i = 0; i < 3; i++) { - mobIds[i] = mobs[i]; - itemIds[i] = items[i]; - } - } -}; - -struct ItemCleanup { - int32_t itemIds[4]; - - ItemCleanup(nlohmann::json ids) { - for (int i = 0; i < 4; i++) { - itemIds[i] = ids[i]; - } - } + // convenience + auto operator[](std::string s) { return task[s]; } }; namespace MissionManager { extern std::map Rewards; - extern std::map SUItems; - extern std::map QuestDropSets; - extern std::map ItemCleanups; + extern std::map Tasks; void init(); - void acceptMission(CNSocket* sock, CNPacketData* data); - void completeTask(CNSocket* sock, CNPacketData* data); + void taskStart(CNSocket* sock, CNPacketData* data); + void taskEnd(CNSocket* sock, CNPacketData* data); void setMission(CNSocket* sock, CNPacketData* data); void quitMission(CNSocket* sock, CNPacketData* data); - int findFreeQSlot(Player *plr); + int findQSlot(Player *plr, int id); void dropQuestItem(CNSocket *sock, int task, int count, int id, int mobid); void giveMissionReward(CNSocket *sock, int task); + + void mobKilled(CNSocket *sock, int mobid); } diff --git a/src/Player.hpp b/src/Player.hpp index 2604b9f..04c8e45 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -6,6 +6,8 @@ #include "CNProtocol.hpp" #include "CNStructs.hpp" +#define ACTIVE_MISSION_COUNT 6 + struct Player { int accountId; int64_t SerialKey; @@ -33,6 +35,6 @@ struct Player { bool isTradeConfirm; bool IsGM; - int tasks[6]; + int tasks[ACTIVE_MISSION_COUNT]; sItemBase QInven[AQINVEN_COUNT]; }; diff --git a/src/TableData.cpp b/src/TableData.cpp index 48cc26f..3991c70 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -55,7 +55,7 @@ void TableData::init() { NPCManager::NPCs[tmp.appearanceData.iNPC_ID] = tmp; } - std::cout << "[INFO] populated " << NPCManager::NPCs.size() << " NPCs" << std::endl; + std::cout << "[INFO] Populated " << NPCManager::NPCs.size() << " NPCs" << std::endl; } catch (const std::exception& err) { std::cerr << "[WARN] Malformed mobs.json file! Reason:" << err.what() << std::endl; @@ -79,7 +79,7 @@ void TableData::init() { NPCManager::Warps[warpID] = warpLoc; } - std::cout << "[INFO] populated " << NPCManager::Warps.size() << " Warps" << std::endl; + std::cout << "[INFO] Populated " << NPCManager::Warps.size() << " Warps" << std::endl; // load mission-related data nlohmann::json tasks = xdtData["m_pMissionTable"]["m_pMissionData"]; @@ -89,30 +89,15 @@ void TableData::init() { // rewards if (task["m_iSUReward"] != 0) { - auto tmp = xdtData["m_pMissionTable"]["m_pRewardData"][(int)task["m_iSUReward"]]; - Reward *rew = new Reward(tmp["m_iMissionRewardID"], tmp["m_iMissionRewarItemType"], - tmp["m_iMissionRewardItemID"], tmp["m_iCash"], tmp["m_iFusionMatter"]); + auto _rew = xdtData["m_pMissionTable"]["m_pRewardData"][(int)task["m_iSUReward"]]; + Reward *rew = new Reward(_rew["m_iMissionRewardID"], _rew["m_iMissionRewarItemType"], + _rew["m_iMissionRewardItemID"], _rew["m_iCash"], _rew["m_iFusionMatter"]); MissionManager::Rewards[task["m_iHTaskID"]] = rew; } - // quest items obtained after completing a certain task - // (distinct from quest items dropped from mobs) - 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) { - MissionManager::QuestDropSets[task["m_iHTaskID"]] = new QuestDropSet(task["m_iCSUEnemyID"], task["m_iCSUItemID"]); - // 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"]); - } + // everything else lol. see TaskData comment. + MissionManager::Tasks[task["m_iHTaskID"]] = new TaskData(task); } std::cout << "[INFO] Loaded mission-related data" << std::endl; @@ -129,10 +114,6 @@ void TableData::cleanup() { */ for (auto& pair : MissionManager::Rewards) delete pair.second; - for (auto& pair : MissionManager::SUItems) - delete pair.second; - for (auto& pair : MissionManager::QuestDropSets) - delete pair.second; - for (auto& pair : MissionManager::ItemCleanups) + for (auto& pair : MissionManager::Tasks) delete pair.second; }