From 78e4779db6a01a289523704ca13ac91aec1f53da Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 23 Jun 2024 21:04:43 -0700 Subject: [PATCH 1/6] Client synchronization improvements --- src/Buddies.cpp | 11 +---------- src/Buddies.hpp | 2 +- src/Combat.cpp | 4 ++++ src/Player.hpp | 2 +- src/PlayerManager.cpp | 36 +++++++++++++++++++++--------------- 5 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/Buddies.cpp b/src/Buddies.cpp index e1e6a02..fc53551 100644 --- a/src/Buddies.cpp +++ b/src/Buddies.cpp @@ -30,7 +30,7 @@ static bool playerHasBuddyWithID(Player* plr, int buddyID) { #pragma endregion // Refresh buddy list -void Buddies::refreshBuddyList(CNSocket* sock) { +void Buddies::sendBuddyList(CNSocket* sock) { Player* plr = PlayerManager::getPlayer(sock); int buddyCnt = Database::getNumBuddies(plr); @@ -277,15 +277,6 @@ static void reqFindNameBuddyAccept(CNSocket* sock, CNPacketData* data) { // Getting buddy state static void reqPktGetBuddyState(CNSocket* sock, CNPacketData* data) { Player* plr = PlayerManager::getPlayer(sock); - - /* - * If the buddy list wasn't synced a second time yet, sync it. - * Not sure why we have to do it again for the client not to trip up. - */ - if (!plr->buddiesSynced) { - refreshBuddyList(sock); - plr->buddiesSynced = true; - } INITSTRUCT(sP_FE2CL_REP_GET_BUDDY_STATE_SUCC, resp); diff --git a/src/Buddies.hpp b/src/Buddies.hpp index f0bc455..8ca4d87 100644 --- a/src/Buddies.hpp +++ b/src/Buddies.hpp @@ -6,5 +6,5 @@ namespace Buddies { void init(); // Buddy list - void refreshBuddyList(CNSocket* sock); + void sendBuddyList(CNSocket* sock); } diff --git a/src/Combat.cpp b/src/Combat.cpp index 37565ee..1b29eb4 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -908,6 +908,10 @@ static void playerTick(CNServer *serv, time_t currTime) { Player *plr = pair.second; bool transmit = false; + // don't tick players that haven't loaded in yet + if (!plr->initialLoadDone) + continue; + // group ticks if (plr->group != nullptr) Groups::groupTickInfo(sock); diff --git a/src/Player.hpp b/src/Player.hpp index 14019ab..edf9748 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -72,8 +72,8 @@ struct Player : public Entity, public ICombatant { bool notify = false; bool hidden = false; bool unwarpable = false; + bool initialLoadDone = false; - bool buddiesSynced = false; int64_t buddyIDs[50] = {}; bool isBuddyBlocked[50] = {}; diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index f74cd4d..27d17e4 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -155,10 +155,8 @@ void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z) { * 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) { +static void sendNanoBookSubset(CNSocket *sock, Player *plr) { #ifdef ACADEMY - Player *plr = getPlayer(sock); - int16_t id = 0; INITSTRUCT(sP_FE2CL_REP_NANO_BOOK_SUBSET, pkt); @@ -294,27 +292,23 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) { sock->setFEKey(lm->FEKey); sock->setActiveKey(SOCKETKEY_FE); // send all packets using the FE key from now on + // Academy builds receive nanos in a separate packet. These need to be sent + // before P_FE2CL_REP_PC_ENTER_SUCC as well as after initial load + // due to a race condition in the client :( + sendNanoBookSubset(sock, plr); + sock->sendPacket(response, P_FE2CL_REP_PC_ENTER_SUCC); - // transmit MOTD after entering the game, so the client hopefully changes modes on time - Chat::sendServerMessage(sock, settings::MOTDSTRING); + // Academy builds receive nanos separately. Need to send this ASAP after P_FE2CL_REP_PC_ENTER_SUCC + // because the client will fail to render equipped nanos if it's sent too late + // transfer ownership of Player object into the shard (still valid in this function though) addPlayer(sock, plr); - // check if there is an expiring vehicle - Items::checkItemExpire(sock, plr); - // set player equip stats Items::setItemStats(plr); - Missions::failInstancedMissions(sock); - - sendNanoBookSubset(sock); - - // initial buddy sync - Buddies::refreshBuddyList(sock); - for (auto& pair : players) if (pair.second->notify) Chat::sendServerMessage(pair.first, "[ADMIN]" + getPlayerName(plr) + " has joined."); @@ -377,6 +371,18 @@ static void loadPlayer(CNSocket* sock, CNPacketData* data) { sock->sendPacket(pkt, P_FE2CL_INSTANCE_MAP_INFO); } + + if (!plr->initialLoadDone) { + // these should be called only once, but not until after + // first load-in or else the client may ignore the packets + Chat::sendServerMessage(sock, settings::MOTDSTRING); // MOTD + Missions::failInstancedMissions(sock); // auto-fail missions + Buddies::sendBuddyList(sock); // buddy list + Items::checkItemExpire(sock, plr); // vehicle expiration + sendNanoBookSubset(sock, plr); // nanos (post-load) + + plr->initialLoadDone = true; + } } static void heartbeatPlayer(CNSocket* sock, CNPacketData* data) { From 6f88613ede30708bca351c2b62c93dda5fda7f5a Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 23 Jun 2024 22:23:19 -0700 Subject: [PATCH 2/6] Remove bad comment --- src/PlayerManager.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index 27d17e4..f1ef7d3 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -299,10 +299,6 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) { sock->sendPacket(response, P_FE2CL_REP_PC_ENTER_SUCC); - // Academy builds receive nanos separately. Need to send this ASAP after P_FE2CL_REP_PC_ENTER_SUCC - // because the client will fail to render equipped nanos if it's sent too late - - // transfer ownership of Player object into the shard (still valid in this function though) addPlayer(sock, plr); From b0e2391f2411e84a9e9cdfcf33bc4caeb765b563 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Sun, 23 Jun 2024 23:30:21 -0700 Subject: [PATCH 3/6] Remove guard on PC_TICK --- src/Combat.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Combat.cpp b/src/Combat.cpp index 1b29eb4..37565ee 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -908,10 +908,6 @@ static void playerTick(CNServer *serv, time_t currTime) { Player *plr = pair.second; bool transmit = false; - // don't tick players that haven't loaded in yet - if (!plr->initialLoadDone) - continue; - // group ticks if (plr->group != nullptr) Groups::groupTickInfo(sock); From 1781ac6b03eae6673dcefee6f6547e2ad8270ae8 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Tue, 25 Jun 2024 15:21:32 -0700 Subject: [PATCH 4/6] Fix delayed loading of nano skill icons We actually don't need to wait for post-load to do the second nano book send. That adds unnecessary delay. Moving it to right after `P_FE2CL_REP_PC_ENTER_SUCC` does the trick and gives the client plenty of time to fetch the icons before loading in-game. --- src/PlayerManager.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index f1ef7d3..a403838 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -293,12 +293,14 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) { sock->setActiveKey(SOCKETKEY_FE); // send all packets using the FE key from now on // Academy builds receive nanos in a separate packet. These need to be sent - // before P_FE2CL_REP_PC_ENTER_SUCC as well as after initial load + // before P_FE2CL_REP_PC_ENTER_SUCC as well as after // due to a race condition in the client :( sendNanoBookSubset(sock, plr); sock->sendPacket(response, P_FE2CL_REP_PC_ENTER_SUCC); + sendNanoBookSubset(sock, plr); + // transfer ownership of Player object into the shard (still valid in this function though) addPlayer(sock, plr); @@ -375,7 +377,6 @@ static void loadPlayer(CNSocket* sock, CNPacketData* data) { Missions::failInstancedMissions(sock); // auto-fail missions Buddies::sendBuddyList(sock); // buddy list Items::checkItemExpire(sock, plr); // vehicle expiration - sendNanoBookSubset(sock, plr); // nanos (post-load) plr->initialLoadDone = true; } From f436108d2470675d0ceaad8a1f2e8f898cb78285 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Wed, 26 Jun 2024 15:15:11 -0700 Subject: [PATCH 5/6] Don't send unnecessary nano book subsets pre-enter --- src/PlayerManager.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index a403838..ff7a271 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -155,7 +155,7 @@ void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z) { * 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, Player *plr) { +static void sendNanoBook(CNSocket *sock, Player *plr, bool resizeOnly) { #ifdef ACADEMY int16_t id = 0; INITSTRUCT(sP_FE2CL_REP_NANO_BOOK_SUBSET, pkt); @@ -163,6 +163,13 @@ static void sendNanoBookSubset(CNSocket *sock, Player *plr) { pkt.PCUID = plr->iID; pkt.bookSize = NANO_COUNT; + if (resizeOnly) { + // triggers nano array resizing without + // actually sending nanos + sock->sendPacket(pkt, P_FE2CL_REP_NANO_BOOK_SUBSET); + return; + } + while (id < NANO_COUNT) { pkt.elementOffset = id; @@ -295,11 +302,11 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) { // Academy builds receive nanos in a separate packet. These need to be sent // before P_FE2CL_REP_PC_ENTER_SUCC as well as after // due to a race condition in the client :( - sendNanoBookSubset(sock, plr); + sendNanoBook(sock, plr, true); sock->sendPacket(response, P_FE2CL_REP_PC_ENTER_SUCC); - sendNanoBookSubset(sock, plr); + sendNanoBook(sock, plr, false); // transfer ownership of Player object into the shard (still valid in this function though) addPlayer(sock, plr); From 225515ec216ee1cbc3e863fed9a2c6ba5a6ed8e4 Mon Sep 17 00:00:00 2001 From: gsemaj Date: Wed, 26 Jun 2024 15:18:49 -0700 Subject: [PATCH 6/6] Fix comment --- src/PlayerManager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index ff7a271..cb824ef 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -299,9 +299,9 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) { sock->setFEKey(lm->FEKey); sock->setActiveKey(SOCKETKEY_FE); // send all packets using the FE key from now on - // Academy builds receive nanos in a separate packet. These need to be sent - // before P_FE2CL_REP_PC_ENTER_SUCC as well as after - // due to a race condition in the client :( + // Academy builds receive nanos in a separate packet. An initial one with the size of the + // nano book needs to be sent before PC_ENTER_SUCC so the client can resize its nano arrays, + // and then proper packets with the nanos included must be sent after, while the game is loading. sendNanoBook(sock, plr, true); sock->sendPacket(response, P_FE2CL_REP_PC_ENTER_SUCC);