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