Implement buff handling for CombatNPC

This commit is contained in:
gsemaj 2023-07-25 13:49:40 -04:00
parent 82bee2051a
commit e325f7a40b
No known key found for this signature in database
GPG Key ID: 24B96BAA40497929
9 changed files with 224 additions and 131 deletions

View File

@ -1,9 +1,12 @@
#include "Abilities.hpp"
#include "servers/CNShardServer.hpp"
#include "NPCManager.hpp"
#include "PlayerManager.hpp"
#include "Buffs.hpp"
#include "Nanos.hpp"
#include "MobAI.hpp"
using namespace Abilities;
@ -55,7 +58,7 @@ static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombat
duration = skill->durationTime[power];
strength = skill->values[0][power];
BuffStack debuff = {
duration, // ticks
(duration * 100) / MS_PER_COMBAT_TICK, // ticks
strength, // value
source->getRef(), // source
BuffClass::NANO, // buff class
@ -64,9 +67,10 @@ static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombat
target->addBuff(timeBuffId,
[](EntityRef self, Buff* buff, int status, BuffStack* stack) {
Buffs::timeBuffUpdate(self, buff, status, stack);
if(status == ETBU_DEL) Buffs::timeBuffTimeout(self);
},
[](EntityRef self, Buff* buff, time_t currTime) {
// no-op
Buffs::timeBuffTick(self, buff);
},
&debuff);
}
@ -82,27 +86,53 @@ static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombat
}
static SkillResult handleSkillLeech(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
// TODO abilities
return SkillResult();
EntityRef sourceRef = source->getRef();
int heal = skill->values[0][power];
int healed = source->heal(sourceRef, heal);
int damage = heal * 2;
int dealt = target->takeDamage(sourceRef, damage);
sSkillResult_Leech result{};
result.Damage.eCT = target->getCharType();
result.Damage.iID = target->getID();
result.Damage.bProtected = dealt <= 0;
result.Damage.iDamage = dealt;
result.Damage.iHP = target->getCurrentHP();
result.Heal.eCT = result.Damage.eCT;
result.Heal.iID = result.Damage.iID;
result.Heal.iHealHP = healed;
result.Heal.iHP = source->getCurrentHP();
return SkillResult(sizeof(sSkillResult_Leech), &result);
}
static SkillResult handleSkillBuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
int duration = skill->durationTime[power];
int strength = skill->values[0][power];
BuffStack passiveBuff = {
skill->drainType == SkillDrainType::PASSIVE ? 1 : duration, // ticks
skill->drainType == SkillDrainType::PASSIVE ? 1 : (duration * 100) / MS_PER_COMBAT_TICK, // ticks
strength, // value
source->getRef(), // source
source == target ? BuffClass::NANO : BuffClass::GROUP_NANO, // buff class
};
int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
SkillDrainType drainType = skill->drainType;
if(!target->addBuff(timeBuffId,
[](EntityRef self, Buff* buff, int status, BuffStack* stack) {
Buffs::timeBuffUpdate(self, buff, status, stack);
[drainType](EntityRef self, Buff* buff, int status, BuffStack* stack) {
if(buff->id == ECSB_BOUNDINGBALL) {
// drain
ICombatant* combatant = dynamic_cast<ICombatant*>(self.getEntity());
combatant->takeDamage(buff->getLastSource(), 0); // aggro
}
if(drainType == SkillDrainType::ACTIVE && status == ETBU_DEL)
Buffs::timeBuffTimeout(self);
},
[](EntityRef self, Buff* buff, time_t currTime) {
// no-op
if(buff->id == ECSB_BOUNDINGBALL)
Buffs::tickDrain(self, buff); // drain
},
&passiveBuff)) return SkillResult(); // no result if already buffed
@ -335,6 +365,12 @@ void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector<ICombatant*>
pkt->iSkillID = skillID;
pkt->eST = (int32_t)skill->skillType;
pkt->iTargetCnt = (int32_t)results.size();
if(npc.kind == EntityKind::MOB) {
Mob* mob = dynamic_cast<Mob*>(entity);
pkt->iValue1 = mob->hitX;
pkt->iValue2 = mob->hitY;
pkt->iValue3 = mob->hitZ;
}
attachSkillResults(results, (uint8_t*)(pkt + 1));
NPCManager::sendToViewable(entity, pkt, P_FE2CL_NPC_SKILL_HIT, resplen);
@ -411,12 +447,6 @@ int Abilities::getCSTBFromST(SkillType skillType) {
case SkillType::PROTECTINFECTION:
result = ECSB_PROTECT_INFECTION;
break;
case SkillType::SNARE:
result = ECSB_DN_MOVE_SPEED;
break;
case SkillType::SLEEP:
result = ECSB_MEZ;
break;
case SkillType::MINIMAPENEMY:
result = ECSB_MINIMAP_ENEMY;
break;
@ -429,15 +459,9 @@ int Abilities::getCSTBFromST(SkillType skillType) {
case SkillType::REWARDCASH:
result = ECSB_REWARD_CASH;
break;
case SkillType::INFECTIONDAMAGE:
result = ECSB_INFECTION;
break;
case SkillType::FREEDOM:
result = ECSB_FREEDOM;
break;
case SkillType::BOUNDINGBALL:
result = ECSB_BOUNDINGBALL;
break;
case SkillType::INVULNERABLE:
result = ECSB_INVULNERABLE;
break;
@ -446,6 +470,22 @@ int Abilities::getCSTBFromST(SkillType skillType) {
break;
case SkillType::NANOSTIMPAK:
result = ECSB_STIMPAKSLOT1;
// shift as necessary
break;
case SkillType::SNARE:
result = ECSB_DN_MOVE_SPEED;
break;
case SkillType::STUN:
result = ECSB_STUN;
break;
case SkillType::SLEEP:
result = ECSB_MEZ;
break;
case SkillType::INFECTIONDAMAGE:
result = ECSB_INFECTION;
break;
case SkillType::BOUNDINGBALL:
result = ECSB_BOUNDINGBALL;
break;
default:
break;

View File

@ -95,6 +95,12 @@ int Buff::getValue(BuffValueSelector selector) {
return value;
}
EntityRef Buff::getLastSource() {
if(stacks.empty())
return self;
return stacks.back().source;
}
bool Buff::isStale() {
return stacks.empty();
}
@ -137,15 +143,55 @@ void Buffs::timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* st
self.sock->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
}
void Buffs::timeBuffTick(EntityRef self, Buff* buff) {
if(self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB)
return; // not implemented
Entity* entity = self.getEntity();
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK, pkt);
pkt.eCT = combatant->getCharType();
pkt.iID = combatant->getID();
pkt.iTB_ID = buff->id;
NPCManager::sendToViewable(entity, &pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
}
void Buffs::timeBuffTimeout(EntityRef self) {
if(self.kind != EntityKind::PLAYER && self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB)
return; // not a combatant
Entity* entity = self.getEntity();
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt); // send a buff timeout to other players
pkt.eCT = combatant->getCharType();
int32_t eCharType = combatant->getCharType();
pkt.eCT = eCharType == 4 ? 2 : eCharType; // convention not followed by client here
pkt.iID = combatant->getID();
pkt.iConditionBitFlag = combatant->getCompositeCondition();
NPCManager::sendToViewable(entity, &pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
}
void Buffs::tickDrain(EntityRef self, Buff* buff) {
if(self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB)
return; // not implemented
Entity* entity = self.getEntity();
ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
int damage = combatant->takeDamage(buff->getLastSource(), combatant->getMaxHP() / 100);
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage);
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, resplen);
sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf;
pkt->iID = self.id;
pkt->eCT = combatant->getCharType();
pkt->iTB_ID = ECSB_BOUNDINGBALL;
sSkillResult_Damage *drain = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
drain->iDamage = damage;
drain->iHP = combatant->getCurrentHP();
drain->eCT = pkt->eCT;
drain->iID = pkt->iID;
NPCManager::sendToViewable(self.getEntity(), (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
}
#pragma endregion

View File

@ -66,6 +66,7 @@ public:
BuffClass maxClass();
int getValue(BuffValueSelector selector);
EntityRef getLastSource();
/*
* In general, a Buff object won't exist
@ -86,5 +87,7 @@ public:
namespace Buffs {
void timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack);
void timeBuffTick(EntityRef self, Buff* buff);
void timeBuffTimeout(EntityRef self);
void tickDrain(EntityRef self, Buff* buff);
}

View File

@ -24,13 +24,11 @@ bool Player::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCal
if(!isAlive())
return false;
EntityRef self = PlayerManager::getSockFromID(iID);
if(!hasBuff(buffId)) {
buffs[buffId] = new Buff(buffId, self, onUpdate, onTick, stack);
buffs[buffId] = new Buff(buffId, getRef(), onUpdate, onTick, stack);
return true;
}
buffs[buffId]->updateCallbacks(onUpdate, onTick);
buffs[buffId]->addStack(stack);
return false;
@ -51,9 +49,9 @@ void Player::removeBuff(int buffId) {
}
}
void Player::removeBuff(int buffId, int buffClass) {
void Player::removeBuff(int buffId, BuffClass buffClass) {
if(hasBuff(buffId)) {
buffs[buffId]->clear((BuffClass)buffClass);
buffs[buffId]->clear(buffClass);
// buff might not be stale since another buff class might remain
if(buffs[buffId]->isStale()) {
delete buffs[buffId];
@ -63,14 +61,13 @@ void Player::removeBuff(int buffId, int buffClass) {
}
void Player::clearBuffs(bool force) {
for(auto buff : buffs) {
if(!force) {
removeBuff(buff.first);
} else {
delete buff.second;
}
auto it = buffs.begin();
while(it != buffs.end()) {
Buff* buff = (*it).second;
if(!force) buff->clear();
delete buff;
it = buffs.erase(it);
}
buffs.clear();
}
bool Player::hasBuff(int buffId) {
@ -171,31 +168,75 @@ void Player::step(time_t currTime) {
// buffs
for(auto buffEntry : buffs) {
buffEntry.second->combatTick(currTime);
if(!isAlive())
break; // unsafe to keep ticking if we're dead
}
}
#pragma endregion
#pragma region CombatNPC
bool CombatNPC::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) { /* stubbed */
if(!isAlive())
return false;
if(!hasBuff(buffId)) {
buffs[buffId] = new Buff(buffId, getRef(), onUpdate, onTick, stack);
return true;
}
buffs[buffId]->updateCallbacks(onUpdate, onTick);
buffs[buffId]->addStack(stack);
return false;
}
Buff* CombatNPC::getBuff(int buffId) { /* stubbed */
if(hasBuff(buffId)) {
return buffs[buffId];
}
return nullptr;
}
void CombatNPC::removeBuff(int buffId) { /* stubbed */ }
void CombatNPC::removeBuff(int buffId, int buffClass) { /* stubbed */ }
void CombatNPC::clearBuffs(bool force) { /* stubbed */ }
bool CombatNPC::hasBuff(int buffId) { /* stubbed */
return false;
void CombatNPC::removeBuff(int buffId) {
if(hasBuff(buffId)) {
buffs[buffId]->clear();
delete buffs[buffId];
buffs.erase(buffId);
}
}
int CombatNPC::getCompositeCondition() { /* stubbed */
return 0;
void CombatNPC::removeBuff(int buffId, BuffClass buffClass) {
if(hasBuff(buffId)) {
buffs[buffId]->clear(buffClass);
// buff might not be stale since another buff class might remain
if(buffs[buffId]->isStale()) {
delete buffs[buffId];
buffs.erase(buffId);
}
}
}
void CombatNPC::clearBuffs(bool force) {
auto it = buffs.begin();
while(it != buffs.end()) {
Buff* buff = (*it).second;
if(!force) buff->clear();
delete buff;
it = buffs.erase(it);
}
}
bool CombatNPC::hasBuff(int buffId) {
auto buff = buffs.find(buffId);
return buff != buffs.end() && !buff->second->isStale();
}
int CombatNPC::getCompositeCondition() {
int conditionBitFlag = 0;
for(auto buff : buffs) {
if(!buff.second->isStale() && buff.second->id > 0)
conditionBitFlag |= CSB_FROM_ECSB(buff.first);
}
return conditionBitFlag;
}
int CombatNPC::takeDamage(EntityRef src, int amt) {
@ -916,6 +957,7 @@ static void playerTick(CNServer *serv, time_t currTime) {
auto it = plr->buffs.begin();
while(it != plr->buffs.end()) {
Buff* buff = (*it).second;
//buff->combatTick() gets called in Player::step
buff->tick(currTime);
if(buff->isStale()) {
// garbage collect

View File

@ -47,7 +47,7 @@ public:
virtual bool addBuff(int, BuffCallback<int, BuffStack*>, BuffCallback<time_t>, BuffStack*) = 0;
virtual Buff* getBuff(int) = 0;
virtual void removeBuff(int) = 0;
virtual void removeBuff(int, int) = 0;
virtual void removeBuff(int, BuffClass) = 0;
virtual void clearBuffs(bool) = 0;
virtual bool hasBuff(int) = 0;
virtual int getCompositeCondition() = 0;
@ -125,7 +125,7 @@ struct CombatNPC : public BaseNPC, public ICombatant {
virtual bool addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) override;
virtual Buff* getBuff(int buffId) override;
virtual void removeBuff(int buffId) override;
virtual void removeBuff(int buffId, int buffClass) override;
virtual void removeBuff(int buffId, BuffClass buffClass) override;
virtual void clearBuffs(bool force) override;
virtual bool hasBuff(int buffId) override;
virtual int getCompositeCondition() override;

View File

@ -295,11 +295,13 @@ static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, i
if (plr->Nanos[plr->activeNano].iStamina > 150)
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina = 150;
// fire damage power disguised as a corruption attack back at the enemy
// TODO ABILITIES
/*std::vector<int> targetData2 = {1, mob->id, 0, 0, 0};
for (auto& pwr : Abilities::Powers)
if (pwr.skillType == EST_DAMAGE)
pwr.handle(sock, targetData2, plr->activeNano, skillID, 0, 200);*/
SkillData skill = Abilities::SkillTable[skillID];
skill.durationTime[0] = 0;
skill.values[0][0] = 200; // have to set
skill.values[0][1] = 200; // all of these
skill.values[0][2] = 200; // because the player might
skill.values[0][3] = 200; // have a boost
Abilities::useNanoSkill(sock, &skill, *plr->getActiveNano(), { mob });
} else {
respdata[i].iHitFlag = HF_BIT_STYLE_LOSE;
respdata[i].iDamage = Abilities::SkillTable[skillID].values[0][0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500;
@ -326,12 +328,6 @@ static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, i
}
static void useAbilities(Mob *mob, time_t currTime) {
/*
* targetData approach
* first integer is the count
* second to fifth integers are IDs, these can be either player iID or mob's iID
* whether the skill targets players or mobs is determined by the skill packet being fired
*/
Player *plr = PlayerManager::getPlayer(mob->target);
if (mob->skillStyle >= 0) { // corruption hit
@ -346,7 +342,7 @@ static void useAbilities(Mob *mob, time_t currTime) {
if (mob->skillStyle == -2) { // eruption hit
int skillID = (int)mob->data["m_iMegaType"];
std::vector<int> targetData = {0, 0, 0, 0, 0};
std::vector<ICombatant*> targets{};
// find the players within range of eruption
for (auto it = mob->viewableChunks.begin(); it != mob->viewableChunks.end(); it++) {
@ -356,26 +352,22 @@ static void useAbilities(Mob *mob, time_t currTime) {
if (ref.kind != EntityKind::PLAYER)
continue;
CNSocket *s= ref.sock;
CNSocket *s = ref.sock;
Player *plr = PlayerManager::getPlayer(s);
if (plr->HP <= 0)
if (!plr->isAlive())
continue;
int distance = hypot(mob->hitX - plr->x, mob->hitY - plr->y);
if (distance < Abilities::SkillTable[skillID].effectArea) {
targetData[0] += 1;
targetData[targetData[0]] = plr->iID;
if (targetData[0] > 3) // make sure not to have more than 4
targets.push_back(plr);
if (targets.size() > 3) // make sure not to have more than 4
break;
}
}
}
// TODO ABILITIES
/*for (auto& pwr : Abilities::Powers)
if (pwr.skillType == Abilities::SkillTable[skillID].skillType)
pwr.handle(mob->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]);*/
Abilities::useNPCSkill(mob->id, skillID, targets);
mob->skillStyle = -3; // eruption cooldown
mob->nextAttack = currTime + 1000;
return;
@ -393,14 +385,11 @@ static void useAbilities(Mob *mob, time_t currTime) {
if (random < prob1) { // active skill hit
int skillID = (int)mob->data["m_iActiveSkill1"];
// TODO ABILITIES
//std::vector<int> targetData = {1, plr->iID, 0, 0, 0};
//for (auto& pwr : Abilities::Powers)
// if (pwr.skillType == Abilities::SkillTable[skillID].skillType) {
// if (pwr.bitFlag != 0 && (plr->iConditionBitFlag & pwr.bitFlag))
// return; // prevent debuffing a player twice
// pwr.handle(mob->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]);
// }
SkillData* skill = &Abilities::SkillTable[skillID];
int debuffID = Abilities::getCSTBFromST(skill->skillType);
if(plr->hasBuff(debuffID))
return; // prevent debuffing a player twice
Abilities::useNPCSkill(mob->getRef(), skillID, { plr });
mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100;
return;
}
@ -441,32 +430,6 @@ static void useAbilities(Mob *mob, time_t currTime) {
return;
}
// TODO abiilities
static void drainMobHP(Mob *mob, int amount) {
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage);
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, resplen);
sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf;
sSkillResult_Damage *drain = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
pkt->iID = mob->id;
pkt->eCT = 4; // mob
pkt->iTB_ID = ECSB_BOUNDINGBALL;
drain->eCT = 4;
drain->iID = mob->id;
drain->iDamage = amount;
drain->iHP = mob->hp -= amount;
NPCManager::sendToViewable(mob, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
if (mob->hp <= 0)
mob->transition(AIState::DEAD, mob->target);
}
void MobAI::incNextMovement(Mob* mob, time_t currTime) {
if (currTime == 0)
currTime = getTime();
@ -553,20 +516,23 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
return;
}
// drain TODO abilities
if (self->skillStyle < 0 && (self->lastDrainTime == 0 || currTime - self->lastDrainTime >= 1000)
&& self->hasBuff(ECSB_BOUNDINGBALL)) {
drainMobHP(self, self->maxHealth / 20); // lose 5% every second
self->lastDrainTime = currTime;
}
// if drain killed the mob, return early
if (self->hp <= 0)
return;
// tick buffs
for(auto buffEntry : self->buffs) {
buffEntry.second->combatTick(currTime);
auto it = npc->buffs.begin();
while(it != npc->buffs.end()) {
Buff* buff = (*it).second;
buff->combatTick(currTime);
// if mob state changed, end the step
if(self->state != AIState::COMBAT)
return;
buff->tick(currTime);
if(buff->isStale()) {
// garbage collect
it = npc->buffs.erase(it);
delete buff;
}
else it++;
}
// skip attack if stunned or asleep
@ -586,6 +552,7 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
}
int distanceToTravel = INT_MAX;
int speed = self->speed;
// movement logic: move when out of range but don't move while casting a skill
if (distance > mobRange && self->skillStyle == -1) {
if (self->nextMovement != 0 && currTime < self->nextMovement)
@ -596,7 +563,7 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
// halve movement speed if snared
if (self->hasBuff(ECSB_DN_MOVE_SPEED))
self->speed /= 2;
speed /= 2;
int targetX = plr->x;
int targetY = plr->y;
@ -605,9 +572,9 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
targetY += self->offsetY*distance/(self->idleRange + 1);
}
distanceToTravel = std::min(distance-mobRange+1, self->speed*2/5);
distanceToTravel = std::min(distance-mobRange+1, speed*2/5);
auto targ = lerp(self->x, self->y, targetX, targetY, distanceToTravel);
if (distanceToTravel < self->speed*2/5 && currTime >= self->nextAttack)
if (distanceToTravel < speed*2/5 && currTime >= self->nextAttack)
self->nextAttack = 0;
NPCManager::updateNPCPosition(self->id, targ.first, targ.second, self->z, self->instanceID, self->angle);
@ -615,7 +582,7 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt);
pkt.iNPC_ID = self->id;
pkt.iSpeed = self->speed;
pkt.iSpeed = speed;
pkt.iToX = self->x = targ.first;
pkt.iToY = self->y = targ.second;
pkt.iToZ = plr->z;
@ -776,11 +743,8 @@ void MobAI::onRoamStart(CombatNPC* npc, EntityRef src) {
self->nextAttack = 0;
// cast a return home heal spell, this is the right way(tm)
// TODO ABILITIES
/*std::vector<int> targetData = { 1, 0, 0, 0, 0 };
for (auto& pwr : Abilities::Powers)
if (pwr.skillType == Abilities::SkillTable[110].skillType)
pwr.handle(self->id, targetData, 110, Abilities::SkillTable[110].durationTime[0], Abilities::SkillTable[110].powerIntensity[0]);*/
Abilities::useNPCSkill(npc->getRef(), 110, { npc });
// clear outlying debuffs
clearDebuff(self);
}
@ -797,12 +761,9 @@ void MobAI::onCombatStart(CombatNPC* npc, EntityRef src) {
self->roamY = self->y;
self->roamZ = self->z;
int skillID = (int)self->data["m_iPassiveBuff"]; // cast passive
// TODO ABILITIES
/*std::vector<int> targetData = { 1, self->id, 0, 0, 0 };
for (auto& pwr : Abilities::Powers)
if (pwr.skillType == Abilities::SkillTable[skillID].skillType)
pwr.handle(self->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]);*/
int skillID = (int)self->data["m_iPassiveBuff"];
if(skillID != 0) // cast passive
Abilities::useNPCSkill(npc->getRef(), skillID, { npc });
}
void MobAI::onRetreat(CombatNPC* npc, EntityRef src) {

View File

@ -376,5 +376,5 @@ void NPCManager::init() {
REGISTER_SHARD_PACKET(P_CL2FE_REQ_NPC_UNSUMMON, npcUnsummonHandler);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_BARKER, npcBarkHandler);
REGISTER_SHARD_TIMER(step, 200);
REGISTER_SHARD_TIMER(step, MS_PER_COMBAT_TICK);
}

View File

@ -92,7 +92,7 @@ struct Player : public Entity, public ICombatant {
virtual bool addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) override;
virtual Buff* getBuff(int buffId) override;
virtual void removeBuff(int buffId) override;
virtual void removeBuff(int buffId, int buffClass) override;
virtual void removeBuff(int buffId, BuffClass buffClass) override;
virtual void clearBuffs(bool force) override;
virtual bool hasBuff(int buffId) override;
virtual int getCompositeCondition() override;

View File

@ -8,6 +8,7 @@
#define REGISTER_SHARD_PACKET(pactype, handlr) CNShardServer::ShardPackets[pactype] = handlr;
#define REGISTER_SHARD_TIMER(handlr, delta) CNShardServer::Timers.push_back(TimerEvent(handlr, delta));
#define MS_PER_PLAYER_TICK 500
#define MS_PER_COMBAT_TICK 200
class CNShardServer : public CNServer {
private: