mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2024-12-22 13:33:35 +00:00
Friends List Overhaul (#630) v103
* Add friends list migration * Change friends to use charID Update friends table to use charID and not LWOOBJID variant. * Fix remove friend Fix remove friend and make the query more readable at a glance. * Add and remove friends in the container Properly add and remove friends in the player container * add enums * Add best friends and basic GM support V1 * Add more features * not online / doesnt exist implementation Implements the not online and invalid character response codes * Address players not being removed Fix an issue where players would not be marked as offline in the friends list due to the message not being sent in all circumstances. Tested changes on 3 clients, switching characters, logging out from character select, switching characters, world transfer and my friends list looked as it was supposed to. * Implement proper friends system Remove debug logs Track count of best friends Add best friends list cap of 5 Add config option and best friend update Add a config option and implement the last missing best friend serialization Added comments and fixed remove best friend bug Added some comments and addressed an issue where removing best friends would not remove them from your internal count of friends. properties and logs fixes whoops, had an issue send reply if already BFFs Send the correct objectID I really need to rename these Fix white space goon * Replace queries with unique ptrs * remove user from player container on deletion Remove the user from the player container when they delete their character.
This commit is contained in:
parent
5888c9f468
commit
24dbd3944d
@ -1,6 +1,6 @@
|
||||
PROJECT_VERSION_MAJOR=1
|
||||
PROJECT_VERSION_MINOR=0
|
||||
PROJECT_VERSION_PATCH=2
|
||||
PROJECT_VERSION_PATCH=3
|
||||
# LICENSE
|
||||
LICENSE=AGPL-3.0
|
||||
# The network version.
|
||||
|
@ -8,6 +8,10 @@
|
||||
#include "dServer.h"
|
||||
#include "GeneralUtils.h"
|
||||
#include "dLogger.h"
|
||||
#include "AddFriendResponseCode.h"
|
||||
#include "AddFriendResponseType.h"
|
||||
#include "RakString.h"
|
||||
#include "dConfig.h"
|
||||
|
||||
extern PlayerContainer playerContainer;
|
||||
|
||||
@ -22,20 +26,20 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
|
||||
if (!player) return;
|
||||
|
||||
//Get our friends list from the Db. Using a derived table since the friend of a player can be in either column.
|
||||
auto stmt = Database::CreatePreppedStmt(
|
||||
std::unique_ptr<sql::PreparedStatement> stmt(Database::CreatePreppedStmt(
|
||||
"SELECT fr.requested_player, best_friend, ci.name FROM "
|
||||
"(SELECT CASE "
|
||||
"WHEN player_id = ? THEN friend_id "
|
||||
"WHEN friend_id = ? THEN player_id "
|
||||
"END AS requested_player, best_friend FROM friends) AS fr "
|
||||
"JOIN charinfo AS ci ON ci.id = fr.requested_player "
|
||||
"WHERE fr.requested_player IS NOT NULL;");
|
||||
"WHERE fr.requested_player IS NOT NULL;"));
|
||||
stmt->setUInt(1, static_cast<uint32_t>(playerID));
|
||||
stmt->setUInt(2, static_cast<uint32_t>(playerID));
|
||||
|
||||
std::vector<FriendData> friends;
|
||||
|
||||
auto res = stmt->executeQuery();
|
||||
std::unique_ptr<sql::ResultSet> res(stmt->executeQuery());
|
||||
while (res->next()) {
|
||||
FriendData fd;
|
||||
fd.isFTP = false; // not a thing in DLU
|
||||
@ -43,18 +47,19 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
|
||||
GeneralUtils::SetBit(fd.friendID, static_cast<size_t>(eObjectBits::OBJECT_BIT_PERSISTENT));
|
||||
GeneralUtils::SetBit(fd.friendID, static_cast<size_t>(eObjectBits::OBJECT_BIT_CHARACTER));
|
||||
|
||||
fd.isBestFriend = res->getInt(2) == 2; //0 = friends, 1 = requested, 2 = bffs
|
||||
fd.isBestFriend = res->getInt(2) == 3; //0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs
|
||||
if (fd.isBestFriend) player->countOfBestFriends+=1;
|
||||
fd.friendName = res->getString(3);
|
||||
|
||||
//Now check if they're online:
|
||||
auto fr = playerContainer.GetPlayerData(fd.friendID);
|
||||
Game::logger->Log("ChatPacketHandler", "friend is %llu\n", fd.friendID);
|
||||
|
||||
if (fr) {
|
||||
fd.isOnline = true;
|
||||
fd.zoneID = fr->zoneID;
|
||||
|
||||
//Since this friend is online, we need to update them on the fact that we've just logged in:
|
||||
SendFriendUpdate(fr, player, 1);
|
||||
SendFriendUpdate(fr, player, 1, fd.isBestFriend);
|
||||
}
|
||||
else {
|
||||
fd.isOnline = false;
|
||||
@ -64,11 +69,6 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
|
||||
friends.push_back(fd);
|
||||
}
|
||||
|
||||
delete res;
|
||||
res = nullptr;
|
||||
delete stmt;
|
||||
stmt = nullptr;
|
||||
|
||||
//Now, we need to send the friendlist to the server they came from:
|
||||
CBITSTREAM;
|
||||
PacketUtils::WriteHeader(bitStream, CHAT_INTERNAL, MSG_CHAT_INTERNAL_ROUTE_TO_PLAYER);
|
||||
@ -91,18 +91,150 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
|
||||
}
|
||||
|
||||
void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
|
||||
auto maxNumberOfBestFriendsAsString = Game::config->GetValue("max_number_of_best_friends");
|
||||
// If this config option doesn't exist, default to 5 which is what live used.
|
||||
auto maxNumberOfBestFriends = maxNumberOfBestFriendsAsString != "" ? std::stoi(maxNumberOfBestFriendsAsString) : 5U;
|
||||
CINSTREAM;
|
||||
LWOOBJID playerID;
|
||||
inStream.Read(playerID);
|
||||
inStream.Read(playerID);
|
||||
std::string playerName = PacketUtils::ReadString(0x14, packet, true);
|
||||
//There's another bool here to determine if it's a best friend request, but we're not handling it right now.
|
||||
LWOOBJID requestorPlayerID;
|
||||
inStream.Read(requestorPlayerID);
|
||||
inStream.Read(requestorPlayerID);
|
||||
uint32_t spacing{};
|
||||
inStream.Read(spacing);
|
||||
std::string playerName = "";
|
||||
uint16_t character;
|
||||
bool noMoreLettersInName = false;
|
||||
|
||||
//We need to check to see if the player is actually online or not:
|
||||
auto targetData = playerContainer.GetPlayerData(playerName);
|
||||
if (targetData) {
|
||||
SendFriendRequest(targetData, playerContainer.GetPlayerData(playerID));
|
||||
for (uint32_t j = 0; j < 33; j++) {
|
||||
inStream.Read(character);
|
||||
if (character == '\0') noMoreLettersInName = true;
|
||||
if (!noMoreLettersInName) playerName.push_back(static_cast<char>(character));
|
||||
}
|
||||
|
||||
char isBestFriendRequest{};
|
||||
inStream.Read(isBestFriendRequest);
|
||||
|
||||
auto requestor = playerContainer.GetPlayerData(requestorPlayerID);
|
||||
std::unique_ptr<PlayerData> requestee(playerContainer.GetPlayerData(playerName));
|
||||
|
||||
// Check if player is online first
|
||||
if (isBestFriendRequest && !requestee) {
|
||||
for (auto friendDataCandidate : requestor->friends) {
|
||||
if (friendDataCandidate.friendName == playerName) {
|
||||
requestee.reset(new PlayerData());
|
||||
// Setup the needed info since you can add a best friend offline.
|
||||
requestee->playerID = friendDataCandidate.friendID;
|
||||
requestee->playerName = RakNet::RakString(friendDataCandidate.friendName.c_str());
|
||||
requestee->zoneID = LWOZONEID();
|
||||
|
||||
FriendData requesteeFriendData{};
|
||||
requesteeFriendData.friendID = requestor->playerID;
|
||||
requesteeFriendData.friendName = requestor->playerName;
|
||||
requesteeFriendData.isFTP = false;
|
||||
requesteeFriendData.isOnline = false;
|
||||
requesteeFriendData.zoneID = requestor->zoneID;
|
||||
requestee->friends.push_back(requesteeFriendData);
|
||||
requestee->sysAddr = UNASSIGNED_SYSTEM_ADDRESS;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If at this point we dont have a target, then they arent online and we cant send the request.
|
||||
// Send the response code that corresponds to what the error is.
|
||||
if (!requestee) {
|
||||
std::unique_ptr<sql::PreparedStatement> nameQuery(Database::CreatePreppedStmt("SELECT name from charinfo where name = ?;"));
|
||||
nameQuery->setString(1, playerName);
|
||||
std::unique_ptr<sql::ResultSet> result(nameQuery->executeQuery());
|
||||
|
||||
requestee.reset(new PlayerData());
|
||||
requestee->playerName = RakNet::RakString(playerName.c_str());
|
||||
|
||||
SendFriendResponse(requestor, requestee.get(), result->next() ? AddFriendResponseType::NOTONLINE : AddFriendResponseType::INVALIDCHARACTER);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBestFriendRequest) {
|
||||
std::unique_ptr<sql::PreparedStatement> friendUpdate(Database::CreatePreppedStmt("SELECT * FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;"));
|
||||
friendUpdate->setUInt(1, static_cast<uint32_t>(requestorPlayerID));
|
||||
friendUpdate->setUInt(2, static_cast<uint32_t>(requestee->playerID));
|
||||
friendUpdate->setUInt(3, static_cast<uint32_t>(requestee->playerID));
|
||||
friendUpdate->setUInt(4, static_cast<uint32_t>(requestorPlayerID));
|
||||
std::unique_ptr<sql::ResultSet> result(friendUpdate->executeQuery());
|
||||
|
||||
LWOOBJID queryPlayerID = LWOOBJID_EMPTY;
|
||||
LWOOBJID queryFriendID = LWOOBJID_EMPTY;
|
||||
uint8_t oldBestFriendStatus{};
|
||||
uint8_t bestFriendStatus{};
|
||||
|
||||
if (result->next()) {
|
||||
// Get the IDs
|
||||
queryPlayerID = result->getInt(1);
|
||||
queryFriendID = result->getInt(2);
|
||||
oldBestFriendStatus = result->getInt(3);
|
||||
bestFriendStatus = oldBestFriendStatus;
|
||||
|
||||
// Set the bits
|
||||
GeneralUtils::SetBit(queryPlayerID, static_cast<size_t>(eObjectBits::OBJECT_BIT_CHARACTER));
|
||||
GeneralUtils::SetBit(queryPlayerID, static_cast<size_t>(eObjectBits::OBJECT_BIT_PERSISTENT));
|
||||
GeneralUtils::SetBit(queryFriendID, static_cast<size_t>(eObjectBits::OBJECT_BIT_CHARACTER));
|
||||
GeneralUtils::SetBit(queryFriendID, static_cast<size_t>(eObjectBits::OBJECT_BIT_PERSISTENT));
|
||||
|
||||
// Since this player can either be the friend of someone else or be friends with someone else
|
||||
// their column in the database determines what bit gets set. When the value hits 3, they
|
||||
// are now best friends with the other player.
|
||||
if (queryPlayerID == requestorPlayerID) {
|
||||
bestFriendStatus |= 1ULL << 0;
|
||||
} else {
|
||||
bestFriendStatus |= 1ULL << 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Only do updates if there was a change in the bff status.
|
||||
if (oldBestFriendStatus != bestFriendStatus) {
|
||||
if (requestee->countOfBestFriends >= maxNumberOfBestFriends || requestor->countOfBestFriends >= maxNumberOfBestFriends) {
|
||||
if (requestee->countOfBestFriends >= maxNumberOfBestFriends) {
|
||||
SendFriendResponse(requestor, requestee.get(), AddFriendResponseType::THEIRFRIENDLISTFULL, false);
|
||||
}
|
||||
if (requestor->countOfBestFriends >= maxNumberOfBestFriends) {
|
||||
SendFriendResponse(requestor, requestee.get(), AddFriendResponseType::YOURFRIENDSLISTFULL, false);
|
||||
}
|
||||
} else {
|
||||
// Then update the database with this new info.
|
||||
std::unique_ptr<sql::PreparedStatement> updateQuery(Database::CreatePreppedStmt("UPDATE friends SET best_friend = ? WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;"));
|
||||
updateQuery->setUInt(1, bestFriendStatus);
|
||||
updateQuery->setUInt(2, static_cast<uint32_t>(requestorPlayerID));
|
||||
updateQuery->setUInt(3, static_cast<uint32_t>(requestee->playerID));
|
||||
updateQuery->setUInt(4, static_cast<uint32_t>(requestee->playerID));
|
||||
updateQuery->setUInt(5, static_cast<uint32_t>(requestorPlayerID));
|
||||
updateQuery->executeUpdate();
|
||||
// Sent the best friend update here if the value is 3
|
||||
if (bestFriendStatus == 3U) {
|
||||
requestee->countOfBestFriends+=1;
|
||||
requestor->countOfBestFriends+=1;
|
||||
if (requestee->sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestee.get(), requestor, AddFriendResponseType::ACCEPTED, false, true);
|
||||
if (requestor->sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee.get(), AddFriendResponseType::ACCEPTED, false, true);
|
||||
for (auto& friendData : requestor->friends) {
|
||||
if (friendData.friendID == requestee->playerID) {
|
||||
friendData.isBestFriend = true;
|
||||
}
|
||||
}
|
||||
for (auto& friendData : requestee->friends) {
|
||||
if (friendData.friendID == requestor->playerID) {
|
||||
friendData.isBestFriend = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (requestor->sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee.get(), AddFriendResponseType::WAITINGAPPROVAL, true, true);
|
||||
}
|
||||
} else {
|
||||
// Do not send this if we are requesting to be a best friend.
|
||||
SendFriendRequest(requestee.get(), requestor);
|
||||
}
|
||||
|
||||
// If the player is actually a player and not a ghost one defined above, release it from being deleted.
|
||||
if (requestee->sysAddr != UNASSIGNED_SYSTEM_ADDRESS) requestee.release();
|
||||
}
|
||||
|
||||
void ChatPacketHandler::HandleFriendResponse(Packet* packet) {
|
||||
@ -110,53 +242,75 @@ void ChatPacketHandler::HandleFriendResponse(Packet* packet) {
|
||||
LWOOBJID playerID;
|
||||
inStream.Read(playerID);
|
||||
inStream.Read(playerID);
|
||||
|
||||
uint8_t responseCode = packet->data[0x14];
|
||||
|
||||
AddFriendResponseCode clientResponseCode = static_cast<AddFriendResponseCode>(packet->data[0x14]);
|
||||
std::string friendName = PacketUtils::ReadString(0x15, packet, true);
|
||||
|
||||
//Now to try and find both of these:
|
||||
auto goonA = playerContainer.GetPlayerData(playerID);
|
||||
auto goonB = playerContainer.GetPlayerData(friendName);
|
||||
if (!goonA || !goonB) return;
|
||||
auto requestor = playerContainer.GetPlayerData(playerID);
|
||||
auto requestee = playerContainer.GetPlayerData(friendName);
|
||||
if (!requestor || !requestee) return;
|
||||
|
||||
if (responseCode != 0) return; //If we're not accepting the request, end here, do not insert to friends table.
|
||||
|
||||
for (auto friendData : goonA->friends) {
|
||||
if (friendData.friendID == goonB->playerID) return;
|
||||
AddFriendResponseType serverResponseCode{};
|
||||
uint8_t isAlreadyBestFriends = 0U;
|
||||
// We need to convert this response code to one we can actually send back to the client.
|
||||
switch (clientResponseCode) {
|
||||
case AddFriendResponseCode::ACCEPTED:
|
||||
serverResponseCode = AddFriendResponseType::ACCEPTED;
|
||||
break;
|
||||
case AddFriendResponseCode::BUSY:
|
||||
serverResponseCode = AddFriendResponseType::BUSY;
|
||||
break;
|
||||
case AddFriendResponseCode::CANCELLED:
|
||||
serverResponseCode = AddFriendResponseType::CANCELLED;
|
||||
break;
|
||||
case AddFriendResponseCode::REJECTED:
|
||||
serverResponseCode = AddFriendResponseType::DECLINED;
|
||||
break;
|
||||
}
|
||||
|
||||
for (auto friendData : goonB->friends) {
|
||||
if (friendData.friendID == goonA->playerID) return;
|
||||
// Now that we have handled the base cases, we need to check the other cases.
|
||||
if (serverResponseCode == AddFriendResponseType::ACCEPTED) {
|
||||
for (auto friendData : requestor->friends) {
|
||||
if (friendData.friendID == requestee->playerID) {
|
||||
serverResponseCode = AddFriendResponseType::ALREADYFRIEND;
|
||||
if (friendData.isBestFriend) {
|
||||
isAlreadyBestFriends = 1U;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the goons to their friends list
|
||||
FriendData goonAData;
|
||||
goonAData.zoneID = goonA->zoneID;
|
||||
goonAData.friendID = goonA->playerID;
|
||||
goonAData.friendName = goonA->playerName;
|
||||
goonAData.isBestFriend = false;
|
||||
goonAData.isFTP = false;
|
||||
goonAData.isOnline = true;
|
||||
goonB->friends.push_back(goonAData);
|
||||
// This message is NOT sent for best friends and is handled differently for those requests.
|
||||
if (serverResponseCode == AddFriendResponseType::ACCEPTED) {
|
||||
// Add the each player to the others friend list.
|
||||
FriendData requestorData;
|
||||
requestorData.zoneID = requestor->zoneID;
|
||||
requestorData.friendID = requestor->playerID;
|
||||
requestorData.friendName = requestor->playerName;
|
||||
requestorData.isBestFriend = false;
|
||||
requestorData.isFTP = false;
|
||||
requestorData.isOnline = true;
|
||||
requestee->friends.push_back(requestorData);
|
||||
|
||||
FriendData goonBData;
|
||||
goonBData.zoneID = goonB->zoneID;
|
||||
goonBData.friendID = goonB->playerID;
|
||||
goonBData.friendName = goonB->playerName;
|
||||
goonBData.isBestFriend = false;
|
||||
goonBData.isFTP = false;
|
||||
goonBData.isOnline = true;
|
||||
goonA->friends.push_back(goonBData);
|
||||
FriendData requesteeData;
|
||||
requesteeData.zoneID = requestee->zoneID;
|
||||
requesteeData.friendID = requestee->playerID;
|
||||
requesteeData.friendName = requestee->playerName;
|
||||
requesteeData.isBestFriend = false;
|
||||
requesteeData.isFTP = false;
|
||||
requesteeData.isOnline = true;
|
||||
requestor->friends.push_back(requesteeData);
|
||||
|
||||
std::unique_ptr<sql::PreparedStatement> statement(Database::CreatePreppedStmt("INSERT IGNORE INTO `friends` (`player_id`, `friend_id`, `best_friend`) VALUES (?,?,?);"));
|
||||
statement->setUInt(1, static_cast<uint32_t>(requestor->playerID));
|
||||
statement->setUInt(2, static_cast<uint32_t>(requestee->playerID));
|
||||
statement->setInt(3, 0);
|
||||
statement->execute();
|
||||
}
|
||||
|
||||
SendFriendResponse(goonB, goonA, responseCode);
|
||||
SendFriendResponse(goonA, goonB, responseCode); //Do we need to send it to both? I think so so both get the updated friendlist but... idk.
|
||||
|
||||
auto stmt = Database::CreatePreppedStmt("INSERT IGNORE INTO `friends` (`player_id`, `friend_id`, `best_friend`) VALUES (?,?,?)");
|
||||
stmt->setUInt(1, static_cast<uint32_t>(goonA->playerID));
|
||||
stmt->setUInt(2, static_cast<uint32_t>(goonB->playerID));
|
||||
stmt->setInt(3, 0);
|
||||
stmt->execute();
|
||||
delete stmt;
|
||||
if (serverResponseCode != AddFriendResponseType::DECLINED) SendFriendResponse(requestor, requestee, serverResponseCode, isAlreadyBestFriends);
|
||||
if (serverResponseCode != AddFriendResponseType::ALREADYFRIEND) SendFriendResponse(requestee, requestor, serverResponseCode, isAlreadyBestFriends);
|
||||
}
|
||||
|
||||
void ChatPacketHandler::HandleRemoveFriend(Packet* packet) {
|
||||
@ -168,11 +322,11 @@ void ChatPacketHandler::HandleRemoveFriend(Packet* packet) {
|
||||
|
||||
//we'll have to query the db here to find the user, since you can delete them while they're offline.
|
||||
//First, we need to find their ID:
|
||||
auto stmt = Database::CreatePreppedStmt("SELECT id FROM charinfo WHERE name=? LIMIT 1;");
|
||||
std::unique_ptr<sql::PreparedStatement> stmt(Database::CreatePreppedStmt("SELECT id FROM charinfo WHERE name=? LIMIT 1;"));
|
||||
stmt->setString(1, friendName.c_str());
|
||||
|
||||
LWOOBJID friendID = 0;
|
||||
auto res = stmt->executeQuery();
|
||||
std::unique_ptr<sql::ResultSet> res(stmt->executeQuery());
|
||||
while (res->next()) {
|
||||
friendID = res->getUInt(1);
|
||||
}
|
||||
@ -181,27 +335,20 @@ void ChatPacketHandler::HandleRemoveFriend(Packet* packet) {
|
||||
GeneralUtils::SetBit(friendID, static_cast<size_t>(eObjectBits::OBJECT_BIT_PERSISTENT));
|
||||
GeneralUtils::SetBit(friendID, static_cast<size_t>(eObjectBits::OBJECT_BIT_CHARACTER));
|
||||
|
||||
delete res;
|
||||
res = nullptr;
|
||||
delete stmt;
|
||||
stmt = nullptr;
|
||||
|
||||
auto deletestmt = Database::CreatePreppedStmt("DELETE FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;");
|
||||
std::unique_ptr<sql::PreparedStatement> deletestmt(Database::CreatePreppedStmt("DELETE FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;"));
|
||||
deletestmt->setUInt(1, static_cast<uint32_t>(playerID));
|
||||
deletestmt->setUInt(2, static_cast<uint32_t>(friendID));
|
||||
deletestmt->setUInt(3, static_cast<uint32_t>(friendID));
|
||||
deletestmt->setUInt(4, static_cast<uint32_t>(playerID));
|
||||
deletestmt->execute();
|
||||
|
||||
delete deletestmt;
|
||||
deletestmt = nullptr;
|
||||
|
||||
//Now, we need to send an update to notify the sender (and possibly, receiver) that their friendship has been ended:
|
||||
auto goonA = playerContainer.GetPlayerData(playerID);
|
||||
if (goonA) {
|
||||
// Remove the friend from our list of friends
|
||||
for (auto friendData = goonA->friends.begin(); friendData != goonA->friends.end(); friendData++) {
|
||||
if ((*friendData).friendID == friendID) {
|
||||
if ((*friendData).isBestFriend) --goonA->countOfBestFriends;
|
||||
goonA->friends.erase(friendData);
|
||||
break;
|
||||
}
|
||||
@ -214,6 +361,7 @@ void ChatPacketHandler::HandleRemoveFriend(Packet* packet) {
|
||||
// Do it again for other person
|
||||
for (auto friendData = goonB->friends.begin(); friendData != goonB->friends.end(); friendData++) {
|
||||
if ((*friendData).friendID == playerID) {
|
||||
if ((*friendData).isBestFriend) --goonB->countOfBestFriends;
|
||||
goonB->friends.erase(friendData);
|
||||
break;
|
||||
}
|
||||
@ -766,7 +914,7 @@ void ChatPacketHandler::SendTeamSetOffWorldFlag(PlayerData* receiver, LWOOBJID i
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendFriendUpdate(PlayerData* friendData, PlayerData* playerData, uint8_t notifyType) {
|
||||
void ChatPacketHandler::SendFriendUpdate(PlayerData* friendData, PlayerData* playerData, uint8_t notifyType, uint8_t isBestFriend) {
|
||||
/*chat notification is displayed if log in / out and friend is updated in friends list
|
||||
[u8] - update type
|
||||
Update types
|
||||
@ -804,19 +952,20 @@ void ChatPacketHandler::SendFriendUpdate(PlayerData* friendData, PlayerData* pla
|
||||
bitStream.Write(playerData->zoneID.GetCloneID());
|
||||
}
|
||||
|
||||
bitStream.Write<uint8_t>(0); //isBFF
|
||||
bitStream.Write<uint8_t>(isBestFriend); //isBFF
|
||||
bitStream.Write<uint8_t>(0); //isFTP
|
||||
|
||||
SystemAddress sysAddr = friendData->sysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendFriendRequest(PlayerData* receiver, PlayerData* sender, bool isBFFReq) {
|
||||
void ChatPacketHandler::SendFriendRequest(PlayerData* receiver, PlayerData* sender) {
|
||||
if (!receiver || !sender) return;
|
||||
|
||||
//Make sure people aren't requesting people that they're already friends with:
|
||||
for (auto fr : receiver->friends) {
|
||||
if (fr.friendID == sender->playerID) {
|
||||
SendFriendResponse(sender, receiver, AddFriendResponseType::ALREADYFRIEND, fr.isBestFriend);
|
||||
return; //we have this player as a friend, yeet this function so it doesn't send another request.
|
||||
}
|
||||
}
|
||||
@ -828,29 +977,33 @@ void ChatPacketHandler::SendFriendRequest(PlayerData* receiver, PlayerData* send
|
||||
//portion that will get routed:
|
||||
PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_ADD_FRIEND_REQUEST);
|
||||
PacketUtils::WritePacketWString(sender->playerName.C_String(), 33, &bitStream);
|
||||
bitStream.Write<uint8_t>(0);
|
||||
bitStream.Write<uint8_t>(0); // This is a BFF flag however this is unused in live and does not have an implementation client side.
|
||||
|
||||
SystemAddress sysAddr = receiver->sysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendFriendResponse(PlayerData* receiver, PlayerData* sender, uint8_t responseCode) {
|
||||
void ChatPacketHandler::SendFriendResponse(PlayerData* receiver, PlayerData* sender, AddFriendResponseType responseCode, uint8_t isBestFriendsAlready, uint8_t isBestFriendRequest) {
|
||||
if (!receiver || !sender) return;
|
||||
|
||||
CBITSTREAM;
|
||||
PacketUtils::WriteHeader(bitStream, CHAT_INTERNAL, MSG_CHAT_INTERNAL_ROUTE_TO_PLAYER);
|
||||
bitStream.Write(receiver->playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
// Portion that will get routed:
|
||||
PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_ADD_FRIEND_RESPONSE);
|
||||
bitStream.Write<uint8_t>(responseCode);
|
||||
bitStream.Write<uint8_t>(1); //isOnline
|
||||
bitStream.Write(responseCode);
|
||||
// For all requests besides accepted, write a flag that says whether or not we are already best friends with the receiver.
|
||||
bitStream.Write<uint8_t>(responseCode != AddFriendResponseType::ACCEPTED ? isBestFriendsAlready : sender->sysAddr != UNASSIGNED_SYSTEM_ADDRESS);
|
||||
// Then write the player name
|
||||
PacketUtils::WritePacketWString(sender->playerName.C_String(), 33, &bitStream);
|
||||
bitStream.Write(sender->playerID);
|
||||
bitStream.Write(sender->zoneID);
|
||||
bitStream.Write<uint8_t>(0); //isBFF
|
||||
bitStream.Write<uint8_t>(0); //isFTP
|
||||
|
||||
// Then if this is an acceptance code, write the following extra info.
|
||||
if (responseCode == AddFriendResponseType::ACCEPTED) {
|
||||
bitStream.Write(sender->playerID);
|
||||
bitStream.Write(sender->zoneID);
|
||||
bitStream.Write(isBestFriendRequest); //isBFF
|
||||
bitStream.Write<uint8_t>(0); //isFTP
|
||||
}
|
||||
SystemAddress sysAddr = receiver->sysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "BitStream.h"
|
||||
|
||||
struct PlayerData;
|
||||
enum class AddFriendResponseType : uint8_t;
|
||||
|
||||
namespace ChatPacketHandler {
|
||||
void HandleFriendlistRequest(Packet* packet);
|
||||
@ -31,10 +32,9 @@ namespace ChatPacketHandler {
|
||||
void SendTeamSetOffWorldFlag(PlayerData* receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID);
|
||||
|
||||
//FriendData is the player we're SENDING this stuff to. Player is the friend that changed state.
|
||||
void SendFriendUpdate(PlayerData* friendData, PlayerData* playerData, uint8_t notifyType);
|
||||
void SendFriendUpdate(PlayerData* friendData, PlayerData* playerData, uint8_t notifyType, uint8_t isBestFriend);
|
||||
|
||||
void SendFriendRequest(PlayerData* receiver, PlayerData* sender, bool isBFFReq = false);
|
||||
void SendFriendResponse(PlayerData* receiver, PlayerData* sender, uint8_t responseCode = 3);
|
||||
void SendFriendRequest(PlayerData* receiver, PlayerData* sender);
|
||||
void SendFriendResponse(PlayerData* receiver, PlayerData* sender, AddFriendResponseType responseCode, uint8_t isBestFriendsAlready = 0U, uint8_t isBestFriendRequest = 0U);
|
||||
void SendRemoveFriend(PlayerData* receiver, std::string& personToRemove, bool isSuccessful);
|
||||
};
|
||||
|
||||
|
@ -59,7 +59,7 @@ void PlayerContainer::RemovePlayer(Packet* packet) {
|
||||
//if (!fr.isOnline) continue;
|
||||
|
||||
auto fd = this->GetPlayerData(fr.friendID);
|
||||
if (fd) ChatPacketHandler::SendFriendUpdate(fd, player, 0);
|
||||
if (fd) ChatPacketHandler::SendFriendUpdate(fd, player, 0, fr.isBestFriend);
|
||||
}
|
||||
|
||||
auto* team = GetTeam(playerID);
|
||||
|
@ -14,6 +14,7 @@ struct PlayerData {
|
||||
LWOZONEID zoneID;
|
||||
std::vector<FriendData> friends;
|
||||
time_t muteExpire;
|
||||
uint8_t countOfBestFriends = 0;
|
||||
};
|
||||
|
||||
struct TeamData {
|
||||
|
15
dCommon/AddFriendResponseCode.h
Normal file
15
dCommon/AddFriendResponseCode.h
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __ADDFRIENDRESPONSECODE__H__
|
||||
#define __ADDFRIENDRESPONSECODE__H__
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum class AddFriendResponseCode : uint8_t {
|
||||
ACCEPTED = 0,
|
||||
REJECTED,
|
||||
BUSY,
|
||||
CANCELLED
|
||||
};
|
||||
|
||||
#endif //!__ADDFRIENDRESPONSECODE__H__
|
24
dCommon/AddFriendResponseType.h
Normal file
24
dCommon/AddFriendResponseType.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __ADDFRIENDRESPONSETYPE__H__
|
||||
#define __ADDFRIENDRESPONSETYPE__H__
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum class AddFriendResponseType : uint8_t {
|
||||
ACCEPTED = 0,
|
||||
ALREADYFRIEND,
|
||||
INVALIDCHARACTER,
|
||||
GENERALERROR,
|
||||
YOURFRIENDSLISTFULL,
|
||||
THEIRFRIENDLISTFULL,
|
||||
DECLINED,
|
||||
BUSY,
|
||||
NOTONLINE,
|
||||
WAITINGAPPROVAL,
|
||||
MYTHRAN,
|
||||
CANCELLED,
|
||||
FRIENDISFREETRIAL
|
||||
};
|
||||
|
||||
#endif //!__ADDFRIENDRESPONSETYPE__H__
|
@ -401,7 +401,7 @@ void EntityManager::ConstructEntity(Entity* entity, const SystemAddress& sysAddr
|
||||
Game::server->Send(&stream, sysAddr, false);
|
||||
}
|
||||
|
||||
PacketUtils::SavePacket("[24]_"+std::to_string(entity->GetObjectID()) + "_" + std::to_string(m_SerializationCounter) + ".bin", (char*)stream.GetData(), stream.GetNumberOfBytesUsed());
|
||||
// PacketUtils::SavePacket("[24]_"+std::to_string(entity->GetObjectID()) + "_" + std::to_string(m_SerializationCounter) + ".bin", (char*)stream.GetData(), stream.GetNumberOfBytesUsed());
|
||||
|
||||
if (entity->IsPlayer())
|
||||
{
|
||||
|
@ -369,10 +369,8 @@ void UserManager::DeleteCharacter(const SystemAddress& sysAddr, Packet* packet)
|
||||
}
|
||||
|
||||
LWOOBJID objectID = PacketUtils::ReadPacketS64(8, packet);
|
||||
objectID = GeneralUtils::ClearBit(objectID, OBJECT_BIT_CHARACTER);
|
||||
objectID = GeneralUtils::ClearBit(objectID, OBJECT_BIT_PERSISTENT);
|
||||
|
||||
uint32_t charID = static_cast<uint32_t>(objectID);
|
||||
uint32_t charID = static_cast<uint32_t>(objectID);
|
||||
|
||||
Game::logger->Log("UserManager", "Received char delete req for ID: %llu (%u)\n", objectID, charID);
|
||||
|
||||
//Check if this user has this character:
|
||||
@ -402,10 +400,14 @@ void UserManager::DeleteCharacter(const SystemAddress& sysAddr, Packet* packet)
|
||||
}
|
||||
{
|
||||
sql::PreparedStatement* stmt = Database::CreatePreppedStmt("DELETE FROM friends WHERE player_id=? OR friend_id=?;");
|
||||
stmt->setUInt64(1, charID);
|
||||
stmt->setUInt64(2, charID);
|
||||
stmt->setUInt(1, charID);
|
||||
stmt->setUInt(2, charID);
|
||||
stmt->execute();
|
||||
delete stmt;
|
||||
CBITSTREAM;
|
||||
PacketUtils::WriteHeader(bitStream, CHAT_INTERNAL, MSG_CHAT_INTERNAL_PLAYER_REMOVED_NOTIFICATION);
|
||||
bitStream.Write(objectID);
|
||||
Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
|
||||
}
|
||||
{
|
||||
sql::PreparedStatement* stmt = Database::CreatePreppedStmt("DELETE FROM leaderboard WHERE character_id=?;");
|
||||
|
@ -102,7 +102,7 @@ PropertySelectQueryProperty PropertyEntranceComponent::SetPropertyValues(Propert
|
||||
return property;
|
||||
}
|
||||
|
||||
std::string PropertyEntranceComponent::BuildQuery(Entity* entity, int32_t sortMethod, std::string customQuery, bool wantLimits) {
|
||||
std::string PropertyEntranceComponent::BuildQuery(Entity* entity, int32_t sortMethod, Character* character, std::string customQuery, bool wantLimits) {
|
||||
std::string base;
|
||||
if (customQuery == "") {
|
||||
base = baseQueryForProperties;
|
||||
@ -115,15 +115,13 @@ std::string PropertyEntranceComponent::BuildQuery(Entity* entity, int32_t sortMe
|
||||
|
||||
auto friendsListQuery = Database::CreatePreppedStmt("SELECT * FROM (SELECT CASE WHEN player_id = ? THEN friend_id WHEN friend_id = ? THEN player_id END AS requested_player FROM friends ) AS fr WHERE requested_player IS NOT NULL ORDER BY requested_player DESC;");
|
||||
|
||||
friendsListQuery->setInt64(1, entity->GetObjectID());
|
||||
friendsListQuery->setInt64(2, entity->GetObjectID());
|
||||
friendsListQuery->setUInt(1, character->GetID());
|
||||
friendsListQuery->setUInt(2, character->GetID());
|
||||
|
||||
auto friendsListQueryResult = friendsListQuery->executeQuery();
|
||||
|
||||
while (friendsListQueryResult->next()) {
|
||||
auto playerIDToConvert = friendsListQueryResult->getInt64(1);
|
||||
playerIDToConvert = GeneralUtils::ClearBit(playerIDToConvert, OBJECT_BIT_CHARACTER);
|
||||
playerIDToConvert = GeneralUtils::ClearBit(playerIDToConvert, OBJECT_BIT_PERSISTENT);
|
||||
auto playerIDToConvert = friendsListQueryResult->getInt(1);
|
||||
friendsList = friendsList + std::to_string(playerIDToConvert) + ",";
|
||||
}
|
||||
// Replace trailing comma with the closing parenthesis.
|
||||
@ -193,7 +191,7 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl
|
||||
|
||||
entries.push_back(playerEntry);
|
||||
|
||||
const auto query = BuildQuery(entity, sortMethod);
|
||||
const auto query = BuildQuery(entity, sortMethod, character);
|
||||
|
||||
auto propertyLookup = Database::CreatePreppedStmt(query);
|
||||
|
||||
@ -262,17 +260,17 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl
|
||||
// Query to get friend and best friend fields
|
||||
auto friendCheck = Database::CreatePreppedStmt("SELECT best_friend FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?)");
|
||||
|
||||
friendCheck->setInt64(1, entity->GetObjectID());
|
||||
friendCheck->setInt64(2, ownerObjId);
|
||||
friendCheck->setInt64(3, ownerObjId);
|
||||
friendCheck->setInt64(4, entity->GetObjectID());
|
||||
friendCheck->setUInt(1, character->GetID());
|
||||
friendCheck->setUInt(2, ownerObjId);
|
||||
friendCheck->setUInt(3, ownerObjId);
|
||||
friendCheck->setUInt(4, character->GetID());
|
||||
|
||||
auto friendResult = friendCheck->executeQuery();
|
||||
|
||||
// If we got a result than the two players are friends.
|
||||
if (friendResult->next()) {
|
||||
isFriend = true;
|
||||
if (friendResult->getInt(1) == 2) {
|
||||
if (friendResult->getInt(1) == 3) {
|
||||
isBestFriend = true;
|
||||
}
|
||||
}
|
||||
@ -326,7 +324,7 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl
|
||||
// Query here is to figure out whether or not to display the button to go to the next page or not.
|
||||
int32_t numberOfProperties = 0;
|
||||
|
||||
auto buttonQuery = BuildQuery(entity, sortMethod, "SELECT COUNT(*) FROM properties as p JOIN charinfo as ci ON ci.prop_clone_id = p.clone_id where p.zone_id = ? AND (p.description LIKE ? OR p.name LIKE ? OR ci.name LIKE ?) AND p.privacy_option >= ? ", false);
|
||||
auto buttonQuery = BuildQuery(entity, sortMethod, character, "SELECT COUNT(*) FROM properties as p JOIN charinfo as ci ON ci.prop_clone_id = p.clone_id where p.zone_id = ? AND (p.description LIKE ? OR p.name LIKE ? OR ci.name LIKE ?) AND p.privacy_option >= ? ", false);
|
||||
auto propertiesLeft = Database::CreatePreppedStmt(buttonQuery);
|
||||
|
||||
propertiesLeft->setUInt(1, this->m_MapID);
|
||||
|
@ -60,7 +60,7 @@ class PropertyEntranceComponent : public Component {
|
||||
|
||||
PropertySelectQueryProperty SetPropertyValues(PropertySelectQueryProperty property, LWOCLONEID cloneId = LWOCLONEID_INVALID, std::string ownerName = "", std::string propertyName = "", std::string propertyDescription = "", float reputation = 0, bool isBFF = false, bool isFriend = false, bool isModeratorApproved = false, bool isAlt = false, bool isOwned = false, uint32_t privacyOption = 0, uint32_t timeLastUpdated = 0, float performanceCost = 0.0f);
|
||||
|
||||
std::string BuildQuery(Entity* entity, int32_t sortMethod, std::string customQuery = "", bool wantLimits = true);
|
||||
std::string BuildQuery(Entity* entity, int32_t sortMethod, Character* character, std::string customQuery = "", bool wantLimits = true);
|
||||
|
||||
private:
|
||||
/**
|
||||
|
@ -669,10 +669,12 @@ void HandlePacket(Packet* packet) {
|
||||
Game::logger->Log("WorldServer", "Deleting player %llu\n", entity->GetObjectID());
|
||||
|
||||
EntityManager::Instance()->DestroyEntity(entity);
|
||||
}
|
||||
|
||||
{
|
||||
CBITSTREAM;
|
||||
PacketUtils::WriteHeader(bitStream, CHAT_INTERNAL, MSG_CHAT_INTERNAL_PLAYER_REMOVED_NOTIFICATION);
|
||||
bitStream.Write(c->GetObjectID());
|
||||
bitStream.Write(user->GetLoggedInChar());
|
||||
Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
|
||||
}
|
||||
|
||||
@ -930,6 +932,19 @@ void HandlePacket(Packet* packet) {
|
||||
playerID = GeneralUtils::ClearBit(playerID, OBJECT_BIT_CHARACTER);
|
||||
playerID = GeneralUtils::ClearBit(playerID, OBJECT_BIT_PERSISTENT);
|
||||
|
||||
auto user = UserManager::Instance()->GetUser(packet->systemAddress);
|
||||
|
||||
if (user) {
|
||||
auto lastCharacter = user->GetLoggedInChar();
|
||||
// This means we swapped characters and we need to remove the previous player from the container.
|
||||
if (static_cast<uint32_t>(lastCharacter) != playerID) {
|
||||
CBITSTREAM;
|
||||
PacketUtils::WriteHeader(bitStream, CHAT_INTERNAL, MSG_CHAT_INTERNAL_PLAYER_REMOVED_NOTIFICATION);
|
||||
bitStream.Write(lastCharacter);
|
||||
Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
|
||||
}
|
||||
}
|
||||
|
||||
UserManager::Instance()->LoginCharacter(packet->systemAddress, static_cast<uint32_t>(playerID));
|
||||
break;
|
||||
}
|
||||
|
@ -60,3 +60,7 @@ classic_survival_scoring=0
|
||||
|
||||
# If this value is 1, pets will consume imagination as they did in live. if 0 they will not consume imagination at all.
|
||||
pets_take_imagination=1
|
||||
|
||||
# If you would like to increase the maximum number of best friends a player can have on the server
|
||||
# Change the value below to what you would like this to be (5 is live accurate)
|
||||
max_number_of_best_friends=5
|
Loading…
Reference in New Issue
Block a user