Merge branch 'master' into Mario/monsters
authorMario <mario.mario@y7mail.com>
Sun, 3 Nov 2013 08:59:33 +0000 (19:59 +1100)
committerMario <mario.mario@y7mail.com>
Sun, 3 Nov 2013 08:59:33 +0000 (19:59 +1100)
40 files changed:
1  2 
defaultXonotic.cfg
qcsrc/client/Main.qc
qcsrc/client/View.qc
qcsrc/client/hud.qc
qcsrc/client/miscfunctions.qc
qcsrc/client/scoreboard.qc
qcsrc/common/deathtypes.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/spawn.qc
qcsrc/common/monsters/sv_monsters.qc
qcsrc/common/notifications.qh
qcsrc/server/accuracy.qc
qcsrc/server/cl_client.qc
qcsrc/server/cl_physics.qc
qcsrc/server/cl_player.qc
qcsrc/server/cl_weapons.qc
qcsrc/server/cl_weaponsystem.qc
qcsrc/server/command/cmd.qc
qcsrc/server/command/getreplies.qc
qcsrc/server/command/sv_cmd.qc
qcsrc/server/g_damage.qc
qcsrc/server/g_world.qc
qcsrc/server/miscfunctions.qc
qcsrc/server/mutators/gamemode_ctf.qc
qcsrc/server/mutators/gamemode_freezetag.qc
qcsrc/server/mutators/gamemode_invasion.qc
qcsrc/server/mutators/gamemode_onslaught.qc
qcsrc/server/mutators/mutator_minstagib.qc
qcsrc/server/sv_main.qc
qcsrc/server/t_items.qc
qcsrc/server/teamplay.qc
qcsrc/server/tturrets/system/system_main.qc
qcsrc/server/tturrets/units/unit_fusionreactor.qc
qcsrc/server/vehicles/vehicles.qc
qcsrc/server/w_electro.qc
qcsrc/server/w_shotgun.qc

Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -1100,13 -1095,10 +1100,13 @@@ vector HUD_DrawMapStats(vector pos, vec
        stat_secrets_total = getstatf(STAT_SECRETS_TOTAL);
  
        // get number of rows
 -      rows = (stat_secrets_total ? 1 : 0);
 +      if(stat_secrets_total)
 +              rows += 1;
 +      if(stat_monsters_total)
 +              rows += 1;
  
        // if no rows, return
-       if not(rows)
+       if (!rows)
                return pos;
  
        //  draw table header
Simple merge
index 83b0797,0000000..c6e1b18
mode 100644,000000..100644
--- /dev/null
@@@ -1,427 -1,0 +1,427 @@@
-                       if not(self.mage_spike)
 +#ifdef REGISTER_MONSTER
 +REGISTER_MONSTER(
 +/* MON_##id   */ MAGE,
 +/* function   */ m_mage,
 +/* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RANGED,
 +/* mins,maxs  */ '-36 -36 -24', '36 36 50',
 +/* model      */ "mage.dpm",
 +/* netname    */ "mage",
 +/* fullname   */ _("Mage")
 +);
 +
 +#else
 +#ifdef SVQC
 +float autocvar_g_monster_mage_health;
 +float autocvar_g_monster_mage_attack_spike_damage;
 +float autocvar_g_monster_mage_attack_spike_radius;
 +float autocvar_g_monster_mage_attack_spike_delay;
 +float autocvar_g_monster_mage_attack_spike_accel;
 +float autocvar_g_monster_mage_attack_spike_decel;
 +float autocvar_g_monster_mage_attack_spike_turnrate;
 +float autocvar_g_monster_mage_attack_spike_speed_max;
 +float autocvar_g_monster_mage_attack_spike_smart;
 +float autocvar_g_monster_mage_attack_spike_smart_trace_min;
 +float autocvar_g_monster_mage_attack_spike_smart_trace_max;
 +float autocvar_g_monster_mage_attack_spike_smart_mindist;
 +float autocvar_g_monster_mage_attack_push_damage;
 +float autocvar_g_monster_mage_attack_push_radius;
 +float autocvar_g_monster_mage_attack_push_delay;
 +float autocvar_g_monster_mage_attack_push_force;
 +float autocvar_g_monster_mage_heal_self;
 +float autocvar_g_monster_mage_heal_allies;
 +float autocvar_g_monster_mage_heal_minhealth;
 +float autocvar_g_monster_mage_heal_range;
 +float autocvar_g_monster_mage_heal_delay;
 +float autocvar_g_monster_mage_shield_time;
 +float autocvar_g_monster_mage_shield_delay;
 +float autocvar_g_monster_mage_shield_blockpercent;
 +float autocvar_g_monster_mage_speed_stop;
 +float autocvar_g_monster_mage_speed_run;
 +float autocvar_g_monster_mage_speed_walk;
 +
 +const float mage_anim_idle            = 0;
 +const float mage_anim_walk            = 1;
 +const float mage_anim_attack  = 2;
 +const float mage_anim_pain            = 3;
 +const float mage_anim_death   = 4;
 +const float mage_anim_run             = 5;
 +
 +void() mage_heal;
 +void() mage_shield;
 +
 +.entity mage_spike;
 +.float shield_ltime;
 +
 +float friend_needshelp(entity e)
 +{
 +      if(e == world)
 +              return FALSE;
 +      if(e.health <= 0)
 +              return FALSE;
 +      if(DIFF_TEAM(e, self) && e != self.monster_owner)
 +              return FALSE;
 +      if(e.frozen)
 +              return FALSE;
 +      if(!IS_PLAYER(e))
 +              return (e.flags & FL_MONSTER && e.health < e.max_health);
 +      if(e.items & IT_INVINCIBLE)
 +              return FALSE;
 +
 +      switch(self.skin)
 +      {
 +              case 0: return (e.health < autocvar_g_balance_health_regenstable);
 +              case 1: return ((e.ammo_cells && e.ammo_cells < g_pickup_cells_max) || (e.ammo_rockets && e.ammo_rockets < g_pickup_rockets_max) || (e.ammo_nails && e.ammo_nails < g_pickup_nails_max) || (e.ammo_shells && e.ammo_shells < g_pickup_shells_max));
 +              case 2: return (e.armorvalue < autocvar_g_balance_armor_regenstable);
 +              case 3: return (e.health > 0);
 +      }
 +      
 +      return FALSE;
 +}
 +
 +void mage_spike_explode()
 +{
 +      self.event_damage = func_null;
 +      
 +      sound(self, CH_SHOTS, "weapons/grenade_impact.wav", VOL_BASE, ATTEN_NORM);
 +      
 +      self.realowner.mage_spike = world;
 +      
 +      pointparticles(particleeffectnum("explosion_small"), self.origin, '0 0 0', 1);
 +      RadiusDamage (self, self.realowner, (autocvar_g_monster_mage_attack_spike_damage), (autocvar_g_monster_mage_attack_spike_damage) * 0.5, (autocvar_g_monster_mage_attack_spike_radius), world, 0, DEATH_MONSTER_MAGE, other);
 +
 +      remove (self);
 +}
 +
 +void mage_spike_touch()
 +{
 +      PROJECTILE_TOUCH;
 +
 +      mage_spike_explode();
 +}
 +
 +// copied from W_Seeker_Think
 +void mage_spike_think()
 +{
 +      entity e;
 +      vector desireddir, olddir, newdir, eorg;
 +      float turnrate;
 +      float dist;
 +      float spd;
 +
 +      if (time > self.ltime || self.enemy.health <= 0 || self.owner.health <= 0)
 +      {
 +              self.projectiledeathtype |= HITTYPE_SPLASH;
 +              mage_spike_explode();
 +      }
 +
 +      spd = vlen(self.velocity);
 +      spd = bound(
 +              spd - (autocvar_g_monster_mage_attack_spike_decel) * frametime,
 +              (autocvar_g_monster_mage_attack_spike_speed_max),
 +              spd + (autocvar_g_monster_mage_attack_spike_accel) * frametime
 +      );
 +
 +      if (self.enemy != world)
 +              if (self.enemy.takedamage != DAMAGE_AIM || self.enemy.deadflag != DEAD_NO)
 +                      self.enemy = world;
 +
 +      if (self.enemy != world)
 +      {
 +              e               = self.enemy;
 +              eorg            = 0.5 * (e.absmin + e.absmax);
 +              turnrate        = (autocvar_g_monster_mage_attack_spike_turnrate); // how fast to turn
 +              desireddir      = normalize(eorg - self.origin);
 +              olddir          = normalize(self.velocity); // get my current direction
 +              dist            = vlen(eorg - self.origin);
 +
 +              // Do evasive maneuvers for world objects? ( this should be a cpu hog. :P )
 +              if ((autocvar_g_monster_mage_attack_spike_smart) && (dist > (autocvar_g_monster_mage_attack_spike_smart_mindist)))
 +              {
 +                      // Is it a better idea (shorter distance) to trace to the target itself?
 +                      if ( vlen(self.origin + olddir * self.wait) < dist)
 +                              traceline(self.origin, self.origin + olddir * self.wait, FALSE, self);
 +                      else
 +                              traceline(self.origin, eorg, FALSE, self);
 +
 +                      // Setup adaptive tracelength
 +                      self.wait = bound((autocvar_g_monster_mage_attack_spike_smart_trace_min), vlen(self.origin - trace_endpos), self.wait = (autocvar_g_monster_mage_attack_spike_smart_trace_max));
 +
 +                      // Calc how important it is that we turn and add this to the desierd (enemy) dir.
 +                      desireddir  = normalize(((trace_plane_normal * (1 - trace_fraction)) + (desireddir * trace_fraction)) * 0.5);
 +              }
 +              
 +              newdir = normalize(olddir + desireddir * turnrate); // take the average of the 2 directions; not the best method but simple & easy
 +              self.velocity = newdir * spd; // make me fly in the new direction at my flight speed
 +      }
 +      else
 +              dist = 0;
 +              
 +      ///////////////
 +
 +      //self.angles = vectoangles(self.velocity);                     // turn model in the new flight direction
 +      self.nextthink = time;// + 0.05; // csqc projectiles
 +      UpdateCSQCProjectile(self);
 +}
 +
 +void mage_attack_spike()
 +{
 +      entity missile;
 +      vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
 +
 +      makevectors(self.angles);
 +
 +      missile = spawn ();
 +      missile.owner = missile.realowner = self;
 +      missile.think = mage_spike_think;
 +      missile.ltime = time + 7;
 +      missile.nextthink = time;
 +      missile.solid = SOLID_BBOX;
 +      missile.movetype = MOVETYPE_FLYMISSILE;
 +      missile.flags = FL_PROJECTILE;
 +      setorigin(missile, self.origin + v_forward * 14 + '0 0 30' + v_right * -14);
 +      setsize (missile, '0 0 0', '0 0 0');    
 +      missile.velocity = dir * 400;
 +      missile.avelocity = '300 300 300';
 +      missile.enemy = self.enemy;
 +      missile.touch = mage_spike_touch;
 +      
 +      self.mage_spike = missile;
 +      
 +      CSQCProjectile(missile, TRUE, PROJECTILE_MAGE_SPIKE, TRUE);
 +}
 +
 +void mage_heal()
 +{
 +      entity head;
 +      float washealed = FALSE;
 +      
 +      for(head = findradius(self.origin, (autocvar_g_monster_mage_heal_range)); head; head = head.chain) if(friend_needshelp(head))
 +      {
 +              washealed = TRUE;
 +              string fx = "";
 +              if(IS_PLAYER(head))
 +              {
 +                      switch(self.skin)
 +                      {
 +                              case 0:
 +                                      if(head.health < autocvar_g_balance_health_regenstable) head.health = bound(0, head.health + (autocvar_g_monster_mage_heal_allies), autocvar_g_balance_health_regenstable);
 +                                      fx = "healing_fx";
 +                                      break;
 +                              case 1:
 +                                      if(head.ammo_cells) head.ammo_cells = bound(head.ammo_cells, head.ammo_cells + 1, g_pickup_cells_max);
 +                                      if(head.ammo_rockets) head.ammo_rockets = bound(head.ammo_rockets, head.ammo_rockets + 1, g_pickup_rockets_max);
 +                                      if(head.ammo_shells) head.ammo_shells = bound(head.ammo_shells, head.ammo_shells + 2, g_pickup_shells_max);
 +                                      if(head.ammo_nails) head.ammo_nails = bound(head.ammo_nails, head.ammo_nails + 5, g_pickup_nails_max);
 +                                      fx = "ammoregen_fx";
 +                                      break;
 +                              case 2:
 +                                      if(head.armorvalue < autocvar_g_balance_armor_regenstable)
 +                                      {
 +                                              head.armorvalue = bound(0, head.armorvalue + (autocvar_g_monster_mage_heal_allies), autocvar_g_balance_armor_regenstable);
 +                                              fx = "armorrepair_fx";
 +                                      }
 +                                      break;
 +                              case 3:
 +                                      head.health = bound(0, head.health - ((head == self)  ? (autocvar_g_monster_mage_heal_self) : (autocvar_g_monster_mage_heal_allies)), autocvar_g_balance_health_regenstable);
 +                                      fx = "rage";
 +                                      break;
 +                      }
 +                      
 +                      pointparticles(particleeffectnum(fx), head.origin, '0 0 0', 1);
 +              }
 +              else
 +              {
 +                      pointparticles(particleeffectnum("healing_fx"), head.origin, '0 0 0', 1);
 +                      head.health = bound(0, head.health + (autocvar_g_monster_mage_heal_allies), head.max_health);
 +                      WaypointSprite_UpdateHealth(head.sprite, head.health);
 +              }
 +      }
 +      
 +      if(washealed)
 +      {
 +              self.frame = mage_anim_attack;
 +              self.attack_finished_single = time + (autocvar_g_monster_mage_heal_delay);
 +      }
 +}
 +
 +void mage_push()
 +{
 +      sound(self, CH_SHOTS, "weapons/tagexp1.wav", 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, (autocvar_g_monster_mage_attack_push_force), DEATH_MONSTER_MAGE, self.enemy);
 +      pointparticles(particleeffectnum("TE_EXPLOSION"), self.origin, '0 0 0', 1);
 +      
 +      self.frame = mage_anim_attack;
 +      self.attack_finished_single = time + (autocvar_g_monster_mage_attack_push_delay);
 +}
 +
 +void mage_teleport()
 +{
 +      if(vlen(self.enemy.origin - self.origin) >= 500)
 +              return;
 +
 +      makevectors(self.enemy.angles);
 +      tracebox(self.enemy.origin + ((v_forward * -1) * 200), self.mins, self.maxs, self.origin, MOVE_NOMONSTERS, self);
 +      
 +      if(trace_fraction < 1)
 +              return;
 +              
 +      pointparticles(particleeffectnum("spawn_event_neutral"), self.origin, '0 0 0', 1);
 +      setorigin(self, self.enemy.origin + ((v_forward * -1) * 200));
 +      
 +      self.attack_finished_single = time + 0.2;
 +}
 +
 +void mage_shield_remove()
 +{
 +      self.effects &= ~(EF_ADDITIVE | EF_BLUE);
 +      self.armorvalue = 0;
 +      self.m_armor_blockpercent = autocvar_g_monsters_armor_blockpercent;
 +}
 +
 +void mage_shield()
 +{
 +      self.effects |= (EF_ADDITIVE | EF_BLUE);
 +      self.lastshielded = time + (autocvar_g_monster_mage_shield_delay);
 +      self.m_armor_blockpercent = (autocvar_g_monster_mage_shield_blockpercent);
 +      self.armorvalue = self.health;
 +      self.shield_ltime = time + (autocvar_g_monster_mage_shield_time);
 +      self.frame = mage_anim_attack;
 +      self.attack_finished_single = time + 1;
 +}
 +
 +float mage_attack(float attack_type)
 +{
 +      switch(attack_type)
 +      {
 +              case MONSTER_ATTACK_MELEE:
 +              {
 +                      if(random() <= 0.7)
 +                      {
 +                              mage_push();
 +                              return TRUE;
 +                      }
 +                              
 +                      return FALSE;
 +              }
 +              case MONSTER_ATTACK_RANGED:
 +              {
-       if not(monster_initialize(MON_MAGE, FALSE)) { remove(self); return; }
++                      if(!self.mage_spike)
 +                      {
 +                              if(random() <= 0.4)
 +                              {
 +                                      mage_teleport();
 +                                      return TRUE;
 +                              }
 +                              else
 +                              {
 +                                      self.frame = mage_anim_attack;
 +                                      self.attack_finished_single = time + (autocvar_g_monster_mage_attack_spike_delay);
 +                                      defer(0.2, mage_attack_spike);
 +                                      return TRUE;
 +                              }
 +                      }
 +                      
 +                      if(self.mage_spike)
 +                              return TRUE;
 +                      else
 +                              return FALSE;
 +              }
 +      }
 +      
 +      return FALSE;
 +}
 +
 +void spawnfunc_monster_mage()
 +{
 +      self.classname = "monster_mage";
 +      
 +      self.monster_spawnfunc = spawnfunc_monster_mage;
 +      
 +      if(Monster_CheckAppearFlags(self))
 +              return;
 +      
-                       if not(self.health) self.health = (autocvar_g_monster_mage_health);
++      if(!monster_initialize(MON_MAGE, FALSE)) { remove(self); return; }
 +}
 +
 +// compatibility with old spawns
 +void spawnfunc_monster_shalrath() { spawnfunc_monster_mage(); }
 +
 +float m_mage(float req)
 +{
 +      switch(req)
 +      {
 +              case MR_THINK:
 +              {
 +                      entity head;
 +                      float need_help = FALSE;
 +                      
 +                      for(head = findradius(self.origin, (autocvar_g_monster_mage_heal_range)); head; head = head.chain)
 +                      if(head != self)
 +                      if(friend_needshelp(head))
 +                      {
 +                              need_help = TRUE;
 +                              break;
 +                      }
 +                              
 +                      if(self.health < (autocvar_g_monster_mage_heal_minhealth) || need_help)
 +                      if(time >= self.attack_finished_single)
 +                      if(random() < 0.5)
 +                              mage_heal();
 +                              
 +                      if(time >= self.shield_ltime && self.armorvalue)
 +                              mage_shield_remove();
 +                              
 +                      if(self.enemy)
 +                      if(self.health < self.max_health)
 +                      if(time >= self.lastshielded)
 +                      if(random() < 0.5)
 +                              mage_shield();
 +                      
 +                      monster_move((autocvar_g_monster_mage_speed_run), (autocvar_g_monster_mage_speed_walk), (autocvar_g_monster_mage_speed_stop), mage_anim_walk, mage_anim_run, mage_anim_idle);
 +                      return TRUE;
 +              }
 +              case MR_DEATH:
 +              {
 +                      self.frame = mage_anim_death;
 +                      return TRUE;
 +              }
 +              case MR_SETUP:
 +              {
++                      if(!self.health) self.health = (autocvar_g_monster_mage_health);
 +                      
 +                      self.monster_loot = spawnfunc_item_health_large;
 +                      self.monster_attackfunc = mage_attack;
 +                      self.frame = mage_anim_walk;
 +                      
 +                      return TRUE;
 +              }
 +              case MR_PRECACHE:
 +              {
 +                      precache_model ("models/monsters/mage.dpm");
 +                      precache_sound ("weapons/grenade_impact.wav");
 +                      precache_sound ("weapons/tagexp1.wav");
 +                      return TRUE;
 +              }
 +      }
 +      
 +      return TRUE;
 +}
 +
 +#endif // SVQC
 +#ifdef CSQC
 +float m_mage(float req)
 +{
 +      switch(req)
 +      {
 +              case MR_PRECACHE:
 +              {
 +                      precache_model ("models/monsters/mage.dpm");
 +                      return TRUE;
 +              }
 +      }
 +      
 +      return TRUE;
 +}
 +
 +#endif // CSQC
 +#endif // REGISTER_MONSTER
index e1515c3,0000000..499a9ff
mode 100644,000000..100644
--- /dev/null
@@@ -1,260 -1,0 +1,260 @@@
-       if not(monster_initialize(MON_SHAMBLER, FALSE)) { remove(self); return; }
 +#ifdef REGISTER_MONSTER
 +REGISTER_MONSTER(
 +/* MON_##id   */ SHAMBLER,
 +/* function   */ m_shambler,
 +/* 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")
 +);
 +
 +#else
 +#ifdef SVQC
 +float autocvar_g_monster_shambler_health;
 +float autocvar_g_monster_shambler_attack_smash_damage;
 +float autocvar_g_monster_shambler_attack_claw_damage;
 +float autocvar_g_monster_shambler_attack_lightning_damage;
 +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;
 +float autocvar_g_monster_shambler_attack_lightning_speed;
 +float autocvar_g_monster_shambler_attack_lightning_speed_up;
 +float autocvar_g_monster_shambler_speed_stop;
 +float autocvar_g_monster_shambler_speed_run;
 +float autocvar_g_monster_shambler_speed_walk;
 +
 +const float shambler_anim_stand       = 0;
 +const float shambler_anim_walk                = 1;
 +const float shambler_anim_run                 = 2;
 +const float shambler_anim_smash       = 3;
 +const float shambler_anim_swingr      = 4;
 +const float shambler_anim_swingl      = 5;
 +const float shambler_anim_magic       = 6;
 +const float shambler_anim_pain                = 7;
 +const float shambler_anim_death       = 8;
 +
 +.float shambler_lastattack; // delay attacks separately
 +
 +void shambler_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);
 +      
 +      tracebox(self.origin + v_forward * 50, self.mins * 0.5, self.maxs * 0.5, self.origin + v_forward * 500, 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));
 +}
 +
 +void shambler_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)
 +      {
 +              defer(0.5, shambler_swing);
 +              self.attack_finished_single += 0.5;
 +      }
 +}
 +
 +void shambler_lightning_explode()
 +{
 +      entity head;
 +      
 +      sound(self, CH_SHOTS, "weapons/electro_impact.wav", VOL_BASE, ATTEN_NORM);
 +      pointparticles(particleeffectnum("electro_impact"), '0 0 0', '0 0 0', 1);
 +
 +      self.event_damage = func_null;
 +      self.takedamage = DAMAGE_NO;
 +      self.movetype = MOVETYPE_NONE;
 +      self.velocity = '0 0 0';
 +
 +      if(self.movetype == MOVETYPE_NONE)
 +              self.velocity = self.oldvelocity;
 +
 +      RadiusDamage (self, self.realowner, (autocvar_g_monster_shambler_attack_lightning_damage), (autocvar_g_monster_shambler_attack_lightning_damage), (autocvar_g_monster_shambler_attack_lightning_radius), world, (autocvar_g_monster_shambler_attack_lightning_force), self.projectiledeathtype, other);
 +      
 +      for(head = findradius(self.origin, (autocvar_g_monster_shambler_attack_lightning_radius_zap)); head; head = head.chain) if(head != self.realowner) if(head.takedamage)
 +      {
 +              te_csqc_lightningarc(self.origin, head.origin);
 +              Damage(head, self, self.realowner, (autocvar_g_monster_shambler_attack_lightning_damage) * Monster_SkillModifier(), DEATH_MONSTER_SHAMBLER_ZAP, head.origin, '0 0 0');
 +      }
 +
 +      self.think = SUB_Remove;
 +      self.nextthink = time + 0.2;
 +}
 +
 +void shambler_lightning_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
 +{
 +      if (self.health <= 0)
 +              return;
 +              
 +      if (!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
 +              return; // g_projectiles_damage says to halt
 +              
 +      self.health = self.health - damage;
 +      
 +      if (self.health <= 0)
 +              W_PrepareExplosionByDamage(attacker, self.use);
 +}
 +
 +void shambler_lightning_touch()
 +{
 +      PROJECTILE_TOUCH;
 +      
 +      self.use ();
 +}
 +
 +void shambler_lightning_think()
 +{
 +      self.nextthink = time;
 +      if (time > self.cnt)
 +      {
 +              other = world;
 +              shambler_lightning_explode();
 +              return;
 +      }
 +}
 +
 +void shambler_lightning()
 +{
 +      entity gren;
 +      
 +      monster_makevectors(self.enemy);
 +
 +      gren = spawn ();
 +      gren.owner = gren.realowner = self;
 +      gren.classname = "grenade";
 +      gren.bot_dodge = TRUE;
 +      gren.bot_dodgerating = (autocvar_g_monster_shambler_attack_lightning_damage);
 +      gren.movetype = MOVETYPE_BOUNCE;
 +      PROJECTILE_MAKETRIGGER(gren);
 +      gren.projectiledeathtype = DEATH_MONSTER_SHAMBLER_ZAP;
 +      setorigin(gren, CENTER_OR_VIEWOFS(self));
 +      setsize(gren, '-8 -8 -8', '8 8 8');
 +      gren.scale = 2.5;
 +
 +      gren.cnt = time + 5;
 +      gren.nextthink = time;
 +      gren.think = shambler_lightning_think;
 +      gren.use = shambler_lightning_explode;
 +      gren.touch = shambler_lightning_touch;
 +
 +      gren.takedamage = DAMAGE_YES;
 +      gren.health = 50;
 +      gren.damageforcescale = 0;
 +      gren.event_damage = shambler_lightning_damage;
 +      gren.damagedbycontents = TRUE;
 +      gren.missile_flags = MIF_SPLASH | MIF_ARC;
 +      W_SetupProjectileVelocityEx(gren, v_forward, v_up, (autocvar_g_monster_shambler_attack_lightning_speed), (autocvar_g_monster_shambler_attack_lightning_speed_up), 0, 0, FALSE);
 +
 +      gren.angles = vectoangles (gren.velocity);
 +      gren.flags = FL_PROJECTILE;
 +
 +      CSQCProjectile(gren, TRUE, PROJECTILE_SHAMBLER_LIGHTNING, TRUE);
 +}
 +
 +float shambler_attack(float attack_type)
 +{
 +      switch(attack_type)
 +      {
 +              case MONSTER_ATTACK_MELEE:
 +              {
 +                      shambler_swing();
 +                      return TRUE;
 +              }
 +              case MONSTER_ATTACK_RANGED:
 +              {
 +                      if(time >= self.shambler_lastattack) // shambler doesn't attack much
 +                      if(random() <= 0.5 && vlen(self.enemy.origin - self.origin) <= 500)
 +                      {
 +                              self.frame = shambler_anim_smash;
 +                              defer(0.7, shambler_smash);
 +                              self.attack_finished_single = time + 1.1;
 +                              self.shambler_lastattack = time + 3;
 +                              return TRUE;
 +                      }
 +                      else if(random() <= 0.1) // small chance, don't want this spammed
 +                      {
 +                              self.frame = shambler_anim_magic;
 +                              self.attack_finished_single = time + 1.1;
 +                              self.shambler_lastattack = time + 3;
 +                              defer(0.6, shambler_lightning);
 +                              return TRUE;
 +                      }
 +                      
 +                      return FALSE;
 +              }
 +      }
 +      
 +      return FALSE;
 +}
 +
 +void spawnfunc_monster_shambler()
 +{
 +      self.classname = "monster_shambler";
 +      
 +      self.monster_spawnfunc = spawnfunc_monster_shambler;
 +      
 +      if(Monster_CheckAppearFlags(self))
 +              return;
 +      
-                       if not(self.health) self.health = (autocvar_g_monster_shambler_health);
-                       if not(self.attack_range) self.attack_range = 150;
++      if(!monster_initialize(MON_SHAMBLER, FALSE)) { remove(self); return; }
 +}
 +
 +float m_shambler(float req)
 +{
 +      switch(req)
 +      {
 +              case MR_THINK:
 +              {
 +                      monster_move((autocvar_g_monster_shambler_speed_run), (autocvar_g_monster_shambler_speed_walk), (autocvar_g_monster_shambler_speed_stop), shambler_anim_run, shambler_anim_walk, shambler_anim_stand);
 +                      return TRUE;
 +              }
 +              case MR_DEATH:
 +              {
 +                      self.frame = shambler_anim_death;
 +                      return TRUE;
 +              }
 +              case MR_SETUP:
 +              {
++                      if(!self.health) self.health = (autocvar_g_monster_shambler_health);
++                      if(!self.attack_range) self.attack_range = 150;
 +                      
 +                      self.monster_loot = spawnfunc_item_health_mega;
 +                      self.monster_attackfunc = shambler_attack;
 +                      self.frame = shambler_anim_stand;
 +                      self.weapon = WEP_NEX;
 +                      
 +                      return TRUE;
 +              }
 +              case MR_PRECACHE:
 +              {
 +                      precache_model ("models/monsters/shambler.mdl");
 +                      return TRUE;
 +              }
 +      }
 +      
 +      return TRUE;
 +}
 +
 +#endif // SVQC
 +#ifdef CSQC
 +float m_shambler(float req)
 +{
 +      switch(req)
 +      {
 +              case MR_PRECACHE:
 +              {
 +                      precache_model ("models/monsters/shambler.mdl");
 +                      return TRUE;
 +              }
 +      }
 +      
 +      return TRUE;
 +}
 +
 +#endif // CSQC
 +#endif // REGISTER_MONSTER
index 48ca45b,0000000..7e3c9f0
mode 100644,000000..100644
--- /dev/null
@@@ -1,183 -1,0 +1,183 @@@
-       if not(monster_initialize(MON_SPIDER, FALSE)) { remove(self); return; }
 +#ifdef REGISTER_MONSTER
 +REGISTER_MONSTER(
 +/* MON_##id   */ SPIDER,
 +/* function   */ m_spider,
 +/* spawnflags */ MON_FLAG_MELEE | MON_FLAG_RANGED,
 +/* mins,maxs  */ '-18 -18 -25', '18 18 30',
 +/* model      */ "spider.dpm",
 +/* netname    */ "spider",
 +/* fullname   */ _("Spider")
 +);
 +
 +#else
 +#ifdef SVQC
 +float autocvar_g_monster_spider_health;
 +float autocvar_g_monster_spider_attack_bite_damage;
 +float autocvar_g_monster_spider_attack_bite_delay;
 +float autocvar_g_monster_spider_attack_web_damagetime;
 +float autocvar_g_monster_spider_attack_web_speed;
 +float autocvar_g_monster_spider_attack_web_speed_up;
 +float autocvar_g_monster_spider_attack_web_delay;
 +float autocvar_g_monster_spider_speed_stop;
 +float autocvar_g_monster_spider_speed_run;
 +float autocvar_g_monster_spider_speed_walk;
 +
 +const float spider_anim_idle          = 0;
 +const float spider_anim_walk          = 1;
 +const float spider_anim_attack                = 2;
 +const float spider_anim_attack2               = 3;
 +
 +.float spider_web_delay;
 +
 +void spider_web_explode()
 +{
 +      entity e;
 +      if(self)
 +      {
 +              pointparticles(particleeffectnum("electro_impact"), self.origin, '0 0 0', 1);
 +              RadiusDamage(self, self.realowner, 0, 0, 25, world, 25, self.projectiledeathtype, world);
 +              
 +              for(e = findradius(self.origin, 25); e; e = e.chain) if(e != self) if(e.takedamage && e.deadflag == DEAD_NO) if(e.health > 0)
 +                      e.spider_slowness = time + (autocvar_g_monster_spider_attack_web_damagetime);
 +              
 +              remove(self);
 +      }
 +}
 +
 +void spider_web_touch()
 +{
 +      PROJECTILE_TOUCH;
 +      
 +      spider_web_explode();
 +}
 +
 +void spider_shootweb()
 +{
 +      monster_makevectors(self.enemy);
 +      
 +      sound(self, CH_SHOTS, "weapons/electro_fire2.wav", VOL_BASE, ATTEN_NORM);
 +
 +      entity proj = spawn ();
 +      proj.classname = "plasma";
 +      proj.owner = proj.realowner = self;
 +      proj.use = spider_web_touch;
 +      proj.think = adaptor_think2use_hittype_splash;
 +      proj.bot_dodge = TRUE;
 +      proj.bot_dodgerating = 0;
 +      proj.nextthink = time + 5;
 +      PROJECTILE_MAKETRIGGER(proj);
 +      proj.projectiledeathtype = DEATH_MONSTER_SPIDER;
 +      setorigin(proj, CENTER_OR_VIEWOFS(self));
 +
 +      //proj.glow_size = 50;
 +      //proj.glow_color = 45;
 +      proj.movetype = MOVETYPE_BOUNCE;
 +      W_SetupProjectileVelocityEx(proj, v_forward, v_up, (autocvar_g_monster_spider_attack_web_speed), (autocvar_g_monster_spider_attack_web_speed_up), 0, 0, FALSE);
 +      proj.touch = spider_web_touch;
 +      setsize(proj, '-4 -4 -4', '4 4 4');
 +      proj.takedamage = DAMAGE_NO;
 +      proj.damageforcescale = 0;
 +      proj.health = 500;
 +      proj.event_damage = func_null;
 +      proj.flags = FL_PROJECTILE;
 +      proj.damagedbycontents = TRUE;
 +
 +      proj.bouncefactor = 0.3;
 +      proj.bouncestop = 0.05;
 +      proj.missile_flags = MIF_SPLASH | MIF_ARC;
 +
 +      CSQCProjectile(proj, TRUE, PROJECTILE_ELECTRO, TRUE);
 +}
 +
 +float spider_attack(float attack_type)
 +{
 +      switch(attack_type)
 +      {
 +              case MONSTER_ATTACK_MELEE:
 +              {       
 +                      return monster_melee(self.enemy, (autocvar_g_monster_spider_attack_bite_damage), ((random() > 0.5) ? spider_anim_attack : spider_anim_attack2), self.attack_range, (autocvar_g_monster_spider_attack_bite_delay), DEATH_MONSTER_SPIDER, TRUE);
 +              }
 +              case MONSTER_ATTACK_RANGED:
 +              {
 +                      if(time >= self.spider_web_delay)
 +                      {
 +                              self.frame = spider_anim_attack2;
 +                              self.attack_finished_single = time + (autocvar_g_monster_spider_attack_web_delay);
 +                              spider_shootweb();
 +                              self.spider_web_delay = time + 3;
 +                              return TRUE;
 +                      }
 +                      
 +                      return FALSE;
 +              }
 +      }
 +      
 +      return FALSE;
 +}
 +
 +void spawnfunc_monster_spider() 
 +{
 +      self.classname = "monster_spider";
 +      
 +      self.monster_spawnfunc = spawnfunc_monster_spider;
 +      
 +      if(Monster_CheckAppearFlags(self))
 +              return;
 +      
-                       if not(self.health) self.health = (autocvar_g_monster_spider_health);
++      if(!monster_initialize(MON_SPIDER, FALSE)) { remove(self); return; }
 +}
 +
 +float m_spider(float req)
 +{
 +      switch(req)
 +      {
 +              case MR_THINK:
 +              {
 +                      monster_move((autocvar_g_monster_spider_speed_run), (autocvar_g_monster_spider_speed_walk), (autocvar_g_monster_spider_speed_stop), spider_anim_walk, spider_anim_walk, spider_anim_idle);
 +                      return TRUE;
 +              }
 +              case MR_DEATH:
 +              {
 +                      self.frame = spider_anim_attack;
 +                      self.angles_x = 180;
 +                      return TRUE;
 +              }
 +              case MR_SETUP:
 +              {
++                      if(!self.health) self.health = (autocvar_g_monster_spider_health);
 +                      
 +                      self.monster_loot = spawnfunc_item_health_medium;
 +                      self.monster_attackfunc = spider_attack;
 +                      self.frame = spider_anim_idle;
 +                      
 +                      return TRUE;
 +              }
 +              case MR_PRECACHE:
 +              {
 +                      precache_model ("models/monsters/spider.dpm");
 +                      precache_sound ("weapons/electro_fire2.wav");
 +                      return TRUE;
 +              }
 +      }
 +      
 +      return TRUE;
 +}
 +
 +#endif // SVQC
 +#ifdef CSQC
 +float m_spider(float req)
 +{
 +      switch(req)
 +      {
 +              case MR_PRECACHE:
 +              {
 +                      precache_model ("models/monsters/spider.dpm");
 +                      return TRUE;
 +              }
 +      }
 +      
 +      return TRUE;
 +}
 +
 +#endif // CSQC
 +#endif // REGISTER_MONSTER
index 69a9508,0000000..eadfab9
mode 100644,000000..100644
--- /dev/null
@@@ -1,164 -1,0 +1,164 @@@
-       if not(monster_initialize(MON_WYVERN, TRUE)) { remove(self); return; }
 +#ifdef REGISTER_MONSTER
 +REGISTER_MONSTER(
 +/* MON_##id   */ WYVERN,
 +/* function   */ m_wyvern,
 +/* 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")
 +);
 +
 +#else
 +#ifdef SVQC
 +float autocvar_g_monster_wyvern_health;
 +float autocvar_g_monster_wyvern_attack_fireball_damage;
 +float autocvar_g_monster_wyvern_attack_fireball_edgedamage;
 +float autocvar_g_monster_wyvern_attack_fireball_damagetime;
 +float autocvar_g_monster_wyvern_attack_fireball_force;
 +float autocvar_g_monster_wyvern_attack_fireball_radius;
 +float autocvar_g_monster_wyvern_attack_fireball_speed;
 +float autocvar_g_monster_wyvern_speed_stop;
 +float autocvar_g_monster_wyvern_speed_run;
 +float autocvar_g_monster_wyvern_speed_walk;
 +
 +const float wyvern_anim_hover         = 0;
 +const float wyvern_anim_fly   = 1;
 +const float wyvern_anim_magic         = 2;
 +const float wyvern_anim_pain  = 3;
 +const float wyvern_anim_death         = 4;
 +
 +void wyvern_fireball_explode()
 +{
 +      entity e;
 +      if(self)
 +      {
 +              pointparticles(particleeffectnum("fireball_explode"), self.origin, '0 0 0', 1);
 +              
 +              RadiusDamage(self, self.realowner, (autocvar_g_monster_wyvern_attack_fireball_damage), (autocvar_g_monster_wyvern_attack_fireball_edgedamage), (autocvar_g_monster_wyvern_attack_fireball_force), world, (autocvar_g_monster_wyvern_attack_fireball_radius), self.projectiledeathtype, world);
 +              
 +              for(e = world; (e = findfloat(e, takedamage, DAMAGE_AIM)); ) if(vlen(e.origin - self.origin) <= (autocvar_g_monster_wyvern_attack_fireball_radius))
 +                      Fire_AddDamage(e, self, 5 * Monster_SkillModifier(), (autocvar_g_monster_wyvern_attack_fireball_damagetime), self.projectiledeathtype);
 +              
 +              remove(self);
 +      }
 +}
 +
 +void wyvern_fireball_touch()
 +{
 +      PROJECTILE_TOUCH;
 +      
 +      wyvern_fireball_explode();
 +}
 +
 +void wyvern_fireball()
 +{
 +      entity missile = spawn();
 +      vector dir = normalize((self.enemy.origin + '0 0 10') - self.origin);
 +      
 +      monster_makevectors(self.enemy);
 +
 +      missile.owner = missile.realowner = self;
 +      missile.solid = SOLID_TRIGGER;
 +      missile.movetype = MOVETYPE_FLYMISSILE;
 +      missile.projectiledeathtype = DEATH_MONSTER_WYVERN;
 +      setsize(missile, '-6 -6 -6', '6 6 6');          
 +      setorigin(missile, self.origin + self.view_ofs + v_forward * 14);
 +      missile.flags = FL_PROJECTILE;
 +      missile.velocity = dir * (autocvar_g_monster_wyvern_attack_fireball_speed);
 +      missile.avelocity = '300 300 300';
 +      missile.nextthink = time + 5;
 +      missile.think = wyvern_fireball_explode;
 +      missile.enemy = self.enemy;
 +      missile.touch = wyvern_fireball_touch;
 +      CSQCProjectile(missile, TRUE, PROJECTILE_FIREMINE, TRUE);
 +}
 +
 +float wyvern_attack(float attack_type)
 +{
 +      switch(attack_type)
 +      {
 +              case MONSTER_ATTACK_MELEE:
 +              case MONSTER_ATTACK_RANGED:
 +              {
 +                      self.attack_finished_single = time + 1.2;
 +                      
 +                      wyvern_fireball();
 +                      
 +                      return TRUE;
 +              }
 +      }
 +      
 +      return FALSE;
 +}
 +
 +void spawnfunc_monster_wyvern()
 +{
 +      self.classname = "monster_wyvern";
 +      
 +      self.monster_spawnfunc = spawnfunc_monster_wyvern;
 +      
 +      if(Monster_CheckAppearFlags(self))
 +              return;
 +      
-                       if not(self.health) self.health = (autocvar_g_monster_wyvern_health);
++      if(!monster_initialize(MON_WYVERN, TRUE)) { remove(self); return; }
 +}
 +
 +// compatibility with old spawns
 +void spawnfunc_monster_wizard() { spawnfunc_monster_wyvern(); }
 +
 +float m_wyvern(float req)
 +{
 +      switch(req)
 +      {
 +              case MR_THINK:
 +              {
 +                      monster_move((autocvar_g_monster_wyvern_speed_run), (autocvar_g_monster_wyvern_speed_walk), (autocvar_g_monster_wyvern_speed_stop), wyvern_anim_fly, wyvern_anim_hover, wyvern_anim_hover);
 +                      return TRUE;
 +              }
 +              case MR_DEATH:
 +              {
 +                      self.frame = wyvern_anim_death;
 +                      self.velocity_x = -200 + 400 * random();
 +                      self.velocity_y = -200 + 400 * random();
 +                      self.velocity_z = 100 + 100 * random();
 +                      return TRUE;
 +              }
 +              case MR_SETUP:
 +              {
++                      if(!self.health) self.health = (autocvar_g_monster_wyvern_health);
 +                      
 +                      self.monster_loot = spawnfunc_item_cells;
 +                      self.monster_attackfunc = wyvern_attack;
 +                      self.frame = wyvern_anim_hover;
 +                      
 +                      return TRUE;
 +              }
 +              case MR_PRECACHE:
 +              {
 +                      precache_model ("models/monsters/wizard.mdl");
 +                      return TRUE;
 +              }
 +      }
 +      
 +      return TRUE;
 +}
 +
 +#endif // SVQC
 +#ifdef CSQC
 +float m_wyvern(float req)
 +{
 +      switch(req)
 +      {
 +              case MR_PRECACHE:
 +              {
 +                      precache_model ("models/monsters/wizard.mdl");
 +                      return TRUE;
 +              }
 +      }
 +      
 +      return TRUE;
 +}
 +
 +#endif // CSQC
 +#endif // REGISTER_MONSTER
index 7295578,0000000..26e88b2
mode 100644,000000..100644
--- /dev/null
@@@ -1,202 -1,0 +1,202 @@@
-       if not(monster_initialize(MON_ZOMBIE, FALSE)) { remove(self); return; }
 +#ifdef REGISTER_MONSTER
 +REGISTER_MONSTER(
 +/* MON_##id   */ ZOMBIE,
 +/* function   */ m_zombie,
 +/* spawnflags */ MON_FLAG_MELEE,
 +/* mins,maxs  */ '-18 -18 -25', '18 18 47',
 +/* model      */ "zombie.dpm",
 +/* netname    */ "zombie",
 +/* fullname   */ _("Zombie")
 +);
 +
 +#else
 +#ifdef SVQC
 +float autocvar_g_monster_zombie_health;
 +float autocvar_g_monster_zombie_attack_melee_damage;
 +float autocvar_g_monster_zombie_attack_melee_delay;
 +float autocvar_g_monster_zombie_attack_leap_damage;
 +float autocvar_g_monster_zombie_attack_leap_force;
 +float autocvar_g_monster_zombie_attack_leap_speed;
 +float autocvar_g_monster_zombie_attack_leap_delay;
 +float autocvar_g_monster_zombie_speed_stop;
 +float autocvar_g_monster_zombie_speed_run;
 +float autocvar_g_monster_zombie_speed_walk;
 +
 +const float zombie_anim_attackleap                    = 0;
 +const float zombie_anim_attackrun1                    = 1;
 +const float zombie_anim_attackrun2                    = 2;
 +const float zombie_anim_attackrun3                    = 3;
 +const float zombie_anim_attackstanding1               = 4;
 +const float zombie_anim_attackstanding2               = 5;
 +const float zombie_anim_attackstanding3               = 6;
 +const float zombie_anim_blockend                      = 7;
 +const float zombie_anim_blockstart                    = 8;
 +const float zombie_anim_deathback1                    = 9;
 +const float zombie_anim_deathback2                    = 10;
 +const float zombie_anim_deathback3                    = 11;
 +const float zombie_anim_deathfront1                   = 12;
 +const float zombie_anim_deathfront2                   = 13;
 +const float zombie_anim_deathfront3                   = 14;
 +const float zombie_anim_deathleft1                    = 15;
 +const float zombie_anim_deathleft2                    = 16;
 +const float zombie_anim_deathright1                   = 17;
 +const float zombie_anim_deathright2                   = 18;
 +const float zombie_anim_idle                          = 19;
 +const float zombie_anim_painback1                     = 20;
 +const float zombie_anim_painback2                     = 21;
 +const float zombie_anim_painfront1                    = 22;
 +const float zombie_anim_painfront2                    = 23;
 +const float zombie_anim_runbackwards          = 24;
 +const float zombie_anim_runbackwardsleft      = 25;
 +const float zombie_anim_runbackwardsright     = 26;
 +const float zombie_anim_runforward                    = 27;
 +const float zombie_anim_runforwardleft                = 28;
 +const float zombie_anim_runforwardright               = 29;
 +const float zombie_anim_spawn                         = 30;
 +
 +void zombie_attack_leap_touch()
 +{
 +      if (self.health <= 0)
 +              return;
 +              
 +      vector angles_face;
 +
 +      if(other.takedamage)
 +      {
 +              angles_face = vectoangles(self.moveto - self.origin);
 +              angles_face = normalize(angles_face) * (autocvar_g_monster_zombie_attack_leap_force);
 +              Damage(other, self, self, (autocvar_g_monster_zombie_attack_leap_damage) * Monster_SkillModifier(), DEATH_MONSTER_ZOMBIE_JUMP, other.origin, angles_face);
 +              self.touch = MonsterTouch; // instantly turn it off to stop damage spam
 +      }
 +
 +      if (trace_dphitcontents)
 +              self.touch = MonsterTouch;
 +}
 +
 +void zombie_blockend()
 +{
 +      if(self.health <= 0)
 +              return;
 +
 +      self.frame = zombie_anim_blockend;
 +      self.armorvalue = 0;
 +      self.m_armor_blockpercent = autocvar_g_monsters_armor_blockpercent;
 +}
 +
 +float zombie_block()
 +{
 +      self.frame = zombie_anim_blockstart;
 +      self.armorvalue = 100;
 +      self.m_armor_blockpercent = 0.9;
 +      self.state = MONSTER_STATE_ATTACK_MELEE; // freeze monster
 +      self.attack_finished_single = time + 2.1;
 +      
 +      defer(2, zombie_blockend);
 +      
 +      return TRUE;
 +}
 +
 +float zombie_attack(float attack_type)
 +{
 +      switch(attack_type)
 +      {
 +              case MONSTER_ATTACK_MELEE:
 +              {
 +                      float rand = random(), chosen_anim;
 +              
 +                      if(rand < 0.33)
 +                              chosen_anim = zombie_anim_attackstanding1;
 +                      else if(rand < 0.66)
 +                              chosen_anim = zombie_anim_attackstanding2;
 +                      else
 +                              chosen_anim = zombie_anim_attackstanding3;
 +                      
 +                      if(random() < 0.3 && self.health < 75 && self.enemy.health > 10)
 +                              return zombie_block();
 +                      
 +                      return monster_melee(self.enemy, (autocvar_g_monster_zombie_attack_melee_damage), chosen_anim, self.attack_range, (autocvar_g_monster_zombie_attack_melee_delay), DEATH_MONSTER_ZOMBIE_MELEE, TRUE);
 +              }
 +              case MONSTER_ATTACK_RANGED:
 +              {
 +                      makevectors(self.angles);
 +                      return monster_leap(zombie_anim_attackleap, zombie_attack_leap_touch, v_forward * (autocvar_g_monster_zombie_attack_leap_speed) + '0 0 200', (autocvar_g_monster_zombie_attack_leap_delay));
 +              }
 +      }
 +      
 +      return FALSE;
 +}
 +
 +void spawnfunc_monster_zombie() 
 +{
 +      self.classname = "monster_zombie";
 +      
 +      self.monster_spawnfunc = spawnfunc_monster_zombie;
 +      
 +      self.spawnflags |= MONSTER_RESPAWN_DEATHPOINT;
 +      
 +      if(Monster_CheckAppearFlags(self))
 +              return;
 +      
-                       if not(self.health) self.health = (autocvar_g_monster_zombie_health);
++      if(!monster_initialize(MON_ZOMBIE, FALSE)) { remove(self); return; }
 +}
 +
 +float m_zombie(float req)
 +{
 +      switch(req)
 +      {
 +              case MR_THINK:
 +              {
 +                      monster_move((autocvar_g_monster_zombie_speed_run), (autocvar_g_monster_zombie_speed_walk), (autocvar_g_monster_zombie_speed_stop), zombie_anim_runforward, zombie_anim_runforward, zombie_anim_idle);
 +                      return TRUE;
 +              }
 +              case MR_DEATH:
 +              {
 +                      self.armorvalue = 0;
 +                      self.m_armor_blockpercent = autocvar_g_monsters_armor_blockpercent;
 +                      self.frame = ((random() > 0.5) ? zombie_anim_deathback1 : zombie_anim_deathfront1);
 +                      return TRUE;
 +              }
 +              case MR_SETUP:
 +              {
++                      if(!self.health) self.health = (autocvar_g_monster_zombie_health);
 +                      
 +                      if(self.spawnflags & MONSTERFLAG_NORESPAWN)
 +                              self.spawnflags &= ~MONSTERFLAG_NORESPAWN; // zombies always respawn
 +                      
 +                      self.monster_loot = spawnfunc_item_health_medium;
 +                      self.monster_attackfunc = zombie_attack;
 +                      self.frame = zombie_anim_spawn;
 +                      self.spawn_time = time + 2.1;
 +                      self.spawnshieldtime = self.spawn_time;
 +                      self.respawntime = 0.2;
 +                      
 +                      return TRUE;
 +              }
 +              case MR_PRECACHE:
 +              {
 +                      precache_model ("models/monsters/zombie.dpm");
 +                      return TRUE;
 +              }
 +      }
 +      
 +      return TRUE;
 +}
 +
 +#endif // SVQC
 +#ifdef CSQC
 +float m_zombie(float req)
 +{
 +      switch(req)
 +      {
 +              case MR_PRECACHE:
 +              {
 +                      precache_model ("models/monsters/zombie.dpm");
 +                      return TRUE;
 +              }
 +      }
 +      
 +      return TRUE;
 +}
 +
 +#endif // CSQC
 +#endif // REGISTER_MONSTER
index bd2a216,0000000..61bf56e
mode 100644,000000..100644
--- /dev/null
@@@ -1,57 -1,0 +1,57 @@@
-       if not(respwn)
 +entity spawnmonster (string monster, float monster_id, entity spawnedby, entity own, vector orig, float respwn, float moveflag)
 +{
 +      // ensure spawnfunc database is initialized
 +      initialize_field_db();
 +      
 +      entity e = spawn();
 +      
 +      e.spawnflags = MONSTERFLAG_SPAWNED;
 +      
-               if not(found)
++      if(!respwn)
 +              e.spawnflags |= MONSTERFLAG_NORESPAWN;
 +      
 +      setorigin(e, orig);
 +      
 +      if(monster != "")
 +      {
 +              float i, found = 0;
 +              entity mon;
 +              for(i = MON_FIRST; i <= MON_LAST; ++i)
 +              {
 +                      mon = get_monsterinfo(i);
 +                      if(mon.netname == monster)
 +                      {
 +                              found = TRUE;
 +                              break;
 +                      }
 +              }
++              if(!found)
 +                      monster = (get_monsterinfo(MON_FIRST)).netname;
 +      }
 +              
 +      if(monster == "")
 +      if(monster_id)
 +              monster = (get_monsterinfo(monster_id)).netname;
 +      
 +      e.realowner = spawnedby;
 +      
 +      if(moveflag)
 +              e.monster_moveflags = moveflag;
 +      
 +      if(IS_PLAYER(spawnedby))
 +      {
 +              if(teamplay && autocvar_g_monsters_teams)
 +                      e.team = spawnedby.team; // colors handled in spawn code
 +                      
 +              if(autocvar_g_monsters_owners)
 +                      e.monster_owner = own; // using .owner makes the monster non-solid for its master
 +                      
 +              e.angles = spawnedby.angles;
 +      }
 +              
 +      monster = strcat("$ spawnfunc_monster_", monster);
 +              
 +      target_spawn_edit_entity(e, monster, world, world, world, world, world);
 +              
 +      return e;
 +}
index f75987e,0000000..1acc476
mode 100644,000000..100644
--- /dev/null
@@@ -1,1124 -1,0 +1,1124 @@@
-       if not((get_monsterinfo(ent.monsterid)).spawnflags & MON_FLAG_RANGED)
 +// =========================
 +//  SVQC Monster Properties
 +// =========================
 +
 +
 +void monster_item_spawn()
 +{
 +      if(self.monster_loot)
 +              self.monster_loot();
 +
 +      self.gravity = 1;
 +      self.reset = SUB_Remove;
 +      self.noalign = TRUE;
 +      self.velocity = randomvec() * 175 + '0 0 325';
 +      self.classname = "droppedweapon"; // hax
 +      self.item_spawnshieldtime = time + 0.7;
 +
 +      SUB_SetFade(self, time + autocvar_g_monsters_drop_time, 1);
 +}
 +
 +void monster_dropitem()
 +{
 +      if(!self.candrop || !self.monster_loot)
 +              return;
 +
 +      vector org = self.origin + ((self.mins + self.maxs) * 0.5);
 +      entity e = spawn();
 +
 +      setorigin(e, org);
 +
 +      e.monster_loot = self.monster_loot;
 +
 +      other = e;
 +      MUTATOR_CALLHOOK(MonsterDropItem);
 +      e = other;
 +
 +      if(e)
 +      {
 +              e.think = monster_item_spawn;
 +              e.nextthink = time + 0.3;
 +      }
 +}
 +
 +float Monster_SkillModifier()
 +{
 +      float t = 0.5+self.monster_skill*((1.2-0.3)/10);
 +      
 +      return t;
 +}
 +
 +float monster_isvalidtarget (entity targ, entity ent)
 +{
 +      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(targ.vehicle_flags & VHF_ISVEHICLE)
-       if not(targ.vehicle_flags & VHF_ISVEHICLE)
++      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(vlen(targ.origin - ent.origin) >= ent.target_range)
 +              return FALSE; // enemy is too far away
 +
 +      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 not(targ.vehicle_flags & VHF_ISVEHICLE)
++      if(!(targ.vehicle_flags & VHF_ISVEHICLE))
 +      if(targ.deadflag != DEAD_NO || ent.deadflag != DEAD_NO || targ.health <= 0 || ent.health <= 0)
 +              return FALSE; // enemy/self is dead
 +
 +      if(ent.monster_owner == targ)
 +              return FALSE; // don't attack our master
 +
 +      if(targ.monster_owner == ent)
 +              return FALSE; // don't attack our pet
 +
-       if not(autocvar_g_monsters_typefrag)
++      if(!(targ.vehicle_flags & VHF_ISVEHICLE))
 +      if(targ.flags & FL_NOTARGET)
 +              return FALSE; // enemy can't be targeted
 +
-       if not(other.flags & FL_MONSTER)
++      if(!autocvar_g_monsters_typefrag)
 +      if(targ.BUTTON_CHAT)
 +              return FALSE; // no typefragging!
 +
 +      if(SAME_TEAM(targ, ent))
 +              return FALSE; // enemy is on our team
 +              
 +      if (targ.frozen)
 +              return FALSE; // ignore frozen
 +
 +      if(autocvar_g_monsters_target_infront || ent.spawnflags & MONSTERFLAG_INFRONT)
 +      if(ent.enemy != targ)
 +      {
 +              float dot;
 +
 +              makevectors (ent.angles);
 +              dot = normalize (targ.origin - ent.origin) * v_forward;
 +
 +              if(dot <= 0.3)
 +                      return FALSE;
 +      }
 +
 +      return TRUE;
 +}
 +
 +entity FindTarget (entity ent)
 +{
 +      if(MUTATOR_CALLHOOK(MonsterFindTarget)) { return ent.enemy; } // Handled by a mutator
 +
 +      entity head, closest_target = world;
 +      head = findradius(ent.origin, ent.target_range);
 +
 +      while(head) // find the closest acceptable target to pass to
 +      {
 +              if(head.monster_attack)
 +              if(monster_isvalidtarget(head, ent))
 +              {
 +                      // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
 +                      vector head_center = CENTER_OR_VIEWOFS(head);
 +                      vector ent_center = CENTER_OR_VIEWOFS(ent);
 +
 +                      //if(ctf_CheckPassDirection(head_center, ent_center, ent.v_angle, head.WarpZone_findradius_nearest))
 +                      if(closest_target)
 +                      {
 +                              vector closest_target_center = CENTER_OR_VIEWOFS(closest_target);
 +                              if(vlen(ent_center - head_center) < vlen(ent_center - closest_target_center))
 +                                      { closest_target = head; }
 +                      }
 +                      else { closest_target = head; }
 +              }
 +
 +              head = head.chain;
 +      }
 +
 +      return closest_target;
 +}
 +
 +void MonsterTouch ()
 +{
 +      if(other == world)
 +              return;
 +
 +      if(self.enemy != other)
-               if not(self.weapon)
++      if(!(other.flags & FL_MONSTER))
 +      if(monster_isvalidtarget(other, self))
 +              self.enemy = other;
 +}
 +
 +string get_monster_model_datafilename(string m, float sk, string fil)
 +{
 +      if(m)
 +              m = strcat(m, "_");
 +      else
 +              m = "models/monsters/*_";
 +      if(sk >= 0)
 +              m = strcat(m, ftos(sk));
 +      else
 +              m = strcat(m, "*");
 +      return strcat(m, ".", fil);
 +}
 +
 +void PrecacheMonsterSounds(string f)
 +{
 +      float fh;
 +      string s;
 +      fh = fopen(f, FILE_READ);
 +      if(fh < 0)
 +              return;
 +      while((s = fgets(fh)))
 +      {
 +              if(tokenize_console(s) != 3)
 +              {
 +                      dprint("Invalid sound info line: ", s, "\n");
 +                      continue;
 +              }
 +              PrecacheGlobalSound(strcat(argv(1), " ", argv(2)));
 +      }
 +      fclose(fh);
 +}
 +
 +void precache_monstersounds()
 +{
 +      string m = (get_monsterinfo(self.monsterid)).model;
 +      float globhandle, n, i;
 +      string f;
 +
 +      globhandle = search_begin(strcat(m, "_*.sounds"), TRUE, FALSE);
 +      if (globhandle < 0)
 +              return;
 +      n = search_getsize(globhandle);
 +      for (i = 0; i < n; ++i)
 +      {
 +              //print(search_getfilename(globhandle, i), "\n");
 +              f = search_getfilename(globhandle, i);
 +              PrecacheMonsterSounds(f);
 +      }
 +      search_end(globhandle);
 +}
 +
 +void ClearMonsterSounds()
 +{
 +#define _MSOUND(m) if(self.monstersound_##m) { strunzone(self.monstersound_##m); self.monstersound_##m = string_null; }
 +      ALLMONSTERSOUNDS
 +#undef _MSOUND
 +}
 +
 +.string GetMonsterSoundSampleField(string type)
 +{
 +      GetMonsterSoundSampleField_notFound = 0;
 +      switch(type)
 +      {
 +#define _MSOUND(m) case #m: return monstersound_##m;
 +              ALLMONSTERSOUNDS
 +#undef _MSOUND
 +      }
 +      GetMonsterSoundSampleField_notFound = 1;
 +      return string_null;
 +}
 +
 +float LoadMonsterSounds(string f, float first)
 +{
 +      float fh;
 +      string s;
 +      var .string field;
 +      fh = fopen(f, FILE_READ);
 +      if(fh < 0)
 +      {
 +              dprint("Monster sound file not found: ", f, "\n");
 +              return 0;
 +      }
 +      while((s = fgets(fh)))
 +      {
 +              if(tokenize_console(s) != 3)
 +                      continue;
 +              field = GetMonsterSoundSampleField(argv(0));
 +              if(GetMonsterSoundSampleField_notFound)
 +                      continue;
 +              if(self.field)
 +                      strunzone(self.field);
 +              self.field = strzone(strcat(argv(1), " ", argv(2)));
 +      }
 +      fclose(fh);
 +      return 1;
 +}
 +
 +.float skin_for_monstersound;
 +void UpdateMonsterSounds()
 +{
 +      entity mon = get_monsterinfo(self.monsterid);
 +
 +      if(self.skin == self.skin_for_monstersound)
 +              return;
 +      self.skin_for_monstersound = self.skin;
 +      ClearMonsterSounds();
 +      //LoadMonsterSounds("sound/monsters/default.sounds", 1);
 +      if(!autocvar_g_debug_defaultsounds)
 +      if(!LoadMonsterSounds(get_monster_model_datafilename(mon.model, self.skin, "sounds"), 0))
 +              LoadMonsterSounds(get_monster_model_datafilename(mon.model, 0, "sounds"), 0);
 +}
 +
 +void MonsterSound(.string samplefield, float sound_delay, float delaytoo, float chan)
 +{
 +      if(delaytoo && time < self.msound_delay)
 +              return; // too early
 +      GlobalSound(self.samplefield, chan, VOICETYPE_PLAYERSOUND);
 +
 +      self.msound_delay = time + sound_delay;
 +}
 +
 +void monster_makevectors(entity e)
 +{
 +      vector v;
 +
 +      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;
 +
 +      makevectors(self.v_angle);
 +}
 +
 +float monster_melee(entity targ, float damg, float anim, float er, float anim_finished, float deathtype, float dostop)
 +{
 +      if (self.health <= 0)
 +              return FALSE; // attacking while dead?!
 +
 +      if(dostop)
 +      {
 +              self.velocity_x = 0;
 +              self.velocity_y = 0;
 +              self.state = MONSTER_STATE_ATTACK_MELEE;
 +      }
 +
 +      self.frame = anim;
 +
 +      if(anim_finished != 0)
 +              self.attack_finished_single = time + anim_finished;
 +
 +      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_SkillModifier(), deathtype, trace_ent.origin, normalize(trace_ent.origin - self.origin));
 +
 +      return TRUE;
 +}
 +
 +void Monster_CheckMinibossFlag ()
 +{
 +      if(MUTATOR_CALLHOOK(MonsterCheckBossFlag))
 +              return;
 +
 +      float chance = random() * 100;
 +
 +      // g_monsters_miniboss_chance cvar or spawnflags 64 causes a monster to be a miniboss
 +      if ((self.spawnflags & MONSTERFLAG_MINIBOSS) || (chance < autocvar_g_monsters_miniboss_chance))
 +      {
 +              self.health += autocvar_g_monsters_miniboss_healthboost;
 +              self.effects |= EF_RED;
-       if not(autocvar_g_monsters_respawn)
++              if(!self.weapon)
 +                      self.weapon = WEP_NEX;
 +      }
 +}
 +
 +float Monster_CanRespawn(entity ent)
 +{
 +      other = ent;
 +      if(ent.deadflag == DEAD_DEAD) // don't call when monster isn't dead
 +      if(MUTATOR_CALLHOOK(MonsterRespawn))
 +              return TRUE; // enabled by a mutator
 +
 +      if(ent.spawnflags & MONSTERFLAG_NORESPAWN)
 +              return FALSE;
 +
-       if not(self.flags & FL_ONGROUND)
++      if(!autocvar_g_monsters_respawn)
 +              return FALSE;
 +
 +      return TRUE;
 +}
 +
 +void Monster_Fade ()
 +{
 +      if(Monster_CanRespawn(self))
 +      {
 +              self.monster_respawned = TRUE;
 +              self.think = self.monster_spawnfunc;
 +              self.nextthink = time + self.respawntime;
 +              self.ltime = 0;
 +              self.deadflag = DEAD_RESPAWNING;
 +              if(self.spawnflags & MONSTER_RESPAWN_DEATHPOINT)
 +              {
 +                      self.pos1 = self.origin;
 +                      self.pos2 = self.angles;
 +              }
 +              self.event_damage = func_null;
 +              self.takedamage = DAMAGE_NO;
 +              setorigin(self, self.pos1);
 +              self.angles = self.pos2;
 +              self.health = self.max_health;
 +              setmodel(self, "null");
 +      }
 +      else
 +              SUB_SetFade(self, time + 3, 1);
 +}
 +
 +float Monster_CanJump (vector vel)
 +{
 +      if(self.state)
 +              return FALSE; // already attacking
-       if not(e.monster_attackfunc)
++      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;
 +}
 +
 +float monster_leap (float anm, void() touchfunc, vector vel, float anim_finished)
 +{
 +      if(!Monster_CanJump(vel))
 +              return FALSE;
 +
 +      self.frame = anm;
 +      self.state = MONSTER_STATE_ATTACK_LEAP;
 +      self.touch = touchfunc;
 +      self.origin_z += 1;
 +      self.velocity = vel;
 +      self.flags &= ~FL_ONGROUND;
 +
 +      self.attack_finished_single = time + anim_finished;
 +
 +      return TRUE;
 +}
 +
 +void monster_checkattack(entity e, entity targ)
 +{
 +      if(e == world)
 +              return;
 +      if(targ == world)
 +              return;
 +
-       if not(self.enemy)
++      if(!e.monster_attackfunc)
 +              return;
 +
 +      if(time < e.attack_finished_single)
 +              return;
 +
 +      if(vlen(targ.origin - e.origin) <= e.attack_range)
 +      if(e.monster_attackfunc(MONSTER_ATTACK_MELEE))
 +      {
 +              MonsterSound(monstersound_melee, 0, FALSE, CH_VOICE);
 +              return;
 +      }
 +
 +      if(vlen(targ.origin - e.origin) > e.attack_range)
 +      if(e.monster_attackfunc(MONSTER_ATTACK_RANGED))
 +      {
 +              MonsterSound(monstersound_ranged, 0, FALSE, CH_VOICE);
 +              return;
 +      }
 +}
 +
 +void monster_use ()
 +{
-               if not(monster_isvalidtarget(self.enemy, self))
++      if(!self.enemy)
 +      if(self.health > 0)
 +      if(monster_isvalidtarget(activator, self))
 +              self.enemy = activator;
 +}
 +
 +.float last_trace;
 +.float last_enemycheck; // for checking enemy
 +vector monster_pickmovetarget(entity targ)
 +{
 +      // enemy is always preferred target
 +      if(self.enemy)
 +      {
 +              makevectors(self.angles);
 +              self.monster_movestate = MONSTER_MOVE_ENEMY;
 +              self.last_trace = time + 1.2;
 +              return self.enemy.origin;
 +      }
 +
 +      switch(self.monster_moveflags)
 +      {
 +              case MONSTER_MOVE_OWNER:
 +              {
 +                      self.monster_movestate = MONSTER_MOVE_OWNER;
 +                      self.last_trace = time + 0.3;
 +                      return (self.monster_owner) ? self.monster_owner.origin : self.origin;
 +              }
 +              case MONSTER_MOVE_SPAWNLOC:
 +              {
 +                      self.monster_movestate = MONSTER_MOVE_SPAWNLOC;
 +                      self.last_trace = time + 2;
 +                      return self.pos1;
 +              }
 +              case MONSTER_MOVE_NOMOVE:
 +              {
 +                      self.monster_movestate = MONSTER_MOVE_NOMOVE;
 +                      self.last_trace = time + 2;
 +                      return self.origin;
 +              }
 +              default:
 +              case MONSTER_MOVE_WANDER:
 +              {
 +                      vector pos;
 +                      self.monster_movestate = MONSTER_MOVE_WANDER;
 +                      self.last_trace = time + 2;
 +
 +                      self.angles_y = rint(random() * 500);
 +                      makevectors(self.angles);
 +                      pos = self.origin + v_forward * 600;
 +
 +                      if(self.flags & FL_FLY || self.flags & FL_SWIM)
 +                      if(self.spawnflags & MONSTERFLAG_FLY_VERTICAL)
 +                      {
 +                              pos_z = random() * 200;
 +                              if(random() >= 0.5)
 +                                      pos_z *= -1;
 +                      }
 +
 +                      if(targ)
 +                      {
 +                              self.last_trace = time + 0.5;
 +                              pos = targ.origin;
 +                      }
 +
 +                      return pos;
 +              }
 +      }
 +}
 +
 +void monster_move(float runspeed, float walkspeed, float stopspeed, float manim_run, float manim_walk, float manim_idle)
 +{
 +      fixedmakevectors(self.angles);
 +
 +      if(self.target2)
 +              self.goalentity = find(world, targetname, self.target2);
 +
 +      entity targ;
 +
 +      if(self.frozen)
 +      {
 +              self.revive_progress = bound(0, self.revive_progress + self.ticrate * self.revive_speed, 1);
 +              self.health = max(1, self.max_health * self.revive_progress);
 +              
 +              WaypointSprite_UpdateHealth(self.sprite, self.health);
 +              
 +              movelib_beak_simple(stopspeed);
 +              
 +              self.frame = manim_idle;
 +              
 +              self.enemy = world;
 +              self.nextthink = time + self.ticrate;
 +
 +              if(self.revive_progress >= 1)
 +                      Unfreeze(self); // wait for next think before attacking
 +
 +              return; // no moving while frozen
 +      }
 +
 +      if(self.flags & FL_SWIM)
 +      {
 +              if(self.waterlevel < WATERLEVEL_WETFEET)
 +              {
 +                      if(time >= self.last_trace)
 +                      {
 +                              self.fish_wasdrowning = TRUE;
 +                              self.last_trace = time + 0.4;
 +
 +                              Damage (self, world, world, 2, DEATH_DROWN, self.origin, '0 0 0');
 +                              self.angles = '90 90 0';
 +                              if(random() < 0.5)
 +                              {
 +                                      self.velocity_y += random() * 50;
 +                                      self.velocity_x -= random() * 50;
 +                              }
 +                              else
 +                              {
 +                                      self.velocity_y -= random() * 50;
 +                                      self.velocity_x += random() * 50;
 +                              }
 +                              self.velocity_z += random() * 150;
 +                      }
 +
 +
 +                      self.movetype = MOVETYPE_BOUNCE;
 +                      //self.velocity_z = -200;
 +
 +                      return;
 +              }
 +              else if(self.fish_wasdrowning)
 +              {
 +                      self.fish_wasdrowning = FALSE;
 +                      self.angles_x = 0;
 +                      self.movetype = MOVETYPE_WALK;
 +              }
 +      }
 +
 +      targ = self.goalentity;
 +
 +      monster_target = targ;
 +      monster_speed_run = runspeed;
 +      monster_speed_walk = walkspeed;
 +
 +      if(MUTATOR_CALLHOOK(MonsterMove) || gameover || (round_handler_IsActive() && !round_handler_IsRoundStarted()) || time < game_starttime || (autocvar_g_campaign && !campaign_bots_may_start) || time < self.spawn_time)
 +      {
 +              runspeed = walkspeed = 0;
 +              if(time >= self.spawn_time)
 +                      self.frame = manim_idle;
 +              movelib_beak_simple(stopspeed);
 +              return;
 +      }
 +
 +      targ = monster_target;
 +      runspeed = bound(0, monster_speed_run * Monster_SkillModifier(), runspeed * 2); // limit maxspeed to prevent craziness
 +      walkspeed = bound(0, monster_speed_walk * Monster_SkillModifier(), walkspeed * 2); // limit maxspeed to prevent craziness
 +      
 +      if(time < self.spider_slowness)
 +      {
 +              runspeed *= 0.5;
 +              walkspeed *= 0.5;
 +      }
 +
 +      if(teamplay)
 +      if(autocvar_g_monsters_teams)
 +      if(DIFF_TEAM(self.monster_owner, self))
 +              self.monster_owner = world;
 +
 +      if(self.enemy && self.enemy.health < 1)
 +              self.enemy = world; // enough!
 +
 +      if(time >= self.last_enemycheck)
 +      {
-               if not(self.enemy)
++              if(!monster_isvalidtarget(self.enemy, self))
 +                      self.enemy = world;
 +
-       if not(self.enemy)
++              if(!self.enemy)
 +              {
 +                      self.enemy = FindTarget(self);
 +                      if(self.enemy)
 +                              MonsterSound(monstersound_sight, 0, FALSE, CH_VOICE);
 +              }
 +
 +              self.last_enemycheck = time + 0.5;
 +      }
 +
 +      if(self.state == MONSTER_STATE_ATTACK_MELEE && time >= self.attack_finished_single)
 +              self.state = 0;
 +
 +      if(self.state != MONSTER_STATE_ATTACK_MELEE) // don't move if set
 +      if(time >= self.last_trace || self.enemy) // update enemy instantly
 +              self.moveto = monster_pickmovetarget(targ);
 +
-       if not(mon)
++      if(!self.enemy)
 +              MonsterSound(monstersound_idle, 7, TRUE, CH_VOICE);
 +
 +      if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE)
 +              self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
 +
 +      if(self.state == MONSTER_STATE_ATTACK_LEAP && (self.flags & FL_ONGROUND))
 +      {
 +              self.state = 0;
 +              self.touch = MonsterTouch;
 +      }
 +
 +      //self.steerto = steerlib_attract2(self.moveto, 0.5, 500, 0.95);
 +
 +      float turny = 0;
 +      vector real_angle = vectoangles(self.steerto) - self.angles;
 +
 +      if(self.state != MONSTER_STATE_ATTACK_LEAP && self.state != MONSTER_STATE_ATTACK_MELEE)
 +              turny = 20;
 +
 +      if(self.flags & FL_SWIM)
 +              turny = vlen(self.angles - self.moveto);
 +
 +      if(turny)
 +      {
 +              turny = bound(turny * -1, shortangle_f(real_angle_y, self.angles_y), turny);
 +              self.angles_y += turny;
 +      }
 +
 +      if(self.state == MONSTER_STATE_ATTACK_MELEE)
 +              self.moveto = self.origin;
 +
 +      if(self.enemy && self.enemy.vehicle)
 +              runspeed = 0;
 +
 +      if(((self.flags & FL_FLY) || (self.flags & FL_SWIM)) && self.spawnflags & MONSTERFLAG_FLY_VERTICAL)
 +              v_forward = normalize(self.moveto - self.origin);
 +      else
 +              self.moveto_z = self.origin_z;
 +
 +      if(vlen(self.origin - self.moveto) > 64)
 +      {
 +              if(self.flags & FL_FLY || self.flags & FL_SWIM)
 +                      movelib_move_simple(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6);
 +              else
 +                      movelib_move_simple_gravity(v_forward, ((self.enemy) ? runspeed : walkspeed), 0.6);
 +
 +              if(time > self.pain_finished)
 +              if(time > self.attack_finished_single)
 +              if(vlen(self.velocity) > 10)
 +                      self.frame = ((self.enemy) ? manim_run : manim_walk);
 +              else
 +                      self.frame = manim_idle;
 +      }
 +      else
 +      {
 +              entity e = find(world, targetname, self.target2);
 +              if(e.target2)
 +                      self.target2 = e.target2;
 +              else if(e.target)
 +                      self.target2 = e.target;
 +
 +              movelib_beak_simple(stopspeed);
 +              if(time > self.attack_finished_single)
 +              if(time > self.pain_finished)
 +              if (vlen(self.velocity) <= 30)
 +                      self.frame = manim_idle;
 +      }
 +
 +      monster_checkattack(self, self.enemy);
 +}
 +
 +void monster_remove(entity mon)
 +{
-       if not(ent.spawnflags & MONSTERFLAG_APPEAR)
++      if(!mon)
 +              return; // nothing to remove
 +              
 +      pointparticles(particleeffectnum("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);
 +}
 +
 +void monster_dead_think()
 +{
 +      self.nextthink = time + self.ticrate;
 +      
 +      CSQCMODEL_AUTOUPDATE();
 +
 +      if(self.ltime != 0)
 +      if(time >= self.ltime)
 +      {
 +              Monster_Fade();
 +              return;
 +      }
 +}
 +
 +void monsters_setstatus()
 +{
 +      self.stat_monsters_total = monsters_total;
 +      self.stat_monsters_killed = monsters_killed;
 +}
 +
 +void Monster_Appear()
 +{
 +      self.enemy = activator;
 +      self.spawnflags &= ~MONSTERFLAG_APPEAR;
 +      self.monster_spawnfunc();
 +}
 +
 +float Monster_CheckAppearFlags(entity ent)
 +{
-               if not(self.monster_respawned)
++      if(!(ent.spawnflags & MONSTERFLAG_APPEAR))
 +              return FALSE;
 +
 +      ent.think = func_null;
 +      ent.nextthink = 0;
 +      ent.use = Monster_Appear;
 +      ent.flags = FL_MONSTER; // set so this monster can get butchered
 +
 +      return TRUE;
 +}
 +
 +void monsters_reset()
 +{
 +      setorigin(self, self.pos1);
 +      self.angles = self.pos2;
 +      
 +      Unfreeze(self); // remove any icy remains
 +
 +      self.health = self.max_health;
 +      self.velocity = '0 0 0';
 +      self.enemy = world;
 +      self.goalentity = world;
 +      self.attack_finished_single = 0;
 +      self.moveto = self.origin;
 +}
 +
 +void monsters_corpse_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
 +{
 +      self.health -= damage;
 +
 +      Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
 +
 +      if(self.health <= -100) // 100 health until gone?
 +      {
 +              Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, self, attacker);
 +              
 +              if(IS_CLIENT(self.realowner))
-               if not(self.monster_respawned)
++              if(!self.monster_respawned)
 +                      self.realowner.monstercount -= 1;
 +
 +              self.think = SUB_Remove;
 +              self.nextthink = time + 0.1;
 +      }
 +}
 +
 +void monster_die(entity attacker, float gibbed)
 +{
 +      self.think = monster_dead_think;
 +      self.nextthink = time;
 +      self.ltime = time + 5;
 +      
 +      if ( self.frozen )
 +      {
 +              Unfreeze(self); // remove any icy remains
 +              self.health = 0; // reset by Unfreeze
 +      }
 +      
 +      monster_dropitem();
 +
 +      MonsterSound(monstersound_death, 0, FALSE, CH_VOICE);
 +
 +      if(!(self.spawnflags & MONSTERFLAG_SPAWNED) && !self.monster_respawned)
 +              monsters_killed += 1;
 +              
 +      if(IS_PLAYER(attacker))
 +      if( autocvar_g_monsters_score_spawned || 
 +                      ( !(self.spawnflags & MONSTERFLAG_SPAWNED) && !self.monster_respawned) )
 +              PlayerScore_Add(attacker, SP_SCORE, +autocvar_g_monsters_score_kill);
 +
 +      if(!Monster_CanRespawn(self) || gibbed)
 +      {
 +              // number of monsters spawned with mobspawn command
 +              totalspawned -= 1;
 +              
 +              if(IS_CLIENT(self.realowner))
-       if not(self.flags & FL_FLY)
++              if(!self.monster_respawned)
 +                      self.realowner.monstercount -= 1;
 +      }
 +
 +      if(self.candrop && self.weapon)
 +              W_ThrowNewWeapon(self, self.weapon, 0, self.origin, randomvec() * 150 + '0 0 325');
 +
 +      self.event_damage       = monsters_corpse_damage;
 +      self.solid                      = SOLID_CORPSE;
 +      self.takedamage         = DAMAGE_AIM;
 +      self.deadflag           = DEAD_DEAD;
 +      self.enemy                      = world;
 +      self.movetype           = MOVETYPE_TOSS;
 +      self.moveto                     = self.origin;
 +      self.touch                      = MonsterTouch; // reset incase monster was pouncing
 +      self.reset                      = func_null;
 +      self.state                      = 0;
 +      self.attack_finished_single = 0;
 +
-       if not(self.monster_respawned)
++      if(!(self.flags & FL_FLY))
 +              self.velocity = '0 0 0';
 +
 +      MON_ACTION(self.monsterid, MR_DEATH);
 +}
 +
 +void monsters_damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
 +{
 +      
 +      if(self.frozen && deathtype != DEATH_KILL)
 +              return;
 +
 +      if(time < self.pain_finished && deathtype != DEATH_KILL)
 +              return;
 +
 +      if(time < self.spawnshieldtime && deathtype != DEATH_KILL)
 +              return;
 +
 +      vector v;
 +      float take, save;
 +
 +      v = healtharmor_applydamage(self.armorvalue, self.m_armor_blockpercent, deathtype, damage);
 +      take = v_x;
 +      save = v_y;
 +
 +      self.health -= take;
 +      
 +      WaypointSprite_UpdateHealth(self.sprite, self.health);
 +
 +      self.dmg_time = time;
 +
 +      if(sound_allowed(MSG_BROADCAST, attacker) && deathtype != DEATH_DROWN)
 +              spamsound (self, CH_PAIN, "misc/bodyimpact1.wav", VOL_BASE, ATTEN_NORM);  // FIXME: PLACEHOLDER
 +
 +      self.velocity += force * self.damageforcescale;
 +
 +      if(deathtype != DEATH_DROWN)
 +      {
 +              Violence_GibSplash_At(hitloc, force, 2, bound(0, take, 200) / 16, self, attacker);
 +              if (take > 50)
 +                      Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, self, attacker);
 +              if (take > 100)
 +                      Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, self, attacker);
 +      }
 +
 +      if(self.health <= 0)
 +      {
 +              if(deathtype == DEATH_KILL)
 +                      self.candrop = FALSE; // killed by mobkill command
 +
 +              // TODO: fix this?
 +              activator = attacker;
 +              other = self.enemy;
 +              SUB_UseTargets();
 +              self.target2 = self.oldtarget2; // reset to original target on death, incase we respawn
 +
 +              monster_die(attacker, (self.health <= -100 || deathtype == DEATH_KILL));
 +              
 +              WaypointSprite_Kill(self.sprite);
 +              
 +              frag_attacker = attacker;
 +              frag_target = self;
 +              MUTATOR_CALLHOOK(MonsterDies);
 +
 +              if(self.health <= -100 || deathtype == DEATH_KILL) // check if we're already gibbed
 +              {
 +                      Violence_GibSplash(self, 1, 0.5, attacker);
 +
 +                      self.think = SUB_Remove;
 +                      self.nextthink = time + 0.1;
 +              }
 +      }
 +}
 +
 +void monster_setupcolors()
 +{
 +      if(IS_PLAYER(self.monster_owner))
 +              self.colormap = self.monster_owner.colormap;
 +      else if(teamplay && self.team)
 +              self.colormap = 1024 + (self.team - 1) * 17;
 +      else
 +      {
 +              if(self.monster_skill <= MONSTER_SKILL_EASY)
 +                      self.colormap = 1029;
 +              else if(self.monster_skill <= MONSTER_SKILL_MEDIUM)
 +                      self.colormap = 1027;
 +              else if(self.monster_skill <= MONSTER_SKILL_HARD)
 +                      self.colormap = 1038;
 +              else if(self.monster_skill <= MONSTER_SKILL_INSANE)
 +                      self.colormap = 1028;
 +              else if(self.monster_skill <= MONSTER_SKILL_NIGHTMARE)
 +                      self.colormap = 1032;
 +              else
 +                      self.colormap = 1024;
 +      }
 +}
 +
 +void monster_think()
 +{
 +      self.think = monster_think;
 +      self.nextthink = self.ticrate;
 +      
 +      if(self.ltime)
 +      if(time >= self.ltime)
 +      {
 +              Damage(self, self, self, self.health + self.max_health, DEATH_KILL, self.origin, self.origin);
 +              return;
 +      }
 +
 +      MON_ACTION(self.monsterid, MR_THINK);
 +      
 +      CSQCMODEL_AUTOUPDATE();
 +}
 +
 +float monster_spawn()
 +{
 +      MON_ACTION(self.monsterid, MR_SETUP);
 +
-       if not(self.monster_respawned)
-       if not(self.skin)
++      if(!self.monster_respawned)
 +      {
 +              Monster_CheckMinibossFlag();
 +              self.health *= Monster_SkillModifier();
 +      }
 +
 +      self.max_health = self.health;
 +      self.pain_finished = self.nextthink;
 +      
 +      if(IS_PLAYER(self.monster_owner))
 +              self.effects |= EF_DIMLIGHT;
 +
-       if not(self.attack_range)
++      if(!self.monster_respawned)
++      if(!self.skin)
 +              self.skin = rint(random() * 4);
 +
-       if not(autocvar_g_monsters)
++      if(!self.attack_range)
 +              self.attack_range = autocvar_g_monsters_attack_range;
 +      
 +      precache_monstersounds();
 +      UpdateMonsterSounds();
 +
 +      if(teamplay)
 +              self.monster_attack = TRUE; // we can have monster enemies in team games
 +              
 +      MonsterSound(monstersound_spawn, 0, FALSE, CH_VOICE);
 +      
 +      WaypointSprite_Spawn(M_NAME(self.monsterid), 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);
 +
 +      self.think = monster_think;
 +      self.nextthink = time + self.ticrate;
 +      
 +      if(MUTATOR_CALLHOOK(MonsterSpawn))
 +              return FALSE;
 +      
 +      return TRUE;
 +}
 +
 +float monster_initialize(float mon_id, float nodrop)
 +{
-       if not(self.monster_skill)
++      if(!autocvar_g_monsters)
 +              return FALSE;
 +
 +      entity mon = get_monsterinfo(mon_id);
 +      
-       if not(self.spawnflags & MONSTERFLAG_SPAWNED) // naturally spawned monster
-       if not(self.monster_respawned)
++      if(!self.monster_skill)
 +              self.monster_skill = cvar("g_monsters_skill");
 +
 +      // support for quake style removing monsters based on skill
 +      if(self.monster_skill == MONSTER_SKILL_EASY) if(self.spawnflags & MONSTERSKILL_NOTEASY) { return FALSE; }
 +      if(self.monster_skill == MONSTER_SKILL_MEDIUM) if(self.spawnflags & MONSTERSKILL_NOTMEDIUM) { return FALSE; }
 +      if(self.monster_skill == MONSTER_SKILL_HARD) if(self.spawnflags & MONSTERSKILL_NOTHARD) { return FALSE; }
 +
 +      if(self.team && !teamplay)
 +              self.team = 0;
 +
-       if not(self.ticrate)
++      if(!(self.spawnflags & MONSTERFLAG_SPAWNED)) // naturally spawned monster
++      if(!self.monster_respawned)
 +              monsters_total += 1;
 +
 +      setmodel(self, mon.model);
 +      setsize(self, mon.mins, mon.maxs);
 +      self.flags                              = FL_MONSTER;
 +      self.takedamage                 = DAMAGE_AIM;
 +      self.bot_attack                 = TRUE;
 +      self.iscreature                 = TRUE;
 +      self.teleportable               = TRUE;
 +      self.damagedbycontents  = TRUE;
 +      self.monsterid                  = mon_id;
 +      self.damageforcescale   = 0;
 +      self.event_damage               = monsters_damage;
 +      self.touch                              = MonsterTouch;
 +      self.use                                = monster_use;
 +      self.solid                              = SOLID_BBOX;
 +      self.movetype                   = MOVETYPE_WALK;
 +      self.spawnshieldtime    = time + autocvar_g_monsters_spawnshieldtime;
 +      self.enemy                              = world;
 +      self.velocity                   = '0 0 0';
 +      self.moveto                             = self.origin;
 +      self.pos1                               = self.origin;
 +      self.pos2                               = self.angles;
 +      self.reset                              = monsters_reset;
 +      self.netname                    = mon.netname;
 +      self.monster_name               = M_NAME(mon_id);
 +      self.candrop                    = TRUE;
 +      self.view_ofs                   = '0 0 1' * (self.maxs_z * 0.5);
 +      self.oldtarget2                 = self.target2;
 +      self.deadflag                   = DEAD_NO;
 +      self.scale                              = 1;
 +      self.noalign                    = nodrop;
 +      self.spawn_time                 = time;
 +      self.spider_slowness    = 0;
 +      self.gravity                    = 1;
 +      self.dphitcontentsmask  = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
 +      
 +      if(autocvar_g_fullbrightplayers)
 +              self.effects |= EF_FULLBRIGHT;
 +              
 +      if(autocvar_g_nodepthtestplayers)
 +              self.effects |= EF_NODEPTHTEST;
 +
 +      if(mon.spawnflags & MONSTER_TYPE_SWIM)
 +              self.flags |= FL_SWIM;
 +
 +      if(mon.spawnflags & MONSTER_TYPE_FLY)
 +      {
 +              self.flags |= FL_FLY;
 +              self.movetype = MOVETYPE_FLY;
 +      }
 +
 +      if(mon.spawnflags & MONSTER_SIZE_BROKEN)
 +              self.scale = 1.3;
 +
-       if not(self.m_armor_blockpercent)
++      if(!self.ticrate)
 +              self.ticrate = autocvar_g_monsters_think_delay;
 +
 +      self.ticrate = bound(sys_frametime, self.ticrate, 60);
 +
-       if not(self.target_range)
++      if(!self.m_armor_blockpercent)
 +              self.m_armor_blockpercent = 0.5;
 +
-       if not(self.respawntime)
++      if(!self.target_range)
 +              self.target_range = autocvar_g_monsters_target_range;
 +
-       if not(self.monster_moveflags)
++      if(!self.respawntime)
 +              self.respawntime = autocvar_g_monsters_respawn_delay;
 +
-       if not(self.noalign)
++      if(!self.monster_moveflags)
 +              self.monster_moveflags = MONSTER_MOVE_WANDER;
 +              
-       if not(monster_spawn())
++      if(!self.noalign)
 +      {
 +              setorigin(self, self.origin + '0 0 20');
 +              tracebox(self.origin + '0 0 64', self.mins, self.maxs, self.origin - '0 0 10000', MOVE_WORLDONLY, self);
 +              setorigin(self, trace_endpos);
 +      }
 +              
-       if not(self.monster_respawned)
++      if(!monster_spawn())
 +              return FALSE;
 +              
++      if(!self.monster_respawned)
 +              monster_setupcolors();
 +      
 +      CSQCMODEL_AUTOINIT();
 +
 +      return TRUE;
 +}
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -182,138 -182,6 +182,138 @@@ void ClientCommand_join(float request
        }
  }
  
