diff --git a/CMakeVariables.txt b/CMakeVariables.txt index ed0cfce8..db89fef7 100644 --- a/CMakeVariables.txt +++ b/CMakeVariables.txt @@ -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. diff --git a/dChatServer/ChatPacketHandler.cpp b/dChatServer/ChatPacketHandler.cpp index 82f24ffa..b84c3eef 100644 --- a/dChatServer/ChatPacketHandler.cpp +++ b/dChatServer/ChatPacketHandler.cpp @@ -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 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(playerID)); stmt->setUInt(2, static_cast(playerID)); std::vector friends; - auto res = stmt->executeQuery(); + std::unique_ptr 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(eObjectBits::OBJECT_BIT_PERSISTENT)); GeneralUtils::SetBit(fd.friendID, static_cast(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(character)); } + + char isBestFriendRequest{}; + inStream.Read(isBestFriendRequest); + + auto requestor = playerContainer.GetPlayerData(requestorPlayerID); + std::unique_ptr 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 nameQuery(Database::CreatePreppedStmt("SELECT name from charinfo where name = ?;")); + nameQuery->setString(1, playerName); + std::unique_ptr 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 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(requestorPlayerID)); + friendUpdate->setUInt(2, static_cast(requestee->playerID)); + friendUpdate->setUInt(3, static_cast(requestee->playerID)); + friendUpdate->setUInt(4, static_cast(requestorPlayerID)); + std::unique_ptr 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(eObjectBits::OBJECT_BIT_CHARACTER)); + GeneralUtils::SetBit(queryPlayerID, static_cast(eObjectBits::OBJECT_BIT_PERSISTENT)); + GeneralUtils::SetBit(queryFriendID, static_cast(eObjectBits::OBJECT_BIT_CHARACTER)); + GeneralUtils::SetBit(queryFriendID, static_cast(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 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(requestorPlayerID)); + updateQuery->setUInt(3, static_cast(requestee->playerID)); + updateQuery->setUInt(4, static_cast(requestee->playerID)); + updateQuery->setUInt(5, static_cast(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(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 statement(Database::CreatePreppedStmt("INSERT IGNORE INTO `friends` (`player_id`, `friend_id`, `best_friend`) VALUES (?,?,?);")); + statement->setUInt(1, static_cast(requestor->playerID)); + statement->setUInt(2, static_cast(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(goonA->playerID)); - stmt->setUInt(2, static_cast(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 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 res(stmt->executeQuery()); while (res->next()) { friendID = res->getUInt(1); } @@ -181,27 +335,20 @@ void ChatPacketHandler::HandleRemoveFriend(Packet* packet) { GeneralUtils::SetBit(friendID, static_cast(eObjectBits::OBJECT_BIT_PERSISTENT)); GeneralUtils::SetBit(friendID, static_cast(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 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(playerID)); deletestmt->setUInt(2, static_cast(friendID)); deletestmt->setUInt(3, static_cast(friendID)); deletestmt->setUInt(4, static_cast(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(0); //isBFF + bitStream.Write(isBestFriend); //isBFF bitStream.Write(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(0); + bitStream.Write(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(responseCode); - bitStream.Write(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(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(0); //isBFF - bitStream.Write(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(0); //isFTP + } SystemAddress sysAddr = receiver->sysAddr; SEND_PACKET; } diff --git a/dChatServer/ChatPacketHandler.h b/dChatServer/ChatPacketHandler.h index e38a65c1..fffd1ca4 100644 --- a/dChatServer/ChatPacketHandler.h +++ b/dChatServer/ChatPacketHandler.h @@ -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); }; - diff --git a/dChatServer/PlayerContainer.cpp b/dChatServer/PlayerContainer.cpp index d261b32f..b40e8386 100644 --- a/dChatServer/PlayerContainer.cpp +++ b/dChatServer/PlayerContainer.cpp @@ -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); diff --git a/dChatServer/PlayerContainer.h b/dChatServer/PlayerContainer.h index dc78d3b0..b1e1defd 100644 --- a/dChatServer/PlayerContainer.h +++ b/dChatServer/PlayerContainer.h @@ -14,6 +14,7 @@ struct PlayerData { LWOZONEID zoneID; std::vector friends; time_t muteExpire; + uint8_t countOfBestFriends = 0; }; struct TeamData { diff --git a/dCommon/AddFriendResponseCode.h b/dCommon/AddFriendResponseCode.h new file mode 100644 index 00000000..bb2faff7 --- /dev/null +++ b/dCommon/AddFriendResponseCode.h @@ -0,0 +1,15 @@ +#pragma once + +#ifndef __ADDFRIENDRESPONSECODE__H__ +#define __ADDFRIENDRESPONSECODE__H__ + +#include + +enum class AddFriendResponseCode : uint8_t { + ACCEPTED = 0, + REJECTED, + BUSY, + CANCELLED +}; + +#endif //!__ADDFRIENDRESPONSECODE__H__ diff --git a/dCommon/AddFriendResponseType.h b/dCommon/AddFriendResponseType.h new file mode 100644 index 00000000..305796e8 --- /dev/null +++ b/dCommon/AddFriendResponseType.h @@ -0,0 +1,24 @@ +#pragma once + +#ifndef __ADDFRIENDRESPONSETYPE__H__ +#define __ADDFRIENDRESPONSETYPE__H__ + +#include + +enum class AddFriendResponseType : uint8_t { + ACCEPTED = 0, + ALREADYFRIEND, + INVALIDCHARACTER, + GENERALERROR, + YOURFRIENDSLISTFULL, + THEIRFRIENDLISTFULL, + DECLINED, + BUSY, + NOTONLINE, + WAITINGAPPROVAL, + MYTHRAN, + CANCELLED, + FRIENDISFREETRIAL +}; + +#endif //!__ADDFRIENDRESPONSETYPE__H__ diff --git a/dGame/EntityManager.cpp b/dGame/EntityManager.cpp index db60a1d1..da0cf685 100644 --- a/dGame/EntityManager.cpp +++ b/dGame/EntityManager.cpp @@ -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()) { diff --git a/dGame/UserManager.cpp b/dGame/UserManager.cpp index 1d14cb0a..3956942a 100644 --- a/dGame/UserManager.cpp +++ b/dGame/UserManager.cpp @@ -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(objectID); + uint32_t charID = static_cast(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=?;"); diff --git a/dGame/dComponents/PropertyEntranceComponent.cpp b/dGame/dComponents/PropertyEntranceComponent.cpp index 1d631e88..87e0c7ed 100644 --- a/dGame/dComponents/PropertyEntranceComponent.cpp +++ b/dGame/dComponents/PropertyEntranceComponent.cpp @@ -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); diff --git a/dGame/dComponents/PropertyEntranceComponent.h b/dGame/dComponents/PropertyEntranceComponent.h index a3be38a6..fe583e92 100644 --- a/dGame/dComponents/PropertyEntranceComponent.h +++ b/dGame/dComponents/PropertyEntranceComponent.h @@ -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: /** diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 4f1ec400..330263ed 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -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(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(playerID)); break; } diff --git a/resources/worldconfig.ini b/resources/worldconfig.ini index a665f059..931da28c 100644 --- a/resources/worldconfig.ini +++ b/resources/worldconfig.ini @@ -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 \ No newline at end of file