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