Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 115 additions & 1 deletion csgo_gc/gc_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "gc_client.h"
#include "graffiti.h"
#include "keyvalue.h"
#include "networking_shared.h"

ClientGC::ClientGC(uint64_t steamId)
: m_steamId{ steamId }
Expand All @@ -10,6 +11,13 @@ ClientGC::ClientGC(uint64_t steamId)
// also called from ServerGC's constructor
Graffiti::Initialize();

// The inventory exists before the worker thread starts, so seed the
// round_mvp event cache here and keep later refreshes on the worker thread.
if (m_inventory.EquippedMusicKitItemId(true))
{
m_cachedMusicKitMVPs.store(static_cast<int32_t>(m_inventory.EquippedMusicKitMVPCount(false)));
}

StartThread();

Platform::Print("ClientGC spawned for user %llu\n", steamId);
Expand All @@ -21,6 +29,69 @@ ClientGC::~ClientGC()
Platform::Print("ClientGC destroyed\n");
}

uint32_t ClientGC::LocalPlayerMusicKitMVPsForRoundMVPEvent() const
{
// round_mvp is observed before the worker-thread inventory mirror applies
// the local increment, so expose the post-MVP value here.
int32_t cachedMVPs = m_cachedMusicKitMVPs.load();
return cachedMVPs >= 0 ? static_cast<uint32_t>(cachedMVPs + 1) : 0;
}

void ClientGC::RefreshCachedMusicKitMVPs()
{
if (!m_inventory.EquippedMusicKitItemId(true))
{
m_cachedMusicKitMVPs.store(-1);
SendMusicKitMVPStateToGameServer();
return;
}

m_cachedMusicKitMVPs.store(static_cast<int32_t>(m_inventory.EquippedMusicKitMVPCount(false)));
SendMusicKitMVPStateToGameServer();
}

void ClientGC::SendMusicKitMVPStateToGameServer()
{
int32_t userId = m_localUserId.load();
if (userId <= 0)
{
return;
}

GCMessageWrite messageWrite{ k_EMsgNetworkMusicKitMVPState };

int32_t cachedMVPs = m_cachedMusicKitMVPs.load();
uint32_t currentMVPs = cachedMVPs >= 0 ? static_cast<uint32_t>(cachedMVPs) : 0;
uint32_t hasEquippedStatTrakMusicKit = cachedMVPs >= 0 ? 1u : 0u;

messageWrite.WriteUint32(static_cast<uint32_t>(userId));
messageWrite.WriteUint32(hasEquippedStatTrakMusicKit);
messageWrite.WriteUint32(currentMVPs);

Platform::Print("ClientGC: syncing music kit MVP state to server: userid=%d haskit=%u mvps=%u\n",
userId,
hasEquippedStatTrakMusicKit,
currentMVPs);
PostToHost(HostEvent::NetMessage, 0, messageWrite.Data(), messageWrite.Size());
}

void ClientGC::SyncLocalPlayerMusicKitState(int userId)
{
if (userId <= 0)
{
return;
}

int32_t previousUserId = m_localUserId.exchange(userId);
if (previousUserId != userId)
{
Platform::Print("ClientGC: local userid changed from %d to %d, syncing music kit state\n",
previousUserId,
userId);
SendMusicKitMVPStateToGameServer();
}
}

void ClientGC::HandleEvent(GCEvent type, uint64_t id, const std::vector<uint8_t> &buffer)
{
switch (type)
Expand All @@ -37,6 +108,23 @@ void ClientGC::HandleEvent(GCEvent type, uint64_t id, const std::vector<uint8_t>
HandleSOCacheRequest();
break;

case GCEvent::LocalPlayerRoundMVP:
LocalPlayerRoundMVP();
break;

case GCEvent::SyncLocalPlayerMusicKitState:
if (buffer.size() == sizeof(uint32_t))
{
uint32_t userId;
memcpy(&userId, buffer.data(), sizeof(userId));
SyncLocalPlayerMusicKitState(static_cast<int>(userId));
}
else
{
assert(false);
}
break;

default:
assert(false);
break;
Expand Down Expand Up @@ -315,6 +403,8 @@ void ClientGC::AdjustItemEquippedState(GCMessageRead &messageRead)
return;
}

RefreshCachedMusicKitMVPs();

// let the gameserver know, too
SendMessageToGame(true, k_ESOMsg_UpdateMultiple, update);
}
Expand Down Expand Up @@ -431,11 +521,12 @@ void ClientGC::IncrementKillCountAttribute(GCMessageRead &messageRead)
return;
}

