You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
230 lines
8.0 KiB
230 lines
8.0 KiB
//====== 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
|
|
|