Merge branch 'master' into samual/weapons
authorMario <mario.mario@y7mail.com>
Mon, 6 Jan 2014 23:53:04 +0000 (10:53 +1100)
committerMario <mario.mario@y7mail.com>
Mon, 6 Jan 2014 23:53:04 +0000 (10:53 +1100)
35 files changed:
1  2 
defaultXonotic.cfg
effectinfo.txt
qcsrc/client/Main.qc
qcsrc/client/progs.src
qcsrc/client/scoreboard.qc
qcsrc/client/weapons/projectile.qc
qcsrc/common/constants.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/sv_monsters.qc
qcsrc/common/notifications.qh
qcsrc/common/util.qh
qcsrc/common/weapons/w_shockwave.qc
qcsrc/common/weapons/w_shotgun.qc
qcsrc/menu/progs.src
qcsrc/server/autocvars.qh
qcsrc/server/cl_client.qc
qcsrc/server/cl_impulse.qc
qcsrc/server/cl_physics.qc
qcsrc/server/defs.qh
qcsrc/server/g_damage.qc
qcsrc/server/g_world.qc
qcsrc/server/miscfunctions.qc
qcsrc/server/mutators/mutator_minstagib.qc
qcsrc/server/progs.src
qcsrc/server/t_items.qc
qcsrc/server/tturrets/system/system_main.qc
qcsrc/server/vehicles/bumblebee.qc
qcsrc/server/vehicles/racer.qc
qcsrc/server/vehicles/raptor.qc
qcsrc/server/vehicles/spiderbot.qc
qcsrc/server/vehicles/vehicles.qc
qcsrc/server/weapons/accuracy.qc

