Merge branch 'master' into Mario/monsters_broken
authorMario <zacjardine@y7mail.com>
Sat, 29 Aug 2015 11:54:45 +0000 (21:54 +1000)
committerMario <zacjardine@y7mail.com>
Sat, 29 Aug 2015 11:54:45 +0000 (21:54 +1000)
# Conflicts:
# qcsrc/common/monsters/all.inc
# qcsrc/common/monsters/all.qc
# qcsrc/common/monsters/all.qh
# qcsrc/common/monsters/monster/mage.qc
# qcsrc/common/monsters/monster/shambler.qc
# qcsrc/common/monsters/monster/spider.qc
# qcsrc/common/monsters/monster/wyvern.qc
# qcsrc/common/monsters/monster/zombie.qc
# qcsrc/common/monsters/sv_monsters.qc
# qcsrc/server/command/cmd.qc
# qcsrc/server/command/sv_cmd.qc
# qcsrc/server/mutators/base.qh

16 files changed:
1  2 
qcsrc/client/csqcmodel_hooks.qc
qcsrc/common/animdecide.qc
qcsrc/common/monsters/all.inc
qcsrc/common/monsters/all.qh
qcsrc/common/monsters/monster/mage.qc
qcsrc/common/monsters/monster/shambler.qc
qcsrc/common/monsters/monster/spider.qc
qcsrc/common/monsters/monster/wyvern.qc
qcsrc/common/monsters/monster/zombie.qc
qcsrc/common/monsters/sv_monsters.qc
qcsrc/server/autocvars.qh
qcsrc/server/command/cmd.qc
qcsrc/server/command/common.qc
qcsrc/server/command/sv_cmd.qc
qcsrc/server/mutators/events.qh
qcsrc/server/mutators/gamemode_invasion.qc

Simple merge
      #include "../server/defs.qh"
  #endif
  
 -// player animation data for this model
 -// each vector is as follows:
 -// _x = startframe
 -// _y = numframes
 -// _z = framerate
 -.vector anim_die1; // player dies
 -.vector anim_die2; // player dies differently
 -.vector anim_draw; // player pulls out a weapon
 -.vector anim_duckwalk; // player walking while crouching
 -.vector anim_duckjump; // player jumping from a crouch
 -.vector anim_duckidle; // player idling while crouching
 -.vector anim_idle; // player standing
 -.vector anim_jump; // player jump
 -.vector anim_pain1; // player flinches from pain
 -.vector anim_pain2; // player flinches from pain, differently
 -.vector anim_shoot; // player shoots
 -.vector anim_taunt; // player taunts others (FIXME: no code references this)
 -.vector anim_run; // player running forward
 -.vector anim_runbackwards; // player running backward
 -.vector anim_strafeleft; // player shuffling left quickly
 -.vector anim_straferight; // player shuffling right quickly
 -.vector anim_forwardright; // player running forward and right
 -.vector anim_forwardleft; // player running forward and left
 -.vector anim_backright; // player running backward and right
 -.vector anim_backleft; // player running back and left
 -.vector anim_melee; // player doing the melee action
 -.vector anim_duck; // player doing the melee action
 -.vector anim_duckwalkbackwards;
 -.vector anim_duckwalkstrafeleft;
 -.vector anim_duckwalkstraferight;
 -.vector anim_duckwalkforwardright;
 -.vector anim_duckwalkforwardleft;
 -.vector anim_duckwalkbackright;
 -.vector anim_duckwalkbackleft;
 -.float animdecide_modelindex;
++bool monsters_animoverride(entity e)
++{
++      int monster_id = 0;
++      for(int i = MON_FIRST; i <= MON_LAST; ++i)
++      {
++              entity mon = get_monsterinfo(i);
++
++              //if(substring(e.model, 0, strlen(mon.model) - 4) == substring(mon.model, 0, strlen(mon.model) - 4))
++              if(e.model == mon.model)
++              {
++                      monster_id = i;
++                      break;
++              }
++      }
++
++      if(!monster_id) { return false; }
++
++      MON_ACTION(monster_id, MR_ANIM);
++
++      vector none = '0 0 0';
++      e.anim_duckwalk = e.anim_walk;
++      e.anim_duckjump = animfixfps(e, '5 1 10', none);
++      e.anim_duckidle = e.anim_idle;
++      e.anim_jump = animfixfps(e, '8 1 10', none);
++      e.anim_taunt = animfixfps(e, '12 1 0.33', none);
++      e.anim_runbackwards = e.anim_run;
++      e.anim_strafeleft = e.anim_run;
++      e.anim_straferight = e.anim_run;
++      e.anim_forwardright = e.anim_run;
++      e.anim_forwardleft = e.anim_run;
++      e.anim_backright = e.anim_run;
++      e.anim_backleft  = e.anim_run;
++      e.anim_duckwalkbackwards = e.anim_walk;
++      e.anim_duckwalkstrafeleft = e.anim_walk;
++      e.anim_duckwalkstraferight = e.anim_walk;
++      e.anim_duckwalkforwardright = e.anim_walk;
++      e.anim_duckwalkforwardleft = e.anim_walk;
++      e.anim_duckwalkbackright = e.anim_walk;
++      e.anim_duckwalkbackleft  = e.anim_walk;
++
++      // these anims ought to stay until stopped explicitly by weaponsystem
++      e.anim_shoot_z = 0.001;
++      e.anim_melee_z = 0.001;
++
++      return true;
++}
  void animdecide_load_if_needed(entity e)
  {
        if(e.modelindex == e.animdecide_modelindex)
                return;
        e.animdecide_modelindex = e.modelindex;
  
++      if(substring(e.model, 0, 16) == "models/monsters/")
++      {
++              if(monsters_animoverride(e))
++                      return;
++      }
++
        vector none = '0 0 0';
        e.anim_die1 = animfixfps(e, '0 1 0.5', none); // 2 seconds
        e.anim_die2 = animfixfps(e, '1 1 0.5', none); // 2 seconds
@@@ -1,10 -1,5 +1,10 @@@
- #include "monster/zombie.qc"
- #include "monster/spider.qc"
 +#ifndef MENUQC
 +#include "../animdecide.qh"
 +vector animfixfps(entity e, vector a, vector b);
 +#endif
 +
  #include "monster/mage.qc"
- #include "monster/wyvern.qc"
  #include "monster/shambler.qc"
+ #include "monster/spider.qc"
+ #include "monster/wyvern.qc"
+ #include "monster/zombie.qc"
@@@ -8,12 -31,7 +31,9 @@@ const int MR_SETUP = 1; // (SERVER) set
  const int MR_THINK = 2; // (SERVER) logic to run every frame
  const int MR_DEATH = 3; // (SERVER) called when monster dies
  const int MR_PRECACHE = 4; // (BOTH) precaches models/sounds used by this monster
 +const int MR_PAIN = 5; // (SERVER) called when monster is damaged
 +const int MR_ANIM = 6; // (BOTH?) sets animations for monster
  
- // functions
- entity get_monsterinfo(float id);
  // special spawn flags
  const int MONSTER_RESPAWN_DEATHPOINT = 16; // re-spawn where we died
  const int MONSTER_TYPE_FLY = 32;
@@@ -33,59 -47,9 +49,21 @@@ const int MON_FLAG_MELEE = 1024
  .string model; // full name of model
  .int spawnflags;
  .vector mins, maxs; // monster hitbox size
++.bool(int) monster_attackfunc;
 +
 +// animations
 +.vector anim_blockend;
 +.vector anim_blockstart;
 +.vector anim_melee1;
 +.vector anim_melee2;
 +.vector anim_melee3;
 +.vector anim_pain3;
 +.vector anim_pain4;
 +.vector anim_pain5;
 +.vector anim_walk;
 +.vector anim_spawn;
  
  // other useful macros
  #define MON_ACTION(monstertype,mrequest) (get_monsterinfo(monstertype)).monster_func(mrequest)
 -#define M_NAME(monstertype) (get_monsterinfo(monstertype)).monster_name
  
- // =====================
- //    Monster Registration
- // =====================
- float m_null(float dummy);
- void register_monster(float id, float(float) func, float(float) attackfunc, float monsterflags, vector min_s, vector max_s, string modelname, string shortname, string mname);
- void register_monsters_done();
- const int MON_MAXCOUNT = 24; // increase as necessary, limit is infinite, but keep loops small!
- const int MON_FIRST = 1;
- int MON_COUNT;
- int MON_LAST;
- #define REGISTER_MONSTER_2(id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname) \
-       int id; \
-       float func(float); \
-       float attackfunc(float); \
-       void RegisterMonsters_##id() \
-       { \
-               MON_LAST = (id = MON_FIRST + MON_COUNT); \
-               ++MON_COUNT; \
-               register_monster(id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname); \
-       } \
-       ACCUMULATE_FUNCTION(RegisterMonsters, RegisterMonsters_##id)
- #ifdef SVQC
- #define REGISTER_MONSTER(id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname) \
-       REGISTER_MONSTER_2(MON_##id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname)
- #elif defined(CSQC)
- #define REGISTER_MONSTER(id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname) \
-       REGISTER_MONSTER_2(MON_##id,func,m_null,monsterflags,min_s,max_s,modelname,shortname,mname)
- #else
- #define REGISTER_MONSTER(id,func,attackfunc,monsterflags,min_s,max_s,modelname,shortname,mname) \
-       REGISTER_MONSTER_2(MON_##id,m_null,m_null,monsterflags,min_s,max_s,modelname,shortname,mname)
- #endif
- #include "all.inc"
- #undef REGISTER_MONSTER
- ACCUMULATE_FUNCTION(RegisterMonsters, register_monsters_done);
  #endif
@@@ -1,18 -1,21 +1,23 @@@
- #ifdef REGISTER_MONSTER
- REGISTER_MONSTER(
+ #ifndef MENUQC
 -bool m_mage(int);
++bool M_Mage(int);
+ #endif
+ REGISTER_MONSTER_SIMPLE(
  /* MON_##id   */ MAGE,
- /* functions  */ M_Mage, M_Mage_Attack,
  /* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RANGED,
  /* mins,maxs  */ '-36 -36 -24', '36 36 50',
  /* model      */ "mage.dpm",
  /* netname    */ "mage",
  /* fullname   */ _("Mage")
- );
+ ) {
+ #ifndef MENUQC
 -    this.monster_func = m_mage;
++    this.monster_func = M_Mage;
++    this.monster_func(MR_PRECACHE);
+ #endif
+ }
  
- #else
  #ifdef SVQC
  float autocvar_g_monster_mage_health;
 +float autocvar_g_monster_mage_damageforcescale = 0.5;
  float autocvar_g_monster_mage_attack_spike_damage;
  float autocvar_g_monster_mage_attack_spike_radius;
  float autocvar_g_monster_mage_attack_spike_delay;
@@@ -236,9 -236,9 +241,9 @@@ void M_Mage_Defend_Heal(
                }
                else
                {
-                       pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1);
+                       Send_Effect("healing_fx", head.origin, '0 0 0', 1);
                        head.health = bound(0, head.health + (autocvar_g_monster_mage_heal_allies), head.max_health);
 -                      if(!(head.spawnflags & MONSTERFLAG_INVINCIBLE))
 +                      if(!(head.spawnflags & MONSTERFLAG_INVINCIBLE) && head.sprite)
                                WaypointSprite_UpdateHealth(head.sprite, head.health);
                }
        }
        }
  }
  
 -void mage_push()
 +void M_Mage_Attack_Push()
  {
-       sound(self, CH_SHOTS, "weapons/tagexp1.wav", 1, ATTEN_NORM);
+       sound(self, CH_SHOTS, W_Sound("tagexp1"), 1, ATTEN_NORM);
        RadiusDamage (self, self, (autocvar_g_monster_mage_attack_push_damage), (autocvar_g_monster_mage_attack_push_damage), (autocvar_g_monster_mage_attack_push_radius), world, world, (autocvar_g_monster_mage_attack_push_force), DEATH_MONSTER_MAGE, self.enemy);
-       pointparticles(particleeffectnum("TE_EXPLOSION"), self.origin, '0 0 0', 1);
+       Send_Effect("TE_EXPLOSION", self.origin, '0 0 0', 1);
  
 -      self.frame = mage_anim_attack;
 +      setanim(self, self.anim_shoot, true, true, true);
        self.attack_finished_single = time + (autocvar_g_monster_mage_attack_push_delay);
  }
  
@@@ -338,11 -337,17 +343,11 @@@ float M_Mage_Attack(float attack_type
        return false;
  }
  
- void spawnfunc_monster_mage() { Monster_Spawn(MON_MAGE); }
 -void spawnfunc_monster_mage()
 -{
 -      self.classname = "monster_mage";
 -
 -      if(!monster_initialize(MON_MAGE.monsterid)) { remove(self); return; }
 -}
++void spawnfunc_monster_mage() { Monster_Spawn(MON_MAGE.monsterid); }
  
 -// compatibility with old spawns
 -void spawnfunc_monster_shalrath() { spawnfunc_monster_mage(); }
 +#endif // SVQC
  
 -float m_mage(float req)
 +bool M_Mage(int req)
  {
        switch(req)
        {
                case MR_SETUP:
                {
                        if(!self.health) self.health = (autocvar_g_monster_mage_health);
 +                      if(!self.speed) { self.speed = (autocvar_g_monster_mage_speed_walk); }
 +                      if(!self.speed2) { self.speed2 = (autocvar_g_monster_mage_speed_run); }
 +                      if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_mage_speed_stop); }
 +                      if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_mage_damageforcescale); }
  
                        self.monster_loot = spawnfunc_item_health_large;
 -                      self.monster_attackfunc = mage_attack;
 -                      self.frame = mage_anim_walk;
