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 =
191 //#NO AUTOCVARS START
193 local float healthboost;
196 // monsterbosses cvar or spawnflag 64 causes a monster to be a miniboss
197 if ((self.spawnflags & 64) || (random() * 100 < cvar("monsterbosspercent")))
199 self.radsuit_finished = time + 1000000000;
203 self.super_damage_finished = time + 1000000000;
204 healthboost = 30 + self.health * 0.5;
205 self.effects = self.effects | (EF_FULLBRIGHT | EF_BLUE);
209 healthboost = 30 + self.health * bound(0.5, skill * 0.5, 1.5);
210 self.effects = self.effects | (EF_FULLBRIGHT | EF_RED);
211 self.healthregen = max(self.healthregen, min(skill * 10, 30));
213 self.health = self.health + healthboost;
214 self.max_health = self.health;
215 self.bodyhealth = self.bodyhealth * 2 + healthboost;
218 self.colormod_x = random();
219 self.colormod_y = random();
220 self.colormod_z = random();
221 self.colormod = normalize(self.colormod);
223 while (self.colormod_x > 0.6 && self.colormod_y > 0.6 && self.colormod_z > 0.6);
230 //============================================================================
236 returns the range catagorization of an entity reletive to self
237 0 melee range, will become hostile even if back is turned
238 1 visibility and infront, or visibility and show hostile
239 2 infront and show hostile
240 3 only triggered by damage
243 float(entity targ) range =
246 r = vlen ((self.origin + self.view_ofs) - (targ.origin + targ.view_ofs));
251 if (r < 2000) // increased from 1000 for DP
260 returns 1 if the entity is visible to self, even if not infront ()
263 float (entity targ) visible =
265 if (vlen(targ.origin - self.origin) > 5000) // long traces are slow
268 traceline ((self.origin + self.view_ofs), (targ.origin + targ.view_ofs), TRUE, self); // see through other monsters
270 if (trace_inopen && trace_inwater)
271 return FALSE; // sight line crossed contents
273 if (trace_fraction == 1)
283 returns 1 if the entity is in front (in sight) of self
286 float(entity targ) infront =
290 makevectors (self.angles);
291 dot = normalize (targ.origin - self.origin) * v_forward;
295 // returns 0 if not infront, or the dotproduct if infront
296 float(vector dir, entity targ) infront2 =
300 dir = normalize(dir);
301 dot = normalize (targ.origin - self.origin) * dir;
303 if (dot >= 0.3) return dot; // infront
308 //============================================================================
314 Turns towards self.ideal_yaw at self.yaw_speed
315 Sets the global variable current_yaw
316 Called every 0.1 sec by monsters
323 local float ideal, move;
325 //current_yaw = self.ideal_yaw;
326 // mod down the current angle
327 current_yaw = anglemod( self.angles_y );
328 ideal = self.ideal_yaw;
330 if (current_yaw == ideal)
333 move = ideal - current_yaw;
334 if (ideal > current_yaw)
347 if (move > self.yaw_speed)
348 move = self.yaw_speed;
352 if (move < 0-self.yaw_speed )
353 move = 0-self.yaw_speed;
356 current_yaw = anglemod (current_yaw + move);
358 self.angles_y = current_yaw;
364 //============================================================================
368 self.goalentity = self.enemy;
369 self.think = self.th_run;
370 self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin);
371 self.nextthink = time + 0.1;
372 SUB_AttackFinished (1); // wait a while before first attack
375 .void() th_sightsound;
381 // skill 5 does not play sight sounds, instead you only hear the appear sound as they are about to attack
383 if (self.classname != "monster_hellfish")
386 if (self.th_sightsound)
387 self.th_sightsound();
392 if (self.health < 1 || !self.th_run)
394 if (self.enemy.health < 1 || !self.enemy.takedamage)
396 if (self.enemy.classname == "player")
398 // let other monsters see this monster for a while
400 sight_entity_time = time + 0.1;
403 self.show_hostile = time + 1; // wake up other monsters
410 //float checkplayertime;
411 entity lastcheckplayer;
412 entity havocbot_list;
415 entity() checkplayer =
418 local float worldcount;
419 // we can just fallback on checkclient if there are no bots
421 return checkclient();
424 if (time < checkplayertime)
426 traceline(self.origin + self.view_ofs, lastcheckplayer.origin + lastcheckplayer.view_ofs, TRUE, self);
427 if (trace_fraction == 1)
428 return lastcheckplayer;
429 if (trace_ent == lastcheckplayer)
430 return lastcheckplayer;
432 checkplayertime = time + 0.1;
435 check = lastcheckplayer;
441 check = findfloat(check, havocattack, TRUE);
442 if (check.classname == "player" || check.classname == "turretbase")
444 traceline(self.origin + self.view_ofs, check.origin + check.view_ofs, TRUE, self);
445 if (trace_fraction == 1)
446 return lastcheckplayer = check;
447 if (trace_ent == check)
448 return lastcheckplayer = check;
450 else if (check == world)
452 worldcount = worldcount + 1;
454 return lastcheckplayer = check;
457 while(check != lastcheckplayer && c < 100);
466 Self is currently not attacking anything, so try to find a target
468 Returns TRUE if an enemy was sighted
470 When a player fires a missile, the point of impact becomes a fakeplayer so
471 that monsters that see the impact will respond as if they had seen the
474 To avoid spending too much time, only a single client (or fakeclient) is
475 checked each frame. This means multi player games will have slightly
476 slower noticing monsters.
488 // if the first or second spawnflag bit is set, the monster will only
489 // wake up on really seeing the player, not another monster getting angry
491 if (self.spawnflags & 3)
493 // don't wake up on seeing another monster getting angry
494 client = checkclient ();
496 return FALSE; // current check entity isn't in PVS
500 if (sight_entity_time >= time)
502 client = sight_entity;
503 if (client.enemy == self.enemy)
508 client = checkclient ();
510 return FALSE; // current check entity isn't in PVS
514 if (client == self.enemy)
517 if (client.flags & FL_NOTARGET)
521 if (client.items & IT_INVISIBILITY)
525 // on skill 5 the monsters usually ignore the player and remain ghostlike
527 if (self.classname != "monster_hellfish")
535 if (!visible (client))
540 if (client.show_hostile < time && !infront (client))
543 else if (r == RANGE_MID)
545 // LordHavoc: was if ( /* client.show_hostile < time || */ !infront (client))
546 if (client.show_hostile < time && !infront (client))
554 if (client.model == "")
557 if (self.enemy.classname != "player" && self.enemy.classname != "turretbase")
559 self.enemy = self.enemy.enemy;
560 if (self.enemy.classname != "player" && self.enemy.classname != "turretbase")
573 //=============================================================================
575 void(float dist) ai_forward =
577 walkmove (self.angles_y, dist);
580 void(float dist) ai_back =
582 walkmove ( (self.angles_y+180), dist);
586 void(float a) monster_setalpha;
595 void(float dist) ai_pain =
609 void(float dist) ai_painforward =
613 walkmove (self.ideal_yaw, dist);
620 The monster is walking it's beat
623 void(float dist) ai_walk =
630 // check for noticing a player
631 if (self.oldenemy.takedamage)
632 if (self.oldenemy.health >= 1)
634 self.enemy = self.oldenemy;
635 self.oldenemy = world;
642 if (self.enemy.takedamage)
644 if (self.enemy.health >= 1)
657 self.findtarget = TRUE;
668 The monster is staying in one place for a while, with slight angle turns
677 if (self.enemy.takedamage)
679 if (self.enemy.health >= 1)
691 self.findtarget = TRUE;
693 if (time > self.pausetime)
700 // change angle slightly
709 don't move, but turn towards ideal_yaw
716 if (self.enemy.takedamage)
718 if (self.enemy.health >= 1)
730 self.findtarget = TRUE;
736 //=============================================================================
743 void(vector pDestvec) ChooseTurn =
745 local vector dir, newdir;
747 dir = self.origin - pDestvec;
749 newdir_x = trace_plane_normal_y;
750 newdir_y = 0 - trace_plane_normal_x;
753 if (dir * newdir > 0)
755 dir_x = 0 - trace_plane_normal_y;
756 dir_y = trace_plane_normal_x;
760 dir_x = trace_plane_normal_y;
761 dir_y = 0 - trace_plane_normal_x;
765 self.ideal_yaw = vectoyaw(dir);
774 float() FacingIdeal =
778 delta = anglemod(self.angles_y - self.ideal_yaw);
779 if (delta > 45 && delta < 315)
785 //=============================================================================
787 .float() th_checkattack;
795 The monster has an enemy it is trying to kill
798 void(float dist) ai_run =
804 // see if the enemy is dead
805 if (self.enemy.health < 1 || self.enemy.takedamage == DAMAGE_NO)
808 // FIXME: look all around for other targets
809 if (self.oldenemy.health >= 1 && self.oldenemy.takedamage)
811 self.enemy = self.oldenemy;
812 self.oldenemy = world;
825 // wake up other monsters
826 self.show_hostile = time + 1;
828 // check knowledge of enemy
829 enemy_range = range(self.enemy);
831 self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin);
834 if (self.attack_state == AS_MELEE)
836 //dprint ("ai_run_melee\n");
837 //Turn and close until within an angle to launch a melee attack
841 self.attack_state = AS_STRAIGHT;
845 else if (self.attack_state == AS_MISSILE)
847 //dprint ("ai_run_missile\n");
848 //Turn in place until within an angle to launch a missile attack
850 if (self.th_missile ())
851 self.attack_state = AS_STRAIGHT;
855 if (self.th_checkattack())
856 return; // beginning an attack
858 if (visible(self.enemy))
859 self.search_time = time + 5;
862 // look for other coop players
863 if (self.search_time < time)
864 self.findtarget = TRUE;
867 if (self.attack_state == AS_SLIDING)
869 //dprint ("ai_run_slide\n");
870 //Strafe sideways, but stay at aproximately the same range
876 if (walkmove (self.ideal_yaw + ofs, movedist))
879 self.lefty = !self.lefty;
881 walkmove (self.ideal_yaw - ofs, movedist);
885 movetogoal (dist); // done in C code...