diff --git a/src/BuddyManager.cpp b/src/BuddyManager.cpp index 0d822a8..fe053fe 100644 --- a/src/BuddyManager.cpp +++ b/src/BuddyManager.cpp @@ -3,6 +3,7 @@ #include "ChatManager.hpp" #include "PlayerManager.hpp" #include "BuddyManager.hpp" +#include "Database.hpp" #include #include @@ -22,6 +23,54 @@ void BuddyManager::init() { REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_BUDDY_WARP, reqBuddyWarp); } +// Refresh buddy list +void BuddyManager::refreshBuddyList(CNSocket* sock) { + Player* plr = PlayerManager::getPlayer(sock); + int buddyCnt = Database::getNumBuddies(plr); + + if (!validOutVarPacket(sizeof(sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC), buddyCnt, sizeof(sBuddyBaseInfo))) { + std::cout << "[WARN] bad sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC packet size\n"; + return; + } + + // initialize response struct + size_t resplen = sizeof(sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC) + buddyCnt * sizeof(sBuddyBaseInfo); + uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; + + memset(respbuf, 0, resplen); + + sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC* resp = (sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC*)respbuf; + sBuddyBaseInfo* respdata = (sBuddyBaseInfo*)(respbuf + sizeof(sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC)); + + // base response fields + resp->iBuddyCnt = buddyCnt; + resp->iID = plr->iID; + resp->iPCUID = plr->PCStyle.iPC_UID; + resp->iListNum = 0; // ??? + + int buddyIndex = 0; + for (int i = 0; i < 50; i++) { + int64_t buddyID = plr->buddyIDs[i]; + if (buddyID != 0) { + sBuddyBaseInfo buddyInfo = {}; + Database::DbPlayer buddyPlayerData = Database::getDbPlayerById(buddyID); + buddyInfo.bBlocked = 0; + buddyInfo.bFreeChat = 1; + buddyInfo.iGender = buddyPlayerData.Gender; + buddyInfo.iID = buddyID; + buddyInfo.iPCUID = buddyID; + buddyInfo.iNameCheckFlag = buddyPlayerData.NameCheck; + buddyInfo.iPCState = buddyPlayerData.PCState; + U8toU16(buddyPlayerData.FirstName, buddyInfo.szFirstName, sizeof(buddyInfo.szFirstName)); + U8toU16(buddyPlayerData.LastName, buddyInfo.szLastName, sizeof(buddyInfo.szLastName)); + respdata[buddyIndex] = buddyInfo; + buddyIndex++; + } + } + + sock->sendPacket((void*)respbuf, P_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC, resplen); +} + // Buddy request void BuddyManager::requestBuddy(CNSocket* sock, CNPacketData* data) { if (data->size != sizeof(sP_CL2FE_REQ_REQUEST_MAKE_BUDDY)) @@ -321,17 +370,32 @@ void BuddyManager::reqBuddyDelete(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); + // remove buddy on our side INITSTRUCT(sP_FE2CL_REP_REMOVE_BUDDY_SUCC, resp); - resp.iBuddyPCUID = pkt->iBuddyPCUID; resp.iBuddySlot = pkt->iBuddySlot; - if (pkt->iBuddySlot < 0 || pkt->iBuddySlot >= 50) return; // sanity check - plr->buddyIDs[resp.iBuddySlot] = 0; - sock->sendPacket((void*)&resp, P_FE2CL_REP_REMOVE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_REMOVE_BUDDY_SUCC)); + + // remove buddy on their side, reusing the struct + CNSocket* otherSock = PlayerManager::getSockFromID(pkt->iBuddyPCUID); + if (otherSock == nullptr) + return; // other player isn't online, no broadcast needed + Player* otherPlr = PlayerManager::getPlayer(otherSock); + // search for the slot with the requesting player's ID + resp.iBuddyPCUID = plr->PCStyle.iPC_UID; + for (int i = 0; i < 50; i++) { + if (otherPlr->buddyIDs[i] == plr->PCStyle.iPC_UID) { + // remove buddy + otherPlr->buddyIDs[i] = 0; + // broadcast + resp.iBuddySlot = i; + otherSock->sendPacket((void*)&resp, P_FE2CL_REP_REMOVE_BUDDY_SUCC, sizeof(sP_FE2CL_REP_REMOVE_BUDDY_SUCC)); + return; + } + } } // Warping to buddy diff --git a/src/BuddyManager.hpp b/src/BuddyManager.hpp index 068aa5d..ada434c 100644 --- a/src/BuddyManager.hpp +++ b/src/BuddyManager.hpp @@ -11,6 +11,9 @@ namespace BuddyManager { void init(); + // Buddy list + void refreshBuddyList(CNSocket* sock); + // Buddy requests void requestBuddy(CNSocket* sock, CNPacketData* data); void reqBuddyByName(CNSocket* sock, CNPacketData* data); diff --git a/src/Database.cpp b/src/Database.cpp index 9fc1dbf..300fb89 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -93,6 +93,11 @@ auto db = make_storage("database.db", make_column("RemainingNPCCount1", &Database::DbQuest::RemainingNPCCount1), make_column("RemainingNPCCount2", &Database::DbQuest::RemainingNPCCount2), make_column("RemainingNPCCount3", &Database::DbQuest::RemainingNPCCount3) + ), + make_table("Buddyships", + make_column("PlayerAId", &Database::Buddyship::PlayerAId), + make_column("PlayerBId", &Database::Buddyship::PlayerBId), + make_column("Status", &Database::Buddyship::Status) ) ); @@ -463,6 +468,7 @@ Player Database::DbToPlayer(DbPlayer player) { Database::removeExpiredVehicles(&result); Database::getNanos(&result); Database::getQuests(&result); + Database::getBuddies(&result); // load completed quests memcpy(&result.aQuestFlag, player.QuestFlag.data(), std::min(sizeof(result.aQuestFlag), player.QuestFlag.size())); @@ -492,6 +498,7 @@ void Database::updatePlayer(Player *player) { updateInventory(player); updateNanos(player); updateQuests(player); + updateBuddies(player); } void Database::updateInventory(Player *player){ @@ -604,6 +611,27 @@ void Database::updateQuests(Player* player) { db.commit(); } +void Database::updateBuddies(Player* player) { + db.begin_transaction(); + + db.remove_all( // remove all buddyships with this player involved + where(c(&Buddyship::PlayerAId) == player->iID || c(&Buddyship::PlayerBId) == player->iID) + ); + + // iterate through player's buddies and add records for each non-zero entry + for (int i = 0; i < 50; i++) { + if (player->buddyIDs[i] != 0) { + Buddyship record; + record.PlayerAId = player->iID; + record.PlayerBId = player->buddyIDs[i]; + record.Status = 0; // still not sure how we'll handle blocking + db.insert(record); + } + } + + db.commit(); +} + void Database::getInventory(Player* player) { // get items from DB auto items = db.get_all( @@ -691,4 +719,28 @@ void Database::getQuests(Player* player) { } } +void Database::getBuddies(Player* player) { + auto buddies = db.get_all( // player can be on either side + where(c(&Buddyship::PlayerAId) == player->iID || c(&Buddyship::PlayerBId) == player->iID) + ); + + // there should never be more than 50 buddyships per player, but just in case + for (int i = 0; i < 50 && i < buddies.size(); i++) { + // if the player is player A, then the buddy is player B, and vice versa + player->buddyIDs[i] = player->iID == buddies.at(i).PlayerAId + ? buddies.at(i).PlayerBId : buddies.at(i).PlayerAId; + } +} + +int Database::getNumBuddies(Player* player) { + std::lock_guard lock(dbCrit); + + auto buddies = db.get_all( // player can be on either side + where(c(&Buddyship::PlayerAId) == player->iID || c(&Buddyship::PlayerBId) == player->iID) + ); + + // again, for peace of mind + return buddies.size() > 50 ? 50 : buddies.size(); +} + #pragma endregion ShardServer diff --git a/src/Database.hpp b/src/Database.hpp index 380b287..ce82080 100644 --- a/src/Database.hpp +++ b/src/Database.hpp @@ -79,6 +79,11 @@ namespace Database { int RemainingNPCCount2; int RemainingNPCCount3; }; + struct Buddyship { + int PlayerAId; + int PlayerBId; + int16_t Status; + }; #pragma endregion DatabaseStructs @@ -121,11 +126,14 @@ namespace Database { void updateInventory(Player *player); void updateNanos(Player *player); void updateQuests(Player* player); + void updateBuddies(Player* player); void getInventory(Player* player); void removeExpiredVehicles(Player* player); void getNanos(Player* player); void getQuests(Player* player); + void getBuddies(Player* player); + int getNumBuddies(Player* player); // parsing blobs void appendBlob(std::vector*blob, int64_t input); diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index 7b9f889..3096f0b 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -9,6 +9,7 @@ #include "GroupManager.hpp" #include "ChatManager.hpp" #include "Database.hpp" +#include "BuddyManager.hpp" #include "settings.hpp" @@ -403,6 +404,9 @@ void PlayerManager::enterPlayer(CNSocket* sock, CNPacketData* data) { MissionManager::failInstancedMissions(sock); + // send over buddy list + BuddyManager::refreshBuddyList(sock); + for (auto& pair : PlayerManager::players) if (pair.second.plr->notify) ChatManager::sendServerMessage(pair.first, "[ADMIN]" + getPlayerName(&plr) + " has joined.");