//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:		Namespace for functions having to do with WC Edit mode
//
// $Workfile:     $
// $Date:         $
//
//-----------------------------------------------------------------------------
// $Log: $
//
// $NoKeywords: $
//=============================================================================//

#include "cbase.h"
#include "mathlib/mathlib.h"
#include "player.h"
#include "wcedit.h"
#include "ai_network.h"
#include "ai_initutils.h"
#include "ai_hull.h"
#include "ai_link.h"
#include "ai_node.h"
#include "ai_dynamiclink.h"
#include "ai_networkmanager.h"
#include "ndebugoverlay.h"
#include "editor_sendcommand.h"
#include "movevars_shared.h"
#include "model_types.h"
// UNDONE: Reduce some dependency here!
#include "physics_prop_ragdoll.h"
#include "items.h"
#include "utlsymbol.h"
#include "physobj.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

extern CAI_Node*		FindPickerAINode( CBasePlayer* pPlayer, NodeType_e nNodeType );
extern CAI_Link*		FindPickerAILink( CBasePlayer* pPlayer );
extern float			GetFloorZ(const Vector &origin);

//-----------------------------------------------------------------------------
// Purpose: Make sure the version of the map in WC is the same as the map 
//			that's being edited 
// Input  :
// Output :
//-----------------------------------------------------------------------------
bool NWCEdit::IsWCVersionValid(void)
{
	int status = Editor_CheckVersion(STRING(gpGlobals->mapname), gpGlobals->mapversion, false);
	if (!status)
	{
		return true;
	}
	else if (status == Editor_NotRunning)
	{
		Msg("\nAborting map_edit\nWorldcraft not running...\n\n");
		UTIL_CenterPrintAll( "Worldcraft not running..." );
		engine->ServerCommand("disconnect\n");
	}
	else
	{
		Msg("\nAborting map_edit\nWC/Engine map versions different...\n\n");
		UTIL_CenterPrintAll( "WC/Engine map versions different..." );
		engine->ServerCommand("disconnect\n");
	}
	return false;
}

//------------------------------------------------------------------------------
// Purpose : Figure out placement position of air nodes form where player is
//			 looking.  Keep distance from player constant but adjust height
//			 based on viewing angle
// Input   :
// Output  :
//------------------------------------------------------------------------------
Vector NWCEdit::AirNodePlacementPosition( void )
{
	CBasePlayer* pPlayer = UTIL_PlayerByIndex(CBaseEntity::m_nDebugPlayer);

	if (!pPlayer) 
	{
		return vec3_origin;
	}

	Vector pForward;
	pPlayer->EyeVectors( &pForward );
	
	Vector	floorVec = pForward;
	floorVec.z = 0;
	VectorNormalize( floorVec );
	VectorNormalize( pForward );

	float cosAngle = DotProduct(floorVec,pForward);

	float lookDist = g_pAINetworkManager->GetEditOps()->m_flAirEditDistance/cosAngle;
	Vector lookPos = pPlayer->EyePosition()+pForward * lookDist;

	return lookPos;
}

