]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/monsters/lib/monsters.qc
889aeb4fc8ecfe1be91fe123bcdf8101e86a8aa0
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / monsters / lib / monsters.qc
1 // TODO: clean up this file?
2
3 void M_Item_Touch ()
4 {
5         if(self && IS_PLAYER(other) && other.deadflag == DEAD_NO)
6         {
7                 Item_Touch();
8                 self.think = SUB_Remove;
9                 self.nextthink = time + 0.1;
10         }
11 }
12
13 void monster_item_spawn()
14 {
15         self.monster_delayedattack();
16                 
17         self.think = monster_item_spawn;
18         self.nextthink = time + 0.1;
19         self.gravity = 1;
20         self.velocity = randomvec() * 175 + '0 0 325';
21         self.touch = M_Item_Touch;
22         
23         SUB_SetFade(self, time + 5, 1);
24 }
25
26 void Monster_DropItem (string itype, string itemsize)
27 {
28         vector org = self.origin + ((self.mins + self.maxs) * 0.5);
29         entity e = spawn();
30         
31         setorigin(e, org);
32         
33         switch(itype)
34         {
35                 case "armor":
36                 {
37                         switch(itemsize)
38                         {
39                                 case "mega": e.monster_delayedattack = spawnfunc_item_armor_large; break;
40                                 case "large": e.monster_delayedattack = spawnfunc_item_armor_big; break;
41                                 case "medium": e.monster_delayedattack = spawnfunc_item_armor_medium; break;
42                                 case "small": e.monster_delayedattack = spawnfunc_item_armor_small; break;
43                                 default: break;
44                         }
45                         break; // break here?
46                 }
47                 case "health":
48                 {
49                         switch(itemsize)
50                         {
51                                 case "mega": e.monster_delayedattack = spawnfunc_item_health_mega; break;
52                                 case "large": e.monster_delayedattack = spawnfunc_item_health_large; break;
53                                 case "medium": e.monster_delayedattack = spawnfunc_item_health_medium; break;
54                                 case "small": e.monster_delayedattack = spawnfunc_item_health_small; break;
55                                 default: break;
56                         }
57                         break; // break here?
58                 }
59                 case "ammo":
60                 {
61                         switch(itemsize)
62                         {
63                                 case "shells": e.monster_delayedattack = spawnfunc_item_shells; break;
64                                 case "cells": e.monster_delayedattack = spawnfunc_item_cells; break;
65                                 case "rockets": e.monster_delayedattack = spawnfunc_item_rockets; break;
66                                 case "bullets":
67                                 case "nails": e.monster_delayedattack = spawnfunc_item_bullets; break;
68                                 default: break;
69                         }
70                         break;
71                 }
72                 default: break;
73         }
74         
75         if(g_minstagib)
76                 e.monster_delayedattack = spawnfunc_item_minst_cells;
77                 
78         e.think = monster_item_spawn;
79         e.nextthink = time + 0.1;
80 }
81
82 float monster_isvalidtarget (entity targ, entity ent)
83 {
84         if(!targ || !ent)
85                 return FALSE; // this check should fix a crash
86                 
87         if(time < game_starttime)
88                 return FALSE; // monsters do nothing before the match has started
89                 
90         WarpZone_TraceLine(ent.origin, targ.origin, MOVE_NORMAL, ent);
91         
92         if(vlen(targ.origin - ent.origin) >= ent.target_range)
93                 return FALSE; // enemy is too far away
94                 
95         if not(targ.vehicle_flags & VHF_ISVEHICLE)
96         if(trace_ent != targ)
97                 return FALSE; // we can't see the enemy
98                 
99         if(targ.takedamage == DAMAGE_NO)
100                 return FALSE; // enemy can't be damaged
101                 
102         if(targ.items & IT_INVISIBILITY)
103                 return FALSE; // enemy is invisible
104         
105         if(IS_SPEC(targ) || IS_OBSERVER(targ))
106                 return FALSE; // enemy is a spectator
107         
108         if not(targ.vehicle_flags & VHF_ISVEHICLE) // vehicles dont count as alive?
109         if(targ.deadflag != DEAD_NO || ent.deadflag != DEAD_NO || targ.health <= 0 || ent.health <= 0)
110                 return FALSE; // enemy/self is dead
111         
112         if(targ.monster_owner == ent || ent.monster_owner == targ)
113                 return FALSE; // enemy owns us, or we own them
114         
115         if not(targ.vehicle_flags & VHF_ISVEHICLE)
116         if(targ.flags & FL_NOTARGET)
117                 return FALSE; // enemy can't be targetted
118         
119         if not(autocvar_g_monsters_typefrag)
120         if(targ.BUTTON_CHAT)
121                 return FALSE; // no typefragging!
122         
123         if not(IsDifferentTeam(targ, ent))
124                 return FALSE; // enemy is on our team
125         
126         return TRUE;
127 }
128
129 entity FindTarget (entity ent) 
130 {
131         if(MUTATOR_CALLHOOK(MonsterFindTarget)) { return ent.enemy; } // Handled by a mutator
132         entity e;
133         
134         for(e = world; (e = findflags(e, monster_attack, TRUE)); ) 
135         if(monster_isvalidtarget(e, ent))
136                 return e;
137
138         return world;
139 }
140
141 void MonsterTouch ()
142 {
143         if(other == world)
144                 return;
145                 
146         if(self.enemy != other)
147         if not(other.flags & FL_MONSTER)
148         if(monster_isvalidtarget(other, self))
149                 self.enemy = other;
150 }
151
152 void monster_sound(string msound, float sound_delay, float delaytoo)
153 {
154         if(delaytoo && time < self.msound_delay)
155                 return; // too early
156                 
157         if(msound == "")
158                 return; // sound doesn't exist
159
160         sound(self, CHAN_AUTO, msound, VOL_BASE, ATTN_NORM);
161
162         self.msound_delay = time + sound_delay;
163 }
164
165 void monster_precachesounds(entity e)
166 {
167         precache_sound(e.msound_idle);
168         precache_sound(e.msound_death);
169         precache_sound(e.msound_attack_melee);
170         precache_sound(e.msound_attack_ranged);
171         precache_sound(e.msound_sight);
172         precache_sound(e.msound_pain);
173 }
174
175 void monster_melee (entity targ, float damg, float er, float deathtype)
176 {
177         float bigdmg = 0, rdmg = damg * random();
178
179         if (self.health <= 0)
180                 return;
181         if (targ == world)
182                 return;
183
184         if (vlen(self.origin - targ.origin) > er * self.scale)
185                 return;
186                 
187         bigdmg = rdmg * self.scale;
188         
189         Damage(targ, self, self, bigdmg * monster_skill, deathtype, targ.origin, normalize(targ.origin - self.origin));
190 }
191
192 void Monster_CheckDropCvars (string mon)
193 {
194         if not(self.candrop)
195                 return; // forced off
196         
197         string dropitem;
198         string dropsize;
199         
200         dropitem = cvar_string(strcat("g_monster_", mon, "_drop"));
201         dropsize = cvar_string(strcat("g_monster_", mon, "_drop_size"));
202         
203         monster_dropitem = dropitem;
204         monster_dropsize = dropsize;
205         MUTATOR_CALLHOOK(MonsterDropItem);
206         dropitem = monster_dropitem;
207         dropsize = monster_dropsize;
208         
209         if(autocvar_g_monsters_forcedrop)
210                 Monster_DropItem(autocvar_g_monsters_drop_type, autocvar_g_monsters_drop_size);
211         else if(dropitem != "")
212                 Monster_DropItem(dropitem, dropsize);      
213         else
214                 Monster_DropItem("armor", "medium");
215 }
216
217 void ScaleMonster (float scle)
218 {
219         // this should prevent monster from falling through floor when scale changes
220         self.scale = scle;
221         setorigin(self, self.origin + ('0 0 30' * scle));
222 }
223
224 void Monster_CheckMinibossFlag ()
225 {
226         if(MUTATOR_CALLHOOK(MonsterCheckBossFlag))
227                 return;
228                 
229         float r = random() * 4, chance = random() * 100;
230
231         // g_monsters_miniboss_chance cvar or spawnflags 64 causes a monster to be a miniboss
232         if ((self.spawnflags & MONSTERFLAG_MINIBOSS) || (chance < autocvar_g_monsters_miniboss_chance))
233         {
234                 self.health += autocvar_g_monsters_miniboss_healthboost;
235                 ScaleMonster(1.5);
236                 self.flags |= MONSTERFLAG_MINIBOSS;
237                 self.weapon = WEP_NEX;
238                 
239                 if (r < 2 || self.team == NUM_TEAM_2)
240                 {
241                         self.strength_finished = -1;  
242                         self.effects |= (EF_FULLBRIGHT | EF_BLUE);
243                 }
244                 else if (r >= 1 || self.team == NUM_TEAM_1)
245                 {
246                         self.invincible_finished = -1;
247                         self.effects |= (EF_FULLBRIGHT | EF_RED);
248                 }
249                 else
250                         self.effects |= (EF_FULLBRIGHT | EF_RED | EF_BLUE);
251                 
252                 if(teamplay)
253                 if(self.team)
254                         return;
255                         
256                 self.colormod = randomvec() * 4;
257         }
258 }
259
260 float Monster_CanRespawn(entity ent)
261 {
262         other = ent;
263         if(MUTATOR_CALLHOOK(MonsterRespawn))
264                 return TRUE; // enabled by a mutator
265                 
266         if(ent.spawnflags & MONSTERFLAG_NORESPAWN)
267                 return FALSE;
268                 
269         if not(autocvar_g_monsters_respawn)
270                 return FALSE;
271                 
272         return TRUE;
273 }
274
275 void Monster_Fade ()
276 {
277         if(Monster_CanRespawn(self))
278         {
279                 self.monster_respawned = TRUE;
280                 setmodel(self, "");
281                 self.think = self.monster_spawnfunc;
282                 self.nextthink = time + self.respawntime;
283                 setorigin(self, self.pos1);
284                 self.angles = self.pos2;
285                 self.health = self.max_health; // TODO: check if resetting to max_health is wise here
286                 return;
287         }
288         self.think = SUB_Remove;
289         self.nextthink = time + 4;
290         SUB_SetFade(self, time + 3, 1);
291 }
292
293 float Monster_CanJump (vector vel)
294 {
295         local vector old = self.velocity;
296         
297         self.velocity = vel;
298         tracetoss(self, self);
299         self.velocity = old;
300         if (trace_ent != self.enemy)
301                 return FALSE;
302
303         return TRUE;
304 }
305
306 float monster_leap (float anm, void() touchfunc, vector vel, float anim_finished)
307 {
308         if not(self.flags & FL_ONGROUND)
309                 return FALSE;
310         if(self.health < 1)
311                 return FALSE; // called when dead?
312         if not(Monster_CanJump(vel))
313                 return FALSE;
314                 
315         self.frame = anm;
316         self.state = MONSTER_STATE_ATTACK_LEAP;
317         self.touch = touchfunc;
318         self.origin_z += 1;
319         self.velocity = vel;
320         if (self.flags & FL_ONGROUND)
321                 self.flags -= FL_ONGROUND;
322                 
323         self.attack_finished_single = time + anim_finished;
324         
325         return TRUE;
326 }
327
328 float GenericCheckAttack ()
329 {
330         // checking attack while dead?
331         if (self.health <= 0 || self.enemy == world)
332                 return FALSE;
333                 
334         if(self.monster_delayedattack && self.delay != -1)
335         {
336                 if(time < self.delay)
337                         return FALSE;
338                         
339                 self.monster_delayedattack();
340         }
341         
342         if (time < self.attack_finished_single)
343                 return FALSE;
344                 
345         if(self.attack_melee)
346         if(vlen(self.enemy.origin - self.origin) <= 100 * self.scale)
347         {
348                 monster_sound(self.msound_attack_melee, 0, FALSE); // no delay for attack sounds
349                 self.attack_melee(); // don't wait for nextthink - too slow
350                 return TRUE;
351         }
352         
353         // monster doesn't have a ranged attack function, so stop here
354         if not(self.attack_ranged)
355                 return FALSE;
356
357         // see if any entities are in the way of the shot
358         if not(findtrajectorywithleading(self.origin, '0 0 0', '0 0 0', self.enemy, 800, 0, 2.5, 0, self))
359                 return FALSE;
360
361         if(self.attack_ranged())
362         {
363                 monster_sound(self.msound_attack_ranged, 0, FALSE); // no delay for attack sounds
364                 return TRUE;
365         }
366
367         return FALSE;
368 }
369
370 void monster_use ()
371 {
372         if (self.enemy)
373                 return;
374         if (self.health <= 0)
375                 return;
376
377         if(!monster_isvalidtarget(activator, self))
378                 return;
379
380         self.enemy = activator;
381 }
382
383 float trace_path(vector from, vector to)
384 {
385         vector dir = normalize(to - from) * 15, offset = '0 0 0';
386         float trace1 = trace_fraction;
387         
388         offset_x = dir_y;
389         offset_y = -dir_x;
390         traceline (from+offset, to+offset, TRUE, self);
391         
392         traceline(from-offset, to-offset, TRUE, self);
393                 
394         return ((trace1 < trace_fraction) ? trace1 : trace_fraction);
395 }
396
397 vector monster_pickmovetarget(entity targ)
398 {
399         // enemy is always preferred target
400         if(self.enemy)
401         {
402                 self.monster_movestate = MONSTER_MOVE_ENEMY;
403                 return self.enemy.origin;
404         }
405         if(targ)
406         {
407                 self.monster_movestate = MONSTER_MOVE_WANDER;
408                 return targ.origin;
409         }
410         
411         switch(self.monster_moveflags)
412         {
413                 case MONSTER_MOVE_OWNER:
414                 {
415                         self.monster_movestate = MONSTER_MOVE_OWNER;
416                         if(self.monster_owner && self.monster_owner.classname != "monster_swarm")
417                                 return self.monster_owner.origin;
418                 }
419                 case MONSTER_MOVE_WANDER:
420                 {
421                         self.monster_movestate = MONSTER_MOVE_WANDER;
422                                 
423                         self.angles_y = random() * 500;
424                         makevectors(self.angles);
425                         return self.origin + v_forward * 600;
426                 }
427                 case MONSTER_MOVE_SPAWNLOC:
428                 {
429                         self.monster_movestate = MONSTER_MOVE_SPAWNLOC;
430                         return self.pos1;
431                 }
432                 default:
433                 case MONSTER_MOVE_NOMOVE:
434                 {
435                         self.monster_movestate = MONSTER_MOVE_NOMOVE;
436                         return self.origin;
437                 }
438         }
439 }
440
441 .float last_trace;
442 void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_run, float manim_walk, float manim_idle)
443 {
444         if(self.target)
445                 self.goalentity = find(world, targetname, self.target);
446                 
447         entity targ;
448
449         if(self.frozen)
450         {
451                 self.revive_progress = bound(0, self.revive_progress + frametime * self.revive_speed, 1);
452                 self.health = max(1, self.max_health * self.revive_progress);
453                 
454                 if(self.sprite) WaypointSprite_UpdateHealth(self.sprite, self.health);
455                         
456                 movelib_beak_simple(stopspeed);
457                         
458                 self.velocity = '0 0 0';
459                 self.enemy = world;
460                 self.nextthink = time + 0.1;
461                 
462                 if(self.revive_progress >= 1)
463                         Unfreeze(self); // wait for next think before attacking
464                         
465                 return; // no moving while frozen
466         }
467         
468         if(self.flags & FL_SWIM)
469         {
470                 if(self.waterlevel < WATERLEVEL_WETFEET)
471                 {
472                         if(time >= self.last_trace)
473                         {
474                                 self.last_trace = time + 0.4;
475                                 self.angles = '0 0 -90';
476                                 Damage (self, world, world, 2, DEATH_DROWN, self.origin, '0 0 0');
477                                 if(random() < 0.5)
478                                 {
479                                         self.velocity_y += random() * 50;
480                                         self.velocity_x -= random() * 50;
481                                 }
482                                 else
483                                 {
484                                         self.velocity_y -= random() * 50;
485                                         self.velocity_x += random() * 50;
486                                 }
487                                 //self.velocity_z += random() * 150;
488                                 self.movetype = MOVETYPE_BOUNCE;
489                                 self.velocity_z = -200;
490                         }
491                         return;
492                 }
493                 else
494                 {
495                         self.angles = '0 0 0';
496                         self.movetype = MOVETYPE_WALK;
497                 }
498         }
499         
500         if(gameover || time < game_starttime)
501         {
502                 runspeed = walkspeed = 0;
503                 self.frame = manim_idle;
504                 movelib_beak_simple(stopspeed);
505                 return;
506         }
507         
508         targ = self.goalentity;
509         
510         monster_target = targ;
511         monster_speed_run = runspeed;
512         monster_speed_walk = walkspeed;
513         MUTATOR_CALLHOOK(MonsterMove);
514         targ = monster_target;
515         runspeed = monster_speed_run;
516         walkspeed = monster_speed_walk;
517                 
518         if(IsDifferentTeam(self.monster_owner, self))
519                 self.monster_owner = world;
520                 
521         if not(monster_isvalidtarget(self.enemy, self))
522                 self.enemy = world; // check enemy each think frame?
523                 
524         if not(self.enemy)
525         {
526                 self.enemy = FindTarget(self);
527                 if(self.enemy)
528                         monster_sound(self.msound_sight, 0, FALSE);
529         }
530                 
531         if(time >= self.last_trace)
532         {
533                 if(self.monster_movestate == MONSTER_MOVE_WANDER && self.goalentity.classname != "td_waypoint")
534                         self.last_trace = time + 2;
535                 else
536                         self.last_trace = time + 0.5;
537                 self.moveto = monster_pickmovetarget(targ);
538         }
539
540         if not(self.enemy)
541                 monster_sound(self.msound_idle, 5, TRUE);
542         
543         vector angles_face = vectoangles(self.moveto - self.origin);
544         vector owner_face = vectoangles(self.monster_owner.origin - self.origin);
545         vector enemy_face = vectoangles(self.enemy.origin - self.origin);
546         self.angles_y = angles_face_y;
547         
548         if(self.state == MONSTER_STATE_ATTACK_LEAP && (self.flags & FL_ONGROUND))
549         {
550                 self.state = 0;
551                 self.touch = MonsterTouch;
552         }
553          
554         v_forward = normalize(self.moveto - self.origin);
555         
556         float l = vlen(self.moveto - self.origin);
557         float t1 = trace_path(self.origin+'0 0 10', self.moveto+'0 0 10');
558         float t2 = trace_path(self.origin-'0 0 15', self.moveto-'0 0 15'); 
559         
560         if(t1*l-t2*l>50 && (t1*l > 100 || t1 > 0.8))
561         if(self.flags & FL_ONGROUND)
562                 movelib_jump_simple(100);
563
564         if(vlen(self.origin - self.moveto) > 64)
565         {
566                 if(self.flags & FL_FLY)
567                         movelib_move_simple(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6);
568                 else
569                         movelib_move_simple_gravity(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6);
570                 if(time > self.pain_finished)
571                 if(time > self.attack_finished_single)
572                         self.frame = ((self.enemy) ? manim_run : manim_walk);
573         }
574         else
575         {
576                 movelib_beak_simple(stopspeed);
577                 if(time > self.attack_finished_single)
578                 if(time > self.pain_finished)
579                 if (vlen(self.velocity) <= 30)
580                 {
581                         self.frame = manim_idle;
582                         if(self.enemy)
583                                 self.angles_y = enemy_face_y;
584                         else
585                                 self.angles_y = ((self.monster_owner) ? owner_face_y : self.pos2_y); // reset looking angle now?
586                 }
587         }
588                 
589         if(self.enemy && self.checkattack)
590                 self.checkattack();
591 }
592
593 void monsters_setstatus()
594 {
595         self.stat_monsters_total = monsters_total;
596         self.stat_monsters_killed = monsters_killed;
597 }
598
599 void Monster_Appear ()
600 {
601         self.enemy = activator;
602         self.spawnflags &~= MONSTERFLAG_APPEAR;
603         self.monster_spawnfunc();
604 }
605
606 void monsters_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
607 {
608         if(self.frozen && deathtype != DEATH_KILL)
609                 return;
610                 
611         if(time < self.pain_finished && deathtype != DEATH_KILL)
612                 return;
613                 
614         if((ignore_turrets && !(attacker.turrcaps_flags & TFL_TURRCAPS_ISTURRET)) || !ignore_turrets)
615         if(monster_isvalidtarget(attacker, self))
616                 self.enemy = attacker;
617                 
618         damage *= self.armorvalue;
619                 
620         self.health -= damage;
621         
622         if(self.sprite)
623                 WaypointSprite_UpdateHealth(self.sprite, self.health);
624                 
625         self.dmg_time = time;
626
627         if(sound_allowed(MSG_BROADCAST, attacker) && deathtype != DEATH_DROWN)
628                 spamsound (self, CH_PAIN, "misc/bodyimpact1.wav", VOL_BASE, ATTN_NORM);  // FIXME: PLACEHOLDER
629         
630         self.velocity += force * self.damageforcescale;
631                 
632         if(deathtype != DEATH_DROWN)
633         {
634                 Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
635                 if (damage > 50)
636                         Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, self, attacker);
637                 if (damage > 100)
638                         Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, self, attacker);
639         }
640                 
641         if(self.health <= 0)
642         {        
643                 if(self.sprite)
644                 {
645                         // Update one more time to avoid waypoint fading without emptying healthbar
646                         WaypointSprite_UpdateHealth(self.sprite, 0);
647                 }
648                 
649                 if(deathtype == DEATH_KILL)
650                         self.candrop = FALSE; // killed by mobkill command
651                         
652                 activator = attacker;
653                 other = self.enemy;
654                 self.target = self.target2;
655                 self.target2 = "";
656                 SUB_UseTargets();
657         
658                 self.monster_die();
659                 
660                 frag_attacker = attacker;
661                 frag_target = self;
662                 MUTATOR_CALLHOOK(MonsterDies);
663         }
664 }
665
666 // used to hook into monster post death functions without a mutator
667 void monster_hook_death()
668 {
669         if(self.sprite)
670         WaypointSprite_Kill(self.sprite);
671                 
672         if(self.weaponentity)
673         {
674                 remove(self.weaponentity);
675                 self.weaponentity = world;
676         }
677                 
678         monster_sound(self.msound_death, 0, FALSE);
679                 
680         if(!(self.spawnflags & MONSTERFLAG_SPAWNED) && !self.monster_respawned)
681                 monsters_killed += 1;
682                 
683         if(self.candrop && self.weapon)
684                 W_ThrowNewWeapon(self, self.weapon, 0, self.origin, randomvec() * 150 + '0 0 325');     
685                 
686         if(self.realowner.classname == "monster_spawner")
687                 self.realowner.spawner_monstercount -= 1;
688                 
689         if(IS_CLIENT(self.realowner))
690                 self.realowner.monstercount -= 1;
691                 
692         totalspawned -= 1;
693 }
694
695 // used to hook into monster post spawn functions without a mutator
696 void monster_hook_spawn()
697 {
698         Monster_CheckMinibossFlag();
699
700         self.max_health = self.health;
701         self.pain_finished = self.nextthink;
702
703         monster_precachesounds(self);
704         
705         if(teamplay && self.team)
706         {
707                 //self.colormod = Team_ColorRGB(self.team);
708                 //self.glowmod = self.colormod;
709                 self.colormap = 1024 + (self.team - 1) * 17;
710                 self.colormod = '1 1 1';
711                 self.glowmod = Team_ColorRGB(self.team);
712                 self.monster_attack = TRUE;
713         }
714         else
715                 self.colormod = self.glowmod = '1 1 1';
716         
717         // TODO: fix this...
718         if (self.target)
719         {
720                 self.target2 = self.target;
721                 self.goalentity = find(world, targetname, self.target);
722         }
723         
724         self.sprite_height *= self.scale; // do this after setting the monster's scale
725                 
726         if(autocvar_g_monsters_healthbars)
727         {
728                 WaypointSprite_Spawn(self.netname, 0, 600, self, '0 0 1' * self.sprite_height, world, 0, self, sprite, FALSE, RADARICON_DANGER, ((self.team) ? Team_ColorRGB(self.team) : '1 0 0'));    
729                 WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
730                 WaypointSprite_UpdateHealth(self.sprite, self.health);
731         }
732         
733         monster_sound(self.msound_spawn, 0, FALSE);
734
735         MUTATOR_CALLHOOK(MonsterSpawn);
736 }
737
738 float monster_initialize(string  net_name,
739                                                  string  bodymodel,
740                                                  vector  min_s,
741                                                  vector  max_s,
742                                                  float   nodrop,
743                                                  void() dieproc,
744                                                  void() spawnproc)
745 {
746         if not(autocvar_g_monsters)
747                 return FALSE;
748                 
749         // support for quake style removing monsters based on skill
750         if(monster_skill <= autocvar_g_monsters_skill_easy && (self.spawnflags & MONSTERSKILL_NOTEASY)) { return FALSE; }
751         if(monster_skill == autocvar_g_monsters_skill_normal && (self.spawnflags & MONSTERSKILL_NOTMEDIUM)) { return FALSE; }
752         if(monster_skill == autocvar_g_monsters_skill_hard && (self.spawnflags & MONSTERSKILL_NOTHARD)) { return FALSE; }
753         if(monster_skill == autocvar_g_monsters_skill_insane && (self.spawnflags & MONSTERSKILL_NOTINSANE)) { return FALSE; }
754         if(monster_skill >= autocvar_g_monsters_skill_nightmare && (self.spawnflags & MONSTERSKILL_NOTNIGHTMARE)) { return FALSE; }
755
756         if(self.model == "")
757         if(bodymodel == "")
758                 error("monsters: missing bodymodel!");
759
760         if(self.netname == "")
761         {
762                 if(net_name != "" && IS_PLAYER(self.realowner))
763                         net_name = strzone(strdecolorize(sprintf("%s's %s", self.realowner.netname, net_name)));
764                 self.netname = ((net_name == "") ? self.classname : net_name);
765         }
766         
767         if not(self.scale)
768                 self.scale = 1;
769         
770         if(self.spawnflags & MONSTERFLAG_GIANT && !autocvar_g_monsters_nogiants)
771                 ScaleMonster(5);
772         else
773                 ScaleMonster(self.scale);
774                 
775         min_s *= self.scale;
776         max_s *= self.scale;
777
778         if(self.team && !teamplay)
779                 self.team = 0;
780
781         self.flags = FL_MONSTER;
782         
783         if(self.model != "")
784                 bodymodel = self.model;
785                 
786         if not(self.spawnflags & MONSTERFLAG_SPAWNED) // naturally spawned monster
787         if not(self.monster_respawned)
788                 monsters_total += 1;
789         
790         precache_model(bodymodel);
791
792         setmodel(self, bodymodel);
793         
794         setsize(self, min_s, max_s);
795
796         self.takedamage                 = DAMAGE_AIM;
797         self.bot_attack                 = TRUE;
798         self.iscreature                 = TRUE;
799         self.teleportable               = TRUE;
800         self.damagedbycontents  = TRUE;
801         self.damageforcescale   = 0.003;
802         self.monster_die                = dieproc;
803         self.event_damage               = monsters_damage;
804         self.touch                              = MonsterTouch;
805         self.use                                = monster_use;
806         self.solid                              = SOLID_BBOX;
807         self.movetype                   = MOVETYPE_WALK;
808         self.delay                              = -1; // used in attack delay code
809         monsters_spawned           += 1;
810         self.think                              = spawnproc;
811         self.nextthink                  = time;
812         self.enemy                              = world;
813         self.velocity                   = '0 0 0';
814         self.moveto                             = self.origin;
815         self.pos1                               = self.origin;
816         self.pos2                               = self.angles;
817         self.candrop                    = TRUE;
818         
819         if not(self.armorvalue)
820                 self.armorvalue = 1;
821         
822         if not(self.target_range)
823                 self.target_range = autocvar_g_monsters_target_range;
824         
825         if not(self.respawntime)
826                 self.respawntime = autocvar_g_monsters_respawn_delay;
827         
828         if not(self.monster_moveflags)
829                 self.monster_moveflags = MONSTER_MOVE_WANDER;
830
831         if(autocvar_g_nodepthtestplayers)
832                 self.effects |= EF_NODEPTHTEST;
833
834         if(autocvar_g_fullbrightplayers)
835                 self.effects |= EF_FULLBRIGHT;
836
837         if not(nodrop)
838         {
839                 setorigin(self, self.origin);
840                 tracebox(self.origin + '0 0 100', min_s, max_s, self.origin - '0 0 10000', MOVE_WORLDONLY, self);
841                 setorigin(self, trace_endpos);
842         }
843
844         return TRUE;
845 }