++                      self.monster_attackfunc = M_Mage_Attack;
  
                        return true;
                }
                case MR_PRECACHE:
                {
                        precache_model("models/monsters/mage.dpm");
-                       precache_sound ("weapons/grenade_impact.wav");
-                       precache_sound ("weapons/tagexp1.wav");
+                       precache_sound (W_Sound("grenade_impact"));
+                       precache_sound (W_Sound("tagexp1"));
                        return true;
                }
 +              #endif
        }
  
        return true;
  }
--
- #endif // REGISTER_MONSTER
 -#endif // SVQC
 -#ifdef CSQC
 -float m_mage(float req)
 -{
 -      switch(req)
 -      {
 -              case MR_PRECACHE:
 -              {
 -                      return true;
 -              }
 -      }
 -
 -      return true;
 -}
 -
 -#endif // CSQC
@@@ -1,23 -1,24 +1,28 @@@
- #ifdef REGISTER_MONSTER
- REGISTER_MONSTER(
+ #ifndef MENUQC
 -bool m_shambler(int);
++bool M_Shambler(int);
+ #endif
+ REGISTER_MONSTER_SIMPLE(
  /* MON_##id   */ SHAMBLER,
- /* functions  */ M_Shambler, M_Shambler_Attack,
  /* spawnflags */ MONSTER_SIZE_BROKEN | MON_FLAG_SUPERMONSTER | MON_FLAG_MELEE | MON_FLAG_RANGED,
  /* mins,maxs  */ '-41 -41 -31', '41 41 65',
  /* model      */ "shambler.mdl",
  /* netname    */ "shambler",
  /* fullname   */ _("Shambler")
- );
+ ) {
+ #ifndef MENUQC
 -      this.monster_func = m_shambler;
++      this.monster_func = M_Shambler;
++      this.monster_func(MR_PRECACHE);
+ #endif
+ }
  
- #else
  #ifdef SVQC
  float autocvar_g_monster_shambler_health;
 +float autocvar_g_monster_shambler_damageforcescale = 0.1;
  float autocvar_g_monster_shambler_attack_smash_damage;
 +float autocvar_g_monster_shambler_attack_smash_range;
  float autocvar_g_monster_shambler_attack_claw_damage;
  float autocvar_g_monster_shambler_attack_lightning_damage;
 +float autocvar_g_monster_shambler_attack_lightning_damage_zap = 15;
  float autocvar_g_monster_shambler_attack_lightning_force;
  float autocvar_g_monster_shambler_attack_lightning_radius;
  float autocvar_g_monster_shambler_attack_lightning_radius_zap;
@@@ -41,28 -40,26 +46,28 @@@ const float shambler_anim_death            = 8
  
  .float shambler_lastattack; // delay attacks separately
  
 -void shambler_smash()
 +void M_Shambler_Attack_Smash()
  {
        makevectors(self.angles);
-       pointparticles(particleeffectnum("explosion_medium"), (self.origin + (v_forward * 150)) - ('0 0 1' * self.maxs.z), '0 0 0', 1);
-       sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM);
+       Send_Effect("explosion_medium", (self.origin + (v_forward * 150)) - ('0 0 1' * self.maxs.z), '0 0 0', 1);
+       sound(self, CH_SHOTS, W_Sound("rocket_impact"), VOL_BASE, ATTEN_NORM);
  
 -      tracebox(self.origin + v_forward * 50, self.mins * 0.5, self.maxs * 0.5, self.origin + v_forward * 500, MOVE_NORMAL, self);
 +      // RadiusDamage does NOT support custom starting location, which means we must use this hack...
 +
 +      tracebox(self.origin + v_forward * 50, self.mins * 0.5, self.maxs * 0.5, self.origin + v_forward * autocvar_g_monster_shambler_attack_smash_range, MOVE_NORMAL, self);
  
        if(trace_ent.takedamage)
 -              Damage(trace_ent, self, self, (autocvar_g_monster_shambler_attack_smash_damage) * Monster_SkillModifier(), DEATH_MONSTER_SHAMBLER_SMASH, trace_ent.origin, normalize(trace_ent.origin - self.origin));
 +              Damage(trace_ent, self, self, (autocvar_g_monster_shambler_attack_smash_damage) * MONSTER_SKILLMOD(self), DEATH_MONSTER_SHAMBLER_SMASH, trace_ent.origin, normalize(trace_ent.origin - self.origin));
  }
  
 -void shambler_swing()
 +void M_Shambler_Attack_Swing()
  {
        float r = (random() < 0.5);
 -      monster_melee(self.enemy, (autocvar_g_monster_shambler_attack_claw_damage), ((r) ? shambler_anim_swingr : shambler_anim_swingl), self.attack_range, 0.8, DEATH_MONSTER_SHAMBLER_CLAW, true);
 -      if(r)
 +      if(r && Monster_Attack_Melee(self.enemy, (autocvar_g_monster_shambler_attack_claw_damage), ((r) ? self.anim_melee2 : self.anim_melee3), self.attack_range, 0.8, DEATH_MONSTER_SHAMBLER_CLAW, true))
        {
 -              defer(0.5, shambler_swing);
 +              Monster_Delay(1, 0, 0.5, M_Shambler_Attack_Swing);
                self.attack_finished_single += 0.5;
 +              self.anim_finished = self.attack_finished_single;
        }
  }
  
@@@ -206,10 -197,14 +211,10 @@@ float M_Shambler_Attack(float attack_ty
        return false;
  }
  
- void spawnfunc_monster_shambler() { Monster_Spawn(MON_SHAMBLER); }
 -void spawnfunc_monster_shambler()
 -{
 -      self.classname = "monster_shambler";
 -
 -      if(!monster_initialize(MON_SHAMBLER.monsterid)) { remove(self); return; }
 -}
++void spawnfunc_monster_shambler() { Monster_Spawn(MON_SHAMBLER.monsterid); }
 +#endif // SVQC
  
 -float m_shambler(float req)
 +bool M_Shambler(int req)
  {
        switch(req)
        {
                {
                        if(!self.health) self.health = (autocvar_g_monster_shambler_health);
                        if(!self.attack_range) self.attack_range = 150;
 +                      if(!self.speed) { self.speed = (autocvar_g_monster_shambler_speed_walk); }
 +                      if(!self.speed2) { self.speed2 = (autocvar_g_monster_shambler_speed_run); }
 +                      if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_shambler_speed_stop); }
 +                      if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_shambler_damageforcescale); }
  
                        self.monster_loot = spawnfunc_item_health_mega;
-                       self.weapon = WEP_ELECTRO; // matches attacks better than WEP_VORTEX
 -                      self.monster_attackfunc = shambler_attack;
 -                      self.frame = shambler_anim_stand;
 -                      self.weapon = WEP_VORTEX.m_id;
++                      self.weapon = WEP_ELECTRO.m_id; // matches attacks better than WEP_VORTEX
 +
 +                      setanim(self, self.anim_shoot, false, true, true);
 +                      self.spawn_time = self.animstate_endtime;
 +                      self.spawnshieldtime = self.spawn_time;
++                      self.monster_attackfunc = M_Shambler_Attack;
  
                        return true;
                }
  
        return true;
  }
--
- #endif // REGISTER_MONSTER
 -#endif // SVQC
 -#ifdef CSQC
 -float m_shambler(float req)
 -{
 -      switch(req)
 -      {
 -              case MR_PRECACHE:
 -              {
 -                      return true;
 -              }
 -      }
 -
 -      return true;
 -}
 -
 -#endif // CSQC
@@@ -1,18 -1,21 +1,23 @@@
- #ifdef REGISTER_MONSTER
- REGISTER_MONSTER(
+ #ifndef MENUQC
 -bool m_spider(int);
++bool M_Spider(int);
+ #endif
+ REGISTER_MONSTER_SIMPLE(
  /* MON_##id   */ SPIDER,
- /* functions  */ M_Spider, M_Spider_Attack,
  /* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RANGED,
  /* mins,maxs  */ '-18 -18 -25', '18 18 30',
  /* model      */ "spider.dpm",
  /* netname    */ "spider",
  /* fullname   */ _("Spider")
- );
+ ) {
+ #ifndef MENUQC
 -      this.monster_func = m_spider;
++      this.monster_func = M_Spider;
++      this.monster_func(MR_PRECACHE);
+ #endif
+ }
  
