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.

459 lines
12 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "ai_motor.h"
  8. #include "ai_behavior_rappel.h"
  9. #include "beam_shared.h"
  10. #include "rope.h"
  11. #include "eventqueue.h"
  12. // memdbgon must be the last include file in a .cpp file!!!
  13. #include "tier0/memdbgon.h"
  14. BEGIN_DATADESC( CAI_RappelBehavior )
  15. DEFINE_FIELD( m_bWaitingToRappel, FIELD_BOOLEAN ),
  16. DEFINE_FIELD( m_bOnGround, FIELD_BOOLEAN ),
  17. DEFINE_FIELD( m_hLine, FIELD_EHANDLE ),
  18. DEFINE_FIELD( m_vecRopeAnchor, FIELD_POSITION_VECTOR ),
  19. END_DATADESC();
  20. //=========================================================
  21. //=========================================================
  22. class CRopeAnchor : public CPointEntity
  23. {
  24. DECLARE_CLASS( CRopeAnchor, CPointEntity );
  25. public:
  26. void Spawn( void );
  27. void FallThink( void );
  28. void RemoveThink( void );
  29. EHANDLE m_hRope;
  30. DECLARE_DATADESC();
  31. };
  32. BEGIN_DATADESC( CRopeAnchor )
  33. DEFINE_FIELD( m_hRope, FIELD_EHANDLE ),
  34. DEFINE_THINKFUNC( FallThink ),
  35. DEFINE_THINKFUNC( RemoveThink ),
  36. END_DATADESC();
  37. LINK_ENTITY_TO_CLASS( rope_anchor, CRopeAnchor );
  38. //---------------------------------------------------------
  39. //---------------------------------------------------------
  40. #define RAPPEL_ROPE_WIDTH 1
  41. void CRopeAnchor::Spawn()
  42. {
  43. BaseClass::Spawn();
  44. // Decent enough default in case something happens to our owner!
  45. float flDist = 384;
  46. if( GetOwnerEntity() )
  47. {
  48. flDist = fabs( GetOwnerEntity()->GetAbsOrigin().z - GetAbsOrigin().z );
  49. }
  50. m_hRope = CRopeKeyframe::CreateWithSecondPointDetached( this, -1, flDist, RAPPEL_ROPE_WIDTH, "cable/cable.vmt", 5, true );
  51. ASSERT( m_hRope != NULL );
  52. SetThink( &CRopeAnchor::FallThink );
  53. SetNextThink( gpGlobals->curtime + 0.2 );
  54. }
  55. //---------------------------------------------------------
  56. //---------------------------------------------------------
  57. void CRopeAnchor::FallThink()
  58. {
  59. SetMoveType( MOVETYPE_FLYGRAVITY );
  60. Vector vecVelocity = GetAbsVelocity();
  61. vecVelocity.x = random->RandomFloat( -30.0f, 30.0f );
  62. vecVelocity.y = random->RandomFloat( -30.0f, 30.0f );
  63. SetAbsVelocity( vecVelocity );
  64. SetThink( &CRopeAnchor::RemoveThink );
  65. SetNextThink( gpGlobals->curtime + 3.0 );
  66. }
  67. //---------------------------------------------------------
  68. //---------------------------------------------------------
  69. void CRopeAnchor::RemoveThink()
  70. {
  71. UTIL_Remove( m_hRope );
  72. SetThink( &CRopeAnchor::SUB_Remove );
  73. SetNextThink( gpGlobals->curtime );
  74. }
  75. //=========================================================
  76. //=========================================================
  77. //-----------------------------------------------------------------------------
  78. // Purpose:
  79. //-----------------------------------------------------------------------------
  80. CAI_RappelBehavior::CAI_RappelBehavior()
  81. {
  82. m_hLine = NULL;
  83. m_bWaitingToRappel = false;
  84. m_bOnGround = true;
  85. }
  86. //-----------------------------------------------------------------------------
  87. //-----------------------------------------------------------------------------
  88. bool CAI_RappelBehavior::KeyValue( const char *szKeyName, const char *szValue )
  89. {
  90. if( FStrEq( szKeyName, "waitingtorappel" ) )
  91. {
  92. m_bWaitingToRappel = ( atoi(szValue) != 0);
  93. m_bOnGround = !m_bWaitingToRappel;
  94. return true;
  95. }
  96. return BaseClass::KeyValue( szKeyName, szValue );
  97. }
  98. void CAI_RappelBehavior::Precache()
  99. {
  100. CBaseEntity::PrecacheModel( "cable/cable.vmt" );
  101. }
  102. //-----------------------------------------------------------------------------
  103. //-----------------------------------------------------------------------------
  104. #define RAPPEL_MAX_SPEED 600 // Go this fast if you're really high.
  105. #define RAPPEL_MIN_SPEED 60 // Go no slower than this.
  106. #define RAPPEL_DECEL_DIST (20.0f * 12.0f) // Start slowing down when you're this close to the ground.
  107. void CAI_RappelBehavior::SetDescentSpeed()
  108. {
  109. // Trace to the floor and see how close we're getting. Slow down if we're close.
  110. // STOP if there's an NPC under us.
  111. trace_t tr;
  112. AI_TraceLine( GetOuter()->GetAbsOrigin(), GetOuter()->GetAbsOrigin() - Vector( 0, 0, 8192 ), MASK_SHOT, GetOuter(), COLLISION_GROUP_NONE, &tr );
  113. float flDist = fabs( GetOuter()->GetAbsOrigin().z - tr.endpos.z );
  114. float speed = RAPPEL_MAX_SPEED;
  115. if( flDist <= RAPPEL_DECEL_DIST )
  116. {
  117. float factor;
  118. factor = flDist / RAPPEL_DECEL_DIST;
  119. speed = MAX( RAPPEL_MIN_SPEED, speed * factor );
  120. }
  121. Vector vecNewVelocity = vec3_origin;
  122. vecNewVelocity.z = -speed;
  123. GetOuter()->SetAbsVelocity( vecNewVelocity );
  124. }
  125. void CAI_RappelBehavior::CleanupOnDeath( CBaseEntity *pCulprit, bool bFireDeathOutput )
  126. {
  127. BaseClass::CleanupOnDeath( pCulprit, bFireDeathOutput );
  128. //This will remove the beam and create a rope if the NPC dies while rappeling down.
  129. if ( m_hLine )
  130. {
  131. CAI_BaseNPC *pNPC = GetOuter();
  132. if ( pNPC )
  133. {
  134. CutZipline();
  135. }
  136. }
  137. }
  138. //-----------------------------------------------------------------------------
  139. // Purpose:
  140. // Input : *pTask -
  141. //-----------------------------------------------------------------------------
  142. void CAI_RappelBehavior::StartTask( const Task_t *pTask )
  143. {
  144. switch( pTask->iTask )
  145. {
  146. case TASK_MOVE_AWAY_PATH:
  147. GetOuter()->GetMotor()->SetIdealYaw( UTIL_AngleMod( GetOuter()->GetLocalAngles().y - 180.0f ) );
  148. BaseClass::StartTask( pTask );
  149. break;
  150. case TASK_RANGE_ATTACK1:
  151. BaseClass::StartTask( pTask );
  152. break;
  153. case TASK_RAPPEL:
  154. {
  155. CreateZipline();
  156. SetDescentSpeed();
  157. }
  158. break;
  159. case TASK_HIT_GROUND:
  160. m_bOnGround = true;
  161. if( GetOuter()->GetGroundEntity() != NULL && GetOuter()->GetGroundEntity()->IsNPC() && GetOuter()->GetGroundEntity()->m_iClassname == GetOuter()->m_iClassname )
  162. {
  163. // Although I tried to get NPC's out from under me, I landed on one. Kill it, so long as it's the same type of character as me.
  164. variant_t val;
  165. val.SetFloat( 0 );
  166. g_EventQueue.AddEvent( GetOuter()->GetGroundEntity(), "sethealth", val, 0, GetOuter(), GetOuter() );
  167. }
  168. TaskComplete();
  169. break;
  170. default:
  171. BaseClass::StartTask( pTask );
  172. break;
  173. }
  174. }
  175. //-----------------------------------------------------------------------------
  176. // Purpose:
  177. // Input : *pTask -
  178. //-----------------------------------------------------------------------------
  179. void CAI_RappelBehavior::RunTask( const Task_t *pTask )
  180. {
  181. switch( pTask->iTask )
  182. {
  183. case TASK_RAPPEL:
  184. {
  185. // If we don't do this, the beam won't show up sometimes. Ideally, all beams would update their
  186. // bboxes correctly, but we're close to shipping and we can't change that now.
  187. if ( m_hLine )
  188. {
  189. m_hLine->RelinkBeam();
  190. }
  191. if( GetEnemy() )
  192. {
  193. // Face the enemy if there's one.
  194. Vector vecEnemyLKP = GetEnemyLKP();
  195. GetOuter()->GetMotor()->SetIdealYawToTargetAndUpdate( vecEnemyLKP );
  196. }
  197. SetDescentSpeed();
  198. if( GetOuter()->GetFlags() & FL_ONGROUND )
  199. {
  200. CBaseEntity *pGroundEnt = GetOuter()->GetGroundEntity();
  201. if( pGroundEnt && pGroundEnt->IsPlayer() )
  202. {
  203. // try to shove the player in the opposite direction as they are facing (so they'll see me)
  204. Vector vecForward;
  205. pGroundEnt->GetVectors( &vecForward, NULL, NULL );
  206. pGroundEnt->SetAbsVelocity( vecForward * -500 );
  207. break;
  208. }
  209. GetOuter()->m_OnRappelTouchdown.FireOutput( GetOuter(), GetOuter(), 0 );
  210. GetOuter()->RemoveFlag( FL_FLY );
  211. CutZipline();
  212. TaskComplete();
  213. }
  214. }
  215. break;
  216. default:
  217. BaseClass::RunTask( pTask );
  218. break;
  219. }
  220. }
  221. //-----------------------------------------------------------------------------
  222. // Purpose:
  223. // Output : Returns true on success, false on failure.
  224. //-----------------------------------------------------------------------------
  225. bool CAI_RappelBehavior::CanSelectSchedule()
  226. {
  227. if ( !GetOuter()->IsInterruptable() )
  228. return false;
  229. if ( m_bWaitingToRappel )
  230. return true;
  231. if ( m_bOnGround )
  232. return false;
  233. return true;
  234. }
  235. //-----------------------------------------------------------------------------
  236. //-----------------------------------------------------------------------------
  237. void CAI_RappelBehavior::GatherConditions()
  238. {
  239. BaseClass::GatherConditions();
  240. if( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
  241. {
  242. // Shoot at the enemy so long as I'm six feet or more above them.
  243. if( (GetAbsOrigin().z - GetEnemy()->GetAbsOrigin().z >= 36.0f) && GetOuter()->GetShotRegulator()->ShouldShoot() )
  244. {
  245. Activity activity = GetOuter()->TranslateActivity( ACT_GESTURE_RANGE_ATTACK1 );
  246. Assert( activity != ACT_INVALID );
  247. GetOuter()->AddGesture( activity );
  248. // FIXME: this seems a bit wacked
  249. GetOuter()->Weapon_SetActivity( GetOuter()->Weapon_TranslateActivity( ACT_RANGE_ATTACK1 ), 0 );
  250. GetOuter()->OnRangeAttack1();
  251. }
  252. }
  253. }
  254. //-----------------------------------------------------------------------------
  255. // Purpose:
  256. // Output : int
  257. //-----------------------------------------------------------------------------
  258. int CAI_RappelBehavior::SelectSchedule()
  259. {
  260. if ( HasCondition( COND_BEGIN_RAPPEL ) )
  261. {
  262. m_bWaitingToRappel = false;
  263. return SCHED_RAPPEL;
  264. }
  265. if ( m_bWaitingToRappel )
  266. {
  267. return SCHED_RAPPEL_WAIT;
  268. }
  269. else
  270. {
  271. return SCHED_RAPPEL;
  272. }
  273. return BaseClass::SelectSchedule();
  274. }
  275. //-----------------------------------------------------------------------------
  276. //-----------------------------------------------------------------------------
  277. void CAI_RappelBehavior::BeginRappel()
  278. {
  279. // Send the message to begin rappeling!
  280. SetCondition( COND_BEGIN_RAPPEL );
  281. m_vecRopeAnchor = GetOuter()->GetAbsOrigin();
  282. trace_t tr;
  283. UTIL_TraceEntity( GetOuter(), GetAbsOrigin(), GetAbsOrigin()-Vector(0,0,4096), MASK_SHOT, GetOuter(), COLLISION_GROUP_NONE, &tr );
  284. if( tr.m_pEnt != NULL && tr.m_pEnt->IsNPC() )
  285. {
  286. Vector forward;
  287. GetOuter()->GetVectors( &forward, NULL, NULL );
  288. CSoundEnt::InsertSound( SOUND_DANGER, tr.m_pEnt->EarPosition() - forward * 12.0f, 32.0f, 0.2f, GetOuter() );
  289. }
  290. }
  291. //-----------------------------------------------------------------------------
  292. //-----------------------------------------------------------------------------
  293. void CAI_RappelBehavior::CutZipline()
  294. {
  295. if( m_hLine )
  296. {
  297. UTIL_Remove( m_hLine );
  298. }
  299. CBaseEntity *pAnchor = CreateEntityByName( "rope_anchor" );
  300. pAnchor->SetOwnerEntity( GetOuter() ); // Boy, this is a hack!!
  301. pAnchor->SetAbsOrigin( m_vecRopeAnchor );
  302. pAnchor->Spawn();
  303. }
  304. //-----------------------------------------------------------------------------
  305. //-----------------------------------------------------------------------------
  306. void CAI_RappelBehavior::CreateZipline()
  307. {
  308. #if 1
  309. if( !m_hLine )
  310. {
  311. int attachment = GetOuter()->LookupAttachment( "zipline" );
  312. if( attachment > 0 )
  313. {
  314. CBeam *pBeam;
  315. pBeam = CBeam::BeamCreate( "cable/cable.vmt", 1 );
  316. pBeam->SetColor( 150, 150, 150 );
  317. pBeam->SetWidth( 0.3 );
  318. pBeam->SetEndWidth( 0.3 );
  319. CAI_BaseNPC *pNPC = GetOuter();
  320. pBeam->PointEntInit( pNPC->GetAbsOrigin() + Vector( 0, 0, 80 ), pNPC );
  321. pBeam->SetEndAttachment( attachment );
  322. m_hLine.Set( pBeam );
  323. }
  324. }
  325. #endif
  326. }
  327. AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_RappelBehavior )
  328. DECLARE_TASK( TASK_RAPPEL )
  329. DECLARE_TASK( TASK_HIT_GROUND )
  330. DECLARE_CONDITION( COND_BEGIN_RAPPEL )
  331. //===============================================
  332. //===============================================
  333. DEFINE_SCHEDULE
  334. (
  335. SCHED_RAPPEL_WAIT,
  336. " Tasks"
  337. " TASK_SET_ACTIVITY ACTIVITY:ACT_RAPPEL_LOOP"
  338. " TASK_WAIT_INDEFINITE 0"
  339. ""
  340. " Interrupts"
  341. " COND_BEGIN_RAPPEL"
  342. );
  343. //===============================================
  344. //===============================================
  345. DEFINE_SCHEDULE
  346. (
  347. SCHED_RAPPEL,
  348. " Tasks"
  349. " TASK_SET_ACTIVITY ACTIVITY:ACT_RAPPEL_LOOP"
  350. " TASK_RAPPEL 0"
  351. " TASK_SET_SCHEDULE SCHEDULE:SCHED_CLEAR_RAPPEL_POINT"
  352. ""
  353. " Interrupts"
  354. ""
  355. " COND_NEW_ENEMY" // Only so the enemy selection code will pick an enemy!
  356. );
  357. //===============================================
  358. //===============================================
  359. DEFINE_SCHEDULE
  360. (
  361. SCHED_CLEAR_RAPPEL_POINT,
  362. " Tasks"
  363. " TASK_HIT_GROUND 0"
  364. " TASK_MOVE_AWAY_PATH 128" // Clear this spot for other rappellers
  365. " TASK_RUN_PATH 0"
  366. " TASK_WAIT_FOR_MOVEMENT 0"
  367. ""
  368. " Interrupts"
  369. ""
  370. );
  371. AI_END_CUSTOM_SCHEDULE_PROVIDER()