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
This commit is contained in:
dongresource 2020-09-25 02:00:26 +02:00
parent 72d625fd8d
commit 279cb78d5f
7 changed files with 66 additions and 99 deletions

View File

@ -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

View File

@ -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<int,int> lerp(int, int, int, int, int);

View File

@ -1,11 +1,14 @@
#pragma once
#include "CNStructs.hpp"
#include "ChunkManager.hpp"
class BaseNPC {
public:
sNPCAppearanceData appearanceData;
NPCClass npcClass;
std::pair<int, int> chunkPos;
std::vector<Chunk*> 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;

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -2,4 +2,5 @@ leak:TableData::init
leak:ChunkManager::addPlayer
leak:ChunkManager::addNPC
leak:NPCManager::npcSummonHandler
leak:BaseNPC::BaseNPC
leak:nlohmann::basic_json