//========= Copyright © 1996-2005, 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 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 ) { AssertMsg( false, "Air node placement in wcedit broken in Left4Dead." ); 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; } // <> 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 = pPlayer->FindPickerAINode(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() ||!pPlayer) { return; } CAI_Link* pAILink = pPlayer->FindPickerAILink(); 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() || !pPlayer) { return; } CAI_Link* pAILink = pPlayer->FindPickerAILink(); 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(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(pEnt) ) continue; // explicitly excluded by designer? if ( ignoredNames.Find(pEnt->GetEntityName().ToCStr()).IsValid() ) continue; NWCEdit::UpdateEntityPosition( pEnt ); iCount++; } Msg("Updated %d entities.\n", iCount); }