|
|
1.1 root 1: // g_ai.c
2:
3: #include "g_local.h"
4:
5: qboolean FindTarget (edict_t *self);
6: extern cvar_t *maxclients;
7:
8: qboolean ai_checkattack (edict_t *self, float dist);
9:
10: qboolean enemy_vis;
11: qboolean enemy_infront;
12: int enemy_range;
13: float enemy_yaw;
14:
15: // ROGUE STUFF
16: #define SLIDING_TROOPS 1
17: #define MAX_SIDESTEP 8.0
18: //
19:
20: //============================================================================
21:
22:
23: /*
24: =================
25: AI_SetSightClient
26:
27: Called once each frame to set level.sight_client to the
28: player to be checked for in findtarget.
29:
30: If all clients are either dead or in notarget, sight_client
31: will be null.
32:
33: In coop games, sight_client will cycle between the clients.
34: =================
35: */
36: void AI_SetSightClient (void)
37: {
38: edict_t *ent;
39: int start, check;
40:
41: if (level.sight_client == NULL)
42: start = 1;
43: else
44: start = level.sight_client - g_edicts;
45:
46: check = start;
47: while (1)
48: {
49: check++;
50: if (check > game.maxclients)
51: check = 1;
52: ent = &g_edicts[check];
53: if (ent->inuse
54: && ent->health > 0
55: && !(ent->flags & (FL_NOTARGET|FL_DISGUISED)) )
56: {
57: level.sight_client = ent;
58: return; // got one
59: }
60: if (check == start)
61: {
62: level.sight_client = NULL;
63: return; // nobody to see
64: }
65: }
66: }
67:
68: //============================================================================
69:
70: /*
71: =============
72: ai_move
73:
74: Move the specified distance at current facing.
75: This replaces the QC functions: ai_forward, ai_back, ai_pain, and ai_painforward
76: ==============
77: */
78: void ai_move (edict_t *self, float dist)
79: {
80: M_walkmove (self, self->s.angles[YAW], dist);
81: }
82:
83:
84: /*
85: =============
86: ai_stand
87:
88: Used for standing around and looking for players
89: Distance is for slight position adjustments needed by the animations
90: ==============
91: */
92: void ai_stand (edict_t *self, float dist)
93: {
94: vec3_t v;
95:
96: if (dist)
97: M_walkmove (self, self->s.angles[YAW], dist);
98:
99: if (self->monsterinfo.aiflags & AI_STAND_GROUND)
100: {
101: if (self->enemy)
102: {
103: VectorSubtract (self->enemy->s.origin, self->s.origin, v);
104: self->ideal_yaw = vectoyaw(v);
105: if (self->s.angles[YAW] != self->ideal_yaw && self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
106: {
107: self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
108: self->monsterinfo.run (self);
109: }
110: if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
111: M_ChangeYaw (self);
112: // PMM
113: if ((self->enemy) && (self->enemy->inuse) && (visible(self, self->enemy)))
114: {
115: self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
116: VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
117: VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target);
118: self->monsterinfo.trail_time = level.time;
119: self->monsterinfo.blind_fire_delay = 0;
120: }
121: // pmm
122: ai_checkattack (self, 0);
123: }
124: else
125: FindTarget (self);
126: return;
127: }
128:
129: if (FindTarget (self))
130: return;
131:
132: if (level.time > self->monsterinfo.pausetime)
133: {
134: self->monsterinfo.walk (self);
135: return;
136: }
137:
138: if (!(self->spawnflags & 1) && (self->monsterinfo.idle) && (level.time > self->monsterinfo.idle_time))
139: {
140: if (self->monsterinfo.idle_time)
141: {
142: self->monsterinfo.idle (self);
143: self->monsterinfo.idle_time = level.time + 15 + random() * 15;
144: }
145: else
146: {
147: self->monsterinfo.idle_time = level.time + random() * 15;
148: }
149: }
150: }
151:
152:
153: /*
154: =============
155: ai_walk
156:
157: The monster is walking it's beat
158: =============
159: */
160: void ai_walk (edict_t *self, float dist)
161: {
162: M_MoveToGoal (self, dist);
163:
164: // check for noticing a player
165: if (FindTarget (self))
166: return;
167:
168: if ((self->monsterinfo.search) && (level.time > self->monsterinfo.idle_time))
169: {
170: if (self->monsterinfo.idle_time)
171: {
172: self->monsterinfo.search (self);
173: self->monsterinfo.idle_time = level.time + 15 + random() * 15;
174: }
175: else
176: {
177: self->monsterinfo.idle_time = level.time + random() * 15;
178: }
179: }
180: }
181:
182:
183: /*
184: =============
185: ai_charge
186:
187: Turns towards target and advances
188: Use this call with a distance of 0 to replace ai_face
189: ==============
190: */
191: void ai_charge (edict_t *self, float dist)
192: {
193: vec3_t v;
194: // PMM
195: float ofs;
196: // PMM
197:
198: // PMM - made AI_MANUAL_STEERING affect things differently here .. they turn, but
199: // don't set the ideal_yaw
200:
201: // This is put in there so monsters won't move towards the origin after killing
202: // a tesla. This could be problematic, so keep an eye on it.
203: if(!self->enemy || !self->enemy->inuse) //PGM
204: return; //PGM
205:
206: // PMM - save blindfire target
207: if (visible(self, self->enemy))
208: VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target);
209: // pmm
210:
211: if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
212: {
213: VectorSubtract (self->enemy->s.origin, self->s.origin, v);
214: self->ideal_yaw = vectoyaw(v);
215: // gi.dprintf ("enemy = %s\n", vtos (self->enemy->s.origin));
216: // gi.dprintf ("enemy: ideal yaw is %f\n", self->ideal_yaw);
217: }
218: // if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
219: M_ChangeYaw (self);
220: // PMM
221: // if (dist)
222: // M_walkmove (self, self->s.angles[YAW], dist);
223:
224: if (dist)
225: {
226: if (self->monsterinfo.aiflags & AI_CHARGING)
227: {
228: M_MoveToGoal (self, dist);
229: return;
230: }
231: // circle strafe support
232: if (self->monsterinfo.attack_state == AS_SLIDING)
233: {
234: if (self->monsterinfo.lefty)
235: ofs = 90;
236: else
237: ofs = -90;
238:
239: if (M_walkmove (self, self->ideal_yaw + ofs, dist))
240: return;
241:
242: self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
243: M_walkmove (self, self->ideal_yaw - ofs, dist);
244: }
245: else
246: M_walkmove (self, self->s.angles[YAW], dist);
247: }
248: // PMM
249: }
250:
251:
252: /*
253: =============
254: ai_turn
255:
256: don't move, but turn towards ideal_yaw
257: Distance is for slight position adjustments needed by the animations
258: =============
259: */
260: void ai_turn (edict_t *self, float dist)
261: {
262: if (dist)
263: M_walkmove (self, self->s.angles[YAW], dist);
264:
265: if (FindTarget (self))
266: return;
267:
268: if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
269: M_ChangeYaw (self);
270: }
271:
272:
273: /*
274:
275: .enemy
276: Will be world if not currently angry at anyone.
277:
278: .movetarget
279: The next path spot to walk toward. If .enemy, ignore .movetarget.
280: When an enemy is killed, the monster will try to return to it's path.
281:
282: .hunt_time
283: Set to time + something when the player is in sight, but movement straight for
284: him is blocked. This causes the monster to use wall following code for
285: movement direction instead of sighting on the player.
286:
287: .ideal_yaw
288: A yaw angle of the intended direction, which will be turned towards at up
289: to 45 deg / state. If the enemy is in view and hunt_time is not active,
290: this will be the exact line towards the enemy.
291:
292: .pausetime
293: A monster will leave it's stand state and head towards it's .movetarget when
294: time > .pausetime.
295:
296: walkmove(angle, speed) primitive is all or nothing
297: */
298:
299: /*
300: =============
301: range
302:
303: returns the range catagorization of an entity reletive to self
304: 0 melee range, will become hostile even if back is turned
305: 1 visibility and infront, or visibility and show hostile
306: 2 infront and show hostile
307: 3 only triggered by damage
308: =============
309: */
310: int range (edict_t *self, edict_t *other)
311: {
312: vec3_t v;
313: float len;
314:
315: VectorSubtract (self->s.origin, other->s.origin, v);
316: len = VectorLength (v);
317: if (len < MELEE_DISTANCE)
318: return RANGE_MELEE;
319: if (len < 500)
320: return RANGE_NEAR;
321: if (len < 1000)
322: return RANGE_MID;
323: return RANGE_FAR;
324: }
325:
326: /*
327: =============
328: visible
329:
330: returns 1 if the entity is visible to self, even if not infront ()
331: =============
332: */
333: qboolean visible (edict_t *self, edict_t *other)
334: {
335: vec3_t spot1;
336: vec3_t spot2;
337: trace_t trace;
338:
339: VectorCopy (self->s.origin, spot1);
340: spot1[2] += self->viewheight;
341: VectorCopy (other->s.origin, spot2);
342: spot2[2] += other->viewheight;
343: trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE);
344:
345: if (trace.fraction == 1.0 || trace.ent == other) // PGM
346: return true;
347: return false;
348: }
349:
350:
351: /*
352: =============
353: infront
354:
355: returns 1 if the entity is in front (in sight) of self
356: =============
357: */
358: qboolean infront (edict_t *self, edict_t *other)
359: {
360: vec3_t vec;
361: float dot;
362: vec3_t forward;
363:
364: AngleVectors (self->s.angles, forward, NULL, NULL);
365: VectorSubtract (other->s.origin, self->s.origin, vec);
366: VectorNormalize (vec);
367: dot = DotProduct (vec, forward);
368:
369: if (dot > 0.3)
370: return true;
371: return false;
372: }
373:
374:
375: //============================================================================
376:
377: void HuntTarget (edict_t *self)
378: {
379: vec3_t vec;
380:
381: self->goalentity = self->enemy;
382: if (self->monsterinfo.aiflags & AI_STAND_GROUND)
383: self->monsterinfo.stand (self);
384: else
385: self->monsterinfo.run (self);
386: VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
387: self->ideal_yaw = vectoyaw(vec);
388: // wait a while before first attack
389: if (!(self->monsterinfo.aiflags & AI_STAND_GROUND))
390: AttackFinished (self, 1);
391: }
392:
393: void FoundTarget (edict_t *self)
394: {
395: // let other monsters see this monster for a while
396: if (self->enemy->client)
397: {
398: if(self->enemy->flags & FL_DISGUISED)
399: {
400: // level.disguise_violator = self->enemy;
401: // level.disguise_violation_framenum = level.framenum + 5;
402: self->enemy->flags &= ~FL_DISGUISED;
403: }
404:
405: level.sight_entity = self;
406: level.sight_entity_framenum = level.framenum;
407: level.sight_entity->light_level = 128;
408: }
409:
410: self->show_hostile = level.time + 1; // wake up other monsters
411:
412: VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
413: self->monsterinfo.trail_time = level.time;
414: // PMM
415: VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target);
416: self->monsterinfo.blind_fire_delay = 0;
417: // PMM
418:
419: if (!self->combattarget)
420: {
421: HuntTarget (self);
422: return;
423: }
424:
425: self->goalentity = self->movetarget = G_PickTarget(self->combattarget);
426: if (!self->movetarget)
427: {
428: self->goalentity = self->movetarget = self->enemy;
429: HuntTarget (self);
430: gi.dprintf("%s at %s, combattarget %s not found\n", self->classname, vtos(self->s.origin), self->combattarget);
431: return;
432: }
433:
434: // clear out our combattarget, these are a one shot deal
435: self->combattarget = NULL;
436: self->monsterinfo.aiflags |= AI_COMBAT_POINT;
437:
438: // clear the targetname, that point is ours!
439: self->movetarget->targetname = NULL;
440: self->monsterinfo.pausetime = 0;
441:
442: // run for it
443: self->monsterinfo.run (self);
444: }
445:
446:
447: /*
448: ===========
449: FindTarget
450:
451: Self is currently not attacking anything, so try to find a target
452:
453: Returns TRUE if an enemy was sighted
454:
455: When a player fires a missile, the point of impact becomes a fakeplayer so
456: that monsters that see the impact will respond as if they had seen the
457: player.
458:
459: To avoid spending too much time, only a single client (or fakeclient) is
460: checked each frame. This means multi player games will have slightly
461: slower noticing monsters.
462: ============
463: */
464: qboolean FindTarget (edict_t *self)
465: {
466: edict_t *client;
467: qboolean heardit;
468: int r;
469:
470: if (self->monsterinfo.aiflags & AI_GOOD_GUY)
471: {
472: if (self->goalentity && self->goalentity->inuse && self->goalentity->classname)
473: {
474: if (strcmp(self->goalentity->classname, "target_actor") == 0)
475: return false;
476: }
477:
478: //FIXME look for monsters?
479: return false;
480: }
481:
482: // if we're going to a combat point, just proceed
483: if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
484: return false;
485:
486: // if the first spawnflag bit is set, the monster will only wake up on
487: // really seeing the player, not another monster getting angry or hearing
488: // something
489:
490: // revised behavior so they will wake up if they "see" a player make a noise
491: // but not weapon impact/explosion noises
492:
493: heardit = false;
494: if ((level.sight_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) )
495: {
496: client = level.sight_entity;
497: if (client->enemy == self->enemy)
498: {
499: return false;
500: }
501: }
502: else if (level.disguise_violation_framenum > level.framenum)
503: {
504: client = level.disguise_violator;
505: }
506: else if (level.sound_entity_framenum >= (level.framenum - 1))
507: {
508: client = level.sound_entity;
509: heardit = true;
510: }
511: else if (!(self->enemy) && (level.sound2_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) )
512: {
513: client = level.sound2_entity;
514: heardit = true;
515: }
516: else
517: {
518: client = level.sight_client;
519: if (!client)
520: return false; // no clients to get mad at
521: }
522:
523: // if the entity went away, forget it
524: if (!client->inuse)
525: return false;
526:
527: if (client == self->enemy)
528: return true; // JDC false;
529:
530: if (client->client)
531: {
532: if (client->flags & FL_NOTARGET)
533: return false;
534: }
535: else if (client->svflags & SVF_MONSTER)
536: {
537: if (!client->enemy)
538: return false;
539: if (client->enemy->flags & FL_NOTARGET)
540: return false;
541: }
542: else if (heardit)
543: {
544: // pgm - a little more paranoia won't hurt....
545: if ((client->owner) && (client->owner->flags & FL_NOTARGET))
546: return false;
547: }
548: else
549: return false;
550:
551: if (!heardit)
552: {
553: r = range (self, client);
554:
555: if (r == RANGE_FAR)
556: return false;
557:
558: // this is where we would check invisibility
559:
560: // is client in an spot too dark to be seen?
561: if (client->light_level <= 5)
562: return false;
563:
564: if (!visible (self, client))
565: {
566: return false;
567: }
568:
569: if (r == RANGE_NEAR)
570: {
571: if (client->show_hostile < level.time && !infront (self, client))
572: {
573: return false;
574: }
575: }
576: else if (r == RANGE_MID)
577: {
578: if (!infront (self, client))
579: {
580: return false;
581: }
582: }
583:
584: self->enemy = client;
585:
586: if (strcmp(self->enemy->classname, "player_noise") != 0)
587: {
588: self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
589:
590: if (!self->enemy->client)
591: {
592: self->enemy = self->enemy->enemy;
593: if (!self->enemy->client)
594: {
595: self->enemy = NULL;
596: return false;
597: }
598: }
599: }
600: }
601: else // heardit
602: {
603: vec3_t temp;
604:
605: if (self->spawnflags & 1)
606: {
607: if (!visible (self, client))
608: return false;
609: }
610: else
611: {
612: if (!gi.inPHS(self->s.origin, client->s.origin))
613: return false;
614: }
615:
616: VectorSubtract (client->s.origin, self->s.origin, temp);
617:
618: if (VectorLength(temp) > 1000) // too far to hear
619: {
620: return false;
621: }
622:
623: // check area portals - if they are different and not connected then we can't hear it
624: if (client->areanum != self->areanum)
625: if (!gi.AreasConnected(self->areanum, client->areanum))
626: return false;
627:
628: self->ideal_yaw = vectoyaw(temp);
629: if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
630: M_ChangeYaw (self);
631:
632: // hunt the sound for a bit; hopefully find the real player
633: self->monsterinfo.aiflags |= AI_SOUND_TARGET;
634: self->enemy = client;
635: }
636:
637: //
638: // got one
639: //
640: FoundTarget (self);
641:
642: if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) && (self->monsterinfo.sight))
643: self->monsterinfo.sight (self, self->enemy);
644:
645: return true;
646: }
647:
648:
649: //=============================================================================
650:
651: /*
652: ============
653: FacingIdeal
654:
655: ============
656: */
657: qboolean FacingIdeal(edict_t *self)
658: {
659: float delta;
660:
661: delta = anglemod(self->s.angles[YAW] - self->ideal_yaw);
662: if (delta > 45 && delta < 315)
663: return false;
664: return true;
665: }
666:
667:
668: //=============================================================================
669:
670: qboolean M_CheckAttack (edict_t *self)
671: {
672: vec3_t spot1, spot2;
673: float chance;
674: trace_t tr;
675:
676: if (self->enemy->health > 0)
677: {
678: // see if any entities are in the way of the shot
679: VectorCopy (self->s.origin, spot1);
680: spot1[2] += self->viewheight;
681: VectorCopy (self->enemy->s.origin, spot2);
682: spot2[2] += self->enemy->viewheight;
683:
684: tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW);
685:
686: // do we have a clear shot?
687: if (tr.ent != self->enemy)
688: {
689: // PGM - we want them to go ahead and shoot at info_notnulls if they can.
690: if(self->enemy->solid != SOLID_NOT || tr.fraction < 1.0) //PGM
691: {
692: // PMM - if we can't see our target, and we're not blocked by a monster, go into blind fire if available
693: if ((!(tr.ent->svflags & SVF_MONSTER)) && (!visible(self, self->enemy)))
694: {
695: if ((self->monsterinfo.blindfire) && (self->monsterinfo.blind_fire_delay <= 20.0))
696: {
697: if (level.time < self->monsterinfo.attack_finished)
698: {
699: return false;
700: }
701: if (level.time < (self->monsterinfo.trail_time + self->monsterinfo.blind_fire_delay))
702: {
703: // wait for our time
704: return false;
705: }
706: else
707: {
708: // gi.WriteByte (svc_temp_entity);
709: // gi.WriteByte (TE_DEBUGTRAIL);
710: // gi.WritePosition (spot1);
711: // gi.WritePosition (self->monsterinfo.blind_fire_target);
712: // gi.multicast (self->s.origin, MULTICAST_ALL);
713: // make sure we're not going to shoot a monster
714: tr = gi.trace (spot1, NULL, NULL, self->monsterinfo.blind_fire_target, self, CONTENTS_MONSTER);
715: if (tr.allsolid || tr.startsolid || ((tr.fraction < 1.0) && (tr.ent != self->enemy)))
716: {
717: if ((g_showlogic) && (g_showlogic->value))
718: gi.dprintf ("blindfire blocked\n");
719: return false;
720: }
721:
722: self->monsterinfo.attack_state = AS_BLIND;
723: return true;
724: }
725: }
726: }
727: // pmm
728: return false;
729: }
730: }
731: }
732:
733: // melee attack
734: if (enemy_range == RANGE_MELEE)
735: {
736: // don't always melee in easy mode
737: if (skill->value == 0 && (rand()&3) )
738: {
739: // PMM - fix for melee only monsters & strafing
740: self->monsterinfo.attack_state = AS_STRAIGHT;
741: return false;
742: }
743: if (self->monsterinfo.melee)
744: self->monsterinfo.attack_state = AS_MELEE;
745: else
746: self->monsterinfo.attack_state = AS_MISSILE;
747: return true;
748: }
749:
750: // missile attack
751: if (!self->monsterinfo.attack)
752: {
753: // PMM - fix for melee only monsters & strafing
754: self->monsterinfo.attack_state = AS_STRAIGHT;
755: return false;
756: }
757:
758: if (level.time < self->monsterinfo.attack_finished)
759: return false;
760:
761: if (enemy_range == RANGE_FAR)
762: return false;
763:
764: if (self->monsterinfo.aiflags & AI_STAND_GROUND)
765: {
766: chance = 0.4;
767: }
768: else if (enemy_range == RANGE_MELEE)
769: {
770: chance = 0.2;
771: }
772: else if (enemy_range == RANGE_NEAR)
773: {
774: chance = 0.1;
775: }
776: else if (enemy_range == RANGE_MID)
777: {
778: chance = 0.02;
779: }
780: else
781: {
782: return false;
783: }
784:
785: if (skill->value == 0)
786: chance *= 0.5;
787: else if (skill->value >= 2)
788: chance *= 2;
789:
790: // PGM - go ahead and shoot every time if it's a info_notnull
791: if ((random () < chance) || (self->enemy->solid == SOLID_NOT))
792: {
793: self->monsterinfo.attack_state = AS_MISSILE;
794: self->monsterinfo.attack_finished = level.time + 2*random();
795: return true;
796: }
797:
798: // PMM -daedalus should strafe more .. this can be done here or in a customized
799: // check_attack code for the hover.
800: if (self->flags & FL_FLY)
801: {
802: // originally, just 0.3
803: float strafe_chance;
804: if (!(strcmp(self->classname, "monster_daedalus")))
805: strafe_chance = 0.8;
806: else
807: strafe_chance = 0.6;
808:
809: if (random() < strafe_chance)
810: self->monsterinfo.attack_state = AS_SLIDING;
811: else
812: self->monsterinfo.attack_state = AS_STRAIGHT;
813: }
814: // do we want the monsters strafing?
815: #ifdef SLIDING_TROOPS
816: else
817: {
818: if (random() < 0.4)
819: self->monsterinfo.attack_state = AS_SLIDING;
820: else
821: self->monsterinfo.attack_state = AS_STRAIGHT;
822: }
823: #endif
824: //-PMM
825:
826: return false;
827: }
828:
829:
830: /*
831: =============
832: ai_run_melee
833:
834: Turn and close until within an angle to launch a melee attack
835: =============
836: */
837: void ai_run_melee(edict_t *self)
838: {
839: self->ideal_yaw = enemy_yaw;
840: if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
841: M_ChangeYaw (self);
842:
843: if (FacingIdeal(self))
844: {
845: self->monsterinfo.melee (self);
846: self->monsterinfo.attack_state = AS_STRAIGHT;
847: }
848: }
849:
850:
851: /*
852: =============
853: ai_run_missile
854:
855: Turn in place until within an angle to launch a missile attack
856: =============
857: */
858: void ai_run_missile(edict_t *self)
859: {
860: self->ideal_yaw = enemy_yaw;
861: if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
862: M_ChangeYaw (self);
863:
864: if (FacingIdeal(self))
865: {
866: self->monsterinfo.attack (self);
867: // if (self->monsterinfo.attack_state == AS_MISSILE)
868: if ((self->monsterinfo.attack_state == AS_MISSILE) || (self->monsterinfo.attack_state == AS_BLIND))
869: self->monsterinfo.attack_state = AS_STRAIGHT;
870: // else if (self->monsterinfo.attack_state != AS_SLIDING)
871: // gi.dprintf ("ai_run_missile: Unexpected attack state %d !\n", self->monsterinfo.attack_state);
872: }
873: };
874:
875:
876: /*
877: =============
878: ai_run_slide
879:
880: Strafe sideways, but stay at aproximately the same range
881: =============
882: */
883: void ai_run_slide(edict_t *self, float distance)
884: {
885: float ofs;
886: float angle;
887:
888: self->ideal_yaw = enemy_yaw;
889:
890: // if (self->flags & FL_FLY)
891: // angle = 90;
892: // else
893: // angle = 45;
894:
895: angle = 90;
896:
897: if (self->monsterinfo.lefty)
898: ofs = angle;
899: else
900: ofs = -angle;
901: //
902: // if (!(self->flags & FL_FLY))
903: // {
904: // // non fliers should actually turn towards the direction their trying to run
905: // self->ideal_yaw += ofs;
906: // }
907: //
908: if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
909: M_ChangeYaw (self);
910:
911: /*
912: if (!(self->flags & FL_FLY))
913: {
914: if (M_walkmove (self, self->ideal_yaw + ofs, distance))
915: return;
916: }
917: else
918: {
919: if (M_walkmove (self, self->ideal_yaw, distance))
920: return;
921: }
922: */
923: // PMM - clamp maximum sideways move for non flyers to make them look less jerky
924: if (!self->flags & FL_FLY)
925: distance = min (distance, MAX_SIDESTEP);
926: if (M_walkmove (self, self->ideal_yaw + ofs, distance))
927: return;
928: self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
929: if (M_walkmove (self, self->ideal_yaw - ofs, distance))
930: return;
931: // PMM - the move failed, so signal the caller (ai_run) to try going straight
932: self->monsterinfo.attack_state = AS_STRAIGHT;
933: /*
934: if (!(self->flags & FL_FLY))
935: {
936: M_walkmove (self, self->ideal_yaw + ofs, distance);
937: }
938: else
939: {
940: M_walkmove (self, self->ideal_yaw, distance);
941: }*/
942: }
943:
944:
945: /*
946: =============
947: ai_checkattack
948:
949: Decides if we're going to attack or do something else
950: used by ai_run and ai_stand
951: =============
952: */
953: qboolean ai_checkattack (edict_t *self, float dist)
954: {
955: vec3_t temp;
956: qboolean hesDeadJim;
957: // PMM
958: qboolean retval;
959:
960: // this causes monsters to run blindly to the combat point w/o firing
961: if (self->goalentity)
962: {
963: if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
964: return false;
965:
966: if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
967: {
968: if ((level.time - self->enemy->teleport_time) > 5.0)
969: {
970: if (self->goalentity == self->enemy)
971: if (self->movetarget)
972: self->goalentity = self->movetarget;
973: else
974: self->goalentity = NULL;
975: self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
976: if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
977: self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
978: }
979: else
980: {
981: self->show_hostile = level.time + 1;
982: return false;
983: }
984: }
985: }
986:
987: enemy_vis = false;
988:
989: // see if the enemy is dead
990: hesDeadJim = false;
991: if ((!self->enemy) || (!self->enemy->inuse))
992: {
993: hesDeadJim = true;
994: }
995: else if (self->monsterinfo.aiflags & AI_MEDIC)
996: {
997: if (self->enemy->health > 0)
998: {
999: hesDeadJim = true;
1000: // self->monsterinfo.aiflags &= ~AI_MEDIC;
1001: }
1002: }
1003: else
1004: {
1005: if (self->monsterinfo.aiflags & AI_BRUTAL)
1006: {
1007: if (self->enemy->health <= -80)
1008: hesDeadJim = true;
1009: }
1010: else
1011: {
1012: if (self->enemy->health <= 0)
1013: hesDeadJim = true;
1014: }
1015: }
1016:
1017: if (hesDeadJim)
1018: {
1019: self->monsterinfo.aiflags &= ~AI_MEDIC;
1020: self->enemy = NULL;
1021: // FIXME: look all around for other targets
1022: if (self->oldenemy && self->oldenemy->health > 0)
1023: {
1024: self->enemy = self->oldenemy;
1025: self->oldenemy = NULL;
1026: HuntTarget (self);
1027: }
1028: //ROGUE - multiple teslas make monsters lose track of the player.
1029: else if(self->monsterinfo.last_player_enemy && self->monsterinfo.last_player_enemy->health > 0)
1030: {
1031: if ((g_showlogic) && (g_showlogic->value))
1032: gi.dprintf("resorting to last_player_enemy...\n");
1033: self->enemy = self->monsterinfo.last_player_enemy;
1034: self->oldenemy = NULL;
1035: self->monsterinfo.last_player_enemy = NULL;
1036: HuntTarget (self);
1037: }
1038: //ROGUE
1039: else
1040: {
1041: if (self->movetarget)
1042: {
1043: self->goalentity = self->movetarget;
1044: self->monsterinfo.walk (self);
1045: }
1046: else
1047: {
1048: // we need the pausetime otherwise the stand code
1049: // will just revert to walking with no target and
1050: // the monsters will wonder around aimlessly trying
1051: // to hunt the world entity
1052: self->monsterinfo.pausetime = level.time + 100000000;
1053: self->monsterinfo.stand (self);
1054: }
1055: return true;
1056: }
1057: }
1058:
1059: self->show_hostile = level.time + 1; // wake up other monsters
1060:
1061: // check knowledge of enemy
1062: enemy_vis = visible(self, self->enemy);
1063: if (enemy_vis)
1064: {
1065: self->monsterinfo.search_time = level.time + 5;
1066: VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
1067: // PMM
1068: self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
1069: self->monsterinfo.trail_time = level.time;
1070: VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target);
1071: self->monsterinfo.blind_fire_delay = 0;
1072: // pmm
1073: }
1074:
1075: // look for other coop players here
1076: // if (coop && self->monsterinfo.search_time < level.time)
1077: // {
1078: // if (FindTarget (self))
1079: // return true;
1080: // }
1081:
1082: enemy_infront = infront(self, self->enemy);
1083: enemy_range = range(self, self->enemy);
1084: VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
1085: enemy_yaw = vectoyaw(temp);
1086:
1087:
1088: // JDC self->ideal_yaw = enemy_yaw;
1089:
1090: // PMM -- reordered so the monster specific checkattack is called before the run_missle/melee/checkvis
1091: // stuff .. this allows for, among other things, circle strafing and attacking while in ai_run
1092: retval = self->monsterinfo.checkattack (self);
1093: if (retval)
1094: {
1095: // PMM
1096: if (self->monsterinfo.attack_state == AS_MISSILE)
1097: {
1098: ai_run_missile (self);
1099: return true;
1100: }
1101: if (self->monsterinfo.attack_state == AS_MELEE)
1102: {
1103: ai_run_melee (self);
1104: return true;
1105: }
1106: // PMM -- added so monsters can shoot blind
1107: if (self->monsterinfo.attack_state == AS_BLIND)
1108: {
1109: ai_run_missile (self);
1110: return true;
1111: }
1112: // pmm
1113:
1114: // if enemy is not currently visible, we will never attack
1115: if (!enemy_vis)
1116: return false;
1117: // PMM
1118: }
1119: return retval;
1120: // PMM
1121: // return self->monsterinfo.checkattack (self);
1122: }
1123:
1124:
1125: /*
1126: =============
1127: ai_run
1128:
1129: The monster has an enemy it is trying to kill
1130: =============
1131: */
1132: void ai_run (edict_t *self, float dist)
1133: {
1134: vec3_t v;
1135: edict_t *tempgoal;
1136: edict_t *save;
1137: qboolean new;
1138: edict_t *marker;
1139: float d1, d2;
1140: trace_t tr;
1141: vec3_t v_forward, v_right;
1142: float left, center, right;
1143: vec3_t left_target, right_target;
1144: //PMM
1145: qboolean retval;
1146: qboolean alreadyMoved = false;
1147:
1148: // if we're going to a combat point, just proceed
1149: if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
1150: {
1151: M_MoveToGoal (self, dist);
1152: return;
1153: }
1154:
1155: // PMM
1156: if (self->monsterinfo.aiflags & AI_DUCKED)
1157: {
1158: if ((g_showlogic) && (g_showlogic->value))
1159: gi.dprintf ("%s - duck flag cleaned up!\n", self->classname);
1160: self->monsterinfo.aiflags &= ~AI_DUCKED;
1161: }
1162: if (self->maxs[2] != self->monsterinfo.base_height)
1163: {
1164: if ((g_showlogic) && (g_showlogic->value))
1165: gi.dprintf ("%s - ducked height corrected!\n", self->classname);
1166: monster_duck_up (self);
1167: }
1168: if ((self->monsterinfo.aiflags & AI_MANUAL_STEERING) && (strcmp(self->classname, "monster_turret")))
1169: {
1170: if ((g_showlogic) && (g_showlogic->value))
1171: gi.dprintf ("%s - manual steering in ai_run!\n", self->classname);
1172: }
1173: // pmm
1174:
1175: //==========
1176: //PGM
1177: // if we're currently looking for a hint path
1178: if (self->monsterinfo.aiflags & AI_HINT_PATH)
1179: {
1180: // determine direction to our destination hintpath.
1181: // FIXME - is this needed EVERY time? I was having trouble with them
1182: // sometimes not going after it, and this fixed it.
1183: // VectorSubtract(self->movetarget->s.origin, self->s.origin, v);
1184: // vectoangles(v, v_forward);
1185: // self->ideal_yaw = v_forward[YAW];
1186: // gi.dprintf("seeking hintpath. origin: %s %0.1f\n", vtos(v), self->ideal_yaw);
1187: M_MoveToGoal (self, dist);
1188: if(!self->inuse)
1189: return; // PGM - g_touchtrigger free problem
1190: // return;
1191:
1192: // if we've already seen the player, and can't see him now, return
1193: if(self->enemy && !visible(self, self->enemy))
1194: return;
1195:
1196: // if not and can't find the player, return
1197: // if(!FindTarget(self))
1198: // return;
1199:
1200: // if we see the player, stop following hintpaths.
1201: if(g_showlogic && g_showlogic->value)
1202: gi.dprintf("stopped following hint paths\n");
1203:
1204: // disconnect from hintpaths and start looking normally for players.
1205: hintpath_stop (self);
1206: // pmm - no longer needed, since hintpath_stop does it
1207: // HuntTarget(self);
1208: return;
1209: }
1210: //PGM
1211: //==========
1212:
1213: if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
1214: {
1215: VectorSubtract (self->s.origin, self->enemy->s.origin, v);
1216: if (VectorLength(v) < 64)
1217: {
1218: self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
1219: self->monsterinfo.stand (self);
1220: return;
1221: }
1222:
1223: M_MoveToGoal (self, dist);
1224: // PMM - prevent double moves for sound_targets
1225: alreadyMoved = true;
1226: // pmm
1227: if(!self->inuse)
1228: return; // PGM - g_touchtrigger free problem
1229:
1230: if (!FindTarget (self))
1231: return;
1232: }
1233:
1234: // PMM -- moved ai_checkattack up here so the monsters can attack while strafing or charging
1235:
1236: // PMM -- if we're dodging, make sure to keep the attack_state AS_SLIDING
1237:
1238: retval = ai_checkattack (self, dist);
1239:
1240: if (self->monsterinfo.aiflags & AI_DODGING)
1241: self->monsterinfo.attack_state = AS_SLIDING;
1242:
1243: if (self->monsterinfo.attack_state == AS_SLIDING)
1244: {
1245: // PMM - protect against double moves
1246: if (!alreadyMoved)
1247: ai_run_slide (self, dist);
1248: // PMM
1249: // we're using attack_state as the return value out of ai_run_slide to indicate whether or not the
1250: // move succeeded. If the move succeeded, and we're still sliding, we're done in here (since we've
1251: // had our chance to shoot in ai_checkattack, and have moved).
1252: // if the move failed, our state is as_straight, and it will be taken care of below
1253: if ((!retval) && (self->monsterinfo.attack_state == AS_SLIDING))
1254: return;
1255: }
1256: else if (self->monsterinfo.aiflags & AI_CHARGING)
1257: {
1258: self->ideal_yaw = enemy_yaw;
1259: if (!(self->monsterinfo.aiflags & AI_MANUAL_STEERING))
1260: M_ChangeYaw (self);
1261: }
1262: if (retval)
1263: {
1264: // PMM - is this useful? Monsters attacking usually call the ai_charge routine..
1265: // the only monster this affects should be the soldier
1266: if ((dist != 0) && (!alreadyMoved) && (self->monsterinfo.attack_state == AS_STRAIGHT) && (!(self->monsterinfo.aiflags & AI_STAND_GROUND)))
1267: {
1268: M_MoveToGoal (self, dist);
1269: }
1270: if ((self->enemy) && (self->enemy->inuse) && (enemy_vis))
1271: {
1272: self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
1273: VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
1274: self->monsterinfo.trail_time = level.time;
1275: //PMM
1276: VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target);
1277: self->monsterinfo.blind_fire_delay = 0;
1278: //pmm
1279: }
1280: return;
1281: }
1282: //PMM
1283: // if (ai_checkattack (self, dist))
1284: // return;
1285:
1286: // if (self->monsterinfo.attack_state == AS_SLIDING)
1287: // {
1288: // ai_run_slide (self, dist);
1289: // return;
1290: // }
1291:
1292: if (enemy_vis)
1293: {
1294: // if (self->monsterinfo.aiflags & AI_LOST_SIGHT)
1295: // gi.dprintf("regained sight\n");
1296: // PMM - check for alreadyMoved
1297: if (!alreadyMoved)
1298: M_MoveToGoal (self, dist);
1299: if(!self->inuse)
1300: return; // PGM - g_touchtrigger free problem
1301:
1302: self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
1303: VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
1304: self->monsterinfo.trail_time = level.time;
1305: // PMM
1306: VectorCopy (self->enemy->s.origin, self->monsterinfo.blind_fire_target);
1307: self->monsterinfo.blind_fire_delay = 0;
1308: // pmm
1309: return;
1310: }
1311:
1312: // coop will change to another enemy if visible
1313: if (coop->value)
1314: { // FIXME: insane guys get mad with this, which causes crashes!
1315: if (FindTarget (self))
1316: return;
1317: }
1318:
1319: //=======
1320: //PGM
1321: // if we've been looking (unsuccessfully) for the player for 10 seconds
1322: // PMM - reduced to 5, makes them much nastier
1323: if((self->monsterinfo.trail_time + 5) <= level.time)
1324: {
1325: // and we haven't checked for valid hint paths in the last 10 seconds
1326: if((self->monsterinfo.last_hint_time + 10) <= level.time)
1327: {
1328: // check for hint_paths.
1329: self->monsterinfo.last_hint_time = level.time;
1330: if(monsterlost_checkhint(self))
1331: return;
1332: }
1333: }
1334: //PGM
1335: //=======
1336:
1337: if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20)))
1338: {
1339: // PMM - double move protection
1340: if (!alreadyMoved)
1341: M_MoveToGoal (self, dist);
1342: self->monsterinfo.search_time = 0;
1343: // gi.dprintf("search timeout\n");
1344: return;
1345: }
1346:
1347: save = self->goalentity;
1348: tempgoal = G_Spawn();
1349: self->goalentity = tempgoal;
1350:
1351: new = false;
1352:
1353: if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT))
1354: {
1355: // just lost sight of the player, decide where to go first
1356: // gi.dprintf("lost sight of player, last seen at %s\n", vtos(self->monsterinfo.last_sighting));
1357: self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN);
1358: self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP);
1359: new = true;
1360: }
1361:
1362: if (self->monsterinfo.aiflags & AI_PURSUE_NEXT)
1363: {
1364: // vec3_t debug_vec;
1365:
1366: self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT;
1367: // VectorSubtract(self->monsterinfo.last_sighting, self->s.origin, debug_vec);
1368: // gi.dprintf("reached current goal: %s %s %f", vtos(self->s.origin), vtos(self->monsterinfo.last_sighting), VectorLength(debug_vec));
1369:
1370: // give ourself more time since we got this far
1371: self->monsterinfo.search_time = level.time + 5;
1372:
1373: if (self->monsterinfo.aiflags & AI_PURSUE_TEMP)
1374: {
1375: // gi.dprintf("was temp goal; retrying original\n");
1376: self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP;
1377: marker = NULL;
1378: VectorCopy (self->monsterinfo.saved_goal, self->monsterinfo.last_sighting);
1379: new = true;
1380: }
1381: else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN)
1382: {
1383: self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN;
1384: marker = PlayerTrail_PickFirst (self);
1385: }
1386: else
1387: {
1388: marker = PlayerTrail_PickNext (self);
1389: }
1390:
1391: if (marker)
1392: {
1393: VectorCopy (marker->s.origin, self->monsterinfo.last_sighting);
1394: self->monsterinfo.trail_time = marker->timestamp;
1395: self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW];
1396: // gi.dprintf("heading is %0.1f\n", self->ideal_yaw);
1397:
1398: // debug_drawline(self.origin, self.last_sighting, 52);
1399: new = true;
1400: }
1401: }
1402:
1403: VectorSubtract (self->s.origin, self->monsterinfo.last_sighting, v);
1404: d1 = VectorLength(v);
1405: if (d1 <= dist)
1406: {
1407: self->monsterinfo.aiflags |= AI_PURSUE_NEXT;
1408: dist = d1;
1409: }
1410:
1411: VectorCopy (self->monsterinfo.last_sighting, self->goalentity->s.origin);
1412:
1413: if (new)
1414: {
1415: // gi.dprintf("checking for course correction\n");
1416:
1417: tr = gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting, self, MASK_PLAYERSOLID);
1418: if (tr.fraction < 1)
1419: {
1420: VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1421: d1 = VectorLength(v);
1422: center = tr.fraction;
1423: d2 = d1 * ((center+1)/2);
1424: self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1425: AngleVectors(self->s.angles, v_forward, v_right, NULL);
1426:
1427: VectorSet(v, d2, -16, 0);
1428: G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
1429: tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target, self, MASK_PLAYERSOLID);
1430: left = tr.fraction;
1431:
1432: VectorSet(v, d2, 16, 0);
1433: G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
1434: tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target, self, MASK_PLAYERSOLID);
1435: right = tr.fraction;
1436:
1437: center = (d1*center)/d2;
1438: if (left >= center && left > right)
1439: {
1440: if (left < 1)
1441: {
1442: VectorSet(v, d2 * left * 0.5, -16, 0);
1443: G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
1444: // gi.dprintf("incomplete path, go part way and adjust again\n");
1445: }
1446: VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
1447: self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
1448: VectorCopy (left_target, self->goalentity->s.origin);
1449: VectorCopy (left_target, self->monsterinfo.last_sighting);
1450: VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1451: self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1452: // gi.dprintf("adjusted left\n");
1453: // debug_drawline(self.origin, self.last_sighting, 152);
1454: }
1455: else if (right >= center && right > left)
1456: {
1457: if (right < 1)
1458: {
1459: VectorSet(v, d2 * right * 0.5, 16, 0);
1460: G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
1461: // gi.dprintf("incomplete path, go part way and adjust again\n");
1462: }
1463: VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
1464: self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
1465: VectorCopy (right_target, self->goalentity->s.origin);
1466: VectorCopy (right_target, self->monsterinfo.last_sighting);
1467: VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1468: self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1469: // gi.dprintf("adjusted right\n");
1470: // debug_drawline(self.origin, self.last_sighting, 152);
1471: }
1472: }
1473: // else gi.dprintf("course was fine\n");
1474: }
1475:
1476: M_MoveToGoal (self, dist);
1477: if(!self->inuse)
1478: return; // PGM - g_touchtrigger free problem
1479:
1480: G_FreeEdict(tempgoal);
1481:
1482: if (self)
1483: self->goalentity = save;
1484: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.