Implement two more mission types + tweaks & fixes

* Weapons will consume your batteries fully.
* Nerfed enemy damage at lower levels.
* Further reworked drain, uses a static variable as a timer (lastDrainTime)
* resendMobHP has been repurposed to drainMobHP.
* Players heal faster after a sizable cooldown.
* Nano type advantage is more noticeable during combat.

Implemented two more mission types + Tweaks

* Item delivery quests now work.
* Timed missions now work.
* All escort missions (type 6) are skipped.
* /minfo now also prints the terminator npc.
* Weapon battery consumption tweaked
* Fixed indentations.
* Heal nanos have better output (25% -> 35%)
* Damage formula had a slight tweak.
* Bugfixed weapon equipping.
* Other tweaks
This commit is contained in:
Jade 2020-10-21 06:24:51 +01:00 committed by Gent Semaj
parent 177c5f0f17
commit c8497a4856
No known key found for this signature in database
GPG Key ID: 76A006BEBA710050
10 changed files with 117 additions and 85 deletions

View File

@ -411,6 +411,7 @@ void minfoCommand(std::string full, std::vector<std::string>& args, CNSocket* so
ChatManager::sendServerMessage(sock, "[MINFO] Current task ID: " + std::to_string(plr->tasks[i]));
ChatManager::sendServerMessage(sock, "[MINFO] Current task type: " + std::to_string((int)(task["m_iHTaskType"])));
ChatManager::sendServerMessage(sock, "[MINFO] Current waypoint NPC ID: " + std::to_string((int)(task["m_iSTGrantWayPoint"])));
ChatManager::sendServerMessage(sock, "[MINFO] Current terminator NPC ID: " + std::to_string((int)(task["m_iHTerminatorNPCID"])));
for (int j = 0; j < 3; j++)
if ((int)(task["m_iCSUEnemyID"][j]) != 0)

View File

@ -29,12 +29,12 @@ void GroupManager::requestGroup(CNSocket* sock, CNPacketData* data) {
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_To);
if (plr == nullptr || otherPlr == nullptr)
return;
return;
otherPlr = PlayerManager::getPlayerFromID(otherPlr->iIDGroup);
if (otherPlr == nullptr)
return;
return;
// fail if the group is full or the other player is already in a group
if (plr->groupCnt >= 4 || otherPlr->groupCnt > 1) {
@ -50,9 +50,9 @@ void GroupManager::requestGroup(CNSocket* sock, CNPacketData* data) {
INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE, resp);
resp.iHostID = plr->iIDGroup;
resp.iHostID = plr->iIDGroup;
otherSock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE, sizeof(sP_FE2CL_PC_GROUP_INVITE));
otherSock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE, sizeof(sP_FE2CL_PC_GROUP_INVITE));
}
void GroupManager::refuseGroup(CNSocket* sock, CNPacketData* data) {
@ -61,7 +61,7 @@ void GroupManager::refuseGroup(CNSocket* sock, CNPacketData* data) {
sP_CL2FE_REQ_PC_GROUP_INVITE_REFUSE* recv = (sP_CL2FE_REQ_PC_GROUP_INVITE_REFUSE*)data->buf;
CNSocket* otherSock = PlayerManager::getSockFromID(recv->iID_From);
CNSocket* otherSock = PlayerManager::getSockFromID(recv->iID_From);
if (otherSock == nullptr)
return;
@ -69,13 +69,13 @@ void GroupManager::refuseGroup(CNSocket* sock, CNPacketData* data) {
Player* plr = PlayerManager::getPlayer(sock);
if (plr == nullptr)
return;
return;
INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE_REFUSE, resp);
resp.iID_To = plr->iID;
otherSock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE_REFUSE, sizeof(sP_FE2CL_PC_GROUP_INVITE_REFUSE));
otherSock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE_REFUSE, sizeof(sP_FE2CL_PC_GROUP_INVITE_REFUSE));
}
void GroupManager::joinGroup(CNSocket* sock, CNPacketData* data) {
@ -87,12 +87,12 @@ void GroupManager::joinGroup(CNSocket* sock, CNPacketData* data) {
Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_From);
if (plr == nullptr || otherPlr == nullptr)
return;
return;
otherPlr = PlayerManager::getPlayerFromID(otherPlr->iIDGroup);
if (otherPlr == nullptr)
return;
return;
// fail if the group is full or the other player is already in a group
if (plr->groupCnt > 1 || plr->iIDGroup != plr->iID || otherPlr->groupCnt >= 4) {
@ -172,7 +172,7 @@ void GroupManager::chatGroup(CNSocket* sock, CNPacketData* data) {
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
if (plr == nullptr || otherPlr == nullptr)
return;
return;
// send to client
INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, resp);
@ -184,14 +184,14 @@ void GroupManager::chatGroup(CNSocket* sock, CNPacketData* data) {
void GroupManager::menuChatGroup(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE))
return; // malformed packet
return; // malformed packet
sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE*)data->buf;
Player* plr = PlayerManager::getPlayer(sock);
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
if (plr == nullptr || otherPlr == nullptr)
return;
return;
// send to client
INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, resp);
@ -276,7 +276,7 @@ void GroupManager::groupKickPlayer(Player* plr) {
Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup);
if (otherPlr == nullptr)
return;
return;
if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_LEAVE), otherPlr->groupCnt - 1, sizeof(sPCGroupMemberInfo))) {
std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_LEAVE packet size\n";
@ -340,7 +340,7 @@ void GroupManager::groupKickPlayer(Player* plr) {
CNSocket* sock = PlayerManager::getSockFromID(plr->iID);
if (sock == nullptr)
return;
return;
INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, resp1);
sock->sendPacket((void*)&resp1, P_FE2CL_PC_GROUP_LEAVE_SUCC, sizeof(sP_FE2CL_PC_GROUP_LEAVE_SUCC));

View File

@ -140,12 +140,12 @@ void ItemManager::itemMoveHandler(CNSocket* sock, CNPacketData* data) {
INITSTRUCT(sP_FE2CL_PC_EQUIP_CHANGE, equipChange);
equipChange.iPC_ID = plr.plr->iID;
if (itemmove->eFrom == (int)SlotType::EQUIP) {
equipChange.iEquipSlotNum = itemmove->iFromSlotNum;
equipChange.EquipSlotItem = resp.ToSlotItem;
} else {
if (itemmove->eTo == (int)SlotType::EQUIP) {
equipChange.iEquipSlotNum = itemmove->iToSlotNum;
equipChange.EquipSlotItem = resp.FromSlotItem;
} else {
equipChange.iEquipSlotNum = itemmove->iFromSlotNum;
equipChange.EquipSlotItem = resp.ToSlotItem;
}
// unequip vehicle if equip slot 8 is 0

View File

@ -77,20 +77,27 @@ void MissionManager::taskStart(CNSocket* sock, CNPacketData* data) {
return;
}
TaskData& task = *Tasks[missionData->iTaskNum];
response.iTaskNum = missionData->iTaskNum;
response.iRemainTime = task["m_iSTGrantTimer"];
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC));
// HACK: auto-succeed Eduardo escort task
// TODO: maybe check for iTaskType == 6 and skip all escort missions?
if (missionData->iTaskNum == 576) {
// HACK: auto-succeed escort task
if (task["m_iHTaskType"] == 6) {
std::cout << "Sending Eduardo success packet" << std::endl;
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_SUCC, response);
endTask(sock, 576);
response.iTaskNum = 576;
endTask(sock, missionData->iTaskNum);
response.iTaskNum = missionData->iTaskNum;
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_END_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_END_SUCC));
}
// Give player their delivery items at the start.
for (int i = 0; i < 3; i++)
if (task["m_iSTItemID"][i] != 0 && task["m_iSTItemNumNeeded"][i] > 0)
dropQuestItem(sock, missionData->iTaskNum, task["m_iSTItemNumNeeded"][i], task["m_iSTItemID"][i], 0);
}
void MissionManager::taskEnd(CNSocket* sock, CNPacketData* data) {
@ -98,6 +105,25 @@ void MissionManager::taskEnd(CNSocket* sock, CNPacketData* data) {
return; // malformed packet
sP_CL2FE_REQ_PC_TASK_END* missionData = (sP_CL2FE_REQ_PC_TASK_END*)data->buf;
// failed timed missions give an iNPC_ID of 0
if (missionData->iNPC_ID == 0) {
TaskData* task = MissionManager::Tasks[missionData->iTaskNum];
// double-checking
if (task->task["m_iHTaskType"] == 3) {
Player* plr = PlayerManager::getPlayer(sock);
int failTaskID = task->task["m_iFOutgoingTask"];
if (failTaskID != 0) {
MissionManager::quitTask(sock, missionData->iTaskNum, false);
for (int i = 0; i < 6; i++)
if (plr->tasks[i] == missionData->iTaskNum)
plr->tasks[i] = failTaskID;
return;
}
}
}
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_SUCC, response);
response.iTaskNum = missionData->iTaskNum;

View File

@ -85,11 +85,12 @@ void MobManager::pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
damage.first = plr->pointDamage;
int difficulty = (int)mob->data["m_iNpcLevel"];
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW > 6 + difficulty), NanoManager::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW >= 11 + difficulty), NanoManager::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
if (plr->batteryW >= 11 + difficulty)
plr->batteryW -= 11 + difficulty;
if (plr->batteryW >= 6 + difficulty)
plr->batteryW -= 6 + difficulty;
else
plr->batteryW = 0;
damage.first = hitMob(sock, mob, damage.first);
@ -126,7 +127,7 @@ void MobManager::npcAttackPc(Mob *mob, time_t currTime) {
sP_FE2CL_NPC_ATTACK_PCs *pkt = (sP_FE2CL_NPC_ATTACK_PCs*)respbuf;
sAttackResult *atk = (sAttackResult*)(respbuf + sizeof(sP_FE2CL_NPC_ATTACK_PCs));
auto damage = getDamage(475 + (int)mob->data["m_iPower"], plr->defense, false, false, -1, -1, 1);
auto damage = getDamage(450 + (int)mob->data["m_iPower"], plr->defense, false, false, -1, -1, rand() % plr->level + 1);
plr->HP -= damage.first;
pkt->iNPC_ID = mob->appearanceData.iNPC_ID;
@ -329,16 +330,16 @@ int MobManager::hitMob(CNSocket *sock, Mob *mob, int damage) {
mob->appearanceData.iHP -= damage;
// wake up sleeping monster
if (mob->appearanceData.iConditionBitFlag & CSB_BIT_MEZ) {
mob->appearanceData.iConditionBitFlag &= ~CSB_BIT_MEZ;
// wake up sleeping monster
if (mob->appearanceData.iConditionBitFlag & CSB_BIT_MEZ) {
mob->appearanceData.iConditionBitFlag &= ~CSB_BIT_MEZ;
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
pkt1.eCT = 2;
pkt1.iID = mob->appearanceData.iNPC_ID;
pkt1.iConditionBitFlag = mob->appearanceData.iConditionBitFlag;
NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
}
INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1);
pkt1.eCT = 2;
pkt1.iID = mob->appearanceData.iNPC_ID;
pkt1.iConditionBitFlag = mob->appearanceData.iConditionBitFlag;
NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT));
}
if (mob->appearanceData.iHP <= 0)
killMob(mob->target, mob);
@ -492,7 +493,7 @@ void MobManager::combatStep(Mob *mob, time_t currTime) {
// movement logic
if (mob->nextMovement != 0 && currTime < mob->nextMovement)
return;
mob->nextMovement = currTime + 399;
mob->nextMovement = currTime + 400;
if (currTime >= mob->nextAttack)
mob->nextAttack = 0;
@ -598,7 +599,7 @@ void MobManager::retreatStep(Mob *mob, time_t currTime) {
if (mob->nextMovement != 0 && currTime < mob->nextMovement)
return;
mob->nextMovement = currTime + 399;
mob->nextMovement = currTime + 400;
// distance between spawn point and current location
int distance = hypot(mob->appearanceData.iX - mob->roamX, mob->appearanceData.iY - mob->roamY);
@ -611,11 +612,9 @@ void MobManager::retreatStep(Mob *mob, time_t currTime) {
pkt.iNPC_ID = mob->appearanceData.iNPC_ID;
pkt.iSpeed = (int)mob->data["m_iRunSpeed"] * 2;
mob->appearanceData.iX = targ.first;
mob->appearanceData.iY = targ.second;
pkt.iToX = mob->appearanceData.iX = targ.first;
pkt.iToY = mob->appearanceData.iY = targ.second;
pkt.iToZ = mob->appearanceData.iZ;
pkt.iToZ = mob->appearanceData.iZ = mob->spawnZ;
// notify all nearby players
NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE));
@ -630,11 +629,14 @@ void MobManager::retreatStep(Mob *mob, time_t currTime) {
mob->nextAttack = 0;
mob->appearanceData.iConditionBitFlag = 0;
resendMobHP(mob);
// HACK: we haven't found a better way to refresh a mob's client-side status
drainMobHP(mob, 0);
}
}
void MobManager::step(CNServer *serv, time_t currTime) {
static time_t lastDrainTime = 0;
for (auto& pair : Mobs) {
int x = pair.second->appearanceData.iX;
int y = pair.second->appearanceData.iY;
@ -644,9 +646,8 @@ void MobManager::step(CNServer *serv, time_t currTime) {
continue;
// drain
if (pair.second->appearanceData.iConditionBitFlag & CSB_BIT_BOUNDINGBALL) {
pair.second->appearanceData.iHP -= pair.second->maxHealth / 50; // lose 10% every second
// TODO: Make this send a damage packet
if (currTime - lastDrainTime >= 600 && pair.second->appearanceData.iConditionBitFlag & CSB_BIT_BOUNDINGBALL) {
drainMobHP(pair.second, pair.second->maxHealth * 3 / 50); // lose 10% every second
}
// unbuffing
@ -693,6 +694,9 @@ void MobManager::step(CNServer *serv, time_t currTime) {
}
}
if (currTime - lastDrainTime >= 600)
lastDrainTime = currTime;
// deallocate all NPCs queued for removal
while (RemovalQueue.size() > 0) {
NPCManager::destroyNPC(RemovalQueue.front());
@ -752,8 +756,10 @@ void MobManager::combatBegin(CNSocket *sock, CNPacketData *data) {
void MobManager::combatEnd(CNSocket *sock, CNPacketData *data) {
Player *plr = PlayerManager::getPlayer(sock);
if (plr != nullptr)
if (plr != nullptr) {
plr->inCombat = false;
plr->healCooldown = 4000;
}
}
void MobManager::dotDamageOnOff(CNSocket *sock, CNPacketData *data) {
@ -848,11 +854,14 @@ void MobManager::playerTick(CNServer *serv, time_t currTime) {
dealGooDamage(sock, PC_MAXHEALTH(plr->level) * 3 / 20);
// heal
if (currTime - lastHealTime >= 6000 && !plr->inCombat && plr->HP < PC_MAXHEALTH(plr->level)) {
plr->HP += PC_MAXHEALTH(plr->level) / 5;
if (plr->HP > PC_MAXHEALTH(plr->level))
plr->HP = PC_MAXHEALTH(plr->level);
transmit = true;
if (currTime - lastHealTime >= 4000 && !plr->inCombat && plr->HP < PC_MAXHEALTH(plr->level)) {
if (currTime - lastHealTime - plr->healCooldown >= 4000) {
plr->HP += PC_MAXHEALTH(plr->level) / 5;
if (plr->HP > PC_MAXHEALTH(plr->level))
plr->HP = PC_MAXHEALTH(plr->level);
transmit = true;
} else
plr->healCooldown -= 4000;
}
for (int i = 0; i < 3; i++) {
@ -894,7 +903,7 @@ void MobManager::playerTick(CNServer *serv, time_t currTime) {
}
// if this was a heal tick, update the counter outside of the loop
if (currTime - lastHealTime >= 6000)
if (currTime - lastHealTime >= 4000)
lastHealTime = currTime;
}
@ -907,15 +916,15 @@ std::pair<int,int> MobManager::getDamage(int attackPower, int defensePower, bool
// base calculation
int damage = attackPower * attackPower / (attackPower + defensePower);
damage = std::max(std::max(29, attackPower / 7), damage - defensePower * (12 + difficulty) / 65);
damage = std::max(10 + attackPower / 10, damage - defensePower * (4 + difficulty) / 40);
damage = damage * (rand() % 40 + 80) / 100;
// Adaptium/Blastons/Cosmix
if (attackerStyle != -1 && defenderStyle != -1 && attackerStyle != defenderStyle) {
if (attackerStyle < defenderStyle || attackerStyle - defenderStyle == 2)
damage = damage * 5 / 4;
damage = damage * 3 / 2;
else
damage = damage * 4 / 5;
damage = damage * 2 / 3;
}
// weapon boosts
@ -988,10 +997,12 @@ void MobManager::pcAttackChars(CNSocket *sock, CNPacketData *data) {
else
damage.first = plr->pointDamage;
damage = getDamage(damage.first, target->defense, true, (plr->batteryW >= 12), -1, -1, 1);
damage = getDamage(damage.first, target->defense, true, (plr->batteryW > 6 + plr->level), -1, -1, 1);
if (plr->batteryW >= 12)
plr->batteryW -= 12;
if (plr->batteryW >= 6 + plr->level)
plr->batteryW -= 6 + plr->level;
else
plr->batteryW = 0;
target->HP -= damage.first;
@ -1017,11 +1028,13 @@ void MobManager::pcAttackChars(CNSocket *sock, CNPacketData *data) {
int difficulty = (int)mob->data["m_iNpcLevel"];
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW >= 11 + difficulty),
damage = getDamage(damage.first, (int)mob->data["m_iProtection"], true, (plr->batteryW > 6 + difficulty),
NanoManager::nanoStyle(plr->activeNano), (int)mob->data["m_iNpcStyle"], difficulty);
if (plr->batteryW >= 11 + difficulty)
plr->batteryW -= 11 + difficulty;
if (plr->batteryW >= 6 + difficulty)
plr->batteryW -= 6 + difficulty;
else
plr->batteryW = 0;
damage.first = hitMob(sock, mob, damage.first);
@ -1045,25 +1058,24 @@ void MobManager::pcAttackChars(CNSocket *sock, CNPacketData *data) {
PlayerManager::sendToViewable(sock, (void*)respbuf, P_FE2CL_PC_ATTACK_CHARs, resplen);
}
// HACK: we haven't found a better way to refresh a mob's client-side status
void MobManager::resendMobHP(Mob *mob) {
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Heal_HP);
void MobManager::drainMobHP(Mob *mob, int amount) {
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage);
assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, resplen);
sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf;
sSkillResult_Heal_HP *heal = (sSkillResult_Heal_HP*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
sSkillResult_Damage *drain = (sSkillResult_Damage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
pkt->iID = mob->appearanceData.iNPC_ID;
pkt->eCT = 4; // mob
pkt->iTB_ID = ECSB_HEAL; // sSkillResult_Heal_HP
pkt->iTB_ID = ECSB_BOUNDINGBALL;
heal->eCT = 4;
heal->iID = mob->appearanceData.iNPC_ID;
heal->iHealHP = 0;
heal->iHP = mob->appearanceData.iHP;
drain->eCT = 4;
drain->iID = mob->appearanceData.iNPC_ID;
drain->iDamage = amount;
drain->iHP = mob->appearanceData.iHP -= amount;
NPCManager::sendToViewable(mob, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen);
}

View File

@ -139,7 +139,7 @@ namespace MobManager {
std::pair<int,int> getDamage(int, int, bool, bool, int, int, int);
void pcAttackChars(CNSocket *sock, CNPacketData *data);
void resendMobHP(Mob *mob);
void drainMobHP(Mob *mob, int amount);
void incNextMovement(Mob *mob, time_t currTime=0);
bool aggroCheck(Mob *mob, time_t currTime);
}

View File

@ -608,13 +608,6 @@ void NPCManager::handleWarp(CNSocket* sock, int32_t warpId) {
if (otherPlr == nullptr || sockTo == nullptr)
continue;
if (otherPlr->instanceID == 0) {
otherPlr->lastX = otherPlr->x;
otherPlr->lastY = otherPlr->y;
otherPlr->lastZ = otherPlr->z;
otherPlr->lastAngle = otherPlr->angle;
}
PlayerManager::sendPlayerTo(sockTo, Warps[warpId].x, Warps[warpId].y, Warps[warpId].z, instanceID);
}
}

View File

@ -672,8 +672,8 @@ void activePower(CNSocket *sock, CNPacketData *data,
// active nano power dispatch table
std::vector<ActivePower> ActivePowers = {
ActivePower(StunPowers, activePower<sSkillResult_Damage_N_Debuff, doDebuff>, EST_STUN, CSB_BIT_STUN, 2250),
ActivePower(HealPowers, activePower<sSkillResult_Heal_HP, doHeal>, EST_HEAL_HP, CSB_BIT_NONE, 25),
ActivePower(GroupHealPowers, activePower<sSkillResult_Heal_HP, doGroupHeal, GHEAL>,EST_HEAL_HP, CSB_BIT_NONE, 15),
ActivePower(HealPowers, activePower<sSkillResult_Heal_HP, doHeal>, EST_HEAL_HP, CSB_BIT_NONE, 35),
ActivePower(GroupHealPowers, activePower<sSkillResult_Heal_HP, doGroupHeal, GHEAL>,EST_HEAL_HP, CSB_BIT_NONE, 20),
// TODO: Recall
ActivePower(DrainPowers, activePower<sSkillResult_Buff, doBuff>, EST_BOUNDINGBALL, CSB_BIT_BOUNDINGBALL, 3000),
ActivePower(SnarePowers, activePower<sSkillResult_Damage_N_Debuff, doDebuff>, EST_SNARE, CSB_BIT_DN_MOVE_SPEED, 4500),

View File

@ -50,6 +50,7 @@ struct Player {
bool inCombat;
bool passiveNanoOut;
int healCooldown;
int pointDamage;
int groupDamage;

View File

@ -280,7 +280,6 @@ void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z) {
plrv.currentChunks.clear();
plrv.chunkPos = std::make_tuple(0, 0, plrv.plr->instanceID);
sock->sendPacket((void*)&pkt, P_FE2CL_REP_PC_GOTO_SUCC, sizeof(sP_FE2CL_REP_PC_GOTO_SUCC));
updatePlayerPosition(sock, X, Y, Z);
}
void PlayerManager::enterPlayer(CNSocket* sock, CNPacketData* data) {