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