Groups, Group Nano powers and Revive (#129)

* Initial Group Implementation

* Request/refuse/join and leave groups.
* Chat into groups.
* Get status updates on every group member each tick.
* Owner leaving the group destroys the entire group.

* Added more nano powers

* Revive for both variants work.
* Many nano powers now have a group variant working.
* Enemy checks for aggro before retreating.
* Enemies keep aggro on dead players with revive nanos out.

* Further Nano powers + Bugfixes

* Infection damage now relies on bitcondition flags.
* Antidote power now works.
* Improved how groups handle leaving players.
* Fixed mob aggro range.
* Group Healing is now functional.
* Possibly fixed the player being unselectable bug.
* Fixed indentations.

* Dismiss nano when starting a MSS ride

* Sneak, Invisibility and Bugfixes

* Sneak and invisibility affect mob aggro.
* Possibly bugfixed equips not showing to other players.
* Aggro checking is less likely to cause nullptr related crashes.

* Group PR cleanup.

* Made sure to label all hacky workarounds
* Implemented the Antidote nano power the right way
* Cleaned up the way various little things are written

Didn't have the opportunity to actually test groups.

Co-authored-by: CakeLancelot <CakeLancelot@users.noreply.github.com>
Co-authored-by: CPunch <sethtstubbs@gmail.com>
Co-authored-by: dongresource <dongresource@protonmail.com>
This commit is contained in:
JadeShrineMaiden 2020-10-05 00:54:08 +01:00 committed by GitHub
parent 131eb94919
commit b8f586bc10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 806 additions and 130 deletions

View File

@ -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)

View File

@ -1,4 +1,5 @@
#include "Database.hpp"
#include "Database.hpp"
#include "contrib/bcrypt/BCrypt.hpp"
#include "CNProtocol.hpp"
#include <string>
@ -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;

336
src/GroupManager.cpp Normal file
View File

@ -0,0 +1,336 @@
#include "CNShardServer.hpp"
#include "CNStructs.hpp"
#include "ChatManager.hpp"
#include "PlayerManager.hpp"
#include "GroupManager.hpp"
#include <iostream>
#include <chrono>
#include <algorithm>
#include <thread>
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));
}

23
src/GroupManager.hpp Normal file
View File

@ -0,0 +1,23 @@
#pragma once
#include "Player.hpp"
#include "CNProtocol.hpp"
#include "CNStructs.hpp"
#include "CNShardServer.hpp"
#include <map>
#include <list>
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);
}

View File

@ -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));
}
}

View File

@ -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);
}

View File

@ -4,6 +4,7 @@
#include "NPCManager.hpp"
#include "ItemManager.hpp"
#include "MissionManager.hpp"
#include "GroupManager.hpp"
#include <cmath>
#include <assert.h>
@ -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<int,int> 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;
}

View File

@ -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);
}

View File

@ -10,7 +10,8 @@ namespace NanoManager {
// active powers
std::set<int> StunPowers = {1, 13, 42, 59, 78, 103};
std::set<int> HealPowers = {2, 7, 12, 38, 53, 61, 82, 92, 98};
std::set<int> HealPowers = {7, 12, 38, 53, 92, 98};
std::set<int> GroupHealPowers = {2, 61, 82};
std::set<int> RecallPowers = {5, 25, 66, 69, 75, 87};
std::set<int> DrainPowers = {10, 34, 37, 56, 93, 97};
std::set<int> SnarePowers = {17, 18, 27, 41, 43, 47, 90, 96, 106};
@ -21,15 +22,19 @@ std::set<int> SleepPowers = {28, 30, 32, 49, 70, 71, 81, 85, 94};
// passive powers
std::set<int> ScavangePowers = {3, 50, 99};
std::set<int> RunPowers = {4, 8, 62, 68, 73, 86};
std::set<int> RunPowers = {4, 68, 86};
std::set<int> GroupRunPowers = {8, 62, 73};
std::set<int> BonusPowers = {6, 54, 104};
std::set<int> GuardPowers = {9, 57, 76};
std::set<int> RadarPowers = {11, 67, 95};
std::set<int> AntidotePowers = {14, 58, 102};
std::set<int> FreedomPowers = {15, 31, 39, 55, 77, 107};
std::set<int> JumpPowers = {16, 35, 44, 60, 88, 100};
std::set<int> FreedomPowers = {31, 39, 107};
std::set<int> GroupFreedomPowers = {15, 55, 77};
std::set<int> JumpPowers = {16, 44, 88};
std::set<int> GroupJumpPowers = {35, 60, 100};
std::set<int> SelfRevivePowers = {22, 48, 83};
std::set<int> SneakPowers = {23, 29, 65, 72, 80, 82};
std::set<int> SneakPowers = {29, 72, 80};
std::set<int> GroupSneakPowers = {23, 65, 84};
std::set<int> 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<class sPAYLOAD,
bool (*work)(CNSocket*,int32_t*,sPAYLOAD*,int,int32_t,int32_t),
bool isLeech=false>
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<ActivePower> ActivePowers = {
ActivePower(StunPowers, activePower<sSkillResult_Damage_N_Debuff, doDebuff>, EST_STUN, CSB_BIT_STUN, 0),
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, 25),
// TODO: Recall
ActivePower(DrainPowers, activePower<sSkillResult_Buff, doBuff>, EST_BOUNDINGBALL, CSB_BIT_BOUNDINGBALL, 0),
ActivePower(SnarePowers, activePower<sSkillResult_Damage_N_Debuff, doDebuff>, EST_SNARE, CSB_BIT_DN_MOVE_SPEED, 0),
ActivePower(DamagePowers, activePower<sSkillResult_Damage, doDamage>, EST_DAMAGE, CSB_BIT_NONE, 12),
// TODO: GroupRevive
ActivePower(LeechPowers, activePower<sSkillResult_Heal_HP, doLeech, true>, EST_BLOODSUCKING, CSB_BIT_NONE, 18),
ActivePower(LeechPowers, activePower<sSkillResult_Heal_HP, doLeech, LEECH>, EST_BLOODSUCKING, CSB_BIT_NONE, 18),
ActivePower(SleepPowers, activePower<sSkillResult_Damage_N_Debuff, doDebuff>, EST_SLEEP, CSB_BIT_MEZ, 0),
};
@ -599,18 +683,32 @@ std::vector<ActivePower> 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<PassivePower> 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

View File

@ -30,8 +30,9 @@ struct PassivePower {
int32_t iCBFlag;
int16_t eCharStatusTimeBuffID;
int16_t iValue;
bool groupPower;
PassivePower(std::set<int> 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<int> 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);
}

View File

@ -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];
};

View File

@ -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;

View File

@ -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);
}

View File

@ -1,6 +1,7 @@
#include "CNShardServer.hpp"
#include "CNStructs.hpp"
#include "PlayerManager.hpp"
#include "NanoManager.hpp"
#include "TransportManager.hpp"
#include <unordered_map>
@ -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;
}

View File

@ -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();