Merge branch 'master' into Mario/weapons
authorMario <zacjardine@y7mail.com>
Fri, 14 Nov 2014 02:05:18 +0000 (13:05 +1100)
committerMario <zacjardine@y7mail.com>
Fri, 14 Nov 2014 02:05:18 +0000 (13:05 +1100)
Conflicts:
mutators.cfg
qcsrc/client/hud.qc
qcsrc/client/waypointsprites.qc
qcsrc/common/constants.qh
qcsrc/common/notifications.qh
qcsrc/server/cl_weapons.qc
qcsrc/server/mutators/mutator_nades.qc
qcsrc/server/progs.src
qcsrc/server/w_electro.qc
qcsrc/server/w_minelayer.qc

39 files changed:
1  2 
defaultXonotic.cfg
mutators.cfg
qcsrc/client/Main.qc
qcsrc/client/View.qc
qcsrc/client/autocvars.qh
qcsrc/client/hud.qc
qcsrc/client/progs.src
qcsrc/client/waypointsprites.qc
qcsrc/client/weapons/projectile.qc
qcsrc/common/constants.qh
qcsrc/common/monsters/monster/mage.qc
qcsrc/common/monsters/sv_monsters.qc
qcsrc/common/notifications.qh
qcsrc/common/stats.qh
qcsrc/common/util.qh
qcsrc/common/weapons/w_arc.qc
qcsrc/common/weapons/w_minelayer.qc
qcsrc/menu/xonotic/dialog_multiplayer_create_mutators.c
qcsrc/server/autocvars.qh
qcsrc/server/bot/aim.qc
qcsrc/server/bot/havocbot/roles.qc
qcsrc/server/cl_client.qc
qcsrc/server/cl_physics.qc
qcsrc/server/cl_player.qc
qcsrc/server/defs.qh
qcsrc/server/g_damage.qc
qcsrc/server/g_world.qc
qcsrc/server/miscfunctions.qc
qcsrc/server/mutators/base.qh
qcsrc/server/mutators/gamemode_ca.qc
qcsrc/server/mutators/gamemode_nexball.qc
qcsrc/server/mutators/mutator_minstagib.qc
qcsrc/server/mutators/mutator_nades.qc
qcsrc/server/mutators/mutator_touchexplode.qc
qcsrc/server/progs.src
qcsrc/server/t_quake3.qc
qcsrc/server/teamplay.qc
qcsrc/server/weapons/csqcprojectile.qc
qcsrc/server/weapons/weaponsystem.qc

Simple merge
diff --cc mutators.cfg
@@@ -163,8 -231,49 +231,57 @@@ set g_campcheck_damage 10
  set g_campcheck_distance 1800
  
  
++<<<<<<< HEAD
 +// ==========
 +//  New Toys
 +// ==========
 +set g_new_toys 0 "Mutator 'New Toys': enable extra fun guns"
 +set g_new_toys_autoreplace 2 "0: never replace, 1: always auto replace guns by available new toys, 2: randomly auto replace guns by available new toys"
++=======
+ // =======
+ //  buffs
+ // =======
+ set cl_buffs_autoreplace 1 "automatically drop current buff when picking up another"
+ set g_buffs 0 "enable buffs (requires buff items or powerups)"
+ set g_buffs_waypoint_distance 1024 "maximum distance at which buff waypoint can be seen from item"
+ set g_buffs_randomize 1 "randomize buff type when player drops buff"
+ set g_buffs_random_lifetime 30 "re-spawn the buff again if it hasn't been touched after this time in seconds"
+ set g_buffs_random_location 0 "randomize buff location on start and when reset"
+ set g_buffs_random_location_attempts 10 "number of random locations a single buff will attempt to respawn at before giving up"
+ set g_buffs_spawn_count 5 "how many buffs to spawn on the map if none exist already"
+ set g_buffs_replace_powerups 1 "replace powerups on the map with random buffs"
+ set g_buffs_cooldown_activate 5 "cooldown period when buff is first activated"
+ set g_buffs_cooldown_respawn 3 "cooldown period when buff is reloading"
+ set g_buffs_ammo 1 "ammo buff: infinite ammunition"
+ set g_buffs_resistance 1 "resistance buff: greatly reduces damage taken"
+ set g_buffs_resistance_blockpercent 0.7 "damage reduction multiplier, higher values mean less damage"
+ set g_buffs_medic 1 "medic buff: increased regeneration speed, extra health, chance to survive a fatal attack"
+ set g_buffs_medic_survive_chance 0.6 "multiplier chance of player surviving a fatal hit"
+ set g_buffs_medic_survive_health 5 "amount of health player survives with after taking a fatal hit"
+ set g_buffs_medic_rot 0.7 "health rot rate multiplier"
+ set g_buffs_medic_max 1.5 "stable health medic limit multiplier"
+ set g_buffs_medic_regen 1.7 "health medic rate multiplier"
+ set g_buffs_vengeance 1 "vengeance buff: attackers also take damage"
+ set g_buffs_vengeance_damage_multiplier 0.6 "amount of damage dealt the attacker takes when hitting a target with vengeance"
+ set g_buffs_bash 1 "bash buff: increased knockback force and immunity to knockback"
+ set g_buffs_bash_force 2 "bash force multiplier"
+ set g_buffs_bash_force_self 1.2 "bash self force multiplier"
+ set g_buffs_disability 1 "disability buff: attacks to players and monsters deal slowness (decreased movement/attack speed) for a few seconds"
+ set g_buffs_disability_time 3 "time in seconds for target disability"
+ set g_buffs_disability_speed 0.5 "player speed multiplier while disabled"
+ set g_buffs_disability_rate 1.7 "player weapon rate multiplier while disabled"
+ set g_buffs_speed 1 "speed buff: increased movement/attack/health regeneration speed, carrier takes slightly more damage"
+ set g_buffs_speed_speed 1.7 "player speed multiplier while holding speed buff"
+ set g_buffs_speed_rate 0.8 "player weapon rate multiplier while holding speed buff"
+ set g_buffs_speed_damage_take 1.2 "damage taken multiplier while holding speed buff"
+ set g_buffs_speed_regen 1.2 "regeneration speed multiplier while holding speed buff"
+ set g_buffs_vampire 1 "vampire buff: attacks to players and monsters heal the carrier"
+ set g_buffs_vampire_damage_steal 0.6 "damage stolen multiplier while holding vampire buff"
+ set g_buffs_jump 1 "jump buff: greatly increased jump height"
+ set g_buffs_jump_height 600 "jump height while holding jump buff"
+ set g_buffs_flight 1 "flight buff: greatly decreased gravity"
+ set g_buffs_flight_gravity 0.3 "player gravity multiplier while holding flight buff"
+ set g_buffs_invisible 1 "invisible buff: carrier becomes invisible"
+ set g_buffs_invisible_alpha 0.4 "player invisibility multiplier while holding invisible buff"
++>>>>>>> master
Simple merge
Simple merge
Simple merge
@@@ -831,30 -858,92 +831,78 @@@ void HUD_Weapons(void
  }
  
  // Ammo (#1)
 -//
 -// TODO: macro
 -float GetAmmoItemCode(float i)
 -{
 -      switch(i)
 -      {
 -              case 0: return IT_SHELLS;
 -              case 1: return IT_NAILS;
 -              case 2: return IT_ROCKETS;
 -              case 3: return IT_CELLS;
 -              case 4: return IT_FUEL;
 -              default: return -1;
 -      }
 -}
 -
 -string GetAmmoPicture(float i)
 -{
 -      switch(i)
 -      {
 -              case 0: return "ammo_shells";
 -              case 1: return "ammo_bullets";
 -              case 2: return "ammo_rockets";
 -              case 3: return "ammo_cells";
 -              case 4: return "ammo_fuel";
 -              default: return "";
 -      }
 -}
 -
+ void DrawNadeScoreBar(vector myPos, vector mySize, vector color)
+ {
+       
+       HUD_Panel_DrawProgressBar(
+               myPos + eX * autocvar_hud_panel_ammo_progressbar_xoffset * mySize_x, 
+               mySize - eX * autocvar_hud_panel_ammo_progressbar_xoffset * mySize_x, 
+               autocvar_hud_panel_ammo_progressbar_name, 
+               getstatf(STAT_NADE_BONUS_SCORE), 0, 0, color, 
+               autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
+ }
+ void DrawAmmoNades(vector myPos, vector mySize, float draw_expanding, float expand_time)
+ {
+       float theAlpha = 1, a, b;
+       vector nade_color, picpos, numpos;
+       
+       nade_color = Nade_Color(getstati(STAT_NADE_BONUS_TYPE));
+       
+       a = getstatf(STAT_NADE_BONUS);
+       b = getstatf(STAT_NADE_BONUS_SCORE);
+       
+       if(autocvar_hud_panel_ammo_iconalign)
+       {
+               numpos = myPos;
+               picpos = myPos + eX * 2 * mySize_y;
+       }
+       else
+       {
+               numpos = myPos + eX * mySize_y;
+               picpos = myPos;
+       }
+       DrawNadeScoreBar(myPos, mySize, nade_color);
+       if(b > 0 || a > 0)
+       {
+               if(autocvar_hud_panel_ammo_text)
+                       drawstring_aspect(numpos, ftos(a), eX * (2/3) * mySize_x + eY * mySize_y, '1 1 1', panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL);
+               
+               if(draw_expanding)
+                       drawpic_aspect_skin_expanding(picpos, "nade_nbg", '1 1 0' * mySize_y, '1 1 1', panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL, expand_time);
+                       
+               drawpic_aspect_skin(picpos, "nade_bg" , '1 1 0' * mySize_y, '1 1 1', panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL);
+               drawpic_aspect_skin(picpos, "nade_nbg" , '1 1 0' * mySize_y, nade_color, panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL);
+       }
+ }
 -void DrawAmmoItem(vector myPos, vector mySize, float itemcode, float currently_selected, float infinite_ammo)
 +void DrawAmmoItem(vector myPos, vector mySize, .float ammotype, float currently_selected, float infinite_ammo)
  {
 -      float a;
 -      if(autocvar__hud_configure)
 +      float a = 0;
 +      if(ammotype != ammo_none)
        {
 -              currently_selected = (itemcode == 2); //rockets always selected
 -              a = 31 + mod(itemcode*93, 128);
 +              if(autocvar__hud_configure)
 +              {
 +                      currently_selected = (ammotype == ammo_rockets); //rockets always selected
 +                      a = 60;
 +              }
 +              else
 +              {
 +                      // how much ammo do we have of this ammotype?
 +                      a = getstati(GetAmmoStat(ammotype));
 +              }
        }
        else
 -              a = getstati(GetAmmoStat(itemcode)); // how much ammo do we have of type itemcode?
 +      {
 +              #if 0
 +              infinite_ammo = TRUE;
 +              #else
 +              return; // just don't draw infinite ammo at all.
 +              #endif
 +      }
  
        vector color;
        if(infinite_ammo)
              drawstring_aspect(numpos, ftos(a), eX * (2/3) * mySize_x + eY * mySize_y, '0 0 0', panel_fg_alpha * theAlpha * 0.5, DRAWFLAG_NORMAL);
      }
        if(a > 0 || infinite_ammo)
 -              drawpic_aspect_skin(picpos, GetAmmoPicture(itemcode), '1 1 0' * mySize_y, '1 1 1', panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL);
 +              drawpic_aspect_skin(picpos, GetAmmoPicture(ammotype), '1 1 0' * mySize_y, '1 1 1', panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL);
        else // "ghost" ammo icon
 -              drawpic_aspect_skin(picpos, GetAmmoPicture(itemcode), '1 1 0' * mySize_y, '0 0 0', panel_fg_alpha * theAlpha * 0.5, DRAWFLAG_NORMAL);
 +              drawpic_aspect_skin(picpos, GetAmmoPicture(ammotype), '1 1 0' * mySize_y, '0 0 0', panel_fg_alpha * theAlpha * 0.5, DRAWFLAG_NORMAL);
  }
  
+ float nade_prevstatus;
+ float nade_prevframe;
+ float nade_statuschange_time;
  void HUD_Ammo(void)
  {
        if(hud != HUD_NORMAL) return;
                mySize -= '2 2 0' * panel_bg_padding;
        }
  
-       const float AMMO_COUNT = 5;
        float rows = 0, columns, row, column;
+       float nade_cnt = getstatf(STAT_NADE_BONUS), nade_score = getstatf(STAT_NADE_BONUS_SCORE);
+       float draw_nades = (nade_cnt > 0 || nade_score > 0), nade_statuschange_elapsedtime;
+       float total_ammo_count;
        vector ammo_size;
-       if(autocvar_hud_panel_ammo_onlycurrent)
-               ammo_size = mySize;
 -      float AMMO_COUNT = 4;
+       if (autocvar_hud_panel_ammo_onlycurrent)
+               total_ammo_count = 1;
        else
+               total_ammo_count = AMMO_COUNT - 1; // fuel
+       if(draw_nades)
        {
-               rows = mySize_y/mySize_x;
-               rows = bound(1, floor((sqrt(4 * (3/1) * rows * AMMO_COUNT + rows * rows) + rows + 0.5) / 2), AMMO_COUNT);
-               //                               ^^^ ammo item aspect goes here
+               ++total_ammo_count;
+               if (nade_cnt != nade_prevframe)
+               {
+                       nade_statuschange_time = time;
+                       nade_prevstatus = nade_prevframe;
+                       nade_prevframe = nade_cnt;
+               }
+       }
+       else
+               nade_prevstatus = nade_prevframe = nade_statuschange_time = 0;
  
-               columns = ceil(AMMO_COUNT/rows);
+       rows = mySize_y/mySize_x;
+       rows = bound(1, floor((sqrt(4 * (3/1) * rows * (total_ammo_count) + rows * rows) + rows + 0.5) / 2), (total_ammo_count));
+       //                               ^^^ ammo item aspect goes here
  
-               ammo_size = eX * mySize_x*(1/columns) + eY * mySize_y*(1/rows);
-       }
+       columns = ceil((total_ammo_count)/rows);
+       ammo_size = eX * mySize_x*(1/columns) + eY * mySize_y*(1/rows);
+       
  
        local vector offset = '0 0 0'; // fteqcc sucks
        float newSize;
                ammo_size_y = newSize;
        }
  
 -      float i, stat_items, currently_selected, infinite_ammo;
 -      infinite_ammo = FALSE;
 -
 +      float i;
 +      float infinite_ammo = (getstati(STAT_ITEMS, 0, 24) & IT_UNLIMITED_WEAPON_AMMO);
+       row = column = 0;
 -
 -      if (autocvar_hud_panel_ammo_onlycurrent)
 +      if(autocvar_hud_panel_ammo_onlycurrent)
        {
                if(autocvar__hud_configure)
                {
                }
                else
                {
 -                      stat_items = getstati(STAT_ITEMS, 0, 24);
 -                      if (stat_items & IT_UNLIMITED_WEAPON_AMMO)
 -                              infinite_ammo = TRUE;
 -                      for (i = 0; i < AMMO_COUNT; ++i) {
 -                              currently_selected = stat_items & GetAmmoItemCode(i);
 -                              if (currently_selected)
 -                              {
 -                                      DrawAmmoItem(pos, ammo_size, i, true, infinite_ammo);
 -                                      break;
 -                              }
 -                      }
 -              }
 +                      DrawAmmoItem(
 +                              pos,
 +                              ammo_size,
 +                              (get_weaponinfo(switchweapon)).ammo_field,
 +                              TRUE,
 +                              infinite_ammo
 +                      );
+               ++row;
+               if(row >= rows)
+               {
+                       row = 0;
+                       column = column + 1;
+               }
 +              }
        }
        else
        {
@@@ -16,9 -17,12 +17,11 @@@ Defs.q
  
  ../common/teams.qh
  ../common/util.qh
+ ../common/nades.qh
+ ../common/buffs.qh
  ../common/test.qh
  ../common/counting.qh
 -../common/items.qh
 -../common/explosion_equation.qh
 +../common/weapons/weapons.qh // TODO
  ../common/mapinfo.qh
  ../common/command/markup.qh
  ../common/command/rpn.qh
@@@ -228,9 -241,7 +228,9 @@@ vector spritelookupcolor(string s, vect
  }
  string spritelookuptext(string s)
  {
-       if(substring(s, 0, 4) == "wpn-")
-               return (get_weaponinfo(stof(substring(s, 4, strlen(s)))).message);
++      if(substring(s, 0, 4) == "wpn-") { return (get_weaponinfo(stof(substring(s, 4, strlen(s)))).message); }
+       if(substring(s, 0, 5) == "buff-") { return Buff_PrettyName(Buff_Type_FromSprite(s)); }
 +
        switch(s)
        {
                case "as-push": return _("Push");
index c98ab7b,0000000..58bd11e
mode 100644,000000..100644
--- /dev/null
@@@ -1,538 -1,0 +1,505 @@@
-                       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;
 +.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:
-                       trailorigin += v_up * 4;
-                       break;
 +                      case PROJECTILE_HOOKBOMB:
 +                              rot = '1000 0 0'; // forward
 +                              break;
 +                      default:
 +                              break;
 +              }
++
++              if(Nade_IDFromProjectile(self.cnt) != 0)
++                      rot = self.avelocity;
++
 +              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: 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;
 +              case PROJECTILE_GRENADE:
 +              case PROJECTILE_GRENADE_BOUNCING:
 +                      trailorigin += v_right * 1 + v_forward * -10;
 +                      break;
 +              default:
 +                      break;
 +      }
++
++      if(Nade_IDFromProjectile(self.cnt) != 0)
++              trailorigin += v_up * 4;
++
 +      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;
 +              }
++
++              self.team = ReadByte() - 1;
 +      }
 +
 +      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_NAPALM_FOUNTAIN: //self.model = ""; self.modelindex = 0; self.traileffect = particleeffectnum("torch_small"); 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_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;
 +                      default:
++                              if(Nade_IDFromProjectile(self.cnt) != 0) { setmodel(self, "models/weapons/v_ok_grenade.md3");self.traileffect = particleeffectnum(Nade_TrailEffect(self.cnt, self.team)); break; }
 +                              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:
-                       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_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_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_NAPALM_FOUNTAIN:
 +                      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;
 +              }
++
++              if(Nade_IDFromProjectile(self.cnt) != 0)
++              {
++                      self.mins = '-16 -16 -16';
++                      self.maxs = '16 16 16';
++                      self.colormod = Nade_Color(Nade_IDFromProjectile(self.cnt));
++                      self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
++                      self.move_movetype = MOVETYPE_BOUNCE;
++                      self.move_touch = func_null;
++                      self.scale = 1.5;
++                      self.avelocity = randomvec() * 720;
++                      
++                      if(Nade_IDFromProjectile(self.cnt) == NADE_TYPE_TRANSLOCATE)
++                              self.solid = SOLID_TRIGGER;
++              }
++
 +              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/sphere/sphere.md3");
 +
 +      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
Simple merge
Simple merge
@@@ -991,7 -1018,8 +1024,8 @@@ string arg_slot[NOTIF_MAX_ARGS]
      ARG_CASE(ARG_CS_SV,     "spree_inf",     (autocvar_notification_show_sprees ? notif_arg_spree_inf(1, input, s2, f2) : "")) \
      ARG_CASE(ARG_CS_SV,     "spree_end",     (autocvar_notification_show_sprees ? notif_arg_spree_inf(-1, "", "", f1) : "")) \
      ARG_CASE(ARG_CS_SV,     "spree_lost",    (autocvar_notification_show_sprees ? notif_arg_spree_inf(-2, "", "", f1) : "")) \
 -    ARG_CASE(ARG_CS_SV,     "item_wepname",  W_Name(f1)) \
 +    ARG_CASE(ARG_CS_SV,     "item_wepname",  WEP_NAME(f1)) \
+     ARG_CASE(ARG_CS_SV,     "item_buffname", sprintf("%s%s", rgb_to_hexcolor(Buff_Color(f1)), Buff_PrettyName(f1))) \
      ARG_CASE(ARG_CS_SV,     "item_wepammo",  (s1 != "" ? sprintf(_(" with %s"), s1) : "")) \
      ARG_CASE(ARG_DC,        "item_centime",  ftos(autocvar_notification_item_centerprinttime)) \
      ARG_CASE(ARG_SV,        "death_team",    Team_ColoredFullName(f1)) \
index 0000000,793582e..44a540b
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,290 +1,290 @@@
 -const float STAT_NEX_CHARGE             = 50;
+ // Full list of all stat constants, icnluded in a single location for easy reference
+ // 255 is the current limit (MAX_CL_STATS - 1), engine will need to be modified if you wish to add more stats
+ const float MAX_CL_STATS                = 256;
+ const float STAT_HEALTH                 = 0;
+ // 1 empty?
+ const float STAT_WEAPON                 = 2;
+ const float STAT_AMMO                   = 3;
+ const float STAT_ARMOR                  = 4;
+ const float STAT_WEAPONFRAME            = 5;
+ const float STAT_SHELLS                 = 6;
+ const float STAT_NAILS                  = 7;
+ const float STAT_ROCKETS                = 8;
+ const float STAT_CELLS                  = 9;
+ const float STAT_ACTIVEWEAPON           = 10;
+ const float STAT_TOTALSECRETS           = 11;
+ const float STAT_TOTALMONSTERS          = 12;
+ const float STAT_SECRETS                = 13;
+ const float STAT_MONSTERS               = 14;
+ const float STAT_ITEMS                  = 15;
+ const float STAT_VIEWHEIGHT             = 16;
+ // 17 empty?
+ // 18 empty?
+ // 19 empty?
+ // 20 empty?
+ const float STAT_VIEWZOOM               = 21;
+ // 22 empty?
+ // 23 empty?
+ // 24 empty?
+ // 25 empty?
+ // 26 empty?
+ // 27 empty?
+ // 28 empty?
+ // 29 empty?
+ // 30 empty?
+ // 31 empty?
+ const float STAT_KH_KEYS                = 32;
+ const float STAT_CTF_STATE              = 33;
+ // 34 empty?
+ const float STAT_WEAPONS                = 35;
+ const float STAT_SWITCHWEAPON           = 36;
+ const float STAT_GAMESTARTTIME          = 37;
+ const float STAT_STRENGTH_FINISHED      = 38;
+ const float STAT_INVINCIBLE_FINISHED    = 39;
+ // 40 empty?
+ // 41 empty?
+ const float STAT_PRESSED_KEYS           = 42;
+ const float STAT_ALLOW_OLDNEXBEAM       = 43; // this stat could later contain some other bits of info, like, more server-side particle config
+ const float STAT_FUEL                   = 44;
+ const float STAT_NB_METERSTART          = 45;
+ const float STAT_SHOTORG                = 46; // compressShotOrigin
+ const float STAT_LEADLIMIT              = 47;
+ const float STAT_WEAPON_CLIPLOAD        = 48;
+ const float STAT_WEAPON_CLIPSIZE        = 49;
 -const float STAT_NEX_CHARGEPOOL         = 53;
