diff --git a/src/Combat.cpp b/src/Combat.cpp index aa9cbd7..b1626f5 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -79,7 +79,7 @@ int CombatNPC::takeDamage(EntityRef src, int amt) { } if (mob->hp <= 0) - killMob(mob->target, mob); + transition(AIState::DEAD, src); return amt; } @@ -144,6 +144,11 @@ void CombatNPC::transition(AIState newState, EntityRef src) { onRetreat(); break; case AIState::DEAD: + /* TODO: fire any triggered events + for (NPCEvent& event : NPCManager::NPCEvents) + if (event.trigger == ON_KILLED && event.npcType == type) + event.handler(src, this); + */ onDeath(src); break; } @@ -314,7 +319,7 @@ void Combat::npcAttackPc(Mob *mob, time_t currTime) { * single RNG roll per mission task, and every group member shares that same * set of rolls. */ -static void genQItemRolls(Player *leader, std::map& rolls) { +void Combat::genQItemRolls(Player *leader, std::map& rolls) { for (int i = 0; i < leader->groupCnt; i++) { if (leader->groupIDs[i] == 0) continue; @@ -331,79 +336,6 @@ static void genQItemRolls(Player *leader, std::map& rolls) { } } -void Combat::killMob(CNSocket *sock, Mob *mob) { - mob->state = AIState::DEAD; - mob->target = nullptr; - mob->cbf = 0; - mob->skillStyle = -1; - mob->unbuffTimes.clear(); - mob->killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step? - - // check for the edge case where hitting the mob did not aggro it - if (sock != nullptr) { - Player* plr = PlayerManager::getPlayer(sock); - - Items::DropRoll rolled; - Items::DropRoll eventRolled; - std::map qitemRolls; - - Player *leader = PlayerManager::getPlayerFromID(plr->iIDGroup); - assert(leader != nullptr); // should never happen - - genQItemRolls(leader, qitemRolls); - - if (plr->groupCnt == 1 && plr->iIDGroup == plr->iID) { - Items::giveMobDrop(sock, mob, rolled, eventRolled); - Missions::mobKilled(sock, mob->type, qitemRolls); - } else { - for (int i = 0; i < leader->groupCnt; i++) { - CNSocket* sockTo = PlayerManager::getSockFromID(leader->groupIDs[i]); - if (sockTo == nullptr) - continue; - - Player *otherPlr = PlayerManager::getPlayer(sockTo); - - // only contribute to group members' kills if they're close enough - int dist = std::hypot(plr->x - otherPlr->x + 1, plr->y - otherPlr->y + 1); - if (dist > 5000) - continue; - - Items::giveMobDrop(sockTo, mob, rolled, eventRolled); - Missions::mobKilled(sockTo, mob->type, qitemRolls); - } - } - } - - // delay the despawn animation - mob->despawned = false; - - // fire any triggered events - for (NPCEvent& event : NPCManager::NPCEvents) - if (event.trigger == ON_KILLED && event.npcType == mob->type) - event.handler(sock, mob); - - auto it = Transport::NPCQueues.find(mob->id); - if (it == Transport::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 { - Transport::NPCQueues.erase(mob->id); - } -} - static void combatBegin(CNSocket *sock, CNPacketData *data) { Player *plr = PlayerManager::getPlayer(sock); diff --git a/src/Combat.hpp b/src/Combat.hpp index bb06e54..14d0076 100644 --- a/src/Combat.hpp +++ b/src/Combat.hpp @@ -17,11 +17,13 @@ struct Bullet { int bulletType; }; + + namespace Combat { extern std::map> Bullets; void init(); void npcAttackPc(Mob *mob, time_t currTime); - void killMob(CNSocket *sock, Mob *mob); + void genQItemRolls(Player* leader, std::map& rolls); } diff --git a/src/MobAI.cpp b/src/MobAI.cpp index ffc8bb2..d9d430e 100644 --- a/src/MobAI.cpp +++ b/src/MobAI.cpp @@ -6,6 +6,8 @@ #include "Combat.hpp" #include "Abilities.hpp" #include "Rand.hpp" +#include "Items.hpp" +#include "Missions.hpp" #include #include @@ -431,7 +433,7 @@ static void drainMobHP(Mob *mob, int amount) { NPCManager::sendToViewable(mob, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen); if (mob->hp <= 0) - Combat::killMob(mob->target, mob); + mob->transition(AIState::DEAD, mob->target); } void Mob::deadStep(time_t currTime) { @@ -775,5 +777,70 @@ void Mob::onRetreat() { } void Mob::onDeath(EntityRef src) { - // stub + target = nullptr; + cbf = 0; + skillStyle = -1; + unbuffTimes.clear(); + killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step? + + // check for the edge case where hitting the mob did not aggro it + if (src.type == EntityType::PLAYER && src.isValid()) { + Player* plr = PlayerManager::getPlayer(src.sock); + + Items::DropRoll rolled; + Items::DropRoll eventRolled; + std::map qitemRolls; + + Player* leader = PlayerManager::getPlayerFromID(plr->iIDGroup); + assert(leader != nullptr); // should never happen + + Combat::genQItemRolls(leader, qitemRolls); + + if (plr->groupCnt == 1 && plr->iIDGroup == plr->iID) { + Items::giveMobDrop(src.sock, this, rolled, eventRolled); + Missions::mobKilled(src.sock, type, qitemRolls); + } + else { + for (int i = 0; i < leader->groupCnt; i++) { + CNSocket* sockTo = PlayerManager::getSockFromID(leader->groupIDs[i]); + if (sockTo == nullptr) + continue; + + Player* otherPlr = PlayerManager::getPlayer(sockTo); + + // only contribute to group members' kills if they're close enough + int dist = std::hypot(plr->x - otherPlr->x + 1, plr->y - otherPlr->y + 1); + if (dist > 5000) + continue; + + Items::giveMobDrop(sockTo, this, rolled, eventRolled); + Missions::mobKilled(sockTo, type, qitemRolls); + } + } + } + + // delay the despawn animation + despawned = false; + + auto it = Transport::NPCQueues.find(id); + if (it == Transport::NPCQueues.end() || it->second.empty()) + return; + + // rewind or empty the movement queue + if (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 != spawnX || point.y != spawnY; point = queue.front()) { + queue.pop(); + queue.push(point); + } + } + else { + Transport::NPCQueues.erase(id); + } }