turret.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. #include "../idlib/precompiled.h"
  2. #pragma hdrstop
  3. #include "Game_local.h"
  4. #define WAKEUPTIME 1100 //activation time.
  5. #define MAXVOLLEY 5 //how many shots in one volley.
  6. #define WARMUPTIME 1900 //how long the pre-volley warning warmup is.
  7. #define VOLLEYDELAY 250 //ms between each shot in a volley.
  8. #define IDLESOUNDDELAY 2000
  9. #define IDLESOUNDRAND 2000
  10. #define MUZZLEFLASHTIME 0.05
  11. #define BEAMWIDTH 4.0f
  12. #define PITCHADJUSTMENT -8 //aim a little below the eyes.
  13. #define THROWTIME_THRESHOLD 1000 //thrown objects are valid targets for X milliseconds after being thrown.
  14. const idEventDef EV_Turret_activate( "turretactivate", "d" );
  15. const idEventDef EV_Turret_isactive( "turretisactive", NULL, 'd' );
  16. const idEventDef EV_Turret_muzzleflashoff( "turretmuzzleoff", NULL );
  17. CLASS_DECLARATION( idAnimatedEntity, idTurret )
  18. EVENT( EV_Turret_activate, idTurret::Event_activate)
  19. EVENT( EV_Turret_isactive, idTurret::Event_isactive)
  20. EVENT( EV_Turret_muzzleflashoff, idTurret::MuzzleflashOff)
  21. END_CLASS
  22. void idTurret::Save( idSaveGame *savefile ) const
  23. {
  24. savefile->WriteInt(state);
  25. savefile->WriteInt(attackState);
  26. savefile->WriteInt(volleyCount);
  27. savefile->WriteInt(nextIdleSound);
  28. savefile->WriteMat3(bodyAxis);
  29. savefile->WriteMat3(turretAxis);
  30. savefile->WriteObject(beamStart);
  31. savefile->WriteObject(beamEnd);
  32. savefile->WriteObject(laserdot);
  33. //BC 7-29-2016
  34. savefile->WriteInt(nextStateTime);
  35. targetEnt.Save(savefile);
  36. }
  37. void idTurret::Restore( idRestoreGame *savefile )
  38. {
  39. savefile->ReadInt(state);
  40. savefile->ReadInt(attackState);
  41. savefile->ReadInt(volleyCount);
  42. savefile->ReadInt(nextIdleSound);
  43. savefile->ReadMat3(bodyAxis);
  44. savefile->ReadMat3(turretAxis);
  45. savefile->ReadObject(reinterpret_cast<idClass *&>(beamStart));
  46. savefile->ReadObject(reinterpret_cast<idClass *&>(beamEnd));
  47. savefile->ReadObject(reinterpret_cast<idClass *&>(laserdot));
  48. //BC 7-29-2016
  49. savefile->ReadInt(nextStateTime);
  50. targetEnt.Restore(savefile);
  51. }
  52. void idTurret::Spawn( void )
  53. {
  54. jointHandle_t bodyJoint, turretJoint;
  55. idVec3 origin;
  56. idDict args;
  57. idVec3 turretPos;
  58. this->state = OFF;
  59. this->attackState = IDLE;
  60. this->nextStateTime = 0;
  61. this->volleyCount = 0;
  62. this->nextIdleSound = 0;
  63. targetEnt = NULL;
  64. BecomeActive( TH_THINK );
  65. bodyJoint = animator.GetJointHandle( "body" );
  66. animator.GetJointTransform( bodyJoint, gameLocal.time, origin, this->bodyAxis );
  67. turretJoint = animator.GetJointHandle( "turret" );
  68. animator.GetJointTransform( turretJoint, gameLocal.time, turretPos, this->turretAxis );
  69. turretPos = this->GetPhysics()->GetOrigin() + turretPos * this->GetPhysics()->GetAxis();
  70. SetSkin(declManager->FindSkin( "skins/turret/skin" ));
  71. //spawn beam end.
  72. args.SetVector( "origin", vec3_origin );
  73. beamEnd = ( idBeam * )gameLocal.SpawnEntityType( idBeam::Type, &args );
  74. //spawn beam start.
  75. args.Clear();
  76. args.Set( "target", beamEnd->name.c_str() );
  77. args.SetVector( "origin", turretPos );
  78. args.SetBool( "start_off", true );
  79. args.Set( "skin", "skins/beam_turret" );
  80. args.SetFloat( "width", BEAMWIDTH );
  81. beamStart = ( idBeam * )gameLocal.SpawnEntityType( idBeam::Type, &args );
  82. beamStart->BindToJoint(this, turretJoint, false);
  83. beamStart->BecomeActive(TH_PHYSICS);
  84. beamStart->GetRenderEntity()->shaderParms[ SHADERPARM_RED ] = spawnArgs.GetVector("_color").x;
  85. beamStart->GetRenderEntity()->shaderParms[ SHADERPARM_GREEN ] = spawnArgs.GetVector("_color").y;
  86. beamStart->GetRenderEntity()->shaderParms[ SHADERPARM_BLUE ] = spawnArgs.GetVector("_color").z;
  87. beamStart->Hide();
  88. args.Clear();
  89. args.SetVector( "origin", vec3_origin );
  90. args.Set( "model", "models/lasersight/tris.ase" );
  91. args.SetInt( "solid", 0 );
  92. laserdot = gameLocal.SpawnEntityType( idStaticEntity::Type, &args );
  93. laserdot->GetRenderEntity()->shaderParms[ SHADERPARM_RED ] = spawnArgs.GetVector("_color").x;
  94. laserdot->GetRenderEntity()->shaderParms[ SHADERPARM_GREEN ] = spawnArgs.GetVector("_color").y;
  95. laserdot->GetRenderEntity()->shaderParms[ SHADERPARM_BLUE ] = spawnArgs.GetVector("_color").z;
  96. laserdot->Hide();
  97. laserdot->SetOrigin(beamEnd->GetPhysics()->GetOrigin());
  98. laserdot->Bind(beamEnd, false);
  99. if (spawnArgs.GetBool( "start_on", "0" ))
  100. {
  101. Event_activate( 1 );
  102. }
  103. //precache the projectile.
  104. }
  105. void idTurret::Event_isactive()
  106. {
  107. idThread::ReturnInt( this->state );
  108. }
  109. void idTurret::Event_activate( int value )
  110. {
  111. if (value == 1)
  112. {
  113. if (state == ON)
  114. {
  115. return;
  116. }
  117. //open.
  118. Event_PlayAnim("opening", 4);
  119. this->state = ON;
  120. StartSound( "snd_opening", SND_CHANNEL_ANY, 0, false, NULL );
  121. this->nextStateTime = gameLocal.time + WAKEUPTIME;
  122. this->nextIdleSound = gameLocal.time + 2000;
  123. }
  124. else
  125. {
  126. if (state == OFF)
  127. {
  128. return;
  129. }
  130. //close up.
  131. int doneTime = Event_PlayAnim("closing", 4);
  132. this->state = CLOSING;
  133. this->nextStateTime = doneTime;
  134. StartSound( "snd_closing", SND_CHANNEL_ANY, 0, false, NULL );
  135. this->beamStart->Hide();
  136. this->laserdot->Hide();
  137. }
  138. }
  139. void idTurret::MuzzleflashOff( void )
  140. {
  141. SetSkin(declManager->FindSkin( "skins/turret/skin" ));
  142. }
  143. void idTurret::GotoWarmupState( void )
  144. {
  145. this->attackState = WARMUP;
  146. StartSound( "snd_warmup", SND_CHANNEL_ANY, 0, false, NULL );
  147. this->volleyCount = 0;
  148. this->nextStateTime = gameLocal.time + WARMUPTIME;
  149. }
  150. bool idTurret::CheckTargetValidity(idEntity *ent)
  151. {
  152. if (CheckTargetLOS(ent, vec3_zero))
  153. {
  154. return true;
  155. }
  156. //first check failed. so now do the fallback offset check.
  157. idBounds bounds = ent->GetPhysics()->GetBounds();
  158. idVec3 tmax;
  159. tmax[2] = bounds[1][2] ;
  160. tmax[2]--; //shrink it a little.
  161. if (CheckTargetLOS(ent, idVec3(0,0,tmax[2])))
  162. {
  163. return true;
  164. }
  165. //both checks have failed. abort.
  166. return false;
  167. }
  168. bool idTurret::CheckTargetLOS(idEntity *ent, idVec3 offset)
  169. {
  170. //check distance.
  171. idVec3 selfPos = GetPhysics()->GetOrigin() + idVec3(0,0,-12);
  172. float distanceToTarget = ( ent->GetPhysics()->GetOrigin() - selfPos ).Normalize();
  173. float maxDist = spawnArgs.GetInt("maxrange", "1024");
  174. if (maxDist < 0)
  175. maxDist = 1024;
  176. if (distanceToTarget >= maxDist)
  177. {
  178. //too far. Out of range.
  179. return false;
  180. }
  181. idVec3 forward, right, up;
  182. ent->GetPhysics()->GetAxis().ToAngles().ToVectors( &forward, &right, &up );
  183. idVec3 offsetPosition = vec3_zero;
  184. offsetPosition += (forward * offset.x) + (right * offset.y) + (up * offset.z);
  185. //check LOS.
  186. trace_t tr;
  187. gameLocal.clip.TracePoint( tr, selfPos, ent->GetPhysics()->GetOrigin() + offsetPosition, CONTENTS_OPAQUE, this );
  188. if (tr.fraction < 0)
  189. {
  190. //blocked.
  191. return false;
  192. }
  193. idVec3 movedir = (ent->GetPhysics()->GetOrigin() + offsetPosition) - selfPos;
  194. movedir.Normalize();
  195. int i;
  196. idVec3 hitpoint = selfPos;
  197. int maxGlassPanes = 32;
  198. for (i = 0; i < maxGlassPanes; i++)
  199. {
  200. trace_t paneTr;
  201. idVec3 startTrace = hitpoint + (movedir * 1.0f);
  202. gameLocal.clip.TracePoint( paneTr, startTrace, startTrace + (movedir * 4096), MASK_SOLID | MASK_SHOT_RENDERMODEL | MASK_SHOT_BOUNDINGBOX, this );
  203. //gameRenderWorld->DebugArrow(idVec4(1,1,0,1), startTrace, paneTr.endpos, 16, 5000);
  204. if ( paneTr.c.material != NULL )
  205. {
  206. surfTypes_t materialType = paneTr.c.material->GetSurfaceType();
  207. if (materialType == SURFTYPE_GLASS)
  208. {
  209. hitpoint = paneTr.endpos;
  210. continue;
  211. }
  212. }
  213. if (paneTr.c.entityNum == ent->entityNumber)
  214. {
  215. break;
  216. }
  217. //if (gameLocal.entities[paneTr.c.entityNum]->IsType(idDoor::Type) || gameLocal.entities[paneTr.c.entityNum]->IsType( idWorldspawn::Type ))
  218. {
  219. return false;
  220. }
  221. }
  222. //turret has a valid target. Start the gunfire sequence.
  223. if ( developer.GetBool() )
  224. {
  225. gameRenderWorld->DebugArrow(idVec4(1,0,0,1), selfPos, ent->GetPhysics()->GetOrigin() + offsetPosition, 16, 5000);
  226. }
  227. return true;
  228. }
  229. void idTurret::UpdateStates( void )
  230. {
  231. if (this->state == CLOSING)
  232. {
  233. idAnimatedEntity::Think();
  234. if (gameLocal.time > this->nextStateTime)
  235. {
  236. this->state = OFF;
  237. }
  238. return;
  239. }
  240. else if (this->state == ON)
  241. {
  242. jointHandle_t bodyJoint, turretJoint;
  243. idRotation bodyRotation, turretRotation;
  244. idVec3 playerPos;
  245. idVec3 turretPos;
  246. idMat3 turretAxis;
  247. bodyJoint = animator.GetJointHandle( "body" );
  248. turretJoint = animator.GetJointHandle( "turret" );
  249. //get yaw.
  250. if (targetEnt.IsValid())
  251. {
  252. playerPos = targetEnt.GetEntity()->GetPhysics()->GetOrigin();
  253. }
  254. else
  255. {
  256. playerPos = gameLocal.GetLocalPlayer()->GetEyePosition();
  257. playerPos[2] += PITCHADJUSTMENT; //aim a little below the eyes
  258. }
  259. idVec3 diff = playerPos - this->GetPhysics()->GetOrigin();
  260. float yaw = diff.ToYaw();
  261. //BC 9-14-2015 fix bad facing.
  262. yaw -= this->GetPhysics()->GetAxis().ToAngles().yaw;
  263. //get pitch.
  264. animator.GetJointTransform(turretJoint, gameLocal.time, turretPos, turretAxis);
  265. turretPos = this->GetPhysics()->GetOrigin() + turretPos * this->GetPhysics()->GetAxis();
  266. //turretPos = turretPos + this->GetPhysics()->GetOrigin();
  267. idVec3 pitchDiff = playerPos - turretPos;
  268. float pitch = pitchDiff.ToPitch();
  269. if (gameLocal.GetLocalPlayer()->health <= 0
  270. || gameLocal.GetLocalPlayer()->fl.notarget
  271. || gameLocal.GetLocalPlayer()->noclip
  272. || g_skill.GetInteger() <= 0)
  273. {
  274. }
  275. else
  276. {
  277. //rotate body.
  278. bodyRotation.SetVec( bodyAxis[1] );
  279. bodyRotation.SetAngle( yaw );
  280. animator.SetJointAxis( bodyJoint, JOINTMOD_WORLD, bodyRotation.ToMat3() );
  281. }
  282. if (this->attackState == IDLE)
  283. {
  284. //make beam idle around.
  285. idVec3 forward, up;
  286. idVec3 bodyPos;
  287. idVec3 facingAngle;
  288. idVec3 idlePos;
  289. bodyPos = this->GetPhysics()->GetOrigin();
  290. bodyPos.z = turretPos.z;
  291. facingAngle = turretPos - bodyPos;
  292. facingAngle.Normalize();
  293. facingAngle.ToAngles().ToVectors(&forward, NULL, &up);
  294. trace_t idleTr;
  295. idlePos = turretPos + (forward * 2048) + (up * -512);
  296. idlePos.x += idMath::Sin(gameLocal.time * 0.001f) * 16.0f;
  297. idlePos.z += idMath::Sin(gameLocal.time * 0.0005f) * 16.0f;
  298. gameLocal.clip.TracePoint( idleTr, turretPos, idlePos, CONTENTS_SOLID, this );
  299. this->beamEnd->SetOrigin(idleTr.endpos);
  300. if (this->nextStateTime > gameLocal.time)
  301. {
  302. return;
  303. }
  304. else
  305. {
  306. //show laser.
  307. this->beamStart->Show();
  308. this->laserdot->Show();
  309. //how often to search for targets.
  310. this->nextStateTime = gameLocal.time + 300;
  311. }
  312. if (this->nextIdleSound < gameLocal.time)
  313. {
  314. int rand = gameLocal.random.RandomInt(3);
  315. StartSound( "snd_idle", SND_CHANNEL_ANY, 0, false, NULL );
  316. if (rand == 0)
  317. {
  318. Event_PlayAnim("idle", 4);
  319. }
  320. else if (rand == 1)
  321. {
  322. Event_PlayAnim("idle2", 4);
  323. }
  324. else
  325. {
  326. Event_PlayAnim("idle3", 4);
  327. }
  328. this->nextIdleSound = gameLocal.time + IDLESOUNDDELAY + (gameLocal.random.RandomFloat() * IDLESOUNDRAND);
  329. }
  330. if (gameLocal.GetLocalPlayer()->health <= 0
  331. || gameLocal.GetLocalPlayer()->fl.notarget
  332. || gameLocal.GetLocalPlayer()->noclip
  333. || g_skill.GetInteger() <= 0)
  334. {
  335. return;
  336. }
  337. //search for things to shoot.
  338. //check maximum range.
  339. //player target check gets priority.
  340. if (CheckTargetValidity(gameLocal.GetLocalPlayer()))
  341. {
  342. targetEnt = NULL;
  343. GotoWarmupState();
  344. return;
  345. }
  346. //if no player
  347. //check if player threw an object.
  348. float maxDist = spawnArgs.GetInt("maxrange", "1024");
  349. int listedEntities;
  350. idEntity *entityList[ MAX_GENTITIES ];
  351. listedEntities = gameLocal.EntitiesWithinRadius( GetPhysics()->GetOrigin(), maxDist, entityList, MAX_GENTITIES );
  352. for (int i = 0; i < listedEntities; i++)
  353. {
  354. idEntity *ent = entityList[ i ];
  355. if (!ent)
  356. {
  357. continue;
  358. }
  359. if ( ent->IsType( idMoveableItem::Type ) || ent->IsType( idMoveable::Type ) )
  360. {
  361. if (ent->lastThrowTime + THROWTIME_THRESHOLD > gameLocal.time)
  362. {
  363. if (CheckTargetValidity(ent))
  364. {
  365. targetEnt = ent;
  366. GotoWarmupState();
  367. return;
  368. }
  369. }
  370. }
  371. if (ent->IsType(idWeevil::Type))
  372. {
  373. if (static_cast<idWeevil *>( ent)->isJumping())
  374. {
  375. targetEnt = ent;
  376. GotoWarmupState();
  377. return;
  378. }
  379. }
  380. }
  381. //no target found. oh well.
  382. }
  383. else if (this->attackState == WARMUP)
  384. {
  385. //update laser.
  386. trace_t tr;
  387. gameLocal.clip.TracePoint( tr, beamStart->GetPhysics()->GetOrigin(), playerPos, CONTENTS_SOLID, this );
  388. this->beamEnd->SetOrigin(tr.endpos);
  389. if (gameLocal.time > nextStateTime)
  390. {
  391. this->attackState = VOLLEYING;
  392. this->nextStateTime = 0;
  393. }
  394. }
  395. else if (this->attackState == VOLLEYING)
  396. {
  397. //update laser.
  398. trace_t tr;
  399. gameLocal.clip.TracePoint( tr, beamStart->GetPhysics()->GetOrigin(), playerPos, CONTENTS_SOLID, this );
  400. this->beamEnd->SetOrigin(tr.endpos);
  401. if (this->volleyCount >= MAXVOLLEY)
  402. {
  403. this->attackState = IDLE;
  404. this->nextStateTime = 0;
  405. return;
  406. }
  407. if (this->nextStateTime < gameLocal.time)
  408. {
  409. const idDict * projectileDef;
  410. idEntity * ent;
  411. idVec3 bulletVelocity;
  412. pitchDiff.Normalize();
  413. //FIRE.
  414. idProjectile *bullet;
  415. projectileDef = gameLocal.FindEntityDefDict( spawnArgs.GetString("projectile", "projectile_turretbullet"), false );
  416. bulletVelocity = projectileDef->GetVector("velocity");
  417. gameLocal.SpawnEntityDef( *projectileDef, &ent, false );
  418. bullet = ( idProjectile * )ent;
  419. bullet->Create( this, turretPos, pitchDiff );
  420. bullet->Launch( turretPos, pitchDiff, pitchDiff * bulletVelocity.x );
  421. //muzzle flash skin.
  422. SetSkin(declManager->FindSkin( "skins/turret/firing" ));
  423. PostEventSec( &EV_Turret_muzzleflashoff, MUZZLEFLASHTIME);
  424. //play animation.
  425. Event_PlayAnim("fire", 4);
  426. //play sound.
  427. StartSound( "snd_fire", SND_CHANNEL_ANY, 0, false, NULL );
  428. //keep track of fire count.
  429. this->volleyCount++;
  430. this->nextStateTime = gameLocal.time + VOLLEYDELAY;
  431. return;
  432. }
  433. }
  434. }
  435. }
  436. void idTurret::Think( void )
  437. {
  438. this->RunPhysics();
  439. UpdateStates();
  440. idAnimatedEntity::Think();
  441. Present();
  442. }
  443. /*
  444. ================
  445. idSecurityCamera::Present
  446. Present is called to allow entities to generate refEntities, lights, etc for the renderer.
  447. ================
  448. */
  449. void idTurret::Present( void )
  450. {
  451. // don't present to the renderer if the entity hasn't changed
  452. if ( !( thinkFlags & TH_UPDATEVISUALS ) )
  453. {
  454. return;
  455. }
  456. BecomeInactive( TH_UPDATEVISUALS );
  457. // if set to invisible, skip
  458. if ( !renderEntity.hModel || IsHidden() )
  459. {
  460. return;
  461. }
  462. // add to refresh list
  463. if ( modelDefHandle == -1 )
  464. {
  465. modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity );
  466. }
  467. else
  468. {
  469. gameRenderWorld->UpdateEntityDef( modelDefHandle, &renderEntity );
  470. }
  471. }