From 599bbedd8c699a81f782b907a45d41f768249741 Mon Sep 17 00:00:00 2001 From: dongresource Date: Tue, 13 Oct 2020 21:44:43 +0200 Subject: [PATCH] Implemented static paths for mobs (like Don Doom and Bad Max). * Mobs now retreat to where they were when they were roaming; not their spawn point * /toggleai still sends them back to their spawn point however * Fixed a bug where mobs would respawn where they were killed instead of their proper spawn point * Fixed mobs roaming despite simulatemobs being set to false * Updated tdata submodule --- src/CNStructs.hpp | 1 + src/ChatManager.cpp | 5 +++- src/MobManager.cpp | 59 ++++++++++++++++++++++++++++++++++------ src/MobManager.hpp | 7 +++-- src/TableData.cpp | 33 +++++++++++++++++++--- src/TableData.hpp | 2 +- src/TransportManager.cpp | 6 ++++ tdata | 2 +- 8 files changed, 97 insertions(+), 18 deletions(-) diff --git a/src/CNStructs.hpp b/src/CNStructs.hpp index 84e1d89..dfa9ded 100644 --- a/src/CNStructs.hpp +++ b/src/CNStructs.hpp @@ -34,6 +34,7 @@ std::string U16toU8(char16_t* src); size_t U8toU16(std::string src, char16_t* des, size_t max); // returns number of char16_t that was written at des time_t getTime(); time_t getTimestamp(); +void terminate(int); // The PROTOCOL_VERSION definition is defined by the build system. #if !defined(PROTOCOL_VERSION) diff --git a/src/ChatManager.cpp b/src/ChatManager.cpp index b5f9124..367081a 100644 --- a/src/ChatManager.cpp +++ b/src/ChatManager.cpp @@ -45,7 +45,6 @@ bool runCmd(std::string full, CNSocket* sock) { void helpCommand(std::string full, std::vector& args, CNSocket* sock) { ChatManager::sendServerMessage(sock, "Commands available to you"); Player *plr = PlayerManager::getPlayer(sock); - int i = 1; for (auto& cmd : ChatManager::commands) { if (cmd.second.requiredAccLevel >= plr->accountId) @@ -280,6 +279,10 @@ void toggleAiCommand(std::string full, std::vector& args, CNSocket* for (auto& pair : MobManager::Mobs) { pair.second->state = MobState::RETREAT; pair.second->target = nullptr; + + pair.second->roamX = pair.second->spawnX; + pair.second->roamY = pair.second->spawnY; + pair.second->roamZ = pair.second->spawnZ; } } diff --git a/src/MobManager.cpp b/src/MobManager.cpp index f906bd5..d2a33f1 100644 --- a/src/MobManager.cpp +++ b/src/MobManager.cpp @@ -210,6 +210,10 @@ int MobManager::hitMob(CNSocket *sock, Mob *mob, int damage) { mob->state = MobState::COMBAT; mob->nextMovement = getTime(); mob->nextAttack = 0; + + mob->roamX = mob->appearanceData.iX; + mob->roamY = mob->appearanceData.iY; + mob->roamZ = mob->appearanceData.iZ; } mob->appearanceData.iHP -= damage; @@ -254,7 +258,29 @@ void MobManager::killMob(CNSocket *sock, Mob *mob) { } } + // delay the despawn animation mob->despawned = false; + + auto it = TransportManager::NPCQueues.find(mob->appearanceData.iNPC_ID); + if (it == TransportManager::NPCQueues.end() || it->second.empty()) + return; + + // rewind or empty the movement queue + if (mob->staticPath) { + /* + * This is inelegant, but we wind forward in the path until we find the point that + * corresponds with the Mob's spawn point. + * + * IMPORTANT: The check in TableData::loadPaths() must pass or else this will loop forever. + */ + auto& queue = it->second; + for (auto point = queue.front(); point.x != mob->spawnX || point.y != mob->spawnY; point = queue.front()) { + queue.pop(); + queue.push(point); + } + } else { + TransportManager::NPCQueues.erase(mob->appearanceData.iNPC_ID); + } } void MobManager::deadStep(Mob *mob, time_t currTime) { @@ -284,6 +310,11 @@ void MobManager::deadStep(Mob *mob, time_t currTime) { mob->appearanceData.iHP = mob->maxHealth; mob->state = MobState::ROAMING; + // reset position + mob->appearanceData.iX = mob->spawnX; + mob->appearanceData.iY = mob->spawnY; + mob->appearanceData.iZ = mob->spawnZ; + INITSTRUCT(sP_FE2CL_NPC_NEW, pkt); pkt.NPCAppearanceData = mob->appearanceData; @@ -360,7 +391,7 @@ void MobManager::combatStep(Mob *mob, time_t currTime) { } // retreat if the player leaves combat range - distance = hypot(plr->x - mob->spawnX, plr->y - mob->spawnY); + distance = hypot(plr->x - mob->roamX, plr->y - mob->roamY); if (distance >= mob->data["m_iCombatRange"]) { mob->target = nullptr; mob->state = MobState::RETREAT; @@ -390,14 +421,18 @@ void MobManager::roamingStep(Mob *mob, time_t currTime) { if (mob->idleRange == 0) return; + // no random roaming if the mob already has a set path + if (mob->staticPath) + return; + if (mob->nextMovement != 0 && currTime < mob->nextMovement) return; incNextMovement(mob, currTime); - - // only calculate a new route if we're not already on one - auto it = TransportManager::NPCQueues.find(mob->appearanceData.iNPC_ID); - if (it != TransportManager::NPCQueues.end() && it->second.empty()) - return; + /* + * mob->nextMovement is also updated whenever the path queue is traversed in + * TransportManager::stepNPCPathing() (which ticks at a higher frequency than nextMovement), + * so we don't have to check if there's already entries in the queue since we know there won't be. + */ int xStart = mob->spawnX - mob->idleRange/2; int yStart = mob->spawnY - mob->idleRange/2; @@ -437,13 +472,13 @@ void MobManager::retreatStep(Mob *mob, time_t currTime) { mob->nextMovement = currTime + 500; - int distance = hypot(mob->appearanceData.iX - mob->spawnX, mob->appearanceData.iY - mob->spawnY); + int distance = hypot(mob->appearanceData.iX - mob->roamX, mob->appearanceData.iY - mob->roamY); //if (distance > mob->data["m_iIdleRange"]) { if (distance > 10) { INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt); - auto targ = lerp(mob->appearanceData.iX, mob->appearanceData.iY, mob->spawnX, mob->spawnY, (int)mob->data["m_iRunSpeed"] * 3); + auto targ = lerp(mob->appearanceData.iX, mob->appearanceData.iY, mob->roamX, mob->roamY, (int)mob->data["m_iRunSpeed"] * 3); pkt.iNPC_ID = mob->appearanceData.iNPC_ID; pkt.iSpeed = (int)mob->data["m_iRunSpeed"] * 3; @@ -720,10 +755,12 @@ std::pair MobManager::getDamage(int attackPower, int defensePower, bool if (attackPower + defensePower * 2 == 0) return ret; + // base calculation int damage = attackPower * attackPower / (attackPower + defensePower); damage = std::max(std::max(29, attackPower / 7), damage - defensePower * (12 + difficulty) / 65); damage = damage * (rand() % 40 + 80) / 100; + // Adaptium/Blastons/Cosmix if (attackerStyle != -1 && defenderStyle != -1 && attackerStyle != defenderStyle) { if (attackerStyle < defenderStyle || attackerStyle - defenderStyle == 2) damage = damage * 5 / 4; @@ -731,6 +768,7 @@ std::pair MobManager::getDamage(int attackPower, int defensePower, bool damage = damage * 4 / 5; } + // weapon boosts if (batteryBoost) damage = damage * 5 / 4; @@ -913,6 +951,11 @@ bool MobManager::aggroCheck(Mob *mob, time_t currTime) { mob->state = MobState::COMBAT; mob->nextMovement = currTime; mob->nextAttack = 0; + + mob->roamX = mob->appearanceData.iX; + mob->roamY = mob->appearanceData.iY; + mob->roamZ = mob->appearanceData.iZ; + return true; } } diff --git a/src/MobManager.hpp b/src/MobManager.hpp index aaf4dc4..fb03351 100644 --- a/src/MobManager.hpp +++ b/src/MobManager.hpp @@ -40,6 +40,7 @@ struct Mob : public BaseNPC { // combat CNSocket *target = nullptr; time_t nextAttack = 0; + int roamX, roamY, roamZ; // temporary; until we're sure what's what nlohmann::json data; @@ -57,9 +58,9 @@ struct Mob : public BaseNPC { if (regenTime >= 300000000) regenTime = 1500; - spawnX = appearanceData.iX; - spawnY = appearanceData.iY; - spawnZ = appearanceData.iZ; + roamX = spawnX = appearanceData.iX; + roamY = spawnY = appearanceData.iY; + roamZ = spawnZ = appearanceData.iZ; appearanceData.iConditionBitFlag = 0; diff --git a/src/TableData.cpp b/src/TableData.cpp index 0328bb8..b4ab3ab 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -44,8 +44,6 @@ void TableData::init() { std::cerr << "[WARN] Malformed NPCs.json file! Reason:" << err.what() << std::endl; } - loadPaths(&nextId); // load paths - // load everything else from xdttable std::cout << "[INFO] Parsing xdt.json..." << std::endl; std::ifstream infile(settings::XDTJSON); @@ -198,6 +196,8 @@ void TableData::init() { std::cerr << "[WARN] Malformed mobs.json file! Reason:" << err.what() << std::endl; } + loadPaths(&nextId); // load paths + loadGruntwork(&nextId); NPCManager::nextId = nextId; @@ -264,6 +264,27 @@ void TableData::loadPaths(int* nextId) { for (nlohmann::json::iterator npcPath = pathDataNPC.begin(); npcPath != pathDataNPC.end(); npcPath++) { constructPathNPC(npcPath); } + + // mob paths + pathDataNPC = pathData["mob"]; + for (nlohmann::json::iterator npcPath = pathDataNPC.begin(); npcPath != pathDataNPC.end(); npcPath++) { + for (auto& pair : MobManager::Mobs) { + if (pair.second->appearanceData.iNPCType == npcPath.value()["iNPCType"]) { + std::cout << "[INFO] Using static path for mob " << pair.second->appearanceData.iNPCType << " with ID " << pair.first << std::endl; + + auto firstPoint = npcPath.value()["points"][0]; + if (firstPoint["iX"] != pair.second->spawnX || firstPoint["iY"] != pair.second->spawnY) { + std::cout << "[FATAL] The first point of the route for mob " << pair.first << + " (type " << pair.second->appearanceData.iNPCType << ") does not correspond with its spawn point." << std::endl; + terminate(0); + } + + constructPathNPC(npcPath, pair.first); + pair.second->staticPath = true; + break; // only one NPC per path + } + } + } std::cout << "[INFO] Loaded " << TransportManager::NPCQueues.size() << " NPC paths" << std::endl; } catch (const std::exception& err) { @@ -319,7 +340,7 @@ void TableData::constructPathSlider(nlohmann::json points, int rotations, int sl TransportManager::NPCQueues[sliderID] = route; } -void TableData::constructPathNPC(nlohmann::json::iterator _pathData) { +void TableData::constructPathNPC(nlohmann::json::iterator _pathData, int32_t id) { auto pathData = _pathData.value(); // Interpolate nlohmann::json pathPoints = pathData["points"]; @@ -337,7 +358,11 @@ void TableData::constructPathNPC(nlohmann::json::iterator _pathData) { from = to; // update point A stopTime = point["stop"]; } - TransportManager::NPCQueues[pathData["iNPCID"]] = points; + + if (id == 0) + id = pathData["iNPCID"]; + + TransportManager::NPCQueues[id] = points; } // load gruntwork output; if it exists diff --git a/src/TableData.hpp b/src/TableData.hpp index 3a66c75..ae0537d 100644 --- a/src/TableData.hpp +++ b/src/TableData.hpp @@ -18,5 +18,5 @@ namespace TableData { void loadPaths(int*); void constructPathSkyway(nlohmann::json::iterator); void constructPathSlider(nlohmann::json, int, int); - void constructPathNPC(nlohmann::json::iterator); + void constructPathNPC(nlohmann::json::iterator, int id=0); } diff --git a/src/TransportManager.cpp b/src/TransportManager.cpp index c8a34ca..69c6290 100644 --- a/src/TransportManager.cpp +++ b/src/TransportManager.cpp @@ -282,6 +282,12 @@ void TransportManager::stepNPCPathing() { continue; } + // skip if not simulating mobs + if (npc->npcClass == NPC_MOB && !MobManager::simulateMobs) { + it++; + continue; + } + // do not roam if not roaming if (npc->npcClass == NPC_MOB && ((Mob*)npc)->state != MobState::ROAMING) { it++; diff --git a/tdata b/tdata index 9c59554..f943077 160000 --- a/tdata +++ b/tdata @@ -1 +1 @@ -Subproject commit 9c595541b3803664359a19379a2cef9b1c2cdc65 +Subproject commit f9430771644d21b2c7c350ab0cf46e27987e91f3