(WIP) Point 1: step functions

This commit is contained in:
gsemaj 2022-04-12 17:12:08 -04:00
parent 0c4cdaeabf
commit 3d572432b3
6 changed files with 168 additions and 163 deletions

View File

@ -38,6 +38,10 @@ int32_t Player::getID() {
return iID; return iID;
} }
void Player::step(time_t currTime) {
// no-op
}
int CombatNPC::takeDamage(EntityRef src, int amt) { int CombatNPC::takeDamage(EntityRef src, int amt) {
/* REFACTOR: all of this logic is strongly coupled to mobs. /* REFACTOR: all of this logic is strongly coupled to mobs.
@ -96,6 +100,10 @@ int32_t CombatNPC::getID() {
return id; return id;
} }
void CombatNPC::step(time_t currTime) {
// stubbed
}
static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shouldCrit, static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shouldCrit,
bool batteryBoost, int attackerStyle, bool batteryBoost, int attackerStyle,
int defenderStyle, int difficulty) { int defenderStyle, int difficulty) {

View File

@ -83,6 +83,8 @@ public:
virtual bool isAlive() = 0; virtual bool isAlive() = 0;
virtual int getCurrentHP() = 0; virtual int getCurrentHP() = 0;
virtual int32_t getID() = 0; virtual int32_t getID() = 0;
virtual void step(time_t currTime) = 0;
}; };
/* /*
@ -122,8 +124,6 @@ struct CombatNPC : public BaseNPC, public ICombatant {
int level = 0; int level = 0;
int speed = 300; int speed = 300;
void (*_stepAI)(CombatNPC*, time_t) = nullptr;
CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP) CombatNPC(int x, int y, int z, int angle, uint64_t iID, int t, int id, int maxHP)
: BaseNPC(angle, iID, t, id), maxHealth(maxHP) { : BaseNPC(angle, iID, t, id), maxHealth(maxHP) {
spawnX = x; spawnX = x;
@ -131,11 +131,6 @@ struct CombatNPC : public BaseNPC, public ICombatant {
spawnZ = z; spawnZ = z;
} }
virtual void stepAI(time_t currTime) {
if (_stepAI != nullptr)
_stepAI(this, currTime);
}
virtual bool isExtant() override { return hp > 0; } virtual bool isExtant() override { return hp > 0; }
virtual int takeDamage(EntityRef src, int amt) override; virtual int takeDamage(EntityRef src, int amt) override;
@ -143,6 +138,7 @@ struct CombatNPC : public BaseNPC, public ICombatant {
virtual bool isAlive() override; virtual bool isAlive() override;
virtual int getCurrentHP() override; virtual int getCurrentHP() override;
virtual int32_t getID() override; virtual int32_t getID() override;
virtual void step(time_t currTime) override;
}; };
// Mob is in MobAI.hpp, Player is in Player.hpp // Mob is in MobAI.hpp, Player is in Player.hpp

View File

@ -14,8 +14,6 @@ using namespace MobAI;
bool MobAI::simulateMobs = settings::SIMULATEMOBS; bool MobAI::simulateMobs = settings::SIMULATEMOBS;
static void roamingStep(Mob *mob, time_t currTime);
/* /*
* Dynamic lerp; distinct from Transport::lerp(). This one doesn't care about height and * Dynamic lerp; distinct from Transport::lerp(). This one doesn't care about height and
* only returns the first step, since the rest will need to be recalculated anyway if chasing player. * only returns the first step, since the rest will need to be recalculated anyway if chasing player.
@ -441,49 +439,49 @@ static void drainMobHP(Mob *mob, int amount) {
Combat::killMob(mob->target, mob); Combat::killMob(mob->target, mob);
} }
static void deadStep(Mob *mob, time_t currTime) { void Mob::deadStep(time_t currTime) {
// 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 (killedTime != 0 && !despawned && currTime - killedTime > 2000) {
mob->despawned = true; despawned = true;
INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt); INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt);
pkt.iNPC_ID = mob->id; pkt.iNPC_ID = id;
NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT)); NPCManager::sendToViewable(this, &pkt, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT));
// if it was summoned, mark it for removal // if it was summoned, mark it for removal
if (mob->summoned) { if (summoned) {
std::cout << "[INFO] Queueing killed summoned mob for removal" << std::endl; std::cout << "[INFO] Queueing killed summoned mob for removal" << std::endl;
NPCManager::queueNPCRemoval(mob->id); NPCManager::queueNPCRemoval(id);
return; return;
} }
// pre-set spawn coordinates if not marked for removal // pre-set spawn coordinates if not marked for removal
mob->x = mob->spawnX; x = spawnX;
mob->y = mob->spawnY; y = spawnY;
mob->z = mob->spawnZ; z = spawnZ;
} }
// to guide their groupmates, group leaders still need to move despite being dead // to guide their groupmates, group leaders still need to move despite being dead
if (mob->groupLeader == mob->id) if (groupLeader == id)
roamingStep(mob, currTime); roamingStep(currTime);
if (mob->killedTime != 0 && currTime - mob->killedTime < mob->regenTime * 100) if (killedTime != 0 && currTime - killedTime < regenTime * 100)
return; return;
std::cout << "respawning mob " << mob->id << " with HP = " << mob->maxHealth << std::endl; std::cout << "respawning mob " << id << " with HP = " << maxHealth << std::endl;
mob->hp = mob->maxHealth; hp = maxHealth;
mob->state = MobState::ROAMING; state = MobState::ROAMING;
// if mob is a group leader/follower, spawn where the group is. // if mob is a group leader/follower, spawn where the group is.
if (mob->groupLeader != 0) { if (groupLeader != 0) {
if (NPCManager::NPCs.find(mob->groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[mob->groupLeader]->kind == EntityType::MOB) { if (NPCManager::NPCs.find(groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[groupLeader]->kind == EntityType::MOB) {
Mob* leaderMob = (Mob*)NPCManager::NPCs[mob->groupLeader]; Mob* leaderMob = (Mob*)NPCManager::NPCs[groupLeader];
mob->x = leaderMob->x + mob->offsetX; x = leaderMob->x + offsetX;
mob->y = leaderMob->y + mob->offsetY; y = leaderMob->y + offsetY;
mob->z = leaderMob->z; z = leaderMob->z;
} else { } else {
std::cout << "[WARN] deadStep: mob cannot find it's leader!" << std::endl; std::cout << "[WARN] deadStep: mob cannot find it's leader!" << std::endl;
} }
@ -491,127 +489,127 @@ static void deadStep(Mob *mob, time_t currTime) {
INITSTRUCT(sP_FE2CL_NPC_NEW, pkt); INITSTRUCT(sP_FE2CL_NPC_NEW, pkt);
pkt.NPCAppearanceData = mob->getAppearanceData(); pkt.NPCAppearanceData = getAppearanceData();
// notify all nearby players // notify all nearby players
NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_NEW, sizeof(sP_FE2CL_NPC_NEW)); NPCManager::sendToViewable(this, &pkt, P_FE2CL_NPC_NEW, sizeof(sP_FE2CL_NPC_NEW));
} }
static void combatStep(Mob *mob, time_t currTime) { void Mob::combatStep(time_t currTime) {
assert(mob->target != nullptr); assert(target != nullptr);
// lose aggro if the player lost connection // lose aggro if the player lost connection
if (PlayerManager::players.find(mob->target) == PlayerManager::players.end()) { if (PlayerManager::players.find(target) == PlayerManager::players.end()) {
mob->target = nullptr; target = nullptr;
mob->state = MobState::RETREAT; state = MobState::RETREAT;
if (!aggroCheck(mob, currTime)) { if (!aggroCheck(this, currTime)) {
clearDebuff(mob); clearDebuff(this);
if (mob->groupLeader != 0) if (groupLeader != 0)
groupRetreat(mob); groupRetreat(this);
} }
return; return;
} }
Player *plr = PlayerManager::getPlayer(mob->target); Player *plr = PlayerManager::getPlayer(target);
// lose aggro if the player became invulnerable or died // lose aggro if the player became invulnerable or died
if (plr->HP <= 0 if (plr->HP <= 0
|| (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) { || (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) {
mob->target = nullptr; target = nullptr;
mob->state = MobState::RETREAT; state = MobState::RETREAT;
if (!aggroCheck(mob, currTime)) { if (!aggroCheck(this, currTime)) {
clearDebuff(mob); clearDebuff(this);
if (mob->groupLeader != 0) if (groupLeader != 0)
groupRetreat(mob); groupRetreat(this);
} }
return; return;
} }
// drain // drain
if (mob->skillStyle < 0 && (mob->lastDrainTime == 0 || currTime - mob->lastDrainTime >= 1000) if (skillStyle < 0 && (lastDrainTime == 0 || currTime - lastDrainTime >= 1000)
&& mob->cbf & CSB_BIT_BOUNDINGBALL) { && cbf & CSB_BIT_BOUNDINGBALL) {
drainMobHP(mob, mob->maxHealth / 20); // lose 5% every second drainMobHP(this, maxHealth / 20); // lose 5% every second
mob->lastDrainTime = currTime; lastDrainTime = currTime;
} }
// if drain killed the mob, return early // if drain killed the mob, return early
if (mob->hp <= 0) if (hp <= 0)
return; return;
// unbuffing // unbuffing
std::unordered_map<int32_t, time_t>::iterator it = mob->unbuffTimes.begin(); std::unordered_map<int32_t, time_t>::iterator it = unbuffTimes.begin();
while (it != mob->unbuffTimes.end()) { while (it != unbuffTimes.end()) {
if (currTime >= it->second) { if (currTime >= it->second) {
mob->cbf &= ~it->first; cbf &= ~it->first;
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1); INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
pkt1.eCT = 2; pkt1.eCT = 2;
pkt1.iID = mob->id; pkt1.iID = id;
pkt1.iConditionBitFlag = mob->cbf; pkt1.iConditionBitFlag = cbf;
NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); NPCManager::sendToViewable(this, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
it = mob->unbuffTimes.erase(it); it = unbuffTimes.erase(it);
} else { } else {
it++; it++;
} }
} }
// skip attack if stunned or asleep // skip attack if stunned or asleep
if (mob->cbf & (CSB_BIT_STUN|CSB_BIT_MEZ)) { if (cbf & (CSB_BIT_STUN|CSB_BIT_MEZ)) {
mob->skillStyle = -1; // in this case we also reset the any outlying abilities the mob might be winding up. skillStyle = -1; // in this case we also reset the any outlying abilities the mob might be winding up.
return; return;
} }
int distance = hypot(plr->x - mob->x, plr->y - mob->y); int distance = hypot(plr->x - x, plr->y - y);
int mobRange = (int)mob->data["m_iAtkRange"] + (int)mob->data["m_iRadius"]; int mobRange = (int)data["m_iAtkRange"] + (int)data["m_iRadius"];
if (currTime >= mob->nextAttack) { if (currTime >= nextAttack) {
if (mob->skillStyle != -1 || distance <= mobRange || Rand::rand(20) == 0) // while not in attack range, 1 / 20 chance. if (skillStyle != -1 || distance <= mobRange || Rand::rand(20) == 0) // while not in attack range, 1 / 20 chance.
useAbilities(mob, currTime); useAbilities(this, currTime);
if (mob->target == nullptr) if (target == nullptr)
return; return;
} }
int distanceToTravel = INT_MAX; int distanceToTravel = INT_MAX;
int speed = mob->speed; int speed = speed;
// movement logic: move when out of range but don't move while casting a skill // movement logic: move when out of range but don't move while casting a skill
if (distance > mobRange && mob->skillStyle == -1) { if (distance > mobRange && skillStyle == -1) {
if (mob->nextMovement != 0 && currTime < mob->nextMovement) if (nextMovement != 0 && currTime < nextMovement)
return; return;
mob->nextMovement = currTime + 400; nextMovement = currTime + 400;
if (currTime >= mob->nextAttack) if (currTime >= nextAttack)
mob->nextAttack = 0; nextAttack = 0;
// halve movement speed if snared // halve movement speed if snared
if (mob->cbf & CSB_BIT_DN_MOVE_SPEED) if (cbf & CSB_BIT_DN_MOVE_SPEED)
speed /= 2; speed /= 2;
int targetX = plr->x; int targetX = plr->x;
int targetY = plr->y; int targetY = plr->y;
if (mob->groupLeader != 0) { if (groupLeader != 0) {
targetX += mob->offsetX*distance/(mob->idleRange + 1); targetX += offsetX*distance/(idleRange + 1);
targetY += mob->offsetY*distance/(mob->idleRange + 1); targetY += offsetY*distance/(idleRange + 1);
} }
distanceToTravel = std::min(distance-mobRange+1, speed*2/5); distanceToTravel = std::min(distance-mobRange+1, speed*2/5);
auto targ = lerp(mob->x, mob->y, targetX, targetY, distanceToTravel); auto targ = lerp(x, y, targetX, targetY, distanceToTravel);
if (distanceToTravel < speed*2/5 && currTime >= mob->nextAttack) if (distanceToTravel < speed*2/5 && currTime >= nextAttack)
mob->nextAttack = 0; nextAttack = 0;
NPCManager::updateNPCPosition(mob->id, targ.first, targ.second, mob->z, mob->instanceID, mob->angle); NPCManager::updateNPCPosition(id, targ.first, targ.second, z, instanceID, angle);
INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt); INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt);
pkt.iNPC_ID = mob->id; pkt.iNPC_ID = id;
pkt.iSpeed = speed; pkt.iSpeed = speed;
pkt.iToX = mob->x = targ.first; pkt.iToX = x = targ.first;
pkt.iToY = mob->y = targ.second; pkt.iToY = y = targ.second;
pkt.iToZ = plr->z; pkt.iToZ = plr->z;
pkt.iMoveStyle = 1; pkt.iMoveStyle = 1;
// notify all nearby players // notify all nearby players
NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); NPCManager::sendToViewable(this, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
} }
/* attack logic /* attack logic
@ -619,21 +617,21 @@ static void combatStep(Mob *mob, time_t currTime) {
* if the mob is one move interval away, we should just start attacking anyways. * if the mob is one move interval away, we should just start attacking anyways.
*/ */
if (distance <= mobRange || distanceToTravel < speed*2/5) { if (distance <= mobRange || distanceToTravel < speed*2/5) {
if (mob->nextAttack == 0 || currTime >= mob->nextAttack) { if (nextAttack == 0 || currTime >= nextAttack) {
mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100; nextAttack = currTime + (int)data["m_iDelayTime"] * 100;
Combat::npcAttackPc(mob, currTime); Combat::npcAttackPc(this, currTime);
} }
} }
// retreat if the player leaves combat range // retreat if the player leaves combat range
int xyDistance = hypot(plr->x - mob->roamX, plr->y - mob->roamY); int xyDistance = hypot(plr->x - roamX, plr->y - roamY);
distance = hypot(xyDistance, plr->z - mob->roamZ); distance = hypot(xyDistance, plr->z - roamZ);
if (distance >= mob->data["m_iCombatRange"]) { if (distance >= data["m_iCombatRange"]) {
mob->target = nullptr; target = nullptr;
mob->state = MobState::RETREAT; state = MobState::RETREAT;
clearDebuff(mob); clearDebuff(this);
if (mob->groupLeader != 0) if (groupLeader != 0)
groupRetreat(mob); groupRetreat(this);
} }
} }
@ -645,23 +643,23 @@ void MobAI::incNextMovement(Mob *mob, time_t currTime) {
mob->nextMovement = currTime + delay/2 + Rand::rand(delay/2); mob->nextMovement = currTime + delay/2 + Rand::rand(delay/2);
} }
static void roamingStep(Mob *mob, time_t currTime) { void Mob::roamingStep(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
* do so more often than if we waited for nextMovement (which is way too slow). * do so more often than if we waited for nextMovement (which is way too slow).
* In the case of group leaders, this step will be called by dead mobs, so disable attack. * In the case of group leaders, this step will be called by dead mobs, so disable attack.
*/ */
if (mob->state != MobState::DEAD && (mob->nextAttack == 0 || currTime >= mob->nextAttack)) { if (state != MobState::DEAD && (nextAttack == 0 || currTime >= nextAttack)) {
mob->nextAttack = currTime + 500; nextAttack = currTime + 500;
if (aggroCheck(mob, currTime)) if (aggroCheck(this, currTime))
return; return;
} }
// no random roaming if the mob already has a set path // no random roaming if the mob already has a set path
if (mob->staticPath) if (staticPath)
return; return;
if (mob->groupLeader != 0 && mob->groupLeader != mob->id) // don't roam by yourself without group leader if (groupLeader != 0 && groupLeader != id) // don't roam by yourself without group leader
return; return;
/* /*
@ -669,62 +667,62 @@ static void roamingStep(Mob *mob, time_t currTime) {
* Transport::stepNPCPathing() (which ticks at a higher frequency than nextMovement), * Transport::stepNPCPathing() (which ticks at a higher frequency than nextMovement),
* so we don't have to check if there's already entries in the queue since we know there won't be. * so we don't have to check if there's already entries in the queue since we know there won't be.
*/ */
if (mob->nextMovement != 0 && currTime < mob->nextMovement) if (nextMovement != 0 && currTime < nextMovement)
return; return;
incNextMovement(mob, currTime); incNextMovement(this, currTime);
int xStart = mob->spawnX - mob->idleRange/2; int xStart = spawnX - idleRange/2;
int yStart = mob->spawnY - mob->idleRange/2; int yStart = spawnY - idleRange/2;
int speed = mob->speed; int speed = speed;
// some mobs don't move (and we mustn't divide/modulus by zero) // some mobs don't move (and we mustn't divide/modulus by zero)
if (mob->idleRange == 0 || speed == 0) if (idleRange == 0 || speed == 0)
return; return;
int farX, farY, distance; int farX, farY, distance;
int minDistance = mob->idleRange / 2; int minDistance = idleRange / 2;
// pick a random destination // pick a random destination
farX = xStart + Rand::rand(mob->idleRange); farX = xStart + Rand::rand(idleRange);
farY = yStart + Rand::rand(mob->idleRange); farY = yStart + Rand::rand(idleRange);
distance = std::abs(std::max(farX - mob->x, farY - mob->y)); distance = std::abs(std::max(farX - x, farY - y));
if (distance == 0) if (distance == 0)
distance += 1; // hack to avoid FPE distance += 1; // hack to avoid FPE
// if it's too short a walk, go further in that direction // if it's too short a walk, go further in that direction
farX = mob->x + (farX - mob->x) * minDistance / distance; farX = x + (farX - x) * minDistance / distance;
farY = mob->y + (farY - mob->y) * minDistance / distance; farY = y + (farY - y) * minDistance / distance;
// but don't got out of bounds // but don't got out of bounds
farX = std::clamp(farX, xStart, xStart + mob->idleRange); farX = std::clamp(farX, xStart, xStart + idleRange);
farY = std::clamp(farY, yStart, yStart + mob->idleRange); farY = std::clamp(farY, yStart, yStart + idleRange);
// halve movement speed if snared // halve movement speed if snared
if (mob->cbf & CSB_BIT_DN_MOVE_SPEED) if (cbf & CSB_BIT_DN_MOVE_SPEED)
speed /= 2; speed /= 2;
std::queue<Vec3> queue; std::queue<Vec3> queue;
Vec3 from = { mob->x, mob->y, mob->z }; Vec3 from = { x, y, z };
Vec3 to = { farX, farY, mob->z }; Vec3 to = { farX, farY, z };
// add a route to the queue; to be processed in Transport::stepNPCPathing() // add a route to the queue; to be processed in Transport::stepNPCPathing()
Transport::lerp(&queue, from, to, speed); Transport::lerp(&queue, from, to, speed);
Transport::NPCQueues[mob->id] = queue; Transport::NPCQueues[id] = queue;
if (mob->groupLeader != 0 && mob->groupLeader == mob->id) { if (groupLeader != 0 && groupLeader == id) {
// make followers follow this npc. // make followers follow this npc.
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
if (mob->groupMember[i] == 0) if (groupMember[i] == 0)
break; break;
if (NPCManager::NPCs.find(mob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[mob->groupMember[i]]->kind != EntityType::MOB) { if (NPCManager::NPCs.find(groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[groupMember[i]]->kind != EntityType::MOB) {
std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl; std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl;
continue; continue;
} }
std::queue<Vec3> queue2; std::queue<Vec3> queue2;
Mob* followerMob = (Mob*)NPCManager::NPCs[mob->groupMember[i]]; Mob* followerMob = (Mob*)NPCManager::NPCs[groupMember[i]];
from = { followerMob->x, followerMob->y, followerMob->z }; from = { followerMob->x, followerMob->y, followerMob->z };
to = { farX + followerMob->offsetX, farY + followerMob->offsetY, followerMob->z }; to = { farX + followerMob->offsetX, farY + followerMob->offsetY, followerMob->z };
Transport::lerp(&queue2, from, to, speed); Transport::lerp(&queue2, from, to, speed);
@ -733,78 +731,77 @@ static void roamingStep(Mob *mob, time_t currTime) {
} }
} }
static void retreatStep(Mob *mob, time_t currTime) { void Mob::retreatStep(time_t currTime) {
if (mob->nextMovement != 0 && currTime < mob->nextMovement) if (nextMovement != 0 && currTime < nextMovement)
return; return;
mob->nextMovement = currTime + 400; nextMovement = currTime + 400;
// distance between spawn point and current location // distance between spawn point and current location
int distance = hypot(mob->x - mob->roamX, mob->y - mob->roamY); int distance = hypot(x - roamX, y - roamY);
//if (distance > mob->data["m_iIdleRange"]) { //if (distance > mob->data["m_iIdleRange"]) {
if (distance > 10) { if (distance > 10) {
INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt); INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt);
auto targ = lerp(mob->x, mob->y, mob->roamX, mob->roamY, (int)mob->speed*4/5); auto targ = lerp(x, y, roamX, roamY, (int)speed*4/5);
pkt.iNPC_ID = mob->id; pkt.iNPC_ID = id;
pkt.iSpeed = (int)mob->speed * 2; pkt.iSpeed = (int)speed * 2;
pkt.iToX = mob->x = targ.first; pkt.iToX = x = targ.first;
pkt.iToY = mob->y = targ.second; pkt.iToY = y = targ.second;
pkt.iToZ = mob->z = mob->spawnZ; pkt.iToZ = z = spawnZ;
pkt.iMoveStyle = 1; pkt.iMoveStyle = 1;
// notify all nearby players // notify all nearby players
NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); NPCManager::sendToViewable(this, &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"]) {
if (distance <= 10) { // retreat back to the spawn point if (distance <= 10) { // retreat back to the spawn point
mob->state = MobState::ROAMING; state = MobState::ROAMING;
mob->hp = mob->maxHealth; hp = maxHealth;
mob->killedTime = 0; killedTime = 0;
mob->nextAttack = 0; nextAttack = 0;
mob->cbf = 0; cbf = 0;
// cast a return home heal spell, this is the right way(tm) // cast a return home heal spell, this is the right way(tm)
std::vector<int> targetData = {1, 0, 0, 0, 0}; std::vector<int> targetData = {1, 0, 0, 0, 0};
for (auto& pwr : Abilities::Powers) for (auto& pwr : Abilities::Powers)
if (pwr.skillType == Abilities::SkillTable[110].skillType) if (pwr.skillType == Abilities::SkillTable[110].skillType)
pwr.handle(mob->id, targetData, 110, Abilities::SkillTable[110].durationTime[0], Abilities::SkillTable[110].powerIntensity[0]); pwr.handle(id, targetData, 110, Abilities::SkillTable[110].durationTime[0], Abilities::SkillTable[110].powerIntensity[0]);
// clear outlying debuffs // clear outlying debuffs
clearDebuff(mob); clearDebuff(this);
} }
} }
void MobAI::step(CombatNPC *npc, time_t currTime) { void Mob::step(time_t currTime) {
assert(npc->kind == EntityType::MOB); assert(kind == EntityType::MOB);
auto mob = (Mob*)npc;
if (mob->playersInView < 0) if (playersInView < 0)
std::cout << "[WARN] Weird playerview value " << mob->playersInView << std::endl; std::cout << "[WARN] Weird playerview value " << playersInView << std::endl;
// skip mob movement and combat if disabled or not in view // skip mob movement and combat if disabled or not in view
if ((!simulateMobs || mob->playersInView == 0) && mob->state != MobState::DEAD if ((!simulateMobs || playersInView == 0) && state != MobState::DEAD
&& mob->state != MobState::RETREAT) && state != MobState::RETREAT)
return; return;
switch (mob->state) { switch (state) {
case MobState::INACTIVE: case MobState::INACTIVE:
// no-op // no-op
break; break;
case MobState::ROAMING: case MobState::ROAMING:
roamingStep(mob, currTime); roamingStep(currTime);
break; break;
case MobState::COMBAT: case MobState::COMBAT:
combatStep(mob, currTime); combatStep(currTime);
break; break;
case MobState::RETREAT: case MobState::RETREAT:
retreatStep(mob, currTime); retreatStep(currTime);
break; break;
case MobState::DEAD: case MobState::DEAD:
deadStep(mob, currTime); deadStep(currTime);
break; break;
} }
} }

View File

@ -11,11 +11,6 @@ enum class MobState {
DEAD DEAD
}; };
namespace MobAI {
// needs to be declared before Mob's constructor
void step(CombatNPC*, time_t);
};
struct Mob : public CombatNPC { struct Mob : public CombatNPC {
// general // general
MobState state = MobState::INACTIVE; MobState state = MobState::INACTIVE;
@ -78,7 +73,6 @@ struct Mob : public CombatNPC {
hp = maxHealth; hp = maxHealth;
kind = EntityType::MOB; kind = EntityType::MOB;
_stepAI = MobAI::step;
} }
// constructor for /summon // constructor for /summon
@ -89,6 +83,14 @@ struct Mob : public CombatNPC {
~Mob() {} ~Mob() {}
virtual void step(time_t currTime) override;
// we may or may not want these to be generalized to all CombatNPCs later
void roamingStep(time_t currTime);
void combatStep(time_t currTime);
void retreatStep(time_t currTime);
void deadStep(time_t currTime);
auto operator[](std::string s) { auto operator[](std::string s) {
return data[s]; return data[s];
} }

View File

@ -355,7 +355,7 @@ static void step(CNServer *serv, time_t currTime) {
continue; continue;
auto npc = (CombatNPC*)pair.second; auto npc = (CombatNPC*)pair.second;
npc->stepAI(currTime); npc->step(currTime);
} }
// deallocate all NPCs queued for removal // deallocate all NPCs queued for removal

View File

@ -96,5 +96,7 @@ struct Player : public Entity, public ICombatant {
virtual int getCurrentHP() override; virtual int getCurrentHP() override;
virtual int32_t getID() override; virtual int32_t getID() override;
virtual void step(time_t currTime) override;
sPCAppearanceData getAppearanceData(); sPCAppearanceData getAppearanceData();
}; };