Skip to content
Merged
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
89 changes: 61 additions & 28 deletions src/multiaddonmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,18 @@ struct ClientJoinInfo_t
CUtlVector<ClientJoinInfo_t> g_ClientsPendingAddon; // List of clients who are still downloading addons
std::unordered_set<uint64> g_ClientsWithAddons; // List of clients who already downloaded everything so they don't get reconnects on mapchange/rejoin

CConVar<CUtlString> mm_extra_addons("mm_extra_addons", FCVAR_NONE, "The workshop IDs of extra addons separated by commas, addons will be downloaded (if not present) and mounted", CUtlString(""),
[](CConVar<CUtlString> *cvar, CSplitScreenSlot slot, const CUtlString *new_val, const CUtlString *old_val)
{
g_ClientsWithAddons.clear();
Message("Clearing client cache due to addons changing");

StringToVector(new_val->Get(), g_MultiAddonManager.m_ExtraAddons);

g_MultiAddonManager.RefreshAddons();
});


MultiAddonManager g_MultiAddonManager;
INetworkGameServer *g_pNetworkGameServer = nullptr;
CSteamGameServerAPIContext g_SteamAPI;
Expand Down Expand Up @@ -291,9 +303,23 @@ bool MultiAddonManager::MountAddon(const char *pszAddon, bool bAddToTail = false
if (!pszAddon || !*pszAddon)
return false;

CUtlVector<std::string> serverMountedAddons;
StringToVector(this->m_sCurrentWorkshopMap.c_str(), serverMountedAddons);
if (serverMountedAddons.Find(pszAddon) != -1)
{
Message("%s: Addon %s is already mounted by the server\n", __func__, pszAddon);
return false;
}

PublishedFileId_t iAddon = V_StringToUint64(pszAddon, 0);
uint32 iAddonState = g_SteamAPI.SteamUGC()->GetItemState(iAddon);

if (iAddonState & k_EItemStateLegacyItem)
{
Message("%s: Addon %s is not compatible with Source 2, skipping\n", __func__, pszAddon);
return false;
}

if (!(iAddonState & k_EItemStateInstalled))
{
Message("%s: Addon %s is not installed, queuing a download\n", __func__, pszAddon);
Expand Down Expand Up @@ -427,7 +453,7 @@ bool MultiAddonManager::DownloadAddon(const char *pszAddon, bool bImportant = fa
return true;
}

void MultiAddonManager::RefreshAddons(bool bReloadMap = false)
void MultiAddonManager::RefreshAddons(bool bReloadMap)
{
if (!g_SteamAPI.SteamUGC())
return;
Expand All @@ -454,6 +480,9 @@ void MultiAddonManager::ClearAddons()
{
m_ExtraAddons.RemoveAll();

// Update the convar to reflect the new addon list, but don't trigger the callback
mm_extra_addons.GetConVarData()->Value(0)->m_StringValue = VectorToString(m_ExtraAddons).c_str();

FOR_EACH_VEC_BACK(m_MountedAddons, i)
UnmountAddon(m_MountedAddons[i].c_str());
}
Expand Down Expand Up @@ -524,6 +553,9 @@ bool MultiAddonManager::AddAddon(const char *pszAddon, bool bRefresh = false)

m_ExtraAddons.AddToTail(pszAddon);

// Update the convar to reflect the new addon list, but don't trigger the callback
mm_extra_addons.GetConVarData()->Value(0)->m_StringValue = VectorToString(m_ExtraAddons).c_str();

g_ClientsWithAddons.clear();
Message("Clearing client cache due to addons changing");

Expand All @@ -547,6 +579,9 @@ bool MultiAddonManager::RemoveAddon(const char *pszAddon, bool bRefresh = false)

m_ExtraAddons.Remove(index);

// Update the convar to reflect the new addon list, but don't trigger the callback
mm_extra_addons.GetConVarData()->Value(0)->m_StringValue = VectorToString(m_ExtraAddons).c_str();

g_ClientsWithAddons.clear();
Message("Clearing client cache due to addons changing");

Expand All @@ -556,17 +591,6 @@ bool MultiAddonManager::RemoveAddon(const char *pszAddon, bool bRefresh = false)
return true;
}

CConVar<CUtlString> mm_extra_addons("mm_extra_addons", FCVAR_NONE, "The workshop IDs of extra addons separated by commas, addons will be downloaded (if not present) and mounted", CUtlString(""),
[](CConVar<CUtlString> *cvar, CSplitScreenSlot slot, const CUtlString *new_val, const CUtlString *old_val)
{
g_ClientsWithAddons.clear();
Message("Clearing client cache due to addons changing");

StringToVector(new_val->Get(), g_MultiAddonManager.m_ExtraAddons);

g_MultiAddonManager.RefreshAddons();
});

CON_COMMAND_F(mm_add_addon, "Add a workshop ID to the extra addon list", FCVAR_LINKED_CONCOMMAND | FCVAR_SPONLY)
{
if (args.ArgC() < 2)
Expand Down Expand Up @@ -694,29 +718,38 @@ bool FASTCALL Hook_SendNetMessage(CServerSideClient *pClient, CNetMessage *pData

void FASTCALL Hook_SetPendingHostStateRequest(int numRequest, CHostStateRequest *pRequest)
{
// When IVEngineServer::ChangeLevel is called by the plugin or the server code,
// (which happens at the end of a map), the server-defined addon does not change.
// Also, host state requests coming from that function will always have "ChangeLevel" in its KV.
// We can use this information to always be aware of what the original addon is.

if (!pRequest->m_pKV)
{
g_MultiAddonManager.ClearCurrentWorkshopMap();
}
else if (V_stricmp(pRequest->m_pKV->GetName(), "ChangeLevel"))
{
if (!V_stricmp(pRequest->m_pKV->GetName(), "map_workshop"))
g_MultiAddonManager.SetCurrentWorkshopMap(pRequest->m_pKV->GetString("customgamemode", ""));
else
g_MultiAddonManager.ClearCurrentWorkshopMap();
}
if (g_MultiAddonManager.m_ExtraAddons.Count() == 0)
return g_pfnSetPendingHostStateRequest(numRequest, pRequest);

CUtlVector<std::string> vecAddons;
StringToVector(pRequest->m_Addons.Get(), vecAddons);

// Clear the string just in case it wasn't somehow, like when reloading the map
pRequest->m_Addons.Clear();

std::string sExtraAddonString = VectorToString(g_MultiAddonManager.m_ExtraAddons);

// If it's empty or the first addon in the string is ours, it means we're on a default map
if (vecAddons.Count() == 0 || g_MultiAddonManager.m_ExtraAddons.HasElement(vecAddons[0]))
// Rebuild the addon list. We always start with the original addon.
if (g_MultiAddonManager.GetCurrentWorkshopMap().empty())
{
Message("%s: setting addon string to \"%s\"\n", __func__, sExtraAddonString.c_str());
pRequest->m_Addons = sExtraAddonString.c_str();
g_MultiAddonManager.ClearCurrentWorkshopMap();
pRequest->m_Addons = VectorToString(g_MultiAddonManager.m_ExtraAddons).c_str();
}
else
{
Message("%s: appending \"%s\" to addon string \"%s\"\n", __func__, sExtraAddonString.c_str(), vecAddons[0].c_str());
pRequest->m_Addons.Format("%s,%s", vecAddons[0].c_str(), sExtraAddonString.c_str());
g_MultiAddonManager.SetCurrentWorkshopMap(vecAddons[0].c_str());
// Don't add the same addon twice. Hopefully no server owner is diabolical enough to do things like `map de_dust2 customgamemode=1234,5678`.
CUtlVector<std::string> newAddons;
newAddons.CopyArray(g_MultiAddonManager.m_ExtraAddons.Base(), g_MultiAddonManager.m_ExtraAddons.Count());
newAddons.FindAndRemove(g_MultiAddonManager.GetCurrentWorkshopMap().c_str());
newAddons.AddToHead(g_MultiAddonManager.GetCurrentWorkshopMap().c_str());
pRequest->m_Addons = VectorToString(newAddons).c_str();
}

g_pfnSetPendingHostStateRequest(numRequest, pRequest);
Expand Down
3 changes: 2 additions & 1 deletion src/multiaddonmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class MultiAddonManager : public ISmmPlugin, public IMetamodListener
bool RemoveAddon(const char *pszAddon, bool bRefresh);
bool DownloadAddon(const char *pszAddon, bool bImportant, bool bForce);
void PrintDownloadProgress();
void RefreshAddons(bool bReloadMap);
void RefreshAddons(bool bReloadMap = false);
void ClearAddons();
void ReloadMap();
std::string GetCurrentWorkshopMap() { return m_sCurrentWorkshopMap; }
Expand All @@ -76,6 +76,7 @@ class MultiAddonManager : public ISmmPlugin, public IMetamodListener
const char *GetLogTag();

CUtlVector<std::string> m_ExtraAddons;
// List of addons mounted by the plugin. Does not contain the original server mounted addon.
CUtlVector<std::string> m_MountedAddons;
private:
CUtlVector<PublishedFileId_t> m_ImportantDownloads; // Important addon downloads that will trigger a map reload when finished
Expand Down