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