summaryrefslogtreecommitdiff
path: root/hammer/hammer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'hammer/hammer.cpp')
-rw-r--r--hammer/hammer.cpp2474
1 files changed, 2474 insertions, 0 deletions
diff --git a/hammer/hammer.cpp b/hammer/hammer.cpp
new file mode 100644
index 0000000..cc8ebc2
--- /dev/null
+++ b/hammer/hammer.cpp
@@ -0,0 +1,2474 @@
+//========= Copyright 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 "tier0/minidump.h"
+#include "materialsystem/imaterialsystemhardwareconfig.h"
+#include "istudiorender.h"
+#include "filesystem.h"
+#include "engine_launcher_api.h"
+#include "filesystem_init.h"
+#include "utlmap.h"
+#include "progdlg.h"
+#include "MapWorld.h"
+#include "HammerVGui.h"
+#include "vgui_controls/Controls.h"
+#include "lpreview_thread.h"
+#include "inputsystem/iinputsystem.h"
+#include "datacache/idatacache.h"
+#include "steam/steam_api.h"
+#include "p4lib/ip4.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;
+}
+
+struct MinidumpWrapperHelper_t
+{
+ int (*m_pfn)(void *pParam);
+ void *m_pParam;
+ int m_iRetVal;
+};
+
+static void MinidumpWrapperHelper( void *arg )
+{
+ MinidumpWrapperHelper_t *info = (MinidumpWrapperHelper_t *)arg;
+ info->m_iRetVal = info->m_pfn( info->m_pParam );
+}
+
+static int WrapFunctionWithMinidumpHandler( int (*pfn)(void *pParam), void *pParam )
+{
+ int nRetVal;
+
+ if ( !Plat_IsInDebugSession() && !CommandLine()->FindParm( "-nominidumps") )
+ {
+ MinidumpWrapperHelper_t info;
+ info.m_pfn = pfn;
+ info.m_pParam = pParam;
+ info.m_iRetVal = 0;
+ CatchAndWriteMiniDumpForVoidPtrFn( MinidumpWrapperHelper, &info, true );
+ nRetVal = info.m_iRetVal;
+ }
+ else
+ {
+ nRetVal = pfn( pParam );
+ }
+
+ return nRetVal;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Outputs a formatted debug string.
+// Input : fmt - format specifier.
+// ... - arguments to format.
+//-----------------------------------------------------------------------------
+void DBG(const 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);
+}
+
+
+//-----------------------------------------------------------------------------
+// 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 );
+ }
+ }
+}
+
+
+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;
+}
+
+
+//-----------------------------------------------------------------------------
+// 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;
+
+ // 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();
+ SteamAPI_SetTryCatchCallbacks( false ); // We don't use exceptions, so tell steam not to use try/catch in callback handlers
+ 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();
+ 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;
+}
+
+
+//-----------------------------------------------------------------------------
+// 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);
+}
+
+bool CHammer::HammerIsIdleMessage(MSG * pMsg)
+{
+ AFX_MANAGE_STATE(AfxGetStaticModuleState());
+ return IsIdleMessage(pMsg) != FALSE;
+}
+
+// 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, m_szAppDir);
+ EnsureTrailingBackslash(p);
+ strcat(p, "Prefabs");
+
+ //
+ // Make sure the prefabs directory exists.
+ //
+ 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 SpewRetval_t HammerDbgOutput( SpewType_t spewType, char const *pMsg )
+{
+ // FIXME: The messages we're getting from the material system
+ // are ones that we really don't care much about.
+ // I'm disabling this for now, we need to decide about what to do with this
+
+ switch( spewType )
+ {
+ case SPEW_ERROR:
+ MessageBox( NULL, (LPCTSTR)pMsg, "Fatal Error", MB_OK | MB_ICONINFORMATION );
+#ifdef _DEBUG
+ return SPEW_DEBUGGER;
+#else
+ TerminateProcess( GetCurrentProcess(), 1 );
+ return SPEW_ABORT;
+#endif
+
+ default:
+ OutputDebugString( pMsg );
+ return (spewType == SPEW_ASSERT) ? SPEW_DEBUGGER : SPEW_CONTINUE;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// 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 );
+}
+
+
+int CHammer::StaticHammerInternalInit( void *pParam )
+{
+ return (int)((CHammer*)pParam)->HammerInternalInit();
+}
+
+
+InitReturnVal_t CHammer::HammerInternalInit()
+{
+ SpewOutputFunc( HammerDbgOutput );
+ 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);
+
+ LoadSequences(); // load cmd sequences - different from options because
+ // users might want to share (darn registry)
+
+ // 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;
+
+ CSplashWnd::ShowSplashScreen(pMainFrame);
+
+
+ // try to init VGUI
+ HammerVGui()->Init( m_pMainWnd->GetSafeHwnd() );
+
+ // The main window has been initialized, so show and update it.
+ //
+ m_nCmdShow = SW_SHOWMAXIMIZED;
+ pMainFrame->ShowWindow(m_nCmdShow);
+ pMainFrame->UpdateWindow();
+
+ // Now that we've initialized the file system, we can parse this config's gameinfo.txt for the additional settings there.
+ g_pGameConfig->ParseGameInfo();
+
+ 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);
+ }
+
+ // 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 ( 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
+
+ CSplashWnd::HideSplashScreen();
+
+ // create the lighting preview thread
+ g_LPreviewThread = CreateSimpleThread( LightingPreviewThreadFN, 0 );
+
+ return INIT_OK;
+}
+
+int CHammer::MainLoop()
+{
+ return WrapFunctionWithMinidumpHandler( StaticInternalMainLoop, this );
+}
+
+
+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();
+
+ if ( bIdle && !HammerOnIdle(lIdleCount++) )
+ {
+ bIdle = false;
+ }
+
+ //
+ // Pump messages until the message queue is empty.
+ //
+ while (::PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
+ {
+ if ( msg.message == WM_QUIT )
+ return 1;
+
+ 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;
+ }
+
+#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 ( GetSpewOutputFunc() == HammerDbgOutput )
+ {
+ SpewOutputFunc( NULL );
+ }
+
+ 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;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// 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)
+{
+ static char szInitialDir[MAX_PATH] = "";
+ if (szInitialDir[0] == '\0')
+ {
+ strcpy(szInitialDir, g_pGameConfig->szMapDir);
+ }
+
+ // TODO: need to prevent (or handle) opening VMF files when using old map file formats
+ CFileDialog dlg(TRUE, NULL, NULL, OFN_LONGNAMES | OFN_HIDEREADONLY | OFN_NOCHANGEDIR, "Valve Map Files (*.vmf;*.vmm)|*.vmf;*.vmm|Valve Map Files Autosave (*.vmf_autosave)|*.vmf_autosave|Worldcraft RMFs (*.rmf)|*.rmf|Worldcraft Maps (*.map)|*.map||");
+ dlg.m_ofn.lpstrInitialDir = szInitialDir;
+ int iRvl = dlg.DoModal();
+
+ if (iRvl == IDCANCEL)
+ {
+ return;
+ }
+
+ //
+ // Get the directory they browsed to for next time.
+ //
+ CString str = dlg.GetPathName();
+ int nSlash = str.ReverseFind('\\');
+ if (nSlash != -1)
+ {
+ strcpy(szInitialDir, str.Left(nSlash));
+ }
+
+ if (str.Find('.') == -1)
+ {
+ switch (dlg.m_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);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : lpszFileName -
+// Output : CDocument*
+//-----------------------------------------------------------------------------
+CDocument* CHammer::OpenDocumentFile(LPCTSTR lpszFileName)
+{
+ if(GetFileAttributes(lpszFileName) == 0xFFFFFFFF)
+ {
+ CString Message;
+
+ Message = "The file " + CString( lpszFileName ) + " does not exist.";
+ AfxMessageBox( Message );
+
+ return NULL;
+ }
+
+ CDocument *pDoc = m_pDocManager->OpenDocumentFile( lpszFileName );
+ CMapDoc *pMapDoc = dynamic_cast< CMapDoc * >( pDoc );
+
+ if ( pMapDoc )
+ {
+ CMapDoc::SetActiveMapDoc( pMapDoc );
+
+ }
+ 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.", lpszFileName, (const char*)newMapPath );
+
+ if ( AfxMessageBox( szRenameMessage, MB_YESNO ) == IDYES )
+ {
+ ((CMapDoc *)pDoc)->SetPathName( newMapPath );
+ }
+ }
+
+ 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;
+
+ // This is for raw input, these shouldn't be translated so skip that here.
+ if ( pMsg->message == WM_INPUT )
+ 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 AfxWnd100sd in Debug and AfxWnd100s in Release
+ if ( !V_stricmp( className, "edit" ) || V_stristr( className, "AfxWnd" ) )
+ {
+ // Typing in an edit control. Don't pretranslate, just translate/dispatch.
+ return FALSE;
+ }
+ }
+
+ return CWinApp::PreTranslateMessage(pMsg);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CHammer::LoadSequences(void)
+{
+ char szRootDir[MAX_PATH];
+ char szFullPath[MAX_PATH];
+ APP()->GetDirectory(DIR_PROGRAM, szRootDir);
+ Q_MakeAbsolutePath( szFullPath, MAX_PATH, "CmdSeq.wc", szRootDir );
+ std::ifstream file(szFullPath, std::ios::in | std::ios::binary);
+
+ if(!file.is_open())
+ return; // 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);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// 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() )
+
+ {
+ // get the time
+ CMapDoc::GetActiveMapDoc()->UpdateCurrentTime();
+
+ // run any animation
+ CMapDoc::GetActiveMapDoc()->UpdateAnimation();
+
+ // redraw the 3d views
+ CMapDoc::GetActiveMapDoc()->RenderAllViews();
+ }
+
+ // 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 frameratefor 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
+ 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 );
+ {
+ 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;
+ }
+ }
+
+ 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 );
+ 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, (const char*)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();
+ }
+}