1 // g_turret.c
2 
3 #include "g_local.h"
4 
5 
AnglesNormalize(vec3_t vec)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 
SnapToEights(float x)18 float SnapToEights(float x)
19 {
20 	x *= 8.0;
21 	if (x > 0.0)
22 		x += 0.5;
23 	else
24 		x -= 0.5;
25 	return 0.125 * (int)x;
26 }
27 
28 
turret_blocked(edict_t * self,edict_t * other)29 void turret_blocked(edict_t *self, edict_t *other)
30 {
31 	edict_t	*attacker;
32 
33 	if (other->takedamage)
34 	{
35 		if (self->teammaster->owner)
36 			attacker = self->teammaster->owner;
37 		else
38 			attacker = self->teammaster;
39 		T_Damage (other, self, attacker, vec3_origin, other->s.origin, vec3_origin, self->teammaster->dmg, 10, 0, MOD_CRUSH);
40 	}
41 }
42 
43 /*QUAKED turret_breach (0 0 0) ?
44 This portion of the turret can change both pitch and yaw.
45 The model  should be made with a flat pitch.
46 It (and the associated base) need to be oriented towards 0.
47 Use "angle" to set the starting angle.
48 
49 "speed"		default 50
50 "dmg"		default 10
51 "angle"		point this forward
52 "target"	point this at an info_notnull at the muzzle tip
53 "minpitch"	min acceptable pitch angle : default -30
54 "maxpitch"	max acceptable pitch angle : default 30
55 "minyaw"	min acceptable yaw angle   : default 0
56 "maxyaw"	max acceptable yaw angle   : default 360
57 */
58 
turret_breach_fire(edict_t * self)59 void turret_breach_fire (edict_t *self)
60 {
61 	vec3_t	f, r, u;
62 	vec3_t	start;
63 	int		damage;
64 	int		speed;
65 
66 	AngleVectors (self->s.angles, f, r, u);
67 	VectorMA (self->s.origin, self->move_origin[0], f, start);
68 	VectorMA (start, self->move_origin[1], r, start);
69 	VectorMA (start, self->move_origin[2], u, start);
70 
71   if(EMPNukeCheck(self, start))
72   {
73 		gi.sound (self, CHAN_AUTO, gi.soundindex("items/empnuke/emp_missfire.wav"), 1, ATTN_NORM, 0);
74     return;
75   }
76 
77 	damage = 100 + random() * 50;
78 	speed = 550 + 50 * skill->value;
79 	fire_rocket (self->teammaster->owner, start, f, damage, speed, 150, damage);
80 	gi.positioned_sound (start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0);
81 }
82 
turret_breach_think(edict_t * self)83 void turret_breach_think (edict_t *self)
84 {
85 	edict_t	*ent;
86 	vec3_t	current_angles;
87 	vec3_t	delta;
88 
89 	VectorCopy (self->s.angles, current_angles);
90 	AnglesNormalize(current_angles);
91 
92 	AnglesNormalize(self->move_angles);
93 	if (self->move_angles[PITCH] > 180)
94 		self->move_angles[PITCH] -= 360;
95 
96 	// clamp angles to mins & maxs
97 	if (self->move_angles[PITCH] > self->pos1[PITCH])
98 		self->move_angles[PITCH] = self->pos1[PITCH];
99 	else if (self->move_angles[PITCH] < self->pos2[PITCH])
100 		self->move_angles[PITCH] = self->pos2[PITCH];
101 
102 	if ((self->move_angles[YAW] < self->pos1[YAW]) || (self->move_angles[YAW] > self->pos2[YAW]))
103 	{
104 		float	dmin, dmax;
105 
106 		dmin = fabs(self->pos1[YAW] - self->move_angles[YAW]);
107 		if (dmin < -180)
108 			dmin += 360;
109 		else if (dmin > 180)
110 			dmin -= 360;
111 		dmax = fabs(self->pos2[YAW] - self->move_angles[YAW]);
112 		if (dmax < -180)
113 			dmax += 360;
114 		else if (dmax > 180)
115 			dmax -= 360;
116 		if (fabs(dmin) < fabs(dmax))
117 			self->move_angles[YAW] = self->pos1[YAW];
118 		else
119 			self->move_angles[YAW] = self->pos2[YAW];
120 	}
121 
122 	VectorSubtract (self->move_angles, current_angles, delta);
123 	if (delta[0] < -180)
124 		delta[0] += 360;
125 	else if (delta[0] > 180)
126 		delta[0] -= 360;
127 	if (delta[1] < -180)
128 		delta[1] += 360;
129 	else if (delta[1] > 180)
130 		delta[1] -= 360;
131 	delta[2] = 0;
132 
133 	if (delta[0] > self->speed * FRAMETIME)
134 		delta[0] = self->speed * FRAMETIME;
135 	if (delta[0] < -1 * self->speed * FRAMETIME)
136 		delta[0] = -1 * self->speed * FRAMETIME;
137 	if (delta[1] > self->speed * FRAMETIME)
138 		delta[1] = self->speed * FRAMETIME;
139 	if (delta[1] < -1 * self->speed * FRAMETIME)
140 		delta[1] = -1 * self->speed * FRAMETIME;
141 
142 	VectorScale (delta, 1.0/FRAMETIME, self->avelocity);
143 
144 	self->nextthink = level.time + FRAMETIME;
145 
146 	for (ent = self->teammaster; ent; ent = ent->teamchain)
147 		ent->avelocity[1] = self->avelocity[1];
148 
149 	// if we have adriver, adjust his velocities
150 	if (self->owner)
151 	{
152 		float	angle;
153 		float	target_z;
154 		float	diff;
155 		vec3_t	target;
156 		vec3_t	dir;
157 
158 		// angular is easy, just copy ours
159 		self->owner->avelocity[0] = self->avelocity[0];
160 		self->owner->avelocity[1] = self->avelocity[1];
161 
162 		// x & y
163 		angle = self->s.angles[1] + self->owner->move_origin[1];
164 		angle *= (M_PI*2 / 360);
165 		target[0] = SnapToEights(self->s.origin[0] + cos(angle) * self->owner->move_origin[0]);
166 		target[1] = SnapToEights(self->s.origin[1] + sin(angle) * self->owner->move_origin[0]);
167 		target[2] = self->owner->s.origin[2];
168 
169 		VectorSubtract (target, self->owner->s.origin, dir);
170 		self->owner->velocity[0] = dir[0] * 1.0 / FRAMETIME;
171 		self->owner->velocity[1] = dir[1] * 1.0 / FRAMETIME;
172 
173 		// z
174 		angle = self->s.angles[PITCH] * (M_PI*2 / 360);
175 		target_z = SnapToEights(self->s.origin[2] + self->owner->move_origin[0] * tan(angle) + self->owner->move_origin[2]);
176 
177 		diff = target_z - self->owner->s.origin[2];
178 		self->owner->velocity[2] = diff * 1.0 / FRAMETIME;
179 
180 		if (self->spawnflags & 65536)
181 		{
182 			turret_breach_fire (self);
183 			self->spawnflags &= ~65536;
184 		}
185 	}
186 }
187 
turret_breach_finish_init(edict_t * self)188 void turret_breach_finish_init (edict_t *self)
189 {
190 	// get and save info for muzzle location
191 	if (!self->target)
192 	{
193 		gi.dprintf("%s at %s needs a target\n", self->classname, vtos(self->s.origin));
194 	}
195 	else
196 	{
197 		self->target_ent = G_PickTarget (self->target);
198 		VectorSubtract (self->target_ent->s.origin, self->s.origin, self->move_origin);
199 		G_FreeEdict(self->target_ent);
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 	if(!(self->spawnflags & 16))
399 	{
400 		level.total_monsters++;
401 	}
402 
403 	self->svflags |= SVF_MONSTER;
404 	self->s.renderfx |= RF_FRAMELERP;
405 	self->takedamage = DAMAGE_AIM;
406 	self->use = monster_use;
407 	self->clipmask = MASK_MONSTERSOLID;
408 	VectorCopy (self->s.origin, self->s.old_origin);
409 	self->monsterinfo.aiflags |= AI_STAND_GROUND|AI_DUCKED;
410 
411 	if (st.item)
412 	{
413 		self->item = FindItemByClassname (st.item);
414 		if (!self->item)
415 			gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item);
416 	}
417 
418 	self->think = turret_driver_link;
419 	self->nextthink = level.time + FRAMETIME;
420 
421 	gi.linkentity (self);
422 }
423