2020-08-24 21:04:56 +00:00
|
|
|
#include "CNShardServer.hpp"
|
|
|
|
#include "CNStructs.hpp"
|
|
|
|
#include "MissionManager.hpp"
|
|
|
|
#include "PlayerManager.hpp"
|
2020-09-23 19:44:27 +00:00
|
|
|
#include "NanoManager.hpp"
|
2020-09-09 19:09:01 +00:00
|
|
|
#include "ItemManager.hpp"
|
|
|
|
|
2020-09-10 13:01:35 +00:00
|
|
|
#include "string.h"
|
|
|
|
|
2020-09-09 19:09:01 +00:00
|
|
|
std::map<int32_t, Reward*> MissionManager::Rewards;
|
2020-09-10 16:40:38 +00:00
|
|
|
std::map<int32_t, TaskData*> MissionManager::Tasks;
|
2020-09-26 01:48:45 +00:00
|
|
|
nlohmann::json MissionManager::AvatarGrowth[37];
|
2020-08-24 21:04:56 +00:00
|
|
|
|
|
|
|
void MissionManager::init() {
|
2020-09-10 16:40:38 +00:00
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_START, taskStart);
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_END, taskEnd);
|
2020-08-24 21:04:56 +00:00
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID, setMission);
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_TASK_STOP, quitMission);
|
|
|
|
}
|
|
|
|
|
2020-11-30 16:59:39 +00:00
|
|
|
bool MissionManager::startTask(Player* plr, int TaskID, bool NanoMission) {
|
2020-09-23 19:44:27 +00:00
|
|
|
if (MissionManager::Tasks.find(TaskID) == MissionManager::Tasks.end()) {
|
2020-09-10 16:40:38 +00:00
|
|
|
std::cout << "[WARN] Player submitted unknown task!?" << std::endl;
|
2020-09-23 19:44:27 +00:00
|
|
|
return false;
|
2020-09-10 16:40:38 +00:00
|
|
|
}
|
2020-10-05 00:03:13 +00:00
|
|
|
|
2020-09-25 09:30:59 +00:00
|
|
|
// client freaks out if nano mission isn't sent first after reloging, so it's easiest to set it here
|
|
|
|
if (NanoMission && plr->tasks[0] != 0) {
|
|
|
|
// lets move task0 to different spot
|
|
|
|
int moveToSlot = 1;
|
|
|
|
for (; moveToSlot < ACTIVE_MISSION_COUNT; moveToSlot++)
|
|
|
|
if (plr->tasks[moveToSlot] == 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
plr->tasks[moveToSlot] = plr->tasks[0];
|
|
|
|
plr->tasks[0] = 0;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
plr->RemainingNPCCount[moveToSlot][i] = plr->RemainingNPCCount[0][i];
|
|
|
|
plr->RemainingNPCCount[0][i] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-23 19:44:27 +00:00
|
|
|
TaskData& task = *MissionManager::Tasks[TaskID];
|
2020-09-10 16:40:38 +00:00
|
|
|
int i;
|
|
|
|
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
|
|
|
if (plr->tasks[i] == 0) {
|
2020-09-23 19:44:27 +00:00
|
|
|
plr->tasks[i] = TaskID;
|
2020-09-21 19:43:53 +00:00
|
|
|
for (int j = 0; j < 3; j++) {
|
|
|
|
plr->RemainingNPCCount[i][j] = (int)task["m_iCSUNumToKill"][j];
|
|
|
|
}
|
2020-09-10 16:40:38 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2020-09-21 19:43:53 +00:00
|
|
|
|
2020-09-23 19:44:27 +00:00
|
|
|
if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != TaskID) {
|
2020-09-10 16:40:38 +00:00
|
|
|
std::cout << "[WARN] Player has more than 6 active missions!?" << std::endl;
|
|
|
|
}
|
2020-08-24 21:04:56 +00:00
|
|
|
|
2020-09-23 19:44:27 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2020-09-25 09:30:59 +00:00
|
|
|
if (!startTask(plr, missionData->iTaskNum, false)) {
|
2020-09-23 19:44:27 +00:00
|
|
|
// 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;
|
|
|
|
}
|
2020-10-05 00:03:13 +00:00
|
|
|
|
2020-10-21 05:24:51 +00:00
|
|
|
TaskData& task = *Tasks[missionData->iTaskNum];
|
|
|
|
|
2020-08-24 21:04:56 +00:00
|
|
|
response.iTaskNum = missionData->iTaskNum;
|
2020-10-21 05:24:51 +00:00
|
|
|
response.iRemainTime = task["m_iSTGrantTimer"];
|
2020-08-24 21:04:56 +00:00
|
|
|
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC));
|
2020-09-25 21:05:36 +00:00
|
|
|
|
2020-10-21 05:24:51 +00:00
|
|
|
// HACK: auto-succeed escort task
|
|
|
|
if (task["m_iHTaskType"] == 6) {
|
2020-09-25 21:05:36 +00:00
|
|
|
std::cout << "Sending Eduardo success packet" << std::endl;
|
|
|
|
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_SUCC, response);
|
|
|
|
|
2020-10-21 05:24:51 +00:00
|
|
|
endTask(sock, missionData->iTaskNum);
|
|
|
|
response.iTaskNum = missionData->iTaskNum;
|
2020-09-25 21:05:36 +00:00
|
|
|
|
|
|
|
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_END_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_END_SUCC));
|
|
|
|
}
|
2020-10-21 05:24:51 +00:00
|
|
|
|
|
|
|
// Give player their delivery items at the start.
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
|
|
if (task["m_iSTItemID"][i] != 0 && task["m_iSTItemNumNeeded"][i] > 0)
|
|
|
|
dropQuestItem(sock, missionData->iTaskNum, task["m_iSTItemNumNeeded"][i], task["m_iSTItemID"][i], 0);
|
2020-08-24 21:04:56 +00:00
|
|
|
}
|
|
|
|
|
2020-09-10 16:40:38 +00:00
|
|
|
void MissionManager::taskEnd(CNSocket* sock, CNPacketData* data) {
|
2020-08-24 21:04:56 +00:00
|
|
|
if (data->size != sizeof(sP_CL2FE_REQ_PC_TASK_END))
|
|
|
|
return; // malformed packet
|
|
|
|
|
|
|
|
sP_CL2FE_REQ_PC_TASK_END* missionData = (sP_CL2FE_REQ_PC_TASK_END*)data->buf;
|
2020-10-21 05:24:51 +00:00
|
|
|
|
|
|
|
// failed timed missions give an iNPC_ID of 0
|
|
|
|
if (missionData->iNPC_ID == 0) {
|
|
|
|
TaskData* task = MissionManager::Tasks[missionData->iTaskNum];
|
2020-12-12 22:22:22 +00:00
|
|
|
if (task->task["m_iSTGrantTimer"] > 0) { // its a timed mission
|
2020-10-21 05:24:51 +00:00
|
|
|
Player* plr = PlayerManager::getPlayer(sock);
|
2020-12-12 22:22:22 +00:00
|
|
|
/*
|
|
|
|
* Enemy killing missions
|
|
|
|
* this is gross and should be cleaned up later
|
|
|
|
* once we comb over mission logic more throughly
|
|
|
|
*/
|
|
|
|
bool mobsAreKilled = false;
|
|
|
|
if (task->task["m_iHTaskType"] == 5) {
|
|
|
|
mobsAreKilled = true;
|
|
|
|
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
|
|
|
if (plr->tasks[i] == missionData->iTaskNum) {
|
|
|
|
for (int j = 0; j < 3; j++) {
|
|
|
|
if (plr->RemainingNPCCount[i][j] > 0) {
|
|
|
|
mobsAreKilled = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!mobsAreKilled) {
|
2020-10-21 05:24:51 +00:00
|
|
|
|
2020-12-12 22:22:22 +00:00
|
|
|
int failTaskID = task->task["m_iFOutgoingTask"];
|
|
|
|
if (failTaskID != 0) {
|
|
|
|
MissionManager::quitTask(sock, missionData->iTaskNum, false);
|
|
|
|
|
|
|
|
for (int i = 0; i < 6; i++)
|
|
|
|
if (plr->tasks[i] == missionData->iTaskNum)
|
|
|
|
plr->tasks[i] = failTaskID;
|
|
|
|
return;
|
|
|
|
}
|
2020-10-21 05:24:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-24 21:04:56 +00:00
|
|
|
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_SUCC, response);
|
|
|
|
|
|
|
|
response.iTaskNum = missionData->iTaskNum;
|
2020-09-09 22:31:09 +00:00
|
|
|
|
2020-09-25 21:05:36 +00:00
|
|
|
if (!endTask(sock, missionData->iTaskNum)) {
|
2020-09-10 16:40:38 +00:00
|
|
|
return;
|
2020-09-10 13:01:35 +00:00
|
|
|
}
|
|
|
|
|
2020-09-25 21:05:36 +00:00
|
|
|
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_END_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_END_SUCC));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MissionManager::endTask(CNSocket *sock, int32_t taskNum) {
|
|
|
|
Player *plr = PlayerManager::getPlayer(sock);
|
|
|
|
|
|
|
|
if (Tasks.find(taskNum) == Tasks.end())
|
|
|
|
return false;
|
|
|
|
|
2020-09-10 16:40:38 +00:00
|
|
|
// ugly pointer/reference juggling for the sake of operator overloading...
|
2020-09-25 21:05:36 +00:00
|
|
|
TaskData& task = *Tasks[taskNum];
|
|
|
|
|
|
|
|
// mission rewards
|
|
|
|
if (Rewards.find(taskNum) != Rewards.end()) {
|
|
|
|
if (giveMissionReward(sock, taskNum) == -1)
|
|
|
|
return false; // we don't want to send anything
|
|
|
|
}
|
|
|
|
// don't take away quest items if we haven't finished the quest
|
2020-09-10 16:40:38 +00:00
|
|
|
|
2020-09-09 22:31:09 +00:00
|
|
|
/*
|
2020-09-10 21:40:17 +00:00
|
|
|
* Give (or take away) quest items
|
2020-09-09 22:31:09 +00:00
|
|
|
*
|
|
|
|
* Some mission tasks give the player a quest item upon completion.
|
|
|
|
* This is distinct from quest item mob drops.
|
|
|
|
* They can be identified by a counter in the task indicator (ie. 1/1 Gravity Decelerator).
|
|
|
|
* The server is responsible for dropping the correct item.
|
2020-09-10 21:40:17 +00:00
|
|
|
* Yes, this is pretty stupid.
|
|
|
|
*
|
|
|
|
* iSUInstancename is the number of items to give. It is usually negative at the end of
|
2020-09-16 18:14:00 +00:00
|
|
|
* a mission, to clean up its quest items.
|
2020-09-09 22:31:09 +00:00
|
|
|
*/
|
2020-09-21 19:43:53 +00:00
|
|
|
|
2020-09-10 16:40:38 +00:00
|
|
|
for (int i = 0; i < 3; i++)
|
|
|
|
if (task["m_iSUItem"][i] != 0)
|
2020-09-25 21:05:36 +00:00
|
|
|
dropQuestItem(sock, taskNum, task["m_iSUInstancename"][i], task["m_iSUItem"][i], 0);
|
2020-09-10 13:01:35 +00:00
|
|
|
|
2020-09-10 16:40:38 +00:00
|
|
|
// update player
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
2020-10-19 17:26:14 +00:00
|
|
|
if (plr->tasks[i] == taskNum) {
|
2020-09-10 16:40:38 +00:00
|
|
|
plr->tasks[i] = 0;
|
2020-09-21 19:43:53 +00:00
|
|
|
for (int j = 0; j < 3; j++) {
|
|
|
|
plr->RemainingNPCCount[i][j] = 0;
|
|
|
|
}
|
|
|
|
}
|
2020-09-10 16:40:38 +00:00
|
|
|
}
|
|
|
|
if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != 0) {
|
|
|
|
std::cout << "[WARN] Player completed non-active mission!?" << std::endl;
|
|
|
|
}
|
|
|
|
|
2020-09-13 18:45:51 +00:00
|
|
|
// if it's the last task
|
2020-10-19 17:26:14 +00:00
|
|
|
if (task["m_iSUOutgoingTask"] == 0) {
|
2020-09-14 14:03:30 +00:00
|
|
|
// save completed mission on player
|
2020-09-13 18:45:51 +00:00
|
|
|
saveMission(plr, (int)(task["m_iHMissionID"])-1);
|
2020-09-23 19:44:27 +00:00
|
|
|
|
|
|
|
// if it's a nano mission, reward the nano.
|
2020-09-29 21:27:48 +00:00
|
|
|
if (task["m_iSTNanoID"] != 0)
|
2020-09-26 01:48:45 +00:00
|
|
|
NanoManager::addNano(sock, task["m_iSTNanoID"], 0, true);
|
2020-09-23 19:44:27 +00:00
|
|
|
|
2020-09-21 19:43:53 +00:00
|
|
|
// remove current mission
|
|
|
|
plr->CurrentMissionID = 0;
|
2020-09-13 18:45:51 +00:00
|
|
|
}
|
|
|
|
|
2020-09-25 21:05:36 +00:00
|
|
|
return true;
|
2020-09-10 13:01:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void MissionManager::setMission(CNSocket* sock, CNPacketData* data) {
|
|
|
|
if (data->size != sizeof(sP_CL2FE_REQ_PC_SET_CURRENT_MISSION_ID))
|
|
|
|
return; // malformed packet
|
|
|
|
|
2020-09-28 18:11:13 +00:00
|
|
|
Player* plr = PlayerManager::getPlayer(sock);
|
2020-10-05 00:03:13 +00:00
|
|
|
|
2020-09-10 13:01:35 +00:00
|
|
|
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;
|
2020-09-21 19:43:53 +00:00
|
|
|
plr->CurrentMissionID = missionData->iCurrentMissionID;
|
2020-09-28 18:11:13 +00:00
|
|
|
|
|
|
|
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_SET_CURRENT_MISSION_ID, sizeof(sP_FE2CL_REP_PC_SET_CURRENT_MISSION_ID));
|
2020-09-10 13:01:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2020-10-14 18:36:38 +00:00
|
|
|
quitTask(sock, missionData->iTaskNum, true);
|
2020-09-25 05:35:27 +00:00
|
|
|
}
|
|
|
|
|
2020-10-14 18:36:38 +00:00
|
|
|
void MissionManager::quitTask(CNSocket* sock, int32_t taskNum, bool manual) {
|
2020-09-25 05:35:27 +00:00
|
|
|
Player* plr = PlayerManager::getPlayer(sock);
|
2020-09-10 21:40:17 +00:00
|
|
|
|
2020-12-01 19:18:01 +00:00
|
|
|
if (Tasks.find(taskNum) == Tasks.end())
|
2020-10-25 22:33:02 +00:00
|
|
|
return; // sanity check
|
2020-09-28 18:11:13 +00:00
|
|
|
|
2020-09-10 21:40:17 +00:00
|
|
|
// update player
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
2020-10-19 17:26:14 +00:00
|
|
|
if (plr->tasks[i] == taskNum) {
|
2020-09-10 21:40:17 +00:00
|
|
|
plr->tasks[i] = 0;
|
2020-09-21 19:43:53 +00:00
|
|
|
for (int j = 0; j < 3; j++) {
|
|
|
|
plr->RemainingNPCCount[i][j] = 0;
|
|
|
|
}
|
|
|
|
}
|
2020-09-10 21:40:17 +00:00
|
|
|
}
|
|
|
|
if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != 0) {
|
|
|
|
std::cout << "[WARN] Player quit non-active mission!?" << std::endl;
|
|
|
|
}
|
2020-09-21 19:43:53 +00:00
|
|
|
// remove current mission
|
|
|
|
plr->CurrentMissionID = 0;
|
2020-09-10 21:40:17 +00:00
|
|
|
|
2020-09-25 05:35:27 +00:00
|
|
|
TaskData& task = *Tasks[taskNum];
|
2020-09-10 21:40:17 +00:00
|
|
|
|
|
|
|
// clean up quest items
|
|
|
|
for (i = 0; i < 3; i++) {
|
2020-09-21 19:43:53 +00:00
|
|
|
if (task["m_iSUItem"][i] == 0 && task["m_iCSUItemID"][i] == 0)
|
2020-09-10 21:40:17 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* It's ok to do this only server-side, because the server decides which
|
2020-09-12 00:25:45 +00:00
|
|
|
* slot later items will be placed in.
|
2020-09-10 21:40:17 +00:00
|
|
|
*/
|
|
|
|
for (int j = 0; j < AQINVEN_COUNT; j++)
|
2020-09-21 19:43:53 +00:00
|
|
|
if (plr->QInven[j].iID == task["m_iSUItem"][i] || plr->QInven[j].iID == task["m_iCSUItemID"][i])
|
2020-09-10 21:40:17 +00:00
|
|
|
memset(&plr->QInven[j], 0, sizeof(sItemBase));
|
|
|
|
}
|
2020-09-10 13:01:35 +00:00
|
|
|
|
2020-10-14 18:36:38 +00:00
|
|
|
if (!manual) {
|
|
|
|
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_FAIL, failResp);
|
|
|
|
failResp.iErrorCode = 1;
|
|
|
|
failResp.iTaskNum = taskNum;
|
|
|
|
sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_TASK_END_FAIL, sizeof(sP_FE2CL_REP_PC_TASK_END_FAIL));
|
|
|
|
}
|
2020-10-19 17:26:14 +00:00
|
|
|
|
2020-10-14 18:36:38 +00:00
|
|
|
INITSTRUCT(sP_FE2CL_REP_PC_TASK_STOP_SUCC, response);
|
2020-09-25 05:35:27 +00:00
|
|
|
response.iTaskNum = taskNum;
|
2020-09-10 13:01:35 +00:00
|
|
|
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_STOP_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_STOP_SUCC));
|
|
|
|
}
|
|
|
|
|
2020-09-10 16:40:38 +00:00
|
|
|
int MissionManager::findQSlot(Player *plr, int id) {
|
2020-09-10 13:01:35 +00:00
|
|
|
int i;
|
|
|
|
|
2020-09-10 21:40:17 +00:00
|
|
|
// two passes. we mustn't fail to find an existing stack.
|
|
|
|
for (i = 0; i < AQINVEN_COUNT; i++)
|
|
|
|
if (plr->QInven[i].iID == id)
|
|
|
|
return i;
|
2020-09-10 13:01:35 +00:00
|
|
|
|
2020-09-10 21:40:17 +00:00
|
|
|
// no stack. start a new one.
|
2020-09-10 13:01:35 +00:00
|
|
|
for (i = 0; i < AQINVEN_COUNT; i++)
|
2020-09-10 21:40:17 +00:00
|
|
|
if (plr->QInven[i].iOpt == 0)
|
2020-09-10 13:01:35 +00:00
|
|
|
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
|
2020-10-05 00:03:13 +00:00
|
|
|
|
2020-09-28 18:11:13 +00:00
|
|
|
Player *plr = PlayerManager::getPlayer(sock);
|
|
|
|
|
2020-09-10 13:01:35 +00:00
|
|
|
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);
|
|
|
|
|
|
|
|
// find free quest item slot
|
2020-09-10 16:40:38 +00:00
|
|
|
int slot = findQSlot(plr, id);
|
2020-09-10 13:01:35 +00:00
|
|
|
if (slot == -1) {
|
|
|
|
// this should never happen
|
|
|
|
std::cout << "[WARN] Player has no room for quest item!?" << std::endl;
|
2020-09-09 19:09:01 +00:00
|
|
|
return;
|
|
|
|
}
|
2020-09-10 21:40:17 +00:00
|
|
|
if (id != 0)
|
|
|
|
std::cout << "new qitem in slot " << slot << std::endl;
|
2020-09-10 13:01:35 +00:00
|
|
|
|
|
|
|
// update player
|
2020-09-10 21:40:17 +00:00
|
|
|
if (id != 0) {
|
|
|
|
plr->QInven[slot].iType = 8;
|
|
|
|
plr->QInven[slot].iID = id;
|
|
|
|
plr->QInven[slot].iOpt += count; // stacking
|
|
|
|
}
|
|
|
|
|
|
|
|
// fully destory deleted items, for good measure
|
|
|
|
if (plr->QInven[slot].iOpt <= 0)
|
|
|
|
memset(&plr->QInven[slot], 0, sizeof(sItemBase));
|
2020-09-10 13:01:35 +00:00
|
|
|
|
|
|
|
// preserve stats
|
|
|
|
reward->m_iCandy = plr->money;
|
|
|
|
reward->m_iFusionMatter = plr->fusionmatter;
|
|
|
|
reward->iFatigue = 100; // prevents warning message
|
|
|
|
reward->iFatigue_Level = 1;
|
2020-10-07 17:29:59 +00:00
|
|
|
reward->m_iBatteryN = plr->batteryN;
|
|
|
|
reward->m_iBatteryW = plr->batteryW;
|
2020-09-10 13:01:35 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen);
|
|
|
|
}
|
|
|
|
|
2020-09-10 21:40:17 +00:00
|
|
|
int MissionManager::giveMissionReward(CNSocket *sock, int task) {
|
2020-09-10 13:01:35 +00:00
|
|
|
Reward *reward = Rewards[task];
|
|
|
|
Player *plr = PlayerManager::getPlayer(sock);
|
2020-09-09 19:09:01 +00:00
|
|
|
|
|
|
|
int nrewards = 0;
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
if (reward->itemIds[i] != 0)
|
|
|
|
nrewards++;
|
|
|
|
}
|
|
|
|
|
|
|
|
int slots[4];
|
|
|
|
for (int i = 0; i < nrewards; i++) {
|
|
|
|
slots[i] = ItemManager::findFreeSlot(plr);
|
|
|
|
if (slots[i] == -1) {
|
|
|
|
std::cout << "Not enough room to complete task" << std::endl;
|
|
|
|
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_FAIL, fail);
|
|
|
|
|
2020-09-10 13:01:35 +00:00
|
|
|
fail.iTaskNum = task;
|
2020-09-09 19:09:01 +00:00
|
|
|
fail.iErrorCode = 13; // inventory full
|
|
|
|
|
|
|
|
sock->sendPacket((void*)&fail, P_FE2CL_REP_PC_TASK_END_FAIL, sizeof(sP_FE2CL_REP_PC_TASK_END_FAIL));
|
2020-11-19 19:07:29 +00:00
|
|
|
|
|
|
|
// delete any temp items we might have set
|
|
|
|
for (int j = 0; j < i; j++) {
|
|
|
|
plr->Inven[slots[j]] = { 0, 0, 0, 0 }; // empty
|
|
|
|
}
|
2020-09-10 21:40:17 +00:00
|
|
|
return -1;
|
2020-09-09 19:09:01 +00:00
|
|
|
}
|
2020-11-19 19:07:29 +00:00
|
|
|
|
|
|
|
plr->Inven[slots[i]] = { 999, 999, 999, 0 }; // temp item; overwritten later
|
2020-09-09 19:09:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
|
|
|
size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + nrewards * sizeof(sItemReward);
|
|
|
|
assert(resplen < CN_PACKET_BUFFER_SIZE);
|
|
|
|
sP_FE2CL_REP_REWARD_ITEM *resp = (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);
|
|
|
|
|
|
|
|
// update player
|
|
|
|
plr->money += reward->money;
|
2020-11-23 23:42:34 +00:00
|
|
|
if (plr->iConditionBitFlag & CSB_BIT_REWARD_CASH) { // nano boost for taros
|
|
|
|
int boost = 0;
|
2020-11-25 00:16:06 +00:00
|
|
|
if (NanoManager::getNanoBoost(plr)) // for gumballs
|
2020-11-23 23:42:34 +00:00
|
|
|
boost = 1;
|
|
|
|
plr->money += reward->money * (5 + boost) / 25;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (plr->iConditionBitFlag & CSB_BIT_REWARD_BLOB) { // nano boost for fm
|
|
|
|
int boost = 0;
|
2020-11-25 00:16:06 +00:00
|
|
|
if (NanoManager::getNanoBoost(plr)) // for gumballs
|
2020-11-23 23:42:34 +00:00
|
|
|
boost = 1;
|
|
|
|
updateFusionMatter(sock, reward->fusionmatter * (30 + boost) / 25);
|
|
|
|
} else
|
|
|
|
updateFusionMatter(sock, reward->fusionmatter);
|
2020-09-09 19:09:01 +00:00
|
|
|
|
|
|
|
// simple rewards
|
|
|
|
resp->m_iCandy = plr->money;
|
|
|
|
resp->m_iFusionMatter = plr->fusionmatter;
|
|
|
|
resp->iFatigue = 100; // prevents warning message
|
|
|
|
resp->iFatigue_Level = 1;
|
|
|
|
resp->iItemCnt = nrewards;
|
2020-10-07 17:29:59 +00:00
|
|
|
resp->m_iBatteryN = plr->batteryN;
|
|
|
|
resp->m_iBatteryW = plr->batteryW;
|
2020-09-09 19:09:01 +00:00
|
|
|
|
|
|
|
for (int i = 0; i < nrewards; i++) {
|
|
|
|
item[i].sItem.iType = reward->itemTypes[i];
|
|
|
|
item[i].sItem.iID = reward->itemIds[i];
|
|
|
|
item[i].iSlotNum = slots[i];
|
|
|
|
item[i].eIL = 1;
|
|
|
|
|
2020-11-19 19:07:29 +00:00
|
|
|
// update player inventory, overwriting temporary item
|
|
|
|
plr->Inven[slots[i]] = item[i].sItem;
|
2020-09-09 19:09:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen);
|
2020-09-23 19:44:27 +00:00
|
|
|
|
2020-09-10 21:40:17 +00:00
|
|
|
return 0;
|
2020-08-24 21:04:56 +00:00
|
|
|
}
|
2020-09-10 16:40:38 +00:00
|
|
|
|
2020-09-23 21:04:58 +00:00
|
|
|
void MissionManager::updateFusionMatter(CNSocket* sock, int fusion) {
|
2020-09-23 19:44:27 +00:00
|
|
|
Player *plr = PlayerManager::getPlayer(sock);
|
|
|
|
|
2020-09-23 21:04:58 +00:00
|
|
|
plr->fusionmatter += fusion;
|
|
|
|
|
2020-09-26 01:48:45 +00:00
|
|
|
// there's a much lower FM cap in the Future
|
2020-12-06 16:53:41 +00:00
|
|
|
int fmCap = AvatarGrowth[plr->level]["m_iFMLimit"];
|
|
|
|
if (plr->fusionmatter > fmCap)
|
|
|
|
plr->fusionmatter = fmCap;
|
2020-09-26 01:48:45 +00:00
|
|
|
else if (plr->fusionmatter < 0) // if somehow lowered too far
|
|
|
|
plr->fusionmatter = 0;
|
2020-09-25 21:05:36 +00:00
|
|
|
|
2020-12-06 16:53:41 +00:00
|
|
|
// don't run nano mission logic at level 36
|
|
|
|
if (plr->level >= 36)
|
|
|
|
return;
|
|
|
|
|
2020-09-26 01:48:45 +00:00
|
|
|
// check if it is enough for the nano mission
|
2020-12-06 16:53:41 +00:00
|
|
|
int fmNano = AvatarGrowth[plr->level]["m_iReqBlob_NanoCreate"];
|
|
|
|
if (plr->fusionmatter < fmNano)
|
2020-09-25 21:05:36 +00:00
|
|
|
return;
|
2020-09-23 19:44:27 +00:00
|
|
|
|
2020-09-26 01:48:45 +00:00
|
|
|
// don't give the Blossom nano mission until the player's in the Past
|
|
|
|
if (plr->level == 4 && plr->PCStyle2.iPayzoneFlag == 0)
|
|
|
|
return;
|
|
|
|
|
2020-09-25 21:05:36 +00:00
|
|
|
// check if the nano task is already started
|
|
|
|
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
|
|
|
TaskData& task = *Tasks[plr->tasks[i]];
|
|
|
|
if (task["m_iSTNanoID"] != 0)
|
|
|
|
return; // nano mission was already started!
|
|
|
|
}
|
2020-09-23 19:44:27 +00:00
|
|
|
|
2020-09-25 21:05:36 +00:00
|
|
|
// start the nano mission
|
|
|
|
startTask(plr, AvatarGrowth[plr->level]["m_iNanoQuestTaskID"], true);
|
2020-09-23 19:44:27 +00:00
|
|
|
|
2020-09-25 21:05:36 +00:00
|
|
|
INITSTRUCT(sP_FE2CL_REP_PC_TASK_START_SUCC, response);
|
|
|
|
response.iTaskNum = AvatarGrowth[plr->level]["m_iNanoQuestTaskID"];
|
|
|
|
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC));
|
2020-12-01 01:36:20 +00:00
|
|
|
|
|
|
|
// play the beam animation for other players
|
|
|
|
INITSTRUCT(sP_FE2CL_PC_EVENT, bcast);
|
|
|
|
bcast.iEventID = 1; // beam effect
|
|
|
|
bcast.iPC_ID = plr->iID;
|
|
|
|
PlayerManager::sendToViewable(sock, (void*)&bcast, P_FE2CL_PC_EVENT, sizeof(sP_FE2CL_PC_EVENT));
|
2020-09-23 19:44:27 +00:00
|
|
|
}
|
|
|
|
|
2020-09-10 16:40:38 +00:00
|
|
|
void MissionManager::mobKilled(CNSocket *sock, int mobid) {
|
|
|
|
Player *plr = PlayerManager::getPlayer(sock);
|
2020-09-28 18:11:13 +00:00
|
|
|
|
2020-09-10 21:40:17 +00:00
|
|
|
bool missionmob = false;
|
2020-09-10 16:40:38 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2020-09-10 21:40:17 +00:00
|
|
|
// acknowledge killing of mission mob...
|
2020-10-19 17:26:14 +00:00
|
|
|
if (task["m_iCSUNumToKill"][j] != 0) {
|
2020-09-10 21:40:17 +00:00
|
|
|
missionmob = true;
|
2020-11-25 18:33:12 +00:00
|
|
|
if (plr->RemainingNPCCount[i][j] > 0) {
|
2020-09-22 08:18:29 +00:00
|
|
|
plr->RemainingNPCCount[i][j]--;
|
|
|
|
}
|
2020-09-21 19:43:53 +00:00
|
|
|
}
|
2020-09-10 16:40:38 +00:00
|
|
|
// drop quest item
|
2020-09-21 19:43:53 +00:00
|
|
|
if (task["m_iCSUItemNumNeeded"][j] != 0 && !isQuestItemFull(sock, task["m_iCSUItemID"][j], task["m_iCSUItemNumNeeded"][j]) ) {
|
2020-09-10 16:40:38 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-09-10 21:40:17 +00:00
|
|
|
|
|
|
|
// ...but only once
|
|
|
|
// XXX: is it actually necessary to do it this way?
|
|
|
|
if (missionmob) {
|
|
|
|
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));
|
|
|
|
}
|
2020-09-10 16:40:38 +00:00
|
|
|
}
|
2020-09-13 18:45:51 +00:00
|
|
|
|
|
|
|
void MissionManager::saveMission(Player* player, int missionId) {
|
2020-10-19 17:26:14 +00:00
|
|
|
// sanity check missionID so we don't get exceptions
|
2020-09-21 19:43:53 +00:00
|
|
|
if (missionId < 0 || missionId>1023) {
|
|
|
|
std::cout << "[WARN] Client submitted invalid missionId: " <<missionId<< std::endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-09-25 21:05:36 +00:00
|
|
|
// Missions are stored in int64_t array
|
2020-09-13 18:45:51 +00:00
|
|
|
int row = missionId / 64;
|
2020-09-14 13:20:55 +00:00
|
|
|
int column = missionId % 64;
|
|
|
|
player->aQuestFlag[row] |= (1ULL << column);
|
2020-09-13 18:45:51 +00:00
|
|
|
}
|
2020-09-21 19:43:53 +00:00
|
|
|
|
|
|
|
bool MissionManager::isQuestItemFull(CNSocket* sock, int itemId, int itemCount) {
|
|
|
|
Player* plr = PlayerManager::getPlayer(sock);
|
2020-09-28 18:11:13 +00:00
|
|
|
|
2020-09-21 19:43:53 +00:00
|
|
|
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);
|
|
|
|
}
|
2020-10-14 04:26:30 +00:00
|
|
|
|
|
|
|
void MissionManager::failInstancedMissions(CNSocket* sock) {
|
|
|
|
// loop through all tasks; if the required instance is being left, "fail" the task
|
|
|
|
Player* plr = PlayerManager::getPlayer(sock);
|
2020-10-14 18:36:38 +00:00
|
|
|
for (int i = 0; i < 6; i++) {
|
|
|
|
int taskNum = plr->tasks[i];
|
2020-10-14 04:26:30 +00:00
|
|
|
if (MissionManager::Tasks.find(taskNum) == MissionManager::Tasks.end())
|
|
|
|
continue; // sanity check
|
|
|
|
|
|
|
|
TaskData* task = MissionManager::Tasks[taskNum];
|
2020-10-14 18:36:38 +00:00
|
|
|
if (task->task["m_iRequireInstanceID"] != 0) { // mission is instanced
|
2020-10-14 04:26:30 +00:00
|
|
|
int failTaskID = task->task["m_iFOutgoingTask"];
|
|
|
|
if (failTaskID != 0) {
|
2020-10-14 18:36:38 +00:00
|
|
|
MissionManager::quitTask(sock, taskNum, false);
|
|
|
|
plr->tasks[i] = failTaskID;
|
2020-10-14 04:26:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|