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 	damage = 100 + random() * 50;
72 	speed = 550 + 50 * skill->value;
73 	fire_rocket (self->teammaster->owner, start, f, damage, speed, 150, damage);
74 	gi.positioned_sound (start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0);
75 }
76 
turret_breach_think(edict_t * self)77 void turret_breach_think (edict_t *self)
78 {
79 	edict_t	*ent;
80 	vec3_t	current_angles;
81 	vec3_t	delta;
82 
83 	VectorCopy (self->s.angles, current_angles);
84 	AnglesNormalize(current_angles);
85 
86 	AnglesNormalize(self->move_angles);
87 	if (self->move_angles[PITCH] > 180)
88 		self->move_angles[PITCH] -= 360;
89 
90 	// clamp angles to mins & maxs
91 	if (self->move_angles[PITCH] > self->pos1[PITCH])
92 		self->move_angles[PITCH] = self->pos1[PITCH];
93 	else if (self->move_angles[PITCH] < self->pos2[PITCH])
94 		self->move_angles[PITCH] = self->pos2[PITCH];
95 
96 	if ((self->move_angles[YAW] < self->pos1[YAW]) || (self->move_angles[YAW] > self->pos2[YAW]))
97 	{
98 		float	dmin, dmax;
99 
100 		dmin = fabs(self->pos1[YAW] - self->move_angles[YAW]);
101 		if (dmin < -180)
102 			dmin += 360;
103 		else if (dmin > 180)
104 			dmin -= 360;
105 		dmax = fabs(self->pos2[YAW] - self->move_angles[YAW]);
106 		if (dmax < -180)
107 			dmax += 360;
108 		else if (dmax > 180)
109 			dmax -= 360;
110 		if (fabs(dmin) < fabs(dmax))
111 			self->move_angles[YAW] = self->pos1[YAW];
112 		else
113 			self->move_angles[YAW] = self->pos2[YAW];
114 	}
115 
116 	VectorSubtract (self->move_angles, current_angles, delta);
117 	if (delta[0] < -180)
118 		delta[0] += 360;
119 	else if (delta[0] > 180)
120 		delta[0] -= 360;
121 	if (delta[1] < -180)
122 		delta[1] += 360;
123 	else if (delta[1] > 180)
124 		delta[1] -= 360;
125 	delta[2] = 0;
126 
127 	if (delta[0] > self->speed * FRAMETIME)
128 		delta[0] = self->speed * FRAMETIME;
129 	if (delta[0] < -1 * self->speed * FRAMETIME)
130 		delta[0] = -1 * self->speed * FRAMETIME;
131 	if (delta[1] > self->speed * FRAMETIME)
132 		delta[1] = self->speed * FRAMETIME;
133 	if (delta[1] < -1 * self->speed * FRAMETIME)
134 		delta[1] = -1 * self->speed * FRAMETIME;
135 
136 	VectorScale (delta, 1.0/FRAMETIME, self->avelocity);
137 
138 	self->nextthink = level.time + FRAMETIME;
139 
140 	for (ent = self->teammaster; ent; ent = ent->teamchain)
141 		ent->avelocity[1] = self->avelocity[1];
142 
143 	// if we have adriver, adjust his velocities
144 	if (self->owner)
145 	{
146 		float	angle;
147 		float	target_z;
148 		float	diff;
149 		vec3_t	target;
150 		vec3_t	dir;
151 
152 		// angular is easy, just copy ours
153 		self->owner->avelocity[0] = self->avelocity[0];
154 		self->owner->avelocity[1] = self->avelocity[1];
155 
156 		// x & y
157 		angle = self->s.angles[1] + self->owner->move_origin[1];
158 		angle *= (M_PI*2 / 360);
159 		target[0] = SnapToEights(self->s.origin[0] + cos(angle) * self->owner->move_origin[0]);
160 		target[1] = SnapToEights(self->s.origin[1] + sin(angle) * self->owner->move_origin[0]);
161 		target[2] = self->owner->s.origin[2];
162 
163 		VectorSubtract (target, self->owner->s.origin, dir);
164 		self->owner->velocity[0] = dir[0] * 1.0 / FRAMETIME;
165 		self->owner->velocity[1] = dir[1] * 1.0 / FRAMETIME;
166 
167 		// z
168 		angle = self->s.angles[PITCH] * (M_PI*2 / 360);
169 		target_z = SnapToEights(self->s.origin[2] + self->owner->move_origin[0] * tan(angle) + self->owner->move_origin[2]);
170 
171 		diff = target_z - self->owner->s.origin[2];
172 		self->owner->velocity[2] = diff * 1.0 / FRAMETIME;
173 
174 		if (self->spawnflags & 65536)
175 		{
176 			turret_breach_fire (self);
177 			self->spawnflags &= ~65536;
178 		}
179 	}
180 }
181 
turret_breach_finish_init(edict_t * self)182 void turret_breach_finish_init (edict_t *self)
183 {
184 	// get and save info for muzzle location
185 	if (!self->target)
186 	{
187 		gi.dprintf("%s at %s needs a target\n", self->classname, vtos(self->s.origin));
188 	}
189 	else
190 	{
191 		self->target_ent = G_PickTarget (self->target);
192 		VectorSubtract (self->target_ent->s.origin, self->s.origin, self->move_origin);
193 		G_FreeEdict(self->target_ent);
194 	}
195 
196 	self->teammaster->dmg = self->dmg;
197 	self->think = turret_breach_think;
198 	self->think (self);
199 }
200 
SP_turret_breach(edict_t * self)201 void SP_turret_breach (edict_t *self)
202 {
203 	self->solid = SOLID_BSP;
204 	self->movetype = MOVETYPE_PUSH;
205 	gi.setmodel (self, self->model);
206 
207 	if (!self->speed)
208 		self->speed = 50;
209 	if (!self->dmg)
210 		self->dmg = 10;
211 
212 	if (!st.minpitch)
213 		st.minpitch = -30;
214 	if (!st.maxpitch)
215 		st.maxpitch = 30;
216 	if (!st.maxyaw)
217 		st.maxyaw = 360;
218 
219 	self->pos1[PITCH] = -1 * st.minpitch;
220 	self->pos1[YAW]   = st.minyaw;
221 	self->pos2[PITCH] = -1 * st.maxpitch;
222 	self->pos2[YAW]   = st.maxyaw;
223 
224 	self->ideal_yaw = self->s.angles[YAW];
225 	self->move_angles[YAW] = self->ideal_yaw;
226 
227 	self->blocked = turret_blocked;
228 
229 	self->think = turret_breach_finish_init;
230 	self->nextthink = level.time + FRAMETIME;
231 	gi.linkentity (self);
232 }
233 
234 
235 /*QUAKED turret_base (0 0 0) ?
236 This portion of the turret changes yaw only.
237 MUST be teamed with a turret_breach.
238 */
239 
SP_turret_base(edict_t * self)240 void SP_turret_base (edict_t *self)
241 {
242 	self->solid = SOLID_BSP;
243 	self->movetype = MOVETYPE_PUSH;
244 	gi.setmodel (self, self->model);
245 	self->blocked = turret_blocked;
246 	gi.linkentity (self);
247 }
248 
249 
250 /*QUAKED turret_driver (1 .5 0) (-16 -16 -24) (16 16 32)
251 Must NOT be on the team with the rest of the turret parts.
252 Instead it must target the turret_breach.
253 */
254 
255 void infantry_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage);
256 void infantry_stand (edict_t *self);
257 void monster_use (edict_t *self, edict_t *other, edict_t *activator);
258 
turret_driver_die(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)259 void turret_driver_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
260 {
261 	edict_t	*ent;
262 
263 	// level the gun
264 	self->target_ent->move_angles[0] = 0;
265 
266 	// remove the driver from the end of them team chain
267 	for (ent = self->target_ent->teammaster; ent->teamchain != self; ent = ent->teamchain)
268 		;
269 	ent->teamchain = NULL;
270 	self->teammaster = NULL;
271 	self->flags &= ~FL_TEAMSLAVE;
272 
273 	self->target_ent->owner = NULL;
274 	self->target_ent->teammaster->owner = NULL;
275 
276 	infantry_die (self, inflictor, attacker, damage);
277 }
278 
279 qboolean FindTarget (edict_t *self);
280 
turret_driver_think(edict_t * self)281 void turret_driver_think (edict_t *self)
282 {
283 	vec3_t	target;
284 	vec3_t	dir;
285 	float	reaction_time;
286 
287 	self->nextthink = level.time + FRAMETIME;
288 
289 	if (self->enemy && (!self->enemy->inuse || self->enemy->health <= 0))
290 		self->enemy = NULL;
291 
292 	if (!self->enemy)
293 	{
294 		if (!FindTarget (self))
295 			return;
296 		self->monsterinfo.trail_time = level.time;
297 		self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
298 	}
299 	else
300 	{
301 		if (visible (self, self->enemy))
302 		{
303 			if (self->monsterinfo.aiflags & AI_LOST_SIGHT)
304 			{
305 				self->monsterinfo.trail_time = level.time;
306 				self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
307 			}
308 		}
309 		else
310 		{
311 			self->monsterinfo.aiflags |= AI_LOST_SIGHT;
312 			return;
313 		}
314 	}
315 
316 	// let the turret know where we want it to aim
317 	VectorCopy (self->enemy->s.origin, target);
318 	target[2] += self->enemy->viewheight;
319 	VectorSubtract (target, self->target_ent->s.origin, dir);
320 	vectoangles (dir, self->target_ent->move_angles);
321 
322 	// decide if we should shoot
323 	if (level.time < self->monsterinfo.attack_finished)
324 		return;
325 
326 	reaction_time = (3 - skill->value) * 1.0;
327 	if ((level.time - self->monsterinfo.trail_time) < reaction_time)
328 		return;
329 
330 	self->monsterinfo.attack_finished = level.time + reaction_time + 1.0;
331 	//FIXME how do we really want to pass this along?
332 	self->target_ent->spawnflags |= 65536;
333 }
334 
turret_driver_link(edict_t * self)335 void turret_driver_link (edict_t *self)
336 {
337 	vec3_t	vec;
338 	edict_t	*ent;
339 
340 	self->think = turret_driver_think;
341 	self->nextthink = level.time + FRAMETIME;
342 
343 	self->target_ent = G_PickTarget (self->target);
344 	self->target_ent->owner = self;
345 	self->target_ent->teammaster->owner = self;
346 	VectorCopy (self->target_ent->s.angles, self->s.angles);
347 
348 	vec[0] = self->target_ent->s.origin[0] - self->s.origin[0];
349 	vec[1] = self->target_ent->s.origin[1] - self->s.origin[1];
350 	vec[2] = 0;
351 	self->move_origin[0] = VectorLength(vec);
352 
353 	VectorSubtract (self->s.origin, self->target_ent->s.origin, vec);
354 	vectoangles (vec, vec);
355 	AnglesNormalize(vec);
356 	self->move_origin[1] = vec[1];
357 
358 	self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2];
359 
360 	// add the driver to the end of them team chain
361 	for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain)
362 		;
363 	ent->teamchain = self;
364 	self->teammaster = self->target_ent->teammaster;
365 	self->flags |= FL_TEAMSLAVE;
366 }
367 
SP_turret_driver(edict_t * self)368 void SP_turret_driver (edict_t *self)
369 {
370 	if (deathmatch->value)
371 	{
372 		G_FreeEdict (self);
373 		return;
374 	}
375 
376 	self->movetype = MOVETYPE_PUSH;
377 	self->solid = SOLID_BBOX;
378 	self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2");
379 	VectorSet (self->mins, -16, -16, -24);
380 	VectorSet (self->maxs, 16, 16, 32);
381 
382 	self->health = 100;
383 	self->gib_health = 0;
384 	self->mass = 200;
385 	self->viewheight = 24;
386 
387 	self->die = turret_driver_die;
388 	self->monsterinfo.stand = infantry_stand;
389 
390 	self->flags |= FL_NO_KNOCKBACK;
391 
392 	level.total_monsters++;
393 
394 	self->svflags |= SVF_MONSTER;
395 	self->s.renderfx |= RF_FRAMELERP;
396 	self->takedamage = DAMAGE_AIM;
397 	self->use = monster_use;
398 	self->clipmask = MASK_MONSTERSOLID;
399 	VectorCopy (self->s.origin, self->s.old_origin);
400 	self->monsterinfo.aiflags |= AI_STAND_GROUND|AI_DUCKED;
401 
402 	if (st.item)
403 	{
404 		self->item = FindItemByClassname (st.item);
405 		if (!self->item)
406 			gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item);
407 	}
408 
409 	self->think = turret_driver_link;
410 	self->nextthink = level.time + FRAMETIME;
411 
412 	gi.linkentity (self);
413 }
414