Mobs roam proper distances now.

* Mob roaming is now integrated into the TransportManager
* Doubled the roaming distance, since it was clearly too small
* Tripled retreat speed
* Made use of NPCManager::sendToViewable() in TransportManager
This commit is contained in:
dongresource 2020-10-12 21:03:18 +02:00
parent a53f38b87d
commit 901e011740
3 changed files with 66 additions and 46 deletions

View File

@ -5,6 +5,7 @@
#include "ItemManager.hpp" #include "ItemManager.hpp"
#include "MissionManager.hpp" #include "MissionManager.hpp"
#include "GroupManager.hpp" #include "GroupManager.hpp"
#include "TransportManager.hpp"
#include <cmath> #include <cmath>
#include <assert.h> #include <assert.h>
@ -366,12 +367,14 @@ void MobManager::combatStep(Mob *mob, time_t currTime) {
} }
} }
/* inline void MobManager::incNextMovement(Mob *mob, time_t currTime) {
* TODO: precalculate a path, lerp through it, repeat. if (currTime == 0)
* The way it works right now, mobs only move around a little bit. This will result currTime = getTime();
* in more natural movement, and will mesh well with pre-calculated paths (for Don Doom,
* Bad Max, etc.) once those have been made. int delay = (int)mob->data["m_iDelayTime"] * 1000;
*/ mob->nextMovement = currTime + delay/2 + rand() % (delay/2);
}
void MobManager::roamingStep(Mob *mob, time_t currTime) { void MobManager::roamingStep(Mob *mob, time_t currTime) {
/* /*
* We reuse nextAttack to avoid scanning for players all the time, but to still * We reuse nextAttack to avoid scanning for players all the time, but to still
@ -389,34 +392,44 @@ void MobManager::roamingStep(Mob *mob, time_t currTime) {
if (mob->nextMovement != 0 && currTime < mob->nextMovement) if (mob->nextMovement != 0 && currTime < mob->nextMovement)
return; return;
incNextMovement(mob, currTime);
int delay = (int)mob->data["m_iDelayTime"] * 1000; // only calculate a new route if we're not already on one
mob->nextMovement = currTime + delay/2 + rand() % (delay/2); auto it = TransportManager::NPCQueues.find(mob->appearanceData.iNPC_ID);
if (it != TransportManager::NPCQueues.end() && it->second.empty())
return;
std::cout << "charting new path for Mob " << (int)mob->appearanceData.iNPC_ID << std::endl;
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;
int farX = xStart + rand() % mob->idleRange;
int farY = yStart + rand() % mob->idleRange;
int speed = mob->data["m_iWalkSpeed"]; int speed = mob->data["m_iWalkSpeed"];
int farX, farY;
int distance; // for short walk detection
/*
* We don't want the mob to just take one step and stop, so we make sure
* it has walked a half-decent distance.
*/
do {
farX = xStart + rand() % mob->idleRange;
farY = yStart + rand() % mob->idleRange;
distance = std::hypot(mob->appearanceData.iX - farX, mob->appearanceData.iY - farY);
} while (distance < 500);
// halve movement speed if snared // halve movement speed if snared
if (mob->appearanceData.iConditionBitFlag & CSB_BIT_DN_MOVE_SPEED) if (mob->appearanceData.iConditionBitFlag & CSB_BIT_DN_MOVE_SPEED)
speed /= 2; speed /= 2;
auto targ = lerp(mob->appearanceData.iX, mob->appearanceData.iY, farX, farY, speed); std::queue<WarpLocation> queue;
WarpLocation from = { mob->appearanceData.iX, mob->appearanceData.iY, mob->appearanceData.iZ };
WarpLocation to = { farX, farY, mob->appearanceData.iZ };
NPCManager::updateNPCPosition(mob->appearanceData.iNPC_ID, targ.first, targ.second, mob->appearanceData.iZ); // add a route to the queue; to be processed in TransportManager::stepNPCPathing()
TransportManager::lerp(&queue, from, to, speed);
pkt.iNPC_ID = mob->appearanceData.iNPC_ID; TransportManager::NPCQueues[mob->appearanceData.iNPC_ID] = queue;
pkt.iSpeed = speed;
pkt.iToX = mob->appearanceData.iX = targ.first;
pkt.iToY = mob->appearanceData.iY = targ.second;
pkt.iToZ = mob->appearanceData.iZ;
// notify all nearby players
NPCManager::sendToViewable(mob, &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) {
@ -432,10 +445,10 @@ void MobManager::retreatStep(Mob *mob, time_t currTime) {
if (distance > 10) { if (distance > 10) {
INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt); INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt);
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, (int)mob->data["m_iRunSpeed"] * 3);
pkt.iNPC_ID = mob->appearanceData.iNPC_ID; pkt.iNPC_ID = mob->appearanceData.iNPC_ID;
pkt.iSpeed = mob->data["m_iRunSpeed"]; pkt.iSpeed = (int)mob->data["m_iRunSpeed"] * 3;
pkt.iToX = mob->appearanceData.iX = targ.first; pkt.iToX = mob->appearanceData.iX = targ.first;
pkt.iToY = mob->appearanceData.iY = targ.second; pkt.iToY = mob->appearanceData.iY = targ.second;
pkt.iToZ = mob->appearanceData.iZ; pkt.iToZ = mob->appearanceData.iZ;
@ -702,7 +715,9 @@ void MobManager::playerTick(CNServer *serv, time_t currTime) {
lastHealTime = currTime; lastHealTime = currTime;
} }
std::pair<int,int> MobManager::getDamage(int attackPower, int defensePower, bool shouldCrit, bool batteryBoost, int attackerStyle, int defenderStyle, int difficulty) { std::pair<int,int> MobManager::getDamage(int attackPower, int defensePower, bool shouldCrit,
bool batteryBoost, int attackerStyle,
int defenderStyle, int difficulty) {
std::pair<int,int> ret = {0, 1}; std::pair<int,int> ret = {0, 1};
if (attackPower + defensePower * 2 == 0) if (attackPower + defensePower * 2 == 0)
return ret; return ret;
@ -816,7 +831,8 @@ void MobManager::pcAttackChars(CNSocket *sock, CNPacketData *data) {
int difficulty = (int)mob->data["m_iNpcLevel"]; int difficulty = (int)mob->data["m_iNpcLevel"];
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW >= 11 + difficulty), NanoManager::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty); damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW >= 11 + difficulty),
NanoManager::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
if (plr->batteryW >= 11 + difficulty) if (plr->batteryW >= 11 + difficulty)
plr->batteryW -= 11 + difficulty; plr->batteryW -= 11 + difficulty;

View File

@ -35,6 +35,7 @@ struct Mob : public BaseNPC {
// roaming // roaming
int idleRange; int idleRange;
time_t nextMovement = 0; time_t nextMovement = 0;
bool staticPath = false;
// combat // combat
CNSocket *target = nullptr; CNSocket *target = nullptr;
@ -50,7 +51,7 @@ struct Mob : public BaseNPC {
data = d; data = d;
regenTime = data["m_iRegenTime"]; regenTime = data["m_iRegenTime"];
idleRange = data["m_iIdleRange"]; idleRange = (int)data["m_iIdleRange"] * 2; // TODO: tuning?
// XXX: temporarily force respawns for Fusions until we implement instancing // XXX: temporarily force respawns for Fusions until we implement instancing
if (regenTime >= 300000000) if (regenTime >= 300000000)
@ -111,5 +112,6 @@ namespace MobManager {
void pcAttackChars(CNSocket *sock, CNPacketData *data); void pcAttackChars(CNSocket *sock, CNPacketData *data);
void resendMobHP(Mob *mob); void resendMobHP(Mob *mob);
void incNextMovement(Mob *mob, time_t currTime=0);
bool aggroCheck(Mob *mob, time_t currTime); bool aggroCheck(Mob *mob, time_t currTime);
} }

View File

@ -4,6 +4,7 @@
#include "NanoManager.hpp" #include "NanoManager.hpp"
#include "TransportManager.hpp" #include "TransportManager.hpp"
#include "TableData.hpp" #include "TableData.hpp"
#include "MobManager.hpp"
#include <unordered_map> #include <unordered_map>
#include <cmath> #include <cmath>
@ -275,12 +276,18 @@ void TransportManager::stepNPCPathing() {
if (NPCManager::NPCs.find(it->first) != NPCManager::NPCs.end()) if (NPCManager::NPCs.find(it->first) != NPCManager::NPCs.end())
npc = NPCManager::NPCs[it->first]; npc = NPCManager::NPCs[it->first];
if (npc == nullptr) { if (npc == nullptr || queue->empty()) {
// pluck out dead path + update iterator // pluck out dead path + update iterator
it = NPCQueues.erase(it); it = NPCQueues.erase(it);
continue; continue;
} }
// do not roam if not roaming
if (npc->npcClass == NPC_MOB && ((Mob*)npc)->state != MobState::ROAMING) {
it++;
continue;
}
WarpLocation point = queue->front(); // get point WarpLocation point = queue->front(); // get point
queue->pop(); // remove point from front of queue queue->pop(); // remove point from front of queue
@ -291,10 +298,6 @@ void TransportManager::stepNPCPathing() {
// update NPC location to update viewables // update NPC location to update viewables
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, point.x, point.y, point.z); NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, point.x, point.y, point.z);
// get chunks in view
auto chunk = ChunkManager::grabChunk(npc->appearanceData.iX, npc->appearanceData.iY, npc->instanceID);
auto chunks = ChunkManager::grabChunks(chunk);
switch (npc->npcClass) { switch (npc->npcClass) {
case NPC_BUS: case NPC_BUS:
INITSTRUCT(sP_FE2CL_TRANSPORTATION_MOVE, busMove); INITSTRUCT(sP_FE2CL_TRANSPORTATION_MOVE, busMove);
@ -306,13 +309,11 @@ void TransportManager::stepNPCPathing() {
busMove.iToZ = point.z; busMove.iToZ = point.z;
busMove.iSpeed = distanceBetween; // set to distance to match how monkeys work busMove.iSpeed = distanceBetween; // set to distance to match how monkeys work
// send packet to players in view NPCManager::sendToViewable(npc, &busMove, P_FE2CL_TRANSPORTATION_MOVE, sizeof(sP_FE2CL_TRANSPORTATION_MOVE));
for (Chunk* chunk : chunks) {
for (CNSocket* s : chunk->players) {
s->sendPacket(&busMove, P_FE2CL_TRANSPORTATION_MOVE, sizeof(sP_FE2CL_TRANSPORTATION_MOVE));
}
}
break; break;
case NPC_MOB:
MobManager::incNextMovement((Mob*)npc);
/* fallthrough */
default: default:
INITSTRUCT(sP_FE2CL_NPC_MOVE, move); INITSTRUCT(sP_FE2CL_NPC_MOVE, move);
move.iNPC_ID = npc->appearanceData.iNPC_ID; move.iNPC_ID = npc->appearanceData.iNPC_ID;
@ -322,16 +323,17 @@ void TransportManager::stepNPCPathing() {
move.iToZ = point.z; move.iToZ = point.z;
move.iSpeed = distanceBetween; move.iSpeed = distanceBetween;
// send packet to players in view NPCManager::sendToViewable(npc, &move, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
for (Chunk* chunk : chunks) {
for (CNSocket* s : chunk->players) {
s->sendPacket(&move, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
}
}
break; break;
} }
queue->push(point); // move processed point to the back to maintain cycle /*
* Move processed point to the back to maintain cycle, unless this is a
* dynamically calculated mob route.
*/
if (!(npc->npcClass == NPC_MOB && !((Mob*)npc)->staticPath))
queue->push(point);
it++; // go to next entry in map it++; // go to next entry in map
} }
} }