//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: The application object. // //===========================================================================// #include "stdafx.h" #include #include #include #pragma warning(push, 1) #pragma warning(disable:4701 4702 4530) #include #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 // // 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 g_HammerToLPreviewMsgQueue; CMessageQueue 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 //----------------------------------------------------------------------------- // 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(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 *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 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(); } }