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 "Abilities.hpp"
#include "servers/CNShardServer.hpp"
#include "NPCManager.hpp" #include "NPCManager.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "Buffs.hpp" #include "Buffs.hpp"
#include "Nanos.hpp" #include "Nanos.hpp"
#include "MobAI.hpp"
using namespace Abilities; using namespace Abilities;
@ -55,7 +58,7 @@ static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombat
duration = skill->durationTime[power]; duration = skill->durationTime[power];
strength = skill->values[0][power]; strength = skill->values[0][power];
BuffStack debuff = { BuffStack debuff = {
duration, // ticks (duration * 100) / MS_PER_COMBAT_TICK, // ticks
strength, // value strength, // value
source->getRef(), // source source->getRef(), // source
BuffClass::NANO, // buff class BuffClass::NANO, // buff class
@ -64,9 +67,10 @@ static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombat
target->addBuff(timeBuffId, target->addBuff(timeBuffId,
[](EntityRef self, Buff* buff, int status, BuffStack* stack) { [](EntityRef self, Buff* buff, int status, BuffStack* stack) {
Buffs::timeBuffUpdate(self, buff, status, stack); Buffs::timeBuffUpdate(self, buff, status, stack);
if(status == ETBU_DEL) Buffs::timeBuffTimeout(self);
}, },
[](EntityRef self, Buff* buff, time_t currTime) { [](EntityRef self, Buff* buff, time_t currTime) {
// no-op Buffs::timeBuffTick(self, buff);
}, },
&debuff); &debuff);
} }
@ -82,27 +86,53 @@ static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombat
} }
static SkillResult handleSkillLeech(SkillData* skill, int power, ICombatant* source, ICombatant* target) { static SkillResult handleSkillLeech(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
// TODO abilities EntityRef sourceRef = source->getRef();
return SkillResult(); 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) { static SkillResult handleSkillBuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
int duration = skill->durationTime[power]; int duration = skill->durationTime[power];
int strength = skill->values[0][power]; int strength = skill->values[0][power];
BuffStack passiveBuff = { BuffStack passiveBuff = {
skill->drainType == SkillDrainType::PASSIVE ? 1 : duration, // ticks skill->drainType == SkillDrainType::PASSIVE ? 1 : (duration * 100) / MS_PER_COMBAT_TICK, // ticks
strength, // value strength, // value
source->getRef(), // source source->getRef(), // source
source == target ? BuffClass::NANO : BuffClass::GROUP_NANO, // buff class source == target ? BuffClass::NANO : BuffClass::GROUP_NANO, // buff class
}; };
int timeBuffId = Abilities::getCSTBFromST(skill->skillType); int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
SkillDrainType drainType = skill->drainType;
if(!target->addBuff(timeBuffId, if(!target->addBuff(timeBuffId,
[](EntityRef self, Buff* buff, int status, BuffStack* stack) { [drainType](EntityRef self, Buff* buff, int status, BuffStack* stack) {
Buffs::timeBuffUpdate(self, buff, status, 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) { [](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 &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->iSkillID = skillID;
pkt->eST = (int32_t)skill->skillType; pkt->eST = (int32_t)skill->skillType;
pkt->iTargetCnt = (int32_t)results.size(); 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)); attachSkillResults(results, (uint8_t*)(pkt + 1));
NPCManager::sendToViewable(entity, pkt, P_FE2CL_NPC_SKILL_HIT, resplen); NPCManager::sendToViewable(entity, pkt, P_FE2CL_NPC_SKILL_HIT, resplen);
@ -411,12 +447,6 @@ int Abilities::getCSTBFromST(SkillType skillType) {
case SkillType::PROTECTINFECTION: case SkillType::PROTECTINFECTION:
result = ECSB_PROTECT_INFECTION; result = ECSB_PROTECT_INFECTION;
break; break;
case SkillType::SNARE:
result = ECSB_DN_MOVE_SPEED;
break;
case SkillType::SLEEP:
result = ECSB_MEZ;
break;
case SkillType::MINIMAPENEMY: case SkillType::MINIMAPENEMY:
result = ECSB_MINIMAP_ENEMY; result = ECSB_MINIMAP_ENEMY;
break; break;
@ -429,15 +459,9 @@ int Abilities::getCSTBFromST(SkillType skillType) {
case SkillType::REWARDCASH: case SkillType::REWARDCASH:
result = ECSB_REWARD_CASH; result = ECSB_REWARD_CASH;
break; break;
case SkillType::INFECTIONDAMAGE:
result = ECSB_INFECTION;
break;
case SkillType::FREEDOM: case SkillType::FREEDOM:
result = ECSB_FREEDOM; result = ECSB_FREEDOM;
break; break;
case SkillType::BOUNDINGBALL:
result = ECSB_BOUNDINGBALL;
break;
case SkillType::INVULNERABLE: case SkillType::INVULNERABLE:
result = ECSB_INVULNERABLE; result = ECSB_INVULNERABLE;
break; break;
@ -446,6 +470,22 @@ int Abilities::getCSTBFromST(SkillType skillType) {
break; break;
case SkillType::NANOSTIMPAK: case SkillType::NANOSTIMPAK:
result = ECSB_STIMPAKSLOT1; 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; break;
default: default:
break; break;

View File

@ -95,6 +95,12 @@ int Buff::getValue(BuffValueSelector selector) {
return value; return value;
} }
EntityRef Buff::getLastSource() {
if(stacks.empty())
return self;
return stacks.back().source;
}
bool Buff::isStale() { bool Buff::isStale() {
return stacks.empty(); 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)); 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) { void Buffs::timeBuffTimeout(EntityRef self) {
if(self.kind != EntityKind::PLAYER && self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB) if(self.kind != EntityKind::PLAYER && self.kind != EntityKind::COMBAT_NPC && self.kind != EntityKind::MOB)
return; // not a combatant return; // not a combatant
Entity* entity = self.getEntity(); Entity* entity = self.getEntity();
ICombatant* combatant = dynamic_cast<ICombatant*>(entity); ICombatant* combatant = dynamic_cast<ICombatant*>(entity);
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt); // send a buff timeout to other players 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.iID = combatant->getID();
pkt.iConditionBitFlag = combatant->getCompositeCondition(); pkt.iConditionBitFlag = combatant->getCompositeCondition();
NPCManager::sendToViewable(entity, &pkt, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); 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 #pragma endregion

View File

@ -66,6 +66,7 @@ public:
BuffClass maxClass(); BuffClass maxClass();
int getValue(BuffValueSelector selector); int getValue(BuffValueSelector selector);
EntityRef getLastSource();
/* /*
* In general, a Buff object won't exist * In general, a Buff object won't exist
@ -86,5 +87,7 @@ public:
namespace Buffs { namespace Buffs {
void timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack); void timeBuffUpdate(EntityRef self, Buff* buff, int status, BuffStack* stack);
void timeBuffTick(EntityRef self, Buff* buff);
void timeBuffTimeout(EntityRef self); 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()) if(!isAlive())
return false; return false;
EntityRef self = PlayerManager::getSockFromID(iID);
if(!hasBuff(buffId)) { if(!hasBuff(buffId)) {
buffs[buffId] = new Buff(buffId, self, onUpdate, onTick, stack); buffs[buffId] = new Buff(buffId, getRef(), onUpdate, onTick, stack);
return true; return true;
} }
buffs[buffId]->updateCallbacks(onUpdate, onTick); buffs[buffId]->updateCallbacks(onUpdate, onTick);
buffs[buffId]->addStack(stack); buffs[buffId]->addStack(stack);
return false; 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)) { if(hasBuff(buffId)) {
buffs[buffId]->clear((BuffClass)buffClass); buffs[buffId]->clear(buffClass);
// buff might not be stale since another buff class might remain // buff might not be stale since another buff class might remain
if(buffs[buffId]->isStale()) { if(buffs[buffId]->isStale()) {
delete buffs[buffId]; delete buffs[buffId];
@ -63,14 +61,13 @@ void Player::removeBuff(int buffId, int buffClass) {
} }
void Player::clearBuffs(bool force) { void Player::clearBuffs(bool force) {
for(auto buff : buffs) { auto it = buffs.begin();
if(!force) { while(it != buffs.end()) {
removeBuff(buff.first); Buff* buff = (*it).second;
} else { if(!force) buff->clear();
delete buff.second; delete buff;
} it = buffs.erase(it);
} }
buffs.clear();
} }
bool Player::hasBuff(int buffId) { bool Player::hasBuff(int buffId) {
@ -171,31 +168,75 @@ void Player::step(time_t currTime) {
// buffs // buffs
for(auto buffEntry : buffs) { for(auto buffEntry : buffs) {
buffEntry.second->combatTick(currTime); buffEntry.second->combatTick(currTime);
if(!isAlive())
break; // unsafe to keep ticking if we're dead
} }
} }
#pragma endregion #pragma endregion
#pragma region CombatNPC #pragma region CombatNPC
bool CombatNPC::addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) { /* stubbed */ 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; return false;
} }
Buff* CombatNPC::getBuff(int buffId) { /* stubbed */ Buff* CombatNPC::getBuff(int buffId) { /* stubbed */
if(hasBuff(buffId)) {
return buffs[buffId];
}
return nullptr; return nullptr;
} }
void CombatNPC::removeBuff(int buffId) { /* stubbed */ } void CombatNPC::removeBuff(int buffId) {
if(hasBuff(buffId)) {
void CombatNPC::removeBuff(int buffId, int buffClass) { /* stubbed */ } buffs[buffId]->clear();
delete buffs[buffId];
void CombatNPC::clearBuffs(bool force) { /* stubbed */ } buffs.erase(buffId);
}
bool CombatNPC::hasBuff(int buffId) { /* stubbed */
return false;
} }
int CombatNPC::getCompositeCondition() { /* stubbed */ void CombatNPC::removeBuff(int buffId, BuffClass buffClass) {
return 0; 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) { int CombatNPC::takeDamage(EntityRef src, int amt) {
@ -916,6 +957,7 @@ static void playerTick(CNServer *serv, time_t currTime) {
auto it = plr->buffs.begin(); auto it = plr->buffs.begin();
while(it != plr->buffs.end()) { while(it != plr->buffs.end()) {
Buff* buff = (*it).second; Buff* buff = (*it).second;
//buff->combatTick() gets called in Player::step
buff->tick(currTime); buff->tick(currTime);
if(buff->isStale()) { if(buff->isStale()) {
// garbage collect // garbage collect

View File

@ -47,7 +47,7 @@ public:
virtual bool addBuff(int, BuffCallback<int, BuffStack*>, BuffCallback<time_t>, BuffStack*) = 0; virtual bool addBuff(int, BuffCallback<int, BuffStack*>, BuffCallback<time_t>, BuffStack*) = 0;
virtual Buff* getBuff(int) = 0; virtual Buff* getBuff(int) = 0;
virtual void removeBuff(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 void clearBuffs(bool) = 0;
virtual bool hasBuff(int) = 0; virtual bool hasBuff(int) = 0;
virtual int getCompositeCondition() = 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 bool addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) override;
virtual Buff* getBuff(int buffId) override; virtual Buff* getBuff(int buffId) override;
virtual void removeBuff(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 void clearBuffs(bool force) override;
virtual bool hasBuff(int buffId) override; virtual bool hasBuff(int buffId) override;
virtual int getCompositeCondition() 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) if (plr->Nanos[plr->activeNano].iStamina > 150)
respdata[i].iNanoStamina = 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 // fire damage power disguised as a corruption attack back at the enemy
// TODO ABILITIES SkillData skill = Abilities::SkillTable[skillID];
/*std::vector<int> targetData2 = {1, mob->id, 0, 0, 0}; skill.durationTime[0] = 0;
for (auto& pwr : Abilities::Powers) skill.values[0][0] = 200; // have to set
if (pwr.skillType == EST_DAMAGE) skill.values[0][1] = 200; // all of these
pwr.handle(sock, targetData2, plr->activeNano, skillID, 0, 200);*/ 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 { } else {
respdata[i].iHitFlag = HF_BIT_STYLE_LOSE; respdata[i].iHitFlag = HF_BIT_STYLE_LOSE;
respdata[i].iDamage = Abilities::SkillTable[skillID].values[0][0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500; 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) { 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); Player *plr = PlayerManager::getPlayer(mob->target);
if (mob->skillStyle >= 0) { // corruption hit if (mob->skillStyle >= 0) { // corruption hit
@ -346,7 +342,7 @@ static void useAbilities(Mob *mob, time_t currTime) {
if (mob->skillStyle == -2) { // eruption hit if (mob->skillStyle == -2) { // eruption hit
int skillID = (int)mob->data["m_iMegaType"]; 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 // find the players within range of eruption
for (auto it = mob->viewableChunks.begin(); it != mob->viewableChunks.end(); it++) { 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) if (ref.kind != EntityKind::PLAYER)
continue; continue;
CNSocket *s= ref.sock; CNSocket *s = ref.sock;
Player *plr = PlayerManager::getPlayer(s); Player *plr = PlayerManager::getPlayer(s);
if (plr->HP <= 0) if (!plr->isAlive())
continue; continue;
int distance = hypot(mob->hitX - plr->x, mob->hitY - plr->y); int distance = hypot(mob->hitX - plr->x, mob->hitY - plr->y);
if (distance < Abilities::SkillTable[skillID].effectArea) { if (distance < Abilities::SkillTable[skillID].effectArea) {
targetData[0] += 1; targets.push_back(plr);
targetData[targetData[0]] = plr->iID; if (targets.size() > 3) // make sure not to have more than 4
if (targetData[0] > 3) // make sure not to have more than 4
break; break;
} }
} }
} }
// TODO ABILITIES Abilities::useNPCSkill(mob->id, skillID, targets);
/*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]);*/
mob->skillStyle = -3; // eruption cooldown mob->skillStyle = -3; // eruption cooldown
mob->nextAttack = currTime + 1000; mob->nextAttack = currTime + 1000;
return; return;
@ -393,14 +385,11 @@ static void useAbilities(Mob *mob, time_t currTime) {
if (random < prob1) { // active skill hit if (random < prob1) { // active skill hit
int skillID = (int)mob->data["m_iActiveSkill1"]; int skillID = (int)mob->data["m_iActiveSkill1"];
// TODO ABILITIES SkillData* skill = &Abilities::SkillTable[skillID];
//std::vector<int> targetData = {1, plr->iID, 0, 0, 0}; int debuffID = Abilities::getCSTBFromST(skill->skillType);
//for (auto& pwr : Abilities::Powers) if(plr->hasBuff(debuffID))
// if (pwr.skillType == Abilities::SkillTable[skillID].skillType) { return; // prevent debuffing a player twice
// if (pwr.bitFlag != 0 && (plr->iConditionBitFlag & pwr.bitFlag)) Abilities::useNPCSkill(mob->getRef(), skillID, { plr });
// return; // prevent debuffing a player twice
// pwr.handle(mob->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]);
// }
mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100; mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100;
return; return;
} }
@ -441,32 +430,6 @@ static void useAbilities(Mob *mob, time_t currTime) {
return; 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) { void MobAI::incNextMovement(Mob* mob, time_t currTime) {
if (currTime == 0) if (currTime == 0)
currTime = getTime(); currTime = getTime();
@ -553,20 +516,23 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
return; 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 // tick buffs
for(auto buffEntry : self->buffs) { auto it = npc->buffs.begin();
buffEntry.second->combatTick(currTime); 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 // skip attack if stunned or asleep
@ -586,6 +552,7 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
} }
int distanceToTravel = INT_MAX; int distanceToTravel = INT_MAX;
int speed = self->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 && self->skillStyle == -1) { if (distance > mobRange && self->skillStyle == -1) {
if (self->nextMovement != 0 && currTime < self->nextMovement) if (self->nextMovement != 0 && currTime < self->nextMovement)
@ -596,7 +563,7 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
// halve movement speed if snared // halve movement speed if snared
if (self->hasBuff(ECSB_DN_MOVE_SPEED)) if (self->hasBuff(ECSB_DN_MOVE_SPEED))
self->speed /= 2; speed /= 2;
int targetX = plr->x; int targetX = plr->x;
int targetY = plr->y; int targetY = plr->y;
@ -605,9 +572,9 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
targetY += self->offsetY*distance/(self->idleRange + 1); 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); 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; self->nextAttack = 0;
NPCManager::updateNPCPosition(self->id, targ.first, targ.second, self->z, self->instanceID, self->angle); 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); INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt);
pkt.iNPC_ID = self->id; pkt.iNPC_ID = self->id;
pkt.iSpeed = self->speed; pkt.iSpeed = speed;
pkt.iToX = self->x = targ.first; pkt.iToX = self->x = targ.first;
pkt.iToY = self->y = targ.second; pkt.iToY = self->y = targ.second;
pkt.iToZ = plr->z; pkt.iToZ = plr->z;
@ -776,11 +743,8 @@ void MobAI::onRoamStart(CombatNPC* npc, EntityRef src) {
self->nextAttack = 0; self->nextAttack = 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)
// TODO ABILITIES Abilities::useNPCSkill(npc->getRef(), 110, { npc });
/*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]);*/
// clear outlying debuffs // clear outlying debuffs
clearDebuff(self); clearDebuff(self);
} }
@ -797,12 +761,9 @@ void MobAI::onCombatStart(CombatNPC* npc, EntityRef src) {
self->roamY = self->y; self->roamY = self->y;
self->roamZ = self->z; self->roamZ = self->z;
int skillID = (int)self->data["m_iPassiveBuff"]; // cast passive int skillID = (int)self->data["m_iPassiveBuff"];
// TODO ABILITIES if(skillID != 0) // cast passive
/*std::vector<int> targetData = { 1, self->id, 0, 0, 0 }; Abilities::useNPCSkill(npc->getRef(), skillID, { npc });
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]);*/
} }
void MobAI::onRetreat(CombatNPC* npc, EntityRef src) { 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_NPC_UNSUMMON, npcUnsummonHandler);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_BARKER, npcBarkHandler); 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 bool addBuff(int buffId, BuffCallback<int, BuffStack*> onUpdate, BuffCallback<time_t> onTick, BuffStack* stack) override;
virtual Buff* getBuff(int buffId) override; virtual Buff* getBuff(int buffId) override;
virtual void removeBuff(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 void clearBuffs(bool force) override;
virtual bool hasBuff(int buffId) override; virtual bool hasBuff(int buffId) override;
virtual int getCompositeCondition() override; virtual int getCompositeCondition() override;

View File

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