-                       if not(trace_ent.flags & FL_MONSTER) { sprint(self, "You need to aim at your monster to edit its properties.\n"); return; }
 +void ClientCommand_mobedit(float request, float argc)
 +{
 +      switch(request)
 +      {
 +              case CMD_REQUEST_COMMAND:
 +              {
 +                      makevectors(self.v_angle);
 +                      WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_NORMAL, self);
 +                      
++                      if(!(trace_ent.flags & FL_MONSTER)) { sprint(self, "You need to aim at your monster to edit its properties.\n"); return; }
 +                      if(trace_ent.realowner != self) { sprint(self, "That monster does not belong to you.\n"); return; }
 +                      
 +                      switch(argv(1))
 +                      {
 +                              case "skin": if(trace_ent.monsterid != MON_MAGE) { trace_ent.skin = stof(argv(2)); } return;
 +                              case "movetarget": trace_ent.monster_moveflags = stof(argv(2)); return;
 +                      }
 +              }
 +              default:
 +                      sprint(self, "Incorrect parameters for ^2mobedit^7\n");
 +              case CMD_REQUEST_USAGE:
 +              {
 +                      sprint(self, "\nUsage:^3 cmd mobedit [argument]\n");
 +                      sprint(self, "  Where 'argument' can be skin or movetarget.\n");
 +                      return;
 +              }
 +      }
 +}
 +      
 +void ClientCommand_mobkill(float request)
 +{
 +      switch(request)
 +      {
 +              case CMD_REQUEST_COMMAND:
 +              {
 +                      makevectors(self.v_angle);
 +                      WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 100, MOVE_NORMAL, self);
 +                      
 +                      if(trace_ent.flags & FL_MONSTER)
 +                      {
 +                              if(trace_ent.realowner != self)
 +                              {
 +                                      sprint(self, "That monster does not belong to you.\n");
 +                                      return;
 +                              }
 +                              sprint(self, strcat("Your pet '", trace_ent.monster_name, "' has been brutally mutilated.\n"));
 +                              Damage (trace_ent, world, world, trace_ent.health + trace_ent.max_health + 200, DEATH_KILL, trace_ent.origin, '0 0 0');
 +                              return;
 +                      }
 +                      else
 +                              sprint(self, "You need to aim at your monster to kill it.\n");
 +                      
 +                      return;
 +              }
 +      
 +              default:
 +                      sprint(self, "Incorrect parameters for ^2mobkill^7\n");
 +              case CMD_REQUEST_USAGE:
 +              {
 +                      sprint(self, "\nUsage:^3 cmd mobkill\n");
 +                      sprint(self, "  Aim at your monster to kill it.\n");
 +                      return;
 +              }
 +      }
 +}
 +
 +void ClientCommand_mobspawn(float request, float argc)
 +{
 +      switch(request)
 +      {
 +              case CMD_REQUEST_COMMAND:
 +              {
 +                      entity e;
 +                      string tospawn;
 +                      float moveflag, i;
 +                      
 +                      moveflag = (argv(2) ? stof(argv(2)) : 1); // follow owner if not defined
 +                      tospawn = strtolower(argv(1));
 +                      
 +                      if(tospawn == "list")
 +                      {
 +                              sprint(self, monsterlist_reply);
 +                              return;
 +                      }
 +                      
 +                      if(tospawn == "random")
 +                      {
 +                              RandomSelection_Init();
 +                              for(i = MON_FIRST; i <= MON_LAST; ++i)
 +                                      RandomSelection_Add(world, 0, (get_monsterinfo(i)).netname, 1, 1);
 +                              
 +                              tospawn = RandomSelection_chosen_string;
 +                      }
 +                      
 +                      if(autocvar_g_monsters_max <= 0 || autocvar_g_monsters_max_perplayer <= 0) { sprint(self, "Monster spawning is disabled.\n"); }
 +                      else if(!IS_PLAYER(self)) { sprint(self, "You can't spawn monsters while spectating.\n"); }
 +                      else if(g_invasion) { sprint(self, "You can't spawn monsters during an invasion!\n"); }
 +                      else if(!autocvar_g_monsters) { Send_Notification(NOTIF_ONE, self, MSG_INFO, INFO_MONSTERS_DISABLED); }
 +                      else if(self.vehicle) { sprint(self, "You can't spawn monsters while driving a vehicle.\n"); }
 +                      else if(autocvar_g_campaign) { sprint(self, "You can't spawn monsters in campaign mode.\n"); }
 +                      else if(self.deadflag != DEAD_NO) { sprint(self, "You can't spawn monsters while dead.\n"); }
 +                      else if(self.monstercount >= autocvar_g_monsters_max_perplayer) { sprint(self, "You have spawned too many monsters, kill some before trying to spawn any more.\n"); }
 +                      else if(totalspawned >= autocvar_g_monsters_max) { sprint(self, "The global maximum monster count has been reached, kill some before trying to spawn any more.\n"); }
 +                      else // all worked out, so continue
 +                      {
 +                              self.monstercount += 1;
 +                              totalspawned += 1;
 +                      
 +                              makevectors(self.v_angle);
 +                              WarpZone_TraceBox (CENTER_OR_VIEWOFS(self), PL_MIN, PL_MAX, CENTER_OR_VIEWOFS(self) + v_forward * 150, TRUE, self);
 +                              //WarpZone_TraceLine(self.origin + self.view_ofs, self.origin + self.view_ofs + v_forward * 150, MOVE_NORMAL, self);
 +                      
 +                              e = spawnmonster(tospawn, 0, self, self, trace_endpos, FALSE, moveflag);
 +                              
 +                              sprint(self, strcat("Spawned ", e.monster_name, "\n"));
 +                      }
 +                      
 +                      return;
 +              }
 +      
 +              default:
 +                      sprint(self, "Incorrect parameters for ^2mobspawn^7\n");
 +              case CMD_REQUEST_USAGE:
 +              {
 +                      sprint(self, "\nUsage:^3 cmd mobspawn monster\n");
 +                      sprint(self, "  See 'cmd mobspawn list' for available arguments.\n");
 +                      sprint(self, "  Argument 'random' spawns a randomly selected monster.\n");
 +                      return;
 +              }
 +      }
 +}
 +
  void ClientCommand_ready(float request) // todo: anti-spam for toggling readyness
  {
        switch(request)
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -96,11 -96,9 +96,11 @@@ const string STR_OBSERVER = "observer"
  #define FOR_EACH_REALCLIENT(v) FOR_EACH_CLIENT(v) if(IS_REAL_CLIENT(v))
  
  #define FOR_EACH_PLAYER(v) FOR_EACH_CLIENT(v) if(IS_PLAYER(v))
- #define FOR_EACH_SPEC(v) FOR_EACH_CLIENT(v) if not(IS_PLAYER(v)) // Samual: shouldn't this be IS_SPEC(v)? and rather create a separate macro to include observers too
+ #define FOR_EACH_SPEC(v) FOR_EACH_CLIENT(v) if (!IS_PLAYER(v)) // Samual: shouldn't this be IS_SPEC(v)? and rather create a separate macro to include observers too
  #define FOR_EACH_REALPLAYER(v) FOR_EACH_REALCLIENT(v) if(IS_PLAYER(v))
  
 +#define FOR_EACH_MONSTER(v) for(v = world; (v = findflags(v, flags, FL_MONSTER)) != world; )
 +
  #define CENTER_OR_VIEWOFS(ent) (ent.origin + (IS_PLAYER(ent) ? ent.view_ofs : ((ent.mins + ent.maxs) * 0.5)))
  
  // copies a string to a tempstring (so one can strunzone it)
@@@ -793,12 -787,7 +793,12 @@@ void ctf_FlagTouch(
                else
                        return; // do nothing
        }
-               if not(autocvar_g_ctf_allow_monster_touch)
 +      else if(toucher.flags & FL_MONSTER)
 +      {
-       else if not(IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
++              if(!autocvar_g_ctf_allow_monster_touch)
 +                      return; // do nothing
 +      }
+       else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
        {
                if(time > self.wait) // if we haven't in a while, play a sound/effect
                {
@@@ -498,9 -558,31 +498,9 @@@ MUTATOR_HOOKFUNCTION(freezetag_PlayerDa
        return 1;
  }
  
 -MUTATOR_HOOKFUNCTION(freezetag_PlayerJump)
 -{
 -      if(self.freezetag_frozen)
 -              return TRUE; // no jumping in freezetag when frozen
 -      
 -      return FALSE;
 -}
 -
 -MUTATOR_HOOKFUNCTION(freezetag_ForbidThrowCurrentWeapon)
 -{
 -      if (self.freezetag_frozen)
 -              return 1;
 -      return 0;
 -}
 -
 -MUTATOR_HOOKFUNCTION(freezetag_ItemTouch)
 -{
 -      if (other.freezetag_frozen)
 -              return MUT_ITEMTOUCH_RETURN;
 -      return MUT_ITEMTOUCH_CONTINUE;
 -}
 -
  MUTATOR_HOOKFUNCTION(freezetag_BotRoles)
  {
-       if not(self.deadflag)
+       if (!self.deadflag)
        {
                if (random() < 0.5)
                        self.havocbot_role = havocbot_role_ft_freeing;
index 4e339f7,0000000..de8b13e
mode 100644,000000..100644
--- /dev/null
@@@ -1,335 -1,0 +1,335 @@@
-       if not(g_invasion) { remove(self); return; }
 +void invasion_spawnpoint()
 +{
-       if not(self.monster_respawned)
++      if(!g_invasion) { remove(self); return; }
 +      
 +      self.classname = "invasion_spawnpoint";
 +}
 +
 +float invasion_PickMonster(float supermonster_count)
 +{
 +      if(autocvar_g_invasion_zombies_only)
 +              return MON_ZOMBIE;
 +
 +      float i;
 +      entity mon;
 +      
 +      RandomSelection_Init();
 +      
 +      for(i = MON_FIRST; i <= MON_LAST; ++i)
 +      {
 +              mon = get_monsterinfo(i);
 +              if((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM) || (mon.spawnflags & MON_FLAG_SUPERMONSTER && supermonster_count >= 1))
 +                      continue; // flying/swimming monsters not yet supported
 +              
 +              RandomSelection_Add(world, i, "", 1, 1);
 +      }
 +      
 +      return RandomSelection_chosen_float;
 +}
 +
 +entity invasion_PickSpawn()
 +{
 +      entity e;
 +      
 +      RandomSelection_Init();
 +      
 +      for(e = world;(e = find(e, classname, "invasion_spawnpoint")); )
 +              RandomSelection_Add(e, 0, string_null, 1, 1);
 +              
 +      return RandomSelection_chosen_ent;
 +}
 +
 +void invasion_SpawnChosenMonster(float mon)
 +{
 +      entity spawn_point, monster;
 +      
 +      spawn_point = invasion_PickSpawn();
 +      
 +      if(spawn_point == world)
 +      {
 +              dprint("Warning: couldn't find any invasion_spawnpoint spawnpoints, no monsters will spawn!\n");
 +              return;
 +      }
 +      
 +      monster = spawnmonster("", mon, spawn_point, spawn_point, spawn_point.origin, FALSE, 2);
 +      
 +      if(roundcnt >= maxrounds)
 +              monster.spawnflags |= MONSTERFLAG_MINIBOSS;
 +}
 +
 +void invasion_SpawnMonsters(float supermonster_count)
 +{
 +      float chosen_monster = invasion_PickMonster(supermonster_count);
 +      
 +      invasion_SpawnChosenMonster(chosen_monster);
 +}
 +
 +float Invasion_CheckWinner()
 +{
 +      entity head;
 +      if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
 +      {
 +              FOR_EACH_MONSTER(head)
 +                      monster_remove(head);
 +              
 +              if(roundcnt >= maxrounds)
 +              {
 +                      NextLevel();
 +                      return 1;
 +              }
 +      
 +              Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
 +              Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
 +              round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
 +              return 1;
 +      }
 +      
 +      // boss round
 +      if(roundcnt >= maxrounds)
 +      {
 +              if(numspawned < 1)
 +              {
 +                      maxspawned = 1;
 +                      invasion_SpawnMonsters(0);
 +              }
 +      }
 +      else
 +      {
 +              float total_alive_monsters = 0, supermonster_count = 0;
 +              
 +              FOR_EACH_MONSTER(head) if(head.health > 0)
 +              {
 +                      if((get_monsterinfo(head.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
 +                              ++supermonster_count;
 +                      ++total_alive_monsters;
 +              }
 +
 +              if((total_alive_monsters + numkilled) < maxspawned && maxcurrent < 10) // 10 at a time should be plenty
 +              {
 +                      if(time >= last_check)
 +                      {
 +                              invasion_SpawnMonsters(supermonster_count);
 +                              last_check = time + 2;
 +                      }
 +                      
 +                      return 0;
 +              }
 +      }
 +      
 +      if(numspawned < 1 || numkilled < maxspawned)
 +              return 0; // nothing has spawned yet, or there are still alive monsters
 +      
 +      if(roundcnt >= maxrounds)
 +      {
 +              NextLevel();
 +              return 1;
 +      }
 +      
 +      entity winner = world;
 +      float winning_score = 0;
 +      
 +      FOR_EACH_PLAYER(head)
 +      {
 +              float cs = PlayerScore_Add(head, SP_KILLS, 0);
 +              if(cs > winning_score)
 +              {
 +                      winning_score = cs;
 +                      winner = head;
 +              }
 +      }
 +      
 +      FOR_EACH_MONSTER(head)
 +              monster_remove(head);
 +
 +      if(winner)
 +      {
 +              Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, winner.netname);
 +              Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_PLAYER_WIN, winner.netname);
 +      }
 +      
 +      round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
 +
 +      return 1;
 +}
 +
 +float Invasion_CheckPlayers()
 +{
 +      return TRUE;
 +}
 +
 +void Invasion_RoundStart()
 +{
 +      entity e;
 +      float numplayers = 0;
 +      FOR_EACH_PLAYER(e)
 +      {
 +              e.player_blocked = 0;
 +              ++numplayers;
 +      }
 +              
 +      roundcnt += 1;
 +      
 +      invasion_monsterskill = roundcnt + (numplayers * 0.3);
 +      
 +      maxcurrent = 0;
 +      numspawned = 0;
 +      numkilled = 0;
 +              
 +      if(roundcnt > 1)
 +              maxspawned = rint(autocvar_g_invasion_monster_count * (roundcnt * 0.5));
 +      else
 +              maxspawned = autocvar_g_invasion_monster_count;
 +}
 +
 +MUTATOR_HOOKFUNCTION(invasion_MonsterDies)
 +{
-       if not(self.spawnflags & MONSTERFLAG_SPAWNED)
++      if(!self.monster_respawned)
 +      {
 +              numkilled += 1;
 +              maxcurrent -= 1;
 +      
 +              if(IS_PLAYER(frag_attacker))
 +                      PlayerScore_Add(frag_attacker, SP_KILLS, +1);
 +      }
 +      
 +      return FALSE;
 +}
 +
 +MUTATOR_HOOKFUNCTION(invasion_MonsterSpawn)
 +{
-       if not(self.monster_respawned)
++      if(!(self.spawnflags & MONSTERFLAG_SPAWNED))
 +      {
 +              monster_remove(self);
 +              return FALSE;
 +      }
 +      
 +      if(roundcnt < maxrounds && self.spawnflags & MONSTERFLAG_MINIBOSS)
 +              self.spawnflags &= ~MONSTERFLAG_MINIBOSS;
 +      
++      if(!self.monster_respawned)
 +      {
 +              numspawned += 1;
 +              maxcurrent += 1;
 +      }
 +      
 +      self.monster_skill = invasion_monsterskill;
 +      
 +      if((get_monsterinfo(self.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
 +              Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, M_NAME(self.monsterid));
 +      
 +      self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
 +      
 +      return FALSE;
 +}
 +
 +MUTATOR_HOOKFUNCTION(invasion_PlayerThink)
 +{
 +      monsters_total = maxspawned; // TODO: make sure numspawned never exceeds maxspawned
 +      monsters_killed = numkilled;
 +      
 +      return FALSE;
 +}
 +
 +MUTATOR_HOOKFUNCTION(invasion_PlayerSpawn)
 +{
 +      self.bot_attack = FALSE;
 +      return FALSE;
 +}
 +
 +MUTATOR_HOOKFUNCTION(invasion_PlayerDamage)
 +{
 +      if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target) && frag_attacker != frag_target)
 +      {
 +              frag_damage = 0;
 +              frag_force = '0 0 0';
 +      }
 +      
 +      return FALSE;
 +}
 +
 +MUTATOR_HOOKFUNCTION(invasion_PlayerCommand)
 +{
 +      if(MUTATOR_RETURNVALUE) // command was already handled?
 +              return FALSE;
 +      
 +      if(cmd_name == "debuginvasion")
 +      {
 +              sprint(self, strcat("maxspawned = ", ftos(maxspawned), "\n"));
 +              sprint(self, strcat("numspawned = ", ftos(numspawned), "\n"));
 +              sprint(self, strcat("numkilled = ", ftos(numkilled), "\n"));
 +              sprint(self, strcat("roundcnt = ", ftos(roundcnt), "\n"));
 +              sprint(self, strcat("monsters_total = ", ftos(monsters_total), "\n"));
 +              sprint(self, strcat("monsters_killed = ", ftos(monsters_killed), "\n"));
 +              sprint(self, strcat("invasion_monsterskill = ", ftos(invasion_monsterskill), "\n"));
 +              
 +              return TRUE;
 +      }
 +      
 +      return FALSE;
 +}
 +
 +MUTATOR_HOOKFUNCTION(invasion_SetStartItems)
 +{
 +      start_health = 200;
 +      start_armorvalue = 200;
 +      
 +      return FALSE;
 +}
 +
 +void invasion_ScoreRules()
 +{
 +      ScoreRules_basics(0, 0, 0, FALSE);
 +      ScoreInfo_SetLabel_PlayerScore(SP_KILLS, "frags", SFL_SORT_PRIO_PRIMARY);
 +      ScoreRules_basics_end();
 +}
 +
 +void invasion_Initialize()
 +{
 +      independent_players = 1; // to disable extra useless scores
 +
 +      invasion_ScoreRules();
 +      
 +      independent_players = 0;
 +
 +      round_handler_Spawn(Invasion_CheckPlayers, Invasion_CheckWinner, Invasion_RoundStart);
 +      round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
 +      
 +      allowed_to_spawn = TRUE;
 +      
 +      roundcnt = 0;
 +}
 +
 +MUTATOR_DEFINITION(gamemode_invasion)
 +{
 +      MUTATOR_HOOK(MonsterDies, invasion_MonsterDies, CBC_ORDER_ANY);
 +      MUTATOR_HOOK(MonsterSpawn, invasion_MonsterSpawn, CBC_ORDER_ANY);
 +      MUTATOR_HOOK(PlayerPreThink, invasion_PlayerThink, CBC_ORDER_ANY);
 +      MUTATOR_HOOK(PlayerSpawn, invasion_PlayerSpawn, CBC_ORDER_ANY);
 +      MUTATOR_HOOK(PlayerDamage_Calculate, invasion_PlayerDamage, CBC_ORDER_ANY);
 +      MUTATOR_HOOK(SV_ParseClientCommand, invasion_PlayerCommand, CBC_ORDER_ANY);
 +      MUTATOR_HOOK(SetStartItems, invasion_SetStartItems, CBC_ORDER_ANY);
 +      
 +      MUTATOR_ONADD
 +      {
 +              if(time > 1) // game loads at time 1
 +                      error("This is a game type and it cannot be added at runtime.");
 +              invasion_Initialize();
 +              
 +              cvar_settemp("g_monsters", "1");
 +      }
 +
 +      MUTATOR_ONROLLBACK_OR_REMOVE
 +      {
 +              // we actually cannot roll back invasion_Initialize here
 +              // BUT: we don't need to! If this gets called, adding always
 +              // succeeds.
 +      }
 +
 +      MUTATOR_ONREMOVE
 +      {
 +              print("This is a game type and it cannot be removed at runtime.");
 +              return -1;
 +      }
 +
 +      return 0;
 +}
Simple merge
@@@ -680,10 -680,8 +680,10 @@@ void Item_Touch (void
                }
        }
  
-       if not(IS_PLAYER(other))
+       if (!IS_PLAYER(other))
                return;
 +      if (other.frozen)
 +              return;
        if (other.deadflag)
                return;
        if (self.solid != SOLID_TRIGGER)
Simple merge
@@@ -1099,16 -1099,14 +1099,16 @@@ float turret_stdproc_init (string cvar_
          self.respawntime = 60;
      self.respawntime = max(-1, self.respawntime);
  
-     if not (self.health)
+     if (!self.health)
          self.health = 1000;
      self.tur_health = max(1, self.health);
 +      self.bot_attack = TRUE;
 +    self.monster_attack = TRUE;
  
-     if not (self.turrcaps_flags)
+     if (!self.turrcaps_flags)
          self.turrcaps_flags = TFL_TURRCAPS_RADIUSDMG | TFL_TURRCAPS_MEDPROJ | TFL_TURRCAPS_PLAYERKILL;
  
-     if not (self.damage_flags)
+     if (!self.damage_flags)
          self.damage_flags = TFL_DMG_YES | TFL_DMG_RETALIATE | TFL_DMG_AIMSHAKE;
  
  // Shot stuff.
@@@ -38,18 -38,18 +38,18 @@@ float turret_fusionreactor_firecheck(
                return 0;
  
        if (self.ammo < self.shot_dmg)
 -              return 0;
 -
 -      if (self.enemy.ammo >= self.enemy.ammo_max)
 -              return 0;
 +              return 0;       
        
        if (vlen(self.enemy.origin - self.origin) > self.target_range)
 -              return 0;                               
 +              return 0;       
 +      
 +      if (self.enemy.ammo >= self.enemy.ammo_max)
 +              return 0;
        
 -      if(self.team != self.enemy.team)
 +      if(DIFF_TEAM(self, self.enemy))
                return 0;
        
-       if not (self.enemy.ammo_flags & TFL_AMMO_ENERGY)
+       if (!(self.enemy.ammo_flags & TFL_AMMO_ENERGY))
                return 0;    
        
        return 1;
Simple merge
Simple merge
Simple merge