++const float STAT_VORTEX_CHARGE          = 50;
+ const float STAT_LAST_PICKUP            = 51;
+ const float STAT_HUD                    = 52;
 -// 84 empty?
++const float STAT_VORTEX_CHARGEPOOL      = 53;
+ const float STAT_HIT_TIME               = 54;
+ const float STAT_TYPEHIT_TIME           = 55;
+ const float STAT_LAYED_MINES            = 56;
+ const float STAT_HAGAR_LOAD             = 57;
+ const float STAT_SWITCHINGWEAPON        = 58;
+ const float STAT_SUPERWEAPONS_FINISHED  = 59;
+ const float STAT_VEHICLESTAT_HEALTH     = 60;
+ const float STAT_VEHICLESTAT_SHIELD     = 61;
+ const float STAT_VEHICLESTAT_ENERGY     = 62;
+ const float STAT_VEHICLESTAT_AMMO1      = 63;
+ const float STAT_VEHICLESTAT_RELOAD1    = 64;
+ const float STAT_VEHICLESTAT_AMMO2      = 65;
+ const float STAT_VEHICLESTAT_RELOAD2    = 66;
+ const float STAT_VEHICLESTAT_W2MODE     = 67;
+ // 68 empty?
+ const float STAT_NADE_TIMER             = 69;
+ const float STAT_SECRETS_TOTAL          = 70;
+ const float STAT_SECRETS_FOUND          = 71;
+ const float STAT_RESPAWN_TIME           = 72;
+ const float STAT_ROUNDSTARTTIME         = 73;
+ const float STAT_WEAPONS2               = 74;
+ const float STAT_WEAPONS3               = 75;
+ const float STAT_MONSTERS_TOTAL         = 76;
+ const float STAT_MONSTERS_KILLED        = 77;
+ const float STAT_BUFFS                  = 78;
+ const float STAT_NADE_BONUS             = 79;
+ const float STAT_NADE_BONUS_TYPE        = 80;
+ const float STAT_NADE_BONUS_SCORE       = 81;
+ const float STAT_HEALING_ORB            = 82;
+ const float STAT_HEALING_ORB_ALPHA      = 83;
++const float STAT_PLASMA                 = 84;
+ // 85 empty?
+ // 86 empty?
+ // 87 empty?
+ // 88 empty?
+ // 89 empty?
+ // 90 empty?
+ // 91 empty?
+ // 92 empty?
+ // 93 empty?
+ // 94 empty?
+ // 95 empty?
+ // 96 empty?
+ // 97 empty?
+ // 98 empty?
+ // 99 empty?
+ /* The following stats change depending on the gamemode, so can share the same ID */
+ // IDs 100 to 104 reserved for gamemodes
+ // freeze tag, clan arena, jailbreak
+ const float STAT_REDALIVE               = 100;
+ const float STAT_BLUEALIVE              = 101;
+ const float STAT_YELLOWALIVE            = 102;
+ const float STAT_PINKALIVE              = 103;
+ // domination
+ const float STAT_DOM_TOTAL_PPS          = 100;
+ const float STAT_DOM_PPS_RED            = 101;
+ const float STAT_DOM_PPS_BLUE           = 102;
+ const float STAT_DOM_PPS_YELLOW         = 103;
+ const float STAT_DOM_PPS_PINK           = 104;
+ // vip
+ const float STAT_VIP                    = 100;
+ const float STAT_VIP_RED                = 101;
+ const float STAT_VIP_BLUE               = 102;
+ const float STAT_VIP_YELLOW             = 103;
+ const float STAT_VIP_PINK               = 104;
+ // key hunt
+ const float STAT_KH_REDKEY_TEAM         = 100;
+ const float STAT_KH_BLUEKEY_TEAM        = 101;
+ const float STAT_KH_YELLOWKEY_TEAM      = 102;
+ const float STAT_KH_PINKKEY_TEAM        = 103;
+ /* Gamemode-specific stats end here */
+ const float STAT_FROZEN                 = 105;
+ const float STAT_REVIVE_PROGRESS        = 106;
+ // 107 empty?
+ // 108 empty?
+ // 109 empty?
+ // 110 empty?
+ // 111 empty?
+ // 112 empty?
+ // 113 empty?
+ // 114 empty?
+ // 115 empty?
+ // 116 empty?
+ // 117 empty?
+ // 118 empty?
+ // 119 empty?
+ // 120 empty?
+ // 121 empty?
+ // 122 empty?
+ // 123 empty?
+ // 124 empty?
+ // 125 empty?
+ // 126 empty?
+ // 127 empty?
+ // 128 empty?
+ // 129 empty?
+ // 130 empty?
+ // 131 empty?
+ // 132 empty?
+ // 133 empty?
+ // 134 empty?
+ // 135 empty?
+ // 136 empty?
+ // 137 empty?
+ // 138 empty?
+ // 139 empty?
+ // 140 empty?
+ // 141 empty?
+ // 142 empty?
+ // 143 empty?
+ // 144 empty?
+ // 145 empty?
+ // 146 empty?
+ // 147 empty?
+ // 148 empty?
+ // 149 empty?
+ // 150 empty?
+ // 151 empty?
+ // 152 empty?
+ // 153 empty?
+ // 154 empty?
+ // 155 empty?
+ // 156 empty?
+ // 157 empty?
+ // 158 empty?
+ // 159 empty?
+ // 160 empty?
+ // 161 empty?
+ // 162 empty?
+ // 162 empty?
+ // 163 empty?
+ // 164 empty?
+ // 165 empty?
+ // 166 empty?
+ // 167 empty?
+ // 168 empty?
+ // 169 empty?
+ // 170 empty?
+ // 171 empty?
+ // 172 empty?
+ // 173 empty?
+ // 174 empty?
+ // 175 empty?
+ // 176 empty?
+ // 177 empty?
+ // 178 empty?
+ // 179 empty?
+ // 180 empty?
+ // 181 empty?
+ // 182 empty?
+ // 183 empty?
+ // 184 empty?
+ // 185 empty?
+ // 186 empty?
+ // 187 empty?
+ // 188 empty?
+ // 189 empty?
+ // 190 empty?
+ // 191 empty?
+ // 192 empty?
+ // 193 empty?
+ // 194 empty?
+ // 195 empty?
+ // 196 empty?
+ // 197 empty?
+ // 198 empty?
+ // 199 empty?
+ // 200 empty?
+ // 201 empty?
+ // 202 empty?
+ // 203 empty?
+ // 204 empty?
+ // 205 empty?
+ // 206 empty?
+ // 207 empty?
+ // 208 empty?
+ // 209 empty?
+ // 210 empty?
+ // 211 empty?
+ // 212 empty?
+ // 213 empty?
+ // 214 empty?
+ // 215 empty?
+ // 216 empty?
+ // 217 empty?
+ // 218 empty?
+ // 219 empty?
+ const float STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR     = 220;
+ const float STAT_MOVEVARS_AIRCONTROL_PENALTY            = 221;
+ const float STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW           = 222;
+ const float STAT_MOVEVARS_AIRSTRAFEACCEL_QW             = 223;
+ const float STAT_MOVEVARS_AIRCONTROL_POWER              = 224;
+ const float STAT_MOVEFLAGS                              = 225;
+ const float STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL   = 226;
+ const float STAT_MOVEVARS_WARSOWBUNNY_ACCEL             = 227;
+ const float STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED          = 228;
+ const float STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL         = 229;
+ const float STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO   = 230;
+ const float STAT_MOVEVARS_AIRSTOPACCELERATE             = 231;
+ const float STAT_MOVEVARS_AIRSTRAFEACCELERATE           = 232;
+ const float STAT_MOVEVARS_MAXAIRSTRAFESPEED             = 233;
+ const float STAT_MOVEVARS_AIRCONTROL                    = 234;
+ const float STAT_FRAGLIMIT                              = 235;
+ const float STAT_TIMELIMIT                              = 236;
+ const float STAT_MOVEVARS_WALLFRICTION                  = 237;
+ const float STAT_MOVEVARS_FRICTION                      = 238;
+ const float STAT_MOVEVARS_WATERFRICTION                 = 239;
+ const float STAT_MOVEVARS_TICRATE                       = 240;
+ const float STAT_MOVEVARS_TIMESCALE                     = 241;
+ const float STAT_MOVEVARS_GRAVITY                       = 242;
+ const float STAT_MOVEVARS_STOPSPEED                     = 243;
+ const float STAT_MOVEVARS_MAXSPEED                      = 244;
+ const float STAT_MOVEVARS_SPECTATORMAXSPEED             = 245;
+ const float STAT_MOVEVARS_ACCELERATE                    = 246;
+ const float STAT_MOVEVARS_AIRACCELERATE                 = 247;
+ const float STAT_MOVEVARS_WATERACCELERATE               = 248;
+ const float STAT_MOVEVARS_ENTGRAVITY                    = 249;
+ const float STAT_MOVEVARS_JUMPVELOCITY                  = 250;
+ const float STAT_MOVEVARS_EDGEFRICTION                  = 251;
+ const float STAT_MOVEVARS_MAXAIRSPEED                   = 252;
+ const float STAT_MOVEVARS_STEPHEIGHT                    = 253;
+ const float STAT_MOVEVARS_AIRACCEL_QW                   = 254;
+ const float STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION    = 255;
Simple merge
index e01d30b,0000000..c666619
mode 100644,000000..100644
--- /dev/null
@@@ -1,1427 -1,0 +1,1427 @@@
-               self.owner.freezetag_frozen
 +#ifdef REGISTER_WEAPON
 +REGISTER_WEAPON(
 +/* WEP_##id  */ ARC,
 +/* function  */ W_Arc,
 +/* ammotype  */ ammo_cells,
 +/* impulse   */ 3,
 +/* flags     */ WEP_FLAG_NORMAL,
 +/* rating    */ BOT_PICKUP_RATING_HIGH,
 +/* color     */ '1 1 1',
 +/* modelname */ "arc",
 +/* simplemdl */ "foobar",
 +/* crosshair */ "gfx/crosshairhlac 0.7",
 +/* wepimg    */ "weaponhlac",
 +/* refname   */ "arc",
 +/* wepname   */ _("Arc")
 +);
 +
 +#define ARC_SETTINGS(w_cvar,w_prop) ARC_SETTINGS_LIST(w_cvar, w_prop, ARC, arc)
 +#define ARC_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
 +      w_cvar(id, sn, NONE, beam_ammo) \
 +      w_cvar(id, sn, NONE, beam_animtime) \
 +      w_cvar(id, sn, NONE, beam_botaimspeed) \
 +      w_cvar(id, sn, NONE, beam_botaimlifetime) \
 +      w_cvar(id, sn, NONE, beam_damage) \
 +      w_cvar(id, sn, NONE, beam_degreespersegment) \
 +      w_cvar(id, sn, NONE, beam_distancepersegment) \
 +      w_cvar(id, sn, NONE, beam_falloff_halflifedist) \
 +      w_cvar(id, sn, NONE, beam_falloff_maxdist) \
 +      w_cvar(id, sn, NONE, beam_falloff_mindist) \
 +      w_cvar(id, sn, NONE, beam_force) \
 +      w_cvar(id, sn, NONE, beam_healing_amax) \
 +      w_cvar(id, sn, NONE, beam_healing_aps) \
 +      w_cvar(id, sn, NONE, beam_healing_hmax) \
 +      w_cvar(id, sn, NONE, beam_healing_hps) \
 +      w_cvar(id, sn, NONE, beam_maxangle) \
 +      w_cvar(id, sn, NONE, beam_nonplayerdamage) \
 +      w_cvar(id, sn, NONE, beam_range) \
 +      w_cvar(id, sn, NONE, beam_refire) \
 +      w_cvar(id, sn, NONE, beam_returnspeed) \
 +      w_cvar(id, sn, NONE, beam_tightness) \
 +      w_cvar(id, sn, NONE, burst_ammo) \
 +      w_cvar(id, sn, NONE, burst_damage) \
 +      w_cvar(id, sn, NONE, burst_healing_aps) \
 +      w_cvar(id, sn, NONE, burst_healing_hps) \
 +      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)
 +
 +#ifndef MENUQC
 +#define ARC_MAX_SEGMENTS 20
 +vector arc_shotorigin[4];
 +.vector beam_start;
 +.vector beam_dir;
 +.vector beam_wantdir;
 +.float beam_type;
 +
 +#define ARC_BT_MISS        0
 +#define ARC_BT_WALL        1
 +#define ARC_BT_HEAL        2
 +#define ARC_BT_HIT         3
 +#define ARC_BT_BURST_MISS  10
 +#define ARC_BT_BURST_WALL  11
 +#define ARC_BT_BURST_HEAL  12
 +#define ARC_BT_BURST_HIT   13
 +#define ARC_BT_BURSTMASK   10
 +
 +#define ARC_SF_SETTINGS    1
 +#define ARC_SF_START       2
 +#define ARC_SF_WANTDIR     4
 +#define ARC_SF_BEAMDIR     8
 +#define ARC_SF_BEAMTYPE    16
 +#define ARC_SF_LOCALMASK   14
 +#endif
 +#ifdef SVQC
 +ARC_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
 +.entity arc_beam;
 +.float BUTTON_ATCK_prev; // for better animation control
 +.float beam_prev;
 +.float beam_initialized;
 +.float beam_bursting;
 +.float beam_teleporttime;
 +#endif
 +#ifdef CSQC
 +void Ent_ReadArcBeam(float isnew);
 +
 +.vector beam_color;
 +.float beam_alpha;
 +.float beam_thickness;
 +.float beam_traileffect;
 +.float beam_hiteffect;
 +.float beam_hitlight[4]; // 0: radius, 123: rgb
 +.float beam_muzzleeffect;
 +.float beam_muzzlelight[4]; // 0: radius, 123: rgb
 +.string beam_image;
 +
 +.entity beam_muzzleentity;
 +
 +.float beam_degreespersegment;
 +.float beam_distancepersegment;
 +.float beam_usevieworigin;
 +.float beam_initialized;
 +.float beam_maxangle;
 +.float beam_range;
 +.float beam_returnspeed;
 +.float beam_tightness;
 +.vector beam_shotorigin;
 +
 +entity Draw_ArcBeam_callback_entity;
 +float Draw_ArcBeam_callback_last_thickness;
 +vector Draw_ArcBeam_callback_last_top; // NOTE: in same coordinate system as player.
 +vector Draw_ArcBeam_callback_last_bottom; // NOTE: in same coordinate system as player.
 +#endif
 +#else
 +#ifdef SVQC
 +void spawnfunc_weapon_arc(void) { weapon_defaultspawnfunc(WEP_ARC); }
 +
 +float W_Arc_Beam_Send(entity to, float sf)
 +{
 +      WriteByte(MSG_ENTITY, ENT_CLIENT_ARC_BEAM);
 +
 +      // Truncate information when this beam is displayed to the owner client
 +      // - The owner client has no use for beam start position or directions,
 +      //    it always figures this information out for itself with csqc code.
 +      // - Spectating the owner also truncates this information.
 +      float drawlocal = ((to == self.owner) || ((to.enemy == self.owner) && IS_SPEC(to)));
 +      if(drawlocal) { sf &= ~ARC_SF_LOCALMASK; }
 +
 +      WriteByte(MSG_ENTITY, sf);
 +
 +      if(sf & ARC_SF_SETTINGS) // settings information
 +      {
 +              WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_degreespersegment));
 +              WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_distancepersegment));
 +              WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_maxangle));
 +              WriteCoord(MSG_ENTITY, WEP_CVAR(arc, beam_range));
 +              WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_returnspeed));
 +              WriteByte(MSG_ENTITY, WEP_CVAR(arc, beam_tightness) * 10);
 +
 +              WriteByte(MSG_ENTITY, drawlocal);
 +      }
 +      if(sf & ARC_SF_START) // starting location
 +      {
 +              WriteCoord(MSG_ENTITY, self.beam_start_x);
 +              WriteCoord(MSG_ENTITY, self.beam_start_y);
 +              WriteCoord(MSG_ENTITY, self.beam_start_z);
 +      }
 +      if(sf & ARC_SF_WANTDIR) // want/aim direction
 +      {
 +              WriteCoord(MSG_ENTITY, self.beam_wantdir_x);
 +              WriteCoord(MSG_ENTITY, self.beam_wantdir_y);
 +              WriteCoord(MSG_ENTITY, self.beam_wantdir_z);
 +      }
 +      if(sf & ARC_SF_BEAMDIR) // beam direction
 +      {
 +              WriteCoord(MSG_ENTITY, self.beam_dir_x);
 +              WriteCoord(MSG_ENTITY, self.beam_dir_y);
 +              WriteCoord(MSG_ENTITY, self.beam_dir_z);
 +      }
 +      if(sf & ARC_SF_BEAMTYPE) // beam type
 +      {
 +              WriteByte(MSG_ENTITY, self.beam_type);
 +      }
 +
 +      return TRUE;
 +}
 +
 +void Reset_ArcBeam(entity player, vector forward)
 +{
 +      if (!player.arc_beam) {
 +              return;
 +      }
 +      player.arc_beam.beam_dir = forward;
 +      player.arc_beam.beam_teleporttime = time;
 +}
 +
 +void W_Arc_Beam_Think(void)
 +{
 +      if(self != self.owner.arc_beam)
 +      {
 +              remove(self);
 +              return;
 +      }
 +
 +      if(
 +              !IS_PLAYER(self.owner)
 +              ||
 +              (self.owner.WEP_AMMO(ARC) <= 0 && !(self.owner.items & IT_UNLIMITED_WEAPON_AMMO))
 +              ||
 +              self.owner.deadflag != DEAD_NO
 +              ||
 +              (!self.owner.BUTTON_ATCK /* FIXME(Samual): && !self.beam_bursting */)
 +              ||
++              self.owner.frozen
 +      )
 +      {
 +              if(self == self.owner.arc_beam) { self.owner.arc_beam = world; }
 +              entity oldself = self;
 +              self = self.owner;
 +              if(!WEP_ACTION(WEP_ARC, WR_CHECKAMMO1) && !WEP_ACTION(WEP_ARC, WR_CHECKAMMO2))
 +              {
 +                      // note: this doesn't force the switch
 +                      W_SwitchToOtherWeapon(self);
 +              }
 +              self = oldself;
 +              remove(self);
 +              return;
 +      }
 +
 +      float burst = 0;
 +      if(/*self.owner.BUTTON_ATCK2 || */self.beam_bursting)
 +      {
 +              if(!self.beam_bursting)
 +                      self.beam_bursting = TRUE;
 +              burst = ARC_BT_BURSTMASK;
 +      }
 +
 +      // decrease ammo
 +      float coefficient = frametime;
 +      if(!(self.owner.items & IT_UNLIMITED_WEAPON_AMMO))
 +      {
 +              float rootammo;
 +              if(burst)
 +                      { rootammo = WEP_CVAR(arc, burst_ammo); }
 +              else
 +                      { rootammo = WEP_CVAR(arc, beam_ammo); }
 +
 +              if(rootammo)
 +              {
 +                      coefficient = min(coefficient, self.owner.WEP_AMMO(ARC) / rootammo);
 +                      self.owner.WEP_AMMO(ARC) = max(0, self.owner.WEP_AMMO(ARC) - (rootammo * frametime));
 +              }
 +      }
 +
 +      makevectors(self.owner.v_angle);
 +
 +      W_SetupShot_Range(
 +              self.owner,
 +              TRUE,
 +              0,
 +              "",
 +              0,
 +              WEP_CVAR(arc, beam_damage) * coefficient,
 +              WEP_CVAR(arc, beam_range)
 +      );
 +
 +      // After teleport, "lock" the beam until the teleport is confirmed.
 +      if (time < self.beam_teleporttime + ANTILAG_LATENCY(self.owner)) {
 +              w_shotdir = self.beam_dir;
 +      }
 +
 +      // network information: shot origin and want/aim direction
 +      if(self.beam_start != w_shotorg)
 +      {
 +              self.SendFlags |= ARC_SF_START;
 +              self.beam_start = w_shotorg;
 +      }
 +      if(self.beam_wantdir != w_shotdir)
 +      {
 +              self.SendFlags |= ARC_SF_WANTDIR;
 +              self.beam_wantdir = w_shotdir;
 +      }
 +
 +      if(!self.beam_initialized)
 +      {
 +              self.beam_dir = w_shotdir;
 +              self.beam_initialized = TRUE;
 +      }
 +
 +      // WEAPONTODO: Detect player velocity so that the beam curves when moving too
 +      // idea: blend together self.beam_dir with the inverted direction the player is moving in
 +      // might have to make some special accomodation so that it only uses view_right and view_up
 +
 +      // note that if we do this, it'll always be corrected to a maximum angle by beam_maxangle handling
 +
 +      float segments; 
 +      if(self.beam_dir != w_shotdir)
 +      {
 +              // calculate how much we're going to move the end of the beam to the want position
 +              // WEAPONTODO (server and client):
 +              // blendfactor never actually becomes 0 in this situation, which is a problem
 +              // regarding precision... this means that self.beam_dir and w_shotdir approach
 +              // eachother, however they never actually become the same value with this method.
 +              // Perhaps we should do some form of rounding/snapping?
 +              float angle = vlen(w_shotdir - self.beam_dir) * RAD2DEG;
 +              if(angle && (angle > WEP_CVAR(arc, beam_maxangle)))
 +              {
 +                      // if the angle is greater than maxangle, force the blendfactor to make this the maximum factor
 +                      float blendfactor = bound(
 +                              0,
 +                              (1 - (WEP_CVAR(arc, beam_returnspeed) * frametime)),
 +                              min(WEP_CVAR(arc, beam_maxangle) / angle, 1)
 +                      );
 +                      self.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (self.beam_dir * blendfactor));
 +              }
 +              else
 +              {
 +                      // the radius is not too far yet, no worries :D
 +                      float blendfactor = bound(
 +                              0,
 +                              (1 - (WEP_CVAR(arc, beam_returnspeed) * frametime)),
 +                              1
 +                      );
 +                      self.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (self.beam_dir * blendfactor));
 +              }
 +
 +              // network information: beam direction
 +              self.SendFlags |= ARC_SF_BEAMDIR;
 +
 +              // calculate how many segments are needed
 +              float max_allowed_segments;
 +
 +              if(WEP_CVAR(arc, beam_distancepersegment))
 +              {
 +                      max_allowed_segments = min(
 +                              ARC_MAX_SEGMENTS,
 +                              1 + (vlen(w_shotdir / WEP_CVAR(arc, beam_distancepersegment)))
 +                      );
 +              }
 +              else { max_allowed_segments = ARC_MAX_SEGMENTS; }
 +
 +              if(WEP_CVAR(arc, beam_degreespersegment))
 +              {
 +                      segments = bound(
 +                              1, 
 +                              (
 +                                      min(
 +                                              angle,
 +                                              WEP_CVAR(arc, beam_maxangle)
 +                                      )
 +                                      /
 +                                      WEP_CVAR(arc, beam_degreespersegment)
 +                              ),
 +                              max_allowed_segments
 +                      );
 +              }
 +              else { segments = 1; }
 +      }
 +      else { segments = 1; }
 +
 +      vector beam_endpos = (w_shotorg + (self.beam_dir * WEP_CVAR(arc, beam_range)));
 +      vector beam_controlpoint = w_shotorg + w_shotdir * (WEP_CVAR(arc, beam_range) * (1 - WEP_CVAR(arc, beam_tightness)));
 +
 +      float i;
 +      float new_beam_type = 0;
 +      vector last_origin = w_shotorg;
 +      for(i = 1; i <= segments; ++i)
 +      {
 +              // WEAPONTODO (client):
 +              // In order to do nice fading and pointing on the starting segment, we must always
 +              // have that drawn as a separate triangle... However, that is difficult to do when
 +              // keeping in mind the above problems and also optimizing the amount of segments
 +              // drawn on screen at any given time. (Automatic beam quality scaling, essentially)
 +
 +              vector new_origin = bezier_quadratic_getpoint(
 +                      w_shotorg,
 +                      beam_controlpoint,
 +                      beam_endpos,
 +                      i / segments);
 +              vector new_dir = normalize(new_origin - last_origin);
 +
 +              WarpZone_traceline_antilag(
 +                      self.owner,
 +                      last_origin,
 +                      new_origin,
 +                      MOVE_NORMAL,
 +                      self.owner,
 +                      ANTILAG_LATENCY(self.owner)
 +              );
 +
 +              // Do all the transforms for warpzones right now, as we already
 +              // "are" in the post-trace system (if we hit a player, that's
 +              // always BEHIND the last passed wz).
 +              last_origin = trace_endpos;
 +              w_shotorg = WarpZone_TransformOrigin(WarpZone_trace_transform, w_shotorg);
 +              beam_controlpoint = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_controlpoint);
 +              beam_endpos = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_endpos);
 +              new_dir = WarpZone_TransformVelocity(WarpZone_trace_transform, new_dir);
 +
 +              float is_player = (
 +                      trace_ent.classname == "player"
 +                      ||
 +                      trace_ent.classname == "body"
 +                      ||
 +                      (trace_ent.flags & FL_MONSTER)
 +              );
 +
 +              if(trace_ent && trace_ent.takedamage && (is_player || WEP_CVAR(arc, beam_nonplayerdamage)))
 +              {
 +                      // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?)
 +                      // NO. trace_endpos should be just fine. If not,
 +                      // that's an engine bug that needs proper debugging.
 +                      vector hitorigin = trace_endpos;
 +
 +                      float falloff = ExponentialFalloff(
 +                              WEP_CVAR(arc, beam_falloff_mindist),
 +                              WEP_CVAR(arc, beam_falloff_maxdist),
 +                              WEP_CVAR(arc, beam_falloff_halflifedist),
 +                              vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, hitorigin) - w_shotorg)
 +                      );
 +
 +                      if(is_player && SAME_TEAM(self.owner, trace_ent))
 +                      {
 +                              float roothealth, rootarmor;
 +                              if(burst)
 +                              {
 +                                      roothealth = WEP_CVAR(arc, burst_healing_hps);
 +                                      rootarmor = WEP_CVAR(arc, burst_healing_aps);
 +                              }
 +                              else
 +                              {
 +                                      roothealth = WEP_CVAR(arc, beam_healing_hps);
 +                                      rootarmor = WEP_CVAR(arc, beam_healing_aps);
 +                              }
 +
 +                              if(trace_ent.health <= WEP_CVAR(arc, beam_healing_hmax) && roothealth)
 +                              {
 +                                      trace_ent.health = min(
 +                                              trace_ent.health + (roothealth * coefficient),
 +                                              WEP_CVAR(arc, beam_healing_hmax)
 +                                      );
 +                              }
 +                              if(trace_ent.armorvalue <= WEP_CVAR(arc, beam_healing_amax) && rootarmor)
 +                              {
 +                                      trace_ent.armorvalue = min(
 +                                              trace_ent.armorvalue + (rootarmor * coefficient),
 +                                              WEP_CVAR(arc, beam_healing_amax)
 +                                      );
 +                              }
 +
 +                              // stop rot, set visual effect
 +                              if(roothealth || rootarmor)
 +                              {
 +                                      trace_ent.pauserothealth_finished = max(
 +                                              trace_ent.pauserothealth_finished,
 +                                              time + autocvar_g_balance_pause_health_rot
 +                                      );
 +                                      trace_ent.pauserotarmor_finished = max(
 +                                              trace_ent.pauserotarmor_finished,
 +                                              time + autocvar_g_balance_pause_armor_rot
 +                                      );
 +                                      new_beam_type = ARC_BT_HEAL;
 +                              }
 +                      }
 +                      else
 +                      {
 +                              float rootdamage;
 +                              if(is_player)
 +                              {
 +                                      if(burst)
 +                                              { rootdamage = WEP_CVAR(arc, burst_damage); }
 +                                      else
 +                                              { rootdamage = WEP_CVAR(arc, beam_damage); }
 +                              }
 +                              else
 +                                      { rootdamage = WEP_CVAR(arc, beam_nonplayerdamage); }
 +
 +                              if(accuracy_isgooddamage(self.owner, trace_ent))
 +                              {
 +                                      accuracy_add(
 +                                              self.owner,
 +                                              WEP_ARC,
 +                                              0,
 +                                              rootdamage * coefficient * falloff
 +                                      );
 +                              }
 +
 +                              Damage(
 +                                      trace_ent,
 +                                      self.owner,
 +                                      self.owner,
 +                                      rootdamage * coefficient * falloff,
 +                                      WEP_ARC,
 +                                      hitorigin,
 +                                      WEP_CVAR(arc, beam_force) * new_dir * coefficient * falloff
 +                              );
 +
 +                              new_beam_type = ARC_BT_HIT;
 +                      }
 +                      break; 
 +              }
 +              else if(trace_fraction != 1)
 +              {
 +                      // we collided with geometry
 +                      new_beam_type = ARC_BT_WALL;
 +                      break;
 +              }
 +      }
 +
 +      // te_explosion(trace_endpos);
 +
 +      // if we're bursting, use burst visual effects
 +      new_beam_type += burst;
 +
 +      // network information: beam type
 +      if(new_beam_type != self.beam_type)
 +      {
 +              self.SendFlags |= ARC_SF_BEAMTYPE;
 +              self.beam_type = new_beam_type;
 +      }
 +
 +      self.owner.beam_prev = time;
 +      self.nextthink = time;
 +}
 +
 +void W_Arc_Beam(float burst)
 +{
 +      // FIXME(Samual): remove this when overheat and burst work.
 +      if (burst)
 +      {
 +              centerprint(self, "^4NOTE:^7 Arc burst (secondary) is not implemented yet.");
 +      }
 +
 +      // only play fire sound if 1 sec has passed since player let go the fire button
 +      if(time - self.beam_prev > 1)
 +      {
 +              sound(self, CH_WEAPON_A, "weapons/lgbeam_fire.wav", VOL_BASE, ATTN_NORM);
 +      }
 +
 +      entity beam = self.arc_beam = spawn();
 +      beam.classname = "W_Arc_Beam";
 +      beam.solid = SOLID_NOT;
 +      beam.think = W_Arc_Beam_Think;
 +      beam.owner = self;
 +      beam.movetype = MOVETYPE_NONE;
 +      beam.bot_dodge = TRUE;
 +      beam.bot_dodgerating = WEP_CVAR(arc, beam_damage);
 +      beam.beam_bursting = burst;
 +      Net_LinkEntity(beam, FALSE, 0, W_Arc_Beam_Send);
 +
 +      entity oldself = self;
 +      self = beam;
 +      self.think();
 +      self = oldself;
 +}
 +
 +float W_Arc(float req)
 +{
 +      switch(req)
 +      {
 +              case WR_AIM:
 +              {
 +                      if(WEP_CVAR(arc, beam_botaimspeed))
 +                      {
 +                              self.BUTTON_ATCK = bot_aim(
 +                                      WEP_CVAR(arc, beam_botaimspeed),
 +                                      0,
 +                                      WEP_CVAR(arc, beam_botaimlifetime),
 +                                      FALSE
 +                              );
 +                      }
 +                      else
 +                      {
 +                              self.BUTTON_ATCK = bot_aim(
 +                                      1000000,
 +                                      0,
 +                                      0.001,
 +                                      FALSE
 +                              );
 +                      }
 +                      return TRUE;
 +              }
 +              case WR_THINK:
 +              {
 +                      #if 0
 +                      if(self.arc_beam.beam_heat > threshold)
 +                      {
 +                              stop the beam somehow
 +                              play overheat animation
 +                      }
 +                      #endif
 +
 +                      if(self.BUTTON_ATCK || self.BUTTON_ATCK2 /* FIXME(Samual): || self.arc_beam.beam_bursting */)
 +                      {
 +                              if(self.BUTTON_ATCK_prev)
 +                              {
 +                                      #if 0
 +                                      if(self.animstate_startframe == self.anim_shoot_x && self.animstate_numframes == self.anim_shoot_y)
 +                                              weapon_thinkf(WFRAME_DONTCHANGE, autocvar_g_balance_arc_primary_animtime, w_ready);
 +                                      else
 +                                      #endif
 +                                              weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
 +                              }
 +
 +                              if((!self.arc_beam) || wasfreed(self.arc_beam))
 +                              {
 +                                      if(weapon_prepareattack(!!self.BUTTON_ATCK2, 0))
 +                                      {
 +                                              W_Arc_Beam(!!self.BUTTON_ATCK2);
 +                                              
 +                                              if(!self.BUTTON_ATCK_prev)
 +                                              {
 +                                                      weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
 +                                                      self.BUTTON_ATCK_prev = 1;
 +                                              }
 +                                      }
 +                              }
 +                      } 
 +                      else // todo
 +                      {
 +                              if(self.BUTTON_ATCK_prev != 0)
 +                              {
 +                                      weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
 +                                      ATTACK_FINISHED(self) = time + WEP_CVAR(arc, beam_refire) * W_WeaponRateFactor();
 +                              }
 +                              self.BUTTON_ATCK_prev = 0;
 +                      }
 +
 +                      #if 0
 +                      if(self.BUTTON_ATCK2)
 +                      if(weapon_prepareattack(1, autocvar_g_balance_arc_secondary_refire))
 +                      {
 +                              W_Arc_Attack2();
 +                              self.arc_count = autocvar_g_balance_arc_secondary_count;
 +                              weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_arc_secondary_animtime, w_arc_checkattack);
 +                              self.arc_secondarytime = time + autocvar_g_balance_arc_secondary_refire2 * W_WeaponRateFactor();
 +                      }
 +                      #endif
 +
 +                      return TRUE;
 +              }
 +              case WR_INIT:
 +              {
 +                      precache_model("models/weapons/g_arc.md3");
 +                      precache_model("models/weapons/v_arc.md3");
 +                      precache_model("models/weapons/h_arc.iqm");
 +                      precache_sound("weapons/lgbeam_fire.wav");
 +                      if(!arc_shotorigin[0])
 +                      {
 +                              arc_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 1);
 +                              arc_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 2);
 +                              arc_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 3);
 +                              arc_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 4);
 +                      }
 +                      ARC_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP)
 +                      return TRUE;
 +              }
 +              case WR_CHECKAMMO1:
 +              {
 +                      return ((!WEP_CVAR(arc, beam_ammo)) || (self.WEP_AMMO(ARC) > 0));
 +              }
 +              case WR_CHECKAMMO2:
 +              {
 +                      // arc currently has no secondary attack
 +                      return FALSE;
 +                      //return ((!WEP_CVAR(arc, burst_ammo)) || (self.WEP_AMMO(ARC) > 0));
 +              }
 +              case WR_CONFIG:
 +              {
 +                      ARC_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS)
 +                      return TRUE;
 +              }
 +              case WR_KILLMESSAGE:
 +              {
 +                      return WEAPON_ARC_MURDER;
 +              }
 +      }
 +      return FALSE;
 +}
 +#endif
 +#ifdef CSQC
 +void Draw_ArcBeam_callback(vector start, vector hit, vector end)
 +{
 +      entity beam = Draw_ArcBeam_callback_entity;
 +      vector transformed_view_org;
 +      transformed_view_org = WarpZone_TransformOrigin(WarpZone_trace_transform, view_origin);
 +
 +      // Thickdir shall be perpendicular to the beam and to the view-to-beam direction (WEAPONTODO: WHY)
 +      // WEAPONTODO: Wouldn't it be better to be perpendicular to the beam and to the view FORWARD direction?
 +      vector thickdir = normalize(cross(normalize(start - hit), transformed_view_org - start));
 +
 +      vector hitorigin;
 +
 +      // draw segment
 +      #if 0
 +      if(trace_fraction != 1)
 +      {
 +              // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?)
 +              hitorigin = start + (Draw_ArcBeam_callback_new_dir * Draw_ArcBeam_callback_segmentdist * trace_fraction);
 +              hitorigin = WarpZone_TransformOrigin(WarpZone_trace_transform, hitorigin);
 +      }
 +      else
 +      {
 +              hitorigin = hit;
 +      }
 +      #else
 +      hitorigin = hit;
 +      #endif
 +
 +      // decide upon thickness
 +      float thickness = beam.beam_thickness;
 +
 +      // draw primary beam render
 +      vector top    = hitorigin + (thickdir * thickness);
 +      vector bottom = hitorigin - (thickdir * thickness);
 +      
 +      vector last_top = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_top);
 +      vector last_bottom = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_bottom);
 +
 +      R_BeginPolygon(beam.beam_image, DRAWFLAG_NORMAL); // DRAWFLAG_ADDITIVE
 +      R_PolygonVertex(
 +              top,
 +              '0 0.5 0' + ('0 0.5 0' * (thickness / beam.beam_thickness)),
 +              beam.beam_color,
 +              beam.beam_alpha
 +      );
 +      R_PolygonVertex(
 +              last_top,
 +              '0 0.5 0' + ('0 0.5 0' * (Draw_ArcBeam_callback_last_thickness / beam.beam_thickness)),
 +              beam.beam_color,
 +              beam.beam_alpha
 +      );
 +      R_PolygonVertex(
 +              last_bottom,
 +              '0 0.5 0' * (1 - (Draw_ArcBeam_callback_last_thickness / beam.beam_thickness)),
 +              beam.beam_color,
 +              beam.beam_alpha
 +      );
 +      R_PolygonVertex(
 +              bottom,
 +              '0 0.5 0' * (1 - (thickness / beam.beam_thickness)),
 +              beam.beam_color,
 +              beam.beam_alpha
 +      );
 +      R_EndPolygon();
 +
 +      // draw trailing particles
 +      // NOTES:
 +      //  - Don't use spammy particle counts here, use a FEW small particles around the beam
 +      //  - We're not using WarpZone_TrailParticles here because we will handle warpzones ourselves.
 +      if(beam.beam_traileffect)
 +      {
 +              trailparticles(beam, beam.beam_traileffect, start, hitorigin);
 +      }
 +
 +      // set up for the next 
 +      Draw_ArcBeam_callback_last_thickness = thickness;
 +      Draw_ArcBeam_callback_last_top = WarpZone_UnTransformOrigin(WarpZone_trace_transform, top);
 +      Draw_ArcBeam_callback_last_bottom = WarpZone_UnTransformOrigin(WarpZone_trace_transform, bottom);
 +}
 +
 +void Reset_ArcBeam(void)
 +{
 +      entity e;
 +      for (e = world; (e = findfloat(e, beam_usevieworigin, 1)); ) {
 +              e.beam_initialized = FALSE;
 +      }
 +      for (e = world; (e = findfloat(e, beam_usevieworigin, 2)); ) {
 +              e.beam_initialized = FALSE;
 +      }
 +}
 +
 +void Draw_ArcBeam(void)
 +{
 +      if(!self.beam_usevieworigin)
 +      {
 +              InterpolateOrigin_Do();
 +      }
 +
 +      // origin = beam starting origin
 +      // v_angle = wanted/aim direction
 +      // angles = current direction of beam
 +
 +      vector start_pos;
 +      vector wantdir; //= view_forward;
 +      vector beamdir; //= self.beam_dir;
 +
 +      float segments;
 +      if(self.beam_usevieworigin)
 +      {
 +              // WEAPONTODO:
 +              // Currently we have to replicate nearly the same method of figuring
 +              // out the shotdir that the server does... Ideally in the future we
 +              // should be able to acquire this from a generalized function built
 +              // into a weapon system for client code. 
 +
 +              // find where we are aiming
 +              makevectors(warpzone_save_view_angles);
 +              vector forward = v_forward;
 +              vector right = v_right;
 +              vector up = v_up;
 +
 +              // decide upon start position
 +              if(self.beam_usevieworigin == 2)
 +                      { start_pos = warpzone_save_view_origin; }
 +              else
 +                      { start_pos = self.origin; }
 +
 +              // trace forward with an estimation
 +              WarpZone_TraceLine(
 +                      start_pos,
 +                      start_pos + forward * self.beam_range,
 +                      MOVE_NOMONSTERS,
 +                      self
 +              );
 +
 +              // untransform in case our trace went through a warpzone
 +              vector end_pos = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos);
 +
 +              // un-adjust trueaim if shotend is too close
 +              if(vlen(end_pos - start_pos) < g_trueaim_minrange)
 +                      end_pos = start_pos + (forward * g_trueaim_minrange);
 +
 +              // move shot origin to the actual gun muzzle origin
 +              vector origin_offset =
 +                        right * -self.beam_shotorigin_y 
 +                      + up * self.beam_shotorigin_z;
 +
 +              start_pos = start_pos + origin_offset;
 +
 +              // Move it also forward, but only as far as possible without hitting anything. Don't poke into walls!
 +              traceline(start_pos, start_pos + forward * self.beam_shotorigin_x, MOVE_NORMAL, self);
 +              start_pos = trace_endpos;
 +
 +              // calculate the aim direction now
 +              wantdir = normalize(end_pos - start_pos);
 +
 +              if(!self.beam_initialized)
 +              {
 +                      self.beam_dir = wantdir;
 +                      self.beam_initialized = TRUE;
 +              }
 +
 +              if(self.beam_dir != wantdir)
 +              {
 +                      // calculate how much we're going to move the end of the beam to the want position
 +                      // WEAPONTODO (server and client):
 +                      // blendfactor never actually becomes 0 in this situation, which is a problem
 +                      // regarding precision... this means that self.beam_dir and w_shotdir approach
 +                      // eachother, however they never actually become the same value with this method.
 +                      // Perhaps we should do some form of rounding/snapping?
 +                      float angle = vlen(wantdir - self.beam_dir) * RAD2DEG;
 +                      if(angle && (angle > self.beam_maxangle))
 +                      {
 +                              // if the angle is greater than maxangle, force the blendfactor to make this the maximum factor
 +                              float blendfactor = bound(
 +                                      0,
 +                                      (1 - (self.beam_returnspeed * frametime)),
 +                                      min(self.beam_maxangle / angle, 1)
 +                              );
 +                              self.beam_dir = normalize((wantdir * (1 - blendfactor)) + (self.beam_dir * blendfactor));
 +                      }
 +                      else
 +                      {
 +                              // the radius is not too far yet, no worries :D
 +                              float blendfactor = bound(
 +                                      0,
 +                                      (1 - (self.beam_returnspeed * frametime)),
 +                                      1
 +                              );
 +                              self.beam_dir = normalize((wantdir * (1 - blendfactor)) + (self.beam_dir * blendfactor));
 +                      }
 +
 +                      // calculate how many segments are needed
 +                      float max_allowed_segments;
 +
 +                      if(self.beam_distancepersegment)
 +                      {
 +                              max_allowed_segments = min(
 +                                      ARC_MAX_SEGMENTS,
 +                                      1 + (vlen(wantdir / self.beam_distancepersegment))
 +                              );
 +                      }
 +                      else { max_allowed_segments = ARC_MAX_SEGMENTS; }
 +
 +                      if(self.beam_degreespersegment)
 +                      {
 +                              segments = bound(
 +                                      1, 
 +                                      (
 +                                              min(
 +                                                      angle,
 +                                                      self.beam_maxangle
 +                                              )
 +                                              /
 +                                              self.beam_degreespersegment
 +                                      ),
 +                                      max_allowed_segments
 +                              );
 +                      }
 +                      else { segments = 1; }
 +              }
 +              else { segments = 1; }
 +
 +              // set the beam direction which the rest of the code will refer to
 +              beamdir = self.beam_dir;
 +
 +              // finally, set self.angles to the proper direction so that muzzle attachment points in proper direction
 +              self.angles = fixedvectoangles2(forward, up); // TODO(Samual): is this == warpzone_save_view_angles?
 +      }
 +      else
 +      {
 +              // set the values from the provided info from the networked entity
 +              start_pos = self.origin;
 +              wantdir = self.v_angle;
 +              beamdir = self.angles;
 +
 +              if(beamdir != wantdir)
 +              {
 +                      float angle = vlen(wantdir - beamdir) * RAD2DEG;
 +
 +                      // calculate how many segments are needed
 +                      float max_allowed_segments;
 +
 +                      if(self.beam_distancepersegment)
 +                      {
 +                              max_allowed_segments = min(
 +                                      ARC_MAX_SEGMENTS,
 +                                      1 + (vlen(wantdir / self.beam_distancepersegment))
 +                              );
 +                      }
 +                      else { max_allowed_segments = ARC_MAX_SEGMENTS; }
 +
 +                      if(self.beam_degreespersegment)
 +                      {
 +                              segments = bound(
 +                                      1, 
 +                                      (
 +                                              min(
 +                                                      angle,
 +                                                      self.beam_maxangle
 +                                              )
 +                                              /
 +                                              self.beam_degreespersegment
 +                                      ),
 +                                      max_allowed_segments
 +                              );
 +                      }
 +                      else { segments = 1; }
 +              }
 +              else { segments = 1; }
 +      }
 +
 +      setorigin(self, start_pos);
 +      self.beam_muzzleentity.angles_z = random() * 360; // WEAPONTODO: use avelocity instead?
 +
 +      vector beam_endpos = (start_pos + (beamdir * self.beam_range));
 +      vector beam_controlpoint = start_pos + wantdir * (self.beam_range * (1 - self.beam_tightness));
 +
 +      Draw_ArcBeam_callback_entity = self;
 +      Draw_ArcBeam_callback_last_thickness = 0;
 +      Draw_ArcBeam_callback_last_top = start_pos;
 +      Draw_ArcBeam_callback_last_bottom = start_pos;
 +
 +      vector last_origin = start_pos;
 +      vector original_start_pos = start_pos;
 +
 +      float i;
 +      for(i = 1; i <= segments; ++i)
 +      {
 +              // WEAPONTODO (client):
 +              // In order to do nice fading and pointing on the starting segment, we must always
 +              // have that drawn as a separate triangle... However, that is difficult to do when
 +              // keeping in mind the above problems and also optimizing the amount of segments
 +              // drawn on screen at any given time. (Automatic beam quality scaling, essentially)
 +
 +              vector new_origin = bezier_quadratic_getpoint(
 +                      start_pos,
 +                      beam_controlpoint,
 +                      beam_endpos,
 +                      i / segments);
 +
 +              WarpZone_TraceBox_ThroughZone(
 +                      last_origin,
 +                      '0 0 0',
 +                      '0 0 0',
 +                      new_origin,
 +                      MOVE_NORMAL,
 +                      world,
 +                      world,
 +                      Draw_ArcBeam_callback
 +              );
 +
 +              // Do all the transforms for warpzones right now, as we already "are" in the post-trace
 +              // system (if we hit a player, that's always BEHIND the last passed wz).
 +              last_origin = trace_endpos;
 +              start_pos = WarpZone_TransformOrigin(WarpZone_trace_transform, start_pos);
 +              beam_controlpoint = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_controlpoint);
 +              beam_endpos = WarpZone_TransformOrigin(WarpZone_trace_transform, beam_endpos);
 +              beamdir = WarpZone_TransformVelocity(WarpZone_trace_transform, beamdir);
 +              Draw_ArcBeam_callback_last_top = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_top);
 +              Draw_ArcBeam_callback_last_bottom = WarpZone_TransformOrigin(WarpZone_trace_transform, Draw_ArcBeam_callback_last_bottom);
 +
 +              if(trace_fraction < 1) { break; }
 +      }
 +
 +      // visual effects for startpoint and endpoint
 +      if(self.beam_hiteffect)
 +      {
 +              // FIXME we really should do this on the server so it actually
 +              // matches gameplay. What this client side stuff is doing is no
 +              // more than guesswork.
 +              pointparticles(
 +                      self.beam_hiteffect,
 +                      last_origin,
 +                      beamdir * -1,
 +                      frametime * 2
 +              );
 +      }
 +      if(self.beam_hitlight[0])
 +      {
 +              adddynamiclight(
 +                      last_origin,
 +                      self.beam_hitlight[0],
 +                      vec3(
 +                              self.beam_hitlight[1],
 +                              self.beam_hitlight[2],
 +                              self.beam_hitlight[3]
 +                      )
 +              );
 +      }
 +      if(self.beam_muzzleeffect)
 +      {
 +              pointparticles(
 +                      self.beam_muzzleeffect,
 +                      original_start_pos + wantdir * 20,
 +                      wantdir * 1000,
 +                      frametime * 0.1
 +              );
 +      }
 +      if(self.beam_muzzlelight[0])
 +      {
 +              adddynamiclight(
 +                      original_start_pos + wantdir * 20,
 +                      self.beam_muzzlelight[0],
 +                      vec3(
 +                              self.beam_muzzlelight[1],
 +                              self.beam_muzzlelight[2],
 +                              self.beam_muzzlelight[3]
 +                      )
 +              );
 +      }
 +
 +      // cleanup
 +      Draw_ArcBeam_callback_entity = world;
 +      Draw_ArcBeam_callback_last_thickness = 0;
 +      Draw_ArcBeam_callback_last_top = '0 0 0';
 +      Draw_ArcBeam_callback_last_bottom = '0 0 0';
 +}
 +
 +void Remove_ArcBeam(void)
 +{
 +      remove(self.beam_muzzleentity);
 +      sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTEN_NORM);
 +}
 +
 +void Ent_ReadArcBeam(float isnew)
 +{
 +      float sf = ReadByte();
 +      entity flash;
 +
 +      if(isnew)
 +      {
 +              // calculate shot origin offset from gun alignment
 +              float gunalign = autocvar_cl_gunalign;
 +              if(gunalign != 1 && gunalign != 2 && gunalign != 4)
 +                      gunalign = 3; // default value
 +              --gunalign;
 +
 +              self.beam_shotorigin = arc_shotorigin[gunalign];
 +
 +              // set other main attributes of the beam
 +              self.draw = Draw_ArcBeam;
 +              self.entremove = Remove_ArcBeam;
 +              sound(self, CH_SHOTS_SINGLE, "weapons/lgbeam_fly.wav", VOL_BASE, ATTEN_NORM);
 +
 +              flash = spawn();
 +              flash.owner = self;
 +              flash.effects = EF_ADDITIVE | EF_FULLBRIGHT;
 +              flash.drawmask = MASK_NORMAL;
 +              flash.solid = SOLID_NOT;
 +              flash.avelocity_z = 5000;
 +              setattachment(flash, self, "");
 +              setorigin(flash, '0 0 0');
 +
 +              self.beam_muzzleentity = flash;
 +      }
 +      else
 +      {
 +              flash = self.beam_muzzleentity;
 +      }
 +
 +      if(sf & ARC_SF_SETTINGS) // settings information
 +      {
 +              self.beam_degreespersegment = ReadShort();
 +              self.beam_distancepersegment = ReadShort();
 +              self.beam_maxangle = ReadShort();
 +              self.beam_range = ReadCoord();
 +              self.beam_returnspeed = ReadShort();
 +              self.beam_tightness = (ReadByte() / 10);
 +
 +              if(ReadByte())
 +              {
 +                      if(autocvar_chase_active)
 +                              { self.beam_usevieworigin = 1; }
 +                      else // use view origin
 +                              { self.beam_usevieworigin = 2; }
 +              }
 +              else
 +              {
 +                      self.beam_usevieworigin = 0;
 +              }
 +      }
 +
 +      if(!self.beam_usevieworigin)
 +      {
 +              // self.iflags = IFLAG_ORIGIN | IFLAG_ANGLES | IFLAG_V_ANGLE; // why doesn't this work?
 +              self.iflags = IFLAG_ORIGIN;
 +
 +              InterpolateOrigin_Undo();
 +      }
 +
 +      if(sf & ARC_SF_START) // starting location
 +      {
 +              self.origin_x = ReadCoord();
 +              self.origin_y = ReadCoord();
 +              self.origin_z = ReadCoord();
 +      }
 +      else if(self.beam_usevieworigin) // infer the location from player location
 +      {
 +              if(self.beam_usevieworigin == 2)
 +              {
 +                      // use view origin
 +                      self.origin = view_origin;
 +              }
 +              else
 +              {
 +                      // use player origin so that third person display still works
 +                      self.origin = getplayerorigin(player_localnum) + ('0 0 1' * getstati(STAT_VIEWHEIGHT));
 +              }
 +      }
 +
 +      setorigin(self, self.origin);
 +
 +      if(sf & ARC_SF_WANTDIR) // want/aim direction
 +      {
 +              self.v_angle_x = ReadCoord();
 +              self.v_angle_y = ReadCoord();
 +              self.v_angle_z = ReadCoord();
 +      }
 +
 +      if(sf & ARC_SF_BEAMDIR) // beam direction
 +      {
 +              self.angles_x = ReadCoord();
 +              self.angles_y = ReadCoord();
 +              self.angles_z = ReadCoord();
 +      }
 +
 +      if(sf & ARC_SF_BEAMTYPE) // beam type
 +      {
 +              self.beam_type = ReadByte();
 +              switch(self.beam_type)
 +              {
 +                      case ARC_BT_MISS:
 +                      {
 +                              self.beam_color = '-1 -1 1';
 +                              self.beam_alpha = 0.5;
 +                              self.beam_thickness = 8;
 +                              self.beam_traileffect = FALSE;
 +                              self.beam_hiteffect = particleeffectnum("electro_lightning");
 +                              self.beam_hitlight[0] = 0;
 +                              self.beam_hitlight[1] = 1;
 +                              self.beam_hitlight[2] = 1;
 +                              self.beam_hitlight[3] = 1;
 +                              self.beam_muzzleeffect = -1; //particleeffectnum("nex_muzzleflash");
 +                              self.beam_muzzlelight[0] = 0;
 +                              self.beam_muzzlelight[1] = 1;
 +                              self.beam_muzzlelight[2] = 1;
 +                              self.beam_muzzlelight[3] = 1;
 +                              if(self.beam_muzzleeffect >= 0)
 +                              {
 +                                      self.beam_image = "particles/lgbeam";
 +                                      setmodel(flash, "models/flash.md3");
 +                                      flash.alpha = self.beam_alpha;
 +                                      flash.colormod = self.beam_color;
 +                                      flash.scale = 0.5;
 +                              }
 +                              break;
 +                      }
 +                      case ARC_BT_WALL: // grenadelauncher_muzzleflash healray_muzzleflash
 +                      {
 +                              self.beam_color = '0.5 0.5 1';
 +                              self.beam_alpha = 0.5;
 +                              self.beam_thickness = 8;
 +                              self.beam_traileffect = FALSE;
 +                              self.beam_hiteffect = particleeffectnum("electro_lightning");
 +                              self.beam_hitlight[0] = 0;
 +                              self.beam_hitlight[1] = 1;
 +                              self.beam_hitlight[2] = 1;
 +                              self.beam_hitlight[3] = 1;
 +                              self.beam_muzzleeffect = -1; // particleeffectnum("grenadelauncher_muzzleflash");
 +                              self.beam_muzzlelight[0] = 0;
 +                              self.beam_muzzlelight[1] = 1;
 +                              self.beam_muzzlelight[2] = 1;
 +                              self.beam_muzzlelight[3] = 1;
 +                              self.beam_image = "particles/lgbeam";
 +                              if(self.beam_muzzleeffect >= 0)
 +                              {
 +                                      setmodel(flash, "models/flash.md3");
 +                                      flash.alpha = self.beam_alpha;
 +                                      flash.colormod = self.beam_color;
 +                                      flash.scale = 0.5;
 +                              }
 +                              break;
 +                      }
 +                      case ARC_BT_HEAL:
 +                      {
 +                              self.beam_color = '0 1 0';
 +                              self.beam_alpha = 0.5;
 +                              self.beam_thickness = 8;
 +                              self.beam_traileffect = FALSE;
 +                              self.beam_hiteffect = particleeffectnum("healray_impact"); 
 +                              self.beam_hitlight[0] = 0;
 +                              self.beam_hitlight[1] = 1;
 +                              self.beam_hitlight[2] = 1;
 +                              self.beam_hitlight[3] = 1;
 +                              self.beam_muzzleeffect = -1; //particleeffectnum("nex_muzzleflash");
 +                              self.beam_muzzlelight[0] = 0;
 +                              self.beam_muzzlelight[1] = 1;
 +                              self.beam_muzzlelight[2] = 1;
 +                              self.beam_muzzlelight[3] = 1;
 +                              self.beam_image = "particles/lgbeam";
 +                              if(self.beam_muzzleeffect >= 0)
 +                              {
 +                                      self.beam_image = "particles/lgbeam";
 +                                      setmodel(flash, "models/flash.md3");
 +                                      flash.alpha = self.beam_alpha;
 +                                      flash.colormod = self.beam_color;
 +                                      flash.scale = 0.5;
 +                              }
 +                              break;
 +                      }
 +                      case ARC_BT_HIT:
 +                      {
 +                              self.beam_color = '1 0 1';
 +                              self.beam_alpha = 0.5;
 +                              self.beam_thickness = 8;
 +                              self.beam_traileffect = FALSE;
 +                              self.beam_hiteffect = particleeffectnum("electro_lightning"); 
 +                              self.beam_hitlight[0] = 20;
 +                              self.beam_hitlight[1] = 1;
 +                              self.beam_hitlight[2] = 0;
 +                              self.beam_hitlight[3] = 0;
 +                              self.beam_muzzleeffect = -1; //particleeffectnum("nex_muzzleflash");
 +                              self.beam_muzzlelight[0] = 50;
 +                              self.beam_muzzlelight[1] = 1;
 +                              self.beam_muzzlelight[2] = 0;
 +                              self.beam_muzzlelight[3] = 0;
 +                              self.beam_image = "particles/lgbeam";
 +                              if(self.beam_muzzleeffect >= 0)
 +                              {
 +                                      self.beam_image = "particles/lgbeam";
 +                                      setmodel(flash, "models/flash.md3");
 +                                      flash.alpha = self.beam_alpha;
 +                                      flash.colormod = self.beam_color;
 +                                      flash.scale = 0.5;
 +                              }
 +                              break;
 +                      }
 +                      case ARC_BT_BURST_MISS:
 +                      {
 +                              self.beam_color = '-1 -1 1';
 +                              self.beam_alpha = 0.5;
 +                              self.beam_thickness = 14;
 +                              self.beam_traileffect = FALSE;
 +                              self.beam_hiteffect = particleeffectnum("electro_lightning"); 
 +                              self.beam_hitlight[0] = 0;
 +                              self.beam_hitlight[1] = 1;
 +                              self.beam_hitlight[2] = 1;
 +                              self.beam_hitlight[3] = 1;
 +                              self.beam_muzzleeffect = -1; //particleeffectnum("nex_muzzleflash");
 +                              self.beam_muzzlelight[0] = 0;
 +                              self.beam_muzzlelight[1] = 1;
 +                              self.beam_muzzlelight[2] = 1;
 +                              self.beam_muzzlelight[3] = 1;
 +                              self.beam_image = "particles/lgbeam";
 +                              setmodel(flash, "models/flash.md3");
 +                              flash.alpha = self.beam_alpha;
 +                              flash.colormod = self.beam_color;
 +                              flash.scale = 0.5;
 +                              break;
 +                      }
 +                      case ARC_BT_BURST_WALL:
 +                      {
 +                              self.beam_color = '0.5 0.5 1';
 +                              self.beam_alpha = 0.5;
 +                              self.beam_thickness = 14;
 +                              self.beam_traileffect = FALSE;
 +                              self.beam_hiteffect = particleeffectnum("electro_lightning"); 
 +                              self.beam_hitlight[0] = 0;
 +                              self.beam_hitlight[1] = 1;
 +                              self.beam_hitlight[2] = 1;
 +                              self.beam_hitlight[3] = 1;
 +                              self.beam_muzzleeffect = -1; //particleeffectnum("nex_muzzleflash");
 +                              self.beam_muzzlelight[0] = 0;
 +                              self.beam_muzzlelight[1] = 1;
 +                              self.beam_muzzlelight[2] = 1;
 +                              self.beam_muzzlelight[3] = 1;
 +                              self.beam_image = "particles/lgbeam";
 +                              if(self.beam_muzzleeffect >= 0)
 +                              {
 +                                      self.beam_image = "particles/lgbeam";
 +                                      setmodel(flash, "models/flash.md3");
 +                                      flash.alpha = self.beam_alpha;
 +                                      flash.colormod = self.beam_color;
 +                                      flash.scale = 0.5;
 +                              }
 +                              break;
 +                      }
 +                      case ARC_BT_BURST_HEAL:
 +                      {
 +                              self.beam_color = '0 1 0';
 +                              self.beam_alpha = 0.5;
 +                              self.beam_thickness = 14;
 +                              self.beam_traileffect = FALSE;
 +                              self.beam_hiteffect = particleeffectnum("electro_lightning"); 
 +                              self.beam_hitlight[0] = 0;
 +                              self.beam_hitlight[1] = 1;
 +                              self.beam_hitlight[2] = 1;
 +                              self.beam_hitlight[3] = 1;
 +                              self.beam_muzzleeffect = -1; //particleeffectnum("nex_muzzleflash");
 +                              self.beam_muzzlelight[0] = 0;
 +                              self.beam_muzzlelight[1] = 1;
 +                              self.beam_muzzlelight[2] = 1;
 +                              self.beam_muzzlelight[3] = 1;
 +                              self.beam_image = "particles/lgbeam";
 +                              if(self.beam_muzzleeffect >= 0)
 +                              {
 +                                      self.beam_image = "particles/lgbeam";
 +                                      setmodel(flash, "models/flash.md3");
 +                                      flash.alpha = self.beam_alpha;
 +                                      flash.colormod = self.beam_color;
 +                                      flash.scale = 0.5;
 +                              }
 +                              break;
 +                      }
 +                      case ARC_BT_BURST_HIT:
 +                      {
 +                              self.beam_color = '1 0 1';
 +                              self.beam_alpha = 0.5;
 +                              self.beam_thickness = 14;
 +                              self.beam_traileffect = FALSE;
 +                              self.beam_hiteffect = particleeffectnum("electro_lightning"); 
 +                              self.beam_hitlight[0] = 0;
 +                              self.beam_hitlight[1] = 1;
 +                              self.beam_hitlight[2] = 1;
 +                              self.beam_hitlight[3] = 1;
 +                              self.beam_muzzleeffect = -1; //particleeffectnum("nex_muzzleflash");
 +                              self.beam_muzzlelight[0] = 0;
 +                              self.beam_muzzlelight[1] = 1;
 +                              self.beam_muzzlelight[2] = 1;
 +                              self.beam_muzzlelight[3] = 1;
 +                              self.beam_image = "particles/lgbeam";
 +                              if(self.beam_muzzleeffect >= 0)
 +                              {
 +                                      self.beam_image = "particles/lgbeam";
 +                                      setmodel(flash, "models/flash.md3");
 +                                      flash.alpha = self.beam_alpha;
 +                                      flash.colormod = self.beam_color;
 +                                      flash.scale = 0.5;
 +                              }
 +                              break;
 +                      }
 +
 +                      // shouldn't be possible, but lets make it colorful if it does :D
 +                      default:
 +                      {
 +                              self.beam_color = randomvec();
 +                              self.beam_alpha = 1;
 +                              self.beam_thickness = 8;
 +                              self.beam_traileffect = FALSE;
 +                              self.beam_hiteffect = FALSE; 
 +                              self.beam_hitlight[0] = 0;
 +                              self.beam_hitlight[1] = 1;
 +                              self.beam_hitlight[2] = 1;
 +                              self.beam_hitlight[3] = 1;
 +                              self.beam_muzzleeffect = -1; //particleeffectnum("nex_muzzleflash");
 +                              self.beam_muzzlelight[0] = 0;
 +                              self.beam_muzzlelight[1] = 1;
 +                              self.beam_muzzlelight[2] = 1;
 +                              self.beam_muzzlelight[3] = 1;
 +                              self.beam_image = "particles/lgbeam";
 +                              if(self.beam_muzzleeffect >= 0)
 +                              {
 +                                      self.beam_image = "particles/lgbeam";
 +                                      setmodel(flash, "models/flash.md3");
 +                                      flash.alpha = self.beam_alpha;
 +                                      flash.colormod = self.beam_color;
 +                                      flash.scale = 0.5;
 +                              }
 +                              break;
 +                      }
 +              }
 +      }
 +
 +      if(!self.beam_usevieworigin)
 +      {
 +              InterpolateOrigin_Note();
 +      }
 +}
 +
 +float W_Arc(float req)
 +{
 +      switch(req)
 +      {
 +              case WR_IMPACTEFFECT:
 +              {
 +                      // todo
 +                      return TRUE;
 +              }
 +              case WR_INIT:
 +              {
 +                      precache_sound("weapons/lgbeam_fly.wav");
 +                      return TRUE;
 +              }
 +              case WR_ZOOMRETICLE:
 +              {
 +                      // no weapon specific image for this weapon
 +                      return FALSE;
 +              }
 +      }
 +      return FALSE;
 +}
 +#endif
 +#endif
