diff --git a/src/ChatManager.cpp b/src/ChatManager.cpp index fd902d4..031d036 100644 --- a/src/ChatManager.cpp +++ b/src/ChatManager.cpp @@ -4,11 +4,13 @@ #include "PlayerManager.hpp" #include "TransportManager.hpp" #include "TableData.hpp" -#include "limits.h" +#include "NPCManager.hpp" +#include "MobManager.hpp" #include #include #include +#include std::map ChatManager::commands; @@ -184,11 +186,87 @@ void mssCommand(std::string full, std::vector& args, CNSocket* sock // mss ???? ChatManager::sendServerMessage(sock, "[MSS] Unknown command '" + args[2] + "'"); +} +void summonWCommand(std::string full, std::vector& args, CNSocket* sock) { + Player* plr = PlayerManager::getPlayer(sock); + + char *rest; + int type = std::strtol(args[1].c_str(), &rest, 10); + if (*rest) { + ChatManager::sendServerMessage(sock, "Invalid NPC number: " + args[1]); + return; + } + + // permission & sanity check + if (plr == nullptr || type >= 3314) + return; + + int team = NPCManager::NPCData[type]["m_iTeam"]; + + assert(NPCManager::nextId < INT32_MAX); + + BaseNPC *npc = nullptr; + if (team == 2) { + npc = new Mob(plr->x, plr->y, plr->z, plr->instanceID, type, NPCManager::NPCData[type], NPCManager::nextId++); + npc->appearanceData.iAngle = (plr->angle + 180) % 360; + + NPCManager::NPCs[npc->appearanceData.iNPC_ID] = npc; + MobManager::Mobs[npc->appearanceData.iNPC_ID] = (Mob*)npc; + + // re-enable respawning + ((Mob*)npc)->summoned = false; + } else { + ChatManager::sendServerMessage(sock, "Error: /summonW only supports Mobs at this time."); + return; + } + + NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, plr->x, plr->y, plr->z); + + ChatManager::sendServerMessage(sock, "/summonW: placed mob with type: " + std::to_string(type) + + ", id: " + std::to_string(npc->appearanceData.iNPC_ID)); + TableData::RunningMobs[npc->appearanceData.iNPC_ID] = npc; +} + +void unsummonWCommand(std::string full, std::vector& args, CNSocket* sock) { + PlayerView& plrv = PlayerManager::players[sock]; + Player* plr = plrv.plr; + + // shamelessly stolen from npcRotateCommand() + BaseNPC* npc = nullptr; + int lastDist = INT_MAX; + for (auto c = plrv.currentChunks.begin(); c != plrv.currentChunks.end(); c++) { + Chunk* chunk = *c; + for (auto _npc = chunk->NPCs.begin(); _npc != chunk->NPCs.end(); _npc++) { + BaseNPC* npcTemp = NPCManager::NPCs[*_npc]; + int distXY = std::hypot(plr->x - npcTemp->appearanceData.iX, plr->y - npcTemp->appearanceData.iY); + int dist = std::hypot(distXY, plr->z - npcTemp->appearanceData.iZ); + if (dist < lastDist) { + npc = npcTemp; + lastDist = dist; + } + } + } + + if (npc == nullptr) { + ChatManager::sendServerMessage(sock, "/unsummonW: No NPCs found nearby"); + return; + } + + if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) == TableData::RunningMobs.end()) { + ChatManager::sendServerMessage(sock, "/unsummonW: Closest NPC is not a gruntwork mob."); + return; + } + + ChatManager::sendServerMessage(sock, "/unsummonW: removed mob with type: " + std::to_string(npc->appearanceData.iNPCType) + + ", id: " + std::to_string(npc->appearanceData.iNPC_ID)); + + TableData::RunningMobs.erase(npc->appearanceData.iNPC_ID); + + NPCManager::destroyNPC(npc->appearanceData.iNPC_ID); } void npcRotateCommand(std::string full, std::vector& args, CNSocket* sock) { - PlayerView& plrv = PlayerManager::players[sock]; Player* plr = plrv.plr; @@ -230,8 +308,8 @@ void refreshCommand(std::string full, std::vector& args, CNSocket* } void flushCommand(std::string full, std::vector& args, CNSocket* sock) { - ChatManager::sendServerMessage(sock, "Wrote gruntwork to " + settings::GRUNTWORKJSON); TableData::flush(); + ChatManager::sendServerMessage(sock, "Wrote gruntwork to " + settings::GRUNTWORKJSON); } void ChatManager::init() { @@ -244,6 +322,8 @@ void ChatManager::init() { // TODO: add help command registerCommand("mss", 30, mssCommand); registerCommand("npcr", 30, npcRotateCommand); + registerCommand("summonW", 30, summonWCommand); + registerCommand("unsummonW", 30, unsummonWCommand); registerCommand("flush", 30, flushCommand); registerCommand("level", 50, levelCommand); registerCommand("population", 100, populationCommand); diff --git a/src/MissionManager.cpp b/src/MissionManager.cpp index 73f1666..a34736a 100644 --- a/src/MissionManager.cpp +++ b/src/MissionManager.cpp @@ -306,6 +306,8 @@ void MissionManager::dropQuestItem(CNSocket *sock, int task, int count, int id, reward->m_iFusionMatter = plr->fusionmatter; reward->iFatigue = 100; // prevents warning message reward->iFatigue_Level = 1; + reward->m_iBatteryN = plr->batteryN; + reward->m_iBatteryW = plr->batteryW; reward->iItemCnt = 1; // remember to update resplen if you change this reward->iTaskID = task; @@ -365,6 +367,8 @@ int MissionManager::giveMissionReward(CNSocket *sock, int task) { resp->iFatigue = 100; // prevents warning message resp->iFatigue_Level = 1; resp->iItemCnt = nrewards; + resp->m_iBatteryN = plr->batteryN; + resp->m_iBatteryW = plr->batteryW; for (int i = 0; i < nrewards; i++) { item[i].sItem.iType = reward->itemTypes[i]; diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index 64f9918..841ddf8 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -531,7 +531,6 @@ void NPCManager::npcSummonHandler(CNSocket* sock, CNPacketData* data) { return; // malformed packet sP_CL2FE_REQ_NPC_SUMMON* req = (sP_CL2FE_REQ_NPC_SUMMON*)data->buf; - INITSTRUCT(sP_FE2CL_NPC_ENTER, resp); Player* plr = PlayerManager::getPlayer(sock); // permission & sanity check @@ -541,20 +540,15 @@ void NPCManager::npcSummonHandler(CNSocket* sock, CNPacketData* data) { int team = NPCData[req->iNPCType]["m_iTeam"]; assert(nextId < INT32_MAX); - resp.NPCAppearanceData.iNPC_ID = nextId++; - resp.NPCAppearanceData.iNPCType = req->iNPCType; - resp.NPCAppearanceData.iHP = 1000; - resp.NPCAppearanceData.iX = plr->x; - resp.NPCAppearanceData.iY = plr->y; - resp.NPCAppearanceData.iZ = plr->z; + int id = nextId++; if (team == 2) { - NPCs[resp.NPCAppearanceData.iNPC_ID] = new Mob(plr->x, plr->y, plr->z, plr->instanceID, req->iNPCType, NPCData[req->iNPCType], resp.NPCAppearanceData.iNPC_ID); - MobManager::Mobs[resp.NPCAppearanceData.iNPC_ID] = (Mob*)NPCs[resp.NPCAppearanceData.iNPC_ID]; + NPCs[id] = new Mob(plr->x, plr->y, plr->z, plr->instanceID, req->iNPCType, NPCData[req->iNPCType], id); + MobManager::Mobs[id] = (Mob*)NPCs[id]; } else - NPCs[resp.NPCAppearanceData.iNPC_ID] = new BaseNPC(plr->x, plr->y, plr->z, plr->instanceID, req->iNPCType, resp.NPCAppearanceData.iNPC_ID); + NPCs[id] = new BaseNPC(plr->x, plr->y, plr->z, plr->instanceID, req->iNPCType, id); - updateNPCPosition(resp.NPCAppearanceData.iNPC_ID, plr->x, plr->y, plr->z); + updateNPCPosition(id, plr->x, plr->y, plr->z); } void NPCManager::npcWarpHandler(CNSocket* sock, CNPacketData* data) { diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index 107c5b2..4d978ca 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -222,7 +222,6 @@ void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z, int I) { } void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z) { - PlayerManager::updatePlayerPosition(sock, X, Y, Z); INITSTRUCT(sP_FE2CL_REP_PC_GOTO_SUCC, pkt); pkt.iX = X; diff --git a/src/TableData.cpp b/src/TableData.cpp index 5139404..fccf2b0 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -14,6 +14,7 @@ std::map> TableData::RunningSkywayRoutes; std::map TableData::RunningNPCRotations; +std::map TableData::RunningMobs; void TableData::init() { int32_t nextId = 0; @@ -197,7 +198,7 @@ void TableData::init() { std::cerr << "[WARN] Malformed mobs.json file! Reason:" << err.what() << std::endl; } - loadGruntwork(); + loadGruntwork(&nextId); NPCManager::nextId = nextId; } @@ -340,7 +341,7 @@ void TableData::constructPathNPC(nlohmann::json::iterator _pathData) { } // load gruntwork output; if it exists -void TableData::loadGruntwork() { +void TableData::loadGruntwork(int32_t *nextId) { try { std::ifstream inFile(settings::GRUNTWORKJSON); nlohmann::json gruntwork; @@ -376,6 +377,23 @@ void TableData::loadGruntwork() { npc->appearanceData.iAngle = angle; } + // mobs + auto mobs = gruntwork["mobs"]; + for (auto _mob = mobs.begin(); _mob != mobs.end(); _mob++) { + auto mob = _mob.value(); + + Mob *npc = new Mob(mob["iX"], mob["iY"], mob["iZ"], INSTANCE_OVERWORLD, mob["iNPCType"], + NPCManager::NPCData[(int)mob["iNPCType"]], (*nextId)++); + + NPCManager::NPCs[npc->appearanceData.iNPC_ID] = npc; + MobManager::Mobs[npc->appearanceData.iNPC_ID] = npc; + TableData::RunningMobs[npc->appearanceData.iNPC_ID] = npc; + NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, mob["iX"], mob["iY"], mob["iZ"]); + + // re-enable respawning + npc->summoned = false; + } + std::cout << "[INFO] Loaded gruntwork.json" << std::endl; } catch (const std::exception& err) { @@ -417,5 +435,24 @@ void TableData::flush() { gruntwork["rotations"].push_back(rotation); } + for (auto& pair : RunningMobs) { + nlohmann::json mob; + Mob *m = (Mob*)pair.second; // we need spawnX, etc + + if (NPCManager::NPCs.find(pair.first) == NPCManager::NPCs.end()) + continue; + + // NOTE: this format deviates slightly from the one in mobs.json + mob["iNPCType"] = (int)m->appearanceData.iNPCType; + mob["iHP"] = (int)m->maxHealth; + mob["iX"] = m->spawnX; + mob["iY"] = m->spawnY; + mob["iZ"] = m->spawnZ; + // this is a bit imperfect, since this is a live angle, not a spawn angle so it'll change often, but eh + mob["iAngle"] = m->appearanceData.iAngle; + + gruntwork["mobs"].push_back(mob); + } + file << gruntwork << std::endl; } diff --git a/src/TableData.hpp b/src/TableData.hpp index 09f2ccf..3a66c75 100644 --- a/src/TableData.hpp +++ b/src/TableData.hpp @@ -7,10 +7,11 @@ namespace TableData { extern std::map> RunningSkywayRoutes; extern std::map RunningNPCRotations; + extern std::map RunningMobs; void init(); void cleanup(); - void loadGruntwork(); + void loadGruntwork(int32_t*); void flush(); int getItemType(int); diff --git a/suppr.txt b/suppr.txt index 254330a..463ff98 100644 --- a/suppr.txt +++ b/suppr.txt @@ -3,4 +3,6 @@ leak:ChunkManager::addPlayer leak:ChunkManager::addNPC leak:NPCManager::updateNPCPosition leak:NPCManager::npcSummonHandler +leak:summonWCommand +leak:TableData::loadGruntwork leak:nlohmann::basic_json