diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /hammer/hammer.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'hammer/hammer.cpp')
| -rw-r--r-- | hammer/hammer.cpp | 2474 |
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(); + } +} |