index 198097b,0000000..e0e9c62
mode 100644,000000..100644
--- /dev/null
@@@ -1,620 -1,0 +1,620 @@@
-       if(!IS_PLAYER(self.realowner) || self.realowner.deadflag != DEAD_NO || self.realowner.freezetag_frozen)
 +#ifdef REGISTER_WEAPON
 +REGISTER_WEAPON(
 +/* WEP_##id  */ MINE_LAYER,
 +/* function  */ W_MineLayer,
 +/* ammotype  */ ammo_rockets,
 +/* impulse   */ 4,
 +/* flags     */ WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_RELOADABLE | WEP_TYPE_SPLASH,
 +/* rating    */ BOT_PICKUP_RATING_HIGH,
 +/* color     */ '0.75 1 0',
 +/* modelname */ "minelayer",
 +/* simplemdl */ "foobar",
 +/* crosshair */ "gfx/crosshairminelayer 0.9",
 +/* wepimg    */ "weaponminelayer",
 +/* refname   */ "minelayer",
 +/* wepname   */ _("Mine Layer")
 +);
 +
 +#define MINELAYER_SETTINGS(w_cvar,w_prop) MINELAYER_SETTINGS_LIST(w_cvar, w_prop, MINE_LAYER, minelayer)
 +#define MINELAYER_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
 +      w_cvar(id, sn, NONE, ammo) \
 +      w_cvar(id, sn, NONE, animtime) \
 +      w_cvar(id, sn, NONE, damage) \
 +      w_cvar(id, sn, NONE, damageforcescale) \
 +      w_cvar(id, sn, NONE, detonatedelay) \
 +      w_cvar(id, sn, NONE, edgedamage) \
 +      w_cvar(id, sn, NONE, force) \
 +      w_cvar(id, sn, NONE, health) \
 +      w_cvar(id, sn, NONE, lifetime) \
 +      w_cvar(id, sn, NONE, lifetime_countdown) \
 +      w_cvar(id, sn, NONE, limit) \
 +      w_cvar(id, sn, NONE, protection) \
 +      w_cvar(id, sn, NONE, proximityradius) \
 +      w_cvar(id, sn, NONE, radius) \
 +      w_cvar(id, sn, NONE, refire) \
 +      w_cvar(id, sn, NONE, remote_damage) \
 +      w_cvar(id, sn, NONE, remote_edgedamage) \
 +      w_cvar(id, sn, NONE, remote_force) \
 +      w_cvar(id, sn, NONE, remote_radius) \
 +      w_cvar(id, sn, NONE, speed) \
 +      w_cvar(id, sn, NONE, time) \
 +      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
 +MINELAYER_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
 +void W_MineLayer_Think(void);
 +.float minelayer_detonate, mine_explodeanyway;
 +.float mine_time;
 +.vector mine_orientation;
 +#endif
 +#else
 +#ifdef SVQC
 +void spawnfunc_weapon_minelayer(void) { weapon_defaultspawnfunc(WEP_MINE_LAYER); }
 +
 +void W_MineLayer_Stick(entity to)
 +{
 +      spamsound(self, CH_SHOTS, "weapons/mine_stick.wav", VOL_BASE, ATTN_NORM);
 +
 +      // in order for mines to face properly when sticking to the ground, they must be a server side entity rather than a csqc projectile
 +
 +      entity newmine;
 +      newmine = spawn();
 +      newmine.classname = self.classname;
 +
 +      newmine.bot_dodge = self.bot_dodge;
 +      newmine.bot_dodgerating = self.bot_dodgerating;
 +
 +      newmine.owner = self.owner;
 +      newmine.realowner = self.realowner;
 +      setsize(newmine, '-4 -4 -4', '4 4 4');
 +      setorigin(newmine, self.origin);
 +      setmodel(newmine, "models/mine.md3");
 +      newmine.angles = vectoangles(-trace_plane_normal); // face against the surface
 +
 +      newmine.mine_orientation = -trace_plane_normal;
 +
 +      newmine.takedamage = self.takedamage;
 +      newmine.damageforcescale = self.damageforcescale;
 +      newmine.health = self.health;
 +      newmine.event_damage = self.event_damage;
 +      newmine.spawnshieldtime = self.spawnshieldtime;
 +      newmine.damagedbycontents = TRUE;
 +
 +      newmine.movetype = MOVETYPE_NONE; // lock the mine in place
 +      newmine.projectiledeathtype = self.projectiledeathtype;
 +
 +      newmine.mine_time = self.mine_time;
 +
 +      newmine.touch = func_null;
 +      newmine.think = W_MineLayer_Think;
 +      newmine.nextthink = time;
 +      newmine.cnt = self.cnt;
 +      newmine.flags = self.flags;
 +
 +      remove(self);
 +      self = newmine;
 +
 +      if(to)
 +              SetMovetypeFollow(self, to);
 +}
 +
 +void W_MineLayer_Explode(void)
 +{
 +      if(other.takedamage == DAMAGE_AIM)
 +              if(IS_PLAYER(other))
 +                      if(DIFF_TEAM(self.realowner, other))
 +                              if(other.deadflag == DEAD_NO)
 +                                      if(IsFlying(other))
 +                                              Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_AIRSHOT);
 +
 +      self.event_damage = func_null;
 +      self.takedamage = DAMAGE_NO;
 +
 +      RadiusDamage(self, self.realowner, WEP_CVAR(minelayer, damage), WEP_CVAR(minelayer, edgedamage), WEP_CVAR(minelayer, radius), world, world, WEP_CVAR(minelayer, force), self.projectiledeathtype, other);
 +
 +      if(self.realowner.weapon == WEP_MINE_LAYER)
 +      {
 +              entity oldself;
 +              oldself = self;
 +              self = self.realowner;
 +              if(!WEP_ACTION(WEP_MINE_LAYER, WR_CHECKAMMO1))
 +              {
 +                      self.cnt = WEP_MINE_LAYER;
 +                      ATTACK_FINISHED(self) = time;
 +                      self.switchweapon = w_getbestweapon(self);
 +              }
 +              self = oldself;
 +      }
 +      self.realowner.minelayer_mines -= 1;
 +      remove(self);
 +}
 +
 +void W_MineLayer_DoRemoteExplode(void)
 +{
 +      self.event_damage = func_null;
 +      self.takedamage = DAMAGE_NO;
 +
 +      if(self.movetype == MOVETYPE_NONE || self.movetype == MOVETYPE_FOLLOW)
 +              self.velocity = self.mine_orientation; // particle fx and decals need .velocity
 +
 +      RadiusDamage(self, self.realowner, WEP_CVAR(minelayer, remote_damage), WEP_CVAR(minelayer, remote_edgedamage), WEP_CVAR(minelayer, remote_radius), world, world, WEP_CVAR(minelayer, remote_force), self.projectiledeathtype | HITTYPE_BOUNCE, world);
 +
 +      if(self.realowner.weapon == WEP_MINE_LAYER)
 +      {
 +              entity oldself;
 +              oldself = self;
 +              self = self.realowner;
 +              if(!WEP_ACTION(WEP_MINE_LAYER, WR_CHECKAMMO1))
 +              {
 +                      self.cnt = WEP_MINE_LAYER;
 +                      ATTACK_FINISHED(self) = time;
 +                      self.switchweapon = w_getbestweapon(self);
 +              }
 +              self = oldself;
 +      }
 +      self.realowner.minelayer_mines -= 1;
 +      remove(self);
 +}
 +
 +void W_MineLayer_RemoteExplode(void)
 +{
 +      if(self.realowner.deadflag == DEAD_NO)
 +              if((self.spawnshieldtime >= 0)
 +                      ? (time >= self.spawnshieldtime) // timer
 +                      : (vlen(NearestPointOnBox(self.realowner, self.origin) - self.origin) > WEP_CVAR(minelayer, remote_radius)) // safety device
 +              )
 +              {
 +                      W_MineLayer_DoRemoteExplode();
 +              }
 +}
 +
 +void W_MineLayer_ProximityExplode(void)
 +{
 +      // make sure no friend is in the mine's radius. If there is any, explosion is delayed until he's at a safe distance
 +      if(WEP_CVAR(minelayer, protection) && self.mine_explodeanyway == 0)
 +      {
 +              entity head;
 +              head = findradius(self.origin, WEP_CVAR(minelayer, radius));
 +              while(head)
 +              {
 +                      if(head == self.realowner || SAME_TEAM(head, self.realowner))
 +                              return;
 +                      head = head.chain;
 +              }
 +      }
 +
 +      self.mine_time = 0;
 +      W_MineLayer_Explode();
 +}
 +
 +float W_MineLayer_Count(entity e)
 +{
 +      float minecount = 0;
 +      entity mine;
 +      for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.realowner == e)
 +              minecount += 1;
 +
 +      return minecount;
 +}
 +
 +void W_MineLayer_Think(void)
 +{
 +      entity head;
 +
 +      self.nextthink = time;
 +
 +      if(self.movetype == MOVETYPE_FOLLOW)
 +      {
 +              if(LostMovetypeFollow(self))
 +              {
 +                      UnsetMovetypeFollow(self);
 +                      self.movetype = MOVETYPE_NONE;
 +              }
 +      }
 +      
 +      // our lifetime has expired, it's time to die - mine_time just allows us to play a sound for this
 +      // TODO: replace this mine_trigger.wav sound with a real countdown
 +      if((time > self.cnt) && (!self.mine_time))
 +      {
 +              if(WEP_CVAR(minelayer, lifetime_countdown) > 0)
 +                      spamsound(self, CH_SHOTS, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM);
 +              self.mine_time = time + WEP_CVAR(minelayer, lifetime_countdown);
 +              self.mine_explodeanyway = 1; // make the mine super aggressive -- Samual: Rather, make it not care if a team mate is near.
 +      }
 +
 +      // a player's mines shall explode if he disconnects or dies
 +      // TODO: Do this on team change too -- Samual: But isn't a player killed when they switch teams?
-               if(IS_PLAYER(head) && head.deadflag == DEAD_NO && !head.freezetag_frozen)
++      if(!IS_PLAYER(self.realowner) || self.realowner.deadflag != DEAD_NO || self.realowner.frozen)
 +      {
 +              other = world;
 +              self.projectiledeathtype |= HITTYPE_BOUNCE;
 +              W_MineLayer_Explode();
 +              return;
 +      }
 +
 +      // set the mine for detonation when a foe gets close enough
 +      head = findradius(self.origin, WEP_CVAR(minelayer, proximityradius));
 +      while(head)
 +      {
++              if(IS_PLAYER(head) && head.deadflag == DEAD_NO && !head.frozen)
 +              if(head != self.realowner && DIFF_TEAM(head, self.realowner)) // don't trigger for team mates
 +              if(!self.mine_time)
 +              {
 +                      spamsound(self, CH_SHOTS, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM);
 +                      self.mine_time = time + WEP_CVAR(minelayer, time);
 +              }
 +              head = head.chain;
 +      }
 +
 +      // explode if it's time to
 +      if(self.mine_time && time >= self.mine_time)
 +      {
 +              W_MineLayer_ProximityExplode();
 +              return;
 +      }
 +
 +      // remote detonation
 +      if(self.realowner.weapon == WEP_MINE_LAYER)
 +      if(self.realowner.deadflag == DEAD_NO)
 +      if(self.minelayer_detonate)
 +              W_MineLayer_RemoteExplode();
 +}
 +
 +void W_MineLayer_Touch(void)
 +{
 +      if(self.movetype == MOVETYPE_NONE || self.movetype == MOVETYPE_FOLLOW)
 +              return; // we're already a stuck mine, why do we get called? TODO does this even happen?
 +
 +      if(WarpZone_Projectile_Touch())
 +      {
 +              if(wasfreed(self))
 +                      self.realowner.minelayer_mines -= 1;
 +              return;
 +      }
 +
 +      if(other && IS_PLAYER(other) && other.deadflag == DEAD_NO)
 +      {
 +              // hit a player
 +              // don't stick
 +      }
 +      else
 +      {
 +              W_MineLayer_Stick(other);
 +      }
 +}
 +
 +void W_MineLayer_Damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
 +{
 +      if(self.health <= 0)
 +              return;
 +              
 +      float is_from_enemy = (inflictor.realowner != self.realowner);
 +              
 +      if(!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, (is_from_enemy ? 1 : -1)))
 +              return; // g_projectiles_damage says to halt
 +              
 +      self.health = self.health - damage;
 +      self.angles = vectoangles(self.velocity);
 +      
 +      if(self.health <= 0)
 +              W_PrepareExplosionByDamage(attacker, W_MineLayer_Explode);
 +}
 +
 +void W_MineLayer_Attack(void)
 +{
 +      entity mine;
 +      entity flash;
 +
 +      // scan how many mines we placed, and return if we reached our limit
 +      if(WEP_CVAR(minelayer, limit))
 +      {
 +              if(self.minelayer_mines >= WEP_CVAR(minelayer, limit))
 +              {
 +                      // the refire delay keeps this message from being spammed
 +                      sprint(self, strcat("minelayer: You cannot place more than ^2", ftos(WEP_CVAR(minelayer, limit)), " ^7mines at a time\n") );
 +                      play2(self, "weapons/unavailable.wav");
 +                      return;
 +              }
 +      }
 +
 +      W_DecreaseAmmo(WEP_CVAR(minelayer, ammo));
 +
 +      W_SetupShot_ProjectileSize(self, '-4 -4 -4', '4 4 4', FALSE, 5, "weapons/mine_fire.wav", CH_WEAPON_A, WEP_CVAR(minelayer, damage));
 +      pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
 +
 +      mine = WarpZone_RefSys_SpawnSameRefSys(self);
 +      mine.owner = mine.realowner = self;
 +      if(WEP_CVAR(minelayer, detonatedelay) >= 0)
 +              mine.spawnshieldtime = time + WEP_CVAR(minelayer, detonatedelay);
 +      else
 +              mine.spawnshieldtime = -1;
 +      mine.classname = "mine";
 +      mine.bot_dodge = TRUE;
 +      mine.bot_dodgerating = WEP_CVAR(minelayer, damage) * 2; // * 2 because it can detonate inflight which makes it even more dangerous
 +
 +      mine.takedamage = DAMAGE_YES;
 +      mine.damageforcescale = WEP_CVAR(minelayer, damageforcescale);
 +      mine.health = WEP_CVAR(minelayer, health);
 +      mine.event_damage = W_MineLayer_Damage;
 +      mine.damagedbycontents = TRUE;
 +
 +      mine.movetype = MOVETYPE_TOSS;
 +      PROJECTILE_MAKETRIGGER(mine);
 +      mine.projectiledeathtype = WEP_MINE_LAYER;
 +      setsize(mine, '-4 -4 -4', '4 4 4'); // give it some size so it can be shot
 +
 +      setorigin(mine, w_shotorg - v_forward * 4); // move it back so it hits the wall at the right point
 +      W_SetupProjVelocity_Basic(mine, WEP_CVAR(minelayer, speed), 0);
 +      mine.angles = vectoangles(mine.velocity);
 +
 +      mine.touch = W_MineLayer_Touch;
 +      mine.think = W_MineLayer_Think;
 +      mine.nextthink = time;
 +      mine.cnt = time + (WEP_CVAR(minelayer, lifetime) - WEP_CVAR(minelayer, lifetime_countdown));
 +      mine.flags = FL_PROJECTILE;
 +      mine.missile_flags = MIF_SPLASH | MIF_ARC | MIF_PROXY;
 +
 +      CSQCProjectile(mine, TRUE, PROJECTILE_MINE, TRUE);
 +
 +      // muzzle flash for 1st person view
 +      flash = spawn();
 +      setmodel(flash, "models/flash.md3"); // precision set below
 +      SUB_SetFade(flash, time, 0.1);
 +      flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
 +      W_AttachToShotorg(flash, '5 0 0');
 +
 +      // common properties
 +
 +      other = mine; MUTATOR_CALLHOOK(EditProjectile);
 +      
 +      self.minelayer_mines = W_MineLayer_Count(self);
 +}
 +
 +float W_MineLayer_PlacedMines(float detonate)
 +{
 +      entity mine;
 +      float minfound = 0;
 +
 +      for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.realowner == self)
 +      {
 +              if(detonate)
 +              {
 +                      if(!mine.minelayer_detonate)
 +                      {
 +                              mine.minelayer_detonate = TRUE;
 +                              minfound = 1;
 +                      }
 +              }
 +              else
 +                      minfound = 1;
 +      }
 +      return minfound;
 +}
 +
 +float W_MineLayer(float req)
 +{
 +      entity mine;
 +      float ammo_amount;
 +      switch(req)
 +      {
 +              case WR_AIM:
 +              {
 +                      // aim and decide to fire if appropriate
 +                      if(self.minelayer_mines >= WEP_CVAR(minelayer, limit))
 +                              self.BUTTON_ATCK = FALSE;
 +                      else
 +                              self.BUTTON_ATCK = bot_aim(WEP_CVAR(minelayer, speed), 0, WEP_CVAR(minelayer, lifetime), FALSE);
 +                      if(skill >= 2) // skill 0 and 1 bots won't detonate mines!
 +                      {
 +                              // decide whether to detonate mines
 +                              entity targetlist, targ;
 +                              float edgedamage, coredamage, edgeradius, recipricoledgeradius, d;
 +                              float selfdamage, teamdamage, enemydamage;
 +                              edgedamage = WEP_CVAR(minelayer, edgedamage);
 +                              coredamage = WEP_CVAR(minelayer, damage);
 +                              edgeradius = WEP_CVAR(minelayer, radius);
 +                              recipricoledgeradius = 1 / edgeradius;
 +                              selfdamage = 0;
 +                              teamdamage = 0;
 +                              enemydamage = 0;
 +                              targetlist = findchainfloat(bot_attack, TRUE);
 +                              mine = find(world, classname, "mine");
 +                              while(mine)
 +                              {
 +                                      if(mine.realowner != self)
 +                                      {
 +                                              mine = find(mine, classname, "mine");
 +                                              continue;
 +                                      }
 +                                      targ = targetlist;
 +                                      while(targ)
 +                                      {
 +                                              d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - mine.origin);
 +                                              d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000);
 +                                              // count potential damage according to type of target
 +                                              if(targ == self)
 +                                                      selfdamage = selfdamage + d;
 +                                              else if(targ.team == self.team && teamplay)
 +                                                      teamdamage = teamdamage + d;
 +                                              else if(bot_shouldattack(targ))
 +                                                      enemydamage = enemydamage + d;
 +                                              targ = targ.chain;
 +                                      }
 +                                      mine = find(mine, classname, "mine");
 +                              }
 +                              float desirabledamage;
 +                              desirabledamage = enemydamage;
 +                              if(time > self.invincible_finished && time > self.spawnshieldtime)
 +                                      desirabledamage = desirabledamage - selfdamage * autocvar_g_balance_selfdamagepercent;
 +                              if(teamplay && self.team)
 +                                      desirabledamage = desirabledamage - teamdamage;
 +
 +                              mine = find(world, classname, "mine");
 +                              while(mine)
 +                              {
 +                                      if(mine.realowner != self)
 +                                      {
 +                                              mine = find(mine, classname, "mine");
 +                                              continue;
 +                                      }
 +                                      makevectors(mine.v_angle);
 +                                      targ = targetlist;
 +                                      if(skill > 9) // normal players only do this for the target they are tracking
 +                                      {
 +                                              targ = targetlist;
 +                                              while(targ)
 +                                              {
 +                                                      if(
 +                                                              (v_forward * normalize(mine.origin - targ.origin)< 0.1)
 +                                                              && desirabledamage > 0.1*coredamage
 +                                                      )self.BUTTON_ATCK2 = TRUE;
 +                                                      targ = targ.chain;
 +                                              }
 +                                      }else{
 +                                              float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000);
 +                                              //As the distance gets larger, a correct detonation gets near imposible
 +                                              //Bots are assumed to use the mine spawnfunc_light to see if the mine gets near a player
 +                                              if(v_forward * normalize(mine.origin - self.enemy.origin)< 0.1)
 +                                                      if(IS_PLAYER(self.enemy))
 +                                                              if(desirabledamage >= 0.1*coredamage)
 +                                                                      if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1))
 +                                                                              self.BUTTON_ATCK2 = TRUE;
 +                                      //      dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n");
 +                                      }
 +
 +                                      mine = find(mine, classname, "mine");
 +                              }
 +                              // if we would be doing at X percent of the core damage, detonate it
 +                              // but don't fire a new shot at the same time!
 +                              if(desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events
 +                                      self.BUTTON_ATCK2 = TRUE;
 +                              if((skill > 6.5) && (selfdamage > self.health))
 +                                      self.BUTTON_ATCK2 = FALSE;
 +                              //if(self.BUTTON_ATCK2 == TRUE)
 +                              //      dprint(ftos(desirabledamage),"\n");
 +                              if(self.BUTTON_ATCK2 == TRUE) self.BUTTON_ATCK = FALSE;
 +                      }
 +                      
 +                      return TRUE;
 +              }
 +              case WR_THINK:
 +              {
 +                      if(autocvar_g_balance_minelayer_reload_ammo && self.clip_load < WEP_CVAR(minelayer, ammo)) // forced reload
 +                      {
 +                              // not if we're holding the minelayer without enough ammo, but can detonate existing mines
 +                              if(!(W_MineLayer_PlacedMines(FALSE) && self.WEP_AMMO(MINE_LAYER) < WEP_CVAR(minelayer, ammo)))
 +                                      WEP_ACTION(self.weapon, WR_RELOAD);
 +                      }
 +                      else if(self.BUTTON_ATCK)
 +                      {
 +                              if(weapon_prepareattack(0, WEP_CVAR(minelayer, refire)))
 +                              {
 +                                      W_MineLayer_Attack();
 +                                      weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(minelayer, animtime), w_ready);
 +                              }
 +                      }
 +
 +                      if(self.BUTTON_ATCK2)
 +                      {
 +                              if(W_MineLayer_PlacedMines(TRUE))
 +                                      sound(self, CH_WEAPON_B, "weapons/mine_det.wav", VOL_BASE, ATTN_NORM);
 +                      }
 +                      
 +                      return TRUE;
 +              }
 +              case WR_INIT:
 +              {
 +                      precache_model("models/flash.md3");
 +                      precache_model("models/mine.md3");
 +                      precache_model("models/weapons/g_minelayer.md3");
 +                      precache_model("models/weapons/v_minelayer.md3");
 +                      precache_model("models/weapons/h_minelayer.iqm");
 +                      precache_sound("weapons/mine_det.wav");
 +                      precache_sound("weapons/mine_fire.wav");
 +                      precache_sound("weapons/mine_stick.wav");
 +                      precache_sound("weapons/mine_trigger.wav");
 +                      MINELAYER_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP)
 +                      return TRUE;
 +              }
 +              case WR_CHECKAMMO1:
 +              {
 +                      // don't switch while placing a mine
 +                      if(ATTACK_FINISHED(self) <= time || self.weapon != WEP_MINE_LAYER)
 +                      {
 +                              ammo_amount = self.WEP_AMMO(MINE_LAYER) >= WEP_CVAR(minelayer, ammo);
 +                              ammo_amount += self.(weapon_load[WEP_MINE_LAYER]) >= WEP_CVAR(minelayer, ammo);
 +                              return ammo_amount;
 +                      }
 +                      return TRUE;
 +              }
 +              case WR_CHECKAMMO2:
 +              {
 +                      if(W_MineLayer_PlacedMines(FALSE))
 +                              return TRUE;
 +                      else
 +                              return FALSE;
 +              }
 +              case WR_CONFIG:
 +              {
 +                      MINELAYER_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS)
 +                      return TRUE;
 +              }
 +              case WR_RESETPLAYER:
 +              {
 +                      self.minelayer_mines = 0;
 +                      return TRUE;
 +              }
 +              case WR_RELOAD:
 +              {
 +                      W_Reload(WEP_CVAR(minelayer, ammo), "weapons/reload.wav");
 +                      return TRUE;
 +              }
 +              case WR_SUICIDEMESSAGE:
 +              {
 +                      return WEAPON_MINELAYER_SUICIDE;
 +              }
 +              case WR_KILLMESSAGE:
 +              {
 +                      return WEAPON_MINELAYER_MURDER;
 +              }
 +      }
 +      return FALSE;
 +}
 +#endif
 +#ifdef CSQC
 +float W_MineLayer(float req)
 +{
 +      switch(req)
 +      {
 +              case WR_IMPACTEFFECT:
 +              {
 +                      vector org2;
 +                      org2 = w_org + w_backoff * 12;
 +                      pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1);
 +                      if(!w_issilent)
 +                              sound(self, CH_SHOTS, "weapons/mine_exp.wav", VOL_BASE, ATTN_NORM);
 +                      
 +                      return TRUE;
 +              }
 +              case WR_INIT:
 +              {
 +                      precache_sound("weapons/mine_exp.wav");
 +                      return TRUE;
 +              }
 +              case WR_ZOOMRETICLE:
 +              {
 +                      // no weapon specific image for this weapon
 +                      return FALSE;
 +              }
 +      }
 +      return FALSE;
 +}
 +#endif
 +#endif
