Counter Strike : Global Offensive Source Code
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.

837 lines
28 KiB

  1. //========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Namespace for functions having to do with WC Edit mode
  4. //
  5. // $Workfile: $
  6. // $Date: $
  7. //
  8. //-----------------------------------------------------------------------------
  9. // $Log: $
  10. //
  11. // $NoKeywords: $
  12. //=============================================================================//
  13. #include "cbase.h"
  14. #include "mathlib/mathlib.h"
  15. #include "player.h"
  16. #include "wcedit.h"
  17. #include "ai_network.h"
  18. #include "ai_initutils.h"
  19. #include "ai_hull.h"
  20. #include "ai_link.h"
  21. #include "ai_node.h"
  22. #include "ai_dynamiclink.h"
  23. #include "ai_networkmanager.h"
  24. #include "ndebugoverlay.h"
  25. #include "editor_sendcommand.h"
  26. #include "movevars_shared.h"
  27. #include "model_types.h"
  28. // UNDONE: Reduce some dependency here!
  29. #include "physics_prop_ragdoll.h"
  30. #include "items.h"
  31. #include "utlsymbol.h"
  32. #include "physobj.h"
  33. // memdbgon must be the last include file in a .cpp file!!!
  34. #include "tier0/memdbgon.h"
  35. extern float GetFloorZ(const Vector &origin);
  36. //-----------------------------------------------------------------------------
  37. // Purpose: Make sure the version of the map in WC is the same as the map
  38. // that's being edited
  39. // Input :
  40. // Output :
  41. //-----------------------------------------------------------------------------
  42. bool NWCEdit::IsWCVersionValid(void)
  43. {
  44. int status = Editor_CheckVersion(STRING(gpGlobals->mapname), gpGlobals->mapversion, false);
  45. if (!status)
  46. {
  47. return true;
  48. }
  49. else if (status == Editor_NotRunning)
  50. {
  51. Msg("\nAborting map_edit\nWorldcraft not running...\n\n");
  52. UTIL_CenterPrintAll( "Worldcraft not running..." );
  53. engine->ServerCommand("disconnect\n");
  54. }
  55. else
  56. {
  57. Msg("\nAborting map_edit\nWC/Engine map versions different...\n\n");
  58. UTIL_CenterPrintAll( "WC/Engine map versions different..." );
  59. engine->ServerCommand("disconnect\n");
  60. }
  61. return false;
  62. }
  63. //------------------------------------------------------------------------------
  64. // Purpose : Figure out placement position of air nodes form where player is
  65. // looking. Keep distance from player constant but adjust height
  66. // based on viewing angle
  67. // Input :
  68. // Output :
  69. //------------------------------------------------------------------------------
  70. Vector NWCEdit::AirNodePlacementPosition( void )
  71. {
  72. AssertMsg( false, "Air node placement in wcedit broken in Left4Dead." );
  73. CBasePlayer* pPlayer = UTIL_PlayerByIndex(CBaseEntity::m_nDebugPlayer);
  74. if (!pPlayer)
  75. {
  76. return vec3_origin;
  77. }
  78. Vector pForward;
  79. pPlayer->EyeVectors( &pForward );
  80. Vector floorVec = pForward;
  81. floorVec.z = 0;
  82. VectorNormalize( floorVec );
  83. VectorNormalize( pForward );
  84. float cosAngle = DotProduct(floorVec,pForward);
  85. float lookDist = g_pAINetworkManager->GetEditOps()->m_flAirEditDistance/cosAngle;
  86. Vector lookPos = pPlayer->EyePosition()+pForward * lookDist;
  87. return lookPos;
  88. }
  89. //-----------------------------------------------------------------------------
  90. // Purpose: For create nodes in wc edit mode
  91. // Input :
  92. // Output :
  93. //-----------------------------------------------------------------------------
  94. void NWCEdit::CreateAINode( CBasePlayer *pPlayer )
  95. {
  96. // -------------------------------------------------------------
  97. // Check that WC is running with the right map version
  98. // -------------------------------------------------------------
  99. if ( !IsWCVersionValid() || !pPlayer )
  100. return;
  101. pPlayer->AddSolidFlags( FSOLID_NOT_SOLID );
  102. int hullType = g_pAINetworkManager->GetEditOps()->m_iHullDrawNum;
  103. // -----------------------------------
  104. // Get position of node to create
  105. // -----------------------------------
  106. Vector vNewNodePos = vec3_origin;
  107. bool bPositionValid = false;
  108. if (g_pAINetworkManager->GetEditOps()->m_bAirEditMode)
  109. {
  110. vNewNodePos = NWCEdit::AirNodePlacementPosition();
  111. // Make sure we can see the node
  112. trace_t tr;
  113. UTIL_TraceLine(pPlayer->EyePosition(), vNewNodePos, MASK_NPCSOLID_BRUSHONLY, pPlayer, COLLISION_GROUP_NONE, &tr);
  114. if (tr.fraction == 1.0)
  115. {
  116. bPositionValid = true;
  117. }
  118. }
  119. else
  120. {
  121. // Place node by where the player is looking
  122. Vector forward;
  123. pPlayer->EyeVectors( &forward );
  124. Vector startTrace = pPlayer->EyePosition();
  125. Vector endTrace = pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH;
  126. trace_t tr;
  127. UTIL_TraceLine(startTrace,endTrace,MASK_NPCSOLID, pPlayer, COLLISION_GROUP_NONE, &tr );
  128. if ( tr.fraction != 1.0)
  129. {
  130. // Raise the end position up off the floor, place the node and drop him down
  131. tr.endpos.z += 48;
  132. vNewNodePos = tr.endpos;
  133. bPositionValid = true;
  134. }
  135. }
  136. // -------------------------------------------------------------------------------
  137. // Now check that this is a valid location for the new node bu using test hull
  138. // -------------------------------------------------------------------------------
  139. if (bPositionValid)
  140. {
  141. CBaseEntity *testHull = (CBaseEntity*)CAI_TestHull::GetTestHull();
  142. // Set the size of the test hull
  143. UTIL_SetSize(testHull, NAI_Hull::Mins(hullType), NAI_Hull::Maxs(hullType));
  144. // Set origin of test hull
  145. testHull->SetLocalOrigin( vNewNodePos );
  146. // -----------------------------------------------------------------------
  147. // If a ground node, drop to floor and make sure can stand at test postion
  148. // -----------------------------------------------------------------------
  149. if (!g_pAINetworkManager->GetEditOps()->m_bAirEditMode)
  150. {
  151. UTIL_DropToFloor( testHull, MASK_NPCSOLID );
  152. vNewNodePos = testHull->GetAbsOrigin();
  153. CTraceFilterSimple traceFilter( testHull, COLLISION_GROUP_NONE );
  154. if (!UTIL_CheckBottom(testHull, &traceFilter, sv_stepsize.GetFloat()))
  155. {
  156. CAI_TestHull::ReturnTestHull();
  157. bPositionValid = false;
  158. goto DoneCreate;
  159. }
  160. }
  161. // -----------------------------------------------------------------------
  162. // Make sure hull fits at location by seeing if it can move up a fraction
  163. // -----------------------------------------------------------------------
  164. Vector vUpBit = testHull->GetAbsOrigin();
  165. vUpBit.z += 1;
  166. trace_t tr;
  167. UTIL_TraceHull( testHull->GetAbsOrigin(), vUpBit, NAI_Hull::Mins(hullType),
  168. NAI_Hull::Maxs(hullType), MASK_NPCSOLID, testHull, COLLISION_GROUP_NONE, &tr );
  169. if (tr.startsolid || tr.fraction != 1.0)
  170. {
  171. CAI_TestHull::ReturnTestHull();
  172. bPositionValid = false;
  173. goto DoneCreate;
  174. }
  175. // <<TEMP>> Round position till DS fixed WC bug
  176. testHull->SetLocalOrigin( Vector( floor(testHull->GetAbsOrigin().x),
  177. floor(testHull->GetAbsOrigin().y ), floor(testHull->GetAbsOrigin().z) ) );
  178. // ---------------------------------------
  179. // Send new node to WC
  180. // ---------------------------------------
  181. int status;
  182. if (g_pAINetworkManager->GetEditOps()->m_bAirEditMode)
  183. {
  184. status = Editor_CreateNode("info_node_air", g_pAINetworkManager->GetEditOps()->m_nNextWCIndex, testHull->GetLocalOrigin().x, testHull->GetLocalOrigin().y, testHull->GetLocalOrigin().z, false);
  185. }
  186. else
  187. {
  188. // Create slightly higher in WC so it can be dropped when its loaded again
  189. Vector origin = testHull->GetLocalOrigin();
  190. origin.z += 24.0;
  191. testHull->SetLocalOrigin( origin );
  192. status = Editor_CreateNode("info_node", g_pAINetworkManager->GetEditOps()->m_nNextWCIndex, testHull->GetLocalOrigin().x, testHull->GetLocalOrigin().y, testHull->GetLocalOrigin().z, false);
  193. }
  194. if (status == Editor_BadCommand)
  195. {
  196. Msg( "Worldcraft failed on creation...\n" );
  197. CAI_TestHull::ReturnTestHull();
  198. }
  199. else if (status == Editor_OK)
  200. {
  201. // -----------------------
  202. // Create a new ai node
  203. // -----------------------
  204. CNodeEnt *pNodeEnt;
  205. if (g_pAINetworkManager->GetEditOps()->m_bAirEditMode)
  206. {
  207. pNodeEnt = (CNodeEnt*)CreateEntityByName("info_node_air");
  208. }
  209. else
  210. {
  211. pNodeEnt = (CNodeEnt*)CreateEntityByName("info_node");
  212. }
  213. // Note this is a new entity being created as part of wc editing
  214. pNodeEnt->SetLocalOrigin( testHull->GetLocalOrigin() );
  215. CAI_TestHull::ReturnTestHull();
  216. pNodeEnt->m_NodeData.nWCNodeID = g_pAINetworkManager->GetEditOps()->m_nNextWCIndex;
  217. pNodeEnt->m_debugOverlays |= OVERLAY_WC_CHANGE_ENTITY;
  218. pNodeEnt->Spawn();
  219. }
  220. }
  221. DoneCreate:
  222. // ----------------------------------------------------------
  223. // Flash a red box as a warning that the hull won't fit here
  224. // ----------------------------------------------------------
  225. if (!bPositionValid)
  226. {
  227. NDebugOverlay::Box(vNewNodePos, NAI_Hull::Mins(hullType), NAI_Hull::Maxs(hullType), 255,0,0,0,0.1);
  228. }
  229. // Restore player collidability
  230. pPlayer->SetSolid( SOLID_BBOX );
  231. }
  232. //-----------------------------------------------------------------------------
  233. // Purpose:
  234. // Input :
  235. // Output :
  236. //-----------------------------------------------------------------------------
  237. void NWCEdit::UndoDestroyAINode(void)
  238. {
  239. // -------------------------------------------------------------
  240. // Check that WC is running with the right map version
  241. // -------------------------------------------------------------
  242. if (!IsWCVersionValid())
  243. {
  244. return;
  245. }
  246. if (g_pAINetworkManager->GetEditOps()->m_pLastDeletedNode)
  247. {
  248. Vector nodePos = g_pAINetworkManager->GetEditOps()->m_pLastDeletedNode->GetOrigin();
  249. int status;
  250. int nOldWCID = g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable[g_pAINetworkManager->GetEditOps()->m_pLastDeletedNode->GetId()];
  251. if (g_pAINetworkManager->GetEditOps()->m_bAirEditMode)
  252. {
  253. status = Editor_CreateNode("info_node_air", nOldWCID, nodePos.x, nodePos.y, nodePos.z, false);
  254. }
  255. else
  256. {
  257. status = Editor_CreateNode("info_node", nOldWCID, nodePos.x, nodePos.y, nodePos.z, false);
  258. }
  259. if (status == Editor_BadCommand)
  260. {
  261. Msg( "Worldcraft failed on creation...\n" );
  262. }
  263. else if (status == Editor_OK)
  264. {
  265. g_pAINetworkManager->GetEditOps()->m_pLastDeletedNode->SetType( NODE_GROUND );
  266. //@ tofo g_pAINetworkManager->GetEditOps()->m_pLastDeletedNode->m_pNetwork->BuildNetworkGraph();
  267. g_pAINetworkManager->BuildNetworkGraph();
  268. g_pAINetworkManager->GetEditOps()->m_pLastDeletedNode = NULL;
  269. }
  270. }
  271. }
  272. //-----------------------------------------------------------------------------
  273. // Purpose: For destroying nodes in wc edit mode
  274. // Input :
  275. // Output :
  276. //-----------------------------------------------------------------------------
  277. void NWCEdit::DestroyAINode( CBasePlayer *pPlayer )
  278. {
  279. // -------------------------------------------------------------
  280. // Check that WC is running with the right map version
  281. // -------------------------------------------------------------
  282. if (!IsWCVersionValid())
  283. {
  284. return;
  285. }
  286. if (!pPlayer)
  287. {
  288. return;
  289. }
  290. NodeType_e nNodeType = NODE_GROUND;
  291. if (g_pAINetworkManager->GetEditOps()->m_bAirEditMode)
  292. {
  293. nNodeType = NODE_AIR;
  294. }
  295. CAI_Node* pAINode = pPlayer->FindPickerAINode(nNodeType);
  296. if (pAINode)
  297. {
  298. int status = Editor_DeleteNode(g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable[pAINode->GetId()], false);
  299. if (status == Editor_BadCommand)
  300. {
  301. Msg( "Worldcraft failed on deletion...\n" );
  302. }
  303. else if (status == Editor_OK)
  304. {
  305. // Mark this node as deleted and changed
  306. pAINode->SetType( NODE_DELETED );
  307. pAINode->m_eNodeInfo |= bits_NODE_WC_CHANGED;
  308. // Note that network needs to be rebuild
  309. g_pAINetworkManager->GetEditOps()->SetRebuildFlags();
  310. g_pAINetworkManager->GetEditOps()->m_pLastDeletedNode = pAINode;
  311. // Now go through at delete any dynamic links that were attached to this node
  312. for (int link = 0; link < pAINode->NumLinks(); link++)
  313. {
  314. int nSrcID = pAINode->GetLinkByIndex(link)->m_iSrcID;
  315. int nDstID = pAINode->GetLinkByIndex(link)->m_iDestID;
  316. if (CAI_DynamicLink::GetDynamicLink(nSrcID, nDstID))
  317. {
  318. int nWCSrcID = g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable[nSrcID];
  319. int nWCDstID = g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable[nDstID];
  320. int status = Editor_DeleteNodeLink(nWCSrcID, nWCDstID);
  321. if (status == Editor_BadCommand)
  322. {
  323. Msg( "Worldcraft failed on node link deletion...\n" );
  324. }
  325. }
  326. }
  327. }
  328. }
  329. }
  330. //-----------------------------------------------------------------------------
  331. // Purpose: For restroring links in WC edit mode. This actually means
  332. // destroying links in WC that have been marked as
  333. // Input :
  334. // Output :
  335. //-----------------------------------------------------------------------------
  336. void NWCEdit::CreateAILink( CBasePlayer* pPlayer )
  337. {
  338. // -------------------------------------------------------------
  339. // Check that WC is running with the right map version
  340. // -------------------------------------------------------------
  341. if (!IsWCVersionValid() ||!pPlayer)
  342. {
  343. return;
  344. }
  345. CAI_Link* pAILink = pPlayer->FindPickerAILink();
  346. if (pAILink && (pAILink->m_LinkInfo & bits_LINK_OFF))
  347. {
  348. int nWCSrcID = g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable[pAILink->m_iSrcID];
  349. int nWCDstID = g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable[pAILink->m_iDestID];
  350. int status = Editor_DeleteNodeLink(nWCSrcID, nWCDstID, false);
  351. if (status == Editor_BadCommand)
  352. {
  353. Msg( "Worldcraft failed on node link creation...\n" );
  354. }
  355. else if (status == Editor_OK)
  356. {
  357. // Don't actually destroy the dynamic link while editing. Just mark the link
  358. pAILink->m_LinkInfo &= ~bits_LINK_OFF;
  359. CAI_DynamicLink* pDynamicLink = CAI_DynamicLink::GetDynamicLink(pAILink->m_iSrcID, pAILink->m_iDestID);
  360. UTIL_Remove(pDynamicLink);
  361. }
  362. }
  363. }
  364. //-----------------------------------------------------------------------------
  365. // Purpose: For destroying links in wc edit mode. Actually have to create
  366. // a link in WC that is marked as off
  367. // Input :
  368. // Output :
  369. //-----------------------------------------------------------------------------
  370. void NWCEdit::DestroyAILink( CBasePlayer *pPlayer )
  371. {
  372. // -------------------------------------------------------------
  373. // Check that WC is running with the right map version
  374. // -------------------------------------------------------------
  375. if (!IsWCVersionValid() || !pPlayer)
  376. {
  377. return;
  378. }
  379. CAI_Link* pAILink = pPlayer->FindPickerAILink();
  380. if (pAILink)
  381. {
  382. int nWCSrcID = g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable[pAILink->m_iSrcID];
  383. int nWCDstID = g_pAINetworkManager->GetEditOps()->m_pNodeIndexTable[pAILink->m_iDestID];
  384. int status = Editor_CreateNodeLink(nWCSrcID, nWCDstID, false);
  385. if (status == Editor_BadCommand)
  386. {
  387. Msg( "Worldcraft failed on node link creation...\n" );
  388. }
  389. else if (status == Editor_OK)
  390. {
  391. // Create dynamic link and mark the link
  392. CAI_DynamicLink* pNewLink = (CAI_DynamicLink*)CreateEntityByName("info_node_link" );;
  393. pNewLink->m_nSrcID = pAILink->m_iSrcID;
  394. pNewLink->m_nDestID = pAILink->m_iDestID;
  395. pNewLink->m_nLinkState = LINK_OFF;
  396. pAILink->m_LinkInfo |= bits_LINK_OFF;
  397. }
  398. }
  399. }
  400. Vector *g_EntityPositions = NULL;
  401. QAngle *g_EntityOrientations = NULL;
  402. string_t *g_EntityClassnames = NULL;
  403. //-----------------------------------------------------------------------------
  404. // Purpose: Saves the entity's position for future communication with Hammer
  405. //-----------------------------------------------------------------------------
  406. void NWCEdit::RememberEntityPosition( CBaseEntity *pEntity )
  407. {
  408. if ( !(pEntity->ObjectCaps() & FCAP_WCEDIT_POSITION) )
  409. return;
  410. if ( !g_EntityPositions )
  411. {
  412. g_EntityPositions = new Vector[NUM_ENT_ENTRIES];
  413. g_EntityOrientations = new QAngle[NUM_ENT_ENTRIES];
  414. // have to save these too because some entities change the classname on spawn (e.g. prop_physics_override, physics_prop)
  415. g_EntityClassnames = new string_t[NUM_ENT_ENTRIES];
  416. }
  417. int index = pEntity->entindex();
  418. g_EntityPositions[index] = pEntity->GetAbsOrigin();
  419. g_EntityOrientations[index] = pEntity->GetAbsAngles();
  420. g_EntityClassnames[index] = pEntity->m_iClassname;
  421. }
  422. //-----------------------------------------------------------------------------
  423. // Purpose: Sends Hammer an update to the current position
  424. //-----------------------------------------------------------------------------
  425. void NWCEdit::UpdateEntityPosition( CBaseEntity *pEntity )
  426. {
  427. const Vector &newPos = pEntity->GetAbsOrigin();
  428. const QAngle &newAng = pEntity->GetAbsAngles();
  429. 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 );
  430. if ( Ragdoll_IsPropRagdoll(pEntity) )
  431. {
  432. char tmp[2048];
  433. Ragdoll_GetAngleOverrideString( tmp, sizeof(tmp), pEntity );
  434. DevMsg( 1, "pose: %s\n", tmp );
  435. }
  436. if ( !(pEntity->ObjectCaps() & FCAP_WCEDIT_POSITION) )
  437. return;
  438. // can't do this unless in edit mode
  439. if ( !engine->IsInEditMode() )
  440. return;
  441. int entIndex = pEntity->entindex();
  442. Vector pos = g_EntityPositions[entIndex];
  443. EditorSendResult_t result = Editor_BadCommand;
  444. const char *pClassname = STRING(g_EntityClassnames[entIndex]);
  445. if ( pEntity->GetModel() && modelinfo->GetModelType(pEntity->GetModel()) == mod_brush )
  446. {
  447. QAngle xformAngles;
  448. RotationDelta( g_EntityOrientations[entIndex], newAng, &xformAngles );
  449. if ( xformAngles.Length() > 1e-4 )
  450. {
  451. result = Editor_RotateEntity( pClassname, pos.x, pos.y, pos.z, xformAngles );
  452. }
  453. else
  454. {
  455. // don't call through for an identity rotation, may just increase error
  456. result = Editor_OK;
  457. }
  458. }
  459. else
  460. {
  461. if ( Ragdoll_IsPropRagdoll(pEntity) )
  462. {
  463. char tmp[2048];
  464. Ragdoll_GetAngleOverrideString( tmp, sizeof(tmp), pEntity );
  465. result = Editor_SetKeyValue( pClassname, pos.x, pos.y, pos.z, "angleOverride", tmp );
  466. if ( result != Editor_OK )
  467. goto error;
  468. }
  469. result = Editor_SetKeyValue( pClassname, pos.x, pos.y, pos.z, "angles", CFmtStr("%f %f %f", newAng.x, newAng.y, newAng.z) );
  470. }
  471. if ( result != Editor_OK )
  472. goto error;
  473. result = Editor_SetKeyValue( pClassname, pos.x, pos.y, pos.z, "origin", CFmtStr("%f %f %f", newPos.x, newPos.y, newPos.z) );
  474. if ( result != Editor_OK )
  475. goto error;
  476. NDebugOverlay::EntityBounds(pEntity, 0, 255, 0, 0 ,5);
  477. // save the update
  478. RememberEntityPosition( pEntity );
  479. return;
  480. error:
  481. NDebugOverlay::EntityBounds(pEntity, 255, 0, 0, 0 ,5);
  482. }
  483. //------------------------------------------------------------------------------
  484. // Purpose :
  485. // Input :
  486. // Output :
  487. //------------------------------------------------------------------------------
  488. void CC_WC_Create( void )
  489. {
  490. // Only allowed in wc_edit_mode
  491. if (engine->IsInEditMode())
  492. {
  493. CBaseEntity::m_nDebugPlayer = UTIL_GetCommandClientIndex();
  494. if (g_pAINetworkManager->GetEditOps()->m_bLinkEditMode)
  495. {
  496. NWCEdit::CreateAILink(UTIL_GetCommandClient());
  497. }
  498. else
  499. {
  500. NWCEdit::CreateAINode(UTIL_GetCommandClient());
  501. }
  502. }
  503. }
  504. 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);
  505. //------------------------------------------------------------------------------
  506. // Purpose :
  507. // Input :
  508. // Output :
  509. //------------------------------------------------------------------------------
  510. void CC_WC_Destroy( void )
  511. {
  512. // Only allowed in wc_edit_mode
  513. if (engine->IsInEditMode())
  514. {
  515. CBaseEntity::m_nDebugPlayer = UTIL_GetCommandClientIndex();
  516. // UNDONE: For now just deal with info_nodes
  517. //CBaseEntity* pEntity = FindEntity( pEdict, ""); - use when generalize this to any class
  518. //int status = Editor_DeleteEntity("info_node", pEdict->origin.x, pEdict->origin.y, pEdict->origin.z, false);
  519. if (g_pAINetworkManager->GetEditOps()->m_bLinkEditMode)
  520. {
  521. NWCEdit::DestroyAILink(UTIL_GetCommandClient());
  522. }
  523. else
  524. {
  525. NWCEdit::DestroyAINode(UTIL_GetCommandClient());
  526. }
  527. }
  528. }
  529. 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);
  530. //------------------------------------------------------------------------------
  531. // Purpose :
  532. // Input :
  533. // Output :
  534. //------------------------------------------------------------------------------
  535. void CC_WC_DestroyUndo( void )
  536. {
  537. // Only allowed in wc_edit_mode
  538. if (engine->IsInEditMode())
  539. {
  540. CBaseEntity::m_nDebugPlayer = UTIL_GetCommandClientIndex();
  541. NWCEdit::UndoDestroyAINode();
  542. }
  543. }
  544. static ConCommand wc_destroy_undo("wc_destroy_undo", CC_WC_DestroyUndo, "When in WC edit mode restores the last deleted node", FCVAR_CHEAT);
  545. //------------------------------------------------------------------------------
  546. // Purpose :
  547. // Input :
  548. // Output :
  549. //------------------------------------------------------------------------------
  550. void CC_WC_AirNodeEdit( void )
  551. {
  552. // Only allowed in wc_edit_mode
  553. if (engine->IsInEditMode())
  554. {
  555. // Toggle air edit mode state
  556. if (g_pAINetworkManager->GetEditOps()->m_bAirEditMode)
  557. {
  558. g_pAINetworkManager->GetEditOps()->m_bAirEditMode = false;
  559. }
  560. else
  561. {
  562. g_pAINetworkManager->GetEditOps()->m_bAirEditMode = true;
  563. }
  564. }
  565. }
  566. 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);
  567. //------------------------------------------------------------------------------
  568. // Purpose :
  569. // Input :
  570. // Output :
  571. //------------------------------------------------------------------------------
  572. void CC_WC_AirNodeEditFurther( void )
  573. {
  574. // Only allowed in wc_edit_mode
  575. if (engine->IsInEditMode() && g_pAINetworkManager->GetEditOps()->m_bAirEditMode)
  576. {
  577. g_pAINetworkManager->GetEditOps()->m_flAirEditDistance += 10.0;
  578. }
  579. }
  580. 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);
  581. //------------------------------------------------------------------------------
  582. // Purpose :
  583. // Input :
  584. // Output :
  585. //------------------------------------------------------------------------------
  586. void CC_WC_AirNodeEditNearer( void )
  587. {
  588. // Only allowed in wc_edit_mode
  589. if (engine->IsInEditMode() && g_pAINetworkManager->GetEditOps()->m_bAirEditMode)
  590. {
  591. g_pAINetworkManager->GetEditOps()->m_flAirEditDistance -= 10.0;
  592. }
  593. }
  594. 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);
  595. //------------------------------------------------------------------------------
  596. // Purpose :
  597. // Input :
  598. // Output :
  599. //------------------------------------------------------------------------------
  600. void CC_WC_LinkEdit( void )
  601. {
  602. // Only allowed in wc_edit_mode
  603. if (engine->IsInEditMode())
  604. {
  605. // Toggle air edit mode state
  606. if (g_pAINetworkManager->GetEditOps()->m_bLinkEditMode)
  607. {
  608. g_pAINetworkManager->GetEditOps()->m_bLinkEditMode = false;
  609. }
  610. // Don't allow link mode if graph outdated
  611. else if (!(g_pAINetworkManager->GetEditOps()->m_debugNetOverlays & bits_debugNeedRebuild))
  612. {
  613. g_pAINetworkManager->GetEditOps()->m_bLinkEditMode = true;
  614. }
  615. }
  616. }
  617. static ConCommand wc_link_edit("wc_link_edit", CC_WC_LinkEdit, 0, FCVAR_CHEAT);
  618. /// This is an entity used by the hammer_update_safe_entities command. It allows designers
  619. /// to specify objects that should be ignored. It stores an array of sixteen strings
  620. /// which may correspond to names. Designers may ignore more than sixteen objects by
  621. /// placing more than one of these in a level.
  622. class CWC_UpdateIgnoreList : public CBaseEntity
  623. {
  624. public:
  625. DECLARE_CLASS( CWC_UpdateIgnoreList, CBaseEntity );
  626. enum { MAX_IGNORELIST_NAMES = 16 }; ///< the number of names in the array below
  627. inline const string_t &GetName( int x ) const { return m_nIgnoredEntityNames[x]; }
  628. protected:
  629. // the list of names to ignore
  630. string_t m_nIgnoredEntityNames[MAX_IGNORELIST_NAMES];
  631. public:
  632. DECLARE_DATADESC();
  633. };
  634. LINK_ENTITY_TO_CLASS( hammer_updateignorelist, CWC_UpdateIgnoreList );
  635. BEGIN_DATADESC( CWC_UpdateIgnoreList )
  636. // Be still, classcheck!
  637. //DEFINE_FIELD( m_nIgnoredEntityNames, FIELD_STRING, MAX_IGNORELIST_NAMES ),
  638. DEFINE_KEYFIELD( m_nIgnoredEntityNames[0], FIELD_STRING, "IgnoredName01" ),
  639. DEFINE_KEYFIELD( m_nIgnoredEntityNames[1], FIELD_STRING, "IgnoredName02" ),
  640. DEFINE_KEYFIELD( m_nIgnoredEntityNames[2], FIELD_STRING, "IgnoredName03" ),
  641. DEFINE_KEYFIELD( m_nIgnoredEntityNames[3], FIELD_STRING, "IgnoredName04" ),
  642. DEFINE_KEYFIELD( m_nIgnoredEntityNames[4], FIELD_STRING, "IgnoredName05" ),
  643. DEFINE_KEYFIELD( m_nIgnoredEntityNames[5], FIELD_STRING, "IgnoredName06" ),
  644. DEFINE_KEYFIELD( m_nIgnoredEntityNames[6], FIELD_STRING, "IgnoredName07" ),
  645. DEFINE_KEYFIELD( m_nIgnoredEntityNames[7], FIELD_STRING, "IgnoredName08" ),
  646. DEFINE_KEYFIELD( m_nIgnoredEntityNames[8], FIELD_STRING, "IgnoredName09" ),
  647. DEFINE_KEYFIELD( m_nIgnoredEntityNames[9], FIELD_STRING, "IgnoredName10" ),
  648. DEFINE_KEYFIELD( m_nIgnoredEntityNames[10], FIELD_STRING, "IgnoredName11" ),
  649. DEFINE_KEYFIELD( m_nIgnoredEntityNames[11], FIELD_STRING, "IgnoredName12" ),
  650. DEFINE_KEYFIELD( m_nIgnoredEntityNames[12], FIELD_STRING, "IgnoredName13" ),
  651. DEFINE_KEYFIELD( m_nIgnoredEntityNames[13], FIELD_STRING, "IgnoredName14" ),
  652. DEFINE_KEYFIELD( m_nIgnoredEntityNames[14], FIELD_STRING, "IgnoredName15" ),
  653. DEFINE_KEYFIELD( m_nIgnoredEntityNames[15], FIELD_STRING, "IgnoredName16" ),
  654. END_DATADESC()
  655. CON_COMMAND( hammer_update_entity, "Updates the entity's position/angles when in edit mode" )
  656. {
  657. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  658. return;
  659. if ( args.ArgC() < 2 )
  660. {
  661. CBasePlayer *pPlayer = UTIL_GetCommandClient();
  662. trace_t tr;
  663. Vector forward;
  664. pPlayer->EyeVectors( &forward );
  665. UTIL_TraceLine(pPlayer->EyePosition(), pPlayer->EyePosition() + forward * MAX_COORD_RANGE,
  666. MASK_SHOT_HULL|CONTENTS_GRATE|CONTENTS_DEBRIS, pPlayer, COLLISION_GROUP_NONE, &tr );
  667. if ( tr.DidHit() && !tr.DidHitWorld() )
  668. {
  669. NWCEdit::UpdateEntityPosition( tr.m_pEnt );
  670. }
  671. }
  672. else
  673. {
  674. CBaseEntity *pEnt = NULL;
  675. while ((pEnt = gEntList.FindEntityGeneric( pEnt, args[1] ) ) != NULL)
  676. {
  677. NWCEdit::UpdateEntityPosition( pEnt );
  678. }
  679. }
  680. }
  681. 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." )
  682. {
  683. int iCount = 0;
  684. CBaseEntity *pEnt = NULL;
  685. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  686. return;
  687. Msg("\n====================================================\nPerforming Safe Entity Update\n" );
  688. // first look for any exclusion objects -- these are entities that list specific things to be ignored.
  689. // All the names that are inside them, we store into a hash table (here implemented through a local
  690. // CUtlSymbolTable)
  691. CUtlSymbolTable ignoredNames(16,32,true); // grow 16 strings at a time. Case insensitive.
  692. while ( (pEnt = gEntList.FindEntityByClassname( pEnt, "hammer_updateignorelist" )) != NULL )
  693. {
  694. // for each name in each of those strings, add it to the symbol table.
  695. CWC_UpdateIgnoreList *piglist = static_cast<CWC_UpdateIgnoreList *>(pEnt);
  696. for (int ii = 0 ; ii < CWC_UpdateIgnoreList::MAX_IGNORELIST_NAMES ; ++ii )
  697. {
  698. if (!!piglist->GetName(ii)) // if not null
  699. { // add to symtab
  700. ignoredNames.AddString(piglist->GetName(ii).ToCStr());
  701. }
  702. }
  703. }
  704. if ( ignoredNames.GetNumStrings() > 0 )
  705. {
  706. Msg( "Ignoring %d specified targetnames.\n", ignoredNames.GetNumStrings() );
  707. }
  708. // now iterate through everything in the world
  709. for ( pEnt = gEntList.FirstEnt(); pEnt != NULL; pEnt = gEntList.NextEnt(pEnt) )
  710. {
  711. if ( !(pEnt->ObjectCaps() & FCAP_WCEDIT_POSITION) )
  712. continue;
  713. // If we have a parent, or any children, we're not safe to update
  714. if ( pEnt->GetMoveParent() || pEnt->FirstMoveChild() )
  715. continue;
  716. IPhysicsObject *pPhysics = pEnt->VPhysicsGetObject();
  717. if ( !pPhysics )
  718. continue;
  719. // If we are affected by any constraints, we're not safe to update
  720. if ( pPhysics->IsAttachedToConstraint( Ragdoll_IsPropRagdoll(pEnt) ) )
  721. continue;
  722. // Motion disabled?
  723. if ( !pPhysics->IsMoveable() )
  724. continue;
  725. // ignore brush models (per bug 61318)
  726. if ( dynamic_cast<CPhysBox *>(pEnt) )
  727. continue;
  728. // explicitly excluded by designer?
  729. if ( ignoredNames.Find(pEnt->GetEntityName().ToCStr()).IsValid() )
  730. continue;
  731. NWCEdit::UpdateEntityPosition( pEnt );
  732. iCount++;
  733. }
  734. Msg("Updated %d entities.\n", iCount);
  735. }