1 /*
2 ==============================================================================
3
4 TURRET
5
6 ==============================================================================
7 */
8
9 #include "g_local.h"
10 #include "m_turret.h"
11
12 #define SPAWN_BLASTER 0x0008
13 #define SPAWN_MACHINEGUN 0x0010
14 #define SPAWN_ROCKET 0x0020
15 #define SPAWN_HEATBEAM 0x0040
16 #define SPAWN_WEAPONCHOICE 0x0078
17 #define SPAWN_INSTANT_WEAPON 0x0050
18 #define SPAWN_WALL_UNIT 0x0080
19
20 extern qboolean FindTarget (edict_t *self);
21
22 void turret_run (edict_t *self);
23 void TurretAim (edict_t *self);
24 void turret_sight (edict_t *self, edict_t *other);
25 void turret_search (edict_t *self);
26 void turret_stand (edict_t *self);
27 void turret_wake (edict_t *self);
28 void turret_ready_gun (edict_t *self);
29 void turret_run (edict_t *self);
30
31 void turret_attack (edict_t *self);
32 mmove_t turret_move_fire;
33 mmove_t turret_move_fire_blind;
34
35
TurretAim(edict_t * self)36 void TurretAim(edict_t *self)
37 {
38 vec3_t end, dir;
39 vec3_t ang;
40 float move, idealPitch, idealYaw, current, speed;
41 int orientation;
42
43 // gi.dprintf("turret_aim: %d %d\n", self->s.frame, self->monsterinfo.nextframe);
44
45 if(!self->enemy || self->enemy == world)
46 {
47 if(!FindTarget (self))
48 return;
49 }
50
51 // if turret is still in inactive mode, ready the gun, but don't aim
52 if(self->s.frame < FRAME_active01)
53 {
54 turret_ready_gun(self);
55 return;
56 }
57 // if turret is still readying, don't aim.
58 if(self->s.frame < FRAME_run01)
59 return;
60
61 // PMM - blindfire aiming here
62 if (self->monsterinfo.currentmove == &turret_move_fire_blind)
63 {
64 VectorCopy(self->monsterinfo.blind_fire_target, end);
65 if (self->enemy->s.origin[2] < self->monsterinfo.blind_fire_target[2])
66 end[2] += self->enemy->viewheight + 10;
67 else
68 end[2] += self->enemy->mins[2] - 10;
69 }
70 else
71 {
72 VectorCopy(self->enemy->s.origin, end);
73 if (self->enemy->client)
74 end[2] += self->enemy->viewheight;
75 }
76
77 VectorSubtract(end, self->s.origin, dir);
78 vectoangles2(dir, ang);
79
80 //
81 // Clamp first
82 //
83
84 idealPitch = ang[PITCH];
85 idealYaw = ang[YAW];
86
87 orientation = self->offset[1];
88 switch(orientation)
89 {
90 case -1: // up pitch: 0 to 90
91 if(idealPitch < -90)
92 idealPitch += 360;
93 if(idealPitch > -5)
94 idealPitch = -5;
95 break;
96 case -2: // down pitch: -180 to -360
97 if(idealPitch > -90)
98 idealPitch -= 360;
99 if(idealPitch < -355)
100 idealPitch = -355;
101 else if(idealPitch > -185)
102 idealPitch = -185;
103 break;
104 case 0: // +X pitch: 0 to -90, -270 to -360 (or 0 to 90)
105 //gi.dprintf("idealpitch %0.1f idealyaw %0.1f\n", idealPitch, idealYaw);
106 if(idealPitch < -180)
107 idealPitch += 360;
108
109 if(idealPitch > 85)
110 idealPitch = 85;
111 else if(idealPitch < -85)
112 idealPitch = -85;
113
114 //gi.dprintf("idealpitch %0.1f idealyaw %0.1f\n", idealPitch, idealYaw);
115 // yaw: 270 to 360, 0 to 90
116 // yaw: -90 to 90 (270-360 == -90-0)
117 if(idealYaw > 180)
118 idealYaw -= 360;
119 if(idealYaw > 85)
120 idealYaw = 85;
121 else if(idealYaw < -85)
122 idealYaw = -85;
123 //gi.dprintf("idealpitch %0.1f idealyaw %0.1f\n", idealPitch, idealYaw);
124 break;
125 case 90: // +Y pitch: 0 to 90, -270 to -360 (or 0 to 90)
126 if(idealPitch < -180)
127 idealPitch += 360;
128
129 if(idealPitch > 85)
130 idealPitch = 85;
131 else if(idealPitch < -85)
132 idealPitch = -85;
133
134 // yaw: 0 to 180
135 if(idealYaw > 270)
136 idealYaw -= 360;
137 if(idealYaw > 175) idealYaw = 175;
138 else if(idealYaw < 5) idealYaw = 5;
139
140 break;
141 case 180: // -X pitch: 0 to 90, -270 to -360 (or 0 to 90)
142 if(idealPitch < -180)
143 idealPitch += 360;
144
145 if(idealPitch > 85)
146 idealPitch = 85;
147 else if(idealPitch < -85)
148 idealPitch = -85;
149
150 // yaw: 90 to 270
151 if(idealYaw > 265) idealYaw = 265;
152 else if(idealYaw < 95) idealYaw = 95;
153
154 break;
155 case 270: // -Y pitch: 0 to 90, -270 to -360 (or 0 to 90)
156 if(idealPitch < -180)
157 idealPitch += 360;
158
159 if(idealPitch > 85)
160 idealPitch = 85;
161 else if(idealPitch < -85)
162 idealPitch = -85;
163
164 // yaw: 180 to 360
165 if(idealYaw < 90)
166 idealYaw += 360;
167 if(idealYaw > 355) idealYaw = 355;
168 else if(idealYaw < 185) idealYaw = 185;
169 break;
170 }
171
172 //
173 // adjust pitch
174 //
175 current = self->s.angles[PITCH];
176 speed = self->yaw_speed;
177
178 if(idealPitch != current)
179 {
180 move = idealPitch - current;
181
182 while(move >= 360)
183 move -= 360;
184 if (move >= 90)
185 {
186 move = move - 360;
187 }
188
189 while(move <= -360)
190 move += 360;
191 if (move <= -90)
192 {
193 move = move + 360;
194 }
195
196 if (move > 0)
197 {
198 if (move > speed)
199 move = speed;
200 }
201 else
202 {
203 if (move < -speed)
204 move = -speed;
205 }
206
207 self->s.angles[PITCH] = anglemod (current + move);
208 }
209
210 //
211 // adjust yaw
212 //
213 current = self->s.angles[YAW];
214 speed = self->yaw_speed;
215
216 if(idealYaw != current)
217 {
218 move = idealYaw - current;
219
220 // while(move >= 360)
221 // move -= 360;
222 if (move >= 180)
223 {
224 move = move - 360;
225 }
226
227 // while(move <= -360)
228 // move += 360;
229 if (move <= -180)
230 {
231 move = move + 360;
232 }
233
234 if (move > 0)
235 {
236 if (move > speed)
237 move = speed;
238 }
239 else
240 {
241 if (move < -speed)
242 move = -speed;
243 }
244
245 self->s.angles[YAW] = anglemod (current + move);
246 }
247
248 }
249
turret_sight(edict_t * self,edict_t * other)250 void turret_sight (edict_t *self, edict_t *other)
251 {
252 }
253
turret_search(edict_t * self)254 void turret_search (edict_t *self)
255 {
256 }
257
258 mframe_t turret_frames_stand [] =
259 {
260 ai_stand, 0, NULL,
261 ai_stand, 0, NULL
262 };
263 mmove_t turret_move_stand = {FRAME_stand01, FRAME_stand02, turret_frames_stand, NULL};
264
turret_stand(edict_t * self)265 void turret_stand (edict_t *self)
266 {
267 //gi.dprintf("turret_stand\n");
268 self->monsterinfo.currentmove = &turret_move_stand;
269 }
270
271 mframe_t turret_frames_ready_gun [] =
272 {
273 ai_stand, 0, NULL,
274 ai_stand, 0, NULL,
275 ai_stand, 0, NULL,
276
277 ai_stand, 0, NULL,
278 ai_stand, 0, NULL,
279 ai_stand, 0, NULL,
280
281 ai_stand, 0, NULL
282 };
283 mmove_t turret_move_ready_gun = { FRAME_active01, FRAME_run01, turret_frames_ready_gun, turret_run };
284
turret_ready_gun(edict_t * self)285 void turret_ready_gun (edict_t *self)
286 {
287 self->monsterinfo.currentmove = &turret_move_ready_gun;
288 }
289
290 mframe_t turret_frames_seek [] =
291 {
292 ai_walk, 0, TurretAim,
293 ai_walk, 0, TurretAim
294 };
295 mmove_t turret_move_seek = {FRAME_run01, FRAME_run02, turret_frames_seek, NULL};
296
turret_walk(edict_t * self)297 void turret_walk (edict_t *self)
298 {
299 if(self->s.frame < FRAME_run01)
300 turret_ready_gun(self);
301 else
302 self->monsterinfo.currentmove = &turret_move_seek;
303 }
304
305
306 mframe_t turret_frames_run [] =
307 {
308 ai_run, 0, TurretAim,
309 ai_run, 0, TurretAim
310 };
311 mmove_t turret_move_run = {FRAME_run01, FRAME_run02, turret_frames_run, turret_run};
312
turret_run(edict_t * self)313 void turret_run (edict_t *self)
314 {
315 if(self->s.frame < FRAME_run01)
316 turret_ready_gun(self);
317 else
318 self->monsterinfo.currentmove = &turret_move_run;
319 }
320
321 // **********************
322 // ATTACK
323 // **********************
324
325 #define TURRET_BULLET_DAMAGE 4
326 #define TURRET_HEAT_DAMAGE 4
327
TurretFire(edict_t * self)328 void TurretFire (edict_t *self)
329 {
330 vec3_t forward;
331 vec3_t start, end, dir;
332 float time, dist, chance;
333 trace_t trace;
334 int rocketSpeed;
335
336 TurretAim(self);
337
338 if(!self->enemy || !self->enemy->inuse)
339 return;
340
341 VectorSubtract(self->enemy->s.origin, self->s.origin, dir);
342 VectorNormalize(dir);
343 AngleVectors(self->s.angles, forward, NULL, NULL);
344 chance = DotProduct(dir, forward);
345 if(chance < 0.98)
346 {
347 // gi.dprintf("off-angle\n");
348 return;
349 }
350
351 chance = random();
352
353 // rockets fire less often than the others do.
354 if (self->spawnflags & SPAWN_ROCKET)
355 {
356 chance = chance * 3;
357
358 rocketSpeed = 550;
359 if (skill->value == 2)
360 {
361 rocketSpeed += 200 * random();
362 }
363 else if (skill->value == 3)
364 {
365 rocketSpeed += 100 + (200 * random());
366 }
367 }
368 else if (self->spawnflags & SPAWN_BLASTER)
369 {
370 if (skill->value == 0)
371 rocketSpeed = 600;
372 else if (skill->value == 1)
373 rocketSpeed = 800;
374 else
375 rocketSpeed = 1000;
376 chance = chance * 2;
377 }
378
379 // up the fire chance 20% per skill level.
380 chance = chance - (0.2 * skill->value);
381
382 if(/*chance < 0.5 && */visible(self, self->enemy))
383 {
384 VectorCopy(self->s.origin, start);
385 VectorCopy(self->enemy->s.origin, end);
386
387 // aim for the head.
388 if ((self->enemy) && (self->enemy->client))
389 end[2]+=self->enemy->viewheight;
390 else
391 end[2]+=22;
392
393 VectorSubtract(end, start, dir);
394 dist = VectorLength(dir);
395
396 // check for predictive fire if distance less than 512
397 if(!(self->spawnflags & SPAWN_INSTANT_WEAPON) && (dist<512))
398 {
399 chance = random();
400 // ramp chance. easy - 50%, avg - 60%, hard - 70%, nightmare - 80%
401 chance += (3 - skill->value) * 0.1;
402 if(chance < 0.8)
403 {
404 // lead the target....
405 time = dist / 1000;
406 VectorMA(end, time, self->enemy->velocity, end);
407 VectorSubtract(end, start, dir);
408 }
409 }
410
411 VectorNormalize(dir);
412 trace = gi.trace(start, vec3_origin, vec3_origin, end, self, MASK_SHOT);
413 if(trace.ent == self->enemy || trace.ent == world)
414 {
415 if(self->spawnflags & SPAWN_BLASTER)
416 monster_fire_blaster(self, start, dir, 20, rocketSpeed, MZ2_TURRET_BLASTER, EF_BLASTER);
417 else if(self->spawnflags & SPAWN_MACHINEGUN)
418 monster_fire_bullet (self, start, dir, TURRET_BULLET_DAMAGE, 0, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_TURRET_MACHINEGUN);
419 else if(self->spawnflags & SPAWN_ROCKET)
420 {
421 if(dist * trace.fraction > 72)
422 monster_fire_rocket (self, start, dir, 50, rocketSpeed, MZ2_TURRET_ROCKET);
423 }
424 }
425 }
426 }
427
428 // PMM
TurretFireBlind(edict_t * self)429 void TurretFireBlind (edict_t *self)
430 {
431 vec3_t forward;
432 vec3_t start, end, dir;
433 float dist, chance;
434 int rocketSpeed;
435
436 TurretAim(self);
437
438 if(!self->enemy || !self->enemy->inuse)
439 return;
440
441 VectorSubtract(self->monsterinfo.blind_fire_target, self->s.origin, dir);
442 VectorNormalize(dir);
443 AngleVectors(self->s.angles, forward, NULL, NULL);
444 chance = DotProduct(dir, forward);
445 if(chance < 0.98)
446 {
447 // gi.dprintf("off-angle\n");
448 return;
449 }
450
451 if (self->spawnflags & SPAWN_ROCKET)
452 {
453 rocketSpeed = 550;
454 if (skill->value == 2)
455 {
456 rocketSpeed += 200 * random();
457 }
458 else if (skill->value == 3)
459 {
460 rocketSpeed += 100 + (200 * random());
461 }
462 }
463
464 VectorCopy(self->s.origin, start);
465 VectorCopy(self->monsterinfo.blind_fire_target, end);
466
467 if (self->enemy->s.origin[2] < self->monsterinfo.blind_fire_target[2])
468 end[2] += self->enemy->viewheight + 10;
469 else
470 end[2] += self->enemy->mins[2] - 10;
471
472 VectorSubtract(end, start, dir);
473 dist = VectorLength(dir);
474
475 VectorNormalize(dir);
476
477 if(self->spawnflags & SPAWN_BLASTER)
478 monster_fire_blaster(self, start, dir, 20, 1000, MZ2_TURRET_BLASTER, EF_BLASTER);
479 else if(self->spawnflags & SPAWN_ROCKET)
480 monster_fire_rocket (self, start, dir, 50, rocketSpeed, MZ2_TURRET_ROCKET);
481 }
482 //pmm
483
484 mframe_t turret_frames_fire [] =
485 {
486 ai_run, 0, TurretFire,
487 ai_run, 0, TurretAim,
488 ai_run, 0, TurretAim,
489 ai_run, 0, TurretAim
490 };
491 mmove_t turret_move_fire = {FRAME_pow01, FRAME_pow04, turret_frames_fire, turret_run};
492
493 //PMM
494
495 // the blind frames need to aim first
496 mframe_t turret_frames_fire_blind [] =
497 {
498 ai_run, 0, TurretAim,
499 ai_run, 0, TurretAim,
500 ai_run, 0, TurretAim,
501 ai_run, 0, TurretFireBlind
502 };
503 mmove_t turret_move_fire_blind = {FRAME_pow01, FRAME_pow04, turret_frames_fire_blind, turret_run};
504 //pmm
505
turret_attack(edict_t * self)506 void turret_attack(edict_t *self)
507 {
508 float r, chance;
509
510 if(self->s.frame < FRAME_run01)
511 turret_ready_gun(self);
512 // PMM
513 else if (self->monsterinfo.attack_state != AS_BLIND)
514 {
515 self->monsterinfo.nextframe = FRAME_pow01;
516 self->monsterinfo.currentmove = &turret_move_fire;
517 }
518 else
519 {
520 // setup shot probabilities
521 if (self->monsterinfo.blind_fire_delay < 1.0)
522 chance = 1.0;
523 else if (self->monsterinfo.blind_fire_delay < 7.5)
524 chance = 0.4;
525 else
526 chance = 0.1;
527
528 r = random();
529
530 // minimum of 3 seconds, plus 0-4, after the shots are done - total time should be max less than 7.5
531 self->monsterinfo.blind_fire_delay += 0.4 + 3.0 + random()*4.0;
532 // don't shoot at the origin
533 if (VectorCompare (self->monsterinfo.blind_fire_target, vec3_origin))
534 return;
535
536 // don't shoot if the dice say not to
537 if (r > chance)
538 return;
539
540 self->monsterinfo.nextframe = FRAME_pow01;
541 self->monsterinfo.currentmove = &turret_move_fire_blind;
542 }
543 // pmm
544 }
545
546 // **********************
547 // PAIN
548 // **********************
549
turret_pain(edict_t * self,edict_t * other,float kick,int damage)550 void turret_pain (edict_t *self, edict_t *other, float kick, int damage)
551 {
552 return;
553 }
554
555 // **********************
556 // DEATH
557 // **********************
558
turret_die(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)559 void turret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
560 {
561 vec3_t forward;
562 vec3_t start;
563 edict_t *base;
564
565 gi.WriteByte (svc_temp_entity);
566 gi.WriteByte (TE_PLAIN_EXPLOSION);
567 gi.WritePosition (self->s.origin);
568 gi.multicast (self->s.origin, MULTICAST_PHS);
569
570 AngleVectors(self->s.angles, forward, NULL, NULL);
571 VectorMA(self->s.origin, 1, forward, start);
572
573 ThrowDebris (self, "models/objects/debris1/tris.md2", 1, start);
574 ThrowDebris (self, "models/objects/debris1/tris.md2", 2, start);
575 ThrowDebris (self, "models/objects/debris1/tris.md2", 1, start);
576 ThrowDebris (self, "models/objects/debris1/tris.md2", 2, start);
577
578 if(self->teamchain)
579 {
580 base = self->teamchain;
581 base->solid = SOLID_BBOX;
582 base->takedamage = DAMAGE_NO;
583 base->movetype = MOVETYPE_NONE;
584 gi.linkentity (base);
585 }
586
587 if(self->target)
588 {
589 if(self->enemy && self->enemy->inuse)
590 G_UseTargets (self, self->enemy);
591 else
592 G_UseTargets (self, self);
593 }
594
595 G_FreeEdict(self);
596 }
597
598 // **********************
599 // WALL SPAWN
600 // **********************
601
turret_wall_spawn(edict_t * turret)602 void turret_wall_spawn (edict_t *turret)
603 {
604 edict_t *ent;
605 int angle;
606
607 ent = G_Spawn();
608 VectorCopy(turret->s.origin, ent->s.origin);
609 VectorCopy(turret->s.angles, ent->s.angles);
610
611 angle = ent->s.angles[1];
612 if(ent->s.angles[0] == 90)
613 angle = -1;
614 else if(ent->s.angles[0] == 270)
615 angle = -2;
616 switch (angle)
617 {
618 case -1:
619 VectorSet(ent->mins, -16, -16, -8);
620 VectorSet(ent->maxs, 16, 16, 0);
621 break;
622 case -2:
623 VectorSet(ent->mins, -16, -16, 0);
624 VectorSet(ent->maxs, 16, 16, 8);
625 break;
626 case 0:
627 VectorSet(ent->mins, -8, -16, -16);
628 VectorSet(ent->maxs, 0, 16, 16);
629 break;
630 case 90:
631 VectorSet(ent->mins, -16, -8, -16);
632 VectorSet(ent->maxs, 16, 0, 16);
633 break;
634 case 180:
635 VectorSet(ent->mins, 0, -16, -16);
636 VectorSet(ent->maxs, 8, 16, 16);
637 break;
638 case 270:
639 VectorSet(ent->mins, -16, 0, -16);
640 VectorSet(ent->maxs, 16, 8, 16);
641 break;
642
643 }
644
645 ent->movetype = MOVETYPE_PUSH;
646 ent->solid = SOLID_NOT;
647
648 ent->teammaster = turret;
649 turret->teammaster = turret;
650 turret->teamchain = ent;
651 ent->teamchain = NULL;
652 ent->flags |= FL_TEAMSLAVE;
653 ent->owner = turret;
654
655 ent->s.modelindex = gi.modelindex("models/monsters/turretbase/tris.md2");
656
657 gi.linkentity (ent);
658 }
659
turret_wake(edict_t * self)660 void turret_wake (edict_t *self)
661 {
662 // the wall section will call this when it stops moving.
663 // just return without doing anything. easiest way to have a null function.
664 if(self->flags & FL_TEAMSLAVE)
665 {
666 return;
667 }
668
669 self->monsterinfo.stand = turret_stand;
670 self->monsterinfo.walk = turret_walk;
671 self->monsterinfo.run = turret_run;
672 self->monsterinfo.dodge = NULL;
673 self->monsterinfo.attack = turret_attack;
674 self->monsterinfo.melee = NULL;
675 self->monsterinfo.sight = turret_sight;
676 self->monsterinfo.search = turret_search;
677 self->monsterinfo.currentmove = &turret_move_stand;
678 self->takedamage = DAMAGE_AIM;
679 self->movetype = MOVETYPE_NONE;
680 // prevent counting twice
681 self->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
682
683 gi.linkentity (self);
684
685 stationarymonster_start (self);
686
687 if(self->spawnflags & SPAWN_MACHINEGUN)
688 {
689 self->s.skinnum = 1;
690 }
691 else if(self->spawnflags & SPAWN_ROCKET)
692 {
693 self->s.skinnum = 2;
694 }
695
696 // but we do want the death to count
697 self->monsterinfo.aiflags &= ~AI_DO_NOT_COUNT;
698 }
699
700 extern void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*));
701
turret_activate(edict_t * self,edict_t * other,edict_t * activator)702 void turret_activate (edict_t *self, edict_t *other, edict_t *activator)
703 {
704 vec3_t endpos;
705 vec3_t forward;
706 edict_t *base;
707
708 self->movetype = MOVETYPE_PUSH;
709 if(!self->speed)
710 self->speed = 15;
711 self->moveinfo.speed = self->speed;
712 self->moveinfo.accel = self->speed;
713 self->moveinfo.decel = self->speed;
714
715 if(self->s.angles[0] == 270)
716 {
717 VectorSet (forward, 0,0,1);
718 }
719 else if(self->s.angles[0] == 90)
720 {
721 VectorSet (forward, 0,0,-1);
722 }
723 else if(self->s.angles[1] == 0)
724 {
725 VectorSet (forward, 1,0,0);
726 }
727 else if(self->s.angles[1] == 90)
728 {
729 VectorSet (forward, 0,1,0);
730 }
731 else if(self->s.angles[1] == 180)
732 {
733 VectorSet (forward, -1,0,0);
734 }
735 else if(self->s.angles[1] == 270)
736 {
737 VectorSet (forward, 0,-1,0);
738 }
739
740 // start up the turret
741 VectorMA(self->s.origin, 32, forward, endpos);
742 Move_Calc(self, endpos, turret_wake);
743
744 base = self->teamchain;
745 if(base)
746 {
747 base->movetype = MOVETYPE_PUSH;
748 base->speed = self->speed;
749 base->moveinfo.speed = base->speed;
750 base->moveinfo.accel = base->speed;
751 base->moveinfo.decel = base->speed;
752
753 // start up the wall section
754 VectorMA(self->teamchain->s.origin, 32, forward, endpos);
755 Move_Calc(self->teamchain, endpos, turret_wake);
756 }
757
758 gi.sound (self, CHAN_VOICE, gi.soundindex ("world/dr_short.wav"), 1, ATTN_NORM, 0);
759 }
760
761 // PMM
762 // checkattack .. ignore range, just attack if available
turret_checkattack(edict_t * self)763 qboolean turret_checkattack (edict_t *self)
764 {
765 vec3_t spot1, spot2;
766 float chance, nexttime;
767 trace_t tr;
768 int enemy_range;
769
770 if (self->enemy->health > 0)
771 {
772 // see if any entities are in the way of the shot
773 VectorCopy (self->s.origin, spot1);
774 spot1[2] += self->viewheight;
775 VectorCopy (self->enemy->s.origin, spot2);
776 spot2[2] += self->enemy->viewheight;
777
778 tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW);
779
780 // do we have a clear shot?
781 if (tr.ent != self->enemy)
782 {
783 // PGM - we want them to go ahead and shoot at info_notnulls if they can.
784 if(self->enemy->solid != SOLID_NOT || tr.fraction < 1.0) //PGM
785 {
786 // PMM - if we can't see our target, and we're not blocked by a monster, go into blind fire if available
787 if ((!(tr.ent->svflags & SVF_MONSTER)) && (!visible(self, self->enemy)))
788 {
789 if ((self->monsterinfo.blindfire) && (self->monsterinfo.blind_fire_delay <= 10.0))
790 {
791 if (level.time < self->monsterinfo.attack_finished)
792 {
793 return false;
794 }
795 if (level.time < (self->monsterinfo.trail_time + self->monsterinfo.blind_fire_delay))
796 {
797 // wait for our time
798 return false;
799 }
800 else
801 {
802 // make sure we're not going to shoot something we don't want to shoot
803 tr = gi.trace (spot1, NULL, NULL, self->monsterinfo.blind_fire_target, self, CONTENTS_MONSTER);
804 if (tr.allsolid || tr.startsolid || ((tr.fraction < 1.0) && (tr.ent != self->enemy)))
805 {
806 return false;
807 }
808
809 self->monsterinfo.attack_state = AS_BLIND;
810 self->monsterinfo.attack_finished = level.time + 0.5 + 2*random();
811 return true;
812 }
813 }
814 }
815 // pmm
816 return false;
817 }
818 }
819 }
820
821 if (level.time < self->monsterinfo.attack_finished)
822 return false;
823
824 enemy_range = range(self, self->enemy);
825
826 if (enemy_range == RANGE_MELEE)
827 {
828 // don't always melee in easy mode
829 if (skill->value == 0 && (rand()&3) )
830 return false;
831 self->monsterinfo.attack_state = AS_MISSILE;
832 return true;
833 }
834
835 if (self->spawnflags & SPAWN_ROCKET)
836 {
837 chance = 0.10;
838 nexttime = (1.8 - (0.2 * skill->value));
839 }
840 else if(self->spawnflags & SPAWN_BLASTER)
841 {
842 chance = 0.35;
843 nexttime = (1.2 - (0.2 * skill->value));
844 }
845 else
846 {
847 chance = 0.50;
848 nexttime = (0.8 - (0.1 * skill->value));
849 }
850
851 if (skill->value == 0)
852 chance *= 0.5;
853 else if (skill->value > 1)
854 chance *= 2;
855
856 // PGM - go ahead and shoot every time if it's a info_notnull
857 // PMM - added visibility check
858 if ( ((random () < chance) && (visible(self, self->enemy))) || (self->enemy->solid == SOLID_NOT))
859 {
860 self->monsterinfo.attack_state = AS_MISSILE;
861 // self->monsterinfo.attack_finished = level.time + 0.3 + 2*random();
862 self->monsterinfo.attack_finished = level.time + nexttime;
863 return true;
864 }
865
866 self->monsterinfo.attack_state = AS_STRAIGHT;
867
868 return false;
869 }
870
871
872 // **********************
873 // SPAWN
874 // **********************
875
876 /*QUAKED monster_turret (1 .5 0) (-16 -16 -16) (16 16 16) Ambush Trigger_Spawn Sight Blaster MachineGun Rocket Heatbeam WallUnit
877
878 The automated defense turret that mounts on walls.
879 Check the weapon you want it to use: blaster, machinegun, rocket, heatbeam.
880 Default weapon is blaster.
881 When activated, wall units move 32 units in the direction they're facing.
882 */
SP_monster_turret(edict_t * self)883 void SP_monster_turret (edict_t *self)
884 {
885 int angle;
886
887 if (deathmatch->value)
888 {
889 G_FreeEdict (self);
890 return;
891 }
892
893 // VERSIONING
894 // if (g_showlogic && g_showlogic->value)
895 // gi.dprintf ("%s\n", ROGUE_VERSION_STRING);
896
897 // self->plat2flags = ROGUE_VERSION_ID;
898 // versions
899
900 // pre-caches
901 gi.soundindex ("world/dr_short.wav");
902 gi.modelindex ("models/objects/debris1/tris.md2");
903
904 self->s.modelindex = gi.modelindex("models/monsters/turret/tris.md2");
905
906 VectorSet (self->mins, -12, -12, -12);
907 VectorSet (self->maxs, 12, 12, 12);
908 self->movetype = MOVETYPE_NONE;
909 self->solid = SOLID_BBOX;
910
911 self->health = 240;
912 self->gib_health = -100;
913 self->mass = 250;
914 self->yaw_speed = 45;
915
916 self->flags |= FL_MECHANICAL;
917
918 self->pain = turret_pain;
919 self->die = turret_die;
920
921 // map designer didn't specify weapon type. set it now.
922 if(!(self->spawnflags & SPAWN_WEAPONCHOICE))
923 {
924 self->spawnflags |= SPAWN_BLASTER;
925 // self->spawnflags |= SPAWN_MACHINEGUN;
926 // self->spawnflags |= SPAWN_ROCKET;
927 // self->spawnflags |= SPAWN_HEATBEAM;
928 }
929
930 if(self->spawnflags & SPAWN_HEATBEAM)
931 {
932 self->spawnflags &= ~SPAWN_HEATBEAM;
933 self->spawnflags |= SPAWN_BLASTER;
934 }
935
936 if(!(self->spawnflags & SPAWN_WALL_UNIT))
937 {
938 self->monsterinfo.stand = turret_stand;
939 self->monsterinfo.walk = turret_walk;
940 self->monsterinfo.run = turret_run;
941 self->monsterinfo.dodge = NULL;
942 self->monsterinfo.attack = turret_attack;
943 self->monsterinfo.melee = NULL;
944 self->monsterinfo.sight = turret_sight;
945 self->monsterinfo.search = turret_search;
946 self->monsterinfo.currentmove = &turret_move_stand;
947 }
948
949 // PMM
950 self->monsterinfo.checkattack = turret_checkattack;
951
952 self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
953 self->monsterinfo.scale = MODEL_SCALE;
954 self->gravity = 0;
955
956 VectorCopy(self->s.angles, self->offset);
957 angle=(int)self->s.angles[1];
958 switch(angle)
959 {
960 case -1: // up
961 self->s.angles[0] = 270;
962 self->s.angles[1] = 0;
963 self->s.origin[2] += 2;
964 break;
965 case -2: // down
966 self->s.angles[0] = 90;
967 self->s.angles[1] = 0;
968 self->s.origin[2] -= 2;
969 break;
970 case 0:
971 self->s.origin[0] += 2;
972 break;
973 case 90:
974 self->s.origin[1] += 2;
975 break;
976 case 180:
977 self->s.origin[0] -= 2;
978 break;
979 case 270:
980 self->s.origin[1] -= 2;
981 break;
982 default:
983 break;
984 }
985
986 gi.linkentity (self);
987
988
989 if(self->spawnflags & SPAWN_WALL_UNIT)
990 {
991 if(!self->targetname)
992 {
993 // gi.dprintf("Wall Unit Turret without targetname! %s\n", vtos(self->s.origin));
994 G_FreeEdict(self);
995 return;
996 }
997
998 self->takedamage = DAMAGE_NO;
999 self->use = turret_activate;
1000 turret_wall_spawn(self);
1001 if ((!(self->monsterinfo.aiflags & AI_GOOD_GUY)) && (!(self->monsterinfo.aiflags & AI_DO_NOT_COUNT)))
1002 level.total_monsters++;
1003
1004 }
1005 else
1006 {
1007 stationarymonster_start (self);
1008 }
1009
1010 if(self->spawnflags & SPAWN_MACHINEGUN)
1011 {
1012 gi.soundindex ("infantry/infatck1.wav");
1013 self->s.skinnum = 1;
1014 }
1015 else if(self->spawnflags & SPAWN_ROCKET)
1016 {
1017 gi.soundindex ("weapons/rockfly.wav");
1018 gi.modelindex ("models/objects/rocket/tris.md2");
1019 gi.soundindex ("chick/chkatck2.wav");
1020 self->s.skinnum = 2;
1021 }
1022 else
1023 {
1024 if (!(self->spawnflags & SPAWN_BLASTER))
1025 {
1026 self->spawnflags |= SPAWN_BLASTER;
1027 }
1028 gi.modelindex ("models/objects/laser/tris.md2");
1029 gi.soundindex ("misc/lasfly.wav");
1030 gi.soundindex ("soldier/solatck2.wav");
1031 }
1032
1033 // PMM - turrets don't get mad at monsters, and visa versa
1034 self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
1035 // PMM - blindfire
1036 if(self->spawnflags & (SPAWN_ROCKET|SPAWN_BLASTER))
1037 self->monsterinfo.blindfire = true;
1038 }
1039