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 if (self.classname == "monster_ogre")
119 sound (self, CHAN_VOICE, "ogre/ogdrag.wav", 1, ATTN_IDLE);// play chainsaw drag sound
121 //dprint ("t_movetarget\n");
122 self.goalentity = self.movetarget = find (world, targetname, other.target);
123 self.ideal_yaw = vectoyaw(self.goalentity.origin - self.origin);
124 if (!self.movetarget)
126 self.pausetime = time + 999999;
132 void() monster_wanderpaththink =
136 self.nextthink = time + random() * 10 + 1;
137 if (self.owner.health < 1) // dead, also handled in death code
139 self.owner.movetarget = world;
149 traceline(self.owner.origin, v * 1024 + self.owner.origin, FALSE, self);
150 v = trace_endpos - (normalize(v) * 16) - self.owner.origin;
157 setorigin(self, v1 + self.owner.origin);
158 self.owner.ideal_yaw = vectoyaw(self.origin - self.owner.origin);
161 void() monster_wanderpathtouch =
163 if (other.health < 1)
165 if (other.movetarget != self)
169 return; // fighting, not following a path
171 if (other.classname == "monster_ogre")
172 sound (other, CHAN_VOICE, "ogre/ogdrag.wav", 1, ATTN_IDLE);// play chainsaw drag sound
173 monster_wanderpaththink();
176 void() monster_spawnwanderpath =
179 newmis.classname = "monster_wanderpath";
180 newmis.solid = SOLID_TRIGGER;
181 newmis.touch = monster_wanderpathtouch;
182 setsize (newmis, '-8 -8 -8', '8 8 8');
183 newmis.think = monster_wanderpaththink;
184 newmis.nextthink = time + random() * 10 + 1;
186 self.goalentity = self.movetarget = newmis;
189 void() monster_checkbossflag =
192 local float healthboost;
195 // monsterbosses cvar or spawnflag 64 causes a monster to be a miniboss
196 if ((self.spawnflags & 64) || (random() * 100 < cvar("monsterbosspercent")))
198 self.radsuit_finished = time + 1000000000;
202 self.super_damage_finished = time + 1000000000;
203 healthboost = 30 + self.health * 0.5;
204 self.effects = self.effects | (EF_FULLBRIGHT | EF_BLUE);
208 healthboost = 30 + self.health * bound(0.5, skill * 0.5, 1.5);
209 self.effects = self.effects | (EF_FULLBRIGHT | EF_RED);
210 self.healthregen = max(self.healthregen, min(skill * 10, 30));
212 self.health = self.health + healthboost;
213 self.max_health = self.health;
214 self.bodyhealth = self.bodyhealth * 2 + healthboost;
217 self.colormod_x = random();
218 self.colormod_y = random();
219 self.colormod_z = random();
220 self.colormod = normalize(self.colormod);
222 while (self.colormod_x > 0.6 && self.colormod_y > 0.6 && self.colormod_z > 0.6);
228 //============================================================================
234 returns the range catagorization of an entity reletive to self
235 0 melee range, will become hostile even if back is turned
236 1 visibility and infront, or visibility and show hostile
237 2 infront and show hostile
238 3 only triggered by damage
241 float(entity targ) range =
244 r = vlen ((self.origin + self.view_ofs) - (targ.origin + targ.view_ofs));
249 if (r < 2000) // increased from 1000 for DP
258 returns 1 if the entity is visible to self, even if not infront ()
261 float (entity targ) visible =
263 if (vlen(targ.origin - self.origin) > 5000) // long traces are slow
266 traceline ((self.origin + self.view_ofs), (targ.origin + targ.view_ofs), TRUE, self); // see through other monsters
268 if (trace_inopen && trace_inwater)
269 return FALSE; // sight line crossed contents
271 if (trace_fraction == 1)
281 returns 1 if the entity is in front (in sight) of self
284 float(entity targ) infront =
288 makevectors (self.angles);
289 dot = normalize (targ.origin - self.origin) * v_forward;
293 // returns 0 if not infront, or the dotproduct if infront
294 float(vector dir, entity targ) infront2 =
298 dir = normalize(dir);
299 dot = normalize (targ.origin - self.origin) * dir;
301 if (dot >= 0.3) return dot; // infront
306 //============================================================================
312 Turns towards self.ideal_yaw at self.yaw_speed
313 Sets the global variable current_yaw
314 Called every 0.1 sec by monsters
321 local float ideal, move;
323 //current_yaw = self.ideal_yaw;
324 // mod down the current angle
325 current_yaw = anglemod( self.angles_y );
326 ideal = self.ideal_yaw;
328 if (current_yaw == ideal)
331 move = ideal - current_yaw;
332 if (ideal > current_yaw)
345 if (move > self.yaw_speed)
346 move = self.yaw_speed;
350 if (move < 0-self.yaw_speed )
351 move = 0-self.yaw_speed;
354 current_yaw = anglemod (current_yaw + move);
356 self.angles_y = current_yaw;
362 //============================================================================
366 self.goalentity = self.enemy;
367 self.think = self.th_run;
368 self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin);
369 self.nextthink = time + 0.1;
370 SUB_AttackFinished (1); // wait a while before first attack
373 .void() th_sightsound;
379 // skill 5 does not play sight sounds, instead you only hear the appear sound as they are about to attack
381 if (self.classname != "monster_hellfish")
384 if (self.th_sightsound)
385 self.th_sightsound();
390 if (self.health < 1 || !self.th_run)
392 if (self.enemy.health < 1 || !self.enemy.takedamage)
394 if (self.enemy.classname == "player")
396 // let other monsters see this monster for a while
398 sight_entity_time = time + 0.1;
401 self.show_hostile = time + 1; // wake up other monsters
408 //float checkplayertime;
409 entity lastcheckplayer;
410 entity havocbot_list;
413 entity() checkplayer =
416 local float worldcount;
417 // we can just fallback on checkclient if there are no bots
419 return checkclient();
422 if (time < checkplayertime)
424 traceline(self.origin + self.view_ofs, lastcheckplayer.origin + lastcheckplayer.view_ofs, TRUE, self);
425 if (trace_fraction == 1)
426 return lastcheckplayer;
427 if (trace_ent == lastcheckplayer)
428 return lastcheckplayer;
430 checkplayertime = time + 0.1;
433 check = lastcheckplayer;
439 check = findfloat(check, havocattack, TRUE);
440 if (check.classname == "player" || check.classname == "turretbase")
442 traceline(self.origin + self.view_ofs, check.origin + check.view_ofs, TRUE, self);
443 if (trace_fraction == 1)
444 return lastcheckplayer = check;
445 if (trace_ent == check)
446 return lastcheckplayer = check;
448 else if (check == world)
450 worldcount = worldcount + 1;
452 return lastcheckplayer = check;
455 while(check != lastcheckplayer && c < 100);
464 Self is currently not attacking anything, so try to find a target
466 Returns TRUE if an enemy was sighted
468 When a player fires a missile, the point of impact becomes a fakeplayer so
469 that monsters that see the impact will respond as if they had seen the
472 To avoid spending too much time, only a single client (or fakeclient) is
473 checked each frame. This means multi player games will have slightly
474 slower noticing monsters.
486 // if the first or second spawnflag bit is set, the monster will only
487 // wake up on really seeing the player, not another monster getting angry
489 if (self.spawnflags & 3)
491 // don't wake up on seeing another monster getting angry
492 client = checkclient ();
494 return FALSE; // current check entity isn't in PVS
498 if (sight_entity_time >= time)
500 client = sight_entity;
501 if (client.enemy == self.enemy)
506 client = checkclient ();
508 return FALSE; // current check entity isn't in PVS
512 if (client == self.enemy)
515 if (client.flags & FL_NOTARGET)
519 if (client.items & IT_INVISIBILITY)
523 // on skill 5 the monsters usually ignore the player and remain ghostlike
525 if (self.classname != "monster_hellfish")
533 if (!visible (client))
538 if (client.show_hostile < time && !infront (client))
541 else if (r == RANGE_MID)
543 // LordHavoc: was if ( /* client.show_hostile < time || */ !infront (client))
544 if (client.show_hostile < time && !infront (client))
552 if (client.model == "")
555 if (self.enemy.classname != "player" && self.enemy.classname != "turretbase")
557 self.enemy = self.enemy.enemy;
558 if (self.enemy.classname != "player" && self.enemy.classname != "turretbase")
571 //=============================================================================
573 void(float dist) ai_forward =
575 walkmove (self.angles_y, dist);
578 void(float dist) ai_back =
580 walkmove ( (self.angles_y+180), dist);
584 void(float a) monster_setalpha;
593 void(float dist) ai_pain =
607 void(float dist) ai_painforward =
611 walkmove (self.ideal_yaw, dist);
618 The monster is walking it's beat
621 void(float dist) ai_walk =
628 // check for noticing a player
629 if (self.oldenemy.takedamage)
630 if (self.oldenemy.health >= 1)
632 self.enemy = self.oldenemy;
633 self.oldenemy = world;
640 if (self.enemy.takedamage)
642 if (self.enemy.health >= 1)
655 self.findtarget = TRUE;
666 The monster is staying in one place for a while, with slight angle turns
675 if (self.enemy.takedamage)
677 if (self.enemy.health >= 1)
689 self.findtarget = TRUE;
691 if (time > self.pausetime)
698 // change angle slightly
707 don't move, but turn towards ideal_yaw
714 if (self.enemy.takedamage)
716 if (self.enemy.health >= 1)
728 self.findtarget = TRUE;
734 //=============================================================================
741 void(vector pDestvec) ChooseTurn =
743 local vector dir, newdir;
745 dir = self.origin - pDestvec;
747 newdir_x = trace_plane_normal_y;
748 newdir_y = 0 - trace_plane_normal_x;
751 if (dir * newdir > 0)
753 dir_x = 0 - trace_plane_normal_y;
754 dir_y = trace_plane_normal_x;
758 dir_x = trace_plane_normal_y;
759 dir_y = 0 - trace_plane_normal_x;
763 self.ideal_yaw = vectoyaw(dir);
772 float() FacingIdeal =
776 delta = anglemod(self.angles_y - self.ideal_yaw);
777 if (delta > 45 && delta < 315)
783 //=============================================================================
785 .float() th_checkattack;
793 The monster has an enemy it is trying to kill
796 void(float dist) ai_run =
802 // see if the enemy is dead
803 if (self.enemy.health < 1 || self.enemy.takedamage == DAMAGE_NO)
806 // FIXME: look all around for other targets
807 if (self.oldenemy.health >= 1 && self.oldenemy.takedamage)
809 self.enemy = self.oldenemy;
810 self.oldenemy = world;
823 // wake up other monsters
824 self.show_hostile = time + 1;
826 // check knowledge of enemy
827 enemy_range = range(self.enemy);
829 self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin);
832 if (self.attack_state == AS_MELEE)
834 //dprint ("ai_run_melee\n");
835 //Turn and close until within an angle to launch a melee attack
839 self.attack_state = AS_STRAIGHT;
843 else if (self.attack_state == AS_MISSILE)
845 //dprint ("ai_run_missile\n");
846 //Turn in place until within an angle to launch a missile attack
848 if (self.th_missile ())
849 self.attack_state = AS_STRAIGHT;
853 if (self.th_checkattack())
854 return; // beginning an attack
856 if (visible(self.enemy))
857 self.search_time = time + 5;
860 // look for other coop players
861 if (self.search_time < time)
862 self.findtarget = TRUE;
865 if (self.attack_state == AS_SLIDING)
867 //dprint ("ai_run_slide\n");
868 //Strafe sideways, but stay at aproximately the same range
874 if (walkmove (self.ideal_yaw + ofs, movedist))
877 self.lefty = !self.lefty;
879 walkmove (self.ideal_yaw - ofs, movedist);
883 movetogoal (dist); // done in C code...