Implement most of the remaining client-side GM commands

* Muting a player's freechat
* Kicking players
* Querying info about a player
* Teleporting yourself to a player
* Teleporting a player to yourself
* Teleporting a player to another player
* Teleporting a player to arbitrary coords
* Teleporting a player to arbitrary coords in an arbitrary mapnum
* /unstick

Also:
* Renamed misleading setSpecialPlayer() to setValuePlayer()
* Revamped monitor logic
* Added server-side checks to account level 50 commands
* Made sure even trade chat is run through sanitizeText()
* Moved setSpecialState() closer to its calling functions
* Interpret client commands even in Buddy and Group chat (but not in
Trade chat)
This commit is contained in:
2020-12-31 02:13:43 +01:00
parent c78b3ca69f
commit a12acbb68f
9 changed files with 349 additions and 71 deletions

View File

@@ -37,16 +37,21 @@ void PlayerManager::init() {
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_MOVETRANSPORTATION, moveSliderPlayer);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_SLOPE, moveSlopePlayer);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_GOTO, gotoPlayer);
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_PC_SET_VALUE, setSpecialPlayer);
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_PC_SET_VALUE, setValuePlayer);
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_GM_REQ_PC_SPECIAL_STATE_SWITCH, setGMSpecialSwitchPlayer);
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_TARGET_PC_SPECIAL_STATE_ONOFF, setGMSpecialOnOff);
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);
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_PC_LOCATION, locatePlayer);
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_KICK_PLAYER, kickPlayer);
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_WARP_TO_PC, warpToPlayer);
REGISTER_SHARD_PACKET(P_CL2FE_GM_REQ_TARGET_PC_TELEPORT, teleportPlayer);
}
void PlayerManager::addPlayer(CNSocket* key, Player plr) {
@@ -664,6 +669,10 @@ void PlayerManager::gotoPlayer(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_GOTO))
return; // ignore the malformed packet
Player *plr = getPlayer(sock);
if (plr->accountLevel > 50)
return;
sP_CL2FE_REQ_PC_GOTO* gotoData = (sP_CL2FE_REQ_PC_GOTO*)data->buf;
INITSTRUCT(sP_FE2CL_REP_PC_GOTO_SUCC, response);
@@ -677,12 +686,15 @@ void PlayerManager::gotoPlayer(CNSocket* sock, CNPacketData* data) {
sendPlayerTo(sock, gotoData->iToX, gotoData->iToY, gotoData->iToZ, INSTANCE_OVERWORLD);
}
void PlayerManager::setSpecialPlayer(CNSocket* sock, CNPacketData* data) {
void PlayerManager::setValuePlayer(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_GM_REQ_PC_SET_VALUE))
return; // ignore the malformed packet
Player *plr = getPlayer(sock);
if (plr->accountLevel > 50)
return;
sP_CL2FE_GM_REQ_PC_SET_VALUE* setData = (sP_CL2FE_GM_REQ_PC_SET_VALUE*)data->buf;
Player *plr = PlayerManager::getPlayer(sock);
INITSTRUCT(sP_FE2CL_GM_REP_PC_SET_VALUE, response);
@@ -897,6 +909,29 @@ void PlayerManager::setGMSpecialSwitchPlayer(CNSocket* sock, CNPacketData* data)
setSpecialState(sock, data);
}
void PlayerManager::setSpecialState(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_GM_REQ_PC_SPECIAL_STATE_SWITCH))
return; // ignore the malformed packet
sP_CL2FE_GM_REQ_PC_SPECIAL_STATE_SWITCH* setData = (sP_CL2FE_GM_REQ_PC_SPECIAL_STATE_SWITCH*)data->buf;
Player *plr = getPlayer(sock);
// 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;
response.iPC_ID = setData->iPC_ID;
response.iReqSpecialStateFlag = setData->iSpecialStateFlag;
response.iSpecialState = plr->iSpecialState;
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_SPECIAL_STATE_SWITCH_SUCC, sizeof(sP_FE2CL_REP_PC_SPECIAL_STATE_SWITCH_SUCC));
sendToViewable(sock, (void*)&response, P_FE2CL_PC_SPECIAL_STATE_CHANGE, sizeof(sP_FE2CL_PC_SPECIAL_STATE_CHANGE));
}
void PlayerManager::changePlayerGuide(CNSocket *sock, CNPacketData *data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_CHANGE_MENTOR))
return;
@@ -945,6 +980,186 @@ void PlayerManager::setFirstUseFlag(CNSocket* sock, CNPacketData* data) {
plr->iFirstUseFlag[1] |= (1ULL << (flag->iFlagCode - 65));
}
void PlayerManager::setGMSpecialOnOff(CNSocket *sock, CNPacketData *data) {
if (data->size != sizeof(sP_CL2FE_GM_REQ_TARGET_PC_SPECIAL_STATE_ONOFF))
return; // sanity check
Player *plr = getPlayer(sock);
// access check
if (plr->accountLevel > 30)
return;
sP_CL2FE_GM_REQ_TARGET_PC_SPECIAL_STATE_ONOFF *req = (sP_CL2FE_GM_REQ_TARGET_PC_SPECIAL_STATE_ONOFF*)data->buf;
CNSocket *otherSock = getSockFromAny(req->eTargetSearchBy, req->iTargetPC_ID, req->iTargetPC_UID,
U16toU8(req->szTargetPC_FirstName), U16toU8(req->szTargetPC_LastName));
if (otherSock == nullptr) {
ChatManager::sendServerMessage(sock, "player to teleport not found");
return;
}
Player *otherPlr = getPlayer(otherSock);
if (req->iONOFF)
otherPlr->iSpecialState |= req->iSpecialStateFlag;
else
otherPlr->iSpecialState &= ~req->iSpecialStateFlag;
// this is only used for muting players, so no need to update the client since that logic is server-side
}
void PlayerManager::locatePlayer(CNSocket *sock, CNPacketData *data) {
if (data->size != sizeof(sP_CL2FE_GM_REQ_PC_LOCATION))
return; // sanity check
Player *plr = getPlayer(sock);
// access check
if (plr->accountLevel > 30)
return;
sP_CL2FE_GM_REQ_PC_LOCATION *req = (sP_CL2FE_GM_REQ_PC_LOCATION*)data->buf;
CNSocket *otherSock = getSockFromAny(req->eTargetSearchBy, req->iTargetPC_ID, req->iTargetPC_UID,
U16toU8(req->szTargetPC_FirstName), U16toU8(req->szTargetPC_LastName));
if (otherSock == nullptr) {
ChatManager::sendServerMessage(sock, "player not found");
return;
}
INITSTRUCT(sP_FE2CL_GM_REP_PC_LOCATION, resp);
Player *otherPlr = getPlayer(otherSock);
resp.iTargetPC_UID = otherPlr->accountId;
resp.iTargetPC_ID = otherPlr->iID;
resp.iShardID = 0; // sharding is unsupported
resp.iMapType = !!PLAYERID(otherPlr->instanceID); // private instance or not
resp.iMapID = PLAYERID(otherPlr->instanceID);
resp.iMapNum = MAPNUM(otherPlr->instanceID);
resp.iX = otherPlr->x;
resp.iY = otherPlr->y;
resp.iZ = otherPlr->z;
memcpy(resp.szTargetPC_FirstName, otherPlr->PCStyle.szFirstName, sizeof(resp.szTargetPC_FirstName));
memcpy(resp.szTargetPC_LastName, otherPlr->PCStyle.szLastName, sizeof(resp.szTargetPC_LastName));
sock->sendPacket((void*)&resp, P_FE2CL_GM_REP_PC_LOCATION, sizeof(sP_FE2CL_GM_REP_PC_LOCATION));
}
void PlayerManager::kickPlayer(CNSocket *sock, CNPacketData *data) {
if (data->size != sizeof(sP_CL2FE_GM_REQ_KICK_PLAYER))
return; // sanity check
Player *plr = getPlayer(sock);
// access check
if (plr->accountLevel > 30)
return;
sP_CL2FE_GM_REQ_KICK_PLAYER *req = (sP_CL2FE_GM_REQ_KICK_PLAYER*)data->buf;
CNSocket *otherSock = getSockFromAny(req->eTargetSearchBy, req->iTargetPC_ID, req->iTargetPC_UID,
U16toU8(req->szTargetPC_FirstName), U16toU8(req->szTargetPC_LastName));
if (otherSock == nullptr) {
ChatManager::sendServerMessage(sock, "player not found");
return;
}
Player *otherPlr = getPlayer(otherSock);
if (otherPlr->accountLevel > plr->accountLevel) {
ChatManager::sendServerMessage(sock, "player has higher access level");
return;
}
INITSTRUCT(sP_FE2CL_REP_PC_EXIT_SUCC, response);
response.iID = otherPlr->iID;
response.iExitCode = 3; // "a GM has terminated your connection"
// send to target player
otherSock->sendPacket((void*)&response, P_FE2CL_REP_PC_EXIT_SUCC, sizeof(sP_FE2CL_REP_PC_EXIT_SUCC));
}
void PlayerManager::warpToPlayer(CNSocket *sock, CNPacketData *data) {
if (data->size != sizeof(sP_CL2FE_REQ_PC_WARP_TO_PC))
return; // sanity check
Player *plr = getPlayer(sock);
// access check
if (plr->accountLevel > 30)
return;
sP_CL2FE_REQ_PC_WARP_TO_PC *req = (sP_CL2FE_REQ_PC_WARP_TO_PC*)data->buf;
Player *otherPlr = getPlayerFromID(req->iPC_ID);
if (otherPlr == nullptr) {
ChatManager::sendServerMessage(sock, "player not found");
return;
}
sendPlayerTo(sock, otherPlr->x, otherPlr->y, otherPlr->z, otherPlr->instanceID);
}
// GM teleport command
void PlayerManager::teleportPlayer(CNSocket *sock, CNPacketData *data) {
if (data->size != sizeof(sP_CL2FE_GM_REQ_TARGET_PC_TELEPORT))
return; // sanity check
Player *plr = getPlayer(sock);
// access check
if (plr->accountLevel > 30)
return;
sP_CL2FE_GM_REQ_TARGET_PC_TELEPORT *req = (sP_CL2FE_GM_REQ_TARGET_PC_TELEPORT*)data->buf;
// player to teleport
CNSocket *targetSock = getSockFromAny(req->eTargetPCSearchBy, req->iTargetPC_ID, req->iTargetPC_UID,
U16toU8(req->szTargetPC_FirstName), U16toU8(req->szTargetPC_LastName));
if (targetSock == nullptr) {
ChatManager::sendServerMessage(sock, "player to teleport not found");
return;
}
CNSocket *goalSock = nullptr;
Player *goalPlr = nullptr;
Player *targetPlr = nullptr;
uint64_t instance = plr->instanceID;
const int unstickRange = 400;
switch (req->eTeleportType) {
case eCN_GM_TeleportMapType__MyLocation:
sendPlayerTo(targetSock, plr->x, plr->y, plr->z);
break;
case eCN_GM_TeleportMapType__MapXYZ:
instance = req->iToMap;
// fallthrough
case eCN_GM_TeleportMapType__XYZ:
sendPlayerTo(targetSock, req->iToX, req->iToY, req->iToZ, instance);
break;
case eCN_GM_TeleportMapType__SomeoneLocation:
// player to teleport to
goalSock = getSockFromAny(req->eGoalPCSearchBy, req->iGoalPC_ID, req->iGoalPC_UID,
U16toU8(req->szGoalPC_FirstName), U16toU8(req->szGoalPC_LastName));
if (goalSock == nullptr) {
ChatManager::sendServerMessage(sock, "teleportation target player not found");
return;
}
goalPlr = getPlayer(goalSock);
sendPlayerTo(targetSock, goalPlr->x, goalPlr->y, goalPlr->z, goalPlr->instanceID);
break;
case eCN_GM_TeleportMapType__Unstick:
targetPlr = getPlayer(targetSock);
sendPlayerTo(targetSock, targetPlr->x - unstickRange/2 + rand() % unstickRange,
targetPlr->y - unstickRange/2 + rand() % unstickRange, targetPlr->z + 80);
break;
}
}
#pragma region Helper methods
Player *PlayerManager::getPlayer(CNSocket* key) {
if (players.find(key) != players.end())
@@ -1014,32 +1229,9 @@ void PlayerManager::exitDuplicate(int accountId) {
}
}
void PlayerManager::setSpecialState(CNSocket* sock, CNPacketData* data) {
if (data->size != sizeof(sP_CL2FE_GM_REQ_PC_SPECIAL_STATE_SWITCH))
return; // ignore the malformed packet
Player *plr = getPlayer(sock);
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;
response.iPC_ID = setData->iPC_ID;
response.iReqSpecialStateFlag = setData->iSpecialStateFlag;
response.iSpecialState = plr->iSpecialState;
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_SPECIAL_STATE_SWITCH_SUCC, sizeof(sP_FE2CL_REP_PC_SPECIAL_STATE_SWITCH_SUCC));
sendToViewable(sock, (void*)&response, P_FE2CL_PC_SPECIAL_STATE_CHANGE, sizeof(sP_FE2CL_PC_SPECIAL_STATE_CHANGE));
}
// TODO: just call getPlayer() after getSockFromID()?
Player *PlayerManager::getPlayerFromID(int32_t iID) {
for (auto& pair : PlayerManager::players)
for (auto& pair : players)
if (pair.second->iID == iID)
return pair.second;
@@ -1047,10 +1239,39 @@ Player *PlayerManager::getPlayerFromID(int32_t iID) {
}
CNSocket *PlayerManager::getSockFromID(int32_t iID) {
for (auto& pair : PlayerManager::players)
for (auto& pair : players)
if (pair.second->iID == iID)
return pair.first;
return nullptr;
}
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;
}
#pragma endregion