1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19 */
20 // g_turret.c
21 
22 #include "g_local.h"
23 
24 
AnglesNormalize(vec3_t vec)25 void AnglesNormalize(vec3_t vec)
26 {
27 	while(vec[0] > 360)
28 		vec[0] -= 360;
29 	while(vec[0] < 0)
30 		vec[0] += 360;
31 	while(vec[1] > 360)
32 		vec[1] -= 360;
33 	while(vec[1] < 0)
34 		vec[1] += 360;
35 }
36 
SnapToEights(float x)37 float SnapToEights(float x)
38 {
39 	x *= 8.0;
40 	if (x > 0.0)
41 		x += 0.5;
42 	else
43 		x -= 0.5;
44 	return 0.125 * (int)x;
45 }
46 
47 
turret_blocked(edict_t * self,edict_t * other)48 void turret_blocked(edict_t *self, edict_t *other)
49 {
50 	edict_t	*attacker;
51 
52 	if (other->takedamage)
53 	{
54 		if (self->teammaster->owner)
55 			attacker = self->teammaster->owner;
56 		else
57 			attacker = self->teammaster;
58 		T_Damage (other, self, attacker, vec3_origin, other->s.origin, vec3_origin, self->teammaster->dmg, 10, 0, MOD_CRUSH);
59 	}
60 }
61 
62 /*QUAKED turret_breach (0 0 0) ?
63 This portion of the turret can change both pitch and yaw.
64 The model  should be made with a flat pitch.
65 It (and the associated base) need to be oriented towards 0.
66 Use "angle" to set the starting angle.
67 
68 "speed"		default 50
69 "dmg"		default 10
70 "angle"		point this forward
71 "target"	point this at an info_notnull at the muzzle tip
72 "minpitch"	min acceptable pitch angle : default -30
73 "maxpitch"	max acceptable pitch angle : default 30
74 "minyaw"	min acceptable yaw angle   : default 0
75 "maxyaw"	max acceptable yaw angle   : default 360
76 */
77 
turret_breach_fire(edict_t * self)78 void turret_breach_fire (edict_t *self)
79 {
80 	vec3_t	f, r, u;
81 	vec3_t	start;
82 	int		damage;
83 	int		speed;
84 
85 	AngleVectors (self->s.angles, f, r, u);
86 	VectorMA (self->s.origin, self->move_origin[0], f, start);
87 	VectorMA (start, self->move_origin[1], r, start);
88 	VectorMA (start, self->move_origin[2], u, start);
89 
90 	damage = 100 + random() * 50;
91 	speed = 550 + 50 * skill->value;
92 	fire_rocket (self->teammaster->owner, start, f, damage, speed, 150, damage);
93 	gi.positioned_sound (start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0);
94 }
95 
turret_breach_think(edict_t * self)96 void turret_breach_think (edict_t *self)
97 {
98 	edict_t	*ent;
99 	vec3_t	current_angles;
100 	vec3_t	delta;
101 
102 	VectorCopy (self->s.angles, current_angles);
103 	AnglesNormalize(current_angles);
104 
105 	AnglesNormalize(self->move_angles);
106 	if (self->move_angles[PITCH] > 180)
107 		self->move_angles[PITCH] -= 360;
108 
109 	// clamp angles to mins & maxs
110 	if (self->move_angles[PITCH] > self->pos1[PITCH])
111 		self->move_angles[PITCH] = self->pos1[PITCH];
112 	else if (self->move_angles[PITCH] < self->pos2[PITCH])
113 		self->move_angles[PITCH] = self->pos2[PITCH];
114 
115 	if ((self->move_angles[YAW] < self->pos1[YAW]) || (self->move_angles[YAW] > self->pos2[YAW]))
116 	{
117 		float	dmin, dmax;
118 
119 		dmin = fabs(self->pos1[YAW] - self->move_angles[YAW]);
120 		if (dmin < -180)
121 			dmin += 360;
122 		else if (dmin > 180)
123 			dmin -= 360;
124 		dmax = fabs(self->pos2[YAW] - self->move_angles[YAW]);
125 		if (dmax < -180)
126 			dmax += 360;
127 		else if (dmax > 180)
128 			dmax -= 360;
129 		if (fabs(dmin) < fabs(dmax))
130 			self->move_angles[YAW] = self->pos1[YAW];
131 		else
132 			self->move_angles[YAW] = self->pos2[YAW];
133 	}
134 
135 	VectorSubtract (self->move_angles, current_angles, delta);
136 	if (delta[0] < -180)
137 		delta[0] += 360;
138 	else if (delta[0] > 180)
139 		delta[0] -= 360;
140 	if (delta[1] < -180)
141 		delta[1] += 360;
142 	else if (delta[1] > 180)
143 		delta[1] -= 360;
144 	delta[2] = 0;
145 
146 	if (delta[0] > self->speed * FRAMETIME)
147 		delta[0] = self->speed * FRAMETIME;
148 	if (delta[0] < -1 * self->speed * FRAMETIME)
149 		delta[0] = -1 * self->speed * FRAMETIME;
150 	if (delta[1] > self->speed * FRAMETIME)
151 		delta[1] = self->speed * FRAMETIME;
152 	if (delta[1] < -1 * self->speed * FRAMETIME)
153 		delta[1] = -1 * self->speed * FRAMETIME;
154 
155 	VectorScale (delta, 1.0/FRAMETIME, self->avelocity);
156 
157 	self->nextthink = level.time + FRAMETIME;
158 
159 	for (ent = self->teammaster; ent; ent = ent->teamchain)
160 		ent->avelocity[1] = self->avelocity[1];
161 
162 	// if we have adriver, adjust his velocities
163 	if (self->owner)
164 	{
165 		float	angle;
166 		float	target_z;
167 		float	diff;
168 		vec3_t	target;
169 		vec3_t	dir;
170 
171 		// angular is easy, just copy ours
172 		self->owner->avelocity[0] = self->avelocity[0];
173 		self->owner->avelocity[1] = self->avelocity[1];
174 
175 		// x & y
176 		angle = self->s.angles[1] + self->owner->move_origin[1];
177 		angle *= (M_PI*2 / 360);
178 		target[0] = SnapToEights(self->s.origin[0] + cos(angle) * self->owner->move_origin[0]);
179 		target[1] = SnapToEights(self->s.origin[1] + sin(angle) * self->owner->move_origin[0]);
180 		target[2] = self->owner->s.origin[2];
181 
182 		VectorSubtract (target, self->owner->s.origin, dir);
183 		self->owner->velocity[0] = dir[0] * 1.0 / FRAMETIME;
184 		self->owner->velocity[1] = dir[1] * 1.0 / FRAMETIME;
185 
186 		// z
187 		angle = self->s.angles[PITCH] * (M_PI*2 / 360);
188 		target_z = SnapToEights(self->s.origin[2] + self->owner->move_origin[0] * tan(angle) + self->owner->move_origin[2]);
189 
190 		diff = target_z - self->owner->s.origin[2];
191 		self->owner->velocity[2] = diff * 1.0 / FRAMETIME;
192 
193 		if (self->spawnflags & 65536)
194 		{
195 			turret_breach_fire (self);
196 			self->spawnflags &= ~65536;
197 		}
198 	}
199 }
200 
turret_breach_finish_init(edict_t * self)201 void turret_breach_finish_init (edict_t *self)
202 {
203 	// get and save info for muzzle location
204 	if (!self->target)
205 	{
206 		gi.dprintf("%s at %s needs a target\n", self->classname, vtos(self->s.origin));
207 	}
208 	else
209 	{
210 		self->target_ent = G_PickTarget (self->target);
211 		VectorSubtract (self->target_ent->s.origin, self->s.origin, self->move_origin);
212 		G_FreeEdict(self->target_ent);
213 	}
214 
215 	self->teammaster->dmg = self->dmg;
216 	self->think = turret_breach_think;
217 	self->think (self);
218 }
219 
SP_turret_breach(edict_t * self)220 void SP_turret_breach (edict_t *self)
221 {
222 	self->solid = SOLID_BSP;
223 	self->movetype = MOVETYPE_PUSH;
224 	gi.setmodel (self, self->model);
225 
226 	if (!self->speed)
227 		self->speed = 50;
228 	if (!self->dmg)
229 		self->dmg = 10;
230 
231 	if (!st.minpitch)
232 		st.minpitch = -30;
233 	if (!st.maxpitch)
234 		st.maxpitch = 30;
235 	if (!st.maxyaw)
236 		st.maxyaw = 360;
237 
238 	self->pos1[PITCH] = -1 * st.minpitch;
239 	self->pos1[YAW]   = st.minyaw;
240 	self->pos2[PITCH] = -1 * st.maxpitch;
241 	self->pos2[YAW]   = st.maxyaw;
242 
243 	self->ideal_yaw = self->s.angles[YAW];
244 	self->move_angles[YAW] = self->ideal_yaw;
245 
246 	self->blocked = turret_blocked;
247 
248 	self->think = turret_breach_finish_init;
249 	self->nextthink = level.time + FRAMETIME;
250 	gi.linkentity (self);
251 }
252 
253 
254 /*QUAKED turret_base (0 0 0) ?
255 This portion of the turret changes yaw only.
256 MUST be teamed with a turret_breach.
257 */
258 
SP_turret_base(edict_t * self)259 void SP_turret_base (edict_t *self)
260 {
261 	self->solid = SOLID_BSP;
262 	self->movetype = MOVETYPE_PUSH;
263 	gi.setmodel (self, self->model);
264 	self->blocked = turret_blocked;
265 	gi.linkentity (self);
266 }
267 
268 
269 /*QUAKED turret_driver (1 .5 0) (-16 -16 -24) (16 16 32)
270 Must NOT be on the team with the rest of the turret parts.
271 Instead it must target the turret_breach.
272 */
273 
274 void infantry_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage);
275 void infantry_stand (edict_t *self);
276 void monster_use (edict_t *self, edict_t *other, edict_t *activator);
277 
turret_driver_die(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)278 void turret_driver_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
279 {
280 	edict_t	*ent;
281 
282 	// level the gun
283 	self->target_ent->move_angles[0] = 0;
284 
285 	// remove the driver from the end of them team chain
286 	for (ent = self->target_ent->teammaster; ent->teamchain != self; ent = ent->teamchain)
287 		;
288 	ent->teamchain = NULL;
289 	self->teammaster = NULL;
290 	self->flags &= ~FL_TEAMSLAVE;
291 
292 	self->target_ent->owner = NULL;
293 	self->target_ent->teammaster->owner = NULL;
294 
295 	infantry_die (self, inflictor, attacker, damage);
296 }
297 
298 qboolean FindTarget (edict_t *self);
299 
turret_driver_think(edict_t * self)300 void turret_driver_think (edict_t *self)
301 {
302 	vec3_t	target;
303 	vec3_t	dir;
304 	float	reaction_time;
305 
306 	self->nextthink = level.time + FRAMETIME;
307 
308 	if (self->enemy && (!self->enemy->inuse || self->enemy->health <= 0))
309 		self->enemy = NULL;
310 
311 	if (!self->enemy)
312 	{
313 		if (!FindTarget (self))
314 			return;
315 		self->monsterinfo.trail_time = level.time;
316 		self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
317 	}
318 	else
319 	{
320 		if (visible (self, self->enemy))
321 		{
322 			if (self->monsterinfo.aiflags & AI_LOST_SIGHT)
323 			{
324 				self->monsterinfo.trail_time = level.time;
325 				self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
326 			}
327 		}
328 		else
329 		{
330 			self->monsterinfo.aiflags |= AI_LOST_SIGHT;
331 			return;
332 		}
333 	}
334 
335 	// let the turret know where we want it to aim
336 	VectorCopy (self->enemy->s.origin, target);
337 	target[2] += self->enemy->viewheight;
338 	VectorSubtract (target, self->target_ent->s.origin, dir);
339 	vectoangles (dir, self->target_ent->move_angles);
340 
341 	// decide if we should shoot
342 	if (level.time < self->monsterinfo.attack_finished)
343 		return;
344 
345 	reaction_time = (3 - skill->value) * 1.0;
346 	if ((level.time - self->monsterinfo.trail_time) < reaction_time)
347 		return;
348 
349 	self->monsterinfo.attack_finished = level.time + reaction_time + 1.0;
350 	//FIXME how do we really want to pass this along?
351 	self->target_ent->spawnflags |= 65536;
352 }
353 
turret_driver_link(edict_t * self)354 void turret_driver_link (edict_t *self)
355 {
356 	vec3_t	vec;
357 	edict_t	*ent;
358 
359 	self->think = turret_driver_think;
360 	self->nextthink = level.time + FRAMETIME;
361 
362 	self->target_ent = G_PickTarget (self->target);
363 	self->target_ent->owner = self;
364 	self->target_ent->teammaster->owner = self;
365 	VectorCopy (self->target_ent->s.angles, self->s.angles);
366 
367 	vec[0] = self->target_ent->s.origin[0] - self->s.origin[0];
368 	vec[1] = self->target_ent->s.origin[1] - self->s.origin[1];
369 	vec[2] = 0;
370 	self->move_origin[0] = VectorLength(vec);
371 
372 	VectorSubtract (self->s.origin, self->target_ent->s.origin, vec);
373 	vectoangles (vec, vec);
374 	AnglesNormalize(vec);
375 	self->move_origin[1] = vec[1];
376 
377 	self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2];
378 
379 	// add the driver to the end of them team chain
380 	for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain)
381 		;
382 	ent->teamchain = self;
383 	self->teammaster = self->target_ent->teammaster;
384 	self->flags |= FL_TEAMSLAVE;
385 }
386 
SP_turret_driver(edict_t * self)387 void SP_turret_driver (edict_t *self)
388 {
389 	if (deathmatch->value)
390 	{
391 		G_FreeEdict (self);
392 		return;
393 	}
394 
395 	self->movetype = MOVETYPE_PUSH;
396 	self->solid = SOLID_BBOX;
397 	self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2");
398 	VectorSet (self->mins, -16, -16, -24);
399 	VectorSet (self->maxs, 16, 16, 32);
400 
401 	self->health = 100;
402 	self->gib_health = 0;
403 	self->mass = 200;
404 	self->viewheight = 24;
405 
406 	self->die = turret_driver_die;
407 	self->monsterinfo.stand = infantry_stand;
408 
409 	self->flags |= FL_NO_KNOCKBACK;
410 
411 	level.total_monsters++;
412 
413 	self->svflags |= SVF_MONSTER;
414 	self->s.renderfx |= RF_FRAMELERP;
415 	self->takedamage = DAMAGE_AIM;
416 	self->use = monster_use;
417 	self->clipmask = MASK_MONSTERSOLID;
418 	VectorCopy (self->s.origin, self->s.old_origin);
419 	self->monsterinfo.aiflags |= AI_STAND_GROUND|AI_DUCKED;
420 
421 	if (st.item)
422 	{
423 		self->item = FindItemByClassname (st.item);
424 		if (!self->item)
425 			gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item);
426 	}
427 
428 	self->think = turret_driver_link;
429 	self->nextthink = level.time + FRAMETIME;
430 
431 	gi.linkentity (self);
432 }
433