source-engine/hammer/hammer.cpp

2475 lines
67 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= 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();
}
}