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