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