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