Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2474 lines
67 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: The application object.
  4. //
  5. //===========================================================================//
  6. #include "stdafx.h"
  7. #include <io.h>
  8. #include <stdlib.h>
  9. #include <direct.h>
  10. #pragma warning(push, 1)
  11. #pragma warning(disable:4701 4702 4530)
  12. #include <fstream>
  13. #pragma warning(pop)
  14. #include "BuildNum.h"
  15. #include "EditGameConfigs.h"
  16. #include "Splash.h"
  17. #include "Options.h"
  18. #include "custommessages.h"
  19. #include "MainFrm.h"
  20. #include "MessageWnd.h"
  21. #include "ChildFrm.h"
  22. #include "MapDoc.h"
  23. #include "Manifest.h"
  24. #include "MapView3D.h"
  25. #include "MapView2D.h"
  26. #include "PakDoc.h"
  27. #include "PakViewDirec.h"
  28. #include "PakFrame.h"
  29. #include "Prefabs.h"
  30. #include "GlobalFunctions.h"
  31. #include "Shell.h"
  32. #include "ShellMessageWnd.h"
  33. #include "Options.h"
  34. #include "TextureSystem.h"
  35. #include "ToolManager.h"
  36. #include "Hammer.h"
  37. #include "StudioModel.h"
  38. #include "ibsplighting.h"
  39. #include "statusbarids.h"
  40. #include "tier0/icommandline.h"
  41. #include "soundsystem.h"
  42. #include "IHammer.h"
  43. #include "op_entity.h"
  44. #include "tier0/dbg.h"
  45. #include "tier0/minidump.h"
  46. #include "materialsystem/imaterialsystemhardwareconfig.h"
  47. #include "istudiorender.h"
  48. #include "filesystem.h"
  49. #include "engine_launcher_api.h"
  50. #include "filesystem_init.h"
  51. #include "utlmap.h"
  52. #include "progdlg.h"
  53. #include "MapWorld.h"
  54. #include "HammerVGui.h"
  55. #include "vgui_controls/Controls.h"
  56. #include "lpreview_thread.h"
  57. #include "inputsystem/iinputsystem.h"
  58. #include "datacache/idatacache.h"
  59. #include "steam/steam_api.h"
  60. #include "p4lib/ip4.h"
  61. // memdbgon must be the last include file in a .cpp file!!!
  62. #include <tier0/memdbgon.h>
  63. //
  64. // Note!
  65. //
  66. // If this DLL is dynamically linked against the MFC
  67. // DLLs, any functions exported from this DLL which
  68. // call into MFC must have the AFX_MANAGE_STATE macro
  69. // added at the very beginning of the function.
  70. //
  71. // For example:
  72. //
  73. // extern "C" BOOL PASCAL EXPORT ExportedFunction()
  74. // {
  75. // AFX_MANAGE_STATE(AfxGetStaticModuleState());
  76. // // normal function body here
  77. // }
  78. //
  79. // It is very important that this macro appear in each
  80. // function, prior to any calls into MFC. This means that
  81. // it must appear as the first statement within the
  82. // function, even before any object variable declarations
  83. // as their constructors may generate calls into the MFC
  84. // DLL.
  85. //
  86. // Please see MFC Technical Notes 33 and 58 for additional
  87. // details.
  88. //
  89. // dvs: hack
  90. extern LPCTSTR GetErrorString(void);
  91. extern void MakePrefabLibrary(LPCTSTR pszName);
  92. void EditorUtil_ConvertPath(CString &str, bool bSave);
  93. static bool bMakeLib = false;
  94. static float fSequenceVersion = 0.2f;
  95. static char *pszSequenceHdr = "Worldcraft Command Sequences\r\n\x1a";
  96. CHammer theApp;
  97. COptions Options;
  98. CShell g_Shell;
  99. CShellMessageWnd g_ShellMessageWnd;
  100. CMessageWnd *g_pwndMessage = NULL;
  101. // IPC structures for lighting preview thread
  102. CMessageQueue<MessageToLPreview> g_HammerToLPreviewMsgQueue;
  103. CMessageQueue<MessageFromLPreview> g_LPreviewToHammerMsgQueue;
  104. ThreadHandle_t g_LPreviewThread;
  105. CSteamAPIContext g_SteamAPIContext;
  106. CSteamAPIContext *steamapicontext = &g_SteamAPIContext;
  107. bool CHammer::m_bIsNewDocumentVisible = true;
  108. //-----------------------------------------------------------------------------
  109. // Expose singleton
  110. //-----------------------------------------------------------------------------
  111. EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CHammer, IHammer, INTERFACEVERSION_HAMMER, theApp);
  112. //-----------------------------------------------------------------------------
  113. // global interfaces
  114. //-----------------------------------------------------------------------------
  115. IBaseFileSystem *g_pFileSystem;
  116. IEngineAPI *g_pEngineAPI;
  117. CreateInterfaceFn g_Factory;
  118. bool g_bHDR = true;
  119. bool IsRunningInEngine()
  120. {
  121. return g_pEngineAPI != NULL;
  122. }
  123. struct MinidumpWrapperHelper_t
  124. {
  125. int (*m_pfn)(void *pParam);
  126. void *m_pParam;
  127. int m_iRetVal;
  128. };
  129. static void MinidumpWrapperHelper( void *arg )
  130. {
  131. MinidumpWrapperHelper_t *info = (MinidumpWrapperHelper_t *)arg;
  132. info->m_iRetVal = info->m_pfn( info->m_pParam );
  133. }
  134. static int WrapFunctionWithMinidumpHandler( int (*pfn)(void *pParam), void *pParam )
  135. {
  136. int nRetVal;
  137. if ( !Plat_IsInDebugSession() && !CommandLine()->FindParm( "-nominidumps") )
  138. {
  139. MinidumpWrapperHelper_t info;
  140. info.m_pfn = pfn;
  141. info.m_pParam = pParam;
  142. info.m_iRetVal = 0;
  143. CatchAndWriteMiniDumpForVoidPtrFn( MinidumpWrapperHelper, &info, true );
  144. nRetVal = info.m_iRetVal;
  145. }
  146. else
  147. {
  148. nRetVal = pfn( pParam );
  149. }
  150. return nRetVal;
  151. }
  152. //-----------------------------------------------------------------------------
  153. // Purpose: Outputs a formatted debug string.
  154. // Input : fmt - format specifier.
  155. // ... - arguments to format.
  156. //-----------------------------------------------------------------------------
  157. void DBG(const char *fmt, ...)
  158. {
  159. char ach[128];
  160. va_list va;
  161. va_start(va, fmt);
  162. vsprintf(ach, fmt, va);
  163. va_end(va);
  164. OutputDebugString(ach);
  165. }
  166. void Msg(int type, const char *fmt, ...)
  167. {
  168. if ( !g_pwndMessage )
  169. return;
  170. va_list vl;
  171. char szBuf[512];
  172. va_start(vl, fmt);
  173. int len = _vsnprintf(szBuf, 512, fmt, vl);
  174. va_end(vl);
  175. if ((type == mwError) || (type == mwWarning))
  176. {
  177. g_pwndMessage->ShowMessageWindow();
  178. }
  179. char temp = 0;
  180. char *pMsg = szBuf;
  181. do
  182. {
  183. if (len >= MESSAGE_WND_MESSAGE_LENGTH)
  184. {
  185. temp = pMsg[MESSAGE_WND_MESSAGE_LENGTH-1];
  186. pMsg[MESSAGE_WND_MESSAGE_LENGTH-1] = '\0';
  187. }
  188. g_pwndMessage->AddMsg((MWMSGTYPE)type, pMsg);
  189. if (len >= MESSAGE_WND_MESSAGE_LENGTH)
  190. {
  191. pMsg[MESSAGE_WND_MESSAGE_LENGTH-1] = temp;
  192. pMsg += MESSAGE_WND_MESSAGE_LENGTH-1;
  193. }
  194. len -= MESSAGE_WND_MESSAGE_LENGTH-1;
  195. } while (len > 0);
  196. }
  197. //-----------------------------------------------------------------------------
  198. // Purpose: this routine calls the default doc template's OpenDocumentFile() but
  199. // with the ability to override the visible flag
  200. // Input : lpszPathName - the document to open
  201. // bMakeVisible - ignored
  202. // Output : returns the opened document if successful
  203. //-----------------------------------------------------------------------------
  204. CDocument *CHammerDocTemplate::OpenDocumentFile( LPCTSTR lpszPathName, BOOL bMakeVisible )
  205. {
  206. CDocument *pDoc = __super::OpenDocumentFile( lpszPathName, CHammer::IsNewDocumentVisible() );
  207. return pDoc;
  208. }
  209. //-----------------------------------------------------------------------------
  210. // Purpose: this function will attempt an orderly shutdown of all maps. It will attempt to
  211. // close only documents that have no references, hopefully freeing up additional documents
  212. // Input : bEndSession - ignored
  213. //-----------------------------------------------------------------------------
  214. void CHammerDocTemplate::CloseAllDocuments( BOOL bEndSession )
  215. {
  216. bool bFound = true;
  217. // rough loop to always remove the first map doc that has no references, then start over, try again.
  218. // if we still have maps with references ( that's bad ), we'll exit out of this loop and just do
  219. // the default shutdown to force them all to close.
  220. while( bFound )
  221. {
  222. bFound = false;
  223. POSITION pos = GetFirstDocPosition();
  224. while( pos != NULL )
  225. {
  226. CDocument *pDoc = GetNextDoc( pos );
  227. CMapDoc *pMapDoc = dynamic_cast< CMapDoc * >( pDoc );
  228. if ( pMapDoc && pMapDoc->GetReferenceCount() == 0 )
  229. {
  230. pDoc->OnCloseDocument();
  231. bFound = true;
  232. break;
  233. }
  234. }
  235. }
  236. #if 0
  237. POSITION pos = GetFirstDocPosition();
  238. while( pos != NULL )
  239. {
  240. CDocument *pDoc = GetNextDoc( pos );
  241. CMapDoc *pMapDoc = dynamic_cast< CMapDoc * >( pDoc );
  242. if ( pMapDoc )
  243. {
  244. pMapDoc->ForceNoReference();
  245. }
  246. }
  247. __super::CloseAllDocuments( bEndSession );
  248. #endif
  249. }
  250. //-----------------------------------------------------------------------------
  251. // Purpose: This function will allow hammer to control the initial visibility of an opening document
  252. // Input : pFrame - the new document's frame
  253. // pDoc - the new document
  254. // bMakeVisible - ignored as a parameter
  255. //-----------------------------------------------------------------------------
  256. void CHammerDocTemplate::InitialUpdateFrame( CFrameWnd* pFrame, CDocument* pDoc, BOOL bMakeVisible )
  257. {
  258. bMakeVisible = CHammer::IsNewDocumentVisible();
  259. __super::InitialUpdateFrame( pFrame, pDoc, bMakeVisible );
  260. if ( bMakeVisible )
  261. {
  262. CMapDoc *pMapDoc = dynamic_cast< CMapDoc * >( pDoc );
  263. if ( pMapDoc )
  264. {
  265. pMapDoc->SetInitialUpdate();
  266. }
  267. }
  268. }
  269. //-----------------------------------------------------------------------------
  270. // Purpose: this function will let all other maps know that an instanced map has been updated ( usually for volume size )
  271. // Input : pInstanceMapDoc - the document that has been updated
  272. //-----------------------------------------------------------------------------
  273. void CHammerDocTemplate::UpdateInstanceMap( CMapDoc *pInstanceMapDoc )
  274. {
  275. POSITION pos = GetFirstDocPosition();
  276. while( pos != NULL )
  277. {
  278. CDocument *pDoc = GetNextDoc( pos );
  279. CMapDoc *pMapDoc = dynamic_cast< CMapDoc * >( pDoc );
  280. if ( pMapDoc && pMapDoc != pInstanceMapDoc )
  281. {
  282. pMapDoc->UpdateInstanceMap( pInstanceMapDoc );
  283. }
  284. }
  285. }
  286. class CHammerCmdLine : public CCommandLineInfo
  287. {
  288. public:
  289. CHammerCmdLine(void)
  290. {
  291. m_bShowLogo = true;
  292. m_bGame = false;
  293. m_bConfigDir = false;
  294. }
  295. void ParseParam(LPCTSTR lpszParam, BOOL bFlag, BOOL bLast)
  296. {
  297. if ((!m_bGame) && (bFlag && !stricmp(lpszParam, "game")))
  298. {
  299. m_bGame = true;
  300. }
  301. else if (m_bGame)
  302. {
  303. if (!bFlag)
  304. {
  305. m_strGame = lpszParam;
  306. }
  307. m_bGame = false;
  308. }
  309. else if (bFlag && !strcmpi(lpszParam, "nologo"))
  310. {
  311. m_bShowLogo = false;
  312. }
  313. else if (bFlag && !strcmpi(lpszParam, "makelib"))
  314. {
  315. bMakeLib = TRUE;
  316. }
  317. else if (!bFlag && bMakeLib)
  318. {
  319. MakePrefabLibrary(lpszParam);
  320. }
  321. else if ((!m_bConfigDir) && (bFlag && !stricmp(lpszParam, "configdir")))
  322. {
  323. m_bConfigDir = true;
  324. }
  325. else if (m_bConfigDir)
  326. {
  327. if ( !bFlag )
  328. {
  329. Options.configs.m_strConfigDir = lpszParam;
  330. }
  331. m_bConfigDir = false;
  332. }
  333. else
  334. {
  335. CCommandLineInfo::ParseParam(lpszParam, bFlag, bLast);
  336. }
  337. }
  338. bool m_bShowLogo;
  339. bool m_bGame; // Used to find and parse the "-game blah" parameter pair.
  340. bool m_bConfigDir; // Used to find and parse the "-configdir blah" parameter pair.
  341. 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.
  342. };
  343. BEGIN_MESSAGE_MAP(CHammer, CWinApp)
  344. //{{AFX_MSG_MAP(CHammer)
  345. ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
  346. ON_COMMAND(ID_FILE_OPEN, OnFileOpen)
  347. ON_COMMAND(ID_FILE_NEW, OnFileNew)
  348. ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)
  349. ON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)
  350. //}}AFX_MSG_MAP
  351. END_MESSAGE_MAP()
  352. //-----------------------------------------------------------------------------
  353. // Purpose: Constructor. Initializes member variables and creates a scratch
  354. // buffer for use when loading WAD files.
  355. //-----------------------------------------------------------------------------
  356. CHammer::CHammer(void)
  357. {
  358. m_bActiveApp = true;
  359. m_SuppressVideoAllocation = false;
  360. m_bForceRenderNextFrame = false;
  361. m_bClosing = false;
  362. }
  363. //-----------------------------------------------------------------------------
  364. // Purpose: Destructor. Frees scratch buffer used when loading WAD files.
  365. // Deletes all command sequences used when compiling maps.
  366. //-----------------------------------------------------------------------------
  367. CHammer::~CHammer(void)
  368. {
  369. }
  370. //-----------------------------------------------------------------------------
  371. // Inherited from IAppSystem
  372. //-----------------------------------------------------------------------------
  373. bool CHammer::Connect( CreateInterfaceFn factory )
  374. {
  375. if ( !BaseClass::Connect( factory ) )
  376. return false;
  377. // bool bCVarOk = ConnectStudioRenderCVars( factory );
  378. g_pFileSystem = ( IBaseFileSystem * )factory( BASEFILESYSTEM_INTERFACE_VERSION, NULL );
  379. g_pStudioRender = ( IStudioRender * )factory( STUDIO_RENDER_INTERFACE_VERSION, NULL );
  380. g_pEngineAPI = ( IEngineAPI * )factory( VENGINE_LAUNCHER_API_VERSION, NULL );
  381. g_pMDLCache = (IMDLCache*)factory( MDLCACHE_INTERFACE_VERSION, NULL );
  382. p4 = ( IP4 * )factory( P4_INTERFACE_VERSION, NULL );
  383. g_Factory = factory;
  384. if ( !g_pMDLCache || !g_pFileSystem || !g_pFullFileSystem || !materials || !g_pMaterialSystemHardwareConfig || !g_pStudioRender )
  385. return false;
  386. // ensure we're in the same directory as the .EXE
  387. char *p;
  388. GetModuleFileName(NULL, m_szAppDir, MAX_PATH);
  389. p = strrchr(m_szAppDir, '\\');
  390. if(p)
  391. {
  392. // chop off \wc.exe
  393. p[0] = 0;
  394. }
  395. if ( IsRunningInEngine() )
  396. {
  397. strcat( m_szAppDir, "\\bin" );
  398. }
  399. // Create the message window object for capturing errors and warnings.
  400. // This does NOT create the window itself. That happens later in CMainFrame::Create.
  401. g_pwndMessage = CMessageWnd::CreateMessageWndObject();
  402. // Default location for GameConfig.txt is the same directory as Hammer.exe but this may be overridden on the command line
  403. char szGameConfigDir[MAX_PATH];
  404. APP()->GetDirectory( DIR_PROGRAM, szGameConfigDir );
  405. Options.configs.m_strConfigDir = szGameConfigDir;
  406. CHammerCmdLine cmdInfo;
  407. ParseCommandLine(cmdInfo);
  408. // Set up SteamApp() interface (for checking app ownership)
  409. SteamAPI_InitSafe();
  410. SteamAPI_SetTryCatchCallbacks( false ); // We don't use exceptions, so tell steam not to use try/catch in callback handlers
  411. g_SteamAPIContext.Init();
  412. // Load the options
  413. // NOTE: Have to do this now, because we need it before Inits() are called
  414. // NOTE: SetRegistryKey will cause hammer to look into the registry for its values
  415. SetRegistryKey("Valve");
  416. Options.Init();
  417. return true;
  418. }
  419. void CHammer::Disconnect()
  420. {
  421. g_pStudioRender = NULL;
  422. g_pFileSystem = NULL;
  423. g_pEngineAPI = NULL;
  424. g_pMDLCache = NULL;
  425. BaseClass::Disconnect();
  426. }
  427. void *CHammer::QueryInterface( const char *pInterfaceName )
  428. {
  429. // We also implement the IMatSystemSurface interface
  430. if (!Q_strncmp( pInterfaceName, INTERFACEVERSION_HAMMER, Q_strlen(INTERFACEVERSION_HAMMER) + 1))
  431. return (IHammer*)this;
  432. return NULL;
  433. }
  434. //-----------------------------------------------------------------------------
  435. // Methods related to message pumping
  436. //-----------------------------------------------------------------------------
  437. bool CHammer::HammerPreTranslateMessage(MSG * pMsg)
  438. {
  439. AFX_MANAGE_STATE(AfxGetStaticModuleState());
  440. // Copy this into the current message, needed for MFC
  441. #if _MSC_VER >= 1300
  442. _AFX_THREAD_STATE* pState = AfxGetThreadState();
  443. pState->m_msgCur = *pMsg;
  444. #else
  445. m_msgCur = *pMsg;
  446. #endif
  447. return (/*pMsg->message == WM_KICKIDLE ||*/ PreTranslateMessage(pMsg) != FALSE);
  448. }
  449. bool CHammer::HammerIsIdleMessage(MSG * pMsg)
  450. {
  451. AFX_MANAGE_STATE(AfxGetStaticModuleState());
  452. return IsIdleMessage(pMsg) != FALSE;
  453. }
  454. // return TRUE if more idle processing
  455. bool CHammer::HammerOnIdle(long count)
  456. {
  457. AFX_MANAGE_STATE(AfxGetStaticModuleState());
  458. return OnIdle(count) != FALSE;
  459. }
  460. //-----------------------------------------------------------------------------
  461. // Purpose: Adds a backslash to the end of a string if there isn't one already.
  462. // Input : psz - String to add the backslash to.
  463. //-----------------------------------------------------------------------------
  464. static void EnsureTrailingBackslash(char *psz)
  465. {
  466. if ((psz[0] != '\0') && (psz[strlen(psz) - 1] != '\\'))
  467. {
  468. strcat(psz, "\\");
  469. }
  470. }
  471. //-----------------------------------------------------------------------------
  472. // Purpose: Tweaks our data members to enable us to import old Hammer settings
  473. // from the registry.
  474. //-----------------------------------------------------------------------------
  475. static const char *s_pszOldAppName = NULL;
  476. void CHammer::BeginImportWCSettings(void)
  477. {
  478. s_pszOldAppName = m_pszAppName;
  479. m_pszAppName = "Worldcraft";
  480. SetRegistryKey("Valve");
  481. }
  482. //-----------------------------------------------------------------------------
  483. // Purpose: Tweaks our data members to enable us to import old Valve Hammer Editor
  484. // settings from the registry.
  485. //-----------------------------------------------------------------------------
  486. void CHammer::BeginImportVHESettings(void)
  487. {
  488. s_pszOldAppName = m_pszAppName;
  489. m_pszAppName = "Valve Hammer Editor";
  490. SetRegistryKey("Valve");
  491. }
  492. //-----------------------------------------------------------------------------
  493. // Purpose: Restores our tweaked data members to their original state.
  494. //-----------------------------------------------------------------------------
  495. void CHammer::EndImportSettings(void)
  496. {
  497. m_pszAppName = s_pszOldAppName;
  498. SetRegistryKey("Valve");
  499. }
  500. //-----------------------------------------------------------------------------
  501. // Purpose: Retrieves various important directories.
  502. // Input : dir - Enumerated directory to retrieve.
  503. // p - Pointer to buffer that receives the full path to the directory.
  504. //-----------------------------------------------------------------------------
  505. void CHammer::GetDirectory(DirIndex_t dir, char *p)
  506. {
  507. switch (dir)
  508. {
  509. case DIR_PROGRAM:
  510. {
  511. strcpy(p, m_szAppDir);
  512. EnsureTrailingBackslash(p);
  513. break;
  514. }
  515. case DIR_PREFABS:
  516. {
  517. strcpy(p, m_szAppDir);
  518. EnsureTrailingBackslash(p);
  519. strcat(p, "Prefabs");
  520. //
  521. // Make sure the prefabs directory exists.
  522. //
  523. if ((_access( p, 0 )) == -1)
  524. {
  525. CreateDirectory(p, NULL);
  526. }
  527. break;
  528. }
  529. //
  530. // Get the game directory with a trailing backslash. This is
  531. // where the base game's resources are, such as "C:\Half-Life\valve\".
  532. //
  533. case DIR_GAME_EXE:
  534. {
  535. strcpy(p, g_pGameConfig->m_szGameExeDir);
  536. EnsureTrailingBackslash(p);
  537. break;
  538. }
  539. //
  540. // Get the mod directory with a trailing backslash. This is where
  541. // the mod's resources are, such as "C:\Half-Life\tfc\".
  542. //
  543. case DIR_MOD:
  544. {
  545. strcpy(p, g_pGameConfig->m_szModDir);
  546. EnsureTrailingBackslash(p);
  547. break;
  548. }
  549. //
  550. // Get the materials directory with a trailing backslash. This is where
  551. // the mod's materials are, such as "C:\Half-Life\tfc\materials".
  552. //
  553. case DIR_MATERIALS:
  554. {
  555. strcpy(p, g_pGameConfig->m_szModDir);
  556. EnsureTrailingBackslash(p);
  557. Q_strcat(p, "materials\\", MAX_PATH);
  558. break;
  559. }
  560. case DIR_AUTOSAVE:
  561. {
  562. strcpy( p, m_szAutosaveDir );
  563. EnsureTrailingBackslash(p);
  564. break;
  565. }
  566. }
  567. }
  568. void CHammer::SetDirectory(DirIndex_t dir, const char *p)
  569. {
  570. switch(dir)
  571. {
  572. case DIR_AUTOSAVE:
  573. {
  574. strcpy( m_szAutosaveDir, p );
  575. break;
  576. }
  577. }
  578. }
  579. //-----------------------------------------------------------------------------
  580. // Purpose: Returns a color from the application configuration storage.
  581. //-----------------------------------------------------------------------------
  582. COLORREF CHammer::GetProfileColor(const char *pszSection, const char *pszKey, int r, int g, int b)
  583. {
  584. int newR, newG, newB;
  585. CString strDefault;
  586. CString strReturn;
  587. char szBuff[128];
  588. sprintf(szBuff, "%i %i %i", r, g, b);
  589. strDefault = szBuff;
  590. strReturn = GetProfileString(pszSection, pszKey, strDefault);
  591. if (strReturn.IsEmpty())
  592. return 0;
  593. // Parse out colors.
  594. char *pStart;
  595. char *pCurrent;
  596. pStart = szBuff;
  597. pCurrent = pStart;
  598. strcpy( szBuff, (char *)(LPCSTR)strReturn );
  599. while (*pCurrent && *pCurrent != ' ')
  600. pCurrent++;
  601. *pCurrent++ = 0;
  602. newR = atoi(pStart);
  603. pStart = pCurrent;
  604. while (*pCurrent && *pCurrent != ' ')
  605. pCurrent++;
  606. *pCurrent++ = 0;
  607. newG = atoi(pStart);
  608. pStart = pCurrent;
  609. while (*pCurrent)
  610. pCurrent++;
  611. *pCurrent++ = 0;
  612. newB = atoi(pStart);
  613. return COLORREF(RGB(newR, newG, newB));
  614. }
  615. //-----------------------------------------------------------------------------
  616. // Purpose:
  617. // Input : *pszURL -
  618. //-----------------------------------------------------------------------------
  619. void CHammer::OpenURL(const char *pszURL, HWND hwnd)
  620. {
  621. if (HINSTANCE(32) > ::ShellExecute(hwnd, "open", pszURL, NULL, NULL, 0))
  622. {
  623. AfxMessageBox("The website couldn't be opened.");
  624. }
  625. }
  626. //-----------------------------------------------------------------------------
  627. // Purpose: Opens a URL in the default web browser by string ID.
  628. //-----------------------------------------------------------------------------
  629. void CHammer::OpenURL(UINT nID, HWND hwnd)
  630. {
  631. CString str;
  632. str.LoadString(nID);
  633. OpenURL(str, hwnd);
  634. }
  635. //-----------------------------------------------------------------------------
  636. // Purpose: Launches the help system for the specified help topic.
  637. // Input : pszTopic - Topic to open.
  638. //-----------------------------------------------------------------------------
  639. void CHammer::Help(const char *pszTopic)
  640. {
  641. //
  642. // Get the directory that the help file should be in (our program directory).
  643. //
  644. /*char szHelpDir[MAX_PATH];
  645. GetDirectory(DIR_PROGRAM, szHelpDir);
  646. //
  647. // Find the application that is associated with compiled HTML files.
  648. //
  649. char szHelpExe[MAX_PATH];
  650. HINSTANCE hResult = FindExecutable("wc.chm", szHelpDir, szHelpExe);
  651. if (hResult > (HINSTANCE)32)
  652. {
  653. //
  654. // Build the full topic with which to launch the help application.
  655. //
  656. char szParam[2 * MAX_PATH];
  657. strcpy(szParam, szHelpDir);
  658. strcat(szParam, "wc.chm");
  659. if (pszTopic != NULL)
  660. {
  661. strcat(szParam, "::/");
  662. strcat(szParam, pszTopic);
  663. }
  664. //
  665. // Launch the help application for the given topic.
  666. //
  667. hResult = ShellExecute(NULL, "open", szHelpExe, szParam, szHelpDir, SW_SHOW);
  668. }
  669. if (hResult <= (HINSTANCE)32)
  670. {
  671. char szError[MAX_PATH];
  672. sprintf(szError, "The help system could not be launched. The the following error was returned:\n%s (0x%X)", GetErrorString(), hResult);
  673. AfxMessageBox(szError);
  674. }
  675. */
  676. }
  677. static SpewRetval_t HammerDbgOutput( SpewType_t spewType, char const *pMsg )
  678. {
  679. // FIXME: The messages we're getting from the material system
  680. // are ones that we really don't care much about.
  681. // I'm disabling this for now, we need to decide about what to do with this
  682. switch( spewType )
  683. {
  684. case SPEW_ERROR:
  685. MessageBox( NULL, (LPCTSTR)pMsg, "Fatal Error", MB_OK | MB_ICONINFORMATION );
  686. #ifdef _DEBUG
  687. return SPEW_DEBUGGER;
  688. #else
  689. TerminateProcess( GetCurrentProcess(), 1 );
  690. return SPEW_ABORT;
  691. #endif
  692. default:
  693. OutputDebugString( pMsg );
  694. return (spewType == SPEW_ASSERT) ? SPEW_DEBUGGER : SPEW_CONTINUE;
  695. }
  696. }
  697. //-----------------------------------------------------------------------------
  698. // Purpose:
  699. //-----------------------------------------------------------------------------
  700. static HANDLE dwChangeHandle = NULL;
  701. void UpdatePrefabs_Init()
  702. {
  703. // Watch the prefabs tree for file or directory creation
  704. // and deletion.
  705. if (dwChangeHandle == NULL)
  706. {
  707. char szPrefabDir[MAX_PATH];
  708. APP()->GetDirectory(DIR_PREFABS, szPrefabDir);
  709. dwChangeHandle = FindFirstChangeNotification(
  710. szPrefabDir, // directory to watch
  711. TRUE, // watch the subtree
  712. FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_FILE_NAME); // watch file and dir name changes
  713. if (dwChangeHandle == INVALID_HANDLE_VALUE)
  714. {
  715. ExitProcess(GetLastError());
  716. }
  717. }
  718. }
  719. //-----------------------------------------------------------------------------
  720. // Purpose:
  721. //-----------------------------------------------------------------------------
  722. void UpdatePrefabs()
  723. {
  724. // Wait for notification.
  725. DWORD dwWaitStatus = WaitForSingleObject(dwChangeHandle, 0);
  726. if (dwWaitStatus == WAIT_OBJECT_0)
  727. {
  728. // A file was created or deleted in the prefabs tree.
  729. // Refresh the prefabs and restart the change notification.
  730. CPrefabLibrary::FreeAllLibraries();
  731. CPrefabLibrary::LoadAllLibraries();
  732. GetMainWnd()->m_ObjectBar.UpdateListForTool(ToolManager()->GetActiveToolID());
  733. if (FindNextChangeNotification(dwChangeHandle) == FALSE)
  734. {
  735. ExitProcess(GetLastError());
  736. }
  737. }
  738. }
  739. //-----------------------------------------------------------------------------
  740. // Purpose:
  741. //-----------------------------------------------------------------------------
  742. void UpdatePrefabs_Shutdown()
  743. {
  744. FindCloseChangeNotification(dwChangeHandle);
  745. }
  746. //-----------------------------------------------------------------------------
  747. // Purpose:
  748. //-----------------------------------------------------------------------------
  749. BOOL CHammer::InitInstance()
  750. {
  751. return TRUE;
  752. }
  753. //-----------------------------------------------------------------------------
  754. // Purpose: Prompt the user to select a game configuration.
  755. //-----------------------------------------------------------------------------
  756. CGameConfig *CHammer::PromptForGameConfig()
  757. {
  758. CEditGameConfigs dlg(TRUE, GetMainWnd());
  759. if (dlg.DoModal() != IDOK)
  760. {
  761. return NULL;
  762. }
  763. return dlg.GetSelectedGame();
  764. }
  765. //-----------------------------------------------------------------------------
  766. // Purpose:
  767. // Output : Returns true on success, false on failure.
  768. //-----------------------------------------------------------------------------
  769. bool CHammer::InitSessionGameConfig(const char *szGame)
  770. {
  771. CGameConfig *pConfig = NULL;
  772. bool bManualChoice = false;
  773. if ( CommandLine()->FindParm( "-chooseconfig" ) )
  774. {
  775. pConfig = PromptForGameConfig();
  776. bManualChoice = true;
  777. }
  778. if (!bManualChoice)
  779. {
  780. if (szGame && szGame[0] != '\0')
  781. {
  782. // They passed in -game on the command line, use that.
  783. pConfig = Options.configs.FindConfigForGame(szGame);
  784. if (!pConfig)
  785. {
  786. Msg(mwError, "Invalid game \"%s\" specified on the command-line, ignoring.", szGame);
  787. }
  788. }
  789. else
  790. {
  791. // No -game on the command line, try using VPROJECT.
  792. const char *pszGameDir = getenv("vproject");
  793. if ( pszGameDir )
  794. {
  795. pConfig = Options.configs.FindConfigForGame(pszGameDir);
  796. if (!pConfig)
  797. {
  798. Msg(mwError, "Invalid game \"%s\" found in VPROJECT environment variable, ignoring.", pszGameDir);
  799. }
  800. }
  801. }
  802. }
  803. if (pConfig == NULL)
  804. {
  805. // Nothing useful was passed in or found in VPROJECT.
  806. // If there's only one config, use that.
  807. if (Options.configs.GetGameConfigCount() == 1)
  808. {
  809. pConfig = Options.configs.GetGameConfig(0);
  810. }
  811. else
  812. {
  813. // Otherwise, prompt for a config to use.
  814. pConfig = PromptForGameConfig();
  815. }
  816. }
  817. if (pConfig)
  818. {
  819. CGameConfig::SetActiveGame(pConfig);
  820. return true;
  821. }
  822. return false;
  823. }
  824. //-----------------------------------------------------------------------------
  825. // Check for 16-bit color or higher.
  826. //-----------------------------------------------------------------------------
  827. bool CHammer::Check16BitColor()
  828. {
  829. // Check for 15-bit color or higher.
  830. HDC hDC = ::CreateCompatibleDC(NULL);
  831. if (hDC)
  832. {
  833. int bpp = GetDeviceCaps(hDC, BITSPIXEL);
  834. if (bpp < 15)
  835. {
  836. AfxMessageBox("Your screen must be in 16-bit color or higher to run Hammer.");
  837. return false;
  838. }
  839. ::DeleteDC(hDC);
  840. }
  841. return true;
  842. }
  843. //-----------------------------------------------------------------------------
  844. // Purpose: Returns true if Hammer is in the process of shutting down.
  845. //-----------------------------------------------------------------------------
  846. InitReturnVal_t CHammer::Init()
  847. {
  848. return (InitReturnVal_t)WrapFunctionWithMinidumpHandler( &CHammer::StaticHammerInternalInit, this );
  849. }
  850. int CHammer::StaticHammerInternalInit( void *pParam )
  851. {
  852. return (int)((CHammer*)pParam)->HammerInternalInit();
  853. }
  854. InitReturnVal_t CHammer::HammerInternalInit()
  855. {
  856. SpewOutputFunc( HammerDbgOutput );
  857. MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false );
  858. InitReturnVal_t nRetVal = BaseClass::Init();
  859. if ( nRetVal != INIT_OK )
  860. return nRetVal;
  861. if ( !Check16BitColor() )
  862. return INIT_FAILED;
  863. //
  864. // Create a custom window class for this application so that engine's
  865. // FindWindow will find us.
  866. //
  867. WNDCLASS wndcls;
  868. memset(&wndcls, 0, sizeof(WNDCLASS));
  869. wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
  870. wndcls.lpfnWndProc = AfxWndProc;
  871. wndcls.hInstance = AfxGetInstanceHandle();
  872. wndcls.hIcon = LoadIcon(IDR_MAINFRAME);
  873. wndcls.hCursor = LoadCursor( IDC_ARROW );
  874. wndcls.hbrBackground = (HBRUSH)0; // (COLOR_WINDOW + 1);
  875. wndcls.lpszMenuName = "IDR_MAINFRAME";
  876. wndcls.cbWndExtra = 0;
  877. // HL Shell class name
  878. wndcls.lpszClassName = "VALVEWORLDCRAFT";
  879. // Register it, exit if it fails
  880. if(!AfxRegisterClass(&wndcls))
  881. {
  882. AfxMessageBox("Could not register Hammer's main window class");
  883. return INIT_FAILED;
  884. }
  885. srand(time(NULL));
  886. WriteProfileString("General", "Directory", m_szAppDir);
  887. //
  888. // Create a window to receive shell commands from the engine, and attach it
  889. // to our shell processor.
  890. //
  891. g_ShellMessageWnd.Create();
  892. g_ShellMessageWnd.SetShell(&g_Shell);
  893. if (bMakeLib)
  894. return INIT_FAILED; // made library .. don't want to enter program
  895. CHammerCmdLine cmdInfo;
  896. ParseCommandLine(cmdInfo);
  897. //
  898. // Create and optionally display the splash screen.
  899. //
  900. CSplashWnd::EnableSplashScreen(cmdInfo.m_bShowLogo);
  901. LoadSequences(); // load cmd sequences - different from options because
  902. // users might want to share (darn registry)
  903. // other init:
  904. randomize();
  905. /*
  906. #ifdef _AFXDLL
  907. Enable3dControls(); // Call this when using MFC in a shared DLL
  908. #else
  909. Enable3dControlsStatic(); // Call this when linking to MFC statically
  910. #endif
  911. */
  912. LoadStdProfileSettings(); // Load standard INI file options (including MRU)
  913. // Register the application's document templates. Document templates
  914. // serve as the connection between documents, frame windows and views.
  915. pMapDocTemplate = new CHammerDocTemplate(
  916. IDR_MAPDOC,
  917. RUNTIME_CLASS(CMapDoc),
  918. RUNTIME_CLASS(CChildFrame), // custom MDI child frame
  919. RUNTIME_CLASS(CMapView2D));
  920. AddDocTemplate(pMapDocTemplate);
  921. pManifestDocTemplate = new CHammerDocTemplate(
  922. IDR_MANIFESTDOC,
  923. RUNTIME_CLASS(CManifest),
  924. RUNTIME_CLASS(CChildFrame), // custom MDI child frame
  925. RUNTIME_CLASS(CMapView2D));
  926. HINSTANCE hInst = AfxFindResourceHandle( MAKEINTRESOURCE( IDR_MAPDOC ), RT_MENU );
  927. pManifestDocTemplate->m_hMenuShared = ::LoadMenu( hInst, MAKEINTRESOURCE( IDR_MAPDOC ) );
  928. hInst = AfxFindResourceHandle( MAKEINTRESOURCE( IDR_MAPDOC ), RT_ACCELERATOR );
  929. pManifestDocTemplate->m_hAccelTable = ::LoadAccelerators( hInst, MAKEINTRESOURCE( IDR_MAPDOC ) );
  930. AddDocTemplate(pManifestDocTemplate);
  931. // register shell file types
  932. RegisterShellFileTypes();
  933. //
  934. // Initialize the rich edit control so we can use it in the entity help dialog.
  935. //
  936. AfxInitRichEdit();
  937. //
  938. // Create main MDI Frame window. Must be done AFTER registering the multidoc template!
  939. //
  940. CMainFrame *pMainFrame = new CMainFrame;
  941. if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
  942. return INIT_FAILED;
  943. m_pMainWnd = pMainFrame;
  944. CSplashWnd::ShowSplashScreen(pMainFrame);
  945. // try to init VGUI
  946. HammerVGui()->Init( m_pMainWnd->GetSafeHwnd() );
  947. // The main window has been initialized, so show and update it.
  948. //
  949. m_nCmdShow = SW_SHOWMAXIMIZED;
  950. pMainFrame->ShowWindow(m_nCmdShow);
  951. pMainFrame->UpdateWindow();
  952. // Now that we've initialized the file system, we can parse this config's gameinfo.txt for the additional settings there.
  953. g_pGameConfig->ParseGameInfo();
  954. materials->ModInit();
  955. //
  956. // Initialize the texture manager and load all textures.
  957. //
  958. if (!g_Textures.Initialize(m_pMainWnd->m_hWnd))
  959. {
  960. Msg(mwError, "Failed to initialize texture system.");
  961. }
  962. else
  963. {
  964. //
  965. // Initialize studio model rendering (must happen after g_Textures.Initialize since
  966. // g_Textures.Initialize kickstarts the material system and sets up g_MaterialSystemClientFactory)
  967. //
  968. StudioModel::Initialize();
  969. g_Textures.LoadAllGraphicsFiles();
  970. g_Textures.SetActiveConfig(g_pGameConfig);
  971. }
  972. // Watch for changes to models.
  973. InitStudioFileChangeWatcher();
  974. LoadFileSystemDialogModule();
  975. // Load detail object descriptions.
  976. char szGameDir[_MAX_PATH];
  977. APP()->GetDirectory(DIR_MOD, szGameDir);
  978. DetailObjects::LoadEmitDetailObjectDictionary( szGameDir );
  979. // Initialize the sound system
  980. g_Sounds.Initialize();
  981. UpdatePrefabs_Init();
  982. // Indicate that we are ready to use.
  983. m_pMainWnd->FlashWindow(TRUE);
  984. // Parse command line for standard shell commands, DDE, file open
  985. if ( !IsRunningInEngine() )
  986. {
  987. if ( Q_stristr( cmdInfo.m_strFileName, ".vmf" ) )
  988. {
  989. // we don't want to make a new file (default behavior if no file
  990. // is specified on the commandline.)
  991. // Dispatch commands specified on the command line
  992. if (!ProcessShellCommand(cmdInfo))
  993. return INIT_FAILED;
  994. }
  995. }
  996. if ( Options.general.bClosedCorrectly == FALSE )
  997. {
  998. CString strLastGoodSave = APP()->GetProfileString("General", "Last Good Save", "");
  999. if ( strLastGoodSave.GetLength() != 0 )
  1000. {
  1001. char msg[1024];
  1002. 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 );
  1003. if ( AfxMessageBox( msg, MB_YESNO ) == IDYES )
  1004. {
  1005. LoadLastGoodSave();
  1006. }
  1007. }
  1008. }
  1009. #ifdef VPROF_HAMMER
  1010. g_VProfCurrentProfile.Start();
  1011. #endif
  1012. CSplashWnd::HideSplashScreen();
  1013. // create the lighting preview thread
  1014. g_LPreviewThread = CreateSimpleThread( LightingPreviewThreadFN, 0 );
  1015. return INIT_OK;
  1016. }
  1017. int CHammer::MainLoop()
  1018. {
  1019. return WrapFunctionWithMinidumpHandler( StaticInternalMainLoop, this );
  1020. }
  1021. int CHammer::StaticInternalMainLoop( void *pParam )
  1022. {
  1023. return ((CHammer*)pParam)->InternalMainLoop();
  1024. }
  1025. int CHammer::InternalMainLoop()
  1026. {
  1027. MSG msg;
  1028. g_pDataCache->SetSize( 128 * 1024 * 1024 );
  1029. // For tracking the idle time state
  1030. bool bIdle = true;
  1031. long lIdleCount = 0;
  1032. // We've got our own message pump here
  1033. g_pInputSystem->EnableMessagePump( false );
  1034. // Acquire and dispatch messages until a WM_QUIT message is received.
  1035. for (;;)
  1036. {
  1037. RunFrame();
  1038. if ( bIdle && !HammerOnIdle(lIdleCount++) )
  1039. {
  1040. bIdle = false;
  1041. }
  1042. //
  1043. // Pump messages until the message queue is empty.
  1044. //
  1045. while (::PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
  1046. {
  1047. if ( msg.message == WM_QUIT )
  1048. return 1;
  1049. if ( !HammerPreTranslateMessage(&msg) )
  1050. {
  1051. ::TranslateMessage(&msg);
  1052. ::DispatchMessage(&msg);
  1053. }
  1054. // Reset idle state after pumping idle message.
  1055. if ( HammerIsIdleMessage(&msg) )
  1056. {
  1057. bIdle = true;
  1058. lIdleCount = 0;
  1059. }
  1060. }
  1061. }
  1062. Assert(0); // not reachable
  1063. }
  1064. //-----------------------------------------------------------------------------
  1065. // Shuts down hammer
  1066. //-----------------------------------------------------------------------------
  1067. void CHammer::Shutdown()
  1068. {
  1069. if ( g_LPreviewThread )
  1070. {
  1071. MessageToLPreview StopMsg( LPREVIEW_MSG_EXIT );
  1072. g_HammerToLPreviewMsgQueue.QueueMessage( StopMsg );
  1073. ThreadJoin( g_LPreviewThread );
  1074. g_LPreviewThread = 0;
  1075. }
  1076. #ifdef VPROF_HAMMER
  1077. g_VProfCurrentProfile.Stop();
  1078. #endif
  1079. // PrintBudgetGroupTimes_Recursive( g_VProfCurrentProfile.GetRoot() );
  1080. HammerVGui()->Shutdown();
  1081. UnloadFileSystemDialogModule();
  1082. // Delete the command sequences.
  1083. int nSequenceCount = m_CmdSequences.GetSize();
  1084. for (int i = 0; i < nSequenceCount; i++)
  1085. {
  1086. CCommandSequence *pSeq = m_CmdSequences[i];
  1087. if ( pSeq != NULL )
  1088. {
  1089. delete pSeq;
  1090. m_CmdSequences[i] = NULL;
  1091. }
  1092. }
  1093. g_Textures.ShutDown();
  1094. // Shutdown the sound system
  1095. g_Sounds.ShutDown();
  1096. materials->ModShutdown();
  1097. BaseClass::Shutdown();
  1098. }
  1099. //-----------------------------------------------------------------------------
  1100. // Methods used by the engine
  1101. //-----------------------------------------------------------------------------
  1102. const char *CHammer::GetDefaultMod()
  1103. {
  1104. return g_pGameConfig->GetMod();
  1105. }
  1106. const char *CHammer::GetDefaultGame()
  1107. {
  1108. return g_pGameConfig->GetGame();
  1109. }
  1110. const char *CHammer::GetDefaultModFullPath()
  1111. {
  1112. return g_pGameConfig->m_szModDir;
  1113. }
  1114. //-----------------------------------------------------------------------------
  1115. // Pops up the options dialog
  1116. //-----------------------------------------------------------------------------
  1117. RequestRetval_t CHammer::RequestNewConfig()
  1118. {
  1119. if ( !Options.RunConfigurationDialog() )
  1120. return REQUEST_QUIT;
  1121. return REQUEST_OK;
  1122. }
  1123. //-----------------------------------------------------------------------------
  1124. // Purpose: Returns true if Hammer is in the process of shutting down.
  1125. //-----------------------------------------------------------------------------
  1126. bool CHammer::IsClosing()
  1127. {
  1128. return m_bClosing;
  1129. }
  1130. //-----------------------------------------------------------------------------
  1131. // Purpose: Signals the beginning of app shutdown. Should be called before
  1132. // rendering views.
  1133. //-----------------------------------------------------------------------------
  1134. void CHammer::BeginClosing()
  1135. {
  1136. m_bClosing = true;
  1137. }
  1138. //-----------------------------------------------------------------------------
  1139. // Purpose:
  1140. //-----------------------------------------------------------------------------
  1141. int CHammer::ExitInstance()
  1142. {
  1143. g_ShellMessageWnd.DestroyWindow();
  1144. UpdatePrefabs_Shutdown();
  1145. if ( GetSpewOutputFunc() == HammerDbgOutput )
  1146. {
  1147. SpewOutputFunc( NULL );
  1148. }
  1149. SaveStdProfileSettings();
  1150. return CWinApp::ExitInstance();
  1151. }
  1152. //-----------------------------------------------------------------------------
  1153. // Purpose: this function sets the global flag indicating if new documents should
  1154. // be visible.
  1155. // Input : bIsVisible - flag to indicate visibility status.
  1156. //-----------------------------------------------------------------------------
  1157. void CHammer::SetIsNewDocumentVisible( bool bIsVisible )
  1158. {
  1159. CHammer::m_bIsNewDocumentVisible = bIsVisible;
  1160. }
  1161. //-----------------------------------------------------------------------------
  1162. // Purpose: this functionr eturns the global flag indicating if new documents should
  1163. // be visible.
  1164. //-----------------------------------------------------------------------------
  1165. bool CHammer::IsNewDocumentVisible( void )
  1166. {
  1167. return CHammer::m_bIsNewDocumentVisible;
  1168. }
  1169. /////////////////////////////////////////////////////////////////////////////
  1170. // CAboutDlg dialog used for App About
  1171. class CAboutDlg : public CDialog
  1172. {
  1173. public:
  1174. CAboutDlg();
  1175. // Dialog Data
  1176. //{{AFX_DATA(CAboutDlg)
  1177. enum { IDD = IDD_ABOUTBOX };
  1178. CStatic m_cRedHerring;
  1179. CButton m_Order;
  1180. //}}AFX_DATA
  1181. // ClassWizard generated virtual function overrides
  1182. //{{AFX_VIRTUAL(CAboutDlg)
  1183. protected:
  1184. virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
  1185. //}}AFX_VIRTUAL
  1186. // Implementation
  1187. protected:
  1188. //{{AFX_MSG(CAboutDlg)
  1189. afx_msg void OnOrder();
  1190. virtual BOOL OnInitDialog();
  1191. //}}AFX_MSG
  1192. DECLARE_MESSAGE_MAP()
  1193. };
  1194. //-----------------------------------------------------------------------------
  1195. // Purpose:
  1196. //-----------------------------------------------------------------------------
  1197. CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
  1198. {
  1199. //{{AFX_DATA_INIT(CAboutDlg)
  1200. //}}AFX_DATA_INIT
  1201. }
  1202. //-----------------------------------------------------------------------------
  1203. // Purpose:
  1204. // Input : pDX -
  1205. //-----------------------------------------------------------------------------
  1206. void CAboutDlg::DoDataExchange(CDataExchange* pDX)
  1207. {
  1208. CDialog::DoDataExchange(pDX);
  1209. //{{AFX_DATA_MAP(CAboutDlg)
  1210. DDX_Control(pDX, IDC_REDHERRING, m_cRedHerring);
  1211. DDX_Control(pDX, IDC_ORDER, m_Order);
  1212. //}}AFX_DATA_MAP
  1213. }
  1214. #include <process.h>
  1215. //-----------------------------------------------------------------------------
  1216. // Purpose:
  1217. //-----------------------------------------------------------------------------
  1218. void CAboutDlg::OnOrder()
  1219. {
  1220. char szBuf[MAX_PATH];
  1221. GetWindowsDirectory(szBuf, MAX_PATH);
  1222. strcat(szBuf, "\\notepad.exe");
  1223. _spawnl(_P_NOWAIT, szBuf, szBuf, "order.txt", NULL);
  1224. }
  1225. #define DEMO_VERSION 0
  1226. // 1, 4, 8, 17, 0, 0 // Encodes "Valve"
  1227. #if DEMO_VERSION
  1228. char gVersion[] = {
  1229. #if DEMO_VERSION==1
  1230. 7, 38, 68, 32, 4, 77, 12, 1, 0 // Encodes "PC Gamer Demo"
  1231. #elif DEMO_VERSION==2
  1232. 7, 38, 68, 32, 4, 77, 12, 0, 0 // Encodes "PC Games Demo"
  1233. #elif DEMO_VERSION==3
  1234. 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"
  1235. #elif DEMO_VERSION==4
  1236. 25, 0, 28, 19, 72, 103, 12, 29, 69, 19, 65, 0, 6, 0, 2, 0 // Encodes "Next-Generation Demo"
  1237. #elif DEMO_VERSION==5
  1238. 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"
  1239. #elif DEMO_VERSION==6
  1240. 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"
  1241. #elif DEMO_VERSION==7
  1242. 50, 72, 52, 43, 36, 121, 0 // Encodes "e-PLAY"
  1243. #elif DEMO_VERSION==8
  1244. 4, 17, 22, 6, 17, 69, 14, 10, 0, 49, 76, 1, 28, 0 // Encodes "Strategy Plus"
  1245. #elif DEMO_VERSION==9
  1246. 7, 38, 68, 42, 4, 71, 8, 9, 73, 15, 69, 0 // Encodes "PC Magazine"
  1247. #elif DEMO_VERSION==10
  1248. 5, 10, 8, 11, 12, 78, 14, 83, 115, 21, 79, 26, 10, 0 // Encodes "Rolling Stone"
  1249. #elif DEMO_VERSION==11
  1250. 16, 4, 9, 2, 22, 80, 6, 7, 0 // Encodes "Gamespot"
  1251. #endif
  1252. };
  1253. static char gKey[] = "Wedge is a tool"; // Decrypt key
  1254. // XOR a string with a key
  1255. void Encode( char *pstring, char *pkey, int strLen )
  1256. {
  1257. int i, len;
  1258. len = strlen( pkey );
  1259. for ( i = 0; i < strLen; i++ )
  1260. pstring[i] ^= pkey[ i % len ];
  1261. }
  1262. #endif // DEMO_VERSION
  1263. //-----------------------------------------------------------------------------
  1264. // Purpose:
  1265. // Output : Returns TRUE on success, FALSE on failure.
  1266. //-----------------------------------------------------------------------------
  1267. BOOL CAboutDlg::OnInitDialog(void)
  1268. {
  1269. CDialog::OnInitDialog();
  1270. m_Order.SetRedraw(FALSE);
  1271. #if DEMO_VERSION
  1272. static BOOL bFirst = TRUE;
  1273. if(bFirst)
  1274. {
  1275. Encode(gVersion, gKey, sizeof(gVersion)-1);
  1276. bFirst = FALSE;
  1277. }
  1278. CString str;
  1279. str.Format("%s Demo", gVersion);
  1280. m_cRedHerring.SetWindowText(str);
  1281. #endif // DEMO_VERSION
  1282. //
  1283. // Display the build number.
  1284. //
  1285. CWnd *pWnd = GetDlgItem(IDC_BUILD_NUMBER);
  1286. if (pWnd != NULL)
  1287. {
  1288. char szTemp1[MAX_PATH];
  1289. char szTemp2[MAX_PATH];
  1290. int nBuild = build_number();
  1291. pWnd->GetWindowText(szTemp1, sizeof(szTemp1));
  1292. sprintf(szTemp2, szTemp1, nBuild);
  1293. pWnd->SetWindowText(szTemp2);
  1294. }
  1295. //
  1296. // For SDK builds, append "SDK" to the version number.
  1297. //
  1298. #ifdef SDK_BUILD
  1299. char szTemp[MAX_PATH];
  1300. GetWindowText(szTemp, sizeof(szTemp));
  1301. strcat(szTemp, " SDK");
  1302. SetWindowText(szTemp);
  1303. #endif // SDK_BUILD
  1304. return TRUE;
  1305. }
  1306. BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
  1307. //{{AFX_MSG_MAP(CAboutDlg)
  1308. ON_BN_CLICKED(IDC_ORDER, OnOrder)
  1309. //}}AFX_MSG_MAP
  1310. END_MESSAGE_MAP()
  1311. //-----------------------------------------------------------------------------
  1312. // Purpose:
  1313. //-----------------------------------------------------------------------------
  1314. void CHammer::OnAppAbout(void)
  1315. {
  1316. CAboutDlg aboutDlg;
  1317. aboutDlg.DoModal();
  1318. #ifdef VPROF_HAMMER
  1319. g_VProfCurrentProfile.OutputReport();
  1320. g_VProfCurrentProfile.Reset();
  1321. g_pMemAlloc->DumpStats();
  1322. #endif
  1323. }
  1324. //-----------------------------------------------------------------------------
  1325. // Purpose:
  1326. //-----------------------------------------------------------------------------
  1327. void CHammer::OnFileNew(void)
  1328. {
  1329. pMapDocTemplate->OpenDocumentFile(NULL);
  1330. if(Options.general.bLoadwinpos && Options.general.bIndependentwin)
  1331. {
  1332. ::GetMainWnd()->LoadWindowStates();
  1333. }
  1334. }
  1335. //-----------------------------------------------------------------------------
  1336. // Purpose:
  1337. //-----------------------------------------------------------------------------
  1338. void CHammer::OnFileOpen(void)
  1339. {
  1340. static char szInitialDir[MAX_PATH] = "";
  1341. if (szInitialDir[0] == '\0')
  1342. {
  1343. strcpy(szInitialDir, g_pGameConfig->szMapDir);
  1344. }
  1345. // TODO: need to prevent (or handle) opening VMF files when using old map file formats
  1346. 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||");
  1347. dlg.m_ofn.lpstrInitialDir = szInitialDir;
  1348. int iRvl = dlg.DoModal();
  1349. if (iRvl == IDCANCEL)
  1350. {
  1351. return;
  1352. }
  1353. //
  1354. // Get the directory they browsed to for next time.
  1355. //
  1356. CString str = dlg.GetPathName();
  1357. int nSlash = str.ReverseFind('\\');
  1358. if (nSlash != -1)
  1359. {
  1360. strcpy(szInitialDir, str.Left(nSlash));
  1361. }
  1362. if (str.Find('.') == -1)
  1363. {
  1364. switch (dlg.m_ofn.nFilterIndex)
  1365. {
  1366. case 1:
  1367. {
  1368. str += ".vmf";
  1369. break;
  1370. }
  1371. case 2:
  1372. {
  1373. str += ".vmf_autosave";
  1374. break;
  1375. }
  1376. case 3:
  1377. {
  1378. str += ".rmf";
  1379. break;
  1380. }
  1381. case 4:
  1382. {
  1383. str += ".map";
  1384. break;
  1385. }
  1386. }
  1387. }
  1388. OpenDocumentFile(str);
  1389. }
  1390. //-----------------------------------------------------------------------------
  1391. // Purpose:
  1392. // Input : lpszFileName -
  1393. // Output : CDocument*
  1394. //-----------------------------------------------------------------------------
  1395. CDocument* CHammer::OpenDocumentFile(LPCTSTR lpszFileName)
  1396. {
  1397. if(GetFileAttributes(lpszFileName) == 0xFFFFFFFF)
  1398. {
  1399. CString Message;
  1400. Message = "The file " + CString( lpszFileName ) + " does not exist.";
  1401. AfxMessageBox( Message );
  1402. return NULL;
  1403. }
  1404. CDocument *pDoc = m_pDocManager->OpenDocumentFile( lpszFileName );
  1405. CMapDoc *pMapDoc = dynamic_cast< CMapDoc * >( pDoc );
  1406. if ( pMapDoc )
  1407. {
  1408. CMapDoc::SetActiveMapDoc( pMapDoc );
  1409. }
  1410. if( pDoc && Options.general.bLoadwinpos && Options.general.bIndependentwin)
  1411. {
  1412. ::GetMainWnd()->LoadWindowStates();
  1413. }
  1414. if ( pMapDoc && !CHammer::IsNewDocumentVisible() )
  1415. {
  1416. pMapDoc->ShowWindow( false );
  1417. }
  1418. else
  1419. {
  1420. pMapDoc->ShowWindow( true );
  1421. }
  1422. if ( pDoc && ((CMapDoc *)pDoc)->IsAutosave() )
  1423. {
  1424. char szRenameMessage[MAX_PATH+MAX_PATH+256];
  1425. CString newMapPath = *((CMapDoc *)pDoc)->AutosavedFrom();
  1426. 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 );
  1427. if ( AfxMessageBox( szRenameMessage, MB_YESNO ) == IDYES )
  1428. {
  1429. ((CMapDoc *)pDoc)->SetPathName( newMapPath );
  1430. }
  1431. }
  1432. return pDoc;
  1433. }
  1434. //-----------------------------------------------------------------------------
  1435. // Returns true if this is a key message that is not a special dialog navigation message.
  1436. //-----------------------------------------------------------------------------
  1437. inline bool IsKeyStrokeMessage( MSG *pMsg )
  1438. {
  1439. if ( ( pMsg->message != WM_KEYDOWN ) && ( pMsg->message != WM_CHAR ) )
  1440. return false;
  1441. // Check for special dialog navigation characters -- they don't count
  1442. if ( ( pMsg->wParam == VK_ESCAPE ) || ( pMsg->wParam == VK_RETURN ) || ( pMsg->wParam == VK_TAB ) )
  1443. return false;
  1444. if ( ( pMsg->wParam == VK_UP ) || ( pMsg->wParam == VK_DOWN ) || ( pMsg->wParam == VK_LEFT ) || ( pMsg->wParam == VK_RIGHT ) )
  1445. return false;
  1446. return true;
  1447. }
  1448. //-----------------------------------------------------------------------------
  1449. //-----------------------------------------------------------------------------
  1450. BOOL CHammer::PreTranslateMessage(MSG* pMsg)
  1451. {
  1452. // CG: The following lines were added by the Splash Screen component.
  1453. if (CSplashWnd::PreTranslateAppMessage(pMsg))
  1454. return TRUE;
  1455. // This is for raw input, these shouldn't be translated so skip that here.
  1456. if ( pMsg->message == WM_INPUT )
  1457. return TRUE;
  1458. // Suppress the accelerator table for edit controls so that users can type
  1459. // uppercase characters without invoking Hammer tools.
  1460. if ( IsKeyStrokeMessage( pMsg ) )
  1461. {
  1462. char className[80];
  1463. ::GetClassNameA( pMsg->hwnd, className, sizeof( className ) );
  1464. // The classname of dialog window in the VGUI model browser and particle browser is AfxWnd100sd in Debug and AfxWnd100s in Release
  1465. if ( !V_stricmp( className, "edit" ) || V_stristr( className, "AfxWnd" ) )
  1466. {
  1467. // Typing in an edit control. Don't pretranslate, just translate/dispatch.
  1468. return FALSE;
  1469. }
  1470. }
  1471. return CWinApp::PreTranslateMessage(pMsg);
  1472. }
  1473. //-----------------------------------------------------------------------------
  1474. // Purpose:
  1475. //-----------------------------------------------------------------------------
  1476. void CHammer::LoadSequences(void)
  1477. {
  1478. char szRootDir[MAX_PATH];
  1479. char szFullPath[MAX_PATH];
  1480. APP()->GetDirectory(DIR_PROGRAM, szRootDir);
  1481. Q_MakeAbsolutePath( szFullPath, MAX_PATH, "CmdSeq.wc", szRootDir );
  1482. std::ifstream file(szFullPath, std::ios::in | std::ios::binary);
  1483. if(!file.is_open())
  1484. return; // none to load
  1485. // skip past header & version
  1486. float fThisVersion;
  1487. file.seekg(strlen(pszSequenceHdr));
  1488. file.read((char*)&fThisVersion, sizeof fThisVersion);
  1489. // read number of sequences
  1490. DWORD dwSize;
  1491. int nSeq;
  1492. file.read((char*)&dwSize, sizeof dwSize);
  1493. nSeq = dwSize;
  1494. for(int i = 0; i < nSeq; i++)
  1495. {
  1496. CCommandSequence *pSeq = new CCommandSequence;
  1497. file.read(pSeq->m_szName, 128);
  1498. // read commands in sequence
  1499. file.read((char*)&dwSize, sizeof dwSize);
  1500. int nCmd = dwSize;
  1501. CCOMMAND cmd;
  1502. for(int iCmd = 0; iCmd < nCmd; iCmd++)
  1503. {
  1504. if(fThisVersion < 0.2f)
  1505. {
  1506. file.read((char*)&cmd, sizeof(CCOMMAND)-1);
  1507. cmd.bNoWait = FALSE;
  1508. }
  1509. else
  1510. {
  1511. file.read((char*)&cmd, sizeof(CCOMMAND));
  1512. }
  1513. pSeq->m_Commands.Add(cmd);
  1514. }
  1515. m_CmdSequences.Add(pSeq);
  1516. }
  1517. }
  1518. //-----------------------------------------------------------------------------
  1519. // Purpose:
  1520. //-----------------------------------------------------------------------------
  1521. void CHammer::SaveSequences(void)
  1522. {
  1523. char szRootDir[MAX_PATH];
  1524. char szFullPath[MAX_PATH];
  1525. APP()->GetDirectory(DIR_PROGRAM, szRootDir);
  1526. Q_MakeAbsolutePath( szFullPath, MAX_PATH, "CmdSeq.wc", szRootDir );
  1527. std::ofstream file( szFullPath, std::ios::out | std::ios::binary );
  1528. // write header
  1529. file.write(pszSequenceHdr, Q_strlen(pszSequenceHdr));
  1530. // write out version
  1531. file.write((char*)&fSequenceVersion, sizeof(float));
  1532. // write out each sequence..
  1533. int i, nSeq = m_CmdSequences.GetSize();
  1534. DWORD dwSize = nSeq;
  1535. file.write((char*)&dwSize, sizeof dwSize);
  1536. for(i = 0; i < nSeq; i++)
  1537. {
  1538. CCommandSequence *pSeq = m_CmdSequences[i];
  1539. // write name of sequence
  1540. file.write(pSeq->m_szName, 128);
  1541. // write number of commands
  1542. int nCmd = pSeq->m_Commands.GetSize();
  1543. dwSize = nCmd;
  1544. file.write((char*)&dwSize, sizeof dwSize);
  1545. // write commands ..
  1546. for(int iCmd = 0; iCmd < nCmd; iCmd++)
  1547. {
  1548. CCOMMAND &cmd = pSeq->m_Commands[iCmd];
  1549. file.write((char*)&cmd, sizeof cmd);
  1550. }
  1551. }
  1552. }
  1553. void CHammer::SetForceRenderNextFrame()
  1554. {
  1555. m_bForceRenderNextFrame = true;
  1556. }
  1557. bool CHammer::GetForceRenderNextFrame()
  1558. {
  1559. return m_bForceRenderNextFrame;
  1560. }
  1561. //-----------------------------------------------------------------------------
  1562. // Purpose:
  1563. // Input : *pDoc -
  1564. //-----------------------------------------------------------------------------
  1565. void CHammer::UpdateLighting(CMapDoc *pDoc)
  1566. {
  1567. static int lastPercent = -20000;
  1568. int curPercent = -10000;
  1569. IBSPLighting *pLighting = pDoc->GetBSPLighting();
  1570. if ( pLighting )
  1571. {
  1572. // Update 5x / second.
  1573. static DWORD lastTime = 0;
  1574. DWORD curTime = GetTickCount();
  1575. if ( curTime - lastTime < 200 )
  1576. {
  1577. curPercent = lastPercent; // no change
  1578. }
  1579. else
  1580. {
  1581. curPercent = (int)( pLighting->GetPercentComplete() * 10000.0f );
  1582. lastTime = curTime;
  1583. }
  1584. // Redraw the views when new lightmaps are ready.
  1585. if ( pLighting->CheckForNewLightmaps() )
  1586. {
  1587. SetForceRenderNextFrame();
  1588. pDoc->UpdateAllViews( MAPVIEW_UPDATE_ONLY_3D );
  1589. }
  1590. }
  1591. // Update the status text.
  1592. if ( curPercent == -10000 )
  1593. {
  1594. SetStatusText( SBI_LIGHTPROGRESS, "<->" );
  1595. }
  1596. else if( curPercent != lastPercent )
  1597. {
  1598. char str[256];
  1599. sprintf( str, "%.2f%%", curPercent / 100.0f );
  1600. SetStatusText( SBI_LIGHTPROGRESS, str );
  1601. }
  1602. lastPercent = curPercent;
  1603. }
  1604. //-----------------------------------------------------------------------------
  1605. // Purpose: Performs idle processing. Runs the frame and does MFC idle processing.
  1606. // Input : lCount - The number of times OnIdle has been called in succession,
  1607. // indicating the relative length of time the app has been idle without
  1608. // user input.
  1609. // Output : Returns TRUE if there is more idle processing to do, FALSE if not.
  1610. //-----------------------------------------------------------------------------
  1611. BOOL CHammer::OnIdle(LONG lCount)
  1612. {
  1613. UpdatePrefabs();
  1614. CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();
  1615. if (pDoc)
  1616. {
  1617. UpdateLighting(pDoc);
  1618. }
  1619. g_Textures.UpdateFileChangeWatchers();
  1620. UpdateStudioFileChangeWatcher();
  1621. return(CWinApp::OnIdle(lCount));
  1622. }
  1623. //-----------------------------------------------------------------------------
  1624. // Purpose: Renders the realtime views.
  1625. //-----------------------------------------------------------------------------
  1626. void CHammer::RunFrame(void)
  1627. {
  1628. // Note: since hammer may well not even have a 3D window visible
  1629. // at any given time, we have to call into the material system to
  1630. // make it deal with device lost. Usually this happens during SwapBuffers,
  1631. // but we may well not call SwapBuffers at any given moment.
  1632. materials->HandleDeviceLost();
  1633. if (!IsActiveApp())
  1634. {
  1635. Sleep(50);
  1636. }
  1637. #ifdef VPROF_HAMMER
  1638. g_VProfCurrentProfile.MarkFrame();
  1639. #endif
  1640. HammerVGui()->Simulate();
  1641. if ( CMapDoc::GetActiveMapDoc() && !IsClosing() || m_bForceRenderNextFrame )
  1642. HandleLightingPreview();
  1643. // never render without document or when closing down
  1644. // usually only render when active, but not compiling a map unless forced
  1645. if ( CMapDoc::GetActiveMapDoc() && !IsClosing() &&
  1646. ( ( !IsRunningCommands() && IsActiveApp() ) || m_bForceRenderNextFrame ) &&
  1647. CMapDoc::GetActiveMapDoc()->HasInitialUpdate() )
  1648. {
  1649. // get the time
  1650. CMapDoc::GetActiveMapDoc()->UpdateCurrentTime();
  1651. // run any animation
  1652. CMapDoc::GetActiveMapDoc()->UpdateAnimation();
  1653. // redraw the 3d views
  1654. CMapDoc::GetActiveMapDoc()->RenderAllViews();
  1655. }
  1656. // No matter what, we want to keep caching in materials...
  1657. if ( IsActiveApp() )
  1658. {
  1659. g_Textures.LazyLoadTextures();
  1660. }
  1661. m_bForceRenderNextFrame = false;
  1662. }
  1663. //-----------------------------------------------------------------------------
  1664. // Purpose: Overloaded Run so that we can control the frameratefor realtime
  1665. // rendering in the 3D view.
  1666. // Output : As MFC CWinApp::Run.
  1667. //-----------------------------------------------------------------------------
  1668. int CHammer::Run(void)
  1669. {
  1670. Assert(0);
  1671. return 0;
  1672. }
  1673. //-----------------------------------------------------------------------------
  1674. // Purpose: Returns true if the editor is the active app, false if not.
  1675. //-----------------------------------------------------------------------------
  1676. bool CHammer::IsActiveApp(void)
  1677. {
  1678. return m_bActiveApp;
  1679. }
  1680. //-----------------------------------------------------------------------------
  1681. // Purpose: Called from CMainFrame::OnSysCommand, this informs the app when it
  1682. // is minimized and unminimized. This allows us to stop rendering the
  1683. // 3D view when we are minimized.
  1684. // Input : bMinimized - TRUE when minmized, FALSE otherwise.
  1685. //-----------------------------------------------------------------------------
  1686. void CHammer::OnActivateApp(bool bActive)
  1687. {
  1688. // static int nCount = 0;
  1689. // if (bActive)
  1690. // DBG("ON %d\n", nCount);
  1691. // else
  1692. // DBG("OFF %d\n", nCount);
  1693. // nCount++;
  1694. m_bActiveApp = bActive;
  1695. }
  1696. //-----------------------------------------------------------------------------
  1697. // Purpose: Called from the shell to relinquish our video memory in favor of the
  1698. // engine. This is called by the engine when it starts up.
  1699. //-----------------------------------------------------------------------------
  1700. void CHammer::ReleaseVideoMemory()
  1701. {
  1702. POSITION pos = GetFirstDocTemplatePosition();
  1703. while (pos)
  1704. {
  1705. CDocTemplate* pTemplate = (CDocTemplate*)GetNextDocTemplate(pos);
  1706. POSITION pos2 = pTemplate->GetFirstDocPosition();
  1707. while (pos2)
  1708. {
  1709. CDocument * pDocument;
  1710. if ((pDocument=pTemplate->GetNextDoc(pos2)) != NULL)
  1711. {
  1712. static_cast<CMapDoc*>(pDocument)->ReleaseVideoMemory();
  1713. }
  1714. }
  1715. }
  1716. }
  1717. void CHammer::SuppressVideoAllocation( bool bSuppress )
  1718. {
  1719. m_SuppressVideoAllocation = bSuppress;
  1720. }
  1721. bool CHammer::CanAllocateVideo() const
  1722. {
  1723. return !m_SuppressVideoAllocation;
  1724. }
  1725. //-------------------------------------------------------------------------------
  1726. // Purpose: Runs through the autosave directory and fills the autosave map.
  1727. // Also sets the total amount of space used by the directory.
  1728. // Input : pFileMap - CUtlMap that will hold the list of files in the dir
  1729. // pdwTotalDirSize - pointer to the DWORD that will hold directory size
  1730. // pstrMapTitle - the name of the current map to be saved
  1731. // Output : returns an int containing the next number to use for the autosave
  1732. //-------------------------------------------------------------------------------
  1733. int CHammer::GetNextAutosaveNumber( CUtlMap<FILETIME, WIN32_FIND_DATA, int> *pFileMap, DWORD *pdwTotalDirSize, const CString *pstrMapTitle ) const
  1734. {
  1735. FILETIME oldestAutosaveTime;
  1736. oldestAutosaveTime.dwHighDateTime = 0;
  1737. oldestAutosaveTime.dwLowDateTime = 0;
  1738. char szRootDir[MAX_PATH];
  1739. APP()->GetDirectory(DIR_AUTOSAVE, szRootDir);
  1740. CString strAutosaveDirectory( szRootDir );
  1741. int nNumberActualAutosaves = 0;
  1742. int nCurrentAutosaveNumber = 1;
  1743. int nOldestAutosaveNumber = 1;
  1744. int nExpectedNextAutosaveNumber = 1;
  1745. int nLastHole = 0;
  1746. int nMaxAutosavesPerMap = Options.general.iMaxAutosavesPerMap;
  1747. WIN32_FIND_DATA fileData;
  1748. HANDLE hFile;
  1749. DWORD dwTotalAutosaveDirectorySize = 0;
  1750. hFile = FindFirstFile( strAutosaveDirectory + "*.vmf_autosave", &fileData );
  1751. if ( hFile != INVALID_HANDLE_VALUE )
  1752. {
  1753. //go through and for each file check to see if it is an autosave for this map; also keep track of total file size
  1754. //for directory.
  1755. while( GetLastError() != ERROR_NO_MORE_FILES && hFile != INVALID_HANDLE_VALUE )
  1756. {
  1757. (*pFileMap).Insert( fileData.ftLastAccessTime, fileData );
  1758. DWORD dwFileSize = fileData.nFileSizeLow;
  1759. dwTotalAutosaveDirectorySize += dwFileSize;
  1760. FILETIME fileAccessTime = fileData.ftLastAccessTime;
  1761. CString currentFilename( fileData.cFileName );
  1762. //every autosave file ends in three digits; this code separates the name from the digits
  1763. CString strMapName = currentFilename.Left( currentFilename.GetLength() - 17 );
  1764. CString strCurrentNumber = currentFilename.Mid( currentFilename.GetLength() - 16, 3 );
  1765. int nMapNumber = atoi( (char *)strCurrentNumber.GetBuffer() );
  1766. if ( strMapName.CompareNoCase( (*pstrMapTitle) ) == 0 )
  1767. {
  1768. //keep track of real number of autosaves with map name; deals with instance where older maps get deleted
  1769. //and create sequence holes in autosave map names.
  1770. nNumberActualAutosaves++;
  1771. if ( oldestAutosaveTime.dwLowDateTime == 0 )
  1772. {
  1773. //the first file is automatically the oldest
  1774. oldestAutosaveTime = fileAccessTime;
  1775. }
  1776. if ( nMapNumber != nExpectedNextAutosaveNumber )
  1777. {
  1778. //the current map number is different than what was expected
  1779. //there is a hole in the sequence
  1780. nLastHole = nMapNumber;
  1781. }
  1782. nExpectedNextAutosaveNumber = nMapNumber + 1;
  1783. if ( nExpectedNextAutosaveNumber > 999 )
  1784. {
  1785. nExpectedNextAutosaveNumber = 1;
  1786. }
  1787. if ( CompareFileTime( &fileAccessTime, &oldestAutosaveTime ) == -1 )
  1788. {
  1789. //this file is older than previous oldest file
  1790. oldestAutosaveTime = fileAccessTime;
  1791. nOldestAutosaveNumber = nMapNumber;
  1792. }
  1793. }
  1794. FindNextFile(hFile, &fileData);
  1795. }
  1796. FindClose(hFile);
  1797. }
  1798. if ( nNumberActualAutosaves < nMaxAutosavesPerMap )
  1799. {
  1800. //there are less autosaves than wanted for the map; use the larger
  1801. //of the next expected or the last found hole as the number.
  1802. nCurrentAutosaveNumber = max( nExpectedNextAutosaveNumber, nLastHole );
  1803. }
  1804. else
  1805. {
  1806. //there are no holes, use the oldest.
  1807. nCurrentAutosaveNumber = nOldestAutosaveNumber;
  1808. }
  1809. *pdwTotalDirSize = dwTotalAutosaveDirectorySize;
  1810. return nCurrentAutosaveNumber;
  1811. }
  1812. static bool LessFunc( const FILETIME &lhs, const FILETIME &rhs )
  1813. {
  1814. return CompareFileTime(&lhs, &rhs) < 0;
  1815. }
  1816. //-----------------------------------------------------------------------------
  1817. // Purpose: This is called when the autosave timer goes off. It checks to
  1818. // make sure the document has changed and handles deletion of old
  1819. // files when the total directory size is too big
  1820. //-----------------------------------------------------------------------------
  1821. void CHammer::Autosave( void )
  1822. {
  1823. if ( !Options.general.bEnableAutosave )
  1824. {
  1825. return;
  1826. }
  1827. if ( VerifyAutosaveDirectory() != true )
  1828. {
  1829. Options.general.bEnableAutosave = false;
  1830. return;
  1831. }
  1832. CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();
  1833. //value from options is in megs
  1834. DWORD dwMaxAutosaveSpace = Options.general.iMaxAutosaveSpace * 1024 * 1024;
  1835. CUtlMap<FILETIME, WIN32_FIND_DATA, int> autosaveFiles;
  1836. autosaveFiles.SetLessFunc( LessFunc );
  1837. if ( pDoc && pDoc->NeedsAutosave() )
  1838. {
  1839. char szRootDir[MAX_PATH];
  1840. APP()->GetDirectory(DIR_AUTOSAVE, szRootDir);
  1841. CString strAutosaveDirectory( szRootDir );
  1842. //expand the path if $SteamUserDir etc are used for SDK users
  1843. EditorUtil_ConvertPath(strAutosaveDirectory, true);
  1844. CString strExtension = ".vmf";
  1845. //this will hold the name of the map w/o leading directory info or file extension
  1846. CString strMapTitle;
  1847. //full path of map file
  1848. CString strMapFilename = pDoc->GetPathName();
  1849. DWORD dwTotalAutosaveDirectorySize = 0;
  1850. int nCurrentAutosaveNumber = 0;
  1851. // the map hasn't been saved before and doesn't have a filename; using default: 'autosave'
  1852. if ( strMapFilename.IsEmpty() )
  1853. {
  1854. strMapTitle = "autosave";
  1855. }
  1856. // the map already has a filename
  1857. else
  1858. {
  1859. int nFilenameBeginOffset = strMapFilename.ReverseFind( '\\' ) + 1;
  1860. int nFilenameEndOffset = strMapFilename.Find( '.' );
  1861. //get the filename of the map, between the leading '\' and the '.'
  1862. strMapTitle = strMapFilename.Mid( nFilenameBeginOffset, nFilenameEndOffset - nFilenameBeginOffset );
  1863. }
  1864. nCurrentAutosaveNumber = GetNextAutosaveNumber( &autosaveFiles, &dwTotalAutosaveDirectorySize, &strMapTitle );
  1865. //creating the proper suffix for the autosave file
  1866. char szNumberChars[4];
  1867. CString strAutosaveString = itoa( nCurrentAutosaveNumber, szNumberChars, 10 );
  1868. CString strAutosaveNumber = "000";
  1869. strAutosaveNumber += strAutosaveString;
  1870. strAutosaveNumber = strAutosaveNumber.Right( 3 );
  1871. strAutosaveNumber = "_" + strAutosaveNumber;
  1872. CString strSaveName = strAutosaveDirectory + strMapTitle + strAutosaveNumber + strExtension + "_autosave";
  1873. pDoc->SaveVMF( (char *)strSaveName.GetBuffer(), SAVEFLAGS_AUTOSAVE );
  1874. //don't autosave again unless they make changes
  1875. pDoc->SetAutosaveFlag( FALSE );
  1876. //if there is too much space used for autosaves, delete the oldest file until the size is acceptable
  1877. while( dwTotalAutosaveDirectorySize > dwMaxAutosaveSpace )
  1878. {
  1879. int nFirstElementIndex = autosaveFiles.FirstInorder();
  1880. if ( !autosaveFiles.IsValidIndex( nFirstElementIndex ) )
  1881. {
  1882. Assert( false );
  1883. break;
  1884. }
  1885. WIN32_FIND_DATA fileData = autosaveFiles.Element( nFirstElementIndex );
  1886. DWORD dwOldestFileSize = fileData.nFileSizeLow;
  1887. char filename[MAX_PATH];
  1888. strcpy( filename, fileData.cFileName );
  1889. DeleteFile( strAutosaveDirectory + filename );
  1890. dwTotalAutosaveDirectorySize -= dwOldestFileSize;
  1891. autosaveFiles.RemoveAt( nFirstElementIndex );
  1892. }
  1893. autosaveFiles.RemoveAll();
  1894. }
  1895. }
  1896. //-----------------------------------------------------------------------------
  1897. // Purpose: Verifies that the autosave directory exists and attempts to create it if
  1898. // it doesn't. Also returns various failure errors.
  1899. // This function is now called at two different times: immediately after a new
  1900. // directory is entered in the options screen and during every autosave call.
  1901. // If called with a directory, the input directory is checked for correctness.
  1902. // Otherwise, the system directory DIR_AUTOSAVE is checked
  1903. //-----------------------------------------------------------------------------
  1904. bool CHammer::VerifyAutosaveDirectory( char *szAutosaveDirectory ) const
  1905. {
  1906. HANDLE hDir;
  1907. HANDLE hTestFile;
  1908. char szRootDir[MAX_PATH];
  1909. if ( szAutosaveDirectory )
  1910. {
  1911. strcpy( szRootDir, szAutosaveDirectory );
  1912. EnsureTrailingBackslash( szRootDir );
  1913. }
  1914. else
  1915. {
  1916. APP()->GetDirectory(DIR_AUTOSAVE, szRootDir);
  1917. }
  1918. if ( szRootDir[0] == 0 )
  1919. {
  1920. AfxMessageBox( "No autosave directory has been selected.\nThe autosave feature will be disabled until a directory is entered.", MB_OK );
  1921. return false;
  1922. }
  1923. CString strAutosaveDirectory( szRootDir );
  1924. {
  1925. EditorUtil_ConvertPath(strAutosaveDirectory, true);
  1926. if ( ( strAutosaveDirectory[1] != ':' ) || ( strAutosaveDirectory[2] != '\\' ) )
  1927. {
  1928. 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 );
  1929. return false;
  1930. }
  1931. }
  1932. hDir = CreateFile (
  1933. strAutosaveDirectory,
  1934. GENERIC_READ,
  1935. FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
  1936. NULL,
  1937. OPEN_EXISTING,
  1938. FILE_FLAG_BACKUP_SEMANTICS,
  1939. NULL
  1940. );
  1941. if ( hDir == INVALID_HANDLE_VALUE )
  1942. {
  1943. bool bDirResult = CreateDirectory( strAutosaveDirectory, NULL );
  1944. if ( !bDirResult )
  1945. {
  1946. 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 );
  1947. return false;
  1948. }
  1949. }
  1950. else
  1951. {
  1952. CloseHandle( hDir );
  1953. hTestFile = CreateFile( strAutosaveDirectory + "test.txt",
  1954. GENERIC_READ,
  1955. FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
  1956. NULL,
  1957. CREATE_NEW,
  1958. FILE_FLAG_BACKUP_SEMANTICS,
  1959. NULL
  1960. );
  1961. if ( hTestFile == INVALID_HANDLE_VALUE )
  1962. {
  1963. if ( GetLastError() == ERROR_ACCESS_DENIED )
  1964. {
  1965. 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 );
  1966. return false;
  1967. }
  1968. else
  1969. {
  1970. 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 );
  1971. return false;
  1972. }
  1973. }
  1974. CloseHandle( hTestFile );
  1975. DeleteFile( strAutosaveDirectory + "test.txt" );
  1976. }
  1977. return true;
  1978. }
  1979. //-----------------------------------------------------------------------------
  1980. // Purpose: Called when Hammer starts after a crash. It loads the newest
  1981. // autosave file after Hammer has initialized.
  1982. //-----------------------------------------------------------------------------
  1983. void CHammer::LoadLastGoodSave( void )
  1984. {
  1985. CString strLastGoodSave = APP()->GetProfileString("General", "Last Good Save", "");
  1986. char szMapDir[MAX_PATH];
  1987. strcpy(szMapDir, g_pGameConfig->szMapDir);
  1988. CDocument *pCurrentDoc;
  1989. if ( !strLastGoodSave.IsEmpty() )
  1990. {
  1991. pCurrentDoc = APP()->OpenDocumentFile( strLastGoodSave );
  1992. if ( !pCurrentDoc )
  1993. {
  1994. AfxMessageBox( "There was an error loading the last saved file.", MB_OK );
  1995. return;
  1996. }
  1997. char szAutoSaveDir[MAX_PATH];
  1998. APP()->GetDirectory(DIR_AUTOSAVE, szAutoSaveDir);
  1999. if ( !((CMapDoc *)pCurrentDoc)->IsAutosave() && Q_stristr( pCurrentDoc->GetPathName(), szAutoSaveDir ) )
  2000. {
  2001. //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
  2002. //It assumes the file should go into the gameConfig map directory
  2003. char szRenameMessage[MAX_PATH+MAX_PATH+256];
  2004. char szLastSaveCopy[MAX_PATH];
  2005. Q_strcpy( szLastSaveCopy, strLastGoodSave );
  2006. char *pszFileName = Q_strrchr( strLastGoodSave, '\\') + 1;
  2007. char *pszFileNameEnd = Q_strrchr( strLastGoodSave, '_');
  2008. if ( !pszFileNameEnd )
  2009. {
  2010. pszFileNameEnd = Q_strrchr( strLastGoodSave, '.');
  2011. }
  2012. strcpy( pszFileNameEnd, ".vmf" );
  2013. CString newMapPath( szMapDir );
  2014. newMapPath.Append( "\\" );
  2015. newMapPath.Append( pszFileName );
  2016. 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 );
  2017. if ( AfxMessageBox( szRenameMessage, MB_YESNO ) == IDYES )
  2018. {
  2019. pCurrentDoc->SetPathName( newMapPath );
  2020. }
  2021. }
  2022. }
  2023. }
  2024. //-----------------------------------------------------------------------------
  2025. // Purpose: Called from the General Options dialog when the autosave timer or
  2026. // directory values have changed.
  2027. //-----------------------------------------------------------------------------
  2028. void CHammer::ResetAutosaveTimer()
  2029. {
  2030. Options.general.bEnableAutosave = true;
  2031. CMainFrame *pMainWnd = ::GetMainWnd();
  2032. if ( pMainWnd )
  2033. {
  2034. pMainWnd->ResetAutosaveTimer();
  2035. }
  2036. }