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.
231 lines
8.0 KiB
231 lines
8.0 KiB
4 years ago
|
//====== 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
|