Simple merge
Simple merge
Simple merge
@@@ -1200,29 -1192,15 +1191,9 @@@ void ClientConnect (void
        if(!sv_foginterval && world.fog != "")
                stuffcmd(self, strcat("\nfog ", world.fog, "\nr_fog_exp2 0\nr_drawfog 1\n"));
  
 -      if(autocvar_g_hitplots || strstrofs(strcat(" ", autocvar_g_hitplots_individuals, " "), strcat(" ", self.netaddress, " "), 0) >= 0)
 -      {
 -              self.hitplotfh = fopen(strcat("hits-", matchid, "-", self.netaddress, "-", ftos(self.playerid), ".plot"), FILE_WRITE);
 -              fputs(self.hitplotfh, strcat("#name ", self.netname, "\n"));
 -      }
 -      else
 -              self.hitplotfh = -1;
 +      W_HitPlotOpen(self);
  
-       if(g_race || g_cts) {
-               string rr;
-               if(g_cts)
-                       rr = CTS_RECORD;
-               else
-                       rr = RACE_RECORD;
-               msg_entity = self;
-               race_send_recordtime(MSG_ONE);
-               race_send_speedaward(MSG_ONE);
-               speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed")));
-               speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp")));
-               race_send_speedaward_alltimebest(MSG_ONE);
-               float i;
-               for (i = 1; i <= RANKINGS_CNT; ++i) {
-                       race_SendRankings(i, 0, 0, MSG_ONE);
-               }
-       }
-       else if(autocvar_sv_teamnagger && !(autocvar_bot_vs_human && (c3==-1 && c4==-1)) && !g_ca) // teamnagger is currently bad for ca
+       if(autocvar_sv_teamnagger && !(autocvar_bot_vs_human && (c3==-1 && c4==-1)) && !g_ca && !g_cts && !g_race) // teamnagger is currently bad for ca, race & cts
                send_CSQC_teamnagger();
  
        CheatInitClient();
@@@ -2355,12 -2375,9 +2365,12 @@@ void PlayerPreThink (void
                        do_crouch = 0;
                if(self.vehicle)
                        do_crouch = 0;
-               if(self.freezetag_frozen)
+               if(self.frozen)
                        do_crouch = 0;
 -              if(self.weapon == WEP_SHOTGUN && self.weaponentity.wframe == WFRAME_FIRE2 && time < self.weapon_nextthink)
 +
 +              // WEAPONTODO: THIS SHIT NEEDS TO GO EVENTUALLY
 +              // It cannot be predicted by the engine! 
 +              if((self.weapon == WEP_SHOCKWAVE || self.weapon == WEP_SHOTGUN) && self.weaponentity.wframe == WFRAME_FIRE2 && time < self.weapon_nextthink)
                        do_crouch = 0;
  
                if (do_crouch)
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
@@@ -102,8 -103,13 +103,13 @@@ MUTATOR_HOOKABLE(SpectateCopy)
  MUTATOR_HOOKABLE(ForbidThrowCurrentWeapon);
        // returns 1 if throwing the current weapon shall not be allowed
  
+ MUTATOR_HOOKABLE(WeaponRateFactor);
+       // allows changing attack rate
+       // INPUT, OUTPUT:
+               float weapon_rate;
  MUTATOR_HOOKABLE(SetStartItems);
 -      // adjusts {warmup_}start_{items,weapons,ammo_{cells,rockets,nails,shells,fuel}}
 +      // adjusts {warmup_}start_{items,weapons,ammo_{cells,plasma,rockets,nails,shells,fuel}}
  
  MUTATOR_HOOKABLE(BuildMutatorsString);
        // appends ":mutatorname" to ret_string for logging
Simple merge
@@@ -38,41 -27,522 +27,520 @@@ void nade_spawn(entity _nade
        timer.owner = _nade;
        timer.skin = 10;
  
-       switch(_nade.realowner.team)
+       _nade.effects |= EF_LOWPRECISION;
+       CSQCProjectile(_nade, TRUE, Nade_ProjectileFromID(_nade.nade_type, FALSE), TRUE);
+ }
+ void napalm_damage(float dist, float damage, float edgedamage, float burntime)
+ {
+       entity e;
+       float d;
+       vector p;
+       if ( damage < 0 )
+               return;
+       RandomSelection_Init();
+       for(e = WarpZone_FindRadius(self.origin, dist, TRUE); e; e = e.chain)
+               if(e.takedamage == DAMAGE_AIM)
+               if(self.realowner != e || autocvar_g_nades_napalm_selfdamage)
+               if(!IS_PLAYER(e) || !self.realowner || DIFF_TEAM(e, self))
+               if(!e.frozen)
+               {
+                       p = e.origin;
+                       p_x += e.mins_x + random() * (e.maxs_x - e.mins_x);
+                       p_y += e.mins_y + random() * (e.maxs_y - e.mins_y);
+                       p_z += e.mins_z + random() * (e.maxs_z - e.mins_z);
+                       d = vlen(WarpZone_UnTransformOrigin(e, self.origin) - p);
+                       if(d < dist)
+                       {
+                               e.fireball_impactvec = p;
+                               RandomSelection_Add(e, 0, string_null, 1 / (1 + d), !Fire_IsBurning(e));
+                       }
+               }
+       if(RandomSelection_chosen_ent)
+       {
+               d = vlen(WarpZone_UnTransformOrigin(RandomSelection_chosen_ent, self.origin) - RandomSelection_chosen_ent.fireball_impactvec);
+               d = damage + (edgedamage - damage) * (d / dist);
+               Fire_AddDamage(RandomSelection_chosen_ent, self.realowner, d * burntime, burntime, self.projectiledeathtype | HITTYPE_BOUNCE);
+               //trailparticles(self, particleeffectnum("fireball_laser"), self.origin, RandomSelection_chosen_ent.fireball_impactvec);
+               pointparticles(particleeffectnum("fireball_laser"), self.origin, RandomSelection_chosen_ent.fireball_impactvec - self.origin, 1);
+       }
+ }
+ void napalm_ball_think()
+ {
+       if(round_handler_IsActive())
+       if(!round_handler_IsRoundStarted())
+       {
+               remove(self);
+               return;
+       }
+       if(time > self.pushltime)
+       {
+               remove(self);
+               return;
+       }
+       vector midpoint = ((self.absmin + self.absmax) * 0.5);
+       if(pointcontents(midpoint) == CONTENT_WATER)
+       {
+               self.velocity = self.velocity * 0.5;
+               if(pointcontents(midpoint + '0 0 16') == CONTENT_WATER)
+                       { self.velocity_z = 200; }
+       }
+       self.angles = vectoangles(self.velocity);
+       napalm_damage(autocvar_g_nades_napalm_ball_radius,autocvar_g_nades_napalm_ball_damage,
+                                 autocvar_g_nades_napalm_ball_damage,autocvar_g_nades_napalm_burntime);
+       self.nextthink = time + 0.1;
+ }
+ void nade_napalm_ball()
+ {
+       entity proj;
+       vector kick;
+       spamsound(self, CH_SHOTS, "weapons/fireball_fire.wav", VOL_BASE, ATTEN_NORM);
+       proj = spawn ();
+       proj.owner = self.owner;
+       proj.realowner = self.realowner;
+       proj.team = self.owner.team;
+       proj.classname = "grenade";
+       proj.bot_dodge = TRUE;
+       proj.bot_dodgerating = autocvar_g_nades_napalm_ball_damage;
+       proj.movetype = MOVETYPE_BOUNCE;
+       proj.projectiledeathtype = DEATH_NADE_NAPALM;
+       PROJECTILE_MAKETRIGGER(proj);
+       setmodel(proj, "null");
+       proj.scale = 1;//0.5;
+       setsize(proj, '-4 -4 -4', '4 4 4');
+       setorigin(proj, self.origin);
+       proj.think = napalm_ball_think;
+       proj.nextthink = time;
+       proj.damageforcescale = autocvar_g_nades_napalm_ball_damageforcescale;
+       proj.effects = EF_LOWPRECISION | EF_FLAME;
+       kick_x =(random() - 0.5) * 2 * autocvar_g_nades_napalm_ball_spread;
+       kick_y = (random() - 0.5) * 2 * autocvar_g_nades_napalm_ball_spread;
+       kick_z = (random()/2+0.5) * autocvar_g_nades_napalm_ball_spread;
+       proj.velocity = kick;
+       proj.pushltime = time + autocvar_g_nades_napalm_ball_lifetime;
+       proj.angles = vectoangles(proj.velocity);
+       proj.flags = FL_PROJECTILE;
+       proj.missile_flags = MIF_SPLASH | MIF_PROXY | MIF_ARC;
+       //CSQCProjectile(proj, TRUE, PROJECTILE_NAPALM_FIRE, TRUE);
+ }
+ void napalm_fountain_think()
+ {
+       if(round_handler_IsActive())
+       if(!round_handler_IsRoundStarted())
+       {
+               remove(self);
+               return;
+       }
+       if(time >= self.ltime)
+       {
+               remove(self);
+               return;
+       }
+       vector midpoint = ((self.absmin + self.absmax) * 0.5);
+       if(pointcontents(midpoint) == CONTENT_WATER)
+       {
+               self.velocity = self.velocity * 0.5;
+               if(pointcontents(midpoint + '0 0 16') == CONTENT_WATER)
+                       { self.velocity_z = 200; }
+               UpdateCSQCProjectile(self);
+       }
+       napalm_damage(autocvar_g_nades_napalm_fountain_radius, autocvar_g_nades_napalm_fountain_damage,
+               autocvar_g_nades_napalm_fountain_edgedamage, autocvar_g_nades_napalm_burntime);
+       self.nextthink = time + 0.1;
+       if(time >= self.nade_special_time)
+       {
+               self.nade_special_time = time + autocvar_g_nades_napalm_fountain_delay;
+               nade_napalm_ball();
+       }
+ }
+ void nade_napalm_boom()
+ {
+       entity fountain;
+       local float c;
+       for (c = 0; c < autocvar_g_nades_napalm_ball_count; c ++)
+               nade_napalm_ball();
+       fountain = spawn();
+       fountain.owner = self.owner;
+       fountain.realowner = self.realowner;
+       fountain.origin = self.origin;
+       setorigin(fountain, fountain.origin);
+       fountain.think = napalm_fountain_think;
+       fountain.nextthink = time;
+       fountain.ltime = time + autocvar_g_nades_napalm_fountain_lifetime;
+       fountain.pushltime = fountain.ltime;
+       fountain.team = self.team;
+       fountain.movetype = MOVETYPE_TOSS;
+       fountain.projectiledeathtype = DEATH_NADE_NAPALM;
+       fountain.bot_dodge = TRUE;
+       fountain.bot_dodgerating = autocvar_g_nades_napalm_fountain_damage;
+       fountain.nade_special_time = time;
+       setsize(fountain, '-16 -16 -16', '16 16 16');
+       CSQCProjectile(fountain, TRUE, PROJECTILE_NAPALM_FOUNTAIN, TRUE);
+ }
+ void nade_ice_freeze(entity freezefield, entity frost_target, float freeze_time)
+ {
+       frost_target.frozen_by = freezefield.realowner;
+       pointparticles(particleeffectnum("electro_impact"), frost_target.origin, '0 0 0', 1);
+       Freeze(frost_target, 1/freeze_time, 3, FALSE);
+       if(frost_target.ballcarried)
+       if(g_keepaway) { ka_DropEvent(frost_target); }
+       else { DropBall(frost_target.ballcarried, frost_target.origin, frost_target.velocity);}
+       if(frost_target.flagcarried) { ctf_Handle_Throw(frost_target, world, DROP_THROW); }
+       if(frost_target.nade) { toss_nade(frost_target, '0 0 0', time + 0.05); }
+       
+       kh_Key_DropAll(frost_target, FALSE);
+ }
+ void nade_ice_think()
+ {
+       if(round_handler_IsActive())
+       if(!round_handler_IsRoundStarted())
+       {
+               remove(self);
+               return;
+       }
+       if(time >= self.ltime)
+       {
+               if ( autocvar_g_nades_ice_explode )
+               {
+                       string expef;
+                       switch(self.realowner.team)
+                       {
+                               case NUM_TEAM_1: expef = "nade_red_explode"; break;
+                               case NUM_TEAM_2: expef = "nade_blue_explode"; break;
+                               case NUM_TEAM_3: expef = "nade_yellow_explode"; break;
+                               case NUM_TEAM_4: expef = "nade_pink_explode"; break;
+                               default:                 expef = "nade_neutral_explode"; break;
+                       }
+                       pointparticles(particleeffectnum(expef), self.origin + '0 0 1', '0 0 0', 1);
+                       sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM);
+                       RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage,
 -                              autocvar_g_nades_nade_radius, self, autocvar_g_nades_nade_force, self.projectiledeathtype, self.enemy);
++                              autocvar_g_nades_nade_radius, self, world, autocvar_g_nades_nade_force, self.projectiledeathtype, self.enemy);
+                       Damage_DamageInfo(self.origin, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage,
+                               autocvar_g_nades_nade_radius, '1 1 1' * autocvar_g_nades_nade_force, self.projectiledeathtype, 0, self);
+               }
+               remove(self);
+               return;
+       }
+       self.nextthink = time+0.1;
+       // gaussian
+       float randomr;
+       randomr = random();
+       randomr = exp(-5*randomr*randomr)*autocvar_g_nades_nade_radius;
+       float randomw;
+       randomw = random()*M_PI*2;
+       vector randomp;
+       randomp_x = randomr*cos(randomw);
+       randomp_y = randomr*sin(randomw);
+       randomp_z = 1;
+       pointparticles(particleeffectnum("electro_muzzleflash"), self.origin + randomp, '0 0 0', 1);
+       if(time >= self.nade_special_time)
+       {
+               self.nade_special_time = time+0.7;
+               pointparticles(particleeffectnum("electro_impact"), self.origin, '0 0 0', 1);
+               pointparticles(particleeffectnum("icefield"), self.origin, '0 0 0', 1);
+       }
+       float current_freeze_time = self.ltime - time - 0.1;
+       entity e;
+       for(e = findradius(self.origin, autocvar_g_nades_nade_radius); e; e = e.chain)
+       if(e != self)
+       if(!autocvar_g_nades_ice_teamcheck || (DIFF_TEAM(e, self.realowner) || e == self.realowner))
+       if(e.takedamage && e.deadflag == DEAD_NO)
+       if(e.health > 0)
+       if(!e.revival_time || ((time - e.revival_time) >= 1.5))
+       if(!e.frozen)
+       if(current_freeze_time > 0)
+               nade_ice_freeze(self, e, current_freeze_time);
+ }
+ void nade_ice_boom()
+ {
+       entity fountain;
+       fountain = spawn();
+       fountain.owner = self.owner;
+       fountain.realowner = self.realowner;
+       fountain.origin = self.origin;
+       setorigin(fountain, fountain.origin);
+       fountain.think = nade_ice_think;
+       fountain.nextthink = time;
+       fountain.ltime = time + autocvar_g_nades_ice_freeze_time;
+       fountain.pushltime = fountain.wait = fountain.ltime;
+       fountain.team = self.team;
+       fountain.movetype = MOVETYPE_TOSS;
+       fountain.projectiledeathtype = DEATH_NADE_ICE;
+       fountain.bot_dodge = FALSE;
+       setsize(fountain, '-16 -16 -16', '16 16 16');
+       fountain.nade_special_time = time+0.3;
+       fountain.angles = self.angles;
+       if ( autocvar_g_nades_ice_explode )
+       {
+               setmodel(fountain, "models/grenademodel.md3");
+               entity timer = spawn();
+               setmodel(timer, "models/ok_nade_counter/ok_nade_counter.md3");
+               setattachment(timer, fountain, "");
+               timer.classname = "nade_timer";
+               timer.colormap = self.colormap;
+               timer.glowmod = self.glowmod;
+               timer.think = nade_timer_think;
+               timer.nextthink = time;
+               timer.wait = fountain.ltime;
+               timer.owner = fountain;
+               timer.skin = 10;
+       }
+       else
+               setmodel(fountain, "null");
+ }
+ void nade_translocate_boom()
+ {
+       if(self.realowner.vehicle)
+               return;
+       vector locout = self.origin + '0 0 1' * (1 - self.realowner.mins_z - 24);
+       tracebox(locout, self.realowner.mins, self.realowner.maxs, locout, MOVE_NOMONSTERS, self.realowner);
+       locout = trace_endpos;
+       makevectors(self.realowner.angles);
+       entity oldself = self;
+       self = self.realowner;
+       MUTATOR_CALLHOOK(PortalTeleport);
+       self.realowner = self;
+       self = oldself;
+       TeleportPlayer(self, self.realowner, locout, self.realowner.mangle, v_forward * vlen(self.realowner.velocity), '0 0 0', '0 0 0', TELEPORT_FLAGS_TELEPORTER);
+ }
+ void nade_spawn_boom()
+ {
+       entity spawnloc = spawn();
+       setorigin(spawnloc, self.origin);
+       setsize(spawnloc, self.realowner.mins, self.realowner.maxs);
+       spawnloc.movetype = MOVETYPE_NONE;
+       spawnloc.solid = SOLID_NOT;
+       spawnloc.drawonlytoclient = self.realowner;
+       spawnloc.effects = EF_STARDUST;
+       spawnloc.cnt = autocvar_g_nades_spawn_count;
+       if(self.realowner.nade_spawnloc)
+       {
+               remove(self.realowner.nade_spawnloc);
+               self.realowner.nade_spawnloc = world;
+       }
+       self.realowner.nade_spawnloc = spawnloc;
+ }
+ void nade_heal_think()
+ {
+       if(time >= self.ltime)
+       {
+               remove(self);
+               return;
+       }
+       
+       self.nextthink = time;
+       
+       if(time >= self.nade_special_time)
+       {
+               self.nade_special_time = time+0.25;
+               self.nade_show_particles = 1;
+       }
+       else
+               self.nade_show_particles = 0;
+ }
+ void nade_heal_touch()
+ {
+       float maxhealth;
+       float health_factor;
+       if(IS_PLAYER(other) || (other.flags & FL_MONSTER))
+       if(other.deadflag == DEAD_NO)
+       if(!other.frozen)
+       {
+               health_factor = autocvar_g_nades_heal_rate*frametime/2;
+               if ( other != self.realowner )
+               {
+                       if ( SAME_TEAM(other,self) )
+                               health_factor *= autocvar_g_nades_heal_friend;
+                       else
+                               health_factor *= autocvar_g_nades_heal_foe;
+               }
+               if ( health_factor > 0 )
+               {
+                       maxhealth = (other.flags & FL_MONSTER) ? other.max_health : g_pickup_healthmega_max;
+                       if ( other.health < maxhealth )
+                       {
+                               if ( self.nade_show_particles )
+                                       pointparticles(particleeffectnum("healing_fx"), other.origin, '0 0 0', 1);
+                               other.health = min(other.health+health_factor, maxhealth);
+                       }
+                       other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);  
+               }
+               else if ( health_factor < 0 )
+               {
+                       Damage(other,self,self.realowner,-health_factor,DEATH_NADE_HEAL,other.origin,'0 0 0');
+               }
+               
+       }
+       
+       if ( IS_REAL_CLIENT(other) || (other.vehicle_flags & VHF_ISVEHICLE) )
        {
-               case NUM_TEAM_1: p = PROJECTILE_NADE_RED; break;
-               case NUM_TEAM_2: p = PROJECTILE_NADE_BLUE; break;
-               case NUM_TEAM_3: p = PROJECTILE_NADE_YELLOW; break;
-               case NUM_TEAM_4: p = PROJECTILE_NADE_PINK; break;
-               default:                 p = PROJECTILE_NADE; break;
+               entity show_red = (other.vehicle_flags & VHF_ISVEHICLE) ? other.owner : other;
+               show_red.stat_healing_orb = time+0.1;
+               show_red.stat_healing_orb_alpha = 0.75 * (self.ltime - time) / self.healer_lifetime;
        }