- #else
  #ifdef SVQC
  float autocvar_g_monster_spider_health;
 +float autocvar_g_monster_spider_damageforcescale = 0.6;
  float autocvar_g_monster_spider_attack_bite_damage;
  float autocvar_g_monster_spider_attack_bite_delay;
  float autocvar_g_monster_spider_attack_web_damagetime;
@@@ -92,7 -93,7 +97,7 @@@ void M_Spider_Attack_Web(
        CSQCProjectile(proj, true, PROJECTILE_ELECTRO, true);
  }
  
- float M_Spider_Attack(float attack_type)
 -float spider_attack(float attack_type)
++bool M_Spider_Attack(int attack_type)
  {
        switch(attack_type)
        {
        return false;
  }
  
- void spawnfunc_monster_spider() { Monster_Spawn(MON_SPIDER); }
 -void spawnfunc_monster_spider()
 -{
 -      self.classname = "monster_spider";
 -
 -      if(!monster_initialize(MON_SPIDER.monsterid)) { remove(self); return; }
 -}
++void spawnfunc_monster_spider() { Monster_Spawn(MON_SPIDER.monsterid); }
 +#endif // SVQC
  
 -float m_spider(float req)
 +bool M_Spider(int req)
  {
        switch(req)
        {
                case MR_SETUP:
                {
                        if(!self.health) self.health = (autocvar_g_monster_spider_health);
 +                      if(!self.speed) { self.speed = (autocvar_g_monster_spider_speed_walk); }
 +                      if(!self.speed2) { self.speed2 = (autocvar_g_monster_spider_speed_run); }
 +                      if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_spider_speed_stop); }
 +                      if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_spider_damageforcescale); }
  
                        self.monster_loot = spawnfunc_item_health_medium;
 -                      self.monster_attackfunc = spider_attack;
 -                      self.frame = spider_anim_idle;
++                      self.monster_attackfunc = M_Spider_Attack;
  
                        return true;
                }
                case MR_PRECACHE:
                {
                        precache_model("models/monsters/spider.dpm");
-                       precache_sound ("weapons/electro_fire2.wav");
+                       precache_sound (W_Sound("electro_fire2"));
                        return true;
                }
 +              #endif
        }
  
        return true;
  }
--
- #endif // REGISTER_MONSTER
 -#endif // SVQC
 -#ifdef CSQC
 -float m_spider(float req)
 -{
 -      switch(req)
 -      {
 -              case MR_PRECACHE:
 -              {
 -                      return true;
 -              }
 -      }
 -
 -      return true;
 -}
 -
 -#endif // CSQC
@@@ -1,18 -1,21 +1,23 @@@
- #ifdef REGISTER_MONSTER
- REGISTER_MONSTER(
+ #ifndef MENUQC
 -bool m_wyvern(int);
++bool M_Wyvern(int);
+ #endif
+ REGISTER_MONSTER_SIMPLE(
  /* MON_##id   */ WYVERN,
- /* functions  */ M_Wyvern, M_Wyvern_Attack,
  /* spawnflags */ MONSTER_TYPE_FLY | MONSTER_SIZE_BROKEN | MON_FLAG_RANGED,
  /* mins,maxs  */ '-20 -20 -58', '20 20 20',
  /* model      */ "wizard.mdl",
  /* netname    */ "wyvern",
  /* fullname   */ _("Wyvern")
- );
+ ) {
+ #ifndef MENUQC
 -      this.monster_func = m_wyvern;
++      this.monster_func = M_Wyvern;
++      this.monster_func(MR_PRECACHE);
+ #endif
+ }
  
- #else
  #ifdef SVQC
  float autocvar_g_monster_wyvern_health;
 +float autocvar_g_monster_wyvern_damageforcescale = 0.6;
  float autocvar_g_monster_wyvern_attack_fireball_damage;
  float autocvar_g_monster_wyvern_attack_fireball_edgedamage;
  float autocvar_g_monster_wyvern_attack_fireball_damagetime;
@@@ -96,11 -96,17 +101,10 @@@ float M_Wyvern_Attack(float attack_type
        return false;
  }
  
- void spawnfunc_monster_wyvern() { Monster_Spawn(MON_WYVERN); }
 -void spawnfunc_monster_wyvern()
 -{
 -      self.classname = "monster_wyvern";
 -
 -      if(!monster_initialize(MON_WYVERN.monsterid)) { remove(self); return; }
 -}
--
 -// compatibility with old spawns
 -void spawnfunc_monster_wizard() { spawnfunc_monster_wyvern(); }
