g_turret.cpp 17 KB

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