mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2024-11-22 13:30:06 +00:00
Implement buff handling for CombatNPC
This commit is contained in:
parent
82bee2051a
commit
e325f7a40b
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -24,10 +24,8 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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
|
||||||
|
@ -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;
|
||||||
|
123
src/MobAI.cpp
123
src/MobAI.cpp
@ -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
|
// tick buffs
|
||||||
if (self->skillStyle < 0 && (self->lastDrainTime == 0 || currTime - self->lastDrainTime >= 1000)
|
auto it = npc->buffs.begin();
|
||||||
&& self->hasBuff(ECSB_BOUNDINGBALL)) {
|
while(it != npc->buffs.end()) {
|
||||||
drainMobHP(self, self->maxHealth / 20); // lose 5% every second
|
Buff* buff = (*it).second;
|
||||||
self->lastDrainTime = currTime;
|
buff->combatTick(currTime);
|
||||||
}
|
|
||||||
|
|
||||||
// if drain killed the mob, return early
|
// if mob state changed, end the step
|
||||||
if (self->hp <= 0)
|
if(self->state != AIState::COMBAT)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// tick buffs
|
buff->tick(currTime);
|
||||||
for(auto buffEntry : self->buffs) {
|
if(buff->isStale()) {
|
||||||
buffEntry.second->combatTick(currTime);
|
// 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) {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user