5 float MONSTER_WANDER = 64; // disable wandering around
6 float MONSTER_APPEAR = 128; // spawn invisible, and appear when triggered
9 .float monsterawaitingteleport; // avoid awaking monsters in teleport rooms
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
16 float sight_entity_time;
21 Will be world if not currently angry at anyone.
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.
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.
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.
38 A monster will leave it's stand state and head towards it's .movetarget when
41 walkmove(angle, speed) primitive is all or nothing
50 float(float v) anglemod =
52 v = v - 360 * floor(v / 360);
57 ==============================================================================
61 The angle of the movetarget effects standing and bowing direction, but has no effect on movement, which allways heads to the next target.
64 must be present. The name of this movetarget.
67 the next spot to move to. If not present, stop here for good.
70 The number of seconds to spend standing or bowing for path_stand or path_bow
72 ==============================================================================
79 objerror ("monster_movetarget: no targetname");
81 self.solid = SOLID_TRIGGER;
82 self.touch = t_movetarget;
83 setsize (self, '-8 -8 -8', '8 8 8');
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.
98 Something has bumped into a movetarget. If it is a monster
99 moving towards it, change the next destination and continue.
102 void() t_movetarget =
106 if (other.health < 1)
108 if (other.movetarget != self)
112 return; // fighting, not following a path
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
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)
128 self.pausetime = time + 999999;
134 void() monster_wanderpaththink =
138 self.nextthink = time + random() * 10 + 1;
139 if (self.owner.health < 1) // dead, also handled in death code
141 self.owner.movetarget = world;
151 traceline(self.owner.origin, v * 1024 + self.owner.origin, FALSE, self);
152 v = trace_endpos - (normalize(v) * 16) - self.owner.origin;
159 setorigin(self, v1 + self.owner.origin);
160 self.owner.ideal_yaw = vectoyaw(self.origin - self.owner.origin);
163 void() monster_wanderpathtouch =
165 if (other.health < 1)
167 if (other.movetarget != self)
171 return; // fighting, not following a path
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
177 monster_wanderpaththink();
180 void() monster_spawnwanderpath =
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;
190 self.goalentity = self.movetarget = newmis;
193 void() monster_checkbossflag =
195 //#NO AUTOCVARS START
197 local float healthboost;
200 // monsterbosses cvar or spawnflag 64 causes a monster to be a miniboss
201 if ((self.spawnflags & 64) || (random() * 100 < cvar("monsterbosspercent")))
203 self.radsuit_finished = time + 1000000000;
207 self.super_damage_finished = time + 1000000000;
208 healthboost = 30 + self.health * 0.5;
209 self.effects = self.effects | (EF_FULLBRIGHT | EF_BLUE);
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));
217 self.health = self.health + healthboost;
218 self.max_health = self.health;
219 self.bodyhealth = self.bodyhealth * 2 + healthboost;
222 self.colormod_x = random();
223 self.colormod_y = random();
224 self.colormod_z = random();
225 self.colormod = normalize(self.colormod);
227 while (self.colormod_x > 0.6 && self.colormod_y > 0.6 && self.colormod_z > 0.6);
234 //============================================================================
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
247 float(entity targ) range =
250 r = vlen ((self.origin + self.view_ofs) - (targ.origin + targ.view_ofs));
255 if (r < 2000) // increased from 1000 for DP
264 returns 1 if the entity is visible to self, even if not infront ()
267 float (entity targ) visible =
269 if (vlen(targ.origin - self.origin) > 5000) // long traces are slow
272 traceline ((self.origin + self.view_ofs), (targ.origin + targ.view_ofs), TRUE, self); // see through other monsters
274 if (trace_inopen && trace_inwater)
275 return FALSE; // sight line crossed contents
277 if (trace_fraction == 1)
287 returns 1 if the entity is in front (in sight) of self
290 float(entity targ) infront =
294 makevectors (self.angles);
295 dot = normalize (targ.origin - self.origin) * v_forward;
299 // returns 0 if not infront, or the dotproduct if infront
300 float(vector dir, entity targ) infront2 =
304 dir = normalize(dir);
305 dot = normalize (targ.origin - self.origin) * dir;
307 if (dot >= 0.3) return dot; // infront
312 //============================================================================
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
327 local float ideal, move;
329 //current_yaw = self.ideal_yaw;
330 // mod down the current angle
331 current_yaw = anglemod( self.angles_y );
332 ideal = self.ideal_yaw;
334 if (current_yaw == ideal)
337 move = ideal - current_yaw;
338 if (ideal > current_yaw)
351 if (move > self.yaw_speed)
352 move = self.yaw_speed;
356 if (move < 0-self.yaw_speed )
357 move = 0-self.yaw_speed;
360 current_yaw = anglemod (current_yaw + move);
362 self.angles_y = current_yaw;
368 //============================================================================
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
379 .void() th_sightsound;
385 // skill 5 does not play sight sounds, instead you only hear the appear sound as they are about to attack
387 if (self.classname != "monster_hellfish")
390 if (self.th_sightsound)
391 self.th_sightsound();
396 if (self.health < 1 || !self.th_run)
398 if (self.enemy.health < 1 || !self.enemy.takedamage)
400 if (self.enemy.classname == "player")
402 // let other monsters see this monster for a while
404 sight_entity_time = time + 0.1;
407 self.show_hostile = time + 1; // wake up other monsters
414 //float checkplayertime;
415 entity lastcheckplayer;
416 entity havocbot_list;
419 entity() checkplayer =
422 local float worldcount;
423 // we can just fallback on checkclient if there are no bots
425 return checkclient();
428 if (time < checkplayertime)
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;
436 checkplayertime = time + 0.1;
439 check = lastcheckplayer;
445 check = findfloat(check, havocattack, TRUE);
446 if (check.classname == "player" || check.classname == "turretbase")
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;
454 else if (check == world)
456 worldcount = worldcount + 1;
458 return lastcheckplayer = check;
461 while(check != lastcheckplayer && c < 100);
470 Self is currently not attacking anything, so try to find a target
472 Returns TRUE if an enemy was sighted
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
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.
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
495 if (self.spawnflags & 3)
497 // don't wake up on seeing another monster getting angry
498 client = checkclient ();
500 return FALSE; // current check entity isn't in PVS
504 if (sight_entity_time >= time)
506 client = sight_entity;
507 if (client.enemy == self.enemy)
512 client = checkclient ();
514 return FALSE; // current check entity isn't in PVS
518 if (client == self.enemy)
521 if (client.flags & FL_NOTARGET)
525 if (client.items & IT_INVISIBILITY)
529 // on skill 5 the monsters usually ignore the player and remain ghostlike
531 if (self.classname != "monster_hellfish")
539 if (!visible (client))
544 if (client.show_hostile < time && !infront (client))
547 else if (r == RANGE_MID)
549 // LordHavoc: was if ( /* client.show_hostile < time || */ !infront (client))
550 if (client.show_hostile < time && !infront (client))
558 if (client.model == "")
561 if (self.enemy.classname != "player" && self.enemy.classname != "turretbase")
563 self.enemy = self.enemy.enemy;
564 if (self.enemy.classname != "player" && self.enemy.classname != "turretbase")
577 //=============================================================================
579 void(float dist) ai_forward =
581 walkmove (self.angles_y, dist);
584 void(float dist) ai_back =
586 walkmove ( (self.angles_y+180), dist);
590 void(float a) monster_setalpha;
599 void(float dist) ai_pain =
613 void(float dist) ai_painforward =
617 walkmove (self.ideal_yaw, dist);
624 The monster is walking it's beat
627 void(float dist) ai_walk =
634 // check for noticing a player
635 if (self.oldenemy.takedamage)
636 if (self.oldenemy.health >= 1)
638 self.enemy = self.oldenemy;
639 self.oldenemy = world;
646 if (self.enemy.takedamage)
648 if (self.enemy.health >= 1)
661 self.findtarget = TRUE;
672 The monster is staying in one place for a while, with slight angle turns
681 if (self.enemy.takedamage)
683 if (self.enemy.health >= 1)
695 self.findtarget = TRUE;
697 if (time > self.pausetime)
704 // change angle slightly
713 don't move, but turn towards ideal_yaw
720 if (self.enemy.takedamage)
722 if (self.enemy.health >= 1)
734 self.findtarget = TRUE;
740 //=============================================================================
747 void(vector pDestvec) ChooseTurn =
749 local vector dir, newdir;
751 dir = self.origin - pDestvec;
753 newdir_x = trace_plane_normal_y;
754 newdir_y = 0 - trace_plane_normal_x;
757 if (dir * newdir > 0)
759 dir_x = 0 - trace_plane_normal_y;
760 dir_y = trace_plane_normal_x;
764 dir_x = trace_plane_normal_y;
765 dir_y = 0 - trace_plane_normal_x;
769 self.ideal_yaw = vectoyaw(dir);
778 float() FacingIdeal =
782 delta = anglemod(self.angles_y - self.ideal_yaw);
783 if (delta > 45 && delta < 315)
789 //=============================================================================
791 .float() th_checkattack;
799 The monster has an enemy it is trying to kill
802 void(float dist) ai_run =
808 // see if the enemy is dead
809 if (self.enemy.health < 1 || self.enemy.takedamage == DAMAGE_NO)
812 // FIXME: look all around for other targets
813 if (self.oldenemy.health >= 1 && self.oldenemy.takedamage)
815 self.enemy = self.oldenemy;
816 self.oldenemy = world;
829 // wake up other monsters
830 self.show_hostile = time + 1;
832 // check knowledge of enemy
833 enemy_range = range(self.enemy);
835 self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin);
838 if (self.attack_state == AS_MELEE)
840 //dprint ("ai_run_melee\n");
841 //Turn and close until within an angle to launch a melee attack
845 self.attack_state = AS_STRAIGHT;
849 else if (self.attack_state == AS_MISSILE)
851 //dprint ("ai_run_missile\n");
852 //Turn in place until within an angle to launch a missile attack
854 if (self.th_missile ())
855 self.attack_state = AS_STRAIGHT;
859 if (self.th_checkattack())
860 return; // beginning an attack
862 if (visible(self.enemy))
863 self.search_time = time + 5;
866 // look for other coop players
867 if (self.search_time < time)
868 self.findtarget = TRUE;
871 if (self.attack_state == AS_SLIDING)
873 //dprint ("ai_run_slide\n");
874 //Strafe sideways, but stay at aproximately the same range
880 if (walkmove (self.ideal_yaw + ofs, movedist))
883 self.lefty = !self.lefty;
885 walkmove (self.ideal_yaw - ofs, movedist);
889 movetogoal (dist); // done in C code...