+ }
  
-       CSQCProjectile(_nade, TRUE, p, TRUE);
+ void nade_heal_boom()
+ {
+       entity healer;
+       healer = spawn();
+       healer.owner = self.owner;
+       healer.realowner = self.realowner;
+       setorigin(healer, self.origin);
+       healer.healer_lifetime = autocvar_g_nades_heal_time; // save the cvar
+       healer.ltime = time + healer.healer_lifetime;
+       healer.team = self.realowner.team;
+       healer.bot_dodge = FALSE;
+       healer.solid = SOLID_TRIGGER;
+       healer.touch = nade_heal_touch;
+       setmodel(healer, "models/ctf/shield.md3");
+       healer.healer_radius = autocvar_g_nades_nade_radius;
+       vector size = '1 1 1' * healer.healer_radius / 2;
+       setsize(healer,-size,size);
+       
+       Net_LinkEntity(healer, TRUE, 0, healer_send);
+       
+       healer.think = nade_heal_think;
+       healer.nextthink = time;
+       healer.SendFlags |= 1;
+ }
  
+ void nade_monster_boom()
+ {
+       entity e = spawnmonster(self.pokenade_type, 0, self.realowner, self.realowner, self.origin, FALSE, FALSE, 1);
+       
+       if(autocvar_g_nades_pokenade_monster_lifetime > 0)
+               e.monster_lifetime = time + autocvar_g_nades_pokenade_monster_lifetime;
+       e.monster_skill = MONSTER_SKILL_INSANE;
  }
  
  void nade_boom()
  {
        string expef;
+       float nade_blast = 1;
  
-       switch(self.realowner.team)
+       switch ( self.nade_type )
        {
-               case NUM_TEAM_1: expef = "nade_red_explode"; break;
-               case NUM_TEAM_2: expef = "nade_blue_explode"; break;
-               case NUM_TEAM_3: expef = "nade_yellow_explode"; break;
-               case NUM_TEAM_4: expef = "nade_pink_explode"; break;
-               default:                 expef = "nade_explode"; break;
+               case NADE_TYPE_NAPALM:
+                       nade_blast = autocvar_g_nades_napalm_blast;
+                       expef = "explosion_medium";
+                       break;
+               case NADE_TYPE_ICE:
+                       nade_blast = 0;
+                       expef = "electro_combo"; // hookbomb_explode electro_combo bigplasma_impact
+                       break;
+               case NADE_TYPE_TRANSLOCATE:
+                       nade_blast = 0;
+                       expef = "";
+                       break;
+               case NADE_TYPE_MONSTER:
+               case NADE_TYPE_SPAWN:
+                       nade_blast = 0;
+                       switch(self.realowner.team)
+                       {
+                               case NUM_TEAM_1: expef = "spawn_event_red"; break;
+                               case NUM_TEAM_2: expef = "spawn_event_blue"; break;
+                               case NUM_TEAM_3: expef = "spawn_event_yellow"; break;
+                               case NUM_TEAM_4: expef = "spawn_event_pink"; break;
+                               default: expef = "spawn_event_neutral"; break;
+                       }
+                       break;
+               case NADE_TYPE_HEAL:
+                       nade_blast = 0;
+                       expef = "spawn_event_red";
+                       break;
+               default:
+               case NADE_TYPE_NORMAL:
+                       switch(self.realowner.team)
+                       {
+                               case NUM_TEAM_1: expef = "nade_red_explode"; break;
+                               case NUM_TEAM_2: expef = "nade_blue_explode"; break;
+                               case NUM_TEAM_3: expef = "nade_yellow_explode"; break;
+                               case NUM_TEAM_4: expef = "nade_pink_explode"; break;
+                               default:                 expef = "nade_neutral_explode"; break;
+                       }
        }
  
-       sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTEN_NORM);
-       sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM);
        pointparticles(particleeffectnum(expef), self.origin + '0 0 1', '0 0 0', 1);
  
