]> de.git.xonotic.org Git - voretournament/voretournament.git/blob - data/qcsrc/server/monsters/ai.qc
Set good crosshair defaults for the new crosshairs
[voretournament/voretournament.git] / data / qcsrc / server / monsters / ai.qc
1 void() movetarget_f;\r
2 void() t_movetarget;\r
3 void() FoundTarget;\r
4 \r
5 float MONSTER_WANDER = 64; // disable wandering around\r
6 float MONSTER_APPEAR = 128; // spawn invisible, and appear when triggered\r
7 \r
8 .float ismonster;\r
9 .float monsterawaitingteleport; // avoid awaking monsters in teleport rooms\r
10 \r
11 // when a monster becomes angry at a player, that monster will be used\r
12 // as the sight target the next frame so that monsters near that one\r
13 // will wake up even if they wouldn't have noticed the player\r
14 //\r
15 entity sight_entity;\r
16 float sight_entity_time;\r
17 \r
18 /*\r
19 \r
20 .enemy\r
21 Will be world if not currently angry at anyone.\r
22 \r
23 .movetarget\r
24 The next path spot to walk toward.  If .enemy, ignore .movetarget.\r
25 When an enemy is killed, the monster will try to return to it's path.\r
26 \r
27 .huntt_ime\r
28 Set to time + something when the player is in sight, but movement straight for\r
29 him is blocked.  This causes the monster to use wall following code for\r
30 movement direction instead of sighting on the player.\r
31 \r
32 .ideal_yaw\r
33 A yaw angle of the intended direction, which will be turned towards at up\r
34 to 45 deg / state.  If the enemy is in view and hunt_time is not active,\r
35 this will be the exact line towards the enemy.\r
36 \r
37 .pausetime\r
38 A monster will leave it's stand state and head towards it's .movetarget when\r
39 time > .pausetime.\r
40 \r
41 walkmove(angle, speed) primitive is all or nothing\r
42 */\r
43 \r
44 \r
45 //\r
46 // globals\r
47 //\r
48 //float current_yaw;\r
49 \r
50 float(float v) anglemod =\r
51 {\r
52         v = v - 360 * floor(v / 360);\r
53         return v;\r
54 };\r
55 \r
56 /*\r
57 ==============================================================================\r
58 \r
59 MOVETARGET CODE\r
60 \r
61 The angle of the movetarget effects standing and bowing direction, but has no effect on movement, which allways heads to the next target.\r
62 \r
63 targetname\r
64 must be present.  The name of this movetarget.\r
65 \r
66 target\r
67 the next spot to move to.  If not present, stop here for good.\r
68 \r
69 pausetime\r
70 The number of seconds to spend standing or bowing for path_stand or path_bow\r
71 \r
72 ==============================================================================\r
73 */\r
74 \r
75 \r
76 void() movetarget_f =\r
77 {\r
78         if (!self.targetname)\r
79                 objerror ("monster_movetarget: no targetname");\r
80 \r
81         self.solid = SOLID_TRIGGER;\r
82         self.touch = t_movetarget;\r
83         setsize (self, '-8 -8 -8', '8 8 8');\r
84 };\r
85 \r
86 /*QUAKED path_corner (0.5 0.3 0) (-8 -8 -8) (8 8 8)\r
87 Monsters will continue walking towards the next target corner.\r
88 */\r
89 void() path_corner =\r
90 {\r
91         movetarget_f ();\r
92 };\r
93 \r
94 /*\r
95 =============\r
96 t_movetarget\r
97 \r
98 Something has bumped into a movetarget.  If it is a monster\r
99 moving towards it, change the next destination and continue.\r
100 ==============\r
101 */\r
102 void() t_movetarget =\r
103 {\r
104         local entity temp;\r
105 \r
106         if (other.health < 1)\r
107                 return;\r
108         if (other.movetarget != self)\r
109                 return;\r
110 \r
111         if (other.enemy)\r
112                 return;         // fighting, not following a path\r
113 \r
114         temp = self;\r
115         self = other;\r
116         other = temp;\r
117 \r
118         if (self.classname == "monster_ogre")\r
119                 sound (self, CHAN_VOICE, "ogre/ogdrag.wav", 1, ATTN_IDLE);// play chainsaw drag sound\r
120 \r
121 //dprint ("t_movetarget\n");\r
122         self.goalentity = self.movetarget = find (world, targetname, other.target);\r
123         self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);\r
124         if (!self.movetarget)\r
125         {\r
126                 self.pausetime = time + 999999;\r
127                 self.th_stand ();\r
128                 return;\r
129         }\r
130 };\r
131 \r
132 void() monster_wanderpaththink =\r
133 {\r
134         local vector v, v1;\r
135         local float b, c;\r
136         self.nextthink = time + random() * 10 + 1;\r
137         if (self.owner.health < 1) // dead, also handled in death code\r
138         {\r
139                 self.owner.movetarget = world;\r
140                 remove(self);\r
141                 return;\r
142         }\r
143         b = -1;\r
144         c = 10;\r
145         while (c > 0)\r
146         {\r
147                 c = c - 1;\r
148                 v = randomvec();\r
149                 traceline(self.owner.origin, v * 1024 + self.owner.origin, FALSE, self);\r
150                 v = trace_endpos - (normalize(v) * 16) - self.owner.origin;\r
151                 if (vlen(v) > b)\r
152                 {\r
153                         b = vlen(v);\r
154                         v1 = v;\r
155                 }\r
156         }\r
157         setorigin(self, v1 + self.owner.origin);\r
158         self.owner.ideal_yaw = vectoyaw(self.origin - self.owner.origin);\r
159 };\r
160 \r
161 void() monster_wanderpathtouch =\r
162 {\r
163         if (other.health < 1)\r
164                 return;\r
165         if (other.movetarget != self)\r
166                 return;\r
167 \r
168         if (other.enemy)\r
169                 return;         // fighting, not following a path\r
170 \r
171         if (other.classname == "monster_ogre")\r
172                 sound (other, CHAN_VOICE, "ogre/ogdrag.wav", 1, ATTN_IDLE);// play chainsaw drag sound\r
173         monster_wanderpaththink();\r
174 };\r
175 \r
176 void() monster_spawnwanderpath =\r
177 {\r
178         newmis = spawn();\r
179         newmis.classname = "monster_wanderpath";\r
180         newmis.solid = SOLID_TRIGGER;\r
181         newmis.touch = monster_wanderpathtouch;\r
182         setsize (newmis, '-8 -8 -8', '8 8 8');\r
183         newmis.think = monster_wanderpaththink;\r
184         newmis.nextthink = time + random() * 10 + 1;\r
185         newmis.owner = self;\r
186         self.goalentity = self.movetarget = newmis;\r
187 };\r
188 \r
189 void() monster_checkbossflag =\r
190 {\r
191 #if 0\r
192         local float healthboost;\r
193         local float r;\r
194 \r
195         // monsterbosses cvar or spawnflag 64 causes a monster to be a miniboss\r
196         if ((self.spawnflags & 64) || (random() * 100 < cvar("monsterbosspercent")))\r
197         {\r
198                 self.radsuit_finished = time + 1000000000;\r
199                 r = random() * 4;\r
200                 if (r < 2)\r
201                 {\r
202                         self.super_damage_finished = time + 1000000000;\r
203                         healthboost = 30 + self.health * 0.5;\r
204                         self.effects = self.effects | (EF_FULLBRIGHT | EF_BLUE);\r
205                 }\r
206                 if (r >= 1)\r
207                 {\r
208                         healthboost = 30 + self.health * bound(0.5, skill * 0.5, 1.5);\r
209                         self.effects = self.effects | (EF_FULLBRIGHT | EF_RED);\r
210                         self.healthregen = max(self.healthregen, min(skill * 10, 30));\r
211                 }\r
212                 self.health = self.health + healthboost;\r
213                 self.max_health = self.health;\r
214                 self.bodyhealth = self.bodyhealth * 2 + healthboost;\r
215                 do\r
216                 {\r
217                         self.colormod_x = random();\r
218                         self.colormod_y = random();\r
219                         self.colormod_z = random();\r
220                         self.colormod =  normalize(self.colormod);\r
221                 }\r
222                 while (self.colormod_x > 0.6 && self.colormod_y > 0.6 && self.colormod_z > 0.6);\r
223         }\r
224 #endif\r
225 };\r
226 \r
227 \r
228 //============================================================================\r
229 \r
230 /*\r
231 =============\r
232 range\r
233 \r
234 returns the range catagorization of an entity reletive to self\r
235 0       melee range, will become hostile even if back is turned\r
236 1       visibility and infront, or visibility and show hostile\r
237 2       infront and show hostile\r
238 3       only triggered by damage\r
239 =============\r
240 */\r
241 float(entity targ) range =\r
242 {\r
243         local float r;\r
244         r = vlen ((self.origin + self.view_ofs) - (targ.origin + targ.view_ofs));\r
245         if (r < 120)\r
246                 return RANGE_MELEE;\r
247         if (r < 500)\r
248                 return RANGE_NEAR;\r
249         if (r < 2000) // increased from 1000 for DP\r
250                 return RANGE_MID;\r
251         return RANGE_FAR;\r
252 };\r
253 \r
254 /*\r
255 =============\r
256 visible\r
257 \r
258 returns 1 if the entity is visible to self, even if not infront ()\r
259 =============\r
260 */\r
261 float (entity targ) visible =\r
262 {\r
263         if (vlen(targ.origin - self.origin) > 5000) // long traces are slow\r
264                 return FALSE;\r
265 \r
266         traceline ((self.origin + self.view_ofs), (targ.origin + targ.view_ofs), TRUE, self);   // see through other monsters\r
267 \r
268         if (trace_inopen && trace_inwater)\r
269                 return FALSE;                   // sight line crossed contents\r
270 \r
271         if (trace_fraction == 1)\r
272                 return TRUE;\r
273         return FALSE;\r
274 };\r
275 \r
276 \r
277 /*\r
278 =============\r
279 infront\r
280 \r
281 returns 1 if the entity is in front (in sight) of self\r
282 =============\r
283 */\r
284 float(entity targ) infront =\r
285 {\r
286         local float dot;\r
287 \r
288         makevectors (self.angles);\r
289         dot = normalize (targ.origin - self.origin) * v_forward;\r
290 \r
291         return (dot > 0.3);\r
292 };\r
293 // returns 0 if not infront, or the dotproduct if infront\r
294 float(vector dir, entity targ) infront2 =\r
295 {\r
296         local float dot;\r
297 \r
298         dir = normalize(dir);\r
299         dot = normalize (targ.origin - self.origin) * dir;\r
300 \r
301         if (dot >= 0.3) return dot; // infront\r
302         return 0;\r
303 };\r
304 \r
305 \r
306 //============================================================================\r
307 \r
308 /*\r
309 ===========\r
310 ChangeYaw\r
311 \r
312 Turns towards self.ideal_yaw at self.yaw_speed\r
313 Sets the global variable current_yaw\r
314 Called every 0.1 sec by monsters\r
315 ============\r
316 */\r
317 /*\r
318 \r
319 void() ChangeYaw =\r
320 {\r
321         local float ideal, move;\r
322 \r
323 //current_yaw = self.ideal_yaw;\r
324 // mod down the current angle\r
325         current_yaw = anglemod( self.angles_y );\r
326         ideal = self.ideal_yaw;\r
327 \r
328         if (current_yaw == ideal)\r
329                 return;\r
330 \r
331         move = ideal - current_yaw;\r
332         if (ideal > current_yaw)\r
333         {\r
334                 if (move > 180)\r
335                         move = move - 360;\r
336         }\r
337         else\r
338         {\r
339                 if (move < -180)\r
340                         move = move + 360;\r
341         }\r
342 \r
343         if (move > 0)\r
344         {\r
345                 if (move > self.yaw_speed)\r
346                         move = self.yaw_speed;\r
347         }\r
348         else\r
349         {\r
350                 if (move < 0-self.yaw_speed )\r
351                         move = 0-self.yaw_speed;\r
352         }\r
353 \r
354         current_yaw = anglemod (current_yaw + move);\r
355 \r
356         self.angles_y = current_yaw;\r
357 };\r
358 \r
359 */\r
360 \r
361 \r
362 //============================================================================\r
363 \r
364 void() HuntTarget =\r
365 {\r
366         self.goalentity = self.enemy;\r
367         self.think = self.th_run;\r
368         self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin);\r
369         self.nextthink = time + 0.1;\r
370         SUB_AttackFinished (1); // wait a while before first attack\r
371 };\r
372 \r
373 .void() th_sightsound;\r
374 \r
375 void() SightSound =\r
376 {\r
377         if (self.health < 1)\r
378                 return;\r
379         // skill 5 does not play sight sounds, instead you only hear the appear sound as they are about to attack\r
380         if (skill >= 5)\r
381         if (self.classname != "monster_hellfish")\r
382                 return;\r
383 \r
384         if (self.th_sightsound)\r
385                 self.th_sightsound();\r
386 };\r
387 \r
388 void() FoundTarget =\r
389 {\r
390         if (self.health < 1 || !self.th_run)\r
391                 return;\r
392         if (self.enemy.health < 1 || !self.enemy.takedamage)\r
393                 return;\r
394         if (self.enemy.classname == "player")\r
395         {\r
396                 // let other monsters see this monster for a while\r
397                 sight_entity = self;\r
398                 sight_entity_time = time + 0.1;\r
399         }\r
400 \r
401         self.show_hostile = time + 1;           // wake up other monsters\r
402 \r
403         SightSound ();\r
404         HuntTarget ();\r
405 };\r
406 \r
407 /*\r
408 //float checkplayertime;\r
409 entity lastcheckplayer;\r
410 entity havocbot_list;\r
411 \r
412 \r
413 entity() checkplayer =\r
414 {\r
415         local entity check;\r
416         local float worldcount;\r
417         // we can just fallback on checkclient if there are no bots\r
418         if (!havocbot_list)\r
419                 return checkclient();\r
420 */\r
421         /*\r
422         if (time < checkplayertime)\r
423         {\r
424                 traceline(self.origin + self.view_ofs, lastcheckplayer.origin + lastcheckplayer.view_ofs, TRUE, self);\r
425                 if (trace_fraction == 1)\r
426                         return lastcheckplayer;\r
427                 if (trace_ent == lastcheckplayer)\r
428                         return lastcheckplayer;\r
429         }\r
430         checkplayertime = time + 0.1;\r
431         */\r
432 /*\r
433         check = lastcheckplayer;\r
434         worldcount = 0;\r
435         c = 0;\r
436         do\r
437         {\r
438                 c = c + 1;\r
439                 check = findfloat(check, havocattack, TRUE);\r
440                 if (check.classname == "player" || check.classname == "turretbase")\r
441                 {\r
442                         traceline(self.origin + self.view_ofs, check.origin + check.view_ofs, TRUE, self);\r
443                         if (trace_fraction == 1)\r
444                                 return lastcheckplayer = check;\r
445                         if (trace_ent == check)\r
446                                 return lastcheckplayer = check;\r
447                 }\r
448                 else if (check == world)\r
449                 {\r
450                         worldcount = worldcount + 1;\r
451                         if (worldcount >= 2)\r
452                                 return lastcheckplayer = check;\r
453                 }\r
454         }\r
455         while(check != lastcheckplayer && c < 100);\r
456         return world;\r
457 };\r
458 */\r
459 \r
460 /*\r
461 ===========\r
462 FindTarget\r
463 \r
464 Self is currently not attacking anything, so try to find a target\r
465 \r
466 Returns TRUE if an enemy was sighted\r
467 \r
468 When a player fires a missile, the point of impact becomes a fakeplayer so\r
469 that monsters that see the impact will respond as if they had seen the\r
470 player.\r
471 \r
472 To avoid spending too much time, only a single client (or fakeclient) is\r
473 checked each frame.  This means multi player games will have slightly\r
474 slower noticing monsters.\r
475 ============\r
476 */\r
477 .float findtarget;\r
478 float() FindTarget =\r
479 {\r
480         local entity client;\r
481         local float r;\r
482 \r
483         if (self.health < 1)\r
484                 return FALSE;\r
485 \r
486         // if the first or second spawnflag bit is set, the monster will only\r
487         // wake up on really seeing the player, not another monster getting angry\r
488 \r
489         if (self.spawnflags & 3)\r
490         {\r
491                 // don't wake up on seeing another monster getting angry\r
492                 client = checkclient ();\r
493                 if (!client)\r
494                         return FALSE;   // current check entity isn't in PVS\r
495         }\r
496         else\r
497         {\r
498                 if (sight_entity_time >= time)\r
499                 {\r
500                         client = sight_entity;\r
501                         if (client.enemy == self.enemy)\r
502                                 return TRUE;\r
503                 }\r
504                 else\r
505                 {\r
506                         client = checkclient ();\r
507                         if (!client)\r
508                                 return FALSE;   // current check entity isn't in PVS\r
509                 }\r
510         }\r
511 \r
512         if (client == self.enemy)\r
513                 return FALSE;\r
514 \r
515         if (client.flags & FL_NOTARGET)\r
516                 return FALSE;\r
517 \r
518 #if 0\r
519         if (client.items & IT_INVISIBILITY)\r
520                 return FALSE;\r
521 #endif\r
522 \r
523         // on skill 5 the monsters usually ignore the player and remain ghostlike\r
524         if (skill >= 5)\r
525         if (self.classname != "monster_hellfish")\r
526         if (random() < 0.99)\r
527                 return FALSE;\r
528 \r
529         r = range(client);\r
530         if (r == RANGE_FAR)\r
531                 return FALSE;\r
532 \r
533         if (!visible (client))\r
534                 return FALSE;\r
535 \r
536         if (r == RANGE_NEAR)\r
537         {\r
538                 if (client.show_hostile < time && !infront (client))\r
539                         return FALSE;\r
540         }\r
541         else if (r == RANGE_MID)\r
542         {\r
543                 // LordHavoc: was if ( /* client.show_hostile < time || */ !infront (client))\r
544                 if (client.show_hostile < time && !infront (client))\r
545                         return FALSE;\r
546         }\r
547 \r
548         //\r
549         // got one\r
550         //\r
551 \r
552         if (client.model == "")\r
553                 return FALSE;\r
554         self.enemy = client;\r
555         if (self.enemy.classname != "player" && self.enemy.classname != "turretbase")\r
556         {\r
557                 self.enemy = self.enemy.enemy;\r
558                 if (self.enemy.classname != "player" && self.enemy.classname != "turretbase")\r
559                 {\r
560                         self.enemy = world;\r
561                         return FALSE;\r
562                 }\r
563         }\r
564 \r
565         FoundTarget ();\r
566 \r
567         return TRUE;\r
568 };\r
569 \r
570 \r
571 //=============================================================================\r
572 \r
573 void(float dist) ai_forward =\r
574 {\r
575         walkmove (self.angles_y, dist);\r
576 };\r
577 \r
578 void(float dist) ai_back =\r
579 {\r
580         walkmove ( (self.angles_y+180), dist);\r
581 };\r
582 \r
583 \r
584 void(float a) monster_setalpha;\r
585 \r
586 /*\r
587 =============\r
588 ai_pain\r
589 \r
590 stagger back a bit\r
591 =============\r
592 */\r
593 void(float dist) ai_pain =\r
594 {\r
595         if (self.health < 1)\r
596                 return;\r
597         ai_back (dist);\r
598 };\r
599 \r
600 /*\r
601 =============\r
602 ai_painforward\r
603 \r
604 stagger back a bit\r
605 =============\r
606 */\r
607 void(float dist) ai_painforward =\r
608 {\r
609         if (self.health < 1)\r
610                 return;\r
611         walkmove (self.ideal_yaw, dist);\r
612 };\r
613 \r
614 /*\r
615 =============\r
616 ai_walk\r
617 \r
618 The monster is walking it's beat\r
619 =============\r
620 */\r
621 void(float dist) ai_walk =\r
622 {\r
623         if (self.health < 1)\r
624                 return;\r
625 \r
626         movedist = dist;\r
627 \r
628         // check for noticing a player\r
629         if (self.oldenemy.takedamage)\r
630         if (self.oldenemy.health >= 1)\r
631         {\r
632                 self.enemy = self.oldenemy;\r
633                 self.oldenemy = world;\r
634                 FoundTarget();\r
635                 monster_setalpha(0);\r
636                 return;\r
637         }\r
638         if (self.enemy)\r
639         {\r
640                 if (self.enemy.takedamage)\r
641                 {\r
642                         if (self.enemy.health >= 1)\r
643                         {\r
644                                 FoundTarget();\r
645                                 monster_setalpha(0);\r
646                                 return;\r
647                         }\r
648                         else\r
649                                 self.enemy = world;\r
650                 }\r
651                 else\r
652                         self.enemy = world;\r
653         }\r
654 \r
655         self.findtarget = TRUE;\r
656 \r
657         movetogoal (dist);\r
658         monster_setalpha(0);\r
659 };\r
660 \r
661 \r
662 /*\r
663 =============\r
664 ai_stand\r
665 \r
666 The monster is staying in one place for a while, with slight angle turns\r
667 =============\r
668 */\r
669 void() ai_stand =\r
670 {\r
671         if (self.health < 1)\r
672                 return;\r
673         if (self.enemy)\r
674         {\r
675                 if (self.enemy.takedamage)\r
676                 {\r
677                         if (self.enemy.health >= 1)\r
678                         {\r
679                                 FoundTarget();\r
680                                 monster_setalpha(0);\r
681                                 return;\r
682                         }\r
683                         else\r
684                                 self.enemy = world;\r
685                 }\r
686                 else\r
687                         self.enemy = world;\r
688         }\r
689         self.findtarget = TRUE;\r
690 \r
691         if (time > self.pausetime)\r
692         {\r
693                 self.th_walk ();\r
694                 monster_setalpha(0);\r
695                 return;\r
696         }\r
697 \r
698 // change angle slightly\r
699 \r
700         monster_setalpha(0);\r
701 };\r
702 \r
703 /*\r
704 =============\r
705 ai_turn\r
706 \r
707 don't move, but turn towards ideal_yaw\r
708 =============\r
709 */\r
710 void() ai_turn =\r
711 {\r
712         if (self.enemy)\r
713         {\r
714                 if (self.enemy.takedamage)\r
715                 {\r
716                         if (self.enemy.health >= 1)\r
717                         {\r
718                                 FoundTarget();\r
719                                 monster_setalpha(0);\r
720                                 return;\r
721                         }\r
722                         else\r
723                                 self.enemy = world;\r
724                 }\r
725                 else\r
726                         self.enemy = world;\r
727         }\r
728         self.findtarget = TRUE;\r
729 \r
730         ChangeYaw ();\r
731         monster_setalpha(0);\r
732 };\r
733 \r
734 //=============================================================================\r
735 \r
736 /*\r
737 =============\r
738 ChooseTurn\r
739 =============\r
740 */\r
741 void(vector pDestvec) ChooseTurn =\r
742 {\r
743         local vector dir, newdir;\r
744 \r
745         dir = self.origin - pDestvec;\r
746 \r
747         newdir_x = trace_plane_normal_y;\r
748         newdir_y = 0 - trace_plane_normal_x;\r
749         newdir_z = 0;\r
750 \r
751         if (dir * newdir > 0)\r
752         {\r
753                 dir_x = 0 - trace_plane_normal_y;\r
754                 dir_y = trace_plane_normal_x;\r
755         }\r
756         else\r
757         {\r
758                 dir_x = trace_plane_normal_y;\r
759                 dir_y = 0 - trace_plane_normal_x;\r
760         }\r
761 \r
762         dir_z = 0;\r
763         self.ideal_yaw = vectoyaw(dir);\r
764 };\r
765 \r
766 /*\r
767 ============\r
768 FacingIdeal\r
769 \r
770 ============\r
771 */\r
772 float() FacingIdeal =\r
773 {\r
774         local float delta;\r
775 \r
776         delta = anglemod(self.angles_y - self.ideal_yaw);\r
777         if (delta > 45 && delta < 315)\r
778                 return FALSE;\r
779         return TRUE;\r
780 };\r
781 \r
782 \r
783 //=============================================================================\r
784 \r
785 .float() th_checkattack;\r
786 \r
787 \r
788 \r
789 /*\r
790 =============\r
791 ai_run\r
792 \r
793 The monster has an enemy it is trying to kill\r
794 =============\r
795 */\r
796 void(float dist) ai_run =\r
797 {\r
798         local float ofs;\r
799         if (self.health < 1)\r
800                 return;\r
801         movedist = dist;\r
802         // see if the enemy is dead\r
803         if (self.enemy.health < 1 || self.enemy.takedamage == DAMAGE_NO)\r
804         {\r
805                 self.enemy = world;\r
806                 // FIXME: look all around for other targets\r
807                 if (self.oldenemy.health >= 1 && self.oldenemy.takedamage)\r
808                 {\r
809                         self.enemy = self.oldenemy;\r
810                         self.oldenemy = world;\r
811                         HuntTarget ();\r
812                 }\r
813                 else\r
814                 {\r
815                         if (self.movetarget)\r
816                                 self.th_walk ();\r
817                         else\r
818                                 self.th_stand ();\r
819                         return;\r
820                 }\r
821         }\r
822 \r
823         // wake up other monsters\r
824         self.show_hostile = time + 1;\r
825 \r
826         // check knowledge of enemy\r
827         enemy_range = range(self.enemy);\r
828 \r
829         self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin);\r
830         ChangeYaw ();\r
831 \r
832         if (self.attack_state == AS_MELEE)\r
833         {\r
834                 //dprint ("ai_run_melee\n");\r
835                 //Turn and close until within an angle to launch a melee attack\r
836                 if (FacingIdeal())\r
837                 {\r
838                         self.th_melee ();\r
839                         self.attack_state = AS_STRAIGHT;\r
840                 }\r
841                 return;\r
842         }\r
843         else if (self.attack_state == AS_MISSILE)\r
844         {\r
845                 //dprint ("ai_run_missile\n");\r
846                 //Turn in place until within an angle to launch a missile attack\r
847                 if (FacingIdeal())\r
848                 if (self.th_missile ())\r
849                         self.attack_state = AS_STRAIGHT;\r
850                 return;\r
851         }\r
852 \r
853         if (self.th_checkattack())\r
854                 return;                                 // beginning an attack\r
855 \r
856         if (visible(self.enemy))\r
857                 self.search_time = time + 5;\r
858         else if (coop)\r
859         {\r
860                 // look for other coop players\r
861                 if (self.search_time < time)\r
862                         self.findtarget = TRUE;\r
863         }\r
864 \r
865         if (self.attack_state == AS_SLIDING)\r
866         {\r
867                 //dprint ("ai_run_slide\n");\r
868                 //Strafe sideways, but stay at aproximately the same range\r
869                 if (self.lefty)\r
870                         ofs = 90;\r
871                 else\r
872                         ofs = -90;\r
873 \r
874                 if (walkmove (self.ideal_yaw + ofs, movedist))\r
875                         return;\r
876 \r
877                 self.lefty = !self.lefty;\r
878 \r
879                 walkmove (self.ideal_yaw - ofs, movedist);\r
880         }\r
881 \r
882         // head straight in\r
883         movetogoal (dist);              // done in C code...\r
884 };\r
885 \r