Mobs fight back now.

There is still a lot of tuning, lerping and cleanup to do.
This commit is contained in:
dongresource 2020-09-24 03:18:41 +02:00
parent 006d1000c7
commit 1f18104a6f
2 changed files with 178 additions and 9 deletions

View File

@ -4,6 +4,7 @@
#include "ItemManager.hpp" #include "ItemManager.hpp"
#include "MissionManager.hpp" #include "MissionManager.hpp"
#include <cmath>
#include <assert.h> #include <assert.h>
std::map<int32_t, Mob*> MobManager::Mobs; std::map<int32_t, Mob*> MobManager::Mobs;
@ -59,13 +60,30 @@ 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
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; mob->appearanceData.iHP -= 100;
// wake up sleeping monster // wake up sleeping monster
// TODO: remove client-side bit somehow
mob->appearanceData.iConditionBitFlag &= ~CSB_BIT_MEZ; mob->appearanceData.iConditionBitFlag &= ~CSB_BIT_MEZ;
if (mob->appearanceData.iHP <= 0) if (mob->appearanceData.iHP <= 0)
killMob(sock, mob); 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 = 100;
@ -85,6 +103,38 @@ void MobManager::pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
PlayerManager::sendToViewable(sock, (void*)respbuf, P_FE2CL_PC_ATTACK_NPCs, resplen); PlayerManager::sendToViewable(sock, (void*)respbuf, P_FE2CL_PC_ATTACK_NPCs, resplen);
} }
void MobManager::npcAttackPc(Mob *mob) {
// player pointer has already been validated
Player *plr = PlayerManager::getPlayer(mob->target);
const size_t resplen = sizeof(sP_FE2CL_PC_ATTACK_NPCs_SUCC) + sizeof(sAttackResult);
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, resplen);
sP_FE2CL_NPC_ATTACK_PCs *pkt = (sP_FE2CL_NPC_ATTACK_PCs*)respbuf;
sAttackResult *atk = (sAttackResult*)(respbuf + sizeof(sP_FE2CL_NPC_ATTACK_PCs));
plr->HP += (int)mob->data["m_iPower"]; // already negative
pkt->iNPC_ID = mob->appearanceData.iNPC_ID;
pkt->iPCCnt = 1;
atk->iID = plr->iID;
atk->iDamage = -(int)mob->data["m_iPower"];
atk->iHP = plr->HP;
atk->iHitFlag = 2;
mob->target->sendPacket((void*)respbuf, P_FE2CL_NPC_ATTACK_PCs, resplen);
PlayerManager::sendToViewable(mob->target, (void*)&pkt, P_FE2CL_NPC_ATTACK_PCs, resplen);
if (plr->HP <= 0) {
mob->target = nullptr;
mob->state = MobState::RETREAT;
std::cout << "retreat state\n";
}
}
void MobManager::combatBegin(CNSocket *sock, CNPacketData *data) {} // stub void MobManager::combatBegin(CNSocket *sock, CNPacketData *data) {} // stub
void MobManager::combatEnd(CNSocket *sock, CNPacketData *data) {} // stub void MobManager::combatEnd(CNSocket *sock, CNPacketData *data) {} // stub
void MobManager::dotDamageOnOff(CNSocket *sock, CNPacketData *data) {} // stub void MobManager::dotDamageOnOff(CNSocket *sock, CNPacketData *data) {} // stub
@ -136,10 +186,11 @@ void MobManager::giveReward(CNSocket *sock) {
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->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 << "killed mob " << mob->appearanceData.iNPC_ID << std::endl; 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);
@ -173,6 +224,7 @@ 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);
@ -186,6 +238,68 @@ void MobManager::deadStep(Mob *mob, time_t currTime) {
} }
} }
void MobManager::combatStep(Mob *mob, time_t currTime) {
assert(mob->target != nullptr);
// sanity check: did the target player lose connection?
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))
return;
int distance = hypot(plr->x - mob->appearanceData.iX, plr->y - mob->appearanceData.iY);
/*
* If the mob is close enough to attack, do so. If not, get closer.
* 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
npcAttackPc(mob);
} else if (mob->nextAttack != 0 && currTime >= mob->nextAttack) {
mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100;
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;
mob->nextMovement = currTime + (int)mob->data["m_iDelayTime"] * 100;
INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt);
pkt.iNPC_ID = mob->appearanceData.iNPC_ID;
pkt.iSpeed = mob->data["m_iWalkSpeed"];
pkt.iToX = mob->appearanceData.iX = mob->target->plr->x; // FIXME: lerping
pkt.iToY = mob->appearanceData.iY = mob->target->plr->y;
pkt.iToZ = mob->appearanceData.iZ = mob->target->plr->z;
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));
}
}
}
// TODO: retreat if kited too far
}
void MobManager::roamingStep(Mob *mob, time_t currTime) { void MobManager::roamingStep(Mob *mob, time_t currTime) {
if (mob->nextMovement != 0 && currTime < mob->nextMovement) if (mob->nextMovement != 0 && currTime < mob->nextMovement)
return; return;
@ -225,6 +339,41 @@ void MobManager::roamingStep(Mob *mob, time_t currTime) {
} }
} }
void MobManager::retreatStep(Mob *mob, time_t currTime) {
// distance between spawn point and current location
int distance = hypot(mob->appearanceData.iX - mob->spawnX, mob->appearanceData.iY - mob->spawnY);
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);
pkt.iNPC_ID = mob->appearanceData.iNPC_ID;
pkt.iSpeed = mob->data["m_iWalkSpeed"];
pkt.iToX = mob->appearanceData.iX = mob->spawnX;
pkt.iToY = mob->appearanceData.iY = mob->spawnY;
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));
}
}
}
// 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;
mob->appearanceData.iConditionBitFlag = 0;
}
}
void MobManager::step(CNServer *serv, time_t currTime) { void MobManager::step(CNServer *serv, time_t currTime) {
for (auto& pair : Mobs) { for (auto& pair : Mobs) {
int x = pair.second->appearanceData.iX; int x = pair.second->appearanceData.iX;
@ -242,6 +391,12 @@ void MobManager::step(CNServer *serv, time_t currTime) {
case MobState::ROAMING: case MobState::ROAMING:
roamingStep(pair.second, currTime); roamingStep(pair.second, currTime);
break; break;
case MobState::COMBAT:
combatStep(pair.second, currTime);
break;
case MobState::RETREAT:
retreatStep(pair.second, currTime);
break;
case MobState::DEAD: case MobState::DEAD:
deadStep(pair.second, currTime); deadStep(pair.second, currTime);
break; break;

View File

@ -18,27 +18,38 @@ enum class MobState {
}; };
struct Mob : public BaseNPC { struct Mob : public BaseNPC {
// general
MobState state; MobState state;
const int maxHealth; const int maxHealth;
time_t killedTime = 0;
const int regenTime;
bool despawned = false; // for the sake of death animations
int spawnX; int spawnX;
int spawnY; int spawnY;
int spawnZ; int spawnZ;
const int idleRange; // dead
time_t killedTime = 0;
int regenTime;
bool despawned = false; // for the sake of death animations
// roaming
int idleRange;
time_t nextMovement = 0; time_t nextMovement = 0;
// combat
CNSocket *target = nullptr;
time_t nextAttack = 0;
// temporary; until we're sure what's what // temporary; until we're sure what's what
nlohmann::json data; nlohmann::json data;
Mob(int x, int y, int z, int type, int hp, int angle, nlohmann::json d) Mob(int x, int y, int z, int type, int hp, int angle, nlohmann::json d)
: BaseNPC(x, y, z, type), maxHealth(hp), regenTime(d["m_iRegenTime"]), idleRange(d["m_iIdleRange"]), data(d) { : BaseNPC(x, y, z, type), maxHealth(hp) {
state = MobState::ROAMING; state = MobState::ROAMING;
data = d;
regenTime = data["m_iRegenTime"];
idleRange = data["m_iIdleRange"];
spawnX = appearanceData.iX; spawnX = appearanceData.iX;
spawnY = appearanceData.iY; spawnY = appearanceData.iY;
spawnZ = appearanceData.iZ; spawnZ = appearanceData.iZ;
@ -62,6 +73,8 @@ namespace MobManager {
void step(CNServer*, time_t); void step(CNServer*, time_t);
void deadStep(Mob*, time_t); void deadStep(Mob*, time_t);
void combatStep(Mob*, time_t);
void retreatStep(Mob*, time_t);
void roamingStep(Mob*, time_t); void roamingStep(Mob*, time_t);
void pcAttackNpcs(CNSocket *sock, CNPacketData *data); void pcAttackNpcs(CNSocket *sock, CNPacketData *data);
@ -69,6 +82,7 @@ namespace MobManager {
void combatEnd(CNSocket *sock, CNPacketData *data); void combatEnd(CNSocket *sock, CNPacketData *data);
void dotDamageOnOff(CNSocket *sock, CNPacketData *data); void dotDamageOnOff(CNSocket *sock, CNPacketData *data);
void npcAttackPc(Mob *mob);
void killMob(CNSocket *sock, Mob *mob); void killMob(CNSocket *sock, Mob *mob);
void giveReward(CNSocket *sock); void giveReward(CNSocket *sock);
} }