summaryrefslogtreecommitdiff
path: root/game/server/tf/workshop/maps_workshop.h
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf/workshop/maps_workshop.h')
-rw-r--r--game/server/tf/workshop/maps_workshop.h230
1 files changed, 230 insertions, 0 deletions
diff --git a/game/server/tf/workshop/maps_workshop.h b/game/server/tf/workshop/maps_workshop.h
new file mode 100644
index 0000000..1c58dd5
--- /dev/null
+++ b/game/server/tf/workshop/maps_workshop.h
@@ -0,0 +1,230 @@
+//====== Copyright Valve Corporation, All rights reserved. =================
+//
+// Requests subscribed maps from the workshop, holds a list of them along with metadata.
+//
+//=============================================================================
+
+#if !defined TF_MAPS_WORKSHOP_H
+#define TF_MAPS_WORKSHOP_H
+#if defined( COMPILER_MSVC )
+#pragma once
+#endif
+
+#include "igamesystem.h"
+
+// Enable verbose debug spew to DevMsg
+// #define TF_WORKSHOP_DEBUG
+
+#define TFWorkshopMsg(...) Msg("[TF Workshop] " __VA_ARGS__)
+#define TFWorkshopWarning(...) Warning("[TF Workshop] " __VA_ARGS__)
+
+#ifdef TF_WORKSHOP_DEBUG
+#define TFWorkshopDebug(...) DevMsg("[TF Workshop Debug] " __VA_ARGS__)
+#else // TF_WORKSHOP_DEBUG
+#define TFWorkshopDebug(...)
+#endif // TF_WORKSHOP_DEBUG
+
+class CTFMapsWorkshop;
+
+CTFMapsWorkshop *TFMapsWorkshop();
+
+// Represents a workshop map
+class CTFWorkshopMap
+{
+public:
+ // Rechecks local files and steam for map status. Currently triggers a synchronous fstat(), so only call during
+ // initialization/user-action.
+ // If eRefresh_HighPriority is passed, we will ask UGC to retreive any available updates as high priority.
+ enum eRefreshType { eRefresh_Normal, eRefresh_HighPriority };
+ void Refresh( eRefreshType refreshType = eRefresh_Normal );
+
+ enum eState
+ {
+ eState_Refreshing,
+ eState_Error,
+ eState_Downloading,
+ eState_Downloaded
+ };
+ eState State() const { return m_eState; }
+
+ // Returns true if downloaded. Optionally returns progress, which is [0, 1]
+ // Any map that returns IsValid() is either downloaded or attempting to download/sync
+ bool Downloaded( /* out */ float *flProgress = NULL );
+
+ // Only known after map state leaves refreshing
+ const char *CanonicalName() const { return m_strCanonicalName.Length() ? m_strCanonicalName.Get() : NULL; }
+
+ bool GetLocalFile( /* out */ CUtlString &strLocalFile );
+
+ PublishedFileId_t FileID() const { return m_nFileID; }
+
+private:
+ friend class CTFMapsWorkshop;
+ CTFWorkshopMap( PublishedFileId_t nMapID );
+
+ // Forwarded callback from maps workshop about map downloads
+ void OnUGCDownload( DownloadItemResult_t *pResult );
+ void OnUGCItemInstalled( ItemInstalled_t *pResult );
+
+ // Update the map name and local filename.
+ // Requires download complete due the way ISteamUGC currently works.
+ // Currently triggers a sync directory enumeration :-/
+ void UpdateMapName();
+
+ CCallResult<CTFWorkshopMap, SteamUGCQueryCompleted_t> m_callbackQueryUGCDetails;
+ void Steam_OnQueryUGCDetails( SteamUGCQueryCompleted_t *pResult, bool bError );
+
+ PublishedFileId_t m_nFileID;
+ uint32 m_rtimeUpdated;
+ int32 m_nFileSize;
+ CUtlString m_strCanonicalName;
+ CUtlString m_strMapName;
+ eState m_eState;
+ bool m_bHighPriority;
+};
+
+// Autogamesystem to request user maps on startup and call update on the workshop manager.
+class CTFMapsWorkshop : public CAutoGameSystemPerFrame
+{
+public:
+ CTFMapsWorkshop();
+
+ bool Init( void ) OVERRIDE;
+ void Shutdown( void ) OVERRIDE;
+ virtual const char* Name( void ) OVERRIDE { return "TFMapsWorkshop"; }
+
+ // Recheck subscriptions and on-disk maps for sync
+ void Refresh();
+
+ // Is this a valid original filename for a uploaded workshop map. Checked on upload and against workshop files
+ // before considering them for download. (e.g. cp_foo.bsp)
+ static inline bool IsValidOriginalFileNameForMap( const CUtlString &originalName );
+ // Is valid for the display name of a workshop map, (e.g. cp_foo)
+ static inline bool IsValidDisplayNameForMap( const CUtlString &originalName );
+
+ // Is user currently subscribed to this map
+ bool IsSubscribed( PublishedFileId_t nFileID );
+
+ // Build a canonical map name given its ID and original file name.
+ bool CanonicalNameForMap( PublishedFileId_t, const CUtlString &strOriginalName, /* out */ CUtlString &strCanonName );
+
+ enum eNameType
+ {
+ // Map name looks like a workshop map, but we don't know its proper name. Returns e.g. "workshop/12345".
+ eName_Incomplete,
+ // Map ID is known and canonical name provided
+ eName_Canon
+ };
+ eNameType GetMapName( PublishedFileId_t nMapID, /* out */ CUtlString &mapName );
+
+ // Attempt to work out a map id from a local name, either the full canonical name ( workshop/cp_map.ugc12345 ) or a
+ // sufficient shorthand name ( workshop/12345 ).
+ //
+ // NOTE This does not validate the friendly name of the map: workshop/cp_bogus_name.ugc12345 will return 12345 just the
+ // same.
+ PublishedFileId_t MapIDFromName( CUtlString mapName );
+
+ // Add this map to our list for this session, triggering download/etc as if it were subscribed
+ bool AddMap( PublishedFileId_t nFileID );
+
+ // *blocking*
+ // Synchronously prepare a map for use, including downloading and optionally copying it to the local disk.
+ enum eSyncType
+ {
+ eSync_LocalDisk,
+ eSync_SteamOnly
+ };
+
+ // Forwarded IServerGameDLL hooks to prepare workshop maps on demand.
+ IServerGameDLL::ePrepareLevelResourcesResult
+ AsyncPrepareLevelResources( /* in/out */ char *pszMapName, size_t nMapNameSize,
+ /* in/out */ char *pszMapFile, size_t nMapFileSize,
+ float *flProgress = NULL );
+
+ // Blocking version of AsyncPrepareLevelResources
+ void PrepareLevelResources( /* in/out */ char *pszMapName, size_t nMapNameSize,
+ /* in/out */ char *pszMapFile, size_t nMapFileSize );
+
+ IServerGameDLL::eCanProvideLevelResult OnCanProvideLevel( /* in/out */ char *pMapName, int nMapNameMax );
+
+ // When the gameserver steam context becomes available.
+ void GameServerSteamAPIActivated();
+
+ // Spews a list of current maps and their status to console
+ void PrintStatusToConsole();
+
+private:
+ CCallback<CTFMapsWorkshop, DownloadItemResult_t, false> m_callbackDownloadItem;
+ CCallback<CTFMapsWorkshop, ItemInstalled_t, false> m_callbackItemInstalled;
+
+ // gameserver API variants
+ CCallback<CTFMapsWorkshop, DownloadItemResult_t, true> m_callbackDownloadItem_GameServer;
+ CCallback<CTFMapsWorkshop, ItemInstalled_t, true> m_callbackItemInstalled_GameServer;
+ void Steam_OnUGCDownload( DownloadItemResult_t *pResult );
+ void Steam_OnUGCItemInstalled( ItemInstalled_t *pResult );
+
+ // See if we have any tracked workshop maps that this name matches, canonical or otherwise
+ CTFWorkshopMap *FindMapByName( const char *pMapName );
+ // Will create a tracked map if this name looks like a workshop map
+ CTFWorkshopMap *FindOrCreateMapByName( const char *pMapName );
+
+ // All managed workshop maps
+ CUtlMap< PublishedFileId_t, CTFWorkshopMap * > m_mapMaps;
+ CUtlVector< PublishedFileId_t > m_vecSubscribedMaps;
+
+ PublishedFileId_t m_nPreparingMap;
+};
+
+//
+// Util
+//
+
+// inline so we can access this from client dll for the uploader
+inline bool CTFMapsWorkshop::IsValidOriginalFileNameForMap( const CUtlString &originalName )
+{
+ // Matching: ([a-z0-9]+_)*[a-z0-9]\.bsp
+
+ int len = originalName.Length();
+ const unsigned int nMaxFileName = MAX_DISPLAY_MAP_NAME + 4; // Map minus extension must be within MAX_DISPLAY_MAP_NAME
+ if ( len < 6 || len > nMaxFileName || originalName.Slice( len - 4 ) != ".bsp" )
+ {
+ TFWorkshopWarning( "Map filename must be at least 6 characters and not more than %u characters ending in .bsp\n", nMaxFileName );
+ return false;
+ }
+
+ CUtlString baseName = originalName.Slice( 0, len - 4 );
+ return IsValidDisplayNameForMap( baseName );
+}
+
+inline bool CTFMapsWorkshop::IsValidDisplayNameForMap( const CUtlString &originalName )
+{
+ // Matching: ([a-z0-9]+_)*[a-z0-9]
+
+ int len = originalName.Length();
+ const unsigned int nMaxDisplayName = MAX_DISPLAY_MAP_NAME;
+ if ( len < 2 || len > nMaxDisplayName )
+ {
+ TFWorkshopWarning( "Map display name must be at least 2 characters and not more than %u characters\n", nMaxDisplayName );
+ return false;
+ }
+
+ for ( int i = 0; i < len; i++ )
+ {
+ char c = originalName[i];
+ if ( !( c >= 'a' && c <= 'z' ) && !( c >= '0' && c <= '9' ) && c != '_' )
+ {
+ TFWorkshopWarning( "Invalid character %c in map name\n", c );
+ return false;
+ }
+
+ if ( c == '_' && ( i == 0 || i == len - 1 || originalName[ i - 1 ] == '_' ) )
+ {
+ TFWorkshopWarning( "Invalid map name: _ cannot appear consecutively nor at the beginning/end of a map name\n" );
+ return false;
+ }
+ }
+
+ return true;
+}
+
+#endif // TF_MAPS_WORKSHOP_H