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