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 /*
257 void infantry_stand (edict_t *self);
258 void monster_use (edict_t *self, edict_t *other, edict_t *activator);
259 */
260 
261 
turret_driver_die(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)262 void turret_driver_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
263 {
264 	edict_t	*ent;
265 
266 	// level the gun
267 	self->target_ent->move_angles[0] = 0;
268 
269 	// remove the driver from the end of them team chain
270 	for (ent = self->target_ent->teammaster; ent->teamchain != self; ent = ent->teamchain)
271 		;
272 	ent->teamchain = NULL;
273 	self->teammaster = NULL;
274 	self->flags &= ~FL_TEAMSLAVE;
275 
276 	self->target_ent->owner = NULL;
277 	self->target_ent->teammaster->owner = NULL;
278 
279 //	infantry_die (self, inflictor, attacker, damage);
280 
281 }
282 
283 
284 //qboolean FindTarget (edict_t *self);
285 
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 		/*
301 		if (!FindTarget (self))
302 			return;
303 		*/
304 
305 		self->monsterinfo.trail_time = level.time;
306 		self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
307 	}
308 	else
309 	{
310 		if (visible (self, self->enemy))
311 		{
312 			if (self->monsterinfo.aiflags & AI_LOST_SIGHT)
313 			{
314 				self->monsterinfo.trail_time = level.time;
315 				self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
316 			}
317 		}
318 		else
319 		{
320 			self->monsterinfo.aiflags |= AI_LOST_SIGHT;
321 			return;
322 		}
323 	}
324 
325 	// let the turret know where we want it to aim
326 	VectorCopy (self->enemy->s.origin, target);
327 	target[2] += self->enemy->viewheight;
328 	VectorSubtract (target, self->target_ent->s.origin, dir);
329 	vectoangles (dir, self->target_ent->move_angles);
330 
331 	// decide if we should shoot
332 	if (level.time < self->monsterinfo.attack_finished)
333 		return;
334 
335 	reaction_time = (3 - skill->value) * 1.0;
336 	if ((level.time - self->monsterinfo.trail_time) < reaction_time)
337 		return;
338 
339 	self->monsterinfo.attack_finished = level.time + reaction_time + 1.0;
340 	//FIXME how do we really want to pass this along?
341 	self->target_ent->spawnflags |= 65536;
342 }
343 
turret_driver_link(edict_t * self)344 void turret_driver_link (edict_t *self)
345 {
346 	vec3_t	vec;
347 	edict_t	*ent;
348 
349 	self->think = turret_driver_think;
350 	self->nextthink = level.time + FRAMETIME;
351 
352 	self->target_ent = G_PickTarget (self->target);
353 	self->target_ent->owner = self;
354 	self->target_ent->teammaster->owner = self;
355 	VectorCopy (self->target_ent->s.angles, self->s.angles);
356 
357 	vec[0] = self->target_ent->s.origin[0] - self->s.origin[0];
358 	vec[1] = self->target_ent->s.origin[1] - self->s.origin[1];
359 	vec[2] = 0;
360 	self->move_origin[0] = VectorLength(vec);
361 
362 	VectorSubtract (self->s.origin, self->target_ent->s.origin, vec);
363 	vectoangles (vec, vec);
364 	AnglesNormalize(vec);
365 	self->move_origin[1] = vec[1];
366 
367 	self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2];
368 
369 	// add the driver to the end of them team chain
370 	for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain)
371 		;
372 	ent->teamchain = self;
373 	self->teammaster = self->target_ent->teammaster;
374 	self->flags |= FL_TEAMSLAVE;
375 }
376 
SP_turret_driver(edict_t * self)377 void SP_turret_driver (edict_t *self)
378 {
379 	if (deathmatch->value)
380 	{
381 		G_FreeEdict (self);
382 		return;
383 	}
384 
385 	self->movetype = MOVETYPE_PUSH;
386 	self->solid = SOLID_BBOX;
387 	self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2");
388 	VectorSet (self->mins, -16, -16, -24);
389 	VectorSet (self->maxs, 16, 16, 32);
390 
391 	self->health = 100;
392 	self->gib_health = 0;
393 	self->mass = 200;
394 	self->viewheight = 24;
395 
396 	self->die = turret_driver_die;
397 
398 //	self->monsterinfo.stand = infantry_stand;
399 
400 	self->flags |= FL_NO_KNOCKBACK;
401 
402 	level.total_monsters++;
403 
404 	self->svflags |= SVF_MONSTER;
405 	self->s.renderfx |= RF_FRAMELERP;
406 	self->takedamage = DAMAGE_AIM;
407 
408 //	self->use = monster_use;
409 
410 	self->clipmask = MASK_MONSTERSOLID;
411 	VectorCopy (self->s.origin, self->s.old_origin);
412 	self->monsterinfo.aiflags |= AI_STAND_GROUND|AI_DUCKED;
413 
414 	if (st.item)
415 	{
416 		self->item = FindItemByClassname (st.item);
417 		if (!self->item)
418 			gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item);
419 	}
420 
421 	self->think = turret_driver_link;
422 	self->nextthink = level.time + FRAMETIME;
423 
424 	gi.linkentity (self);
425 }
426