|
|
1.1 root 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:
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:
250: void turret_sight (edict_t *self, edict_t *other)
251: {
252: }
253:
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:
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:
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:
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:
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:
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: chance = chance * 2;
371: }
372:
373: // up the fire chance 20% per skill level.
374: chance = chance - (0.2 * skill->value);
375:
376: if(/*chance < 0.5 && */visible(self, self->enemy))
377: {
378: VectorCopy(self->s.origin, start);
379: VectorCopy(self->enemy->s.origin, end);
380:
381: // aim for the head.
382: if ((self->enemy) && (self->enemy->client))
383: end[2]+=self->enemy->viewheight;
384: else
385: end[2]+=22;
386:
387: VectorSubtract(end, start, dir);
388: dist = VectorLength(dir);
389:
390: // check for predictive fire if distance less than 512
391: if(!(self->spawnflags & SPAWN_INSTANT_WEAPON) && (dist<512))
392: {
393: chance = random();
394: // ramp chance. easy - 50%, avg - 60%, hard - 70%, nightmare - 80%
395: chance += (3 - skill->value) * 0.1;
396: if(chance < 0.8)
397: {
398: // lead the target....
399: time = dist / 1000;
400: VectorMA(end, time, self->enemy->velocity, end);
401: VectorSubtract(end, start, dir);
402: }
403: }
404:
405: VectorNormalize(dir);
406: trace = gi.trace(start, vec3_origin, vec3_origin, end, self, MASK_SHOT);
407: if(trace.ent == self->enemy || trace.ent == world)
408: {
409: if(self->spawnflags & SPAWN_BLASTER)
410: monster_fire_blaster(self, start, dir, 20, 1000, MZ2_TURRET_BLASTER, EF_BLASTER);
411: else if(self->spawnflags & SPAWN_MACHINEGUN)
412: monster_fire_bullet (self, start, dir, TURRET_BULLET_DAMAGE, 0, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_TURRET_MACHINEGUN);
413: else if(self->spawnflags & SPAWN_ROCKET)
414: {
415: if(dist * trace.fraction > 72)
416: monster_fire_rocket (self, start, dir, 50, rocketSpeed, MZ2_TURRET_ROCKET);
417: }
418: }
419: }
420: }
421:
422: // PMM
423: void TurretFireBlind (edict_t *self)
424: {
425: vec3_t forward;
426: vec3_t start, end, dir;
427: float dist, chance;
428: int rocketSpeed;
429:
430: TurretAim(self);
431:
432: if(!self->enemy || !self->enemy->inuse)
433: return;
434:
435: VectorSubtract(self->monsterinfo.blind_fire_target, self->s.origin, dir);
436: VectorNormalize(dir);
437: AngleVectors(self->s.angles, forward, NULL, NULL);
438: chance = DotProduct(dir, forward);
439: if(chance < 0.98)
440: {
441: // gi.dprintf("off-angle\n");
442: return;
443: }
444:
445: if (self->spawnflags & SPAWN_ROCKET)
446: {
447: rocketSpeed = 550;
448: if (skill->value == 2)
449: {
450: rocketSpeed += 200 * random();
451: }
452: else if (skill->value == 3)
453: {
454: rocketSpeed += 100 + (200 * random());
455: }
456: }
457:
458: VectorCopy(self->s.origin, start);
459: VectorCopy(self->monsterinfo.blind_fire_target, end);
460:
461: if (self->enemy->s.origin[2] < self->monsterinfo.blind_fire_target[2])
462: end[2] += self->enemy->viewheight + 10;
463: else
464: end[2] += self->enemy->mins[2] - 10;
465:
466: VectorSubtract(end, start, dir);
467: dist = VectorLength(dir);
468:
469: VectorNormalize(dir);
470:
471: if(self->spawnflags & SPAWN_BLASTER)
472: monster_fire_blaster(self, start, dir, 20, 1000, MZ2_TURRET_BLASTER, EF_BLASTER);
473: else if(self->spawnflags & SPAWN_ROCKET)
474: monster_fire_rocket (self, start, dir, 50, rocketSpeed, MZ2_TURRET_ROCKET);
475: }
476: //pmm
477:
478: mframe_t turret_frames_fire [] =
479: {
480: ai_run, 0, TurretFire,
481: ai_run, 0, TurretAim,
482: ai_run, 0, TurretAim,
483: ai_run, 0, TurretAim
484: };
485: mmove_t turret_move_fire = {FRAME_pow01, FRAME_pow04, turret_frames_fire, turret_run};
486:
487: //PMM
488:
489: // the blind frames need to aim first
490: mframe_t turret_frames_fire_blind [] =
491: {
492: ai_run, 0, TurretAim,
493: ai_run, 0, TurretAim,
494: ai_run, 0, TurretAim,
495: ai_run, 0, TurretFireBlind
496: };
497: mmove_t turret_move_fire_blind = {FRAME_pow01, FRAME_pow04, turret_frames_fire_blind, turret_run};
498: //pmm
499:
500: void turret_attack(edict_t *self)
501: {
502: float r, chance;
503:
504: if(self->s.frame < FRAME_run01)
505: turret_ready_gun(self);
506: // PMM
507: else if (self->monsterinfo.attack_state != AS_BLIND)
508: {
509: self->monsterinfo.nextframe = FRAME_pow01;
510: self->monsterinfo.currentmove = &turret_move_fire;
511: }
512: else
513: {
514: // setup shot probabilities
515: if (self->monsterinfo.blind_fire_delay < 1.0)
516: chance = 1.0;
517: else if (self->monsterinfo.blind_fire_delay < 7.5)
518: chance = 0.4;
519: else
520: chance = 0.1;
521:
522: r = random();
523:
524: if ((g_showlogic) && (g_showlogic->value))
525: gi.dprintf ("chance = %2.2f, roll = %2.2f\n", chance, r);
526:
527: // minimum of 3 seconds, plus 0-4, after the shots are done - total time should be max less than 7.5
528: self->monsterinfo.blind_fire_delay += 0.4 + 3.0 + random()*4.0;
529: // don't shoot at the origin
530: if (VectorCompare (self->monsterinfo.blind_fire_target, vec3_origin))
531: return;
532:
533: // don't shoot if the dice say not to
534: if (r > chance)
535: {
536: if ((g_showlogic) && (g_showlogic->value))
537: gi.dprintf ("blindfire - NO SHOT\n");
538: return;
539: }
540:
541: self->monsterinfo.nextframe = FRAME_pow01;
542: self->monsterinfo.currentmove = &turret_move_fire_blind;
543: }
544: // pmm
545: }
546:
547: // **********************
548: // PAIN
549: // **********************
550:
551: void turret_pain (edict_t *self, edict_t *other, float kick, int damage)
552: {
553: return;
554: }
555:
556: // **********************
557: // DEATH
558: // **********************
559:
560: void turret_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
561: {
562: vec3_t forward;
563: vec3_t start;
564: edict_t *base;
565:
566: gi.WriteByte (svc_temp_entity);
567: gi.WriteByte (TE_PLAIN_EXPLOSION);
568: gi.WritePosition (self->s.origin);
569: gi.multicast (self->s.origin, MULTICAST_PHS);
570:
571: AngleVectors(self->s.angles, forward, NULL, NULL);
572: VectorMA(self->s.origin, 1, forward, start);
573:
574: ThrowDebris (self, "models/objects/debris1/tris.md2", 1, start);
575: ThrowDebris (self, "models/objects/debris1/tris.md2", 2, start);
576: ThrowDebris (self, "models/objects/debris1/tris.md2", 1, start);
577: ThrowDebris (self, "models/objects/debris1/tris.md2", 2, start);
578:
579: if(self->teamchain)
580: {
581: base = self->teamchain;
582: base->solid = SOLID_BBOX;
583: base->takedamage = DAMAGE_NO;
584: base->movetype = MOVETYPE_NONE;
585: gi.linkentity (base);
586: }
587:
588: if(self->target)
589: {
590: if(self->enemy && self->enemy->inuse)
591: G_UseTargets (self, self->enemy);
592: else
593: G_UseTargets (self, self);
594: }
595:
596: G_FreeEdict(self);
597: }
598:
599: // **********************
600: // WALL SPAWN
601: // **********************
602:
603: void turret_wall_spawn (edict_t *turret)
604: {
605: edict_t *ent;
606: int angle;
607:
608: ent = G_Spawn();
609: VectorCopy(turret->s.origin, ent->s.origin);
610: VectorCopy(turret->s.angles, ent->s.angles);
611:
612: angle = ent->s.angles[1];
613: if(ent->s.angles[0] == 90)
614: angle = -1;
615: else if(ent->s.angles[0] == 270)
616: angle = -2;
617: switch (angle)
618: {
619: case -1:
620: VectorSet(ent->mins, -16, -16, -8);
621: VectorSet(ent->maxs, 16, 16, 0);
622: break;
623: case -2:
624: VectorSet(ent->mins, -16, -16, 0);
625: VectorSet(ent->maxs, 16, 16, 8);
626: break;
627: case 0:
628: VectorSet(ent->mins, -8, -16, -16);
629: VectorSet(ent->maxs, 0, 16, 16);
630: break;
631: case 90:
632: VectorSet(ent->mins, -16, -8, -16);
633: VectorSet(ent->maxs, 16, 0, 16);
634: break;
635: case 180:
636: VectorSet(ent->mins, 0, -16, -16);
637: VectorSet(ent->maxs, 8, 16, 16);
638: break;
639: case 270:
640: VectorSet(ent->mins, -16, 0, -16);
641: VectorSet(ent->maxs, 16, 8, 16);
642: break;
643:
644: }
645:
646: ent->movetype = MOVETYPE_PUSH;
647: ent->solid = SOLID_NOT;
648:
649: ent->teammaster = turret;
650: turret->teammaster = turret;
651: turret->teamchain = ent;
652: ent->teamchain = NULL;
653: ent->flags |= FL_TEAMSLAVE;
654: ent->owner = turret;
655:
656: ent->s.modelindex = gi.modelindex("models/monsters/turretbase/tris.md2");
657:
658: gi.linkentity (ent);
659: }
660:
661: void turret_wake (edict_t *self)
662: {
663: // the wall section will call this when it stops moving.
664: // just return without doing anything. easiest way to have a null function.
665: if(self->flags & FL_TEAMSLAVE)
666: {
667: return;
668: }
669:
670: self->monsterinfo.stand = turret_stand;
671: self->monsterinfo.walk = turret_walk;
672: self->monsterinfo.run = turret_run;
673: self->monsterinfo.dodge = NULL;
674: self->monsterinfo.attack = turret_attack;
675: self->monsterinfo.melee = NULL;
676: self->monsterinfo.sight = turret_sight;
677: self->monsterinfo.search = turret_search;
678: self->monsterinfo.currentmove = &turret_move_stand;
679: self->takedamage = DAMAGE_AIM;
680: self->movetype = MOVETYPE_NONE;
681: // prevent counting twice
682: self->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
683:
684: gi.linkentity (self);
685:
686: stationarymonster_start (self);
687:
688: if(self->spawnflags & SPAWN_MACHINEGUN)
689: {
690: self->s.skinnum = 1;
691: }
692: else if(self->spawnflags & SPAWN_ROCKET)
693: {
694: self->s.skinnum = 2;
695: }
696:
697: // but we do want the death to count
698: self->monsterinfo.aiflags &= ~AI_DO_NOT_COUNT;
699: }
700:
701: extern void Move_Calc (edict_t *ent, vec3_t dest, void(*func)(edict_t*));
702:
703: void turret_activate (edict_t *self, edict_t *other, edict_t *activator)
704: {
705: vec3_t endpos;
706: vec3_t forward;
707: edict_t *base;
708:
709: self->movetype = MOVETYPE_PUSH;
710: if(!self->speed)
711: self->speed = 15;
712: self->moveinfo.speed = self->speed;
713: self->moveinfo.accel = self->speed;
714: self->moveinfo.decel = self->speed;
715:
716: if(self->s.angles[0] == 270)
717: {
718: VectorSet (forward, 0,0,1);
719: }
720: else if(self->s.angles[0] == 90)
721: {
722: VectorSet (forward, 0,0,-1);
723: }
724: else if(self->s.angles[1] == 0)
725: {
726: VectorSet (forward, 1,0,0);
727: }
728: else if(self->s.angles[1] == 90)
729: {
730: VectorSet (forward, 0,1,0);
731: }
732: else if(self->s.angles[1] == 180)
733: {
734: VectorSet (forward, -1,0,0);
735: }
736: else if(self->s.angles[1] == 270)
737: {
738: VectorSet (forward, 0,-1,0);
739: }
740:
741: // start up the turret
742: VectorMA(self->s.origin, 32, forward, endpos);
743: Move_Calc(self, endpos, turret_wake);
744:
745: base = self->teamchain;
746: if(base)
747: {
748: base->movetype = MOVETYPE_PUSH;
749: base->speed = self->speed;
750: base->moveinfo.speed = base->speed;
751: base->moveinfo.accel = base->speed;
752: base->moveinfo.decel = base->speed;
753:
754: // start up the wall section
755: VectorMA(self->teamchain->s.origin, 32, forward, endpos);
756: Move_Calc(self->teamchain, endpos, turret_wake);
757: }
758:
759: gi.sound (self, CHAN_VOICE, gi.soundindex ("world/dr_short.wav"), 1, ATTN_NORM, 0);
760: }
761:
762: // PMM
763: // checkattack .. ignore range, just attack if available
764: qboolean turret_checkattack (edict_t *self)
765: {
766: vec3_t spot1, spot2;
767: float chance, nexttime;
768: trace_t tr;
769: int enemy_range;
770:
771: if (self->enemy->health > 0)
772: {
773: // see if any entities are in the way of the shot
774: VectorCopy (self->s.origin, spot1);
775: spot1[2] += self->viewheight;
776: VectorCopy (self->enemy->s.origin, spot2);
777: spot2[2] += self->enemy->viewheight;
778:
779: tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW);
780:
781: // do we have a clear shot?
782: if (tr.ent != self->enemy)
783: {
784: // PGM - we want them to go ahead and shoot at info_notnulls if they can.
785: if(self->enemy->solid != SOLID_NOT || tr.fraction < 1.0) //PGM
786: {
787: // PMM - if we can't see our target, and we're not blocked by a monster, go into blind fire if available
788: if ((!(tr.ent->svflags & SVF_MONSTER)) && (!visible(self, self->enemy)))
789: {
790: if ((self->monsterinfo.blindfire) && (self->monsterinfo.blind_fire_delay <= 10.0))
791: {
792: if (level.time < self->monsterinfo.attack_finished)
793: {
794: return false;
795: }
796: if (level.time < (self->monsterinfo.trail_time + self->monsterinfo.blind_fire_delay))
797: {
798: // wait for our time
799: return false;
800: }
801: else
802: {
803: // make sure we're not going to shoot something we don't want to shoot
804: tr = gi.trace (spot1, NULL, NULL, self->monsterinfo.blind_fire_target, self, CONTENTS_MONSTER);
805: if (tr.allsolid || tr.startsolid || ((tr.fraction < 1.0) && (tr.ent != self->enemy)))
806: {
807: if ((g_showlogic) && (g_showlogic->value))
808: gi.dprintf ("blindfire blocked\n");
809: return false;
810: }
811:
812: self->monsterinfo.attack_state = AS_BLIND;
813: self->monsterinfo.attack_finished = level.time + 0.5 + 2*random();
814: return true;
815: }
816: }
817: }
818: // pmm
819: return false;
820: }
821: }
822: }
823:
824: if (level.time < self->monsterinfo.attack_finished)
825: return false;
826:
827: enemy_range = range(self, self->enemy);
828:
829: if (enemy_range == RANGE_MELEE)
830: {
831: // don't always melee in easy mode
832: if (skill->value == 0 && (rand()&3) )
833: return false;
834: self->monsterinfo.attack_state = AS_MISSILE;
835: return true;
836: }
837:
838: if (self->spawnflags & SPAWN_ROCKET)
839: {
840: chance = 0.10;
841: nexttime = (1.8 - (0.2 * skill->value));
842: }
843: else if(self->spawnflags & SPAWN_BLASTER)
844: {
845: chance = 0.35;
846: nexttime = (1.2 - (0.2 * skill->value));
847: }
848: else
849: {
850: chance = 0.50;
851: nexttime = (0.8 - (0.1 * skill->value));
852: }
853:
854: if (skill->value == 0)
855: chance *= 0.5;
856: else if (skill->value > 1)
857: chance *= 2;
858:
859: // PGM - go ahead and shoot every time if it's a info_notnull
860: // PMM - added visibility check
861: if ( ((random () < chance) && (visible(self, self->enemy))) || (self->enemy->solid == SOLID_NOT))
862: {
863: self->monsterinfo.attack_state = AS_MISSILE;
864: // self->monsterinfo.attack_finished = level.time + 0.3 + 2*random();
865: self->monsterinfo.attack_finished = level.time + nexttime;
866: return true;
867: }
868:
869: self->monsterinfo.attack_state = AS_STRAIGHT;
870:
871: return false;
872: }
873:
874:
875: // **********************
876: // SPAWN
877: // **********************
878:
879: /*QUAKED monster_turret (1 .5 0) (-16 -16 -16) (16 16 16) Ambush Trigger_Spawn Sight Blaster MachineGun Rocket Heatbeam WallUnit
880:
881: The automated defense turret that mounts on walls.
882: Check the weapon you want it to use: blaster, machinegun, rocket, heatbeam.
883: Default weapon is blaster.
884: When activated, wall units move 32 units in the direction they're facing.
885: */
886: void SP_monster_turret (edict_t *self)
887: {
888: int angle;
889:
890: if (deathmatch->value)
891: {
892: G_FreeEdict (self);
893: return;
894: }
895:
896: // VERSIONING
897: if (g_showlogic && g_showlogic->value)
898: gi.dprintf ("%s\n", ROGUE_VERSION_STRING);
899:
900: self->plat2flags = ROGUE_VERSION_ID;
901: // versions
902:
903: // pre-caches
904: gi.soundindex ("world/dr_short.wav");
905: gi.modelindex ("models/objects/debris1/tris.md2");
906:
907: self->s.modelindex = gi.modelindex("models/monsters/turret/tris.md2");
908:
909: VectorSet (self->mins, -12, -12, -12);
910: VectorSet (self->maxs, 12, 12, 12);
911: self->movetype = MOVETYPE_NONE;
912: self->solid = SOLID_BBOX;
913:
914: self->health = 240;
915: self->gib_health = -100;
916: self->mass = 250;
917: self->yaw_speed = 45;
918:
919: self->flags |= FL_MECHANICAL;
920:
921: self->pain = turret_pain;
922: self->die = turret_die;
923:
924: // map designer didn't specify weapon type. set it now.
925: if(!(self->spawnflags & SPAWN_WEAPONCHOICE))
926: {
927: self->spawnflags |= SPAWN_BLASTER;
928: // self->spawnflags |= SPAWN_MACHINEGUN;
929: // self->spawnflags |= SPAWN_ROCKET;
930: // self->spawnflags |= SPAWN_HEATBEAM;
931: }
932:
933: if(self->spawnflags & SPAWN_HEATBEAM)
934: {
935: self->spawnflags &= ~SPAWN_HEATBEAM;
936: self->spawnflags |= SPAWN_BLASTER;
937: }
938:
939: if(!(self->spawnflags & SPAWN_WALL_UNIT))
940: {
941: self->monsterinfo.stand = turret_stand;
942: self->monsterinfo.walk = turret_walk;
943: self->monsterinfo.run = turret_run;
944: self->monsterinfo.dodge = NULL;
945: self->monsterinfo.attack = turret_attack;
946: self->monsterinfo.melee = NULL;
947: self->monsterinfo.sight = turret_sight;
948: self->monsterinfo.search = turret_search;
949: self->monsterinfo.currentmove = &turret_move_stand;
950: }
951:
952: // PMM
953: self->monsterinfo.checkattack = turret_checkattack;
954:
955: self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
956: self->monsterinfo.scale = MODEL_SCALE;
957: self->gravity = 0;
958:
959: VectorCopy(self->s.angles, self->offset);
960: angle=(int)self->s.angles[1];
961: switch(angle)
962: {
963: case -1: // up
964: self->s.angles[0] = 270;
965: self->s.angles[1] = 0;
966: self->s.origin[2] += 2;
967: break;
968: case -2: // down
969: self->s.angles[0] = 90;
970: self->s.angles[1] = 0;
971: self->s.origin[2] -= 2;
972: break;
973: case 0:
974: self->s.origin[0] += 2;
975: break;
976: case 90:
977: self->s.origin[1] += 2;
978: break;
979: case 180:
980: self->s.origin[0] -= 2;
981: break;
982: case 270:
983: self->s.origin[1] -= 2;
984: break;
985: default:
986: break;
987: }
988:
989: gi.linkentity (self);
990:
991:
992: if(self->spawnflags & SPAWN_WALL_UNIT)
993: {
994: if(!self->targetname)
995: {
996: gi.dprintf("Wall Unit Turret without targetname! %s\n", vtos(self->s.origin));
997: G_FreeEdict(self);
998: return;
999: }
1000:
1001: self->takedamage = DAMAGE_NO;
1002: self->use = turret_activate;
1003: turret_wall_spawn(self);
1004: if ((!(self->monsterinfo.aiflags & AI_GOOD_GUY)) && (!(self->monsterinfo.aiflags & AI_DO_NOT_COUNT)))
1005: level.total_monsters++;
1006:
1007: }
1008: else
1009: {
1010: stationarymonster_start (self);
1011: }
1012:
1013: if(self->spawnflags & SPAWN_MACHINEGUN)
1014: {
1015: gi.soundindex ("infantry/infatck1.wav");
1016: self->s.skinnum = 1;
1017: }
1018: else if(self->spawnflags & SPAWN_ROCKET)
1019: {
1020: gi.soundindex ("weapons/rockfly.wav");
1021: gi.modelindex ("models/objects/rocket/tris.md2");
1022: gi.soundindex ("chick/chkatck2.wav");
1023: self->s.skinnum = 2;
1024: }
1025: else
1026: {
1027: if (!(self->spawnflags & SPAWN_BLASTER))
1028: {
1029: if ((g_showlogic) && (g_showlogic->value))
1030: gi.dprintf ("Unknown spawn flags for turret. Defaulting to blaster.\n");
1031: self->spawnflags |= SPAWN_BLASTER;
1032: }
1033: gi.modelindex ("models/objects/laser/tris.md2");
1034: gi.soundindex ("misc/lasfly.wav");
1035: gi.soundindex ("soldier/solatck2.wav");
1036: }
1037:
1038: // PMM - turrets don't get mad at monsters, and visa versa
1039: self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
1040: // PMM - blindfire
1041: if(self->spawnflags & (SPAWN_ROCKET|SPAWN_BLASTER))
1042: self->monsterinfo.blindfire = true;
1043: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.