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