-       Damage_DamageInfo(self.origin, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, autocvar_g_nades_nade_radius, '1 1 1' * autocvar_g_nades_nade_force, self.projectiledeathtype, 0, self);
+       sound(self, CH_SHOTS_SINGLE, "misc/null.wav", VOL_BASE, ATTEN_NORM);
+       sound(self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM);
  
--      self.takedamage = DAMAGE_NO;
-       RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage,
 -
+       if(nade_blast)
+       {
+               RadiusDamage(self, self.realowner, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage,
 -                               autocvar_g_nades_nade_radius, self, autocvar_g_nades_nade_force, self.projectiledeathtype, self.enemy);
 +                               autocvar_g_nades_nade_radius, self, world, autocvar_g_nades_nade_force, self.projectiledeathtype, self.enemy);
+               Damage_DamageInfo(self.origin, autocvar_g_nades_nade_damage, autocvar_g_nades_nade_edgedamage, autocvar_g_nades_nade_radius, '1 1 1' * autocvar_g_nades_nade_force, self.projectiledeathtype, 0, self);
+       }
+       switch ( self.nade_type )
+       {
+               case NADE_TYPE_NAPALM: nade_napalm_boom(); break;
+               case NADE_TYPE_ICE: nade_ice_boom(); break;
+               case NADE_TYPE_TRANSLOCATE: nade_translocate_boom(); break;
+               case NADE_TYPE_SPAWN: nade_spawn_boom(); break;
+               case NADE_TYPE_HEAL: nade_heal_boom(); break;
+               case NADE_TYPE_MONSTER: nade_monster_boom(); break;
+       }
  
        remove(self);
  }
