2020-08-18 20:42:30 +00:00
|
|
|
#include "PlayerManager.hpp"
|
2020-08-20 21:43:48 +00:00
|
|
|
#include "NPCManager.hpp"
|
2020-08-18 20:42:30 +00:00
|
|
|
#include "CNShardServer.hpp"
|
2021-03-16 22:29:13 +00:00
|
|
|
#include "Missions.hpp"
|
|
|
|
#include "Items.hpp"
|
|
|
|
#include "Nanos.hpp"
|
|
|
|
#include "Groups.hpp"
|
|
|
|
#include "Chat.hpp"
|
2021-03-16 01:06:54 +00:00
|
|
|
#include "db/Database.hpp"
|
2021-03-16 22:29:13 +00:00
|
|
|
#include "Buddies.hpp"
|
2021-03-13 22:55:16 +00:00
|
|
|
#include "Combat.hpp"
|
2021-03-16 22:29:13 +00:00
|
|
|
#include "Racing.hpp"
|
2021-03-12 22:18:00 +00:00
|
|
|
#include "BuiltinCommands.hpp"
|
2021-03-13 20:22:29 +00:00
|
|
|
#include "Abilities.hpp"
|
2020-08-18 20:42:30 +00:00
|
|
|
|
|
|
|
#include "settings.hpp"
|
|
|
|
|
2020-09-11 23:22:58 +00:00
|
|
|
#include <assert.h>
|
|
|
|
|
2020-08-18 20:42:30 +00:00
|
|
|
#include <algorithm>
|
|
|
|
#include <vector>
|
|
|
|
#include <cmath>
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
using namespace PlayerManager;
|
2020-08-18 20:42:30 +00:00
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
std::map<CNSocket*, Player*> PlayerManager::players;
|
2020-08-18 20:42:30 +00:00
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static void addPlayer(CNSocket* key, Player plr) {
|
2020-08-24 22:02:07 +00:00
|
|
|
Player *p = new Player();
|
|
|
|
|
|
|
|
memcpy(p, &plr, sizeof(Player));
|
|
|
|
|
2020-11-17 23:16:16 +00:00
|
|
|
players[key] = p;
|
2020-11-19 01:37:58 +00:00
|
|
|
p->chunkPos = std::make_tuple(0, 0, 0);
|
2020-11-19 22:19:46 +00:00
|
|
|
p->viewableChunks = new std::set<Chunk*>();
|
2020-11-17 23:16:16 +00:00
|
|
|
p->lastHeartbeat = 0;
|
2021-03-09 02:40:23 +00:00
|
|
|
p->buyback = new std::vector<sItemBase>();
|
2020-08-19 00:11:31 +00:00
|
|
|
|
2020-10-31 20:31:25 +00:00
|
|
|
std::cout << getPlayerName(p) << " has joined!" << std::endl;
|
2020-08-19 00:11:31 +00:00
|
|
|
std::cout << players.size() << " players" << std::endl;
|
2020-08-18 20:42:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void PlayerManager::removePlayer(CNSocket* key) {
|
2020-11-17 23:16:16 +00:00
|
|
|
Player* plr = getPlayer(key);
|
|
|
|
uint64_t fromInstance = plr->instanceID;
|
2020-10-05 00:03:13 +00:00
|
|
|
|
2021-03-16 22:29:13 +00:00
|
|
|
Groups::groupKickPlayer(plr);
|
2020-08-18 20:42:30 +00:00
|
|
|
|
2020-11-17 02:23:34 +00:00
|
|
|
// remove player's bullets
|
2021-03-13 22:55:16 +00:00
|
|
|
Combat::Bullets.erase(plr->iID);
|
2020-11-17 02:23:34 +00:00
|
|
|
|
2020-11-28 16:20:37 +00:00
|
|
|
// remove player's ongoing race, if it exists
|
2021-03-16 22:29:13 +00:00
|
|
|
Racing::EPRaces.erase(key);
|
2020-11-28 16:20:37 +00:00
|
|
|
|
2020-10-14 04:26:30 +00:00
|
|
|
// save player to DB
|
2020-11-17 23:16:16 +00:00
|
|
|
Database::updatePlayer(plr);
|
2020-10-14 04:26:30 +00:00
|
|
|
|
2020-11-18 00:07:04 +00:00
|
|
|
// remove player visually and untrack
|
2021-03-16 22:29:13 +00:00
|
|
|
Chunking::removePlayerFromChunks(Chunking::getViewableChunks(plr->chunkPos), key);
|
|
|
|
Chunking::untrackPlayer(plr->chunkPos, key);
|
2020-08-18 20:42:30 +00:00
|
|
|
|
2020-12-01 18:58:34 +00:00
|
|
|
std::cout << getPlayerName(plr) << " has left!" << std::endl;
|
2020-08-25 22:09:31 +00:00
|
|
|
|
2021-03-09 02:40:23 +00:00
|
|
|
delete plr->buyback;
|
2020-11-26 03:49:37 +00:00
|
|
|
delete plr->viewableChunks;
|
2020-11-17 23:16:16 +00:00
|
|
|
delete plr;
|
2020-08-25 22:09:31 +00:00
|
|
|
players.erase(key);
|
2020-09-17 19:22:31 +00:00
|
|
|
|
2020-10-19 02:30:12 +00:00
|
|
|
// if the player was in a lair, clean it up
|
2021-03-16 22:29:13 +00:00
|
|
|
Chunking::destroyInstanceIfEmpty(fromInstance);
|
2020-10-19 02:30:12 +00:00
|
|
|
|
2020-10-22 09:39:11 +00:00
|
|
|
// remove player's buffs from the server
|
|
|
|
auto it = NPCManager::EggBuffs.begin();
|
|
|
|
while (it != NPCManager::EggBuffs.end()) {
|
|
|
|
if (it->first.first == key) {
|
|
|
|
it = NPCManager::EggBuffs.erase(it);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
it++;
|
|
|
|
}
|
|
|
|
|
2020-09-17 19:22:31 +00:00
|
|
|
std::cout << players.size() << " players" << std::endl;
|
2020-08-18 20:42:30 +00:00
|
|
|
}
|
|
|
|
|
2020-11-18 00:07:04 +00:00
|
|
|
void PlayerManager::updatePlayerPosition(CNSocket* sock, int X, int Y, int Z, uint64_t I, int angle) {
|
2020-11-17 23:16:16 +00:00
|
|
|
Player* plr = getPlayer(sock);
|
2020-11-18 00:07:04 +00:00
|
|
|
plr->angle = angle;
|
2020-11-19 01:37:58 +00:00
|
|
|
ChunkPos oldChunk = plr->chunkPos;
|
2021-03-16 22:29:13 +00:00
|
|
|
ChunkPos newChunk = Chunking::chunkPosAt(X, Y, I);
|
2020-11-17 23:16:16 +00:00
|
|
|
plr->x = X;
|
|
|
|
plr->y = Y;
|
|
|
|
plr->z = Z;
|
2020-11-18 00:07:04 +00:00
|
|
|
plr->instanceID = I;
|
|
|
|
if (oldChunk == newChunk)
|
|
|
|
return; // didn't change chunks
|
2021-03-16 22:29:13 +00:00
|
|
|
Chunking::updatePlayerChunk(sock, oldChunk, newChunk);
|
2020-08-23 15:42:37 +00:00
|
|
|
}
|
|
|
|
|
2020-10-12 16:55:41 +00:00
|
|
|
void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z, uint64_t I) {
|
2020-11-17 23:16:16 +00:00
|
|
|
Player* plr = getPlayer(sock);
|
2020-11-26 15:01:48 +00:00
|
|
|
plr->onMonkey = false;
|
2020-10-19 02:30:12 +00:00
|
|
|
|
2020-11-18 00:07:04 +00:00
|
|
|
if (plr->instanceID == INSTANCE_OVERWORLD) {
|
2020-10-22 17:14:24 +00:00
|
|
|
// save last uninstanced coords
|
2020-11-17 23:16:16 +00:00
|
|
|
plr->lastX = plr->x;
|
|
|
|
plr->lastY = plr->y;
|
|
|
|
plr->lastZ = plr->z;
|
|
|
|
plr->lastAngle = plr->angle;
|
2020-10-22 17:14:24 +00:00
|
|
|
}
|
|
|
|
|
2021-03-16 22:29:13 +00:00
|
|
|
Missions::failInstancedMissions(sock); // fail any instanced missions
|
2020-10-22 17:14:24 +00:00
|
|
|
|
2020-11-17 23:16:16 +00:00
|
|
|
uint64_t fromInstance = plr->instanceID; // pre-warp instance, saved for post-warp
|
2020-10-19 02:30:12 +00:00
|
|
|
|
2020-12-31 02:31:46 +00:00
|
|
|
if (I == INSTANCE_OVERWORLD || (I != fromInstance && fromInstance != 0)) {
|
|
|
|
// annoying but necessary to set the flag back
|
|
|
|
INITSTRUCT(sP_FE2CL_REP_PC_WARP_USE_NPC_SUCC, resp);
|
|
|
|
resp.iX = X;
|
|
|
|
resp.iY = Y;
|
|
|
|
resp.iZ = Z;
|
|
|
|
resp.iCandy = plr->money;
|
|
|
|
resp.eIL = 4; // do not take away any items
|
|
|
|
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_WARP_USE_NPC_SUCC, sizeof(sP_FE2CL_REP_PC_WARP_USE_NPC_SUCC));
|
|
|
|
}
|
|
|
|
|
2020-10-02 20:17:24 +00:00
|
|
|
if (I != INSTANCE_OVERWORLD) {
|
|
|
|
INITSTRUCT(sP_FE2CL_INSTANCE_MAP_INFO, pkt);
|
2020-10-18 20:43:22 +00:00
|
|
|
pkt.iInstanceMapNum = (int32_t)MAPNUM(I); // lower 32 bits are mapnum
|
2020-12-28 15:51:31 +00:00
|
|
|
if (I != fromInstance // do not retransmit MAP_INFO on recall
|
2021-03-16 22:29:13 +00:00
|
|
|
&& Racing::EPData.find(pkt.iInstanceMapNum) != Racing::EPData.end()) {
|
|
|
|
EPInfo* ep = &Racing::EPData[pkt.iInstanceMapNum];
|
2020-11-29 03:03:20 +00:00
|
|
|
pkt.iEP_ID = ep->EPID;
|
|
|
|
pkt.iMapCoordX_Min = ep->zoneX * 51200;
|
|
|
|
pkt.iMapCoordX_Max = (ep->zoneX + 1) * 51200;
|
|
|
|
pkt.iMapCoordY_Min = ep->zoneY * 51200;
|
|
|
|
pkt.iMapCoordY_Max = (ep->zoneY + 1) * 51200;
|
|
|
|
pkt.iMapCoordZ_Min = INT32_MIN;
|
|
|
|
pkt.iMapCoordZ_Max = INT32_MAX;
|
|
|
|
}
|
2020-12-31 02:31:46 +00:00
|
|
|
|
2020-10-02 20:17:24 +00:00
|
|
|
sock->sendPacket((void*)&pkt, P_FE2CL_INSTANCE_MAP_INFO, sizeof(sP_FE2CL_INSTANCE_MAP_INFO));
|
|
|
|
}
|
2020-10-19 02:30:12 +00:00
|
|
|
|
2020-11-19 02:41:16 +00:00
|
|
|
INITSTRUCT(sP_FE2CL_REP_PC_GOTO_SUCC, pkt2);
|
|
|
|
pkt2.iX = X;
|
|
|
|
pkt2.iY = Y;
|
|
|
|
pkt2.iZ = Z;
|
|
|
|
sock->sendPacket((void*)&pkt2, P_FE2CL_REP_PC_GOTO_SUCC, sizeof(sP_FE2CL_REP_PC_GOTO_SUCC));
|
2021-03-16 22:29:13 +00:00
|
|
|
Chunking::updatePlayerChunk(sock, plr->chunkPos, std::make_tuple(0, 0, 0)); // force player to reload chunks
|
2020-11-18 00:07:04 +00:00
|
|
|
updatePlayerPosition(sock, X, Y, Z, I, plr->angle);
|
|
|
|
|
2020-10-25 22:14:35 +00:00
|
|
|
// post-warp: check if the source instance has no more players in it and delete it if so
|
2021-03-16 22:29:13 +00:00
|
|
|
Chunking::destroyInstanceIfEmpty(fromInstance);
|
2020-11-18 00:07:04 +00:00
|
|
|
|
2020-12-29 13:31:48 +00:00
|
|
|
// clean up EPRaces if we were likely in an IZ and left
|
|
|
|
if (fromInstance != INSTANCE_OVERWORLD && fromInstance != I
|
2021-03-16 22:29:13 +00:00
|
|
|
&& Racing::EPRaces.find(sock) != Racing::EPRaces.end())
|
|
|
|
Racing::EPRaces.erase(sock);
|
2020-10-03 15:21:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z) {
|
2020-11-18 00:07:04 +00:00
|
|
|
sendPlayerTo(sock, X, Y, Z, getPlayer(sock)->instanceID);
|
2020-10-03 15:21:36 +00:00
|
|
|
}
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
/*
|
|
|
|
* Sends all nanos, from 0 to 58 (the contents of the Nanos array in PC_ENTER_SUCC are totally irrelevant).
|
|
|
|
* The first Nano in the in-game nanobook is the Unstable Nano, which is Van Kleiss.
|
|
|
|
* 0 (in plr->Nanos) is the null nano entry.
|
|
|
|
* 58 is a "Coming Soon" duplicate entry for an actual Van Kleiss nano, identical to the Unstable Nano.
|
|
|
|
* Nanos the player hasn't unlocked will (and should) be greyed out. Thus, all nanos should be accounted
|
|
|
|
* for in these packets, even if the player hasn't unlocked them.
|
|
|
|
*/
|
|
|
|
static void sendNanoBookSubset(CNSocket *sock) {
|
|
|
|
#ifdef ACADEMY
|
|
|
|
Player *plr = getPlayer(sock);
|
|
|
|
|
|
|
|
int16_t id = 0;
|
|
|
|
INITSTRUCT(sP_FE2CL_REP_NANO_BOOK_SUBSET, pkt);
|
|
|
|
|
|
|
|
pkt.PCUID = plr->iID;
|
|
|
|
pkt.bookSize = NANO_COUNT;
|
|
|
|
|
|
|
|
while (id < NANO_COUNT) {
|
|
|
|
pkt.elementOffset = id;
|
|
|
|
|
|
|
|
for (int i = id - pkt.elementOffset; id < NANO_COUNT && i < 10; id++, i = id - pkt.elementOffset)
|
|
|
|
pkt.element[i] = plr->Nanos[id];
|
|
|
|
|
|
|
|
sock->sendPacket((void*)&pkt, P_FE2CL_REP_NANO_BOOK_SUBSET, sizeof(sP_FE2CL_REP_NANO_BOOK_SUBSET));
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
2020-08-19 00:11:31 +00:00
|
|
|
if (data->size != sizeof(sP_CL2FE_REQ_PC_ENTER))
|
|
|
|
return; // ignore the malformed packet
|
|
|
|
|
2020-08-18 20:42:30 +00:00
|
|
|
sP_CL2FE_REQ_PC_ENTER* enter = (sP_CL2FE_REQ_PC_ENTER*)data->buf;
|
2020-08-23 00:26:18 +00:00
|
|
|
INITSTRUCT(sP_FE2CL_REP_PC_ENTER_SUCC, response);
|
2020-08-18 20:42:30 +00:00
|
|
|
|
|
|
|
// TODO: check if serialkey exists, if it doesn't send sP_FE2CL_REP_PC_ENTER_FAIL
|
|
|
|
Player plr = CNSharedData::getPlayer(enter->iEnterSerialKey);
|
2020-10-05 00:03:13 +00:00
|
|
|
|
2020-10-04 23:54:08 +00:00
|
|
|
plr.groupCnt = 1;
|
|
|
|
plr.iIDGroup = plr.groupIDs[0] = plr.iID;
|
2020-08-18 20:42:30 +00:00
|
|
|
|
|
|
|
DEBUGLOG(
|
|
|
|
std::cout << "P_CL2FE_REQ_PC_ENTER:" << std::endl;
|
2020-08-24 23:08:02 +00:00
|
|
|
std::cout << "\tID: " << U16toU8(enter->szID) << std::endl;
|
|
|
|
std::cout << "\tSerial: " << enter->iEnterSerialKey << std::endl;
|
|
|
|
std::cout << "\tTemp: " << enter->iTempValue << std::endl;
|
|
|
|
std::cout << "\tPC_UID: " << plr.PCStyle.iPC_UID << std::endl;
|
2020-09-14 13:20:55 +00:00
|
|
|
)
|
2020-08-18 20:42:30 +00:00
|
|
|
|
2020-09-27 05:12:26 +00:00
|
|
|
// check if account is already in use
|
|
|
|
if (isAccountInUse(plr.accountId)) {
|
|
|
|
// kick the other player
|
|
|
|
exitDuplicate(plr.accountId);
|
|
|
|
}
|
|
|
|
|
2020-09-02 22:22:00 +00:00
|
|
|
response.iID = plr.iID;
|
2020-08-22 23:31:09 +00:00
|
|
|
response.uiSvrTime = getTime();
|
2020-09-22 02:26:12 +00:00
|
|
|
response.PCLoadData2CL.iUserLevel = plr.accountLevel;
|
2020-09-02 22:22:00 +00:00
|
|
|
response.PCLoadData2CL.iHP = plr.HP;
|
2020-08-22 23:31:09 +00:00
|
|
|
response.PCLoadData2CL.iLevel = plr.level;
|
2020-08-26 17:40:10 +00:00
|
|
|
response.PCLoadData2CL.iCandy = plr.money;
|
2020-09-19 03:26:20 +00:00
|
|
|
response.PCLoadData2CL.iFusionMatter = plr.fusionmatter;
|
2020-09-21 19:43:53 +00:00
|
|
|
response.PCLoadData2CL.iMentor = plr.mentor;
|
2020-09-12 20:43:04 +00:00
|
|
|
response.PCLoadData2CL.iMentorCount = 1; // how many guides the player has had
|
2020-08-22 23:31:09 +00:00
|
|
|
response.PCLoadData2CL.iX = plr.x;
|
|
|
|
response.PCLoadData2CL.iY = plr.y;
|
|
|
|
response.PCLoadData2CL.iZ = plr.z;
|
2020-09-02 22:22:00 +00:00
|
|
|
response.PCLoadData2CL.iAngle = plr.angle;
|
2020-09-21 19:43:53 +00:00
|
|
|
response.PCLoadData2CL.iBatteryN = plr.batteryN;
|
|
|
|
response.PCLoadData2CL.iBatteryW = plr.batteryW;
|
2020-10-19 17:26:14 +00:00
|
|
|
response.PCLoadData2CL.iBuddyWarpTime = 60; // sets 60s warp cooldown on login
|
2020-09-07 00:16:44 +00:00
|
|
|
|
2020-09-22 03:54:07 +00:00
|
|
|
response.PCLoadData2CL.iWarpLocationFlag = plr.iWarpLocationFlag;
|
|
|
|
response.PCLoadData2CL.aWyvernLocationFlag[0] = plr.aSkywayLocationFlag[0];
|
|
|
|
response.PCLoadData2CL.aWyvernLocationFlag[1] = plr.aSkywayLocationFlag[1];
|
|
|
|
|
2020-09-07 00:16:44 +00:00
|
|
|
response.PCLoadData2CL.iActiveNanoSlotNum = -1;
|
2020-08-22 23:31:09 +00:00
|
|
|
response.PCLoadData2CL.iFatigue = 50;
|
|
|
|
response.PCLoadData2CL.PCStyle = plr.PCStyle;
|
2020-10-05 00:03:13 +00:00
|
|
|
|
2020-10-19 17:26:14 +00:00
|
|
|
// client doesnt read this, it gets it from charinfo
|
|
|
|
// response.PCLoadData2CL.PCStyle2 = plr.PCStyle2;
|
2020-09-14 14:03:30 +00:00
|
|
|
// inventory
|
2020-08-18 20:42:30 +00:00
|
|
|
for (int i = 0; i < AEQUIP_COUNT; i++)
|
2020-08-22 23:31:09 +00:00
|
|
|
response.PCLoadData2CL.aEquip[i] = plr.Equip[i];
|
2020-08-24 21:04:56 +00:00
|
|
|
for (int i = 0; i < AINVEN_COUNT; i++)
|
2020-08-22 23:31:09 +00:00
|
|
|
response.PCLoadData2CL.aInven[i] = plr.Inven[i];
|
2020-09-21 19:43:53 +00:00
|
|
|
// quest inventory
|
|
|
|
for (int i = 0; i < AQINVEN_COUNT; i++)
|
|
|
|
response.PCLoadData2CL.aQInven[i] = plr.QInven[i];
|
2020-09-14 14:03:30 +00:00
|
|
|
// nanos
|
2020-09-07 00:16:44 +00:00
|
|
|
for (int i = 1; i < SIZEOF_NANO_BANK_SLOT; i++) {
|
2020-09-02 22:22:00 +00:00
|
|
|
response.PCLoadData2CL.aNanoBank[i] = plr.Nanos[i];
|
2020-11-25 22:36:30 +00:00
|
|
|
//response.PCLoadData2CL.aNanoBank[i] = plr.Nanos[i] = {0};
|
2020-08-18 20:42:30 +00:00
|
|
|
}
|
2020-09-07 00:16:44 +00:00
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
response.PCLoadData2CL.aNanoSlots[i] = plr.equippedNanos[i];
|
|
|
|
}
|
2020-10-19 17:26:14 +00:00
|
|
|
// missions in progress
|
2020-09-21 19:43:53 +00:00
|
|
|
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
|
|
|
if (plr.tasks[i] == 0)
|
|
|
|
break;
|
|
|
|
response.PCLoadData2CL.aRunningQuest[i].m_aCurrTaskID = plr.tasks[i];
|
2021-03-16 22:29:13 +00:00
|
|
|
TaskData &task = *Missions::Tasks[plr.tasks[i]];
|
2020-09-21 19:43:53 +00:00
|
|
|
for (int j = 0; j < 3; j++) {
|
|
|
|
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCID[j] = (int)task["m_iCSUEnemyID"][j];
|
|
|
|
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCCount[j] = plr.RemainingNPCCount[i][j];
|
|
|
|
/*
|
|
|
|
* client doesn't care about NeededItem ID and Count,
|
|
|
|
* it gets Count from Quest Inventory
|
2020-10-05 00:03:13 +00:00
|
|
|
*
|
2020-09-21 19:43:53 +00:00
|
|
|
* KillNPCCount sets RemainEnemyNum in the client
|
2020-09-21 21:30:05 +00:00
|
|
|
* Yes, this is extraordinary stupid.
|
2020-09-21 19:43:53 +00:00
|
|
|
*/
|
|
|
|
}
|
|
|
|
}
|
|
|
|
response.PCLoadData2CL.iCurrentMissionID = plr.CurrentMissionID;
|
2020-08-18 20:42:30 +00:00
|
|
|
|
2020-09-21 19:43:53 +00:00
|
|
|
// completed missions
|
|
|
|
// the packet requires 32 items, but the client only checks the first 16 (shrug)
|
2020-09-13 18:45:51 +00:00
|
|
|
for (int i = 0; i < 16; i++) {
|
|
|
|
response.PCLoadData2CL.aQuestFlag[i] = plr.aQuestFlag[i];
|
|
|
|
}
|
|
|
|
|
2020-12-07 18:01:29 +00:00
|
|
|
// Computress tips
|
2020-12-07 18:24:56 +00:00
|
|
|
if (settings::DISABLEFIRSTUSEFLAG) {
|
|
|
|
response.PCLoadData2CL.iFirstUseFlag1 = UINT64_MAX;
|
|
|
|
response.PCLoadData2CL.iFirstUseFlag2 = UINT64_MAX;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
response.PCLoadData2CL.iFirstUseFlag1 = plr.iFirstUseFlag[0];
|
|
|
|
response.PCLoadData2CL.iFirstUseFlag2 = plr.iFirstUseFlag[1];
|
|
|
|
}
|
2020-08-25 01:42:52 +00:00
|
|
|
|
2020-08-18 20:42:30 +00:00
|
|
|
plr.SerialKey = enter->iEnterSerialKey;
|
2020-10-13 03:42:47 +00:00
|
|
|
plr.instanceID = INSTANCE_OVERWORLD; // the player should never be in an instance on enter
|
2020-08-18 20:42:30 +00:00
|
|
|
|
2020-08-22 23:31:09 +00:00
|
|
|
sock->setEKey(CNSocketEncryption::createNewKey(response.uiSvrTime, response.iID + 1, response.PCLoadData2CL.iFusionMatter + 1));
|
2020-08-18 20:42:30 +00:00
|
|
|
sock->setFEKey(plr.FEKey);
|
2020-08-22 23:31:09 +00:00
|
|
|
sock->setActiveKey(SOCKETKEY_FE); // send all packets using the FE key from now on
|
|
|
|
|
|
|
|
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_ENTER_SUCC, sizeof(sP_FE2CL_REP_PC_ENTER_SUCC));
|
2020-08-18 20:42:30 +00:00
|
|
|
|
2020-08-21 17:38:45 +00:00
|
|
|
// transmit MOTD after entering the game, so the client hopefully changes modes on time
|
2021-03-16 22:29:13 +00:00
|
|
|
Chat::sendServerMessage(sock, settings::MOTDSTRING);
|
2020-08-18 20:42:30 +00:00
|
|
|
|
|
|
|
addPlayer(sock, plr);
|
2020-10-19 17:26:14 +00:00
|
|
|
// check if there is an expiring vehicle
|
2021-03-16 22:29:13 +00:00
|
|
|
Items::checkItemExpire(sock, getPlayer(sock));
|
2020-10-05 00:03:13 +00:00
|
|
|
|
2020-10-19 17:26:14 +00:00
|
|
|
// set player equip stats
|
2021-03-16 22:29:13 +00:00
|
|
|
Items::setItemStats(getPlayer(sock));
|
2020-10-14 18:36:38 +00:00
|
|
|
|
2021-03-16 22:29:13 +00:00
|
|
|
Missions::failInstancedMissions(sock);
|
2020-10-31 17:12:13 +00:00
|
|
|
|
2020-11-25 22:36:30 +00:00
|
|
|
sendNanoBookSubset(sock);
|
|
|
|
|
2020-11-08 18:58:44 +00:00
|
|
|
// initial buddy sync
|
2021-03-16 22:29:13 +00:00
|
|
|
Buddies::refreshBuddyList(sock);
|
2020-11-08 17:42:27 +00:00
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
for (auto& pair : players)
|
2020-11-17 23:16:16 +00:00
|
|
|
if (pair.second->notify)
|
2021-03-16 22:29:13 +00:00
|
|
|
Chat::sendServerMessage(pair.first, "[ADMIN]" + getPlayerName(&plr) + " has joined.");
|
2020-08-18 20:42:30 +00:00
|
|
|
}
|
|
|
|
|
2020-09-17 22:45:43 +00:00
|
|
|
void PlayerManager::sendToViewable(CNSocket* sock, void* buf, uint32_t type, size_t size) {
|
2020-11-18 00:07:04 +00:00
|
|
|
Player* plr = getPlayer(sock);
|
2020-11-19 22:19:46 +00:00
|
|
|
for (auto it = plr->viewableChunks->begin(); it != plr->viewableChunks->end(); it++) {
|
|
|
|
Chunk* chunk = *it;
|
2020-09-17 22:45:43 +00:00
|
|
|
for (CNSocket* otherSock : chunk->players) {
|
|
|
|
if (otherSock == sock)
|
|
|
|
continue;
|
2020-10-05 00:03:13 +00:00
|
|
|
|
2020-09-17 22:45:43 +00:00
|
|
|
otherSock->sendPacket(buf, type, size);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static void loadPlayer(CNSocket* sock, CNPacketData* data) {
|
2020-08-19 00:11:31 +00:00
|
|
|
if (data->size != sizeof(sP_CL2FE_REQ_PC_LOADING_COMPLETE))
|
|
|
|
return; // ignore the malformed packet
|
|
|
|
|
2020-08-18 20:42:30 +00:00
|
|
|
sP_CL2FE_REQ_PC_LOADING_COMPLETE* complete = (sP_CL2FE_REQ_PC_LOADING_COMPLETE*)data->buf;
|
2020-08-23 00:26:18 +00:00
|
|
|
INITSTRUCT(sP_FE2CL_REP_PC_LOADING_COMPLETE_SUCC, response);
|
2020-08-25 18:42:52 +00:00
|
|
|
Player *plr = getPlayer(sock);
|
2020-08-18 20:42:30 +00:00
|
|
|
|
|
|
|
DEBUGLOG(
|
|
|
|
std::cout << "P_CL2FE_REQ_PC_LOADING_COMPLETE:" << std::endl;
|
2020-08-24 23:08:02 +00:00
|
|
|
std::cout << "\tPC_ID: " << complete->iPC_ID << std::endl;
|
2020-08-18 20:42:30 +00:00
|
|
|
)
|
|
|
|
|
2020-08-24 23:08:02 +00:00
|
|
|
response.iPC_ID = complete->iPC_ID;
|
2020-08-18 20:42:30 +00:00
|
|
|
|
2020-11-18 00:07:04 +00:00
|
|
|
updatePlayerPosition(sock, plr->x, plr->y, plr->z, plr->instanceID, plr->angle);
|
2020-08-25 18:42:52 +00:00
|
|
|
|
2020-08-22 23:31:09 +00:00
|
|
|
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_LOADING_COMPLETE_SUCC, sizeof(sP_FE2CL_REP_PC_LOADING_COMPLETE_SUCC));
|
2020-08-18 20:42:30 +00:00
|
|
|
}
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static void heartbeatPlayer(CNSocket* sock, CNPacketData* data) {
|
2020-11-17 23:16:16 +00:00
|
|
|
getPlayer(sock)->lastHeartbeat = getTime();
|
2020-08-19 17:22:54 +00:00
|
|
|
}
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static void exitGame(CNSocket* sock, CNPacketData* data) {
|
2020-08-22 18:08:37 +00:00
|
|
|
if (data->size != sizeof(sP_CL2FE_REQ_PC_EXIT))
|
|
|
|
return;
|
2020-08-24 21:04:56 +00:00
|
|
|
|
2020-08-19 17:22:54 +00:00
|
|
|
sP_CL2FE_REQ_PC_EXIT* exitData = (sP_CL2FE_REQ_PC_EXIT*)data->buf;
|
2020-08-23 00:26:18 +00:00
|
|
|
INITSTRUCT(sP_FE2CL_REP_PC_EXIT_SUCC, response);
|
2020-08-19 17:22:54 +00:00
|
|
|
|
2020-08-22 23:31:09 +00:00
|
|
|
response.iID = exitData->iID;
|
|
|
|
response.iExitCode = 1;
|
2020-08-19 17:22:54 +00:00
|
|
|
|
2020-08-22 23:31:09 +00:00
|
|
|
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_EXIT_SUCC, sizeof(sP_FE2CL_REP_PC_EXIT_SUCC));
|
2020-08-19 17:22:54 +00:00
|
|
|
}
|
2020-08-22 18:02:08 +00:00
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static WarpLocation* getRespawnPoint(Player *plr) {
|
|
|
|
WarpLocation* best = nullptr;
|
|
|
|
uint32_t curDist, bestDist = UINT32_MAX;
|
|
|
|
|
|
|
|
for (auto& targ : NPCManager::RespawnPoints) {
|
|
|
|
curDist = sqrt(pow(plr->x - targ.x, 2) + pow(plr->y - targ.y, 2));
|
|
|
|
if (curDist < bestDist && targ.instanceID == MAPNUM(plr->instanceID)) { // only mapNum needs to match
|
|
|
|
best = &targ;
|
|
|
|
bestDist = curDist;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return best;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
2020-08-24 21:04:56 +00:00
|
|
|
if (data->size != sizeof(sP_CL2FE_REQ_PC_REGEN))
|
|
|
|
return;
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
Player *plr = getPlayer(sock);
|
|
|
|
WarpLocation* target = getRespawnPoint(plr);
|
2020-08-24 21:04:56 +00:00
|
|
|
|
|
|
|
sP_CL2FE_REQ_PC_REGEN* reviveData = (sP_CL2FE_REQ_PC_REGEN*)data->buf;
|
|
|
|
INITSTRUCT(sP_FE2CL_REP_PC_REGEN_SUCC, response);
|
2020-09-12 20:43:04 +00:00
|
|
|
INITSTRUCT(sP_FE2CL_PC_REGEN, resp2);
|
2020-10-05 00:03:13 +00:00
|
|
|
|
2020-09-25 14:57:32 +00:00
|
|
|
int activeSlot = -1;
|
2020-11-25 22:46:27 +00:00
|
|
|
bool move = false;
|
|
|
|
|
2020-11-12 00:59:52 +00:00
|
|
|
if (reviveData->iRegenType == 3 && plr->iConditionBitFlag & CSB_BIT_PHOENIX) {
|
2020-10-04 23:54:08 +00:00
|
|
|
// nano revive
|
|
|
|
plr->Nanos[plr->activeNano].iStamina = 0;
|
2020-11-12 00:59:52 +00:00
|
|
|
plr->HP = PC_MAXHEALTH(plr->level);
|
2021-03-16 22:29:13 +00:00
|
|
|
Nanos::applyBuff(sock, plr->Nanos[plr->activeNano].iSkillID, 2, 1, 0);
|
2020-11-12 00:59:52 +00:00
|
|
|
} else if (reviveData->iRegenType == 4) {
|
2020-10-04 23:54:08 +00:00
|
|
|
plr->HP = PC_MAXHEALTH(plr->level);
|
|
|
|
} else {
|
2020-11-25 22:46:27 +00:00
|
|
|
move = true;
|
2020-10-04 23:54:08 +00:00
|
|
|
if (reviveData->iRegenType != 5)
|
|
|
|
plr->HP = PC_MAXHEALTH(plr->level);
|
2020-11-27 20:39:38 +00:00
|
|
|
}
|
2020-10-05 00:03:13 +00:00
|
|
|
|
2020-11-27 20:39:38 +00:00
|
|
|
for (int i = 0; i < 3; i++) {
|
|
|
|
int nanoID = plr->equippedNanos[i];
|
|
|
|
// halve nano health if respawning
|
2020-11-29 01:43:42 +00:00
|
|
|
// all revives not 3-5 are normal respawns.
|
2020-12-06 04:25:23 +00:00
|
|
|
if (reviveData->iRegenType < 3 || reviveData->iRegenType > 5)
|
2020-11-27 20:39:38 +00:00
|
|
|
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;
|
2020-09-12 18:21:36 +00:00
|
|
|
}
|
|
|
|
|
2020-12-14 23:22:27 +00:00
|
|
|
int x, y, z;
|
2020-11-26 16:03:42 +00:00
|
|
|
if (move && target != nullptr) {
|
2020-12-14 23:22:27 +00:00
|
|
|
// go to Resurrect 'Em
|
|
|
|
x = target->x;
|
|
|
|
y = target->y;
|
|
|
|
z = target->z;
|
|
|
|
} else if (PLAYERID(plr->instanceID)) {
|
|
|
|
// respawn at entrance to the Lair
|
|
|
|
x = plr->recallX;
|
|
|
|
y = plr->recallY;
|
|
|
|
z = plr->recallZ;
|
2020-11-26 16:03:42 +00:00
|
|
|
} else {
|
2020-12-14 23:22:27 +00:00
|
|
|
// no other choice; respawn in place
|
|
|
|
x = plr->x;
|
|
|
|
y = plr->y;
|
|
|
|
z = plr->z;
|
2020-11-26 16:03:42 +00:00
|
|
|
}
|
2020-12-14 23:22:27 +00:00
|
|
|
|
|
|
|
// Response parameters
|
|
|
|
response.PCRegenData.iActiveNanoSlotNum = activeSlot;
|
|
|
|
response.PCRegenData.iX = x;
|
|
|
|
response.PCRegenData.iY = y;
|
|
|
|
response.PCRegenData.iZ = z;
|
2020-09-12 18:21:36 +00:00
|
|
|
response.PCRegenData.iHP = plr->HP;
|
|
|
|
response.iFusionMatter = plr->fusionmatter;
|
2020-10-04 23:54:08 +00:00
|
|
|
response.bMoveLocation = 0;
|
2020-12-14 23:22:27 +00:00
|
|
|
response.PCRegenData.iMapNum = MAPNUM(plr->instanceID);
|
2020-08-24 21:04:56 +00:00
|
|
|
|
|
|
|
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_REGEN_SUCC, sizeof(sP_FE2CL_REP_PC_REGEN_SUCC));
|
2020-09-12 20:43:04 +00:00
|
|
|
|
|
|
|
// Update other players
|
|
|
|
resp2.PCRegenDataForOtherPC.iPC_ID = plr->iID;
|
2020-12-14 23:22:27 +00:00
|
|
|
resp2.PCRegenDataForOtherPC.iX = x;
|
|
|
|
resp2.PCRegenDataForOtherPC.iY = y;
|
|
|
|
resp2.PCRegenDataForOtherPC.iZ = z;
|
2020-09-12 20:43:04 +00:00
|
|
|
resp2.PCRegenDataForOtherPC.iHP = plr->HP;
|
|
|
|
resp2.PCRegenDataForOtherPC.iAngle = plr->angle;
|
2020-11-27 16:50:57 +00:00
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
Player *otherPlr = getPlayerFromID(plr->iIDGroup);
|
2020-12-14 23:22:27 +00:00
|
|
|
if (otherPlr != nullptr) {
|
2021-03-16 22:29:13 +00:00
|
|
|
int bitFlag = Groups::getGroupFlags(otherPlr);
|
2020-12-14 23:22:27 +00:00
|
|
|
resp2.PCRegenDataForOtherPC.iConditionBitFlag = plr->iConditionBitFlag = plr->iSelfConditionBitFlag | bitFlag;
|
2020-11-27 16:50:57 +00:00
|
|
|
|
2020-12-14 23:22:27 +00:00
|
|
|
resp2.PCRegenDataForOtherPC.iPCState = plr->iPCState;
|
|
|
|
resp2.PCRegenDataForOtherPC.iSpecialState = plr->iSpecialState;
|
|
|
|
resp2.PCRegenDataForOtherPC.Nano = plr->Nanos[plr->activeNano];
|
2020-09-12 20:43:04 +00:00
|
|
|
|
2020-12-14 23:22:27 +00:00
|
|
|
sendToViewable(sock, (void*)&resp2, P_FE2CL_PC_REGEN, sizeof(sP_FE2CL_PC_REGEN));
|
|
|
|
}
|
2020-11-25 22:46:27 +00:00
|
|
|
|
2020-12-14 23:22:27 +00:00
|
|
|
if (!move)
|
2020-11-25 22:46:27 +00:00
|
|
|
return;
|
|
|
|
|
2021-03-16 22:29:13 +00:00
|
|
|
Chunking::updatePlayerChunk(sock, plr->chunkPos, std::make_tuple(0, 0, 0)); // force player to reload chunks
|
2020-12-14 23:22:27 +00:00
|
|
|
updatePlayerPosition(sock, x, y, z, plr->instanceID, plr->angle);
|
2020-08-24 21:04:56 +00:00
|
|
|
}
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static void enterPlayerVehicle(CNSocket* sock, CNPacketData* data) {
|
2020-11-17 23:16:16 +00:00
|
|
|
Player* plr = getPlayer(sock);
|
2020-09-14 13:53:48 +00:00
|
|
|
|
2020-12-22 21:52:25 +00:00
|
|
|
bool expired = plr->Equip[8].iTimeLimit < getTimestamp() && plr->Equip[8].iTimeLimit != 0;
|
|
|
|
|
|
|
|
if (plr->Equip[8].iID > 0 && !expired) {
|
2020-08-27 02:35:13 +00:00
|
|
|
INITSTRUCT(sP_FE2CL_PC_VEHICLE_ON_SUCC, response);
|
|
|
|
sock->sendPacket((void*)&response, P_FE2CL_PC_VEHICLE_ON_SUCC, sizeof(sP_FE2CL_PC_VEHICLE_ON_SUCC));
|
2020-09-14 13:53:48 +00:00
|
|
|
|
2020-09-14 14:03:30 +00:00
|
|
|
// send to other players
|
2020-11-17 23:16:16 +00:00
|
|
|
plr->iPCState |= 8;
|
2020-08-27 02:35:13 +00:00
|
|
|
INITSTRUCT(sP_FE2CL_PC_STATE_CHANGE, response2);
|
2020-11-17 23:16:16 +00:00
|
|
|
response2.iPC_ID = plr->iID;
|
|
|
|
response2.iState = plr->iPCState;
|
|
|
|
sendToViewable(sock, (void*)&response2, P_FE2CL_PC_STATE_CHANGE, sizeof(sP_FE2CL_PC_STATE_CHANGE));
|
2020-09-14 13:53:48 +00:00
|
|
|
|
2020-08-27 02:35:13 +00:00
|
|
|
} else {
|
|
|
|
INITSTRUCT(sP_FE2CL_PC_VEHICLE_ON_FAIL, response);
|
|
|
|
sock->sendPacket((void*)&response, P_FE2CL_PC_VEHICLE_ON_FAIL, sizeof(sP_FE2CL_PC_VEHICLE_ON_FAIL));
|
2020-09-23 08:20:47 +00:00
|
|
|
|
2020-09-23 09:21:32 +00:00
|
|
|
// check if vehicle didn't expire
|
2020-12-22 21:52:25 +00:00
|
|
|
if (expired) {
|
2020-11-17 23:16:16 +00:00
|
|
|
plr->toRemoveVehicle.eIL = 0;
|
|
|
|
plr->toRemoveVehicle.iSlotNum = 8;
|
2021-03-16 22:29:13 +00:00
|
|
|
Items::checkItemExpire(sock, plr);
|
2020-09-23 08:20:47 +00:00
|
|
|
}
|
2020-08-24 21:04:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static void exitPlayerVehicle(CNSocket* sock, CNPacketData* data) {
|
2020-11-17 23:16:16 +00:00
|
|
|
Player* plr = getPlayer(sock);
|
2020-08-24 21:04:56 +00:00
|
|
|
|
2020-11-17 23:16:16 +00:00
|
|
|
if (plr->iPCState & 8) {
|
2020-10-28 21:05:01 +00:00
|
|
|
INITSTRUCT(sP_FE2CL_PC_VEHICLE_OFF_SUCC, response);
|
|
|
|
sock->sendPacket((void*)&response, P_FE2CL_PC_VEHICLE_OFF_SUCC, sizeof(sP_FE2CL_PC_VEHICLE_OFF_SUCC));
|
2020-09-14 13:53:48 +00:00
|
|
|
|
2020-10-28 21:05:01 +00:00
|
|
|
// send to other players
|
2020-11-17 23:16:16 +00:00
|
|
|
plr->iPCState &= ~8;
|
2020-10-28 21:05:01 +00:00
|
|
|
INITSTRUCT(sP_FE2CL_PC_STATE_CHANGE, response2);
|
2020-11-17 23:16:16 +00:00
|
|
|
response2.iPC_ID = plr->iID;
|
|
|
|
response2.iState = plr->iPCState;
|
2020-10-28 21:05:01 +00:00
|
|
|
|
|
|
|
sendToViewable(sock, (void*)&response2, P_FE2CL_PC_STATE_CHANGE, sizeof(sP_FE2CL_PC_STATE_CHANGE));
|
|
|
|
}
|
2020-08-24 21:04:56 +00:00
|
|
|
}
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static void setSpecialSwitchPlayer(CNSocket* sock, CNPacketData* data) {
|
2021-03-12 22:18:00 +00:00
|
|
|
BuiltinCommands::setSpecialState(sock, data);
|
2020-12-31 01:13:43 +00:00
|
|
|
}
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static void changePlayerGuide(CNSocket *sock, CNPacketData *data) {
|
2020-08-28 19:42:00 +00:00
|
|
|
if (data->size != sizeof(sP_CL2FE_REQ_PC_CHANGE_MENTOR))
|
|
|
|
return;
|
|
|
|
|
|
|
|
sP_CL2FE_REQ_PC_CHANGE_MENTOR *pkt = (sP_CL2FE_REQ_PC_CHANGE_MENTOR*)data->buf;
|
|
|
|
INITSTRUCT(sP_FE2CL_REP_PC_CHANGE_MENTOR_SUCC, resp);
|
|
|
|
Player *plr = getPlayer(sock);
|
|
|
|
|
|
|
|
resp.iMentor = pkt->iMentor;
|
|
|
|
resp.iMentorCnt = 1;
|
|
|
|
resp.iFusionMatter = plr->fusionmatter; // no cost
|
2020-10-05 00:03:13 +00:00
|
|
|
|
2020-08-28 19:42:00 +00:00
|
|
|
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_CHANGE_MENTOR_SUCC, sizeof(sP_FE2CL_REP_PC_CHANGE_MENTOR_SUCC));
|
2020-09-25 05:35:27 +00:00
|
|
|
// if it's changed from computress
|
|
|
|
if (plr->mentor == 5) {
|
|
|
|
// we're warping to the past
|
|
|
|
plr->PCStyle2.iPayzoneFlag = 1;
|
|
|
|
// remove all active missions
|
|
|
|
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
|
|
|
if (plr->tasks[i] != 0)
|
2021-03-16 22:29:13 +00:00
|
|
|
Missions::quitTask(sock, plr->tasks[i], true);
|
2020-09-25 05:35:27 +00:00
|
|
|
}
|
2020-09-26 01:48:45 +00:00
|
|
|
|
|
|
|
// start Blossom nano mission if applicable
|
2021-03-16 22:29:13 +00:00
|
|
|
Missions::updateFusionMatter(sock, 0);
|
2020-09-25 05:35:27 +00:00
|
|
|
}
|
|
|
|
// save it on player
|
2020-09-21 19:43:53 +00:00
|
|
|
plr->mentor = pkt->iMentor;
|
2020-08-28 19:42:00 +00:00
|
|
|
}
|
|
|
|
|
2021-03-16 21:06:10 +00:00
|
|
|
static void setFirstUseFlag(CNSocket* sock, CNPacketData* data) {
|
2020-12-07 18:01:29 +00:00
|
|
|
if (data->size != sizeof(sP_CL2FE_REQ_PC_FIRST_USE_FLAG_SET))
|
|
|
|
return;
|
|
|
|
|
|
|
|
sP_CL2FE_REQ_PC_FIRST_USE_FLAG_SET* flag = (sP_CL2FE_REQ_PC_FIRST_USE_FLAG_SET*)data->buf;
|
|
|
|
Player* plr = getPlayer(sock);
|
|
|
|
|
|
|
|
if (flag->iFlagCode < 1 || flag->iFlagCode > 128) {
|
|
|
|
std::cout << "[WARN] Client submitted invalid first use flag number?!" << std::endl;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (flag->iFlagCode <= 64)
|
|
|
|
plr->iFirstUseFlag[0] |= (1ULL << (flag->iFlagCode - 1));
|
|
|
|
else
|
|
|
|
plr->iFirstUseFlag[1] |= (1ULL << (flag->iFlagCode - 65));
|
|
|
|
}
|
|
|
|
|
2020-08-24 21:04:56 +00:00
|
|
|
#pragma region Helper methods
|
2020-08-24 22:02:07 +00:00
|
|
|
Player *PlayerManager::getPlayer(CNSocket* key) {
|
2020-09-28 18:11:13 +00:00
|
|
|
if (players.find(key) != players.end())
|
2020-11-17 23:16:16 +00:00
|
|
|
return players[key];
|
2020-10-05 00:03:13 +00:00
|
|
|
|
2020-12-01 18:58:34 +00:00
|
|
|
// this should never happen
|
|
|
|
assert(false);
|
2020-08-24 21:04:56 +00:00
|
|
|
}
|
2020-08-25 01:34:53 +00:00
|
|
|
|
2020-10-31 20:31:25 +00:00
|
|
|
std::string PlayerManager::getPlayerName(Player *plr, bool id) {
|
|
|
|
// the print in CNShardServer can print packets from players that haven't yet joined
|
|
|
|
if (plr == nullptr)
|
|
|
|
return "NOT IN GAME";
|
|
|
|
|
2020-12-15 14:53:45 +00:00
|
|
|
std::string ret = "";
|
2020-12-17 00:59:55 +00:00
|
|
|
if (id && plr->accountLevel <= 30)
|
2020-12-15 14:53:45 +00:00
|
|
|
ret += "(GM) ";
|
|
|
|
|
|
|
|
ret += U16toU8(plr->PCStyle.szFirstName) + " " + U16toU8(plr->PCStyle.szLastName);
|
2020-10-31 20:31:25 +00:00
|
|
|
|
|
|
|
if (id)
|
|
|
|
ret += " [" + std::to_string(plr->iID) + "]";
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2020-09-01 22:37:09 +00:00
|
|
|
bool PlayerManager::isAccountInUse(int accountId) {
|
2020-11-17 23:16:16 +00:00
|
|
|
std::map<CNSocket*, Player*>::iterator it;
|
2021-03-16 21:06:10 +00:00
|
|
|
for (it = players.begin(); it != players.end(); it++) {
|
2020-11-17 23:16:16 +00:00
|
|
|
if (it->second->accountId == accountId)
|
2020-09-01 22:37:09 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void PlayerManager::exitDuplicate(int accountId) {
|
2020-11-17 23:16:16 +00:00
|
|
|
std::map<CNSocket*, Player*>::iterator it;
|
2020-10-01 23:37:50 +00:00
|
|
|
|
|
|
|
// disconnect any duplicate players
|
|
|
|
for (it = players.begin(); it != players.end(); it++) {
|
2020-11-17 23:16:16 +00:00
|
|
|
if (it->second->accountId == accountId) {
|
2020-09-01 22:37:09 +00:00
|
|
|
CNSocket* sock = it->first;
|
2020-10-01 23:37:50 +00:00
|
|
|
|
2020-09-01 22:37:09 +00:00
|
|
|
INITSTRUCT(sP_FE2CL_REP_PC_EXIT_DUPLICATE, resp);
|
|
|
|
resp.iErrorCode = 0;
|
|
|
|
sock->sendPacket((void*)&resp, P_FE2CL_REP_PC_EXIT_DUPLICATE, sizeof(sP_FE2CL_REP_PC_EXIT_DUPLICATE));
|
2020-10-01 23:37:50 +00:00
|
|
|
|
2020-09-01 22:37:09 +00:00
|
|
|
sock->kill();
|
2020-10-01 23:37:50 +00:00
|
|
|
CNShardServer::_killConnection(sock);
|
2020-12-08 00:53:21 +00:00
|
|
|
break;
|
2020-09-01 22:37:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-09-22 15:30:49 +00:00
|
|
|
|
2020-12-31 01:13:43 +00:00
|
|
|
// TODO: just call getPlayer() after getSockFromID()?
|
2020-10-04 23:54:08 +00:00
|
|
|
Player *PlayerManager::getPlayerFromID(int32_t iID) {
|
2020-12-31 01:13:43 +00:00
|
|
|
for (auto& pair : players)
|
2020-11-17 23:16:16 +00:00
|
|
|
if (pair.second->iID == iID)
|
|
|
|
return pair.second;
|
2020-10-04 23:54:08 +00:00
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
CNSocket *PlayerManager::getSockFromID(int32_t iID) {
|
2020-12-31 01:13:43 +00:00
|
|
|
for (auto& pair : players)
|
2020-11-17 23:16:16 +00:00
|
|
|
if (pair.second->iID == iID)
|
2020-10-02 21:00:36 +00:00
|
|
|
return pair.first;
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
2020-12-31 01:13:43 +00:00
|
|
|
|
|
|
|
CNSocket *PlayerManager::getSockFromName(std::string firstname, std::string lastname) {
|
|
|
|
for (auto& pair : players)
|
|
|
|
if (U16toU8(pair.second->PCStyle.szFirstName) == firstname
|
|
|
|
&& U16toU8(pair.second->PCStyle.szLastName) == lastname)
|
|
|
|
return pair.first;
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
CNSocket *PlayerManager::getSockFromAny(int by, int id, int uid, std::string firstname, std::string lastname) {
|
|
|
|
switch (by) {
|
|
|
|
case eCN_GM_TargetSearchBy__PC_ID:
|
|
|
|
assert(id != 0);
|
|
|
|
return getSockFromID(id);
|
|
|
|
case eCN_GM_TargetSearchBy__PC_UID: // account id; not player id
|
|
|
|
assert(uid != 0);
|
|
|
|
for (auto& pair : players)
|
|
|
|
if (pair.second->accountId == uid)
|
|
|
|
return pair.first;
|
|
|
|
case eCN_GM_TargetSearchBy__PC_Name:
|
|
|
|
assert(firstname != "" && lastname != ""); // XXX: remove this if we start messing around with edited names?
|
|
|
|
return getSockFromName(firstname, lastname);
|
|
|
|
}
|
|
|
|
|
|
|
|
// not found
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2020-08-24 22:02:07 +00:00
|
|
|
#pragma endregion
|
2021-03-16 21:06:10 +00:00
|
|
|
|
|
|
|
void PlayerManager::init() {
|
|
|
|
// register packet types
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_ENTER, enterPlayer);
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_LOADING_COMPLETE, loadPlayer);
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REP_LIVE_CHECK, heartbeatPlayer);
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_REGEN, revivePlayer);
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_EXIT, exitGame);
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_SPECIAL_STATE_SWITCH, setSpecialSwitchPlayer);
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VEHICLE_ON, enterPlayerVehicle);
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VEHICLE_OFF, exitPlayerVehicle);
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_CHANGE_MENTOR, changePlayerGuide);
|
|
|
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_FIRST_USE_FLAG_SET, setFirstUseFlag);
|
|
|
|
}
|