|
|
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "stdafx.h"
#include "GameConfig.h"
#include "GlobalFunctions.h"
#include "History.h"
#include "MainFrm.h"
#include "MapCheckDlg.h"
#include "MapDoc.h"
#include "MapEntity.h"
#include "MapSolid.h"
#include "MapWorld.h"
#include "Options.h"
#include "ToolManager.h"
#include "VisGroup.h"
#include "hammer.h"
#include "MapOverlay.h"
#include "Selection.h"
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
// ********
// NOTE: Make sure the order matches g_MapErrorStringIDs below!
// ********
typedef enum { ErrorNoPlayerStart, ErrorMixedFace, ErrorDuplicatePlanes, ErrorMissingTarget, ErrorInvalidTexture, ErrorSolidStructure, ErrorUnusedKeyvalues, ErrorEmptyEntity, ErrorDuplicateKeys, ErrorSolidContents, ErrorInvalidTextureAxes, ErrorDuplicateFaceIDs, ErrorDuplicateNodeIDs, ErrorBadConnections, ErrorHiddenGroupHiddenChildren, ErrorHiddenGroupVisibleChildren, ErrorHiddenGroupMixedChildren, ErrorHiddenObjectNoVisGroup, ErrorHiddenChildOfEntity, ErrorIllegallyHiddenObject, ErrorKillInputRaceCondition, ErrorOverlayFaceList, } MapErrorType;
// ********
// NOTE: Make sure the order matches MapErrorType above!
// ********
struct { int m_StrResourceID; int m_DescriptionResourceID; } g_MapErrorStrings[] = { {IDS_NOPLAYERSTART, IDS_NOPLAYERSTART_DESC}, {IDS_MIXEDFACES, IDS_MIXEDFACES_DESC}, {IDS_DUPLICATEPLANES, IDS_DUPLICATEPLANES_DESC}, {IDS_UNMATCHEDTARGET, IDS_UNMATCHEDTARGET_DESC}, {IDS_INVALIDTEXTURE, IDS_INVALIDTEXTURE_DESC}, {IDS_SOLIDSTRUCTURE, IDS_SOLIDSTRUCTURE_DESC}, {IDS_UNUSEDKEYVALUES, IDS_UNUSEDKEYVALUES_DESC}, {IDS_EMPTYENTITY, IDS_EMPTYENTITY_DESC}, {IDS_DUPLICATEKEYS, IDS_DUPLICATEKEYS_DESC}, {IDS_SOLIDCONTENT, IDS_SOLIDCONTENT_DESC}, {IDS_INVALIDTEXTUREAXES, IDS_INVALIDTEXTUREAXES_DESC}, {IDS_DUPLICATEFACEID, IDS_DUPLICATEFACEID_DESC}, {IDS_DUPLICATE_NODE_ID, IDS_DUPLICATE_NODE_ID_DESC}, {IDS_BAD_CONNECTIONS, IDS_BAD_CONNECTIONS_DESC}, {IDS_HIDDEN_GROUP_HIDDEN_CHILDREN, IDS_HIDDEN_GROUP_HIDDEN_CHILDREN_DESC}, {IDS_HIDDEN_GROUP_VISIBLE_CHILDREN, IDS_HIDDEN_GROUP_VISIBLE_CHILDREN_DESC}, {IDS_HIDDEN_GROUP_MIXED_CHILDREN, IDS_HIDDEN_GROUP_MIXED_CHILDREN_DESC}, {IDS_HIDDEN_NO_VISGROUP, IDS_HIDDEN_NO_VISGROUP_DESC}, {IDS_HIDDEN_CHILD_OF_ENTITY, IDS_HIDDEN_CHILD_OF_ENTITY_DESC}, {IDS_HIDDEN_ILLEGALLY, IDS_HIDDEN_ILLEGALLY_DESC}, {IDS_KILL_INPUT_RACE_CONDITION, IDS_KILL_INPUT_RACE_CONDITION_DESC}, {IDS_BAD_OVERLAY, IDS_DAB_OVERLAY_DESC} };
typedef enum { CantFix, NeedsFix, Fixed, } FIXCODE;
struct MapError { CMapClass *pObjects[3]; MapErrorType Type; DWORD dwExtra; FIXCODE Fix; };
//
// Fix functions.
//
static void FixDuplicatePlanes(MapError *pError); static void FixSolidStructure(MapError *pError); static void FixInvalidTexture(MapError *pError); static void FixInvalidTextureAxes(MapError *pError); static void FixUnusedKeyvalues(MapError *pError); static void FixEmptyEntity(MapError *pError); static void FixBadConnections(MapError *pError); static void FixInvalidContents(MapError *pError); static void FixDuplicateFaceIDs(MapError *pError); static void FixDuplicateNodeIDs(MapError *pError); static void FixMissingTarget(MapError *pError); void FixHiddenObject(MapError *pError); static void FixKillInputRaceCondition(MapError *pError); static void FixOverlayFaceList(MapError *pError);
CMapCheckDlg *s_pDlg = NULL;
BEGIN_MESSAGE_MAP(CMapCheckDlg, CDialog) //{{AFX_MSG_MAP(CMapCheckDlg)
ON_BN_CLICKED(IDC_GO, OnGo) ON_LBN_SELCHANGE(IDC_ERRORS, OnSelchangeErrors) ON_LBN_DBLCLK(IDC_ERRORS, OnDblClkErrors) ON_WM_PAINT() ON_BN_CLICKED(IDC_FIX, OnFix) ON_BN_CLICKED(IDC_FIXALL, OnFixall) ON_WM_DESTROY() ON_WM_CLOSE() ON_BN_CLICKED(IDC_CHECK_VISIBLE_ONLY, OnCheckVisibleOnly) //}}AFX_MSG_MAP
END_MESSAGE_MAP()
//-----------------------------------------------------------------------------
// Visibility check
//-----------------------------------------------------------------------------
inline bool IsCheckVisible( CMapClass *pClass ) { return (Options.general.bCheckVisibleMapErrors == FALSE) || pClass->IsVisible(); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMapCheckDlg::CheckForProblems(CWnd *pwndParent) { if (!s_pDlg) { s_pDlg = new CMapCheckDlg; s_pDlg->Create(IDD, pwndParent); }
if (!s_pDlg->DoCheck()) { // Found problems.
s_pDlg->ShowWindow(SW_SHOW); } }
//-----------------------------------------------------------------------------
// Purpose:
// Input : pParent -
//-----------------------------------------------------------------------------
CMapCheckDlg::CMapCheckDlg(CWnd *pParent) : CDialog(CMapCheckDlg::IDD, pParent) { //{{AFX_DATA_INIT(CMapCheckDlg)
m_bCheckVisible = FALSE; //}}AFX_DATA_INIT
m_bCheckVisible = Options.general.bCheckVisibleMapErrors; }
//-----------------------------------------------------------------------------
// Purpose:
// Input : pDX -
//-----------------------------------------------------------------------------
void CMapCheckDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CMapCheckDlg)
DDX_Control(pDX, IDC_FIXALL, m_cFixAll); DDX_Control(pDX, IDC_FIX, m_Fix); DDX_Control(pDX, IDC_GO, m_Go); DDX_Control(pDX, IDC_DESCRIPTION, m_Description); DDX_Control(pDX, IDC_ERRORS, m_Errors); DDX_Check(pDX, IDC_CHECK_VISIBLE_ONLY, m_bCheckVisible); //}}AFX_DATA_MAP
if ( pDX->m_bSaveAndValidate ) { Options.general.bCheckVisibleMapErrors = m_bCheckVisible; } }
//-----------------------------------------------------------------------------
// Checkbox indicating whether we should check visible errors
//-----------------------------------------------------------------------------
void CMapCheckDlg::OnCheckVisibleOnly() { UpdateData( TRUE ); DoCheck(); }
//-----------------------------------------------------------------------------
// Purpose: Selects the current error objects and centers the views on it.
//-----------------------------------------------------------------------------
void CMapCheckDlg::OnGo() { GotoSelectedErrors(); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMapCheckDlg::GotoSelectedErrors() { int iSel = m_Errors.GetCurSel(); if (iSel == LB_ERR) { return; }
ToolManager()->SetTool(TOOL_POINTER);
CMapObjectList Objects; for (int i = 0; i < m_Errors.GetCount(); i++) { if (m_Errors.GetSel(i) > 0) { MapError *pError = (MapError *)m_Errors.GetItemDataPtr(i); if (pError) { Objects.AddToTail(pError->pObjects[0]); } } }
CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();
pDoc->SelectObjectList(&Objects); pDoc->CenterViewsOnSelection(); }
//-----------------------------------------------------------------------------
// Purpose: Fixes all the selected errors.
//-----------------------------------------------------------------------------
void CMapCheckDlg::OnFix() { int iSel = m_Errors.GetCurSel(); if (iSel == LB_ERR) { return; }
UpdateBox ub; CMapObjectList Objects; ub.Objects = &Objects;
for (int i = 0; i < m_Errors.GetCount(); i++) { if (m_Errors.GetSel(i) > 0) { MapError *pError = (MapError *)m_Errors.GetItemDataPtr(i); if (pError) { Fix(pError, ub); } } }
OnSelchangeErrors(); CMapDoc::GetActiveMapDoc()->UpdateAllViews( MAPVIEW_UPDATE_OBJECTS ); }
//-----------------------------------------------------------------------------
// Purpose:
// Input : pError -
// ub -
//-----------------------------------------------------------------------------
void CMapCheckDlg::Fix(MapError *pError, UpdateBox &ub) { CMapDoc::GetActiveMapDoc()->SetModifiedFlag();
if (pError->Fix != NeedsFix) { // should never get here because this button is supposed
// to be disabled if the error cannot be fixed
return; }
//
// Expand the bounds of the update region to include the broken objects.
//
for (int i = 0; i < 2; i++) { if (!pError->pObjects[i]) { continue; }
ub.Objects->AddToTail(pError->pObjects[i]);
Vector mins; Vector maxs; pError->pObjects[i]->GetRender2DBox(mins, maxs); ub.Box.UpdateBounds(mins, maxs); }
//
// Perform the fix.
//
switch (pError->Type) { case ErrorDuplicatePlanes: { FixDuplicatePlanes(pError); break; } case ErrorDuplicateFaceIDs: { FixDuplicatePlanes(pError); break; } case ErrorDuplicateNodeIDs: { FixDuplicateNodeIDs(pError); break; } case ErrorMissingTarget: { FixMissingTarget(pError); break; } case ErrorSolidStructure: { FixSolidStructure(pError); break; } case ErrorSolidContents: { FixInvalidContents(pError); break; } case ErrorInvalidTexture: { FixInvalidTexture(pError); break; } case ErrorInvalidTextureAxes: { FixInvalidTextureAxes(pError); break; } case ErrorUnusedKeyvalues: { FixUnusedKeyvalues(pError); break; } case ErrorBadConnections: { FixBadConnections(pError); break; } case ErrorEmptyEntity: { FixEmptyEntity(pError); break; } case ErrorHiddenGroupVisibleChildren: case ErrorHiddenGroupMixedChildren: case ErrorHiddenGroupHiddenChildren: case ErrorHiddenObjectNoVisGroup: case ErrorHiddenChildOfEntity: case ErrorIllegallyHiddenObject: { FixHiddenObject(pError); break; } case ErrorKillInputRaceCondition: { FixKillInputRaceCondition(pError); break; } case ErrorOverlayFaceList: { FixOverlayFaceList( pError ); break; } }
pError->Fix = Fixed;
//
// Expand the bounds of the update region to include the fixed objects.
//
for (int i = 0; i < 2; i++) { if (!pError->pObjects[i]) { continue; }
Vector mins; Vector maxs; pError->pObjects[i]->GetRender2DBox(mins, maxs); ub.Box.UpdateBounds(mins, maxs); } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMapCheckDlg::OnFixall() { int iSel = m_Errors.GetCurSel(); if (iSel == LB_ERR) { return; }
MapError *pError = (MapError *) m_Errors.GetItemDataPtr(iSel);
if (pError->Fix == CantFix) { // should never get here because this button is supposed
// to be disabled if the error cannot be fixed
return; }
UpdateBox ub; CMapObjectList Objects; ub.Objects = &Objects;
// For every selected error...
for (int i = 0; i < m_Errors.GetCount(); i++) { if (m_Errors.GetSel(i) > 0) { pError = (MapError *)m_Errors.GetItemDataPtr(i); if ((pError) && (pError->Fix == NeedsFix)) { // Find and fix every error of the same type.
for (int j = 0; j < m_Errors.GetCount(); j++) { MapError *pError2 = (MapError *)m_Errors.GetItemDataPtr(j); if ((pError2->Type != pError->Type) || (pError2->Fix != NeedsFix)) { continue; }
Fix(pError2, ub); } } } }
OnSelchangeErrors(); CMapDoc::GetActiveMapDoc()->UpdateAllViews( MAPVIEW_UPDATE_OBJECTS ); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMapCheckDlg::OnSelchangeErrors() { // change description to match error
int iSel = m_Errors.GetCurSel();
if(iSel == LB_ERR) { m_Fix.EnableWindow(FALSE); m_cFixAll.EnableWindow(FALSE); m_Go.EnableWindow(FALSE); }
CString str; MapError *pError; pError = (MapError*) m_Errors.GetItemDataPtr(iSel);
// Figure out which error string we're using.
int iErrorStr = (int)pError->Type; iErrorStr = clamp( iErrorStr, 0, ARRAYSIZE( g_MapErrorStrings ) - 1 ); Assert( iErrorStr == (int)pError->Type ); str.LoadString(g_MapErrorStrings[iErrorStr].m_DescriptionResourceID); m_Description.SetWindowText(str);
m_Go.EnableWindow(pError->pObjects[0] != NULL);
// set state of fix button
m_Fix.EnableWindow(pError->Fix == NeedsFix); m_cFixAll.EnableWindow(pError->Fix != CantFix);
// set text of fix button
switch (pError->Fix) { case NeedsFix: m_Fix.SetWindowText("&Fix"); break; case CantFix: m_Fix.SetWindowText("Can't fix"); break; case Fixed: m_Fix.SetWindowText("(fixed)"); break; }
CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();
pDoc->GetSelection()->SetMode(selectObjects); if (pError->pObjects[0]) { pDoc->SelectObject(pError->pObjects[0], scClear|scSelect|scSaveChanges ); } else { pDoc->SelectObject(NULL, scClear|scSaveChanges ); } }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMapCheckDlg::OnDblClkErrors() { GotoSelectedErrors(); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMapCheckDlg::OnPaint() { CPaintDC dc(this); // device context for painting
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMapCheckDlg::KillErrorList() { // delete items in list.. their data ptrs are allocated objects
int iSize = m_Errors.GetCount(); for(int i = 0; i < iSize; i++) { MapError *pError = (MapError*) m_Errors.GetItemDataPtr(i); delete pError; }
m_Errors.ResetContent(); }
//-----------------------------------------------------------------------------
// Purpose: Builds the listbox string for the given error and adds it to the list.
//-----------------------------------------------------------------------------
static void AddErrorToListBox(CListBox *pList, MapError *pError) { CString str;
// Figure out which error string we're using.
int iErrorStr = (int)pError->Type; iErrorStr = clamp( iErrorStr, 0, ARRAYSIZE( g_MapErrorStrings ) - 1 ); Assert( iErrorStr == (int)pError->Type ); str.LoadString(g_MapErrorStrings[iErrorStr].m_StrResourceID);
if (str.Find('%') != -1) { if (pError->Type == ErrorUnusedKeyvalues) { // dwExtra has the name of the string in it
CString str2 = str; CMapEntity *pEntity = (CMapEntity *)pError->pObjects[0]; str.Format(str2, pEntity->GetClassName(), pError->dwExtra); } else { CString str2 = str; str.Format(str2, pError->dwExtra); } }
int iIndex = pList->AddString(str); pList->SetItemDataPtr(iIndex, (PVOID)pError); }
//-----------------------------------------------------------------------------
// Purpose:
// Input : pList -
// Type -
// dwExtra -
// ... -
//-----------------------------------------------------------------------------
static void AddError(CListBox *pList, MapErrorType Type, DWORD dwExtra, ...) { MapError *pError = new MapError; memset(pError, 0, sizeof(MapError));
pError->Type = Type; pError->dwExtra = dwExtra; pError->Fix = CantFix;
va_list vl; va_start(vl, dwExtra);
//
// Get the object pointer from the variable argument list.
//
switch (Type) { case ErrorNoPlayerStart: { // no objects.
break; }
case ErrorMixedFace: case ErrorMissingTarget: case ErrorDuplicatePlanes: case ErrorDuplicateFaceIDs: case ErrorDuplicateNodeIDs: case ErrorSolidStructure: case ErrorSolidContents: case ErrorInvalidTexture: case ErrorUnusedKeyvalues: case ErrorBadConnections: case ErrorEmptyEntity: case ErrorDuplicateKeys: case ErrorInvalidTextureAxes: case ErrorHiddenGroupHiddenChildren: case ErrorHiddenGroupVisibleChildren: case ErrorHiddenGroupMixedChildren: case ErrorHiddenObjectNoVisGroup: case ErrorHiddenChildOfEntity: case ErrorIllegallyHiddenObject: case ErrorOverlayFaceList: { pError->pObjects[0] = va_arg(vl, CMapClass *); break; }
case ErrorKillInputRaceCondition: { pError->pObjects[0] = va_arg(vl, CMapClass *); pError->dwExtra = (DWORD)va_arg(vl, CEntityConnection *); break; } }
//
// Set the can fix flag.
//
switch (Type) { case ErrorSolidContents: case ErrorDuplicatePlanes: case ErrorDuplicateFaceIDs: case ErrorDuplicateNodeIDs: case ErrorSolidStructure: case ErrorInvalidTexture: case ErrorUnusedKeyvalues: case ErrorMissingTarget: case ErrorBadConnections: case ErrorEmptyEntity: case ErrorDuplicateKeys: case ErrorInvalidTextureAxes: case ErrorHiddenGroupHiddenChildren: case ErrorHiddenGroupVisibleChildren: case ErrorHiddenGroupMixedChildren: case ErrorHiddenObjectNoVisGroup: case ErrorHiddenChildOfEntity: case ErrorIllegallyHiddenObject: case ErrorKillInputRaceCondition: case ErrorOverlayFaceList: { pError->Fix = NeedsFix; break; } }
va_end(vl);
AddErrorToListBox(pList, pError); }
//-----------------------------------------------------------------------------
// Purpose:
// Input : pObject -
// DWORD -
// Output :
//-----------------------------------------------------------------------------
static BOOL FindPlayer(CMapEntity *pObject, DWORD) { if ( !IsCheckVisible( pObject ) ) return TRUE;
if (pObject->IsPlaceholder() && pObject->IsClass("info_player_start")) { return(FALSE); } return(TRUE); }
//-----------------------------------------------------------------------------
// Purpose:
// Input : pList -
// pWorld -
//-----------------------------------------------------------------------------
static void CheckRequirements(CListBox *pList, CMapWorld *pWorld) { // ensure there's a player start ..
if (pWorld->EnumChildren((ENUMMAPCHILDRENPROC)FindPlayer, 0, MAPCLASS_TYPE(CMapEntity))) { // if rvl is !0, it was not stopped prematurely.. which means there is
// NO player start.
AddError(pList, ErrorNoPlayerStart, 0); } }
//-----------------------------------------------------------------------------
// Purpose:
// Input : pSolid -
// pList -
// Output :
//-----------------------------------------------------------------------------
static BOOL _CheckMixedFaces(CMapSolid *pSolid, CListBox *pList) { if ( !IsCheckVisible( pSolid ) ) return TRUE;
// run thru faces..
int iFaces = pSolid->GetFaceCount(); int iSolid = 2; // start off ambivalent
int i; for(i = 0; i < iFaces; i++) { CMapFace *pFace = pSolid->GetFace(i);
char ch = pFace->texture.texture[0]; if((ch == '*' && iSolid == 1) || (ch != '*' && iSolid == 0)) { break; } else iSolid = (ch == '*') ? 0 : 1; }
if(i == iFaces) // all ok
return TRUE;
// NOT ok
AddError(pList, ErrorMixedFace, 0, pSolid);
return TRUE; }
static void CheckMixedFaces(CListBox *pList, CMapWorld *pWorld) { pWorld->EnumChildren((ENUMMAPCHILDRENPROC)_CheckMixedFaces, (DWORD)pList, MAPCLASS_TYPE(CMapSolid)); }
//-----------------------------------------------------------------------------
// Purpose: Returns true if there is another node entity in the world with the
// same node ID as the given entity.
// Input : pNode -
// pWorld -
//-----------------------------------------------------------------------------
bool FindDuplicateNodeID(CMapEntity *pNode, CMapWorld *pWorld) { if ( !IsCheckVisible( pNode ) ) return false;
EnumChildrenPos_t pos; CMapClass *pChild = pWorld->GetFirstDescendent(pos); while (pChild != NULL) { CMapEntity *pEntity = dynamic_cast<CMapEntity *>(pChild); if (pEntity && IsCheckVisible( pEntity ) && (pEntity != pNode) && pEntity->IsNodeClass()) { int nNodeID1 = pNode->GetNodeID(); int nNodeID2 = pEntity->GetNodeID(); if ((nNodeID1 != 0) && (nNodeID2 != 0) && (nNodeID1 == nNodeID2)) { return true; } } pChild = pWorld->GetNextDescendent(pos); }
return false; }
//-----------------------------------------------------------------------------
// Purpose: Checks for node entities with the same node ID.
//-----------------------------------------------------------------------------
static void CheckDuplicateNodeIDs(CListBox *pList, CMapWorld *pWorld) { EnumChildrenPos_t pos; CMapClass *pChild = pWorld->GetFirstDescendent(pos); while (pChild != NULL) { CMapEntity *pEntity = dynamic_cast<CMapEntity *>(pChild); if (pEntity && pEntity->IsNodeClass()) { if (FindDuplicateNodeID(pEntity, pWorld)) { AddError(pList, ErrorDuplicateNodeIDs, (DWORD)pWorld, pEntity); } } pChild = pWorld->GetNextDescendent(pos); } }
//-----------------------------------------------------------------------------
// Purpose: Checks for faces with identical face normals in this solid object.
// Input : pSolid - Solid to check for duplicate faces.
// Output : Returns TRUE if the face contains at least one duplicate face,
// FALSE if the solid contains no duplicate faces.
//-----------------------------------------------------------------------------
BOOL DoesContainDuplicates(CMapSolid *pSolid) { int iFaces = pSolid->GetFaceCount(); for (int i = 0; i < iFaces; i++) { CMapFace *pFace = pSolid->GetFace(i); Vector& pts1 = pFace->plane.normal;
for (int j = 0; j < iFaces; j++) { // Don't check self.
if (j == i) { continue; }
CMapFace *pFace2 = pSolid->GetFace(j); Vector& pts2 = pFace2->plane.normal;
if (pts1 == pts2) { return(TRUE); } } } return(FALSE); }
//-----------------------------------------------------------------------------
// Purpose:
// Input : pSolid -
// pList -
// Output :
//-----------------------------------------------------------------------------
static BOOL _CheckDuplicatePlanes(CMapSolid *pSolid, CListBox *pList) { if ( !IsCheckVisible( pSolid ) ) return TRUE;
if (DoesContainDuplicates(pSolid)) { AddError(pList, ErrorDuplicatePlanes, 0, pSolid); }
return(TRUE); }
static void CheckDuplicatePlanes(CListBox *pList, CMapWorld *pWorld) { pWorld->EnumChildren((ENUMMAPCHILDRENPROC)_CheckDuplicatePlanes, (DWORD)pList, MAPCLASS_TYPE(CMapSolid)); }
struct FindDuplicateFaceIDs_t { CMapFaceList All; // Collects all the face IDs in this map.
CMapFaceList Duplicates; // Collects the duplicate face IDs in this map.
};
//-----------------------------------------------------------------------------
// Purpose:
// Input : pSolid -
// pData -
// Output : Returns TRUE to continue enumerating.
//-----------------------------------------------------------------------------
static BOOL _CheckDuplicateFaceIDs(CMapSolid *pSolid, FindDuplicateFaceIDs_t *pData) { if ( !IsCheckVisible( pSolid ) ) return TRUE;
int nFaceCount = pSolid->GetFaceCount(); for (int i = 0; i < nFaceCount; i++) { CMapFace *pFace = pSolid->GetFace(i); if (pData->All.FindFaceID(pFace->GetFaceID()) != -1) { if (pData->Duplicates.FindFaceID(pFace->GetFaceID()) != -1) { pData->Duplicates.AddToTail(pFace); } } else { pData->All.AddToTail(pFace); } }
return(TRUE); }
//-----------------------------------------------------------------------------
// Purpose: Reports errors for all faces with duplicate face IDs.
// Input : pList -
// pWorld -
//-----------------------------------------------------------------------------
static void CheckDuplicateFaceIDs(CListBox *pList, CMapWorld *pWorld) { FindDuplicateFaceIDs_t Lists; Lists.All.SetGrowSize(128); Lists.Duplicates.SetGrowSize(128);
pWorld->EnumChildren((ENUMMAPCHILDRENPROC)_CheckDuplicateFaceIDs, (DWORD)&Lists, MAPCLASS_TYPE(CMapSolid));
for (int i = 0; i < Lists.Duplicates.Count(); i++) { CMapFace *pFace = Lists.Duplicates.Element(i); AddError(pList, ErrorDuplicateFaceIDs, (DWORD)pFace, (CMapSolid *)pFace->GetParent()); } }
//-----------------------------------------------------------------------------
// Checks if a particular target is valid.
//-----------------------------------------------------------------------------
static void CheckValidTarget(CMapEntity *pEntity, const char *pFieldName, const char *pTargetName, CListBox *pList, bool bCheckClassNames) { if (!pTargetName) return;
// These procedural names are always assumed to exist.
if (!stricmp(pTargetName, "!activator") || !stricmp(pTargetName, "!caller") || !stricmp(pTargetName, "!player") || !stricmp(pTargetName, "!self")) return;
CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();
// Search by name first.
CMapEntityList Found; bool bFound = pDoc->FindEntitiesByName(Found, pTargetName, (Options.general.bCheckVisibleMapErrors == TRUE)); if (!bFound && bCheckClassNames) { // Not found, search by classname.
bFound = pDoc->FindEntitiesByClassName(Found, pTargetName, (Options.general.bCheckVisibleMapErrors == TRUE)); }
if (!bFound) { // No dice, flag it as an error.
AddError(pList, ErrorMissingTarget, (DWORD)pFieldName, pEntity); } }
//-----------------------------------------------------------------------------
// Purpose: Checks the given entity for references by name to nonexistent entities.
// Input : pEntity -
// pList -
// Output : Returns TRUE to keep enumerating.
//-----------------------------------------------------------------------------
static BOOL _CheckMissingTargets(CMapEntity *pEntity, CListBox *pList) { if ( !IsCheckVisible( pEntity ) ) return TRUE;
GDclass *pClass = pEntity->GetClass(); if (!pClass) { // Unknown class -- just check for target references.
static char *pszTarget = "target"; const char *pszValue = pEntity->GetKeyValue(pszTarget); CheckValidTarget(pEntity, pszTarget, pszValue, pList, false); } else { // Known class -- check all target_destination and target_name_or_class keyvalues.
for (int i = 0; i < pClass->GetVariableCount(); i++) { GDinputvariable *pVar = pClass->GetVariableAt(i); if ((pVar->GetType() != ivTargetDest) && (pVar->GetType() != ivTargetNameOrClass)) continue;
const char *pszValue = pEntity->GetKeyValue(pVar->GetName()); CheckValidTarget(pEntity, pVar->GetName(), pszValue, pList, (pVar->GetType() == ivTargetNameOrClass)); } }
return TRUE; }
static void CheckMissingTargets(CListBox *pList, CMapWorld *pWorld) { pWorld->EnumChildren((ENUMMAPCHILDRENPROC)_CheckMissingTargets, (DWORD)pList, MAPCLASS_TYPE(CMapEntity)); }
//-----------------------------------------------------------------------------
// Purpose: Determines whether a solid is good or bad.
// Input : pSolid - Solid to check.
// pList - List box into which to place errors.
// Output : Always returns TRUE to continue enumerating.
//-----------------------------------------------------------------------------
static BOOL _CheckSolidIntegrity(CMapSolid *pSolid, CListBox *pList) { if ( !IsCheckVisible( pSolid ) ) return TRUE;
CCheckFaceInfo cfi; int nFaces = pSolid->GetFaceCount(); for (int i = 0; i < nFaces; i++) { CMapFace *pFace = pSolid->GetFace(i);
//
// Reset the iPoint member so results from previous faces don't carry over.
//
cfi.iPoint = -1;
//
// Check the face.
//
if (!pFace->CheckFace(&cfi)) { AddError(pList, ErrorSolidStructure, 0, pSolid); break; } }
return(TRUE); }
static void CheckSolidIntegrity(CListBox *pList, CMapWorld *pWorld) { pWorld->EnumChildren((ENUMMAPCHILDRENPROC)_CheckSolidIntegrity, (DWORD)pList, MAPCLASS_TYPE(CMapSolid)); }
//-----------------------------------------------------------------------------
// Purpose:
// Input : pSolid -
// pList -
// Output :
//-----------------------------------------------------------------------------
static BOOL _CheckSolidContents(CMapSolid *pSolid, CListBox *pList) { if ( !IsCheckVisible( pSolid ) ) return TRUE;
CCheckFaceInfo cfi; int nFaces = pSolid->GetFaceCount(); CMapFace *pFace = pSolid->GetFace(0); DWORD dwContents = pFace->texture.q2contents;
for (int i = 1; i < nFaces; i++) { pFace = pSolid->GetFace(i); if (pFace->texture.q2contents == dwContents) { continue; } AddError(pList, ErrorSolidContents, 0, pSolid); break; }
return TRUE; }
static void CheckSolidContents(CListBox *pList, CMapWorld *pWorld) { if (CMapDoc::GetActiveMapDoc() && CMapDoc::GetActiveMapDoc()->GetGame() && CMapDoc::GetActiveMapDoc()->GetGame()->mapformat == mfQuake2) { pWorld->EnumChildren((ENUMMAPCHILDRENPROC)_CheckSolidContents, (DWORD)pList, MAPCLASS_TYPE(CMapSolid)); } }
//-----------------------------------------------------------------------------
// Purpose: Determines if there are any invalid textures or texture axes on any
// face of this solid. Adds an error message to the list box for each
// error found.
// Input : pSolid - Solid to check.
// pList - Pointer to the error list box.
// Output : Returns TRUE.
//-----------------------------------------------------------------------------
static BOOL _CheckInvalidTextures(CMapSolid *pSolid, CListBox *pList) { if ( !IsCheckVisible( pSolid ) ) return TRUE;
int nFaces = pSolid->GetFaceCount(); for(int i = 0; i < nFaces; i++) { const CMapFace *pFace = pSolid->GetFace(i);
IEditorTexture *pTex = pFace->GetTexture(); if (pTex->IsDummy()) { AddError(pList, ErrorInvalidTexture, (DWORD)pFace->texture.texture, pSolid); return TRUE; }
if (!pFace->IsTextureAxisValid()) { AddError(pList, ErrorInvalidTextureAxes, i, pSolid); return(TRUE); } } return(TRUE); }
static void CheckInvalidTextures(CListBox *pList, CMapWorld *pWorld) { pWorld->EnumChildren((ENUMMAPCHILDRENPROC)_CheckInvalidTextures, (DWORD)pList, MAPCLASS_TYPE(CMapSolid)); }
//-----------------------------------------------------------------------------
// Purpose:
// Input : pEntity -
// pList -
// Output :
//-----------------------------------------------------------------------------
static BOOL _CheckUnusedKeyvalues(CMapEntity *pEntity, CListBox *pList) { if ( !IsCheckVisible( pEntity ) ) return TRUE;
if (!pEntity->IsClass() || pEntity->IsClass("multi_manager")) { return(TRUE); // can't check if no class associated
}
GDclass *pClass = pEntity->GetClass();
for (int i = pEntity->GetFirstKeyValue(); i != pEntity->GetInvalidKeyValue(); i=pEntity->GetNextKeyValue( i ) ) { if (pClass->VarForName(pEntity->GetKey(i)) == NULL) { AddError(pList, ErrorUnusedKeyvalues, (DWORD)pEntity->GetKey(i), pEntity); return(TRUE); } } return(TRUE); }
static void CheckUnusedKeyvalues(CListBox *pList, CMapWorld *pWorld) { pWorld->EnumChildren((ENUMMAPCHILDRENPROC)_CheckUnusedKeyvalues, (DWORD)pList, MAPCLASS_TYPE(CMapEntity)); }
//-----------------------------------------------------------------------------
// Purpose:
// Input : pEntity -
// pList -
// Output :
//-----------------------------------------------------------------------------
static BOOL _CheckEmptyEntities(CMapEntity *pEntity, CListBox *pList) { if ( !IsCheckVisible( pEntity ) ) return TRUE;
if(!pEntity->IsPlaceholder() && !pEntity->GetChildCount()) { AddError(pList, ErrorEmptyEntity, (DWORD)pEntity->GetClassName(), pEntity); } return(TRUE); }
static void CheckEmptyEntities(CListBox *pList, CMapWorld *pWorld) { pWorld->EnumChildren((ENUMMAPCHILDRENPROC)_CheckEmptyEntities, (DWORD)pList, MAPCLASS_TYPE(CMapEntity)); }
//-----------------------------------------------------------------------------
// Purpose: Checks the entity for bad I/O connections.
// Input : pEntity - the entity to check
// pList - list box that tracks the errors
// Output : Returns TRUE to keep enumerating.
//-----------------------------------------------------------------------------
static BOOL _CheckBadConnections(CMapEntity *pEntity, CListBox *pList) { if ( !IsCheckVisible( pEntity ) ) return TRUE;
if (CEntityConnection::ValidateOutputConnections(pEntity, (Options.general.bCheckVisibleMapErrors == TRUE)) == CONNECTION_BAD) { AddError(pList, ErrorBadConnections, (DWORD)pEntity->GetClassName(), pEntity); }
// TODO: Check for a "Kill" input with the same output, target, and delay as another input. This
// creates a race condition in the game where the order of arrival is not guaranteed
//int nConnCount = pEntity->Connections_GetCount();
//for (int i = 0; i < nConnCount; i++)
//{
// CEntityConnection *pConn = pEntity->Connections_Get(i);
// if (!stricmp(pConn->GetInputName(), "kill"))
// {
// }
//}
return TRUE; }
static void CheckBadConnections(CListBox *pList, CMapWorld *pWorld) { pWorld->EnumChildren((ENUMMAPCHILDRENPROC)_CheckBadConnections, (DWORD)pList, MAPCLASS_TYPE(CMapEntity)); }
static bool HasVisGroupHiddenChildren(CMapClass *pObject) { const CMapObjectList *pChildren = pObject->GetChildren();
FOR_EACH_OBJ( *pChildren, pos ) { if (!pChildren->Element(pos)->IsVisGroupShown()) return true; }
return false; }
static bool HasVisGroupShownChildren(CMapClass *pObject) { const CMapObjectList *pChildren = pObject->GetChildren(); FOR_EACH_OBJ( *pChildren, pos ) { if (pChildren->Element(pos)->IsVisGroupShown()) return true; }
return false; }
//-----------------------------------------------------------------------------
// Purpose: Makes sure that the visgroup assignments are valid.
//-----------------------------------------------------------------------------
static BOOL _CheckVisGroups(CMapClass *pObject, CListBox *pList) { CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();
// dvs: FIXME: not working yet, revisit
// Entities cannot have hidden children.
//CMapEntity *pEntity = dynamic_cast<CMapEntity *>(pObject);
//if (pEntity && HasVisGroupHiddenChildren(pEntity))
//{
// AddError(pList, ErrorHiddenChildOfEntity, 0, pEntity);
// return TRUE;
//}
// Check the validity of any object that claims to be hidden by visgroups.
if (!pObject->IsVisGroupShown()) { // Groups cannot be hidden by visgroups.
if (pObject->IsGroup()) { bool bHidden = HasVisGroupHiddenChildren(pObject); bool bVisible = HasVisGroupShownChildren(pObject);
if (bHidden && !bVisible) { AddError(pList, ErrorHiddenGroupHiddenChildren, 0, pObject); } else if (!bHidden && bVisible) { AddError(pList, ErrorHiddenGroupVisibleChildren, 0, pObject); } else { AddError(pList, ErrorHiddenGroupMixedChildren, 0, pObject); }
return TRUE; }
// Check for unanticipated objects that are hidden but forbidden from visgroup membership.
if (!pDoc->VisGroups_ObjectCanBelongToVisGroup(pObject)) { AddError(pList, ErrorIllegallyHiddenObject, 0, pObject); return TRUE; } // Hidden objects must belong to at least one visgroup.
if (pObject->GetVisGroupCount() == 0) { AddError(pList, ErrorHiddenObjectNoVisGroup, 0, pObject); return TRUE; } }
return TRUE; }
static void CheckVisGroups(CListBox *pList, CMapWorld *pWorld) { pWorld->EnumChildrenRecurseGroupsOnly((ENUMMAPCHILDRENPROC)_CheckVisGroups, (DWORD)pList); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
static BOOL _CheckOverlayFaceList( CMapEntity *pEntity, CListBox *pList ) { if ( !IsCheckVisible( pEntity ) ) return TRUE;
const CMapObjectList *pChildren = pEntity->GetChildren();
FOR_EACH_OBJ( *pChildren, pos ) { CMapOverlay *pOverlay = dynamic_cast<CMapOverlay*>( pChildren->Element(pos) ); if ( pOverlay ) { // Check to see if the overlay has assigned faces.
if ( pOverlay->GetFaceCount() <= 0 ) { AddError( pList, ErrorOverlayFaceList, 0, pEntity ); return TRUE; } } }
return TRUE; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
static void CheckOverlayFaceList( CListBox *pList, CMapWorld *pWorld ) { pWorld->EnumChildren( ( ENUMMAPCHILDRENPROC )_CheckOverlayFaceList, ( DWORD )pList, MAPCLASS_TYPE( CMapEntity )); }
//
// ** FIX FUNCTIONS
//
static void FixDuplicatePlanes(MapError *pError) { // duplicate planes in pObjects[0]
// run thru faces..
CMapSolid *pSolid = (CMapSolid*) pError->pObjects[0];
ReStart: int iFaces = pSolid->GetFaceCount(); for(int i = 0; i < iFaces; i++) { CMapFace *pFace = pSolid->GetFace(i); Vector& pts1 = pFace->plane.normal; for (int j = 0; j < iFaces; j++) { // Don't check self
if (j == i) { continue; }
CMapFace *pFace2 = pSolid->GetFace(j); Vector& pts2 = pFace2->plane.normal; if (pts1 == pts2) { pSolid->DeleteFace(j); goto ReStart; } } } }
//-----------------------------------------------------------------------------
// Purpose: Repairs an invalid solid.
// Input : pError - Contains information about the error.
//-----------------------------------------------------------------------------
static void FixSolidStructure(MapError *pError) { CMapSolid *pSolid = (CMapSolid *)pError->pObjects[0];
//
// First make sure all the faces are good.
//
int nFaces = pSolid->GetFaceCount(); for (int i = nFaces - 1; i >= 0; i--) { CMapFace *pFace = pSolid->GetFace(i); if (!pFace->CheckFace(NULL)) { pFace->Fix(); } //
// If the face has no points, just remove it from the solid.
//
if (pFace->GetPointCount() == 0) { pSolid->DeleteFace(i); } }
//
// Rebuild the solid from the planes.
//
pSolid->CreateFromPlanes(); pSolid->PostUpdate(Notify_Changed); }
LPCTSTR GetDefaultTextureName(); // dvs: BAD!
//-----------------------------------------------------------------------------
// Purpose: Replaces any missing textures with the default texture.
// Input : pError -
//-----------------------------------------------------------------------------
static void FixInvalidTexture(MapError *pError) { CMapSolid *pSolid = (CMapSolid *)pError->pObjects[0];
int nFaces = pSolid->GetFaceCount(); for (int i = 0; i < nFaces; i++) { CMapFace *pFace = pSolid->GetFace(i); if (pFace != NULL) { IEditorTexture *pTex = pFace->GetTexture(); if (pTex != NULL) { if (pTex->IsDummy()) { pFace->SetTexture(GetDefaultTextureName()); } } } } }
static void FixInvalidTextureAxes(MapError *pError) { CMapSolid *pSolid = (CMapSolid *)pError->pObjects[0];
int nFaces = pSolid->GetFaceCount(); for (int i = 0; i < nFaces; i++) { CMapFace *pFace = pSolid->GetFace(i); if (!pFace->IsTextureAxisValid()) { pFace->InitializeTextureAxes(Options.GetTextureAlignment(), INIT_TEXTURE_FORCE | INIT_TEXTURE_AXES); } } }
static void FixInvalidContents(MapError *pError) { CMapSolid *pSolid = (CMapSolid *)pError->pObjects[0];
CMapFace *pFace = pSolid->GetFace(0); DWORD dwContents = pFace->texture.q2contents;
int nFaces = pSolid->GetFaceCount(); for (int i = 1; i < nFaces; i++) { pFace = pSolid->GetFace(i); pFace->texture.q2contents = dwContents; } }
//-----------------------------------------------------------------------------
// Purpose: Fixes duplicate face IDs by assigning the face a unique ID within
// the world.
// Input : pError - Holds the world and the face that is in error.
//-----------------------------------------------------------------------------
static void FixDuplicateFaceIDs(MapError *pError) { CMapWorld *pWorld = (CMapWorld *)pError->pObjects[0]; CMapFace *pFace = (CMapFace *)pError->dwExtra;
pFace->SetFaceID(pWorld->FaceID_GetNext()); }
//-----------------------------------------------------------------------------
// Purpose:
// Input : pError -
//-----------------------------------------------------------------------------
static void FixUnusedKeyvalues(MapError *pError) { CMapEntity *pEntity = (CMapEntity*) pError->pObjects[0];
GDclass *pClass = pEntity->GetClass(); if (!pClass) { return; }
int iNext; for ( int i=pEntity->GetFirstKeyValue(); i != pEntity->GetInvalidKeyValue(); i = iNext ) { iNext = pEntity->GetNextKeyValue( i ); if (pClass->VarForName(pEntity->GetKey(i)) == NULL) { pEntity->DeleteKeyValue(pEntity->GetKey(i)); } } }
//-----------------------------------------------------------------------------
// Purpose: Removes any bad connections from the entity associated with the error.
// Input : pError -
//-----------------------------------------------------------------------------
static void FixBadConnections(MapError *pError) { CMapEntity *pEntity = (CMapEntity *)pError->pObjects[0]; CEntityConnection::FixBadConnections(pEntity, (Options.general.bCheckVisibleMapErrors == TRUE)); }
//-----------------------------------------------------------------------------
// Purpose: Fixes a race condition caused by a Kill input being triggered at the
// same instant as another input.
// Input : pError -
//-----------------------------------------------------------------------------
static void FixKillInputRaceCondition(MapError *pError) { CEntityConnection *pConn = (CEntityConnection *)pError->pObjects[1];
// Delay the Kill command so that it arrives after the other command,
// solving the race condition.
pConn->SetDelay(pConn->GetDelay() + 0.01); }
//-----------------------------------------------------------------------------
// Purpose:
// Input : pError -
//-----------------------------------------------------------------------------
static void FixOverlayFaceList( MapError *pError ) { CMapEntity *pEntity = static_cast<CMapEntity*>( pError->pObjects[0] ); if ( !pEntity ) return;
const CMapObjectList *pChildren = pEntity->GetChildren();
FOR_EACH_OBJ( *pChildren, pos ) { CMapOverlay *pOverlay = dynamic_cast<CMapOverlay*>( pChildren->Element(pos) ); if ( pOverlay ) { // Destroy itself.
CMapDoc *pDoc = CMapDoc::GetActiveMapDoc(); pDoc->RemoveObjectFromWorld( pEntity, true ); GetHistory()->KeepForDestruction( pEntity ); return; } } }
//-----------------------------------------------------------------------------
// Purpose:
// Input : pError -
//-----------------------------------------------------------------------------
static void FixEmptyEntity(MapError *pError) { CMapClass *pKillMe = pError->pObjects[0];
if (pKillMe->GetParent() != NULL) { GetHistory()->KeepForDestruction(pKillMe); pKillMe->GetParent()->RemoveChild(pKillMe); } }
//-----------------------------------------------------------------------------
// Purpose: Fixes duplicate node IDs by assigning the entity a unique node ID.
// Input : pError - Holds the world and the entity that is in error.
//-----------------------------------------------------------------------------
static void FixDuplicateNodeIDs(MapError *pError) { CMapEntity *pEntity = (CMapEntity *)pError->pObjects[0]; pEntity->AssignNodeID(); }
//-----------------------------------------------------------------------------
// Purpose: Clears a bad target reference from the given entity.
// Input : pError -
//-----------------------------------------------------------------------------
static void FixMissingTarget(MapError *pError) { CMapEntity *pEntity = (CMapEntity *)pError->pObjects[0]; const char *pszKey = (const char *)pError->dwExtra; pEntity->SetKeyValue(pszKey, NULL); }
//-----------------------------------------------------------------------------
// Purpose: Fix a an invalid visgroup state. This is either:
// 1) A group that is hidden
// 2) An object that is hidden but not in any visgroups
//-----------------------------------------------------------------------------
void FixHiddenObject(MapError *pError) { CMapClass *pObject = pError->pObjects[0];
// Tweak the object's visgroup state directly to avoid changing the
// hidden/shown state of the object's children.
pObject->m_bVisGroupShown = true; pObject->m_bVisGroupAutoShown = true; pObject->m_VisGroups.RemoveAll();
// Create a new visgroup to out the objects in (for hiding or inspection/deletion).
CMapObjectList Objects; Objects.AddToTail(pObject); CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();
if ((pError->Type == ErrorHiddenGroupHiddenChildren) || (pError->Type == ErrorHiddenObjectNoVisGroup)) { // The objects aren't in the compile, so just hide them.
pDoc->VisGroups_CreateNamedVisGroup(Objects, "_hidden by Check for Problems", true, false); } else if (pError->Type == ErrorIllegallyHiddenObject) { // Do nothing, the object is now shown.
} else { // ErrorHiddenGroupVisibleChildren
// ErrorHiddenGroupMixedChildren
// ErrorHiddenChildOfEntity
// The objects either ARE in the compile, or they can't be hidden in a visgroup.
// Don't hide them, just stick them in a visgroup for inspection
pDoc->VisGroups_CreateNamedVisGroup(Objects, "found by Check for Problems", false, false); } }
//-----------------------------------------------------------------------------
// Purpose: Checks the map for problems. Returns true if the map is okay,
// false if problems were found.
//-----------------------------------------------------------------------------
bool CMapCheckDlg::DoCheck(void) { CMapWorld *pWorld = GetActiveWorld();
// Clear error list
KillErrorList();
// Map validation
CheckRequirements(&m_Errors, pWorld);
// Solid validation
CheckMixedFaces(&m_Errors, pWorld); //CheckDuplicatePlanes(&m_Errors, pWorld);
CheckDuplicateFaceIDs(&m_Errors, pWorld); CheckDuplicateNodeIDs(&m_Errors, pWorld); CheckSolidIntegrity(&m_Errors, pWorld); CheckSolidContents(&m_Errors, pWorld); CheckInvalidTextures(&m_Errors, pWorld);
// Entity validation
CheckUnusedKeyvalues(&m_Errors, pWorld); CheckEmptyEntities(&m_Errors, pWorld); CheckMissingTargets(&m_Errors, pWorld); CheckBadConnections(&m_Errors, pWorld);
CheckVisGroups(&m_Errors, pWorld);
CheckOverlayFaceList(&m_Errors, pWorld);
if (!m_Errors.GetCount()) { AfxMessageBox("No errors were found."); EndDialog(IDOK); return true; }
return false; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMapCheckDlg::OnOK() { DestroyWindow(); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMapCheckDlg::OnClose() { DestroyWindow(); }
//-----------------------------------------------------------------------------
// Purpose: Called when our window is being destroyed.
//-----------------------------------------------------------------------------
void CMapCheckDlg::OnDestroy() { delete this; s_pDlg = NULL; }
|