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