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]]; Mob *mob = Mobs[pktdata[i]];
// cannot kill mobs multiple times; cannot harm retreating mobs int damage = hitMob(sock, mob, 100);
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);
respdata[i].iID = mob->appearanceData.iNPC_ID; respdata[i].iID = mob->appearanceData.iNPC_ID;
respdata[i].iDamage = 100; respdata[i].iDamage = damage;
respdata[i].iHP = mob->appearanceData.iHP; respdata[i].iHP = mob->appearanceData.iHP;
respdata[i].iHitFlag = 2; // hitscan, not a rocket or a grenade respdata[i].iHitFlag = 2; // hitscan, not a rocket or a grenade
} }
@ -131,7 +108,6 @@ void MobManager::npcAttackPc(Mob *mob) {
if (plr->HP <= 0) { if (plr->HP <= 0) {
mob->target = nullptr; mob->target = nullptr;
mob->state = MobState::RETREAT; 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) { void MobManager::killMob(CNSocket *sock, Mob *mob) {
mob->state = MobState::DEAD; mob->state = MobState::DEAD;
mob->target = nullptr; mob->target = nullptr;
mob->appearanceData.iConditionBitFlag = 0; mob->appearanceData.iConditionBitFlag = 0;
mob->killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step? 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); giveReward(sock);
MissionManager::mobKilled(sock, mob->appearanceData.iNPCType); 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) { 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 // despawn the mob after a short delay
if (mob->killedTime != 0 && !mob->despawned && currTime - mob->killedTime > 2000) { if (mob->killedTime != 0 && !mob->despawned && currTime - mob->killedTime > 2000) {
mob->despawned = true; mob->despawned = true;
@ -210,11 +206,7 @@ void MobManager::deadStep(Mob *mob, time_t currTime) {
pkt.iNPC_ID = mob->appearanceData.iNPC_ID; pkt.iNPC_ID = mob->appearanceData.iNPC_ID;
for (Chunk *chunk : chunks) { NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT));
for (CNSocket *s : chunk->players) {
s->sendPacket(&pkt, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT));
}
}
// if it was summoned, remove it permanently // if it was summoned, remove it permanently
if (mob->summoned) { if (mob->summoned) {
@ -231,18 +223,13 @@ void MobManager::deadStep(Mob *mob, time_t currTime) {
mob->appearanceData.iHP = mob->maxHealth; mob->appearanceData.iHP = mob->maxHealth;
mob->state = MobState::ROAMING; mob->state = MobState::ROAMING;
//std::cout << "roaming state\n";
INITSTRUCT(sP_FE2CL_NPC_NEW, pkt); INITSTRUCT(sP_FE2CL_NPC_NEW, pkt);
pkt.NPCAppearanceData = mob->appearanceData; pkt.NPCAppearanceData = mob->appearanceData;
// notify all nearby players // notify all nearby players
for (Chunk *chunk : chunks) { NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_NEW, sizeof(sP_FE2CL_NPC_NEW));
for (CNSocket *s : chunk->players) {
s->sendPacket(&pkt, P_FE2CL_NPC_NEW, sizeof(sP_FE2CL_NPC_NEW));
}
}
} }
void MobManager::combatStep(Mob *mob, time_t currTime) { 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()) { if (PlayerManager::players.find(mob->target) == PlayerManager::players.end()) {
mob->target = nullptr; mob->target = nullptr;
mob->state = MobState::RETREAT; mob->state = MobState::RETREAT;
//std::cout << "RETREAT state\n";
return; return;
} }
Player *plr = PlayerManager::getPlayer(mob->target); Player *plr = PlayerManager::getPlayer(mob->target);
// skip attack/move if stunned or asleep // did something else kill the player in the mean time?
if (mob->appearanceData.iConditionBitFlag & (CSB_BIT_STUN|CSB_BIT_MEZ)) if (plr->HP <= 0) {
mob->target = nullptr;
mob->state = MobState::RETREAT;
return; return;
}
int distance = hypot(plr->x - mob->appearanceData.iX, plr->y - mob->appearanceData.iY); 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. * No, I'm not 100% sure this is how it's supposed to work.
*/ */
if (distance <= (int)mob->data["m_iAtkRange"]) { if (distance <= (int)mob->data["m_iAtkRange"]) {
//std::cout << "attack logic\n";
// attack logic // attack logic
if (mob->nextAttack == 0) { if (mob->nextAttack == 0) {
mob->nextAttack = currTime + (int)mob->data["m_iInitalTime"] * 100; // I *think* this is what this is 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); npcAttackPc(mob);
} }
} else { } else {
//std::cout << "movement logic\n";
//std::cout << "distance: " << distance << " attack range: " << mob->data["m_iAtkRange"] << std::endl;
// movement logic // movement logic
if (mob->nextMovement != 0 && currTime < mob->nextMovement) if (mob->nextMovement != 0 && currTime < mob->nextMovement)
return; return;
@ -301,15 +287,8 @@ void MobManager::combatStep(Mob *mob, time_t currTime) {
pkt.iToY = mob->appearanceData.iY = targ.second; pkt.iToY = mob->appearanceData.iY = targ.second;
pkt.iToZ = mob->appearanceData.iZ; pkt.iToZ = mob->appearanceData.iZ;
auto chunk = ChunkManager::grabChunk(mob->appearanceData.iX, mob->appearanceData.iY);
auto chunks = ChunkManager::grabChunks(chunk);
// notify all nearby players // notify all nearby players
for (Chunk *chunk : chunks) { NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
for (CNSocket *s : chunk->players) {
s->sendPacket(&pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
}
}
} }
// retreat if kited too far // retreat if kited too far
@ -317,7 +296,6 @@ void MobManager::combatStep(Mob *mob, time_t currTime) {
if (distance >= mob->data["m_iCombatRange"]) { if (distance >= mob->data["m_iCombatRange"]) {
mob->target = nullptr; mob->target = nullptr;
mob->state = MobState::RETREAT; 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; int delay = (int)mob->data["m_iDelayTime"] * 1000;
mob->nextMovement = currTime + delay/2 + rand() % (delay/2); 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); INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt);
int xStart = mob->spawnX - mob->idleRange/2; int xStart = mob->spawnX - mob->idleRange/2;
int yStart = mob->spawnY - 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.iToY = mob->appearanceData.iY = targ.second;
pkt.iToZ = mob->appearanceData.iZ; pkt.iToZ = mob->appearanceData.iZ;
auto chunk = ChunkManager::grabChunk(mob->appearanceData.iX, mob->appearanceData.iY);
auto chunks = ChunkManager::grabChunks(chunk);
// notify all nearby players // notify all nearby players
for (Chunk *chunk : chunks) { NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
for (CNSocket *s : chunk->players) {
s->sendPacket(&pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
}
}
} }
void MobManager::retreatStep(Mob *mob, time_t currTime) { 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"]) { if (distance > mob->data["m_iIdleRange"]) {
INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt); 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"]); auto targ = lerp(mob->appearanceData.iX, mob->appearanceData.iY, mob->spawnX, mob->spawnY, mob->data["m_iRunSpeed"]);
pkt.iNPC_ID = mob->appearanceData.iNPC_ID; pkt.iNPC_ID = mob->appearanceData.iNPC_ID;
@ -392,17 +356,12 @@ void MobManager::retreatStep(Mob *mob, time_t currTime) {
pkt.iToZ = mob->appearanceData.iZ; pkt.iToZ = mob->appearanceData.iZ;
// notify all nearby players // notify all nearby players
for (Chunk *chunk : chunks) { NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
for (CNSocket *s : chunk->players) {
s->sendPacket(&pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
}
}
} }
// if we got there // if we got there
if (distance <= mob->data["m_iIdleRange"]) { if (distance <= mob->data["m_iIdleRange"]) {
mob->state = MobState::ROAMING; mob->state = MobState::ROAMING;
//std::cout << "roaming state\n";
mob->appearanceData.iHP = mob->maxHealth; mob->appearanceData.iHP = mob->maxHealth;
mob->killedTime = 0; mob->killedTime = 0;
mob->nextAttack = 0; mob->nextAttack = 0;
@ -423,6 +382,11 @@ void MobManager::step(CNServer *serv, time_t currTime) {
if (!settings::SIMULATEMOBS && pair.second->state != MobState::DEAD) if (!settings::SIMULATEMOBS && pair.second->state != MobState::DEAD)
continue; 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) { switch (pair.second->state) {
case MobState::INACTIVE: case MobState::INACTIVE:
// no-op // no-op

View File

@ -94,6 +94,7 @@ namespace MobManager {
void dotDamageOnOff(CNSocket *sock, CNPacketData *data); void dotDamageOnOff(CNSocket *sock, CNPacketData *data);
void npcAttackPc(Mob *mob); void npcAttackPc(Mob *mob);
int hitMob(CNSocket *sock, Mob *mob, int damage);
void killMob(CNSocket *sock, Mob *mob); void killMob(CNSocket *sock, Mob *mob);
void giveReward(CNSocket *sock); void giveReward(CNSocket *sock);
std::pair<int,int> lerp(int, int, int, int, int); std::pair<int,int> lerp(int, int, int, int, int);

View File

@ -1,11 +1,14 @@
#pragma once #pragma once
#include "CNStructs.hpp" #include "CNStructs.hpp"
#include "ChunkManager.hpp"
class BaseNPC { class BaseNPC {
public: public:
sNPCAppearanceData appearanceData; sNPCAppearanceData appearanceData;
NPCClass npcClass; NPCClass npcClass;
std::pair<int, int> chunkPos;
std::vector<Chunk*> currentChunks;
BaseNPC() {}; BaseNPC() {};
BaseNPC(int x, int y, int z, int type, int id) { BaseNPC(int x, int y, int z, int type, int id) {
@ -18,6 +21,9 @@ public:
appearanceData.iConditionBitFlag = 0; appearanceData.iConditionBitFlag = 0;
appearanceData.iBarkerType = 0; appearanceData.iBarkerType = 0;
appearanceData.iNPC_ID = id; 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) { BaseNPC(int x, int y, int z, int type, int id, NPCClass classType) : BaseNPC(x, y, z, type, id) {
npcClass = classType; 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); 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) { void NPCManager::npcVendorBuy(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY)) if (data->size != sizeof(sP_CL2FE_REQ_PC_VENDOR_ITEM_BUY))
return; // malformed packet return; // malformed packet

View File

@ -28,6 +28,8 @@ namespace NPCManager {
void removeNPC(int32_t); void removeNPC(int32_t);
void updateNPCPosition(int32_t, int X, int Y, int Z); 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 npcBarkHandler(CNSocket* sock, CNPacketData* data);
void npcSummonHandler(CNSocket* sock, CNPacketData* data); void npcSummonHandler(CNSocket* sock, CNPacketData* data);
void npcUnsummonHandler(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* mob = MobManager::Mobs[pktdata[i]];
mob->appearanceData.iHP -= amount; int damage = MobManager::hitMob(sock, mob, amount);
if (mob->appearanceData.iHP <= 0)
MobManager::killMob(sock, mob);
respdata[i].eCT = 4; respdata[i].eCT = 4;
respdata[i].iDamage = amount; respdata[i].iDamage = damage;
respdata[i].iID = mob->appearanceData.iNPC_ID; respdata[i].iID = mob->appearanceData.iNPC_ID;
respdata[i].iHP = mob->appearanceData.iHP; respdata[i].iHP = mob->appearanceData.iHP;
respdata[i].iConditionBitFlag = mob->appearanceData.iConditionBitFlag |= iCBFlag; 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* 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].eCT = 4;
respdata[i].iDamage = amount; respdata[i].iDamage = damage;
respdata[i].iID = mob->appearanceData.iNPC_ID; respdata[i].iID = mob->appearanceData.iNPC_ID;
respdata[i].iHP = mob->appearanceData.iHP; 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* 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);
damagedata->eCT = 4; damagedata->eCT = 4;
damagedata->iDamage = amount; damagedata->iDamage = damage;
damagedata->iID = mob->appearanceData.iNPC_ID; damagedata->iID = mob->appearanceData.iNPC_ID;
damagedata->iHP = mob->appearanceData.iHP; damagedata->iHP = mob->appearanceData.iHP;

View File

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