|
|
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: //============================================================================
16:
17:
18: /*
19: =================
20: AI_SetSightClient
21:
22: Called once each frame to set level.sight_client to the
23: player to be checked for in findtarget.
24:
25: If all clients are either dead or in notarget, sight_client
26: will be null.
27:
28: In coop games, sight_client will cycle between the clients.
29: =================
30: */
31: void AI_SetSightClient (void)
32: {
33: edict_t *ent;
34: int start, check;
35:
36: if (level.sight_client == NULL)
37: start = 1;
38: else
39: start = level.sight_client - g_edicts;
40:
41: check = start;
42: while (1)
43: {
44: check++;
45: if (check > game.maxclients)
46: check = 1;
47: ent = &g_edicts[check];
48: if (ent->inuse
49: && ent->health > 0
50: && !(ent->flags & FL_NOTARGET) )
51: {
52: level.sight_client = ent;
53: return; // got one
54: }
55: if (check == start)
56: {
57: level.sight_client = NULL;
58: return; // nobody to see
59: }
60: }
61: }
62:
63: //============================================================================
64:
65: /*
66: =============
67: ai_move
68:
69: Move the specified distance at current facing.
70: This replaces the QC functions: ai_forward, ai_back, ai_pain, and ai_painforward
71: ==============
72: */
73: void ai_move (edict_t *self, float dist)
74: {
75: M_walkmove (self, self->s.angles[YAW], dist);
76: }
77:
78:
79: /*
80: =============
81: ai_stand
82:
83: Used for standing around and looking for players
84: Distance is for slight position adjustments needed by the animations
85: ==============
86: */
87: void ai_stand (edict_t *self, float dist)
88: {
89: vec3_t v;
90:
91: if (dist)
92: M_walkmove (self, self->s.angles[YAW], dist);
93:
94: if (self->monsterinfo.aiflags & AI_STAND_GROUND)
95: {
96: if (self->enemy)
97: {
98: VectorSubtract (self->enemy->s.origin, self->s.origin, v);
99: self->ideal_yaw = vectoyaw(v);
100: if (self->s.angles[YAW] != self->ideal_yaw && self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
101: {
102: self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
103: self->monsterinfo.run (self);
104: }
105: M_ChangeYaw (self);
106: ai_checkattack (self, 0);
107: }
108: else
109: FindTarget (self);
110: return;
111: }
112:
113: if (FindTarget (self))
114: return;
115:
116: if (level.time > self->monsterinfo.pausetime)
117: {
118: self->monsterinfo.walk (self);
119: return;
120: }
121:
122: if (!(self->spawnflags & 1) && (self->monsterinfo.idle) && (level.time > self->monsterinfo.idle_time))
123: {
124: if (self->monsterinfo.idle_time)
125: {
126: self->monsterinfo.idle (self);
127: self->monsterinfo.idle_time = level.time + 15 + random() * 15;
128: }
129: else
130: {
131: self->monsterinfo.idle_time = level.time + random() * 15;
132: }
133: }
134: }
135:
136:
137: /*
138: =============
139: ai_walk
140:
141: The monster is walking it's beat
142: =============
143: */
144: void ai_walk (edict_t *self, float dist)
145: {
146: M_MoveToGoal (self, dist);
147:
148: // check for noticing a player
149: if (FindTarget (self))
150: return;
151:
152: if ((self->monsterinfo.search) && (level.time > self->monsterinfo.idle_time))
153: {
154: if (self->monsterinfo.idle_time)
155: {
156: self->monsterinfo.search (self);
157: self->monsterinfo.idle_time = level.time + 15 + random() * 15;
158: }
159: else
160: {
161: self->monsterinfo.idle_time = level.time + random() * 15;
162: }
163: }
164: }
165:
166:
167: /*
168: =============
169: ai_charge
170:
171: Turns towards target and advances
172: Use this call with a distnace of 0 to replace ai_face
173: ==============
174: */
175: void ai_charge (edict_t *self, float dist)
176: {
177: vec3_t v;
178:
179: VectorSubtract (self->enemy->s.origin, self->s.origin, v);
180: self->ideal_yaw = vectoyaw(v);
181: M_ChangeYaw (self);
182:
183: if (dist)
184: M_walkmove (self, self->s.angles[YAW], dist);
185: }
186:
187:
188: /*
189: =============
190: ai_turn
191:
192: don't move, but turn towards ideal_yaw
193: Distance is for slight position adjustments needed by the animations
194: =============
195: */
196: void ai_turn (edict_t *self, float dist)
197: {
198: if (dist)
199: M_walkmove (self, self->s.angles[YAW], dist);
200:
201: if (FindTarget (self))
202: return;
203:
204: M_ChangeYaw (self);
205: }
206:
207:
208: /*
209:
210: .enemy
211: Will be world if not currently angry at anyone.
212:
213: .movetarget
214: The next path spot to walk toward. If .enemy, ignore .movetarget.
215: When an enemy is killed, the monster will try to return to it's path.
216:
217: .hunt_time
218: Set to time + something when the player is in sight, but movement straight for
219: him is blocked. This causes the monster to use wall following code for
220: movement direction instead of sighting on the player.
221:
222: .ideal_yaw
223: A yaw angle of the intended direction, which will be turned towards at up
224: to 45 deg / state. If the enemy is in view and hunt_time is not active,
225: this will be the exact line towards the enemy.
226:
227: .pausetime
228: A monster will leave it's stand state and head towards it's .movetarget when
229: time > .pausetime.
230:
231: walkmove(angle, speed) primitive is all or nothing
232: */
233:
234: /*
235: =============
236: range
237:
238: returns the range catagorization of an entity reletive to self
239: 0 melee range, will become hostile even if back is turned
240: 1 visibility and infront, or visibility and show hostile
241: 2 infront and show hostile
242: 3 only triggered by damage
243: =============
244: */
245: int range (edict_t *self, edict_t *other)
246: {
247: vec3_t v;
248: float len;
249:
250: VectorSubtract (self->s.origin, other->s.origin, v);
251: len = VectorLength (v);
252: if (len < MELEE_DISTANCE)
253: return RANGE_MELEE;
254: if (len < 500)
255: return RANGE_NEAR;
256: if (len < 1000)
257: return RANGE_MID;
258: return RANGE_FAR;
259: }
260:
261: /*
262: =============
263: visible
264:
265: returns 1 if the entity is visible to self, even if not infront ()
266: =============
267: */
268: qboolean visible (edict_t *self, edict_t *other)
269: {
270: vec3_t spot1;
271: vec3_t spot2;
272: trace_t trace;
273:
274: VectorCopy (self->s.origin, spot1);
275: spot1[2] += self->viewheight;
276: VectorCopy (other->s.origin, spot2);
277: spot2[2] += other->viewheight;
278: trace = gi.trace (spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE);
279:
280: if (trace.fraction == 1.0)
281: return true;
282: return false;
283: }
284:
285:
286: /*
287: =============
288: infront
289:
290: returns 1 if the entity is in front (in sight) of self
291: =============
292: */
293: qboolean infront (edict_t *self, edict_t *other)
294: {
295: vec3_t vec;
296: float dot;
297: vec3_t forward;
298:
299: AngleVectors (self->s.angles, forward, NULL, NULL);
300: VectorSubtract (other->s.origin, self->s.origin, vec);
301: VectorNormalize (vec);
302: dot = DotProduct (vec, forward);
303:
304: if (dot > 0.3)
305: return true;
306: return false;
307: }
308:
309:
310: //============================================================================
311:
312: void HuntTarget (edict_t *self)
313: {
314: vec3_t vec;
315:
316: self->goalentity = self->enemy;
317: if (self->monsterinfo.aiflags & AI_STAND_GROUND)
318: self->monsterinfo.stand (self);
319: else
320: self->monsterinfo.run (self);
321: VectorSubtract (self->enemy->s.origin, self->s.origin, vec);
322: self->ideal_yaw = vectoyaw(vec);
323: // wait a while before first attack
324: if (!(self->monsterinfo.aiflags & AI_STAND_GROUND))
325: AttackFinished (self, 1);
326: }
327:
328: void FoundTarget (edict_t *self)
329: {
330: // let other monsters see this monster for a while
331: if (self->enemy->client)
332: {
333: level.sight_entity = self;
334: level.sight_entity_framenum = level.framenum;
335: level.sight_entity->light_level = 128;
336: }
337:
338: self->show_hostile = level.time + 1; // wake up other monsters
339:
340: VectorCopy(self->enemy->s.origin, self->monsterinfo.last_sighting);
341: self->monsterinfo.trail_time = level.time;
342:
343: if (!self->combattarget)
344: {
345: HuntTarget (self);
346: return;
347: }
348:
349: self->goalentity = self->movetarget = G_PickTarget(self->combattarget);
350: if (!self->movetarget)
351: {
352: self->goalentity = self->movetarget = self->enemy;
353: HuntTarget (self);
354: gi.dprintf("%s at %s, combattarget %s not found\n", self->classname, vtos(self->s.origin), self->combattarget);
355: return;
356: }
357:
358: // clear out our combattarget, these are a one shot deal
359: self->combattarget = NULL;
360: self->monsterinfo.aiflags |= AI_COMBAT_POINT;
361:
362: // clear the targetname, that point is ours!
363: self->movetarget->targetname = NULL;
364: self->monsterinfo.pausetime = 0;
365:
366: // run for it
367: self->monsterinfo.run (self);
368: }
369:
370:
371: /*
372: ===========
373: FindTarget
374:
375: Self is currently not attacking anything, so try to find a target
376:
377: Returns TRUE if an enemy was sighted
378:
379: When a player fires a missile, the point of impact becomes a fakeplayer so
380: that monsters that see the impact will respond as if they had seen the
381: player.
382:
383: To avoid spending too much time, only a single client (or fakeclient) is
384: checked each frame. This means multi player games will have slightly
385: slower noticing monsters.
386: ============
387: */
388: qboolean FindTarget (edict_t *self)
389: {
390: edict_t *client;
391: qboolean heardit;
392: int r;
393:
394: if (self->monsterinfo.aiflags & AI_GOOD_GUY)
395: {
396: if (self->goalentity && self->goalentity->inuse && self->goalentity->classname)
397: {
398: if (strcmp(self->goalentity->classname, "target_actor") == 0)
399: return false;
400: }
401:
402: //FIXME look for monsters?
403: return false;
404: }
405:
406: // if we're going to a combat point, just proceed
407: if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
408: return false;
409:
410: // if the first spawnflag bit is set, the monster will only wake up on
411: // really seeing the player, not another monster getting angry or hearing
412: // something
413:
414: // revised behavior so they will wake up if they "see" a player make a noise
415: // but not weapon impact/explosion noises
416:
417: heardit = false;
418: if ((level.sight_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) )
419: {
420: client = level.sight_entity;
421: if (client->enemy == self->enemy)
422: {
423: return false;
424: }
425: }
426: else if (level.sound_entity_framenum >= (level.framenum - 1))
427: {
428: client = level.sound_entity;
429: heardit = true;
430: }
431: else if (!(self->enemy) && (level.sound2_entity_framenum >= (level.framenum - 1)) && !(self->spawnflags & 1) )
432: {
433: client = level.sound2_entity;
434: heardit = true;
435: }
436: else
437: {
438: client = level.sight_client;
439: if (!client)
440: return false; // no clients to get mad at
441: }
442:
443: // if the entity went away, forget it
444: if (!client->inuse)
445: return false;
446:
447: if (client == self->enemy)
448: return true; // JDC false;
449:
450: if (client->client)
451: {
452: if (client->flags & FL_NOTARGET)
453: return false;
454: }
455: else if (client->svflags & SVF_MONSTER)
456: {
457: if (!client->enemy)
458: return false;
459: if (client->enemy->flags & FL_NOTARGET)
460: return false;
461: }
462: else if (heardit)
463: {
464: if (client->owner->flags & FL_NOTARGET)
465: return false;
466: }
467: else
468: return false;
469:
470: if (!heardit)
471: {
472: r = range (self, client);
473:
474: if (r == RANGE_FAR)
475: return false;
476:
477: // this is where we would check invisibility
478:
479: // is client in an spot too dark to be seen?
480: if (client->light_level <= 5)
481: return false;
482:
483: if (!visible (self, client))
484: {
485: return false;
486: }
487:
488: if (r == RANGE_NEAR)
489: {
490: if (client->show_hostile < level.time && !infront (self, client))
491: {
492: return false;
493: }
494: }
495: else if (r == RANGE_MID)
496: {
497: if (!infront (self, client))
498: {
499: return false;
500: }
501: }
502:
503: self->enemy = client;
504:
505: if (strcmp(self->enemy->classname, "player_noise") != 0)
506: {
507: self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
508:
509: if (!self->enemy->client)
510: {
511: self->enemy = self->enemy->enemy;
512: if (!self->enemy->client)
513: {
514: self->enemy = NULL;
515: return false;
516: }
517: }
518: }
519: }
520: else // heardit
521: {
522: vec3_t temp;
523:
524: if (self->spawnflags & 1)
525: {
526: if (!visible (self, client))
527: return false;
528: }
529: else
530: {
531: if (!gi.inPHS(self->s.origin, client->s.origin))
532: return false;
533: }
534:
535: VectorSubtract (client->s.origin, self->s.origin, temp);
536:
537: if (VectorLength(temp) > 1000) // too far to hear
538: {
539: return false;
540: }
541:
542: // check area portals - if they are different and not connected then we can't hear it
543: if (client->areanum != self->areanum)
544: if (!gi.AreasConnected(self->areanum, client->areanum))
545: return false;
546:
547: self->ideal_yaw = vectoyaw(temp);
548: M_ChangeYaw (self);
549:
550: // hunt the sound for a bit; hopefully find the real player
551: self->monsterinfo.aiflags |= AI_SOUND_TARGET;
552: self->enemy = client;
553: }
554:
555: //
556: // got one
557: //
558: FoundTarget (self);
559:
560: if (!(self->monsterinfo.aiflags & AI_SOUND_TARGET) && (self->monsterinfo.sight))
561: self->monsterinfo.sight (self, self->enemy);
562:
563: return true;
564: }
565:
566:
567: //=============================================================================
568:
569: /*
570: ============
571: FacingIdeal
572:
573: ============
574: */
575: qboolean FacingIdeal(edict_t *self)
576: {
577: float delta;
578:
579: delta = anglemod(self->s.angles[YAW] - self->ideal_yaw);
580: if (delta > 45 && delta < 315)
581: return false;
582: return true;
583: }
584:
585:
586: //=============================================================================
587:
588: qboolean M_CheckAttack (edict_t *self)
589: {
590: vec3_t spot1, spot2;
591: float chance;
592: trace_t tr;
593:
594: if (self->enemy->health > 0)
595: {
596: // see if any entities are in the way of the shot
597: VectorCopy (self->s.origin, spot1);
598: spot1[2] += self->viewheight;
599: VectorCopy (self->enemy->s.origin, spot2);
600: spot2[2] += self->enemy->viewheight;
601:
602: tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_WINDOW);
603:
604: // do we have a clear shot?
605: if (tr.ent != self->enemy)
606: return false;
607: }
608:
609: // melee attack
610: if (enemy_range == RANGE_MELEE)
611: {
612: // don't always melee in easy mode
613: if (skill->value == 0 && (rand()&3) )
614: return false;
615: if (self->monsterinfo.melee)
616: self->monsterinfo.attack_state = AS_MELEE;
617: else
618: self->monsterinfo.attack_state = AS_MISSILE;
619: return true;
620: }
621:
622: // missile attack
623: if (!self->monsterinfo.attack)
624: return false;
625:
626: if (level.time < self->monsterinfo.attack_finished)
627: return false;
628:
629: if (enemy_range == RANGE_FAR)
630: return false;
631:
632: if (self->monsterinfo.aiflags & AI_STAND_GROUND)
633: {
634: chance = 0.4;
635: }
636: else if (enemy_range == RANGE_MELEE)
637: {
638: chance = 0.2;
639: }
640: else if (enemy_range == RANGE_NEAR)
641: {
642: chance = 0.1;
643: }
644: else if (enemy_range == RANGE_MID)
645: {
646: chance = 0.02;
647: }
648: else
649: {
650: return false;
651: }
652:
653: if (skill->value == 0)
654: chance *= 0.5;
655: else if (skill->value >= 2)
656: chance *= 2;
657:
658: if (random () < chance)
659: {
660: self->monsterinfo.attack_state = AS_MISSILE;
661: self->monsterinfo.attack_finished = level.time + 2*random();
662: return true;
663: }
664:
665: if (self->flags & FL_FLY)
666: {
667: if (random() < 0.3)
668: self->monsterinfo.attack_state = AS_SLIDING;
669: else
670: self->monsterinfo.attack_state = AS_STRAIGHT;
671: }
672:
673: return false;
674: }
675:
676:
677: /*
678: =============
679: ai_run_melee
680:
681: Turn and close until within an angle to launch a melee attack
682: =============
683: */
684: void ai_run_melee(edict_t *self)
685: {
686: self->ideal_yaw = enemy_yaw;
687: M_ChangeYaw (self);
688:
689: if (FacingIdeal(self))
690: {
691: self->monsterinfo.melee (self);
692: self->monsterinfo.attack_state = AS_STRAIGHT;
693: }
694: }
695:
696:
697: /*
698: =============
699: ai_run_missile
700:
701: Turn in place until within an angle to launch a missile attack
702: =============
703: */
704: void ai_run_missile(edict_t *self)
705: {
706: self->ideal_yaw = enemy_yaw;
707: M_ChangeYaw (self);
708:
709: if (FacingIdeal(self))
710: {
711: self->monsterinfo.attack (self);
712: self->monsterinfo.attack_state = AS_STRAIGHT;
713: }
714: };
715:
716:
717: /*
718: =============
719: ai_run_slide
720:
721: Strafe sideways, but stay at aproximately the same range
722: =============
723: */
724: void ai_run_slide(edict_t *self, float distance)
725: {
726: float ofs;
727:
728: self->ideal_yaw = enemy_yaw;
729: M_ChangeYaw (self);
730:
731: if (self->monsterinfo.lefty)
732: ofs = 90;
733: else
734: ofs = -90;
735:
736: if (M_walkmove (self, self->ideal_yaw + ofs, distance))
737: return;
738:
739: self->monsterinfo.lefty = 1 - self->monsterinfo.lefty;
740: M_walkmove (self, self->ideal_yaw - ofs, distance);
741: }
742:
743:
744: /*
745: =============
746: ai_checkattack
747:
748: Decides if we're going to attack or do something else
749: used by ai_run and ai_stand
750: =============
751: */
752: qboolean ai_checkattack (edict_t *self, float dist)
753: {
754: vec3_t temp;
755: qboolean hesDeadJim;
756:
757: // this causes monsters to run blindly to the combat point w/o firing
758: if (self->goalentity)
759: {
760: if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
761: return false;
762:
763: if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
764: {
765: if ((level.time - self->enemy->teleport_time) > 5.0)
766: {
767: if (self->goalentity == self->enemy)
768: if (self->movetarget)
769: self->goalentity = self->movetarget;
770: else
771: self->goalentity = NULL;
772: self->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
773: if (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND)
774: self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
775: }
776: else
777: {
778: self->show_hostile = level.time + 1;
779: return false;
780: }
781: }
782: }
783:
784: enemy_vis = false;
785:
786: // see if the enemy is dead
787: hesDeadJim = false;
788: if ((!self->enemy) || (!self->enemy->inuse))
789: {
790: hesDeadJim = true;
791: }
792: else if (self->monsterinfo.aiflags & AI_MEDIC)
793: {
794: if (self->enemy->health > 0)
795: {
796: hesDeadJim = true;
797: self->monsterinfo.aiflags &= ~AI_MEDIC;
798: }
799: }
800: else
801: {
802: if (self->monsterinfo.aiflags & AI_BRUTAL)
803: {
804: if (self->enemy->health <= -80)
805: hesDeadJim = true;
806: }
807: else
808: {
809: if (self->enemy->health <= 0)
810: hesDeadJim = true;
811: }
812: }
813:
814: if (hesDeadJim)
815: {
816: self->enemy = NULL;
817: // FIXME: look all around for other targets
818: if (self->oldenemy && self->oldenemy->health > 0)
819: {
820: self->enemy = self->oldenemy;
821: self->oldenemy = NULL;
822: HuntTarget (self);
823: }
824: else
825: {
826: if (self->movetarget)
827: {
828: self->goalentity = self->movetarget;
829: self->monsterinfo.walk (self);
830: }
831: else
832: {
833: // we need the pausetime otherwise the stand code
834: // will just revert to walking with no target and
835: // the monsters will wonder around aimlessly trying
836: // to hunt the world entity
837: self->monsterinfo.pausetime = level.time + 100000000;
838: self->monsterinfo.stand (self);
839: }
840: return true;
841: }
842: }
843:
844: self->show_hostile = level.time + 1; // wake up other monsters
845:
846: // check knowledge of enemy
847: enemy_vis = visible(self, self->enemy);
848: if (enemy_vis)
849: {
850: self->monsterinfo.search_time = level.time + 5;
851: VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
852: }
853:
854: // look for other coop players here
855: // if (coop && self->monsterinfo.search_time < level.time)
856: // {
857: // if (FindTarget (self))
858: // return true;
859: // }
860:
861: enemy_infront = infront(self, self->enemy);
862: enemy_range = range(self, self->enemy);
863: VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
864: enemy_yaw = vectoyaw(temp);
865:
866:
867: // JDC self->ideal_yaw = enemy_yaw;
868:
869: if (self->monsterinfo.attack_state == AS_MISSILE)
870: {
871: ai_run_missile (self);
872: return true;
873: }
874: if (self->monsterinfo.attack_state == AS_MELEE)
875: {
876: ai_run_melee (self);
877: return true;
878: }
879:
880: // if enemy is not currently visible, we will never attack
881: if (!enemy_vis)
882: return false;
883:
884: return self->monsterinfo.checkattack (self);
885: }
886:
887:
888: /*
889: =============
890: ai_run
891:
892: The monster has an enemy it is trying to kill
893: =============
894: */
895: void ai_run (edict_t *self, float dist)
896: {
897: vec3_t v;
898: edict_t *tempgoal;
899: edict_t *save;
900: qboolean new;
901: edict_t *marker;
902: float d1, d2;
903: trace_t tr;
904: vec3_t v_forward, v_right;
905: float left, center, right;
906: vec3_t left_target, right_target;
907:
908: // if we're going to a combat point, just proceed
909: if (self->monsterinfo.aiflags & AI_COMBAT_POINT)
910: {
911: M_MoveToGoal (self, dist);
912: return;
913: }
914:
915: if (self->monsterinfo.aiflags & AI_SOUND_TARGET)
916: {
917: VectorSubtract (self->s.origin, self->enemy->s.origin, v);
918: if (VectorLength(v) < 64)
919: {
920: self->monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
921: self->monsterinfo.stand (self);
922: return;
923: }
924:
925: M_MoveToGoal (self, dist);
926:
927: if (!FindTarget (self))
928: return;
929: }
930:
931: if (ai_checkattack (self, dist))
932: return;
933:
934: if (self->monsterinfo.attack_state == AS_SLIDING)
935: {
936: ai_run_slide (self, dist);
937: return;
938: }
939:
940: if (enemy_vis)
941: {
942: // if (self.aiflags & AI_LOST_SIGHT)
943: // dprint("regained sight\n");
944: M_MoveToGoal (self, dist);
945: self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
946: VectorCopy (self->enemy->s.origin, self->monsterinfo.last_sighting);
947: self->monsterinfo.trail_time = level.time;
948: return;
949: }
950:
951: // coop will change to another enemy if visible
952: if (coop->value)
953: { // FIXME: insane guys get mad with this, which causes crashes!
954: if (FindTarget (self))
955: return;
956: }
957:
958: if ((self->monsterinfo.search_time) && (level.time > (self->monsterinfo.search_time + 20)))
959: {
960: M_MoveToGoal (self, dist);
961: self->monsterinfo.search_time = 0;
962: // dprint("search timeout\n");
963: return;
964: }
965:
966: save = self->goalentity;
967: tempgoal = G_Spawn();
968: self->goalentity = tempgoal;
969:
970: new = false;
971:
972: if (!(self->monsterinfo.aiflags & AI_LOST_SIGHT))
973: {
974: // just lost sight of the player, decide where to go first
975: // dprint("lost sight of player, last seen at "); dprint(vtos(self.last_sighting)); dprint("\n");
976: self->monsterinfo.aiflags |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN);
977: self->monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP);
978: new = true;
979: }
980:
981: if (self->monsterinfo.aiflags & AI_PURSUE_NEXT)
982: {
983: self->monsterinfo.aiflags &= ~AI_PURSUE_NEXT;
984: // dprint("reached current goal: "); dprint(vtos(self.origin)); dprint(" "); dprint(vtos(self.last_sighting)); dprint(" "); dprint(ftos(vlen(self.origin - self.last_sighting))); dprint("\n");
985:
986: // give ourself more time since we got this far
987: self->monsterinfo.search_time = level.time + 5;
988:
989: if (self->monsterinfo.aiflags & AI_PURSUE_TEMP)
990: {
991: // dprint("was temp goal; retrying original\n");
992: self->monsterinfo.aiflags &= ~AI_PURSUE_TEMP;
993: marker = NULL;
994: VectorCopy (self->monsterinfo.saved_goal, self->monsterinfo.last_sighting);
995: new = true;
996: }
997: else if (self->monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN)
998: {
999: self->monsterinfo.aiflags &= ~AI_PURSUIT_LAST_SEEN;
1000: marker = PlayerTrail_PickFirst (self);
1001: }
1002: else
1003: {
1004: marker = PlayerTrail_PickNext (self);
1005: }
1006:
1007: if (marker)
1008: {
1009: VectorCopy (marker->s.origin, self->monsterinfo.last_sighting);
1010: self->monsterinfo.trail_time = marker->timestamp;
1011: self->s.angles[YAW] = self->ideal_yaw = marker->s.angles[YAW];
1012: // dprint("heading is "); dprint(ftos(self.ideal_yaw)); dprint("\n");
1013:
1014: // debug_drawline(self.origin, self.last_sighting, 52);
1015: new = true;
1016: }
1017: }
1018:
1019: VectorSubtract (self->s.origin, self->monsterinfo.last_sighting, v);
1020: d1 = VectorLength(v);
1021: if (d1 <= dist)
1022: {
1023: self->monsterinfo.aiflags |= AI_PURSUE_NEXT;
1024: dist = d1;
1025: }
1026:
1027: VectorCopy (self->monsterinfo.last_sighting, self->goalentity->s.origin);
1028:
1029: if (new)
1030: {
1031: // gi.dprintf("checking for course correction\n");
1032:
1033: tr = gi.trace(self->s.origin, self->mins, self->maxs, self->monsterinfo.last_sighting, self, MASK_PLAYERSOLID);
1034: if (tr.fraction < 1)
1035: {
1036: VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1037: d1 = VectorLength(v);
1038: center = tr.fraction;
1039: d2 = d1 * ((center+1)/2);
1040: self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1041: AngleVectors(self->s.angles, v_forward, v_right, NULL);
1042:
1043: VectorSet(v, d2, -16, 0);
1044: G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
1045: tr = gi.trace(self->s.origin, self->mins, self->maxs, left_target, self, MASK_PLAYERSOLID);
1046: left = tr.fraction;
1047:
1048: VectorSet(v, d2, 16, 0);
1049: G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
1050: tr = gi.trace(self->s.origin, self->mins, self->maxs, right_target, self, MASK_PLAYERSOLID);
1051: right = tr.fraction;
1052:
1053: center = (d1*center)/d2;
1054: if (left >= center && left > right)
1055: {
1056: if (left < 1)
1057: {
1058: VectorSet(v, d2 * left * 0.5, -16, 0);
1059: G_ProjectSource (self->s.origin, v, v_forward, v_right, left_target);
1060: // gi.dprintf("incomplete path, go part way and adjust again\n");
1061: }
1062: VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
1063: self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
1064: VectorCopy (left_target, self->goalentity->s.origin);
1065: VectorCopy (left_target, self->monsterinfo.last_sighting);
1066: VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1067: self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1068: // gi.dprintf("adjusted left\n");
1069: // debug_drawline(self.origin, self.last_sighting, 152);
1070: }
1071: else if (right >= center && right > left)
1072: {
1073: if (right < 1)
1074: {
1075: VectorSet(v, d2 * right * 0.5, 16, 0);
1076: G_ProjectSource (self->s.origin, v, v_forward, v_right, right_target);
1077: // gi.dprintf("incomplete path, go part way and adjust again\n");
1078: }
1079: VectorCopy (self->monsterinfo.last_sighting, self->monsterinfo.saved_goal);
1080: self->monsterinfo.aiflags |= AI_PURSUE_TEMP;
1081: VectorCopy (right_target, self->goalentity->s.origin);
1082: VectorCopy (right_target, self->monsterinfo.last_sighting);
1083: VectorSubtract (self->goalentity->s.origin, self->s.origin, v);
1084: self->s.angles[YAW] = self->ideal_yaw = vectoyaw(v);
1085: // gi.dprintf("adjusted right\n");
1086: // debug_drawline(self.origin, self.last_sighting, 152);
1087: }
1088: }
1089: // else gi.dprintf("course was fine\n");
1090: }
1091:
1092: M_MoveToGoal (self, dist);
1093:
1094: G_FreeEdict(tempgoal);
1095:
1096: if (self)
1097: self->goalentity = save;
1098: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.