diff --git a/Makefile b/Makefile index 492bad0..f39c5c2 100644 --- a/Makefile +++ b/Makefile @@ -49,6 +49,7 @@ CXXSRC=\ src/TableData.cpp\ src/ChunkManager.cpp\ src/BuddyManager.cpp\ + src/GroupManager.cpp\ # headers (for timestamp purposes) CHDR=\ @@ -86,6 +87,7 @@ CXXHDR=\ src/TableData.hpp\ src/ChunkManager.hpp\ src/BuddyManager.hpp\ + src/GroupManager.hpp\ COBJ=$(CSRC:.c=.o) CXXOBJ=$(CXXSRC:.cpp=.o) diff --git a/src/Database.cpp b/src/Database.cpp index 7e3b699..09e017a 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -1,4 +1,5 @@ #include "Database.hpp" +#include "Database.hpp" #include "contrib/bcrypt/BCrypt.hpp" #include "CNProtocol.hpp" #include @@ -456,7 +457,6 @@ Player Database::DbToPlayer(DbPlayer player) { result.equippedNanos[1] = player.Nano2; result.equippedNanos[2] = player.Nano3; - result.dotDamage = false; result.inCombat = false; result.iWarpLocationFlag = player.WarpLocationFlag; diff --git a/src/GroupManager.cpp b/src/GroupManager.cpp new file mode 100644 index 0000000..bc87339 --- /dev/null +++ b/src/GroupManager.cpp @@ -0,0 +1,336 @@ +#include "CNShardServer.hpp" +#include "CNStructs.hpp" +#include "ChatManager.hpp" +#include "PlayerManager.hpp" +#include "GroupManager.hpp" + +#include +#include +#include +#include + +void GroupManager::init() { + REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GROUP_INVITE, requestGroup); + REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GROUP_INVITE_REFUSE, refuseGroup); + REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GROUP_JOIN, joinGroup); + REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GROUP_LEAVE, leaveGroup); + REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE, chatGroup); + REGISTER_SHARD_PACKET(P_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE, menuChatGroup); +} + +void GroupManager::requestGroup(CNSocket* sock, CNPacketData* data) { + if (data->size != sizeof(sP_CL2FE_REQ_PC_GROUP_INVITE)) + return; // malformed packet + + sP_CL2FE_REQ_PC_GROUP_INVITE* recv = (sP_CL2FE_REQ_PC_GROUP_INVITE*)data->buf; + + Player* plr = PlayerManager::getPlayer(sock); + Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_To); + + if (plr == nullptr || otherPlr == nullptr) + return; + + otherPlr = PlayerManager::getPlayerFromID(otherPlr->iIDGroup); + + if (otherPlr == nullptr) + return; + + // fail if the group is full or the other player is already in a group + if (plr->groupCnt >= 4 || otherPlr->groupCnt > 1) { + INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE_FAIL, resp); + sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE_FAIL, sizeof(sP_FE2CL_PC_GROUP_INVITE_FAIL)); + return; + } + + CNSocket* otherSock = PlayerManager::getSockFromID(recv->iID_To); + + if (otherSock == nullptr) + return; + + INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE, resp); + + resp.iHostID = plr->iIDGroup; + + otherSock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_INVITE, sizeof(sP_FE2CL_PC_GROUP_INVITE)); +} + +void GroupManager::refuseGroup(CNSocket* sock, CNPacketData* data) { + if (data->size != sizeof(sP_CL2FE_REQ_PC_GROUP_INVITE_REFUSE)) + return; // malformed packet + + sP_CL2FE_REQ_PC_GROUP_INVITE_REFUSE* recv = (sP_CL2FE_REQ_PC_GROUP_INVITE_REFUSE*)data->buf; + + CNSocket* otherSock = PlayerManager::getSockFromID(recv->iID_From); + + if (otherSock == nullptr) + return; + + Player* plr = PlayerManager::getPlayer(sock); + + if (plr == nullptr) + 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)); +} + +void GroupManager::joinGroup(CNSocket* sock, CNPacketData* data) { + if (data->size != sizeof(sP_CL2FE_REQ_PC_GROUP_JOIN)) + return; // malformed packet + + sP_CL2FE_REQ_PC_GROUP_JOIN* recv = (sP_CL2FE_REQ_PC_GROUP_JOIN*)data->buf; + Player* plr = PlayerManager::getPlayer(sock); + Player* otherPlr = PlayerManager::getPlayerFromID(recv->iID_From); + + if (plr == nullptr || otherPlr == nullptr) + return; + + otherPlr = PlayerManager::getPlayerFromID(otherPlr->iIDGroup); + + if (otherPlr == nullptr) + return; + + // fail if the group is full the other player is already in a group + if (plr->groupCnt > 1 || plr->iIDGroup != plr->iID || otherPlr->groupCnt >= 4) { + INITSTRUCT(sP_FE2CL_PC_GROUP_INVITE_FAIL, resp); + sock->sendPacket((void*)&resp, P_FE2CL_PC_GROUP_JOIN_FAIL, sizeof(sP_FE2CL_PC_GROUP_JOIN_FAIL)); + return; + } + + if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_JOIN), otherPlr->groupCnt + 1, sizeof(sPCGroupMemberInfo))) { + std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size\n"; + return; + } + + plr->iIDGroup = otherPlr->iID; + otherPlr->groupCnt += 1; + otherPlr->groupIDs[otherPlr->groupCnt-1] = plr->iID; + + size_t resplen = sizeof(sP_FE2CL_PC_GROUP_JOIN) + otherPlr->groupCnt * sizeof(sPCGroupMemberInfo); + uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; + + memset(respbuf, 0, resplen); + + sP_FE2CL_PC_GROUP_JOIN *resp = (sP_FE2CL_PC_GROUP_JOIN*)respbuf; + sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_JOIN)); + + resp->iID_NewMember = plr->iID; + resp->iMemberPCCnt = otherPlr->groupCnt; + + for (int i = 0; i < otherPlr->groupCnt; i++) { + Player* varPlr = PlayerManager::getPlayerFromID(otherPlr->groupIDs[i]); + + if (varPlr == nullptr) + continue; + + respdata[i].iPC_ID = varPlr->iID; + respdata[i].iPCUID = varPlr->PCStyle.iPC_UID; + respdata[i].iNameCheck = varPlr->PCStyle.iNameCheck; + memcpy(respdata[i].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName)); + memcpy(respdata[i].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName)); + respdata[i].iSpecialState = varPlr->iSpecialState; + respdata[i].iLv = varPlr->level; + respdata[i].iHP = varPlr->HP; + respdata[i].iMaxHP = PC_MAXHEALTH(varPlr->level); + //respdata[i].iMapType = 0; + //respdata[i].iMapNum = 0; + respdata[i].iX = varPlr->x; + respdata[i].iY = varPlr->y; + respdata[i].iZ = varPlr->z; + // client doesnt read nano data here + } + + sendToGroup(otherPlr, (void*)&respbuf, P_FE2CL_PC_GROUP_JOIN, resplen); +} + +void GroupManager::leaveGroup(CNSocket* sock, CNPacketData* data) { + if (data->size != sizeof(P_CL2FE_REQ_PC_GROUP_LEAVE)) + return; // malformed packet + + Player* plr = PlayerManager::getPlayer(sock); + + if (plr == nullptr) + return; + + groupKickPlayer(plr); +} + +void GroupManager::chatGroup(CNSocket* sock, CNPacketData* data) { + if (data->size != sizeof(sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE)) + return; // malformed packet + + sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE* chat = (sP_CL2FE_REQ_SEND_ALL_GROUP_FREECHAT_MESSAGE*)data->buf; + Player* plr = PlayerManager::getPlayer(sock); + Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup); + + if (plr == nullptr || otherPlr == nullptr) + return; + + // send to client + INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, resp); + memcpy(resp.szFreeChat, chat->szFreeChat, sizeof(chat->szFreeChat)); + resp.iSendPCID = plr->iID; + resp.iEmoteCode = chat->iEmoteCode; + sendToGroup(otherPlr, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC)); +} + +void GroupManager::menuChatGroup(CNSocket* sock, CNPacketData* data) { + if (data->size != sizeof(sP_CL2FE_REQ_SEND_ALL_GROUP_MENUCHAT_MESSAGE)) + 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; + + // send to client + INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, resp); + memcpy(resp.szFreeChat, chat->szFreeChat, sizeof(chat->szFreeChat)); + resp.iSendPCID = plr->iID; + resp.iEmoteCode = chat->iEmoteCode; + sendToGroup(otherPlr, (void*)&resp, P_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, sizeof(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC)); +} + +void GroupManager::sendToGroup(Player* plr, void* buf, uint32_t type, size_t size) { + for (int i = 0; i < plr->groupCnt; i++) { + CNSocket* sock = PlayerManager::getSockFromID(plr->groupIDs[i]); + + if (sock == nullptr) + continue; + + if (type == P_FE2CL_PC_GROUP_LEAVE_SUCC) { + Player* leavingPlr = PlayerManager::getPlayer(sock); + leavingPlr->iIDGroup = leavingPlr->iID; + } + + sock->sendPacket(buf, type, size); + } +} + +void GroupManager::groupTickInfo(Player* plr) { + if (!validOutVarPacket(sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO), plr->groupCnt, sizeof(sPCGroupMemberInfo))) { + std::cout << "[WARN] bad sP_FE2CL_PC_GROUP_JOIN packet size\n"; + return; + } + + size_t resplen = sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO) + plr->groupCnt * sizeof(sPCGroupMemberInfo); + uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; + + memset(respbuf, 0, resplen); + + sP_FE2CL_PC_GROUP_MEMBER_INFO *resp = (sP_FE2CL_PC_GROUP_MEMBER_INFO*)respbuf; + sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_MEMBER_INFO)); + + resp->iID = plr->iID; + resp->iMemberPCCnt = plr->groupCnt; + + for (int i = 0; i < plr->groupCnt; i++) { + Player* varPlr = PlayerManager::getPlayerFromID(plr->groupIDs[i]); + + if (varPlr == nullptr) + continue; + + respdata[i].iPC_ID = varPlr->iID; + respdata[i].iPCUID = varPlr->PCStyle.iPC_UID; + respdata[i].iNameCheck = varPlr->PCStyle.iNameCheck; + memcpy(respdata[i].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName)); + memcpy(respdata[i].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName)); + respdata[i].iSpecialState = varPlr->iSpecialState; + respdata[i].iLv = varPlr->level; + respdata[i].iHP = varPlr->HP; + respdata[i].iMaxHP = PC_MAXHEALTH(varPlr->level); + //respdata[i].iMapType = 0; + //respdata[i].iMapNum = 0; + respdata[i].iX = varPlr->x; + respdata[i].iY = varPlr->y; + respdata[i].iZ = varPlr->z; + if (varPlr->activeNano > 0) { + respdata[i].bNano = 1; + respdata[i].Nano = varPlr->Nanos[varPlr->activeNano]; + } + } + + sendToGroup(plr, (void*)&respbuf, P_FE2CL_PC_GROUP_MEMBER_INFO, resplen); +} + +void GroupManager::groupKickPlayer(Player* plr) { + // if you are the group leader, destroy your own group and kick everybody + if (plr->iID == plr->iIDGroup) { + INITSTRUCT(sP_FE2CL_PC_GROUP_LEAVE_SUCC, resp1); + sendToGroup(plr, (void*)&resp1, P_FE2CL_PC_GROUP_LEAVE_SUCC, sizeof(sP_FE2CL_PC_GROUP_LEAVE_SUCC)); + plr->groupCnt = 1; + return; + } + + Player* otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup); + + if (otherPlr == nullptr) + 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"; + return; + } + + size_t resplen = sizeof(sP_FE2CL_PC_GROUP_LEAVE) + (otherPlr->groupCnt - 1) * sizeof(sPCGroupMemberInfo); + uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; + + memset(respbuf, 0, resplen); + + sP_FE2CL_PC_GROUP_LEAVE *resp = (sP_FE2CL_PC_GROUP_LEAVE*)respbuf; + sPCGroupMemberInfo *respdata = (sPCGroupMemberInfo*)(respbuf+sizeof(sP_FE2CL_PC_GROUP_LEAVE)); + + resp->iID_LeaveMember = plr->iID; + resp->iMemberPCCnt = otherPlr->groupCnt - 1; + + int moveDown = 0; + + for (int i = 0; i < otherPlr->groupCnt; i++) { + Player* varPlr = PlayerManager::getPlayerFromID(otherPlr->groupIDs[i]); + + if (varPlr == nullptr) + continue; + + if (moveDown == 1) + otherPlr->groupIDs[i-1] = otherPlr->groupIDs[i]; + + respdata[i-moveDown].iPC_ID = varPlr->iID; + respdata[i-moveDown].iPCUID = varPlr->PCStyle.iPC_UID; + respdata[i-moveDown].iNameCheck = varPlr->PCStyle.iNameCheck; + memcpy(respdata[i-moveDown].szFirstName, varPlr->PCStyle.szFirstName, sizeof(varPlr->PCStyle.szFirstName)); + memcpy(respdata[i-moveDown].szLastName, varPlr->PCStyle.szLastName, sizeof(varPlr->PCStyle.szLastName)); + respdata[i-moveDown].iSpecialState = varPlr->iSpecialState; + respdata[i-moveDown].iLv = varPlr->level; + respdata[i-moveDown].iHP = varPlr->HP; + respdata[i-moveDown].iMaxHP = PC_MAXHEALTH(varPlr->level); + //respdata[i-moveDown]].iMapType = 0; + //respdata[i-moveDown]].iMapNum = 0; + respdata[i-moveDown].iX = varPlr->x; + respdata[i-moveDown].iY = varPlr->y; + respdata[i-moveDown].iZ = varPlr->z; + // client doesnt read nano data here + + if (varPlr == plr) { + moveDown = 1; + otherPlr->groupIDs[i] = 0; + } + } + + plr->iIDGroup = plr->iID; + otherPlr->groupCnt -= 1; + + sendToGroup(otherPlr, (void*)&respbuf, P_FE2CL_PC_GROUP_LEAVE, resplen); + + CNSocket* sock = PlayerManager::getSockFromID(plr->iID); + + if (sock == nullptr) + 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)); +} diff --git a/src/GroupManager.hpp b/src/GroupManager.hpp new file mode 100644 index 0000000..6fb87c0 --- /dev/null +++ b/src/GroupManager.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "Player.hpp" +#include "CNProtocol.hpp" +#include "CNStructs.hpp" +#include "CNShardServer.hpp" + +#include +#include + +namespace GroupManager { + void init(); + + void requestGroup(CNSocket* sock, CNPacketData* data); + void refuseGroup(CNSocket* sock, CNPacketData* data); + void joinGroup(CNSocket* sock, CNPacketData* data); + void leaveGroup(CNSocket* sock, CNPacketData* data); + void chatGroup(CNSocket* sock, CNPacketData* data); + void menuChatGroup(CNSocket* sock, CNPacketData* data); + void sendToGroup(Player* plr, void* buf, uint32_t type, size_t size); + void groupTickInfo(Player* plr); + void groupKickPlayer(Player* plr); +} diff --git a/src/ItemManager.cpp b/src/ItemManager.cpp index 3a8ffc5..51d1153 100644 --- a/src/ItemManager.cpp +++ b/src/ItemManager.cpp @@ -911,3 +911,16 @@ void ItemManager::setItemStats(Player* plr) { plr->defense += itemStatsDat->defense; } } + +// HACK: work around the invisible weapon bug +void ItemManager::updateEquips(CNSocket* sock, Player* plr) { + for (int i = 0; i < 4; i++) { + INITSTRUCT(sP_FE2CL_PC_EQUIP_CHANGE, resp); + + resp.iPC_ID = plr->iID; + resp.iEquipSlotNum = i; + resp.EquipSlotItem = plr->Equip[i]; + + PlayerManager::sendToViewable(sock, (void*)&resp, P_FE2CL_PC_EQUIP_CHANGE, sizeof(sP_FE2CL_PC_EQUIP_CHANGE)); + } +} diff --git a/src/ItemManager.hpp b/src/ItemManager.hpp index feb96e2..1297b08 100644 --- a/src/ItemManager.hpp +++ b/src/ItemManager.hpp @@ -50,4 +50,5 @@ namespace ItemManager { Item* getItemData(int32_t id, int32_t type); void checkItemExpire(CNSocket* sock, Player* player); void setItemStats(Player* plr); + void updateEquips(CNSocket* sock, Player* plr); } diff --git a/src/MobManager.cpp b/src/MobManager.cpp index 456bc63..4f53011 100644 --- a/src/MobManager.cpp +++ b/src/MobManager.cpp @@ -4,6 +4,7 @@ #include "NPCManager.hpp" #include "ItemManager.hpp" #include "MissionManager.hpp" +#include "GroupManager.hpp" #include #include @@ -95,7 +96,7 @@ void MobManager::pcAttackNpcs(CNSocket *sock, CNPacketData *data) { PlayerManager::sendToViewable(sock, (void*)respbuf, P_FE2CL_PC_ATTACK_NPCs, resplen); } -void MobManager::npcAttackPc(Mob *mob) { +void MobManager::npcAttackPc(Mob *mob, time_t currTime) { Player *plr = PlayerManager::getPlayer(mob->target); if (plr == nullptr) @@ -126,6 +127,7 @@ void MobManager::npcAttackPc(Mob *mob) { if (plr->HP <= 0) { mob->target = nullptr; mob->state = MobState::RETREAT; + aggroCheck(mob, currTime); } } @@ -267,8 +269,10 @@ void MobManager::combatStep(Mob *mob, time_t currTime) { if (PlayerManager::players.find(mob->target) == PlayerManager::players.end()) { mob->target = nullptr; mob->state = MobState::RETREAT; + aggroCheck(mob, currTime); return; } + Player *plr = PlayerManager::getPlayer(mob->target); if (plr == nullptr) @@ -278,6 +282,7 @@ void MobManager::combatStep(Mob *mob, time_t currTime) { if (plr->HP <= 0) { mob->target = nullptr; mob->state = MobState::RETREAT; + aggroCheck(mob, currTime); return; } @@ -291,10 +296,10 @@ void MobManager::combatStep(Mob *mob, time_t currTime) { // attack logic if (mob->nextAttack == 0) { mob->nextAttack = currTime + (int)mob->data["m_iInitalTime"] * 100; // I *think* this is what this is - npcAttackPc(mob); + npcAttackPc(mob, currTime); } else if (mob->nextAttack != 0 && currTime >= mob->nextAttack) { mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100; - npcAttackPc(mob); + npcAttackPc(mob, currTime); } } else { // movement logic @@ -344,31 +349,9 @@ void MobManager::roamingStep(Mob *mob, time_t currTime) { * do so more often than if we waited for nextMovement (which is way too slow). */ if (mob->nextAttack == 0 || currTime >= mob->nextAttack) { - mob->nextAttack = currTime + (int)mob->data["m_iDelayTime"] * 100; - - /* - * Aggro on nearby players. - * Even if they're in range, we can't assume they're all in the same one chunk - * as the mob, since it might be near a chunk boundary. - */ - for (Chunk *chunk : mob->currentChunks) { - for (CNSocket *s : chunk->players) { - Player *plr = s->plr; - - // height is relevant for aggro distance because of platforming - int xyDistance = hypot(mob->appearanceData.iX - plr->x, mob->appearanceData.iY - plr->y); - int distance = hypot(xyDistance, mob->appearanceData.iZ - plr->z); - if (distance > mob->data["m_iSightRange"]) - continue; - - // found player. engage. - mob->target = s; - mob->state = MobState::COMBAT; - mob->nextMovement = currTime; - mob->nextAttack = 0; - return; - } - } + mob->nextAttack = currTime + 500; + if (aggroCheck(mob, currTime)) + return; } // some mobs don't move (and we mustn't divide/modulus by zero) @@ -439,15 +422,11 @@ void MobManager::retreatStep(Mob *mob, time_t currTime) { mob->nextAttack = 0; mob->appearanceData.iConditionBitFlag = 0; - //INITSTRUCT(sP_FE2CL_NPC_ENTER, enterData); - //enterData.NPCAppearanceData = mob->appearanceData; - //NPCManager::sendToViewable(mob, &enterData, P_FE2CL_NPC_ENTER, sizeof(sP_FE2CL_NPC_ENTER)); resendMobHP(mob); } } void MobManager::step(CNServer *serv, time_t currTime) { - for (auto& pair : Mobs) { int x = pair.second->appearanceData.iX; int y = pair.second->appearanceData.iY; @@ -525,8 +504,21 @@ std::pair MobManager::lerp(int x1, int y1, int x2, int y2, int speed) { void MobManager::combatBegin(CNSocket *sock, CNPacketData *data) { Player *plr = PlayerManager::getPlayer(sock); - if (plr != nullptr) - plr->inCombat = true; + if (plr == nullptr) { + std::cout << "[WARN] combatBegin: null player!" << std::endl; + return; + } + + plr->inCombat = true; + + // HACK: make sure the player has the right weapon out for combat + INITSTRUCT(sP_FE2CL_PC_EQUIP_CHANGE, resp); + + resp.iPC_ID = plr->iID; + resp.iEquipSlotNum = 0; + resp.EquipSlotItem = plr->Equip[0]; + + PlayerManager::sendToViewable(sock, (void*)&resp, P_FE2CL_PC_EQUIP_CHANGE, sizeof(sP_FE2CL_PC_EQUIP_CHANGE)); } void MobManager::combatEnd(CNSocket *sock, CNPacketData *data) { @@ -540,26 +532,58 @@ void MobManager::dotDamageOnOff(CNSocket *sock, CNPacketData *data) { sP_CL2FE_DOT_DAMAGE_ONOFF *pkt = (sP_CL2FE_DOT_DAMAGE_ONOFF*)data->buf; Player *plr = PlayerManager::getPlayer(sock); - if (plr != nullptr) - plr->dotDamage = (bool)pkt->iFlag; + if (plr == nullptr) { + std::cout << "[WARN] dotDamageOnOff: null player!" << std::endl; + return; + } + + if ((plr->iConditionBitFlag & CSB_BIT_INFECTION) != (bool)pkt->iFlag) + plr->iConditionBitFlag ^= CSB_BIT_INFECTION; + + INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt1); + + pkt1.eCSTB = ECSB_INFECTION; //eCharStatusTimeBuffID + pkt1.eTBU = 1; //eTimeBuffUpdate + pkt1.eTBT = 0; //eTimeBuffType 1 means nano + pkt1.iConditionBitFlag = plr->iConditionBitFlag; + + sock->sendPacket((void*)&pkt1, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE)); } void MobManager::dealGooDamage(CNSocket *sock, int amount) { size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_DotDamage); assert(resplen < CN_PACKET_BUFFER_SIZE - 8); uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; + Player *plr = PlayerManager::getPlayer(sock); + + if (plr == nullptr) + return; memset(respbuf, 0, resplen); sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf; sSkillResult_DotDamage *dmg = (sSkillResult_DotDamage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK)); - Player *plr = PlayerManager::getPlayer(sock); - if (plr == nullptr) - return; + if (plr->iConditionBitFlag & CSB_BIT_PROTECT_INFECTION) { + amount = -2; // -2 is the magic number for "Protected" to appear as the damage number + dmg->bProtected = 1; - // update player - plr->HP -= amount; + // it's hypothetically possible to have the protection bit without a nano + if (plr->activeNano != -1) + plr->Nanos[plr->activeNano].iStamina -= 3; + } else { + plr->HP -= amount; + } + + if (plr->activeNano != -1) { + dmg->iStamina = plr->Nanos[plr->activeNano].iStamina; + + if (plr->Nanos[plr->activeNano].iStamina <= 0) { + dmg->bNanoDeactive = 1; + plr->Nanos[plr->activeNano].iStamina = 0; + NanoManager::summonNano(PlayerManager::getSockFromID(plr->iID), -1); + } + } pkt->iID = plr->iID; pkt->eCT = 1; // player @@ -569,6 +593,7 @@ void MobManager::dealGooDamage(CNSocket *sock, int amount) { dmg->iID = plr->iID; dmg->iDamage = amount; dmg->iHP = plr->HP; + dmg->iConditionBitFlag = plr->iConditionBitFlag; sock->sendPacket((void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen); PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen); @@ -581,13 +606,17 @@ void MobManager::playerTick(CNServer *serv, time_t currTime) { CNSocket *sock = pair.first; Player *plr = pair.second.plr; bool transmit = false; + + // group ticks + if (plr->groupCnt > 1) + GroupManager::groupTickInfo(plr); // do not tick dead players if (plr->HP <= 0) continue; - + // fm patch/lake damage - if (plr->dotDamage) + if (plr->iConditionBitFlag & CSB_BIT_INFECTION) dealGooDamage(sock, PC_MAXHEALTH(plr->level) * 3 / 20); // heal @@ -605,8 +634,10 @@ void MobManager::playerTick(CNServer *serv, time_t currTime) { if (plr->passiveNanoOut) plr->Nanos[plr->activeNano].iStamina -= 1; - if (plr->Nanos[plr->activeNano].iStamina < 0) + if (plr->Nanos[plr->activeNano].iStamina <= 0) { + plr->Nanos[plr->activeNano].iStamina = 0; NanoManager::summonNano(PlayerManager::getSockFromID(plr->iID), -1); + } transmit = true; } else if (plr->Nanos[plr->equippedNanos[i]].iStamina < 150) { // regain stamina @@ -754,6 +785,7 @@ 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); assert(resplen < CN_PACKET_BUFFER_SIZE - 8); @@ -775,3 +807,43 @@ void MobManager::resendMobHP(Mob *mob) { NPCManager::sendToViewable(mob, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen); } + +/* + * Aggro on nearby players. + * Even if they're in range, we can't assume they're all in the same one chunk + * as the mob, since it might be near a chunk boundary. + */ +bool MobManager::aggroCheck(Mob *mob, time_t currTime) { + for (Chunk *chunk : mob->currentChunks) { + for (CNSocket *s : chunk->players) { + Player *plr = s->plr; + + if (plr->HP <= 0) + continue; + + int mobRange = mob->data["m_iSightRange"]; + + if (plr->iConditionBitFlag & CSB_BIT_UP_STEALTH) + mobRange /= 3; + + if (plr->iSpecialState & CN_SPECIAL_STATE_FLAG__INVISIBLE) + mobRange = -1; + + // height is relevant for aggro distance because of platforming + int xyDistance = hypot(mob->appearanceData.iX - plr->x, mob->appearanceData.iY - plr->y); + int distance = hypot(xyDistance, mob->appearanceData.iZ - plr->z); + + if (distance > mobRange) + continue; + + // found player. engage. + mob->target = s; + mob->state = MobState::COMBAT; + mob->nextMovement = currTime; + mob->nextAttack = 0; + return true; + } + } + + return false; +} diff --git a/src/MobManager.hpp b/src/MobManager.hpp index 61b2ac1..2d446cd 100644 --- a/src/MobManager.hpp +++ b/src/MobManager.hpp @@ -101,7 +101,7 @@ namespace MobManager { void dotDamageOnOff(CNSocket *sock, CNPacketData *data); void dealGooDamage(CNSocket *sock, int amount); - void npcAttackPc(Mob *mob); + void npcAttackPc(Mob *mob, time_t currTime); int hitMob(CNSocket *sock, Mob *mob, int damage); void killMob(CNSocket *sock, Mob *mob); void giveReward(CNSocket *sock); @@ -110,4 +110,5 @@ namespace MobManager { void pcAttackChars(CNSocket *sock, CNPacketData *data); void resendMobHP(Mob *mob); + bool aggroCheck(Mob *mob, time_t currTime); } diff --git a/src/NanoManager.cpp b/src/NanoManager.cpp index 1a5f1ee..c49e967 100644 --- a/src/NanoManager.cpp +++ b/src/NanoManager.cpp @@ -10,7 +10,8 @@ namespace NanoManager { // active powers std::set StunPowers = {1, 13, 42, 59, 78, 103}; -std::set HealPowers = {2, 7, 12, 38, 53, 61, 82, 92, 98}; +std::set HealPowers = {7, 12, 38, 53, 92, 98}; +std::set GroupHealPowers = {2, 61, 82}; std::set RecallPowers = {5, 25, 66, 69, 75, 87}; std::set DrainPowers = {10, 34, 37, 56, 93, 97}; std::set SnarePowers = {17, 18, 27, 41, 43, 47, 90, 96, 106}; @@ -21,15 +22,19 @@ std::set SleepPowers = {28, 30, 32, 49, 70, 71, 81, 85, 94}; // passive powers std::set ScavangePowers = {3, 50, 99}; -std::set RunPowers = {4, 8, 62, 68, 73, 86}; +std::set RunPowers = {4, 68, 86}; +std::set GroupRunPowers = {8, 62, 73}; std::set BonusPowers = {6, 54, 104}; std::set GuardPowers = {9, 57, 76}; std::set RadarPowers = {11, 67, 95}; std::set AntidotePowers = {14, 58, 102}; -std::set FreedomPowers = {15, 31, 39, 55, 77, 107}; -std::set JumpPowers = {16, 35, 44, 60, 88, 100}; +std::set FreedomPowers = {31, 39, 107}; +std::set GroupFreedomPowers = {15, 55, 77}; +std::set JumpPowers = {16, 44, 88}; +std::set GroupJumpPowers = {35, 60, 100}; std::set SelfRevivePowers = {22, 48, 83}; -std::set SneakPowers = {23, 29, 65, 72, 80, 82}; +std::set SneakPowers = {29, 72, 80}; +std::set GroupSneakPowers = {23, 65, 84}; std::set TreasureFinderPowers = {26, 40, 74}; /* @@ -147,14 +152,33 @@ void NanoManager::nanoSkillUseHandler(CNSocket* sock, CNPacketData* data) { int16_t nanoId = plr->activeNano; int16_t skillId = plr->Nanos[nanoId].iSkillID; + + DEBUGLOG( + std::cout << U16toU8(plr->PCStyle.szFirstName) << U16toU8(plr->PCStyle.szLastName) << " requested to summon nano skill " << std::endl; + ) for (auto& pwr : ActivePowers) if (pwr.powers.count(skillId)) // std::set's contains method is C++20 only... pwr.handle(sock, data, nanoId, skillId); + + // Group Revive is handled separately (XXX: move into table?) + if (GroupRevivePowers.find(skillId) == GroupRevivePowers.end()) + return; - DEBUGLOG( - std::cout << U16toU8(plr->PCStyle.szFirstName) << U16toU8(plr->PCStyle.szLastName) << " requested to summon nano skill " << std::endl; - ) + Player *leader = PlayerManager::getPlayerFromID(plr->iIDGroup); + + if (leader == nullptr) + return; + + for (int i = 0; i < leader->groupCnt; i++) { + Player* varPlr = PlayerManager::getPlayerFromID(leader->groupIDs[i]); + + if (varPlr == nullptr) + return; + + if (varPlr->HP <= 0) + revivePlayer(varPlr); + } } void NanoManager::nanoSkillSetHandler(CNSocket* sock, CNPacketData* data) { @@ -285,7 +309,7 @@ void NanoManager::summonNano(CNSocket *sock, int slot) { if (plr->activeNano > 0) for (auto& pwr : PassivePowers) if (pwr.powers.count(plr->Nanos[plr->activeNano].iSkillID)) { // std::set's contains method is C++20 only... - nanoUnbuff(sock, pwr.iCBFlag, pwr.eCharStatusTimeBuffID, pwr.iValue); + nanoUnbuff(sock, pwr.iCBFlag, pwr.eCharStatusTimeBuffID, pwr.iValue, pwr.groupPower); plr->passiveNanoOut = false; } @@ -298,7 +322,7 @@ void NanoManager::summonNano(CNSocket *sock, int slot) { for (auto& pwr : PassivePowers) if (pwr.powers.count(skillId)) { // std::set's contains method is C++20 only... resp.eCSTB___Add = 1; - nanoBuff(sock, nanoId, skillId, pwr.eSkillType, pwr.iCBFlag, pwr.eCharStatusTimeBuffID, pwr.iValue); + nanoBuff(sock, nanoId, skillId, pwr.eSkillType, pwr.iCBFlag, pwr.eCharStatusTimeBuffID, pwr.iValue, pwr.groupPower); plr->passiveNanoOut = true; } } else @@ -444,6 +468,43 @@ bool doHeal(CNSocket *sock, int32_t *pktdata, sSkillResult_Heal_HP *respdata, in return true; } +bool doGroupHeal(CNSocket *sock, int32_t *pktdata, sSkillResult_Heal_HP *respdata, int i, int32_t iCBFlag, int32_t amount) { + Player *plr = nullptr; + + for (auto& pair : PlayerManager::players) { + if (pair.second.plr->iID == pktdata[0]) { + plr = pair.second.plr; + break; + } + } + + // player not found + if (plr == nullptr) + return false; + + Player *leader = PlayerManager::getPlayer(sock); + + // player not found + if (leader == nullptr) + return false; + + int healedAmount = PC_MAXHEALTH(plr->level) * amount / 100; + + leader->HP += healedAmount; + + if (leader->HP > PC_MAXHEALTH(leader->level)) + leader->HP = PC_MAXHEALTH(leader->level); + + respdata[i].eCT = 1; + respdata[i].iID = plr->iID; + respdata[i].iHP = plr->HP; + respdata[i].iHealHP = healedAmount; + + std::cout << (int)plr->iID << " was healed" << std::endl; + + return true; +} + bool doDamage(CNSocket *sock, int32_t *pktdata, sSkillResult_Damage *respdata, int i, int32_t iCBFlag, int32_t amount) { if (MobManager::Mobs.find(pktdata[i]) == MobManager::Mobs.end()) { // not sure how to best handle this @@ -521,9 +582,16 @@ bool doLeech(CNSocket *sock, int32_t *pktdata, sSkillResult_Heal_HP *healdata, i return true; } +// XXX: Special flags. This is still pretty dirty. +enum { + NONE, + LEECH, + GHEAL +}; + template + bool (*work)(CNSocket*,int32_t*,sPAYLOAD*,int,int32_t,int32_t), + int specialCase=NONE> void activePower(CNSocket *sock, CNPacketData *data, int16_t nanoId, int16_t skillId, int16_t eSkillType, int32_t iCBFlag, int32_t amount) { @@ -538,33 +606,44 @@ void activePower(CNSocket *sock, CNPacketData *data, int32_t *pktdata = (int32_t*)((uint8_t*)data->buf + sizeof(sP_CL2FE_REQ_NANO_SKILL_USE)); + size_t resplen; + + Player *plr = PlayerManager::getPlayer(sock); + Player *otherPlr = plr; + + if (plr == nullptr) + return; + + // special case since leech is atypically encoded + if constexpr (specialCase == LEECH) + resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + sizeof(sSkillResult_Heal_HP) + sizeof(sSkillResult_Damage); + else if constexpr (specialCase == GHEAL) { + otherPlr = PlayerManager::getPlayerFromID(plr->iIDGroup); + + if (otherPlr == nullptr) + return; + + pkt->iTargetCnt = otherPlr->groupCnt; + } + + resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + pkt->iTargetCnt * sizeof(sPAYLOAD); + // validate response packet if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC), pkt->iTargetCnt, sizeof(sPAYLOAD))) { std::cout << "[WARN] bad sP_FE2CL_NANO_SKILL_USE packet size" << std::endl; return; } - size_t resplen; - - // special case since leech is atypically encoded - if constexpr (isLeech) - resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + sizeof(sSkillResult_Heal_HP) + sizeof(sSkillResult_Damage); - else - resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC) + pkt->iTargetCnt * sizeof(sPAYLOAD); - 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)); - - Player *plr = PlayerManager::getPlayer(sock); - - if (plr == nullptr) - return; plr->Nanos[plr->activeNano].iStamina -= 40; + if (plr->Nanos[plr->activeNano].iStamina < 0) + plr->Nanos[plr->activeNano].iStamina = 0; resp->iPC_ID = plr->iID; resp->iSkillID = skillId; @@ -573,8 +652,13 @@ void activePower(CNSocket *sock, CNPacketData *data, resp->eST = eSkillType; resp->iTargetCnt = pkt->iTargetCnt; + CNSocket *workSock = sock; + for (int i = 0; i < pkt->iTargetCnt; i++) { - if (!work(sock, pktdata, respdata, i, iCBFlag, amount)) + if constexpr (specialCase == GHEAL) + workSock = PlayerManager::getSockFromID(otherPlr->groupIDs[i]); + + if (!work(workSock, pktdata, respdata, i, iCBFlag, amount)) return; } @@ -586,12 +670,12 @@ void activePower(CNSocket *sock, CNPacketData *data, std::vector ActivePowers = { ActivePower(StunPowers, activePower, EST_STUN, CSB_BIT_STUN, 0), ActivePower(HealPowers, activePower, EST_HEAL_HP, CSB_BIT_NONE, 25), + ActivePower(GroupHealPowers, activePower,EST_HEAL_HP, CSB_BIT_NONE, 25), // TODO: Recall ActivePower(DrainPowers, activePower, EST_BOUNDINGBALL, CSB_BIT_BOUNDINGBALL, 0), ActivePower(SnarePowers, activePower, EST_SNARE, CSB_BIT_DN_MOVE_SPEED, 0), ActivePower(DamagePowers, activePower, EST_DAMAGE, CSB_BIT_NONE, 12), - // TODO: GroupRevive - ActivePower(LeechPowers, activePower, EST_BLOODSUCKING, CSB_BIT_NONE, 18), + ActivePower(LeechPowers, activePower, EST_BLOODSUCKING, CSB_BIT_NONE, 18), ActivePower(SleepPowers, activePower, EST_SLEEP, CSB_BIT_MEZ, 0), }; @@ -599,18 +683,32 @@ std::vector ActivePowers = { #pragma endregion #pragma region Passive Powers -void NanoManager::nanoBuff(CNSocket* sock, int16_t nanoId, int skillId, int16_t eSkillType, int32_t iCBFlag, int16_t eCharStatusTimeBuffID, int16_t iValue) { +void NanoManager::nanoBuff(CNSocket* sock, int16_t nanoId, int skillId, int16_t eSkillType, int32_t iCBFlag, int16_t eCharStatusTimeBuffID, int16_t iValue, bool groupPower) { Player *plr = PlayerManager::getPlayer(sock); + Player *leader; if (plr == nullptr) return; + + if (plr->iID == plr->iIDGroup) + leader = plr; + else + leader = PlayerManager::getPlayerFromID(plr->iIDGroup); + + if (leader == nullptr) + return; + + int pktCnt = 1; + + if (groupPower) + pktCnt = leader->groupCnt; - if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE), 1, sizeof(sSkillResult_Buff))) { + if (!validOutVarPacket(sizeof(sP_FE2CL_NANO_SKILL_USE), pktCnt, sizeof(sSkillResult_Buff))) { std::cout << "[WARN] bad sP_FE2CL_NANO_SKILL_USE packet size\n"; return; } - size_t resplen = sizeof(sP_FE2CL_NANO_SKILL_USE) + sizeof(sSkillResult_Buff); + size_t resplen = sizeof(sP_FE2CL_NANO_SKILL_USE) + pktCnt * sizeof(sSkillResult_Buff); uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; memset(respbuf, 0, resplen); @@ -623,16 +721,28 @@ void NanoManager::nanoBuff(CNSocket* sock, int16_t nanoId, int skillId, int16_t resp->iNanoID = nanoId; resp->iNanoStamina = plr->Nanos[plr->activeNano].iStamina; resp->eST = eSkillType; - resp->iTargetCnt = 1; + resp->iTargetCnt = pktCnt; - // this looks stupid but in the future there will be more counts (for group powers) - for (int i = 0; i < 1; i++) { + for (int i = 0; i < pktCnt; i++) { + Player* varPlr; + CNSocket* sockTo; - if (!(plr->iConditionBitFlag & iCBFlag)) - plr->iConditionBitFlag ^= iCBFlag; + if (plr->iID == leader->groupIDs[i]) { + varPlr = plr; + sockTo = sock; + } else { + varPlr = PlayerManager::getPlayerFromID(leader->groupIDs[i]); + sockTo = PlayerManager::getSockFromID(leader->groupIDs[i]); + } + + if (varPlr == nullptr || sockTo == nullptr) + return; + + if (!(varPlr->iConditionBitFlag & iCBFlag)) + varPlr->iConditionBitFlag ^= iCBFlag; respdata[i].eCT = 1; - respdata[i].iID = plr->iID; + respdata[i].iID = varPlr->iID; respdata[i].iConditionBitFlag = iCBFlag; INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, pkt1); @@ -640,38 +750,65 @@ void NanoManager::nanoBuff(CNSocket* sock, int16_t nanoId, int skillId, int16_t pkt1.eCSTB = eCharStatusTimeBuffID; //eCharStatusTimeBuffID pkt1.eTBU = 1; //eTimeBuffUpdate pkt1.eTBT = 1; //eTimeBuffType 1 means nano - pkt1.iConditionBitFlag = plr->iConditionBitFlag; + pkt1.iConditionBitFlag = varPlr->iConditionBitFlag; if (iValue > 0) pkt1.TimeBuff.iValue = iValue; - sock->sendPacket((void*)&pkt1, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE)); + sockTo->sendPacket((void*)&pkt1, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE)); } sock->sendPacket((void*)&respbuf, P_FE2CL_NANO_SKILL_USE_SUCC, resplen); PlayerManager::sendToViewable(sock, (void*)&respbuf, P_FE2CL_NANO_SKILL_USE, resplen); } -void NanoManager::nanoUnbuff(CNSocket* sock, int32_t iCBFlag, int16_t eCharStatusTimeBuffID, int16_t iValue) { +void NanoManager::nanoUnbuff(CNSocket* sock, int32_t iCBFlag, int16_t eCharStatusTimeBuffID, int16_t iValue, bool groupPower) { INITSTRUCT(sP_FE2CL_PC_BUFF_UPDATE, resp1); Player *plr = PlayerManager::getPlayer(sock); + Player *leader; if (plr == nullptr) return; - - if (plr->iConditionBitFlag & iCBFlag) - plr->iConditionBitFlag ^= iCBFlag; - resp1.eCSTB = eCharStatusTimeBuffID; //eCharStatusTimeBuffID - resp1.eTBU = 2; //eTimeBuffUpdate - resp1.eTBT = 1; //eTimeBuffType 1 means nano - resp1.iConditionBitFlag = plr->iConditionBitFlag; + if (plr->iID == plr->iIDGroup) + leader = plr; + else + leader = PlayerManager::getPlayerFromID(plr->iIDGroup); - if (iValue > 0) - resp1.TimeBuff.iValue = iValue; + if (leader == nullptr) + return; - sock->sendPacket((void*)&resp1, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE)); + int pktCnt = 1; + + if (groupPower) + pktCnt = leader->groupCnt; + + for (int i = 0; i < pktCnt; i++) { + Player* varPlr; + CNSocket* sockTo; + + if (plr->iID == leader->groupIDs[i]) { + varPlr = plr; + sockTo = sock; + } else { + varPlr = PlayerManager::getPlayerFromID(leader->groupIDs[i]); + sockTo = PlayerManager::getSockFromID(leader->groupIDs[i]); + } + + if (varPlr->iConditionBitFlag & iCBFlag) + varPlr->iConditionBitFlag ^= iCBFlag; + + resp1.eCSTB = eCharStatusTimeBuffID; //eCharStatusTimeBuffID + resp1.eTBU = 2; //eTimeBuffUpdate + resp1.eTBT = 1; //eTimeBuffType 1 means nano + resp1.iConditionBitFlag = varPlr->iConditionBitFlag; + + if (iValue > 0) + resp1.TimeBuff.iValue = iValue; + + sockTo->sendPacket((void*)&resp1, P_FE2CL_PC_BUFF_UPDATE, sizeof(sP_FE2CL_PC_BUFF_UPDATE)); + } } // 0=A 1=B 2=C -1=Not found @@ -684,18 +821,63 @@ int NanoManager::nanoStyle(int nanoId) { namespace NanoManager { std::vector PassivePowers = { - PassivePower(ScavangePowers, EST_REWARDBLOB, CSB_BIT_REWARD_BLOB, ECSB_REWARD_BLOB, 0), - PassivePower(RunPowers, EST_RUN, CSB_BIT_UP_MOVE_SPEED, ECSB_UP_MOVE_SPEED, 200), - PassivePower(BonusPowers, EST_REWARDCASH, CSB_BIT_REWARD_CASH, ECSB_REWARD_CASH, 0), - PassivePower(GuardPowers, EST_PROTECTBATTERY, CSB_BIT_PROTECT_BATTERY, ECSB_PROTECT_BATTERY, 0), - PassivePower(RadarPowers, EST_MINIMAPENEMY, CSB_BIT_MINIMAP_ENEMY, ECSB_MINIMAP_ENEMY, 0), - PassivePower(AntidotePowers, EST_PROTECTINFECTION, CSB_BIT_PROTECT_INFECTION, ECSB_PROTECT_INFECTION, 0), - PassivePower(FreedomPowers, EST_FREEDOM, CSB_BIT_FREEDOM, ECSB_FREEDOM, 0), - PassivePower(JumpPowers, EST_JUMP, CSB_BIT_UP_JUMP_HEIGHT, ECSB_UP_JUMP_HEIGHT, 400), - PassivePower(SelfRevivePowers, EST_PHOENIX, CSB_BIT_PHOENIX, ECSB_PHOENIX, 0), - PassivePower(SneakPowers, EST_STEALTH, CSB_BIT_UP_STEALTH, ECSB_UP_STEALTH, 0), - PassivePower(TreasureFinderPowers, EST_MINIMAPTRESURE, CSB_BIT_MINIMAP_TRESURE, ECSB_MINIMAP_TRESURE, 0), + PassivePower(ScavangePowers, EST_REWARDBLOB, CSB_BIT_REWARD_BLOB, ECSB_REWARD_BLOB, 0, false), + PassivePower(RunPowers, EST_RUN, CSB_BIT_UP_MOVE_SPEED, ECSB_UP_MOVE_SPEED, 200, false), + PassivePower(GroupRunPowers, EST_RUN, CSB_BIT_UP_MOVE_SPEED, ECSB_UP_MOVE_SPEED, 200, true), + PassivePower(BonusPowers, EST_REWARDCASH, CSB_BIT_REWARD_CASH, ECSB_REWARD_CASH, 0, false), + PassivePower(GuardPowers, EST_PROTECTBATTERY, CSB_BIT_PROTECT_BATTERY, ECSB_PROTECT_BATTERY, 0, false), + PassivePower(RadarPowers, EST_MINIMAPENEMY, CSB_BIT_MINIMAP_ENEMY, ECSB_MINIMAP_ENEMY, 0, false), + PassivePower(AntidotePowers, EST_PROTECTINFECTION, CSB_BIT_PROTECT_INFECTION, ECSB_PROTECT_INFECTION, 0, false), + PassivePower(FreedomPowers, EST_FREEDOM, CSB_BIT_FREEDOM, ECSB_FREEDOM, 0, false), + PassivePower(GroupFreedomPowers, EST_FREEDOM, CSB_BIT_FREEDOM, ECSB_FREEDOM, 0, true), + PassivePower(JumpPowers, EST_JUMP, CSB_BIT_UP_JUMP_HEIGHT, ECSB_UP_JUMP_HEIGHT, 400, false), + PassivePower(GroupJumpPowers, EST_JUMP, CSB_BIT_UP_JUMP_HEIGHT, ECSB_UP_JUMP_HEIGHT, 400, true), + PassivePower(SelfRevivePowers, EST_PHOENIX, CSB_BIT_PHOENIX, ECSB_PHOENIX, 0, false), + PassivePower(SneakPowers, EST_STEALTH, CSB_BIT_UP_STEALTH, ECSB_UP_STEALTH, 0, false), + PassivePower(GroupSneakPowers, EST_STEALTH, CSB_BIT_UP_STEALTH, ECSB_UP_STEALTH, 0, true), + PassivePower(TreasureFinderPowers, EST_MINIMAPTRESURE, CSB_BIT_MINIMAP_TRESURE, ECSB_MINIMAP_TRESURE, 0, false), }; }; // namespace + +void NanoManager::revivePlayer(Player* plr) { + CNSocket* sock = PlayerManager::getSockFromID(plr->iID); + + INITSTRUCT(sP_FE2CL_REP_PC_REGEN_SUCC, response); + INITSTRUCT(sP_FE2CL_PC_REGEN, resp2); + + plr->HP = PC_MAXHEALTH(plr->level); + + // Nanos + int activeSlot = -1; + for (int n = 0; n < 3; n++) { + int nanoID = plr->equippedNanos[n]; + if (plr->activeNano == nanoID) { + activeSlot = n; + } + } + + // Response parameters + response.PCRegenData.iActiveNanoSlotNum = activeSlot; + response.PCRegenData.iX = plr->x; + response.PCRegenData.iY = plr->y; + response.PCRegenData.iZ = plr->z; + response.PCRegenData.iHP = plr->HP; + response.iFusionMatter = plr->fusionmatter; + response.bMoveLocation = 0; + response.PCRegenData.iMapNum = 0; + + sock->sendPacket((void*)&response, P_FE2CL_REP_PC_REGEN_SUCC, sizeof(sP_FE2CL_REP_PC_REGEN_SUCC)); + + // Update other players + resp2.PCRegenDataForOtherPC.iPC_ID = plr->iID; + resp2.PCRegenDataForOtherPC.iX = plr->x; + resp2.PCRegenDataForOtherPC.iY = plr->y; + resp2.PCRegenDataForOtherPC.iZ = plr->z; + resp2.PCRegenDataForOtherPC.iHP = plr->HP; + resp2.PCRegenDataForOtherPC.iAngle = plr->angle; + resp2.PCRegenDataForOtherPC.Nano = plr->Nanos[plr->activeNano]; + + PlayerManager::sendToViewable(sock, (void*)&resp2, P_FE2CL_PC_REGEN, sizeof(sP_FE2CL_PC_REGEN)); +} #pragma endregion diff --git a/src/NanoManager.hpp b/src/NanoManager.hpp index 10bfeab..ee1ea59 100644 --- a/src/NanoManager.hpp +++ b/src/NanoManager.hpp @@ -30,8 +30,9 @@ struct PassivePower { int32_t iCBFlag; int16_t eCharStatusTimeBuffID; int16_t iValue; + bool groupPower; - PassivePower(std::set p, int16_t t, int32_t f, int16_t b, int16_t a) : powers(p), eSkillType(t), iCBFlag(f), eCharStatusTimeBuffID(b), iValue(a) {} + PassivePower(std::set p, int16_t t, int32_t f, int16_t b, int16_t a, bool g) : powers(p), eSkillType(t), iCBFlag(f), eCharStatusTimeBuffID(b), iValue(a), groupPower(g) {} }; struct NanoData { @@ -60,8 +61,9 @@ namespace NanoManager { void setNanoSkill(CNSocket* sock, int16_t nanoId, int16_t skillId); void resetNanoSkill(CNSocket* sock, int16_t nanoId); - void nanoBuff(CNSocket* sock, int16_t nanoId, int skillId, int16_t eSkillType, int32_t iCBFlag, int16_t eCharStatusTimeBuffID, int16_t iValue = 0); - void nanoUnbuff(CNSocket* sock, int32_t iCBFlag, int16_t eCharStatusTimeBuffID, int16_t iValue = 0); + void nanoBuff(CNSocket* sock, int16_t nanoId, int skillId, int16_t eSkillType, int32_t iCBFlag, int16_t eCharStatusTimeBuffID, int16_t iValue = 0, bool groupPower = false); + void nanoUnbuff(CNSocket* sock, int32_t iCBFlag, int16_t eCharStatusTimeBuffID, int16_t iValue = 0, bool groupPower = false); int nanoStyle(int nanoId); + void revivePlayer(Player* plr); } diff --git a/src/Player.hpp b/src/Player.hpp index 51b21c8..9319d6c 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -47,7 +47,6 @@ struct Player { bool isTradeConfirm; bool inCombat; - bool dotDamage; bool passiveNanoOut; int pointDamage; @@ -61,4 +60,8 @@ struct Player { int32_t CurrentMissionID; sTimeLimitItemDeleteInfo2CL toRemoveVehicle; + + int32_t iIDGroup; + int groupCnt; + int32_t groupIDs[4]; }; diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index 0081796..74cbb8a 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -5,6 +5,8 @@ #include "CNShared.hpp" #include "MissionManager.hpp" #include "ItemManager.hpp" +#include "NanoManager.hpp" +#include "GroupManager.hpp" #include "ChatManager.hpp" #include "settings.hpp" @@ -61,6 +63,8 @@ void PlayerManager::addPlayer(CNSocket* key, Player plr) { void PlayerManager::removePlayer(CNSocket* key) { PlayerView& view = players[key]; + + GroupManager::groupKickPlayer(view.plr); INITSTRUCT(sP_FE2CL_PC_EXIT, exitPacket); exitPacket.iID = players[key].plr->iID; @@ -242,6 +246,9 @@ void PlayerManager::enterPlayer(CNSocket* sock, CNPacketData* data) { // TODO: check if serialkey exists, if it doesn't send sP_FE2CL_REP_PC_ENTER_FAIL Player plr = CNSharedData::getPlayer(enter->iEnterSerialKey); + + plr.groupCnt = 1; + plr.iIDGroup = plr.groupIDs[0] = plr.iID; DEBUGLOG( std::cout << "P_CL2FE_REQ_PC_ENTER:" << std::endl; @@ -650,7 +657,6 @@ void PlayerManager::gotoPlayer(CNSocket* sock, CNPacketData* data) { sP_CL2FE_REQ_PC_GOTO* gotoData = (sP_CL2FE_REQ_PC_GOTO*)data->buf; INITSTRUCT(sP_FE2CL_REP_PC_GOTO_SUCC, response); - PlayerView& plrv = players[sock]; DEBUGLOG( std::cout << "P_CL2FE_REQ_PC_GOTO:" << std::endl; @@ -740,24 +746,36 @@ void PlayerManager::revivePlayer(CNSocket* sock, CNPacketData* data) { sP_CL2FE_REQ_PC_REGEN* reviveData = (sP_CL2FE_REQ_PC_REGEN*)data->buf; INITSTRUCT(sP_FE2CL_REP_PC_REGEN_SUCC, response); INITSTRUCT(sP_FE2CL_PC_REGEN, resp2); - - // Nanos + int activeSlot = -1; - for (int n = 0; n < 3; n++) { - int nanoID = plr->equippedNanos[n]; - plr->Nanos[nanoID].iStamina = 75; // max is 150, so 75 is half - response.PCRegenData.Nanos[n] = plr->Nanos[nanoID]; - if (plr->activeNano == nanoID) { - activeSlot = n; + + if (reviveData->iRegenType == 3 && plr->iConditionBitFlag & CSB_BIT_PHOENIX) { + // nano revive + plr->Nanos[plr->activeNano].iStamina = 0; + NanoManager::nanoUnbuff(sock, CSB_BIT_PHOENIX, ECSB_PHOENIX, 0, false); + plr->HP = PC_MAXHEALTH(plr->level); + } else { + plr->x = target.x; + plr->y = target.y; + plr->z = target.z; + + if (reviveData->iRegenType != 5) + plr->HP = PC_MAXHEALTH(plr->level); + + for (int i = 0; i < 3; i++) { + int nanoID = plr->equippedNanos[i]; + + // halve nano health if respawning + if (reviveData->iRegenType != 5) { + plr->Nanos[nanoID].iStamina = 75; // max is 150, so 75 is half + response.PCRegenData.Nanos[i] = plr->Nanos[nanoID]; + } + + if (plr->activeNano == nanoID) + activeSlot = i; } } - // Update player - plr->x = target.x; - plr->y = target.y; - plr->z = target.z; - plr->HP = PC_MAXHEALTH(plr->level); - // Response parameters response.PCRegenData.iActiveNanoSlotNum = activeSlot; response.PCRegenData.iX = plr->x; @@ -765,8 +783,8 @@ void PlayerManager::revivePlayer(CNSocket* sock, CNPacketData* data) { response.PCRegenData.iZ = plr->z; response.PCRegenData.iHP = plr->HP; response.iFusionMatter = plr->fusionmatter; - response.bMoveLocation = reviveData->eIL; - response.PCRegenData.iMapNum = reviveData->iIndex; + response.bMoveLocation = 0; + response.PCRegenData.iMapNum = 0; sock->sendPacket((void*)&response, P_FE2CL_REP_PC_REGEN_SUCC, sizeof(sP_FE2CL_REP_PC_REGEN_SUCC)); @@ -777,6 +795,9 @@ void PlayerManager::revivePlayer(CNSocket* sock, CNPacketData* data) { resp2.PCRegenDataForOtherPC.iZ = plr->z; resp2.PCRegenDataForOtherPC.iHP = plr->HP; resp2.PCRegenDataForOtherPC.iAngle = plr->angle; + resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->iConditionBitFlag; + resp2.PCRegenDataForOtherPC.iPCState = plr->iPCState; + resp2.PCRegenDataForOtherPC.iSpecialState = plr->iSpecialState; resp2.PCRegenDataForOtherPC.Nano = plr->Nanos[plr->activeNano]; sendToViewable(sock, (void*)&resp2, P_FE2CL_PC_REGEN, sizeof(sP_FE2CL_PC_REGEN)); @@ -929,8 +950,15 @@ void PlayerManager::setSpecialState(CNSocket* sock, CNPacketData* data) { Player *plr = getPlayer(sock); + if (plr == nullptr) + return; + sP_CL2FE_GM_REQ_PC_SPECIAL_STATE_SWITCH* setData = (sP_CL2FE_GM_REQ_PC_SPECIAL_STATE_SWITCH*)data->buf; + // HACK: work around the invisible weapon bug + if (setData->iSpecialStateFlag == CN_SPECIAL_STATE_FLAG__FULL_UI) + ItemManager::updateEquips(sock, plr); + INITSTRUCT(sP_FE2CL_PC_SPECIAL_STATE_CHANGE, response); plr->iSpecialState ^= setData->iSpecialStateFlag; @@ -943,7 +971,15 @@ void PlayerManager::setSpecialState(CNSocket* sock, CNPacketData* data) { sendToViewable(sock, (void*)&response, P_FE2CL_PC_SPECIAL_STATE_CHANGE, sizeof(sP_FE2CL_PC_SPECIAL_STATE_CHANGE)); } -CNSocket* PlayerManager::getSockFromID(int32_t iID) { +Player *PlayerManager::getPlayerFromID(int32_t iID) { + for (auto& pair : PlayerManager::players) + if (pair.second.plr->iID == iID) + return pair.second.plr; + + return nullptr; +} + +CNSocket *PlayerManager::getSockFromID(int32_t iID) { for (auto& pair : PlayerManager::players) if (pair.second.plr->iID == iID) return pair.first; diff --git a/src/PlayerManager.hpp b/src/PlayerManager.hpp index d331f18..21f100d 100644 --- a/src/PlayerManager.hpp +++ b/src/PlayerManager.hpp @@ -69,5 +69,6 @@ namespace PlayerManager { bool isAccountInUse(int accountId); void exitDuplicate(int accountId); void setSpecialState(CNSocket* sock, CNPacketData* data); - CNSocket* getSockFromID(int32_t iID); + Player *getPlayerFromID(int32_t iID); + CNSocket *getSockFromID(int32_t iID); } diff --git a/src/TransportManager.cpp b/src/TransportManager.cpp index 1ffaf92..e26b84f 100644 --- a/src/TransportManager.cpp +++ b/src/TransportManager.cpp @@ -1,6 +1,7 @@ #include "CNShardServer.hpp" #include "CNStructs.hpp" #include "PlayerManager.hpp" +#include "NanoManager.hpp" #include "TransportManager.hpp" #include @@ -153,6 +154,7 @@ void TransportManager::transportWarpHandler(CNSocket* sock, CNPacketData* data) break; case 2: // Monkey Skyway if (SkywayPaths.find(route.mssRouteNum) != SkywayPaths.end()) { // check if route exists + NanoManager::summonNano(sock, -1); // make sure that no nano is active during the ride SkywayQueues[sock] = SkywayPaths[route.mssRouteNum]; // set socket point queue to route break; } diff --git a/src/main.cpp b/src/main.cpp index 3ad96e4..a708743 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,6 +12,7 @@ #include "Database.hpp" #include "TableData.hpp" #include "ChunkManager.hpp" +#include "GroupManager.hpp" #include "settings.hpp" @@ -99,6 +100,7 @@ int main() { NPCManager::init(); TransportManager::init(); // BuddyManager::init(); // stubbed until we have database integration + lots of bug fixes + GroupManager::init(); Database::open();