You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1772 lines
46 KiB
1772 lines
46 KiB
//========= 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;
|
|
}
|