csgo-2018-source/hammer/hammer.cpp

2867 lines
78 KiB
C++
Raw Normal View History

2021-07-25 12:11:47 +08:00
//===== Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose: The application object.
//
//===========================================================================//
#include "stdafx.h"
#include <io.h>
#include <stdlib.h>
#include <direct.h>
#pragma warning(push, 1)
#pragma warning(disable:4701 4702 4530)
#include <fstream>
#pragma warning(pop)
#include "BuildNum.h"
#include "EditGameConfigs.h"
#include "Splash.h"
#include "Options.h"
#include "custommessages.h"
#include "MainFrm.h"
#include "MessageWnd.h"
#include "ChildFrm.h"
#include "MapDoc.h"
#include "Manifest.h"
#include "MapView3D.h"
#include "MapView2D.h"
#include "PakDoc.h"
#include "PakViewDirec.h"
#include "PakFrame.h"
#include "Prefabs.h"
#include "GlobalFunctions.h"
#include "Shell.h"
#include "ShellMessageWnd.h"
#include "Options.h"
#include "TextureSystem.h"
#include "ToolManager.h"
#include "Hammer.h"
#include "StudioModel.h"
#include "ibsplighting.h"
#include "statusbarids.h"
#include "tier0/icommandline.h"
#include "soundsystem.h"
#include "IHammer.h"
#include "op_entity.h"
#include "tier0/dbg.h"
#include "materialsystem/imaterialsystemhardwareconfig.h"
#include "istudiorender.h"
#include "FileSystem.h"
#include "engine_launcher_api.h"
#include "filesystem_init.h"
#include "utlmap.h"
#include "utlvector.h"
#include "progdlg.h"
#include "MapWorld.h"
#include "HammerVGui.h"
#include "vgui_controls/Controls.h"
#include "lpreview_thread.h"
#include "SteamWriteMiniDump.h"
#include "inputsystem/iinputsystem.h"
#include "datacache/idatacache.h"
#include "steam/steam_api.h"
#include "toolframework/ienginetool.h"
#include "toolutils/enginetools_int.h"
#include "objectproperties.h"
#include "particles/particles.h"
#include "p4lib/ip4.h"
#include "syncfiledialog.h"
#include "vstdlib/jobthread.h"
#include "gridnav.h"
#include "tablet.h"
#include "dialogwithcheckbox.h"
#include "configmanager.h"
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
//
// Note!
//
// If this DLL is dynamically linked against the MFC
// DLLs, any functions exported from this DLL which
// call into MFC must have the AFX_MANAGE_STATE macro
// added at the very beginning of the function.
//
// For example:
//
// extern "C" BOOL PASCAL EXPORT ExportedFunction()
// {
// AFX_MANAGE_STATE(AfxGetStaticModuleState());
// // normal function body here
// }
//
// It is very important that this macro appear in each
// function, prior to any calls into MFC. This means that
// it must appear as the first statement within the
// function, even before any object variable declarations
// as their constructors may generate calls into the MFC
// DLL.
//
// Please see MFC Technical Notes 33 and 58 for additional
// details.
//
// dvs: hack
extern LPCTSTR GetErrorString(void);
extern void MakePrefabLibrary(LPCTSTR pszName);
void EditorUtil_ConvertPath(CString &str, bool bSave);
static bool bMakeLib = false;
static float fSequenceVersion = 0.2f;
static char *pszSequenceHdr = "Worldcraft Command Sequences\r\n\x1a";
CHammer theApp;
COptions Options;
CShell g_Shell;
CShellMessageWnd g_ShellMessageWnd;
CMessageWnd *g_pwndMessage = NULL;
// IPC structures for lighting preview thread
CMessageQueue<MessageToLPreview> g_HammerToLPreviewMsgQueue;
CMessageQueue<MessageFromLPreview> g_LPreviewToHammerMsgQueue;
ThreadHandle_t g_LPreviewThread;
CSteamAPIContext g_SteamAPIContext;
CSteamAPIContext *steamapicontext = &g_SteamAPIContext;
bool CHammer::m_bIsNewDocumentVisible = true;
//-----------------------------------------------------------------------------
// Expose singleton
//-----------------------------------------------------------------------------
EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CHammer, IHammer, INTERFACEVERSION_HAMMER, theApp);
//-----------------------------------------------------------------------------
// global interfaces
//-----------------------------------------------------------------------------
IBaseFileSystem *g_pFileSystem;
IEngineAPI *g_pEngineAPI;
CreateInterfaceFn g_Factory;
bool g_bHDR = true;
bool IsRunningInEngine()
{
return g_pEngineAPI != NULL;
}
int WrapFunctionWithMinidumpHandler( int (*pfn)(void *pParam), void *pParam, int errorRetVal )
{
int nRetVal;
if ( !Plat_IsInDebugSession() && !CommandLine()->FindParm( "-nominidumps") )
{
_set_se_translator( SteamWriteMiniDumpUsingExceptionInfo );
try // this try block allows the SE translator to work
{
nRetVal = pfn( pParam );
}
catch( ... )
{
return errorRetVal;
}
}
else
{
nRetVal = pfn( pParam );
}
return nRetVal;
}
//-----------------------------------------------------------------------------
// Purpose: Logging listener so that Hammer can capture warning and error
// output to write to the message window.
//-----------------------------------------------------------------------------
class CHammerMessageLoggingListener : public ILoggingListener
{
public:
virtual void Log( const LoggingContext_t *pContext, const tchar *pMessage )
{
if ( pContext->m_Severity == LS_ERROR )
{
Msg( mwError, pMessage );
}
else if ( pContext->m_Severity == LS_WARNING )
{
Msg( mwWarning, pMessage );
}
}
};
//-----------------------------------------------------------------------------
// Purpose: Outputs a formatted debug string.
// Input : fmt - format specifier.
// ... - arguments to format.
//-----------------------------------------------------------------------------
void DBG(char *fmt, ...)
{
char ach[128];
va_list va;
va_start(va, fmt);
vsprintf(ach, fmt, va);
va_end(va);
OutputDebugString(ach);
}
void Msg(int type, const char *fmt, ...)
{
if ( !g_pwndMessage )
return;
va_list vl;
char szBuf[512];
va_start(vl, fmt);
int len = _vsnprintf(szBuf, 512, fmt, vl);
va_end(vl);
if ((type == mwError) || (type == mwWarning))
{
g_pwndMessage->ShowMessageWindow();
}
char temp = 0;
char *pMsg = szBuf;
do
{
if (len >= MESSAGE_WND_MESSAGE_LENGTH)
{
temp = pMsg[MESSAGE_WND_MESSAGE_LENGTH-1];
pMsg[MESSAGE_WND_MESSAGE_LENGTH-1] = '\0';
}
g_pwndMessage->AddMsg((MWMSGTYPE)type, pMsg);
if (len >= MESSAGE_WND_MESSAGE_LENGTH)
{
pMsg[MESSAGE_WND_MESSAGE_LENGTH-1] = temp;
pMsg += MESSAGE_WND_MESSAGE_LENGTH-1;
}
len -= MESSAGE_WND_MESSAGE_LENGTH-1;
} while (len > 0);
}
//
// Post-init and pre-shutdown routines management
//
//-----------------------------------------------------------------------------
// Purpose: this routine calls the default doc template's OpenDocumentFile() but
// with the ability to override the visible flag
// Input : lpszPathName - the document to open
// bMakeVisible - ignored
// Output : returns the opened document if successful
//-----------------------------------------------------------------------------
CDocument *CHammerDocTemplate::OpenDocumentFile( LPCTSTR lpszPathName, BOOL bMakeVisible )
{
CDocument *pDoc = __super::OpenDocumentFile( lpszPathName, CHammer::IsNewDocumentVisible() );
return pDoc;
}
//-----------------------------------------------------------------------------
// Purpose: this function will attempt an orderly shutdown of all maps. It will attempt to
// close only documents that have no references, hopefully freeing up additional documents
// Input : bEndSession - ignored
//-----------------------------------------------------------------------------
void CHammerDocTemplate::CloseAllDocuments( BOOL bEndSession )
{
bool bFound = true;
// rough loop to always remove the first map doc that has no references, then start over, try again.
// if we still have maps with references ( that's bad ), we'll exit out of this loop and just do
// the default shutdown to force them all to close.
while( bFound )
{
bFound = false;
POSITION pos = GetFirstDocPosition();
while( pos != NULL )
{
CDocument *pDoc = GetNextDoc( pos );
CMapDoc *pMapDoc = dynamic_cast< CMapDoc * >( pDoc );
if ( pMapDoc && pMapDoc->GetReferenceCount() == 0 )
{
pDoc->OnCloseDocument();
bFound = true;
break;
}
}
}
#if 0
POSITION pos = GetFirstDocPosition();
while( pos != NULL )
{
CDocument *pDoc = GetNextDoc( pos );
CMapDoc *pMapDoc = dynamic_cast< CMapDoc * >( pDoc );
if ( pMapDoc )
{
pMapDoc->ForceNoReference();
}
}
__super::CloseAllDocuments( bEndSession );
#endif
}
//-----------------------------------------------------------------------------
// Purpose: This function will allow hammer to control the initial visibility of an opening document
// Input : pFrame - the new document's frame
// pDoc - the new document
// bMakeVisible - ignored as a parameter
//-----------------------------------------------------------------------------
void CHammerDocTemplate::InitialUpdateFrame( CFrameWnd* pFrame, CDocument* pDoc, BOOL bMakeVisible )
{
bMakeVisible = CHammer::IsNewDocumentVisible();
__super::InitialUpdateFrame( pFrame, pDoc, bMakeVisible );
if ( bMakeVisible )
{
CMapDoc *pMapDoc = dynamic_cast< CMapDoc * >( pDoc );
if ( pMapDoc )
{
pMapDoc->SetInitialUpdate();
}
}
}
//-----------------------------------------------------------------------------
// Purpose: this function will let all other maps know that an instanced map has been updated ( usually for volume size )
// Input : pInstanceMapDoc - the document that has been updated
//-----------------------------------------------------------------------------
void CHammerDocTemplate::UpdateInstanceMap( CMapDoc *pInstanceMapDoc )
{
POSITION pos = GetFirstDocPosition();
while( pos != NULL )
{
CDocument *pDoc = GetNextDoc( pos );
CMapDoc *pMapDoc = dynamic_cast< CMapDoc * >( pDoc );
if ( pMapDoc && pMapDoc != pInstanceMapDoc )
{
pMapDoc->UpdateInstanceMap( pInstanceMapDoc );
}
}
}
template < typename T, int Instance >
static T& ReliableStaticStorage()
{
Instance;
static T storage;
return storage;
}
enum Storage_t
{
APP_FN_POST_INIT,
APP_FN_PRE_SHUTDOWN,
APP_FN_MESSAGE_LOOP,
APP_FN_MESSAGE_PRETRANSLATE
};
#define s_appRegisteredPostInitFns ReliableStaticStorage< CUtlVector< void (*)() >, APP_FN_POST_INIT >()
#define s_appRegisteredPreShutdownFns ReliableStaticStorage< CUtlVector< void (*)() >, APP_FN_PRE_SHUTDOWN >()
#define s_appRegisteredMessageLoop ReliableStaticStorage< CUtlVector< void (*)() >, APP_FN_MESSAGE_LOOP >()
#define s_appRegisteredMessagePreTrans ReliableStaticStorage< CUtlVector< void (*)( MSG * ) >, APP_FN_MESSAGE_PRETRANSLATE >()
void AppRegisterPostInitFn( void (*fn)() )
{
s_appRegisteredPostInitFns.AddToTail( fn );
}
void AppRegisterMessageLoopFn( void (*fn)() )
{
s_appRegisteredMessageLoop.AddToTail( fn );
}
void AppRegisterMessagePretranslateFn( void (*fn)( MSG * ) )
{
s_appRegisteredMessagePreTrans.AddToTail( fn );
}
void AppRegisterPreShutdownFn( void (*fn)() )
{
s_appRegisteredPreShutdownFns.AddToTail( fn );
}
class CHammerCmdLine : public CCommandLineInfo
{
public:
CHammerCmdLine(void)
{
m_bShowLogo = true;
m_bGame = false;
m_bConfigDir = false;
}
void ParseParam(LPCTSTR lpszParam, BOOL bFlag, BOOL bLast)
{
if ((!m_bGame) && (bFlag && !stricmp(lpszParam, "game")))
{
m_bGame = true;
}
else if (m_bGame)
{
if (!bFlag)
{
m_strGame = lpszParam;
}
m_bGame = false;
}
else if (bFlag && !strcmpi(lpszParam, "nologo"))
{
m_bShowLogo = false;
}
else if (bFlag && !strcmpi(lpszParam, "makelib"))
{
bMakeLib = TRUE;
}
else if (!bFlag && bMakeLib)
{
MakePrefabLibrary(lpszParam);
}
else if ((!m_bConfigDir) && (bFlag && !stricmp(lpszParam, "configdir")))
{
m_bConfigDir = true;
}
else if (m_bConfigDir)
{
if ( !bFlag )
{
Options.configs.m_strConfigDir = lpszParam;
}
m_bConfigDir = false;
}
else
{
CCommandLineInfo::ParseParam(lpszParam, bFlag, bLast);
}
}
bool m_bShowLogo;
bool m_bGame; // Used to find and parse the "-game blah" parameter pair.
bool m_bConfigDir; // Used to find and parse the "-configdir blah" parameter pair.
CString m_strGame; // The name of the game to use for this session, ie "hl2" or "cstrike". This should match the mod dir, not the config name.
};
BEGIN_MESSAGE_MAP(CHammer, CWinApp)
//{{AFX_MSG_MAP(CHammer)
ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
ON_COMMAND(ID_FILE_OPEN, OnFileOpen)
ON_COMMAND(ID_FILE_NEW, OnFileNew)
ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
//-----------------------------------------------------------------------------
// Purpose: Constructor. Initializes member variables and creates a scratch
// buffer for use when loading WAD files.
//-----------------------------------------------------------------------------
CHammer::CHammer(void)
{
m_bActiveApp = true;
m_SuppressVideoAllocation = false;
m_bForceRenderNextFrame = false;
m_bClosing = false;
m_bFoundryMode = false;
m_CustomAcceleratorWindow = NULL;
}
//-----------------------------------------------------------------------------
// Purpose: Destructor. Frees scratch buffer used when loading WAD files.
// Deletes all command sequences used when compiling maps.
//-----------------------------------------------------------------------------
CHammer::~CHammer(void)
{
}
//-----------------------------------------------------------------------------
// Inherited from IAppSystem
//-----------------------------------------------------------------------------
bool CHammer::Connect( CreateInterfaceFn factory )
{
if ( !BaseClass::Connect( factory ) )
return false;
// bool bCVarOk = ConnectStudioRenderCVars( factory );
g_pFileSystem = ( IBaseFileSystem * )factory( BASEFILESYSTEM_INTERFACE_VERSION, NULL );
g_pStudioRender = ( IStudioRender * )factory( STUDIO_RENDER_INTERFACE_VERSION, NULL );
g_pEngineAPI = ( IEngineAPI * )factory( VENGINE_LAUNCHER_API_VERSION, NULL );
g_pMDLCache = (IMDLCache*)factory( MDLCACHE_INTERFACE_VERSION, NULL );
p4 = ( IP4 * )factory( P4_INTERFACE_VERSION, NULL );
g_Factory = factory;
if ( !g_pMDLCache || !g_pFileSystem || !g_pFullFileSystem || !materials || !g_pMaterialSystemHardwareConfig || !g_pStudioRender )
return false;
WinTab_Init();
// ensure we're in the same directory as the .EXE
char *p;
GetModuleFileName(NULL, m_szAppDir, MAX_PATH);
p = strrchr(m_szAppDir, '\\');
if(p)
{
// chop off \wc.exe
p[0] = 0;
}
if ( IsRunningInEngine() )
{
strcat( m_szAppDir, "\\bin" );
}
// Create the message window object for capturing errors and warnings.
// This does NOT create the window itself. That happens later in CMainFrame::Create.
g_pwndMessage = CMessageWnd::CreateMessageWndObject();
// Default location for GameConfig.txt is the same directory as Hammer.exe but this may be overridden on the command line
char szGameConfigDir[MAX_PATH];
APP()->GetDirectory( DIR_PROGRAM, szGameConfigDir );
Options.configs.m_strConfigDir = szGameConfigDir;
CHammerCmdLine cmdInfo;
ParseCommandLine(cmdInfo);
// Set up SteamApp() interface (for checking app ownership)
SteamAPI_InitSafe();
g_SteamAPIContext.Init();
// Load the options
// NOTE: Have to do this now, because we need it before Inits() are called
// NOTE: SetRegistryKey will cause hammer to look into the registry for its values
SetRegistryKey("Valve");
Options.Init();
if ( g_pThreadPool )
{
ThreadPoolStartParams_t startParams;
// this will set ideal processor on each thread
startParams.fDistribute = TRS_TRUE;
g_pThreadPool->Start( startParams );
}
return true;
}
void CHammer::Disconnect()
{
g_pStudioRender = NULL;
g_pFileSystem = NULL;
g_pEngineAPI = NULL;
g_pMDLCache = NULL;
BaseClass::Disconnect();
}
void *CHammer::QueryInterface( const char *pInterfaceName )
{
// We also implement the IMatSystemSurface interface
if (!Q_strncmp( pInterfaceName, INTERFACEVERSION_HAMMER, Q_strlen(INTERFACEVERSION_HAMMER) + 1))
return (IHammer*)this;
return NULL;
}
void CHammer::InitFoundryMode( CreateInterfaceFn factory, void *hGameWnd, const char *szGameDir )
{
m_bFoundryMode = true;
if ( !CommandLine()->FindParm( "-foundrymode" ) )
Error( "Running in Foundry requires -FoundryMode on the command line." );
if ( !Connect( factory ) )
Error( "CHammer::Connect failed" );
if ( !InitSessionGameConfig( szGameDir ) )
Error( "InitSessionGameConfig failed." );
if ( HammerInternalInit() != INIT_OK )
Error( "HammerInternalInit failed" );
}
void CHammer::NoteEngineGotFocus()
{
// Release focus on all our vgui stuff so the engine can own it.
HammerVGui()->SetFocus( NULL );
// Deactivate all CMapViews.
CMapDoc::NoteEngineGotFocus();
}
bool CHammer::IsHammerVisible()
{
CWnd *pWnd = GetMainWnd();
if ( !pWnd )
return false;
return pWnd->IsWindowVisible() ? true : false;
}
void CHammer::ToggleHammerVisible()
{
CWnd *pWnd = GetMainWnd();
if ( !pWnd )
return;
if ( pWnd->IsWindowVisible() )
{
pWnd->ShowWindow( SW_HIDE );
}
else
{
pWnd->ShowWindow( SW_SHOW );
}
}
//-----------------------------------------------------------------------------
// Methods related to message pumping
//-----------------------------------------------------------------------------
bool CHammer::HammerPreTranslateMessage(MSG * pMsg)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
// Copy this into the current message, needed for MFC
#if _MSC_VER >= 1300
_AFX_THREAD_STATE* pState = AfxGetThreadState();
pState->m_msgCur = *pMsg;
#else
m_msgCur = *pMsg;
#endif
return (/*pMsg->message == WM_KICKIDLE ||*/ PreTranslateMessage(pMsg) != FALSE);
}
//-----------------------------------------------------------------------------
// Return true if the message just dispatched should cause OnIdle to run.
//
// Return false for messages which do not usually affect the state of the user
// interface and happen very often.
//-----------------------------------------------------------------------------
bool CHammer::HammerIsIdleMessage(MSG *pMsg)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
// We generate lots of WM_TIMER messages and shouldn't call OnIdle because of them.
// This fixes tool tips not popping up when a map is open.
if ( pMsg->message == WM_TIMER )
return false;
return ( IsIdleMessage(pMsg) == TRUE );
}
// return TRUE if more idle processing
bool CHammer::HammerOnIdle(long count)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
return OnIdle(count) != FALSE;
}
//-----------------------------------------------------------------------------
// Purpose: Adds a backslash to the end of a string if there isn't one already.
// Input : psz - String to add the backslash to.
//-----------------------------------------------------------------------------
static void EnsureTrailingBackslash(char *psz)
{
if ((psz[0] != '\0') && (psz[strlen(psz) - 1] != '\\'))
{
strcat(psz, "\\");
}
}
//-----------------------------------------------------------------------------
// Purpose: Tweaks our data members to enable us to import old Hammer settings
// from the registry.
//-----------------------------------------------------------------------------
static const char *s_pszOldAppName = NULL;
void CHammer::BeginImportWCSettings(void)
{
s_pszOldAppName = m_pszAppName;
m_pszAppName = "Worldcraft";
SetRegistryKey("Valve");
}
//-----------------------------------------------------------------------------
// Purpose: Tweaks our data members to enable us to import old Valve Hammer Editor
// settings from the registry.
//-----------------------------------------------------------------------------
void CHammer::BeginImportVHESettings(void)
{
s_pszOldAppName = m_pszAppName;
m_pszAppName = "Valve Hammer Editor";
SetRegistryKey("Valve");
}
//-----------------------------------------------------------------------------
// Purpose: Restores our tweaked data members to their original state.
//-----------------------------------------------------------------------------
void CHammer::EndImportSettings(void)
{
m_pszAppName = s_pszOldAppName;
SetRegistryKey("Valve");
}
//-----------------------------------------------------------------------------
// Purpose: Retrieves various important directories.
// Input : dir - Enumerated directory to retrieve.
// p - Pointer to buffer that receives the full path to the directory.
//-----------------------------------------------------------------------------
void CHammer::GetDirectory(DirIndex_t dir, char *p)
{
switch (dir)
{
case DIR_PROGRAM:
{
strcpy(p, m_szAppDir);
EnsureTrailingBackslash(p);
break;
}
case DIR_PREFABS:
{
strcpy(p, g_pGameConfig->m_szPrefabDir);
if (*p == '\0')
{
// The prefab folder has not been set up so quietly set it to the app directory + "/Prefabs"
strcpy(p, m_szAppDir);
EnsureTrailingBackslash(p);
strcat(p, "Prefabs");
strcpy( g_pGameConfig->m_szPrefabDir, p );
}
//
// Make sure the prefabs folder exists. If not, create it
//
if ((_access( p, 0 )) == -1)
{
CreateDirectory(p, NULL);
}
break;
}
//
// Get the game directory with a trailing backslash. This is
// where the base game's resources are, such as "C:\Half-Life\valve\".
//
case DIR_GAME_EXE:
{
strcpy(p, g_pGameConfig->m_szGameExeDir);
EnsureTrailingBackslash(p);
break;
}
//
// Get the mod directory with a trailing backslash. This is where
// the mod's resources are, such as "C:\Half-Life\tfc\".
//
case DIR_MOD:
{
strcpy(p, g_pGameConfig->m_szModDir);
EnsureTrailingBackslash(p);
break;
}
//
// Get the materials directory with a trailing backslash. This is where
// the mod's materials are, such as "C:\Half-Life\tfc\materials".
//
case DIR_MATERIALS:
{
strcpy(p, g_pGameConfig->m_szModDir);
EnsureTrailingBackslash(p);
Q_strcat(p, "materials\\", MAX_PATH);
break;
}
case DIR_AUTOSAVE:
{
strcpy( p, m_szAutosaveDir );
EnsureTrailingBackslash(p);
break;
}
}
}
void CHammer::SetDirectory(DirIndex_t dir, const char *p)
{
switch(dir)
{
case DIR_AUTOSAVE:
{
strcpy( m_szAutosaveDir, p );
break;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Returns a color from the application configuration storage.
//-----------------------------------------------------------------------------
COLORREF CHammer::GetProfileColor(const char *pszSection, const char *pszKey, int r, int g, int b)
{
int newR, newG, newB;
CString strDefault;
CString strReturn;
char szBuff[128];
sprintf(szBuff, "%i %i %i", r, g, b);
strDefault = szBuff;
strReturn = GetProfileString(pszSection, pszKey, strDefault);
if (strReturn.IsEmpty())
return 0;
// Parse out colors.
char *pStart;
char *pCurrent;
pStart = szBuff;
pCurrent = pStart;
strcpy( szBuff, (char *)(LPCSTR)strReturn );
while (*pCurrent && *pCurrent != ' ')
pCurrent++;
*pCurrent++ = 0;
newR = atoi(pStart);
pStart = pCurrent;
while (*pCurrent && *pCurrent != ' ')
pCurrent++;
*pCurrent++ = 0;
newG = atoi(pStart);
pStart = pCurrent;
while (*pCurrent)
pCurrent++;
*pCurrent++ = 0;
newB = atoi(pStart);
return COLORREF(RGB(newR, newG, newB));
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pszURL -
//-----------------------------------------------------------------------------
void CHammer::OpenURL(const char *pszURL, HWND hwnd)
{
if (HINSTANCE(32) > ::ShellExecute(hwnd, "open", pszURL, NULL, NULL, 0))
{
AfxMessageBox("The website couldn't be opened.");
}
}
//-----------------------------------------------------------------------------
// Purpose: Opens a URL in the default web browser by string ID.
//-----------------------------------------------------------------------------
void CHammer::OpenURL(UINT nID, HWND hwnd)
{
CString str;
str.LoadString(nID);
OpenURL(str, hwnd);
}
//-----------------------------------------------------------------------------
// Purpose: Launches the help system for the specified help topic.
// Input : pszTopic - Topic to open.
//-----------------------------------------------------------------------------
void CHammer::Help(const char *pszTopic)
{
//
// Get the directory that the help file should be in (our program directory).
//
/*char szHelpDir[MAX_PATH];
GetDirectory(DIR_PROGRAM, szHelpDir);
//
// Find the application that is associated with compiled HTML files.
//
char szHelpExe[MAX_PATH];
HINSTANCE hResult = FindExecutable("wc.chm", szHelpDir, szHelpExe);
if (hResult > (HINSTANCE)32)
{
//
// Build the full topic with which to launch the help application.
//
char szParam[2 * MAX_PATH];
strcpy(szParam, szHelpDir);
strcat(szParam, "wc.chm");
if (pszTopic != NULL)
{
strcat(szParam, "::/");
strcat(szParam, pszTopic);
}
//
// Launch the help application for the given topic.
//
hResult = ShellExecute(NULL, "open", szHelpExe, szParam, szHelpDir, SW_SHOW);
}
if (hResult <= (HINSTANCE)32)
{
char szError[MAX_PATH];
sprintf(szError, "The help system could not be launched. The the following error was returned:\n%s (0x%X)", GetErrorString(), hResult);
AfxMessageBox(szError);
}
*/
}
static CSimpleWindowsLoggingListener s_SimpleWindowsLoggingListener;
static CHammerMessageLoggingListener s_HammerMessageLoggingListener;
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
static HANDLE dwChangeHandle = NULL;
void UpdatePrefabs_Init()
{
// Watch the prefabs tree for file or directory creation
// and deletion.
if (dwChangeHandle == NULL)
{
char szPrefabDir[MAX_PATH];
APP()->GetDirectory(DIR_PREFABS, szPrefabDir);
dwChangeHandle = FindFirstChangeNotification(
szPrefabDir, // directory to watch
TRUE, // watch the subtree
FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_FILE_NAME); // watch file and dir name changes
if (dwChangeHandle == INVALID_HANDLE_VALUE)
{
ExitProcess(GetLastError());
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void UpdatePrefabs()
{
// Wait for notification.
DWORD dwWaitStatus = WaitForSingleObject(dwChangeHandle, 0);
if (dwWaitStatus == WAIT_OBJECT_0)
{
// A file was created or deleted in the prefabs tree.
// Refresh the prefabs and restart the change notification.
CPrefabLibrary::FreeAllLibraries();
CPrefabLibrary::LoadAllLibraries();
GetMainWnd()->m_ObjectBar.UpdateListForTool(ToolManager()->GetActiveToolID());
if (FindNextChangeNotification(dwChangeHandle) == FALSE)
{
ExitProcess(GetLastError());
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void UpdatePrefabs_Shutdown()
{
FindCloseChangeNotification(dwChangeHandle);
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
BOOL CHammer::InitInstance()
{
return TRUE;
}
//-----------------------------------------------------------------------------
// Purpose: Prompt the user to select a game configuration.
//-----------------------------------------------------------------------------
CGameConfig *CHammer::PromptForGameConfig()
{
CEditGameConfigs dlg(TRUE, GetMainWnd());
if (dlg.DoModal() != IDOK)
{
return NULL;
}
return dlg.GetSelectedGame();
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CHammer::InitSessionGameConfig(const char *szGame)
{
CGameConfig *pConfig = NULL;
bool bManualChoice = false;
if ( CommandLine()->FindParm( "-chooseconfig" ) )
{
pConfig = PromptForGameConfig();
bManualChoice = true;
}
if (!bManualChoice)
{
if (szGame && szGame[0] != '\0')
{
// They passed in -game on the command line, use that.
pConfig = Options.configs.FindConfigForGame(szGame);
if (!pConfig)
{
Msg(mwError, "Invalid game \"%s\" specified on the command-line, ignoring.", szGame);
}
}
else
{
// No -game on the command line, try using VPROJECT.
const char *pszGameDir = getenv("vproject");
if ( pszGameDir )
{
pConfig = Options.configs.FindConfigForGame(pszGameDir);
if (!pConfig)
{
Msg(mwError, "Invalid game \"%s\" found in VPROJECT environment variable, ignoring.", pszGameDir);
}
}
}
}
if (pConfig == NULL)
{
// Nothing useful was passed in or found in VPROJECT.
// If there's only one config, use that.
if (Options.configs.GetGameConfigCount() == 1)
{
pConfig = Options.configs.GetGameConfig(0);
}
else
{
// Otherwise, prompt for a config to use.
pConfig = PromptForGameConfig();
}
}
if (pConfig)
{
CGameConfig::SetActiveGame(pConfig);
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Check for 16-bit color or higher.
//-----------------------------------------------------------------------------
bool CHammer::Check16BitColor()
{
// Check for 15-bit color or higher.
HDC hDC = ::CreateCompatibleDC(NULL);
if (hDC)
{
int bpp = GetDeviceCaps(hDC, BITSPIXEL);
if (bpp < 15)
{
AfxMessageBox("Your screen must be in 16-bit color or higher to run Hammer.");
return false;
}
::DeleteDC(hDC);
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Returns true if Hammer is in the process of shutting down.
//-----------------------------------------------------------------------------
InitReturnVal_t CHammer::Init()
{
return (InitReturnVal_t)WrapFunctionWithMinidumpHandler( &CHammer::StaticHammerInternalInit, this, INIT_FAILED );
}
int CHammer::StaticHammerInternalInit( void *pParam )
{
return (int)((CHammer*)pParam)->HammerInternalInit();
}
void HammerFileSystem_ReportSearchPath( const char *szPathID )
{
char szSearchPath[ 4096 ];
g_pFullFileSystem->GetSearchPath( szPathID, true, szSearchPath, sizeof( szSearchPath ) );
Msg( mwStatus, "------------------------------------------------------------------" );
char *pszOnePath = strtok( szSearchPath, ";" );
while ( pszOnePath )
{
Msg( mwStatus, "Search Path (%s): %s", szPathID, pszOnePath );
pszOnePath = strtok( NULL, ";" );
}
}
InitReturnVal_t CHammer::HammerInternalInit()
{
if ( !IsFoundryMode() )
{
LoggingSystem_PushLoggingState();
LoggingSystem_RegisterLoggingListener( &s_SimpleWindowsLoggingListener );
LoggingSystem_RegisterLoggingListener( &s_HammerMessageLoggingListener );
MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false );
}
InitReturnVal_t nRetVal = BaseClass::Init();
if ( nRetVal != INIT_OK )
return nRetVal;
if ( !Check16BitColor() )
return INIT_FAILED;
//
// Create a custom window class for this application so that engine's
// FindWindow will find us.
//
WNDCLASS wndcls;
memset(&wndcls, 0, sizeof(WNDCLASS));
wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndcls.lpfnWndProc = AfxWndProc;
wndcls.hInstance = AfxGetInstanceHandle();
wndcls.hIcon = LoadIcon(IDR_MAINFRAME);
wndcls.hCursor = LoadCursor( IDC_ARROW );
wndcls.hbrBackground = (HBRUSH)0; // (COLOR_WINDOW + 1);
wndcls.lpszMenuName = "IDR_MAINFRAME";
wndcls.cbWndExtra = 0;
// HL Shell class name
wndcls.lpszClassName = "VALVEWORLDCRAFT";
// Register it, exit if it fails
if(!AfxRegisterClass(&wndcls))
{
AfxMessageBox("Could not register Hammer's main window class");
return INIT_FAILED;
}
srand(time(NULL));
WriteProfileString("General", "Directory", m_szAppDir);
//
// Create a window to receive shell commands from the engine, and attach it
// to our shell processor.
//
g_ShellMessageWnd.Create();
g_ShellMessageWnd.SetShell(&g_Shell);
if (bMakeLib)
return INIT_FAILED; // made library .. don't want to enter program
CHammerCmdLine cmdInfo;
ParseCommandLine(cmdInfo);
//
// Create and optionally display the splash screen.
//
CSplashWnd::EnableSplashScreen(cmdInfo.m_bShowLogo);
//
// load cmd sequences - different from options because
// users might want to share (darn registry)
//
if ( !LoadSequences( "CmdSeq.wc" ) )
{
// Try to load the default sequences if there are no user-defined ones.
LoadSequences( "CmdSeqDefault.wc" );
}
// other init:
randomize();
/*
#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif
*/
LoadStdProfileSettings(); // Load standard INI file options (including MRU)
// Register the application's document templates. Document templates
// serve as the connection between documents, frame windows and views.
pMapDocTemplate = new CHammerDocTemplate(
IDR_MAPDOC,
RUNTIME_CLASS(CMapDoc),
RUNTIME_CLASS(CChildFrame), // custom MDI child frame
RUNTIME_CLASS(CMapView2D));
AddDocTemplate(pMapDocTemplate);
pManifestDocTemplate = new CHammerDocTemplate(
IDR_MANIFESTDOC,
RUNTIME_CLASS(CManifest),
RUNTIME_CLASS(CChildFrame), // custom MDI child frame
RUNTIME_CLASS(CMapView2D));
HINSTANCE hInst = AfxFindResourceHandle( MAKEINTRESOURCE( IDR_MAPDOC ), RT_MENU );
pManifestDocTemplate->m_hMenuShared = ::LoadMenu( hInst, MAKEINTRESOURCE( IDR_MAPDOC ) );
hInst = AfxFindResourceHandle( MAKEINTRESOURCE( IDR_MAPDOC ), RT_ACCELERATOR );
pManifestDocTemplate->m_hAccelTable = ::LoadAccelerators( hInst, MAKEINTRESOURCE( IDR_MAPDOC ) );
AddDocTemplate(pManifestDocTemplate);
// register shell file types
RegisterShellFileTypes();
//
// Initialize the rich edit control so we can use it in the entity help dialog.
//
AfxInitRichEdit();
//
// Create main MDI Frame window. Must be done AFTER registering the multidoc template!
//
CMainFrame *pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return INIT_FAILED;
m_pMainWnd = pMainFrame;
// try to init VGUI
HammerVGui()->Init( m_pMainWnd->GetSafeHwnd() );
// The main window has been initialized, so show and update it.
if ( IsFoundryMode() )
{
m_nCmdShow = SW_SHOW;
CRect rcDesktop;
GetWindowRect( GetDesktopWindow(), &rcDesktop );
CRect rcEngineWnd;
HWND hEngineWnd = (HWND)enginetools->GetEngineHwnd();
GetWindowRect( hEngineWnd, &rcEngineWnd );
// Move the engine to the right side of the screen.
int nEngineWndX = rcDesktop.Width() - rcEngineWnd.Width();
SetWindowPos( hEngineWnd, NULL, nEngineWndX, 0, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW );
// Move Hammer to the left and make it square.
int nHammerWndWidth = nEngineWndX;
int nHammerWndHeight = min( nHammerWndWidth, rcDesktop.Height() - 100 );
pMainFrame->SetWindowPos( NULL, 0, 0, nHammerWndWidth, nHammerWndHeight, SWP_NOZORDER | SWP_SHOWWINDOW );
// Move the properties dialog below the engine window.
pMainFrame->pObjectProperties->SetWindowPos( NULL, nEngineWndX, rcEngineWnd.Height(), 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW );
}
else
{
m_nCmdShow = SW_SHOWMAXIMIZED;
pMainFrame->ShowWindow(m_nCmdShow);
}
pMainFrame->UpdateWindow();
if ( !IsFoundryMode() )
{
//
// Init the game and mod dirs in the file system.
// This needs to happen before calling Init on the material system.
//
CFSSearchPathsInit initInfo;
initInfo.m_pFileSystem = g_pFullFileSystem;
initInfo.m_pDirectoryName = g_pGameConfig->m_szModDir;
if ( !initInfo.m_pDirectoryName[0] )
{
static char pTempBuf[MAX_PATH];
APP()->GetDirectory(DIR_PROGRAM, pTempBuf);
strcat( pTempBuf, "..\\hl2" );
initInfo.m_pDirectoryName = pTempBuf;
}
CSplashWnd::ShowSplashScreen(pMainFrame);
if ( FileSystem_LoadSearchPaths( initInfo ) != FS_OK )
{
Error( "Unable to load search paths!\n" );
}
// Report this for the user's sake
HammerFileSystem_ReportSearchPath( "GAME" );
}
// Now that we've initialized the file system, we can parse this config's gameinfo.txt for the additional settings there.
g_pGameConfig->ParseGameInfo();
if ( !IsFoundryMode() )
{
materials->ModInit();
}
//
// Initialize the texture manager and load all textures.
//
if (!g_Textures.Initialize(m_pMainWnd->m_hWnd))
{
Msg(mwError, "Failed to initialize texture system.");
}
else
{
//
// Initialize studio model rendering (must happen after g_Textures.Initialize since
// g_Textures.Initialize kickstarts the material system and sets up g_MaterialSystemClientFactory)
//
StudioModel::Initialize();
g_Textures.LoadAllGraphicsFiles();
g_Textures.SetActiveConfig(g_pGameConfig);
}
//
// Initialize the particle system manager
//
g_pParticleSystemMgr->Init( NULL, true );
g_pParticleSystemMgr->AddBuiltinSimulationOperators();
g_pParticleSystemMgr->AddBuiltinRenderingOperators();
// Watch for changes to models.
InitStudioFileChangeWatcher();
LoadFileSystemDialogModule();
// Load detail object descriptions.
char szGameDir[_MAX_PATH];
APP()->GetDirectory(DIR_MOD, szGameDir);
DetailObjects::LoadEmitDetailObjectDictionary( szGameDir );
// Initialize the sound system
g_Sounds.Initialize();
UpdatePrefabs_Init();
// Indicate that we are ready to use.
m_pMainWnd->FlashWindow(TRUE);
// Parse command line for standard shell commands, DDE, file open
if ( !IsRunningInEngine() )
{
if ( Q_stristr( cmdInfo.m_strFileName, ".vmf" ) )
{
// we don't want to make a new file (default behavior if no file
// is specified on the commandline.)
// Dispatch commands specified on the command line
if (!ProcessShellCommand(cmdInfo))
return INIT_FAILED;
}
}
if ( !IsFoundryMode() && Options.general.bClosedCorrectly == FALSE )
{
CString strLastGoodSave = APP()->GetProfileString("General", "Last Good Save", "");
if ( strLastGoodSave.GetLength() != 0 )
{
char msg[1024];
V_snprintf( msg, sizeof( msg ), "Hammer did not shut down correctly the last time it was used.\nWould you like to load the last saved file?\n(%s)", (const char*)strLastGoodSave );
if ( AfxMessageBox( msg, MB_YESNO ) == IDYES )
{
LoadLastGoodSave();
}
}
}
#ifdef VPROF_HAMMER
g_VProfCurrentProfile.Start();
#endif
// Execute the post-init registered callbacks
for ( int iFn = 0; iFn < s_appRegisteredPostInitFns.Count(); ++ iFn )
{
void (*fn)() = s_appRegisteredPostInitFns[ iFn ];
(*fn)();
}
WinTab_Open( m_pMainWnd->m_hWnd );
CSplashWnd::HideSplashScreen();
// create the lighting preview thread
g_LPreviewThread = CreateSimpleThread( LightingPreviewThreadFN, 0 );
return INIT_OK;
}
int CHammer::MainLoop()
{
return WrapFunctionWithMinidumpHandler( StaticInternalMainLoop, this, -1 );
}
int CHammer::StaticInternalMainLoop( void *pParam )
{
return ((CHammer*)pParam)->InternalMainLoop();
}
int CHammer::InternalMainLoop()
{
MSG msg;
g_pDataCache->SetSize( 128 * 1024 * 1024 );
// For tracking the idle time state
bool bIdle = true;
long lIdleCount = 0;
// We've got our own message pump here
g_pInputSystem->EnableMessagePump( false );
// Acquire and dispatch messages until a WM_QUIT message is received.
for (;;)
{
RunFrame();
// Do idle processing at most once per frame, incrementing the counter until we get an
// idle message. The counter is used as a general indication of how idle the application is,
// so critical idle processing is done for lower values of lIdleCount, and less important
// stuff is done as lIdleCount increases.
//
// When there's no more idle work to do, OnIdle returns false and we stop doing idle
// processing until another idle message is processed by the message loop.
if ( bIdle && !HammerOnIdle(lIdleCount++) )
{
bIdle = false; // done with idle work for now
}
// Execute the message loop registered callbacks
for ( int iFn = 0; iFn < s_appRegisteredMessageLoop.Count(); ++ iFn )
{
void (*fn)() = s_appRegisteredMessageLoop[ iFn ];
(*fn)();
}
//
// Pump messages until the message queue is empty.
//
while (::PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
{
if ( msg.message == WM_QUIT )
return 1;
// Pump the message through a custom message
// pre-translation chain
for ( int iFn = 0; iFn < s_appRegisteredMessagePreTrans.Count(); ++ iFn )
{
void (*fn)( MSG * ) = s_appRegisteredMessagePreTrans[ iFn ];
(*fn)( &msg );
}
if ( !HammerPreTranslateMessage(&msg) )
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
// Reset idle state after pumping idle message.
if ( HammerIsIdleMessage(&msg) )
{
bIdle = true;
lIdleCount = 0;
}
}
}
Assert(0); // not reachable
}
//-----------------------------------------------------------------------------
// Shuts down hammer
//-----------------------------------------------------------------------------
void CHammer::Shutdown()
{
if ( g_LPreviewThread )
{
MessageToLPreview StopMsg( LPREVIEW_MSG_EXIT );
g_HammerToLPreviewMsgQueue.QueueMessage( StopMsg );
ThreadJoin( g_LPreviewThread );
g_LPreviewThread = 0;
}
// Execute the pre-shutdown registered callbacks
for ( int iFn = s_appRegisteredPreShutdownFns.Count(); iFn --> 0 ; )
{
void (*fn)() = s_appRegisteredPreShutdownFns[ iFn ];
(*fn)();
}
#ifdef VPROF_HAMMER
g_VProfCurrentProfile.Stop();
#endif
// PrintBudgetGroupTimes_Recursive( g_VProfCurrentProfile.GetRoot() );
HammerVGui()->Shutdown();
UnloadFileSystemDialogModule();
// Delete the command sequences.
int nSequenceCount = m_CmdSequences.GetSize();
for (int i = 0; i < nSequenceCount; i++)
{
CCommandSequence *pSeq = m_CmdSequences[i];
if ( pSeq != NULL )
{
delete pSeq;
m_CmdSequences[i] = NULL;
}
}
g_Textures.ShutDown();
// Shutdown the sound system
g_Sounds.ShutDown();
materials->ModShutdown();
BaseClass::Shutdown();
}
//-----------------------------------------------------------------------------
// Methods used by the engine
//-----------------------------------------------------------------------------
const char *CHammer::GetDefaultMod()
{
return g_pGameConfig->GetMod();
}
const char *CHammer::GetDefaultGame()
{
return g_pGameConfig->GetGame();
}
const char *CHammer::GetDefaultModFullPath()
{
return g_pGameConfig->m_szModDir;
}
//-----------------------------------------------------------------------------
// Pops up the options dialog
//-----------------------------------------------------------------------------
RequestRetval_t CHammer::RequestNewConfig()
{
if ( !Options.RunConfigurationDialog() )
return REQUEST_QUIT;
return REQUEST_OK;
}
//-----------------------------------------------------------------------------
// Purpose: Returns true if Hammer is in the process of shutting down.
//-----------------------------------------------------------------------------
bool CHammer::IsClosing()
{
return m_bClosing;
}
//-----------------------------------------------------------------------------
// Purpose: Signals the beginning of app shutdown. Should be called before
// rendering views.
//-----------------------------------------------------------------------------
void CHammer::BeginClosing()
{
m_bClosing = true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CHammer::ExitInstance()
{
g_ShellMessageWnd.DestroyWindow();
UpdatePrefabs_Shutdown();
if ( !IsFoundryMode() )
{
LoggingSystem_PopLoggingState();
}
SaveStdProfileSettings();
return CWinApp::ExitInstance();
}
//-----------------------------------------------------------------------------
// Purpose: this function sets the global flag indicating if new documents should
// be visible.
// Input : bIsVisible - flag to indicate visibility status.
//-----------------------------------------------------------------------------
void CHammer::SetIsNewDocumentVisible( bool bIsVisible )
{
CHammer::m_bIsNewDocumentVisible = bIsVisible;
}
//-----------------------------------------------------------------------------
// Purpose: this functionr eturns the global flag indicating if new documents should
// be visible.
//-----------------------------------------------------------------------------
bool CHammer::IsNewDocumentVisible( void )
{
return CHammer::m_bIsNewDocumentVisible;
}
void CHammer::SetCustomAccelerator( HWND hWnd, WORD nID )
{
m_CustomAcceleratorWindow = hWnd;
m_CustomAccelerator = ::LoadAccelerators( AfxGetInstanceHandle(), MAKEINTRESOURCE( nID ) );
}
void CHammer::ClearCustomAccelerator( )
{
m_CustomAcceleratorWindow = NULL;
}
/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About
class CAboutDlg : public CDialog
{
public:
CAboutDlg();
// Dialog Data
//{{AFX_DATA(CAboutDlg)
enum { IDD = IDD_ABOUTBOX };
CStatic m_cRedHerring;
CButton m_Order;
//}}AFX_DATA
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CAboutDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
//}}AFX_VIRTUAL
// Implementation
protected:
//{{AFX_MSG(CAboutDlg)
afx_msg void OnOrder();
virtual BOOL OnInitDialog();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
//{{AFX_DATA_INIT(CAboutDlg)
//}}AFX_DATA_INIT
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : pDX -
//-----------------------------------------------------------------------------
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CAboutDlg)
DDX_Control(pDX, IDC_REDHERRING, m_cRedHerring);
DDX_Control(pDX, IDC_ORDER, m_Order);
//}}AFX_DATA_MAP
}
#include <process.h>
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CAboutDlg::OnOrder()
{
char szBuf[MAX_PATH];
GetWindowsDirectory(szBuf, MAX_PATH);
strcat(szBuf, "\\notepad.exe");
_spawnl(_P_NOWAIT, szBuf, szBuf, "order.txt", NULL);
}
#define DEMO_VERSION 0
// 1, 4, 8, 17, 0, 0 // Encodes "Valve"
#if DEMO_VERSION
char gVersion[] = {
#if DEMO_VERSION==1
7, 38, 68, 32, 4, 77, 12, 1, 0 // Encodes "PC Gamer Demo"
#elif DEMO_VERSION==2
7, 38, 68, 32, 4, 77, 12, 0, 0 // Encodes "PC Games Demo"
#elif DEMO_VERSION==3
20, 10, 9, 23, 16, 84, 12, 1, 0, 38, 65, 25, 6, 1, 11, 119, 50, 11, 21, 9, 68, 0 // Encodes "Computer Gaming World Demo"
#elif DEMO_VERSION==4
25, 0, 28, 19, 72, 103, 12, 29, 69, 19, 65, 0, 6, 0, 2, 0 // Encodes "Next-Generation Demo"
#elif DEMO_VERSION==5
20, 10, 9, 23, 16, 84, 12, 1, 0, 38, 65, 25, 10, 79, 41, 57, 17, 1, 21, 17, 65, 0, 29, 77, 4, 78, 0, 0 // Encodes "Computer Game Entertainment"
#elif DEMO_VERSION==6
20, 10, 9, 23, 16, 84, 12, 1, 0, 0, 78, 16, 79, 33, 9, 35, 69, 52, 11, 4, 89, 12, 1, 0 // Encodes "Computer and Net Player"
#elif DEMO_VERSION==7
50, 72, 52, 43, 36, 121, 0 // Encodes "e-PLAY"
#elif DEMO_VERSION==8
4, 17, 22, 6, 17, 69, 14, 10, 0, 49, 76, 1, 28, 0 // Encodes "Strategy Plus"
#elif DEMO_VERSION==9
7, 38, 68, 42, 4, 71, 8, 9, 73, 15, 69, 0 // Encodes "PC Magazine"
#elif DEMO_VERSION==10
5, 10, 8, 11, 12, 78, 14, 83, 115, 21, 79, 26, 10, 0 // Encodes "Rolling Stone"
#elif DEMO_VERSION==11
16, 4, 9, 2, 22, 80, 6, 7, 0 // Encodes "Gamespot"
#endif
};
static char gKey[] = "Wedge is a tool"; // Decrypt key
// XOR a string with a key
void Encode( char *pstring, char *pkey, int strLen )
{
int i, len;
len = strlen( pkey );
for ( i = 0; i < strLen; i++ )
pstring[i] ^= pkey[ i % len ];
}
#endif // DEMO_VERSION
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns TRUE on success, FALSE on failure.
//-----------------------------------------------------------------------------
BOOL CAboutDlg::OnInitDialog(void)
{
CDialog::OnInitDialog();
m_Order.SetRedraw(FALSE);
#if DEMO_VERSION
static BOOL bFirst = TRUE;
if(bFirst)
{
Encode(gVersion, gKey, sizeof(gVersion)-1);
bFirst = FALSE;
}
CString str;
str.Format("%s Demo", gVersion);
m_cRedHerring.SetWindowText(str);
#endif // DEMO_VERSION
//
// Display the build number.
//
CWnd *pWnd = GetDlgItem(IDC_BUILD_NUMBER);
if (pWnd != NULL)
{
char szTemp1[MAX_PATH];
char szTemp2[MAX_PATH];
int nBuild = build_number();
pWnd->GetWindowText(szTemp1, sizeof(szTemp1));
sprintf(szTemp2, szTemp1, nBuild);
pWnd->SetWindowText(szTemp2);
}
//
// For SDK builds, append "SDK" to the version number.
//
#ifdef SDK_BUILD
char szTemp[MAX_PATH];
GetWindowText(szTemp, sizeof(szTemp));
strcat(szTemp, " SDK");
SetWindowText(szTemp);
#endif // SDK_BUILD
return TRUE;
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
//{{AFX_MSG_MAP(CAboutDlg)
ON_BN_CLICKED(IDC_ORDER, OnOrder)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CHammer::OnAppAbout(void)
{
CAboutDlg aboutDlg;
aboutDlg.DoModal();
#ifdef VPROF_HAMMER
g_VProfCurrentProfile.OutputReport();
g_VProfCurrentProfile.Reset();
g_pMemAlloc->DumpStats();
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CHammer::OnFileNew(void)
{
pMapDocTemplate->OpenDocumentFile(NULL);
if(Options.general.bLoadwinpos && Options.general.bIndependentwin)
{
::GetMainWnd()->LoadWindowStates();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CHammer::OnFileOpen(void)
{
// if there is no initial directory use the one specified in the game configuration
static char szInitialDir[MAX_PATH] = "";
if (szInitialDir[0] == '\0')
{
strcpy(szInitialDir, g_pGameConfig->szMapDir);
}
OPENFILENAME ofn;
//memory buffer to contain the file name
char szFileNameBuffer[MAX_PATH];
ZeroMemory( &ofn , sizeof( ofn));
ofn.lStructSize = sizeof ( ofn );
ofn.hwndOwner = AfxGetMainWnd()->GetSafeHwnd();
ofn.lpstrFile = szFileNameBuffer;
ofn.lpstrFile[0] = '\0';
ofn.nMaxFile = sizeof( szFileNameBuffer );
ofn.lpstrFilter = "Valve Map Files (*.vmf;*.vmm)\0*.vmf;*.vmm\0Valve Map Files Autosave (*.vmf_autosave)\0*.vmf_autosave\0Worldcraft RMFs (*.rmf)\0*.rmf\0Worldcraft Maps (*.map)\0*.map\0All\0*.*\0";
ofn.nFilterIndex =1;
ofn.lpstrFileTitle = NULL ;
ofn.nMaxFileTitle = 0 ;
ofn.lpstrInitialDir=szInitialDir;
ofn.Flags = OFN_LONGNAMES | OFN_HIDEREADONLY | OFN_NOCHANGEDIR;
// if the user cancels or closes the Open dialog box or an error occurs, the return value is zero.
if (!GetOpenFileName( &ofn ) )
{
return;
}
//
// Get the directory they browsed to for next time.
//
CString str = ofn.lpstrFile;
int nSlash = str.ReverseFind('\\');
if (nSlash != -1)
{
strcpy(szInitialDir, str.Left(nSlash));
}
// add the appropriate extension (based on filter type) if it was unspecified by the user
if (str.Find('.') == -1)
{
switch (ofn.nFilterIndex)
{
case 1:
{
str += ".vmf";
break;
}
case 2:
{
str += ".vmf_autosave";
break;
}
case 3:
{
str += ".rmf";
break;
}
case 4:
{
str += ".map";
break;
}
}
}
OpenDocumentFile(str);
}
//-----------------------------------------------------------------------------
// This is the generic file open function that is called by the framework.
//-----------------------------------------------------------------------------
CDocument *CHammer::OpenDocumentFile(LPCTSTR lpszFileName)
{
CDocument *pDoc = OpenDocumentOrInstanceFile( lpszFileName );
// Do work that needs to happen after opening all instances here.
// NOTE: Make sure this work doesn't need to happen per instance!!!
return pDoc;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CDocument *CHammer::OpenDocumentOrInstanceFile(LPCTSTR lpszFileName)
{
// CWinApp::OnOpenRecentFile may get its file history cycled through by instances being opened, thus the pointer becomes invalid
CString SaveFileName = lpszFileName;
if(GetFileAttributes( SaveFileName ) == 0xFFFFFFFF)
{
CString Message;
Message = "The file " + SaveFileName + " does not exist.";
AfxMessageBox( Message );
return NULL;
}
CheckForFileSync( SaveFileName, CHammer::m_bIsNewDocumentVisible );
CDocument *pDoc = m_pDocManager->OpenDocumentFile( SaveFileName );
CMapDoc *pMapDoc = dynamic_cast< CMapDoc * >( pDoc );
if ( pMapDoc )
{
CMapDoc::SetActiveMapDoc( pMapDoc );
pMapDoc->CheckFileStatus();
}
if( pDoc && Options.general.bLoadwinpos && Options.general.bIndependentwin)
{
::GetMainWnd()->LoadWindowStates();
}
if ( pMapDoc && !CHammer::IsNewDocumentVisible() )
{
pMapDoc->ShowWindow( false );
}
else
{
pMapDoc->ShowWindow( true );
}
if ( pDoc && ((CMapDoc *)pDoc)->IsAutosave() )
{
char szRenameMessage[MAX_PATH+MAX_PATH+256];
CString newMapPath = *((CMapDoc *)pDoc)->AutosavedFrom();
sprintf( szRenameMessage, "This map was loaded from an autosave file.\nWould you like to rename it from \"%s\" to \"%s\"?\nNOTE: This will not save the file with the new name; it will only rename it.", SaveFileName, newMapPath );
if ( AfxMessageBox( szRenameMessage, MB_ICONHAND | MB_YESNO ) == IDYES )
{
((CMapDoc *)pDoc)->SetPathName( newMapPath );
}
}
else
{
if ( CHammer::m_bIsNewDocumentVisible == true )
{
pMapDoc->CheckFileStatus();
if ( pMapDoc->IsReadOnly() == true && pMapDoc->IsCheckedOut() == false )
{
if ( pMapDoc->IsVersionControlled() )
{
CUtlString dialogText;
dialogText.Format("This map is not checked out. Would you like to check it out?\n\n%s", SaveFileName );
CDialogWithCheckbox Dialog( "Checkout File", dialogText,"Check out BSP.", false, !pMapDoc->BspOkToCheckOut() );
if( Dialog.DoModal() == IDOK )
{
pMapDoc->CheckOut();
if ( pMapDoc->IsReadOnly() )
{
AfxMessageBox( "Checkout was NOT successful!", MB_OK ) ;
}
}
if ( Dialog.IsCheckboxChecked() )
{
pMapDoc->CheckOutBsp();
}
}
else
{
char szMessage[ MAX_PATH + MAX_PATH+ 256 ];
sprintf( szMessage, "This map is marked as READ ONLY. You will not be able to save this file.\n\n%s", SaveFileName );
AfxMessageBox( szMessage );
}
}
}
}
return pDoc;
}
//-----------------------------------------------------------------------------
// Returns true if this is a key message that is not a special dialog navigation message.
//-----------------------------------------------------------------------------
inline bool IsKeyStrokeMessage( MSG *pMsg )
{
if ( ( pMsg->message != WM_KEYDOWN ) && ( pMsg->message != WM_CHAR ) )
return false;
// Check for special dialog navigation characters -- they don't count
if ( ( pMsg->wParam == VK_ESCAPE ) || ( pMsg->wParam == VK_RETURN ) || ( pMsg->wParam == VK_TAB ) )
return false;
if ( ( pMsg->wParam == VK_UP ) || ( pMsg->wParam == VK_DOWN ) || ( pMsg->wParam == VK_LEFT ) || ( pMsg->wParam == VK_RIGHT ) )
return false;
return true;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
BOOL CHammer::PreTranslateMessage(MSG* pMsg)
{
// CG: The following lines were added by the Splash Screen component.
if (CSplashWnd::PreTranslateAppMessage(pMsg))
return TRUE;
// Suppress the accelerator table for edit controls so that users can type
// uppercase characters without invoking Hammer tools.
if ( IsKeyStrokeMessage( pMsg ) )
{
char className[80];
::GetClassNameA( pMsg->hwnd, className, sizeof( className ) );
// The classname of dialog window in the VGUI model browser and particle browser is AfxWnd80sd in Debug and AfxWnd80s in Release
// For later versions of visual studio, it is afxwnd100s and afxwnd100sd. So for future proofing this we're just gonig to check on afxwnd
if ( !V_stricmp( className, "edit" ) || !V_strnicmp( className, "afxwnd", strlen( "afxwnd" ) ) )
{
// Typing in an edit control. Don't pretranslate, just translate/dispatch.
return FALSE;
}
if ( m_CustomAcceleratorWindow != NULL )
{
if ( TranslateAccelerator( m_CustomAcceleratorWindow, m_CustomAccelerator, pMsg ) != 0 )
{
return TRUE;
}
}
}
return CWinApp::PreTranslateMessage(pMsg);
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CHammer::LoadSequences( const char *szSeqFileName )
{
char szRootDir[MAX_PATH];
char szFullPath[MAX_PATH];
APP()->GetDirectory(DIR_PROGRAM, szRootDir);
Q_MakeAbsolutePath( szFullPath, MAX_PATH, szSeqFileName, szRootDir );
std::ifstream file(szFullPath, std::ios::in | std::ios::binary);
if( !file.is_open() )
{
return false; // none to load
}
// skip past header & version
float fThisVersion;
file.seekg(strlen(pszSequenceHdr));
file.read((char*)&fThisVersion, sizeof fThisVersion);
// read number of sequences
DWORD dwSize;
int nSeq;
file.read((char*)&dwSize, sizeof dwSize);
nSeq = dwSize;
for(int i = 0; i < nSeq; i++)
{
CCommandSequence *pSeq = new CCommandSequence;
file.read(pSeq->m_szName, 128);
// read commands in sequence
file.read((char*)&dwSize, sizeof dwSize);
int nCmd = dwSize;
CCOMMAND cmd;
for(int iCmd = 0; iCmd < nCmd; iCmd++)
{
if(fThisVersion < 0.2f)
{
file.read((char*)&cmd, sizeof(CCOMMAND)-1);
cmd.bNoWait = FALSE;
}
else
{
file.read((char*)&cmd, sizeof(CCOMMAND));
}
pSeq->m_Commands.Add(cmd);
}
m_CmdSequences.Add(pSeq);
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CHammer::SaveSequences(void)
{
char szRootDir[MAX_PATH];
char szFullPath[MAX_PATH];
APP()->GetDirectory(DIR_PROGRAM, szRootDir);
Q_MakeAbsolutePath( szFullPath, MAX_PATH, "CmdSeq.wc", szRootDir );
std::ofstream file( szFullPath, std::ios::out | std::ios::binary );
// write header
file.write(pszSequenceHdr, Q_strlen(pszSequenceHdr));
// write out version
file.write((char*)&fSequenceVersion, sizeof(float));
// write out each sequence..
int i, nSeq = m_CmdSequences.GetSize();
DWORD dwSize = nSeq;
file.write((char*)&dwSize, sizeof dwSize);
for(i = 0; i < nSeq; i++)
{
CCommandSequence *pSeq = m_CmdSequences[i];
// write name of sequence
file.write(pSeq->m_szName, 128);
// write number of commands
int nCmd = pSeq->m_Commands.GetSize();
dwSize = nCmd;
file.write((char*)&dwSize, sizeof dwSize);
// write commands ..
for(int iCmd = 0; iCmd < nCmd; iCmd++)
{
CCOMMAND &cmd = pSeq->m_Commands[iCmd];
file.write((char*)&cmd, sizeof cmd);
}
}
}
void CHammer::SetForceRenderNextFrame()
{
m_bForceRenderNextFrame = true;
}
bool CHammer::GetForceRenderNextFrame()
{
return m_bForceRenderNextFrame;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pDoc -
//-----------------------------------------------------------------------------
void CHammer::UpdateLighting(CMapDoc *pDoc)
{
static int lastPercent = -20000;
int curPercent = -10000;
IBSPLighting *pLighting = pDoc->GetBSPLighting();
if ( pLighting )
{
// Update 5x / second.
static DWORD lastTime = 0;
DWORD curTime = GetTickCount();
if ( curTime - lastTime < 200 )
{
curPercent = lastPercent; // no change
}
else
{
curPercent = (int)( pLighting->GetPercentComplete() * 10000.0f );
lastTime = curTime;
}
// Redraw the views when new lightmaps are ready.
if ( pLighting->CheckForNewLightmaps() )
{
SetForceRenderNextFrame();
pDoc->UpdateAllViews( MAPVIEW_UPDATE_ONLY_3D );
}
}
// Update the status text.
if ( curPercent == -10000 )
{
SetStatusText( SBI_LIGHTPROGRESS, "<->" );
}
else if( curPercent != lastPercent )
{
char str[256];
sprintf( str, "%.2f%%", curPercent / 100.0f );
SetStatusText( SBI_LIGHTPROGRESS, str );
}
lastPercent = curPercent;
}
//-----------------------------------------------------------------------------
// Purpose: Performs idle processing. Runs the frame and does MFC idle processing.
// Input : lCount - The number of times OnIdle has been called in succession,
// indicating the relative length of time the app has been idle without
// user input.
// Output : Returns TRUE if there is more idle processing to do, FALSE if not.
//-----------------------------------------------------------------------------
BOOL CHammer::OnIdle(LONG lCount)
{
UpdatePrefabs();
CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();
if (pDoc)
{
UpdateLighting(pDoc);
}
g_Textures.UpdateFileChangeWatchers();
UpdateStudioFileChangeWatcher();
return(CWinApp::OnIdle(lCount));
}
//-----------------------------------------------------------------------------
// Purpose: Renders the realtime views.
//-----------------------------------------------------------------------------
void CHammer::RunFrame(void)
{
// Note: since hammer may well not even have a 3D window visible
// at any given time, we have to call into the material system to
// make it deal with device lost. Usually this happens during SwapBuffers,
// but we may well not call SwapBuffers at any given moment.
materials->HandleDeviceLost();
if (!IsActiveApp())
{
Sleep(50);
}
#ifdef VPROF_HAMMER
g_VProfCurrentProfile.MarkFrame();
#endif
HammerVGui()->Simulate();
if ( CMapDoc::GetActiveMapDoc() && !IsClosing() || m_bForceRenderNextFrame )
HandleLightingPreview();
// never render without document or when closing down
// usually only render when active, but not compiling a map unless forced
if ( CMapDoc::GetActiveMapDoc() && !IsClosing() &&
( ( !IsRunningCommands() && IsActiveApp() ) || m_bForceRenderNextFrame ) &&
CMapDoc::GetActiveMapDoc()->HasInitialUpdate() )
{
CMapDoc *pMapDoc = CMapDoc::GetActiveMapDoc();
// get the time
pMapDoc->UpdateCurrentTime();
// run any animation
pMapDoc->UpdateAnimation();
// redraw the 3d views
pMapDoc->RenderAllViews();
// update the grid nav
CGridNav *pGridNav = pMapDoc->GetGridNav();
if ( pGridNav && pGridNav->IsEnabled() && pGridNav->IsPreviewActive() )
{
CMapView3D *pView = pMapDoc->GetFirst3DView();
if ( pView )
{
CCamera *pCam = pView->GetCamera();
Assert( pCam );
Vector vViewPos, vViewDir;
pCam->GetViewPoint( vViewPos );
pCam->GetViewForward( vViewDir );
pGridNav->Update( pMapDoc, vViewPos, vViewDir );
}
}
}
// No matter what, we want to keep caching in materials...
if ( IsActiveApp() )
{
g_Textures.LazyLoadTextures();
}
m_bForceRenderNextFrame = false;
}
//-----------------------------------------------------------------------------
// Purpose: Overloaded Run so that we can control the framerate for realtime
// rendering in the 3D view.
// Output : As MFC CWinApp::Run.
//-----------------------------------------------------------------------------
int CHammer::Run(void)
{
Assert(0);
return 0;
}
//-----------------------------------------------------------------------------
// Purpose: Returns true if the editor is the active app, false if not.
//-----------------------------------------------------------------------------
bool CHammer::IsActiveApp(void)
{
return m_bActiveApp;
}
//-----------------------------------------------------------------------------
// Purpose: Called from CMainFrame::OnSysCommand, this informs the app when it
// is minimized and unminimized. This allows us to stop rendering the
// 3D view when we are minimized.
// Input : bMinimized - TRUE when minmized, FALSE otherwise.
//-----------------------------------------------------------------------------
void CHammer::OnActivateApp(bool bActive)
{
// static int nCount = 0;
// if (bActive)
// DBG("ON %d\n", nCount);
// else
// DBG("OFF %d\n", nCount);
// nCount++;
m_bActiveApp = bActive;
}
//-----------------------------------------------------------------------------
// Purpose: Called from the shell to relinquish our video memory in favor of the
// engine. This is called by the engine when it starts up.
//-----------------------------------------------------------------------------
void CHammer::ReleaseVideoMemory()
{
POSITION pos = GetFirstDocTemplatePosition();
while (pos)
{
CDocTemplate* pTemplate = (CDocTemplate*)GetNextDocTemplate(pos);
POSITION pos2 = pTemplate->GetFirstDocPosition();
while (pos2)
{
CDocument * pDocument;
if ((pDocument=pTemplate->GetNextDoc(pos2)) != NULL)
{
static_cast<CMapDoc*>(pDocument)->ReleaseVideoMemory();
}
}
}
}
void CHammer::SuppressVideoAllocation( bool bSuppress )
{
m_SuppressVideoAllocation = bSuppress;
}
bool CHammer::CanAllocateVideo() const
{
return !m_SuppressVideoAllocation;
}
//-------------------------------------------------------------------------------
// Purpose: Runs through the autosave directory and fills the autosave map.
// Also sets the total amount of space used by the directory.
// Input : pFileMap - CUtlMap that will hold the list of files in the dir
// pdwTotalDirSize - pointer to the DWORD that will hold directory size
// pstrMapTitle - the name of the current map to be saved
// Output : returns an int containing the next number to use for the autosave
//-------------------------------------------------------------------------------
int CHammer::GetNextAutosaveNumber( CUtlMap<FILETIME, WIN32_FIND_DATA, int> *pFileMap, DWORD *pdwTotalDirSize, const CString *pstrMapTitle ) const
{
FILETIME oldestAutosaveTime;
oldestAutosaveTime.dwHighDateTime = 0;
oldestAutosaveTime.dwLowDateTime = 0;
char szRootDir[MAX_PATH];
APP()->GetDirectory(DIR_AUTOSAVE, szRootDir);
CString strAutosaveDirectory( szRootDir );
int nNumberActualAutosaves = 0;
int nCurrentAutosaveNumber = 1;
int nOldestAutosaveNumber = 1;
int nExpectedNextAutosaveNumber = 1;
int nLastHole = 0;
int nMaxAutosavesPerMap = Options.general.iMaxAutosavesPerMap;
WIN32_FIND_DATA fileData;
HANDLE hFile;
DWORD dwTotalAutosaveDirectorySize = 0;
hFile = FindFirstFile( strAutosaveDirectory + "*.vmf_autosave", &fileData );
if ( hFile != INVALID_HANDLE_VALUE )
{
//go through and for each file check to see if it is an autosave for this map; also keep track of total file size
//for directory.
while( GetLastError() != ERROR_NO_MORE_FILES && hFile != INVALID_HANDLE_VALUE )
{
(*pFileMap).Insert( fileData.ftLastAccessTime, fileData );
DWORD dwFileSize = fileData.nFileSizeLow;
dwTotalAutosaveDirectorySize += dwFileSize;
FILETIME fileAccessTime = fileData.ftLastAccessTime;
CString currentFilename( fileData.cFileName );
//every autosave file ends in three digits; this code separates the name from the digits
CString strMapName = currentFilename.Left( currentFilename.GetLength() - 17 );
CString strCurrentNumber = currentFilename.Mid( currentFilename.GetLength() - 16, 3 );
int nMapNumber = atoi( (char *)strCurrentNumber.GetBuffer() );
if ( strMapName.CompareNoCase( (*pstrMapTitle) ) == 0 )
{
//keep track of real number of autosaves with map name; deals with instance where older maps get deleted
//and create sequence holes in autosave map names.
nNumberActualAutosaves++;
if ( oldestAutosaveTime.dwLowDateTime == 0 )
{
//the first file is automatically the oldest
oldestAutosaveTime = fileAccessTime;
}
if ( nMapNumber != nExpectedNextAutosaveNumber )
{
//the current map number is different than what was expected
//there is a hole in the sequence
nLastHole = nMapNumber;
}
nExpectedNextAutosaveNumber = nMapNumber + 1;
if ( nExpectedNextAutosaveNumber > 999 )
{
nExpectedNextAutosaveNumber = 1;
}
if ( CompareFileTime( &fileAccessTime, &oldestAutosaveTime ) == -1 )
{
//this file is older than previous oldest file
oldestAutosaveTime = fileAccessTime;
nOldestAutosaveNumber = nMapNumber;
}
}
FindNextFile(hFile, &fileData);
}
FindClose(hFile);
}
if ( nNumberActualAutosaves < nMaxAutosavesPerMap )
{
//there are less autosaves than wanted for the map; use the larger
//of the next expected or the last found hole as the number.
nCurrentAutosaveNumber = max( nExpectedNextAutosaveNumber, nLastHole );
}
else
{
//there are no holes, use the oldest.
nCurrentAutosaveNumber = nOldestAutosaveNumber;
}
*pdwTotalDirSize = dwTotalAutosaveDirectorySize;
return nCurrentAutosaveNumber;
}
static bool LessFunc( const FILETIME &lhs, const FILETIME &rhs )
{
return CompareFileTime(&lhs, &rhs) < 0;
}
//-----------------------------------------------------------------------------
// Purpose: This is called when the autosave timer goes off. It checks to
// make sure the document has changed and handles deletion of old
// files when the total directory size is too big
//-----------------------------------------------------------------------------
void CHammer::Autosave( void )
{
if ( !Options.general.bEnableAutosave )
{
return;
}
if ( VerifyAutosaveDirectory() != true )
{
Options.general.bEnableAutosave = false;
return;
}
CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();
//value from options is in megs
DWORD dwMaxAutosaveSpace = Options.general.iMaxAutosaveSpace * 1024 * 1024;
CUtlMap<FILETIME, WIN32_FIND_DATA, int> autosaveFiles;
autosaveFiles.SetLessFunc( LessFunc );
if ( pDoc && pDoc->NeedsAutosave() )
{
char szRootDir[MAX_PATH];
APP()->GetDirectory(DIR_AUTOSAVE, szRootDir);
CString strAutosaveDirectory( szRootDir );
//expand the path if $SteamUserDir etc are used for SDK users
if ( CGameConfigManager::IsSDKDeployment() )
{
EditorUtil_ConvertPath(strAutosaveDirectory, true);
}
CString strExtension = ".vmf";
//this will hold the name of the map w/o leading directory info or file extension
CString strMapTitle;
//full path of map file
CString strMapFilename = pDoc->GetPathName();
DWORD dwTotalAutosaveDirectorySize = 0;
int nCurrentAutosaveNumber = 0;
// the map hasn't been saved before and doesn't have a filename; using default: 'autosave'
if ( strMapFilename.IsEmpty() )
{
strMapTitle = "autosave";
}
// the map already has a filename
else
{
int nFilenameBeginOffset = strMapFilename.ReverseFind( '\\' ) + 1;
int nFilenameEndOffset = strMapFilename.Find( '.' );
//get the filename of the map, between the leading '\' and the '.'
strMapTitle = strMapFilename.Mid( nFilenameBeginOffset, nFilenameEndOffset - nFilenameBeginOffset );
}
nCurrentAutosaveNumber = GetNextAutosaveNumber( &autosaveFiles, &dwTotalAutosaveDirectorySize, &strMapTitle );
//creating the proper suffix for the autosave file
char szNumberChars[4];
CString strAutosaveString = itoa( nCurrentAutosaveNumber, szNumberChars, 10 );
CString strAutosaveNumber = "000";
strAutosaveNumber += strAutosaveString;
strAutosaveNumber = strAutosaveNumber.Right( 3 );
strAutosaveNumber = "_" + strAutosaveNumber;
CString strSaveName = strAutosaveDirectory + strMapTitle + strAutosaveNumber + strExtension + "_autosave";
pDoc->SaveVMF( (char *)strSaveName.GetBuffer(), SAVEFLAGS_AUTOSAVE );
//don't autosave again unless they make changes
pDoc->SetAutosaveFlag( FALSE );
//if there is too much space used for autosaves, delete the oldest file until the size is acceptable
while( dwTotalAutosaveDirectorySize > dwMaxAutosaveSpace )
{
int nFirstElementIndex = autosaveFiles.FirstInorder();
if ( !autosaveFiles.IsValidIndex( nFirstElementIndex ) )
{
Assert( false );
break;
}
WIN32_FIND_DATA fileData = autosaveFiles.Element( nFirstElementIndex );
DWORD dwOldestFileSize = fileData.nFileSizeLow;
char filename[MAX_PATH];
strcpy( filename, fileData.cFileName );
DeleteFile( strAutosaveDirectory + filename );
dwTotalAutosaveDirectorySize -= dwOldestFileSize;
autosaveFiles.RemoveAt( nFirstElementIndex );
}
autosaveFiles.RemoveAll();
}
}
//-----------------------------------------------------------------------------
// Purpose: Verifies that the autosave directory exists and attempts to create it if
// it doesn't. Also returns various failure errors.
// This function is now called at two different times: immediately after a new
// directory is entered in the options screen and during every autosave call.
// If called with a directory, the input directory is checked for correctness.
// Otherwise, the system directory DIR_AUTOSAVE is checked
//-----------------------------------------------------------------------------
bool CHammer::VerifyAutosaveDirectory( char *szAutosaveDirectory ) const
{
HANDLE hDir;
HANDLE hTestFile;
char szRootDir[MAX_PATH];
if ( szAutosaveDirectory )
{
strcpy( szRootDir, szAutosaveDirectory );
EnsureTrailingBackslash( szRootDir );
}
else
{
APP()->GetDirectory(DIR_AUTOSAVE, szRootDir);
}
if ( szRootDir[0] == 0 )
{
AfxMessageBox( "No autosave directory has been selected.\nThe autosave feature will be disabled until a directory is entered.", MB_OK );
return false;
}
CString strAutosaveDirectory( szRootDir );
if ( CGameConfigManager::IsSDKDeployment() )
{
EditorUtil_ConvertPath(strAutosaveDirectory, true);
if ( ( strAutosaveDirectory[1] != ':' ) || ( strAutosaveDirectory[2] != '\\' ) )
{
AfxMessageBox( "The current autosave directory does not have an absolute path.\nThe autosave feature will be disabled until a new directory is entered.", MB_OK );
return false;
}
}
else
{
if ( ( szRootDir[1] != ':' ) || ( szRootDir[2] != '\\' ) )
{
AfxMessageBox( "The current autosave directory does not have an absolute path.\nThe autosave feature will be disabled until a new directory is entered.", MB_OK );
return false;
}
}
hDir = CreateFile (
strAutosaveDirectory,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL
);
if ( hDir == INVALID_HANDLE_VALUE )
{
bool bDirResult = CreateDirectory( strAutosaveDirectory, NULL ) ? true : false;
if ( !bDirResult )
{
AfxMessageBox( "The current autosave directory does not exist and could not be created. \nThe autosave feature will be disabled until a new directory is entered.", MB_OK );
return false;
}
}
else
{
CloseHandle( hDir );
hTestFile = CreateFile( strAutosaveDirectory + "test.txt",
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
NULL,
CREATE_NEW,
FILE_FLAG_BACKUP_SEMANTICS,
NULL
);
if ( hTestFile == INVALID_HANDLE_VALUE )
{
if ( GetLastError() == ERROR_ACCESS_DENIED )
{
AfxMessageBox( "The autosave directory is marked as read only. Please remove the read only attribute or select a new directory in Tools->Options->General.\nThe autosave feature will be disabled.", MB_OK );
return false;
}
else
{
AfxMessageBox( "There is a problem with the autosave directory. Please select a new directory in Tools->Options->General.\nThe autosave feature will be disabled.", MB_OK );
return false;
}
}
CloseHandle( hTestFile );
DeleteFile( strAutosaveDirectory + "test.txt" );
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Called when Hammer starts after a crash. It loads the newest
// autosave file after Hammer has initialized.
//-----------------------------------------------------------------------------
void CHammer::LoadLastGoodSave( void )
{
CString strLastGoodSave = APP()->GetProfileString("General", "Last Good Save", "");
char szMapDir[MAX_PATH];
strcpy(szMapDir, g_pGameConfig->szMapDir);
CDocument *pCurrentDoc;
if ( !strLastGoodSave.IsEmpty() )
{
pCurrentDoc = APP()->OpenDocumentFile( strLastGoodSave );
if ( !pCurrentDoc )
{
AfxMessageBox( "There was an error loading the last saved file.", MB_OK );
return;
}
char szAutoSaveDir[MAX_PATH];
APP()->GetDirectory(DIR_AUTOSAVE, szAutoSaveDir);
if ( !((CMapDoc *)pCurrentDoc)->IsAutosave() && Q_stristr( pCurrentDoc->GetPathName(), szAutoSaveDir ) )
{
//This handles the case where someone recovers from a crash and tries to load an autosave file that doesn't have the new autosave chunk in it
//It assumes the file should go into the gameConfig map directory
char szRenameMessage[MAX_PATH+MAX_PATH+256];
char szLastSaveCopy[MAX_PATH];
Q_strcpy( szLastSaveCopy, strLastGoodSave );
char *pszFileName = Q_strrchr( strLastGoodSave, '\\') + 1;
char *pszFileNameEnd = Q_strrchr( strLastGoodSave, '_');
if ( !pszFileNameEnd )
{
pszFileNameEnd = Q_strrchr( strLastGoodSave, '.');
}
strcpy( pszFileNameEnd, ".vmf" );
CString newMapPath( szMapDir );
newMapPath.Append( "\\" );
newMapPath.Append( pszFileName );
sprintf( szRenameMessage, "The last saved map was found in the autosave directory.\nWould you like to rename it from \"%s\" to \"%s\"?\nNOTE: This will not save the file with the new name; it will only rename it.", szLastSaveCopy, newMapPath );
if ( AfxMessageBox( szRenameMessage, MB_YESNO ) == IDYES )
{
pCurrentDoc->SetPathName( newMapPath );
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Called from the General Options dialog when the autosave timer or
// directory values have changed.
//-----------------------------------------------------------------------------
void CHammer::ResetAutosaveTimer()
{
Options.general.bEnableAutosave = true;
CMainFrame *pMainWnd = ::GetMainWnd();
if ( pMainWnd )
{
pMainWnd->ResetAutosaveTimer();
}
}
//-----------------------------------------------------------------------------
bool UTIL_IsDedicatedServer( void )
{
return false;
}