@@@ -101,26 -572,29 +570,28 @@@ void nade_beep(
  
  void nade_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
  {
 -      if(DEATH_ISWEAPON(deathtype, WEP_LASER))
+       if(self.nade_type == NADE_TYPE_TRANSLOCATE || self.nade_type == NADE_TYPE_SPAWN)
+               return;
 +      if(DEATH_ISWEAPON(deathtype, WEP_BLASTER))
                return;
  
 -      if(DEATH_ISWEAPON(deathtype, WEP_NEX) || DEATH_ISWEAPON(deathtype, WEP_MINSTANEX))
 +      if(DEATH_ISWEAPON(deathtype, WEP_VORTEX) || DEATH_ISWEAPON(deathtype, WEP_VAPORIZER))
        {
                force *= 6;
                damage = self.max_health * 0.55;
        }
  
 -      if(DEATH_ISWEAPON(deathtype, WEP_UZI))
 +      if(DEATH_ISWEAPON(deathtype, WEP_MACHINEGUN))
                damage = self.max_health * 0.1;
  
-       if((DEATH_ISWEAPON(deathtype, WEP_SHOCKWAVE) || DEATH_ISWEAPON(deathtype, WEP_SHOTGUN)) && !(deathtype & HITTYPE_SECONDARY)) // WEAPONTODO
-               damage = self.max_health * 1.1;
-       if((DEATH_ISWEAPON(deathtype, WEP_SHOCKWAVE) || DEATH_ISWEAPON(deathtype, WEP_SHOTGUN)) && (deathtype & HITTYPE_SECONDARY))
 -      if(DEATH_ISWEAPON(deathtype, WEP_SHOTGUN))
 -      if(deathtype & HITTYPE_SECONDARY)
++      if((DEATH_ISWEAPON(deathtype, WEP_SHOCKWAVE) || DEATH_ISWEAPON(deathtype, WEP_SHOTGUN)) && (deathtype & HITTYPE_SECONDARY)) // WEAPONTODO
        {
                damage = self.max_health * 0.1;
-               force *= 15;
+               force *= 10;
        }
+       else
+               damage = self.max_health * 1.1;
  
        self.velocity += force;
  
@@@ -11,11 -11,17 +11,14 @@@ sys-post.q
  ../warpzonelib/common.qh
  ../warpzonelib/util_server.qh
  ../warpzonelib/server.qh
 -
  ../common/constants.qh
+ ../common/stats.qh
  ../common/teams.qh
  ../common/util.qh
+ ../common/nades.qh
+ ../common/buffs.qh
  ../common/test.qh
  ../common/counting.qh
 -../common/items.qh
 -../common/explosion_equation.qh
  ../common/urllib.qh
  ../common/command/markup.qh
  ../common/command/rpn.qh
@@@ -228,7 -209,15 +225,10 @@@ target_spawn.q
  func_breakable.qc
  target_music.qc
  
 -../common/items.qc
 -
+ ../common/nades.qc
+ ../common/buffs.qc
 -
 -accuracy.qc
  ../csqcmodellib/sv_model.qc
 -csqcprojectile.qc
  
  playerdemo.qc
  
Simple merge
Simple merge
index 11daf4c,0000000..3dd93c0
mode 100644,000000..100644
--- /dev/null
@@@ -1,112 -1,0 +1,114 @@@
 +.float csqcprojectile_type;
 +
 +float CSQCProjectile_SendEntity(entity to, float sf)
 +{
 +      float ft, fr;
 +
 +      // note: flag 0x08 = no trail please (teleport bit)
 +      sf = sf & 0x0F;
 +
 +      if(self.csqcprojectile_clientanimate)
 +              sf |= 0x80; // client animated, not interpolated
 +
 +      if(self.flags & FL_ONGROUND)
 +              sf |= 0x40;
 +
 +      ft = fr = 0;
 +      if(self.fade_time != 0 || self.fade_rate != 0)
 +      {
 +              ft = (self.fade_time - time) / sys_frametime;
 +              fr = (1 / self.fade_rate) / sys_frametime;
 +              if(ft <= 255 && fr <= 255 && fr >= 1)
 +                      sf |= 0x20;
 +      }
 +
 +      if(self.gravity != 0)
 +              sf |= 0x10;
 +
 +      WriteByte(MSG_ENTITY, ENT_CLIENT_PROJECTILE);
 +      WriteByte(MSG_ENTITY, sf);
 +
 +      if(sf & 1)
 +      {
 +              WriteCoord(MSG_ENTITY, self.origin_x);
 +              WriteCoord(MSG_ENTITY, self.origin_y);
 +              WriteCoord(MSG_ENTITY, self.origin_z);
 +
 +              if(sf & 0x80)
 +              {
 +                      WriteCoord(MSG_ENTITY, self.velocity_x);
 +                      WriteCoord(MSG_ENTITY, self.velocity_y);
 +                      WriteCoord(MSG_ENTITY, self.velocity_z);
 +                      if(sf & 0x10)
 +                              WriteCoord(MSG_ENTITY, self.gravity);
 +              }
 +
 +              if(sf & 0x20)
 +              {
 +                      WriteByte(MSG_ENTITY, ft);
 +                      WriteByte(MSG_ENTITY, fr);
 +              }
++
++              WriteByte(MSG_ENTITY, self.realowner.team);
 +      }
 +
 +      if(sf & 2)
 +              WriteByte(MSG_ENTITY, self.csqcprojectile_type); // TODO maybe put this into sf?
 +
 +      return 1;
 +}
 +
 +.vector csqcprojectile_oldorigin;
 +void CSQCProjectile_Check(entity e)
 +{
 +      if(e.csqcprojectile_clientanimate)
 +      if(e.flags & FL_ONGROUND)
 +      if(e.origin != e.csqcprojectile_oldorigin)
 +              UpdateCSQCProjectile(e);
 +      e.csqcprojectile_oldorigin = e.origin;
 +}
 +
 +void CSQCProjectile(entity e, float clientanimate, float type, float docull)
 +{
 +      Net_LinkEntity(e, docull, 0, CSQCProjectile_SendEntity);
 +
 +      e.csqcprojectile_clientanimate = clientanimate;
 +
 +      if(e.movetype == MOVETYPE_TOSS || e.movetype == MOVETYPE_BOUNCE)
 +      {
 +              if(e.gravity == 0)
 +                      e.gravity = 1;
 +      }
 +      else
 +              e.gravity = 0;
 +
 +      if(!sound_allowed(MSG_BROADCAST, e))
 +              type |= 0x80;
 +      e.csqcprojectile_type = type;
 +}
 +
 +void UpdateCSQCProjectile(entity e)
 +{
 +      if(e.SendEntity == CSQCProjectile_SendEntity)
 +      {
 +              // send new origin data
 +              e.SendFlags |= 0x01;
 +      }
 +// FIXME HACK
 +      else if(e.SendEntity == ItemSend)
 +      {
 +              ItemUpdate(e);
 +      }
 +// END HACK
 +}
 +
 +void UpdateCSQCProjectileAfterTeleport(entity e)
 +{
 +      if(e.SendEntity == CSQCProjectile_SendEntity)
 +      {
 +              // send new origin data
 +              e.SendFlags |= 0x01;
 +              // mark as teleported
 +              e.SendFlags |= 0x08;
 +      }
 +}
index 7c8ce35,0000000..4eb6a73
mode 100644,000000..100644
--- /dev/null
@@@ -1,938 -1,0 +1,942 @@@
-       if(self.freezetag_frozen)
 +/*
 +===========================================================================
 +
 +  CLIENT WEAPONSYSTEM CODE
 +  Bring back W_Weaponframe
 +
 +===========================================================================
 +*/
 +
 +.float weapon_frametime;
 +
 +float W_WeaponRateFactor()
 +{
 +      float t;
 +      t = 1.0 / g_weaponratefactor;
 +
++      weapon_rate = t;
++      MUTATOR_CALLHOOK(WeaponRateFactor);
++      t = weapon_rate;
++
 +      return t;
 +}
 +
 +// VorteX: static frame globals
 +const float WFRAME_DONTCHANGE = -1;
 +const float WFRAME_FIRE1 = 0;
 +const float WFRAME_FIRE2 = 1;
 +const float WFRAME_IDLE = 2;
 +const float WFRAME_RELOAD = 3;
 +.float wframe;
 +
 +void(float fr, float t, void() func) weapon_thinkf;
 +
 +float CL_Weaponentity_CustomizeEntityForClient()
 +{
 +      self.viewmodelforclient = self.owner;
 +      if(IS_SPEC(other))
 +              if(other.enemy == self.owner)
 +                      self.viewmodelforclient = other;
 +      return TRUE;
 +}
 +
 +/*
 + * supported formats:
 + *
 + * 1. simple animated model, muzzle flash handling on h_ model:
 + *    h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
 + *      tags:
 + *        shot = muzzle end (shot origin, also used for muzzle flashes)
 + *        shell = casings ejection point (must be on the right hand side of the gun)
 + *        weapon = attachment for v_tuba.md3
 + *    v_tuba.md3 - first and third person model
 + *    g_tuba.md3 - pickup model
 + *
 + * 2. simple animated model, muzzle flash handling on v_ model:
 + *    h_tuba.dpm, h_tuba.dpm.framegroups - invisible model controlling the animation
 + *      tags:
 + *        weapon = attachment for v_tuba.md3
 + *    v_tuba.md3 - first and third person model
 + *      tags:
 + *        shot = muzzle end (shot origin, also used for muzzle flashes)
 + *        shell = casings ejection point (must be on the right hand side of the gun)
 + *    g_tuba.md3 - pickup model
 + *
 + * 3. fully animated model, muzzle flash handling on h_ model:
 + *    h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
 + *      tags:
 + *        shot = muzzle end (shot origin, also used for muzzle flashes)
 + *        shell = casings ejection point (must be on the right hand side of the gun)
 + *        handle = corresponding to the origin of v_tuba.md3 (used for muzzle flashes)
 + *    v_tuba.md3 - third person model
 + *    g_tuba.md3 - pickup model
 + *
 + * 4. fully animated model, muzzle flash handling on v_ model:
 + *    h_tuba.dpm, h_tuba.dpm.framegroups - animated first person model
 + *      tags:
 + *        shot = muzzle end (shot origin)
 + *        shell = casings ejection point (must be on the right hand side of the gun)
 + *    v_tuba.md3 - third person model
 + *      tags:
 + *        shot = muzzle end (for muzzle flashes)
 + *    g_tuba.md3 - pickup model
 + */
 +
 +// writes:
 +//   self.origin, self.angles
 +//   self.weaponentity
 +//   self.movedir, self.view_ofs
 +//   attachment stuff
 +//   anim stuff
 +// to free:
 +//   call again with ""
 +//   remove the ent
 +void CL_WeaponEntity_SetModel(string name)
 +{
 +      float v_shot_idx;
 +      if (name != "")
 +      {
 +              // if there is a child entity, hide it until we're sure we use it
 +              if (self.weaponentity)
 +                      self.weaponentity.model = "";
 +              setmodel(self, strcat("models/weapons/v_", name, ".md3")); // precision set below
 +              v_shot_idx = gettagindex(self, "shot"); // used later
 +              if(!v_shot_idx)
 +                      v_shot_idx = gettagindex(self, "tag_shot");
 +
 +              setmodel(self, strcat("models/weapons/h_", name, ".iqm")); // precision set below
 +              // preset some defaults that work great for renamed zym files (which don't need an animinfo)
 +              self.anim_fire1  = animfixfps(self, '0 1 0.01', '0 0 0');
 +              self.anim_fire2  = animfixfps(self, '1 1 0.01', '0 0 0');
 +              self.anim_idle   = animfixfps(self, '2 1 0.01', '0 0 0');
 +              self.anim_reload = animfixfps(self, '3 1 0.01', '0 0 0');
 +
 +              // if we have a "weapon" tag, let's attach the v_ model to it ("invisible hand" style model)
 +              // if we don't, this is a "real" animated model
 +              if(gettagindex(self, "weapon"))
 +              {
 +                      if (!self.weaponentity)
 +                              self.weaponentity = spawn();
 +                      setmodel(self.weaponentity, strcat("models/weapons/v_", name, ".md3")); // precision does not matter
 +                      setattachment(self.weaponentity, self, "weapon");
 +              }
 +              else if(gettagindex(self, "tag_weapon"))
 +              {
 +                      if (!self.weaponentity)
 +                              self.weaponentity = spawn();
 +                      setmodel(self.weaponentity, strcat("models/weapons/v_", name, ".md3")); // precision does not matter
 +                      setattachment(self.weaponentity, self, "tag_weapon");
 +              }
 +              else
 +              {
 +                      if(self.weaponentity)
 +                              remove(self.weaponentity);
 +                      self.weaponentity = world;
 +              }
 +
 +              setorigin(self,'0 0 0');
 +              self.angles = '0 0 0';
 +              self.frame = 0;
 +              self.viewmodelforclient = world;
 +
 +              float idx;
 +
 +              if(v_shot_idx) // v_ model attached to invisible h_ model
 +              {
 +                      self.movedir = gettaginfo(self.weaponentity, v_shot_idx);
 +              }
 +              else
 +              {
 +                      idx = gettagindex(self, "shot");
 +                      if(!idx)
 +                              idx = gettagindex(self, "tag_shot");
 +                      if(idx)
 +                              self.movedir = gettaginfo(self, idx);
 +                      else
 +                      {
 +                              print("WARNING: weapon model ", self.model, " does not support the 'shot' tag, will display shots TOTALLY wrong\n");
 +                              self.movedir = '0 0 0';
 +                      }
 +              }
 +
 +              if(self.weaponentity) // v_ model attached to invisible h_ model
 +              {
 +                      idx = gettagindex(self.weaponentity, "shell");
 +                      if(!idx)
 +                              idx = gettagindex(self.weaponentity, "tag_shell");
 +                      if(idx)
 +                              self.spawnorigin = gettaginfo(self.weaponentity, idx);
 +              }
 +              else
 +                      idx = 0;
 +              if(!idx)
 +              {
 +                      idx = gettagindex(self, "shell");
 +                      if(!idx)
 +                              idx = gettagindex(self, "tag_shell");
 +                      if(idx)
 +                              self.spawnorigin = gettaginfo(self, idx);
 +                      else
 +                      {
 +                              print("WARNING: weapon model ", self.model, " does not support the 'shell' tag, will display casings wrong\n");
 +                              self.spawnorigin = self.movedir;
 +                      }
 +              }
 +
 +              if(v_shot_idx)
 +              {
 +                      self.oldorigin = '0 0 0'; // use regular attachment
 +              }
 +              else
 +              {
 +                      if(self.weaponentity)
 +                      {
 +                              idx = gettagindex(self, "weapon");
 +                              if(!idx)
 +                                      idx = gettagindex(self, "tag_weapon");
 +                      }
 +                      else
 +                      {
 +                              idx = gettagindex(self, "handle");
 +                              if(!idx)
 +                                      idx = gettagindex(self, "tag_handle");
 +                      }
 +                      if(idx)
 +                      {
 +                              self.oldorigin = self.movedir - gettaginfo(self, idx);
 +                      }
 +                      else
 +                      {
 +                              print("WARNING: weapon model ", self.model, " does not support the 'handle' tag and neither does the v_ model support the 'shot' tag, will display muzzle flashes TOTALLY wrong\n");
 +                              self.oldorigin = '0 0 0'; // there is no way to recover from this
 +                      }
 +              }
 +
 +              self.viewmodelforclient = self.owner;
 +      }
 +      else
 +      {
 +              self.model = "";
 +              if(self.weaponentity)
 +                      remove(self.weaponentity);
 +              self.weaponentity = world;
 +              self.movedir = '0 0 0';
 +              self.spawnorigin = '0 0 0';
 +              self.oldorigin = '0 0 0';
 +              self.anim_fire1  = '0 1 0.01';
 +              self.anim_fire2  = '0 1 0.01';
 +              self.anim_idle   = '0 1 0.01';
 +              self.anim_reload = '0 1 0.01';
 +      }
 +
 +      self.view_ofs = '0 0 0';
 +
 +      if(self.movedir_x >= 0)
 +      {
 +              vector v0;
 +              v0 = self.movedir;
 +              self.movedir = shotorg_adjust(v0, FALSE, FALSE);
 +              self.view_ofs = shotorg_adjust(v0, FALSE, TRUE) - v0;
 +      }
 +      self.owner.stat_shotorg = compressShotOrigin(self.movedir);
 +      self.movedir = decompressShotOrigin(self.owner.stat_shotorg); // make them match perfectly
 +
 +      self.spawnorigin += self.view_ofs; // offset the casings origin by the same amount
 +
 +      // check if an instant weapon switch occurred
 +      setorigin(self, self.view_ofs);
 +      // reset animstate now
 +      self.wframe = WFRAME_IDLE;
 +      setanim(self, self.anim_idle, TRUE, FALSE, TRUE);
 +}
 +
 +vector CL_Weapon_GetShotOrg(float wpn)
 +{
 +      entity wi, oldself;
 +      vector ret;
 +      wi = get_weaponinfo(wpn);
 +      oldself = self;
 +      self = spawn();
 +      CL_WeaponEntity_SetModel(wi.mdl);
 +      ret = self.movedir;
 +      CL_WeaponEntity_SetModel("");
 +      remove(self);
 +      self = oldself;
 +      return ret;
 +}
 +
 +void CL_Weaponentity_Think()
 +{
 +      float tb;
 +      self.nextthink = time;
 +      if (intermission_running)
 +              self.frame = self.anim_idle_x;
 +      if (self.owner.weaponentity != self)
 +      {
 +              if (self.weaponentity)
 +                      remove(self.weaponentity);
 +              remove(self);
 +              return;
 +      }
 +      if (self.owner.deadflag != DEAD_NO)
 +      {
 +              self.model = "";
 +              if (self.weaponentity)
 +                      self.weaponentity.model = "";
 +              return;
 +      }
 +      if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
 +      {
 +              self.weaponname = self.owner.weaponname;
 +              self.dmg = self.owner.modelindex;
 +              self.deadflag = self.owner.deadflag;
 +
 +              CL_WeaponEntity_SetModel(self.owner.weaponname);
 +      }
 +
 +      tb = (self.effects & (EF_TELEPORT_BIT | EF_RESTARTANIM_BIT));
 +      self.effects = self.owner.effects & EFMASK_CHEAP;
 +      self.effects &= ~EF_LOWPRECISION;
 +      self.effects &= ~EF_FULLBRIGHT; // can mask team color, so get rid of it
 +      self.effects &= ~EF_TELEPORT_BIT;
 +      self.effects &= ~EF_RESTARTANIM_BIT;
 +      self.effects |= tb;
 +
 +      if(self.owner.alpha == default_player_alpha)
 +              self.alpha = default_weapon_alpha;
 +      else if(self.owner.alpha != 0)
 +              self.alpha = self.owner.alpha;
 +      else
 +              self.alpha = 1;
 +
 +      self.glowmod = self.owner.weaponentity_glowmod;
 +      self.colormap = self.owner.colormap;
 +      if (self.weaponentity)
 +      {
 +              self.weaponentity.effects = self.effects;
 +              self.weaponentity.alpha = self.alpha;
 +              self.weaponentity.colormap = self.colormap;
 +              self.weaponentity.glowmod = self.glowmod;
 +      }
 +
 +      self.angles = '0 0 0';
 +
 +      float f = (self.owner.weapon_nextthink - time);
 +      if (self.state == WS_RAISE && !intermission_running)
 +      {
 +              entity newwep = get_weaponinfo(self.owner.switchweapon);
 +              f = f * g_weaponratefactor / max(f, newwep.switchdelay_raise);
 +              self.angles_x = -90 * f * f;
 +      }
 +      else if (self.state == WS_DROP && !intermission_running)
 +      {
 +              entity oldwep = get_weaponinfo(self.owner.weapon);
 +              f = 1 - f * g_weaponratefactor / max(f, oldwep.switchdelay_drop);
 +              self.angles_x = -90 * f * f;
 +      }
 +      else if (self.state == WS_CLEAR)
 +      {
 +              f = 1;
 +              self.angles_x = -90 * f * f;
 +      }
 +}
 +
 +void CL_ExteriorWeaponentity_Think()
 +{
 +      float tag_found;
 +      self.nextthink = time;
 +      if (self.owner.exteriorweaponentity != self)
 +      {
 +              remove(self);
 +              return;
 +      }
 +      if (self.owner.deadflag != DEAD_NO)
 +      {
 +              self.model = "";
 +              return;
 +      }
 +      if (self.weaponname != self.owner.weaponname || self.dmg != self.owner.modelindex || self.deadflag != self.owner.deadflag)
 +      {
 +              self.weaponname = self.owner.weaponname;
 +              self.dmg = self.owner.modelindex;
 +              self.deadflag = self.owner.deadflag;
 +              if (self.owner.weaponname != "")
 +                      setmodel(self, strcat("models/weapons/v_", self.owner.weaponname, ".md3")); // precision set below
 +              else
 +                      self.model = "";
 +
 +              if((tag_found = gettagindex(self.owner, "tag_weapon")))
 +              {
 +                      self.tag_index = tag_found;
 +                      self.tag_entity = self.owner;
 +              }
 +              else
 +                      setattachment(self, self.owner, "bip01 r hand");
 +      }
 +      self.effects = self.owner.effects;
 +      self.effects |= EF_LOWPRECISION;
 +      self.effects = self.effects & EFMASK_CHEAP; // eat performance
 +      if(self.owner.alpha == default_player_alpha)
 +              self.alpha = default_weapon_alpha;
 +      else if(self.owner.alpha != 0)
 +              self.alpha = self.owner.alpha;
 +      else
 +              self.alpha = 1;
 +
 +      self.glowmod = self.owner.weaponentity_glowmod;
 +      self.colormap = self.owner.colormap;
 +
 +      CSQCMODEL_AUTOUPDATE();
 +}
 +
 +// spawning weaponentity for client
 +void CL_SpawnWeaponentity()
 +{
 +      self.weaponentity = spawn();
 +      self.weaponentity.classname = "weaponentity";
 +      self.weaponentity.solid = SOLID_NOT;
 +      self.weaponentity.owner = self;
 +      setmodel(self.weaponentity, ""); // precision set when changed
 +      setorigin(self.weaponentity, '0 0 0');
 +      self.weaponentity.angles = '0 0 0';
 +      self.weaponentity.viewmodelforclient = self;
 +      self.weaponentity.flags = 0;
 +      self.weaponentity.think = CL_Weaponentity_Think;
 +      self.weaponentity.customizeentityforclient = CL_Weaponentity_CustomizeEntityForClient;
 +      self.weaponentity.nextthink = time;
 +
 +      self.exteriorweaponentity = spawn();
 +      self.exteriorweaponentity.classname = "exteriorweaponentity";
 +      self.exteriorweaponentity.solid = SOLID_NOT;
 +      self.exteriorweaponentity.exteriorweaponentity = self.exteriorweaponentity;
 +      self.exteriorweaponentity.owner = self;
 +      setorigin(self.exteriorweaponentity, '0 0 0');
 +      self.exteriorweaponentity.angles = '0 0 0';
 +      self.exteriorweaponentity.think = CL_ExteriorWeaponentity_Think;
 +      self.exteriorweaponentity.nextthink = time;
 +
 +      {
 +              entity oldself = self;
 +              self = self.exteriorweaponentity;
 +              CSQCMODEL_AUTOINIT();
 +              self = oldself;
 +      }
 +}
 +
 +// Weapon subs
 +void w_clear()
 +{
 +      if (self.weapon != -1)
 +      {
 +              self.weapon = 0;
 +              self.switchingweapon = 0;
 +      }
 +      if (self.weaponentity)
 +      {
 +              self.weaponentity.state = WS_CLEAR;
 +              self.weaponentity.effects = 0;
 +      }
 +}
 +
 +void w_ready()
 +{
 +      if (self.weaponentity)
 +              self.weaponentity.state = WS_READY;
 +      weapon_thinkf(WFRAME_IDLE, 1000000, w_ready);
 +}
 +
 +.float prevdryfire;
 +.float prevwarntime;
 +float weapon_prepareattack_checkammo(float secondary)
 +{
 +      if (!(self.items & IT_UNLIMITED_WEAPON_AMMO))
 +      if (!WEP_ACTION(self.weapon, WR_CHECKAMMO1 + secondary))
 +      {
 +              // always keep the Mine Layer if we placed mines, so that we can detonate them
 +              entity mine;
 +              if(self.weapon == WEP_MINE_LAYER)
 +              for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
 +                      return FALSE;
 +
 +              if(self.weapon == self.switchweapon && time - self.prevdryfire > 1) // only play once BEFORE starting to switch weapons
 +              {
 +                      sound (self, CH_WEAPON_A, "weapons/dryfire.wav", VOL_BASE, ATTEN_NORM);
 +                      self.prevdryfire = time;
 +              }
 +
 +              if(WEP_ACTION(self.weapon, WR_CHECKAMMO2 - secondary)) // check if the other firing mode has enough ammo
 +              {
 +                      if(time - self.prevwarntime > 1)
 +                      {
 +                              Send_Notification(
 +                                      NOTIF_ONE,
 +                                      self,
 +                                      MSG_MULTI,
 +                                      ITEM_WEAPON_PRIMORSEC,
 +                                      self.weapon,
 +                                      secondary,
 +                                      (1 - secondary)
 +                              );
 +                      }
 +                      self.prevwarntime = time;
 +              }
 +              else // this weapon is totally unable to fire, switch to another one
 +              {
 +                      W_SwitchToOtherWeapon(self);
 +              }
 +
 +              return FALSE;
 +      }
 +      return TRUE;
 +}
 +.float race_penalty;
 +float weapon_prepareattack_check(float secondary, float attacktime)
 +{
 +      if(!weapon_prepareattack_checkammo(secondary))
 +              return FALSE;
 +
 +      //if sv_ready_restart_after_countdown is set, don't allow the player to shoot
 +      //if all players readied up and the countdown is running
 +      if(time < game_starttime || time < self.race_penalty) {
 +              return FALSE;
 +      }
 +
 +      if (timeout_status == TIMEOUT_ACTIVE) //don't allow the player to shoot while game is paused
 +              return FALSE;
 +
 +      // do not even think about shooting if switching
 +      if(self.switchweapon != self.weapon)
 +              return FALSE;
 +
 +      if(attacktime >= 0)
 +      {
 +              // don't fire if previous attack is not finished
 +              if (ATTACK_FINISHED(self) > time + self.weapon_frametime * 0.5)
 +                      return FALSE;
 +              // don't fire while changing weapon
 +              if (self.weaponentity.state != WS_READY)
 +                      return FALSE;
 +      }
 +
 +      return TRUE;
 +}
 +float weapon_prepareattack_do(float secondary, float attacktime)
 +{
 +      self.weaponentity.state = WS_INUSE;
 +
 +      self.spawnshieldtime = min(self.spawnshieldtime, time); // kill spawn shield when you fire
 +
 +      // if the weapon hasn't been firing continuously, reset the timer
 +      if(attacktime >= 0)
 +      {
 +              if (ATTACK_FINISHED(self) < time - self.weapon_frametime * 1.5)
 +              {
 +                      ATTACK_FINISHED(self) = time;
 +                      //dprint("resetting attack finished to ", ftos(time), "\n");
 +              }
 +              ATTACK_FINISHED(self) = ATTACK_FINISHED(self) + attacktime * W_WeaponRateFactor();
 +      }
 +      self.bulletcounter += 1;
 +      //dprint("attack finished ", ftos(ATTACK_FINISHED(self)), "\n");
 +      return TRUE;
 +}
 +float weapon_prepareattack(float secondary, float attacktime)
 +{
 +      if(weapon_prepareattack_check(secondary, attacktime))
 +      {
 +              weapon_prepareattack_do(secondary, attacktime);
 +              return TRUE;
 +      }
 +      else
 +              return FALSE;
 +}
 +
 +void weapon_thinkf(float fr, float t, void() func)
 +{
 +      vector a;
 +      vector of, or, ou;
 +      float restartanim;
 +
 +      if(fr == WFRAME_DONTCHANGE)
 +      {
 +              fr = self.weaponentity.wframe;
 +              restartanim = FALSE;
 +      }
 +      else if (fr == WFRAME_IDLE)
 +              restartanim = FALSE;
 +      else
 +              restartanim = TRUE;
 +
 +      of = v_forward;
 +      or = v_right;
 +      ou = v_up;
 +
 +      if (self.weaponentity)
 +      {
 +              self.weaponentity.wframe = fr;
 +              a = '0 0 0';
 +              if (fr == WFRAME_IDLE)
 +                      a = self.weaponentity.anim_idle;
 +              else if (fr == WFRAME_FIRE1)
 +                      a = self.weaponentity.anim_fire1;
 +              else if (fr == WFRAME_FIRE2)
 +                      a = self.weaponentity.anim_fire2;
 +              else // if (fr == WFRAME_RELOAD)
 +                      a = self.weaponentity.anim_reload;
 +              a_z *= g_weaponratefactor;
 +              setanim(self.weaponentity, a, restartanim == FALSE, restartanim, restartanim);
 +      }
 +
 +      v_forward = of;
 +      v_right = or;
 +      v_up = ou;
 +
 +      if(self.weapon_think == w_ready && func != w_ready && self.weaponentity.state == WS_RAISE)
 +      {
 +              backtrace("Tried to override initial weapon think function - should this really happen?");
 +      }
 +
 +      t *= W_WeaponRateFactor();
 +
 +      // VorteX: haste can be added here
 +      if (self.weapon_think == w_ready)
 +      {
 +              self.weapon_nextthink = time;
 +              //dprint("started firing at ", ftos(time), "\n");
 +      }
 +      if (self.weapon_nextthink < time - self.weapon_frametime * 1.5 || self.weapon_nextthink > time + self.weapon_frametime * 1.5)
 +      {
 +              self.weapon_nextthink = time;
 +              //dprint("reset weapon animation timer at ", ftos(time), "\n");
 +      }
 +      self.weapon_nextthink = self.weapon_nextthink + t;
 +      self.weapon_think = func;
 +      //dprint("next ", ftos(self.weapon_nextthink), "\n");
 +
 +      if((fr == WFRAME_FIRE1 || fr == WFRAME_FIRE2) && t)
 +      {
 +              if((self.weapon == WEP_SHOCKWAVE || self.weapon == WEP_SHOTGUN) && fr == WFRAME_FIRE2)
 +                      animdecide_setaction(self, ANIMACTION_MELEE, restartanim);
 +              else
 +                      animdecide_setaction(self, ANIMACTION_SHOOT, restartanim);
 +      }
 +      else
 +      {
 +              if(self.anim_upper_action == ANIMACTION_SHOOT || self.anim_upper_action == ANIMACTION_MELEE)
 +                      self.anim_upper_action = 0;
 +      }
 +}
 +
 +float forbidWeaponUse()
 +{
 +      if(time < game_starttime && !autocvar_sv_ready_restart_after_countdown)
 +              return 1;
 +      if(round_handler_IsActive() && !round_handler_IsRoundStarted())
 +              return 1;
 +      if(self.player_blocked)
 +              return 1;
++      if(self.frozen)
 +              return 1;
 +      return 0;
 +}
 +
 +void W_WeaponFrame()
 +{
 +      vector fo, ri, up;
 +
 +      if (frametime)
 +              self.weapon_frametime = frametime;
 +
 +      if (!self.weaponentity || self.health < 1)
 +              return; // Dead player can't use weapons and injure impulse commands
 +
 +      if(forbidWeaponUse())
 +      if(self.weaponentity.state != WS_CLEAR)
 +      {
 +              w_ready();
 +              return;
 +      }
 +
 +      if(!self.switchweapon)
 +      {
 +              self.weapon = 0;
 +              self.switchingweapon = 0;
 +              self.weaponentity.state = WS_CLEAR;
 +              self.weaponname = "";
 +              //self.items &= ~IT_AMMO;
 +              return;
 +      }
 +
 +      makevectors(self.v_angle);
 +      fo = v_forward; // save them in case the weapon think functions change it
 +      ri = v_right;
 +      up = v_up;
 +
 +      // Change weapon
 +      if (self.weapon != self.switchweapon)
 +      {
 +              if (self.weaponentity.state == WS_CLEAR)
 +              {
 +                      // end switching!
 +                      self.switchingweapon = self.switchweapon;
 +                      entity newwep = get_weaponinfo(self.switchweapon);
 +
 +                      // the two weapon entities will notice this has changed and update their models
 +                      self.weapon = self.switchweapon;
 +                      self.weaponname = newwep.mdl;
 +                      self.bulletcounter = 0;
 +                      //self.ammo_field = newwep.ammo_field;
 +                      WEP_ACTION(self.switchweapon, WR_SETUP);
 +                      self.weaponentity.state = WS_RAISE;
 +
 +                      // set our clip load to the load of the weapon we switched to, if it's reloadable
 +                      if(newwep.spawnflags & WEP_FLAG_RELOADABLE && newwep.reloading_ammo) // prevent accessing undefined cvars
 +                      {
 +                              self.clip_load = self.(weapon_load[self.switchweapon]);
 +                              self.clip_size = newwep.reloading_ammo;
 +                      }
 +                      else
 +                              self.clip_load = self.clip_size = 0;
 +
 +                      weapon_thinkf(WFRAME_IDLE, newwep.switchdelay_raise, w_ready);
 +              }
 +              else if (self.weaponentity.state == WS_DROP)
 +              {
 +                      // in dropping phase we can switch at any time
 +                      self.switchingweapon = self.switchweapon;
 +              }
 +              else if (self.weaponentity.state == WS_READY)
 +              {
 +                      // start switching!
 +                      self.switchingweapon = self.switchweapon;
 +                      entity oldwep = get_weaponinfo(self.weapon);
 +
 +                      // set up weapon switch think in the future, and start drop anim
 +                      #ifndef INDEPENDENT_ATTACK_FINISHED
 +                      if(ATTACK_FINISHED(self) <= time + self.weapon_frametime * 0.5)
 +                      {
 +                      #endif
 +                              sound(self, CH_WEAPON_SINGLE, "weapons/weapon_switch.wav", VOL_BASE, ATTN_NORM);
 +                              self.weaponentity.state = WS_DROP;
 +                              weapon_thinkf(WFRAME_DONTCHANGE, oldwep.switchdelay_drop, w_clear);
 +                      #ifndef INDEPENDENT_ATTACK_FINISHED
 +                      }
 +                      #endif
 +              }
 +      }
 +
 +      // LordHavoc: network timing test code
 +      //if (self.button0)
 +      //      print(ftos(frametime), " ", ftos(time), " >= ", ftos(ATTACK_FINISHED(self)), " >= ", ftos(self.weapon_nextthink), "\n");
 +
 +      float w;
 +      w = self.weapon;
 +
 +      // call the think code which may fire the weapon
 +      // and do so multiple times to resolve framerate dependency issues if the
 +      // server framerate is very low and the weapon fire rate very high
 +      float c;
 +      c = 0;
 +      while (c < W_TICSPERFRAME)
 +      {
 +              c = c + 1;
 +              if(w && !(self.weapons & WepSet_FromWeapon(w)))
 +              {
 +                      if(self.weapon == self.switchweapon)
 +                              W_SwitchWeapon_Force(self, w_getbestweapon(self));
 +                      w = 0;
 +              }
 +
 +              v_forward = fo;
 +              v_right = ri;
 +              v_up = up;
 +
 +              if(w)
 +                      WEP_ACTION(self.weapon, WR_THINK);
 +              else
 +                      WEP_ACTION(self.weapon, WR_GONETHINK);
 +
 +              if (time + self.weapon_frametime * 0.5 >= self.weapon_nextthink)
 +              {
 +                      if(self.weapon_think)
 +                      {
 +                              v_forward = fo;
 +                              v_right = ri;
 +                              v_up = up;
 +                              self.weapon_think();
 +                      }
 +                      else
 +                              bprint("\{1}^1ERROR: undefined weapon think function for ", self.netname, "\n");
 +              }
 +      }
 +}
 +
 +void W_AttachToShotorg(entity flash, vector offset)
 +{
 +      entity xflash;
 +      flash.owner = self;
 +      flash.angles_z = random() * 360;
 +
 +      if(gettagindex(self.weaponentity, "shot"))
 +              setattachment(flash, self.weaponentity, "shot");
 +      else
 +              setattachment(flash, self.weaponentity, "tag_shot");
 +      setorigin(flash, offset);
 +
 +      xflash = spawn();
 +      copyentity(flash, xflash);
 +
 +      flash.viewmodelforclient = self;
 +
 +      if(self.weaponentity.oldorigin_x > 0)
 +      {
 +              setattachment(xflash, self.exteriorweaponentity, "");
 +              setorigin(xflash, self.weaponentity.oldorigin + offset);
 +      }
 +      else
 +      {
 +              if(gettagindex(self.exteriorweaponentity, "shot"))
 +                      setattachment(xflash, self.exteriorweaponentity, "shot");
 +              else
 +                      setattachment(xflash, self.exteriorweaponentity, "tag_shot");
 +              setorigin(xflash, offset);
 +      }
 +}
 +
 +void W_DecreaseAmmo(float ammo_use)
 +{
 +      entity wep = get_weaponinfo(self.weapon);
 +
 +      if((self.items & IT_UNLIMITED_WEAPON_AMMO) && !wep.reloading_ammo)
 +              return;
 +
 +      // if this weapon is reloadable, decrease its load. Else decrease the player's ammo
 +      if(wep.reloading_ammo)
 +      {
 +              self.clip_load -= ammo_use;
 +              self.(weapon_load[self.weapon]) = self.clip_load;
 +      }
 +      else if(wep.ammo_field != ammo_none)
 +      {
 +              self.(wep.ammo_field) -= ammo_use;
 +              if(self.(wep.ammo_field) < 0)
 +              {
 +                      backtrace(sprintf(
 +                              "W_DecreaseAmmo(%.2f): '%s' subtracted too much %s from '%s', resulting with '%.2f' left... "
 +                              "Please notify Samual immediately with a copy of this backtrace!\n",
 +                              ammo_use,
 +                              wep.netname,
 +                              GetAmmoPicture(wep.ammo_field),
 +                              self.netname,
 +                              self.(wep.ammo_field)
 +                      ));
 +              }
 +      }
 +}
 +
 +// weapon reloading code
 +
 +.float reload_ammo_amount, reload_ammo_min, reload_time;
 +.float reload_complain;
 +.string reload_sound;
 +
 +void W_ReloadedAndReady()
 +{
 +      // finish the reloading process, and do the ammo transfer
 +
 +      self.clip_load = self.old_clip_load; // restore the ammo counter, in case we still had ammo in the weapon before reloading
 +
 +      // if the gun uses no ammo, max out weapon load, else decrease ammo as we increase weapon load
 +      if(!self.reload_ammo_min || self.items & IT_UNLIMITED_WEAPON_AMMO || self.ammo_field == ammo_none)
 +              self.clip_load = self.reload_ammo_amount;
 +      else
 +      {
 +              while(self.clip_load < self.reload_ammo_amount && self.(self.ammo_field)) // make sure we don't add more ammo than we have
 +              {
 +                      self.clip_load += 1;
 +                      self.(self.ammo_field) -= 1;
 +              }
 +      }
 +      self.(weapon_load[self.weapon]) = self.clip_load;
 +
 +      // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
 +      // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
 +      // so your weapon is disabled for a few seconds without reason
 +
 +      //ATTACK_FINISHED(self) -= self.reload_time - 1;
 +
 +      w_ready();
 +}
 +
 +void W_Reload(float sent_ammo_min, string sent_sound)
 +{
 +      // set global values to work with
 +      entity e;
 +      e = get_weaponinfo(self.weapon);
 +
 +      self.reload_ammo_min = sent_ammo_min;
 +      self.reload_ammo_amount = e.reloading_ammo;;
 +      self.reload_time = e.reloading_time;
 +      self.reload_sound = sent_sound;
 +
 +      // don't reload weapons that don't have the RELOADABLE flag
 +      if (!(e.spawnflags & WEP_FLAG_RELOADABLE))
 +      {
 +              dprint("Warning: Attempted to reload a weapon that does not have the WEP_FLAG_RELOADABLE flag. Fix your code!\n");
 +              return;
 +      }
 +
 +      // return if reloading is disabled for this weapon
 +      if(!self.reload_ammo_amount)
 +              return;
 +
 +      // our weapon is fully loaded, no need to reload
 +      if (self.clip_load >= self.reload_ammo_amount)
 +              return;
 +
 +      // no ammo, so nothing to load
 +      if(self.ammo_field != ammo_none)
 +      if(!self.(self.ammo_field) && self.reload_ammo_min)
 +      if (!(self.items & IT_UNLIMITED_WEAPON_AMMO))
 +      {
 +              if(IS_REAL_CLIENT(self) && self.reload_complain < time)
 +              {
 +                      play2(self, "weapons/unavailable.wav");
 +                      sprint(self, strcat("You don't have enough ammo to reload the ^2", WEP_NAME(self.weapon), "\n"));
 +                      self.reload_complain = time + 1;
 +              }
 +              // switch away if the amount of ammo is not enough to keep using this weapon
 +              if (!(WEP_ACTION(self.weapon, WR_CHECKAMMO1) + WEP_ACTION(self.weapon, WR_CHECKAMMO2)))
 +              {
 +                      self.clip_load = -1; // reload later
 +                      W_SwitchToOtherWeapon(self);
 +              }
 +              return;
 +      }
 +
 +      if (self.weaponentity)
 +      {
 +              if (self.weaponentity.wframe == WFRAME_RELOAD)
 +                      return;
 +
 +              // allow switching away while reloading, but this will cause a new reload!
 +              self.weaponentity.state = WS_READY;
 +      }
 +
 +      // now begin the reloading process
 +
 +      sound(self, CH_WEAPON_SINGLE, self.reload_sound, VOL_BASE, ATTEN_NORM);
 +
 +      // do not set ATTACK_FINISHED in reload code any more. This causes annoying delays if eg: You start reloading a weapon,
 +      // then quickly switch to another weapon and back. Reloading is canceled, but the reload delay is still there,
 +      // so your weapon is disabled for a few seconds without reason
 +
 +      //ATTACK_FINISHED(self) = max(time, ATTACK_FINISHED(self)) + self.reload_time + 1;
 +
 +      weapon_thinkf(WFRAME_RELOAD, self.reload_time, W_ReloadedAndReady);
 +
 +      if(self.clip_load < 0)
 +              self.clip_load = 0;
 +      self.old_clip_load = self.clip_load;
 +      self.clip_load = self.(weapon_load[self.weapon]) = -1;
 +}