assert(message.event_type() == 0);
assert(message.event_type() == 0 || message.event_type() == 1);

CMsgSOSingleObject update;
if (m_inventory.IncrementKillCountAttribute(message.item_id(), message.amount(), update))
{
RefreshCachedMusicKitMVPs();
SendMessageToGame(true, k_ESOMsg_Update, update);
}
else
Expand All @@ -444,6 +535,29 @@ void ClientGC::IncrementKillCountAttribute(GCMessageRead &messageRead)
}
}

void ClientGC::LocalPlayerRoundMVP()
{
// Music kit StatTrak progress is not driven by the retired official GC path,
// so mirror the increment locally when the client observes a local round_mvp.
uint64_t itemId = m_inventory.EquippedMusicKitItemId(true);
if (!itemId)
{
Platform::Print("LocalPlayerRoundMVP: local MVP without equipped StatTrak music kit\n");
return;
}

CMsgSOSingleObject update;
if (!m_inventory.IncrementKillCountAttribute(itemId, 1, update))
{
Platform::Print("LocalPlayerRoundMVP: failed to increment music kit %llu\n", itemId);
return;
}

RefreshCachedMusicKitMVPs();
Platform::Print("LocalPlayerRoundMVP: incremented music kit %llu\n", itemId);
SendMessageToGame(true, k_ESOMsg_Update, update);
}