++void spawnfunc_monster_wyvern() { Monster_Spawn(MON_WYVERN.monsterid); }
 +#endif // SVQC
  
 -float m_wyvern(float req)
 +bool M_Wyvern(int req)
  {
        switch(req)
        {
                case MR_SETUP:
                {
                        if(!self.health) self.health = (autocvar_g_monster_wyvern_health);
 +                      if(!self.speed) { self.speed = (autocvar_g_monster_wyvern_speed_walk); }
 +                      if(!self.speed2) { self.speed2 = (autocvar_g_monster_wyvern_speed_run); }
 +                      if(!self.stopspeed) { self.stopspeed = (autocvar_g_monster_wyvern_speed_stop); }
 +                      if(!self.damageforcescale) { self.damageforcescale = (autocvar_g_monster_wyvern_damageforcescale); }
  
                        self.monster_loot = spawnfunc_item_cells;
 -                      self.monster_attackfunc = wyvern_attack;
 -                      self.frame = wyvern_anim_hover;
++                      self.monster_attackfunc = M_Wyvern_Attack;
  
                        return true;
                }
  
        return true;
  }
--
- #endif // REGISTER_MONSTER
 -#endif // SVQC
 -#ifdef CSQC
 -float m_wyvern(float req)
 -{
 -      switch(req)
 -      {
 -              case MR_PRECACHE:
 -              {
 -                      return true;
 -              }
 -      }
 -
 -      return true;
 -}
 -
 -#endif // CSQC
@@@ -1,18 -1,21 +1,23 @@@
- #ifdef REGISTER_MONSTER
- REGISTER_MONSTER(
+ #ifndef MENUQC
 -bool m_zombie(int);
++bool M_Zombie(int);
+ #endif
+ REGISTER_MONSTER_SIMPLE(
  /* MON_##id   */ ZOMBIE,
- /* functions  */ M_Zombie, M_Zombie_Attack,
  /* spawnflags */ MON_FLAG_MELEE,
  /* mins,maxs  */ '-18 -18 -25', '18 18 47',
  /* model      */ "zombie.dpm",
  /* netname    */ "zombie",
  /* fullname   */ _("Zombie")
- );
+ ) {
+ #ifndef MENUQC
 -      this.monster_func = m_zombie;
++      this.monster_func = M_Zombie;
++      this.monster_func(MR_PRECACHE);
+ #endif
+ }
  
- #else
  #ifdef SVQC
  float autocvar_g_monster_zombie_health;
 +float autocvar_g_monster_zombie_damageforcescale = 0.55;
  float autocvar_g_monster_zombie_attack_melee_damage;
  float autocvar_g_monster_zombie_attack_melee_delay;
  float autocvar_g_monster_zombie_attack_leap_damage;
@@@ -133,10 -130,14 +138,10 @@@ float M_Zombie_Attack(float attack_type
        return false;
  }
  
- void spawnfunc_monster_zombie() { Monster_Spawn(MON_ZOMBIE); }
 -void spawnfunc_monster_zombie()
 -{
 -      self.classname = "monster_zombie";
 -
 -      if(!monster_initialize(MON_ZOMBIE.monsterid)) { remove(self); return; }
 -}
++void spawnfunc_monster_zombie() { Monster_Spawn(MON_ZOMBIE.monsterid); }
 +#endif // SVQC
  
 -float m_zombie(float req)
 +bool M_Zombie(int req)
  {
        switch(req)
        {
                        self.spawnflags |= MONSTER_RESPAWN_DEATHPOINT;
  
                        self.monster_loot = spawnfunc_item_health_medium;
 -                      self.monster_attackfunc = zombie_attack;
 -                      self.frame = zombie_anim_spawn;
 -                      self.spawn_time = time + 2.1;
++                      self.monster_attackfunc = M_Zombie_Attack;
                        self.spawnshieldtime = self.spawn_time;
                        self.respawntime = 0.2;
 +                      self.damageforcescale = 0.0001; // no push while spawning
 +
 +                      setanim(self, self.anim_spawn, false, true, true);
 +                      self.spawn_time = self.animstate_endtime;
  
                        return true;
                }
  
        return true;
  }
--
- #endif // REGISTER_MONSTER
 -#endif // SVQC
 -#ifdef CSQC
 -float m_zombie(float req)
 -{
 -      switch(req)
 -      {
 -              case MR_PRECACHE:
 -              {
 -                      return true;
 -              }
 -      }
 -
 -      return true;
 -}
 -
 -#endif // CSQC
        #include "../triggers/triggers.qh"
      #include "../../csqcmodellib/sv_model.qh"
      #include "../../server/round_handler.qh"
-     #include "../../server/tturrets/include/turrets.qh"
  #endif
  
 -// =========================
 -//    SVQC Monster Properties
 -// =========================
 -
 +void monsters_setstatus()
 +{
 +      self.stat_monsters_total = monsters_total;
 +      self.stat_monsters_killed = monsters_killed;
 +}
  
  void monster_dropitem()
  {
        }
  }
  
 -float Monster_SkillModifier()
 -{
 -      float t = 0.5+self.monster_skill*((1.2-0.3)/10);
 -
 -      return t;
 -}
 -
 -float monster_isvalidtarget (entity targ, entity ent)
 +void monster_makevectors(entity e)
  {
-       if(self.flags & FL_MONSTER)
 -      if(!targ || !ent)
 -              return false; // someone doesn't exist
 -
 -      if(targ == ent)
 -              return false; // don't attack ourselves
 -
 -      //traceline(ent.origin, targ.origin, MOVE_NORMAL, ent);
 -
 -      //if(trace_ent != targ)
 -              //return false;
 -
 -      if(IS_VEHICLE(targ))
 -      if(!((get_monsterinfo(ent.monsterid)).spawnflags & MON_FLAG_RANGED))
 -              return false; // melee attacks are useless against vehicles
 -
 -      if(time < game_starttime)
 -              return false; // monsters do nothing before the match has started
 -
 -      if(targ.takedamage == DAMAGE_NO)
 -              return false; // enemy can't be damaged
 -
 -      if(targ.items & IT_INVISIBILITY)
 -              return false; // enemy is invisible
 -
 -      if(substring(targ.classname, 0, 10) == "onslaught_")
 -              return false; // don't attack onslaught targets
 -
 -      if(IS_SPEC(targ) || IS_OBSERVER(targ))
 -              return false; // enemy is a spectator
 -
 -      if(!IS_VEHICLE(targ))
 -      if(targ.deadflag != DEAD_NO || ent.deadflag != DEAD_NO || targ.health <= 0 || ent.health <= 0)
 -              return false; // enemy/self is dead
++      if(IS_MONSTER(self))
 +      {
 +              vector v;
  
 -      if(ent.monster_owner == targ)
 -              return false; // don't attack our master
 +              v = e.origin + (e.mins + e.maxs) * 0.5;
 +              self.v_angle = vectoangles(v - (self.origin + self.view_ofs));
 +              self.v_angle_x = -self.v_angle_x;
 +      }
  
 -      if(targ.monster_owner == ent)
 -              return false; // don't attack our pet
 +      makevectors(self.v_angle);
 +}
  
 -      if(!IS_VEHICLE(targ))
 -      if(targ.flags & FL_NOTARGET)
 -              return false; // enemy can't be targeted
 +// ===============
 +// Target handling
 +// ===============
  
 -      if(!autocvar_g_monsters_typefrag)
 -      if(targ.BUTTON_CHAT)
 -              return false; // no typefragging!
 +bool Monster_ValidTarget(entity mon, entity player)
 +{
 +      // ensure we're not checking nonexistent monster/target
 +      if(!mon || !player) { return false; }
 +
 +      if((player == mon)
 +      || (autocvar_g_monsters_lineofsight && !checkpvs(mon.origin + mon.view_ofs, player)) // enemy cannot be seen
-       || ((player.vehicle_flags & VHF_ISVEHICLE) && !((get_monsterinfo(mon.monsterid)).spawnflags & MON_FLAG_RANGED)) // melee vs vehicle is useless
++      || (IS_VEHICLE(player) && !((get_monsterinfo(mon.monsterid)).spawnflags & MON_FLAG_RANGED)) // melee vs vehicle is useless
 +      || (time < game_starttime) // monsters do nothing before match has started
 +      || (player.takedamage == DAMAGE_NO)
 +      || (player.items & IT_INVISIBILITY)
 +      || (IS_SPEC(player) || IS_OBSERVER(player)) // don't attack spectators
-       || (!(player.vehicle_flags & VHF_ISVEHICLE) && (player.deadflag != DEAD_NO || mon.deadflag != DEAD_NO || player.health <= 0 || mon.health <= 0))
++      || (!IS_VEHICLE(player) && (player.deadflag != DEAD_NO || mon.deadflag != DEAD_NO || player.health <= 0 || mon.health <= 0))
 +      || (mon.monster_follow == player || player.monster_follow == mon)
-       || (!(player.vehicle_flags & VHF_ISVEHICLE) && (player.flags & FL_NOTARGET))
++      || (!IS_VEHICLE(player) && (player.flags & FL_NOTARGET))
 +      || (!autocvar_g_monsters_typefrag && player.BUTTON_CHAT)
 +      || (SAME_TEAM(player, mon))
 +      || (player.frozen)
 +      || (player.alpha != 0 && player.alpha < 0.5)
 +      )
 +      {
 +              // if any of the above checks fail, target is not valid
 +              return false;
 +      }
  
 -      if(SAME_TEAM(targ, ent))
 -              return false; // enemy is on our team
 +      traceline(mon.origin + self.view_ofs, player.origin, 0, mon);
  
 -      if (targ.frozen)
 -              return false; // ignore frozen
 +      if((trace_fraction < 1) && (trace_ent != player))
 +              return false;
  
 -      if(autocvar_g_monsters_target_infront || (ent.spawnflags & MONSTERFLAG_INFRONT))
 -      if(ent.enemy != targ)
 +      if(autocvar_g_monsters_target_infront || (mon.spawnflags & MONSTERFLAG_INFRONT))
 +      if(mon.enemy != player)
        {
                float dot;
  
@@@ -305,7 -258,7 +304,7 @@@ void Monster_Sounds_Clear(
        return string_null;
  }
  
- float Monster_Sounds_Load(string f, float first)
 -float LoadMonsterSounds(string f, float first)
++bool Monster_Sounds_Load(string f, int first)
  {
        float fh;
        string s;
        fh = fopen(f, FILE_READ);
        if(fh < 0)
        {
-               dprint("Monster sound file not found: ", f, "\n");
+               LOG_TRACE("Monster sound file not found: ", f, "\n");
 -              return 0;
 +              return false;
        }
        while((s = fgets(fh)))
        {
@@@ -354,121 -311,45 +353,139 @@@ void Monster_Sound(.string samplefield
        self.msound_delay = time + sound_delay;
  }
  
 -void monster_makevectors(entity e)
 +
 +// =======================
 +// Monster attack handlers
 +// =======================
 +
 +float Monster_Attack_Melee(entity targ, float damg, vector anim, float er, float animtime, int deathtype, float dostop)
  {
 -      vector v;
 +      if(dostop && (self.flags & FL_MONSTER)) { self.state = MONSTER_ATTACK_MELEE; }
  
 -      v = e.origin + (e.mins + e.maxs) * 0.5;
 -      self.v_angle = vectoangles(v - (self.origin + self.view_ofs));
 -      self.v_angle_x = -self.v_angle.x;
 +      setanim(self, anim, false, true, false);
  
 -      makevectors(self.v_angle);
 +      if(self.animstate_endtime > time && (self.flags & FL_MONSTER))
 +              self.attack_finished_single = self.anim_finished = self.animstate_endtime;
 +      else
 +              self.attack_finished_single = self.anim_finished = time + animtime;
 +
 +      monster_makevectors(targ);
 +
 +      traceline(self.origin + self.view_ofs, self.origin + v_forward * er, 0, self);
 +
 +      if(trace_ent.takedamage)
 +              Damage(trace_ent, self, self, damg * MONSTER_SKILLMOD(self), deathtype, trace_ent.origin, normalize(trace_ent.origin - self.origin));
 +
 +      return true;
  }
  
 -float monster_melee(entity targ, float damg, float anim, float er, float anim_finished, int deathtype, float dostop)
 +float Monster_Attack_Leap_Check(vector vel)
  {
 -      if (self.health <= 0)
 -              return false; // attacking while dead?!
 +      if(self.state && (self.flags & FL_MONSTER))
 +              return false; // already attacking
 +      if(!(self.flags & FL_ONGROUND))
 +              return false; // not on the ground
 +      if(self.health <= 0)
 +              return false; // called when dead?
 +      if(time < self.attack_finished_single)
 +              return false; // still attacking
 +
 +      vector old = self.velocity;
 +
 +      self.velocity = vel;
 +      tracetoss(self, self);
 +      self.velocity = old;
 +      if (trace_ent != self.enemy)
 +              return false;
 +
 +      return true;
 +}
 +
 +bool Monster_Attack_Leap(vector anm, void() touchfunc, vector vel, float animtime)
 +{
 +      if(!Monster_Attack_Leap_Check(vel))
 +              return false;
 +
 +      setanim(self, anm, false, true, false);
 +
 +      if(self.animstate_endtime > time && (self.flags & FL_MONSTER))
 +              self.attack_finished_single = self.anim_finished = self.animstate_endtime;
 +      else
 +              self.attack_finished_single = self.anim_finished = time + animtime;
  
 -      if(dostop)
 +      if(self.flags & FL_MONSTER)
 +              self.state = MONSTER_ATTACK_RANGED;
 +      self.touch = touchfunc;
 +      self.origin_z += 1;
 +      self.velocity = vel;
 +      self.flags &= ~FL_ONGROUND;
 +
 +      return true;
 +}
 +
 +void Monster_Attack_Check(entity e, entity targ)
 +{
 +      if((e == world || targ == world)
 +      || (!e.monster_attackfunc)
 +      || (time < e.attack_finished_single)
 +      ) { return; }
 +
 +      float targ_vlen = vlen(targ.origin - e.origin);
 +
 +      if(targ_vlen <= e.attack_range)
        {
 -              self.velocity_x = 0;
 -              self.velocity_y = 0;
 -              self.state = MONSTER_STATE_ATTACK_MELEE;
 +              float attack_success = e.monster_attackfunc(MONSTER_ATTACK_MELEE);
 +              if(attack_success == 1)
 +                      Monster_Sound(monstersound_melee, 0, false, CH_VOICE);
 +              else if(attack_success > 0)
 +                      return;
        }
  
 -      self.frame = anim;
 +      if(targ_vlen > e.attack_range)
 +      {
 +              float attack_success = e.monster_attackfunc(MONSTER_ATTACK_RANGED);
 +              if(attack_success == 1)
 +                      Monster_Sound(monstersound_melee, 0, false, CH_VOICE);
 +              else if(attack_success > 0)
 +                      return;
 +      }
 +}
  
 -      if(anim_finished != 0)
 -              self.attack_finished_single = time + anim_finished;
  
 -      monster_makevectors(targ);
 +// ======================
 +// Main monster functions
 +// ======================
  
 -      traceline(self.origin + self.view_ofs, self.origin + v_forward * er, 0, self);
++void Monster_UpdateModel()
++{
++      // assume some defaults
++      /*self.anim_idle   = animfixfps(self, '0 1 0.01', '0 0 0');
++      self.anim_walk   = animfixfps(self, '1 1 0.01', '0 0 0');
++      self.anim_run    = animfixfps(self, '2 1 0.01', '0 0 0');
++      self.anim_fire1  = animfixfps(self, '3 1 0.01', '0 0 0');
++      self.anim_fire2  = animfixfps(self, '4 1 0.01', '0 0 0');
++      self.anim_melee  = animfixfps(self, '5 1 0.01', '0 0 0');
++      self.anim_pain1  = animfixfps(self, '6 1 0.01', '0 0 0');
++      self.anim_pain2  = animfixfps(self, '7 1 0.01', '0 0 0');
++      self.anim_die1   = animfixfps(self, '8 1 0.01', '0 0 0');
++      self.anim_die2   = animfixfps(self, '9 1 0.01', '0 0 0');*/
++
++      // then get the real values
++      MON_ACTION(self.monsterid, MR_ANIM);
++}
 -      if(trace_ent.takedamage)
 -              Damage(trace_ent, self, self, damg * Monster_SkillModifier(), deathtype, trace_ent.origin, normalize(trace_ent.origin - self.origin));
 +void Monster_Touch()
 +{
 +      if(other == world) { return; }
  
 -      return true;
 +      if(other.monster_attack)
 +      if(self.enemy != other)
-       if(!(self.flags & FL_MONSTER))
++      if(!IS_MONSTER(other))
 +      if(Monster_ValidTarget(self, other))
 +              self.enemy = other;
  }
  
 -void Monster_CheckMinibossFlag ()
 +void Monster_Miniboss_Check()
  {
        if(MUTATOR_CALLHOOK(MonsterCheckBossFlag))
                return;
        }
  }
  
- float Monster_Respawn_Check()
 -float Monster_CanRespawn(entity ent)
++bool Monster_Respawn_Check()
  {
-       if(self.spawnflags & MONSTERFLAG_NORESPAWN) { return false; }
-       if(!autocvar_g_monsters_respawn) { return false; }
 -      other = ent;
 -      if(ent.deadflag == DEAD_DEAD) // don't call when monster isn't dead
 -      if(MUTATOR_CALLHOOK(MonsterRespawn, ent))
++      if(self.deadflag == DEAD_DEAD) // don't call when monster isn't dead
++      if(MUTATOR_CALLHOOK(MonsterRespawn, self))
+               return true; // enabled by a mutator
 -      if(ent.spawnflags & MONSTERFLAG_NORESPAWN)
++      if(self.spawnflags & MONSTERFLAG_NORESPAWN)
+               return false;
+       if(!autocvar_g_monsters_respawn)
+               return false;
  
        return true;
  }
@@@ -888,19 -835,24 +907,19 @@@ void Monster_Move(float runspeed, floa
                self.angles_y += turny;
        }
  
 -      monster_checkattack(self, self.enemy);
 +      Monster_Attack_Check(self, self.enemy);
  }
  
 -void monster_remove(entity mon)
 +void Monster_Remove(entity mon)
  {
 -      if(!mon)
 -              return; // nothing to remove
 -
 -      Send_Effect("item_pickup", mon.origin, '0 0 0', 1);
 +      if(!mon) { return; }
  
-       if(!MUTATOR_CALLHOOK(MonsterRemove))
-               pointparticles(particleeffectnum("item_pickup"), mon.origin, '0 0 0', 1);
 -      if(mon.weaponentity)
 -              remove(mon.weaponentity);
 -
 -      if(mon.iceblock)
 -              remove(mon.iceblock);
++      if(!MUTATOR_CALLHOOK(MonsterRemove, mon))
++              Send_Effect("item_pickup", mon.origin, '0 0 0', 1);
  
 +      if(mon.weaponentity) { remove(mon.weaponentity); }
 +      if(mon.iceblock) { remove(mon.iceblock); }
        WaypointSprite_Kill(mon.sprite);
 -
        remove(mon);
  }
  
@@@ -1108,65 -1053,49 +1126,102 @@@ void Monster_Damage(entity inflictor, e
        }
  }
  
 -void monster_setupcolors(entity mon)
 +// don't check for enemies, just keep walking in a straight line
 +void Monster_Move_2D(float mspeed, float allow_jumpoff)
  {
 -      if(IS_PLAYER(mon.monster_owner))
 -              mon.colormap = mon.monster_owner.colormap;
 -      else if(teamplay && mon.team)
 -              mon.colormap = 1024 + (mon.team - 1) * 17;
 -      else
 +      if(gameover || (round_handler_IsActive() && !round_handler_IsRoundStarted()) || self.draggedby != world || time < game_starttime || (autocvar_g_campaign && !campaign_bots_may_start) || time < self.spawn_time)
        {
 -              if(mon.monster_skill <= MONSTER_SKILL_EASY)
 -                      mon.colormap = 1029;
 -              else if(mon.monster_skill <= MONSTER_SKILL_MEDIUM)
 -                      mon.colormap = 1027;
 -              else if(mon.monster_skill <= MONSTER_SKILL_HARD)
 -                      mon.colormap = 1038;
 -              else if(mon.monster_skill <= MONSTER_SKILL_INSANE)
 -                      mon.colormap = 1028;
 -              else if(mon.monster_skill <= MONSTER_SKILL_NIGHTMARE)
 -                      mon.colormap = 1032;
 -              else
 -                      mon.colormap = 1024;
 +              mspeed = 0;
 +              if(time >= self.spawn_time)
 +                      setanim(self, self.anim_idle, true, false, false);
 +              movelib_beak_simple(0.6);
 +              return;
        }
 -}
  
 -void monster_changeteam(entity ent, float newteam)
 -{
 -      if(!teamplay) { return; }
 +      float reverse = FALSE;
 +      vector a, b;
 +      
 +      makevectors(self.angles);
 +      a = self.origin + '0 0 16';
 +      b = self.origin + '0 0 16' + v_forward * 32;
 +      
 +      traceline(a, b, MOVE_NORMAL, self);
 +      
 +      if(trace_fraction != 1.0)
 +      {
 +              reverse = TRUE;
 +              
 +              if(trace_ent)
 +              if(IS_PLAYER(trace_ent) && !(trace_ent.items & IT_STRENGTH))
 +                      reverse = FALSE;
 +      }
 +      
 +      // TODO: fix this... tracing is broken if the floor is thin
 +      /*
 +      if(!allow_jumpoff)
 +      {
 +              a = b - '0 0 32';
 +              traceline(b, a, MOVE_WORLDONLY, self);
 +              if(trace_fraction == 1.0)
 +                      reverse = TRUE;
 +      } */
 +      
 +      if(reverse)
 +      {
 +              self.angles_y = anglemods(self.angles_y - 180);
 +              makevectors(self.angles);
 +      }
 +      
 +      movelib_move_simple_gravity(v_forward, mspeed, 1);
-       
 -      ent.team = newteam;
 -      ent.monster_attack = true; // new team, activate attacking
 -      monster_setupcolors(ent);
 +      if(time > self.pain_finished)
 +      if(time > self.attack_finished_single)
 +      if(vlen(self.velocity) > 10)
 +              setanim(self, self.anim_walk, true, false, false);
 +      else
 +              setanim(self, self.anim_idle, true, false, false);
 +}
  
 -      if(ent.sprite)
++void Monster_Anim()
++{
++      int deadbits = (self.anim_state & (ANIMSTATE_DEAD1 | ANIMSTATE_DEAD2));
++      if(self.deadflag)
+       {
 -              WaypointSprite_UpdateTeamRadar(ent.sprite, RADARICON_DANGER, ((newteam) ? Team_ColorRGB(newteam) : '1 0 0'));
 -
 -              ent.sprite.team = newteam;
 -              ent.sprite.SendFlags |= 1;
++              if (!deadbits)
++              {
++                      // Decide on which death animation to use.
++                      if(random() < 0.5)
++                              deadbits = ANIMSTATE_DEAD1;
++                      else
++                              deadbits = ANIMSTATE_DEAD2;
++              }
++      }
++      else
++      {
++              // Clear a previous death animation.
++              deadbits = 0;
++      }
++      int animbits = deadbits;
++      if(self.frozen)
++              animbits |= ANIMSTATE_FROZEN;
++      if(self.crouch)
++              animbits |= ANIMSTATE_DUCK; // not that monsters can crouch currently...
++      animdecide_setstate(self, animbits, false);
++      animdecide_setimplicitstate(self, (self.flags & FL_ONGROUND));
++
++      /* // weapon entities for monsters?
++      if (self.weaponentity)
++      {
++              updateanim(self.weaponentity);
++              if (!self.weaponentity.animstate_override)
++                      setanim(self.weaponentity, self.weaponentity.anim_idle, true, false, false);
+       }
++      */
+ }
 -void monster_think()
 +void Monster_Think()
  {
 -      self.think = monster_think;
 +      self.think = Monster_Think;
        self.nextthink = self.ticrate;
  
        if(self.monster_lifetime)
                return;
        }
  
 -      MON_ACTION(self.monsterid, MR_THINK);
 +      if(MON_ACTION(self.monsterid, MR_THINK))
 +              Monster_Move(self.speed2, self.speed, self.stopspeed);
 +
++      Monster_Anim();
        CSQCMODEL_AUTOUPDATE();
  }
  
