mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2024-11-22 13:30:06 +00:00
Compare commits
12 Commits
5671adbd74
...
1a7bb826d8
Author | SHA1 | Date | |
---|---|---|---|
|
1a7bb826d8 | ||
|
da9fb499de | ||
|
2bc2235179 | ||
|
618126f963 | ||
|
b16ffe4f19 | ||
|
6edc01c1f1 | ||
|
c0e566050b | ||
|
7a59248ace | ||
|
6ee55d7406 | ||
|
41622ad8aa | ||
|
0f09808bc4 | ||
|
b6171ebdc1 |
@ -1,5 +1,7 @@
|
|||||||
#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"
|
||||||
@ -45,11 +47,39 @@ static SkillResult handleSkillHealHP(SkillData* skill, int power, ICombatant* so
|
|||||||
}
|
}
|
||||||
|
|
||||||
static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
static SkillResult handleSkillDamageNDebuff(SkillData* skill, int power, ICombatant* source, ICombatant* target) {
|
||||||
// TODO abilities
|
// take aggro
|
||||||
|
target->takeDamage(source->getRef(), 0);
|
||||||
|
|
||||||
|
int duration = 0;
|
||||||
|
int strength = 0;
|
||||||
|
bool blocked = target->hasBuff(ECSB_FREEDOM);
|
||||||
|
if(!blocked) {
|
||||||
|
duration = skill->durationTime[power];
|
||||||
|
strength = skill->values[0][power];
|
||||||
|
BuffStack debuff = {
|
||||||
|
(duration * 100) / MS_PER_COMBAT_TICK, // ticks
|
||||||
|
strength, // value
|
||||||
|
source->getRef(), // source
|
||||||
|
BuffClass::NANO, // buff class
|
||||||
|
};
|
||||||
|
int timeBuffId = Abilities::getCSTBFromST(skill->skillType);
|
||||||
|
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) {
|
||||||
|
Buffs::timeBuffTick(self, buff);
|
||||||
|
},
|
||||||
|
&debuff);
|
||||||
|
}
|
||||||
|
|
||||||
sSkillResult_Damage_N_Debuff result{};
|
sSkillResult_Damage_N_Debuff result{};
|
||||||
|
result.iDamage = duration / 10; // we use the duration as the damage number (why?)
|
||||||
|
result.iHP = target->getCurrentHP();
|
||||||
result.eCT = target->getCharType();
|
result.eCT = target->getCharType();
|
||||||
result.iID = target->getID();
|
result.iID = target->getID();
|
||||||
result.bProtected = false;
|
result.bProtected = blocked;
|
||||||
result.iConditionBitFlag = target->getCompositeCondition();
|
result.iConditionBitFlag = target->getCompositeCondition();
|
||||||
return SkillResult(sizeof(sSkillResult_Damage_N_Debuff), &result);
|
return SkillResult(sizeof(sSkillResult_Damage_N_Debuff), &result);
|
||||||
}
|
}
|
||||||
@ -60,20 +90,31 @@ static SkillResult handleSkillLeech(SkillData* skill, int power, ICombatant* sou
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 strength = skill->values[0][power];
|
||||||
BuffStack passiveBuff = {
|
BuffStack passiveBuff = {
|
||||||
skill->drainType == SkillDrainType::PASSIVE ? 1 : skill->durationTime[power], // ticks
|
skill->drainType == SkillDrainType::PASSIVE ? 1 : (duration * 100) / MS_PER_COMBAT_TICK, // ticks
|
||||||
skill->values[0][power], // 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) {
|
||||||
|
if(buff->id == ECSB_BOUNDINGBALL) {
|
||||||
|
// drain
|
||||||
|
ICombatant* combatant = dynamic_cast<ICombatant*>(self.getEntity());
|
||||||
|
combatant->takeDamage(buff->getLastSource(), 0); // aggro
|
||||||
|
}
|
||||||
Buffs::timeBuffUpdate(self, buff, status, stack);
|
Buffs::timeBuffUpdate(self, buff, status, stack);
|
||||||
|
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
|
||||||
|
|
||||||
@ -216,16 +257,18 @@ static std::vector<SkillResult> handleSkill(SkillData* skill, int power, ICombat
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void attachSkillResults(std::vector<SkillResult> results, size_t resultSize, uint8_t* pivot) {
|
static void attachSkillResults(std::vector<SkillResult> results, uint8_t* pivot) {
|
||||||
for(SkillResult& result : results) {
|
for(SkillResult& result : results) {
|
||||||
memcpy(pivot, result.payload, resultSize);
|
size_t sz = result.size;
|
||||||
pivot += resultSize;
|
memcpy(pivot, result.payload, sz);
|
||||||
|
pivot += sz;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std::vector<ICombatant*> affected) {
|
void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std::vector<ICombatant*> affected) {
|
||||||
|
|
||||||
Player* plr = PlayerManager::getPlayer(sock);
|
Player* plr = PlayerManager::getPlayer(sock);
|
||||||
|
ICombatant* combatant = dynamic_cast<ICombatant*>(plr);
|
||||||
|
|
||||||
int boost = 0;
|
int boost = 0;
|
||||||
if (Nanos::getNanoBoost(plr))
|
if (Nanos::getNanoBoost(plr))
|
||||||
@ -237,17 +280,19 @@ void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std:
|
|||||||
nano.iStamina = 0;
|
nano.iStamina = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<SkillResult> results = handleSkill(skill, boost, plr, affected);
|
std::vector<SkillResult> results = handleSkill(skill, boost, combatant, affected);
|
||||||
if(results.empty()) return; // no effect; no need for confirmation packets
|
if(results.empty()) return; // no effect; no need for confirmation packets
|
||||||
|
|
||||||
size_t resultSize = MAX_SKILLRESULT_SIZE; // lazy
|
// lazy validation since skill results might be different sizes
|
||||||
if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC), results.size(), resultSize)) {
|
if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC), results.size(), MAX_SKILLRESULT_SIZE)) {
|
||||||
std::cout << "[WARN] bad sP_FE2CL_NANO_SKILL_USE_SUCC packet size\n";
|
std::cout << "[WARN] bad sP_FE2CL_NANO_SKILL_USE_SUCC packet size\n";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize response struct
|
// initialize response struct
|
||||||
size_t resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + results.size() * resultSize;
|
size_t resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC);
|
||||||
|
for(SkillResult& sr : results)
|
||||||
|
resplen += sr.size;
|
||||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||||
memset(respbuf, 0, resplen);
|
memset(respbuf, 0, resplen);
|
||||||
|
|
||||||
@ -260,7 +305,7 @@ void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std:
|
|||||||
pkt->eST = (int32_t)skill->skillType;
|
pkt->eST = (int32_t)skill->skillType;
|
||||||
pkt->iTargetCnt = (int32_t)results.size();
|
pkt->iTargetCnt = (int32_t)results.size();
|
||||||
|
|
||||||
attachSkillResults(results, resultSize, (uint8_t*)(pkt + 1));
|
attachSkillResults(results, (uint8_t*)(pkt + 1));
|
||||||
sock->sendPacket(pkt, P_FE2CL_NANO_SKILL_USE_SUCC, resplen);
|
sock->sendPacket(pkt, P_FE2CL_NANO_SKILL_USE_SUCC, resplen);
|
||||||
|
|
||||||
if(skill->skillType == SkillType::RECALL_GROUP)
|
if(skill->skillType == SkillType::RECALL_GROUP)
|
||||||
@ -284,15 +329,16 @@ void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector<ICombatant*>
|
|||||||
std::vector<SkillResult> results = handleSkill(skill, 0, src, affected);
|
std::vector<SkillResult> results = handleSkill(skill, 0, src, affected);
|
||||||
if(results.empty()) return; // no effect; no need for confirmation packets
|
if(results.empty()) return; // no effect; no need for confirmation packets
|
||||||
|
|
||||||
size_t resultSize = results.back().size; // guaranteed to be the same for every item
|
// lazy validation since skill results might be different sizes
|
||||||
|
if (!validOutVarPacket(sizeof(sP_FE2CL_NPC_SKILL_HIT), results.size(), MAX_SKILLRESULT_SIZE)) {
|
||||||
if (!validOutVarPacket(sizeof(sP_FE2CL_NPC_SKILL_HIT), results.size(), resultSize)) {
|
|
||||||
std::cout << "[WARN] bad sP_FE2CL_NPC_SKILL_HIT packet size\n";
|
std::cout << "[WARN] bad sP_FE2CL_NPC_SKILL_HIT packet size\n";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize response struct
|
// initialize response struct
|
||||||
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + results.size() * resultSize;
|
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT);
|
||||||
|
for(SkillResult& sr : results)
|
||||||
|
resplen += sr.size;
|
||||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||||
memset(respbuf, 0, resplen);
|
memset(respbuf, 0, resplen);
|
||||||
|
|
||||||
@ -302,7 +348,7 @@ void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector<ICombatant*>
|
|||||||
pkt->eST = (int32_t)skill->skillType;
|
pkt->eST = (int32_t)skill->skillType;
|
||||||
pkt->iTargetCnt = (int32_t)results.size();
|
pkt->iTargetCnt = (int32_t)results.size();
|
||||||
|
|
||||||
attachSkillResults(results, resultSize, (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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,12 +423,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;
|
||||||
@ -395,15 +435,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;
|
||||||
@ -412,6 +446,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,6 +52,7 @@ void Player::removeBuff(int buffId) {
|
|||||||
void Player::removeBuff(int buffId, int buffClass) {
|
void Player::removeBuff(int buffId, int buffClass) {
|
||||||
if(hasBuff(buffId)) {
|
if(hasBuff(buffId)) {
|
||||||
buffs[buffId]->clear((BuffClass)buffClass);
|
buffs[buffId]->clear((BuffClass)buffClass);
|
||||||
|
// 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];
|
||||||
buffs.erase(buffId);
|
buffs.erase(buffId);
|
||||||
@ -61,6 +60,17 @@ 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffs.clear();
|
||||||
|
}
|
||||||
|
|
||||||
bool Player::hasBuff(int buffId) {
|
bool Player::hasBuff(int buffId) {
|
||||||
auto buff = buffs.find(buffId);
|
auto buff = buffs.find(buffId);
|
||||||
return buff != buffs.end() && !buff->second->isStale();
|
return buff != buffs.end() && !buff->second->isStale();
|
||||||
@ -165,23 +175,68 @@ void Player::step(time_t currTime) {
|
|||||||
|
|
||||||
#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];
|
||||||
bool CombatNPC::hasBuff(int buffId) { /* stubbed */
|
buffs.erase(buffId);
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int CombatNPC::getCompositeCondition() { /* stubbed */
|
void CombatNPC::removeBuff(int buffId, int buffClass) {
|
||||||
return 0;
|
if(hasBuff(buffId)) {
|
||||||
|
buffs[buffId]->clear((BuffClass)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) {
|
||||||
|
for(auto buff : buffs) {
|
||||||
|
if(!force) {
|
||||||
|
removeBuff(buff.first);
|
||||||
|
} else {
|
||||||
|
delete buff.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffs.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
@ -678,7 +678,6 @@ static void whoisCommand(std::string full, std::vector<std::string>& args, CNSoc
|
|||||||
Chat::sendServerMessage(sock, "[WHOIS] ID: " + std::to_string(npc->id));
|
Chat::sendServerMessage(sock, "[WHOIS] ID: " + std::to_string(npc->id));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] Type: " + std::to_string(npc->type));
|
Chat::sendServerMessage(sock, "[WHOIS] Type: " + std::to_string(npc->type));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] HP: " + std::to_string(npc->hp));
|
Chat::sendServerMessage(sock, "[WHOIS] HP: " + std::to_string(npc->hp));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] CBF: " + std::to_string(npc->cbf));
|
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] EntityType: " + std::to_string((int)npc->kind));
|
Chat::sendServerMessage(sock, "[WHOIS] EntityType: " + std::to_string((int)npc->kind));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] X: " + std::to_string(npc->x));
|
Chat::sendServerMessage(sock, "[WHOIS] X: " + std::to_string(npc->x));
|
||||||
Chat::sendServerMessage(sock, "[WHOIS] Y: " + std::to_string(npc->y));
|
Chat::sendServerMessage(sock, "[WHOIS] Y: " + std::to_string(npc->y));
|
||||||
|
@ -40,7 +40,7 @@ sNPCAppearanceData BaseNPC::getAppearanceData() {
|
|||||||
sNPCAppearanceData data = {};
|
sNPCAppearanceData data = {};
|
||||||
data.iAngle = angle;
|
data.iAngle = angle;
|
||||||
data.iBarkerType = 0; // unused?
|
data.iBarkerType = 0; // unused?
|
||||||
data.iConditionBitFlag = cbf;
|
data.iConditionBitFlag = 0;
|
||||||
data.iHP = hp;
|
data.iHP = hp;
|
||||||
data.iNPCType = type;
|
data.iNPCType = type;
|
||||||
data.iNPC_ID = id;
|
data.iNPC_ID = id;
|
||||||
@ -50,6 +50,12 @@ sNPCAppearanceData BaseNPC::getAppearanceData() {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sNPCAppearanceData CombatNPC::getAppearanceData() {
|
||||||
|
sNPCAppearanceData data = BaseNPC::getAppearanceData();
|
||||||
|
data.iConditionBitFlag = getCompositeCondition();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Entity coming into view.
|
* Entity coming into view.
|
||||||
*/
|
*/
|
||||||
|
@ -48,6 +48,7 @@ public:
|
|||||||
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, int) = 0;
|
||||||
|
virtual void clearBuffs(bool) = 0;
|
||||||
virtual bool hasBuff(int) = 0;
|
virtual bool hasBuff(int) = 0;
|
||||||
virtual int getCompositeCondition() = 0;
|
virtual int getCompositeCondition() = 0;
|
||||||
virtual int takeDamage(EntityRef, int) = 0;
|
virtual int takeDamage(EntityRef, int) = 0;
|
||||||
@ -72,7 +73,6 @@ public:
|
|||||||
int type;
|
int type;
|
||||||
int hp;
|
int hp;
|
||||||
int angle;
|
int angle;
|
||||||
int cbf;
|
|
||||||
bool loopingPath = false;
|
bool loopingPath = false;
|
||||||
|
|
||||||
BaseNPC(int _A, uint64_t iID, int t, int _id) {
|
BaseNPC(int _A, uint64_t iID, int t, int _id) {
|
||||||
@ -80,7 +80,6 @@ public:
|
|||||||
type = t;
|
type = t;
|
||||||
hp = 400;
|
hp = 400;
|
||||||
angle = _A;
|
angle = _A;
|
||||||
cbf = 0;
|
|
||||||
id = _id;
|
id = _id;
|
||||||
instanceID = iID;
|
instanceID = iID;
|
||||||
};
|
};
|
||||||
@ -88,7 +87,7 @@ public:
|
|||||||
virtual void enterIntoViewOf(CNSocket *sock) override;
|
virtual void enterIntoViewOf(CNSocket *sock) override;
|
||||||
virtual void disappearFromViewOf(CNSocket *sock) override;
|
virtual void disappearFromViewOf(CNSocket *sock) override;
|
||||||
|
|
||||||
sNPCAppearanceData getAppearanceData();
|
virtual sNPCAppearanceData getAppearanceData();
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CombatNPC : public BaseNPC, public ICombatant {
|
struct CombatNPC : public BaseNPC, public ICombatant {
|
||||||
@ -105,6 +104,8 @@ struct CombatNPC : public BaseNPC, public ICombatant {
|
|||||||
std::map<AIState, void (*)(CombatNPC*, time_t)> stateHandlers;
|
std::map<AIState, void (*)(CombatNPC*, time_t)> stateHandlers;
|
||||||
std::map<AIState, void (*)(CombatNPC*, EntityRef)> transitionHandlers;
|
std::map<AIState, void (*)(CombatNPC*, EntityRef)> transitionHandlers;
|
||||||
|
|
||||||
|
std::unordered_map<int, Buff*> buffs = {};
|
||||||
|
|
||||||
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;
|
||||||
@ -117,12 +118,15 @@ struct CombatNPC : public BaseNPC, public ICombatant {
|
|||||||
transitionHandlers[AIState::INACTIVE] = {};
|
transitionHandlers[AIState::INACTIVE] = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual sNPCAppearanceData getAppearanceData() override;
|
||||||
|
|
||||||
virtual bool isExtant() override { return hp > 0; }
|
virtual bool isExtant() override { return hp > 0; }
|
||||||
|
|
||||||
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, int buffClass) 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;
|
||||||
virtual int takeDamage(EntityRef src, int amt) override;
|
virtual int takeDamage(EntityRef src, int amt) override;
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#include "EntityRef.hpp"
|
#include "EntityRef.hpp"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
struct Group {
|
struct Group {
|
||||||
std::vector<EntityRef> members;
|
std::vector<EntityRef> members;
|
||||||
@ -14,6 +15,10 @@ struct Group {
|
|||||||
});
|
});
|
||||||
return filtered;
|
return filtered;
|
||||||
}
|
}
|
||||||
|
EntityRef getLeader() {
|
||||||
|
assert(members.size() > 0);
|
||||||
|
return members[0];
|
||||||
|
}
|
||||||
|
|
||||||
Group(EntityRef leader);
|
Group(EntityRef leader);
|
||||||
};
|
};
|
||||||
|
@ -51,13 +51,13 @@ int Mob::takeDamage(EntityRef src, int amt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// wake up sleeping monster
|
// wake up sleeping monster
|
||||||
if (cbf & CSB_BIT_MEZ) {
|
if (hasBuff(ECSB_MEZ)) {
|
||||||
cbf &= ~CSB_BIT_MEZ;
|
removeBuff(ECSB_MEZ);
|
||||||
|
|
||||||
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 = id;
|
pkt1.iID = id;
|
||||||
pkt1.iConditionBitFlag = cbf;
|
pkt1.iConditionBitFlag = getCompositeCondition();
|
||||||
NPCManager::sendToViewable(this, &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));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,13 +96,12 @@ static std::pair<int,int> lerp(int x1, int y1, int x2, int y2, int speed) {
|
|||||||
|
|
||||||
void MobAI::clearDebuff(Mob *mob) {
|
void MobAI::clearDebuff(Mob *mob) {
|
||||||
mob->skillStyle = -1;
|
mob->skillStyle = -1;
|
||||||
mob->cbf = 0;
|
mob->clearBuffs(false);
|
||||||
mob->unbuffTimes.clear();
|
|
||||||
|
|
||||||
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 = mob->id;
|
||||||
pkt1.iConditionBitFlag = mob->cbf;
|
pkt1.iConditionBitFlag = mob->getCompositeCondition();
|
||||||
NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -442,6 +441,7 @@ static void useAbilities(Mob *mob, time_t currTime) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO abiilities
|
||||||
static void drainMobHP(Mob *mob, int amount) {
|
static void drainMobHP(Mob *mob, int amount) {
|
||||||
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage);
|
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage);
|
||||||
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
|
||||||
@ -553,38 +553,26 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// drain
|
// tick buffs
|
||||||
if (self->skillStyle < 0 && (self->lastDrainTime == 0 || currTime - self->lastDrainTime >= 1000)
|
auto it = npc->buffs.begin();
|
||||||
&& self->cbf & CSB_BIT_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);
|
||||||
|
buff->tick(currTime);
|
||||||
|
if(buff->isStale()) {
|
||||||
|
// garbage collect
|
||||||
|
it = npc->buffs.erase(it);
|
||||||
|
delete buff;
|
||||||
|
}
|
||||||
|
else it++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if drain killed the mob, return early
|
// if debuffs killed the mob, return early
|
||||||
if (self->hp <= 0)
|
if (self->hp <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// unbuffing
|
|
||||||
std::unordered_map<int32_t, time_t>::iterator it = self->unbuffTimes.begin();
|
|
||||||
while (it != self->unbuffTimes.end()) {
|
|
||||||
|
|
||||||
if (currTime >= it->second) {
|
|
||||||
self->cbf &= ~it->first;
|
|
||||||
|
|
||||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
|
|
||||||
pkt1.eCT = 2;
|
|
||||||
pkt1.iID = self->id;
|
|
||||||
pkt1.iConditionBitFlag = self->cbf;
|
|
||||||
NPCManager::sendToViewable(self, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
|
||||||
|
|
||||||
it = self->unbuffTimes.erase(it);
|
|
||||||
} else {
|
|
||||||
it++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip attack if stunned or asleep
|
// skip attack if stunned or asleep
|
||||||
if (self->cbf & (CSB_BIT_STUN|CSB_BIT_MEZ)) {
|
if (self->hasBuff(ECSB_STUN) || self->hasBuff(ECSB_MEZ)) {
|
||||||
self->skillStyle = -1; // in this case we also reset the any outlying abilities the mob might be winding up.
|
self->skillStyle = -1; // in this case we also reset the any outlying abilities the mob might be winding up.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -600,6 +588,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)
|
||||||
@ -609,8 +598,8 @@ void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
|
|||||||
self->nextAttack = 0;
|
self->nextAttack = 0;
|
||||||
|
|
||||||
// halve movement speed if snared
|
// halve movement speed if snared
|
||||||
if (self->cbf & CSB_BIT_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;
|
||||||
@ -619,9 +608,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);
|
||||||
@ -629,7 +618,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;
|
||||||
@ -715,7 +704,7 @@ void MobAI::roamingStep(CombatNPC* npc, time_t currTime) {
|
|||||||
farY = std::clamp(farY, yStart, yStart + self->idleRange);
|
farY = std::clamp(farY, yStart, yStart + self->idleRange);
|
||||||
|
|
||||||
// halve movement speed if snared
|
// halve movement speed if snared
|
||||||
if (self->cbf & CSB_BIT_DN_MOVE_SPEED)
|
if (self->hasBuff(ECSB_DN_MOVE_SPEED))
|
||||||
self->speed /= 2;
|
self->speed /= 2;
|
||||||
|
|
||||||
std::queue<Vec3> queue;
|
std::queue<Vec3> queue;
|
||||||
@ -788,7 +777,6 @@ void MobAI::onRoamStart(CombatNPC* npc, EntityRef src) {
|
|||||||
self->hp = self->maxHealth;
|
self->hp = self->maxHealth;
|
||||||
self->killedTime = 0;
|
self->killedTime = 0;
|
||||||
self->nextAttack = 0;
|
self->nextAttack = 0;
|
||||||
self->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)
|
||||||
// TODO ABILITIES
|
// TODO ABILITIES
|
||||||
@ -833,9 +821,8 @@ void MobAI::onDeath(CombatNPC* npc, EntityRef src) {
|
|||||||
Mob* self = (Mob*)npc;
|
Mob* self = (Mob*)npc;
|
||||||
|
|
||||||
self->target = nullptr;
|
self->target = nullptr;
|
||||||
self->cbf = 0;
|
|
||||||
self->skillStyle = -1;
|
self->skillStyle = -1;
|
||||||
self->unbuffTimes.clear();
|
self->clearBuffs(true);
|
||||||
self->killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step?
|
self->killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step?
|
||||||
|
|
||||||
// check for the edge case where hitting the mob did not aggro it
|
// check for the edge case where hitting the mob did not aggro it
|
||||||
|
@ -21,8 +21,6 @@ namespace MobAI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct Mob : public CombatNPC {
|
struct Mob : public CombatNPC {
|
||||||
// general
|
|
||||||
std::unordered_map<int32_t,time_t> unbuffTimes = {};
|
|
||||||
|
|
||||||
// dead
|
// dead
|
||||||
time_t killedTime = 0;
|
time_t killedTime = 0;
|
||||||
@ -71,8 +69,6 @@ struct Mob : public CombatNPC {
|
|||||||
offsetX = 0;
|
offsetX = 0;
|
||||||
offsetY = 0;
|
offsetY = 0;
|
||||||
|
|
||||||
cbf = 0;
|
|
||||||
|
|
||||||
// NOTE: there appear to be discrepancies in the dump
|
// NOTE: there appear to be discrepancies in the dump
|
||||||
hp = maxHealth;
|
hp = maxHealth;
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -93,6 +93,7 @@ struct Player : public Entity, public ICombatant {
|
|||||||
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, int buffClass) 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;
|
||||||
virtual int takeDamage(EntityRef src, int amt) override;
|
virtual int takeDamage(EntityRef src, int amt) 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