g_turret.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. // Copyright (c) ZeniMax Media Inc.
  2. // Licensed under the GNU General Public License 2.0.
  3. // g_turret.c
  4. #include "g_local.h"
  5. void SpawnTargetingSystem (edict_t *turret); // PGM
  6. void AnglesNormalize(vec3_t vec)
  7. {
  8. while(vec[0] > 360)
  9. vec[0] -= 360;
  10. while(vec[0] < 0)
  11. vec[0] += 360;
  12. while(vec[1] > 360)
  13. vec[1] -= 360;
  14. while(vec[1] < 0)
  15. vec[1] += 360;
  16. }
  17. float SnapToEights(float x)
  18. {
  19. x *= 8.0;
  20. if (x > 0.0)
  21. x += 0.5;
  22. else
  23. x -= 0.5;
  24. return 0.125 * (int)x;
  25. }
  26. void turret_blocked(edict_t *self, edict_t *other)
  27. {
  28. edict_t *attacker;
  29. if (other->takedamage)
  30. {
  31. if (self->teammaster->owner)
  32. attacker = self->teammaster->owner;
  33. else
  34. attacker = self->teammaster;
  35. T_Damage (other, self, attacker, vec3_origin, other->s.origin, vec3_origin, self->teammaster->dmg, 10, 0, MOD_CRUSH);
  36. }
  37. }
  38. /*QUAKED turret_breach (0 0 0) ?
  39. This portion of the turret can change both pitch and yaw.
  40. The model should be made with a flat pitch.
  41. It (and the associated base) need to be oriented towards 0.
  42. Use "angle" to set the starting angle.
  43. "speed" default 50
  44. "dmg" default 10
  45. "angle" point this forward
  46. "target" point this at an info_notnull at the muzzle tip
  47. "minpitch" min acceptable pitch angle : default -30
  48. "maxpitch" max acceptable pitch angle : default 30
  49. "minyaw" min acceptable yaw angle : default 0
  50. "maxyaw" max acceptable yaw angle : default 360
  51. */
  52. void turret_breach_fire (edict_t *self)
  53. {
  54. vec3_t f, r, u;
  55. vec3_t start;
  56. int damage;
  57. int speed;
  58. AngleVectors (self->s.angles, f, r, u);
  59. VectorMA (self->s.origin, self->move_origin[0], f, start);
  60. VectorMA (start, self->move_origin[1], r, start);
  61. VectorMA (start, self->move_origin[2], u, start);
  62. damage = 100 + random() * 50;
  63. speed = 550 + 50 * skill->value;
  64. fire_rocket (self->teammaster->owner, start, f, damage, speed, 150, damage);
  65. gi.positioned_sound (start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0);
  66. }
  67. void turret_breach_think (edict_t *self)
  68. {
  69. edict_t *ent;
  70. vec3_t current_angles;
  71. vec3_t delta;
  72. VectorCopy (self->s.angles, current_angles);
  73. AnglesNormalize(current_angles);
  74. AnglesNormalize(self->move_angles);
  75. if (self->move_angles[PITCH] > 180)
  76. self->move_angles[PITCH] -= 360;
  77. // clamp angles to mins & maxs
  78. if (self->move_angles[PITCH] > self->pos1[PITCH])
  79. self->move_angles[PITCH] = self->pos1[PITCH];
  80. else if (self->move_angles[PITCH] < self->pos2[PITCH])
  81. self->move_angles[PITCH] = self->pos2[PITCH];
  82. if ((self->move_angles[YAW] < self->pos1[YAW]) || (self->move_angles[YAW] > self->pos2[YAW]))
  83. {
  84. float dmin, dmax;
  85. dmin = fabs(self->pos1[YAW] - self->move_angles[YAW]);
  86. if (dmin < -180)
  87. dmin += 360;
  88. else if (dmin > 180)
  89. dmin -= 360;
  90. dmax = fabs(self->pos2[YAW] - self->move_angles[YAW]);
  91. if (dmax < -180)
  92. dmax += 360;
  93. else if (dmax > 180)
  94. dmax -= 360;
  95. if (fabs(dmin) < fabs(dmax))
  96. self->move_angles[YAW] = self->pos1[YAW];
  97. else
  98. self->move_angles[YAW] = self->pos2[YAW];
  99. }
  100. VectorSubtract (self->move_angles, current_angles, delta);
  101. if (delta[0] < -180)
  102. delta[0] += 360;
  103. else if (delta[0] > 180)
  104. delta[0] -= 360;
  105. if (delta[1] < -180)
  106. delta[1] += 360;
  107. else if (delta[1] > 180)
  108. delta[1] -= 360;
  109. delta[2] = 0;
  110. if (delta[0] > self->speed * FRAMETIME)
  111. delta[0] = self->speed * FRAMETIME;
  112. if (delta[0] < -1 * self->speed * FRAMETIME)
  113. delta[0] = -1 * self->speed * FRAMETIME;
  114. if (delta[1] > self->speed * FRAMETIME)
  115. delta[1] = self->speed * FRAMETIME;
  116. if (delta[1] < -1 * self->speed * FRAMETIME)
  117. delta[1] = -1 * self->speed * FRAMETIME;
  118. VectorScale (delta, 1.0/FRAMETIME, self->avelocity);
  119. self->nextthink = level.time + FRAMETIME;
  120. for (ent = self->teammaster; ent; ent = ent->teamchain)
  121. ent->avelocity[1] = self->avelocity[1];
  122. // if we have adriver, adjust his velocities
  123. if (self->owner)
  124. {
  125. float angle;
  126. float target_z;
  127. float diff;
  128. vec3_t target;
  129. vec3_t dir;
  130. // angular is easy, just copy ours
  131. self->owner->avelocity[0] = self->avelocity[0];
  132. self->owner->avelocity[1] = self->avelocity[1];
  133. // x & y
  134. angle = self->s.angles[1] + self->owner->move_origin[1];
  135. angle *= (M_PI*2 / 360);
  136. target[0] = SnapToEights(self->s.origin[0] + cos(angle) * self->owner->move_origin[0]);
  137. target[1] = SnapToEights(self->s.origin[1] + sin(angle) * self->owner->move_origin[0]);
  138. target[2] = self->owner->s.origin[2];
  139. VectorSubtract (target, self->owner->s.origin, dir);
  140. self->owner->velocity[0] = dir[0] * 1.0 / FRAMETIME;
  141. self->owner->velocity[1] = dir[1] * 1.0 / FRAMETIME;
  142. // z
  143. angle = self->s.angles[PITCH] * (M_PI*2 / 360);
  144. target_z = SnapToEights(self->s.origin[2] + self->owner->move_origin[0] * tan(angle) + self->owner->move_origin[2]);
  145. diff = target_z - self->owner->s.origin[2];
  146. self->owner->velocity[2] = diff * 1.0 / FRAMETIME;
  147. if (self->spawnflags & 65536)
  148. {
  149. turret_breach_fire (self);
  150. self->spawnflags &= ~65536;
  151. }
  152. }
  153. }
  154. void turret_breach_finish_init (edict_t *self)
  155. {
  156. // get and save info for muzzle location
  157. if (!self->target)
  158. {
  159. gi.dprintf("%s at %s needs a target\n", self->classname, vtos(self->s.origin));
  160. }
  161. else
  162. {
  163. self->target_ent = G_PickTarget (self->target);
  164. if(self->target_ent)
  165. {
  166. VectorSubtract (self->target_ent->s.origin, self->s.origin, self->move_origin);
  167. G_FreeEdict(self->target_ent);
  168. }
  169. else
  170. gi.dprintf("could not find target entity for %s at %s\n", self->classname, vtos(self->s.origin));
  171. }
  172. self->teammaster->dmg = self->dmg;
  173. self->think = turret_breach_think;
  174. self->think (self);
  175. }
  176. void SP_turret_breach (edict_t *self)
  177. {
  178. self->solid = SOLID_BSP;
  179. self->movetype = MOVETYPE_PUSH;
  180. gi.setmodel (self, self->model);
  181. if (!self->speed)
  182. self->speed = 50;
  183. if (!self->dmg)
  184. self->dmg = 10;
  185. if (!st.minpitch)
  186. st.minpitch = -30;
  187. if (!st.maxpitch)
  188. st.maxpitch = 30;
  189. if (!st.maxyaw)
  190. st.maxyaw = 360;
  191. self->pos1[PITCH] = -1 * st.minpitch;
  192. self->pos1[YAW] = st.minyaw;
  193. self->pos2[PITCH] = -1 * st.maxpitch;
  194. self->pos2[YAW] = st.maxyaw;
  195. self->ideal_yaw = self->s.angles[YAW];
  196. self->move_angles[YAW] = self->ideal_yaw;
  197. self->blocked = turret_blocked;
  198. self->think = turret_breach_finish_init;
  199. self->nextthink = level.time + FRAMETIME;
  200. gi.linkentity (self);
  201. }
  202. /*QUAKED turret_base (0 0 0) ?
  203. This portion of the turret changes yaw only.
  204. MUST be teamed with a turret_breach.
  205. */
  206. void SP_turret_base (edict_t *self)
  207. {
  208. self->solid = SOLID_BSP;
  209. self->movetype = MOVETYPE_PUSH;
  210. gi.setmodel (self, self->model);
  211. self->blocked = turret_blocked;
  212. gi.linkentity (self);
  213. }
  214. /*QUAKED turret_driver (1 .5 0) (-16 -16 -24) (16 16 32)
  215. Must NOT be on the team with the rest of the turret parts.
  216. Instead it must target the turret_breach.
  217. */
  218. void infantry_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage);
  219. void infantry_stand (edict_t *self);
  220. void monster_use (edict_t *self, edict_t *other, edict_t *activator);
  221. void turret_driver_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
  222. {
  223. edict_t *ent;
  224. // level the gun
  225. self->target_ent->move_angles[0] = 0;
  226. // remove the driver from the end of them team chain
  227. for (ent = self->target_ent->teammaster; ent->teamchain != self; ent = ent->teamchain)
  228. ;
  229. ent->teamchain = NULL;
  230. self->teammaster = NULL;
  231. self->flags &= ~FL_TEAMSLAVE;
  232. self->target_ent->owner = NULL;
  233. self->target_ent->teammaster->owner = NULL;
  234. infantry_die (self, inflictor, attacker, damage);
  235. }
  236. qboolean FindTarget (edict_t *self);
  237. void turret_driver_think (edict_t *self)
  238. {
  239. vec3_t target;
  240. vec3_t dir;
  241. float reaction_time;
  242. self->nextthink = level.time + FRAMETIME;
  243. if (self->enemy && (!self->enemy->inuse || self->enemy->health <= 0))
  244. self->enemy = NULL;
  245. if (!self->enemy)
  246. {
  247. if (!FindTarget (self))
  248. return;
  249. self->monsterinfo.trail_time = level.time;
  250. self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
  251. }
  252. else
  253. {
  254. if (visible (self, self->enemy))
  255. {
  256. if (self->monsterinfo.aiflags & AI_LOST_SIGHT)
  257. {
  258. self->monsterinfo.trail_time = level.time;
  259. self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
  260. }
  261. }
  262. else
  263. {
  264. self->monsterinfo.aiflags |= AI_LOST_SIGHT;
  265. return;
  266. }
  267. }
  268. // let the turret know where we want it to aim
  269. VectorCopy (self->enemy->s.origin, target);
  270. target[2] += self->enemy->viewheight;
  271. VectorSubtract (target, self->target_ent->s.origin, dir);
  272. vectoangles (dir, self->target_ent->move_angles);
  273. // decide if we should shoot
  274. if (level.time < self->monsterinfo.attack_finished)
  275. return;
  276. reaction_time = (3 - skill->value) * 1.0;
  277. if ((level.time - self->monsterinfo.trail_time) < reaction_time)
  278. return;
  279. self->monsterinfo.attack_finished = level.time + reaction_time + 1.0;
  280. //FIXME how do we really want to pass this along?
  281. self->target_ent->spawnflags |= 65536;
  282. }
  283. void turret_driver_link (edict_t *self)
  284. {
  285. vec3_t vec;
  286. edict_t *ent;
  287. self->think = turret_driver_think;
  288. self->nextthink = level.time + FRAMETIME;
  289. self->target_ent = G_PickTarget (self->target);
  290. self->target_ent->owner = self;
  291. self->target_ent->teammaster->owner = self;
  292. VectorCopy (self->target_ent->s.angles, self->s.angles);
  293. vec[0] = self->target_ent->s.origin[0] - self->s.origin[0];
  294. vec[1] = self->target_ent->s.origin[1] - self->s.origin[1];
  295. vec[2] = 0;
  296. self->move_origin[0] = VectorLength(vec);
  297. VectorSubtract (self->s.origin, self->target_ent->s.origin, vec);
  298. vectoangles (vec, vec);
  299. AnglesNormalize(vec);
  300. self->move_origin[1] = vec[1];
  301. self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2];
  302. // add the driver to the end of them team chain
  303. for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain)
  304. ;
  305. ent->teamchain = self;
  306. self->teammaster = self->target_ent->teammaster;
  307. self->flags |= FL_TEAMSLAVE;
  308. }
  309. void SP_turret_driver (edict_t *self)
  310. {
  311. if (deathmatch->value)
  312. {
  313. G_FreeEdict (self);
  314. return;
  315. }
  316. self->movetype = MOVETYPE_PUSH;
  317. self->solid = SOLID_BBOX;
  318. self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2");
  319. VectorSet (self->mins, -16, -16, -24);
  320. VectorSet (self->maxs, 16, 16, 32);
  321. self->health = 100;
  322. self->gib_health = 0;
  323. self->mass = 200;
  324. self->viewheight = 24;
  325. self->die = turret_driver_die;
  326. self->monsterinfo.stand = infantry_stand;
  327. self->flags |= FL_NO_KNOCKBACK;
  328. level.total_monsters++;
  329. self->svflags |= SVF_MONSTER;
  330. self->s.renderfx |= RF_FRAMELERP;
  331. self->takedamage = DAMAGE_AIM;
  332. self->use = monster_use;
  333. self->clipmask = MASK_MONSTERSOLID;
  334. VectorCopy (self->s.origin, self->s.old_origin);
  335. self->monsterinfo.aiflags |= AI_STAND_GROUND|AI_DUCKED;
  336. if (st.item)
  337. {
  338. self->item = FindItemByClassname (st.item);
  339. if (!self->item)
  340. gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item);
  341. }
  342. self->think = turret_driver_link;
  343. self->nextthink = level.time + FRAMETIME;
  344. gi.linkentity (self);
  345. }
  346. //============
  347. // ROGUE
  348. // invisible turret drivers so we can have unmanned turrets.
  349. // originally designed to shoot at func_trains and such, so they
  350. // fire at the center of the bounding box, rather than the entity's
  351. // origin.
  352. void turret_brain_think (edict_t *self)
  353. {
  354. vec3_t target;
  355. vec3_t dir;
  356. vec3_t endpos;
  357. float reaction_time;
  358. trace_t trace;
  359. self->nextthink = level.time + FRAMETIME;
  360. if (self->enemy)
  361. {
  362. if(!self->enemy->inuse)
  363. self->enemy = NULL;
  364. else if(self->enemy->takedamage && self->enemy->health <= 0)
  365. self->enemy = NULL;
  366. }
  367. if (!self->enemy)
  368. {
  369. if (!FindTarget (self))
  370. return;
  371. self->monsterinfo.trail_time = level.time;
  372. self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
  373. }
  374. else
  375. {
  376. VectorAdd (self->enemy->absmax, self->enemy->absmin, endpos);
  377. VectorScale (endpos, 0.5, endpos);
  378. trace = gi.trace (self->target_ent->s.origin, vec3_origin, vec3_origin, endpos, self->target_ent, MASK_SHOT);
  379. if(trace.fraction == 1 || trace.ent == self->enemy)
  380. {
  381. if (self->monsterinfo.aiflags & AI_LOST_SIGHT)
  382. {
  383. self->monsterinfo.trail_time = level.time;
  384. self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
  385. }
  386. }
  387. else
  388. {
  389. self->monsterinfo.aiflags |= AI_LOST_SIGHT;
  390. return;
  391. }
  392. }
  393. // let the turret know where we want it to aim
  394. VectorCopy (endpos, target);
  395. VectorSubtract (target, self->target_ent->s.origin, dir);
  396. vectoangles (dir, self->target_ent->move_angles);
  397. // decide if we should shoot
  398. if (level.time < self->monsterinfo.attack_finished)
  399. return;
  400. if(self->delay)
  401. reaction_time = self->delay;
  402. else
  403. reaction_time = (3 - skill->value) * 1.0;
  404. if ((level.time - self->monsterinfo.trail_time) < reaction_time)
  405. return;
  406. self->monsterinfo.attack_finished = level.time + reaction_time + 1.0;
  407. //FIXME how do we really want to pass this along?
  408. self->target_ent->spawnflags |= 65536;
  409. }
  410. // =================
  411. // =================
  412. void turret_brain_link (edict_t *self)
  413. {
  414. vec3_t vec;
  415. edict_t *ent;
  416. if (self->killtarget)
  417. {
  418. self->enemy = G_PickTarget (self->killtarget);
  419. }
  420. self->think = turret_brain_think;
  421. self->nextthink = level.time + FRAMETIME;
  422. self->target_ent = G_PickTarget (self->target);
  423. self->target_ent->owner = self;
  424. self->target_ent->teammaster->owner = self;
  425. VectorCopy (self->target_ent->s.angles, self->s.angles);
  426. vec[0] = self->target_ent->s.origin[0] - self->s.origin[0];
  427. vec[1] = self->target_ent->s.origin[1] - self->s.origin[1];
  428. vec[2] = 0;
  429. self->move_origin[0] = VectorLength(vec);
  430. VectorSubtract (self->s.origin, self->target_ent->s.origin, vec);
  431. vectoangles (vec, vec);
  432. AnglesNormalize(vec);
  433. self->move_origin[1] = vec[1];
  434. self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2];
  435. // add the driver to the end of them team chain
  436. for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain)
  437. ;
  438. ent->teamchain = self;
  439. self->teammaster = self->target_ent->teammaster;
  440. self->flags |= FL_TEAMSLAVE;
  441. }
  442. // =================
  443. // =================
  444. void turret_brain_deactivate (edict_t *self, edict_t *other, edict_t *activator)
  445. {
  446. self->think = NULL;
  447. self->nextthink = 0;
  448. }
  449. // =================
  450. // =================
  451. void turret_brain_activate (edict_t *self, edict_t *other, edict_t *activator)
  452. {
  453. if (!self->enemy)
  454. {
  455. self->enemy = activator;
  456. }
  457. // wait at least 3 seconds to fire.
  458. self->monsterinfo.attack_finished = level.time + 3;
  459. self->use = turret_brain_deactivate;
  460. self->think = turret_brain_link;
  461. self->nextthink = level.time + FRAMETIME;
  462. }
  463. /*QUAKED turret_invisible_brain (1 .5 0) (-16 -16 -16) (16 16 16)
  464. Invisible brain to drive the turret.
  465. Does not search for targets. If targeted, can only be turned on once
  466. and then off once. After that they are completely disabled.
  467. "delay" the delay between firing (default ramps for skill level)
  468. "Target" the turret breach
  469. "Killtarget" the item you want it to attack.
  470. Target the brain if you want it activated later, instead of immediately. It will wait 3 seconds
  471. before firing to acquire the target.
  472. */
  473. void SP_turret_invisible_brain (edict_t *self)
  474. {
  475. if (!self->killtarget)
  476. {
  477. gi.dprintf("turret_invisible_brain with no killtarget!\n");
  478. G_FreeEdict (self);
  479. return;
  480. }
  481. if (!self->target)
  482. {
  483. gi.dprintf("turret_invisible_brain with no target!\n");
  484. G_FreeEdict (self);
  485. return;
  486. }
  487. if (self->targetname)
  488. {
  489. self->use = turret_brain_activate;
  490. }
  491. else
  492. {
  493. self->think = turret_brain_link;
  494. self->nextthink = level.time + FRAMETIME;
  495. }
  496. self->movetype = MOVETYPE_PUSH;
  497. gi.linkentity (self);
  498. }
  499. // ROGUE
  500. //============