mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2024-11-17 03:20:06 +00:00
f4f5f2e0bd
Storing certain things in appearance data and others in their own fields was gross. Now everything is stored on the same level and functions have been added to generate appearance data when it's needed by the client.
728 lines
27 KiB
C++
728 lines
27 KiB
C++
#include "Abilities.hpp"
|
|
#include "PlayerManager.hpp"
|
|
#include "Player.hpp"
|
|
#include "NPCManager.hpp"
|
|
#include "Nanos.hpp"
|
|
#include "Groups.hpp"
|
|
#include "Eggs.hpp"
|
|
|
|
/*
|
|
* TODO: This file is in desperate need of deduplication and rewriting.
|
|
*/
|
|
|
|
std::map<int32_t, SkillData> Abilities::SkillTable;
|
|
|
|
/*
|
|
* targetData approach
|
|
* first integer is the count
|
|
* second to fifth integers are IDs, these can be either player iID or mob's iID
|
|
*/
|
|
std::vector<int> Abilities::findTargets(Player* plr, int skillID, CNPacketData* data) {
|
|
std::vector<int> tD(5);
|
|
|
|
if (SkillTable[skillID].targetType <= 2 && data != nullptr) { // client gives us the targets
|
|
sP_CL2FE_REQ_NANO_SKILL_USE* pkt = (sP_CL2FE_REQ_NANO_SKILL_USE*)data->buf;
|
|
|
|
// validate request check
|
|
if (!validInVarPacket(sizeof(sP_CL2FE_REQ_NANO_SKILL_USE), pkt->iTargetCnt, sizeof(int32_t), data->size)) {
|
|
std::cout << "[WARN] bad sP_CL2FE_REQ_NANO_SKILL_USE packet size" << std::endl;
|
|
return tD;
|
|
}
|
|
|
|
int32_t *pktdata = (int32_t*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_NANO_SKILL_USE));
|
|
tD[0] = pkt->iTargetCnt;
|
|
|
|
for (int i = 0; i < pkt->iTargetCnt; i++)
|
|
tD[i+1] = pktdata[i];
|
|
|
|
} else if (SkillTable[skillID].targetType == 2) { // self target only
|
|
tD[0] = 1;
|
|
tD[1] = plr->iID;
|
|
|
|
} else if (SkillTable[skillID].targetType == 3) { // entire group as target
|
|
Player *otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
|
|
|
if (otherPlr == nullptr)
|
|
return tD;
|
|
|
|
if (SkillTable[skillID].effectArea == 0) { // for buffs
|
|
tD[0] = otherPlr->groupCnt;
|
|
for (int i = 0; i < otherPlr->groupCnt; i++)
|
|
tD[i+1] = otherPlr->groupIDs[i];
|
|
return tD;
|
|
}
|
|
|
|
for (int i = 0; i < otherPlr->groupCnt; i++) { // group heals have an area limit
|
|
Player *otherPlr2 = PlayerManager::getPlayerFromID(otherPlr->groupIDs[i]);
|
|
if (otherPlr2 == nullptr)
|
|
continue;
|
|
if (true) {//hypot(otherPlr2->x - plr->x, otherPlr2->y - plr->y) < SkillTable[skillID].effectArea) {
|
|
tD[i+1] = otherPlr->groupIDs[i];
|
|
tD[0] += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return tD;
|
|
}
|
|
|
|
void Abilities::removeBuff(CNSocket* sock, std::vector<int> targetData, int32_t bitFlag, int16_t timeBuffID, int16_t amount, bool groupPower) {
|
|
Player *plr = PlayerManager::getPlayer(sock);
|
|
|
|
plr->iSelfConditionBitFlag &= ~bitFlag;
|
|
int groupFlags = 0;
|
|
|
|
if (groupPower) {
|
|
plr->iGroupConditionBitFlag &= ~bitFlag;
|
|
Player *leader = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
|
if (leader != nullptr)
|
|
groupFlags = Groups::getGroupFlags(leader);
|
|
}
|
|
|
|
for (int i = 0; i < targetData[0]; i++) {
|
|
Player* varPlr = PlayerManager::getPlayerFromID(targetData[i+1]);
|
|
if (!((groupFlags | varPlr->iSelfConditionBitFlag) & bitFlag)) {
|
|
CNSocket* sockTo = PlayerManager::getSockFromID(targetData[i+1]);
|
|
if (sockTo == nullptr)
|
|
continue; // sanity check
|
|
|
|
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, resp);
|
|
resp.eCSTB = timeBuffID; // eCharStatusTimeBuffID
|
|
resp.eTBU = 2; // eTimeBuffUpdate
|
|
resp.eTBT = 1; // eTimeBuffType 1 means nano
|
|
varPlr->iConditionBitFlag &= ~bitFlag;
|
|
resp.iConditionBitFlag = varPlr->iConditionBitFlag |= groupFlags | varPlr->iSelfConditionBitFlag;
|
|
|
|
if (amount > 0)
|
|
resp.TimeBuff.iValue = amount;
|
|
|
|
sockTo->sendPacket((void*)&resp, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
|
}
|
|
}
|
|
}
|
|
|
|
int Abilities::applyBuff(CNSocket* sock, int skillID, int eTBU, int eTBT, int32_t groupFlags) {
|
|
if (SkillTable[skillID].drainType == 1)
|
|
return 0;
|
|
|
|
int32_t bitFlag = 0;
|
|
|
|
for (auto& pwr : Powers) {
|
|
if (pwr.skillType == SkillTable[skillID].skillType) {
|
|
bitFlag = pwr.bitFlag;
|
|
Player *plr = PlayerManager::getPlayer(sock);
|
|
if (eTBU == 1 || !((groupFlags | plr->iSelfConditionBitFlag) & bitFlag)) {
|
|
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, resp);
|
|
resp.eCSTB = pwr.timeBuffID;
|
|
resp.eTBU = eTBU;
|
|
resp.eTBT = eTBT;
|
|
|
|
if (eTBU == 1)
|
|
plr->iConditionBitFlag |= bitFlag;
|
|
else
|
|
plr->iConditionBitFlag &= ~bitFlag;
|
|
|
|
resp.iConditionBitFlag = plr->iConditionBitFlag |= groupFlags | plr->iSelfConditionBitFlag;
|
|
resp.TimeBuff.iValue = SkillTable[skillID].powerIntensity[0];
|
|
sock->sendPacket((void*)&resp, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
|
}
|
|
return bitFlag;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
namespace Abilities {
|
|
|
|
bool doDebuff(CNSocket *sock, sSkillResult_Buff *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) {
|
|
if (NPCManager::NPCs.find(targetID) == NPCManager::NPCs.end()) {
|
|
std::cout << "[WARN] doDebuff: NPC ID not found" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
BaseNPC* npc = NPCManager::NPCs[targetID];
|
|
if (npc->kind != EntityType::MOB) {
|
|
std::cout << "[WARN] doDebuff: NPC is not a mob" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
Mob* mob = (Mob*)npc;
|
|
Combat::hitMob(sock, mob, 0);
|
|
|
|
respdata[i].eCT = 4;
|
|
respdata[i].iID = mob->id;
|
|
respdata[i].bProtected = 1;
|
|
if (mob->skillStyle < 0 && mob->state != MobState::RETREAT
|
|
&& !(mob->cbf & CSB_BIT_FREEDOM)) { // only debuff if the enemy is not retreating, casting corruption or in freedom
|
|
mob->cbf |= bitFlag;
|
|
mob->unbuffTimes[bitFlag] = getTime() + duration * 100;
|
|
respdata[i].bProtected = 0;
|
|
}
|
|
respdata[i].iConditionBitFlag = mob->cbf;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool doBuff(CNSocket *sock, sSkillResult_Buff *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) {
|
|
Player *plr = nullptr;
|
|
CNSocket *sockTo = nullptr;
|
|
|
|
for (auto& pair : PlayerManager::players) {
|
|
if (pair.second->iID == targetID) {
|
|
sockTo = pair.first;
|
|
plr = pair.second;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// player not found
|
|
if (sockTo == nullptr || plr == nullptr) {
|
|
std::cout << "[WARN] doBuff: player ID not found" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
respdata[i].eCT = 1;
|
|
respdata[i].iID = plr->iID;
|
|
respdata[i].iConditionBitFlag = 0;
|
|
|
|
// only apply buffs if the player is actually alive
|
|
if (plr->HP > 0) {
|
|
respdata[i].iConditionBitFlag = bitFlag;
|
|
|
|
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt);
|
|
pkt.eCSTB = timeBuffID; // eCharStatusTimeBuffID
|
|
pkt.eTBU = 1; // eTimeBuffUpdate
|
|
pkt.eTBT = 1; // eTimeBuffType 1 means nano
|
|
pkt.iConditionBitFlag = plr->iConditionBitFlag |= bitFlag;
|
|
|
|
if (amount > 0)
|
|
pkt.TimeBuff.iValue = amount;
|
|
|
|
sockTo->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool doDamageNDebuff(CNSocket *sock, sSkillResult_Damage_N_Debuff *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) {
|
|
if (NPCManager::NPCs.find(targetID) == NPCManager::NPCs.end()) {
|
|
// not sure how to best handle this
|
|
std::cout << "[WARN] doDamageNDebuff: NPC ID not found" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
BaseNPC* npc = NPCManager::NPCs[targetID];
|
|
if (npc->kind != EntityType::MOB) {
|
|
std::cout << "[WARN] doDamageNDebuff: NPC is not a mob" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
Mob* mob = (Mob*)npc;
|
|
|
|
Combat::hitMob(sock, mob, 0); // just to gain aggro
|
|
|
|
respdata[i].eCT = 4;
|
|
respdata[i].iDamage = duration / 10;
|
|
respdata[i].iID = mob->id;
|
|
respdata[i].iHP = mob->hp;
|
|
respdata[i].bProtected = 1;
|
|
if (mob->skillStyle < 0 && mob->state != MobState::RETREAT
|
|
&& !(mob->cbf & CSB_BIT_FREEDOM)) { // only debuff if the enemy is not retreating, casting corruption or in freedom
|
|
mob->cbf |= bitFlag;
|
|
mob->unbuffTimes[bitFlag] = getTime() + duration * 100;
|
|
respdata[i].bProtected = 0;
|
|
}
|
|
respdata[i].iConditionBitFlag = mob->cbf;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool doHeal(CNSocket *sock, sSkillResult_Heal_HP *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) {
|
|
Player *plr = nullptr;
|
|
|
|
for (auto& pair : PlayerManager::players) {
|
|
if (pair.second->iID == targetID) {
|
|
plr = pair.second;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// player not found
|
|
if (plr == nullptr) {
|
|
std::cout << "[WARN] doHeal: player ID not found" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
int healedAmount = PC_MAXHEALTH(plr->level) * amount / 1000;
|
|
|
|
// do not heal dead players
|
|
if (plr->HP <= 0)
|
|
healedAmount = 0;
|
|
|
|
plr->HP += healedAmount;
|
|
|
|
if (plr->HP > PC_MAXHEALTH(plr->level))
|
|
plr->HP = PC_MAXHEALTH(plr->level);
|
|
|
|
respdata[i].eCT = 1;
|
|
respdata[i].iID = plr->iID;
|
|
respdata[i].iHP = plr->HP;
|
|
respdata[i].iHealHP = healedAmount;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool doDamage(CNSocket *sock, sSkillResult_Damage *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) {
|
|
if (NPCManager::NPCs.find(targetID) == NPCManager::NPCs.end()) {
|
|
// not sure how to best handle this
|
|
std::cout << "[WARN] doDamage: NPC ID not found" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
BaseNPC* npc = NPCManager::NPCs[targetID];
|
|
if (npc->kind != EntityType::MOB) {
|
|
std::cout << "[WARN] doDamage: NPC is not a mob" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
Mob* mob = (Mob*)npc;
|
|
Player *plr = PlayerManager::getPlayer(sock);
|
|
|
|
int damage = Combat::hitMob(sock, mob, std::max(PC_MAXHEALTH(plr->level) * amount / 1000, mob->maxHealth * amount / 1000));
|
|
|
|
respdata[i].eCT = 4;
|
|
respdata[i].iDamage = damage;
|
|
respdata[i].iID = mob->id;
|
|
respdata[i].iHP = mob->hp;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* NOTE: Leech is specially encoded.
|
|
*
|
|
* It manages to fit inside the nanoPower<>() mold with only a slight hack,
|
|
* but it really is it's own thing. There is a hard assumption that players
|
|
* will only every leech a single mob, and the sanity check that enforces that
|
|
* assumption is critical.
|
|
*/
|
|
|
|
bool doLeech(CNSocket *sock, sSkillResult_Heal_HP *healdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) {
|
|
// this sanity check is VERY important
|
|
if (i != 0) {
|
|
std::cout << "[WARN] Player attempted to leech more than one mob!" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
sSkillResult_Damage *damagedata = (sSkillResult_Damage*)(((uint8_t*)healdata) + sizeof(sSkillResult_Heal_HP));
|
|
Player *plr = PlayerManager::getPlayer(sock);
|
|
|
|
int healedAmount = amount;
|
|
|
|
plr->HP += healedAmount;
|
|
|
|
if (plr->HP > PC_MAXHEALTH(plr->level))
|
|
plr->HP = PC_MAXHEALTH(plr->level);
|
|
|
|
healdata->eCT = 1;
|
|
healdata->iID = plr->iID;
|
|
healdata->iHP = plr->HP;
|
|
healdata->iHealHP = healedAmount;
|
|
|
|
if (NPCManager::NPCs.find(targetID) == NPCManager::NPCs.end()) {
|
|
// not sure how to best handle this
|
|
std::cout << "[WARN] doLeech: NPC ID not found" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
BaseNPC* npc = NPCManager::NPCs[targetID];
|
|
if (npc->kind != EntityType::MOB) {
|
|
std::cout << "[WARN] doLeech: NPC is not a mob" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
Mob* mob = (Mob*)npc;
|
|
|
|
int damage = Combat::hitMob(sock, mob, amount * 2);
|
|
|
|
damagedata->eCT = 4;
|
|
damagedata->iDamage = damage;
|
|
damagedata->iID = mob->id;
|
|
damagedata->iHP = mob->hp;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool doResurrect(CNSocket *sock, sSkillResult_Resurrect *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) {
|
|
Player *plr = nullptr;
|
|
|
|
for (auto& pair : PlayerManager::players) {
|
|
if (pair.second->iID == targetID) {
|
|
plr = pair.second;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// player not found
|
|
if (plr == nullptr) {
|
|
std::cout << "[WARN] doResurrect: player ID not found" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
respdata[i].eCT = 1;
|
|
respdata[i].iID = plr->iID;
|
|
respdata[i].iRegenHP = plr->HP;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool doMove(CNSocket *sock, sSkillResult_Move *respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) {
|
|
Player *plr = nullptr;
|
|
|
|
for (auto& pair : PlayerManager::players) {
|
|
if (pair.second->iID == targetID) {
|
|
plr = pair.second;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// player not found
|
|
if (plr == nullptr) {
|
|
std::cout << "[WARN] doMove: player ID not found" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
Player *plr2 = PlayerManager::getPlayer(sock);
|
|
|
|
respdata[i].eCT = 1;
|
|
respdata[i].iID = plr->iID;
|
|
respdata[i].iMapNum = plr2->recallInstance;
|
|
respdata[i].iMoveX = plr2->recallX;
|
|
respdata[i].iMoveY = plr2->recallY;
|
|
respdata[i].iMoveZ = plr2->recallZ;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool doDamageNDebuff(Mob* mob, sSkillResult_Damage_N_Debuff* respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) {
|
|
CNSocket* sock = nullptr;
|
|
Player* plr = nullptr;
|
|
|
|
for (auto& pair : PlayerManager::players) {
|
|
if (pair.second->iID == targetID) {
|
|
sock = pair.first;
|
|
plr = pair.second;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// player not found
|
|
if (plr == nullptr) {
|
|
std::cout << "[WARN] doDamageNDebuff: player ID not found" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
respdata[i].eCT = 1;
|
|
respdata[i].iDamage = duration / 10;
|
|
respdata[i].iID = plr->iID;
|
|
respdata[i].iHP = plr->HP;
|
|
respdata[i].iStamina = plr->Nanos[plr->activeNano].iStamina;
|
|
if (plr->iConditionBitFlag & CSB_BIT_FREEDOM)
|
|
respdata[i].bProtected = 1;
|
|
else {
|
|
if (!(plr->iConditionBitFlag & bitFlag)) {
|
|
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt);
|
|
pkt.eCSTB = timeBuffID; // eCharStatusTimeBuffID
|
|
pkt.eTBU = 1; // eTimeBuffUpdate
|
|
pkt.eTBT = 2;
|
|
pkt.iConditionBitFlag = plr->iConditionBitFlag |= bitFlag;
|
|
pkt.TimeBuff.iValue = amount * 5;
|
|
sock->sendPacket((void*)&pkt, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE));
|
|
}
|
|
|
|
respdata[i].bProtected = 0;
|
|
std::pair<CNSocket*, int32_t> key = std::make_pair(sock, bitFlag);
|
|
time_t until = getTime() + (time_t)duration * 100;
|
|
Eggs::EggBuffs[key] = until;
|
|
}
|
|
respdata[i].iConditionBitFlag = plr->iConditionBitFlag;
|
|
|
|
if (plr->HP <= 0) {
|
|
mob->target = nullptr;
|
|
mob->state = MobState::RETREAT;
|
|
if (!MobAI::aggroCheck(mob, getTime())) {
|
|
MobAI::clearDebuff(mob);
|
|
if (mob->groupLeader != 0)
|
|
MobAI::groupRetreat(mob);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool doHeal(Mob* mob, sSkillResult_Heal_HP* respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) {
|
|
if (NPCManager::NPCs.find(targetID) == NPCManager::NPCs.end()) {
|
|
std::cout << "[WARN] doHeal: NPC ID not found" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
BaseNPC* npc = NPCManager::NPCs[targetID];
|
|
if (npc->kind != EntityType::MOB) {
|
|
std::cout << "[WARN] doHeal: NPC is not a mob" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
Mob* targetMob = (Mob*)npc;
|
|
|
|
int healedAmount = amount * targetMob->maxHealth / 1000;
|
|
targetMob->hp += healedAmount;
|
|
if (targetMob->hp > targetMob->maxHealth)
|
|
targetMob->hp = targetMob->maxHealth;
|
|
|
|
respdata[i].eCT = 4;
|
|
respdata[i].iID = targetMob->id;
|
|
respdata[i].iHP = targetMob->hp;
|
|
respdata[i].iHealHP = healedAmount;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool doReturnHeal(Mob* mob, sSkillResult_Heal_HP* respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) {
|
|
int healedAmount = amount * mob->maxHealth / 1000;
|
|
mob->hp += healedAmount;
|
|
if (mob->hp > mob->maxHealth)
|
|
mob->hp = mob->maxHealth;
|
|
|
|
respdata[i].eCT = 4;
|
|
respdata[i].iID = mob->id;
|
|
respdata[i].iHP = mob->hp;
|
|
respdata[i].iHealHP = healedAmount;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool doDamage(Mob* mob, sSkillResult_Damage* respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) {
|
|
Player* plr = nullptr;
|
|
|
|
for (auto& pair : PlayerManager::players) {
|
|
if (pair.second->iID == targetID) {
|
|
plr = pair.second;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// player not found
|
|
if (plr == nullptr) {
|
|
std::cout << "[WARN] doDamage: player ID not found" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
int damage = amount * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500;
|
|
|
|
if (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)
|
|
damage = 0;
|
|
|
|
respdata[i].eCT = 1;
|
|
respdata[i].iDamage = damage;
|
|
respdata[i].iID = plr->iID;
|
|
respdata[i].iHP = plr->HP -= damage;
|
|
|
|
if (plr->HP <= 0) {
|
|
mob->target = nullptr;
|
|
mob->state = MobState::RETREAT;
|
|
if (!MobAI::aggroCheck(mob, getTime())) {
|
|
MobAI::clearDebuff(mob);
|
|
if (mob->groupLeader != 0)
|
|
MobAI::groupRetreat(mob);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool doLeech(Mob* mob, sSkillResult_Heal_HP* healdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) {
|
|
// this sanity check is VERY important
|
|
if (i != 0) {
|
|
std::cout << "[WARN] Mob attempted to leech more than one player!" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
Player* plr = nullptr;
|
|
|
|
for (auto& pair : PlayerManager::players) {
|
|
if (pair.second->iID == targetID) {
|
|
plr = pair.second;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// player not found
|
|
if (plr == nullptr) {
|
|
std::cout << "[WARN] doLeech: player ID not found" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
sSkillResult_Damage* damagedata = (sSkillResult_Damage*)(((uint8_t*)healdata) + sizeof(sSkillResult_Heal_HP));
|
|
|
|
int healedAmount = amount * PC_MAXHEALTH(plr->level) / 1000;
|
|
|
|
mob->hp += healedAmount;
|
|
if (mob->hp > mob->maxHealth)
|
|
mob->hp = mob->maxHealth;
|
|
|
|
healdata->eCT = 4;
|
|
healdata->iID = mob->id;
|
|
healdata->iHP = mob->hp;
|
|
healdata->iHealHP = healedAmount;
|
|
|
|
int damage = healedAmount;
|
|
|
|
if (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)
|
|
damage = 0;
|
|
|
|
damagedata->eCT = 1;
|
|
damagedata->iDamage = damage;
|
|
damagedata->iID = plr->iID;
|
|
damagedata->iHP = plr->HP -= damage;
|
|
|
|
if (plr->HP <= 0) {
|
|
mob->target = nullptr;
|
|
mob->state = MobState::RETREAT;
|
|
if (!MobAI::aggroCheck(mob, getTime())) {
|
|
MobAI::clearDebuff(mob);
|
|
if (mob->groupLeader != 0)
|
|
MobAI::groupRetreat(mob);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool doBatteryDrain(Mob* mob, sSkillResult_BatteryDrain* respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) {
|
|
Player* plr = nullptr;
|
|
|
|
for (auto& pair : PlayerManager::players) {
|
|
if (pair.second->iID == targetID) {
|
|
plr = pair.second;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// player not found
|
|
if (plr == nullptr) {
|
|
std::cout << "[WARN] doBatteryDrain: player ID not found" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
respdata[i].eCT = 1;
|
|
respdata[i].iID = plr->iID;
|
|
|
|
if (plr->iConditionBitFlag & CSB_BIT_PROTECT_BATTERY) {
|
|
respdata[i].bProtected = 1;
|
|
respdata[i].iDrainW = 0;
|
|
respdata[i].iDrainN = 0;
|
|
}
|
|
else {
|
|
respdata[i].bProtected = 0;
|
|
respdata[i].iDrainW = amount * (18 + (int)mob->data["m_iNpcLevel"]) / 36;
|
|
respdata[i].iDrainN = amount * (18 + (int)mob->data["m_iNpcLevel"]) / 36;
|
|
}
|
|
|
|
respdata[i].iBatteryW = plr->batteryW -= (respdata[i].iDrainW < plr->batteryW) ? respdata[i].iDrainW : plr->batteryW;
|
|
respdata[i].iBatteryN = plr->batteryN -= (respdata[i].iDrainN < plr->batteryN) ? respdata[i].iDrainN : plr->batteryN;
|
|
respdata[i].iStamina = plr->Nanos[plr->activeNano].iStamina;
|
|
respdata[i].iConditionBitFlag = plr->iConditionBitFlag;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool doBuff(Mob* mob, sSkillResult_Buff* respdata, int i, int32_t targetID, int32_t bitFlag, int16_t timeBuffID, int16_t duration, int16_t amount) {
|
|
respdata[i].eCT = 4;
|
|
respdata[i].iID = mob->id;
|
|
mob->cbf |= bitFlag;
|
|
respdata[i].iConditionBitFlag = mob->cbf;
|
|
|
|
return true;
|
|
}
|
|
|
|
template<class sPAYLOAD,
|
|
bool (*work)(EntityRef, sPAYLOAD*, int, int32_t, int32_t, int16_t, int16_t, int16_t)>
|
|
void power(EntityRef ref, std::vector<int> targetData,
|
|
int16_t nanoID, int16_t skillID, int16_t duration, int16_t amount,
|
|
int16_t skillType, int32_t bitFlag, int16_t timeBuffID) {
|
|
size_t resplen;
|
|
// special case since leech is atypically encoded
|
|
if (skillType == EST_BLOODSUCKING)
|
|
resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + sizeof(sSkillResult_Heal_HP) + sizeof(sSkillResult_Damage);
|
|
else
|
|
resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + targetData[0] * sizeof(sPAYLOAD);
|
|
|
|
// validate response packet
|
|
if (!validOutVarPacket(sizeof(sP_FE2CL_NPC_SKILL_HIT), targetData[0], sizeof(sPAYLOAD))) {
|
|
std::cout << "[WARN] bad sP_FE2CL_NPC_SKILL_HIT packet size" << std::endl;
|
|
return;
|
|
}
|
|
|
|
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
|
memset(respbuf, 0, resplen);
|
|
|
|
sP_FE2CL_NPC_SKILL_HIT* resp = (sP_FE2CL_NPC_SKILL_HIT*)respbuf;
|
|
sPAYLOAD* respdata = (sPAYLOAD*)(respbuf + sizeof(sP_FE2CL_NPC_SKILL_HIT));
|
|
|
|
resp->iNPC_ID = mob->id;
|
|
resp->iSkillID = skillID;
|
|
resp->iValue1 = mob->hitX;
|
|
resp->iValue2 = mob->hitY;
|
|
resp->iValue3 = mob->hitZ;
|
|
resp->eST = skillType;
|
|
resp->iTargetCnt = targetData[0];
|
|
|
|
for (int i = 0; i < targetData[0]; i++)
|
|
if (!work(mob, respdata, i, targetData[i + 1], bitFlag, timeBuffID, duration, amount))
|
|
return;
|
|
|
|
NPCManager::sendToViewable(mob, (void*)&respbuf, P_FE2CL_NPC_SKILL_HIT, resplen);
|
|
}
|
|
|
|
// nano power dispatch table
|
|
std::vector<Power> Powers = {};/*
|
|
Power(EST_STUN, CSB_BIT_STUN, ECSB_STUN, power<sSkillResult_Damage_N_Debuff, doDamageNDebuff>),
|
|
Power(EST_HEAL_HP, CSB_BIT_NONE, ECSB_NONE, power<sSkillResult_Heal_HP, doHeal>),
|
|
Power(EST_BOUNDINGBALL, CSB_BIT_BOUNDINGBALL, ECSB_BOUNDINGBALL, power<sSkillResult_Buff, doDebuff>),
|
|
Power(EST_SNARE, CSB_BIT_DN_MOVE_SPEED, ECSB_DN_MOVE_SPEED, power<sSkillResult_Damage_N_Debuff, doDamageNDebuff>),
|
|
Power(EST_DAMAGE, CSB_BIT_NONE, ECSB_NONE, power<sSkillResult_Damage, doDamage>),
|
|
Power(EST_BLOODSUCKING, CSB_BIT_NONE, ECSB_NONE, power<sSkillResult_Heal_HP, doLeech>),
|
|
Power(EST_SLEEP, CSB_BIT_MEZ, ECSB_MEZ, power<sSkillResult_Damage_N_Debuff, doDamageNDebuff>),
|
|
Power(EST_REWARDBLOB, CSB_BIT_REWARD_BLOB, ECSB_REWARD_BLOB, power<sSkillResult_Buff, doBuff>),
|
|
Power(EST_RUN, CSB_BIT_UP_MOVE_SPEED, ECSB_UP_MOVE_SPEED, power<sSkillResult_Buff, doBuff>),
|
|
Power(EST_REWARDCASH, CSB_BIT_REWARD_CASH, ECSB_REWARD_CASH, power<sSkillResult_Buff, doBuff>),
|
|
Power(EST_PROTECTBATTERY, CSB_BIT_PROTECT_BATTERY, ECSB_PROTECT_BATTERY, power<sSkillResult_Buff, doBuff>),
|
|
Power(EST_MINIMAPENEMY, CSB_BIT_MINIMAP_ENEMY, ECSB_MINIMAP_ENEMY, power<sSkillResult_Buff, doBuff>),
|
|
Power(EST_PROTECTINFECTION, CSB_BIT_PROTECT_INFECTION, ECSB_PROTECT_INFECTION, power<sSkillResult_Buff, doBuff>),
|
|
Power(EST_JUMP, CSB_BIT_UP_JUMP_HEIGHT, ECSB_UP_JUMP_HEIGHT, power<sSkillResult_Buff, doBuff>),
|
|
Power(EST_FREEDOM, CSB_BIT_FREEDOM, ECSB_FREEDOM, power<sSkillResult_Buff, doBuff>),
|
|
Power(EST_PHOENIX, CSB_BIT_PHOENIX, ECSB_PHOENIX, power<sSkillResult_Buff, doBuff>),
|
|
Power(EST_STEALTH, CSB_BIT_UP_STEALTH, ECSB_UP_STEALTH, power<sSkillResult_Buff, doBuff>),
|
|
Power(EST_MINIMAPTRESURE, CSB_BIT_MINIMAP_TRESURE, ECSB_MINIMAP_TRESURE, power<sSkillResult_Buff, doBuff>),
|
|
Power(EST_RECALL, CSB_BIT_NONE, ECSB_NONE, power<sSkillResult_Move, doMove>),
|
|
Power(EST_RECALL_GROUP, CSB_BIT_NONE, ECSB_NONE, power<sSkillResult_Move, doMove>),
|
|
Power(EST_RETROROCKET_SELF, CSB_BIT_NONE, ECSB_NONE, power<sSkillResult_Buff, doBuff>),
|
|
Power(EST_PHOENIX_GROUP, CSB_BIT_NONE, ECSB_NONE, power<sSkillResult_Resurrect, doResurrect>),
|
|
Power(EST_NANOSTIMPAK, CSB_BIT_STIMPAKSLOT1, ECSB_STIMPAKSLOT1, power<sSkillResult_Buff, doBuff>),
|
|
Power(EST_NANOSTIMPAK, CSB_BIT_STIMPAKSLOT2, ECSB_STIMPAKSLOT2, power<sSkillResult_Buff, doBuff>),
|
|
Power(EST_NANOSTIMPAK, CSB_BIT_STIMPAKSLOT3, ECSB_STIMPAKSLOT3, power<sSkillResult_Buff, doBuff>),
|
|
//
|
|
Power(EST_STUN, CSB_BIT_STUN, ECSB_STUN, power<sSkillResult_Damage_N_Debuff, doDamageNDebuff>),
|
|
Power(EST_HEAL_HP, CSB_BIT_NONE, ECSB_NONE, power<sSkillResult_Heal_HP, doHeal>),
|
|
Power(EST_RETURNHOMEHEAL, CSB_BIT_NONE, ECSB_NONE, power<sSkillResult_Heal_HP, doReturnHeal>),
|
|
Power(EST_SNARE, CSB_BIT_DN_MOVE_SPEED, ECSB_DN_MOVE_SPEED, power<sSkillResult_Damage_N_Debuff, doDamageNDebuff>),
|
|
Power(EST_DAMAGE, CSB_BIT_NONE, ECSB_NONE, power<sSkillResult_Damage, doDamage>),
|
|
Power(EST_BATTERYDRAIN, CSB_BIT_NONE, ECSB_NONE, power<sSkillResult_BatteryDrain, doBatteryDrain>),
|
|
Power(EST_SLEEP, CSB_BIT_MEZ, ECSB_MEZ, power<sSkillResult_Damage_N_Debuff, doDamageNDebuff>),
|
|
Power(EST_BLOODSUCKING, CSB_BIT_NONE, ECSB_NONE, power<sSkillResult_Heal_HP, doLeech>),
|
|
Power(EST_FREEDOM, CSB_BIT_FREEDOM, ECSB_FREEDOM, power<sSkillResult_Buff, doBuff>)
|
|
};*/
|
|
|
|
}; // namespace
|