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.
2866 lines
78 KiB
2866 lines
78 KiB
//===== Copyright © 1996-2005, 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 "materialsystem/imaterialsystemhardwareconfig.h"
|
|
#include "istudiorender.h"
|
|
#include "FileSystem.h"
|
|
#include "engine_launcher_api.h"
|
|
#include "filesystem_init.h"
|
|
#include "utlmap.h"
|
|
#include "utlvector.h"
|
|
#include "progdlg.h"
|
|
#include "MapWorld.h"
|
|
#include "HammerVGui.h"
|
|
#include "vgui_controls/Controls.h"
|
|
#include "lpreview_thread.h"
|
|
#include "SteamWriteMiniDump.h"
|
|
#include "inputsystem/iinputsystem.h"
|
|
#include "datacache/idatacache.h"
|
|
#include "steam/steam_api.h"
|
|
#include "toolframework/ienginetool.h"
|
|
#include "toolutils/enginetools_int.h"
|
|
#include "objectproperties.h"
|
|
#include "particles/particles.h"
|
|
#include "p4lib/ip4.h"
|
|
#include "syncfiledialog.h"
|
|
#include "vstdlib/jobthread.h"
|
|
#include "gridnav.h"
|
|
#include "tablet.h"
|
|
#include "dialogwithcheckbox.h"
|
|
#include "configmanager.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;
|
|
}
|
|
|
|
|
|
int WrapFunctionWithMinidumpHandler( int (*pfn)(void *pParam), void *pParam, int errorRetVal )
|
|
{
|
|
int nRetVal;
|
|
|
|
if ( !Plat_IsInDebugSession() && !CommandLine()->FindParm( "-nominidumps") )
|
|
{
|
|
_set_se_translator( SteamWriteMiniDumpUsingExceptionInfo );
|
|
|
|
try // this try block allows the SE translator to work
|
|
{
|
|
nRetVal = pfn( pParam );
|
|
}
|
|
catch( ... )
|
|
{
|
|
return errorRetVal;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nRetVal = pfn( pParam );
|
|
}
|
|
|
|
return nRetVal;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Logging listener so that Hammer can capture warning and error
|
|
// output to write to the message window.
|
|
//-----------------------------------------------------------------------------
|
|
class CHammerMessageLoggingListener : public ILoggingListener
|
|
{
|
|
public:
|
|
virtual void Log( const LoggingContext_t *pContext, const tchar *pMessage )
|
|
{
|
|
if ( pContext->m_Severity == LS_ERROR )
|
|
{
|
|
Msg( mwError, pMessage );
|
|
}
|
|
else if ( pContext->m_Severity == LS_WARNING )
|
|
{
|
|
Msg( mwWarning, pMessage );
|
|
}
|
|
}
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Outputs a formatted debug string.
|
|
// Input : fmt - format specifier.
|
|
// ... - arguments to format.
|
|
//-----------------------------------------------------------------------------
|
|
void DBG(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);
|
|
}
|
|
|
|
//
|
|
// Post-init and pre-shutdown routines management
|
|
//
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
template < typename T, int Instance >
|
|
static T& ReliableStaticStorage()
|
|
{
|
|
Instance;
|
|
static T storage;
|
|
return storage;
|
|
}
|
|
|
|
enum Storage_t
|
|
{
|
|
APP_FN_POST_INIT,
|
|
APP_FN_PRE_SHUTDOWN,
|
|
APP_FN_MESSAGE_LOOP,
|
|
APP_FN_MESSAGE_PRETRANSLATE
|
|
};
|
|
|
|
#define s_appRegisteredPostInitFns ReliableStaticStorage< CUtlVector< void (*)() >, APP_FN_POST_INIT >()
|
|
#define s_appRegisteredPreShutdownFns ReliableStaticStorage< CUtlVector< void (*)() >, APP_FN_PRE_SHUTDOWN >()
|
|
#define s_appRegisteredMessageLoop ReliableStaticStorage< CUtlVector< void (*)() >, APP_FN_MESSAGE_LOOP >()
|
|
#define s_appRegisteredMessagePreTrans ReliableStaticStorage< CUtlVector< void (*)( MSG * ) >, APP_FN_MESSAGE_PRETRANSLATE >()
|
|
|
|
void AppRegisterPostInitFn( void (*fn)() )
|
|
{
|
|
s_appRegisteredPostInitFns.AddToTail( fn );
|
|
}
|
|
|
|
void AppRegisterMessageLoopFn( void (*fn)() )
|
|
{
|
|
s_appRegisteredMessageLoop.AddToTail( fn );
|
|
}
|
|
|
|
void AppRegisterMessagePretranslateFn( void (*fn)( MSG * ) )
|
|
{
|
|
s_appRegisteredMessagePreTrans.AddToTail( fn );
|
|
}
|
|
|
|
void AppRegisterPreShutdownFn( void (*fn)() )
|
|
{
|
|
s_appRegisteredPreShutdownFns.AddToTail( fn );
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
m_bFoundryMode = false;
|
|
m_CustomAcceleratorWindow = NULL;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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;
|
|
|
|
WinTab_Init();
|
|
|
|
// 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();
|
|
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();
|
|
|
|
if ( g_pThreadPool )
|
|
{
|
|
ThreadPoolStartParams_t startParams;
|
|
// this will set ideal processor on each thread
|
|
startParams.fDistribute = TRS_TRUE;
|
|
|
|
g_pThreadPool->Start( startParams );
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
void CHammer::InitFoundryMode( CreateInterfaceFn factory, void *hGameWnd, const char *szGameDir )
|
|
{
|
|
m_bFoundryMode = true;
|
|
|
|
if ( !CommandLine()->FindParm( "-foundrymode" ) )
|
|
Error( "Running in Foundry requires -FoundryMode on the command line." );
|
|
|
|
if ( !Connect( factory ) )
|
|
Error( "CHammer::Connect failed" );
|
|
|
|
if ( !InitSessionGameConfig( szGameDir ) )
|
|
Error( "InitSessionGameConfig failed." );
|
|
|
|
if ( HammerInternalInit() != INIT_OK )
|
|
Error( "HammerInternalInit failed" );
|
|
}
|
|
|
|
|
|
void CHammer::NoteEngineGotFocus()
|
|
{
|
|
// Release focus on all our vgui stuff so the engine can own it.
|
|
HammerVGui()->SetFocus( NULL );
|
|
|
|
// Deactivate all CMapViews.
|
|
CMapDoc::NoteEngineGotFocus();
|
|
}
|
|
|
|
|
|
bool CHammer::IsHammerVisible()
|
|
{
|
|
CWnd *pWnd = GetMainWnd();
|
|
if ( !pWnd )
|
|
return false;
|
|
|
|
return pWnd->IsWindowVisible() ? true : false;
|
|
}
|
|
|
|
|
|
void CHammer::ToggleHammerVisible()
|
|
{
|
|
CWnd *pWnd = GetMainWnd();
|
|
if ( !pWnd )
|
|
return;
|
|
|
|
if ( pWnd->IsWindowVisible() )
|
|
{
|
|
pWnd->ShowWindow( SW_HIDE );
|
|
}
|
|
else
|
|
{
|
|
pWnd->ShowWindow( SW_SHOW );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Return true if the message just dispatched should cause OnIdle to run.
|
|
//
|
|
// Return false for messages which do not usually affect the state of the user
|
|
// interface and happen very often.
|
|
//-----------------------------------------------------------------------------
|
|
bool CHammer::HammerIsIdleMessage(MSG *pMsg)
|
|
{
|
|
AFX_MANAGE_STATE(AfxGetStaticModuleState());
|
|
|
|
// We generate lots of WM_TIMER messages and shouldn't call OnIdle because of them.
|
|
// This fixes tool tips not popping up when a map is open.
|
|
if ( pMsg->message == WM_TIMER )
|
|
return false;
|
|
|
|
return ( IsIdleMessage(pMsg) == TRUE );
|
|
}
|
|
|
|
// 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, g_pGameConfig->m_szPrefabDir);
|
|
|
|
if (*p == '\0')
|
|
{
|
|
// The prefab folder has not been set up so quietly set it to the app directory + "/Prefabs"
|
|
strcpy(p, m_szAppDir);
|
|
EnsureTrailingBackslash(p);
|
|
strcat(p, "Prefabs");
|
|
strcpy( g_pGameConfig->m_szPrefabDir, p );
|
|
}
|
|
|
|
//
|
|
// Make sure the prefabs folder exists. If not, create it
|
|
//
|
|
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 CSimpleWindowsLoggingListener s_SimpleWindowsLoggingListener;
|
|
static CHammerMessageLoggingListener s_HammerMessageLoggingListener;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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, INIT_FAILED );
|
|
}
|
|
|
|
|
|
int CHammer::StaticHammerInternalInit( void *pParam )
|
|
{
|
|
return (int)((CHammer*)pParam)->HammerInternalInit();
|
|
}
|
|
|
|
void HammerFileSystem_ReportSearchPath( const char *szPathID )
|
|
{
|
|
char szSearchPath[ 4096 ];
|
|
g_pFullFileSystem->GetSearchPath( szPathID, true, szSearchPath, sizeof( szSearchPath ) );
|
|
|
|
Msg( mwStatus, "------------------------------------------------------------------" );
|
|
|
|
char *pszOnePath = strtok( szSearchPath, ";" );
|
|
while ( pszOnePath )
|
|
{
|
|
Msg( mwStatus, "Search Path (%s): %s", szPathID, pszOnePath );
|
|
pszOnePath = strtok( NULL, ";" );
|
|
}
|
|
}
|
|
|
|
InitReturnVal_t CHammer::HammerInternalInit()
|
|
{
|
|
if ( !IsFoundryMode() )
|
|
{
|
|
LoggingSystem_PushLoggingState();
|
|
LoggingSystem_RegisterLoggingListener( &s_SimpleWindowsLoggingListener );
|
|
LoggingSystem_RegisterLoggingListener( &s_HammerMessageLoggingListener );
|
|
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);
|
|
|
|
//
|
|
// load cmd sequences - different from options because
|
|
// users might want to share (darn registry)
|
|
//
|
|
if ( !LoadSequences( "CmdSeq.wc" ) )
|
|
{
|
|
// Try to load the default sequences if there are no user-defined ones.
|
|
LoadSequences( "CmdSeqDefault.wc" );
|
|
}
|
|
|
|
// 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;
|
|
|
|
// try to init VGUI
|
|
HammerVGui()->Init( m_pMainWnd->GetSafeHwnd() );
|
|
|
|
// The main window has been initialized, so show and update it.
|
|
if ( IsFoundryMode() )
|
|
{
|
|
m_nCmdShow = SW_SHOW;
|
|
|
|
CRect rcDesktop;
|
|
GetWindowRect( GetDesktopWindow(), &rcDesktop );
|
|
|
|
CRect rcEngineWnd;
|
|
HWND hEngineWnd = (HWND)enginetools->GetEngineHwnd();
|
|
GetWindowRect( hEngineWnd, &rcEngineWnd );
|
|
|
|
// Move the engine to the right side of the screen.
|
|
int nEngineWndX = rcDesktop.Width() - rcEngineWnd.Width();
|
|
SetWindowPos( hEngineWnd, NULL, nEngineWndX, 0, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW );
|
|
|
|
// Move Hammer to the left and make it square.
|
|
int nHammerWndWidth = nEngineWndX;
|
|
int nHammerWndHeight = min( nHammerWndWidth, rcDesktop.Height() - 100 );
|
|
pMainFrame->SetWindowPos( NULL, 0, 0, nHammerWndWidth, nHammerWndHeight, SWP_NOZORDER | SWP_SHOWWINDOW );
|
|
|
|
// Move the properties dialog below the engine window.
|
|
pMainFrame->pObjectProperties->SetWindowPos( NULL, nEngineWndX, rcEngineWnd.Height(), 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW );
|
|
}
|
|
else
|
|
{
|
|
m_nCmdShow = SW_SHOWMAXIMIZED;
|
|
pMainFrame->ShowWindow(m_nCmdShow);
|
|
}
|
|
|
|
pMainFrame->UpdateWindow();
|
|
|
|
|
|
if ( !IsFoundryMode() )
|
|
{
|
|
//
|
|
// Init the game and mod dirs in the file system.
|
|
// This needs to happen before calling Init on the material system.
|
|
//
|
|
CFSSearchPathsInit initInfo;
|
|
initInfo.m_pFileSystem = g_pFullFileSystem;
|
|
initInfo.m_pDirectoryName = g_pGameConfig->m_szModDir;
|
|
if ( !initInfo.m_pDirectoryName[0] )
|
|
{
|
|
static char pTempBuf[MAX_PATH];
|
|
APP()->GetDirectory(DIR_PROGRAM, pTempBuf);
|
|
strcat( pTempBuf, "..\\hl2" );
|
|
initInfo.m_pDirectoryName = pTempBuf;
|
|
}
|
|
|
|
CSplashWnd::ShowSplashScreen(pMainFrame);
|
|
|
|
if ( FileSystem_LoadSearchPaths( initInfo ) != FS_OK )
|
|
{
|
|
Error( "Unable to load search paths!\n" );
|
|
}
|
|
|
|
// Report this for the user's sake
|
|
HammerFileSystem_ReportSearchPath( "GAME" );
|
|
}
|
|
|
|
// Now that we've initialized the file system, we can parse this config's gameinfo.txt for the additional settings there.
|
|
g_pGameConfig->ParseGameInfo();
|
|
|
|
if ( !IsFoundryMode() )
|
|
{
|
|
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);
|
|
}
|
|
|
|
//
|
|
// Initialize the particle system manager
|
|
//
|
|
g_pParticleSystemMgr->Init( NULL, true );
|
|
g_pParticleSystemMgr->AddBuiltinSimulationOperators();
|
|
g_pParticleSystemMgr->AddBuiltinRenderingOperators();
|
|
|
|
// 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 ( !IsFoundryMode() && 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
|
|
|
|
// Execute the post-init registered callbacks
|
|
for ( int iFn = 0; iFn < s_appRegisteredPostInitFns.Count(); ++ iFn )
|
|
{
|
|
void (*fn)() = s_appRegisteredPostInitFns[ iFn ];
|
|
(*fn)();
|
|
}
|
|
|
|
WinTab_Open( m_pMainWnd->m_hWnd );
|
|
|
|
CSplashWnd::HideSplashScreen();
|
|
|
|
// create the lighting preview thread
|
|
g_LPreviewThread = CreateSimpleThread( LightingPreviewThreadFN, 0 );
|
|
|
|
return INIT_OK;
|
|
}
|
|
|
|
int CHammer::MainLoop()
|
|
{
|
|
return WrapFunctionWithMinidumpHandler( StaticInternalMainLoop, this, -1 );
|
|
}
|
|
|
|
|
|
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();
|
|
|
|
// Do idle processing at most once per frame, incrementing the counter until we get an
|
|
// idle message. The counter is used as a general indication of how idle the application is,
|
|
// so critical idle processing is done for lower values of lIdleCount, and less important
|
|
// stuff is done as lIdleCount increases.
|
|
//
|
|
// When there's no more idle work to do, OnIdle returns false and we stop doing idle
|
|
// processing until another idle message is processed by the message loop.
|
|
if ( bIdle && !HammerOnIdle(lIdleCount++) )
|
|
{
|
|
bIdle = false; // done with idle work for now
|
|
}
|
|
|
|
// Execute the message loop registered callbacks
|
|
for ( int iFn = 0; iFn < s_appRegisteredMessageLoop.Count(); ++ iFn )
|
|
{
|
|
void (*fn)() = s_appRegisteredMessageLoop[ iFn ];
|
|
(*fn)();
|
|
}
|
|
|
|
//
|
|
// Pump messages until the message queue is empty.
|
|
//
|
|
while (::PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
|
|
{
|
|
if ( msg.message == WM_QUIT )
|
|
return 1;
|
|
|
|
// Pump the message through a custom message
|
|
// pre-translation chain
|
|
for ( int iFn = 0; iFn < s_appRegisteredMessagePreTrans.Count(); ++ iFn )
|
|
{
|
|
void (*fn)( MSG * ) = s_appRegisteredMessagePreTrans[ iFn ];
|
|
(*fn)( &msg );
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Execute the pre-shutdown registered callbacks
|
|
for ( int iFn = s_appRegisteredPreShutdownFns.Count(); iFn --> 0 ; )
|
|
{
|
|
void (*fn)() = s_appRegisteredPreShutdownFns[ iFn ];
|
|
(*fn)();
|
|
}
|
|
|
|
#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 ( !IsFoundryMode() )
|
|
{
|
|
LoggingSystem_PopLoggingState();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
void CHammer::SetCustomAccelerator( HWND hWnd, WORD nID )
|
|
{
|
|
m_CustomAcceleratorWindow = hWnd;
|
|
m_CustomAccelerator = ::LoadAccelerators( AfxGetInstanceHandle(), MAKEINTRESOURCE( nID ) );
|
|
}
|
|
|
|
|
|
void CHammer::ClearCustomAccelerator( )
|
|
{
|
|
m_CustomAcceleratorWindow = NULL;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// 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)
|
|
{
|
|
// if there is no initial directory use the one specified in the game configuration
|
|
static char szInitialDir[MAX_PATH] = "";
|
|
if (szInitialDir[0] == '\0')
|
|
{
|
|
strcpy(szInitialDir, g_pGameConfig->szMapDir);
|
|
}
|
|
|
|
OPENFILENAME ofn;
|
|
|
|
//memory buffer to contain the file name
|
|
char szFileNameBuffer[MAX_PATH];
|
|
|
|
ZeroMemory( &ofn , sizeof( ofn));
|
|
ofn.lStructSize = sizeof ( ofn );
|
|
ofn.hwndOwner = AfxGetMainWnd()->GetSafeHwnd();
|
|
ofn.lpstrFile = szFileNameBuffer;
|
|
ofn.lpstrFile[0] = '\0';
|
|
ofn.nMaxFile = sizeof( szFileNameBuffer );
|
|
ofn.lpstrFilter = "Valve Map Files (*.vmf;*.vmm)\0*.vmf;*.vmm\0Valve Map Files Autosave (*.vmf_autosave)\0*.vmf_autosave\0Worldcraft RMFs (*.rmf)\0*.rmf\0Worldcraft Maps (*.map)\0*.map\0All\0*.*\0";
|
|
ofn.nFilterIndex =1;
|
|
ofn.lpstrFileTitle = NULL ;
|
|
ofn.nMaxFileTitle = 0 ;
|
|
ofn.lpstrInitialDir=szInitialDir;
|
|
ofn.Flags = OFN_LONGNAMES | OFN_HIDEREADONLY | OFN_NOCHANGEDIR;
|
|
|
|
// if the user cancels or closes the Open dialog box or an error occurs, the return value is zero.
|
|
if (!GetOpenFileName( &ofn ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Get the directory they browsed to for next time.
|
|
//
|
|
CString str = ofn.lpstrFile;
|
|
int nSlash = str.ReverseFind('\\');
|
|
if (nSlash != -1)
|
|
{
|
|
strcpy(szInitialDir, str.Left(nSlash));
|
|
}
|
|
|
|
|
|
// add the appropriate extension (based on filter type) if it was unspecified by the user
|
|
if (str.Find('.') == -1)
|
|
{
|
|
switch (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);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// This is the generic file open function that is called by the framework.
|
|
//-----------------------------------------------------------------------------
|
|
CDocument *CHammer::OpenDocumentFile(LPCTSTR lpszFileName)
|
|
{
|
|
CDocument *pDoc = OpenDocumentOrInstanceFile( lpszFileName );
|
|
|
|
// Do work that needs to happen after opening all instances here.
|
|
// NOTE: Make sure this work doesn't need to happen per instance!!!
|
|
|
|
return pDoc;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
CDocument *CHammer::OpenDocumentOrInstanceFile(LPCTSTR lpszFileName)
|
|
{
|
|
// CWinApp::OnOpenRecentFile may get its file history cycled through by instances being opened, thus the pointer becomes invalid
|
|
CString SaveFileName = lpszFileName;
|
|
|
|
if(GetFileAttributes( SaveFileName ) == 0xFFFFFFFF)
|
|
{
|
|
CString Message;
|
|
|
|
Message = "The file " + SaveFileName + " does not exist.";
|
|
AfxMessageBox( Message );
|
|
|
|
return NULL;
|
|
}
|
|
|
|
CheckForFileSync( SaveFileName, CHammer::m_bIsNewDocumentVisible );
|
|
|
|
CDocument *pDoc = m_pDocManager->OpenDocumentFile( SaveFileName );
|
|
CMapDoc *pMapDoc = dynamic_cast< CMapDoc * >( pDoc );
|
|
|
|
if ( pMapDoc )
|
|
{
|
|
CMapDoc::SetActiveMapDoc( pMapDoc );
|
|
pMapDoc->CheckFileStatus();
|
|
}
|
|
|
|
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.", SaveFileName, newMapPath );
|
|
|
|
if ( AfxMessageBox( szRenameMessage, MB_ICONHAND | MB_YESNO ) == IDYES )
|
|
{
|
|
((CMapDoc *)pDoc)->SetPathName( newMapPath );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( CHammer::m_bIsNewDocumentVisible == true )
|
|
{
|
|
pMapDoc->CheckFileStatus();
|
|
if ( pMapDoc->IsReadOnly() == true && pMapDoc->IsCheckedOut() == false )
|
|
{
|
|
if ( pMapDoc->IsVersionControlled() )
|
|
{
|
|
CUtlString dialogText;
|
|
dialogText.Format("This map is not checked out. Would you like to check it out?\n\n%s", SaveFileName );
|
|
|
|
CDialogWithCheckbox Dialog( "Checkout File", dialogText,"Check out BSP.", false, !pMapDoc->BspOkToCheckOut() );
|
|
|
|
if( Dialog.DoModal() == IDOK )
|
|
{
|
|
pMapDoc->CheckOut();
|
|
if ( pMapDoc->IsReadOnly() )
|
|
{
|
|
AfxMessageBox( "Checkout was NOT successful!", MB_OK ) ;
|
|
}
|
|
}
|
|
|
|
if ( Dialog.IsCheckboxChecked() )
|
|
{
|
|
pMapDoc->CheckOutBsp();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
char szMessage[ MAX_PATH + MAX_PATH+ 256 ];
|
|
sprintf( szMessage, "This map is marked as READ ONLY. You will not be able to save this file.\n\n%s", SaveFileName );
|
|
AfxMessageBox( szMessage );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
// 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 AfxWnd80sd in Debug and AfxWnd80s in Release
|
|
// For later versions of visual studio, it is afxwnd100s and afxwnd100sd. So for future proofing this we're just gonig to check on afxwnd
|
|
if ( !V_stricmp( className, "edit" ) || !V_strnicmp( className, "afxwnd", strlen( "afxwnd" ) ) )
|
|
{
|
|
// Typing in an edit control. Don't pretranslate, just translate/dispatch.
|
|
return FALSE;
|
|
}
|
|
|
|
if ( m_CustomAcceleratorWindow != NULL )
|
|
{
|
|
if ( TranslateAccelerator( m_CustomAcceleratorWindow, m_CustomAccelerator, pMsg ) != 0 )
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return CWinApp::PreTranslateMessage(pMsg);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CHammer::LoadSequences( const char *szSeqFileName )
|
|
{
|
|
char szRootDir[MAX_PATH];
|
|
char szFullPath[MAX_PATH];
|
|
APP()->GetDirectory(DIR_PROGRAM, szRootDir);
|
|
Q_MakeAbsolutePath( szFullPath, MAX_PATH, szSeqFileName, szRootDir );
|
|
std::ifstream file(szFullPath, std::ios::in | std::ios::binary);
|
|
|
|
if( !file.is_open() )
|
|
{
|
|
return false; // 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);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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() )
|
|
|
|
{
|
|
CMapDoc *pMapDoc = CMapDoc::GetActiveMapDoc();
|
|
|
|
// get the time
|
|
pMapDoc->UpdateCurrentTime();
|
|
|
|
// run any animation
|
|
pMapDoc->UpdateAnimation();
|
|
|
|
// redraw the 3d views
|
|
pMapDoc->RenderAllViews();
|
|
|
|
// update the grid nav
|
|
CGridNav *pGridNav = pMapDoc->GetGridNav();
|
|
if ( pGridNav && pGridNav->IsEnabled() && pGridNav->IsPreviewActive() )
|
|
{
|
|
CMapView3D *pView = pMapDoc->GetFirst3DView();
|
|
if ( pView )
|
|
{
|
|
CCamera *pCam = pView->GetCamera();
|
|
Assert( pCam );
|
|
|
|
Vector vViewPos, vViewDir;
|
|
pCam->GetViewPoint( vViewPos );
|
|
pCam->GetViewForward( vViewDir );
|
|
pGridNav->Update( pMapDoc, vViewPos, vViewDir );
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 framerate for 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
|
|
if ( CGameConfigManager::IsSDKDeployment() )
|
|
{
|
|
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 );
|
|
if ( CGameConfigManager::IsSDKDeployment() )
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( ( szRootDir[1] != ':' ) || ( szRootDir[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 ) ? true : false;
|
|
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, 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();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool UTIL_IsDedicatedServer( void )
|
|
{
|
|
return false;
|
|
}
|
|
|