//-----------------------------------------------------------------------------
// Purpose: For create nodes in wc edit mode
// Input  :
// Output :
//-----------------------------------------------------------------------------
void NWCEdit::CreateAINode( CBasePlayer *pPlayer )
{
	// -------------------------------------------------------------
	//  Check that WC is running with the right map version
	// -------------------------------------------------------------
	if ( !IsWCVersionValid() || !pPlayer )
		return;

	pPlayer->AddSolidFlags( FSOLID_NOT_SOLID );

	int hullType = g_pAINetworkManager->GetEditOps()->m_iHullDrawNum;

	// -----------------------------------
	//  Get position of node to create
	// -----------------------------------
	Vector vNewNodePos = vec3_origin;
	bool bPositionValid = false;
	if (g_pAINetworkManager->GetEditOps()->m_bAirEditMode)
	{
		vNewNodePos = NWCEdit::AirNodePlacementPosition();

		// Make sure we can see the node
		trace_t tr;
		UTIL_TraceLine(pPlayer->EyePosition(), vNewNodePos, MASK_NPCSOLID_BRUSHONLY, pPlayer, COLLISION_GROUP_NONE, &tr);
		if (tr.fraction == 1.0)
		{
			bPositionValid = true;
		}
	}
	else
	{
		// Place node by where the player is looking
		Vector forward;
		pPlayer->EyeVectors( &forward );
		Vector	startTrace	= pPlayer->EyePosition();
		Vector	endTrace	= pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH;
		trace_t	tr;
		UTIL_TraceLine(startTrace,endTrace,MASK_NPCSOLID, pPlayer, COLLISION_GROUP_NONE, &tr );
		if ( tr.fraction != 1.0)
		{
			// Raise the end position up off the floor, place the node and drop him down
			tr.endpos.z += 48;
			vNewNodePos = tr.endpos;
			bPositionValid = true;
		}
	}

	// -------------------------------------------------------------------------------
	// Now check that this is a valid location for the new node bu using test hull
	// -------------------------------------------------------------------------------
	if (bPositionValid)
	{
		CBaseEntity *testHull = (CBaseEntity*)CAI_TestHull::GetTestHull();

		// Set the size of the test hull
		UTIL_SetSize(testHull, NAI_Hull::Mins(hullType), NAI_Hull::Maxs(hullType));

		// Set origin of test hull
		testHull->SetLocalOrigin( vNewNodePos );

		// -----------------------------------------------------------------------
		// If a ground node, drop to floor and make sure can stand at test postion
		// -----------------------------------------------------------------------
		if (!g_pAINetworkManager->GetEditOps()->m_bAirEditMode)
		{
			UTIL_DropToFloor( testHull, MASK_NPCSOLID );
			vNewNodePos = testHull->GetAbsOrigin();
			CTraceFilterSimple traceFilter( testHull, COLLISION_GROUP_NONE );
			if (!UTIL_CheckBottom(testHull, &traceFilter, sv_stepsize.GetFloat()))
			{
				CAI_TestHull::ReturnTestHull();
				bPositionValid = false;
				goto DoneCreate;
			}
		}

		// -----------------------------------------------------------------------
		// Make sure hull fits at location by seeing if it can move up a fraction
		// -----------------------------------------------------------------------
		Vector vUpBit = testHull->GetAbsOrigin();
		vUpBit.z += 1;
		trace_t tr;
		UTIL_TraceHull( testHull->GetAbsOrigin(), vUpBit, NAI_Hull::Mins(hullType), 
			NAI_Hull::Maxs(hullType), MASK_NPCSOLID, testHull, COLLISION_GROUP_NONE, &tr );
		if (tr.startsolid || tr.fraction != 1.0)
		{
			CAI_TestHull::ReturnTestHull();
			bPositionValid = false;
			goto DoneCreate;
		}

		// <<TEMP>> Round position till DS fixed WC bug
		testHull->SetLocalOrigin( Vector( floor(testHull->GetAbsOrigin().x),
			floor(testHull->GetAbsOrigin().y ), floor(testHull->GetAbsOrigin().z) ) );

		// ---------------------------------------
		//  Send new node to WC
		// ---------------------------------------
		int status;
		if (g_pAINetworkManager->GetEditOps()->m_bAirEditMode)
		{
			status = Editor_CreateNode("info_node_air", g_pAINetworkManager->GetEditOps()->m_nNextWCIndex, testHull->GetLocalOrigin().x, testHull->GetLocalOrigin().y, testHull->GetLocalOrigin().z, false);
		}
		else
		{
			// Create slightly higher in WC so it can be dropped when its loaded again
			Vector origin = testHull->GetLocalOrigin();
			origin.z += 24.0;
			testHull->SetLocalOrigin( origin );
			status = Editor_CreateNode("info_node", g_pAINetworkManager->GetEditOps()->m_nNextWCIndex, testHull->GetLocalOrigin().x, testHull->GetLocalOrigin().y, testHull->GetLocalOrigin().z, false);
		}
		if (status == Editor_BadCommand)
		{
			Msg( "Worldcraft failed on creation...\n" );
			CAI_TestHull::ReturnTestHull();
		}
		else if (status == Editor_OK)
		{
			// -----------------------
			// Create a new ai node
			// -----------------------
			CNodeEnt *pNodeEnt;
			if (g_pAINetworkManager->GetEditOps()->m_bAirEditMode)
			{
				pNodeEnt = (CNodeEnt*)CreateEntityByName("info_node_air");
			}
			else
			{
				pNodeEnt = (CNodeEnt*)CreateEntityByName("info_node");
			}

			// Note this is a new entity being created as part of wc editing
			pNodeEnt->SetLocalOrigin( testHull->GetLocalOrigin() );
			CAI_TestHull::ReturnTestHull();

			pNodeEnt->m_NodeData.nWCNodeID =	g_pAINetworkManager->GetEditOps()->m_nNextWCIndex;

			pNodeEnt->m_debugOverlays |= OVERLAY_WC_CHANGE_ENTITY;
			pNodeEnt->Spawn();
		}	
	}

DoneCreate:
	// ----------------------------------------------------------
	// Flash a red box as a warning that the hull won't fit here
	// ----------------------------------------------------------
	if (!bPositionValid)
	{
		NDebugOverlay::Box(vNewNodePos, NAI_Hull::Mins(hullType), NAI_Hull::Maxs(hullType), 255,0,0,0,0.1);
	}

	// Restore player collidability
	pPlayer->SetSolid( SOLID_BBOX );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  :
// Output :
//-----------------------------------------------------------------------------
void NWCEdit::UndoDestroyAINode(void)
{
	// -------------------------------------------------------------
	//  Check that WC is running with the right map version
	// -------------------------------------------------------------
	if (!IsWCVersionValid())
	{
		return;
	}

	if (g_pAINetworkManager->GetEditOps()->m_pLastDeletedNode)
	{
		Vector nodePos = g_pAINetworkManager->GetEditOps()->m_pLastDeletedNode->GetOrigin();

		int status;
		int	nOldWCID	= g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable[g_pAINetworkManager->GetEditOps()->m_pLastDeletedNode->GetId()];
		
		if (g_pAINetworkManager->GetEditOps()->m_bAirEditMode)
		{
			status = Editor_CreateNode("info_node_air", nOldWCID, nodePos.x, nodePos.y, nodePos.z, false);
		}
		else
		{
			status = Editor_CreateNode("info_node", nOldWCID, nodePos.x, nodePos.y, nodePos.z, false);
		}
		if (status == Editor_BadCommand)
		{
			Msg( "Worldcraft failed on creation...\n" );
		}
		else if (status == Editor_OK)
		{
			g_pAINetworkManager->GetEditOps()->m_pLastDeletedNode->SetType( NODE_GROUND );
			//@ tofo g_pAINetworkManager->GetEditOps()->m_pLastDeletedNode->m_pNetwork->BuildNetworkGraph();
			g_pAINetworkManager->BuildNetworkGraph();
			g_pAINetworkManager->GetEditOps()->m_pLastDeletedNode = NULL;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: For destroying nodes in wc edit mode
// Input  :
// Output :
//-----------------------------------------------------------------------------
void NWCEdit::DestroyAINode( CBasePlayer *pPlayer )
{
	// -------------------------------------------------------------
	//  Check that WC is running with the right map version
	// -------------------------------------------------------------
	if (!IsWCVersionValid())
	{
		return;
	}

	if (!pPlayer)
	{
		return;
	}

	NodeType_e nNodeType = NODE_GROUND;
	if (g_pAINetworkManager->GetEditOps()->m_bAirEditMode)
	{
		nNodeType = NODE_AIR;
	}

	CAI_Node* pAINode = FindPickerAINode(pPlayer, nNodeType);
	if (pAINode)
	{
		int status = Editor_DeleteNode(g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable[pAINode->GetId()], false);

		if (status == Editor_BadCommand)
		{
			Msg( "Worldcraft failed on deletion...\n" );
		}
		else if (status == Editor_OK)
		{
			// Mark this node as deleted and changed
			pAINode->SetType( NODE_DELETED );
			pAINode->m_eNodeInfo   |= bits_NODE_WC_CHANGED;

			// Note that network needs to be rebuild
			g_pAINetworkManager->GetEditOps()->SetRebuildFlags();
			g_pAINetworkManager->GetEditOps()->m_pLastDeletedNode	= pAINode;

			// Now go through at delete any dynamic links that were attached to this node
			for (int link = 0; link < pAINode->NumLinks(); link++)
			{
				int nSrcID = pAINode->GetLinkByIndex(link)->m_iSrcID;
				int nDstID = pAINode->GetLinkByIndex(link)->m_iDestID;
				if (CAI_DynamicLink::GetDynamicLink(nSrcID, nDstID))
				{
					int nWCSrcID = g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable[nSrcID];
					int nWCDstID = g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable[nDstID];
					int	status	 = Editor_DeleteNodeLink(nWCSrcID, nWCDstID);

					if (status == Editor_BadCommand)
					{
						Msg( "Worldcraft failed on node link deletion...\n" );
					}
				}				
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: For restroring links in WC edit mode.  This actually means
//			destroying links in WC that have been marked as 
// Input  :
// Output :
//-----------------------------------------------------------------------------
void NWCEdit::CreateAILink( CBasePlayer* pPlayer )
{
	// -------------------------------------------------------------
	//  Check that WC is running with the right map version
	// -------------------------------------------------------------
	if (!IsWCVersionValid())
	{
		return;
	}

	CAI_Link* pAILink = FindPickerAILink(pPlayer);
	if (pAILink && (pAILink->m_LinkInfo & bits_LINK_OFF))
	{
		int nWCSrcID = g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable[pAILink->m_iSrcID];
		int nWCDstID = g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable[pAILink->m_iDestID];
		int	status	 = Editor_DeleteNodeLink(nWCSrcID, nWCDstID, false);

		if (status == Editor_BadCommand)
		{
			Msg( "Worldcraft failed on node link creation...\n" );
		}
		else if (status == Editor_OK)
		{
			// Don't actually destroy the dynamic link while editing.  Just mark the link
			pAILink->m_LinkInfo &= ~bits_LINK_OFF;

			CAI_DynamicLink* pDynamicLink = CAI_DynamicLink::GetDynamicLink(pAILink->m_iSrcID, pAILink->m_iDestID);
			UTIL_Remove(pDynamicLink);
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: For destroying links in wc edit mode.  Actually have to create
//			a link in WC that is marked as off
// Input  :
// Output :
//-----------------------------------------------------------------------------
void NWCEdit::DestroyAILink( CBasePlayer *pPlayer )
{
	// -------------------------------------------------------------
	//  Check that WC is running with the right map version
	// -------------------------------------------------------------
	if (!IsWCVersionValid())
	{
		return;
	}

	CAI_Link* pAILink = FindPickerAILink(pPlayer);
	if (pAILink)
	{
		int nWCSrcID = g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable[pAILink->m_iSrcID];
		int nWCDstID = g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable[pAILink->m_iDestID];
		int	status	 = Editor_CreateNodeLink(nWCSrcID, nWCDstID, false);

		if (status == Editor_BadCommand)
		{
			Msg( "Worldcraft failed on node link creation...\n" );
		}
		else if (status == Editor_OK)
		{
			// Create dynamic link and mark the link
			CAI_DynamicLink* pNewLink	= (CAI_DynamicLink*)CreateEntityByName("info_node_link" );;
			pNewLink->m_nSrcID			= pAILink->m_iSrcID;
			pNewLink->m_nDestID			= pAILink->m_iDestID;
			pNewLink->m_nLinkState		= LINK_OFF;
			pAILink->m_LinkInfo |= bits_LINK_OFF;
		}
	}
}

Vector *g_EntityPositions = NULL;
QAngle *g_EntityOrientations = NULL;
string_t *g_EntityClassnames = NULL;

//-----------------------------------------------------------------------------
// Purpose: Saves the entity's position for future communication with Hammer
//-----------------------------------------------------------------------------
void NWCEdit::RememberEntityPosition( CBaseEntity *pEntity )
{
	if ( !(pEntity->ObjectCaps() & FCAP_WCEDIT_POSITION) )
		return;

	if ( !g_EntityPositions )
	{
		g_EntityPositions = new Vector[NUM_ENT_ENTRIES];
		g_EntityOrientations = new QAngle[NUM_ENT_ENTRIES];
		// have to save these too because some entities change the classname on spawn (e.g. prop_physics_override, physics_prop)
		g_EntityClassnames = new string_t[NUM_ENT_ENTRIES];
	}
	int index = pEntity->entindex();
	g_EntityPositions[index] = pEntity->GetAbsOrigin();
	g_EntityOrientations[index] = pEntity->GetAbsAngles();
	g_EntityClassnames[index] = pEntity->m_iClassname;
}

//-----------------------------------------------------------------------------
// Purpose: Sends Hammer an update to the current position
//-----------------------------------------------------------------------------
void NWCEdit::UpdateEntityPosition( CBaseEntity *pEntity )
{
	const Vector &newPos = pEntity->GetAbsOrigin();
	const QAngle &newAng = pEntity->GetAbsAngles();

	DevMsg( 1, "%s\n   origin %f %f %f\n   angles %f %f %f\n", pEntity->GetClassname(), newPos.x, newPos.y, newPos.z, newAng.x, newAng.y, newAng.z );
	if ( Ragdoll_IsPropRagdoll(pEntity) )
	{
		char tmp[2048];
		Ragdoll_GetAngleOverrideString( tmp, sizeof(tmp), pEntity );
		DevMsg( 1, "pose: %s\n", tmp );
	}

	if ( !(pEntity->ObjectCaps() & FCAP_WCEDIT_POSITION) )
		return;
	
	// can't do this unless in edit mode
	if ( !engine->IsInEditMode() )
		return;

	int entIndex = pEntity->entindex();
	Vector pos = g_EntityPositions[entIndex];
	EditorSendResult_t result = Editor_BadCommand;
	const char *pClassname = STRING(g_EntityClassnames[entIndex]);

	if ( pEntity->GetModel() && modelinfo->GetModelType(pEntity->GetModel()) == mod_brush )
	{
		QAngle xformAngles;
		RotationDelta( g_EntityOrientations[entIndex], newAng, &xformAngles );
		if ( xformAngles.Length() > 1e-4 )
		{
			result = Editor_RotateEntity( pClassname, pos.x, pos.y, pos.z, xformAngles );
		}
		else
		{
			// don't call through for an identity rotation, may just increase error
			result = Editor_OK;
		}
	}
	else
	{
		if ( Ragdoll_IsPropRagdoll(pEntity) )
		{
			char tmp[2048];
			Ragdoll_GetAngleOverrideString( tmp, sizeof(tmp), pEntity );
			result = Editor_SetKeyValue( pClassname, pos.x, pos.y, pos.z, "angleOverride", tmp );
			if ( result != Editor_OK )
				goto error;
		}
		result = Editor_SetKeyValue( pClassname, pos.x, pos.y, pos.z, "angles", CFmtStr("%f %f %f", newAng.x, newAng.y, newAng.z) );
	}
	if ( result != Editor_OK )
		goto error;

	result = Editor_SetKeyValue( pClassname, pos.x, pos.y, pos.z, "origin", CFmtStr("%f %f %f", newPos.x, newPos.y, newPos.z) );
	if ( result != Editor_OK )
		goto error;

	NDebugOverlay::EntityBounds(pEntity, 0, 255, 0, 0 ,5);
	// save the update
	RememberEntityPosition( pEntity );
	return;

error:
	NDebugOverlay::EntityBounds(pEntity, 255, 0, 0, 0 ,5);
}

//------------------------------------------------------------------------------
// Purpose : 
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CC_WC_Create( void )
{
	// Only allowed in wc_edit_mode
	if (engine->IsInEditMode())
	{
		CBaseEntity::m_nDebugPlayer = UTIL_GetCommandClientIndex();

		if (g_pAINetworkManager->GetEditOps()->m_bLinkEditMode)
		{
			NWCEdit::CreateAILink(UTIL_GetCommandClient());
		}
		else
		{
			NWCEdit::CreateAINode(UTIL_GetCommandClient());
		}
	}
}
static ConCommand wc_create("wc_create", CC_WC_Create, "When in WC edit mode, creates a node where the player is looking if a node is allowed at that location for the currently selected hull size (see ai_next_hull)", FCVAR_CHEAT);

//------------------------------------------------------------------------------
// Purpose : 
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CC_WC_Destroy( void )
{
	// Only allowed in wc_edit_mode
	if (engine->IsInEditMode())
	{
		CBaseEntity::m_nDebugPlayer = UTIL_GetCommandClientIndex();

		// UNDONE: For now just deal with info_nodes
		//CBaseEntity* pEntity = FindEntity( pEdict, ""); - use when generalize this to any class
		//int status = Editor_DeleteEntity("info_node", pEdict->origin.x, pEdict->origin.y, pEdict->origin.z, false);

		if (g_pAINetworkManager->GetEditOps()->m_bLinkEditMode)
		{
			NWCEdit::DestroyAILink(UTIL_GetCommandClient());
		}
		else
		{
			NWCEdit::DestroyAINode(UTIL_GetCommandClient());
		}
	}
}
static ConCommand wc_destroy("wc_destroy", CC_WC_Destroy, "When in WC edit mode, destroys the node that the player is nearest to looking at.  (The node will be highlighted by a red box).", FCVAR_CHEAT);

//------------------------------------------------------------------------------
// Purpose : 
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CC_WC_DestroyUndo( void )
{
	// Only allowed in wc_edit_mode
	if (engine->IsInEditMode())
	{
		CBaseEntity::m_nDebugPlayer = UTIL_GetCommandClientIndex();

		NWCEdit::UndoDestroyAINode();
	}
}
static ConCommand wc_destroy_undo("wc_destroy_undo", CC_WC_DestroyUndo, "When in WC edit mode restores the last deleted node", FCVAR_CHEAT);

//------------------------------------------------------------------------------
// Purpose : 
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CC_WC_AirNodeEdit( void )
{
	// Only allowed in wc_edit_mode
	if (engine->IsInEditMode())
	{
		// Toggle air edit mode state
		if (g_pAINetworkManager->GetEditOps()->m_bAirEditMode)
		{
			g_pAINetworkManager->GetEditOps()->m_bAirEditMode = false;
		}
		else
		{
			g_pAINetworkManager->GetEditOps()->m_bAirEditMode = true;
		}
	}
}
static ConCommand wc_air_node_edit("wc_air_node_edit", CC_WC_AirNodeEdit, "When in WC edit mode, toggles laying down or air nodes instead of ground nodes", FCVAR_CHEAT);

//------------------------------------------------------------------------------
// Purpose : 
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CC_WC_AirNodeEditFurther( void )
{
	// Only allowed in wc_edit_mode
	if (engine->IsInEditMode() && g_pAINetworkManager->GetEditOps()->m_bAirEditMode)
	{
		g_pAINetworkManager->GetEditOps()->m_flAirEditDistance += 10.0;
	}
}
static ConCommand wc_air_edit_further("wc_air_edit_further", CC_WC_AirNodeEditFurther, "When in WC edit mode and editing air nodes,  moves position of air node crosshair and placement location further away from player", FCVAR_CHEAT);

//------------------------------------------------------------------------------
// Purpose : 
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CC_WC_AirNodeEditNearer( void )
{
	// Only allowed in wc_edit_mode
	if (engine->IsInEditMode() && g_pAINetworkManager->GetEditOps()->m_bAirEditMode)
	{
		g_pAINetworkManager->GetEditOps()->m_flAirEditDistance -= 10.0;
	}
}
static ConCommand wc_air_edit_nearer("wc_air_edit_nearer", CC_WC_AirNodeEditNearer, "When in WC edit mode and editing air nodes,  moves position of air node crosshair and placement location nearer to from player", FCVAR_CHEAT);

//------------------------------------------------------------------------------
// Purpose : 
// Input   :
// Output  :
//------------------------------------------------------------------------------
void CC_WC_LinkEdit( void )
{
	// Only allowed in wc_edit_mode 
	if (engine->IsInEditMode())
	{
		// Toggle air edit mode state
		if (g_pAINetworkManager->GetEditOps()->m_bLinkEditMode)
		{
			g_pAINetworkManager->GetEditOps()->m_bLinkEditMode = false;
		}
		// Don't allow link mode if graph outdated
		else if (!(g_pAINetworkManager->GetEditOps()->m_debugNetOverlays & bits_debugNeedRebuild))
		{
			g_pAINetworkManager->GetEditOps()->m_bLinkEditMode = true;
		}
	}
}
static ConCommand wc_link_edit("wc_link_edit", CC_WC_LinkEdit, 0, FCVAR_CHEAT);


/// This is an entity used by the hammer_update_safe_entities command. It allows designers
/// to specify objects that should be ignored. It stores an array of sixteen strings
/// which may correspond to names. Designers may ignore more than sixteen objects by 
/// placing more than one of these in a level.
class CWC_UpdateIgnoreList : public CBaseEntity
{
public:
	DECLARE_CLASS( CWC_UpdateIgnoreList, CBaseEntity );

	enum { MAX_IGNORELIST_NAMES = 16 }; ///< the number of names in the array below

	inline const string_t &GetName( int x ) const { return m_nIgnoredEntityNames[x]; } 

protected:
	// the list of names to ignore
	string_t m_nIgnoredEntityNames[MAX_IGNORELIST_NAMES]; 

public:
	DECLARE_DATADESC();
};


LINK_ENTITY_TO_CLASS( hammer_updateignorelist, CWC_UpdateIgnoreList );

BEGIN_DATADESC( CWC_UpdateIgnoreList )

	// Be still, classcheck!
	//DEFINE_FIELD( m_nIgnoredEntityNames, FIELD_STRING, MAX_IGNORELIST_NAMES ),

	DEFINE_KEYFIELD( m_nIgnoredEntityNames[0], FIELD_STRING, "IgnoredName01" ),
	DEFINE_KEYFIELD( m_nIgnoredEntityNames[1], FIELD_STRING, "IgnoredName02" ),
	DEFINE_KEYFIELD( m_nIgnoredEntityNames[2], FIELD_STRING, "IgnoredName03" ),
	DEFINE_KEYFIELD( m_nIgnoredEntityNames[3], FIELD_STRING, "IgnoredName04" ),
	DEFINE_KEYFIELD( m_nIgnoredEntityNames[4], FIELD_STRING, "IgnoredName05" ),
	DEFINE_KEYFIELD( m_nIgnoredEntityNames[5], FIELD_STRING, "IgnoredName06" ),
	DEFINE_KEYFIELD( m_nIgnoredEntityNames[6], FIELD_STRING, "IgnoredName07" ),
	DEFINE_KEYFIELD( m_nIgnoredEntityNames[7], FIELD_STRING, "IgnoredName08" ),
	DEFINE_KEYFIELD( m_nIgnoredEntityNames[8], FIELD_STRING, "IgnoredName09" ),
	DEFINE_KEYFIELD( m_nIgnoredEntityNames[9], FIELD_STRING, "IgnoredName10" ),
	DEFINE_KEYFIELD( m_nIgnoredEntityNames[10], FIELD_STRING, "IgnoredName11" ),
	DEFINE_KEYFIELD( m_nIgnoredEntityNames[11], FIELD_STRING, "IgnoredName12" ),
	DEFINE_KEYFIELD( m_nIgnoredEntityNames[12], FIELD_STRING, "IgnoredName13" ),
	DEFINE_KEYFIELD( m_nIgnoredEntityNames[13], FIELD_STRING, "IgnoredName14" ),
	DEFINE_KEYFIELD( m_nIgnoredEntityNames[14], FIELD_STRING, "IgnoredName15" ),
	DEFINE_KEYFIELD( m_nIgnoredEntityNames[15], FIELD_STRING, "IgnoredName16" ),

END_DATADESC()



CON_COMMAND( hammer_update_entity, "Updates the entity's position/angles when in edit mode" )
{
	if ( !UTIL_IsCommandIssuedByServerAdmin() )
		return;

	if ( args.ArgC() < 2 )
	{
		CBasePlayer *pPlayer = UTIL_GetCommandClient();
		trace_t tr;
		Vector forward;
		pPlayer->EyeVectors( &forward );
		UTIL_TraceLine(pPlayer->EyePosition(), pPlayer->EyePosition() + forward * MAX_COORD_RANGE,
			MASK_SHOT_HULL|CONTENTS_GRATE|CONTENTS_DEBRIS, pPlayer, COLLISION_GROUP_NONE, &tr );

		if ( tr.DidHit() && !tr.DidHitWorld() )
		{
			NWCEdit::UpdateEntityPosition( tr.m_pEnt );
		}
	}
	else
	{
		CBaseEntity *pEnt = NULL;
		while ((pEnt = gEntList.FindEntityGeneric( pEnt, args[1] ) ) != NULL)
		{
			NWCEdit::UpdateEntityPosition( pEnt );
		}
	}
}

CON_COMMAND( hammer_update_safe_entities, "Updates entities in the map that can safely be updated (don't have parents or are affected by constraints). Also excludes entities mentioned in any hammer_updateignorelist objects in this map." )
{
	int iCount = 0;
	CBaseEntity *pEnt = NULL;

	if ( !UTIL_IsCommandIssuedByServerAdmin() )
		return;

	Msg("\n====================================================\nPerforming Safe Entity Update\n" );

	// first look for any exclusion objects -- these are entities that list specific things to be ignored.
	// All the names that are inside them, we store into a hash table (here implemented through a local
	// CUtlSymbolTable)

	CUtlSymbolTable ignoredNames(16,32,true); // grow 16 strings at a time. Case insensitive.
	while ( (pEnt = gEntList.FindEntityByClassname( pEnt, "hammer_updateignorelist" )) != NULL )
	{
		// for each name in each of those strings, add it to the symbol table.
		CWC_UpdateIgnoreList *piglist = static_cast<CWC_UpdateIgnoreList *>(pEnt);
		for (int ii = 0 ; ii < CWC_UpdateIgnoreList::MAX_IGNORELIST_NAMES ; ++ii )
		{
			if (!!piglist->GetName(ii))  // if not null
			{	// add to symtab
				ignoredNames.AddString(piglist->GetName(ii).ToCStr());
			}
		}
	}

	if ( ignoredNames.GetNumStrings() > 0 )
	{
		Msg( "Ignoring %d specified targetnames.\n", ignoredNames.GetNumStrings() );
	}


	// now iterate through everything in the world
	for ( pEnt = gEntList.FirstEnt(); pEnt != NULL; pEnt = gEntList.NextEnt(pEnt) )
	{
		if ( !(pEnt->ObjectCaps() & FCAP_WCEDIT_POSITION) )
			continue;

		// If we have a parent, or any children, we're not safe to update
		if ( pEnt->GetMoveParent() || pEnt->FirstMoveChild() )
			continue;

		IPhysicsObject *pPhysics = pEnt->VPhysicsGetObject();
		if ( !pPhysics )
			continue;
		// If we are affected by any constraints, we're not safe to update
		if ( pPhysics->IsAttachedToConstraint( Ragdoll_IsPropRagdoll(pEnt) )  )
			continue;
		// Motion disabled?
		if ( !pPhysics->IsMoveable() )
			continue;

		// ignore brush models (per bug 61318)
		if ( dynamic_cast<CPhysBox *>(pEnt) )
			continue;

		// explicitly excluded by designer?
		if ( ignoredNames.Find(pEnt->GetEntityName().ToCStr()).IsValid() )
			continue;

		NWCEdit::UpdateEntityPosition( pEnt );
		iCount++;
	}

	Msg("Updated %d entities.\n", iCount);
}