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