@@@ -1219,21 -1142,18 +1276,21 @@@ float Monster_Spawn_Setup(
        if(teamplay)
                self.monster_attack = true; // we can have monster enemies in team games
  
 -      MonsterSound(monstersound_spawn, 0, false, CH_VOICE);
 +      Monster_Sound(monstersound_spawn, 0, false, CH_VOICE);
  
 -      entity wp = WaypointSprite_Spawn(WP_Monster, 0, 1024, self, '0 0 1' * (self.maxs.z + 15), world, self.team, self, sprite, true, RADARICON_DANGER);
 -      wp.wp_extra = self.monsterid;
 -      wp.colormod = ((self.team) ? Team_ColorRGB(self.team) : '1 0 0');
 -      if(!(self.spawnflags & MONSTERFLAG_INVINCIBLE))
 +      if(autocvar_g_monsters_healthbars)
        {
-               WaypointSprite_Spawn(self.monster_name, 0, 1024, self, '0 0 1' * (self.maxs_z + 15), world, self.team, 
-                                                        self, sprite, true, RADARICON_DANGER, ((self.team) ? Team_ColorRGB(self.team) : '1 0 0'));
 -              WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
 -              WaypointSprite_UpdateHealth(self.sprite, self.health);
++              entity wp = WaypointSprite_Spawn(WP_Monster, 0, 1024, self, '0 0 1' * (self.maxs.z + 15), world, self.team, self, sprite, true, RADARICON_DANGER);
++              wp.wp_extra = self.monsterid;
++              wp.colormod = ((self.team) ? Team_ColorRGB(self.team) : '1 0 0');
 +              if(!(self.spawnflags & MONSTERFLAG_INVINCIBLE))
 +              {
 +                      WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
 +                      WaypointSprite_UpdateHealth(self.sprite, self.health);
 +              }
        }
  
 -      self.think = monster_think;
 +      self.think = Monster_Think;
        self.nextthink = time + self.ticrate;
  
        if(MUTATOR_CALLHOOK(MonsterSpawn))
@@@ -1323,13 -1247,22 +1380,15 @@@ bool Monster_Spawn(int mon_id
  
        setsize(self, mon.mins * self.scale, mon.maxs * self.scale);
  
 -      if(!self.ticrate)
 -              self.ticrate = autocvar_g_monsters_think_delay;
 -
 -      self.ticrate = bound(sys_frametime, self.ticrate, 60);
 -
 -      if(!self.m_armor_blockpercent)
 -              self.m_armor_blockpercent = 0.5;
 -
 -      if(!self.target_range)
 -              self.target_range = autocvar_g_monsters_target_range;
 +      self.ticrate = bound(sys_frametime, ((!self.ticrate) ? autocvar_g_monsters_think_delay : self.ticrate), 60);
  
 -      if(!self.respawntime)
 -              self.respawntime = autocvar_g_monsters_respawn_delay;
++      Monster_UpdateModel();
 -      if(!self.monster_moveflags)
 -              self.monster_moveflags = MONSTER_MOVE_WANDER;
 +      if(!Monster_Spawn_Setup())
 +      {
 +              Monster_Remove(self);
 +              return false;
 +      }
  
        if(!self.noalign)
        {
Simple merge
Simple merge
@@@ -312,147 -311,6 +311,147 @@@ void CommonCommand_cvar_purechanges(flo
        }
  }
  
- void CommonCommand_editmob(float request, entity caller, float argc)
++void CommonCommand_editmob(int request, entity caller, int argc)
 +{
 +      switch(request)
 +      {
 +              case CMD_REQUEST_COMMAND:
 +              {
 +                      if(autocvar_g_campaign) { print_to(caller, "Monster editing is disabled in singleplayer"); return; }
 +                      // no checks for g_monsters here, as it may be toggled mid match which existing monsters
 +
 +                      if(caller)
 +                      {
 +                              makevectors(self.v_angle);
 +                              WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_NORMAL, self);
 +                      }
 +
 +                      entity mon = trace_ent;
-                       float is_visible = (mon.flags & FL_MONSTER);
++                      bool is_visible = IS_MONSTER(mon);
 +                      string argument = argv(2);
 +
 +                      switch(argv(1))
 +                      {
 +                              case "name":
 +                              {
 +                                      if(!caller) { print_to(caller, "Only players can edit monsters"); return; }
 +                                      if(!argument) { break; } // escape to usage
 +                                      if(!autocvar_g_monsters_edit) { print_to(caller, "Monster editing is disabled"); return; }
 +                                      if(mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; }
 +                                      if(!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; }
 +
 +                                      string mon_oldname = mon.monster_name;
 +
 +                                      mon.monster_name = argument;
-                                       if(mon.sprite) { WaypointSprite_UpdateSprites(mon.sprite, strzone(mon.monster_name), string_null, string_null); }
++                                      if(mon.sprite) { WaypointSprite_UpdateSprites(mon.sprite, WP_Monster, WP_Null, WP_Null); }
 +                                      print_to(caller, sprintf("Your pet '%s' is now known as '%s'", mon_oldname, mon.monster_name));
 +                                      return;
 +                              }
 +                              case "spawn":
 +                              {
 +                                      if(!caller) { print_to(caller, "Only players can spawn monsters"); return; }
 +                                      if(!argv(2)) { break; } // escape to usage
 +
-                                       float moveflag, tmp_moncount = 0;
++                                      int moveflag, tmp_moncount = 0;
 +                                      string arg_lower = strtolower(argument);
 +                                      moveflag = (argv(3)) ? stof(argv(3)) : 1; // follow owner if not defined
 +                                      ret_string = "Monster spawning is currently disabled by a mutator";
 +
 +                                      if(arg_lower == "list") { print_to(caller, monsterlist_reply); return; }
 +
 +                                      FOR_EACH_MONSTER(mon) { if(mon.realowner == caller) ++tmp_moncount; }
 +
 +                                      if(!autocvar_g_monsters) { print_to(caller, "Monsters are disabled"); return; }
 +                                      if(autocvar_g_monsters_max <= 0 || autocvar_g_monsters_max_perplayer <= 0) { print_to(caller, "Monster spawning is disabled"); return; }
 +                                      if(!IS_PLAYER(caller)) { print_to(caller, "You must be playing to spawn a monster"); return; }
 +                                      if(MUTATOR_CALLHOOK(AllowMobSpawning)) { print_to(caller, ret_string); return; }
 +                                      if(caller.vehicle) { print_to(caller, "You can't spawn monsters while driving a vehicle"); return; }
 +                                      if(caller.frozen) { print_to(caller, "You can't spawn monsters while frozen"); return; }
 +                                      if(caller.deadflag != DEAD_NO) { print_to(caller, "You can't spawn monsters while dead"); return; }
 +                                      if(tmp_moncount >= autocvar_g_monsters_max) { print_to(caller, "The maximum monster count has been reached"); return; }
 +                                      if(tmp_moncount >= autocvar_g_monsters_max_perplayer) { print_to(caller, "You can't spawn any more monsters"); return; }
 +
-                                       float i = 0, found = false;
-                                       for(i = MON_FIRST; i <= MON_LAST; ++i)
++                                      bool found = false;
++                                      for(int i = MON_FIRST; i <= MON_LAST; ++i)
 +                                      {
 +                                              mon = get_monsterinfo(i);
 +                                              if(mon.netname == arg_lower) { found = true; break; }
 +                                      }
 +
 +                                      if(!found && arg_lower != "random") { print_to(caller, "Invalid monster"); return; }
 +
 +                                      totalspawned += 1;
 +                                      WarpZone_TraceBox (CENTER_OR_VIEWOFS(caller), caller.mins, caller.maxs, CENTER_OR_VIEWOFS(caller) + v_forward * 150, true, caller);
 +                                      mon = spawnmonster(arg_lower, 0, caller, caller, trace_endpos, false, false, moveflag);
 +                                      print_to(caller, strcat("Spawned ", mon.monster_name));
 +                                      return;
 +                              }
 +                              case "kill":
 +                              {
 +                                      if(!caller) { print_to(caller, "Only players can kill monsters"); return; }
 +                                      if(mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; }
 +                                      if(!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; }
 +
 +                                      Damage (mon, world, world, mon.health + mon.max_health + 200, DEATH_KILL, mon.origin, '0 0 0');
 +                                      print_to(caller, strcat("Your pet '", mon.monster_name, "' has been brutally mutilated"));
 +                                      return;
 +                              }
 +                              case "skin":
 +                              {
 +                                      if(!caller) { print_to(caller, "Only players can edit monsters"); return; }
 +                                      if(!argument) { break; } // escape to usage
 +                                      if(!autocvar_g_monsters_edit) { print_to(caller, "Monster editing is disabled"); return; }
 +                                      if(!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; }
 +                                      if(mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; }
-                                       if(mon.monsterid == MON_MAGE) { print_to(caller, "Mage skins can't be changed"); return; } // TODO
++                                      if(mon.monsterid == MON_MAGE.monsterid) { print_to(caller, "Mage skins can't be changed"); return; } // TODO
 +
 +                                      mon.skin = stof(argument);
 +                                      print_to(caller, strcat("Monster skin successfully changed to ", ftos(mon.skin)));
 +                                      return;
 +                              }
 +                              case "movetarget":
 +                              {
 +                                      if(!caller) { print_to(caller, "Only players can edit monsters"); return; }
 +                                      if(!argument) { break; } // escape to usage
 +                                      if(!autocvar_g_monsters_edit) { print_to(caller, "Monster editing is disabled"); return; }
 +                                      if(!is_visible) { print_to(caller, "You must look at your monster to edit it"); return; }
 +                                      if(mon.realowner != caller && autocvar_g_monsters_edit < 2) { print_to(caller, "This monster does not belong to you"); return; }
 +
 +                                      mon.monster_moveflags = stof(argument);
 +                                      print_to(caller, strcat("Monster move target successfully changed to ", ftos(mon.monster_moveflags)));
 +                                      return;
 +                              }
 +                              case "butcher":
 +                              {
 +                                      if(caller) { print_to(caller, "This command is not available to players"); return; }
 +                                      if(g_invasion) { print_to(caller, "This command does not work during an invasion!"); return; }
 +
-                                       float tmp_remcount = 0;
++                                      int tmp_remcount = 0;
 +                                      entity tmp_entity;
 +
 +                                      FOR_EACH_MONSTER(tmp_entity) { Monster_Remove(tmp_entity); ++tmp_remcount; }
 +
 +                                      monsters_total = monsters_killed = totalspawned = 0;
 +
 +                                      print_to(caller, (tmp_remcount) ? sprintf("Killed %d monster%s", tmp_remcount, (tmp_remcount == 1) ? "" : "s") : "No monsters to kill");
 +                                      return;
 +                              }
 +                      }
 +              }
 +
 +              default:
 +              case CMD_REQUEST_USAGE:
 +              {
 +                      print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " editmob command [arguments]"));
 +                      print_to(caller, "  Where 'command' can be butcher spawn skin movetarget kill name");
 +                      print_to(caller, "  spawn, skin, movetarget and name require 'arguments'");
 +                      print_to(caller, "  spawn also takes arguments list and random");
 +                      print_to(caller, "  Monster will follow owner if third argument of spawn command is not defined");
 +                      return;
 +              }
 +      }
 +}
 +
  void CommonCommand_info(float request, entity caller, float argc)
  {
        switch(request)
Simple merge
index 0000000,33fa9ee..d84ec70
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,589 +1,596 @@@
+ #ifndef SERVER_MUTATORS_EVENTS_H
+ #define SERVER_MUTATORS_EVENTS_H
+ #include "../../common/mutators/base.qh"
+ // register all possible hooks here
+ /** called when a player becomes observer, after shared setup */
+ #define EV_MakePlayerObserver(i, o) \
+     /**/
+ MUTATOR_HOOKABLE(MakePlayerObserver, EV_MakePlayerObserver)
+ /** */
+ #define EV_PutClientInServer(i, o) \
+     /** client wanting to spawn */ i(entity, self) \
+     /**/
+ MUTATOR_HOOKABLE(PutClientInServer, EV_PutClientInServer);
+ /** called when a player spawns as player, after shared setup, before his weapon is chosen (so items may be changed in here) */
+ #define EV_PlayerSpawn(i, o) \
+     /** spot that was used, or world */ i(entity, spawn_spot) \
+     /**/
+ entity spawn_spot;
+ MUTATOR_HOOKABLE(PlayerSpawn, EV_PlayerSpawn);
+ /** called in reset_map */
+ #define EV_reset_map_global(i, o) \
+     /**/
+ MUTATOR_HOOKABLE(reset_map_global, EV_reset_map_global);
+ /** called in reset_map */
+ #define EV_reset_map_players(i, o) \
+     /**/
+ MUTATOR_HOOKABLE(reset_map_players, EV_reset_map_players);
+ /** returns 1 if clearing player score shall not be allowed */
+ #define EV_ForbidPlayerScore_Clear(i, o) \
+     /**/
+ MUTATOR_HOOKABLE(ForbidPlayerScore_Clear, EV_ForbidPlayerScore_Clear);
+ /** called when a player disconnects */
+ #define EV_ClientDisconnect(i, o) \
+     /**/
+ MUTATOR_HOOKABLE(ClientDisconnect, EV_ClientDisconnect);
+ /** called when a player dies to e.g. remove stuff he was carrying. */
+ #define EV_PlayerDies(i, o) \
+     /**/ i(entity, frag_inflictor) \
+     /**/ i(entity, frag_attacker) \
+     /** same as self */ i(entity, frag_target) \
+     /**/ i(int, frag_deathtype) \
+     /**/
+ entity frag_inflictor;
+ entity frag_attacker;
+ entity frag_target;
+ int frag_deathtype;
+ MUTATOR_HOOKABLE(PlayerDies, EV_PlayerDies);
+ /** called when a player dies to e.g. remove stuff he was carrying */
+ #define EV_PlayHitsound(i, o) \
+     /**/ i(entity, frag_victim) \
+     /**/
+ entity frag_victim;
+ MUTATOR_HOOKABLE(PlayHitsound, EV_PlayHitsound);
+ /** called when a weapon sound is about to be played, allows custom paths etc. */
+ #define EV_WeaponSound(i, o) \
+     /**/ i(string, weapon_sound) \
+     /**/ i(string, weapon_sound_output) \
+     /**/ o(string, weapon_sound_output) \
+     /**/
+ string weapon_sound;
+ string weapon_sound_output;
+ MUTATOR_HOOKABLE(WeaponSound, EV_WeaponSound);
+ /** called when a weapon model is about to be set, allows custom paths etc. */
+ #define EV_WeaponModel(i, o) \
+     /**/ i(string, weapon_model) \
+     /**/ i(string, weapon_model_output) \
+     /**/ o(string, weapon_model_output) \
+     /**/
+ string weapon_model;
+ string weapon_model_output;
+ MUTATOR_HOOKABLE(WeaponModel, EV_WeaponModel);
+ /** called when an item model is about to be set, allows custom paths etc. */
+ #define EV_ItemModel(i, o) \
+     /**/ i(string, item_model) \
+     /**/ i(string, item_model_output) \
+     /**/ o(string, item_model_output) \
+     /**/
+ string item_model;
+ string item_model_output;
+ MUTATOR_HOOKABLE(ItemModel, EV_ItemModel);
+ /** called when a player presses the jump key */
+ #define EV_PlayerJump(i, o) \
+     /**/ i(float, player_multijump) \
+     /**/ i(float, player_jumpheight) \
+     /**/ o(float, player_multijump) \
+     /**/ o(float, player_jumpheight) \
+     /**/
+ float player_multijump;
+ float player_jumpheight;
+ MUTATOR_HOOKABLE(PlayerJump, EV_PlayerJump);
+ /** called when someone was fragged by "self", and is expected to change frag_score to adjust scoring for the kill */
+ #define EV_GiveFragsForKill(i, o) \
+     /** same as self */ i(entity, frag_attacker) \
+     /**/ i(entity, frag_target) \
+     /**/ i(float, frag_score) \
+     /**/ o(float, frag_score) \
+     /**/
+ float frag_score;
+ MUTATOR_HOOKABLE(GiveFragsForKill, EV_GiveFragsForKill);
+ /** called when the match ends */
+ MUTATOR_HOOKABLE(MatchEnd, EV_NO_ARGS);
+ /** should adjust ret_float to contain the team count */
+ #define EV_GetTeamCount(i, o) \
+     /**/ i(float, ret_float) \
+     /**/ o(float, ret_float) \
+     /**/
+ float ret_float;
+ MUTATOR_HOOKABLE(GetTeamCount, EV_GetTeamCount);
+ /** copies variables for spectating "other" to "self" */
+ #define EV_SpectateCopy(i, o) \
+     /**/ i(entity, other) \
+     /**/ i(entity, self) \
+     /**/
+ MUTATOR_HOOKABLE(SpectateCopy, EV_SpectateCopy);
+ /** called when formatting a chat message to replace fancy functions */
+ #define EV_FormatMessage(i, o) \
+     /**/ i(string, format_escape) \
+     /**/ i(string, format_replacement) \
+     /**/ o(string, format_replacement) \
+     /**/
+ string format_escape;
+ string format_replacement;
+ MUTATOR_HOOKABLE(FormatMessage, EV_FormatMessage);
+ /** returns 1 if throwing the current weapon shall not be allowed */
+ MUTATOR_HOOKABLE(ForbidThrowCurrentWeapon, EV_NO_ARGS);
+ /** allows changing attack rate */
+ #define EV_WeaponRateFactor(i, o) \
+     /**/ i(float, weapon_rate) \
+     /**/ o(float, weapon_rate) \
+     /**/
+ float weapon_rate;
+ MUTATOR_HOOKABLE(WeaponRateFactor, EV_WeaponRateFactor);
+ /** allows changing weapon speed (projectiles mostly) */
+ #define EV_WeaponSpeedFactor(i, o) \
+     /**/ i(float, ret_float) \
+     /**/ o(float, ret_float) \
+     /**/
+ MUTATOR_HOOKABLE(WeaponSpeedFactor, EV_WeaponSpeedFactor);
+ /** adjusts {warmup_}start_{items,weapons,ammo_{cells,plasma,rockets,nails,shells,fuel}} */
+ MUTATOR_HOOKABLE(SetStartItems, EV_NO_ARGS);
+ /** called every frame. customizes the waypoint for spectators */
+ #define EV_CustomizeWaypoint(i, o) \
+     /** waypoint */ i(entity, self) \
+     /** player; other.enemy = spectator */ i(entity, other) \
+     /**/
+ MUTATOR_HOOKABLE(CustomizeWaypoint, EV_CustomizeWaypoint);
+ /**
+  * checks if the current item may be spawned (self.items and self.weapons may be read and written to, as well as the ammo_ fields)
+  * return error to request removal
+  */
+ MUTATOR_HOOKABLE(FilterItem, EV_NO_ARGS);
+ /** return error to request removal */
+ #define EV_TurretSpawn(i, o) \
+     /** turret */ i(entity, self) \
+     /**/
+ MUTATOR_HOOKABLE(TurretSpawn, EV_TurretSpawn);
+ /** return error to not attack */
+ #define EV_TurretFire(i, o) \
+     /** turret */ i(entity, self) \
+     /**/
+ MUTATOR_HOOKABLE(TurretFire, EV_TurretFire);
+ /** return error to not attack */
+ #define EV_Turret_CheckFire(i, o) \
+     /**/ i(bool, ret_bool) \
+     /**/ o(bool, ret_bool) \
+     /**/
+ bool ret_bool;
+ MUTATOR_HOOKABLE(Turret_CheckFire, EV_Turret_CheckFire);
+ /** return error to prevent entity spawn, or modify the entity */
+ MUTATOR_HOOKABLE(OnEntityPreSpawn, EV_NO_ARGS);
+ /** runs in the event loop for players; is called for ALL player entities, also bots, also the dead, or spectators */
+ MUTATOR_HOOKABLE(PlayerPreThink, EV_NO_ARGS);
+ /** TODO change this into a general PlayerPostThink hook? */
+ MUTATOR_HOOKABLE(GetPressedKeys, EV_NO_ARGS);
+ /**
+  * called before any player physics, may adjust variables for movement,
+  * is run AFTER bot code and idle checking
+  */
+ MUTATOR_HOOKABLE(PlayerPhysics, EV_NO_ARGS);
+ /** is meant to call GetCvars_handle*(get_cvars_s, get_cvars_f, cvarfield, "cvarname") for cvars this mutator needs from the client */
+ #define EV_GetCvars(i, o) \
+     /**/ i(float, get_cvars_f) \
+     /**/ i(string, get_cvars_s) \
+     /**/
+ float get_cvars_f;
+ string get_cvars_s;
+ MUTATOR_HOOKABLE(GetCvars, EV_NO_ARGS); // NOTE: Can't use EV_GetCvars because of `SZ_GetSpace: overflow`
+ /** can edit any "just fired" projectile */
+ #define EV_EditProjectile(i, o) \
+     /**/ i(entity, self) \
+     /**/ i(entity, other) \
+     /**/
+ MUTATOR_HOOKABLE(EditProjectile, EV_EditProjectile);
+ /** called when a monster spawns */
+ MUTATOR_HOOKABLE(MonsterSpawn, EV_NO_ARGS);
+ /** called when a monster dies */
+ #define EV_MonsterDies(i, o) \
+     /**/ i(entity, frag_attacker) \
+     /**/
+ MUTATOR_HOOKABLE(MonsterDies, EV_MonsterDies);
++/** called when a monster dies */
++#define EV_MonsterRemove(i, o) \
++    /**/ i(entity, rem_mon) \
++    /**/
++entity rem_mon; // avoiding ovewriting self & other
++MUTATOR_HOOKABLE(MonsterRemove, EV_MonsterRemove);
++
+ /** called when a monster wants to respawn */
+ #define EV_MonsterRespawn(i, o) \
+     /**/ i(entity, other) \
+     /**/
+ MUTATOR_HOOKABLE(MonsterRespawn, EV_MonsterRespawn);
+ /** called when a monster is dropping loot */
+ #define EV_MonsterDropItem(i, o) \
+     /**/ i(entity, other) \
+     /**/ o(entity, other) \
+     /**/
+ .void() monster_loot;
+ MUTATOR_HOOKABLE(MonsterDropItem, EV_MonsterDropItem);
+ /**
+  * called when a monster moves
+  * returning true makes the monster stop
+  */
+ #define EV_MonsterMove(i, o) \
+     /**/ i(float, monster_speed_run) \
+     /**/ o(float, monster_speed_run) \
+     /**/ i(float, monster_speed_walk) \
+     /**/ o(float, monster_speed_walk) \
+     /**/ i(entity, monster_target) \
+     /**/
+ float monster_speed_run;
+ float monster_speed_walk;
+ entity monster_target;
+ MUTATOR_HOOKABLE(MonsterMove, EV_MonsterMove);
+ /** called when a monster looks for another target */
+ MUTATOR_HOOKABLE(MonsterFindTarget, EV_NO_ARGS);
+ /** called to change a random monster to a miniboss */
+ MUTATOR_HOOKABLE(MonsterCheckBossFlag, EV_NO_ARGS);
+ /**
+  * called when a player tries to spawn a monster
+  * return 1 to prevent spawning
+  */
+ MUTATOR_HOOKABLE(AllowMobSpawning, EV_NO_ARGS);
+ /** called when a player gets damaged to e.g. remove stuff he was carrying. */
+ #define EV_PlayerDamage_SplitHealthArmor(i, o) \
+     /**/ i(entity, frag_inflictor) \
+     /**/ i(entity, frag_attacker) \
+     /** same as self */ i(entity, frag_target) \
+     /** NOTE: this force already HAS been applied */ i(vector, damage_force) \
+     /**/ i(float, damage_take) \
+     /**/ o(float, damage_take) \
+       /**/ i(float, damage_save) \
+     /**/ o(float, damage_save) \
+     /**/
+ vector damage_force;
+ float damage_take;
+ float damage_save;
+ MUTATOR_HOOKABLE(PlayerDamage_SplitHealthArmor, EV_PlayerDamage_SplitHealthArmor);
+ /**
+  * called to adjust damage and force values which are applied to the player, used for e.g. strength damage/force multiplier
+  * i'm not sure if I should change this around slightly (Naming of the entities, and also how they're done in g_damage).
+  */
+ #define EV_PlayerDamage_Calculate(i, o) \
+     /**/ i(entity, frag_attacker) \
+     /**/ i(entity, frag_target) \
+     /**/ i(float, frag_deathtype) \
+       /**/ i(float, frag_damage) \
+     /**/ o(float, frag_damage) \
+       /**/ i(float, frag_mirrordamage) \
+     /**/ o(float, frag_mirrordamage) \
+     /**/ i(vector, frag_force) \
+     /**/ o(vector, frag_force) \
+     /**/
+ float frag_damage;
+ float frag_mirrordamage;
+ vector frag_force;
+ MUTATOR_HOOKABLE(PlayerDamage_Calculate, EV_PlayerDamage_Calculate);
+ /**
+  * Called when a player is damaged
+  */
+ #define EV_PlayerDamaged(i, o) \
+     /** attacker */ i(entity, mutator_argv_entity_0) \
+     /** target */ i(entity, mutator_argv_entity_1) \
+     /** health */ i(int, mutator_argv_int_0) \
+     /** armor */ i(int, mutator_argv_int_1) \
+     /** location */ i(vector, mutator_argv_vector_0) \
+     /**/
+ MUTATOR_HOOKABLE(PlayerDamaged, EV_PlayerDamaged);
+ /** called at the end of player_powerups() in cl_client.qc, used for manipulating the values which are set by powerup items. */
+ #define EV_PlayerPowerups(i, o) \
+     /**/ i(entity, self) \
+     /**/ i(int, olditems) \
+     /**/
+ int olditems;
+ MUTATOR_HOOKABLE(PlayerPowerups, EV_PlayerPowerups);
+ /**
+  * called every player think frame
+  * return 1 to disable regen
+  */
+ #define EV_PlayerRegen(i, o) \
+     /**/ i(float, regen_mod_max) \
+     /**/ o(float, regen_mod_max) \
+     /**/ i(float, regen_mod_regen) \
+     /**/ o(float, regen_mod_regen) \
+     /**/ i(float, regen_mod_rot) \
+     /**/ o(float, regen_mod_rot) \
+     /**/ i(float, regen_mod_limit) \
+     /**/ o(float, regen_mod_limit) \
+     /**/
+ float regen_mod_max;
+ float regen_mod_regen;
+ float regen_mod_rot;
+ float regen_mod_limit;
+ MUTATOR_HOOKABLE(PlayerRegen, EV_PlayerRegen);
+ /**
+  * called when the use key is pressed
+  * if MUTATOR_RETURNVALUE is 1, don't do anything
+  * return 1 if the use key actually did something
+  */
+ MUTATOR_HOOKABLE(PlayerUseKey, EV_NO_ARGS);
+ /**
+  * called when a client command is parsed
+  * NOTE: hooks MUST start with if (MUTATOR_RETURNVALUE) return false;
+  * NOTE: return true if you handled the command, return false to continue handling
+  * NOTE: THESE HOOKS MUST NEVER EVER CALL tokenize()
+  * // example:
+  * MUTATOR_HOOKFUNCTION(foo_SV_ParseClientCommand)
+  * {
+  *     if (MUTATOR_RETURNVALUE) // command was already handled?
+  *         return false;
+  *     if (cmd_name == "echocvar" && cmd_argc >= 2)
+  *     {
+  *         print(cvar_string(argv(1)), "\n");
+  *         return true;
+  *     }
+  *     if (cmd_name == "echostring" && cmd_argc >= 2)
+  *     {
+  *         print(substring(cmd_string, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), "\n");
+  *         return true;
+  *     }
+  *     return false;
+  * }
+  */
+ #define EV_SV_ParseClientCommand(i, o) \
+     /** command name */ i(string, cmd_name) \
+     /** also, argv() can be used */ i(int, cmd_argc) \
+     /** whole command, use only if you really have to */ i(string, cmd_string) \
+     /**/
+ string cmd_name;
+ int cmd_argc;
+ string cmd_string;
+ MUTATOR_HOOKABLE(SV_ParseClientCommand, EV_SV_ParseClientCommand);
+ /**
+  * called when a spawnpoint is being evaluated
+  * return 1 to make the spawnpoint unusable
+  */
+ #define EV_Spawn_Score(i, o) \
+     /** player wanting to spawn */ i(entity, self) \
+     /** spot to be evaluated */ i(entity, spawn_spot) \
+     /** _x is priority, _y is "distance" */ i(vector, spawn_score) \
+     /**/ o(vector, spawn_score) \
+     /**/
+ vector spawn_score;
+ MUTATOR_HOOKABLE(Spawn_Score, EV_Spawn_Score);
+ /** runs globally each server frame */
+ MUTATOR_HOOKABLE(SV_StartFrame, EV_NO_ARGS);
+ #define EV_SetModname(i, o) \
+     /** name of the mutator/mod if it warrants showing as such in the server browser */ \
+     o(string, modname) \
+     /**/
+ MUTATOR_HOOKABLE(SetModname, EV_SetModname);
+ /**
+  * called for each item being spawned on a map, including dropped weapons
+  * return 1 to remove an item
+  */
+ #define EV_Item_Spawn(i, o) \
+     /** the item */ i(entity, self) \
+     /**/
+ MUTATOR_HOOKABLE(Item_Spawn, EV_Item_Spawn);
+ #define EV_SetWeaponreplace(i, o) \
+     /** map entity */ i(entity, self) \
+     /** weapon info */ i(entity, other) \
+     /**/ i(string, ret_string) \
+     /**/ o(string, ret_string) \
+     /**/
+ MUTATOR_HOOKABLE(SetWeaponreplace, EV_SetWeaponreplace);
+ /** called when an item is about to respawn */
+ #define EV_Item_RespawnCountdown(i, o) \
+     /**/ i(string, item_name) \
+     /**/ o(string, item_name) \
+     /**/ i(vector, item_color) \
+     /**/ o(vector, item_color) \
+     /**/
+ string item_name;
+ vector item_color;
+ MUTATOR_HOOKABLE(Item_RespawnCountdown, EV_Item_RespawnCountdown);
+ /** called when a bot checks a target to attack */
+ #define EV_BotShouldAttack(i, o) \
+     /**/ i(entity, checkentity) \
+     /**/
+ entity checkentity;
+ MUTATOR_HOOKABLE(BotShouldAttack, EV_BotShouldAttack);
+ /**
+  * called whenever a player goes through a portal gun teleport
+  * allows you to strip a player of an item if they go through the teleporter to help prevent cheating
+  */
+ #define EV_PortalTeleport(i, o) \
+     /**/ i(entity, self) \
+     /**/
+ MUTATOR_HOOKABLE(PortalTeleport, EV_PortalTeleport);
+ /**
+  * called whenever a player uses impulse 33 (help me) in cl_impulse.qc
+  * normally help me ping uses self.waypointsprite_attachedforcarrier,
+  * but if your mutator uses something different then you can handle it
+  * in a special manner using this hook
+  */
+ #define EV_HelpMePing(i, o) \
+     /** the player who pressed impulse 33 */ i(entity, self) \
+     /**/
+ MUTATOR_HOOKABLE(HelpMePing, EV_HelpMePing);
+ /**
+  * called when a vehicle initializes
+  * return true to remove the vehicle
+  */
+ MUTATOR_HOOKABLE(VehicleSpawn, EV_NO_ARGS);
+ /**
+  * called when a player enters a vehicle
+  * allows mutators to set special settings in this event
+  */
+ #define EV_VehicleEnter(i, o) \
+     /** player */ i(entity, vh_player) \
+     /** vehicle */ i(entity, vh_vehicle) \
+     /**/
+ entity vh_player;
+ entity vh_vehicle;
+ MUTATOR_HOOKABLE(VehicleEnter, EV_VehicleEnter);
+ /**
+  * called when a player touches a vehicle
+  * return true to stop player from entering the vehicle
+  */
+ #define EV_VehicleTouch(i, o) \
+     /** vehicle */ i(entity, self) \
+     /** player */ i(entity, other) \
+     /**/
+ MUTATOR_HOOKABLE(VehicleTouch, EV_VehicleTouch);
+ /**
+  * called when a player exits a vehicle
+  * allows mutators to set special settings in this event
+  */
+ #define EV_VehicleExit(i, o) \
+     /** player */ i(entity, vh_player) \
+     /** vehicle */ i(entity, vh_vehicle) \
+     /**/
+ MUTATOR_HOOKABLE(VehicleExit, EV_VehicleExit);
+ /** called when a speedrun is aborted and the player is teleported back to start position */
+ #define EV_AbortSpeedrun(i, o) \
+     /** player */ i(entity, self) \
+     /**/
+ MUTATOR_HOOKABLE(AbortSpeedrun, EV_AbortSpeedrun);
+ /** called at when a item is touched. Called early, can edit item properties. */
+ #define EV_ItemTouch(i, o) \
+     /** item */ i(entity, self) \
+     /** player */ i(entity, other) \
+     /**/
+ MUTATOR_HOOKABLE(ItemTouch, EV_ItemTouch);
+ enum {
+       MUT_ITEMTOUCH_CONTINUE, // return this flag to make the function continue as normal
+       MUT_ITEMTOUCH_RETURN, // return this flag to make the function return (handled entirely by mutator)
+       MUT_ITEMTOUCH_PICKUP // return this flag to have the item "picked up" and taken even after mutator handled it
+ };
+ /** called at when a player connect */
+ #define EV_ClientConnect(i, o) \
+     /** player */ i(entity, self) \
+     /**/
+ MUTATOR_HOOKABLE(ClientConnect, EV_ClientConnect);
+ #define EV_HavocBot_ChooseRole(i, o) \
+     /**/ i(entity, self) \
+     /**/
+ MUTATOR_HOOKABLE(HavocBot_ChooseRole, EV_HavocBot_ChooseRole);
+ /** called when a target is checked for accuracy */
+ #define EV_AccuracyTargetValid(i, o) \
+     /** attacker */ i(entity, frag_attacker) \
+     /** target */ i(entity, frag_target) \
+     /**/
+ MUTATOR_HOOKABLE(AccuracyTargetValid, EV_AccuracyTargetValid);
+ enum {
+       MUT_ACCADD_VALID, // return this flag to make the function continue if target is a client
+       MUT_ACCADD_INVALID, // return this flag to make the function always continue
+       MUT_ACCADD_INDIFFERENT // return this flag to make the function always return
+ };
+ /** Called when clearing the global parameters for a model */
+ MUTATOR_HOOKABLE(ClearModelParams, EV_NO_ARGS);
+ /** Called when getting the global parameters for a model */
+ #define EV_GetModelParams(i, o) \
+     /** entity id */ i(string, checkmodel_input) \
+     /** entity id */ i(string, checkmodel_command) \
+     /**/
+ string checkmodel_input, checkmodel_command;
+ MUTATOR_HOOKABLE(GetModelParams, EV_GetModelParams);
+ /** called when a bullet has hit a target */
+ #define EV_FireBullet_Hit(i, o) \
+     /**/ i(entity, self) \
+     /**/ i(entity, bullet_hit) \
+     /**/ i(vector, bullet_startpos) \
+     /**/ i(vector, bullet_endpos) \
+     /**/ i(float, frag_damage) \
+     /**/ o(float, frag_damage) \
+     /**/
+ entity bullet_hit;
+ //vector bullet_hitloc; // the end pos matches the hit location, apparently
+ vector bullet_startpos;
+ vector bullet_endpos;
+ //float frag_damage;
+ MUTATOR_HOOKABLE(FireBullet_Hit, EV_FireBullet_Hit);
+ #define EV_FixPlayermodel(i, o) \
+     /**/ i(string, ret_string) \
+     /**/ o(string, ret_string) \
+     /**/
+ MUTATOR_HOOKABLE(FixPlayermodel, EV_FixPlayermodel);
+ /** Return error to play frag remaining announcements */
+ MUTATOR_HOOKABLE(Scores_CountFragsRemaining, EV_NO_ARGS);
+ #endif