From 279cb78d5f8353238c4421364f39c522fa35348e Mon Sep 17 00:00:00 2001 From: dongresource Date: Fri, 25 Sep 2020 02:00:26 +0200 Subject: [PATCH] Mob-related cleanup. * NPCs now keep track of their chunk information like PlayerView does for players * NPCManager::sendToViewable() parallels PlayerManager::sendToViewable() * Nano damage and debuffs now count as attacking a mob * Mobs will de-aggro if something else killed their target --- src/MobManager.cpp | 120 ++++++++++++++++---------------------------- src/MobManager.hpp | 1 + src/NPC.hpp | 6 +++ src/NPCManager.cpp | 8 +++ src/NPCManager.hpp | 2 + src/NanoManager.cpp | 27 +++------- suppr.txt | 1 + 7 files changed, 66 insertions(+), 99 deletions(-) diff --git a/src/MobManager.cpp b/src/MobManager.cpp index 4b8f2c0..38b6945 100644 --- a/src/MobManager.cpp +++ b/src/MobManager.cpp @@ -60,33 +60,10 @@ void MobManager::pcAttackNpcs(CNSocket *sock, CNPacketData *data) { } Mob *mob = Mobs[pktdata[i]]; - // cannot kill mobs multiple times; cannot harm retreating mobs - if (mob->state != MobState::ROAMING && mob->state != MobState::COMBAT) { - respdata[i].iID = mob->appearanceData.iNPC_ID; - respdata[i].iHP = mob->appearanceData.iHP; - respdata[i].iHitFlag = 2; // probably still necessary - continue; - } - - if (mob->state == MobState::ROAMING) { - assert(mob->target == nullptr); - mob->target = sock; - mob->state = MobState::COMBAT; - mob->nextMovement = getTime(); - //std::cout << "combat state\n"; - } - - mob->appearanceData.iHP -= 100; - - // wake up sleeping monster - // TODO: remove client-side bit somehow - mob->appearanceData.iConditionBitFlag &= ~CSB_BIT_MEZ; - - if (mob->appearanceData.iHP <= 0) - killMob(mob->target, mob); + int damage = hitMob(sock, mob, 100); respdata[i].iID = mob->appearanceData.iNPC_ID; - respdata[i].iDamage = 100; + respdata[i].iDamage = damage; respdata[i].iHP = mob->appearanceData.iHP; respdata[i].iHitFlag = 2; // hitscan, not a rocket or a grenade } @@ -131,7 +108,6 @@ void MobManager::npcAttackPc(Mob *mob) { if (plr->HP <= 0) { mob->target = nullptr; mob->state = MobState::RETREAT; - //std::cout << "retreat state\n"; } } @@ -184,14 +160,37 @@ void MobManager::giveReward(CNSocket *sock) { } +int MobManager::hitMob(CNSocket *sock, Mob *mob, int damage) { + // cannot kill mobs multiple times; cannot harm retreating mobs + if (mob->state != MobState::ROAMING && mob->state != MobState::COMBAT) { + return 0; // no damage + } + + if (mob->state == MobState::ROAMING) { + assert(mob->target == nullptr); + mob->target = sock; + mob->state = MobState::COMBAT; + mob->nextMovement = getTime(); + } + + mob->appearanceData.iHP -= damage; + + // wake up sleeping monster + // TODO: remove client-side bit somehow + mob->appearanceData.iConditionBitFlag &= ~CSB_BIT_MEZ; + + if (mob->appearanceData.iHP <= 0) + killMob(mob->target, mob); + + return damage; +} + void MobManager::killMob(CNSocket *sock, Mob *mob) { mob->state = MobState::DEAD; mob->target = nullptr; mob->appearanceData.iConditionBitFlag = 0; mob->killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step? - //std::cout << "dead state mob " << mob->appearanceData.iNPC_ID << std::endl; - giveReward(sock); MissionManager::mobKilled(sock, mob->appearanceData.iNPCType); @@ -199,9 +198,6 @@ void MobManager::killMob(CNSocket *sock, Mob *mob) { } void MobManager::deadStep(Mob *mob, time_t currTime) { - auto chunk = ChunkManager::grabChunk(mob->appearanceData.iX, mob->appearanceData.iY); - auto chunks = ChunkManager::grabChunks(chunk); - // despawn the mob after a short delay if (mob->killedTime != 0 && !mob->despawned && currTime - mob->killedTime > 2000) { mob->despawned = true; @@ -210,11 +206,7 @@ void MobManager::deadStep(Mob *mob, time_t currTime) { pkt.iNPC_ID = mob->appearanceData.iNPC_ID; - for (Chunk *chunk : chunks) { - for (CNSocket *s : chunk->players) { - s->sendPacket(&pkt, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT)); - } - } + NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT)); // if it was summoned, remove it permanently if (mob->summoned) { @@ -231,18 +223,13 @@ void MobManager::deadStep(Mob *mob, time_t currTime) { mob->appearanceData.iHP = mob->maxHealth; mob->state = MobState::ROAMING; - //std::cout << "roaming state\n"; INITSTRUCT(sP_FE2CL_NPC_NEW, pkt); pkt.NPCAppearanceData = mob->appearanceData; // notify all nearby players - for (Chunk *chunk : chunks) { - for (CNSocket *s : chunk->players) { - s->sendPacket(&pkt, P_FE2CL_NPC_NEW, sizeof(sP_FE2CL_NPC_NEW)); - } - } + NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_NEW, sizeof(sP_FE2CL_NPC_NEW)); } void MobManager::combatStep(Mob *mob, time_t currTime) { @@ -252,14 +239,16 @@ void MobManager::combatStep(Mob *mob, time_t currTime) { if (PlayerManager::players.find(mob->target) == PlayerManager::players.end()) { mob->target = nullptr; mob->state = MobState::RETREAT; - //std::cout << "RETREAT state\n"; return; } Player *plr = PlayerManager::getPlayer(mob->target); - // skip attack/move if stunned or asleep - if (mob->appearanceData.iConditionBitFlag & (CSB_BIT_STUN|CSB_BIT_MEZ)) + // did something else kill the player in the mean time? + if (plr->HP <= 0) { + mob->target = nullptr; + mob->state = MobState::RETREAT; return; + } int distance = hypot(plr->x - mob->appearanceData.iX, plr->y - mob->appearanceData.iY); @@ -268,7 +257,6 @@ void MobManager::combatStep(Mob *mob, time_t currTime) { * No, I'm not 100% sure this is how it's supposed to work. */ if (distance <= (int)mob->data["m_iAtkRange"]) { - //std::cout << "attack logic\n"; // attack logic if (mob->nextAttack == 0) { mob->nextAttack = currTime + (int)mob->data["m_iInitalTime"] * 100; // I *think* this is what this is @@ -278,8 +266,6 @@ void MobManager::combatStep(Mob *mob, time_t currTime) { npcAttackPc(mob); } } else { - //std::cout << "movement logic\n"; - //std::cout << "distance: " << distance << " attack range: " << mob->data["m_iAtkRange"] << std::endl; // movement logic if (mob->nextMovement != 0 && currTime < mob->nextMovement) return; @@ -301,15 +287,8 @@ void MobManager::combatStep(Mob *mob, time_t currTime) { pkt.iToY = mob->appearanceData.iY = targ.second; pkt.iToZ = mob->appearanceData.iZ; - auto chunk = ChunkManager::grabChunk(mob->appearanceData.iX, mob->appearanceData.iY); - auto chunks = ChunkManager::grabChunks(chunk); - // notify all nearby players - for (Chunk *chunk : chunks) { - for (CNSocket *s : chunk->players) { - s->sendPacket(&pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); - } - } + NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); } // retreat if kited too far @@ -317,7 +296,6 @@ void MobManager::combatStep(Mob *mob, time_t currTime) { if (distance >= mob->data["m_iCombatRange"]) { mob->target = nullptr; mob->state = MobState::RETREAT; - //std::cout << "retreat state\n"; } } @@ -338,10 +316,6 @@ void MobManager::roamingStep(Mob *mob, time_t currTime) { int delay = (int)mob->data["m_iDelayTime"] * 1000; mob->nextMovement = currTime + delay/2 + rand() % (delay/2); - // skip move if stunned or asleep - if (mob->appearanceData.iConditionBitFlag & (CSB_BIT_STUN|CSB_BIT_MEZ)) - return; - INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt); int xStart = mob->spawnX - mob->idleRange/2; int yStart = mob->spawnY - mob->idleRange/2; @@ -362,15 +336,8 @@ void MobManager::roamingStep(Mob *mob, time_t currTime) { pkt.iToY = mob->appearanceData.iY = targ.second; pkt.iToZ = mob->appearanceData.iZ; - auto chunk = ChunkManager::grabChunk(mob->appearanceData.iX, mob->appearanceData.iY); - auto chunks = ChunkManager::grabChunks(chunk); - // notify all nearby players - for (Chunk *chunk : chunks) { - for (CNSocket *s : chunk->players) { - s->sendPacket(&pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); - } - } + NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); } void MobManager::retreatStep(Mob *mob, time_t currTime) { @@ -380,9 +347,6 @@ void MobManager::retreatStep(Mob *mob, time_t currTime) { if (distance > mob->data["m_iIdleRange"]) { INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt); - auto chunk = ChunkManager::grabChunk(mob->appearanceData.iX, mob->appearanceData.iY); - auto chunks = ChunkManager::grabChunks(chunk); - auto targ = lerp(mob->appearanceData.iX, mob->appearanceData.iY, mob->spawnX, mob->spawnY, mob->data["m_iRunSpeed"]); pkt.iNPC_ID = mob->appearanceData.iNPC_ID; @@ -392,17 +356,12 @@ void MobManager::retreatStep(Mob *mob, time_t currTime) { pkt.iToZ = mob->appearanceData.iZ; // notify all nearby players - for (Chunk *chunk : chunks) { - for (CNSocket *s : chunk->players) { - s->sendPacket(&pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); - } - } + NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); } // if we got there if (distance <= mob->data["m_iIdleRange"]) { mob->state = MobState::ROAMING; - //std::cout << "roaming state\n"; mob->appearanceData.iHP = mob->maxHealth; mob->killedTime = 0; mob->nextAttack = 0; @@ -423,6 +382,11 @@ void MobManager::step(CNServer *serv, time_t currTime) { if (!settings::SIMULATEMOBS && pair.second->state != MobState::DEAD) continue; + // skip attack/move if stunned or asleep + if (pair.second->appearanceData.iConditionBitFlag & (CSB_BIT_STUN|CSB_BIT_MEZ) + && (pair.second->state == MobState::ROAMING || pair.second->state == MobState::COMBAT)) + continue; + switch (pair.second->state) { case MobState::INACTIVE: // no-op diff --git a/src/MobManager.hpp b/src/MobManager.hpp index 72e1844..bb7a46a 100644 --- a/src/MobManager.hpp +++ b/src/MobManager.hpp @@ -94,6 +94,7 @@ namespace MobManager { void dotDamageOnOff(CNSocket *sock, CNPacketData *data); void npcAttackPc(Mob *mob); + int hitMob(CNSocket *sock, Mob *mob, int damage); void killMob(CNSocket *sock, Mob *mob); void giveReward(CNSocket *sock); std::pair lerp(int, int, int, int, int); diff --git a/src/NPC.hpp b/src/NPC.hpp index be8b263..94e9a9b 100644 --- a/src/NPC.hpp +++ b/src/NPC.hpp @@ -1,11 +1,14 @@ #pragma once #include "CNStructs.hpp" +#include "ChunkManager.hpp" class BaseNPC { public: sNPCAppearanceData appearanceData; NPCClass npcClass; + std::pair chunkPos; + std::vector currentChunks; BaseNPC() {}; BaseNPC(int x, int y, int z, int type, int id) { @@ -18,6 +21,9 @@ public: appearanceData.iConditionBitFlag = 0; appearanceData.iBarkerType = 0; appearanceData.iNPC_ID = id; + + chunkPos = ChunkManager::grabChunk(x, y); + currentChunks = ChunkManager::grabChunks(chunkPos); }; BaseNPC(int x, int y, int z, int type, int id, NPCClass classType) : BaseNPC(x, y, z, type, id) { npcClass = classType; diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index fd51aeb..1a47170 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -147,6 +147,14 @@ void NPCManager::updateNPCPosition(int32_t id, int X, int Y, int Z) { ChunkManager::addNPC(X, Y, npc->appearanceData.iNPC_ID); } +void NPCManager::sendToViewable(BaseNPC *npc, void *buf, uint32_t type, size_t size) { + for (Chunk *chunk : npc->currentChunks) { + for (CNSocket *s : chunk->players) { + s->sendPacket(buf, type, size); + } + } +} + void NPCManager::npcVendorBuy(CNSocket* sock, CNPacketData* data) { if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY)) return; // malformed packet diff --git a/src/NPCManager.hpp b/src/NPCManager.hpp index c48f687..d23e361 100644 --- a/src/NPCManager.hpp +++ b/src/NPCManager.hpp @@ -28,6 +28,8 @@ namespace NPCManager { void removeNPC(int32_t); void updateNPCPosition(int32_t, int X, int Y, int Z); + void sendToViewable(BaseNPC* npc, void* buf, uint32_t type, size_t size); + void npcBarkHandler(CNSocket* sock, CNPacketData* data); void npcSummonHandler(CNSocket* sock, CNPacketData* data); void npcUnsummonHandler(CNSocket* sock, CNPacketData* data); diff --git a/src/NanoManager.cpp b/src/NanoManager.cpp index a132b54..f93dab1 100644 --- a/src/NanoManager.cpp +++ b/src/NanoManager.cpp @@ -309,13 +309,10 @@ bool doDebuff(CNSocket *sock, int32_t *pktdata, sSkillResult_Damage_N_Debuff *re Mob* mob = MobManager::Mobs[pktdata[i]]; - mob->appearanceData.iHP -= amount; - - if (mob->appearanceData.iHP <= 0) - MobManager::killMob(sock, mob); + int damage = MobManager::hitMob(sock, mob, amount); respdata[i].eCT = 4; - respdata[i].iDamage = amount; + respdata[i].iDamage = damage; respdata[i].iID = mob->appearanceData.iNPC_ID; respdata[i].iHP = mob->appearanceData.iHP; respdata[i].iConditionBitFlag = mob->appearanceData.iConditionBitFlag |= iCBFlag; @@ -380,16 +377,10 @@ bool doDamage(CNSocket *sock, int32_t *pktdata, sSkillResult_Damage *respdata, i } Mob* mob = MobManager::Mobs[pktdata[i]]; - mob->appearanceData.iHP -= amount; + int damage = MobManager::hitMob(sock, mob, amount); - // wake up sleeping monster - mob->appearanceData.iConditionBitFlag &= ~CSB_BIT_MEZ; - - if (mob->appearanceData.iHP <= 0) - MobManager::killMob(sock, mob); - respdata[i].eCT = 4; - respdata[i].iDamage = amount; + respdata[i].iDamage = damage; respdata[i].iID = mob->appearanceData.iNPC_ID; respdata[i].iHP = mob->appearanceData.iHP; @@ -433,16 +424,10 @@ bool doLeech(CNSocket *sock, int32_t *pktdata, sSkillResult_Heal_HP *healdata, i } Mob* mob = MobManager::Mobs[pktdata[i]]; - mob->appearanceData.iHP -= amount; - - // wake up sleeping monster - mob->appearanceData.iConditionBitFlag &= ~CSB_BIT_MEZ; - - if (mob->appearanceData.iHP <= 0) - MobManager::killMob(sock, mob); + int damage = MobManager::hitMob(sock, mob, amount); damagedata->eCT = 4; - damagedata->iDamage = amount; + damagedata->iDamage = damage; damagedata->iID = mob->appearanceData.iNPC_ID; damagedata->iHP = mob->appearanceData.iHP; diff --git a/suppr.txt b/suppr.txt index 781647b..767e5a4 100644 --- a/suppr.txt +++ b/suppr.txt @@ -2,4 +2,5 @@ leak:TableData::init leak:ChunkManager::addPlayer leak:ChunkManager::addNPC leak:NPCManager::npcSummonHandler +leak:BaseNPC::BaseNPC leak:nlohmann::basic_json