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