mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2024-12-22 19:30:05 +00:00
[refactor] Remaining ICombatant implementation
This commit is contained in:
parent
07fe8ca367
commit
4b834579c5
@ -6,774 +6,8 @@
|
||||
#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;
|
||||
mob->takeDamage(sock, 0);
|
||||
|
||||
respdata[i].eCT = 4;
|
||||
respdata[i].iID = mob->id;
|
||||
respdata[i].bProtected = 1;
|
||||
if (mob->skillStyle < 0 && mob->state != AIState::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;
|
||||
|
||||
mob->takeDamage(sock, 0);
|
||||
|
||||
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 != AIState::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 = mob->takeDamage(sock, 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 = mob->takeDamage(sock, 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) {
|
||||
if (!MobAI::aggroCheck(mob, getTime()))
|
||||
mob->transition(AIState::RETREAT, mob->target);
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!MobAI::aggroCheck(mob, getTime()))
|
||||
mob->transition(AIState::RETREAT, mob->target);
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!MobAI::aggroCheck(mob, getTime()))
|
||||
mob->transition(AIState::RETREAT, mob->target);
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
/*** OLD NANO HANDLER ***
|
||||
Player *plr = PlayerManager::getPlayer(sock);
|
||||
|
||||
if (skillType == EST_RETROROCKET_SELF || skillType == EST_RECALL) // rocket and self recall does not need any trailing structs
|
||||
targetData[0] = 0;
|
||||
|
||||
size_t resplen;
|
||||
// special case since leech is atypically encoded
|
||||
if (skillType == EST_BLOODSUCKING)
|
||||
resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + sizeof(sSkillResult_Heal_HP) + sizeof(sSkillResult_Damage);
|
||||
else
|
||||
resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + targetData[0] * sizeof(sPAYLOAD);
|
||||
|
||||
// validate response packet
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC), targetData[0], sizeof(sPAYLOAD))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_NANO_SKILL_USE packet size" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
|
||||
memset(respbuf, 0, resplen);
|
||||
|
||||
sP_FE2CL_NANO_SKILL_USE_SUCC *resp = (sP_FE2CL_NANO_SKILL_USE_SUCC*)respbuf;
|
||||
sPAYLOAD *respdata = (sPAYLOAD*)(respbuf+sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC));
|
||||
|
||||
resp->iPC_ID = plr->iID;
|
||||
resp->iSkillID = skillID;
|
||||
resp->iNanoID = nanoID;
|
||||
resp->iNanoStamina = plr->Nanos[plr->activeNano].iStamina;
|
||||
resp->eST = skillType;
|
||||
resp->iTargetCnt = targetData[0];
|
||||
|
||||
if (SkillTable[skillID].drainType == 2) {
|
||||
if (SkillTable[skillID].targetType >= 2)
|
||||
plr->iSelfConditionBitFlag |= bitFlag;
|
||||
if (SkillTable[skillID].targetType == 3)
|
||||
plr->iGroupConditionBitFlag |= bitFlag;
|
||||
}
|
||||
|
||||
for (int i = 0; i < targetData[0]; i++)
|
||||
if (!work(sock, respdata, i, targetData[i+1], bitFlag, timeBuffID, duration, amount))
|
||||
return;
|
||||
|
||||
sock->sendPacket((void*)&respbuf, P_FE2CL_NANO_SKILL_USE_SUCC, resplen);
|
||||
assert(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) == sizeof(sP_FE2CL_NANO_SKILL_USE));
|
||||
if (skillType == EST_RECALL_GROUP) { // in the case of group recall, nobody but group members need the packet
|
||||
for (int i = 0; i < targetData[0]; i++) {
|
||||
CNSocket *sock2 = PlayerManager::getSockFromID(targetData[i+1]);
|
||||
sock2->sendPacket((void*)&respbuf, P_FE2CL_NANO_SKILL_USE, resplen);
|
||||
}
|
||||
} else
|
||||
PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_NANO_SKILL_USE, resplen);
|
||||
|
||||
// Warping on recall
|
||||
if (skillType == EST_RECALL || skillType == EST_RECALL_GROUP) {
|
||||
if ((int32_t)plr->instanceID == plr->recallInstance)
|
||||
PlayerManager::sendPlayerTo(sock, plr->recallX, plr->recallY, plr->recallZ, plr->recallInstance);
|
||||
else {
|
||||
INITSTRUCT(sP_FE2CL_REP_WARP_USE_RECALL_FAIL, response)
|
||||
sock->sendPacket((void*)&response, P_FE2CL_REP_WARP_USE_RECALL_FAIL, sizeof(sP_FE2CL_REP_WARP_USE_RECALL_FAIL));
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/*** OLD MOB ABILITY HANDLER ***
|
||||
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
|
||||
|
@ -3,29 +3,6 @@
|
||||
#include "core/Core.hpp"
|
||||
#include "Combat.hpp"
|
||||
|
||||
typedef void (*PowerHandler)(EntityRef, std::vector<int>, int16_t, int16_t, int16_t, int16_t, int16_t, int32_t, int16_t);
|
||||
|
||||
struct Power {
|
||||
int16_t skillType;
|
||||
int32_t bitFlag;
|
||||
int16_t timeBuffID;
|
||||
PowerHandler handler;
|
||||
|
||||
Power(int16_t s, int32_t b, int16_t t, PowerHandler h) : skillType(s), bitFlag(b), timeBuffID(t), handler(h) {}
|
||||
|
||||
void handle(EntityRef ref, std::vector<int> targetData, int16_t nanoID, int16_t skillID, int16_t duration, int16_t amount) {
|
||||
if (handler == nullptr)
|
||||
return;
|
||||
|
||||
handler(ref, targetData, nanoID, skillID, duration, amount, skillType, bitFlag, timeBuffID);
|
||||
}
|
||||
|
||||
/* overload for non-nano abilities */
|
||||
void handle(EntityRef ref, std::vector<int> targetData, int16_t skillID, int16_t duration, int16_t amount) {
|
||||
handle(ref, targetData, -1, skillID, duration, amount);
|
||||
}
|
||||
};
|
||||
|
||||
struct SkillData {
|
||||
int skillType;
|
||||
int targetType;
|
||||
@ -37,11 +14,5 @@ struct SkillData {
|
||||
};
|
||||
|
||||
namespace Abilities {
|
||||
extern std::vector<Power> Powers;
|
||||
extern std::map<int32_t, SkillData> SkillTable;
|
||||
|
||||
void removeBuff(CNSocket* sock, std::vector<int> targetData, int32_t bitFlag, int16_t timeBuffID, int16_t amount, bool groupPower);
|
||||
int applyBuff(CNSocket* sock, int skillID, int eTBU, int eTBT, int32_t groupFlags);
|
||||
|
||||
std::vector<int> findTargets(Player* plr, int skillID, CNPacketData* data = nullptr);
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ void Chunking::trackEntity(ChunkPos chunkPos, const EntityRef& ref) {
|
||||
|
||||
chunks[chunkPos]->entities.insert(ref);
|
||||
|
||||
if (ref.type == EntityType::PLAYER)
|
||||
if (ref.kind == EntityKind::PLAYER)
|
||||
chunks[chunkPos]->nplayers++;
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef& ref) {
|
||||
|
||||
chunk->entities.erase(ref); // gone
|
||||
|
||||
if (ref.type == EntityType::PLAYER)
|
||||
if (ref.kind == EntityKind::PLAYER)
|
||||
chunks[chunkPos]->nplayers--;
|
||||
assert(chunks[chunkPos]->nplayers >= 0);
|
||||
|
||||
@ -89,19 +89,19 @@ void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef& ref) {
|
||||
Entity *other = otherRef.getEntity();
|
||||
|
||||
// notify all visible players of the existence of this Entity
|
||||
if (alive && otherRef.type == EntityType::PLAYER) {
|
||||
if (alive && otherRef.kind == EntityKind::PLAYER) {
|
||||
ent->enterIntoViewOf(otherRef.sock);
|
||||
}
|
||||
|
||||
// notify this *player* of the existence of all visible Entities
|
||||
if (ref.type == EntityType::PLAYER && other->isExtant()) {
|
||||
if (ref.kind == EntityKind::PLAYER && other->isExtant()) {
|
||||
other->enterIntoViewOf(ref.sock);
|
||||
}
|
||||
|
||||
// for mobs, increment playersInView
|
||||
if (ref.type == EntityType::MOB && otherRef.type == EntityType::PLAYER)
|
||||
if (ref.kind == EntityKind::MOB && otherRef.kind == EntityKind::PLAYER)
|
||||
((Mob*)ent)->playersInView++;
|
||||
if (otherRef.type == EntityType::MOB && ref.type == EntityType::PLAYER)
|
||||
if (otherRef.kind == EntityKind::MOB && ref.kind == EntityKind::PLAYER)
|
||||
((Mob*)other)->playersInView++;
|
||||
}
|
||||
}
|
||||
@ -121,19 +121,19 @@ void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef& r
|
||||
Entity *other = otherRef.getEntity();
|
||||
|
||||
// notify all visible players of the departure of this Entity
|
||||
if (alive && otherRef.type == EntityType::PLAYER) {
|
||||
if (alive && otherRef.kind == EntityKind::PLAYER) {
|
||||
ent->disappearFromViewOf(otherRef.sock);
|
||||
}
|
||||
|
||||
// notify this *player* of the departure of all visible Entities
|
||||
if (ref.type == EntityType::PLAYER && other->isExtant()) {
|
||||
if (ref.kind == EntityKind::PLAYER && other->isExtant()) {
|
||||
other->disappearFromViewOf(ref.sock);
|
||||
}
|
||||
|
||||
// for mobs, decrement playersInView
|
||||
if (ref.type == EntityType::MOB && otherRef.type == EntityType::PLAYER)
|
||||
if (ref.kind == EntityKind::MOB && otherRef.kind == EntityKind::PLAYER)
|
||||
((Mob*)ent)->playersInView--;
|
||||
if (otherRef.type == EntityType::MOB && ref.type == EntityType::PLAYER)
|
||||
if (otherRef.kind == EntityKind::MOB && ref.kind == EntityKind::PLAYER)
|
||||
((Mob*)other)->playersInView--;
|
||||
}
|
||||
}
|
||||
@ -155,7 +155,7 @@ static void emptyChunk(ChunkPos chunkPos) {
|
||||
// unspawn all of the mobs/npcs
|
||||
std::set refs(chunk->entities);
|
||||
for (const EntityRef& ref : refs) {
|
||||
if (ref.type == EntityType::PLAYER)
|
||||
if (ref.kind == EntityKind::PLAYER)
|
||||
assert(0);
|
||||
|
||||
// every call of this will check if the chunk is empty and delete it if so
|
||||
@ -268,14 +268,14 @@ void Chunking::createInstance(uint64_t instanceID) {
|
||||
std::cout << "Creating instance " << instanceID << std::endl;
|
||||
for (ChunkPos &coords : templateChunks) {
|
||||
for (const EntityRef& ref : chunks[coords]->entities) {
|
||||
if (ref.type == EntityType::PLAYER)
|
||||
if (ref.kind == EntityKind::PLAYER)
|
||||
continue;
|
||||
|
||||
int npcID = ref.id;
|
||||
BaseNPC* baseNPC = (BaseNPC*)ref.getEntity();
|
||||
|
||||
// make a copy of each NPC in the template chunks and put them in the new instance
|
||||
if (baseNPC->kind == EntityType::MOB) {
|
||||
if (baseNPC->kind == EntityKind::MOB) {
|
||||
if (((Mob*)baseNPC)->groupLeader != 0 && ((Mob*)baseNPC)->groupLeader != npcID)
|
||||
continue; // follower; don't copy individually
|
||||
|
||||
|
121
src/Combat.cpp
121
src/Combat.cpp
@ -44,41 +44,8 @@ void Player::step(time_t currTime) {
|
||||
|
||||
int CombatNPC::takeDamage(EntityRef src, int amt) {
|
||||
|
||||
/* REFACTOR: all of this logic is strongly coupled to mobs.
|
||||
* come back to this when more of it is moved to CombatNPC.
|
||||
* remove this cast when done */
|
||||
Mob* mob = (Mob*)this;
|
||||
|
||||
// cannot kill mobs multiple times; cannot harm retreating mobs
|
||||
if (mob->state != AIState::ROAMING && mob->state != AIState::COMBAT) {
|
||||
return 0; // no damage
|
||||
}
|
||||
|
||||
if (mob->skillStyle >= 0)
|
||||
return 0; // don't hurt a mob casting corruption
|
||||
|
||||
if (mob->state == AIState::ROAMING) {
|
||||
assert(mob->target == nullptr && src.type == EntityType::PLAYER); // players only for now
|
||||
mob->transition(AIState::COMBAT, src);
|
||||
|
||||
if (mob->groupLeader != 0)
|
||||
MobAI::followToCombat(mob);
|
||||
}
|
||||
|
||||
hp -= amt;
|
||||
|
||||
// wake up sleeping monster
|
||||
if (mob->cbf & CSB_BIT_MEZ) {
|
||||
mob->cbf &= ~CSB_BIT_MEZ;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
|
||||
pkt1.eCT = 2;
|
||||
pkt1.iID = mob->id;
|
||||
pkt1.iConditionBitFlag = mob->cbf;
|
||||
NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
||||
}
|
||||
|
||||
if (mob->hp <= 0)
|
||||
if (hp <= 0)
|
||||
transition(AIState::DEAD, src);
|
||||
|
||||
return amt;
|
||||
@ -101,62 +68,29 @@ int32_t CombatNPC::getID() {
|
||||
}
|
||||
|
||||
void CombatNPC::step(time_t currTime) {
|
||||
if (playersInView < 0)
|
||||
std::cout << "[WARN] Weird playerview value " << playersInView << std::endl;
|
||||
|
||||
// skip movement and combat if disabled or not in view
|
||||
if ((!MobAI::simulateMobs || playersInView == 0) && state != AIState::DEAD
|
||||
&& state != AIState::RETREAT)
|
||||
return;
|
||||
|
||||
switch (state) {
|
||||
case AIState::INACTIVE:
|
||||
// no-op
|
||||
break;
|
||||
case AIState::ROAMING:
|
||||
roamingStep(currTime);
|
||||
break;
|
||||
case AIState::COMBAT:
|
||||
combatStep(currTime);
|
||||
break;
|
||||
case AIState::RETREAT:
|
||||
retreatStep(currTime);
|
||||
break;
|
||||
case AIState::DEAD:
|
||||
deadStep(currTime);
|
||||
break;
|
||||
|
||||
if(stateHandlers.find(state) != stateHandlers.end())
|
||||
stateHandlers[state](this, currTime);
|
||||
else {
|
||||
std::cout << "[WARN] State " << (int)state << " has no handler; going inactive" << std::endl;
|
||||
transition(AIState::INACTIVE, id);
|
||||
}
|
||||
}
|
||||
|
||||
void CombatNPC::transition(AIState newState, EntityRef src) {
|
||||
|
||||
state = newState;
|
||||
switch (newState) {
|
||||
case AIState::INACTIVE:
|
||||
onInactive();
|
||||
break;
|
||||
case AIState::ROAMING:
|
||||
onRoamStart();
|
||||
break;
|
||||
case AIState::COMBAT:
|
||||
/* TODO: fire any triggered events
|
||||
for (NPCEvent& event : NPCManager::NPCEvents)
|
||||
if (event.trigger == ON_COMBAT && event.npcType == type)
|
||||
event.handler(src, this);
|
||||
*/
|
||||
onCombatStart(src);
|
||||
break;
|
||||
case AIState::RETREAT:
|
||||
onRetreat();
|
||||
break;
|
||||
case AIState::DEAD:
|
||||
/* TODO: fire any triggered events
|
||||
for (NPCEvent& event : NPCManager::NPCEvents)
|
||||
if (event.trigger == ON_KILLED && event.npcType == type)
|
||||
event.handler(src, this);
|
||||
*/
|
||||
onDeath(src);
|
||||
break;
|
||||
if (transitionHandlers.find(newState) != transitionHandlers.end())
|
||||
transitionHandlers[newState](this, src);
|
||||
else {
|
||||
std::cout << "[WARN] Transition to " << (int)state << " has no handler; going inactive" << std::endl;
|
||||
transition(AIState::INACTIVE, id);
|
||||
}
|
||||
/* TODO: fire any triggered events
|
||||
for (NPCEvent& event : NPCManager::NPCEvents)
|
||||
if (event.trigger == ON_KILLED && event.npcType == type)
|
||||
event.handler(src, this);
|
||||
*/
|
||||
}
|
||||
|
||||
static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shouldCrit,
|
||||
@ -256,7 +190,7 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
|
||||
|
||||
|
||||
BaseNPC* npc = NPCManager::NPCs[targets[i]];
|
||||
if (npc->kind != EntityType::MOB) {
|
||||
if (npc->kind != EntityKind::MOB) {
|
||||
std::cout << "[WARN] pcAttackNpcs: NPC is not a mob" << std::endl;
|
||||
return;
|
||||
}
|
||||
@ -450,7 +384,7 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t *pktdata = (int32_t*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs));
|
||||
sGM_PVPTarget* pktdata = (sGM_PVPTarget*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_PC_ATTACK_CHARs));
|
||||
|
||||
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_ATTACK_CHARs_SUCC), pkt->iTargetCnt, sizeof(sAttackResult))) {
|
||||
std::cout << "[WARN] bad sP_FE2CL_PC_ATTACK_CHARs_SUCC packet size\n";
|
||||
@ -471,7 +405,6 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
|
||||
for (int i = 0; i < pkt->iTargetCnt; i++) {
|
||||
|
||||
ICombatant* target = nullptr;
|
||||
sGM_PVPTarget* targdata = (sGM_PVPTarget*)(pktdata + i * 2);
|
||||
std::pair<int, int> damage;
|
||||
|
||||
if (pkt->iTargetCnt > 1)
|
||||
@ -479,10 +412,10 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
|
||||
else
|
||||
damage.first = plr->pointDamage;
|
||||
|
||||
if (targdata->eCT == 1) { // eCT == 1; attack player
|
||||
if (pktdata[i].eCT == 1) { // eCT == 1; attack player
|
||||
|
||||
for (auto& pair : PlayerManager::players) {
|
||||
if (pair.second->iID == targdata->iID) {
|
||||
if (pair.second->iID == pktdata[i].iID) {
|
||||
target = pair.second;
|
||||
break;
|
||||
}
|
||||
@ -498,14 +431,14 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
|
||||
|
||||
} else { // eCT == 4; attack mob
|
||||
|
||||
if (NPCManager::NPCs.find(targdata->iID) == NPCManager::NPCs.end()) {
|
||||
if (NPCManager::NPCs.find(pktdata[i].iID) == NPCManager::NPCs.end()) {
|
||||
// not sure how to best handle this
|
||||
std::cout << "[WARN] pcAttackChars: NPC ID not found" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
BaseNPC* npc = NPCManager::NPCs[targdata->iID];
|
||||
if (npc->kind != EntityType::MOB) {
|
||||
BaseNPC* npc = NPCManager::NPCs[pktdata[i].iID];
|
||||
if (npc->kind != EntityKind::MOB) {
|
||||
std::cout << "[WARN] pcAttackChars: NPC is not a mob" << std::endl;
|
||||
return;
|
||||
}
|
||||
@ -524,7 +457,7 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
|
||||
|
||||
damage.first = target->takeDamage(sock, damage.first);
|
||||
|
||||
respdata[i].eCT = targdata->eCT;
|
||||
respdata[i].eCT = pktdata[i].eCT;
|
||||
respdata[i].iID = target->getID();
|
||||
respdata[i].iDamage = damage.first;
|
||||
respdata[i].iHP = target->getCurrentHP();
|
||||
@ -707,7 +640,7 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
|
||||
}
|
||||
|
||||
BaseNPC* npc = NPCManager::NPCs[pktdata[i]];
|
||||
if (npc->kind != EntityType::MOB) {
|
||||
if (npc->kind != EntityKind::MOB) {
|
||||
std::cout << "[WARN] projectileHit: NPC is not a mob" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
@ -283,10 +283,10 @@ static void unsummonWCommand(std::string full, std::vector<std::string>& args, C
|
||||
return;
|
||||
}
|
||||
|
||||
if (NPCManager::NPCs.find(npc->id) != NPCManager::NPCs.end() && NPCManager::NPCs[npc->id]->kind == EntityType::MOB) {
|
||||
if (NPCManager::NPCs.find(npc->id) != NPCManager::NPCs.end() && NPCManager::NPCs[npc->id]->kind == EntityKind::MOB) {
|
||||
int leadId = ((Mob*)npc)->groupLeader;
|
||||
if (leadId != 0) {
|
||||
if (NPCManager::NPCs.find(leadId) == NPCManager::NPCs.end() || NPCManager::NPCs[leadId]->kind != EntityType::MOB) {
|
||||
if (NPCManager::NPCs.find(leadId) == NPCManager::NPCs.end() || NPCManager::NPCs[leadId]->kind != EntityKind::MOB) {
|
||||
std::cout << "[WARN] unsummonW: leader not found!" << std::endl;
|
||||
}
|
||||
Mob* leadNpc = (Mob*)NPCManager::NPCs[leadId];
|
||||
@ -294,7 +294,7 @@ static void unsummonWCommand(std::string full, std::vector<std::string>& args, C
|
||||
if (leadNpc->groupMember[i] == 0)
|
||||
break;
|
||||
|
||||
if (NPCManager::NPCs.find(leadNpc->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadNpc->groupMember[i]]->kind != EntityType::MOB) {
|
||||
if (NPCManager::NPCs.find(leadNpc->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadNpc->groupMember[i]]->kind != EntityKind::MOB) {
|
||||
std::cout << "[WARN] unsommonW: leader can't find a group member!" << std::endl;
|
||||
continue;
|
||||
}
|
||||
@ -324,7 +324,7 @@ static void toggleAiCommand(std::string full, std::vector<std::string>& args, CN
|
||||
|
||||
// return all mobs to their spawn points
|
||||
for (auto& pair : NPCManager::NPCs) {
|
||||
if (pair.second->kind != EntityType::MOB)
|
||||
if (pair.second->kind != EntityKind::MOB)
|
||||
continue;
|
||||
|
||||
Mob* mob = (Mob*)pair.second;
|
||||
@ -609,7 +609,7 @@ static void summonGroupCommand(std::string full, std::vector<std::string>& args,
|
||||
}
|
||||
|
||||
BaseNPC *npc = NPCManager::summonNPC(x, y, z, plr->instanceID, type, wCommand);
|
||||
if (team == 2 && i > 0 && npc->kind == EntityType::MOB) {
|
||||
if (team == 2 && i > 0 && npc->kind == EntityKind::MOB) {
|
||||
leadNpc->groupMember[i-1] = npc->id;
|
||||
Mob* mob = (Mob*)NPCManager::NPCs[npc->id];
|
||||
mob->groupLeader = leadNpc->id;
|
||||
@ -624,7 +624,7 @@ static void summonGroupCommand(std::string full, std::vector<std::string>& args,
|
||||
if (PLAYERID(plr->instanceID) != 0) {
|
||||
npc = NPCManager::summonNPC(plr->x, plr->y, plr->z, plr->instanceID, type, wCommand, true);
|
||||
|
||||
if (team == 2 && i > 0 && npc->kind == EntityType::MOB) {
|
||||
if (team == 2 && i > 0 && npc->kind == EntityKind::MOB) {
|
||||
leadNpc->groupMember[i-1] = npc->id;
|
||||
Mob* mob = (Mob*)NPCManager::NPCs[npc->id];
|
||||
mob->groupLeader = leadNpc->id;
|
||||
@ -639,7 +639,7 @@ static void summonGroupCommand(std::string full, std::vector<std::string>& args,
|
||||
Chat::sendServerMessage(sock, "/summonGroup(W): placed mob with type: " + std::to_string(type) +
|
||||
", id: " + std::to_string(npc->id));
|
||||
|
||||
if (i == 0 && team == 2 && npc->kind == EntityType::MOB) {
|
||||
if (i == 0 && team == 2 && npc->kind == EntityKind::MOB) {
|
||||
type = type2;
|
||||
leadNpc = (Mob*)NPCManager::NPCs[npc->id];
|
||||
leadNpc->groupLeader = leadNpc->id;
|
||||
@ -694,7 +694,7 @@ static void lairUnlockCommand(std::string full, std::vector<std::string>& args,
|
||||
int lastDist = INT_MAX;
|
||||
for (Chunk *chnk : Chunking::getViewableChunks(plr->chunkPos)) {
|
||||
for (const EntityRef& ref : chnk->entities) {
|
||||
if (ref.type == EntityType::PLAYER)
|
||||
if (ref.kind == EntityKind::PLAYER)
|
||||
continue;
|
||||
|
||||
BaseNPC* npc = (BaseNPC*)ref.getEntity();
|
||||
|
40
src/Eggs.cpp
40
src/Eggs.cpp
@ -19,7 +19,8 @@ int Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) {
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
|
||||
int bitFlag = Groups::getGroupFlags(otherPlr);
|
||||
int CBFlag = Abilities::applyBuff(sock, skillId, 1, 3, bitFlag);
|
||||
// TODO ABILITIES
|
||||
int CBFlag = 0;// Abilities::applyBuff(sock, skillId, 1, 3, bitFlag);
|
||||
|
||||
size_t resplen;
|
||||
|
||||
@ -100,23 +101,24 @@ static void eggStep(CNServer* serv, time_t currTime) {
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||
|
||||
int groupFlags = Groups::getGroupFlags(otherPlr);
|
||||
for (auto& pwr : Abilities::Powers) {
|
||||
if (pwr.bitFlag == CBFlag) { // pick the power with the right flag and unbuff
|
||||
INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, resp);
|
||||
resp.eCSTB = pwr.timeBuffID;
|
||||
resp.eTBU = 2;
|
||||
resp.eTBT = 3; // for egg buffs
|
||||
plr->iConditionBitFlag &= ~CBFlag;
|
||||
resp.iConditionBitFlag = plr->iConditionBitFlag |= groupFlags | plr->iSelfConditionBitFlag;
|
||||
sock->sendPacket(resp, P_FE2CL_PC_BUFF_UPDATE);
|
||||
// TODO ABILITIES
|
||||
//for (auto& pwr : Abilities::Powers) {
|
||||
// if (pwr.bitFlag == CBFlag) { // pick the power with the right flag and unbuff
|
||||
// INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, resp);
|
||||
// resp.eCSTB = pwr.timeBuffID;
|
||||
// resp.eTBU = 2;
|
||||
// resp.eTBT = 3; // for egg buffs
|
||||
// plr->iConditionBitFlag &= ~CBFlag;
|
||||
// resp.iConditionBitFlag = plr->iConditionBitFlag |= groupFlags | plr->iSelfConditionBitFlag;
|
||||
// sock->sendPacket(resp, P_FE2CL_PC_BUFF_UPDATE);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, resp2); // send a buff timeout to other players
|
||||
resp2.eCT = 1;
|
||||
resp2.iID = plr->iID;
|
||||
resp2.iConditionBitFlag = plr->iConditionBitFlag;
|
||||
PlayerManager::sendToViewable(sock, resp2, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT);
|
||||
}
|
||||
}
|
||||
// INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, resp2); // send a buff timeout to other players
|
||||
// resp2.eCT = 1;
|
||||
// resp2.iID = plr->iID;
|
||||
// resp2.iConditionBitFlag = plr->iConditionBitFlag;
|
||||
// PlayerManager::sendToViewable(sock, resp2, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT);
|
||||
// }
|
||||
//}
|
||||
// remove buff from the map
|
||||
it = EggBuffs.erase(it);
|
||||
}
|
||||
@ -124,7 +126,7 @@ static void eggStep(CNServer* serv, time_t currTime) {
|
||||
|
||||
// check dead eggs and eggs in inactive chunks
|
||||
for (auto npc : NPCManager::NPCs) {
|
||||
if (npc.second->kind != EntityType::EGG)
|
||||
if (npc.second->kind != EntityKind::EGG)
|
||||
continue;
|
||||
|
||||
auto egg = (Egg*)npc.second;
|
||||
@ -163,7 +165,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) {
|
||||
return;
|
||||
}
|
||||
auto egg = (Egg*)eggRef.getEntity();
|
||||
if (egg->kind != EntityType::EGG) {
|
||||
if (egg->kind != EntityKind::EGG) {
|
||||
std::cout << "[WARN] Player tried to open something other than an?!" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ static_assert(std::is_standard_layout<EntityRef>::value);
|
||||
static_assert(std::is_trivially_copyable<EntityRef>::value);
|
||||
|
||||
EntityRef::EntityRef(CNSocket *s) {
|
||||
type = EntityType::PLAYER;
|
||||
kind = EntityKind::PLAYER;
|
||||
sock = s;
|
||||
}
|
||||
|
||||
@ -20,11 +20,11 @@ EntityRef::EntityRef(int32_t i) {
|
||||
id = i;
|
||||
|
||||
assert(NPCManager::NPCs.find(id) != NPCManager::NPCs.end());
|
||||
type = NPCManager::NPCs[id]->kind;
|
||||
kind = NPCManager::NPCs[id]->kind;
|
||||
}
|
||||
|
||||
bool EntityRef::isValid() const {
|
||||
if (type == EntityType::PLAYER)
|
||||
if (kind == EntityKind::PLAYER)
|
||||
return PlayerManager::players.find(sock) != PlayerManager::players.end();
|
||||
|
||||
return NPCManager::NPCs.find(id) != NPCManager::NPCs.end();
|
||||
@ -33,7 +33,7 @@ bool EntityRef::isValid() const {
|
||||
Entity *EntityRef::getEntity() const {
|
||||
assert(isValid());
|
||||
|
||||
if (type == EntityType::PLAYER)
|
||||
if (kind == EntityKind::PLAYER)
|
||||
return PlayerManager::getPlayer(sock);
|
||||
|
||||
return NPCManager::NPCs[id];
|
||||
|
@ -6,7 +6,7 @@
|
||||
#include <stdint.h>
|
||||
#include <set>
|
||||
|
||||
enum class EntityType : uint8_t {
|
||||
enum EntityKind {
|
||||
INVALID,
|
||||
PLAYER,
|
||||
SIMPLE_NPC,
|
||||
@ -27,7 +27,7 @@ enum class AIState {
|
||||
class Chunk;
|
||||
|
||||
struct Entity {
|
||||
EntityType kind = EntityType::INVALID;
|
||||
EntityKind kind = EntityKind::INVALID;
|
||||
int x = 0, y = 0, z = 0;
|
||||
uint64_t instanceID = 0;
|
||||
ChunkPos chunkPos = {};
|
||||
@ -44,7 +44,7 @@ struct Entity {
|
||||
};
|
||||
|
||||
struct EntityRef {
|
||||
EntityType type;
|
||||
EntityKind kind;
|
||||
union {
|
||||
CNSocket *sock;
|
||||
int32_t id;
|
||||
@ -57,10 +57,10 @@ struct EntityRef {
|
||||
Entity *getEntity() const;
|
||||
|
||||
bool operator==(const EntityRef& other) const {
|
||||
if (type != other.type)
|
||||
if (kind != other.kind)
|
||||
return false;
|
||||
|
||||
if (type == EntityType::PLAYER)
|
||||
if (kind == EntityKind::PLAYER)
|
||||
return sock == other.sock;
|
||||
|
||||
return id == other.id;
|
||||
@ -68,21 +68,20 @@ struct EntityRef {
|
||||
|
||||
// arbitrary ordering
|
||||
bool operator<(const EntityRef& other) const {
|
||||
if (type == other.type) {
|
||||
if (type == EntityType::PLAYER)
|
||||
if (kind == other.kind) {
|
||||
if (kind == EntityKind::PLAYER)
|
||||
return sock < other.sock;
|
||||
else
|
||||
return id < other.id;
|
||||
}
|
||||
|
||||
return type < other.type;
|
||||
return kind < other.kind;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Interfaces
|
||||
*/
|
||||
|
||||
class ICombatant {
|
||||
public:
|
||||
ICombatant() {}
|
||||
@ -93,7 +92,6 @@ public:
|
||||
virtual bool isAlive() = 0;
|
||||
virtual int getCurrentHP() = 0;
|
||||
virtual int32_t getID() = 0;
|
||||
|
||||
virtual void step(time_t currTime) = 0;
|
||||
};
|
||||
|
||||
@ -134,11 +132,17 @@ struct CombatNPC : public BaseNPC, public ICombatant {
|
||||
AIState state = AIState::INACTIVE;
|
||||
int playersInView = 0; // for optimizing away AI in empty chunks
|
||||
|
||||
std::map<AIState, void (*)(CombatNPC*, time_t)> stateHandlers;
|
||||
std::map<AIState, void (*)(CombatNPC*, EntityRef)> transitionHandlers;
|
||||
|
||||
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) {
|
||||
spawnX = x;
|
||||
spawnY = y;
|
||||
spawnZ = z;
|
||||
|
||||
stateHandlers[AIState::INACTIVE] = {};
|
||||
transitionHandlers[AIState::INACTIVE] = {};
|
||||
}
|
||||
|
||||
virtual bool isExtant() override { return hp > 0; }
|
||||
@ -148,19 +152,9 @@ struct CombatNPC : public BaseNPC, public ICombatant {
|
||||
virtual bool isAlive() override;
|
||||
virtual int getCurrentHP() override;
|
||||
virtual int32_t getID() override;
|
||||
|
||||
virtual void step(time_t currTime) override;
|
||||
virtual void roamingStep(time_t currTime) {} // no-ops by default
|
||||
virtual void combatStep(time_t currTime) {}
|
||||
virtual void retreatStep(time_t currTime) {}
|
||||
virtual void deadStep(time_t currTime) {}
|
||||
|
||||
virtual void transition(AIState newState, EntityRef src);
|
||||
virtual void onInactive() {} // no-ops by default
|
||||
virtual void onRoamStart() {}
|
||||
virtual void onCombatStart(EntityRef src) {}
|
||||
virtual void onRetreat() {}
|
||||
virtual void onDeath(EntityRef src) {}
|
||||
};
|
||||
|
||||
// Mob is in MobAI.hpp, Player is in Player.hpp
|
||||
@ -173,7 +167,7 @@ struct Egg : public BaseNPC {
|
||||
Egg(uint64_t iID, int t, int32_t id, bool summon)
|
||||
: BaseNPC(0, iID, t, id) {
|
||||
summoned = summon;
|
||||
kind = EntityType::EGG;
|
||||
kind = EntityKind::EGG;
|
||||
}
|
||||
|
||||
virtual bool isExtant() override { return !dead; }
|
||||
@ -185,7 +179,7 @@ struct Egg : public BaseNPC {
|
||||
struct Bus : public BaseNPC {
|
||||
Bus(int angle, uint64_t iID, int t, int id) :
|
||||
BaseNPC(angle, iID, t, id) {
|
||||
kind = EntityType::BUS;
|
||||
kind = EntityKind::BUS;
|
||||
loopingPath = true;
|
||||
}
|
||||
|
||||
|
@ -135,10 +135,11 @@ static void joinGroup(CNSocket* sock, CNPacketData* data) {
|
||||
// client doesnt read nano data here
|
||||
|
||||
if (varPlr != plr) { // apply the new member's buffs to the group and the group's buffs to the new member
|
||||
if (Abilities::SkillTable[varPlr->Nanos[varPlr->activeNano].iSkillID].targetType == 3)
|
||||
// TODO ABILITIES
|
||||
/*if (Abilities::SkillTable[varPlr->Nanos[varPlr->activeNano].iSkillID].targetType == 3)
|
||||
Abilities::applyBuff(sock, varPlr->Nanos[varPlr->activeNano].iSkillID, 1, 1, bitFlag);
|
||||
if (Abilities::SkillTable[plr->Nanos[plr->activeNano].iSkillID].targetType == 3)
|
||||
Abilities::applyBuff(sockTo, plr->Nanos[plr->activeNano].iSkillID, 1, 1, bitFlag);
|
||||
Abilities::applyBuff(sockTo, plr->Nanos[plr->activeNano].iSkillID, 1, 1, bitFlag);*/
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,7 +222,8 @@ static void groupUnbuff(Player* plr) {
|
||||
Player* otherPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]);
|
||||
CNSocket* sock = PlayerManager::getSockFromID(plr->groupIDs[n]);
|
||||
|
||||
Abilities::applyBuff(sock, otherPlr->Nanos[otherPlr->activeNano].iSkillID, 2, 1, 0);
|
||||
// TODO ABILITIES
|
||||
//Abilities::applyBuff(sock, otherPlr->Nanos[otherPlr->activeNano].iSkillID, 2, 1, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -295,10 +297,11 @@ void Groups::groupKickPlayer(Player* plr) {
|
||||
moveDown = 1;
|
||||
otherPlr->groupIDs[i] = 0;
|
||||
} else { // remove the leaving member's buffs from the group and remove the group buffs from the leaving member.
|
||||
if (Abilities::SkillTable[varPlr->Nanos[varPlr->activeNano].iSkillID].targetType == 3)
|
||||
// TODO ABILITIES
|
||||
/*if (Abilities::SkillTable[varPlr->Nanos[varPlr->activeNano].iSkillID].targetType == 3)
|
||||
Abilities::applyBuff(sock, varPlr->Nanos[varPlr->activeNano].iSkillID, 2, 1, 0);
|
||||
if (Abilities::SkillTable[plr->Nanos[varPlr->activeNano].iSkillID].targetType == 3)
|
||||
Abilities::applyBuff(sockTo, plr->Nanos[plr->activeNano].iSkillID, 2, 1, bitFlag);
|
||||
Abilities::applyBuff(sockTo, plr->Nanos[plr->activeNano].iSkillID, 2, 1, bitFlag);*/
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -371,7 +371,7 @@ static void taskStart(CNSocket* sock, CNPacketData* data) {
|
||||
for (ChunkPos& chunkPos : Chunking::getChunksInMap(plr->instanceID)) { // check all NPCs in the instance
|
||||
Chunk* chunk = Chunking::chunks[chunkPos];
|
||||
for (EntityRef ref : chunk->entities) {
|
||||
if (ref.type != EntityType::PLAYER) {
|
||||
if (ref.kind != EntityKind::PLAYER) {
|
||||
BaseNPC* npc = (BaseNPC*)ref.getEntity();
|
||||
NPCPath* path = Transport::findApplicablePath(npc->id, npc->type, missionData->iTaskNum);
|
||||
if (path != nullptr) {
|
||||
|
450
src/MobAI.cpp
450
src/MobAI.cpp
@ -16,6 +16,52 @@ using namespace MobAI;
|
||||
|
||||
bool MobAI::simulateMobs = settings::SIMULATEMOBS;
|
||||
|
||||
void Mob::step(time_t currTime) {
|
||||
if (playersInView < 0)
|
||||
std::cout << "[WARN] Weird playerview value " << playersInView << std::endl;
|
||||
|
||||
// skip movement and combat if disabled or not in view
|
||||
if ((!MobAI::simulateMobs || playersInView == 0) && state != AIState::DEAD
|
||||
&& state != AIState::RETREAT)
|
||||
return;
|
||||
|
||||
// call superclass step
|
||||
CombatNPC::step(currTime);
|
||||
}
|
||||
|
||||
int Mob::takeDamage(EntityRef src, int amt) {
|
||||
|
||||
// cannot kill mobs multiple times; cannot harm retreating mobs
|
||||
if (state != AIState::ROAMING && state != AIState::COMBAT) {
|
||||
return 0; // no damage
|
||||
}
|
||||
|
||||
if (skillStyle >= 0)
|
||||
return 0; // don't hurt a mob casting corruption
|
||||
|
||||
if (state == AIState::ROAMING) {
|
||||
assert(target == nullptr && src.kind == EntityKind::PLAYER); // TODO: players only for now
|
||||
transition(AIState::COMBAT, src);
|
||||
|
||||
if (groupLeader != 0)
|
||||
MobAI::followToCombat(this);
|
||||
}
|
||||
|
||||
// wake up sleeping monster
|
||||
if (cbf & CSB_BIT_MEZ) {
|
||||
cbf &= ~CSB_BIT_MEZ;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
|
||||
pkt1.eCT = 2;
|
||||
pkt1.iID = id;
|
||||
pkt1.iConditionBitFlag = cbf;
|
||||
NPCManager::sendToViewable(this, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
||||
}
|
||||
|
||||
// call superclass takeDamage
|
||||
return CombatNPC::takeDamage(src, amt);
|
||||
}
|
||||
|
||||
/*
|
||||
* Dynamic lerp; distinct from Transport::lerp(). This one doesn't care about height and
|
||||
* only returns the first step, since the rest will need to be recalculated anyway if chasing player.
|
||||
@ -58,13 +104,13 @@ void MobAI::clearDebuff(Mob *mob) {
|
||||
}
|
||||
|
||||
void MobAI::followToCombat(Mob *mob) {
|
||||
if (NPCManager::NPCs.find(mob->groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[mob->groupLeader]->kind == EntityType::MOB) {
|
||||
if (NPCManager::NPCs.find(mob->groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[mob->groupLeader]->kind == EntityKind::MOB) {
|
||||
Mob* leadMob = (Mob*)NPCManager::NPCs[mob->groupLeader];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (leadMob->groupMember[i] == 0)
|
||||
break;
|
||||
|
||||
if (NPCManager::NPCs.find(leadMob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadMob->groupMember[i]]->kind != EntityType::MOB) {
|
||||
if (NPCManager::NPCs.find(leadMob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadMob->groupMember[i]]->kind != EntityKind::MOB) {
|
||||
std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl;
|
||||
continue;
|
||||
}
|
||||
@ -84,7 +130,7 @@ void MobAI::followToCombat(Mob *mob) {
|
||||
}
|
||||
|
||||
void MobAI::groupRetreat(Mob *mob) {
|
||||
if (NPCManager::NPCs.find(mob->groupLeader) == NPCManager::NPCs.end() || NPCManager::NPCs[mob->groupLeader]->kind != EntityType::MOB)
|
||||
if (NPCManager::NPCs.find(mob->groupLeader) == NPCManager::NPCs.end() || NPCManager::NPCs[mob->groupLeader]->kind != EntityKind::MOB)
|
||||
return;
|
||||
|
||||
Mob* leadMob = (Mob*)NPCManager::NPCs[mob->groupLeader];
|
||||
@ -92,7 +138,7 @@ void MobAI::groupRetreat(Mob *mob) {
|
||||
if (leadMob->groupMember[i] == 0)
|
||||
break;
|
||||
|
||||
if (NPCManager::NPCs.find(leadMob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadMob->groupMember[i]]->kind != EntityType::MOB) {
|
||||
if (NPCManager::NPCs.find(leadMob->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[leadMob->groupMember[i]]->kind != EntityKind::MOB) {
|
||||
std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl;
|
||||
continue;
|
||||
}
|
||||
@ -127,7 +173,7 @@ bool MobAI::aggroCheck(Mob *mob, time_t currTime) {
|
||||
Chunk* chunk = *it;
|
||||
for (const EntityRef& ref : chunk->entities) {
|
||||
// TODO: support targetting other CombatNPCs
|
||||
if (ref.type != EntityType::PLAYER)
|
||||
if (ref.kind != EntityKind::PLAYER)
|
||||
continue;
|
||||
|
||||
CNSocket *s = ref.sock;
|
||||
@ -247,10 +293,11 @@ static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, i
|
||||
if (plr->Nanos[plr->activeNano].iStamina > 150)
|
||||
respdata[i].iNanoStamina = plr->Nanos[plr->activeNano].iStamina = 150;
|
||||
// fire damage power disguised as a corruption attack back at the enemy
|
||||
std::vector<int> targetData2 = {1, mob->id, 0, 0, 0};
|
||||
// TODO ABILITIES
|
||||
/*std::vector<int> targetData2 = {1, mob->id, 0, 0, 0};
|
||||
for (auto& pwr : Abilities::Powers)
|
||||
if (pwr.skillType == EST_DAMAGE)
|
||||
pwr.handle(sock, targetData2, plr->activeNano, skillID, 0, 200);
|
||||
pwr.handle(sock, targetData2, plr->activeNano, skillID, 0, 200);*/
|
||||
} else {
|
||||
respdata[i].iHitFlag = HF_BIT_STYLE_LOSE;
|
||||
respdata[i].iDamage = Abilities::SkillTable[skillID].powerIntensity[0] * PC_MAXHEALTH((int)mob->data["m_iNpcLevel"]) / 1500;
|
||||
@ -304,7 +351,7 @@ static void useAbilities(Mob *mob, time_t currTime) {
|
||||
Chunk* chunk = *it;
|
||||
for (const EntityRef& ref : chunk->entities) {
|
||||
// TODO: see aggroCheck()
|
||||
if (ref.type != EntityType::PLAYER)
|
||||
if (ref.kind != EntityKind::PLAYER)
|
||||
continue;
|
||||
|
||||
CNSocket *s= ref.sock;
|
||||
@ -323,9 +370,10 @@ static void useAbilities(Mob *mob, time_t currTime) {
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& pwr : Abilities::Powers)
|
||||
// TODO ABILITIES
|
||||
/*for (auto& pwr : Abilities::Powers)
|
||||
if (pwr.skillType == Abilities::SkillTable[skillID].skillType)
|
||||
pwr.handle(mob->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]);
|
||||
pwr.handle(mob->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]);*/
|
||||
mob->skillStyle = -3; // eruption cooldown
|
||||
mob->nextAttack = currTime + 1000;
|
||||
return;
|
||||
@ -343,13 +391,14 @@ static void useAbilities(Mob *mob, time_t currTime) {
|
||||
|
||||
if (random < prob1) { // active skill hit
|
||||
int skillID = (int)mob->data["m_iActiveSkill1"];
|
||||
std::vector<int> targetData = {1, plr->iID, 0, 0, 0};
|
||||
for (auto& pwr : Abilities::Powers)
|
||||
if (pwr.skillType == Abilities::SkillTable[skillID].skillType) {
|
||||
if (pwr.bitFlag != 0 && (plr->iConditionBitFlag & pwr.bitFlag))
|
||||
return; // prevent debuffing a player twice
|
||||
pwr.handle(mob->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]);
|
||||
}
|
||||
// TODO ABILITIES
|
||||
//std::vector<int> targetData = {1, plr->iID, 0, 0, 0};
|
||||
//for (auto& pwr : Abilities::Powers)
|
||||
// if (pwr.skillType == Abilities::SkillTable[skillID].skillType) {
|
||||
// if (pwr.bitFlag != 0 && (plr->iConditionBitFlag & pwr.bitFlag))
|
||||
// return; // prevent debuffing a player twice
|
||||
// pwr.handle(mob->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]);
|
||||
// }
|
||||
mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100;
|
||||
return;
|
||||
}
|
||||
@ -415,49 +464,58 @@ static void drainMobHP(Mob *mob, int amount) {
|
||||
mob->transition(AIState::DEAD, mob->target);
|
||||
}
|
||||
|
||||
void Mob::deadStep(time_t currTime) {
|
||||
void MobAI::incNextMovement(Mob* mob, time_t currTime) {
|
||||
if (currTime == 0)
|
||||
currTime = getTime();
|
||||
|
||||
int delay = (int)mob->data["m_iDelayTime"] * 1000;
|
||||
mob->nextMovement = currTime + delay / 2 + Rand::rand(delay / 2);
|
||||
}
|
||||
|
||||
void MobAI::deadStep(CombatNPC* npc, time_t currTime) {
|
||||
Mob* self = (Mob*)npc;
|
||||
|
||||
// despawn the mob after a short delay
|
||||
if (killedTime != 0 && !despawned && currTime - killedTime > 2000) {
|
||||
despawned = true;
|
||||
if (self->killedTime != 0 && !self->despawned && currTime - self->killedTime > 2000) {
|
||||
self->despawned = true;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_NPC_EXIT, pkt);
|
||||
|
||||
pkt.iNPC_ID = id;
|
||||
pkt.iNPC_ID = self->id;
|
||||
|
||||
NPCManager::sendToViewable(this, &pkt, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT));
|
||||
NPCManager::sendToViewable(self, &pkt, P_FE2CL_NPC_EXIT, sizeof(sP_FE2CL_NPC_EXIT));
|
||||
|
||||
// if it was summoned, mark it for removal
|
||||
if (summoned) {
|
||||
if (self->summoned) {
|
||||
std::cout << "[INFO] Queueing killed summoned mob for removal" << std::endl;
|
||||
NPCManager::queueNPCRemoval(id);
|
||||
NPCManager::queueNPCRemoval(self->id);
|
||||
return;
|
||||
}
|
||||
|
||||
// pre-set spawn coordinates if not marked for removal
|
||||
x = spawnX;
|
||||
y = spawnY;
|
||||
z = spawnZ;
|
||||
self->x = self->spawnX;
|
||||
self->y = self->spawnY;
|
||||
self->z = self->spawnZ;
|
||||
}
|
||||
|
||||
// to guide their groupmates, group leaders still need to move despite being dead
|
||||
if (groupLeader == id)
|
||||
roamingStep(currTime);
|
||||
if (self->groupLeader == self->id)
|
||||
roamingStep(self, currTime);
|
||||
|
||||
if (killedTime != 0 && currTime - killedTime < regenTime * 100)
|
||||
if (self->killedTime != 0 && currTime - self->killedTime < self->regenTime * 100)
|
||||
return;
|
||||
|
||||
std::cout << "respawning mob " << id << " with HP = " << maxHealth << std::endl;
|
||||
std::cout << "respawning mob " << self->id << " with HP = " << self->maxHealth << std::endl;
|
||||
|
||||
hp = maxHealth;
|
||||
state = AIState::ROAMING;
|
||||
self->transition(AIState::ROAMING, self->id);
|
||||
|
||||
// if mob is a group leader/follower, spawn where the group is.
|
||||
if (groupLeader != 0) {
|
||||
if (NPCManager::NPCs.find(groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[groupLeader]->kind == EntityType::MOB) {
|
||||
Mob* leaderMob = (Mob*)NPCManager::NPCs[groupLeader];
|
||||
x = leaderMob->x + offsetX;
|
||||
y = leaderMob->y + offsetY;
|
||||
z = leaderMob->z;
|
||||
if (self->groupLeader != 0) {
|
||||
if (NPCManager::NPCs.find(self->groupLeader) != NPCManager::NPCs.end() && NPCManager::NPCs[self->groupLeader]->kind == EntityKind::MOB) {
|
||||
Mob* leaderMob = (Mob*)NPCManager::NPCs[self->groupLeader];
|
||||
self->x = leaderMob->x + self->offsetX;
|
||||
self->y = leaderMob->y + self->offsetY;
|
||||
self->z = leaderMob->z;
|
||||
} else {
|
||||
std::cout << "[WARN] deadStep: mob cannot find it's leader!" << std::endl;
|
||||
}
|
||||
@ -465,162 +523,157 @@ void Mob::deadStep(time_t currTime) {
|
||||
|
||||
INITSTRUCT(sP_FE2CL_NPC_NEW, pkt);
|
||||
|
||||
pkt.NPCAppearanceData = getAppearanceData();
|
||||
pkt.NPCAppearanceData = self->getAppearanceData();
|
||||
|
||||
// notify all nearby players
|
||||
NPCManager::sendToViewable(this, &pkt, P_FE2CL_NPC_NEW, sizeof(sP_FE2CL_NPC_NEW));
|
||||
NPCManager::sendToViewable(self, &pkt, P_FE2CL_NPC_NEW, sizeof(sP_FE2CL_NPC_NEW));
|
||||
}
|
||||
|
||||
void Mob::combatStep(time_t currTime) {
|
||||
assert(target != nullptr);
|
||||
void MobAI::combatStep(CombatNPC* npc, time_t currTime) {
|
||||
Mob* self = (Mob*)npc;
|
||||
assert(self->target != nullptr);
|
||||
|
||||
// lose aggro if the player lost connection
|
||||
if (PlayerManager::players.find(target) == PlayerManager::players.end()) {
|
||||
if (!MobAI::aggroCheck(this, getTime()))
|
||||
transition(AIState::RETREAT, target);
|
||||
if (PlayerManager::players.find(self->target) == PlayerManager::players.end()) {
|
||||
if (!MobAI::aggroCheck(self, getTime()))
|
||||
self->transition(AIState::RETREAT, self->target);
|
||||
return;
|
||||
}
|
||||
|
||||
Player *plr = PlayerManager::getPlayer(target);
|
||||
Player *plr = PlayerManager::getPlayer(self->target);
|
||||
|
||||
// lose aggro if the player became invulnerable or died
|
||||
if (plr->HP <= 0
|
||||
|| (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVULNERABLE)) {
|
||||
if (!MobAI::aggroCheck(this, getTime()))
|
||||
transition(AIState::RETREAT, target);
|
||||
if (!MobAI::aggroCheck(self, getTime()))
|
||||
self->transition(AIState::RETREAT, self->target);
|
||||
return;
|
||||
}
|
||||
|
||||
// drain
|
||||
if (skillStyle < 0 && (lastDrainTime == 0 || currTime - lastDrainTime >= 1000)
|
||||
&& cbf & CSB_BIT_BOUNDINGBALL) {
|
||||
drainMobHP(this, maxHealth / 20); // lose 5% every second
|
||||
lastDrainTime = currTime;
|
||||
if (self->skillStyle < 0 && (self->lastDrainTime == 0 || currTime - self->lastDrainTime >= 1000)
|
||||
&& self->cbf & CSB_BIT_BOUNDINGBALL) {
|
||||
drainMobHP(self, self->maxHealth / 20); // lose 5% every second
|
||||
self->lastDrainTime = currTime;
|
||||
}
|
||||
|
||||
// if drain killed the mob, return early
|
||||
if (hp <= 0)
|
||||
if (self->hp <= 0)
|
||||
return;
|
||||
|
||||
// unbuffing
|
||||
std::unordered_map<int32_t, time_t>::iterator it = unbuffTimes.begin();
|
||||
while (it != unbuffTimes.end()) {
|
||||
std::unordered_map<int32_t, time_t>::iterator it = self->unbuffTimes.begin();
|
||||
while (it != self->unbuffTimes.end()) {
|
||||
|
||||
if (currTime >= it->second) {
|
||||
cbf &= ~it->first;
|
||||
self->cbf &= ~it->first;
|
||||
|
||||
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
|
||||
pkt1.eCT = 2;
|
||||
pkt1.iID = id;
|
||||
pkt1.iConditionBitFlag = cbf;
|
||||
NPCManager::sendToViewable(this, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
|
||||
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 = unbuffTimes.erase(it);
|
||||
it = self->unbuffTimes.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
|
||||
// skip attack if stunned or asleep
|
||||
if (cbf & (CSB_BIT_STUN|CSB_BIT_MEZ)) {
|
||||
skillStyle = -1; // in this case we also reset the any outlying abilities the mob might be winding up.
|
||||
if (self->cbf & (CSB_BIT_STUN|CSB_BIT_MEZ)) {
|
||||
self->skillStyle = -1; // in this case we also reset the any outlying abilities the mob might be winding up.
|
||||
return;
|
||||
}
|
||||
|
||||
int distance = hypot(plr->x - x, plr->y - y);
|
||||
int mobRange = (int)data["m_iAtkRange"] + (int)data["m_iRadius"];
|
||||
int distance = hypot(plr->x - self->x, plr->y - self->y);
|
||||
int mobRange = (int)self->data["m_iAtkRange"] + (int)self->data["m_iRadius"];
|
||||
|
||||
if (currTime >= nextAttack) {
|
||||
if (skillStyle != -1 || distance <= mobRange || Rand::rand(20) == 0) // while not in attack range, 1 / 20 chance.
|
||||
useAbilities(this, currTime);
|
||||
if (target == nullptr)
|
||||
if (currTime >= self->nextAttack) {
|
||||
if (self->skillStyle != -1 || distance <= mobRange || Rand::rand(20) == 0) // while not in attack range, 1 / 20 chance.
|
||||
useAbilities(self, currTime);
|
||||
if (self->target == nullptr)
|
||||
return;
|
||||
}
|
||||
|
||||
int distanceToTravel = INT_MAX;
|
||||
// movement logic: move when out of range but don't move while casting a skill
|
||||
if (distance > mobRange && skillStyle == -1) {
|
||||
if (nextMovement != 0 && currTime < nextMovement)
|
||||
if (distance > mobRange && self->skillStyle == -1) {
|
||||
if (self->nextMovement != 0 && currTime < self->nextMovement)
|
||||
return;
|
||||
nextMovement = currTime + 400;
|
||||
if (currTime >= nextAttack)
|
||||
nextAttack = 0;
|
||||
self->nextMovement = currTime + 400;
|
||||
if (currTime >= self->nextAttack)
|
||||
self->nextAttack = 0;
|
||||
|
||||
// halve movement speed if snared
|
||||
if (cbf & CSB_BIT_DN_MOVE_SPEED)
|
||||
speed /= 2;
|
||||
if (self->cbf & CSB_BIT_DN_MOVE_SPEED)
|
||||
self->speed /= 2;
|
||||
|
||||
int targetX = plr->x;
|
||||
int targetY = plr->y;
|
||||
if (groupLeader != 0) {
|
||||
targetX += offsetX*distance/(idleRange + 1);
|
||||
targetY += offsetY*distance/(idleRange + 1);
|
||||
if (self->groupLeader != 0) {
|
||||
targetX += self->offsetX*distance/(self->idleRange + 1);
|
||||
targetY += self->offsetY*distance/(self->idleRange + 1);
|
||||
}
|
||||
|
||||
distanceToTravel = std::min(distance-mobRange+1, speed*2/5);
|
||||
auto targ = lerp(x, y, targetX, targetY, distanceToTravel);
|
||||
if (distanceToTravel < speed*2/5 && currTime >= nextAttack)
|
||||
nextAttack = 0;
|
||||
distanceToTravel = std::min(distance-mobRange+1, self->speed*2/5);
|
||||
auto targ = lerp(self->x, self->y, targetX, targetY, distanceToTravel);
|
||||
if (distanceToTravel < self->speed*2/5 && currTime >= self->nextAttack)
|
||||
self->nextAttack = 0;
|
||||
|
||||
NPCManager::updateNPCPosition(id, targ.first, targ.second, z, instanceID, angle);
|
||||
NPCManager::updateNPCPosition(self->id, targ.first, targ.second, self->z, self->instanceID, self->angle);
|
||||
|
||||
INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt);
|
||||
|
||||
pkt.iNPC_ID = id;
|
||||
pkt.iSpeed = speed;
|
||||
pkt.iToX = x = targ.first;
|
||||
pkt.iToY = y = targ.second;
|
||||
pkt.iNPC_ID = self->id;
|
||||
pkt.iSpeed = self->speed;
|
||||
pkt.iToX = self->x = targ.first;
|
||||
pkt.iToY = self->y = targ.second;
|
||||
pkt.iToZ = plr->z;
|
||||
pkt.iMoveStyle = 1;
|
||||
|
||||
// notify all nearby players
|
||||
NPCManager::sendToViewable(this, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
|
||||
NPCManager::sendToViewable(self, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
|
||||
}
|
||||
|
||||
/* attack logic
|
||||
* 2/5 represents 400 ms which is the time interval mobs use per movement logic step
|
||||
* if the mob is one move interval away, we should just start attacking anyways.
|
||||
*/
|
||||
if (distance <= mobRange || distanceToTravel < speed*2/5) {
|
||||
if (nextAttack == 0 || currTime >= nextAttack) {
|
||||
nextAttack = currTime + (int)data["m_iDelayTime"] * 100;
|
||||
Combat::npcAttackPc(this, currTime);
|
||||
if (distance <= mobRange || distanceToTravel < self->speed*2/5) {
|
||||
if (self->nextAttack == 0 || currTime >= self->nextAttack) {
|
||||
self->nextAttack = currTime + (int)self->data["m_iDelayTime"] * 100;
|
||||
Combat::npcAttackPc(self, currTime);
|
||||
}
|
||||
}
|
||||
|
||||
// retreat if the player leaves combat range
|
||||
int xyDistance = hypot(plr->x - roamX, plr->y - roamY);
|
||||
distance = hypot(xyDistance, plr->z - roamZ);
|
||||
if (distance >= data["m_iCombatRange"]) {
|
||||
transition(AIState::RETREAT, target);
|
||||
int xyDistance = hypot(plr->x - self->roamX, plr->y - self->roamY);
|
||||
distance = hypot(xyDistance, plr->z - self->roamZ);
|
||||
if (distance >= self->data["m_iCombatRange"]) {
|
||||
self->transition(AIState::RETREAT, self->target);
|
||||
}
|
||||
}
|
||||
|
||||
void MobAI::incNextMovement(Mob *mob, time_t currTime) {
|
||||
if (currTime == 0)
|
||||
currTime = getTime();
|
||||
void MobAI::roamingStep(CombatNPC* npc, time_t currTime) {
|
||||
Mob* self = (Mob*)npc;
|
||||
|
||||
int delay = (int)mob->data["m_iDelayTime"] * 1000;
|
||||
mob->nextMovement = currTime + delay/2 + Rand::rand(delay/2);
|
||||
}
|
||||
|
||||
void Mob::roamingStep(time_t currTime) {
|
||||
/*
|
||||
* We reuse nextAttack to avoid scanning for players all the time, but to still
|
||||
* do so more often than if we waited for nextMovement (which is way too slow).
|
||||
* In the case of group leaders, this step will be called by dead mobs, so disable attack.
|
||||
*/
|
||||
if (state != AIState::DEAD && (nextAttack == 0 || currTime >= nextAttack)) {
|
||||
nextAttack = currTime + 500;
|
||||
if (aggroCheck(this, currTime))
|
||||
if (self->state != AIState::DEAD && (self->nextAttack == 0 || currTime >= self->nextAttack)) {
|
||||
self->nextAttack = currTime + 500;
|
||||
if (aggroCheck(self, currTime))
|
||||
return;
|
||||
}
|
||||
|
||||
// no random roaming if the mob already has a set path
|
||||
if (staticPath)
|
||||
if (self->staticPath)
|
||||
return;
|
||||
|
||||
if (groupLeader != 0 && groupLeader != id) // don't roam by yourself without group leader
|
||||
if (self->groupLeader != 0 && self->groupLeader != self->id) // don't roam by yourself without group leader
|
||||
return;
|
||||
|
||||
/*
|
||||
@ -628,155 +681,162 @@ void Mob::roamingStep(time_t currTime) {
|
||||
* Transport::stepNPCPathing() (which ticks at a higher frequency than nextMovement),
|
||||
* so we don't have to check if there's already entries in the queue since we know there won't be.
|
||||
*/
|
||||
if (nextMovement != 0 && currTime < nextMovement)
|
||||
if (self->nextMovement != 0 && currTime < self->nextMovement)
|
||||
return;
|
||||
incNextMovement(this, currTime);
|
||||
incNextMovement(self, currTime);
|
||||
|
||||
int xStart = spawnX - idleRange/2;
|
||||
int yStart = spawnY - idleRange/2;
|
||||
int xStart = self->spawnX - self->idleRange/2;
|
||||
int yStart = self->spawnY - self->idleRange/2;
|
||||
|
||||
// some mobs don't move (and we mustn't divide/modulus by zero)
|
||||
if (idleRange == 0 || speed == 0)
|
||||
if (self->idleRange == 0 || self->speed == 0)
|
||||
return;
|
||||
|
||||
int farX, farY, distance;
|
||||
int minDistance = idleRange / 2;
|
||||
int minDistance = self->idleRange / 2;
|
||||
|
||||
// pick a random destination
|
||||
farX = xStart + Rand::rand(idleRange);
|
||||
farY = yStart + Rand::rand(idleRange);
|
||||
farX = xStart + Rand::rand(self->idleRange);
|
||||
farY = yStart + Rand::rand(self->idleRange);
|
||||
|
||||
distance = std::abs(std::max(farX - x, farY - y));
|
||||
distance = std::abs(std::max(farX - self->x, farY - self->y));
|
||||
if (distance == 0)
|
||||
distance += 1; // hack to avoid FPE
|
||||
|
||||
// if it's too short a walk, go further in that direction
|
||||
farX = x + (farX - x) * minDistance / distance;
|
||||
farY = y + (farY - y) * minDistance / distance;
|
||||
farX = self->x + (farX - self->x) * minDistance / distance;
|
||||
farY = self->y + (farY - self->y) * minDistance / distance;
|
||||
|
||||
// but don't got out of bounds
|
||||
farX = std::clamp(farX, xStart, xStart + idleRange);
|
||||
farY = std::clamp(farY, yStart, yStart + idleRange);
|
||||
farX = std::clamp(farX, xStart, xStart + self->idleRange);
|
||||
farY = std::clamp(farY, yStart, yStart + self->idleRange);
|
||||
|
||||
// halve movement speed if snared
|
||||
if (cbf & CSB_BIT_DN_MOVE_SPEED)
|
||||
speed /= 2;
|
||||
if (self->cbf & CSB_BIT_DN_MOVE_SPEED)
|
||||
self->speed /= 2;
|
||||
|
||||
std::queue<Vec3> queue;
|
||||
Vec3 from = { x, y, z };
|
||||
Vec3 to = { farX, farY, z };
|
||||
Vec3 from = { self->x, self->y, self->z };
|
||||
Vec3 to = { farX, farY, self->z };
|
||||
|
||||
// add a route to the queue; to be processed in Transport::stepNPCPathing()
|
||||
Transport::lerp(&queue, from, to, speed);
|
||||
Transport::NPCQueues[id] = queue;
|
||||
Transport::lerp(&queue, from, to, self->speed);
|
||||
Transport::NPCQueues[self->id] = queue;
|
||||
|
||||
if (groupLeader != 0 && groupLeader == id) {
|
||||
if (self->groupLeader != 0 && self->groupLeader == self->id) {
|
||||
// make followers follow this npc.
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (groupMember[i] == 0)
|
||||
if (self->groupMember[i] == 0)
|
||||
break;
|
||||
|
||||
if (NPCManager::NPCs.find(groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[groupMember[i]]->kind != EntityType::MOB) {
|
||||
if (NPCManager::NPCs.find(self->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[self->groupMember[i]]->kind != EntityKind::MOB) {
|
||||
std::cout << "[WARN] roamingStep: leader can't find a group member!" << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::queue<Vec3> queue2;
|
||||
Mob* followerMob = (Mob*)NPCManager::NPCs[groupMember[i]];
|
||||
Mob* followerMob = (Mob*)NPCManager::NPCs[self->groupMember[i]];
|
||||
from = { followerMob->x, followerMob->y, followerMob->z };
|
||||
to = { farX + followerMob->offsetX, farY + followerMob->offsetY, followerMob->z };
|
||||
Transport::lerp(&queue2, from, to, speed);
|
||||
Transport::lerp(&queue2, from, to, self->speed);
|
||||
Transport::NPCQueues[followerMob->id] = queue2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Mob::retreatStep(time_t currTime) {
|
||||
if (nextMovement != 0 && currTime < nextMovement)
|
||||
void MobAI::retreatStep(CombatNPC* npc, time_t currTime) {
|
||||
Mob* self = (Mob*)npc;
|
||||
|
||||
if (self->nextMovement != 0 && currTime < self->nextMovement)
|
||||
return;
|
||||
|
||||
nextMovement = currTime + 400;
|
||||
self->nextMovement = currTime + 400;
|
||||
|
||||
// distance between spawn point and current location
|
||||
int distance = hypot(x - roamX, y - roamY);
|
||||
int distance = hypot(self->x - self->roamX, self->y - self->roamY);
|
||||
|
||||
//if (distance > mob->data["m_iIdleRange"]) {
|
||||
if (distance > 10) {
|
||||
INITSTRUCT(sP_FE2CL_NPC_MOVE, pkt);
|
||||
|
||||
auto targ = lerp(x, y, roamX, roamY, (int)speed*4/5);
|
||||
auto targ = lerp(self->x, self->y, self->roamX, self->roamY, (int)self->speed*4/5);
|
||||
|
||||
pkt.iNPC_ID = id;
|
||||
pkt.iSpeed = (int)speed * 2;
|
||||
pkt.iToX = x = targ.first;
|
||||
pkt.iToY = y = targ.second;
|
||||
pkt.iToZ = z = spawnZ;
|
||||
pkt.iNPC_ID = self->id;
|
||||
pkt.iSpeed = (int)self->speed * 2;
|
||||
pkt.iToX = self->x = targ.first;
|
||||
pkt.iToY = self->y = targ.second;
|
||||
pkt.iToZ = self->z = self->spawnZ;
|
||||
pkt.iMoveStyle = 1;
|
||||
|
||||
// notify all nearby players
|
||||
NPCManager::sendToViewable(this, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
|
||||
NPCManager::sendToViewable(self, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
|
||||
}
|
||||
|
||||
// if we got there
|
||||
//if (distance <= mob->data["m_iIdleRange"]) {
|
||||
if (distance <= 10) { // retreat back to the spawn point
|
||||
state = AIState::ROAMING;
|
||||
hp = maxHealth;
|
||||
killedTime = 0;
|
||||
nextAttack = 0;
|
||||
cbf = 0;
|
||||
|
||||
// cast a return home heal spell, this is the right way(tm)
|
||||
std::vector<int> targetData = {1, 0, 0, 0, 0};
|
||||
for (auto& pwr : Abilities::Powers)
|
||||
if (pwr.skillType == Abilities::SkillTable[110].skillType)
|
||||
pwr.handle(id, targetData, 110, Abilities::SkillTable[110].durationTime[0], Abilities::SkillTable[110].powerIntensity[0]);
|
||||
// clear outlying debuffs
|
||||
clearDebuff(this);
|
||||
self->transition(AIState::ROAMING, self->id);
|
||||
}
|
||||
}
|
||||
|
||||
void Mob::onInactive() {
|
||||
// no-op
|
||||
void MobAI::onRoamStart(CombatNPC* npc, EntityRef src) {
|
||||
Mob* self = (Mob*)npc;
|
||||
|
||||
self->hp = self->maxHealth;
|
||||
self->killedTime = 0;
|
||||
self->nextAttack = 0;
|
||||
self->cbf = 0;
|
||||
|
||||
// cast a return home heal spell, this is the right way(tm)
|
||||
// TODO ABILITIES
|
||||
/*std::vector<int> targetData = { 1, 0, 0, 0, 0 };
|
||||
for (auto& pwr : Abilities::Powers)
|
||||
if (pwr.skillType == Abilities::SkillTable[110].skillType)
|
||||
pwr.handle(self->id, targetData, 110, Abilities::SkillTable[110].durationTime[0], Abilities::SkillTable[110].powerIntensity[0]);*/
|
||||
// clear outlying debuffs
|
||||
clearDebuff(self);
|
||||
}
|
||||
|
||||
void Mob::onRoamStart() {
|
||||
// stub
|
||||
}
|
||||
void MobAI::onCombatStart(CombatNPC* npc, EntityRef src) {
|
||||
Mob* self = (Mob*)npc;
|
||||
|
||||
void Mob::onCombatStart(EntityRef src) {
|
||||
assert(src.type == EntityType::PLAYER);
|
||||
target = src.sock;
|
||||
nextMovement = getTime();
|
||||
nextAttack = 0;
|
||||
assert(src.kind == EntityKind::PLAYER);
|
||||
self->target = src.sock;
|
||||
self->nextMovement = getTime();
|
||||
self->nextAttack = 0;
|
||||
|
||||
roamX = x;
|
||||
roamY = y;
|
||||
roamZ = z;
|
||||
self->roamX = self->x;
|
||||
self->roamY = self->y;
|
||||
self->roamZ = self->z;
|
||||
|
||||
int skillID = (int)data["m_iPassiveBuff"]; // cast passive
|
||||
std::vector<int> targetData = { 1, id, 0, 0, 0 };
|
||||
int skillID = (int)self->data["m_iPassiveBuff"]; // cast passive
|
||||
// TODO ABILITIES
|
||||
/*std::vector<int> targetData = { 1, self->id, 0, 0, 0 };
|
||||
for (auto& pwr : Abilities::Powers)
|
||||
if (pwr.skillType == Abilities::SkillTable[skillID].skillType)
|
||||
pwr.handle(id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]);
|
||||
pwr.handle(self->id, targetData, skillID, Abilities::SkillTable[skillID].durationTime[0], Abilities::SkillTable[skillID].powerIntensity[0]);*/
|
||||
}
|
||||
|
||||
void Mob::onRetreat() {
|
||||
target = nullptr;
|
||||
MobAI::clearDebuff(this);
|
||||
if (groupLeader != 0)
|
||||
MobAI::groupRetreat(this);
|
||||
void MobAI::onRetreat(CombatNPC* npc, EntityRef src) {
|
||||
Mob* self = (Mob*)npc;
|
||||
|
||||
self->target = nullptr;
|
||||
MobAI::clearDebuff(self);
|
||||
if (self->groupLeader != 0)
|
||||
MobAI::groupRetreat(self);
|
||||
}
|
||||
|
||||
void Mob::onDeath(EntityRef src) {
|
||||
target = nullptr;
|
||||
cbf = 0;
|
||||
skillStyle = -1;
|
||||
unbuffTimes.clear();
|
||||
killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step?
|
||||
void MobAI::onDeath(CombatNPC* npc, EntityRef src) {
|
||||
Mob* self = (Mob*)npc;
|
||||
|
||||
self->target = nullptr;
|
||||
self->cbf = 0;
|
||||
self->skillStyle = -1;
|
||||
self->unbuffTimes.clear();
|
||||
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
|
||||
if (src.type == EntityType::PLAYER && src.isValid()) {
|
||||
if (src.kind == EntityKind::PLAYER && src.isValid()) {
|
||||
Player* plr = PlayerManager::getPlayer(src.sock);
|
||||
|
||||
Items::DropRoll rolled;
|
||||
@ -789,8 +849,8 @@ void Mob::onDeath(EntityRef src) {
|
||||
Combat::genQItemRolls(leader, qitemRolls);
|
||||
|
||||
if (plr->groupCnt == 1 && plr->iIDGroup == plr->iID) {
|
||||
Items::giveMobDrop(src.sock, this, rolled, eventRolled);
|
||||
Missions::mobKilled(src.sock, type, qitemRolls);
|
||||
Items::giveMobDrop(src.sock, self, rolled, eventRolled);
|
||||
Missions::mobKilled(src.sock, self->type, qitemRolls);
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < leader->groupCnt; i++) {
|
||||
@ -805,21 +865,21 @@ void Mob::onDeath(EntityRef src) {
|
||||
if (dist > 5000)
|
||||
continue;
|
||||
|
||||
Items::giveMobDrop(sockTo, this, rolled, eventRolled);
|
||||
Missions::mobKilled(sockTo, type, qitemRolls);
|
||||
Items::giveMobDrop(sockTo, self, rolled, eventRolled);
|
||||
Missions::mobKilled(sockTo, self->type, qitemRolls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// delay the despawn animation
|
||||
despawned = false;
|
||||
self->despawned = false;
|
||||
|
||||
auto it = Transport::NPCQueues.find(id);
|
||||
auto it = Transport::NPCQueues.find(self->id);
|
||||
if (it == Transport::NPCQueues.end() || it->second.empty())
|
||||
return;
|
||||
|
||||
// rewind or empty the movement queue
|
||||
if (staticPath) {
|
||||
if (self->staticPath) {
|
||||
/*
|
||||
* This is inelegant, but we wind forward in the path until we find the point that
|
||||
* corresponds with the Mob's spawn point.
|
||||
@ -827,12 +887,12 @@ void Mob::onDeath(EntityRef src) {
|
||||
* IMPORTANT: The check in TableData::loadPaths() must pass or else this will loop forever.
|
||||
*/
|
||||
auto& queue = it->second;
|
||||
for (auto point = queue.front(); point.x != spawnX || point.y != spawnY; point = queue.front()) {
|
||||
for (auto point = queue.front(); point.x != self->spawnX || point.y != self->spawnY; point = queue.front()) {
|
||||
queue.pop();
|
||||
queue.push(point);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Transport::NPCQueues.erase(id);
|
||||
Transport::NPCQueues.erase(self->id);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,20 @@
|
||||
#include "NPCManager.hpp"
|
||||
#include "Entities.hpp"
|
||||
|
||||
/* kill me */
|
||||
struct Mob;
|
||||
namespace MobAI {
|
||||
void deadStep(CombatNPC* self, time_t currTime);
|
||||
void combatStep(CombatNPC* self, time_t currTime);
|
||||
void roamingStep(CombatNPC* self, time_t currTime);
|
||||
void retreatStep(CombatNPC* self, time_t currTime);
|
||||
|
||||
void onRoamStart(CombatNPC* self, EntityRef src);
|
||||
void onCombatStart(CombatNPC* self, EntityRef src);
|
||||
void onRetreat(CombatNPC* self, EntityRef src);
|
||||
void onDeath(CombatNPC* self, EntityRef src);
|
||||
}
|
||||
|
||||
struct Mob : public CombatNPC {
|
||||
// general
|
||||
std::unordered_map<int32_t,time_t> unbuffTimes = {};
|
||||
@ -60,7 +74,18 @@ struct Mob : public CombatNPC {
|
||||
// NOTE: there appear to be discrepancies in the dump
|
||||
hp = maxHealth;
|
||||
|
||||
kind = EntityType::MOB;
|
||||
kind = EntityKind::MOB;
|
||||
|
||||
// AI
|
||||
stateHandlers[AIState::DEAD] = MobAI::deadStep;
|
||||
stateHandlers[AIState::COMBAT] = MobAI::combatStep;
|
||||
stateHandlers[AIState::ROAMING] = MobAI::roamingStep;
|
||||
stateHandlers[AIState::RETREAT] = MobAI::retreatStep;
|
||||
|
||||
transitionHandlers[AIState::DEAD] = MobAI::onDeath;
|
||||
transitionHandlers[AIState::COMBAT] = MobAI::onCombatStart;
|
||||
transitionHandlers[AIState::ROAMING] = MobAI::onRoamStart;
|
||||
transitionHandlers[AIState::RETREAT] = MobAI::onRetreat;
|
||||
}
|
||||
|
||||
// constructor for /summon
|
||||
@ -71,16 +96,8 @@ struct Mob : public CombatNPC {
|
||||
|
||||
~Mob() {}
|
||||
|
||||
virtual void roamingStep(time_t currTime) override;
|
||||
virtual void combatStep(time_t currTime) override;
|
||||
virtual void retreatStep(time_t currTime) override;
|
||||
virtual void deadStep(time_t currTime) override;
|
||||
|
||||
virtual void onInactive() override;
|
||||
virtual void onRoamStart() override;
|
||||
virtual void onCombatStart(EntityRef src) override;
|
||||
virtual void onRetreat() override;
|
||||
virtual void onDeath(EntityRef src) override;
|
||||
virtual int takeDamage(EntityRef src, int amt) override;
|
||||
virtual void step(time_t currTime) override;
|
||||
|
||||
auto operator[](std::string s) {
|
||||
return data[s];
|
||||
|
@ -83,7 +83,7 @@ void NPCManager::sendToViewable(BaseNPC *npc, void *buf, uint32_t type, size_t s
|
||||
for (auto it = npc->viewableChunks.begin(); it != npc->viewableChunks.end(); it++) {
|
||||
Chunk* chunk = *it;
|
||||
for (const EntityRef& ref : chunk->entities) {
|
||||
if (ref.type == EntityType::PLAYER)
|
||||
if (ref.kind == EntityKind::PLAYER)
|
||||
ref.sock->sendPacket(buf, type, size);
|
||||
}
|
||||
}
|
||||
@ -275,7 +275,7 @@ BaseNPC* NPCManager::getNearestNPC(std::set<Chunk*>* chunks, int X, int Y, int Z
|
||||
for (auto c = chunks->begin(); c != chunks->end(); c++) { // haha get it
|
||||
Chunk* chunk = *c;
|
||||
for (auto ent = chunk->entities.begin(); ent != chunk->entities.end(); ent++) {
|
||||
if (ent->type == EntityType::PLAYER)
|
||||
if (ent->kind == EntityKind::PLAYER)
|
||||
continue;
|
||||
|
||||
BaseNPC* npcTemp = (BaseNPC*)ent->getEntity();
|
||||
@ -351,7 +351,7 @@ void NPCManager::queueNPCRemoval(int32_t id) {
|
||||
|
||||
static void step(CNServer *serv, time_t currTime) {
|
||||
for (auto& pair : NPCs) {
|
||||
if (pair.second->kind != EntityType::COMBAT_NPC && pair.second->kind != EntityType::MOB)
|
||||
if (pair.second->kind != EntityKind::COMBAT_NPC && pair.second->kind != EntityKind::MOB)
|
||||
continue;
|
||||
auto npc = (CombatNPC*)pair.second;
|
||||
|
||||
|
@ -87,11 +87,12 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) {
|
||||
|
||||
// passive nano unbuffing
|
||||
if (Abilities::SkillTable[skillID].drainType == 2) {
|
||||
std::vector<int> targetData = Abilities::findTargets(plr, skillID);
|
||||
// TODO ABILITIES
|
||||
/*std::vector<int> targetData = Abilities::findTargets(plr, skillID);
|
||||
|
||||
for (auto& pwr : Abilities::Powers)
|
||||
if (pwr.skillType == Abilities::SkillTable[skillID].skillType)
|
||||
Abilities::removeBuff(sock, targetData, pwr.bitFlag, pwr.timeBuffID, 0,(Abilities::SkillTable[skillID].targetType == 3));
|
||||
Abilities::removeBuff(sock, targetData, pwr.bitFlag, pwr.timeBuffID, 0,(Abilities::SkillTable[skillID].targetType == 3));*/
|
||||
}
|
||||
|
||||
if (nanoID >= NANO_COUNT || nanoID < 0)
|
||||
@ -102,20 +103,20 @@ void Nanos::summonNano(CNSocket *sock, int slot, bool silent) {
|
||||
|
||||
// passive nano buffing
|
||||
if (Abilities::SkillTable[skillID].drainType == 2) {
|
||||
std::vector<int> targetData = Abilities::findTargets(plr, skillID);
|
||||
|
||||
int boost = 0;
|
||||
if (getNanoBoost(plr))
|
||||
boost = 1;
|
||||
|
||||
for (auto& pwr : Abilities::Powers) {
|
||||
if (pwr.skillType == Abilities::SkillTable[skillID].skillType) {
|
||||
resp.eCSTB___Add = 1; // the part that makes nano go ZOOMAZOOM
|
||||
plr->nanoDrainRate = Abilities::SkillTable[skillID].batteryUse[boost*3];
|
||||
// TODO ABILITIES
|
||||
//std::vector<int> targetData = Abilities::findTargets(plr, skillID);
|
||||
//for (auto& pwr : Abilities::Powers) {
|
||||
// if (pwr.skillType == Abilities::SkillTable[skillID].skillType) {
|
||||
// resp.eCSTB___Add = 1; // the part that makes nano go ZOOMAZOOM
|
||||
// plr->nanoDrainRate = Abilities::SkillTable[skillID].batteryUse[boost*3];
|
||||
|
||||
pwr.handle(sock, targetData, nanoID, skillID, 0, Abilities::SkillTable[skillID].powerIntensity[boost]);
|
||||
}
|
||||
}
|
||||
// pwr.handle(sock, targetData, nanoID, skillID, 0, Abilities::SkillTable[skillID].powerIntensity[boost]);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
if (!silent) // silent nano death but only for the summoning player
|
||||
@ -296,8 +297,6 @@ static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) {
|
||||
std::cout << PlayerManager::getPlayerName(plr) << " requested to summon nano skill " << std::endl;
|
||||
)
|
||||
|
||||
std::vector<int> targetData = Abilities::findTargets(plr, skillID, data);
|
||||
|
||||
int boost = 0;
|
||||
if (getNanoBoost(plr))
|
||||
boost = 1;
|
||||
@ -306,9 +305,11 @@ static void nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) {
|
||||
if (plr->Nanos[plr->activeNano].iStamina < 0)
|
||||
plr->Nanos[plr->activeNano].iStamina = 0;
|
||||
|
||||
// TODO ABILITIES
|
||||
/*std::vector<int> targetData = Abilities::findTargets(plr, skillID, data);
|
||||
for (auto& pwr : Abilities::Powers)
|
||||
if (pwr.skillType == Abilities::SkillTable[skillID].skillType)
|
||||
pwr.handle(sock, targetData, nanoID, skillID, Abilities::SkillTable[skillID].durationTime[boost], Abilities::SkillTable[skillID].powerIntensity[boost]);
|
||||
pwr.handle(sock, targetData, nanoID, skillID, Abilities::SkillTable[skillID].durationTime[boost], Abilities::SkillTable[skillID].powerIntensity[boost]);*/
|
||||
|
||||
if (plr->Nanos[plr->activeNano].iStamina < 0)
|
||||
summonNano(sock, -1);
|
||||
|
@ -85,7 +85,7 @@ struct Player : public Entity, public ICombatant {
|
||||
time_t lastShot = 0;
|
||||
std::vector<sItemBase> buyback = {};
|
||||
|
||||
Player() { kind = EntityType::PLAYER; }
|
||||
Player() { kind = EntityKind::PLAYER; }
|
||||
|
||||
virtual void enterIntoViewOf(CNSocket *sock) override;
|
||||
virtual void disappearFromViewOf(CNSocket *sock) override;
|
||||
|
@ -353,7 +353,7 @@ void PlayerManager::sendToViewable(CNSocket* sock, void* buf, uint32_t type, siz
|
||||
for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) {
|
||||
Chunk* chunk = *it;
|
||||
for (const EntityRef& ref : chunk->entities) {
|
||||
if (ref.type != EntityType::PLAYER || ref.sock == sock)
|
||||
if (ref.kind != EntityKind::PLAYER || ref.sock == sock)
|
||||
continue;
|
||||
|
||||
ref.sock->sendPacket(buf, type, size);
|
||||
@ -411,7 +411,8 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
||||
if (!(plr->iConditionBitFlag & CSB_BIT_PHOENIX))
|
||||
return; // sanity check
|
||||
plr->Nanos[plr->activeNano].iStamina = 0;
|
||||
Abilities::applyBuff(sock, plr->Nanos[plr->activeNano].iSkillID, 2, 1, 0);
|
||||
// TODO ABILITIES
|
||||
//Abilities::applyBuff(sock, plr->Nanos[plr->activeNano].iSkillID, 2, 1, 0);
|
||||
// fallthrough
|
||||
case ePCRegenType::HereByPhoenixGroup: // revived by group member's nano
|
||||
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
||||
|
@ -43,7 +43,7 @@ namespace PlayerManager {
|
||||
for (auto it = plr->viewableChunks.begin(); it != plr->viewableChunks.end(); it++) {
|
||||
Chunk* chunk = *it;
|
||||
for (const EntityRef& ref : chunk->entities) {
|
||||
if (ref.type != EntityType::PLAYER || ref.sock == sock)
|
||||
if (ref.kind != EntityKind::PLAYER || ref.sock == sock)
|
||||
continue;
|
||||
|
||||
ref.sock->sendPacket(pkt, type);
|
||||
|
@ -1226,7 +1226,7 @@ void TableData::flush() {
|
||||
continue;
|
||||
|
||||
int x, y, z;
|
||||
if (npc->kind == EntityType::MOB) {
|
||||
if (npc->kind == EntityKind::MOB) {
|
||||
Mob *m = (Mob*)npc;
|
||||
x = m->spawnX;
|
||||
y = m->spawnY;
|
||||
@ -1259,7 +1259,7 @@ void TableData::flush() {
|
||||
|
||||
int x, y, z;
|
||||
std::vector<Mob*> followers;
|
||||
if (npc->kind == EntityType::MOB) {
|
||||
if (npc->kind == EntityKind::MOB) {
|
||||
Mob* m = (Mob*)npc;
|
||||
x = m->spawnX;
|
||||
y = m->spawnY;
|
||||
@ -1271,7 +1271,7 @@ void TableData::flush() {
|
||||
|
||||
// add follower data to vector; go until OOB or until follower ID is 0
|
||||
for (int i = 0; i < 4 && m->groupMember[i] > 0; i++) {
|
||||
if (NPCManager::NPCs.find(m->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[m->groupMember[i]]->kind != EntityType::MOB) {
|
||||
if (NPCManager::NPCs.find(m->groupMember[i]) == NPCManager::NPCs.end() || NPCManager::NPCs[m->groupMember[i]]->kind != EntityKind::MOB) {
|
||||
std::cout << "[WARN] Follower with ID " << m->groupMember[i] << " not found; skipping\n";
|
||||
continue;
|
||||
}
|
||||
|
@ -257,13 +257,13 @@ static void stepNPCPathing() {
|
||||
}
|
||||
|
||||
// skip if not simulating mobs
|
||||
if (npc->kind == EntityType::MOB && !MobAI::simulateMobs) {
|
||||
if (npc->kind == EntityKind::MOB && !MobAI::simulateMobs) {
|
||||
it++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// do not roam if not roaming
|
||||
if (npc->kind == EntityType::MOB && ((Mob*)npc)->state != AIState::ROAMING) {
|
||||
if (npc->kind == EntityKind::MOB && ((Mob*)npc)->state != AIState::ROAMING) {
|
||||
it++;
|
||||
continue;
|
||||
}
|
||||
@ -280,7 +280,7 @@ static void stepNPCPathing() {
|
||||
|
||||
// TODO: move walking logic into Entity stack
|
||||
switch (npc->kind) {
|
||||
case EntityType::BUS:
|
||||
case EntityKind::BUS:
|
||||
INITSTRUCT(sP_FE2CL_TRANSPORTATION_MOVE, busMove);
|
||||
|
||||
busMove.eTT = 3;
|
||||
@ -293,7 +293,7 @@ static void stepNPCPathing() {
|
||||
|
||||
NPCManager::sendToViewable(npc, &busMove, P_FE2CL_TRANSPORTATION_MOVE, sizeof(sP_FE2CL_TRANSPORTATION_MOVE));
|
||||
break;
|
||||
case EntityType::MOB:
|
||||
case EntityKind::MOB:
|
||||
MobAI::incNextMovement((Mob*)npc);
|
||||
/* fallthrough */
|
||||
default:
|
||||
@ -385,7 +385,7 @@ NPCPath* Transport::findApplicablePath(int32_t id, int32_t type, int taskID) {
|
||||
|
||||
void Transport::constructPathNPC(int32_t id, NPCPath* path) {
|
||||
BaseNPC* npc = NPCManager::NPCs[id];
|
||||
if (npc->kind == EntityType::MOB)
|
||||
if (npc->kind == EntityKind::MOB)
|
||||
((Mob*)(npc))->staticPath = true;
|
||||
npc->loopingPath = path->isLoop;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user