Simple merge
diff --cc effectinfo.txt
Simple merge
Simple merge
Simple merge
Simple merge
index aa825a5,0000000..c98ab7b
mode 100644,000000..100644
--- /dev/null
@@@ -1,529 -1,0 +1,538 @@@
 +.vector iorigin1, iorigin2;
 +.float spawntime;
 +.vector trail_oldorigin;
 +.float trail_oldtime;
 +.float fade_time, fade_rate;
 +
 +void SUB_Stop()
 +{
 +      self.move_velocity = self.move_avelocity = '0 0 0';
 +      self.move_movetype = MOVETYPE_NONE;
 +}
 +
 +.float alphamod;
 +.float count; // set if clientside projectile
 +.float cnt; // sound index
 +.float gravity;
 +.float snd_looping;
 +.float silent;
 +
 +void Projectile_ResetTrail(vector to)
 +{
 +      self.trail_oldorigin = to;
 +      self.trail_oldtime = time;
 +}
 +
 +void Projectile_DrawTrail(vector to)
 +{
 +      vector from;
 +      float t0;
 +
 +      from = self.trail_oldorigin;
 +      t0 = self.trail_oldtime;
 +      self.trail_oldorigin = to;
 +      self.trail_oldtime = time;
 +
 +      // force the effect even for stationary firemine
 +      if(self.cnt == PROJECTILE_FIREMINE)
 +              if(from == to)
 +                      from_z += 1;
 +
 +      if (self.traileffect)
 +      {
 +              particles_alphamin = particles_alphamax = particles_fade = sqrt(self.alpha);
 +              boxparticles(self.traileffect, self, from, to, self.velocity, self.velocity, 1, PARTICLES_USEALPHA | PARTICLES_USEFADE | PARTICLES_DRAWASTRAIL);
 +      }
 +}
 +
 +void Projectile_Draw()
 +{
 +      vector rot;
 +      vector trailorigin;
 +      float f;
 +      float drawn;
 +      float t;
 +      float a;
 +
 +      f = self.move_flags;
 +
 +      if(self.count & 0x80)
 +      {
 +              //self.move_flags &= ~FL_ONGROUND;
 +              if(self.move_movetype == MOVETYPE_NONE || self.move_movetype == MOVETYPE_FLY)
 +                      Movetype_Physics_NoMatchServer();
 +                      // the trivial movetypes do not have to match the
 +                      // server's ticrate as they are ticrate independent
 +                      // NOTE: this assumption is only true if MOVETYPE_FLY
 +                      // projectiles detonate on impact. If they continue
 +                      // moving, we might still be ticrate dependent.
 +              else
 +                      Movetype_Physics_MatchServer(autocvar_cl_projectiles_sloppy);
 +              if(!(self.move_flags & FL_ONGROUND))
 +                      if(self.velocity != '0 0 0')
 +                              self.move_angles = self.angles = vectoangles(self.velocity);
 +      }
 +      else
 +      {
 +              InterpolateOrigin_Do();
 +      }
 +
 +      if(self.count & 0x80)
 +      {
 +              drawn = (time >= self.spawntime - 0.02);
 +              t = max(time, self.spawntime);
 +      }
 +      else
 +      {
 +              drawn = (self.iflags & IFLAG_VALID);
 +              t = time;
 +      }
 +
 +      if(!(f & FL_ONGROUND))
 +      {
 +              rot = '0 0 0';
 +              switch(self.cnt)
 +              {
 +                      /*
 +                      case PROJECTILE_GRENADE:
 +                              rot = '-2000 0 0'; // forward
 +                              break;
 +                      */
 +                      case PROJECTILE_GRENADE_BOUNCING:
 +                              rot = '0 -1000 0'; // sideways
 +                              break;
 +                      case PROJECTILE_NADE_RED_BURN:
 +                      case PROJECTILE_NADE_RED:
 +                      case PROJECTILE_NADE_BLUE_BURN:
 +                      case PROJECTILE_NADE_BLUE:
 +                      case PROJECTILE_NADE_YELLOW_BURN:
 +                      case PROJECTILE_NADE_YELLOW:
 +                      case PROJECTILE_NADE_PINK_BURN:
 +                      case PROJECTILE_NADE_PINK:
 +                      case PROJECTILE_NADE_BURN:
 +                      case PROJECTILE_NADE:
 +                              rot = self.avelocity;
 +                              break;
 +                      case PROJECTILE_HOOKBOMB:
 +                              rot = '1000 0 0'; // forward
 +                              break;
 +                      default:
 +                              break;
 +              }
 +              self.angles = AnglesTransform_ToAngles(AnglesTransform_Multiply(AnglesTransform_FromAngles(self.angles), rot * (t - self.spawntime)));
 +      }
 +
 +      vector ang;
 +      ang = self.angles;
 +      ang_x = -ang_x;
 +      makevectors(ang);
 +
 +      a = 1 - (time - self.fade_time) * self.fade_rate;
 +      self.alpha = bound(0, self.alphamod * a, 1);
 +      if(self.alpha <= 0)
 +              drawn = 0;
 +      self.renderflags = 0;
 +
 +      trailorigin = self.origin;
 +      switch(self.cnt)
 +      {
 +          case PROJECTILE_NADE_RED_BURN:
 +              case PROJECTILE_NADE_RED:
 +              case PROJECTILE_NADE_BLUE_BURN:
 +              case PROJECTILE_NADE_BLUE:
 +              case PROJECTILE_NADE_YELLOW_BURN:
 +              case PROJECTILE_NADE_YELLOW:
 +              case PROJECTILE_NADE_PINK_BURN:
 +              case PROJECTILE_NADE_PINK:
 +              case PROJECTILE_NADE_BURN:
 +              case PROJECTILE_NADE:
 +                      trailorigin += v_up * 4;
 +                      break;
 +              case PROJECTILE_GRENADE:
 +              case PROJECTILE_GRENADE_BOUNCING:
 +                      trailorigin += v_right * 1 + v_forward * -10;
 +                      break;
 +              default:
 +                      break;
 +      }
 +      if(drawn)
 +              Projectile_DrawTrail(trailorigin);
 +      else
 +              Projectile_ResetTrail(trailorigin);
 +
 +      self.drawmask = 0;
 +
 +      if(!drawn)
 +              return;
 +
 +      switch(self.cnt)
 +      {
 +              // Possibly add dlights here.
 +              default:
 +                      break;
 +      }
 +
 +      self.drawmask = MASK_NORMAL;
 +}
 +
 +void loopsound(entity e, float ch, string samp, float vol, float attn)
 +{
 +      if(self.silent)
 +              return;
 +
 +      sound(e, ch, samp, vol, attn);
 +      e.snd_looping = ch;
 +}
 +
 +void Ent_RemoveProjectile()
 +{
 +      if(self.count & 0x80)
 +      {
 +              tracebox(self.origin, self.mins, self.maxs, self.origin + self.velocity * 0.05, MOVE_NORMAL, self);
 +              Projectile_DrawTrail(trace_endpos);
 +      }
 +}
 +
 +void Ent_Projectile()
 +{
 +      float f;
 +
 +      // projectile properties:
 +      //   kind (interpolated, or clientside)
 +      //
 +      //   modelindex
 +      //   origin
 +      //   scale
 +      //   if clientside:
 +      //     velocity
 +      //     gravity
 +      //   soundindex (hardcoded list)
 +      //   effects
 +      //
 +      // projectiles don't send angles, because they always follow the velocity
 +
 +      f = ReadByte();
 +      self.count = (f & 0x80);
 +      self.iflags = (self.iflags & IFLAG_INTERNALMASK) | IFLAG_AUTOANGLES | IFLAG_ANGLES | IFLAG_ORIGIN;
 +      self.solid = SOLID_TRIGGER;
 +      //self.effects = EF_NOMODELFLAGS;
 +
 +      // this should make collisions with bmodels more exact, but it leads to
 +      // projectiles no longer being able to lie on a bmodel
 +      self.move_nomonsters = MOVE_WORLDONLY;
 +      if(f & 0x40)
 +              self.move_flags |= FL_ONGROUND;
 +      else
 +              self.move_flags &= ~FL_ONGROUND;
 +
 +      if(!self.move_time)
 +      {
 +              // for some unknown reason, we don't need to care for
 +              // sv_gameplayfix_delayprojectiles here.
 +              self.move_time = time;
 +              self.spawntime = time;
 +      }
 +      else
 +              self.move_time = max(self.move_time, time);
 +
 +      if(!(self.count & 0x80))
 +              InterpolateOrigin_Undo();
 +
 +      if(f & 1)
 +      {
 +              self.origin_x = ReadCoord();
 +              self.origin_y = ReadCoord();
 +              self.origin_z = ReadCoord();
 +              setorigin(self, self.origin);
 +              if(self.count & 0x80)
 +              {
 +                      self.velocity_x = ReadCoord();
 +                      self.velocity_y = ReadCoord();
 +                      self.velocity_z = ReadCoord();
 +                      if(f & 0x10)
 +                              self.gravity = ReadCoord();
 +                      else
 +                              self.gravity = 0; // none
 +                      self.move_origin = self.origin;
 +                      self.move_velocity = self.velocity;
 +              }
 +
 +              if(time == self.spawntime || (self.count & 0x80) || (f & 0x08))
 +              {
 +                      self.trail_oldorigin = self.origin;
 +                      if(!(self.count & 0x80))
 +                              InterpolateOrigin_Reset();
 +              }
 +
 +              if(f & 0x20)
 +              {
 +                      self.fade_time = time + ReadByte() * ticrate;
 +                      self.fade_rate = 1 / (ReadByte() * ticrate);
 +              }
 +              else
 +              {
 +                      self.fade_time = 0;
 +                      self.fade_rate = 0;
 +              }
 +      }
 +
 +      if(f & 2)
 +      {
 +              self.cnt = ReadByte();
 +
 +              self.silent = (self.cnt & 0x80);
 +              self.cnt = (self.cnt & 0x7F);
 +
 +              self.scale = 1;
 +              self.traileffect = 0;
 +              switch(self.cnt)
 +              {
 +                      case PROJECTILE_ELECTRO: setmodel(self, "models/ebomb.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break;
 +                      case PROJECTILE_ROCKET: setmodel(self, "models/rocket.md3");self.traileffect = particleeffectnum("TR_ROCKET"); self.scale = 2; break;
 +                      case PROJECTILE_CRYLINK: setmodel(self, "models/plasmatrail.mdl");self.traileffect = particleeffectnum("TR_CRYLINKPLASMA"); break;
 +                      case PROJECTILE_CRYLINK_BOUNCING: setmodel(self, "models/plasmatrail.mdl");self.traileffect = particleeffectnum("TR_CRYLINKPLASMA"); break;
 +                      case PROJECTILE_ELECTRO_BEAM: setmodel(self, "models/elaser.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break;
 +                      case PROJECTILE_GRENADE: setmodel(self, "models/grenademodel.md3");self.traileffect = particleeffectnum("TR_GRENADE"); break;
 +                      case PROJECTILE_GRENADE_BOUNCING: setmodel(self, "models/grenademodel.md3");self.traileffect = particleeffectnum("TR_GRENADE"); break;
 +                      case PROJECTILE_MINE: setmodel(self, "models/mine.md3");self.traileffect = particleeffectnum("TR_GRENADE"); break;
 +                      case PROJECTILE_BLASTER: setmodel(self, "models/laser.mdl");self.traileffect = particleeffectnum(""); break;
 +                      case PROJECTILE_HLAC: setmodel(self, "models/hlac_bullet.md3");self.traileffect = particleeffectnum(""); break;
 +                      case PROJECTILE_PORTO_RED: setmodel(self, "models/grenademodel.md3");self.traileffect = particleeffectnum("TR_WIZSPIKE"); self.scale = 4; break;
 +                      case PROJECTILE_PORTO_BLUE: setmodel(self, "models/grenademodel.md3");self.traileffect = particleeffectnum("TR_WIZSPIKE"); self.scale = 4; break;
 +                      case PROJECTILE_HOOKBOMB: setmodel(self, "models/grenademodel.md3");self.traileffect = particleeffectnum("TR_KNIGHTSPIKE"); break;
 +                      case PROJECTILE_HAGAR: setmodel(self, "models/hagarmissile.mdl");self.traileffect = particleeffectnum("tr_hagar"); self.scale = 0.75; break;
 +                      case PROJECTILE_HAGAR_BOUNCING: setmodel(self, "models/hagarmissile.mdl");self.traileffect = particleeffectnum("tr_hagar"); self.scale = 0.75; break;
 +                      case PROJECTILE_FIREBALL: self.model = ""; self.modelindex = 0; self.traileffect = particleeffectnum("fireball"); break; // particle effect is good enough
 +                      case PROJECTILE_FIREMINE: self.model = ""; self.modelindex = 0; self.traileffect = particleeffectnum("firemine"); break; // particle effect is good enough
 +                      case PROJECTILE_TAG: setmodel(self, "models/laser.mdl"); self.traileffect = particleeffectnum("TR_ROCKET"); break;
 +                      case PROJECTILE_FLAC: setmodel(self, "models/hagarmissile.mdl"); self.scale = 0.4; self.traileffect = particleeffectnum("TR_SEEKER"); break;
 +                      case PROJECTILE_SEEKER: setmodel(self, "models/tagrocket.md3"); self.traileffect = particleeffectnum("TR_SEEKER"); break;
 +
++                      case PROJECTILE_MAGE_SPIKE: setmodel(self, "models/ebomb.mdl"); self.traileffect = particleeffectnum("TR_VORESPIKE"); break;
++                      case PROJECTILE_SHAMBLER_LIGHTNING: setmodel(self, "models/ebomb.mdl"); self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break;
++
 +                      case PROJECTILE_RAPTORBOMB:    setmodel(self, "models/vehicles/clusterbomb.md3"); self.gravity = 1; self.avelocity = '0 0 180'; self.traileffect = particleeffectnum(""); break;
 +                      case PROJECTILE_RAPTORBOMBLET: setmodel(self, "models/vehicles/bomblet.md3");     self.gravity = 1; self.avelocity = '0 0 180'; self.traileffect = particleeffectnum(""); break;
 +                      case PROJECTILE_RAPTORCANNON:  setmodel(self, "models/plasmatrail.mdl"); self.traileffect = particleeffectnum("TR_CRYLINKPLASMA"); break;
 +
 +                      case PROJECTILE_SPIDERROCKET: setmodel(self, "models/vehicles/rocket02.md3"); self.traileffect = particleeffectnum("spiderbot_rocket_thrust"); break;
 +                      case PROJECTILE_WAKIROCKET:   setmodel(self, "models/vehicles/rocket01.md3");  self.traileffect = particleeffectnum("wakizashi_rocket_thrust"); break;
 +                      case PROJECTILE_WAKICANNON:   setmodel(self, "models/laser.mdl");  self.traileffect = particleeffectnum(""); break;
 +
 +                      case PROJECTILE_BUMBLE_GUN: setmodel(self, "models/elaser.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break;
 +                      case PROJECTILE_BUMBLE_BEAM: setmodel(self, "models/elaser.mdl");self.traileffect = particleeffectnum("TR_NEXUIZPLASMA"); break;
 +
 +                      case PROJECTILE_NADE_RED: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_red"); break;
 +                      case PROJECTILE_NADE_RED_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_red_burn"); break;
 +                      case PROJECTILE_NADE_BLUE: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_blue"); break;
 +                      case PROJECTILE_NADE_BLUE_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_blue_burn"); break;
 +                      case PROJECTILE_NADE_YELLOW: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_yellow"); break;
 +                      case PROJECTILE_NADE_YELLOW_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_yellow_burn"); break;
 +                      case PROJECTILE_NADE_PINK: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_pink"); break;
 +                      case PROJECTILE_NADE_PINK_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_pink_burn"); break;
 +                      case PROJECTILE_NADE: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade"); break;
 +                      case PROJECTILE_NADE_BURN: setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum("nade_burn"); break;
 +
 +                      default:
 +                              error("Received invalid CSQC projectile, can't work with this!");
 +                              break;
 +              }
 +
 +              self.mins = '0 0 0';
 +              self.maxs = '0 0 0';
 +              self.colormod = '0 0 0';
 +              self.move_touch = SUB_Stop;
 +              self.move_movetype = MOVETYPE_TOSS;
 +              self.alphamod = 1;
 +
 +              switch(self.cnt)
 +              {
 +                      case PROJECTILE_ELECTRO:
 +                              // only new engines support sound moving with object
 +                              loopsound(self, CH_SHOTS_SINGLE, "weapons/electro_fly.wav", VOL_BASE, ATTEN_NORM);
 +                              self.mins = '0 0 -4';
 +                              self.maxs = '0 0 -4';
 +                              self.move_movetype = MOVETYPE_BOUNCE;
 +                              self.move_touch = func_null;
 +                              self.move_bounce_factor = g_balance_electro_secondary_bouncefactor;
 +                              self.move_bounce_stopspeed = g_balance_electro_secondary_bouncestop;
 +                              break;
 +                      case PROJECTILE_ROCKET:
 +                              loopsound(self, CH_SHOTS_SINGLE, "weapons/rocket_fly.wav", VOL_BASE, ATTEN_NORM);
 +                              self.mins = '-3 -3 -3';
 +                              self.maxs = '3 3 3';
 +                              break;
 +                      case PROJECTILE_GRENADE:
 +                              self.mins = '-3 -3 -3';
 +                              self.maxs = '3 3 3';
 +                              break;
 +                      case PROJECTILE_NADE_RED_BURN:
 +                      case PROJECTILE_NADE_RED:
 +                      case PROJECTILE_NADE_BLUE_BURN:
 +                      case PROJECTILE_NADE_BLUE:
 +                              self.mins = '-3 -3 -3';
 +                              self.maxs = '3 3 3';
 +                              self.move_movetype = MOVETYPE_BOUNCE;
 +                              self.move_touch = func_null;
 +                              self.scale = 1.5;
 +                              self.avelocity = randomvec() * 720;
 +                              break;
 +                      case PROJECTILE_GRENADE_BOUNCING:
 +                              self.mins = '-3 -3 -3';
 +                              self.maxs = '3 3 3';
 +                              self.move_movetype = MOVETYPE_BOUNCE;
 +                              self.move_touch = func_null;
 +                              self.move_bounce_factor = g_balance_grenadelauncher_bouncefactor;
 +                              self.move_bounce_stopspeed = g_balance_grenadelauncher_bouncestop;
 +                              break;
 +                      case PROJECTILE_NADE_RED_BURN:
 +                      case PROJECTILE_NADE_RED:
 +                      case PROJECTILE_NADE_BLUE_BURN:
 +                      case PROJECTILE_NADE_BLUE:
 +                      case PROJECTILE_NADE_YELLOW_BURN:
 +                      case PROJECTILE_NADE_YELLOW:
 +                      case PROJECTILE_NADE_PINK_BURN:
 +                      case PROJECTILE_NADE_PINK:
 +                      case PROJECTILE_NADE_BURN:
 +                      case PROJECTILE_NADE:
 +                              self.mins = '-16 -16 -16';
 +                              self.maxs = '16 16 16';
 +                              self.move_movetype = MOVETYPE_BOUNCE;
 +                              self.move_touch = func_null;
 +                              self.scale = 1.5;
 +                              self.avelocity = randomvec() * 720;
 +                              break;
++                      case PROJECTILE_SHAMBLER_LIGHTNING:
++                              self.mins = '-8 -8 -8';
++                              self.maxs = '8 8 8';
++                              self.scale = 2.5;
++                              self.avelocity = randomvec() * 720;
++                              break;
 +                      case PROJECTILE_MINE:
 +                              self.mins = '-4 -4 -4';
 +                              self.maxs = '4 4 4';
 +                              break;
 +                      case PROJECTILE_PORTO_RED:
 +                              self.colormod = '2 1 1';
 +                              self.alphamod = 0.5;
 +                              self.move_movetype = MOVETYPE_BOUNCE;
 +                              self.move_touch = func_null;
 +                              break;
 +                      case PROJECTILE_PORTO_BLUE:
 +                              self.colormod = '1 1 2';
 +                              self.alphamod = 0.5;
 +                              self.move_movetype = MOVETYPE_BOUNCE;
 +                              self.move_touch = func_null;
 +                              break;
 +                      case PROJECTILE_HAGAR_BOUNCING:
 +                              self.move_movetype = MOVETYPE_BOUNCE;
 +                              self.move_touch = func_null;
 +                              break;
 +                      case PROJECTILE_CRYLINK_BOUNCING:
 +                              self.move_movetype = MOVETYPE_BOUNCE;
 +                              self.move_touch = func_null;
 +                              break;
 +                      case PROJECTILE_FIREBALL:
 +                              loopsound(self, CH_SHOTS_SINGLE, "weapons/fireball_fly2.wav", VOL_BASE, ATTEN_NORM);
 +                              self.mins = '-16 -16 -16';
 +                              self.maxs = '16 16 16';
 +                              break;
 +                      case PROJECTILE_FIREMINE:
 +                              loopsound(self, CH_SHOTS_SINGLE, "weapons/fireball_fly.wav", VOL_BASE, ATTEN_NORM);
 +                              self.move_movetype = MOVETYPE_BOUNCE;
 +                              self.move_touch = func_null;
 +                              self.mins = '-4 -4 -4';
 +                              self.maxs = '4 4 4';
 +                              break;
 +                      case PROJECTILE_TAG:
 +                              self.mins = '-2 -2 -2';
 +                              self.maxs = '2 2 2';
 +                              break;
 +                      case PROJECTILE_FLAC:
 +                              self.mins = '-2 -2 -2';
 +                              self.maxs = '2 2 2';
 +                              break;
 +                      case PROJECTILE_SEEKER:
 +                              loopsound(self, CH_SHOTS_SINGLE, "weapons/tag_rocket_fly.wav", VOL_BASE, ATTEN_NORM);
 +                              self.mins = '-4 -4 -4';
 +                              self.maxs = '4 4 4';
 +                              break;
 +            case PROJECTILE_RAPTORBOMB:
 +                              self.mins = '-3 -3 -3';
 +                              self.maxs = '3 3 3';
 +                              break;
 +            case PROJECTILE_RAPTORBOMBLET:
 +                              break;
 +            case PROJECTILE_RAPTORCANNON:
 +                              break;
 +            case PROJECTILE_SPIDERROCKET:
 +                loopsound(self, CH_SHOTS_SINGLE, "weapons/tag_rocket_fly.wav", VOL_BASE, ATTEN_NORM);
 +                              break;
 +            case PROJECTILE_WAKIROCKET:
 +                loopsound(self, CH_SHOTS_SINGLE, "weapons/tag_rocket_fly.wav", VOL_BASE, ATTEN_NORM);
 +                              break;
 +            /*
 +            case PROJECTILE_WAKICANNON:
 +                              break;
 +                      case PROJECTILE_BUMBLE_GUN:
 +                              // only new engines support sound moving with object
 +                              loopsound(self, CH_SHOTS_SINGLE, "weapons/electro_fly.wav", VOL_BASE, ATTEN_NORM);
 +                              self.mins = '0 0 -4';
 +                              self.maxs = '0 0 -4';
 +                              self.move_movetype = MOVETYPE_BOUNCE;
 +                              self.move_touch = func_null;
 +                              self.move_bounce_factor = g_balance_electro_secondary_bouncefactor;
 +                              self.move_bounce_stopspeed = g_balance_electro_secondary_bouncestop;
 +                              break;
 +                      */
 +                      default:
 +                              break;
 +              }
 +              setsize(self, self.mins, self.maxs);
 +      }
 +
 +      if(self.gravity)
 +      {
 +              if(self.move_movetype == MOVETYPE_FLY)
 +                      self.move_movetype = MOVETYPE_TOSS;
 +              if(self.move_movetype == MOVETYPE_BOUNCEMISSILE)
 +                      self.move_movetype = MOVETYPE_BOUNCE;
 +      }
 +      else
 +      {
 +              if(self.move_movetype == MOVETYPE_TOSS)
 +                      self.move_movetype = MOVETYPE_FLY;
 +              if(self.move_movetype == MOVETYPE_BOUNCE)
 +                      self.move_movetype = MOVETYPE_BOUNCEMISSILE;
 +      }
 +
 +      if(!(self.count & 0x80))
 +              InterpolateOrigin_Note();
 +
 +      self.draw = Projectile_Draw;
 +      self.entremove = Ent_RemoveProjectile;
 +}
 +
 +void Projectile_Precache()
 +{
 +      precache_model("models/ebomb.mdl");
 +      precache_model("models/elaser.mdl");
 +      precache_model("models/grenademodel.md3");
 +      precache_model("models/mine.md3");
 +      precache_model("models/hagarmissile.mdl");
 +      precache_model("models/hlac_bullet.md3");
 +      precache_model("models/laser.mdl");
 +      precache_model("models/plasmatrail.mdl");
 +      precache_model("models/rocket.md3");
 +      precache_model("models/tagrocket.md3");
 +      precache_model("models/tracer.mdl");
 +
 +      precache_model("models/weapons/v_ok_grenade.md3");
 +
 +      precache_sound("weapons/electro_fly.wav");
 +      precache_sound("weapons/rocket_fly.wav");
 +      precache_sound("weapons/fireball_fly.wav");
 +      precache_sound("weapons/fireball_fly2.wav");
 +      precache_sound("weapons/tag_rocket_fly.wav");
 +
 +}
Simple merge
index 0000000,2c8ebd5..77ff402
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,427 +1,427 @@@
 -      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);
+ #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.freezetag_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, (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);
++      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, 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, 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(!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(!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 0000000,866782d..6556487
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,260 +1,260 @@@
 -      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);
+ #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;
 -      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);
++      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, 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;
 -                      self.weapon = WEP_NEX;
++      W_SetupProjVelocity_Explicit(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(!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_VORTEX;
+                       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 0000000,0f46a96..8428115
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,183 +1,183 @@@
 -              RadiusDamage(self, self.realowner, 0, 0, 25, world, 25, self.projectiledeathtype, world);
+ #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);
 -      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);
++              RadiusDamage(self, self.realowner, 0, 0, 25, world, 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_SetupProjVelocity_Explicit(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(!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 0000000,2d72b4b..ad8c6b0
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,164 +1,164 @@@
 -              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);
+ #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, 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(!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 0000000,927501e..013eea7
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1097 +1,1097 @@@
 -                      self.weapon = WEP_NEX;
+ // =========================
+ //    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(!((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(!(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(!(targ.vehicle_flags & VHF_ISVEHICLE))
+       if(targ.flags & FL_NOTARGET)
+               return FALSE; // enemy can't be targeted
+       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.freezetag_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(!(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(!self.weapon)
++                      self.weapon = WEP_VORTEX;
+       }
+ }
+ 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(!autocvar_g_monsters_respawn)
+               return FALSE;
+       return TRUE;
+ }
+ void Monster_Fade ()
+ {
+       if(Monster_CanRespawn(self))
+       {
+               self.spawnflags |= MONSTERFLAG_RESPAWNED;
+               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
+       {
+               // number of monsters spawned with mobspawn command
+               totalspawned -= 1;
+               if(IS_CLIENT(self.realowner))
+                       self.realowner.monstercount -= 1;
+               SUB_SetFade(self, time + 3, 1);
+       }
+ }
+ float Monster_CanJump (vector vel)
+ {
+       if(self.state)
+               return FALSE; // already attacking
+       if(!(self.flags & FL_ONGROUND))
+               return FALSE; // not on the ground
+       if(self.health <= 0)
+               return FALSE; // called when dead?
+       if(time < self.attack_finished_single)
+               return FALSE; // still attacking
+       vector old = self.velocity;
+       self.velocity = vel;
+       tracetoss(self, self);
+       self.velocity = old;
+       if (trace_ent != self.enemy)
+               return FALSE;
+       return TRUE;
+ }
+ 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(!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(!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.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(!monster_isvalidtarget(self.enemy, self))
+                       self.enemy = world;
+               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(!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(!mon)
+               return; // nothing to remove
+       pointparticles(particleeffectnum("item_pickup"), mon.origin, '0 0 0', 1);
+       if(mon.weaponentity)
+               remove(mon.weaponentity);
+       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(!(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;
+       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);
+               // number of monsters spawned with mobspawn command
+               totalspawned -= 1;
+               if(IS_CLIENT(self.realowner))
+                       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;
+       monster_dropitem();
+       MonsterSound(monstersound_death, 0, FALSE, CH_VOICE);
+       if(!(self.spawnflags & MONSTERFLAG_SPAWNED) && !(self.spawnflags & MONSTERFLAG_RESPAWNED))
+               monsters_killed += 1;
+       if(IS_PLAYER(attacker))
+       if(autocvar_g_monsters_score_spawned || !((self.spawnflags & MONSTERFLAG_SPAWNED) || (self.spawnflags & MONSTERFLAG_RESPAWNED)))
+               PlayerScore_Add(attacker, SP_SCORE, +autocvar_g_monsters_score_kill);
+       if(gibbed)
+       {
+               // number of monsters spawned with mobspawn command
+               totalspawned -= 1;
+               if(IS_CLIENT(self.realowner))
+                       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(!(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(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(!(self.spawnflags & MONSTERFLAG_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(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
+       if(!self.skin)
+               self.skin = rint(random() * 4);
+       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(!autocvar_g_monsters)
+               return FALSE;
+       entity mon = get_monsterinfo(mon_id);
+       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(!(self.spawnflags & MONSTERFLAG_SPAWNED)) // naturally spawned monster
+       if(!(self.spawnflags & MONSTERFLAG_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(!self.ticrate)
+               self.ticrate = autocvar_g_monsters_think_delay;
+       self.ticrate = bound(sys_frametime, self.ticrate, 60);
+       if(!self.m_armor_blockpercent)
+               self.m_armor_blockpercent = 0.5;
+       if(!self.target_range)
+               self.target_range = autocvar_g_monsters_target_range;
+       if(!self.respawntime)
+               self.respawntime = autocvar_g_monsters_respawn_delay;
+       if(!self.monster_moveflags)
+               self.monster_moveflags = MONSTER_MOVE_WANDER;
+       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(!monster_spawn())
+               return FALSE;
+       if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
+               monster_setupcolors();
+       CSQCMODEL_AUTOINIT();
+       return TRUE;
+ }
Simple merge
Simple merge
index 59abc68,0000000..6cf877f
mode 100644,000000..100644
--- /dev/null
@@@ -1,749 -1,0 +1,749 @@@
-               is_player = (trace_ent.classname == "player" || trace_ent.classname == "body");
 +#ifdef REGISTER_WEAPON
 +REGISTER_WEAPON(
 +/* WEP_##id  */ SHOCKWAVE,
 +/* function  */ W_Shockwave,
 +/* ammotype  */ ammo_none,
 +/* impulse   */ 2,
 +/* flags     */ WEP_FLAG_NORMAL | WEP_TYPE_HITSCAN,
 +/* rating    */ BOT_PICKUP_RATING_LOW,
 +/* color     */ '0.5 0.25 0',
 +/* modelname */ "shotgun",
 +/* simplemdl */ "foobar",
 +/* crosshair */ "gfx/crosshairshotgun 0.7",
 +/* wepimg    */ "weaponshotgun",
 +/* refname   */ "shockwave",
 +/* wepname   */ _("Shockwave")
 +);
 +
 +#define SHOCKWAVE_SETTINGS(w_cvar,w_prop) SHOCKWAVE_SETTINGS_LIST(w_cvar, w_prop, SHOCKWAVE, shockwave)
 +#define SHOCKWAVE_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
 +      w_cvar(id, sn, NONE, blast_animtime) \
 +      w_cvar(id, sn, NONE, blast_damage) \
 +      w_cvar(id, sn, NONE, blast_distance) \
 +      w_cvar(id, sn, NONE, blast_edgedamage) \
 +      w_cvar(id, sn, NONE, blast_force) \
 +      w_cvar(id, sn, NONE, blast_force_forwardbias) \
 +      w_cvar(id, sn, NONE, blast_force_zscale) \
 +      w_cvar(id, sn, NONE, blast_jump_damage) \
 +      w_cvar(id, sn, NONE, blast_jump_edgedamage) \
 +      w_cvar(id, sn, NONE, blast_jump_force) \
 +      w_cvar(id, sn, NONE, blast_jump_force_velocitybias) \
 +      w_cvar(id, sn, NONE, blast_jump_force_zscale) \
 +      w_cvar(id, sn, NONE, blast_jump_multiplier_accuracy) \
 +      w_cvar(id, sn, NONE, blast_jump_multiplier_distance) \
 +      w_cvar(id, sn, NONE, blast_jump_multiplier_min) \
 +      w_cvar(id, sn, NONE, blast_jump_radius) \
 +      w_cvar(id, sn, NONE, blast_multiplier_accuracy) \
 +      w_cvar(id, sn, NONE, blast_multiplier_distance) \
 +      w_cvar(id, sn, NONE, blast_multiplier_min) \
 +      w_cvar(id, sn, NONE, blast_refire) \
 +      w_cvar(id, sn, NONE, blast_splash_damage) \
 +      w_cvar(id, sn, NONE, blast_splash_edgedamage) \
 +      w_cvar(id, sn, NONE, blast_splash_force) \
 +      w_cvar(id, sn, NONE, blast_splash_force_forwardbias) \
 +      w_cvar(id, sn, NONE, blast_splash_multiplier_accuracy) \
 +      w_cvar(id, sn, NONE, blast_splash_multiplier_distance) \
 +      w_cvar(id, sn, NONE, blast_splash_multiplier_min) \
 +      w_cvar(id, sn, NONE, blast_splash_radius) \
 +      w_cvar(id, sn, NONE, blast_spread_max) \
 +      w_cvar(id, sn, NONE, blast_spread_min) \
 +      w_cvar(id, sn, NONE, melee_animtime) \
 +      w_cvar(id, sn, NONE, melee_damage) \
 +      w_cvar(id, sn, NONE, melee_delay) \
 +      w_cvar(id, sn, NONE, melee_force) \
 +      w_cvar(id, sn, NONE, melee_multihit) \
 +      w_cvar(id, sn, NONE, melee_no_doubleslap) \
 +      w_cvar(id, sn, NONE, melee_nonplayerdamage) \
 +      w_cvar(id, sn, NONE, melee_range) \
 +      w_cvar(id, sn, NONE, melee_refire) \
 +      w_cvar(id, sn, NONE, melee_swing_side) \
 +      w_cvar(id, sn, NONE, melee_swing_up) \
 +      w_cvar(id, sn, NONE, melee_time) \
 +      w_cvar(id, sn, NONE, melee_traces) \
 +      w_prop(id, sn, float,  switchdelay_raise, switchdelay_raise) \
 +      w_prop(id, sn, float,  switchdelay_drop, switchdelay_drop) \
 +      w_prop(id, sn, string, weaponreplace, weaponreplace) \
 +      w_prop(id, sn, float,  weaponstart, weaponstart) \
 +      w_prop(id, sn, float,  weaponstartoverride, weaponstartoverride) \
 +      w_prop(id, sn, float,  weaponthrowable, weaponthrowable)
 +
 +#ifdef SVQC
 +SHOCKWAVE_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
 +#endif
 +#else
 +#ifdef SVQC
 +void spawnfunc_weapon_shockwave()
 +{
 +      //if(autocvar_sv_q3acompat_machineshockwaveswap) // WEAPONTODO
 +      if(autocvar_sv_q3acompat_machineshotgunswap)
 +      if(self.classname != "droppedweapon")
 +      {
 +              weapon_defaultspawnfunc(WEP_MACHINEGUN);
 +              return;
 +      }
 +      weapon_defaultspawnfunc(WEP_SHOCKWAVE);
 +}
 +
 +#define MAX_SHOCKWAVE_HITS 10
 +#define DEBUG_SHOCKWAVE
 +
 +.float swing_prev;
 +.entity swing_alreadyhit;
 +.float shockwave_blasttime;
 +entity shockwave_hit[MAX_SHOCKWAVE_HITS];
 +float shockwave_hit_damage[MAX_SHOCKWAVE_HITS];
 +vector shockwave_hit_force[MAX_SHOCKWAVE_HITS];
 +
 +// MELEE ATTACK MODE
 +void W_Shockwave_Melee_Think()
 +{
 +      // declarations
 +      float i, f, swing, swing_factor, swing_damage, meleetime, is_player;
 +      entity target_victim;
 +      vector targpos;
 +
 +      // check to see if we can still continue, otherwise give up now
 +      if((self.realowner.deadflag != DEAD_NO) && WEP_CVAR(shockwave, melee_no_doubleslap))
 +      {
 +              remove(self);
 +              return;
 +      }
 +
 +      // set start time of melee
 +      if(!self.cnt)
 +      {
 +              self.cnt = time; 
 +              W_PlayStrengthSound(self.realowner);
 +      }
 +
 +      // update values for v_* vectors
 +      makevectors(self.realowner.v_angle);
 +      
 +      // calculate swing percentage based on time
 +      meleetime = WEP_CVAR(shockwave, melee_time) * W_WeaponRateFactor();
 +      swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10);
 +      f = ((1 - swing) * WEP_CVAR(shockwave, melee_traces));
 +      
 +      // perform the traces needed for this frame 
 +      for(i=self.swing_prev; i < f; ++i)
 +      {
 +              swing_factor = ((1 - (i / WEP_CVAR(shockwave, melee_traces))) * 2 - 1);
 +              
 +              targpos = (self.realowner.origin + self.realowner.view_ofs 
 +                      + (v_forward * WEP_CVAR(shockwave, melee_range))
 +                      + (v_up * swing_factor * WEP_CVAR(shockwave, melee_swing_up))
 +                      + (v_right * swing_factor * WEP_CVAR(shockwave, melee_swing_side)));
 +
 +              WarpZone_traceline_antilag(
 +                      self.realowner,
 +                      (self.realowner.origin + self.realowner.view_ofs),
 +                      targpos,
 +                      FALSE,
 +                      self.realowner,
 +                      ANTILAG_LATENCY(self.realowner)
 +              );
 +              
 +              // draw lightning beams for debugging
 +              te_lightning2(world, targpos, self.realowner.origin + self.realowner.view_ofs + v_forward * 5 - v_up * 5); 
 +              te_customflash(targpos, 40,  2, '1 1 1');
 +              
++              is_player = (trace_ent.classname == "player" || trace_ent.classname == "body" || (trace_ent.flags & FL_MONSTER));
 +
 +              if((trace_fraction < 1) // if trace is good, apply the damage and remove self if necessary
 +                      && (trace_ent.takedamage == DAMAGE_AIM)  
 +                      && (trace_ent != self.swing_alreadyhit)
 +                      && (is_player || WEP_CVAR(shockwave, melee_nonplayerdamage)))
 +              {
 +                      target_victim = trace_ent; // so it persists through other calls
 +                      
 +                      if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught
 +                              swing_damage = (WEP_CVAR(shockwave, melee_damage) * min(1, swing_factor + 1));
 +                      else
 +                              swing_damage = (WEP_CVAR(shockwave, melee_nonplayerdamage) * min(1, swing_factor + 1));
 +
 +                      // trigger damage with this calculated info
 +                      Damage(
 +                              target_victim,
 +                              self.realowner,
 +                              self.realowner, 
 +                              swing_damage,
 +                              (WEP_SHOCKWAVE | HITTYPE_SECONDARY), 
 +                              (self.realowner.origin + self.realowner.view_ofs), 
 +                              (v_forward * WEP_CVAR(shockwave, melee_force))
 +                      );
 +
 +                      // handle accuracy
 +                      if(accuracy_isgooddamage(self.realowner, target_victim))
 +                              { accuracy_add(self.realowner, WEP_SHOCKWAVE, 0, swing_damage); }
 +
 +                      #ifdef DEBUG_SHOCKWAVE
 +                      print(sprintf(
 +                              "MELEE: %s hitting %s with %f damage (factor: %f) at %f time.\n",
 +                              self.realowner.netname,
 +                              target_victim.netname,
 +                              swing_damage,
 +                              swing_factor,
 +                              time
 +                      ));
 +                      #endif
 +
 +                      // allow multiple hits with one swing, but not against the same player twice
 +                      if(WEP_CVAR(shockwave, melee_multihit))
 +                      {
 +                              self.swing_alreadyhit = target_victim;
 +                              continue; // move along to next trace
 +                      }
 +                      else
 +                      {
 +                              remove(self);
 +                              return;
 +                      }
 +              }
 +      }
 +      
 +      if(time >= self.cnt + meleetime)
 +      {
 +              // melee is finished
 +              remove(self);
 +              return;
 +      }
 +      else
 +      {
 +              // set up next frame 
 +              self.swing_prev = i;
 +              self.nextthink = time;
 +      }
 +}
 +
 +void W_Shockwave_Melee()
 +{
 +      sound(self, CH_WEAPON_A, "weapons/shotgun_melee.wav", VOL_BASE, ATTN_NORM);
 +      weapon_thinkf(WFRAME_FIRE2, WEP_CVAR(shockwave, melee_animtime), w_ready);
 +
 +      entity meleetemp;
 +      meleetemp = spawn();
 +      meleetemp.owner = meleetemp.realowner = self;
 +      meleetemp.think = W_Shockwave_Melee_Think;
 +      meleetemp.nextthink = time + WEP_CVAR(shockwave, melee_delay) * W_WeaponRateFactor();
 +      W_SetupShot_Range(self, TRUE, 0, "", 0, WEP_CVAR(shockwave, melee_damage), WEP_CVAR(shockwave, melee_range));
 +}
 +
 +// SHOCKWAVE ATTACK MODE
 +float W_Shockwave_Attack_CheckSpread(
 +      vector targetorg,
 +      vector nearest_on_line,
 +      vector sw_shotorg,
 +      vector attack_endpos)
 +{
 +      float spreadlimit;
 +      float distance_of_attack = vlen(sw_shotorg - attack_endpos);
 +      float distance_from_line = vlen(targetorg - nearest_on_line);
 +      
 +      spreadlimit = (distance_of_attack ? min(1, (vlen(sw_shotorg - nearest_on_line) / distance_of_attack)) : 1);
 +      spreadlimit =
 +              (
 +                      (WEP_CVAR(shockwave, blast_spread_min) * (1 - spreadlimit))
 +                      +
 +                      (WEP_CVAR(shockwave, blast_spread_max) * spreadlimit)
 +              );
 +
 +      if(
 +              (spreadlimit && (distance_from_line <= spreadlimit))
 +              &&
 +              ((vlen(normalize(targetorg - sw_shotorg) - normalize(attack_endpos - sw_shotorg)) * RAD2DEG) <= 90)
 +      )
 +              { return bound(0, (distance_from_line / spreadlimit), 1); }
 +      else
 +              { return FALSE; }
 +}
 +
 +float W_Shockwave_Attack_IsVisible(
 +      entity head,
 +      vector nearest_on_line,
 +      vector sw_shotorg,
 +      vector attack_endpos)
 +{
 +      vector nearest_to_attacker = head.WarpZone_findradius_nearest;
 +      vector center = (head.origin + (head.mins + head.maxs) * 0.5);
 +      vector corner;
 +      float i;
 +
 +      // STEP ONE: Check if the nearest point is clear
 +      if(W_Shockwave_Attack_CheckSpread(nearest_to_attacker, nearest_on_line, sw_shotorg, attack_endpos))
 +      {
 +              WarpZone_TraceLine(sw_shotorg, nearest_to_attacker, MOVE_NOMONSTERS, self);
 +              if(trace_fraction == 1) { return TRUE; } // yes, the nearest point is clear and we can allow the damage
 +      }
 +
 +      // STEP TWO: Check if shotorg to center point is clear
 +      if(W_Shockwave_Attack_CheckSpread(center, nearest_on_line, sw_shotorg, attack_endpos))
 +      {
 +              WarpZone_TraceLine(sw_shotorg, center, MOVE_NOMONSTERS, self);
 +              if(trace_fraction == 1) { return TRUE; } // yes, the center point is clear and we can allow the damage
 +      }
 +
 +      // STEP THREE: Check each corner to see if they are clear
 +      for(i=1; i<=8; ++i)
 +      {
 +              corner = get_corner_position(head, i);
 +              if(W_Shockwave_Attack_CheckSpread(corner, nearest_on_line, sw_shotorg, attack_endpos))
 +              {
 +                      WarpZone_TraceLine(sw_shotorg, corner, MOVE_NOMONSTERS, self);
 +                      if(trace_fraction == 1) { return TRUE; } // yes, this corner is clear and we can allow the damage
 +              }
 +      }
 +
 +      return FALSE;
 +}
 +
 +float W_Shockwave_Attack_CheckHit(
 +      float queue,
 +      entity head,
 +      vector final_force,
 +      float final_damage)
 +{
 +      if(!head) { return FALSE; }
 +      float i;
 +      
 +      for(i = 0; i <= queue; ++i)
 +      {
 +              if(shockwave_hit[i] == head)
 +              {
 +                      if(vlen(final_force) > vlen(shockwave_hit_force[i])) { shockwave_hit_force[i] = final_force; }
 +                      if(final_damage > shockwave_hit_damage[i]) { shockwave_hit_damage[i] = final_damage; }
 +                      return FALSE;
 +              }
 +      }
 +
 +      shockwave_hit[queue] = head;
 +      shockwave_hit_force[queue] = final_force;
 +      shockwave_hit_damage[queue] = final_damage;
 +      return TRUE;
 +}
 +
 +void W_Shockwave_Attack()
 +{
 +      // declarations
 +      float multiplier, multiplier_from_accuracy, multiplier_from_distance;
 +      float final_damage;
 +      vector final_force, center, vel;
 +      entity head;
 +
 +      float i, queue = 0;
 +      
 +      // set up the shot direction
 +      W_SetupShot(self, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, WEP_CVAR(shockwave, blast_damage));
 +      vector attack_endpos = (w_shotorg + (w_shotdir * WEP_CVAR(shockwave, blast_distance)));
 +      WarpZone_TraceLine(w_shotorg, attack_endpos, MOVE_NOMONSTERS, self);
 +      vector attack_hitpos = trace_endpos;
 +      float distance_to_end = vlen(w_shotorg - attack_endpos);
 +      float distance_to_hit = vlen(w_shotorg - attack_hitpos);
 +      //entity transform = WarpZone_trace_transform;
 +
 +      // do the firing effect now
 +      //SendCSQCShockwaveParticle(attack_endpos); // WEAPONTODO
 +      Damage_DamageInfo(
 +              attack_hitpos,
 +              WEP_CVAR(shockwave, blast_splash_damage),
 +              WEP_CVAR(shockwave, blast_splash_edgedamage),
 +              WEP_CVAR(shockwave, blast_splash_radius),
 +              w_shotdir * WEP_CVAR(shockwave, blast_splash_force),
 +              WEP_SHOCKWAVE,
 +              0,
 +              self
 +      );
 +
 +      // splash damage/jumping trace
 +      head = WarpZone_FindRadius(
 +              attack_hitpos,
 +              max(
 +                      WEP_CVAR(shockwave, blast_splash_radius),
 +                      WEP_CVAR(shockwave, blast_jump_radius)
 +              ),
 +              FALSE
 +      );
 +      
 +      while(head)
 +      {
 +              if(head.takedamage)
 +              {
 +                      float distance_to_head = vlen(attack_hitpos - head.WarpZone_findradius_nearest);
 +                      
 +                      if((head == self) && (distance_to_head <= WEP_CVAR(shockwave, blast_jump_radius)))
 +                      {
 +                              // ========================
 +                              //  BLAST JUMP CALCULATION
 +                              // ========================
 +                              
 +                              // calculate importance of distance and accuracy for this attack
 +                              multiplier_from_accuracy = (1 -
 +                                      (distance_to_head ?
 +                                              min(1, (distance_to_head / WEP_CVAR(shockwave, blast_jump_radius)))
 +                                              :
 +                                              0
 +                                      )
 +                              );
 +                              multiplier_from_distance = (1 -
 +                                      (distance_to_hit ?
 +                                              min(1, (distance_to_hit / distance_to_end))
 +                                              :
 +                                              0
 +                                      )
 +                              );
 +                              multiplier =
 +                                      max(
 +                                              WEP_CVAR(shockwave, blast_jump_multiplier_min),
 +                                              (
 +                                                      (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_jump_multiplier_accuracy))
 +                                                      +
 +                                                      (multiplier_from_distance * WEP_CVAR(shockwave, blast_jump_multiplier_distance))
 +                                              )
 +                                      );
 +
 +                              // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage
 +                              final_damage =
 +                                      (
 +                                              (WEP_CVAR(shockwave, blast_jump_damage) * multiplier)
 +                                              +
 +                                              (WEP_CVAR(shockwave, blast_jump_edgedamage) * (1 - multiplier))
 +                                      );
 +
 +                              // figure out the direction of force
 +                              vel = normalize(combine_to_vector(head.velocity_x, head.velocity_y, 0));
 +                              vel *=
 +                                      (
 +                                              bound(0, (vlen(vel) / autocvar_sv_maxspeed), 1)
 +                                              *
 +                                              WEP_CVAR(shockwave, blast_jump_force_velocitybias)
 +                                      );
 +                              final_force = normalize((CENTER_OR_VIEWOFS(head) - attack_hitpos) + vel);
 +
 +                              // now multiply the direction by force units
 +                              final_force *= (WEP_CVAR(shockwave, blast_jump_force) * multiplier);
 +                              final_force_z *= WEP_CVAR(shockwave, blast_jump_force_zscale);
 +
 +                              // trigger damage with this calculated info
 +                              Damage(
 +                                      head,
 +                                      self,
 +                                      self,
 +                                      final_damage,
 +                                      WEP_SHOCKWAVE,
 +                                      head.origin,
 +                                      final_force
 +                              );
 +
 +                              #ifdef DEBUG_SHOCKWAVE
 +                              print(sprintf(
 +                                      "SELF HIT: multiplier = %f, damage = %f, force = %f... "
 +                                      "multiplier_from_accuracy = %f, multiplier_from_distance = %f.\n",
 +                                      multiplier,
 +                                      final_damage,
 +                                      vlen(final_force),
 +                                      multiplier_from_accuracy,
 +                                      multiplier_from_distance
 +                              ));
 +                              #endif
 +                      }
 +                      else if(distance_to_head <= WEP_CVAR(shockwave, blast_splash_radius))
 +                      {
 +                              // ==========================
 +                              //  BLAST SPLASH CALCULATION
 +                              // ==========================
 +                              
 +                              // calculate importance of distance and accuracy for this attack
 +                              multiplier_from_accuracy = (1 -
 +                                      (distance_to_head ?
 +                                              min(1, (distance_to_head / WEP_CVAR(shockwave, blast_splash_radius)))
 +                                              :
 +                                              0
 +                                      )
 +                              );
 +                              multiplier_from_distance = (1 -
 +                                      (distance_to_hit ?
 +                                              min(1, (distance_to_hit / distance_to_end))
 +                                              :
 +                                              0
 +                                      )
 +                              );
 +                              multiplier =
 +                                      max(
 +                                              WEP_CVAR(shockwave, blast_splash_multiplier_min),
 +                                              (
 +                                                      (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_splash_multiplier_accuracy))
 +                                                      +
 +                                                      (multiplier_from_distance * WEP_CVAR(shockwave, blast_splash_multiplier_distance))
 +                                              )
 +                                      );
 +
 +                              // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage
 +                              final_damage =
 +                                      (
 +                                              (WEP_CVAR(shockwave, blast_splash_damage) * multiplier)
 +                                              +
 +                                              (WEP_CVAR(shockwave, blast_splash_edgedamage) * (1 - multiplier))
 +                                      );
 +
 +                              // figure out the direction of force
 +                              final_force = (w_shotdir * WEP_CVAR(shockwave, blast_splash_force_forwardbias));
 +                              final_force = normalize(CENTER_OR_VIEWOFS(head) - (attack_hitpos - final_force));
 +                              //te_lightning2(world, attack_hitpos, (attack_hitpos + (final_force * 200)));
 +
 +                              // now multiply the direction by force units
 +                              final_force *= (WEP_CVAR(shockwave, blast_splash_force) * multiplier);
 +                              final_force_z *= WEP_CVAR(shockwave, blast_force_zscale);
 +
 +                              // queue damage with this calculated info
 +                              if(W_Shockwave_Attack_CheckHit(queue, head, final_force, final_damage)) { queue = min(queue + 1, MAX_SHOCKWAVE_HITS); }
 +
 +                              #ifdef DEBUG_SHOCKWAVE
 +                              print(sprintf(
 +                                      "SPLASH HIT: multiplier = %f, damage = %f, force = %f... "
 +                                      "multiplier_from_accuracy = %f, multiplier_from_distance = %f.\n",
 +                                      multiplier,
 +                                      final_damage,
 +                                      vlen(final_force),
 +                                      multiplier_from_accuracy,
 +                                      multiplier_from_distance
 +                              ));
 +                              #endif
 +                      }
 +              }
 +              head = head.chain;
 +      }
 +
 +      // cone damage trace
 +      head = WarpZone_FindRadius(w_shotorg, WEP_CVAR(shockwave, blast_distance), FALSE);
 +      while(head)
 +      {
 +              if((head != self) && head.takedamage)
 +              {
 +                      // ========================
 +                      //  BLAST CONE CALCULATION
 +                      // ========================
 +
 +                      // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) 
 +                      center = CENTER_OR_VIEWOFS(head);
 +
 +                      // find the closest point on the enemy to the center of the attack
 +                      float ang; // angle between shotdir and h
 +                      float h; // hypotenuse, which is the distance between attacker to head
 +                      float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
 +                      
 +                      h = vlen(center - self.origin);
 +                      ang = acos(dotproduct(normalize(center - self.origin), w_shotdir));
 +                      a = h * cos(ang);
 +                      // WEAPONTODO: replace with simpler method
 +
 +                      vector nearest_on_line = (w_shotorg + a * w_shotdir);
 +                      vector nearest_to_attacker = WarpZoneLib_NearestPointOnBox(center + head.mins, center + head.maxs, nearest_on_line);
 +
 +                      if((vlen(head.WarpZone_findradius_dist) <= WEP_CVAR(shockwave, blast_distance)) 
 +                              && (W_Shockwave_Attack_IsVisible(head, nearest_on_line, w_shotorg, attack_endpos)))
 +                      {
 +                              // calculate importance of distance and accuracy for this attack
 +                              multiplier_from_accuracy = (1 -
 +                                      W_Shockwave_Attack_CheckSpread(
 +                                              nearest_to_attacker,
 +                                              nearest_on_line,
 +                                              w_shotorg,
 +                                              attack_endpos
 +                                      )
 +                              );
 +                              multiplier_from_distance = (1 -
 +                                      (distance_to_hit ?
 +                                              min(1, (vlen(head.WarpZone_findradius_dist) / distance_to_end))
 +                                              :
 +                                              0
 +                                      )
 +                              );
 +                              multiplier =
 +                                      max(
 +                                              WEP_CVAR(shockwave, blast_multiplier_min),
 +                                              (
 +                                                      (multiplier_from_accuracy * WEP_CVAR(shockwave, blast_multiplier_accuracy))
 +                                                      +
 +                                                      (multiplier_from_distance * WEP_CVAR(shockwave, blast_multiplier_distance))
 +                                              )
 +                                      );
 +
 +                              // calculate damage from multiplier: 1 = "highest" damage, 0 = "lowest" edgedamage
 +                              final_damage =
 +                                      (
 +                                              (WEP_CVAR(shockwave, blast_damage) * multiplier)
 +                                              +
 +                                              (WEP_CVAR(shockwave, blast_edgedamage) * (1 - multiplier))
 +                                      );
 +
 +                              // figure out the direction of force
 +                              final_force = (w_shotdir * WEP_CVAR(shockwave, blast_force_forwardbias));
 +                              final_force = normalize(center - (nearest_on_line - final_force));
 +                              //te_lightning2(world, nearest_on_line, (attack_hitpos + (final_force * 200)));
 +
 +                              // now multiply the direction by force units
 +                              final_force *= (WEP_CVAR(shockwave, blast_force) * multiplier);
 +                              final_force_z *= WEP_CVAR(shockwave, blast_force_zscale);
 +
 +                              // queue damage with this calculated info
 +                              if(W_Shockwave_Attack_CheckHit(queue, head, final_force, final_damage)) { queue = min(queue + 1, MAX_SHOCKWAVE_HITS); }
 +
 +                              #ifdef DEBUG_SHOCKWAVE
 +                              print(sprintf(
 +                                      "BLAST HIT: multiplier = %f, damage = %f, force = %f... "
 +                                      "multiplier_from_accuracy = %f, multiplier_from_distance = %f.\n",
 +                                      multiplier,
 +                                      final_damage,
 +                                      vlen(final_force),
 +                                      multiplier_from_accuracy,
 +                                      multiplier_from_distance
 +                              ));
 +                              #endif
 +                      }
 +              }
 +              head = head.chain;
 +      }
 +
 +      for(i = 1; i <= queue; ++i)
 +      {
 +              head = shockwave_hit[i-1];
 +              final_force = shockwave_hit_force[i-1];
 +              final_damage = shockwave_hit_damage[i-1];
 +              
 +              Damage(
 +                      head,
 +                      self,
 +                      self,
 +                      final_damage,
 +                      WEP_SHOCKWAVE,
 +                      head.origin,
 +                      final_force
 +              );
 +
 +              if(accuracy_isgooddamage(self.realowner, head))
 +              {
 +                      print("wtf\n");
 +                      accuracy_add(self.realowner, WEP_SHOCKWAVE, 0, final_damage);
 +              }
 +
 +              #ifdef DEBUG_SHOCKWAVE
 +              print(sprintf(
 +                      "SHOCKWAVE by %s: damage = %f, force = %f.\n",
 +                      self.netname,
 +                      final_damage,
 +                      vlen(final_force)
 +              ));
 +              #endif
 +              
 +              shockwave_hit[i-1] = world;
 +              shockwave_hit_force[i-1] = '0 0 0';
 +              shockwave_hit_damage[i-1] = 0;
 +      }
 +}
 +
 +float W_Shockwave(float req)
 +{
 +      switch(req)
 +      {
 +              case WR_AIM:
 +              {
 +                      if(vlen(self.origin - self.enemy.origin) <= WEP_CVAR(shockwave, melee_range))
 +                              { self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE); }
 +                      else
 +                              { self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE); }
 +                      
 +                      return TRUE;
 +              }
 +              case WR_THINK:
 +              {
 +                      if(self.BUTTON_ATCK)
 +                      {
 +                              if(time >= self.shockwave_blasttime) // handle refire separately so the secondary can be fired straight after a primary
 +                              {
 +                                      if(weapon_prepareattack(0, WEP_CVAR(shockwave, blast_animtime)))
 +                                      {
 +                                              W_Shockwave_Attack();
 +                                              self.shockwave_blasttime = time + WEP_CVAR(shockwave, blast_refire) * W_WeaponRateFactor();
 +                                              weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(shockwave, blast_animtime), w_ready);
 +                                      }
 +                              }
 +                      }
 +                      else if(self.BUTTON_ATCK2)
 +                      {
 +                              //if(self.clip_load >= 0) // we are not currently reloading
 +                              if(!self.crouch) // no crouchmelee please
 +                              if(weapon_prepareattack(1, WEP_CVAR(shockwave, melee_refire)))
 +                              {
 +                                      // attempt forcing playback of the anim by switching to another anim (that we never play) here...
 +                                      weapon_thinkf(WFRAME_FIRE1, 0, W_Shockwave_Melee);
 +                              }
 +                      }
 +                      
 +                      return TRUE;
 +              }
 +              case WR_INIT:
 +              {
 +                      precache_model("models/uziflash.md3");
 +                      precache_model("models/weapons/g_shockwave.md3");
 +                      precache_model("models/weapons/v_shockwave.md3");
 +                      precache_model("models/weapons/h_shockwave.iqm");
 +                      precache_sound("misc/itempickup.wav");
 +                      precache_sound("weapons/shockwave_fire.wav");
 +                      precache_sound("weapons/shockwave_melee.wav");
 +                      SHOCKWAVE_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP)
 +                      return TRUE;
 +              }
 +              case WR_CHECKAMMO1:
 +              case WR_CHECKAMMO2:
 +              {
 +                      // shockwave has infinite ammo
 +                      return TRUE;
 +              }
 +              case WR_CONFIG:
 +              {
 +                      SHOCKWAVE_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS)
 +                      return TRUE;
 +              }
 +              case WR_SUICIDEMESSAGE:
 +              {
 +                      return WEAPON_THINKING_WITH_PORTALS;
 +              }
 +              case WR_KILLMESSAGE:
 +              {
 +                      if(w_deathtype & HITTYPE_SECONDARY)
 +                              return WEAPON_SHOCKWAVE_MURDER_SLAP;
 +                      else
 +                              return WEAPON_SHOCKWAVE_MURDER;
 +              }
 +      }
 +      return FALSE;
 +}
 +#endif
 +#ifdef CSQC
 +float W_Shockwave(float req)
 +{
 +      switch(req)
 +      {
 +              case WR_IMPACTEFFECT:
 +              {
 +                      vector org2;
 +                      org2 = w_org + w_backoff * 2;
 +                      pointparticles(particleeffectnum("laser_impact"), org2, w_backoff * 1000, 1); // WEAPONTODO: replace with proper impact effect
 +                      return TRUE;
 +              }
 +              case WR_INIT:
 +              {
 +                      //precache_sound("weapons/ric1.wav");
 +                      //precache_sound("weapons/ric2.wav");
 +                      //precache_sound("weapons/ric3.wav");
 +                      return FALSE;
 +              }
 +              case WR_ZOOMRETICLE:
 +              {
 +                      // no weapon specific image for this weapon
 +                      return FALSE;
 +              }
 +      }
 +      return FALSE;
 +}
 +#endif
 +#endif
index 87aa92a,0000000..8353442
mode 100644,000000..100644
--- /dev/null
@@@ -1,329 -1,0 +1,329 @@@
-               is_player = (IS_PLAYER(trace_ent) || trace_ent.classname == "body");
 +#ifdef REGISTER_WEAPON
 +REGISTER_WEAPON(
 +/* WEP_##id  */ SHOTGUN,
 +/* function  */ W_Shotgun,
 +/* ammotype  */ ammo_none,
 +/* impulse   */ 2,
 +/* flags     */ WEP_FLAG_RELOADABLE | WEP_TYPE_HITSCAN | WEP_FLAG_MUTATORBLOCKED,
 +/* rating    */ BOT_PICKUP_RATING_LOW,
 +/* color     */ '0.5 0.25 0',
 +/* modelname */ "shotgun",
 +/* simplemdl */ "foobar",
 +/* crosshair */ "gfx/crosshairshotgun 0.65",
 +/* wepimg    */ "weaponshotgun",
 +/* refname   */ "shotgun",
 +/* wepname   */ _("Shotgun")
 +);
 +
 +#define SHOTGUN_SETTINGS(w_cvar,w_prop) SHOTGUN_SETTINGS_LIST(w_cvar, w_prop, SHOTGUN, shotgun)
 +#define SHOTGUN_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
 +      w_cvar(id, sn, PRI,  ammo) \
 +      w_cvar(id, sn, BOTH, animtime) \
 +      w_cvar(id, sn, BOTH, refire) \
 +      w_cvar(id, sn, PRI,  bullets) \
 +      w_cvar(id, sn, BOTH, damage) \
 +      w_cvar(id, sn, BOTH, force) \
 +      w_cvar(id, sn, PRI,  solidpenetration) \
 +      w_cvar(id, sn, PRI,  spread) \
 +      w_cvar(id, sn, NONE, secondary) \
 +      w_cvar(id, sn, SEC,  melee_time) \
 +      w_cvar(id, sn, SEC,  melee_no_doubleslap) \
 +      w_cvar(id, sn, SEC,  melee_traces) \
 +      w_cvar(id, sn, SEC,  melee_swing_up) \
 +      w_cvar(id, sn, SEC,  melee_swing_side) \
 +      w_cvar(id, sn, SEC,  melee_nonplayerdamage) \
 +      w_cvar(id, sn, SEC,  melee_multihit) \
 +      w_cvar(id, sn, SEC,  melee_delay) \
 +      w_cvar(id, sn, SEC,  melee_range) \
 +      w_prop(id, sn, float,  reloading_ammo, reload_ammo) \
 +      w_prop(id, sn, float,  reloading_time, reload_time) \
 +      w_prop(id, sn, float,  switchdelay_raise, switchdelay_raise) \
 +      w_prop(id, sn, float,  switchdelay_drop, switchdelay_drop) \
 +      w_prop(id, sn, string, weaponreplace, weaponreplace) \
 +      w_prop(id, sn, float,  weaponstart, weaponstart) \
 +      w_prop(id, sn, float,  weaponstartoverride, weaponstartoverride) \
 +      w_prop(id, sn, float,  weaponthrowable, weaponthrowable)
 +
 +#ifdef SVQC
 +SHOTGUN_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
 +#endif
 +#else
 +#ifdef SVQC
 +void spawnfunc_weapon_shotgun(void) { weapon_defaultspawnfunc(WEP_SHOTGUN); }
 +
 +void W_Shotgun_Attack (void)
 +{
 +      float   sc;
 +      entity flash;
 +
 +      W_DecreaseAmmo(WEP_CVAR_PRI(shotgun, ammo));
 +
 +      W_SetupShot (self, TRUE, 5, "weapons/shotgun_fire.wav", CH_WEAPON_A, WEP_CVAR_PRI(shotgun, damage) * WEP_CVAR_PRI(shotgun, bullets));
 +      for (sc = 0;sc < WEP_CVAR_PRI(shotgun, bullets);sc = sc + 1)
 +              fireBullet(w_shotorg, w_shotdir, WEP_CVAR_PRI(shotgun, spread), WEP_CVAR_PRI(shotgun, solidpenetration), WEP_CVAR_PRI(shotgun, damage), WEP_CVAR_PRI(shotgun, force), WEP_SHOTGUN, 0);
 +
 +      pointparticles(particleeffectnum("shotgun_muzzleflash"), w_shotorg, w_shotdir * 1000, WEP_CVAR_PRI(shotgun, ammo));
 +
 +      // casing code
 +      if (autocvar_g_casings >= 1)
 +              for (sc = 0;sc < WEP_CVAR_PRI(shotgun, ammo);sc = sc + 1)
 +                      SpawnCasing (((random () * 50 + 50) * v_right) - (v_forward * (random () * 25 + 25)) - ((random () * 5 - 30) * v_up), 2, vectoangles(v_forward),'0 250 0', 100, 1, self);
 +
 +      // muzzle flash for 1st person view
 +      flash = spawn();
 +      setmodel(flash, "models/uziflash.md3"); // precision set below
 +      flash.think = SUB_Remove;
 +      flash.nextthink = time + 0.06;
 +      flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
 +      W_AttachToShotorg(flash, '5 0 0');
 +}
 +
 +.float swing_prev;
 +.entity swing_alreadyhit;
 +void W_Shotgun_Melee_Think()
 +{
 +      // declarations
 +      float i, f, swing, swing_factor, swing_damage, meleetime, is_player;
 +      entity target_victim;
 +      vector targpos;
 +
 +      if(!self.cnt) // set start time of melee
 +      {
 +              self.cnt = time;
 +              W_PlayStrengthSound(self.realowner);
 +      }
 +
 +      makevectors(self.realowner.v_angle); // update values for v_* vectors
 +
 +      // calculate swing percentage based on time
 +      meleetime = WEP_CVAR_SEC(shotgun, melee_time) * W_WeaponRateFactor();
 +      swing = bound(0, (self.cnt + meleetime - time) / meleetime, 10);
 +      f = ((1 - swing) * WEP_CVAR_SEC(shotgun, melee_traces));
 +
 +      // check to see if we can still continue, otherwise give up now
 +      if((self.realowner.deadflag != DEAD_NO) && WEP_CVAR_SEC(shotgun, melee_no_doubleslap))
 +      {
 +              remove(self);
 +              return;
 +      }
 +
 +      // if okay, perform the traces needed for this frame
 +      for(i=self.swing_prev; i < f; ++i)
 +      {
 +              swing_factor = ((1 - (i / WEP_CVAR_SEC(shotgun, melee_traces))) * 2 - 1);
 +
 +              targpos = (self.realowner.origin + self.realowner.view_ofs
 +                      + (v_forward * WEP_CVAR_SEC(shotgun, melee_range))
 +                      + (v_up * swing_factor * WEP_CVAR_SEC(shotgun, melee_swing_up))
 +                      + (v_right * swing_factor * WEP_CVAR_SEC(shotgun, melee_swing_side)));
 +
 +              WarpZone_traceline_antilag(self, self.realowner.origin + self.realowner.view_ofs, targpos, FALSE, self, ANTILAG_LATENCY(self.realowner));
 +
 +              // draw lightning beams for debugging
 +              //te_lightning2(world, targpos, self.realowner.origin + self.realowner.view_ofs + v_forward * 5 - v_up * 5);
 +              //te_customflash(targpos, 40,  2, '1 1 1');
 +
++              is_player = (IS_PLAYER(trace_ent) || trace_ent.classname == "body" || (trace_ent.flags & FL_MONSTER));
 +
 +              if((trace_fraction < 1) // if trace is good, apply the damage and remove self
 +                      && (trace_ent.takedamage == DAMAGE_AIM)
 +                      && (trace_ent != self.swing_alreadyhit)
 +                      && (is_player || WEP_CVAR_SEC(shotgun, melee_nonplayerdamage)))
 +              {
 +                      target_victim = trace_ent; // so it persists through other calls
 +
 +                      if(is_player) // this allows us to be able to nerf the non-player damage done in e.g. assault or onslaught.
 +                              swing_damage = (WEP_CVAR_SEC(shotgun, damage) * min(1, swing_factor + 1));
 +                      else
 +                              swing_damage = (WEP_CVAR_SEC(shotgun, melee_nonplayerdamage) * min(1, swing_factor + 1));
 +
 +                      //print(strcat(self.realowner.netname, " hitting ", target_victim.netname, " with ", strcat(ftos(swing_damage), " damage (factor: ", ftos(swing_factor), ") at "), ftos(time), " seconds.\n"));
 +
 +                      Damage(target_victim, self.realowner, self.realowner,
 +                              swing_damage, WEP_SHOTGUN | HITTYPE_SECONDARY,
 +                              self.realowner.origin + self.realowner.view_ofs,
 +                              v_forward * WEP_CVAR_SEC(shotgun, force));
 +
 +                      if(accuracy_isgooddamage(self.realowner, target_victim)) { accuracy_add(self.realowner, WEP_SHOTGUN, 0, swing_damage); }
 +
 +                      // draw large red flash for debugging
 +                      //te_customflash(targpos, 200, 2, '15 0 0');
 +
 +                      if(WEP_CVAR_SEC(shotgun, melee_multihit)) // allow multiple hits with one swing, but not against the same player twice.
 +                      {
 +                              self.swing_alreadyhit = target_victim;
 +                              continue; // move along to next trace
 +                      }
 +                      else
 +                      {
 +                              remove(self);
 +                              return;
 +                      }
 +              }
 +      }
 +
 +      if(time >= self.cnt + meleetime)
 +      {
 +              // melee is finished
 +              remove(self);
 +              return;
 +      }
 +      else
 +      {
 +              // set up next frame
 +              self.swing_prev = i;
 +              self.nextthink = time;
 +      }
 +}
 +
 +void W_Shotgun_Attack2 (void)
 +{
 +      sound (self, CH_WEAPON_A, "weapons/shotgun_melee.wav", VOL_BASE, ATTEN_NORM);
 +      weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(shotgun, animtime), w_ready);
 +
 +      entity meleetemp;
 +      meleetemp = spawn();
 +      meleetemp.realowner = self;
 +      meleetemp.think = W_Shotgun_Melee_Think;
 +      meleetemp.nextthink = time + WEP_CVAR_SEC(shotgun, melee_delay) * W_WeaponRateFactor();
 +      W_SetupShot_Range(self, TRUE, 0, "", 0, WEP_CVAR_SEC(shotgun, damage), WEP_CVAR_SEC(shotgun, melee_range));
 +}
 +
 +.float shotgun_primarytime;
 +
 +float W_Shotgun(float req)
 +{
 +      switch(req)
 +      {
 +              case WR_AIM:
 +              {
 +                      if(vlen(self.origin-self.enemy.origin) <= WEP_CVAR_SEC(shotgun, melee_range))
 +                              self.BUTTON_ATCK2 = bot_aim(1000000, 0, 0.001, FALSE);
 +                      else
 +                              self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE);
 +
 +                      return TRUE;
 +              }
 +              case WR_THINK:
 +              {
 +                      if(WEP_CVAR(shotgun, reload_ammo) && self.clip_load < WEP_CVAR_PRI(shotgun, ammo)) // forced reload
 +                      {
 +                              // don't force reload an empty shotgun if its melee attack is active
 +                              if (!WEP_CVAR(shotgun, secondary))
 +                                      WEP_ACTION(self.weapon, WR_RELOAD);
 +                      }
 +                      else
 +                      {
 +                              if (self.BUTTON_ATCK)
 +                              {
 +                                      if (time >= self.shotgun_primarytime) // handle refire separately so the secondary can be fired straight after a primary
 +                                      {
 +                                              if(weapon_prepareattack(0, WEP_CVAR_PRI(shotgun, animtime)))
 +                                              {
 +                                                      W_Shotgun_Attack();
 +                                                      self.shotgun_primarytime = time + WEP_CVAR_PRI(shotgun, refire) * W_WeaponRateFactor();
 +                                                      weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(shotgun, animtime), w_ready);
 +                                              }
 +                                      }
 +                              }
 +                      }
 +                      if (self.clip_load >= 0) // we are not currently reloading
 +                      if (!self.crouch) // no crouchmelee please
 +                      if (self.BUTTON_ATCK2 && WEP_CVAR(shotgun, secondary))
 +                      if (weapon_prepareattack(1, WEP_CVAR_SEC(shotgun, refire)))
 +                      {
 +                              // attempt forcing playback of the anim by switching to another anim (that we never play) here...
 +                              weapon_thinkf(WFRAME_FIRE1, 0, W_Shotgun_Attack2);
 +                      }
 +                      
 +                      return TRUE;
 +              }
 +              case WR_INIT:
 +              {
 +                      precache_model ("models/uziflash.md3");
 +                      precache_model ("models/weapons/g_shotgun.md3");
 +                      precache_model ("models/weapons/v_shotgun.md3");
 +                      precache_model ("models/weapons/h_shotgun.iqm");
 +                      precache_sound ("misc/itempickup.wav");
 +                      precache_sound ("weapons/shotgun_fire.wav");
 +                      precache_sound ("weapons/shotgun_melee.wav");
 +                      SHOTGUN_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP)
 +                      return TRUE;
 +              }
 +              case WR_SETUP:
 +              {
 +                      self.ammo_field = ammo_none;
 +                      return TRUE;
 +              }
 +              case WR_CHECKAMMO1:
 +              case WR_CHECKAMMO2:
 +              {
 +                      // shotgun has infinite ammo
 +                      return TRUE;
 +              }
 +              case WR_CONFIG:
 +              {
 +                      SHOTGUN_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS)
 +                      return TRUE;
 +              }
 +              case WR_RELOAD:
 +              {
 +                      W_Reload(WEP_CVAR_PRI(shotgun, ammo), "weapons/reload.wav"); // WEAPONTODO
 +                      return TRUE;
 +              }
 +              case WR_SUICIDEMESSAGE:
 +              {
 +                      return WEAPON_THINKING_WITH_PORTALS;
 +              }
 +              case WR_KILLMESSAGE:
 +              {
 +                      if(w_deathtype & HITTYPE_SECONDARY)
 +                              return WEAPON_SHOTGUN_MURDER_SLAP;
 +                      else
 +                              return WEAPON_SHOTGUN_MURDER;
 +              }
 +      }
 +      return FALSE;
 +}
 +#endif
 +#ifdef CSQC
 +.float prevric;
 +float W_Shotgun(float req)
 +{
 +      switch(req)
 +      {
 +              case WR_IMPACTEFFECT:
 +              {
 +                      vector org2;
 +                      org2 = w_org + w_backoff * 2;
 +                      pointparticles(particleeffectnum("shotgun_impact"), org2, w_backoff * 1000, 1);
 +                      if(!w_issilent && time - self.prevric > 0.25)
 +                      {
 +                              if(w_random < 0.0165)
 +                                      sound(self, CH_SHOTS, "weapons/ric1.wav", VOL_BASE, ATTEN_NORM);
 +                              else if(w_random < 0.033)
 +                                      sound(self, CH_SHOTS, "weapons/ric2.wav", VOL_BASE, ATTEN_NORM);
 +                              else if(w_random < 0.05)
 +                                      sound(self, CH_SHOTS, "weapons/ric3.wav", VOL_BASE, ATTEN_NORM);
 +                              self.prevric = time;
 +                      }
 +
 +                      return TRUE;
 +              }
 +              case WR_INIT:
 +              {
 +                      precache_sound("weapons/ric1.wav");
 +                      precache_sound("weapons/ric2.wav");
 +                      precache_sound("weapons/ric3.wav");
 +                      return TRUE;
 +              }
 +              case WR_ZOOMRETICLE:
 +              {
 +                      // no weapon specific image for this weapon
 +                      return FALSE;
 +              }
 +      }
 +      return FALSE;
 +}
 +#endif
 +#endif
@@@ -50,7 -51,8 +51,8 @@@ xonotic/util.q
  ../common/campaign_file.qc
  ../common/campaign_setup.qc
  ../common/mapinfo.qc
 -../common/items.qc
 +../common/weapons/weapons.qc // TODO
  ../common/urllib.qc
+ ../common/monsters/monsters.qc
  
  ../warpzonelib/mathlib.qc
@@@ -117,7 -336,54 +117,6 @@@ float autocvar_g_balance_health_regenst
  float autocvar_g_balance_health_rot;
  float autocvar_g_balance_health_rotlinear;
  float autocvar_g_balance_health_rotstable;
- float autocvar_g_balance_health_start;
 -float autocvar_g_balance_hlac_primary_ammo;
 -float autocvar_g_balance_hlac_primary_animtime;
 -float autocvar_g_balance_hlac_primary_damage;
 -float autocvar_g_balance_hlac_primary_edgedamage;
 -float autocvar_g_balance_hlac_primary_force;
 -float autocvar_g_balance_hlac_primary_lifetime;
 -float autocvar_g_balance_hlac_primary_radius;
 -float autocvar_g_balance_hlac_primary_refire;
 -float autocvar_g_balance_hlac_primary_speed;
 -float autocvar_g_balance_hlac_primary_spread_add;
 -float autocvar_g_balance_hlac_primary_spread_crouchmod;
 -float autocvar_g_balance_hlac_primary_spread_max;
 -float autocvar_g_balance_hlac_primary_spread_min;
 -float autocvar_g_balance_hlac_secondary;
 -float autocvar_g_balance_hlac_secondary_ammo;
 -float autocvar_g_balance_hlac_secondary_animtime;
 -float autocvar_g_balance_hlac_secondary_damage;
 -float autocvar_g_balance_hlac_secondary_edgedamage;
 -float autocvar_g_balance_hlac_secondary_force;
 -float autocvar_g_balance_hlac_secondary_lifetime;
 -float autocvar_g_balance_hlac_secondary_radius;
 -float autocvar_g_balance_hlac_secondary_refire;
 -float autocvar_g_balance_hlac_secondary_shots;
 -float autocvar_g_balance_hlac_secondary_speed;
 -float autocvar_g_balance_hlac_secondary_spread;
 -float autocvar_g_balance_hlac_secondary_spread_crouchmod;
 -float autocvar_g_balance_hlac_reload_ammo;
 -float autocvar_g_balance_hlac_reload_time;
 -float autocvar_g_balance_hook_primary_animtime;
 -float autocvar_g_balance_hook_primary_fuel;
 -float autocvar_g_balance_hook_primary_hooked_fuel;
 -float autocvar_g_balance_hook_primary_hooked_time_free;
 -float autocvar_g_balance_hook_primary_hooked_time_max;
 -float autocvar_g_balance_hook_primary_refire;
 -float autocvar_g_balance_hook_secondary_ammo;
 -float autocvar_g_balance_hook_secondary_animtime;
 -float autocvar_g_balance_hook_secondary_damage;
 -float autocvar_g_balance_hook_secondary_duration;
 -float autocvar_g_balance_hook_secondary_edgedamage;
 -float autocvar_g_balance_hook_secondary_force;
 -float autocvar_g_balance_hook_secondary_gravity;
 -float autocvar_g_balance_hook_secondary_lifetime;
 -float autocvar_g_balance_hook_secondary_power;
 -float autocvar_g_balance_hook_secondary_radius;
 -float autocvar_g_balance_hook_secondary_refire;
 -float autocvar_g_balance_hook_secondary_speed;
 -float autocvar_g_balance_hook_secondary_health;
 -float autocvar_g_balance_hook_secondary_damageforcescale;
  float autocvar_g_balance_keyhunt_damageforcescale;
  float autocvar_g_balance_keyhunt_delay_collect;
  float autocvar_g_balance_keyhunt_delay_return;
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -23,22 -26,10 +23,25 @@@ sys-post.q
  ../common/command/shared_defs.qh
  ../common/net_notice.qh
  ../common/animdecide.qh
+ ../common/monsters/monsters.qh
+ ../common/monsters/sv_monsters.qh
+ ../common/monsters/spawn.qh
  
 +../common/weapons/config.qh
 +../common/weapons/weapons.qh // TODO
 +weapons/accuracy.qh
 +weapons/common.qh
 +weapons/csqcprojectile.qh // TODO
 +weapons/hitplot.qh
 +weapons/selection.qh
 +weapons/spawning.qh
 +weapons/throwing.qh
 +weapons/tracing.qh
 +weapons/weaponstats.qh
 +weapons/weaponsystem.qh
 +
 +t_items.qh
 +
  autocvars.qh
  constants.qh
  defs.qh               // Should rename this, it has fields and globals
@@@ -234,6 -227,13 +238,11 @@@ playerstats.q
  
  round_handler.qc
  
 -../common/explosion_equation.qc
 -
+ ../common/monsters/sv_monsters.qc
+ ../common/monsters/monsters.qc
+ ../common/monsters/spawn.qc
  mutators/base.qc
  mutators/gamemode_assault.qc
  mutators/gamemode_ca.qc
Simple merge
Simple merge
Simple merge
@@@ -690,8 -695,10 +695,11 @@@ void raptor_blowup(
  {
      self.deadflag    = DEAD_DEAD;
      self.vehicle_exit(VHEF_NORMAL);
-     RadiusDamage (self, self.enemy, 250, 15, 250, world, world, 250, DEATH_VH_RAPT_DEATH, world);
 +
 -                              autocvar_g_vehicle_raptor_blowup_radius, world,
+       RadiusDamage(self, self.enemy, autocvar_g_vehicle_raptor_blowup_coredamage,
+                               autocvar_g_vehicle_raptor_blowup_edgedamage,
++                              autocvar_g_vehicle_raptor_blowup_radius, world, world,
+                               autocvar_g_vehicle_raptor_blowup_forceintensity, DEATH_VH_RAPT_DEATH, world);
  
      self.alpha          = -1;
      self.movetype       = MOVETYPE_NONE;
@@@ -718,7 -723,10 +723,10 @@@ void spiderbot_blowup(
      SUB_SetFade(g1, time, min(autocvar_g_vehicle_spiderbot_respawntime, 10));
      SUB_SetFade(g2, time, min(autocvar_g_vehicle_spiderbot_respawntime, 10));
  
-     RadiusDamage (self, self.enemy, 250, 15, 250, world, world, 250, DEATH_VH_SPID_DEATH, world);
+       RadiusDamage(self, self.enemy, autocvar_g_vehicle_spiderbot_blowup_coredamage,
+                               autocvar_g_vehicle_spiderbot_blowup_edgedamage,
 -                              autocvar_g_vehicle_spiderbot_blowup_radius, world,
++                              autocvar_g_vehicle_spiderbot_blowup_radius, world, world,
+                               autocvar_g_vehicle_spiderbot_blowup_forceintensity, DEATH_VH_SPID_DEATH, world);
  
      self.alpha = self.tur_head.alpha = self.gun1.alpha = self.gun2.alpha = -1;
      self.movetype   = MOVETYPE_NONE;
Simple merge
index 5b22669,0000000..09d4233
mode 100644,000000..100644
--- /dev/null
@@@ -1,120 -1,0 +1,124 @@@
-       if(IS_CLIENT(targ))
 +float accuracy_byte(float n, float d)
 +{
 +      //printf("accuracy: %d / %d\n", n, d);
 +      if(n <= 0)
 +              return 0;
 +      if(n > d)
 +              return 255;
 +      return 1 + rint(n * 100.0 / d);
 +}
 +
 +float accuracy_send(entity to, float sf)
 +{
 +      float w, f;
 +      entity a;
 +      WriteByte(MSG_ENTITY, ENT_CLIENT_ACCURACY);
 +
 +      a = self.owner;
 +      if(IS_SPEC(a))
 +              a = a.enemy;
 +      a = a.accuracy;
 +
 +      if(to != a.owner)
 +              if (!(self.owner.cvar_cl_accuracy_data_share && autocvar_sv_accuracy_data_share))
 +                      sf = 0;
 +      // note: zero sendflags can never be sent... so we can use that to say that we send no accuracy!
 +      WriteInt24_t(MSG_ENTITY, sf);
 +      if(sf == 0)
 +              return TRUE;
 +      // note: we know that client and server agree about SendFlags...
 +      for(w = 0, f = 1; w <= WEP_LAST - WEP_FIRST; ++w)
 +      {
 +              if(sf & f)
 +                      WriteByte(MSG_ENTITY, accuracy_byte(self.(accuracy_hit[w]), self.(accuracy_fired[w])));
 +              if(f == 0x800000)
 +                      f = 1;
 +              else
 +                      f *= 2;
 +      }
 +      return TRUE;
 +}
 +
 +// init/free
 +void accuracy_init(entity e)
 +{
 +      e.accuracy = spawn();
 +      e.accuracy.owner = e;
 +      e.accuracy.classname = "accuracy";
 +      e.accuracy.drawonlytoclient = e;
 +      Net_LinkEntity(e.accuracy, FALSE, 0, accuracy_send);
 +}
 +
 +void accuracy_free(entity e)
 +{
 +      remove(e.accuracy);
 +}
 +
 +// force a resend of a player's accuracy stats
 +void accuracy_resend(entity e)
 +{
 +      e.accuracy.SendFlags = 0xFFFFFF;
 +}
 +
 +// update accuracy stats
 +.float hit_time;
 +.float fired_time;
 +
 +void accuracy_add(entity e, float w, float fired, float hit)
 +{
 +      entity a;
 +      float b;
 +      if(IS_INDEPENDENT_PLAYER(e))
 +              return;
 +      a = e.accuracy;
 +      if(!a || !(hit || fired))
 +              return;
 +      w -= WEP_FIRST;
 +      b = accuracy_byte(a.(accuracy_hit[w]), a.(accuracy_fired[w]));
 +      if(hit)
 +              a.(accuracy_hit[w]) += hit;
 +      if(fired)
 +              a.(accuracy_fired[w]) += fired;
 +
 +    if(hit && a.hit_time != time) // only run this once per frame
 +    {
 +        a.(accuracy_cnt_hit[w]) += 1;
 +        a.hit_time = time;
 +    }
 +
 +    if(fired && a.fired_time != time) // only run this once per frame
 +    {
 +        a.(accuracy_cnt_fired[w]) += 1;
 +        a.fired_time = time;
 +    }
 +
 +      if(b == accuracy_byte(a.(accuracy_hit[w]), a.(accuracy_fired[w])))
 +              return;
 +      w = pow(2, mod(w, 24));
 +      a.SendFlags |= w;
 +      FOR_EACH_CLIENT(a)
 +              if(IS_SPEC(a))
 +                      if(a.enemy == e)
 +                              a.SendFlags |= w;
 +}
 +
 +float accuracy_isgooddamage(entity attacker, entity targ)
 +{
++      frag_attacker = attacker;
++      frag_target = targ;
++      float mutator_check = MUTATOR_CALLHOOK(AccuracyTargetValid);
++
 +      if(!warmup_stage)
 +      if(targ.deadflag == DEAD_NO)
++      if(mutator_check == MUT_ACCADD_INVALID || (mutator_check == MUT_ACCADD_VALID && IS_CLIENT(targ)))
 +      if(DIFF_TEAM(attacker, targ))
 +              return TRUE;
 +      return FALSE;
 +}
 +
 +float accuracy_canbegooddamage(entity attacker)
 +{
 +      if(!warmup_stage)
 +              return TRUE;
 +      return FALSE;
 +}