void ClientGC::ApplySticker(GCMessageRead &messageRead)
{
CMsgApplySticker message;
Expand Down
8 changes: 8 additions & 0 deletions csgo_gc/gc_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class ClientGC final : public SharedGC
public:
ClientGC(uint64_t steamId);
~ClientGC();
uint32_t LocalPlayerMusicKitMVPsForRoundMVPEvent() const;

private:
void HandleEvent(GCEvent type, uint64_t id, const std::vector<uint8_t> &buffer) override;
Expand All @@ -17,6 +18,9 @@ class ClientGC final : public SharedGC
void HandleMessage(uint32_t type, const void *data, uint32_t size);
void HandleNetMessage(const void *data, uint32_t size);
void HandleSOCacheRequest();
void RefreshCachedMusicKitMVPs();
void SyncLocalPlayerMusicKitState(int userId);
void SendMusicKitMVPStateToGameServer();

// send to the local game and the game server we're connected to (if we're connected)
void SendMessageToGame(bool sendToGameServer, uint32_t type,
Expand All @@ -29,6 +33,8 @@ class ClientGC final : public SharedGC
void ClientRequestJoinServerData(GCMessageRead &messageRead);
void SetItemPositions(GCMessageRead &messageRead);
void IncrementKillCountAttribute(GCMessageRead &messageRead);
// Increment the equipped StatTrak music kit when the local player receives round MVP.
void LocalPlayerRoundMVP();
void ApplySticker(GCMessageRead &messageRead);
void StoreGetUserData(GCMessageRead &messageRead);
void StorePurchaseInit(GCMessageRead &messageRead);
Expand All @@ -50,6 +56,8 @@ class ClientGC final : public SharedGC
const uint64_t m_steamId;

Inventory m_inventory;
std::atomic<int32_t> m_localUserId{};
std::atomic<int32_t> m_cachedMusicKitMVPs{ -1 };

// microtransactions, we only have one going at a time
uint64_t m_transactionId{};
Expand Down
68 changes: 67 additions & 1 deletion csgo_gc/gc_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "gc_const.h"
#include "gc_const_csgo.h"
#include "graffiti.h"
#include "networking_shared.h"

// yuck!! needed for CSteamID (construct full id from account id)
#include "steam/steamclientpublic.h"
Expand All @@ -23,6 +24,20 @@ ServerGC::~ServerGC()
Platform::Print("ServerGC destroyed\n");
}

bool ServerGC::RoundMVPMusicKitCountForUserId(int userId, int &musickitmvps) const
{
std::lock_guard<std::mutex> lock{ m_musicKitMVPStateMutex };

auto it = m_musicKitMVPStateByUserId.find(userId);
if (it == m_musicKitMVPStateByUserId.end() || !it->second.hasEquippedStatTrakMusicKit)
{
return false;
}

musickitmvps = static_cast<int>(it->second.currentMVPs + 1);
return true;
}

void ServerGC::HandleEvent(GCEvent type, uint64_t id, const std::vector<uint8_t> &buffer)
{
switch (type)
Expand Down Expand Up @@ -82,6 +97,16 @@ void ServerGC::HandleClientSOCacheUnsubscribe(uint64_t steamId)
{
Platform::Print("HandleClientSOCacheUnsubscribe: %llu\n", steamId);

{
std::lock_guard<std::mutex> lock{ m_musicKitMVPStateMutex };
auto stateIt = m_musicKitMVPStateBySteamId.find(steamId);
if (stateIt != m_musicKitMVPStateBySteamId.end())
{
m_musicKitMVPStateByUserId.erase(stateIt->second.userId);
m_musicKitMVPStateBySteamId.erase(stateIt);
}
}

CMsgSOCacheUnsubscribed message;
message.mutable_owner_soid()->set_type(SoIdTypeSteamId);
message.mutable_owner_soid()->set_id(steamId);
Expand Down Expand Up @@ -195,7 +220,12 @@ void ServerGC::HandleNetMessage(uint64_t steamId, const void *data, uint32_t siz

if (!validate.IsProtobuf())
{
// all the allowed messages are protobuf based
if (validate.TypeUnmasked() == k_EMsgNetworkMusicKitMVPState)
{
UpdateMusicKitMVPState(steamId, validate);
return;
}

Platform::Print("ServerGC: ignoring non protobuf message %u from %llu\n",
validate.TypeUnmasked(), steamId);
return;
Expand Down Expand Up @@ -252,6 +282,42 @@ void ServerGC::HandleNetMessage(uint64_t steamId, const void *data, uint32_t siz
}
}

void ServerGC::UpdateMusicKitMVPState(uint64_t steamId, GCMessageRead &messageRead)
{
uint32_t userId = messageRead.ReadUint32();
uint32_t hasEquippedStatTrakMusicKit = messageRead.ReadUint32();
uint32_t currentMVPs = messageRead.ReadUint32();
if (!messageRead.IsValid() || !userId || hasEquippedStatTrakMusicKit > 1)
{
Platform::Print("ServerGC: ignoring malformed music kit MVP state from %llu\n", steamId);
return;
}

MusicKitMVPState state;
state.userId = static_cast<int>(userId);
state.currentMVPs = currentMVPs;
state.hasEquippedStatTrakMusicKit = hasEquippedStatTrakMusicKit != 0;

{
std::lock_guard<std::mutex> lock{ m_musicKitMVPStateMutex };

auto &slot = m_musicKitMVPStateBySteamId[steamId];
if (slot.userId > 0 && slot.userId != state.userId)
{
m_musicKitMVPStateByUserId.erase(slot.userId);
}

slot = state;
m_musicKitMVPStateByUserId[state.userId] = state;
}

Platform::Print("ServerGC: updated music kit MVP state from %llu: userid=%u haskit=%u mvps=%u\n",
steamId,
userId,
hasEquippedStatTrakMusicKit,
currentMVPs);
}

void ServerGC::SendServerWelcome()
{
// we don't care about anything in this message, just reply
Expand Down
13 changes: 13 additions & 0 deletions csgo_gc/gc_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class ServerGC final : public SharedGC
public:
ServerGC();
~ServerGC();
bool RoundMVPMusicKitCountForUserId(int userId, int &musickitmvps) const;

private:
void HandleEvent(GCEvent type, uint64_t id, const std::vector<uint8_t> &buffer) override;
Expand All @@ -18,6 +19,18 @@ class ServerGC final : public SharedGC

void SendServerWelcome();
void IncrementKillCountAttribute(GCMessageRead &messageRead);
void UpdateMusicKitMVPState(uint64_t steamId, GCMessageRead &messageRead);

bool m_sentWelcome{};

struct MusicKitMVPState
{
int userId{};
uint32_t currentMVPs{};
bool hasEquippedStatTrakMusicKit{};
};

mutable std::mutex m_musicKitMVPStateMutex;
std::unordered_map<uint64_t, MusicKitMVPState> m_musicKitMVPStateBySteamId;
std::unordered_map<int, MusicKitMVPState> m_musicKitMVPStateByUserId;
};
2 changes: 2 additions & 0 deletions csgo_gc/gc_shared.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ enum class GCEvent
Message, // id contains the message type, buffer contains the payload
NetMessage, // id contains the recipient steam id, buffer contains the payload
SOCacheRequest, // sent to client gc when connected to a gameserver
LocalPlayerRoundMVP, // sent to client gc when the local player earns round MVP
SyncLocalPlayerMusicKitState, // sent to client gc from the main thread, buffer contains the local userid
ClientSOCacheUnsubscribe, // sent to server gc when a client disconnects, id contains the steam id
};

Expand Down
Loading