diff --git a/src/CNShardServer.cpp b/src/CNShardServer.cpp index f87a987..c0c8cd7 100644 --- a/src/CNShardServer.cpp +++ b/src/CNShardServer.cpp @@ -2,6 +2,7 @@ #include "CNStructs.hpp" #include "CNShardServer.hpp" #include "PlayerManager.hpp" +#include "MobManager.hpp" #include "CNShared.hpp" #include "settings.hpp" #include "Database.hpp" @@ -90,4 +91,6 @@ void CNShardServer::onStep() { event.scheduledEvent = currTime + event.delta; } } + + MobManager::step(currTime); } diff --git a/src/MobManager.cpp b/src/MobManager.cpp index 3cda968..3cb7ec8 100644 --- a/src/MobManager.cpp +++ b/src/MobManager.cpp @@ -6,6 +6,8 @@ #include +std::map MobManager::Mobs; + void MobManager::init() { REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ATTACK_NPCs, pcAttackNpcs); @@ -48,24 +50,23 @@ void MobManager::pcAttackNpcs(CNSocket *sock, CNPacketData *data) { resp->iNPCCnt = pkt->iNPCCnt; for (int i = 0; i < pkt->iNPCCnt; i++) { - if (NPCManager::NPCs.find(pktdata[i]) == NPCManager::NPCs.end()) { + if (Mobs.find(pktdata[i]) == Mobs.end()) { // not sure how to best handle this std::cout << "[WARN] pcAttackNpcs: mob ID not found" << std::endl; return; } - BaseNPC& mob = NPCManager::NPCs[pktdata[i]]; + Mob *mob = Mobs[pktdata[i]]; - mob.appearanceData.iHP -= 100; + mob->appearanceData.iHP -= 100; - if (mob.appearanceData.iHP <= 0) { - giveReward(sock); - MissionManager::mobKilled(sock, mob.appearanceData.iNPCType); - // TODO: despawn mobs when they die - } + std::cout << "mob health is now " << mob->appearanceData.iHP << std::endl; - respdata[i].iID = mob.appearanceData.iNPC_ID; + if (mob->appearanceData.iHP <= 0) + killMob(sock, mob); + + respdata[i].iID = mob->appearanceData.iNPC_ID; respdata[i].iDamage = 100; - respdata[i].iHP = mob.appearanceData.iHP; + respdata[i].iHP = mob->appearanceData.iHP; respdata[i].iHitFlag = 2; // hitscan, not a rocket or a grenade } @@ -129,3 +130,61 @@ void MobManager::giveReward(CNSocket *sock) { sock->sendPacket((void*)respbuf, P_FE2CL_REP_REWARD_ITEM, resplen); } } + +void MobManager::killMob(CNSocket *sock, Mob *mob) { + mob->state = MobState::DEAD; + mob->killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step? + + std::cout << "killed mob " << mob->appearanceData.iNPC_ID << std::endl; + + giveReward(sock); + MissionManager::mobKilled(sock, mob->appearanceData.iNPCType); + + PlayerView& plrv = PlayerManager::players[sock]; + + INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt); + + pkt.iNPC_ID = mob->appearanceData.iNPC_ID; + + sock->sendPacket(&pkt, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT)); + for (CNSocket *s : plrv.viewable) + s->sendPacket(&pkt, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT)); +} + +void MobManager::deadStep(Mob *mob, time_t currTime) { + if (mob->killedTime != 0 && currTime - mob->killedTime < mob->regenTime * 100) + return; + + std::cout << "respawning mob " << mob->appearanceData.iNPC_ID << " with HP = " << mob->maxHealth << std::endl; + + mob->appearanceData.iHP = mob->maxHealth; + mob->state = MobState::ROAMING; + + INITSTRUCT(sP_FE2CL_NPC_NEW, pkt); + + pkt.NPCAppearanceData = mob->appearanceData; + + // FIXME: use the chunk's visibility list, when that becomes a thing + for (auto& pair : PlayerManager::players) { + Player *plr = pair.second.plr; + + int diffX = abs(plr->x - mob->appearanceData.iX); + int diffY = abs(plr->y - mob->appearanceData.iY); + + if (diffX < settings::PLAYERDISTANCE && diffY < settings::PLAYERDISTANCE) + pair.first->sendPacket(&pkt, P_FE2CL_NPC_NEW, sizeof(sP_FE2CL_NPC_NEW)); + } +} + +void MobManager::step(time_t currTime) { + for (auto& pair : Mobs) { + switch (pair.second->state) { + case MobState::DEAD: + deadStep(pair.second, currTime); + break; + default: + // unhandled for now + break; + } + } +} diff --git a/src/MobManager.hpp b/src/MobManager.hpp index dde0d23..f15e4e5 100644 --- a/src/MobManager.hpp +++ b/src/MobManager.hpp @@ -3,14 +3,46 @@ #include "CNProtocol.hpp" #include "CNShared.hpp" #include "CNShardServer.hpp" +#include "NPC.hpp" + +#include + +enum class MobState { + INACTIVE, + ROAMING, + COMBAT, + RETREAT, + DEAD +}; + +struct Mob : public BaseNPC { + MobState state; + const int maxHealth; + time_t killedTime = 0; + const int regenTime; + + Mob(int x, int y, int z, int type, int hp, int angle, int rt) + : BaseNPC(x, y, z, type), maxHealth(hp), regenTime(rt) { + state = MobState::ROAMING; + + // NOTE: there appear to be discrepancies in the dump + appearanceData.iHP = maxHealth; + } +}; namespace MobManager { + extern std::map Mobs; + void init(); + void step(time_t); + + void deadStep(Mob*, time_t); void pcAttackNpcs(CNSocket *sock, CNPacketData *data); void combatBegin(CNSocket *sock, CNPacketData *data); void combatEnd(CNSocket *sock, CNPacketData *data); void dotDamageOnOff(CNSocket *sock, CNPacketData *data); + void killMob(CNSocket *sock, Mob *mob); void giveReward(CNSocket *sock); } diff --git a/src/NPC.hpp b/src/NPC.hpp index 3381cd0..371d420 100644 --- a/src/NPC.hpp +++ b/src/NPC.hpp @@ -20,18 +20,4 @@ public: // hopefully no collisions happen :eyes: appearanceData.iNPC_ID = (int32_t)rand(); }; - - BaseNPC(int x, int y, int z, int type, int hp, int cond, int angle, int barker) { - appearanceData.iX = x; - appearanceData.iY = y; - appearanceData.iZ = z; - appearanceData.iNPCType = type; - appearanceData.iHP = hp; - appearanceData.iAngle = angle; - appearanceData.iConditionBitFlag = cond; - appearanceData.iBarkerType = barker; - - // hopefully no collisions happen :eyes: - appearanceData.iNPC_ID = (int32_t)rand(); - } }; diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index 9e8d617..932079d 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -1,6 +1,7 @@ #include "NPCManager.hpp" #include "ItemManager.hpp" #include "settings.hpp" +#include "MobManager.hpp" #include #include @@ -10,7 +11,7 @@ #include "contrib/JSON.hpp" -std::map NPCManager::NPCs; +std::map NPCManager::NPCs; std::map NPCManager::Warps; std::vector NPCManager::RespawnPoints; @@ -255,10 +256,17 @@ void NPCManager::updatePlayerNPCS(CNSocket* sock, PlayerView& view) { std::list noView; for (auto& pair : NPCs) { - int diffX = abs(view.plr->x - pair.second.appearanceData.iX); - int diffY = abs(view.plr->y - pair.second.appearanceData.iY); + int diffX = abs(view.plr->x - pair.second->appearanceData.iX); + int diffY = abs(view.plr->y - pair.second->appearanceData.iY); if (diffX < settings::NPCDISTANCE && diffY < settings::NPCDISTANCE) { + // if it's a mob and it's dead (or otherwise not there), skip it. + if (MobManager::Mobs.find(pair.first) != MobManager::Mobs.end()) { + Mob *mob = (Mob*) pair.second; + if (mob->state == MobState::DEAD || mob->state == MobState::INACTIVE) + continue; + } + yesView.push_back(pair.first); } else { @@ -290,7 +298,7 @@ void NPCManager::updatePlayerNPCS(CNSocket* sock, PlayerView& view) { if (std::find(view.viewableNPCs.begin(), view.viewableNPCs.end(), id) == view.viewableNPCs.end()) { // needs to be added to viewableNPCs! send NPC_ENTER - enterData.NPCAppearanceData = NPCs[id].appearanceData; + enterData.NPCAppearanceData = NPCs[id]->appearanceData; sock->sendPacket((void*)&enterData, P_FE2CL_NPC_ENTER, sizeof(sP_FE2CL_NPC_ENTER)); // add to viewable diff --git a/src/NPCManager.hpp b/src/NPCManager.hpp index 9231442..1470994 100644 --- a/src/NPCManager.hpp +++ b/src/NPCManager.hpp @@ -15,7 +15,7 @@ struct WarpLocation { }; namespace NPCManager { - extern std::map NPCs; + extern std::map NPCs; extern std::map Warps; extern std::vector RespawnPoints; void init(); diff --git a/src/TableData.cpp b/src/TableData.cpp index dbe6de0..2c40efb 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -4,6 +4,7 @@ #include "ItemManager.hpp" #include "settings.hpp" #include "MissionManager.hpp" +#include "MobManager.hpp" #include "contrib/JSON.hpp" @@ -20,17 +21,18 @@ void TableData::init() { // read file into json inFile >> npcData; - for (nlohmann::json::iterator npc = npcData.begin(); npc != npcData.end(); npc++) { - BaseNPC tmp(npc.value()["x"], npc.value()["y"], npc.value()["z"], npc.value()["id"]); + for (nlohmann::json::iterator _npc = npcData.begin(); _npc != npcData.end(); _npc++) { + auto npc = _npc.value(); + BaseNPC *tmp = new BaseNPC(npc["x"], npc["y"], npc["z"], npc["id"]); // Temporary fix, IDs will be pulled from json later - tmp.appearanceData.iNPC_ID = i; + tmp->appearanceData.iNPC_ID = i; i++; - NPCManager::NPCs[tmp.appearanceData.iNPC_ID] = tmp; + NPCManager::NPCs[tmp->appearanceData.iNPC_ID] = tmp; - if (npc.value()["id"] == 641 || npc.value()["id"] == 642) - NPCManager::RespawnPoints.push_back({ npc.value()["x"], npc.value()["y"], ((int)npc.value()["z"]) + RESURRECT_HEIGHT }); + if (npc["id"] == 641 || npc["id"] == 642) + NPCManager::RespawnPoints.push_back({ npc["x"], npc["y"], ((int)npc["z"]) + RESURRECT_HEIGHT }); } } @@ -38,31 +40,6 @@ void TableData::init() { std::cerr << "[WARN] Malformed NPCs.json file! Reason:" << err.what() << std::endl; } - // load temporary mob dump - try { - std::ifstream inFile(settings::MOBJSON); - nlohmann::json npcData; - - // read file into json - inFile >> npcData; - - for (nlohmann::json::iterator npc = npcData.begin(); npc != npcData.end(); npc++) { - BaseNPC tmp(npc.value()["iX"], npc.value()["iY"], npc.value()["iZ"], npc.value()["iNPCType"], - npc.value()["iHP"], npc.value()["iConditionBitFlag"], npc.value()["iAngle"], npc.value()["iBarkerType"]); - - // Temporary fix, IDs will be pulled from json later - tmp.appearanceData.iNPC_ID = i; - i++; - - NPCManager::NPCs[tmp.appearanceData.iNPC_ID] = tmp; - } - - 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; - } - // load everything else from xdttable std::cout << "[INFO] Parsing xdt.json..." << std::endl; std::ifstream infile(settings::XDTJSON); @@ -75,9 +52,10 @@ void TableData::init() { // load warps nlohmann::json warpData = xdtData["m_pInstanceTable"]["m_pWarpData"]; - for (nlohmann::json::iterator warp = warpData.begin(); warp != warpData.end(); warp++) { - WarpLocation warpLoc = { warp.value()["m_iToX"], warp.value()["m_iToY"], warp.value()["m_iToZ"] }; - int warpID = warp.value()["m_iWarpNumber"]; + for (nlohmann::json::iterator _warp = warpData.begin(); _warp != warpData.end(); _warp++) { + auto warp = _warp.value(); + WarpLocation warpLoc = { warp["m_iToX"], warp["m_iToY"], warp["m_iToZ"] }; + int warpID = warp["m_iWarpNumber"]; NPCManager::Warps[warpID] = warpLoc; } @@ -87,16 +65,18 @@ void TableData::init() { nlohmann::json transRouteData = xdtData["m_pTransportationTable"]["m_pTransportationData"]; nlohmann::json transLocData = xdtData["m_pTransportationTable"]["m_pTransportationWarpLocation"]; - for (nlohmann::json::iterator tLoc = transLocData.begin(); tLoc != transLocData.end(); tLoc++) { - TransportLocation transLoc = { tLoc.value()["m_iNPCID"], tLoc.value()["m_iXpos"], tLoc.value()["m_iYpos"], tLoc.value()["m_iZpos"] }; - TransportManager::Locations[tLoc.value()["m_iLocationID"]] = transLoc; + for (nlohmann::json::iterator _tLoc = transLocData.begin(); _tLoc != transLocData.end(); _tLoc++) { + auto tLoc = _tLoc.value(); + TransportLocation transLoc = { tLoc["m_iNPCID"], tLoc["m_iXpos"], tLoc["m_iYpos"], tLoc["m_iZpos"] }; + TransportManager::Locations[tLoc["m_iLocationID"]] = transLoc; } std::cout << "[INFO] Loaded " << TransportManager::Locations.size() << " S.C.A.M.P.E.R. locations" << std::endl; // TODO: Skyway operates differently - for (nlohmann::json::iterator tRoute = transRouteData.begin(); tRoute != transRouteData.end(); tRoute++) { - TransportRoute transRoute = { tRoute.value()["m_iMoveType"], tRoute.value()["m_iStartLocation"], tRoute.value()["m_iEndLocation"], - tRoute.value()["m_iCost"] , tRoute.value()["m_iSpeed"], tRoute.value()["m_iRouteNum"] }; - TransportManager::Routes[tRoute.value()["m_iVehicleID"]] = transRoute; + for (nlohmann::json::iterator _tRoute = transRouteData.begin(); _tRoute != transRouteData.end(); _tRoute++) { + auto tRoute = _tRoute.value(); + TransportRoute transRoute = { tRoute["m_iMoveType"], tRoute["m_iStartLocation"], tRoute["m_iEndLocation"], + tRoute["m_iCost"] , tRoute["m_iSpeed"], tRoute["m_iRouteNum"] }; + TransportManager::Routes[tRoute["m_iVehicleID"]] = transRoute; } std::cout << "[INFO] Loaded " << TransportManager::Routes.size() << " transportation routes" << std::endl; @@ -128,9 +108,12 @@ void TableData::init() { nlohmann::json itemSet; for (int i = 0; i < 12; i++) { itemSet = xdtData[setNames[i]]["m_pItemData"]; - for (nlohmann::json::iterator item = itemSet.begin(); item != itemSet.end(); item++) - ItemManager::ItemData[std::pair(item.value()["m_iItemNumber"], i == 11 ? 9 : (i == 10 ? 7 : (int)item.value()["m_iEquipLoc"]))] - = { item.value()["m_iTradeAble"] == 1, item.value()["m_iSellAble"] == 1, item.value()["m_iItemPrice"], item.value()["m_iItemSellPrice"], item.value()["m_iStackNumber"], i > 9 ? 0 : (int)item.value()["m_iMinReqLev"] }; + + for (nlohmann::json::iterator _item = itemSet.begin(); _item != itemSet.end(); _item++) { + auto item = _item.value(); + ItemManager::ItemData[std::pair(item["m_iItemNumber"], i == 11 ? 9 : (i == 10 ? 7 : (int)item["m_iEquipLoc"]))] + = { item["m_iTradeAble"] == 1, item["m_iSellAble"] == 1, item["m_iItemPrice"], item["m_iItemSellPrice"], item["m_iStackNumber"], i > 9 ? 0 : (int)item["m_iMinReqLev"] }; + } } std::cout << "[INFO] Loaded " << ItemManager::ItemData.size() << " items" << std::endl; @@ -138,9 +121,10 @@ void TableData::init() { // load vendor listings nlohmann::json listings = xdtData["m_pVendorTable"]["m_pItemData"]; - for (nlohmann::json::iterator listing = listings.begin(); listing != listings.end(); listing++) { - VendorListing vListing = { listing.value()["m_iSortNumber"], listing.value()["m_iItemType"], listing.value()["m_iitemID"] }; - ItemManager::VendorTables[listing.value()["m_iNpcNumber"]].push_back(vListing); + for (nlohmann::json::iterator _lst = listings.begin(); _lst != listings.end(); _lst++) { + auto lst = _lst.value(); + VendorListing vListing = { lst["m_iSortNumber"], lst["m_iItemType"], lst["m_iitemID"] }; + ItemManager::VendorTables[lst["m_iNpcNumber"]].push_back(vListing); } std::cout << "[INFO] Loaded " << ItemManager::VendorTables.size() << " vendor tables" << std::endl; @@ -148,6 +132,36 @@ void TableData::init() { catch (const std::exception& err) { std::cerr << "[WARN] Malformed xdt.json file! Reason:" << err.what() << std::endl; } + + // load temporary mob dump + try { + std::ifstream inFile(settings::MOBJSON); + nlohmann::json npcData; + + // read file into json + inFile >> npcData; + + nlohmann::json npcTableData = xdtData["m_pNpcTable"]["m_pNpcData"]; + + for (nlohmann::json::iterator _npc = npcData.begin(); _npc != npcData.end(); _npc++) { + auto npc = _npc.value(); + auto td = npcTableData[(int)npc["iNPCType"]]; + Mob *tmp = new Mob(npc["iX"], npc["iY"], npc["iZ"], npc["iNPCType"], npc["iHP"], npc["iAngle"], td["m_iRegenTime"]); + + // Temporary fix, IDs will be pulled from json later + tmp->appearanceData.iNPC_ID = i; + + NPCManager::NPCs[i] = tmp; + MobManager::Mobs[i] = (Mob*)NPCManager::NPCs[i]; + + i++; + } + + 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; + } } void TableData::cleanup() { @@ -159,4 +173,6 @@ void TableData::cleanup() { delete pair.second; for (auto& pair : MissionManager::Tasks) delete pair.second; + for (auto& pair : NPCManager::NPCs) + delete pair.second; }